diff --git a/.env.example b/.env.example index 4bdc5535b1..1b06de63c5 100644 --- a/.env.example +++ b/.env.example @@ -1,10 +1,9 @@ -NEXT_PUBLIC_SENTRY_DSN=https://sentry.io -SENTRY_CSP_REPORT_URI=https://sentry.io +NEXT_PUBLIC_ROLLBAR_CLIENT_TOKEN=xxx NEXT_PUBLIC_WALLET_CONNECT_PROJECT_ID=xxx NEXT_PUBLIC_RE_CAPTCHA_APP_SITE_KEY=xxx NEXT_PUBLIC_GOOGLE_ANALYTICS_PROPERTY_ID=UA-XXXXXX-X NEXT_PUBLIC_MIXPANEL_PROJECT_TOKEN=xxx NEXT_PUBLIC_GROWTH_BOOK_CLIENT_KEY=xxx NEXT_PUBLIC_AUTH0_CLIENT_ID=xxx -FAVICON_GENERATOR_API_KEY=xxx -NEXT_PUBLIC_GROWTH_BOOK_CLIENT_KEY=xxx \ No newline at end of file +NEXT_PUBLIC_GROWTH_BOOK_CLIENT_KEY=xxx +NEXT_PUBLIC_MARKETPLACE_RATING_AIRTABLE_API_KEY=xxx \ No newline at end of file diff --git a/.eslintignore b/.eslintignore deleted file mode 100644 index 37523e907c..0000000000 --- a/.eslintignore +++ /dev/null @@ -1,8 +0,0 @@ -node_modules -node_modules_linux - -playwright/envs.js -deploy/tools/envs-validator/index.js -deploy/tools/feature-reporter/build/** -deploy/tools/feature-reporter/index.js -public/** \ No newline at end of file diff --git a/.github/workflows/chakra-npm-publisher.yml b/.github/workflows/chakra-npm-publisher.yml new file mode 100644 index 0000000000..4ab143035a --- /dev/null +++ b/.github/workflows/chakra-npm-publisher.yml @@ -0,0 +1,52 @@ +name: Publish Chakra theme package to NPM + +on: + workflow_dispatch: + inputs: + version: + description: Package version + type: string + required: true + workflow_call: + inputs: + version: + description: Package version + type: string + required: true + +jobs: + publish: + runs-on: ubuntu-latest + name: Publish package to NPM registry + permissions: + id-token: write + + steps: + - name: Checkout repo + uses: actions/checkout@v4 + + # Also it will setup .npmrc file to publish to npm + - name: Setup node + uses: actions/setup-node@v4 + with: + node-version: '22.11.0' + registry-url: 'https://registry.npmjs.org' + + - name: Update package version + run: | + cd ./theme + npm version ${{ inputs.version }} + + - name: Build the package + run: | + yarn + cd ./theme + yarn + yarn build + + - name: Publish to NPM registry + run: | + cd ./theme + npm publish --provenance --access public + env: + NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} diff --git a/.github/workflows/checks.yml b/.github/workflows/checks.yml index 028cab6e11..54aa4a141c 100644 --- a/.github/workflows/checks.yml +++ b/.github/workflows/checks.yml @@ -30,7 +30,7 @@ jobs: - name: Setup node uses: actions/setup-node@v4 with: - node-version: 20.11.0 + node-version: 22.11.0 cache: 'yarn' - name: Cache node_modules @@ -43,7 +43,7 @@ jobs: - name: Install dependencies if: steps.cache-node-modules.outputs.cache-hit != 'true' - run: yarn --frozen-lockfile --ignore-optional + run: yarn --frozen-lockfile - name: Run ESLint run: yarn lint:eslint @@ -62,7 +62,7 @@ jobs: - name: Setup node uses: actions/setup-node@v4 with: - node-version: 20.11.0 + node-version: 22.11.0 cache: 'yarn' - name: Cache node_modules @@ -75,10 +75,10 @@ jobs: - name: Install dependencies if: steps.cache-node-modules.outputs.cache-hit != 'true' - run: yarn --frozen-lockfile --ignore-optional + run: yarn --frozen-lockfile - name: Install script dependencies - run: cd ./deploy/tools/envs-validator && yarn --frozen-lockfile --ignore-optional + run: cd ./deploy/tools/envs-validator && yarn --frozen-lockfile - name: Run validation tests run: | @@ -95,11 +95,13 @@ jobs: steps: - name: Checkout repo uses: actions/checkout@v4 + with: + fetch-depth: 0 - name: Setup node uses: actions/setup-node@v4 with: - node-version: 20.11.0 + node-version: 22.11.0 cache: 'yarn' - name: Cache node_modules @@ -112,17 +114,64 @@ jobs: - name: Install dependencies if: steps.cache-node-modules.outputs.cache-hit != 'true' - run: yarn --frozen-lockfile --ignore-optional + run: yarn --frozen-lockfile - name: Run Jest - run: yarn test:jest + run: yarn test:jest ${{ github.event_name == 'pull_request' && '--changedSince=origin/main' || '' }} --passWithNoTests + + pw_affected_tests: + name: Resolve affected Playwright tests + runs-on: ubuntu-latest + needs: [ code_quality, envs_validation ] + if: github.event_name == 'pull_request' + steps: + - name: Checkout repo + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Setup node + uses: actions/setup-node@v4 + with: + node-version: 22.11.0 + cache: 'yarn' + + - name: Cache node_modules + uses: actions/cache@v4 + id: cache-node-modules + with: + path: | + node_modules + key: node_modules-${{ runner.os }}-${{ hashFiles('yarn.lock') }} + + - name: Install dependencies + if: steps.cache-node-modules.outputs.cache-hit != 'true' + run: yarn --frozen-lockfile + + - name: Install script dependencies + run: cd ./deploy/tools/affected-tests && yarn --frozen-lockfile + + - name: Run script + run: yarn test:pw:detect-affected + + - name: Upload result file + uses: actions/upload-artifact@v4 + with: + name: playwright-affected-tests + path: ./playwright/affected-tests.txt + retention-days: 3 pw_tests: name: 'Playwright tests / Project: ${{ matrix.project }}' - needs: [ code_quality, envs_validation ] + needs: [ code_quality, envs_validation, pw_affected_tests ] + if: | + always() && + needs.code_quality.result == 'success' && + needs.envs_validation.result == 'success' && + (needs.pw_affected_tests.result == 'success' || needs.pw_affected_tests.result == 'skipped') runs-on: ubuntu-latest container: - image: mcr.microsoft.com/playwright:v1.41.1-focal + image: mcr.microsoft.com/playwright:v1.49.0-noble strategy: fail-fast: false @@ -141,7 +190,7 @@ jobs: - name: Setup node uses: actions/setup-node@v4 with: - node-version: 20.11.0 + node-version: 22.11.0 cache: 'yarn' - name: Cache node_modules @@ -154,10 +203,18 @@ jobs: - name: Install dependencies if: steps.cache-node-modules.outputs.cache-hit != 'true' - run: yarn --frozen-lockfile --ignore-optional + run: yarn --frozen-lockfile + + - name: Download affected tests list + if: ${{ needs.pw_affected_tests.result == 'success' }} + uses: actions/download-artifact@v4 + continue-on-error: true + with: + name: playwright-affected-tests + path: ./playwright - name: Run PlayWright - run: yarn test:pw:ci + run: yarn test:pw:ci --affected=${{ github.event_name == 'pull_request' }} --pass-with-no-tests env: HOME: /root PW_PROJECT: ${{ matrix.project }} diff --git a/.github/workflows/copy-issues-labels.yml b/.github/workflows/copy-issues-labels.yml new file mode 100644 index 0000000000..e05b6e88ee --- /dev/null +++ b/.github/workflows/copy-issues-labels.yml @@ -0,0 +1,116 @@ +name: Copy issues labels to pull request + +on: + workflow_dispatch: + inputs: + pr_number: + description: Pull request number + required: true + type: string + issues: + description: JSON encoded list of issue ids + required: true + type: string + workflow_call: + inputs: + pr_number: + description: Pull request number + required: true + type: string + issues: + description: JSON encoded list of issue ids + required: true + type: string + +jobs: + run: + name: Run + runs-on: ubuntu-latest + steps: + - name: Find unique labels + id: find_unique_labels + uses: actions/github-script@v7 + env: + ISSUES: ${{ inputs.issues }} + with: + script: | + const issues = JSON.parse(process.env.ISSUES); + + const WHITE_LISTED_LABELS = [ + 'client feature', + 'feature', + + 'bug', + + 'dependencies', + 'performance', + + 'chore', + 'enhancement', + 'refactoring', + 'tech', + 'ENVs', + ] + + const labels = await Promise.all(issues.map(getIssueLabels)); + const uniqueLabels = uniqueStringArray(labels.flat().filter((label) => WHITE_LISTED_LABELS.includes(label))); + + if (uniqueLabels.length === 0) { + core.info('No labels found.\n'); + return []; + } + + core.info(`Found following labels: ${ uniqueLabels.join(', ') }.\n`); + return uniqueLabels; + + async function getIssueLabels(issue) { + core.info(`Obtaining labels list for the issue #${ issue }...`); + + try { + const response = await github.request('GET /repos/{owner}/{repo}/issues/{issue_number}/labels', { + owner: 'blockscout', + repo: 'frontend', + issue_number: issue, + }); + return response.data.map(({ name }) => name); + } catch (error) { + core.error(`Failed to obtain labels for the issue #${ issue }: ${ error.message }`); + return []; + } + } + + function uniqueStringArray(array) { + return Array.from(new Set(array)); + } + + - name: Update pull request labels + id: update_pr_labels + uses: actions/github-script@v7 + env: + LABELS: ${{ steps.find_unique_labels.outputs.result }} + PR_NUMBER: ${{ inputs.pr_number }} + with: + script: | + const labels = JSON.parse(process.env.LABELS); + const prNumber = Number(process.env.PR_NUMBER); + + if (labels.length === 0) { + core.info('Nothing to update.\n'); + return; + } + + for (const label of labels) { + await addLabelToPr(prNumber, label); + } + core.info('Done.\n'); + + async function addLabelToPr(prNumber, label) { + console.log(`Adding label to the pull request #${ prNumber }...`); + + return await github.request('POST /repos/{owner}/{repo}/issues/{issue_number}/labels', { + owner: 'blockscout', + repo: 'frontend', + issue_number: prNumber, + labels: [ label ], + }); + } \ No newline at end of file diff --git a/.github/workflows/deploy-main.yml b/.github/workflows/deploy-main.yml index 087934c334..73bdd72cf7 100644 --- a/.github/workflows/deploy-main.yml +++ b/.github/workflows/deploy-main.yml @@ -4,6 +4,16 @@ on: push: branches: - main + paths-ignore: + - '.github/ISSUE_TEMPLATE/**' + - '.husky/**' + - '.vscode/**' + - 'docs/**' + - 'jest/**' + - 'mocks/**' + - 'playwright/**' + - 'stubs/**' + - 'tools/**' workflow_dispatch: concurrency: @@ -15,27 +25,3 @@ jobs: name: Publish Docker image uses: './.github/workflows/publish-image.yml' secrets: inherit - - deploy_main: - name: Deploy frontend - needs: publish_image - uses: blockscout/blockscout-ci-cd/.github/workflows/deploy_helmfile.yaml@master - with: - appName: front - globalEnv: main - helmfileDir: deploy - kubeConfigSecret: ci/data/dev/kubeconfig/k8s-dev - vaultRole: ci-dev - secrets: inherit - - deploy_l2: - name: Deploy frontend (L2) - needs: publish_image - uses: blockscout/blockscout-ci-cd/.github/workflows/deploy_helmfile.yaml@master - with: - appName: l2-optimism-goerli - globalEnv: optimism-goerli - helmfileDir: deploy - kubeConfigSecret: ci/data/dev/kubeconfig/k8s-dev - vaultRole: ci-dev - secrets: inherit diff --git a/.github/workflows/deploy-review-l2.yml b/.github/workflows/deploy-review-l2.yml index 02feb3ce64..5df2f0d45c 100644 --- a/.github/workflows/deploy-review-l2.yml +++ b/.github/workflows/deploy-review-l2.yml @@ -2,6 +2,37 @@ name: Deploy review environment (L2) on: workflow_dispatch: + inputs: + envs_preset: + description: ENVs preset + required: false + default: "" + type: choice + options: + - none + - arbitrum + - arbitrum_nova + - arbitrum_sepolia + - base + - celo_alfajores + - garnet + - gnosis + - eth + - eth_sepolia + - eth_goerli + - filecoin + - optimism + - optimism_celestia + - optimism_sepolia + - polygon + - rootstock + - scroll_sepolia + - shibarium + - stability + - zkevm + - zilliqa_prototestnet + - zksync + - zora jobs: make_slug: @@ -23,6 +54,7 @@ jobs: uses: './.github/workflows/publish-image.yml' with: tags: ghcr.io/blockscout/frontend:review-${{ needs.make_slug.outputs.REF_SLUG }} + build_args: ENVS_PRESET=${{ inputs.envs_preset }} secrets: inherit deploy_review_l2: diff --git a/.github/workflows/deploy-review.yml b/.github/workflows/deploy-review.yml index c5edbc8c50..fba9827b94 100644 --- a/.github/workflows/deploy-review.yml +++ b/.github/workflows/deploy-review.yml @@ -2,6 +2,37 @@ name: Deploy review environment on: workflow_dispatch: + inputs: + envs_preset: + description: ENVs preset + required: false + default: "" + type: choice + options: + - none + - arbitrum + - arbitrum_nova + - arbitrum_sepolia + - base + - celo_alfajores + - garnet + - gnosis + - eth + - eth_sepolia + - eth_goerli + - filecoin + - optimism + - optimism_celestia + - optimism_sepolia + - polygon + - rootstock + - shibarium + - scroll_sepolia + - stability + - zkevm + - zilliqa_prototestnet + - zksync + - zora jobs: make_slug: @@ -23,6 +54,7 @@ jobs: uses: './.github/workflows/publish-image.yml' with: tags: ghcr.io/blockscout/frontend:review-${{ needs.make_slug.outputs.REF_SLUG }} + build_args: ENVS_PRESET=${{ inputs.envs_preset }} secrets: inherit deploy_review: diff --git a/.github/workflows/label-issues-in-release.yml b/.github/workflows/label-issues-in-release.yml index 4bc9861b0d..b95fd45616 100644 --- a/.github/workflows/label-issues-in-release.yml +++ b/.github/workflows/label-issues-in-release.yml @@ -59,7 +59,7 @@ jobs: steps: - name: Getting tags of the two latestest releases id: tags - uses: actions/github-script@v6 + uses: actions/github-script@v7 env: TAG: ${{ inputs.tag }} with: @@ -108,7 +108,7 @@ jobs: - name: Looking for commits between two releases id: commits - uses: actions/github-script@v6 + uses: actions/github-script@v7 env: PREVIOUS_TAG: ${{ steps.tags.outputs.previous }} LATEST_TAG: ${{ steps.tags.outputs.latest }} @@ -137,7 +137,7 @@ jobs: - name: Looking for issues linked to commits id: linked_issues - uses: actions/github-script@v6 + uses: actions/github-script@v7 env: COMMITS: ${{ steps.commits.outputs.result }} with: @@ -225,7 +225,7 @@ jobs: - name: Creating label id: label_creating - uses: actions/github-script@v6 + uses: actions/github-script@v7 env: LABEL_NAME: ${{ inputs.label_name }} LABEL_COLOR: ${{ inputs.label_color }} @@ -253,7 +253,7 @@ jobs: - name: Adding label to issues id: labeling_issues - uses: actions/github-script@v6 + uses: actions/github-script@v7 env: LABEL_NAME: ${{ inputs.label_name }} ISSUES: ${{ steps.linked_issues.outputs.result }} diff --git a/.github/workflows/pre-release.yml b/.github/workflows/pre-release.yml index e0cb8684b8..05155cbd32 100644 --- a/.github/workflows/pre-release.yml +++ b/.github/workflows/pre-release.yml @@ -32,7 +32,7 @@ jobs: steps: - name: Determine if it is the initial version of the pre-release id: is_initial - uses: actions/github-script@v6 + uses: actions/github-script@v7 env: TAG: ${{ github.ref_name }} with: @@ -55,7 +55,10 @@ jobs: label_description: Tasks in pre-release right now secrets: inherit + # Temporary disable this step because it is broken + # There is an issue with building web3modal deps upload_source_maps: name: Upload source maps to Sentry + if: false uses: './.github/workflows/upload-source-maps.yml' secrets: inherit diff --git a/.github/workflows/project-management.yml b/.github/workflows/project-management.yml index 791515b07d..1da733baeb 100644 --- a/.github/workflows/project-management.yml +++ b/.github/workflows/project-management.yml @@ -28,7 +28,7 @@ jobs: issues: "[${{ github.event.issue.number }}]" secrets: inherit - review_requested_issues: + pr_linked_issues: name: Get issues linked to PR runs-on: ubuntu-latest if: ${{ github.event.pull_request && github.event.action == 'review_requested' }} @@ -37,7 +37,7 @@ jobs: steps: - name: Fetching issues linked to pull request id: linked_issues - uses: actions/github-script@v6 + uses: actions/github-script@v7 env: PULL_REQUEST_NUMBER: ${{ github.event.pull_request.number }} with: @@ -76,14 +76,24 @@ jobs: return issues; - review_requested_tasks: + issues_in_review: name: Update status for issues in review - needs: [ review_requested_issues ] - if: ${{ needs.review_requested_issues.outputs.issues }} + needs: [ pr_linked_issues ] + if: ${{ needs.pr_linked_issues.outputs.issues }} uses: './.github/workflows/update-project-cards.yml' secrets: inherit with: project_name: ${{ vars.PROJECT_NAME }} field_name: Status field_value: Review - issues: ${{ needs.review_requested_issues.outputs.issues }} + issues: ${{ needs.pr_linked_issues.outputs.issues }} + + copy_labels: + name: Copy issues labels to pull request + needs: [ pr_linked_issues ] + if: ${{ needs.pr_linked_issues.outputs.issues }} + uses: './.github/workflows/copy-issues-labels.yml' + secrets: inherit + with: + pr_number: ${{ github.event.pull_request.number }} + issues: ${{ needs.pr_linked_issues.outputs.issues }} diff --git a/.github/workflows/publish-image.yml b/.github/workflows/publish-image.yml index ba0382b078..97c1102e8f 100644 --- a/.github/workflows/publish-image.yml +++ b/.github/workflows/publish-image.yml @@ -7,6 +7,10 @@ on: description: Image tags required: false type: string + build_args: + description: Build-time variables + required: false + type: string platforms: description: Image platforms (you can specify multiple platforms separated by comma) required: false @@ -18,6 +22,10 @@ on: description: Image tags required: false type: string + build_args: + description: Build-time variables + required: false + type: string platforms: description: Image platforms (you can specify multiple platforms separated by comma) required: false @@ -72,4 +80,5 @@ jobs: labels: ${{ steps.meta.outputs.labels }} build-args: | GIT_COMMIT_SHA=${{ env.SHORT_SHA }} - GIT_TAG=${{ github.ref_type == 'tag' && github.ref_name || '' }} \ No newline at end of file + GIT_TAG=${{ github.ref_type == 'tag' && github.ref_name || '' }} + ${{ inputs.build_args }} \ No newline at end of file diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index c3ca89391e..7485ee7c82 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -12,7 +12,7 @@ jobs: steps: - name: Remove label id: tags - uses: actions/github-script@v6 + uses: actions/github-script@v7 env: LABEL_NAME: pre-release with: @@ -81,8 +81,17 @@ jobs: with: platforms: linux/amd64,linux/arm64/v8 + sync_envs_docs: + name: Sync ENV variables docs + uses: './.github/workflows/sync-envs-docs.yml' + needs: publish_image + secrets: inherit + + # Temporary disable this step because it is broken + # There is an issue with building web3modal deps upload_source_maps: name: Upload source maps to Sentry + if: false needs: publish_image uses: './.github/workflows/upload-source-maps.yml' secrets: inherit diff --git a/.github/workflows/sync-envs-docs.yml b/.github/workflows/sync-envs-docs.yml new file mode 100644 index 0000000000..462b22b314 --- /dev/null +++ b/.github/workflows/sync-envs-docs.yml @@ -0,0 +1,41 @@ +name: Sync ENV variables docs + +on: + workflow_dispatch: + workflow_call: + +jobs: + run: + name: Run + runs-on: ubuntu-latest + steps: + - name: Checkout repo + uses: actions/checkout@v4 + + - name: Copy main ENV file to Blockscout Docs repository + uses: dmnemec/copy_file_to_another_repo_action@main + env: + API_TOKEN_GITHUB: ${{ secrets.GITHIB_BOT_TOKEN }} + with: + source_file: 'docs/ENVS.md' + destination_repo: 'blockscout/docs' + destination_folder: 'setup/env-variables/frontend-common-envs' + rename: 'envs.md' + destination_branch: 'master' + user_email: 'bot@blockscout.com' + user_name: 'blockscout-bot' + use_rsync: true + + - name: Copy deprecated ENV file to Blockscout Docs repository + uses: dmnemec/copy_file_to_another_repo_action@main + env: + API_TOKEN_GITHUB: ${{ secrets.GITHIB_BOT_TOKEN }} + with: + source_file: 'docs/DEPRECATED_ENVS.md' + destination_repo: 'blockscout/docs' + destination_folder: 'setup/env-variables/frontend-common-envs' + rename: 'deprecated-envs.md' + destination_branch: 'master' + user_email: 'bot@blockscout.com' + user_name: 'blockscout-bot' + use_rsync: true diff --git a/.github/workflows/update-project-cards.yml b/.github/workflows/update-project-cards.yml index 98ffbed2bb..b071ba61da 100644 --- a/.github/workflows/update-project-cards.yml +++ b/.github/workflows/update-project-cards.yml @@ -48,7 +48,7 @@ jobs: steps: - name: Getting project info id: project_info - uses: actions/github-script@v6 + uses: actions/github-script@v7 env: PROJECT_NAME: ${{ inputs.project_name }} FIELD_NAME: ${{ inputs.field_name }} @@ -131,7 +131,7 @@ jobs: - name: Getting project items that linked to the issues id: items - uses: actions/github-script@v6 + uses: actions/github-script@v7 env: ISSUES: ${{ inputs.issues }} with: @@ -193,7 +193,7 @@ jobs: - name: Updating field value of the project items id: updating_items - uses: actions/github-script@v6 + uses: actions/github-script@v7 env: ITEMS: ${{ steps.items.outputs.result }} PROJECT_ID: ${{ steps.project_info.outputs.id }} diff --git a/.github/workflows/upload-source-maps.yml b/.github/workflows/upload-source-maps.yml index 55b18067fe..6c0ec2fa04 100644 --- a/.github/workflows/upload-source-maps.yml +++ b/.github/workflows/upload-source-maps.yml @@ -1,3 +1,4 @@ +# TODO @tom2drum setup source maps for Rollbar name: Upload source maps to Sentry on: workflow_call: @@ -21,7 +22,7 @@ jobs: - name: Setup node uses: actions/setup-node@v4 with: - node-version: 20.11.0 + node-version: 22.11.0 cache: 'yarn' - name: Cache node_modules @@ -34,7 +35,7 @@ jobs: - name: Install dependencies if: steps.cache-node-modules.outputs.cache-hit != 'true' - run: yarn --frozen-lockfile --ignore-optional + run: yarn --frozen-lockfile - name: Make production build with source maps run: yarn build diff --git a/.gitignore b/.gitignore index 6d849c1369..74d489ae6b 100644 --- a/.gitignore +++ b/.gitignore @@ -12,8 +12,8 @@ # next.js /.next/ /out/ -/public/assets/ -/public/envs.js +/public/assets/envs.js +/public/assets/configs /public/icons/sprite.svg /public/icons/README.md /analyze @@ -25,6 +25,7 @@ .DS_Store *.pem .tools +grafana # debug npm-debug.log* @@ -42,16 +43,15 @@ yarn-error.log* .eslintcache -# Sentry -.sentryclirc - **.decrypted~** /test-results/ /playwright-report/ /playwright/.cache/ /playwright/.browser/ /playwright/envs.js +/playwright/affected-tests.txt **.dec** -.env \ No newline at end of file +# build outputs +/tools/preset-sync/index.js diff --git a/.nvmrc b/.nvmrc index 8b0beab16a..fdb2eaaff0 100644 --- a/.nvmrc +++ b/.nvmrc @@ -1 +1 @@ -20.11.0 +22.11.0 \ No newline at end of file diff --git a/.vscode/tasks.json b/.vscode/tasks.json index cd1be4e2b7..2811c40961 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -25,6 +25,27 @@ "instanceLimit": 1 } }, + { + "type": "shell", + "command": "yarn dev:preset:sync --name=${input:dev_config_preset}", + "problemMatcher": [], + "label": "dev preset sync", + "detail": "syncronize dev preset", + "presentation": { + "reveal": "always", + "panel": "shared", + "focus": true, + "close": false, + "revealProblems": "onProblem", + }, + "icon": { + "color": "terminal.ansiMagenta", + "id": "repo-sync" + }, + "runOptions": { + "instanceLimit": 1 + } + }, // CODE CHECKS { @@ -155,6 +176,27 @@ "instanceLimit": 1 } }, + { + "type": "shell", + "command": "yarn test:pw:detect-affected", + "problemMatcher": [], + "label": "pw: detect affected", + "detail": "detect PW tests affected by changes in current branch", + "presentation": { + "reveal": "always", + "panel": "shared", + "focus": true, + "close": false, + "revealProblems": "onProblem", + }, + "icon": { + "color": "terminal.ansiBlue", + "id": "diff" + }, + "runOptions": { + "instanceLimit": 1 + }, + }, // JEST TESTS { @@ -305,6 +347,7 @@ "options": [ "", "--update-snapshots", + "--update-snapshots --affected", "--ui", ], "default": "" @@ -315,15 +358,28 @@ "description": "Choose dev server config preset:", "options": [ "main", - "main.L2", - "poa_core", - "eth_goerli", - "sepolia", + "localhost", + "arbitrum", + "arbitrum_sepolia", + "base", + "celo_alfajores", + "garnet", + "gnosis", "eth", - "rootstock", + "eth_goerli", + "eth_sepolia", + "filecoin", + "optimism", + "optimism_celestia", + "optimism_sepolia", "polygon", - "gnosis", - "localhost", + "rootstock_testnet", + "shibarium", + "stability_testnet", + "zkevm", + "zilliqa_prototestnet", + "zksync", + "zora", ], "default": "main" }, diff --git a/Dockerfile b/Dockerfile index f38d68ebaa..0141bee554 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,9 +1,10 @@ # ***************************** # *** STAGE 1: Dependencies *** # ***************************** -FROM node:20.11.0-alpine AS deps +FROM node:22.11.0-alpine AS deps # Check https://github.com/nodejs/docker-node/tree/b4117f9333da4138b03a546ec926ef50a31506c3#nodealpine to understand why libc6-compat might be needed. -RUN apk add --no-cache libc6-compat +RUN apk add --no-cache libc6-compat python3 make g++ +RUN ln -sf /usr/bin/python3 /usr/bin/python ### APP # Install dependencies @@ -26,18 +27,26 @@ WORKDIR /envs-validator COPY ./deploy/tools/envs-validator/package.json ./deploy/tools/envs-validator/yarn.lock ./ RUN yarn --frozen-lockfile +### FAVICON GENERATOR +# Install dependencies +WORKDIR /favicon-generator +COPY ./deploy/tools/favicon-generator/package.json ./deploy/tools/favicon-generator/yarn.lock ./ +RUN yarn --frozen-lockfile + # ***************************** # ****** STAGE 2: Build ******* # ***************************** -FROM node:20.11.0-alpine AS builder +FROM node:22.11.0-alpine AS builder RUN apk add --no-cache --upgrade libc6-compat bash -# pass commit sha and git tag to the app image +# pass build args to env variables ARG GIT_COMMIT_SHA ENV NEXT_PUBLIC_GIT_COMMIT_SHA=$GIT_COMMIT_SHA ARG GIT_TAG ENV NEXT_PUBLIC_GIT_TAG=$GIT_TAG +ARG NEXT_OPEN_TELEMETRY_ENABLED +ENV NEXT_OPEN_TELEMETRY_ENABLED=$NEXT_OPEN_TELEMETRY_ENABLED ENV NODE_ENV production @@ -48,7 +57,7 @@ COPY --from=deps /app/node_modules ./node_modules COPY . . # Generate .env.registry with ENVs list and save build args into .env file -COPY --chmod=+x ./deploy/scripts/collect_envs.sh ./ +COPY --chmod=755 ./deploy/scripts/collect_envs.sh ./ RUN ./collect_envs.sh ./docs/ENVS.md # Next.js collects completely anonymous telemetry data about general usage. @@ -57,8 +66,8 @@ RUN ./collect_envs.sh ./docs/ENVS.md # ENV NEXT_TELEMETRY_DISABLED 1 # Build app for production -RUN yarn build RUN yarn svg:build-sprite +RUN yarn build ### FEATURE REPORTER @@ -74,11 +83,15 @@ COPY --from=deps /envs-validator/node_modules ./deploy/tools/envs-validator/node RUN cd ./deploy/tools/envs-validator && yarn build +### FAVICON GENERATOR +# Copy dependencies and source code +COPY --from=deps /favicon-generator/node_modules ./deploy/tools/favicon-generator/node_modules + # ***************************** # ******* STAGE 3: Run ******** # ***************************** # Production image, copy all the files and run next -FROM node:20.11.0-alpine AS runner +FROM node:22.11.0-alpine AS runner RUN apk add --no-cache --upgrade bash curl jq unzip ### APP @@ -102,15 +115,15 @@ COPY --from=builder /app/deploy/tools/feature-reporter/index.js ./feature-report # Copy scripts ## Entripoint -COPY --chmod=+x ./deploy/scripts/entrypoint.sh . +COPY --chmod=755 ./deploy/scripts/entrypoint.sh . ## ENV validator and client script maker -COPY --chmod=+x ./deploy/scripts/validate_envs.sh . -COPY --chmod=+x ./deploy/scripts/make_envs_script.sh . +COPY --chmod=755 ./deploy/scripts/validate_envs.sh . +COPY --chmod=755 ./deploy/scripts/make_envs_script.sh . ## Assets downloader -COPY --chmod=+x ./deploy/scripts/download_assets.sh . +COPY --chmod=755 ./deploy/scripts/download_assets.sh . ## Favicon generator -COPY --chmod=+x ./deploy/scripts/favicon_generator.sh . -COPY ./deploy/tools/favicon-generator ./deploy/tools/favicon-generator +COPY --chmod=755 ./deploy/scripts/favicon_generator.sh . +COPY --from=builder /app/deploy/tools/favicon-generator ./deploy/tools/favicon-generator RUN ["chmod", "-R", "777", "./deploy/tools/favicon-generator"] RUN ["chmod", "-R", "777", "./public"] @@ -118,6 +131,11 @@ RUN ["chmod", "-R", "777", "./public"] COPY --from=builder /app/.env.registry . COPY --from=builder /app/.env . +# Copy ENVs presets +ARG ENVS_PRESET +ENV ENVS_PRESET=$ENVS_PRESET +COPY ./configs/envs ./configs/envs + # Automatically leverage output traces to reduce image size # https://nextjs.org/docs/advanced-features/output-file-tracing COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./ diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md new file mode 100644 index 0000000000..2dc937a6eb --- /dev/null +++ b/RELEASE_NOTES.md @@ -0,0 +1,37 @@ +## 🚀 New Features +- Description of the new feature 1. +- Description of the new feature 2. + +## 🐛 Bug Fixes +- Description of the bug fix 1. +- Description of the bug fix 2. + +## ⚡ Performance Improvements +- Description of the performance improvement 1. +- Description of the performance improvement 2. + +## 📦 Dependencies updates +- Updated dependency: PackageName 1 to version x.x.x. +- Updated dependency: PackageName 2 to version x.x.x. + +## 🎨 Design updates +- New style 1. +- New style 2. + +## ✨ Other Changes +- Another minor change 1. +- Another minor change 2. + +## 🚨 Changes in ENV variables +- Added new environment variable: ENV_VARIABLE_NAME with value. +- Updated existing environment variable: ENV_VARIABLE_NAME to new value. + +**Full list of the ENV variables**: [v1.2.3](https://github.com/blockscout/frontend/blob/v1.2.3/docs/ENVS.md) + +## 🦄 New Contributors +- @contributor1 made their first contribution in https://github.com/blockscout/frontend/pull/1 +- @contributor2 made their first contribution in https://github.com/blockscout/frontend/pull/2 + +--- + +**Full Changelog**: https://github.com/blockscout/frontend/compare/v1.2.2...v1.2.3 diff --git a/configs/app/chain.ts b/configs/app/chain.ts index 3f90e329bd..a6fef46488 100644 --- a/configs/app/chain.ts +++ b/configs/app/chain.ts @@ -1,7 +1,22 @@ +import type { RollupType } from 'types/client/rollup'; +import type { NetworkVerificationType, NetworkVerificationTypeEnvs } from 'types/networks'; + import { getEnvValue } from './utils'; const DEFAULT_CURRENCY_DECIMALS = 18; +const rollupType = getEnvValue('NEXT_PUBLIC_ROLLUP_TYPE') as RollupType; + +const verificationType: NetworkVerificationType = (() => { + if (rollupType === 'arbitrum') { + return 'posting'; + } + if (rollupType === 'zkEvm') { + return 'sequencing'; + } + return getEnvValue('NEXT_PUBLIC_NETWORK_VERIFICATION_TYPE') as NetworkVerificationTypeEnvs || 'mining'; +})(); + const chain = Object.freeze({ id: getEnvValue('NEXT_PUBLIC_NETWORK_ID'), name: getEnvValue('NEXT_PUBLIC_NETWORK_NAME'), @@ -13,9 +28,11 @@ const chain = Object.freeze({ decimals: Number(getEnvValue('NEXT_PUBLIC_NETWORK_CURRENCY_DECIMALS')) || DEFAULT_CURRENCY_DECIMALS, image: getEnvValue('NEXT_PUBLIC_NETWORK_CURRENCY_IMAGE_URL'), }, - governanceToken: { - symbol: getEnvValue('NEXT_PUBLIC_NETWORK_GOVERNANCE_TOKEN_SYMBOL'), + secondaryCoin: { + symbol: getEnvValue('NEXT_PUBLIC_NETWORK_SECONDARY_COIN_SYMBOL'), }, + hasMultipleGasCurrencies: getEnvValue('NEXT_PUBLIC_NETWORK_MULTIPLE_GAS_CURRENCIES') === 'true', + tokenStandard: getEnvValue('NEXT_PUBLIC_NETWORK_TOKEN_STANDARD_NAME') || 'ERC', rpcUrl: getEnvValue('NEXT_PUBLIC_NETWORK_RPC_URL'), isTestnet: getEnvValue('NEXT_PUBLIC_IS_TESTNET') === 'true', isDevNet: getEnvValue('NEXT_PUBLIC_IS_DEVNET') === 'true', diff --git a/configs/app/features/account.ts b/configs/app/features/account.ts index 120a94b7e9..82ae3b458b 100644 --- a/configs/app/features/account.ts +++ b/configs/app/features/account.ts @@ -1,45 +1,16 @@ import type { Feature } from './types'; -import stripTrailingSlash from 'lib/stripTrailingSlash'; - -import app from '../app'; +import services from '../services'; import { getEnvValue } from '../utils'; -const authUrl = stripTrailingSlash(getEnvValue('NEXT_PUBLIC_AUTH_URL') || app.baseUrl); - -const logoutUrl = (() => { - try { - const envUrl = getEnvValue('NEXT_PUBLIC_LOGOUT_URL'); - const auth0ClientId = getEnvValue('NEXT_PUBLIC_AUTH0_CLIENT_ID'); - const returnUrl = authUrl + '/auth/logout'; - - if (!envUrl || !auth0ClientId) { - throw Error(); - } - - const url = new URL(envUrl); - url.searchParams.set('client_id', auth0ClientId); - url.searchParams.set('returnTo', returnUrl); - - return url.toString(); - } catch (error) { - return; - } -})(); - const title = 'My account'; -const config: Feature<{ authUrl: string; logoutUrl: string }> = (() => { - if ( - getEnvValue('NEXT_PUBLIC_IS_ACCOUNT_SUPPORTED') === 'true' && - authUrl && - logoutUrl - ) { +const config: Feature<{ isEnabled: true; recaptchaSiteKey: string }> = (() => { + if (getEnvValue('NEXT_PUBLIC_IS_ACCOUNT_SUPPORTED') === 'true' && services.reCaptchaV2.siteKey) { return Object.freeze({ title, isEnabled: true, - authUrl, - logoutUrl, + recaptchaSiteKey: services.reCaptchaV2.siteKey, }); } diff --git a/configs/app/features/addressMetadata.ts b/configs/app/features/addressMetadata.ts new file mode 100644 index 0000000000..5ca5b78ada --- /dev/null +++ b/configs/app/features/addressMetadata.ts @@ -0,0 +1,27 @@ +import type { Feature } from './types'; + +import { getEnvValue } from '../utils'; + +const apiHost = getEnvValue('NEXT_PUBLIC_METADATA_SERVICE_API_HOST'); + +const title = 'Address metadata'; + +const config: Feature<{ api: { endpoint: string; basePath: string } }> = (() => { + if (apiHost) { + return Object.freeze({ + title, + isEnabled: true, + api: { + endpoint: apiHost, + basePath: '', + }, + }); + } + + return Object.freeze({ + title, + isEnabled: false, + }); +})(); + +export default config; diff --git a/configs/app/features/addressProfileAPI.ts b/configs/app/features/addressProfileAPI.ts new file mode 100644 index 0000000000..e46301ee6f --- /dev/null +++ b/configs/app/features/addressProfileAPI.ts @@ -0,0 +1,45 @@ +import type { Feature } from './types'; +import type { AddressProfileAPIConfig } from 'types/client/addressProfileAPIConfig'; + +import { getEnvValue, parseEnvJson } from '../utils'; + +const value = parseEnvJson(getEnvValue('NEXT_PUBLIC_ADDRESS_USERNAME_TAG')); + +function checkApiUrlTemplate(apiUrlTemplate: string): boolean { + try { + const testUrl = apiUrlTemplate.replace('{address}', '0x0000000000000000000000000000000000000000'); + new URL(testUrl).toString(); + return true; + } catch (error) { + return false; + } +} + +const title = 'User profile API'; + +const config: Feature<{ + apiUrlTemplate: string; + tagLinkTemplate?: string; + tagIcon?: string; + tagBgColor?: string; + tagTextColor?: string; +}> = (() => { + if (value && checkApiUrlTemplate(value.api_url_template)) { + return Object.freeze({ + title, + isEnabled: true, + apiUrlTemplate: value.api_url_template, + tagLinkTemplate: value.tag_link_template, + tagIcon: value.tag_icon, + tagBgColor: value.tag_bg_color, + tagTextColor: value.tag_text_color, + }); + } + + return Object.freeze({ + title, + isEnabled: false, + }); +})(); + +export default config; diff --git a/configs/app/features/adsBanner.ts b/configs/app/features/adsBanner.ts index 0785ab160f..c455ed4546 100644 --- a/configs/app/features/adsBanner.ts +++ b/configs/app/features/adsBanner.ts @@ -1,7 +1,7 @@ import type { Feature } from './types'; import type { AdButlerConfig } from 'types/client/adButlerConfig'; import { SUPPORTED_AD_BANNER_PROVIDERS } from 'types/client/adProviders'; -import type { AdBannerProviders } from 'types/client/adProviders'; +import type { AdBannerProviders, AdBannerAdditionalProviders } from 'types/client/adProviders'; import { getEnvValue, parseEnvJson } from '../utils'; @@ -11,6 +11,8 @@ const provider: AdBannerProviders = (() => { return envValue && SUPPORTED_AD_BANNER_PROVIDERS.includes(envValue) ? envValue : 'slise'; })(); +const additionalProvider = getEnvValue('NEXT_PUBLIC_AD_BANNER_ADDITIONAL_PROVIDER') as AdBannerAdditionalProviders; + const title = 'Banner ads'; type AdsBannerFeaturePayload = { @@ -23,7 +25,16 @@ type AdsBannerFeaturePayload = { mobile: AdButlerConfig; }; }; -} +} | { + provider: Exclude; + additionalProvider: 'adbutler'; + adButler: { + config: { + desktop: AdButlerConfig; + mobile: AdButlerConfig; + }; + }; +}; const config: Feature = (() => { if (provider === 'adbutler') { @@ -44,6 +55,24 @@ const config: Feature = (() => { }); } } else if (provider !== 'none') { + + if (additionalProvider === 'adbutler') { + const desktopConfig = parseEnvJson(getEnvValue('NEXT_PUBLIC_AD_ADBUTLER_CONFIG_DESKTOP')); + const mobileConfig = parseEnvJson(getEnvValue('NEXT_PUBLIC_AD_ADBUTLER_CONFIG_MOBILE')); + + return Object.freeze({ + title, + isEnabled: true, + provider, + additionalProvider, + adButler: { + config: { + desktop: desktopConfig, + mobile: mobileConfig, + }, + }, + }); + } return Object.freeze({ title, isEnabled: true, diff --git a/configs/app/features/adsText.ts b/configs/app/features/adsText.ts index b44fa59e85..82946f984f 100644 --- a/configs/app/features/adsText.ts +++ b/configs/app/features/adsText.ts @@ -1,12 +1,12 @@ import type { Feature } from './types'; -import { SUPPORTED_AD_BANNER_PROVIDERS } from 'types/client/adProviders'; +import { SUPPORTED_AD_TEXT_PROVIDERS } from 'types/client/adProviders'; import type { AdTextProviders } from 'types/client/adProviders'; import { getEnvValue } from '../utils'; const provider: AdTextProviders = (() => { const envValue = getEnvValue('NEXT_PUBLIC_AD_TEXT_PROVIDER') as AdTextProviders; - return envValue && SUPPORTED_AD_BANNER_PROVIDERS.includes(envValue) ? envValue : 'coinzilla'; + return envValue && SUPPORTED_AD_TEXT_PROVIDERS.includes(envValue) ? envValue : 'coinzilla'; })(); const title = 'Text ads'; diff --git a/configs/app/features/advancedFilter.ts b/configs/app/features/advancedFilter.ts new file mode 100644 index 0000000000..7495d369f9 --- /dev/null +++ b/configs/app/features/advancedFilter.ts @@ -0,0 +1,23 @@ +import type { Feature } from './types'; + +import { getEnvValue } from '../utils'; + +const isDisabled = getEnvValue('NEXT_PUBLIC_ADVANCED_FILTER_ENABLED') === 'false'; + +const title = 'Advanced filter'; + +const config: Feature<{}> = (() => { + if (!isDisabled) { + return Object.freeze({ + title, + isEnabled: true, + }); + } + + return Object.freeze({ + title, + isEnabled: false, + }); +})(); + +export default config; diff --git a/configs/app/features/celo.ts b/configs/app/features/celo.ts new file mode 100644 index 0000000000..9d169e4220 --- /dev/null +++ b/configs/app/features/celo.ts @@ -0,0 +1,24 @@ +import type { Feature } from './types'; + +import { getEnvValue } from '../utils'; + +const title = 'Celo chain'; + +const config: Feature<{ L2UpgradeBlock: number | undefined; BLOCKS_PER_EPOCH: number }> = (() => { + + if (getEnvValue('NEXT_PUBLIC_CELO_ENABLED') === 'true') { + return Object.freeze({ + title, + isEnabled: true, + L2UpgradeBlock: getEnvValue('NEXT_PUBLIC_CELO_L2_UPGRADE_BLOCK') ? Number(getEnvValue('NEXT_PUBLIC_CELO_L2_UPGRADE_BLOCK')) : undefined, + BLOCKS_PER_EPOCH: 17_280, + }); + } + + return Object.freeze({ + title, + isEnabled: false, + }); +})(); + +export default config; diff --git a/configs/app/features/csvExport.ts b/configs/app/features/csvExport.ts index 442b4bedc7..fe1e24ec34 100644 --- a/configs/app/features/csvExport.ts +++ b/configs/app/features/csvExport.ts @@ -4,13 +4,13 @@ import services from '../services'; const title = 'Export data to CSV file'; -const config: Feature<{ reCaptcha: { siteKey: string }}> = (() => { - if (services.reCaptcha.siteKey) { +const config: Feature<{ reCaptcha: { siteKey: string } }> = (() => { + if (services.reCaptchaV2.siteKey) { return Object.freeze({ title, isEnabled: true, reCaptcha: { - siteKey: services.reCaptcha.siteKey, + siteKey: services.reCaptchaV2.siteKey, }, }); } diff --git a/configs/app/features/dataAvailability.ts b/configs/app/features/dataAvailability.ts new file mode 100644 index 0000000000..add9e5fec5 --- /dev/null +++ b/configs/app/features/dataAvailability.ts @@ -0,0 +1,21 @@ +import type { Feature } from './types'; + +import { getEnvValue } from '../utils'; + +const title = 'Data availability'; + +const config: Feature<{ isEnabled: true }> = (() => { + if (getEnvValue('NEXT_PUBLIC_DATA_AVAILABILITY_ENABLED') === 'true') { + return Object.freeze({ + title, + isEnabled: true, + }); + } + + return Object.freeze({ + title, + isEnabled: false, + }); +})(); + +export default config; diff --git a/configs/app/features/deFiDropdown.ts b/configs/app/features/deFiDropdown.ts new file mode 100644 index 0000000000..62b8fdcd14 --- /dev/null +++ b/configs/app/features/deFiDropdown.ts @@ -0,0 +1,21 @@ +import type { Feature } from './types'; +import type { DeFiDropdownItem } from 'types/client/deFiDropdown'; + +import { getEnvValue, parseEnvJson } from '../utils'; + +const items = parseEnvJson>(getEnvValue('NEXT_PUBLIC_DEFI_DROPDOWN_ITEMS')) || []; + +const title = 'DeFi dropdown'; + +const config: Feature<{ items: Array }> = items.length > 0 ? + Object.freeze({ + title, + isEnabled: true, + items, + }) : + Object.freeze({ + title, + isEnabled: false, + }); + +export default config; diff --git a/configs/app/features/easterEggBadge.ts b/configs/app/features/easterEggBadge.ts new file mode 100644 index 0000000000..d8ce02926b --- /dev/null +++ b/configs/app/features/easterEggBadge.ts @@ -0,0 +1,24 @@ +import type { Feature } from './types'; + +import { getEnvValue } from '../utils'; + +const badgeClaimLink = getEnvValue('NEXT_PUBLIC_GAME_BADGE_CLAIM_LINK'); + +const title = 'Easter egg badge'; + +const config: Feature<{ badgeClaimLink: string }> = (() => { + if (badgeClaimLink) { + return Object.freeze({ + title, + isEnabled: true, + badgeClaimLink, + }); + } + + return Object.freeze({ + title, + isEnabled: false, + }); +})(); + +export default config; diff --git a/configs/app/features/faultProofSystem.ts b/configs/app/features/faultProofSystem.ts new file mode 100644 index 0000000000..38a8f021db --- /dev/null +++ b/configs/app/features/faultProofSystem.ts @@ -0,0 +1,22 @@ +import type { Feature } from './types'; + +import { getEnvValue } from '../utils'; +import rollup from './rollup'; + +const title = 'Fault proof system'; + +const config: Feature<{ isEnabled: true }> = (() => { + if (rollup.isEnabled && rollup.type === 'optimistic' && getEnvValue('NEXT_PUBLIC_FAULT_PROOF_ENABLED') === 'true') { + return Object.freeze({ + title, + isEnabled: true, + }); + } + + return Object.freeze({ + title, + isEnabled: false, + }); +})(); + +export default config; diff --git a/configs/app/features/gasTracker.ts b/configs/app/features/gasTracker.ts new file mode 100644 index 0000000000..c20242e602 --- /dev/null +++ b/configs/app/features/gasTracker.ts @@ -0,0 +1,37 @@ +import type { Feature } from './types'; +import { GAS_UNITS } from 'types/client/gasTracker'; +import type { GasUnit } from 'types/client/gasTracker'; + +import { getEnvValue, parseEnvJson } from '../utils'; + +const isDisabled = getEnvValue('NEXT_PUBLIC_GAS_TRACKER_ENABLED') === 'false'; + +const units = ((): Array => { + const envValue = getEnvValue('NEXT_PUBLIC_GAS_TRACKER_UNITS'); + if (!envValue) { + return [ 'usd', 'gwei' ]; + } + + const units = parseEnvJson>(envValue)?.filter((type) => GAS_UNITS.includes(type)) || []; + + return units; +})(); + +const title = 'Gas tracker'; + +const config: Feature<{ units: Array }> = (() => { + if (!isDisabled && units.length > 0) { + return Object.freeze({ + title, + isEnabled: true, + units, + }); + } + + return Object.freeze({ + title, + isEnabled: false, + }); +})(); + +export default config; diff --git a/configs/app/features/getGasButton.ts b/configs/app/features/getGasButton.ts new file mode 100644 index 0000000000..db392f3c9a --- /dev/null +++ b/configs/app/features/getGasButton.ts @@ -0,0 +1,35 @@ +import type { Feature } from './types'; +import type { GasRefuelProviderConfig } from 'types/client/gasRefuelProviderConfig'; + +import chain from '../chain'; +import { getEnvValue, parseEnvJson } from '../utils'; +import marketplace from './marketplace'; + +const value = parseEnvJson(getEnvValue('NEXT_PUBLIC_GAS_REFUEL_PROVIDER_CONFIG')); + +const title = 'Get gas button'; + +const config: Feature<{ + name: string; + logoUrl?: string; + url: string; + dappId?: string; +}> = (() => { + if (value) { + return Object.freeze({ + title, + isEnabled: true, + name: value.name, + logoUrl: value.logo, + url: value.url_template.replace('{chainId}', chain.id || ''), + dappId: marketplace.isEnabled ? value.dapp_id : undefined, + }); + } + + return Object.freeze({ + title, + isEnabled: false, + }); +})(); + +export default config; diff --git a/configs/app/features/graphqlApiDocs.ts b/configs/app/features/graphqlApiDocs.ts index 09285ac898..d26c3bfde3 100644 --- a/configs/app/features/graphqlApiDocs.ts +++ b/configs/app/features/graphqlApiDocs.ts @@ -7,6 +7,14 @@ const defaultTxHash = getEnvValue('NEXT_PUBLIC_GRAPHIQL_TRANSACTION'); const title = 'GraphQL API documentation'; const config: Feature<{ defaultTxHash: string | undefined }> = (() => { + + if (defaultTxHash === 'none') { + return Object.freeze({ + title, + isEnabled: false, + }); + } + return Object.freeze({ title, isEnabled: true, diff --git a/configs/app/features/index.ts b/configs/app/features/index.ts index 9bd4c2253c..2652c0b480 100644 --- a/configs/app/features/index.ts +++ b/configs/app/features/index.ts @@ -1,26 +1,44 @@ +export { default as advancedFilter } from './advancedFilter'; export { default as account } from './account'; export { default as addressVerification } from './addressVerification'; +export { default as addressMetadata } from './addressMetadata'; export { default as adsBanner } from './adsBanner'; export { default as adsText } from './adsText'; export { default as beaconChain } from './beaconChain'; export { default as bridgedTokens } from './bridgedTokens'; export { default as blockchainInteraction } from './blockchainInteraction'; +export { default as celo } from './celo'; export { default as csvExport } from './csvExport'; +export { default as dataAvailability } from './dataAvailability'; +export { default as deFiDropdown } from './deFiDropdown'; +export { default as easterEggBadge } from './easterEggBadge'; +export { default as faultProofSystem } from './faultProofSystem'; +export { default as gasTracker } from './gasTracker'; +export { default as getGasButton } from './getGasButton'; export { default as googleAnalytics } from './googleAnalytics'; export { default as graphqlApiDocs } from './graphqlApiDocs'; export { default as growthBook } from './growthBook'; export { default as marketplace } from './marketplace'; +export { default as metasuites } from './metasuites'; export { default as mixpanel } from './mixpanel'; +export { default as mudFramework } from './mudFramework'; +export { default as multichainButton } from './multichainButton'; export { default as nameService } from './nameService'; +export { default as pools } from './pools'; +export { default as publicTagsSubmission } from './publicTagsSubmission'; export { default as restApiDocs } from './restApiDocs'; -export { default as optimisticRollup } from './optimisticRollup'; +export { default as rewards } from './rewards'; +export { default as rollbar } from './rollbar'; +export { default as rollup } from './rollup'; export { default as safe } from './safe'; -export { default as sentry } from './sentry'; +export { default as saveOnGas } from './saveOnGas'; export { default as sol2uml } from './sol2uml'; export { default as stats } from './stats'; export { default as suave } from './suave'; export { default as txInterpretation } from './txInterpretation'; export { default as userOps } from './userOps'; +export { default as addressProfileAPI } from './addressProfileAPI'; +export { default as validators } from './validators'; export { default as verifiedTokens } from './verifiedTokens'; export { default as web3Wallet } from './web3Wallet'; -export { default as zkEvmRollup } from './zkEvmRollup'; +export { default as xStarScore } from './xStarScore'; diff --git a/configs/app/features/marketplace.ts b/configs/app/features/marketplace.ts index a20d10e48a..ab5ab4a965 100644 --- a/configs/app/features/marketplace.ts +++ b/configs/app/features/marketplace.ts @@ -4,25 +4,71 @@ import chain from '../chain'; import { getEnvValue, getExternalAssetFilePath } from '../utils'; // config file will be downloaded at run-time and saved in the public folder +const enabled = getEnvValue('NEXT_PUBLIC_MARKETPLACE_ENABLED'); const configUrl = getExternalAssetFilePath('NEXT_PUBLIC_MARKETPLACE_CONFIG_URL'); const submitFormUrl = getEnvValue('NEXT_PUBLIC_MARKETPLACE_SUBMIT_FORM'); +const suggestIdeasFormUrl = getEnvValue('NEXT_PUBLIC_MARKETPLACE_SUGGEST_IDEAS_FORM'); const categoriesUrl = getExternalAssetFilePath('NEXT_PUBLIC_MARKETPLACE_CATEGORIES_URL'); +const adminServiceApiHost = getEnvValue('NEXT_PUBLIC_ADMIN_SERVICE_API_HOST'); +const securityReportsUrl = getExternalAssetFilePath('NEXT_PUBLIC_MARKETPLACE_SECURITY_REPORTS_URL'); +const featuredApp = getEnvValue('NEXT_PUBLIC_MARKETPLACE_FEATURED_APP'); +const bannerContentUrl = getExternalAssetFilePath('NEXT_PUBLIC_MARKETPLACE_BANNER_CONTENT_URL'); +const bannerLinkUrl = getEnvValue('NEXT_PUBLIC_MARKETPLACE_BANNER_LINK_URL'); +const ratingAirtableApiKey = getEnvValue('NEXT_PUBLIC_MARKETPLACE_RATING_AIRTABLE_API_KEY'); +const ratingAirtableBaseId = getEnvValue('NEXT_PUBLIC_MARKETPLACE_RATING_AIRTABLE_BASE_ID'); +const graphLinksUrl = getExternalAssetFilePath('NEXT_PUBLIC_MARKETPLACE_GRAPH_LINKS_URL'); const title = 'Marketplace'; -const config: Feature<{ configUrl: string; submitFormUrl: string; categoriesUrl: string | undefined }> = (() => { - if ( - chain.rpcUrl && - configUrl && - submitFormUrl - ) { - return Object.freeze({ - title, - isEnabled: true, - configUrl, +const config: Feature<( + { configUrl: string } | + { api: { endpoint: string; basePath: string } } +) & { + submitFormUrl: string; + categoriesUrl: string | undefined; + suggestIdeasFormUrl: string | undefined; + securityReportsUrl: string | undefined; + featuredApp: string | undefined; + banner: { contentUrl: string; linkUrl: string } | undefined; + rating: { airtableApiKey: string; airtableBaseId: string } | undefined; + graphLinksUrl: string | undefined; +}> = (() => { + if (enabled === 'true' && chain.rpcUrl && submitFormUrl) { + const props = { submitFormUrl, categoriesUrl, - }); + suggestIdeasFormUrl, + securityReportsUrl, + featuredApp, + banner: bannerContentUrl && bannerLinkUrl ? { + contentUrl: bannerContentUrl, + linkUrl: bannerLinkUrl, + } : undefined, + rating: ratingAirtableApiKey && ratingAirtableBaseId ? { + airtableApiKey: ratingAirtableApiKey, + airtableBaseId: ratingAirtableBaseId, + } : undefined, + graphLinksUrl, + }; + + if (configUrl) { + return Object.freeze({ + title, + isEnabled: true, + configUrl, + ...props, + }); + } else if (adminServiceApiHost) { + return Object.freeze({ + title, + isEnabled: true, + api: { + endpoint: adminServiceApiHost, + basePath: '', + }, + ...props, + }); + } } return Object.freeze({ diff --git a/configs/app/features/metasuites.ts b/configs/app/features/metasuites.ts new file mode 100644 index 0000000000..333e7d5a8a --- /dev/null +++ b/configs/app/features/metasuites.ts @@ -0,0 +1,21 @@ +import type { Feature } from './types'; + +import { getEnvValue } from '../utils'; + +const title = 'MetaSuites extension'; + +const config: Feature<{ isEnabled: true }> = (() => { + if (getEnvValue('NEXT_PUBLIC_METASUITES_ENABLED') === 'true') { + return Object.freeze({ + title, + isEnabled: true, + }); + } + + return Object.freeze({ + title, + isEnabled: false, + }); +})(); + +export default config; diff --git a/configs/app/features/mudFramework.ts b/configs/app/features/mudFramework.ts new file mode 100644 index 0000000000..86df2af34a --- /dev/null +++ b/configs/app/features/mudFramework.ts @@ -0,0 +1,22 @@ +import type { Feature } from './types'; + +import { getEnvValue } from '../utils'; +import rollup from './rollup'; + +const title = 'MUD framework'; + +const config: Feature<{ isEnabled: true }> = (() => { + if (rollup.isEnabled && rollup.type === 'optimistic' && getEnvValue('NEXT_PUBLIC_HAS_MUD_FRAMEWORK') === 'true') { + return Object.freeze({ + title, + isEnabled: true, + }); + } + + return Object.freeze({ + title, + isEnabled: false, + }); +})(); + +export default config; diff --git a/configs/app/features/multichainButton.ts b/configs/app/features/multichainButton.ts new file mode 100644 index 0000000000..923a16bd27 --- /dev/null +++ b/configs/app/features/multichainButton.ts @@ -0,0 +1,31 @@ +import type { Feature } from './types'; +import type { MultichainProviderConfig, MultichainProviderConfigParsed } from 'types/client/multichainProviderConfig'; + +import { getEnvValue, parseEnvJson } from '../utils'; +import marketplace from './marketplace'; + +const value = parseEnvJson>(getEnvValue('NEXT_PUBLIC_MULTICHAIN_BALANCE_PROVIDER_CONFIG')); + +const title = 'Multichain balance'; + +const config: Feature<{ providers: Array }> = (() => { + if (value) { + return Object.freeze({ + title, + isEnabled: true, + providers: value.map((provider) => ({ + name: provider.name, + logoUrl: provider.logo, + urlTemplate: provider.url_template, + dappId: marketplace.isEnabled ? provider.dapp_id : undefined, + })), + }); + } + + return Object.freeze({ + title, + isEnabled: false, + }); +})(); + +export default config; diff --git a/configs/app/features/optimisticRollup.ts b/configs/app/features/optimisticRollup.ts deleted file mode 100644 index 6b4f646d59..0000000000 --- a/configs/app/features/optimisticRollup.ts +++ /dev/null @@ -1,30 +0,0 @@ -import type { Feature } from './types'; - -import { getEnvValue } from '../utils'; - -const title = 'Rollup (L2) chain'; - -const config: Feature<{ L1BaseUrl: string; withdrawalUrl: string }> = (() => { - const L1BaseUrl = getEnvValue('NEXT_PUBLIC_L1_BASE_URL'); - const withdrawalUrl = getEnvValue('NEXT_PUBLIC_OPTIMISTIC_L2_WITHDRAWAL_URL'); - - if ( - getEnvValue('NEXT_PUBLIC_IS_OPTIMISTIC_L2_NETWORK') === 'true' && - L1BaseUrl && - withdrawalUrl - ) { - return Object.freeze({ - title, - isEnabled: true, - L1BaseUrl, - withdrawalUrl, - }); - } - - return Object.freeze({ - title, - isEnabled: false, - }); -})(); - -export default config; diff --git a/configs/app/features/pools.ts b/configs/app/features/pools.ts new file mode 100644 index 0000000000..310100da4f --- /dev/null +++ b/configs/app/features/pools.ts @@ -0,0 +1,28 @@ +import type { Feature } from './types'; + +import { getEnvValue } from '../utils'; + +const contractInfoApiHost = getEnvValue('NEXT_PUBLIC_CONTRACT_INFO_API_HOST'); +const dexPoolsEnabled = getEnvValue('NEXT_PUBLIC_DEX_POOLS_ENABLED') === 'true'; + +const title = 'DEX Pools'; + +const config: Feature<{ api: { endpoint: string; basePath: string } }> = (() => { + if (contractInfoApiHost && dexPoolsEnabled) { + return Object.freeze({ + title, + isEnabled: true, + api: { + endpoint: contractInfoApiHost, + basePath: '', + }, + }); + } + + return Object.freeze({ + title, + isEnabled: false, + }); +})(); + +export default config; diff --git a/configs/app/features/publicTagsSubmission.ts b/configs/app/features/publicTagsSubmission.ts new file mode 100644 index 0000000000..6ba9ade0f0 --- /dev/null +++ b/configs/app/features/publicTagsSubmission.ts @@ -0,0 +1,29 @@ +import type { Feature } from './types'; + +import services from '../services'; +import { getEnvValue } from '../utils'; +import addressMetadata from './addressMetadata'; + +const apiHost = getEnvValue('NEXT_PUBLIC_ADMIN_SERVICE_API_HOST'); + +const title = 'Public tag submission'; + +const config: Feature<{ api: { endpoint: string; basePath: string } }> = (() => { + if (services.reCaptchaV2.siteKey && addressMetadata.isEnabled && apiHost) { + return Object.freeze({ + title, + isEnabled: true, + api: { + endpoint: apiHost, + basePath: '', + }, + }); + } + + return Object.freeze({ + title, + isEnabled: false, + }); +})(); + +export default config; diff --git a/configs/app/features/restApiDocs.ts b/configs/app/features/restApiDocs.ts index 281282ab05..ae25f05c0a 100644 --- a/configs/app/features/restApiDocs.ts +++ b/configs/app/features/restApiDocs.ts @@ -2,15 +2,23 @@ import type { Feature } from './types'; import { getEnvValue } from '../utils'; -const specUrl = getEnvValue('NEXT_PUBLIC_API_SPEC_URL') || `https://raw.githubusercontent.com/blockscout/blockscout-api-v2-swagger/main/swagger.yaml`; +const DEFAULT_URL = `https://raw.githubusercontent.com/blockscout/blockscout-api-v2-swagger/main/swagger.yaml`; +const envValue = getEnvValue('NEXT_PUBLIC_API_SPEC_URL'); const title = 'REST API documentation'; const config: Feature<{ specUrl: string }> = (() => { + if (envValue === 'none') { + return Object.freeze({ + title, + isEnabled: false, + }); + } + return Object.freeze({ title, isEnabled: true, - specUrl, + specUrl: envValue || DEFAULT_URL, }); })(); diff --git a/configs/app/features/rewards.ts b/configs/app/features/rewards.ts new file mode 100644 index 0000000000..9f19ae58bf --- /dev/null +++ b/configs/app/features/rewards.ts @@ -0,0 +1,29 @@ +import type { Feature } from './types'; + +import { getEnvValue } from '../utils'; +import account from './account'; +import blockchainInteraction from './blockchainInteraction'; + +const apiHost = getEnvValue('NEXT_PUBLIC_REWARDS_SERVICE_API_HOST'); + +const title = 'Rewards service integration'; + +const config: Feature<{ api: { endpoint: string; basePath: string } }> = (() => { + if (apiHost && account.isEnabled && blockchainInteraction.isEnabled) { + return Object.freeze({ + title, + isEnabled: true, + api: { + endpoint: apiHost, + basePath: '', + }, + }); + } + + return Object.freeze({ + title, + isEnabled: false, + }); +})(); + +export default config; diff --git a/configs/app/features/rollbar.ts b/configs/app/features/rollbar.ts new file mode 100644 index 0000000000..276f782e4e --- /dev/null +++ b/configs/app/features/rollbar.ts @@ -0,0 +1,43 @@ +import type { Feature } from './types'; + +import app from '../app'; +import { getEnvValue } from '../utils'; + +const clientToken = getEnvValue('NEXT_PUBLIC_ROLLBAR_CLIENT_TOKEN'); +const instance = (() => { + const envValue = getEnvValue('NEXT_PUBLIC_APP_INSTANCE'); + if (envValue) { + return envValue; + } + + return app.host?.replace('.blockscout.com', '').replace('.k8s-dev', '').replaceAll('-', '_'); +})(); +const environment = getEnvValue('NEXT_PUBLIC_APP_ENV') || 'production'; +const codeVersion = getEnvValue('NEXT_PUBLIC_GIT_TAG') || getEnvValue('NEXT_PUBLIC_GIT_COMMIT_SHA'); + +const title = 'Rollbar error monitoring'; + +const config: Feature<{ + clientToken: string; + environment: string; + instance: string | undefined; + codeVersion: string | undefined; +}> = (() => { + if (clientToken) { + return Object.freeze({ + title, + isEnabled: true, + clientToken, + environment, + instance, + codeVersion, + }); + } + + return Object.freeze({ + title, + isEnabled: false, + }); +})(); + +export default config; diff --git a/configs/app/features/rollup.ts b/configs/app/features/rollup.ts new file mode 100644 index 0000000000..3728e60c18 --- /dev/null +++ b/configs/app/features/rollup.ts @@ -0,0 +1,48 @@ +import type { Feature } from './types'; +import type { RollupType } from 'types/client/rollup'; +import { ROLLUP_TYPES } from 'types/client/rollup'; + +import stripTrailingSlash from 'lib/stripTrailingSlash'; + +import { getEnvValue } from '../utils'; + +const type = (() => { + const envValue = getEnvValue('NEXT_PUBLIC_ROLLUP_TYPE'); + return ROLLUP_TYPES.find((type) => type === envValue); +})(); + +const L1BaseUrl = getEnvValue('NEXT_PUBLIC_ROLLUP_L1_BASE_URL'); +const L2WithdrawalUrl = getEnvValue('NEXT_PUBLIC_ROLLUP_L2_WITHDRAWAL_URL'); + +const title = 'Rollup (L2) chain'; + +const config: Feature<{ + type: RollupType; + L1BaseUrl: string; + homepage: { showLatestBlocks: boolean }; + outputRootsEnabled: boolean; + L2WithdrawalUrl: string | undefined; + parentChainName: string | undefined; +}> = (() => { + if (type && L1BaseUrl) { + return Object.freeze({ + title, + isEnabled: true, + type, + L1BaseUrl: stripTrailingSlash(L1BaseUrl), + L2WithdrawalUrl: type === 'optimistic' ? L2WithdrawalUrl : undefined, + outputRootsEnabled: type === 'optimistic' && getEnvValue('NEXT_PUBLIC_ROLLUP_OUTPUT_ROOTS_ENABLED') !== 'false', + parentChainName: type === 'arbitrum' ? getEnvValue('NEXT_PUBLIC_ROLLUP_PARENT_CHAIN_NAME') : undefined, + homepage: { + showLatestBlocks: getEnvValue('NEXT_PUBLIC_ROLLUP_HOMEPAGE_SHOW_LATEST_BLOCKS') === 'true', + }, + }); + } + + return Object.freeze({ + title, + isEnabled: false, + }); +})(); + +export default config; diff --git a/configs/app/features/safe.ts b/configs/app/features/safe.ts index bed8bf14a6..b2762a78da 100644 --- a/configs/app/features/safe.ts +++ b/configs/app/features/safe.ts @@ -1,35 +1,14 @@ import type { Feature } from './types'; -import chain from '../chain'; - -// https://docs.safe.global/safe-core-api/available-services -const SAFE_API_MAP: Record = { - '42161': 'https://safe-transaction-arbitrum.safe.global', - '1313161554': 'https://safe-transaction-aurora.safe.global', - '43114': 'https://safe-transaction-avalanche.safe.global', - '8453': 'https://safe-transaction-base.safe.global', - '84531': 'https://safe-transaction-base-testnet.safe.global', - '56': 'https://safe-transaction-bsc.safe.global', - '42220': 'https://safe-transaction-celo.safe.global', - '1': 'https://safe-transaction-mainnet.safe.global', - '100': 'https://safe-transaction-gnosis-chain.safe.global', - '5': 'https://safe-transaction-goerli.safe.global', - '10': 'https://safe-transaction-optimism.safe.global', - '137': 'https://safe-transaction-polygon.safe.global', -}; +import { getEnvValue } from '../utils'; function getApiUrl(): string | undefined { - if (!chain.id) { - return; - } - - const apiHost = SAFE_API_MAP[chain.id]; - - if (!apiHost) { + try { + const envValue = getEnvValue('NEXT_PUBLIC_SAFE_TX_SERVICE_URL'); + return new URL('/api/v1/safes', envValue).toString(); + } catch (error) { return; } - - return `${ apiHost }/api/v1/safes/`; } const title = 'Safe address tags'; diff --git a/configs/app/features/saveOnGas.ts b/configs/app/features/saveOnGas.ts new file mode 100644 index 0000000000..64d24ed56f --- /dev/null +++ b/configs/app/features/saveOnGas.ts @@ -0,0 +1,25 @@ +import type { Feature } from './types'; + +import { getEnvValue } from '../utils'; +import marketplace from './marketplace'; + +const title = 'Save on gas with GasHawk'; + +const config: Feature<{ + apiUrlTemplate: string; +}> = (() => { + if (getEnvValue('NEXT_PUBLIC_SAVE_ON_GAS_ENABLED') === 'true' && marketplace.isEnabled) { + return Object.freeze({ + title, + isEnabled: true, + apiUrlTemplate: 'https://core.gashawk.io/apiv2/stats/address/
/savingsPotential/0x1', + }); + } + + return Object.freeze({ + title, + isEnabled: false, + }); +})(); + +export default config; diff --git a/configs/app/features/sentry.ts b/configs/app/features/sentry.ts deleted file mode 100644 index fb840ca9e5..0000000000 --- a/configs/app/features/sentry.ts +++ /dev/null @@ -1,44 +0,0 @@ -import type { Feature } from './types'; - -import app from '../app'; -import { getEnvValue } from '../utils'; - -const dsn = getEnvValue('NEXT_PUBLIC_SENTRY_DSN'); -const instance = (() => { - const envValue = getEnvValue('NEXT_PUBLIC_APP_INSTANCE'); - if (envValue) { - return envValue; - } - - return app.host?.replace('.blockscout.com', '').replaceAll('-', '_'); -})(); -const environment = getEnvValue('NEXT_PUBLIC_APP_ENV') || 'production'; -const release = getEnvValue('NEXT_PUBLIC_GIT_TAG'); -const title = 'Sentry error monitoring'; - -const config: Feature<{ - dsn: string; - instance: string; - release: string | undefined; - environment: string; - enableTracing: boolean; -}> = (() => { - if (dsn && instance && environment) { - return Object.freeze({ - title, - isEnabled: true, - dsn, - instance, - release, - environment, - enableTracing: getEnvValue('NEXT_PUBLIC_SENTRY_ENABLE_TRACING') === 'true', - }); - } - - return Object.freeze({ - title, - isEnabled: false, - }); -})(); - -export default config; diff --git a/configs/app/features/sol2uml.ts b/configs/app/features/sol2uml.ts index 06853e62d8..5a0ac2d4be 100644 --- a/configs/app/features/sol2uml.ts +++ b/configs/app/features/sol2uml.ts @@ -1,5 +1,7 @@ import type { Feature } from './types'; +import stripTrailingSlash from 'lib/stripTrailingSlash'; + import { getEnvValue } from '../utils'; const apiEndpoint = getEnvValue('NEXT_PUBLIC_VISUALIZE_API_HOST'); @@ -13,7 +15,7 @@ const config: Feature<{ api: { endpoint: string; basePath: string } }> = (() => isEnabled: true, api: { endpoint: apiEndpoint, - basePath: '', + basePath: stripTrailingSlash(getEnvValue('NEXT_PUBLIC_VISUALIZE_API_BASE_PATH') || ''), }, }); } diff --git a/configs/app/features/stats.ts b/configs/app/features/stats.ts index b05f2f9659..d3a90ce061 100644 --- a/configs/app/features/stats.ts +++ b/configs/app/features/stats.ts @@ -1,5 +1,7 @@ import type { Feature } from './types'; +import stripTrailingSlash from 'lib/stripTrailingSlash'; + import { getEnvValue } from '../utils'; const apiEndpoint = getEnvValue('NEXT_PUBLIC_STATS_API_HOST'); @@ -13,7 +15,7 @@ const config: Feature<{ api: { endpoint: string; basePath: string } }> = (() => isEnabled: true, api: { endpoint: apiEndpoint, - basePath: '', + basePath: stripTrailingSlash(getEnvValue('NEXT_PUBLIC_STATS_API_BASE_PATH') || ''), }, }); } diff --git a/configs/app/features/validators.ts b/configs/app/features/validators.ts new file mode 100644 index 0000000000..668501e28c --- /dev/null +++ b/configs/app/features/validators.ts @@ -0,0 +1,29 @@ +import type { Feature } from './types'; +import { VALIDATORS_CHAIN_TYPE } from 'types/client/validators'; +import type { ValidatorsChainType } from 'types/client/validators'; + +import { getEnvValue } from '../utils'; + +const chainType = ((): ValidatorsChainType | undefined => { + const envValue = getEnvValue('NEXT_PUBLIC_VALIDATORS_CHAIN_TYPE') as ValidatorsChainType | undefined; + return envValue && VALIDATORS_CHAIN_TYPE.includes(envValue) ? envValue : undefined; +})(); + +const title = 'Validators list'; + +const config: Feature<{ chainType: ValidatorsChainType }> = (() => { + if (chainType) { + return Object.freeze({ + title, + isEnabled: true, + chainType, + }); + } + + return Object.freeze({ + title, + isEnabled: false, + }); +})(); + +export default config; diff --git a/configs/app/features/web3Wallet.ts b/configs/app/features/web3Wallet.ts index f4df4d56ea..893f92394b 100644 --- a/configs/app/features/web3Wallet.ts +++ b/configs/app/features/web3Wallet.ts @@ -21,7 +21,7 @@ const wallets = ((): Array | undefined => { const title = 'Web3 wallet integration (add token or network to the wallet)'; -const config: Feature<{ wallets: Array; addToken: { isDisabled: boolean }}> = (() => { +const config: Feature<{ wallets: Array; addToken: { isDisabled: boolean } }> = (() => { if (wallets && wallets.length > 0) { return Object.freeze({ title, diff --git a/configs/app/features/xStarScore.ts b/configs/app/features/xStarScore.ts new file mode 100644 index 0000000000..4f8efa262d --- /dev/null +++ b/configs/app/features/xStarScore.ts @@ -0,0 +1,23 @@ +import type { Feature } from './types'; + +import { getEnvValue } from '../utils'; + +const title = 'XStar score'; +const url = getEnvValue('NEXT_PUBLIC_XSTAR_SCORE_URL'); + +const config: Feature<{ url: string }> = (() => { + if (url) { + return Object.freeze({ + title, + url, + isEnabled: true, + }); + } + + return Object.freeze({ + title, + isEnabled: false, + }); +})(); + +export default config; diff --git a/configs/app/features/zkEvmRollup.ts b/configs/app/features/zkEvmRollup.ts deleted file mode 100644 index 7fda84b09f..0000000000 --- a/configs/app/features/zkEvmRollup.ts +++ /dev/null @@ -1,25 +0,0 @@ -import type { Feature } from './types'; - -import { getEnvValue } from '../utils'; - -const title = 'ZkEVM rollup (L2) chain'; - -const config: Feature<{ L1BaseUrl: string; withdrawalUrl?: string }> = (() => { - const L1BaseUrl = getEnvValue('NEXT_PUBLIC_L1_BASE_URL'); - const isZkEvm = getEnvValue('NEXT_PUBLIC_IS_ZKEVM_L2_NETWORK') === 'true'; - - if (isZkEvm && L1BaseUrl) { - return Object.freeze({ - title, - isEnabled: true, - L1BaseUrl, - }); - } - - return Object.freeze({ - title, - isEnabled: false, - }); -})(); - -export default config; diff --git a/configs/app/meta.ts b/configs/app/meta.ts index cf0f309534..3d7b777e03 100644 --- a/configs/app/meta.ts +++ b/configs/app/meta.ts @@ -1,13 +1,17 @@ import app from './app'; import { getEnvValue, getExternalAssetFilePath } from './utils'; -const defaultImageUrl = app.baseUrl + '/static/og_placeholder.png'; +const defaultImageUrl = '/static/og_placeholder.png'; const meta = Object.freeze({ - promoteBlockscoutInTitle: getEnvValue('NEXT_PUBLIC_PROMOTE_BLOCKSCOUT_IN_TITLE') || 'true', + promoteBlockscoutInTitle: getEnvValue('NEXT_PUBLIC_PROMOTE_BLOCKSCOUT_IN_TITLE') === 'false' ? false : true, og: { description: getEnvValue('NEXT_PUBLIC_OG_DESCRIPTION') || '', - imageUrl: getExternalAssetFilePath('NEXT_PUBLIC_OG_IMAGE_URL') || defaultImageUrl, + imageUrl: app.baseUrl + (getExternalAssetFilePath('NEXT_PUBLIC_OG_IMAGE_URL') || defaultImageUrl), + enhancedDataEnabled: getEnvValue('NEXT_PUBLIC_OG_ENHANCED_DATA_ENABLED') === 'true', + }, + seo: { + enhancedDataEnabled: getEnvValue('NEXT_PUBLIC_SEO_ENHANCED_DATA_ENABLED') === 'true', }, }); diff --git a/configs/app/services.ts b/configs/app/services.ts index 86df538215..ee7d3a0b66 100644 --- a/configs/app/services.ts +++ b/configs/app/services.ts @@ -1,7 +1,7 @@ import { getEnvValue } from './utils'; export default Object.freeze({ - reCaptcha: { + reCaptchaV2: { siteKey: getEnvValue('NEXT_PUBLIC_RE_CAPTCHA_APP_SITE_KEY'), }, }); diff --git a/configs/app/ui.ts b/configs/app/ui.ts index 907901221c..a5e79cb479 100644 --- a/configs/app/ui.ts +++ b/configs/app/ui.ts @@ -1,8 +1,13 @@ import type { ContractCodeIde } from 'types/client/contract'; -import { NAVIGATION_LINK_IDS, type NavItemExternal, type NavigationLinkId } from 'types/client/navigation-items'; -import type { ChainIndicatorId } from 'types/homepage'; +import { NAVIGATION_LINK_IDS, type NavItemExternal, type NavigationLinkId, type NavigationLayout } from 'types/client/navigation'; +import { HOME_STATS_WIDGET_IDS, type ChainIndicatorId, type HeroBannerConfig, type HomeStatsWidgetId } from 'types/homepage'; import type { NetworkExplorer } from 'types/networks'; +import type { ColorThemeId } from 'types/settings'; +import type { FontFamily } from 'types/ui'; +import { COLOR_THEMES } from 'lib/settings/colorTheme'; + +import * as features from './features'; import * as views from './ui/views'; import { getEnvValue, getExternalAssetFilePath, parseEnvJson } from './utils'; @@ -21,11 +26,34 @@ const hiddenLinks = (() => { return result; })(); -// eslint-disable-next-line max-len -const HOMEPAGE_PLATE_BACKGROUND_DEFAULT = 'radial-gradient(103.03% 103.03% at 0% 0%, rgba(183, 148, 244, 0.8) 0%, rgba(0, 163, 196, 0.8) 100%), var(--chakra-colors-blue-400)'; +const homePageStats: Array = (() => { + const parsedValue = parseEnvJson>(getEnvValue('NEXT_PUBLIC_HOMEPAGE_STATS')); + + if (!Array.isArray(parsedValue)) { + const rollupFeature = features.rollup; + + if (rollupFeature.isEnabled && [ 'zkEvm', 'zkSync', 'arbitrum' ].includes(rollupFeature.type)) { + return [ 'latest_batch', 'average_block_time', 'total_txs', 'wallet_addresses', 'gas_tracker' ]; + } + + return [ 'total_blocks', 'average_block_time', 'total_txs', 'wallet_addresses', 'gas_tracker' ]; + } + + return parsedValue.filter((item) => HOME_STATS_WIDGET_IDS.includes(item)); +})(); + +const highlightedRoutes = (() => { + const parsedValue = parseEnvJson>(getEnvValue('NEXT_PUBLIC_NAVIGATION_HIGHLIGHTED_ROUTES')); + return Array.isArray(parsedValue) ? parsedValue : []; +})(); + +const defaultColorTheme = (() => { + const envValue = getEnvValue('NEXT_PUBLIC_COLOR_THEME_DEFAULT') as ColorThemeId | undefined; + return COLOR_THEMES.find((theme) => theme.id === envValue); +})(); const UI = Object.freeze({ - sidebar: { + navigation: { logo: { 'default': getExternalAssetFilePath('NEXT_PUBLIC_NETWORK_LOGO'), dark: getExternalAssetFilePath('NEXT_PUBLIC_NETWORK_LOGO_DARK'), @@ -35,8 +63,10 @@ const UI = Object.freeze({ dark: getExternalAssetFilePath('NEXT_PUBLIC_NETWORK_ICON_DARK'), }, hiddenLinks, + highlightedRoutes, otherLinks: parseEnvJson>(getEnvValue('NEXT_PUBLIC_OTHER_LINKS')) || [], featuredNetworks: getExternalAssetFilePath('NEXT_PUBLIC_FEATURED_NETWORKS'), + layout: (getEnvValue('NEXT_PUBLIC_NAVIGATION_LAYOUT') || 'vertical') as NavigationLayout, }, footer: { links: getExternalAssetFilePath('NEXT_PUBLIC_FOOTER_LINKS'), @@ -45,9 +75,12 @@ const UI = Object.freeze({ }, homepage: { charts: parseEnvJson>(getEnvValue('NEXT_PUBLIC_HOMEPAGE_CHARTS')) || [], + stats: homePageStats, + heroBanner: parseEnvJson(getEnvValue('NEXT_PUBLIC_HOMEPAGE_HERO_BANNER_CONFIG')), + // !!! DEPRECATED !!! plate: { - background: getEnvValue('NEXT_PUBLIC_HOMEPAGE_PLATE_BACKGROUND') || HOMEPAGE_PLATE_BACKGROUND_DEFAULT, - textColor: getEnvValue('NEXT_PUBLIC_HOMEPAGE_PLATE_TEXT_COLOR') || 'white', + background: getEnvValue('NEXT_PUBLIC_HOMEPAGE_PLATE_BACKGROUND'), + textColor: getEnvValue('NEXT_PUBLIC_HOMEPAGE_PLATE_TEXT_COLOR'), }, showGasTracker: getEnvValue('NEXT_PUBLIC_HOMEPAGE_SHOW_GAS_TRACKER') === 'false' ? false : true, showAvgBlockTime: getEnvValue('NEXT_PUBLIC_HOMEPAGE_SHOW_AVG_BLOCK_TIME') === 'false' ? false : true, @@ -71,6 +104,15 @@ const UI = Object.freeze({ ides: { items: parseEnvJson>(getEnvValue('NEXT_PUBLIC_CONTRACT_CODE_IDES')) || [], }, + hasContractAuditReports: getEnvValue('NEXT_PUBLIC_HAS_CONTRACT_AUDIT_REPORTS') === 'true' ? true : false, + colorTheme: { + 'default': defaultColorTheme, + }, + fonts: { + heading: parseEnvJson(getEnvValue('NEXT_PUBLIC_FONT_FAMILY_HEADING')), + body: parseEnvJson(getEnvValue('NEXT_PUBLIC_FONT_FAMILY_BODY')), + }, + maxContentWidth: getEnvValue('NEXT_PUBLIC_MAX_CONTENT_WIDTH_ENABLED') === 'false' ? false : true, }); export default UI; diff --git a/configs/app/ui/views/address.ts b/configs/app/ui/views/address.ts index c2f0f4fb52..8972c02fce 100644 --- a/configs/app/ui/views/address.ts +++ b/configs/app/ui/views/address.ts @@ -1,5 +1,8 @@ -import type { AddressViewId, IdenticonType } from 'types/views/address'; -import { ADDRESS_VIEWS_IDS, IDENTICON_TYPES } from 'types/views/address'; +import type { VerifiedContractsFilter } from 'types/api/contracts'; +import type { SmartContractVerificationMethodExtra } from 'types/client/contract'; +import { SMART_CONTRACT_EXTRA_VERIFICATION_METHODS, SMART_CONTRACT_LANGUAGE_FILTERS } from 'types/client/contract'; +import type { AddressFormat, AddressViewId, IdenticonType } from 'types/views/address'; +import { ADDRESS_FORMATS, ADDRESS_VIEWS_IDS, IDENTICON_TYPES } from 'types/views/address'; import { getEnvValue, parseEnvJson } from 'configs/app/utils'; @@ -9,6 +12,28 @@ const identiconType: IdenticonType = (() => { return IDENTICON_TYPES.find((type) => value === type) || 'jazzicon'; })(); +const formats: Array = (() => { + const value = (parseEnvJson>(getEnvValue('NEXT_PUBLIC_VIEWS_ADDRESS_FORMAT')) || []) + .filter((format) => ADDRESS_FORMATS.includes(format)); + + if (value.length === 0) { + return [ 'base16' ]; + } + + return value; +})(); + +const bech32Prefix = (() => { + const value = getEnvValue('NEXT_PUBLIC_VIEWS_ADDRESS_BECH_32_PREFIX'); + + if (!value || !formats.includes('bech32')) { + return undefined; + } + + // these are the limits of the bech32 prefix - https://github.com/bitcoin/bips/blob/master/bip-0173.mediawiki#bech32 + return value.length >= 1 && value.length <= 83 ? value : undefined; +})(); + const hiddenViews = (() => { const parsedValue = parseEnvJson>(getEnvValue('NEXT_PUBLIC_VIEWS_ADDRESS_HIDDEN_VIEWS')) || []; @@ -24,10 +49,42 @@ const hiddenViews = (() => { return result; })(); +const extraVerificationMethods: Array = (() => { + const envValue = getEnvValue('NEXT_PUBLIC_VIEWS_CONTRACT_EXTRA_VERIFICATION_METHODS'); + if (envValue === 'none') { + return []; + } + + if (!envValue) { + return SMART_CONTRACT_EXTRA_VERIFICATION_METHODS; + } + + const parsedMethods = parseEnvJson>(envValue) || []; + + return parsedMethods.filter((method) => SMART_CONTRACT_EXTRA_VERIFICATION_METHODS.includes(method)); +})(); + +const languageFilters: Array = (() => { + const envValue = parseEnvJson>(getEnvValue('NEXT_PUBLIC_VIEWS_CONTRACT_LANGUAGE_FILTERS')); + if (!envValue) { + // "Scilla" is chain specific language, so we don't want to show it in default scenario + const DEFAULT_LANGUAGE_FILTERS = SMART_CONTRACT_LANGUAGE_FILTERS.filter((filter) => filter !== 'scilla'); + return DEFAULT_LANGUAGE_FILTERS; + } + + return envValue.filter((filter) => SMART_CONTRACT_LANGUAGE_FILTERS.includes(filter)); +})(); + const config = Object.freeze({ identiconType, + hashFormat: { + availableFormats: formats, + bech32Prefix, + }, hiddenViews, solidityscanEnabled: getEnvValue('NEXT_PUBLIC_VIEWS_CONTRACT_SOLIDITYSCAN_ENABLED') === 'true', + extraVerificationMethods, + languageFilters, }); export default config; diff --git a/configs/app/ui/views/nft.ts b/configs/app/ui/views/nft.ts index b0d9f9b28c..ab9636ccea 100644 --- a/configs/app/ui/views/nft.ts +++ b/configs/app/ui/views/nft.ts @@ -4,6 +4,9 @@ import { getEnvValue, parseEnvJson } from 'configs/app/utils'; const config = Object.freeze({ marketplaces: parseEnvJson>(getEnvValue('NEXT_PUBLIC_VIEWS_NFT_MARKETPLACES')) || [], + verifiedFetch: { + isEnabled: getEnvValue('NEXT_PUBLIC_HELIA_VERIFIED_FETCH_ENABLED') === 'false' ? false : true, + }, }); export default config; diff --git a/configs/app/utils.ts b/configs/app/utils.ts index 2ad992653c..b188d14e8d 100644 --- a/configs/app/utils.ts +++ b/configs/app/utils.ts @@ -5,7 +5,8 @@ export const replaceQuotes = (value: string | undefined) => value?.replaceAll('\ export const getEnvValue = (envName: string) => { // eslint-disable-next-line no-restricted-properties - const envs = isBrowser() ? window.__envs : process.env; + const envs = (isBrowser() ? window.__envs : process.env) ?? {}; + if (isBrowser() && envs.NEXT_PUBLIC_APP_INSTANCE === 'pw') { const storageValue = localStorage.getItem(envName); @@ -38,10 +39,22 @@ export const getExternalAssetFilePath = (envName: string) => { export const buildExternalAssetFilePath = (name: string, value: string) => { try { const fileName = name.replace(/^NEXT_PUBLIC_/, '').replace(/_URL$/, '').toLowerCase(); - const url = new URL(value); - const fileExtension = url.pathname.match(regexp.FILE_EXTENSION)?.[1]; - return `/assets/${ fileName }.${ fileExtension }`; + + const fileExtension = getAssetFileExtension(value); + if (!fileExtension) { + throw new Error('Cannot get file path'); + } + return `/assets/configs/${ fileName }.${ fileExtension }`; } catch (error) { return; } }; + +function getAssetFileExtension(value: string) { + try { + const url = new URL(value); + return url.pathname.match(regexp.FILE_EXTENSION)?.[1]; + } catch (error) { + return parseEnvJson(value) ? 'json' : undefined; + } +} diff --git a/configs/envs/.env.arbitrum b/configs/envs/.env.arbitrum new file mode 100644 index 0000000000..1fc81ce901 --- /dev/null +++ b/configs/envs/.env.arbitrum @@ -0,0 +1,44 @@ +# Set of ENVs for Arbitrum One network explorer +# https://arbitrum.blockscout.com +# This is an auto-generated file. To update all values, run "yarn dev:preset:sync --name=arbitrum" + +# Local ENVs +NEXT_PUBLIC_APP_PROTOCOL=http +NEXT_PUBLIC_APP_HOST=localhost +NEXT_PUBLIC_APP_PORT=3000 +NEXT_PUBLIC_APP_ENV=development +NEXT_PUBLIC_API_WEBSOCKET_PROTOCOL=ws + +# Instance ENVs +NEXT_PUBLIC_ADMIN_SERVICE_API_HOST=https://admin-rs.services.blockscout.com +NEXT_PUBLIC_API_BASE_PATH=/ +NEXT_PUBLIC_API_HOST=arbitrum.blockscout.com +NEXT_PUBLIC_API_SPEC_URL=https://raw.githubusercontent.com/blockscout/blockscout-api-v2-swagger/main/swagger.yaml +NEXT_PUBLIC_CONTRACT_CODE_IDES=[{'title':'Remix IDE','url':'https://remix.ethereum.org/?address={hash}&blockscout={domain}','icon_url':'https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/ide-icons/remix.png'}] +NEXT_PUBLIC_CONTRACT_INFO_API_HOST=https://contracts-info.services.blockscout.com +NEXT_PUBLIC_GRAPHIQL_TRANSACTION=0x37c798810d49ba132b40efe7f4fdf6806a8fc58226bb5e185ddc91f896577abf +NEXT_PUBLIC_HOMEPAGE_CHARTS=['daily_txs'] +NEXT_PUBLIC_HOMEPAGE_PLATE_BACKGROUND=rgba(27, 74, 221, 1) +NEXT_PUBLIC_IS_ACCOUNT_SUPPORTED=true +NEXT_PUBLIC_LOGOUT_URL=https://blockscout-arbitrum.us.auth0.com/v2/logout +NEXT_PUBLIC_MARKETPLACE_ENABLED=false +NEXT_PUBLIC_METADATA_SERVICE_API_HOST=https://metadata.services.blockscout.com +NEXT_PUBLIC_NETWORK_CURRENCY_DECIMALS=18 +NEXT_PUBLIC_NETWORK_CURRENCY_NAME=ETH +NEXT_PUBLIC_NETWORK_CURRENCY_SYMBOL=ETH +NEXT_PUBLIC_NETWORK_EXPLORERS=[{'title':'GeckoTerminal','logo':'https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/explorer-logos/geckoterminal.png','baseUrl':'https://www.geckoterminal.com/','paths':{'token':'/arbitrum/pools'}}] +NEXT_PUBLIC_NETWORK_ICON=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/network-icons/arbitrum-one-icon-light.svg +NEXT_PUBLIC_NETWORK_ICON_DARK=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/network-icons/arbitrum-one-icon-dark.svg +NEXT_PUBLIC_NETWORK_ID=42161 +NEXT_PUBLIC_NETWORK_LOGO=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/network-logos/arbitrum-one-logo-light.svg +NEXT_PUBLIC_NETWORK_LOGO_DARK=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/network-logos/arbitrum-one-logo-dark.svg +NEXT_PUBLIC_NETWORK_NAME=Arbitrum One +NEXT_PUBLIC_NETWORK_RPC_URL=https://arbitrum.llamarpc.com +NEXT_PUBLIC_NETWORK_SHORT_NAME=Arbitrum One +NEXT_PUBLIC_OG_ENHANCED_DATA_ENABLED=true +NEXT_PUBLIC_OG_IMAGE_URL=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/og-images/arbitrum-one.png +NEXT_PUBLIC_ROLLUP_L1_BASE_URL=https://eth.blockscout.com +NEXT_PUBLIC_ROLLUP_TYPE=arbitrum +NEXT_PUBLIC_TRANSACTION_INTERPRETATION_PROVIDER=blockscout +NEXT_PUBLIC_VIEWS_CONTRACT_SOLIDITYSCAN_ENABLED=true +NEXT_PUBLIC_VISUALIZE_API_HOST=https://visualizer.services.blockscout.com \ No newline at end of file diff --git a/configs/envs/.env.arbitrum_nova b/configs/envs/.env.arbitrum_nova new file mode 100644 index 0000000000..2888565d7d --- /dev/null +++ b/configs/envs/.env.arbitrum_nova @@ -0,0 +1,42 @@ +# Set of ENVs for Arbitrum One network explorer +# https://arbitrum.blockscout.com +# This is an auto-generated file. To update all values, run "yarn dev:preset:sync --name=arbitrum" + +# Local ENVs +NEXT_PUBLIC_APP_PROTOCOL=http +NEXT_PUBLIC_APP_HOST=localhost +NEXT_PUBLIC_APP_PORT=3000 +NEXT_PUBLIC_APP_ENV=development +NEXT_PUBLIC_API_WEBSOCKET_PROTOCOL=ws + +# Instance ENVs +NEXT_PUBLIC_ADMIN_SERVICE_API_HOST=https://admin-rs.services.blockscout.com +NEXT_PUBLIC_API_BASE_PATH=/ +NEXT_PUBLIC_API_HOST=arbitrum-nova.blockscout.com +NEXT_PUBLIC_API_SPEC_URL=https://raw.githubusercontent.com/blockscout/blockscout-api-v2-swagger/main/swagger.yaml +NEXT_PUBLIC_CONTRACT_CODE_IDES=[{'title':'Remix IDE','url':'https://remix.ethereum.org/?address={hash}&blockscout={domain}','icon_url':'https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/ide-icons/remix.png'}] +NEXT_PUBLIC_CONTRACT_INFO_API_HOST=https://contracts-info.services.blockscout.com +NEXT_PUBLIC_GRAPHIQL_TRANSACTION=0x37c798810d49ba132b40efe7f4fdf6806a8fc58226bb5e185ddc91f896577abf +NEXT_PUBLIC_HOMEPAGE_CHARTS=['daily_txs'] +NEXT_PUBLIC_HOMEPAGE_PLATE_BACKGROUND=rgba(27, 74, 221, 1) +NEXT_PUBLIC_IS_ACCOUNT_SUPPORTED=true +NEXT_PUBLIC_LOGOUT_URL=https://blockscout-arbitrum.us.auth0.com/v2/logout +NEXT_PUBLIC_MARKETPLACE_ENABLED=false +NEXT_PUBLIC_METADATA_SERVICE_API_HOST=https://metadata.services.blockscout.com +NEXT_PUBLIC_NETWORK_CURRENCY_DECIMALS=18 +NEXT_PUBLIC_NETWORK_CURRENCY_NAME=ETH +NEXT_PUBLIC_NETWORK_CURRENCY_SYMBOL=ETH +NEXT_PUBLIC_NETWORK_EXPLORERS=[{'title':'GeckoTerminal','logo':'https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/explorer-logos/geckoterminal.png','baseUrl':'https://www.geckoterminal.com/','paths':{'token':'/arbitrum/pools'}}] +NEXT_PUBLIC_NETWORK_ICON=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/network-icons/arbitrum-nova-icon.svg +NEXT_PUBLIC_NETWORK_ID=42170 +NEXT_PUBLIC_NETWORK_LOGO=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/network-icons/arbitrum-nova.svg +NEXT_PUBLIC_NETWORK_NAME=Arbitrum Nova +NEXT_PUBLIC_NETWORK_RPC_URL=https://arbitrum.llamarpc.com +NEXT_PUBLIC_NETWORK_SHORT_NAME=Arbitrum Nova +NEXT_PUBLIC_OG_ENHANCED_DATA_ENABLED=true +NEXT_PUBLIC_OG_IMAGE_URL=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/og-images/arbitrum-nova.png +NEXT_PUBLIC_ROLLUP_L1_BASE_URL=https://eth.blockscout.com +NEXT_PUBLIC_ROLLUP_TYPE=arbitrum +NEXT_PUBLIC_TRANSACTION_INTERPRETATION_PROVIDER=blockscout +NEXT_PUBLIC_VIEWS_CONTRACT_SOLIDITYSCAN_ENABLED=true +NEXT_PUBLIC_VISUALIZE_API_HOST=https://visualizer.services.blockscout.com \ No newline at end of file diff --git a/configs/envs/.env.arbitrum_sepolia b/configs/envs/.env.arbitrum_sepolia new file mode 100644 index 0000000000..58155f9212 --- /dev/null +++ b/configs/envs/.env.arbitrum_sepolia @@ -0,0 +1,47 @@ +# Set of ENVs for Arbitrum Sepolia network explorer +# https://arbitrum-sepolia.blockscout.com +# This is an auto-generated file. To update all values, run "yarn dev:preset:sync --name=arbitrum_sepolia" + +# Local ENVs +NEXT_PUBLIC_APP_PROTOCOL=http +NEXT_PUBLIC_APP_HOST=localhost +NEXT_PUBLIC_APP_PORT=3000 +NEXT_PUBLIC_APP_ENV=development +NEXT_PUBLIC_API_WEBSOCKET_PROTOCOL=ws + +# Instance ENVs +NEXT_PUBLIC_ADMIN_SERVICE_API_HOST=https://admin-rs.services.blockscout.com +NEXT_PUBLIC_API_BASE_PATH=/ +NEXT_PUBLIC_API_HOST=arbitrum-sepolia.blockscout.com +NEXT_PUBLIC_API_SPEC_URL=https://raw.githubusercontent.com/blockscout/blockscout-api-v2-swagger/main/swagger.yaml +NEXT_PUBLIC_CONTRACT_CODE_IDES=[{'title':'Remix IDE','url':'https://remix.ethereum.org/?address={hash}&blockscout={domain}','icon_url':'https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/ide-icons/remix.png'}] +NEXT_PUBLIC_CONTRACT_INFO_API_HOST=https://contracts-info.services.blockscout.com +NEXT_PUBLIC_FEATURED_NETWORKS=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/featured-networks/arbitrum-sepolia.json +NEXT_PUBLIC_GRAPHIQL_TRANSACTION=0xb730960249381c72588024f5e213abd8e032d968aeb9629103e70677b0850bfa +NEXT_PUBLIC_HAS_USER_OPS=true +NEXT_PUBLIC_HOMEPAGE_CHARTS=['daily_txs'] +NEXT_PUBLIC_HOMEPAGE_HERO_BANNER_CONFIG={'background':['rgba(27, 74, 221, 1)']} +NEXT_PUBLIC_IS_ACCOUNT_SUPPORTED=true +NEXT_PUBLIC_IS_TESTNET=true +NEXT_PUBLIC_LOGOUT_URL=https://blockscout-arbitrum.us.auth0.com/v2/logout +NEXT_PUBLIC_MARKETPLACE_ENABLED=false +NEXT_PUBLIC_METADATA_SERVICE_API_HOST=https://metadata.services.blockscout.com +NEXT_PUBLIC_NETWORK_CURRENCY_DECIMALS=18 +NEXT_PUBLIC_NETWORK_CURRENCY_NAME=ETH +NEXT_PUBLIC_NETWORK_CURRENCY_SYMBOL=ETH +NEXT_PUBLIC_NETWORK_ICON=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/network-icons/arbitrum-sepolia.svg +NEXT_PUBLIC_NETWORK_ICON_DARK=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/network-icons/arbitrum-sepolia-dark.svg +NEXT_PUBLIC_NETWORK_ID=421614 +NEXT_PUBLIC_NETWORK_LOGO=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/network-logos/arbitrum-sepolia.svg +NEXT_PUBLIC_NETWORK_LOGO_DARK=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/network-logos/arbitrum-sepolia-dark.svg +NEXT_PUBLIC_NETWORK_NAME=Arbitrum Sepolia +NEXT_PUBLIC_NETWORK_RPC_URL=https://sepolia-rollup.arbitrum.io/rpc +NEXT_PUBLIC_NETWORK_SHORT_NAME=Arbitrum Sepolia +NEXT_PUBLIC_OG_ENHANCED_DATA_ENABLED=true +NEXT_PUBLIC_OG_IMAGE_URL=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/og-images/arbitrum-sepolia.png +NEXT_PUBLIC_ROLLUP_L1_BASE_URL=https://eth-sepolia.blockscout.com +NEXT_PUBLIC_ROLLUP_TYPE=arbitrum +NEXT_PUBLIC_SENTRY_DSN=https://fdcd971162e04694bf03564c5be3d291@o1222505.ingest.sentry.io/4503902500421632 +NEXT_PUBLIC_STATS_API_HOST=https://stats-arbitrum-sepolia.k8s-prod-2.blockscout.com +NEXT_PUBLIC_TRANSACTION_INTERPRETATION_PROVIDER=blockscout +NEXT_PUBLIC_VISUALIZE_API_HOST=https://visualizer.services.blockscout.com \ No newline at end of file diff --git a/configs/envs/.env.base b/configs/envs/.env.base new file mode 100644 index 0000000000..8eab44c966 --- /dev/null +++ b/configs/envs/.env.base @@ -0,0 +1,66 @@ +# Set of ENVs for Base Mainnet network explorer +# https://base.blockscout.com +# This is an auto-generated file. To update all values, run "yarn dev:preset:sync --name=base" + +# Local ENVs +NEXT_PUBLIC_APP_PROTOCOL=http +NEXT_PUBLIC_APP_HOST=localhost +NEXT_PUBLIC_APP_PORT=3000 +NEXT_PUBLIC_APP_ENV=development +NEXT_PUBLIC_API_WEBSOCKET_PROTOCOL=ws + +# Instance ENVs +NEXT_PUBLIC_AD_ADBUTLER_CONFIG_DESKTOP={ "id": "728301", "width": "728", "height": "90" } +NEXT_PUBLIC_AD_ADBUTLER_CONFIG_MOBILE={ "id": "728302", "width": "320", "height": "100" } +NEXT_PUBLIC_AD_BANNER_ADDITIONAL_PROVIDER=adbutler +NEXT_PUBLIC_ADMIN_SERVICE_API_HOST=https://admin-rs.services.blockscout.com +NEXT_PUBLIC_API_BASE_PATH=/ +NEXT_PUBLIC_API_HOST=base.blockscout.com +NEXT_PUBLIC_API_SPEC_URL=https://raw.githubusercontent.com/blockscout/blockscout-api-v2-swagger/main/swagger.yaml +NEXT_PUBLIC_CONTRACT_CODE_IDES=[{'title':'Remix IDE','url':'https://remix.ethereum.org/?address={hash}&blockscout={domain}','icon_url':'https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/ide-icons/remix.png'}] +NEXT_PUBLIC_CONTRACT_INFO_API_HOST=https://contracts-info.services.blockscout.com +NEXT_PUBLIC_DEFI_DROPDOWN_ITEMS=[{'text':'Swap','icon':'swap','dappId':'aerodrome'},{'text':'Payment link','icon':'payment_link','dappId':'peanut-protocol'},{'text':'Get gas','icon':'gas','dappId':'smol-refuel'}] +NEXT_PUBLIC_FEATURED_NETWORKS=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/featured-networks/base-mainnet.json +NEXT_PUBLIC_FOOTER_LINKS=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/footer-links/base-mainnet.json +NEXT_PUBLIC_GRAPHIQL_TRANSACTION=0xfd5c5dae7b69fe29e61d19b9943e688aa0f1be1e983c4fba8fe985f90ff69d5f +NEXT_PUBLIC_HAS_USER_OPS=true +NEXT_PUBLIC_HIDE_INDEXING_ALERT_INT_TXS=true +NEXT_PUBLIC_HOMEPAGE_CHARTS=['daily_txs'] +NEXT_PUBLIC_HOMEPAGE_PLATE_BACKGROUND=linear-gradient(136.9deg,rgb(107 94 236) 1.5%,rgb(0 82 255) 56.84%,rgb(82 62 231) 98.54%) +NEXT_PUBLIC_IS_ACCOUNT_SUPPORTED=true +NEXT_PUBLIC_LOGOUT_URL=https://basechain.us.auth0.com/v2/logout +NEXT_PUBLIC_MARKETPLACE_BANNER_CONTENT_URL=https://gist.githubusercontent.com/maxaleks/0d18fc309ff499075127b364cc69306d/raw/2a51f961a8c1b9f884f2ab7eed79d4b69330e1ae/peanut_protocol_banner.html +NEXT_PUBLIC_MARKETPLACE_BANNER_LINK_URL=https://base.blockscout.com/apps/peanut-protocol +NEXT_PUBLIC_MARKETPLACE_CATEGORIES_URL=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/marketplace-categories/default.json +NEXT_PUBLIC_MARKETPLACE_ENABLED=true +NEXT_PUBLIC_MARKETPLACE_SECURITY_REPORTS_URL=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/marketplace-security-reports/default.json +NEXT_PUBLIC_MARKETPLACE_SUBMIT_FORM=https://airtable.com/appiy5yijZpMMSKjT/shr6uMGPKjj1DK7NL +NEXT_PUBLIC_MARKETPLACE_SUGGEST_IDEAS_FORM=https://airtable.com/appiy5yijZpMMSKjT/pag3t82DUCyhGRZZO/form +NEXT_PUBLIC_METADATA_SERVICE_API_HOST=https://metadata.services.blockscout.com +NEXT_PUBLIC_METASUITES_ENABLED=true +NEXT_PUBLIC_MULTICHAIN_BALANCE_PROVIDER_CONFIG=[{'name': 'zerion', 'dapp_id': 'zerion', 'url_template': 'https://app.zerion.io/{address}/overview?utm_source=blockscout&utm_medium=address', 'logo': 'https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/marketplace-logos/zerion.svg'}] +NEXT_PUBLIC_NAME_SERVICE_API_HOST=https://bens.services.blockscout.com +NEXT_PUBLIC_NAVIGATION_HIGHLIGHTED_ROUTES=['/apps'] +NEXT_PUBLIC_NETWORK_CURRENCY_DECIMALS=18 +NEXT_PUBLIC_NETWORK_CURRENCY_NAME=Ether +NEXT_PUBLIC_NETWORK_CURRENCY_SYMBOL=ETH +NEXT_PUBLIC_NETWORK_EXPLORERS=[{'title':'GeckoTerminal','logo':'https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/explorer-logos/geckoterminal.png','baseUrl':'https://www.geckoterminal.com/','paths':{'token':'/base/pools'}},{'title':'L2scan','baseUrl':'https://base.l2scan.co/','paths':{'tx':'/tx','address':'/address','token':'/token','block':'/block'}},{'title':'Tenderly','logo':'https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/explorer-logos/tenderly.png','baseUrl':'https://dashboard.tenderly.co','paths':{'tx':'/tx/base'}},{'title':'3xpl','logo':'https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/explorer-logos/3xpl.png','baseUrl':'https://3xpl.com/','paths':{'tx':'/base/transaction','address':'/base/address'}}] +NEXT_PUBLIC_NETWORK_ICON=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/network-icons/base.svg +NEXT_PUBLIC_NETWORK_ID=8453 +NEXT_PUBLIC_NETWORK_LOGO=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/network-logos/base.svg +NEXT_PUBLIC_NETWORK_NAME=Base Mainnet +NEXT_PUBLIC_NETWORK_RPC_URL=https://mainnet.base.org/ +NEXT_PUBLIC_NETWORK_SHORT_NAME=Mainnet +NEXT_PUBLIC_NETWORK_VERIFICATION_TYPE=validation +NEXT_PUBLIC_OG_ENHANCED_DATA_ENABLED=true +NEXT_PUBLIC_OG_IMAGE_URL=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/og-images/base-mainnet.png +NEXT_PUBLIC_OTHER_LINKS=[{'url':'https://base.drpc.org?ref=559183','text':'Public RPC'}] +NEXT_PUBLIC_ROLLUP_L1_BASE_URL=https://eth.blockscout.com/ +NEXT_PUBLIC_ROLLUP_L2_WITHDRAWAL_URL=https://bridge.base.org/withdraw +NEXT_PUBLIC_ROLLUP_TYPE=optimistic +NEXT_PUBLIC_SAFE_TX_SERVICE_URL=https://safe-transaction-base.safe.global +NEXT_PUBLIC_STATS_API_HOST=https://stats-l2-base-mainnet.k8s-prod-1.blockscout.com +NEXT_PUBLIC_TRANSACTION_INTERPRETATION_PROVIDER=blockscout +NEXT_PUBLIC_VIEWS_ADDRESS_IDENTICON_TYPE=gradient_avatar +NEXT_PUBLIC_VIEWS_CONTRACT_SOLIDITYSCAN_ENABLED=true +NEXT_PUBLIC_VISUALIZE_API_HOST=https://visualizer.services.blockscout.com \ No newline at end of file diff --git a/configs/envs/.env.blackfort_testnet b/configs/envs/.env.blackfort_testnet new file mode 100644 index 0000000000..eeaad00054 --- /dev/null +++ b/configs/envs/.env.blackfort_testnet @@ -0,0 +1,44 @@ +# Set of ENVs for BXN Testnet network explorer +# https://blackfort-testnet.blockscout.com +# This is an auto-generated file. To update all values, run "yarn dev:preset:sync --name=blackfort_testnet" + +# Local ENVs +NEXT_PUBLIC_APP_PROTOCOL=http +NEXT_PUBLIC_APP_HOST=localhost +NEXT_PUBLIC_APP_PORT=3000 +NEXT_PUBLIC_APP_ENV=development +NEXT_PUBLIC_API_WEBSOCKET_PROTOCOL=ws + +# Instance ENVs +NEXT_PUBLIC_ADMIN_SERVICE_API_HOST=https://admin-rs.services.blockscout.com +NEXT_PUBLIC_API_BASE_PATH=/ +NEXT_PUBLIC_API_HOST=blackfort-testnet.blockscout.com +NEXT_PUBLIC_API_SPEC_URL=https://raw.githubusercontent.com/blockscout/blockscout-api-v2-swagger/main/swagger.yaml +NEXT_PUBLIC_CONTRACT_CODE_IDES=[{'title':'Remix IDE','url':'https://remix.ethereum.org/?address={hash}&blockscout={domain}','icon_url':'https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/ide-icons/remix.png'}] +NEXT_PUBLIC_CONTRACT_INFO_API_HOST=https://contracts-info.services.blockscout.com +NEXT_PUBLIC_FEATURED_NETWORKS=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/featured-networks/blackfort-testnet.json +NEXT_PUBLIC_FOOTER_LINKS=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/footer-links/blackfort.json +NEXT_PUBLIC_GRAPHIQL_TRANSACTION=0xcb4140e22cde3412eb5aecdedf2403032c7a251f5c96b11122aca5b1b88ed953 +NEXT_PUBLIC_HOMEPAGE_CHARTS=['daily_txs'] +NEXT_PUBLIC_HOMEPAGE_PLATE_BACKGROUND=linear-gradient(92deg, rgb(3, 150, 254) 0.24%, rgb(36, 209, 245) 98.31%) +NEXT_PUBLIC_IS_TESTNET=true +NEXT_PUBLIC_MARKETPLACE_ENABLED=false +NEXT_PUBLIC_METADATA_SERVICE_API_HOST=https://metadata.services.blockscout.com +NEXT_PUBLIC_NETWORK_CURRENCY_DECIMALS=18 +NEXT_PUBLIC_NETWORK_CURRENCY_NAME=TBXN +NEXT_PUBLIC_NETWORK_CURRENCY_SYMBOL=TBXN +NEXT_PUBLIC_NETWORK_ICON=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/network-icons/blackfort.svg +NEXT_PUBLIC_NETWORK_ICON_DARK=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/network-icons/blackfort-dark.svg +NEXT_PUBLIC_NETWORK_ID=4777 +NEXT_PUBLIC_NETWORK_LOGO=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/network-logos/blackfort.svg +NEXT_PUBLIC_NETWORK_LOGO_DARK=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/network-logos/blackfort-dark.svg +NEXT_PUBLIC_NETWORK_NAME=BXN Testnet +NEXT_PUBLIC_NETWORK_RPC_URL=https://testnet.blackfort.network/rpc +NEXT_PUBLIC_NETWORK_SHORT_NAME=BXN Testnet +NEXT_PUBLIC_OG_ENHANCED_DATA_ENABLED=true +NEXT_PUBLIC_OG_IMAGE_URL=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/og-images/blackfort.png +NEXT_PUBLIC_STATS_API_HOST=https://stats-blackfort-testnet.k8s.blockscout.com +NEXT_PUBLIC_TRANSACTION_INTERPRETATION_PROVIDER=blockscout +NEXT_PUBLIC_VIEWS_CONTRACT_SOLIDITYSCAN_ENABLED=true +NEXT_PUBLIC_VISUALIZE_API_HOST=https://visualizer.services.blockscout.com +NEXT_PUBLIC_VALIDATORS_CHAIN_TYPE=blackfort \ No newline at end of file diff --git a/configs/envs/.env.celo_alfajores b/configs/envs/.env.celo_alfajores new file mode 100644 index 0000000000..d93f9e36ae --- /dev/null +++ b/configs/envs/.env.celo_alfajores @@ -0,0 +1,43 @@ +# Set of ENVs for Celo Alfajores network explorer +# https://celo-alfajores.blockscout.com +# This is an auto-generated file. To update all values, run "yarn dev:preset:sync --name=celo_alfajores" + +# Local ENVs +NEXT_PUBLIC_APP_PROTOCOL=http +NEXT_PUBLIC_APP_HOST=localhost +NEXT_PUBLIC_APP_PORT=3000 +NEXT_PUBLIC_APP_ENV=development +NEXT_PUBLIC_API_WEBSOCKET_PROTOCOL=ws + +NEXT_PUBLIC_CELO_ENABLED=true +NEXT_PUBLIC_CELO_L2_UPGRADE_BLOCK=26369280 + +# Instance ENVs +NEXT_PUBLIC_API_BASE_PATH=/ +NEXT_PUBLIC_API_HOST=celo-alfajores.blockscout.com +NEXT_PUBLIC_API_SPEC_URL=https://raw.githubusercontent.com/blockscout/blockscout-api-v2-swagger/main/swagger.yaml +NEXT_PUBLIC_CONTRACT_CODE_IDES=[{'title':'Remix IDE','url':'https://remix.ethereum.org/?address={hash}&blockscout={domain}','icon_url':'https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/ide-icons/remix.png'}] +NEXT_PUBLIC_GAS_TRACKER_ENABLED=false +NEXT_PUBLIC_HOMEPAGE_CHARTS=['daily_txs'] +NEXT_PUBLIC_HOMEPAGE_PLATE_BACKGROUND=rgba(252, 255, 82, 1) +NEXT_PUBLIC_HOMEPAGE_PLATE_TEXT_COLOR=rgba(0, 0, 0, 1) +NEXT_PUBLIC_IS_TESTNET=true +NEXT_PUBLIC_MARKETPLACE_ENABLED=false +NEXT_PUBLIC_METADATA_SERVICE_API_HOST=https://metadata.services.blockscout.com +NEXT_PUBLIC_NETWORK_CURRENCY_DECIMALS=18 +NEXT_PUBLIC_NETWORK_CURRENCY_NAME=CELO +NEXT_PUBLIC_NETWORK_CURRENCY_SYMBOL=CELO +NEXT_PUBLIC_NETWORK_ICON=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/network-icons/celo-icon-light.svg +NEXT_PUBLIC_NETWORK_ICON_DARK=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/network-icons/celo-icon-dark.svg +NEXT_PUBLIC_NETWORK_ID=44787 +NEXT_PUBLIC_NETWORK_LOGO=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/network-logos/celo-logo-light.svg +NEXT_PUBLIC_NETWORK_LOGO_DARK=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/network-logos/celo-logo-dark.svg +NEXT_PUBLIC_NETWORK_NAME=Celo Alfajores +NEXT_PUBLIC_NETWORK_RPC_URL=https://alfajores-forno.celo-testnet.org +NEXT_PUBLIC_NETWORK_SHORT_NAME=Alfajores +NEXT_PUBLIC_OG_ENHANCED_DATA_ENABLED=true +NEXT_PUBLIC_OG_IMAGE_URL=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/og-images/celo.png +NEXT_PUBLIC_TRANSACTION_INTERPRETATION_PROVIDER=blockscout +NEXT_PUBLIC_VIEWS_CONTRACT_SOLIDITYSCAN_ENABLED=true +NEXT_PUBLIC_VISUALIZE_API_HOST=https://visualizer.services.blockscout.com +NEXT_PUBLIC_HOMEPAGE_STATS=['total_blocks','average_block_time','total_txs','wallet_addresses','gas_tracker','current_epoch'] \ No newline at end of file diff --git a/configs/envs/.env.eth b/configs/envs/.env.eth index 75ef364321..215b86e5d1 100644 --- a/configs/envs/.env.eth +++ b/configs/envs/.env.eth @@ -1,48 +1,70 @@ # Set of ENVs for Ethereum network explorer -# https://eth.blockscout.com/ +# https://eth.blockscout.com +# This is an auto-generated file. To update all values, run "yarn dev:preset:sync --name=eth" -# app configuration +# Local ENVs NEXT_PUBLIC_APP_PROTOCOL=http NEXT_PUBLIC_APP_HOST=localhost NEXT_PUBLIC_APP_PORT=3000 +NEXT_PUBLIC_APP_ENV=development +NEXT_PUBLIC_API_WEBSOCKET_PROTOCOL=ws -# blockchain parameters -NEXT_PUBLIC_NETWORK_NAME=Ethereum -NEXT_PUBLIC_NETWORK_SHORT_NAME=ETH -NEXT_PUBLIC_NETWORK_ID=1 -NEXT_PUBLIC_NETWORK_CURRENCY_NAME=Ether -NEXT_PUBLIC_NETWORK_CURRENCY_SYMBOL=ETH -NEXT_PUBLIC_NETWORK_CURRENCY_DECIMALS=18 -NEXT_PUBLIC_NETWORK_VERIFICATION_TYPE=validation -NEXT_PUBLIC_NETWORK_RPC_URL=https://eth.llamarpc.com - -# api configuration -NEXT_PUBLIC_API_HOST=eth.blockscout.com +# Instance ENVs +NEXT_PUBLIC_AD_ADBUTLER_CONFIG_DESKTOP={ "id": "728471", "width": "728", "height": "90" } +NEXT_PUBLIC_AD_ADBUTLER_CONFIG_MOBILE={ "id": "728470", "width": "320", "height": "100" } +NEXT_PUBLIC_AD_BANNER_ADDITIONAL_PROVIDER=adbutler +NEXT_PUBLIC_ADMIN_SERVICE_API_HOST=https://admin-rs.services.blockscout.com NEXT_PUBLIC_API_BASE_PATH=/ - -# ui config -## homepage -NEXT_PUBLIC_HOMEPAGE_CHARTS=['daily_txs', 'coin_price', 'market_cap'] -## sidebar -NEXT_PUBLIC_FEATURED_NETWORKS=https://raw.githubusercontent.com/blockscout/frontend-configs/dev/configs/featured-networks/eth.json -## footer -##views -NEXT_PUBLIC_VIEWS_NFT_MARKETPLACES=[{'name':'OpenSea','collection_url':'https://opensea.io/assets/ethereum/{hash}','instance_url':'https://opensea.io/assets/ethereum/{hash}/{id}','logo_url':'https://opensea.io/static/images/logos/opensea-logo.svg'},{'name':'LooksRare','collection_url':'https://looksrare.org/collections/{hash}','instance_url':'https://looksrare.org/collections/{hash}/{id}','logo_url':'https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/nft-marketplace-logos/looks-rare.png'}] -## misc -NEXT_PUBLIC_NETWORK_EXPLORERS=[{'title':'Etherscan','baseUrl':'https://etherscan.io/','paths':{'tx':'/tx','address':'/address','token':'/token','block':'/block'}}] - -# app features -NEXT_PUBLIC_APP_ENV=development -NEXT_PUBLIC_GRAPHIQL_TRANSACTION=0xf7d4972356e6ae44ae948d0cf19ef2beaf0e574c180997e969a2837da15e349d +NEXT_PUBLIC_API_HOST=eth.blockscout.com +NEXT_PUBLIC_API_SPEC_URL=https://raw.githubusercontent.com/blockscout/blockscout-api-v2-swagger/main/swagger.yaml +NEXT_PUBLIC_CONTRACT_CODE_IDES=[{'title':'Remix IDE','url':'https://remix.ethereum.org/?address={hash}&blockscout={domain}','icon_url':'https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/ide-icons/remix.png'}] +NEXT_PUBLIC_CONTRACT_INFO_API_HOST=https://contracts-info.services.blockscout.com +NEXT_PUBLIC_DATA_AVAILABILITY_ENABLED=true +NEXT_PUBLIC_DEFI_DROPDOWN_ITEMS=[{'text':'Swapscout','icon':'swap','dappId':'swapscout'},{'text':'Disperse','icon':'txn_batches_slim','dappId':'smol'},{'text':'Payment link','icon':'payment_link','dappId':'peanut-protocol'},{'text':'Get gas','icon':'gas','dappId':'smol-refuel'}] +NEXT_PUBLIC_FEATURED_NETWORKS=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/featured-networks/eth.json +NEXT_PUBLIC_FOOTER_LINKS=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/footer-links/eth-mainnet.json +NEXT_PUBLIC_GAS_REFUEL_PROVIDER_CONFIG={'name': 'Need gas?', 'url_template': 'https://smolrefuel.com/?outboundChain={chainId}&partner=blockscout&utm_source=blockscout&disableBridges=true', 'dapp_id': 'smol-refuel', 'logo': 'https://blockscout-content.s3.amazonaws.com/smolrefuel-logo-action-button.png'} +NEXT_PUBLIC_GRAPHIQL_TRANSACTION=0xd01175f1efa23f36c5579b3c13e2bbd0885017643a7efef5cbcb6b474384dfa8 NEXT_PUBLIC_HAS_BEACON_CHAIN=true +NEXT_PUBLIC_HAS_CONTRACT_AUDIT_REPORTS=true +NEXT_PUBLIC_HAS_USER_OPS=true +NEXT_PUBLIC_HIDE_INDEXING_ALERT_BLOCKS=true +NEXT_PUBLIC_HOMEPAGE_CHARTS=['daily_txs', 'coin_price', 'market_cap'] NEXT_PUBLIC_IS_ACCOUNT_SUPPORTED=true -NEXT_PUBLIC_AUTH_URL=http://localhost:3000 -NEXT_PUBLIC_LOGOUT_URL=https://blockscoutcom.us.auth0.com/v2/logout -NEXT_PUBLIC_STATS_API_HOST=https://stats-eth-main.k8s.blockscout.com -NEXT_PUBLIC_VISUALIZE_API_HOST=https://visualizer.services.blockscout.com -NEXT_PUBLIC_CONTRACT_INFO_API_HOST=https://contracts-info.services.blockscout.com -NEXT_PUBLIC_ADMIN_SERVICE_API_HOST=https://admin-rs.services.blockscout.com +NEXT_PUBLIC_LOGOUT_URL=https://ethereum-mainnet.us.auth0.com/v2/logout +NEXT_PUBLIC_MAINTENANCE_ALERT_MESSAGE=

Participated in our recent Blockscout activities? Check your eligibility and claim your NFT Scout badges. More exciting things are coming soon!

+NEXT_PUBLIC_MARKETPLACE_BANNER_CONTENT_URL=https://gist.githubusercontent.com/maikReal/974c47f86a3158c1a86b092ae2f044b3/raw/abcc7e02150cd85d4974503a0357162c0a2c35a9/merits-banner.html +NEXT_PUBLIC_MARKETPLACE_BANNER_LINK_URL=https://swap.blockscout.com?utm_source=blockscout&utm_medium=eth +NEXT_PUBLIC_MARKETPLACE_CATEGORIES_URL=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/marketplace-categories/default.json +NEXT_PUBLIC_MARKETPLACE_ENABLED=true +NEXT_PUBLIC_MARKETPLACE_RATING_AIRTABLE_BASE_ID=appGkvtmKI7fXE4Vs +NEXT_PUBLIC_MARKETPLACE_SECURITY_REPORTS_URL=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/marketplace-security-reports/default.json +NEXT_PUBLIC_MARKETPLACE_SUBMIT_FORM=https://airtable.com/appiy5yijZpMMSKjT/shr6uMGPKjj1DK7NL +NEXT_PUBLIC_MARKETPLACE_SUGGEST_IDEAS_FORM=https://airtable.com/appiy5yijZpMMSKjT/pag3t82DUCyhGRZZO/form +NEXT_PUBLIC_METADATA_SERVICE_API_HOST=https://metadata.services.blockscout.com +NEXT_PUBLIC_METASUITES_ENABLED=true +NEXT_PUBLIC_MULTICHAIN_BALANCE_PROVIDER_CONFIG=[{'name': 'zerion', 'url_template': 'https://app.zerion.io/{address}/overview', 'logo': 'https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/marketplace-logos/zerion.svg'},{'name': 'zapper', 'url_template': 'https://zapper.xyz/account/{address}', 'logo': 'https://blockscout-content.s3.amazonaws.com/zapper-icon.png'}] +NEXT_PUBLIC_NAME_SERVICE_API_HOST=https://bens.services.blockscout.com +NEXT_PUBLIC_NAVIGATION_HIGHLIGHTED_ROUTES=['/apps'] +NEXT_PUBLIC_NETWORK_CURRENCY_DECIMALS=18 +NEXT_PUBLIC_NETWORK_CURRENCY_NAME=Ether +NEXT_PUBLIC_NETWORK_CURRENCY_SYMBOL=ETH +NEXT_PUBLIC_NETWORK_EXPLORERS=[{'title':'GeckoTerminal','logo':'https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/explorer-logos/geckoterminal.png','baseUrl':'https://www.geckoterminal.com/','paths':{'token':'/eth/pools'}},{'title':'Etherscan','logo':'https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/explorer-logos/etherscan.png','baseUrl':'https://etherscan.io/','paths':{'tx':'/tx','address':'/address','token':'/token','block':'/block'}}, {'title':'Blockchair','logo':'https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/explorer-logos/blockchair.png','baseUrl':'https://blockchair.com/','paths':{'tx':'/ethereum/transaction','address':'/ethereum/address','token':'/ethereum/erc-20/token','block':'/ethereum/block'}},{'title':'Sentio','logo':'https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/explorer-logos/sentio.png','baseUrl':'https://app.sentio.xyz/','paths':{'tx':'/tx/1','address':'/contract/1'}}, {'title':'Tenderly','logo':'https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/explorer-logos/tenderly.png','baseUrl':'https://dashboard.tenderly.co','paths':{'tx':'/tx/mainnet'}}, {'title':'0xPPL','logo':'https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/explorer-logos/0xPPL.png','baseUrl':'https://0xppl.com','paths':{'tx':'/Ethereum/tx','address':'/','token':'/c/Ethereum'}}, {'title':'3xpl','logo':'https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/explorer-logos/3xpl.png','baseUrl':'https://3xpl.com/','paths':{'tx':'/ethereum/transaction','address':'/ethereum/address'}} ] +NEXT_PUBLIC_NETWORK_ID=1 +NEXT_PUBLIC_NETWORK_NAME=Ethereum +NEXT_PUBLIC_NETWORK_RPC_URL=https://eth.drpc.org +NEXT_PUBLIC_NETWORK_SHORT_NAME=Ethereum +NEXT_PUBLIC_NETWORK_VERIFICATION_TYPE=validation +NEXT_PUBLIC_OG_ENHANCED_DATA_ENABLED=true +NEXT_PUBLIC_OG_IMAGE_URL=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/og-images/eth.jpg +NEXT_PUBLIC_OTHER_LINKS=[{'url':'https://eth.drpc.org?ref=559183','text':'Public RPC'}] +NEXT_PUBLIC_REWARDS_SERVICE_API_HOST=https://merits.blockscout.com +NEXT_PUBLIC_SAFE_TX_SERVICE_URL=https://safe-transaction-mainnet.safe.global +NEXT_PUBLIC_SAVE_ON_GAS_ENABLED=true +NEXT_PUBLIC_SEO_ENHANCED_DATA_ENABLED=true +NEXT_PUBLIC_STATS_API_HOST=https://stats-eth-main.k8s-prod-1.blockscout.com NEXT_PUBLIC_TRANSACTION_INTERPRETATION_PROVIDER=blockscout - -#meta -NEXT_PUBLIC_OG_IMAGE_URL=https://github.com/blockscout/frontend-configs/blob/main/configs/og-images/eth.jpg?raw=true +NEXT_PUBLIC_VIEWS_CONTRACT_SOLIDITYSCAN_ENABLED=true +NEXT_PUBLIC_VISUALIZE_API_HOST=https://visualizer.services.blockscout.com +NEXT_PUBLIC_XSTAR_SCORE_URL=https://docs.xname.app/the-solution-adaptive-proof-of-humanity-on-blockchain/xhs-scoring-algorithm?utm_source=blockscout&utm_medium=address +NEXT_PUBLIC_GAME_BADGE_CLAIM_LINK=https://badges.blockscout.com/mint/hiddenBlockBadge \ No newline at end of file diff --git a/configs/envs/.env.eth_goerli b/configs/envs/.env.eth_goerli index 390a8f135c..5bf4167f20 100644 --- a/configs/envs/.env.eth_goerli +++ b/configs/envs/.env.eth_goerli @@ -33,6 +33,7 @@ NEXT_PUBLIC_NETWORK_ICON=https://raw.githubusercontent.com/blockscout/frontend-c NEXT_PUBLIC_VIEWS_NFT_MARKETPLACES=[{'name':'LooksRare','collection_url':'https://goerli.looksrare.org/collections/{hash}','instance_url':'https://goerli.looksrare.org/collections/{hash}/{id}','logo_url':'https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/nft-marketplace-logos/looks-rare.png'}] ## misc NEXT_PUBLIC_NETWORK_EXPLORERS=[{'title':'Bitquery','baseUrl':'https://explorer.bitquery.io/','paths':{'tx':'/goerli/tx','address':'/goerli/address','token':'/goerli/token','block':'/goerli/block'}},{'title':'Etherscan','baseUrl':'https://goerli.etherscan.io/','paths':{'tx':'/tx','address':'/address','token':'/token','block':'/block'}}] +NEXT_PUBLIC_HAS_CONTRACT_AUDIT_REPORTS=true # app features NEXT_PUBLIC_APP_ENV=development @@ -52,6 +53,7 @@ NEXT_PUBLIC_VIEWS_CONTRACT_SOLIDITYSCAN_ENABLED='true' NEXT_PUBLIC_HAS_BEACON_CHAIN=true NEXT_PUBLIC_HAS_USER_OPS=true NEXT_PUBLIC_CONTRACT_CODE_IDES=[{'title':'Remix IDE','url':'https://remix.blockscout.com/?address={hash}&blockscout=eth-goerli.blockscout.com','icon_url':'https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/ide-icons/remix.png'}] +NEXT_PUBLIC_TRANSACTION_INTERPRETATION_PROVIDER=blockscout #meta NEXT_PUBLIC_OG_IMAGE_URL=https://github.com/blockscout/frontend-configs/blob/main/configs/og-images/eth-goerli.png?raw=true diff --git a/configs/envs/.env.eth_sepolia b/configs/envs/.env.eth_sepolia new file mode 100644 index 0000000000..6de7b51a9a --- /dev/null +++ b/configs/envs/.env.eth_sepolia @@ -0,0 +1,72 @@ +# Set of ENVs for Sepolia network explorer +# https://eth-sepolia.blockscout.com +# This is an auto-generated file. To update all values, run "yarn dev:preset:sync --name=eth_sepolia" + +# Local ENVs +NEXT_PUBLIC_APP_PROTOCOL=http +NEXT_PUBLIC_APP_HOST=localhost +NEXT_PUBLIC_APP_PORT=3000 +NEXT_PUBLIC_APP_ENV=development +NEXT_PUBLIC_API_WEBSOCKET_PROTOCOL=ws + +# Instance ENVs +NEXT_PUBLIC_AD_ADBUTLER_CONFIG_DESKTOP={ "id": "632019", "width": "728", "height": "90" } +NEXT_PUBLIC_AD_ADBUTLER_CONFIG_MOBILE={ "id": "632018", "width": "320", "height": "100" } +NEXT_PUBLIC_ADMIN_SERVICE_API_HOST=https://admin-rs.services.blockscout.com +NEXT_PUBLIC_API_BASE_PATH=/ +NEXT_PUBLIC_API_HOST=eth-sepolia.k8s-dev.blockscout.com +NEXT_PUBLIC_API_SPEC_URL=https://raw.githubusercontent.com/blockscout/blockscout-api-v2-swagger/main/swagger.yaml +NEXT_PUBLIC_CONTRACT_CODE_IDES=[{'title':'Remix IDE','url':'https://remix.ethereum.org/?address={hash}&blockscout={domain}','icon_url':'https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/ide-icons/remix.png'}] +NEXT_PUBLIC_CONTRACT_INFO_API_HOST=https://contracts-info-test.k8s-dev.blockscout.com +NEXT_PUBLIC_DATA_AVAILABILITY_ENABLED=true +NEXT_PUBLIC_DEFI_DROPDOWN_ITEMS=[{'text':'Swap','icon':'swap','dappId':'cow-swap'},{'text':'Payment link','icon':'payment_link','dappId':'peanut-protocol'}] +NEXT_PUBLIC_FEATURED_NETWORKS=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/featured-networks/eth-sepolia.json +NEXT_PUBLIC_FOOTER_LINKS=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/footer-links/sepolia.json +NEXT_PUBLIC_GRAPHIQL_TRANSACTION=0xbf69c7abc4fee283b59a9633dadfdaedde5c5ee0fba3e80a08b5b8a3acbd4363 +NEXT_PUBLIC_HAS_BEACON_CHAIN=true +NEXT_PUBLIC_HAS_USER_OPS=true +NEXT_PUBLIC_HOMEPAGE_CHARTS=['daily_txs'] +NEXT_PUBLIC_HOMEPAGE_HERO_BANNER_CONFIG={'background':['rgba(51, 53, 67, 1)'],'text_color':['rgba(165, 252, 122, 1)']} +NEXT_PUBLIC_IS_ACCOUNT_SUPPORTED=true +NEXT_PUBLIC_IS_TESTNET=true +NEXT_PUBLIC_LOGOUT_URL=https://blockscout-goerli.us.auth0.com/v2/logout +NEXT_PUBLIC_MAINTENANCE_ALERT_MESSAGE=

Participated in our recent Blockscout activities? Check your eligibility and claim your NFT Scout badges. More exciting things are coming soon!

+NEXT_PUBLIC_MARKETPLACE_BANNER_CONTENT_URL=https://gist.githubusercontent.com/maikReal/974c47f86a3158c1a86b092ae2f044b3/raw/abcc7e02150cd85d4974503a0357162c0a2c35a9/merits-banner.html +NEXT_PUBLIC_MARKETPLACE_BANNER_LINK_URL=https://swap.blockscout.com?utm_source=blockscout&utm_medium=eth-sepolia +NEXT_PUBLIC_MARKETPLACE_CATEGORIES_URL=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/marketplace-categories/default.json +NEXT_PUBLIC_MARKETPLACE_ENABLED=true +NEXT_PUBLIC_MARKETPLACE_RATING_AIRTABLE_API_KEY=patbqG4V2CI998jAq.9810c58c9de973ba2650621c94559088cbdfa1a914498e385621ed035d33c0d0 +NEXT_PUBLIC_MARKETPLACE_RATING_AIRTABLE_BASE_ID=appGkvtmKI7fXE4Vs +NEXT_PUBLIC_MARKETPLACE_SECURITY_REPORTS_URL=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/marketplace-security-reports/default.json +NEXT_PUBLIC_MARKETPLACE_SUBMIT_FORM=https://airtable.com/appiy5yijZpMMSKjT/shr6uMGPKjj1DK7NL +NEXT_PUBLIC_MARKETPLACE_SUGGEST_IDEAS_FORM=https://airtable.com/appiy5yijZpMMSKjT/pag3t82DUCyhGRZZO/form +NEXT_PUBLIC_METADATA_SERVICE_API_HOST=https://metadata.services.blockscout.com +NEXT_PUBLIC_METASUITES_ENABLED=true +NEXT_PUBLIC_MULTICHAIN_BALANCE_PROVIDER_CONFIG=[{'name': 'zerion', 'url_template': 'https://app.zerion.io/{address}/overview', 'logo': 'https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/marketplace-logos/zerion.svg'},{'name': 'zapper', 'url_template': 'https://zapper.xyz/account/{address}', 'logo': 'https://blockscout-content.s3.amazonaws.com/zapper-icon.png'}] +NEXT_PUBLIC_NAME_SERVICE_API_HOST=https://bens.services.blockscout.com +NEXT_PUBLIC_NAVIGATION_HIGHLIGHTED_ROUTES=['/apps'] +NEXT_PUBLIC_NAVIGATION_LAYOUT=horizontal +NEXT_PUBLIC_NETWORK_CURRENCY_DECIMALS=18 +NEXT_PUBLIC_NETWORK_CURRENCY_NAME=Ether +NEXT_PUBLIC_NETWORK_CURRENCY_SYMBOL=ETH +NEXT_PUBLIC_NETWORK_EXPLORERS=[{'title':'GeckoTerminal','logo':'https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/explorer-logos/geckoterminal.png','baseUrl':'https://www.geckoterminal.com/','paths':{'token':'/sepolia-testnet/pools'}},{'title':'Etherscan','logo':'https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/explorer-logos/etherscan.png','baseUrl':'https://sepolia.etherscan.io/','paths':{'tx':'/tx','address':'/address','token':'/token','block':'/block'}}, {'title':'Tenderly','logo':'https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/explorer-logos/tenderly.png','baseUrl':'https://dashboard.tenderly.co','paths':{'tx':'/tx/sepolia'}} ] +NEXT_PUBLIC_NETWORK_ICON=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/network-icons/sepolia.png +NEXT_PUBLIC_NETWORK_ICON_DARK=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/network-icons/sepolia.png +NEXT_PUBLIC_NETWORK_ID=11155111 +NEXT_PUBLIC_NETWORK_LOGO=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/network-logos/sepolia.svg +NEXT_PUBLIC_NETWORK_LOGO_DARK=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/network-logos/sepolia.svg +NEXT_PUBLIC_NETWORK_NAME=Sepolia +NEXT_PUBLIC_NETWORK_RPC_URL=https://eth-sepolia.public.blastapi.io +NEXT_PUBLIC_NETWORK_SHORT_NAME=Sepolia +NEXT_PUBLIC_NETWORK_VERIFICATION_TYPE=validation +NEXT_PUBLIC_OG_ENHANCED_DATA_ENABLED=true +NEXT_PUBLIC_OG_IMAGE_URL=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/og-images/sepolia-testnet.png +NEXT_PUBLIC_OTHER_LINKS=[{'url':'https://sepolia.drpc.org?ref=559183','text':'Public RPC'}] +NEXT_PUBLIC_REWARDS_SERVICE_API_HOST=https://points.k8s-dev.blockscout.com +NEXT_PUBLIC_SAFE_TX_SERVICE_URL=https://safe-transaction-sepolia.safe.global +NEXT_PUBLIC_STATS_API_HOST=https://stats-sepolia.k8s.blockscout.com +NEXT_PUBLIC_TRANSACTION_INTERPRETATION_PROVIDER=noves +NEXT_PUBLIC_VIEWS_CONTRACT_SOLIDITYSCAN_ENABLED=true +NEXT_PUBLIC_VISUALIZE_API_HOST=https://visualizer.services.blockscout.com +NEXT_PUBLIC_XSTAR_SCORE_URL=https://docs.xname.app/the-solution-adaptive-proof-of-humanity-on-blockchain/xhs-scoring-algorithm?utm_source=blockscout&utm_medium=address +NEXT_PUBLIC_DEX_POOLS_ENABLED=true diff --git a/configs/envs/.env.filecoin b/configs/envs/.env.filecoin new file mode 100644 index 0000000000..0f6e05cd25 --- /dev/null +++ b/configs/envs/.env.filecoin @@ -0,0 +1,42 @@ +# Set of ENVs for Filecoin Virtual Machine network explorer +# https://filecoin.blockscout.com +# This is an auto-generated file. To update all values, run "yarn dev:preset:sync --name=filecoin" + +# Local ENVs +NEXT_PUBLIC_APP_PROTOCOL=http +NEXT_PUBLIC_APP_HOST=localhost +NEXT_PUBLIC_APP_PORT=3000 +NEXT_PUBLIC_APP_ENV=development +NEXT_PUBLIC_API_WEBSOCKET_PROTOCOL=ws + +# Instance ENVs +NEXT_PUBLIC_API_BASE_PATH=/ +NEXT_PUBLIC_API_HOST=filecoin.blockscout.com +NEXT_PUBLIC_API_SPEC_URL=https://raw.githubusercontent.com/blockscout/blockscout-api-v2-swagger/main/swagger.yaml +NEXT_PUBLIC_CONTRACT_CODE_IDES=[{'title':'Remix IDE','url':'https://remix.ethereum.org/?address={hash}&blockscout={domain}','icon_url':'https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/ide-icons/remix.png'}] +NEXT_PUBLIC_FOOTER_LINKS=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/footer-links/fvm.json +NEXT_PUBLIC_GRAPHIQL_TRANSACTION=0x5abb6212c1802402b828ed20c2bd4d4a6153b8bee68a5259cba3c8d7a7c6b775 +NEXT_PUBLIC_HOMEPAGE_CHARTS=['daily_txs', 'coin_price', 'market_cap'] +NEXT_PUBLIC_HOMEPAGE_PLATE_BACKGROUND=linear-gradient(237deg, rgb(26, 58, 150) 14.83%, rgb(111, 223, 164) 132.56%) +NEXT_PUBLIC_HOMEPAGE_PLATE_TEXT_COLOR=rgba(255, 255, 255, 1) +NEXT_PUBLIC_IS_ACCOUNT_SUPPORTED=true +NEXT_PUBLIC_LOGOUT_URL=https://blockscout-filecoin.us.auth0.com/v2/logout +NEXT_PUBLIC_MARKETPLACE_ENABLED=false +NEXT_PUBLIC_METADATA_SERVICE_API_HOST=https://metadata.services.blockscout.com +NEXT_PUBLIC_NETWORK_CURRENCY_DECIMALS=18 +NEXT_PUBLIC_NETWORK_CURRENCY_NAME=FIL +NEXT_PUBLIC_NETWORK_CURRENCY_SYMBOL=FIL +NEXT_PUBLIC_NETWORK_EXPLORERS=[{'title':'GeckoTerminal','logo':'https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/explorer-logos/geckoterminal.png','baseUrl':'https://www.geckoterminal.com/','paths':{'token':'/filecoin/pools'}}] +NEXT_PUBLIC_NETWORK_ICON=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/network-icons/filecoin-icon-dark.svg +NEXT_PUBLIC_NETWORK_ICON_DARK=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/network-icons/filecoin-icon-light.svg +NEXT_PUBLIC_NETWORK_ID=314 +NEXT_PUBLIC_NETWORK_LOGO=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/network-logos/filecoin-logo-dark.svg +NEXT_PUBLIC_NETWORK_LOGO_DARK=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/network-logos/filecoin-logo-light.svg +NEXT_PUBLIC_NETWORK_NAME=Filecoin Virtual Machine +NEXT_PUBLIC_NETWORK_RPC_URL=https://rpc.ankr.com/filecoin +NEXT_PUBLIC_NETWORK_SHORT_NAME=Filecoin Virtual Machine +NEXT_PUBLIC_OG_ENHANCED_DATA_ENABLED=true +NEXT_PUBLIC_OG_IMAGE_URL=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/og-images/filecoin.png +NEXT_PUBLIC_STATS_API_HOST=https://stats-filecoin.k8s-prod-1.blockscout.com +NEXT_PUBLIC_TRANSACTION_INTERPRETATION_PROVIDER=blockscout +NEXT_PUBLIC_VISUALIZE_API_HOST=https://visualizer.services.blockscout.com \ No newline at end of file diff --git a/configs/envs/.env.garnet b/configs/envs/.env.garnet new file mode 100644 index 0000000000..44720ed11c --- /dev/null +++ b/configs/envs/.env.garnet @@ -0,0 +1,53 @@ +# Set of ENVs for Garnet Testnet network explorer +# https://explorer.garnetchain.com +# This is an auto-generated file. To update all values, run "yarn preset:sync --name=garnet" + +# Local ENVs +NEXT_PUBLIC_APP_PROTOCOL=http +NEXT_PUBLIC_APP_HOST=localhost +NEXT_PUBLIC_APP_PORT=3000 +NEXT_PUBLIC_APP_ENV=development +NEXT_PUBLIC_API_WEBSOCKET_PROTOCOL=ws + +# Instance ENVs +NEXT_PUBLIC_AD_BANNER_PROVIDER=none +NEXT_PUBLIC_AD_TEXT_PROVIDER=none +NEXT_PUBLIC_ADMIN_SERVICE_API_HOST=https://admin-rs.services.blockscout.com +NEXT_PUBLIC_API_BASE_PATH=/ +NEXT_PUBLIC_API_HOST=explorer.garnetchain.com +NEXT_PUBLIC_API_SPEC_URL=https://raw.githubusercontent.com/blockscout/blockscout-api-v2-swagger/main/swagger.yaml +NEXT_PUBLIC_CONTRACT_CODE_IDES=[{'title':'Remix IDE','url':'https://remix.ethereum.org/?address={hash}&blockscout={domain}','icon_url':'https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/ide-icons/remix.png'}] +NEXT_PUBLIC_CONTRACT_INFO_API_HOST=https://contracts-info.services.blockscout.com +NEXT_PUBLIC_FEATURED_NETWORKS=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/featured-networks/redstone-testnet.json +NEXT_PUBLIC_FOOTER_LINKS=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/footer-links/redstone.json +NEXT_PUBLIC_GRAPHIQL_TRANSACTION=0x5b0ba69f2cf5fbc6da96b6cf475c5521f7a385efd9d68673f69c1fc54f737a52 +NEXT_PUBLIC_HAS_MUD_FRAMEWORK=true +NEXT_PUBLIC_HOMEPAGE_CHARTS=['daily_txs'] +NEXT_PUBLIC_HOMEPAGE_HERO_BANNER_CONFIG={'background':['rgb(169, 31, 47)']} +NEXT_PUBLIC_IS_ACCOUNT_SUPPORTED=true +NEXT_PUBLIC_IS_TESTNET=true +NEXT_PUBLIC_LOGOUT_URL=https://redstone-lattice.us.auth0.com/v2/logout +NEXT_PUBLIC_MARKETPLACE_ENABLED=false +NEXT_PUBLIC_METADATA_SERVICE_API_HOST=https://metadata.services.blockscout.com +NEXT_PUBLIC_NAME_SERVICE_API_HOST=https://bens.services.blockscout.com +NEXT_PUBLIC_NETWORK_CURRENCY_DECIMALS=18 +NEXT_PUBLIC_NETWORK_CURRENCY_NAME=Ether +NEXT_PUBLIC_NETWORK_CURRENCY_SYMBOL=ETH +NEXT_PUBLIC_NETWORK_ICON=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/network-icons/garnet.svg +NEXT_PUBLIC_NETWORK_ICON_DARK=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/network-icons/garnet-dark.svg +NEXT_PUBLIC_NETWORK_ID=17069 +NEXT_PUBLIC_NETWORK_LOGO=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/network-logos/garnet.svg +NEXT_PUBLIC_NETWORK_LOGO_DARK=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/network-logos/garnet-dark.svg +NEXT_PUBLIC_NETWORK_NAME=Garnet Testnet +NEXT_PUBLIC_NETWORK_RPC_URL=https://partner-rpc.garnetchain.com/tireless-strand-dreamt-overcome +NEXT_PUBLIC_NETWORK_SHORT_NAME=Garnet Testnet +NEXT_PUBLIC_OG_DESCRIPTION=Redstone is the home for onchain games, worlds, and other MUD applications +NEXT_PUBLIC_OG_ENHANCED_DATA_ENABLED=true +NEXT_PUBLIC_OG_IMAGE_URL=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/og-images/garnet.png +NEXT_PUBLIC_ROLLUP_L1_BASE_URL=https://eth-holesky.blockscout.com/ +NEXT_PUBLIC_ROLLUP_L2_WITHDRAWAL_URL=https://garnet.qry.live/withdraw +NEXT_PUBLIC_ROLLUP_TYPE=optimistic +NEXT_PUBLIC_STATS_API_HOST=https://stats-redstone-garnet.k8s.blockscout.com +NEXT_PUBLIC_TRANSACTION_INTERPRETATION_PROVIDER=blockscout +NEXT_PUBLIC_VIEWS_CONTRACT_SOLIDITYSCAN_ENABLED=true +NEXT_PUBLIC_VISUALIZE_API_HOST=https://visualizer.services.blockscout.com \ No newline at end of file diff --git a/configs/envs/.env.gnosis b/configs/envs/.env.gnosis index 1e20f6c303..7e1d1e6993 100644 --- a/configs/envs/.env.gnosis +++ b/configs/envs/.env.gnosis @@ -1,53 +1,70 @@ -# Set of ENVs for Gnosis network explorer -# https://gnosis.blockscout.com/ +# Set of ENVs for Gnosis chain network explorer +# https://gnosis.blockscout.com +# This is an auto-generated file. To update all values, run "yarn dev:preset:sync --name=gnosis" -# app configuration +# Local ENVs NEXT_PUBLIC_APP_PROTOCOL=http NEXT_PUBLIC_APP_HOST=localhost NEXT_PUBLIC_APP_PORT=3000 +NEXT_PUBLIC_APP_ENV=development +NEXT_PUBLIC_API_WEBSOCKET_PROTOCOL=ws -# blockchain parameters -NEXT_PUBLIC_NETWORK_NAME=Gnosis -NEXT_PUBLIC_NETWORK_SHORT_NAME=Gnosis -NEXT_PUBLIC_NETWORK_ID=100 -NEXT_PUBLIC_NETWORK_CURRENCY_NAME=xDAI -NEXT_PUBLIC_NETWORK_CURRENCY_SYMBOL=xDAI -NEXT_PUBLIC_NETWORK_CURRENCY_DECIMALS=18 -NEXT_PUBLIC_NETWORK_VERIFICATION_TYPE=validation -NEXT_PUBLIC_NETWORK_RPC_URL=https://rpc.gnosischain.com - -# api configuration -NEXT_PUBLIC_API_HOST=gnosis.blockscout.com +# Instance ENVs +NEXT_PUBLIC_AD_ADBUTLER_CONFIG_DESKTOP={'id':'523705','width':'728','height':'90'} +NEXT_PUBLIC_AD_ADBUTLER_CONFIG_MOBILE={'id':'539876','width':'320','height':'100'} +NEXT_PUBLIC_AD_BANNER_ADDITIONAL_PROVIDER=adbutler +NEXT_PUBLIC_ADMIN_SERVICE_API_HOST=https://admin-rs.services.blockscout.com NEXT_PUBLIC_API_BASE_PATH=/ - -# ui config -## homepage -NEXT_PUBLIC_HOMEPAGE_CHARTS=['daily_txs'] -NEXT_PUBLIC_HOMEPAGE_PLATE_BACKGROUND="rgb(46, 74, 60)" -NEXT_PUBLIC_HOMEPAGE_PLATE_TEXT_COLOR="rgb(255, 255, 255)" -## sidebar +NEXT_PUBLIC_API_HOST=gnosis.blockscout.com +NEXT_PUBLIC_API_SPEC_URL=https://raw.githubusercontent.com/blockscout/blockscout-api-v2-swagger/main/swagger.yaml +NEXT_PUBLIC_BEACON_CHAIN_CURRENCY_SYMBOL=GNO +NEXT_PUBLIC_BRIDGED_TOKENS_BRIDGES=[{'type':'omni','title':'OmniBridge','short_title':'OMNI'},{'type':'amb','title':'Arbitrary Message Bridge','short_title':'AMB'}] +NEXT_PUBLIC_BRIDGED_TOKENS_CHAINS=[{'id':'1','title':'Ethereum','short_title':'ETH','base_url':'https://eth.blockscout.com/token/'},{'id':'56','title':'Binance Smart Chain','short_title':'BSC','base_url':'https://bscscan.com/token/'},{'id':'99','title':'POA','short_title':'POA','base_url':'https://blockscout.com/poa/core/token/'}] +NEXT_PUBLIC_CONTRACT_CODE_IDES=[{'title':'Remix IDE','url':'https://remix.ethereum.org/?address={hash}&blockscout={domain}','icon_url':'https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/ide-icons/remix.png'}] +NEXT_PUBLIC_CONTRACT_INFO_API_HOST=https://contracts-info.services.blockscout.com +NEXT_PUBLIC_DATA_AVAILABILITY_ENABLED=true +NEXT_PUBLIC_DEFI_DROPDOWN_ITEMS=[{'text':'Swap','icon':'swap','dappId':'cow-swap'},{'text':'Payment link','icon':'payment_link','dappId':'peanut-protocol'},{'text':'Get gas','icon':'gas','dappId':'smol-refuel'}] NEXT_PUBLIC_FEATURED_NETWORKS=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/featured-networks/gnosis-chain-mainnet.json -NEXT_PUBLIC_NETWORK_LOGO=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/network-logos/gnosis.svg -NEXT_PUBLIC_NETWORK_ICON=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/network-icons/gnosis.svg -## footer NEXT_PUBLIC_FOOTER_LINKS=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/footer-links/gnosis.json -## views -## misc - -# app features -NEXT_PUBLIC_APP_ENV=development NEXT_PUBLIC_GRAPHIQL_TRANSACTION=0x082762f95047d39d612daafec832f88163f3815fde4ddd8944f2a5198a396e0f -# NEXT_PUBLIC_BEACON_CHAIN_CURRENCY_SYMBOL=GNO +NEXT_PUBLIC_HAS_BEACON_CHAIN=true +NEXT_PUBLIC_HAS_USER_OPS=true +NEXT_PUBLIC_HIDE_INDEXING_ALERT_INT_TXS=true +NEXT_PUBLIC_HOMEPAGE_CHARTS=['daily_txs', 'tvl'] +NEXT_PUBLIC_HOMEPAGE_PLATE_BACKGROUND=rgb(46, 74, 60) +NEXT_PUBLIC_HOMEPAGE_PLATE_TEXT_COLOR=rgb(255, 255, 255) NEXT_PUBLIC_IS_ACCOUNT_SUPPORTED=true -NEXT_PUBLIC_LOGOUT_URL=https://blockscoutcom.us.auth0.com/v2/logout -NEXT_PUBLIC_MARKETPLACE_CONFIG_URL=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/marketplace/gnosis-chain.json -NEXT_PUBLIC_MARKETPLACE_SUBMIT_FORM=https://airtable.com/shrmiO9mDGJoPNmJe -NEXT_PUBLIC_VISUALIZE_API_HOST=https://visualizer.services.blockscout.com -NEXT_PUBLIC_WEB3_WALLETS=['token_pocket','metamask'] -NEXT_PUBLIC_CONTRACT_INFO_API_HOST=https://contracts-info.services.blockscout.com -NEXT_PUBLIC_ADMIN_SERVICE_API_HOST=https://admin-rs.services.blockscout.com -NEXT_PUBLIC_BRIDGED_TOKENS_CHAINS=[{'id':'1','title':'Ethereum','short_title':'ETH','base_url':'https://eth.blockscout.com/token/'},{'id':'56','title':'Binance Smart Chain','short_title':'BSC','base_url':'https://bscscan.com/token/'},{'id':'99','title':'POA','short_title':'POA','base_url':'https://blockscout.com/poa/core/token/'}] -NEXT_PUBLIC_BRIDGED_TOKENS_BRIDGES=[{'type':'omni','title':'OmniBridge','short_title':'OMNI'},{'type':'amb','title':'Arbitrary Message Bridge','short_title':'AMB'}] - -#meta -NEXT_PUBLIC_OG_IMAGE_URL=https://github.com/blockscout/frontend-configs/blob/main/configs/og-images/polygon-mainnet.png?raw=true +NEXT_PUBLIC_LOGOUT_URL=https://login.blockscout.com/v2/logout +NEXT_PUBLIC_MARKETPLACE_BANNER_CONTENT_URL=https://gist.githubusercontent.com/maxaleks/0d18fc309ff499075127b364cc69306d/raw/2a51f961a8c1b9f884f2ab7eed79d4b69330e1ae/peanut_protocol_banner.html +NEXT_PUBLIC_MARKETPLACE_BANNER_LINK_URL=https://gnosis.blockscout.com/apps/peanut-protocol +NEXT_PUBLIC_MARKETPLACE_CATEGORIES_URL=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/marketplace-categories/default.json +NEXT_PUBLIC_MARKETPLACE_ENABLED=true +NEXT_PUBLIC_MARKETPLACE_SECURITY_REPORTS_URL=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/marketplace-security-reports/default.json +NEXT_PUBLIC_MARKETPLACE_SUBMIT_FORM=https://airtable.com/appiy5yijZpMMSKjT/shr6uMGPKjj1DK7NL +NEXT_PUBLIC_MARKETPLACE_SUGGEST_IDEAS_FORM=https://airtable.com/appiy5yijZpMMSKjT/pag3t82DUCyhGRZZO/form +NEXT_PUBLIC_MARKETPLACE_GRAPH_LINKS_URL=https://raw.githubusercontent.com/blockscout/frontend-configs/refs/heads/marketplace-graph-test/test-configs/marketplace-graph-links.json +NEXT_PUBLIC_METADATA_SERVICE_API_HOST=https://metadata.services.blockscout.com +NEXT_PUBLIC_METASUITES_ENABLED=true +NEXT_PUBLIC_MULTICHAIN_BALANCE_PROVIDER_CONFIG=[{'name': 'zerion', 'dapp_id': 'zerion', 'url_template': 'https://app.zerion.io/{address}/overview?utm_source=blockscout&utm_medium=address', 'logo': 'https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/marketplace-logos/zerion.svg'}] +NEXT_PUBLIC_NAME_SERVICE_API_HOST=https://bens.services.blockscout.com +NEXT_PUBLIC_NAVIGATION_HIGHLIGHTED_ROUTES=['/apps'] +NEXT_PUBLIC_NETWORK_CURRENCY_DECIMALS=18 +NEXT_PUBLIC_NETWORK_CURRENCY_NAME=XDAI +NEXT_PUBLIC_NETWORK_CURRENCY_SYMBOL=XDAI +NEXT_PUBLIC_NETWORK_EXPLORERS=[{'title':'GeckoTerminal','logo':'https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/explorer-logos/geckoterminal.png','baseUrl':'https://www.geckoterminal.com/','paths':{'token':'/xdai/pools'}},{'title':'Tenderly','logo':'https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/explorer-logos/tenderly.png','baseUrl':'https://dashboard.tenderly.co','paths':{'tx':'/tx/gnosis-chain'}},{'title':'3xpl','logo':'https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/explorer-logos/3xpl.png','baseUrl':'https://3xpl.com/','paths':{'tx':'/gnosis-chain/transaction','address':'/gnosis-chain/address'}}] +NEXT_PUBLIC_NETWORK_ICON=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/network-icons/gnosis.svg +NEXT_PUBLIC_NETWORK_ID=100 +NEXT_PUBLIC_NETWORK_LOGO=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/network-logos/gnosis.svg +NEXT_PUBLIC_NETWORK_NAME=Gnosis chain +NEXT_PUBLIC_NETWORK_RPC_URL=https://rpc.gnosischain.com +NEXT_PUBLIC_NETWORK_SECONDARY_COIN_SYMBOL=GNO +NEXT_PUBLIC_NETWORK_SHORT_NAME=Gnosis chain +NEXT_PUBLIC_NETWORK_VERIFICATION_TYPE=validation +NEXT_PUBLIC_OG_ENHANCED_DATA_ENABLED=true +NEXT_PUBLIC_OG_IMAGE_URL=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/og-images/gnosis-chain-mainnet.png +NEXT_PUBLIC_OTHER_LINKS=[{'url':'https://gnosis.drpc.org?ref=559183','text':'Public RPC'}] +NEXT_PUBLIC_SAFE_TX_SERVICE_URL=https://safe-transaction-gnosis-chain.safe.global +NEXT_PUBLIC_STATS_API_HOST=https://stats-gnosis-mainnet.k8s-prod-1.blockscout.com +NEXT_PUBLIC_TRANSACTION_INTERPRETATION_PROVIDER=blockscout +NEXT_PUBLIC_VIEWS_CONTRACT_SOLIDITYSCAN_ENABLED=true +NEXT_PUBLIC_VISUALIZE_API_HOST=https://visualizer.services.blockscout.com \ No newline at end of file diff --git a/configs/envs/.env.jest b/configs/envs/.env.jest index 7a14a10b01..abe2107a80 100644 --- a/configs/envs/.env.jest +++ b/configs/envs/.env.jest @@ -24,8 +24,6 @@ NEXT_PUBLIC_API_BASE_PATH=/ # ui config ## homepage NEXT_PUBLIC_HOMEPAGE_CHARTS=['daily_txs','coin_price','market_cap'] -NEXT_PUBLIC_HOMEPAGE_SHOW_AVG_BLOCK_TIME=true -NEXT_PUBLIC_HOMEPAGE_SHOW_GAS_TRACKER=true NEXT_PUBLIC_HOMEPAGE_PLATE_BACKGROUND= ## sidebar NEXT_PUBLIC_NETWORK_LOGO= @@ -44,8 +42,6 @@ NEXT_PUBLIC_APP_INSTANCE=jest NEXT_PUBLIC_APP_ENV=testing NEXT_PUBLIC_MARKETPLACE_CONFIG_URL=https://localhost:3000/marketplace-config.json NEXT_PUBLIC_MARKETPLACE_SUBMIT_FORM=https://localhost:3000/marketplace-submit-form -NEXT_PUBLIC_IS_OPTIMISTIC_L2_NETWORK=false -NEXT_PUBLIC_IS_ZKEVM_L2_NETWORK=false NEXT_PUBLIC_IS_ACCOUNT_SUPPORTED=true NEXT_PUBLIC_AUTH_URL=http://localhost:3100 NEXT_PUBLIC_LOGOUT_URL=https://blockscoutcom.us.auth0.com/v2/logout diff --git a/configs/envs/.env.main b/configs/envs/.env.main index 267b3448a6..0b0b075299 100644 --- a/configs/envs/.env.main +++ b/configs/envs/.env.main @@ -1,55 +1,69 @@ -# Set of ENVs for Develompent network explorer -# https://blockscout-main.k8s-dev.blockscout.com/ +# Set of ENVs for Sepolia network explorer +# https://eth-sepolia.k8s-dev.blockscout.com +# This is an auto-generated file. To update all values, run "yarn dev:preset:sync --name=main" -# app configuration +# Local ENVs NEXT_PUBLIC_APP_PROTOCOL=http NEXT_PUBLIC_APP_HOST=localhost NEXT_PUBLIC_APP_PORT=3000 +NEXT_PUBLIC_APP_ENV=development +NEXT_PUBLIC_APP_INSTANCE=rubber_duck +NEXT_PUBLIC_API_WEBSOCKET_PROTOCOL=ws -# blockchain parameters -NEXT_PUBLIC_NETWORK_NAME=Goerli -NEXT_PUBLIC_NETWORK_SHORT_NAME=Goerli -NEXT_PUBLIC_NETWORK_ID=5 -NEXT_PUBLIC_NETWORK_CURRENCY_NAME=Ether -NEXT_PUBLIC_NETWORK_CURRENCY_SYMBOL=ETH -NEXT_PUBLIC_NETWORK_CURRENCY_DECIMALS=18 -NEXT_PUBLIC_NETWORK_GOVERNANCE_TOKEN_SYMBOL=ETH -NEXT_PUBLIC_NETWORK_RPC_URL=https://rpc.ankr.com/eth_goerli -NEXT_PUBLIC_NETWORK_VERIFICATION_TYPE=validation -NEXT_PUBLIC_IS_TESTNET=true - -# api configuration -NEXT_PUBLIC_API_HOST=blockscout-main.k8s-dev.blockscout.com +# Instance ENVs +NEXT_PUBLIC_AD_ADBUTLER_CONFIG_DESKTOP={ "id": "632019", "width": "728", "height": "90" } +NEXT_PUBLIC_AD_ADBUTLER_CONFIG_MOBILE={ "id": "632018", "width": "320", "height": "100" } +NEXT_PUBLIC_AD_BANNER_ADDITIONAL_PROVIDER=adbutler +NEXT_PUBLIC_ADMIN_SERVICE_API_HOST=https://admin-rs-test.k8s-dev.blockscout.com NEXT_PUBLIC_API_BASE_PATH=/ -NEXT_PUBLIC_BOOL_SCAN_API=https://dev-api.boolscan.com - -# ui config -## homepage +NEXT_PUBLIC_API_HOST=eth-sepolia.k8s-dev.blockscout.com +NEXT_PUBLIC_API_SPEC_URL=https://raw.githubusercontent.com/blockscout/blockscout-api-v2-swagger/main/swagger.yaml +NEXT_PUBLIC_CONTRACT_CODE_IDES=[{'title':'Remix IDE','url':'https://remix.ethereum.org/?address={hash}&blockscout={domain}','icon_url':'https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/ide-icons/remix.png'}] +NEXT_PUBLIC_CONTRACT_INFO_API_HOST=https://contracts-info-test.k8s-dev.blockscout.com +NEXT_PUBLIC_DATA_AVAILABILITY_ENABLED=true +NEXT_PUBLIC_DEFI_DROPDOWN_ITEMS=[{'text':'Swap','icon':'swap','dappId':'uniswap'},{'text':'Payment link','icon':'payment_link','dappId':'peanut-protocol'},{'text':'Get gas','icon':'gas','dappId':'smol-refuel'}] +NEXT_PUBLIC_FEATURED_NETWORKS=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/featured-networks/eth-sepolia.json +NEXT_PUBLIC_FOOTER_LINKS=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/footer-links/sepolia.json +NEXT_PUBLIC_GRAPHIQL_TRANSACTION=0xbf69c7abc4fee283b59a9633dadfdaedde5c5ee0fba3e80a08b5b8a3acbd4363 +NEXT_PUBLIC_HAS_BEACON_CHAIN=true +NEXT_PUBLIC_HAS_USER_OPS=true NEXT_PUBLIC_HOMEPAGE_CHARTS=['daily_txs'] -## sidebar -NEXT_PUBLIC_FEATURED_NETWORKS=https://raw.githubusercontent.com/blockscout/frontend-configs/dev/configs/featured-networks/eth-goerli.json -NEXT_PUBLIC_NETWORK_LOGO=https://raw.githubusercontent.com/blockscout/frontend-configs/dev/configs/network-logos/goerli.svg -NEXT_PUBLIC_NETWORK_ICON=https://raw.githubusercontent.com/blockscout/frontend-configs/dev/configs/network-icons/goerli.svg -## footer -##views -NEXT_PUBLIC_VIEWS_NFT_MARKETPLACES=[{'name':'LooksRare','collection_url':'https://goerli.looksrare.org/collections/{hash}','instance_url':'https://goerli.looksrare.org/collections/{hash}/{id}','logo_url':'https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/nft-marketplace-logos/looks-rare.png'}] -## misc -NEXT_PUBLIC_NETWORK_EXPLORERS=[{'title':'Bitquery','baseUrl':'https://explorer.bitquery.io/','paths':{'tx':'/goerli/tx','address':'/goerli/address','token':'/goerli/token','block':'/goerli/block'}},{'title':'Etherscan','baseUrl':'https://goerli.etherscan.io/','paths':{'tx':'/tx','address':'/address','token':'/token','block':'/block'}}] -## views -# NEXT_PUBLIC_VIEWS_ADDRESS_HIDDEN_VIEWS=['top_accounts'] -# NEXT_PUBLIC_VIEWS_TX_HIDDEN_FIELDS=['value','fee_currency','gas_price','tx_fee','gas_fees','burnt_fees'] -# NEXT_PUBLIC_VIEWS_TX_ADDITIONAL_FIELDS=['fee_per_gas'] - -# app features -NEXT_PUBLIC_APP_ENV=development -NEXT_PUBLIC_GRAPHIQL_TRANSACTION=0xf7d4972356e6ae44ae948d0cf19ef2beaf0e574c180997e969a2837da15e349d +NEXT_PUBLIC_HOMEPAGE_PLATE_BACKGROUND=rgba(51, 53, 67, 1) +NEXT_PUBLIC_HOMEPAGE_PLATE_TEXT_COLOR=rgba(165, 252, 122, 1) NEXT_PUBLIC_IS_ACCOUNT_SUPPORTED=true -NEXT_PUBLIC_AUTH_URL=http://localhost:3000 -NEXT_PUBLIC_LOGOUT_URL=https://blockscoutcom.us.auth0.com/v2/logout -NEXT_PUBLIC_MARKETPLACE_CONFIG_URL=https://raw.githubusercontent.com/blockscout/frontend-configs/dev/configs/marketplace/eth-goerli.json -NEXT_PUBLIC_MARKETPLACE_CATEGORIES_URL=https://raw.githubusercontent.com/blockscout/frontend-configs/dev/configs/marketplace-categories/default.json -NEXT_PUBLIC_MARKETPLACE_SUBMIT_FORM=https://airtable.com/shrqUAcjgGJ4jU88C -NEXT_PUBLIC_STATS_API_HOST=https://stats-goerli.k8s-dev.blockscout.com -NEXT_PUBLIC_VISUALIZE_API_HOST=https://visualizer.k8s-dev.blockscout.com -NEXT_PUBLIC_CONTRACT_INFO_API_HOST=https://contracts-info-test.k8s-dev.blockscout.com -NEXT_PUBLIC_ADMIN_SERVICE_API_HOST=https://admin-rs-test.k8s-dev.blockscout.com +NEXT_PUBLIC_IS_TESTNET=true +NEXT_PUBLIC_LOGOUT_URL=https://blockscout-goerli.us.auth0.com/v2/logout +NEXT_PUBLIC_MARKETPLACE_CATEGORIES_URL=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/marketplace-categories/default.json +NEXT_PUBLIC_MARKETPLACE_ENABLED=true +NEXT_PUBLIC_MARKETPLACE_SECURITY_REPORTS_URL=https://raw.githubusercontent.com/blockscout/frontend-configs/main/test-configs/marketplace-security-report-mock.json +NEXT_PUBLIC_MARKETPLACE_SUBMIT_FORM=https://airtable.com/appiy5yijZpMMSKjT/shr6uMGPKjj1DK7NL +NEXT_PUBLIC_MARKETPLACE_SUGGEST_IDEAS_FORM=https://airtable.com/appiy5yijZpMMSKjT/pag3t82DUCyhGRZZO/form +NEXT_PUBLIC_METADATA_SERVICE_API_HOST=https://metadata-test.k8s-dev.blockscout.com +NEXT_PUBLIC_METASUITES_ENABLED=true +NEXT_PUBLIC_MULTICHAIN_BALANCE_PROVIDER_CONFIG=[{'name': 'zerion', 'url_template': 'https://app.zerion.io/{address}/overview', 'logo': 'https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/marketplace-logos/zerion.svg'}] +NEXT_PUBLIC_NAME_SERVICE_API_HOST=https://bens-rs-test.k8s-dev.blockscout.com +NEXT_PUBLIC_NAVIGATION_HIGHLIGHTED_ROUTES=['/blocks','/apps'] +NEXT_PUBLIC_NAVIGATION_LAYOUT=horizontal +NEXT_PUBLIC_NETWORK_CURRENCY_DECIMALS=18 +NEXT_PUBLIC_NETWORK_CURRENCY_NAME=Ether +NEXT_PUBLIC_NETWORK_CURRENCY_SYMBOL=ETH +NEXT_PUBLIC_NETWORK_EXPLORERS=[{'title':'Etherscan','logo':'https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/explorer-logos/etherscan.png', 'baseUrl':'https://sepolia.etherscan.io/','paths':{'tx':'/tx','address':'/address','token':'/token','block':'/block'}}, {'title':'Tenderly','logo':'https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/explorer-logos/tenderly.png','logo':'https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/explorer-logos/tenderly.png','baseUrl':'https://dashboard.tenderly.co','paths':{'tx':'/tx/sepolia'}} ] +NEXT_PUBLIC_NETWORK_ICON=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/network-icons/sepolia.png +NEXT_PUBLIC_NETWORK_ICON_DARK=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/network-icons/sepolia.png +NEXT_PUBLIC_NETWORK_ID=11155111 +NEXT_PUBLIC_NETWORK_LOGO=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/network-logos/sepolia.svg +NEXT_PUBLIC_NETWORK_LOGO_DARK=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/network-logos/sepolia.svg +NEXT_PUBLIC_NETWORK_NAME=Sepolia +NEXT_PUBLIC_NETWORK_RPC_URL=https://eth-sepolia.public.blastapi.io +NEXT_PUBLIC_NETWORK_SHORT_NAME=Sepolia +NEXT_PUBLIC_NETWORK_VERIFICATION_TYPE=validation +NEXT_PUBLIC_OG_ENHANCED_DATA_ENABLED=true +NEXT_PUBLIC_OG_IMAGE_URL=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/og-images/sepolia-testnet.png +NEXT_PUBLIC_OTHER_LINKS=[{'url':'https://sepolia.drpc.org?ref=559183','text':'Public RPC'}] +NEXT_PUBLIC_REWARDS_SERVICE_API_HOST=https://points.k8s-dev.blockscout.com +NEXT_PUBLIC_SAFE_TX_SERVICE_URL=https://safe-transaction-sepolia.safe.global +NEXT_PUBLIC_SEO_ENHANCED_DATA_ENABLED=true +NEXT_PUBLIC_STATS_API_HOST=https://stats-sepolia.k8s-dev.blockscout.com +NEXT_PUBLIC_TRANSACTION_INTERPRETATION_PROVIDER=blockscout +NEXT_PUBLIC_VIEWS_CONTRACT_SOLIDITYSCAN_ENABLED=true +NEXT_PUBLIC_VISUALIZE_API_HOST=https://visualizer-test.k8s-dev.blockscout.com \ No newline at end of file diff --git a/configs/envs/.env.main.L2 b/configs/envs/.env.main.L2 deleted file mode 100644 index fb9359be95..0000000000 --- a/configs/envs/.env.main.L2 +++ /dev/null @@ -1,52 +0,0 @@ -# Set of ENVs for Develompent L2 network explorer -# https://blockscout-optimism-goerli.k8s-dev.blockscout.com/ - -# app configuration -NEXT_PUBLIC_APP_PROTOCOL=http -NEXT_PUBLIC_APP_HOST=localhost -NEXT_PUBLIC_APP_PORT=3000 - -# blockchain parameters -NEXT_PUBLIC_NETWORK_NAME=Base Göerli -NEXT_PUBLIC_NETWORK_SHORT_NAME=Base -NEXT_PUBLIC_NETWORK_ID=84531 -NEXT_PUBLIC_NETWORK_CURRENCY_NAME=Ether -NEXT_PUBLIC_NETWORK_CURRENCY_SYMBOL=ETH -NEXT_PUBLIC_NETWORK_CURRENCY_DECIMALS=18 -NEXT_PUBLIC_NETWORK_VERIFICATION_TYPE=validation -NEXT_PUBLIC_NETWORK_RPC_URL=https://goerli.base.org -NEXT_PUBLIC_IS_TESTNET=true - -# api configuration -NEXT_PUBLIC_API_HOST=blockscout-optimism-goerli.k8s-dev.blockscout.com -NEXT_PUBLIC_API_BASE_PATH=/ - -# ui config -## homepage -NEXT_PUBLIC_HOMEPAGE_PLATE_BACKGROUND=linear-gradient(136.9deg,rgb(107 94 236) 1.5%,rgb(0 82 255) 56.84%,rgb(82 62 231) 98.54%) -NEXT_PUBLIC_HOMEPAGE_CHARTS=['daily_txs'] -## sidebar -NEXT_PUBLIC_FEATURED_NETWORKS=https://raw.githubusercontent.com/blockscout/frontend-configs/dev/configs/featured-networks/base-goerli.json -NEXT_PUBLIC_NETWORK_LOGO=https://raw.githubusercontent.com/blockscout/frontend-configs/dev/configs/network-logos/base.svg -NEXT_PUBLIC_NETWORK_ICON=https://raw.githubusercontent.com/blockscout/frontend-configs/dev/configs/network-icons/base.svg -## footer -## misc -## views -NEXT_PUBLIC_VIEWS_ADDRESS_IDENTICON_TYPE=gradient_avatar - -# app features -NEXT_PUBLIC_APP_ENV=development -NEXT_PUBLIC_AD_BANNER_PROVIDER=adbutler -NEXT_PUBLIC_AD_ADBUTLER_CONFIG_DESKTOP='{ "id": "632019", "width": "728", "height": "90" }' -NEXT_PUBLIC_AD_ADBUTLER_CONFIG_MOBILE="{ 'id': '632018', 'width': '320', 'height': '100' }" -NEXT_PUBLIC_GRAPHIQL_TRANSACTION=0x4a0ed8ddf751a7cb5297f827699117b0f6d21a0b2907594d300dc9fed75c7e62 -NEXT_PUBLIC_WEB3_WALLETS=['coinbase'] -NEXT_PUBLIC_WEB3_DISABLE_ADD_TOKEN_TO_WALLET=true -NEXT_PUBLIC_IS_ACCOUNT_SUPPORTED=true -NEXT_PUBLIC_LOGOUT_URL=https://blockscoutcom.us.auth0.com/v2/logout -NEXT_PUBLIC_MARKETPLACE_CONFIG_URL=https://raw.githubusercontent.com/blockscout/frontend-configs/dev/configs/marketplace/base-goerli.json -NEXT_PUBLIC_MARKETPLACE_SUBMIT_FORM=https://airtable.com/shrqUAcjgGJ4jU88C -NEXT_PUBLIC_STATS_API_HOST=https://stats-optimism-goerli.k8s-dev.blockscout.com -NEXT_PUBLIC_IS_OPTIMISTIC_L2_NETWORK=true -NEXT_PUBLIC_L1_BASE_URL=https://blockscout-main.k8s-dev.blockscout.com -NEXT_PUBLIC_OPTIMISTIC_L2_WITHDRAWAL_URL=https://app.optimism.io/bridge/withdraw diff --git a/configs/envs/.env.optimism b/configs/envs/.env.optimism new file mode 100644 index 0000000000..8fd9911afc --- /dev/null +++ b/configs/envs/.env.optimism @@ -0,0 +1,69 @@ +# Set of ENVs for OP Mainnet network explorer +# https://optimism.blockscout.com +# This is an auto-generated file. To update all values, run "yarn dev:preset:sync --name=optimism" + +# Local ENVs +NEXT_PUBLIC_APP_PROTOCOL=http +NEXT_PUBLIC_APP_HOST=localhost +NEXT_PUBLIC_APP_PORT=3000 +NEXT_PUBLIC_APP_ENV=development +NEXT_PUBLIC_API_WEBSOCKET_PROTOCOL=ws + +# Instance ENVs +NEXT_PUBLIC_AD_ADBUTLER_CONFIG_DESKTOP={ "id": "749780", "width": "728", "height": "90" } +NEXT_PUBLIC_AD_ADBUTLER_CONFIG_MOBILE={ "id": "749779", "width": "320", "height": "100" } +NEXT_PUBLIC_AD_BANNER_ADDITIONAL_PROVIDER=adbutler +NEXT_PUBLIC_ADMIN_SERVICE_API_HOST=https://admin-rs.services.blockscout.com +NEXT_PUBLIC_API_BASE_PATH=/ +NEXT_PUBLIC_API_HOST=optimism.blockscout.com +NEXT_PUBLIC_API_SPEC_URL=https://raw.githubusercontent.com/blockscout/blockscout-api-v2-swagger/main/swagger.yaml +NEXT_PUBLIC_CONTRACT_CODE_IDES=[{'title':'Remix IDE','url':'https://remix.ethereum.org/?address={hash}&blockscout={domain}','icon_url':'https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/ide-icons/remix.png'}] +NEXT_PUBLIC_CONTRACT_INFO_API_HOST=https://contracts-info.services.blockscout.com +NEXT_PUBLIC_DEFI_DROPDOWN_ITEMS=[{'text':'Swap','icon':'swap','dappId':'velodrome'},{'text':'Payment link','icon':'payment_link','dappId':'peanut-protocol'},{'text':'Get gas','icon':'gas','dappId':'smol-refuel'}] +NEXT_PUBLIC_FAULT_PROOF_ENABLED=true +NEXT_PUBLIC_FEATURED_NETWORKS=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/featured-networks/optimism-mainnet.json +NEXT_PUBLIC_FOOTER_LINKS=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/footer-links/optimism.json +NEXT_PUBLIC_GRAPHIQL_TRANSACTION=0x97f34a4cf685e365460dd38dbe16e092d8e4cc4b6ac779e3abcf4c18df6b1329 +NEXT_PUBLIC_HAS_USER_OPS=true +NEXT_PUBLIC_HOMEPAGE_CHARTS=['daily_txs', 'coin_price', 'market_cap', 'secondary_coin_price'] +NEXT_PUBLIC_HOMEPAGE_PLATE_BACKGROUND=linear-gradient(90deg, rgb(232, 52, 53) 0%, rgb(139, 28, 232) 100%) +NEXT_PUBLIC_HOMEPAGE_PLATE_TEXT_COLOR=rgb(255, 255, 255) +NEXT_PUBLIC_IS_ACCOUNT_SUPPORTED=true +NEXT_PUBLIC_LOGOUT_URL=https://optimism-goerli.us.auth0.com/v2/logout +NEXT_PUBLIC_MARKETPLACE_BANNER_CONTENT_URL=https://gist.githubusercontent.com/maxaleks/0d18fc309ff499075127b364cc69306d/raw/2a51f961a8c1b9f884f2ab7eed79d4b69330e1ae/peanut_protocol_banner.html +NEXT_PUBLIC_MARKETPLACE_BANNER_LINK_URL=https://optimism.blockscout.com/apps/peanut-protocol +NEXT_PUBLIC_MARKETPLACE_CATEGORIES_URL=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/marketplace-categories/default.json +NEXT_PUBLIC_MARKETPLACE_ENABLED=true +NEXT_PUBLIC_MARKETPLACE_SECURITY_REPORTS_URL=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/marketplace-security-reports/default.json +NEXT_PUBLIC_MARKETPLACE_SUBMIT_FORM=https://airtable.com/appiy5yijZpMMSKjT/shr6uMGPKjj1DK7NL +NEXT_PUBLIC_MARKETPLACE_SUGGEST_IDEAS_FORM=https://airtable.com/appiy5yijZpMMSKjT/pag3t82DUCyhGRZZO/form +NEXT_PUBLIC_METADATA_SERVICE_API_HOST=https://metadata.services.blockscout.com +NEXT_PUBLIC_METASUITES_ENABLED=true +NEXT_PUBLIC_MULTICHAIN_BALANCE_PROVIDER_CONFIG=[{'name': 'zerion', 'dapp_id': 'zerion', 'url_template': 'https://app.zerion.io/{address}/overview?utm_source=blockscout&utm_medium=address', 'logo': 'https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/marketplace-logos/zerion.svg'}] +NEXT_PUBLIC_NAME_SERVICE_API_HOST=https://bens.services.blockscout.com +NEXT_PUBLIC_NAVIGATION_HIGHLIGHTED_ROUTES=['/apps'] +NEXT_PUBLIC_NETWORK_CURRENCY_DECIMALS=18 +NEXT_PUBLIC_NETWORK_CURRENCY_NAME=Ether +NEXT_PUBLIC_NETWORK_CURRENCY_SYMBOL=ETH +NEXT_PUBLIC_NETWORK_EXPLORERS=[{'title':'GeckoTerminal','logo':'https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/explorer-logos/geckoterminal.png','baseUrl':'https://www.geckoterminal.com/','paths':{'token':'/optimism/pools'}}, {'title':'Tenderly','logo':'https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/explorer-logos/tenderly.png','baseUrl':'https://dashboard.tenderly.co','paths':{'tx':'/tx/optimistic'}},{'title':'3xpl','logo':'https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/explorer-logos/3xpl.png','baseUrl':'https://3xpl.com/','paths':{'tx':'/optimism/transaction','address':'/optimism/address'}}] +NEXT_PUBLIC_NETWORK_ICON=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/network-icons/optimism.svg +NEXT_PUBLIC_NETWORK_ICON_DARK=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/network-icons/optimism.svg +NEXT_PUBLIC_NETWORK_ID=10 +NEXT_PUBLIC_NETWORK_LOGO=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/network-logos/optimism.svg +NEXT_PUBLIC_NETWORK_LOGO_DARK=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/network-logos/optimism.svg +NEXT_PUBLIC_NETWORK_NAME=OP Mainnet +NEXT_PUBLIC_NETWORK_RPC_URL=https://mainnet.optimism.io +NEXT_PUBLIC_NETWORK_SECONDARY_COIN_SYMBOL=OP +NEXT_PUBLIC_NETWORK_SHORT_NAME=OP Mainnet +NEXT_PUBLIC_OG_ENHANCED_DATA_ENABLED=true +NEXT_PUBLIC_OG_IMAGE_URL=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/og-images/optimism-mainnet.png +NEXT_PUBLIC_OTHER_LINKS=[{'url':'https://optimism.drpc.org?ref=559183','text':'Public RPC'}] +NEXT_PUBLIC_ROLLUP_L1_BASE_URL=https://eth.blockscout.com/ +NEXT_PUBLIC_ROLLUP_L2_WITHDRAWAL_URL=https://app.optimism.io/bridge/withdraw +NEXT_PUBLIC_ROLLUP_TYPE=optimistic +NEXT_PUBLIC_SAFE_TX_SERVICE_URL=https://safe-transaction-optimism.safe.global +NEXT_PUBLIC_STATS_API_HOST=https://stats-optimism-mainnet.k8s-prod-1.blockscout.com +NEXT_PUBLIC_TRANSACTION_INTERPRETATION_PROVIDER=blockscout +NEXT_PUBLIC_VIEWS_CONTRACT_SOLIDITYSCAN_ENABLED=true +NEXT_PUBLIC_VISUALIZE_API_HOST=https://visualizer.services.blockscout.com +NEXT_PUBLIC_WEB3_WALLETS=['token_pocket', 'metamask'] \ No newline at end of file diff --git a/configs/envs/.env.optimism_celestia b/configs/envs/.env.optimism_celestia new file mode 100644 index 0000000000..650975e1c1 --- /dev/null +++ b/configs/envs/.env.optimism_celestia @@ -0,0 +1,46 @@ +# Set of ENVs for OP Celestia Raspberry network explorer +# https://opcelestia-raspberry.gelatoscout.com +# This is an auto-generated file. To update all values, run "yarn dev:preset:sync --name=optimism_celestia" + +# Local ENVs +NEXT_PUBLIC_APP_PROTOCOL=http +NEXT_PUBLIC_APP_HOST=localhost +NEXT_PUBLIC_APP_PORT=3000 +NEXT_PUBLIC_APP_ENV=development +NEXT_PUBLIC_API_WEBSOCKET_PROTOCOL=ws + +# Instance ENVs +NEXT_PUBLIC_AD_ADBUTLER_CONFIG_DESKTOP={'id':'721628','width':'728','height':'90'} +NEXT_PUBLIC_AD_ADBUTLER_CONFIG_MOBILE={'id':'721627','width':'300','height':'100'} +NEXT_PUBLIC_AD_BANNER_PROVIDER=adbutler +NEXT_PUBLIC_AD_TEXT_PROVIDER=none +NEXT_PUBLIC_API_BASE_PATH=/ +NEXT_PUBLIC_API_HOST=opcelestia-raspberry.gelatoscout.com +NEXT_PUBLIC_API_SPEC_URL=https://raw.githubusercontent.com/blockscout/blockscout-api-v2-swagger/main/swagger.yaml +NEXT_PUBLIC_CONTRACT_CODE_IDES=[{'title':'Remix IDE','url':'https://remix.ethereum.org/?address={hash}&blockscout={domain}','icon_url':'https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/ide-icons/remix.png'}] +NEXT_PUBLIC_FEATURED_NETWORKS=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/featured-networks/opcelestia-raspberry.json +NEXT_PUBLIC_GRAPHIQL_TRANSACTION=0x0f5b54de81848d8d8baa02c69030037218a2b4df622d64a2a429e11721606656 +NEXT_PUBLIC_HOMEPAGE_CHARTS=['daily_txs'] +NEXT_PUBLIC_HOMEPAGE_PLATE_BACKGROUND=rgba(255, 0, 0, 1) +NEXT_PUBLIC_IS_TESTNET=true +NEXT_PUBLIC_MARKETPLACE_ENABLED=false +NEXT_PUBLIC_METADATA_SERVICE_API_HOST=https://metadata.services.blockscout.com +NEXT_PUBLIC_NETWORK_CURRENCY_DECIMALS=18 +NEXT_PUBLIC_NETWORK_CURRENCY_NAME=ETH +NEXT_PUBLIC_NETWORK_CURRENCY_SYMBOL=ETH +NEXT_PUBLIC_NETWORK_ICON=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/network-icons/optimism.svg +NEXT_PUBLIC_NETWORK_ICON_DARK=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/network-icons/optimism.svg +NEXT_PUBLIC_NETWORK_ID=123420111 +NEXT_PUBLIC_NETWORK_LOGO=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/network-logos/optimism.svg +NEXT_PUBLIC_NETWORK_LOGO_DARK=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/network-logos/optimism.svg +NEXT_PUBLIC_NETWORK_NAME=OP Celestia Raspberry +NEXT_PUBLIC_NETWORK_RPC_URL=https://rpc.opcelestia-raspberry.gelato.digital +NEXT_PUBLIC_NETWORK_SHORT_NAME=opcelestia-raspberry +NEXT_PUBLIC_OG_ENHANCED_DATA_ENABLED=true +NEXT_PUBLIC_ROLLUP_L1_BASE_URL=https://eth-sepolia.blockscout.com/ +NEXT_PUBLIC_ROLLUP_L2_WITHDRAWAL_URL=https://bridge.gelato.network/bridge/opcelestia-raspberry +NEXT_PUBLIC_ROLLUP_TYPE=optimistic +NEXT_PUBLIC_STATS_API_HOST=https://stats-opcelestia-raspberry.k8s.blockscout.com +NEXT_PUBLIC_TRANSACTION_INTERPRETATION_PROVIDER=blockscout +NEXT_PUBLIC_VISUALIZE_API_HOST=https://visualizer.services.blockscout.com +NEXT_PUBLIC_WEB3_WALLETS=none \ No newline at end of file diff --git a/configs/envs/.env.optimism_sepolia b/configs/envs/.env.optimism_sepolia new file mode 100644 index 0000000000..146b064965 --- /dev/null +++ b/configs/envs/.env.optimism_sepolia @@ -0,0 +1,54 @@ +# Set of ENVs for OP Sepolia network explorer +# https://optimism-sepolia.blockscout.com +# This is an auto-generated file. To update all values, run "yarn dev:preset:sync --name=optimism_sepolia" + +# Local ENVs +NEXT_PUBLIC_APP_PROTOCOL=http +NEXT_PUBLIC_APP_HOST=localhost +NEXT_PUBLIC_APP_PORT=3000 +NEXT_PUBLIC_APP_ENV=development +NEXT_PUBLIC_API_WEBSOCKET_PROTOCOL=ws + +# Instance ENVs +NEXT_PUBLIC_ADMIN_SERVICE_API_HOST=https://admin-rs.services.blockscout.com +NEXT_PUBLIC_API_BASE_PATH=/ +NEXT_PUBLIC_API_HOST=optimism-sepolia.blockscout.com +NEXT_PUBLIC_API_SPEC_URL=https://raw.githubusercontent.com/blockscout/blockscout-api-v2-swagger/main/swagger.yaml +NEXT_PUBLIC_CONTRACT_CODE_IDES=[{'title':'Remix IDE','url':'https://remix.ethereum.org/?address={hash}&blockscout={domain}','icon_url':'https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/ide-icons/remix.png'}] +NEXT_PUBLIC_CONTRACT_INFO_API_HOST=https://contracts-info.services.blockscout.com +NEXT_PUBLIC_FAULT_PROOF_ENABLED=true +NEXT_PUBLIC_FEATURED_NETWORKS=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/featured-networks/optimism-sepolia.json +NEXT_PUBLIC_FOOTER_LINKS=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/footer-links/optimism.json +NEXT_PUBLIC_GRAPHIQL_TRANSACTION=0x59d26836041ab35169bdce431d68d070b7b8acb589fa52e126e6c828b6ece5e9 +NEXT_PUBLIC_HAS_USER_OPS=true +NEXT_PUBLIC_HOMEPAGE_CHARTS=['daily_txs'] +NEXT_PUBLIC_HOMEPAGE_PLATE_BACKGROUND=linear-gradient(90deg, rgb(232, 52, 53) 0%, rgb(139, 28, 232) 100%) +NEXT_PUBLIC_HOMEPAGE_PLATE_TEXT_COLOR=rgb(255, 255, 255) +NEXT_PUBLIC_IS_ACCOUNT_SUPPORTED=true +NEXT_PUBLIC_IS_TESTNET=true +NEXT_PUBLIC_LOGOUT_URL=https://optimism-goerli.us.auth0.com/v2/logout +NEXT_PUBLIC_MAINTENANCE_ALERT_MESSAGE=

Build faster with the Superchain Dev Console: Get testnet ETH and tools to help you build, launch, and grow your app on the Superchain

+NEXT_PUBLIC_MARKETPLACE_ENABLED=false +NEXT_PUBLIC_METADATA_SERVICE_API_HOST=https://metadata.services.blockscout.com +NEXT_PUBLIC_NAME_SERVICE_API_HOST=https://bens.services.blockscout.com +NEXT_PUBLIC_NETWORK_CURRENCY_DECIMALS=18 +NEXT_PUBLIC_NETWORK_CURRENCY_NAME=Ether +NEXT_PUBLIC_NETWORK_CURRENCY_SYMBOL=ETH +NEXT_PUBLIC_NETWORK_EXPLORERS=[{'title':'Tenderly','logo':'https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/explorer-logos/tenderly.png','baseUrl':'https://dashboard.tenderly.co','paths':{'tx':'/tx/optimistic-sepolia'}}] +NEXT_PUBLIC_NETWORK_ICON=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/network-icons/optimism.svg +NEXT_PUBLIC_NETWORK_ICON_DARK=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/network-icons/optimism.svg +NEXT_PUBLIC_NETWORK_ID=11155420 +NEXT_PUBLIC_NETWORK_LOGO=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/network-logos/optimism.svg +NEXT_PUBLIC_NETWORK_LOGO_DARK=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/network-logos/optimism.svg +NEXT_PUBLIC_NETWORK_NAME=OP Sepolia +NEXT_PUBLIC_NETWORK_RPC_URL=https://sepolia.optimism.io +NEXT_PUBLIC_NETWORK_SHORT_NAME=OP Sepolia +NEXT_PUBLIC_OG_ENHANCED_DATA_ENABLED=true +NEXT_PUBLIC_ROLLUP_L1_BASE_URL=https://eth-sepolia.blockscout.com/ +NEXT_PUBLIC_ROLLUP_L2_WITHDRAWAL_URL=https://app.optimism.io/bridge/withdraw +NEXT_PUBLIC_ROLLUP_TYPE=optimistic +NEXT_PUBLIC_STATS_API_HOST=https://stats-optimism-sepolia.k8s.blockscout.com +NEXT_PUBLIC_TRANSACTION_INTERPRETATION_PROVIDER=blockscout +NEXT_PUBLIC_VIEWS_CONTRACT_SOLIDITYSCAN_ENABLED=true +NEXT_PUBLIC_VISUALIZE_API_HOST=https://visualizer.services.blockscout.com +NEXT_PUBLIC_WEB3_WALLETS=['token_pocket', 'metamask'] \ No newline at end of file diff --git a/configs/envs/.env.poa_core b/configs/envs/.env.poa_core deleted file mode 100644 index f874d89f5d..0000000000 --- a/configs/envs/.env.poa_core +++ /dev/null @@ -1,40 +0,0 @@ -# Set of ENVs for POA network explorer -# https://blockscout.com/poa/core/ - -# app configuration -NEXT_PUBLIC_APP_PROTOCOL=http -NEXT_PUBLIC_APP_HOST=localhost -NEXT_PUBLIC_APP_PORT=3000 - -# blockchain parameters -NEXT_PUBLIC_NETWORK_NAME=POA -NEXT_PUBLIC_NETWORK_SHORT_NAME=POA -NEXT_PUBLIC_NETWORK_ID=99 -NEXT_PUBLIC_NETWORK_CURRENCY_NAME=POA -NEXT_PUBLIC_NETWORK_CURRENCY_SYMBOL=POA -NEXT_PUBLIC_NETWORK_CURRENCY_DECIMALS=18 - -# api configuration -NEXT_PUBLIC_API_HOST=blockscout.com -NEXT_PUBLIC_API_BASE_PATH=/poa/core - -# ui config -## homepage -NEXT_PUBLIC_HOMEPAGE_CHARTS=['daily_txs','coin_price','market_cap'] -NEXT_PUBLIC_HOMEPAGE_PLATE_BACKGROUND='no-repeat bottom 20% right 0px/100% url(https://neon-labs.org/images/index/banner.jpg)' -NEXT_PUBLIC_HOMEPAGE_PLATE_TEXT_COLOR=\#DCFE76 -## sidebar -NEXT_PUBLIC_FEATURED_NETWORKS=https://raw.githubusercontent.com/blockscout/frontend-configs/dev/configs/featured-networks/eth-goerli.json -NEXT_PUBLIC_NETWORK_LOGO=https://raw.githubusercontent.com/blockscout/frontend-configs/dev/configs/network-logos/poa.svg -NEXT_PUBLIC_NETWORK_ICON=https://raw.githubusercontent.com/blockscout/frontend-configs/dev/configs/network-icons/poa.svg -## footer -## misc -NEXT_PUBLIC_NETWORK_EXPLORERS=[{'title':'Anyblock','baseUrl':'https://explorer.anyblock.tools','paths':{'tx':'/ethereum/poa/core/transaction','address':'/ethereum/poa/core/address','block':'/ethereum/poa/core/block'}}] - -# app features -NEXT_PUBLIC_APP_ENV=development -NEXT_PUBLIC_IS_ACCOUNT_SUPPORTED=true -NEXT_PUBLIC_AUTH_URL=http://localhost:3000 -NEXT_PUBLIC_LOGOUT_URL=https://blockscoutcom.us.auth0.com/v2/logout -NEXT_PUBLIC_NETWORK_VERIFICATION_TYPE=validation -NEXT_PUBLIC_NETWORK_RPC_URL=https://core.poa.network diff --git a/configs/envs/.env.polygon b/configs/envs/.env.polygon index 2cc023145d..ddd94291fc 100644 --- a/configs/envs/.env.polygon +++ b/configs/envs/.env.polygon @@ -1,45 +1,47 @@ -# Set of ENVs for Ethereum network explorer -# https://polygon.blockscout.com/ +# Set of ENVs for Polygon Mainnet network explorer +# https://polygon.blockscout.com +# This is an auto-generated file. To update all values, run "yarn dev:preset:sync --name=polygon" -# app configuration +# Local ENVs NEXT_PUBLIC_APP_PROTOCOL=http NEXT_PUBLIC_APP_HOST=localhost NEXT_PUBLIC_APP_PORT=3000 +NEXT_PUBLIC_APP_ENV=development +NEXT_PUBLIC_API_WEBSOCKET_PROTOCOL=ws -# blockchain parameters -NEXT_PUBLIC_NETWORK_NAME=Polygon -NEXT_PUBLIC_NETWORK_SHORT_NAME=Polygon -NEXT_PUBLIC_NETWORK_ID=137 -NEXT_PUBLIC_NETWORK_CURRENCY_NAME=MATIC -NEXT_PUBLIC_NETWORK_CURRENCY_SYMBOL=MATIC -NEXT_PUBLIC_NETWORK_CURRENCY_DECIMALS=18 -NEXT_PUBLIC_NETWORK_VERIFICATION_TYPE=validation -NEXT_PUBLIC_NETWORK_RPC_URL=https://polygon.blockpi.network/v1/rpc/public - -# api configuration -NEXT_PUBLIC_API_HOST=polygon.blockscout.com +# Instance ENVs +NEXT_PUBLIC_AD_ADBUTLER_CONFIG_DESKTOP={ "id": "632019", "width": "728", "height": "90" } +NEXT_PUBLIC_AD_ADBUTLER_CONFIG_MOBILE={ "id": "632018", "width": "320", "height": "100" } +NEXT_PUBLIC_AD_BANNER_PROVIDER=adbutler NEXT_PUBLIC_API_BASE_PATH=/ - -# ui config -## homepage -NEXT_PUBLIC_HOMEPAGE_CHARTS=['daily_txs'] -NEXT_PUBLIC_HOMEPAGE_PLATE_BACKGROUND="linear-gradient(122deg, rgba(162, 41, 197, 1) 0%, rgba(123, 63, 228, 1) 100%)" -NEXT_PUBLIC_HOMEPAGE_PLATE_TEXT_COLOR="rgba(255, 255, 255, 1)" -## sidebar +NEXT_PUBLIC_API_HOST=polygon.blockscout.com +NEXT_PUBLIC_API_SPEC_URL=https://raw.githubusercontent.com/blockscout/blockscout-api-v2-swagger/main/swagger.yaml +NEXT_PUBLIC_CONTRACT_CODE_IDES=[{'title':'Remix IDE','url':'https://remix.ethereum.org/?address={hash}&blockscout={domain}','icon_url':'https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/ide-icons/remix.png'}] +NEXT_PUBLIC_CONTRACT_INFO_API_HOST=https://contracts-info.services.blockscout.com NEXT_PUBLIC_FEATURED_NETWORKS=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/featured-networks/polygon-mainnet.json -NEXT_PUBLIC_NETWORK_LOGO=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/network-logos/polygon.svg +NEXT_PUBLIC_GRAPHIQL_TRANSACTION=0x25fcb396fc8652dcd0040f677a1dcc6fecff390ecafc815894379a3f254f1aa9 +NEXT_PUBLIC_HIDE_INDEXING_ALERT_BLOCKS=true +NEXT_PUBLIC_HIDE_INDEXING_ALERT_INT_TXS=true +NEXT_PUBLIC_HOMEPAGE_CHARTS=['daily_txs'] +NEXT_PUBLIC_HOMEPAGE_PLATE_BACKGROUND=linear-gradient(122deg, rgba(162, 41, 197, 1) 0%, rgba(123, 63, 228, 1) 100%) +NEXT_PUBLIC_HOMEPAGE_PLATE_TEXT_COLOR=rgba(255, 255, 255, 1) +NEXT_PUBLIC_MARKETPLACE_ENABLED=false +NEXT_PUBLIC_METADATA_SERVICE_API_HOST=https://metadata.services.blockscout.com +NEXT_PUBLIC_METASUITES_ENABLED=true +NEXT_PUBLIC_NAME_SERVICE_API_HOST=https://bens.services.blockscout.com +NEXT_PUBLIC_NETWORK_CURRENCY_DECIMALS=18 +NEXT_PUBLIC_NETWORK_CURRENCY_NAME=MATIC +NEXT_PUBLIC_NETWORK_CURRENCY_SYMBOL=MATIC +NEXT_PUBLIC_NETWORK_EXPLORERS=[{'title':'GeckoTerminal','logo':'https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/explorer-logos/geckoterminal.png','baseUrl':'https://www.geckoterminal.com/','paths':{'token':'/polygon_pos/pools'}}] NEXT_PUBLIC_NETWORK_ICON=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/network-icons/polygon-short.svg -## footer -## views -## misc - -# app features -NEXT_PUBLIC_APP_ENV=development -# NEXT_PUBLIC_GRAPHIQL_TRANSACTION=0x97fa753626b8d44011d0b9f9a947c735f20b6e895efdee49d7cda76a50001017 -NEXT_PUBLIC_HAS_BEACON_CHAIN=false -# NEXT_PUBLIC_STATS_API_HOST=https://stats-rsk-testnet.k8s.blockscout.com +NEXT_PUBLIC_NETWORK_ID=137 +NEXT_PUBLIC_NETWORK_LOGO=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/network-logos/polygon.svg +NEXT_PUBLIC_NETWORK_NAME=Polygon Mainnet +NEXT_PUBLIC_NETWORK_RPC_URL=https://polygon.blockpi.network/v1/rpc/public +NEXT_PUBLIC_NETWORK_SHORT_NAME=Polygon +NEXT_PUBLIC_OG_ENHANCED_DATA_ENABLED=true +NEXT_PUBLIC_OG_IMAGE_URL=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/og-images/polygon-mainnet.png +NEXT_PUBLIC_SAFE_TX_SERVICE_URL=https://safe-transaction-polygon.safe.global +NEXT_PUBLIC_TRANSACTION_INTERPRETATION_PROVIDER=blockscout NEXT_PUBLIC_VISUALIZE_API_HOST=https://visualizer.services.blockscout.com -NEXT_PUBLIC_WEB3_WALLETS=['token_pocket','metamask'] - -#meta -NEXT_PUBLIC_OG_IMAGE_URL=https://github.com/blockscout/frontend-configs/blob/main/configs/og-images/polygon-mainnet.png?raw=true +NEXT_PUBLIC_WEB3_WALLETS=['token_pocket', 'metamask'] \ No newline at end of file diff --git a/configs/envs/.env.pw b/configs/envs/.env.pw index 999347058b..a984b9f6fb 100644 --- a/configs/envs/.env.pw +++ b/configs/envs/.env.pw @@ -17,6 +17,7 @@ NEXT_PUBLIC_IS_TESTNET=true NEXT_PUBLIC_NETWORK_VERIFICATION_TYPE=validation # api configuration +NEXT_PUBLIC_API_PROTOCOL=http NEXT_PUBLIC_API_HOST=localhost NEXT_PUBLIC_API_PORT=3003 NEXT_PUBLIC_API_BASE_PATH=/ @@ -24,8 +25,6 @@ NEXT_PUBLIC_API_BASE_PATH=/ # ui config ## homepage NEXT_PUBLIC_HOMEPAGE_CHARTS=['daily_txs','coin_price','market_cap'] -NEXT_PUBLIC_HOMEPAGE_SHOW_AVG_BLOCK_TIME=true -NEXT_PUBLIC_HOMEPAGE_SHOW_GAS_TRACKER=true ## sidebar ## footer NEXT_PUBLIC_GIT_TAG=v1.0.11 @@ -39,16 +38,22 @@ NEXT_PUBLIC_MAINTENANCE_ALERT_MESSAGE= # app features NEXT_PUBLIC_APP_ENV=testing NEXT_PUBLIC_APP_INSTANCE=pw +NEXT_PUBLIC_MARKETPLACE_ENABLED=true NEXT_PUBLIC_MARKETPLACE_CONFIG_URL=https://localhost:3000/marketplace-config.json NEXT_PUBLIC_MARKETPLACE_SUBMIT_FORM=https://localhost:3000/marketplace-submit-form -NEXT_PUBLIC_IS_OPTIMISTIC_L2_NETWORK=false -NEXT_PUBLIC_IS_ZKEVM_L2_NETWORK=false +NEXT_PUBLIC_MARKETPLACE_SUGGEST_IDEAS_FORM=https://localhost:3000/marketplace-suggest-ideas-form NEXT_PUBLIC_AD_BANNER_PROVIDER=slise NEXT_PUBLIC_IS_ACCOUNT_SUPPORTED=true NEXT_PUBLIC_AUTH_URL=http://localhost:3100 NEXT_PUBLIC_LOGOUT_URL=https://blockscoutcom.us.auth0.com/v2/logout NEXT_PUBLIC_AUTH0_CLIENT_ID=xxx -NEXT_PUBLIC_STATS_API_HOST=https://localhost:3004 -NEXT_PUBLIC_CONTRACT_INFO_API_HOST=https://localhost:3005 -NEXT_PUBLIC_ADMIN_SERVICE_API_HOST=https://localhost:3006 +NEXT_PUBLIC_STATS_API_HOST=http://localhost:3004 +NEXT_PUBLIC_CONTRACT_INFO_API_HOST=http://localhost:3005 +NEXT_PUBLIC_ADMIN_SERVICE_API_HOST=http://localhost:3006 +NEXT_PUBLIC_METADATA_SERVICE_API_HOST=http://localhost:3007 +NEXT_PUBLIC_NAME_SERVICE_API_HOST=http://localhost:3008 NEXT_PUBLIC_RE_CAPTCHA_APP_SITE_KEY=xxx +NEXT_PUBLIC_WALLET_CONNECT_PROJECT_ID=xxx +NEXT_PUBLIC_VIEWS_ADDRESS_FORMAT=['base16','bech32'] +NEXT_PUBLIC_VIEWS_ADDRESS_BECH_32_PREFIX=tom +NEXT_PUBLIC_HELIA_VERIFIED_FETCH_ENABLED=false \ No newline at end of file diff --git a/configs/envs/.env.rootstock b/configs/envs/.env.rootstock deleted file mode 100644 index ad4bcfb880..0000000000 --- a/configs/envs/.env.rootstock +++ /dev/null @@ -1,45 +0,0 @@ -# Set of ENVs for Ethereum network explorer -# https://eth.blockscout.com/ - -# app configuration -NEXT_PUBLIC_APP_PROTOCOL=http -NEXT_PUBLIC_APP_HOST=localhost -NEXT_PUBLIC_APP_PORT=3000 - -# blockchain parameters -NEXT_PUBLIC_NETWORK_NAME=Rootstock Testnet -NEXT_PUBLIC_NETWORK_SHORT_NAME=Rootstock -NEXT_PUBLIC_NETWORK_ID=31 -NEXT_PUBLIC_NETWORK_CURRENCY_NAME=tRBTC -NEXT_PUBLIC_NETWORK_CURRENCY_SYMBOL=tRBTC -NEXT_PUBLIC_NETWORK_CURRENCY_DECIMALS=18 -NEXT_PUBLIC_NETWORK_VERIFICATION_TYPE=validation -NEXT_PUBLIC_NETWORK_RPC_URL=https://public-node.testnet.rsk.co - -# api configuration -NEXT_PUBLIC_API_HOST=rootstock-testnet.blockscout.com -NEXT_PUBLIC_API_BASE_PATH=/ - -# ui config -## homepage -NEXT_PUBLIC_HOMEPAGE_CHARTS=['daily_txs'] -NEXT_PUBLIC_HOMEPAGE_PLATE_BACKGROUND="rgb(255, 145, 0)" -## sidebar -NEXT_PUBLIC_FEATURED_NETWORKS=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/featured-networks/rsk-testnet.json -NEXT_PUBLIC_NETWORK_LOGO=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/network-logos/rootstock.svg -NEXT_PUBLIC_NETWORK_ICON=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/network-icons/rootstock-short.svg -## footer -NEXT_PUBLIC_FOOTER_LINKS=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/footer-links/rootstock.json -## views -NEXT_PUBLIC_VIEWS_BLOCK_HIDDEN_FIELDS=['burnt_fees','total_reward','nonce'] -## misc - -# app features -NEXT_PUBLIC_APP_ENV=development -NEXT_PUBLIC_GRAPHIQL_TRANSACTION=0x97fa753626b8d44011d0b9f9a947c735f20b6e895efdee49d7cda76a50001017 -NEXT_PUBLIC_HAS_BEACON_CHAIN=false -NEXT_PUBLIC_STATS_API_HOST=https://stats-rsk-testnet.k8s.blockscout.com -NEXT_PUBLIC_VISUALIZE_API_HOST=https://visualizer.services.blockscout.com - -#meta -NEXT_PUBLIC_OG_IMAGE_URL=https://github.com/blockscout/frontend-configs/blob/main/configs/og-images/rootstock-testnet.png?raw=true diff --git a/configs/envs/.env.rootstock_testnet b/configs/envs/.env.rootstock_testnet new file mode 100644 index 0000000000..06368491ef --- /dev/null +++ b/configs/envs/.env.rootstock_testnet @@ -0,0 +1,46 @@ +# Set of ENVs for Rootstock Testnet network explorer +# https://rootstock-testnet.blockscout.com +# This is an auto-generated file. To update all values, run "yarn dev:preset:sync --name=rootstock_testnet" + +# Local ENVs +NEXT_PUBLIC_APP_PROTOCOL=http +NEXT_PUBLIC_APP_HOST=localhost +NEXT_PUBLIC_APP_PORT=3000 +NEXT_PUBLIC_APP_ENV=development +NEXT_PUBLIC_API_WEBSOCKET_PROTOCOL=ws + +# Instance ENVs +NEXT_PUBLIC_API_BASE_PATH=/ +NEXT_PUBLIC_API_HOST=rootstock-testnet.blockscout.com +NEXT_PUBLIC_API_SPEC_URL=https://raw.githubusercontent.com/blockscout/blockscout-api-v2-swagger/main/swagger.yaml +NEXT_PUBLIC_CONTRACT_CODE_IDES=[{'title':'Remix IDE','url':'https://remix.ethereum.org/?address={hash}&blockscout={domain}','icon_url':'https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/ide-icons/remix.png'}] +NEXT_PUBLIC_FEATURED_NETWORKS=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/featured-networks/rsk-testnet.json +NEXT_PUBLIC_FOOTER_LINKS=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/footer-links/rootstock.json +NEXT_PUBLIC_GRAPHIQL_TRANSACTION=0x98b25020fa6551a439dfee58fb16ca11d9e93d4cdf15f3f07b697cf08cf11643 +NEXT_PUBLIC_HOMEPAGE_CHARTS=['daily_txs'] +NEXT_PUBLIC_HOMEPAGE_PLATE_BACKGROUND=rgb(255, 145, 0) +NEXT_PUBLIC_HOMEPAGE_PLATE_TEXT_COLOR=rgb(255, 255, 255) +NEXT_PUBLIC_IS_ACCOUNT_SUPPORTED=true +NEXT_PUBLIC_IS_TESTNET=true +NEXT_PUBLIC_LOGOUT_URL=https://rootstock.us.auth0.com/v2/logout +NEXT_PUBLIC_MARKETPLACE_ENABLED=false +NEXT_PUBLIC_METADATA_SERVICE_API_HOST=https://metadata.services.blockscout.com +NEXT_PUBLIC_NAME_SERVICE_API_HOST=https://bens.services.blockscout.com +NEXT_PUBLIC_NETWORK_CURRENCY_DECIMALS=18 +NEXT_PUBLIC_NETWORK_CURRENCY_NAME=tRBTC +NEXT_PUBLIC_NETWORK_CURRENCY_SYMBOL=tRBTC +NEXT_PUBLIC_NETWORK_EXPLORERS=[{'title':'Tenderly','logo':'https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/explorer-logos/tenderly.png','baseUrl':'https://dashboard.tenderly.co','paths':{'tx':'/tx/rsk-testnet'}}] +NEXT_PUBLIC_NETWORK_ICON=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/network-icons/rootstock-short.svg +NEXT_PUBLIC_NETWORK_ID=31 +NEXT_PUBLIC_NETWORK_LOGO=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/network-logos/rootstock.svg +NEXT_PUBLIC_NETWORK_NAME=Rootstock Testnet +NEXT_PUBLIC_NETWORK_RPC_URL=https://public-node.testnet.rsk.co +NEXT_PUBLIC_NETWORK_SHORT_NAME=Rootstock +NEXT_PUBLIC_OG_ENHANCED_DATA_ENABLED=true +NEXT_PUBLIC_OG_IMAGE_URL=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/og-images/rootstock-testnet.png +NEXT_PUBLIC_STATS_API_HOST=https://stats-rsk-testnet.k8s.blockscout.com +NEXT_PUBLIC_TRANSACTION_INTERPRETATION_PROVIDER=blockscout +NEXT_PUBLIC_VIEWS_BLOCK_HIDDEN_FIELDS=['burnt_fees','total_reward','nonce'] +NEXT_PUBLIC_VIEWS_CONTRACT_SOLIDITYSCAN_ENABLED=true +NEXT_PUBLIC_VISUALIZE_API_HOST=https://visualizer.services.blockscout.com +NEXT_PUBLIC_HOMEPAGE_STATS=['total_blocks','average_block_time','total_txs','wallet_addresses','gas_tracker','btc_locked'] \ No newline at end of file diff --git a/configs/envs/.env.scroll_sepolia b/configs/envs/.env.scroll_sepolia new file mode 100644 index 0000000000..c29abc0442 --- /dev/null +++ b/configs/envs/.env.scroll_sepolia @@ -0,0 +1,41 @@ +# Set of ENVs for Scroll Sepolia Testnet network explorer +# https://scroll-sepolia.blockscout.com +# This is an auto-generated file. To update all values, run "yarn dev:preset:sync --name=scroll_sepolia" + +# Local ENVs +NEXT_PUBLIC_APP_PROTOCOL=http +NEXT_PUBLIC_APP_HOST=localhost +NEXT_PUBLIC_APP_PORT=3000 +NEXT_PUBLIC_APP_ENV=development +NEXT_PUBLIC_API_WEBSOCKET_PROTOCOL=ws + +# Instance ENVs +NEXT_PUBLIC_API_BASE_PATH=/ +NEXT_PUBLIC_API_HOST=scroll-sepolia.blockscout.com +NEXT_PUBLIC_API_SPEC_URL=https://raw.githubusercontent.com/blockscout/blockscout-api-v2-swagger/main/swagger.yaml +NEXT_PUBLIC_CONTRACT_CODE_IDES=[{'title':'Remix IDE','url':'https://remix.ethereum.org/?address={hash}&blockscout={domain}','icon_url':'https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/ide-icons/remix.png'}] +NEXT_PUBLIC_FEATURED_NETWORKS=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/featured-networks/scroll-testnet.json +NEXT_PUBLIC_GRAPHIQL_TRANSACTION=0xa0d22caf6217a488b1e97b646c5ed88e8a3020a607bcd1f3fe8d4c430bb19ad5 +NEXT_PUBLIC_HOMEPAGE_CHARTS=['daily_txs'] +NEXT_PUBLIC_HOMEPAGE_HERO_BANNER_CONFIG={'background':['rgba(255, 238, 218, 1)'],'text_color':['rgba(25, 6, 2, 1)']} +NEXT_PUBLIC_IS_TESTNET=true +NEXT_PUBLIC_METADATA_SERVICE_API_HOST=https://metadata.services.blockscout.com +NEXT_PUBLIC_NETWORK_CURRENCY_DECIMALS=18 +NEXT_PUBLIC_NETWORK_CURRENCY_NAME=Ether +NEXT_PUBLIC_NETWORK_CURRENCY_SYMBOL=ETH +NEXT_PUBLIC_NETWORK_ICON=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/network-icons/scroll.svg +NEXT_PUBLIC_NETWORK_ICON_DARK=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/network-icons/scroll-dark.svg +NEXT_PUBLIC_NETWORK_ID=534351 +NEXT_PUBLIC_NETWORK_LOGO=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/network-logos/scroll.svg +NEXT_PUBLIC_NETWORK_LOGO_DARK=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/network-logos/scroll-dark.svg +NEXT_PUBLIC_NETWORK_NAME=Scroll Sepolia Testnet +NEXT_PUBLIC_NETWORK_RPC_URL=https://sepolia-rpc.scroll.io +NEXT_PUBLIC_NETWORK_SHORT_NAME=Scroll Sepolia Testnet +NEXT_PUBLIC_OG_ENHANCED_DATA_ENABLED=true +NEXT_PUBLIC_OG_IMAGE_URL=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/og-images/scroll-testnet.png +NEXT_PUBLIC_RE_CAPTCHA_APP_SITE_KEY=6Ld0iT8aAAAAAJdju0CmAwGjW7JTDvIw-Q5pwt5T +NEXT_PUBLIC_STATS_API_HOST=https://stats-scroll-sepolia.k8s-prod-2.blockscout.com +NEXT_PUBLIC_TRANSACTION_INTERPRETATION_PROVIDER=blockscout +NEXT_PUBLIC_VISUALIZE_API_HOST=https://visualizer.services.blockscout.com +NEXT_PUBLIC_ROLLUP_TYPE=scroll +NEXT_PUBLIC_ROLLUP_L1_BASE_URL=https://eth-sepolia.blockscout.com/ \ No newline at end of file diff --git a/configs/envs/.env.sepolia b/configs/envs/.env.sepolia deleted file mode 100644 index b54adc0775..0000000000 --- a/configs/envs/.env.sepolia +++ /dev/null @@ -1,60 +0,0 @@ -# Set of ENVs for Sepolia testnet network explorer -# https://eth-sepolia.blockscout.com/ - -# app configuration -NEXT_PUBLIC_APP_PROTOCOL=http -NEXT_PUBLIC_APP_HOST=localhost -NEXT_PUBLIC_APP_PORT=3000 - -# blockchain parameters -NEXT_PUBLIC_NETWORK_NAME=Sepolia -NEXT_PUBLIC_NETWORK_SHORT_NAME=Sepolia -NEXT_PUBLIC_NETWORK_ID=11155111 -NEXT_PUBLIC_NETWORK_CURRENCY_NAME=Ether -NEXT_PUBLIC_NETWORK_CURRENCY_SYMBOL=ETH -NEXT_PUBLIC_NETWORK_CURRENCY_DECIMALS=18 -NEXT_PUBLIC_NETWORK_VERIFICATION_TYPE=validation -NEXT_PUBLIC_NETWORK_RPC_URL=https://eth-sepolia.public.blastapi.io -NEXT_PUBLIC_IS_TESTNET=true - -# api configuration -NEXT_PUBLIC_API_HOST=eth-sepolia.blockscout.com -NEXT_PUBLIC_API_BASE_PATH=/ - -# ui config -## homepage -NEXT_PUBLIC_HOMEPAGE_CHARTS=['daily_txs'] -NEXT_PUBLIC_HOMEPAGE_PLATE_BACKGROUND='rgba(51, 53, 67, 1)' -NEXT_PUBLIC_HOMEPAGE_PLATE_TEXT_COLOR='rgba(165, 252, 122, 1)' -## sidebar -NEXT_PUBLIC_FEATURED_NETWORKS=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/featured-networks/eth-sepolia.json -NEXT_PUBLIC_NETWORK_LOGO=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/network-logos/sepolia.svg -NEXT_PUBLIC_NETWORK_LOGO_DARK=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/network-logos/sepolia.svg -NEXT_PUBLIC_NETWORK_ICON=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/network-icons/sepolia.png -NEXT_PUBLIC_NETWORK_ICON_DARK=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/network-icons/sepolia.png -NEXT_PUBLIC_OTHER_LINKS=[{'url':'https://sepolia.drpc.org?ref=559183','text':'Public RPC'}] -## footer -NEXT_PUBLIC_FOOTER_LINKS=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/footer-links/sepolia.json -##views -NEXT_PUBLIC_VIEWS_NFT_MARKETPLACES=[{'name':'LooksRare','collection_url':'https://sepolia.looksrare.org/collections/{hash}','instance_url':'https://sepolia.looksrare.org/collections/{hash}/{id}','logo_url':'https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/nft-marketplace-logos/looks-rare.png'}] -## misc -NEXT_PUBLIC_NETWORK_EXPLORERS=[{'title':'Etherscan','baseUrl':'https://sepolia.etherscan.io/','paths':{'tx':'/tx','address':'/address','token':'/token','block':'/block'}},{'title':'Tenderly','baseUrl':'https://dashboard.tenderly.co','paths':{'tx':'/tx/sepolia'}}] - -# app features -NEXT_PUBLIC_APP_ENV=development -NEXT_PUBLIC_GRAPHIQL_TRANSACTION=0xbf69c7abc4fee283b59a9633dadfdaedde5c5ee0fba3e80a08b5b8a3acbd4363 -NEXT_PUBLIC_IS_ACCOUNT_SUPPORTED=true -NEXT_PUBLIC_AUTH_URL=http://localhost:3000 -NEXT_PUBLIC_LOGOUT_URL=https://blockscout-goerli.us.auth0.com/v2/logout -NEXT_PUBLIC_MARKETPLACE_CONFIG_URL=https://raw.githubusercontent.com/blockscout/frontend-configs/dev/configs/marketplace/eth-goerli.json -NEXT_PUBLIC_MARKETPLACE_SUBMIT_FORM=https://airtable.com/shrqUAcjgGJ4jU88C -NEXT_PUBLIC_STATS_API_HOST=https://stats-sepolia.k8s-dev.blockscout.com -NEXT_PUBLIC_VISUALIZE_API_HOST=https://visualizer.services.blockscout.com -NEXT_PUBLIC_CONTRACT_INFO_API_HOST=https://contracts-info.services.blockscout.com -NEXT_PUBLIC_ADMIN_SERVICE_API_HOST=https://admin-rs.services.blockscout.com -NEXT_PUBLIC_WEB3_WALLETS=['token_pocket','metamask'] -NEXT_PUBLIC_VIEWS_CONTRACT_SOLIDITYSCAN_ENABLED=true -NEXT_PUBLIC_HAS_BEACON_CHAIN=true - -#meta -NEXT_PUBLIC_OG_IMAGE_URL=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/og-images/sepolia-testnet.png diff --git a/configs/envs/.env.shibarium b/configs/envs/.env.shibarium new file mode 100644 index 0000000000..dde91dbeb8 --- /dev/null +++ b/configs/envs/.env.shibarium @@ -0,0 +1,64 @@ +# Set of ENVs for Shibarium network explorer +# https://www.shibariumscan.io +# This is an auto-generated file. To update all values, run "yarn preset:sync --name=shibarium" + +# Local ENVs +NEXT_PUBLIC_APP_PROTOCOL=http +NEXT_PUBLIC_APP_HOST=localhost +NEXT_PUBLIC_APP_PORT=3000 +NEXT_PUBLIC_APP_ENV=development +NEXT_PUBLIC_API_WEBSOCKET_PROTOCOL=ws + +# Instance ENVs +NEXT_PUBLIC_AD_ADBUTLER_CONFIG_DESKTOP={ "id": "727504", "width": "728", "height": "90" } +NEXT_PUBLIC_AD_ADBUTLER_CONFIG_MOBILE={ "id": "727507", "width": "320", "height": "100" } +NEXT_PUBLIC_AD_BANNER_ADDITIONAL_PROVIDER=adbutler +NEXT_PUBLIC_AD_BANNER_PROVIDER=hype +NEXT_PUBLIC_ADMIN_SERVICE_API_HOST=https://admin-rs.services.blockscout.com +NEXT_PUBLIC_API_BASE_PATH=/ +NEXT_PUBLIC_API_HOST=www.shibariumscan.io +NEXT_PUBLIC_API_SPEC_URL=https://raw.githubusercontent.com/blockscout/blockscout-api-v2-swagger/main/swagger.yaml +NEXT_PUBLIC_APP_INSTANCE=shibarium_mainnet +NEXT_PUBLIC_CONTRACT_CODE_IDES=[{'title':'Remix IDE','url':'https://remix.ethereum.org/?address={hash}&blockscout={domain}','icon_url':'https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/ide-icons/remix.png'}] +NEXT_PUBLIC_CONTRACT_INFO_API_HOST=https://contracts-info.services.blockscout.com +NEXT_PUBLIC_DEFI_DROPDOWN_ITEMS=[{'text':'Get gas','icon':'gas','dappId':'smol-refuel'}] +NEXT_PUBLIC_FEATURED_NETWORKS=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/featured-networks/shibarium-mainnet.json +NEXT_PUBLIC_GAS_REFUEL_PROVIDER_CONFIG={'name': 'Need gas?', 'url_template': 'https://smolrefuel.com/?outboundChain={chainId}&partner=blockscout&utm_source=blockscout&disableBridges=true', 'dapp_id': 'smol-refuel', 'logo': 'https://blockscout-content.s3.amazonaws.com/smolrefuel-logo-action-button.png'} +NEXT_PUBLIC_GRAPHIQL_TRANSACTION=0xce531d29c0c469fb00b443b8091b8c059b4f13d7e025dd0ef843401d02b9a1a9 +NEXT_PUBLIC_HAS_CONTRACT_AUDIT_REPORTS=true +NEXT_PUBLIC_HIDE_INDEXING_ALERT_INT_TXS=true +NEXT_PUBLIC_HOMEPAGE_CHARTS=['daily_txs', 'coin_price', 'market_cap'] +NEXT_PUBLIC_HOMEPAGE_HERO_BANNER_CONFIG={'background':['linear-gradient(180deg, rgba(224, 111, 44, 1) 0%, rgba(228, 144, 52, 1) 100%)'],'text_color':['rgba(255, 255, 255, 1)']} +NEXT_PUBLIC_IS_ACCOUNT_SUPPORTED=true +NEXT_PUBLIC_LOGOUT_URL=https://shibarium.us.auth0.com/v2/logout +NEXT_PUBLIC_MAINTENANCE_ALERT_MESSAGE=

Participated in our recent Blockscout activities? Check your eligibility and claim your NFT Scout badges. More exciting things are coming soon!

+NEXT_PUBLIC_MARKETPLACE_CATEGORIES_URL=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/marketplace-categories/default.json +NEXT_PUBLIC_MARKETPLACE_ENABLED=true +NEXT_PUBLIC_MARKETPLACE_RATING_AIRTABLE_API_KEY=patbqG4V2CI998jAq.9810c58c9de973ba2650621c94559088cbdfa1a914498e385621ed035d33c0d0 +NEXT_PUBLIC_MARKETPLACE_RATING_AIRTABLE_BASE_ID=appGkvtmKI7fXE4Vs +NEXT_PUBLIC_MARKETPLACE_SECURITY_REPORTS_URL=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/marketplace-security-reports/default.json +NEXT_PUBLIC_MARKETPLACE_SUBMIT_FORM=https://airtable.com/appiy5yijZpMMSKjT/shr6uMGPKjj1DK7NL +NEXT_PUBLIC_MARKETPLACE_SUGGEST_IDEAS_FORM=https://airtable.com/appiy5yijZpMMSKjT/pag3t82DUCyhGRZZO/form +NEXT_PUBLIC_METADATA_SERVICE_API_HOST=https://metadata.services.blockscout.com +NEXT_PUBLIC_NAVIGATION_HIGHLIGHTED_ROUTES=['/apps'] +NEXT_PUBLIC_NETWORK_CURRENCY_DECIMALS=18 +NEXT_PUBLIC_NETWORK_CURRENCY_NAME=BONE +NEXT_PUBLIC_NETWORK_CURRENCY_SYMBOL=BONE +NEXT_PUBLIC_NETWORK_EXPLORERS=[{'title':'GeckoTerminal','logo':'https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/explorer-logos/geckoterminal.png','baseUrl':'https://www.geckoterminal.com/','paths':{'token':'/shibarium/pools'}}] +NEXT_PUBLIC_NETWORK_ICON=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/network-icons/shibarium-short.png +NEXT_PUBLIC_NETWORK_ICON_DARK=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/network-icons/shibarium-short.png +NEXT_PUBLIC_NETWORK_ID=109 +NEXT_PUBLIC_NETWORK_LOGO=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/network-logos/shibarium-light.png +NEXT_PUBLIC_NETWORK_LOGO_DARK=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/network-logos/shibarium-dark.png +NEXT_PUBLIC_NETWORK_NAME=Shibarium +NEXT_PUBLIC_NETWORK_RPC_URL=https://www.shibrpc.com +NEXT_PUBLIC_NETWORK_SHORT_NAME=Shibarium +NEXT_PUBLIC_OG_ENHANCED_DATA_ENABLED=true +NEXT_PUBLIC_OG_IMAGE_URL=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/og-images/shibarium-mainnet.png +NEXT_PUBLIC_ROLLUP_L1_BASE_URL=https://eth.blockscout.com/ +NEXT_PUBLIC_ROLLUP_TYPE=shibarium +NEXT_PUBLIC_STATS_API_HOST=https://stats-shibarium-mainnet.k8s-prod-2.blockscout.com +NEXT_PUBLIC_TRANSACTION_INTERPRETATION_PROVIDER=blockscout +NEXT_PUBLIC_VIEWS_BLOCK_HIDDEN_FIELDS=['miner'] +NEXT_PUBLIC_VIEWS_CONTRACT_SOLIDITYSCAN_ENABLED=true +NEXT_PUBLIC_VISUALIZE_API_HOST=https://visualizer.services.blockscout.com \ No newline at end of file diff --git a/configs/envs/.env.stability_testnet b/configs/envs/.env.stability_testnet new file mode 100644 index 0000000000..e96ca8462d --- /dev/null +++ b/configs/envs/.env.stability_testnet @@ -0,0 +1,55 @@ +# Set of ENVs for Stability Testnet network explorer +# https://stability-testnet.blockscout.com +# This is an auto-generated file. To update all values, run "yarn dev:preset:sync --name=stability_testnet" + +# Local ENVs +NEXT_PUBLIC_APP_PROTOCOL=http +NEXT_PUBLIC_APP_HOST=localhost +NEXT_PUBLIC_APP_PORT=3000 +NEXT_PUBLIC_APP_ENV=development +NEXT_PUBLIC_API_WEBSOCKET_PROTOCOL=ws + +# Instance ENVs +NEXT_PUBLIC_ADMIN_SERVICE_API_HOST=https://admin-rs.services.blockscout.com +NEXT_PUBLIC_API_BASE_PATH=/ +NEXT_PUBLIC_API_HOST=stability-testnet.blockscout.com +NEXT_PUBLIC_API_SPEC_URL=https://raw.githubusercontent.com/blockscout/blockscout-api-v2-swagger/main/swagger.yaml +NEXT_PUBLIC_CONTRACT_CODE_IDES=[{'title':'Remix IDE','url':'https://remix.ethereum.org/?address={hash}&blockscout={domain}','icon_url':'https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/ide-icons/remix.png'}] +NEXT_PUBLIC_CONTRACT_INFO_API_HOST=https://contracts-info.services.blockscout.com/ +NEXT_PUBLIC_FEATURED_NETWORKS=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/featured-networks/stability-testnet.json +NEXT_PUBLIC_GAS_TRACKER_ENABLED=false +NEXT_PUBLIC_GRAPHIQL_TRANSACTION=0x38125475465a4113a216448af2c9570d0e2c25ef313f8cfbef74f1daad7a97b5 +NEXT_PUBLIC_HOMEPAGE_CHARTS=['daily_txs'] +NEXT_PUBLIC_HOMEPAGE_PLATE_BACKGROUND=rgba(46, 51, 81, 1) +NEXT_PUBLIC_HOMEPAGE_PLATE_TEXT_COLOR=rgba(122, 235, 246, 1) +NEXT_PUBLIC_IS_ACCOUNT_SUPPORTED=true +NEXT_PUBLIC_IS_TESTNET=true +NEXT_PUBLIC_LOGOUT_URL=https://blockscout-stability.us.auth0.com/v2/logout +NEXT_PUBLIC_MARKETPLACE_CATEGORIES_URL=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/marketplace-categories/default.json +NEXT_PUBLIC_MARKETPLACE_ENABLED=true +NEXT_PUBLIC_MARKETPLACE_SUBMIT_FORM=https://airtable.com/appiy5yijZpMMSKjT/shr6uMGPKjj1DK7NL +NEXT_PUBLIC_MARKETPLACE_SUGGEST_IDEAS_FORM=https://airtable.com/appiy5yijZpMMSKjT/pag3t82DUCyhGRZZO/form +NEXT_PUBLIC_METADATA_SERVICE_API_HOST=https://metadata.services.blockscout.com +NEXT_PUBLIC_NETWORK_CURRENCY_DECIMALS=18 +NEXT_PUBLIC_NETWORK_CURRENCY_NAME=FREE +NEXT_PUBLIC_NETWORK_CURRENCY_SYMBOL=FREE +NEXT_PUBLIC_NETWORK_ICON=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/network-icons/stability-short.svg +NEXT_PUBLIC_NETWORK_ICON_DARK=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/network-icons/stability-short-dark.svg +NEXT_PUBLIC_NETWORK_ID=20180427 +NEXT_PUBLIC_NETWORK_LOGO=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/network-logos/stability.svg +NEXT_PUBLIC_NETWORK_LOGO_DARK=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/network-logos/stability-dark.svg +NEXT_PUBLIC_NETWORK_NAME=Stability Testnet +NEXT_PUBLIC_NETWORK_RPC_URL=https://free.testnet.stabilityprotocol.com +NEXT_PUBLIC_NETWORK_SHORT_NAME=Stability Testnet +NEXT_PUBLIC_NETWORK_VERIFICATION_TYPE=validation +NEXT_PUBLIC_OG_ENHANCED_DATA_ENABLED=true +NEXT_PUBLIC_OG_IMAGE_URL=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/og-images/stability.png +NEXT_PUBLIC_STATS_API_HOST=https://stats-stability-testnet.k8s.blockscout.com +NEXT_PUBLIC_TRANSACTION_INTERPRETATION_PROVIDER=blockscout +NEXT_PUBLIC_VALIDATORS_CHAIN_TYPE=stability +NEXT_PUBLIC_VIEWS_ADDRESS_HIDDEN_VIEWS=['top_accounts'] +NEXT_PUBLIC_VIEWS_BLOCK_HIDDEN_FIELDS=['burnt_fees','total_reward'] +NEXT_PUBLIC_VIEWS_CONTRACT_SOLIDITYSCAN_ENABLED=true +NEXT_PUBLIC_VIEWS_TX_ADDITIONAL_FIELDS=['fee_per_gas'] +NEXT_PUBLIC_VIEWS_TX_HIDDEN_FIELDS=['value','fee_currency','gas_price','gas_fees','burnt_fees'] +NEXT_PUBLIC_VISUALIZE_API_HOST=https://visualizer.services.blockscout.com \ No newline at end of file diff --git a/configs/envs/.env.zilliqa_prototestnet b/configs/envs/.env.zilliqa_prototestnet new file mode 100644 index 0000000000..00730e8999 --- /dev/null +++ b/configs/envs/.env.zilliqa_prototestnet @@ -0,0 +1,41 @@ +# Set of ENVs for Zilliqa EVM proto-testnet network explorer +# https://zilliqa-prototestnet.blockscout.com +# This is an auto-generated file. To update all values, run "yarn dev:preset:sync --name=zilliqa_prototestnet" + +# Local ENVs +NEXT_PUBLIC_APP_PROTOCOL=http +NEXT_PUBLIC_APP_HOST=localhost +NEXT_PUBLIC_APP_PORT=3000 +NEXT_PUBLIC_APP_ENV=development +NEXT_PUBLIC_API_WEBSOCKET_PROTOCOL=ws + +NEXT_PUBLIC_VIEWS_CONTRACT_LANGUAGE_FILTERS=['solidity','vyper','yul','scilla'] + + +# Instance ENVs +NEXT_PUBLIC_API_BASE_PATH=/ +NEXT_PUBLIC_API_HOST=zilliqa-prototestnet.blockscout.com +NEXT_PUBLIC_API_SPEC_URL=https://raw.githubusercontent.com/blockscout/blockscout-api-v2-swagger/main/swagger.yaml +NEXT_PUBLIC_CONTRACT_CODE_IDES=[{'title':'Remix IDE','url':'https://remix.ethereum.org/?address={hash}&blockscout={domain}','icon_url':'https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/ide-icons/remix.png'}] +NEXT_PUBLIC_GRAPHIQL_TRANSACTION=0x3d1ded3a7924cd3256a4b1a447c9bfb194f54b9a8ceb441edb8bb01563b516db +NEXT_PUBLIC_HOMEPAGE_CHARTS=['daily_txs'] +NEXT_PUBLIC_HOMEPAGE_HERO_BANNER_CONFIG={'background':['linear-gradient(90deg, rgba(0, 208, 198, 1) 0.06%, rgba(43, 146, 151, 1) 99.97%)','linear-gradient(90deg, rgba(0, 208, 198, 1) 0.06%, rgba(43, 146, 151, 1) 50.02%, rgba(0, 0, 0, 1) 99.97%)'],'text_color':['rgba(255, 255, 255, 1)','rgba(255, 255, 255, 1)'],'button':{'_default':{'background':['rgba(38, 6, 124, 1)']},'_hover':{'background':['rgba(17, 4, 87, 1)']}}} +NEXT_PUBLIC_IS_TESTNET=true +NEXT_PUBLIC_METADATA_SERVICE_API_HOST=https://metadata.services.blockscout.com +NEXT_PUBLIC_NETWORK_CURRENCY_DECIMALS=18 +NEXT_PUBLIC_NETWORK_CURRENCY_NAME=ZIL +NEXT_PUBLIC_NETWORK_CURRENCY_SYMBOL=ZIL +NEXT_PUBLIC_NETWORK_ICON=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/network-icons/zilliqa.svg +NEXT_PUBLIC_NETWORK_ICON_DARK=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/network-icons/zilliqa-dark.svg +NEXT_PUBLIC_NETWORK_ID=33103 +NEXT_PUBLIC_NETWORK_LOGO=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/network-logos/zilliqa.svg +NEXT_PUBLIC_NETWORK_LOGO_DARK=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/network-logos/zilliqa-dark.svg +NEXT_PUBLIC_NETWORK_NAME=Zilliqa EVM proto-testnet +NEXT_PUBLIC_NETWORK_RPC_URL=https://api.zq2-prototestnet.zilliqa.com +NEXT_PUBLIC_NETWORK_SHORT_NAME=Zilliqa EVM proto-testnet +NEXT_PUBLIC_OG_ENHANCED_DATA_ENABLED=true +NEXT_PUBLIC_OG_IMAGE_URL=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/og-images/zilliqa.png +NEXT_PUBLIC_TRANSACTION_INTERPRETATION_PROVIDER=blockscout +NEXT_PUBLIC_VIEWS_ADDRESS_BECH_32_PREFIX=zil +NEXT_PUBLIC_VIEWS_ADDRESS_FORMAT=["base16", "bech32"] +NEXT_PUBLIC_VISUALIZE_API_HOST=https://visualizer.services.blockscout.com \ No newline at end of file diff --git a/configs/envs/.env.zkevm b/configs/envs/.env.zkevm index 91d7172d3f..65db320a6b 100644 --- a/configs/envs/.env.zkevm +++ b/configs/envs/.env.zkevm @@ -1,48 +1,48 @@ -# Set of ENVs for zkevm (dev only) -# https://eth.blockscout.com/ +# Set of ENVs for Polygon zkEVM network explorer +# https://zkevm.blockscout.com +# This is an auto-generated file. To update all values, run "yarn dev:preset:sync --name=zkevm" -# app configuration +# Local ENVs NEXT_PUBLIC_APP_PROTOCOL=http NEXT_PUBLIC_APP_HOST=localhost NEXT_PUBLIC_APP_PORT=3000 +NEXT_PUBLIC_APP_ENV=development +NEXT_PUBLIC_API_WEBSOCKET_PROTOCOL=ws -# blockchain parameters -NEXT_PUBLIC_NETWORK_NAME=zkEVM -NEXT_PUBLIC_NETWORK_SHORT_NAME=zkEVM -NEXT_PUBLIC_NETWORK_ID=1 -NEXT_PUBLIC_NETWORK_CURRENCY_NAME=Ether -NEXT_PUBLIC_NETWORK_CURRENCY_SYMBOL=ETH -NEXT_PUBLIC_NETWORK_CURRENCY_DECIMALS=18 -NEXT_PUBLIC_NETWORK_VERIFICATION_TYPE=validation -NEXT_PUBLIC_NETWORK_RPC_URL=https://eth.llamarpc.com - -# api configuration -NEXT_PUBLIC_API_HOST=65.109.173.70 -NEXT_PUBLIC_API_PORT=80 -NEXT_PUBLIC_API_PROTOCOL=http +# Instance ENVs +NEXT_PUBLIC_AD_ADBUTLER_CONFIG_DESKTOP={ "id": "632019", "width": "728", "height": "90" } +NEXT_PUBLIC_AD_ADBUTLER_CONFIG_MOBILE={ "id": "632018", "width": "320", "height": "100" } +NEXT_PUBLIC_AD_BANNER_PROVIDER=adbutler NEXT_PUBLIC_API_BASE_PATH=/ - -# ui config -## homepage +NEXT_PUBLIC_API_HOST=zkevm.blockscout.com +NEXT_PUBLIC_API_SPEC_URL=https://raw.githubusercontent.com/blockscout/blockscout-api-v2-swagger/main/swagger.yaml +NEXT_PUBLIC_CONTRACT_CODE_IDES=[{'title':'Remix IDE','url':'https://remix.ethereum.org/?address={hash}&blockscout={domain}','icon_url':'https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/ide-icons/remix.png'}] +NEXT_PUBLIC_CONTRACT_INFO_API_HOST=https://contracts-info.services.blockscout.com +NEXT_PUBLIC_FEATURED_NETWORKS=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/featured-networks/zkevm.json +NEXT_PUBLIC_GRAPHIQL_TRANSACTION=0x25fcb396fc8652dcd0040f677a1dcc6fecff390ecafc815894379a3f254f1aa9 +NEXT_PUBLIC_HIDE_INDEXING_ALERT_INT_TXS=true NEXT_PUBLIC_HOMEPAGE_CHARTS=['daily_txs'] -## sidebar -NEXT_PUBLIC_FEATURED_NETWORKS=https://raw.githubusercontent.com/blockscout/frontend-configs/dev/configs/featured-networks/eth.json -## footer -## misc -NEXT_PUBLIC_NETWORK_EXPLORERS=[{'title':'Etherscan','baseUrl':'https://etherscan.io/','paths':{'tx':'/tx','address':'/address','token':'/token','block':'/block'}}] -# app features -NEXT_PUBLIC_APP_INSTANCE=local -NEXT_PUBLIC_APP_ENV=development -NEXT_PUBLIC_GRAPHIQL_TRANSACTION=0xf7d4972356e6ae44ae948d0cf19ef2beaf0e574c180997e969a2837da15e349d -NEXT_PUBLIC_HAS_BEACON_CHAIN=true -NEXT_PUBLIC_IS_ACCOUNT_SUPPORTED=true -# NEXT_PUBLIC_AUTH_URL=http://localhost:3000 -NEXT_PUBLIC_API_WEBSOCKET_PROTOCOL=ws -NEXT_PUBLIC_LOGOUT_URL=https://blockscoutcom.us.auth0.com/v2/logout -NEXT_PUBLIC_STATS_API_HOST=https://stats-eth-main.k8s.blockscout.com +NEXT_PUBLIC_HOMEPAGE_PLATE_BACKGROUND=linear-gradient(122deg, rgba(162, 41, 197, 1) 0%, rgba(123, 63, 228, 1) 100%) +NEXT_PUBLIC_HOMEPAGE_PLATE_TEXT_COLOR=rgba(255, 255, 255, 1) +NEXT_PUBLIC_MARKETPLACE_ENABLED=false +NEXT_PUBLIC_METADATA_SERVICE_API_HOST=https://metadata.services.blockscout.com +NEXT_PUBLIC_METASUITES_ENABLED=true +NEXT_PUBLIC_NETWORK_CURRENCY_DECIMALS=18 +NEXT_PUBLIC_NETWORK_CURRENCY_NAME=MATIC +NEXT_PUBLIC_NETWORK_CURRENCY_SYMBOL=MATIC +NEXT_PUBLIC_NETWORK_EXPLORERS=[{'title':'GeckoTerminal','logo':'https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/explorer-logos/geckoterminal.png','baseUrl':'https://www.geckoterminal.com/','paths':{'token':'/polygon-zkevm/pools'}}] +NEXT_PUBLIC_NETWORK_ICON=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/network-icons/polygon-short.svg +NEXT_PUBLIC_NETWORK_ID=1101 +NEXT_PUBLIC_NETWORK_LOGO=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/network-logos/polygon.svg +NEXT_PUBLIC_NETWORK_NAME=Polygon zkEVM +NEXT_PUBLIC_NETWORK_RPC_URL=https://zkevm-rpc.com +NEXT_PUBLIC_NETWORK_SHORT_NAME=zkEVM +NEXT_PUBLIC_OG_ENHANCED_DATA_ENABLED=true +NEXT_PUBLIC_ROLLUP_L1_BASE_URL=https://eth.blockscout.com/ +NEXT_PUBLIC_ROLLUP_TYPE=zkEvm +NEXT_PUBLIC_SAFE_TX_SERVICE_URL=https://safe-transaction-zkevm.safe.global +NEXT_PUBLIC_STATS_API_HOST=https://stats-polygon-zkevm.k8s-prod-1.blockscout.com +NEXT_PUBLIC_TRANSACTION_INTERPRETATION_PROVIDER=blockscout +NEXT_PUBLIC_VIEWS_CONTRACT_SOLIDITYSCAN_ENABLED=true NEXT_PUBLIC_VISUALIZE_API_HOST=https://visualizer.services.blockscout.com -NEXT_PUBLIC_CONTRACT_INFO_API_HOST=https://contracts-info.services.blockscout.com -NEXT_PUBLIC_ADMIN_SERVICE_API_HOST=https://admin-rs.services.blockscout.com -# rollup -NEXT_PUBLIC_IS_ZKEVM_L2_NETWORK=true -NEXT_PUBLIC_L1_BASE_URL=http://65.109.173.70:81 +NEXT_PUBLIC_WEB3_WALLETS=['token_pocket', 'metamask'] \ No newline at end of file diff --git a/configs/envs/.env.zksync b/configs/envs/.env.zksync new file mode 100644 index 0000000000..f7b18c0548 --- /dev/null +++ b/configs/envs/.env.zksync @@ -0,0 +1,52 @@ +# Set of ENVs for ZkSync Era network explorer +# https://zksync.blockscout.com +# This is an auto-generated file. To update all values, run "yarn dev:preset:sync --name=zksync" + +# Local ENVs +NEXT_PUBLIC_APP_PROTOCOL=http +NEXT_PUBLIC_APP_HOST=localhost +NEXT_PUBLIC_APP_PORT=3000 +NEXT_PUBLIC_APP_ENV=development +NEXT_PUBLIC_API_WEBSOCKET_PROTOCOL=ws + +# Instance ENVs +NEXT_PUBLIC_VIEWS_CONTRACT_EXTRA_VERIFICATION_METHODS=none + +NEXT_PUBLIC_ADMIN_SERVICE_API_HOST=https://admin-rs.services.blockscout.com +NEXT_PUBLIC_API_BASE_PATH=/ +NEXT_PUBLIC_API_HOST=zksync.blockscout.com +NEXT_PUBLIC_API_SPEC_URL=https://raw.githubusercontent.com/blockscout/blockscout-api-v2-swagger/main/swagger.yaml +NEXT_PUBLIC_CONTRACT_INFO_API_HOST=https://contracts-info.services.blockscout.com +NEXT_PUBLIC_FEATURED_NETWORKS=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/featured-networks/zksync.json +NEXT_PUBLIC_FOOTER_LINKS=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/footer-links/zksync.json +NEXT_PUBLIC_GRAPHIQL_TRANSACTION=0x79c7802ccdf3be5a49c47cc751aad351b0027e8275f6f54878eda50ee559a648 +NEXT_PUBLIC_HOMEPAGE_CHARTS=['daily_txs'] +NEXT_PUBLIC_HOMEPAGE_PLATE_BACKGROUND=rgba(53, 103, 246, 1) +NEXT_PUBLIC_HOMEPAGE_PLATE_TEXT_COLOR=rgba(255, 255, 255, 1) +NEXT_PUBLIC_IS_ACCOUNT_SUPPORTED=true +NEXT_PUBLIC_LOGOUT_URL=https://zksync.us.auth0.com/v2/logout +NEXT_PUBLIC_METADATA_SERVICE_API_HOST=https://metadata.services.blockscout.com +NEXT_PUBLIC_METASUITES_ENABLED=true +NEXT_PUBLIC_MULTICHAIN_BALANCE_PROVIDER_CONFIG=[{'name': 'zerion', 'dapp_id': 'zerion', 'url_template': 'https://app.zerion.io/{address}/overview?utm_source=blockscout&utm_medium=address', 'logo': 'https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/marketplace-logos/zerion.svg'}] +NEXT_PUBLIC_NETWORK_CURRENCY_DECIMALS=18 +NEXT_PUBLIC_NETWORK_CURRENCY_NAME=ETH +NEXT_PUBLIC_NETWORK_CURRENCY_SYMBOL=ETH +NEXT_PUBLIC_NETWORK_EXPLORERS=[{'title':'GeckoTerminal','logo':'https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/explorer-logos/geckoterminal.png','baseUrl':'https://www.geckoterminal.com/','paths':{'token':'/zksync/pools'}},{'title':'L2scan','logo':'https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/explorer-logos/zksync.png','baseUrl':'https://zksync-era.l2scan.co/','paths':{'tx':'/tx','address':'/address','token':'/token','block':'/block'}}] +NEXT_PUBLIC_NETWORK_ICON=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/network-icons/zksync-short.svg +NEXT_PUBLIC_NETWORK_ICON_DARK=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/network-icons/zksync-short-dark.svg +NEXT_PUBLIC_NETWORK_ID=324 +NEXT_PUBLIC_NETWORK_LOGO=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/network-logos/zksync.svg +NEXT_PUBLIC_NETWORK_LOGO_DARK=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/network-logos/zksync-dark.svg +NEXT_PUBLIC_NETWORK_NAME=ZkSync Era +NEXT_PUBLIC_NETWORK_RPC_URL=https://mainnet.era.zksync.io +NEXT_PUBLIC_NETWORK_SHORT_NAME=ZkSync Era +NEXT_PUBLIC_OG_ENHANCED_DATA_ENABLED=true +NEXT_PUBLIC_OG_IMAGE_URL=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/og-images/zksync.png +NEXT_PUBLIC_OTHER_LINKS=[{'url':'https://zksync.drpc.org?ref=559183','text':'Public RPC'}] +NEXT_PUBLIC_ROLLUP_L1_BASE_URL=https://eth.blockscout.com/ +NEXT_PUBLIC_ROLLUP_TYPE=zkSync +NEXT_PUBLIC_SAFE_TX_SERVICE_URL=https://safe-transaction-zksync.safe.global +NEXT_PUBLIC_STATS_API_HOST=https://stats-zksync-era-mainnet.k8s-prod-2.blockscout.com +NEXT_PUBLIC_TRANSACTION_INTERPRETATION_PROVIDER=blockscout +NEXT_PUBLIC_VIEWS_CONTRACT_SOLIDITYSCAN_ENABLED=true +NEXT_PUBLIC_VISUALIZE_API_HOST=https://visualizer.services.blockscout.com \ No newline at end of file diff --git a/configs/envs/.env.zora b/configs/envs/.env.zora new file mode 100644 index 0000000000..de5a551bd1 --- /dev/null +++ b/configs/envs/.env.zora @@ -0,0 +1,60 @@ +# Set of ENVs for Zora Mainnet network explorer +# https://explorer.zora.energy +# This is an auto-generated file. To update all values, run "yarn dev:preset:sync --name=zora" + +# Local ENVs +NEXT_PUBLIC_APP_PROTOCOL=http +NEXT_PUBLIC_APP_HOST=localhost +NEXT_PUBLIC_APP_PORT=3000 +NEXT_PUBLIC_APP_ENV=development +NEXT_PUBLIC_API_WEBSOCKET_PROTOCOL=ws + +# Instance ENVs +NEXT_PUBLIC_AD_BANNER_PROVIDER=none +NEXT_PUBLIC_AD_TEXT_PROVIDER=none +NEXT_PUBLIC_ADMIN_SERVICE_API_HOST=https://admin-rs.services.blockscout.com +NEXT_PUBLIC_API_BASE_PATH=/ +NEXT_PUBLIC_API_HOST=explorer.zora.energy +NEXT_PUBLIC_API_SPEC_URL=https://raw.githubusercontent.com/blockscout/blockscout-api-v2-swagger/main/swagger.yaml +NEXT_PUBLIC_CONTRACT_CODE_IDES=[{'title':'Remix IDE','url':'https://remix.ethereum.org/?address={hash}&blockscout={domain}','icon_url':'https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/ide-icons/remix.png'}] +NEXT_PUBLIC_CONTRACT_INFO_API_HOST=https://contracts-info.services.blockscout.com +NEXT_PUBLIC_DEFI_DROPDOWN_ITEMS=[{'text':'Swap','icon':'swap','dappId':'uniswap'}] +NEXT_PUBLIC_FEATURED_NETWORKS=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/featured-networks/zora.json +NEXT_PUBLIC_GRAPHIQL_TRANSACTION=0x6d54c0226a57f5bc854f8aa589bb15113388f984f318c9e1b2722115e4e35873 +NEXT_PUBLIC_HAS_CONTRACT_AUDIT_REPORTS=true +NEXT_PUBLIC_HAS_USER_OPS=true +NEXT_PUBLIC_HOMEPAGE_CHARTS=['daily_txs'] +NEXT_PUBLIC_HOMEPAGE_PLATE_BACKGROUND=linear-gradient(89deg, rgb(63, 36, 22) 0.56%, rgb(44, 56, 105) 98.31%) +NEXT_PUBLIC_IS_ACCOUNT_SUPPORTED=true +NEXT_PUBLIC_LOGOUT_URL=https://zora-blockscout.us.auth0.com/v2/logout +NEXT_PUBLIC_MARKETPLACE_ENABLED=true +NEXT_PUBLIC_MARKETPLACE_RATING_AIRTABLE_API_KEY=patbqG4V2CI998jAq.9810c58c9de973ba2650621c94559088cbdfa1a914498e385621ed035d33c0d0 +NEXT_PUBLIC_MARKETPLACE_RATING_AIRTABLE_BASE_ID=appGkvtmKI7fXE4Vs +NEXT_PUBLIC_MARKETPLACE_SUBMIT_FORM=https://airtable.com/appiy5yijZpMMSKjT/shr6uMGPKjj1DK7NL +NEXT_PUBLIC_MARKETPLACE_SUGGEST_IDEAS_FORM=https://airtable.com/appiy5yijZpMMSKjT/pag3t82DUCyhGRZZO/form +NEXT_PUBLIC_METADATA_SERVICE_API_HOST=https://metadata.services.blockscout.com +NEXT_PUBLIC_NAME_SERVICE_API_HOST=https://bens.services.blockscout.com +NEXT_PUBLIC_NAVIGATION_HIGHLIGHTED_ROUTES=['/apps'] +NEXT_PUBLIC_NETWORK_CURRENCY_DECIMALS=18 +NEXT_PUBLIC_NETWORK_CURRENCY_NAME=Ether +NEXT_PUBLIC_NETWORK_CURRENCY_SYMBOL=ETH +NEXT_PUBLIC_NETWORK_EXPLORERS=[{'title':'GeckoTerminal','logo':'https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/explorer-logos/geckoterminal.png','baseUrl':'https://www.geckoterminal.com/','paths':{'token':'/zora-network/pools'}}] +NEXT_PUBLIC_NETWORK_ICON=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/network-icons/zora.svg +NEXT_PUBLIC_NETWORK_ICON_DARK=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/network-icons/zora-dark.svg +NEXT_PUBLIC_NETWORK_ID=7777777 +NEXT_PUBLIC_NETWORK_LOGO=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/network-logos/zora.svg +NEXT_PUBLIC_NETWORK_LOGO_DARK=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/network-logos/zora-dark.svg +NEXT_PUBLIC_NETWORK_NAME=Zora Mainnet +NEXT_PUBLIC_NETWORK_RPC_URL=https://rpc.zora.energy +NEXT_PUBLIC_NETWORK_SHORT_NAME=Zora Mainnet +NEXT_PUBLIC_NETWORK_VERIFICATION_TYPE=validation +NEXT_PUBLIC_OG_ENHANCED_DATA_ENABLED=true +NEXT_PUBLIC_OG_IMAGE_URL=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/og-images/zora-mainnet.png +NEXT_PUBLIC_ROLLUP_L1_BASE_URL=https://eth.blockscout.com/ +NEXT_PUBLIC_ROLLUP_L2_WITHDRAWAL_URL=https://bridge.zora.energy +NEXT_PUBLIC_ROLLUP_TYPE=optimistic +NEXT_PUBLIC_STATS_API_HOST=https://stats-l2-zora-mainnet.k8s-prod-1.blockscout.com +NEXT_PUBLIC_TRANSACTION_INTERPRETATION_PROVIDER=blockscout +NEXT_PUBLIC_VISUALIZE_API_HOST=https://visualizer.services.blockscout.com +NEXT_PUBLIC_MULTICHAIN_BALANCE_PROVIDER_CONFIG=[{'name': 'zerion', 'dapp_id': 'zerion', 'url_template': 'https://app.zerion.io/{address}/overview?utm_source=blockscout&utm_medium=address', 'logo': 'https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/marketplace-logos/zerion.svg'}] +NEXT_PUBLIC_ADDRESS_USERNAME_TAG={'api_url_template': 'https://api.zora.co/discover/user/{address}', 'tag_link_template': 'httpszora.co/{username}', 'tag_icon': 'https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/network-icons/zora.svg', 'tag_bg_color': 'rgba(0,0,0)', 'tag_text_color': 'rgba(255,255,255)'} diff --git a/deploy/helmfile.yaml b/deploy/helmfile.yaml index 0e401dce3d..6052eda969 100644 --- a/deploy/helmfile.yaml +++ b/deploy/helmfile.yaml @@ -16,28 +16,6 @@ repositories: url: https://bedag.github.io/helm-charts releases: - # Deploy front-main - - name: bs-stack - chart: blockscout/blockscout-stack - version: 1.2.* - namespace: front-main - labels: - app: front - values: - - values/main/values.yaml - - global: - env: "main" - # Deploy l2-optimism-goerli - - name: bs-stack - chart: blockscout/blockscout-stack - version: 1.2.* - namespace: l2-optimism-goerli - labels: - app: l2-optimism-goerli - values: - - values/l2-optimism-goerli/values.yaml - - global: - env: "optimism-goerli" # Deploy review L2 - name: reg-secret chart: bedag/raw @@ -55,7 +33,7 @@ releases: type: kubernetes.io/dockerconfigjson - name: bs-stack chart: blockscout/blockscout-stack - version: 1.2.* + version: 1.*.* namespace: review-l2-{{ requiredEnv "GITHUB_REF_NAME_SLUG" }} labels: app: review-l2-{{ requiredEnv "GITHUB_REF_NAME_SLUG" }} @@ -80,7 +58,7 @@ releases: type: kubernetes.io/dockerconfigjson - name: bs-stack chart: blockscout/blockscout-stack - version: 1.2.* + version: 1.*.* namespace: review-{{ requiredEnv "GITHUB_REF_NAME_SLUG" }} labels: app: review-{{ requiredEnv "GITHUB_REF_NAME_SLUG" }} diff --git a/deploy/scripts/download_assets.sh b/deploy/scripts/download_assets.sh index f80d043ad0..f53bbb2bea 100755 --- a/deploy/scripts/download_assets.sh +++ b/deploy/scripts/download_assets.sh @@ -16,6 +16,9 @@ ASSETS_DIR="$1" ASSETS_ENVS=( "NEXT_PUBLIC_MARKETPLACE_CONFIG_URL" "NEXT_PUBLIC_MARKETPLACE_CATEGORIES_URL" + "NEXT_PUBLIC_MARKETPLACE_SECURITY_REPORTS_URL" + "NEXT_PUBLIC_MARKETPLACE_BANNER_CONTENT_URL" + "NEXT_PUBLIC_MARKETPLACE_GRAPH_LINKS_URL" "NEXT_PUBLIC_FEATURED_NETWORKS" "NEXT_PUBLIC_FOOTER_LINKS" "NEXT_PUBLIC_NETWORK_LOGO" @@ -47,10 +50,14 @@ get_target_filename() { # Extract the extension from the filename local extension="${filename##*.}" else - # Remove query parameters from the URL and get the filename - local filename=$(basename "${url%%\?*}") - # Extract the extension from the filename - local extension="${filename##*.}" + if [[ "$url" == http* ]]; then + # Remove query parameters from the URL and get the filename + local filename=$(basename "${url%%\?*}") + # Extract the extension from the filename + local extension="${filename##*.}" + else + local extension="json" + fi fi # Convert the extension to lowercase @@ -78,16 +85,31 @@ download_and_save_asset() { # Copy the local file to the destination cp "${url#file://}" "$destination" else - # Download the asset using curl - curl -s -o "$destination" "$url" + # Check if the value is a URL + if [[ "$url" == http* ]]; then + # Download the asset using curl + curl -s -o "$destination" "$url" + else + # Convert single-quoted JSON-like content to valid JSON + json_content=$(echo "${!env_var}" | sed "s/'/\"/g") + + # Save the JSON content to a file + echo "$json_content" > "$destination" + fi + fi + + if [[ "$url" == file://* ]] || [[ "$url" == http* ]]; then + local source_name=$url + else + local source_name="raw input" fi # Check if the download was successful if [ $? -eq 0 ]; then - echo " [+] $env_var: Successfully saved file from $url to $destination." + echo " [+] $env_var: Successfully saved file from $source_name to $destination." return 0 else - echo " [-] $env_var: Failed to save file from $url." + echo " [-] $env_var: Failed to save file from $source_name." return 1 fi } diff --git a/deploy/scripts/entrypoint.sh b/deploy/scripts/entrypoint.sh index 2924f09189..ac1f91d6c5 100755 --- a/deploy/scripts/entrypoint.sh +++ b/deploy/scripts/entrypoint.sh @@ -1,12 +1,52 @@ #!/bin/bash + +export_envs_from_preset() { + if [ -z "$ENVS_PRESET" ]; then + return + fi + + if [ "$ENVS_PRESET" = "none" ]; then + return + fi + + local preset_file="./configs/envs/.env.$ENVS_PRESET" + + if [ ! -f "$preset_file" ]; then + return + fi + + local blacklist=( + "NEXT_PUBLIC_APP_PROTOCOL" + "NEXT_PUBLIC_APP_HOST" + "NEXT_PUBLIC_APP_PORT" + "NEXT_PUBLIC_APP_ENV" + "NEXT_PUBLIC_API_WEBSOCKET_PROTOCOL" + ) + + while IFS='=' read -r name value; do + name="${name#"${name%%[![:space:]]*}"}" # Trim leading whitespace + if [[ -n $name && $name == "NEXT_PUBLIC_"* && ! "${blacklist[*]}" =~ "$name" ]]; then + export "$name"="$value" + fi + done < <(grep "^[^#;]" "$preset_file") +} + +# If there is a preset, load the environment variables from the its file +export_envs_from_preset + # Download external assets -./download_assets.sh ./public/assets +./download_assets.sh ./public/assets/configs # Check run-time ENVs values -./validate_envs.sh -if [ $? -ne 0 ]; then - exit 1 +if [ "$SKIP_ENVS_VALIDATION" != "true" ]; then + ./validate_envs.sh + if [ $? -ne 0 ]; then + exit 1 + fi +else + echo "😱 Skipping ENVs validation." + echo fi # Generate favicons bundle diff --git a/deploy/scripts/favicon_generator.sh b/deploy/scripts/favicon_generator.sh index 4f153dcb0b..7863bda406 100755 --- a/deploy/scripts/favicon_generator.sh +++ b/deploy/scripts/favicon_generator.sh @@ -4,13 +4,14 @@ master_url="${FAVICON_MASTER_URL:-$NEXT_PUBLIC_NETWORK_ICON}" export MASTER_URL="$master_url" cd ./deploy/tools/favicon-generator -./script.sh +yarn install --frozen-lockfile +node "$(pwd)/index.js" if [ $? -ne 0 ]; then cd ../../../ exit 1 else cd ../../../ - favicon_folder="./public/favicon/" + favicon_folder="./public/assets/favicon/" echo "⏳ Replacing default favicons with freshly generated pack..." if [ -d "$favicon_folder" ]; then diff --git a/deploy/scripts/make_envs_script.sh b/deploy/scripts/make_envs_script.sh index 1124a1a77c..b3548cb207 100755 --- a/deploy/scripts/make_envs_script.sh +++ b/deploy/scripts/make_envs_script.sh @@ -3,7 +3,7 @@ echo "🌀 Creating client script with ENV values..." # Define the output file name -output_file="${1:-./public/envs.js}" +output_file="${1:-./public/assets/envs.js}" touch $output_file; truncate -s 0 $output_file; @@ -18,6 +18,12 @@ echo "window.__envs = {" >> $output_file; # Iterate through all environment variables for var in $(env | grep '^NEXT_PUBLIC_' | cut -d= -f1); do + # Skip variables that start with NEXT_PUBLIC_VERCEL. Vercel injects these + # and they can cause runtime errors, particularly when commit messages wrap lines. + if [[ $var == NEXT_PUBLIC_VERCEL* ]]; then + continue + fi + # Get the value of the variable value="${!var}" diff --git a/deploy/tools/affected-tests/.gitignore b/deploy/tools/affected-tests/.gitignore new file mode 100644 index 0000000000..30bc162798 --- /dev/null +++ b/deploy/tools/affected-tests/.gitignore @@ -0,0 +1 @@ +/node_modules \ No newline at end of file diff --git a/deploy/tools/affected-tests/index.js b/deploy/tools/affected-tests/index.js new file mode 100644 index 0000000000..f2fc450eb6 --- /dev/null +++ b/deploy/tools/affected-tests/index.js @@ -0,0 +1,208 @@ +/* eslint-disable no-console */ +const { execSync } = require('child_process'); +const dependencyTree = require('dependency-tree'); +const fs = require('fs'); +const path = require('path'); + +const ROOT_DIR = path.resolve(__dirname, '../../../'); + +const TARGET_FILE = path.resolve(ROOT_DIR, './playwright/affected-tests.txt'); + +const NON_EXISTENT_DEPS = []; + +const DIRECTORIES_WITH_TESTS = [ + path.resolve(ROOT_DIR, './ui'), +]; +const VISITED = {}; + +function getAllPwFilesInDirectory(directory) { + const files = fs.readdirSync(directory, { recursive: true }); + return files + .filter((file) => file.endsWith('.pw.tsx')) + .map((file) => path.join(directory, file)); +} + +function getFileDeps(filename, changedNpmModules) { + return dependencyTree.toList({ + filename, + directory: ROOT_DIR, + filter: (path) => { + if (path.indexOf('node_modules') === -1) { + return true; + } + + if (changedNpmModules.some((module) => path.startsWith(module))) { + return true; + } + + return false; + }, + tsConfig: path.resolve(ROOT_DIR, './tsconfig.json'), + nonExistent: NON_EXISTENT_DEPS, + visited: VISITED, + }); +} + +async function getChangedFiles() { + const command = process.env.CI ? + `git diff --name-only origin/${ process.env.GITHUB_BASE_REF } ${ process.env.GITHUB_SHA } -- ${ ROOT_DIR }` : + `git diff --name-only main $(git branch --show-current) -- ${ ROOT_DIR }`; + + console.log('Executing command: ', command); + const files = execSync(command) + .toString() + .trim() + .split('\n') + .filter(Boolean); + + return files.map((file) => path.join(ROOT_DIR, file)); +} + +function checkChangesInChakraTheme(changedFiles) { + const themeDir = path.resolve(ROOT_DIR, './theme'); + return changedFiles.some((file) => file.startsWith(themeDir)); +} + +function checkChangesInSvgSprite(changedFiles) { + const iconDir = path.resolve(ROOT_DIR, './icons'); + const areIconsChanged = changedFiles.some((file) => file.startsWith(iconDir)); + + if (!areIconsChanged) { + return false; + } + + const svgNamesFile = path.resolve(ROOT_DIR, './public/icons/name.d.ts'); + const areSvgNamesChanged = changedFiles.some((file) => file === svgNamesFile); + + if (!areSvgNamesChanged) { + // If only the icons have changed and not the names in the SVG file, we will need to run all tests. + // This is because we cannot correctly identify the test files that depend on these changes. + return true; + } + + // If the icon names have changed, then there should be changes in the components that use them. + // Otherwise, typescript would complain about that. + return false; +} + +function createTargetFile(content) { + fs.writeFileSync(TARGET_FILE, content); +} + +function getPackageJsonUpdatedProps(packageJsonFile) { + const command = process.env.CI ? + `git diff --unified=0 origin/${ process.env.GITHUB_BASE_REF } ${ process.env.GITHUB_SHA } -- ${ packageJsonFile }` : + `git diff --unified=0 main $(git branch --show-current) -- ${ packageJsonFile }`; + + console.log('Executing command: ', command); + const changedLines = execSync(command) + .toString() + .trim() + .split('\n') + .filter(Boolean) + .filter((line) => line.startsWith('+ ') || line.startsWith('- ')); + + const changedProps = [ ...new Set( + changedLines + .map((line) => line.replaceAll(' ', '').replaceAll('+', '').replaceAll('-', '')) + .map((line) => line.split(':')[0].replaceAll('"', '')), + ) ]; + + return changedProps; +} + +function getUpdatedNpmModules(changedFiles) { + const packageJsonFile = path.resolve(ROOT_DIR, './package.json'); + + if (!changedFiles.includes(packageJsonFile)) { + return []; + } + + try { + const packageJsonContent = JSON.parse(fs.readFileSync(packageJsonFile, 'utf-8')); + const usedNpmModules = [ + ...Object.keys(packageJsonContent.dependencies || {}), + ...Object.keys(packageJsonContent.devDependencies || {}), + ]; + const updatedProps = getPackageJsonUpdatedProps(packageJsonFile); + + return updatedProps.filter((prop) => usedNpmModules.includes(prop)); + } catch (error) {} +} + +async function run() { + // NOTES: + // - The absence of TARGET_FILE implies that all tests should be run. + // - The empty TARGET_FILE implies that no tests should be run. + + const start = Date.now(); + + fs.unlink(TARGET_FILE, () => {}); + + const changedFiles = await getChangedFiles(); + + if (!changedFiles.length) { + createTargetFile(''); + console.log('No changed files found. Exiting...'); + return; + } + + console.log('Changed files in the branch: ', changedFiles); + + if (checkChangesInChakraTheme(changedFiles)) { + console.log('Changes in Chakra theme detected. It is advisable to run all test suites. Exiting...'); + return; + } + + if (checkChangesInSvgSprite(changedFiles)) { + console.log('There are some changes in the SVG sprite that cannot be linked to a specific component. It is advisable to run all test suites. Exiting...'); + return; + } + + let changedNpmModules = getUpdatedNpmModules(changedFiles); + + if (!changedNpmModules) { + console.log('Some error occurred while detecting changed NPM modules. It is advisable to run all test suites. Exiting...'); + return; + } + + console.log('Changed NPM modules in the branch: ', changedNpmModules); + + changedNpmModules = [ + ...changedNpmModules, + ...changedNpmModules.map((module) => `@types/${ module }`), // there are some deps that are resolved to .d.ts files + ].map((module) => path.resolve(ROOT_DIR, `./node_modules/${ module }`)); + + const allTestFiles = DIRECTORIES_WITH_TESTS.reduce((acc, dir) => { + return acc.concat(getAllPwFilesInDirectory(dir)); + }, []); + + const isDepChanged = (dep) => changedFiles.includes(dep) || changedNpmModules.some((module) => dep.startsWith(module)); + + const testFilesToRun = allTestFiles + .map((file) => ({ file, deps: getFileDeps(file, changedNpmModules) })) + .filter(({ deps }) => deps.some(isDepChanged)); + const testFileNamesToRun = testFilesToRun.map(({ file }) => path.relative(ROOT_DIR, file)); + + if (!testFileNamesToRun.length) { + createTargetFile(''); + console.log('No tests to run. Exiting...'); + return; + } + + createTargetFile(testFileNamesToRun.join('\n')); + + const end = Date.now(); + + const testFilesToRunWithFilteredDeps = testFilesToRun.map(({ file, deps }) => ({ + file, + deps: deps.filter(isDepChanged), + })); + + console.log('Total time: ', ((end - start) / 1_000).toLocaleString()); + console.log('Total test to run: ', testFileNamesToRun.length); + console.log('Tests to run with changed deps: ', testFilesToRunWithFilteredDeps); + console.log('Non existent deps: ', NON_EXISTENT_DEPS); +} + +run(); diff --git a/deploy/tools/affected-tests/package.json b/deploy/tools/affected-tests/package.json new file mode 100644 index 0000000000..bfba5734fa --- /dev/null +++ b/deploy/tools/affected-tests/package.json @@ -0,0 +1,10 @@ +{ + "name": "affected-tests", + "version": "1.0.0", + "main": "index.js", + "author": "Vasilii (tom) Goriunov ", + "license": "MIT", + "dependencies": { + "dependency-tree": "10.0.9" + } +} diff --git a/deploy/tools/affected-tests/yarn.lock b/deploy/tools/affected-tests/yarn.lock new file mode 100644 index 0000000000..bd755eda2b --- /dev/null +++ b/deploy/tools/affected-tests/yarn.lock @@ -0,0 +1,716 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +"@babel/parser@^7.21.8": + version "7.23.9" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.23.9.tgz#7b903b6149b0f8fa7ad564af646c4c38a77fc44b" + integrity sha512-9tcKgqKbs3xGJ+NtKF2ndOBBLVwPjl1SHxPQkd36r3Dlirw3xWUeGaTbqr7uGZcTaxkVNwc+03SVP7aCdWrTlA== + +"@dependents/detective-less@^4.1.0": + version "4.1.0" + resolved "https://registry.yarnpkg.com/@dependents/detective-less/-/detective-less-4.1.0.tgz#4a979ee7a6a79eb33602862d6a1263e30f98002e" + integrity sha512-KrkT6qO5NxqNfy68sBl6CTSoJ4SNDIS5iQArkibhlbGU4LaDukZ3q2HIkh8aUKDio6o4itU4xDR7t82Y2eP1Bg== + dependencies: + gonzales-pe "^4.3.0" + node-source-walk "^6.0.1" + +"@nodelib/fs.scandir@2.1.5": + version "2.1.5" + resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz#7619c2eb21b25483f6d167548b4cfd5a7488c3d5" + integrity sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g== + dependencies: + "@nodelib/fs.stat" "2.0.5" + run-parallel "^1.1.9" + +"@nodelib/fs.stat@2.0.5", "@nodelib/fs.stat@^2.0.2": + version "2.0.5" + resolved "https://registry.yarnpkg.com/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz#5bd262af94e9d25bd1e71b05deed44876a222e8b" + integrity sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A== + +"@nodelib/fs.walk@^1.2.3": + version "1.2.8" + resolved "https://registry.yarnpkg.com/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz#e95737e8bb6746ddedf69c556953494f196fe69a" + integrity sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg== + dependencies: + "@nodelib/fs.scandir" "2.1.5" + fastq "^1.6.0" + +"@typescript-eslint/types@5.62.0": + version "5.62.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.62.0.tgz#258607e60effa309f067608931c3df6fed41fd2f" + integrity sha512-87NVngcbVXUahrRTqIK27gD2t5Cu1yuCXxbLcFtCzZGlfyVWWh8mLHkoxzjsB6DDNnvdL+fW8MiwPEJyGJQDgQ== + +"@typescript-eslint/typescript-estree@^5.59.5": + version "5.62.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-5.62.0.tgz#7d17794b77fabcac615d6a48fb143330d962eb9b" + integrity sha512-CmcQ6uY7b9y694lKdRB8FEel7JbU/40iSAPomu++SjLMntB+2Leay2LO6i8VnJk58MtE9/nQSFIH6jpyRWyYzA== + dependencies: + "@typescript-eslint/types" "5.62.0" + "@typescript-eslint/visitor-keys" "5.62.0" + debug "^4.3.4" + globby "^11.1.0" + is-glob "^4.0.3" + semver "^7.3.7" + tsutils "^3.21.0" + +"@typescript-eslint/visitor-keys@5.62.0": + version "5.62.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-5.62.0.tgz#2174011917ce582875954ffe2f6912d5931e353e" + integrity sha512-07ny+LHRzQXepkGg6w0mFY41fVUNBrL2Roj/++7V1txKugfjm/Ci/qSND03r2RhlJhJYMcTn9AhhSSqQp0Ysyw== + dependencies: + "@typescript-eslint/types" "5.62.0" + eslint-visitor-keys "^3.3.0" + +app-module-path@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/app-module-path/-/app-module-path-2.2.0.tgz#641aa55dfb7d6a6f0a8141c4b9c0aa50b6c24dd5" + integrity sha512-gkco+qxENJV+8vFcDiiFhuoSvRXb2a/QPqpSoWhVz829VNJfOTnELbBmPmNKFxf3xdNnw4DWCkzkDaavcX/1YQ== + +array-union@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/array-union/-/array-union-2.1.0.tgz#b798420adbeb1de828d84acd8a2e23d3efe85e8d" + integrity sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw== + +ast-module-types@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/ast-module-types/-/ast-module-types-5.0.0.tgz#32b2b05c56067ff38e95df66f11d6afd6c9ba16b" + integrity sha512-JvqziE0Wc0rXQfma0HZC/aY7URXHFuZV84fJRtP8u+lhp0JYCNd5wJzVXP45t0PH0Mej3ynlzvdyITYIu0G4LQ== + +balanced-match@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" + integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== + +brace-expansion@^1.1.7: + version "1.1.11" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" + integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA== + dependencies: + balanced-match "^1.0.0" + concat-map "0.0.1" + +braces@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.3.tgz#490332f40919452272d55a8480adc0c441358789" + integrity sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA== + dependencies: + fill-range "^7.1.1" + +color-name@^1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" + integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== + +commander@^10.0.1: + version "10.0.1" + resolved "https://registry.yarnpkg.com/commander/-/commander-10.0.1.tgz#881ee46b4f77d1c1dccc5823433aa39b022cbe06" + integrity sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug== + +concat-map@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" + integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg== + +debug@^4.3.4: + version "4.3.4" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865" + integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ== + dependencies: + ms "2.1.2" + +dependency-tree@10.0.9: + version "10.0.9" + resolved "https://registry.yarnpkg.com/dependency-tree/-/dependency-tree-10.0.9.tgz#0c6c0dbeb0c5ec2cf83bf755f30e9cb12e7b4ac7" + integrity sha512-dwc59FRIsht+HfnTVM0BCjJaEWxdq2YAvEDy4/Hn6CwS3CBWMtFnL3aZGAkQn3XCYxk/YcTDE4jX2Q7bFTwCjA== + dependencies: + commander "^10.0.1" + filing-cabinet "^4.1.6" + precinct "^11.0.5" + typescript "^5.0.4" + +detective-amd@^5.0.2: + version "5.0.2" + resolved "https://registry.yarnpkg.com/detective-amd/-/detective-amd-5.0.2.tgz#579900f301c160efe037a6377ec7e937434b2793" + integrity sha512-XFd/VEQ76HSpym80zxM68ieB77unNuoMwopU2TFT/ErUk5n4KvUTwW4beafAVUugrjV48l4BmmR0rh2MglBaiA== + dependencies: + ast-module-types "^5.0.0" + escodegen "^2.0.0" + get-amd-module-type "^5.0.1" + node-source-walk "^6.0.1" + +detective-cjs@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/detective-cjs/-/detective-cjs-5.0.1.tgz#836ad51c6de4863efc7c419ec243694f760ff8b2" + integrity sha512-6nTvAZtpomyz/2pmEmGX1sXNjaqgMplhQkskq2MLrar0ZAIkHMrDhLXkRiK2mvbu9wSWr0V5/IfiTrZqAQMrmQ== + dependencies: + ast-module-types "^5.0.0" + node-source-walk "^6.0.0" + +detective-es6@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/detective-es6/-/detective-es6-4.0.1.tgz#38d5d49a6d966e992ef8f2d9bffcfe861a58a88a" + integrity sha512-k3Z5tB4LQ8UVHkuMrFOlvb3GgFWdJ9NqAa2YLUU/jTaWJIm+JJnEh4PsMc+6dfT223Y8ACKOaC0qcj7diIhBKw== + dependencies: + node-source-walk "^6.0.1" + +detective-postcss@^6.1.3: + version "6.1.3" + resolved "https://registry.yarnpkg.com/detective-postcss/-/detective-postcss-6.1.3.tgz#51a2d4419327ad85d0af071c7054c79fafca7e73" + integrity sha512-7BRVvE5pPEvk2ukUWNQ+H2XOq43xENWbH0LcdCE14mwgTBEAMoAx+Fc1rdp76SmyZ4Sp48HlV7VedUnP6GA1Tw== + dependencies: + is-url "^1.2.4" + postcss "^8.4.23" + postcss-values-parser "^6.0.2" + +detective-sass@^5.0.3: + version "5.0.3" + resolved "https://registry.yarnpkg.com/detective-sass/-/detective-sass-5.0.3.tgz#63e54bc9b32f4bdbd9d5002308f9592a3d3a508f" + integrity sha512-YsYT2WuA8YIafp2RVF5CEfGhhyIVdPzlwQgxSjK+TUm3JoHP+Tcorbk3SfG0cNZ7D7+cYWa0ZBcvOaR0O8+LlA== + dependencies: + gonzales-pe "^4.3.0" + node-source-walk "^6.0.1" + +detective-scss@^4.0.3: + version "4.0.3" + resolved "https://registry.yarnpkg.com/detective-scss/-/detective-scss-4.0.3.tgz#79758baa0158f72bfc4481eb7e21cc3b5f1ea6eb" + integrity sha512-VYI6cHcD0fLokwqqPFFtDQhhSnlFWvU614J42eY6G0s8c+MBhi9QAWycLwIOGxlmD8I/XvGSOUV1kIDhJ70ZPg== + dependencies: + gonzales-pe "^4.3.0" + node-source-walk "^6.0.1" + +detective-stylus@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/detective-stylus/-/detective-stylus-4.0.0.tgz#ce97b6499becdc291de7b3c11df8c352c1eee46e" + integrity sha512-TfPotjhszKLgFBzBhTOxNHDsutIxx9GTWjrL5Wh7Qx/ydxKhwUrlSFeLIn+ZaHPF+h0siVBkAQSuy6CADyTxgQ== + +detective-typescript@^11.1.0: + version "11.1.0" + resolved "https://registry.yarnpkg.com/detective-typescript/-/detective-typescript-11.1.0.tgz#2deea5364cae1f0d9d3688bc596e662b049438cc" + integrity sha512-Mq8egjnW2NSCkzEb/Az15/JnBI/Ryyl6Po0Y+0mABTFvOS6DAyUGRZqz1nyhu4QJmWWe0zaGs/ITIBeWkvCkGw== + dependencies: + "@typescript-eslint/typescript-estree" "^5.59.5" + ast-module-types "^5.0.0" + node-source-walk "^6.0.1" + typescript "^5.0.4" + +dir-glob@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/dir-glob/-/dir-glob-3.0.1.tgz#56dbf73d992a4a93ba1584f4534063fd2e41717f" + integrity sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA== + dependencies: + path-type "^4.0.0" + +enhanced-resolve@^5.14.1: + version "5.15.0" + resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-5.15.0.tgz#1af946c7d93603eb88e9896cee4904dc012e9c35" + integrity sha512-LXYT42KJ7lpIKECr2mAXIaMldcNCh/7E0KBKOu4KSfkHmP+mZmSs+8V5gBAqisWBy0OO4W5Oyys0GO1Y8KtdKg== + dependencies: + graceful-fs "^4.2.4" + tapable "^2.2.0" + +escodegen@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/escodegen/-/escodegen-2.1.0.tgz#ba93bbb7a43986d29d6041f99f5262da773e2e17" + integrity sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w== + dependencies: + esprima "^4.0.1" + estraverse "^5.2.0" + esutils "^2.0.2" + optionalDependencies: + source-map "~0.6.1" + +eslint-visitor-keys@^3.3.0: + version "3.4.3" + 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== + +esprima@^4.0.0, esprima@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71" + integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A== + +estraverse@^5.2.0: + version "5.3.0" + resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-5.3.0.tgz#2eea5290702f26ab8fe5370370ff86c965d21123" + integrity sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA== + +esutils@^2.0.2: + version "2.0.3" + resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64" + integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g== + +fast-glob@^3.2.9: + version "3.3.2" + resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.3.2.tgz#a904501e57cfdd2ffcded45e99a54fef55e46129" + integrity sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow== + dependencies: + "@nodelib/fs.stat" "^2.0.2" + "@nodelib/fs.walk" "^1.2.3" + glob-parent "^5.1.2" + merge2 "^1.3.0" + micromatch "^4.0.4" + +fastq@^1.6.0: + version "1.17.0" + resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.17.0.tgz#ca5e1a90b5e68f97fc8b61330d5819b82f5fab03" + integrity sha512-zGygtijUMT7jnk3h26kUms3BkSDp4IfIKjmnqI2tvx6nuBfiF1UqOxbnLfzdv+apBy+53oaImsKtMw/xYbW+1w== + dependencies: + reusify "^1.0.4" + +filing-cabinet@^4.1.6: + version "4.1.6" + resolved "https://registry.yarnpkg.com/filing-cabinet/-/filing-cabinet-4.1.6.tgz#8d6d12cf3a84365bbd94e1cbf07d71c113420dd2" + integrity sha512-C+HZbuQTER36sKzGtUhrAPAoK6+/PrrUhYDBQEh3kBRdsyEhkLbp1ML8S0+6e6gCUrUlid+XmubxJrhvL2g/Zw== + dependencies: + app-module-path "^2.2.0" + commander "^10.0.1" + enhanced-resolve "^5.14.1" + is-relative-path "^1.0.2" + module-definition "^5.0.1" + module-lookup-amd "^8.0.5" + resolve "^1.22.3" + resolve-dependency-path "^3.0.2" + sass-lookup "^5.0.1" + stylus-lookup "^5.0.1" + tsconfig-paths "^4.2.0" + typescript "^5.0.4" + +fill-range@^7.1.1: + version "7.1.1" + resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.1.1.tgz#44265d3cac07e3ea7dc247516380643754a05292" + integrity sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg== + dependencies: + to-regex-range "^5.0.1" + +fs.realpath@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" + integrity sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw== + +function-bind@^1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.2.tgz#2c02d864d97f3ea6c8830c464cbd11ab6eab7a1c" + integrity sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA== + +get-amd-module-type@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/get-amd-module-type/-/get-amd-module-type-5.0.1.tgz#bef38ea3674e1aa1bda9c59c8b0da598582f73f2" + integrity sha512-jb65zDeHyDjFR1loOVk0HQGM5WNwoGB8aLWy3LKCieMKol0/ProHkhO2X1JxojuN10vbz1qNn09MJ7tNp7qMzw== + dependencies: + ast-module-types "^5.0.0" + node-source-walk "^6.0.1" + +get-own-enumerable-property-symbols@^3.0.0: + version "3.0.2" + resolved "https://registry.yarnpkg.com/get-own-enumerable-property-symbols/-/get-own-enumerable-property-symbols-3.0.2.tgz#b5fde77f22cbe35f390b4e089922c50bce6ef664" + integrity sha512-I0UBV/XOz1XkIJHEUDMZAbzCThU/H8DxmSfmdGcKPnVhu2VfFqr34jr9777IyaTYvxjedWhqVIilEDsCdP5G6g== + +glob-parent@^5.1.2: + version "5.1.2" + resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4" + integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow== + dependencies: + is-glob "^4.0.1" + +glob@^7.2.3: + version "7.2.3" + resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.3.tgz#b8df0fb802bbfa8e89bd1d938b4e16578ed44f2b" + integrity sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q== + dependencies: + fs.realpath "^1.0.0" + inflight "^1.0.4" + inherits "2" + minimatch "^3.1.1" + once "^1.3.0" + path-is-absolute "^1.0.0" + +globby@^11.1.0: + version "11.1.0" + resolved "https://registry.yarnpkg.com/globby/-/globby-11.1.0.tgz#bd4be98bb042f83d796f7e3811991fbe82a0d34b" + integrity sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g== + dependencies: + array-union "^2.1.0" + dir-glob "^3.0.1" + fast-glob "^3.2.9" + ignore "^5.2.0" + merge2 "^1.4.1" + slash "^3.0.0" + +gonzales-pe@^4.3.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/gonzales-pe/-/gonzales-pe-4.3.0.tgz#fe9dec5f3c557eead09ff868c65826be54d067b3" + integrity sha512-otgSPpUmdWJ43VXyiNgEYE4luzHCL2pz4wQ0OnDluC6Eg4Ko3Vexy/SrSynglw/eR+OhkzmqFCZa/OFa/RgAOQ== + dependencies: + minimist "^1.2.5" + +graceful-fs@^4.2.4: + 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== + +hasown@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/hasown/-/hasown-2.0.0.tgz#f4c513d454a57b7c7e1650778de226b11700546c" + integrity sha512-vUptKVTpIJhcczKBbgnS+RtcuYMB8+oNzPK2/Hp3hanz8JmpATdmmgLgSaadVREkDm+e2giHwY3ZRkyjSIDDFA== + dependencies: + function-bind "^1.1.2" + +ignore@^5.2.0: + version "5.3.1" + resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.3.1.tgz#5073e554cd42c5b33b394375f538b8593e34d4ef" + integrity sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw== + +inflight@^1.0.4: + version "1.0.6" + resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" + integrity sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA== + dependencies: + once "^1.3.0" + wrappy "1" + +inherits@2: + version "2.0.4" + resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" + integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== + +is-core-module@^2.13.0: + version "2.13.1" + resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.13.1.tgz#ad0d7532c6fea9da1ebdc82742d74525c6273384" + integrity sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw== + dependencies: + hasown "^2.0.0" + +is-extglob@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" + integrity sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ== + +is-glob@^4.0.1, is-glob@^4.0.3: + version "4.0.3" + resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084" + integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg== + dependencies: + is-extglob "^2.1.1" + +is-number@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b" + integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== + +is-obj@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/is-obj/-/is-obj-1.0.1.tgz#3e4729ac1f5fde025cd7d83a896dab9f4f67db0f" + integrity sha512-l4RyHgRqGN4Y3+9JHVrNqO+tN0rV5My76uW5/nuO4K1b6vw5G8d/cmFjP9tRfEsdhZNt0IFdZuK/c2Vr4Nb+Qg== + +is-regexp@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-regexp/-/is-regexp-1.0.0.tgz#fd2d883545c46bac5a633e7b9a09e87fa2cb5069" + integrity sha512-7zjFAPO4/gwyQAAgRRmqeEeyIICSdmCqa3tsVHMdBzaXXRiqopZL4Cyghg/XulGWrtABTpbnYYzzIRffLkP4oA== + +is-relative-path@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/is-relative-path/-/is-relative-path-1.0.2.tgz#091b46a0d67c1ed0fe85f1f8cfdde006bb251d46" + integrity sha512-i1h+y50g+0hRbBD+dbnInl3JlJ702aar58snAeX+MxBAPvzXGej7sYoPMhlnykabt0ZzCJNBEyzMlekuQZN7fA== + +is-url-superb@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/is-url-superb/-/is-url-superb-4.0.0.tgz#b54d1d2499bb16792748ac967aa3ecb41a33a8c2" + integrity sha512-GI+WjezhPPcbM+tqE9LnmsY5qqjwHzTvjJ36wxYX5ujNXefSUJ/T17r5bqDV8yLhcgB59KTPNOc9O9cmHTPWsA== + +is-url@^1.2.4: + version "1.2.4" + resolved "https://registry.yarnpkg.com/is-url/-/is-url-1.2.4.tgz#04a4df46d28c4cff3d73d01ff06abeb318a1aa52" + integrity sha512-ITvGim8FhRiYe4IQ5uHSkj7pVaPDrCTkNd3yq3cV7iZAcJdHTUMPMEHcqSOy9xZ9qFenQCvi+2wjH9a1nXqHww== + +json5@^2.2.2: + version "2.2.3" + resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.3.tgz#78cd6f1a19bdc12b73db5ad0c61efd66c1e29283" + integrity sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg== + +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" + +merge2@^1.3.0, merge2@^1.4.1: + version "1.4.1" + resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae" + integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg== + +micromatch@^4.0.4: + version "4.0.8" + resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.8.tgz#d66fa18f3a47076789320b9b1af32bd86d9fa202" + integrity sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA== + dependencies: + braces "^3.0.3" + picomatch "^2.3.1" + +minimatch@^3.1.1: + 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" + +minimist@^1.2.5, minimist@^1.2.6: + version "1.2.8" + resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.8.tgz#c1a464e7693302e082a075cee0c057741ac4772c" + integrity sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA== + +module-definition@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/module-definition/-/module-definition-5.0.1.tgz#62d1194e5d5ea6176b7dc7730f818f466aefa32f" + integrity sha512-kvw3B4G19IXk+BOXnYq/D/VeO9qfHaapMeuS7w7sNUqmGaA6hywdFHMi+VWeR9wUScXM7XjoryTffCZ5B0/8IA== + dependencies: + ast-module-types "^5.0.0" + node-source-walk "^6.0.1" + +module-lookup-amd@^8.0.5: + version "8.0.5" + resolved "https://registry.yarnpkg.com/module-lookup-amd/-/module-lookup-amd-8.0.5.tgz#aaeea41979105b49339380ca3f7d573db78c32a5" + integrity sha512-vc3rYLjDo5Frjox8NZpiyLXsNWJ5BWshztc/5KSOMzpg9k5cHH652YsJ7VKKmtM4SvaxuE9RkrYGhiSjH3Ehow== + dependencies: + commander "^10.0.1" + glob "^7.2.3" + requirejs "^2.3.6" + requirejs-config-file "^4.0.0" + +ms@2.1.2: + version "2.1.2" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" + integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== + +nanoid@^3.3.7: + version "3.3.7" + resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.7.tgz#d0c301a691bc8d54efa0a2226ccf3fe2fd656bd8" + integrity sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g== + +node-source-walk@^6.0.0, node-source-walk@^6.0.1, node-source-walk@^6.0.2: + version "6.0.2" + resolved "https://registry.yarnpkg.com/node-source-walk/-/node-source-walk-6.0.2.tgz#ba81bc4bc0f6f05559b084bea10be84c3f87f211" + integrity sha512-jn9vOIK/nfqoFCcpK89/VCVaLg1IHE6UVfDOzvqmANaJ/rWCTEdH8RZ1V278nv2jr36BJdyQXIAavBLXpzdlag== + dependencies: + "@babel/parser" "^7.21.8" + +once@^1.3.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" + +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" + integrity sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg== + +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== + +path-type@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/path-type/-/path-type-4.0.0.tgz#84ed01c0a7ba380afe09d90a8c180dcd9d03043b" + integrity sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw== + +picocolors@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.0.0.tgz#cb5bdc74ff3f51892236eaf79d68bc44564ab81c" + integrity sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ== + +picomatch@^2.3.1: + version "2.3.1" + resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42" + integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== + +postcss-values-parser@^6.0.2: + version "6.0.2" + resolved "https://registry.yarnpkg.com/postcss-values-parser/-/postcss-values-parser-6.0.2.tgz#636edc5b86c953896f1bb0d7a7a6615df00fb76f" + integrity sha512-YLJpK0N1brcNJrs9WatuJFtHaV9q5aAOj+S4DI5S7jgHlRfm0PIbDCAFRYMQD5SHq7Fy6xsDhyutgS0QOAs0qw== + dependencies: + color-name "^1.1.4" + is-url-superb "^4.0.0" + quote-unquote "^1.0.0" + +postcss@^8.4.23: + version "8.4.33" + resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.33.tgz#1378e859c9f69bf6f638b990a0212f43e2aaa742" + integrity sha512-Kkpbhhdjw2qQs2O2DGX+8m5OVqEcbB9HRBvuYM9pgrjEFUg30A9LmXNlTAUj4S9kgtGyrMbTzVjH7E+s5Re2yg== + dependencies: + nanoid "^3.3.7" + picocolors "^1.0.0" + source-map-js "^1.0.2" + +precinct@^11.0.5: + version "11.0.5" + resolved "https://registry.yarnpkg.com/precinct/-/precinct-11.0.5.tgz#3e15b3486670806f18addb54b8533e23596399ff" + integrity sha512-oHSWLC8cL/0znFhvln26D14KfCQFFn4KOLSw6hmLhd+LQ2SKt9Ljm89but76Pc7flM9Ty1TnXyrA2u16MfRV3w== + dependencies: + "@dependents/detective-less" "^4.1.0" + commander "^10.0.1" + detective-amd "^5.0.2" + detective-cjs "^5.0.1" + detective-es6 "^4.0.1" + detective-postcss "^6.1.3" + detective-sass "^5.0.3" + detective-scss "^4.0.3" + detective-stylus "^4.0.0" + detective-typescript "^11.1.0" + module-definition "^5.0.1" + node-source-walk "^6.0.2" + +queue-microtask@^1.2.2: + version "1.2.3" + resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243" + integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A== + +quote-unquote@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/quote-unquote/-/quote-unquote-1.0.0.tgz#67a9a77148effeaf81a4d428404a710baaac8a0b" + integrity sha512-twwRO/ilhlG/FIgYeKGFqyHhoEhqgnKVkcmqMKi2r524gz3ZbDTcyFt38E9xjJI2vT+KbRNHVbnJ/e0I25Azwg== + +requirejs-config-file@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/requirejs-config-file/-/requirejs-config-file-4.0.0.tgz#4244da5dd1f59874038cc1091d078d620abb6ebc" + integrity sha512-jnIre8cbWOyvr8a5F2KuqBnY+SDA4NXr/hzEZJG79Mxm2WiFQz2dzhC8ibtPJS7zkmBEl1mxSwp5HhC1W4qpxw== + dependencies: + esprima "^4.0.0" + stringify-object "^3.2.1" + +requirejs@^2.3.6: + version "2.3.7" + resolved "https://registry.yarnpkg.com/requirejs/-/requirejs-2.3.7.tgz#0b22032e51a967900e0ae9f32762c23a87036bd0" + integrity "sha1-CyIDLlGpZ5AOCunzJ2LCOocDa9A= sha512-DouTG8T1WanGok6Qjg2SXuCMzszOo0eHeH9hDZ5Y4x8Je+9JB38HdTLT4/VA8OaUhBa0JPVHJ0pyBkM1z+pDsw==" + +resolve-dependency-path@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/resolve-dependency-path/-/resolve-dependency-path-3.0.2.tgz#012816717bcbe8b846835da11af9d2beb5acef50" + integrity sha512-Tz7zfjhLfsvR39ADOSk9us4421J/1ztVBo4rWUkF38hgHK5m0OCZ3NxFVpqHRkjctnwVa15igEUHFJp8MCS7vA== + +resolve@^1.22.3: + version "1.22.8" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.8.tgz#b6c87a9f2aa06dfab52e3d70ac8cde321fa5a48d" + integrity sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw== + dependencies: + is-core-module "^2.13.0" + path-parse "^1.0.7" + supports-preserve-symlinks-flag "^1.0.0" + +reusify@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.0.4.tgz#90da382b1e126efc02146e90845a88db12925d76" + integrity sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw== + +run-parallel@^1.1.9: + version "1.2.0" + resolved "https://registry.yarnpkg.com/run-parallel/-/run-parallel-1.2.0.tgz#66d1368da7bdf921eb9d95bd1a9229e7f21a43ee" + integrity sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA== + dependencies: + queue-microtask "^1.2.2" + +sass-lookup@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/sass-lookup/-/sass-lookup-5.0.1.tgz#1f01d7ff21e09d8c9dcf8d05b3fca28f2f96e6ed" + integrity sha512-t0X5PaizPc2H4+rCwszAqHZRtr4bugo4pgiCvrBFvIX0XFxnr29g77LJcpyj9A0DcKf7gXMLcgvRjsonYI6x4g== + dependencies: + commander "^10.0.1" + +semver@^7.3.7: + version "7.5.4" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.5.4.tgz#483986ec4ed38e1c6c48c34894a9182dbff68a6e" + integrity sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA== + dependencies: + lru-cache "^6.0.0" + +slash@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634" + integrity sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q== + +source-map-js@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.0.2.tgz#adbc361d9c62df380125e7f161f71c826f1e490c" + integrity sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw== + +source-map@~0.6.1: + version "0.6.1" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" + integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== + +stringify-object@^3.2.1: + version "3.3.0" + resolved "https://registry.yarnpkg.com/stringify-object/-/stringify-object-3.3.0.tgz#703065aefca19300d3ce88af4f5b3956d7556629" + integrity sha512-rHqiFh1elqCQ9WPLIC8I0Q/g/wj5J1eMkyoiD6eoQApWHP0FtlK7rqnhmabL5VUY9JQCcqwwvlOaSuutekgyrw== + dependencies: + get-own-enumerable-property-symbols "^3.0.0" + is-obj "^1.0.1" + is-regexp "^1.0.0" + +strip-bom@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-3.0.0.tgz#2334c18e9c759f7bdd56fdef7e9ae3d588e68ed3" + integrity sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA== + +stylus-lookup@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/stylus-lookup/-/stylus-lookup-5.0.1.tgz#3c4d116c3b1e8e1a8169c0d9cd20e608595560f4" + integrity sha512-tLtJEd5AGvnVy4f9UHQMw4bkJJtaAcmo54N+ovQBjDY3DuWyK9Eltxzr5+KG0q4ew6v2EHyuWWNnHeiw/Eo7rQ== + dependencies: + commander "^10.0.1" + +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== + +tapable@^2.2.0: + version "2.2.1" + resolved "https://registry.yarnpkg.com/tapable/-/tapable-2.2.1.tgz#1967a73ef4060a82f12ab96af86d52fdb76eeca0" + integrity sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ== + +to-regex-range@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4" + integrity sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ== + dependencies: + is-number "^7.0.0" + +tsconfig-paths@^4.2.0: + 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== + dependencies: + json5 "^2.2.2" + minimist "^1.2.6" + strip-bom "^3.0.0" + +tslib@^1.8.1: + version "1.14.1" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00" + integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg== + +tsutils@^3.21.0: + version "3.21.0" + resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-3.21.0.tgz#b48717d394cea6c1e096983eed58e9d61715b623" + integrity sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA== + dependencies: + tslib "^1.8.1" + +typescript@^5.0.4: + version "5.3.3" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.3.3.tgz#b3ce6ba258e72e6305ba66f5c9b452aaee3ffe37" + integrity sha512-pXWcraxM0uxAS+tN0AG/BF2TyqmHO014Z070UsJ+pFvYuRSq8KH8DmWpnbXe0pEPDHXZV3FcAbJkijJ5oNEnWw== + +wrappy@1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" + integrity sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ== + +yallist@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72" + integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A== diff --git a/deploy/tools/envs-validator/index.ts b/deploy/tools/envs-validator/index.ts index a7ef2b6673..69e0c3c734 100644 --- a/deploy/tools/envs-validator/index.ts +++ b/deploy/tools/envs-validator/index.ts @@ -20,7 +20,9 @@ async function run() { return result; }, {} as Record); + printDeprecationWarning(appEnvs); await checkPlaceholdersCongruity(appEnvs); + checkDeprecatedEnvs(appEnvs); await validateEnvs(appEnvs); } catch (error) { @@ -37,11 +39,15 @@ async function validateEnvs(appEnvs: Record) { 'NEXT_PUBLIC_FEATURED_NETWORKS', 'NEXT_PUBLIC_MARKETPLACE_CONFIG_URL', 'NEXT_PUBLIC_MARKETPLACE_CATEGORIES_URL', + 'NEXT_PUBLIC_MARKETPLACE_SECURITY_REPORTS_URL', + 'NEXT_PUBLIC_MARKETPLACE_GRAPH_LINKS_URL', 'NEXT_PUBLIC_FOOTER_LINKS', ]; for await (const envName of envsWithJsonConfig) { - appEnvs[envName] = await(appEnvs[envName] ? getExternalJsonContent(envName) : Promise.resolve()) || '[]'; + if (appEnvs[envName]) { + appEnvs[envName] = await getExternalJsonContent(envName) || '[]'; + } } await schema.validate(appEnvs, { stripUnknown: false, abortEarly: false }); @@ -132,3 +138,64 @@ function getEnvsPlaceholders(filePath: string): Promise> { }); }); } + +function printDeprecationWarning(envsMap: Record) { + if (envsMap.NEXT_PUBLIC_RE_CAPTCHA_APP_SITE_KEY && envsMap.NEXT_PUBLIC_RE_CAPTCHA_V3_APP_SITE_KEY) { + console.log('❗❗❗❗❗❗❗❗❗❗❗❗❗❗❗❗❗❗❗❗'); + // eslint-disable-next-line max-len + console.warn('The NEXT_PUBLIC_RE_CAPTCHA_V3_APP_SITE_KEY variable is now deprecated and will be removed in the next release. Please migrate to the NEXT_PUBLIC_RE_CAPTCHA_APP_SITE_KEY variable.'); + console.log('❗❗❗❗❗❗❗❗❗❗❗❗❗❗❗❗❗❗❗❗\n'); + } + + if ( + (envsMap.NEXT_PUBLIC_SENTRY_DSN || envsMap.SENTRY_CSP_REPORT_URI || envsMap.NEXT_PUBLIC_SENTRY_ENABLE_TRACING) && + envsMap.NEXT_PUBLIC_ROLLBAR_CLIENT_TOKEN + ) { + console.log('❗❗❗❗❗❗❗❗❗❗❗❗❗❗❗❗❗❗❗❗'); + // eslint-disable-next-line max-len + console.warn('The Sentry monitoring is now deprecated and will be removed in the next release. Please migrate to the Rollbar error monitoring.'); + console.log('❗❗❗❗❗❗❗❗❗❗❗❗❗❗❗❗❗❗❗❗\n'); + } + + if ( + envsMap.NEXT_PUBLIC_HOMEPAGE_PLATE_TEXT_COLOR || + envsMap.NEXT_PUBLIC_HOMEPAGE_PLATE_BACKGROUND + ) { + console.log('❗❗❗❗❗❗❗❗❗❗❗❗❗❗❗❗❗❗❗❗'); + // eslint-disable-next-line max-len + console.warn('The NEXT_PUBLIC_HOMEPAGE_PLATE_TEXT_COLOR and NEXT_PUBLIC_HOMEPAGE_PLATE_BACKGROUND variables are now deprecated and will be removed in the next release. Please migrate to the NEXT_PUBLIC_HOMEPAGE_HERO_BANNER_CONFIG variable.'); + console.log('❗❗❗❗❗❗❗❗❗❗❗❗❗❗❗❗❗❗❗❗\n'); + } + + if ( + envsMap.NEXT_PUBLIC_AUTH0_CLIENT_ID || + envsMap.NEXT_PUBLIC_AUTH_URL || + envsMap.NEXT_PUBLIC_LOGOUT_URL + ) { + console.log('❗❗❗❗❗❗❗❗❗❗❗❗❗❗❗❗❗❗❗❗'); + // eslint-disable-next-line max-len + console.warn('The NEXT_PUBLIC_AUTH0_CLIENT_ID, NEXT_PUBLIC_AUTH_URL and NEXT_PUBLIC_LOGOUT_URL variables are now deprecated and will be removed in the next release.'); + console.log('❗❗❗❗❗❗❗❗❗❗❗❗❗❗❗❗❗❗❗❗\n'); + } +} + +function checkDeprecatedEnvs(envsMap: Record) { + !silent && console.log(`🌀 Checking deprecated environment variables...`); + + if (!envsMap.NEXT_PUBLIC_RE_CAPTCHA_APP_SITE_KEY && envsMap.NEXT_PUBLIC_RE_CAPTCHA_V3_APP_SITE_KEY) { + // eslint-disable-next-line max-len + console.log('🚨 The NEXT_PUBLIC_RE_CAPTCHA_V3_APP_SITE_KEY variable is no longer supported. Please pass NEXT_PUBLIC_RE_CAPTCHA_APP_SITE_KEY or remove it completely.'); + throw new Error(); + } + + if ( + (envsMap.NEXT_PUBLIC_SENTRY_DSN || envsMap.SENTRY_CSP_REPORT_URI || envsMap.NEXT_PUBLIC_SENTRY_ENABLE_TRACING) && + !envsMap.NEXT_PUBLIC_ROLLBAR_CLIENT_TOKEN + ) { + // eslint-disable-next-line max-len + console.log('🚨 The Sentry error monitoring is no longer supported. Please migrate to the Rollbar error monitoring.'); + throw new Error(); + } + + !silent && console.log('👍 All good!\n'); +} diff --git a/deploy/tools/envs-validator/schema.ts b/deploy/tools/envs-validator/schema.ts index 9d5ef4d354..612a5333c2 100644 --- a/deploy/tools/envs-validator/schema.ts +++ b/deploy/tools/envs-validator/schema.ts @@ -1,3 +1,4 @@ +/* eslint-disable max-len */ declare module 'yup' { interface StringSchema { // Yup's URL validator is not perfect so we made our own @@ -9,29 +10,43 @@ declare module 'yup' { import * as yup from 'yup'; import type { AdButlerConfig } from '../../../types/client/adButlerConfig'; -import { SUPPORTED_AD_TEXT_PROVIDERS, SUPPORTED_AD_BANNER_PROVIDERS } from '../../../types/client/adProviders'; -import type { AdTextProviders, AdBannerProviders } from '../../../types/client/adProviders'; -import type { ContractCodeIde } from '../../../types/client/contract'; -import type { MarketplaceAppOverview } from '../../../types/client/marketplace'; -import { NAVIGATION_LINK_IDS } from '../../../types/client/navigation-items'; -import type { NavItemExternal, NavigationLinkId } from '../../../types/client/navigation-items'; +import type { AddressProfileAPIConfig } from '../../../types/client/addressProfileAPIConfig'; +import { SUPPORTED_AD_TEXT_PROVIDERS, SUPPORTED_AD_BANNER_PROVIDERS, SUPPORTED_AD_BANNER_ADDITIONAL_PROVIDERS } from '../../../types/client/adProviders'; +import type { AdTextProviders, AdBannerProviders, AdBannerAdditionalProviders } from '../../../types/client/adProviders'; +import { SMART_CONTRACT_EXTRA_VERIFICATION_METHODS, SMART_CONTRACT_LANGUAGE_FILTERS, type ContractCodeIde, type SmartContractVerificationMethodExtra } from '../../../types/client/contract'; +import type { DeFiDropdownItem } from '../../../types/client/deFiDropdown'; +import type { GasRefuelProviderConfig } from '../../../types/client/gasRefuelProviderConfig'; +import { GAS_UNITS } from '../../../types/client/gasTracker'; +import type { GasUnit } from '../../../types/client/gasTracker'; +import type { MarketplaceAppOverview, MarketplaceAppSecurityReportRaw, MarketplaceAppSecurityReport } from '../../../types/client/marketplace'; +import type { MultichainProviderConfig } from '../../../types/client/multichainProviderConfig'; +import { NAVIGATION_LINK_IDS } from '../../../types/client/navigation'; +import type { NavItemExternal, NavigationLinkId, NavigationLayout } from '../../../types/client/navigation'; +import { ROLLUP_TYPES } from '../../../types/client/rollup'; import type { BridgedTokenChain, TokenBridge } from '../../../types/client/token'; import { PROVIDERS as TX_INTERPRETATION_PROVIDERS } from '../../../types/client/txInterpretation'; +import { VALIDATORS_CHAIN_TYPE } from '../../../types/client/validators'; +import type { ValidatorsChainType } from '../../../types/client/validators'; import type { WalletType } from '../../../types/client/wallets'; import { SUPPORTED_WALLETS } from '../../../types/client/wallets'; import type { CustomLink, CustomLinksGroup } from '../../../types/footerLinks'; -import type { ChainIndicatorId } from '../../../types/homepage'; -import { type NetworkVerificationType, type NetworkExplorer, type FeaturedNetwork, NETWORK_GROUPS } from '../../../types/networks'; -import type { AddressViewId } from '../../../types/views/address'; -import { ADDRESS_VIEWS_IDS, IDENTICON_TYPES } from '../../../types/views/address'; +import { CHAIN_INDICATOR_IDS, HOME_STATS_WIDGET_IDS } from '../../../types/homepage'; +import type { ChainIndicatorId, HeroBannerButtonState, HeroBannerConfig, HomeStatsWidgetId } from '../../../types/homepage'; +import { type NetworkVerificationTypeEnvs, type NetworkExplorer, type FeaturedNetwork, NETWORK_GROUPS } from '../../../types/networks'; +import { COLOR_THEME_IDS } from '../../../types/settings'; +import type { FontFamily } from '../../../types/ui'; +import type { AddressFormat, AddressViewId } from '../../../types/views/address'; +import { ADDRESS_FORMATS, ADDRESS_VIEWS_IDS, IDENTICON_TYPES } from '../../../types/views/address'; import { BLOCK_FIELDS_IDS } from '../../../types/views/block'; import type { BlockFieldId } from '../../../types/views/block'; import type { NftMarketplaceItem } from '../../../types/views/nft'; import type { TxAdditionalFieldsId, TxFieldsId } from '../../../types/views/tx'; import { TX_ADDITIONAL_FIELDS_IDS, TX_FIELDS_IDS } from '../../../types/views/tx'; +import type { VerifiedContractsFilter } from '../../../types/api/contracts'; import { replaceQuotes } from '../../../configs/app/utils'; import * as regexp from '../../../lib/regexp'; +import type { IconName } from '../../../ui/shared/IconSvg'; const protocols = [ 'http', 'https' ]; @@ -70,28 +85,172 @@ const marketplaceAppSchema: yup.ObjectSchema = yup site: yup.string().test(urlTest), twitter: yup.string().test(urlTest), telegram: yup.string().test(urlTest), - github: yup.string().test(urlTest), + github: yup.lazy(value => + Array.isArray(value) ? + yup.array().of(yup.string().required().test(urlTest)) : + yup.string().test(urlTest), + ), + discord: yup.string().test(urlTest), internalWallet: yup.boolean(), priority: yup.number(), }); +const issueSeverityDistributionSchema: yup.ObjectSchema = yup + .object({ + critical: yup.number().required(), + gas: yup.number().required(), + high: yup.number().required(), + informational: yup.number().required(), + low: yup.number().required(), + medium: yup.number().required(), + }); + +const solidityscanReportSchema: yup.ObjectSchema = yup + .object({ + contractname: yup.string().required(), + scan_status: yup.string().required(), + scan_summary: yup + .object({ + issue_severity_distribution: issueSeverityDistributionSchema.required(), + lines_analyzed_count: yup.number().required(), + scan_time_taken: yup.number().required(), + score: yup.string().required(), + score_v2: yup.string().required(), + threat_score: yup.string().required(), + }) + .required(), + scanner_reference_url: yup.string().test(urlTest).required(), + }); + +const contractDataSchema: yup.ObjectSchema = yup + .object({ + address: yup.string().required(), + isVerified: yup.boolean().required(), + solidityScanReport: solidityscanReportSchema.nullable().notRequired(), + }); + +const chainsDataSchema = yup.lazy((objValue) => { + let schema = yup.object(); + Object.keys(objValue).forEach((key) => { + schema = schema.shape({ + [key]: yup.object({ + overallInfo: yup.object({ + verifiedNumber: yup.number().required(), + totalContractsNumber: yup.number().required(), + solidityScanContractsNumber: yup.number().required(), + securityScore: yup.number().required(), + issueSeverityDistribution: issueSeverityDistributionSchema.required(), + }).required(), + contractsData: yup.array().of(contractDataSchema).required(), + }), + }); + }); + return schema; +}); + +const securityReportSchema: yup.ObjectSchema = yup + .object({ + appName: yup.string().required(), + chainsData: chainsDataSchema, + }); + const marketplaceSchema = yup .object() .shape({ + NEXT_PUBLIC_MARKETPLACE_ENABLED: yup.boolean(), NEXT_PUBLIC_MARKETPLACE_CONFIG_URL: yup .array() .json() - .of(marketplaceAppSchema), + .of(marketplaceAppSchema) + .when('NEXT_PUBLIC_MARKETPLACE_ENABLED', { + is: true, + then: (schema) => schema, + // eslint-disable-next-line max-len + otherwise: (schema) => schema.max(-1, 'NEXT_PUBLIC_MARKETPLACE_CONFIG_URL cannot not be used without NEXT_PUBLIC_MARKETPLACE_ENABLED'), + }), NEXT_PUBLIC_MARKETPLACE_CATEGORIES_URL: yup .array() .json() - .of(yup.string()), + .of(yup.string()) + .when('NEXT_PUBLIC_MARKETPLACE_ENABLED', { + is: true, + then: (schema) => schema, + // eslint-disable-next-line max-len + otherwise: (schema) => schema.max(-1, 'NEXT_PUBLIC_MARKETPLACE_CATEGORIES_URL cannot not be used without NEXT_PUBLIC_MARKETPLACE_ENABLED'), + }), NEXT_PUBLIC_MARKETPLACE_SUBMIT_FORM: yup .string() - .when('NEXT_PUBLIC_MARKETPLACE_CONFIG_URL', { - is: (value: Array) => value.length > 0, + .when('NEXT_PUBLIC_MARKETPLACE_ENABLED', { + is: true, then: (schema) => schema.test(urlTest).required(), - otherwise: (schema) => schema.max(-1, 'NEXT_PUBLIC_MARKETPLACE_SUBMIT_FORM cannot not be used without NEXT_PUBLIC_MARKETPLACE_CONFIG_URL'), + // eslint-disable-next-line max-len + otherwise: (schema) => schema.max(-1, 'NEXT_PUBLIC_MARKETPLACE_SUBMIT_FORM cannot not be used without NEXT_PUBLIC_MARKETPLACE_ENABLED'), + }), + NEXT_PUBLIC_MARKETPLACE_SUGGEST_IDEAS_FORM: yup + .string() + .when('NEXT_PUBLIC_MARKETPLACE_ENABLED', { + is: true, + then: (schema) => schema.test(urlTest), + // eslint-disable-next-line max-len + otherwise: (schema) => schema.max(-1, 'NEXT_PUBLIC_MARKETPLACE_SUGGEST_IDEAS_FORM cannot not be used without NEXT_PUBLIC_MARKETPLACE_ENABLED'), + }), + NEXT_PUBLIC_MARKETPLACE_SECURITY_REPORTS_URL: yup + .array() + .json() + .of(securityReportSchema) + .when('NEXT_PUBLIC_MARKETPLACE_ENABLED', { + is: true, + then: (schema) => schema, + // eslint-disable-next-line max-len + otherwise: (schema) => schema.max(-1, 'NEXT_PUBLIC_MARKETPLACE_SECURITY_REPORTS_URL cannot not be used without NEXT_PUBLIC_MARKETPLACE_ENABLED'), + }), + NEXT_PUBLIC_MARKETPLACE_FEATURED_APP: yup + .string() + .when('NEXT_PUBLIC_MARKETPLACE_ENABLED', { + is: true, + then: (schema) => schema, + // eslint-disable-next-line max-len + otherwise: (schema) => schema.max(-1, 'NEXT_PUBLIC_MARKETPLACE_FEATURED_APP cannot not be used without NEXT_PUBLIC_MARKETPLACE_ENABLED'), + }), + NEXT_PUBLIC_MARKETPLACE_BANNER_CONTENT_URL: yup + .string() + .when('NEXT_PUBLIC_MARKETPLACE_ENABLED', { + is: true, + then: (schema) => schema.test(urlTest), + // eslint-disable-next-line max-len + otherwise: (schema) => schema.max(-1, 'NEXT_PUBLIC_MARKETPLACE_BANNER_CONTENT_URL cannot not be used without NEXT_PUBLIC_MARKETPLACE_ENABLED'), + }), + NEXT_PUBLIC_MARKETPLACE_BANNER_LINK_URL: yup + .string() + .when('NEXT_PUBLIC_MARKETPLACE_ENABLED', { + is: true, + then: (schema) => schema.test(urlTest), + // eslint-disable-next-line max-len + otherwise: (schema) => schema.max(-1, 'NEXT_PUBLIC_MARKETPLACE_BANNER_LINK_URL cannot not be used without NEXT_PUBLIC_MARKETPLACE_ENABLED'), + }), + NEXT_PUBLIC_MARKETPLACE_RATING_AIRTABLE_API_KEY: yup + .string() + .when('NEXT_PUBLIC_MARKETPLACE_ENABLED', { + is: true, + then: (schema) => schema, + // eslint-disable-next-line max-len + otherwise: (schema) => schema.max(-1, 'NEXT_PUBLIC_MARKETPLACE_RATING_AIRTABLE_API_KEY cannot not be used without NEXT_PUBLIC_MARKETPLACE_ENABLED'), + }), + NEXT_PUBLIC_MARKETPLACE_RATING_AIRTABLE_BASE_ID: yup + .string() + .when('NEXT_PUBLIC_MARKETPLACE_ENABLED', { + is: true, + then: (schema) => schema, + // eslint-disable-next-line max-len + otherwise: (schema) => schema.max(-1, 'NEXT_PUBLIC_MARKETPLACE_RATING_AIRTABLE_BASE_ID cannot not be used without NEXT_PUBLIC_MARKETPLACE_ENABLED'), + }), + NEXT_PUBLIC_MARKETPLACE_GRAPH_LINKS_URL: yup + .string() + .when('NEXT_PUBLIC_MARKETPLACE_ENABLED', { + is: true, + then: (schema) => schema, + // eslint-disable-next-line max-len + otherwise: (schema) => schema.max(-1, 'NEXT_PUBLIC_MARKETPLACE_GRAPH_LINKS_URL cannot not be used without NEXT_PUBLIC_MARKETPLACE_ENABLED'), }), }); @@ -114,23 +273,69 @@ const beaconChainSchema = yup const rollupSchema = yup .object() .shape({ - NEXT_PUBLIC_IS_OPTIMISTIC_L2_NETWORK: yup.boolean(), - NEXT_PUBLIC_OPTIMISTIC_L2_WITHDRAWAL_URL: yup + NEXT_PUBLIC_ROLLUP_TYPE: yup.string().oneOf(ROLLUP_TYPES), + NEXT_PUBLIC_ROLLUP_L1_BASE_URL: yup .string() - .when('NEXT_PUBLIC_IS_OPTIMISTIC_L2_NETWORK', { + .when('NEXT_PUBLIC_ROLLUP_TYPE', { is: (value: string) => value, then: (schema) => schema.test(urlTest).required(), - // eslint-disable-next-line max-len - otherwise: (schema) => schema.max(-1, 'NEXT_PUBLIC_OPTIMISTIC_L2_WITHDRAWAL_URL cannot not be used if NEXT_PUBLIC_IS_OPTIMISTIC_L2_NETWORK is not set to "true"'), + otherwise: (schema) => schema.max(-1, 'NEXT_PUBLIC_ROLLUP_L1_BASE_URL cannot not be used if NEXT_PUBLIC_ROLLUP_TYPE is not defined'), }), - NEXT_PUBLIC_IS_ZKEVM_L2_NETWORK: yup.boolean(), - NEXT_PUBLIC_L1_BASE_URL: yup + NEXT_PUBLIC_ROLLUP_L2_WITHDRAWAL_URL: yup .string() - .when([ 'NEXT_PUBLIC_IS_OPTIMISTIC_L2_NETWORK', 'NEXT_PUBLIC_IS_ZKEVM_L2_NETWORK' ], { - is: (isOptimistic?: boolean, isZk?: boolean) => isOptimistic || isZk, + .when('NEXT_PUBLIC_ROLLUP_TYPE', { + is: (value: string) => value === 'optimistic', then: (schema) => schema.test(urlTest).required(), - // eslint-disable-next-line max-len - otherwise: (schema) => schema.max(-1, 'NEXT_PUBLIC_L1_BASE_URL cannot not be used if NEXT_PUBLIC_IS_OPTIMISTIC_L2_NETWORK or NEXT_PUBLIC_IS_ZKEVM_L2_NETWORK is not set to "true"'), + otherwise: (schema) => schema.max(-1, 'NEXT_PUBLIC_ROLLUP_L2_WITHDRAWAL_URL can be used only if NEXT_PUBLIC_ROLLUP_TYPE is set to \'optimistic\' '), + }), + NEXT_PUBLIC_ROLLUP_OUTPUT_ROOTS_ENABLED: yup + .boolean() + .when('NEXT_PUBLIC_ROLLUP_TYPE', { + is: 'optimistic', + then: (schema) => schema, + otherwise: (schema) => schema.test( + 'not-exist', + 'NEXT_PUBLIC_ROLLUP_OUTPUT_ROOTS_ENABLED can only be used if NEXT_PUBLIC_ROLLUP_TYPE is set to \'optimistic\' ', + value => value === undefined, + ), + }), + NEXT_PUBLIC_ROLLUP_PARENT_CHAIN_NAME: yup + .string() + .when('NEXT_PUBLIC_ROLLUP_TYPE', { + is: 'arbitrum', + then: (schema) => schema, + otherwise: (schema) => schema.test( + 'not-exist', + 'NEXT_PUBLIC_ROLLUP_PARENT_CHAIN_NAME can only be used if NEXT_PUBLIC_ROLLUP_TYPE is set to \'arbitrum\' ', + value => value === undefined, + ), + }), + NEXT_PUBLIC_ROLLUP_HOMEPAGE_SHOW_LATEST_BLOCKS: yup + .boolean() + .when('NEXT_PUBLIC_ROLLUP_TYPE', { + is: (value: string) => value, + then: (schema) => schema, + otherwise: (schema) => schema.test( + 'not-exist', + 'NEXT_PUBLIC_ROLLUP_HOMEPAGE_SHOW_LATEST_BLOCKS cannot not be used if NEXT_PUBLIC_ROLLUP_TYPE is not defined', + value => value === undefined, + ), + }), + }); + +const celoSchema = yup + .object() + .shape({ + NEXT_PUBLIC_CELO_ENABLED: yup.boolean(), + NEXT_PUBLIC_CELO_L2_UPGRADE_BLOCK: yup + .string() + .when('NEXT_PUBLIC_CELO_ENABLED', { + is: (value: boolean) => value, + then: (schema) => schema.min(0).optional(), + otherwise: (schema) => schema.max( + -1, + 'NEXT_PUBLIC_CELO_L2_UPGRADE_BLOCK cannot not be used if NEXT_PUBLIC_CELO_ENABLED is not set to "true"', + ), }), }); @@ -147,16 +352,28 @@ const adButlerConfigSchema = yup height: yup.number().positive().required(), }) .required(), + }) + .when('NEXT_PUBLIC_AD_BANNER_ADDITIONAL_PROVIDER', { + is: (value: AdBannerProviders) => value === 'adbutler', + then: (schema) => schema + .shape({ + id: yup.string().required(), + width: yup.number().positive().required(), + height: yup.number().positive().required(), + }) + .required(), }); const adsBannerSchema = yup .object() .shape({ NEXT_PUBLIC_AD_BANNER_PROVIDER: yup.string().oneOf(SUPPORTED_AD_BANNER_PROVIDERS), + NEXT_PUBLIC_AD_BANNER_ADDITIONAL_PROVIDER: yup.string().oneOf(SUPPORTED_AD_BANNER_ADDITIONAL_PROVIDERS), NEXT_PUBLIC_AD_ADBUTLER_CONFIG_DESKTOP: adButlerConfigSchema, NEXT_PUBLIC_AD_ADBUTLER_CONFIG_MOBILE: adButlerConfigSchema, }); +// DEPRECATED const sentrySchema = yup .object() .shape({ @@ -174,20 +391,6 @@ const sentrySchema = yup is: (value: string) => Boolean(value), then: (schema) => schema, }), - NEXT_PUBLIC_APP_INSTANCE: yup - .string() - .when('NEXT_PUBLIC_SENTRY_DSN', { - is: (value: string) => Boolean(value), - then: (schema) => schema, - otherwise: (schema) => schema.max(-1, 'NEXT_PUBLIC_APP_INSTANCE cannot not be used without NEXT_PUBLIC_SENTRY_DSN'), - }), - NEXT_PUBLIC_APP_ENV: yup - .string() - .when('NEXT_PUBLIC_SENTRY_DSN', { - is: (value: string) => Boolean(value), - then: (schema) => schema, - otherwise: (schema) => schema.max(-1, 'NEXT_PUBLIC_APP_ENV cannot not be used without NEXT_PUBLIC_SENTRY_DSN'), - }), }); const accountSchema = yup @@ -198,7 +401,7 @@ const accountSchema = yup .string() .when('NEXT_PUBLIC_IS_ACCOUNT_SUPPORTED', { is: (value: boolean) => value, - then: (schema) => schema.required(), + then: (schema) => schema, otherwise: (schema) => schema.max(-1, 'NEXT_PUBLIC_AUTH0_CLIENT_ID cannot not be used if NEXT_PUBLIC_IS_ACCOUNT_SUPPORTED is not set to "true"'), }), NEXT_PUBLIC_AUTH_URL: yup @@ -209,18 +412,11 @@ const accountSchema = yup otherwise: (schema) => schema.max(-1, 'NEXT_PUBLIC_AUTH_URL cannot not be used if NEXT_PUBLIC_IS_ACCOUNT_SUPPORTED is not set to "true"'), }), NEXT_PUBLIC_LOGOUT_URL: yup - .string() - .when('NEXT_PUBLIC_IS_ACCOUNT_SUPPORTED', { - is: (value: boolean) => value, - then: (schema) => schema.test(urlTest).required(), - otherwise: (schema) => schema.max(-1, 'NEXT_PUBLIC_LOGOUT_URL cannot not be used if NEXT_PUBLIC_IS_ACCOUNT_SUPPORTED is not set to "true"'), - }), - NEXT_PUBLIC_ADMIN_SERVICE_API_HOST: yup .string() .when('NEXT_PUBLIC_IS_ACCOUNT_SUPPORTED', { is: (value: boolean) => value, then: (schema) => schema.test(urlTest), - otherwise: (schema) => schema.max(-1, 'NEXT_PUBLIC_ADMIN_SERVICE_API_HOST cannot not be used if NEXT_PUBLIC_IS_ACCOUNT_SUPPORTED is not set to "true"'), + otherwise: (schema) => schema.max(-1, 'NEXT_PUBLIC_LOGOUT_URL cannot not be used if NEXT_PUBLIC_IS_ACCOUNT_SUPPORTED is not set to "true"'), }), }); @@ -241,6 +437,34 @@ const navItemExternalSchema: yup.ObjectSchema = yup url: yup.string().test(urlTest).required(), }); +const fontFamilySchema: yup.ObjectSchema = yup + .object() + .transform(replaceQuotes) + .json() + .shape({ + name: yup.string().required(), + url: yup.string().test(urlTest).required(), + }); + +const heroBannerButtonStateSchema: yup.ObjectSchema = yup.object({ + background: yup.array().max(2).of(yup.string()), + text_color: yup.array().max(2).of(yup.string()), +}); + +const heroBannerSchema: yup.ObjectSchema = yup.object() + .transform(replaceQuotes) + .json() + .shape({ + background: yup.array().max(2).of(yup.string()), + text_color: yup.array().max(2).of(yup.string()), + border: yup.array().max(2).of(yup.string()), + button: yup.object({ + _default: heroBannerButtonStateSchema, + _hover: heroBannerButtonStateSchema, + _selected: heroBannerButtonStateSchema, + }), + }); + const footerLinkSchema: yup.ObjectSchema = yup .object({ text: yup.string().required(), @@ -259,6 +483,7 @@ const footerLinkGroupSchema: yup.ObjectSchema = yup const networkExplorerSchema: yup.ObjectSchema = yup .object({ title: yup.string().required(), + logo: yup.string().test(urlTest), baseUrl: yup.string().test(urlTest).required(), paths: yup .object() @@ -320,6 +545,24 @@ const bridgedTokensSchema = yup }), }); +const deFiDropdownItemSchema: yup.ObjectSchema = yup + .object({ + text: yup.string().required(), + icon: yup.string().required(), + dappId: yup.string(), + url: yup.string().test(urlTest), + }) + .test('oneOfRequired', 'NEXT_PUBLIC_DEFI_DROPDOWN_ITEMS: Either dappId or url is required', function(value) { + return Boolean(value.dappId) || Boolean(value.url); + }) as yup.ObjectSchema; + +const multichainProviderConfigSchema: yup.ObjectSchema = yup.object({ + name: yup.string().required(), + url_template: yup.string().required(), + logo: yup.string().required(), + dapp_id: yup.string(), +}); + const schema = yup .object() .noUnknown(true, (params) => { @@ -337,6 +580,8 @@ const schema = yup NEXT_PUBLIC_APP_HOST: yup.string().required(), NEXT_PUBLIC_APP_PROTOCOL: yup.string().oneOf(protocols), NEXT_PUBLIC_APP_PORT: yup.number().positive().integer(), + NEXT_PUBLIC_APP_ENV: yup.string(), + NEXT_PUBLIC_APP_INSTANCE: yup.string(), // 2. Blockchain parameters NEXT_PUBLIC_NETWORK_NAME: yup.string().required(), @@ -347,8 +592,20 @@ const schema = yup NEXT_PUBLIC_NETWORK_CURRENCY_WEI_NAME: yup.string(), NEXT_PUBLIC_NETWORK_CURRENCY_SYMBOL: yup.string(), NEXT_PUBLIC_NETWORK_CURRENCY_DECIMALS: yup.number().integer().positive(), - NEXT_PUBLIC_NETWORK_GOVERNANCE_TOKEN_SYMBOL: yup.string(), - NEXT_PUBLIC_NETWORK_VERIFICATION_TYPE: yup.string().oneOf([ 'validation', 'mining' ]), + NEXT_PUBLIC_NETWORK_SECONDARY_COIN_SYMBOL: yup.string(), + NEXT_PUBLIC_NETWORK_MULTIPLE_GAS_CURRENCIES: yup.boolean(), + NEXT_PUBLIC_NETWORK_VERIFICATION_TYPE: yup + .string().oneOf([ 'validation', 'mining' ]) + .when('NEXT_PUBLIC_ROLLUP_TYPE', { + is: (value: string) => value === 'arbitrum' || value === 'zkEvm', + then: (schema) => schema.test( + 'not-exist', + 'NEXT_PUBLIC_NETWORK_VERIFICATION_TYPE can not be set for Arbitrum and ZkEVM rollups', + value => value === undefined, + ), + otherwise: (schema) => schema, + }), + NEXT_PUBLIC_NETWORK_TOKEN_STANDARD_NAME: yup.string(), NEXT_PUBLIC_IS_TESTNET: yup.boolean(), NEXT_PUBLIC_IS_DEVNET: yup.boolean(), NEXT_PUBLIC_NETWORK_CURRENCY_IMAGE_URL: yup.string(), @@ -365,12 +622,34 @@ const schema = yup .array() .transform(replaceQuotes) .json() - .of(yup.string().oneOf([ 'daily_txs', 'coin_price', 'market_cap', 'tvl' ])), + .of(yup.string().oneOf(CHAIN_INDICATOR_IDS)), + NEXT_PUBLIC_HOMEPAGE_STATS: yup + .array() + .transform(replaceQuotes) + .json() + .of(yup.string().oneOf(HOME_STATS_WIDGET_IDS)), NEXT_PUBLIC_HOMEPAGE_PLATE_TEXT_COLOR: yup.string(), NEXT_PUBLIC_HOMEPAGE_PLATE_BACKGROUND: yup.string(), NEXT_PUBLIC_HOMEPAGE_SHOW_GAS_TRACKER: yup.boolean(), NEXT_PUBLIC_HOMEPAGE_SHOW_AVG_BLOCK_TIME: yup.boolean(), NEXT_PUBLIC_HOMEPAGE_TX_ICON: yup.string(), + NEXT_PUBLIC_HOMEPAGE_HERO_BANNER_CONFIG: yup + .mixed() + .test( + 'shape', + (ctx) => { + try { + heroBannerSchema.validateSync(ctx.originalValue); + throw new Error('Unknown validation error'); + } catch (error: unknown) { + const message = typeof error === 'object' && error !== null && 'errors' in error && Array.isArray(error.errors) ? error.errors.join(', ') : ''; + return 'Invalid schema were provided for NEXT_PUBLIC_HOMEPAGE_HERO_BANNER_CONFIG' + (message ? `: ${ message }` : ''); + } + }, + (data) => { + const isUndefined = data === undefined; + return isUndefined || heroBannerSchema.isValidSync(data); + }), // b. sidebar NEXT_PUBLIC_FEATURED_NETWORKS: yup @@ -387,6 +666,12 @@ const schema = yup .transform(replaceQuotes) .json() .of(yup.string().oneOf(NAVIGATION_LINK_IDS)), + NEXT_PUBLIC_NAVIGATION_HIGHLIGHTED_ROUTES: yup + .array() + .transform(replaceQuotes) + .json() + .of(yup.string()), + NEXT_PUBLIC_NAVIGATION_LAYOUT: yup.string().oneOf([ 'horizontal', 'vertical' ]), NEXT_PUBLIC_NETWORK_LOGO: yup.string().test(urlTest), NEXT_PUBLIC_NETWORK_LOGO_DARK: yup.string().test(urlTest), NEXT_PUBLIC_NETWORK_ICON: yup.string().test(urlTest), @@ -407,12 +692,46 @@ const schema = yup .json() .of(yup.string().oneOf(BLOCK_FIELDS_IDS)), NEXT_PUBLIC_VIEWS_ADDRESS_IDENTICON_TYPE: yup.string().oneOf(IDENTICON_TYPES), + NEXT_PUBLIC_VIEWS_ADDRESS_FORMAT: yup + .array() + .transform(replaceQuotes) + .json() + .of(yup.string().oneOf(ADDRESS_FORMATS)), + NEXT_PUBLIC_VIEWS_ADDRESS_BECH_32_PREFIX: yup + .string() + .when('NEXT_PUBLIC_VIEWS_ADDRESS_FORMAT', { + is: (value: Array | undefined) => value && value.includes('bech32'), + then: (schema) => schema.required().min(1).max(83), + otherwise: (schema) => schema.max(-1, 'NEXT_PUBLIC_VIEWS_ADDRESS_BECH_32_PREFIX is required if NEXT_PUBLIC_VIEWS_ADDRESS_FORMAT contains "bech32"'), + }), + NEXT_PUBLIC_VIEWS_ADDRESS_HIDDEN_VIEWS: yup .array() .transform(replaceQuotes) .json() .of(yup.string().oneOf(ADDRESS_VIEWS_IDS)), NEXT_PUBLIC_VIEWS_CONTRACT_SOLIDITYSCAN_ENABLED: yup.boolean(), + NEXT_PUBLIC_VIEWS_CONTRACT_EXTRA_VERIFICATION_METHODS: yup + .mixed() + .test( + 'shape', + 'Invalid schema were provided for NEXT_PUBLIC_VIEWS_CONTRACT_EXTRA_VERIFICATION_METHODS, it should be either array of method ids or "none" string literal', + (data) => { + const isNoneSchema = yup.string().oneOf([ 'none' ]); + const isArrayOfMethodsSchema = yup + .array() + .transform(replaceQuotes) + .json() + .of(yup.string().oneOf(SMART_CONTRACT_EXTRA_VERIFICATION_METHODS)); + + return isNoneSchema.isValidSync(data) || isArrayOfMethodsSchema.isValidSync(data); + }), + NEXT_PUBLIC_VIEWS_CONTRACT_LANGUAGE_FILTERS: yup + .array() + .transform(replaceQuotes) + .json() + .of(yup.string().oneOf(SMART_CONTRACT_LANGUAGE_FILTERS)), + NEXT_PUBLIC_VIEWS_TX_HIDDEN_FIELDS: yup .array() .transform(replaceQuotes) @@ -428,6 +747,7 @@ const schema = yup .transform(replaceQuotes) .json() .of(nftMarketplaceSchema), + NEXT_PUBLIC_HELIA_VERIFIED_FETCH_ENABLED: yup.boolean(), // e. misc NEXT_PUBLIC_NETWORK_EXPLORERS: yup @@ -440,17 +760,50 @@ const schema = yup .transform(replaceQuotes) .json() .of(contractCodeIdeSchema), + NEXT_PUBLIC_HAS_CONTRACT_AUDIT_REPORTS: yup.boolean(), NEXT_PUBLIC_HIDE_INDEXING_ALERT_BLOCKS: yup.boolean(), NEXT_PUBLIC_HIDE_INDEXING_ALERT_INT_TXS: yup.boolean(), NEXT_PUBLIC_MAINTENANCE_ALERT_MESSAGE: yup.string(), + NEXT_PUBLIC_COLOR_THEME_DEFAULT: yup.string().oneOf(COLOR_THEME_IDS), + NEXT_PUBLIC_FONT_FAMILY_HEADING: yup + .mixed() + .test('shape', 'Invalid schema were provided for NEXT_PUBLIC_FONT_FAMILY_HEADING', (data) => { + const isUndefined = data === undefined; + return isUndefined || fontFamilySchema.isValidSync(data); + }), + NEXT_PUBLIC_FONT_FAMILY_BODY: yup + .mixed() + .test('shape', 'Invalid schema were provided for NEXT_PUBLIC_FONT_FAMILY_BODY', (data) => { + const isUndefined = data === undefined; + return isUndefined || fontFamilySchema.isValidSync(data); + }), + NEXT_PUBLIC_MAX_CONTENT_WIDTH_ENABLED: yup.boolean(), // 5. Features configuration - NEXT_PUBLIC_API_SPEC_URL: yup.string().test(urlTest), + NEXT_PUBLIC_API_SPEC_URL: yup + .mixed() + .test('shape', 'Invalid schema were provided for NEXT_PUBLIC_API_SPEC_URL, it should be either URL-string or "none" string literal', (data) => { + const isNoneSchema = yup.string().oneOf([ 'none' ]); + const isUrlStringSchema = yup.string().test(urlTest); + + return isNoneSchema.isValidSync(data) || isUrlStringSchema.isValidSync(data); + }), NEXT_PUBLIC_STATS_API_HOST: yup.string().test(urlTest), + NEXT_PUBLIC_STATS_API_BASE_PATH: yup.string(), NEXT_PUBLIC_VISUALIZE_API_HOST: yup.string().test(urlTest), + NEXT_PUBLIC_VISUALIZE_API_BASE_PATH: yup.string(), NEXT_PUBLIC_CONTRACT_INFO_API_HOST: yup.string().test(urlTest), NEXT_PUBLIC_NAME_SERVICE_API_HOST: yup.string().test(urlTest), - NEXT_PUBLIC_GRAPHIQL_TRANSACTION: yup.string().matches(regexp.HEX_REGEXP), + NEXT_PUBLIC_METADATA_SERVICE_API_HOST: yup.string().test(urlTest), + NEXT_PUBLIC_ADMIN_SERVICE_API_HOST: yup.string().test(urlTest), + NEXT_PUBLIC_GRAPHIQL_TRANSACTION: yup + .mixed() + .test('shape', 'Invalid schema were provided for NEXT_PUBLIC_GRAPHIQL_TRANSACTION, it should be either Hex-string or "none" string literal', (data) => { + const isNoneSchema = yup.string().oneOf([ 'none' ]); + const isHashStringSchema = yup.string().matches(regexp.HEX_REGEXP); + + return isNoneSchema.isValidSync(data) || isHashStringSchema.isValidSync(data); + }), NEXT_PUBLIC_WEB3_WALLETS: yup .mixed() .test('shape', 'Invalid schema were provided for NEXT_PUBLIC_WEB3_WALLETS, it should be either array or "none" string literal', (data) => { @@ -469,15 +822,97 @@ const schema = yup NEXT_PUBLIC_PROMOTE_BLOCKSCOUT_IN_TITLE: yup.boolean(), NEXT_PUBLIC_OG_DESCRIPTION: yup.string(), NEXT_PUBLIC_OG_IMAGE_URL: yup.string().test(urlTest), + NEXT_PUBLIC_OG_ENHANCED_DATA_ENABLED: yup.boolean(), + NEXT_PUBLIC_SEO_ENHANCED_DATA_ENABLED: yup.boolean(), + NEXT_PUBLIC_SAFE_TX_SERVICE_URL: yup.string().test(urlTest), NEXT_PUBLIC_IS_SUAVE_CHAIN: yup.boolean(), NEXT_PUBLIC_HAS_USER_OPS: yup.boolean(), + NEXT_PUBLIC_METASUITES_ENABLED: yup.boolean(), + NEXT_PUBLIC_MULTICHAIN_BALANCE_PROVIDER_CONFIG: yup + .array() + .transform(replaceQuotes) + .json() + .of(multichainProviderConfigSchema), + NEXT_PUBLIC_GAS_REFUEL_PROVIDER_CONFIG: yup + .mixed() + .test('shape', 'Invalid schema were provided for NEXT_PUBLIC_GAS_REFUEL_PROVIDER_CONFIG, it should have name and url template', (data) => { + const isUndefined = data === undefined; + const valueSchema = yup.object().transform(replaceQuotes).json().shape({ + name: yup.string().required(), + url_template: yup.string().required(), + logo: yup.string(), + dapp_id: yup.string(), + }); + + return isUndefined || valueSchema.isValidSync(data); + }), + NEXT_PUBLIC_VALIDATORS_CHAIN_TYPE: yup.string().oneOf(VALIDATORS_CHAIN_TYPE), + NEXT_PUBLIC_GAS_TRACKER_ENABLED: yup.boolean(), + NEXT_PUBLIC_GAS_TRACKER_UNITS: yup.array().transform(replaceQuotes).json().of(yup.string().oneOf(GAS_UNITS)), + NEXT_PUBLIC_DATA_AVAILABILITY_ENABLED: yup.boolean(), + NEXT_PUBLIC_ADVANCED_FILTER_ENABLED: yup.boolean(), + NEXT_PUBLIC_DEFI_DROPDOWN_ITEMS: yup + .array() + .transform(replaceQuotes) + .json() + .of(deFiDropdownItemSchema), + NEXT_PUBLIC_FAULT_PROOF_ENABLED: yup.boolean() + .when('NEXT_PUBLIC_ROLLUP_TYPE', { + is: 'optimistic', + then: (schema) => schema, + otherwise: (schema) => schema.test( + 'not-exist', + 'NEXT_PUBLIC_FAULT_PROOF_ENABLED can only be used with NEXT_PUBLIC_ROLLUP_TYPE=optimistic', + value => value === undefined, + ), + }), + NEXT_PUBLIC_HAS_MUD_FRAMEWORK: yup.boolean() + .when('NEXT_PUBLIC_ROLLUP_TYPE', { + is: 'optimistic', + then: (schema) => schema, + otherwise: (schema) => schema.test( + 'not-exist', + 'NEXT_PUBLIC_HAS_MUD_FRAMEWORK can only be used with NEXT_PUBLIC_ROLLUP_TYPE=optimistic', + value => value === undefined, + ), + }), + NEXT_PUBLIC_DEX_POOLS_ENABLED: yup.boolean() + .when('NEXT_PUBLIC_CONTRACT_INFO_API_HOST', { + is: (value: string) => Boolean(value), + then: (schema) => schema, + otherwise: (schema) => schema.test( + 'not-exist', + 'NEXT_PUBLIC_DEX_POOLS_ENABLED can only be used with NEXT_PUBLIC_CONTRACT_INFO_API_HOST', + value => value === undefined, + ), + }), + NEXT_PUBLIC_SAVE_ON_GAS_ENABLED: yup.boolean(), + NEXT_PUBLIC_ADDRESS_USERNAME_TAG: yup + .mixed() + .test('shape', 'Invalid schema were provided for NEXT_PUBLIC_ADDRESS_USERNAME_TAG, it should have api_url_template', (data) => { + const isUndefined = data === undefined; + const valueSchema = yup.object().transform(replaceQuotes).json().shape({ + api_url_template: yup.string().required(), + tag_link_template: yup.string(), + tag_icon: yup.string(), + tag_bg_color: yup.string(), + tag_text_color: yup.string(), + }); + + return isUndefined || valueSchema.isValidSync(data); + }), + NEXT_PUBLIC_REWARDS_SERVICE_API_HOST: yup.string().test(urlTest), + NEXT_PUBLIC_XSTAR_SCORE_URL: yup.string().test(urlTest), + NEXT_PUBLIC_GAME_BADGE_CLAIM_LINK: yup.string().test(urlTest), // 6. External services envs NEXT_PUBLIC_WALLET_CONNECT_PROJECT_ID: yup.string(), NEXT_PUBLIC_RE_CAPTCHA_APP_SITE_KEY: yup.string(), + NEXT_PUBLIC_RE_CAPTCHA_V3_APP_SITE_KEY: yup.string(), // DEPRECATED NEXT_PUBLIC_GOOGLE_ANALYTICS_PROPERTY_ID: yup.string(), NEXT_PUBLIC_MIXPANEL_PROJECT_TOKEN: yup.string(), NEXT_PUBLIC_GROWTH_BOOK_CLIENT_KEY: yup.string(), + NEXT_PUBLIC_ROLLBAR_CLIENT_TOKEN: yup.string(), // Misc NEXT_PUBLIC_USE_NEXT_JS_PROXY: yup.boolean(), @@ -490,6 +925,7 @@ const schema = yup .concat(adsBannerSchema) .concat(marketplaceSchema) .concat(rollupSchema) + .concat(celoSchema) .concat(beaconChainSchema) .concat(bridgedTokensSchema) .concat(sentrySchema); diff --git a/deploy/tools/envs-validator/test.sh b/deploy/tools/envs-validator/test.sh index 179ef25bc9..46d3ea3fca 100755 --- a/deploy/tools/envs-validator/test.sh +++ b/deploy/tools/envs-validator/test.sh @@ -1,6 +1,5 @@ #!/bin/bash -secrets_file=".env.secrets" test_folder="./test" common_file="${test_folder}/.env.common" @@ -8,10 +7,9 @@ common_file="${test_folder}/.env.common" export NEXT_PUBLIC_GIT_COMMIT_SHA=$(git rev-parse --short HEAD) export NEXT_PUBLIC_GIT_TAG=$(git describe --tags --abbrev=0) ../../scripts/collect_envs.sh ../../../docs/ENVS.md -cp ../../../.env.example ${secrets_file} # Copy test assets -mkdir -p "./public/assets" +mkdir -p "./public/assets/configs" cp -r ${test_folder}/assets ./public/ # Build validator script @@ -26,7 +24,6 @@ validate_file() { dotenv \ -e $test_file \ -e $common_file \ - -e $secrets_file \ yarn run validate -- --silent if [ $? -eq 0 ]; then @@ -46,4 +43,4 @@ for file in "${test_files[@]}"; do if [ $? -eq 1 ]; then exit 1 fi -done \ No newline at end of file +done diff --git a/deploy/tools/envs-validator/test/.env.adbutler_add b/deploy/tools/envs-validator/test/.env.adbutler_add new file mode 100644 index 0000000000..7f1968e4bb --- /dev/null +++ b/deploy/tools/envs-validator/test/.env.adbutler_add @@ -0,0 +1,4 @@ +NEXT_PUBLIC_AD_BANNER_PROVIDER='slise' +NEXT_PUBLIC_AD_BANNER_ADDITIONAL_PROVIDER='adbutler' +NEXT_PUBLIC_AD_ADBUTLER_CONFIG_DESKTOP={'id':'123456','width':'728','height':'90'} +NEXT_PUBLIC_AD_ADBUTLER_CONFIG_MOBILE={'id':'654321','width':'300','height':'100'} \ No newline at end of file diff --git a/deploy/tools/envs-validator/test/.env.alt b/deploy/tools/envs-validator/test/.env.alt new file mode 100644 index 0000000000..172a809a56 --- /dev/null +++ b/deploy/tools/envs-validator/test/.env.alt @@ -0,0 +1,8 @@ +NEXT_PUBLIC_GRAPHIQL_TRANSACTION=none +NEXT_PUBLIC_API_SPEC_URL=none +NEXT_PUBLIC_VIEWS_CONTRACT_EXTRA_VERIFICATION_METHODS=none +NEXT_PUBLIC_HOMEPAGE_STATS=[] +NEXT_PUBLIC_VIEWS_ADDRESS_FORMAT=['base16','bech32'] +NEXT_PUBLIC_VIEWS_ADDRESS_BECH_32_PREFIX=foo +NEXT_PUBLIC_RE_CAPTCHA_APP_SITE_KEY=xxx +NEXT_PUBLIC_RE_CAPTCHA_V3_APP_SITE_KEY=deprecated \ No newline at end of file diff --git a/deploy/tools/envs-validator/test/.env.arbitrum b/deploy/tools/envs-validator/test/.env.arbitrum new file mode 100644 index 0000000000..ee0ce91f00 --- /dev/null +++ b/deploy/tools/envs-validator/test/.env.arbitrum @@ -0,0 +1,4 @@ +NEXT_PUBLIC_ROLLUP_TYPE=arbitrum +NEXT_PUBLIC_ROLLUP_L1_BASE_URL=https://example.com +NEXT_PUBLIC_ROLLUP_HOMEPAGE_SHOW_LATEST_BLOCKS=true +NEXT_PUBLIC_ROLLUP_PARENT_CHAIN_NAME=DuckChain \ No newline at end of file diff --git a/deploy/tools/envs-validator/test/.env.base b/deploy/tools/envs-validator/test/.env.base index 584200a7c0..debb88fd4e 100644 --- a/deploy/tools/envs-validator/test/.env.base +++ b/deploy/tools/envs-validator/test/.env.base @@ -1,3 +1,14 @@ +NEXT_PUBLIC_ROLLBAR_CLIENT_TOKEN=https://rollbar.com +NEXT_PUBLIC_AUTH_URL=https://example.com +NEXT_PUBLIC_IS_ACCOUNT_SUPPORTED=true +NEXT_PUBLIC_LOGOUT_URL=https://example.com +NEXT_PUBLIC_WALLET_CONNECT_PROJECT_ID=xxx +NEXT_PUBLIC_RE_CAPTCHA_APP_SITE_KEY=xxx +NEXT_PUBLIC_GOOGLE_ANALYTICS_PROPERTY_ID=UA-XXXXXX-X +NEXT_PUBLIC_MIXPANEL_PROJECT_TOKEN=xxx +NEXT_PUBLIC_GROWTH_BOOK_CLIENT_KEY=xxx +NEXT_PUBLIC_AUTH0_CLIENT_ID=xxx +NEXT_PUBLIC_GROWTH_BOOK_CLIENT_KEY=xxx NEXT_PUBLIC_AD_TEXT_PROVIDER=coinzilla NEXT_PUBLIC_AD_BANNER_PROVIDER=slise NEXT_PUBLIC_ADMIN_SERVICE_API_HOST=https://example.com @@ -9,28 +20,39 @@ NEXT_PUBLIC_APP_PORT=3000 NEXT_PUBLIC_APP_PROTOCOL=http NEXT_PUBLIC_BRIDGED_TOKENS_CHAINS=[{'id':'1','title':'Ethereum','short_title':'ETH','base_url':'https://example.com'}] NEXT_PUBLIC_BRIDGED_TOKENS_BRIDGES=[{'type':'omni','title':'OmniBridge','short_title':'OMNI'}] +NEXT_PUBLIC_COLOR_THEME_DEFAULT=dim NEXT_PUBLIC_CONTRACT_CODE_IDES=[{'title':'Remix IDE','url':'https://remix.blockscout.com/?address={hash}&blockscout={domain}','icon_url':'https://example.com/icon.svg'}] NEXT_PUBLIC_CONTRACT_INFO_API_HOST=https://example.com +NEXT_PUBLIC_DATA_AVAILABILITY_ENABLED=true NEXT_PUBLIC_FEATURED_NETWORKS=https://example.com +NEXT_PUBLIC_NAVIGATION_HIGHLIGHTED_ROUTES=['/accounts','/apps'] +NEXT_PUBLIC_NAVIGATION_LAYOUT=horizontal +NEXT_PUBLIC_FONT_FAMILY_HEADING={'name':'Montserrat','url':'https://fonts.googleapis.com/css2?family=Montserrat:wght@400;500;600;700&display=swap'} +NEXT_PUBLIC_FONT_FAMILY_BODY={'name':'Raleway','url':'https://fonts.googleapis.com/css2?family=Raleway:wght@400;500;600;700&display=swap'} NEXT_PUBLIC_FOOTER_LINKS=https://example.com NEXT_PUBLIC_GRAPHIQL_TRANSACTION=0xf7d4972356e6ae44ae948d0cf19ef2beaf0e574c180997e969a2837da15e349d +NEXT_PUBLIC_HELIA_VERIFIED_FETCH_ENABLED=false NEXT_PUBLIC_HIDE_INDEXING_ALERT_BLOCKS=false NEXT_PUBLIC_HIDE_INDEXING_ALERT_INT_TXS=false +NEXT_PUBLIC_MAX_CONTENT_WIDTH_ENABLED=false NEXT_PUBLIC_HOMEPAGE_CHARTS=['daily_txs'] +NEXT_PUBLIC_HOMEPAGE_STATS=['total_blocks','average_block_time','total_txs','wallet_addresses','gas_tracker','current_epoch'] NEXT_PUBLIC_HOMEPAGE_PLATE_TEXT_COLOR='#fff' NEXT_PUBLIC_HOMEPAGE_PLATE_BACKGROUND='rgb(255, 145, 0)' -NEXT_PUBLIC_HOMEPAGE_SHOW_GAS_TRACKER=true -NEXT_PUBLIC_HOMEPAGE_SHOW_AVG_BLOCK_TIME=true +NEXT_PUBLIC_HOMEPAGE_HERO_BANNER_CONFIG={'background':['lightpink'],'text_color':['deepskyblue','white'],'border':['3px solid black']} +NEXT_PUBLIC_GAS_TRACKER_ENABLED=true +NEXT_PUBLIC_GAS_TRACKER_UNITS=['gwei'] NEXT_PUBLIC_IS_TESTNET=true -NEXT_PUBLIC_MARKETPLACE_CONFIG_URL=https://example.com -NEXT_PUBLIC_MARKETPLACE_CATEGORIES_URL=https://example.com -NEXT_PUBLIC_MARKETPLACE_SUBMIT_FORM=https://example.com NEXT_PUBLIC_MAINTENANCE_ALERT_MESSAGE='Hello' +NEXT_PUBLIC_METADATA_SERVICE_API_HOST=https://example.com +NEXT_PUBLIC_METASUITES_ENABLED=true +NEXT_PUBLIC_NAVIGATION_HIDDEN_LINKS=['eth_rpc_api','rpc_api'] NEXT_PUBLIC_NETWORK_CURRENCY_DECIMALS=18 NEXT_PUBLIC_NETWORK_CURRENCY_NAME=Ether NEXT_PUBLIC_NETWORK_CURRENCY_SYMBOL=ETH NEXT_PUBLIC_NETWORK_EXPLORERS=[{'title':'Explorer','baseUrl':'https://example.com/','paths':{'tx':'/tx','address':'/address','token':'/token','block':'/block'}}] -NEXT_PUBLIC_NETWORK_GOVERNANCE_TOKEN_SYMBOL=gETH +NEXT_PUBLIC_NETWORK_SECONDARY_COIN_SYMBOL=GNO +NEXT_PUBLIC_NETWORK_MULTIPLE_GAS_CURRENCIES=true NEXT_PUBLIC_NETWORK_ICON=https://example.com/icon.png NEXT_PUBLIC_NETWORK_ICON_DARK=https://example.com/icon.png NEXT_PUBLIC_NETWORK_LOGO=https://example.com/logo.png @@ -40,16 +62,30 @@ NEXT_PUBLIC_NETWORK_SHORT_NAME=Test NEXT_PUBLIC_NETWORK_VERIFICATION_TYPE=validation NEXT_PUBLIC_OG_DESCRIPTION='Hello world!' NEXT_PUBLIC_OG_IMAGE_URL=https://example.com/image.png +NEXT_PUBLIC_OG_ENHANCED_DATA_ENABLED=true +NEXT_PUBLIC_SEO_ENHANCED_DATA_ENABLED=true NEXT_PUBLIC_OTHER_LINKS=[{'url':'https://blockscout.com','text':'Blockscout'}] NEXT_PUBLIC_PROMOTE_BLOCKSCOUT_IN_TITLE=true +NEXT_PUBLIC_SAFE_TX_SERVICE_URL=https://safe-transaction-mainnet.safe.global NEXT_PUBLIC_STATS_API_HOST=https://example.com +NEXT_PUBLIC_STATS_API_BASE_PATH=/ NEXT_PUBLIC_USE_NEXT_JS_PROXY=false NEXT_PUBLIC_VIEWS_ADDRESS_IDENTICON_TYPE=gradient_avatar +NEXT_PUBLIC_VIEWS_ADDRESS_FORMAT=['base16'] NEXT_PUBLIC_VIEWS_ADDRESS_HIDDEN_VIEWS=['top_accounts'] +NEXT_PUBLIC_VIEWS_CONTRACT_EXTRA_VERIFICATION_METHODS=['solidity-hardhat','solidity-foundry'] +NEXT_PUBLIC_VIEWS_CONTRACT_LANGUAGE_FILTERS=['solidity','vyper','yul','scilla'] NEXT_PUBLIC_VIEWS_BLOCK_HIDDEN_FIELDS=['burnt_fees','total_reward'] NEXT_PUBLIC_VIEWS_NFT_MARKETPLACES=[{'name':'NFT Marketplace','collection_url':'https://example.com/{hash}','instance_url':'https://example.com/{hash}/{id}','logo_url':'https://example.com/logo.png'}] NEXT_PUBLIC_VIEWS_TX_ADDITIONAL_FIELDS=['fee_per_gas'] NEXT_PUBLIC_VIEWS_TX_HIDDEN_FIELDS=['value','fee_currency','gas_price','tx_fee','gas_fees','burnt_fees'] NEXT_PUBLIC_VISUALIZE_API_HOST=https://example.com +NEXT_PUBLIC_VISUALIZE_API_BASE_PATH=https://example.com NEXT_PUBLIC_WEB3_DISABLE_ADD_TOKEN_TO_WALLET=false NEXT_PUBLIC_WEB3_WALLETS=['coinbase','metamask','token_pocket'] +NEXT_PUBLIC_VALIDATORS_CHAIN_TYPE=stability +NEXT_PUBLIC_DEFI_DROPDOWN_ITEMS=[{'text':'Swap','icon':'swap','dappId':'uniswap'},{'text':'Payment link','icon':'payment_link','url':'https://example.com'}] +NEXT_PUBLIC_MULTICHAIN_BALANCE_PROVIDER_CONFIG=[{'name': 'zerion', 'url_template': 'https://app.zerion.io/{address}/overview', 'logo': 'https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/marketplace-logos/zerion.svg'}] +NEXT_PUBLIC_GAS_REFUEL_PROVIDER_CONFIG={'name': 'Need gas?', 'dapp_id': 'smol-refuel', 'url_template': 'https://smolrefuel.com/?outboundChain={chainId}&partner=blockscout&utm_source=blockscout&utm_medium=address&disableBridges=true', 'logo': 'https://blockscout-content.s3.amazonaws.com/smolrefuel-logo-action-button.png'} +NEXT_PUBLIC_SAVE_ON_GAS_ENABLED=true +NEXT_PUBLIC_REWARDS_SERVICE_API_HOST=https://example.com diff --git a/deploy/tools/envs-validator/test/.env.celo b/deploy/tools/envs-validator/test/.env.celo new file mode 100644 index 0000000000..1082044208 --- /dev/null +++ b/deploy/tools/envs-validator/test/.env.celo @@ -0,0 +1,2 @@ +NEXT_PUBLIC_CELO_ENABLED=true +NEXT_PUBLIC_CELO_L2_UPGRADE_BLOCK=420 \ No newline at end of file diff --git a/deploy/tools/envs-validator/test/.env.common b/deploy/tools/envs-validator/test/.env.common index 1f900840ff..5788f392d3 100644 --- a/deploy/tools/envs-validator/test/.env.common +++ b/deploy/tools/envs-validator/test/.env.common @@ -1,7 +1,4 @@ NEXT_PUBLIC_API_HOST=blockscout.com NEXT_PUBLIC_APP_HOST=localhost -NEXT_PUBLIC_AUTH_URL=https://example.com -NEXT_PUBLIC_IS_ACCOUNT_SUPPORTED=true -NEXT_PUBLIC_LOGOUT_URL=https://example.com NEXT_PUBLIC_NETWORK_ID=1 NEXT_PUBLIC_NETWORK_NAME=Testnet diff --git a/deploy/tools/envs-validator/test/.env.marketplace b/deploy/tools/envs-validator/test/.env.marketplace new file mode 100644 index 0000000000..6cc6b1f839 --- /dev/null +++ b/deploy/tools/envs-validator/test/.env.marketplace @@ -0,0 +1,12 @@ +NEXT_PUBLIC_MARKETPLACE_ENABLED=true +NEXT_PUBLIC_MARKETPLACE_CONFIG_URL=https://example.com +NEXT_PUBLIC_MARKETPLACE_CATEGORIES_URL=https://example.com +NEXT_PUBLIC_MARKETPLACE_SUBMIT_FORM=https://example.com +NEXT_PUBLIC_MARKETPLACE_SUGGEST_IDEAS_FORM=https://example.com +NEXT_PUBLIC_MARKETPLACE_SECURITY_REPORTS_URL=https://example.com +NEXT_PUBLIC_ADMIN_SERVICE_API_HOST=https://example.com +NEXT_PUBLIC_MARKETPLACE_FEATURED_APP=aave +NEXT_PUBLIC_MARKETPLACE_BANNER_CONTENT_URL=https://gist.githubusercontent.com/maxaleks/36f779fd7d74877b57ec7a25a9a3a6c9/raw/746a8a59454c0537235ee44616c4690ce3bbf3c8/banner.html +NEXT_PUBLIC_MARKETPLACE_BANNER_LINK_URL=https://www.basename.app +NEXT_PUBLIC_MARKETPLACE_RATING_AIRTABLE_API_KEY=test +NEXT_PUBLIC_MARKETPLACE_RATING_AIRTABLE_BASE_ID=test diff --git a/deploy/tools/envs-validator/test/.env.optimism b/deploy/tools/envs-validator/test/.env.optimism new file mode 100644 index 0000000000..4821e1ca62 --- /dev/null +++ b/deploy/tools/envs-validator/test/.env.optimism @@ -0,0 +1,6 @@ +NEXT_PUBLIC_ROLLUP_TYPE=optimistic +NEXT_PUBLIC_ROLLUP_L1_BASE_URL=https://example.com +NEXT_PUBLIC_ROLLUP_L2_WITHDRAWAL_URL=https://example.com +NEXT_PUBLIC_FAULT_PROOF_ENABLED=true +NEXT_PUBLIC_ROLLUP_HOMEPAGE_SHOW_LATEST_BLOCKS=true +NEXT_PUBLIC_ROLLUP_OUTPUT_ROOTS_ENABLED=false \ No newline at end of file diff --git a/deploy/tools/envs-validator/test/.env.rollup b/deploy/tools/envs-validator/test/.env.rollup deleted file mode 100644 index 8534221d99..0000000000 --- a/deploy/tools/envs-validator/test/.env.rollup +++ /dev/null @@ -1,3 +0,0 @@ -NEXT_PUBLIC_IS_OPTIMISTIC_L2_NETWORK=true -NEXT_PUBLIC_L1_BASE_URL=https://example.com -NEXT_PUBLIC_OPTIMISTIC_L2_WITHDRAWAL_URL=https://example.com \ No newline at end of file diff --git a/deploy/tools/envs-validator/test/.env.sentry b/deploy/tools/envs-validator/test/.env.sentry deleted file mode 100644 index c34c4d4f26..0000000000 --- a/deploy/tools/envs-validator/test/.env.sentry +++ /dev/null @@ -1,5 +0,0 @@ -NEXT_PUBLIC_SENTRY_DSN=https://sentry.io -SENTRY_CSP_REPORT_URI=https://sentry.io -NEXT_PUBLIC_SENTRY_ENABLE_TRACING=true -NEXT_PUBLIC_APP_ENV=production -NEXT_PUBLIC_APP_INSTANCE=duck \ No newline at end of file diff --git a/deploy/tools/envs-validator/test/assets/featured_networks.json b/deploy/tools/envs-validator/test/assets/configs/featured_networks.json similarity index 100% rename from deploy/tools/envs-validator/test/assets/featured_networks.json rename to deploy/tools/envs-validator/test/assets/configs/featured_networks.json diff --git a/deploy/tools/envs-validator/test/assets/footer_links.json b/deploy/tools/envs-validator/test/assets/configs/footer_links.json similarity index 100% rename from deploy/tools/envs-validator/test/assets/footer_links.json rename to deploy/tools/envs-validator/test/assets/configs/footer_links.json diff --git a/deploy/tools/envs-validator/test/assets/marketplace_categories.json b/deploy/tools/envs-validator/test/assets/configs/marketplace_categories.json similarity index 100% rename from deploy/tools/envs-validator/test/assets/marketplace_categories.json rename to deploy/tools/envs-validator/test/assets/configs/marketplace_categories.json diff --git a/deploy/tools/envs-validator/test/assets/marketplace_config.json b/deploy/tools/envs-validator/test/assets/configs/marketplace_config.json similarity index 100% rename from deploy/tools/envs-validator/test/assets/marketplace_config.json rename to deploy/tools/envs-validator/test/assets/configs/marketplace_config.json diff --git a/deploy/tools/envs-validator/test/assets/configs/marketplace_security_reports.json b/deploy/tools/envs-validator/test/assets/configs/marketplace_security_reports.json new file mode 100644 index 0000000000..cf0f481ae3 --- /dev/null +++ b/deploy/tools/envs-validator/test/assets/configs/marketplace_security_reports.json @@ -0,0 +1,1073 @@ +[ + { + "appName": "paraswap", + "doc": "https://developers.paraswap.network/smart-contracts", + "chainsData": { + "1": { + "overallInfo": { + "verifiedNumber": 4, + "totalContractsNumber": 4, + "solidityScanContractsNumber": 4, + "securityScore": 77.41749999999999, + "issueSeverityDistribution": { + "critical": 5, + "gas": 58, + "high": 9, + "informational": 27, + "low": 41, + "medium": 5 + } + }, + "contractsData": [ + { + "address": "0xDEF171Fe48CF0115B1d80b88dc8eAB59176FEe57", + "isVerified": true, + "solidityScanReport": { + "connection_id": "", + "contract_address": "0xDEF171Fe48CF0115B1d80b88dc8eAB59176FEe57", + "contract_chain": "optimism", + "contract_platform": "blockscout", + "contract_url": "https://optimism.blockscout.com/address/0xDEF171Fe48CF0115B1d80b88dc8eAB59176FEe57", + "contractname": "AugustusSwapper", + "is_quick_scan": true, + "node_reference_id": null, + "request_type": "threat_scan", + "scanner_reference_url": "https://solidityscan.com/quickscan/0xDEF171Fe48CF0115B1d80b88dc8eAB59176FEe57/blockscout/eth?ref=blockscout", + "scan_status": "scan_done", + "scan_summary": { + "issue_severity_distribution": { + "critical": 0, + "gas": 8, + "high": 4, + "informational": 7, + "low": 8, + "medium": 1 + }, + "lines_analyzed_count": 180, + "scan_time_taken": 1, + "score": "3.61", + "score_v2": "72.22", + "threat_score": "73.68" + } + } + }, + { + "address": "0x216b4b4ba9f3e719726886d34a177484278bfcae", + "isVerified": true, + "solidityScanReport": { + "connection_id": "", + "contract_address": "0x216b4b4ba9f3e719726886d34a177484278bfcae", + "contract_chain": "eth", + "contract_platform": "blockscout", + "contract_url": "https://eth.blockscout.com/address/0x216b4b4ba9f3e719726886d34a177484278bfcae", + "contractname": "TokenTransferProxy", + "is_quick_scan": true, + "node_reference_id": null, + "request_type": "threat_scan", + "scanner_reference_url": "https://solidityscan.com/quickscan/0x216b4b4ba9f3e719726886d34a177484278bfcae/blockscout/eth?ref=blockscout", + "scan_status": "scan_done", + "scan_summary": { + "issue_severity_distribution": { + "critical": 1, + "gas": 29, + "high": 5, + "informational": 14, + "low": 21, + "medium": 3 + }, + "lines_analyzed_count": 553, + "scan_time_taken": 1, + "score": "3.92", + "score_v2": "78.48", + "threat_score": "78.95" + } + } + }, + { + "address": "0xa68bEA62Dc4034A689AA0F58A76681433caCa663", + "isVerified": true, + "solidityScanReport": { + "connection_id": "", + "contract_address": "0xa68bEA62Dc4034A689AA0F58A76681433caCa663", + "contract_chain": "eth", + "contract_platform": "blockscout", + "contract_url": "https://eth.blockscout.com/address/0xa68bEA62Dc4034A689AA0F58A76681433caCa663", + "contractname": "AugustusRegistry", + "is_quick_scan": true, + "node_reference_id": null, + "request_type": "threat_scan", + "scanner_reference_url": "https://solidityscan.com/quickscan/0xa68bEA62Dc4034A689AA0F58A76681433caCa663/blockscout/eth?ref=blockscout", + "scan_status": "scan_done", + "scan_summary": { + "issue_severity_distribution": { + "critical": 0, + "gas": 3, + "high": 0, + "informational": 5, + "low": 4, + "medium": 0 + }, + "lines_analyzed_count": 103, + "scan_time_taken": 0, + "score": "4.22", + "score_v2": "84.47", + "threat_score": "88.89" + } + } + }, + { + "address": "0xeF13101C5bbD737cFb2bF00Bbd38c626AD6952F7", + "isVerified": true, + "solidityScanReport": { + "connection_id": "", + "contract_address": "0xeF13101C5bbD737cFb2bF00Bbd38c626AD6952F7", + "contract_chain": "eth", + "contract_platform": "blockscout", + "contract_url": "https://eth.blockscout.com/address/0xeF13101C5bbD737cFb2bF00Bbd38c626AD6952F7", + "contractname": "FeeClaimer", + "is_quick_scan": true, + "node_reference_id": null, + "request_type": "threat_scan", + "scanner_reference_url": "https://solidityscan.com/quickscan/0xeF13101C5bbD737cFb2bF00Bbd38c626AD6952F7/blockscout/eth?ref=blockscout", + "scan_status": "scan_done", + "scan_summary": { + "issue_severity_distribution": { + "critical": 0, + "gas": 18, + "high": 0, + "informational": 1, + "low": 8, + "medium": 1 + }, + "lines_analyzed_count": 149, + "scan_time_taken": 0, + "score": "3.72", + "score_v2": "74.50", + "threat_score": "94.74" + } + } + } + ] + }, + "10": { + "overallInfo": { + "verifiedNumber": 3, + "totalContractsNumber": 4, + "solidityScanContractsNumber": 3, + "securityScore": 75.44333333333333, + "issueSeverityDistribution": { + "critical": 4, + "gas": 29, + "high": 4, + "informational": 20, + "low": 20, + "medium": 2 + } + }, + "contractsData": [ + { + "address": "0xDEF171Fe48CF0115B1d80b88dc8eAB59176FEe57", + "isVerified": true, + "solidityScanReport": { + "connection_id": "", + "contract_address": "0xDEF171Fe48CF0115B1d80b88dc8eAB59176FEe57", + "contract_chain": "optimism", + "contract_platform": "blockscout", + "contract_url": "https://optimism.blockscout.com/address/0xDEF171Fe48CF0115B1d80b88dc8eAB59176FEe57", + "contractname": "AugustusSwapper", + "is_quick_scan": true, + "node_reference_id": null, + "request_type": "threat_scan", + "scanner_reference_url": "https://solidityscan.com/quickscan/0xDEF171Fe48CF0115B1d80b88dc8eAB59176FEe57/blockscout/optimism?ref=blockscout", + "scan_status": "scan_done", + "scan_summary": { + "issue_severity_distribution": { + "critical": 0, + "gas": 8, + "high": 4, + "informational": 7, + "low": 8, + "medium": 1 + }, + "lines_analyzed_count": 180, + "scan_time_taken": 1, + "score": "3.61", + "score_v2": "72.22", + "threat_score": "73.68" + } + } + }, + { + "address": "0x216B4B4Ba9F3e719726886d34a177484278Bfcae", + "isVerified": false, + "solidityScanReport": null + }, + { + "address": "0x6e7bE86000dF697facF4396efD2aE2C322165dC3", + "isVerified": true, + "solidityScanReport": { + "connection_id": "", + "contract_address": "0x6e7bE86000dF697facF4396efD2aE2C322165dC3", + "contract_chain": "optimism", + "contract_platform": "blockscout", + "contract_url": "https://optimism.blockscout.com/address/0x6e7bE86000dF697facF4396efD2aE2C322165dC3", + "contractname": "AugustusRegistry", + "is_quick_scan": true, + "node_reference_id": null, + "request_type": "threat_scan", + "scanner_reference_url": "https://solidityscan.com/quickscan/0x6e7bE86000dF697facF4396efD2aE2C322165dC3/blockscout/optimism?ref=blockscout", + "scan_status": "scan_done", + "scan_summary": { + "issue_severity_distribution": { + "critical": 0, + "gas": 3, + "high": 0, + "informational": 5, + "low": 4, + "medium": 0 + }, + "lines_analyzed_count": 102, + "scan_time_taken": 0, + "score": "4.22", + "score_v2": "84.31", + "threat_score": "88.89" + } + } + }, + { + "address": "0xA7465CCD97899edcf11C56D2d26B49125674e45F", + "isVerified": true, + "solidityScanReport": { + "connection_id": "", + "contract_address": "0xA7465CCD97899edcf11C56D2d26B49125674e45F", + "contract_chain": "optimism", + "contract_platform": "blockscout", + "contract_url": "https://optimism.blockscout.com/address/0xA7465CCD97899edcf11C56D2d26B49125674e45F", + "contractname": "FeeClaimer", + "is_quick_scan": true, + "node_reference_id": null, + "request_type": "threat_scan", + "scanner_reference_url": "https://solidityscan.com/quickscan/0xA7465CCD97899edcf11C56D2d26B49125674e45F/blockscout/optimism?ref=blockscout", + "scan_status": "scan_done", + "scan_summary": { + "issue_severity_distribution": { + "critical": 0, + "gas": 18, + "high": 0, + "informational": 8, + "low": 8, + "medium": 1 + }, + "lines_analyzed_count": 149, + "scan_time_taken": 1, + "score": "3.49", + "score_v2": "69.80", + "threat_score": "94.74" + } + } + } + ] + }, + "8453": { + "overallInfo": { + "verifiedNumber": 1, + "totalContractsNumber": 4, + "solidityScanContractsNumber": 1, + "securityScore": 73.33, + "issueSeverityDistribution": { + "critical": 4, + "gas": 8, + "high": 4, + "informational": 5, + "low": 8, + "medium": 1 + } + }, + "contractsData": [ + { + "address": "0x59C7C832e96D2568bea6db468C1aAdcbbDa08A52", + "isVerified": true, + "solidityScanReport": { + "connection_id": "", + "contract_address": "0x59C7C832e96D2568bea6db468C1aAdcbbDa08A52", + "contract_chain": "base", + "contract_platform": "blockscout", + "contract_url": "https://base.blockscout.com/address/0x59C7C832e96D2568bea6db468C1aAdcbbDa08A52", + "contractname": "AugustusSwapper", + "is_quick_scan": true, + "node_reference_id": null, + "request_type": "threat_scan", + "scanner_reference_url": "https://solidityscan.com/quickscan/0x59C7C832e96D2568bea6db468C1aAdcbbDa08A52/blockscout/base?ref=blockscout", + "scan_status": "scan_done", + "scan_summary": { + "issue_severity_distribution": { + "critical": 0, + "gas": 8, + "high": 4, + "informational": 5, + "low": 8, + "medium": 1 + }, + "lines_analyzed_count": 180, + "scan_time_taken": 1, + "score": "3.67", + "score_v2": "73.33", + "threat_score": "73.68" + } + } + }, + { + "address": "0x93aAAe79a53759cD164340E4C8766E4Db5331cD7", + "isVerified": false, + "solidityScanReport": null + }, + { + "address": "0x7e31b336f9e8ba52ba3c4ac861b033ba90900bb3", + "isVerified": false, + "solidityScanReport": null + }, + { + "address": "0x9aaB4B24541af30fD72784ED98D8756ac0eFb3C7", + "isVerified": false, + "solidityScanReport": null + } + ] + } + } + }, + { + "appName": "mean-finance", + "doc": "https://docs.mean.finance/guides/smart-contract-registry", + "chainsData": { + "1": { + "overallInfo": { + "verifiedNumber": 4, + "totalContractsNumber": 6, + "solidityScanContractsNumber": 4, + "securityScore": 61.36750000000001, + "issueSeverityDistribution": { + "critical": 6, + "gas": 25, + "high": 1, + "informational": 10, + "low": 20, + "medium": 3 + } + }, + "contractsData": [ + { + "address": "0xA5AdC5484f9997fBF7D405b9AA62A7d88883C345", + "isVerified": false, + "solidityScanReport": null + }, + { + "address": "0x20bdAE1413659f47416f769a4B27044946bc9923", + "isVerified": true, + "solidityScanReport": { + "connection_id": "", + "contract_address": "0x20bdAE1413659f47416f769a4B27044946bc9923", + "contract_chain": "optimism", + "contract_platform": "blockscout", + "contract_url": "https://optimism.blockscout.com/address/0x20bdAE1413659f47416f769a4B27044946bc9923", + "contractname": "DCAPermissionsManager", + "is_quick_scan": true, + "node_reference_id": null, + "request_type": "threat_scan", + "scanner_reference_url": "https://solidityscan.com/quickscan/0x20bdAE1413659f47416f769a4B27044946bc9923/blockscout/eth?ref=blockscout", + "scan_status": "scan_done", + "scan_summary": { + "issue_severity_distribution": { + "critical": 2, + "gas": 22, + "high": 0, + "informational": 8, + "low": 11, + "medium": 3 + }, + "lines_analyzed_count": 314, + "scan_time_taken": 1, + "score": "3.87", + "score_v2": "77.39", + "threat_score": "88.89" + } + } + }, + { + "address": "0xDf0dbc66f85979a1d54671c4D9e439F306Be27EE", + "isVerified": false, + "solidityScanReport": null + }, + { + "address": "0x4ACd4BC402bc8e6BA8aBDdcA639d8011ef0b8a4b", + "isVerified": true, + "solidityScanReport": { + "connection_id": "", + "contract_address": "0x4ACd4BC402bc8e6BA8aBDdcA639d8011ef0b8a4b", + "contract_chain": "eth", + "contract_platform": "blockscout", + "contract_url": "https://eth.blockscout.com/address/0x4ACd4BC402bc8e6BA8aBDdcA639d8011ef0b8a4b", + "contractname": "DCAHubPositionDescriptor", + "is_quick_scan": true, + "node_reference_id": null, + "request_type": "threat_scan", + "scanner_reference_url": "https://solidityscan.com/quickscan/0x4ACd4BC402bc8e6BA8aBDdcA639d8011ef0b8a4b/blockscout/eth?ref=blockscout", + "scan_status": "scan_done", + "scan_summary": { + "issue_severity_distribution": { + "critical": 0, + "gas": 1, + "high": 1, + "informational": 2, + "low": 3, + "medium": 0 + }, + "lines_analyzed_count": 280, + "scan_time_taken": 1, + "score": "4.77", + "score_v2": "95.36", + "threat_score": "100.00" + } + } + }, + { + "address": "0x49c590F6a2dfB0f809E82B9e2BF788C0Dd1c31f9", + "isVerified": true, + "solidityScanReport": { + "connection_id": "", + "contract_address": "0x49c590F6a2dfB0f809E82B9e2BF788C0Dd1c31f9", + "contract_chain": "eth", + "contract_platform": "blockscout", + "contract_url": "https://eth.blockscout.com/address/0x49c590F6a2dfB0f809E82B9e2BF788C0Dd1c31f9", + "contractname": "DCAHubCompanion", + "is_quick_scan": true, + "node_reference_id": null, + "request_type": "threat_scan", + "scanner_reference_url": "https://solidityscan.com/quickscan/0x49c590F6a2dfB0f809E82B9e2BF788C0Dd1c31f9/blockscout/eth?ref=blockscout", + "scan_status": "scan_done", + "scan_summary": { + "issue_severity_distribution": { + "critical": 0, + "gas": 1, + "high": 0, + "informational": 0, + "low": 3, + "medium": 0 + }, + "lines_analyzed_count": 11, + "scan_time_taken": 0, + "score": "1.82", + "score_v2": "36.36", + "threat_score": "100.00" + } + } + }, + { + "address": "0x5ad2fED59E8DF461c6164c31B4267Efb7cBaF9C0", + "isVerified": true, + "solidityScanReport": { + "connection_id": "", + "contract_address": "0x5ad2fED59E8DF461c6164c31B4267Efb7cBaF9C0", + "contract_chain": "eth", + "contract_platform": "blockscout", + "contract_url": "https://eth.blockscout.com/address/0x5ad2fED59E8DF461c6164c31B4267Efb7cBaF9C0", + "contractname": "DCAHubCompanion", + "is_quick_scan": true, + "node_reference_id": null, + "request_type": "threat_scan", + "scanner_reference_url": "https://solidityscan.com/quickscan/0x5ad2fED59E8DF461c6164c31B4267Efb7cBaF9C0/blockscout/eth?ref=blockscout", + "scan_status": "scan_done", + "scan_summary": { + "issue_severity_distribution": { + "critical": 0, + "gas": 1, + "high": 0, + "informational": 0, + "low": 3, + "medium": 0 + }, + "lines_analyzed_count": 11, + "scan_time_taken": 0, + "score": "1.82", + "score_v2": "36.36", + "threat_score": "100.00" + } + } + } + ] + }, + "10": { + "overallInfo": { + "verifiedNumber": 5, + "totalContractsNumber": 6, + "solidityScanContractsNumber": 5, + "securityScore": 66.986, + "issueSeverityDistribution": { + "critical": 6, + "gas": 26, + "high": 1, + "informational": 10, + "low": 23, + "medium": 3 + } + }, + "contractsData": [ + { + "address": "0xA5AdC5484f9997fBF7D405b9AA62A7d88883C345", + "isVerified": true, + "solidityScanReport": { + "connection_id": "", + "contract_address": "0xA5AdC5484f9997fBF7D405b9AA62A7d88883C345", + "contract_chain": "optimism", + "contract_platform": "blockscout", + "contract_url": "https://optimism.blockscout.com/address/0xA5AdC5484f9997fBF7D405b9AA62A7d88883C345", + "contractname": "DCAHub", + "is_quick_scan": true, + "node_reference_id": null, + "request_type": "threat_scan", + "scanner_reference_url": "https://solidityscan.com/quickscan/0xA5AdC5484f9997fBF7D405b9AA62A7d88883C345/blockscout/optimism?ref=blockscout", + "scan_status": "scan_done", + "scan_summary": { + "issue_severity_distribution": { + "critical": 0, + "gas": 1, + "high": 0, + "informational": 0, + "low": 3, + "medium": 0 + }, + "lines_analyzed_count": 23, + "scan_time_taken": 0, + "score": "3.48", + "score_v2": "69.57", + "threat_score": "94.44" + } + } + }, + { + "address": "0x20bdAE1413659f47416f769a4B27044946bc9923", + "isVerified": true, + "solidityScanReport": { + "connection_id": "", + "contract_address": "0x20bdAE1413659f47416f769a4B27044946bc9923", + "contract_chain": "optimism", + "contract_platform": "blockscout", + "contract_url": "https://optimism.blockscout.com/address/0x20bdAE1413659f47416f769a4B27044946bc9923", + "contractname": "DCAPermissionsManager", + "is_quick_scan": true, + "node_reference_id": null, + "request_type": "threat_scan", + "scanner_reference_url": "https://solidityscan.com/quickscan/0x20bdAE1413659f47416f769a4B27044946bc9923/blockscout/optimism?ref=blockscout", + "scan_status": "scan_done", + "scan_summary": { + "issue_severity_distribution": { + "critical": 2, + "gas": 22, + "high": 0, + "informational": 8, + "low": 11, + "medium": 3 + }, + "lines_analyzed_count": 314, + "scan_time_taken": 1, + "score": "3.87", + "score_v2": "77.39", + "threat_score": "88.89" + } + } + }, + { + "address": "0xDf0dbc66f85979a1d54671c4D9e439F306Be27EE", + "isVerified": true, + "solidityScanReport": { + "connection_id": "", + "contract_address": "0xDf0dbc66f85979a1d54671c4D9e439F306Be27EE", + "contract_chain": "optimism", + "contract_platform": "blockscout", + "contract_url": "https://optimism.blockscout.com/address/0xDf0dbc66f85979a1d54671c4D9e439F306Be27EE", + "contractname": "DCAHubCompanion", + "is_quick_scan": true, + "node_reference_id": null, + "request_type": "threat_scan", + "scanner_reference_url": "https://solidityscan.com/quickscan/0xDf0dbc66f85979a1d54671c4D9e439F306Be27EE/blockscout/optimism?ref=blockscout", + "scan_status": "scan_done", + "scan_summary": { + "issue_severity_distribution": { + "critical": 0, + "gas": 1, + "high": 0, + "informational": 0, + "low": 3, + "medium": 0 + }, + "lines_analyzed_count": 16, + "scan_time_taken": 0, + "score": "2.81", + "score_v2": "56.25", + "threat_score": "100.00" + } + } + }, + { + "address": "0x4ACd4BC402bc8e6BA8aBDdcA639d8011ef0b8a4b", + "isVerified": true, + "solidityScanReport": { + "connection_id": "", + "contract_address": "0x4ACd4BC402bc8e6BA8aBDdcA639d8011ef0b8a4b", + "contract_chain": "eth", + "contract_platform": "blockscout", + "contract_url": "https://eth.blockscout.com/address/0x4ACd4BC402bc8e6BA8aBDdcA639d8011ef0b8a4b", + "contractname": "DCAHubPositionDescriptor", + "is_quick_scan": true, + "node_reference_id": null, + "request_type": "threat_scan", + "scanner_reference_url": "https://solidityscan.com/quickscan/0x4ACd4BC402bc8e6BA8aBDdcA639d8011ef0b8a4b/blockscout/optimism?ref=blockscout", + "scan_status": "scan_done", + "scan_summary": { + "issue_severity_distribution": { + "critical": 0, + "gas": 1, + "high": 1, + "informational": 2, + "low": 3, + "medium": 0 + }, + "lines_analyzed_count": 280, + "scan_time_taken": 1, + "score": "4.77", + "score_v2": "95.36", + "threat_score": "100.00" + } + } + }, + { + "address": "0x49c590F6a2dfB0f809E82B9e2BF788C0Dd1c31f9", + "isVerified": true, + "solidityScanReport": { + "connection_id": "", + "contract_address": "0x49c590F6a2dfB0f809E82B9e2BF788C0Dd1c31f9", + "contract_chain": "optimism", + "contract_platform": "blockscout", + "contract_url": "https://optimism.blockscout.com/address/0x49c590F6a2dfB0f809E82B9e2BF788C0Dd1c31f9", + "contractname": "DCAHubCompanion", + "is_quick_scan": true, + "node_reference_id": null, + "request_type": "threat_scan", + "scanner_reference_url": "https://solidityscan.com/quickscan/0x49c590F6a2dfB0f809E82B9e2BF788C0Dd1c31f9/blockscout/optimism?ref=blockscout", + "scan_status": "scan_done", + "scan_summary": { + "issue_severity_distribution": { + "critical": 0, + "gas": 1, + "high": 0, + "informational": 0, + "low": 3, + "medium": 0 + }, + "lines_analyzed_count": 11, + "scan_time_taken": 0, + "score": "1.82", + "score_v2": "36.36", + "threat_score": "100.00" + } + } + }, + { + "address": "0x5ad2fED59E8DF461c6164c31B4267Efb7cBaF9C0", + "isVerified": false, + "solidityScanReport": null + } + ] + }, + "8453": { + "overallInfo": { + "verifiedNumber": 4, + "totalContractsNumber": 6, + "solidityScanContractsNumber": 4, + "securityScore": 74.88, + "issueSeverityDistribution": { + "critical": 6, + "gas": 25, + "high": 1, + "informational": 7, + "low": 20, + "medium": 3 + } + }, + "contractsData": [ + { + "address": "0xA5AdC5484f9997fBF7D405b9AA62A7d88883C345", + "isVerified": true, + "solidityScanReport": { + "connection_id": "", + "contract_address": "0xA5AdC5484f9997fBF7D405b9AA62A7d88883C345", + "contract_chain": "base", + "contract_platform": "blockscout", + "contract_url": "https://base.blockscout.com/address/0xA5AdC5484f9997fBF7D405b9AA62A7d88883C345", + "contractname": "DCAHub", + "is_quick_scan": true, + "node_reference_id": null, + "request_type": "threat_scan", + "scanner_reference_url": "https://solidityscan.com/quickscan/0xA5AdC5484f9997fBF7D405b9AA62A7d88883C345/blockscout/base?ref=blockscout", + "scan_status": "scan_done", + "scan_summary": { + "issue_severity_distribution": { + "critical": 0, + "gas": 1, + "high": 0, + "informational": 0, + "low": 3, + "medium": 0 + }, + "lines_analyzed_count": 23, + "scan_time_taken": 0, + "score": "3.48", + "score_v2": "69.57", + "threat_score": "94.44" + } + } + }, + { + "address": "0x20bdAE1413659f47416f769a4B27044946bc9923", + "isVerified": true, + "solidityScanReport": { + "connection_id": "", + "contract_address": "0x20bdAE1413659f47416f769a4B27044946bc9923", + "contract_chain": "base", + "contract_platform": "blockscout", + "contract_url": "https://base.blockscout.com/address/0x20bdAE1413659f47416f769a4B27044946bc9923", + "contractname": "DCAPermissionsManager", + "is_quick_scan": true, + "node_reference_id": null, + "request_type": "threat_scan", + "scanner_reference_url": "https://solidityscan.com/quickscan/0x20bdAE1413659f47416f769a4B27044946bc9923/blockscout/base?ref=blockscout", + "scan_status": "scan_done", + "scan_summary": { + "issue_severity_distribution": { + "critical": 2, + "gas": 22, + "high": 0, + "informational": 5, + "low": 11, + "medium": 3 + }, + "lines_analyzed_count": 314, + "scan_time_taken": 1, + "score": "3.92", + "score_v2": "78.34", + "threat_score": "88.89" + } + } + }, + { + "address": "0xDf0dbc66f85979a1d54671c4D9e439F306Be27EE", + "isVerified": true, + "solidityScanReport": { + "connection_id": "", + "contract_address": "0xDf0dbc66f85979a1d54671c4D9e439F306Be27EE", + "contract_chain": "base", + "contract_platform": "blockscout", + "contract_url": "https://base.blockscout.com/address/0xDf0dbc66f85979a1d54671c4D9e439F306Be27EE", + "contractname": "DCAHubCompanion", + "is_quick_scan": true, + "node_reference_id": null, + "request_type": "threat_scan", + "scanner_reference_url": "https://solidityscan.com/quickscan/0xDf0dbc66f85979a1d54671c4D9e439F306Be27EE/blockscout/base?ref=blockscout", + "scan_status": "scan_done", + "scan_summary": { + "issue_severity_distribution": { + "critical": 0, + "gas": 1, + "high": 0, + "informational": 0, + "low": 3, + "medium": 0 + }, + "lines_analyzed_count": 16, + "scan_time_taken": 0, + "score": "2.81", + "score_v2": "56.25", + "threat_score": "100.00" + } + } + }, + { + "address": "0x4ACd4BC402bc8e6BA8aBDdcA639d8011ef0b8a4b", + "isVerified": true, + "solidityScanReport": { + "connection_id": "", + "contract_address": "0x4ACd4BC402bc8e6BA8aBDdcA639d8011ef0b8a4b", + "contract_chain": "eth", + "contract_platform": "blockscout", + "contract_url": "https://eth.blockscout.com/address/0x4ACd4BC402bc8e6BA8aBDdcA639d8011ef0b8a4b", + "contractname": "DCAHubPositionDescriptor", + "is_quick_scan": true, + "node_reference_id": null, + "request_type": "threat_scan", + "scanner_reference_url": "https://solidityscan.com/quickscan/0x4ACd4BC402bc8e6BA8aBDdcA639d8011ef0b8a4b/blockscout/base?ref=blockscout", + "scan_status": "scan_done", + "scan_summary": { + "issue_severity_distribution": { + "critical": 0, + "gas": 1, + "high": 1, + "informational": 2, + "low": 3, + "medium": 0 + }, + "lines_analyzed_count": 280, + "scan_time_taken": 1, + "score": "4.77", + "score_v2": "95.36", + "threat_score": "100.00" + } + } + }, + { + "address": "0x49c590F6a2dfB0f809E82B9e2BF788C0Dd1c31f9", + "isVerified": false, + "solidityScanReport": null + }, + { + "address": "0x5ad2fED59E8DF461c6164c31B4267Efb7cBaF9C0", + "isVerified": false, + "solidityScanReport": null + } + ] + } + } + }, + { + "appName": "cow-swap", + "doc": "https://docs.cow.fi/cow-protocol/reference/contracts/core#deployments", + "chainsData": { + "1": { + "overallInfo": { + "verifiedNumber": 3, + "totalContractsNumber": 3, + "solidityScanContractsNumber": 3, + "securityScore": 87.60000000000001, + "issueSeverityDistribution": { + "critical": 4, + "gas": 18, + "high": 0, + "informational": 13, + "low": 14, + "medium": 3 + } + }, + "contractsData": [ + { + "address": "0x9008D19f58AAbD9eD0D60971565AA8510560ab41", + "isVerified": true, + "solidityScanReport": { + "connection_id": "", + "contract_address": "0x9008D19f58AAbD9eD0D60971565AA8510560ab41", + "contract_chain": "eth", + "contract_platform": "blockscout", + "contract_url": "https://eth.blockscout.com/address/0x9008D19f58AAbD9eD0D60971565AA8510560ab41", + "contractname": "GPv2Settlement", + "is_quick_scan": true, + "node_reference_id": null, + "request_type": "threat_scan", + "scanner_reference_url": "https://solidityscan.com/quickscan/0x9008D19f58AAbD9eD0D60971565AA8510560ab41/blockscout/eth?ref=blockscout", + "scan_status": "scan_done", + "scan_summary": { + "issue_severity_distribution": { + "critical": 0, + "gas": 16, + "high": 0, + "informational": 7, + "low": 5, + "medium": 3 + }, + "lines_analyzed_count": 493, + "scan_time_taken": 1, + "score": "4.57", + "score_v2": "91.48", + "threat_score": "94.74" + } + } + }, + { + "address": "0x2c4c28DDBdAc9C5E7055b4C863b72eA0149D8aFE", + "isVerified": true, + "solidityScanReport": { + "connection_id": "", + "contract_address": "0x2c4c28DDBdAc9C5E7055b4C863b72eA0149D8aFE", + "contract_chain": "eth", + "contract_platform": "blockscout", + "contract_url": "https://eth.blockscout.com/address/0x2c4c28DDBdAc9C5E7055b4C863b72eA0149D8aFE", + "contractname": "EIP173Proxy", + "is_quick_scan": true, + "node_reference_id": null, + "request_type": "threat_scan", + "scanner_reference_url": "https://solidityscan.com/quickscan/0x2c4c28DDBdAc9C5E7055b4C863b72eA0149D8aFE/blockscout/eth?ref=blockscout", + "scan_status": "scan_done", + "scan_summary": { + "issue_severity_distribution": { + "critical": 0, + "gas": 0, + "high": 0, + "informational": 4, + "low": 5, + "medium": 0 + }, + "lines_analyzed_count": 94, + "scan_time_taken": 0, + "score": "4.26", + "score_v2": "85.11", + "threat_score": "88.89" + } + } + }, + { + "address": "0xC92E8bdf79f0507f65a392b0ab4667716BFE0110", + "isVerified": true, + "solidityScanReport": { + "connection_id": "", + "contract_address": "0xC92E8bdf79f0507f65a392b0ab4667716BFE0110", + "contract_chain": "eth", + "contract_platform": "blockscout", + "contract_url": "https://eth.blockscout.com/address/0xC92E8bdf79f0507f65a392b0ab4667716BFE0110", + "contractname": "GPv2VaultRelayer", + "is_quick_scan": true, + "node_reference_id": null, + "request_type": "threat_scan", + "scanner_reference_url": "https://solidityscan.com/quickscan/0xC92E8bdf79f0507f65a392b0ab4667716BFE0110/blockscout/eth?ref=blockscout", + "scan_status": "scan_done", + "scan_summary": { + "issue_severity_distribution": { + "critical": 0, + "gas": 2, + "high": 0, + "informational": 2, + "low": 4, + "medium": 0 + }, + "lines_analyzed_count": 87, + "scan_time_taken": 0, + "score": "4.31", + "score_v2": "86.21", + "threat_score": "94.74" + } + } + } + ] + }, + "100": { + "overallInfo": { + "verifiedNumber": 3, + "totalContractsNumber": 3, + "solidityScanContractsNumber": 3, + "securityScore": 87.60000000000001, + "issueSeverityDistribution": { + "critical": 4, + "gas": 18, + "high": 0, + "informational": 13, + "low": 14, + "medium": 3 + } + }, + "contractsData": [ + { + "address": "0x9008D19f58AAbD9eD0D60971565AA8510560ab41", + "isVerified": true, + "solidityScanReport": { + "connection_id": "", + "contract_address": "0x9008D19f58AAbD9eD0D60971565AA8510560ab41", + "contract_chain": "gnosis", + "contract_platform": "blockscout", + "contract_url": "https://gnosis.blockscout.com/address/0x9008D19f58AAbD9eD0D60971565AA8510560ab41", + "contractname": "GPv2Settlement", + "is_quick_scan": true, + "node_reference_id": null, + "request_type": "threat_scan", + "scanner_reference_url": "https://solidityscan.com/quickscan/0x9008D19f58AAbD9eD0D60971565AA8510560ab41/blockscout/gnosis?ref=blockscout", + "scan_status": "scan_done", + "scan_summary": { + "issue_severity_distribution": { + "critical": 0, + "gas": 16, + "high": 0, + "informational": 7, + "low": 5, + "medium": 3 + }, + "lines_analyzed_count": 493, + "scan_time_taken": 1, + "score": "4.57", + "score_v2": "91.48", + "threat_score": "94.74" + } + } + }, + { + "address": "0x2c4c28DDBdAc9C5E7055b4C863b72eA0149D8aFE", + "isVerified": true, + "solidityScanReport": { + "connection_id": "", + "contract_address": "0x2c4c28DDBdAc9C5E7055b4C863b72eA0149D8aFE", + "contract_chain": "eth", + "contract_platform": "blockscout", + "contract_url": "https://eth.blockscout.com/address/0x2c4c28DDBdAc9C5E7055b4C863b72eA0149D8aFE", + "contractname": "EIP173Proxy", + "is_quick_scan": true, + "node_reference_id": null, + "request_type": "threat_scan", + "scanner_reference_url": "https://solidityscan.com/quickscan/0x2c4c28DDBdAc9C5E7055b4C863b72eA0149D8aFE/blockscout/gnosis?ref=blockscout", + "scan_status": "scan_done", + "scan_summary": { + "issue_severity_distribution": { + "critical": 0, + "gas": 0, + "high": 0, + "informational": 4, + "low": 5, + "medium": 0 + }, + "lines_analyzed_count": 94, + "scan_time_taken": 0, + "score": "4.26", + "score_v2": "85.11", + "threat_score": "88.89" + } + } + }, + { + "address": "0xC92E8bdf79f0507f65a392b0ab4667716BFE0110", + "isVerified": true, + "solidityScanReport": { + "connection_id": "", + "contract_address": "0xC92E8bdf79f0507f65a392b0ab4667716BFE0110", + "contract_chain": "eth", + "contract_platform": "blockscout", + "contract_url": "https://eth.blockscout.com/address/0xC92E8bdf79f0507f65a392b0ab4667716BFE0110", + "contractname": "GPv2VaultRelayer", + "is_quick_scan": true, + "node_reference_id": null, + "request_type": "threat_scan", + "scanner_reference_url": "https://solidityscan.com/quickscan/0xC92E8bdf79f0507f65a392b0ab4667716BFE0110/blockscout/gnosis?ref=blockscout", + "scan_status": "scan_done", + "scan_summary": { + "issue_severity_distribution": { + "critical": 0, + "gas": 2, + "high": 0, + "informational": 2, + "low": 4, + "medium": 0 + }, + "lines_analyzed_count": 87, + "scan_time_taken": 0, + "score": "4.31", + "score_v2": "86.21", + "threat_score": "94.74" + } + } + } + ] + } + } + } +] diff --git a/deploy/tools/envs-validator/yarn.lock b/deploy/tools/envs-validator/yarn.lock index ab1bf0f2b4..8259f31c56 100644 --- a/deploy/tools/envs-validator/yarn.lock +++ b/deploy/tools/envs-validator/yarn.lock @@ -21,6 +21,11 @@ resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz#2203b118c157721addfe69d47b70465463066d78" integrity sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w== +"@jridgewell/resolve-uri@^3.1.0": + version "3.1.2" + resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz#7a0ee601f60f99a20c7c7c5ff0c80388c1189bd6" + integrity sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw== + "@jridgewell/set-array@^1.0.1": version "1.1.2" resolved "https://registry.yarnpkg.com/@jridgewell/set-array/-/set-array-1.1.2.tgz#7c6cf998d6d20b914c0a55a91ae928ff25965e72" @@ -44,7 +49,20 @@ resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz#d7c6e6755c78567a951e04ab52ef0fd26de59f32" integrity sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg== -"@jridgewell/trace-mapping@^0.3.17", "@jridgewell/trace-mapping@^0.3.9": +"@jridgewell/sourcemap-codec@^1.4.14": + version "1.5.0" + resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz#3188bcb273a414b0d215fd22a58540b989b9409a" + integrity sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ== + +"@jridgewell/trace-mapping@^0.3.20": + version "0.3.25" + resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz#15f190e98895f3fc23276ee14bc76b675c2e50f0" + integrity sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ== + dependencies: + "@jridgewell/resolve-uri" "^3.1.0" + "@jridgewell/sourcemap-codec" "^1.4.14" + +"@jridgewell/trace-mapping@^0.3.9": version "0.3.18" resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.18.tgz#25783b2086daf6ff1dcb53c9249ae480e4dd4cd6" integrity sha512-w+niJYzMHdd7USdiH2U6869nqhD2nbfZXND5Yp93qIbEmnDNk7PD48o+YchRVpzMU7M6jVCbenTR7PA1FLQ9pA== @@ -52,28 +70,12 @@ "@jridgewell/resolve-uri" "3.1.0" "@jridgewell/sourcemap-codec" "1.4.14" -"@types/eslint-scope@^3.7.3": - version "3.7.4" - resolved "https://registry.yarnpkg.com/@types/eslint-scope/-/eslint-scope-3.7.4.tgz#37fc1223f0786c39627068a12e94d6e6fc61de16" - integrity sha512-9K4zoImiZc3HlIp6AVUDE4CWYx22a+lhSZMYNpbjW04+YF0KWj4pJXnEMjdnFTiQibFFmElcsasJXDbdI/EPhA== - dependencies: - "@types/eslint" "*" - "@types/estree" "*" - -"@types/eslint@*": - version "8.44.0" - resolved "https://registry.yarnpkg.com/@types/eslint/-/eslint-8.44.0.tgz#55818eabb376e2272f77fbf5c96c43137c3c1e53" - integrity sha512-gsF+c/0XOguWgaOgvFs+xnnRqt9GwgTvIks36WpE6ueeI4KCEHHd8K/CKHqhOqrJKsYH8m27kRzQEvWXAwXUTw== - dependencies: - "@types/estree" "*" - "@types/json-schema" "*" +"@types/estree@^1.0.5": + version "1.0.6" + resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.6.tgz#628effeeae2064a1b4e79f78e81d87b7e5fc7b50" + integrity sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw== -"@types/estree@*", "@types/estree@^1.0.0": - version "1.0.1" - resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.1.tgz#aa22750962f3bf0e79d753d3cc067f010c95f194" - integrity sha512-LG4opVs2ANWZ1TJoKc937iMmNstM/d0ae1vNbnBvBhqCSezgVUOzcLCqbI5elV8Vy6WKwKjaqR+zO9VKirBBCA== - -"@types/json-schema@*", "@types/json-schema@^7.0.8": +"@types/json-schema@^7.0.8": version "7.0.12" resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.12.tgz#d70faba7039d5fca54c83c7dbab41051d2b6f6cb" integrity sha512-Hr5Jfhc9eYOQNPYO5WLDq/n4jqijdHNlDXjuAQkkt+mWdQR+XJToOHrsD4cPaMXpn6KO7y2+wM8AZEs8VpBLVA== @@ -83,10 +85,10 @@ resolved "https://registry.yarnpkg.com/@types/node/-/node-20.4.2.tgz#129cc9ae69f93824f92fac653eebfb4812ab4af9" integrity sha512-Dd0BYtWgnWJKwO1jkmTrzofjK2QXXcai0dmtzvIBhcA+RsG5h8R3xlyta0kGOZRNfL9GuRtb1knmPEhQrePCEw== -"@webassemblyjs/ast@1.11.6", "@webassemblyjs/ast@^1.11.5": - version "1.11.6" - resolved "https://registry.yarnpkg.com/@webassemblyjs/ast/-/ast-1.11.6.tgz#db046555d3c413f8966ca50a95176a0e2c642e24" - integrity sha512-IN1xI7PwOvLPgjcf180gC1bqn3q/QaOCwYUahIOhbYUu8KA/3tw2RT/T0Gidi1l7Hhj5D/INhJxiICObqpMu4Q== +"@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" @@ -101,10 +103,10 @@ 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-buffer@1.11.6": - version "1.11.6" - resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-buffer/-/helper-buffer-1.11.6.tgz#b66d73c43e296fd5e88006f18524feb0f2c7c093" - integrity sha512-z3nFzdcp1mb8nEOFFk8DrYLpHvhKC3grJD2ardfKOzmbmJvEf/tPIqCY+sNcwZIY8ZD7IkB2l7/pqhUhqm7hLA== +"@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-numbers@1.11.6": version "1.11.6" @@ -120,15 +122,15 @@ resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.11.6.tgz#bb2ebdb3b83aa26d9baad4c46d4315283acd51e9" integrity sha512-sFFHKwcmBprO9e7Icf0+gddyWYDViL8bpPjJJl0WHxCdETktXdmtWLGVzoHbqUcY4Be1LkNfwTmXOJUFZYSJdA== -"@webassemblyjs/helper-wasm-section@1.11.6": - version "1.11.6" - resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.11.6.tgz#ff97f3863c55ee7f580fd5c41a381e9def4aa577" - integrity sha512-LPpZbSOwTpEC2cgn4hTydySy1Ke+XEu+ETXuoyvuyezHO3Kjdu90KK95Sh9xTbmjrCsUwvWwCOQQNta37VrS9g== +"@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.11.6" - "@webassemblyjs/helper-buffer" "1.11.6" + "@webassemblyjs/ast" "1.12.1" + "@webassemblyjs/helper-buffer" "1.12.1" "@webassemblyjs/helper-wasm-bytecode" "1.11.6" - "@webassemblyjs/wasm-gen" "1.11.6" + "@webassemblyjs/wasm-gen" "1.12.1" "@webassemblyjs/ieee754@1.11.6": version "1.11.6" @@ -149,59 +151,59 @@ resolved "https://registry.yarnpkg.com/@webassemblyjs/utf8/-/utf8-1.11.6.tgz#90f8bc34c561595fe156603be7253cdbcd0fab5a" integrity sha512-vtXf2wTQ3+up9Zsg8sa2yWiQpzSsMyXj0qViVP6xKGCUT8p8YJ6HqI7l5eCnWx1T/FYdsv07HQs2wTFbbof/RA== -"@webassemblyjs/wasm-edit@^1.11.5": - version "1.11.6" - resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-edit/-/wasm-edit-1.11.6.tgz#c72fa8220524c9b416249f3d94c2958dfe70ceab" - integrity sha512-Ybn2I6fnfIGuCR+Faaz7YcvtBKxvoLV3Lebn1tM4o/IAJzmi9AWYIPWpyBfU8cC+JxAO57bk4+zdsTjJR+VTOw== +"@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.11.6" - "@webassemblyjs/helper-buffer" "1.11.6" + "@webassemblyjs/ast" "1.12.1" + "@webassemblyjs/helper-buffer" "1.12.1" "@webassemblyjs/helper-wasm-bytecode" "1.11.6" - "@webassemblyjs/helper-wasm-section" "1.11.6" - "@webassemblyjs/wasm-gen" "1.11.6" - "@webassemblyjs/wasm-opt" "1.11.6" - "@webassemblyjs/wasm-parser" "1.11.6" - "@webassemblyjs/wast-printer" "1.11.6" - -"@webassemblyjs/wasm-gen@1.11.6": - version "1.11.6" - resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-gen/-/wasm-gen-1.11.6.tgz#fb5283e0e8b4551cc4e9c3c0d7184a65faf7c268" - integrity sha512-3XOqkZP/y6B4F0PBAXvI1/bky7GryoogUtfwExeP/v7Nzwo1QLcq5oQmpKlftZLbT+ERUOAZVQjuNVak6UXjPA== - dependencies: - "@webassemblyjs/ast" "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-opt@1.11.6": - version "1.11.6" - resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-opt/-/wasm-opt-1.11.6.tgz#d9a22d651248422ca498b09aa3232a81041487c2" - integrity sha512-cOrKuLRE7PCe6AsOVl7WasYf3wbSo4CeOk6PkrjS7g57MFfVUF9u6ysQBBODX0LdgSvQqRiGz3CXvIDKcPNy4g== +"@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.11.6" - "@webassemblyjs/helper-buffer" "1.11.6" - "@webassemblyjs/wasm-gen" "1.11.6" - "@webassemblyjs/wasm-parser" "1.11.6" + "@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-parser@1.11.6", "@webassemblyjs/wasm-parser@^1.11.5": - version "1.11.6" - resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-parser/-/wasm-parser-1.11.6.tgz#bb85378c527df824004812bbdb784eea539174a1" - integrity sha512-6ZwPeGzMJM3Dqp3hCsLgESxBGtT/OeCvCZ4TA1JUPYgmhAx38tTPR9JaKy0S5H3evQpO/h2uWs2j6Yc/fjkpTQ== +"@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.11.6" + "@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/wast-printer@1.11.6": - version "1.11.6" - resolved "https://registry.yarnpkg.com/@webassemblyjs/wast-printer/-/wast-printer-1.11.6.tgz#a7bf8dd7e362aeb1668ff43f35cb849f188eff20" - integrity sha512-JM7AhRcE+yW2GWYaKeHL5vt4xqee5N2WcezptmgyhNS+ScggqcT1OtXykhAb13Sn5Yas0j2uv9tHgrjwvzAP4A== +"@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.11.6" + "@webassemblyjs/ast" "1.12.1" "@xtuc/long" "4.2.2" "@webpack-cli/configtest@^2.1.1": @@ -229,10 +231,10 @@ resolved "https://registry.yarnpkg.com/@xtuc/long/-/long-4.2.2.tgz#d291c6a4e97989b5c61d9acf396ae4fe133a718d" integrity sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ== -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@^8.7.1, acorn@^8.8.2: version "8.10.0" @@ -261,32 +263,32 @@ ansi-styles@^4.1.0: dependencies: color-convert "^2.0.1" -braces@^3.0.2: - version "3.0.2" - resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107" - integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A== +braces@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.3.tgz#490332f40919452272d55a8480adc0c441358789" + integrity sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA== dependencies: - fill-range "^7.0.1" + fill-range "^7.1.1" -browserslist@^4.14.5: - version "4.21.9" - resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.21.9.tgz#e11bdd3c313d7e2a9e87e8b4b0c7872b13897635" - integrity sha512-M0MFoZzbUrRU4KNfCrDLnvyE7gub+peetoTid3TBIqtunaDJyXlwhakT+/VkvSXcfIzFfK/nkCs4nmyTmxdNSg== +browserslist@^4.21.10: + version "4.24.2" + resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.24.2.tgz#f5845bc91069dbd55ee89faf9822e1d885d16580" + integrity sha512-ZIc+Q62revdMcqC6aChtW4jz3My3klmCO1fEmINZY/8J3EpBg5/A/D0AKmBveUh6pgoeycoMkVMko84tuYS+Gg== dependencies: - caniuse-lite "^1.0.30001503" - electron-to-chromium "^1.4.431" - node-releases "^2.0.12" - update-browserslist-db "^1.0.11" + caniuse-lite "^1.0.30001669" + electron-to-chromium "^1.5.41" + node-releases "^2.0.18" + update-browserslist-db "^1.1.1" buffer-from@^1.0.0: version "1.1.2" resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.2.tgz#2b146a6fd72e80b4f55d255f35ed59a3a9a41bd5" integrity sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ== -caniuse-lite@^1.0.30001503: - version "1.0.30001517" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001517.tgz#90fabae294215c3495807eb24fc809e11dc2f0a8" - integrity sha512-Vdhm5S11DaFVLlyiKu4hiUTkpZu+y1KA/rZZqVQfOD5YdDT/eQKlkt7NaE0WGOFgX32diqt9MiP9CAiFeRklaA== +caniuse-lite@^1.0.30001669: + version "1.0.30001673" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001673.tgz#5aa291557af1c71340e809987367410aab7a5a9e" + integrity sha512-WTrjUCSMp3LYX0nE12ECkV0a+e6LC85E0Auz75555/qr78Oc8YWhEPNfDd6SHdtlCMSzqtuXY0uyEMNRcsKpKw== chalk@^4.1.0: version "4.1.2" @@ -366,12 +368,12 @@ dotenv@^16.0.0: resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-16.3.1.tgz#369034de7d7e5b120972693352a3bf112172cc3e" integrity sha512-IPzF4w4/Rd94bA9imS68tZBaYyBWSCE47V1RGuMrB94iyTOIEwRmVL2x/4An+6mETpLrKJ5hQkB8W4kFAadeIQ== -electron-to-chromium@^1.4.431: - version "1.4.467" - resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.467.tgz#b0660bf644baff7eedea33b8c742fb53ec60e3c2" - integrity sha512-2qI70O+rR4poYeF2grcuS/bCps5KJh6y1jtZMDDEteyKJQrzLOEhFyXCLcHW6DTBjKjWkk26JhWoAi+Ux9A0fg== +electron-to-chromium@^1.5.41: + version "1.5.47" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.5.47.tgz#ef0751bc19b28be8ee44cd8405309de3bf3b20c7" + integrity sha512-zS5Yer0MOYw4rtK2iq43cJagHZ8sXN0jDHDKzB+86gSBSAI4v07S97mcq+Gs2vclAxSh1j7vOAHxSVgduiiuVQ== -enhanced-resolve@^5.0.0, enhanced-resolve@^5.15.0, enhanced-resolve@^5.7.0: +enhanced-resolve@^5.0.0, enhanced-resolve@^5.7.0: version "5.15.0" resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-5.15.0.tgz#1af946c7d93603eb88e9896cee4904dc012e9c35" integrity sha512-LXYT42KJ7lpIKECr2mAXIaMldcNCh/7E0KBKOu4KSfkHmP+mZmSs+8V5gBAqisWBy0OO4W5Oyys0GO1Y8KtdKg== @@ -379,6 +381,14 @@ enhanced-resolve@^5.0.0, enhanced-resolve@^5.15.0, enhanced-resolve@^5.7.0: graceful-fs "^4.2.4" tapable "^2.2.0" +enhanced-resolve@^5.17.1: + version "5.17.1" + resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-5.17.1.tgz#67bfbbcc2f81d511be77d686a90267ef7f898a15" + integrity sha512-LMHl3dXhTcfv8gM4kEzIUeTQ+7fpdA0l2tUf34BddXPkz2A5xJ5L/Pchd5BL6rdccM9QGvu0sWZzK1Z1t4wwyg== + dependencies: + graceful-fs "^4.2.4" + tapable "^2.2.0" + envinfo@^7.7.3: version "7.10.0" resolved "https://registry.yarnpkg.com/envinfo/-/envinfo-7.10.0.tgz#55146e3909cc5fe63c22da63fb15b05aeac35b13" @@ -389,10 +399,10 @@ es-module-lexer@^1.2.1: resolved "https://registry.yarnpkg.com/es-module-lexer/-/es-module-lexer-1.3.0.tgz#6be9c9e0b4543a60cd166ff6f8b4e9dae0b0c16f" integrity sha512-vZK7T0N2CBmBOixhmjdqx2gWVbFZ4DXZ/NyRMZVlJXPa7CyFS+/a4QQsDGDQy9ZfEzxFuNEsMLeQJnKP2p5/JA== -escalade@^3.1.1: - version "3.1.1" - resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.1.tgz#d8cfdc7000965c5a0174b4a82eaa5c0552742e40" - integrity sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw== +escalade@^3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.2.0.tgz#011a3f69856ba189dffa7dc8fcce99d2a87903e5" + integrity sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA== eslint-scope@5.1.1: version "5.1.1" @@ -439,10 +449,10 @@ fastest-levenshtein@^1.0.12: resolved "https://registry.yarnpkg.com/fastest-levenshtein/-/fastest-levenshtein-1.0.16.tgz#210e61b6ff181de91ea9b3d1b84fdedd47e034e5" integrity sha512-eRnCtTTtGZFpQCwhJiUOuxPQWRXVKYDn0b2PeHfXL6/Zi53SLAzAHfVhVWK2AryC/WH05kGfxhFIPvTF0SXQzg== -fill-range@^7.0.1: - version "7.0.1" - resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.0.1.tgz#1919a6a7c75fe38b2c7c77e5198535da9acdda40" - integrity sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ== +fill-range@^7.1.1: + version "7.1.1" + resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.1.1.tgz#44265d3cac07e3ea7dc247516380643754a05292" + integrity sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg== dependencies: to-regex-range "^5.0.1" @@ -464,7 +474,7 @@ glob-to-regexp@^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== -graceful-fs@^4.1.2, graceful-fs@^4.2.4, graceful-fs@^4.2.9: +graceful-fs@^4.1.2, graceful-fs@^4.2.11, graceful-fs@^4.2.4: 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== @@ -577,11 +587,11 @@ merge-stream@^2.0.0: integrity sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w== micromatch@^4.0.0: - version "4.0.5" - resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.5.tgz#bc8999a7cbbf77cdc89f132f6e467051b49090c6" - integrity sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA== + version "4.0.8" + resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.8.tgz#d66fa18f3a47076789320b9b1af32bd86d9fa202" + integrity sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA== dependencies: - braces "^3.0.2" + braces "^3.0.3" picomatch "^2.3.1" mime-db@1.52.0: @@ -606,10 +616,10 @@ neo-async@^2.6.2: resolved "https://registry.yarnpkg.com/neo-async/-/neo-async-2.6.2.tgz#b4aafb93e3aeb2d8174ca53cf163ab7d7308305f" integrity sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw== -node-releases@^2.0.12: - version "2.0.13" - resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.13.tgz#d5ed1627c23e3461e819b02e57b75e4899b1c81d" - integrity sha512-uYr7J37ae/ORWdZeQ1xxMJe3NtdmqMC/JZK+geofDrkLUApKRHPd18/TxtBOJ4A0/+uUIliorNrfYV6s1b02eQ== +node-releases@^2.0.18: + version "2.0.18" + resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.18.tgz#f010e8d35e2fe8d6b2944f03f70213ecedc4ca3f" + integrity sha512-d9VeXT4SJ7ZeOqGX6R5EM022wpL+eWPooLI+5UpWn2jCT1aosUQEhQP214x33Wkwx3JQMvIm+tIoVOdodFS40g== p-limit@^2.2.0: version "2.3.0" @@ -645,10 +655,10 @@ path-parse@^1.0.7: resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735" integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw== -picocolors@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.0.0.tgz#cb5bdc74ff3f51892236eaf79d68bc44564ab81c" - integrity sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ== +picocolors@^1.1.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.1.1.tgz#3d321af3eab939b083c8f929a1d12cda81c26b6b" + integrity sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA== picomatch@^2.3.1: version "2.3.1" @@ -796,21 +806,21 @@ tapable@^2.1.1, tapable@^2.2.0: resolved "https://registry.yarnpkg.com/tapable/-/tapable-2.2.1.tgz#1967a73ef4060a82f12ab96af86d52fdb76eeca0" integrity sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ== -terser-webpack-plugin@^5.3.7: - version "5.3.9" - resolved "https://registry.yarnpkg.com/terser-webpack-plugin/-/terser-webpack-plugin-5.3.9.tgz#832536999c51b46d468067f9e37662a3b96adfe1" - integrity sha512-ZuXsqE07EcggTWQjXUj+Aot/OMcD0bMKGgF63f7UxYcu5/AJF53aIpK1YoP5xR9l6s/Hy2b+t1AM0bLNPRuhwA== +terser-webpack-plugin@^5.3.10: + 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.17" + "@jridgewell/trace-mapping" "^0.3.20" jest-worker "^27.4.5" schema-utils "^3.1.1" serialize-javascript "^6.0.1" - terser "^5.16.8" + terser "^5.26.0" -terser@^5.16.8: - version "5.19.2" - resolved "https://registry.yarnpkg.com/terser/-/terser-5.19.2.tgz#bdb8017a9a4a8de4663a7983f45c506534f9234e" - integrity sha512-qC5+dmecKJA4cpYxRa5aVkKehYsQKc+AHeKl0Oe62aYjBL8ZA33tTljktDHJSaxxMnbI5ZYw+o/S2DxxLu8OfA== +terser@^5.26.0: + version "5.36.0" + resolved "https://registry.yarnpkg.com/terser/-/terser-5.36.0.tgz#8b0dbed459ac40ff7b4c9fd5a3a2029de105180e" + integrity sha512-IYV9eNMuFAV4THUspIRXkLakHnV6XO7FEdtKjf/mDyrnqUg9LnlOn6/RwRvM9SZjR4GUq8Nk8zj67FzVARr74w== dependencies: "@jridgewell/source-map" "^0.3.3" acorn "^8.8.2" @@ -867,13 +877,13 @@ type-fest@^2.19.0: resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-2.19.0.tgz#88068015bb33036a598b952e55e9311a60fd3a9b" integrity sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA== -update-browserslist-db@^1.0.11: - version "1.0.11" - resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.0.11.tgz#9a2a641ad2907ae7b3616506f4b977851db5b940" - integrity sha512-dCwEFf0/oT85M1fHBg4F0jtLwJrutGoHSQXCh7u4o2t1drG+c0a9Flnqww6XUKSfQMPpJBRjU8d4RXB09qtvaA== +update-browserslist-db@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.1.1.tgz#80846fba1d79e82547fb661f8d141e0945755fe5" + integrity sha512-R8UzCaa9Az+38REPiJ1tXlImTJXlVfgHZsglwBD/k6nj76ctsH1E3q4doGrukiLQd3sGQYu56r5+lo5r94l29A== dependencies: - escalade "^3.1.1" - picocolors "^1.0.0" + escalade "^3.2.0" + picocolors "^1.1.0" uri-js@^4.2.2: version "4.4.1" @@ -882,10 +892,10 @@ uri-js@^4.2.2: dependencies: punycode "^2.1.0" -watchpack@^2.4.0: - version "2.4.0" - resolved "https://registry.yarnpkg.com/watchpack/-/watchpack-2.4.0.tgz#fa33032374962c78113f93c7f2fb4c54c9862a5d" - integrity sha512-Lcvm7MGST/4fup+ifyKi2hjyIAwcdI4HRgtvTpIUxBRhB+RFtUh8XtDOxUfctVCnhVi+QQj49i91OyvzkJl6cg== +watchpack@^2.4.1: + version "2.4.2" + resolved "https://registry.yarnpkg.com/watchpack/-/watchpack-2.4.2.tgz#2feeaed67412e7c33184e5a79ca738fbd38564da" + integrity sha512-TnbFSbcOCcDgjZ4piURLCbJ3nJhznVh9kw6F6iokjiFPl8ONxe9A6nMDVXDiNbrSfLILs6vB07F7wLBrwPYzJw== dependencies: glob-to-regexp "^0.4.1" graceful-fs "^4.1.2" @@ -923,33 +933,32 @@ webpack-sources@^3.2.3: integrity sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w== webpack@^5.88.2: - version "5.88.2" - resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.88.2.tgz#f62b4b842f1c6ff580f3fcb2ed4f0b579f4c210e" - integrity sha512-JmcgNZ1iKj+aiR0OvTYtWQqJwq37Pf683dY9bVORwVbUrDhLhdn/PlO2sHsFHPkj7sHNQF3JwaAkp49V+Sq1tQ== - dependencies: - "@types/eslint-scope" "^3.7.3" - "@types/estree" "^1.0.0" - "@webassemblyjs/ast" "^1.11.5" - "@webassemblyjs/wasm-edit" "^1.11.5" - "@webassemblyjs/wasm-parser" "^1.11.5" + version "5.95.0" + resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.95.0.tgz#8fd8c454fa60dad186fbe36c400a55848307b4c0" + integrity sha512-2t3XstrKULz41MNMBF+cJ97TyHdyQ8HCt//pqErqDvNjU9YQBnZxIHa11VXsi7F3mb5/aO2tuDxdeTPdU7xu9Q== + dependencies: + "@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.14.5" + acorn-import-attributes "^1.9.5" + browserslist "^4.21.10" chrome-trace-event "^1.0.2" - enhanced-resolve "^5.15.0" + enhanced-resolve "^5.17.1" 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.9" + 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.7" - watchpack "^2.4.0" + terser-webpack-plugin "^5.3.10" + watchpack "^2.4.1" webpack-sources "^3.2.3" which@^2.0.1: diff --git a/deploy/tools/favicon-generator/.gitignore b/deploy/tools/favicon-generator/.gitignore old mode 100755 new mode 100644 index 09af2dfe51..8b8c2bf2a8 --- a/deploy/tools/favicon-generator/.gitignore +++ b/deploy/tools/favicon-generator/.gitignore @@ -1,4 +1,4 @@ -/output -config.json -response.json -favicon_package** \ No newline at end of file +/node_modules +/public +.env +/output \ No newline at end of file diff --git a/deploy/tools/favicon-generator/config.template.json b/deploy/tools/favicon-generator/config.template.json deleted file mode 100755 index 4d032e8bb9..0000000000 --- a/deploy/tools/favicon-generator/config.template.json +++ /dev/null @@ -1,41 +0,0 @@ -{ - "favicon_generation": { - "api_key": "", - "master_picture": { - "type": "url", - "url": "" - }, - "files_location": { - "type": "path", - "path": "/favicons" - }, - "favicon_design": { - "desktop_browser": {}, - "ios": { - "picture_aspect": "no_change", - "assets": { - "ios6_and_prior_icons": false, - "ios7_and_later_icons": true, - "precomposed_icons": false, - "declare_only_default_icon": true - } - }, - "safari_pinned_tab": { - "picture_aspect": "black_and_white", - "threshold": 60 - } - }, - "settings": { - "compression": "3", - "scaling_algorithm": "Mitchell", - "error_on_image_too_small": true, - "readme_file": false, - "html_code_file": false, - "use_path_as_is": false - }, - "versioning": { - "param_name": "ver", - "param_value": "15Zd8" - } - } -} \ No newline at end of file diff --git a/deploy/tools/favicon-generator/index.js b/deploy/tools/favicon-generator/index.js new file mode 100644 index 0000000000..b552a9f6c9 --- /dev/null +++ b/deploy/tools/favicon-generator/index.js @@ -0,0 +1,71 @@ +/* eslint-disable no-console */ +const { favicons } = require('favicons'); +const fs = require('fs/promises'); +const path = require('path'); + +generateFavicons(); + +async function generateFavicons() { + console.log('Generating favicons...'); + const masterUrl = process.env.MASTER_URL; + try { + if (!masterUrl) { + throw new Error('FAVICON_MASTER_URL or NEXT_PUBLIC_NETWORK_ICON must be set'); + } + + const fetch = await import('node-fetch'); + const response = await fetch.default(masterUrl); + const buffer = await response.arrayBuffer(); + const source = Buffer.from(buffer); + + const configuration = { + path: '/output', + appName: 'Blockscout', + icons: { + android: true, + appleIcon: { + background: 'transparent', + }, + appleStartup: false, + favicons: true, + windows: false, + yandex: false, + }, + }; + + try { + const result = await favicons(source, configuration); + + const outputDir = path.resolve(process.cwd(), 'output'); + await fs.mkdir(outputDir, { recursive: true }); + + for (const image of result.images) { + // keep only necessary files + if (image.name === 'apple-touch-icon-180x180.png' || image.name === 'android-chrome-192x192.png' || + (!image.name.startsWith('apple-touch-icon') && !image.name.startsWith('android-chrome')) + ) { + await fs.writeFile(path.join(outputDir, image.name), image.contents); + } + + // copy android-chrome-192x192.png to logo-icon.png for marketing purposes + if (image.name === 'android-chrome-192x192.png') { + await fs.writeFile(path.join(outputDir, 'logo-icon.png'), image.contents); + } + } + + for (const file of result.files) { + if (file.name !== 'manifest.webmanifest') { + await fs.writeFile(path.join(outputDir, file.name), file.contents); + } + } + + console.log('Favicons generated successfully!'); + } catch (faviconError) { + console.error('Error generating favicons:', faviconError); + process.exit(1); + } + } catch (error) { + console.error('Error in favicon generation process:', error); + process.exit(1); + } +} diff --git a/deploy/tools/favicon-generator/package.json b/deploy/tools/favicon-generator/package.json new file mode 100644 index 0000000000..f4127ccc37 --- /dev/null +++ b/deploy/tools/favicon-generator/package.json @@ -0,0 +1,20 @@ +{ + "name": "favicon-generator", + "version": "1.0.0", + "main": "index.js", + "keywords": [], + "author": "", + "license": "ISC", + "description": "", + "dependencies": { + "favicons": "^7.2.0", + "ts-loader": "^9.4.4", + "webpack": "^5.88.2", + "webpack-cli": "^5.1.4" + }, + "devDependencies": { + "dotenv-cli": "^7.4.2", + "node-loader": "^2.0.0", + "tsconfig-paths-webpack-plugin": "^4.1.0" + } +} diff --git a/deploy/tools/favicon-generator/script.sh b/deploy/tools/favicon-generator/script.sh deleted file mode 100755 index 69145399ed..0000000000 --- a/deploy/tools/favicon-generator/script.sh +++ /dev/null @@ -1,110 +0,0 @@ -#!/bin/bash - -echo "🌀 Generating favicons bundle..." - -# Check if MASTER_URL is provided -if [ -z "$MASTER_URL" ]; then - echo "🛑 Error: MASTER_URL variable is not provided." - exit 1 -fi - -# Check if FAVICON_GENERATOR_API_KEY is provided -if [ -z "$FAVICON_GENERATOR_API_KEY" ]; then - echo "🛑 Error: FAVICON_GENERATOR_API_KEY variable is not provided." - exit 1 -fi - -# Mask the FAVICON_GENERATOR_API_KEY to display only the first 8 characters -API_KEY_MASKED="${FAVICON_GENERATOR_API_KEY:0:8}***" -echo "🆗 The following variables are provided:" -echo " MASTER_URL: $MASTER_URL" -echo " FAVICON_GENERATOR_API_KEY: $API_KEY_MASKED" -echo - -# RealFaviconGenerator API endpoint URL -API_URL="https://realfavicongenerator.net/api/favicon" - -# Target folder for the downloaded assets -TARGET_FOLDER="./output" - -# Path to the config JSON template file -CONFIG_TEMPLATE_FILE="config.template.json" - -# Path to the generated config JSON file -CONFIG_FILE="config.json" - -# Replace and placeholders in the JSON template file -API_KEY_VALUE="$FAVICON_GENERATOR_API_KEY" -sed -e "s||$API_KEY_VALUE|" -e "s||$MASTER_URL|" "$CONFIG_TEMPLATE_FILE" > "$CONFIG_FILE" - -# Make the API POST request with JSON data from the config file -echo "⏳ Making request to API..." -API_RESPONSE=$(curl -s -X POST -H "Content-Type: application/json" -d @"$CONFIG_FILE" "$API_URL") - -# Create the response.json file with the API response -echo "$API_RESPONSE" > response.json - -# Check if the API response is valid JSON and contains success status -if ! jq -e '.favicon_generation_result.result.status == "success"' <<< "$API_RESPONSE" >/dev/null; then - echo "🛑 Error: API response does not contain the expected structure or has an error status." - ERROR_MESSAGE=$(echo "$API_RESPONSE" | jq -r '.favicon_generation_result.result.error_message' | tr -d '\\') - if [ -n "$ERROR_MESSAGE" ]; then - echo "🛑 $ERROR_MESSAGE" - fi - exit 1 -fi -echo "🆗 API responded with success status." - -# Parse the JSON response to extract the file URL and remove backslashes -FILE_URL=$(echo "$API_RESPONSE" | jq -r '.favicon_generation_result.favicon.package_url' | tr -d '\\') -PREVIEW_URL=$(echo "$API_RESPONSE" | jq -r '.favicon_generation_result.preview_picture_url' | tr -d '\\') - -# Check if FILE_URL is empty -if [ -z "$FILE_URL" ]; then - echo "🛑 File URL not found in JSON response." - exit 1 -fi - -echo "🆗 Found following file URL in the response: $FILE_URL" -echo "🆗 Favicon preview URL: $PREVIEW_URL" -echo - -# Generate a filename based on the URL -FILE_NAME=$(basename "$FILE_URL") - -# Check if the target folder exists and clear its contents if it does -if [ -d "$TARGET_FOLDER" ]; then - rm -r "$TARGET_FOLDER" -fi -mkdir -p "$TARGET_FOLDER" - -# Download the file -echo "⏳ Trying to download the file..." -curl -s -L "$FILE_URL" -o "$FILE_NAME" - -# Check if the download was successful -if [ $? -eq 0 ]; then - echo "🆗 File downloaded successfully." - echo -else - echo "🛑 Error: Failed to download the file." - exit 1 -fi - -# Unzip the downloaded file to the target folder -echo "⏳ Unzipping the file..." -unzip -q "$FILE_NAME" -d "$TARGET_FOLDER" - -# Check if the unzip operation was successful -if [ $? -eq 0 ]; then - echo "🆗 File unzipped successfully." - echo -else - echo "🛑 Failed to unzip the file." - exit 1 -fi - -# Clean up - remove the JSON response file and temporary JSON config file -rm response.json "$CONFIG_FILE" - -echo "✅ Done." \ No newline at end of file diff --git a/deploy/tools/favicon-generator/yarn.lock b/deploy/tools/favicon-generator/yarn.lock new file mode 100644 index 0000000000..af54e5366c --- /dev/null +++ b/deploy/tools/favicon-generator/yarn.lock @@ -0,0 +1,1178 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +"@discoveryjs/json-ext@^0.5.0": + version "0.5.7" + resolved "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-0.5.7.tgz" + integrity sha512-dBVuXR082gk3jsFp7Rd/JI4kytwGHecnCoTtXFb7DB6CNHp4rg5k1bhg0nWdLGLnOV71lmDzGQaLMy8iPLY0pw== + +"@emnapi/runtime@^1.2.0": + version "1.3.1" + resolved "https://registry.yarnpkg.com/@emnapi/runtime/-/runtime-1.3.1.tgz#0fcaa575afc31f455fd33534c19381cfce6c6f60" + integrity sha512-kEBmG8KyqtxJZv+ygbEim+KCGtIq1fC22Ms3S4ziXmYKm8uyoLX0MHONVKwp+9opg390VaKRNt4a7A9NwmpNhw== + dependencies: + tslib "^2.4.0" + +"@img/sharp-darwin-arm64@0.33.5": + version "0.33.5" + resolved "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.33.5.tgz" + integrity sha512-UT4p+iz/2H4twwAoLCqfA9UH5pI6DggwKEGuaPy7nCVQ8ZsiY5PIcrRvD1DzuY3qYL07NtIQcWnBSY/heikIFQ== + optionalDependencies: + "@img/sharp-libvips-darwin-arm64" "1.0.4" + +"@img/sharp-darwin-x64@0.33.5": + version "0.33.5" + resolved "https://registry.yarnpkg.com/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.33.5.tgz#e03d3451cd9e664faa72948cc70a403ea4063d61" + integrity sha512-fyHac4jIc1ANYGRDxtiqelIbdWkIuQaI84Mv45KvGRRxSAa7o7d1ZKAOBaYbnepLC1WqxfpimdeWfvqqSGwR2Q== + optionalDependencies: + "@img/sharp-libvips-darwin-x64" "1.0.4" + +"@img/sharp-libvips-darwin-arm64@1.0.4": + version "1.0.4" + resolved "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.0.4.tgz" + integrity sha512-XblONe153h0O2zuFfTAbQYAX2JhYmDHeWikp1LM9Hul9gVPjFY427k6dFEcOL72O01QxQsWi761svJ/ev9xEDg== + +"@img/sharp-libvips-darwin-x64@1.0.4": + version "1.0.4" + resolved "https://registry.yarnpkg.com/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.0.4.tgz#e0456f8f7c623f9dbfbdc77383caa72281d86062" + integrity sha512-xnGR8YuZYfJGmWPvmlunFaWJsb9T/AO2ykoP3Fz/0X5XV2aoYBPkX6xqCQvUTKKiLddarLaxpzNe+b1hjeWHAQ== + +"@img/sharp-libvips-linux-arm64@1.0.4": + version "1.0.4" + resolved "https://registry.yarnpkg.com/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.0.4.tgz#979b1c66c9a91f7ff2893556ef267f90ebe51704" + integrity sha512-9B+taZ8DlyyqzZQnoeIvDVR/2F4EbMepXMc/NdVbkzsJbzkUjhXv/70GQJ7tdLA4YJgNP25zukcxpX2/SueNrA== + +"@img/sharp-libvips-linux-arm@1.0.5": + version "1.0.5" + resolved "https://registry.yarnpkg.com/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.0.5.tgz#99f922d4e15216ec205dcb6891b721bfd2884197" + integrity sha512-gvcC4ACAOPRNATg/ov8/MnbxFDJqf/pDePbBnuBDcjsI8PssmjoKMAz4LtLaVi+OnSb5FK/yIOamqDwGmXW32g== + +"@img/sharp-libvips-linux-s390x@1.0.4": + version "1.0.4" + resolved "https://registry.yarnpkg.com/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.0.4.tgz#f8a5eb1f374a082f72b3f45e2fb25b8118a8a5ce" + integrity sha512-u7Wz6ntiSSgGSGcjZ55im6uvTrOxSIS8/dgoVMoiGE9I6JAfU50yH5BoDlYA1tcuGS7g/QNtetJnxA6QEsCVTA== + +"@img/sharp-libvips-linux-x64@1.0.4": + version "1.0.4" + resolved "https://registry.yarnpkg.com/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.0.4.tgz#d4c4619cdd157774906e15770ee119931c7ef5e0" + integrity sha512-MmWmQ3iPFZr0Iev+BAgVMb3ZyC4KeFc3jFxnNbEPas60e1cIfevbtuyf9nDGIzOaW9PdnDciJm+wFFaTlj5xYw== + +"@img/sharp-libvips-linuxmusl-arm64@1.0.4": + version "1.0.4" + resolved "https://registry.yarnpkg.com/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.0.4.tgz#166778da0f48dd2bded1fa3033cee6b588f0d5d5" + integrity sha512-9Ti+BbTYDcsbp4wfYib8Ctm1ilkugkA/uscUn6UXK1ldpC1JjiXbLfFZtRlBhjPZ5o1NCLiDbg8fhUPKStHoTA== + +"@img/sharp-libvips-linuxmusl-x64@1.0.4": + version "1.0.4" + resolved "https://registry.yarnpkg.com/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.0.4.tgz#93794e4d7720b077fcad3e02982f2f1c246751ff" + integrity sha512-viYN1KX9m+/hGkJtvYYp+CCLgnJXwiQB39damAO7WMdKWlIhmYTfHjwSbQeUK/20vY154mwezd9HflVFM1wVSw== + +"@img/sharp-linux-arm64@0.33.5": + version "0.33.5" + resolved "https://registry.yarnpkg.com/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.33.5.tgz#edb0697e7a8279c9fc829a60fc35644c4839bb22" + integrity sha512-JMVv+AMRyGOHtO1RFBiJy/MBsgz0x4AWrT6QoEVVTyh1E39TrCUpTRI7mx9VksGX4awWASxqCYLCV4wBZHAYxA== + optionalDependencies: + "@img/sharp-libvips-linux-arm64" "1.0.4" + +"@img/sharp-linux-arm@0.33.5": + version "0.33.5" + resolved "https://registry.yarnpkg.com/@img/sharp-linux-arm/-/sharp-linux-arm-0.33.5.tgz#422c1a352e7b5832842577dc51602bcd5b6f5eff" + integrity sha512-JTS1eldqZbJxjvKaAkxhZmBqPRGmxgu+qFKSInv8moZ2AmT5Yib3EQ1c6gp493HvrvV8QgdOXdyaIBrhvFhBMQ== + optionalDependencies: + "@img/sharp-libvips-linux-arm" "1.0.5" + +"@img/sharp-linux-s390x@0.33.5": + version "0.33.5" + resolved "https://registry.yarnpkg.com/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.33.5.tgz#f5c077926b48e97e4a04d004dfaf175972059667" + integrity sha512-y/5PCd+mP4CA/sPDKl2961b+C9d+vPAveS33s6Z3zfASk2j5upL6fXVPZi7ztePZ5CuH+1kW8JtvxgbuXHRa4Q== + optionalDependencies: + "@img/sharp-libvips-linux-s390x" "1.0.4" + +"@img/sharp-linux-x64@0.33.5": + version "0.33.5" + resolved "https://registry.yarnpkg.com/@img/sharp-linux-x64/-/sharp-linux-x64-0.33.5.tgz#d806e0afd71ae6775cc87f0da8f2d03a7c2209cb" + integrity sha512-opC+Ok5pRNAzuvq1AG0ar+1owsu842/Ab+4qvU879ippJBHvyY5n2mxF1izXqkPYlGuP/M556uh53jRLJmzTWA== + optionalDependencies: + "@img/sharp-libvips-linux-x64" "1.0.4" + +"@img/sharp-linuxmusl-arm64@0.33.5": + version "0.33.5" + resolved "https://registry.yarnpkg.com/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.33.5.tgz#252975b915894fb315af5deea174651e208d3d6b" + integrity sha512-XrHMZwGQGvJg2V/oRSUfSAfjfPxO+4DkiRh6p2AFjLQztWUuY/o8Mq0eMQVIY7HJ1CDQUJlxGGZRw1a5bqmd1g== + optionalDependencies: + "@img/sharp-libvips-linuxmusl-arm64" "1.0.4" + +"@img/sharp-linuxmusl-x64@0.33.5": + version "0.33.5" + resolved "https://registry.yarnpkg.com/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.33.5.tgz#3f4609ac5d8ef8ec7dadee80b560961a60fd4f48" + integrity sha512-WT+d/cgqKkkKySYmqoZ8y3pxx7lx9vVejxW/W4DOFMYVSkErR+w7mf2u8m/y4+xHe7yY9DAXQMWQhpnMuFfScw== + optionalDependencies: + "@img/sharp-libvips-linuxmusl-x64" "1.0.4" + +"@img/sharp-wasm32@0.33.5": + version "0.33.5" + resolved "https://registry.yarnpkg.com/@img/sharp-wasm32/-/sharp-wasm32-0.33.5.tgz#6f44f3283069d935bb5ca5813153572f3e6f61a1" + integrity sha512-ykUW4LVGaMcU9lu9thv85CbRMAwfeadCJHRsg2GmeRa/cJxsVY9Rbd57JcMxBkKHag5U/x7TSBpScF4U8ElVzg== + dependencies: + "@emnapi/runtime" "^1.2.0" + +"@img/sharp-win32-ia32@0.33.5": + version "0.33.5" + resolved "https://registry.yarnpkg.com/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.33.5.tgz#1a0c839a40c5351e9885628c85f2e5dfd02b52a9" + integrity sha512-T36PblLaTwuVJ/zw/LaH0PdZkRz5rd3SmMHX8GSmR7vtNSP5Z6bQkExdSK7xGWyxLw4sUknBuugTelgw2faBbQ== + +"@img/sharp-win32-x64@0.33.5": + version "0.33.5" + resolved "https://registry.yarnpkg.com/@img/sharp-win32-x64/-/sharp-win32-x64-0.33.5.tgz#56f00962ff0c4e0eb93d34a047d29fa995e3e342" + integrity sha512-MpY/o8/8kj+EcnxwvrP4aTJSWw/aZ7JIGR4aBeZkZw5B7/Jn+tY9/VNwtcoGmdT7GfggGIU4kygOMSbYnOrAbg== + +"@jridgewell/gen-mapping@^0.3.5": + version "0.3.5" + resolved "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz" + integrity sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg== + dependencies: + "@jridgewell/set-array" "^1.2.1" + "@jridgewell/sourcemap-codec" "^1.4.10" + "@jridgewell/trace-mapping" "^0.3.24" + +"@jridgewell/resolve-uri@^3.1.0": + version "3.1.2" + resolved "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz" + integrity sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw== + +"@jridgewell/set-array@^1.2.1": + version "1.2.1" + resolved "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz" + integrity sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A== + +"@jridgewell/source-map@^0.3.3": + version "0.3.6" + resolved "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.6.tgz" + 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.14": + version "1.5.0" + resolved "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz" + integrity sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ== + +"@jridgewell/trace-mapping@^0.3.20", "@jridgewell/trace-mapping@^0.3.24", "@jridgewell/trace-mapping@^0.3.25": + version "0.3.25" + resolved "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz" + integrity sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ== + dependencies: + "@jridgewell/resolve-uri" "^3.1.0" + "@jridgewell/sourcemap-codec" "^1.4.14" + +"@types/estree@^1.0.5": + version "1.0.6" + resolved "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz" + integrity sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw== + +"@types/json-schema@^7.0.8": + version "7.0.15" + resolved "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz" + integrity sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA== + +"@types/node@*": + version "22.7.9" + resolved "https://registry.npmjs.org/@types/node/-/node-22.7.9.tgz" + integrity sha512-jrTfRC7FM6nChvU7X2KqcrgquofrWLFDeYC1hKfwNWomVvrn7JIksqf344WN2X/y8xrgqBd2dJATZV4GbatBfg== + dependencies: + undici-types "~6.19.2" + +"@webassemblyjs/ast@1.12.1", "@webassemblyjs/ast@^1.12.1": + version "1.12.1" + resolved "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.12.1.tgz" + integrity sha512-EKfMUOPRRUTy5UII4qJDGPpqfwjOmZ5jeGFwid9mnoqIFK+e0vqoi1qH56JpmZSzEL53jKnNzScdmftJyG5xWg== + dependencies: + "@webassemblyjs/helper-numbers" "1.11.6" + "@webassemblyjs/helper-wasm-bytecode" "1.11.6" + +"@webassemblyjs/floating-point-hex-parser@1.11.6": + version "1.11.6" + resolved "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.11.6.tgz" + integrity sha512-ejAj9hfRJ2XMsNHk/v6Fu2dGS+i4UaXBXGemOfQ/JfQ6mdQg/WXtwleQRLLS4OvfDhv8rYnVwH27YJLMyYsxhw== + +"@webassemblyjs/helper-api-error@1.11.6": + version "1.11.6" + resolved "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.11.6.tgz" + integrity sha512-o0YkoP4pVu4rN8aTJgAyj9hC2Sv5UlkzCHhxqWj8butaLvnpdc2jOwh4ewE6CX0txSfLn/UYaV/pheS2Txg//Q== + +"@webassemblyjs/helper-buffer@1.12.1": + version "1.12.1" + resolved "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.12.1.tgz" + integrity sha512-nzJwQw99DNDKr9BVCOZcLuJJUlqkJh+kVzVl6Fmq/tI5ZtEyWT1KZMyOXltXLZJmDtvLCDgwsyrkohEtopTXCw== + +"@webassemblyjs/helper-numbers@1.11.6": + version "1.11.6" + resolved "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.11.6.tgz" + 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.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.11.6.tgz" + integrity sha512-sFFHKwcmBprO9e7Icf0+gddyWYDViL8bpPjJJl0WHxCdETktXdmtWLGVzoHbqUcY4Be1LkNfwTmXOJUFZYSJdA== + +"@webassemblyjs/helper-wasm-section@1.12.1": + version "1.12.1" + resolved "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.12.1.tgz" + 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/ieee754@1.11.6": + version "1.11.6" + resolved "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.11.6.tgz" + integrity sha512-LM4p2csPNvbij6U1f19v6WR56QZ8JcHg3QIJTlSwzFcmx6WSORicYj6I63f9yU1kEUtrpG+kjkiIAkevHpDXrg== + dependencies: + "@xtuc/ieee754" "^1.2.0" + +"@webassemblyjs/leb128@1.11.6": + version "1.11.6" + resolved "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.11.6.tgz" + integrity sha512-m7a0FhE67DQXgouf1tbN5XQcdWoNgaAuoULHIfGFIEVKA6tu/edls6XnIlkmS6FrXAquJRPni3ZZKjw6FSPjPQ== + dependencies: + "@xtuc/long" "4.2.2" + +"@webassemblyjs/utf8@1.11.6": + version "1.11.6" + resolved "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.11.6.tgz" + integrity sha512-vtXf2wTQ3+up9Zsg8sa2yWiQpzSsMyXj0qViVP6xKGCUT8p8YJ6HqI7l5eCnWx1T/FYdsv07HQs2wTFbbof/RA== + +"@webassemblyjs/wasm-edit@^1.12.1": + version "1.12.1" + resolved "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.12.1.tgz" + 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.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.12.1.tgz" + 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-opt@1.12.1": + version "1.12.1" + resolved "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.12.1.tgz" + 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-parser@1.12.1", "@webassemblyjs/wasm-parser@^1.12.1": + version "1.12.1" + resolved "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.12.1.tgz" + 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/wast-printer@1.12.1": + version "1.12.1" + resolved "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.12.1.tgz" + integrity sha512-+X4WAlOisVWQMikjbcvY2e0rwPsKQ9F688lksZhBcPycBBuii3O7m8FACbDMWDojpAqvjIncrG8J0XHKyQfVeA== + dependencies: + "@webassemblyjs/ast" "1.12.1" + "@xtuc/long" "4.2.2" + +"@webpack-cli/configtest@^2.1.1": + version "2.1.1" + resolved "https://registry.npmjs.org/@webpack-cli/configtest/-/configtest-2.1.1.tgz" + integrity sha512-wy0mglZpDSiSS0XHrVR+BAdId2+yxPSoJW8fsna3ZpYSlufjvxnP4YbKTCBZnNIcGN4r6ZPXV55X4mYExOfLmw== + +"@webpack-cli/info@^2.0.2": + version "2.0.2" + resolved "https://registry.npmjs.org/@webpack-cli/info/-/info-2.0.2.tgz" + integrity sha512-zLHQdI/Qs1UyT5UBdWNqsARasIA+AaF8t+4u2aS2nEpBQh2mWIVb8qAklq0eUENnC5mOItrIB4LiS9xMtph18A== + +"@webpack-cli/serve@^2.0.5": + version "2.0.5" + resolved "https://registry.npmjs.org/@webpack-cli/serve/-/serve-2.0.5.tgz" + integrity sha512-lqaoKnRYBdo1UgDX8uF24AfGMifWK19TxPmM5FHc2vAGxrJ/qtyUyFBWoY1tISZdelsQ5fBcOusifo5o5wSJxQ== + +"@xtuc/ieee754@^1.2.0": + version "1.2.0" + resolved "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz" + integrity sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA== + +"@xtuc/long@4.2.2": + version "4.2.2" + resolved "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz" + integrity sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ== + +acorn-import-attributes@^1.9.5: + version "1.9.5" + resolved "https://registry.npmjs.org/acorn-import-attributes/-/acorn-import-attributes-1.9.5.tgz" + integrity sha512-n02Vykv5uA3eHGM/Z2dQrcD56kL8TyDb2p1+0P83PClMnC/nc+anbQRhIOWnSq4Ke/KvDPrY3C9hDtC/A3eHnQ== + +acorn@^8.7.1, acorn@^8.8.2: + version "8.13.0" + resolved "https://registry.npmjs.org/acorn/-/acorn-8.13.0.tgz" + integrity sha512-8zSiw54Oxrdym50NlZ9sUusyO1Z1ZchgRLWRaK6c86XJFClyCgFKetdowBg5bKxyp/u+CDBJG4Mpp0m3HLZl9w== + +ajv-keywords@^3.5.2: + version "3.5.2" + resolved "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz" + integrity sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ== + +ajv@^6.12.5: + version "6.12.6" + resolved "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz" + integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g== + dependencies: + fast-deep-equal "^3.1.1" + fast-json-stable-stringify "^2.0.0" + json-schema-traverse "^0.4.1" + uri-js "^4.2.2" + +ansi-styles@^4.1.0: + version "4.3.0" + resolved "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz" + integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg== + dependencies: + color-convert "^2.0.1" + +big.js@^5.2.2: + version "5.2.2" + resolved "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz" + integrity sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ== + +braces@^3.0.3: + version "3.0.3" + resolved "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz" + integrity sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA== + dependencies: + fill-range "^7.1.1" + +browserslist@^4.21.10: + version "4.24.2" + resolved "https://registry.npmjs.org/browserslist/-/browserslist-4.24.2.tgz" + integrity sha512-ZIc+Q62revdMcqC6aChtW4jz3My3klmCO1fEmINZY/8J3EpBg5/A/D0AKmBveUh6pgoeycoMkVMko84tuYS+Gg== + dependencies: + caniuse-lite "^1.0.30001669" + electron-to-chromium "^1.5.41" + node-releases "^2.0.18" + update-browserslist-db "^1.1.1" + +buffer-from@^1.0.0: + version "1.1.2" + resolved "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz" + integrity sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ== + +caniuse-lite@^1.0.30001669: + version "1.0.30001669" + resolved "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001669.tgz" + integrity sha512-DlWzFDJqstqtIVx1zeSpIMLjunf5SmwOw0N2Ck/QSQdS8PLS4+9HrLaYei4w8BIAL7IB/UEDu889d8vhCTPA0w== + +chalk@^4.1.0: + version "4.1.2" + resolved "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz" + integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA== + dependencies: + ansi-styles "^4.1.0" + supports-color "^7.1.0" + +chrome-trace-event@^1.0.2: + version "1.0.4" + resolved "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.4.tgz" + integrity sha512-rNjApaLzuwaOTjCiT8lSDdGN1APCiqkChLMJxJPWLunPAt5fy8xgU9/jNOchV84wfIxrA0lRQB7oCT8jrn/wrQ== + +clone-deep@^4.0.1: + version "4.0.1" + resolved "https://registry.npmjs.org/clone-deep/-/clone-deep-4.0.1.tgz" + integrity sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ== + dependencies: + is-plain-object "^2.0.4" + kind-of "^6.0.2" + shallow-clone "^3.0.0" + +color-convert@^2.0.1: + version "2.0.1" + resolved "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz" + integrity sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ== + dependencies: + color-name "~1.1.4" + +color-name@^1.0.0, color-name@~1.1.4: + version "1.1.4" + resolved "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz" + integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== + +color-string@^1.9.0: + version "1.9.1" + resolved "https://registry.npmjs.org/color-string/-/color-string-1.9.1.tgz" + integrity sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg== + dependencies: + color-name "^1.0.0" + simple-swizzle "^0.2.2" + +color@^4.2.3: + version "4.2.3" + resolved "https://registry.npmjs.org/color/-/color-4.2.3.tgz" + integrity sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A== + dependencies: + color-convert "^2.0.1" + color-string "^1.9.0" + +colorette@^2.0.14: + version "2.0.20" + resolved "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz" + integrity sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w== + +commander@^10.0.1: + version "10.0.1" + resolved "https://registry.npmjs.org/commander/-/commander-10.0.1.tgz" + integrity sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug== + +commander@^2.20.0: + version "2.20.3" + resolved "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz" + integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ== + +cross-spawn@^7.0.3: + version "7.0.3" + resolved "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz" + integrity sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w== + dependencies: + path-key "^3.1.0" + shebang-command "^2.0.0" + which "^2.0.1" + +detect-libc@^2.0.3: + version "2.0.3" + resolved "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.3.tgz" + integrity sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw== + +dotenv-cli@^7.4.2: + version "7.4.2" + resolved "https://registry.npmjs.org/dotenv-cli/-/dotenv-cli-7.4.2.tgz" + integrity sha512-SbUj8l61zIbzyhIbg0FwPJq6+wjbzdn9oEtozQpZ6kW2ihCcapKVZj49oCT3oPM+mgQm+itgvUQcG5szxVrZTA== + dependencies: + cross-spawn "^7.0.3" + dotenv "^16.3.0" + dotenv-expand "^10.0.0" + minimist "^1.2.6" + +dotenv-expand@^10.0.0: + version "10.0.0" + resolved "https://registry.npmjs.org/dotenv-expand/-/dotenv-expand-10.0.0.tgz" + integrity sha512-GopVGCpVS1UKH75VKHGuQFqS1Gusej0z4FyQkPdwjil2gNIv+LNsqBlboOzpJFZKVT95GkCyWJbBSdFEFUWI2A== + +dotenv@^16.3.0: + version "16.4.5" + resolved "https://registry.npmjs.org/dotenv/-/dotenv-16.4.5.tgz" + integrity sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg== + +electron-to-chromium@^1.5.41: + version "1.5.43" + resolved "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.43.tgz" + integrity sha512-NxnmFBHDl5Sachd2P46O7UJiMaMHMLSofoIWVJq3mj8NJgG0umiSeljAVP9lGzjI0UDLJJ5jjoGjcrB8RSbjLQ== + +emojis-list@^3.0.0: + version "3.0.0" + resolved "https://registry.npmjs.org/emojis-list/-/emojis-list-3.0.0.tgz" + integrity sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q== + +enhanced-resolve@^5.0.0, enhanced-resolve@^5.17.1, enhanced-resolve@^5.7.0: + version "5.17.1" + resolved "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.17.1.tgz" + integrity sha512-LMHl3dXhTcfv8gM4kEzIUeTQ+7fpdA0l2tUf34BddXPkz2A5xJ5L/Pchd5BL6rdccM9QGvu0sWZzK1Z1t4wwyg== + dependencies: + graceful-fs "^4.2.4" + tapable "^2.2.0" + +envinfo@^7.7.3: + version "7.14.0" + resolved "https://registry.npmjs.org/envinfo/-/envinfo-7.14.0.tgz" + integrity sha512-CO40UI41xDQzhLB1hWyqUKgFhs250pNcGbyGKe1l/e4FSaI/+YE4IMG76GDt0In67WLPACIITC+sOi08x4wIvg== + +es-module-lexer@^1.2.1: + version "1.5.4" + resolved "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.5.4.tgz" + integrity sha512-MVNK56NiMrOwitFB7cqDwq0CQutbw+0BvLshJSse0MUNU+y1FC3bUS/AQg7oUng+/wKrrki7JfmwtVHkVfPLlw== + +escalade@^3.2.0: + version "3.2.0" + resolved "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz" + integrity sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA== + +escape-html@^1.0.3: + version "1.0.3" + resolved "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz" + integrity sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow== + +eslint-scope@5.1.1: + version "5.1.1" + resolved "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz" + integrity sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw== + dependencies: + esrecurse "^4.3.0" + estraverse "^4.1.1" + +esrecurse@^4.3.0: + version "4.3.0" + resolved "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz" + integrity sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag== + dependencies: + estraverse "^5.2.0" + +estraverse@^4.1.1: + version "4.3.0" + resolved "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz" + integrity sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw== + +estraverse@^5.2.0: + version "5.3.0" + resolved "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz" + integrity sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA== + +events@^3.2.0: + version "3.3.0" + resolved "https://registry.npmjs.org/events/-/events-3.3.0.tgz" + integrity sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q== + +fast-deep-equal@^3.1.1: + version "3.1.3" + resolved "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz" + integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== + +fast-json-stable-stringify@^2.0.0: + version "2.1.0" + resolved "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz" + integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw== + +fastest-levenshtein@^1.0.12: + version "1.0.16" + resolved "https://registry.npmjs.org/fastest-levenshtein/-/fastest-levenshtein-1.0.16.tgz" + integrity sha512-eRnCtTTtGZFpQCwhJiUOuxPQWRXVKYDn0b2PeHfXL6/Zi53SLAzAHfVhVWK2AryC/WH05kGfxhFIPvTF0SXQzg== + +favicons@^7.2.0: + version "7.2.0" + resolved "https://registry.npmjs.org/favicons/-/favicons-7.2.0.tgz" + integrity sha512-k/2rVBRIRzOeom3wI9jBPaSEvoTSQEW4iM0EveBmBBKFxO8mSyyRWtDlfC3VnEfu0avmjrMzy8/ZFPSe6F71Hw== + dependencies: + escape-html "^1.0.3" + sharp "^0.33.1" + xml2js "^0.6.1" + +fill-range@^7.1.1: + version "7.1.1" + resolved "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz" + integrity sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg== + dependencies: + to-regex-range "^5.0.1" + +find-up@^4.0.0: + version "4.1.0" + resolved "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz" + integrity sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw== + dependencies: + locate-path "^5.0.0" + path-exists "^4.0.0" + +flat@^5.0.2: + version "5.0.2" + resolved "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz" + integrity sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ== + +function-bind@^1.1.2: + version "1.1.2" + resolved "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz" + integrity sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA== + +glob-to-regexp@^0.4.1: + version "0.4.1" + resolved "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz" + integrity sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw== + +graceful-fs@^4.1.2, graceful-fs@^4.2.11, graceful-fs@^4.2.4: + version "4.2.11" + resolved "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz" + integrity sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ== + +has-flag@^4.0.0: + version "4.0.0" + resolved "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz" + integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== + +hasown@^2.0.2: + version "2.0.2" + resolved "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz" + integrity sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ== + dependencies: + function-bind "^1.1.2" + +import-local@^3.0.2: + version "3.2.0" + resolved "https://registry.npmjs.org/import-local/-/import-local-3.2.0.tgz" + integrity sha512-2SPlun1JUPWoM6t3F0dw0FkCF/jWY8kttcY4f599GLTSjh2OCuuhdTkJQsEcZzBqbXZGKMK2OqW1oZsjtf/gQA== + dependencies: + pkg-dir "^4.2.0" + resolve-cwd "^3.0.0" + +interpret@^3.1.1: + version "3.1.1" + resolved "https://registry.npmjs.org/interpret/-/interpret-3.1.1.tgz" + integrity sha512-6xwYfHbajpoF0xLW+iwLkhwgvLoZDfjYfoFNu8ftMoXINzwuymNLd9u/KmwtdT2GbR+/Cz66otEGEVVUHX9QLQ== + +is-arrayish@^0.3.1: + version "0.3.2" + resolved "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz" + integrity sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ== + +is-core-module@^2.13.0: + version "2.15.1" + resolved "https://registry.npmjs.org/is-core-module/-/is-core-module-2.15.1.tgz" + integrity sha512-z0vtXSwucUJtANQWldhbtbt7BnL0vxiFjIdDLAatwhDYty2bad6s+rijD6Ri4YuYJubLzIJLUidCh09e1djEVQ== + dependencies: + hasown "^2.0.2" + +is-number@^7.0.0: + version "7.0.0" + resolved "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz" + integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== + +is-plain-object@^2.0.4: + version "2.0.4" + resolved "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz" + integrity sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og== + dependencies: + isobject "^3.0.1" + +isexe@^2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz" + integrity sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw== + +isobject@^3.0.1: + version "3.0.1" + resolved "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz" + integrity sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg== + +jest-worker@^27.4.5: + version "27.5.1" + resolved "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz" + integrity sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg== + dependencies: + "@types/node" "*" + merge-stream "^2.0.0" + supports-color "^8.0.0" + +json-parse-even-better-errors@^2.3.1: + version "2.3.1" + resolved "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz" + integrity sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w== + +json-schema-traverse@^0.4.1: + version "0.4.1" + resolved "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz" + integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg== + +json5@^2.1.2, json5@^2.2.2: + version "2.2.3" + resolved "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz" + integrity sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg== + +kind-of@^6.0.2: + version "6.0.3" + resolved "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz" + integrity sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw== + +loader-runner@^4.2.0: + version "4.3.0" + resolved "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.0.tgz" + integrity sha512-3R/1M+yS3j5ou80Me59j7F9IMs4PXs3VqRrm0TU3AbKPxlmpoY1TNscJV/oGJXo8qCatFGTfDbY6W6ipGOYXfg== + +loader-utils@^2.0.0: + version "2.0.4" + resolved "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.4.tgz" + integrity sha512-xXqpXoINfFhgua9xiqD8fPFHgkoq1mmmpE92WlDbm9rNRd/EbRb+Gqf908T2DMfuHjjJlksiK2RbHVOdD/MqSw== + dependencies: + big.js "^5.2.2" + emojis-list "^3.0.0" + json5 "^2.1.2" + +locate-path@^5.0.0: + version "5.0.0" + resolved "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz" + integrity sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g== + dependencies: + p-locate "^4.1.0" + +merge-stream@^2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz" + integrity sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w== + +micromatch@^4.0.0: + version "4.0.8" + resolved "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz" + integrity sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA== + dependencies: + braces "^3.0.3" + picomatch "^2.3.1" + +mime-db@1.52.0: + version "1.52.0" + resolved "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz" + integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg== + +mime-types@^2.1.27: + version "2.1.35" + resolved "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz" + integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw== + dependencies: + mime-db "1.52.0" + +minimist@^1.2.6: + version "1.2.8" + resolved "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz" + integrity sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA== + +neo-async@^2.6.2: + version "2.6.2" + resolved "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz" + integrity sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw== + +node-loader@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/node-loader/-/node-loader-2.0.0.tgz#9109a6d828703fd3e0aa03c1baec12a798071562" + integrity sha512-I5VN34NO4/5UYJaUBtkrODPWxbobrE4hgDqPrjB25yPkonFhCmZ146vTH+Zg417E9Iwoh1l/MbRs1apc5J295Q== + dependencies: + loader-utils "^2.0.0" + +node-releases@^2.0.18: + version "2.0.18" + resolved "https://registry.npmjs.org/node-releases/-/node-releases-2.0.18.tgz" + integrity sha512-d9VeXT4SJ7ZeOqGX6R5EM022wpL+eWPooLI+5UpWn2jCT1aosUQEhQP214x33Wkwx3JQMvIm+tIoVOdodFS40g== + +p-limit@^2.2.0: + version "2.3.0" + resolved "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz" + integrity sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w== + dependencies: + p-try "^2.0.0" + +p-locate@^4.1.0: + version "4.1.0" + resolved "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz" + integrity sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A== + dependencies: + p-limit "^2.2.0" + +p-try@^2.0.0: + version "2.2.0" + resolved "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz" + integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ== + +path-exists@^4.0.0: + version "4.0.0" + resolved "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz" + integrity sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w== + +path-key@^3.1.0: + version "3.1.1" + resolved "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz" + integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q== + +path-parse@^1.0.7: + version "1.0.7" + resolved "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz" + integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw== + +picocolors@^1.1.0: + version "1.1.1" + resolved "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz" + integrity sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA== + +picomatch@^2.3.1: + version "2.3.1" + resolved "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz" + integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== + +pkg-dir@^4.2.0: + version "4.2.0" + resolved "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz" + integrity sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ== + dependencies: + find-up "^4.0.0" + +punycode@^2.1.0: + version "2.3.1" + resolved "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz" + integrity sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg== + +randombytes@^2.1.0: + version "2.1.0" + resolved "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz" + integrity sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ== + dependencies: + safe-buffer "^5.1.0" + +rechoir@^0.8.0: + version "0.8.0" + resolved "https://registry.npmjs.org/rechoir/-/rechoir-0.8.0.tgz" + integrity sha512-/vxpCXddiX8NGfGO/mTafwjq4aFa/71pvamip0++IQk3zG8cbCj0fifNPrjjF1XMXUne91jL9OoxmdykoEtifQ== + dependencies: + resolve "^1.20.0" + +resolve-cwd@^3.0.0: + version "3.0.0" + resolved "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz" + integrity sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg== + dependencies: + resolve-from "^5.0.0" + +resolve-from@^5.0.0: + version "5.0.0" + resolved "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz" + integrity sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw== + +resolve@^1.20.0: + version "1.22.8" + resolved "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz" + integrity sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw== + dependencies: + is-core-module "^2.13.0" + path-parse "^1.0.7" + supports-preserve-symlinks-flag "^1.0.0" + +safe-buffer@^5.1.0: + version "5.2.1" + resolved "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz" + integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== + +sax@>=0.6.0: + version "1.4.1" + resolved "https://registry.npmjs.org/sax/-/sax-1.4.1.tgz" + integrity sha512-+aWOz7yVScEGoKNd4PA10LZ8sk0A/z5+nXQG5giUO5rprX9jgYsTdov9qCchZiPIZezbZH+jRut8nPodFAX4Jg== + +schema-utils@^3.1.1, schema-utils@^3.2.0: + version "3.3.0" + resolved "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz" + integrity sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg== + dependencies: + "@types/json-schema" "^7.0.8" + ajv "^6.12.5" + ajv-keywords "^3.5.2" + +semver@^7.3.4, semver@^7.6.3: + version "7.6.3" + resolved "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz" + integrity sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A== + +serialize-javascript@^6.0.1: + version "6.0.2" + resolved "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz" + integrity sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g== + dependencies: + randombytes "^2.1.0" + +shallow-clone@^3.0.0: + version "3.0.1" + resolved "https://registry.npmjs.org/shallow-clone/-/shallow-clone-3.0.1.tgz" + integrity sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA== + dependencies: + kind-of "^6.0.2" + +sharp@^0.33.1: + version "0.33.5" + resolved "https://registry.npmjs.org/sharp/-/sharp-0.33.5.tgz" + integrity sha512-haPVm1EkS9pgvHrQ/F3Xy+hgcuMV0Wm9vfIBSiwZ05k+xgb0PkBQpGsAA/oWdDobNaZTH5ppvHtzCFbnSEwHVw== + dependencies: + color "^4.2.3" + detect-libc "^2.0.3" + semver "^7.6.3" + optionalDependencies: + "@img/sharp-darwin-arm64" "0.33.5" + "@img/sharp-darwin-x64" "0.33.5" + "@img/sharp-libvips-darwin-arm64" "1.0.4" + "@img/sharp-libvips-darwin-x64" "1.0.4" + "@img/sharp-libvips-linux-arm" "1.0.5" + "@img/sharp-libvips-linux-arm64" "1.0.4" + "@img/sharp-libvips-linux-s390x" "1.0.4" + "@img/sharp-libvips-linux-x64" "1.0.4" + "@img/sharp-libvips-linuxmusl-arm64" "1.0.4" + "@img/sharp-libvips-linuxmusl-x64" "1.0.4" + "@img/sharp-linux-arm" "0.33.5" + "@img/sharp-linux-arm64" "0.33.5" + "@img/sharp-linux-s390x" "0.33.5" + "@img/sharp-linux-x64" "0.33.5" + "@img/sharp-linuxmusl-arm64" "0.33.5" + "@img/sharp-linuxmusl-x64" "0.33.5" + "@img/sharp-wasm32" "0.33.5" + "@img/sharp-win32-ia32" "0.33.5" + "@img/sharp-win32-x64" "0.33.5" + +shebang-command@^2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz" + integrity sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA== + dependencies: + shebang-regex "^3.0.0" + +shebang-regex@^3.0.0: + version "3.0.0" + resolved "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz" + integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== + +simple-swizzle@^0.2.2: + version "0.2.2" + resolved "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz" + integrity sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg== + dependencies: + is-arrayish "^0.3.1" + +source-map-support@~0.5.20: + version "0.5.21" + resolved "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz" + integrity sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w== + dependencies: + buffer-from "^1.0.0" + source-map "^0.6.0" + +source-map@^0.6.0: + version "0.6.1" + resolved "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz" + integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== + +source-map@^0.7.4: + version "0.7.4" + resolved "https://registry.npmjs.org/source-map/-/source-map-0.7.4.tgz" + integrity sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA== + +strip-bom@^3.0.0: + version "3.0.0" + resolved "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz" + integrity sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA== + +supports-color@^7.1.0: + version "7.2.0" + resolved "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz" + integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw== + dependencies: + has-flag "^4.0.0" + +supports-color@^8.0.0: + version "8.1.1" + resolved "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz" + integrity sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q== + dependencies: + has-flag "^4.0.0" + +supports-preserve-symlinks-flag@^1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz" + integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w== + +tapable@^2.1.1, tapable@^2.2.0: + version "2.2.1" + resolved "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz" + integrity sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ== + +terser-webpack-plugin@^5.3.10: + version "5.3.10" + resolved "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.10.tgz" + 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@^5.26.0: + version "5.36.0" + resolved "https://registry.npmjs.org/terser/-/terser-5.36.0.tgz" + integrity sha512-IYV9eNMuFAV4THUspIRXkLakHnV6XO7FEdtKjf/mDyrnqUg9LnlOn6/RwRvM9SZjR4GUq8Nk8zj67FzVARr74w== + dependencies: + "@jridgewell/source-map" "^0.3.3" + acorn "^8.8.2" + commander "^2.20.0" + source-map-support "~0.5.20" + +to-regex-range@^5.0.1: + version "5.0.1" + resolved "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz" + integrity sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ== + dependencies: + is-number "^7.0.0" + +ts-loader@^9.4.4: + version "9.5.1" + resolved "https://registry.npmjs.org/ts-loader/-/ts-loader-9.5.1.tgz" + 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" + +tsconfig-paths-webpack-plugin@^4.1.0: + version "4.1.0" + resolved "https://registry.npmjs.org/tsconfig-paths-webpack-plugin/-/tsconfig-paths-webpack-plugin-4.1.0.tgz" + integrity sha512-xWFISjviPydmtmgeUAuXp4N1fky+VCtfhOkDUFIv5ea7p4wuTomI4QTrXvFBX2S4jZsmyTSrStQl+E+4w+RzxA== + dependencies: + chalk "^4.1.0" + enhanced-resolve "^5.7.0" + tsconfig-paths "^4.1.2" + +tsconfig-paths@^4.1.2: + version "4.2.0" + resolved "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-4.2.0.tgz" + integrity sha512-NoZ4roiN7LnbKn9QqE1amc9DJfzvZXxF4xDavcOWt1BPkdx+m+0gJuPM+S0vCe7zTJMYUP0R8pO2XMr+Y8oLIg== + dependencies: + json5 "^2.2.2" + minimist "^1.2.6" + strip-bom "^3.0.0" + +tslib@^2.4.0: + version "2.8.0" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.8.0.tgz#d124c86c3c05a40a91e6fdea4021bd31d377971b" + integrity sha512-jWVzBLplnCmoaTr13V9dYbiQ99wvZRd0vNWaDRg+aVYRcjDF3nDksxFDE/+fkXnKhpnUUkmx5pK/v8mCtLVqZA== + +undici-types@~6.19.2: + version "6.19.8" + resolved "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz" + integrity sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw== + +update-browserslist-db@^1.1.1: + version "1.1.1" + resolved "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.1.tgz" + integrity sha512-R8UzCaa9Az+38REPiJ1tXlImTJXlVfgHZsglwBD/k6nj76ctsH1E3q4doGrukiLQd3sGQYu56r5+lo5r94l29A== + dependencies: + escalade "^3.2.0" + picocolors "^1.1.0" + +uri-js@^4.2.2: + version "4.4.1" + resolved "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz" + integrity sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg== + dependencies: + punycode "^2.1.0" + +watchpack@^2.4.1: + version "2.4.2" + resolved "https://registry.npmjs.org/watchpack/-/watchpack-2.4.2.tgz" + integrity sha512-TnbFSbcOCcDgjZ4piURLCbJ3nJhznVh9kw6F6iokjiFPl8ONxe9A6nMDVXDiNbrSfLILs6vB07F7wLBrwPYzJw== + dependencies: + glob-to-regexp "^0.4.1" + graceful-fs "^4.1.2" + +webpack-cli@^5.1.4: + version "5.1.4" + resolved "https://registry.npmjs.org/webpack-cli/-/webpack-cli-5.1.4.tgz" + integrity sha512-pIDJHIEI9LR0yxHXQ+Qh95k2EvXpWzZ5l+d+jIo+RdSm9MiHfzazIxwwni/p7+x4eJZuvG1AJwgC4TNQ7NRgsg== + dependencies: + "@discoveryjs/json-ext" "^0.5.0" + "@webpack-cli/configtest" "^2.1.1" + "@webpack-cli/info" "^2.0.2" + "@webpack-cli/serve" "^2.0.5" + colorette "^2.0.14" + commander "^10.0.1" + cross-spawn "^7.0.3" + envinfo "^7.7.3" + fastest-levenshtein "^1.0.12" + import-local "^3.0.2" + interpret "^3.1.1" + rechoir "^0.8.0" + webpack-merge "^5.7.3" + +webpack-merge@^5.7.3: + version "5.10.0" + resolved "https://registry.npmjs.org/webpack-merge/-/webpack-merge-5.10.0.tgz" + integrity sha512-+4zXKdx7UnO+1jaN4l2lHVD+mFvnlZQP/6ljaJVb4SZiwIKeUnrT5l0gkT8z+n4hKpC+jpOv6O9R+gLtag7pSA== + dependencies: + clone-deep "^4.0.1" + flat "^5.0.2" + wildcard "^2.0.0" + +webpack-sources@^3.2.3: + version "3.2.3" + resolved "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.2.3.tgz" + integrity sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w== + +webpack@^5.88.2: + version "5.95.0" + resolved "https://registry.npmjs.org/webpack/-/webpack-5.95.0.tgz" + integrity sha512-2t3XstrKULz41MNMBF+cJ97TyHdyQ8HCt//pqErqDvNjU9YQBnZxIHa11VXsi7F3mb5/aO2tuDxdeTPdU7xu9Q== + dependencies: + "@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-attributes "^1.9.5" + browserslist "^4.21.10" + chrome-trace-event "^1.0.2" + enhanced-resolve "^5.17.1" + 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" + +which@^2.0.1: + version "2.0.2" + resolved "https://registry.npmjs.org/which/-/which-2.0.2.tgz" + integrity sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA== + dependencies: + isexe "^2.0.0" + +wildcard@^2.0.0: + version "2.0.1" + resolved "https://registry.npmjs.org/wildcard/-/wildcard-2.0.1.tgz" + integrity sha512-CC1bOL87PIWSBhDcTrdeLo6eGT7mCFtrg0uIJtqJUFyK+eJnzl8A1niH56uu7KMa5XFrtiV+AQuHO3n7DsHnLQ== + +xml2js@^0.6.1: + version "0.6.2" + resolved "https://registry.npmjs.org/xml2js/-/xml2js-0.6.2.tgz" + integrity sha512-T4rieHaC1EXcES0Kxxj4JWgaUQHDk+qwHcYOCFHfiwKz7tOVPLq7Hjq9dM1WCMhylqMEfP7hMcOIChvotiZegA== + dependencies: + sax ">=0.6.0" + xmlbuilder "~11.0.0" + +xmlbuilder@~11.0.0: + version "11.0.1" + resolved "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-11.0.1.tgz" + integrity sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA== diff --git a/deploy/tools/feature-reporter/yarn.lock b/deploy/tools/feature-reporter/yarn.lock index eaf396937e..783abddc02 100644 --- a/deploy/tools/feature-reporter/yarn.lock +++ b/deploy/tools/feature-reporter/yarn.lock @@ -39,7 +39,15 @@ resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz#d7c6e6755c78567a951e04ab52ef0fd26de59f32" integrity sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg== -"@jridgewell/trace-mapping@^0.3.17", "@jridgewell/trace-mapping@^0.3.9": +"@jridgewell/trace-mapping@^0.3.20": + version "0.3.25" + resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz#15f190e98895f3fc23276ee14bc76b675c2e50f0" + integrity sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ== + dependencies: + "@jridgewell/resolve-uri" "^3.1.0" + "@jridgewell/sourcemap-codec" "^1.4.14" + +"@jridgewell/trace-mapping@^0.3.9": version "0.3.19" resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.19.tgz#f8a3249862f91be48d3127c3cfe992f79b4b8811" integrity sha512-kf37QtfW+Hwx/buWGMPcR60iF9ziHa6r/CZJIHbmcm4+0qrXiVdxegAH0F6yddEVQ7zdkjcGCgCzUu+BcbhQxw== @@ -68,28 +76,12 @@ "@nodelib/fs.scandir" "2.1.5" fastq "^1.6.0" -"@types/eslint-scope@^3.7.3": - version "3.7.4" - resolved "https://registry.yarnpkg.com/@types/eslint-scope/-/eslint-scope-3.7.4.tgz#37fc1223f0786c39627068a12e94d6e6fc61de16" - integrity sha512-9K4zoImiZc3HlIp6AVUDE4CWYx22a+lhSZMYNpbjW04+YF0KWj4pJXnEMjdnFTiQibFFmElcsasJXDbdI/EPhA== - dependencies: - "@types/eslint" "*" - "@types/estree" "*" +"@types/estree@^1.0.5": + version "1.0.6" + resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.6.tgz#628effeeae2064a1b4e79f78e81d87b7e5fc7b50" + integrity "sha1-Yo7/7q4gZKG055946B2Ht+X8e1A= sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==" -"@types/eslint@*": - version "8.44.2" - resolved "https://registry.yarnpkg.com/@types/eslint/-/eslint-8.44.2.tgz#0d21c505f98a89b8dd4d37fa162b09da6089199a" - integrity sha512-sdPRb9K6iL5XZOmBubg8yiFp5yS/JdUDQsq5e6h95km91MCYMuvp7mh1fjPEYUhvHepKpZOjnEaMBR4PxjWDzg== - dependencies: - "@types/estree" "*" - "@types/json-schema" "*" - -"@types/estree@*", "@types/estree@^1.0.0": - version "1.0.1" - resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.1.tgz#aa22750962f3bf0e79d753d3cc067f010c95f194" - integrity sha512-LG4opVs2ANWZ1TJoKc937iMmNstM/d0ae1vNbnBvBhqCSezgVUOzcLCqbI5elV8Vy6WKwKjaqR+zO9VKirBBCA== - -"@types/json-schema@*", "@types/json-schema@^7.0.8": +"@types/json-schema@^7.0.8": version "7.0.12" resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.12.tgz#d70faba7039d5fca54c83c7dbab41051d2b6f6cb" integrity sha512-Hr5Jfhc9eYOQNPYO5WLDq/n4jqijdHNlDXjuAQkkt+mWdQR+XJToOHrsD4cPaMXpn6KO7y2+wM8AZEs8VpBLVA== @@ -99,10 +91,10 @@ resolved "https://registry.yarnpkg.com/@types/node/-/node-20.4.8.tgz#b5dda19adaa473a9bf0ab5cbd8f30ec7d43f5c85" integrity sha512-0mHckf6D2DiIAzh8fM8f3HQCvMKDpK94YQ0DSVkfWTG9BZleYIWudw9cJxX8oCk9bM+vAkDyujDV6dmKHbvQpg== -"@webassemblyjs/ast@1.11.6", "@webassemblyjs/ast@^1.11.5": - version "1.11.6" - resolved "https://registry.yarnpkg.com/@webassemblyjs/ast/-/ast-1.11.6.tgz#db046555d3c413f8966ca50a95176a0e2c642e24" - integrity sha512-IN1xI7PwOvLPgjcf180gC1bqn3q/QaOCwYUahIOhbYUu8KA/3tw2RT/T0Gidi1l7Hhj5D/INhJxiICObqpMu4Q== +"@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" @@ -117,10 +109,10 @@ 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-buffer@1.11.6": - version "1.11.6" - resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-buffer/-/helper-buffer-1.11.6.tgz#b66d73c43e296fd5e88006f18524feb0f2c7c093" - integrity sha512-z3nFzdcp1mb8nEOFFk8DrYLpHvhKC3grJD2ardfKOzmbmJvEf/tPIqCY+sNcwZIY8ZD7IkB2l7/pqhUhqm7hLA== +"@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-numbers@1.11.6": version "1.11.6" @@ -136,15 +128,15 @@ resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.11.6.tgz#bb2ebdb3b83aa26d9baad4c46d4315283acd51e9" integrity sha512-sFFHKwcmBprO9e7Icf0+gddyWYDViL8bpPjJJl0WHxCdETktXdmtWLGVzoHbqUcY4Be1LkNfwTmXOJUFZYSJdA== -"@webassemblyjs/helper-wasm-section@1.11.6": - version "1.11.6" - resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.11.6.tgz#ff97f3863c55ee7f580fd5c41a381e9def4aa577" - integrity sha512-LPpZbSOwTpEC2cgn4hTydySy1Ke+XEu+ETXuoyvuyezHO3Kjdu90KK95Sh9xTbmjrCsUwvWwCOQQNta37VrS9g== +"@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.11.6" - "@webassemblyjs/helper-buffer" "1.11.6" + "@webassemblyjs/ast" "1.12.1" + "@webassemblyjs/helper-buffer" "1.12.1" "@webassemblyjs/helper-wasm-bytecode" "1.11.6" - "@webassemblyjs/wasm-gen" "1.11.6" + "@webassemblyjs/wasm-gen" "1.12.1" "@webassemblyjs/ieee754@1.11.6": version "1.11.6" @@ -165,59 +157,59 @@ resolved "https://registry.yarnpkg.com/@webassemblyjs/utf8/-/utf8-1.11.6.tgz#90f8bc34c561595fe156603be7253cdbcd0fab5a" integrity sha512-vtXf2wTQ3+up9Zsg8sa2yWiQpzSsMyXj0qViVP6xKGCUT8p8YJ6HqI7l5eCnWx1T/FYdsv07HQs2wTFbbof/RA== -"@webassemblyjs/wasm-edit@^1.11.5": - version "1.11.6" - resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-edit/-/wasm-edit-1.11.6.tgz#c72fa8220524c9b416249f3d94c2958dfe70ceab" - integrity sha512-Ybn2I6fnfIGuCR+Faaz7YcvtBKxvoLV3Lebn1tM4o/IAJzmi9AWYIPWpyBfU8cC+JxAO57bk4+zdsTjJR+VTOw== +"@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.11.6" - "@webassemblyjs/helper-buffer" "1.11.6" + "@webassemblyjs/ast" "1.12.1" + "@webassemblyjs/helper-buffer" "1.12.1" "@webassemblyjs/helper-wasm-bytecode" "1.11.6" - "@webassemblyjs/helper-wasm-section" "1.11.6" - "@webassemblyjs/wasm-gen" "1.11.6" - "@webassemblyjs/wasm-opt" "1.11.6" - "@webassemblyjs/wasm-parser" "1.11.6" - "@webassemblyjs/wast-printer" "1.11.6" - -"@webassemblyjs/wasm-gen@1.11.6": - version "1.11.6" - resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-gen/-/wasm-gen-1.11.6.tgz#fb5283e0e8b4551cc4e9c3c0d7184a65faf7c268" - integrity sha512-3XOqkZP/y6B4F0PBAXvI1/bky7GryoogUtfwExeP/v7Nzwo1QLcq5oQmpKlftZLbT+ERUOAZVQjuNVak6UXjPA== - dependencies: - "@webassemblyjs/ast" "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-opt@1.11.6": - version "1.11.6" - resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-opt/-/wasm-opt-1.11.6.tgz#d9a22d651248422ca498b09aa3232a81041487c2" - integrity sha512-cOrKuLRE7PCe6AsOVl7WasYf3wbSo4CeOk6PkrjS7g57MFfVUF9u6ysQBBODX0LdgSvQqRiGz3CXvIDKcPNy4g== +"@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.11.6" - "@webassemblyjs/helper-buffer" "1.11.6" - "@webassemblyjs/wasm-gen" "1.11.6" - "@webassemblyjs/wasm-parser" "1.11.6" + "@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-parser@1.11.6", "@webassemblyjs/wasm-parser@^1.11.5": - version "1.11.6" - resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-parser/-/wasm-parser-1.11.6.tgz#bb85378c527df824004812bbdb784eea539174a1" - integrity sha512-6ZwPeGzMJM3Dqp3hCsLgESxBGtT/OeCvCZ4TA1JUPYgmhAx38tTPR9JaKy0S5H3evQpO/h2uWs2j6Yc/fjkpTQ== +"@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.11.6" + "@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/wast-printer@1.11.6": - version "1.11.6" - resolved "https://registry.yarnpkg.com/@webassemblyjs/wast-printer/-/wast-printer-1.11.6.tgz#a7bf8dd7e362aeb1668ff43f35cb849f188eff20" - integrity sha512-JM7AhRcE+yW2GWYaKeHL5vt4xqee5N2WcezptmgyhNS+ScggqcT1OtXykhAb13Sn5Yas0j2uv9tHgrjwvzAP4A== +"@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.11.6" + "@webassemblyjs/ast" "1.12.1" "@xtuc/long" "4.2.2" "@webpack-cli/configtest@^2.1.1": @@ -245,10 +237,10 @@ resolved "https://registry.yarnpkg.com/@xtuc/long/-/long-4.2.2.tgz#d291c6a4e97989b5c61d9acf396ae4fe133a718d" integrity sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ== -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@^8.7.1, acorn@^8.8.2: version "8.10.0" @@ -288,32 +280,32 @@ binary-extensions@^2.0.0: resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.2.0.tgz#75f502eeaf9ffde42fc98829645be4ea76bd9e2d" integrity sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA== -braces@^3.0.2, braces@~3.0.2: - version "3.0.2" - resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107" - integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A== +braces@^3.0.3, braces@~3.0.2: + version "3.0.3" + resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.3.tgz#490332f40919452272d55a8480adc0c441358789" + integrity sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA== dependencies: - fill-range "^7.0.1" + fill-range "^7.1.1" -browserslist@^4.14.5: - version "4.21.10" - resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.21.10.tgz#dbbac576628c13d3b2231332cb2ec5a46e015bb0" - integrity sha512-bipEBdZfVH5/pwrvqc+Ub0kUPVfGUhlKxbvfD+z1BDnPEO/X98ruXGA1WP5ASpAFKan7Qr6j736IacbZQuAlKQ== +browserslist@^4.21.10: + version "4.24.2" + resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.24.2.tgz#f5845bc91069dbd55ee89faf9822e1d885d16580" + integrity "sha1-9YRbyRBp29Ve6J+vmCLh2IXRZYA= sha512-ZIc+Q62revdMcqC6aChtW4jz3My3klmCO1fEmINZY/8J3EpBg5/A/D0AKmBveUh6pgoeycoMkVMko84tuYS+Gg==" dependencies: - caniuse-lite "^1.0.30001517" - electron-to-chromium "^1.4.477" - node-releases "^2.0.13" - update-browserslist-db "^1.0.11" + caniuse-lite "^1.0.30001669" + electron-to-chromium "^1.5.41" + node-releases "^2.0.18" + update-browserslist-db "^1.1.1" buffer-from@^1.0.0: version "1.1.2" resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.2.tgz#2b146a6fd72e80b4f55d255f35ed59a3a9a41bd5" integrity sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ== -caniuse-lite@^1.0.30001517: - version "1.0.30001519" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001519.tgz#3e7b8b8a7077e78b0eb054d69e6edf5c7df35601" - integrity sha512-0QHgqR+Jv4bxHMp8kZ1Kn8CH55OikjKJ6JmKkZYP1F3D7w+lnFXF70nG5eNfsZS89jadi5Ywy5UCSKLAglIRkg== +caniuse-lite@^1.0.30001669: + version "1.0.30001673" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001673.tgz#5aa291557af1c71340e809987367410aab7a5a9e" + integrity "sha1-WqKRVXrxxxNA6AmYc2dBCqt6Wp4= sha512-WTrjUCSMp3LYX0nE12ECkV0a+e6LC85E0Auz75555/qr78Oc8YWhEPNfDd6SHdtlCMSzqtuXY0uyEMNRcsKpKw==" chokidar@^3.5.3: version "3.5.3" @@ -400,15 +392,15 @@ dotenv@^16.0.0: resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-16.3.1.tgz#369034de7d7e5b120972693352a3bf112172cc3e" integrity sha512-IPzF4w4/Rd94bA9imS68tZBaYyBWSCE47V1RGuMrB94iyTOIEwRmVL2x/4An+6mETpLrKJ5hQkB8W4kFAadeIQ== -electron-to-chromium@^1.4.477: - version "1.4.487" - resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.487.tgz#e2ef8b15f2791bf68fa6f38f2656f1a551d360ae" - integrity sha512-XbCRs/34l31np/p33m+5tdBrdXu9jJkZxSbNxj5I0H1KtV2ZMSB+i/HYqDiRzHaFx2T5EdytjoBRe8QRJE2vQg== +electron-to-chromium@^1.5.41: + version "1.5.47" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.5.47.tgz#ef0751bc19b28be8ee44cd8405309de3bf3b20c7" + integrity "sha1-7wdRvBmyi+juRM2EBTCd4787IMc= sha512-zS5Yer0MOYw4rtK2iq43cJagHZ8sXN0jDHDKzB+86gSBSAI4v07S97mcq+Gs2vclAxSh1j7vOAHxSVgduiiuVQ==" -enhanced-resolve@^5.15.0: - version "5.15.0" - resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-5.15.0.tgz#1af946c7d93603eb88e9896cee4904dc012e9c35" - integrity sha512-LXYT42KJ7lpIKECr2mAXIaMldcNCh/7E0KBKOu4KSfkHmP+mZmSs+8V5gBAqisWBy0OO4W5Oyys0GO1Y8KtdKg== +enhanced-resolve@^5.17.1: + version "5.17.1" + resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-5.17.1.tgz#67bfbbcc2f81d511be77d686a90267ef7f898a15" + integrity sha512-LMHl3dXhTcfv8gM4kEzIUeTQ+7fpdA0l2tUf34BddXPkz2A5xJ5L/Pchd5BL6rdccM9QGvu0sWZzK1Z1t4wwyg== dependencies: graceful-fs "^4.2.4" tapable "^2.2.0" @@ -423,10 +415,10 @@ es-module-lexer@^1.2.1: resolved "https://registry.yarnpkg.com/es-module-lexer/-/es-module-lexer-1.3.0.tgz#6be9c9e0b4543a60cd166ff6f8b4e9dae0b0c16f" integrity sha512-vZK7T0N2CBmBOixhmjdqx2gWVbFZ4DXZ/NyRMZVlJXPa7CyFS+/a4QQsDGDQy9ZfEzxFuNEsMLeQJnKP2p5/JA== -escalade@^3.1.1: - version "3.1.1" - resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.1.tgz#d8cfdc7000965c5a0174b4a82eaa5c0552742e40" - integrity sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw== +escalade@^3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.2.0.tgz#011a3f69856ba189dffa7dc8fcce99d2a87903e5" + integrity sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA== eslint-scope@5.1.1: version "5.1.1" @@ -491,10 +483,10 @@ fastq@^1.6.0: dependencies: reusify "^1.0.4" -fill-range@^7.0.1: - version "7.0.1" - resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.0.1.tgz#1919a6a7c75fe38b2c7c77e5198535da9acdda40" - integrity sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ== +fill-range@^7.1.1: + version "7.1.1" + resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.1.1.tgz#44265d3cac07e3ea7dc247516380643754a05292" + integrity sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg== dependencies: to-regex-range "^5.0.1" @@ -540,7 +532,7 @@ globby@^11.0.4: merge2 "^1.4.1" slash "^3.0.0" -graceful-fs@^4.1.2, graceful-fs@^4.2.4, graceful-fs@^4.2.9: +graceful-fs@^4.1.2, graceful-fs@^4.2.11, graceful-fs@^4.2.4: 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== @@ -670,11 +662,11 @@ merge2@^1.3.0, merge2@^1.4.1: integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg== micromatch@^4.0.4: - version "4.0.5" - resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.5.tgz#bc8999a7cbbf77cdc89f132f6e467051b49090c6" - integrity sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA== + version "4.0.8" + resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.8.tgz#d66fa18f3a47076789320b9b1af32bd86d9fa202" + integrity sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA== dependencies: - braces "^3.0.2" + braces "^3.0.3" picomatch "^2.3.1" mime-db@1.52.0: @@ -704,10 +696,10 @@ neo-async@^2.6.2: resolved "https://registry.yarnpkg.com/neo-async/-/neo-async-2.6.2.tgz#b4aafb93e3aeb2d8174ca53cf163ab7d7308305f" integrity sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw== -node-releases@^2.0.13: - version "2.0.13" - resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.13.tgz#d5ed1627c23e3461e819b02e57b75e4899b1c81d" - integrity sha512-uYr7J37ae/ORWdZeQ1xxMJe3NtdmqMC/JZK+geofDrkLUApKRHPd18/TxtBOJ4A0/+uUIliorNrfYV6s1b02eQ== +node-releases@^2.0.18: + version "2.0.18" + resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.18.tgz#f010e8d35e2fe8d6b2944f03f70213ecedc4ca3f" + integrity sha512-d9VeXT4SJ7ZeOqGX6R5EM022wpL+eWPooLI+5UpWn2jCT1aosUQEhQP214x33Wkwx3JQMvIm+tIoVOdodFS40g== normalize-path@^3.0.0, normalize-path@~3.0.0: version "3.0.0" @@ -753,10 +745,10 @@ path-type@^4.0.0: resolved "https://registry.yarnpkg.com/path-type/-/path-type-4.0.0.tgz#84ed01c0a7ba380afe09d90a8c180dcd9d03043b" integrity sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw== -picocolors@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.0.0.tgz#cb5bdc74ff3f51892236eaf79d68bc44564ab81c" - integrity sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ== +picocolors@^1.1.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.1.1.tgz#3d321af3eab939b083c8f929a1d12cda81c26b6b" + integrity "sha1-PTIa8+q5ObCDyPkpodEs2oHCa2s= sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==" picomatch@^2.0.4, picomatch@^2.2.1, picomatch@^2.3.1: version "2.3.1" @@ -921,21 +913,21 @@ tapable@^2.1.1, tapable@^2.2.0: resolved "https://registry.yarnpkg.com/tapable/-/tapable-2.2.1.tgz#1967a73ef4060a82f12ab96af86d52fdb76eeca0" integrity sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ== -terser-webpack-plugin@^5.3.7: - version "5.3.9" - resolved "https://registry.yarnpkg.com/terser-webpack-plugin/-/terser-webpack-plugin-5.3.9.tgz#832536999c51b46d468067f9e37662a3b96adfe1" - integrity sha512-ZuXsqE07EcggTWQjXUj+Aot/OMcD0bMKGgF63f7UxYcu5/AJF53aIpK1YoP5xR9l6s/Hy2b+t1AM0bLNPRuhwA== +terser-webpack-plugin@^5.3.10: + 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.17" + "@jridgewell/trace-mapping" "^0.3.20" jest-worker "^27.4.5" schema-utils "^3.1.1" serialize-javascript "^6.0.1" - terser "^5.16.8" + terser "^5.26.0" -terser@^5.16.8: - version "5.19.2" - resolved "https://registry.yarnpkg.com/terser/-/terser-5.19.2.tgz#bdb8017a9a4a8de4663a7983f45c506534f9234e" - integrity sha512-qC5+dmecKJA4cpYxRa5aVkKehYsQKc+AHeKl0Oe62aYjBL8ZA33tTljktDHJSaxxMnbI5ZYw+o/S2DxxLu8OfA== +terser@^5.26.0: + version "5.36.0" + resolved "https://registry.yarnpkg.com/terser/-/terser-5.36.0.tgz#8b0dbed459ac40ff7b4c9fd5a3a2029de105180e" + integrity "sha1-iw2+1FmsQP97TJ/Vo6ICneEFGA4= sha512-IYV9eNMuFAV4THUspIRXkLakHnV6XO7FEdtKjf/mDyrnqUg9LnlOn6/RwRvM9SZjR4GUq8Nk8zj67FzVARr74w==" dependencies: "@jridgewell/source-map" "^0.3.3" acorn "^8.8.2" @@ -971,13 +963,13 @@ typescript@5.1: resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.1.6.tgz#02f8ac202b6dad2c0dd5e0913745b47a37998274" integrity sha512-zaWCozRZ6DLEWAWFrVDz1H6FVXzUSfTy5FUMWsQlU8Ym5JP9eO4xkTIROFCQvhQf61z6O/G6ugw3SgAnvvm+HA== -update-browserslist-db@^1.0.11: - version "1.0.11" - resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.0.11.tgz#9a2a641ad2907ae7b3616506f4b977851db5b940" - integrity sha512-dCwEFf0/oT85M1fHBg4F0jtLwJrutGoHSQXCh7u4o2t1drG+c0a9Flnqww6XUKSfQMPpJBRjU8d4RXB09qtvaA== +update-browserslist-db@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.1.1.tgz#80846fba1d79e82547fb661f8d141e0945755fe5" + integrity "sha1-gIRvuh156CVH+2YfjRQeCUV1X+U= sha512-R8UzCaa9Az+38REPiJ1tXlImTJXlVfgHZsglwBD/k6nj76ctsH1E3q4doGrukiLQd3sGQYu56r5+lo5r94l29A==" dependencies: - escalade "^3.1.1" - picocolors "^1.0.0" + escalade "^3.2.0" + picocolors "^1.1.0" uri-js@^4.2.2: version "4.4.1" @@ -986,10 +978,10 @@ uri-js@^4.2.2: dependencies: punycode "^2.1.0" -watchpack@^2.4.0: - version "2.4.0" - resolved "https://registry.yarnpkg.com/watchpack/-/watchpack-2.4.0.tgz#fa33032374962c78113f93c7f2fb4c54c9862a5d" - integrity sha512-Lcvm7MGST/4fup+ifyKi2hjyIAwcdI4HRgtvTpIUxBRhB+RFtUh8XtDOxUfctVCnhVi+QQj49i91OyvzkJl6cg== +watchpack@^2.4.1: + version "2.4.2" + resolved "https://registry.yarnpkg.com/watchpack/-/watchpack-2.4.2.tgz#2feeaed67412e7c33184e5a79ca738fbd38564da" + integrity "sha1-L+6u1nQS58MxhOWnnKc4+9OFZNo= sha512-TnbFSbcOCcDgjZ4piURLCbJ3nJhznVh9kw6F6iokjiFPl8ONxe9A6nMDVXDiNbrSfLILs6vB07F7wLBrwPYzJw==" dependencies: glob-to-regexp "^0.4.1" graceful-fs "^4.1.2" @@ -1027,33 +1019,32 @@ webpack-sources@^3.2.3: integrity sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w== webpack@^5.88.2: - version "5.88.2" - resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.88.2.tgz#f62b4b842f1c6ff580f3fcb2ed4f0b579f4c210e" - integrity sha512-JmcgNZ1iKj+aiR0OvTYtWQqJwq37Pf683dY9bVORwVbUrDhLhdn/PlO2sHsFHPkj7sHNQF3JwaAkp49V+Sq1tQ== - dependencies: - "@types/eslint-scope" "^3.7.3" - "@types/estree" "^1.0.0" - "@webassemblyjs/ast" "^1.11.5" - "@webassemblyjs/wasm-edit" "^1.11.5" - "@webassemblyjs/wasm-parser" "^1.11.5" + version "5.95.0" + resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.95.0.tgz#8fd8c454fa60dad186fbe36c400a55848307b4c0" + integrity "sha1-j9jEVPpg2tGG++NsQApVhIMHtMA= sha512-2t3XstrKULz41MNMBF+cJ97TyHdyQ8HCt//pqErqDvNjU9YQBnZxIHa11VXsi7F3mb5/aO2tuDxdeTPdU7xu9Q==" + dependencies: + "@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.14.5" + acorn-import-attributes "^1.9.5" + browserslist "^4.21.10" chrome-trace-event "^1.0.2" - enhanced-resolve "^5.15.0" + enhanced-resolve "^5.17.1" 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.9" + 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.7" - watchpack "^2.4.0" + terser-webpack-plugin "^5.3.10" + watchpack "^2.4.1" webpack-sources "^3.2.3" which@^2.0.1: diff --git a/deploy/values/l2-optimism-goerli/values.yaml b/deploy/values/l2-optimism-goerli/values.yaml deleted file mode 100644 index f743299ddd..0000000000 --- a/deploy/values/l2-optimism-goerli/values.yaml +++ /dev/null @@ -1,203 +0,0 @@ -fullNameOverride: bs-stack -nameOverride: bs-stack -imagePullSecrets: - - name: regcred -config: - network: - id: 420 - name: "Base Göerli" - shortname: Base - currency: - name: Ether - symbol: ETH - decimals: 18 - account: - enabled: true - testnet: true - -blockscout: - enabled: true - image: - repository: blockscout/blockscout-optimism - imagePullPolicy: Always - tag: latest - replicaCount: 1 - ingress: - enabled: true - annotations: - kubernetes.io/ingress.class: internal-and-public - nginx.ingress.kubernetes.io/proxy-body-size: "500m" - nginx.ingress.kubernetes.io/client-max-body-size: "500M" - nginx.ingress.kubernetes.io/proxy-buffering: "off" - nginx.ingress.kubernetes.io/proxy-connect-timeout: "15m" - nginx.ingress.kubernetes.io/proxy-send-timeout: "15m" - nginx.ingress.kubernetes.io/proxy-read-timeout: "15m" - nginx.ingress.kubernetes.io/force-ssl-redirect: "true" - nginx.ingress.kubernetes.io/ssl-redirect: "true" - nginx.ingress.kubernetes.io/cors-allow-origin: "https://*.blockscout-main.k8s-dev.blockscout.com, https://*.k8s-dev.blockscout.com, http://localhost:3000" - nginx.ingress.kubernetes.io/cors-allow-credentials: "true" - nginx.ingress.kubernetes.io/cors-allow-methods: "PUT, GET, POST, OPTIONS, DELETE, PATCH" - nginx.ingress.kubernetes.io/enable-cors: "true" - nginx.ingress.kubernetes.io/cors-allow-headers: "DNT,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range,Authorization,x-csrf-token" - hostname: blockscout-optimism-goerli.k8s-dev.blockscout.com - tls: - enabled: true - resources: - limits: - memory: "4Gi" - cpu: "2" - requests: - memory: "2Gi" - cpu: "1" - - env: - ENV: test - RESOURCE_MODE: account - PUBLIC: 'false' - PORT: 4000 - PORT_PG: 5432 - PORT_NETWORK_HTTP: 8545 - PORT_NETWORK_WS: 8546 - ETHEREUM_JSONRPC_VARIANT: geth - MIX_ENV: prod - MICROSERVICE_SC_VERIFIER_ENABLED: 'true' - MICROSERVICE_SC_VERIFIER_TYPE: 'eth_bytecode_db' - MICROSERVICE_SC_VERIFIER_URL: https://sc-verifier-test.k8s-dev.blockscout.com - DISABLE_EXCHANGE_RATES: 'true' - APPS_MENU: 'true' - EXTERNAL_APPS: '[{"title": "Marketplace", "url": "/apps"}]' - JSON_RPC: https://goerli.optimism.io - FIRST_BLOCK: '4667000' - TRACE_FIRST_BLOCK: '4667000' - LAST_BLOCK: '4677000' - TRACE_LAST_BLOCK: '4677000' - DISABLE_REALTIME_INDEXER: 'false' - INDEXER_OPTIMISM_L1_PORTAL_CONTRACT: 0x5b47E1A08Ea6d985D6649300584e6722Ec4B1383 - INDEXER_OPTIMISM_L1_WITHDRAWALS_START_BLOCK: '8299683' - INDEXER_OPTIMISM_L2_WITHDRAWALS_START_BLOCK: '4066066' - INDEXER_OPTIMISM_L2_MESSAGE_PASSER_CONTRACT: 0x4200000000000000000000000000000000000016 - INDEXER_OPTIMISM_L1_OUTPUT_ROOTS_START_BLOCK: '8299683' - INDEXER_OPTIMISM_L1_OUTPUT_ORACLE_CONTRACT: 0xE6Dfba0953616Bacab0c9A8ecb3a9BBa77FC15c0 - INDEXER_OPTIMISM_L1_BATCH_START_BLOCK: '8381594' - INDEXER_OPTIMISM_L1_BATCH_INBOX: 0xff00000000000000000000000000000000000420 - INDEXER_OPTIMISM_L1_BATCH_SUBMITTER: 0x7431310e026b69bfc676c0013e12a1a11411eec9 - INDEXER_OPTIMISM_L1_DEPOSITS_START_BLOCK: '8381594' - ETHEREUM_JSONRPC_DEBUG_TRACE_TRANSACTION_TIMEOUT: '25s' - envFromSecret: - INDEXER_OPTIMISM_L1_RPC: ref+vault://deployment-values/blockscout/dev/l2-optimism-goerli?token_env=VAULT_TOKEN&address=https://vault.k8s.blockscout.com#/INDEXER_OPTIMISM_L1_RPC - ACCOUNT_USERNAME: ref+vault://deployment-values/blockscout/dev/l2-optimism-goerli?token_env=VAULT_TOKEN&address=https://vault.k8s.blockscout.com#/ACCOUNT_USERNAME - ACCOUNT_PASSWORD: ref+vault://deployment-values/blockscout/dev/l2-optimism-goerli?token_env=VAULT_TOKEN&address=https://vault.k8s.blockscout.com#/ACCOUNT_PASSWORD - MAILSLURP_API_KEY: ref+vault://deployment-values/blockscout/dev/l2-optimism-goerli?token_env=VAULT_TOKEN&address=https://vault.k8s.blockscout.com#/MAILSLURP_API_KEY - MAILSLURP_EMAIL_ID: ref+vault://deployment-values/blockscout/dev/l2-optimism-goerli?token_env=VAULT_TOKEN&address=https://vault.k8s.blockscout.com#/MAILSLURP_EMAIL_ID - ACCOUNT_AUTH0_DOMAIN: ref+vault://deployment-values/blockscout/dev/l2-optimism-goerli?token_env=VAULT_TOKEN&address=https://vault.k8s.blockscout.com#/ACCOUNT_AUTH0_DOMAIN - ACCOUNT_AUTH0_CLIENT_ID: ref+vault://deployment-values/blockscout/dev/l2-optimism-goerli?token_env=VAULT_TOKEN&address=https://vault.k8s.blockscout.com#/ACCOUNT_AUTH0_CLIENT_ID - ACCOUNT_AUTH0_CLIENT_SECRET: ref+vault://deployment-values/blockscout/dev/l2-optimism-goerli?token_env=VAULT_TOKEN&address=https://vault.k8s.blockscout.com#/ACCOUNT_AUTH0_CLIENT_SECRET - ACCOUNT_SENDGRID_API_KEY: ref+vault://deployment-values/blockscout/dev/l2-optimism-goerli?token_env=VAULT_TOKEN&address=https://vault.k8s.blockscout.com#/ACCOUNT_SENDGRID_API_KEY - ACCOUNT_SENDGRID_SENDER: ref+vault://deployment-values/blockscout/dev/l2-optimism-goerli?token_env=VAULT_TOKEN&address=https://vault.k8s.blockscout.com#/ACCOUNT_SENDGRID_SENDER - ACCOUNT_SENDGRID_TEMPLATE: ref+vault://deployment-values/blockscout/dev/l2-optimism-goerli?token_env=VAULT_TOKEN&address=https://vault.k8s.blockscout.com#/ACCOUNT_SENDGRID_TEMPLATE - ACCOUNT_PUBLIC_TAGS_AIRTABLE_URL: ref+vault://deployment-values/blockscout/dev/l2-optimism-goerli?token_env=VAULT_TOKEN&address=https://vault.k8s.blockscout.com#/ACCOUNT_PUBLIC_TAGS_AIRTABLE_URL - ACCOUNT_PUBLIC_TAGS_AIRTABLE_API_KEY: ref+vault://deployment-values/blockscout/dev/l2-optimism-goerli?token_env=VAULT_TOKEN&address=https://vault.k8s.blockscout.com#/ACCOUNT_PUBLIC_TAGS_AIRTABLE_API_KEY - ACCOUNT_CLOAK_KEY: ref+vault://deployment-values/blockscout/dev/l2-optimism-goerli?token_env=VAULT_TOKEN&address=https://vault.k8s.blockscout.com#/ACCOUNT_CLOAK_KEY - SECRET_KEY_BASE: ref+vault://deployment-values/blockscout/dev/l2-optimism-goerli?token_env=VAULT_TOKEN&address=https://vault.k8s.blockscout.com#/SECRET_KEY_BASE - DATABASE_URL: ref+vault://deployment-values/blockscout/dev/l2-optimism-goerli?token_env=VAULT_TOKEN&address=https://vault.k8s.blockscout.com#/DATABASE_URL - ACCOUNT_DATABASE_URL: ref+vault://deployment-values/blockscout/dev/l2-optimism-goerli?token_env=VAULT_TOKEN&address=https://vault.k8s.blockscout.com#/ACCOUNT_DATABASE_URL - ACCOUNT_REDIS_URL: ref+vault://deployment-values/blockscout/dev/l2-optimism-goerli?token_env=VAULT_TOKEN&address=https://vault.k8s.blockscout.com#/ACCOUNT_REDIS_URL - ETHEREUM_JSONRPC_TRACE_URL: ref+vault://deployment-values/blockscout/dev/l2-optimism-goerli?token_env=VAULT_TOKEN&address=https://vault.k8s.blockscout.com#/ETHEREUM_JSONRPC_TRACE_URL - ETHEREUM_JSONRPC_HTTP_URL: ref+vault://deployment-values/blockscout/dev/l2-optimism-goerli?token_env=VAULT_TOKEN&address=https://vault.k8s.blockscout.com#/ETHEREUM_JSONRPC_HTTP_URL - ETHEREUM_JSONRPC_WS_URL: ref+vault://deployment-values/blockscout/dev/l2-optimism-goerli?token_env=VAULT_TOKEN&address=https://vault.k8s.blockscout.com#/ETHEREUM_JSONRPC_WS_URL - RE_CAPTCHA_SECRET_KEY: ref+vault://deployment-values/blockscout/common?token_env=VAULT_TOKEN&address=https://vault.k8s.blockscout.com#/RE_CAPTCHA_SECRET_KEY - RE_CAPTCHA_CLIENT_KEY: ref+vault://deployment-values/blockscout/common?token_env=VAULT_TOKEN&address=https://vault.k8s.blockscout.com#/RE_CAPTCHA_CLIENT_KEY - -stats: - enabled: true - image: - tag: main - pullPolicy: Always - - ingress: - enabled: true - annotations: - kubernetes.io/ingress.class: internal-and-public - nginx.ingress.kubernetes.io/proxy-body-size: "500m" - nginx.ingress.kubernetes.io/client-max-body-size: "500M" - nginx.ingress.kubernetes.io/proxy-buffering: "off" - nginx.ingress.kubernetes.io/proxy-connect-timeout: "15m" - nginx.ingress.kubernetes.io/proxy-send-timeout: "15m" - nginx.ingress.kubernetes.io/proxy-read-timeout: "15m" - nginx.ingress.kubernetes.io/force-ssl-redirect: "true" - nginx.ingress.kubernetes.io/ssl-redirect: "true" - nginx.ingress.kubernetes.io/cors-allow-origin: "https://*.blockscout-main.k8s-dev.blockscout.com, https://*.k8s-dev.blockscout.com, http://localhost:3000" - nginx.ingress.kubernetes.io/cors-allow-credentials: "true" - nginx.ingress.kubernetes.io/cors-allow-methods: "PUT, GET, POST, OPTIONS, DELETE, PATCH" - nginx.ingress.kubernetes.io/enable-cors: "true" - hostname: stats-optimism-goerli.k8s-dev.blockscout.com - tls: - enabled: true - - resources: - limits: - cpu: 250m - memory: 512Mi - requests: - cpu: 250m - memory: 256Mi - env: - RUST_LOG: info - STATS__RUN_MIGRATIONS: true - STATS__TRACING__FORMAT: json - STATS__METRICS__ENABLED: true - envFromSecret: - STATS__DB_URL: ref+vault://deployment-values/blockscout/dev/l2-optimism-goerli?token_env=VAULT_TOKEN&address=https://vault.k8s.blockscout.com#/STATS__DB_URL - STATS__BLOCKSCOUT_DB_URL: ref+vault://deployment-values/blockscout/dev/l2-optimism-goerli?token_env=VAULT_TOKEN&address=https://vault.k8s.blockscout.com#/STATS__BLOCKSCOUT_DB_URL - -frontend: - enabled: true - image: - pullPolicy: Always - replicaCount: 1 - ingress: - enabled: true - hostname: blockscout-optimism-goerli.k8s-dev.blockscout.com - annotations: - kubernetes.io/ingress.class: internal-and-public - nginx.ingress.kubernetes.io/proxy-body-size: 500m - nginx.ingress.kubernetes.io/client-max-body-size: "500M" - nginx.ingress.kubernetes.io/proxy-buffering: "off" - nginx.ingress.kubernetes.io/proxy-connect-timeout: "15m" - nginx.ingress.kubernetes.io/proxy-send-timeout: "15m" - nginx.ingress.kubernetes.io/proxy-read-timeout: "15m" - cert-manager.io/cluster-issuer: "zerossl-prod" - resources: - limits: - cpu: 200m - memory: 512Mi - requests: - cpu: 200m - memory: 256Mi - env: - NEXT_PUBLIC_NETWORK_VERIFICATION_TYPE: validation - NEXT_PUBLIC_NETWORK_LOGO: https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/network-logos/base.svg - NEXT_PUBLIC_NETWORK_ICON: https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/network-icons/base.svg - NEXT_PUBLIC_FEATURED_NETWORKS: https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/featured-networks/base-goerli.json - NEXT_PUBLIC_MARKETPLACE_SUBMIT_FORM: https://airtable.com/shrqUAcjgGJ4jU88C - NEXT_PUBLIC_LOGOUT_URL: https://blockscoutcom.us.auth0.com/v2/logout - NEXT_PUBLIC_MARKETPLACE_CONFIG_URL: https://raw.githubusercontent.com/blockscout/frontend-configs/dev/configs/marketplace/base-goerli.json - NEXT_PUBLIC_MARKETPLACE_CATEGORIES_URL: https://raw.githubusercontent.com/blockscout/frontend-configs/dev/configs/marketplace-categories/default.json - NEXT_PUBLIC_HOMEPAGE_PLATE_BACKGROUND: "linear-gradient(136.9deg,rgb(107 94 236) 1.5%,rgb(0 82 255) 56.84%,rgb(82 62 231) 98.54%)" - NEXT_PUBLIC_NETWORK_RPC_URL: https://goerli.optimism.io - NEXT_PUBLIC_WEB3_WALLETS: "['coinbase']" - NEXT_PUBLIC_WEB3_DISABLE_ADD_TOKEN_TO_WALLET: true - NEXT_PUBLIC_HOMEPAGE_CHARTS: "['daily_txs']" - NEXT_PUBLIC_VISUALIZE_API_HOST: https://visualizer-test.k8s-dev.blockscout.com - NEXT_PUBLIC_CONTRACT_INFO_API_HOST: https://contracts-info-test.k8s-dev.blockscout.com - NEXT_PUBLIC_ADMIN_SERVICE_API_HOST: https://admin-rs-test.k8s-dev.blockscout.com - NEXT_PUBLIC_IS_OPTIMISTIC_L2_NETWORK: "true" - NEXT_PUBLIC_L1_BASE_URL: https://eth-goerli.blockscout.com/ - NEXT_PUBLIC_OPTIMISTIC_L2_WITHDRAWAL_URL: https://app.optimism.io/bridge/withdraw - NEXT_PUBLIC_GRAPHIQL_TRANSACTION: 0x4a0ed8ddf751a7cb5297f827699117b0f6d21a0b2907594d300dc9fed75c7e62 - envFromSecret: - NEXT_PUBLIC_AUTH0_CLIENT_ID: ref+vault://deployment-values/blockscout/dev/front-main?token_env=VAULT_TOKEN&address=https://vault.k8s.blockscout.com#/NEXT_PUBLIC_AUTH0_CLIENT_ID - NEXT_PUBLIC_WALLET_CONNECT_PROJECT_ID: ref+vault://deployment-values/blockscout/dev/front-main?token_env=VAULT_TOKEN&address=https://vault.k8s.blockscout.com#/NEXT_PUBLIC_WALLET_CONNECT_PROJECT_ID - NEXT_PUBLIC_RE_CAPTCHA_APP_SITE_KEY: ref+vault://deployment-values/blockscout/dev/front-main?token_env=VAULT_TOKEN&address=https://vault.k8s.blockscout.com#/NEXT_PUBLIC_RE_CAPTCHA_APP_SITE_KEY - NEXT_PUBLIC_GOOGLE_ANALYTICS_PROPERTY_ID: ref+vault://deployment-values/blockscout/dev/front-main?token_env=VAULT_TOKEN&address=https://vault.k8s.blockscout.com#/NEXT_PUBLIC_GOOGLE_ANALYTICS_PROPERTY_ID - FAVICON_GENERATOR_API_KEY: ref+vault://deployment-values/blockscout/common?token_env=VAULT_TOKEN&address=https://vault.k8s.blockscout.com#/NEXT_PUBLIC_FAVICON_GENERATOR_API_KEY diff --git a/deploy/values/main/values.yaml b/deploy/values/main/values.yaml deleted file mode 100644 index 28e3752f42..0000000000 --- a/deploy/values/main/values.yaml +++ /dev/null @@ -1,173 +0,0 @@ -fullNameOverride: bs-stack -nameOverride: bs-stack -imagePullSecrets: - - name: regcred -config: - network: - id: 5 - name: Göerli - shortname: Göerli - currency: - name: Ether - symbol: ETH - decimals: 18 - account: - enabled: true - testnet: true - - -blockscout: - enabled: true - image: - pullPolicy: Always - tag: frontend-main - replicaCount: 1 - # enable ingress - ingress: - enabled: true - annotations: - kubernetes.io/ingress.class: internal-and-public - nginx.ingress.kubernetes.io/proxy-body-size: "500m" - nginx.ingress.kubernetes.io/client-max-body-size: "500M" - nginx.ingress.kubernetes.io/proxy-buffering: "off" - nginx.ingress.kubernetes.io/proxy-connect-timeout: "15m" - nginx.ingress.kubernetes.io/proxy-send-timeout: "15m" - nginx.ingress.kubernetes.io/proxy-read-timeout: "15m" - nginx.ingress.kubernetes.io/force-ssl-redirect: "true" - nginx.ingress.kubernetes.io/ssl-redirect: "true" - nginx.ingress.kubernetes.io/cors-allow-origin: "https://*.k8s-dev.blockscout.com, http://localhost:3000" - nginx.ingress.kubernetes.io/cors-allow-credentials: "true" - nginx.ingress.kubernetes.io/cors-allow-methods: "PUT, GET, POST, OPTIONS, DELETE, PATCH" - nginx.ingress.kubernetes.io/enable-cors: "true" - nginx.ingress.kubernetes.io/cors-allow-headers: "DNT,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range,Authorization,x-csrf-token" - nginx.ingress.kubernetes.io/cors-expose-headers: "x-bs-account-csrf" - hostname: blockscout-main.k8s-dev.blockscout.com - tls: - enabled: true - - resources: - limits: - memory: "6Gi" - cpu: "6" - requests: - memory: "3Gi" - cpu: "3" - # Blockscout environment variables - env: - BLOCKSCOUT_VERSION: v5.3.0-beta - ETHEREUM_JSONRPC_VARIANT: geth - HEART_BEAT_TIMEOUT: 30 - SUBNETWORK: Ethereum - NETWORK: (Goerli) - NETWORK_ICON: _network_icon.html - LOGO: /images/goerli_logo.svg - TXS_STATS_DAYS_TO_COMPILE_AT_INIT: 1 - COIN_BALANCE_HISTORY_DAYS: 90 - POOL_SIZE: 100 - DISPLAY_TOKEN_ICONS: 'true' - FETCH_REWARDS_WAY: manual - MICROSERVICE_SC_VERIFIER_ENABLED: 'true' - MICROSERVICE_SC_VERIFIER_TYPE: 'eth_bytecode_db' - MICROSERVICE_SC_VERIFIER_URL: http://eth-bytecode-db-svc.eth-bytecode-db-testing.svc.cluster.local:80 - INDEXER_MEMORY_LIMIT: 5 - APPS_MENU: 'true' - APPS: '[{"title": "Marketplace", "url": "/apps", "embedded?": true}]' - SESSION_COOKIE_DOMAIN: blockscout-main.k8s-dev.blockscout.com - ETHEREUM_JSONRPC_DEBUG_TRACE_TRANSACTION_TIMEOUT: '20s' - INDEXER_INTERNAL_TRANSACTIONS_BATCH_SIZE: 15 - INDEXER_DISABLE_EMPTY_BLOCKS_SANITIZER: 'true' - INDEXER_DISABLE_PENDING_TRANSACTIONS_FETCHER: 'true' - INDEXER_RECEIPTS_BATCH_SIZE: 50 - INDEXER_COIN_BALANCES_BATCH_SIZE: 50 - DISABLE_EXCHANGE_RATES: 'true' - DISABLE_INDEXER: 'false' - FIRST_BLOCK: '8739119' - LAST_BLOCK: '8739119' - TRACE_FIRST_BLOCK: '8739119' - TRACE_LAST_BLOCK: '8739119' - envFromSecret: - ETHEREUM_JSONRPC_TRACE_URL: ref+vault://deployment-values/blockscout/dev/front-main?token_env=VAULT_TOKEN&address=https://vault.k8s.blockscout.com#/ETHEREUM_JSONRPC_TRACE_URL - ETHEREUM_JSONRPC_HTTP_URL: ref+vault://deployment-values/blockscout/dev/front-main?token_env=VAULT_TOKEN&address=https://vault.k8s.blockscout.com#/ETHEREUM_JSONRPC_HTTP_URL - ACCOUNT_USERNAME: ref+vault://deployment-values/blockscout/dev/front-main?token_env=VAULT_TOKEN&address=https://vault.k8s.blockscout.com#/ACCOUNT_USERNAME - ACCOUNT_PASSWORD: ref+vault://deployment-values/blockscout/dev/front-main?token_env=VAULT_TOKEN&address=https://vault.k8s.blockscout.com#/ACCOUNT_PASSWORD - MAILSLURP_API_KEY: ref+vault://deployment-values/blockscout/dev/front-main?token_env=VAULT_TOKEN&address=https://vault.k8s.blockscout.com#/MAILSLURP_API_KEY - MAILSLURP_EMAIL_ID: ref+vault://deployment-values/blockscout/dev/front-main?token_env=VAULT_TOKEN&address=https://vault.k8s.blockscout.com#/MAILSLURP_EMAIL_ID - ACCOUNT_SENDGRID_API_KEY: ref+vault://deployment-values/blockscout/dev/front-main?token_env=VAULT_TOKEN&address=https://vault.k8s.blockscout.com#/ACCOUNT_SENDGRID_API_KEY - ACCOUNT_SENDGRID_SENDER: ref+vault://deployment-values/blockscout/dev/front-main?token_env=VAULT_TOKEN&address=https://vault.k8s.blockscout.com#/ACCOUNT_SENDGRID_SENDER - ACCOUNT_SENDGRID_TEMPLATE: ref+vault://deployment-values/blockscout/dev/front-main?token_env=VAULT_TOKEN&address=https://vault.k8s.blockscout.com#/ACCOUNT_SENDGRID_TEMPLATE - ACCOUNT_PUBLIC_TAGS_AIRTABLE_URL: ref+vault://deployment-values/blockscout/dev/front-main?token_env=VAULT_TOKEN&address=https://vault.k8s.blockscout.com#/ACCOUNT_PUBLIC_TAGS_AIRTABLE_URL - ACCOUNT_PUBLIC_TAGS_AIRTABLE_API_KEY: ref+vault://deployment-values/blockscout/dev/front-main?token_env=VAULT_TOKEN&address=https://vault.k8s.blockscout.com#/ACCOUNT_PUBLIC_TAGS_AIRTABLE_API_KEY - ACCOUNT_CLOAK_KEY: ref+vault://deployment-values/blockscout/dev/front-main?token_env=VAULT_TOKEN&address=https://vault.k8s.blockscout.com#/ACCOUNT_CLOAK_KEY - SECRET_KEY_BASE: ref+vault://deployment-values/blockscout/dev/front-main?token_env=VAULT_TOKEN&address=https://vault.k8s.blockscout.com#/SECRET_KEY_BASE - DATABASE_URL: ref+vault://deployment-values/blockscout/dev/front-main?token_env=VAULT_TOKEN&address=https://vault.k8s.blockscout.com#/DATABASE_URL - DATABASE_READ_ONLY_API_URL: ref+vault://deployment-values/blockscout/dev/front-main?token_env=VAULT_TOKEN&address=https://vault.k8s.blockscout.com#/DATABASE_READ_ONLY_API_URL - API_SENSITIVE_ENDPOINTS_KEY: ref+vault://deployment-values/blockscout/dev/front-main?token_env=VAULT_TOKEN&address=https://vault.k8s.blockscout.com#/API_SENSITIVE_ENDPOINTS_KEY - ACCOUNT_DATABASE_URL: ref+vault://deployment-values/blockscout/dev/front-main?token_env=VAULT_TOKEN&address=https://vault.k8s.blockscout.com#/ACCOUNT_DATABASE_URL - ACCOUNT_REDIS_URL: ref+vault://deployment-values/blockscout/dev/front-main?token_env=VAULT_TOKEN&address=https://vault.k8s.blockscout.com#/ACCOUNT_REDIS_URL - ACCOUNT_AUTH0_DOMAIN: ref+vault://deployment-values/blockscout/dev/front-main?token_env=VAULT_TOKEN&address=https://vault.k8s.blockscout.com#/ACCOUNT_AUTH0_DOMAIN - ACCOUNT_AUTH0_CLIENT_ID: ref+vault://deployment-values/blockscout/dev/front-main?token_env=VAULT_TOKEN&address=https://vault.k8s.blockscout.com#/ACCOUNT_AUTH0_CLIENT_ID - ACCOUNT_AUTH0_CLIENT_SECRET: ref+vault://deployment-values/blockscout/dev/front-main?token_env=VAULT_TOKEN&address=https://vault.k8s.blockscout.com#/ACCOUNT_AUTH0_CLIENT_SECRET - RE_CAPTCHA_SECRET_KEY: ref+vault://deployment-values/blockscout/common?token_env=VAULT_TOKEN&address=https://vault.k8s.blockscout.com#/RE_CAPTCHA_SECRET_KEY - RE_CAPTCHA_CLIENT_KEY: ref+vault://deployment-values/blockscout/common?token_env=VAULT_TOKEN&address=https://vault.k8s.blockscout.com#/RE_CAPTCHA_CLIENT_KEY - -frontend: - enabled: true - image: - tag: main - pullPolicy: Always - replicaCount: 1 - ingress: - enabled: true - annotations: - kubernetes.io/ingress.class: internal-and-public - nginx.ingress.kubernetes.io/proxy-body-size: 500m - nginx.ingress.kubernetes.io/client-max-body-size: "500M" - nginx.ingress.kubernetes.io/proxy-buffering: "off" - nginx.ingress.kubernetes.io/proxy-connect-timeout: "15m" - nginx.ingress.kubernetes.io/proxy-send-timeout: "15m" - nginx.ingress.kubernetes.io/proxy-read-timeout: "15m" - cert-manager.io/cluster-issuer: "zerossl-prod" - hostname: blockscout-main.k8s-dev.blockscout.com - - resources: - limits: - memory: 768Mi - cpu: 500m - requests: - memory: 384Mi - cpu: 250m - - env: - # ui config - NEXT_PUBLIC_FEATURED_NETWORKS: https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/featured-networks/eth-goerli.json - NEXT_PUBLIC_NETWORK_EXPLORERS: "[{'title':'Bitquery','baseUrl':'https://explorer.bitquery.io/','paths':{'tx':'/goerli/tx','address':'/goerli/address','token':'/goerli/token','block':'/goerli/block'}},{'title':'Etherscan','baseUrl':'https://goerli.etherscan.io/','paths':{'tx':'/tx','address':'/address','token':'/token','block':'/block'}}]" - # network config - NEXT_PUBLIC_NETWORK_LOGO: https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/network-logos/goerli.svg - NEXT_PUBLIC_NETWORK_ICON: https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/network-icons/goerli.svg - - NEXT_PUBLIC_NETWORK_VERIFICATION_TYPE: validation - NEXT_PUBLIC_MARKETPLACE_SUBMIT_FORM: https://airtable.com/shrqUAcjgGJ4jU88C - NEXT_PUBLIC_APP_ENV: development - NEXT_PUBLIC_APP_INSTANCE: main - NEXT_PUBLIC_STATS_API_HOST: https://stats-test.k8s-dev.blockscout.com/ - NEXT_PUBLIC_VISUALIZE_API_HOST: http://visualizer-svc.visualizer-testing.svc.cluster.local/ - NEXT_PUBLIC_CONTRACT_INFO_API_HOST: https://contracts-info-test.k8s-dev.blockscout.com - NEXT_PUBLIC_ADMIN_SERVICE_API_HOST: https://admin-rs-test.k8s-dev.blockscout.com - NEXT_PUBLIC_NAME_SERVICE_API_HOST: https://bens-rs-test.k8s-dev.blockscout.com - NEXT_PUBLIC_LOGOUT_URL: https://blockscoutcom.us.auth0.com/v2/logout - NEXT_PUBLIC_NETWORK_RPC_URL: https://rpc.ankr.com/eth_goerli - NEXT_PUBLIC_HOMEPAGE_CHARTS: "['daily_txs','coin_price','market_cap']" - NEXT_PUBLIC_MARKETPLACE_CONFIG_URL: https://raw.githubusercontent.com/blockscout/frontend-configs/dev/configs/marketplace/eth-goerli.json - NEXT_PUBLIC_MARKETPLACE_CATEGORIES_URL: https://raw.githubusercontent.com/blockscout/frontend-configs/dev/configs/marketplace-categories/default.json - NEXT_PUBLIC_GRAPHIQL_TRANSACTION: 0xf7d4972356e6ae44ae948d0cf19ef2beaf0e574c180997e969a2837da15e349d - NEXT_PUBLIC_WEB3_WALLETS: "['token_pocket','coinbase','metamask']" - NEXT_PUBLIC_VIEWS_NFT_MARKETPLACES: "[{'name':'LooksRare','collection_url':'https://goerli.looksrare.org/collections/{hash}','instance_url':'https://goerli.looksrare.org/collections/{hash}/{id}','logo_url':'https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/nft-marketplace-logos/looks-rare.png'}]" - NEXT_PUBLIC_CONTRACT_CODE_IDES: "[{'title':'Remix IDE','url':'https://remix.blockscout.com/?address={hash}&blockscout=eth-goerli.blockscout.com','icon_url':'https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/ide-icons/remix.png'}]" - envFromSecret: - NEXT_PUBLIC_SENTRY_DSN: ref+vault://deployment-values/blockscout/dev/front-main?token_env=VAULT_TOKEN&address=https://vault.k8s.blockscout.com#/NEXT_PUBLIC_SENTRY_DSN - SENTRY_CSP_REPORT_URI: ref+vault://deployment-values/blockscout/dev/front-main?token_env=VAULT_TOKEN&address=https://vault.k8s.blockscout.com#/SENTRY_CSP_REPORT_URI - NEXT_PUBLIC_AUTH0_CLIENT_ID: ref+vault://deployment-values/blockscout/dev/front-main?token_env=VAULT_TOKEN&address=https://vault.k8s.blockscout.com#/NEXT_PUBLIC_AUTH0_CLIENT_ID - NEXT_PUBLIC_WALLET_CONNECT_PROJECT_ID: ref+vault://deployment-values/blockscout/dev/front-main?token_env=VAULT_TOKEN&address=https://vault.k8s.blockscout.com#/NEXT_PUBLIC_WALLET_CONNECT_PROJECT_ID - NEXT_PUBLIC_RE_CAPTCHA_APP_SITE_KEY: ref+vault://deployment-values/blockscout/dev/front-main?token_env=VAULT_TOKEN&address=https://vault.k8s.blockscout.com#/NEXT_PUBLIC_RE_CAPTCHA_APP_SITE_KEY - NEXT_PUBLIC_GOOGLE_ANALYTICS_PROPERTY_ID: ref+vault://deployment-values/blockscout/dev/front-main?token_env=VAULT_TOKEN&address=https://vault.k8s.blockscout.com#/NEXT_PUBLIC_GOOGLE_ANALYTICS_PROPERTY_ID - FAVICON_GENERATOR_API_KEY: ref+vault://deployment-values/blockscout/common?token_env=VAULT_TOKEN&address=https://vault.k8s.blockscout.com#/NEXT_PUBLIC_FAVICON_GENERATOR_API_KEY diff --git a/deploy/values/review-l2/values.yaml.gotmpl b/deploy/values/review-l2/values.yaml.gotmpl index fb661d526f..3a91738499 100644 --- a/deploy/values/review-l2/values.yaml.gotmpl +++ b/deploy/values/review-l2/values.yaml.gotmpl @@ -5,7 +5,7 @@ imagePullSecrets: config: network: id: 420 - name: "Base Göerli" + name: "Base" shortname: Base currency: name: Ether @@ -30,10 +30,12 @@ frontend: kubernetes.io/ingress.class: internal-and-public nginx.ingress.kubernetes.io/proxy-body-size: 500m nginx.ingress.kubernetes.io/client-max-body-size: "500M" - nginx.ingress.kubernetes.io/proxy-buffering: "off" + nginx.ingress.kubernetes.io/proxy-buffering: "on" nginx.ingress.kubernetes.io/proxy-connect-timeout: "15m" nginx.ingress.kubernetes.io/proxy-send-timeout: "15m" nginx.ingress.kubernetes.io/proxy-read-timeout: "15m" + nginx.ingress.kubernetes.io/proxy-buffer-size: "128k" + nginx.ingress.kubernetes.io/proxy-buffers-number: "8" cert-manager.io/cluster-issuer: "zerossl-prod" hostname: review-l2-{{ requiredEnv "GITHUB_REF_NAME_SLUG" }}.k8s-dev.blockscout.com @@ -45,37 +47,33 @@ frontend: memory: 384Mi cpu: 250m env: - NEXT_PUBLIC_APP_ENV: development - NEXT_PUBLIC_APP_INSTANCE: review_L2 - NEXT_PUBLIC_NETWORK_VERIFICATION_TYPE: validation + NEXT_PUBLIC_APP_ENV: review NEXT_PUBLIC_NETWORK_LOGO: https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/network-logos/base.svg NEXT_PUBLIC_NETWORK_ICON: https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/network-icons/base.svg - NEXT_PUBLIC_FEATURED_NETWORKS: https://raw.githubusercontent.com/blockscout/frontend-configs/dev/configs/featured-networks/base-goerli.json - NEXT_PUBLIC_API_HOST: blockscout-optimism-goerli.k8s-dev.blockscout.com - NEXT_PUBLIC_MARKETPLACE_SUBMIT_FORM: https://airtable.com/shrqUAcjgGJ4jU88C + NEXT_PUBLIC_FEATURED_NETWORKS: https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/featured-networks/base-mainnet.json + NEXT_PUBLIC_API_HOST: base.blockscout.com NEXT_PUBLIC_LOGOUT_URL: https://blockscoutcom.us.auth0.com/v2/logout - NEXT_PUBLIC_STATS_API_HOST: https://stats-optimism-goerli.k8s-dev.blockscout.com - NEXT_PUBLIC_MARKETPLACE_CONFIG_URL: https://raw.githubusercontent.com/blockscout/frontend-configs/dev/configs/marketplace/base-goerli.json - NEXT_PUBLIC_MARKETPLACE_CATEGORIES_URL: https://raw.githubusercontent.com/blockscout/frontend-configs/dev/configs/marketplace-categories/default.json + NEXT_PUBLIC_STATS_API_HOST: https://stats-l2-base-mainnet.k8s-prod-1.blockscout.com NEXT_PUBLIC_HOMEPAGE_PLATE_BACKGROUND: "linear-gradient(136.9deg,rgb(107 94 236) 1.5%,rgb(0 82 255) 56.84%,rgb(82 62 231) 98.54%)" - NEXT_PUBLIC_NETWORK_RPC_URL: https://goerli.optimism.io + NEXT_PUBLIC_NETWORK_RPC_URL: https://mainnet.base.org NEXT_PUBLIC_WEB3_WALLETS: "['coinbase']" NEXT_PUBLIC_WEB3_DISABLE_ADD_TOKEN_TO_WALLET: true NEXT_PUBLIC_HOMEPAGE_CHARTS: "['daily_txs']" - NEXT_PUBLIC_VISUALIZE_API_HOST: https://visualizer-optimism-goerli.k8s-dev.blockscout.com - NEXT_PUBLIC_CONTRACT_INFO_API_HOST: https://contracts-info-test.k8s-dev.blockscout.com - NEXT_PUBLIC_ADMIN_SERVICE_API_HOST: https://admin-rs-test.k8s-dev.blockscout.com - NEXT_PUBLIC_IS_OPTIMISTIC_L2_NETWORK: "true" - NEXT_PUBLIC_OPTIMISTIC_L2_WITHDRAWAL_URL: https://app.optimism.io/bridge/withdraw - NEXT_PUBLIC_L1_BASE_URL: https://blockscout-main.k8s-dev.blockscout.com + NEXT_PUBLIC_VISUALIZE_API_HOST: https://visualizer.services.blockscout.com + NEXT_PUBLIC_CONTRACT_INFO_API_HOST: https://contracts-info.services.blockscout.com + NEXT_PUBLIC_ADMIN_SERVICE_API_HOST: https://admin-rs.services.blockscout.com + NEXT_PUBLIC_NAME_SERVICE_API_HOST: https://bens.services.blockscout.com + NEXT_PUBLIC_METADATA_SERVICE_API_HOST: https://metadata.services.blockscout.com + NEXT_PUBLIC_ROLLUP_TYPE: optimistic + NEXT_PUBLIC_ROLLUP_L1_BASE_URL: https://eth.blockscout.com + NEXT_PUBLIC_ROLLUP_L2_WITHDRAWAL_URL: https://app.optimism.io/bridge/withdraw NEXT_PUBLIC_GRAPHIQL_TRANSACTION: 0x4a0ed8ddf751a7cb5297f827699117b0f6d21a0b2907594d300dc9fed75c7e62 NEXT_PUBLIC_USE_NEXT_JS_PROXY: true + NEXT_PUBLIC_NAVIGATION_LAYOUT: horizontal + NEXT_PUBLIC_NAVIGATION_HIGHLIGHTED_ROUTES: "['/blocks','/name-domains']" envFromSecret: - NEXT_PUBLIC_SENTRY_DSN: ref+vault://deployment-values/blockscout/dev/review-l2?token_env=VAULT_TOKEN&address=https://vault.k8s.blockscout.com#/NEXT_PUBLIC_SENTRY_DSN - SENTRY_CSP_REPORT_URI: ref+vault://deployment-values/blockscout/dev/review-l2?token_env=VAULT_TOKEN&address=https://vault.k8s.blockscout.com#/SENTRY_CSP_REPORT_URI NEXT_PUBLIC_AUTH0_CLIENT_ID: ref+vault://deployment-values/blockscout/dev/review-l2?token_env=VAULT_TOKEN&address=https://vault.k8s.blockscout.com#/NEXT_PUBLIC_AUTH0_CLIENT_ID NEXT_PUBLIC_WALLET_CONNECT_PROJECT_ID: ref+vault://deployment-values/blockscout/dev/review-l2?token_env=VAULT_TOKEN&address=https://vault.k8s.blockscout.com#/NEXT_PUBLIC_WALLET_CONNECT_PROJECT_ID - NEXT_PUBLIC_RE_CAPTCHA_APP_SITE_KEY: ref+vault://deployment-values/blockscout/dev/review-l2?token_env=VAULT_TOKEN&address=https://vault.k8s.blockscout.com#/NEXT_PUBLIC_RE_CAPTCHA_APP_SITE_KEY NEXT_PUBLIC_GOOGLE_ANALYTICS_PROPERTY_ID: ref+vault://deployment-values/blockscout/dev/review-l2?token_env=VAULT_TOKEN&address=https://vault.k8s.blockscout.com#/NEXT_PUBLIC_GOOGLE_ANALYTICS_PROPERTY_ID - FAVICON_GENERATOR_API_KEY: ref+vault://deployment-values/blockscout/common?token_env=VAULT_TOKEN&address=https://vault.k8s.blockscout.com#/NEXT_PUBLIC_FAVICON_GENERATOR_API_KEY - NEXT_PUBLIC_OG_IMAGE_URL: https://github.com/blockscout/frontend-configs/blob/main/configs/og-images/base-goerli.png?raw=true + NEXT_PUBLIC_OG_IMAGE_URL: https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/og-images/base-mainnet.png + NEXT_PUBLIC_RE_CAPTCHA_APP_SITE_KEY: ref+vault://deployment-values/blockscout/eth-sepolia/testnet?token_env=VAULT_TOKEN&address=https://vault.k8s.blockscout.com#/RE_CAPTCHA_CLIENT_KEY diff --git a/deploy/values/review/values.yaml.gotmpl b/deploy/values/review/values.yaml.gotmpl index f2fda6ceec..6bd0759fa1 100644 --- a/deploy/values/review/values.yaml.gotmpl +++ b/deploy/values/review/values.yaml.gotmpl @@ -4,7 +4,7 @@ imagePullSecrets: - name: regcred config: network: - id: 5 + id: "11155111" name: Blockscout shortname: Blockscout currency: @@ -30,10 +30,12 @@ frontend: kubernetes.io/ingress.class: internal-and-public nginx.ingress.kubernetes.io/proxy-body-size: 500m nginx.ingress.kubernetes.io/client-max-body-size: "500M" - nginx.ingress.kubernetes.io/proxy-buffering: "off" + nginx.ingress.kubernetes.io/proxy-buffering: "on" nginx.ingress.kubernetes.io/proxy-connect-timeout: "15m" nginx.ingress.kubernetes.io/proxy-send-timeout: "15m" nginx.ingress.kubernetes.io/proxy-read-timeout: "15m" + nginx.ingress.kubernetes.io/proxy-buffer-size: "128k" + nginx.ingress.kubernetes.io/proxy-buffers-number: "8" cert-manager.io/cluster-issuer: "zerossl-prod" hostname: review-{{ requiredEnv "GITHUB_REF_NAME_SLUG" }}.k8s-dev.blockscout.com @@ -45,44 +47,42 @@ frontend: memory: 384Mi cpu: 250m env: - NEXT_PUBLIC_APP_ENV: development - NEXT_PUBLIC_APP_INSTANCE: review - NEXT_PUBLIC_NETWORK_VERIFICATION_TYPE: validation - NEXT_PUBLIC_FEATURED_NETWORKS: https://raw.githubusercontent.com/blockscout/frontend-configs/dev/configs/featured-networks/eth-goerli.json - NEXT_PUBLIC_NETWORK_LOGO: https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/network-logos/goerli.svg - NEXT_PUBLIC_NETWORK_ICON: https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/network-icons/goerli.svg - NEXT_PUBLIC_API_HOST: eth-goerli.blockscout.com - NEXT_PUBLIC_STATS_API_HOST: https://stats-goerli.k8s-dev.blockscout.com/ + NEXT_PUBLIC_APP_ENV: review + NEXT_PUBLIC_FEATURED_NETWORKS: https://raw.githubusercontent.com/blockscout/frontend-configs/dev/configs/featured-networks/eth-sepolia.json + NEXT_PUBLIC_NETWORK_LOGO: https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/network-logos/sepolia.svg + NEXT_PUBLIC_NETWORK_ICON: https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/network-icons/sepolia.png + NEXT_PUBLIC_API_HOST: eth-sepolia.k8s-dev.blockscout.com + NEXT_PUBLIC_STATS_API_HOST: https://stats-sepolia.k8s-dev.blockscout.com/ NEXT_PUBLIC_VISUALIZE_API_HOST: http://visualizer-svc.visualizer-testing.svc.cluster.local/ NEXT_PUBLIC_CONTRACT_INFO_API_HOST: https://contracts-info-test.k8s-dev.blockscout.com NEXT_PUBLIC_ADMIN_SERVICE_API_HOST: https://admin-rs-test.k8s-dev.blockscout.com NEXT_PUBLIC_NAME_SERVICE_API_HOST: https://bens-rs-test.k8s-dev.blockscout.com + NEXT_PUBLIC_METADATA_SERVICE_API_HOST: https://metadata-test.k8s-dev.blockscout.com NEXT_PUBLIC_AUTH_URL: https://blockscout-main.k8s-dev.blockscout.com - NEXT_PUBLIC_MARKETPLACE_SUBMIT_FORM: https://airtable.com/shrqUAcjgGJ4jU88C NEXT_PUBLIC_LOGOUT_URL: https://blockscoutcom.us.auth0.com/v2/logout NEXT_PUBLIC_HOMEPAGE_CHARTS: "['daily_txs','coin_price','market_cap']" - NEXT_PUBLIC_NETWORK_RPC_URL: https://rpc.ankr.com/eth_goerli - NEXT_PUBLIC_NETWORK_EXPLORERS: "[{'title':'Bitquery','baseUrl':'https://explorer.bitquery.io/','paths':{'tx':'/goerli/tx','address':'/goerli/address','token':'/goerli/token','block':'/goerli/block'}},{'title':'Etherscan','baseUrl':'https://goerli.etherscan.io/','paths':{'tx':'/tx','address':'/address','token':'/token','block':'/block'}}]" - NEXT_PUBLIC_MARKETPLACE_CONFIG_URL: https://raw.githubusercontent.com/blockscout/frontend-configs/dev/configs/marketplace/eth-goerli.json - NEXT_PUBLIC_MARKETPLACE_CATEGORIES_URL: https://raw.githubusercontent.com/blockscout/frontend-configs/dev/configs/marketplace-categories/default.json + NEXT_PUBLIC_NETWORK_RPC_URL: https://eth-sepolia.public.blastapi.io + NEXT_PUBLIC_NETWORK_EXPLORERS: "[{'title':'Bitquery','baseUrl':'https://explorer.bitquery.io/','paths':{'tx':'/goerli/tx','address':'/goerli/address','token':'/goerli/token','block':'/goerli/block'}},{'title':'Etherscan','logo':'https://github.com/blockscout/frontend-configs/blob/main/configs/explorer-logos/etherscan.png?raw=true','baseUrl':'https://goerli.etherscan.io/','paths':{'tx':'/tx','address':'/address','token':'/token','block':'/block'}}]" NEXT_PUBLIC_GRAPHIQL_TRANSACTION: 0xf7d4972356e6ae44ae948d0cf19ef2beaf0e574c180997e969a2837da15e349d NEXT_PUBLIC_WEB3_WALLETS: "['token_pocket','coinbase','metamask']" NEXT_PUBLIC_VIEWS_ADDRESS_IDENTICON_TYPE: gradient_avatar - NEXT_PUBLIC_VIEWS_ADDRESS_HIDDEN_VIEWS: "['top_accounts']" NEXT_PUBLIC_VIEWS_CONTRACT_SOLIDITYSCAN_ENABLED: true - NEXT_PUBLIC_VIEWS_TX_HIDDEN_FIELDS: "['value','fee_currency','gas_price','gas_fees','burnt_fees']" - NEXT_PUBLIC_VIEWS_TX_ADDITIONAL_FIELDS: "['fee_per_gas']" NEXT_PUBLIC_USE_NEXT_JS_PROXY: true NEXT_PUBLIC_VIEWS_NFT_MARKETPLACES: "[{'name':'LooksRare','collection_url':'https://goerli.looksrare.org/collections/{hash}','instance_url':'https://goerli.looksrare.org/collections/{hash}/{id}','logo_url':'https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/nft-marketplace-logos/looks-rare.png'}]" NEXT_PUBLIC_HAS_USER_OPS: true NEXT_PUBLIC_CONTRACT_CODE_IDES: "[{'title':'Remix IDE','url':'https://remix.blockscout.com/?address={hash}&blockscout=eth-goerli.blockscout.com','icon_url':'https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/ide-icons/remix.png'}]" + NEXT_PUBLIC_TRANSACTION_INTERPRETATION_PROVIDER: blockscout + NEXT_PUBLIC_HAS_CONTRACT_AUDIT_REPORTS: true + NEXT_PUBLIC_AD_BANNER_PROVIDER: slise + NEXT_PUBLIC_DATA_AVAILABILITY_ENABLED: true + NEXT_PUBLIC_NAVIGATION_HIGHLIGHTED_ROUTES: "['/apps']" + PROMETHEUS_METRICS_ENABLED: true + NEXT_PUBLIC_OG_ENHANCED_DATA_ENABLED: true envFromSecret: - NEXT_PUBLIC_SENTRY_DSN: ref+vault://deployment-values/blockscout/dev/review?token_env=VAULT_TOKEN&address=https://vault.k8s.blockscout.com#/NEXT_PUBLIC_SENTRY_DSN - SENTRY_CSP_REPORT_URI: ref+vault://deployment-values/blockscout/dev/review?token_env=VAULT_TOKEN&address=https://vault.k8s.blockscout.com#/SENTRY_CSP_REPORT_URI NEXT_PUBLIC_AUTH0_CLIENT_ID: ref+vault://deployment-values/blockscout/dev/review?token_env=VAULT_TOKEN&address=https://vault.k8s.blockscout.com#/NEXT_PUBLIC_AUTH0_CLIENT_ID NEXT_PUBLIC_WALLET_CONNECT_PROJECT_ID: ref+vault://deployment-values/blockscout/dev/review?token_env=VAULT_TOKEN&address=https://vault.k8s.blockscout.com#/NEXT_PUBLIC_WALLET_CONNECT_PROJECT_ID - NEXT_PUBLIC_RE_CAPTCHA_APP_SITE_KEY: ref+vault://deployment-values/blockscout/dev/review?token_env=VAULT_TOKEN&address=https://vault.k8s.blockscout.com#/NEXT_PUBLIC_RE_CAPTCHA_APP_SITE_KEY NEXT_PUBLIC_GOOGLE_ANALYTICS_PROPERTY_ID: ref+vault://deployment-values/blockscout/dev/review?token_env=VAULT_TOKEN&address=https://vault.k8s.blockscout.com#/NEXT_PUBLIC_GOOGLE_ANALYTICS_PROPERTY_ID - FAVICON_GENERATOR_API_KEY: ref+vault://deployment-values/blockscout/common?token_env=VAULT_TOKEN&address=https://vault.k8s.blockscout.com#/NEXT_PUBLIC_FAVICON_GENERATOR_API_KEY NEXT_PUBLIC_GROWTH_BOOK_CLIENT_KEY: ref+vault://deployment-values/blockscout/dev/review?token_env=VAULT_TOKEN&address=https://vault.k8s.blockscout.com#/NEXT_PUBLIC_GROWTH_BOOK_CLIENT_KEY NEXT_PUBLIC_MIXPANEL_PROJECT_TOKEN: ref+vault://deployment-values/blockscout/common?token_env=VAULT_TOKEN&address=https://vault.k8s.blockscout.com#/NEXT_PUBLIC_MIXPANEL_PROJECT_TOKEN + NEXT_PUBLIC_RE_CAPTCHA_APP_SITE_KEY: ref+vault://deployment-values/blockscout/eth-sepolia/testnet?token_env=VAULT_TOKEN&address=https://vault.k8s.blockscout.com#/RE_CAPTCHA_CLIENT_KEY + NEXT_PUBLIC_ROLLBAR_CLIENT_TOKEN: ref+vault://deployment-values/blockscout/common?token_env=VAULT_TOKEN&address=https://vault.k8s.blockscout.com#/NEXT_PUBLIC_ROLLBAR_CLIENT_TOKEN diff --git a/docs/BUILD-TIME_ENVS.md b/docs/BUILD-TIME_ENVS.md index 08524e4389..f151a30e77 100644 --- a/docs/BUILD-TIME_ENVS.md +++ b/docs/BUILD-TIME_ENVS.md @@ -5,4 +5,5 @@ These variables are passed to the app during the image build process. They canno | Variable | Type | Description | Optional | Example value | | --- | --- | --- | --- | --- | | NEXT_PUBLIC_GIT_COMMIT_SHA | `string` | SHA of the latest commit in the branch from which image is built | false | `29d0613e` | -| NEXT_PUBLIC_GIT_TAG | `string` | Git tag on the latest commit in the branch from which image is built | true | `v1.0.0` | +| NEXT_PUBLIC_GIT_TAG | `string` | Git tag of the latest commit in the branch from which image is built | true | `v1.0.0` | +| NEXT_OPEN_TELEMETRY_ENABLED | `boolean` | Enables OpenTelemetry SDK | true | `true` | diff --git a/docs/CONTRIBUTING.md b/docs/CONTRIBUTING.md index 08dc19eaf2..db83ba302a 100644 --- a/docs/CONTRIBUTING.md +++ b/docs/CONTRIBUTING.md @@ -49,15 +49,15 @@ To develop locally, follow one of the two paths outlined below: A. Custom configuration: 1. Create `.env.local` file in the root folder and include all required environment variables from the [list](./ENVS.md) -2. Optionally, clone `.env.example` and name it `.env.secrets`. Fill it with necessary secrets for integrating with [external services](./ENVS.md#external-services-configuration). Include only secrets your need. -3. Use `yarn dev` command to start the dev server. +2. Optionally, clone `.env.example` and name it `.env.secrets`. Fill it with necessary secrets for integrating with [external services](./ENVS.md#external-services-configuration). Include only secrets you need. +3. Use `yarn dev` command to start the Dev Server. 4. Open your browser and navigate to the URL provided in the command line output (by default, it is `http://localhost:3000`). B. Pre-defined configuration: 1. Optionally, clone `.env.example` file into `configs/envs/.env.secrets`. Fill it with necessary secrets for integrating with [external services](./ENVS.md#external-services-configuration). Include only secrets your need. 2. Choose one of the predefined configurations located in the `/configs/envs` folder. -3. Start your local dev server using the `yarn dev:` command. +3. Start your local Dev Server using the `yarn dev:preset ` command. 4. Open your browser and navigate to the URL provided in the command line output (by default, it is `http://localhost:3000`). @@ -66,7 +66,7 @@ B. Pre-defined configuration: ## Adding new dependencies For all types of dependencies: - **Do not add** a dependency if the desired functionality is easily implementable -- If adding a dependency is necessary, please be sure that is is well-maintained and trustworthy +- If adding a dependency is necessary, please be sure that it is well-maintained and trustworthy   @@ -79,18 +79,19 @@ These are the steps that you have to follow to make everything work: 2. Make sure that you have added a property to React app config (`configs/app/index.ts`) in appropriate section that is associated with this variable; do not use ENV variable values directly in the application code; decide where this variable belongs to and place it under the certain section: - `app` - the front-end app itself - `api` - the main API configuration + - `chain` - the Blockchain parameters - `UI` - the app UI customization + - `meta` - SEO and meta-tags customization - `features` - the particular feature of the app - - `services` - some 3rd party service integration which is not related to one particular feature -3. For local development purposes add the variable with its appropriate values to pre-defined ENV configs `configs/envs` where it is needed -4. Add the variable to CI configs where it is needed + - `services` - some 3rd party service integration which is not related to one particular feature +3. If a new variable is meant to store the URL of an external API service, remember to include its value in the Content-Security-Policy document header. Refer to `nextjs/csp/policies/app.ts` for details. +4. For local development purposes add the variable with its appropriate values to pre-defined ENV configs `configs/envs` where it is needed +5. Add the variable to CI configs where it is needed - `deploy/values/review/values.yaml.gotmpl` - review development environment - - `deploy/values/main/values.yaml` - main development environment - `deploy/values/review-l2/values.yaml.gotmpl` - review development environment for L2 networks - - `deploy/values/l2-optimism-goerli/values.yaml` - main development environment -5. If your variable is meant to receive a link to some external resource (image or JSON-config file), extend the array `ASSETS_ENVS` in `deploy/scripts/download_assets.sh` with your variable name -6. Add validation schema for the new variable into the file `deploy/tools/envs-validator/schema.ts` -7. Check if modified validation schema is valid by doing the following steps: +6. If your variable is meant to receive a link to some external resource (image or JSON-config file), extend the array `ASSETS_ENVS` in `deploy/scripts/download_assets.sh` with your variable name +7. Add validation schema for the new variable into the file `deploy/tools/envs-validator/schema.ts` +8. Check if modified validation schema is valid by doing the following steps: - change your current directory to `deploy/tools/envs-validator` - install deps with `yarn` command - add your variable into `./test/.env.base` test preset or create a new test preset if needed @@ -98,7 +99,7 @@ These are the steps that you have to follow to make everything work: - add example of file content into `./test/assets` directory; the file name should be constructed by stripping away prefix `NEXT_PUBLIC_` and postfix `_URL` if any, and converting the remaining string to lowercase (for example, `NEXT_PUBLIC_MARKETPLACE_CONFIG_URL` will become `marketplace_config.json`) - in the main script `index.ts` extend array `envsWithJsonConfig` with your variable name - run `yarn test` command to see the validation result -8. Don't forget to mention in the PR notes that new ENV variable was added +9. Don't forget to mention in the PR notes that new ENV variable was added   @@ -108,11 +109,11 @@ Every feature or bugfix should be accompanied by tests, either unit tests or com ### Jest unit tests -If your changes only related to the logic of the app and not to its visual presentation, then try to write unit tests using [Jest](https://jestjs.io/) framework and [React Testing Library](https://testing-library.com/docs/react-testing-library/intro/). In general these tests are "cheaper" and faster than Playwright ones. Use them for testing your utilities and React hooks, as well as the whole components logic. +If your changes are only related to the logic of the app and not to its visual presentation, then try to write unit tests using [Jest](https://jestjs.io/) framework and [React Testing Library](https://testing-library.com/docs/react-testing-library/intro/). In general these tests are "cheaper" and faster than Playwright ones. Use them for testing your utilities and React hooks, as well as the whole components logic. Place your test suites in `.test.ts` or `.test.tsx` files. You can find or add some mocks or other helpful utilities for these tests purposes in the `/jest` folder. -*Note*, that we are using custom renderer and wrapper in all test for React components, so please do not import package `@testing-library/react` directly in your test suites, instead use imports from `jest/lib` utility. +*Note*, that we are using custom renderer and wrapper in all tests for React components, so please do not import package `@testing-library/react` directly in your test suites, instead use imports from `jest/lib` utility. ### Playwright components tests @@ -173,8 +174,8 @@ We have 3 pre-configured projects. You can run your test with the desired projec | Command | Description | | --- | --- | | **Running and building** | -| `yarn dev` | run local dev server with user's configuration | -| `yarn dev:preset ` | run local dev server with predefined configuration | +| `yarn dev` | run local Dev Server with user's configuration | +| `yarn dev:preset ` | run local Dev Server with predefined configuration | | `yarn build:docker` | build a docker image locally | | `yarn start:docker:local` | start an application from previously built local docker image with user's configuration | | `yarn start:docker:preset ` | start an application from previously built local docker image with predefined configuration | @@ -199,7 +200,7 @@ We have 3 pre-configured projects. You can run your test with the desired projec #### VSCode -There are some predefined tasks for all commands described above. You can see its full list by pressing cmd + shift + P and using command `Task: Run task` +There are some predefined tasks for all commands described above. You can see the full list by pressing cmd + shift + P and using command `Task: Run task` Also there is a Jest test launch configuration for debugging and running current test file in the watch mode. diff --git a/docs/DEPRECATED_ENVS.md b/docs/DEPRECATED_ENVS.md new file mode 100644 index 0000000000..171df270f9 --- /dev/null +++ b/docs/DEPRECATED_ENVS.md @@ -0,0 +1,14 @@ +# Deprecated environment variables + +| Variable | Type | Description | Compulsoriness | Default value | Example value | Introduced in version | Deprecated in version | Comment | +| --- | --- | --- | --- | --- | --- | --- | --- | --- | +| NEXT_PUBLIC_FAVICON_GENERATOR_API_KEY | `string` | RealFaviconGenerator [API key](https://realfavicongenerator.net/api/) | Required | - | `` | v1.11.0 | v1.16.0 | Replaced FAVICON_GENERATOR_API_KEY | +| FAVICON_GENERATOR_API_KEY | `string` | RealFaviconGenerator [API key](https://realfavicongenerator.net/api/) | Required | - | `` | v1.16.0+ | v1.37.0 | We don't use RealFaviconGenerator anymore | +| NEXT_PUBLIC_IS_OPTIMISTIC_L2_NETWORK | `boolean` | Set to true for optimistic L2 solutions | Required | - | `true` | v1.17.0 | v1.24.0 | Replaced by NEXT_PUBLIC_ROLLUP_TYPE | +| NEXT_PUBLIC_IS_ZKEVM_L2_NETWORK | `boolean` | Set to true for zkevm L2 solutions | Required | - | `true` | v1.17.0 | v1.24.0 | Replaced by NEXT_PUBLIC_ROLLUP_TYPE | +| NEXT_PUBLIC_OPTIMISTIC_L2_WITHDRAWAL_URL | `string` | URL for optimistic L2 -> L1 withdrawals | Required | - | `https://app.optimism.io/bridge/withdraw` | v1.17.0 | v1.24.0 | Renamed to NEXT_PUBLIC_ROLLUP_L2_WITHDRAWAL_URL | +| NEXT_PUBLIC_L1_BASE_URL | `string` | Blockscout base URL for L1 network | Required | - | `'http://eth-goerli.blockscout.com'` | - | v1.24.0 | Renamed to NEXT_PUBLIC_ROLLUP_L1_BASE_URL | +| NEXT_PUBLIC_HOMEPAGE_SHOW_GAS_TRACKER | `boolean` | Set to false if network doesn't have gas tracker | - | `true` | `false` | - | v1.25.0 | Replaced by NEXT_PUBLIC_GAS_TRACKER_ENABLED | +| NEXT_PUBLIC_NETWORK_GOVERNANCE_TOKEN_SYMBOL | `string` | Network governance token symbol | - | - | `GNO` | v1.12.0 | v1.29.0 | Replaced by NEXT_PUBLIC_NETWORK_SECONDARY_COIN_SYMBOL | +| NEXT_PUBLIC_SWAP_BUTTON_URL | `string` | Application ID in the marketplace or website URL | - | - | `uniswap` | v1.24.0 | v1.31.0 | Replaced by NEXT_PUBLIC_DEFI_DROPDOWN_ITEMS | +| NEXT_PUBLIC_HOMEPAGE_SHOW_AVG_BLOCK_TIME | `boolean` | Set to false if average block time is useless for the network | - | `true` | `false` | v1.0.x+ | v1.35.0 | Replaced by NEXT_PUBLIC_HOMEPAGE_STATS | diff --git a/docs/ENVS.md b/docs/ENVS.md index f11d7b8887..f155e22951 100644 --- a/docs/ENVS.md +++ b/docs/ENVS.md @@ -1,6 +1,6 @@ # Run-time environment variables -The app instance could be customized by passing following variables to NodeJS environment at run-time. See their list below. +The app instance can be customized by passing the following variables to the Node.js environment at runtime. Some of these variables have been deprecated, and their full list can be found in the [file](./DEPRECATED_ENVS.md). **IMPORTANT NOTE!** For _production_ build purposes all json-like values should be single-quoted. If it contains a hash (`#`) or a dollar-sign (`$`) the whole value should be wrapped in single quotes as well (see `dotenv` [readme](https://github.com/bkeepers/dotenv#variable-substitution) for the reference) @@ -16,7 +16,7 @@ Please be aware that all environment variables prefixed with `NEXT_PUBLIC_` will - [API configuration](ENVS.md#api-configuration) - [UI configuration](ENVS.md#ui-configuration) - [Homepage](ENVS.md#homepage) - - [Sidebar](ENVS.md#sidebar) + - [Navigation](ENVS.md#navigation) - [Footer](ENVS.md#footer) - [Favicon](ENVS.md#favicon) - [Meta](ENVS.md#meta) @@ -28,14 +28,15 @@ Please be aware that all environment variables prefixed with `NEXT_PUBLIC_` will - [Misc](ENVS.md#misc) - [App features](ENVS.md#app-features) - [My account](ENVS.md#my-account) + - [Gas tracker](ENVS.md#gas-tracker) + - [Advanced filter](ENVS.md#advanced-filter) - [Address verification](ENVS.md#address-verification-in-my-account) in "My account" - [Blockchain interaction](ENVS.md#blockchain-interaction-writing-to-contract-etc) (writing to contract, etc.) - [Banner ads](ENVS.md#banner-ads) - [Text ads](ENVS.md#text-ads) - [Beacon chain](ENVS.md#beacon-chain) - - [User operations](ENVS.md#user-operations-feature-erc-4337) - - [Optimistic rollup (L2) chain](ENVS.md#optimistic-rollup-l2-chain) - - [ZkEvm rollup (L2) chain](NVS.md#zkevm-rollup-l2-chain) + - [User operations](ENVS.md#user-operations-erc-4337) + - [Rollup chain](ENVS.md#rollup-chain) - [Export data to CSV file](ENVS.md#export-data-to-csv-file) - [Google analytics](ENVS.md#google-analytics) - [Mixpanel analytics](ENVS.md#mixpanel-analytics) @@ -49,54 +50,76 @@ Please be aware that all environment variables prefixed with `NEXT_PUBLIC_` will - [Transaction interpretation](ENVS.md#transaction-interpretation) - [Verified tokens info](ENVS.md#verified-tokens-info) - [Name service integration](ENVS.md#name-service-integration) + - [Metadata service integration](ENVS.md#metadata-service-integration) + - [Public tag submission](ENVS.md#public-tag-submission) + - [Data availability](ENVS.md#data-availability) - [Bridged tokens](ENVS.md#bridged-tokens) - [Safe{Core} address tags](ENVS.md#safecore-address-tags) + - [Address profile API](ENVS.md#address-profile-api) + - [Address XStar XHS score](ENVS.md#address-xstar-xhs-score) - [SUAVE chain](ENVS.md#suave-chain) + - [MetaSuites extension](ENVS.md#metasuites-extension) + - [Validators list](ENVS.md#validators-list) - [Sentry error monitoring](ENVS.md#sentry-error-monitoring) + - [Rollbar error monitoring](ENVS.md#rollbar-error-monitoring) - [OpenTelemetry](ENVS.md#opentelemetry) + - [DeFi dropdown](ENVS.md#defi-dropdown) + - [Multichain balance button](ENVS.md#multichain-balance-button) + - [Get gas button](ENVS.md#get-gas-button) + - [Save on gas with GasHawk](ENVS.md#save-on-gas-with-gashawk) + - [Rewards service API](ENVS.md#rewards-service-api) + - [DEX pools](ENVS.md#dex-pools) - [3rd party services configuration](ENVS.md#external-services-configuration)   ## App configuration -| Variable | Type| Description | Compulsoriness | Default value | Example value | -| --- | --- | --- | --- | --- | --- | -| NEXT_PUBLIC_APP_PROTOCOL | `http \| https` | App url schema | - | `https` | `http` | -| NEXT_PUBLIC_APP_HOST | `string` | App host | Required | - | `blockscout.com` | -| NEXT_PUBLIC_APP_PORT | `number` | Port where app is running | - | `3000` | `3001` | -| NEXT_PUBLIC_USE_NEXT_JS_PROXY | `boolean` | Tells the app to proxy all APIs request through the NextJS app. **We strongly advise not to use it in the production environment**, since it can lead to performance issues of the NodeJS server | - | `false` | `true` | +| Variable | Type| Description | Compulsoriness | Default value | Example value | Version | +| --- | --- | --- | --- | --- | --- | --- | +| NEXT_PUBLIC_APP_PROTOCOL | `http \| https` | App url schema | - | `https` | `http` | v1.0.x+ | +| NEXT_PUBLIC_APP_HOST | `string` | App host | Required | - | `blockscout.com` | v1.0.x+ | +| NEXT_PUBLIC_APP_PORT | `number` | Port where app is running | - | `3000` | `3001` | v1.0.x+ | +| NEXT_PUBLIC_APP_ENV | `string` | App env (e.g development, staging, production, etc.). | - | `production` | `staging` | v1.0.x+ | +| NEXT_PUBLIC_APP_INSTANCE | `string` | Name of app instance. Used for app monitoring purposes. If not provided, it will be constructed from `NEXT_PUBLIC_APP_HOST` | - | - | `wonderful_kepler` | v1.0.x+ | +| NEXT_PUBLIC_USE_NEXT_JS_PROXY | `boolean` | Tells the app to proxy all APIs request through the NextJS app. **We strongly advise not to use it in the production environment**, since it can lead to performance issues of the NodeJS server | - | `false` | `true` | v1.8.0+ |   ## Blockchain parameters -| Variable | Type| Description | Compulsoriness | Default value | Example value | -| --- | --- | --- | --- | --- | --- | -| NEXT_PUBLIC_NETWORK_NAME | `string` | Displayed name of the network | Required | - | `Gnosis Chain` | -| NEXT_PUBLIC_NETWORK_SHORT_NAME | `string` | Used for SEO attributes (e.g, page description) | - | - | `OoG` | -| NEXT_PUBLIC_NETWORK_ID | `number` | Chain id, see [https://chainlist.org](https://chainlist.org) for the reference | Required | - | `99` | -| NEXT_PUBLIC_NETWORK_RPC_URL | `string` | Chain public RPC server url, see [https://chainlist.org](https://chainlist.org) for the reference | - | - | `https://core.poa.network` | -| NEXT_PUBLIC_NETWORK_CURRENCY_NAME | `string` | Network currency name | - | - | `Ether` | -| NEXT_PUBLIC_NETWORK_CURRENCY_WEI_NAME | `string` | Name of network currency subdenomination | - | `wei` | `duck` | -| NEXT_PUBLIC_NETWORK_CURRENCY_SYMBOL | `string` | Network currency symbol | - | - | `ETH` | -| NEXT_PUBLIC_NETWORK_CURRENCY_DECIMALS | `string` | Network currency decimals | - | `18` | `6` | -| NEXT_PUBLIC_NETWORK_GOVERNANCE_TOKEN_SYMBOL | `string` | Network governance token symbol | - | - | `GNO` | -| NEXT_PUBLIC_NETWORK_VERIFICATION_TYPE | `validation` or `mining` | Verification type in the network | - | `mining` | `validation` | -| NEXT_PUBLIC_IS_TESTNET | `boolean`| Set to true if network is testnet | - | `false` | `true` | +*Note!* The `NEXT_PUBLIC_NETWORK_CURRENCY` variables represent the blockchain's native token used for paying transaction fees. `NEXT_PUBLIC_NETWORK_SECONDARY_COIN` variables refer to tokens like protocol-specific tokens (e.g., OP token on Optimism chain) or governance tokens (e.g., GNO on Gnosis chain). + +| Variable | Type| Description | Compulsoriness | Default value | Example value | Version | +| --- | --- | --- | --- | --- | --- | --- | +| NEXT_PUBLIC_NETWORK_NAME | `string` | Displayed name of the network | Required | - | `Gnosis Chain` | v1.0.x+ | +| NEXT_PUBLIC_NETWORK_SHORT_NAME | `string` | Used for SEO attributes (e.g, page description) | - | - | `OoG` | v1.0.x+ | +| NEXT_PUBLIC_NETWORK_ID | `number` | Chain id, see [https://chainlist.org](https://chainlist.org) for the reference | Required | - | `99` | v1.0.x+ | +| NEXT_PUBLIC_NETWORK_RPC_URL | `string` | Chain public RPC server url, see [https://chainlist.org](https://chainlist.org) for the reference | - | - | `https://core.poa.network` | v1.0.x+ | +| NEXT_PUBLIC_NETWORK_CURRENCY_NAME | `string` | Network currency name | - | - | `Ether` | v1.0.x+ | +| NEXT_PUBLIC_NETWORK_CURRENCY_WEI_NAME | `string` | Name of network currency subdenomination | - | `wei` | `duck` | v1.23.0+ | +| NEXT_PUBLIC_NETWORK_CURRENCY_SYMBOL | `string` | Network currency symbol | - | - | `ETH` | v1.0.x+ | +| NEXT_PUBLIC_NETWORK_CURRENCY_DECIMALS | `string` | Network currency decimals | - | `18` | `6` | v1.0.x+ | +| NEXT_PUBLIC_NETWORK_SECONDARY_COIN_SYMBOL | `string` | Network secondary coin symbol. | - | - | `GNO` | v1.29.0+ | +| NEXT_PUBLIC_NETWORK_MULTIPLE_GAS_CURRENCIES | `boolean` | Set to `true` for networks where users can pay transaction fees in either the native coin or ERC-20 tokens. | - | `false` | `true` | v1.33.0+ | +| NEXT_PUBLIC_NETWORK_VERIFICATION_TYPE | `validation` \| `mining` | Verification type in the network. Irrelevant for Arbitrum (verification type is always `posting`) and ZkEvm (verification type is always `sequencing`) L2s | - | `mining` | `validation` | v1.0.x+ | +| NEXT_PUBLIC_NETWORK_TOKEN_STANDARD_NAME | `string` | Name of the standard for creating tokens | - | `ERC` | `BEP` | v1.31.0+ | +| NEXT_PUBLIC_IS_TESTNET | `boolean`| Set to true if network is testnet | - | `false` | `true` | v1.0.x+ | | NEXT_PUBLIC_NETWORK_CURRENCY_IMAGE_URL | `string` | Network currency image url | - | `` | `` | | NEXT_PUBLIC_IS_DEVNET | `boolean`| Set to true if network is devnet | - | `false` | `true` | +| NEXT_PUBLIC_NETWORK_GOVERNANCE_TOKEN_SYMBOL | `string` | Network governance token symbol | - | - | `GNO` | +   ## API configuration -| Variable | Type| Description | Compulsoriness | Default value | Example value | -| --- | --- | --- | --- | --- | --- | -| NEXT_PUBLIC_API_PROTOCOL | `http \| https` | Main API protocol | - | `https` | `http` | -| NEXT_PUBLIC_API_HOST | `string` | Main API host | Required | - | `blockscout.com` | -| NEXT_PUBLIC_API_PORT | `number` | Port where API is running on the host | - | - | `3001` | -| NEXT_PUBLIC_API_BASE_PATH | `string` | Base path for Main API endpoint url | - | - | `/poa/core` | -| NEXT_PUBLIC_API_WEBSOCKET_PROTOCOL | `ws \| wss` | Main API websocket protocol | - | `wss` | `ws` | +| Variable | Type| Description | Compulsoriness | Default value | Example value | Version | +| --- | --- | --- | --- | --- | --- | --- | +| NEXT_PUBLIC_API_PROTOCOL | `http \| https` | Main API protocol | - | `https` | `http` | v1.0.x+ | +| NEXT_PUBLIC_API_HOST | `string` | Main API host | Required | - | `blockscout.com` | v1.0.x+ | +| NEXT_PUBLIC_API_PORT | `number` | Port where API is running on the host | - | - | `3001` | v1.0.x+ | +| NEXT_PUBLIC_API_BASE_PATH | `string` | Base path for Main API endpoint url | - | - | `/poa/core` | v1.0.x+ | +| NEXT_PUBLIC_API_WEBSOCKET_PROTOCOL | `ws \| wss` | Main API websocket protocol | - | `wss` | `ws` | v1.0.x+ |   @@ -104,30 +127,43 @@ Please be aware that all environment variables prefixed with `NEXT_PUBLIC_` will ### Homepage -| Variable | Type| Description | Compulsoriness | Default value | Example value | -| --- | --- | --- | --- | --- | --- | -| NEXT_PUBLIC_HOMEPAGE_CHARTS | `Array<'daily_txs' \| 'coin_price' \| 'market_cap' \| 'tvl'>` | List of charts displayed on the home page | - | - | `['daily_txs','coin_price','market_cap']` | -| NEXT_PUBLIC_HOMEPAGE_PLATE_TEXT_COLOR | `string` | Text color of the hero plate on the homepage (escape "#" symbol if you use HEX color codes or use rgba-value instead) | - | `white` | `\#DCFE76` | -| NEXT_PUBLIC_HOMEPAGE_PLATE_BACKGROUND | `string` | Background css value for hero plate on the homepage (escape "#" symbol if you use HEX color codes or use rgba-value instead) | - | `radial-gradient(103.03% 103.03% at 0% 0%, rgba(183, 148, 244, 0.8) 0%, rgba(0, 163, 196, 0.8) 100%), var(--chakra-colors-blue-400)` | `radial-gradient(at 15% 86%, hsla(350,65%,70%,1) 0px, transparent 50%)` \| `no-repeat bottom 20% right 0px/100% url(https://placekitten/1400/200)` | -| NEXT_PUBLIC_HOMEPAGE_SHOW_GAS_TRACKER | `boolean` | Set to false if network doesn't have gas tracker | - | `true` | `false` | -| NEXT_PUBLIC_HOMEPAGE_SHOW_AVG_BLOCK_TIME | `boolean` | Set to false if average block time is useless for the network | - | `true` | `false` | -| NEXT_PUBLIC_HOMEPAGE_TX_ICON | `string` | Home Page Tx's Icoin | - | `` | `` | -  +| Variable | Type| Description | Compulsoriness | Default value | Example value | Version | +| --- | --- | --- | --- | --- | --- | --- | +| NEXT_PUBLIC_HOMEPAGE_CHARTS | `Array<'daily_txs' \| 'coin_price' \| 'secondary_coin_price' \| 'market_cap' \| 'tvl'>` | List of charts displayed on the home page | - | - | `['daily_txs','coin_price','market_cap']` | v1.0.x+ | +| NEXT_PUBLIC_HOMEPAGE_STATS | `Array<'latest_batch' \| 'total_blocks' \| 'average_block_time' \| 'total_txs' \| 'latest_l1_state_batch' \| 'wallet_addresses' \| 'gas_tracker' \| 'btc_locked' \| 'current_epoch'>` | List of stats widgets displayed on the home page | - | For zkSync, zkEvm and Arbitrum rollups: `['latest_batch','average_block_time','total_txs','wallet_addresses','gas_tracker']`, for other cases: `['total_blocks','average_block_time','total_txs','wallet_addresses','gas_tracker']` | `['total_blocks','total_txs','wallet_addresses']` | v1.35.x+ | +| NEXT_PUBLIC_HOMEPAGE_PLATE_TEXT_COLOR | `string` | Text color of the hero plate on the homepage (escape "#" symbol if you use HEX color codes or use rgba-value instead). **DEPRECATED** _Use `NEXT_PUBLIC_HOMEPAGE_HERO_BANNER_CONFIG` instead_ | - | `white` | `\#DCFE76` | v1.0.x+ | +| NEXT_PUBLIC_HOMEPAGE_PLATE_BACKGROUND | `string` | Background css value for hero plate on the homepage (escape "#" symbol if you use HEX color codes or use rgba-value instead). **DEPRECATED** _Use `NEXT_PUBLIC_HOMEPAGE_HERO_BANNER_CONFIG` instead_ | - | `radial-gradient(103.03% 103.03% at 0% 0%, rgba(183, 148, 244, 0.8) 0%, rgba(0, 163, 196, 0.8) 100%), var(--chakra-colors-blue-400)` | `radial-gradient(at 15% 86%, hsla(350,65%,70%,1) 0px, transparent 50%)` \| `no-repeat bottom 20% right 0px/100% url(https://placekitten/1400/200)` | v1.1.0+ | +| NEXT_PUBLIC_HOMEPAGE_HERO_BANNER_CONFIG | `HeroBannerConfig`, see details [below](#hero-banner-configuration-properties) | Configuration of hero banner appearance. | - | - | See [below](#hero-banner-configuration-properties) | v1.35.0+ | -### Sidebar +#### Hero banner configuration properties + +_Note_ Here, all values are arrays of up to two strings. The first string represents the value for the light color mode, and the second string represents the value for the dark color mode. If the array contains only one string, it will be used for both color modes. | Variable | Type| Description | Compulsoriness | Default value | Example value | | --- | --- | --- | --- | --- | --- | -| NEXT_PUBLIC_NETWORK_LOGO | `string` | Network logo; if not provided, placeholder will be shown; *Note* the logo height should be 24px and width less than 120px | - | - | `https://placekitten.com/240/40` | -| NEXT_PUBLIC_NETWORK_LOGO_DARK | `string` | Network logo for dark color mode; if not provided, **inverted** regular logo will be used instead | - | - | `https://placekitten.com/240/40` | -| NEXT_PUBLIC_NETWORK_ICON | `string` | Network icon; used as a replacement for regular network logo when nav bar is collapsed; if not provided, placeholder will be shown; *Note* the icon size should be at least 60px by 60px | - | - | `https://placekitten.com/60/60` | -| NEXT_PUBLIC_NETWORK_ICON_DARK | `string` | Network icon for dark color mode; if not provided, **inverted** regular icon will be used instead | - | - | `https://placekitten.com/60/60` | -| NEXT_PUBLIC_FEATURED_NETWORKS | `string` | URL of configuration file (`.json` format only) which contains list of featured networks that will be shown in the network menu. See [below](#featured-network-configuration-properties) list of available properties for particular network | - | - | `https://example.com/featured_networks_config.json` | -| NEXT_PUBLIC_OTHER_LINKS | `Array<{url: string; text: string}>` | List of links for the "Other" navigation menu | - | - | `[{'url':'https://blockscout.com','text':'Blockscout'}]` | -| NEXT_PUBLIC_NAVIGATION_HIDDEN_LINKS | `Array` | List of external links hidden in the navigation. Supported ids are `eth_rpc_api`, `rpc_api` | - | - | `['eth_rpc_api']` | +| background | `[string, string]` | Banner background (could be a solid color, gradient or picture). The string should be a valid `background` CSS property value. | - | `['radial-gradient(103.03% 103.03% at 0% 0%, rgba(183, 148, 244, 0.8) 0%, rgba(0, 163, 196, 0.8) 100%), var(--chakra-colors-blue-400)']` | `['lightpink','no-repeat bottom 20% right 0px/100% url(https://placekitten/1400/200)']` | +| text_color | `[string, string]` | Banner text background. The string should be a valid `color` CSS property value. | - | `['white']` | `['lightpink','#DCFE76']` | +| border | `[string, string]` | Banner border. The string should be a valid `border` CSS property value. | - | - | `['1px solid yellow','4px dashed #DCFE76']` | +| button | `Partial>` | The button on the banner. It has three possible states: `_default`, `_hover`, and `_selected`. The `_selected` state reflects when the user is logged in or their wallet is connected to the app. | - | - | `{'_default':{'background':['deeppink'],'text_color':['white']}}` | + +  +### Navigation + +| Variable | Type| Description | Compulsoriness | Default value | Example value | Version | +| --- | --- | --- | --- | --- | --- | --- | +| NEXT_PUBLIC_NETWORK_LOGO | `string` | Network logo; if not provided, placeholder will be shown; *Note* the logo height should be 24px and width less than 120px | - | - | `https://placekitten.com/240/40` | v1.0.x+ | +| NEXT_PUBLIC_NETWORK_LOGO_DARK | `string` | Network logo for dark color mode; if not provided, **inverted** regular logo will be used instead | - | - | `https://placekitten.com/240/40` | v1.0.x+ | +| NEXT_PUBLIC_NETWORK_ICON | `string` | Network icon; used as a replacement for regular network logo when nav bar is collapsed; if not provided, placeholder will be shown; *Note* the icon size should be at least 60px by 60px | - | - | `https://placekitten.com/60/60` | v1.0.x+ | +| NEXT_PUBLIC_NETWORK_ICON_DARK | `string` | Network icon for dark color mode; if not provided, **inverted** regular icon will be used instead | - | - | `https://placekitten.com/60/60` | v1.0.x+ | +| NEXT_PUBLIC_FEATURED_NETWORKS | `string` | URL of configuration file (`.json` format only) or file content string representation. It contains list of featured networks that will be shown in the network menu. See [below](#featured-network-configuration-properties) list of available properties for particular network | - | - | `https://example.com/featured_networks_config.json` \| `[{'title':'Astar(EVM)','url':'https://astar.blockscout.com/','group':'Mainnets','icon':'https://example.com/astar.svg'}]` | v1.0.x+ | +| NEXT_PUBLIC_OTHER_LINKS | `Array<{url: string; text: string}>` | List of links for the "Other" navigation menu | - | - | `[{'url':'https://blockscout.com','text':'Blockscout'}]` | v1.0.x+ | +| NEXT_PUBLIC_NAVIGATION_HIDDEN_LINKS | `Array` | List of external links hidden in the navigation. Supported ids are `eth_rpc_api`, `rpc_api` | - | - | `['eth_rpc_api']` | v1.16.0+ | +| NEXT_PUBLIC_NAVIGATION_HIGHLIGHTED_ROUTES | `Array` | List of menu item routes that should have a lightning label | - | - | `['/accounts']` | v1.31.0+ | +| NEXT_PUBLIC_NAVIGATION_LAYOUT | `vertical \| horizontal` | Navigation menu layout type | - | `vertical` | `horizontal` | v1.32.0+ | |NEXT_PUBLIC_HIDE_ENVS| `Array` |hide menus| - | - | `['/validators', '/dhcs']` | + #### Featured network configuration properties | Variable | Type| Description | Compulsoriness | Default value | Example value | @@ -143,9 +179,9 @@ Please be aware that all environment variables prefixed with `NEXT_PUBLIC_` will ### Footer -| Variable | Type| Description | Compulsoriness | Default value | Example value | -| --- | --- | --- | --- | --- | --- | -| NEXT_PUBLIC_FOOTER_LINKS | `string` | URL of configuration file (`.json` format only) which contains list of link groups to be displayed in the footer. See [below](#footer-links-configuration-properties) list of available properties for particular group | - | - | `https://example.com/footer_links_config.json` | +| Variable | Type| Description | Compulsoriness | Default value | Example value | Version | +| --- | --- | --- | --- | --- | --- | --- | +| NEXT_PUBLIC_FOOTER_LINKS | `string` | URL of configuration file (`.json` format only) or file content string representation. It contains list of link groups to be displayed in the footer. See [below](#footer-links-configuration-properties) list of available properties for particular group | - | - | `https://example.com/footer_links_config.json` \| `[{'title':'My chain','links':[{'text':'About','url':'https://example.com/about'},{'text':'Contacts','url':'https://example.com/contacts'}]}]` | v1.1.1+ | The app version shown in the footer is derived from build-time ENV variables `NEXT_PUBLIC_GIT_TAG` and `NEXT_PUBLIC_GIT_COMMIT_SHA` and cannot be overwritten at run-time. @@ -160,24 +196,25 @@ The app version shown in the footer is derived from build-time ENV variables `NE ### Favicon -By default, the app has generic favicon. You can override this behavior by providing the following variables. Hence, the favicon assets bundle will be generated at the container start time and will be used instead of default one. +By default, the app has generic favicon. You can override this behavior by providing the following variable. Hence, the favicon assets bundle will be generated at the container start time and will be used instead of default one. -| Variable | Type| Description | Compulsoriness | Default value | Example value | -| --- | --- | --- | --- | --- | --- | -| FAVICON_GENERATOR_API_KEY | `string` | RealFaviconGenerator [API key](https://realfavicongenerator.net/api/) | Required | - | `` | -| FAVICON_MASTER_URL | `string` | - | - | `NEXT_PUBLIC_NETWORK_ICON` | `https://placekitten.com/180/180` | +| Variable | Type| Description | Compulsoriness | Default value | Example value | Version | +| --- | --- | --- | --- | --- | --- | --- | +| FAVICON_MASTER_URL | `string` | - | - | `NEXT_PUBLIC_NETWORK_ICON` | `https://placekitten.com/180/180` | v1.11.0+ |   ### Meta -Settings for meta tags and OG tags +Settings for meta tags, OG tags and SEO -| Variable | Type| Description | Compulsoriness | Default value | Example value | -| --- | --- | --- | --- | --- | --- | -| NEXT_PUBLIC_PROMOTE_BLOCKSCOUT_IN_TITLE | `boolean` | Set to `true` to promote Blockscout in meta and OG titles | - | `true` | `true` | -| NEXT_PUBLIC_OG_DESCRIPTION | `string` | Custom OG description | - | - | `Blockscout is the #1 open-source blockchain explorer available today. 100+ chains and counting rely on Blockscout data availability, APIs, and ecosystem tools to support their networks.` | -| NEXT_PUBLIC_OG_IMAGE_URL | `string` | OG image url. Minimum image size is 200 x 20 pixels (recommended: 1200 x 600); maximum supported file size is 8 MB; 2:1 aspect ratio; supported formats: image/jpeg, image/gif, image/png | - | `static/og_placeholder.png` | `https://placekitten.com/1200/600` | +| Variable | Type| Description | Compulsoriness | Default value | Example value | Version | +| --- | --- | --- | --- | --- | --- | --- | +| NEXT_PUBLIC_PROMOTE_BLOCKSCOUT_IN_TITLE | `boolean` | Set to `true` to promote Blockscout in meta and OG titles | - | `true` | `true` | v1.12.0+ | +| NEXT_PUBLIC_OG_DESCRIPTION | `string` | Custom OG description | - | - | `Open-source block explorer by Blockscout. Search transactions, verify smart contracts, analyze addresses, and track network activity. Complete blockchain data and APIs for the %network_title% network.` | v1.12.0+ | +| NEXT_PUBLIC_OG_IMAGE_URL | `string` | OG image url. Minimum image size is 200 x 20 pixels (recommended: 1200 x 600); maximum supported file size is 8 MB; 2:1 aspect ratio; supported formats: image/jpeg, image/gif, image/png | - | `static/og_placeholder.png` | `https://placekitten.com/1200/600` | v1.12.0+ | +| NEXT_PUBLIC_OG_ENHANCED_DATA_ENABLED | `boolean` | Set to `true` to populate OG tags (title, description) with API data for social preview robot requests | - | `false` | `true` | v1.29.0+ | +| NEXT_PUBLIC_SEO_ENHANCED_DATA_ENABLED | `boolean` | Set to `true` to pre-render page titles (e.g Token page) on the server side and inject page h1-tag to the markup before it is sent to the browser. | - | `false` | `true` | v1.30.0+ |   @@ -185,28 +222,35 @@ Settings for meta tags and OG tags #### Block views -| Variable | Type | Description | Compulsoriness | Default value | Example value | -| --- | --- | --- | --- | --- | --- | -| NEXT_PUBLIC_VIEWS_BLOCK_HIDDEN_FIELDS | `Array` | Array of the block fields ids that should be hidden. See below the list of the possible id values. | - | - | `'["burnt_fees","total_reward"]'` | +| Variable | Type | Description | Compulsoriness | Default value | Example value | Version | +| --- | --- | --- | --- | --- | --- | --- | +| NEXT_PUBLIC_VIEWS_BLOCK_HIDDEN_FIELDS | `Array` | Array of the block fields ids that should be hidden. See below the list of the possible id values. | - | - | `'["burnt_fees","total_reward"]'` | v1.10.0+ | ##### Block fields list | Id | Description | | --- | --- | +| `base_fee` | Base fee | | `burnt_fees` | Burnt fees | | `total_reward` | Total block reward | | `nonce` | Block nonce | | `miner` | Address of block's miner or validator | +| `L1_status` | Short interpretation of the batch lifecycle (applicable for Rollup chains) | +| `batch` | Batch index (applicable for Rollup chains) |   #### Address views -| Variable | Type | Description | Compulsoriness | Default value | Example value | -| --- | --- | --- | --- | --- | --- | -| NEXT_PUBLIC_VIEWS_ADDRESS_IDENTICON_TYPE | `"github" \| "jazzicon" \| "gradient_avatar" \| "blockie"` | Style of address identicon appearance. Choose between [GitHub](https://github.blog/2013-08-14-identicons/), [Metamask Jazzicon](https://metamask.github.io/jazzicon/), [Gradient Avatar](https://github.com/varld/gradient-avatar) and [Ethereum Blocky](https://mycryptohq.github.io/ethereum-blockies-base64/) | - | `jazzicon` | `gradient_avatar` | -| NEXT_PUBLIC_VIEWS_ADDRESS_HIDDEN_VIEWS | `Array` | Address views that should not be displayed. See below the list of the possible id values. | - | - | `'["top_accounts"]'` | -| NEXT_PUBLIC_VIEWS_CONTRACT_SOLIDITYSCAN_ENABLED | `boolean` | Set to `true` if SolidityScan reports are supported | - | - | `true` | +| Variable | Type | Description | Compulsoriness | Default value | Example value | Version | +| --- | --- | --- | --- | --- | --- | --- | +| NEXT_PUBLIC_VIEWS_ADDRESS_IDENTICON_TYPE | `"github" \| "jazzicon" \| "gradient_avatar" \| "blockie" \| "nouns"` | Default style of address identicon appearance. Choose between [GitHub](https://github.blog/2013-08-14-identicons/), [Metamask Jazzicon](https://metamask.github.io/jazzicon/), [Gradient Avatar](https://github.com/varld/gradient-avatar), [Ethereum Blocky](https://mycryptohq.github.io/ethereum-blockies-base64/) and [Nouns](https://nouns.wtf) | - | `jazzicon` | `gradient_avatar` | v1.12.0+ | +| NEXT_PUBLIC_VIEWS_ADDRESS_FORMAT | `Array<"base16" \| "bech32">` | Displayed address format, could be either `base16` standard or [`bech32`](https://github.com/bitcoin/bips/blob/master/bip-0173.mediawiki#bech32) standard. If the array contains multiple values, the address format toggle will appear in the UI, allowing the user to switch between formats. The first item in the array will be the default format. | - | `'["base16"]'` | `'["bech32", "base16"]'` | v1.36.0+ | +| NEXT_PUBLIC_VIEWS_ADDRESS_BECH_32_PREFIX | `string` | Human-readable prefix of `bech32` address format. | Required, if `NEXT_PUBLIC_VIEWS_ADDRESS_FORMAT` contains "bech32" value | - | `duck` | v1.36.0+ | +| NEXT_PUBLIC_VIEWS_ADDRESS_HIDDEN_VIEWS | `Array` | Address views that should not be displayed. See below the list of the possible id values. | - | - | `'["top_accounts"]'` | v1.15.0+ | +| NEXT_PUBLIC_VIEWS_CONTRACT_SOLIDITYSCAN_ENABLED | `boolean` | Set to `true` if SolidityScan reports are supported | - | - | `true` | v1.19.0+ | +| NEXT_PUBLIC_VIEWS_CONTRACT_EXTRA_VERIFICATION_METHODS | `Array<'solidity-hardhat' \| 'solidity-foundry'>` | Pass an array of additional methods from which users can choose while verifying a smart contract. Both methods are available by default, pass `'none'` string to disable them all. | - | - | `['solidity-hardhat']` | v1.33.0+ | +| NEXT_PUBLIC_VIEWS_CONTRACT_LANGUAGE_FILTERS | `Array<'solidity' \| 'vyper' \| 'yul' \| 'scilla'>` | Pass an array of contract languages that will be displayed as options in the filter on the verified contract page. | - | `['solidity','vyper','yul']` | `['solidity','vyper','yul','scilla']` | v1.37.0+ | ##### Address views list | Id | Description | @@ -217,10 +261,10 @@ Settings for meta tags and OG tags #### Transaction views -| Variable | Type | Description | Compulsoriness | Default value | Example value | -| --- | --- | --- | --- | --- | --- | -| NEXT_PUBLIC_VIEWS_TX_HIDDEN_FIELDS | `Array` | Array of the transaction fields ids that should be hidden. See below the list of the possible id values. | - | - | `'["value","tx_fee"]'` | -| NEXT_PUBLIC_VIEWS_TX_ADDITIONAL_FIELDS | `Array` | Array of the additional fields ids that should be added to the transaction details. See below the list of the possible id values. | - | - | `'["fee_per_gas"]'` | +| Variable | Type | Description | Compulsoriness | Default value | Example value | Version | +| --- | --- | --- | --- | --- | --- | --- | +| NEXT_PUBLIC_VIEWS_TX_HIDDEN_FIELDS | `Array` | Array of the transaction fields ids that should be hidden. See below the list of the possible id values. | - | - | `'["value","tx_fee"]'` | v1.15.0+ | +| NEXT_PUBLIC_VIEWS_TX_ADDITIONAL_FIELDS | `Array` | Array of the additional fields ids that should be added to the transaction details. See below the list of the possible id values. | - | - | `'["fee_per_gas"]'` | v1.15.0+ | ##### Transaction fields list | Id | Description | @@ -231,6 +275,8 @@ Settings for meta tags and OG tags | `tx_fee` | Total transaction fee | | `gas_fees` | Gas fees breakdown | | `burnt_fees` | Amount of native coin burnt for transaction | +| `L1_status` | Short interpretation of the batch lifecycle (applicable for Rollup chains) | +| `batch` | Batch index (applicable for Rollup chains) | ##### Transaction additional fields list | Id | Description | @@ -241,10 +287,10 @@ Settings for meta tags and OG tags #### NFT views -| Variable | Type | Description | Compulsoriness | Default value | Example value | -| --- | --- | --- | --- | --- | --- | -| NEXT_PUBLIC_VIEWS_NFT_MARKETPLACES | `Array` where `NftMarketplace` can have following [properties](#nft-marketplace-properties) | Used to build up links to NFT collections and NFT instances in external marketplaces. | - | - | `[{'name':'OpenSea','collection_url':'https://opensea.io/assets/ethereum/{hash}','instance_url':'https://opensea.io/assets/ethereum/{hash}/{id}','logo_url':'https://opensea.io/static/images/logos/opensea-logo.svg'}]` | - +| Variable | Type | Description | Compulsoriness | Default value | Example value | Version | +| --- | --- | --- | --- | --- | --- | --- | +| NEXT_PUBLIC_VIEWS_NFT_MARKETPLACES | `Array` where `NftMarketplace` can have following [properties](#nft-marketplace-properties) | Used to build up links to NFT collections and NFT instances in external marketplaces. | - | - | `[{'name':'OpenSea','collection_url':'https://opensea.io/assets/ethereum/{hash}','instance_url':'https://opensea.io/assets/ethereum/{hash}/{id}','logo_url':'https://opensea.io/static/images/logos/opensea-logo.svg'}]` | v1.15.0+ | +| NEXT_PUBLIC_HELIA_VERIFIED_FETCH_ENABLED | `boolean` | Indicates that the [Helia verified fetch](https://github.com/ipfs/helia-verified-fetch/tree/main/packages/verified-fetch) should be used for retrieving content of NFT assets (currently limited to images) directly from IPFS network using trustless gateways. | - | `true` | `false` | v1.37.0+ | ##### NFT marketplace properties | Variable | Type| Description | Compulsoriness | Default value | Example value | @@ -260,18 +306,24 @@ Settings for meta tags and OG tags ### Misc -| Variable | Type| Description | Compulsoriness | Default value | Example value | -| --- | --- | --- | --- | --- | --- | -| NEXT_PUBLIC_NETWORK_EXPLORERS | `Array` where `NetworkExplorer` can have following [properties](#network-explorer-configuration-properties) | Used to build up links to transactions, blocks, addresses in other chain explorers. | - | - | `[{'title':'Anyblock','baseUrl':'https://explorer.anyblock.tools','paths':{'tx':'/ethereum/poa/core/tx'}}]` | -| NEXT_PUBLIC_CONTRACT_CODE_IDES | `Array` where `ContractCodeIde` can have following [properties](#contract-code-ide-configuration-properties) | Used to build up links to IDEs with contract source code. | - | - | `[{'title':'Remix IDE','url':'https://remix.blockscout.com/?address={hash}&blockscout={domain}','icon_url':'https://example.com/icon.svg'}]` | -| NEXT_PUBLIC_HIDE_INDEXING_ALERT_BLOCKS | `boolean` | Set to `true` to hide indexing alert in the page header about indexing chain's blocks | - | `false` | `true` | -| NEXT_PUBLIC_HIDE_INDEXING_ALERT_INT_TXS | `boolean` | Set to `true` to hide indexing alert in the page footer about indexing block's internal transactions | - | `false` | `true` | -| NEXT_PUBLIC_MAINTENANCE_ALERT_MESSAGE | `string` | Used for displaying custom announcements or alerts in the header of the site. Could be a regular string or a HTML code. | - | - | `Hello world! 🤪` | +| Variable | Type| Description | Compulsoriness | Default value | Example value | Version | +| --- | --- | --- | --- | --- | --- | --- | +| NEXT_PUBLIC_NETWORK_EXPLORERS | `Array` where `NetworkExplorer` can have following [properties](#network-explorer-configuration-properties) | Used to build up links to transactions, blocks, addresses in other chain explorers. | - | - | `[{'title':'Anyblock','baseUrl':'https://explorer.anyblock.tools','paths':{'tx':'/ethereum/poa/core/tx'}}]` | v1.0.x+ | +| NEXT_PUBLIC_CONTRACT_CODE_IDES | `Array` where `ContractCodeIde` can have following [properties](#contract-code-ide-configuration-properties) | Used to build up links to IDEs with contract source code. | - | - | `[{'title':'Remix IDE','url':'https://remix.blockscout.com/?address={hash}&blockscout={domain}','icon_url':'https://example.com/icon.svg'}]` | v1.23.0+ | +| NEXT_PUBLIC_HAS_CONTRACT_AUDIT_REPORTS | `boolean` | Set to `true` to enable Submit Audit form on the contract page | - | `false` | `true` | v1.25.0+ | +| NEXT_PUBLIC_HIDE_INDEXING_ALERT_BLOCKS | `boolean` | Set to `true` to hide indexing alert in the page header about indexing chain's blocks | - | `false` | `true` | v1.17.0+ | +| NEXT_PUBLIC_HIDE_INDEXING_ALERT_INT_TXS | `boolean` | Set to `true` to hide indexing alert in the page footer about indexing block's internal transactions | - | `false` | `true` | v1.17.0+ | +| NEXT_PUBLIC_MAINTENANCE_ALERT_MESSAGE | `string` | Used for displaying custom announcements or alerts in the header of the site. Could be a regular string or a HTML code. | - | - | `Hello world! 🤪` | v1.13.0+ | +| NEXT_PUBLIC_COLOR_THEME_DEFAULT | `'light' \| 'dim' \| 'midnight' \| 'dark'` | Preferred color theme of the app | - | - | `midnight` | v1.30.0+ | +| NEXT_PUBLIC_FONT_FAMILY_HEADING | `FontFamily`, see full description [below](#font-family-configuration-properties) | Special typeface to use in page headings (`

`, `

`, etc.) | - | - | `{'name':'Montserrat','url':'https://fonts.googleapis.com/css2?family=Montserrat:wght@400;500;600;700&display=swap'}` | v1.35.0+ | +| NEXT_PUBLIC_FONT_FAMILY_BODY | `FontFamily`, see full description [below](#font-family-configuration-properties) | Main typeface to use in page content elements. | - | - | `{'name':'Raleway','url':'https://fonts.googleapis.com/css2?family=Raleway:wght@400;500;600;700&display=swap'}` | v1.35.0+ | +| NEXT_PUBLIC_MAX_CONTENT_WIDTH_ENABLED | `boolean` | Set to `true` to restrict the page content width on extra-large screens. | - | `true` | `false` | v1.34.1+ | #### Network explorer configuration properties | Variable | Type| Description | Compulsoriness | Default value | Example value | | --- | --- | --- | --- | --- | --- | +| logo | `string` | URL to explorer logo file. Should be at least 40x40. | - | - | `'https://foo.app/icon.png'` | | title | `string` | Displayed name of the explorer | Required | - | `Anyblock` | | baseUrl | `string` | Base url of the explorer | Required | - | `https://explorer.anyblock.tools` | | paths | `Record<'tx' \| 'block' \| 'address' \| 'token', string>` | Map of explorer entities and their paths | Required | - | `{'tx':'/ethereum/poa/core/tx'}` | @@ -286,6 +338,13 @@ Settings for meta tags and OG tags | url | `string` | URL of the IDE with placeholders for contract hash (`{hash}`) and current domain (`{domain}`) | Required | - | `https://remix.blockscout.com/?address={hash}&blockscout={domain}` | | icon_url | `string` | URL of the IDE icon | Required | - | `https://example.com/icon.svg` | +#### Font family configuration properties + +| Variable | Type| Description | Compulsoriness | Default value | Example value | +| --- | --- | --- | --- | --- | --- | +| name | `string` | Font family name; used to define the `font-family` CSS property. | Required | - | `Montserrat` | +| url | `string` | URL for external font. Ensure the font supports the following weights: 400, 500, 600, and 700. | Required | - | `https://fonts.googleapis.com/css2?family=Montserrat:wght@400;500;600;700&display=swap` | +   ## App features @@ -294,12 +353,34 @@ Settings for meta tags and OG tags ### My account -| Variable | Type| Description | Compulsoriness | Default value | Example value | -| --- | --- | --- | --- | --- | --- | -| NEXT_PUBLIC_IS_ACCOUNT_SUPPORTED | `boolean` | Set to true if network has account feature | Required | - | `true` | -| NEXT_PUBLIC_AUTH0_CLIENT_ID | `string` | Client id for [Auth0](https://auth0.com/) provider | Required | - | `` | -| NEXT_PUBLIC_AUTH_URL | `string` | Account auth base url; it is used for building login URL (`${ NEXT_PUBLIC_AUTH_URL }/auth/auth0`) and logout return URL (`${ NEXT_PUBLIC_AUTH_URL }/auth/logout`); if not provided the base app URL will be used instead | Required | - | `https://blockscout.com` | -| NEXT_PUBLIC_LOGOUT_URL | `string` | Account logout url. Required if account is supported for the app instance. | Required | - | `https://blockscoutcom.us.auth0.com/v2/logout` | +| Variable | Type| Description | Compulsoriness | Default value | Example value | Version | +| --- | --- | --- | --- | --- | --- | --- | +| NEXT_PUBLIC_IS_ACCOUNT_SUPPORTED | `boolean` | Set to true if network has account feature | Required | - | `true` | v1.0.x+ | +| NEXT_PUBLIC_RE_CAPTCHA_APP_SITE_KEY | `boolean` | See [below](ENVS.md#google-recaptcha) | Required | - | `` | v1.0.x+ | +| NEXT_PUBLIC_AUTH0_CLIENT_ID | `string` | **DEPRECATED** Client id for [Auth0](https://auth0.com/) provider | - | - | `` | v1.0.x+ | +| NEXT_PUBLIC_AUTH_URL | `string` | **DEPRECATED** Account auth base url; it is used for building login URL (`${ NEXT_PUBLIC_AUTH_URL }/auth/auth0`) and logout return URL (`${ NEXT_PUBLIC_AUTH_URL }/auth/logout`); if not provided the base app URL will be used instead | - | - | `https://blockscout.com` | v1.0.x+ | +| NEXT_PUBLIC_LOGOUT_URL | `string` | **DEPRECATED** Account logout url. Required if account is supported for the app instance. | - | - | `https://blockscoutcom.us.auth0.com/v2/logout` | v1.0.x+ | + +  + +### Gas tracker + +This feature is **enabled by default**. To switch it off pass `NEXT_PUBLIC_GAS_TRACKER_ENABLED=false`. + +| Variable | Type| Description | Compulsoriness | Default value | Example value | Version | +| --- | --- | --- | --- | --- | --- | --- | +| NEXT_PUBLIC_GAS_TRACKER_ENABLED | `boolean` | Set to true to enable "Gas tracker" in the app | Required | `true` | `false` | v1.25.0+ | +| NEXT_PUBLIC_GAS_TRACKER_UNITS | Array<`usd` \| `gwei`> | Array of units for displaying gas prices on the Gas Tracker page, in the stats snippet on the Home page, and in the top bar. The first value in the array will take priority over the second one in all mentioned views. If only one value is provided, gas prices will be displayed only in that unit. | - | `[ 'usd', 'gwei' ]` | `[ 'gwei' ]` | v1.25.0+ | + +  + +### Advanced filter + +This feature is **enabled by default**. To switch it off pass `NEXT_PUBLIC_ADVANCED_FILTER_ENABLED=false`. + +| Variable | Type| Description | Compulsoriness | Default value | Example value | Version | +| --- | --- | --- | --- | --- | --- | --- | +| NEXT_PUBLIC_ADVANCED_FILTER_ENABLED | `boolean` | Set to true to enable "Advanced filter" page in the app | Required | `true` | `false` | v1.37.0+ |   @@ -307,36 +388,38 @@ Settings for meta tags and OG tags *Note* all ENV variables required for [My account](ENVS.md#my-account) feature should be passed alongside the following ones: -| Variable | Type| Description | Compulsoriness | Default value | Example value | -| --- | --- | --- | --- | --- | --- | -| NEXT_PUBLIC_CONTRACT_INFO_API_HOST | `string` | Contract Info API endpoint url | Required | - | `https://contracts-info.services.blockscout.com` | -| NEXT_PUBLIC_ADMIN_SERVICE_API_HOST | `string` | Admin Service API endpoint url | Required | - | `https://admin-rs.services.blockscout.com` | +| Variable | Type| Description | Compulsoriness | Default value | Example value | Version | +| --- | --- | --- | --- | --- | --- | --- | +| NEXT_PUBLIC_CONTRACT_INFO_API_HOST | `string` | Contract Info API endpoint url | Required | - | `https://contracts-info.services.blockscout.com` | v1.1.0+ | +| NEXT_PUBLIC_ADMIN_SERVICE_API_HOST | `string` | Admin Service API endpoint url | Required | - | `https://admin-rs.services.blockscout.com` | v1.1.0+ |   ### Blockchain interaction (writing to contract, etc.) -| Variable | Type| Description | Compulsoriness | Default value | Example value | -| --- | --- | --- | --- | --- | --- | -| NEXT_PUBLIC_WALLET_CONNECT_PROJECT_ID | `string` | Project id for [WalletConnect](https://docs.walletconnect.com/2.0/web3modal/react/installation#obtain-project-id) integration | Required | - | `` | -| NEXT_PUBLIC_NETWORK_RPC_URL | `string` | See in [Blockchain parameters](ENVS.md#blockchain-parameters) section | Required | - | `https://core.poa.network` | -| NEXT_PUBLIC_NETWORK_NAME | `string` | See in [Blockchain parameters](ENVS.md#blockchain-parameters) section | Required | - | `Gnosis Chain` | -| NEXT_PUBLIC_NETWORK_ID | `number` | See in [Blockchain parameters](ENVS.md#blockchain-parameters) section | Required | - | `99` | -| NEXT_PUBLIC_NETWORK_CURRENCY_NAME | `string` | See in [Blockchain parameters](ENVS.md#blockchain-parameters) section | Required | - | `Ether` | -| NEXT_PUBLIC_NETWORK_CURRENCY_SYMBOL | `string` | See in [Blockchain parameters](ENVS.md#blockchain-parameters) section | Required | - | `ETH` | -| NEXT_PUBLIC_NETWORK_CURRENCY_DECIMALS | `string` | See in [Blockchain parameters](ENVS.md#blockchain-parameters) section | - | `18` | `6` | +| Variable | Type| Description | Compulsoriness | Default value | Example value | Version | +| --- | --- | --- | --- | --- | --- | --- | +| NEXT_PUBLIC_WALLET_CONNECT_PROJECT_ID | `string` | Project id for [WalletConnect](https://cloud.walletconnect.com/) integration | Required | - | `` | v1.0.x+ | +| NEXT_PUBLIC_NETWORK_RPC_URL | `string` | See in [Blockchain parameters](ENVS.md#blockchain-parameters) section | Required | - | `https://core.poa.network` | v1.0.x+ | +| NEXT_PUBLIC_NETWORK_NAME | `string` | See in [Blockchain parameters](ENVS.md#blockchain-parameters) section | Required | - | `Gnosis Chain` | v1.0.x+ | +| NEXT_PUBLIC_NETWORK_ID | `number` | See in [Blockchain parameters](ENVS.md#blockchain-parameters) section | Required | - | `99` | v1.0.x+ | +| NEXT_PUBLIC_NETWORK_CURRENCY_NAME | `string` | See in [Blockchain parameters](ENVS.md#blockchain-parameters) section | Required | - | `Ether` | v1.0.x+ | +| NEXT_PUBLIC_NETWORK_CURRENCY_SYMBOL | `string` | See in [Blockchain parameters](ENVS.md#blockchain-parameters) section | Required | - | `ETH` | v1.0.x+ | +| NEXT_PUBLIC_NETWORK_CURRENCY_DECIMALS | `string` | See in [Blockchain parameters](ENVS.md#blockchain-parameters) section | - | `18` | `6` | v1.0.x+ |   ### Banner ads This feature is **enabled by default** with the `slise` ads provider. To switch it off pass `NEXT_PUBLIC_AD_BANNER_PROVIDER=none`. +*Note* that the `getit` ad provider is temporary disabled. -| Variable | Type| Description | Compulsoriness | Default value | Example value | -| --- | --- | --- | --- | --- | --- | -| NEXT_PUBLIC_AD_BANNER_PROVIDER | `slise` \| `adbutler` \| `coinzilla` \| `none` | Ads provider | - | `slise` | `coinzilla` | -| NEXT_PUBLIC_AD_ADBUTLER_CONFIG_DESKTOP | `{ id: string; width: string; height: string }` | Placement config for desktop Adbutler banner | - | - | `{'id':'123456','width':'728','height':'90'}` | -| NEXT_PUBLIC_AD_ADBUTLER_CONFIG_MOBILE | `{ id: string; width: number; height: number }` | Placement config for mobile Adbutler banner | - | - | `{'id':'654321','width':'300','height':'100'}` | +| Variable | Type| Description | Compulsoriness | Default value | Example value | Version | +| --- | --- | --- | --- | --- | --- | --- | +| NEXT_PUBLIC_AD_BANNER_PROVIDER | `slise` \| `adbutler` \| `coinzilla` \| `hype` \| `getit` \| `none` | Ads provider | - | `slise` | `coinzilla` | v1.0.x+ | +| NEXT_PUBLIC_AD_BANNER_ADDITIONAL_PROVIDER | `adbutler` | Additional ads provider to mix with the main one | - | - | `adbutler` | v1.28.0+ | +| NEXT_PUBLIC_AD_ADBUTLER_CONFIG_DESKTOP | `{ id: string; width: string; height: string }` | Placement config for desktop Adbutler banner | - | - | `{'id':'123456','width':'728','height':'90'}` | v1.3.0+ | +| NEXT_PUBLIC_AD_ADBUTLER_CONFIG_MOBILE | `{ id: string; width: number; height: number }` | Placement config for mobile Adbutler banner | - | - | `{'id':'654321','width':'300','height':'100'}` | v1.3.0+ |   @@ -344,117 +427,125 @@ This feature is **enabled by default** with the `slise` ads provider. To switch This feature is **enabled by default** with the `coinzilla` ads provider. To switch it off pass `NEXT_PUBLIC_AD_TEXT_PROVIDER=none`. -| Variable | Type| Description | Compulsoriness | Default value | Example value | -| --- | --- | --- | --- | --- | --- | -| NEXT_PUBLIC_AD_TEXT_PROVIDER | `coinzilla` \| `none` | Ads provider | - | `coinzilla` | `none` | +| Variable | Type| Description | Compulsoriness | Default value | Example value | Version | +| --- | --- | --- | --- | --- | --- | --- | +| NEXT_PUBLIC_AD_TEXT_PROVIDER | `coinzilla` \| `none` | Ads provider | - | `coinzilla` | `none` | v1.0.x+ |   ### Beacon chain -| Variable | Type| Description | Compulsoriness | Default value | Example value | -| --- | --- | --- | --- | --- | --- | -| NEXT_PUBLIC_HAS_BEACON_CHAIN | `boolean` | Set to true for networks with the beacon chain | Required | - | `true` | -| NEXT_PUBLIC_BEACON_CHAIN_CURRENCY_SYMBOL | `string` | Beacon network currency symbol | - | `NEXT_PUBLIC_NETWORK_CURRENCY_SYMBOL` | `ETH` | +| Variable | Type| Description | Compulsoriness | Default value | Example value | Version | +| --- | --- | --- | --- | --- | --- | --- | +| NEXT_PUBLIC_HAS_BEACON_CHAIN | `boolean` | Set to true for networks with the beacon chain | Required | - | `true` | v1.0.x+ | +| NEXT_PUBLIC_BEACON_CHAIN_CURRENCY_SYMBOL | `string` | Beacon network currency symbol | - | `NEXT_PUBLIC_NETWORK_CURRENCY_SYMBOL` | `ETH` | v1.0.x+ |   -### User operations feature (ERC-4337) +### User operations (ERC-4337) -| Variable | Type| Description | Compulsoriness | Default value | Example value | -| --- | --- | --- | --- | --- | --- | -| NEXT_PUBLIC_HAS_USER_OPS | `boolean` | Set to true to show user operations related data and pages | - | - | `true` | +| Variable | Type| Description | Compulsoriness | Default value | Example value | Version | +| --- | --- | --- | --- | --- | --- | --- | +| NEXT_PUBLIC_HAS_USER_OPS | `boolean` | Set to true to show user operations related data and pages | - | - | `true` | v1.23.0+ |   -### Optimistic rollup (L2) chain - -| Variable | Type| Description | Compulsoriness | Default value | Example value | -| --- | --- | --- | --- | --- | --- | -| NEXT_PUBLIC_IS_OPTIMISTIC_L2_NETWORK | `boolean` | Set to true for optimistic L2 solutions | Required | - | `true` | -| NEXT_PUBLIC_OPTIMISTIC_L2_WITHDRAWAL_URL | `string` | URL for optimistic L2 -> L1 withdrawals | Required | - | `https://app.optimism.io/bridge/withdraw` | -| NEXT_PUBLIC_L1_BASE_URL | `string` | Blockscout base URL for L1 network | Required | - | `'http://eth-goerli.blockscout.com'` | - -  - -### ZkEvm rollup (L2) chain -| Variable | Type| Description | Compulsoriness | Default value | Example value | -| --- | --- | --- | --- | --- | --- | -| NEXT_PUBLIC_IS_ZKEVM_L2_NETWORK | `boolean` | Set to true for zkevm L2 solutions | Required | - | `true` | -| NEXT_PUBLIC_L1_BASE_URL | `string` | Blockscout base URL for L1 network | Required | - | `'http://eth-goerli.blockscout.com'` | +### Rollup chain +| Variable | Type| Description | Compulsoriness | Default value | Example value | Version | +| --- | --- | --- | --- | --- | --- | --- | +| NEXT_PUBLIC_ROLLUP_TYPE | `'optimistic' \| 'arbitrum' \| 'shibarium' \| 'zkEvm' \| 'zkSync' \| 'scroll'` | Rollup chain type | Required | - | `'optimistic'` | v1.24.0+ | +| NEXT_PUBLIC_ROLLUP_L1_BASE_URL | `string` | Blockscout base URL for L1 network | Required | - | `'http://eth-goerli.blockscout.com'` | v1.24.0+ | +| NEXT_PUBLIC_ROLLUP_L2_WITHDRAWAL_URL | `string` | URL for L2 -> L1 withdrawals (Optimistic stack only) | Required for `optimistic` rollups | - | `https://app.optimism.io/bridge/withdraw` | v1.24.0+ | +| NEXT_PUBLIC_FAULT_PROOF_ENABLED | `boolean` | Set to `true` for chains with fault proof system enabled (Optimistic stack only) | - | - | `true` | v1.31.0+ | +| NEXT_PUBLIC_HAS_MUD_FRAMEWORK | `boolean` | Set to `true` for instances that use MUD framework (Optimistic stack only) | - | - | `true` | v1.33.0+ | +| NEXT_PUBLIC_ROLLUP_HOMEPAGE_SHOW_LATEST_BLOCKS | `boolean` | Set to `true` to display "Latest blocks" widget instead of "Latest batches" on the home page | - | - | `true` | v1.36.0+ | +| NEXT_PUBLIC_ROLLUP_OUTPUT_ROOTS_ENABLED | `boolean` | Enables "Output roots" page (Optimistic stack only) | - | `true` | `false` | v1.37.0+ | +| NEXT_PUBLIC_ROLLUP_PARENT_CHAIN_NAME | `string` | Set to customize L1 transaction status labels in the UI (e.g., "Sent to "). This setting is applicable only for Arbitrum-based chains. | - | - | `DuckChain` | v1.37.0+ |   ### Export data to CSV file -| Variable | Type| Description | Compulsoriness | Default value | Example value | -| --- | --- | --- | --- | --- | --- | -| NEXT_PUBLIC_RE_CAPTCHA_APP_SITE_KEY | `string` | See [below](ENVS.md#google-recaptcha) | true | - | `` | +| Variable | Type| Description | Compulsoriness | Default value | Example value | Version | +| --- | --- | --- | --- | --- | --- | --- | +| NEXT_PUBLIC_RE_CAPTCHA_APP_SITE_KEY | `string` | See [below](ENVS.md#google-recaptcha) | true | - | `` | v1.0.x+ |   ### Google analytics -| Variable | Type| Description | Compulsoriness | Default value | Example value | -| --- | --- | --- | --- | --- | --- | -| NEXT_PUBLIC_GOOGLE_ANALYTICS_PROPERTY_ID | `string` | Property ID for [Google Analytics](https://analytics.google.com/) service | true | - | `UA-XXXXXX-X` | +| Variable | Type| Description | Compulsoriness | Default value | Example value | Version | +| --- | --- | --- | --- | --- | --- | --- | +| NEXT_PUBLIC_GOOGLE_ANALYTICS_PROPERTY_ID | `string` | Property ID for [Google Analytics](https://analytics.google.com/) service | true | - | `UA-XXXXXX-X` | v1.0.x+ |   ### Mixpanel analytics -| Variable | Type| Description | Compulsoriness | Default value | Example value | -| --- | --- | --- | --- | --- | --- | -| NEXT_PUBLIC_MIXPANEL_PROJECT_TOKEN | `string` | Project token for [Mixpanel](https://mixpanel.com/) analytics service | true | - | `` | +| Variable | Type| Description | Compulsoriness | Default value | Example value | Version | +| --- | --- | --- | --- | --- | --- | --- | +| NEXT_PUBLIC_MIXPANEL_PROJECT_TOKEN | `string` | Project token for [Mixpanel](https://mixpanel.com/) analytics service | true | - | `` | v1.1.0+ |   ### GrowthBook feature flagging and A/B testing -| Variable | Type| Description | Compulsoriness | Default value | Example value | -| --- | --- | --- | --- | --- | --- | -| NEXT_PUBLIC_GROWTH_BOOK_CLIENT_KEY | `string` | Client SDK key for [GrowthBook](https://www.growthbook.io/) service | true | - | `` | +| Variable | Type| Description | Compulsoriness | Default value | Example value | Version | +| --- | --- | --- | --- | --- | --- | --- | +| NEXT_PUBLIC_GROWTH_BOOK_CLIENT_KEY | `string` | Client SDK key for [GrowthBook](https://www.growthbook.io/) service | true | - | `` | v1.22.0+ |   ### GraphQL API documentation -This feature is **always enabled**, but you can configure its behavior by passing the following variables. +This feature is **always enabled**, but you can disable it by passing `none` value to `NEXT_PUBLIC_GRAPHIQL_TRANSACTION` variable. -| Variable | Type| Description | Compulsoriness | Default value | Example value | -| --- | --- | --- | --- | --- | --- | -| NEXT_PUBLIC_GRAPHIQL_TRANSACTION | `string` | Txn hash for default query at GraphQl playground page | - | - | `0x4a0ed8ddf751a7cb5297f827699117b0f6d21a0b2907594d300dc9fed75c7e62` | +| Variable | Type| Description | Compulsoriness | Default value | Example value | Version | +| --- | --- | --- | --- | --- | --- | --- | +| NEXT_PUBLIC_GRAPHIQL_TRANSACTION | `string` | Txn hash for default query at GraphQl playground page. Pass `none` to disable the feature. | - | - | `0x4a0ed8ddf751a7cb5297f827699117b0f6d21a0b2907594d300dc9fed75c7e62` | v1.0.x+ |   ### REST API documentation -| Variable | Type| Description | Compulsoriness | Default value | Example value | -| --- | --- | --- | --- | --- | --- | -| NEXT_PUBLIC_API_SPEC_URL | `string` | Spec to be displayed on `/api-docs` page | Required | `https://raw.githubusercontent.com/blockscout/blockscout-api-v2-swagger/main/swagger.yaml` | `https://raw.githubusercontent.com/blockscout/blockscout-api-v2-swagger/main/swagger.yaml` | +This feature is **always enabled**, but you can disable it by passing `none` value to `NEXT_PUBLIC_API_SPEC_URL` variable. + +| Variable | Type| Description | Compulsoriness | Default value | Example value | Version | +| --- | --- | --- | --- | --- | --- | --- | +| NEXT_PUBLIC_API_SPEC_URL | `string` | Spec to be displayed on `/api-docs` page. Pass `none` to disable the feature. | - | `https://raw.githubusercontent.com/blockscout/blockscout-api-v2-swagger/main/swagger.yaml` | `https://raw.githubusercontent.com/blockscout/blockscout-api-v2-swagger/main/swagger.yaml` | v1.0.x+ |   ### Marketplace -| Variable | Type| Description | Compulsoriness | Default value | Example value | -| --- | --- | --- | --- | --- | --- | -| NEXT_PUBLIC_MARKETPLACE_CONFIG_URL | `string` | URL of configuration file (`.json` format only) which contains list of apps that will be shown on the marketplace page. See [below](#marketplace-app-configuration-properties) list of available properties for an app | Required | - | `https://example.com/marketplace_config.json` | -| NEXT_PUBLIC_MARKETPLACE_SUBMIT_FORM | `string` | Link to form where authors can submit their dapps to the marketplace | Required | - | `https://airtable.com/shrqUAcjgGJ4jU88C` | -| NEXT_PUBLIC_NETWORK_RPC_URL | `string` | See in [Blockchain parameters](ENVS.md#blockchain-parameters) section | Required | - | `https://core.poa.network` | -| NEXT_PUBLIC_MARKETPLACE_CATEGORIES_URL | `string` | URL of configuration file (`.json` format only) which contains the list of categories to be displayed on the markeplace page in the specified order. If no URL is provided, then the list of categories will be compiled based on the `categories` fields from the marketplace (apps) configuration file | - | - | `https://example.com/marketplace_categories.json` | +| Variable | Type| Description | Compulsoriness | Default value | Example value | Version | +| --- | --- | --- | --- | --- | --- | --- | +| NEXT_PUBLIC_MARKETPLACE_ENABLED | `boolean` | `true` means that the marketplace page will be enabled | Required | - | `true` | v1.24.1+ | +| NEXT_PUBLIC_MARKETPLACE_CONFIG_URL | `string` | URL of configuration file (`.json` format only) which contains list of apps that will be shown on the marketplace page. See [below](#marketplace-app-configuration-properties) list of available properties for an app. Can be replaced with NEXT_PUBLIC_ADMIN_SERVICE_API_HOST | Required | - | `https://example.com/marketplace_config.json` | v1.0.x+ | +| NEXT_PUBLIC_ADMIN_SERVICE_API_HOST | `string` | Admin Service API endpoint url. Can be used instead of NEXT_PUBLIC_MARKETPLACE_CONFIG_URL | - | - | `https://admin-rs.services.blockscout.com` | v1.1.0+ | +| NEXT_PUBLIC_MARKETPLACE_SUBMIT_FORM | `string` | Link to form where authors can submit their dapps to the marketplace | Required | - | `https://airtable.com/shrqUAcjgGJ4jU88C` | v1.0.x+ | +| NEXT_PUBLIC_MARKETPLACE_SUGGEST_IDEAS_FORM | `string` | Link to form where users can suggest ideas for the marketplace | - | - | `https://airtable.com/appiy5yijZpMMSKjT/pag3t82DUCyhGRZZO/form` | v1.24.0+ | +| NEXT_PUBLIC_NETWORK_RPC_URL | `string` | See in [Blockchain parameters](ENVS.md#blockchain-parameters) section | Required | - | `https://core.poa.network` | v1.0.x+ | +| NEXT_PUBLIC_MARKETPLACE_CATEGORIES_URL | `string` | URL of configuration file (`.json` format only) which contains the list of categories to be displayed on the marketplace page in the specified order. If no URL is provided, then the list of categories will be compiled based on the `categories` fields from the marketplace (apps) configuration file | - | - | `https://example.com/marketplace_categories.json` | v1.23.0+ | +| NEXT_PUBLIC_MARKETPLACE_SECURITY_REPORTS_URL | `string` | URL of configuration file (`.json` format only) which contains app security reports for displaying security scores on the Marketplace page | - | - | `https://example.com/marketplace_security_reports.json` | v1.28.0+ | +| NEXT_PUBLIC_MARKETPLACE_FEATURED_APP | `string` | ID of the featured application to be displayed on the banner on the Marketplace page | - | - | `uniswap` | v1.29.0+ | +| NEXT_PUBLIC_MARKETPLACE_BANNER_CONTENT_URL | `string` | URL of the banner HTML content | - | - | `https://example.com/banner` | v1.29.0+ | +| NEXT_PUBLIC_MARKETPLACE_BANNER_LINK_URL | `string` | URL of the page the banner leads to | - | - | `https://example.com` | v1.29.0+ | +| NEXT_PUBLIC_MARKETPLACE_RATING_AIRTABLE_API_KEY | `string` | Airtable API key | - | - | - | v1.33.0+ | +| NEXT_PUBLIC_MARKETPLACE_RATING_AIRTABLE_BASE_ID | `string` | Airtable base ID with dapp ratings | - | - | - | v1.33.0+ | +| NEXT_PUBLIC_MARKETPLACE_GRAPH_LINKS_URL | `string` | URL of the file (`.json` format only) which contains the list of The Graph links to be displayed on the Marketplace page | - | - | `https://example.com/graph_links.json` | v1.36.0+ | #### Marketplace app configuration properties -| Property | Type | Description | Compulsoriness | Example value +| Property | Type | Description | Compulsoriness | Example value | | --- | --- | --- | --- | --- | | id | `string` | Used as slug for the app. Must be unique in the app list. | Required | `'app'` | | external | `boolean` | `true` means that the application opens in a new window, but not in an iframe. | - | `true` | | title | `string` | Displayed title of the app. | Required | `'The App'` | | logo | `string` | URL to logo file. Should be at least 288x288. | Required | `'https://foo.app/icon.png'` | | shortDescription | `string` | Displayed only in the app list. | Required | `'Awesome app'` | -| categories | `Array` | Displayed category. Select one of the following below. | Required | `['security', 'tools']` | +| categories | `Array` | Displayed category. | Required | `['Security', 'Tools']` | | author | `string` | Displayed author of the app | Required | `'Bob'` | | url | `string` | URL of the app which will be launched in the iframe. | Required | `'https://foo.app/launch'` | | description | `string` | Displayed only in the modal dialog with additional info about the app. | Required | `'The best app'` | @@ -465,36 +556,23 @@ This feature is **always enabled**, but you can configure its behavior by passin | internalWallet | `boolean` | `true` means that the application can automatically connect to the Blockscout wallet. | - | `true` | | priority | `number` | The higher the priority, the higher the app will appear in the list on the Marketplace page. | - | `7` | -#### Marketplace categories ids - -For each application, you need to specify the `MarketplaceCategoryId` to which it belongs. Select one of the following: - -- `defi` -- `exchanges` -- `finance` -- `games` -- `marketplaces` -- `nft` -- `security` -- `social` -- `tools` -- `yieldFarming` -   ### Solidity to UML diagrams -| Variable | Type| Description | Compulsoriness | Default value | Example value | -| --- | --- | --- | --- | --- | --- | -| NEXT_PUBLIC_VISUALIZE_API_HOST | `string` | Visualize API endpoint url | Required | - | `https://visualizer.services.blockscout.com` | +| Variable | Type| Description | Compulsoriness | Default value | Example value | Version | +| --- | --- | --- | --- | --- | --- | --- | +| NEXT_PUBLIC_VISUALIZE_API_HOST | `string` | Visualize API endpoint url | Required | - | `https://visualizer.services.blockscout.com` | v1.0.x+ | +| NEXT_PUBLIC_VISUALIZE_API_BASE_PATH | `string` | Base path for Visualize API endpoint url | - | - | `/poa/core` | v1.29.0+ |   ### Blockchain statistics -| Variable | Type| Description | Compulsoriness | Default value | Example value | -| --- | --- | --- | --- | --- | --- | -| NEXT_PUBLIC_STATS_API_HOST | `string` | API endpoint url | Required | - | `https://stats.services.blockscout.com` | +| Variable | Type| Description | Compulsoriness | Default value | Example value | Version | +| --- | --- | --- | --- | --- | --- | --- | +| NEXT_PUBLIC_STATS_API_HOST | `string` | Stats API endpoint url | Required | - | `https://stats.services.blockscout.com` | v1.0.x+ | +| NEXT_PUBLIC_STATS_API_BASE_PATH | `string` | Base path for Stats API endpoint url | - | - | `/poa/core` | v1.29.0+ |   @@ -502,26 +580,26 @@ For each application, you need to specify the `MarketplaceCategoryId` to which i This feature is **enabled by default** with the `['metamask']` value. To switch it off pass `NEXT_PUBLIC_WEB3_WALLETS=none`. -| Variable | Type| Description | Compulsoriness | Default value | Example value | -| --- | --- | --- | --- | --- | --- | -| NEXT_PUBLIC_WEB3_WALLETS | `Array<'metamask' \| 'coinbase' \| 'token_pocket'>` | Array of Web3 wallets which will be used to add tokens or chain to. The first wallet which is enabled in user's browser will be shown. | - | `[ 'metamask' ]` | `[ 'coinbase' ]` | -| NEXT_PUBLIC_WEB3_DISABLE_ADD_TOKEN_TO_WALLET | `boolean`| Set to `true` to hide icon "Add to your wallet" next to token addresses | - | - | `true` | +| Variable | Type| Description | Compulsoriness | Default value | Example value | Version | +| --- | --- | --- | --- | --- | --- | --- | +| NEXT_PUBLIC_WEB3_WALLETS | `Array<'metamask' \| 'coinbase' \| 'token_pocket'>` | Array of Web3 wallets which will be used to add tokens or chain to. The first wallet which is enabled in user's browser will be shown. | - | `[ 'metamask' ]` | `[ 'coinbase' ]` | v1.10.0+ | +| NEXT_PUBLIC_WEB3_DISABLE_ADD_TOKEN_TO_WALLET | `boolean`| Set to `true` to hide icon "Add to your wallet" next to token addresses | - | - | `true` | v1.0.x+ |   ### Transaction interpretation -| Variable | Type| Description | Compulsoriness | Default value | Example value | -| --- | --- | --- | --- | --- | --- | -| NEXT_PUBLIC_TRANSACTION_INTERPRETATION_PROVIDER | `blockscout` \| `none` | Transaction interpretation provider that displays human readable transaction description | - | `none` | `blockscout` | +| Variable | Type| Description | Compulsoriness | Default value | Example value | Version | +| --- | --- | --- | --- | --- | --- | --- | +| NEXT_PUBLIC_TRANSACTION_INTERPRETATION_PROVIDER | `blockscout` \| `noves` \| `none` | Transaction interpretation provider that displays human readable transaction description | - | `none` | `blockscout` | v1.21.0+ |   ### Verified tokens info -| Variable | Type| Description | Compulsoriness | Default value | Example value | -| --- | --- | --- | --- | --- | --- | -| NEXT_PUBLIC_CONTRACT_INFO_API_HOST | `string` | Contract Info API endpoint url | Required | - | `https://contracts-info.services.blockscout.com` | +| Variable | Type| Description | Compulsoriness | Default value | Example value | Version | +| --- | --- | --- | --- | --- | --- | --- | +| NEXT_PUBLIC_CONTRACT_INFO_API_HOST | `string` | Contract Info API endpoint url | Required | - | `https://contracts-info.services.blockscout.com` | v1.0.x+ |   @@ -529,9 +607,41 @@ This feature is **enabled by default** with the `['metamask']` value. To switch This feature allows resolving blockchain addresses using human-readable domain names. -| Variable | Type| Description | Compulsoriness | Default value | Example value | -| --- | --- | --- | --- | --- | --- | -| NEXT_PUBLIC_NAME_SERVICE_API_HOST | `string` | Name Service API endpoint url | Required | - | `https://bens.services.blockscout.com` | +| Variable | Type| Description | Compulsoriness | Default value | Example value | Version | +| --- | --- | --- | --- | --- | --- | --- | +| NEXT_PUBLIC_NAME_SERVICE_API_HOST | `string` | Name Service API endpoint url | Required | - | `https://bens.services.blockscout.com` | v1.22.0+ | + +  + +### Metadata service integration + +This feature allows name tags and other public tags for addresses. + +| Variable | Type| Description | Compulsoriness | Default value | Example value | Version | +| --- | --- | --- | --- | --- | --- | --- | +| NEXT_PUBLIC_METADATA_SERVICE_API_HOST | `string` | Metadata Service API endpoint url | Required | - | `https://metadata.services.blockscout.com` | v1.30.0+ | + +  + +### Public tag submission + +This feature allows you to submit an application with a public address tag. + +| Variable | Type| Description | Compulsoriness | Default value | Example value | Version | +| --- | --- | --- | --- | --- | --- | --- | +| NEXT_PUBLIC_METADATA_SERVICE_API_HOST | `string` | Metadata Service API endpoint url | Required | - | `https://metadata.services.blockscout.com` | v1.30.0+ | +| NEXT_PUBLIC_ADMIN_SERVICE_API_HOST | `string` | Admin Service API endpoint url | Required | - | `https://admin-rs.services.blockscout.com` | v1.1.0+ | +| NEXT_PUBLIC_RE_CAPTCHA_APP_SITE_KEY | `string` | See [below](ENVS.md#google-recaptcha) | true | - | `` | v1.0.x+ | + +  + +### Data Availability + +This feature enables views related to blob transactions (EIP-4844), such as the Blob Txns tab on the Transactions page and the Blob details page. + +| Variable | Type| Description | Compulsoriness | Default value | Example value | Version | +| --- | --- | --- | --- | --- | --- | --- | +| NEXT_PUBLIC_DATA_AVAILABILITY_ENABLED | `boolean` | Set to true to enable blob transactions views. | Required | - | `true` | v1.28.0+ |   @@ -539,10 +649,10 @@ This feature allows resolving blockchain addresses using human-readable domain n This feature allows users to view tokens that have been bridged from other EVM chains. Additional tab "Bridged" will be added to the tokens page and the link to original token will be displayed on the token page. -| Variable | Type| Description | Compulsoriness | Default value | Example value | -| --- | --- | --- | --- | --- | --- | -| NEXT_PUBLIC_BRIDGED_TOKENS_CHAINS | `Array` where `BridgedTokenChain` can have following [properties](#bridged-token-chain-configuration-properties) | Used for displaying filter by the chain from which token where bridged. Also, used for creating links to original tokens in other explorers. | Required | - | `[{'id':'1','title':'Ethereum','short_title':'ETH','base_url':'https://eth.blockscout.com/token'}]` | -| NEXT_PUBLIC_BRIDGED_TOKENS_BRIDGES | `Array` where `TokenBridge` can have following [properties](#token-bridge-configuration-properties) | Used for displaying text about bridges types on the tokens page. | Required | - | `[{'type':'omni','title':'OmniBridge','short_title':'OMNI'}]` | +| Variable | Type| Description | Compulsoriness | Default value | Example value | Version | +| --- | --- | --- | --- | --- | --- | --- | +| NEXT_PUBLIC_BRIDGED_TOKENS_CHAINS | `Array` where `BridgedTokenChain` can have following [properties](#bridged-token-chain-configuration-properties) | Used for displaying filter by the chain from which token where bridged. Also, used for creating links to original tokens in other explorers. | Required | - | `[{'id':'1','title':'Ethereum','short_title':'ETH','base_url':'https://eth.blockscout.com/token'}]` | v1.14.0+ | +| NEXT_PUBLIC_BRIDGED_TOKENS_BRIDGES | `Array` where `TokenBridge` can have following [properties](#token-bridge-configuration-properties) | Used for displaying text about bridges types on the tokens page. | Required | - | `[{'type':'omni','title':'OmniBridge','short_title':'OMNI'}]` | v1.14.0+ | #### Bridged token chain configuration properties @@ -567,55 +677,217 @@ This feature allows users to view tokens that have been bridged from other EVM c ### Safe{Core} address tags -For the smart contract addresses which are [Safe{Core} accounts](https://safe.global/) public tag "Multisig: Safe" will be displayed in the address page header alongside to Safe logo. The Safe service is available only for certain networks, see full list [here](https://docs.safe.global/safe-core-api/available-services). Based on provided value of `NEXT_PUBLIC_NETWORK_ID`, the feature will be enabled or disabled. +For the smart contract addresses which are [Safe{Core} accounts](https://safe.global/) public tag "Multisig: Safe" will be displayed in the address page header alongside to Safe logo. + +| Variable | Type| Description | Compulsoriness | Default value | Example value | Version | +| --- | --- | --- | --- | --- | --- | --- | +| NEXT_PUBLIC_SAFE_TX_SERVICE_URL | `string` | The Safe transaction service URL. See full list of supported networks [here](https://docs.safe.global/api-supported-networks). | - | - | `uniswap` | v1.26.0+ |   -### SUAVE chain +### Address profile API -For blockchains that implement SUAVE architecture additional fields will be shown on the transaction page ("Allowed peekers", "Kettle"). Users also will be able to see the list of all transactions for a particular Kettle in the separate view. +This feature allows the integration of an external API to fetch user info for addresses or contracts. When configured, if the API returns a username, a public tag with a custom link will be displayed in the address page header. + +| Variable | Type| Description | Compulsoriness | Default value | Example value | Version | +| --- | --- | --- | --- | --- | --- | --- | +| NEXT_PUBLIC_ADDRESS_USERNAME_TAG | `{api_url: string; tag_link_template: string; tag_icon: string; tag_bg_color: string; tag_text_color: string}` | Address profile API tag configuration properties. See [below](#user-profile-api-configuration-properties). | - | - | `uniswap` | v1.35.0+ | + +  + +#### Address profile API configuration properties | Variable | Type| Description | Compulsoriness | Default value | Example value | | --- | --- | --- | --- | --- | --- | -| NEXT_PUBLIC_IS_SUAVE_CHAIN | `boolean` | Set to true for blockchains with [SUAVE architecture](https://writings.flashbots.net/mevm-suave-centauri-and-beyond) | Required | - | `true` | +| api_url_template | `string` | User profile API URL. Should be a template with `{address}` variable | Required | - | `https://example-api.com/{address}` | +| tag_link_template | `string` | External link to the profile. Should be a template with `{username}` variable | - | - | `https://example.com/{address}` | +| tag_icon | `string` | Public tag icon (.svg) url | - | - | `https://example.com/icon.svg` | +| tag_bg_color | `string` | Public tag background color (escape "#" symbol if you use HEX color codes or use rgba-value instead) | - | - | `\#000000` | +| tag_text_color | `string` | Public tag text color (escape "#" symbol if you use HEX color codes or use rgba-value instead) | - | - | `\#FFFFFF` | + +  + +### Address XStar XHS score + +This feature allows the integration of an XStar API to fetch XHS score for addresses. When configured, if the API returns a score, a public tag with that score will be displayed in the address page header. + +| Variable | Type| Description | Compulsoriness | Default value | Example value | Version | +| --- | --- | --- | --- | --- | --- | --- | +| NEXT_PUBLIC_XSTAR_SCORE_URL | `string` | XStar XHS score documentation URL for the address tag. Enables the XStar score feature. | - | - | `https://docs.xname.app/the-solution-adaptive-proof-of-humanity-on-blockchain/xhs-scoring-algorithm` | v1.36.0+ | + +  + +### SUAVE chain + +For blockchains that implement SUAVE architecture additional fields will be shown on the transaction page ("Allowed peekers", "Kettle"). Users also will be able to see the list of all transactions for a particular Kettle in the separate view. + +| Variable | Type| Description | Compulsoriness | Default value | Example value | Version | +| --- | --- | --- | --- | --- | --- | --- | +| NEXT_PUBLIC_IS_SUAVE_CHAIN | `boolean` | Set to true for blockchains with [SUAVE architecture](https://writings.flashbots.net/mevm-suave-centauri-and-beyond) | Required | - | `true` | v1.14.0+ | + +  + +### Celo chain + +For blockchains that use the Celo platform. _Note_, that once the Celo mainnet becomes an L2 chain, these variables will be migrated to the Rollup configuration section. + +| Variable | Type| Description | Compulsoriness | Default value | Example value | Version | +| --- | --- | --- | --- | --- | --- | --- | +| NEXT_PUBLIC_CELO_ENABLED | `boolean` | Indicates that it is a Celo-based chain. | - | - | `true` | v1.37.0+ | +| NEXT_PUBLIC_CELO_L2_UPGRADE_BLOCK | `number` | Indicates the block number when the Celo-type chain transitioned to L2. This is used to display links to the Epoch block page from a regular block page. | - | - | `26369280` | v1.37.0+ | + +  + +### MetaSuites extension + +Enables [MetaSuites browser extension](https://github.com/blocksecteam/metasuites) to integrate with the app views. + +| Variable | Type| Description | Compulsoriness | Default value | Example value | Version | +| --- | --- | --- | --- | --- | --- | --- | +| NEXT_PUBLIC_METASUITES_ENABLED | `boolean` | Set to true to enable integration | Required | - | `true` | v1.26.0+ | + +  + +### Validators list + +The feature enables the Validators page which provides detailed information about the validators of the PoS chains. + +| Variable | Type| Description | Compulsoriness | Default value | Example value | Version | +| --- | --- | --- | --- | --- | --- | --- | +| NEXT_PUBLIC_VALIDATORS_CHAIN_TYPE | `'stability' \| 'blackfort'` | Chain type | Required | - | `'stability'` | v1.25.0+ |   ### Sentry error monitoring -| Variable | Type| Description | Compulsoriness | Default value | Example value | -| --- | --- | --- | --- | --- | --- | -| NEXT_PUBLIC_SENTRY_DSN | `string` | Client key for your Sentry.io app | Required | - | `` | -| SENTRY_CSP_REPORT_URI | `string` | URL for sending CSP-reports to your Sentry.io app | - | - | `` | -| NEXT_PUBLIC_SENTRY_ENABLE_TRACING | `boolean` | Enables tracing and performance monitoring in Sentry.io | - | `false` | `true` | -| NEXT_PUBLIC_APP_ENV | `string` | App env (e.g development, review or production). Passed as `environment` property to Sentry config | - | `production` | `production` | -| NEXT_PUBLIC_APP_INSTANCE | `string` | Name of app instance. Used as custom tag `app_instance` value in the main Sentry scope. If not provided, it will be constructed from `NEXT_PUBLIC_APP_HOST` | - | - | `wonderful_kepler` | +_Note_ This feature is **deprecated**. All ENV variables will be removed in the future releases. + +| Variable | Type| Description | Compulsoriness | Default value | Example value | Version | +| --- | --- | --- | --- | --- | --- | --- | +| NEXT_PUBLIC_SENTRY_DSN | `string` | Client key for your Sentry.io app | Required | - | `` | v1.0.x+ | +| SENTRY_CSP_REPORT_URI | `string` | URL for sending CSP-reports to your Sentry.io app | - | - | `` | v1.0.x+ | +| NEXT_PUBLIC_SENTRY_ENABLE_TRACING | `boolean` | Enables tracing and performance monitoring in Sentry.io | - | `false` | `true` | v1.17.0+ | + +  + +### Rollbar error monitoring + +| Variable | Type| Description | Compulsoriness | Default value | Example value | Version | +| --- | --- | --- | --- | --- | --- | --- | +| NEXT_PUBLIC_ROLLBAR_CLIENT_TOKEN | `string` | Client token for your Rollbar project | Required | - | `` | v1.37.x+ |   ### OpenTelemetry -OpenTelemetry SDK for Node.js app could be enabled by passing `OTEL_SDK_ENABLED=true` variable. Configure the OpenTelemetry Protocol Exporter by using the generic environment variables described in the [OT docs](https://opentelemetry.io/docs/specs/otel/protocol/exporter/#configuration-options). +OpenTelemetry SDK for Node.js app could be enabled by passing `OTEL_SDK_ENABLED=true` variable. Configure the OpenTelemetry Protocol Exporter by using the generic environment variables described in the [OT docs](https://opentelemetry.io/docs/specs/otel/protocol/exporter/#configuration-options). Note that this Next.js feature is currently experimental. The Docker image should be built with the `NEXT_OPEN_TELEMETRY_ENABLED=true` argument to enable it. + +| Variable | Type| Description | Compulsoriness | Default value | Example value | Version | +| --- | --- | --- | --- | --- | --- | --- | +| OTEL_SDK_ENABLED | `boolean` | Run-time flag to enable the feature | Required | `false` | `true` | v1.18.0+ | + +  + +### DeFi dropdown + +If the feature is enabled, a single button or a dropdown (if more than 1 item is provided) will be displayed at the top of the explorer page, which will take a user to the specified application in the marketplace or to an external site. + +| Variable | Type| Description | Compulsoriness | Default value | Example value | Version | +| --- | --- | --- | --- | --- | --- | --- | +| NEXT_PUBLIC_DEFI_DROPDOWN_ITEMS | `[{ text: string; icon: string; dappId?: string, url?: string }]` | An array of dropdown items containing the button text, icon name and dappId in DAppscout or an external url | - | - | `[{'text':'Swap','icon':'swap','dappId':'uniswap'},{'text':'Payment link','icon':'payment_link','dappId':'peanut-protocol'}]` | v1.31.0+ | + +  + +### Multichain balance button + +If the feature is enabled, a Multichain balance button will be displayed on the address page, which will take you to the portfolio application in the marketplace or to an external site. + +| Variable | Type| Description | Compulsoriness | Default value | Example value | Version | +| --- | --- | --- | --- | --- | --- | --- | +| NEXT_PUBLIC_MULTICHAIN_BALANCE_PROVIDER_CONFIG | `[{ name: string; url_template: string; dapp_id?: string; logo: string }]` | Multichain portfolio application config See [below](#multichain-button-configuration-properties) | - | - | `[{ name: 'zerion', url_template: 'https://app.zerion.io/{address}/overview', logo: 'https://example.com/icon.svg'}]` | v1.31.0+ | + +  + +#### Multichain button configuration properties | Variable | Type| Description | Compulsoriness | Default value | Example value | | --- | --- | --- | --- | --- | --- | -| OTEL_SDK_ENABLED | `boolean` | Flag to enable the feature | Required | `false` | `true` | +| name | `string` | Multichain portfolio application name | Required | - | `zerion` | +| url_template | `string` | Url template to the portfolio. Should be a template with `{address}` variable | Required | - | `https://app.zerion.io/{address}/overview` | +| dapp_id | `string` | Set for open a Blockscout dapp page with the portfolio instead of opening external app page | - | - | `zerion` | +| logo | `string` | Multichain portfolio application logo (.svg) url | - | - | `https://example.com/icon.svg` |   -## External services configuration +### Get gas button -### Google ReCaptcha +If the feature is enabled, a Get gas button will be displayed in the top bar, which will take you to the gas refuel application in the marketplace or to an external site. -For obtaining the variables values please refer to [reCAPTCHA documentation](https://developers.google.com/recaptcha). +| Variable | Type| Description | Compulsoriness | Default value | Example value | Version | +| --- | --- | --- | --- | --- | --- | --- | +| NEXT_PUBLIC_GAS_REFUEL_PROVIDER_CONFIG | `{ name: string; url_template: string; dapp_id?: string; logo?: string }` | Get gas button config. See [below](#get-gas-button-configuration-properties) | - | - | `{ name: 'Need gas?', dapp_id: 'smol-refuel', url_template: 'https://smolrefuel.com/?outboundChain={chainId}', logo: 'https://example.com/icon.png' }` | v1.33.0+ | + +  + +#### Get gas button configuration properties | Variable | Type| Description | Compulsoriness | Default value | Example value | | --- | --- | --- | --- | --- | --- | -| NEXT_PUBLIC_RE_CAPTCHA_APP_SITE_KEY | `string` | Site key | - | - | `` | +| name | `string` | Text on the button | Required | - | `Need gas?` | +| url_template | `string` | Url template, may contain `{chainId}` variable | Required | - | `https://smolrefuel.com/?outboundChain={chainId}` | +| dapp_id | `string` | Set for open a Blockscout dapp page instead of opening external app page | - | - | `smol-refuel` | +| logo | `string` | Gas refuel application logo url | - | - | `https://example.com/icon.png` | +  + +### Save on gas with GasHawk + +The feature enables a "Save with GasHawk" button next to the "Gas used" value on the address page. + +| Variable | Type| Description | Compulsoriness | Default value | Example value | Version | +| --- | --- | --- | --- | --- | --- | --- | +| NEXT_PUBLIC_SAVE_ON_GAS_ENABLED | `boolean` | Set to "true" to enable the feature | - | - | `true` | v1.35.0+ | + +  + +### Rewards service API + +This feature enables Blockscout Merits program. It requires that the [My account](ENVS.md#my-account) and [Blockchain interaction](ENVS.md#blockchain-interaction-writing-to-contract-etc) features are also enabled. + +| Variable | Type| Description | Compulsoriness | Default value | Example value | Version | +| --- | --- | --- | --- | --- | --- | --- | +| NEXT_PUBLIC_REWARDS_SERVICE_API_HOST | `string` | API URL | - | - | `https://example.com` | v1.36.0+ | + +  + +### DEX pools + +| Variable | Type| Description | Compulsoriness | Default value | Example value | Version | +| --- | --- | --- | --- | --- | --- | --- | +| NEXT_PUBLIC_DEX_POOLS_ENABLED | `boolean` | Set to true to enable the feature | Required | - | `true` | v1.37.0+ | +| NEXT_PUBLIC_CONTRACT_INFO_API_HOST | `string` | Contract Info API endpoint url | Required | - | `https://contracts-info.services.blockscout.com` | v1.0.x+ | + +  + +### Badge claim link + +| Variable | Type| Description | Compulsoriness | Default value | Example value | Version | +| --- | --- | --- | --- | --- | --- | --- | +| NEXT_PUBLIC_GAME_BADGE_CLAIM_LINK | `string` | Provide to enable the easter egg badge feature | - | - | `https://example.com` | v1.37.0+ |   +## External services configuration + +### Google ReCaptcha + +For obtaining the variables values please refer to [reCAPTCHA documentation](https://developers.google.com/recaptcha). + +| Variable | Type| Description | Compulsoriness | Default value | Example value | Version | +| --- | --- | --- | --- | --- | --- | --- | +| NEXT_PUBLIC_RE_CAPTCHA_V3_APP_SITE_KEY | `string` | **DEPRECATED** Google reCAPTCHA v3 site key | - | - | `` | v1.36.0+ | +| NEXT_PUBLIC_RE_CAPTCHA_APP_SITE_KEY | `string` | Google reCAPTCHA v2 site key | - | - | `` | v1.0.x+ | + ## Bool data configuration ### Node And Provider Pages @@ -623,4 +895,4 @@ For obtaining the variables values please refer to [reCAPTCHA documentation](htt | Variable | Type| Description | Compulsoriness | Default value | Example value | | --- | --- | --- | --- | --- | --- | | NEXT_PUBLIC_BOOL_SCAN_API | `string` | The api of bool scan backend| - | - | - | -| NEXT_PUBLIC_BOOL_SCAN_RPC | `string` | The rpc of bool chain | - | - | - | +| NEXT_PUBLIC_BOOL_SCAN_RPC | `string` | The rpc of bool chain | - | - | - | \ No newline at end of file diff --git a/docs/PULL_REQUEST_TEMPLATE.md b/docs/PULL_REQUEST_TEMPLATE.md index a57bb9602c..d4378762bf 100644 --- a/docs/PULL_REQUEST_TEMPLATE.md +++ b/docs/PULL_REQUEST_TEMPLATE.md @@ -15,4 +15,7 @@ - [ ] I have tested these changes locally. - [ ] I added tests to cover any new functionality, following this [guide](./CONTRIBUTING.md#writing--running-tests) - [ ] Whenever I fix a bug, I include a regression test to ensure that the bug does not reappear silently. -- [ ] If I have added, changed, renamed, or removed an environment variable, I have updated the list of environment variables in the [documentation](ENVS.md) and made the necessary changes to the validator script according to the [guide](./CONTRIBUTING.md#adding-new-env-variable) +- [ ] If I have added, changed, renamed, or removed an environment variable + - I updated the list of environment variables in the [documentation](ENVS.md) + - I made the necessary changes to the validator script according to the [guide](./CONTRIBUTING.md#adding-new-env-variable) + - I added "ENVs" label to this pull request diff --git a/eslint.config.mjs b/eslint.config.mjs new file mode 100644 index 0000000000..9f7ae682b6 --- /dev/null +++ b/eslint.config.mjs @@ -0,0 +1,442 @@ +import { includeIgnoreFile } from '@eslint/compat'; +import jsPlugin from '@eslint/js'; +import nextJsPlugin from '@next/eslint-plugin-next'; +import stylisticPlugin from '@stylistic/eslint-plugin'; +import reactQueryPlugin from '@tanstack/eslint-plugin-query'; +import importPlugin from 'eslint-plugin-import'; +import importHelpersPlugin from 'eslint-plugin-import-helpers'; +import jestPlugin from 'eslint-plugin-jest'; +import jsxA11yPlugin from 'eslint-plugin-jsx-a11y'; +import noCyrillicStringPlugin from 'eslint-plugin-no-cyrillic-string'; +import playwrightPlugin from 'eslint-plugin-playwright'; +import reactPlugin from 'eslint-plugin-react'; +import reactHooksPlugin from 'eslint-plugin-react-hooks'; +import * as regexpPlugin from 'eslint-plugin-regexp'; +import globals from 'globals'; +import path from 'node:path'; +import { fileURLToPath } from 'node:url'; + +import tseslint from 'typescript-eslint'; + +const RESTRICTED_MODULES = { + paths: [ + { name: 'dayjs', message: 'Please use lib/date/dayjs.ts instead of directly importing dayjs' }, + { name: '@chakra-ui/icons', message: 'Using @chakra-ui/icons is prohibited. Please use regular svg-icon instead (see examples in "icons/" folder)' }, + { name: '@metamask/providers', message: 'Please lazy-load @metamask/providers or use useProvider hook instead' }, + { name: '@metamask/post-message-stream', message: 'Please lazy-load @metamask/post-message-stream or use useProvider hook instead' }, + { name: 'playwright/TestApp', message: 'Please use render() fixture from test() function of playwright/lib module' }, + { + name: '@chakra-ui/react', + importNames: [ 'Popover', 'Menu', 'PinInput', 'useToast' ], + message: 'Please use corresponding component or hook from ui/shared/chakra component instead', + }, + { + name: 'lodash', + message: 'Please use `import [package] from \'lodash/[package]\'` instead.', + }, + ], + patterns: [ + 'icons/*', + '!lodash/*', + ], +}; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = path.dirname(__filename); +const gitignorePath = path.resolve(__dirname, '.gitignore'); + +/** @type {import('eslint').Linter.Config[]} */ +export default tseslint.config( + includeIgnoreFile(gitignorePath), + + { files: [ '**/*.{js,mjs,cjs,ts,jsx,tsx}', '**/*.pw.tsx' ] }, + + { ignores: [ + 'deploy/tools/', + 'public/', + 'theme/dist/', + '.git/', + 'theme/webpack.config.js', + 'next.config.js', + ] }, + + { languageOptions: { globals: { ...globals.browser, ...globals.node } } }, + + { settings: { react: { version: 'detect' } } }, + + jsPlugin.configs.recommended, + + { + plugins: { + '@typescript-eslint': tseslint.plugin, + jest: jestPlugin, + }, + languageOptions: { + parser: tseslint.parser, + parserOptions: { + projectService: true, + }, + globals: jestPlugin.environments.globals.globals, + }, + rules: { + '@typescript-eslint/array-type': [ 'error', { + 'default': 'generic', + readonly: 'generic', + } ], + '@typescript-eslint/consistent-type-imports': [ 'error' ], + '@typescript-eslint/naming-convention': [ 'error', + { + selector: 'default', + format: [ 'camelCase' ], + leadingUnderscore: 'allow', + trailingUnderscore: 'forbid', + }, + { + selector: 'import', + leadingUnderscore: 'allow', + format: [ 'camelCase', 'PascalCase' ], + }, + { + selector: 'class', + format: [ 'PascalCase' ], + }, + { + selector: 'enum', + format: [ 'PascalCase', 'UPPER_CASE' ], + }, + { + selector: 'enumMember', + format: [ 'camelCase', 'PascalCase', 'UPPER_CASE' ], + }, + { + selector: 'function', + format: [ 'camelCase', 'PascalCase' ], + }, + { + selector: 'interface', + format: [ 'PascalCase' ], + }, + { + selector: 'method', + format: [ 'camelCase', 'snake_case', 'UPPER_CASE' ], + leadingUnderscore: 'allow', + }, + { + selector: 'parameter', + format: [ 'camelCase', 'PascalCase' ], + leadingUnderscore: 'allow', + }, + { + selector: 'property', + format: null, + }, + { + selector: 'typeAlias', + format: [ 'PascalCase' ], + }, + { + selector: 'typeLike', + format: [ 'PascalCase' ], + }, + { + selector: 'typeParameter', + format: [ 'PascalCase', 'UPPER_CASE' ], + }, + { + selector: 'variable', + format: [ 'camelCase', 'PascalCase', 'UPPER_CASE' ], + leadingUnderscore: 'allow', + }, + ], + '@typescript-eslint/no-empty-function': [ 'off' ], + '@typescript-eslint/no-unused-vars': [ 'error', { caughtErrors: 'none', ignoreRestSiblings: true } ], + '@typescript-eslint/no-use-before-define': 'off', + '@typescript-eslint/no-useless-constructor': [ 'error' ], + '@typescript-eslint/no-explicit-any': [ 'error', { ignoreRestArgs: true } ], + '@typescript-eslint/no-unused-expressions': [ 'error', { + allowShortCircuit: true, + allowTernary: true, + } ], + }, + }, + { + // disable type-aware linting on JS files + files: [ '**/*.{js,mjs}' ], + ...tseslint.configs.disableTypeChecked, + }, + + { + plugins: { + react: reactPlugin, + }, + rules: { + 'react/jsx-key': 'error', + 'react/jsx-no-bind': [ 'error', { + ignoreRefs: true, + } ], + 'react/jsx-curly-brace-presence': [ 'error', { + props: 'never', + children: 'never', + } ], + 'react/jsx-curly-spacing': [ 'error', { + when: 'always', + children: true, + spacing: { + objectLiterals: 'never', + }, + } ], + 'react/jsx-equals-spacing': [ 'error', 'never' ], + 'react/jsx-fragments': [ 'error', 'syntax' ], + 'react/jsx-no-duplicate-props': 'error', + 'react/jsx-no-target-blank': 'off', + 'react/jsx-no-useless-fragment': 'error', + 'react/jsx-tag-spacing': [ 'error', { + afterOpening: 'never', + beforeSelfClosing: 'never', + closingSlash: 'never', + } ], + 'react/jsx-wrap-multilines': [ 'error', { + declaration: 'parens-new-line', + assignment: 'parens-new-line', + 'return': 'parens-new-line', + arrow: 'parens-new-line', + condition: 'parens-new-line', + logical: 'parens-new-line', + prop: 'parens-new-line', + } ], + 'react/no-access-state-in-setstate': 'error', + 'react/no-deprecated': 'error', + 'react/no-direct-mutation-state': 'error', + 'react/no-find-dom-node': 'off', + 'react/no-redundant-should-component-update': 'error', + 'react/no-render-return-value': 'error', + 'react/no-string-refs': 'off', + 'react/no-unknown-property': 'error', + 'react/no-unused-state': 'error', + 'react/require-optimization': [ 'error' ], + 'react/void-dom-elements-no-children': 'error', + }, + }, + + { + plugins: { + '@next/next': nextJsPlugin, + }, + rules: { + ...nextJsPlugin.configs.recommended.rules, + ...nextJsPlugin.configs['core-web-vitals'].rules, + }, + }, + + { + plugins: { '@tanstack/query': reactQueryPlugin }, + }, + + { + ...playwrightPlugin.configs['flat/recommended'], + files: [ '**/*.pw.tsx' ], + rules: { + ...playwrightPlugin.configs['flat/recommended'].rules, + 'playwright/no-standalone-expect': 'off', // this rules does not work correctly with extended test functions + }, + }, + + { + plugins: { 'react-hooks': reactHooksPlugin }, + ignores: [ '**/*.pw.tsx', 'playwright/**' ], + rules: { + 'react-hooks/rules-of-hooks': 'error', + 'react-hooks/exhaustive-deps': 'error', + }, + }, + + { + files: [ '**/*.test.{ts,js,jsx,tsx}' ], + plugins: { jest: jestPlugin }, + languageOptions: { + globals: jestPlugin.environments.globals.globals, + }, + }, + + regexpPlugin.configs['flat/recommended'], + + { + plugins: { + 'import': importPlugin, + }, + rules: { + 'import/no-duplicates': 'error', + }, + }, + + { + plugins: { + 'import-helpers': importHelpersPlugin, + }, + rules: { + 'import-helpers/order-imports': [ + 'error', + { + newlinesBetween: 'always', + groups: [ + 'module', + '/types/', + [ '/^nextjs/' ], + [ + '/^configs/', + '/^data/', + '/^deploy/', + '/^icons/', + '/^jest/', + '/^lib/', + '/^mocks/', + '/^pages/', + '/^playwright/', + '/^stubs/', + '/^theme/', + '/^ui/', + ], + [ 'parent', 'sibling', 'index' ], + ], + alphabetize: { order: 'asc', ignoreCase: true }, + }, + ], + }, + }, + + { + plugins: { + 'no-cyrillic-string': noCyrillicStringPlugin, + }, + rules: { + 'no-cyrillic-string/no-cyrillic-string': 'error', + }, + }, + + { + plugins: { + 'jsx-a11y': jsxA11yPlugin, + }, + languageOptions: { parserOptions: { ecmaFeatures: { jsx: true } } }, + }, + + { + plugins: { + '@stylistic': stylisticPlugin, + }, + rules: { + // replacement for @typescript-eslint + '@stylistic/indent': [ 'error', 2 ], + '@stylistic/brace-style': [ 'error', '1tbs' ], + '@stylistic/member-delimiter-style': [ 'error' ], + '@stylistic/type-annotation-spacing': 'error', + + // replacement for eslint + '@stylistic/array-bracket-spacing': [ 'error', 'always' ], + '@stylistic/arrow-spacing': [ 'error', { before: true, after: true } ], + '@stylistic/comma-dangle': [ 'error', 'always-multiline' ], + '@stylistic/comma-spacing': [ 'error' ], + '@stylistic/comma-style': [ 'error', 'last' ], + '@stylistic/curly-newline': [ 'error', { multiline: true, minElements: 1 } ], + '@stylistic/eol-last': 'error', + '@stylistic/jsx-quotes': [ 'error', 'prefer-double' ], + '@stylistic/key-spacing': [ 'error', { + beforeColon: false, + afterColon: true, + } ], + '@stylistic/keyword-spacing': 'error', + '@stylistic/linebreak-style': [ 'error', 'unix' ], + '@stylistic/lines-around-comment': [ 'error', { + beforeBlockComment: true, + allowBlockStart: true, + } ], + '@stylistic/no-mixed-operators': [ 'error', { + groups: [ + [ '&&', '||' ], + ], + } ], + '@stylistic/no-mixed-spaces-and-tabs': 'error', + '@stylistic/no-multiple-empty-lines': [ 'error', { + max: 1, + maxEOF: 0, + maxBOF: 0, + } ], + '@stylistic/no-multi-spaces': 'error', + '@stylistic/no-trailing-spaces': 'error', + '@stylistic/object-curly-spacing': [ 'error', 'always' ], + '@stylistic/operator-linebreak': [ 'error', 'after' ], + '@stylistic/quote-props': [ 'error', 'as-needed', { + keywords: true, + numbers: true, + } ], + '@stylistic/quotes': [ 'error', 'single', { + allowTemplateLiterals: true, + } ], + '@stylistic/semi': [ 'error', 'always' ], + '@stylistic/space-before-function-paren': [ 'error', 'never' ], + '@stylistic/space-before-blocks': [ 'error', 'always' ], + '@stylistic/space-in-parens': [ 'error', 'never' ], + '@stylistic/space-infix-ops': 'error', + '@stylistic/space-unary-ops': 'off', + '@stylistic/template-curly-spacing': [ 'error', 'always' ], + '@stylistic/wrap-iife': [ 'error', 'inside' ], + }, + }, + + { + rules: { + // disabled in favor of @typescript-eslint and @stylistic + 'no-use-before-define': 'off', + 'no-useless-constructor': 'off', + 'no-unused-vars': 'off', + 'no-empty': [ 'error', { allowEmptyCatch: true } ], + 'no-unused-expressions': 'off', + + // this is checked by typescript compiler + 'no-redeclare': 'off', + + // rules customizations + eqeqeq: [ 'error', 'allow-null' ], + 'id-match': [ 'error', '^[\\w$]+$' ], + 'max-len': [ 'error', 160, 4 ], + 'no-console': 'error', + 'no-implicit-coercion': [ 'error', { + number: true, + 'boolean': true, + string: true, + } ], + 'no-nested-ternary': 'error', + 'no-multi-str': 'error', + 'no-spaced-func': 'error', + 'no-with': 'error', + 'object-shorthand': 'off', + 'one-var': [ 'error', 'never' ], + 'prefer-const': 'error', + + // restricted imports and properties + 'no-restricted-imports': [ 'error', RESTRICTED_MODULES ], + 'no-restricted-properties': [ 2, { + object: 'process', + property: 'env', + // FIXME: restrict the rule only NEXT_PUBLIC variables + message: 'Please use configs/app/index.ts to import any NEXT_PUBLIC environment variables. For other properties please disable this rule for a while.', + } ], + }, + }, + { + files: [ + 'pages/**', + 'nextjs/**', + 'playwright/**', + 'deploy/tools/**', + 'middleware.ts', + 'instrumentation*.ts', + '*.config.ts', + '*.config.js', + ], + rules: { + // for configs allow to consume env variables from process.env directly + 'no-restricted-properties': 'off', + }, + }, +); diff --git a/global.d.ts b/global.d.ts index 2955f3f872..94cfa4677d 100644 --- a/global.d.ts +++ b/global.d.ts @@ -1,14 +1,14 @@ -import type { WindowProvider } from 'wagmi'; +import type { WalletProvider } from 'types/web3'; type CPreferences = { zone: string; width: string; height: string; -} +}; declare global { export interface Window { - ethereum?: WindowProvider; + ethereum?: WalletProvider | undefined; coinzilla_display: Array; ga?: { getAll: () => Array<{ get: (prop: string) => string }>; @@ -27,3 +27,5 @@ declare global { } } } + +export {}; diff --git a/icons/API_slim.svg b/icons/API_slim.svg new file mode 100644 index 0000000000..f4b36f08ba --- /dev/null +++ b/icons/API_slim.svg @@ -0,0 +1,3 @@ + + + diff --git a/icons/MUD.svg b/icons/MUD.svg new file mode 100644 index 0000000000..8ab1229a71 --- /dev/null +++ b/icons/MUD.svg @@ -0,0 +1,4 @@ + + + + diff --git a/icons/MUD_menu.svg b/icons/MUD_menu.svg new file mode 100644 index 0000000000..c30c571c47 --- /dev/null +++ b/icons/MUD_menu.svg @@ -0,0 +1,3 @@ + + + diff --git a/icons/apps_list.svg b/icons/apps_list.svg new file mode 100644 index 0000000000..62cb5020d6 --- /dev/null +++ b/icons/apps_list.svg @@ -0,0 +1,3 @@ + + + diff --git a/icons/apps_slim.svg b/icons/apps_slim.svg new file mode 100644 index 0000000000..59e2f2d818 --- /dev/null +++ b/icons/apps_slim.svg @@ -0,0 +1,3 @@ + + + diff --git a/icons/arrows/up-head.svg b/icons/arrows/up-head.svg new file mode 100644 index 0000000000..375381a790 --- /dev/null +++ b/icons/arrows/up-head.svg @@ -0,0 +1,3 @@ + + + diff --git a/icons/beta.svg b/icons/beta.svg new file mode 100644 index 0000000000..bba1309f3a --- /dev/null +++ b/icons/beta.svg @@ -0,0 +1,4 @@ + + + + diff --git a/icons/beta_xs.svg b/icons/beta_xs.svg new file mode 100644 index 0000000000..a6dc48ee4e --- /dev/null +++ b/icons/beta_xs.svg @@ -0,0 +1,4 @@ + + + + diff --git a/icons/blob.svg b/icons/blob.svg new file mode 100644 index 0000000000..9b40d72ecb --- /dev/null +++ b/icons/blob.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/icons/blobs/image.svg b/icons/blobs/image.svg new file mode 100644 index 0000000000..be08dd269c --- /dev/null +++ b/icons/blobs/image.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/icons/blobs/raw.svg b/icons/blobs/raw.svg new file mode 100644 index 0000000000..8a97401ff5 --- /dev/null +++ b/icons/blobs/raw.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/icons/blobs/text.svg b/icons/blobs/text.svg new file mode 100644 index 0000000000..08ec8801bf --- /dev/null +++ b/icons/blobs/text.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/icons/block_countdown.svg b/icons/block_countdown.svg new file mode 100644 index 0000000000..0024e52ce9 --- /dev/null +++ b/icons/block_countdown.svg @@ -0,0 +1,4 @@ + + + + diff --git a/icons/brands/blockscout.svg b/icons/brands/blockscout.svg new file mode 100644 index 0000000000..0e3279de01 --- /dev/null +++ b/icons/brands/blockscout.svg @@ -0,0 +1,3 @@ + + + diff --git a/icons/brands/celenium.svg b/icons/brands/celenium.svg new file mode 100644 index 0000000000..8a5d645ac2 --- /dev/null +++ b/icons/brands/celenium.svg @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/icons/brands/graph.svg b/icons/brands/graph.svg new file mode 100644 index 0000000000..bd3cc916d9 --- /dev/null +++ b/icons/brands/graph.svg @@ -0,0 +1,4 @@ + + + + diff --git a/icons/brands/safe.svg b/icons/brands/safe.svg index 9e596a3821..8369513837 100644 --- a/icons/brands/safe.svg +++ b/icons/brands/safe.svg @@ -1,3 +1,3 @@ - - + + diff --git a/icons/brands/solidity_scan.svg b/icons/brands/solidity_scan.svg new file mode 100644 index 0000000000..ac5747c69a --- /dev/null +++ b/icons/brands/solidity_scan.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/icons/verified_token.svg b/icons/certified.svg similarity index 100% rename from icons/verified_token.svg rename to icons/certified.svg diff --git a/icons/checkered_flag.svg b/icons/checkered_flag.svg new file mode 100644 index 0000000000..918c29cea4 --- /dev/null +++ b/icons/checkered_flag.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/icons/clock-light.svg b/icons/clock-light.svg index a41614753a..9b18072460 100644 --- a/icons/clock-light.svg +++ b/icons/clock-light.svg @@ -1,11 +1,3 @@ - - - - - - - - - - + + diff --git a/icons/clock.svg b/icons/clock.svg index f40b6ee05d..14a8c94053 100644 --- a/icons/clock.svg +++ b/icons/clock.svg @@ -1,3 +1,3 @@ - - + + diff --git a/icons/columns.svg b/icons/columns.svg new file mode 100644 index 0000000000..d4bfc97368 --- /dev/null +++ b/icons/columns.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/icons/contracts/proxy.svg b/icons/contracts/proxy.svg new file mode 100644 index 0000000000..1b75cb210f --- /dev/null +++ b/icons/contracts/proxy.svg @@ -0,0 +1,4 @@ + + + + diff --git a/icons/contract.svg b/icons/contracts/regular.svg similarity index 100% rename from icons/contract.svg rename to icons/contracts/regular.svg diff --git a/icons/contracts/regular_many.svg b/icons/contracts/regular_many.svg new file mode 100644 index 0000000000..1f0b62afd2 --- /dev/null +++ b/icons/contracts/regular_many.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/icons/contract_verified.svg b/icons/contracts/verified.svg similarity index 100% rename from icons/contract_verified.svg rename to icons/contracts/verified.svg diff --git a/icons/contracts/verified_many.svg b/icons/contracts/verified_many.svg new file mode 100644 index 0000000000..2a004f596d --- /dev/null +++ b/icons/contracts/verified_many.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/icons/dex-tracker.svg b/icons/dex-tracker.svg new file mode 100644 index 0000000000..64deb3aed4 --- /dev/null +++ b/icons/dex-tracker.svg @@ -0,0 +1,4 @@ + + + + diff --git a/icons/discussions.svg b/icons/discussions.svg deleted file mode 100644 index 47ba3acd98..0000000000 --- a/icons/discussions.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/icons/email-sent.svg b/icons/email-sent.svg deleted file mode 100644 index d31e30f244..0000000000 --- a/icons/email-sent.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/icons/empty_search_result.svg b/icons/empty_search_result.svg index 1e49771d0a..f4d62eff0e 100644 --- a/icons/empty_search_result.svg +++ b/icons/empty_search_result.svg @@ -1,11 +1,16 @@ - - - + + + + + + - - - + + + + + diff --git a/icons/games.svg b/icons/games.svg new file mode 100644 index 0000000000..39c01ca56d --- /dev/null +++ b/icons/games.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/icons/gas.svg b/icons/gas.svg index 902b8cb3ea..4334fe93f5 100644 --- a/icons/gas.svg +++ b/icons/gas.svg @@ -1,3 +1,10 @@ - - + + + + + + + + + diff --git a/icons/gas_xl.svg b/icons/gas_xl.svg new file mode 100644 index 0000000000..5a3913ac16 --- /dev/null +++ b/icons/gas_xl.svg @@ -0,0 +1,3 @@ + + + diff --git a/icons/gear_slim.svg b/icons/gear_slim.svg new file mode 100644 index 0000000000..abc14e6a78 --- /dev/null +++ b/icons/gear_slim.svg @@ -0,0 +1,3 @@ + + + diff --git a/icons/heart_filled.svg b/icons/heart_filled.svg new file mode 100644 index 0000000000..80926b1668 --- /dev/null +++ b/icons/heart_filled.svg @@ -0,0 +1,3 @@ + + + diff --git a/icons/heart_outline.svg b/icons/heart_outline.svg new file mode 100644 index 0000000000..8bf7ce3e36 --- /dev/null +++ b/icons/heart_outline.svg @@ -0,0 +1,4 @@ + + + + diff --git a/icons/hourglass.svg b/icons/hourglass.svg new file mode 100644 index 0000000000..7ebd6d78b2 --- /dev/null +++ b/icons/hourglass.svg @@ -0,0 +1,3 @@ + + + diff --git a/icons/lightning.svg b/icons/lightning.svg index 91b1ae92ca..03fea73d75 100644 --- a/icons/lightning.svg +++ b/icons/lightning.svg @@ -1,3 +1,3 @@ - - + + diff --git a/icons/lightning_navbar.svg b/icons/lightning_navbar.svg new file mode 100644 index 0000000000..9587a9c7a2 --- /dev/null +++ b/icons/lightning_navbar.svg @@ -0,0 +1,4 @@ + + + + diff --git a/icons/link_external.svg b/icons/link_external.svg new file mode 100644 index 0000000000..dbddf710bc --- /dev/null +++ b/icons/link_external.svg @@ -0,0 +1,3 @@ + + + diff --git a/icons/merits.svg b/icons/merits.svg new file mode 100644 index 0000000000..91b128a2b9 --- /dev/null +++ b/icons/merits.svg @@ -0,0 +1,4 @@ + + + + diff --git a/icons/merits_colored.svg b/icons/merits_colored.svg new file mode 100644 index 0000000000..4006f7e43a --- /dev/null +++ b/icons/merits_colored.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/icons/merits_slim.svg b/icons/merits_slim.svg new file mode 100644 index 0000000000..8a0623a169 --- /dev/null +++ b/icons/merits_slim.svg @@ -0,0 +1,4 @@ + + + + diff --git a/icons/merits_with_dot.svg b/icons/merits_with_dot.svg new file mode 100644 index 0000000000..e216115e4e --- /dev/null +++ b/icons/merits_with_dot.svg @@ -0,0 +1,4 @@ + + + + diff --git a/icons/merits_with_dot_slim.svg b/icons/merits_with_dot_slim.svg new file mode 100644 index 0000000000..4b5bf8a0aa --- /dev/null +++ b/icons/merits_with_dot_slim.svg @@ -0,0 +1,4 @@ + + + + diff --git a/icons/open-link.svg b/icons/open-link.svg new file mode 100644 index 0000000000..d0fcc28ab4 --- /dev/null +++ b/icons/open-link.svg @@ -0,0 +1,3 @@ + + + diff --git a/icons/payment_link.svg b/icons/payment_link.svg new file mode 100644 index 0000000000..f97128fff6 --- /dev/null +++ b/icons/payment_link.svg @@ -0,0 +1,4 @@ + + + + diff --git a/icons/private_tags_slim.svg b/icons/private_tags_slim.svg new file mode 100644 index 0000000000..538c47d054 --- /dev/null +++ b/icons/private_tags_slim.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/icons/refresh.svg b/icons/refresh.svg new file mode 100644 index 0000000000..fef0346a50 --- /dev/null +++ b/icons/refresh.svg @@ -0,0 +1,3 @@ + + + diff --git a/icons/repeat_arrow.svg b/icons/repeat.svg similarity index 100% rename from icons/repeat_arrow.svg rename to icons/repeat.svg diff --git a/icons/rocket_xl.svg b/icons/rocket_xl.svg new file mode 100644 index 0000000000..8b3f4ccdbf --- /dev/null +++ b/icons/rocket_xl.svg @@ -0,0 +1,3 @@ + + + diff --git a/icons/share.svg b/icons/share.svg new file mode 100644 index 0000000000..f1124e47d0 --- /dev/null +++ b/icons/share.svg @@ -0,0 +1,3 @@ + + + diff --git a/icons/sign_out.svg b/icons/sign_out.svg new file mode 100644 index 0000000000..d5b68ecdab --- /dev/null +++ b/icons/sign_out.svg @@ -0,0 +1,4 @@ + + + + diff --git a/icons/social/canny.svg b/icons/social/canny.svg deleted file mode 100644 index 8041cd2bf3..0000000000 --- a/icons/social/canny.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/icons/social/tweet.svg b/icons/social/tweet.svg deleted file mode 100644 index 20cc63ccc6..0000000000 --- a/icons/social/tweet.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/icons/social/twitter.svg b/icons/social/twitter.svg new file mode 100644 index 0000000000..21e9812ff7 --- /dev/null +++ b/icons/social/twitter.svg @@ -0,0 +1,3 @@ + + + diff --git a/icons/social/twitter_filled.svg b/icons/social/twitter_filled.svg index 5fc356a969..0d73b850a0 100644 --- a/icons/social/twitter_filled.svg +++ b/icons/social/twitter_filled.svg @@ -1,3 +1,3 @@ - - + + diff --git a/icons/star_filled.svg b/icons/star_filled.svg index 10a3cfb0c8..7b6312c876 100644 --- a/icons/star_filled.svg +++ b/icons/star_filled.svg @@ -1,3 +1,3 @@ - - + + diff --git a/icons/star_outline.svg b/icons/star_outline.svg index e6fd05339c..05286fa1d5 100644 --- a/icons/star_outline.svg +++ b/icons/star_outline.svg @@ -1,3 +1,3 @@ - - + + diff --git a/icons/status/pending.svg b/icons/status/pending.svg index a8c19c187b..f9e5a88d53 100644 --- a/icons/status/pending.svg +++ b/icons/status/pending.svg @@ -1,4 +1,5 @@ - - - + + + + diff --git a/icons/swap.svg b/icons/swap.svg new file mode 100644 index 0000000000..c1566be5fc --- /dev/null +++ b/icons/swap.svg @@ -0,0 +1,3 @@ + + + diff --git a/icons/token-transfers.svg b/icons/token-transfers.svg new file mode 100644 index 0000000000..f3bef44d80 --- /dev/null +++ b/icons/token-transfers.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/icons/validator.svg b/icons/validator.svg new file mode 100644 index 0000000000..e77bb0ba5d --- /dev/null +++ b/icons/validator.svg @@ -0,0 +1,3 @@ + + + diff --git a/icons/finalized.svg b/icons/verification-steps/finalized.svg similarity index 100% rename from icons/finalized.svg rename to icons/verification-steps/finalized.svg diff --git a/icons/unfinalized.svg b/icons/verification-steps/unfinalized.svg similarity index 100% rename from icons/unfinalized.svg rename to icons/verification-steps/unfinalized.svg diff --git a/icons/verified_slim.svg b/icons/verified_slim.svg new file mode 100644 index 0000000000..a13930aab2 --- /dev/null +++ b/icons/verified_slim.svg @@ -0,0 +1,4 @@ + + + + diff --git a/icons/verify-contract.svg b/icons/verify-contract.svg deleted file mode 100644 index 5457c4f160..0000000000 --- a/icons/verify-contract.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/icons/wallet.svg b/icons/wallet.svg index 4dea2adff2..f1765c246d 100644 --- a/icons/wallet.svg +++ b/icons/wallet.svg @@ -1,11 +1,4 @@ - - - - - - - - - - + + + diff --git a/instrumentation.node.ts b/instrumentation.node.ts index bbb183dd00..a42232f4b8 100644 --- a/instrumentation.node.ts +++ b/instrumentation.node.ts @@ -10,7 +10,7 @@ import { } from '@opentelemetry/sdk-metrics'; import { NodeSDK } from '@opentelemetry/sdk-node'; import { SimpleSpanProcessor } from '@opentelemetry/sdk-trace-node'; -import { SemanticResourceAttributes } from '@opentelemetry/semantic-conventions'; +import { SEMRESATTRS_SERVICE_NAME, SEMRESATTRS_SERVICE_VERSION, SEMRESATTRS_SERVICE_INSTANCE_ID } from '@opentelemetry/semantic-conventions'; diag.setLogger(new DiagConsoleLogger(), DiagLogLevel.INFO); @@ -18,9 +18,9 @@ const traceExporter = new OTLPTraceExporter(); const sdk = new NodeSDK({ resource: new Resource({ - [SemanticResourceAttributes.SERVICE_NAME]: 'blockscout_frontend', - [SemanticResourceAttributes.SERVICE_VERSION]: process.env.NEXT_PUBLIC_GIT_TAG || process.env.NEXT_PUBLIC_GIT_COMMIT_SHA || 'unknown_version', - [SemanticResourceAttributes.SERVICE_INSTANCE_ID]: + [SEMRESATTRS_SERVICE_NAME]: 'blockscout_frontend', + [SEMRESATTRS_SERVICE_VERSION]: process.env.NEXT_PUBLIC_GIT_TAG || process.env.NEXT_PUBLIC_GIT_COMMIT_SHA || 'unknown_version', + [SEMRESATTRS_SERVICE_INSTANCE_ID]: process.env.NEXT_PUBLIC_APP_INSTANCE || process.env.NEXT_PUBLIC_APP_HOST?.replace('.blockscout.com', '').replaceAll('-', '_') || 'unknown_app', @@ -46,9 +46,7 @@ const sdk = new NodeSDK({ url.pathname.startsWith('/_next/static/') || url.pathname.startsWith('/_next/data/') || url.pathname.startsWith('/assets/') || - url.pathname.startsWith('/static/') || - url.pathname.startsWith('/favicon/') || - url.pathname.startsWith('/envs.js') + url.pathname.startsWith('/static/') ) { return true; } diff --git a/instrumentation.ts b/instrumentation.ts index c5384e36a7..dc366667cb 100644 --- a/instrumentation.ts +++ b/instrumentation.ts @@ -1,5 +1,5 @@ export async function register() { - if (process.env.NEXT_RUNTIME === 'nodejs') { + if (process.env.NEXT_RUNTIME === 'nodejs' && process.env.NEXT_OPEN_TELEMETRY_ENABLED === 'true') { await import('./instrumentation.node'); } } diff --git a/jest.config.ts b/jest.config.ts index d87d585507..d718d56a7b 100644 --- a/jest.config.ts +++ b/jest.config.ts @@ -1,4 +1,3 @@ -/* eslint-disable max-len */ import type { JestConfigWithTsJest } from 'ts-jest'; /* diff --git a/jest/lib.tsx b/jest/lib.tsx index 7097e2b19a..cfbed98138 100644 --- a/jest/lib.tsx +++ b/jest/lib.tsx @@ -8,22 +8,19 @@ import React from 'react'; import { AppContextProvider } from 'lib/contexts/app'; import { ScrollDirectionProvider } from 'lib/contexts/scrollDirection'; import { SocketProvider } from 'lib/socket/context'; -import theme from 'theme'; +import theme from 'theme/theme'; import 'lib/setLocale'; const PAGE_PROPS = { cookies: '', referrer: '', - id: '', - height_or_hash: '', - hash: '', - number: '', - q: '', - name: '', + query: {}, + adBannerProvider: null, + apiData: null, }; -const TestApp = ({ children }: {children: React.ReactNode}) => { +const TestApp = ({ children }: { children: React.ReactNode }) => { const [ queryClient ] = React.useState(() => new QueryClient({ defaultOptions: { queries: { diff --git a/lib/address/bech32.ts b/lib/address/bech32.ts new file mode 100644 index 0000000000..85fcfda3ed --- /dev/null +++ b/lib/address/bech32.ts @@ -0,0 +1,49 @@ +import { bech32 } from '@scure/base'; + +import config from 'configs/app'; +import bytesToHex from 'lib/bytesToHex'; +import hexToBytes from 'lib/hexToBytes'; + +export const DATA_PART_REGEXP = /^[\da-z]{38}$/; +export const BECH_32_SEPARATOR = '1'; // https://github.com/bitcoin/bips/blob/master/bip-0173.mediawiki#bech32 + +export function toBech32Address(hash: string) { + if (config.UI.views.address.hashFormat.bech32Prefix) { + try { + const words = bech32.toWords(hexToBytes(hash)); + return bech32.encode(config.UI.views.address.hashFormat.bech32Prefix, words); + } catch (error) {} + } + + return hash; +} + +export function isBech32Address(hash: string) { + if (!config.UI.views.address.hashFormat.bech32Prefix) { + return false; + } + + if (!hash.startsWith(`${ config.UI.views.address.hashFormat.bech32Prefix }${ BECH_32_SEPARATOR }`)) { + return false; + } + + const strippedHash = hash.replace(`${ config.UI.views.address.hashFormat.bech32Prefix }${ BECH_32_SEPARATOR }`, ''); + return DATA_PART_REGEXP.test(strippedHash); +} + +export function fromBech32Address(hash: string) { + if (config.UI.views.address.hashFormat.bech32Prefix) { + try { + const { words, prefix } = bech32.decode(hash as `${ string }${ typeof BECH_32_SEPARATOR }${ string }`); + + if (prefix !== config.UI.views.address.hashFormat.bech32Prefix) { + return hash; + } + + const bytes = bech32.fromWords(words); + return bytesToHex(bytes); + } catch (error) {} + } + + return hash; +} diff --git a/lib/address/getCheckedSummedAddress.ts b/lib/address/getCheckedSummedAddress.ts new file mode 100644 index 0000000000..6cf744620d --- /dev/null +++ b/lib/address/getCheckedSummedAddress.ts @@ -0,0 +1,9 @@ +import { getAddress } from 'viem'; + +export default function getCheckedSummedAddress(address: string): string { + try { + return getAddress(address); + } catch (error) { + return address; + } +} diff --git a/lib/address/parseMetaPayload.ts b/lib/address/parseMetaPayload.ts new file mode 100644 index 0000000000..f8d6fbc570 --- /dev/null +++ b/lib/address/parseMetaPayload.ts @@ -0,0 +1,46 @@ +import type { AddressMetadataTag } from 'types/api/addressMetadata'; +import type { AddressMetadataTagFormatted } from 'types/client/addressMetadata'; + +type MetaParsed = NonNullable; + +export default function parseMetaPayload(meta: AddressMetadataTag['meta']): AddressMetadataTagFormatted['meta'] { + try { + const parsedMeta = JSON.parse(meta || ''); + + if (typeof parsedMeta !== 'object' || parsedMeta === null || Array.isArray(parsedMeta)) { + throw new Error('Invalid JSON'); + } + + const result: AddressMetadataTagFormatted['meta'] = {}; + + const stringFields: Array = [ + 'textColor', + 'bgColor', + 'tagIcon', + 'tagUrl', + 'tooltipIcon', + 'tooltipTitle', + 'tooltipDescription', + 'tooltipUrl', + 'appID', + 'appMarketplaceURL', + 'appLogoURL', + 'appActionButtonText', + 'warpcastHandle', + 'data', + 'alertBgColor', + 'alertTextColor', + 'alertStatus', + ]; + + for (const stringField of stringFields) { + if (stringField in parsedMeta && typeof parsedMeta[stringField as keyof typeof parsedMeta] === 'string') { + result[stringField] = parsedMeta[stringField as keyof typeof parsedMeta]; + } + } + + return result; + } catch (error) { + return null; + } +} diff --git a/lib/address/useAddressMetadataInfoQuery.ts b/lib/address/useAddressMetadataInfoQuery.ts new file mode 100644 index 0000000000..14c22ab676 --- /dev/null +++ b/lib/address/useAddressMetadataInfoQuery.ts @@ -0,0 +1,35 @@ +import type { AddressMetadataInfoFormatted, AddressMetadataTagFormatted } from 'types/client/addressMetadata'; + +import config from 'configs/app'; +import useApiQuery from 'lib/api/useApiQuery'; + +import parseMetaPayload from './parseMetaPayload'; + +export default function useAddressMetadataInfoQuery(addresses: Array, isEnabled = true) { + + const resource = 'address_metadata_info'; + + return useApiQuery(resource, { + queryParams: { + addresses, + chainId: config.chain.id, + tagsLimit: '20', + }, + queryOptions: { + enabled: isEnabled && addresses.length > 0 && config.features.addressMetadata.isEnabled, + select: (data) => { + const addresses = Object.entries(data.addresses) + .map(([ address, { tags, reputation } ]) => { + const formattedTags: Array = tags.map((tag) => ({ ...tag, meta: parseMetaPayload(tag.meta) })); + return [ address.toLowerCase(), { tags: formattedTags, reputation } ] as const; + }) + .reduce((result, item) => { + result[item[0]] = item[1]; + return result; + }, {} as AddressMetadataInfoFormatted['addresses']); + + return { addresses }; + }, + }, + }); +} diff --git a/lib/api/buildUrl.test.ts b/lib/api/buildUrl.test.ts new file mode 100644 index 0000000000..29ea283a95 --- /dev/null +++ b/lib/api/buildUrl.test.ts @@ -0,0 +1,43 @@ +import buildUrl from './buildUrl'; + +test('builds URL for resource without path params', () => { + const url = buildUrl('config_backend_version'); + expect(url).toBe('https://localhost:3003/api/v2/config/backend-version'); +}); + +test('builds URL for resource with path params', () => { + const url = buildUrl('block', { height_or_hash: '42' }); + expect(url).toBe('https://localhost:3003/api/v2/blocks/42'); +}); + +describe('falsy query parameters', () => { + test('leaves "false" as query parameter', () => { + const url = buildUrl('block', { height_or_hash: '42' }, { includeTx: false }); + expect(url).toBe('https://localhost:3003/api/v2/blocks/42?includeTx=false'); + }); + + test('leaves "null" as query parameter', () => { + const url = buildUrl('block', { height_or_hash: '42' }, { includeTx: null }); + expect(url).toBe('https://localhost:3003/api/v2/blocks/42?includeTx=null'); + }); + + test('strips out empty string as query parameter', () => { + const url = buildUrl('block', { height_or_hash: '42' }, { includeTx: null, sort: '' }); + expect(url).toBe('https://localhost:3003/api/v2/blocks/42?includeTx=null'); + }); + + test('strips out "undefined" as query parameter', () => { + const url = buildUrl('block', { height_or_hash: '42' }, { includeTx: null, sort: undefined }); + expect(url).toBe('https://localhost:3003/api/v2/blocks/42?includeTx=null'); + }); +}); + +test('builds URL with array-like query parameters', () => { + const url = buildUrl('block', { height_or_hash: '42' }, { includeTx: [ '0x11', '0x22' ], sort: 'asc' }); + expect(url).toBe('https://localhost:3003/api/v2/blocks/42?includeTx=0x11%2C0x22&sort=asc'); +}); + +test('builds URL for resource with custom API endpoint', () => { + const url = buildUrl('token_verified_info', { chainId: '42', hash: '0x11' }); + expect(url).toBe('https://localhost:3005/api/v1/chains/42/token-infos/0x11'); +}); diff --git a/lib/api/buildUrl.ts b/lib/api/buildUrl.ts index b99fb26855..ea7e9fac3f 100644 --- a/lib/api/buildUrl.ts +++ b/lib/api/buildUrl.ts @@ -4,47 +4,24 @@ import config from 'configs/app'; import isNeedProxy from './isNeedProxy'; import { RESOURCES } from './resources'; -import type { - ApiResource, - ResourceName, - ResourcePathParams, -} from './resources'; +import type { ApiResource, ResourceName, ResourcePathParams } from './resources'; export default function buildUrl( resourceName: R, pathParams?: ResourcePathParams, - queryParams?: Record< - string, - string | Array | number | boolean | null | undefined - >, + queryParams?: Record | number | boolean | null | undefined>, + noProxy?: boolean, ): string { const resource: ApiResource = RESOURCES[resourceName]; - const baseUrl = - resource.baseUrl ?? - (isNeedProxy() ? - config.app.baseUrl : - resource.endpoint || config.api.endpoint); - const basePath = - resource.basePath !== undefined ? resource.basePath : config.api.basePath; - let path; - - if (resource.baseUrl) { - path = basePath + resource.path; - } else if (isNeedProxy()) { - path = '/node-api/proxy' + basePath + resource.path; - } else { - path = basePath + resource.path; - } - + const baseUrl = !noProxy && isNeedProxy() ? config.app.baseUrl : (resource.endpoint || config.api.endpoint); + const basePath = resource.basePath !== undefined ? resource.basePath : config.api.basePath; + const path = !noProxy && isNeedProxy() ? '/node-api/proxy' + basePath + resource.path : basePath + resource.path; const url = new URL(compile(path)(pathParams), baseUrl); - queryParams && - Object.entries(queryParams).forEach(([ key, value ]) => { - // there are some pagination params that can be null or false for the next page - value !== undefined && - value !== '' && - url.searchParams.append(key, String(value)); - }); + queryParams && Object.entries(queryParams).forEach(([ key, value ]) => { + // there are some pagination params that can be null or false for the next page + value !== undefined && value !== '' && url.searchParams.append(key, String(value)); + }); return url.toString(); } diff --git a/lib/api/resources.ts b/lib/api/resources.ts index 9723b751a3..b99d04846f 100644 --- a/lib/api/resources.ts +++ b/lib/api/resources.ts @@ -1,8 +1,10 @@ -import { getFeaturePayload } from "configs/app/features/types"; +import type * as bens from '@blockscout/bens-types'; +import type * as stats from '@blockscout/stats-types'; +import type * as visualizer from '@blockscout/visualizer-types'; +import { getFeaturePayload } from 'configs/app/features/types'; import type { UserInfo, CustomAbis, - PublicTags, ApiKeys, VerifiedAddressResponse, TokenInfoApplicationConfig, @@ -10,7 +12,7 @@ import type { WatchlistResponse, TransactionTagsResponse, AddressTagsResponse, -} from "types/api/account"; +} from 'types/api/account'; import type { Address, AddressCounters, @@ -29,78 +31,92 @@ import type { AddressNFTsResponse, AddressCollectionsResponse, AddressNFTTokensFilter, -} from "types/api/address"; -import type { AddressesResponse } from "types/api/addresses"; + AddressMudTables, + AddressMudTablesFilter, + AddressMudRecords, + AddressMudRecordsFilter, + AddressMudRecordsSorting, + AddressMudRecord, + AddressEpochRewardsResponse, + AddressXStarResponse, +} from 'types/api/address'; +import type { AddressesResponse, AddressesMetadataSearchResult, AddressesMetadataSearchFilters } from 'types/api/addresses'; +import type { AddressMetadataInfo, PublicTagTypesResponse } from 'types/api/addressMetadata'; +import type { AdvancedFilterParams, AdvancedFilterResponse, AdvancedFilterMethodsResponse } from 'types/api/advancedFilter'; +import type { + ArbitrumL2MessagesResponse, + ArbitrumL2TxnBatch, + ArbitrumL2TxnBatchesResponse, + ArbitrumL2BatchTxs, + ArbitrumL2BatchBlocks, + ArbitrumL2TxnBatchesItem, + ArbitrumLatestDepositsResponse, +} from 'types/api/arbitrumL2'; +import type { TxBlobs, Blob } from 'types/api/blobs'; import type { BlocksResponse, BlockTransactionsResponse, Block, BlockFilters, BlockWithdrawalsResponse, -} from "types/api/block"; -import type { - DeviceStatisticInfos, - DHCDevice, - DHCDeviceInfo, - DHCDevicePage, - DHCDevicesParams, - EpochInfo, - EraInfo, - NodesPage, - NodesParams, - PageParams, - ProviderDetails, - ProviderInfo, - ProvidersPage, - ValidatorDetails, - ValidatorStatisticInfos, -} from "types/api/boolscan"; -import type { - ChartMarketResponse, - ChartTransactionResponse, -} from "types/api/charts"; -import type { BackendVersionConfig } from "types/api/configs"; + BlockCountdownResponse, + BlockEpoch, + BlockEpochElectionRewardDetailsResponse, +} from 'types/api/block'; +import type { ChartMarketResponse, ChartSecondaryCoinPriceResponse, ChartTransactionResponse } from 'types/api/charts'; +import type { BackendVersionConfig, CsvExportConfig } from 'types/api/configs'; import type { SmartContract, - SmartContractReadMethod, - SmartContractWriteMethod, - SmartContractVerificationConfig, - SolidityscanReport, -} from "types/api/contract"; -import type { - VerifiedContractsResponse, - VerifiedContractsFilters, - VerifiedContractsCounters, -} from "types/api/contracts"; + SmartContractVerificationConfigRaw, + SmartContractSecurityAudits, + SmartContractMudSystemsResponse, + SmartContractMudSystemInfo, +} from 'types/api/contract'; +import type { VerifiedContractsResponse, VerifiedContractsFilters, VerifiedContractsCounters } from 'types/api/contracts'; import type { EnsAddressLookupFilters, - EnsAddressLookupResponse, - EnsDomainDetailed, - EnsDomainEventsResponse, EnsDomainLookupFilters, - EnsDomainLookupResponse, EnsLookupSorting, -} from "types/api/ens"; -import type { IndexingStatus } from "types/api/indexingStatus"; -import type { InternalTransactionsResponse } from "types/api/internalTransaction"; -import type { L2DepositsResponse, L2DepositsItem } from "types/api/l2Deposits"; -import type { L2OutputRootsResponse } from "types/api/l2OutputRoots"; -import type { L2TxnBatchesResponse } from "types/api/l2TxnBatches"; -import type { L2WithdrawalsResponse } from "types/api/l2Withdrawals"; -import type { LogsResponseTx, LogsResponseAddress } from "types/api/log"; -import type { RawTracesResponse } from "types/api/rawTrace"; +} from 'types/api/ens'; +import type { IndexingStatus } from 'types/api/indexingStatus'; +import type { InternalTransactionsResponse } from 'types/api/internalTransaction'; +import type { LogsResponseTx, LogsResponseAddress } from 'types/api/log'; +import type { MudWorldsResponse } from 'types/api/mudWorlds'; +import type { NovesAccountHistoryResponse, NovesDescribeTxsResponse, NovesResponseData } from 'types/api/noves'; import type { - SearchRedirectResult, - SearchResult, - SearchResultFilters, - SearchResultItem, -} from "types/api/search"; + OptimisticL2DepositsResponse, + OptimisticL2DepositsItem, + OptimisticL2OutputRootsResponse, + OptimisticL2TxnBatchesResponse, + OptimisticL2WithdrawalsResponse, + OptimisticL2DisputeGamesResponse, + OptimismL2TxnBatch, + OptimismL2BatchTxs, + OptimismL2BatchBlocks, +} from 'types/api/optimisticL2'; +import type { Pool, PoolsResponse } from 'types/api/pools'; +import type { RawTracesResponse } from 'types/api/rawTrace'; import type { - Counters, - StatsCharts, - StatsChart, - HomeStats, -} from "types/api/stats"; + RewardsConfigResponse, + RewardsCheckRefCodeResponse, + RewardsNonceResponse, + RewardsCheckUserResponse, + RewardsLoginResponse, + RewardsUserBalancesResponse, + RewardsUserDailyCheckResponse, + RewardsUserDailyClaimResponse, + RewardsUserReferralsResponse, +} from 'types/api/rewards'; +import type { + ScrollL2BatchesResponse, + ScrollL2TxnBatch, + ScrollL2TxnBatchTxs, + ScrollL2TxnBatchBlocks, + ScrollL2MessagesResponse, +} from 'types/api/scrollL2'; +import type { SearchRedirectResult, SearchResult, SearchResultFilters, SearchResultItem } from 'types/api/search'; +import type { ShibariumWithdrawalsResponse, ShibariumDepositsResponse } from 'types/api/shibarium'; +import type { HomeStats } from 'types/api/stats'; import type { TokenCounters, TokenInfo, @@ -110,49 +126,49 @@ import type { TokenInstanceTransfersCount, TokenVerifiedInfo, TokenInventoryFilters, -} from "types/api/token"; -import type { - TokensResponse, - TokensFilters, - TokensSorting, - TokenInstanceTransferResponse, - TokensBridgedFilters, -} from "types/api/tokens"; -import type { - TokenTransferResponse, - TokenTransferFilters, -} from "types/api/tokenTransfer"; +} from 'types/api/token'; +import type { TokensResponse, TokensFilters, TokensSorting, TokenInstanceTransferResponse, TokensBridgedFilters } from 'types/api/tokens'; +import type { TokenTransferResponse, TokenTransferFilters } from 'types/api/tokenTransfer'; import type { TransactionsResponseValidated, TransactionsResponsePending, Transaction, TransactionsResponseWatchlist, TransactionsSorting, -} from "types/api/transaction"; -import type { TxInterpretationResponse } from "types/api/txInterpretation"; -import type { TTxsFilters } from "types/api/txsFilters"; -import type { TxStateChanges } from "types/api/txStateChanges"; + TransactionsResponseWithBlobs, + TransactionsStats, +} from 'types/api/transaction'; +import type { TxInterpretationResponse } from 'types/api/txInterpretation'; +import type { TTxsFilters, TTxsWithBlobsFilters } from 'types/api/txsFilters'; +import type { TxStateChanges } from 'types/api/txStateChanges'; +import type { UserOpsResponse, UserOp, UserOpsFilters, UserOpsAccount } from 'types/api/userOps'; import type { - UserOpsResponse, - UserOp, - UserOpsFilters, - UserOpsAccount, -} from "types/api/userOps"; -import type { VerifiedContractsSorting } from "types/api/verifiedContracts"; -import type { VisualizedContract } from "types/api/visualization"; -import type { - WithdrawalsResponse, - WithdrawalsCounters, -} from "types/api/withdrawals"; + ValidatorsStabilityCountersResponse, + ValidatorsStabilityFilters, + ValidatorsStabilityResponse, + ValidatorsStabilitySorting, + ValidatorsBlackfortCountersResponse, + ValidatorsBlackfortResponse, + ValidatorsBlackfortSorting, +} from 'types/api/validators'; +import type { VerifiedContractsSorting } from 'types/api/verifiedContracts'; +import type { WithdrawalsResponse, WithdrawalsCounters } from 'types/api/withdrawals'; import type { + ZkEvmL2DepositsResponse, ZkEvmL2TxnBatch, ZkEvmL2TxnBatchesItem, ZkEvmL2TxnBatchesResponse, ZkEvmL2TxnBatchTxs, -} from "types/api/zkEvmL2TxnBatches"; -import type { ArrayElement } from "types/utils"; + ZkEvmL2WithdrawalsResponse, +} from 'types/api/zkEvmL2'; +import type { ZkSyncBatch, ZkSyncBatchesResponse, ZkSyncBatchTxs } from 'types/api/zkSyncL2'; +import type { MarketplaceAppOverview } from 'types/client/marketplace'; +import type { ArrayElement } from 'types/utils'; + +import config from 'configs/app'; -import config from "configs/app"; +const marketplaceFeature = getFeaturePayload(config.features.marketplace); +const marketplaceApi = marketplaceFeature && 'api' in marketplaceFeature ? marketplaceFeature.api : undefined; export interface ApiResource { path: ResourcePath; @@ -160,1036 +176,1315 @@ export interface ApiResource { basePath?: string; pathParams?: Array; needAuth?: boolean; // for external APIs which require authentication - baseUrl?: string; - filterFields?: Array; + headers?: RequestInit['headers']; } -export const SORTING_FIELDS = [ "sort", "order" ]; +export const SORTING_FIELDS = [ 'sort', 'order' ]; export const RESOURCES = { // ACCOUNT csrf: { - path: "/api/account/v2/get_csrf", + path: '/api/account/v2/get_csrf', }, user_info: { - path: "/api/account/v2/user/info", - }, - email_resend: { - path: "/api/account/v2/email/resend", + path: '/api/account/v2/user/info', }, custom_abi: { - path: "/api/account/v2/user/custom_abis/:id?", - pathParams: [ "id" as const ], + path: '/api/account/v2/user/custom_abis{/:id}', + pathParams: [ 'id' as const ], }, watchlist: { - path: "/api/account/v2/user/watchlist/:id?", - pathParams: [ "id" as const ], - filterFields: [], - }, - public_tags: { - path: "/api/account/v2/user/public_tags/:id?", - pathParams: [ "id" as const ], + path: '/api/account/v2/user/watchlist{/:id}', + pathParams: [ 'id' as const ], + filterFields: [ ], }, private_tags_address: { - path: "/api/account/v2/user/tags/address/:id?", - pathParams: [ "id" as const ], - filterFields: [], + path: '/api/account/v2/user/tags/address{/:id}', + pathParams: [ 'id' as const ], + filterFields: [ ], }, private_tags_tx: { - path: "/api/account/v2/user/tags/transaction/:id?", - pathParams: [ "id" as const ], - filterFields: [], + path: '/api/account/v2/user/tags/transaction{/:id}', + pathParams: [ 'id' as const ], + filterFields: [ ], }, api_keys: { - path: "/api/account/v2/user/api_keys/:id?", - pathParams: [ "id" as const ], + path: '/api/account/v2/user/api_keys{/:id}', + pathParams: [ 'id' as const ], }, // ACCOUNT: ADDRESS VERIFICATION & TOKEN INFO address_verification: { - path: "/api/v1/chains/:chainId/verified-addresses:type", - pathParams: [ "chainId" as const, "type" as const ], + path: '/api/v1/chains/:chainId/verified-addresses:type', + pathParams: [ 'chainId' as const, 'type' as const ], endpoint: getFeaturePayload(config.features.verifiedTokens)?.api.endpoint, basePath: getFeaturePayload(config.features.verifiedTokens)?.api.basePath, needAuth: true, }, verified_addresses: { - path: "/api/v1/chains/:chainId/verified-addresses", - pathParams: [ "chainId" as const ], + path: '/api/v1/chains/:chainId/verified-addresses', + pathParams: [ 'chainId' as const ], endpoint: getFeaturePayload(config.features.verifiedTokens)?.api.endpoint, basePath: getFeaturePayload(config.features.verifiedTokens)?.api.basePath, needAuth: true, }, token_info_applications_config: { - path: "/api/v1/chains/:chainId/token-info-submissions/selectors", - pathParams: [ "chainId" as const ], - endpoint: getFeaturePayload(config.features.addressVerification)?.api - .endpoint, - basePath: getFeaturePayload(config.features.addressVerification)?.api - .basePath, + path: '/api/v1/chains/:chainId/token-info-submissions/selectors', + pathParams: [ 'chainId' as const ], + endpoint: getFeaturePayload(config.features.addressVerification)?.api.endpoint, + basePath: getFeaturePayload(config.features.addressVerification)?.api.basePath, needAuth: true, }, token_info_applications: { - path: "/api/v1/chains/:chainId/token-info-submissions/:id?", - pathParams: [ "chainId" as const, "id" as const ], - endpoint: getFeaturePayload(config.features.addressVerification)?.api - .endpoint, - basePath: getFeaturePayload(config.features.addressVerification)?.api - .basePath, + path: '/api/v1/chains/:chainId/token-info-submissions{/:id}', + pathParams: [ 'chainId' as const, 'id' as const ], + endpoint: getFeaturePayload(config.features.addressVerification)?.api.endpoint, + basePath: getFeaturePayload(config.features.addressVerification)?.api.basePath, needAuth: true, }, - // STATS + // AUTH + auth_send_otp: { + path: '/api/account/v2/send_otp', + }, + auth_confirm_otp: { + path: '/api/account/v2/confirm_otp', + }, + auth_siwe_message: { + path: '/api/account/v2/siwe_message', + }, + auth_siwe_verify: { + path: '/api/account/v2/authenticate_via_wallet', + }, + auth_link_email: { + path: '/api/account/v2/email/link', + }, + auth_link_address: { + path: '/api/account/v2/address/link', + }, + auth_logout: { + path: '/api/account/auth/logout', + }, + + // STATS MICROSERVICE API stats_counters: { - path: "/api/v1/counters", + path: '/api/v1/counters', endpoint: getFeaturePayload(config.features.stats)?.api.endpoint, basePath: getFeaturePayload(config.features.stats)?.api.basePath, }, stats_lines: { - path: "/api/v1/lines", + path: '/api/v1/lines', endpoint: getFeaturePayload(config.features.stats)?.api.endpoint, basePath: getFeaturePayload(config.features.stats)?.api.basePath, }, stats_line: { - path: "/api/v1/lines/:id", - pathParams: [ "id" as const ], + path: '/api/v1/lines/:id', + pathParams: [ 'id' as const ], endpoint: getFeaturePayload(config.features.stats)?.api.endpoint, basePath: getFeaturePayload(config.features.stats)?.api.basePath, }, // NAME SERVICE addresses_lookup: { - path: "/api/v1/:chainId/addresses\\:lookup", - pathParams: [ "chainId" as const ], + path: '/api/v1/:chainId/addresses\\:lookup', + pathParams: [ 'chainId' as const ], + endpoint: getFeaturePayload(config.features.nameService)?.api.endpoint, + basePath: getFeaturePayload(config.features.nameService)?.api.basePath, + filterFields: [ 'address' as const, 'resolved_to' as const, 'owned_by' as const, 'only_active' as const, 'protocols' as const ], + }, + address_domain: { + path: '/api/v1/:chainId/addresses/:address', + pathParams: [ 'chainId' as const, 'address' as const ], endpoint: getFeaturePayload(config.features.nameService)?.api.endpoint, basePath: getFeaturePayload(config.features.nameService)?.api.basePath, - filterFields: [ - "address" as const, - "resolved_to" as const, - "owned_by" as const, - "only_active" as const, - ], }, domain_info: { - path: "/api/v1/:chainId/domains/:name", - pathParams: [ "chainId" as const, "name" as const ], + path: '/api/v1/:chainId/domains/:name', + pathParams: [ 'chainId' as const, 'name' as const ], endpoint: getFeaturePayload(config.features.nameService)?.api.endpoint, basePath: getFeaturePayload(config.features.nameService)?.api.basePath, }, domain_events: { - path: "/api/v1/:chainId/domains/:name/events", - pathParams: [ "chainId" as const, "name" as const ], + path: '/api/v1/:chainId/domains/:name/events', + pathParams: [ 'chainId' as const, 'name' as const ], endpoint: getFeaturePayload(config.features.nameService)?.api.endpoint, basePath: getFeaturePayload(config.features.nameService)?.api.basePath, }, domains_lookup: { - path: "/api/v1/:chainId/domains\\:lookup", - pathParams: [ "chainId" as const ], + path: '/api/v1/:chainId/domains\\:lookup', + pathParams: [ 'chainId' as const ], endpoint: getFeaturePayload(config.features.nameService)?.api.endpoint, basePath: getFeaturePayload(config.features.nameService)?.api.basePath, - filterFields: [ "name" as const, "only_active" as const ], + filterFields: [ 'name' as const, 'only_active' as const, 'protocols' as const ], + }, + domain_protocols: { + path: '/api/v1/:chainId/protocols', + pathParams: [ 'chainId' as const ], + endpoint: getFeaturePayload(config.features.nameService)?.api.endpoint, + basePath: getFeaturePayload(config.features.nameService)?.api.basePath, + }, + + // METADATA SERVICE & PUBLIC TAGS + address_metadata_info: { + path: '/api/v1/metadata', + endpoint: getFeaturePayload(config.features.addressMetadata)?.api.endpoint, + basePath: getFeaturePayload(config.features.addressMetadata)?.api.basePath, + }, + address_metadata_tag_search: { + path: '/api/v1/tags:search', + endpoint: getFeaturePayload(config.features.addressMetadata)?.api.endpoint, + basePath: getFeaturePayload(config.features.addressMetadata)?.api.basePath, + }, + address_metadata_tag_types: { + path: '/api/v1/public-tag-types', + endpoint: getFeaturePayload(config.features.addressMetadata)?.api.endpoint, + basePath: getFeaturePayload(config.features.addressMetadata)?.api.basePath, + }, + public_tag_application: { + path: '/api/v1/chains/:chainId/metadata-submissions/tag', + pathParams: [ 'chainId' as const ], + endpoint: getFeaturePayload(config.features.publicTagsSubmission)?.api.endpoint, + basePath: getFeaturePayload(config.features.publicTagsSubmission)?.api.basePath, }, // VISUALIZATION visualize_sol2uml: { - path: "/api/v1/solidity\\:visualize-contracts", + path: '/api/v1/solidity\\:visualize-contracts', endpoint: getFeaturePayload(config.features.sol2uml)?.api.endpoint, basePath: getFeaturePayload(config.features.sol2uml)?.api.basePath, }, + // MARKETPLACE + marketplace_dapps: { + path: '/api/v1/chains/:chainId/marketplace/dapps', + pathParams: [ 'chainId' as const ], + endpoint: marketplaceApi?.endpoint, + basePath: marketplaceApi?.basePath, + }, + marketplace_dapp: { + path: '/api/v1/chains/:chainId/marketplace/dapps/:dappId', + pathParams: [ 'chainId' as const, 'dappId' as const ], + endpoint: marketplaceApi?.endpoint, + basePath: marketplaceApi?.basePath, + }, + + // REWARDS SERVICE + rewards_config: { + path: '/api/v1/config', + endpoint: getFeaturePayload(config.features.rewards)?.api.endpoint, + basePath: getFeaturePayload(config.features.rewards)?.api.basePath, + }, + rewards_check_ref_code: { + path: '/api/v1/auth/code/:code', + pathParams: [ 'code' as const ], + endpoint: getFeaturePayload(config.features.rewards)?.api.endpoint, + basePath: getFeaturePayload(config.features.rewards)?.api.basePath, + }, + rewards_nonce: { + path: '/api/v1/auth/nonce', + endpoint: getFeaturePayload(config.features.rewards)?.api.endpoint, + basePath: getFeaturePayload(config.features.rewards)?.api.basePath, + }, + rewards_check_user: { + path: '/api/v1/auth/user/:address', + pathParams: [ 'address' as const ], + endpoint: getFeaturePayload(config.features.rewards)?.api.endpoint, + basePath: getFeaturePayload(config.features.rewards)?.api.basePath, + }, + rewards_login: { + path: '/api/v1/auth/login', + endpoint: getFeaturePayload(config.features.rewards)?.api.endpoint, + basePath: getFeaturePayload(config.features.rewards)?.api.basePath, + }, + rewards_logout: { + path: '/api/v1/auth/logout', + endpoint: getFeaturePayload(config.features.rewards)?.api.endpoint, + basePath: getFeaturePayload(config.features.rewards)?.api.basePath, + }, + rewards_user_balances: { + path: '/api/v1/user/balances', + endpoint: getFeaturePayload(config.features.rewards)?.api.endpoint, + basePath: getFeaturePayload(config.features.rewards)?.api.basePath, + }, + rewards_user_daily_check: { + path: '/api/v1/user/daily/check', + endpoint: getFeaturePayload(config.features.rewards)?.api.endpoint, + basePath: getFeaturePayload(config.features.rewards)?.api.basePath, + }, + rewards_user_daily_claim: { + path: '/api/v1/user/daily/claim', + endpoint: getFeaturePayload(config.features.rewards)?.api.endpoint, + basePath: getFeaturePayload(config.features.rewards)?.api.basePath, + }, + rewards_user_referrals: { + path: '/api/v1/user/referrals', + endpoint: getFeaturePayload(config.features.rewards)?.api.endpoint, + basePath: getFeaturePayload(config.features.rewards)?.api.basePath, + }, + // BLOCKS, TXS blocks: { - path: "/api/v2/blocks", - filterFields: [ "type" as const ], + path: '/api/v2/blocks', + filterFields: [ 'type' as const ], }, block: { - path: "/api/v2/blocks/:height_or_hash", - pathParams: [ "height_or_hash" as const ], + path: '/api/v2/blocks/:height_or_hash', + pathParams: [ 'height_or_hash' as const ], }, block_txs: { - path: "/api/v2/blocks/:height_or_hash/transactions", - pathParams: [ "height_or_hash" as const ], - filterFields: [], + path: '/api/v2/blocks/:height_or_hash/transactions', + pathParams: [ 'height_or_hash' as const ], + filterFields: [ 'type' as const ], }, block_withdrawals: { - path: "/api/v2/blocks/:height_or_hash/withdrawals", - pathParams: [ "height_or_hash" as const ], + path: '/api/v2/blocks/:height_or_hash/withdrawals', + pathParams: [ 'height_or_hash' as const ], + filterFields: [], + }, + block_epoch: { + path: '/api/v2/blocks/:height_or_hash/epoch', + pathParams: [ 'height_or_hash' as const ], filterFields: [], }, + block_election_rewards: { + path: '/api/v2/blocks/:height_or_hash/election-rewards/:reward_type', + pathParams: [ 'height_or_hash' as const, 'reward_type' as const ], + filterFields: [], + }, + txs_stats: { + path: '/api/v2/transactions/stats', + }, txs_validated: { - path: "/api/v2/transactions", - filterFields: [ "filter" as const, "type" as const, "method" as const ], + path: '/api/v2/transactions', + filterFields: [ 'filter' as const, 'type' as const, 'method' as const ], }, txs_pending: { - path: "/api/v2/transactions", - filterFields: [ "filter" as const, "type" as const, "method" as const ], + path: '/api/v2/transactions', + filterFields: [ 'filter' as const, 'type' as const, 'method' as const ], + }, + txs_with_blobs: { + path: '/api/v2/transactions', + filterFields: [ 'type' as const ], }, txs_watchlist: { - path: "/api/v2/transactions/watchlist", - filterFields: [], + path: '/api/v2/transactions/watchlist', + filterFields: [ ], }, txs_execution_node: { - path: "/api/v2/transactions/execution-node/:hash", - pathParams: [ "hash" as const ], - filterFields: [], + path: '/api/v2/transactions/execution-node/:hash', + pathParams: [ 'hash' as const ], + filterFields: [ ], }, tx: { - path: "/api/v2/transactions/:hash", - pathParams: [ "hash" as const ], + path: '/api/v2/transactions/:hash', + pathParams: [ 'hash' as const ], }, tx_internal_txs: { - path: "/api/v2/transactions/:hash/internal-transactions", - pathParams: [ "hash" as const ], - filterFields: [], + path: '/api/v2/transactions/:hash/internal-transactions', + pathParams: [ 'hash' as const ], + filterFields: [ ], }, tx_logs: { - path: "/api/v2/transactions/:hash/logs", - pathParams: [ "hash" as const ], - filterFields: [], + path: '/api/v2/transactions/:hash/logs', + pathParams: [ 'hash' as const ], + filterFields: [ ], }, tx_token_transfers: { - path: "/api/v2/transactions/:hash/token-transfers", - pathParams: [ "hash" as const ], - filterFields: [ "type" as const ], + path: '/api/v2/transactions/:hash/token-transfers', + pathParams: [ 'hash' as const ], + filterFields: [ 'type' as const ], }, tx_raw_trace: { - path: "/api/v2/transactions/:hash/raw-trace", - pathParams: [ "hash" as const ], + path: '/api/v2/transactions/:hash/raw-trace', + pathParams: [ 'hash' as const ], }, tx_state_changes: { - path: "/api/v2/transactions/:hash/state-changes", - pathParams: [ "hash" as const ], + path: '/api/v2/transactions/:hash/state-changes', + pathParams: [ 'hash' as const ], filterFields: [], }, + tx_blobs: { + path: '/api/v2/transactions/:hash/blobs', + pathParams: [ 'hash' as const ], + }, tx_interpretation: { - path: "/api/v2/transactions/:hash/summary", - pathParams: [ "hash" as const ], + path: '/api/v2/transactions/:hash/summary', + pathParams: [ 'hash' as const ], }, withdrawals: { - path: "/api/v2/withdrawals", + path: '/api/v2/withdrawals', filterFields: [], }, withdrawals_counters: { - path: "/api/v2/withdrawals/counters", + path: '/api/v2/withdrawals/counters', }, // ADDRESSES addresses: { - path: "/api/v2/addresses/", - filterFields: [], + path: '/api/v2/addresses/', + filterFields: [ ], + }, + addresses_metadata_search: { + path: '/api/v2/proxy/metadata/addresses', + filterFields: [ 'slug' as const, 'tag_type' as const ], }, // ADDRESS address: { - path: "/api/v2/addresses/:hash", - pathParams: [ "hash" as const ], + path: '/api/v2/addresses/:hash', + pathParams: [ 'hash' as const ], }, address_counters: { - path: "/api/v2/addresses/:hash/counters", - pathParams: [ "hash" as const ], + path: '/api/v2/addresses/:hash/counters', + pathParams: [ 'hash' as const ], }, address_tabs_counters: { - path: "/api/v2/addresses/:hash/tabs-counters", - pathParams: [ "hash" as const ], + path: '/api/v2/addresses/:hash/tabs-counters', + pathParams: [ 'hash' as const ], }, // this resource doesn't have pagination, so causing huge problems on some addresses page // address_token_balances: { // path: '/api/v2/addresses/:hash/token-balances', // }, address_txs: { - path: "/api/v2/addresses/:hash/transactions", - pathParams: [ "hash" as const ], - filterFields: [ "filter" as const ], + path: '/api/v2/addresses/:hash/transactions', + pathParams: [ 'hash' as const ], + filterFields: [ 'filter' as const ], }, address_internal_txs: { - path: "/api/v2/addresses/:hash/internal-transactions", - pathParams: [ "hash" as const ], - filterFields: [ "filter" as const ], + path: '/api/v2/addresses/:hash/internal-transactions', + pathParams: [ 'hash' as const ], + filterFields: [ 'filter' as const ], }, address_token_transfers: { - path: "/api/v2/addresses/:hash/token-transfers", - pathParams: [ "hash" as const ], - filterFields: [ "filter" as const, "type" as const, "token" as const ], + path: '/api/v2/addresses/:hash/token-transfers', + pathParams: [ 'hash' as const ], + filterFields: [ 'filter' as const, 'type' as const, 'token' as const ], }, address_blocks_validated: { - path: "/api/v2/addresses/:hash/blocks-validated", - pathParams: [ "hash" as const ], - filterFields: [], + path: '/api/v2/addresses/:hash/blocks-validated', + pathParams: [ 'hash' as const ], + filterFields: [ ], }, address_coin_balance: { - path: "/api/v2/addresses/:hash/coin-balance-history", - pathParams: [ "hash" as const ], - filterFields: [], + path: '/api/v2/addresses/:hash/coin-balance-history', + pathParams: [ 'hash' as const ], + filterFields: [ ], }, address_coin_balance_chart: { - path: "/api/v2/addresses/:hash/coin-balance-history-by-day", - pathParams: [ "hash" as const ], + path: '/api/v2/addresses/:hash/coin-balance-history-by-day', + pathParams: [ 'hash' as const ], }, address_logs: { - path: "/api/v2/addresses/:hash/logs", - pathParams: [ "hash" as const ], - filterFields: [], + path: '/api/v2/addresses/:hash/logs', + pathParams: [ 'hash' as const ], + filterFields: [ ], }, address_tokens: { - path: "/api/v2/addresses/:hash/tokens", - pathParams: [ "hash" as const ], - filterFields: [ "type" as const ], + path: '/api/v2/addresses/:hash/tokens', + pathParams: [ 'hash' as const ], + filterFields: [ 'type' as const ], }, address_nfts: { - path: "/api/v2/addresses/:hash/nft", - pathParams: [ "hash" as const ], - filterFields: [ "type" as const ], + path: '/api/v2/addresses/:hash/nft', + pathParams: [ 'hash' as const ], + filterFields: [ 'type' as const ], }, address_collections: { - path: "/api/v2/addresses/:hash/nft/collections", - pathParams: [ "hash" as const ], - filterFields: [ "type" as const ], + path: '/api/v2/addresses/:hash/nft/collections', + pathParams: [ 'hash' as const ], + filterFields: [ 'type' as const ], }, address_withdrawals: { - path: "/api/v2/addresses/:hash/withdrawals", - pathParams: [ "hash" as const ], + path: '/api/v2/addresses/:hash/withdrawals', + pathParams: [ 'hash' as const ], + filterFields: [], + }, + address_epoch_rewards: { + path: '/api/v2/addresses/:hash/election-rewards', + pathParams: [ 'hash' as const ], filterFields: [], }, + address_xstar_score: { + path: '/api/v2/proxy/xname/addresses/:hash', + pathParams: [ 'hash' as const ], + }, // CONTRACT contract: { - path: "/api/v2/smart-contracts/:hash", - pathParams: [ "hash" as const ], - }, - contract_methods_read: { - path: "/api/v2/smart-contracts/:hash/methods-read", - pathParams: [ "hash" as const ], - }, - contract_methods_read_proxy: { - path: "/api/v2/smart-contracts/:hash/methods-read-proxy", - pathParams: [ "hash" as const ], - }, - contract_method_query: { - path: "/api/v2/smart-contracts/:hash/query-read-method", - pathParams: [ "hash" as const ], - }, - contract_methods_write: { - path: "/api/v2/smart-contracts/:hash/methods-write", - pathParams: [ "hash" as const ], - }, - contract_methods_write_proxy: { - path: "/api/v2/smart-contracts/:hash/methods-write-proxy", - pathParams: [ "hash" as const ], + path: '/api/v2/smart-contracts/:hash', + pathParams: [ 'hash' as const ], }, contract_verification_config: { - path: "/api/v2/smart-contracts/verification/config", + path: '/api/v2/smart-contracts/verification/config', }, contract_verification_via: { - path: "/api/v2/smart-contracts/:hash/verification/via/:method", - pathParams: [ "hash" as const, "method" as const ], + path: '/api/v2/smart-contracts/:hash/verification/via/:method', + pathParams: [ 'hash' as const, 'method' as const ], + }, + contract_solidity_scan_report: { + path: '/api/v2/smart-contracts/:hash/solidityscan-report', + pathParams: [ 'hash' as const ], }, - contract_solidityscan_report: { - path: "/api/v2/smart-contracts/:hash/solidityscan-report", - pathParams: [ "hash" as const ], + contract_security_audits: { + path: '/api/v2/smart-contracts/:hash/audit-reports', + pathParams: [ 'hash' as const ], }, verified_contracts: { - path: "/api/v2/smart-contracts", - filterFields: [ "q" as const, "filter" as const ], + path: '/api/v2/smart-contracts', + filterFields: [ 'q' as const, 'filter' as const ], }, verified_contracts_counters: { - path: "/api/v2/smart-contracts/counters", + path: '/api/v2/smart-contracts/counters', }, // TOKEN token: { - path: "/api/v2/tokens/:hash", - pathParams: [ "hash" as const ], + path: '/api/v2/tokens/:hash', + pathParams: [ 'hash' as const ], }, token_verified_info: { - path: "/api/v1/chains/:chainId/token-infos/:hash", - pathParams: [ "chainId" as const, "hash" as const ], + path: '/api/v1/chains/:chainId/token-infos/:hash', + pathParams: [ 'chainId' as const, 'hash' as const ], endpoint: getFeaturePayload(config.features.verifiedTokens)?.api.endpoint, basePath: getFeaturePayload(config.features.verifiedTokens)?.api.basePath, }, token_counters: { - path: "/api/v2/tokens/:hash/counters", - pathParams: [ "hash" as const ], + path: '/api/v2/tokens/:hash/counters', + pathParams: [ 'hash' as const ], }, token_holders: { - path: "/api/v2/tokens/:hash/holders", - pathParams: [ "hash" as const ], + path: '/api/v2/tokens/:hash/holders', + pathParams: [ 'hash' as const ], filterFields: [], }, token_transfers: { - path: "/api/v2/tokens/:hash/transfers", - pathParams: [ "hash" as const ], + path: '/api/v2/tokens/:hash/transfers', + pathParams: [ 'hash' as const ], filterFields: [], }, token_inventory: { - path: "/api/v2/tokens/:hash/instances", - pathParams: [ "hash" as const ], - filterFields: [ "holder_address_hash" as const ], + path: '/api/v2/tokens/:hash/instances', + pathParams: [ 'hash' as const ], + filterFields: [ 'holder_address_hash' as const ], }, tokens: { - path: "/api/v2/tokens", - filterFields: [ "q" as const, "type" as const ], + path: '/api/v2/tokens', + filterFields: [ 'q' as const, 'type' as const ], }, tokens_bridged: { - path: "/api/v2/tokens/bridged", - filterFields: [ "q" as const, "chain_ids" as const ], + path: '/api/v2/tokens/bridged', + filterFields: [ 'q' as const, 'chain_ids' as const ], }, // TOKEN INSTANCE token_instance: { - path: "/api/v2/tokens/:hash/instances/:id", - pathParams: [ "hash" as const, "id" as const ], + path: '/api/v2/tokens/:hash/instances/:id', + pathParams: [ 'hash' as const, 'id' as const ], }, token_instance_transfers_count: { - path: "/api/v2/tokens/:hash/instances/:id/transfers-count", - pathParams: [ "hash" as const, "id" as const ], + path: '/api/v2/tokens/:hash/instances/:id/transfers-count', + pathParams: [ 'hash' as const, 'id' as const ], }, token_instance_transfers: { - path: "/api/v2/tokens/:hash/instances/:id/transfers", - pathParams: [ "hash" as const, "id" as const ], + path: '/api/v2/tokens/:hash/instances/:id/transfers', + pathParams: [ 'hash' as const, 'id' as const ], filterFields: [], }, token_instance_holders: { - path: "/api/v2/tokens/:hash/instances/:id/holders", - pathParams: [ "hash" as const, "id" as const ], + path: '/api/v2/tokens/:hash/instances/:id/holders', + pathParams: [ 'hash' as const, 'id' as const ], + filterFields: [], + }, + token_instance_refresh_metadata: { + path: '/api/v2/tokens/:hash/instances/:id/refetch-metadata', + pathParams: [ 'hash' as const, 'id' as const ], filterFields: [], }, - // HOMEPAGE - homepage_stats: { - path: "/api/v2/stats", + // TOKEN TRANSFERS + token_transfers_all: { + path: '/api/v2/token-transfers', + filterFields: [ 'type' as const ], }, - homepage_chart_txs: { - path: "/api/v2/stats/charts/transactions", + + // APP STATS + stats: { + path: '/api/v2/stats', + headers: { + 'updated-gas-oracle': 'true', + }, }, - homepage_chart_market: { - path: "/api/v2/stats/charts/market", + stats_charts_txs: { + path: '/api/v2/stats/charts/transactions', }, + stats_charts_market: { + path: '/api/v2/stats/charts/market', + }, + stats_charts_secondary_coin_price: { + path: '/api/v2/stats/charts/secondary-coin-market', + }, + + // HOMEPAGE homepage_blocks: { - path: "/api/v2/main-page/blocks", + path: '/api/v2/main-page/blocks', + }, + homepage_optimistic_deposits: { + path: '/api/v2/main-page/optimism-deposits', }, - homepage_deposits: { - path: "/api/v2/main-page/optimism-deposits", + homepage_arbitrum_deposits: { + path: '/api/v2/main-page/arbitrum/messages/to-rollup', }, homepage_txs: { - path: "/api/v2/main-page/transactions", + path: '/api/v2/main-page/transactions', }, homepage_zkevm_l2_batches: { - path: "/api/v2/main-page/zkevm/batches/confirmed", + path: '/api/v2/main-page/zkevm/batches/confirmed', + }, + homepage_arbitrum_l2_batches: { + path: '/api/v2/main-page/arbitrum/batches/committed', }, homepage_txs_watchlist: { - path: "/api/v2/main-page/transactions/watchlist", + path: '/api/v2/main-page/transactions/watchlist', }, homepage_indexing_status: { - path: "/api/v2/main-page/indexing-status", + path: '/api/v2/main-page/indexing-status', }, homepage_zkevm_latest_batch: { - path: "/api/v2/main-page/zkevm/batches/latest-number", + path: '/api/v2/main-page/zkevm/batches/latest-number', + }, + homepage_zksync_latest_batch: { + path: '/api/v2/main-page/zksync/batches/latest-number', + }, + homepage_arbitrum_latest_batch: { + path: '/api/v2/main-page/arbitrum/batches/latest-number', }, // SEARCH quick_search: { - path: "/api/v2/search/quick", - filterFields: [ "q" ], + path: '/api/v2/search/quick', + filterFields: [ 'q' ], }, search: { - path: "/api/v2/search", - filterFields: [ "q" ], + path: '/api/v2/search', + filterFields: [ 'q' ], }, search_check_redirect: { - path: "/api/v2/search/check-redirect", + path: '/api/v2/search/check-redirect', + }, + + // optimistic L2 + optimistic_l2_deposits: { + path: '/api/v2/optimism/deposits', + filterFields: [], + }, + + optimistic_l2_deposits_count: { + path: '/api/v2/optimism/deposits/count', + }, + + optimistic_l2_withdrawals: { + path: '/api/v2/optimism/withdrawals', + filterFields: [], + }, + + optimistic_l2_withdrawals_count: { + path: '/api/v2/optimism/withdrawals/count', + }, + + optimistic_l2_output_roots: { + path: '/api/v2/optimism/output-roots', + filterFields: [], + }, + + optimistic_l2_output_roots_count: { + path: '/api/v2/optimism/output-roots/count', + }, + + optimistic_l2_txn_batches: { + path: '/api/v2/optimism/batches', + filterFields: [], + }, + + optimistic_l2_txn_batches_count: { + path: '/api/v2/optimism/batches/count', + }, + + optimistic_l2_txn_batch: { + path: '/api/v2/optimism/batches/:number', + pathParams: [ 'number' as const ], + }, + + optimistic_l2_txn_batch_txs: { + path: '/api/v2/transactions/optimism-batch/:number', + pathParams: [ 'number' as const ], + filterFields: [], + }, + + optimistic_l2_txn_batch_blocks: { + path: '/api/v2/blocks/optimism-batch/:number', + pathParams: [ 'number' as const ], + filterFields: [], + }, + + optimistic_l2_dispute_games: { + path: '/api/v2/optimism/games', + filterFields: [], + }, + + optimistic_l2_dispute_games_count: { + path: '/api/v2/optimism/games/count', + }, + + // MUD worlds on optimism + mud_worlds: { + path: '/api/v2/mud/worlds', + filterFields: [], + }, + + address_mud_tables: { + path: '/api/v2/mud/worlds/:hash/tables', + pathParams: [ 'hash' as const ], + filterFields: [ 'q' as const ], + }, + + address_mud_tables_count: { + path: '/api/v2/mud/worlds/:hash/tables/count', + pathParams: [ 'hash' as const ], + }, + + address_mud_records: { + path: '/api/v2/mud/worlds/:hash/tables/:table_id/records', + pathParams: [ 'hash' as const, 'table_id' as const ], + filterFields: [ 'filter_key0' as const, 'filter_key1' as const ], + }, + + address_mud_record: { + path: '/api/v2/mud/worlds/:hash/tables/:table_id/records/:record_id', + pathParams: [ 'hash' as const, 'table_id' as const, 'record_id' as const ], + }, + + contract_mud_systems: { + path: '/api/v2/mud/worlds/:hash/systems', + pathParams: [ 'hash' as const ], + }, + + contract_mud_system_info: { + path: '/api/v2/mud/worlds/:hash/systems/:system_address', + pathParams: [ 'hash' as const, 'system_address' as const ], + }, + + // arbitrum L2 + arbitrum_l2_messages: { + path: '/api/v2/arbitrum/messages/:direction', + pathParams: [ 'direction' as const ], + filterFields: [], + }, + + arbitrum_l2_messages_count: { + path: '/api/v2/arbitrum/messages/:direction/count', + pathParams: [ 'direction' as const ], }, - // L2 - l2_deposits: { - path: "/api/v2/optimism/deposits", + arbitrum_l2_txn_batches: { + path: '/api/v2/arbitrum/batches', filterFields: [], }, - l2_deposits_count: { - path: "/api/v2/optimism/deposits/count", + arbitrum_l2_txn_batches_count: { + path: '/api/v2/arbitrum/batches/count', }, - l2_withdrawals: { - path: "/api/v2/optimism/withdrawals", + arbitrum_l2_txn_batch: { + path: '/api/v2/arbitrum/batches/:number', + pathParams: [ 'number' as const ], + }, + + arbitrum_l2_txn_batch_txs: { + path: '/api/v2/transactions/arbitrum-batch/:number', + pathParams: [ 'number' as const ], filterFields: [], }, - l2_withdrawals_count: { - path: "/api/v2/optimism/withdrawals/count", + arbitrum_l2_txn_batch_blocks: { + path: '/api/v2/blocks/arbitrum-batch/:number', + pathParams: [ 'number' as const ], + filterFields: [], }, - l2_output_roots: { - path: "/api/v2/optimism/output-roots", + // zkEvm L2 + zkevm_l2_deposits: { + path: '/api/v2/zkevm/deposits', filterFields: [], }, - l2_output_roots_count: { - path: "/api/v2/optimism/output-roots/count", + zkevm_l2_deposits_count: { + path: '/api/v2/zkevm/deposits/count', }, - l2_txn_batches: { - path: "/api/v2/optimism/txn-batches", + zkevm_l2_withdrawals: { + path: '/api/v2/zkevm/withdrawals', filterFields: [], }, - l2_txn_batches_count: { - path: "/api/v2/optimism/txn-batches/count", + zkevm_l2_withdrawals_count: { + path: '/api/v2/zkevm/withdrawals/count', }, zkevm_l2_txn_batches: { - path: "/api/v2/zkevm/batches", + path: '/api/v2/zkevm/batches', filterFields: [], }, zkevm_l2_txn_batches_count: { - path: "/api/v2/zkevm/batches/count", + path: '/api/v2/zkevm/batches/count', }, zkevm_l2_txn_batch: { - path: "/api/v2/zkevm/batches/:number", - pathParams: [ "number" as const ], + path: '/api/v2/zkevm/batches/:number', + pathParams: [ 'number' as const ], }, + zkevm_l2_txn_batch_txs: { - path: "/api/v2/transactions/zkevm-batch/:number", - pathParams: [ "number" as const ], + path: '/api/v2/transactions/zkevm-batch/:number', + pathParams: [ 'number' as const ], + filterFields: [], + }, + + // zkSync L2 + zksync_l2_txn_batches: { + path: '/api/v2/zksync/batches', + filterFields: [], + }, + + zksync_l2_txn_batches_count: { + path: '/api/v2/zksync/batches/count', + }, + + zksync_l2_txn_batch: { + path: '/api/v2/zksync/batches/:number', + pathParams: [ 'number' as const ], + }, + + zksync_l2_txn_batch_txs: { + path: '/api/v2/transactions/zksync-batch/:number', + pathParams: [ 'number' as const ], + filterFields: [], + }, + + // SHIBARIUM L2 + shibarium_deposits: { + path: '/api/v2/shibarium/deposits', + filterFields: [], + }, + + shibarium_deposits_count: { + path: '/api/v2/shibarium/deposits/count', + }, + + shibarium_withdrawals: { + path: '/api/v2/shibarium/withdrawals', + filterFields: [], + }, + + shibarium_withdrawals_count: { + path: '/api/v2/shibarium/withdrawals/count', + }, + + // SCROLL L2 + scroll_l2_deposits: { + path: '/api/v2/scroll/deposits', + filterFields: [], + }, + + scroll_l2_deposits_count: { + path: '/api/v2/scroll/deposits/count', + }, + + scroll_l2_withdrawals: { + path: '/api/v2/scroll/withdrawals', + filterFields: [], + }, + + scroll_l2_withdrawals_count: { + path: '/api/v2/scroll/withdrawals/count', + }, + + scroll_l2_txn_batches: { + path: '/api/v2/scroll/batches', filterFields: [], }, + scroll_l2_txn_batches_count: { + path: '/api/v2/scroll/batches/count', + }, + + scroll_l2_txn_batch: { + path: '/api/v2/scroll/batches/:number', + pathParams: [ 'number' as const ], + }, + + scroll_l2_txn_batch_txs: { + path: '/api/v2/transactions/scroll-batch/:number', + pathParams: [ 'number' as const ], + filterFields: [], + }, + + scroll_l2_txn_batch_blocks: { + path: '/api/v2/blocks/scroll-batch/:number', + pathParams: [ 'number' as const ], + filterFields: [], + }, + + // NOVES-FI + noves_transaction: { + path: '/api/v2/proxy/noves-fi/transactions/:hash', + pathParams: [ 'hash' as const ], + }, + noves_address_history: { + path: '/api/v2/proxy/noves-fi/addresses/:address/transactions', + pathParams: [ 'address' as const ], + filterFields: [], + }, + noves_describe_txs: { + path: '/api/v2/proxy/noves-fi/transaction-descriptions', + }, + // USER OPS user_ops: { - path: "/api/v2/proxy/account-abstraction/operations", - filterFields: [ "transaction_hash" as const, "sender" as const ], + path: '/api/v2/proxy/account-abstraction/operations', + filterFields: [ 'transaction_hash' as const, 'sender' as const ], }, user_op: { - path: "/api/v2/proxy/account-abstraction/operations/:hash", - pathParams: [ "hash" as const ], + path: '/api/v2/proxy/account-abstraction/operations/:hash', + pathParams: [ 'hash' as const ], }, user_ops_account: { - path: "/api/v2/proxy/account-abstraction/accounts/:hash", - pathParams: [ "hash" as const ], + path: '/api/v2/proxy/account-abstraction/accounts/:hash', + pathParams: [ 'hash' as const ], + }, + user_op_interpretation: { + path: '/api/v2/proxy/account-abstraction/operations/:hash/summary', + pathParams: [ 'hash' as const ], + }, + + // VALIDATORS + validators_stability: { + path: '/api/v2/validators/stability', + filterFields: [ 'address_hash' as const, 'state_filter' as const ], + }, + validators_stability_counters: { + path: '/api/v2/validators/stability/counters', + }, + validators_blackfort: { + path: '/api/v2/validators/blackfort', + filterFields: [], + }, + validators_blackfort_counters: { + path: '/api/v2/validators/blackfort/counters', + }, + + // BLOBS + blob: { + path: '/api/v2/blobs/:hash', + pathParams: [ 'hash' as const ], + }, + + // ADVANCED FILTER + advanced_filter: { + path: '/api/v2/advanced-filters', + filterFields: [ + 'tx_types' as const, + 'methods' as const, + 'methods_names' as const /* frontend only */, + 'age_from' as const, + 'age_to' as const, + 'age' as const /* frontend only */, + 'from_address_hashes_to_include' as const, + 'from_address_hashes_to_exclude' as const, + 'to_address_hashes_to_include' as const, + 'to_address_hashes_to_exclude' as const, + 'address_relation' as const, + 'amount_from' as const, + 'amount_to' as const, + 'token_contract_address_hashes_to_include' as const, + 'token_contract_symbols_to_include' as const /* frontend only */, + 'token_contract_address_hashes_to_exclude' as const, + 'token_contract_symbols_to_exclude' as const /* frontend only */, + 'block_number' as const, + 'transaction_index' as const, + 'internal_transaction_index' as const, + 'token_transfer_index' as const, + ], + }, + advanced_filter_methods: { + path: '/api/v2/advanced-filters/methods', + filterFields: [ 'q' as const ], + }, + advanced_filter_csv: { + path: '/api/v2/advanced-filters/csv', + }, + + // POOLS + pools: { + path: '/api/v1/chains/:chainId/pools', + pathParams: [ 'chainId' as const ], + filterFields: [ 'query' as const ], + endpoint: getFeaturePayload(config.features.pools)?.api.endpoint, + basePath: getFeaturePayload(config.features.pools)?.api.basePath, + }, + + pool: { + path: '/api/v1/chains/:chainId/pools/:hash', + pathParams: [ 'chainId' as const, 'hash' as const ], + endpoint: getFeaturePayload(config.features.pools)?.api.endpoint, + basePath: getFeaturePayload(config.features.pools)?.api.basePath, }, // CONFIGS config_backend_version: { - path: "/api/v2/config/backend-version", + path: '/api/v2/config/backend-version', + }, + config_csv_export: { + path: '/api/v2/config/csv-export', + }, + + // CSV EXPORT + csv_export_token_holders: { + path: '/api/v2/tokens/:hash/holders/csv', + pathParams: [ 'hash' as const ], }, // OTHER api_v2_key: { - path: "/api/v2/key", + path: '/api/v2/key', }, // API V1 csv_export_txs: { - path: "/api/v1/transactions-csv", + path: '/api/v1/transactions-csv', }, csv_export_internal_txs: { - path: "/api/v1/internal-transactions-csv", + path: '/api/v1/internal-transactions-csv', }, csv_export_token_transfers: { - path: "/api/v1/token-transfers-csv", + path: '/api/v1/token-transfers-csv', }, csv_export_logs: { - path: "/api/v1/logs-csv", + path: '/api/v1/logs-csv', }, - graphql: { - path: "/api/v1/graphql", - }, - - // bool provider - provider_stats: { - baseUrl: config.api.boolApi ?? "", - basePath: "/bool-network-beta", - path: "/blockchain/provider" + encodeURIComponent(":info"), - }, - providers: { - baseUrl: config.api.boolApi ?? "", - basePath: "/bool-network-beta", - path: "/blockchain/providers", - }, - nodes: { - baseUrl: config.api.boolApi ?? "", - basePath: "/bool-network-beta", - path: "/node/validators", - filterFields: [ "validatorStatus" as const, "searchStr" as const ], - }, - epoch_info: { - baseUrl: config.api.boolApi ?? "", - basePath: "/bool-network-beta", - path: "/node/epoch-info", - }, - era_info: { - baseUrl: config.api.boolApi ?? "", - basePath: "/bool-network-beta", - path: "/node/era-info", - }, - device_statistic: { - baseUrl: config.api.boolApi ?? "", - basePath: "/bool-network-beta", - path: "/blockchain/device" + encodeURIComponent(":statistic"), - filterFields: [ - "deviceId" as const, - "startTime" as const, - "endTime" as const, - ], + csv_export_epoch_rewards: { + path: '/api/v1/celo-election-rewards-csv', }, - provider_details: { - baseUrl: config.api.boolApi ?? "", - basePath: "/bool-network-beta", - path: "/blockchain/provider-detail", - filterFields: [ "providerId" as const ], - }, - validator_details: { - baseUrl: config.api.boolApi ?? "", - basePath: "/bool-network-beta", - path: "/node/validator-detail", - filterFields: [ "address" as const ], - }, - validator_statistic: { - baseUrl: config.api.boolApi ?? "", - basePath: "/bool-network-beta", - path: "/node/validator-statistic", - filterFields: [ - "address" as const, - "startTime" as const, - "endTime" as const, - ], - }, - dhc_devices: { - baseUrl: config.api.boolApi ?? "", - basePath: "/bool-network-beta", - path: "/blockchain/device" + encodeURIComponent(":owner"), - filterFields: [ - "pageNo" as const, - "pageSize" as const, - "status" as const, - "ownerAddress" as const, - ], - }, - dhc_devices_info: { - baseUrl: config.api.boolApi ?? "", - basePath: "/bool-network-beta", - path: "/blockchain/device" + encodeURIComponent(":info"), - filterFields: [ "ownerAddress" as const ], - }, - dhc_device: { - baseUrl: config.api.boolApi ?? "", - basePath: "/bool-network-beta", - path: "/blockchain/device", - filterFields: [ "deviceId" as const ], + graphql: { + path: '/api/v1/graphql', }, - bool_rpc: { - baseUrl: config.chain.rpcUrl ?? "", - path: "/", + block_countdown: { + path: '/api', }, }; -export const boolApiNames: Array = [ - "provider_stats", - "providers", - "nodes", - "epoch_info", - "era_info", - "bool_rpc", - "device_statistic", - "provider_details", - "validator_details", - "validator_statistic", - "dhc_devices", - "dhc_devices_info", - "dhc_device", -]; - export type ResourceName = keyof typeof RESOURCES; type ResourcePathMap = { - [K in ResourceName]: (typeof RESOURCES)[K]["path"]; + [K in ResourceName]: typeof RESOURCES[K]['path'] }; export type ResourcePath = ResourcePathMap[keyof ResourcePathMap]; -export type ResourceFiltersKey = - (typeof RESOURCES)[R] extends { filterFields: Array } - ? ArrayElement<(typeof RESOURCES)[R]["filterFields"]> - : never; +export type ResourceFiltersKey = typeof RESOURCES[R] extends { filterFields: Array } ? + ArrayElement : + never; export const resourceKey = (x: keyof typeof RESOURCES) => x; type ResourcePathParamName = - (typeof RESOURCES)[Resource] extends { pathParams: Array } - ? ArrayElement<(typeof RESOURCES)[Resource]["pathParams"]> - : string; + typeof RESOURCES[Resource] extends { pathParams: Array } ? + ArrayElement : + string; -export type ResourcePathParams = - (typeof RESOURCES)[Resource] extends { pathParams: Array } - ? Record, string | undefined> - : never; +export type ResourcePathParams = typeof RESOURCES[Resource] extends { pathParams: Array } ? + Record, string | undefined> : + never; export interface ResourceError { payload?: T; - status: Response["status"]; - statusText: Response["statusText"]; + status: Response['status']; + statusText: Response['statusText']; } export type ResourceErrorAccount = ResourceError<{ errors: T }>; -export type PaginatedResourcesOfBool = "providers" | "nodes" | "dhc_devices"; -export type PaginatedResources = - | "blocks" - | "block_txs" - | "txs_validated" - | "txs_pending" - | "txs_watchlist" - | "txs_execution_node" - | "tx_internal_txs" - | "tx_logs" - | "tx_token_transfers" - | "tx_state_changes" - | "addresses" - | "address_txs" - | "address_internal_txs" - | "address_token_transfers" - | "address_blocks_validated" - | "address_coin_balance" - | "search" - | "address_logs" - | "address_tokens" - | "address_nfts" - | "address_collections" - | "token_transfers" - | "token_holders" - | "token_inventory" - | "tokens" - | "tokens_bridged" - | "token_instance_transfers" - | "token_instance_holders" - | "verified_contracts" - | "l2_output_roots" - | "l2_withdrawals" - | "l2_txn_batches" - | "l2_deposits" - | "zkevm_l2_txn_batches" - | "zkevm_l2_txn_batch_txs" - | "withdrawals" - | "address_withdrawals" - | "block_withdrawals" - | "watchlist" - | "private_tags_address" - | "private_tags_tx" - | "domains_lookup" - | "addresses_lookup" - | "user_ops"; - -export type PaginatedResponse = - ResourcePayload; - -export type ResourcePayloadOfBool = - Q extends "provider_stats" - ? ProviderInfo - : Q extends "era_info" - ? EraInfo - : Q extends "epoch_info" - ? EpochInfo - : Q extends "providers" - ? ProvidersPage - : Q extends "nodes" - ? NodesPage - : Q extends "device_statistic" - ? Array - : Q extends "provider_details" - ? ProviderDetails - : Q extends "validator_statistic" - ? Array - : Q extends "validator_details" - ? ValidatorDetails - : Q extends "dhc_devices" - ? DHCDevicePage - : Q extends "dhc_devices_info" - ? DHCDeviceInfo - : Q extends "dhc_device" - ? DHCDevice - : never; - -type ResourcePayload2 = Q extends "user_info" - ? UserInfo - : Q extends "custom_abi" - ? CustomAbis - : Q extends "public_tags" - ? PublicTags - : Q extends "private_tags_address" - ? AddressTagsResponse - : Q extends "private_tags_tx" - ? TransactionTagsResponse - : Q extends "api_keys" - ? ApiKeys - : Q extends "watchlist" - ? WatchlistResponse - : Q extends "verified_addresses" - ? VerifiedAddressResponse - : Q extends "token_info_applications_config" - ? TokenInfoApplicationConfig - : Q extends "token_info_applications" - ? TokenInfoApplications - : Q extends "homepage_stats" - ? HomeStats - : Q extends "homepage_chart_txs" - ? ChartTransactionResponse - : Q extends "homepage_chart_market" - ? ChartMarketResponse - : never; - -/* eslint-disable @typescript-eslint/indent */ -export type ResourcePayload = - | ResourcePayloadOfBool - | ResourcePayload2 - | (Q extends "homepage_blocks" - ? Array - : Q extends "homepage_txs" - ? Array - : Q extends "homepage_txs_watchlist" - ? Array - : Q extends "homepage_deposits" - ? Array - : Q extends "homepage_zkevm_l2_batches" - ? { items: Array } - : Q extends "homepage_indexing_status" - ? IndexingStatus - : Q extends "homepage_zkevm_latest_batch" - ? number - : Q extends "stats_counters" - ? Counters - : Q extends "stats_lines" - ? StatsCharts - : Q extends "stats_line" - ? StatsChart - : Q extends "blocks" - ? BlocksResponse - : Q extends "block" - ? Block - : Q extends "block_txs" - ? BlockTransactionsResponse - : Q extends "block_withdrawals" - ? BlockWithdrawalsResponse - : Q extends "txs_validated" - ? TransactionsResponseValidated - : Q extends "txs_pending" - ? TransactionsResponsePending - : Q extends "txs_watchlist" - ? TransactionsResponseWatchlist - : Q extends "txs_execution_node" - ? TransactionsResponseValidated - : Q extends "tx" - ? Transaction - : Q extends "tx_internal_txs" - ? InternalTransactionsResponse - : Q extends "tx_logs" - ? LogsResponseTx - : Q extends "tx_token_transfers" - ? TokenTransferResponse - : Q extends "tx_raw_trace" - ? RawTracesResponse - : Q extends "tx_state_changes" - ? TxStateChanges - : Q extends "tx_interpretation" - ? TxInterpretationResponse - : Q extends "addresses" - ? AddressesResponse - : Q extends "address" - ? Address - : Q extends "address_counters" - ? AddressCounters - : Q extends "address_tabs_counters" - ? AddressTabsCounters - : Q extends "address_txs" - ? AddressTransactionsResponse - : Q extends "address_internal_txs" - ? AddressInternalTxsResponse - : Q extends "address_token_transfers" - ? AddressTokenTransferResponse - : Q extends "address_blocks_validated" - ? AddressBlocksValidatedResponse - : Q extends "address_coin_balance" - ? AddressCoinBalanceHistoryResponse - : Q extends "address_coin_balance_chart" - ? AddressCoinBalanceHistoryChart - : Q extends "address_logs" - ? LogsResponseAddress - : Q extends "address_tokens" - ? AddressTokensResponse - : Q extends "address_nfts" - ? AddressNFTsResponse - : Q extends "address_collections" - ? AddressCollectionsResponse - : Q extends "address_withdrawals" - ? AddressWithdrawalsResponse - : Q extends "token" - ? TokenInfo - : Q extends "token_verified_info" - ? TokenVerifiedInfo - : Q extends "token_counters" - ? TokenCounters - : Q extends "token_transfers" - ? TokenTransferResponse - : Q extends "token_holders" - ? TokenHolders - : Q extends "token_instance" - ? TokenInstance - : Q extends "token_instance_transfers_count" - ? TokenInstanceTransfersCount - : Q extends "token_instance_transfers" - ? TokenInstanceTransferResponse - : Q extends "token_instance_holders" - ? TokenHolders - : Q extends "token_inventory" - ? TokenInventoryResponse - : Q extends "tokens" - ? TokensResponse - : Q extends "tokens_bridged" - ? TokensResponse - : Q extends "quick_search" - ? Array - : Q extends "search" - ? SearchResult - : Q extends "search_check_redirect" - ? SearchRedirectResult - : Q extends "contract" - ? SmartContract - : Q extends "contract_methods_read" - ? Array - : Q extends "contract_methods_read_proxy" - ? Array - : Q extends "contract_methods_write" - ? Array - : Q extends "contract_methods_write_proxy" - ? Array - : Q extends "contract_solidityscan_report" - ? SolidityscanReport - : Q extends "verified_contracts" - ? VerifiedContractsResponse - : Q extends "verified_contracts_counters" - ? VerifiedContractsCounters - : Q extends "visualize_sol2uml" - ? VisualizedContract - : Q extends "contract_verification_config" - ? SmartContractVerificationConfig - : Q extends "withdrawals" - ? WithdrawalsResponse - : Q extends "withdrawals_counters" - ? WithdrawalsCounters - : Q extends "l2_output_roots" - ? L2OutputRootsResponse - : Q extends "l2_withdrawals" - ? L2WithdrawalsResponse - : Q extends "l2_deposits" - ? L2DepositsResponse - : Q extends "l2_txn_batches" - ? L2TxnBatchesResponse - : Q extends "l2_output_roots_count" - ? number - : Q extends "l2_withdrawals_count" - ? number - : Q extends "l2_deposits_count" - ? number - : Q extends "l2_txn_batches_count" - ? number - : Q extends "zkevm_l2_txn_batches" - ? ZkEvmL2TxnBatchesResponse - : Q extends "zkevm_l2_txn_batches_count" - ? number - : Q extends "zkevm_l2_txn_batch" - ? ZkEvmL2TxnBatch - : Q extends "zkevm_l2_txn_batch_txs" - ? ZkEvmL2TxnBatchTxs - : Q extends "config_backend_version" - ? BackendVersionConfig - : Q extends "addresses_lookup" - ? EnsAddressLookupResponse - : Q extends "domain_info" - ? EnsDomainDetailed - : Q extends "domain_events" - ? EnsDomainEventsResponse - : Q extends "domains_lookup" - ? EnsDomainLookupResponse - : Q extends "user_ops" - ? UserOpsResponse - : Q extends "user_op" - ? UserOp - : Q extends "user_ops_account" - ? UserOpsAccount - : never); -/* eslint-enable @typescript-eslint/indent */ - -/* eslint-disable @typescript-eslint/indent */ -export type PaginationFilters< - Q extends PaginatedResources | PaginatedResourcesOfBool -> = Q extends "blocks" - ? BlockFilters - : Q extends "txs_validated" | "txs_pending" - ? TTxsFilters - : Q extends "tx_token_transfers" - ? TokenTransferFilters - : Q extends "token_transfers" - ? TokenTransferFilters - : Q extends "address_txs" | "address_internal_txs" - ? AddressTxsFilters - : Q extends "address_token_transfers" - ? AddressTokenTransferFilters - : Q extends "address_tokens" - ? AddressTokensFilter - : Q extends "address_nfts" - ? AddressNFTTokensFilter - : Q extends "address_collections" - ? AddressNFTTokensFilter - : Q extends "search" - ? SearchResultFilters - : Q extends "token_inventory" - ? TokenInventoryFilters - : Q extends "tokens" - ? TokensFilters - : Q extends "tokens_bridged" - ? TokensBridgedFilters - : Q extends "verified_contracts" - ? VerifiedContractsFilters - : Q extends "addresses_lookup" - ? EnsAddressLookupFilters - : Q extends "domains_lookup" - ? EnsDomainLookupFilters - : Q extends "user_ops" - ? UserOpsFilters - : Q extends "providers" - ? PageParams - : Q extends "nodes" - ? NodesParams - : Q extends "dhc_devices" - ? DHCDevicesParams - : Q extends "dhc_devices_info" - ? DHCDevicesParams - : never; -/* eslint-enable @typescript-eslint/indent */ - -/* eslint-disable @typescript-eslint/indent */ -export type PaginationSorting< - Q extends PaginatedResources | PaginatedResourcesOfBool -> = Q extends "tokens" - ? TokensSorting - : Q extends "tokens_bridged" - ? TokensSorting - : Q extends "verified_contracts" - ? VerifiedContractsSorting - : Q extends "address_txs" - ? TransactionsSorting - : Q extends "addresses_lookup" - ? EnsLookupSorting - : Q extends "domains_lookup" - ? EnsLookupSorting - : Q extends "providers" - ? ProvidersPage - : Q extends "nodes" - ? NodesPage - : Q extends "dhc_devices" - ? DHCDevicePage - : never; -/* eslint-enable @typescript-eslint/indent */ +export type PaginatedResources = 'blocks' | 'block_txs' | 'block_election_rewards' | +'txs_validated' | 'txs_pending' | 'txs_with_blobs' | 'txs_watchlist' | 'txs_execution_node' | +'tx_internal_txs' | 'tx_logs' | 'tx_token_transfers' | 'tx_state_changes' | 'tx_blobs' | +'addresses' | 'addresses_metadata_search' | +'address_txs' | 'address_internal_txs' | 'address_token_transfers' | 'address_blocks_validated' | 'address_coin_balance' | +'search' | +'address_logs' | 'address_tokens' | 'address_nfts' | 'address_collections' | 'address_epoch_rewards' | +'token_transfers' | 'token_holders' | 'token_inventory' | 'tokens' | 'tokens_bridged' | +'token_instance_transfers' | 'token_instance_holders' | +'verified_contracts' | +'optimistic_l2_output_roots' | 'optimistic_l2_withdrawals' | 'optimistic_l2_txn_batches' | 'optimistic_l2_deposits' | +'optimistic_l2_dispute_games' | 'optimistic_l2_txn_batch_txs' | 'optimistic_l2_txn_batch_blocks' | +'mud_worlds' | 'address_mud_tables' | 'address_mud_records' | +'shibarium_deposits' | 'shibarium_withdrawals' | +'arbitrum_l2_messages' | 'arbitrum_l2_txn_batches' | 'arbitrum_l2_txn_batch_txs' | 'arbitrum_l2_txn_batch_blocks' | +'zkevm_l2_deposits' | 'zkevm_l2_withdrawals' | 'zkevm_l2_txn_batches' | 'zkevm_l2_txn_batch_txs' | +'zksync_l2_txn_batches' | 'zksync_l2_txn_batch_txs' | +'withdrawals' | 'address_withdrawals' | 'block_withdrawals' | +'watchlist' | 'private_tags_address' | 'private_tags_tx' | +'domains_lookup' | 'addresses_lookup' | 'user_ops' | 'validators_stability' | 'validators_blackfort' | 'noves_address_history' | +'token_transfers_all' | 'scroll_l2_txn_batches' | 'scroll_l2_txn_batch_txs' | 'scroll_l2_txn_batch_blocks' | +'scroll_l2_deposits' | 'scroll_l2_withdrawals' | 'advanced_filter' | 'pools'; + +export type PaginatedResponse = ResourcePayload; + +/* eslint-disable @stylistic/indent */ +// !!! IMPORTANT !!! +// Don't add any new types here because TypeScript cannot handle it properly +// use ResourcePayloadB instead +export type ResourcePayloadA = +Q extends 'user_info' ? UserInfo : +Q extends 'custom_abi' ? CustomAbis : +Q extends 'private_tags_address' ? AddressTagsResponse : +Q extends 'private_tags_tx' ? TransactionTagsResponse : +Q extends 'api_keys' ? ApiKeys : +Q extends 'watchlist' ? WatchlistResponse : +Q extends 'verified_addresses' ? VerifiedAddressResponse : +Q extends 'token_info_applications_config' ? TokenInfoApplicationConfig : +Q extends 'token_info_applications' ? TokenInfoApplications : +Q extends 'stats' ? HomeStats : +Q extends 'stats_charts_txs' ? ChartTransactionResponse : +Q extends 'stats_charts_market' ? ChartMarketResponse : +Q extends 'stats_charts_secondary_coin_price' ? ChartSecondaryCoinPriceResponse : +Q extends 'homepage_blocks' ? Array : +Q extends 'homepage_txs' ? Array : +Q extends 'homepage_txs_watchlist' ? Array : +Q extends 'homepage_optimistic_deposits' ? Array : +Q extends 'homepage_arbitrum_deposits' ? ArbitrumLatestDepositsResponse : +Q extends 'homepage_zkevm_l2_batches' ? { items: Array } : +Q extends 'homepage_arbitrum_l2_batches' ? { items: Array } : +Q extends 'homepage_indexing_status' ? IndexingStatus : +Q extends 'homepage_zkevm_latest_batch' ? number : +Q extends 'homepage_zksync_latest_batch' ? number : +Q extends 'homepage_arbitrum_latest_batch' ? number : +Q extends 'stats_counters' ? stats.Counters : +Q extends 'stats_lines' ? stats.LineCharts : +Q extends 'stats_line' ? stats.LineChart : +Q extends 'blocks' ? BlocksResponse : +Q extends 'block' ? Block : +Q extends 'block_countdown' ? BlockCountdownResponse : +Q extends 'block_txs' ? BlockTransactionsResponse : +Q extends 'block_withdrawals' ? BlockWithdrawalsResponse : +Q extends 'block_epoch' ? BlockEpoch : +Q extends 'block_election_rewards' ? BlockEpochElectionRewardDetailsResponse : +Q extends 'txs_stats' ? TransactionsStats : +Q extends 'txs_validated' ? TransactionsResponseValidated : +Q extends 'txs_pending' ? TransactionsResponsePending : +Q extends 'txs_with_blobs' ? TransactionsResponseWithBlobs : +Q extends 'txs_watchlist' ? TransactionsResponseWatchlist : +Q extends 'txs_execution_node' ? TransactionsResponseValidated : +Q extends 'tx' ? Transaction : +Q extends 'tx_internal_txs' ? InternalTransactionsResponse : +Q extends 'tx_logs' ? LogsResponseTx : +Q extends 'tx_token_transfers' ? TokenTransferResponse : +Q extends 'tx_raw_trace' ? RawTracesResponse : +Q extends 'tx_state_changes' ? TxStateChanges : +Q extends 'tx_blobs' ? TxBlobs : +Q extends 'tx_interpretation' ? TxInterpretationResponse : +Q extends 'addresses' ? AddressesResponse : +Q extends 'addresses_metadata_search' ? AddressesMetadataSearchResult : +Q extends 'address' ? Address : +Q extends 'address_counters' ? AddressCounters : +Q extends 'address_tabs_counters' ? AddressTabsCounters : +Q extends 'address_txs' ? AddressTransactionsResponse : +Q extends 'address_internal_txs' ? AddressInternalTxsResponse : +Q extends 'address_token_transfers' ? AddressTokenTransferResponse : +Q extends 'address_blocks_validated' ? AddressBlocksValidatedResponse : +Q extends 'address_coin_balance' ? AddressCoinBalanceHistoryResponse : +Q extends 'address_coin_balance_chart' ? AddressCoinBalanceHistoryChart : +Q extends 'address_logs' ? LogsResponseAddress : +Q extends 'address_tokens' ? AddressTokensResponse : +Q extends 'address_nfts' ? AddressNFTsResponse : +Q extends 'address_collections' ? AddressCollectionsResponse : +Q extends 'address_withdrawals' ? AddressWithdrawalsResponse : +Q extends 'token' ? TokenInfo : +Q extends 'token_verified_info' ? TokenVerifiedInfo : +Q extends 'token_counters' ? TokenCounters : +Q extends 'token_transfers' ? TokenTransferResponse : +Q extends 'token_holders' ? TokenHolders : +Q extends 'token_instance' ? TokenInstance : +Q extends 'token_instance_transfers_count' ? TokenInstanceTransfersCount : +Q extends 'token_instance_transfers' ? TokenInstanceTransferResponse : +Q extends 'token_instance_holders' ? TokenHolders : +Q extends 'token_inventory' ? TokenInventoryResponse : +Q extends 'tokens' ? TokensResponse : +Q extends 'tokens_bridged' ? TokensResponse : +Q extends 'quick_search' ? Array : +Q extends 'search' ? SearchResult : +Q extends 'search_check_redirect' ? SearchRedirectResult : +Q extends 'contract' ? SmartContract : +Q extends 'contract_solidity_scan_report' ? unknown : +Q extends 'verified_contracts' ? VerifiedContractsResponse : +Q extends 'verified_contracts_counters' ? VerifiedContractsCounters : +Q extends 'visualize_sol2uml' ? visualizer.VisualizeResponse : +Q extends 'contract_verification_config' ? SmartContractVerificationConfigRaw : +Q extends 'optimistic_l2_output_roots' ? OptimisticL2OutputRootsResponse : +Q extends 'optimistic_l2_withdrawals' ? OptimisticL2WithdrawalsResponse : +Q extends 'optimistic_l2_deposits' ? OptimisticL2DepositsResponse : +Q extends 'optimistic_l2_txn_batches' ? OptimisticL2TxnBatchesResponse : +Q extends 'optimistic_l2_txn_batches_count' ? number : +Q extends 'optimistic_l2_txn_batch' ? OptimismL2TxnBatch : +Q extends 'optimistic_l2_txn_batch_txs' ? OptimismL2BatchTxs : +Q extends 'optimistic_l2_txn_batch_blocks' ? OptimismL2BatchBlocks : +Q extends 'optimistic_l2_dispute_games' ? OptimisticL2DisputeGamesResponse : +Q extends 'optimistic_l2_output_roots_count' ? number : +Q extends 'optimistic_l2_withdrawals_count' ? number : +Q extends 'optimistic_l2_deposits_count' ? number : +Q extends 'optimistic_l2_dispute_games_count' ? number : +never; +// !!! IMPORTANT !!! +// See comment above +/* eslint-enable @stylistic/indent */ + +/* eslint-disable @stylistic/indent */ +export type ResourcePayloadB = +Q extends 'config_backend_version' ? BackendVersionConfig : +Q extends 'config_csv_export' ? CsvExportConfig : +Q extends 'address_metadata_info' ? AddressMetadataInfo : +Q extends 'address_metadata_tag_types' ? PublicTagTypesResponse : +Q extends 'blob' ? Blob : +Q extends 'marketplace_dapps' ? Array : +Q extends 'marketplace_dapp' ? MarketplaceAppOverview : +Q extends 'validators_stability' ? ValidatorsStabilityResponse : +Q extends 'validators_stability_counters' ? ValidatorsStabilityCountersResponse : +Q extends 'validators_blackfort' ? ValidatorsBlackfortResponse : +Q extends 'validators_blackfort_counters' ? ValidatorsBlackfortCountersResponse : +Q extends 'shibarium_withdrawals' ? ShibariumWithdrawalsResponse : +Q extends 'shibarium_deposits' ? ShibariumDepositsResponse : +Q extends 'shibarium_withdrawals_count' ? number : +Q extends 'shibarium_deposits_count' ? number : +Q extends 'arbitrum_l2_messages' ? ArbitrumL2MessagesResponse : +Q extends 'arbitrum_l2_messages_count' ? number : +Q extends 'arbitrum_l2_txn_batches' ? ArbitrumL2TxnBatchesResponse : +Q extends 'arbitrum_l2_txn_batches_count' ? number : +Q extends 'arbitrum_l2_txn_batch' ? ArbitrumL2TxnBatch : +Q extends 'arbitrum_l2_txn_batch_txs' ? ArbitrumL2BatchTxs : +Q extends 'arbitrum_l2_txn_batch_blocks' ? ArbitrumL2BatchBlocks : +Q extends 'zkevm_l2_deposits' ? ZkEvmL2DepositsResponse : +Q extends 'zkevm_l2_deposits_count' ? number : +Q extends 'zkevm_l2_withdrawals' ? ZkEvmL2WithdrawalsResponse : +Q extends 'zkevm_l2_withdrawals_count' ? number : +Q extends 'zkevm_l2_txn_batches' ? ZkEvmL2TxnBatchesResponse : +Q extends 'zkevm_l2_txn_batches_count' ? number : +Q extends 'zkevm_l2_txn_batch' ? ZkEvmL2TxnBatch : +Q extends 'zkevm_l2_txn_batch_txs' ? ZkEvmL2TxnBatchTxs : +Q extends 'zksync_l2_txn_batches' ? ZkSyncBatchesResponse : +Q extends 'zksync_l2_txn_batches_count' ? number : +Q extends 'zksync_l2_txn_batch' ? ZkSyncBatch : +Q extends 'zksync_l2_txn_batch_txs' ? ZkSyncBatchTxs : +Q extends 'scroll_l2_txn_batch_txs' ? ScrollL2TxnBatchTxs : +Q extends 'scroll_l2_txn_batch_blocks' ? ScrollL2TxnBatchBlocks : +Q extends 'contract_security_audits' ? SmartContractSecurityAudits : +Q extends 'addresses_lookup' ? bens.LookupAddressResponse : +Q extends 'address_domain' ? bens.GetAddressResponse : +Q extends 'domain_info' ? bens.DetailedDomain : +Q extends 'domain_events' ? bens.ListDomainEventsResponse : +Q extends 'domains_lookup' ? bens.LookupDomainNameResponse : +Q extends 'domain_protocols' ? bens.GetProtocolsResponse : +Q extends 'user_ops' ? UserOpsResponse : +Q extends 'user_op' ? UserOp : +Q extends 'user_ops_account' ? UserOpsAccount : +Q extends 'user_op_interpretation' ? TxInterpretationResponse : +Q extends 'noves_transaction' ? NovesResponseData : +Q extends 'noves_address_history' ? NovesAccountHistoryResponse : +Q extends 'noves_describe_txs' ? NovesDescribeTxsResponse : +Q extends 'mud_worlds' ? MudWorldsResponse : +Q extends 'address_mud_tables' ? AddressMudTables : +Q extends 'address_mud_tables_count' ? number : +Q extends 'address_mud_records' ? AddressMudRecords : +Q extends 'address_mud_record' ? AddressMudRecord : +Q extends 'contract_mud_systems' ? SmartContractMudSystemsResponse : +Q extends 'contract_mud_system_info' ? SmartContractMudSystemInfo : +Q extends 'address_epoch_rewards' ? AddressEpochRewardsResponse : +Q extends 'withdrawals' ? WithdrawalsResponse : +Q extends 'withdrawals_counters' ? WithdrawalsCounters : +Q extends 'rewards_config' ? RewardsConfigResponse : +Q extends 'rewards_check_ref_code' ? RewardsCheckRefCodeResponse : +Q extends 'rewards_nonce' ? RewardsNonceResponse : +Q extends 'rewards_check_user' ? RewardsCheckUserResponse : +Q extends 'rewards_login' ? RewardsLoginResponse : +Q extends 'rewards_user_balances' ? RewardsUserBalancesResponse : +Q extends 'rewards_user_daily_check' ? RewardsUserDailyCheckResponse : +Q extends 'rewards_user_daily_claim' ? RewardsUserDailyClaimResponse : +Q extends 'rewards_user_referrals' ? RewardsUserReferralsResponse : +Q extends 'token_transfers_all' ? TokenTransferResponse : +Q extends 'address_xstar_score' ? AddressXStarResponse : +Q extends 'scroll_l2_txn_batches' ? ScrollL2BatchesResponse : +Q extends 'scroll_l2_txn_batches_count' ? number : +Q extends 'scroll_l2_txn_batch' ? ScrollL2TxnBatch : +Q extends 'scroll_l2_deposits' ? ScrollL2MessagesResponse : +Q extends 'scroll_l2_deposits_count' ? number : +Q extends 'scroll_l2_withdrawals' ? ScrollL2MessagesResponse : +Q extends 'scroll_l2_withdrawals_count' ? number : +Q extends 'advanced_filter' ? AdvancedFilterResponse : +Q extends 'advanced_filter_methods' ? AdvancedFilterMethodsResponse : +Q extends 'pools' ? PoolsResponse : +Q extends 'pool' ? Pool : +never; +/* eslint-enable @stylistic/indent */ + +export type ResourcePayload = ResourcePayloadA | ResourcePayloadB; +export type PaginatedResponseItems = Q extends PaginatedResources ? ResourcePayloadA['items'] | ResourcePayloadB['items'] : never; +export type PaginatedResponseNextPageParams = Q extends PaginatedResources ? + ResourcePayloadA['next_page_params'] | ResourcePayloadB['next_page_params'] : + never; + +/* eslint-disable @stylistic/indent */ +export type PaginationFilters = +Q extends 'blocks' ? BlockFilters : +Q extends 'block_txs' ? TTxsWithBlobsFilters : +Q extends 'txs_validated' | 'txs_pending' ? TTxsFilters : +Q extends 'txs_with_blobs' ? TTxsWithBlobsFilters : +Q extends 'tx_token_transfers' ? TokenTransferFilters : +Q extends 'token_transfers' ? TokenTransferFilters : +Q extends 'address_txs' | 'address_internal_txs' ? AddressTxsFilters : +Q extends 'addresses_metadata_search' ? AddressesMetadataSearchFilters : +Q extends 'address_token_transfers' ? AddressTokenTransferFilters : +Q extends 'address_tokens' ? AddressTokensFilter : +Q extends 'address_nfts' ? AddressNFTTokensFilter : +Q extends 'address_collections' ? AddressNFTTokensFilter : +Q extends 'search' ? SearchResultFilters : +Q extends 'token_inventory' ? TokenInventoryFilters : +Q extends 'tokens' ? TokensFilters : +Q extends 'tokens_bridged' ? TokensBridgedFilters : +Q extends 'verified_contracts' ? VerifiedContractsFilters : +Q extends 'addresses_lookup' ? EnsAddressLookupFilters : +Q extends 'domains_lookup' ? EnsDomainLookupFilters : +Q extends 'user_ops' ? UserOpsFilters : +Q extends 'validators_stability' ? ValidatorsStabilityFilters : +Q extends 'address_mud_tables' ? AddressMudTablesFilter : +Q extends 'address_mud_records' ? AddressMudRecordsFilter : +Q extends 'token_transfers_all' ? TokenTransferFilters : +Q extends 'advanced_filter' ? AdvancedFilterParams : +Q extends 'pools' ? { query: string } : +never; +/* eslint-enable @stylistic/indent */ + +/* eslint-disable @stylistic/indent */ +export type PaginationSorting = +Q extends 'tokens' ? TokensSorting : +Q extends 'tokens_bridged' ? TokensSorting : +Q extends 'verified_contracts' ? VerifiedContractsSorting : +Q extends 'address_txs' ? TransactionsSorting : +Q extends 'addresses_lookup' ? EnsLookupSorting : +Q extends 'domains_lookup' ? EnsLookupSorting : +Q extends 'validators_stability' ? ValidatorsStabilitySorting : +Q extends 'validators_blackfort' ? ValidatorsBlackfortSorting : +Q extends 'address_mud_records' ? AddressMudRecordsSorting : +never; +/* eslint-enable @stylistic/indent */ diff --git a/lib/api/useApiFetch.tsx b/lib/api/useApiFetch.tsx index ca8807555b..1d58a672bf 100644 --- a/lib/api/useApiFetch.tsx +++ b/lib/api/useApiFetch.tsx @@ -19,8 +19,9 @@ import type { ApiResource, ResourceName, ResourcePathParams } from './resources' export interface Params { pathParams?: ResourcePathParams; - queryParams?: Record | number | boolean | undefined>; + queryParams?: Record | number | boolean | undefined | null>; fetchParams?: Pick; + logError?: boolean; } export default function useApiFetch() { @@ -30,7 +31,7 @@ export default function useApiFetch() { return React.useCallback(( resourceName: R, - { pathParams, queryParams, fetchParams }: Params = {}, + { pathParams, queryParams, fetchParams, logError }: Params = {}, ) => { const apiToken = cookies.get(cookies.NAMES.API_TOKEN); @@ -42,6 +43,7 @@ export default function useApiFetch() { 'x-endpoint': resource.endpoint && isNeedProxy() ? resource.endpoint : undefined, Authorization: resource.endpoint && resource.needAuth ? apiToken : undefined, 'x-csrf-token': withBody && csrfToken ? csrfToken : undefined, + ...resource.headers, ...fetchParams?.headers, }, Boolean) as HeadersInit; @@ -58,7 +60,7 @@ export default function useApiFetch() { }, { resource: resource.path, - omitSentryErrorLog: true, // disable logging of API errors to Sentry + logError, }, resourceName, ); diff --git a/lib/api/useApiInfiniteQuery.tsx b/lib/api/useApiInfiniteQuery.tsx new file mode 100644 index 0000000000..98300d0d64 --- /dev/null +++ b/lib/api/useApiInfiniteQuery.tsx @@ -0,0 +1,42 @@ +import type { InfiniteData, QueryKey, UseInfiniteQueryResult, UseInfiniteQueryOptions } from '@tanstack/react-query'; +import { useInfiniteQuery } from '@tanstack/react-query'; + +import type { PaginatedResources, ResourceError, ResourcePayload } from 'lib/api/resources'; +import useApiFetch from 'lib/api/useApiFetch'; +import type { Params as ApiFetchParams } from 'lib/api/useApiFetch'; + +import { getResourceKey } from './useApiQuery'; + +type TQueryData = ResourcePayload; +type TError = ResourceError; +type TPageParam = ApiFetchParams['queryParams'] | null; + +export interface Params { + resourceName: R; + // eslint-disable-next-line max-len + queryOptions?: Omit, TError, InfiniteData>, TQueryData, QueryKey, TPageParam>, 'queryKey' | 'queryFn' | 'getNextPageParam' | 'initialPageParam'>; + pathParams?: ApiFetchParams['pathParams']; +} + +type ReturnType = UseInfiniteQueryResult>, ResourceError>; + +export default function useApiInfiniteQuery({ + resourceName, + queryOptions, + pathParams, +}: Params): ReturnType { + const apiFetch = useApiFetch(); + + return useInfiniteQuery, TError, InfiniteData>, QueryKey, TPageParam>({ + queryKey: getResourceKey(resourceName, { pathParams }), + queryFn: (context) => { + const queryParams = 'pageParam' in context ? (context.pageParam || undefined) : undefined; + return apiFetch(resourceName, { pathParams, queryParams }) as Promise>; + }, + initialPageParam: null, + getNextPageParam: (lastPage) => { + return lastPage.next_page_params as TPageParam; + }, + ...queryOptions, + }); +} diff --git a/lib/api/useApiQuery.tsx b/lib/api/useApiQuery.tsx index b383e3249c..6cd58c024e 100644 --- a/lib/api/useApiQuery.tsx +++ b/lib/api/useApiQuery.tsx @@ -1,22 +1,20 @@ import type { UseQueryOptions } from '@tanstack/react-query'; import { useQuery } from '@tanstack/react-query'; -import type { ResourceError, ResourceName, ResourcePayload } from './resources'; -import type { Params as ApiFetchParams } from './useApiFetch'; +import type { Params as FetchParams } from 'lib/hooks/useFetch'; + +import type { ResourceError, ResourceName, ResourcePathParams, ResourcePayload } from './resources'; import useApiFetch from './useApiFetch'; -export interface Params - extends ApiFetchParams { - queryOptions?: Omit< - UseQueryOptions, ResourceError, ResourcePayload>, - 'queryKey' | 'queryFn' - >; +export interface Params> { + pathParams?: ResourcePathParams; + queryParams?: Record | number | boolean | undefined>; + fetchParams?: Pick; + queryOptions?: Partial, ResourceError, D>, 'queryFn'>>; + logError?: boolean; } -export function getResourceKey( - resource: R, - { pathParams, queryParams }: Params = {}, -) { +export function getResourceKey(resource: R, { pathParams, queryParams }: Params = {}) { if (pathParams || queryParams) { return [ resource, { ...pathParams, ...queryParams } ]; } @@ -24,24 +22,19 @@ export function getResourceKey( return [ resource ]; } -export default function useApiQuery( +export default function useApiQuery>( resource: R, - { queryOptions, pathParams, queryParams, fetchParams }: Params = {}, + { queryOptions, pathParams, queryParams, fetchParams, logError }: Params = {}, ) { const apiFetch = useApiFetch(); - return useQuery, ResourceError, ResourcePayload>({ - // eslint-disable-next-line @tanstack/query/exhaustive-deps - queryKey: getResourceKey(resource, { pathParams, queryParams }), - queryFn: async() => { + return useQuery, ResourceError, D>({ + queryKey: queryOptions?.queryKey || getResourceKey(resource, { pathParams, queryParams }), + queryFn: async({ signal }) => { // all errors and error typing is handled by react-query // so error response will never go to the data // that's why we are safe here to do type conversion "as Promise>" - return apiFetch(resource, { - pathParams, - queryParams, - fetchParams, - }) as Promise>; + return apiFetch(resource, { pathParams, queryParams, logError, fetchParams: { ...fetchParams, signal } }) as Promise>; }, ...queryOptions, }); diff --git a/lib/blob/guessDataType.ts b/lib/blob/guessDataType.ts new file mode 100644 index 0000000000..ea149236a0 --- /dev/null +++ b/lib/blob/guessDataType.ts @@ -0,0 +1,12 @@ +import filetype from 'magic-bytes.js'; + +import hexToBytes from 'lib/hexToBytes'; + +import removeNonSignificantZeroBytes from './removeNonSignificantZeroBytes'; + +export default function guessDataType(data: string) { + const bytes = hexToBytes(data); + const filteredBytes = removeNonSignificantZeroBytes(bytes); + + return filetype(filteredBytes)[0]; +} diff --git a/lib/blob/index.ts b/lib/blob/index.ts new file mode 100644 index 0000000000..ab178e8231 --- /dev/null +++ b/lib/blob/index.ts @@ -0,0 +1 @@ +export { default as guessDataType } from './guessDataType'; diff --git a/lib/blob/removeNonSignificantZeroBytes.ts b/lib/blob/removeNonSignificantZeroBytes.ts new file mode 100644 index 0000000000..9b25287478 --- /dev/null +++ b/lib/blob/removeNonSignificantZeroBytes.ts @@ -0,0 +1,20 @@ +export default function removeNonSignificantZeroBytes(bytes: Uint8Array) { + return shouldRemoveBytes(bytes) ? bytes.filter((item, index) => index % 32) : bytes; +} + +// check if every 0, 32, 64, etc byte is 0 in the provided array +function shouldRemoveBytes(bytes: Uint8Array) { + let result = true; + + for (let index = 0; index < bytes.length; index += 32) { + const element = bytes[index]; + if (element === 0) { + continue; + } else { + result = false; + break; + } + } + + return result; +} diff --git a/lib/block/getBlockReward.ts b/lib/block/getBlockReward.ts index 3c00ac8d19..dfe2558232 100644 --- a/lib/block/getBlockReward.ts +++ b/lib/block/getBlockReward.ts @@ -3,7 +3,7 @@ import BigNumber from 'bignumber.js'; import type { Block } from 'types/api/block'; export default function getBlockReward(block: Block) { - const txFees = BigNumber(block.tx_fees || 0); + const txFees = BigNumber(block.transaction_fees || 0); const burntFees = BigNumber(block.burnt_fees || 0); const minerReward = block.rewards?.find(({ type }) => type === 'Miner Reward' || type === 'Validator Reward')?.reward; const totalReward = BigNumber(minerReward || 0); diff --git a/lib/bytesToBase64.ts b/lib/bytesToBase64.ts new file mode 100644 index 0000000000..60b23ad437 --- /dev/null +++ b/lib/bytesToBase64.ts @@ -0,0 +1,10 @@ +export default function bytesToBase64(bytes: Uint8Array) { + let binary = ''; + for (const byte of bytes) { + binary += String.fromCharCode(byte); + } + + const base64String = btoa(binary); + + return base64String; +} diff --git a/lib/bytesToHex.ts b/lib/bytesToHex.ts new file mode 100644 index 0000000000..be2a1c2757 --- /dev/null +++ b/lib/bytesToHex.ts @@ -0,0 +1,8 @@ +export default function bytesToBase64(bytes: Uint8Array) { + let result = ''; + for (const byte of bytes) { + result += Number(byte).toString(16).padStart(2, '0'); + } + + return `0x${ result }`; +} diff --git a/lib/capitalizeFirstLetter.ts b/lib/capitalizeFirstLetter.ts new file mode 100644 index 0000000000..ae054d9423 --- /dev/null +++ b/lib/capitalizeFirstLetter.ts @@ -0,0 +1,7 @@ +export default function capitalizeFirstLetter(text: string) { + if (!text || !text.length) { + return ''; + } + + return text.charAt(0).toUpperCase() + text.slice(1); +} diff --git a/lib/contexts/addressHighlight.tsx b/lib/contexts/addressHighlight.tsx index f4bb79e8ff..085e676ecc 100644 --- a/lib/contexts/addressHighlight.tsx +++ b/lib/contexts/addressHighlight.tsx @@ -5,7 +5,6 @@ interface AddressHighlightProviderProps { } interface TAddressHighlightContext { - highlightedAddress: string | null; onMouseEnter: (event: React.MouseEvent) => void; onMouseLeave: (event: React.MouseEvent) => void; } @@ -13,30 +12,40 @@ interface TAddressHighlightContext { export const AddressHighlightContext = React.createContext(null); export function AddressHighlightProvider({ children }: AddressHighlightProviderProps) { - const [ highlightedAddress, setHighlightedAddress ] = React.useState(null); const timeoutId = React.useRef(null); + const hashRef = React.useRef(null); const onMouseEnter = React.useCallback((event: React.MouseEvent) => { const hash = event.currentTarget.getAttribute('data-hash'); if (hash) { + hashRef.current = hash; timeoutId.current = window.setTimeout(() => { - setHighlightedAddress(hash); + // for better performance we update DOM-nodes directly bypassing React reconciliation + const nodes = window.document.querySelectorAll(`[data-hash="${ hashRef.current }"]`); + for (const node of nodes) { + node.classList.add('address-entity_highlighted'); + } }, 100); } }, []); const onMouseLeave = React.useCallback(() => { - setHighlightedAddress(null); + if (hashRef.current) { + const nodes = window.document.querySelectorAll(`[data-hash="${ hashRef.current }"]`); + for (const node of nodes) { + node.classList.remove('address-entity_highlighted'); + } + hashRef.current = null; + } typeof timeoutId.current === 'number' && window.clearTimeout(timeoutId.current); }, []); const value = React.useMemo(() => { return { - highlightedAddress, onMouseEnter, onMouseLeave, }; - }, [ highlightedAddress, onMouseEnter, onMouseLeave ]); + }, [ onMouseEnter, onMouseLeave ]); React.useEffect(() => { return () => { @@ -51,9 +60,9 @@ export function AddressHighlightProvider({ children }: AddressHighlightProviderP ); } -export function useAddressHighlightContext() { +export function useAddressHighlightContext(disabled?: boolean) { const context = React.useContext(AddressHighlightContext); - if (context === undefined) { + if (context === undefined || disabled) { return null; } return context; diff --git a/lib/contexts/app.tsx b/lib/contexts/app.tsx index ec0a25e61b..f7672b5f21 100644 --- a/lib/contexts/app.tsx +++ b/lib/contexts/app.tsx @@ -1,21 +1,19 @@ import React, { createContext, useContext } from 'react'; +import type { Route } from 'nextjs-routes'; import type { Props as PageProps } from 'nextjs/getServerSideProps'; type Props = { children: React.ReactNode; pageProps: PageProps; -} +}; const AppContext = createContext({ cookies: '', referrer: '', - id: '', - height_or_hash: '', - hash: '', - number: '', - q: '', - name: '', + query: {}, + adBannerProvider: null, + apiData: null, }); export function AppContextProvider({ children, pageProps }: Props) { @@ -26,6 +24,6 @@ export function AppContextProvider({ children, pageProps }: Props) { ); } -export function useAppContext() { - return useContext(AppContext); +export function useAppContext() { + return useContext>(AppContext); } diff --git a/lib/contexts/chakra.tsx b/lib/contexts/chakra.tsx index 49aa0971f2..151d893dce 100644 --- a/lib/contexts/chakra.tsx +++ b/lib/contexts/chakra.tsx @@ -6,11 +6,13 @@ import { import type { ChakraProviderProps } from '@chakra-ui/react'; import React from 'react'; +import theme from 'theme/theme'; + interface Props extends ChakraProviderProps { cookies?: string; } -export function ChakraProvider({ cookies, theme, children }: Props) { +export function ChakraProvider({ cookies, children }: Props) { const colorModeManager = typeof cookies === 'string' ? cookieStorageManagerSSR(typeof document !== 'undefined' ? document.cookie : cookies) : diff --git a/lib/contexts/marketplace.tsx b/lib/contexts/marketplace.tsx new file mode 100644 index 0000000000..61cde12624 --- /dev/null +++ b/lib/contexts/marketplace.tsx @@ -0,0 +1,48 @@ +import { useRouter } from 'next/router'; +import React, { createContext, useContext, useEffect, useState, useMemo } from 'react'; + +type Props = { + children: React.ReactNode; +}; + +type TMarketplaceContext = { + isAutoConnectDisabled: boolean; + setIsAutoConnectDisabled: (isAutoConnectDisabled: boolean) => void; +}; + +export const MarketplaceContext = createContext({ + isAutoConnectDisabled: false, + setIsAutoConnectDisabled: () => {}, +}); + +export function MarketplaceContextProvider({ children }: Props) { + const router = useRouter(); + const [ isAutoConnectDisabled, setIsAutoConnectDisabled ] = useState(false); + + useEffect(() => { + const handleRouteChange = () => { + setIsAutoConnectDisabled(false); + }; + + router.events.on('routeChangeStart', handleRouteChange); + + return () => { + router.events.off('routeChangeStart', handleRouteChange); + }; + }, [ router.events ]); + + const value = useMemo(() => ({ + isAutoConnectDisabled, + setIsAutoConnectDisabled, + }), [ isAutoConnectDisabled, setIsAutoConnectDisabled ]); + + return ( + + { children } + + ); +} + +export function useMarketplaceContext() { + return useContext(MarketplaceContext); +} diff --git a/lib/contexts/rewards.tsx b/lib/contexts/rewards.tsx new file mode 100644 index 0000000000..c9a93f16bd --- /dev/null +++ b/lib/contexts/rewards.tsx @@ -0,0 +1,289 @@ +import { useBoolean } from '@chakra-ui/react'; +import type { UseQueryResult } from '@tanstack/react-query'; +import { useQueryClient } from '@tanstack/react-query'; +import { useRouter } from 'next/router'; +import React, { createContext, useContext, useEffect, useMemo, useCallback } from 'react'; +import { useSignMessage } from 'wagmi'; + +import type { + RewardsUserBalancesResponse, RewardsUserDailyCheckResponse, + RewardsNonceResponse, RewardsCheckUserResponse, + RewardsLoginResponse, RewardsCheckRefCodeResponse, + RewardsUserDailyClaimResponse, RewardsUserReferralsResponse, + RewardsConfigResponse, +} from 'types/api/rewards'; + +import config from 'configs/app'; +import type { ResourceError } from 'lib/api/resources'; +import useApiFetch from 'lib/api/useApiFetch'; +import useApiQuery, { getResourceKey } from 'lib/api/useApiQuery'; +import { YEAR } from 'lib/consts'; +import * as cookies from 'lib/cookies'; +import decodeJWT from 'lib/decodeJWT'; +import getErrorMessage from 'lib/errors/getErrorMessage'; +import getErrorObjPayload from 'lib/errors/getErrorObjPayload'; +import useToast from 'lib/hooks/useToast'; +import getQueryParamString from 'lib/router/getQueryParamString'; +import removeQueryParam from 'lib/router/removeQueryParam'; +import useAccount from 'lib/web3/useAccount'; +import useProfileQuery from 'ui/snippets/auth/useProfileQuery'; + +const feature = config.features.rewards; + +type ContextQueryResult = + Pick>, 'data' | 'isLoading' | 'refetch' | 'isPending' | 'isFetching' | 'isError'>; + +type TRewardsContext = { + balancesQuery: ContextQueryResult; + dailyRewardQuery: ContextQueryResult; + referralsQuery: ContextQueryResult; + rewardsConfigQuery: ContextQueryResult; + checkUserQuery: ContextQueryResult; + apiToken: string | undefined; + isInitialized: boolean; + isLoginModalOpen: boolean; + openLoginModal: () => void; + closeLoginModal: () => void; + login: (refCode: string) => Promise<{ isNewUser?: boolean; invalidRefCodeError?: boolean }>; + claim: () => Promise; +}; + +const defaultQueryResult = { + data: undefined, + isLoading: false, + isPending: false, + isFetching: false, + isError: false, + refetch: () => Promise.resolve({} as never), +}; + +const initialState = { + balancesQuery: defaultQueryResult, + dailyRewardQuery: defaultQueryResult, + referralsQuery: defaultQueryResult, + rewardsConfigQuery: defaultQueryResult, + checkUserQuery: defaultQueryResult, + apiToken: undefined, + isInitialized: false, + isLoginModalOpen: false, + openLoginModal: () => {}, + closeLoginModal: () => {}, + login: async() => ({}), + claim: async() => {}, +}; + +const RewardsContext = createContext(initialState); + +// Message to sign for the rewards program +function getMessageToSign(address: string, nonce: string, isLogin?: boolean, refCode?: string) { + const signInText = 'Sign-In for the Blockscout Merits program.'; + const signUpText = 'Sign-Up for the Blockscout Merits program. I accept Terms of Service: https://merits.blockscout.com/terms. I love capybaras.'; + const referralText = refCode ? ` Referral code: ${ refCode }` : ''; + const body = isLogin ? signInText : signUpText + referralText; + + const urlObj = window.location.hostname === 'localhost' && feature.isEnabled ? + new URL(feature.api.endpoint) : + window.location; + + return [ + `${ urlObj.hostname } wants you to sign in with your Ethereum account:`, + address, + '', + body, + '', + `URI: ${ urlObj.origin }`, + 'Version: 1', + `Chain ID: ${ config.chain.id }`, + `Nonce: ${ nonce }`, + `Issued At: ${ new Date().toISOString() }`, + `Expiration Time: ${ new Date(Date.now() + YEAR).toISOString() }`, + ].join('\n'); +} + +// Get the registered address from the JWT token +function getRegisteredAddress(token: string) { + const decodedToken = decodeJWT(token); + return decodedToken?.payload.sub; +} + +type Props = { + children: React.ReactNode; +}; + +export function RewardsContextProvider({ children }: Props) { + const router = useRouter(); + const queryClient = useQueryClient(); + const apiFetch = useApiFetch(); + const toast = useToast(); + const { address } = useAccount(); + const { signMessageAsync } = useSignMessage(); + const profileQuery = useProfileQuery(); + + const [ isLoginModalOpen, setIsLoginModalOpen ] = useBoolean(false); + const [ isInitialized, setIsInitialized ] = useBoolean(false); + const [ apiToken, setApiToken ] = React.useState(); + + // Initialize state with the API token from cookies + useEffect(() => { + if (!profileQuery.isLoading) { + const token = cookies.get(cookies.NAMES.REWARDS_API_TOKEN); + const registeredAddress = getRegisteredAddress(token || ''); + if (registeredAddress === profileQuery.data?.address_hash) { + setApiToken(token); + } + setIsInitialized.on(); + } + }, [ setIsInitialized, profileQuery ]); + + // Save the API token to cookies and state + const saveApiToken = useCallback((token: string | undefined) => { + if (token) { + cookies.set(cookies.NAMES.REWARDS_API_TOKEN, token); + } else { + cookies.remove(cookies.NAMES.REWARDS_API_TOKEN); + } + setApiToken(token); + }, []); + + const [ queryOptions, fetchParams ] = useMemo(() => [ + { enabled: Boolean(apiToken) && feature.isEnabled }, + { headers: { Authorization: `Bearer ${ apiToken }` } }, + ], [ apiToken ]); + + const balancesQuery = useApiQuery('rewards_user_balances', { queryOptions, fetchParams }); + const dailyRewardQuery = useApiQuery('rewards_user_daily_check', { queryOptions, fetchParams }); + const referralsQuery = useApiQuery('rewards_user_referrals', { queryOptions, fetchParams }); + const rewardsConfigQuery = useApiQuery('rewards_config', { queryOptions: { enabled: feature.isEnabled } }); + const checkUserQuery = useApiQuery('rewards_check_user', { queryOptions: { enabled: feature.isEnabled }, pathParams: { address } }); + + // Reset queries when the API token is removed + useEffect(() => { + if (isInitialized && !apiToken) { + queryClient.resetQueries({ queryKey: getResourceKey('rewards_user_balances'), exact: true }); + queryClient.resetQueries({ queryKey: getResourceKey('rewards_user_daily_check'), exact: true }); + queryClient.resetQueries({ queryKey: getResourceKey('rewards_user_referrals'), exact: true }); + } + }, [ isInitialized, apiToken, queryClient ]); + + // Handle 401 error + useEffect(() => { + if (apiToken && balancesQuery.error?.status === 401) { + saveApiToken(undefined); + } + }, [ balancesQuery.error, apiToken, saveApiToken ]); + + // Check if the profile address is the same as the registered address + useEffect(() => { + const registeredAddress = getRegisteredAddress(apiToken || ''); + if (registeredAddress && !profileQuery.isLoading && profileQuery.data?.address_hash !== registeredAddress) { + setApiToken(undefined); + } + }, [ apiToken, profileQuery, setApiToken ]); + + // Handle referral code in the URL + useEffect(() => { + const refCode = getQueryParamString(router.query.ref); + if (refCode && isInitialized) { + cookies.set(cookies.NAMES.REWARDS_REFERRAL_CODE, refCode); + removeQueryParam(router, 'ref'); + if (!apiToken) { + setIsLoginModalOpen.on(); + } + } + }, [ router, apiToken, isInitialized, setIsLoginModalOpen ]); + + const errorToast = useCallback((error: unknown) => { + const apiError = getErrorObjPayload<{ message: string }>(error); + toast({ + position: 'top-right', + title: 'Error', + description: apiError?.message || getErrorMessage(error) || 'Something went wrong. Try again later.', + status: 'error', + variant: 'subtle', + isClosable: true, + }); + }, [ toast ]); + + // Login to the rewards program + const login = useCallback(async(refCode: string) => { + try { + if (!address) { + throw new Error(); + } + const [ nonceResponse, checkCodeResponse ] = await Promise.all([ + apiFetch('rewards_nonce') as Promise, + refCode ? + apiFetch('rewards_check_ref_code', { pathParams: { code: refCode } }) as Promise : + Promise.resolve({ valid: true }), + ]); + if (!checkCodeResponse.valid) { + return { invalidRefCodeError: true }; + } + const message = getMessageToSign(address, nonceResponse.nonce, checkUserQuery.data?.exists, refCode); + const signature = await signMessageAsync({ message }); + const loginResponse = await apiFetch('rewards_login', { + fetchParams: { + method: 'POST', + body: { + nonce: nonceResponse.nonce, + message, + signature, + }, + }, + }) as RewardsLoginResponse; + saveApiToken(loginResponse.token); + return { isNewUser: loginResponse.created }; + } catch (_error) { + errorToast(_error); + throw _error; + } + }, [ apiFetch, address, signMessageAsync, errorToast, saveApiToken, checkUserQuery ]); + + // Claim daily reward + const claim = useCallback(async() => { + try { + await apiFetch('rewards_user_daily_claim', { + fetchParams: { + method: 'POST', + ...fetchParams, + }, + }) as RewardsUserDailyClaimResponse; + } catch (_error) { + errorToast(_error); + throw _error; + } + }, [ apiFetch, errorToast, fetchParams ]); + + const value = useMemo(() => { + if (!feature.isEnabled) { + return initialState; + } + return { + balancesQuery, + dailyRewardQuery, + referralsQuery, + rewardsConfigQuery, + checkUserQuery, + apiToken, + isInitialized, + isLoginModalOpen, + openLoginModal: setIsLoginModalOpen.on, + closeLoginModal: setIsLoginModalOpen.off, + login, + claim, + }; + }, [ + isLoginModalOpen, setIsLoginModalOpen, balancesQuery, dailyRewardQuery, checkUserQuery, + apiToken, login, claim, referralsQuery, rewardsConfigQuery, isInitialized, + ]); + + return ( + + { children } + + ); +} + +export function useRewardsContext() { + return useContext(RewardsContext); +} diff --git a/lib/contexts/settings.tsx b/lib/contexts/settings.tsx new file mode 100644 index 0000000000..054248acf9 --- /dev/null +++ b/lib/contexts/settings.tsx @@ -0,0 +1,56 @@ +import React from 'react'; + +import { ADDRESS_FORMATS, type AddressFormat } from 'types/views/address'; + +import * as cookies from 'lib/cookies'; + +import { useAppContext } from './app'; + +interface SettingsProviderProps { + children: React.ReactNode; +} + +interface TSettingsContext { + addressFormat: AddressFormat; + toggleAddressFormat: () => void; +} + +export const SettingsContext = React.createContext(null); + +export function SettingsContextProvider({ children }: SettingsProviderProps) { + const { cookies: appCookies } = useAppContext(); + const initialAddressFormat = cookies.get(cookies.NAMES.ADDRESS_FORMAT, appCookies); + + const [ addressFormat, setAddressFormat ] = React.useState( + initialAddressFormat && ADDRESS_FORMATS.includes(initialAddressFormat as AddressFormat) ? initialAddressFormat as AddressFormat : 'base16', + ); + + const toggleAddressFormat = React.useCallback(() => { + setAddressFormat(prev => { + const nextValue = prev === 'base16' ? 'bech32' : 'base16'; + cookies.set(cookies.NAMES.ADDRESS_FORMAT, nextValue); + return nextValue; + }); + }, []); + + const value = React.useMemo(() => { + return { + addressFormat, + toggleAddressFormat, + }; + }, [ addressFormat, toggleAddressFormat ]); + + return ( + + { children } + + ); +} + +export function useSettingsContext(disabled?: boolean) { + const context = React.useContext(SettingsContext); + if (context === undefined || disabled) { + return null; + } + return context; +} diff --git a/lib/contracts/formatLanguageName.tsx b/lib/contracts/formatLanguageName.tsx new file mode 100644 index 0000000000..e1e9c6d233 --- /dev/null +++ b/lib/contracts/formatLanguageName.tsx @@ -0,0 +1,3 @@ +export default function formatLanguageName(language: string) { + return language.replace(/_/g, ' ').replace(/\b\w/g, char => char.toUpperCase()); +} diff --git a/lib/contracts/licenses.ts b/lib/contracts/licenses.ts new file mode 100644 index 0000000000..123149e294 --- /dev/null +++ b/lib/contracts/licenses.ts @@ -0,0 +1,88 @@ +import type { ContractLicense } from 'types/client/contract'; + +export const CONTRACT_LICENSES: Array = [ + { + type: 'none', + label: 'None', + title: 'No License', + url: 'https://choosealicense.com/no-permission/', + }, + { + type: 'unlicense', + label: 'Unlicense', + title: 'The Unlicense', + url: 'https://choosealicense.com/licenses/unlicense/', + }, + { + type: 'mit', + label: 'MIT', + title: 'MIT License', + url: 'https://choosealicense.com/licenses/mit/', + }, + { + type: 'gnu_gpl_v2', + label: 'GNU GPLv2', + title: 'GNU General Public License v2.0', + url: 'https://choosealicense.com/licenses/gpl-2.0/', + }, + { + type: 'gnu_gpl_v3', + label: 'GNU GPLv3', + title: 'GNU General Public License v3.0', + url: 'https://choosealicense.com/licenses/gpl-3.0/', + }, + { + type: 'gnu_lgpl_v2_1', + label: 'GNU LGPLv2.1', + title: 'GNU Lesser General Public License v2.1', + url: 'https://choosealicense.com/licenses/lgpl-2.1/', + }, + { + type: 'gnu_lgpl_v3', + label: 'GNU LGPLv3', + title: 'GNU Lesser General Public License v3.0', + url: 'https://choosealicense.com/licenses/lgpl-3.0/', + }, + { + type: 'bsd_2_clause', + label: 'BSD-2-Clause', + title: 'BSD 2-clause "Simplified" license', + url: 'https://choosealicense.com/licenses/bsd-2-clause/', + }, + { + type: 'bsd_3_clause', + label: 'BSD-3-Clause', + title: 'BSD 3-clause "New" Or "Revised" license', + url: 'https://choosealicense.com/licenses/bsd-3-clause/', + }, + { + type: 'mpl_2_0', + label: 'MPL-2.0', + title: 'Mozilla Public License 2.0', + url: 'https://choosealicense.com/licenses/mpl-2.0/', + }, + { + type: 'osl_3_0', + label: 'OSL-3.0', + title: 'Open Software License 3.0', + url: 'https://choosealicense.com/licenses/osl-3.0/', + }, + { + type: 'apache_2_0', + label: 'Apache', + title: 'Apache 2.0', + url: 'https://choosealicense.com/licenses/apache-2.0/', + }, + { + type: 'gnu_agpl_v3', + label: 'GNU AGPLv3', + title: 'GNU Affero General Public License', + url: 'https://choosealicense.com/licenses/agpl-3.0/', + }, + { + type: 'bsl_1_1', + label: 'BSL 1.1', + title: 'Business Source License', + url: 'https://mariadb.com/bsl11/', + }, +]; diff --git a/lib/cookies.ts b/lib/cookies.ts index 7f3867f9e6..fc4f8cc144 100644 --- a/lib/cookies.ts +++ b/lib/cookies.ts @@ -3,18 +3,20 @@ import Cookies from 'js-cookie'; import isBrowser from './isBrowser'; export enum NAMES { - NAV_BAR_COLLAPSED='nav_bar_collapsed', - API_TOKEN='_explorer_key', - INVALID_SESSION='invalid_session', - CONFIRM_EMAIL_PAGE_VIEWED='confirm_email_page_viewed', - TXS_SORT='txs_sort', - COLOR_MODE='chakra-ui-color-mode', - COLOR_MODE_HEX='chakra-ui-color-mode-hex', - INDEXING_ALERT='indexing_alert', - ADBLOCK_DETECTED='adblock_detected', - MIXPANEL_DEBUG='_mixpanel_debug', - ADDRESS_NFT_DISPLAY_TYPE='address_nft_display_type', - UUID='uuid', + NAV_BAR_COLLAPSED = 'nav_bar_collapsed', + API_TOKEN = '_explorer_key', + REWARDS_API_TOKEN = 'rewards_api_token', + REWARDS_REFERRAL_CODE = 'rewards_ref_code', + TXS_SORT = 'txs_sort', + COLOR_MODE = 'chakra-ui-color-mode', + COLOR_MODE_HEX = 'chakra-ui-color-mode-hex', + ADDRESS_IDENTICON_TYPE = 'address_identicon_type', + ADDRESS_FORMAT = 'address_format', + INDEXING_ALERT = 'indexing_alert', + ADBLOCK_DETECTED = 'adblock_detected', + MIXPANEL_DEBUG = '_mixpanel_debug', + ADDRESS_NFT_DISPLAY_TYPE = 'address_nft_display_type', + UUID = 'uuid', } export function get(name?: NAMES | undefined | null, serverCookie?: string) { @@ -27,12 +29,16 @@ export function get(name?: NAMES | undefined | null, serverCookie?: string) { } } -export function set(name: string, value: string, attributes: Cookies.CookieAttributes = {}) { +export function set(name: NAMES, value: string, attributes: Cookies.CookieAttributes = {}) { attributes.path = '/'; return Cookies.set(name, value, attributes); } +export function remove(name: NAMES, attributes: Cookies.CookieAttributes = {}) { + return Cookies.remove(name, attributes); +} + export function getFromCookieString(cookieString: string, name?: NAMES | undefined | null) { return cookieString.split(`${ name }=`)[1]?.split(';')[0]; } diff --git a/lib/date/dayjs.ts b/lib/date/dayjs.ts index 06b53c01d6..e0191ce1e0 100644 --- a/lib/date/dayjs.ts +++ b/lib/date/dayjs.ts @@ -38,6 +38,7 @@ dayjs.extend(minMax); dayjs.updateLocale('en', { formats: { llll: `MMM DD YYYY HH:mm:ss A (Z${ nbsp }UTC)`, + lll: 'MMM D, YYYY h:mm A', }, relativeTime: { s: '1s', diff --git a/lib/decodeJWT.ts b/lib/decodeJWT.ts new file mode 100644 index 0000000000..d94e21d4de --- /dev/null +++ b/lib/decodeJWT.ts @@ -0,0 +1,47 @@ +interface JWTHeader { + alg: string; + typ?: string; + [key: string]: unknown; +} + +interface JWTPayload { + [key: string]: unknown; +} + +const base64UrlDecode = (str: string): string => { + // Replace characters according to Base64Url standard + str = str.replace(/-/g, '+').replace(/_/g, '/'); + + // Add padding '=' characters for correct decoding + const pad = str.length % 4; + if (pad) { + str += '='.repeat(4 - pad); + } + + // Decode from Base64 to string + const decodedStr = atob(str); + + return decodedStr; +}; + +export default function decodeJWT(token: string): { header: JWTHeader; payload: JWTPayload; signature: string } | null { + try { + const parts = token.split('.'); + + if (parts.length !== 3) { + throw new Error('Invalid JWT format'); + } + + const [ encodedHeader, encodedPayload, signature ] = parts; + + const headerJson = base64UrlDecode(encodedHeader); + const payloadJson = base64UrlDecode(encodedPayload); + + const header = JSON.parse(headerJson) as JWTHeader; + const payload = JSON.parse(payloadJson) as JWTPayload; + + return { header, payload, signature }; + } catch (error) { + return null; + } +} diff --git a/lib/errors/getErrorMessage.ts b/lib/errors/getErrorMessage.ts new file mode 100644 index 0000000000..5e15f29a1c --- /dev/null +++ b/lib/errors/getErrorMessage.ts @@ -0,0 +1,6 @@ +import getErrorObj from './getErrorObj'; + +export default function getErrorMessage(error: unknown): string | undefined { + const errorObj = getErrorObj(error); + return errorObj && 'message' in errorObj && typeof errorObj.message === 'string' ? errorObj.message : undefined; +} diff --git a/lib/errors/throwOnResourceLoadError.ts b/lib/errors/throwOnResourceLoadError.ts index 32e5169fc7..64075213cf 100644 --- a/lib/errors/throwOnResourceLoadError.ts +++ b/lib/errors/throwOnResourceLoadError.ts @@ -8,7 +8,7 @@ type Params = ({ error: null; }) & { resource?: ResourceName; -} +}; export const RESOURCE_LOAD_ERROR_MESSAGE = 'Resource load error'; diff --git a/lib/getFilterValuesFromQuery.ts b/lib/getFilterValuesFromQuery.ts index ce5eddeb63..1ba9243d63 100644 --- a/lib/getFilterValuesFromQuery.ts +++ b/lib/getFilterValuesFromQuery.ts @@ -1,14 +1,10 @@ +import getValuesArrayFromQuery from './getValuesArrayFromQuery'; + export default function getFilterValue(filterValues: ReadonlyArray, val: string | Array | undefined) { - if (val === undefined) { - return; - } + const valArray = getValuesArrayFromQuery(val); - const valArray = []; - if (typeof val === 'string') { - valArray.push(...val.split(',')); - } - if (Array.isArray(val)) { - val.forEach(el => valArray.push(...el.split(','))); + if (!valArray) { + return; } return valArray.filter(el => filterValues.includes(el as unknown as FilterType)) as unknown as Array; diff --git a/lib/getItemIndex.ts b/lib/getItemIndex.ts new file mode 100644 index 0000000000..6103c23ddd --- /dev/null +++ b/lib/getItemIndex.ts @@ -0,0 +1,5 @@ +const DEFAULT_PAGE_SIZE = 50; + +export default function getItemIndex(index: number, page: number, pageSize: number = DEFAULT_PAGE_SIZE) { + return (page - 1) * pageSize + index + 1; +}; diff --git a/lib/getValuesArrayFromQuery.ts b/lib/getValuesArrayFromQuery.ts new file mode 100644 index 0000000000..647eb03cb3 --- /dev/null +++ b/lib/getValuesArrayFromQuery.ts @@ -0,0 +1,18 @@ +export default function getValuesArrayFromQuery(val: string | Array | undefined) { + if (val === undefined) { + return; + } + + const valArray = []; + if (typeof val === 'string') { + valArray.push(...val.split(',')); + } + if (Array.isArray(val)) { + if (!val.length) { + return; + } + val.forEach(el => valArray.push(...el.split(','))); + } + + return valArray; +} diff --git a/lib/growthbook/init.ts b/lib/growthbook/init.ts index 15188c8c7c..d98b2b94b7 100644 --- a/lib/growthbook/init.ts +++ b/lib/growthbook/init.ts @@ -7,7 +7,6 @@ import { STORAGE_KEY, STORAGE_LIMIT } from './consts'; export interface GrowthBookFeatures { test_value: string; - marketplace_exp: boolean; } export const growthBook = (() => { diff --git a/lib/hexToBase64.ts b/lib/hexToBase64.ts new file mode 100644 index 0000000000..6fced2dc39 --- /dev/null +++ b/lib/hexToBase64.ts @@ -0,0 +1,8 @@ +import bytesToBase64 from './bytesToBase64'; +import hexToBytes from './hexToBytes'; + +export default function hexToBase64(hex: string) { + const bytes = hexToBytes(hex); + + return bytesToBase64(bytes); +} diff --git a/lib/hexToBytes.ts b/lib/hexToBytes.ts index 382fd777d3..d42c931930 100644 --- a/lib/hexToBytes.ts +++ b/lib/hexToBytes.ts @@ -1,7 +1,9 @@ +// hex can be with prefix - `0x{string}` - or without it - `{string}` export default function hexToBytes(hex: string) { const bytes = []; - for (let c = 0; c < hex.length; c += 2) { + const startIndex = hex.startsWith('0x') ? 2 : 0; + for (let c = startIndex; c < hex.length; c += 2) { bytes.push(parseInt(hex.substring(c, c + 2), 16)); } - return bytes; + return new Uint8Array(bytes); } diff --git a/lib/hexToUtf8.ts b/lib/hexToUtf8.ts index 8766ee25cc..95e40ba090 100644 --- a/lib/hexToUtf8.ts +++ b/lib/hexToUtf8.ts @@ -2,7 +2,7 @@ import hexToBytes from 'lib/hexToBytes'; export default function hexToUtf8(hex: string) { const utf8decoder = new TextDecoder(); - const bytes = new Uint8Array(hexToBytes(hex)); + const bytes = hexToBytes(hex); return utf8decoder.decode(bytes); } diff --git a/lib/hooks/useAdblockDetect.tsx b/lib/hooks/useAdblockDetect.tsx index e0ee312647..a3b42de1dd 100644 --- a/lib/hooks/useAdblockDetect.tsx +++ b/lib/hooks/useAdblockDetect.tsx @@ -1,22 +1,46 @@ import { useEffect } from 'react'; +import type { AdBannerProviders } from 'types/client/adProviders'; + +import config from 'configs/app'; import { useAppContext } from 'lib/contexts/app'; import * as cookies from 'lib/cookies'; import isBrowser from 'lib/isBrowser'; +const DEFAULT_URL = 'https://request-global.czilladx.com'; + +// in general, detect should work with any ad-provider url (that is alive) +// but we see some false-positive results in certain browsers +const TEST_URLS: Record = { + slise: 'https://v1.slise.xyz/serve', + coinzilla: 'https://request-global.czilladx.com', + adbutler: 'https://servedbyadbutler.com/app.js', + hype: 'https://api.hypelab.com/v1/scripts/hp-sdk.js', + // I don't have an url for getit to test + // getit: DEFAULT_URL, + none: DEFAULT_URL, +}; + +const feature = config.features.adsBanner; + export default function useAdblockDetect() { const hasAdblockCookie = cookies.get(cookies.NAMES.ADBLOCK_DETECTED, useAppContext().cookies); + const provider = feature.isEnabled && feature.provider; useEffect(() => { - if (isBrowser() && !hasAdblockCookie) { - const url = 'https://request-global.czilladx.com'; + if (isBrowser() && !hasAdblockCookie && provider) { + const url = TEST_URLS[provider] || DEFAULT_URL; fetch(url, { method: 'HEAD', mode: 'no-cors', cache: 'no-store', - }).catch(() => { - cookies.set(cookies.NAMES.ADBLOCK_DETECTED, 'true', { expires: 1 }); - }); + }) + .then(() => { + cookies.set(cookies.NAMES.ADBLOCK_DETECTED, 'false', { expires: 1 }); + }) + .catch(() => { + cookies.set(cookies.NAMES.ADBLOCK_DETECTED, 'true', { expires: 1 }); + }); } // eslint-disable-next-line react-hooks/exhaustive-deps }, []); diff --git a/lib/hooks/useAddressProfileApiQuery.tsx b/lib/hooks/useAddressProfileApiQuery.tsx new file mode 100644 index 0000000000..ef39555d43 --- /dev/null +++ b/lib/hooks/useAddressProfileApiQuery.tsx @@ -0,0 +1,44 @@ +import { useQuery } from '@tanstack/react-query'; +import * as v from 'valibot'; + +import config from 'configs/app'; +import type { ResourceError } from 'lib/api/resources'; +import useFetch from 'lib/hooks/useFetch'; + +const feature = config.features.addressProfileAPI; + +type AddressInfoApiQueryResponse = v.InferOutput; + +const AddressInfoSchema = v.object({ + user_profile: v.object({ + username: v.union([ v.string(), v.null() ]), + }), +}); + +const ERROR_NAME = 'Invalid response schema'; + +export default function useAddressProfileApiQuery(hash: string | undefined, isEnabled = true) { + const fetch = useFetch(); + + return useQuery, AddressInfoApiQueryResponse>({ + queryKey: [ 'username_api', hash ], + queryFn: async() => { + if (!feature.isEnabled || !hash) { + return Promise.reject(); + } + + return fetch(feature.apiUrlTemplate.replace('{address}', hash)); + }, + enabled: isEnabled && Boolean(hash), + refetchOnMount: false, + select: (response) => { + const parsedResponse = v.safeParse(AddressInfoSchema, response); + + if (!parsedResponse.success) { + throw Error(ERROR_NAME); + } + + return parsedResponse.output; + }, + }); +} diff --git a/lib/hooks/useContractTabs.tsx b/lib/hooks/useContractTabs.tsx deleted file mode 100644 index ca02f16ddd..0000000000 --- a/lib/hooks/useContractTabs.tsx +++ /dev/null @@ -1,37 +0,0 @@ -import React from 'react'; - -import type { Address } from 'types/api/address'; - -import ContractCode from 'ui/address/contract/ContractCode'; -import ContractRead from 'ui/address/contract/ContractRead'; -import ContractWrite from 'ui/address/contract/ContractWrite'; - -export default function useContractTabs(data: Address | undefined) { - return React.useMemo(() => { - return [ - { id: 'contact_code', title: 'Code', component: }, - // this is not implemented in api yet - // data?.has_decompiled_code ? - // { id: 'contact_decompiled_code', title: 'Decompiled code', component:
Decompiled code
} : - // undefined, - data?.has_methods_read ? - { id: 'read_contract', title: 'Read contract', component: } : - undefined, - data?.has_methods_read_proxy ? - { id: 'read_proxy', title: 'Read proxy', component: } : - undefined, - data?.has_custom_methods_read ? - { id: 'read_custom_methods', title: 'Read custom', component: } : - undefined, - data?.has_methods_write ? - { id: 'write_contract', title: 'Write contract', component: } : - undefined, - data?.has_methods_write_proxy ? - { id: 'write_proxy', title: 'Write proxy', component: } : - undefined, - data?.has_custom_methods_write ? - { id: 'write_custom_methods', title: 'Write custom', component: } : - undefined, - ].filter(Boolean); - }, [ data ]); -} diff --git a/lib/hooks/useFetch.tsx b/lib/hooks/useFetch.tsx index c32ecb1b21..ea1977f251 100644 --- a/lib/hooks/useFetch.tsx +++ b/lib/hooks/useFetch.tsx @@ -1,116 +1,97 @@ -import * as Sentry from '@sentry/react'; import React from 'react'; -import isBodyAllowed from "lib/api/isBodyAllowed"; -import type { - ResourceError, - ResourceName, - ResourcePath, -} from "lib/api/resources"; -import { boolApiNames } from "lib/api/resources"; +import isBodyAllowed from 'lib/api/isBodyAllowed'; +import type { ResourceError, ResourcePath } from 'lib/api/resources'; +import { useRollbar } from 'lib/rollbar'; export interface Params { - method?: RequestInit["method"]; - headers?: RequestInit["headers"]; - signal?: RequestInit["signal"]; + method?: RequestInit['method']; + headers?: RequestInit['headers']; + signal?: RequestInit['signal']; body?: Record | FormData; credentials?: RequestCredentials; } interface Meta { resource?: ResourcePath; - omitSentryErrorLog?: boolean; + logError?: boolean; } export default function useFetch() { - return React.useCallback( - ( - path: string, - params?: Params, - meta?: Meta, - resourceName?: ResourceName, - ): Promise> => { - const _body = params?.body; - const isFormData = _body instanceof FormData; - const withBody = isBodyAllowed(params?.method); - - const body: FormData | string | undefined = (() => { - if (!withBody) { - return; + const rollbar = useRollbar(); + + return React.useCallback((path: string, params?: Params, meta?: Meta): Promise> => { + const _body = params?.body; + const isFormData = _body instanceof FormData; + const withBody = isBodyAllowed(params?.method); + + const body: FormData | string | undefined = (() => { + if (!withBody) { + return; + } + + if (isFormData) { + return _body; + } + + return JSON.stringify(_body); + })(); + + const reqParams = { + ...params, + body, + headers: { + ...(withBody && !isFormData ? { 'Content-type': 'application/json' } : undefined), + ...params?.headers, + }, + }; + + return fetch(path, reqParams).then(response => { + + const isJson = response.headers.get('content-type')?.includes('application/json'); + + if (!response.ok) { + const error = { + status: response.status, + statusText: response.statusText, + }; + + if (meta?.logError && rollbar) { + rollbar.warn('Client fetch failed', { + resource: meta?.resource, + status_code: error.status, + status_text: error.statusText, + }); } - if (isFormData) { - return _body; + if (!isJson) { + return response.text().then( + (textError) => Promise.reject({ + payload: textError, + status: response.status, + statusText: response.statusText, + }), + ); } - return JSON.stringify(_body); - })(); - - const reqParams = { - ...params, - body, - headers: { - ...(withBody && !isFormData ? - { "Content-type": "application/json" } : - undefined), - ...params?.headers, - }, - }; - - return fetch(path, reqParams).then((response) => { - if (!response.ok) { - const error = { + return response.json().then( + (jsonError) => Promise.reject({ + payload: jsonError as Error, status: response.status, statusText: response.statusText, - }; - - if (!meta?.omitSentryErrorLog) { - Sentry.captureException(new Error("Client fetch failed"), { - tags: { - source: "fetch", - "source.resource": meta?.resource, - "status.code": error.status, - "status.text": error.statusText, - }, - }); - } - - return response.json().then( - (jsonError) => - Promise.reject({ - payload: jsonError as Error, - status: response.status, - statusText: response.statusText, - }), - () => { - return Promise.reject(error); - }, - ); - } else { - if (resourceName && boolApiNames.includes(resourceName)) { - return new Promise((resolve, reject) => { - response.json().then((res: any) => { - if (resourceName === "bool_rpc") { - if (!res.error) { - resolve(res.result); - } else { - reject(res.error?.message); - } - } else { - if (res.code === "000") { - resolve(res.data); - } else { - reject(new Error(res.message)); - } - } - }); - }); - } else { - return response.json() as Promise; - } + }), + () => { + return Promise.reject(error); + }, + ); + + } else { + if (isJson) { + return response.json() as Promise; } - }); - }, - [], - ); + + return Promise.resolve() as Promise; + } + }); + }, [ rollbar ]); } diff --git a/lib/hooks/useFetchProfileInfo.tsx b/lib/hooks/useFetchProfileInfo.tsx deleted file mode 100644 index 4df2f8d57b..0000000000 --- a/lib/hooks/useFetchProfileInfo.tsx +++ /dev/null @@ -1,11 +0,0 @@ -import useApiQuery from 'lib/api/useApiQuery'; -import * as cookies from 'lib/cookies'; - -export default function useFetchProfileInfo() { - return useApiQuery('user_info', { - queryOptions: { - refetchOnMount: false, - enabled: Boolean(cookies.get(cookies.NAMES.API_TOKEN)), - }, - }); -} diff --git a/lib/hooks/useGetCsrfToken.tsx b/lib/hooks/useGetCsrfToken.tsx index 297f1567c2..e1db1e595f 100644 --- a/lib/hooks/useGetCsrfToken.tsx +++ b/lib/hooks/useGetCsrfToken.tsx @@ -1,4 +1,3 @@ -import * as Sentry from '@sentry/react'; import { useQuery } from '@tanstack/react-query'; import buildUrl from 'lib/api/buildUrl'; @@ -6,11 +5,13 @@ import isNeedProxy from 'lib/api/isNeedProxy'; import { getResourceKey } from 'lib/api/useApiQuery'; import * as cookies from 'lib/cookies'; import useFetch from 'lib/hooks/useFetch'; +import { useRollbar } from 'lib/rollbar'; export default function useGetCsrfToken() { const nodeApiFetch = useFetch(); + const rollbar = useRollbar(); - useQuery({ + return useQuery({ queryKey: getResourceKey('csrf'), queryFn: async() => { if (!isNeedProxy()) { @@ -19,12 +20,11 @@ export default function useGetCsrfToken() { const csrfFromHeader = apiResponse.headers.get('x-bs-account-csrf'); if (!csrfFromHeader) { - Sentry.captureException(new Error('Client fetch failed'), { tags: { - source: 'fetch', - 'source.resource': 'csrf', - 'status.code': 500, - 'status.text': 'Unable to obtain csrf token from header', - } }); + rollbar?.warn('Client fetch failed', { + resource: 'csrf', + status_code: 500, + status_text: 'Unable to obtain csrf token from header', + }); return; } diff --git a/lib/hooks/useGraphLinks.tsx b/lib/hooks/useGraphLinks.tsx new file mode 100644 index 0000000000..b8f83b4cf8 --- /dev/null +++ b/lib/hooks/useGraphLinks.tsx @@ -0,0 +1,19 @@ +import { useQuery } from '@tanstack/react-query'; + +import config from 'configs/app'; +import type { ResourceError } from 'lib/api/resources'; +import useFetch from 'lib/hooks/useFetch'; + +const feature = config.features.marketplace; + +export default function useGraphLinks() { + const fetch = useFetch(); + + return useQuery, Record>>({ + queryKey: [ 'graph-links' ], + queryFn: async() => fetch((feature.isEnabled && feature.graphLinksUrl) ? feature.graphLinksUrl : '', undefined, { resource: 'graph-links' }), + enabled: feature.isEnabled && Boolean(feature.graphLinksUrl), + staleTime: Infinity, + placeholderData: {}, + }); +} diff --git a/lib/hooks/useHasAccount.ts b/lib/hooks/useHasAccount.ts deleted file mode 100644 index d314f6f36a..0000000000 --- a/lib/hooks/useHasAccount.ts +++ /dev/null @@ -1,15 +0,0 @@ -import config from 'configs/app'; -import { useAppContext } from 'lib/contexts/app'; -import * as cookies from 'lib/cookies'; - -export default function useHasAccount() { - const appProps = useAppContext(); - - if (!config.features.account.isEnabled) { - return false; - } - - const cookiesString = appProps.cookies; - const hasAuth = Boolean(cookies.get(cookies.NAMES.API_TOKEN, cookiesString)); - return hasAuth; -} diff --git a/lib/hooks/useIsAccountActionAllowed.tsx b/lib/hooks/useIsAccountActionAllowed.tsx deleted file mode 100644 index c8df0912a3..0000000000 --- a/lib/hooks/useIsAccountActionAllowed.tsx +++ /dev/null @@ -1,28 +0,0 @@ -import { useQueryClient } from '@tanstack/react-query'; -import React from 'react'; - -import type { UserInfo } from 'types/api/account'; - -import { resourceKey } from 'lib/api/resources'; -import useLoginUrl from 'lib/hooks/useLoginUrl'; - -export default function useIsAccountActionAllowed() { - const queryClient = useQueryClient(); - - const profileData = queryClient.getQueryData([ resourceKey('user_info') ]); - const isAuth = Boolean(profileData); - const loginUrl = useLoginUrl(); - - return React.useCallback(() => { - if (!loginUrl) { - return false; - } - - if (!isAuth) { - window.location.assign(loginUrl); - return false; - } - - return true; - }, [ isAuth, loginUrl ]); -} diff --git a/lib/hooks/useIsMounted.tsx b/lib/hooks/useIsMounted.tsx new file mode 100644 index 0000000000..d14880ae1b --- /dev/null +++ b/lib/hooks/useIsMounted.tsx @@ -0,0 +1,11 @@ +import React from 'react'; + +export default function useIsMounted() { + const [ isMounted, setIsMounted ] = React.useState(false); + + React.useEffect(() => { + setIsMounted(true); + }, [ ]); + + return isMounted; +} diff --git a/lib/hooks/useIsSafeAddress.tsx b/lib/hooks/useIsSafeAddress.tsx index 49062f7cc2..7888050a6c 100644 --- a/lib/hooks/useIsSafeAddress.tsx +++ b/lib/hooks/useIsSafeAddress.tsx @@ -15,7 +15,7 @@ export default function useIsSafeAddress(hash: string | undefined): boolean { return Promise.reject(); } - return fetch(`${ feature.apiUrl }/${ hash }`, undefined, { omitSentryErrorLog: true }); + return fetch(`${ feature.apiUrl }/${ hash }`); }, enabled: feature.isEnabled && Boolean(hash), refetchOnMount: false, diff --git a/lib/hooks/useLazyRenderedList.tsx b/lib/hooks/useLazyRenderedList.tsx index 245d8a0b0b..3f2d828fe0 100644 --- a/lib/hooks/useLazyRenderedList.tsx +++ b/lib/hooks/useLazyRenderedList.tsx @@ -5,12 +5,12 @@ import { useInView } from 'react-intersection-observer'; const STEP = 10; const MIN_ITEMS_NUM = 50; -export default function useLazyRenderedList(list: Array, isEnabled: boolean) { - const [ renderedItemsNum, setRenderedItemsNum ] = React.useState(MIN_ITEMS_NUM); +export default function useLazyRenderedList(list: Array, isEnabled: boolean, minItemsNum: number = MIN_ITEMS_NUM) { + const [ renderedItemsNum, setRenderedItemsNum ] = React.useState(minItemsNum); const { ref, inView } = useInView({ rootMargin: '200px', triggerOnce: false, - skip: !isEnabled || list.length <= MIN_ITEMS_NUM, + skip: !isEnabled || list.length <= minItemsNum, }); React.useEffect(() => { diff --git a/lib/hooks/useLoginUrl.tsx b/lib/hooks/useLoginUrl.tsx deleted file mode 100644 index a111d1eebb..0000000000 --- a/lib/hooks/useLoginUrl.tsx +++ /dev/null @@ -1,14 +0,0 @@ -import { useRouter } from 'next/router'; - -import { route } from 'nextjs-routes'; - -import config from 'configs/app'; - -const feature = config.features.account; - -export default function useLoginUrl() { - const router = useRouter(); - return feature.isEnabled ? - feature.authUrl + route({ pathname: '/auth/auth0', query: { path: router.asPath } }) : - undefined; -} diff --git a/lib/hooks/useNavItems.tsx b/lib/hooks/useNavItems.tsx index 01710e80cc..bb7c48300f 100644 --- a/lib/hooks/useNavItems.tsx +++ b/lib/hooks/useNavItems.tsx @@ -1,41 +1,22 @@ -import { useRouter } from "next/router"; -import React from "react"; +import { useRouter } from 'next/router'; +import React from 'react'; -import type { - NavItemInternal, - NavItem, - NavGroupItem, - NavItemExternal, -} from "types/client/navigation-items"; +import type { NavItemInternal, NavItem, NavGroupItem } from 'types/client/navigation'; -import config from "configs/app"; -import { rightLineArrow } from "lib/html-entities"; -import UserAvatar from "ui/shared/UserAvatar"; +import config from 'configs/app'; +import { rightLineArrow } from 'lib/html-entities'; interface ReturnType { mainNavItems: Array; accountNavItems: Array; - profileItem: NavItem; } -export function isGroupItem( - item: NavItem | NavGroupItem, -): item is NavGroupItem { - return "subItems" in item; +export function isGroupItem(item: NavItem | NavGroupItem): item is NavGroupItem { + return 'subItems' in item; } export function isInternalItem(item: NavItem): item is NavItemInternal { - return "nextRoute" in item; -} - -function networkName() { - if (config.chain.isDevNet) { - return "alpha_testnet"; - } else if (config.chain.isTestnet) { - return "testnet"; - } else { - return "mainnet"; - } + return 'nextRoute' in item; } export default function useNavItems(): ReturnType { @@ -45,292 +26,301 @@ export default function useNavItems(): ReturnType { return React.useMemo(() => { let blockchainNavItems: Array | Array> = []; - const topAccounts: NavItem | null = !config.UI.views.address.hiddenViews - ?.top_accounts ? - { - text: "Top accounts", - nextRoute: { pathname: "/accounts" as const }, - icon: "top-accounts", - isActive: pathname === "/accounts", - } : - null; + const topAccounts: NavItem | null = !config.UI.views.address.hiddenViews?.top_accounts ? { + text: 'Top accounts', + nextRoute: { pathname: '/accounts' as const }, + icon: 'top-accounts', + isActive: pathname === '/accounts', + } : null; const blocks: NavItem | null = { - text: "Blocks", - nextRoute: { pathname: "/blocks" as const }, - icon: "block", - isActive: - pathname === "/blocks" || pathname === "/block/[height_or_hash]", + text: 'Blocks', + nextRoute: { pathname: '/blocks' as const }, + icon: 'block', + isActive: pathname === '/blocks' || pathname === '/block/[height_or_hash]', }; const txs: NavItem | null = { - text: "Transactions", - nextRoute: { pathname: "/txs" as const }, - icon: "transactions", - isActive: pathname === "/txs" || pathname === "/tx/[hash]", + text: 'Transactions', + nextRoute: { pathname: '/txs' as const }, + icon: 'transactions', + isActive: pathname === '/txs' || pathname === '/tx/[hash]', }; - const userOps: NavItem | null = config.features.userOps.isEnabled ? - { - text: "User operations", - nextRoute: { pathname: "/ops" as const }, - icon: "user_op", - isActive: pathname === "/ops" || pathname === "/op/[hash]", - } : - null; + const userOps: NavItem | null = config.features.userOps.isEnabled ? { + text: 'User operations', + nextRoute: { pathname: '/ops' as const }, + icon: 'user_op', + isActive: pathname === '/ops' || pathname === '/op/[hash]', + } : null; - const verifiedContracts: NavItem | null = { - text: "Verified contracts", - nextRoute: { pathname: "/verified-contracts" as const }, - icon: "verified", - isActive: pathname === "/verified-contracts", + const verifiedContracts: NavItem | null = + { + text: 'Verified contracts', + nextRoute: { pathname: '/verified-contracts' as const }, + icon: 'verified', + isActive: pathname === '/verified-contracts', + }; + const ensLookup = config.features.nameService.isEnabled ? { + text: 'Name services lookup', + nextRoute: { pathname: '/name-domains' as const }, + icon: 'ENS', + isActive: pathname === '/name-domains' || pathname === '/name-domains/[name]', + } : null; + const validators = config.features.validators.isEnabled ? { + text: 'Top validators', + nextRoute: { pathname: '/validators' as const }, + icon: 'validator', + isActive: pathname === '/validators', + } : null; + const rollupDeposits = { + text: `Deposits (L1${ rightLineArrow }L2)`, + nextRoute: { pathname: '/deposits' as const }, + icon: 'arrows/south-east', + isActive: pathname === '/deposits', }; - const ensLookup = config.features.nameService.isEnabled ? - { - text: "Name services lookup", - nextRoute: { pathname: "/name-domains" as const }, - icon: "ENS", - isActive: - pathname === "/name-domains" || pathname === "/name-domains/[name]", - } : - null; + const rollupWithdrawals = { + text: `Withdrawals (L2${ rightLineArrow }L1)`, + nextRoute: { pathname: '/withdrawals' as const }, + icon: 'arrows/north-east', + isActive: pathname === '/withdrawals', + }; + const rollupTxnBatches = { + text: 'Txn batches', + nextRoute: { pathname: '/batches' as const }, + icon: 'txn_batches', + isActive: pathname === '/batches', + }; + const rollupOutputRoots = { + text: 'Output roots', + nextRoute: { pathname: '/output-roots' as const }, + icon: 'output_roots', + isActive: pathname === '/output-roots', + }; + const rollupDisputeGames = config.features.faultProofSystem.isEnabled ? { + text: 'Dispute games', + nextRoute: { pathname: '/dispute-games' as const }, + icon: 'games', + isActive: pathname === '/dispute-games', + } : null; + const mudWorlds = config.features.mudFramework.isEnabled ? { + text: 'MUD worlds', + nextRoute: { pathname: '/mud-worlds' as const }, + icon: 'MUD_menu', + isActive: pathname === '/mud-worlds', + } : null; - const validators: NavItem | undefined = config.app.hideNevs.includes( - "/validators", - ) ? - undefined : - { - text: "Validators", - nextRoute: { pathname: "/validators" as const }, - icon: "bool/nodes", - isActive: pathname.startsWith("/validators"), - }; // beta testnet 中不显示 + const rollupFeature = config.features.rollup; - if (config.features.zkEvmRollup.isEnabled) { + if (rollupFeature.isEnabled && ( + rollupFeature.type === 'optimistic' || + rollupFeature.type === 'arbitrum' || + rollupFeature.type === 'zkEvm' || + rollupFeature.type === 'scroll' + )) { blockchainNavItems = [ [ txs, - userOps, + rollupDeposits, + rollupWithdrawals, + ], + [ blocks, + rollupTxnBatches, + rollupDisputeGames, + rollupFeature.outputRootsEnabled ? rollupOutputRoots : undefined, + ].filter(Boolean), + [ + userOps, + topAccounts, + mudWorlds, validators, - { - text: "Txn batches", - nextRoute: { pathname: "/zkevm-l2-txn-batches" as const }, - icon: "txn_batches", - isActive: - pathname === "/zkevm-l2-txn-batches" || - pathname === "/zkevm-l2-txn-batch/[number]", - }, + verifiedContracts, + ensLookup, ].filter(Boolean), - [ topAccounts, verifiedContracts, ensLookup ].filter(Boolean), ]; - } else if (config.features.optimisticRollup.isEnabled) { + } else if (rollupFeature.isEnabled && rollupFeature.type === 'shibarium') { blockchainNavItems = [ [ txs, - // eslint-disable-next-line max-len - { - text: `Deposits (L1${ rightLineArrow }L2)`, - nextRoute: { pathname: "/l2-deposits" as const }, - icon: "arrows/south-east", - isActive: pathname === "/l2-deposits", - }, - // eslint-disable-next-line max-len - { - text: `Withdrawals (L2${ rightLineArrow }L1)`, - nextRoute: { pathname: "/l2-withdrawals" as const }, - icon: "arrows/north-east", - isActive: pathname === "/l2-withdrawals", - }, + rollupDeposits, + rollupWithdrawals, ], [ blocks, - // eslint-disable-next-line max-len - { - text: "Txn batches", - nextRoute: { pathname: "/l2-txn-batches" as const }, - icon: "txn_batches", - isActive: pathname === "/l2-txn-batches", - }, - // eslint-disable-next-line max-len - { - text: "Output roots", - nextRoute: { pathname: "/l2-output-roots" as const }, - icon: "output_roots", - isActive: pathname === "/l2-output-roots", - }, - ], - [ userOps, topAccounts, verifiedContracts, ensLookup ].filter(Boolean), + userOps, + topAccounts, + verifiedContracts, + ensLookup, + ].filter(Boolean), + ]; + } else if (rollupFeature.isEnabled && rollupFeature.type === 'zkSync') { + blockchainNavItems = [ + [ + txs, + userOps, + blocks, + rollupTxnBatches, + ].filter(Boolean), + [ + topAccounts, + validators, + verifiedContracts, + ensLookup, + ].filter(Boolean), ]; } else { blockchainNavItems = [ txs, userOps, blocks, - validators, topAccounts, + validators, verifiedContracts, ensLookup, config.features.beaconChain.isEnabled && { - text: "Withdrawals", - nextRoute: { pathname: "/withdrawals" as const }, - icon: "arrows/north-east", - isActive: pathname === "/withdrawals", + text: 'Withdrawals', + nextRoute: { pathname: '/withdrawals' as const }, + icon: 'arrows/north-east', + isActive: pathname === '/withdrawals', }, ].filter(Boolean); } + const tokensNavItems = [ + { + text: 'Tokens', + nextRoute: { pathname: '/tokens' as const }, + icon: 'token', + isActive: pathname === '/tokens' || pathname.startsWith('/token/'), + }, + { + text: 'Token transfers', + nextRoute: { pathname: '/token-transfers' as const }, + icon: 'token-transfers', + isActive: pathname === '/token-transfers', + }, + config.features.pools.isEnabled && { + text: 'DEX tracker', + nextRoute: { pathname: '/pools' as const }, + icon: 'dex-tracker', + isActive: pathname === '/pools' || pathname.startsWith('/pool/'), + }, + ].filter(Boolean); + const apiNavItems: Array = [ - config.features.restApiDocs.isEnabled ? - { - text: "REST API", - nextRoute: { pathname: "/api-docs" as const }, - icon: "restAPI", - isActive: pathname === "/api-docs", - } : - null, - config.features.graphqlApiDocs.isEnabled ? - { - text: "GraphQL", - nextRoute: { pathname: "/graphiql" as const }, - icon: "graphQL", - isActive: pathname === "/graphiql", - } : - null, - !config.UI.sidebar.hiddenLinks?.rpc_api && { - text: "RPC API", - icon: "RPC", - url: "https://docs.blockscout.com/for-users/api/rpc-endpoints", + config.features.restApiDocs.isEnabled ? { + text: 'REST API', + nextRoute: { pathname: '/api-docs' as const }, + icon: 'restAPI', + isActive: pathname === '/api-docs', + } : null, + config.features.graphqlApiDocs.isEnabled ? { + text: 'GraphQL', + nextRoute: { pathname: '/graphiql' as const }, + icon: 'graphQL', + isActive: pathname === '/graphiql', + } : null, + !config.UI.navigation.hiddenLinks?.rpc_api && { + text: 'RPC API', + icon: 'RPC', + url: 'https://docs.blockscout.com/for-users/api/rpc-endpoints', }, - !config.UI.sidebar.hiddenLinks?.eth_rpc_api && { - text: "Eth RPC API", - icon: "RPC", - url: " https://docs.blockscout.com/for-users/api/eth-rpc", + !config.UI.navigation.hiddenLinks?.eth_rpc_api && { + text: 'Eth RPC API', + icon: 'RPC', + url: ' https://docs.blockscout.com/for-users/api/eth-rpc', }, ].filter(Boolean); - const mainNavItems: ReturnType["mainNavItems"] = [ + const otherNavItems: Array | Array> = [ { - text: "Blockchain", - icon: "globe-b", - isActive: blockchainNavItems - .flat() - .some((item) => isInternalItem(item) && item.isActive), + text: 'Verify contract', + nextRoute: { pathname: '/contract-verification' as const }, + isActive: pathname.startsWith('/contract-verification'), + }, + config.features.gasTracker.isEnabled && { + text: 'Gas tracker', + nextRoute: { pathname: '/gas-tracker' as const }, + isActive: pathname.startsWith('/gas-tracker'), + }, + config.features.publicTagsSubmission.isEnabled && { + text: 'Submit public tag', + nextRoute: { pathname: '/public-tags/submit' as const }, + isActive: pathname.startsWith('/public-tags/submit'), + }, + ...config.UI.navigation.otherLinks, + ].filter(Boolean); + + const mainNavItems: ReturnType['mainNavItems'] = [ + { + text: 'Blockchain', + icon: 'globe-b', + isActive: blockchainNavItems.flat().some(item => isInternalItem(item) && item.isActive), subItems: blockchainNavItems, }, { - text: "Tokens", - nextRoute: { pathname: "/tokens" as const }, - icon: "token", - isActive: pathname.startsWith("/token"), + text: 'Tokens', + icon: 'token', + isActive: tokensNavItems.flat().some(item => isInternalItem(item) && item.isActive), + subItems: tokensNavItems, }, - config.features.marketplace.isEnabled ? - { - text: "Apps", - nextRoute: { pathname: "/apps" as const }, - icon: "apps", - isActive: pathname.startsWith("/app"), - } : - null, - config.features.stats.isEnabled ? - { - text: "Charts & Sats", - nextRoute: { pathname: "/stats" as const }, - icon: "stats", - isActive: pathname === "/stats", - } : - null, + config.features.marketplace.isEnabled ? { + text: 'DApps', + nextRoute: { pathname: '/apps' as const }, + icon: 'apps', + isActive: pathname.startsWith('/app'), + } : null, + config.features.stats.isEnabled ? { + text: 'Charts & stats', + nextRoute: { pathname: '/stats' as const }, + icon: 'stats', + isActive: pathname.startsWith('/stats'), + } : null, apiNavItems.length > 0 && { - text: "API", - icon: "restAPI", - isActive: apiNavItems.some( - (item) => isInternalItem(item) && item.isActive, - ), + text: 'API', + icon: 'restAPI', + isActive: apiNavItems.some(item => isInternalItem(item) && item.isActive), subItems: apiNavItems, }, - config.app.hideNevs.includes("/dhcs") ? - undefined : - { - text: "DHCs", - nextRoute: { pathname: "/dhcs" as const }, - icon: "bool/provider", - isActive: pathname.startsWith("/dhcs"), - }, // beta testnet 中不显示 - { - text: "Ecosystem", - icon: "ecosystem", - subItems: [ - { - text: "Dashboard", - url: `https://dashboard.boolscan.com?network=${ networkName() }`, - }, - { - text: "Bridge Explorer", - url: `https://bridge.boolscan.com?network=${ networkName() }`, - }, - { - text: "Oracle Explorer", - url: `https://oracle.boolscan.com?network=${ networkName() }`, - }, - ] as Array, - }, { - text: "Others", - icon: "gear", - subItems: [ - { - text: "Verify contract", - nextRoute: { pathname: "/contract-verification" as const }, - isActive: pathname.startsWith("/contract-verification"), - }, - ...config.UI.sidebar.otherLinks, - ], + text: 'Other', + icon: 'gear', + isActive: otherNavItems.flat().some(item => isInternalItem(item) && item.isActive), + subItems: otherNavItems, }, ].filter(Boolean); - const accountNavItems: ReturnType["accountNavItems"] = [ - { - text: "Watch list", - nextRoute: { pathname: "/account/watchlist" as const }, - icon: "watchlist", - isActive: pathname === "/account/watchlist", - }, + const accountNavItems: ReturnType['accountNavItems'] = [ { - text: "Private tags", - nextRoute: { pathname: "/account/tag-address" as const }, - icon: "privattags", - isActive: pathname === "/account/tag-address", + text: 'Watch list', + nextRoute: { pathname: '/account/watchlist' as const }, + icon: 'watchlist', + isActive: pathname === '/account/watchlist', }, { - text: "Public tags", - nextRoute: { pathname: "/account/public-tags-request" as const }, - icon: "publictags", - isActive: pathname === "/account/public-tags-request", + text: 'Private tags', + nextRoute: { pathname: '/account/tag-address' as const }, + icon: 'privattags', + isActive: pathname === '/account/tag-address', }, { - text: "API keys", - nextRoute: { pathname: "/account/api-key" as const }, - icon: "API", - isActive: pathname === "/account/api-key", + text: 'API keys', + nextRoute: { pathname: '/account/api-key' as const }, + icon: 'API', + isActive: pathname === '/account/api-key', }, { - text: "Custom ABI", - nextRoute: { pathname: "/account/custom-abi" as const }, - icon: "ABI", - isActive: pathname === "/account/custom-abi", + text: 'Custom ABI', + nextRoute: { pathname: '/account/custom-abi' as const }, + icon: 'ABI', + isActive: pathname === '/account/custom-abi', }, config.features.addressVerification.isEnabled && { - text: "Verified addrs", - nextRoute: { pathname: "/account/verified-addresses" as const }, - icon: "verified", - isActive: pathname === "/account/verified-addresses", + text: 'Verified addrs', + nextRoute: { pathname: '/account/verified-addresses' as const }, + icon: 'verified', + isActive: pathname === '/account/verified-addresses', }, ].filter(Boolean); - const profileItem = { - text: "My profile", - nextRoute: { pathname: "/auth/profile" as const }, - iconComponent: UserAvatar, - isActive: pathname === "/auth/profile", - }; - - return { mainNavItems, accountNavItems, profileItem }; + return { mainNavItems, accountNavItems }; }, [ pathname ]); } diff --git a/lib/hooks/useNewTxsSocket.tsx b/lib/hooks/useNewTxsSocket.tsx index bedae0049d..576ecec46f 100644 --- a/lib/hooks/useNewTxsSocket.tsx +++ b/lib/hooks/useNewTxsSocket.tsx @@ -57,11 +57,11 @@ export default function useNewTxsSocket() { }, [ setNum ]); const handleSocketClose = React.useCallback(() => { - setSocketAlert('Connection is lost. Please reload page.'); + setSocketAlert('Connection is lost. Please reload the page.'); }, []); const handleSocketError = React.useCallback(() => { - setSocketAlert('An error has occurred while fetching new transactions. Please reload page.'); + setSocketAlert('An error has occurred while fetching new transactions. Please reload the page.'); }, []); const channel = useSocketChannel({ diff --git a/lib/hooks/useNotifyOnNavigation.tsx b/lib/hooks/useNotifyOnNavigation.tsx new file mode 100644 index 0000000000..a3b7a7c92f --- /dev/null +++ b/lib/hooks/useNotifyOnNavigation.tsx @@ -0,0 +1,24 @@ +import { usePathname } from 'next/navigation'; +import { useRouter } from 'next/router'; +import React from 'react'; + +import config from 'configs/app'; +import getQueryParamString from 'lib/router/getQueryParamString'; + +export default function useNotifyOnNavigation() { + const router = useRouter(); + const pathname = usePathname(); + const tab = getQueryParamString(router.query.tab); + + React.useEffect(() => { + if (config.features.metasuites.isEnabled) { + window.postMessage({ source: 'APP_ROUTER', type: 'PATHNAME_CHANGED' }, window.location.origin); + } + }, [ pathname ]); + + React.useEffect(() => { + if (config.features.metasuites.isEnabled) { + window.postMessage({ source: 'APP_ROUTER', type: 'TAB_CHANGED' }, window.location.origin); + } + }, [ tab ]); +} diff --git a/lib/hooks/useRedirectForInvalidAuthToken.tsx b/lib/hooks/useRedirectForInvalidAuthToken.tsx deleted file mode 100644 index 24b74ace64..0000000000 --- a/lib/hooks/useRedirectForInvalidAuthToken.tsx +++ /dev/null @@ -1,27 +0,0 @@ -import * as Sentry from '@sentry/react'; -import { useQueryClient } from '@tanstack/react-query'; -import React from 'react'; - -import { resourceKey } from 'lib/api/resources'; -import type { ResourceError } from 'lib/api/resources'; -import * as cookies from 'lib/cookies'; -import useLoginUrl from 'lib/hooks/useLoginUrl'; - -export default function useRedirectForInvalidAuthToken() { - const queryClient = useQueryClient(); - - const state = queryClient.getQueryState([ resourceKey('user_info') ]); - const errorStatus = state?.error?.status; - const loginUrl = useLoginUrl(); - - React.useEffect(() => { - if (errorStatus === 401) { - const apiToken = cookies.get(cookies.NAMES.API_TOKEN); - - if (apiToken && loginUrl) { - Sentry.captureException(new Error('Invalid API token'), { tags: { source: 'invalid_api_token' } }); - window.location.assign(loginUrl); - } - } - }, [ errorStatus, loginUrl ]); -} diff --git a/lib/hooks/useTimeAgoIncrement.tsx b/lib/hooks/useTimeAgoIncrement.tsx index 270bada27e..2429528e87 100644 --- a/lib/hooks/useTimeAgoIncrement.tsx +++ b/lib/hooks/useTimeAgoIncrement.tsx @@ -19,7 +19,7 @@ function getUnits(diff: number) { return [ DAY, 2 * DAY ]; } -function getUpdateParams(ts: string) { +function getUpdateParams(ts: string | number) { const timeDiff = Date.now() - new Date(ts).getTime(); const [ unit, higherUnit ] = getUnits(timeDiff); @@ -41,7 +41,7 @@ function getUpdateParams(ts: string) { }; } -export default function useTimeAgoIncrement(ts: string | null, isEnabled?: boolean) { +export default function useTimeAgoIncrement(ts: string | number | null, isEnabled?: boolean) { const [ value, setValue ] = React.useState(ts ? dayjs(ts).fromNow() : null); React.useEffect(() => { @@ -78,6 +78,8 @@ export default function useTimeAgoIncrement(ts: string | null, isEnabled?: boole isEnabled && startIncrement(); + !isEnabled && setValue(dayjs(ts).fromNow()); + return () => { timeouts.forEach(window.clearTimeout); intervals.forEach(window.clearInterval); diff --git a/lib/hooks/useToast.tsx b/lib/hooks/useToast.tsx index 4110c513c1..7afc6f3e11 100644 --- a/lib/hooks/useToast.tsx +++ b/lib/hooks/useToast.tsx @@ -2,7 +2,7 @@ import type { UseToastOptions, ToastProps } from '@chakra-ui/react'; import { createToastFn, useChakra } from '@chakra-ui/react'; import React from 'react'; -import Toast from 'ui/shared/Toast'; +import Toast from 'ui/shared/chakra/Toast'; // there is no toastComponent prop in UseToastOptions type // but these options will be passed to createRenderToast under the hood @@ -12,8 +12,10 @@ const defaultOptions: UseToastOptions & { toastComponent?: React.FC position: 'top-right', isClosable: true, containerStyle: { - margin: 8, + margin: 3, + marginBottom: 0, }, + variant: 'subtle', }; export default function useToastModified() { diff --git a/lib/html-entities.ts b/lib/html-entities.ts index 8df513b51d..1163ee9338 100644 --- a/lib/html-entities.ts +++ b/lib/html-entities.ts @@ -23,3 +23,4 @@ export const apos = String.fromCharCode(39); // apostrophe ' export const shift = String.fromCharCode(8679); // upwards white arrow ⇧ export const cmd = String.fromCharCode(8984); // place of interest sign ⌘ export const alt = String.fromCharCode(9095); // alternate key symbol ⎇ +export const copy = String.fromCharCode(169); // copyright symbol © diff --git a/lib/isMetaKey.tsx b/lib/isMetaKey.tsx index 878462bf8d..ce9b0a9d2e 100644 --- a/lib/isMetaKey.tsx +++ b/lib/isMetaKey.tsx @@ -1,3 +1,5 @@ +import type React from 'react'; + export default function isMetaKey(event: React.KeyboardEvent) { - return event.metaKey || event.getModifierState('Meta') || event.getModifierState('OS'); + return event.metaKey || event.getModifierState('Meta'); } diff --git a/lib/makePrettyLink.ts b/lib/makePrettyLink.ts new file mode 100644 index 0000000000..9e05a2d660 --- /dev/null +++ b/lib/makePrettyLink.ts @@ -0,0 +1,9 @@ +export default function makePrettyLink(url: string | undefined): { url: string; domain: string } | undefined { + try { + const urlObj = new URL(url ?? ''); + return { + url: urlObj.href, + domain: urlObj.hostname, + }; + } catch (error) {} +} diff --git a/lib/metadata/__snapshots__/generate.test.ts.snap b/lib/metadata/__snapshots__/generate.test.ts.snap index de88a039ef..6997bf3de1 100644 --- a/lib/metadata/__snapshots__/generate.test.ts.snap +++ b/lib/metadata/__snapshots__/generate.test.ts.snap @@ -2,6 +2,7 @@ exports[`generates correct metadata for: dynamic route 1`] = ` { + "canonical": undefined, "description": "View transaction 0x12345 on Blockscout (Blockscout) Explorer", "opengraph": { "description": "", @@ -14,6 +15,7 @@ exports[`generates correct metadata for: dynamic route 1`] = ` exports[`generates correct metadata for: dynamic route with API data 1`] = ` { + "canonical": undefined, "description": "0x12345, balances and analytics on the Blockscout (Blockscout) Explorer", "opengraph": { "description": "", @@ -26,12 +28,13 @@ exports[`generates correct metadata for: dynamic route with API data 1`] = ` exports[`generates correct metadata for: static route 1`] = ` { - "description": "Blockscout is the #1 open-source blockchain explorer available today. 100+ chains and counting rely on Blockscout data availability, APIs, and ecosystem tools to support their networks.", + "canonical": "http://localhost:3000/txs", + "description": "Open-source block explorer by Blockscout. Search transactions, verify smart contracts, analyze addresses, and track network activity. Complete blockchain data and APIs for the Blockscout (Blockscout) Explorer network.", "opengraph": { "description": "", "imageUrl": "http://localhost:3000/static/og_placeholder.png", - "title": "Blockscout blocks | Blockscout", + "title": "Blockscout transactions - Blockscout explorer | Blockscout", }, - "title": "Blockscout blocks | Blockscout", + "title": "Blockscout transactions - Blockscout explorer | Blockscout", } `; diff --git a/lib/metadata/generate.test.ts b/lib/metadata/generate.test.ts index 5a37e98fe0..f2cd05dcc1 100644 --- a/lib/metadata/generate.test.ts +++ b/lib/metadata/generate.test.ts @@ -4,26 +4,29 @@ import type { Route } from 'nextjs-routes'; import generate from './generate'; -interface TestCase { +interface TestCase { title: string; - route: R; - apiData?: ApiData; + route: { + pathname: Pathname; + query?: Route['query']; + }; + apiData?: ApiData; } -const TEST_CASES: Array> = [ +const TEST_CASES = [ { title: 'static route', route: { - pathname: '/blocks', + pathname: '/txs', }, - }, + } as TestCase<'/txs'>, { title: 'dynamic route', route: { pathname: '/tx/[hash]', query: { hash: '0x12345' }, }, - }, + } as TestCase<'/tx/[hash]'>, { title: 'dynamic route with API data', route: { @@ -31,7 +34,7 @@ const TEST_CASES: Array> = [ query: { hash: '0x12345' }, }, apiData: { symbol: 'USDT' }, - } as TestCase<{ pathname: '/token/[hash]'; query: { hash: string }}>, + } as TestCase<'/token/[hash]'>, ]; describe('generates correct metadata for:', () => { diff --git a/lib/metadata/generate.ts b/lib/metadata/generate.ts index 8d24fd1606..c3d4ed15c5 100644 --- a/lib/metadata/generate.ts +++ b/lib/metadata/generate.ts @@ -1,25 +1,28 @@ import type { ApiData, Metadata } from './types'; +import type { RouteParams } from 'nextjs/types'; import type { Route } from 'nextjs-routes'; import config from 'configs/app'; import getNetworkTitle from 'lib/networks/getNetworkTitle'; +import { currencyUnits } from 'lib/units'; import compileValue from './compileValue'; +import getCanonicalUrl from './getCanonicalUrl'; import getPageOgType from './getPageOgType'; import * as templates from './templates'; -export default function generate(route: R, apiData?: ApiData): Metadata { +export default function generate(route: RouteParams, apiData: ApiData = null): Metadata { const params = { ...route.query, ...apiData, network_name: config.chain.name, network_title: getNetworkTitle(), + network_gwei: currencyUnits.gwei, }; - const compiledTitle = compileValue(templates.title.make(route.pathname), params); - const title = compiledTitle ? compiledTitle + (config.meta.promoteBlockscoutInTitle ? ' | Blockscout' : '') : ''; - const description = compileValue(templates.description.make(route.pathname), params); + const title = compileValue(templates.title.make(route.pathname, Boolean(apiData)), params); + const description = compileValue(templates.description.make(route.pathname, Boolean(apiData)), params); const pageOgType = getPageOgType(route.pathname); @@ -31,5 +34,6 @@ export default function generate(route: R, apiData?: ApiData description: pageOgType !== 'Regular page' ? config.meta.og.description : '', imageUrl: pageOgType !== 'Regular page' ? config.meta.og.imageUrl : '', }, + canonical: getCanonicalUrl(route.pathname), }; } diff --git a/lib/metadata/getCanonicalUrl.ts b/lib/metadata/getCanonicalUrl.ts new file mode 100644 index 0000000000..2a868419a8 --- /dev/null +++ b/lib/metadata/getCanonicalUrl.ts @@ -0,0 +1,24 @@ +import type { Route } from 'nextjs-routes'; + +import config from 'configs/app'; + +const CANONICAL_ROUTES: Array = [ + '/', + '/txs', + '/ops', + '/verified-contracts', + '/name-domains', + '/withdrawals', + '/tokens', + '/stats', + '/api-docs', + '/graphiql', + '/gas-tracker', + '/apps', +]; + +export default function getCanonicalUrl(pathname: Route['pathname']) { + if (CANONICAL_ROUTES.includes(pathname)) { + return config.app.baseUrl + pathname; + } +} diff --git a/lib/metadata/getPageOgType.ts b/lib/metadata/getPageOgType.ts index 01dec9c773..4535257365 100644 --- a/lib/metadata/getPageOgType.ts +++ b/lib/metadata/getPageOgType.ts @@ -9,7 +9,10 @@ const OG_TYPE_DICT: Record = { '/tx/[hash]': 'Regular page', '/blocks': 'Root page', '/block/[height_or_hash]': 'Regular page', + '/block/countdown': 'Regular page', + '/block/countdown/[height]': 'Regular page', '/accounts': 'Root page', + '/accounts/label/[slug]': 'Root page', '/address/[hash]': 'Regular page', '/verified-contracts': 'Root page', '/contract-verification': 'Root page', @@ -20,43 +23,52 @@ const OG_TYPE_DICT: Record = { '/apps': 'Root page', '/apps/[id]': 'Regular page', '/stats': 'Root page', + '/stats/[id]': 'Regular page', '/api-docs': 'Regular page', '/graphiql': 'Regular page', '/search-results': 'Regular page', '/auth/profile': 'Root page', + '/account/rewards': 'Regular page', '/account/watchlist': 'Regular page', '/account/api-key': 'Regular page', '/account/custom-abi': 'Regular page', - '/account/public-tags-request': 'Regular page', '/account/tag-address': 'Regular page', '/account/verified-addresses': 'Root page', + '/public-tags/submit': 'Regular page', '/withdrawals': 'Root page', '/visualize/sol2uml': 'Regular page', '/csv-export': 'Regular page', - '/l2-deposits': 'Root page', - '/l2-output-roots': 'Root page', - '/l2-txn-batches': 'Root page', - '/l2-withdrawals': 'Root page', - '/zkevm-l2-txn-batches': 'Root page', - '/zkevm-l2-txn-batch/[number]': 'Regular page', + '/deposits': 'Root page', + '/output-roots': 'Root page', + '/dispute-games': 'Root page', + '/batches': 'Root page', + '/batches/[number]': 'Regular page', + '/blobs/[hash]': 'Regular page', '/ops': 'Root page', '/op/[hash]': 'Regular page', '/404': 'Regular page', '/name-domains': 'Root page', '/name-domains/[name]': 'Regular page', + '/validators': 'Root page', + '/gas-tracker': 'Root page', + '/mud-worlds': 'Root page', + '/token-transfers': 'Root page', + '/advanced-filter': 'Root page', + '/pools': 'Root page', + '/pools/[hash]': 'Regular page', // service routes, added only to make typescript happy '/login': 'Regular page', + '/sprite': 'Regular page', + '/api/metrics': 'Regular page', + '/api/monitoring/invalid-api-schema': 'Regular page', + '/api/log': 'Regular page', '/api/media-type': 'Regular page', '/api/proxy': 'Regular page', '/api/csrf': 'Regular page', '/api/healthz': 'Regular page', - '/auth/auth0': 'Regular page', - '/auth/unverified-email': 'Regular page', - '/validators': 'Homepage', - '/dhcs': 'Homepage', - '/validators/[hash]': 'Homepage', - '/dhcs/[id]': 'Homepage', + '/api/config': 'Regular page', + '/api/sprite': 'Regular page', }; export default function getPageOgType(pathname: Route['pathname']) { diff --git a/lib/metadata/index.ts b/lib/metadata/index.ts index 6cf182a7b7..903bd988e8 100644 --- a/lib/metadata/index.ts +++ b/lib/metadata/index.ts @@ -1,2 +1,3 @@ export { default as generate } from './generate'; export { default as update } from './update'; +export * from './types'; diff --git a/lib/metadata/templates/description.ts b/lib/metadata/templates/description.ts index 03b0c7e969..a8069e0f28 100644 --- a/lib/metadata/templates/description.ts +++ b/lib/metadata/templates/description.ts @@ -1,8 +1,8 @@ +/* eslint-disable max-len */ import type { Route } from 'nextjs-routes'; // equal og:description -// eslint-disable-next-line max-len -const DEFAULT_TEMPLATE = 'Blockscout is the #1 open-source blockchain explorer available today. 100+ chains and counting rely on Blockscout data availability, APIs, and ecosystem tools to support their networks.'; +const DEFAULT_TEMPLATE = 'Open-source block explorer by Blockscout. Search transactions, verify smart contracts, analyze addresses, and track network activity. Complete blockchain data and APIs for the %network_title% network.'; // FIXME all page descriptions will be updated later const TEMPLATE_MAP: Record = { @@ -12,7 +12,10 @@ const TEMPLATE_MAP: Record = { '/tx/[hash]': 'View transaction %hash% on %network_title%', '/blocks': DEFAULT_TEMPLATE, '/block/[height_or_hash]': 'View the transactions, token transfers, and uncles for block %height_or_hash%', + '/block/countdown': DEFAULT_TEMPLATE, + '/block/countdown/[height]': DEFAULT_TEMPLATE, '/accounts': DEFAULT_TEMPLATE, + '/accounts/label/[slug]': DEFAULT_TEMPLATE, '/address/[hash]': 'View the account balance, transactions, and other data for %hash% on the %network_title%', '/verified-contracts': DEFAULT_TEMPLATE, '/contract-verification': DEFAULT_TEMPLATE, @@ -23,47 +26,58 @@ const TEMPLATE_MAP: Record = { '/apps': DEFAULT_TEMPLATE, '/apps/[id]': DEFAULT_TEMPLATE, '/stats': DEFAULT_TEMPLATE, + '/stats/[id]': DEFAULT_TEMPLATE, '/api-docs': DEFAULT_TEMPLATE, '/graphiql': DEFAULT_TEMPLATE, '/search-results': DEFAULT_TEMPLATE, '/auth/profile': DEFAULT_TEMPLATE, + '/account/rewards': DEFAULT_TEMPLATE, '/account/watchlist': DEFAULT_TEMPLATE, '/account/api-key': DEFAULT_TEMPLATE, '/account/custom-abi': DEFAULT_TEMPLATE, - '/account/public-tags-request': DEFAULT_TEMPLATE, '/account/tag-address': DEFAULT_TEMPLATE, '/account/verified-addresses': DEFAULT_TEMPLATE, + '/public-tags/submit': 'Propose a new public tag for your address, contract or set of contracts for your dApp. Our team will review and approve your submission. Public tags are incredible tool which helps users identify contracts and addresses.', '/withdrawals': DEFAULT_TEMPLATE, '/visualize/sol2uml': DEFAULT_TEMPLATE, '/csv-export': DEFAULT_TEMPLATE, - '/l2-deposits': DEFAULT_TEMPLATE, - '/l2-output-roots': DEFAULT_TEMPLATE, - '/l2-txn-batches': DEFAULT_TEMPLATE, - '/l2-withdrawals': DEFAULT_TEMPLATE, - '/zkevm-l2-txn-batches': DEFAULT_TEMPLATE, - '/zkevm-l2-txn-batch/[number]': DEFAULT_TEMPLATE, + '/deposits': DEFAULT_TEMPLATE, + '/output-roots': DEFAULT_TEMPLATE, + '/dispute-games': DEFAULT_TEMPLATE, + '/batches': DEFAULT_TEMPLATE, + '/batches/[number]': DEFAULT_TEMPLATE, + '/blobs/[hash]': DEFAULT_TEMPLATE, '/ops': DEFAULT_TEMPLATE, '/op/[hash]': DEFAULT_TEMPLATE, '/404': DEFAULT_TEMPLATE, '/name-domains': DEFAULT_TEMPLATE, '/name-domains/[name]': DEFAULT_TEMPLATE, + '/validators': DEFAULT_TEMPLATE, + '/gas-tracker': 'Explore real-time %network_title% gas fees with Blockscout\'s advanced gas fee tracker. Get accurate %network_gwei% estimates and track transaction costs live.', + '/mud-worlds': DEFAULT_TEMPLATE, + '/token-transfers': DEFAULT_TEMPLATE, + '/advanced-filter': DEFAULT_TEMPLATE, + '/pools': DEFAULT_TEMPLATE, + '/pools/[hash]': DEFAULT_TEMPLATE, // service routes, added only to make typescript happy '/login': DEFAULT_TEMPLATE, + '/sprite': DEFAULT_TEMPLATE, + '/api/metrics': DEFAULT_TEMPLATE, + '/api/monitoring/invalid-api-schema': DEFAULT_TEMPLATE, + '/api/log': DEFAULT_TEMPLATE, '/api/media-type': DEFAULT_TEMPLATE, '/api/proxy': DEFAULT_TEMPLATE, '/api/csrf': DEFAULT_TEMPLATE, '/api/healthz': DEFAULT_TEMPLATE, - '/auth/auth0': DEFAULT_TEMPLATE, - '/auth/unverified-email': DEFAULT_TEMPLATE, - '/validators': DEFAULT_TEMPLATE, - '/dhcs': DEFAULT_TEMPLATE, - '/dhcs/[id]': DEFAULT_TEMPLATE, - '/validators/[hash]': DEFAULT_TEMPLATE, + '/api/config': DEFAULT_TEMPLATE, + '/api/sprite': DEFAULT_TEMPLATE, }; -export function make(pathname: Route['pathname']) { - const template = TEMPLATE_MAP[pathname]; +const TEMPLATE_MAP_ENHANCED: Partial> = { + '/stats/[id]': '%description%', +}; - return template ?? ''; +export function make(pathname: Route['pathname'], isEnriched = false) { + return (isEnriched ? TEMPLATE_MAP_ENHANCED[pathname] : undefined) ?? TEMPLATE_MAP[pathname] ?? ''; } diff --git a/lib/metadata/templates/title.ts b/lib/metadata/templates/title.ts index 85407abcfa..46eedf1d30 100644 --- a/lib/metadata/templates/title.ts +++ b/lib/metadata/templates/title.ts @@ -1,64 +1,87 @@ import type { Route } from 'nextjs-routes'; +import config from 'configs/app'; + const TEMPLATE_MAP: Record = { - '/': 'blockchain explorer', - '/txs': 'transactions', - '/txs/kettle/[hash]': 'kettle %hash% transactions', - '/tx/[hash]': 'transaction %hash%', - '/blocks': 'blocks', - '/block/[height_or_hash]': 'block %height_or_hash%', - '/accounts': 'top accounts', - '/address/[hash]': 'address details for %hash%', - '/verified-contracts': 'verified contracts', - '/contract-verification': 'verify contract', - '/address/[hash]/contract-verification': 'contract verification for %hash%', - '/tokens': 'tokens', - '/token/[hash]': '%symbol% token details', - '/token/[hash]/instance/[id]': 'token instance for %symbol%', - '/apps': 'apps marketplace', - '/apps/[id]': '- %app_name%', - '/stats': 'statistics', - '/api-docs': 'REST API', - '/graphiql': 'GraphQL', - '/search-results': 'search result for %q%', - '/auth/profile': '- my profile', - '/account/watchlist': '- watchlist', - '/account/api-key': '- API keys', - '/account/custom-abi': '- custom ABI', - '/account/public-tags-request': '- public tag requests', - '/account/tag-address': '- private tags', - '/account/verified-addresses': '- my verified addresses', - '/withdrawals': 'withdrawals', - '/visualize/sol2uml': 'Solidity UML diagram', - '/csv-export': 'export data to CSV', - '/l2-deposits': 'deposits (L1 > L2)', - '/l2-output-roots': 'output roots', - '/l2-txn-batches': 'tx batches (L2 blocks)', - '/l2-withdrawals': 'withdrawals (L2 > L1)', - '/zkevm-l2-txn-batches': 'zkEvm L2 Tx batches', - '/zkevm-l2-txn-batch/[number]': 'zkEvm L2 Tx batch %number%', - '/ops': 'user operations', - '/op/[hash]': 'user operation %hash%', - '/404': 'error - page not found', - '/name-domains': 'domains search and resolve', - '/name-domains/[name]': '%name% domain details', + '/': '%network_name% blockchain explorer - View %network_name% stats', + '/txs': '%network_name% transactions - %network_name% explorer', + '/txs/kettle/[hash]': '%network_name% kettle %hash% transactions', + '/tx/[hash]': '%network_name% transaction %hash%', + '/blocks': '%network_name% blocks', + '/block/[height_or_hash]': '%network_name% block %height_or_hash%', + '/block/countdown': '%network_name% block countdown index', + '/block/countdown/[height]': '%network_name% block %height% countdown', + '/accounts': '%network_name% top accounts', + '/accounts/label/[slug]': '%network_name% addresses search by label', + '/address/[hash]': '%network_name% address details for %hash%', + '/verified-contracts': 'Verified %network_name% contracts lookup - %network_name% explorer', + '/contract-verification': '%network_name% verify contract', + '/address/[hash]/contract-verification': '%network_name% contract verification for %hash%', + '/tokens': 'Tokens list - %network_name% explorer', + '/token/[hash]': '%network_name% token details', + '/token/[hash]/instance/[id]': '%network_name% NFT instance', + '/apps': '%network_name% DApps - Explore top apps', + '/apps/[id]': '%network_name% marketplace app', + '/stats': '%network_name% stats - %network_name% network insights', + '/stats/[id]': '%network_name% stats - %id% chart', + '/api-docs': '%network_name% API docs - %network_name% developer tools', + '/graphiql': 'GraphQL for %network_name% - %network_name% data query', + '/search-results': '%network_name% search result for %q%', + '/auth/profile': '%network_name% - my profile', + '/account/rewards': '%network_name% - rewards', + '/account/watchlist': '%network_name% - watchlist', + '/account/api-key': '%network_name% - API keys', + '/account/custom-abi': '%network_name% - custom ABI', + '/account/tag-address': '%network_name% - private tags', + '/account/verified-addresses': '%network_name% - my verified addresses', + '/public-tags/submit': '%network_name% - public tag requests', + '/withdrawals': '%network_name% withdrawals - track on %network_name% explorer', + '/visualize/sol2uml': '%network_name% Solidity UML diagram', + '/csv-export': '%network_name% export data to CSV', + '/deposits': '%network_name% deposits (L1 > L2)', + '/output-roots': '%network_name% output roots', + '/dispute-games': '%network_name% dispute games', + '/batches': '%network_name% txn batches', + '/batches/[number]': '%network_name% L2 txn batch %number%', + '/blobs/[hash]': '%network_name% blob %hash% details', + '/ops': 'User operations on %network_name% - %network_name% explorer', + '/op/[hash]': '%network_name% user operation %hash%', + '/404': '%network_name% error - page not found', + '/name-domains': '%network_name% name domains - %network_name% explorer', + '/name-domains/[name]': '%network_name% %name% domain details', + '/validators': '%network_name% validators list', + '/gas-tracker': 'Track %network_name% gas fees in %network_gwei%', + '/mud-worlds': '%network_name% MUD worlds list', + '/token-transfers': '%network_name% token transfers', + '/advanced-filter': '%network_name% advanced filter', + '/pools': '%network_name% DEX pools', + '/pools/[hash]': '%network_name% pool details', // service routes, added only to make typescript happy - '/login': 'login', - '/api/media-type': 'node API media type', - '/api/proxy': 'node API proxy', - '/api/csrf': 'node API CSRF token', - '/api/healthz': 'node API health check', - '/auth/auth0': 'authentication', - '/auth/unverified-email': 'unverified email', - '/validators': 'Validators', - '/dhcs': 'DHCs', - '/validators/[hash]': 'Validator Details', - '/dhcs/[id]': 'DHC Details', + '/login': '%network_name% login', + '/sprite': '%network_name% SVG sprite', + '/api/metrics': '%network_name% node API prometheus metrics', + '/api/monitoring/invalid-api-schema': '%network_name% node API prometheus metrics', + '/api/log': '%network_name% node API request log', + '/api/media-type': '%network_name% node API media type', + '/api/proxy': '%network_name% node API proxy', + '/api/csrf': '%network_name% node API CSRF token', + '/api/healthz': '%network_name% node API health check', + '/api/config': '%network_name% node API app config', + '/api/sprite': '%network_name% node API SVG sprite content', +}; + +const TEMPLATE_MAP_ENHANCED: Partial> = { + '/token/[hash]': '%network_name% %symbol% token details', + '/token/[hash]/instance/[id]': '%network_name% token instance for %symbol%', + '/apps/[id]': '%network_name% - %app_name%', + '/address/[hash]': '%network_name% address details for %domain_name%', + '/stats/[id]': '%title% chart on %network_name%', }; -export function make(pathname: Route['pathname']) { - const template = TEMPLATE_MAP[pathname]; +export function make(pathname: Route['pathname'], isEnriched = false) { + const template = (isEnriched ? TEMPLATE_MAP_ENHANCED[pathname] : undefined) ?? TEMPLATE_MAP[pathname]; + const postfix = config.meta.promoteBlockscoutInTitle ? ' | Blockscout' : ''; - return `%network_name% ${ template }`; + return (template + postfix).trim(); } diff --git a/lib/metadata/types.ts b/lib/metadata/types.ts index 252dbc29cf..4cd6d62c37 100644 --- a/lib/metadata/types.ts +++ b/lib/metadata/types.ts @@ -1,11 +1,18 @@ +import type { LineChart } from '@blockscout/stats-types'; +import type { TokenInfo } from 'types/api/token'; + import type { Route } from 'nextjs-routes'; -/* eslint-disable @typescript-eslint/indent */ -export type ApiData = -R['pathname'] extends '/token/[hash]' ? { symbol: string } : -R['pathname'] extends '/token/[hash]/instance/[id]' ? { symbol: string } : -R['pathname'] extends '/apps/[id]' ? { app_name: string } : -never; +/* eslint-disable @stylistic/indent */ +export type ApiData = +( + Pathname extends '/address/[hash]' ? { domain_name: string } : + Pathname extends '/token/[hash]' ? TokenInfo : + Pathname extends '/token/[hash]/instance/[id]' ? { symbol: string } : + Pathname extends '/apps/[id]' ? { app_name: string } : + Pathname extends '/stats/[id]' ? LineChart['info'] : + never +) | null; export interface Metadata { title: string; @@ -15,4 +22,5 @@ export interface Metadata { description?: string; imageUrl?: string; }; + canonical: string | undefined; } diff --git a/lib/metadata/update.ts b/lib/metadata/update.ts index f6168c1ae6..123e3ca100 100644 --- a/lib/metadata/update.ts +++ b/lib/metadata/update.ts @@ -1,10 +1,11 @@ import type { ApiData } from './types'; +import type { RouteParams } from 'nextjs/types'; import type { Route } from 'nextjs-routes'; import generate from './generate'; -export default function update(route: R, apiData: ApiData) { +export default function update(route: RouteParams, apiData: ApiData) { const { title, description } = generate(route, apiData); window.document.title = title; diff --git a/lib/mixpanel/getPageType.ts b/lib/mixpanel/getPageType.ts index d169b413c5..e361248bbf 100644 --- a/lib/mixpanel/getPageType.ts +++ b/lib/mixpanel/getPageType.ts @@ -7,7 +7,10 @@ export const PAGE_TYPE_DICT: Record = { '/tx/[hash]': 'Transaction details', '/blocks': 'Blocks', '/block/[height_or_hash]': 'Block details', + '/block/countdown': 'Block countdown search', + '/block/countdown/[height]': 'Block countdown', '/accounts': 'Top accounts', + '/accounts/label/[slug]': 'Addresses search by label', '/address/[hash]': 'Address details', '/verified-contracts': 'Verified contracts', '/contract-verification': 'Contract verification', @@ -15,46 +18,55 @@ export const PAGE_TYPE_DICT: Record = { '/tokens': 'Tokens', '/token/[hash]': 'Token details', '/token/[hash]/instance/[id]': 'Token Instance', - '/apps': 'Apps', - '/apps/[id]': 'App', + '/apps': 'DApps', + '/apps/[id]': 'DApp', '/stats': 'Stats', + '/stats/[id]': 'Stats chart', '/api-docs': 'REST API', '/graphiql': 'GraphQL', '/search-results': 'Search results', '/auth/profile': 'Profile', + '/account/rewards': 'Merits', '/account/watchlist': 'Watchlist', '/account/api-key': 'API keys', '/account/custom-abi': 'Custom ABI', - '/account/public-tags-request': 'Public tags', '/account/tag-address': 'Private tags', '/account/verified-addresses': 'Verified addresses', + '/public-tags/submit': 'Submit public tag', '/withdrawals': 'Withdrawals', '/visualize/sol2uml': 'Solidity UML diagram', '/csv-export': 'Export data to CSV file', - '/l2-deposits': 'Deposits (L1 > L2)', - '/l2-output-roots': 'Output roots', - '/l2-txn-batches': 'Tx batches (L2 blocks)', - '/l2-withdrawals': 'Withdrawals (L2 > L1)', - '/zkevm-l2-txn-batches': 'ZkEvm L2 Tx batches', - '/zkevm-l2-txn-batch/[number]': 'ZkEvm L2 Tx batch details', + '/deposits': 'Deposits (L1 > L2)', + '/output-roots': 'Output roots', + '/dispute-games': 'Dispute games', + '/batches': 'Txn batches', + '/batches/[number]': 'L2 txn batch details', + '/blobs/[hash]': 'Blob details', '/ops': 'User operations', '/op/[hash]': 'User operation details', '/404': '404', '/name-domains': 'Domains search and resolve', '/name-domains/[name]': 'Domain details', + '/validators': 'Validators list', + '/gas-tracker': 'Gas tracker', + '/mud-worlds': 'MUD worlds', + '/token-transfers': 'Token transfers', + '/advanced-filter': 'Advanced filter', + '/pools': 'DEX pools', + '/pools/[hash]': 'Pool details', // service routes, added only to make typescript happy '/login': 'Login', + '/sprite': 'Sprite', + '/api/metrics': 'Node API: Prometheus metrics', + '/api/monitoring/invalid-api-schema': 'Node API: Prometheus metrics', + '/api/log': 'Node API: Request log', '/api/media-type': 'Node API: Media type', '/api/proxy': 'Node API: Proxy', '/api/csrf': 'Node API: CSRF token', '/api/healthz': 'Node API: Health check', - '/auth/auth0': 'Auth', - '/auth/unverified-email': 'Unverified email', - '/validators': 'Validators', - '/dhcs': 'DHCs', - '/validators/[hash]': 'Validator Details', - '/dhcs/[id]': 'DHC Details', + '/api/config': 'Node API: App config', + '/api/sprite': 'Node API: SVG sprite content', }; export default function getPageType(pathname: Route['pathname']) { diff --git a/lib/mixpanel/index.ts b/lib/mixpanel/index.ts index 492269c5ea..72f7be7285 100644 --- a/lib/mixpanel/index.ts +++ b/lib/mixpanel/index.ts @@ -3,6 +3,7 @@ import getUuid from './getUuid'; import logEvent from './logEvent'; import useInit from './useInit'; import useLogPageView from './useLogPageView'; +import * as userProfile from './userProfile'; export * from './utils'; export { @@ -11,4 +12,5 @@ export { logEvent, getPageType, getUuid, + userProfile, }; diff --git a/lib/mixpanel/useInit.tsx b/lib/mixpanel/useInit.tsx index 0be8371066..23f02332f8 100644 --- a/lib/mixpanel/useInit.tsx +++ b/lib/mixpanel/useInit.tsx @@ -7,9 +7,11 @@ import { deviceType } from 'react-device-detect'; import config from 'configs/app'; import * as cookies from 'lib/cookies'; +import dayjs from 'lib/date/dayjs'; import getQueryParamString from 'lib/router/getQueryParamString'; import getUuid from './getUuid'; +import * as userProfile from './userProfile'; export default function useMixpanelInit() { const [ isInited, setIsInited ] = React.useState(false); @@ -28,6 +30,7 @@ export default function useMixpanelInit() { debug: Boolean(debugFlagQuery.current || debugFlagCookie), }; const isAuth = Boolean(cookies.get(cookies.NAMES.API_TOKEN)); + const userId = getUuid(); mixpanel.init(feature.projectToken, mixpanelConfig); mixpanel.register({ @@ -38,7 +41,15 @@ export default function useMixpanelInit() { 'Viewport height': window.innerHeight, Language: window.navigator.language, 'Device type': _capitalize(deviceType), - 'User id': getUuid(), + 'User id': userId, + }); + mixpanel.identify(userId); + userProfile.set({ + 'Device Type': _capitalize(deviceType), + ...(isAuth ? { 'With Account': true } : {}), + }); + userProfile.setOnce({ + 'First Time Join': dayjs().toISOString(), }); setIsInited(true); diff --git a/lib/mixpanel/useLogPageView.tsx b/lib/mixpanel/useLogPageView.tsx index 9c3f6a929a..da5a6952f0 100644 --- a/lib/mixpanel/useLogPageView.tsx +++ b/lib/mixpanel/useLogPageView.tsx @@ -1,16 +1,26 @@ +import type { ColorMode } from '@chakra-ui/react'; import { useColorMode } from '@chakra-ui/react'; import { usePathname } from 'next/navigation'; import { useRouter } from 'next/router'; import React from 'react'; import config from 'configs/app'; +import * as cookies from 'lib/cookies'; import getQueryParamString from 'lib/router/getQueryParamString'; +import { COLOR_THEMES } from 'lib/settings/colorTheme'; import getPageType from './getPageType'; import getTabName from './getTabName'; import logEvent from './logEvent'; import { EventTypes } from './utils'; +function getColorTheme(hex: string | undefined, colorMode: ColorMode) { + const colorTheme = COLOR_THEMES.find((theme) => theme.hex === hex) || + COLOR_THEMES.filter((theme) => theme.colorMode === colorMode).slice(-1)[0]; + + return colorTheme.id; +} + export default function useLogPageView(isInited: boolean) { const router = useRouter(); const pathname = usePathname(); @@ -24,11 +34,14 @@ export default function useLogPageView(isInited: boolean) { return; } + const cookieColorModeHex = cookies.get(cookies.NAMES.COLOR_MODE_HEX); + logEvent(EventTypes.PAGE_VIEW, { 'Page type': getPageType(router.pathname), Tab: getTabName(tab), Page: page || undefined, 'Color mode': colorMode, + 'Color theme': getColorTheme(cookieColorModeHex, colorMode), }); // these are only deps that should trigger the effect // in some scenarios page type is not changing (e.g navigation from one address page to another), diff --git a/lib/mixpanel/userProfile.ts b/lib/mixpanel/userProfile.ts new file mode 100644 index 0000000000..774d124723 --- /dev/null +++ b/lib/mixpanel/userProfile.ts @@ -0,0 +1,24 @@ +import mixpanel from 'mixpanel-browser'; + +import type { PickByType } from 'types/utils'; + +interface UserProfileProperties { + 'With Account': boolean; + 'With Connected Wallet': boolean; + 'Device Type': string; + 'First Time Join': string; +} + +type UserProfilePropertiesNumerable = PickByType; + +export function set(props: Partial) { + mixpanel.people.set(props); +} + +export function setOnce(props: Partial) { + mixpanel.people.set_once(props); +} + +export function increment(props: UserProfilePropertiesNumerable) { + mixpanel.people.increment(props); +} diff --git a/lib/mixpanel/utils.ts b/lib/mixpanel/utils.ts index 3f38840a00..42fa625c27 100644 --- a/lib/mixpanel/utils.ts +++ b/lib/mixpanel/utils.ts @@ -1,4 +1,5 @@ import type { WalletType } from 'types/client/wallets'; +import type { ColorThemeId } from 'types/settings'; export enum EventTypes { PAGE_VIEW = 'Page view', @@ -6,6 +7,8 @@ export enum EventTypes { LOCAL_SEARCH = 'Local search', ADD_TO_WALLET = 'Add to wallet', ACCOUNT_ACCESS = 'Account access', + LOGIN = 'Login', + ACCOUNT_LINK_INFO = 'Account link info', PRIVATE_TAG = 'Private tag', VERIFY_ADDRESS = 'Verify address', VERIFY_TOKEN = 'Verify token', @@ -15,19 +18,23 @@ export enum EventTypes { CONTRACT_VERIFICATION = 'Contract verification', QR_CODE = 'QR code', PAGE_WIDGET = 'Page widget', - TX_INTERPRETATION_INTERACTION = 'Transaction interpratetion interaction', + TX_INTERPRETATION_INTERACTION = 'Transaction interpretation interaction', EXPERIMENT_STARTED = 'Experiment started', - FILTERS = 'Filters' + FILTERS = 'Filters', + BUTTON_CLICK = 'Button click', + PROMO_BANNER = 'Promo banner', + APP_FEEDBACK = 'App feedback', } -/* eslint-disable @typescript-eslint/indent */ +/* eslint-disable @stylistic/indent */ export type EventPayload = Type extends EventTypes.PAGE_VIEW ? { 'Page type': string; - 'Tab': string; - 'Page'?: string; + Tab: string; + Page?: string; 'Color mode': 'light' | 'dark'; + 'Color theme': ColorThemeId | undefined; } : Type extends EventTypes.SEARCH_QUERY ? { 'Search query': string; @@ -36,76 +43,131 @@ Type extends EventTypes.SEARCH_QUERY ? { } : Type extends EventTypes.LOCAL_SEARCH ? { 'Search query': string; - 'Source': 'Marketplace'; + Source: 'Marketplace'; } : Type extends EventTypes.ADD_TO_WALLET ? ( { - 'Wallet': WalletType; - 'Target': 'network'; + Wallet: WalletType; + Target: 'network'; } | { - 'Wallet': WalletType; - 'Target': 'token'; - 'Token': string; + Wallet: WalletType; + Target: 'token'; + Token: string; } ) : Type extends EventTypes.ACCOUNT_ACCESS ? { - 'Action': 'Auth0 init' | 'Verification email resent' | 'Logged out'; + Action: 'Dropdown open' | 'Logged out'; +} : +Type extends EventTypes.LOGIN ? ( + { + Action: 'Started'; + Source: string; + } | { + Action: 'Wallet' | 'Email'; + Source: 'Options selector'; + } | { + Action: 'OTP sent'; + Source: 'Email'; + } | { + Action: 'Success'; + Source: 'Email' | 'Wallet'; + } +) : +Type extends EventTypes.ACCOUNT_LINK_INFO ? { + Source: 'Profile' | 'Login modal' | 'Profile dropdown' | 'Merits'; + Status: 'Started' | 'OTP sent' | 'Finished'; + Type: 'Email' | 'Wallet'; } : Type extends EventTypes.PRIVATE_TAG ? { - 'Action': 'Form opened' | 'Submit'; + Action: 'Form opened' | 'Submit'; 'Page type': string; 'Tag type': 'Address' | 'Tx'; } : Type extends EventTypes.VERIFY_ADDRESS ? ( { - 'Action': 'Form opened' | 'Address entered'; + Action: 'Form opened' | 'Address entered'; 'Page type': string; } | { - 'Action': 'Sign ownership'; + Action: 'Sign ownership'; 'Page type': string; 'Sign method': 'wallet' | 'manual'; } ) : Type extends EventTypes.VERIFY_TOKEN ? { - 'Action': 'Form opened' | 'Submit'; + Action: 'Form opened' | 'Submit'; } : Type extends EventTypes.WALLET_CONNECT ? { - 'Source': 'Header' | 'Smart contracts'; - 'Status': 'Started' | 'Connected'; -} : -Type extends EventTypes.WALLET_ACTION ? { - 'Action': 'Open' | 'Address click'; + Source: 'Header' | 'Login' | 'Profile' | 'Profile dropdown' | 'Smart contracts' | 'Swap button' | 'Merits'; + Status: 'Started' | 'Connected'; } : +Type extends EventTypes.WALLET_ACTION ? ( + { + Action: 'Open' | 'Address click'; + } | { + Action: 'Send Transaction' | 'Sign Message' | 'Sign Typed Data'; + Address: string | undefined; + AppId: string; + } +) : Type extends EventTypes.CONTRACT_INTERACTION ? { 'Method type': 'Read' | 'Write'; 'Method name': string; } : Type extends EventTypes.CONTRACT_VERIFICATION ? { - 'Method': string; - 'Status': 'Method selected' | 'Finished'; + Method: string; + Status: 'Method selected' | 'Finished'; } : Type extends EventTypes.QR_CODE ? { 'Page type': string; } : Type extends EventTypes.PAGE_WIDGET ? ( { - 'Type': 'Tokens dropdown' | 'Tokens show all (icon)' | 'Add to watchlist' | 'Address actions (more button)'; + Type: 'Tokens dropdown' | 'Tokens show all (icon)' | 'Add to watchlist' | 'Address actions (more button)'; + } | { + Type: 'Favorite app' | 'More button' | 'Security score' | 'Total contracts' | 'Verified contracts' | 'Analyzed contracts'; + Info: string; + Source: 'Discovery view' | 'Security view' | 'App modal' | 'App page' | 'Security score popup' | 'Banner'; + } | { + Type: 'Security score'; + Source: 'Analyzed contracts popup'; + } | { + Type: 'Action button'; + Info: string; + Source: 'Txn' | 'NFT collection' | 'NFT item'; } | { - 'Type': 'Favorite app' | 'More button'; - 'Info': string; + Type: 'Address tag'; + Info: string; + URL: string; + } | { + Type: 'Share chart'; + Info: string; } ) : Type extends EventTypes.TX_INTERPRETATION_INTERACTION ? { - 'Type': 'Address click' | 'Token click'; + Type: 'Address click' | 'Token click' | 'Domain click'; } : Type extends EventTypes.EXPERIMENT_STARTED ? { 'Experiment name': string; 'Variant name': string; - 'Source': 'growthbook'; + Source: 'growthbook'; } : Type extends EventTypes.FILTERS ? { - 'Source': 'Marketplace'; + Source: 'Marketplace'; 'Filter name': string; } : +Type extends EventTypes.BUTTON_CLICK ? { + Content: string; + Source: string; +} : +Type extends EventTypes.PROMO_BANNER ? { + Source: 'Marketplace'; + Link: string; +} : +Type extends EventTypes.APP_FEEDBACK ? { + Action: 'Rating'; + Source: 'Discovery' | 'App modal' | 'App page'; + AppId: string; + Score: number; +} : undefined; -/* eslint-enable @typescript-eslint/indent */ +/* eslint-enable @stylistic/indent */ diff --git a/lib/monitoring/metrics.ts b/lib/monitoring/metrics.ts new file mode 100644 index 0000000000..05bdad660b --- /dev/null +++ b/lib/monitoring/metrics.ts @@ -0,0 +1,39 @@ +import * as promClient from 'prom-client'; + +const metrics = (() => { + // eslint-disable-next-line no-restricted-properties + if (process.env.PROMETHEUS_METRICS_ENABLED !== 'true') { + return; + } + + promClient.register.clear(); + + const invalidApiSchema = new promClient.Counter({ + name: 'invalid_api_schema', + help: 'Number of invalid external API schema events', + labelNames: [ 'resource', 'url' ] as const, + }); + + const socialPreviewBotRequests = new promClient.Counter({ + name: 'social_preview_bot_requests_total', + help: 'Number of incoming requests from social preview bots', + labelNames: [ 'route', 'bot' ] as const, + }); + + const searchEngineBotRequests = new promClient.Counter({ + name: 'search_engine_bot_requests_total', + help: 'Number of incoming requests from search engine bots', + labelNames: [ 'route', 'bot' ] as const, + }); + + const apiRequestDuration = new promClient.Histogram({ + name: 'api_request_duration_seconds', + help: 'Duration of requests to API in seconds', + labelNames: [ 'route', 'code' ], + buckets: [ 0.2, 0.5, 1, 3, 10 ], + }); + + return { invalidApiSchema, socialPreviewBotRequests, searchEngineBotRequests, apiRequestDuration }; +})(); + +export default metrics; diff --git a/lib/networks/getNetworkValidationActionText.ts b/lib/networks/getNetworkValidationActionText.ts new file mode 100644 index 0000000000..c6a36f1a3d --- /dev/null +++ b/lib/networks/getNetworkValidationActionText.ts @@ -0,0 +1,21 @@ +import config from 'configs/app'; + +export default function getNetworkValidationActionText() { + switch (config.chain.verificationType) { + case 'validation': { + return 'validated'; + } + case 'mining': { + return 'mined'; + } + case 'posting': { + return 'posted'; + } + case 'sequencing': { + return 'sequenced'; + } + default: { + return 'miner'; + } + } +} diff --git a/lib/networks/getNetworkValidatorTitle.ts b/lib/networks/getNetworkValidatorTitle.ts index 7435ee0293..a0c7977cd6 100644 --- a/lib/networks/getNetworkValidatorTitle.ts +++ b/lib/networks/getNetworkValidatorTitle.ts @@ -1,5 +1,21 @@ import config from 'configs/app'; export default function getNetworkValidatorTitle() { - return config.chain.verificationType === 'validation' ? 'validator' : 'miner'; + switch (config.chain.verificationType) { + case 'validation': { + return 'validator'; + } + case 'mining': { + return 'miner'; + } + case 'posting': { + return 'poster'; + } + case 'sequencing': { + return 'sequencer'; + } + default: { + return 'miner'; + } + } } diff --git a/lib/pools/getPoolLinks.ts b/lib/pools/getPoolLinks.ts new file mode 100644 index 0000000000..19d738f01e --- /dev/null +++ b/lib/pools/getPoolLinks.ts @@ -0,0 +1,21 @@ +import type { Pool } from 'types/api/pools'; + +type PoolLink = { + url: string; + image: string; + title: string; +}; + +export default function getPoolLinks(pool?: Pool): Array { + if (!pool) { + return []; + } + + return [ + { + url: pool.coin_gecko_terminal_url, + image: '/static/gecko_terminal.png', + title: 'GeckoTerminal', + }, + ].filter(link => Boolean(link.url)); +} diff --git a/lib/pools/getPoolTitle.ts b/lib/pools/getPoolTitle.ts new file mode 100644 index 0000000000..1e4db61ac6 --- /dev/null +++ b/lib/pools/getPoolTitle.ts @@ -0,0 +1,5 @@ +import type { Pool } from 'types/api/pools'; + +export const getPoolTitle = (pool: Pool) => { + return `${ pool.base_token_symbol } / ${ pool.quote_token_symbol } ${ pool.fee ? `(${ pool.fee }%)` : '' }`; +}; diff --git a/lib/recentSearchKeywords.ts b/lib/recentSearchKeywords.ts index d6f89bf962..5fe5a6bd71 100644 --- a/lib/recentSearchKeywords.ts +++ b/lib/recentSearchKeywords.ts @@ -1,4 +1,4 @@ -import { uniq } from 'lodash'; +import _uniq from 'lodash/uniq'; import isBrowser from './isBrowser'; @@ -27,7 +27,7 @@ export function saveToRecentKeywords(value: string) { } const keywordsArr = getRecentSearchKeywords(); - const result = uniq([ value, ...keywordsArr ]).slice(0, MAX_KEYWORDS_NUMBER - 1); + const result = _uniq([ value, ...keywordsArr ]).slice(0, MAX_KEYWORDS_NUMBER - 1); window.localStorage.setItem(RECENT_KEYWORDS_LS_KEY, JSON.stringify(result)); } diff --git a/lib/regexp.ts b/lib/regexp.ts index d0d708db7a..ab2b3288e3 100644 --- a/lib/regexp.ts +++ b/lib/regexp.ts @@ -5,3 +5,5 @@ export const IPFS_PREFIX = /^ipfs:\/\//i; export const HEX_REGEXP = /^(?:0x)?[\da-fA-F]+$/; export const FILE_EXTENSION = /\.([\da-z]+)$/i; + +export const BLOCK_HEIGHT = /^\d+$/; diff --git a/lib/rollbar/index.tsx b/lib/rollbar/index.tsx new file mode 100644 index 0000000000..9dfbb887a0 --- /dev/null +++ b/lib/rollbar/index.tsx @@ -0,0 +1,23 @@ +import { Provider as DefaultProvider, useRollbar as useRollbarDefault } from '@rollbar/react'; +import type React from 'react'; +import type { Configuration } from 'rollbar'; + +import config from 'configs/app'; + +const feature = config.features.rollbar; + +const FallbackProvider = ({ children }: { children: React.ReactNode }) => children; + +const useRollbarFallback = (): undefined => {}; + +export const Provider = feature.isEnabled ? DefaultProvider : FallbackProvider; +export const useRollbar = feature.isEnabled ? useRollbarDefault : useRollbarFallback; + +export const clientConfig: Configuration | undefined = feature.isEnabled ? { + accessToken: feature.clientToken, + environment: feature.environment, + payload: { + code_version: feature.codeVersion, + app_instance: feature.instance, + }, +} : undefined; diff --git a/lib/rollups/arbitrum.ts b/lib/rollups/arbitrum.ts new file mode 100644 index 0000000000..7dbaac58d3 --- /dev/null +++ b/lib/rollups/arbitrum.ts @@ -0,0 +1,41 @@ +import { ARBITRUM_L2_TX_BATCH_STATUSES, type ArbitrumBatchStatus, type ArbitrumL2TxData } from 'types/api/arbitrumL2'; + +import config from 'configs/app'; + +const rollupFeature = config.features.rollup; + +type Args = { + status: ArbitrumBatchStatus; + commitment_transaction: ArbitrumL2TxData; + confirmation_transaction: ArbitrumL2TxData; +}; + +export const VERIFICATION_STEPS_MAP: Record = { + 'Processed on rollup': 'Processed on rollup', + 'Sent to base': rollupFeature.isEnabled && rollupFeature.parentChainName ? `Sent to ${ rollupFeature.parentChainName }` : 'Sent to parent chain', + 'Confirmed on base': rollupFeature.isEnabled && rollupFeature.parentChainName ? + `Confirmed on ${ rollupFeature.parentChainName }` : + 'Confirmed on parent chain', +}; + +export const verificationSteps = (() => { + return ARBITRUM_L2_TX_BATCH_STATUSES.map((status) => VERIFICATION_STEPS_MAP[status]); +})(); + +export function getVerificationStepStatus({ + status, + commitment_transaction: commitTx, + confirmation_transaction: confirmTx, +}: Args) { + if (status === 'Sent to base') { + if (commitTx.status === 'unfinalized') { + return 'pending'; + } + } + if (status === 'Confirmed on base') { + if (confirmTx.status === 'unfinalized') { + return 'pending'; + } + } + return 'finalized'; +} diff --git a/lib/router/removeQueryParam.ts b/lib/router/removeQueryParam.ts new file mode 100644 index 0000000000..d0a37975c7 --- /dev/null +++ b/lib/router/removeQueryParam.ts @@ -0,0 +1,12 @@ +import type { NextRouter } from 'next/router'; + +export default function removeQueryParam(router: NextRouter, param: string) { + const { pathname, query, asPath } = router; + const newQuery = { ...query }; + delete newQuery[param]; + + const hashIndex = asPath.indexOf('#'); + const hash = hashIndex !== -1 ? asPath.substring(hashIndex) : ''; + + router.replace({ pathname, query: newQuery, hash }, undefined, { shallow: true }); +} diff --git a/lib/router/updateQueryParam.ts b/lib/router/updateQueryParam.ts new file mode 100644 index 0000000000..1dfb1f2a69 --- /dev/null +++ b/lib/router/updateQueryParam.ts @@ -0,0 +1,12 @@ +import type { NextRouter } from 'next/router'; + +export default function updateQueryParam(router: NextRouter, param: string, newValue: string) { + const { pathname, query, asPath } = router; + const newQuery = { ...query }; + newQuery[param] = newValue; + + const hashIndex = asPath.indexOf('#'); + const hash = hashIndex !== -1 ? asPath.substring(hashIndex) : ''; + + router.replace({ pathname, query: newQuery, hash }, undefined, { shallow: true }); +} diff --git a/lib/sentry/config.ts b/lib/sentry/config.ts deleted file mode 100644 index f619c9346c..0000000000 --- a/lib/sentry/config.ts +++ /dev/null @@ -1,108 +0,0 @@ -import * as Sentry from '@sentry/react'; -import { BrowserTracing } from '@sentry/tracing'; - -import appConfig from 'configs/app'; -import { RESOURCE_LOAD_ERROR_MESSAGE } from 'lib/errors/throwOnResourceLoadError'; - -const feature = appConfig.features.sentry; - -export const config: Sentry.BrowserOptions | undefined = (() => { - if (!feature.isEnabled) { - return; - } - - const tracesSampleRate: number | undefined = (() => { - switch (feature.environment) { - case 'development': - return 1; - case 'staging': - return 0.75; - case 'production': - return 0.2; - } - })(); - - return { - environment: feature.environment, - dsn: feature.dsn, - release: feature.release, - enableTracing: feature.enableTracing, - tracesSampleRate, - integrations: feature.enableTracing ? [ new BrowserTracing() ] : undefined, - - // error filtering settings - // were taken from here - https://docs.sentry.io/platforms/node/guides/azure-functions/configuration/filtering/#decluttering-sentry - ignoreErrors: [ - // Random plugins/extensions - 'top.GLOBALS', - // See: http://blog.errorception.com/2012/03/tale-of-unfindable-js-error.html - 'originalCreateNotification', - 'canvas.contentDocument', - 'MyApp_RemoveAllHighlights', - 'http://tt.epicplay.com', - 'Can\'t find variable: ZiteReader', - 'jigsaw is not defined', - 'ComboSearch is not defined', - 'http://loading.retry.widdit.com/', - 'atomicFindClose', - // Facebook borked - 'fb_xd_fragment', - // ISP "optimizing" proxy - `Cache-Control: no-transform` seems to reduce this. (thanks @acdha) - // See http://stackoverflow.com/questions/4113268/how-to-stop-javascript-injection-from-vodafone-proxy - 'bmi_SafeAddOnload', - 'EBCallBackMessageReceived', - // See http://toolbar.conduit.com/Developer/HtmlAndGadget/Methods/JSInjection.aspx - 'conduitPage', - // Generic error code from errors outside the security sandbox - 'Script error.', - - // Relay and WalletConnect errors - 'The quota has been exceeded', - 'Attempt to connect to relay via', - 'WebSocket connection failed for URL: wss://relay.walletconnect.com', - - // API errors - RESOURCE_LOAD_ERROR_MESSAGE, - ], - denyUrls: [ - // Facebook flakiness - /graph\.facebook\.com/i, - // Facebook blocked - /connect\.facebook\.net\/en_US\/all\.js/i, - // Woopra flakiness - /eatdifferent\.com\.woopra-ns\.com/i, - /static\.woopra\.com\/js\/woopra\.js/i, - // Chrome and other extensions - /extensions\//i, - /^chrome:\/\//i, - /^chrome-extension:\/\//i, - /^moz-extension:\/\//i, - // Other plugins - /127\.0\.0\.1:4001\/isrunning/i, // Cacaoweb - /webappstoolbarba\.texthelp\.com\//i, - /metrics\.itunes\.apple\.com\.edgesuite\.net\//i, - - // AD fetch failed errors - /czilladx\.com/i, - /coinzilla\.com/i, - /coinzilla\.io/i, - /slise\.xyz/i, - ], - }; -})(); - -export function configureScope(scope: Sentry.Scope) { - if (!feature.isEnabled) { - return; - } - scope.setTag('app_instance', feature.instance); -} - -export function init() { - if (!config) { - return; - } - - Sentry.init(config); - Sentry.configureScope(configureScope); -} diff --git a/lib/settings/colorTheme.ts b/lib/settings/colorTheme.ts new file mode 100644 index 0000000000..c5b7edaa1f --- /dev/null +++ b/lib/settings/colorTheme.ts @@ -0,0 +1,42 @@ +import type { ColorMode } from '@chakra-ui/react'; + +import type { ColorThemeId } from 'types/settings'; + +interface ColorTheme { + id: ColorThemeId; + label: string; + colorMode: ColorMode; + hex: string; + sampleBg: string; +} + +export const COLOR_THEMES: Array = [ + { + id: 'light', + label: 'Light', + colorMode: 'light', + hex: '#FFFFFF', + sampleBg: 'linear-gradient(154deg, #EFEFEF 50%, rgba(255, 255, 255, 0.00) 330.86%)', + }, + { + id: 'dim', + label: 'Dim', + colorMode: 'dark', + hex: '#232B37', + sampleBg: 'linear-gradient(152deg, #232B37 50%, rgba(255, 255, 255, 0.00) 290.71%)', + }, + { + id: 'midnight', + label: 'Midnight', + colorMode: 'dark', + hex: '#1B2E48', + sampleBg: 'linear-gradient(148deg, #1B3F71 50%, rgba(255, 255, 255, 0.00) 312.35%)', + }, + { + id: 'dark', + label: 'Dark', + colorMode: 'dark', + hex: '#101112', + sampleBg: 'linear-gradient(161deg, #000 9.37%, #383838 92.52%)', + }, +]; diff --git a/lib/settings/identIcon.ts b/lib/settings/identIcon.ts new file mode 100644 index 0000000000..1c26b6ef0f --- /dev/null +++ b/lib/settings/identIcon.ts @@ -0,0 +1,29 @@ +import type { IdenticonType } from 'types/views/address'; + +export const IDENTICONS: Array<{ label: string; id: IdenticonType; sampleBg: string }> = [ + { + label: 'GitHub', + id: 'github', + sampleBg: 'url("/static/identicon_logos/github.png") center / contain no-repeat', + }, + { + label: 'Metamask jazzicon', + id: 'jazzicon', + sampleBg: 'url("/static/identicon_logos/jazzicon.png") center / contain no-repeat', + }, + { + label: 'Ethereum blockies', + id: 'blockie', + sampleBg: 'url("/static/identicon_logos/blockies.png") center / contain no-repeat', + }, + { + label: 'Gradient avatar', + id: 'gradient_avatar', + sampleBg: 'url("/static/identicon_logos/gradient_avatar.png") center / contain no-repeat', + }, + { + label: 'Nouns', + id: 'nouns', + sampleBg: 'url("/static/identicon_logos/nouns.svg") center / contain no-repeat', + }, +]; diff --git a/lib/shortenString.ts b/lib/shortenString.ts index 28a7f67411..4125ec06f2 100644 --- a/lib/shortenString.ts +++ b/lib/shortenString.ts @@ -1,11 +1,11 @@ -export default function shortenString(string: string | null) { +export default function shortenString(string: string | null, charNumber: number | undefined = 8) { if (!string) { return ''; } - if (string.length <= 7) { + if (string.length <= charNumber) { return string; } - return string.slice(0, 4) + '...' + string.slice(-4); + return string.slice(0, charNumber - 4) + '...' + string.slice(-4); } diff --git a/lib/socket/types.ts b/lib/socket/types.ts index 4d05a1acb7..11ae7db150 100644 --- a/lib/socket/types.ts +++ b/lib/socket/types.ts @@ -1,12 +1,14 @@ import type { Channel } from 'phoenix'; import type { AddressCoinBalanceHistoryItem, AddressTokensBalancesSocketMessage } from 'types/api/address'; +import type { NewArbitrumBatchSocketResponse } from 'types/api/arbitrumL2'; import type { NewBlockSocketResponse } from 'types/api/block'; import type { SmartContractVerificationResponse } from 'types/api/contract'; import type { RawTracesResponse } from 'types/api/rawTrace'; +import type { TokenInstanceMetadataSocketMessage } from 'types/api/token'; import type { TokenTransfer } from 'types/api/tokenTransfer'; import type { Transaction } from 'types/api/transaction'; -import type { NewZkEvmBatchSocketResponse } from 'types/api/zkEvmL2TxnBatches'; +import type { NewZkEvmBatchSocketResponse } from 'types/api/zkEvmL2'; export type SocketMessageParams = SocketMessage.NewBlock | SocketMessage.BlocksIndexStatus | @@ -15,23 +17,28 @@ SocketMessage.TxStatusUpdate | SocketMessage.TxRawTrace | SocketMessage.NewTx | SocketMessage.NewPendingTx | -SocketMessage.NewDeposits | +SocketMessage.NewOptimisticDeposits | +SocketMessage.NewArbitrumDeposits | SocketMessage.AddressBalance | SocketMessage.AddressCurrentCoinBalance | SocketMessage.AddressTokenBalance | SocketMessage.AddressTokenBalancesErc20 | SocketMessage.AddressTokenBalancesErc721 | SocketMessage.AddressTokenBalancesErc1155 | +SocketMessage.AddressTokenBalancesErc404 | SocketMessage.AddressCoinBalance | SocketMessage.AddressTxs | SocketMessage.AddressTxsPending | SocketMessage.AddressTokenTransfer | SocketMessage.AddressChangedBytecode | +SocketMessage.AddressFetchedBytecode | SocketMessage.SmartContractWasVerified | SocketMessage.TokenTransfers | SocketMessage.TokenTotalSupply | +SocketMessage.TokenInstanceMetadataFetched | SocketMessage.ContractVerification | SocketMessage.NewZkEvmL2Batch | +SocketMessage.NewArbitrumL2Batch | SocketMessage.Unknown; interface SocketMessageParamsGeneric { @@ -40,16 +47,16 @@ interface SocketMessageParamsGeneric void; } -// eslint-disable-next-line @typescript-eslint/no-namespace export namespace SocketMessage { export type NewBlock = SocketMessageParamsGeneric<'new_block', NewBlockSocketResponse>; - export type BlocksIndexStatus = SocketMessageParamsGeneric<'block_index_status', {finished: boolean; ratio: string}>; - export type InternalTxsIndexStatus = SocketMessageParamsGeneric<'internal_txs_index_status', {finished: boolean; ratio: string}>; + export type BlocksIndexStatus = SocketMessageParamsGeneric<'block_index_status', { finished: boolean; ratio: string }>; + export type InternalTxsIndexStatus = SocketMessageParamsGeneric<'internal_txs_index_status', { finished: boolean; ratio: string }>; export type TxStatusUpdate = SocketMessageParamsGeneric<'collated', NewBlockSocketResponse>; export type TxRawTrace = SocketMessageParamsGeneric<'raw_trace', RawTracesResponse>; export type NewTx = SocketMessageParamsGeneric<'transaction', { transaction: number }>; export type NewPendingTx = SocketMessageParamsGeneric<'pending_transaction', { pending_transaction: number }>; - export type NewDeposits = SocketMessageParamsGeneric<'deposits', { deposits: number }>; + export type NewOptimisticDeposits = SocketMessageParamsGeneric<'deposits', { deposits: number }>; + export type NewArbitrumDeposits = SocketMessageParamsGeneric<'new_messages_to_rollup_amount', { new_messages_to_rollup_amount: number }>; export type AddressBalance = SocketMessageParamsGeneric<'balance', { balance: string; block_number: number; exchange_rate: string }>; export type AddressCurrentCoinBalance = SocketMessageParamsGeneric<'current_coin_balance', { coin_balance: string; block_number: number; exchange_rate: string }>; @@ -57,15 +64,19 @@ export namespace SocketMessage { export type AddressTokenBalancesErc20 = SocketMessageParamsGeneric<'updated_token_balances_erc_20', AddressTokensBalancesSocketMessage>; export type AddressTokenBalancesErc721 = SocketMessageParamsGeneric<'updated_token_balances_erc_721', AddressTokensBalancesSocketMessage>; export type AddressTokenBalancesErc1155 = SocketMessageParamsGeneric<'updated_token_balances_erc_1155', AddressTokensBalancesSocketMessage>; + export type AddressTokenBalancesErc404 = SocketMessageParamsGeneric<'updated_token_balances_erc_404', AddressTokensBalancesSocketMessage>; export type AddressCoinBalance = SocketMessageParamsGeneric<'coin_balance', { coin_balance: AddressCoinBalanceHistoryItem }>; export type AddressTxs = SocketMessageParamsGeneric<'transaction', { transactions: Array }>; export type AddressTxsPending = SocketMessageParamsGeneric<'pending_transaction', { transactions: Array }>; export type AddressTokenTransfer = SocketMessageParamsGeneric<'token_transfer', { token_transfers: Array }>; export type AddressChangedBytecode = SocketMessageParamsGeneric<'changed_bytecode', Record>; + export type AddressFetchedBytecode = SocketMessageParamsGeneric<'fetched_bytecode', { fetched_bytecode: string }>; export type SmartContractWasVerified = SocketMessageParamsGeneric<'smart_contract_was_verified', Record>; - export type TokenTransfers = SocketMessageParamsGeneric<'token_transfer', {token_transfer: number }>; - export type TokenTotalSupply = SocketMessageParamsGeneric<'total_supply', {total_supply: number }>; + export type TokenTransfers = SocketMessageParamsGeneric<'token_transfer', { token_transfer: number }>; + export type TokenTotalSupply = SocketMessageParamsGeneric<'total_supply', { total_supply: number }>; + export type TokenInstanceMetadataFetched = SocketMessageParamsGeneric<'fetched_token_instance_metadata', TokenInstanceMetadataSocketMessage>; export type ContractVerification = SocketMessageParamsGeneric<'verification_result', SmartContractVerificationResponse>; export type NewZkEvmL2Batch = SocketMessageParamsGeneric<'new_zkevm_confirmed_batch', NewZkEvmBatchSocketResponse>; + export type NewArbitrumL2Batch = SocketMessageParamsGeneric<'new_arbitrum_batch', NewArbitrumBatchSocketResponse>; export type Unknown = SocketMessageParamsGeneric; } diff --git a/lib/solidityScan/schema.ts b/lib/solidityScan/schema.ts new file mode 100644 index 0000000000..04ade657b4 --- /dev/null +++ b/lib/solidityScan/schema.ts @@ -0,0 +1,25 @@ +import * as v from 'valibot'; + +export const SolidityScanIssueSeverityDistributionSchema = v.object({ + critical: v.number(), + gas: v.number(), + high: v.number(), + informational: v.number(), + low: v.number(), + medium: v.number(), +}); + +export const SolidityScanSchema = v.object({ + scan_report: v.object({ + contractname: v.string(), + scan_status: v.string(), + scan_summary: v.object({ + score_v2: v.string(), + issue_severity_distribution: SolidityScanIssueSeverityDistributionSchema, + }), + scanner_reference_url: v.string(), + }), +}); + +export type SolidityScanReport = v.InferOutput; +export type SolidityScanReportSeverityDistribution = v.InferOutput; diff --git a/lib/solidityScan/useFetchReport.ts b/lib/solidityScan/useFetchReport.ts new file mode 100644 index 0000000000..69e84b6e62 --- /dev/null +++ b/lib/solidityScan/useFetchReport.ts @@ -0,0 +1,51 @@ +import React from 'react'; +import * as v from 'valibot'; + +import buildUrl from 'lib/api/buildUrl'; +import useApiQuery from 'lib/api/useApiQuery'; +import { SOLIDITY_SCAN_REPORT } from 'stubs/contract'; + +import { SolidityScanSchema } from './schema'; + +interface Params { + hash: string; +} + +const RESOURCE_NAME = 'contract_solidity_scan_report'; +const ERROR_NAME = 'Invalid response schema'; + +export default function useFetchReport({ hash }: Params) { + const query = useApiQuery(RESOURCE_NAME, { + pathParams: { hash }, + queryOptions: { + select: (response) => { + const parsedResponse = v.safeParse(SolidityScanSchema, response); + + if (!parsedResponse.success) { + throw Error(ERROR_NAME); + } + + return parsedResponse.output; + }, + enabled: Boolean(hash), + placeholderData: SOLIDITY_SCAN_REPORT, + retry: 0, + }, + }); + + const errorMessage = query.error && 'message' in query.error ? query.error.message : undefined; + + React.useEffect(() => { + if (errorMessage === ERROR_NAME) { + fetch('/node-api/monitoring/invalid-api-schema', { + method: 'POST', + body: JSON.stringify({ + resource: RESOURCE_NAME, + url: buildUrl(RESOURCE_NAME, { hash }, undefined, true), + }), + }); + } + }, [ errorMessage, hash ]); + + return query; +} diff --git a/lib/token/metadata/attributesParser.ts b/lib/token/metadata/attributesParser.ts index c863000366..63e572fe72 100644 --- a/lib/token/metadata/attributesParser.ts +++ b/lib/token/metadata/attributesParser.ts @@ -19,7 +19,7 @@ function formatValue(value: string | number, display: string | undefined, trait: } case 'date': { return { - value: dayjs(value).format('YYYY-MM-DD'), + value: dayjs(Number(value) * 1000).format('YYYY-MM-DD'), }; } default: { @@ -48,11 +48,25 @@ export default function attributesParser(attributes: Array): Metadata[' return; } - const value = 'value' in item && (typeof item.value === 'string' || typeof item.value === 'number') ? item.value : undefined; + const value = (() => { + if (!('value' in item)) { + return; + } + switch (typeof item.value) { + case 'string': + case 'number': + return item.value; + case 'boolean': + return String(item.value); + case 'object': + return JSON.stringify(item.value); + } + })(); + const trait = 'trait_type' in item && typeof item.trait_type === 'string' ? item.trait_type : undefined; const display = 'display_type' in item && typeof item.display_type === 'string' ? item.display_type : undefined; - if (!value) { + if (value === undefined) { return; } @@ -61,5 +75,6 @@ export default function attributesParser(attributes: Array): Metadata[' trait_type: _upperFirst(trait || 'property'), }; }) + .filter((item) => item?.value) .filter(Boolean); } diff --git a/lib/token/tokenTypes.ts b/lib/token/tokenTypes.ts index 5246fc2418..a382e61340 100644 --- a/lib/token/tokenTypes.ts +++ b/lib/token/tokenTypes.ts @@ -1,14 +1,23 @@ import type { NFTTokenType, TokenType } from 'types/api/token'; -export const NFT_TOKEN_TYPES: Array<{ title: string; id: NFTTokenType }> = [ - { title: 'ERC-721', id: 'ERC-721' }, - { title: 'ERC-1155', id: 'ERC-1155' }, -]; +import config from 'configs/app'; -export const TOKEN_TYPES: Array<{ title: string; id: TokenType }> = [ - { title: 'ERC-20', id: 'ERC-20' }, +const tokenStandardName = config.chain.tokenStandard; + +export const NFT_TOKEN_TYPES: Record = { + 'ERC-721': `${ tokenStandardName }-721`, + 'ERC-1155': `${ tokenStandardName }-1155`, + 'ERC-404': `${ tokenStandardName }-404`, +}; + +export const TOKEN_TYPES: Record = { + 'ERC-20': `${ tokenStandardName }-20`, ...NFT_TOKEN_TYPES, -]; +}; + +export const NFT_TOKEN_TYPE_IDS: Array = [ 'ERC-721', 'ERC-1155', 'ERC-404' ]; +export const TOKEN_TYPE_IDS: Array = [ 'ERC-20', ...NFT_TOKEN_TYPE_IDS ]; -export const NFT_TOKEN_TYPE_IDS = NFT_TOKEN_TYPES.map(i => i.id); -export const TOKEN_TYPE_IDS = TOKEN_TYPES.map(i => i.id); +export function getTokenTypeName(typeId: TokenType) { + return TOKEN_TYPES[typeId]; +} diff --git a/lib/tx/arbitrumMessageStatusDescription.ts b/lib/tx/arbitrumMessageStatusDescription.ts new file mode 100644 index 0000000000..8f2a635a12 --- /dev/null +++ b/lib/tx/arbitrumMessageStatusDescription.ts @@ -0,0 +1,10 @@ +/* eslint-disable max-len */ +import type { ArbitrumMessageStatus } from 'types/api/transaction'; + +export const MESSAGE_DESCRIPTIONS: Record = { + 'Syncing with base layer': 'The incoming message was discovered on the rollup, but the corresponding message on L1 has not yet been found', + 'Settlement pending': 'The transaction with the message was included in a rollup block, but there is no batch on L1 containing the block yet', + 'Waiting for confirmation': 'The rollup block with the transaction containing the message was included in a batch on L1, but it is still waiting for the expiration of the fraud proof countdown', + 'Ready for relay': 'The rollup state was confirmed successfully, and the message can be executed—funds can be claimed on L1', + Relayed: '', +}; diff --git a/lib/validations/address.ts b/lib/validations/address.ts deleted file mode 100644 index cfca644c41..0000000000 --- a/lib/validations/address.ts +++ /dev/null @@ -1,5 +0,0 @@ -// maybe it depends on the network?? - -export const ADDRESS_REGEXP = /^0x[a-fA-F\d]{40}$/; - -export const ADDRESS_LENGTH = 42; diff --git a/lib/validations/email.ts b/lib/validations/email.ts deleted file mode 100644 index 98e0dc6d71..0000000000 --- a/lib/validations/email.ts +++ /dev/null @@ -1,3 +0,0 @@ -export const EMAIL_REGEXP = /^[\w.%+-]+@[a-zA-Z\d-]+(?:\.[a-zA-Z\d-]+)+$/; - -export const validator = (value: string) => EMAIL_REGEXP.test(value) ? true : 'Invalid email'; diff --git a/lib/validations/url.ts b/lib/validations/url.ts deleted file mode 100644 index b4b30d4e05..0000000000 --- a/lib/validations/url.ts +++ /dev/null @@ -1,12 +0,0 @@ -export const validator = (value: string | undefined) => { - if (!value) { - return true; - } - - try { - new URL(value); - return true; - } catch (error) { - return 'Incorrect URL'; - } -}; diff --git a/lib/web3/client.ts b/lib/web3/client.ts index e41e345455..8188f22869 100644 --- a/lib/web3/client.ts +++ b/lib/web3/client.ts @@ -2,10 +2,18 @@ import { createPublicClient, http } from 'viem'; import currentChain from './currentChain'; -export const publicClient = createPublicClient({ - chain: currentChain, - transport: http(), - batch: { - multicall: true, - }, -}); +export const publicClient = (() => { + if (currentChain.rpcUrls.default.http.filter(Boolean).length === 0) { + return; + } + + try { + return createPublicClient({ + chain: currentChain, + transport: http(), + batch: { + multicall: true, + }, + }); + } catch (error) {} +})(); diff --git a/lib/web3/currentChain.ts b/lib/web3/currentChain.ts index 23a089e97b..dd3892859f 100644 --- a/lib/web3/currentChain.ts +++ b/lib/web3/currentChain.ts @@ -1,20 +1,16 @@ -import type { Chain } from 'wagmi'; +import { type Chain } from 'viem'; import config from 'configs/app'; -const currentChain: Chain = { +const currentChain = { id: Number(config.chain.id), name: config.chain.name ?? '', - network: config.chain.name ?? '', nativeCurrency: { decimals: config.chain.currency.decimals, name: config.chain.currency.name ?? '', symbol: config.chain.currency.symbol ?? '', }, rpcUrls: { - 'public': { - http: [ config.chain.rpcUrl ?? '' ], - }, 'default': { http: [ config.chain.rpcUrl ?? '' ], }, @@ -25,6 +21,7 @@ const currentChain: Chain = { url: config.app.baseUrl, }, }, -}; + testnet: config.chain.isTestnet, +} as const satisfies Chain; export default currentChain; diff --git a/lib/web3/useAccount.ts b/lib/web3/useAccount.ts new file mode 100644 index 0000000000..f3dfcd48c8 --- /dev/null +++ b/lib/web3/useAccount.ts @@ -0,0 +1,23 @@ +import type { UseAccountReturnType } from 'wagmi'; +import { useAccount } from 'wagmi'; + +import config from 'configs/app'; + +function useAccountFallback(): UseAccountReturnType { + return { + address: undefined, + addresses: undefined, + chain: undefined, + chainId: undefined, + connector: undefined, + isConnected: false, + isConnecting: false, + isDisconnected: true, + isReconnecting: false, + status: 'disconnected', + }; +} + +const hook = config.features.blockchainInteraction.isEnabled ? useAccount : useAccountFallback; + +export default hook; diff --git a/lib/web3/useAccountWithDomain.ts b/lib/web3/useAccountWithDomain.ts new file mode 100644 index 0000000000..0eeb18e4c5 --- /dev/null +++ b/lib/web3/useAccountWithDomain.ts @@ -0,0 +1,31 @@ +import React from 'react'; + +import config from 'configs/app'; +import useApiQuery from 'lib/api/useApiQuery'; + +import useAccount from './useAccount'; + +export default function useAccountWithDomain(isEnabled: boolean) { + const { address } = useAccount(); + + const isQueryEnabled = config.features.nameService.isEnabled && Boolean(address) && Boolean(isEnabled); + + const domainQuery = useApiQuery('address_domain', { + pathParams: { + chainId: config.chain.id, + address, + }, + queryOptions: { + enabled: isQueryEnabled, + refetchOnMount: false, + }, + }); + + return React.useMemo(() => { + return { + address: isEnabled ? address : undefined, + domain: domainQuery.data?.domain?.name, + isLoading: isQueryEnabled && domainQuery.isLoading, + }; + }, [ address, domainQuery.data?.domain?.name, domainQuery.isLoading, isEnabled, isQueryEnabled ]); +} diff --git a/lib/web3/useAddOrSwitchChain.tsx b/lib/web3/useAddOrSwitchChain.tsx index 05811de6d7..71114f5642 100644 --- a/lib/web3/useAddOrSwitchChain.tsx +++ b/lib/web3/useAddOrSwitchChain.tsx @@ -1,3 +1,4 @@ +import _get from 'lodash/get'; import React from 'react'; import config from 'configs/app'; @@ -24,9 +25,10 @@ export default function useAddOrSwitchChain() { const errorObj = getErrorObj(error); const code = errorObj && 'code' in errorObj ? errorObj.code : undefined; + const originalErrorCode = _get(errorObj, 'data.originalError.code'); // This error code indicates that the chain has not been added to Wallet. - if (code === 4902) { + if (code === 4902 || originalErrorCode === 4902) { const params = [ { chainId: hexadecimalChainId, chainName: config.chain.name, @@ -39,7 +41,6 @@ export default function useAddOrSwitchChain() { blockExplorerUrls: [ config.app.baseUrl ], } ] as never; // in wagmi types for wallet_addEthereumChain method is not provided - // eslint-disable-next-line @typescript-eslint/no-explicit-any return await provider.request({ method: 'wallet_addEthereumChain', diff --git a/lib/web3/useProvider.tsx b/lib/web3/useProvider.tsx index 029eb24835..43cc7aa8fa 100644 --- a/lib/web3/useProvider.tsx +++ b/lib/web3/useProvider.tsx @@ -1,16 +1,14 @@ import React from 'react'; -import type { WindowProvider } from 'wagmi'; - -import 'wagmi/window'; import type { WalletType } from 'types/client/wallets'; +import type { WalletProvider } from 'types/web3'; import config from 'configs/app'; const feature = config.features.web3Wallet; export default function useProvider() { - const [ provider, setProvider ] = React.useState(); + const [ provider, setProvider ] = React.useState(); const [ wallet, setWallet ] = React.useState(); const initializeProvider = React.useMemo(() => async() => { diff --git a/lib/web3/useWallet.ts b/lib/web3/useWallet.ts new file mode 100644 index 0000000000..ac026e3390 --- /dev/null +++ b/lib/web3/useWallet.ts @@ -0,0 +1,61 @@ +import { useAppKit, useAppKitState } from '@reown/appkit/react'; +import React from 'react'; +import { useDisconnect, useAccountEffect } from 'wagmi'; + +import * as mixpanel from 'lib/mixpanel/index'; +import useAccount from 'lib/web3/useAccount'; + +interface Params { + source: mixpanel.EventPayload['Source']; +} + +export default function useWeb3Wallet({ source }: Params) { + const { open: openModal } = useAppKit(); + const { open: isOpen } = useAppKitState(); + const { disconnect } = useDisconnect(); + const [ isOpening, setIsOpening ] = React.useState(false); + const [ isClientLoaded, setIsClientLoaded ] = React.useState(false); + const isConnectionStarted = React.useRef(false); + + React.useEffect(() => { + setIsClientLoaded(true); + }, []); + + const handleConnect = React.useCallback(async() => { + setIsOpening(true); + await openModal(); + setIsOpening(false); + mixpanel.logEvent(mixpanel.EventTypes.WALLET_CONNECT, { Source: source, Status: 'Started' }); + isConnectionStarted.current = true; + }, [ openModal, source ]); + + const handleAccountConnected = React.useCallback(({ isReconnected }: { isReconnected: boolean }) => { + if (!isReconnected && isConnectionStarted.current) { + mixpanel.logEvent(mixpanel.EventTypes.WALLET_CONNECT, { Source: source, Status: 'Connected' }); + mixpanel.userProfile.setOnce({ + 'With Connected Wallet': true, + }); + } + isConnectionStarted.current = false; + }, [ source ]); + + const handleDisconnect = React.useCallback(() => { + disconnect(); + }, [ disconnect ]); + + useAccountEffect({ onConnect: handleAccountConnected }); + + const account = useAccount(); + const address = account.address; + const isConnected = isClientLoaded && !account.isDisconnected && account.address !== undefined; + + return React.useMemo(() => ({ + connect: handleConnect, + disconnect: handleDisconnect, + isOpen: isOpening || isOpen, + isConnected, + isReconnecting: account.isReconnecting, + address, + openModal, + }), [ handleConnect, handleDisconnect, isOpening, isOpen, isConnected, account.isReconnecting, address, openModal ]); +} diff --git a/lib/web3/wagmiConfig.ts b/lib/web3/wagmiConfig.ts new file mode 100644 index 0000000000..423b4b9b7e --- /dev/null +++ b/lib/web3/wagmiConfig.ts @@ -0,0 +1,39 @@ +import { WagmiAdapter } from '@reown/appkit-adapter-wagmi'; +import { http } from 'viem'; +import { createConfig } from 'wagmi'; + +import config from 'configs/app'; +import currentChain from 'lib/web3/currentChain'; +const feature = config.features.blockchainInteraction; + +const wagmi = (() => { + const chains = [ currentChain ]; + + if (!feature.isEnabled) { + const wagmiConfig = createConfig({ + chains: [ currentChain ], + transports: { + [currentChain.id]: http(config.chain.rpcUrl || `${ config.api.endpoint }/api/eth-rpc`), + }, + ssr: true, + batch: { multicall: { wait: 100 } }, + }); + + return { config: wagmiConfig, adapter: null }; + } + + const wagmiAdapter = new WagmiAdapter({ + networks: chains, + multiInjectedProviderDiscovery: true, + transports: { + [currentChain.id]: http(), + }, + projectId: feature.walletConnect.projectId, + ssr: true, + batch: { multicall: { wait: 100 } }, + }); + + return { config: wagmiAdapter.wagmiConfig, adapter: wagmiAdapter }; +})(); + +export default wagmi; diff --git a/lib/xStarScore/useFetchXStarScore.ts b/lib/xStarScore/useFetchXStarScore.ts new file mode 100644 index 0000000000..3247c95a31 --- /dev/null +++ b/lib/xStarScore/useFetchXStarScore.ts @@ -0,0 +1,51 @@ +import React from 'react'; +import * as v from 'valibot'; + +import config from 'configs/app'; +import buildUrl from 'lib/api/buildUrl'; +import useApiQuery from 'lib/api/useApiQuery'; + +interface Params { + hash: string; +} + +const RESOURCE_NAME = 'address_xstar_score'; +const ERROR_NAME = 'Invalid response schema'; + +export default function useFetchXStarScore({ hash }: Params) { + const query = useApiQuery(RESOURCE_NAME, { + pathParams: { hash }, + queryOptions: { + select: (response) => { + const parsedResponse = v.safeParse(v.object({ data: v.object({ level: v.nullable(v.string()) }) }), response); + + if (!parsedResponse.success) { + throw Error(ERROR_NAME); + } + + return parsedResponse.output; + }, + enabled: Boolean(hash) && config.features.xStarScore.isEnabled, + placeholderData: { + data: { level: 'Base' }, + }, + retry: 0, + }, + }); + + const errorMessage = query.error && 'message' in query.error ? query.error.message : undefined; + + React.useEffect(() => { + if (errorMessage === ERROR_NAME) { + fetch('/node-api/monitoring/invalid-api-schema', { + method: 'POST', + body: JSON.stringify({ + resource: RESOURCE_NAME, + url: buildUrl(RESOURCE_NAME, { hash }, undefined, true), + }), + }); + } + }, [ errorMessage, hash ]); + + return query; +} diff --git a/middleware.ts b/middleware.ts index 17648c82f8..cd820f475e 100644 --- a/middleware.ts +++ b/middleware.ts @@ -19,8 +19,13 @@ export function middleware(req: NextRequest) { return accountResponse; } - const end = Date.now(); const res = NextResponse.next(); + + middlewares.colorTheme(req, res); + middlewares.addressFormat(req, res); + + const end = Date.now(); + res.headers.append('Content-Security-Policy', cspPolicy); res.headers.append('Server-Timing', `middleware;dur=${ end - start }`); res.headers.append('Docker-ID', process.env.HOSTNAME || ''); diff --git a/mocks/ad/textAd.ts b/mocks/ad/textAd.ts index 6ee891bea8..0e5bb6feae 100644 --- a/mocks/ad/textAd.ts +++ b/mocks/ad/textAd.ts @@ -2,7 +2,7 @@ export const duck = { ad: { name: 'Hello utia!', description_short: 'Utia is the best! Go with utia! Utia is the best! Go with utia!', - thumbnail: 'https://utia.utia', + thumbnail: 'http://localhost:3100/utia.jpg', url: 'https://test.url', cta_button: 'Click me!', }, diff --git a/mocks/address/address.ts b/mocks/address/address.ts index 7c7bc612cb..b3ad305aea 100644 --- a/mocks/address/address.ts +++ b/mocks/address/address.ts @@ -8,7 +8,7 @@ export const hash = '0xd789a607CEac2f0E14867de4EB15b15C9FFB5859'; export const withName: AddressParam = { hash: hash, - implementation_name: null, + implementations: null, is_contract: false, is_verified: null, name: 'ArianeeStore', @@ -20,7 +20,7 @@ export const withName: AddressParam = { export const withEns: AddressParam = { hash: hash, - implementation_name: null, + implementations: null, is_contract: false, is_verified: null, name: 'ArianeeStore', @@ -30,9 +30,27 @@ export const withEns: AddressParam = { ens_domain_name: 'kitty.kitty.kitty.cat.eth', }; +export const withNameTag: AddressParam = { + hash: hash, + implementations: null, + is_contract: false, + is_verified: null, + name: 'ArianeeStore', + private_tags: [], + watchlist_names: [], + public_tags: [], + ens_domain_name: 'kitty.kitty.kitty.cat.eth', + metadata: { + reputation: null, + tags: [ + { tagType: 'name', name: 'Mrs. Duckie', slug: 'mrs-duckie', ordinal: 0, meta: null }, + ], + }, +}; + export const withoutName: AddressParam = { hash: hash, - implementation_name: null, + implementations: null, is_contract: false, is_verified: null, name: null, @@ -44,7 +62,7 @@ export const withoutName: AddressParam = { export const token: Address = { hash: hash, - implementation_name: null, + implementations: null, is_contract: true, is_verified: false, name: null, @@ -55,44 +73,56 @@ export const token: Address = { token: tokenInfo, block_number_balance_updated_at: 8201413, coin_balance: '1', - creation_tx_hash: '0xc38cf7377bf72d6436f63c37b01b24d032101f20ec1849286dc703c712f10c98', + creation_transaction_hash: '0xc38cf7377bf72d6436f63c37b01b24d032101f20ec1849286dc703c712f10c98', creator_address_hash: '0x34A9c688512ebdB575e82C50c9803F6ba2916E72', - exchange_rate: null, - implementation_address: null, - has_custom_methods_read: false, - has_custom_methods_write: false, + exchange_rate: '0.04311', has_decompiled_code: false, has_logs: false, - has_methods_read: false, - has_methods_read_proxy: false, - has_methods_write: false, - has_methods_write_proxy: false, has_token_transfers: true, has_tokens: true, has_validated_blocks: false, ens_domain_name: null, }; +export const eoa: Address = { + block_number_balance_updated_at: 30811263, + coin_balance: '2782650189688719421432220500', + creation_transaction_hash: '0xf2aff6501b632604c39978b47d309813d8a1bcca721864bbe86abf59704f195e', + creator_address_hash: '0x803ad3F50b9e1fF68615e8B053A186e1be288943', + exchange_rate: '0.04311', + has_decompiled_code: false, + has_logs: true, + has_token_transfers: false, + has_tokens: true, + has_validated_blocks: false, + hash: hash, + implementations: [], + is_contract: false, + is_verified: false, + name: null, + private_tags: [ publicTag ], + public_tags: [ privateTag ], + token: null, + watchlist_names: [ watchlistName ], + watchlist_address_id: 42, + ens_domain_name: null, +}; + export const contract: Address = { block_number_balance_updated_at: 30811263, coin_balance: '27826501896887194214322205', - creation_tx_hash: '0xf2aff6501b632604c39978b47d309813d8a1bcca721864bbe86abf59704f195e', + creation_transaction_hash: '0xf2aff6501b632604c39978b47d309813d8a1bcca721864bbe86abf59704f195e', creator_address_hash: '0x803ad3F50b9e1fF68615e8B053A186e1be288943', exchange_rate: '0.04311', - has_custom_methods_read: false, - has_custom_methods_write: false, has_decompiled_code: false, has_logs: true, - has_methods_read: true, - has_methods_read_proxy: true, - has_methods_write: true, - has_methods_write_proxy: true, has_token_transfers: false, has_tokens: false, has_validated_blocks: false, hash: hash, - implementation_address: '0x2F4F4A52295940C576417d29F22EEb92B440eC89', - implementation_name: 'HomeBridge', + implementations: [ + { address: '0x2F4F4A52295940C576417d29F22EEb92B440eC89', name: 'HomeBridge' }, + ], is_contract: true, is_verified: true, name: 'EternalStorageProxy', @@ -107,23 +137,16 @@ export const contract: Address = { export const validator: Address = { block_number_balance_updated_at: 30811932, coin_balance: '22910462800601256910890', - creation_tx_hash: null, + creation_transaction_hash: null, creator_address_hash: null, exchange_rate: '0.00432018', - has_custom_methods_read: false, - has_custom_methods_write: false, has_decompiled_code: false, has_logs: false, - has_methods_read: false, - has_methods_read_proxy: false, - has_methods_write: false, - has_methods_write_proxy: false, has_token_transfers: false, has_tokens: false, has_validated_blocks: true, hash: hash, - implementation_address: null, - implementation_name: null, + implementations: [], is_contract: false, is_verified: false, name: 'Kiryl Ihnatsyeu', @@ -134,3 +157,12 @@ export const validator: Address = { watchlist_address_id: null, ens_domain_name: null, }; + +export const filecoin = { + ...validator, + filecoin: { + actor_type: 'evm' as const, + id: 'f02977693', + robust: 'f410fuiwj6a3yxajbohrl5vu6ns6o2e2jriul52lvzci', + }, +}; diff --git a/mocks/address/coinBalanceHistory.ts b/mocks/address/coinBalanceHistory.ts index 7bd121dafe..cc78d75602 100644 --- a/mocks/address/coinBalanceHistory.ts +++ b/mocks/address/coinBalanceHistory.ts @@ -35,33 +35,36 @@ export const baseResponse: AddressCoinBalanceHistoryResponse = { next_page_params: null, }; -export const chartResponse: AddressCoinBalanceHistoryChart = [ - { - date: '2022-11-02', - value: '128238612887883515', - }, - { - date: '2022-11-03', - value: '199807583157570922', - }, - { - date: '2022-11-04', - value: '114487912907005778', - }, - { - date: '2022-11-05', - value: '219533112907005778', - }, - { - date: '2022-11-06', - value: '116487912907005778', - }, - { - date: '2022-11-07', - value: '199807583157570922', - }, - { - date: '2022-11-08', - value: '216488112907005778', - }, -]; +export const chartResponse: AddressCoinBalanceHistoryChart = { + items: [ + { + date: '2022-11-02', + value: '128238612887883515', + }, + { + date: '2022-11-03', + value: '199807583157570922', + }, + { + date: '2022-11-04', + value: '114487912907005778', + }, + { + date: '2022-11-05', + value: '219533112907005778', + }, + { + date: '2022-11-06', + value: '116487912907005778', + }, + { + date: '2022-11-07', + value: '199807583157570922', + }, + { + date: '2022-11-08', + value: '216488112907005778', + }, + ], + days: 10, +}; diff --git a/mocks/address/epochRewards.ts b/mocks/address/epochRewards.ts new file mode 100644 index 0000000000..5b22a66cd8 --- /dev/null +++ b/mocks/address/epochRewards.ts @@ -0,0 +1,50 @@ +import type { AddressEpochRewardsResponse } from 'types/api/address'; + +import { tokenInfo } from 'mocks/tokens/tokenInfo'; + +import { withEns, withName, withoutName } from './address'; + +export const epochRewards: AddressEpochRewardsResponse = { + items: [ + { + type: 'delegated_payment', + amount: '136609473658452408568', + account: withName, + associated_account: withName, + block_hash: '0x', + block_number: 26369280, + block_timestamp: '2022-05-15T13:16:24Z', + epoch_number: 1526, + token: tokenInfo, + }, + { + type: 'group', + amount: '117205842355246195095', + account: withoutName, + associated_account: withoutName, + block_hash: '0x', + block_number: 26352000, + block_timestamp: '2022-05-15T13:16:24Z', + epoch_number: 1525, + token: tokenInfo, + }, + { + type: 'validator', + amount: '125659647325556554060', + account: withEns, + associated_account: withEns, + block_hash: '0x', + block_number: 26300160, + block_timestamp: '2022-05-15T13:16:24Z', + epoch_number: 1524, + token: tokenInfo, + }, + ], + next_page_params: { + amount: '71952055594478242556', + associated_account_address_hash: '0x30d060f129817c4de5fbc1366d53e19f43c8c64f', + block_number: 25954560, + items_count: 50, + type: 'delegated_payment', + }, +}; diff --git a/mocks/address/implementations.ts b/mocks/address/implementations.ts new file mode 100644 index 0000000000..1d77032284 --- /dev/null +++ b/mocks/address/implementations.ts @@ -0,0 +1,11 @@ +export const multiple = [ + { address: '0xA84d24bD8ACE4d349C5f8c5DeeDd8bc071Ce5e2b', name: null }, + { address: '0xc9e91eDeA9DC16604022e4E5b437Df9c64EdB05A', name: 'Diamond' }, + { address: '0x2041832c62C0F89426b48B5868146C0b1fcd23E7', name: null }, + { address: '0x5f7DC6ECcF05594429671F83cc0e42EE18bC0974', name: 'VariablePriceFacet' }, + { address: '0x7abC92E242e88e4B0d6c5Beb4Df80e94D2c8A78c', name: null }, + { address: '0x84178a0c58A860eCCFB7E3aeA64a09543062A356', name: 'MultiSaleFacet' }, + { address: '0x33aD95537e63e9f09d96dE201e10715Ed40D9400', name: 'SVGTemplatesFacet' }, + { address: '0xfd86Aa7f902185a8Df9859c25E4BF52D3DaDd9FA', name: 'ERC721AReceiverFacet' }, + { address: '0x6945a35df18e59Ce09fec4B6cD3C4F9cFE6369de', name: null }, +]; diff --git a/mocks/address/tabCounters.ts b/mocks/address/tabCounters.ts new file mode 100644 index 0000000000..3853ffab4d --- /dev/null +++ b/mocks/address/tabCounters.ts @@ -0,0 +1,11 @@ +import type { AddressTabsCounters } from 'types/api/address'; + +export const base: AddressTabsCounters = { + internal_txs_count: 13, + logs_count: 51, + token_balances_count: 3, + token_transfers_count: 3, + transactions_count: 51, + validations_count: 42, + withdrawals_count: 11, +}; diff --git a/mocks/address/tokens.ts b/mocks/address/tokens.ts index c8c7386134..7505eebc57 100644 --- a/mocks/address/tokens.ts +++ b/mocks/address/tokens.ts @@ -1,4 +1,4 @@ -import type { AddressCollectionsResponse, AddressNFTsResponse, AddressTokenBalance } from 'types/api/address'; +import type { AddressCollectionsResponse, AddressNFTsResponse, AddressTokenBalance, AddressTokensResponse } from 'types/api/address'; import * as tokens from 'mocks/tokens/tokenInfo'; import * as tokenInstance from 'mocks/tokens/tokenInstance'; @@ -38,6 +38,17 @@ export const erc20LongSymbol: AddressTokenBalance = { token_instance: null, }; +export const erc20BigAmount: AddressTokenBalance = { + token: { + ...tokens.tokenInfoERC20LongSymbol, + exchange_rate: '4200000000', + name: 'DuckDuckGoose Stable Coin', + }, + token_id: null, + value: '39000000000000000000', + token_instance: null, +}; + export const erc721a: AddressTokenBalance = { token: tokens.tokenInfoERC721a, token_id: null, @@ -94,28 +105,53 @@ export const erc1155LongId: AddressTokenBalance = { value: '42', }; -export const erc20List = { +export const erc404a: AddressTokenBalance = { + token: tokens.tokenInfoERC404, + token_id: '42', + token_instance: tokenInstance.base, + value: '240000000000000', +}; + +export const erc404b: AddressTokenBalance = { + token: tokens.tokenInfoERC404, + token_instance: null, + value: '11', + token_id: null, +}; + +export const erc20List: AddressTokensResponse = { items: [ erc20a, erc20b, erc20c, ], + next_page_params: null, }; -export const erc721List = { +export const erc721List: AddressTokensResponse = { items: [ erc721a, erc721b, erc721c, ], + next_page_params: null, }; -export const erc1155List = { +export const erc1155List: AddressTokensResponse = { items: [ erc1155withoutName, erc1155a, erc1155b, ], + next_page_params: null, +}; + +export const erc404List: AddressTokensResponse = { + items: [ + erc404a, + erc404b, + ], + next_page_params: null, }; export const nfts: AddressNFTsResponse = { @@ -132,6 +168,12 @@ export const nfts: AddressNFTsResponse = { token_type: 'ERC-721', value: '1', }, + { + ...tokenInstance.unique, + token: tokens.tokenInfoERC404, + token_type: 'ERC-404', + value: '11000', + }, ], next_page_params: null, }; @@ -142,22 +184,27 @@ const nftInstance = { value: '11', }; +const nftInstanceWithoutImage = { + ...nftInstance, + image_url: null, +}; + export const collections: AddressCollectionsResponse = { items: [ { token: tokens.tokenInfoERC1155a, amount: '100', - token_instances: Array(5).fill(nftInstance), + token_instances: Array(5).fill(nftInstanceWithoutImage), }, { token: tokens.tokenInfoERC20LongSymbol, amount: '100', - token_instances: Array(5).fill(nftInstance), + token_instances: Array(5).fill(nftInstanceWithoutImage), }, { token: tokens.tokenInfoERC1155WithoutName, amount: '1', - token_instances: [ nftInstance ], + token_instances: [ nftInstanceWithoutImage ], }, ], next_page_params: { diff --git a/mocks/advancedFilter/advancedFilter.ts b/mocks/advancedFilter/advancedFilter.ts new file mode 100644 index 0000000000..87f383b0cf --- /dev/null +++ b/mocks/advancedFilter/advancedFilter.ts @@ -0,0 +1,124 @@ +import type { AdvancedFilterResponse } from 'types/api/advancedFilter'; + +export const baseResponse: AdvancedFilterResponse = { + items: [ + { + timestamp: '2024-12-06T12:38:59.000000Z', + total: null, + type: 'coin_transfer', + value: '0', + hash: '0x35e5793d3da98d8e8e3944e40fa15028806502b53a2319501c6acdb8c83ed4bc', + from: { + ens_domain_name: null, + hash: '0xC1b634853Cb333D3aD8663715b08f41A3Aec47cc', + implementations: [], + is_contract: false, + is_verified: false, + metadata: null, + name: null, + private_tags: [], + public_tags: [], + watchlist_names: [], + }, + token: null, + to: { + ens_domain_name: null, + hash: '0x1c479675ad559DC151F6Ec7ed3FbF8ceE79582B6', + implementations: [ + { + address: '0x31DA64D19Cd31A19CD09F4070366Fe2144792cf7', + name: 'SequencerInbox', + }, + ], + is_contract: true, + is_verified: true, + metadata: null, + name: 'TransparentUpgradeableProxy', + private_tags: [], + public_tags: [], + watchlist_names: [], + }, + method: 'addSequencerL2BatchFromBlobs', + fee: '2657475294553624', + }, + { + timestamp: '2024-12-06T12:38:59.000000Z', + total: null, + type: 'coin_transfer', + value: '1328910000000000', + hash: '0x0d7a6c1e91540f767bc4d48bbcf2aa3fa22c93d0d8a60fb34bd7f0ecec5565b0', + from: { + ens_domain_name: null, + hash: '0x9BDc51980d3b81a0fBd031d0F0E39e9E1aFCB294', + implementations: [], + is_contract: false, + is_verified: false, + metadata: null, + name: null, + private_tags: [], + public_tags: [], + watchlist_names: [], + }, + token: null, + to: { + ens_domain_name: null, + hash: '0xFe4cda7cc3603bdB9447cAd4A6550290AFeF6b38', + implementations: [], + is_contract: false, + is_verified: false, + metadata: null, + name: null, + private_tags: [], + public_tags: [], + watchlist_names: [], + }, + method: null, + fee: '279416150328000', + }, + { + timestamp: '2024-12-06T12:38:59.000000Z', + total: null, + type: 'coin_transfer', + value: '0', + hash: '0x925bb2b7bf0b7a37ba4012bd718015cae29fa44e7846a7563c01f11ef99461e2', + from: { + ens_domain_name: null, + hash: '0x807Db16fd01766EE8A7040B6d32F4169c0A0Bf47', + implementations: [], + is_contract: false, + is_verified: false, + metadata: null, + name: null, + private_tags: [], + public_tags: [], + watchlist_names: [], + }, + token: null, + to: { + ens_domain_name: null, + hash: '0x7f39C581F595B53c5cb19bD0b3f8dA6c935E2Ca0', + implementations: [], + is_contract: true, + is_verified: true, + metadata: null, + name: 'WstETH', + private_tags: [], + public_tags: [], + watchlist_names: [], + }, + method: 'approve', + fee: '620080096879104', + }, + ], + next_page_params: { + block_number: 5867485, + internal_transaction_index: null, + token_transfer_index: null, + transaction_index: 208, + items_count: 50, + }, + search_params: { + tokens: {}, + methods: {}, + }, +}; diff --git a/mocks/apps/app.html b/mocks/apps/app.html new file mode 100644 index 0000000000..c7c675b977 --- /dev/null +++ b/mocks/apps/app.html @@ -0,0 +1,32 @@ + + + + + Mock HTML Content + + + +

Full view app

+ + diff --git a/mocks/apps/apps.ts b/mocks/apps/apps.ts index a8b27a70a4..2f748c625a 100644 --- a/mocks/apps/apps.ts +++ b/mocks/apps/apps.ts @@ -11,6 +11,9 @@ export const apps = [ description: 'Hop is a scalable rollup-to-rollup general token bridge. It allows users to send tokens from one rollup or sidechain to another almost immediately without having to wait for the networks challenge period.', external: true, url: 'https://goerli.hop.exchange/send?token=ETH&sourceNetwork=ethereum', + github: [ 'https://github.com/hop-protocol/hop', 'https://github.com/hop-protocol/hop-ui' ], + discord: 'https://discord.gg/hopprotocol', + twitter: 'https://twitter.com/HopProtocol', }, { author: 'Blockscout', diff --git a/mocks/apps/ratings.ts b/mocks/apps/ratings.ts new file mode 100644 index 0000000000..3a0322850d --- /dev/null +++ b/mocks/apps/ratings.ts @@ -0,0 +1,13 @@ +import { apps } from './apps'; + +export const ratings = { + records: [ + { + fields: { + appId: apps[0].id, + rating: 4.3, + count: 15, + }, + }, + ], +}; diff --git a/mocks/apps/securityReports.ts b/mocks/apps/securityReports.ts new file mode 100644 index 0000000000..33457ddf2f --- /dev/null +++ b/mocks/apps/securityReports.ts @@ -0,0 +1,60 @@ +import { apps } from './apps'; + +export const securityReports = [ + { + appName: apps[0].id, + doc: 'http://docs.li.fi/smart-contracts/deployments#mainnet', + chainsData: { + '1': { + overallInfo: { + verifiedNumber: 1, + totalContractsNumber: 1, + solidityScanContractsNumber: 1, + securityScore: 87.5, + issueSeverityDistribution: { + critical: 4, + gas: 1, + high: 0, + informational: 4, + low: 2, + medium: 0, + }, + }, + contractsData: [ + { + address: '0x1231DEB6f5749EF6cE6943a275A1D3E7486F4EaE', + isVerified: true, + solidityScanReport: { + connection_id: '', + contract_address: '0x1231DEB6f5749EF6cE6943a275A1D3E7486F4EaE', + contract_chain: 'optimism', + contract_platform: 'blockscout', + contract_url: 'http://optimism.blockscout.com/address/0x1231DEB6f5749EF6cE6943a275A1D3E7486F4EaE', + contractname: 'LiFiDiamond', + is_quick_scan: true, + node_reference_id: null, + request_type: 'threat_scan', + scanner_reference_url: 'http://solidityscan.com/quickscan/0x1231DEB6f5749EF6cE6943a275A1D3E7486F4EaE/blockscout/eth?ref=blockscout', + scan_status: 'scan_done', + scan_summary: { + issue_severity_distribution: { + critical: 0, + gas: 1, + high: 0, + informational: 4, + low: 2, + medium: 0, + }, + lines_analyzed_count: 72, + scan_time_taken: 1, + score: '4.38', + score_v2: '87.50', + threat_score: '100.00', + }, + }, + }, + ], + }, + }, + }, +]; diff --git a/mocks/arbitrum/deposits.ts b/mocks/arbitrum/deposits.ts new file mode 100644 index 0000000000..b1f47d3f76 --- /dev/null +++ b/mocks/arbitrum/deposits.ts @@ -0,0 +1,46 @@ +import type { ArbitrumL2MessagesResponse, ArbitrumLatestDepositsResponse } from 'types/api/arbitrumL2'; + +export const baseResponse: ArbitrumL2MessagesResponse = { + items: [ + { + completion_transaction_hash: '0x0b7d58c0a6b4695ba28d99df928591fb931c812c0aab6d0093ff5040d2f9bc5e', + id: 181920, + origination_address: '0x2B51Ae4412F79c3c1cB12AA40Ea4ECEb4e80511a', + origination_transaction_block_number: 123456, + origination_transaction_hash: '0x210d9f70f411de1079e32a98473b04345a5ea6ff2340a8511ebc2df641274436', + origination_timestamp: '2023-06-01T14:46:48.000000Z', + status: 'initiated', + }, + { + completion_transaction_hash: '0x0b7d58c0a6b4695ba28d99df928591fb931c812c0aab6d0093ff5040d2f9bc5e', + id: 181921, + origination_address: '0x2B51Ae4412F79c3c1cB12AA40Ea4ECEb4e80511a', + origination_transaction_block_number: 123400, + origination_transaction_hash: '0x210d9f70f411de1079e32a98473b04345a5ea6ff2340a8511ebc2df641274436', + origination_timestamp: '2023-06-01T14:46:48.000000Z', + status: 'relayed', + }, + ], + next_page_params: { + items_count: 50, + id: 123, + direction: 'to-rollup', + }, +}; + +export const latestDepositsResponse: ArbitrumLatestDepositsResponse = { + items: [ + { + completion_transaction_hash: '0x3ccdf87449d3de6a9dcd3eddb7bc9ecdf1770d4631f03cdf12a098911618d138', + origination_transaction_block_number: 123400, + origination_transaction_hash: '0x210d9f70f411de1079e32a98473b04345a5ea6ff2340a8511ebc2df641274436', + origination_timestamp: '2023-06-01T14:46:48.000000Z', + }, + { + completion_transaction_hash: '0xd16d918b2f95a5cdf66824f6291b6d5eb80b6f4acab3f9fb82ee0ec4109646a0', + origination_timestamp: null, + origination_transaction_block_number: null, + origination_transaction_hash: null, + }, + ], +}; diff --git a/mocks/arbitrum/txnBatch.ts b/mocks/arbitrum/txnBatch.ts new file mode 100644 index 0000000000..5d163ba6bc --- /dev/null +++ b/mocks/arbitrum/txnBatch.ts @@ -0,0 +1,40 @@ +/* eslint-disable max-len */ +import type { ArbitrumL2TxnBatch } from 'types/api/arbitrumL2'; + +import { finalized } from './txnBatches'; + +export const batchData: ArbitrumL2TxnBatch = { + ...finalized, + after_acc: '0xcd064f3409015e8e6407e492e5275a185e492c6b43ccf127f22092d8057a9ffb', + before_acc: '0x2ed7c4985eb778d76ec400a43805e7feecc8c2afcdb492dbe5caf227de6d37bc', + start_block: 1245209, + end_block: 1245490, + data_availability: { + batch_data_container: 'in_blob4844', + }, +}; + +export const batchDataAnytrust: ArbitrumL2TxnBatch = { + ...finalized, + after_acc: '0xcd064f3409015e8e6407e492e5275a185e492c6b43ccf127f22092d8057a9ffb', + before_acc: '0x2ed7c4985eb778d76ec400a43805e7feecc8c2afcdb492dbe5caf227de6d37bc', + start_block: 1245209, + end_block: 1245490, + data_availability: { + batch_data_container: 'in_anytrust', + bls_signature: '0x142577943e30b1ad1b4e40a1c08e00c24a68d6c366f953e361048b7127e327b5bdb8f168ba986beae40cfaf79ea2788004d750555684751e361d6f6445e5c521b45ac93a76da24add241a4a5410ca3a09fa82cf0aafd78801cbd0ad99d5be6b3', + data_hash: '0x4ffada101d8185bcba227f2cff9e0ea0a4deeb08f328601a898131429a436ebe', + timeout: '2024-08-22T12:39:22Z', + signers: [ + { + key: '0x0c6694955b524d718ca445831c5375393773401f33725a79661379dddabd5fff28619dc070befd9ed73d699e5c236c1a163be58ba81002b6130709bc064af5d7ba947130b72056bf17263800f1a3ab2269c6a510ef8e7412fd56d1ef1b916a1306e3b1d9c82c099371bd9861582acaada3a16e9dfee5d0ebce61096598a82f112d0a935e8cab5c48d82e3104b0c7ba79157dad1a019a3e7f6ad077b8e6308b116fec0f58239622463c3631fa01e2b4272409215b8009422c16715dbede590906', + proof: '0x06dcb5e56764bb72e6a45e6deb301ca85d8c4315c1da2efa29927f2ac8fb25571ce31d2d603735fe03196f6d56bcbf9a1999a89a74d5369822c4445d676c15ed52e5008daa775dc9a839c99ff963a19946ac740579874dac4f639907ae1bc69f', + trusted: false, + }, + { + key: '0x0ee5aaeabd57313285207eb89366b411286cf3f1c5e30eb7e355f55385308b91d5807284323ee89a9743c70676f4949504ced3ed41612cbfda06ad55200c1c77d3fb3700059befd64c44bc4a57cb567ec1481ee564cf6cd6cf1f2f4a2dee6db00c547c38400ab118dedae8afd5bab93b703f76a0991baa5d43fbb125194c06b5461f8c738a3c4278a3d98e5456aec0720883c0d28919537a36e2ffd5f731e742b6653557d154c164e068ef983b367ef626faaed46f4eadecbb12b7e55f23175d', + trusted: true, + }, + ], + }, +}; diff --git a/mocks/arbitrum/txnBatches.ts b/mocks/arbitrum/txnBatches.ts new file mode 100644 index 0000000000..54ab913fa4 --- /dev/null +++ b/mocks/arbitrum/txnBatches.ts @@ -0,0 +1,39 @@ +import type { ArbitrumL2TxnBatchesItem, ArbitrumL2TxnBatchesResponse } from 'types/api/arbitrumL2'; + +export const finalized: ArbitrumL2TxnBatchesItem = { + number: 12345, + blocks_count: 12345, + transactions_count: 10000, + commitment_transaction: { + block_number: 12345, + timestamp: '2022-04-17T08:51:58.000000Z', + hash: '0x262e7215739d6a7e33b2c20b45a838801a0f5f080f20bec8e54eb078420c4661', + status: 'finalized', + }, + batch_data_container: 'in_blob4844', +}; + +export const unfinalized: ArbitrumL2TxnBatchesItem = { + number: 12344, + blocks_count: 10000, + transactions_count: 103020, + commitment_transaction: { + block_number: 12340, + timestamp: '2022-04-17T08:51:58.000000Z', + hash: '0x262e7215739d6a7e33b2c20b45a838801a0f5f080f20bec8e54eb078420c4661', + status: 'unfinalized', + }, + batch_data_container: null, + +}; + +export const baseResponse: ArbitrumL2TxnBatchesResponse = { + items: [ + finalized, + unfinalized, + ], + next_page_params: { + items_count: 50, + number: 123, + }, +}; diff --git a/mocks/arbitrum/withdrawals.ts b/mocks/arbitrum/withdrawals.ts new file mode 100644 index 0000000000..a825e18de6 --- /dev/null +++ b/mocks/arbitrum/withdrawals.ts @@ -0,0 +1,29 @@ +import type { ArbitrumL2MessagesResponse } from 'types/api/arbitrumL2'; + +export const baseResponse: ArbitrumL2MessagesResponse = { + items: [ + { + completion_transaction_hash: '0x0b7d58c0a6b4695ba28d99df928591fb931c812c0aab6d0093ff5040d2f9bc5e', + id: 181920, + origination_address: '0x2B51Ae4412F79c3c1cB12AA40Ea4ECEb4e80511a', + origination_transaction_block_number: 123456, + origination_transaction_hash: '0x210d9f70f411de1079e32a98473b04345a5ea6ff2340a8511ebc2df641274436', + origination_timestamp: '2023-06-01T14:46:48.000000Z', + status: 'sent', + }, + { + completion_transaction_hash: '0x0b7d58c0a6b4695ba28d99df928591fb931c812c0aab6d0093ff5040d2f9bc5e', + id: 181921, + origination_address: '0x2B51Ae4412F79c3c1cB12AA40Ea4ECEb4e80511a', + origination_transaction_block_number: 123400, + origination_transaction_hash: '0x210d9f70f411de1079e32a98473b04345a5ea6ff2340a8511ebc2df641274436', + origination_timestamp: '2023-06-01T14:46:48.000000Z', + status: 'confirmed', + }, + ], + next_page_params: { + items_count: 50, + id: 123, + direction: 'from-rollup', + }, +}; diff --git a/mocks/blobs/blobs.ts b/mocks/blobs/blobs.ts new file mode 100644 index 0000000000..24d25b465f --- /dev/null +++ b/mocks/blobs/blobs.ts @@ -0,0 +1,36 @@ +import type { Blob, TxBlobs } from 'types/api/blobs'; + +export const base1: Blob = { + blob_data: '0x004242004242004242004242004242004242', + hash: '0x016316f61a259aa607096440fc3eeb90356e079be01975d2fb18347bd50df33c', + kzg_commitment: '0xa95caabd009e189b9f205e0328ff847ad886e4f8e719bd7219875fbb9688fb3fbe7704bb1dfa7e2993a3dea8d0cf767d', + kzg_proof: '0x89cf91c4c8be6f2a390d4262425f79dffb74c174fb15a210182184543bf7394e5a7970a774ee8e0dabc315424c22df0f', + transaction_hashes: [ + { block_consensus: true, transaction_hash: '0x970d8c45c713a50a1fa351b00ca29a8890cac474c59cc8eee4eddec91a1729f0' }, + ], +}; + +export const base2: Blob = { + blob_data: '0x89504E470D0A1A0A0000000D494844520000003C0000003C0403', + hash: '0x0197fdb17195c176b23160f335daabd4b6a231aaaadd73ec567877c66a3affd1', + kzg_commitment: '0x89b0d8ac715ee134135471994a161ef068a784f51982fcd7161aa8e3e818eb83017ccfbfc30c89b796a2743d77554e2f', + kzg_proof: '0x8255a6c6a236483814b8e68992e70f3523f546866a9fed6b8e0ecfef314c65634113b8aa02d6c5c6e91b46e140f17a07', + transaction_hashes: [ + { block_consensus: true, transaction_hash: '0x22d597ebcf3e8d60096dd0363bc2f0f5e2df27ba1dacd696c51aa7c9409f3193' }, + ], +}; + +export const withoutData: Blob = { + blob_data: null, + hash: '0x0197fdb17195c176b23160f335daabd4b6a231aaaadd73ec567877c66a3affd3', + kzg_commitment: null, + kzg_proof: null, + transaction_hashes: [ + { block_consensus: true, transaction_hash: '0x22d597ebcf3e8d60096dd0363bc2f0f5e2df27ba1dacd696c51aa7c9409f3193' }, + ], +}; + +export const txBlobs: TxBlobs = { + items: [ base1, base2, withoutData ], + next_page_params: null, +}; diff --git a/mocks/blocks/block.ts b/mocks/blocks/block.ts index c386d16916..e6402fca53 100644 --- a/mocks/blocks/block.ts +++ b/mocks/blocks/block.ts @@ -1,5 +1,12 @@ /* eslint-disable max-len */ -import type { Block, BlocksResponse } from 'types/api/block'; +import type { RpcBlock } from 'viem'; + +import type { Block, BlocksResponse, ZilliqaBlockData } from 'types/api/block'; + +import { ZERO_ADDRESS } from 'lib/consts'; + +import * as addressMock from '../address/address'; +import * as tokenMock from '../tokens/tokenInfo'; export const base: Block = { base_fee_per_gas: '10000000000', @@ -15,7 +22,7 @@ export const base: Block = { height: 30146364, miner: { hash: '0xdAd49e6CbDE849353ab27DeC6319E687BFc91A41', - implementation_name: null, + implementations: null, is_contract: false, is_verified: null, name: 'Alex Emelyanov', @@ -45,8 +52,8 @@ export const base: Block = { state_root: 'TODO', timestamp: '2022-11-11T11:59:35Z', total_difficulty: '10258276095980170141167591583995189665817672619', - tx_count: 5, - tx_fees: '26853607500000000', + transaction_count: 5, + transaction_fees: '26853607500000000', type: 'block', uncles_hashes: [], }; @@ -65,7 +72,7 @@ export const genesis: Block = { height: 0, miner: { hash: '0x0000000000000000000000000000000000000000', - implementation_name: null, + implementations: null, is_contract: false, is_verified: null, name: null, @@ -82,8 +89,8 @@ export const genesis: Block = { state_root: 'TODO', timestamp: '2017-12-16T00:13:24.000000Z', total_difficulty: '131072', - tx_count: 0, - tx_fees: '0', + transaction_count: 0, + transaction_fees: '0', type: 'block', uncles_hashes: [], }; @@ -94,7 +101,7 @@ export const base2: Block = { size: 592, miner: { hash: '0xDfE10D55d9248B2ED66f1647df0b0A46dEb25165', - implementation_name: null, + implementations: null, is_contract: false, is_verified: null, name: 'Kiryl Ihnatsyeu', @@ -104,7 +111,7 @@ export const base2: Block = { ens_domain_name: null, }, timestamp: '2022-11-11T11:46:05Z', - tx_count: 253, + transaction_count: 253, gas_target_percentage: 23.6433, gas_used: '6333342', gas_used_percentage: 87.859504, @@ -135,6 +142,87 @@ export const rootstock: Block = { minimum_gas_price: '59240000', }; +export const celo: Block = { + ...base, + celo: { + base_fee: { + token: tokenMock.tokenInfoERC20a, + amount: '445690000000000', + breakdown: [ + { + address: addressMock.withName, + amount: '356552000000000.0000000000000', + percentage: 80, + }, + { + address: { + ...addressMock.withoutName, + hash: ZERO_ADDRESS, + }, + amount: '89138000000000.0000000000000', + percentage: 20, + }, + ], + recipient: addressMock.contract, + }, + epoch_number: 1486, + is_epoch_block: true, + }, +}; + +export const zilliqaWithAggregateQuorumCertificate: Block = { + ...base, + zilliqa: { + view: 1137735, + aggregate_quorum_certificate: { + signature: '0x82d29e8f06adc890f6574c3d0ae0c811de1db695b05ed2755ef384fe21bc44f6505b99e201f6000a65f38ff6a13e286306d0e380ef1b43a273eb9947b3f11f852e14b93c258c32b516f89696fcb1190b147364b789572ebdf85d79c4cf3cbbbb', + view: 1137735, + signers: [ 1, 2, 3, 8 ], + nested_quorum_certificates: [ + { + signature: '0xaeb3567577f9db68565c6f97c158b17522620a9684c6f6beaa78920951ad4cae0f287b630bdd034c4a4f89ada42e3dbe012985e976a6f64057d735a4531a26b4e46c182414eabbe625e5b15e6645be5b6522bdec113df408874f6d1e0d894dca', + view: 1137732, + proposed_by_validator_index: 1, + signers: [ 3, 8 ], + }, + { + signature: '0xaeb3567577f9db68565c6f97c158b17522620a9684c6f6beaa78920951ad4cae0f287b630bdd034c4a4f89ada42e3dbe012985e976a6f64057d735a4531a26b4e46c182414eabbe625e5b15e6645be5b6522bdec113df408874f6d1e0d894dca', + view: 1137732, + proposed_by_validator_index: 2, + signers: [ 0, 2 ], + }, + ], + }, + quorum_certificate: { + signature: '0xaeb3567577f9db68565c6f97c158b17522620a9684c6f6beaa78920951ad4cae0f287b630bdd034c4a4f89ada42e3dbe012985e976a6f64057d735a4531a26b4e46c182414eabbe625e5b15e6645be5b6522bdec113df408874f6d1e0d894dca', + view: 1137732, + signers: [ 0, 2, 3, 8 ], + }, + }, +}; + +export const zilliqaWithoutAggregateQuorumCertificate: Block = { + ...base, + zilliqa: { + ...zilliqaWithAggregateQuorumCertificate.zilliqa, + aggregate_quorum_certificate: null, + } as ZilliqaBlockData, +}; + +export const withBlobTxs: Block = { + ...base, + blob_gas_price: '21518435987', + blob_gas_used: '393216', + burnt_blob_fees: '8461393325064192', + excess_blob_gas: '79429632', + blob_tx_count: 1, +}; + +export const withWithdrawals: Block = { + ...base, + withdrawals_count: 2, +}; + export const baseListResponse: BlocksResponse = { items: [ base, @@ -142,3 +230,107 @@ export const baseListResponse: BlocksResponse = { ], next_page_params: null, }; + +export const rpcBlockBase: RpcBlock = { + difficulty: '0x37fcc04bef8', + extraData: '0x476574682f76312e302e312d38326566323666362f6c696e75782f676f312e34', + gasLimit: '0x2fefd8', + gasUsed: '0x0', + hash: '0xfbafb4b7b6f6789338d15ff046f40dc608a42b1a33b093e109c6d7a36cd76f61', + logsBloom: '0x0', + miner: '0xe6a7a1d47ff21b6321162aea7c6cb457d5476bca', + mixHash: '0x038956b9df89d0c1f980fd656d045e912beafa515cff7d7fd3c5f34ffdcb9e4b', + nonce: '0xd8d3392f340bbb22', + number: '0x1869f', + parentHash: '0x576fd45e598c9f86835f50fe2c6e6d11df2d4c4b01f19e4241b7e793d852f9e4', + receiptsRoot: '0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421', + sha3Uncles: '0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347', + size: '0x225', + stateRoot: '0x32356228651d64cc5e6e7be87a556ecdbf40e876251dc867ba9e4bb82a0124a3', + timestamp: '0x55d19741', + totalDifficulty: '0x259e89748daae17', + transactions: [ + '0x0e70849f10e22fe2e53fe6755f86a572aa6bb2fc472f0b87d9e561efa1fc2e1f', + '0xae5624c77f06d0164301380afa7780ebe49debe77eb3d5167004d69bd188a09f', + ], + transactionsRoot: '0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421', + uncles: [], + baseFeePerGas: null, + blobGasUsed: `0x0`, + excessBlobGas: `0x0`, + sealFields: [], + withdrawals: [ + { address: '0xb9d7934878b5fb9610b3fe8a5e441e8fad7e293f', amount: '0x12128cd', index: '0x3216bbb', validatorIndex: '0x4dca3' }, + { address: '0xb9d7934878b5fb9610b3fe8a5e441e8fad7e293f', amount: '0x12027dd', index: '0x3216bbc', validatorIndex: '0x4dca4' }, + ], +}; + +export const rpcBlockWithTxsInfo: RpcBlock = { + ...rpcBlockBase, + transactions: [ + { + accessList: [ + { + address: '0x7af661a6463993e05a171f45d774cf37e761c83f', + storageKeys: [ + '0x0000000000000000000000000000000000000000000000000000000000000007', + '0x000000000000000000000000000000000000000000000000000000000000000c', + '0x0000000000000000000000000000000000000000000000000000000000000008', + '0x0000000000000000000000000000000000000000000000000000000000000006', + '0x0000000000000000000000000000000000000000000000000000000000000009', + '0x000000000000000000000000000000000000000000000000000000000000000a', + ], + }, + { + address: '0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2', + storageKeys: [ + '0x0d726f311404f8052d44e7004a6ffb747709a6d3666a62ce3f5aad13374680ab', + '0x1a824a6850dcbd9223afea4418727593881e2911ed2e734272a263153159fe26', + '0xfae3a383c82daf853bbd8bbcd21280410599b135c274c01354ea7d3a5e09f43c', + ], + }, + ], + blockHash: '0xeb37ebc94e31773e5c5703073fd3911b2ab596f099d00d18b55ae3ac8203c1d5', + blockNumber: '0x136058d', + chainId: '0x1', + from: '0x111527f1386c6725a2f5986230f3060bdcac041f', + gas: '0xf4240', + gasPrice: '0x1780b2ff9', + hash: '0x0e70849f10e22fe2e53fe6755f86a572aa6bb2fc472f0b87d9e561efa1fc2e1f', + input: '0x258d7af661a6463993e05a171f45d774cf37e761c83f402ab3277301b3574863a151d042dc870fb1b3f0c72cbbdd53a85898f62415fe124406f6608d8802269d1283cdb2a5a329649e5cb4cdcee91ab6', + // maxFeePerGas: '0x3ac1bf7ee', + // maxPriorityFeePerGas: '0x0', + nonce: '0x127b2', + r: '0x3c47223f880a3fb7b1eca368d9d7320d2278f0b679109d9ed0af4080ee386f23', + s: '0x587a441f9472b312ff302d7132547aa250ea06c6203c76831d56a46ec188e664', + to: '0x000000d40b595b94918a28b27d1e2c66f43a51d3', + transactionIndex: '0x0', + type: '0x1', + v: '0x1', + value: '0x31', + yParity: '0x1', + }, + { + accessList: [], + blockHash: '0xeb37ebc94e31773e5c5703073fd3911b2ab596f099d00d18b55ae3ac8203c1d5', + blockNumber: '0x136058d', + chainId: '0x1', + from: '0xe25d2cb47b606bb6fd9272125457a7230e26f956', + gas: '0x47bb0', + gasPrice: '0x1ba875cb6', + hash: '0xae5624c77f06d0164301380afa7780ebe49debe77eb3d5167004d69bd188a09f', + input: '0x3593564c000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000006696237b00000000000000000000000000000000000000000000000000000000000000040b080604000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000002800000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000b1a2bc2ec500000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000b1a2bc2ec5000000000000000000000000000000000000000000000000000000006d1aaedfab0f00000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2000000000000000000000000d84d4e8e1e8f268e027c29fa4d48c4b7e4d422990000000000000000000000000000000000000000000000000000000000000060000000000000000000000000d84d4e8e1e8f268e027c29fa4d48c4b7e4d42299000000000000000000000000000000fee13a103a10d593b9ae06b3e05f2e7e1c00000000000000000000000000000000000000000000000000000000000000190000000000000000000000000000000000000000000000000000000000000060000000000000000000000000d84d4e8e1e8f268e027c29fa4d48c4b7e4d42299000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000006cd4db3c8c8d', + // maxFeePerGas: '0x23493c9cd', + // maxPriorityFeePerGas: '0x427c2cbd', + nonce: '0x32b', + r: '0x6566181b3cfd01702b24a2124ea7698b8cc815c7f37d1ea55800f176ca7a94cf', + s: '0x34f8dd837f37746ccd18f4fa71e05de98a2212f1c931f740598e491518616bb3', + to: '0x3fc91a3afd70395cd496c647d5a6cc9d4b2b7fad', + transactionIndex: '0x1', + type: '0x1', + v: '0x1', + value: '0xb1a2bc2ec50000', + yParity: '0x1', + }, + ], +}; diff --git a/mocks/blocks/epoch.ts b/mocks/blocks/epoch.ts new file mode 100644 index 0000000000..58f614fbe6 --- /dev/null +++ b/mocks/blocks/epoch.ts @@ -0,0 +1,57 @@ +import _padStart from 'lodash/padStart'; + +import type { BlockEpoch, BlockEpochElectionRewardDetails, BlockEpochElectionRewardDetailsResponse } from 'types/api/block'; + +import * as addressMock from '../address/address'; +import * as tokenMock from '../tokens/tokenInfo'; +import * as tokenTransferMock from '../tokens/tokenTransfer'; + +export const blockEpoch1: BlockEpoch = { + number: 1486, + distribution: { + carbon_offsetting_transfer: tokenTransferMock.erc20, + community_transfer: tokenTransferMock.erc20, + reserve_bolster_transfer: null, + }, + aggregated_election_rewards: { + delegated_payment: { + count: 0, + total: '71210001063118670575', + token: tokenMock.tokenInfoERC20d, + }, + group: { + count: 10, + total: '157705500305820107521', + token: tokenMock.tokenInfoERC20b, + }, + validator: { + count: 10, + total: '1348139501689262297152', + token: tokenMock.tokenInfoERC20c, + }, + voter: { + count: 38, + total: '2244419545166303388', + token: tokenMock.tokenInfoERC20a, + }, + }, +}; + +function getRewardDetailsItem(index: number): BlockEpochElectionRewardDetails { + return { + amount: `${ 100 - index }210001063118670575`, + account: { + ...addressMock.withoutName, + hash: `0x30D060F129817c4DE5fBc1366d53e19f43c8c6${ _padStart(String(index), 2, '0') }`, + }, + associated_account: { + ...addressMock.withoutName, + hash: `0x456f41406B32c45D59E539e4BBA3D7898c3584${ _padStart(String(index), 2, '0') }`, + }, + }; +} + +export const electionRewardDetails1: BlockEpochElectionRewardDetailsResponse = { + items: Array(15).fill('').map((item, index) => getRewardDetailsItem(index)), + next_page_params: null, +}; diff --git a/mocks/config/network.ts b/mocks/config/network.ts index a1c9075425..f5dbcf1157 100644 --- a/mocks/config/network.ts +++ b/mocks/config/network.ts @@ -1,6 +1,6 @@ import type { FeaturedNetwork } from 'types/networks'; -const FEATURED_NETWORKS: Array = [ +export const FEATURED_NETWORKS: Array = [ { title: 'Gnosis Chain', url: 'https://blockscout.com/xdai/mainnet', group: 'Mainnets', isActive: true }, { title: 'Arbitrum on xDai', url: 'https://blockscout.com/xdai/aox', group: 'Mainnets' }, { title: 'Ethereum', url: 'https://blockscout.com/eth/mainnet', group: 'Mainnets' }, @@ -13,5 +13,3 @@ const FEATURED_NETWORKS: Array = [ { title: 'LUKSO L14', url: 'https://blockscout.com/lukso/l14', group: 'Other' }, { title: 'Astar', url: 'https://blockscout.com/astar', group: 'Other' }, ]; - -export const FEATURED_NETWORKS_MOCK = JSON.stringify(FEATURED_NETWORKS); diff --git a/mocks/contract/audits.ts b/mocks/contract/audits.ts new file mode 100644 index 0000000000..a2a6644229 --- /dev/null +++ b/mocks/contract/audits.ts @@ -0,0 +1,16 @@ +import type { SmartContractSecurityAudits } from 'types/api/contract'; + +export const contractAudits: SmartContractSecurityAudits = { + items: [ + { + audit_company_name: 'OpenZeppelin', + audit_publish_date: '2023-03-01', + audit_report_url: 'https://blog.openzeppelin.com/eip-4337-ethereum-account-abstraction-incremental-audit', + }, + { + audit_company_name: 'OpenZeppelin', + audit_publish_date: '2023-03-01', + audit_report_url: 'https://blog.openzeppelin.com/eip-4337-ethereum-account-abstraction-incremental-audit', + }, + ], +}; diff --git a/mocks/contract/info.ts b/mocks/contract/info.ts index c5e1a7e040..715a908931 100644 --- a/mocks/contract/info.ts +++ b/mocks/contract/info.ts @@ -1,7 +1,7 @@ /* eslint-disable max-len */ import type { SmartContract } from 'types/api/contract'; -export const verified: Partial = { +export const verified: SmartContract = { abi: [ { anonymous: false, inputs: [ { indexed: true, internalType: 'address', name: 'src', type: 'address' }, { indexed: true, internalType: 'address', name: 'guy', type: 'address' }, { indexed: false, internalType: 'uint256', name: 'wad', type: 'uint256' } ], name: 'Approval', type: 'event' } ], can_be_visualized_via_sol2uml: true, compiler_version: 'v0.5.16+commit.9c3226ce', @@ -16,6 +16,7 @@ export const verified: Partial = { }, evm_version: 'default', is_verified: true, + is_blueprint: false, name: 'WPOA', optimization_enabled: true, optimization_runs: 1500, @@ -23,7 +24,7 @@ export const verified: Partial = { verified_at: '2021-08-03T10:40:41.679421Z', decoded_constructor_args: [ [ '0xc59615da2da226613b1c78f0c6676cac497910bc', { internalType: 'address', name: '_token', type: 'address' } ], - [ '1800', { internalType: 'uint256', name: '_duration', type: 'uint256' } ], + [ [ 1800, 3600, 7200 ], { internalType: 'uint256[]', name: '_durations', type: 'uint256[]' } ], [ '900000000', { internalType: 'uint256', name: '_totalSupply', type: 'uint256' } ], ], external_libraries: [ @@ -31,9 +32,26 @@ export const verified: Partial = { { address_hash: '0xa62744BeE8646e237441CDbfdedD3458861748A8', name: 'math' }, ], language: 'solidity', + license_type: 'gnu_gpl_v3', + is_self_destructed: false, + is_verified_via_eth_bytecode_db: null, + is_changed_bytecode: null, + is_verified_via_sourcify: null, + is_fully_verified: null, + is_partially_verified: null, + sourcify_repo_url: null, + file_path: '', + additional_sources: [], + verified_twin_address_hash: null, + proxy_type: null, }; -export const withMultiplePaths: Partial = { +export const certified: SmartContract = { + ...verified, + certified: true, +}; + +export const withMultiplePaths: SmartContract = { ...verified, file_path: './simple_storage.sol', additional_sources: [ @@ -44,7 +62,7 @@ export const withMultiplePaths: Partial = { ], }; -export const verifiedViaSourcify: Partial = { +export const verifiedViaSourcify: SmartContract = { ...verified, is_verified_via_sourcify: true, is_fully_verified: false, @@ -52,36 +70,83 @@ export const verifiedViaSourcify: Partial = { sourcify_repo_url: 'https://repo.sourcify.dev/contracts//full_match/99/0x51891596E158b2857e5356DC847e2D15dFbCF2d0/', }; -export const verifiedViaEthBytecodeDb: Partial = { +export const verifiedViaEthBytecodeDb: SmartContract = { ...verified, is_verified_via_eth_bytecode_db: true, }; -export const withTwinAddress: Partial = { +export const withTwinAddress: SmartContract = { ...verified, is_verified: false, verified_twin_address_hash: '0xa62744bee8646e237441cdbfdedd3458861748a8', }; -export const withProxyAddress: Partial = { +export const withProxyAddress: SmartContract = { ...verified, is_verified: false, verified_twin_address_hash: '0xa62744bee8646e237441cdbfdedd3458861748a8', - minimal_proxy_address_hash: '0xa62744bee8646e237441cdbfdedd3458861748a8', + proxy_type: 'eip1967', }; -export const selfDestructed: Partial = { +export const selfDestructed: SmartContract = { ...verified, is_self_destructed: true, }; -export const withChangedByteCode: Partial = { +export const withChangedByteCode: SmartContract = { ...verified, is_changed_bytecode: true, + is_blueprint: true, +}; + +export const zkSync: SmartContract = { + ...verified, + zk_compiler_version: 'v1.2.5', + optimization_enabled: true, + optimization_runs: 's', +}; + +export const stylusRust: SmartContract = { + ...verified, + language: 'stylus_rust', + github_repository_metadata: { + commit: 'af5029f822815e32def0015bf8e591e769c62f34', + path_prefix: 'examples/erc20', + repository_url: 'https://github.com/blockscout/cargo-stylus-test-examples', + }, + compiler_version: 'v0.5.6', + package_name: 'erc20', + evm_version: null, }; -export const nonVerified: Partial = { +export const nonVerified: SmartContract = { is_verified: false, + is_blueprint: false, creation_bytecode: 'creation_bytecode', deployed_bytecode: 'deployed_bytecode', + is_self_destructed: false, + abi: null, + compiler_version: null, + evm_version: null, + optimization_enabled: null, + optimization_runs: null, + name: null, + verified_at: null, + is_verified_via_eth_bytecode_db: null, + is_changed_bytecode: null, + is_verified_via_sourcify: null, + is_fully_verified: null, + is_partially_verified: null, + sourcify_repo_url: null, + source_code: null, + constructor_args: null, + decoded_constructor_args: null, + can_be_visualized_via_sol2uml: null, + file_path: '', + additional_sources: [], + external_libraries: null, + verified_twin_address_hash: null, + proxy_type: null, + language: null, + license_type: null, }; diff --git a/mocks/contract/methods.ts b/mocks/contract/methods.ts index 745c19770a..5fa714921c 100644 --- a/mocks/contract/methods.ts +++ b/mocks/contract/methods.ts @@ -1,11 +1,6 @@ -import type { - SmartContractQueryMethodReadError, - SmartContractQueryMethodReadSuccess, - SmartContractReadMethod, - SmartContractWriteMethod, -} from 'types/api/contract'; +import type { SmartContractMethodRead, SmartContractMethodWrite } from 'ui/address/contract/methods/types'; -export const read: Array = [ +export const read: Array = [ { constant: true, inputs: [ @@ -26,94 +21,15 @@ export const read: Array = [ method_id: '06fdde03', name: 'name', outputs: [ - { internalType: 'string', name: '', type: 'string', value: 'Wrapped POA' }, + { internalType: 'string', name: '', type: 'string' }, ], payable: false, stateMutability: 'view', type: 'function', }, - { - constant: true, - inputs: [], - method_id: '18160ddd', - name: 'totalSupply', - outputs: [ - { internalType: 'uint256', name: '', type: 'uint256', value: '139905710421584994690047413' }, - ], - payable: false, - stateMutability: 'view', - type: 'function', - }, - { - constant: true, - error: '(-32015) VM execution error. (revert)', - inputs: [], - method_id: 'df0ad3de', - name: 'upgradeabilityAdmin', - outputs: [ - { name: '', type: 'address', value: '' }, - ], - payable: false, - stateMutability: 'view', - type: 'function', - }, - { - constant: true, - inputs: [], - method_id: '165ec2e2', - name: 'arianeeWhitelist', - outputs: [ - { - name: '', - type: 'address', - value: '0xd3eee7f8e8021db24825c3457d5479f2b57f40ef', - }, - ], - payable: false, - stateMutability: 'view', - type: 'function', - }, - { - inputs: [], - method_id: '69598efe', - name: 'totalPartitions', - constant: true, - payable: false, - outputs: [ - { - type: 'bytes32[]', - name: 'bytes32[]', - value: [ - '0x7265736572766564000000000000000000000000000000000000000000000000', - '0x6973737565640000000000000000000000000000000000000000000000000000', - ], - }, - ], - stateMutability: 'view', - type: 'function', - }, ]; -export const readResultSuccess: SmartContractQueryMethodReadSuccess = { - is_error: false, - result: { - names: [ 'amount' ], - output: [ - { type: 'uint256', value: '42' }, - ], - }, -}; - -export const readResultError: SmartContractQueryMethodReadError = { - is_error: true, - result: { - message: 'Some shit happened', - code: -32017, - raw: '49276d20616c7761797320726576657274696e67207769746820616e206572726f72', - }, -}; - -export const write: Array = [ +export const write: Array = [ { payable: true, stateMutability: 'payable', @@ -132,6 +48,7 @@ export const write: Array = [ payable: false, stateMutability: 'nonpayable', type: 'function', + method_id: '0x01', }, { constant: false, @@ -146,6 +63,7 @@ export const write: Array = [ payable: true, stateMutability: 'payable', type: 'function', + method_id: '0x02', }, { stateMutability: 'payable', @@ -159,6 +77,7 @@ export const write: Array = [ payable: false, stateMutability: 'nonpayable', type: 'function', + method_id: '0x03', }, { constant: false, @@ -173,6 +92,7 @@ export const write: Array = [ payable: false, stateMutability: 'nonpayable', type: 'function', + method_id: '0x04', }, { constant: false, @@ -190,6 +110,7 @@ export const write: Array = [ payable: false, stateMutability: 'nonpayable', type: 'function', + method_id: '0x05', }, { constant: false, @@ -208,5 +129,6 @@ export const write: Array = [ payable: false, stateMutability: 'nonpayable', type: 'function', + is_invalid: true, }, ]; diff --git a/mocks/contract/solidityscanReport.ts b/mocks/contract/solidityscanReport.ts index 000adabf7f..e3d83d590d 100644 --- a/mocks/contract/solidityscanReport.ts +++ b/mocks/contract/solidityscanReport.ts @@ -1,5 +1,8 @@ -export const solidityscanReportAverage = { +import type { SolidityScanReport } from 'lib/solidityScan/schema'; + +export const solidityscanReportAverage: SolidityScanReport = { scan_report: { + contractname: 'foo', scan_status: 'scan_done', scan_summary: { issue_severity_distribution: { @@ -10,18 +13,15 @@ export const solidityscanReportAverage = { low: 2, medium: 0, }, - lines_analyzed_count: 18, - scan_time_taken: 1, - score: '3.61', score_v2: '72.22', - threat_score: '94.74', }, scanner_reference_url: 'https://solidityscan.com/quickscan/0xc1EF7811FF2ebFB74F80ed7423f2AdAA37454be2/blockscout/eth-goerli?ref=blockscout', }, }; -export const solidityscanReportGreat = { +export const solidityscanReportGreat: SolidityScanReport = { scan_report: { + contractname: 'foo', scan_status: 'scan_done', scan_summary: { issue_severity_distribution: { @@ -32,18 +32,15 @@ export const solidityscanReportGreat = { low: 0, medium: 0, }, - lines_analyzed_count: 18, - scan_time_taken: 1, - score: '3.61', score_v2: '100', - threat_score: '94.74', }, scanner_reference_url: 'https://solidityscan.com/quickscan/0xc1EF7811FF2ebFB74F80ed7423f2AdAA37454be2/blockscout/eth-goerli?ref=blockscout', }, }; -export const solidityscanReportLow = { +export const solidityscanReportLow: SolidityScanReport = { scan_report: { + contractname: 'foo', scan_status: 'scan_done', scan_summary: { issue_severity_distribution: { @@ -54,11 +51,7 @@ export const solidityscanReportLow = { low: 2, medium: 10, }, - lines_analyzed_count: 18, - scan_time_taken: 1, - score: '3.61', score_v2: '22.22', - threat_score: '94.74', }, scanner_reference_url: 'https://solidityscan.com/quickscan/0xc1EF7811FF2ebFB74F80ed7423f2AdAA37454be2/blockscout/eth-goerli?ref=blockscout', }, diff --git a/mocks/contracts/index.ts b/mocks/contracts/index.ts index 6db06926ba..06d6bfe580 100644 --- a/mocks/contracts/index.ts +++ b/mocks/contracts/index.ts @@ -3,7 +3,7 @@ import type { VerifiedContract, VerifiedContractsResponse } from 'types/api/cont export const contract1: VerifiedContract = { address: { hash: '0xef490030ac0d53B70E304b6Bc5bF657dc6780bEB', - implementation_name: null, + implementations: null, is_contract: true, is_verified: null, name: 'MockERC20', @@ -18,14 +18,15 @@ export const contract1: VerifiedContract = { language: 'solidity', market_cap: null, optimization_enabled: false, - tx_count: 7334224, + transaction_count: 7334224, verified_at: '2022-09-16T18:49:29.605179Z', + license_type: 'mit', }; export const contract2: VerifiedContract = { address: { hash: '0xB2218bdEbe8e90f80D04286772B0968ead666942', - implementation_name: null, + implementations: null, is_contract: true, is_verified: null, name: 'EternalStorageProxyWithSomeExternalLibrariesAndEvenMore', @@ -34,20 +35,48 @@ export const contract2: VerifiedContract = { watchlist_names: [], ens_domain_name: null, }, + certified: true, coin_balance: '9078234570352343999', compiler_version: 'v0.3.1+commit.0463ea4c', has_constructor_args: true, language: 'vyper', market_cap: null, optimization_enabled: true, - tx_count: 440, + transaction_count: 440, verified_at: '2021-09-07T20:01:56.076979Z', + license_type: 'bsd_3_clause', +}; + +export const contract3: VerifiedContract = { + address: { + ens_domain_name: null, + hash: '0xf145e3A26c6706F64d95Dc8d9d45022D8b3D676B', + implementations: [], + is_contract: true, + is_verified: true, + metadata: null, + name: 'StylusTestToken', + private_tags: [], + public_tags: [], + watchlist_names: [], + }, + certified: false, + coin_balance: '0', + compiler_version: 'v0.5.6', + has_constructor_args: false, + language: 'stylus_rust', + license_type: 'none', + market_cap: null, + optimization_enabled: false, + transaction_count: 0, + verified_at: '2024-12-03T14:05:42.796224Z', }; export const baseResponse: VerifiedContractsResponse = { items: [ contract1, contract2, + contract3, ], next_page_params: { items_count: '50', diff --git a/mocks/ens/domain.ts b/mocks/ens/domain.ts index 3126aaecf5..6cf446b833 100644 --- a/mocks/ens/domain.ts +++ b/mocks/ens/domain.ts @@ -1,17 +1,45 @@ -import type { EnsDomainDetailed } from 'types/api/ens'; +import * as bens from '@blockscout/bens-types'; -const domainTokenA = { +const domainTokenA: bens.Token = { id: '97352314626701792030827861137068748433918254309635329404916858191911576754327', contract_hash: '0x57f1887a8bf19b14fc0df6fd9b2acc9af147ea85', - type: 'NATIVE_DOMAIN_TOKEN' as const, + type: bens.TokenType.NATIVE_DOMAIN_TOKEN, }; const domainTokenB = { id: '423546333', contract_hash: '0x57f1887a8bf19b14fc0df6fd9b2acc9af147ea86', - type: 'WRAPPED_DOMAIN_TOKEN' as const, + type: bens.TokenType.WRAPPED_DOMAIN_TOKEN, }; -export const ensDomainA: EnsDomainDetailed = { +export const protocolA: bens.ProtocolInfo = { + id: 'ens', + short_name: 'ENS', + title: 'Ethereum Name Service', + description: 'The Ethereum Name Service (ENS) is a distributed, open, and extensible naming system based on the Ethereum blockchain.', + tld_list: [ + 'eth', + 'xyz', + ], + icon_url: 'https://i.imgur.com/GOfUwCb.jpeg', + docs_url: 'https://docs.ens.domains/', + deployment_blockscout_base_url: 'http://localhost:3200/', +}; + +export const protocolB: bens.ProtocolInfo = { + id: 'duck', + short_name: 'DUCK', + title: 'Duck Name Service', + description: '"Duck Name Service" is a cutting-edge blockchain naming service, providing seamless naming for crypto and decentralized applications. 🦆', + tld_list: [ + 'duck', + 'quack', + ], + icon_url: 'https://localhost:3000/duck.jpg', + docs_url: 'https://docs.duck.domains/', + deployment_blockscout_base_url: '', +}; + +export const ensDomainA: bens.DetailedDomain = { id: '0xb140bf9645e54f02ed3c1bcc225566b515a98d1688f10494a5c3bc5b447936a7', tokens: [ domainTokenA, @@ -27,7 +55,6 @@ export const ensDomainA: EnsDomainDetailed = { owner: { hash: '0x114d4603199df73e7d157787f8778e21fcd13066', }, - wrapped_owner: null, registration_date: '2021-06-27T13:34:44.000Z', expiry_date: '2025-03-01T14:20:24.000Z', other_addresses: { @@ -35,26 +62,38 @@ export const ensDomainA: EnsDomainDetailed = { GNO: 'DDAfbb505ad214D7b80b1f830fcCc89B60fb7A83', NEAR: 'a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48.factory.bridge.near', }, + protocol: protocolA, + resolver_address: { + hash: '0xD578780f1dA7404d9CC0eEbC9D684c140CC4b638', + }, + resolved_with_wildcard: true, + stored_offchain: true, + wrapped_owner: { + hash: '0xD4416b13d2b3a9aBae7AcD5D6C2BbDBE25686401', + }, }; -export const ensDomainB: EnsDomainDetailed = { +export const ensDomainB: bens.DetailedDomain = { id: '0x632ac7bec8e883416b371b36beaa822f4784208c99d063ee030020e2bd09b885', tokens: [ domainTokenA ], name: 'kitty.kitty.kitty.cat.eth', - resolved_address: null, + resolved_address: undefined, registrant: { hash: '0x114d4603199df73e7d157787f8778e21fcd13066', }, owner: { hash: '0x114d4603199df73e7d157787f8778e21fcd13066', }, - wrapped_owner: null, + wrapped_owner: undefined, registration_date: '2023-08-13T13:01:12.000Z', - expiry_date: null, + expiry_date: undefined, other_addresses: {}, + protocol: undefined, + resolved_with_wildcard: false, + stored_offchain: false, }; -export const ensDomainC: EnsDomainDetailed = { +export const ensDomainC: bens.DetailedDomain = { id: '0xdb7f351de6d93bda077a9211bdc49f249326d87932e4787d109b0262e9d189ad', tokens: [ domainTokenA ], name: 'duck.duck.eth', @@ -67,13 +106,16 @@ export const ensDomainC: EnsDomainDetailed = { owner: { hash: '0x114d4603199df73e7d157787f8778e21fcd13066', }, - wrapped_owner: null, + wrapped_owner: undefined, registration_date: '2022-04-24T07:34:44.000Z', expiry_date: '2022-11-01T13:10:36.000Z', other_addresses: {}, + protocol: undefined, + resolved_with_wildcard: false, + stored_offchain: false, }; -export const ensDomainD: EnsDomainDetailed = { +export const ensDomainD: bens.DetailedDomain = { id: '0xdb7f351de6d93bda077a9211bdc49f249326d87932e4787d109b0262e9d189ae', tokens: [ domainTokenA ], name: '🦆.duck.eth', @@ -83,9 +125,12 @@ export const ensDomainD: EnsDomainDetailed = { resolved_address: { hash: '0x114d4603199df73e7d157787f8778e21fcd13066', }, - owner: null, - wrapped_owner: null, + owner: undefined, + wrapped_owner: undefined, registration_date: '2022-04-24T07:34:44.000Z', expiry_date: '2027-09-23T13:10:36.000Z', other_addresses: {}, + protocol: undefined, + resolved_with_wildcard: false, + stored_offchain: false, }; diff --git a/mocks/ens/events.ts b/mocks/ens/events.ts index 2d60cc721a..d13fbcbe9a 100644 --- a/mocks/ens/events.ts +++ b/mocks/ens/events.ts @@ -1,6 +1,6 @@ -import type { EnsDomainEvent } from 'types/api/ens'; +import type * as bens from '@blockscout/bens-types'; -export const ensDomainEventA: EnsDomainEvent = { +export const ensDomainEventA: bens.DomainEvent = { transaction_hash: '0x86c66b9fad66e4f20d42a6eed4fe12a0f48a274786fd85e9d4aa6c60e84b5874', timestamp: '2021-06-27T13:34:44.000000Z', from_address: { @@ -9,7 +9,7 @@ export const ensDomainEventA: EnsDomainEvent = { action: '0xf7a16963', }; -export const ensDomainEventB = { +export const ensDomainEventB: bens.DomainEvent = { transaction_hash: '0x150bf7d5cd42457dd9c799ddd9d4bf6c30c703e1954a88c6d4b668b23fe0fbf8', timestamp: '2022-11-02T14:20:24.000000Z', from_address: { diff --git a/mocks/l2deposits/deposits.ts b/mocks/l2deposits/deposits.ts deleted file mode 100644 index b94c2dc657..0000000000 --- a/mocks/l2deposits/deposits.ts +++ /dev/null @@ -1,33 +0,0 @@ -export const data = { - items: [ - { - l1_block_number: 8382841, - l1_block_timestamp: '2022-05-27T01:13:48.000000Z', - l1_tx_hash: '0xaf3e5f4ef03eac22a622b3434c5dc9f4465aa291900a86bcf0ad9fb14429f05e', - l1_tx_origin: '0x6197d1eef304eb5284a0f6720f79403b4e9bf3a5', - l2_tx_gas_limit: '2156928', - l2_tx_hash: '0xb9212c76069b926917816767e4c5a0ef80e519b1ac1c3d3fb5818078f4984667', - }, - { - l1_block_number: 8382841, - l1_block_timestamp: '2022-05-27T01:13:48.000000Z', - l1_tx_hash: '0xa280f18cc72f9ad904087eb262c236048e935ad184a85bbd042d544c172c10bf', - l1_tx_origin: '0x6197d1eef304eb5284a0f6720f79403b4e9bf3a5', - l2_tx_gas_limit: '1216064', - l2_tx_hash: '0xaaaeb47a78b5c42d870f8d831a683a7cefe1b031a992170b28b43b82bd08318c', - }, - { - l1_block_number: 8382834, - l1_block_timestamp: '2022-06-27T01:11:48.000000Z', - l1_tx_hash: '0xfca8cc5440bffa8b975873c02bba3ff3380dd75fbc3260d10179e282cf72d6d4', - l1_tx_origin: '0x6197d1eef304eb5284a0f6720f79403b4e9bf3a5', - l2_tx_gas_limit: '405824', - l2_tx_hash: '0xa0604ebf2614ad708aeefa83f766fb25928dadb5ffb2f45028f5b4f1fa4d9358', - }, - ], - next_page_params: { - items_count: 50, - l1_block_number: 8382363, - tx_hash: '0x2012f0ce966ce6573e7826e9235f227edf5a2f8382b8d646c979f85a77e15c05', - }, -}; diff --git a/mocks/l2outputRoots/outputRoots.ts b/mocks/l2outputRoots/outputRoots.ts deleted file mode 100644 index 9f21572297..0000000000 --- a/mocks/l2outputRoots/outputRoots.ts +++ /dev/null @@ -1,32 +0,0 @@ -export const outputRootsData = { - items: [ - { - l1_block_number: 8456113, - l1_timestamp: '2022-02-08T12:08:48.000000Z', - l1_tx_hash: '0x19455a53758d5de89070164ff09c40d93f1b4447e721090f03aa150f6159265a', - l2_block_number: 5214988, - l2_output_index: 9926, - output_root: '0xa7de9bd3986ce5ca8de9f0ab6c7473f4cebe225fb13b57cc5c8472de84a8bab3', - }, - { - l1_block_number: 8456099, - l1_timestamp: '2022-02-08T12:05:24.000000Z', - l1_tx_hash: '0x6aa081e8e33a085e4ec7124fcd8a5f7d36aac0828f176e80d4b70e313a11695b', - l2_block_number: 5214868, - l2_output_index: 9925, - output_root: '0x4ec2822d2f7b4f834d693d88f8a4cf15899882915980a21756d29cfd9f9f3898', - }, - { - l1_block_number: 8456078, - l1_timestamp: '2022-02-08T12:00:48.000000Z', - l1_tx_hash: '0x4238988b0959e41a7b09cef67f58698e05e3bcc29b8d2f60e6c77dc68c91f16e', - l2_block_number: 5214748, - l2_output_index: 9924, - output_root: '0x78b2e13c20f4bbfb4a008127edaaf25aa476f933669edd4856305bf4ab64a92b', - }, - ], - next_page_params: { - index: 9877, - items_count: 50, - }, -}; diff --git a/mocks/l2txnBatches/txnBatches.ts b/mocks/l2txnBatches/txnBatches.ts deleted file mode 100644 index 4459c2b57f..0000000000 --- a/mocks/l2txnBatches/txnBatches.ts +++ /dev/null @@ -1,36 +0,0 @@ -export const txnBatchesData = { - items: [ - { - epoch_number: 8547349, - l1_tx_hashes: [ - '0x5bc94d02b65743dfaa9e10a2d6e175aff2a05cce2128c8eaf848bd84ab9325c5', - '0x92a51bc623111dbb91f243e3452e60fab6f090710357f9d9b75ac8a0f67dfd9d', - ], - l1_timestamp: '2023-02-24T10:16:12.000000Z', - l2_block_number: 5902836, - tx_count: 0, - }, - { - epoch_number: 8547348, - l1_tx_hashes: [ - '0xc45f846ee28ce9ba116ce2d378d3dd00b55d324b833b3ecd4241c919c572c4aa', - ], - l1_timestamp: '2023-02-24T10:16:00.000000Z', - l2_block_number: 5902835, - tx_count: 0, - }, - { - epoch_number: 8547348, - l1_tx_hashes: [ - '0x48139721f792d3a68c3781b4cf50e66e8fc7dbb38adff778e09066ea5be9adb8', - ], - l1_timestamp: '2023-02-24T10:16:00.000000Z', - l2_block_number: 5902834, - tx_count: 0, - }, - ], - next_page_params: { - block_number: 5902834, - items_count: 50, - }, -}; diff --git a/mocks/l2withdrawals/withdrawals.ts b/mocks/l2withdrawals/withdrawals.ts deleted file mode 100644 index 0e0d69a22f..0000000000 --- a/mocks/l2withdrawals/withdrawals.ts +++ /dev/null @@ -1,50 +0,0 @@ -export const data = { - items: [ - { - challenge_period_end: null, - from: { - hash: '0x67aab90c548b284be30b05c376001b4db90b87ba', - implementation_name: null, - is_contract: false, - is_verified: false, - name: null, - private_tags: [], - public_tags: [], - watchlist_names: [], - }, - l1_tx_hash: '0x1a235bee32ac10cb7efdad98415737484ca66386e491cde9e17d42b136dca684', - l2_timestamp: '2022-02-15T12:50:02.000000Z', - l2_tx_hash: '0x918cd8c5c24c17e06cd02b0379510c4ad56324bf153578fb9caaaa2fe4e7dc35', - msg_nonce: 396, - msg_nonce_raw: '1766847064778384329583297500742918515827483896875618958121606201292620172', - msg_nonce_version: 1, - status: 'Ready to prove', - }, - { - challenge_period_end: null, - from: null, - l1_tx_hash: null, - l2_timestamp: null, - l2_tx_hash: '0x2f117bee32ac10cb7efdad98415737484ca66386e491cde9e17d42b136def593', - msg_nonce: 391, - msg_nonce_raw: '1766847064778384329583297500742918515827483896875618958121606201292620167', - msg_nonce_version: 1, - status: 'Ready to prove', - }, - { - challenge_period_end: '2022-11-11T12:50:02.000000Z', - from: null, - l1_tx_hash: null, - l2_timestamp: null, - l2_tx_hash: '0xe14b1f46838176702244a5343629bcecf728ca2d9881d47b4ce46e00c387d7e3', - msg_nonce: 390, - msg_nonce_raw: '1766847064778384329583297500742918515827483896875618958121606201292620166', - msg_nonce_version: 1, - status: 'Ready for relay', - }, - ], - next_page_params: { - items_count: 50, - nonce: '1766847064778384329583297500742918515827483896875618958121606201292620123', - }, -}; diff --git a/mocks/metadata/address.ts b/mocks/metadata/address.ts new file mode 100644 index 0000000000..186711cc04 --- /dev/null +++ b/mocks/metadata/address.ts @@ -0,0 +1,115 @@ +/* eslint-disable max-len */ +import type { AddressMetadataTagApi } from 'types/api/addressMetadata'; + +export const nameTag: AddressMetadataTagApi = { + slug: 'quack-quack', + name: 'Quack quack', + tagType: 'name', + ordinal: 99, + meta: null, +}; + +export const customNameTag: AddressMetadataTagApi = { + slug: 'unicorn-uproar', + name: 'Unicorn Uproar', + tagType: 'name', + ordinal: 777, + meta: { + tagUrl: 'https://example.com', + bgColor: 'linear-gradient(45deg, deeppink, deepskyblue)', + textColor: '#FFFFFF', + }, +}; + +export const genericTag: AddressMetadataTagApi = { + slug: 'duck-owner', + name: 'duck owner 🦆', + tagType: 'generic', + ordinal: 55, + meta: { + bgColor: 'rgba(255,243,12,90%)', + }, +}; + +export const infoTagWithLink: AddressMetadataTagApi = { + slug: 'goosegang', + name: 'GooseGanG GooseGanG GooseGanG GooseGanG GooseGanG GooseGanG GooseGanG', + tagType: 'classifier', + ordinal: 11, + meta: { + tagUrl: 'https://example.com', + }, +}; + +export const tagWithTooltip: AddressMetadataTagApi = { + slug: 'blockscout-heroes', + name: 'BlockscoutHeroes', + tagType: 'classifier', + ordinal: 42, + meta: { + tooltipDescription: 'The Blockscout team, EVM blockchain aficionados, illuminate Ethereum networks with unparalleled insight and prowess, leading the way in blockchain exploration! 🚀🔎', + tooltipIcon: 'https://localhost:3100/icon.svg', + tooltipTitle: 'Blockscout team member', + tooltipUrl: 'https://blockscout.com', + }, +}; + +export const protocolTag: AddressMetadataTagApi = { + slug: 'aerodrome', + name: 'Aerodrome', + tagType: 'protocol', + ordinal: 0, + meta: null, +}; + +export const protocolTagWithMeta: AddressMetadataTagApi = { + slug: 'uniswap', + name: 'Uniswap', + tagType: 'protocol', + ordinal: 0, + meta: { + appID: 'uniswap', + appMarketplaceURL: 'https://example.com', + appLogoURL: 'https://localhost:3100/icon.svg', + appActionButtonText: 'Swap', + textColor: '#FFFFFF', + bgColor: '#FF007A', + }, +}; + +export const warpcastTag: AddressMetadataTagApi = { + slug: 'warpcast-account', + name: 'Farcaster', + tagType: 'protocol', + ordinal: 0, + meta: { + bgColor: '#8465CB', + tagIcon: 'data:image/svg+xml;utf8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20xmlns%3Axlink%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink%22%20viewBox%3D%220%200%2032%2029%22%3E%3Cpath%20d%3D%22M%205.507%200.072%20L%2026.097%200.072%20L%2026.097%204.167%20L%2031.952%204.167%20L%2030.725%208.263%20L%2029.686%208.263%20L%2029.686%2024.833%20C%2030.207%2024.833%2030.63%2025.249%2030.63%2025.763%20L%2030.63%2026.88%20L%2030.819%2026.88%20C%2031.341%2026.88%2031.764%2027.297%2031.764%2027.811%20L%2031.764%2028.928%20L%2021.185%2028.928%20L%2021.185%2027.811%20C%2021.185%2027.297%2021.608%2026.88%2022.13%2026.88%20L%2022.319%2026.88%20L%2022.319%2025.763%20C%2022.319%2025.316%2022.639%2024.943%2023.065%2024.853%20L%2023.045%2015.71%20C%2022.711%2012.057%2019.596%209.194%2015.802%209.194%20C%2012.008%209.194%208.893%2012.057%208.559%2015.71%20L%208.539%2024.845%20C%209.043%2024.919%209.663%2025.302%209.663%2025.763%20L%209.663%2026.88%20L%209.852%2026.88%20C%2010.373%2026.88%2010.796%2027.297%2010.796%2027.811%20L%2010.796%2028.928%20L%200.218%2028.928%20L%200.218%2027.811%20C%200.218%2027.297%200.641%2026.88%201.162%2026.88%20L%201.351%2026.88%20L%201.351%2025.763%20C%201.351%2025.249%201.774%2024.833%202.296%2024.833%20L%202.296%208.263%20L%201.257%208.263%20L%200.029%204.167%20L%205.507%204.167%20L%205.507%200.072%20Z%22%20fill%3D%22rgb(255%2C%20255%2C%20255)%22%3E%3C%2Fpath%3E%3Cpath%20d%3D%22M%2026.097%200.072%20L%2026.166%200.072%20L%2026.166%200.004%20L%2026.097%200.004%20Z%20M%205.507%200.072%20L%205.507%200.004%20L%205.438%200.004%20L%205.438%200.072%20Z%20M%2026.097%204.167%20L%2026.028%204.167%20L%2026.028%204.235%20L%2026.097%204.235%20Z%20M%2031.952%204.167%20L%2032.019%204.187%20L%2032.045%204.099%20L%2031.952%204.099%20L%2031.952%204.167%20Z%20M%2030.725%208.263%20L%2030.725%208.331%20L%2030.776%208.331%20L%2030.791%208.282%20Z%20M%2029.686%208.263%20L%2029.686%208.195%20L%2029.617%208.195%20L%2029.617%208.263%20Z%20M%2029.686%2024.833%20L%2029.617%2024.833%20L%2029.617%2024.901%20L%2029.686%2024.901%20Z%20M%2030.63%2026.88%20L%2030.561%2026.88%20L%2030.561%2026.948%20L%2030.63%2026.948%20Z%20M%2031.764%2028.928%20L%2031.764%2028.996%20L%2031.832%2028.996%20L%2031.832%2028.928%20Z%20M%2021.185%2028.928%20L%2021.116%2028.928%20L%2021.116%2028.996%20L%2021.185%2028.996%20Z%20M%2022.319%2026.88%20L%2022.319%2026.948%20L%2022.388%2026.948%20L%2022.388%2026.88%20Z%20M%2023.065%2024.853%20L%2023.08%2024.919%20L%2023.134%2024.908%20L%2023.134%2024.853%20Z%20M%2023.045%2015.71%20L%2023.114%2015.71%20L%2023.114%2015.707%20L%2023.113%2015.704%20Z%20M%208.559%2015.71%20L%208.49%2015.704%20L%208.49%2015.707%20L%208.49%2015.71%20Z%20M%208.539%2024.845%20L%208.47%2024.845%20L%208.469%2024.904%20L%208.528%2024.913%20Z%20M%209.663%2026.88%20L%209.594%2026.88%20L%209.594%2026.948%20L%209.663%2026.948%20Z%20M%2010.796%2028.928%20L%2010.796%2028.996%20L%2010.865%2028.996%20L%2010.865%2028.928%20Z%20M%200.218%2028.928%20L%200.149%2028.928%20L%200.149%2028.996%20L%200.218%2028.996%20Z%20M%201.351%2026.88%20L%201.351%2026.948%20L%201.42%2026.948%20L%201.42%2026.88%20Z%20M%202.296%2024.833%20L%202.296%2024.901%20L%202.365%2024.901%20L%202.365%2024.833%20Z%20M%202.296%208.263%20L%202.365%208.263%20L%202.365%208.195%20L%202.296%208.195%20Z%20M%201.257%208.263%20L%201.191%208.282%20L%201.205%208.331%20L%201.257%208.331%20Z%20M%200.029%204.167%20L%200.029%204.1%20L%20-0.063%204.1%20L%20-0.037%204.187%20L%200.029%204.167%20Z%20M%205.507%204.167%20L%205.507%204.235%20L%205.576%204.235%20L%205.576%204.167%20Z%20M%2026.097%200.004%20L%205.507%200.004%20L%205.507%200.139%20L%2026.097%200.139%20Z%20M%2026.166%204.167%20L%2026.166%200.072%20L%2026.028%200.072%20L%2026.028%204.167%20L%2026.166%204.167%20Z%20M%2031.952%204.099%20L%2026.097%204.099%20L%2026.097%204.235%20L%2031.952%204.235%20Z%20M%2030.791%208.282%20L%2032.019%204.187%20L%2031.886%204.148%20L%2030.658%208.244%20Z%20M%2029.686%208.331%20L%2030.725%208.331%20L%2030.725%208.195%20L%2029.686%208.195%20Z%20M%2029.755%2024.833%20L%2029.755%208.263%20L%2029.617%208.263%20L%2029.617%2024.833%20Z%20M%2030.699%2025.763%20C%2030.699%2025.212%2030.245%2024.765%2029.686%2024.765%20L%2029.686%2024.9%20C%2030.169%2024.9%2030.561%2025.287%2030.561%2025.763%20Z%20M%2030.699%2026.88%20L%2030.699%2025.763%20L%2030.561%2025.763%20L%2030.561%2026.88%20Z%20M%2030.819%2026.813%20L%2030.63%2026.813%20L%2030.63%2026.948%20L%2030.819%2026.948%20Z%20M%2031.832%2027.811%20C%2031.832%2027.26%2031.379%2026.813%2030.819%2026.813%20L%2030.819%2026.948%20C%2031.303%2026.948%2031.695%2027.335%2031.695%2027.811%20Z%20M%2031.832%2028.928%20L%2031.832%2027.811%20L%2031.695%2027.811%20L%2031.695%2028.928%20Z%20M%2026.097%2028.996%20L%2031.764%2028.996%20L%2031.764%2028.86%20L%2026.097%2028.86%20Z%20M%2023.074%2028.996%20L%2026.097%2028.996%20L%2026.097%2028.86%20L%2023.074%2028.86%20Z%20M%2021.185%2028.996%20L%2023.074%2028.996%20L%2023.074%2028.86%20L%2021.185%2028.86%20Z%20M%2021.116%2027.811%20L%2021.116%2028.928%20L%2021.254%2028.928%20L%2021.254%2027.811%20Z%20M%2022.13%2026.813%20C%2021.57%2026.813%2021.116%2027.26%2021.116%2027.811%20L%2021.254%2027.811%20C%2021.254%2027.335%2021.646%2026.948%2022.13%2026.948%20Z%20M%2022.319%2026.813%20L%2022.13%2026.813%20L%2022.13%2026.948%20L%2022.319%2026.948%20Z%20M%2022.25%2025.763%20L%2022.25%2026.88%20L%2022.388%2026.88%20L%2022.388%2025.763%20Z%20M%2023.051%2024.787%20C%2022.593%2024.883%2022.25%2025.284%2022.25%2025.763%20L%2022.388%2025.763%20C%2022.388%2025.349%2022.684%2025.003%2023.08%2024.919%20Z%20M%2022.976%2015.71%20L%2022.996%2024.853%20L%2023.134%2024.853%20L%2023.114%2015.71%20Z%20M%2015.802%209.262%20C%2019.559%209.262%2022.645%2012.098%2022.976%2015.716%20L%2023.113%2015.704%20C%2022.776%2012.016%2019.632%209.126%2015.802%209.126%20Z%20M%208.628%2015.716%20C%208.959%2012.098%2012.044%209.262%2015.802%209.262%20L%2015.802%209.126%20C%2011.972%209.126%208.828%2012.016%208.49%2015.704%20Z%20M%208.608%2024.845%20L%208.628%2015.71%20L%208.49%2015.71%20L%208.47%2024.845%20Z%20M%209.732%2025.763%20C%209.732%2025.502%209.557%2025.273%209.331%2025.105%20C%209.104%2024.935%208.812%2024.817%208.549%2024.778%20L%208.528%2024.912%20C%208.769%2024.948%209.039%2025.057%209.248%2025.213%20C%209.459%2025.37%209.594%2025.563%209.594%2025.763%20Z%20M%209.732%2026.88%20L%209.732%2025.763%20L%209.594%2025.763%20L%209.594%2026.88%20Z%20M%209.852%2026.813%20L%209.663%2026.813%20L%209.663%2026.948%20L%209.852%2026.948%20Z%20M%2010.865%2027.811%20C%2010.865%2027.26%2010.411%2026.813%209.852%2026.813%20L%209.852%2026.948%20C%2010.335%2026.948%2010.727%2027.335%2010.727%2027.811%20Z%20M%2010.865%2028.928%20L%2010.865%2027.811%20L%2010.727%2027.811%20L%2010.727%2028.928%20Z%20M%208.529%2028.996%20L%2010.796%2028.996%20L%2010.796%2028.86%20L%208.529%2028.86%20Z%20M%208.372%2028.996%20L%208.529%2028.996%20L%208.529%2028.86%20L%208.372%2028.86%20Z%20M%205.507%2028.996%20L%208.372%2028.996%20L%208.372%2028.86%20L%205.507%2028.86%20Z%20M%200.218%2028.996%20L%205.507%2028.996%20L%205.507%2028.86%20L%200.218%2028.86%20Z%20M%200.149%2027.811%20L%200.149%2028.928%20L%200.287%2028.928%20L%200.287%2027.811%20Z%20M%201.162%2026.813%20C%200.603%2026.813%200.149%2027.26%200.149%2027.811%20L%200.287%2027.811%20C%200.287%2027.335%200.679%2026.948%201.162%2026.948%20Z%20M%201.351%2026.813%20L%201.162%2026.813%20L%201.162%2026.948%20L%201.351%2026.948%20Z%20M%201.282%2025.763%20L%201.282%2026.88%20L%201.42%2026.88%20L%201.42%2025.763%20Z%20M%202.296%2024.765%20C%201.736%2024.765%201.282%2025.212%201.282%2025.763%20L%201.42%2025.763%20C%201.42%2025.287%201.812%2024.9%202.296%2024.9%20Z%20M%202.227%208.263%20L%202.227%2024.833%20L%202.365%2024.833%20L%202.365%208.263%20Z%20M%201.257%208.331%20L%202.296%208.331%20L%202.296%208.195%20L%201.257%208.195%20Z%20M%20-0.037%204.187%20L%201.191%208.282%20L%201.323%208.244%20L%200.095%204.148%20Z%20M%205.507%204.099%20L%200.029%204.099%20L%200.029%204.235%20L%205.507%204.235%20L%205.507%204.099%20Z%20M%205.438%200.072%20L%205.438%204.167%20L%205.576%204.167%20L%205.576%200.072%20Z%22%20fill%3D%22rgb(255%2C255%2C255)%22%3E%3C%2Fpath%3E%3C%2Fsvg%3E', tagUrl: 'https://warpcast.com/mbj357', + textColor: '#FFFFFF', + tooltipDescription: 'This address is linked to a Farcaster account', + warpcastHandle: 'duckYduck', + }, +}; + +export const noteTag: AddressMetadataTagApi = { + slug: 'scam-tag', + name: 'Phish 🐟', + tagType: 'note', + ordinal: 100, + meta: { + alertBgColor: 'deeppink', + alertTextColor: 'white', + data: 'Warning! This is scam! See the report', + }, +}; + +export const noteTag2: AddressMetadataTagApi = { + slug: 'note0', + name: 'note_0', + tagType: 'note', + ordinal: 0, + meta: { + alertStatus: 'info', + data: 'The token MILF was launched on May 13, 2021. The maximum total supply of the token is 100 billion.', + }, +}; diff --git a/mocks/metadata/appActionButton.ts b/mocks/metadata/appActionButton.ts new file mode 100644 index 0000000000..47938638cd --- /dev/null +++ b/mocks/metadata/appActionButton.ts @@ -0,0 +1,38 @@ +import type { AddressMetadataTagApi } from 'types/api/addressMetadata'; + +const appID = 'uniswap'; +const appMarketplaceURL = 'https://example.com'; +export const appLogoURL = 'https://localhost:3100/icon.svg'; +const appActionButtonText = 'Swap'; +const textColor = '#FFFFFF'; +const bgColor = '#FF007A'; + +export const buttonWithoutStyles: AddressMetadataTagApi['meta'] = { + appID, + appMarketplaceURL, + appLogoURL, + appActionButtonText, +}; + +export const linkWithoutStyles: AddressMetadataTagApi['meta'] = { + appMarketplaceURL, + appLogoURL, + appActionButtonText, +}; + +export const buttonWithStyles: AddressMetadataTagApi['meta'] = { + appID, + appMarketplaceURL, + appLogoURL, + appActionButtonText, + textColor, + bgColor, +}; + +export const linkWithStyles: AddressMetadataTagApi['meta'] = { + appMarketplaceURL, + appLogoURL, + appActionButtonText, + textColor, + bgColor, +}; diff --git a/mocks/metadata/publicTagTypes.ts b/mocks/metadata/publicTagTypes.ts new file mode 100644 index 0000000000..80bf92acba --- /dev/null +++ b/mocks/metadata/publicTagTypes.ts @@ -0,0 +1,34 @@ +export const publicTagTypes = { + tagTypes: [ + { + id: '96f9db76-02fc-477d-a003-640a0c5e7e15', + type: 'name' as const, + description: 'Alias for the address', + }, + { + id: 'e75f396e-f52a-44c9-8790-a1dbae496b72', + type: 'generic' as const, + description: 'Group classification for the address', + }, + { + id: '11a2d4f3-412e-4eb7-b663-86c6f48cdec3', + type: 'information' as const, + description: 'Tags with custom data for the address, e.g. additional link to project, or classification details, or minor account details', + }, + { + id: 'd37443d4-748f-4314-a4a0-283b666e9f29', + type: 'classifier' as const, + description: 'E.g. "ERC20", "Contract", "CEX", "DEX", "NFT"', + }, + { + id: 'ea9d0f91-9b46-44ff-be70-128bac468f6f', + type: 'protocol' as const, + description: 'Special tag type for protocol-related contracts, e.g. for bridges', + }, + { + id: 'd2600acb-473c-445f-ac72-ed6fef53e06a', + type: 'note' as const, + description: 'Short general-purpose description for the address', + }, + ], +}; diff --git a/mocks/mud/mudTables.ts b/mocks/mud/mudTables.ts new file mode 100644 index 0000000000..b5f0186c91 --- /dev/null +++ b/mocks/mud/mudTables.ts @@ -0,0 +1,94 @@ +/* eslint-disable max-len */ +import type { AddressMudRecord, AddressMudRecords, AddressMudRecordsItem, AddressMudTables } from 'types/api/address'; +import type { MudWorldSchema, MudWorldTable } from 'types/api/mudWorlds'; + +export const table1: MudWorldTable = { + table_full_name: 'tb.store.Tables', + table_id: '0x746273746f72650000000000000000005461626c657300000000000000000000', + table_name: 'Tables', + table_namespace: 'store', + table_type: 'onchain', +}; + +export const table2: MudWorldTable = { + table_full_name: 'ot.world.FunctionSignatur', + table_id: '0x6f74776f726c6400000000000000000046756e6374696f6e5369676e61747572', + table_name: 'FunctionSignatur', + table_namespace: 'world', + table_type: 'offchain', +}; + +export const schema1: MudWorldSchema = { + key_names: [ 'moduleAddress', 'argumentsHash' ], + key_types: [ 'address', 'bytes32' ], + value_names: [ 'fieldLayout', 'keySchema', 'valueSchema', 'abiEncodedKeyNames', 'abiEncodedFieldNames' ], + value_types: [ 'bytes32', 'bytes32', 'bytes32', 'bytes', 'bytes' ], +}; + +export const schema2: MudWorldSchema = { + key_names: [], + key_types: [], + value_names: [ 'value' ], + value_types: [ 'address' ], +}; + +export const mudTables: AddressMudTables = { + items: [ + { + table: table1, + schema: schema1, + }, + { + table: table2, + schema: schema2, + }, + ], + next_page_params: { + items_count: 50, + table_id: '1', + }, +}; + +const record: AddressMudRecordsItem = { + decoded: { + abiEncodedFieldNames: '0x00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000e00000000000000000000000000000000000000000000000000000000000000006706c617965720000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000576616c7565000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000974696d657374616d700000000000000000000000000000000000000000000000', + abiEncodedKeyNames: '0x00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000026964000000000000000000000000000000000000000000000000000000000000', + goldCosts: [ '100000', '150000', '200000', '250000', '400000', '550000', '700000' ], + prototypeIds: [ + '0x53776f7264736d616e0000000000000000000000000000000000000000000000', + '0x50696b656d616e00000000000000000000000000000000000000000000000000', + '0x50696b656d616e00000000000000000000000000000000000000000000000000', + '0x4172636865720000000000000000000000000000000000000000000000000000', + '0x4b6e696768740000000000000000000000000000000000000000000000000000', + ], + keySchema: '0x002001001f000000000000000000000000000000000000000000000000000000', + tableId: '0x6f74000000000000000000000000000044726177557064617465000000000000', + valueSchema: '0x00540300611f1f00000000000000000000000000000000000000000000000000', + }, + id: '0x007a651a000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007', + is_deleted: false, + timestamp: '2024-05-09T15:14:32.000000Z', +}; + +export const mudRecords: AddressMudRecords = { + items: [ record, record ], + next_page_params: { + items_count: 50, + key0: '1', + key1: '2', + key_bytes: '3', + }, + schema: { + key_names: [ 'tableId' ], + key_types: [ 'bytes32' ], + value_names: [ 'prototypeIds', 'goldCosts', 'keySchema', 'valueSchema', 'abiEncodedKeyNames', 'abiEncodedFieldNames' ], + value_types: [ 'bytes32[]', 'int32[]', 'bytes32', 'bytes32', 'bytes32', 'bytes', 'bytes' ], + }, + table: table1, +}; + +export const mudRecord: AddressMudRecord = { + record, + schema: mudRecords.schema, + table: table1, +}; diff --git a/mocks/mud/mudWorlds.ts b/mocks/mud/mudWorlds.ts new file mode 100644 index 0000000000..beb384a33c --- /dev/null +++ b/mocks/mud/mudWorlds.ts @@ -0,0 +1,27 @@ +import type { MudWorldsResponse } from 'types/api/mudWorlds'; + +import { withName, withoutName } from 'mocks/address/address'; + +export const mudWorlds: MudWorldsResponse = { + items: [ + { + address: withName, + coin_balance: '300000000000000000', + transaction_count: 3938, + }, + { + address: withoutName, + coin_balance: '0', + transaction_count: 0, + }, + { + address: withoutName, + coin_balance: '0', + transaction_count: 0, + }, + ], + next_page_params: { + items_count: 50, + world: '0x18f01f12ca21b6fc97b917c3e32f671f8a933caa', + }, +}; diff --git a/mocks/noves/transaction.ts b/mocks/noves/transaction.ts new file mode 100644 index 0000000000..6feb72a564 --- /dev/null +++ b/mocks/noves/transaction.ts @@ -0,0 +1,103 @@ +import type { NovesResponseData } from 'types/api/noves'; + +import type { TokensData } from 'ui/tx/assetFlows/utils/getTokensData'; + +export const hash = '0x380400d04ebb4179a35b1d7fdef260776915f015e978f8587ef2704b843d4e53'; + +export const transaction: NovesResponseData = { + accountAddress: '0xef6595A423c99f3f2821190A4d96fcE4DcD89a80', + chain: 'eth-goerli', + classificationData: { + description: 'Called function \'stake\' on contract 0xef326CdAdA59D3A740A76bB5f4F88Fb2.', + protocol: { + name: null, + }, + received: [], + sent: [ + { + action: 'sent', + actionFormatted: 'Sent', + amount: '3000', + from: { + address: '0xef6595A423c99f3f2821190A4d96fcE4DcD89a80', + name: 'This wallet', + }, + to: { + address: '0xdD15D2650387Fb6FEDE27ae7392C402a393F8A37', + name: null, + }, + token: { + address: '0x1bfe4298796198f8664b18a98640cec7c89b5baa', + decimals: 18, + name: 'PQR-Test', + symbol: 'PQR', + }, + }, + { + action: 'paidGas', + actionFormatted: 'Paid Gas', + amount: '0.000395521502109448', + from: { + address: '0xef6595A423c99f3f2821190A4d96fcE4DcD89a80', + name: 'This wallet', + }, + to: { + address: null, + name: 'Validators', + }, + token: { + address: 'ETH', + decimals: 18, + name: 'ETH', + symbol: 'ETH', + }, + }, + ], + source: { + type: null, + }, + type: 'unclassified', + typeFormatted: 'Unclassified', + }, + rawTransactionData: { + blockNumber: 10388918, + fromAddress: '0xef6595A423c99f3f2821190A4d96fcE4DcD89a80', + gas: 275079, + gasPrice: 1500000008, + timestamp: 1705488588, + toAddress: '0xef326CdAdA59D3A740A76bB5f4F88Fb2f1076164', + transactionFee: { + amount: '395521502109448', + token: { + decimals: 18, + symbol: 'ETH', + }, + }, + transactionHash: '0x380400d04ebb4179a35b1d7fdef260776915f015e978f8587ef2704b843d4e53', + }, + txTypeVersion: 2, +}; + +export const tokenData: TokensData = { + nameList: [ 'PQR-Test', 'ETH' ], + symbolList: [ 'PQR' ], + idList: [], + byName: { + 'PQR-Test': { + name: 'PQR-Test', + symbol: 'PQR', + address: '0x1bfe4298796198f8664b18a98640cec7c89b5baa', + id: undefined, + }, + ETH: { name: 'ETH', symbol: null, address: '', id: undefined }, + }, + bySymbol: { + PQR: { + name: 'PQR-Test', + symbol: 'PQR', + address: '0x1bfe4298796198f8664b18a98640cec7c89b5baa', + id: undefined, + }, + 'null': { name: 'ETH', symbol: null, address: '', id: undefined }, + }, +}; diff --git a/mocks/optimism/deposits.ts b/mocks/optimism/deposits.ts new file mode 100644 index 0000000000..e2f0ba1e9e --- /dev/null +++ b/mocks/optimism/deposits.ts @@ -0,0 +1,33 @@ +export const data = { + items: [ + { + l1_block_number: 8382841, + l1_block_timestamp: '2022-05-27T01:13:48.000000Z', + l1_transaction_hash: '0xaf3e5f4ef03eac22a622b3434c5dc9f4465aa291900a86bcf0ad9fb14429f05e', + l1_transaction_origin: '0x6197d1eef304eb5284a0f6720f79403b4e9bf3a5', + l2_transaction_gas_limit: '2156928', + l2_transaction_hash: '0xb9212c76069b926917816767e4c5a0ef80e519b1ac1c3d3fb5818078f4984667', + }, + { + l1_block_number: 8382841, + l1_block_timestamp: '2022-05-27T01:13:48.000000Z', + l1_transaction_hash: '0xa280f18cc72f9ad904087eb262c236048e935ad184a85bbd042d544c172c10bf', + l1_transaction_origin: '0x6197d1eef304eb5284a0f6720f79403b4e9bf3a5', + l2_transaction_gas_limit: '1216064', + l2_transaction_hash: '0xaaaeb47a78b5c42d870f8d831a683a7cefe1b031a992170b28b43b82bd08318c', + }, + { + l1_block_number: 8382834, + l1_block_timestamp: '2022-06-27T01:11:48.000000Z', + l1_transaction_hash: '0xfca8cc5440bffa8b975873c02bba3ff3380dd75fbc3260d10179e282cf72d6d4', + l1_transaction_origin: '0x6197d1eef304eb5284a0f6720f79403b4e9bf3a5', + l2_transaction_gas_limit: '405824', + l2_transaction_hash: '0xa0604ebf2614ad708aeefa83f766fb25928dadb5ffb2f45028f5b4f1fa4d9358', + }, + ], + next_page_params: { + items_count: 50, + l1_block_number: 8382363, + transaction_hash: '0x2012f0ce966ce6573e7826e9235f227edf5a2f8382b8d646c979f85a77e15c05', + }, +}; diff --git a/mocks/optimism/disputeGames.ts b/mocks/optimism/disputeGames.ts new file mode 100644 index 0000000000..f522775cac --- /dev/null +++ b/mocks/optimism/disputeGames.ts @@ -0,0 +1,26 @@ +export const data = { + items: [ + { + contract_address: '0x5cbe1b88b6357e6a8f0821bea72cc0b88c231f1c', + created_at: '2022-05-27T01:13:48.000000Z', + game_type: 0, + index: 6662, + l2_block_number: 12542890, + resolved_at: null, + status: 'In progress', + }, + { + contract_address: '0x5cbe1b88b6357e6a8f0821bea72cc0b88c231f1c', + created_at: '2022-05-27T01:13:48.000000Z', + game_type: 0, + index: 6662, + l2_block_number: 12542890, + resolved_at: '2022-05-27T01:13:48.000000Z', + status: 'Defender wins', + }, + ], + next_page_params: { + items_count: 50, + index: 8382363, + }, +}; diff --git a/mocks/optimism/outputRoots.ts b/mocks/optimism/outputRoots.ts new file mode 100644 index 0000000000..0be0426fee --- /dev/null +++ b/mocks/optimism/outputRoots.ts @@ -0,0 +1,32 @@ +export const outputRootsData = { + items: [ + { + l1_block_number: 8456113, + l1_timestamp: '2022-02-08T12:08:48.000000Z', + l1_transaction_hash: '0x19455a53758d5de89070164ff09c40d93f1b4447e721090f03aa150f6159265a', + l2_block_number: 5214988, + l2_output_index: 9926, + output_root: '0xa7de9bd3986ce5ca8de9f0ab6c7473f4cebe225fb13b57cc5c8472de84a8bab3', + }, + { + l1_block_number: 8456099, + l1_timestamp: '2022-02-08T12:05:24.000000Z', + l1_transaction_hash: '0x6aa081e8e33a085e4ec7124fcd8a5f7d36aac0828f176e80d4b70e313a11695b', + l2_block_number: 5214868, + l2_output_index: 9925, + output_root: '0x4ec2822d2f7b4f834d693d88f8a4cf15899882915980a21756d29cfd9f9f3898', + }, + { + l1_block_number: 8456078, + l1_timestamp: '2022-02-08T12:00:48.000000Z', + l1_transaction_hash: '0x4238988b0959e41a7b09cef67f58698e05e3bcc29b8d2f60e6c77dc68c91f16e', + l2_block_number: 5214748, + l2_output_index: 9924, + output_root: '0x78b2e13c20f4bbfb4a008127edaaf25aa476f933669edd4856305bf4ab64a92b', + }, + ], + next_page_params: { + index: 9877, + items_count: 50, + }, +}; diff --git a/mocks/optimism/txnBatches.ts b/mocks/optimism/txnBatches.ts new file mode 100644 index 0000000000..3344f6420d --- /dev/null +++ b/mocks/optimism/txnBatches.ts @@ -0,0 +1,104 @@ +import type { + OptimismL2TxnBatchTypeCallData, + OptimismL2TxnBatchTypeCelestia, + OptimismL2TxnBatchTypeEip4844, + OptimisticL2TxnBatchesResponse, +} from 'types/api/optimisticL2'; + +export const txnBatchesData: OptimisticL2TxnBatchesResponse = { + items: [ + { + batch_data_container: 'in_blob4844', + internal_id: 260998, + l1_timestamp: '2022-11-10T11:29:11.000000Z', + l1_transaction_hashes: [ + '0x9553351f6bd1577f4e782738c087be08697fb11f3b91745138d71ba166d62c3b', + ], + l2_block_end: 124882074, + l2_block_start: 124881833, + transaction_count: 4011, + }, + { + batch_data_container: 'in_calldata', + internal_id: 260997, + l1_timestamp: '2022-11-03T11:20:59.000000Z', + l1_transaction_hashes: [ + '0x80f5fba70d5685bc2b70df836942e892b24afa7bba289a2fac0ca8f4d554cc72', + ], + l2_block_end: 124881832, + l2_block_start: 124881613, + transaction_count: 4206, + }, + { + internal_id: 260996, + l1_timestamp: '2024-09-03T11:14:23.000000Z', + l1_transaction_hashes: [ + '0x39f4c46cae57bae936acb9159e367794f41f021ed3788adb80ad93830edb5f22', + ], + l2_block_end: 124881612, + l2_block_start: 124881380, + transaction_count: 4490, + }, + ], + next_page_params: { + id: 5902834, + items_count: 50, + }, +}; + +export const txnBatchTypeCallData: OptimismL2TxnBatchTypeCallData = { + batch_data_container: 'in_calldata', + internal_id: 309123, + l1_timestamp: '2022-08-10T10:30:24.000000Z', + l1_transaction_hashes: [ + '0x478c45f182631ae6f7249d40f31fdac36f41d88caa2e373fba35340a7345ca67', + ], + l2_block_end: 10146784, + l2_block_start: 10145379, + transaction_count: 1608, +}; + +export const txnBatchTypeCelestia: OptimismL2TxnBatchTypeCelestia = { + batch_data_container: 'in_celestia', + blobs: [ + { + commitment: '0x39c18c21c6b127d58809b8d3b5931472421f9b51532959442f53038f10b78f2a', + height: 2584868, + l1_timestamp: '2024-08-28T16:51:12.000000Z', + l1_transaction_hash: '0x2bb0b96a8ba0f063a243ac3dee0b2f2d87edb2ba9ef44bfcbc8ed191af1c4c24', + namespace: '0x00000000000000000000000000000000000000000008e5f679bf7116cb', + }, + ], + internal_id: 309667, + l1_timestamp: '2022-08-28T16:51:12.000000Z', + l1_transaction_hashes: [ + '0x2bb0b96a8ba0f063a243ac3dee0b2f2d87edb2ba9ef44bfcbc8ed191af1c4c24', + ], + l2_block_end: 10935879, + l2_block_start: 10934514, + transaction_count: 1574, +}; + +export const txnBatchTypeEip4844: OptimismL2TxnBatchTypeEip4844 = { + batch_data_container: 'in_blob4844', + blobs: [ + { + hash: '0x012a4f0c6db6bce9d3d357b2bf847764320bcb0107ab318f3a532f637bc60dfe', + l1_timestamp: '2022-08-23T03:59:12.000000Z', + l1_transaction_hash: '0x3870f136497e5501dc20d0974daf379c8636c958794d59a9c90d4f8a9f0ed20a', + }, + { + hash: '0x01d1097cce23229931afbc2fd1cf0d707da26df7b39cef1c542276ae718de4f6', + l1_timestamp: '2022-08-23T03:59:12.000000Z', + l1_transaction_hash: '0x3870f136497e5501dc20d0974daf379c8636c958794d59a9c90d4f8a9f0ed20a', + }, + ], + internal_id: 2538459, + l1_timestamp: '2022-08-23T03:59:12.000000Z', + l1_transaction_hashes: [ + '0x3870f136497e5501dc20d0974daf379c8636c958794d59a9c90d4f8a9f0ed20a', + ], + l2_block_end: 16291502, + l2_block_start: 16291373, + transaction_count: 704, +}; diff --git a/mocks/optimism/withdrawals.ts b/mocks/optimism/withdrawals.ts new file mode 100644 index 0000000000..b9d9588cdc --- /dev/null +++ b/mocks/optimism/withdrawals.ts @@ -0,0 +1,50 @@ +import type { OptimisticL2WithdrawalsResponse } from 'types/api/optimisticL2'; + +export const data: OptimisticL2WithdrawalsResponse = { + items: [ + { + challenge_period_end: null, + from: { + hash: '0x67aab90c548b284be30b05c376001b4db90b87ba', + implementations: null, + is_contract: false, + is_verified: false, + name: null, + private_tags: [], + public_tags: [], + watchlist_names: [], + ens_domain_name: null, + }, + l1_transaction_hash: '0x1a235bee32ac10cb7efdad98415737484ca66386e491cde9e17d42b136dca684', + l2_timestamp: '2022-02-15T12:50:02.000000Z', + l2_transaction_hash: '0x918cd8c5c24c17e06cd02b0379510c4ad56324bf153578fb9caaaa2fe4e7dc35', + msg_nonce: 396, + msg_nonce_version: 1, + status: 'Ready to prove', + }, + { + challenge_period_end: null, + from: null, + l1_transaction_hash: null, + l2_timestamp: null, + l2_transaction_hash: '0x2f117bee32ac10cb7efdad98415737484ca66386e491cde9e17d42b136def593', + msg_nonce: 391, + msg_nonce_version: 1, + status: 'Ready to prove', + }, + { + challenge_period_end: '2022-11-11T12:50:02.000000Z', + from: null, + l1_transaction_hash: null, + l2_timestamp: null, + l2_transaction_hash: '0xe14b1f46838176702244a5343629bcecf728ca2d9881d47b4ce46e00c387d7e3', + msg_nonce: 390, + msg_nonce_version: 1, + status: 'Ready for relay', + }, + ], + next_page_params: { + items_count: 50, + nonce: '1766847064778384329583297500742918515827483896875618958121606201292620123', + }, +}; diff --git a/mocks/pools/pool.ts b/mocks/pools/pool.ts new file mode 100644 index 0000000000..78dce73b1d --- /dev/null +++ b/mocks/pools/pool.ts @@ -0,0 +1,24 @@ +import type { Pool } from 'types/api/pools'; + +export const base: Pool = { + contract_address: '0x06da0fd433c1a5d7a4faa01111c044910a184553', + chain_id: '1', + base_token_address: '0xdac17f958d2ee523a2206206994597c13d831ec7', + base_token_symbol: 'USDT', + base_token_icon_url: 'https://localhost:3000/utia.jpg', + quote_token_address: '0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2', + quote_token_symbol: 'WETH', + quote_token_icon_url: 'https://localhost:3000/secondary_utia.jpg', + fully_diluted_valuation_usd: '75486579078', + market_cap_usd: '139312819076.195', + liquidity: '2099941.2238', + dex: { id: 'sushiswap', name: 'SushiSwap' }, + fee: '0.03', + coin_gecko_terminal_url: 'https://www.geckoterminal.com/eth/pools/0x06da0fd433c1a5d7a4faa01111c044910a184553', +}; + +export const noIcons: Pool = { + ...base, + base_token_icon_url: null, + quote_token_icon_url: null, +}; diff --git a/mocks/rewards/balance.ts b/mocks/rewards/balance.ts new file mode 100644 index 0000000000..8c78eab127 --- /dev/null +++ b/mocks/rewards/balance.ts @@ -0,0 +1,10 @@ +import type { RewardsUserBalancesResponse } from 'types/api/rewards'; + +export const base: RewardsUserBalancesResponse = { + total: '250', + staked: '0', + unstaked: '0', + total_staking_rewards: '0', + total_referral_rewards: '0', + pending_referral_rewards: '0', +}; diff --git a/mocks/rewards/dailyReward.ts b/mocks/rewards/dailyReward.ts new file mode 100644 index 0000000000..558c22aca4 --- /dev/null +++ b/mocks/rewards/dailyReward.ts @@ -0,0 +1,12 @@ +import type { RewardsUserDailyCheckResponse } from 'types/api/rewards'; + +export const base: RewardsUserDailyCheckResponse = { + available: true, + daily_reward: '10', + streak_reward: '10', + pending_referral_rewards: '0', + total_reward: '20', + date: '', + reset_at: '', + streak: '6', +}; diff --git a/mocks/rewards/referrals.ts b/mocks/rewards/referrals.ts new file mode 100644 index 0000000000..bac2b26042 --- /dev/null +++ b/mocks/rewards/referrals.ts @@ -0,0 +1,7 @@ +import type { RewardsUserReferralsResponse } from 'types/api/rewards'; + +export const base: RewardsUserReferralsResponse = { + code: 'QWERTY', + link: 'https://example.com?ref=QWERTY', + referrals: '15', +}; diff --git a/mocks/rewards/rewardsConfig.ts b/mocks/rewards/rewardsConfig.ts new file mode 100644 index 0000000000..25ae8b7277 --- /dev/null +++ b/mocks/rewards/rewardsConfig.ts @@ -0,0 +1,10 @@ +import type { RewardsConfigResponse } from 'types/api/rewards'; + +export const base: RewardsConfigResponse = { + rewards: { + registration: '100', + registration_with_referral: '200', + daily_claim: '10', + referral_share: '0.1', + }, +}; diff --git a/mocks/scroll/messages.ts b/mocks/scroll/messages.ts new file mode 100644 index 0000000000..d2bbf43a70 --- /dev/null +++ b/mocks/scroll/messages.ts @@ -0,0 +1,26 @@ +import type { ScrollL2MessagesResponse } from 'types/api/scrollL2'; + +export const baseResponse: ScrollL2MessagesResponse = { + items: [ + { + id: 930795, + origination_transaction_block_number: 20639178, + origination_transaction_hash: '0x70380f2c6ecd53aa6e0608e6c9d770acaa29c0508869ec296bae3e09678ea9f4', + origination_timestamp: '2024-08-30T05:03:23.000000Z', + completion_transaction_hash: null, + value: '5084131319054877748', + }, + { + id: 930748, + origination_transaction_block_number: 20638104, + origination_transaction_hash: '0x7e7b4d5ff0b7a6af5e52f4aa2ad9eca3c0c5664368cbb781e04b5b13c6109b2b', + origination_timestamp: '2024-08-30T01:26:35.000000Z', + completion_transaction_hash: '0x426b16ea3a42228f6d754ae55c348986122cdb1e4331b6fd454975776f513ea1', + value: '0', + }, + ], + next_page_params: { + items_count: 50, + id: 1, + }, +}; diff --git a/mocks/scroll/txnBatches.ts b/mocks/scroll/txnBatches.ts new file mode 100644 index 0000000000..03e038b4fb --- /dev/null +++ b/mocks/scroll/txnBatches.ts @@ -0,0 +1,50 @@ +import type { ScrollL2BatchesResponse } from 'types/api/scrollL2'; + +export const batchData = { + number: 66928, + commitment_transaction: { + block_number: 19114878, + hash: '0x57552c0dbcf56383ee2efdf8fd6be143b355135fc300361924582c308877b8b7', + timestamp: '2024-01-29T21:31:35.000000Z', + }, + confirmation_transaction: { + block_number: null, + hash: null, + timestamp: null, + }, + data_availability: { + batch_data_container: 'in_blob4844' as const, + }, + start_block: 456000, + end_block: 789000, + transaction_count: 654, +}; + +export const baseResponse: ScrollL2BatchesResponse = { + items: [ + batchData, + { + number: 66879, + commitment_transaction: { + block_number: 19114386, + hash: '0x0d33245814b9e61c8f0ed6fd3fb7464f34be33d2c3aee69629d65e8995d77edc', + timestamp: '2024-01-29T19:52:35.000000Z', + }, + confirmation_transaction: { + block_number: 19114558, + hash: '0x6f9a19d503947ec91d6e9d5c2129913a7def86fd0f87061c06e5994cf857bee0', + timestamp: '2024-01-29T20:27:11.000000Z', + }, + data_availability: { + batch_data_container: 'in_calldata', + }, + start_block: 456000, + end_block: 789000, + transaction_count: 962, + }, + ], + next_page_params: { + items_count: 50, + number: 1, + }, +}; diff --git a/mocks/search/index.ts b/mocks/search/index.ts index ef384d1d70..c0510f0225 100644 --- a/mocks/search/index.ts +++ b/mocks/search/index.ts @@ -6,6 +6,8 @@ import type { SearchResultLabel, SearchResult, SearchResultUserOp, + SearchResultBlob, + SearchResultDomain, } from 'types/api/search'; export const token1: SearchResultToken = { @@ -94,6 +96,15 @@ export const contract1: SearchResultAddressOrContract = { url: '/address/0xb64a30399f7F6b0C154c2E7Af0a3ec7B0A5b131a', }; +export const contract2: SearchResultAddressOrContract = { + address: '0xb64a30399f7F6b0C154c2E7Af0a3ec7B0A5b131a', + name: 'Super utko', + type: 'contract' as const, + is_smart_contract_verified: true, + certified: true, + url: '/address/0xb64a30399f7F6b0C154c2E7Af0a3ec7B0A5b131a', +}; + export const label1: SearchResultLabel = { address: '0xb64a30399f7F6b0C154c2E7Af0a3ec7B0A5b131a', name: 'utko', @@ -103,7 +114,7 @@ export const label1: SearchResultLabel = { }; export const tx1: SearchResultTx = { - tx_hash: '0x349d4025d03c6faec117ee10ac0bce7c7a805dd2cbff7a9f101304d9a8a525dd', + transaction_hash: '0x349d4025d03c6faec117ee10ac0bce7c7a805dd2cbff7a9f101304d9a8a525dd', type: 'transaction' as const, timestamp: '2022-12-11T17:55:20Z', url: '/tx/0x349d4025d03c6faec117ee10ac0bce7c7a805dd2cbff7a9f101304d9a8a525dd', @@ -116,6 +127,26 @@ export const userOp1: SearchResultUserOp = { url: '/op/0xcb560d77b0f3af074fa05c1e5c691bcdfe457e630062b5907e9e71fc74b2ec61', }; +export const blob1: SearchResultBlob = { + blob_hash: '0x0108dd3e414da9f3255f7a831afa606e8dfaea93d082dfa9b15305583cbbdbbe', + type: 'blob' as const, + timestamp: null, +}; + +export const domain1: SearchResultDomain = { + address: '0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045', + ens_info: { + address_hash: '0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045', + expiry_date: '2039-09-01T07:36:18.000Z', + name: 'vitalik.eth', + names_count: 1, + }, + is_smart_contract_verified: false, + name: null, + type: 'ens_domain', + url: '/address/0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045', +}; + export const baseResponse: SearchResult = { items: [ token1, @@ -124,6 +155,8 @@ export const baseResponse: SearchResult = { address1, contract1, tx1, + blob1, + domain1, ], next_page_params: null, }; diff --git a/mocks/shibarium/deposits.ts b/mocks/shibarium/deposits.ts new file mode 100644 index 0000000000..7081042cd8 --- /dev/null +++ b/mocks/shibarium/deposits.ts @@ -0,0 +1,61 @@ +import type { ShibariumDepositsResponse } from 'types/api/shibarium'; + +export const data: ShibariumDepositsResponse = { + items: [ + { + l1_block_number: 8382841, + timestamp: '2022-05-27T01:13:48.000000Z', + l1_transaction_hash: '0xaf3e5f4ef03eac22a622b3434c5dc9f4465aa291900a86bcf0ad9fb14429f05e', + user: { + hash: '0x6197d1eef304eb5284a0f6720f79403b4e9bf3a5', + implementations: null, + is_contract: false, + is_verified: false, + name: null, + private_tags: [], + public_tags: [], + watchlist_names: [], + ens_domain_name: null, + }, + l2_transaction_hash: '0xb9212c76069b926917816767e4c5a0ef80e519b1ac1c3d3fb5818078f4984667', + }, + { + l1_block_number: 8382841, + timestamp: '2022-05-27T01:13:48.000000Z', + l1_transaction_hash: '0xaf3e5f4ef03eac22a622b3434c5dc9f4465aa291900a86bcf0ad9fb14429f05e', + user: { + hash: '0x6197d1eef304eb5284a0f6720f79403b4e9bf3a5', + implementations: null, + is_contract: false, + is_verified: false, + name: null, + private_tags: [], + public_tags: [], + watchlist_names: [], + ens_domain_name: null, + }, + l2_transaction_hash: '0xb9212c76069b926917816767e4c5a0ef80e519b1ac1c3d3fb5818078f4984667', + }, + { + l1_block_number: 8382841, + timestamp: '2022-05-27T01:13:48.000000Z', + l1_transaction_hash: '0xaf3e5f4ef03eac22a622b3434c5dc9f4465aa291900a86bcf0ad9fb14429f05e', + user: { + hash: '0x6197d1eef304eb5284a0f6720f79403b4e9bf3a5', + implementations: null, + is_contract: false, + is_verified: false, + name: null, + private_tags: [], + public_tags: [], + watchlist_names: [], + ens_domain_name: null, + }, + l2_transaction_hash: '0xb9212c76069b926917816767e4c5a0ef80e519b1ac1c3d3fb5818078f4984667', + }, + ], + next_page_params: { + items_count: 50, + block_number: 8382363, + }, +}; diff --git a/mocks/shibarium/withdrawals.ts b/mocks/shibarium/withdrawals.ts new file mode 100644 index 0000000000..6c1e875264 --- /dev/null +++ b/mocks/shibarium/withdrawals.ts @@ -0,0 +1,61 @@ +import type { ShibariumWithdrawalsResponse } from 'types/api/shibarium'; + +export const data: ShibariumWithdrawalsResponse = { + items: [ + { + l2_block_number: 8382841, + timestamp: '2022-05-27T01:13:48.000000Z', + l1_transaction_hash: '0xaf3e5f4ef03eac22a622b3434c5dc9f4465aa291900a86bcf0ad9fb14429f05e', + user: { + hash: '0x6197d1eef304eb5284a0f6720f79403b4e9bf3a5', + implementations: null, + is_contract: false, + is_verified: false, + name: null, + private_tags: [], + public_tags: [], + watchlist_names: [], + ens_domain_name: null, + }, + l2_transaction_hash: '0xb9212c76069b926917816767e4c5a0ef80e519b1ac1c3d3fb5818078f4984667', + }, + { + l2_block_number: 8382841, + timestamp: '2022-05-27T01:13:48.000000Z', + l1_transaction_hash: '0xaf3e5f4ef03eac22a622b3434c5dc9f4465aa291900a86bcf0ad9fb14429f05e', + user: { + hash: '0x6197d1eef304eb5284a0f6720f79403b4e9bf3a5', + implementations: null, + is_contract: false, + is_verified: false, + name: null, + private_tags: [], + public_tags: [], + watchlist_names: [], + ens_domain_name: null, + }, + l2_transaction_hash: '0xb9212c76069b926917816767e4c5a0ef80e519b1ac1c3d3fb5818078f4984667', + }, + { + l2_block_number: 8382841, + timestamp: '2022-05-27T01:13:48.000000Z', + l1_transaction_hash: '0xaf3e5f4ef03eac22a622b3434c5dc9f4465aa291900a86bcf0ad9fb14429f05e', + user: { + hash: '0x6197d1eef304eb5284a0f6720f79403b4e9bf3a5', + implementations: null, + is_contract: false, + is_verified: false, + name: null, + private_tags: [], + public_tags: [], + watchlist_names: [], + ens_domain_name: null, + }, + l2_transaction_hash: '0xb9212c76069b926917816767e4c5a0ef80e519b1ac1c3d3fb5818078f4984667', + }, + ], + next_page_params: { + items_count: 50, + block_number: 8382363, + }, +}; diff --git a/mocks/stats/daily_txs.ts b/mocks/stats/daily_txs.ts index 03cb3f20ac..76653e9240 100644 --- a/mocks/stats/daily_txs.ts +++ b/mocks/stats/daily_txs.ts @@ -2,127 +2,148 @@ export const base = { chart_data: [ { date: '2022-11-28', - tx_count: 26815, + transaction_count: 26815, }, { date: '2022-11-27', - tx_count: 34784, + transaction_count: 34784, }, { date: '2022-11-26', - tx_count: 77527, + transaction_count: 77527, }, { date: '2022-11-25', - tx_count: 39687, + transaction_count: 39687, }, { date: '2022-11-24', - tx_count: 40752, + transaction_count: 40752, }, { date: '2022-11-23', - tx_count: 32569, + transaction_count: 32569, }, { date: '2022-11-22', - tx_count: 34449, + transaction_count: 34449, }, { date: '2022-11-21', - tx_count: 106047, + transaction_count: 106047, }, { date: '2022-11-20', - tx_count: 107713, + transaction_count: 107713, }, { date: '2022-11-19', - tx_count: 96311, + transaction_count: 96311, }, { date: '2022-11-18', - tx_count: 30828, + transaction_count: 30828, }, { date: '2022-11-17', - tx_count: 27422, + transaction_count: 27422, }, { date: '2022-11-16', - tx_count: 75898, + transaction_count: 75898, }, { date: '2022-11-15', - tx_count: 84084, + transaction_count: 84084, }, { date: '2022-11-14', - tx_count: 62266, + transaction_count: 62266, }, { date: '2022-11-13', - tx_count: 22338, + transaction_count: 22338, }, { date: '2022-11-12', - tx_count: 86764, + transaction_count: 86764, }, { date: '2022-11-11', - tx_count: 79493, + transaction_count: 79493, }, { date: '2022-11-10', - tx_count: 92887, + transaction_count: 92887, }, { date: '2022-11-09', - tx_count: 43691, + transaction_count: 43691, }, { date: '2022-11-08', - tx_count: 74197, + transaction_count: 74197, }, { date: '2022-11-07', - tx_count: 58131, + transaction_count: 58131, }, { date: '2022-11-06', - tx_count: 62477, + transaction_count: 62477, }, { date: '2022-11-05', - tx_count: 82897, + transaction_count: 82897, }, { date: '2022-11-04', - tx_count: 91725, + transaction_count: 91725, }, { date: '2022-11-03', - tx_count: 83667, + transaction_count: 83667, }, { date: '2022-11-02', - tx_count: 63743, + transaction_count: 63743, }, { date: '2022-11-01', - tx_count: 152059, + transaction_count: 152059, }, { date: '2022-10-31', - tx_count: 62519, + transaction_count: 62519, }, { date: '2022-10-30', - tx_count: 48569, + transaction_count: 48569, }, { date: '2022-10-29', - tx_count: 36789, + transaction_count: 36789, }, ], }; + +export const partialData = { + chart_data: [ + { date: '2022-11-28', transaction_count: 26815 }, + { date: '2022-11-27', transaction_count: 34784 }, + { date: '2022-11-26', transaction_count: 77527 }, + { date: '2022-11-25', transaction_count: null }, + { date: '2022-11-24', transaction_count: null }, + { date: '2022-11-23', transaction_count: null }, + { date: '2022-11-22', transaction_count: 63433 }, + { date: '2022-11-21', transaction_count: null }, + ], +}; + +export const noData = { + chart_data: [ + { date: '2022-11-25', transaction_count: null }, + { date: '2022-11-24', transaction_count: null }, + { date: '2022-11-23', transaction_count: null }, + ], +}; diff --git a/mocks/stats/index.ts b/mocks/stats/index.ts index 4e7a63f59c..f4bc3f53e3 100644 --- a/mocks/stats/index.ts +++ b/mocks/stats/index.ts @@ -1,24 +1,33 @@ +import _mapValues from 'lodash/mapValues'; + import type { HomeStats } from 'types/api/stats'; export const base: HomeStats = { average_block_time: 6212.0, coin_price: '0.00199678', coin_price_change_percentage: -7.42, + coin_image: 'http://localhost:3100/utia.jpg', gas_prices: { average: { - fiat_price: '1.01', - price: 20.41, - time: 12283, + fiat_price: '1.39', + price: 23.75, + time: 12030.25, + base_fee: 2.22222, + priority_fee: 12.424242, }, fast: { - fiat_price: '1.26', - price: 25.47, - time: 9321, + fiat_price: '1.74', + price: 29.72, + time: 8763.25, + base_fee: 4.44444, + priority_fee: 22.242424, }, slow: { - fiat_price: '0.97', - price: 19.55, - time: 24543, + fiat_price: '1.35', + price: 23.04, + time: 20100.25, + base_fee: 1.11111, + priority_fee: 7.8909, }, }, gas_price_updated_at: '2022-11-11T11:09:49.051171Z', @@ -39,3 +48,44 @@ export const withBtcLocked: HomeStats = { ...base, rootstock_locked_btc: '3337493406696977561374', }; + +export const withoutFiatPrices: HomeStats = { + ...base, + gas_prices: _mapValues(base.gas_prices, (price) => price ? ({ ...price, fiat_price: null }) : null), +}; + +export const withoutGweiPrices: HomeStats = { + ...base, + gas_prices: _mapValues(base.gas_prices, (price) => price ? ({ ...price, price: null }) : null), +}; + +export const withoutBothPrices: HomeStats = { + ...base, + gas_prices: _mapValues(base.gas_prices, (price) => price ? ({ ...price, price: null, fiat_price: null }) : null), +}; + +export const withoutGasInfo: HomeStats = { + ...base, + gas_prices: null, +}; + +export const withSecondaryCoin: HomeStats = { + ...base, + secondary_coin_price: '3.398', + secondary_coin_image: 'http://localhost:3100/secondary_utia.jpg', +}; + +export const noChartData: HomeStats = { + ...base, + transactions_today: null, + coin_price: null, + market_cap: null, + tvl: null, +}; + +export const indexingStatus = { + finished_indexing_blocks: false, + indexed_blocks_ratio: '0.1', + finished_indexing: true, + indexed_internal_transactions_ratio: '1', +}; diff --git a/mocks/stats/line.ts b/mocks/stats/line.ts new file mode 100644 index 0000000000..0c10eed840 --- /dev/null +++ b/mocks/stats/line.ts @@ -0,0 +1,198 @@ +import type * as stats from '@blockscout/stats-types'; + +export const averageGasPrice: stats.LineChart = { + chart: [ + { + date: '2023-12-22', + date_to: '2023-12-22', + value: '37.7804422597599', + is_approximate: false, + }, + { + date: '2023-12-23', + date_to: '2023-12-23', + value: '25.84889883009387', + is_approximate: false, + }, + { + date: '2023-12-24', + date_to: '2023-12-24', + value: '25.818463227198574', + is_approximate: false, + }, + { + date: '2023-12-25', + date_to: '2023-12-25', + value: '26.045513050051298', + is_approximate: false, + }, + { + date: '2023-12-26', + date_to: '2023-12-26', + value: '21.42600692652399', + is_approximate: false, + }, + { + date: '2023-12-27', + date_to: '2023-12-27', + value: '31.066730409846656', + is_approximate: false, + }, + { + date: '2023-12-28', + date_to: '2023-12-28', + value: '33.63955781902089', + is_approximate: false, + }, + { + date: '2023-12-29', + date_to: '2023-12-29', + value: '28.064736756058384', + is_approximate: false, + }, + { + date: '2023-12-30', + date_to: '2023-12-30', + value: '23.074500869678175', + is_approximate: false, + }, + { + date: '2023-12-31', + date_to: '2023-12-31', + value: '17.651005734615133', + is_approximate: false, + }, + { + date: '2024-01-01', + date_to: '2023-01-01', + value: '14.906085174476441', + is_approximate: false, + }, + { + date: '2024-01-02', + date_to: '2023-01-02', + value: '22.28459059038656', + is_approximate: false, + }, + { + date: '2024-01-03', + date_to: '2023-01-03', + value: '39.8311646806592', + is_approximate: false, + }, + { + date: '2024-01-04', + date_to: '2023-01-04', + value: '26.09989322256083', + is_approximate: false, + }, + { + date: '2024-01-05', + date_to: '2023-01-05', + value: '22.821996688111998', + is_approximate: false, + }, + { + date: '2024-01-06', + date_to: '2023-01-06', + value: '20.32680041262083', + is_approximate: false, + }, + { + date: '2024-01-07', + date_to: '2023-01-07', + value: '32.535045831809704', + is_approximate: false, + }, + { + date: '2024-01-08', + date_to: '2023-01-08', + value: '27.443477102139482', + is_approximate: false, + }, + { + date: '2024-01-09', + date_to: '2023-01-09', + value: '20.7911332558055', + is_approximate: false, + }, + { + date: '2024-01-10', + date_to: '2023-01-10', + value: '42.10740192523919', + is_approximate: false, + }, + { + date: '2024-01-11', + date_to: '2023-01-11', + value: '35.75215680343582', + is_approximate: false, + }, + { + date: '2024-01-12', + date_to: '2023-01-12', + value: '27.430414798093253', + is_approximate: false, + }, + { + date: '2024-01-13', + date_to: '2023-01-13', + value: '20.170934096589875', + is_approximate: false, + }, + { + date: '2024-01-14', + date_to: '2023-01-14', + value: '38.79660984371034', + is_approximate: false, + }, + { + date: '2024-01-15', + date_to: '2023-01-15', + value: '26.140740484554204', + is_approximate: false, + }, + { + date: '2024-01-16', + date_to: '2023-01-16', + value: '36.708543184194156', + is_approximate: false, + }, + { + date: '2024-01-17', + date_to: '2023-01-17', + value: '40.325438794298876', + is_approximate: false, + }, + { + date: '2024-01-18', + date_to: '2023-01-18', + value: '37.55145309930694', + is_approximate: false, + }, + { + date: '2024-01-19', + date_to: '2023-01-19', + value: '33.271450114434664', + is_approximate: false, + }, + { + date: '2024-01-20', + date_to: '2023-01-20', + value: '19.303304377685638', + is_approximate: false, + }, + { + date: '2024-01-21', + date_to: '2023-01-21', + value: '14.375908594704976', + is_approximate: false, + }, + ], + info: { + title: 'Chart title', + description: 'Chart description', + id: 'chart', + resolutions: [ 'DAY', 'MONTH' ], + }, +}; diff --git a/mocks/stats/lines.ts b/mocks/stats/lines.ts new file mode 100644 index 0000000000..9dfccb0fdf --- /dev/null +++ b/mocks/stats/lines.ts @@ -0,0 +1,161 @@ +import type * as stats from '@blockscout/stats-types'; + +export const base: stats.LineCharts = { + sections: [ + { + id: 'accounts', + title: 'Accounts', + charts: [ + { + id: 'accountsGrowth', + title: 'Accounts growth', + description: 'Cumulative accounts number per period', + units: undefined, + resolutions: [ 'DAY', 'MONTH' ], + }, + { + id: 'activeAccounts', + title: 'Active accounts', + description: 'Active accounts number per period', + units: undefined, + resolutions: [ 'DAY', 'MONTH' ], + }, + { + id: 'newAccounts', + title: 'New accounts', + description: 'New accounts number per day', + units: undefined, + resolutions: [ 'DAY', 'MONTH' ], + }, + ], + }, + { + id: 'transactions', + title: 'Transactions', + charts: [ + { + id: 'averageTxnFee', + title: 'Average transaction fee', + description: 'The average amount in ETH spent per transaction', + units: 'ETH', + resolutions: [ 'DAY', 'MONTH' ], + }, + { + id: 'newTxns', + title: 'New transactions', + description: 'New transactions number', + units: undefined, + resolutions: [ 'DAY', 'MONTH' ], + }, + { + id: 'txnsFee', + title: 'Transactions fees', + description: 'Amount of tokens paid as fees', + units: 'ETH', + resolutions: [ 'DAY', 'MONTH' ], + }, + { + id: 'txnsGrowth', + title: 'Transactions growth', + description: 'Cumulative transactions number', + units: undefined, + resolutions: [ 'DAY', 'MONTH' ], + }, + { + id: 'txnsSuccessRate', + title: 'Transactions success rate', + description: 'Successful transactions rate per day', + units: undefined, + resolutions: [ 'DAY', 'MONTH' ], + }, + ], + }, + { + id: 'blocks', + title: 'Blocks', + charts: [ + { + id: 'averageBlockRewards', + title: 'Average block rewards', + description: 'Average amount of distributed reward in tokens per day', + units: 'ETH', + resolutions: [ 'DAY', 'MONTH' ], + }, + { + id: 'averageBlockSize', + title: 'Average block size', + description: 'Average size of blocks in bytes', + units: 'Bytes', + resolutions: [ 'DAY', 'MONTH' ], + }, + { + id: 'newBlocks', + title: 'New blocks', + description: 'New blocks number', + units: undefined, + resolutions: [ 'DAY', 'MONTH' ], + }, + ], + }, + { + id: 'tokens', + title: 'Tokens', + charts: [ + { + id: 'newNativeCoinTransfers', + title: 'New ETH transfers', + description: 'New token transfers number for the period', + units: undefined, + resolutions: [ 'DAY', 'MONTH' ], + }, + ], + }, + { + id: 'gas', + title: 'Gas', + charts: [ + { + id: 'averageGasLimit', + title: 'Average gas limit', + description: 'Average gas limit per block for the period', + units: undefined, + resolutions: [ 'DAY', 'MONTH' ], + }, + { + id: 'averageGasPrice', + title: 'Average gas price', + description: 'Average gas price for the period (Gwei)', + units: 'Gwei', + resolutions: [ 'DAY', 'MONTH' ], + }, + { + id: 'gasUsedGrowth', + title: 'Gas used growth', + description: 'Cumulative gas used for the period', + units: undefined, + resolutions: [ 'DAY', 'MONTH' ], + }, + ], + }, + { + id: 'contracts', + title: 'Contracts', + charts: [ + { + id: 'newVerifiedContracts', + title: 'New verified contracts', + description: 'New verified contracts number for the period', + units: undefined, + resolutions: [ 'DAY', 'MONTH' ], + }, + { + id: 'verifiedContractsGrowth', + title: 'Verified contracts growth', + description: 'Cumulative number verified contracts for the period', + units: undefined, + resolutions: [ 'DAY', 'MONTH' ], + }, + ], + }, + ], +}; diff --git a/mocks/tokens/tokenHolders.ts b/mocks/tokens/tokenHolders.ts index 582476e903..89f2595482 100644 --- a/mocks/tokens/tokenHolders.ts +++ b/mocks/tokens/tokenHolders.ts @@ -2,18 +2,14 @@ import type { TokenHolders } from 'types/api/token'; import { withName, withoutName } from 'mocks/address/address'; -import { tokenInfoERC1155a, tokenInfoERC20a } from './tokenInfo'; - export const tokenHoldersERC20: TokenHolders = { items: [ { address: withName, - token: tokenInfoERC20a, value: '107014805905725000000', }, { address: withoutName, - token: tokenInfoERC20a, value: '207014805905725000000', }, ], @@ -27,13 +23,11 @@ export const tokenHoldersERC1155: TokenHolders = { items: [ { address: withName, - token: tokenInfoERC1155a, value: '107014805905725000000', token_id: '12345', }, { address: withoutName, - token: tokenInfoERC1155a, value: '207014805905725000000', token_id: '12345', }, diff --git a/mocks/tokens/tokenInfo.ts b/mocks/tokens/tokenInfo.ts index 1034b9bc64..a22c7537e5 100644 --- a/mocks/tokens/tokenInfo.ts +++ b/mocks/tokens/tokenInfo.ts @@ -8,7 +8,7 @@ export const tokenInfo: TokenInfo = { holders: '46554', name: 'ARIANEE', symbol: 'ARIA', - type: 'ERC-20', + type: 'ERC-20' as const, total_supply: '1235', icon_url: 'http://localhost:3000/token-icon.png', }; @@ -27,8 +27,8 @@ export const tokenInfoERC20a: TokenInfo<'ERC-20'> = { name: 'hyfi.token', symbol: 'HyFi', total_supply: '369000000000000000000000000', - type: 'ERC-20', - icon_url: 'https://example.com/token-icon.png', + type: 'ERC-20' as const, + icon_url: 'http://localhost:3000/token-icon.png', }; export const tokenInfoERC20b: TokenInfo<'ERC-20'> = { @@ -40,7 +40,7 @@ export const tokenInfoERC20b: TokenInfo<'ERC-20'> = { name: 'USD Coin', symbol: 'USDC', total_supply: '900000000000000000000000000', - type: 'ERC-20', + type: 'ERC-20' as const, icon_url: null, }; @@ -53,7 +53,7 @@ export const tokenInfoERC20c: TokenInfo<'ERC-20'> = { name: 'Ethereum', symbol: 'ETH', total_supply: '1000000000000000000000000', - type: 'ERC-20', + type: 'ERC-20' as const, icon_url: null, }; @@ -66,7 +66,7 @@ export const tokenInfoERC20d: TokenInfo<'ERC-20'> = { name: 'Zeta', symbol: 'ZETA', total_supply: '2100000000000000000000000000', - type: 'ERC-20', + type: 'ERC-20' as const, icon_url: null, }; @@ -79,7 +79,7 @@ export const tokenInfoERC20LongSymbol: TokenInfo<'ERC-20'> = { name: 'Zeta', symbol: 'ipfs://QmUpFUfVKDCWeZQk5pvDFUxnpQP9N6eLSHhNUy49T1JVtY', total_supply: '2100000000000000000000000000', - type: 'ERC-20', + type: 'ERC-20' as const, icon_url: null, }; @@ -92,7 +92,7 @@ export const tokenInfoERC721a: TokenInfo<'ERC-721'> = { name: 'HyFi Athena', symbol: 'HYFI_ATHENA', total_supply: '105', - type: 'ERC-721', + type: 'ERC-721' as const, icon_url: null, }; @@ -105,7 +105,7 @@ export const tokenInfoERC721b: TokenInfo<'ERC-721'> = { name: 'World Of Women Galaxy', symbol: 'WOWG', total_supply: null, - type: 'ERC-721', + type: 'ERC-721' as const, icon_url: null, }; @@ -118,7 +118,7 @@ export const tokenInfoERC721c: TokenInfo<'ERC-721'> = { name: 'Puma', symbol: 'PUMA', total_supply: null, - type: 'ERC-721', + type: 'ERC-721' as const, icon_url: null, }; @@ -131,7 +131,7 @@ export const tokenInfoERC721LongSymbol: TokenInfo<'ERC-721'> = { name: 'Puma', symbol: 'ipfs://QmUpFUfVKDCWeZQk5pvDFUxnpQP9N6eLSHhNUy49T1JVtY', total_supply: null, - type: 'ERC-721', + type: 'ERC-721' as const, icon_url: null, }; @@ -144,7 +144,7 @@ export const tokenInfoERC1155a: TokenInfo<'ERC-1155'> = { name: 'HyFi Membership', symbol: 'HYFI_MEMBERSHIP', total_supply: '482', - type: 'ERC-1155', + type: 'ERC-1155' as const, icon_url: null, }; @@ -157,7 +157,7 @@ export const tokenInfoERC1155b: TokenInfo<'ERC-1155'> = { name: 'WinkyVerse Collections', symbol: 'WVC', total_supply: '4943', - type: 'ERC-1155', + type: 'ERC-1155' as const, icon_url: null, }; @@ -170,8 +170,21 @@ export const tokenInfoERC1155WithoutName: TokenInfo<'ERC-1155'> = { name: null, symbol: null, total_supply: '482', - type: 'ERC-1155', + type: 'ERC-1155' as const, + icon_url: null, +}; + +export const tokenInfoERC404: TokenInfo<'ERC-404'> = { + address: '0xB5C457dDB4cE3312a6C5a2b056a1652bd542a208', + circulating_market_cap: '0.0', + decimals: '18', + exchange_rate: '1484.13', + holders: '81', icon_url: null, + name: 'OMNI404', + symbol: 'O404', + total_supply: '6482275000000000000', + type: 'ERC-404' as const, }; export const bridgedTokenA: TokenInfo<'ERC-20'> = { diff --git a/mocks/tokens/tokenInstance.ts b/mocks/tokens/tokenInstance.ts index 4173c0a609..1dc1b96493 100644 --- a/mocks/tokens/tokenInstance.ts +++ b/mocks/tokens/tokenInstance.ts @@ -5,9 +5,9 @@ import * as addressMock from '../address/address'; export const base: TokenInstance = { animation_url: null, - external_app_url: null, + external_app_url: 'https://duck.nft/get-your-duck-today', id: '32925298983216553915666621415831103694597106215670571463977478984525997408266', - image_url: null, + image_url: 'https://example.com/image.jpg', is_unique: false, holder_address_hash: null, metadata: { @@ -73,6 +73,7 @@ export const base: TokenInstance = { name: 'GENESIS #188848, 22a5f8bbb1602995. Blockchain pixel PFP NFT + "on music video" trait inspired by God', }, owner: addressMock.withName, + thumbnails: null, }; export const withRichMetadata: TokenInstance = { diff --git a/mocks/tokens/tokenTransfer.ts b/mocks/tokens/tokenTransfer.ts index 1e0573d43e..f17da56ec4 100644 --- a/mocks/tokens/tokenTransfer.ts +++ b/mocks/tokens/tokenTransfer.ts @@ -1,9 +1,10 @@ +import type { TokenInfo } from 'types/api/token'; import type { TokenTransfer, TokenTransferResponse } from 'types/api/tokenTransfer'; export const erc20: TokenTransfer = { from: { hash: '0xd789a607CEac2f0E14867de4EB15b15C9FFB5859', - implementation_name: null, + implementations: null, is_contract: true, is_verified: true, name: 'ArianeeStore', @@ -14,7 +15,7 @@ export const erc20: TokenTransfer = { }, to: { hash: '0x7d20a8D54F955b4483A66aB335635ab66e151c51', - implementation_name: null, + implementations: null, is_contract: true, is_verified: false, name: null, @@ -39,9 +40,10 @@ export const erc20: TokenTransfer = { decimals: '18', value: '31567373703130350', }, - tx_hash: '0x62d597ebcf3e8d60096dd0363bc2f0f5e2df27ba1dacd696c51aa7c9409f3193', + transaction_hash: '0x62d597ebcf3e8d60096dd0363bc2f0f5e2df27ba1dacd696c51aa7c9409f3193', type: 'token_transfer', timestamp: '2022-10-10T14:34:30.000000Z', + block_number: '12345', block_hash: '1', log_index: '1', method: 'updateSmartAsset', @@ -50,7 +52,7 @@ export const erc20: TokenTransfer = { export const erc721: TokenTransfer = { from: { hash: '0x621C2a125ec4A6D8A7C7A655A18a2868d35eb43C', - implementation_name: null, + implementations: null, is_contract: false, is_verified: false, name: null, @@ -61,7 +63,7 @@ export const erc721: TokenTransfer = { }, to: { hash: '0x47eE48AEBc4ab9Ed908b805b8c8dAAa71B31Db1A', - implementation_name: null, + implementations: null, is_contract: false, is_verified: false, name: null, @@ -85,9 +87,10 @@ export const erc721: TokenTransfer = { total: { token_id: '875879856', }, - tx_hash: '0xf13bc7afe5e02b494dd2f22078381d36a4800ef94a0ccc147431db56c301e6cc', + transaction_hash: '0xf13bc7afe5e02b494dd2f22078381d36a4800ef94a0ccc147431db56c301e6cc', type: 'token_transfer', timestamp: '2022-10-10T14:34:30.000000Z', + block_number: '12345', block_hash: '1', log_index: '1', method: 'updateSmartAsset', @@ -96,7 +99,7 @@ export const erc721: TokenTransfer = { export const erc1155A: TokenTransfer = { from: { hash: '0x0000000000000000000000000000000000000000', - implementation_name: null, + implementations: null, is_contract: false, is_verified: false, name: null, @@ -107,7 +110,7 @@ export const erc1155A: TokenTransfer = { }, to: { hash: '0xBb36c792B9B45Aaf8b848A1392B0d6559202729E', - implementation_name: null, + implementations: null, is_contract: false, is_verified: false, name: null, @@ -133,9 +136,10 @@ export const erc1155A: TokenTransfer = { value: '42', decimals: null, }, - tx_hash: '0x05d6589367633c032d757a69c5fb16c0e33e3994b0d9d1483f82aeee1f05d746', + transaction_hash: '0x05d6589367633c032d757a69c5fb16c0e33e3994b0d9d1483f82aeee1f05d746', type: 'token_minting', timestamp: '2022-10-10T14:34:30.000000Z', + block_number: '12345', block_hash: '1', log_index: '1', }; @@ -143,7 +147,7 @@ export const erc1155A: TokenTransfer = { export const erc1155B: TokenTransfer = { ...erc1155A, token: { - ...erc1155A.token, + ...(erc1155A.token as TokenInfo<'ERC-1155'>), name: 'SastanaNFT', symbol: 'ipfs://QmUpFUfVKDCWeZQk5pvDFUxnpQP9N6eLSHhNUy49T1JVtY', }, @@ -153,7 +157,7 @@ export const erc1155B: TokenTransfer = { export const erc1155C: TokenTransfer = { ...erc1155A, token: { - ...erc1155A.token, + ...(erc1155A.token as TokenInfo<'ERC-1155'>), name: 'SastanaNFT', symbol: 'ipfs://QmUpFUfVKDCWeZQk5pvDFUxnpQP9N6eLSHhNUy49T1JVtY', }, @@ -163,13 +167,72 @@ export const erc1155C: TokenTransfer = { export const erc1155D: TokenTransfer = { ...erc1155A, token: { - ...erc1155A.token, + ...(erc1155A.token as TokenInfo<'ERC-1155'>), name: 'SastanaNFT', symbol: 'ipfs://QmUpFUfVKDCWeZQk5pvDFUxnpQP9N6eLSHhNUy49T1JVtY', }, total: { token_id: '456', value: '42', decimals: null }, }; +export const erc404A: TokenTransfer = { + from: { + hash: '0x0000000000000000000000000000000000000000', + implementations: null, + is_contract: false, + is_verified: false, + name: null, + private_tags: [], + public_tags: [], + watchlist_names: [], + ens_domain_name: null, + }, + to: { + hash: '0xBb36c792B9B45Aaf8b848A1392B0d6559202729E', + implementations: null, + is_contract: false, + is_verified: false, + name: null, + private_tags: [], + public_tags: [], + watchlist_names: [], + ens_domain_name: 'kitty.kitty.cat.eth', + }, + token: { + address: '0xF56b7693E4212C584de4a83117f805B8E89224CB', + circulating_market_cap: null, + decimals: null, + exchange_rate: null, + holders: '1', + name: null, + symbol: 'MY_SYMBOL_IS_VERY_LONG', + type: 'ERC-404', + total_supply: '0', + icon_url: null, + }, + total: { + value: '42000000000000000000000000', + decimals: '18', + token_id: null, + }, + transaction_hash: '0x05d6589367633c032d757a69c5fb16c0e33e3994b0d9d1483f82aeee1f05d746', + type: 'token_transfer', + method: 'swap', + timestamp: '2022-10-10T14:34:30.000000Z', + block_number: '12345', + block_hash: '1', + log_index: '1', +}; + +export const erc404B: TokenTransfer = { + ...erc404A, + token: { + ...(erc404A.token as TokenInfo<'ERC-404'>), + name: 'SastanaNFT', + symbol: 'ipfs://QmUpFUfVKDCWeZQk5pvDFUxnpQP9N6eLSHhNUy49T1JVtY', + }, + total: { token_id: '4625304364899952' }, +}; + export const mixTokens: TokenTransferResponse = { items: [ erc20, @@ -178,6 +241,8 @@ export const mixTokens: TokenTransferResponse = { erc1155B, erc1155C, erc1155D, + erc404A, + erc404B, ], next_page_params: null, }; diff --git a/mocks/txs/internalTxs.ts b/mocks/txs/internalTxs.ts index 07eb83dc5e..7917e08517 100644 --- a/mocks/txs/internalTxs.ts +++ b/mocks/txs/internalTxs.ts @@ -1,12 +1,12 @@ import type { InternalTransaction, InternalTransactionsResponse } from 'types/api/internalTransaction'; export const base: InternalTransaction = { - block: 29611822, + block_number: 29611822, created_contract: null, error: null, from: { hash: '0xd789a607CEac2f0E14867de4EB15b15C9FFB5859', - implementation_name: null, + implementations: null, is_contract: true, is_verified: true, name: 'ArianeeStore', @@ -21,7 +21,7 @@ export const base: InternalTransaction = { timestamp: '2022-10-10T14:43:05.000000Z', to: { hash: '0x502a9C8af2441a1E276909405119FaE21F3dC421', - implementation_name: null, + implementations: null, is_contract: true, is_verified: true, name: 'ArianeeCreditHistory', @@ -56,7 +56,7 @@ export const withContractCreated: InternalTransaction = { }, created_contract: { hash: '0xdda21946FF3FAa027104b15BE6970CA756439F5a', - implementation_name: null, + implementations: null, is_contract: true, is_verified: null, name: 'Shavuha token', diff --git a/mocks/txs/state.ts b/mocks/txs/state.ts index 204fe1c867..d409f4679a 100644 --- a/mocks/txs/state.ts +++ b/mocks/txs/state.ts @@ -1,9 +1,9 @@ -import type { TxStateChange } from 'types/api/txStateChanges'; +import type { TxStateChange, TxStateChanges } from 'types/api/txStateChanges'; export const mintToken: TxStateChange = { address: { hash: '0x0000000000000000000000000000000000000000', - implementation_name: null, + implementations: null, is_contract: false, is_verified: false, name: null, @@ -35,13 +35,13 @@ export const mintToken: TxStateChange = { type: 'ERC-721', icon_url: null, }, - type: 'token', + type: 'token' as const, }; export const receiveMintedToken: TxStateChange = { address: { hash: '0xC8F71D0ae51AfBdB009E2eC1Ea8CC9Ee204A42B5', - implementation_name: null, + implementations: null, is_contract: false, is_verified: false, name: null, @@ -73,13 +73,13 @@ export const receiveMintedToken: TxStateChange = { type: 'ERC-721', icon_url: null, }, - type: 'token', + type: 'token' as const, }; export const transfer1155Token: TxStateChange = { address: { hash: '0x51243E83Db20F8FC2761D894067A2A9eb7B158DE', - implementation_name: null, + implementations: null, is_contract: false, is_verified: false, name: null, @@ -105,13 +105,13 @@ export const transfer1155Token: TxStateChange = { type: 'ERC-1155', }, token_id: '1', - type: 'token', + type: 'token' as const, }; export const receiveCoin: TxStateChange = { address: { hash: '0x8dC847Af872947Ac18d5d63fA646EB65d4D99560', - implementation_name: null, + implementations: null, is_contract: false, is_verified: null, name: null, @@ -125,13 +125,13 @@ export const receiveCoin: TxStateChange = { change: '29726406604060', is_miner: true, token: null, - type: 'coin', + type: 'coin' as const, }; export const sendCoin: TxStateChange = { address: { hash: '0xC8F71D0ae51AfBdB009E2eC1Ea8CC9Ee204A42B5', - implementation_name: null, + implementations: null, is_contract: false, is_verified: null, name: null, @@ -145,13 +145,14 @@ export const sendCoin: TxStateChange = { change: '-3844844822720562', is_miner: false, token: null, - type: 'coin', + type: 'coin' as const, }; -export const sendERC20Token = { +export const sendERC20Token: TxStateChange = { address: { hash: '0x7f6479df95Aa3036a3BE02DB6300ea201ABd9981', - implementation_name: null, + ens_domain_name: null, + implementations: null, is_contract: false, is_verified: false, name: null, @@ -173,13 +174,12 @@ export const sendERC20Token = { name: 'Tether USD', symbol: 'USDT', total_supply: '39030615894320966', - type: 'ERC-20', - token_id: null, + type: 'ERC-20' as const, }, - type: 'token', + type: 'token' as const, }; -export const baseResponse = { +export const baseResponse: TxStateChanges = { items: [ mintToken, receiveMintedToken, diff --git a/mocks/txs/stats.ts b/mocks/txs/stats.ts new file mode 100644 index 0000000000..7b05dc975a --- /dev/null +++ b/mocks/txs/stats.ts @@ -0,0 +1,8 @@ +import type { TransactionsStats } from 'types/api/transaction'; + +export const base: TransactionsStats = { + pending_transactions_count: '4200', + transaction_fees_avg_24h: '22342870314428', + transaction_fees_sum_24h: '22184012506492688277', + transactions_count_24h: '992890', +}; diff --git a/mocks/txs/tx.ts b/mocks/txs/tx.ts index 3ca7876b99..92bd43ed9b 100644 --- a/mocks/txs/tx.ts +++ b/mocks/txs/tx.ts @@ -1,13 +1,14 @@ /* eslint-disable max-len */ import type { Transaction } from 'types/api/transaction'; +import * as addressMock from 'mocks/address/address'; import { publicTag, privateTag, watchlistName } from 'mocks/address/tag'; import * as tokenTransferMock from 'mocks/tokens/tokenTransfer'; import * as decodedInputDataMock from 'mocks/txs/decodedInputData'; export const base: Transaction = { base_fee_per_gas: '10000000000', - block: 29611750, + block_number: 29611750, confirmation_duration: [ 0, 6364, @@ -22,7 +23,7 @@ export const base: Transaction = { }, from: { hash: '0x047A81aFB05D9B1f8844bf60fcA05DCCFbC584B9', - implementation_name: null, + implementations: null, is_contract: false, name: null, is_verified: null, @@ -47,8 +48,8 @@ export const base: Transaction = { status: 'ok', timestamp: '2022-10-10T14:34:30.000000Z', to: { - hash: '0xd789a607CEac2f0E14867de4EB15b15C9FFB5859', - implementation_name: null, + hash: addressMock.hash, + implementations: null, is_contract: false, is_verified: true, name: null, @@ -60,14 +61,14 @@ export const base: Transaction = { token_transfers: [], token_transfers_overflow: false, tx_burnt_fee: '461030000000000', - tx_tag: null, - tx_types: [ + transaction_tag: null, + transaction_types: [ 'contract_call', ], type: 2, value: '42000000000000000000', actions: [], - has_error_in_internal_txs: false, + has_error_in_internal_transactions: false, }; export const withWatchListNames: Transaction = { @@ -92,7 +93,7 @@ export const withContractCreation: Transaction = { to: null, created_contract: { hash: '0xdda21946FF3FAa027104b15BE6970CA756439F5a', - implementation_name: null, + implementations: null, is_contract: true, is_verified: null, name: 'Shavuha token', @@ -101,7 +102,7 @@ export const withContractCreation: Transaction = { watchlist_names: [], ens_domain_name: null, }, - tx_types: [ + transaction_types: [ 'contract_creation', ], }; @@ -110,8 +111,8 @@ export const withTokenTransfer: Transaction = { ...base, hash: '0x62d597ebcf3e8d60096dd0363bc2f0f5e2df27ba1dacd696c51aa7c9409f3196', to: { - hash: '0xd789a607CEac2f0E14867de4EB15b15C9FFB5859', - implementation_name: null, + hash: addressMock.hash, + implementations: null, is_contract: true, is_verified: true, name: 'ArianeeStore', @@ -127,9 +128,11 @@ export const withTokenTransfer: Transaction = { tokenTransferMock.erc1155B, tokenTransferMock.erc1155C, tokenTransferMock.erc1155D, + tokenTransferMock.erc404A, + tokenTransferMock.erc404B, ], token_transfers_overflow: true, - tx_types: [ + transaction_types: [ 'token_transfer', ], }; @@ -164,8 +167,8 @@ export const withRawRevertReason: Transaction = { raw: '4f6e6c79206368616972706572736f6e2063616e206769766520726967687420746f20766f74652e', }, to: { - hash: '0xd789a607CEac2f0E14867de4EB15b15C9FFB5859', - implementation_name: null, + hash: addressMock.hash, + implementations: null, is_verified: true, is_contract: true, name: 'Bad guy', @@ -179,7 +182,7 @@ export const withRawRevertReason: Transaction = { export const pending: Transaction = { ...base, base_fee_per_gas: null, - block: null, + block_number: null, confirmation_duration: [], confirmations: 0, decoded_input: null, @@ -194,7 +197,7 @@ export const pending: Transaction = { status: null, timestamp: null, tx_burnt_fee: null, - tx_tag: null, + transaction_tag: null, type: null, value: '0', }; @@ -281,7 +284,7 @@ export const stabilityTx: Transaction = { stability_fee: { dapp_address: { hash: '0xDc2B93f3291030F3F7a6D9363ac37757f7AD5C43', - implementation_name: null, + implementations: null, is_contract: false, is_verified: null, name: null, @@ -297,7 +300,7 @@ export const stabilityTx: Transaction = { decimals: '18', exchange_rate: '123.567', holders: '92', - icon_url: null, + icon_url: 'https://example.com/icon.png', name: 'Stability Gas', symbol: 'GAS', total_supply: '10000000000000000000000000', @@ -306,7 +309,7 @@ export const stabilityTx: Transaction = { total_fee: '68762500000000', validator_address: { hash: '0x1432997a4058acbBe562F3c1E79738c142039044', - implementation_name: null, + implementations: null, is_contract: false, is_verified: null, name: null, @@ -319,12 +322,57 @@ export const stabilityTx: Transaction = { }, }; +export const celoTxn: Transaction = { + ...base, + celo: { + gas_token: { + address: '0x874069Fa1Eb16D44d622F2e0Ca25eeA172369bC1', + circulating_market_cap: null, + decimals: '18', + exchange_rate: '0.42', + holders: '205738', + icon_url: 'https://example.com/icon.png', + name: 'Celo Dollar', + symbol: 'cUSD', + total_supply: '7145754483836626799435133', + type: 'ERC-20', + }, + }, +}; + +export const arbitrumTxn: Transaction = { + ...base, + arbitrum: { + batch_number: 743991, + commitment_transaction: { + hash: '0x71a25e01dde129a308704de217d200ea42e0f5b8c221c8ba8b2b680ff347f708', + status: 'unfinalized', + timestamp: '2024-11-19T14:26:23.000000Z', + }, + confirmation_transaction: { + hash: null, + status: null, + timestamp: null, + }, + contains_message: null, + gas_used_for_l1: '129773', + gas_used_for_l2: '128313', + message_related_info: { + associated_l1_transaction: null, + message_status: 'Relayed', + }, + network_fee: '1283130000000', + poster_fee: '1297730000000', + status: 'Sent to base', + }, +}; + export const base2 = { ...base, hash: '0x02d597ebcf3e8d60096dd0363bc2f0f5e2df27ba1dacd696c51aa7c9409f3193', from: { ...base.from, - hash: '0xd789a607CEac2f0E14867de4EB15b15C9FFB5859', + hash: addressMock.hash, }, }; @@ -333,7 +381,7 @@ export const base3 = { hash: '0x12d597ebcf3e8d60096dd0363bc2f0f5e2df27ba1dacd696c51aa7c9409f3193', from: { ...base.from, - hash: '0xd789a607CEac2f0E14867de4EB15b15C9FFB5859', + hash: addressMock.hash, }, }; @@ -341,3 +389,37 @@ export const base4 = { ...base, hash: '0x22d597ebcf3e8d60096dd0363bc2f0f5e2df27ba1dacd696c51aa7c9409f3193', }; + +export const withBlob = { + ...base, + blob_gas_price: '21518435987', + blob_gas_used: '131072', + blob_versioned_hashes: [ + '0x01a8c328b0370068aaaef49c107f70901cd79adcda81e3599a88855532122e09', + '0x0197fdb17195c176b23160f335daabd4b6a231aaaadd73ec567877c66a3affd1', + ], + burnt_blob_fee: '2820464441688064', + max_fee_per_blob_gas: '60000000000', + transaction_types: [ 'blob_transaction' as const ], + type: 3, +}; + +export const withRecipientName = { + ...base, + to: addressMock.withName, +}; + +export const withRecipientEns = { + ...base, + to: addressMock.withEns, +}; + +export const withRecipientNameTag = { + ...withRecipientEns, + to: addressMock.withNameTag, +}; + +export const withRecipientContract = { + ...withRecipientEns, + to: addressMock.contract, +}; diff --git a/mocks/txs/txInterpretation.ts b/mocks/txs/txInterpretation.ts index e9c1a43b85..764d697a6b 100644 --- a/mocks/txs/txInterpretation.ts +++ b/mocks/txs/txInterpretation.ts @@ -1,5 +1,7 @@ import type { TxInterpretationResponse } from 'types/api/txInterpretation'; +import { hash } from 'mocks/address/address'; + export const txInterpretation: TxInterpretationResponse = { data: { summaries: [ { @@ -25,8 +27,8 @@ export const txInterpretation: TxInterpretationResponse = { to_address: { type: 'address', value: { - hash: '0x48c04ed5691981C42154C6167398f95e8f38a7fF', - implementation_name: null, + hash: hash, + implementations: null, is_contract: false, is_verified: false, name: null, diff --git a/mocks/user/profile.ts b/mocks/user/profile.ts index e5caed3b63..7794447c25 100644 --- a/mocks/user/profile.ts +++ b/mocks/user/profile.ts @@ -1,6 +1,25 @@ -export const base = { +import type { UserInfo } from 'types/api/account'; + +export const base: UserInfo = { avatar: 'https://avatars.githubusercontent.com/u/22130104', email: 'tom@ohhhh.me', name: 'tom goriunov', nickname: 'tom2drum', + address_hash: null, +}; + +export const withoutEmail: UserInfo = { + avatar: 'https://avatars.githubusercontent.com/u/22130104', + email: null, + name: 'tom goriunov', + nickname: 'tom2drum', + address_hash: '0xd789a607CEac2f0E14867de4EB15b15C9FFB5859', +}; + +export const withEmailAndWallet: UserInfo = { + avatar: 'https://avatars.githubusercontent.com/u/22130104', + email: 'tom@ohhhh.me', + name: 'tom goriunov', + nickname: 'tom2drum', + address_hash: '0xd789a607CEac2f0E14867de4EB15b15C9FFB5859', }; diff --git a/mocks/userOps/userOp.ts b/mocks/userOps/userOp.ts index efb7517187..d1508aad6c 100644 --- a/mocks/userOps/userOp.ts +++ b/mocks/userOps/userOp.ts @@ -1,3 +1,4 @@ +/* eslint-disable max-len */ import type { UserOp } from 'types/api/userOps'; export const userOpData: UserOp = { @@ -24,7 +25,7 @@ export const userOpData: UserOp = { sender: { ens_domain_name: null, hash: '0xF0C14FF4404b188fAA39a3507B388998c10FE284', - implementation_name: null, + implementations: null, is_contract: true, is_verified: null, name: null, @@ -33,21 +34,20 @@ export const userOpData: UserOp = { entry_point: { ens_domain_name: null, hash: '0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789', - implementation_name: null, + implementations: null, is_contract: true, is_verified: null, name: null, }, sponsor_type: 'paymaster_sponsor', raw: { - // eslint-disable-next-line max-len + call_data: '0xb61d27f600000000000000000000000059f6aa952df7f048fd076e33e0ea8bb552d5ffd8000000000000000000000000000000000000000000000000003f3d017500800000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000', call_gas_limit: '26624', init_code: '0x', max_fee_per_gas: '1575000898', max_priority_fee_per_gas: '1575000898', nonce: '79', - // eslint-disable-next-line max-len paymaster_and_data: '0x7cea357b5ac0639f89f9e378a1f03aa5005c0a250000000000000000000000000000000000000000000000000000000065b3a8800000000000000000000000000000000000000000000000000000000065aa6e0028fa4c57ac1141bc9ecd8c9243f618ade8ea1db10ab6c1d1798a222a824764ff2269a72ae7a3680fa8b03a80d8a00cdc710eaf37afdcc55f8c9c4defa3fdf2471b', pre_verification_gas: '48396', sender: '0xF0C14FF4404b188fAA39a3507B388998c10FE284', @@ -59,17 +59,43 @@ export const userOpData: UserOp = { bundler: { ens_domain_name: null, hash: '0xd53Eb5203e367BbDD4f72338938224881Fc501Ab', - implementation_name: null, + implementations: null, is_contract: false, is_verified: null, name: null, }, - // eslint-disable-next-line max-len call_data: '0xb61d27f600000000000000000000000059f6aa952df7f048fd076e33e0ea8bb552d5ffd8000000000000000000000000000000000000000000000000003f3d017500800000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000', + execute_call_data: '0x3cf80e6c', + decoded_call_data: { + method_call: 'execute(address dest, uint256 value, bytes func)', + method_id: 'b61d27f6', + parameters: [ + { + name: 'dest', + type: 'address', + value: '0xb0ccffd05f5a87c4c3ceffaa217900422a249915', + }, + { + name: 'value', + type: 'uint256', + value: '0', + }, + { + name: 'func', + type: 'bytes', + value: '0x3cf80e6c', + }, + ], + }, + decoded_execute_call_data: { + method_call: 'advanceEpoch()', + method_id: '3cf80e6c', + parameters: [], + }, paymaster: { ens_domain_name: null, hash: '0x7ceA357B5AC0639F89F9e378a1f03Aa5005C0a25', - implementation_name: null, + implementations: null, is_contract: true, is_verified: null, name: null, diff --git a/mocks/userOps/userOps.ts b/mocks/userOps/userOps.ts index a58ff6ed74..9a44e62594 100644 --- a/mocks/userOps/userOps.ts +++ b/mocks/userOps/userOps.ts @@ -6,7 +6,7 @@ export const userOpsData: UserOpsResponse = { address: { ens_domain_name: null, hash: '0xF0C14FF4404b188fAA39a3507B388998c10FE284', - implementation_name: null, + implementations: null, is_contract: true, is_verified: null, name: null, @@ -22,7 +22,7 @@ export const userOpsData: UserOpsResponse = { address: { ens_domain_name: null, hash: '0x2c298CcaFFD1549e1C21F46966A6c236fCC66dB2', - implementation_name: null, + implementations: null, is_contract: true, is_verified: null, name: null, @@ -38,7 +38,7 @@ export const userOpsData: UserOpsResponse = { address: { ens_domain_name: null, hash: '0x2c298CcaFFD1549e1C21F46966A6c236fCC66dB2', - implementation_name: null, + implementations: null, is_contract: true, is_verified: null, name: null, diff --git a/mocks/validators/blackfort.ts b/mocks/validators/blackfort.ts new file mode 100644 index 0000000000..6a5232a321 --- /dev/null +++ b/mocks/validators/blackfort.ts @@ -0,0 +1,41 @@ +import type { + ValidatorBlackfort, + ValidatorsBlackfortCountersResponse, + ValidatorsBlackfortResponse, +} from 'types/api/validators'; + +import * as addressMock from '../address/address'; + +export const validator1: ValidatorBlackfort = { + address: addressMock.withName, + name: 'testnet-3', + commission: 10, + delegated_amount: '0', + self_bonded_amount: '10000', +}; + +export const validator2: ValidatorBlackfort = { + address: addressMock.withEns, + name: 'GooseGanG GooseGanG GooseGanG GooseGanG GooseGanG GooseGanG GooseGanG', + commission: 5000, + delegated_amount: '10000', + self_bonded_amount: '100', +}; + +export const validator3: ValidatorBlackfort = { + address: addressMock.withoutName, + name: 'testnet-1', + commission: 0, + delegated_amount: '0', + self_bonded_amount: '10000', +}; + +export const validatorsResponse: ValidatorsBlackfortResponse = { + items: [ validator1, validator2, validator3 ], + next_page_params: null, +}; + +export const validatorsCountersResponse: ValidatorsBlackfortCountersResponse = { + new_validators_counter_24h: '11', + validators_counter: '140', +}; diff --git a/mocks/validators/stability.ts b/mocks/validators/stability.ts new file mode 100644 index 0000000000..a85f596674 --- /dev/null +++ b/mocks/validators/stability.ts @@ -0,0 +1,37 @@ +import type { + ValidatorStability, + ValidatorsStabilityCountersResponse, + ValidatorsStabilityResponse, +} from 'types/api/validators'; + +import * as addressMock from '../address/address'; + +export const validator1: ValidatorStability = { + address: addressMock.withName, + blocks_validated_count: 7334224, + state: 'active', +}; + +export const validator2: ValidatorStability = { + address: addressMock.withEns, + blocks_validated_count: 8937453, + state: 'probation', +}; + +export const validator3: ValidatorStability = { + address: addressMock.withoutName, + blocks_validated_count: 1234, + state: 'inactive', +}; + +export const validatorsResponse: ValidatorsStabilityResponse = { + items: [ validator1, validator2, validator3 ], + next_page_params: null, +}; + +export const validatorsCountersResponse: ValidatorsStabilityCountersResponse = { + active_validators_counter: '42', + active_validators_percentage: 7.14, + new_validators_counter_24h: '11', + validators_counter: '140', +}; diff --git a/mocks/withdrawals/withdrawals.ts b/mocks/withdrawals/withdrawals.ts index 37d1da51e1..97742fe3d6 100644 --- a/mocks/withdrawals/withdrawals.ts +++ b/mocks/withdrawals/withdrawals.ts @@ -1,4 +1,7 @@ -export const data = { +import type { AddressParam } from 'types/api/addressParams'; +import type { WithdrawalsResponse } from 'types/api/withdrawals'; + +export const data: WithdrawalsResponse = { items: [ { amount: '192175000000000', @@ -6,11 +9,11 @@ export const data = { index: 11688, receiver: { hash: '0xf97e180c050e5Ab072211Ad2C213Eb5AEE4DF134', - implementation_name: null, + implementations: null, is_contract: false, is_verified: null, name: null, - }, + } as AddressParam, timestamp: '2022-06-07T18:12:24.000000Z', validator_index: 49622, }, @@ -20,11 +23,11 @@ export const data = { index: 11687, receiver: { hash: '0xf97e987c050e5Ab072211Ad2C213Eb5AEE4DF134', - implementation_name: null, + implementations: null, is_contract: false, is_verified: null, name: null, - }, + } as AddressParam, timestamp: '2022-05-07T18:12:24.000000Z', validator_index: 49621, }, @@ -34,11 +37,11 @@ export const data = { index: 11686, receiver: { hash: '0xf97e123c050e5Ab072211Ad2C213Eb5AEE4DF134', - implementation_name: null, + implementations: null, is_contract: false, is_verified: null, name: null, - }, + } as AddressParam, timestamp: '2022-04-07T18:12:24.000000Z', validator_index: 49620, }, diff --git a/mocks/zkEvm/deposits.ts b/mocks/zkEvm/deposits.ts new file mode 100644 index 0000000000..91ecc077c3 --- /dev/null +++ b/mocks/zkEvm/deposits.ts @@ -0,0 +1,28 @@ +import type { ZkEvmL2DepositsResponse } from 'types/api/zkEvmL2'; + +export const baseResponse: ZkEvmL2DepositsResponse = { + items: [ + { + block_number: 19681943, + index: 182177, + l1_transaction_hash: '0x29074452f976064aca1ca5c6e7c82d890c10454280693e6eca0257ae000c8e85', + l2_transaction_hash: null, + symbol: 'DAI', + timestamp: '2022-04-18T11:08:11.000000Z', + value: '0.003', + }, + { + block_number: 19681894, + index: 182176, + l1_transaction_hash: '0x0b7d58c0a6b4695ba28d99df928591fb931c812c0aab6d0093ff5040d2f9bc5e', + l2_transaction_hash: '0x210d9f70f411de1079e32a98473b04345a5ea6ff2340a8511ebc2df641274436', + symbol: 'ETH', + timestamp: '2022-04-18T10:58:23.000000Z', + value: '0.0046651390188845', + }, + ], + next_page_params: { + items_count: 50, + index: 1, + }, +}; diff --git a/mocks/zkEvm/txnBatches.ts b/mocks/zkEvm/txnBatches.ts new file mode 100644 index 0000000000..194148c16b --- /dev/null +++ b/mocks/zkEvm/txnBatches.ts @@ -0,0 +1,40 @@ +import type { ZkEvmL2TxnBatch, ZkEvmL2TxnBatchesResponse } from 'types/api/zkEvmL2'; + +export const txnBatchData: ZkEvmL2TxnBatch = { + acc_input_hash: '0x4bf88aabe33713b7817266d7860912c58272d808da7397cdc627ca53b296fad3', + global_exit_root: '0xad3228b676f7d3cd4284a5443f17f1962b36e491b30a40b2405849e597ba5fb5', + number: 5, + sequence_transaction_hash: '0x7ae010e9758441b302db10282807358af460f38c49c618d26a897592f64977f7', + state_root: '0x183b4a38a4a6027947ceb93b323cc94c548c8c05cf605d73ca88351d77cae1a3', + status: 'Finalized', + timestamp: '2023-10-20T10:08:18.000000Z', + transactions: [ + '0xb5d432c270057c223b973f3b5f00dbad32823d9ef26f3e8d97c819c7c573453a', + ], + verify_transaction_hash: '0x6f7eeaa0eb966e63d127bba6bf8f9046d303c2a1185b542f0b5083f682a0e87f', +}; + +export const txnBatchesData: ZkEvmL2TxnBatchesResponse = { + items: [ + { + timestamp: '2023-06-01T14:46:48.000000Z', + status: 'Finalized', + verify_transaction_hash: '0x48139721f792d3a68c3781b4cf50e66e8fc7dbb38adff778e09066ea5be9adb8', + sequence_transaction_hash: '0x6aa081e8e33a085e4ec7124fcd8a5f7d36aac0828f176e80d4b70e313a11695b', + number: 5218590, + transaction_count: 9, + }, + { + timestamp: '2023-06-01T14:46:48.000000Z', + status: 'Unfinalized', + verify_transaction_hash: null, + sequence_transaction_hash: null, + number: 5218591, + transaction_count: 9, + }, + ], + next_page_params: { + number: 5902834, + items_count: 50, + }, +}; diff --git a/mocks/zkEvm/withdrawals.ts b/mocks/zkEvm/withdrawals.ts new file mode 100644 index 0000000000..c89635f4e5 --- /dev/null +++ b/mocks/zkEvm/withdrawals.ts @@ -0,0 +1,28 @@ +import type { ZkEvmL2WithdrawalsResponse } from 'types/api/zkEvmL2'; + +export const baseResponse: ZkEvmL2WithdrawalsResponse = { + items: [ + { + block_number: 11722417, + index: 47040, + l1_transaction_hash: null, + l2_transaction_hash: '0x68c378e412e51553524545ef1d3f00f69496fb37827c0b3b7e0870d245970408', + symbol: 'ETH', + timestamp: '2022-04-18T09:20:37.000000Z', + value: '0.025', + }, + { + block_number: 11722480, + index: 47041, + l1_transaction_hash: '0xbf76feb85b8b8f24dacb17f962dd359f82efc512928d7b11ffca92fb812ad6a5', + l2_transaction_hash: '0xfe3c168ac1751b8399f1e819f1d83ee4cf764128bc604d454abee29114dabf49', + symbol: 'ETH', + timestamp: '2022-04-18T09:23:45.000000Z', + value: '4', + }, + ], + next_page_params: { + items_count: 50, + index: 1, + }, +}; diff --git a/mocks/zkSync/zkSyncTxnBatch.ts b/mocks/zkSync/zkSyncTxnBatch.ts new file mode 100644 index 0000000000..79c5e28b39 --- /dev/null +++ b/mocks/zkSync/zkSyncTxnBatch.ts @@ -0,0 +1,20 @@ +import type { ZkSyncBatch } from 'types/api/zkSyncL2'; + +export const base: ZkSyncBatch = { + commit_transaction_hash: '0x7cd80c88977c2b310f79196b0b2136da18012be015ce80d0d9e9fe6cfad52b16', + commit_transaction_timestamp: '2022-03-19T09:37:38.726996Z', + end_block: 1245490, + execute_transaction_hash: '0x110b9a19afbabd5818a996ab2b493a9b23c888d73d95f1ab5272dbae503e103a', + execute_transaction_timestamp: '2022-03-19T10:29:05.358066Z', + l1_gas_price: '4173068062', + l1_transaction_count: 0, + l2_fair_gas_price: '100000000', + l2_transaction_count: 287, + number: 8051, + prove_transaction_hash: '0xb424162ba5afe17c710dceb5fc8d15d7d46a66223454dae8c74aa39f6802625b', + prove_transaction_timestamp: '2022-03-19T10:29:05.279179Z', + root_hash: '0x108c635b94f941fcabcb85500daec2f6be4f0747dff649b1cdd9dd7a7a264792', + start_block: 1245209, + status: 'Executed on L1', + timestamp: '2022-03-19T09:05:49.000000Z', +}; diff --git a/mocks/zkSync/zkSyncTxnBatches.ts b/mocks/zkSync/zkSyncTxnBatches.ts new file mode 100644 index 0000000000..9bd3188eca --- /dev/null +++ b/mocks/zkSync/zkSyncTxnBatches.ts @@ -0,0 +1,49 @@ +import type { ZkSyncBatchesItem, ZkSyncBatchesResponse } from 'types/api/zkSyncL2'; + +export const sealed: ZkSyncBatchesItem = { + commit_transaction_hash: null, + commit_transaction_timestamp: null, + execute_transaction_hash: null, + execute_transaction_timestamp: null, + number: 8055, + prove_transaction_hash: null, + prove_transaction_timestamp: null, + status: 'Sealed on L2', + timestamp: '2022-03-19T12:53:36.000000Z', + transaction_count: 738, +}; + +export const sent: ZkSyncBatchesItem = { + commit_transaction_hash: '0x262e7215739d6a7e33b2c20b45a838801a0f5f080f20bec8e54eb078420c4661', + commit_transaction_timestamp: '2022-03-19T13:09:07.357570Z', + execute_transaction_hash: null, + execute_transaction_timestamp: null, + number: 8054, + prove_transaction_hash: null, + prove_transaction_timestamp: null, + status: 'Sent to L1', + timestamp: '2022-03-19T11:36:45.000000Z', + transaction_count: 766, +}; + +export const executed: ZkSyncBatchesItem = { + commit_transaction_hash: '0xa2628f477e1027ac1c60fa75c186b914647769ac1cb9c7e1cab50b13506a0035', + commit_transaction_timestamp: '2022-03-19T11:52:18.963659Z', + execute_transaction_hash: '0xb7bd6b2b17498c66d3f6e31ac3685133a81b7f728d4f6a6f42741daa257d0d68', + execute_transaction_timestamp: '2022-03-19T13:28:16.712656Z', + number: 8053, + prove_transaction_hash: '0x9d44f2b775bd771f8a53205755b3897929aa672d2cd419b3b988c16d41d4f21e', + prove_transaction_timestamp: '2022-03-19T13:28:16.603104Z', + status: 'Executed on L1', + timestamp: '2022-03-19T10:01:52.000000Z', + transaction_count: 1071, +}; + +export const baseResponse: ZkSyncBatchesResponse = { + items: [ + sealed, + sent, + executed, + ], + next_page_params: null, +}; diff --git a/mocks/zkevmL2txnBatches/zkevmL2txnBatch.ts b/mocks/zkevmL2txnBatches/zkevmL2txnBatch.ts deleted file mode 100644 index 172481ce60..0000000000 --- a/mocks/zkevmL2txnBatches/zkevmL2txnBatch.ts +++ /dev/null @@ -1,15 +0,0 @@ -import type { ZkEvmL2TxnBatch } from 'types/api/zkEvmL2TxnBatches'; - -export const txnBatchData: ZkEvmL2TxnBatch = { - acc_input_hash: '0x4bf88aabe33713b7817266d7860912c58272d808da7397cdc627ca53b296fad3', - global_exit_root: '0xad3228b676f7d3cd4284a5443f17f1962b36e491b30a40b2405849e597ba5fb5', - number: 5, - sequence_tx_hash: '0x7ae010e9758441b302db10282807358af460f38c49c618d26a897592f64977f7', - state_root: '0x183b4a38a4a6027947ceb93b323cc94c548c8c05cf605d73ca88351d77cae1a3', - status: 'Finalized', - timestamp: '2023-10-20T10:08:18.000000Z', - transactions: [ - '0xb5d432c270057c223b973f3b5f00dbad32823d9ef26f3e8d97c819c7c573453a', - ], - verify_tx_hash: '0x6f7eeaa0eb966e63d127bba6bf8f9046d303c2a1185b542f0b5083f682a0e87f', -}; diff --git a/mocks/zkevmL2txnBatches/zkevmL2txnBatches.ts b/mocks/zkevmL2txnBatches/zkevmL2txnBatches.ts deleted file mode 100644 index 5d81a72686..0000000000 --- a/mocks/zkevmL2txnBatches/zkevmL2txnBatches.ts +++ /dev/null @@ -1,26 +0,0 @@ -import type { ZkEvmL2TxnBatchesResponse } from 'types/api/zkEvmL2TxnBatches'; - -export const txnBatchesData: ZkEvmL2TxnBatchesResponse = { - items: [ - { - timestamp: '2023-06-01T14:46:48.000000Z', - status: 'Finalized', - verify_tx_hash: '0x48139721f792d3a68c3781b4cf50e66e8fc7dbb38adff778e09066ea5be9adb8', - sequence_tx_hash: '0x6aa081e8e33a085e4ec7124fcd8a5f7d36aac0828f176e80d4b70e313a11695b', - number: 5218590, - tx_count: 9, - }, - { - timestamp: '2023-06-01T14:46:48.000000Z', - status: 'Unfinalized', - verify_tx_hash: null, - sequence_tx_hash: null, - number: 5218591, - tx_count: 9, - }, - ], - next_page_params: { - number: 5902834, - items_count: 50, - }, -}; diff --git a/next-env.d.ts b/next-env.d.ts index 4f11a03dc6..a4a7b3f5cf 100644 --- a/next-env.d.ts +++ b/next-env.d.ts @@ -2,4 +2,4 @@ /// // NOTE: This file should not be edited -// see https://nextjs.org/docs/basic-features/typescript for more information. +// see https://nextjs.org/docs/pages/building-your-application/configuring/typescript for more information. diff --git a/next.config.js b/next.config.js index 8789a1a641..1ac9991d07 100644 --- a/next.config.js +++ b/next.config.js @@ -10,6 +10,7 @@ const headers = require('./nextjs/headers'); const redirects = require('./nextjs/redirects'); const rewrites = require('./nextjs/rewrites'); +/** @type {import('next').NextConfig} */ const moduleExports = { transpilePackages: [ 'react-syntax-highlighter', @@ -17,13 +18,7 @@ const moduleExports = { 'swagger-ui-react', ], reactStrictMode: true, - webpack(config, { webpack }) { - config.plugins.push( - new webpack.DefinePlugin({ - __SENTRY_DEBUG__: false, - __SENTRY_TRACING__: false, - }), - ); + webpack(config) { config.module.rules.push( { test: /\.svg$/, @@ -45,7 +40,10 @@ const moduleExports = { output: 'standalone', productionBrowserSourceMaps: true, experimental: { - instrumentationHook: true, + staleTimes: { + dynamic: 30, + 'static': 180, + }, }, }; diff --git a/nextjs/PageNextJs.tsx b/nextjs/PageNextJs.tsx index 1c15c9f419..9993410d8b 100644 --- a/nextjs/PageNextJs.tsx +++ b/nextjs/PageNextJs.tsx @@ -2,21 +2,23 @@ import Head from 'next/head'; import React from 'react'; import type { Route } from 'nextjs-routes'; +import type { Props as PageProps } from 'nextjs/getServerSideProps'; +import config from 'configs/app'; import useAdblockDetect from 'lib/hooks/useAdblockDetect'; import useGetCsrfToken from 'lib/hooks/useGetCsrfToken'; import * as metadata from 'lib/metadata'; import * as mixpanel from 'lib/mixpanel'; -import { init as initSentry } from 'lib/sentry/config'; -type Props = Route & { +interface Props { + pathname: Pathname; children: React.ReactNode; + query?: PageProps['query']; + apiData?: PageProps['apiData']; } -initSentry(); - -const PageNextJs = (props: Props) => { - const { title, description, opengraph } = metadata.generate(props); +const PageNextJs = (props: Props) => { + const { title, description, opengraph, canonical } = metadata.generate(props, props.apiData); useGetCsrfToken(); useAdblockDetect(); @@ -29,14 +31,20 @@ const PageNextJs = (props: Props) => { { title } + { canonical && } { /* OG TAGS */ } { opengraph.description && } + + + { /* Twitter Meta Tags */ } + + + { opengraph.description && } - { props.children } diff --git a/nextjs/csp/generateCspPolicy.ts b/nextjs/csp/generateCspPolicy.ts index da84b197e5..2f82ae563a 100644 --- a/nextjs/csp/generateCspPolicy.ts +++ b/nextjs/csp/generateCspPolicy.ts @@ -6,14 +6,18 @@ function generateCspPolicy() { descriptors.app(), descriptors.ad(), descriptors.cloudFlare(), + descriptors.gasHawk(), descriptors.googleAnalytics(), descriptors.googleFonts(), descriptors.googleReCaptcha(), descriptors.growthBook(), + descriptors.helia(), + descriptors.marketplace(), descriptors.mixpanel(), descriptors.monaco(), + descriptors.rollbar(), descriptors.safe(), - descriptors.sentry(), + descriptors.usernameApi(), descriptors.walletConnect(), descriptors.boolNetwork(), ); diff --git a/nextjs/csp/policies/ad.ts b/nextjs/csp/policies/ad.ts index 2808dfb07c..9b018aa019 100644 --- a/nextjs/csp/policies/ad.ts +++ b/nextjs/csp/policies/ad.ts @@ -3,30 +3,62 @@ import sha256 from 'crypto-js/sha256'; import type CspDev from 'csp-dev'; import { connectAdbutler, placeAd } from 'ui/shared/ad/adbutlerScript'; +import { hypeInit } from 'ui/shared/ad/hypeBannerScript'; export function ad(): CspDev.DirectiveDescriptor { return { 'connect-src': [ + // coinzilla 'coinzilla.com', '*.coinzilla.com', 'https://request-global.czilladx.com', + + // adbutler + 'servedbyadbutler.com', + + // slise '*.slise.xyz', + + // hype + 'api.hypelab.com', + '*.ixncdn.com', + '*.cloudfront.net', + + //getit + 'v1.getittech.io', + 'ipapi.co', ], 'frame-src': [ + // coinzilla 'https://request-global.czilladx.com', ], 'script-src': [ + // coinzilla 'coinzillatag.com', + + // adbutler 'servedbyadbutler.com', `'sha256-${ Base64.stringify(sha256(connectAdbutler)) }'`, - `'sha256-${ Base64.stringify(sha256(placeAd ?? '')) }'`, + `'sha256-${ Base64.stringify(sha256(placeAd(undefined) ?? '')) }'`, + `'sha256-${ Base64.stringify(sha256(placeAd('mobile') ?? '')) }'`, + + // slise '*.slise.xyz', + + //hype + `'sha256-${ Base64.stringify(sha256(hypeInit ?? '')) }'`, + 'https://api.hypelab.com', + 'd1q98dzwj6s2rb.cloudfront.net', ], 'img-src': [ - 'servedbyadbutler.com', + // coinzilla 'cdn.coinzilla.io', + + // adbutler + 'servedbyadbutler.com', ], 'font-src': [ + // coinzilla 'https://request-global.czilladx.com', ], }; diff --git a/nextjs/csp/policies/app.ts b/nextjs/csp/policies/app.ts index c1b5355516..2ba5f68f5d 100644 --- a/nextjs/csp/policies/app.ts +++ b/nextjs/csp/policies/app.ts @@ -11,24 +11,17 @@ const MAIN_DOMAINS = [ config.app.host, ].filter(Boolean); -const getCspReportUrl = () => { +const externalFontsDomains = (() => { try { - const sentryFeature = config.features.sentry; - if (!sentryFeature.isEnabled || !process.env.SENTRY_CSP_REPORT_URI) { - return; - } - - const url = new URL(process.env.SENTRY_CSP_REPORT_URI); - - // https://docs.sentry.io/product/security-policy-reporting/#additional-configuration - url.searchParams.set('sentry_environment', sentryFeature.environment); - sentryFeature.release && url.searchParams.set('sentry_release', sentryFeature.release); - - return url.toString(); - } catch (error) { - return; - } -}; + return [ + config.UI.fonts.heading?.url, + config.UI.fonts.body?.url, + ] + .filter(Boolean) + .map((urlString) => new URL(urlString)) + .map((url) => url.hostname); + } catch (error) {} +})(); export function app(): CspDev.DirectiveDescriptor { return { @@ -54,6 +47,8 @@ export function app(): CspDev.DirectiveDescriptor { getFeaturePayload(config.features.verifiedTokens)?.api.endpoint, getFeaturePayload(config.features.addressVerification)?.api.endpoint, getFeaturePayload(config.features.nameService)?.api.endpoint, + getFeaturePayload(config.features.addressMetadata)?.api.endpoint, + getFeaturePayload(config.features.rewards)?.api.endpoint, // chain RPC server config.chain.rpcUrl, @@ -61,6 +56,9 @@ export function app(): CspDev.DirectiveDescriptor { // github (spec for api-docs page) 'raw.githubusercontent.com', + + // github api (used for Stylus contract verification) + 'api.github.com', ].filter(Boolean), 'script-src': [ @@ -71,8 +69,12 @@ export function app(): CspDev.DirectiveDescriptor { // https://github.com/vercel/next.js/issues/14221#issuecomment-657258278 config.app.isDev ? KEY_WORDS.UNSAFE_EVAL : '', - // hash of ColorModeScript + // hash of ColorModeScript: system + dark '\'sha256-e7MRMmTzLsLQvIy1iizO1lXf7VWYoQ6ysj5fuUzvRwE=\'', + '\'sha256-9A7qFFHmxdWjZMQmfzYD2XWaNHLu1ZmQB0Ds4Go764k=\'', + + // CapybaraRunner + '\'sha256-5+YTmTcBwCYdJ8Jetbr6kyjGp0Ry/H7ptpoun6CrSwQ=\'', ], 'style-src': [ @@ -108,12 +110,14 @@ export function app(): CspDev.DirectiveDescriptor { ], 'media-src': [ + KEY_WORDS.BLOB, '*', // see comment for img-src directive ], 'font-src': [ KEY_WORDS.DATA, ...MAIN_DOMAINS, + ...(externalFontsDomains || []), ], 'object-src': [ @@ -129,16 +133,11 @@ export function app(): CspDev.DirectiveDescriptor { '*', ], - ...((() => { - if (!config.features.sentry.isEnabled) { - return {}; - } - - return { - 'report-uri': [ - getCspReportUrl(), - ].filter(Boolean), - }; - })()), + 'frame-ancestors': [ + KEY_WORDS.SELF, + + // allow remix.ethereum.org to embed our contract page in iframe + 'remix.ethereum.org', + ], }; } diff --git a/nextjs/csp/policies/gasHawk.ts b/nextjs/csp/policies/gasHawk.ts new file mode 100644 index 0000000000..6a1a8e624a --- /dev/null +++ b/nextjs/csp/policies/gasHawk.ts @@ -0,0 +1,30 @@ +import type CspDev from 'csp-dev'; + +import config from 'configs/app'; + +const feature = config.features.saveOnGas; + +export function gasHawk(): CspDev.DirectiveDescriptor { + if (!feature.isEnabled) { + return {}; + } + + const apiOrigin = (() => { + try { + const url = new URL(feature.apiUrlTemplate); + return url.origin; + } catch (error) { + return ''; + } + })(); + + if (!apiOrigin) { + return {}; + } + + return { + 'connect-src': [ + apiOrigin, + ], + }; +} diff --git a/nextjs/csp/policies/googleReCaptcha.ts b/nextjs/csp/policies/googleReCaptcha.ts index d759e70d59..f7d0187523 100644 --- a/nextjs/csp/policies/googleReCaptcha.ts +++ b/nextjs/csp/policies/googleReCaptcha.ts @@ -3,7 +3,7 @@ import type CspDev from 'csp-dev'; import config from 'configs/app'; export function googleReCaptcha(): CspDev.DirectiveDescriptor { - if (!config.services.reCaptcha.siteKey) { + if (!config.services.reCaptchaV2.siteKey) { return {}; } diff --git a/nextjs/csp/policies/helia.ts b/nextjs/csp/policies/helia.ts new file mode 100644 index 0000000000..c6e925d378 --- /dev/null +++ b/nextjs/csp/policies/helia.ts @@ -0,0 +1,16 @@ +import type CspDev from 'csp-dev'; + +import config from 'configs/app'; + +export function helia(): CspDev.DirectiveDescriptor { + if (!config.UI.views.nft.verifiedFetch.isEnabled) { + return {}; + } + + return { + 'connect-src': [ + 'https://delegated-ipfs.dev', + 'https://trustless-gateway.link', + ], + }; +} diff --git a/nextjs/csp/policies/index.ts b/nextjs/csp/policies/index.ts index 8c34ccf05c..3180748a43 100644 --- a/nextjs/csp/policies/index.ts +++ b/nextjs/csp/policies/index.ts @@ -1,13 +1,17 @@ export { ad } from './ad'; export { app } from './app'; export { cloudFlare } from './cloudFlare'; +export { gasHawk } from './gasHawk'; export { googleAnalytics } from './googleAnalytics'; export { googleFonts } from './googleFonts'; export { googleReCaptcha } from './googleReCaptcha'; export { growthBook } from './growthBook'; +export { helia } from './helia'; +export { marketplace } from './marketplace'; export { mixpanel } from './mixpanel'; export { monaco } from './monaco'; +export { rollbar } from './rollbar'; export { safe } from './safe'; -export { sentry } from './sentry'; +export { usernameApi } from './usernameApi'; export { walletConnect } from './walletConnect'; export { boolNetwork } from './boolNetwork'; diff --git a/nextjs/csp/policies/marketplace.ts b/nextjs/csp/policies/marketplace.ts new file mode 100644 index 0000000000..08474a4bc1 --- /dev/null +++ b/nextjs/csp/policies/marketplace.ts @@ -0,0 +1,22 @@ +import type CspDev from 'csp-dev'; + +import config from 'configs/app'; + +const feature = config.features.marketplace; + +export function marketplace(): CspDev.DirectiveDescriptor { + if (!feature.isEnabled) { + return {}; + } + + return { + 'connect-src': [ + 'api' in feature ? feature.api.endpoint : '', + feature.rating ? 'https://api.airtable.com' : '', + ], + + 'frame-src': [ + '*', + ], + }; +} diff --git a/nextjs/csp/policies/monaco.ts b/nextjs/csp/policies/monaco.ts index edf8ac5f1c..64d6086d9f 100644 --- a/nextjs/csp/policies/monaco.ts +++ b/nextjs/csp/policies/monaco.ts @@ -11,6 +11,13 @@ export function monaco(): CspDev.DirectiveDescriptor { 'https://cdn.jsdelivr.net/npm/monaco-editor@0.33.0/min/vs/editor/editor.main.nls.js', 'https://cdn.jsdelivr.net/npm/monaco-editor@0.33.0/min/vs/basic-languages/solidity/solidity.js', 'https://cdn.jsdelivr.net/npm/monaco-editor@0.33.0/min/vs/basic-languages/elixir/elixir.js', + 'https://cdn.jsdelivr.net/npm/monaco-editor@0.33.0/min/vs/basic-languages/javascript/javascript.js', + 'https://cdn.jsdelivr.net/npm/monaco-editor@0.33.0/min/vs/basic-languages/typescript/typescript.js', + 'https://cdn.jsdelivr.net/npm/monaco-editor@0.33.0/min/vs/basic-languages/rust/rust.js', + 'https://cdn.jsdelivr.net/npm/monaco-editor@0.33.0/min/vs/language/json/jsonMode.js', + 'https://cdn.jsdelivr.net/npm/monaco-editor@0.33.0/min/vs/language/json/jsonWorker.js', + 'https://cdn.jsdelivr.net/npm/monaco-editor@0.33.0/min/vs/language/typescript/tsMode.js', + 'https://cdn.jsdelivr.net/npm/monaco-editor@0.33.0/min/vs/language/typescript/tsWorker.js', 'https://cdn.jsdelivr.net/npm/monaco-editor@0.33.0/min/vs/base/worker/workerMain.js', ], 'style-src': [ diff --git a/nextjs/csp/policies/rollbar.ts b/nextjs/csp/policies/rollbar.ts new file mode 100644 index 0000000000..87470735f7 --- /dev/null +++ b/nextjs/csp/policies/rollbar.ts @@ -0,0 +1,15 @@ +import type CspDev from 'csp-dev'; + +import config from 'configs/app'; + +export function rollbar(): CspDev.DirectiveDescriptor { + if (!config.features.rollbar.isEnabled) { + return {}; + } + + return { + 'connect-src': [ + 'api.rollbar.com', + ], + }; +} diff --git a/nextjs/csp/policies/sentry.ts b/nextjs/csp/policies/sentry.ts deleted file mode 100644 index 298c17a63e..0000000000 --- a/nextjs/csp/policies/sentry.ts +++ /dev/null @@ -1,10 +0,0 @@ -import type CspDev from 'csp-dev'; - -export function sentry(): CspDev.DirectiveDescriptor { - return { - 'connect-src': [ - 'sentry.io', - '*.sentry.io', - ], - }; -} diff --git a/nextjs/csp/policies/usernameApi.ts b/nextjs/csp/policies/usernameApi.ts new file mode 100644 index 0000000000..4b2c2bd912 --- /dev/null +++ b/nextjs/csp/policies/usernameApi.ts @@ -0,0 +1,26 @@ +import type CspDev from 'csp-dev'; + +import config from 'configs/app'; + +const feature = config.features.addressProfileAPI; + +export function usernameApi(): CspDev.DirectiveDescriptor { + if (!feature.isEnabled) { + return {}; + } + + const apiOrigin = (() => { + try { + const url = new URL(feature.apiUrlTemplate); + return url.origin; + } catch (error) { + return ''; + } + })(); + + return { + 'connect-src': [ + apiOrigin, + ], + }; +} diff --git a/nextjs/csp/policies/walletConnect.ts b/nextjs/csp/policies/walletConnect.ts index 41cb948066..728c7eab4a 100644 --- a/nextjs/csp/policies/walletConnect.ts +++ b/nextjs/csp/policies/walletConnect.ts @@ -12,10 +12,17 @@ export function walletConnect(): CspDev.DirectiveDescriptor { return { 'connect-src': [ '*.web3modal.com', + '*.web3modal.org', '*.walletconnect.com', + '*.walletconnect.org', 'wss://relay.walletconnect.com', + 'wss://relay.walletconnect.org', 'wss://www.walletlink.org', ], + 'frame-ancestors': [ + '*.walletconnect.org', + '*.walletconnect.com', + ], 'img-src': [ KEY_WORDS.BLOB, '*.walletconnect.com', diff --git a/nextjs/getServerSideProps.ts b/nextjs/getServerSideProps.ts index 5d0deebef1..3927590800 100644 --- a/nextjs/getServerSideProps.ts +++ b/nextjs/getServerSideProps.ts @@ -1,29 +1,48 @@ -import type { GetServerSideProps } from 'next'; +import type { GetServerSideProps, GetServerSidePropsContext, GetServerSidePropsResult } from 'next'; + +import type { AdBannerProviders } from 'types/client/adProviders'; +import type { RollupType } from 'types/client/rollup'; + +import type { Route } from 'nextjs-routes'; import config from 'configs/app'; +const rollupFeature = config.features.rollup; +const adBannerFeature = config.features.adsBanner; +import isNeedProxy from 'lib/api/isNeedProxy'; +import type * as metadata from 'lib/metadata'; -export type Props = { +export interface Props { + query: Route['query']; cookies: string; referrer: string; - id: string; - height_or_hash: string; - hash: string; - number: string; - q: string; - name: string; + adBannerProvider: AdBannerProviders | null; + // if apiData is undefined, Next.js will complain that it is not serializable + // so we force it to be always present in the props but it can be null + apiData: metadata.ApiData | null; } -export const base: GetServerSideProps = async({ req, query }) => { +export const base = async ({ req, query }: GetServerSidePropsContext): +Promise>> => { + const adBannerProvider = (() => { + if (adBannerFeature.isEnabled) { + if ('additionalProvider' in adBannerFeature && adBannerFeature.additionalProvider) { + // we need to get a random ad provider on the server side to keep it consistent with the client side + const randomIndex = Math.round(Math.random()); + return [ adBannerFeature.provider, adBannerFeature.additionalProvider ][randomIndex]; + } else { + return adBannerFeature.provider; + } + } + return null; + })(); + return { props: { + query, cookies: req.headers.cookie || '', referrer: req.headers.referer || '', - id: query.id?.toString() || '', - hash: query.hash?.toString() || '', - height_or_hash: query.height_or_hash?.toString() || '', - number: query.number?.toString() || '', - q: query.q?.toString() || '', - name: query.name?.toString() || '', + adBannerProvider: adBannerProvider, + apiData: null, }, }; }; @@ -48,8 +67,9 @@ export const verifiedAddresses: GetServerSideProps = async(context) => { return account(context); }; -export const beaconChain: GetServerSideProps = async(context) => { - if (!config.features.beaconChain.isEnabled) { +const DEPOSITS_ROLLUP_TYPES: Array = [ 'optimistic', 'shibarium', 'zkEvm', 'arbitrum', 'scroll' ]; +export const deposits: GetServerSideProps = async(context) => { + if (!(rollupFeature.isEnabled && DEPOSITS_ROLLUP_TYPES.includes(rollupFeature.type))) { return { notFound: true, }; @@ -58,8 +78,12 @@ export const beaconChain: GetServerSideProps = async(context) => { return base(context); }; -export const L2: GetServerSideProps = async(context) => { - if (!config.features.optimisticRollup.isEnabled) { +const WITHDRAWALS_ROLLUP_TYPES: Array = [ 'optimistic', 'shibarium', 'zkEvm', 'arbitrum', 'scroll' ]; +export const withdrawals: GetServerSideProps = async(context) => { + if ( + !config.features.beaconChain.isEnabled && + !(rollupFeature.isEnabled && WITHDRAWALS_ROLLUP_TYPES.includes(rollupFeature.type)) + ) { return { notFound: true, }; @@ -68,8 +92,8 @@ export const L2: GetServerSideProps = async(context) => { return base(context); }; -export const zkEvmL2: GetServerSideProps = async(context) => { - if (!config.features.zkEvmRollup.isEnabled) { +export const rollup: GetServerSideProps = async(context) => { + if (!config.features.rollup.isEnabled) { return { notFound: true, }; @@ -78,8 +102,8 @@ export const zkEvmL2: GetServerSideProps = async(context) => { return base(context); }; -export const marketplace: GetServerSideProps = async(context) => { - if (!config.features.marketplace.isEnabled) { +export const outputRoots: GetServerSideProps = async(context) => { + if (!(rollupFeature.isEnabled && rollupFeature.outputRootsEnabled)) { return { notFound: true, }; @@ -88,6 +112,28 @@ export const marketplace: GetServerSideProps = async(context) => { return base(context); }; +const BATCH_ROLLUP_TYPES: Array = [ 'zkEvm', 'zkSync', 'arbitrum', 'optimistic', 'scroll' ]; +export const batch: GetServerSideProps = async(context) => { + if (!(rollupFeature.isEnabled && BATCH_ROLLUP_TYPES.includes(rollupFeature.type))) { + return { + notFound: true, + }; + } + + return base(context); +}; + +export const marketplace = async (context: GetServerSidePropsContext): +Promise>> => { + if (!config.features.marketplace.isEnabled) { + return { + notFound: true, + }; + } + + return base(context); +}; + export const apiDocs: GetServerSideProps = async(context) => { if (!config.features.restApiDocs.isEnabled) { return { @@ -98,6 +144,16 @@ export const apiDocs: GetServerSideProps = async(context) => { return base(context); }; +export const graphIQl: GetServerSideProps = async(context) => { + if (!config.features.graphqlApiDocs.isEnabled) { + return { + notFound: true, + }; + } + + return base(context); +}; + export const csvExport: GetServerSideProps = async(context) => { if (!config.features.csvExport.isEnabled) { return { @@ -148,6 +204,16 @@ export const accounts: GetServerSideProps = async(context) => { return base(context); }; +export const accountsLabelSearch: GetServerSideProps = async(context) => { + if (!config.features.addressMetadata.isEnabled || !context.query.tagType) { + return { + notFound: true, + }; + } + + return base(context); +}; + export const userOps: GetServerSideProps = async(context) => { if (!config.features.userOps.isEnabled) { return { @@ -157,3 +223,105 @@ export const userOps: GetServerSideProps = async(context) => { return base(context); }; + +export const validators: GetServerSideProps = async(context) => { + if (!config.features.validators.isEnabled) { + return { + notFound: true, + }; + } + + return base(context); +}; + +export const gasTracker: GetServerSideProps = async(context) => { + if (!config.features.gasTracker.isEnabled) { + return { + notFound: true, + }; + } + + return base(context); +}; + +export const advancedFilter: GetServerSideProps = async(context) => { + if (!config.features.advancedFilter.isEnabled) { + return { + notFound: true, + }; + } + + return base(context); +}; + +export const dataAvailability: GetServerSideProps = async(context) => { + if (!config.features.dataAvailability.isEnabled) { + return { + notFound: true, + }; + } + + return base(context); +}; + +export const login: GetServerSideProps = async(context) => { + + if (!isNeedProxy()) { + return { + notFound: true, + }; + } + + return base(context); +}; + +export const dev: GetServerSideProps = async(context) => { + if (!config.app.isDev) { + return { + notFound: true, + }; + } + + return base(context); +}; + +export const publicTagsSubmit: GetServerSideProps = async(context) => { + + if (!config.features.publicTagsSubmission.isEnabled) { + return { + notFound: true, + }; + } + + return base(context); +}; + +export const disputeGames: GetServerSideProps = async(context) => { + if (!config.features.faultProofSystem.isEnabled) { + return { + notFound: true, + }; + } + + return base(context); +}; + +export const mud: GetServerSideProps = async(context) => { + if (!config.features.mudFramework.isEnabled) { + return { + notFound: true, + }; + } + + return base(context); +}; + +export const pools: GetServerSideProps = async(context) => { + if (!config.features.pools.isEnabled) { + return { + notFound: true, + }; + } + + return base(context); +}; diff --git a/nextjs/middlewares/account.ts b/nextjs/middlewares/account.ts index e46c5a07fe..7ab562542f 100644 --- a/nextjs/middlewares/account.ts +++ b/nextjs/middlewares/account.ts @@ -1,10 +1,7 @@ import type { NextRequest } from 'next/server'; import { NextResponse } from 'next/server'; -import { route } from 'nextjs-routes'; - import config from 'configs/app'; -import { DAY } from 'lib/consts'; import * as cookies from 'lib/cookies'; export function account(req: NextRequest) { @@ -25,37 +22,7 @@ export function account(req: NextRequest) { const isProfileRoute = req.nextUrl.pathname.includes('/auth/profile'); if ((isAccountRoute || isProfileRoute)) { - const authUrl = feature.authUrl + route({ pathname: '/auth/auth0', query: { path: req.nextUrl.pathname } }); - return NextResponse.redirect(authUrl); - } - } - - // if user hasn't confirmed email yet - if (req.cookies.get(cookies.NAMES.INVALID_SESSION)) { - // if user has both cookies, make redirect to logout - if (apiTokenCookie) { - // yes, we could have checked that the current URL is not the logout URL, but we hadn't - // logout URL is always external URL in auth0.com sub-domain - // at least we hope so - - const res = NextResponse.redirect(feature.logoutUrl); - res.cookies.delete(cookies.NAMES.CONFIRM_EMAIL_PAGE_VIEWED); // reset cookie to show email verification page again - - return res; - } - - // if user hasn't seen email verification page, make redirect to it - if (!req.cookies.get(cookies.NAMES.CONFIRM_EMAIL_PAGE_VIEWED)) { - if (!req.nextUrl.pathname.includes('/auth/unverified-email')) { - const url = config.app.baseUrl + route({ pathname: '/auth/unverified-email' }); - const res = NextResponse.redirect(url); - res.cookies.set({ - name: cookies.NAMES.CONFIRM_EMAIL_PAGE_VIEWED, - value: 'true', - expires: Date.now() + 7 * DAY, - }); - return res; - } + return NextResponse.redirect(config.app.baseUrl); } } } diff --git a/nextjs/middlewares/addressFormat.ts b/nextjs/middlewares/addressFormat.ts new file mode 100644 index 0000000000..bfd8bf14e0 --- /dev/null +++ b/nextjs/middlewares/addressFormat.ts @@ -0,0 +1,20 @@ +import type { NextRequest, NextResponse } from 'next/server'; + +import type { AddressFormat } from 'types/views/address'; + +import config from 'configs/app'; +import * as cookiesLib from 'lib/cookies'; + +export default function addressFormatMiddleware(req: NextRequest, res: NextResponse) { + const addressFormatCookie = req.cookies.get(cookiesLib.NAMES.ADDRESS_FORMAT); + const defaultFormat = config.UI.views.address.hashFormat.availableFormats[0]; + + if (addressFormatCookie) { + const isValidCookie = config.UI.views.address.hashFormat.availableFormats.includes(addressFormatCookie.value as AddressFormat); + if (!isValidCookie) { + res.cookies.set(cookiesLib.NAMES.ADDRESS_FORMAT, defaultFormat, { path: '/' }); + } + } else { + res.cookies.set(cookiesLib.NAMES.ADDRESS_FORMAT, defaultFormat, { path: '/' }); + } +} diff --git a/nextjs/middlewares/colorTheme.ts b/nextjs/middlewares/colorTheme.ts new file mode 100644 index 0000000000..dcb8314cae --- /dev/null +++ b/nextjs/middlewares/colorTheme.ts @@ -0,0 +1,15 @@ +import type { NextRequest, NextResponse } from 'next/server'; + +import appConfig from 'configs/app'; +import * as cookiesLib from 'lib/cookies'; + +export default function colorThemeMiddleware(req: NextRequest, res: NextResponse) { + const colorModeCookie = req.cookies.get(cookiesLib.NAMES.COLOR_MODE); + + if (!colorModeCookie) { + if (appConfig.UI.colorTheme.default) { + res.cookies.set(cookiesLib.NAMES.COLOR_MODE, appConfig.UI.colorTheme.default.colorMode, { path: '/' }); + res.cookies.set(cookiesLib.NAMES.COLOR_MODE_HEX, appConfig.UI.colorTheme.default.hex, { path: '/' }); + } + } +} diff --git a/nextjs/middlewares/index.ts b/nextjs/middlewares/index.ts index 1fe6fae29c..4e4fdbef4e 100644 --- a/nextjs/middlewares/index.ts +++ b/nextjs/middlewares/index.ts @@ -1 +1,3 @@ export { account } from './account'; +export { default as colorTheme } from './colorTheme'; +export { default as addressFormat } from './addressFormat'; diff --git a/nextjs/nextjs-routes.d.ts b/nextjs/nextjs-routes.d.ts index dd927fc5b5..1061648947 100644 --- a/nextjs/nextjs-routes.d.ts +++ b/nextjs/nextjs-routes.d.ts @@ -9,55 +9,76 @@ declare module "nextjs-routes" { | StaticRoute<"/404"> | StaticRoute<"/account/api-key"> | StaticRoute<"/account/custom-abi"> - | StaticRoute<"/account/public-tags-request"> + | StaticRoute<"/account/rewards"> | StaticRoute<"/account/tag-address"> | StaticRoute<"/account/verified-addresses"> | StaticRoute<"/account/watchlist"> | StaticRoute<"/accounts"> + | DynamicRoute<"/accounts/label/[slug]", { "slug": string }> | DynamicRoute<"/address/[hash]/contract-verification", { "hash": string }> | DynamicRoute<"/address/[hash]", { "hash": string }> + | StaticRoute<"/advanced-filter"> + | StaticRoute<"/api/config"> | StaticRoute<"/api/csrf"> | StaticRoute<"/api/healthz"> + | StaticRoute<"/api/log"> | StaticRoute<"/api/media-type"> + | StaticRoute<"/api/metrics"> + | StaticRoute<"/api/monitoring/invalid-api-schema"> | StaticRoute<"/api/proxy"> + | StaticRoute<"/api/sprite"> | StaticRoute<"/api-docs"> | DynamicRoute<"/apps/[id]", { "id": string }> | StaticRoute<"/apps"> - | StaticRoute<"/auth/auth0"> | StaticRoute<"/auth/profile"> - | StaticRoute<"/auth/unverified-email"> + | DynamicRoute<"/batches/[number]", { "number": string }> + | StaticRoute<"/batches"> + | DynamicRoute<"/blobs/[hash]", { "hash": string }> | DynamicRoute<"/block/[height_or_hash]", { "height_or_hash": string }> + | DynamicRoute<"/block/countdown/[height]", { "height": string }> + | StaticRoute<"/block/countdown"> | StaticRoute<"/blocks"> | StaticRoute<"/contract-verification"> | StaticRoute<"/csv-export"> +<<<<<<< HEAD | DynamicRoute<"/dhcs/[id]", { "id": string }> | StaticRoute<"/dhcs"> +======= + | StaticRoute<"/deposits"> + | StaticRoute<"/dispute-games"> + | StaticRoute<"/gas-tracker"> +>>>>>>> 3a400a12e9079490a50fd4e8a885f663e0a26739 | StaticRoute<"/graphiql"> | StaticRoute<"/"> - | StaticRoute<"/l2-deposits"> - | StaticRoute<"/l2-output-roots"> - | StaticRoute<"/l2-txn-batches"> - | StaticRoute<"/l2-withdrawals"> | StaticRoute<"/login"> + | StaticRoute<"/mud-worlds"> | DynamicRoute<"/name-domains/[name]", { "name": string }> | StaticRoute<"/name-domains"> | DynamicRoute<"/op/[hash]", { "hash": string }> | StaticRoute<"/ops"> + | StaticRoute<"/output-roots"> + | DynamicRoute<"/pools/[hash]", { "hash": string }> + | StaticRoute<"/pools"> + | StaticRoute<"/public-tags/submit"> | StaticRoute<"/search-results"> + | StaticRoute<"/sprite"> + | DynamicRoute<"/stats/[id]", { "id": string }> | StaticRoute<"/stats"> | DynamicRoute<"/token/[hash]", { "hash": string }> | DynamicRoute<"/token/[hash]/instance/[id]", { "hash": string; "id": string }> + | StaticRoute<"/token-transfers"> | StaticRoute<"/tokens"> | DynamicRoute<"/tx/[hash]", { "hash": string }> | StaticRoute<"/txs"> | DynamicRoute<"/txs/kettle/[hash]", { "hash": string }> +<<<<<<< HEAD | DynamicRoute<"/validators/[hash]", { "hash": string }> +======= +>>>>>>> 3a400a12e9079490a50fd4e8a885f663e0a26739 | StaticRoute<"/validators"> | StaticRoute<"/verified-contracts"> | StaticRoute<"/visualize/sol2uml"> - | StaticRoute<"/withdrawals"> - | DynamicRoute<"/zkevm-l2-txn-batch/[number]", { "number": string }> - | StaticRoute<"/zkevm-l2-txn-batches">; + | StaticRoute<"/withdrawals">; interface StaticRoute { pathname: Pathname; diff --git a/nextjs/redirects.js b/nextjs/redirects.js index 3c2ed3ba9a..a5908dc12f 100644 --- a/nextjs/redirects.js +++ b/nextjs/redirects.js @@ -49,16 +49,8 @@ const oldUrls = [ destination: '/account/custom-abi', }, { - source: '/account/public_tags_request', - destination: '/account/public-tags-request', - }, - { - source: '/account/public_tags_request/:id/edit', - destination: '/account/public-tags-request', - }, - { - source: '/account/public_tags_request/new', - destination: '/account/public-tags-request', + source: '/account/public-tags-request', + destination: '/public-tags/submit', }, // TRANSACTIONS @@ -242,6 +234,32 @@ const oldUrls = [ source: '/token/:hash/write-proxy', destination: '/token/:hash?tab=write_proxy', }, + + // ROLLUPs + { + source: '/l2-txn-batches', + destination: '/batches', + }, + { + source: '/zkevm-l2-txn-batches', + destination: '/batches', + }, + { + source: '/zkevm-l2-txn-batch/:path*', + destination: '/batches/:path*', + }, + { + source: '/l2-deposits', + destination: '/deposits', + }, + { + source: '/l2-withdrawals', + destination: '/withdrawals', + }, + { + source: '/l2-output-roots', + destination: '/output-roots', + }, ]; async function redirects() { diff --git a/nextjs/types.ts b/nextjs/types.ts index 60c007daff..a6f2a38a4e 100644 --- a/nextjs/types.ts +++ b/nextjs/types.ts @@ -1,6 +1,13 @@ import type { NextPage } from 'next'; +import type React from 'react'; + +import type { Route } from 'nextjs-routes'; -// eslint-disable-next-line @typescript-eslint/ban-types export type NextPageWithLayout

= NextPage & { getLayout?: (page: React.ReactElement) => React.ReactNode; +}; + +export interface RouteParams { + pathname: Pathname; + query?: Route['query']; } diff --git a/nextjs/utils/detectBotRequest.ts b/nextjs/utils/detectBotRequest.ts new file mode 100644 index 0000000000..d87a3972c0 --- /dev/null +++ b/nextjs/utils/detectBotRequest.ts @@ -0,0 +1,52 @@ +import type { IncomingMessage } from 'http'; + +type SocialPreviewBot = 'twitter' | 'facebook' | 'telegram' | 'slack'; +type SearchEngineBot = 'google' | 'bing' | 'yahoo' | 'duckduckgo'; + +type ReturnType = { + type: 'social_preview'; + bot: SocialPreviewBot; +} | { + type: 'search_engine'; + bot: SearchEngineBot; +} | undefined; + +export default function detectBotRequest(req: IncomingMessage): ReturnType { + const userAgent = req.headers['user-agent']; + + if (!userAgent) { + return; + } + + if (userAgent.toLowerCase().includes('twitter')) { + return { type: 'social_preview', bot: 'twitter' }; + } + + if (userAgent.toLowerCase().includes('facebook')) { + return { type: 'social_preview', bot: 'facebook' }; + } + + if (userAgent.toLowerCase().includes('telegram')) { + return { type: 'social_preview', bot: 'telegram' }; + } + + if (userAgent.toLowerCase().includes('slack')) { + return { type: 'social_preview', bot: 'slack' }; + } + + if (userAgent.toLowerCase().includes('googlebot')) { + return { type: 'search_engine', bot: 'google' }; + } + + if (userAgent.toLowerCase().includes('bingbot')) { + return { type: 'search_engine', bot: 'bing' }; + } + + if (userAgent.toLowerCase().includes('yahoo')) { + return { type: 'search_engine', bot: 'yahoo' }; + } + + if (userAgent.toLowerCase().includes('duckduck')) { + return { type: 'search_engine', bot: 'duckduckgo' }; + } +} diff --git a/nextjs/utils/fetch.ts b/nextjs/utils/fetch.ts deleted file mode 100644 index 22bfc32ff6..0000000000 --- a/nextjs/utils/fetch.ts +++ /dev/null @@ -1,62 +0,0 @@ -import type { IncomingMessage } from 'http'; -import _pick from 'lodash/pick'; -import type { NextApiRequest } from 'next'; -import type { NextApiRequestCookies } from 'next/dist/server/api-utils'; -import type { RequestInit, Response } from 'node-fetch'; -import nodeFetch from 'node-fetch'; - -import { httpLogger } from 'nextjs/utils/logger'; - -import * as cookies from 'lib/cookies'; - -export default function fetchFactory( - _req: NextApiRequest | (IncomingMessage & { cookies: NextApiRequestCookies }), -) { - // first arg can be only a string - // FIXME migrate to RequestInfo later if needed - return function fetch(url: string, init?: RequestInit): Promise { - const apiToken = _req.cookies[cookies.NAMES.API_TOKEN]; - - const headers = { - accept: _req.headers['accept'] || 'application/json', - 'content-type': _req.headers['content-type'] || 'application/json', - cookie: apiToken ? `${ cookies.NAMES.API_TOKEN }=${ apiToken }` : '', - ..._pick(_req.headers, [ - 'x-csrf-token', - 'Authorization', - // feature flags - 'updated-gas-oracle', - ]) as Record, - }; - - httpLogger.logger.info({ - message: 'Trying to call API', - url, - req: _req, - }); - - httpLogger.logger.info({ - message: 'API request headers', - headers, - }); - - const body = (() => { - const _body = init?.body; - if (!_body) { - return; - } - - if (typeof _body === 'string') { - return _body; - } - - return JSON.stringify(_body); - })(); - - return nodeFetch(url, { - ...init, - headers, - body, - }); - }; -} diff --git a/nextjs/utils/fetchApi.ts b/nextjs/utils/fetchApi.ts new file mode 100644 index 0000000000..eed5c613b4 --- /dev/null +++ b/nextjs/utils/fetchApi.ts @@ -0,0 +1,53 @@ +import fetch, { AbortError } from 'node-fetch'; + +import buildUrl from 'nextjs/utils/buildUrl'; +import { httpLogger } from 'nextjs/utils/logger'; + +import { RESOURCES } from 'lib/api/resources'; +import type { ResourceName, ResourcePathParams, ResourcePayload } from 'lib/api/resources'; +import { SECOND } from 'lib/consts'; +import metrics from 'lib/monitoring/metrics'; + +type Params = ( + { + resource: R; + pathParams?: ResourcePathParams; + queryParams?: Record; + } | { + url: string; + route: string; + } +) & { + timeout?: number; +}; + +export default async function fetchApi>(params: Params): Promise { + const controller = new AbortController(); + const timeout = setTimeout(() => { + controller.abort(); + }, params.timeout || SECOND); + + const url = 'url' in params ? params.url : buildUrl(params.resource, params.pathParams, params.queryParams); + const route = 'route' in params ? params.route : RESOURCES[params.resource]['path']; + + const end = metrics?.apiRequestDuration.startTimer(); + + try { + const response = await fetch(url, { signal: controller.signal }); + + const duration = end?.({ route, code: response.status }); + if (response.status === 200) { + httpLogger.logger.info({ message: 'API fetch', url, code: response.status, duration }); + } else { + httpLogger.logger.error({ message: 'API fetch', url, code: response.status, duration }); + } + + return await response.json() as Promise; + } catch (error) { + const code = error instanceof AbortError ? 504 : 500; + const duration = end?.({ route, code }); + httpLogger.logger.error({ message: 'API fetch', url, code, duration }); + } finally { + clearTimeout(timeout); + } +} diff --git a/nextjs/utils/fetchProxy.ts b/nextjs/utils/fetchProxy.ts new file mode 100644 index 0000000000..a8f0c032f2 --- /dev/null +++ b/nextjs/utils/fetchProxy.ts @@ -0,0 +1,59 @@ +import type { IncomingMessage } from 'http'; +import _pick from 'lodash/pick'; +import type { NextApiRequest } from 'next'; +import type { NextApiRequestCookies } from 'next/dist/server/api-utils'; +import type { RequestInit, Response } from 'node-fetch'; +import nodeFetch from 'node-fetch'; + +import { httpLogger } from 'nextjs/utils/logger'; + +import * as cookies from 'lib/cookies'; + +export default function fetchFactory( + _req: NextApiRequest | (IncomingMessage & { cookies: NextApiRequestCookies }), +) { + // first arg can be only a string + // FIXME migrate to RequestInfo later if needed + return function fetch(url: string, init?: RequestInit): Promise { + const apiToken = _req.cookies[cookies.NAMES.API_TOKEN]; + + const headers = { + accept: _req.headers['accept'] || 'application/json', + 'content-type': _req.headers['content-type'] || 'application/json', + cookie: apiToken ? `${ cookies.NAMES.API_TOKEN }=${ apiToken }` : '', + ..._pick(_req.headers, [ + 'x-csrf-token', + 'Authorization', // the old value, just in case + 'authorization', // Node.js automatically lowercases headers + // feature flags + 'updated-gas-oracle', + ]) as Record, + }; + + httpLogger.logger.info({ + message: 'API fetch via Next.js proxy', + url, + // headers, + // init, + }); + + const body = (() => { + const _body = init?.body; + if (!_body) { + return; + } + + if (typeof _body === 'string') { + return _body; + } + + return JSON.stringify(_body); + })(); + + return nodeFetch(url, { + ...init, + headers, + body, + }); + }; +} diff --git a/nextjs/utils/logRequestFromBot.ts b/nextjs/utils/logRequestFromBot.ts new file mode 100644 index 0000000000..6c22b63258 --- /dev/null +++ b/nextjs/utils/logRequestFromBot.ts @@ -0,0 +1,28 @@ +import type { IncomingMessage, ServerResponse } from 'http'; + +import metrics from 'lib/monitoring/metrics'; + +import detectBotRequest from './detectBotRequest'; + +export default async function logRequestFromBot(req: IncomingMessage | undefined, res: ServerResponse | undefined, pathname: string) { + if (!req || !res || !metrics) { + return; + } + + const botInfo = detectBotRequest(req); + + if (!botInfo) { + return; + } + + switch (botInfo.type) { + case 'search_engine': { + metrics.searchEngineBotRequests.inc({ route: pathname, bot: botInfo.bot }); + return; + } + case 'social_preview': { + metrics.socialPreviewBotRequests.inc({ route: pathname, bot: botInfo.bot }); + return; + } + } +} diff --git a/package.json b/package.json index 13cfafc405..62aef0248f 100644 --- a/package.json +++ b/package.json @@ -4,19 +4,21 @@ "private": false, "homepage": "https://github.com/blockscout/frontend#readme", "engines": { - "node": "20.11.0", - "npm": "10.2.4" + "node": "22.11.0", + "npm": "10.9.0" }, "scripts": { "dev": "./tools/scripts/dev.sh", "dev:preset": "./tools/scripts/dev.preset.sh", + "dev:preset:sync": "tsc -p ./tools/preset-sync/tsconfig.json && node ./tools/preset-sync/index.js", "build": "next build", + "build:next": "./deploy/scripts/download_assets.sh ./public/assets/configs && yarn svg:build-sprite && ./deploy/scripts/make_envs_script.sh && next build", "build:docker": "docker build --build-arg GIT_COMMIT_SHA=$(git rev-parse --short HEAD) --build-arg GIT_TAG=$(git describe --tags --abbrev=0) -t blockscout-frontend:local ./", "start": "next start", "start:docker:local": "docker run -p 3000:3000 --env-file .env.local blockscout-frontend:local", "start:docker:preset": "./tools/scripts/docker.preset.sh", - "lint:eslint": "eslint . --ext .js,.jsx,.ts,.tsx", - "lint:eslint:fix": "eslint . --ext .js,.jsx,.ts,.tsx --fix", + "lint:eslint": "eslint .", + "lint:eslint:fix": "eslint . --fix", "lint:tsc": "tsc -p ./tsconfig.json", "lint:envs-validator:test": "cd ./deploy/tools/envs-validator && ./test.sh", "prepare": "husky install", @@ -24,88 +26,107 @@ "svg:build-sprite": "icons build -i ./icons -o ./public/icons --optimize", "test:pw": "./tools/scripts/pw.sh", "test:pw:local": "export NODE_PATH=$(pwd)/node_modules && yarn test:pw", - "test:pw:docker": "docker run --rm --network host -v $(pwd):/work/ -w /work/ -it mcr.microsoft.com/playwright:v1.41.1-focal ./tools/scripts/pw.docker.sh", + "test:pw:docker": "docker run --rm --ipc=host -v $(pwd):/work/ -w /work/ -it mcr.microsoft.com/playwright:v1.49.0-noble ./tools/scripts/pw.docker.sh", + "test:pw:docker:deps": "docker run --rm --ipc=host -v $(pwd):/work/ -w /work/ -it mcr.microsoft.com/playwright:v1.49.0-noble ./tools/scripts/pw.docker.deps.sh", "test:pw:ci": "yarn test:pw --project=$PW_PROJECT", + "test:pw:detect-affected": "node ./deploy/tools/affected-tests/index.js", "test:jest": "jest", "test:jest:watch": "jest --watch", - "favicon:generate:dev": "./tools/scripts/favicon-generator.dev.sh" + "favicon:generate:dev": "./tools/scripts/favicon-generator.dev.sh", + "monitoring:prometheus:local": "docker run --name blockscout_prometheus -d -p 127.0.0.1:9090:9090 -v $(pwd)/prometheus.yml:/etc/prometheus/prometheus.yml prom/prometheus", + "monitoring:grafana:local": "docker run -d -p 4000:3000 --name=blockscout_grafana --user $(id -u) --volume $(pwd)/grafana:/var/lib/grafana grafana/grafana-enterprise" }, "dependencies": { + "@blockscout/bens-types": "1.4.1", + "@blockscout/stats-types": "2.0.0", + "@blockscout/visualizer-types": "0.2.0", "@chakra-ui/react": "2.7.1", "@chakra-ui/theme-tools": "^2.0.18", + "@cloudnouns/kit": "1.1.6", "@emotion/react": "^11.10.4", "@emotion/styled": "^11.10.4", "@growthbook/growthbook-react": "0.21.0", + "@helia/verified-fetch": "2.0.1", + "@hypelab/sdk-react": "^1.0.0", "@metamask/post-message-stream": "^7.0.0", "@metamask/providers": "^10.2.1", "@monaco-editor/react": "^4.4.6", - "@next/bundle-analyzer": "^14.0.1", - "@opentelemetry/auto-instrumentations-node": "^0.39.4", - "@opentelemetry/exporter-metrics-otlp-proto": "^0.45.1", - "@opentelemetry/exporter-trace-otlp-http": "^0.45.0", - "@opentelemetry/resources": "^1.18.0", - "@opentelemetry/sdk-node": "^0.45.0", - "@opentelemetry/sdk-trace-node": "^1.18.0", - "@opentelemetry/semantic-conventions": "^1.18.0", - "@sentry/cli": "^2.21.2", - "@sentry/react": "7.24.0", - "@sentry/tracing": "7.24.0", + "@next/bundle-analyzer": "15.0.3", + "@opentelemetry/auto-instrumentations-node": "0.43.0", + "@opentelemetry/exporter-jaeger": "1.27.0", + "@opentelemetry/exporter-metrics-otlp-proto": "0.49.1", + "@opentelemetry/exporter-trace-otlp-http": "0.49.1", + "@opentelemetry/resources": "1.22.0", + "@opentelemetry/sdk-node": "0.49.1", + "@opentelemetry/sdk-trace-node": "1.22.0", + "@opentelemetry/semantic-conventions": "1.22.0", + "@rollbar/react": "0.12.0-beta", + "@scure/base": "1.1.9", + "@reown/appkit": "1.6.0", + "@reown/appkit-adapter-wagmi": "1.6.0", "@slise/embed-react": "^2.2.0", - "@tanstack/react-query": "^5.4.3", - "@tanstack/react-query-devtools": "^5.4.3", + "@tanstack/react-query": "5.55.4", + "@tanstack/react-query-devtools": "5.55.4", "@types/papaparse": "^5.3.5", "@types/react-scroll": "^1.8.4", - "@web3modal/wagmi": "3.5.0", + "airtable": "^0.12.2", "bignumber.js": "^9.1.0", "blo": "^1.1.1", - "chakra-dayzed-datepicker": "^0.2.10", "chakra-react-select": "^4.4.3", "crypto-js": "^4.2.0", "d3": "^7.6.1", - "dappscout-iframe": "^0.1.0", - "date-fns": "^3.6.0", + "dappscout-iframe": "0.2.5", "dayjs": "^1.11.5", - "dayzed": "^3.2.3", "dom-to-image": "^2.6.0", + "focus-visible": "^5.2.0", "framer-motion": "^6.5.1", - "gradient-avatar": "^1.0.2", + "getit-sdk": "^1.0.4", + "gradient-avatar": "git+https://github.com/blockscout/gradient-avatar.git", "graphiql": "^2.2.0", "graphql": "^16.8.1", "graphql-ws": "^5.11.3", "js-cookie": "^3.0.1", "lodash": "^4.0.0", + "magic-bytes.js": "1.8.0", "mixpanel-browser": "^2.47.0", "monaco-editor": "^0.34.1", - "next": "13.5.4", + "next": "15.0.3", "nextjs-routes": "^1.0.8", "node-fetch": "^3.2.9", "papaparse": "^5.3.2", - "path-to-regexp": "^6.2.1", + "path-to-regexp": "8.1.0", "phoenix": "^1.6.15", "pino-http": "^8.2.1", "pino-pretty": "^9.1.1", + "prom-client": "15.1.1", "qrcode": "^1.5.1", - "react": "18.2.0", + "react": "18.3.1", "react-device-detect": "^2.2.3", - "react-dom": "18.2.0", - "react-google-recaptcha": "^2.1.0", - "react-hook-form": "^7.33.1", + "react-dom": "18.3.1", + "react-google-recaptcha": "3.1.0", + "react-hook-form": "7.52.1", "react-identicons": "^1.2.5", "react-intersection-observer": "^9.5.2", "react-jazzicon": "^1.0.4", "react-number-format": "^5.3.1", "react-scroll": "^1.8.7", + "rollbar": "2.26.4", "swagger-ui-react": "^5.9.0", "use-font-face-observer": "^1.2.1", - "viem": "1.20.1", - "wagmi": "1.4.12", + "valibot": "0.38.0", + "viem": "2.21.54", + "wagmi": "2.14.0", "xss": "^1.0.14" }, "devDependencies": { - "@playwright/experimental-ct-react": "1.41.1", - "@playwright/test": "1.41.1", + "@eslint/compat": "1.2.2", + "@eslint/js": "9.14.0", + "@next/eslint-plugin-next": "15.0.3", + "@playwright/experimental-ct-react": "1.49.0", + "@playwright/test": "1.49.0", + "@stylistic/eslint-plugin": "2.10.1", "@svgr/webpack": "^6.5.1", - "@tanstack/eslint-plugin-query": "^5.0.5", + "@tanstack/eslint-plugin-query": "5.60.1", "@testing-library/react": "^14.0.0", "@total-typescript/ts-reset": "^0.4.0", "@types/crypto-js": "^4.1.1", @@ -115,11 +136,11 @@ "@types/jest": "^29.2.0", "@types/js-cookie": "^3.0.2", "@types/mixpanel-browser": "^2.38.1", - "@types/node": "20.11.0", + "@types/node": "20.16.7", "@types/phoenix": "^1.5.4", "@types/qrcode": "^1.5.0", - "@types/react": "18.0.9", - "@types/react-dom": "18.0.5", + "@types/react": "18.3.12", + "@types/react-dom": "18.3.1", "@types/react-google-recaptcha": "^2.1.5", "@types/swagger-ui-react": "^4.11.0", "@types/ws": "^8.5.3", @@ -127,14 +148,18 @@ "@vitejs/plugin-react": "^4.0.0", "css-loader": "^6.7.3", "dotenv-cli": "^6.0.0", - "eslint": "^8.32.0", - "eslint-config-next": "13.3.0", - "eslint-plugin-es5": "^1.5.0", - "eslint-plugin-import-helpers": "^1.2.1", - "eslint-plugin-jest": "^27.1.6", + "eslint": "9.14.0", + "eslint-config-next": "15.0.3", + "eslint-plugin-import": "2.31.0", + "eslint-plugin-import-helpers": "2.0.1", + "eslint-plugin-jest": "28.9.0", + "eslint-plugin-jsx-a11y": "6.10.2", "eslint-plugin-no-cyrillic-string": "^1.0.5", - "eslint-plugin-playwright": "^0.11.2", - "eslint-plugin-regexp": "^1.7.0", + "eslint-plugin-playwright": "2.0.1", + "eslint-plugin-react": "7.37.2", + "eslint-plugin-react-hooks": "5.0.0", + "eslint-plugin-regexp": "2.6.0", + "globals": "15.12.0", "husky": "^8.0.0", "jest": "^29.2.1", "jest-environment-jsdom": "^29.2.1", @@ -146,12 +171,17 @@ "svgo": "^2.8.0", "ts-jest": "^29.0.3", "ts-node": "^10.9.1", - "typescript": "^5.1.0", + "typescript": "5.4.2", + "typescript-eslint": "8.14.0", "vite-plugin-svgr": "^2.2.2", - "vite-tsconfig-paths": "^3.5.2", - "ws": "^8.11.0" + "vite-tsconfig-paths": "4.3.2", + "ws": "^8.17.1" }, "lint-staged": { "*.{js,jsx,ts,tsx}": "eslint --cache --fix" + }, + "resolutions": { + "@types/react": "18.3.12", + "@types/react-dom": "18.3.1" } } diff --git a/pages/404.tsx b/pages/404.tsx index 0e747ae875..bd4e020c0d 100644 --- a/pages/404.tsx +++ b/pages/404.tsx @@ -1,19 +1,21 @@ -import * as Sentry from '@sentry/react'; import React from 'react'; import type { NextPageWithLayout } from 'nextjs/types'; import PageNextJs from 'nextjs/PageNextJs'; +import { useRollbar } from 'lib/rollbar'; import AppError from 'ui/shared/AppError/AppError'; import LayoutError from 'ui/shared/layout/LayoutError'; const error = new Error('Not found', { cause: { status: 404 } }); const Page: NextPageWithLayout = () => { + const rollbar = useRollbar(); + React.useEffect(() => { - Sentry.captureException(new Error('Page not found'), { tags: { source: '404' } }); - }, []); + rollbar?.error('Page not found'); + }, [ rollbar ]); return ( diff --git a/pages/_app.tsx b/pages/_app.tsx index 2f8ff8374b..bc583e44e8 100644 --- a/pages/_app.tsx +++ b/pages/_app.tsx @@ -1,6 +1,5 @@ -import type { ChakraProps } from '@chakra-ui/react'; +import { type ChakraProps } from '@chakra-ui/react'; import { GrowthBookProvider } from '@growthbook/growthbook-react'; -import * as Sentry from '@sentry/react'; import { QueryClientProvider } from '@tanstack/react-query'; import { ReactQueryDevtools } from '@tanstack/react-query-devtools'; import type { AppProps } from 'next/app'; @@ -12,21 +11,28 @@ import config from 'configs/app'; import useQueryClientConfig from 'lib/api/useQueryClientConfig'; import { AppContextProvider } from 'lib/contexts/app'; import { ChakraProvider } from 'lib/contexts/chakra'; +import { MarketplaceContextProvider } from 'lib/contexts/marketplace'; +import { RewardsContextProvider } from 'lib/contexts/rewards'; import { ScrollDirectionProvider } from 'lib/contexts/scrollDirection'; +import { SettingsContextProvider } from 'lib/contexts/settings'; import { growthBook } from 'lib/growthbook/init'; import useLoadFeatures from 'lib/growthbook/useLoadFeatures'; +import useNotifyOnNavigation from 'lib/hooks/useNotifyOnNavigation'; +import { clientConfig as rollbarConfig, Provider as RollbarProvider } from 'lib/rollbar'; import { SocketProvider } from 'lib/socket/context'; -import theme from 'theme'; +import RewardsLoginModal from 'ui/rewards/login/RewardsLoginModal'; import AppErrorBoundary from 'ui/shared/AppError/AppErrorBoundary'; +import AppErrorGlobalContainer from 'ui/shared/AppError/AppErrorGlobalContainer'; import GoogleAnalytics from 'ui/shared/GoogleAnalytics'; import Layout from 'ui/shared/layout/Layout'; import Web3ModalProvider from 'ui/shared/Web3ModalProvider'; import 'lib/setLocale'; +// import 'focus-visible/dist/focus-visible'; type AppPropsWithLayout = AppProps & { Component: NextPageWithLayout; -} +}; const ERROR_SCREEN_STYLES: ChakraProps = { h: '100vh', @@ -36,44 +42,50 @@ const ERROR_SCREEN_STYLES: ChakraProps = { justifyContent: 'center', width: 'fit-content', maxW: '800px', - margin: '0 auto', + margin: { base: '0 auto', lg: '0 auto' }, p: { base: 4, lg: 0 }, }; function MyApp({ Component, pageProps }: AppPropsWithLayout) { useLoadFeatures(); + useNotifyOnNavigation(); const queryClient = useQueryClientConfig(); - const handleError = React.useCallback((error: Error) => { - Sentry.captureException(error); - }, []); - const getLayout = Component.getLayout ?? ((page) => { page }); return ( - - - - - - - - - { getLayout() } - - - - - - - - - + + + + + + + + + + + + + { getLayout() } + { config.features.rewards.isEnabled && } + + + + + + + + + + + + + ); } diff --git a/pages/_document.tsx b/pages/_document.tsx index 74ebe08fb5..1706e247ac 100644 --- a/pages/_document.tsx +++ b/pages/_document.tsx @@ -3,9 +3,11 @@ import type { DocumentContext } from 'next/document'; import Document, { Html, Head, Main, NextScript } from 'next/document'; import React from 'react'; +import logRequestFromBot from 'nextjs/utils/logRequestFromBot'; import * as serverTiming from 'nextjs/utils/serverTiming'; -import theme from 'theme'; +import config from 'configs/app'; +import theme from 'theme/theme'; import * as svgSprite from 'ui/shared/IconSvg'; class MyDocument extends Document { @@ -21,6 +23,8 @@ class MyDocument extends Document { return result; }; + await logRequestFromBot(ctx.req, ctx.res, ctx.pathname); + const initialProps = await Document.getInitialProps(ctx); return initialProps; @@ -32,24 +36,24 @@ class MyDocument extends Document { { /* FONTS */ } { /* eslint-disable-next-line @next/next/no-sync-scripts */ } - - -

+ + { !isHidden && ( + <> + + +
+ + ) }
); }; diff --git a/ui/shared/ad/CoinzillaBanner.tsx b/ui/shared/ad/CoinzillaBanner.tsx index bfdb38fb88..373c777d53 100644 --- a/ui/shared/ad/CoinzillaBanner.tsx +++ b/ui/shared/ad/CoinzillaBanner.tsx @@ -2,27 +2,60 @@ import { Flex, chakra } from '@chakra-ui/react'; import Script from 'next/script'; import React from 'react'; +import type { BannerProps } from './types'; + +import useIsMobile from 'lib/hooks/useIsMobile'; import isBrowser from 'lib/isBrowser'; -const CoinzillaBanner = ({ className }: { className?: string }) => { +const CoinzillaBanner = ({ className, platform }: BannerProps) => { const isInBrowser = isBrowser(); + const isMobileViewport = useIsMobile(); + + // On the home page there are two ad banners + // - one in the stats section with prop "platform === mobile", should be hidden on mobile devices + // - another - a regular ad banner, should be hidden on desktop devices + // The Coinzilla provider doesn't work properly with 2 banners with the same id on the page + // So we use this flag to skip ad initialization for the first home page banner on mobile devices + // For all other pages this is not the case + const isHidden = (isMobileViewport && platform === 'mobile'); + + const { width, height } = (() => { + switch (platform) { + case 'desktop': + return { width: 728, height: 90 }; + case 'mobile': + return { width: 320, height: 100 }; + default: + return { width: undefined, height: undefined }; + } + + })(); React.useEffect(() => { - if (isInBrowser) { + if (isInBrowser && !isHidden) { window.coinzilla_display = window.coinzilla_display || []; const cDisplayPreferences = { zone: '26660bf627543e46851', - width: '728', - height: '90', + width: width ? String(width) : '728', + height: height ? String(height) : '90', }; window.coinzilla_display.push(cDisplayPreferences); } - }, [ isInBrowser ]); + }, [ height, isInBrowser, isHidden, width ]); return ( - - + { banner } + + ); +}; + +export default chakra(HypeBanner); diff --git a/ui/shared/ad/SliseBanner.tsx b/ui/shared/ad/SliseBanner.tsx index 56ae2a7f3a..fda81b6209 100644 --- a/ui/shared/ad/SliseBanner.tsx +++ b/ui/shared/ad/SliseBanner.tsx @@ -2,9 +2,35 @@ import { Flex, chakra } from '@chakra-ui/react'; import { SliseAd } from '@slise/embed-react'; import React from 'react'; +import type { BannerProps } from './types'; + import config from 'configs/app'; -const SliseBanner = ({ className }: { className?: string }) => { +const SliseBanner = ({ className, platform }: BannerProps) => { + + if (platform === 'desktop') { + return ( + + + + ); + } + + if (platform === 'mobile') { + return ( + + + + ); + } return ( <> diff --git a/ui/shared/ad/TextAd.tsx b/ui/shared/ad/TextAd.tsx index 58fca43814..faa4ae90bb 100644 --- a/ui/shared/ad/TextAd.tsx +++ b/ui/shared/ad/TextAd.tsx @@ -7,10 +7,10 @@ import * as cookies from 'lib/cookies'; import CoinzillaTextAd from './CoinzillaTextAd'; -const TextAd = ({ className }: {className?: string}) => { +const TextAd = ({ className }: { className?: string }) => { const hasAdblockCookie = cookies.get(cookies.NAMES.ADBLOCK_DETECTED, useAppContext().cookies); - if (!config.features.adsText.isEnabled || hasAdblockCookie) { + if (!config.features.adsText.isEnabled || hasAdblockCookie === 'true') { return null; } diff --git a/ui/shared/ad/adbutlerScript.ts b/ui/shared/ad/adbutlerScript.ts index 6228a231bf..c2734fb9b0 100644 --- a/ui/shared/ad/adbutlerScript.ts +++ b/ui/shared/ad/adbutlerScript.ts @@ -1,17 +1,29 @@ /* eslint-disable max-len */ +import type { BannerPlatform } from './types'; + import config from 'configs/app'; export const ADBUTLER_ACCOUNT = 182226; export const connectAdbutler = `if (!window.AdButler){(function(){var s = document.createElement("script"); s.async = true; s.type = "text/javascript";s.src = 'https://servedbyadbutler.com/app.js';var n = document.getElementsByTagName("script")[0]; n.parentNode.insertBefore(s, n);}());}`; -export const placeAd = (() => { +export const placeAd = ((platform: BannerPlatform | undefined) => { const feature = config.features.adsBanner; - if (!feature.isEnabled || feature.provider !== 'adbutler') { + if (!('adButler' in feature)) { return; } + if (platform === 'mobile') { + return ` + var AdButler = AdButler || {}; AdButler.ads = AdButler.ads || []; + var abkw = window.abkw || ''; + var plc${ feature.adButler.config.mobile.id } = window.plc${ feature.adButler.config.mobile.id } || 0; + document.getElementById('ad-banner').innerHTML = '<'+'div id="placement_${ feature.adButler.config.mobile.id }_'+plc${ feature.adButler.config.mobile.id }+'">'; + AdButler.ads.push({handler: function(opt){ AdButler.register(${ ADBUTLER_ACCOUNT }, ${ feature.adButler.config.mobile.id }, [${ feature.adButler.config.mobile.width },${ feature.adButler.config.mobile.height }], 'placement_${ feature.adButler.config.mobile.id }_'+opt.place, opt); }, opt: { place: plc${ feature.adButler.config.mobile.id }++, keywords: abkw, domain: 'servedbyadbutler.com', click:'CLICK_MACRO_PLACEHOLDER' }}); + `; + } + return ` var AdButler = AdButler || {}; AdButler.ads = AdButler.ads || []; var abkw = window.abkw || ''; @@ -26,4 +38,4 @@ export const placeAd = (() => { AdButler.ads.push({handler: function(opt){ AdButler.register(${ ADBUTLER_ACCOUNT }, ${ feature.adButler.config.desktop.id }, [${ feature.adButler.config.desktop.width },${ feature.adButler.config.desktop.height }], 'placement_${ feature.adButler.config.desktop.id }_'+opt.place, opt); }, opt: { place: plc${ feature.adButler.config.desktop.id }++, keywords: abkw, domain: 'servedbyadbutler.com', click:'CLICK_MACRO_PLACEHOLDER' }}); } `; -})(); +}); diff --git a/ui/shared/ad/hypeBannerScript.ts b/ui/shared/ad/hypeBannerScript.ts new file mode 100644 index 0000000000..bfe0cb10d8 --- /dev/null +++ b/ui/shared/ad/hypeBannerScript.ts @@ -0,0 +1,21 @@ +import config from 'configs/app'; + +const PRODUCTION_PROPERTY_SLUG = '127fddd522'; +const HYPE_API_URL = 'https://api.hypelab.com'; + +export const hypeInit = (() => { + const feature = config.features.adsBanner; + + if (!feature.isEnabled || feature.provider !== 'hype') { + return; + } + + return `!(function (h, y, p, e, l, a, b) { + ((l = document.createElement(h)).async = !0), + (l.src = y), + (l.onload = function () { + (a = { URL: p, propertySlug: e, environment: 'production' }), HypeLab.initialize(a); + }), + (b = document.getElementsByTagName(h)[0]).parentNode.insertBefore(l, b); + })('script', 'https://api.hypelab.com/v1/scripts/hp-sdk.js?v=0', '${ HYPE_API_URL }', '${ PRODUCTION_PROPERTY_SLUG }');`; +})(); diff --git a/ui/shared/ad/types.ts b/ui/shared/ad/types.ts new file mode 100644 index 0000000000..f14b625484 --- /dev/null +++ b/ui/shared/ad/types.ts @@ -0,0 +1,6 @@ +export type BannerPlatform = 'mobile' | 'desktop'; + +export interface BannerProps { + className?: string; + platform?: BannerPlatform; +} diff --git a/ui/shared/address/AddressFromTo.pw.tsx b/ui/shared/address/AddressFromTo.pw.tsx index 78124f3cd1..d8ca3ed9fa 100644 --- a/ui/shared/address/AddressFromTo.pw.tsx +++ b/ui/shared/address/AddressFromTo.pw.tsx @@ -1,62 +1,53 @@ -import { test, expect } from '@playwright/experimental-ct-react'; import React from 'react'; import * as addressMock from 'mocks/address/address'; -import TestApp from 'playwright/TestApp'; -import * as configs from 'playwright/utils/configs'; +import { test, expect } from 'playwright/lib'; +import * as pwConfig from 'playwright/utils/config'; import AddressFromTo from './AddressFromTo'; -test.use({ viewport: configs.viewport.mobile }); +test.use({ viewport: pwConfig.viewport.mobile }); -test('outgoing txn', async({ mount }) => { - const component = await mount( - - - , +test('outgoing txn', async({ render }) => { + const component = await render( + , ); await expect(component).toHaveScreenshot(); }); -test('incoming txn', async({ mount }) => { - const component = await mount( - - - , +test('incoming txn', async({ render }) => { + const component = await render( + , ); await expect(component).toHaveScreenshot(); }); -test('compact mode', async({ mount }) => { - const component = await mount( - - - , +test('compact mode', async({ render }) => { + const component = await render( + , ); await expect(component).toHaveScreenshot(); }); -test('loading state', async({ mount }) => { - const component = await mount( - - - , +test('loading state', async({ render }) => { + const component = await render( + , ); await expect(component).toHaveScreenshot(); }); diff --git a/ui/shared/address/AddressFromTo.tsx b/ui/shared/address/AddressFromTo.tsx index e3d2763ee9..6ab0418b7a 100644 --- a/ui/shared/address/AddressFromTo.tsx +++ b/ui/shared/address/AddressFromTo.tsx @@ -1,5 +1,5 @@ import type { ThemeTypings } from '@chakra-ui/react'; -import { Flex, chakra, useBreakpointValue } from '@chakra-ui/react'; +import { Flex, Grid, chakra, useBreakpointValue } from '@chakra-ui/react'; import React from 'react'; import type { AddressParam } from 'types/api/addressParams'; @@ -25,7 +25,7 @@ interface Props { noIcon?: boolean; } -const AddressFromTo = ({ from, to, current, mode: modeProp, className, isLoading, tokenHash = '', truncation, noIcon }: Props) => { +const AddressFromTo = ({ from, to, current, mode: modeProp, className, isLoading, tokenHash = '', noIcon }: Props) => { const mode = useBreakpointValue( { base: (typeof modeProp === 'object' ? modeProp.base : modeProp), @@ -52,8 +52,8 @@ const AddressFromTo = ({ from, to, current, mode: modeProp, className, isLoading noCopy={ current === from.hash } noIcon={ noIcon } tokenHash={ tokenHash } - truncation={ truncation } - maxW={ truncation === 'constant' ? undefined : 'calc(100% - 28px)' } + truncation="constant" + maxW="calc(100% - 28px)" w="min-content" /> @@ -65,8 +65,8 @@ const AddressFromTo = ({ from, to, current, mode: modeProp, className, isLoading noCopy={ current === to.hash } noIcon={ noIcon } tokenHash={ tokenHash } - truncation={ truncation } - maxW={ truncation === 'constant' ? undefined : 'calc(100% - 28px)' } + truncation="constant" + maxW="calc(100% - 28px)" w="min-content" ml="28px" /> @@ -76,10 +76,10 @@ const AddressFromTo = ({ from, to, current, mode: modeProp, className, isLoading } const isOutgoing = current === from.hash; - const iconSizeWithMargins = (5 + (isOutgoing ? 4 : 2) + 3) * 4; + const iconSize = 20; return ( - + ) } - + ); }; diff --git a/ui/shared/address/AddressFromToIcon.pw.tsx b/ui/shared/address/AddressFromToIcon.pw.tsx index e65b55479e..c1a6490630 100644 --- a/ui/shared/address/AddressFromToIcon.pw.tsx +++ b/ui/shared/address/AddressFromToIcon.pw.tsx @@ -1,21 +1,18 @@ import { Box } from '@chakra-ui/react'; -import { test, expect } from '@playwright/experimental-ct-react'; import React from 'react'; -import TestApp from 'playwright/TestApp'; +import { test, expect } from 'playwright/lib'; import AddressFromToIcon from './AddressFromToIcon'; test.use({ viewport: { width: 36, height: 36 } }); [ 'in', 'out', 'self', 'unspecified' ].forEach((type) => { - test(`${ type } txn type +@dark-mode`, async({ mount }) => { - const component = await mount( - - - - - , + test(`${ type } txn type +@dark-mode`, async({ render }) => { + const component = await render( + + + , ); await expect(component).toHaveScreenshot(); }); diff --git a/ui/shared/address/__screenshots__/AddressFromTo.pw.tsx_default_compact-mode-1.png b/ui/shared/address/__screenshots__/AddressFromTo.pw.tsx_default_compact-mode-1.png index 073c5688d4..40fce705a4 100644 Binary files a/ui/shared/address/__screenshots__/AddressFromTo.pw.tsx_default_compact-mode-1.png and b/ui/shared/address/__screenshots__/AddressFromTo.pw.tsx_default_compact-mode-1.png differ diff --git a/ui/shared/address/__screenshots__/AddressFromTo.pw.tsx_default_incoming-txn-1.png b/ui/shared/address/__screenshots__/AddressFromTo.pw.tsx_default_incoming-txn-1.png index 3c3e387f1b..ab596d4026 100644 Binary files a/ui/shared/address/__screenshots__/AddressFromTo.pw.tsx_default_incoming-txn-1.png and b/ui/shared/address/__screenshots__/AddressFromTo.pw.tsx_default_incoming-txn-1.png differ diff --git a/ui/shared/address/__screenshots__/AddressFromTo.pw.tsx_default_outgoing-txn-1.png b/ui/shared/address/__screenshots__/AddressFromTo.pw.tsx_default_outgoing-txn-1.png index 1e28068da6..f86332ebf8 100644 Binary files a/ui/shared/address/__screenshots__/AddressFromTo.pw.tsx_default_outgoing-txn-1.png and b/ui/shared/address/__screenshots__/AddressFromTo.pw.tsx_default_outgoing-txn-1.png differ diff --git a/ui/shared/address/utils.ts b/ui/shared/address/utils.ts index 0e760aa9ed..38e33eb08a 100644 --- a/ui/shared/address/utils.ts +++ b/ui/shared/address/utils.ts @@ -25,7 +25,7 @@ export function getTxCourseType(from: string, to: string | undefined, current?: export const unknownAddress: Omit = { is_contract: false, is_verified: false, - implementation_name: '', + implementations: null, name: '', private_tags: [], public_tags: [], diff --git a/ui/shared/alerts/ServiceDegradationWarning.tsx b/ui/shared/alerts/ServiceDegradationWarning.tsx index 2e9911b8c4..3dd49467d3 100644 --- a/ui/shared/alerts/ServiceDegradationWarning.tsx +++ b/ui/shared/alerts/ServiceDegradationWarning.tsx @@ -11,7 +11,7 @@ const ServiceDegradationWarning = ({ isLoading, className }: Props) => { - Data sync in progress... page will refresh automatically once transaction data is available + Data sync in progress... page will refresh automatically once data is available ); diff --git a/ui/shared/batch/ArbitrumL2TxnBatchDA.tsx b/ui/shared/batch/ArbitrumL2TxnBatchDA.tsx new file mode 100644 index 0000000000..1acbe4ab28 --- /dev/null +++ b/ui/shared/batch/ArbitrumL2TxnBatchDA.tsx @@ -0,0 +1,48 @@ +import { Skeleton, Tag } from '@chakra-ui/react'; +import React from 'react'; + +import type { ArbitrumL2TxnBatchesItem } from 'types/api/arbitrumL2'; + +export interface Props { + dataContainer: ArbitrumL2TxnBatchesItem['batch_data_container']; + isLoading?: boolean; +} + +const ArbitrumL2TxnBatchDA = ({ dataContainer, isLoading }: Props) => { + let text: string; + + if (dataContainer === null) { + return null; + } + + switch (dataContainer) { + case 'in_blob4844': + text = 'blob'; + break; + case 'in_anytrust': + text = 'anytrust'; + break; + case 'in_calldata': + text = 'calldata'; + break; + case 'in_celestia': + text = 'celestia'; + break; + default: + text = ''; + } + + if (!text) { + return null; + } + + return ( + + + { text } + + + ); +}; + +export default ArbitrumL2TxnBatchDA; diff --git a/ui/shared/batch/OptimisticL2TxnBatchDA.tsx b/ui/shared/batch/OptimisticL2TxnBatchDA.tsx new file mode 100644 index 0000000000..93e6c6395f --- /dev/null +++ b/ui/shared/batch/OptimisticL2TxnBatchDA.tsx @@ -0,0 +1,33 @@ +import React from 'react'; + +import type { OptimisticL2TxnBatchesItem } from 'types/api/optimisticL2'; +import type { ExcludeUndefined } from 'types/utils'; + +import Tag from 'ui/shared/chakra/Tag'; + +export interface Props { + container: ExcludeUndefined; + isLoading?: boolean; +} + +const OptimisticL2TxnBatchDA = ({ container, isLoading }: Props) => { + + const text = (() => { + switch (container) { + case 'in_blob4844': + return 'EIP-4844 blob'; + case 'in_calldata': + return 'Calldata'; + case 'in_celestia': + return 'Celestia blob'; + } + })(); + + return ( + + { text } + + ); +}; + +export default React.memo(OptimisticL2TxnBatchDA); diff --git a/ui/shared/batch/ScrollL2TxnBatchDA.tsx b/ui/shared/batch/ScrollL2TxnBatchDA.tsx new file mode 100644 index 0000000000..5e47a8d290 --- /dev/null +++ b/ui/shared/batch/ScrollL2TxnBatchDA.tsx @@ -0,0 +1,31 @@ +import React from 'react'; + +import type { ScrollL2TxnBatch } from 'types/api/scrollL2'; +import type { ExcludeUndefined } from 'types/utils'; + +import Tag from 'ui/shared/chakra/Tag'; + +export interface Props { + container: ExcludeUndefined; + isLoading?: boolean; +} + +const ScrollL2TxnBatchDA = ({ container, isLoading }: Props) => { + + const text = (() => { + switch (container) { + case 'in_blob4844': + return 'EIP-4844 blob'; + case 'in_calldata': + return 'Calldata'; + } + })(); + + return ( + + { text } + + ); +}; + +export default React.memo(ScrollL2TxnBatchDA); diff --git a/ui/shared/blob/BlobDataType.pw.tsx b/ui/shared/blob/BlobDataType.pw.tsx new file mode 100644 index 0000000000..69b6a0e6ef --- /dev/null +++ b/ui/shared/blob/BlobDataType.pw.tsx @@ -0,0 +1,22 @@ +import React from 'react'; + +import { test, expect } from 'playwright/lib'; + +import BlobDataType from './BlobDataType'; + +test.use({ viewport: { width: 100, height: 50 } }); + +test('image data', async({ render }) => { + const component = await render(); + await expect(component).toHaveScreenshot(); +}); + +test('raw data', async({ render }) => { + const component = await render(); + await expect(component).toHaveScreenshot(); +}); + +test('text data', async({ render }) => { + const component = await render(); + await expect(component).toHaveScreenshot(); +}); diff --git a/ui/shared/blob/BlobDataType.tsx b/ui/shared/blob/BlobDataType.tsx new file mode 100644 index 0000000000..6b0b36a3df --- /dev/null +++ b/ui/shared/blob/BlobDataType.tsx @@ -0,0 +1,56 @@ +import { Flex, Skeleton, useColorModeValue } from '@chakra-ui/react'; +import React from 'react'; + +import * as blobUtils from 'lib/blob'; +import type { IconName } from 'ui/shared/IconSvg'; +import IconSvg from 'ui/shared/IconSvg'; + +interface Props { + data: string; + isLoading?: boolean; +} + +const TYPES: Record = { + image: { iconName: 'blobs/image', label: 'Image' }, + text: { iconName: 'blobs/text', label: 'Text' }, + raw: { iconName: 'blobs/raw', label: 'Raw' }, +}; + +const BlobDataType = ({ data, isLoading }: Props) => { + const iconColor = useColorModeValue('gray.500', 'gray.400'); + + const guessedType = React.useMemo(() => { + if (isLoading) { + return; + } + return blobUtils.guessDataType(data); + }, [ data, isLoading ]); + + const { iconName, label } = (() => { + if (guessedType?.mime?.startsWith('image/')) { + return TYPES.image; + } + + if ( + guessedType?.mime?.startsWith('text/') || + [ + 'application/json', + 'application/xml', + 'application/javascript', + ].includes(guessedType?.mime || '') + ) { + return TYPES.text; + } + + return TYPES.raw; + })(); + + return ( + + + { label } + + ); +}; + +export default React.memo(BlobDataType); diff --git a/ui/shared/blob/__screenshots__/BlobDataType.pw.tsx_default_image-data-1.png b/ui/shared/blob/__screenshots__/BlobDataType.pw.tsx_default_image-data-1.png new file mode 100644 index 0000000000..d6f99a60a3 Binary files /dev/null and b/ui/shared/blob/__screenshots__/BlobDataType.pw.tsx_default_image-data-1.png differ diff --git a/ui/shared/blob/__screenshots__/BlobDataType.pw.tsx_default_raw-data-1.png b/ui/shared/blob/__screenshots__/BlobDataType.pw.tsx_default_raw-data-1.png new file mode 100644 index 0000000000..a19467a9dc Binary files /dev/null and b/ui/shared/blob/__screenshots__/BlobDataType.pw.tsx_default_raw-data-1.png differ diff --git a/ui/shared/blob/__screenshots__/BlobDataType.pw.tsx_default_text-data-1.png b/ui/shared/blob/__screenshots__/BlobDataType.pw.tsx_default_text-data-1.png new file mode 100644 index 0000000000..91fbf22ca3 Binary files /dev/null and b/ui/shared/blob/__screenshots__/BlobDataType.pw.tsx_default_text-data-1.png differ diff --git a/ui/shared/block/BlockGasUsed.tsx b/ui/shared/block/BlockGasUsed.tsx new file mode 100644 index 0000000000..0c1ee6f416 --- /dev/null +++ b/ui/shared/block/BlockGasUsed.tsx @@ -0,0 +1,54 @@ +import { chakra, Tooltip, Box, useColorModeValue } from '@chakra-ui/react'; +import BigNumber from 'bignumber.js'; +import React from 'react'; + +import config from 'configs/app'; + +import GasUsedToTargetRatio from '../GasUsedToTargetRatio'; +import TextSeparator from '../TextSeparator'; +import Utilization from '../Utilization/Utilization'; + +const rollupFeature = config.features.rollup; + +interface Props { + className?: string; + gasUsed?: string; + gasLimit: string; + gasTarget?: number; + isLoading?: boolean; +} + +const BlockGasUsed = ({ className, gasUsed, gasLimit, gasTarget, isLoading }: Props) => { + const hasGasUtilization = + gasUsed && gasUsed !== '0' && + (!rollupFeature.isEnabled || rollupFeature.type === 'optimistic' || rollupFeature.type === 'shibarium'); + + const separatorColor = useColorModeValue('gray.200', 'gray.700'); + + if (!hasGasUtilization) { + return null; + } + + return ( + <> + + + + + + { gasTarget && ( + <> + + + + ) } + + ); +}; + +export default React.memo(chakra(BlockGasUsed)); diff --git a/ui/shared/chakra/Menu.tsx b/ui/shared/chakra/Menu.tsx new file mode 100644 index 0000000000..9d74cce995 --- /dev/null +++ b/ui/shared/chakra/Menu.tsx @@ -0,0 +1,10 @@ +import type { MenuProps } from '@chakra-ui/react'; +// eslint-disable-next-line no-restricted-imports +import { Menu as MenuBase } from '@chakra-ui/react'; +import React from 'react'; + +const Menu = (props: MenuProps) => { + return ; +}; + +export default React.memo(Menu); diff --git a/ui/shared/chakra/PinInput.tsx b/ui/shared/chakra/PinInput.tsx new file mode 100644 index 0000000000..0dee1c39bd --- /dev/null +++ b/ui/shared/chakra/PinInput.tsx @@ -0,0 +1,10 @@ +import type { PinInputProps, StyleProps } from '@chakra-ui/react'; +// eslint-disable-next-line no-restricted-imports +import { PinInput as PinInputBase } from '@chakra-ui/react'; +import React from 'react'; + +const PinInput = (props: PinInputProps & { bgColor?: StyleProps['bgColor'] }) => { + return ; +}; + +export default React.memo(PinInput); diff --git a/ui/shared/chakra/Popover.tsx b/ui/shared/chakra/Popover.tsx new file mode 100644 index 0000000000..a5866e841a --- /dev/null +++ b/ui/shared/chakra/Popover.tsx @@ -0,0 +1,10 @@ +import type { PopoverProps } from '@chakra-ui/react'; +// eslint-disable-next-line no-restricted-imports +import { Popover as PopoverBase } from '@chakra-ui/react'; +import React from 'react'; + +const Popover = (props: PopoverProps) => { + return ; +}; + +export default React.memo(Popover); diff --git a/ui/shared/chakra/Tag.tsx b/ui/shared/chakra/Tag.tsx index 08d2d6f130..f8e0cb762f 100644 --- a/ui/shared/chakra/Tag.tsx +++ b/ui/shared/chakra/Tag.tsx @@ -4,7 +4,7 @@ import React from 'react'; import TruncatedTextTooltip from 'ui/shared/TruncatedTextTooltip'; -interface Props extends TagProps { +export interface Props extends TagProps { isLoading?: boolean; } diff --git a/ui/shared/Toast.tsx b/ui/shared/chakra/Toast.tsx similarity index 91% rename from ui/shared/Toast.tsx rename to ui/shared/chakra/Toast.tsx index 67451e02f6..c886a67438 100644 --- a/ui/shared/Toast.tsx +++ b/ui/shared/chakra/Toast.tsx @@ -20,7 +20,7 @@ function getBgColor(status?: AlertStatus) { } } -const Toast = ({ onClose, title, description, id, isClosable, status }: ToastProps) => { +const Toast = ({ onClose, title, description, id, isClosable, status, icon }: ToastProps) => { const ids = id ? { @@ -48,7 +48,7 @@ const Toast = ({ onClose, title, description, id, isClosable, status }: ToastPro maxWidth="400px" > - { title && { title } } + { title && { icon }{ title } } { description && ( { description } diff --git a/ui/shared/chart/ChartArea.tsx b/ui/shared/chart/ChartArea.tsx index 479e010508..5bb03281a5 100644 --- a/ui/shared/chart/ChartArea.tsx +++ b/ui/shared/chart/ChartArea.tsx @@ -11,10 +11,10 @@ interface Props extends React.SVGProps { yScale: d3.ScaleTime | d3.ScaleLinear; color?: string; data: Array; - disableAnimation?: boolean; + noAnimation?: boolean; } -const ChartArea = ({ id, xScale, yScale, color, data, disableAnimation, ...props }: Props) => { +const ChartArea = ({ id, xScale, yScale, color, data, noAnimation, ...props }: Props) => { const ref = React.useRef(null); const theme = useTheme(); @@ -26,7 +26,7 @@ const ChartArea = ({ id, xScale, yScale, color, data, disableAnimation, ...props }; React.useEffect(() => { - if (disableAnimation) { + if (noAnimation) { d3.select(ref.current).attr('opacity', 1); return; } @@ -34,10 +34,11 @@ const ChartArea = ({ id, xScale, yScale, color, data, disableAnimation, ...props .duration(750) .ease(d3.easeBackIn) .attr('opacity', 1); - }, [ disableAnimation ]); + }, [ noAnimation ]); const d = React.useMemo(() => { const area = d3.area() + .defined(({ isApproximate }) => !isApproximate) .x(({ date }) => xScale(date)) .y1(({ value }) => yScale(value)) .y0(() => yScale(yScale.domain()[0])) diff --git a/ui/shared/chart/ChartAxis.tsx b/ui/shared/chart/ChartAxis.tsx index 487e3ace61..203ba65ccf 100644 --- a/ui/shared/chart/ChartAxis.tsx +++ b/ui/shared/chart/ChartAxis.tsx @@ -5,13 +5,13 @@ import React from 'react'; interface Props extends Omit, 'scale'> { type: 'left' | 'bottom'; scale: d3.ScaleTime | d3.ScaleLinear; - disableAnimation?: boolean; + noAnimation?: boolean; ticks: number; tickFormatGenerator?: (axis: d3.Axis) => (domainValue: d3.AxisDomain, index: number) => string; anchorEl?: SVGRectElement | null; } -const ChartAxis = ({ type, scale, ticks, tickFormatGenerator, disableAnimation, anchorEl, ...props }: Props) => { +const ChartAxis = ({ type, scale, ticks, tickFormatGenerator, noAnimation, anchorEl, ...props }: Props) => { const ref = React.useRef(null); const textColorToken = useColorModeValue('blackAlpha.600', 'whiteAlpha.500'); @@ -31,7 +31,7 @@ const ChartAxis = ({ type, scale, ticks, tickFormatGenerator, disableAnimation, const axisGroup = d3.select(ref.current); - if (disableAnimation) { + if (noAnimation) { axisGroup.call(axis); } else { axisGroup.transition().duration(750).ease(d3.easeLinear).call(axis); @@ -42,7 +42,7 @@ const ChartAxis = ({ type, scale, ticks, tickFormatGenerator, disableAnimation, .attr('opacity', 1) .attr('color', textColor) .attr('font-size', '0.75rem'); - }, [ scale, ticks, tickFormatGenerator, disableAnimation, type, textColor ]); + }, [ scale, ticks, tickFormatGenerator, noAnimation, type, textColor ]); React.useEffect(() => { if (!anchorEl) { diff --git a/ui/shared/chart/ChartGridLine.tsx b/ui/shared/chart/ChartGridLine.tsx index 2d140f25c1..3756b9239b 100644 --- a/ui/shared/chart/ChartGridLine.tsx +++ b/ui/shared/chart/ChartGridLine.tsx @@ -5,12 +5,12 @@ import React from 'react'; interface Props extends Omit, 'scale'> { type: 'vertical' | 'horizontal'; scale: d3.ScaleTime | d3.ScaleLinear; - disableAnimation?: boolean; + noAnimation?: boolean; size: number; ticks: number; } -const ChartGridLine = ({ type, scale, ticks, size, disableAnimation, ...props }: Props) => { +const ChartGridLine = ({ type, scale, ticks, size, noAnimation, ...props }: Props) => { const ref = React.useRef(null); const strokeColor = useToken('colors', 'divider'); @@ -24,7 +24,7 @@ const ChartGridLine = ({ type, scale, ticks, size, disableAnimation, ...props }: const axis = axisGenerator(scale).ticks(ticks).tickSize(-size); const gridGroup = d3.select(ref.current); - if (disableAnimation) { + if (noAnimation) { gridGroup.call(axis); } else { gridGroup.transition().duration(750).ease(d3.easeLinear).call(axis); @@ -32,7 +32,7 @@ const ChartGridLine = ({ type, scale, ticks, size, disableAnimation, ...props }: gridGroup.select('.domain').remove(); gridGroup.selectAll('text').remove(); gridGroup.selectAll('line').attr('stroke', strokeColor); - }, [ scale, ticks, size, disableAnimation, type, strokeColor ]); + }, [ scale, ticks, size, noAnimation, type, strokeColor ]); return ; }; diff --git a/ui/shared/chart/ChartIntervalSelect.tsx b/ui/shared/chart/ChartIntervalSelect.tsx new file mode 100644 index 0000000000..dcb9b0a394 --- /dev/null +++ b/ui/shared/chart/ChartIntervalSelect.tsx @@ -0,0 +1,49 @@ +import type { TagProps } from '@chakra-ui/react'; +import { Skeleton } from '@chakra-ui/react'; +import React from 'react'; + +import type { StatsInterval, StatsIntervalIds } from 'types/client/stats'; +import type { SelectOption } from 'ui/shared/select/types'; + +import Select from 'ui/shared/select/Select'; +import TagGroupSelect from 'ui/shared/tagGroupSelect/TagGroupSelect'; +import { STATS_INTERVALS } from 'ui/stats/constants'; + +const intervalList = Object.keys(STATS_INTERVALS).map((id: string) => ({ + value: id, + label: STATS_INTERVALS[id as StatsIntervalIds].title, +})) as Array; + +const intervalListShort = Object.keys(STATS_INTERVALS).map((id: string) => ({ + id: id, + title: STATS_INTERVALS[id as StatsIntervalIds].shortTitle, +})) as Array; + +type Props = { + interval: StatsIntervalIds; + onIntervalChange: (newInterval: StatsIntervalIds) => void; + isLoading?: boolean; + selectTagSize?: TagProps['size']; +}; + +const ChartIntervalSelect = ({ interval, onIntervalChange, isLoading, selectTagSize }: Props) => { + return ( + <> + + items={ intervalListShort } onChange={ onIntervalChange } value={ interval } tagSize={ selectTagSize }/> + + + { ({ isOpen, onToggle }) => ( + + ) } + + ); +}; + +export default React.memo(PopoverFilterRadio); diff --git a/ui/shared/filters/TableColumnFilter.tsx b/ui/shared/filters/TableColumnFilter.tsx new file mode 100644 index 0000000000..92ae533621 --- /dev/null +++ b/ui/shared/filters/TableColumnFilter.tsx @@ -0,0 +1,55 @@ +import { + chakra, + Flex, + Text, + Link, + Button, +} from '@chakra-ui/react'; +import React from 'react'; + +type Props = { + title: string; + isFilled?: boolean; + isTouched?: boolean; + hasReset?: boolean; + onFilter: () => void; + onReset?: () => void; + onClose?: () => void; + children: React.ReactNode; +}; + +const TableColumnFilter = ({ title, isFilled, isTouched, hasReset, onFilter, onReset, onClose, children }: Props) => { + const onFilterClick = React.useCallback(() => { + onClose && onClose(); + onFilter(); + }, [ onClose, onFilter ]); + return ( + <> + + { title } + { hasReset && ( + + Reset + + ) } + + { children } + + + ); +}; + +export default chakra(TableColumnFilter); diff --git a/ui/shared/filters/TableColumnFilterWrapper.tsx b/ui/shared/filters/TableColumnFilterWrapper.tsx new file mode 100644 index 0000000000..0d070665ab --- /dev/null +++ b/ui/shared/filters/TableColumnFilterWrapper.tsx @@ -0,0 +1,75 @@ +import { + PopoverTrigger, + PopoverContent, + PopoverBody, + useDisclosure, + chakra, + Portal, + Button, +} from '@chakra-ui/react'; +import React from 'react'; + +import Popover from 'ui/shared/chakra/Popover'; +import IconSvg from 'ui/shared/IconSvg'; + +interface Props { + columnName: string; + isActive?: boolean; + isLoading?: boolean; + className?: string; + children: React.ReactNode; + value?: string; +} + +const TableColumnFilterWrapper = ({ columnName, isActive, className, children, isLoading, value }: Props) => { + const { isOpen, onToggle, onClose } = useDisclosure(); + + const content = React.Children.only(children) as React.ReactElement & { + ref?: React.Ref; + }; + + const modifiedContent = React.cloneElement( + content, + { onClose }, + ); + + return ( + + + + + + + + { modifiedContent } + + + + + ); +}; + +export default chakra(TableColumnFilterWrapper); diff --git a/ui/shared/filters/TokenTypeFilter.tsx b/ui/shared/filters/TokenTypeFilter.tsx index 9d80814284..9c844a58fa 100644 --- a/ui/shared/filters/TokenTypeFilter.tsx +++ b/ui/shared/filters/TokenTypeFilter.tsx @@ -3,13 +3,14 @@ import React from 'react'; import type { NFTTokenType, TokenType } from 'types/api/token'; -import { NFT_TOKEN_TYPES, TOKEN_TYPES } from 'lib/token/tokenTypes'; +import { + TOKEN_TYPES, TOKEN_TYPE_IDS, NFT_TOKEN_TYPE_IDS } from 'lib/token/tokenTypes'; type Props = { onChange: (nextValue: Array) => void; defaultValue?: Array; nftOnly: T extends NFTTokenType ? true : false; -} +}; const TokenTypeFilter = ({ nftOnly, onChange, defaultValue }: Props) => { const { value, setValue } = useCheckboxGroup({ defaultValue }); @@ -42,9 +43,9 @@ const TokenTypeFilter = ({ nftOnly, onChange - { (nftOnly ? NFT_TOKEN_TYPES : TOKEN_TYPES).map(({ title, id }) => ( + { (nftOnly ? NFT_TOKEN_TYPE_IDS : TOKEN_TYPE_IDS).map((id) => ( - { title } + { TOKEN_TYPES[id] } )) } diff --git a/ui/shared/forms/FieldError.tsx b/ui/shared/forms/components/FieldError.tsx similarity index 100% rename from ui/shared/forms/FieldError.tsx rename to ui/shared/forms/components/FieldError.tsx diff --git a/ui/shared/forms/components/ImageUrlPreview.tsx b/ui/shared/forms/components/ImageUrlPreview.tsx new file mode 100644 index 0000000000..86a368c8b7 --- /dev/null +++ b/ui/shared/forms/components/ImageUrlPreview.tsx @@ -0,0 +1,47 @@ +import type { ColorMode } from '@chakra-ui/react'; +import { Image, Skeleton, chakra, DarkMode } from '@chakra-ui/react'; +import React from 'react'; + +interface Props { + src: string | undefined; + onLoad?: () => void; + onError?: () => void; + isInvalid: boolean; + className?: string; + fallback: React.ReactElement; + colorMode?: ColorMode; +} + +const ImageUrlPreview = ({ + src, + isInvalid, + onError, + onLoad, + className, + fallback: fallbackProp, + colorMode, +}: Props) => { + const skeleton = ; + + const fallback = (() => { + if (src && !isInvalid) { + return colorMode === 'dark' ? { skeleton } : skeleton; + } + return fallbackProp; + })(); + + return ( + Image preview + ); +}; + +export default chakra(React.memo(ImageUrlPreview)); diff --git a/ui/shared/forms/fields/FormFieldAddress.tsx b/ui/shared/forms/fields/FormFieldAddress.tsx new file mode 100644 index 0000000000..3caa01ec34 --- /dev/null +++ b/ui/shared/forms/fields/FormFieldAddress.tsx @@ -0,0 +1,39 @@ +import type { ChakraProps } from '@chakra-ui/react'; +import React from 'react'; +import type { FieldValues, Path } from 'react-hook-form'; + +import type { FormFieldPropsBase } from './types'; +import type { PartialBy } from 'types/utils'; + +import { addressValidator } from '../validators/address'; +import FormFieldText from './FormFieldText'; + +const FormFieldAddress = ( + props: PartialBy, 'placeholder'>, +) => { + const rules = React.useMemo( + () => ({ + ...props.rules, + validate: { + ...props.rules?.validate, + address: addressValidator, + }, + }), + [ props.rules ], + ); + + return ( + + ); +}; + +export type WrappedComponent = < + FormFields extends FieldValues, + Name extends Path = Path, +>(props: PartialBy, 'placeholder'> & ChakraProps) => React.JSX.Element; + +export default React.memo(FormFieldAddress) as WrappedComponent; diff --git a/ui/shared/forms/fields/FormFieldCheckbox.tsx b/ui/shared/forms/fields/FormFieldCheckbox.tsx new file mode 100644 index 0000000000..61dce934ac --- /dev/null +++ b/ui/shared/forms/fields/FormFieldCheckbox.tsx @@ -0,0 +1,63 @@ +import type { ChakraProps } from '@chakra-ui/react'; +import { chakra, Checkbox } from '@chakra-ui/react'; +import React from 'react'; +import { useController, useFormContext, type FieldValues, type Path } from 'react-hook-form'; + +import type { FormFieldPropsBase } from './types'; + +interface Props< + FormFields extends FieldValues, + Name extends Path = Path, +> extends Omit, 'size' | 'bgColor' | 'placeholder'> { + label: string; +} + +const FormFieldCheckbox = < + FormFields extends FieldValues, + Name extends Path = Path, +>({ + name, + label, + rules, + onChange, + isReadOnly, + className, +}: Props) => { + const { control } = useFormContext(); + const { field, formState } = useController({ + control, + name, + rules, + }); + + const isDisabled = formState.isSubmitting; + + const handleChange: typeof field.onChange = React.useCallback((...args) => { + field.onChange(...args); + onChange?.(); + }, [ field, onChange ]); + + return ( + + { label } + + ); +}; + +const WrappedFormFieldCheckbox = chakra(FormFieldCheckbox); + +export type WrappedComponent = < + FormFields extends FieldValues, + Name extends Path = Path, +>(props: Props & ChakraProps) => React.JSX.Element; + +export default React.memo(WrappedFormFieldCheckbox) as WrappedComponent; diff --git a/ui/shared/forms/fields/FormFieldEmail.tsx b/ui/shared/forms/fields/FormFieldEmail.tsx new file mode 100644 index 0000000000..bdee970502 --- /dev/null +++ b/ui/shared/forms/fields/FormFieldEmail.tsx @@ -0,0 +1,36 @@ +import type { ChakraProps } from '@chakra-ui/react'; +import React from 'react'; +import type { FieldValues, Path } from 'react-hook-form'; + +import type { FormFieldPropsBase } from './types'; +import type { PartialBy } from 'types/utils'; + +import { EMAIL_REGEXP } from '../validators/email'; +import FormFieldText from './FormFieldText'; + +const FormFieldEmail = ( + props: PartialBy, 'placeholder'>, +) => { + const rules = React.useMemo( + () => ({ + ...props.rules, + pattern: EMAIL_REGEXP, + }), + [ props.rules ], + ); + + return ( + + ); +}; + +export type WrappedComponent = < + FormFields extends FieldValues, + Name extends Path = Path, +>(props: PartialBy, 'placeholder'> & ChakraProps) => React.JSX.Element; + +export default React.memo(FormFieldEmail) as WrappedComponent; diff --git a/ui/shared/forms/fields/FormFieldFancySelect.tsx b/ui/shared/forms/fields/FormFieldFancySelect.tsx new file mode 100644 index 0000000000..0bb4131608 --- /dev/null +++ b/ui/shared/forms/fields/FormFieldFancySelect.tsx @@ -0,0 +1,50 @@ +import React from 'react'; +import type { Path, FieldValues } from 'react-hook-form'; +import { useController, useFormContext } from 'react-hook-form'; + +import type { FormFieldPropsBase } from './types'; + +// import type { Option } from 'ui/shared/forms/inputs/select/types'; +import useIsMobile from 'lib/hooks/useIsMobile'; +import type { Props as FancySelectProps } from 'ui/shared/forms/inputs/select/FancySelect'; +import FancySelect from 'ui/shared/forms/inputs/select/FancySelect'; + +// FIXME: Try to get this to work to add more constraints to the props type +// this type only works for plain objects, not for nested objects or arrays (e.g. ui/publicTags/submit/types.ts:FormFields) +// type SelectField = { [K in keyof O]: NonNullable extends Option ? K : never }[keyof O]; + +type Props< + FormFields extends FieldValues, + Name extends Path, +> = Omit, 'bgColor' | 'size'> & Partial & { + size?: 'md' | 'lg'; +}; + +const FormFieldFancySelect = < + FormFields extends FieldValues, + Name extends Path, +>(props: Props) => { + const isMobile = useIsMobile(); + const defaultSize = isMobile ? 'md' : 'lg'; + + const { control } = useFormContext(); + const { field, fieldState, formState } = useController({ + control, + name: props.name, + rules: { ...props.rules, required: props.isRequired }, + }); + + const isDisabled = formState.isSubmitting; + + return ( + + ); +}; + +export default React.memo(FormFieldFancySelect) as typeof FormFieldFancySelect; diff --git a/ui/shared/forms/fields/FormFieldText.tsx b/ui/shared/forms/fields/FormFieldText.tsx new file mode 100644 index 0000000000..526e0e63a3 --- /dev/null +++ b/ui/shared/forms/fields/FormFieldText.tsx @@ -0,0 +1,115 @@ +import type { ChakraProps } from '@chakra-ui/react'; +import { FormControl, Input, InputGroup, InputRightElement, Textarea, chakra, shouldForwardProp } from '@chakra-ui/react'; +import React from 'react'; +import type { FieldValues, Path } from 'react-hook-form'; +import { useController, useFormContext } from 'react-hook-form'; + +import type { FormFieldPropsBase } from './types'; + +import FormInputPlaceholder from '../inputs/FormInputPlaceholder'; + +interface Props< + FormFields extends FieldValues, + Name extends Path = Path, +> extends FormFieldPropsBase { + asComponent?: 'Input' | 'Textarea'; +} + +const FormFieldText = < + FormFields extends FieldValues, + Name extends Path = Path, +>({ + name, + placeholder, + isReadOnly, + isRequired, + rules, + onBlur, + type = 'text', + rightElement, + asComponent, + max, + + className, + size = 'md', + bgColor, + minH, + maxH, +}: Props) => { + const { control } = useFormContext(); + const { field, fieldState, formState } = useController({ + control, + name, + rules: { ...rules, required: isRequired }, + }); + + const isDisabled = formState.isSubmitting; + + const handleBlur = React.useCallback(() => { + field.onBlur(); + onBlur?.(); + }, [ field, onBlur ]); + + const Component = asComponent === 'Textarea' ? Textarea : Input; + const input = ( + + ); + const inputPlaceholder = size !== 'xs' && ; + + return ( + + { rightElement ? ( + + { input } + { inputPlaceholder } + { rightElement({ field }) } + + ) : ( + <> + { input } + { inputPlaceholder } + + ) } + + ); +}; + +const WrappedFormFieldText = chakra(FormFieldText, { + shouldForwardProp: (prop) => { + const isChakraProp = !shouldForwardProp(prop); + + if (isChakraProp && ![ 'bgColor', 'size', 'minH', 'maxH' ].includes(prop)) { + return false; + } + + return true; + }, +}); + +export type WrappedComponent = < + FormFields extends FieldValues, + Name extends Path = Path, +>(props: Props & ChakraProps) => React.JSX.Element; + +export default React.memo(WrappedFormFieldText) as WrappedComponent; diff --git a/ui/shared/forms/fields/FormFieldUrl.tsx b/ui/shared/forms/fields/FormFieldUrl.tsx new file mode 100644 index 0000000000..0ea64a26fd --- /dev/null +++ b/ui/shared/forms/fields/FormFieldUrl.tsx @@ -0,0 +1,26 @@ +import React from 'react'; +import type { FieldValues } from 'react-hook-form'; + +import type { FormFieldPropsBase } from './types'; + +import { urlValidator } from '../validators/url'; +import FormFieldText, { type WrappedComponent } from './FormFieldText'; + +const FormFieldUrl = ( + props: FormFieldPropsBase, +) => { + const rules = React.useMemo( + () => ({ + ...props.rules, + validate: { + ...props.rules?.validate, + url: urlValidator, + }, + }), + [ props.rules ], + ); + + return ; +}; + +export default React.memo(FormFieldUrl) as WrappedComponent; diff --git a/ui/shared/forms/fields/types.ts b/ui/shared/forms/fields/types.ts new file mode 100644 index 0000000000..ccbc3ee2e9 --- /dev/null +++ b/ui/shared/forms/fields/types.ts @@ -0,0 +1,26 @@ +import type { FormControlProps } from '@chakra-ui/react'; +import type React from 'react'; +import type { ControllerRenderProps, FieldValues, Path, RegisterOptions } from 'react-hook-form'; + +export interface FormFieldPropsBase< + FormFields extends FieldValues, + Name extends Path = Path, +> { + name: Name; + placeholder: string; + isReadOnly?: boolean; + isRequired?: boolean; + rules?: Omit, 'valueAsNumber' | 'valueAsDate' | 'setValueAs' | 'disabled'>; + onBlur?: () => void; + onChange?: () => void; + type?: HTMLInputElement['type']; + rightElement?: ({ field }: { field: ControllerRenderProps }) => React.ReactNode; + max?: HTMLInputElement['max']; + + // styles + size?: FormControlProps['size']; + bgColor?: FormControlProps['bgColor']; + maxH?: FormControlProps['maxH']; + minH?: FormControlProps['minH']; + className?: string; +} diff --git a/ui/shared/forms/inputs/FormInputPlaceholder.tsx b/ui/shared/forms/inputs/FormInputPlaceholder.tsx new file mode 100644 index 0000000000..94cfe261ba --- /dev/null +++ b/ui/shared/forms/inputs/FormInputPlaceholder.tsx @@ -0,0 +1,37 @@ +import { FormLabel, chakra } from '@chakra-ui/react'; +import React from 'react'; +import type { FieldError } from 'react-hook-form'; + +interface Props { + text: string; + icon?: React.ReactNode; + error?: Partial; + isFancy?: boolean; +} + +const FormInputPlaceholder = ({ text, icon, error, isFancy }: Props) => { + let errorMessage = error?.message; + + if (!errorMessage && error?.type === 'pattern') { + errorMessage = 'Invalid format'; + } + + return ( + + { icon } + { text } + { errorMessage && ( + + { ' ' } + - { errorMessage } + + ) } + + ); +}; + +export default React.memo(FormInputPlaceholder); diff --git a/ui/shared/forms/DragAndDropArea.tsx b/ui/shared/forms/inputs/file/DragAndDropArea.tsx similarity index 90% rename from ui/shared/forms/DragAndDropArea.tsx rename to ui/shared/forms/inputs/file/DragAndDropArea.tsx index 51cab94cec..05299d6dea 100644 --- a/ui/shared/forms/DragAndDropArea.tsx +++ b/ui/shared/forms/inputs/file/DragAndDropArea.tsx @@ -2,16 +2,17 @@ import { chakra, Center, useColorModeValue } from '@chakra-ui/react'; import type { DragEvent } from 'react'; import React from 'react'; -import { getAllFileEntries, convertFileEntryToFile } from './utils/files'; +import { getAllFileEntries, convertFileEntryToFile } from './utils'; interface Props { children: React.ReactNode; onDrop: (files: Array) => void; className?: string; isDisabled?: boolean; + fullFilePath?: boolean; } -const DragAndDropArea = ({ onDrop, children, className, isDisabled }: Props) => { +const DragAndDropArea = ({ onDrop, children, className, isDisabled, fullFilePath }: Props) => { const [ isDragOver, setIsDragOver ] = React.useState(false); const handleDrop = React.useCallback(async(event: DragEvent) => { @@ -22,11 +23,11 @@ const DragAndDropArea = ({ onDrop, children, className, isDisabled }: Props) => } const fileEntries = await getAllFileEntries(event.dataTransfer.items); - const files = await Promise.all(fileEntries.map(convertFileEntryToFile)); + const files = await Promise.all(fileEntries.map((fileEntry) => convertFileEntryToFile(fileEntry, fullFilePath))); onDrop(files); setIsDragOver(false); - }, [ isDisabled, onDrop ]); + }, [ isDisabled, onDrop, fullFilePath ]); const handleDragOver = React.useCallback((event: DragEvent) => { event.preventDefault(); diff --git a/ui/shared/forms/FileInput.tsx b/ui/shared/forms/inputs/file/FileInput.tsx similarity index 100% rename from ui/shared/forms/FileInput.tsx rename to ui/shared/forms/inputs/file/FileInput.tsx diff --git a/ui/shared/forms/FileSnippet.tsx b/ui/shared/forms/inputs/file/FileSnippet.tsx similarity index 97% rename from ui/shared/forms/FileSnippet.tsx rename to ui/shared/forms/inputs/file/FileSnippet.tsx index 172c045ed9..73656e46d8 100644 --- a/ui/shared/forms/FileSnippet.tsx +++ b/ui/shared/forms/inputs/file/FileSnippet.tsx @@ -76,7 +76,7 @@ const FileSnippet = ({ file, className, index, onRemove, isDisabled, error }: Pr diff --git a/ui/shared/forms/inputs/file/utils.ts b/ui/shared/forms/inputs/file/utils.ts new file mode 100644 index 0000000000..5990b19ffb --- /dev/null +++ b/ui/shared/forms/inputs/file/utils.ts @@ -0,0 +1,68 @@ +import stripLeadingSlash from 'lib/stripLeadingSlash'; + +// Function to get all files in drop directory +export async function getAllFileEntries(dataTransferItemList: DataTransferItemList): Promise> { + const fileEntries: Array = []; + + // Use BFS to traverse entire directory/file structure + const queue: Array = []; + + // Unfortunately dataTransferItemList is not iterable i.e. no forEach + for (let i = 0; i < dataTransferItemList.length; i++) { + // Note webkitGetAsEntry a non-standard feature and may change + // Usage is necessary for handling directories + // + typescript types are kinda wrong - https://developer.mozilla.org/en-US/docs/Web/API/DataTransferItem/webkitGetAsEntry + const item = dataTransferItemList[i].webkitGetAsEntry() as FileSystemFileEntry | FileSystemDirectoryEntry | null; + item && queue.push(item); + } + + while (queue.length > 0) { + const entry = queue.shift(); + if (entry?.isFile) { + fileEntries.push(entry as FileSystemFileEntry); + } else if (entry?.isDirectory && 'createReader' in entry) { + queue.push(...await readAllDirectoryEntries(entry.createReader())); + } + } + return fileEntries; +} + +// Get all the entries (files or sub-directories) in a directory +// by calling readEntries until it returns empty array +async function readAllDirectoryEntries(directoryReader: FileSystemDirectoryReader) { + const entries: Array = []; + let readEntries = await readEntriesPromise(directoryReader); + + while (readEntries && readEntries.length > 0) { + entries.push(...readEntries); + readEntries = await readEntriesPromise(directoryReader); + } + return entries; +} + +// Wrap readEntries in a promise to make working with readEntries easier +// readEntries will return only some of the entries in a directory +// e.g. Chrome returns at most 100 entries at a time +async function readEntriesPromise(directoryReader: FileSystemDirectoryReader): Promise | undefined> { + try { + return await new Promise((resolve, reject) => { + directoryReader.readEntries( + (fileEntry) => { + resolve(fileEntry as Array); + }, + reject, + ); + }); + } catch (err) {} +} + +export function convertFileEntryToFile(entry: FileSystemFileEntry, fullFilePath?: boolean): Promise { + return new Promise((resolve) => { + entry.file(async(file: File) => { + const newFile = fullFilePath ? + new File([ file ], stripLeadingSlash(entry.fullPath), { lastModified: file.lastModified, type: file.type }) : + file; + resolve(newFile); + }); + }); +} diff --git a/ui/shared/forms/inputs/select/FancySelect.pw.tsx b/ui/shared/forms/inputs/select/FancySelect.pw.tsx new file mode 100644 index 0000000000..1c6ebcfcbd --- /dev/null +++ b/ui/shared/forms/inputs/select/FancySelect.pw.tsx @@ -0,0 +1,100 @@ +import _noop from 'lodash/noop'; +import React from 'react'; + +import { test, expect } from 'playwright/lib'; + +import FancySelect from './FancySelect'; + +const OPTIONS = [ + { value: 'v0.8.17+commit.8df45f5f', label: 'v0.8.17+commit.8df45f5f' }, + { value: 'v0.8.16+commit.07a7930e', label: 'v0.8.16+commit.07a7930e' }, + { value: 'v0.8.15+commit.e14f2714', label: 'v0.8.15+commit.e14f2714' }, +]; + +test.use({ viewport: { width: 500, height: 300 } }); + +const defaultProps = { + options: OPTIONS, + isRequired: true, + placeholder: 'Compiler', + name: 'compiler', + onChange: _noop, +}; + +[ 'md' as const, 'lg' as const ].forEach((size) => { + test.describe(`size ${ size } +@dark-mode`, () => { + test('empty', async({ render, page }) => { + const component = await render( + , + ); + + await expect(component).toHaveScreenshot(); + + await component.getByLabel(/compiler/i).focus(); + await component.getByLabel(/compiler/i).type('1'); + await expect(page).toHaveScreenshot(); + }); + + test('filled', async({ render }) => { + const component = await render( + , + ); + + await expect(component).toHaveScreenshot(); + }); + + test('error', async({ render }) => { + const component = await render( + , + ); + + await expect(component).toHaveScreenshot(); + + await component.getByLabel(/compiler/i).focus(); + await component.getByLabel(/compiler/i).type('1'); + await expect(component).toHaveScreenshot(); + }); + + test('disabled', async({ render }) => { + const component = await render( + , + ); + + await expect(component).toHaveScreenshot(); + }); + + test('read-only', async({ render }) => { + const component = await render( + , + ); + + await expect(component).toHaveScreenshot(); + }); + }); +}); diff --git a/ui/shared/FancySelect/FancySelect.tsx b/ui/shared/forms/inputs/select/FancySelect.tsx similarity index 90% rename from ui/shared/FancySelect/FancySelect.tsx rename to ui/shared/forms/inputs/select/FancySelect.tsx index 4f56d7811c..27274f2e79 100644 --- a/ui/shared/FancySelect/FancySelect.tsx +++ b/ui/shared/forms/inputs/select/FancySelect.tsx @@ -6,8 +6,8 @@ import type { FieldError, FieldErrorsImpl, Merge } from 'react-hook-form'; import type { Option } from './types'; -import { getChakraStyles } from 'ui/shared/FancySelect/utils'; -import InputPlaceholder from 'ui/shared/InputPlaceholder'; +import FormInputPlaceholder from 'ui/shared/forms/inputs/FormInputPlaceholder'; +import { getChakraStyles } from 'ui/shared/forms/inputs/select/utils'; interface CommonProps { error?: Merge> | undefined; @@ -24,7 +24,7 @@ interface AsyncSelectProps extends AsyncProps onChange: (newValue: SingleValue @@ -48,7 +48,7 @@ const Pagination = ({ page, onNextPageClick, onPrevPageClick, resetPage, hasPage ); @@ -80,21 +66,36 @@ type RadioButtonGroupProps = { name: string; defaultValue: string; options: Array<{ value: T } & RadioItemProps>; -} + autoWidth?: boolean; + className?: string; + isLoading?: boolean; +}; -const RadioButtonGroup = ({ onChange, name, defaultValue, options }: RadioButtonGroupProps) => { +const RadioButtonGroup = ({ onChange, name, defaultValue, options, autoWidth = false, className, isLoading }: RadioButtonGroupProps) => { const { getRootProps, getRadioProps } = useRadioGroup({ name, defaultValue, onChange }); const group = getRootProps(); return ( - - { options.map((option) => { - const props = getRadioProps({ value: option.value }); - return ; - }) } - + + + { options.map((option) => { + const props = getRadioProps({ value: option.value }); + return ; + }) } + + ); }; -export default RadioButtonGroup; +const WrappedRadioButtonGroup = chakra(RadioButtonGroup); +type WrappedComponent = (props: RadioButtonGroupProps & ChakraProps) => React.JSX.Element; + +export default React.memo(WrappedRadioButtonGroup) as WrappedComponent; diff --git a/ui/shared/radioButtonGroup/__screenshots__/RadioButtonGroup.pw.tsx_default_radio-button-group-1.png b/ui/shared/radioButtonGroup/__screenshots__/RadioButtonGroup.pw.tsx_default_radio-button-group-1.png index 212b0b3e89..97a17fcc54 100644 Binary files a/ui/shared/radioButtonGroup/__screenshots__/RadioButtonGroup.pw.tsx_default_radio-button-group-1.png and b/ui/shared/radioButtonGroup/__screenshots__/RadioButtonGroup.pw.tsx_default_radio-button-group-1.png differ diff --git a/ui/shared/reCaptcha/ReCaptcha.tsx b/ui/shared/reCaptcha/ReCaptcha.tsx new file mode 100644 index 0000000000..83152d7266 --- /dev/null +++ b/ui/shared/reCaptcha/ReCaptcha.tsx @@ -0,0 +1,24 @@ +import React from 'react'; +import ReCaptcha from 'react-google-recaptcha'; + +import config from 'configs/app'; + +interface Props { + disabledFeatureMessage?: React.ReactNode; +} + +const ReCaptchaInvisible = ({ disabledFeatureMessage }: Props, ref: React.Ref) => { + if (!config.services.reCaptchaV2.siteKey) { + return disabledFeatureMessage ?? null; + } + + return ( + + ); +}; + +export default React.forwardRef(ReCaptchaInvisible); diff --git a/ui/shared/reCaptcha/useReCaptcha.tsx b/ui/shared/reCaptcha/useReCaptcha.tsx new file mode 100644 index 0000000000..f4a5713854 --- /dev/null +++ b/ui/shared/reCaptcha/useReCaptcha.tsx @@ -0,0 +1,39 @@ +import React from 'react'; +import type ReCAPTCHA from 'react-google-recaptcha'; + +export default function useReCaptcha() { + const ref = React.useRef(null); + const rejectCb = React.useRef<((error: Error) => void) | null>(null); + + const [ isOpen, setIsOpen ] = React.useState(false); + + const executeAsync = React.useCallback(async() => { + setIsOpen(true); + const tokenPromise = ref.current?.executeAsync() || Promise.reject(new Error('Unable to execute ReCaptcha')); + const modalOpenPromise = new Promise((resolve, reject) => { + rejectCb.current = reject; + }); + + return Promise.race([ tokenPromise, modalOpenPromise ]); + }, [ ref ]); + + const handleContainerClick = React.useCallback(() => { + setIsOpen(false); + rejectCb.current?.(new Error('ReCaptcha is not solved')); + }, []); + + React.useEffect(() => { + if (!isOpen) { + return; + } + + const container = window.document.querySelector('div:has(div):has(iframe[title="recaptcha challenge expires in two minutes"])'); + container?.addEventListener('click', handleContainerClick); + + return () => { + container?.removeEventListener('click', handleContainerClick); + }; + }, [ isOpen, handleContainerClick ]); + + return React.useMemo(() => ({ ref, executeAsync }), [ ref, executeAsync ]); +} diff --git a/ui/shared/search/utils.ts b/ui/shared/search/utils.ts index 652c973ebd..31da3bc447 100644 --- a/ui/shared/search/utils.ts +++ b/ui/shared/search/utils.ts @@ -1,9 +1,9 @@ -import type { SearchResultItem } from 'types/api/search'; import type { MarketplaceAppOverview } from 'types/client/marketplace'; +import type { SearchResultItem } from 'types/client/search'; import config from 'configs/app'; -export type ApiCategory = 'token' | 'nft' | 'address' | 'public_tag' | 'transaction' | 'block' | 'user_operation'; +export type ApiCategory = 'token' | 'nft' | 'address' | 'public_tag' | 'transaction' | 'block' | 'user_operation' | 'blob' | 'domain'; export type Category = ApiCategory | 'app'; export type ItemsCategoriesMap = @@ -13,12 +13,12 @@ Record<'app', Array>; export type SearchResultAppItem = { type: 'app'; app: MarketplaceAppOverview; -} +}; -export const searchCategories: Array<{id: Category; title: string }> = [ - { id: 'app', title: 'Apps' }, - { id: 'token', title: 'Tokens (ERC-20)' }, - { id: 'nft', title: 'NFTs (ERC-721 & 1155)' }, +export const searchCategories: Array<{ id: Category; title: string }> = [ + { id: 'app', title: 'DApps' }, + { id: 'token', title: `Tokens (${ config.chain.tokenStandard }-20)` }, + { id: 'nft', title: `NFTs (${ config.chain.tokenStandard }-721 & 1155)` }, { id: 'address', title: 'Addresses' }, { id: 'public_tag', title: 'Public tags' }, { id: 'transaction', title: 'Transactions' }, @@ -29,8 +29,17 @@ if (config.features.userOps.isEnabled) { searchCategories.push({ id: 'user_operation', title: 'User operations' }); } +if (config.features.dataAvailability.isEnabled) { + searchCategories.push({ id: 'blob', title: 'Blobs' }); +} + +if (config.features.nameService.isEnabled) { + searchCategories.unshift({ id: 'domain', title: 'Names' }); +} + export const searchItemTitles: Record = { - app: { itemTitle: 'App', itemTitleShort: 'App' }, + app: { itemTitle: 'DApp', itemTitleShort: 'App' }, + domain: { itemTitle: 'Name', itemTitleShort: 'Name' }, token: { itemTitle: 'Token', itemTitleShort: 'Token' }, nft: { itemTitle: 'NFT', itemTitleShort: 'NFT' }, address: { itemTitle: 'Address', itemTitleShort: 'Address' }, @@ -38,6 +47,7 @@ export const searchItemTitles: Record { + isOpen: boolean; + onToggle: () => void; + value: Value; +} + +export interface Props { + className?: string; + isLoading?: boolean; + options: Array>; + name: string; + defaultValue?: Value; + onChange: (value: Value) => void; + children?: (props: InjectedProps) => React.ReactNode; +} + +const Select = ({ className, isLoading, options, name, defaultValue, onChange, children }: Props) => { + const { isOpen, onToggle, onClose } = useDisclosure(); + + const handleChange = React.useCallback((value: Value) => { + onChange(value); + onClose(); + }, [ onChange, onClose ]); + + const { value, getRootProps, getRadioProps, setValue } = useRadioGroup({ + name, + defaultValue, + onChange: handleChange, + }); + + React.useEffect(() => { + if (defaultValue) { + setValue(defaultValue); + } + }, [ defaultValue, setValue ]); + + return ( + + + { children?.({ isOpen, onToggle, value: value as Value }) || ( + option.value === value)?.label || String(value) } + /> + ) } + + + + ); +}; + +export default React.memo(chakra(Select)); diff --git a/ui/shared/select/SelectButton.tsx b/ui/shared/select/SelectButton.tsx new file mode 100644 index 0000000000..0bcbf202c5 --- /dev/null +++ b/ui/shared/select/SelectButton.tsx @@ -0,0 +1,46 @@ +import { Box, Button, Skeleton } from '@chakra-ui/react'; +import React from 'react'; + +import IconSvg from 'ui/shared/IconSvg'; + +interface Props { + className?: string; + onClick: () => void; + isOpen: boolean; + isLoading?: boolean; + label: string; +} + +const SelectButton = ({ className, onClick, isOpen, isLoading, label }: Props, ref: React.Ref) => { + + if (isLoading) { + return ; + } + + return ( + + ); +}; + +export default React.forwardRef(SelectButton); diff --git a/ui/shared/select/SelectContent.tsx b/ui/shared/select/SelectContent.tsx new file mode 100644 index 0000000000..2241a8454d --- /dev/null +++ b/ui/shared/select/SelectContent.tsx @@ -0,0 +1,36 @@ +import type { useRadioGroup } from '@chakra-ui/react'; +import { PopoverBody, PopoverContent } from '@chakra-ui/react'; +import React from 'react'; + +import type { SelectOption as TSelectOption } from './types'; + +import SelectOption from './SelectOption'; + +interface Props { + options: Array; + getRootProps: ReturnType['getRootProps']; + getRadioProps: ReturnType['getRadioProps']; + value: string | number; +} + +const SelectContent = ({ options, getRootProps, getRadioProps, value }: Props) => { + + const root = getRootProps(); + + return ( + + + { options.map((option) => { + const radio = getRadioProps({ value: option.value }); + return ( + + { option.label } + + ); + }) } + + + ); +}; + +export default React.memo(SelectContent); diff --git a/ui/shared/select/SelectOption.tsx b/ui/shared/select/SelectOption.tsx new file mode 100644 index 0000000000..c8fe1ba962 --- /dev/null +++ b/ui/shared/select/SelectOption.tsx @@ -0,0 +1,40 @@ +import type { UseRadioProps } from '@chakra-ui/react'; +import { Box, useRadio, useColorModeValue } from '@chakra-ui/react'; +import React from 'react'; + +import IconSvg from '../IconSvg'; + +interface Props extends UseRadioProps { + children: React.ReactNode; +} + +const SelectOption = (props: Props) => { + const { getInputProps, getRadioProps } = useRadio(props); + + const input = getInputProps(); + const checkbox = getRadioProps(); + const bgColorHover = useColorModeValue('blue.50', 'whiteAlpha.100'); + + return ( + + { props.isChecked ? : } + + + { props.children } + + + ); +}; + +export default React.memo(SelectOption); diff --git a/ui/shared/select/types.ts b/ui/shared/select/types.ts new file mode 100644 index 0000000000..7ec98a9086 --- /dev/null +++ b/ui/shared/select/types.ts @@ -0,0 +1,4 @@ +export interface SelectOption { + value: Value | undefined; + label: string; +} diff --git a/ui/shared/solidityscanReport/SolidityscanReportButton.tsx b/ui/shared/solidityscanReport/SolidityscanReportButton.tsx new file mode 100644 index 0000000000..c099ef70b6 --- /dev/null +++ b/ui/shared/solidityscanReport/SolidityscanReportButton.tsx @@ -0,0 +1,62 @@ +import { Button, Spinner, Tooltip, useColorModeValue, chakra } from '@chakra-ui/react'; +import React from 'react'; + +import useIsMobile from 'lib/hooks/useIsMobile'; +import usePreventFocusAfterModalClosing from 'lib/hooks/usePreventFocusAfterModalClosing'; +import IconSvg from 'ui/shared/IconSvg'; + +import useScoreLevelAndColor from './useScoreLevelAndColor'; + +interface Props { + score: number; + isLoading?: boolean; + onlyIcon?: boolean; + onClick?: () => void; + label?: string | React.ReactElement; + isActive: boolean; + className?: string; +} + +const SolidityscanReportButton = ( + { score, isLoading, onlyIcon, onClick, label = 'Security score', isActive, className }: Props, + ref: React.ForwardedRef, +) => { + const { scoreColor } = useScoreLevelAndColor(score); + const colorLoading = useColorModeValue('gray.300', 'gray.600'); + const isMobile = useIsMobile(); + const onFocusCapture = usePreventFocusAfterModalClosing(); + + return ( + + + + ); +}; + +export default chakra(React.forwardRef(SolidityscanReportButton)); diff --git a/ui/shared/solidityscanReport/SolidityscanReportDetails.tsx b/ui/shared/solidityscanReport/SolidityscanReportDetails.tsx new file mode 100644 index 0000000000..210056f514 --- /dev/null +++ b/ui/shared/solidityscanReport/SolidityscanReportDetails.tsx @@ -0,0 +1,65 @@ +import { Box, Flex, Text, Grid, useColorModeValue, chakra } from '@chakra-ui/react'; +import React from 'react'; + +import type { SolidityScanReportSeverityDistribution } from 'lib/solidityScan/schema'; + +type DistributionItem = { + id: keyof SolidityScanReportSeverityDistribution; + name: string; + color: string; +}; + +const DISTRIBUTION_ITEMS: Array = [ + { id: 'critical', name: 'Critical', color: '#891F11' }, + { id: 'high', name: 'High', color: '#EC672C' }, + { id: 'medium', name: 'Medium', color: '#FBE74D' }, + { id: 'low', name: 'Low', color: '#68C88E' }, + { id: 'informational', name: 'Informational', color: '#A3AEBE' }, + { id: 'gas', name: 'Gas', color: '#A47585' }, +]; + +interface Props { + vulnerabilities: SolidityScanReportSeverityDistribution; + vulnerabilitiesCount: number; +} + +type ItemProps = { + item: DistributionItem; + vulnerabilities: SolidityScanReportSeverityDistribution; + vulnerabilitiesCount: number; +}; + +const SolidityScanReportItem = ({ item, vulnerabilities, vulnerabilitiesCount }: ItemProps) => { + const bgBar = useColorModeValue('blackAlpha.50', 'whiteAlpha.50'); + const yetAnotherGrayColor = useColorModeValue('gray.400', 'gray.500'); + const vulnerability = vulnerabilities[item.id]; + + if (vulnerability === undefined) { + return null; + } + + return ( + <> + + + { item.name } + 0 ? 'text' : yetAnotherGrayColor }>{ vulnerabilities[item.id] } + + + + + + ); +}; + +const SolidityscanReportDetails = ({ vulnerabilities, vulnerabilitiesCount }: Props) => { + return ( + + { DISTRIBUTION_ITEMS.map(item => ( + + )) } + + ); +}; + +export default chakra(SolidityscanReportDetails); diff --git a/ui/shared/solidityscanReport/SolidityscanReportScore.tsx b/ui/shared/solidityscanReport/SolidityscanReportScore.tsx new file mode 100644 index 0000000000..56bd296630 --- /dev/null +++ b/ui/shared/solidityscanReport/SolidityscanReportScore.tsx @@ -0,0 +1,45 @@ +import { Box, Flex, Text, chakra, Center, useColorModeValue } from '@chakra-ui/react'; +import React from 'react'; + +import IconSvg from 'ui/shared/IconSvg'; + +import useScoreLevelAndColor from './useScoreLevelAndColor'; + +interface Props { + className?: string; + score: number; +} + +const SolidityscanReportScore = ({ className, score }: Props) => { + const { scoreLevel, scoreColor } = useScoreLevelAndColor(score); + + const chartGrayColor = useColorModeValue('gray.100', 'gray.700'); + const yetAnotherGrayColor = useColorModeValue('gray.400', 'gray.500'); + const popoverBgColor = useColorModeValue('white', 'gray.900'); + + return ( + + +
+ +
+
+ + + { score } + / 100 + + Security score is { scoreLevel } + +
+ ); +}; + +export default chakra(SolidityscanReportScore); diff --git a/ui/shared/solidityscanReport/useScoreLevelAndColor.ts b/ui/shared/solidityscanReport/useScoreLevelAndColor.ts new file mode 100644 index 0000000000..b61d2a9109 --- /dev/null +++ b/ui/shared/solidityscanReport/useScoreLevelAndColor.ts @@ -0,0 +1,21 @@ +import { useColorModeValue } from '@chakra-ui/react'; + +export default function useScoreLevelAndColor(score: number) { + const greatScoreColor = useColorModeValue('green.600', 'green.400'); + const averageScoreColor = useColorModeValue('purple.600', 'purple.400'); + const lowScoreColor = useColorModeValue('red.600', 'red.400'); + + let scoreColor; + let scoreLevel; + if (score >= 80) { + scoreColor = greatScoreColor; + scoreLevel = 'GREAT'; + } else if (score >= 30) { + scoreColor = averageScoreColor; + scoreLevel = 'AVERAGE'; + } else { + scoreColor = lowScoreColor; + scoreLevel = 'LOW'; + } + return { scoreColor, scoreLevel }; +} diff --git a/ui/shared/sort/ButtonDesktop.tsx b/ui/shared/sort/ButtonDesktop.tsx new file mode 100644 index 0000000000..e127a5e9db --- /dev/null +++ b/ui/shared/sort/ButtonDesktop.tsx @@ -0,0 +1,57 @@ +import { + Box, + useColorModeValue, + Button, + Skeleton, + chakra, +} from '@chakra-ui/react'; +import React from 'react'; + +import IconSvg from 'ui/shared/IconSvg'; + +type ButtonProps = { + isActive: boolean; + onClick: () => void; + isLoading?: boolean; + children: React.ReactNode; + className?: string; +}; + +const ButtonDesktop = ({ children, isActive, onClick, isLoading, className }: ButtonProps, ref: React.ForwardedRef) => { + const primaryColor = useColorModeValue('blackAlpha.800', 'whiteAlpha.800'); + const secondaryColor = useColorModeValue('blackAlpha.600', 'whiteAlpha.600'); + + return ( + + + + ); +}; + +export default chakra(React.forwardRef(ButtonDesktop)); diff --git a/ui/shared/sort/ButtonMobile.tsx b/ui/shared/sort/ButtonMobile.tsx new file mode 100644 index 0000000000..7a898c9135 --- /dev/null +++ b/ui/shared/sort/ButtonMobile.tsx @@ -0,0 +1,35 @@ +import { IconButton, chakra, Skeleton } from '@chakra-ui/react'; +import React from 'react'; + +import IconSvg from 'ui/shared/IconSvg'; + +type Props = { + onClick: () => void; + isActive: boolean; + className?: string; + isLoading?: boolean; +}; + +const ButtonMobile = ({ onClick, isActive, className, isLoading }: Props, ref: React.ForwardedRef) => { + if (isLoading) { + return ; + } + + return ( + } + aria-label="sort" + size="sm" + variant="outline" + colorScheme="gray" + minWidth="36px" + onClick={ onClick } + isActive={ isActive } + display="flex" + className={ className } + /> + ); +}; + +export default chakra(React.forwardRef(ButtonMobile)); diff --git a/ui/shared/sort/Sort.tsx b/ui/shared/sort/Sort.tsx index 37b378abf4..fb0aba87a6 100644 --- a/ui/shared/sort/Sort.tsx +++ b/ui/shared/sort/Sort.tsx @@ -1,58 +1,38 @@ -import { - chakra, - Menu, - MenuButton, - MenuList, - MenuOptionGroup, - MenuItemOption, - useDisclosure, -} from '@chakra-ui/react'; +import { chakra } from '@chakra-ui/react'; import React from 'react'; -import SortButton from './SortButton'; +import type { SelectOption } from 'ui/shared/select/types'; -export interface Option { - title: string; - id: Sort | undefined; -} +import useIsMobile from 'lib/hooks/useIsMobile'; +import Select, { type Props as SelectProps } from 'ui/shared/select/Select'; -interface Props { - options: Array>; - sort: Sort | undefined; - setSort: (value: Sort | undefined) => void; - isLoading?: boolean; -} +import SortButtonDesktop from './ButtonDesktop'; +import SortButtonMobile from './ButtonMobile'; -const Sort = ({ sort, setSort, options, isLoading }: Props) => { - const { isOpen, onToggle } = useDisclosure(); +type Props = Omit, 'children'>; - const setSortingFromMenu = React.useCallback((val: string | Array) => { - const value = val as Sort | Array; - setSort(Array.isArray(value) ? value[0] : value); - }, [ setSort ]); +const Sort = ({ name, options, isLoading, onChange, defaultValue }: Props) => { + const isMobile = useIsMobile(false); return ( - - - - - - - { options.map((option) => ( - - { option.title } - - )) } - - - + ); }; diff --git a/ui/shared/sort/SortButton.tsx b/ui/shared/sort/SortButton.tsx deleted file mode 100644 index 903bb1fcec..0000000000 --- a/ui/shared/sort/SortButton.tsx +++ /dev/null @@ -1,34 +0,0 @@ -import { IconButton, chakra, Skeleton } from '@chakra-ui/react'; -import React from 'react'; - -import IconSvg from 'ui/shared/IconSvg'; - -type Props = { - onClick: () => void; - isActive: boolean; - className?: string; - isLoading?: boolean; -} - -const SortButton = ({ onClick, isActive, className, isLoading }: Props) => { - if (isLoading) { - return ; - } - - return ( - } - aria-label="sort" - size="sm" - variant="outline" - colorScheme="gray-dark" - minWidth="36px" - onClick={ onClick } - isActive={ isActive } - display="flex" - className={ className } - /> - ); -}; - -export default chakra(SortButton); diff --git a/ui/shared/sort/getNextSortOrder.tsx b/ui/shared/sort/getNextSortOrder.tsx new file mode 100644 index 0000000000..e69de29bb2 diff --git a/ui/shared/sort/getNextSortValue.ts b/ui/shared/sort/getNextSortValue.ts index 9e29662971..06b4153fb0 100644 --- a/ui/shared/sort/getNextSortValue.ts +++ b/ui/shared/sort/getNextSortValue.ts @@ -1,10 +1,19 @@ export default function getNextSortValue( - sortSequence: Record>, field: SortField, + sortSequence: Record>, field: SortField, ) { return (prevValue: Sort | undefined) => { const sequence = sortSequence[field]; - const curIndex = sequence.findIndex((sort) => sort === prevValue); - const nextIndex = curIndex + 1 > sequence.length - 1 ? 0 : curIndex + 1; - return sequence[nextIndex]; + return getNextValueFromSequence(sequence, prevValue); }; } + +export function getNextValueFromSequence(sequence: Array, prevValue: T) { + const curIndex = sequence.findIndex((val) => val === prevValue); + const nextIndex = curIndex + 1 > sequence.length - 1 ? 0 : curIndex + 1; + return sequence[nextIndex]; +} + +// asc desc undefined +type Order = 'asc' | 'desc' | undefined; +const sequence: Array = [ 'desc', 'asc', undefined ]; +export const getNextOrderValue = (getNextValueFromSequence).bind(undefined, sequence); diff --git a/ui/shared/sort/getSortParamsFromQuery.ts b/ui/shared/sort/getSortParamsFromQuery.ts new file mode 100644 index 0000000000..fd094917a9 --- /dev/null +++ b/ui/shared/sort/getSortParamsFromQuery.ts @@ -0,0 +1,23 @@ +import type { Query } from 'nextjs-routes'; + +import getQueryParamString from 'lib/router/getQueryParamString'; + +export default function getSortParamsFromQuery(query: Query, sortOptions: Record>) { + if (!query.sort || !query.order) { + return undefined; + } + + const sortStr = getQueryParamString(query.sort); + + if (!Object.keys(sortOptions).includes(sortStr)) { + return undefined; + } + + const orderStr = getQueryParamString(query.order); + + if (!sortOptions[sortStr].includes(orderStr)) { + return undefined; + } + + return ({ sort: sortStr, order: orderStr } as T); +} diff --git a/ui/shared/sort/getSortValueFromQuery.ts b/ui/shared/sort/getSortValueFromQuery.ts index 9e8f823c60..4bb9fc4a78 100644 --- a/ui/shared/sort/getSortValueFromQuery.ts +++ b/ui/shared/sort/getSortValueFromQuery.ts @@ -1,14 +1,14 @@ -import type { Query } from 'nextjs-routes'; +import type { SelectOption } from 'ui/shared/select/types'; -import type { Option } from 'ui/shared/sort/Sort'; +import type { Query } from 'nextjs-routes'; -export default function getSortValueFromQuery(query: Query, sortOptions: Array>) { +export default function getSortValueFromQuery(query: Query, sortOptions: Array>) { if (!query.sort || !query.order) { return undefined; } const str = query.sort + '-' + query.order; - if (sortOptions.map(option => option.id).includes(str as SortValue)) { + if (sortOptions.map(option => option.value).includes(str as SortValue)) { return str as SortValue; } } diff --git a/ui/shared/stats/StatsWidget.pw.tsx b/ui/shared/stats/StatsWidget.pw.tsx new file mode 100644 index 0000000000..cc9fad8da4 --- /dev/null +++ b/ui/shared/stats/StatsWidget.pw.tsx @@ -0,0 +1,61 @@ +import React from 'react'; + +import { test, expect } from 'playwright/lib'; + +import StatsWidget from './StatsWidget'; + +test.use({ viewport: { width: 300, height: 100 } }); + +test('with positive diff +@dark-mode', async({ render }) => { + const component = await render( + , + ); + + await expect(component).toHaveScreenshot(); +}); + +// according to current logic we don't show diff if it's negative +test('with negative diff', async({ render }) => { + const component = await render( + , + ); + + await expect(component).toHaveScreenshot(); +}); + +test('loading state', async({ render }) => { + const component = await render( + , + ); + + await expect(component).toHaveScreenshot(); +}); + +test('with period only', async({ render }) => { + const component = await render( + , + ); + + await expect(component).toHaveScreenshot(); +}); diff --git a/ui/shared/stats/StatsWidget.tsx b/ui/shared/stats/StatsWidget.tsx new file mode 100644 index 0000000000..2c52639068 --- /dev/null +++ b/ui/shared/stats/StatsWidget.tsx @@ -0,0 +1,130 @@ +import { Box, Flex, Text, Skeleton, useColorModeValue, chakra } from '@chakra-ui/react'; +import NextLink from 'next/link'; +import React from 'react'; + +import type { Route } from 'nextjs-routes'; + +import Hint from 'ui/shared/Hint'; +import IconSvg, { type IconName } from 'ui/shared/IconSvg'; +import TruncatedValue from 'ui/shared/TruncatedValue'; + +export type Props = { + className?: string; + label: string; + value: string | React.ReactNode; + valuePrefix?: string; + valuePostfix?: string; + hint?: string | React.ReactNode; + isLoading?: boolean; + diff?: string | number; + diffFormatted?: string; + diffPeriod?: '24h'; + period?: '1h' | '24h'; + href?: Route; + icon?: IconName; +}; + +const Container = ({ href, children }: { href?: Route; children: React.JSX.Element }) => { + if (href) { + return ( + + { children } + + ); + } + + return children; +}; + +const StatsWidget = ({ + className, + icon, + label, + value, + valuePrefix, + valuePostfix, + isLoading, + hint, + diff, + diffPeriod = '24h', + diffFormatted, + period, + href, +}: Props) => { + const bgColor = useColorModeValue('gray.50', 'whiteAlpha.100'); + const skeletonBgColor = useColorModeValue('blackAlpha.50', 'whiteAlpha.50'); + const hintColor = useColorModeValue('gray.600', 'gray.400'); + + return ( + + + { icon && ( + + ) } + + +

{ label }

+
+ + { valuePrefix && { valuePrefix } } + { typeof value === 'string' ? ( + + ) : ( + value + ) } + { valuePostfix && { valuePostfix } } + { diff && Number(diff) > 0 && ( + <> + + +{ diffFormatted || Number(diff).toLocaleString() } + + ({ diffPeriod }) + + ) } + { period && ({ period }) } + +
+ { typeof hint === 'string' ? ( + + + + ) : hint } +
+
+ ); +}; + +export default chakra(StatsWidget); diff --git a/ui/shared/stats/__screenshots__/StatsWidget.pw.tsx_dark-color-mode_with-positive-diff-dark-mode-1.png b/ui/shared/stats/__screenshots__/StatsWidget.pw.tsx_dark-color-mode_with-positive-diff-dark-mode-1.png new file mode 100644 index 0000000000..b6a31ee043 Binary files /dev/null and b/ui/shared/stats/__screenshots__/StatsWidget.pw.tsx_dark-color-mode_with-positive-diff-dark-mode-1.png differ diff --git a/ui/shared/stats/__screenshots__/StatsWidget.pw.tsx_default_loading-state-1.png b/ui/shared/stats/__screenshots__/StatsWidget.pw.tsx_default_loading-state-1.png new file mode 100644 index 0000000000..e1f7bd8279 Binary files /dev/null and b/ui/shared/stats/__screenshots__/StatsWidget.pw.tsx_default_loading-state-1.png differ diff --git a/ui/shared/stats/__screenshots__/StatsWidget.pw.tsx_default_with-negative-diff-1.png b/ui/shared/stats/__screenshots__/StatsWidget.pw.tsx_default_with-negative-diff-1.png new file mode 100644 index 0000000000..dfdb9fcf0a Binary files /dev/null and b/ui/shared/stats/__screenshots__/StatsWidget.pw.tsx_default_with-negative-diff-1.png differ diff --git a/ui/shared/stats/__screenshots__/StatsWidget.pw.tsx_default_with-period-only-1.png b/ui/shared/stats/__screenshots__/StatsWidget.pw.tsx_default_with-period-only-1.png new file mode 100644 index 0000000000..56a4b17ea2 Binary files /dev/null and b/ui/shared/stats/__screenshots__/StatsWidget.pw.tsx_default_with-period-only-1.png differ diff --git a/ui/shared/stats/__screenshots__/StatsWidget.pw.tsx_default_with-positive-diff-dark-mode-1.png b/ui/shared/stats/__screenshots__/StatsWidget.pw.tsx_default_with-positive-diff-dark-mode-1.png new file mode 100644 index 0000000000..46baf19c3c Binary files /dev/null and b/ui/shared/stats/__screenshots__/StatsWidget.pw.tsx_default_with-positive-diff-dark-mode-1.png differ diff --git a/ui/shared/statusTag/ArbitrumL2MessageStatus.tsx b/ui/shared/statusTag/ArbitrumL2MessageStatus.tsx new file mode 100644 index 0000000000..85bc6795ff --- /dev/null +++ b/ui/shared/statusTag/ArbitrumL2MessageStatus.tsx @@ -0,0 +1,47 @@ +import React from 'react'; + +import type { ArbitrumL2MessagesItem } from 'types/api/arbitrumL2'; + +import type { StatusTagType } from './StatusTag'; +import StatusTag from './StatusTag'; + +export interface Props { + status: ArbitrumL2MessagesItem['status']; + isLoading?: boolean; +} + +const ArbitrumL2MessageStatus = ({ status, isLoading }: Props) => { + let type: StatusTagType; + let text: string; + + switch (status) { + case 'relayed': { + type = 'ok'; + text = 'Relayed'; + break; + } + case 'confirmed': { + type = 'ok'; + text = 'Ready for relay'; + break; + } + case 'sent': { + type = 'pending'; + text = 'Waiting'; + break; + } + case 'initiated': { + type = 'pending'; + text = 'Pending'; + break; + } + default: + type = 'pending'; + text = status; + break; + } + + return ; +}; + +export default ArbitrumL2MessageStatus; diff --git a/ui/shared/statusTag/ArbitrumL2TxnBatchStatus.tsx b/ui/shared/statusTag/ArbitrumL2TxnBatchStatus.tsx new file mode 100644 index 0000000000..81536cdf5e --- /dev/null +++ b/ui/shared/statusTag/ArbitrumL2TxnBatchStatus.tsx @@ -0,0 +1,28 @@ +import React from 'react'; + +import type { ArbitrumL2TxnBatchesItem } from 'types/api/arbitrumL2'; + +import type { StatusTagType } from './StatusTag'; +import StatusTag from './StatusTag'; + +export interface Props { + status: ArbitrumL2TxnBatchesItem['commitment_transaction']['status']; + isLoading?: boolean; +} + +const ArbitrumL2TxnBatchStatus = ({ status, isLoading }: Props) => { + let type: StatusTagType; + + switch (status) { + case 'finalized': + type = 'ok'; + break; + default: + type = 'pending'; + break; + } + + return ; +}; + +export default ArbitrumL2TxnBatchStatus; diff --git a/ui/shared/statusTag/ScrollL2TxnBatchStatus.tsx b/ui/shared/statusTag/ScrollL2TxnBatchStatus.tsx new file mode 100644 index 0000000000..ca6ffbd8a5 --- /dev/null +++ b/ui/shared/statusTag/ScrollL2TxnBatchStatus.tsx @@ -0,0 +1,26 @@ +import React from 'react'; + +import type { StatusTagType } from './StatusTag'; +import StatusTag from './StatusTag'; + +export interface Props { + status: 'Finalized' | 'Committed'; + isLoading?: boolean; +} + +const ZkEvmL2TxnBatchStatus = ({ status, isLoading }: Props) => { + let type: StatusTagType; + + switch (status) { + case 'Finalized': + type = 'ok'; + break; + default: + type = 'pending'; + break; + } + + return ; +}; + +export default ZkEvmL2TxnBatchStatus; diff --git a/ui/shared/statusTag/StatusTag.pw.tsx b/ui/shared/statusTag/StatusTag.pw.tsx index d289c54b5c..0aa9e64356 100644 --- a/ui/shared/statusTag/StatusTag.pw.tsx +++ b/ui/shared/statusTag/StatusTag.pw.tsx @@ -1,37 +1,20 @@ -import { test, expect } from '@playwright/experimental-ct-react'; import React from 'react'; -import TestApp from 'playwright/TestApp'; +import { test, expect } from 'playwright/lib'; import StatusTag from './StatusTag'; -test('ok status', async({ page, mount }) => { - await mount( - - - , - ); - +test('ok status', async({ page, render }) => { + await render(); await expect(page).toHaveScreenshot({ clip: { x: 0, y: 0, width: 75, height: 30 } }); }); -test('error status', async({ page, mount }) => { - await mount( - - - , - ); - +test('error status', async({ page, render }) => { + await render(); await expect(page).toHaveScreenshot({ clip: { x: 0, y: 0, width: 75, height: 30 } }); - }); -test('pending status', async({ page, mount }) => { - await mount( - - - , - ); - +test('pending status', async({ page, render }) => { + await render(); await expect(page).toHaveScreenshot({ clip: { x: 0, y: 0, width: 75, height: 30 } }); }); diff --git a/ui/shared/statusTag/StatusTag.tsx b/ui/shared/statusTag/StatusTag.tsx index 8a9d8a3519..7a01aa5a5c 100644 --- a/ui/shared/statusTag/StatusTag.tsx +++ b/ui/shared/statusTag/StatusTag.tsx @@ -1,6 +1,7 @@ -import { TagLabel, Tooltip } from '@chakra-ui/react'; +import { TagLabel, Tooltip, chakra } from '@chakra-ui/react'; import React from 'react'; +import capitalizeFirstLetter from 'lib/capitalizeFirstLetter'; import Tag from 'ui/shared/chakra/Tag'; import type { IconName } from 'ui/shared/IconSvg'; import IconSvg from 'ui/shared/IconSvg'; @@ -12,12 +13,15 @@ export interface Props { text: string; errorText?: string | null; isLoading?: boolean; + className?: string; } -const StatusTag = ({ type, text, errorText, isLoading }: Props) => { +const StatusTag = ({ type, text, errorText, isLoading, className }: Props) => { let icon: IconName; let colorScheme; + const capitalizedText = capitalizeFirstLetter(text); + switch (type) { case 'ok': icon = 'status/success'; @@ -29,20 +33,18 @@ const StatusTag = ({ type, text, errorText, isLoading }: Props) => { break; case 'pending': icon = 'status/pending'; - // FIXME: it's not gray on mockups - // need to implement new color scheme or redefine colors here colorScheme = 'gray'; break; } return ( - - - { text } + + + { capitalizedText } ); }; -export default StatusTag; +export default chakra(StatusTag); diff --git a/ui/shared/statusTag/ValidatorStabilityStatus.tsx b/ui/shared/statusTag/ValidatorStabilityStatus.tsx new file mode 100644 index 0000000000..93b1bb771f --- /dev/null +++ b/ui/shared/statusTag/ValidatorStabilityStatus.tsx @@ -0,0 +1,23 @@ +import React from 'react'; + +import type { ValidatorStability } from 'types/api/validators'; + +import StatusTag from './StatusTag'; + +interface Props { + state: ValidatorStability['state']; + isLoading?: boolean; +} + +const ValidatorStabilityStatus = ({ state, isLoading }: Props) => { + switch (state) { + case 'active': + return ; + case 'probation': + return ; + case 'inactive': + return ; + } +}; + +export default React.memo(ValidatorStabilityStatus); diff --git a/ui/shared/statusTag/ZkEvmL2TxnBatchStatus.tsx b/ui/shared/statusTag/ZkEvmL2TxnBatchStatus.tsx index f7601bde46..55d98dd7db 100644 --- a/ui/shared/statusTag/ZkEvmL2TxnBatchStatus.tsx +++ b/ui/shared/statusTag/ZkEvmL2TxnBatchStatus.tsx @@ -1,6 +1,6 @@ import React from 'react'; -import type { ZkEvmL2TxnBatchesItem } from 'types/api/zkEvmL2TxnBatches'; +import type { ZkEvmL2TxnBatchesItem } from 'types/api/zkEvmL2'; import type { StatusTagType } from './StatusTag'; import StatusTag from './StatusTag'; diff --git a/ui/shared/statusTag/ZkSyncL2TxnBatchStatus.tsx b/ui/shared/statusTag/ZkSyncL2TxnBatchStatus.tsx new file mode 100644 index 0000000000..7e0536eff4 --- /dev/null +++ b/ui/shared/statusTag/ZkSyncL2TxnBatchStatus.tsx @@ -0,0 +1,28 @@ +import React from 'react'; + +import type { ZkSyncBatchStatus } from 'types/api/zkSyncL2'; + +import type { StatusTagType } from './StatusTag'; +import StatusTag from './StatusTag'; + +export interface Props { + status: ZkSyncBatchStatus; + isLoading?: boolean; +} + +const ZkSyncL2TxnBatchStatus = ({ status, isLoading }: Props) => { + let type: StatusTagType; + + switch (status) { + case 'Executed on L1': + type = 'ok'; + break; + default: + type = 'pending'; + break; + } + + return ; +}; + +export default ZkSyncL2TxnBatchStatus; diff --git a/ui/shared/statusTag/__screenshots__/StatusTag.pw.tsx_default_error-status-1.png b/ui/shared/statusTag/__screenshots__/StatusTag.pw.tsx_default_error-status-1.png index 456a05197e..9391200038 100644 Binary files a/ui/shared/statusTag/__screenshots__/StatusTag.pw.tsx_default_error-status-1.png and b/ui/shared/statusTag/__screenshots__/StatusTag.pw.tsx_default_error-status-1.png differ diff --git a/ui/shared/statusTag/__screenshots__/StatusTag.pw.tsx_default_ok-status-1.png b/ui/shared/statusTag/__screenshots__/StatusTag.pw.tsx_default_ok-status-1.png index 1f91068cef..24dd6061e8 100644 Binary files a/ui/shared/statusTag/__screenshots__/StatusTag.pw.tsx_default_ok-status-1.png and b/ui/shared/statusTag/__screenshots__/StatusTag.pw.tsx_default_ok-status-1.png differ diff --git a/ui/shared/statusTag/__screenshots__/StatusTag.pw.tsx_default_pending-status-1.png b/ui/shared/statusTag/__screenshots__/StatusTag.pw.tsx_default_pending-status-1.png index d5d9f0cec1..6651f21e65 100644 Binary files a/ui/shared/statusTag/__screenshots__/StatusTag.pw.tsx_default_pending-status-1.png and b/ui/shared/statusTag/__screenshots__/StatusTag.pw.tsx_default_pending-status-1.png differ diff --git a/ui/shared/tagGroupSelect/TagGroupSelect.pw.tsx b/ui/shared/tagGroupSelect/TagGroupSelect.pw.tsx new file mode 100644 index 0000000000..4ccc12110c --- /dev/null +++ b/ui/shared/tagGroupSelect/TagGroupSelect.pw.tsx @@ -0,0 +1,22 @@ +import _noop from 'lodash/noop'; +import React from 'react'; + +import { test, expect } from 'playwright/lib'; + +import TagGroupSelect from './TagGroupSelect'; + +test.use({ viewport: { width: 480, height: 140 } }); + +test('base view +@dark-mode', async({ render }) => { + const component = await render( + , + ); + + await component.getByText('Option 2').hover(); + + await expect(component).toHaveScreenshot(); +}); diff --git a/ui/shared/tagGroupSelect/TagGroupSelect.tsx b/ui/shared/tagGroupSelect/TagGroupSelect.tsx new file mode 100644 index 0000000000..3d18c26d74 --- /dev/null +++ b/ui/shared/tagGroupSelect/TagGroupSelect.tsx @@ -0,0 +1,60 @@ +import type { TagProps } from '@chakra-ui/react'; +import { HStack, Tag } from '@chakra-ui/react'; +import React from 'react'; + +type Props = { + items: Array<{ id: T; title: string }>; + tagSize?: TagProps['size']; +} & ( + { + value?: T; + onChange: (value: T) => void; + isMulti?: false; + } | { + value: Array; + onChange: (value: Array) => void; + isMulti: true; + } +); + +const TagGroupSelect = ({ items, value, isMulti, onChange, tagSize }: Props) => { + const onItemClick = React.useCallback((event: React.SyntheticEvent) => { + const itemValue = (event.currentTarget as HTMLDivElement).getAttribute('data-id') as T; + if (isMulti) { + let newValue; + if (value.includes(itemValue)) { + newValue = value.filter(i => i !== itemValue); + } else { + newValue = [ ...value, itemValue ]; + } + onChange(newValue); + } else { + onChange(itemValue); + } + }, [ isMulti, onChange, value ]); + + return ( + + { items.map(item => { + const isSelected = isMulti ? value.includes(item.id) : value === item.id; + return ( + + { item.title } + + ); + }) } + + ); +}; + +export default TagGroupSelect; diff --git a/ui/shared/tagGroupSelect/__screenshots__/TagGroupSelect.pw.tsx_dark-color-mode_base-view-dark-mode-1.png b/ui/shared/tagGroupSelect/__screenshots__/TagGroupSelect.pw.tsx_dark-color-mode_base-view-dark-mode-1.png new file mode 100644 index 0000000000..5bf6e8edb1 Binary files /dev/null and b/ui/shared/tagGroupSelect/__screenshots__/TagGroupSelect.pw.tsx_dark-color-mode_base-view-dark-mode-1.png differ diff --git a/ui/shared/tagGroupSelect/__screenshots__/TagGroupSelect.pw.tsx_default_base-view-dark-mode-1.png b/ui/shared/tagGroupSelect/__screenshots__/TagGroupSelect.pw.tsx_default_base-view-dark-mode-1.png new file mode 100644 index 0000000000..2a0a13206c Binary files /dev/null and b/ui/shared/tagGroupSelect/__screenshots__/TagGroupSelect.pw.tsx_default_base-view-dark-mode-1.png differ diff --git a/ui/shared/tx/TxFee.pw.tsx b/ui/shared/tx/TxFee.pw.tsx new file mode 100644 index 0000000000..8ccd98ade6 --- /dev/null +++ b/ui/shared/tx/TxFee.pw.tsx @@ -0,0 +1,30 @@ +import React from 'react'; + +import * as txMock from 'mocks/txs/tx'; +import { test, expect } from 'playwright/lib'; + +import TxFee from './TxFee'; + +test.use({ viewport: { width: 300, height: 100 } }); + +test('base view', async({ render }) => { + const component = await render(); + await expect(component).toHaveScreenshot(); +}); + +test('no usd value', async({ render }) => { + const component = await render(); + await expect(component).toHaveScreenshot(); +}); + +test('celo gas token', async({ render, mockAssetResponse }) => { + await mockAssetResponse(txMock.celoTxn.celo?.gas_token?.icon_url as string, './playwright/mocks/image_svg.svg'); + const component = await render(); + await expect(component).toHaveScreenshot(); +}); + +test('stability token', async({ render, mockAssetResponse }) => { + await mockAssetResponse(txMock.stabilityTx.stability_fee?.token.icon_url as string, './playwright/mocks/image_svg.svg'); + const component = await render(); + await expect(component).toHaveScreenshot(); +}); diff --git a/ui/shared/tx/TxFee.tsx b/ui/shared/tx/TxFee.tsx new file mode 100644 index 0000000000..01fa1eb240 --- /dev/null +++ b/ui/shared/tx/TxFee.tsx @@ -0,0 +1,77 @@ +import { chakra, Skeleton } from '@chakra-ui/react'; +import React from 'react'; + +import type { Transaction } from 'types/api/transaction'; + +import config from 'configs/app'; +import getCurrencyValue from 'lib/getCurrencyValue'; +import { currencyUnits } from 'lib/units'; +import CurrencyValue from 'ui/shared/CurrencyValue'; +import TokenEntity from 'ui/shared/entities/token/TokenEntity'; + +interface Props { + className?: string; + isLoading?: boolean; + tx: Transaction; + withCurrency?: boolean; + withUsd?: boolean; + accuracy?: number; + accuracyUsd?: number; +} + +const TxFee = ({ className, tx, accuracy, accuracyUsd, isLoading, withCurrency = true, withUsd }: Props) => { + + if (tx.celo?.gas_token) { + const token = tx.celo.gas_token; + const { valueStr, usd } = getCurrencyValue({ + value: tx.fee.value || '0', + exchangeRate: token.exchange_rate, + decimals: token.decimals, + accuracy, + accuracyUsd, + }); + return ( + + { valueStr } + + { usd && withUsd && (${ usd }) } + + ); + } + + if (tx.stability_fee) { + const token = tx.stability_fee.token; + const { valueStr, usd } = getCurrencyValue({ + value: tx.stability_fee.total_fee, + exchangeRate: token.exchange_rate, + decimals: token.decimals, + accuracy, + accuracyUsd, + }); + + return ( + + { valueStr } + { valueStr !== '0' && } + { usd && withUsd && (${ usd }) } + + ); + } + + const showCurrency = withCurrency && !config.UI.views.tx.hiddenFields?.fee_currency; + + return ( + + ); +}; + +export default React.memo(chakra(TxFee)); diff --git a/ui/shared/tx/TxFeeStability.tsx b/ui/shared/tx/TxFeeStability.tsx deleted file mode 100644 index d1c10c0d9a..0000000000 --- a/ui/shared/tx/TxFeeStability.tsx +++ /dev/null @@ -1,36 +0,0 @@ -import { Skeleton, chakra } from '@chakra-ui/react'; -import React from 'react'; - -import type { Transaction } from 'types/api/transaction'; -import type { ExcludeUndefined } from 'types/utils'; - -import getCurrencyValue from 'lib/getCurrencyValue'; -import TokenEntity from 'ui/shared/entities/token/TokenEntity'; - -interface Props { - data: ExcludeUndefined; - isLoading?: boolean; - hideUsd?: boolean; - accuracy?: number; - className?: string; -} - -const TxFeeStability = ({ data, isLoading, hideUsd, accuracy, className }: Props) => { - - const { valueStr, usd } = getCurrencyValue({ - value: data.total_fee, - exchangeRate: data.token.exchange_rate, - decimals: data.token.decimals, - accuracy, - }); - - return ( - - { valueStr } - { valueStr !== '0' && } - { usd && !hideUsd && (${ usd }) } - - ); -}; - -export default React.memo(chakra(TxFeeStability)); diff --git a/ui/shared/tx/__screenshots__/TxFee.pw.tsx_default_base-view-1.png b/ui/shared/tx/__screenshots__/TxFee.pw.tsx_default_base-view-1.png new file mode 100644 index 0000000000..7ac54aea40 Binary files /dev/null and b/ui/shared/tx/__screenshots__/TxFee.pw.tsx_default_base-view-1.png differ diff --git a/ui/shared/tx/__screenshots__/TxFee.pw.tsx_default_celo-gas-token-1.png b/ui/shared/tx/__screenshots__/TxFee.pw.tsx_default_celo-gas-token-1.png new file mode 100644 index 0000000000..7bf968661e Binary files /dev/null and b/ui/shared/tx/__screenshots__/TxFee.pw.tsx_default_celo-gas-token-1.png differ diff --git a/ui/shared/tx/__screenshots__/TxFee.pw.tsx_default_no-usd-value-1.png b/ui/shared/tx/__screenshots__/TxFee.pw.tsx_default_no-usd-value-1.png new file mode 100644 index 0000000000..c9b10f06fe Binary files /dev/null and b/ui/shared/tx/__screenshots__/TxFee.pw.tsx_default_no-usd-value-1.png differ diff --git a/ui/shared/tx/__screenshots__/TxFee.pw.tsx_default_stability-token-1.png b/ui/shared/tx/__screenshots__/TxFee.pw.tsx_default_stability-token-1.png new file mode 100644 index 0000000000..82eebabc86 Binary files /dev/null and b/ui/shared/tx/__screenshots__/TxFee.pw.tsx_default_stability-token-1.png differ diff --git a/ui/shared/tx/interpretation/TxInterpretation.tsx b/ui/shared/tx/interpretation/TxInterpretation.tsx new file mode 100644 index 0000000000..7eda2b5992 --- /dev/null +++ b/ui/shared/tx/interpretation/TxInterpretation.tsx @@ -0,0 +1,186 @@ +import { Skeleton, Tooltip, chakra } from '@chakra-ui/react'; +import BigNumber from 'bignumber.js'; +import React from 'react'; + +import type { AddressParam } from 'types/api/addressParams'; +import type { + TxInterpretationSummary, + TxInterpretationVariable, + TxInterpretationVariableString, +} from 'types/api/txInterpretation'; + +import config from 'configs/app'; +import dayjs from 'lib/date/dayjs'; +import * as mixpanel from 'lib/mixpanel/index'; +import { currencyUnits } from 'lib/units'; +import Tag from 'ui/shared/chakra/Tag'; +import AddressEntity from 'ui/shared/entities/address/AddressEntity'; +import EnsEntity from 'ui/shared/entities/ens/EnsEntity'; +import TokenEntity from 'ui/shared/entities/token/TokenEntity'; +import IconSvg from 'ui/shared/IconSvg'; + +import { + extractVariables, + getStringChunks, + fillStringVariables, + checkSummary, + NATIVE_COIN_SYMBOL_VAR_NAME, + WEI_VAR_NAME, +} from './utils'; + +type Props = { + summary?: TxInterpretationSummary; + isLoading?: boolean; + addressDataMap?: Record; + className?: string; +}; + +type NonStringTxInterpretationVariable = Exclude; + +const TxInterpretationElementByType = ( + { variable, addressDataMap }: { variable?: NonStringTxInterpretationVariable; addressDataMap?: Record }, +) => { + const onAddressClick = React.useCallback(() => { + mixpanel.logEvent(mixpanel.EventTypes.TX_INTERPRETATION_INTERACTION, { Type: 'Address click' }); + }, []); + + const onTokenClick = React.useCallback(() => { + mixpanel.logEvent(mixpanel.EventTypes.TX_INTERPRETATION_INTERACTION, { Type: 'Token click' }); + }, []); + + const onDomainClick = React.useCallback(() => { + mixpanel.logEvent(mixpanel.EventTypes.TX_INTERPRETATION_INTERACTION, { Type: 'Domain click' }); + }, []); + + if (!variable) { + return null; + } + + const { type, value } = variable; + switch (type) { + case 'address': { + return ( + + + + ); + } + case 'token': + return ( + + + + ); + case 'domain': { + if (config.features.nameService.isEnabled) { + return ( + + + + ); + } + return { value + ' ' }; + } + case 'currency': { + let numberString = ''; + if (BigNumber(value).isLessThan(0.1)) { + numberString = BigNumber(value).toPrecision(2); + } else if (BigNumber(value).isLessThan(10000)) { + numberString = BigNumber(value).dp(2).toFormat(); + } else if (BigNumber(value).isLessThan(1000000)) { + numberString = BigNumber(value).dividedBy(1000).toFormat(2) + 'K'; + } else { + numberString = BigNumber(value).dividedBy(1000000).toFormat(2) + 'M'; + } + return { numberString + ' ' }; + } + case 'timestamp': { + return { dayjs(Number(value) * 1000).format('MMM DD YYYY') }; + } + case 'method': { + return ( + + { value } + + ); + } + } +}; + +const TxInterpretation = ({ summary, isLoading, addressDataMap, className }: Props) => { + if (!summary) { + return null; + } + + const template = summary.summary_template; + const variables = summary.summary_template_variables; + + if (!checkSummary(template, variables)) { + return null; + } + + const intermediateResult = fillStringVariables(template, variables); + + const variablesNames = extractVariables(intermediateResult); + const chunks = getStringChunks(intermediateResult); + + return ( + + + + + { chunks.map((chunk, index) => { + let content = null; + if (variablesNames[index] === NATIVE_COIN_SYMBOL_VAR_NAME) { + content = { currencyUnits.ether + ' ' }; + } else if (variablesNames[index] === WEI_VAR_NAME) { + content = { currencyUnits.wei + ' ' }; + } else { + content = ( + + ); + } + return ( + + { chunk.trim() + (chunk.trim() && variablesNames[index] ? ' ' : '') } + { index < variablesNames.length && content } + + ); + }) } + + ); +}; + +export default chakra(TxInterpretation); diff --git a/ui/shared/tx/interpretation/utils.test.ts b/ui/shared/tx/interpretation/utils.test.ts new file mode 100644 index 0000000000..9485deaf13 --- /dev/null +++ b/ui/shared/tx/interpretation/utils.test.ts @@ -0,0 +1,25 @@ +import { extractVariables, getStringChunks, checkSummary } from './utils'; + +const template = '{action_type} {source_amount} {native} into {destination_amount} {destination_token}'; + +it('extracts variables names', () => { + const result = extractVariables(template); + expect(result).toEqual([ 'action_type', 'source_amount', 'native', 'destination_amount', 'destination_token' ]); +}); + +it('split string without capturing variables', () => { + const result = getStringChunks(template); + expect(result).toEqual([ '', ' ', ' ', ' into ', ' ', '' ]); +}); + +it('checks that summary is valid', () => { + const result = checkSummary('{foo} {native} {bar} {wei}', { foo: { type: 'string', value: 'foo' }, bar: { type: 'string', value: 'bar' } }); + expect(result).toBe(true); +}); + +it('checks that summary is invalid', () => { + + // @ts-ignore: + const result = checkSummary('{foo} {native} {bar}', { foo: { type: 'string', value: null }, bar: { type: 'string', value: 'bar' } }); + expect(result).toBe(false); +}); diff --git a/ui/shared/tx/interpretation/utils.ts b/ui/shared/tx/interpretation/utils.ts new file mode 100644 index 0000000000..28e3d4b71d --- /dev/null +++ b/ui/shared/tx/interpretation/utils.ts @@ -0,0 +1,51 @@ +// we use that regex as a separator when splitting template and dont want to capture variables + +import type { TxInterpretationVariable } from 'types/api/txInterpretation'; + +// eslint-disable-next-line regexp/no-useless-non-capturing-group +export const VAR_REGEXP = /\{(?:[^}]+)\}/g; + +export const NATIVE_COIN_SYMBOL_VAR_NAME = 'native'; +export const WEI_VAR_NAME = 'wei'; + +export function extractVariables(templateString: string) { + + const matches = templateString.match(VAR_REGEXP); + + const variablesNames = matches ? matches.map(match => match.slice(1, -1)) : []; + + return variablesNames; +} + +export function getStringChunks(template: string) { + return template.split(VAR_REGEXP); +} + +export function checkSummary(template: string, variables: Record) { + const variablesNames = extractVariables(template); + let result = true; + for (const name of variablesNames) { + if (name === NATIVE_COIN_SYMBOL_VAR_NAME || name === WEI_VAR_NAME) { + continue; + } + if (!variables[name] || variables[name].value === undefined || variables[name].value === null) { + result = false; + break; + } + } + + return result; +} + +export function fillStringVariables(template: string, variables: Record) { + const variablesNames = extractVariables(template); + + let result = template; + variablesNames.forEach(name => { + if (variables[name] && variables[name].type === 'string') { + result = result.replace(`{${ name }}`, variables[name].value as string); + } + }); + + return result; +} diff --git a/ui/shared/userOps/UserOpSponsorType.tsx b/ui/shared/userOps/UserOpSponsorType.tsx index cd8d27327d..a1fd63fc64 100644 --- a/ui/shared/userOps/UserOpSponsorType.tsx +++ b/ui/shared/userOps/UserOpSponsorType.tsx @@ -1,11 +1,11 @@ import { Tag } from '@chakra-ui/react'; import React from 'react'; -import { UserOpSponsorType } from 'types/api/userOps'; +import type { UserOpSponsorType as TUserOpSponsorType } from 'types/api/userOps'; type Props = { - sponsorType: UserOpSponsorType; -} + sponsorType: TUserOpSponsorType; +}; const UserOpSponsorType = ({ sponsorType }: Props) => { let text: string = sponsorType; diff --git a/ui/shared/userOps/UserOpStatus.tsx b/ui/shared/userOps/UserOpStatus.tsx index 6c3a00d40b..ea58a5feba 100644 --- a/ui/shared/userOps/UserOpStatus.tsx +++ b/ui/shared/userOps/UserOpStatus.tsx @@ -6,7 +6,7 @@ import StatusTag from 'ui/shared/statusTag/StatusTag'; type Props = { status?: boolean; isLoading?: boolean; -} +}; const UserOpStatus = ({ status, isLoading }: Props) => { if (status === undefined) { diff --git a/ui/shared/userOps/UserOpsAddress.tsx b/ui/shared/userOps/UserOpsAddress.tsx deleted file mode 100644 index 73036de756..0000000000 --- a/ui/shared/userOps/UserOpsAddress.tsx +++ /dev/null @@ -1,23 +0,0 @@ -import React from 'react'; - -import type { AddressParamBasic } from 'types/api/addressParams'; - -import AddressEntity from '../entities/address/AddressEntity'; -import type { EntityProps } from '../entities/address/AddressEntity'; - -type Props = Omit & { - address: string | AddressParamBasic; -} - -const UserOpsAddress = ({ address, ...props }: Props) => { - let addressParam; - if (typeof address === 'string') { - addressParam = { hash: address }; - } else { - addressParam = address; - } - - return ; -}; - -export default UserOpsAddress; diff --git a/ui/shared/verificationSteps/VerificationStep.tsx b/ui/shared/verificationSteps/VerificationStep.tsx index 17a7c98632..5efea2af37 100644 --- a/ui/shared/verificationSteps/VerificationStep.tsx +++ b/ui/shared/verificationSteps/VerificationStep.tsx @@ -9,14 +9,20 @@ type Props = { step: Step; isLast: boolean; isPassed: boolean; -} + isPending?: boolean; +}; -const VerificationStep = ({ step, isLast, isPassed }: Props) => { - const stepColor = isPassed ? 'green.500' : 'text_secondary'; +const VerificationStep = ({ step, isLast, isPassed, isPending }: Props) => { + let stepColor = 'text_secondary'; + if (isPending) { + stepColor = 'yellow.500'; + } else if (isPassed) { + stepColor = 'green.500'; + } return ( - + { typeof step === 'string' ? step : step.content } { !isLast && } diff --git a/ui/shared/verificationSteps/VerificationSteps.pw.tsx b/ui/shared/verificationSteps/VerificationSteps.pw.tsx index 9447631099..3c83438f8f 100644 --- a/ui/shared/verificationSteps/VerificationSteps.pw.tsx +++ b/ui/shared/verificationSteps/VerificationSteps.pw.tsx @@ -1,33 +1,24 @@ import { Box } from '@chakra-ui/react'; -import { test, expect } from '@playwright/experimental-ct-react'; import React from 'react'; import { ZKEVM_L2_TX_STATUSES } from 'types/api/transaction'; -import TestApp from 'playwright/TestApp'; +import { test, expect } from 'playwright/lib'; import VerificationSteps from './VerificationSteps'; -test('first step +@mobile +@dark-mode', async({ mount }) => { - - const component = await mount( - - - - - , +test('first step +@mobile +@dark-mode', async({ render }) => { + const component = await render( + + + , ); - await expect(component).toHaveScreenshot(); }); -test('second status', async({ mount }) => { - - const component = await mount( - - - , +test('second status', async({ render }) => { + const component = await render( + , ); - await expect(component).toHaveScreenshot(); }); diff --git a/ui/shared/verificationSteps/VerificationSteps.tsx b/ui/shared/verificationSteps/VerificationSteps.tsx index a7a6c506d8..889a09d370 100644 --- a/ui/shared/verificationSteps/VerificationSteps.tsx +++ b/ui/shared/verificationSteps/VerificationSteps.tsx @@ -7,13 +7,14 @@ import VerificationStep from './VerificationStep'; export interface Props { currentStep: string; + currentStepPending?: boolean; steps: Array; isLoading?: boolean; rightSlot?: React.ReactNode; className?: string; } -const VerificationSteps = ({ currentStep, steps, isLoading, rightSlot, className }: Props) => { +const VerificationSteps = ({ currentStep, currentStepPending, steps, isLoading, rightSlot, className }: Props) => { const currentStepIndex = steps.findIndex((step) => { const label = typeof step === 'string' ? step : step.label; return label === currentStep; @@ -34,6 +35,7 @@ const VerificationSteps = ({ currentStep, steps, isLoading, rightSlot, className step={ step } isLast={ index === steps.length - 1 && !rightSlot } isPassed={ index <= currentStepIndex } + isPending={ index === currentStepIndex && currentStepPending } /> )) } { rightSlot } diff --git a/ui/shared/verificationSteps/__screenshots__/VerificationSteps.pw.tsx_dark-color-mode_first-step-mobile-dark-mode-1.png b/ui/shared/verificationSteps/__screenshots__/VerificationSteps.pw.tsx_dark-color-mode_first-step-mobile-dark-mode-1.png index 1b723b918c..fb2092510b 100644 Binary files a/ui/shared/verificationSteps/__screenshots__/VerificationSteps.pw.tsx_dark-color-mode_first-step-mobile-dark-mode-1.png and b/ui/shared/verificationSteps/__screenshots__/VerificationSteps.pw.tsx_dark-color-mode_first-step-mobile-dark-mode-1.png differ diff --git a/ui/shared/verificationSteps/__screenshots__/VerificationSteps.pw.tsx_default_first-step-mobile-dark-mode-1.png b/ui/shared/verificationSteps/__screenshots__/VerificationSteps.pw.tsx_default_first-step-mobile-dark-mode-1.png index ec5715c72c..7eabdffd00 100644 Binary files a/ui/shared/verificationSteps/__screenshots__/VerificationSteps.pw.tsx_default_first-step-mobile-dark-mode-1.png and b/ui/shared/verificationSteps/__screenshots__/VerificationSteps.pw.tsx_default_first-step-mobile-dark-mode-1.png differ diff --git a/ui/shared/verificationSteps/__screenshots__/VerificationSteps.pw.tsx_default_second-status-1.png b/ui/shared/verificationSteps/__screenshots__/VerificationSteps.pw.tsx_default_second-status-1.png index bed6995133..b1f33566da 100644 Binary files a/ui/shared/verificationSteps/__screenshots__/VerificationSteps.pw.tsx_default_second-status-1.png and b/ui/shared/verificationSteps/__screenshots__/VerificationSteps.pw.tsx_default_second-status-1.png differ diff --git a/ui/shared/verificationSteps/__screenshots__/VerificationSteps.pw.tsx_mobile_first-step-mobile-dark-mode-1.png b/ui/shared/verificationSteps/__screenshots__/VerificationSteps.pw.tsx_mobile_first-step-mobile-dark-mode-1.png index cf24c70de8..a69cfda93a 100644 Binary files a/ui/shared/verificationSteps/__screenshots__/VerificationSteps.pw.tsx_mobile_first-step-mobile-dark-mode-1.png and b/ui/shared/verificationSteps/__screenshots__/VerificationSteps.pw.tsx_mobile_first-step-mobile-dark-mode-1.png differ diff --git a/ui/shared/verificationSteps/types.ts b/ui/shared/verificationSteps/types.ts index 582decfa4e..7dc2ef7f36 100644 --- a/ui/shared/verificationSteps/types.ts +++ b/ui/shared/verificationSteps/types.ts @@ -1 +1,3 @@ +import type React from 'react'; + export type Step = string | { label: string; content: React.ReactNode }; diff --git a/ui/snippets/auth/AuthGuard.tsx b/ui/snippets/auth/AuthGuard.tsx new file mode 100644 index 0000000000..b3ef71097d --- /dev/null +++ b/ui/snippets/auth/AuthGuard.tsx @@ -0,0 +1,39 @@ +import { useDisclosure } from '@chakra-ui/react'; +import React from 'react'; + +import AuthModal from './AuthModal'; +import useIsAuth from './useIsAuth'; + +interface InjectedProps { + onClick: () => void; +} + +interface Props { + children: (props: InjectedProps) => React.ReactNode; + onAuthSuccess: () => void; +} + +const AuthGuard = ({ children, onAuthSuccess }: Props) => { + const authModal = useDisclosure(); + const isAuth = useIsAuth(); + + const handleClick = React.useCallback(() => { + isAuth ? onAuthSuccess() : authModal.onOpen(); + }, [ authModal, isAuth, onAuthSuccess ]); + + const handleModalClose = React.useCallback((isSuccess?: boolean) => { + if (isSuccess) { + onAuthSuccess(); + } + authModal.onClose(); + }, [ authModal, onAuthSuccess ]); + + return ( + <> + { children({ onClick: handleClick }) } + { authModal.isOpen && } + + ); +}; + +export default React.memo(AuthGuard); diff --git a/ui/snippets/auth/AuthModal.pw.tsx b/ui/snippets/auth/AuthModal.pw.tsx new file mode 100644 index 0000000000..9bd8b49955 --- /dev/null +++ b/ui/snippets/auth/AuthModal.pw.tsx @@ -0,0 +1,64 @@ +import type { BrowserContext } from '@playwright/test'; +import React from 'react'; + +import * as profileMock from 'mocks/user/profile'; +import { contextWithAuth } from 'playwright/fixtures/auth'; +import { test, expect } from 'playwright/lib'; + +import AuthModalStory from './AuthModal.pwstory'; + +test('email login', async({ render, page, mockApiResponse }) => { + + await render(); + + await expect(page.getByText('Status: Not authenticated')).toBeVisible(); + + await page.getByText('Log in').click(); + await expect(page).toHaveScreenshot(); + + // fill email + await page.getByText('Continue with email').click(); + await page.getByLabel(/email/i).getByPlaceholder(' ').fill('john.doe@example.com'); + await expect(page).toHaveScreenshot(); + + // send otp code + await mockApiResponse('auth_send_otp', {} as never); + await page.getByText('Send a code').click(); + + // fill otp code + await page.getByLabel(/enter your pin code/i).nth(0).fill('123456'); + await expect(page).toHaveScreenshot(); + + // submit otp code + await mockApiResponse('auth_confirm_otp', profileMock.base as never); + await page.getByText('Submit').click(); + await expect(page).toHaveScreenshot(); + + await page.getByLabel('Close').click(); + await expect(page.getByText('Status: Authenticated')).toBeVisible(); +}); + +const linkEmailTest = test.extend<{ context: BrowserContext }>({ + context: contextWithAuth, +}); + +linkEmailTest('link email to account', async({ render, page, mockApiResponse }) => { + await mockApiResponse('user_info', profileMock.base); + await render(); + + await expect(page.getByText('Status: Authenticated')).toBeVisible(); + + // fill email + await page.getByText('Link email').click(); + await page.getByLabel(/email/i).getByPlaceholder(' ').fill('john.doe@example.com'); + await expect(page).toHaveScreenshot(); + + // send and fill otp code + await mockApiResponse('auth_send_otp', {} as never); + await page.getByText('Send a code').click(); + await page.getByLabel(/enter your pin code/i).nth(0).fill('123456'); + await mockApiResponse('auth_link_email', profileMock.base as never); + await page.getByText('Submit').click(); + + await expect(page).toHaveScreenshot(); +}); diff --git a/ui/snippets/auth/AuthModal.pwstory.tsx b/ui/snippets/auth/AuthModal.pwstory.tsx new file mode 100644 index 0000000000..37df1e2d8b --- /dev/null +++ b/ui/snippets/auth/AuthModal.pwstory.tsx @@ -0,0 +1,30 @@ +import { Box, Button, useDisclosure } from '@chakra-ui/react'; +import React from 'react'; + +import AuthModal from './AuthModal'; +import useIsAuth from './useIsAuth'; + +interface Props { + flow: 'email_login' | 'email_link'; +} + +const AuthModalStory = ({ flow }: Props) => { + const authModal = useDisclosure(); + const isAuth = useIsAuth(); + + const initialScreen = flow === 'email_login' ? { type: 'select_method' as const } : { type: 'email' as const, isAuth: true }; + + const handleClose = React.useCallback(() => { + authModal.onClose(); + }, [ authModal ]); + + return ( + <> + + { authModal.isOpen && } + Status: { isAuth ? 'Authenticated' : 'Not authenticated' } + + ); +}; + +export default AuthModalStory; diff --git a/ui/snippets/auth/AuthModal.tsx b/ui/snippets/auth/AuthModal.tsx new file mode 100644 index 0000000000..99ecf0cad9 --- /dev/null +++ b/ui/snippets/auth/AuthModal.tsx @@ -0,0 +1,192 @@ +import { Modal, ModalBody, ModalCloseButton, ModalContent, ModalHeader, ModalOverlay } from '@chakra-ui/react'; +import { useQueryClient } from '@tanstack/react-query'; +import { useRouter } from 'next/router'; +import React from 'react'; + +import type { Screen, ScreenSuccess } from './types'; + +import config from 'configs/app'; +import { getResourceKey } from 'lib/api/useApiQuery'; +import useGetCsrfToken from 'lib/hooks/useGetCsrfToken'; +import * as mixpanel from 'lib/mixpanel'; +import IconSvg from 'ui/shared/IconSvg'; + +import AuthModalScreenConnectWallet from './screens/AuthModalScreenConnectWallet'; +import AuthModalScreenEmail from './screens/AuthModalScreenEmail'; +import AuthModalScreenOtpCode from './screens/AuthModalScreenOtpCode'; +import AuthModalScreenSelectMethod from './screens/AuthModalScreenSelectMethod'; +import AuthModalScreenSuccessEmail from './screens/AuthModalScreenSuccessEmail'; +import AuthModalScreenSuccessWallet from './screens/AuthModalScreenSuccessWallet'; + +const feature = config.features.account; + +interface Props { + initialScreen: Screen; + onClose: (isSuccess?: boolean) => void; + mixpanelConfig?: { + wallet_connect?: { + source: mixpanel.EventPayload['Source']; + }; + account_link_info: { + source: mixpanel.EventPayload['Source']; + }; + }; + closeOnError?: boolean; +} + +const AuthModal = ({ initialScreen, onClose, mixpanelConfig, closeOnError }: Props) => { + const [ steps, setSteps ] = React.useState>([ initialScreen ]); + const [ isSuccess, setIsSuccess ] = React.useState(false); + + const router = useRouter(); + const csrfQuery = useGetCsrfToken(); + const queryClient = useQueryClient(); + + React.useEffect(() => { + if ('isAuth' in initialScreen && initialScreen.isAuth) { + mixpanel.logEvent(mixpanel.EventTypes.ACCOUNT_LINK_INFO, { + Status: 'Started', + Type: initialScreen.type === 'connect_wallet' ? 'Wallet' : 'Email', + Source: mixpanelConfig?.account_link_info.source ?? 'Profile dropdown', + }); + } else { + mixpanel.logEvent(mixpanel.EventTypes.LOGIN, { + Action: 'Started', + Source: mixpanel.getPageType(router.pathname), + }); + } + }, [ initialScreen, mixpanelConfig, router.pathname ]); + + const onNextStep = React.useCallback((screen: Screen) => { + setSteps((prev) => [ ...prev, screen ]); + }, []); + + const onPrevStep = React.useCallback(() => { + setSteps((prev) => prev.length > 1 ? prev.slice(0, -1) : prev); + }, []); + + const onReset = React.useCallback((isAuth?: boolean) => { + isAuth || closeOnError ? onClose() : setSteps([ initialScreen ]); + }, [ initialScreen, onClose, closeOnError ]); + + const onAuthSuccess = React.useCallback(async(screen: ScreenSuccess) => { + setIsSuccess(true); + + if ('isAuth' in initialScreen && initialScreen.isAuth) { + mixpanel.logEvent(mixpanel.EventTypes.ACCOUNT_LINK_INFO, { + Status: 'Finished', + Type: screen.type === 'success_wallet' ? 'Wallet' : 'Email', + Source: mixpanelConfig?.account_link_info.source ?? 'Profile dropdown', + }); + } else { + mixpanel.logEvent(mixpanel.EventTypes.LOGIN, { + Action: 'Success', + Source: screen.type === 'success_wallet' ? 'Wallet' : 'Email', + }); + } + + queryClient.setQueryData(getResourceKey('user_info'), () => screen.profile); + await csrfQuery.refetch(); + onNextStep(screen); + }, [ initialScreen, mixpanelConfig?.account_link_info.source, onNextStep, csrfQuery, queryClient ]); + + const onModalClose = React.useCallback(() => { + onClose(isSuccess); + }, [ isSuccess, onClose ]); + + const header = (() => { + const currentStep = steps[steps.length - 1]; + switch (currentStep.type) { + case 'select_method': + return 'Select a way to login'; + case 'connect_wallet': + return currentStep.isAuth ? 'Add wallet' : 'Continue with wallet'; + case 'email': + return currentStep.isAuth ? 'Add email' : 'Continue with email'; + case 'otp_code': + return 'Confirmation code'; + case 'success_email': + case 'success_wallet': + return 'Congrats!'; + } + })(); + + const content = (() => { + const currentStep = steps[steps.length - 1]; + switch (currentStep.type) { + case 'select_method': + return ; + case 'connect_wallet': + return ( + + ); + case 'email': + return ( + + ); + case 'otp_code': + return ; + case 'success_email': + return ( + + ); + case 'success_wallet': + return ( + + ); + } + })(); + + if (!feature.isEnabled) { + return null; + } + + return ( + + + + + { steps.length > 1 && !steps[steps.length - 1].type.startsWith('success') && ( + + ) } + { header } + + + + { content } + + + + ); +}; + +export default React.memo(AuthModal); diff --git a/ui/snippets/auth/__screenshots__/AuthModal.pw.tsx_default_email-login-1.png b/ui/snippets/auth/__screenshots__/AuthModal.pw.tsx_default_email-login-1.png new file mode 100644 index 0000000000..6305bdd8e8 Binary files /dev/null and b/ui/snippets/auth/__screenshots__/AuthModal.pw.tsx_default_email-login-1.png differ diff --git a/ui/snippets/auth/__screenshots__/AuthModal.pw.tsx_default_email-login-2.png b/ui/snippets/auth/__screenshots__/AuthModal.pw.tsx_default_email-login-2.png new file mode 100644 index 0000000000..005b2a0754 Binary files /dev/null and b/ui/snippets/auth/__screenshots__/AuthModal.pw.tsx_default_email-login-2.png differ diff --git a/ui/snippets/auth/__screenshots__/AuthModal.pw.tsx_default_email-login-3.png b/ui/snippets/auth/__screenshots__/AuthModal.pw.tsx_default_email-login-3.png new file mode 100644 index 0000000000..6ffd05d3c4 Binary files /dev/null and b/ui/snippets/auth/__screenshots__/AuthModal.pw.tsx_default_email-login-3.png differ diff --git a/ui/snippets/auth/__screenshots__/AuthModal.pw.tsx_default_email-login-4.png b/ui/snippets/auth/__screenshots__/AuthModal.pw.tsx_default_email-login-4.png new file mode 100644 index 0000000000..d8807216ed Binary files /dev/null and b/ui/snippets/auth/__screenshots__/AuthModal.pw.tsx_default_email-login-4.png differ diff --git a/ui/snippets/auth/__screenshots__/AuthModal.pw.tsx_default_link-email-to-account-1.png b/ui/snippets/auth/__screenshots__/AuthModal.pw.tsx_default_link-email-to-account-1.png new file mode 100644 index 0000000000..cab2eadcce Binary files /dev/null and b/ui/snippets/auth/__screenshots__/AuthModal.pw.tsx_default_link-email-to-account-1.png differ diff --git a/ui/snippets/auth/__screenshots__/AuthModal.pw.tsx_default_link-email-to-account-2.png b/ui/snippets/auth/__screenshots__/AuthModal.pw.tsx_default_link-email-to-account-2.png new file mode 100644 index 0000000000..4aa441a048 Binary files /dev/null and b/ui/snippets/auth/__screenshots__/AuthModal.pw.tsx_default_link-email-to-account-2.png differ diff --git a/ui/snippets/auth/fields/AuthModalFieldOtpCode.tsx b/ui/snippets/auth/fields/AuthModalFieldOtpCode.tsx new file mode 100644 index 0000000000..d59e8a7a22 --- /dev/null +++ b/ui/snippets/auth/fields/AuthModalFieldOtpCode.tsx @@ -0,0 +1,39 @@ +import { HStack, PinInputField, Text } from '@chakra-ui/react'; +import React from 'react'; +import { useController, useFormContext } from 'react-hook-form'; + +import type { OtpCodeFormFields } from '../types'; + +import PinInput from 'ui/shared/chakra/PinInput'; + +const CODE_LENGTH = 6; + +interface Props { + isDisabled?: boolean; +} + +const AuthModalFieldOtpCode = ({ isDisabled: isDisabledProp }: Props) => { + const { control } = useFormContext(); + const { field, fieldState, formState } = useController({ + control, + name: 'code', + rules: { required: true, minLength: CODE_LENGTH, maxLength: CODE_LENGTH }, + }); + + const isDisabled = isDisabledProp || formState.isSubmitting; + + return ( + <> + + + { Array.from({ length: CODE_LENGTH }).map((_, index) => ( + + )) } + + + { fieldState.error?.message && { fieldState.error.message } } + + ); +}; + +export default React.memo(AuthModalFieldOtpCode); diff --git a/ui/snippets/auth/screens/AuthModalScreenConnectWallet.tsx b/ui/snippets/auth/screens/AuthModalScreenConnectWallet.tsx new file mode 100644 index 0000000000..c05c209bf5 --- /dev/null +++ b/ui/snippets/auth/screens/AuthModalScreenConnectWallet.tsx @@ -0,0 +1,41 @@ +import { Center, Spinner } from '@chakra-ui/react'; +import React from 'react'; + +import type { ScreenSuccess } from '../types'; +import type { UserInfo } from 'types/api/account'; + +import type * as mixpanel from 'lib/mixpanel'; + +import useSignInWithWallet from '../useSignInWithWallet'; + +interface Props { + onSuccess: (screen: ScreenSuccess) => void; + onError: (isAuth?: boolean) => void; + isAuth?: boolean; + source?: mixpanel.EventPayload['Source']; +} + +const AuthModalScreenConnectWallet = ({ onSuccess, onError, isAuth, source }: Props) => { + const isStartedRef = React.useRef(false); + + const handleSignInSuccess = React.useCallback(({ address, profile }: { address: string; profile: UserInfo }) => { + onSuccess({ type: 'success_wallet', address, isAuth, profile }); + }, [ onSuccess, isAuth ]); + + const handleSignInError = React.useCallback(() => { + onError(isAuth); + }, [ onError, isAuth ]); + + const { start } = useSignInWithWallet({ onSuccess: handleSignInSuccess, onError: handleSignInError, source, isAuth }); + + React.useEffect(() => { + if (!isStartedRef.current) { + isStartedRef.current = true; + start(); + } + }, [ start ]); + + return
; +}; + +export default React.memo(AuthModalScreenConnectWallet); diff --git a/ui/snippets/auth/screens/AuthModalScreenEmail.tsx b/ui/snippets/auth/screens/AuthModalScreenEmail.tsx new file mode 100644 index 0000000000..51c959e289 --- /dev/null +++ b/ui/snippets/auth/screens/AuthModalScreenEmail.tsx @@ -0,0 +1,104 @@ +import { chakra, Button, Text } from '@chakra-ui/react'; +import React from 'react'; +import type { SubmitHandler } from 'react-hook-form'; +import { FormProvider, useForm } from 'react-hook-form'; + +import type { EmailFormFields, Screen } from '../types'; + +import useApiFetch from 'lib/api/useApiFetch'; +import getErrorMessage from 'lib/errors/getErrorMessage'; +import getErrorObjPayload from 'lib/errors/getErrorObjPayload'; +import useToast from 'lib/hooks/useToast'; +import * as mixpanel from 'lib/mixpanel'; +import FormFieldEmail from 'ui/shared/forms/fields/FormFieldEmail'; +import ReCaptcha from 'ui/shared/reCaptcha/ReCaptcha'; +import useReCaptcha from 'ui/shared/reCaptcha/useReCaptcha'; + +interface Props { + onSubmit: (screen: Screen) => void; + isAuth?: boolean; + mixpanelConfig?: { + account_link_info: { + source: mixpanel.EventPayload['Source']; + }; + }; +} + +const AuthModalScreenEmail = ({ onSubmit, isAuth, mixpanelConfig }: Props) => { + + const apiFetch = useApiFetch(); + const toast = useToast(); + const recaptcha = useReCaptcha(); + + const formApi = useForm({ + mode: 'onBlur', + defaultValues: { + email: '', + }, + }); + + const onFormSubmit: SubmitHandler = React.useCallback(async(formData) => { + try { + const token = await recaptcha.executeAsync(); + + await apiFetch('auth_send_otp', { + fetchParams: { + method: 'POST', + body: { + email: formData.email, + recaptcha_response: token, + }, + }, + }); + if (isAuth) { + mixpanelConfig?.account_link_info.source !== 'Profile' && mixpanel.logEvent(mixpanel.EventTypes.ACCOUNT_LINK_INFO, { + Source: mixpanelConfig?.account_link_info.source ?? 'Profile dropdown', + Status: 'OTP sent', + Type: 'Email', + }); + } else { + mixpanel.logEvent(mixpanel.EventTypes.LOGIN, { + Action: 'OTP sent', + Source: 'Email', + }); + } + onSubmit({ type: 'otp_code', email: formData.email, isAuth }); + } catch (error) { + toast({ + status: 'error', + title: 'Error', + description: getErrorObjPayload<{ message: string }>(error)?.message || getErrorMessage(error) || 'Something went wrong', + }); + } + }, [ recaptcha, apiFetch, isAuth, onSubmit, mixpanelConfig?.account_link_info.source, toast ]); + + return ( + + + Account email, used for transaction notifications from your watchlist. + + name="email" + isRequired + placeholder="Email" + bgColor="dialog_bg" + mt={ 6 } + /> + + + + + ); +}; + +export default React.memo(AuthModalScreenEmail); diff --git a/ui/snippets/auth/screens/AuthModalScreenOtpCode.tsx b/ui/snippets/auth/screens/AuthModalScreenOtpCode.tsx new file mode 100644 index 0000000000..87cd2ec9fd --- /dev/null +++ b/ui/snippets/auth/screens/AuthModalScreenOtpCode.tsx @@ -0,0 +1,144 @@ +import { chakra, Box, Text, Button } from '@chakra-ui/react'; +import React from 'react'; +import type { SubmitHandler } from 'react-hook-form'; +import { FormProvider, useForm } from 'react-hook-form'; + +import type { OtpCodeFormFields, ScreenSuccess } from '../types'; +import type { UserInfo } from 'types/api/account'; + +import useApiFetch from 'lib/api/useApiFetch'; +import getErrorMessage from 'lib/errors/getErrorMessage'; +import getErrorObjPayload from 'lib/errors/getErrorObjPayload'; +import useToast from 'lib/hooks/useToast'; +import IconSvg from 'ui/shared/IconSvg'; +import ReCaptcha from 'ui/shared/reCaptcha/ReCaptcha'; +import useReCaptcha from 'ui/shared/reCaptcha/useReCaptcha'; + +import AuthModalFieldOtpCode from '../fields/AuthModalFieldOtpCode'; + +interface Props { + email: string; + onSuccess: (screen: ScreenSuccess) => void; + isAuth?: boolean; +} + +const AuthModalScreenOtpCode = ({ email, onSuccess, isAuth }: Props) => { + + const apiFetch = useApiFetch(); + const toast = useToast(); + const recaptcha = useReCaptcha(); + const [ isCodeSending, setIsCodeSending ] = React.useState(false); + + const formApi = useForm({ + mode: 'onBlur', + defaultValues: { + code: '', + }, + }); + + const onFormSubmit: SubmitHandler = React.useCallback((formData) => { + const resource = isAuth ? 'auth_link_email' : 'auth_confirm_otp'; + return apiFetch(resource, { + fetchParams: { + method: 'POST', + body: { + otp: formData.code, + email, + }, + }, + }) + .then((response) => { + if (!('name' in response)) { + throw Error('Something went wrong'); + } + onSuccess({ type: 'success_email', email, isAuth, profile: response }); + }) + .catch((error) => { + const apiError = getErrorObjPayload<{ message: string }>(error); + + if (apiError?.message) { + formApi.setError('code', { message: apiError.message }); + return; + } + + toast({ + status: 'error', + title: 'Error', + description: getErrorMessage(error) || 'Something went wrong', + }); + }); + }, [ apiFetch, email, onSuccess, isAuth, toast, formApi ]); + + const handleResendCodeClick = React.useCallback(async() => { + try { + formApi.clearErrors('code'); + setIsCodeSending(true); + const token = await recaptcha.executeAsync(); + await apiFetch('auth_send_otp', { + fetchParams: { + method: 'POST', + body: { email, recaptcha_response: token }, + }, + }); + + toast({ + status: 'success', + title: 'Success', + description: 'Code has been sent to your email', + }); + } catch (error) { + const apiError = getErrorObjPayload<{ message: string }>(error); + + toast({ + status: 'error', + title: 'Error', + description: apiError?.message || getErrorMessage(error) || 'Something went wrong', + }); + } finally { + setIsCodeSending(false); + } + }, [ apiFetch, email, formApi, toast, recaptcha ]); + + return ( + + + + Please check{ ' ' } + { email }{ ' ' } + and enter your code below. + + + + + + + + ); +}; + +export default React.memo(AuthModalScreenOtpCode); diff --git a/ui/snippets/auth/screens/AuthModalScreenSelectMethod.tsx b/ui/snippets/auth/screens/AuthModalScreenSelectMethod.tsx new file mode 100644 index 0000000000..5c03710246 --- /dev/null +++ b/ui/snippets/auth/screens/AuthModalScreenSelectMethod.tsx @@ -0,0 +1,38 @@ +import { Button, VStack } from '@chakra-ui/react'; +import React from 'react'; + +import type { Screen } from '../types'; + +import * as mixpanel from 'lib/mixpanel'; + +interface Props { + onSelectMethod: (screen: Screen) => void; +} + +const AuthModalScreenSelectMethod = ({ onSelectMethod }: Props) => { + + const handleEmailClick = React.useCallback(() => { + mixpanel.logEvent(mixpanel.EventTypes.LOGIN, { + Action: 'Email', + Source: 'Options selector', + }); + onSelectMethod({ type: 'email' }); + }, [ onSelectMethod ]); + + const handleConnectWalletClick = React.useCallback(() => { + mixpanel.logEvent(mixpanel.EventTypes.LOGIN, { + Action: 'Wallet', + Source: 'Options selector', + }); + onSelectMethod({ type: 'connect_wallet' }); + }, [ onSelectMethod ]); + + return ( + + + + + ); +}; + +export default React.memo(AuthModalScreenSelectMethod); diff --git a/ui/snippets/auth/screens/AuthModalScreenSuccessEmail.tsx b/ui/snippets/auth/screens/AuthModalScreenSuccessEmail.tsx new file mode 100644 index 0000000000..24cd84c3ec --- /dev/null +++ b/ui/snippets/auth/screens/AuthModalScreenSuccessEmail.tsx @@ -0,0 +1,65 @@ +import { chakra, Box, Text, Button } from '@chakra-ui/react'; +import React from 'react'; + +import type { Screen } from '../types'; +import type { UserInfo } from 'types/api/account'; + +import config from 'configs/app'; + +interface Props { + email: string; + onConnectWallet: (screen: Screen) => void; + onClose: () => void; + isAuth?: boolean; + profile: UserInfo | undefined; +} + +const AuthModalScreenSuccessEmail = ({ email, onConnectWallet, onClose, isAuth, profile }: Props) => { + const handleConnectWalletClick = React.useCallback(() => { + onConnectWallet({ type: 'connect_wallet', isAuth: true }); + }, [ onConnectWallet ]); + + if (isAuth) { + return ( + + + Your account was linked to{ ' ' } + { email }{ ' ' } + email. Use for the next login. + + + + ); + } + + return ( + + + { email }{ ' ' } + email has been successfully used to log in to your Blockscout account. + + { !profile?.address_hash && config.features.blockchainInteraction.isEnabled ? ( + <> + Add your web3 wallet to safely interact with smart contracts and dapps inside Blockscout. + + + ) : ( + + ) } + + ); +}; + +export default React.memo(AuthModalScreenSuccessEmail); diff --git a/ui/snippets/auth/screens/AuthModalScreenSuccessWallet.tsx b/ui/snippets/auth/screens/AuthModalScreenSuccessWallet.tsx new file mode 100644 index 0000000000..2febd91f29 --- /dev/null +++ b/ui/snippets/auth/screens/AuthModalScreenSuccessWallet.tsx @@ -0,0 +1,74 @@ +import { chakra, Box, Text, Button, Flex } from '@chakra-ui/react'; +import React from 'react'; + +import type { Screen } from '../types'; +import type { UserInfo } from 'types/api/account'; + +import config from 'configs/app'; +import { apos } from 'lib/html-entities'; +import shortenString from 'lib/shortenString'; + +interface Props { + address: string; + onAddEmail: (screen: Screen) => void; + onClose: () => void; + isAuth?: boolean; + profile: UserInfo | undefined; +} + +const AuthModalScreenSuccessWallet = ({ address, onAddEmail, onClose, isAuth, profile }: Props) => { + const handleAddEmailClick = React.useCallback(() => { + onAddEmail({ type: 'email', isAuth: true }); + }, [ onAddEmail ]); + + if (isAuth) { + return ( + + + Your account was linked to{ ' ' } + { shortenString(address) }{ ' ' } + wallet. Use for the next login. + + + + ); + } + + return ( + + + Wallet{ ' ' } + { shortenString(address) }{ ' ' } + has been successfully used to log in to your Blockscout account. + + { !profile?.email ? ( + <> + + Add your email to receive exclusive updates about Blockscout { config.features.rewards.isEnabled ? 'Merits ' : ' ' } + and notifications about addresses in your watch list. + + + + + + + ) : ( + + ) } + + ); +}; + +export default React.memo(AuthModalScreenSuccessWallet); diff --git a/ui/snippets/auth/types.ts b/ui/snippets/auth/types.ts new file mode 100644 index 0000000000..38aac0c427 --- /dev/null +++ b/ui/snippets/auth/types.ts @@ -0,0 +1,34 @@ +import type { UserInfo } from 'types/api/account'; + +export type ScreenSuccess = { + type: 'success_email'; + email: string; + profile: UserInfo; + isAuth?: boolean; +} | { + type: 'success_wallet'; + address: string; + profile: UserInfo; + isAuth?: boolean; +}; +export type Screen = { + type: 'select_method'; +} | { + type: 'connect_wallet'; + isAuth?: boolean; +} | { + type: 'email'; + isAuth?: boolean; +} | { + type: 'otp_code'; + email: string; + isAuth?: boolean; +} | ScreenSuccess; + +export interface EmailFormFields { + email: string; +} + +export interface OtpCodeFormFields { + code: string; +} diff --git a/ui/snippets/auth/useIsAuth.ts b/ui/snippets/auth/useIsAuth.ts new file mode 100644 index 0000000000..c4e5467e1e --- /dev/null +++ b/ui/snippets/auth/useIsAuth.ts @@ -0,0 +1,18 @@ +import config from 'configs/app'; +import { useAppContext } from 'lib/contexts/app'; +import * as cookies from 'lib/cookies'; + +import useProfileQuery from './useProfileQuery'; + +export default function useAuth() { + const appProps = useAppContext(); + const profileQuery = useProfileQuery(); + + if (!config.features.account.isEnabled) { + return false; + } + + const cookiesString = appProps.cookies; + const hasAuth = Boolean(cookies.get(cookies.NAMES.API_TOKEN, cookiesString) || profileQuery.data); + return hasAuth; +} diff --git a/ui/snippets/auth/useLogout.ts b/ui/snippets/auth/useLogout.ts new file mode 100644 index 0000000000..be9ac90ee9 --- /dev/null +++ b/ui/snippets/auth/useLogout.ts @@ -0,0 +1,69 @@ +import { useQueryClient } from '@tanstack/react-query'; +import { useRouter } from 'next/router'; +import React from 'react'; + +import type { Route } from 'nextjs-routes'; + +import config from 'configs/app'; +import useApiFetch from 'lib/api/useApiFetch'; +import { getResourceKey } from 'lib/api/useApiQuery'; +import * as cookies from 'lib/cookies'; +import useToast from 'lib/hooks/useToast'; +import * as mixpanel from 'lib/mixpanel'; + +const PROTECTED_ROUTES: Array = [ + '/account/api-key', + '/account/custom-abi', + '/account/rewards', + '/account/tag-address', + '/account/verified-addresses', + '/account/watchlist', + '/auth/profile', +]; + +export default function useLogout() { + const router = useRouter(); + const queryClient = useQueryClient(); + const toast = useToast(); + const apiFetch = useApiFetch(); + + return React.useCallback(async() => { + try { + await apiFetch('auth_logout'); + cookies.remove(cookies.NAMES.API_TOKEN); + + if (config.features.rewards.isEnabled) { + const rewardsToken = cookies.get(cookies.NAMES.REWARDS_API_TOKEN); + await apiFetch('rewards_logout', { fetchParams: { + method: 'POST', + headers: { Authorization: `Bearer ${ rewardsToken }` }, + } }); + cookies.remove(cookies.NAMES.REWARDS_API_TOKEN); + } + + queryClient.resetQueries({ + queryKey: getResourceKey('user_info'), + exact: true, + }); + queryClient.resetQueries({ + queryKey: getResourceKey('custom_abi'), + exact: true, + }); + + mixpanel.logEvent(mixpanel.EventTypes.ACCOUNT_ACCESS, { Action: 'Logged out' }, { send_immediately: true }); + + if ( + PROTECTED_ROUTES.includes(router.pathname) || + (router.pathname === '/txs' && router.query.tab === 'watchlist') + ) { + router.push({ pathname: '/' }, undefined, { shallow: true }); + } + } catch (error) { + toast({ + status: 'error', + title: 'Logout failed', + description: 'Please try again later', + }); + } + }, [ apiFetch, queryClient, router, toast ]); +} diff --git a/ui/snippets/auth/useProfileQuery.ts b/ui/snippets/auth/useProfileQuery.ts new file mode 100644 index 0000000000..3cacf32044 --- /dev/null +++ b/ui/snippets/auth/useProfileQuery.ts @@ -0,0 +1,11 @@ +import useApiQuery from 'lib/api/useApiQuery'; +import * as cookies from 'lib/cookies'; + +export default function useProfileQuery() { + return useApiQuery('user_info', { + queryOptions: { + refetchOnMount: false, + enabled: Boolean(cookies.get(cookies.NAMES.API_TOKEN)), + }, + }); +} diff --git a/ui/snippets/auth/useRedirectForInvalidAuthToken.ts b/ui/snippets/auth/useRedirectForInvalidAuthToken.ts new file mode 100644 index 0000000000..96634a138b --- /dev/null +++ b/ui/snippets/auth/useRedirectForInvalidAuthToken.ts @@ -0,0 +1,22 @@ +import React from 'react'; + +import * as cookies from 'lib/cookies'; +import { useRollbar } from 'lib/rollbar'; +import useProfileQuery from 'ui/snippets/auth/useProfileQuery'; + +export default function useRedirectForInvalidAuthToken() { + const rollbar = useRollbar(); + const profileQuery = useProfileQuery(); + const errorStatus = profileQuery.error?.status; + + React.useEffect(() => { + if (errorStatus === 401) { + const apiToken = cookies.get(cookies.NAMES.API_TOKEN); + + if (apiToken) { + cookies.remove(cookies.NAMES.API_TOKEN); + window.location.assign('/'); + } + } + }, [ errorStatus, rollbar ]); +} diff --git a/ui/snippets/auth/useSignInWithWallet.ts b/ui/snippets/auth/useSignInWithWallet.ts new file mode 100644 index 0000000000..f2af71ff13 --- /dev/null +++ b/ui/snippets/auth/useSignInWithWallet.ts @@ -0,0 +1,85 @@ +import React from 'react'; +import { useSignMessage } from 'wagmi'; + +import type { UserInfo } from 'types/api/account'; + +import config from 'configs/app'; +import useApiFetch from 'lib/api/useApiFetch'; +import getErrorMessage from 'lib/errors/getErrorMessage'; +import getErrorObj from 'lib/errors/getErrorObj'; +import getErrorObjPayload from 'lib/errors/getErrorObjPayload'; +import useToast from 'lib/hooks/useToast'; +import type * as mixpanel from 'lib/mixpanel'; +import useWeb3Wallet from 'lib/web3/useWallet'; + +interface Props { + onSuccess?: ({ address, profile }: { address: string; profile: UserInfo }) => void; + onError?: () => void; + source?: mixpanel.EventPayload['Source']; + isAuth?: boolean; +} + +function useSignInWithWallet({ onSuccess, onError, source = 'Login', isAuth }: Props) { + const [ isPending, setIsPending ] = React.useState(false); + const isConnectingWalletRef = React.useRef(false); + + const apiFetch = useApiFetch(); + const toast = useToast(); + const web3Wallet = useWeb3Wallet({ source }); + const { signMessageAsync } = useSignMessage(); + + const proceedToAuth = React.useCallback(async(address: string) => { + try { + const siweMessage = await apiFetch('auth_siwe_message', { queryParams: { address } }) as { siwe_message: string }; + const signature = await signMessageAsync({ message: siweMessage.siwe_message }); + const resource = isAuth ? 'auth_link_address' : 'auth_siwe_verify'; + const response = await apiFetch(resource, { + fetchParams: { + method: 'POST', + body: { message: siweMessage.siwe_message, signature }, + }, + }); + if (!('name' in response)) { + throw Error('Something went wrong'); + } + onSuccess?.({ address, profile: response }); + } catch (error) { + const errorObj = getErrorObj(error); + const apiErrorMessage = getErrorObjPayload<{ message: string }>(error)?.message; + const shortMessage = errorObj && 'shortMessage' in errorObj && typeof errorObj.shortMessage === 'string' ? errorObj.shortMessage : undefined; + onError?.(); + toast({ + status: 'error', + title: 'Error', + description: apiErrorMessage || shortMessage || getErrorMessage(error) || 'Something went wrong', + }); + } finally { + setIsPending(false); + } + }, [ apiFetch, isAuth, onError, onSuccess, signMessageAsync, toast ]); + + const start = React.useCallback(() => { + setIsPending(true); + if (web3Wallet.address) { + proceedToAuth(web3Wallet.address); + } else { + isConnectingWalletRef.current = true; + web3Wallet.openModal(); + } + }, [ proceedToAuth, web3Wallet ]); + + React.useEffect(() => { + if (web3Wallet.address && isConnectingWalletRef.current) { + isConnectingWalletRef.current = false; + proceedToAuth(web3Wallet.address); + } + }, [ proceedToAuth, web3Wallet.address ]); + + return React.useMemo(() => ({ start, isPending }), [ start, isPending ]); +} + +function useSignInWithWalletFallback() { + return React.useMemo(() => ({ start: () => {}, isPending: false }), [ ]); +} + +export default config.features.blockchainInteraction.isEnabled ? useSignInWithWallet : useSignInWithWalletFallback; diff --git a/ui/snippets/footer/Footer.pw.tsx b/ui/snippets/footer/Footer.pw.tsx index 5749ebf485..d0cefccf62 100644 --- a/ui/snippets/footer/Footer.pw.tsx +++ b/ui/snippets/footer/Footer.pw.tsx @@ -1,54 +1,28 @@ -import { test as base, expect } from '@playwright/experimental-ct-react'; import React from 'react'; -import type { WindowProvider } from 'wagmi'; -import { buildExternalAssetFilePath } from 'configs/app/utils'; import { FOOTER_LINKS } from 'mocks/config/footerLinks'; -import contextWithEnvs from 'playwright/fixtures/contextWithEnvs'; -import TestApp from 'playwright/TestApp'; -import * as app from 'playwright/utils/app'; -import buildApiUrl from 'playwright/utils/buildApiUrl'; -import * as configs from 'playwright/utils/configs'; +import { test, expect } from 'playwright/lib'; +import * as pwConfig from 'playwright/utils/config'; import Footer from './Footer'; -const FOOTER_LINKS_URL = app.url + buildExternalAssetFilePath('NEXT_PUBLIC_FOOTER_LINKS', 'https://localhost:3000/footer-links.json') || ''; - -const BACKEND_VERSION_API_URL = buildApiUrl('config_backend_version'); -const INDEXING_ALERT_API_URL = buildApiUrl('homepage_indexing_status'); - -base.describe('with custom links, max cols', () => { - const test = base.extend({ - context: contextWithEnvs([ - { name: 'NEXT_PUBLIC_FOOTER_LINKS', value: FOOTER_LINKS_URL }, - // eslint-disable-next-line @typescript-eslint/no-explicit-any - ]) as any, - }); - - test.beforeEach(async({ page, mount }) => { - await page.route(FOOTER_LINKS_URL, (route) => { - return route.fulfill({ - body: JSON.stringify(FOOTER_LINKS), - }); +const FOOTER_LINKS_URL = 'https://localhost:3000/footer-links.json'; + +test.describe('with custom links, max cols', () => { + test.beforeEach(async({ render, mockApiResponse, mockConfigResponse, injectMetaMaskProvider, mockEnvs }) => { + await mockEnvs([ + [ 'NEXT_PUBLIC_FOOTER_LINKS', FOOTER_LINKS_URL ], + ]); + await mockConfigResponse('NEXT_PUBLIC_FOOTER_LINKS', FOOTER_LINKS_URL, FOOTER_LINKS); + await injectMetaMaskProvider(); + await mockApiResponse('homepage_indexing_status', { + finished_indexing: false, + finished_indexing_blocks: false, + indexed_internal_transactions_ratio: '0.1', + indexed_blocks_ratio: '0.1', }); - await page.evaluate(() => { - window.ethereum = { - isMetaMask: true, - _events: {}, - } as WindowProvider; - }); - - await page.route(INDEXING_ALERT_API_URL, (route) => route.fulfill({ - status: 200, - body: JSON.stringify({ finished_indexing: false, indexed_internal_transactions_ratio: 0.1 }), - })); - - await mount( - -