diff --git a/.env.example b/.env.example index e56bd6211b..4b1cc9e280 100644 --- a/.env.example +++ b/.env.example @@ -7,3 +7,4 @@ 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 diff --git a/.eslintrc.js b/.eslintrc.js index 20881d308f..49e2ad4c43 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -4,9 +4,20 @@ const RESTRICTED_MODULES = { { 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', '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/*', ], }; diff --git a/.github/workflows/chakra-npm-publisher.yml b/.github/workflows/chakra-npm-publisher.yml new file mode 100644 index 0000000000..1a63a30a3d --- /dev/null +++ b/.github/workflows/chakra-npm-publisher.yml @@ -0,0 +1,51 @@ +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: '20.x' + registry-url: 'https://registry.npmjs.org' + + - name: Update package version + run: | + cd ./theme + npm version ${{ inputs.version }} + + - name: Build the package + run: | + 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/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..28e2af1e9a 100644 --- a/.github/workflows/deploy-review-l2.yml +++ b/.github/workflows/deploy-review-l2.yml @@ -2,6 +2,31 @@ name: Deploy review environment (L2) on: workflow_dispatch: + inputs: + envs_preset: + description: ENVs preset + required: false + default: "" + type: choice + options: + - none + - arbitrum + - arbitrum_nova + - base + - celo_alfajores + - garnet + - gnosis + - eth + - eth_sepolia + - eth_goerli + - optimism + - optimism_celestia + - optimism_sepolia + - polygon + - rootstock + - stability + - zkevm + - zksync jobs: make_slug: @@ -23,6 +48,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..935c52b432 100644 --- a/.github/workflows/deploy-review.yml +++ b/.github/workflows/deploy-review.yml @@ -2,6 +2,31 @@ name: Deploy review environment on: workflow_dispatch: + inputs: + envs_preset: + description: ENVs preset + required: false + default: "" + type: choice + options: + - none + - arbitrum + - arbitrum_nova + - base + - celo_alfajores + - garnet + - gnosis + - eth + - eth_sepolia + - eth_goerli + - optimism + - optimism_celestia + - optimism_sepolia + - polygon + - rootstock + - stability + - zkevm + - zksync jobs: make_slug: @@ -23,6 +48,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/pre-release.yml b/.github/workflows/pre-release.yml index c45cb9249d..05155cbd32 100644 --- a/.github/workflows/pre-release.yml +++ b/.github/workflows/pre-release.yml @@ -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 ce53245267..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' }} @@ -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..6895728156 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 }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index fb0c863799..df2c0433b1 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -81,8 +81,11 @@ jobs: with: platforms: linux/amd64,linux/arm64/v8 + # 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/.gitignore b/.gitignore index 4ee6adeee0..d1d6685c18 100644 --- a/.gitignore +++ b/.gitignore @@ -15,8 +15,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 @@ -28,6 +28,7 @@ .DS_Store *.pem .tools +grafana # debug npm-debug.log* @@ -56,4 +57,7 @@ yarn-error.log* /playwright/envs.js /playwright/affected-tests.txt -**.dec** \ No newline at end of file +**.dec** + +# build outputs +/tools/preset-sync/index.js diff --git a/.vscode/tasks.json b/.vscode/tasks.json index ad9f96bd7f..b1d379bf3b 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 { @@ -337,20 +358,25 @@ "description": "Choose dev server config preset:", "options": [ "main", - "main.L2", + "localhost", + "arbitrum", + "base", + "celo_alfajores", + "garnet", + "gnosis", "eth", "eth_goerli", - "sepolia", + "eth_sepolia", + "optimism", + "optimism_celestia", + "optimism_sepolia", "polygon", + "rootstock_testnet", + "stability_testnet", "zkevm", "zksync", - "gnosis", - "rootstock", - "stability", - "poa_core", - "localhost", ], "default": "main" }, ], -} \ No newline at end of file +} diff --git a/Dockerfile b/Dockerfile index f38d68ebaa..a325403e92 100644 --- a/Dockerfile +++ b/Dockerfile @@ -3,7 +3,8 @@ # ***************************** FROM node:20.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 @@ -33,11 +34,13 @@ RUN yarn --frozen-lockfile FROM node:20.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 +51,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 +60,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 @@ -102,14 +105,14 @@ 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 --chmod=755 ./deploy/scripts/favicon_generator.sh . COPY ./deploy/tools/favicon-generator ./deploy/tools/favicon-generator RUN ["chmod", "-R", "777", "./deploy/tools/favicon-generator"] RUN ["chmod", "-R", "777", "./public"] @@ -118,6 +121,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..30ec4f8b6f --- /dev/null +++ b/RELEASE_NOTES.md @@ -0,0 +1,33 @@ +## 🚀 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. + +## ✨ 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 1ec156e4e0..56971abda6 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'), @@ -12,12 +27,14 @@ const chain = Object.freeze({ symbol: getEnvValue('NEXT_PUBLIC_NETWORK_CURRENCY_SYMBOL'), decimals: Number(getEnvValue('NEXT_PUBLIC_NETWORK_CURRENCY_DECIMALS')) || DEFAULT_CURRENCY_DECIMALS, }, - 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', - verificationType: getEnvValue('NEXT_PUBLIC_NETWORK_VERIFICATION_TYPE') || 'mining', + verificationType, }); export default chain; 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/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/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/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 fbe1a6a949..24ea0a2088 100644 --- a/configs/app/features/index.ts +++ b/configs/app/features/index.ts @@ -1,5 +1,6 @@ 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'; @@ -7,17 +8,24 @@ export { default as bridgedTokens } from './bridgedTokens'; export { default as blockchainInteraction } from './blockchainInteraction'; export { default as csvExport } from './csvExport'; export { default as dataAvailability } from './dataAvailability'; +export { default as deFiDropdown } from './deFiDropdown'; +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 publicTagsSubmission } from './publicTagsSubmission'; export { default as restApiDocs } from './restApiDocs'; export { default as rollup } from './rollup'; export { default as safe } from './safe'; +export { default as saveOnGas } from './saveOnGas'; export { default as sentry } from './sentry'; export { default as sol2uml } from './sol2uml'; export { default as stats } from './stats'; diff --git a/configs/app/features/marketplace.ts b/configs/app/features/marketplace.ts index b136ce0d09..72f5d1ebde 100644 --- a/configs/app/features/marketplace.ts +++ b/configs/app/features/marketplace.ts @@ -11,6 +11,11 @@ const suggestIdeasFormUrl = getEnvValue('NEXT_PUBLIC_MARKETPLACE_SUGGEST_IDEAS_F 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 title = 'Marketplace'; @@ -22,31 +27,43 @@ const config: Feature<( categoriesUrl: string | undefined; suggestIdeasFormUrl: string | undefined; securityReportsUrl: string | undefined; -} -> = (() => { + featuredApp: string | undefined; + banner: { contentUrl: string; linkUrl: string } | undefined; + rating: { airtableApiKey: string; airtableBaseId: 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, + }; + if (configUrl) { return Object.freeze({ title, isEnabled: true, configUrl, - submitFormUrl, - categoriesUrl, - suggestIdeasFormUrl, - securityReportsUrl, + ...props, }); } else if (adminServiceApiHost) { return Object.freeze({ title, isEnabled: true, - submitFormUrl, - categoriesUrl, - suggestIdeasFormUrl, - securityReportsUrl, api: { endpoint: adminServiceApiHost, basePath: '', }, + ...props, }); } } 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..47b1433d05 --- /dev/null +++ b/configs/app/features/multichainButton.ts @@ -0,0 +1,29 @@ +import type { Feature } from './types'; +import type { MultichainProviderConfig } 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<{name: string; logoUrl?: string; urlTemplate: string; dappId?: string }> = (() => { + if (value) { + return Object.freeze({ + title, + isEnabled: true, + name: value.name, + logoUrl: value.logo, + urlTemplate: value.url_template, + dappId: marketplace.isEnabled ? value.dapp_id : undefined, + }); + } + + 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..296d5e143a --- /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.reCaptcha.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/rollup.ts b/configs/app/features/rollup.ts index 2560734097..48c5422270 100644 --- a/configs/app/features/rollup.ts +++ b/configs/app/features/rollup.ts @@ -2,6 +2,8 @@ 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 = (() => { @@ -21,7 +23,7 @@ const config: Feature<{ type: RollupType; L1BaseUrl: string; L2WithdrawalUrl?: s title, isEnabled: true, type, - L1BaseUrl, + L1BaseUrl: stripTrailingSlash(L1BaseUrl), L2WithdrawalUrl, }); } 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/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/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/ui.ts b/configs/app/ui.ts index 7f6832bf1f..674580d944 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,11 +75,13 @@ 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'), }, - showAvgBlockTime: getEnvValue('NEXT_PUBLIC_HOMEPAGE_SHOW_AVG_BLOCK_TIME') === 'false' ? false : true, }, views, indexingAlert: { @@ -70,6 +102,14 @@ const UI = Object.freeze({ 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 376708f729..fcc88c557b 100644 --- a/configs/app/ui/views/address.ts +++ b/configs/app/ui/views/address.ts @@ -1,3 +1,5 @@ +import type { SmartContractVerificationMethodExtra } from 'types/client/contract'; +import { SMART_CONTRACT_EXTRA_VERIFICATION_METHODS } from 'types/client/contract'; import type { AddressViewId, IdenticonType } from 'types/views/address'; import { ADDRESS_VIEWS_IDS, IDENTICON_TYPES } from 'types/views/address'; @@ -27,10 +29,26 @@ 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>(getEnvValue('NEXT_PUBLIC_VIEWS_CONTRACT_EXTRA_VERIFICATION_METHODS')) || []; + + return SMART_CONTRACT_EXTRA_VERIFICATION_METHODS.filter((method) => parsedMethods.includes(method)); +})(); + const config = Object.freeze({ identiconType, hiddenViews, solidityscanEnabled: getEnvValue('NEXT_PUBLIC_VIEWS_CONTRACT_SOLIDITYSCAN_ENABLED') === 'true', + extraVerificationMethods, }); export default config; diff --git a/configs/app/utils.ts b/configs/app/utils.ts index 5f24c8829f..b188d14e8d 100644 --- a/configs/app/utils.ts +++ b/configs/app/utils.ts @@ -5,7 +5,7 @@ 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); @@ -39,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..a1ec2fef59 --- /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 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..c07ded0b8a --- /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 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.base b/configs/envs/.env.base new file mode 100644 index 0000000000..2c9cdf6699 --- /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 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..2934345c90 --- /dev/null +++ b/configs/envs/.env.celo_alfajores @@ -0,0 +1,40 @@ +# 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 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 + +# 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 2715974d4b..0ea799c9a3 100644 --- a/configs/envs/.env.eth +++ b/configs/envs/.env.eth @@ -1,57 +1,65 @@ # 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 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','logo':'https://github.com/blockscout/frontend-configs/blob/main/configs/explorer-logos/etherscan.png?raw=true','baseUrl':'https://etherscan.io/','paths':{'tx':'/tx','address':'/address','token':'/token','block':'/block'}}, {'title':'Blockchair','logo':'https://github.com/blockscout/frontend-configs/blob/main/configs/explorer-logos/blockchair.png?raw=true','baseUrl':'https://blockchair.com/','paths':{'tx':'/ethereum/transaction','address':'/ethereum/address','token':'/ethereum/erc-20/token','block':'/ethereum/block'}},{'title':'Sentio','logo':'https://github.com/blockscout/frontend-configs/blob/main/configs/explorer-logos/sentio.png?raw=true','baseUrl':'https://app.sentio.xyz/','paths':{'tx':'/tx/1','address':'/contract/1'}}, {'title':'Tenderly','logo':'https://github.com/blockscout/frontend-configs/blob/main/configs/explorer-logos/tenderly.png?raw=true','baseUrl':'https://dashboard.tenderly.co','paths':{'tx':'/tx/mainnet'}}, {'title':'0xPPL','logo':'https://github.com/blockscout/frontend-configs/blob/main/configs/explorer-logos/0xPPl.png?raw=true','baseUrl':'https://0xppl.com','paths':{'tx':'/Ethereum/tx','address':'/','token':'/c/Ethereum'}}, {'title':'3xpl','logo':'https://github.com/blockscout/frontend-configs/blob/main/configs/explorer-logos/3xpl.png?raw=true','baseUrl':'https://3xpl.com/','paths':{'tx':'/ethereum/transaction','address':'/ethereum/address'}} ]" - -# 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':'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/eth.json +NEXT_PUBLIC_FOOTER_LINKS=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/footer-links/eth-mainnet.json +NEXT_PUBLIC_GRAPHIQL_TRANSACTION=0xd01175f1efa23f36c5579b3c13e2bbd0885017643a7efef5cbcb6b474384dfa8 NEXT_PUBLIC_HAS_BEACON_CHAIN=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-prod-1.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_TRANSACTION_INTERPRETATION_PROVIDER=blockscout -NEXT_PUBLIC_AD_BANNER_PROVIDER=hype -NEXT_PUBLIC_SAFE_TX_SERVICE_URL=https://safe-transaction-mainnet.safe.global -NEXT_PUBLIC_NAME_SERVICE_API_HOST=https://bens.services.blockscout.com -NEXT_PUBLIC_MARKETPLACE_ENABLED=true -NEXT_PUBLIC_MARKETPLACE_SUBMIT_FORM=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/marketplace-categories/default.json +NEXT_PUBLIC_LOGOUT_URL=https://ethereum-mainnet.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_FEATURED_APP=gearbox-protocol +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_RATING_AIRTABLE_BASE_ID=appGkvtmKI7fXE4Vs +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_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_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_SAFE_TX_SERVICE_URL=https://safe-transaction-mainnet.safe.global +NEXT_PUBLIC_SENTRY_ENABLE_TRACING=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 NEXT_PUBLIC_VIEWS_CONTRACT_SOLIDITYSCAN_ENABLED=true - -#meta -NEXT_PUBLIC_OG_IMAGE_URL=https://github.com/blockscout/frontend-configs/blob/main/configs/og-images/eth.jpg?raw=true +NEXT_PUBLIC_VISUALIZE_API_HOST=https://visualizer.services.blockscout.com +NEXT_PUBLIC_SAVE_ON_GAS_ENABLED=true diff --git a/configs/envs/.env.eth_sepolia b/configs/envs/.env.eth_sepolia new file mode 100644 index 0000000000..1304b37c72 --- /dev/null +++ b/configs/envs/.env.eth_sepolia @@ -0,0 +1,65 @@ +# Set of ENVs for Sepolia network explorer +# https://eth-sepolia.blockscout.com +# This is an auto-generated file. To update all values, run "yarn 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.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':'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/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_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_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/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':'/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_SAFE_TX_SERVICE_URL=https://safe-transaction-sepolia.safe.global +NEXT_PUBLIC_SENTRY_ENABLE_TRACING=true +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 \ No newline at end of file diff --git a/configs/envs/.env.garnet b/configs/envs/.env.garnet new file mode 100644 index 0000000000..a6763934bc --- /dev/null +++ b/configs/envs/.env.garnet @@ -0,0 +1,50 @@ +# Set of ENVs for Garnet (dev only) +# https://https://explorer.garnetchain.com// + +# app configuration +NEXT_PUBLIC_APP_PROTOCOL=http +NEXT_PUBLIC_APP_HOST=localhost +NEXT_PUBLIC_APP_PORT=3000 + +# blockchain parameters +NEXT_PUBLIC_NETWORK_NAME="Garnet Testnet" +NEXT_PUBLIC_NETWORK_ID=17069 +NEXT_PUBLIC_NETWORK_CURRENCY_NAME=Ether +NEXT_PUBLIC_NETWORK_CURRENCY_SYMBOL=ETH +NEXT_PUBLIC_NETWORK_CURRENCY_DECIMALS=18 +NEXT_PUBLIC_NETWORK_RPC_URL=https://partner-rpc.garnetchain.com/tireless-strand-dreamt-overcome + +# api configuration +NEXT_PUBLIC_API_HOST=explorer.garnetchain.com +NEXT_PUBLIC_API_BASE_PATH=/ + +# ui config +## homepage +NEXT_PUBLIC_HOMEPAGE_CHARTS=['daily_txs'] +## views +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'}] +# app features +NEXT_PUBLIC_APP_INSTANCE=local +NEXT_PUBLIC_APP_ENV=development +NEXT_PUBLIC_GRAPHIQL_TRANSACTION=0xf7d4972356e6ae44ae948d0cf19ef2beaf0e574c180997e969a2837da15e349d +NEXT_PUBLIC_IS_ACCOUNT_SUPPORTED=true +NEXT_PUBLIC_AUTH_URL=http://localhost:3000/login +NEXT_PUBLIC_API_WEBSOCKET_PROTOCOL=ws +NEXT_PUBLIC_LOGOUT_URL=https://blockscoutcom.us.auth0.com/v2/logout +NEXT_PUBLIC_VISUALIZE_API_HOST=https://visualizer.services.blockscout.com +NEXT_PUBLIC_TRANSACTION_INTERPRETATION_PROVIDER=blockscout +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_AD_BANNER_PROVIDER=none +## sidebar +NEXT_PUBLIC_NETWORK_ICON=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/network-icons/garnet.svg +NEXT_PUBLIC_NETWORK_LOGO=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/network-logos/garnet.svg +NEXT_PUBLIC_NETWORK_ICON_DARK=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/network-icons/garnet-dark.svg +NEXT_PUBLIC_NETWORK_LOGO_DARK=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/network-logos/garnet-dark.svg +NEXT_PUBLIC_HOMEPAGE_PLATE_BACKGROUND=rgb(169, 31, 47) +NEXT_PUBLIC_OG_DESCRIPTION="Redstone is the home for onchain games, worlds, and other MUD applications" +# rollup +NEXT_PUBLIC_ROLLUP_TYPE=optimistic +NEXT_PUBLIC_ROLLUP_L1_BASE_URL=https://eth-holesky.blockscout.com/ +NEXT_PUBLIC_ROLLUP_L2_WITHDRAWAL_URL=https://garnet.qry.live/withdraw +NEXT_PUBLIC_HAS_MUD_FRAMEWORK=true \ No newline at end of file diff --git a/configs/envs/.env.gnosis b/configs/envs/.env.gnosis index 1e20f6c303..fad2dde745 100644 --- a/configs/envs/.env.gnosis +++ b/configs/envs/.env.gnosis @@ -1,53 +1,69 @@ -# 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 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_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 e1f80c7e75..abe2107a80 100644 --- a/configs/envs/.env.jest +++ b/configs/envs/.env.jest @@ -24,7 +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_PLATE_BACKGROUND= ## sidebar NEXT_PUBLIC_NETWORK_LOGO= diff --git a/configs/envs/.env.main b/configs/envs/.env.main index 48eee58328..8303446fc1 100644 --- a/configs/envs/.env.main +++ b/configs/envs/.env.main @@ -1,58 +1,68 @@ -# 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 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_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=/ - -# 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_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_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_SECURITY_REPORTS_URL=https://gist.githubusercontent.com/maxaleks/ce5c7e3de53e8f5b240b88265daf5839/raw/328383c958a8f7ecccf6d50c953bcdf8ab3faa0a/security_reports_goerli_test.json -NEXT_PUBLIC_MARKETPLACE_SUBMIT_FORM=https://airtable.com/shrqUAcjgGJ4jU88C +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_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_SWAP_BUTTON_URL=uniswap +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_SAFE_TX_SERVICE_URL=https://safe-transaction-sepolia.safe.global +NEXT_PUBLIC_SENTRY_ENABLE_TRACING=true +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 diff --git a/configs/envs/.env.main.L2 b/configs/envs/.env.main.L2 deleted file mode 100644 index e410b29b8e..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_ROLLUP_TYPE=optimistic -NEXT_PUBLIC_ROLLUP_L1_BASE_URL=https://blockscout-main.k8s-dev.blockscout.com -NEXT_PUBLIC_ROLLUP_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..f23300e95f --- /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 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..cb66ee2c0b --- /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 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..8636924480 --- /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 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..4fe0631ac0 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 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 223abf5f32..dec30c09fe 100644 --- a/configs/envs/.env.pw +++ b/configs/envs/.env.pw @@ -25,7 +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 ## sidebar ## footer NEXT_PUBLIC_GIT_TAG=v1.0.11 @@ -51,5 +50,7 @@ NEXT_PUBLIC_AUTH0_CLIENT_ID=xxx 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 \ No newline at end of file +NEXT_PUBLIC_WALLET_CONNECT_PROJECT_ID=xxx 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..a4218a2fac --- /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 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.sepolia b/configs/envs/.env.sepolia deleted file mode 100644 index 72a98f40a4..0000000000 --- a/configs/envs/.env.sepolia +++ /dev/null @@ -1,63 +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 -NEXT_PUBLIC_HAS_USER_OPS=true -NEXT_PUBLIC_AD_BANNER_PROVIDER=getit -NEXT_PUBLIC_DATA_AVAILABILITY_ENABLED=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.stability_testnet b/configs/envs/.env.stability_testnet new file mode 100644 index 0000000000..c057fda27c --- /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 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.zkevm b/configs/envs/.env.zkevm index 9d1ef2487a..1bc614e2d7 100644 --- a/configs/envs/.env.zkevm +++ b/configs/envs/.env.zkevm @@ -1,51 +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 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://zkevm-rpc.com - -# api configuration -NEXT_PUBLIC_API_HOST=zkevm.blockscout.com -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/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_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_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)' -## 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_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_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_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_ROLLUP_L1_BASE_URL=https://polygon.blockscout.com +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_WEB3_WALLETS=['token_pocket', 'metamask'] diff --git a/configs/envs/.env.zksync b/configs/envs/.env.zksync index 6693f0ca4e..19d27abd49 100644 --- a/configs/envs/.env.zksync +++ b/configs/envs/.env.zksync @@ -1,59 +1,52 @@ -# Set of ENVs for zkSync (dev only) -# https://zksync.blockscout.com/ +# Set of ENVs for ZkSync Era network explorer +# https://zksync.blockscout.com +# This is an auto-generated file. To update all values, run "yarn preset:sync --name=zksync" -# 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=ZkSync Era -NEXT_PUBLIC_NETWORK_SHORT_NAME=ZkSync Era -NEXT_PUBLIC_NETWORK_ID=324 -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://mainnet.era.zksync.io +# Instance ENVs +NEXT_PUBLIC_VIEWS_CONTRACT_EXTRA_VERIFICATION_METHODS=none -# api configuration -NEXT_PUBLIC_API_HOST=zksync.blockscout.com -NEXT_PUBLIC_API_PORT=80 -NEXT_PUBLIC_API_PROTOCOL=http +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'] -## sidebar -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/zksync.svg -NEXT_PUBLIC_NETWORK_LOGO_DARK=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/network-logos/zksync-dark.svg -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_HOMEPAGE_PLATE_BACKGROUND='rgba(53, 103, 246, 1)' -NEXT_PUBLIC_HOMEPAGE_PLATE_TEXT_COLOR='rgba(255, 255, 255, 1)' -NEXT_PUBLIC_OTHER_LINKS=[{'url':'https://zksync.drpc.org?ref=559183','text':'Public RPC'}] -## footer +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 -## views -NEXT_PUBLIC_VIEWS_CONTRACT_SOLIDITYSCAN_ENABLED=false -## misc -NEXT_PUBLIC_NETWORK_EXPLORERS=[{'title':'l2scan','baseUrl':'https://zksync-era.l2scan.co/','paths':{'tx':'/tx','address':'/address','token':'/token','block':'/block'}}] -NEXT_PUBLIC_OG_IMAGE_URL=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/og-images/zksync.png -# app features -NEXT_PUBLIC_APP_INSTANCE=local -NEXT_PUBLIC_APP_ENV=development 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_AUTH_URL=http://localhost:3000 -NEXT_PUBLIC_API_WEBSOCKET_PROTOCOL=ws NEXT_PUBLIC_LOGOUT_URL=https://zksync.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_SAFE_TX_SERVICE_URL=https://safe-transaction-zksync.safe.global -# rollup +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_ROLLUP_L1_BASE_URL=https://eth.blockscout.com +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 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 cc9f4a09a7..d21e63482d 100755 --- a/deploy/scripts/download_assets.sh +++ b/deploy/scripts/download_assets.sh @@ -17,6 +17,7 @@ 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_FEATURED_NETWORKS" "NEXT_PUBLIC_FOOTER_LINKS" "NEXT_PUBLIC_NETWORK_LOGO" @@ -48,10 +49,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 @@ -79,16 +84,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..298303c7a0 100755 --- a/deploy/scripts/entrypoint.sh +++ b/deploy/scripts/entrypoint.sh @@ -1,7 +1,42 @@ #!/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 diff --git a/deploy/scripts/favicon_generator.sh b/deploy/scripts/favicon_generator.sh index 4f153dcb0b..0cfc9f7f8a 100755 --- a/deploy/scripts/favicon_generator.sh +++ b/deploy/scripts/favicon_generator.sh @@ -10,7 +10,7 @@ if [ $? -ne 0 ]; then 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/yarn.lock b/deploy/tools/affected-tests/yarn.lock index e160e0a2c6..385918d425 100644 --- a/deploy/tools/affected-tests/yarn.lock +++ b/deploy/tools/affected-tests/yarn.lock @@ -91,11 +91,11 @@ brace-expansion@^1.1.7: concat-map "0.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== + 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" color-name@^1.1.4: version "1.1.4" @@ -276,10 +276,10 @@ filing-cabinet@^4.1.6: tsconfig-paths "^4.2.0" typescript "^5.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" diff --git a/deploy/tools/envs-validator/index.ts b/deploy/tools/envs-validator/index.ts index 9770ea6399..d2c35ba50a 100644 --- a/deploy/tools/envs-validator/index.ts +++ b/deploy/tools/envs-validator/index.ts @@ -20,6 +20,7 @@ async function run() { return result; }, {} as Record); + printDeprecationWarning(appEnvs); await checkPlaceholdersCongruity(appEnvs); await validateEnvs(appEnvs); @@ -135,3 +136,15 @@ function getEnvsPlaceholders(filePath: string): Promise> { }); }); } + +function printDeprecationWarning(envsMap: Record) { + 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'); + } +} diff --git a/deploy/tools/envs-validator/schema.ts b/deploy/tools/envs-validator/schema.ts index c9e3bf5aa5..77992c3b41 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 @@ -11,12 +12,15 @@ import * as yup from 'yup'; import type { AdButlerConfig } from '../../../types/client/adButlerConfig'; 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 type { ContractCodeIde } from '../../../types/client/contract'; +import { SMART_CONTRACT_EXTRA_VERIFICATION_METHODS, 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 { NAVIGATION_LINK_IDS } from '../../../types/client/navigation-items'; -import type { NavItemExternal, NavigationLinkId } from '../../../types/client/navigation-items'; +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'; @@ -25,8 +29,11 @@ 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 { 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 { AddressViewId } from '../../../types/views/address'; import { ADDRESS_VIEWS_IDS, IDENTICON_TYPES } from '../../../types/views/address'; import { BLOCK_FIELDS_IDS } from '../../../types/views/block'; @@ -37,6 +44,7 @@ import { TX_ADDITIONAL_FIELDS_IDS, TX_FIELDS_IDS } from '../../../types/views/tx import { replaceQuotes } from '../../../configs/app/utils'; import * as regexp from '../../../lib/regexp'; +import type { IconName } from '../../../ui/shared/IconSvg'; const protocols = [ 'http', 'https' ]; @@ -194,6 +202,46 @@ const marketplaceSchema = yup // 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'), + }), }); const beaconChainSchema = yup @@ -228,7 +276,7 @@ const rollupSchema = yup .when('NEXT_PUBLIC_ROLLUP_TYPE', { is: (value: string) => value === 'optimistic', then: (schema) => schema.test(urlTest).required(), - otherwise: (schema) => schema.max(-1, 'NEXT_PUBLIC_ROLLUP_L2_WITHDRAWAL_URL cannot not be used if NEXT_PUBLIC_ROLLUP_TYPE is not defined'), + otherwise: (schema) => schema.max(-1, 'NEXT_PUBLIC_ROLLUP_L2_WITHDRAWAL_URL can be used only if NEXT_PUBLIC_ROLLUP_TYPE is set to \'optimistic\' '), }), }); @@ -324,13 +372,6 @@ const accountSchema = yup 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"'), - }), }); const featuredNetworkSchema: yup.ObjectSchema = yup @@ -350,6 +391,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(), @@ -430,6 +499,17 @@ 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 schema = yup .object() .noUnknown(true, (params) => { @@ -457,8 +537,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(), // 3. API configuration @@ -474,10 +566,31 @@ 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_AVG_BLOCK_TIME: yup.boolean(), + 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 @@ -494,6 +607,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), @@ -518,6 +637,21 @@ const schema = yup .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_TX_HIDDEN_FIELDS: yup .array() .transform(replaceQuotes) @@ -549,14 +683,46 @@ const schema = yup 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) => { @@ -575,15 +741,68 @@ 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_SWAP_BUTTON_URL: yup.string(), + NEXT_PUBLIC_MULTICHAIN_BALANCE_PROVIDER_CONFIG: yup + .mixed() + .test('shape', 'Invalid schema were provided for NEXT_PUBLIC_MULTICHAIN_BALANCE_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_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_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_SAVE_ON_GAS_ENABLED: yup.boolean(), // 6. External services envs NEXT_PUBLIC_WALLET_CONNECT_PROJECT_ID: yup.string(), 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.alt b/deploy/tools/envs-validator/test/.env.alt new file mode 100644 index 0000000000..6482ea4197 --- /dev/null +++ b/deploy/tools/envs-validator/test/.env.alt @@ -0,0 +1,4 @@ +NEXT_PUBLIC_GRAPHIQL_TRANSACTION=none +NEXT_PUBLIC_API_SPEC_URL=none +NEXT_PUBLIC_VIEWS_CONTRACT_EXTRA_VERIFICATION_METHODS=none +NEXT_PUBLIC_HOMEPAGE_STATS=[] \ 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 559c63fc79..f5231bdb0e 100644 --- a/deploy/tools/envs-validator/test/.env.base +++ b/deploy/tools/envs-validator/test/.env.base @@ -1,3 +1,15 @@ +NEXT_PUBLIC_SENTRY_DSN=https://sentry.io +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 +FAVICON_GENERATOR_API_KEY=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 +21,38 @@ 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_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_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_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,19 +62,27 @@ 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_HIDDEN_VIEWS=['top_accounts'] +NEXT_PUBLIC_VIEWS_CONTRACT_EXTRA_VERIFICATION_METHODS=['solidity-hardhat','solidity-foundry'] 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_SWAP_BUTTON_URL=uniswap -NEXT_PUBLIC_VALIDATORS_CHAIN_TYPE=stability \ No newline at end of file +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 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 index eaf12c6dc9..6cc6b1f839 100644 --- a/deploy/tools/envs-validator/test/.env.marketplace +++ b/deploy/tools/envs-validator/test/.env.marketplace @@ -5,3 +5,8 @@ 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.rollup b/deploy/tools/envs-validator/test/.env.rollup index 1cb3e1af88..4cff2bb371 100644 --- a/deploy/tools/envs-validator/test/.env.rollup +++ b/deploy/tools/envs-validator/test/.env.rollup @@ -1,3 +1,4 @@ NEXT_PUBLIC_ROLLUP_TYPE=optimistic NEXT_PUBLIC_ROLLUP_L1_BASE_URL=https://example.com -NEXT_PUBLIC_ROLLUP_L2_WITHDRAWAL_URL=https://example.com \ No newline at end of file +NEXT_PUBLIC_ROLLUP_L2_WITHDRAWAL_URL=https://example.com +NEXT_PUBLIC_FAULT_PROOF_ENABLED=true 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/configs/marketplace_categories.json b/deploy/tools/envs-validator/test/assets/configs/marketplace_categories.json new file mode 100644 index 0000000000..15b31a5557 --- /dev/null +++ b/deploy/tools/envs-validator/test/assets/configs/marketplace_categories.json @@ -0,0 +1,5 @@ +[ + "Swaps", + "Bridges", + "NFT" +] 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..6ea4de04cf 100644 --- a/deploy/tools/envs-validator/yarn.lock +++ b/deploy/tools/envs-validator/yarn.lock @@ -262,11 +262,11 @@ ansi-styles@^4.1.0: 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== + 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" @@ -439,10 +439,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" diff --git a/deploy/tools/favicon-generator/script.sh b/deploy/tools/favicon-generator/script.sh index 69145399ed..444932c723 100755 --- a/deploy/tools/favicon-generator/script.sh +++ b/deploy/tools/favicon-generator/script.sh @@ -33,9 +33,12 @@ CONFIG_TEMPLATE_FILE="config.template.json" # Path to the generated config JSON file CONFIG_FILE="config.json" +# Escape special characters in MASTER_URL for sed +ESCAPED_MASTER_URL=$(printf '%s\n' "$MASTER_URL" | sed -e 's/[\/&]/\\&/g') + # 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" +sed -e "s||$API_KEY_VALUE|" -e "s||$ESCAPED_MASTER_URL|" "$CONFIG_TEMPLATE_FILE" > "$CONFIG_FILE" # Make the API POST request with JSON data from the config file echo "⏳ Making request to API..." diff --git a/deploy/tools/feature-reporter/yarn.lock b/deploy/tools/feature-reporter/yarn.lock index eaf396937e..502e97507e 100644 --- a/deploy/tools/feature-reporter/yarn.lock +++ b/deploy/tools/feature-reporter/yarn.lock @@ -289,11 +289,11 @@ binary-extensions@^2.0.0: 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== + 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" @@ -491,10 +491,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" diff --git a/deploy/values/l2-optimism-goerli/values.yaml b/deploy/values/l2-optimism-goerli/values.yaml deleted file mode 100644 index afd8dcc1a0..0000000000 --- a/deploy/values/l2-optimism-goerli/values.yaml +++ /dev/null @@ -1,206 +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_ENABLED: true - NEXT_PUBLIC_MARKETPLACE_SUBMIT_FORM: https://airtable.com/shrqUAcjgGJ4jU88C - NEXT_PUBLIC_MARKETPLACE_SUGGEST_IDEAS_FORM: https://airtable.com/appiy5yijZpMMSKjT/pag3t82DUCyhGRZZO/form - 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_ROLLUP_TYPE: optimistic - NEXT_PUBLIC_ROLLUP_L1_BASE_URL: https://eth-goerli.blockscout.com/ - NEXT_PUBLIC_ROLLUP_L2_WITHDRAWAL_URL: https://app.optimism.io/bridge/withdraw - NEXT_PUBLIC_GRAPHIQL_TRANSACTION: 0x4a0ed8ddf751a7cb5297f827699117b0f6d21a0b2907594d300dc9fed75c7e62 - NEXT_PUBLIC_SWAP_BUTTON_URL: sushiswap - 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 493941ad0a..0000000000 --- a/deploy/values/main/values.yaml +++ /dev/null @@ -1,178 +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: 10 - INDEXER_TOKEN_BALANCES_BATCH_SIZE: 15 - INDEXER_TOKEN_BALANCES_CONCURRENCY: 4 - 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_ENABLED: true - NEXT_PUBLIC_MARKETPLACE_SUBMIT_FORM: https://airtable.com/shrqUAcjgGJ4jU88C - NEXT_PUBLIC_MARKETPLACE_SUGGEST_IDEAS_FORM: https://airtable.com/appiy5yijZpMMSKjT/pag3t82DUCyhGRZZO/form - 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'}]" - NEXT_PUBLIC_SWAP_BUTTON_URL: uniswap - 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 eea25de5e8..d03f0984eb 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 @@ -49,32 +49,29 @@ frontend: env: NEXT_PUBLIC_APP_ENV: development NEXT_PUBLIC_APP_INSTANCE: review_L2 - 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/dev/configs/featured-networks/base-goerli.json - NEXT_PUBLIC_API_HOST: blockscout-optimism-goerli.k8s-dev.blockscout.com - NEXT_PUBLIC_MARKETPLACE_ENABLED: true - NEXT_PUBLIC_MARKETPLACE_SUBMIT_FORM: https://airtable.com/shrqUAcjgGJ4jU88C - NEXT_PUBLIC_MARKETPLACE_SUGGEST_IDEAS_FORM: https://airtable.com/appiy5yijZpMMSKjT/pag3t82DUCyhGRZZO/form + 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_ROLLUP_TYPE=optimistic - NEXT_PUBLIC_ROLLUP_L1_BASE_URL=https://blockscout-main.k8s-dev.blockscout.com - NEXT_PUBLIC_ROLLUP_L2_WITHDRAWAL_URL=https://app.optimism.io/bridge/withdraw + 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_SWAP_BUTTON_URL: sushiswap + 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 @@ -83,4 +80,4 @@ frontend: 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 diff --git a/deploy/values/review/values.yaml.gotmpl b/deploy/values/review/values.yaml.gotmpl index 7f6418f43f..5b2623182f 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: 11155111 + id: "11155111" name: Blockscout shortname: Blockscout currency: @@ -49,47 +49,35 @@ frontend: 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-sepolia.blockscout.com - NEXT_PUBLIC_STATS_API_HOST: https://stats-goerli.k8s-dev.blockscout.com/ + 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_ENABLED: true - 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_MARKETPLACE_SUGGEST_IDEAS_FORM: https://airtable.com/appiy5yijZpMMSKjT/pag3t82DUCyhGRZZO/form - NEXT_PUBLIC_MARKETPLACE_SECURITY_REPORTS_URL: https://gist.githubusercontent.com/maxaleks/ce5c7e3de53e8f5b240b88265daf5839/raw/328383c958a8f7ecccf6d50c953bcdf8ab3faa0a/security_reports_goerli_test.json 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://eth-sepolia.public.blastapi.io - NEXT_PUBLIC_NETWORK_ID: '11155111' 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_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_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: noves - NEXT_PUBLIC_SWAP_BUTTON_URL: uniswap + NEXT_PUBLIC_TRANSACTION_INTERPRETATION_PROVIDER: blockscout NEXT_PUBLIC_HAS_CONTRACT_AUDIT_REPORTS: true - NEXT_PUBLIC_AD_BANNER_PROVIDER: getit - NEXT_PUBLIC_AD_BANNER_ADDITIONAL_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_AD_BANNER_PROVIDER: slise NEXT_PUBLIC_DATA_AVAILABILITY_ENABLED: true + NEXT_PUBLIC_NAVIGATION_HIGHLIGHTED_ROUTES: "['/apps']" + PROMETHEUS_METRICS_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 diff --git a/docs/BUILD-TIME_ENVS.md b/docs/BUILD-TIME_ENVS.md index 08524e4389..7f0db07fc2 100644 --- a/docs/BUILD-TIME_ENVS.md +++ b/docs/BUILD-TIME_ENVS.md @@ -6,3 +6,4 @@ These variables are passed to the app during the image build process. They canno | --- | --- | --- | --- | --- | | 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_OPEN_TELEMETRY_ENABLED | `boolean` | Enables OpenTelemetry SDK | true | `true` | diff --git a/docs/CONTRIBUTING.md b/docs/CONTRIBUTING.md index 58dcae80c9..0284e6e3cf 100644 --- a/docs/CONTRIBUTING.md +++ b/docs/CONTRIBUTING.md @@ -88,9 +88,7 @@ These are the steps that you have to follow to make everything work: 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 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: diff --git a/docs/DEPRECATED_ENVS.md b/docs/DEPRECATED_ENVS.md index b46ea04108..f48fa86e98 100644 --- a/docs/DEPRECATED_ENVS.md +++ b/docs/DEPRECATED_ENVS.md @@ -1,9 +1,13 @@ # Deprecated environment variables -| Variable | Type | Description | Compulsoriness | Default value | Example value | Deprecated in version | Comment | -| --- | --- | --- | --- | --- | --- | --- | --- | -| NEXT_PUBLIC_IS_OPTIMISTIC_L2_NETWORK | `boolean` | Set to true for optimistic L2 solutions | Required | - | `true` | 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.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.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 | \ No newline at end of file +| 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 | +| 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 | Replaces by NEXT_PUBLIC_HOMEPAGE_STATS diff --git a/docs/ENVS.md b/docs/ENVS.md index 3058167161..595ceb6366 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) @@ -34,7 +34,7 @@ Please be aware that all environment variables prefixed with `NEXT_PUBLIC_` will - [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) + - [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) @@ -49,55 +49,66 @@ 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) - [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) - [OpenTelemetry](ENVS.md#opentelemetry) - - [Swap button](ENVS.md#swap-button) + - [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) - [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_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+ |   ## 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+ |   @@ -105,26 +116,40 @@ Please be aware that all environment variables prefixed with `NEXT_PUBLIC_` will ### Homepage +| 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+ | + +#### 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_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_AVG_BLOCK_TIME | `boolean` | Set to false if average block time is useless for the network | - | `true` | `false` | +| 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']}}` |   -### Sidebar +### Navigation -| 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']` | +| 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+ | #### Featured network configuration properties @@ -141,9 +166,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,22 +185,24 @@ The app version shown in the footer is derived from build-time ENV variables `NE 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. -| 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_GENERATOR_API_KEY | `string` | RealFaviconGenerator [API key](https://realfavicongenerator.net/api/) | Required | - | `` | v1.16.0+ | +| 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 | - | - | `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.` | 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+ |   @@ -183,9 +210,9 @@ 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 @@ -195,21 +222,23 @@ Settings for meta tags and OG tags | `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" \| "universal_profile\|[identicon]"` | Type of identicon displayed next to addresses. In case of universal_profile you can provide a second identicon type that will be displayed when no universal profile is found | 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 [LUKSO Universal Profile](https://universalprofile.cloud/) | - | `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` | -| NEXT_PUBLIC_UNIVERSAL_PROFILES_API_URL | `string` | LUKSO UNIVERSAL PROFILE API URL used for getting various universal profile data | - | - | `https://api.universalprofile.com` | -| NEXT_PUBLIC_ALGOLIA_APP_ID | `string` | Algolia App's ID | - | - | `ABCD1234` | -| NEXT_PUBLIC_ALGOLIA_API_KEY | `string` | Algolia API key used for authenticating requests | - | - | `abcd1234defg5678` | -| NEXT_PUBLIC_ALGOLIA_INDEX_NAME | `string` | Index name from where data should be requested | - | - | `prod_mainnet_data` | -| 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" \| "universal_profile\|[identicon]` | Type of identicon displayed next to addresses. In case of universal_profile you can provide a second identicon type that will be displayed when no universal profile is found | 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 [LUKSO Universal Profile](https://universalprofile.cloud/)| - | `jazzicon` | `gradient_avatar` | v1.12.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_UNIVERSAL_PROFILES_API_URL | `string` | LUKSO UNIVERSAL PROFILE API URL used for getting various universal profile data | - | - | `https://api.universalprofile.com` | - | +| NEXT_PUBLIC_ALGOLIA_APP_ID | `string` | Algolia App's ID | - | - | `ABCD1234` | - | +| NEXT_PUBLIC_ALGOLIA_API_KEY | `string` | Algolia API key used for authenticating requests | - | - | `abcd1234defg5678 `| - | +| NEXT_PUBLIC_ALGOLIA_INDEX_NAME | `string` | Index name from where data should be | - | - | `prod_mainnet_data`| - | ##### Address views list | Id | Description | @@ -220,10 +249,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 | @@ -234,6 +263,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 | @@ -244,9 +275,9 @@ 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+ | ##### NFT marketplace properties @@ -263,14 +294,18 @@ 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_HAS_CONTRACT_AUDIT_REPORTS | `boolean` | Set to `true` to enable Submit Audit form on the contract page | - | `false` | `true` | -| 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 @@ -291,6 +326,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 @@ -299,12 +341,12 @@ 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_AUTH0_CLIENT_ID | `string` | Client id for [Auth0](https://auth0.com/) provider | Required | - | `` | v1.0.x+ | +| 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` | v1.0.x+ | +| 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` | v1.0.x+ |   @@ -312,10 +354,10 @@ Settings for meta tags and OG tags 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 | -| --- | --- | --- | --- | --- | --- | -| NEXT_PUBLIC_GAS_TRACKER_ENABLED | `boolean` | Set to true to enable "Gas tracker" in the app | Required | `true` | `false` | -| 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' ]` | +| 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+ |   @@ -323,37 +365,38 @@ This feature is **enabled by default**. To switch it off pass `NEXT_PUBLIC_GAS_T *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://cloud.walletconnect.com/) 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` \| `hype` \| `getit` \| `none` | Ads provider | - | `slise` | `coinzilla` | -| NEXT_PUBLIC_AD_BANNER_ADDITIONAL_PROVIDER | `adbutler` | Additional ads provider to mix with the main one | - | - | `adbutler` | -| 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+ |   @@ -361,60 +404,70 @@ 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+ |   ### Rollup chain -| Variable | Type| Description | Compulsoriness | Default value | Example value | -| --- | --- | --- | --- | --- | --- | -| NEXT_PUBLIC_ROLLUP_TYPE | `'optimistic' \| 'shibarium' \| 'zkEvm' \| 'zkSync' ` | Rollup chain type | Required | - | `'optimistic'` | -| NEXT_PUBLIC_ROLLUP_L1_BASE_URL | `string` | Blockscout base URL for L1 network | Required | - | `'http://eth-goerli.blockscout.com'` | -| NEXT_PUBLIC_ROLLUP_L2_WITHDRAWAL_URL | `string` | URL for L2 -> L1 withdrawals | - | - | `https://app.optimism.io/bridge/withdraw` | +| Variable | Type| Description | Compulsoriness | Default value | Example value | Version | +| --- | --- | --- | --- | --- | --- | --- | +| NEXT_PUBLIC_ROLLUP_TYPE | `'optimistic' \| 'arbitrum' \| 'shibarium' \| 'zkEvm' \| 'zkSync' ` | 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+ |   ### 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 | Version | +| --- | --- | --- | --- | --- | --- | --- | +| NEXT_PUBLIC_GROWTH_BOOK_CLIENT_KEY | `string` | Client SDK key for [GrowthBook](https://www.growthbook.io/) service | true | - | `` | v1.22.0+ |   @@ -428,45 +481,52 @@ This feature is **enabled by default** with the `coinzilla` ads provider. To swi ### 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_ENABLED | `boolean` | `true` means that the marketplace page will be enabled | - | - | `true` | -| 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` | -| 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` | -| 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_MARKETPLACE_SUGGEST_IDEAS_FORM | `string` | Link to form where users can suggest ideas for the marketplace | - | - | `https://airtable.com/appiy5yijZpMMSKjT/pag3t82DUCyhGRZZO/form` | -| 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 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` | -| 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` | +| 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+ | #### 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'` | @@ -477,36 +537,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+ |   @@ -514,26 +561,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` \| `noves` \| `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+ |   @@ -541,9 +588,30 @@ 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+ |   @@ -551,9 +619,9 @@ This feature allows resolving blockchain addresses using human-readable domain n 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 | -| --- | --- | --- | --- | --- | --- | -| NEXT_PUBLIC_DATA_AVAILABILITY_ENABLED | `boolean` | Set to true to enable blob transactions views. | Required | - | `true` | +| 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+ |   @@ -561,10 +629,10 @@ This feature enables views related to blob transactions (EIP-4844), such as the 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 @@ -591,9 +659,9 @@ This feature allows users to view tokens that have been bridged from other EVM c 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 | -| --- | --- | --- | --- | --- | --- | -| 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` | +| 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+ |   @@ -601,9 +669,29 @@ For the smart contract addresses which are [Safe{Core} accounts](https://safe.gl 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 | -| --- | --- | --- | --- | --- | --- | -| 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` | +| 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+ | + +  + +### 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+ |   @@ -629,23 +717,85 @@ The feature enables the Validators page which provides detailed information abou ### Sentry error monitoring +| 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+ | +| NEXT_PUBLIC_APP_ENV | `string` | App env (e.g development, review or production). Passed as `environment` property to Sentry config | - | `production` | `production` | v1.0.x+ | +| 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` | v1.0.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). 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 | | --- | --- | --- | --- | --- | --- | -| 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` | +| 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` |   -### OpenTelemetry +### Get gas button + +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. -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). +| 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 | | --- | --- | --- | --- | --- | --- | -| OTEL_SDK_ENABLED | `boolean` | Flag to enable the feature | Required | `false` | `true` | +| 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+ |   @@ -665,6 +815,6 @@ If the feature is enabled, a Swap button will be displayed at the top of the exp For obtaining the variables values please refer to [reCAPTCHA documentation](https://developers.google.com/recaptcha). -| Variable | Type| Description | Compulsoriness | Default value | Example value | -| --- | --- | --- | --- | --- | --- | -| NEXT_PUBLIC_RE_CAPTCHA_APP_SITE_KEY | `string` | Site key | - | - | `` | +| Variable | Type| Description | Compulsoriness | Default value | Example value | Version | +| --- | --- | --- | --- | --- | --- | --- | +| NEXT_PUBLIC_RE_CAPTCHA_APP_SITE_KEY | `string` | Site key | - | - | `` | v1.0.x+ | 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/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_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/blob.svg b/icons/blob.svg index 9dc2b542ff..9b40d72ecb 100644 --- a/icons/blob.svg +++ b/icons/blob.svg @@ -1,5 +1,5 @@ - - - - + + + + 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/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 785174078b..110cd4b797 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/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/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/empty_search_result.svg b/icons/empty_search_result.svg index a60b3b1e70..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/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_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/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/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/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/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 2bdea23a41..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 bf2eca9845..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 index 63d915c99b..c1566be5fc 100644 --- a/icons/swap.svg +++ b/icons/swap.svg @@ -1,3 +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/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 8f04aff392..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..5cae612a92 100644 --- a/instrumentation.node.ts +++ b/instrumentation.node.ts @@ -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/jest/lib.tsx b/jest/lib.tsx index 048f26733a..f0f439e5f6 100644 --- a/jest/lib.tsx +++ b/jest/lib.tsx @@ -8,20 +8,16 @@ 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: '', - adBannerProvider: '', + query: {}, + adBannerProvider: null, + apiData: null, }; const TestApp = ({ children }: {children: React.ReactNode}) => { 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.ts b/lib/api/buildUrl.ts index 07805dbd9c..ea7e9fac3f 100644 --- a/lib/api/buildUrl.ts +++ b/lib/api/buildUrl.ts @@ -10,11 +10,12 @@ export default function buildUrl( resourceName: R, pathParams?: ResourcePathParams, queryParams?: Record | number | boolean | null | undefined>, + noProxy?: boolean, ): string { const resource: ApiResource = RESOURCES[resourceName]; - const baseUrl = isNeedProxy() ? config.app.baseUrl : (resource.endpoint || config.api.endpoint); + const baseUrl = !noProxy && isNeedProxy() ? config.app.baseUrl : (resource.endpoint || config.api.endpoint); const basePath = resource.basePath !== undefined ? resource.basePath : config.api.basePath; - const path = isNeedProxy() ? '/node-api/proxy' + basePath + resource.path : basePath + resource.path; + 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 ]) => { diff --git a/lib/api/resources.ts b/lib/api/resources.ts index ae6547ae87..dfb166b3d2 100644 --- a/lib/api/resources.ts +++ b/lib/api/resources.ts @@ -1,8 +1,10 @@ +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, @@ -29,33 +31,52 @@ import type { AddressNFTsResponse, AddressCollectionsResponse, AddressNFTTokensFilter, + AddressMudTables, + AddressMudTablesFilter, + AddressMudRecords, + AddressMudRecordsFilter, + AddressMudRecordsSorting, + AddressMudRecord, } from 'types/api/address'; -import type { AddressesResponse } from 'types/api/addresses'; +import type { AddressesResponse, AddressesMetadataSearchResult, AddressesMetadataSearchFilters } from 'types/api/addresses'; +import type { AddressMetadataInfo, PublicTagTypesResponse } from 'types/api/addressMetadata'; +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 { ChartMarketResponse, ChartTransactionResponse } from 'types/api/charts'; -import type { BackendVersionConfig } from 'types/api/configs'; +import type { + BlocksResponse, + BlockTransactionsResponse, + Block, + BlockFilters, + BlockWithdrawalsResponse, + 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, + SmartContractVerificationConfigRaw, SmartContractSecurityAudits, } 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 { LogsResponseTx, LogsResponseAddress } from 'types/api/log'; +import type { MudWorldsResponse } from 'types/api/mudWorlds'; import type { NovesAccountHistoryResponse, NovesDescribeTxsResponse, NovesResponseData } from 'types/api/noves'; import type { OptimisticL2DepositsResponse, @@ -63,11 +84,15 @@ import type { OptimisticL2OutputRootsResponse, OptimisticL2TxnBatchesResponse, OptimisticL2WithdrawalsResponse, + OptimisticL2DisputeGamesResponse, + OptimismL2TxnBatch, + OptimismL2BatchTxs, + OptimismL2BatchBlocks, } from 'types/api/optimisticL2'; import type { RawTracesResponse } from 'types/api/rawTrace'; import type { SearchRedirectResult, SearchResult, SearchResultFilters, SearchResultItem } from 'types/api/search'; import type { ShibariumWithdrawalsResponse, ShibariumDepositsResponse } from 'types/api/shibarium'; -import type { Counters, StatsCharts, StatsChart, HomeStats } from 'types/api/stats'; +import type { HomeStats } from 'types/api/stats'; import type { TokenCounters, TokenInfo, @@ -87,16 +112,31 @@ import type { TransactionsResponseWatchlist, TransactionsSorting, 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 { ValidatorsCountersResponse, ValidatorsFilters, ValidatorsResponse, ValidatorsSorting } from 'types/api/validators'; +import type { + ValidatorsStabilityCountersResponse, + ValidatorsStabilityFilters, + ValidatorsStabilityResponse, + ValidatorsStabilitySorting, + ValidatorsBlackfortCountersResponse, + ValidatorsBlackfortResponse, + ValidatorsBlackfortSorting, +} from 'types/api/validators'; import type { VerifiedContractsSorting } from 'types/api/verifiedContracts'; -import type { VisualizedContract } from 'types/api/visualization'; import type { WithdrawalsResponse, WithdrawalsCounters } from 'types/api/withdrawals'; -import type { ZkEvmL2TxnBatch, ZkEvmL2TxnBatchesItem, ZkEvmL2TxnBatchesResponse, ZkEvmL2TxnBatchTxs } from 'types/api/zkEvmL2'; +import type { + ZkEvmL2DepositsResponse, + ZkEvmL2TxnBatch, + ZkEvmL2TxnBatchesItem, + ZkEvmL2TxnBatchesResponse, + ZkEvmL2TxnBatchTxs, + 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'; @@ -129,30 +169,26 @@ export const RESOURCES = { path: '/api/account/v2/email/resend', }, custom_abi: { - path: '/api/account/v2/user/custom_abis/:id?', + path: '/api/account/v2/user/custom_abis{/:id}', pathParams: [ 'id' as const ], }, watchlist: { - path: '/api/account/v2/user/watchlist/:id?', + 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 ], - }, private_tags_address: { - path: '/api/account/v2/user/tags/address/:id?', + path: '/api/account/v2/user/tags/address{/:id}', pathParams: [ 'id' as const ], filterFields: [ ], }, private_tags_tx: { - path: '/api/account/v2/user/tags/transaction/:id?', + path: '/api/account/v2/user/tags/transaction{/:id}', pathParams: [ 'id' as const ], filterFields: [ ], }, api_keys: { - path: '/api/account/v2/user/api_keys/:id?', + path: '/api/account/v2/user/api_keys{/:id}', pathParams: [ 'id' as const ], }, @@ -182,7 +218,7 @@ export const RESOURCES = { }, token_info_applications: { - path: '/api/v1/chains/:chainId/token-info-submissions/:id?', + 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, @@ -213,7 +249,13 @@ export const RESOURCES = { 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 ], + 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, }, domain_info: { path: '/api/v1/:chainId/domains/:name', @@ -232,7 +274,36 @@ export const RESOURCES = { 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 @@ -275,6 +346,19 @@ export const RESOURCES = { 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 ], @@ -345,6 +429,10 @@ export const RESOURCES = { path: '/api/v2/addresses/', filterFields: [ ], }, + addresses_metadata_search: { + path: '/api/v2/proxy/metadata/addresses', + filterFields: [ 'slug' as const, 'tag_type' as const ], + }, // ADDRESS address: { @@ -423,26 +511,6 @@ export const RESOURCES = { 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 ], - }, contract_verification_config: { path: '/api/v2/smart-contracts/verification/config', }, @@ -450,7 +518,7 @@ export const RESOURCES = { path: '/api/v2/smart-contracts/:hash/verification/via/:method', pathParams: [ 'hash' as const, 'method' as const ], }, - contract_solidityscan_report: { + contract_solidity_scan_report: { path: '/api/v2/smart-contracts/:hash/solidityscan-report', pathParams: [ 'hash' as const ], }, @@ -525,6 +593,11 @@ export const RESOURCES = { 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: [], + }, // APP STATS stats: { @@ -539,20 +612,29 @@ export const RESOURCES = { 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', }, - homepage_deposits: { + homepage_optimistic_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', }, homepage_zkevm_l2_batches: { 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', }, @@ -565,6 +647,9 @@ export const RESOURCES = { 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: { @@ -580,43 +665,153 @@ export const RESOURCES = { }, // optimistic L2 - l2_deposits: { + optimistic_l2_deposits: { path: '/api/v2/optimism/deposits', filterFields: [], }, - l2_deposits_count: { + optimistic_l2_deposits_count: { path: '/api/v2/optimism/deposits/count', }, - l2_withdrawals: { + optimistic_l2_withdrawals: { path: '/api/v2/optimism/withdrawals', filterFields: [], }, - l2_withdrawals_count: { + optimistic_l2_withdrawals_count: { path: '/api/v2/optimism/withdrawals/count', }, - l2_output_roots: { + optimistic_l2_output_roots: { path: '/api/v2/optimism/output-roots', filterFields: [], }, - l2_output_roots_count: { + optimistic_l2_output_roots_count: { path: '/api/v2/optimism/output-roots/count', }, - l2_txn_batches: { - path: '/api/v2/optimism/txn-batches', + 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 ], + }, + + // 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 ], + }, + + arbitrum_l2_txn_batches: { + path: '/api/v2/arbitrum/batches', + filterFields: [], + }, + + arbitrum_l2_txn_batches_count: { + path: '/api/v2/arbitrum/batches/count', + }, + + 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_txn_batches_count: { - path: '/api/v2/optimism/txn-batches/count', + arbitrum_l2_txn_batch_blocks: { + path: '/api/v2/blocks/arbitrum-batch/:number', + pathParams: [ 'number' as const ], + filterFields: [], }, // zkEvm L2 + zkevm_l2_deposits: { + path: '/api/v2/zkevm/deposits', + filterFields: [], + }, + + zkevm_l2_deposits_count: { + path: '/api/v2/zkevm/deposits/count', + }, + + zkevm_l2_withdrawals: { + path: '/api/v2/zkevm/withdrawals', + filterFields: [], + }, + + zkevm_l2_withdrawals_count: { + path: '/api/v2/zkevm/withdrawals/count', + }, + zkevm_l2_txn_batches: { path: '/api/v2/zkevm/batches', filterFields: [], @@ -710,14 +905,19 @@ export const RESOURCES = { }, // VALIDATORS - validators: { - path: '/api/v2/validators/:chainType', - pathParams: [ 'chainType' as const ], + validators_stability: { + path: '/api/v2/validators/stability', filterFields: [ 'address_hash' as const, 'state_filter' as const ], }, - validators_counters: { - path: '/api/v2/validators/:chainType/counters', - pathParams: [ 'chainType' 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 @@ -730,11 +930,21 @@ export const RESOURCES = { 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', }, + universal_profile: { path: '', }, @@ -755,6 +965,9 @@ export const RESOURCES = { graphql: { path: '/api/v1/graphql', }, + block_countdown: { + path: '/api', + }, }; export type ResourceName = keyof typeof RESOURCES; @@ -787,23 +1000,26 @@ export interface ResourceError { export type ResourceErrorAccount = ResourceError<{ errors: T }> -export type PaginatedResources = 'blocks' | 'block_txs' | +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' | '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' | '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' | +'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' | -'zkevm_l2_txn_batches' | 'zkevm_l2_txn_batch_txs' | +'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' | 'noves_address_history'; +'domains_lookup' | 'addresses_lookup' | 'user_ops' | 'validators_stability' | 'validators_blackfort' | 'noves_address_history'; export type PaginatedResponse = ResourcePayload; @@ -814,7 +1030,6 @@ export type PaginatedResponse = ResourcePayload export type ResourcePayloadA = 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 : @@ -825,21 +1040,29 @@ 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_deposits' ? 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 'stats_counters' ? Counters : -Q extends 'stats_lines' ? StatsCharts : -Q extends 'stats_line' ? StatsChart : +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 : @@ -854,6 +1077,7 @@ 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 : @@ -868,7 +1092,6 @@ Q extends 'address_tokens' ? AddressTokensResponse : Q extends 'address_nfts' ? AddressNFTsResponse : Q extends 'address_collections' ? AddressCollectionsResponse : Q extends 'address_withdrawals' ? AddressWithdrawalsResponse : -Q extends 'universal_profile' ? Array : Q extends 'token' ? TokenInfo : Q extends 'token_verified_info' ? TokenVerifiedInfo : Q extends 'token_counters' ? TokenCounters : @@ -885,30 +1108,24 @@ 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 'contract_solidity_scan_report' ? unknown : 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' ? OptimisticL2OutputRootsResponse : -Q extends 'l2_withdrawals' ? OptimisticL2WithdrawalsResponse : -Q extends 'l2_deposits' ? OptimisticL2DepositsResponse : -Q extends 'l2_txn_batches' ? OptimisticL2TxnBatchesResponse : -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 '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 @@ -916,24 +1133,47 @@ never; /* eslint-disable @typescript-eslint/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' ? ValidatorsResponse : -Q extends 'validators_counters' ? ValidatorsCountersResponse : +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 'contract_security_audits' ? SmartContractSecurityAudits : -Q extends 'addresses_lookup' ? EnsAddressLookupResponse : -Q extends 'domain_info' ? EnsDomainDetailed : -Q extends 'domain_events' ? EnsDomainEventsResponse : -Q extends 'domains_lookup' ? EnsDomainLookupResponse : +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 : @@ -941,6 +1181,14 @@ 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 'withdrawals' ? WithdrawalsResponse : +Q extends 'withdrawals_counters' ? WithdrawalsCounters : +Q extends 'universal_profile' ? Array: never; /* eslint-enable @typescript-eslint/indent */ @@ -959,6 +1207,7 @@ 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 : @@ -971,7 +1220,9 @@ Q extends 'verified_contracts' ? VerifiedContractsFilters : Q extends 'addresses_lookup' ? EnsAddressLookupFilters : Q extends 'domains_lookup' ? EnsDomainLookupFilters : Q extends 'user_ops' ? UserOpsFilters : -Q extends 'validators' ? ValidatorsFilters : +Q extends 'validators_stability' ? ValidatorsStabilityFilters : +Q extends 'address_mud_tables' ? AddressMudTablesFilter : +Q extends 'address_mud_records' ? AddressMudRecordsFilter : never; /* eslint-enable @typescript-eslint/indent */ @@ -983,6 +1234,8 @@ Q extends 'verified_contracts' ? VerifiedContractsSorting : Q extends 'address_txs' ? TransactionsSorting : Q extends 'addresses_lookup' ? EnsLookupSorting : Q extends 'domains_lookup' ? EnsLookupSorting : -Q extends 'validators' ? ValidatorsSorting : +Q extends 'validators_stability' ? ValidatorsStabilitySorting : +Q extends 'validators_blackfort' ? ValidatorsBlackfortSorting : +Q extends 'address_mud_records' ? AddressMudRecordsSorting : never; /* eslint-enable @typescript-eslint/indent */ diff --git a/lib/api/useApiFetch.tsx b/lib/api/useApiFetch.tsx index 85da773ab1..86e5fa9ea9 100644 --- a/lib/api/useApiFetch.tsx +++ b/lib/api/useApiFetch.tsx @@ -19,7 +19,7 @@ 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; } diff --git a/lib/api/useApiInfiniteQuery.tsx b/lib/api/useApiInfiniteQuery.tsx new file mode 100644 index 0000000000..fd663f1388 --- /dev/null +++ b/lib/api/useApiInfiniteQuery.tsx @@ -0,0 +1,43 @@ +import type { InfiniteData, QueryKey, UseInfiniteQueryResult } from '@tanstack/react-query'; +import { useInfiniteQuery, type UseInfiniteQueryOptions } 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>({ + // eslint-disable-next-line @tanstack/query/exhaustive-deps + 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 0d28e70c54..925266680f 100644 --- a/lib/api/useApiQuery.tsx +++ b/lib/api/useApiQuery.tsx @@ -1,12 +1,16 @@ 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, ResourceError, ResourcePayload>, 'queryKey' | 'queryFn'>; +export interface Params> { + pathParams?: ResourcePathParams; + queryParams?: Record | number | boolean | undefined>; + fetchParams?: Pick; + queryOptions?: Partial, ResourceError, D>, 'queryFn'>>; } export function getResourceKey(resource: R, { pathParams, queryParams }: Params = {}) { @@ -17,20 +21,20 @@ export function getResourceKey(resource: R, { pathParams return [ resource ]; } -export default function useApiQuery( +export default function useApiQuery>( resource: R, - { queryOptions, pathParams, queryParams, fetchParams }: Params = {}, + { queryOptions, pathParams, queryParams, fetchParams }: Params = {}, ) { const apiFetch = useApiFetch(); - return useQuery, ResourceError, ResourcePayload>({ + return useQuery, ResourceError, D>({ // eslint-disable-next-line @tanstack/query/exhaustive-deps - queryKey: getResourceKey(resource, { pathParams, queryParams }), - queryFn: async() => { + 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, fetchParams: { ...fetchParams, signal } }) as Promise>; }, ...queryOptions, }); 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 84f5f896ec..085e676ecc 100644 --- a/lib/contexts/addressHighlight.tsx +++ b/lib/contexts/addressHighlight.tsx @@ -60,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 1d7c7bf8c3..dbeceace4d 100644 --- a/lib/contexts/app.tsx +++ b/lib/contexts/app.tsx @@ -1,5 +1,6 @@ import React, { createContext, useContext } from 'react'; +import type { Route } from 'nextjs-routes'; import type { Props as PageProps } from 'nextjs/getServerSideProps'; type Props = { @@ -10,13 +11,9 @@ type Props = { const AppContext = createContext({ cookies: '', referrer: '', - id: '', - height_or_hash: '', - hash: '', - number: '', - q: '', - name: '', - adBannerProvider: '', + query: {}, + adBannerProvider: null, + apiData: null, }); export function AppContextProvider({ children, pageProps }: Props) { @@ -27,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..2aba76e39b --- /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; +} + +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/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/getArbitrumVerificationStepStatus.ts b/lib/getArbitrumVerificationStepStatus.ts new file mode 100644 index 0000000000..e86114d115 --- /dev/null +++ b/lib/getArbitrumVerificationStepStatus.ts @@ -0,0 +1,25 @@ +import type { ArbitrumBatchStatus, ArbitrumL2TxData } from 'types/api/arbitrumL2'; + +type Args = { + status: ArbitrumBatchStatus; + commitment_transaction: ArbitrumL2TxData; + confirmation_transaction: ArbitrumL2TxData; +} + +export default function getArbitrumVerificationStepStatus({ + 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/growthbook/init.ts b/lib/growthbook/init.ts index 10674a4fcc..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; - security_score_exp: boolean; } export const growthBook = (() => { 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/useContractTabs.tsx b/lib/hooks/useContractTabs.tsx index ca02f16ddd..24667f69a1 100644 --- a/lib/hooks/useContractTabs.tsx +++ b/lib/hooks/useContractTabs.tsx @@ -1,37 +1,143 @@ +import { useRouter } from 'next/router'; import React from 'react'; import type { Address } from 'types/api/address'; +import useApiQuery from 'lib/api/useApiQuery'; +import * as cookies from 'lib/cookies'; +import getQueryParamString from 'lib/router/getQueryParamString'; +import useSocketChannel from 'lib/socket/useSocketChannel'; +import * as stubs from 'stubs/contract'; import ContractCode from 'ui/address/contract/ContractCode'; -import ContractRead from 'ui/address/contract/ContractRead'; -import ContractWrite from 'ui/address/contract/ContractWrite'; +import ContractMethodsCustom from 'ui/address/contract/methods/ContractMethodsCustom'; +import ContractMethodsProxy from 'ui/address/contract/methods/ContractMethodsProxy'; +import ContractMethodsRegular from 'ui/address/contract/methods/ContractMethodsRegular'; +import { divideAbiIntoMethodTypes } from 'ui/address/contract/methods/utils'; + +const CONTRACT_TAB_IDS = [ + 'contract_code', + 'read_contract', + 'read_contract_rpc', + 'read_proxy', + 'read_custom_methods', + 'write_contract', + 'write_contract_rpc', + 'write_proxy', + 'write_custom_methods', +] as const; + +interface ContractTab { + id: typeof CONTRACT_TAB_IDS[number]; + title: string; + component: JSX.Element; +} + +interface ReturnType { + tabs: Array; + isLoading: boolean; +} + +export default function useContractTabs(data: Address | undefined, isPlaceholderData: boolean): ReturnType { + const [ isQueryEnabled, setIsQueryEnabled ] = React.useState(false); + + const router = useRouter(); + const tab = getQueryParamString(router.query.tab); + + const isEnabled = Boolean(data?.hash) && data?.is_contract && !isPlaceholderData && CONTRACT_TAB_IDS.concat('contract' as never).includes(tab); + + const enableQuery = React.useCallback(() => { + setIsQueryEnabled(true); + }, []); + + const contractQuery = useApiQuery('contract', { + pathParams: { hash: data?.hash }, + queryOptions: { + enabled: isEnabled && isQueryEnabled, + refetchOnMount: false, + placeholderData: data?.is_verified ? stubs.CONTRACT_CODE_VERIFIED : stubs.CONTRACT_CODE_UNVERIFIED, + }, + }); + + const customAbiQuery = useApiQuery('custom_abi', { + queryOptions: { + enabled: isEnabled && isQueryEnabled && Boolean(cookies.get(cookies.NAMES.API_TOKEN)), + refetchOnMount: false, + }, + }); + + const channel = useSocketChannel({ + topic: `addresses:${ data?.hash?.toLowerCase() }`, + isDisabled: !isEnabled, + onJoin: enableQuery, + onSocketError: enableQuery, + }); + + const methods = React.useMemo(() => divideAbiIntoMethodTypes(contractQuery.data?.abi ?? []), [ contractQuery.data?.abi ]); + const methodsCustomAbi = React.useMemo(() => { + return divideAbiIntoMethodTypes( + customAbiQuery.data + ?.find((item) => data && item.contract_address_hash.toLowerCase() === data.hash.toLowerCase()) + ?.abi ?? + [], + ); + }, [ customAbiQuery.data, data ]); + + const verifiedImplementations = React.useMemo(() => { + return data?.implementations?.filter(({ name, address }) => name && address && address !== data?.hash) || []; + }, [ data?.hash, data?.implementations ]); -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 ]); + return { + tabs: [ + { + id: 'contract_code' as const, + title: 'Code', + component: , + }, + methods.read.length > 0 && { + id: 'read_contract' as const, + title: 'Read contract', + component: , + }, + methodsCustomAbi.read.length > 0 && { + id: 'read_custom_methods' as const, + title: 'Read custom', + component: , + }, + verifiedImplementations.length > 0 && { + id: 'read_proxy' as const, + title: 'Read proxy', + component: ( + + ), + }, + methods.write.length > 0 && { + id: 'write_contract' as const, + title: 'Write contract', + component: , + }, + methodsCustomAbi.write.length > 0 && { + id: 'write_custom_methods' as const, + title: 'Write custom', + component: , + }, + verifiedImplementations.length > 0 && { + id: 'write_proxy' as const, + title: 'Write proxy', + component: ( + + ), + }, + ].filter(Boolean), + isLoading: contractQuery.isPlaceholderData, + }; + }, [ contractQuery, channel, data?.hash, verifiedImplementations, methods.read, methods.write, methodsCustomAbi.read, methodsCustomAbi.write ]); } 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/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/useNavItems.tsx b/lib/hooks/useNavItems.tsx index f3f40fc038..b1a0bf5245 100644 --- a/lib/hooks/useNavItems.tsx +++ b/lib/hooks/useNavItems.tsx @@ -1,7 +1,7 @@ import { useRouter } from 'next/router'; import React from 'react'; -import type { NavItemInternal, NavItem, NavGroupItem } 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'; @@ -72,48 +72,62 @@ export default function useNavItems(): ReturnType { 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 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 rollupFeature = config.features.rollup; - if (rollupFeature.isEnabled && rollupFeature.type === 'zkEvm') { - blockchainNavItems = [ - [ - txs, - userOps, - blocks, - { - text: 'Txn batches', - nextRoute: { pathname: '/batches' as const }, - icon: 'txn_batches', - isActive: pathname === '/batches' || pathname === '/batches/[number]', - }, - ].filter(Boolean), - [ - topAccounts, - validators, - verifiedContracts, - ensLookup, - ].filter(Boolean), - ]; - } else if (rollupFeature.isEnabled && rollupFeature.type === 'optimistic') { + if (rollupFeature.isEnabled && (rollupFeature.type === 'optimistic' || rollupFeature.type === 'arbitrum' || rollupFeature.type === 'zkEvm')) { blockchainNavItems = [ [ txs, - // eslint-disable-next-line max-len - { text: `Deposits (L1${ rightLineArrow }L2)`, nextRoute: { pathname: '/deposits' as const }, icon: 'arrows/south-east', isActive: pathname === '/deposits' }, - // eslint-disable-next-line max-len - { text: `Withdrawals (L2${ rightLineArrow }L1)`, nextRoute: { pathname: '/withdrawals' as const }, icon: 'arrows/north-east', isActive: pathname === '/withdrawals' }, + rollupDeposits, + rollupWithdrawals, ], [ blocks, - // eslint-disable-next-line max-len - { text: 'Txn batches', nextRoute: { pathname: '/batches' as const }, icon: 'txn_batches', isActive: pathname === '/batches' }, - // eslint-disable-next-line max-len - { text: 'Output roots', nextRoute: { pathname: '/output-roots' as const }, icon: 'output_roots', isActive: pathname === '/output-roots' }, - ], + rollupTxnBatches, + rollupDisputeGames, + rollupFeature.type === 'optimistic' ? rollupOutputRoots : undefined, + ].filter(Boolean), [ userOps, topAccounts, + mudWorlds, validators, verifiedContracts, ensLookup, @@ -123,10 +137,8 @@ export default function useNavItems(): ReturnType { blockchainNavItems = [ [ txs, - // eslint-disable-next-line max-len - { text: `Deposits (L1${ rightLineArrow }L2)`, nextRoute: { pathname: '/deposits' as const }, icon: 'arrows/south-east', isActive: pathname === '/deposits' }, - // eslint-disable-next-line max-len - { text: `Withdrawals (L2${ rightLineArrow }L1)`, nextRoute: { pathname: '/withdrawals' as const }, icon: 'arrows/north-east', isActive: pathname === '/withdrawals' }, + rollupDeposits, + rollupWithdrawals, ], [ blocks, @@ -142,12 +154,7 @@ export default function useNavItems(): ReturnType { txs, userOps, blocks, - { - text: 'Txn batches', - nextRoute: { pathname: '/batches' as const }, - icon: 'txn_batches', - isActive: pathname === '/batches' || pathname === '/batches/[number]', - }, + rollupTxnBatches, ].filter(Boolean), [ topAccounts, @@ -187,18 +194,37 @@ export default function useNavItems(): ReturnType { icon: 'graphQL', isActive: pathname === '/graphiql', } : null, - !config.UI.sidebar.hiddenLinks?.rpc_api && { + !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 && { + !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 otherNavItems: Array | Array> = [ + { + 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', @@ -233,19 +259,8 @@ export default function useNavItems(): ReturnType { { text: 'Other', icon: 'gear', - subItems: [ - { - 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.UI.sidebar.otherLinks, - ].filter(Boolean), + isActive: otherNavItems.flat().some(item => isInternalItem(item) && item.isActive), + subItems: otherNavItems, }, ].filter(Boolean); @@ -262,12 +277,6 @@ export default function useNavItems(): ReturnType { icon: 'privattags', isActive: pathname === '/account/tag-address', }, - { - text: 'Public tags', - nextRoute: { pathname: '/account/public-tags-request' as const }, - icon: 'publictags', - isActive: pathname === '/account/public-tags-request', - }, { text: 'API keys', nextRoute: { pathname: '/account/api-key' as const }, 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/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/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..a664df8d20 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`] = ` { + "canonical": "http://localhost:3000/txs", "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.", "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..9282da7fe7 100644 --- a/lib/metadata/generate.ts +++ b/lib/metadata/generate.ts @@ -1,4 +1,5 @@ import type { ApiData, Metadata } from './types'; +import type { RouteParams } from 'nextjs/types'; import type { Route } from 'nextjs-routes'; @@ -6,10 +7,11 @@ import config from 'configs/app'; import getNetworkTitle from 'lib/networks/getNetworkTitle'; 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, @@ -17,8 +19,7 @@ export default function generate(route: R, apiData?: ApiData network_title: getNetworkTitle(), }; - const compiledTitle = compileValue(templates.title.make(route.pathname), params); - const title = compiledTitle ? compiledTitle + (config.meta.promoteBlockscoutInTitle ? ' | Blockscout' : '') : ''; + const title = compileValue(templates.title.make(route.pathname, Boolean(apiData)), params); const description = compileValue(templates.description.make(route.pathname), params); const pageOgType = getPageOgType(route.pathname); @@ -31,5 +32,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 ef7ef24e26..1f8498fe69 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', @@ -27,14 +30,15 @@ const OG_TYPE_DICT: Record = { '/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', '/deposits': 'Root page', '/output-roots': 'Root page', + '/dispute-games': 'Root page', '/batches': 'Root page', '/batches/[number]': 'Regular page', '/blobs/[hash]': 'Regular page', @@ -45,13 +49,20 @@ const OG_TYPE_DICT: Record = { '/name-domains/[name]': 'Regular page', '/validators': 'Root page', '/gas-tracker': 'Root page', + '/mud-worlds': 'Root 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', + '/api/config': 'Regular page', + '/api/sprite': 'Regular page', '/auth/auth0': 'Regular page', '/auth/unverified-email': 'Regular page', }; 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 c064709b6f..e4d0ccd715 100644 --- a/lib/metadata/templates/description.ts +++ b/lib/metadata/templates/description.ts @@ -1,3 +1,4 @@ +/* eslint-disable max-len */ import type { Route } from 'nextjs-routes'; // equal og:description @@ -12,7 +13,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, @@ -30,14 +34,15 @@ const TEMPLATE_MAP: Record = { '/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, '/deposits': DEFAULT_TEMPLATE, '/output-roots': DEFAULT_TEMPLATE, + '/dispute-games': DEFAULT_TEMPLATE, '/batches': DEFAULT_TEMPLATE, '/batches/[number]': DEFAULT_TEMPLATE, '/blobs/[hash]': DEFAULT_TEMPLATE, @@ -48,13 +53,20 @@ const TEMPLATE_MAP: Record = { '/name-domains/[name]': DEFAULT_TEMPLATE, '/validators': DEFAULT_TEMPLATE, '/gas-tracker': DEFAULT_TEMPLATE, + '/mud-worlds': 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, + '/api/config': DEFAULT_TEMPLATE, + '/api/sprite': DEFAULT_TEMPLATE, '/auth/auth0': DEFAULT_TEMPLATE, '/auth/unverified-email': DEFAULT_TEMPLATE, }; diff --git a/lib/metadata/templates/title.ts b/lib/metadata/templates/title.ts index 9a667bb9ce..e0c9d9e44c 100644 --- a/lib/metadata/templates/title.ts +++ b/lib/metadata/templates/title.ts @@ -1,61 +1,82 @@ 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]': 'NFT instance', - '/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', - '/deposits': 'deposits (L1 > L2)', - '/output-roots': 'output roots', - '/batches': 'tx batches (L2 blocks)', - '/batches/[number]': 'L2 tx batch %number%', - '/blobs/[hash]': 'blob %hash% details', - '/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', - '/validators': 'validators list', - '/gas-tracker': 'gas tracker', + '/': '%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', + '/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/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% tx batches (L2 blocks)', + '/batches/[number]': '%network_name% L2 tx 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': '%network_name% gas tracker - Current gas fees', + '/mud-worlds': '%network_name% MUD worlds list', // 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', + '/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', + '/auth/auth0': '%network_name% authentication', + '/auth/unverified-email': '%network_name% unverified email', +}; + +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%', }; -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..fda74301ba 100644 --- a/lib/metadata/types.ts +++ b/lib/metadata/types.ts @@ -1,11 +1,16 @@ +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; +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 } : + never +) | null; export interface Metadata { title: string; @@ -15,4 +20,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 fb4c48e7fe..3fc7896f81 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', @@ -25,14 +28,15 @@ export const PAGE_TYPE_DICT: Record = { '/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', '/deposits': 'Deposits (L1 > L2)', '/output-roots': 'Output roots', + '/dispute-games': 'Dispute games', '/batches': 'Tx batches (L2 blocks)', '/batches/[number]': 'L2 tx batch details', '/blobs/[hash]': 'Blob details', @@ -43,13 +47,20 @@ export const PAGE_TYPE_DICT: Record = { '/name-domains/[name]': 'Domain details', '/validators': 'Validators list', '/gas-tracker': 'Gas tracker', + '/mud-worlds': 'MUD worlds', // 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', + '/api/config': 'Node API: App config', + '/api/sprite': 'Node API: SVG sprite content', '/auth/auth0': 'Auth', '/auth/unverified-email': 'Unverified email', }; 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 20c4b92970..4d59ec007e 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', @@ -19,6 +20,8 @@ export enum EventTypes { EXPERIMENT_STARTED = 'Experiment started', FILTERS = 'Filters', BUTTON_CLICK = 'Button click', + PROMO_BANNER = 'Promo banner', + APP_FEEDBACK = 'App feedback', } /* eslint-disable @typescript-eslint/indent */ @@ -29,6 +32,7 @@ Type extends EventTypes.PAGE_VIEW ? 'Tab': string; 'Page'?: string; 'Color mode': 'light' | 'dark'; + 'Color theme': ColorThemeId | undefined; } : Type extends EventTypes.SEARCH_QUERY ? { 'Search query': string; @@ -100,10 +104,18 @@ Type extends EventTypes.PAGE_WIDGET ? ( } | { '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'; + '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': 'Address tag'; + 'Info': string; + 'URL': string; } ) : Type extends EventTypes.TX_INTERPRETATION_INTERACTION ? { @@ -119,8 +131,18 @@ Type extends EventTypes.FILTERS ? { 'Filter name': string; } : Type extends EventTypes.BUTTON_CLICK ? { - 'Content': 'Swap button'; + '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 */ 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/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/router/removeQueryParam.ts b/lib/router/removeQueryParam.ts index 83daa6bd28..d0a37975c7 100644 --- a/lib/router/removeQueryParam.ts +++ b/lib/router/removeQueryParam.ts @@ -1,8 +1,12 @@ import type { NextRouter } from 'next/router'; export default function removeQueryParam(router: NextRouter, param: string) { - const { pathname, query } = router; + const { pathname, query, asPath } = router; const newQuery = { ...query }; delete newQuery[param]; - router.replace({ pathname, query: newQuery }, undefined, { shallow: true }); + + 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 index de9fadce8b..1dfb1f2a69 100644 --- a/lib/router/updateQueryParam.ts +++ b/lib/router/updateQueryParam.ts @@ -1,8 +1,12 @@ import type { NextRouter } from 'next/router'; export default function updateQueryParam(router: NextRouter, param: string, newValue: string) { - const { pathname, query } = router; + const { pathname, query, asPath } = router; const newQuery = { ...query }; newQuery[param] = newValue; - router.replace({ pathname, query: newQuery }, undefined, { shallow: true }); + + const hashIndex = asPath.indexOf('#'); + const hash = hashIndex !== -1 ? asPath.substring(hashIndex) : ''; + + router.replace({ pathname, query: newQuery, hash }, undefined, { shallow: true }); } 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..1159308576 --- /dev/null +++ b/lib/settings/identIcon.ts @@ -0,0 +1,24 @@ +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', + }, +]; diff --git a/lib/socket/types.ts b/lib/socket/types.ts index b2d8d7301a..927b7ec7ea 100644 --- a/lib/socket/types.ts +++ b/lib/socket/types.ts @@ -1,9 +1,11 @@ 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/zkEvmL2'; @@ -15,7 +17,8 @@ SocketMessage.TxStatusUpdate | SocketMessage.TxRawTrace | SocketMessage.NewTx | SocketMessage.NewPendingTx | -SocketMessage.NewDeposits | +SocketMessage.NewOptimisticDeposits | +SocketMessage.NewArbitrumDeposits | SocketMessage.AddressBalance | SocketMessage.AddressCurrentCoinBalance | SocketMessage.AddressTokenBalance | @@ -28,11 +31,14 @@ 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 { @@ -50,7 +56,8 @@ export namespace SocketMessage { 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 }>; @@ -64,10 +71,13 @@ export namespace SocketMessage { 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 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 00119abe90..9f1ce7d53c 100644 --- a/lib/token/metadata/attributesParser.ts +++ b/lib/token/metadata/attributesParser.ts @@ -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; } diff --git a/lib/token/tokenTypes.ts b/lib/token/tokenTypes.ts index 4b7fabf9ea..a382e61340 100644 --- a/lib/token/tokenTypes.ts +++ b/lib/token/tokenTypes.ts @@ -1,15 +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' }, - { title: 'ERC-404', id: 'ERC-404' }, -]; - -export const TOKEN_TYPES: Array<{ title: string; id: TokenType }> = [ - { title: 'ERC-20', id: 'ERC-20' }, +import config from 'configs/app'; + +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/color.ts b/lib/validations/color.ts new file mode 100644 index 0000000000..1f31d02801 --- /dev/null +++ b/lib/validations/color.ts @@ -0,0 +1,17 @@ +export const COLOR_HEX_REGEXP = /^#[A-Fa-f\d]{3,6}$/; + +export const validator = (value: string | undefined) => { + if (!value || value.length === 0) { + return true; + } + + if (value.length !== 4 && value.length !== 7) { + return 'Invalid length'; + } + + if (!COLOR_HEX_REGEXP.test(value)) { + return 'Invalid hex code'; + } + + return true; +}; 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/wagmiConfig.ts b/lib/web3/wagmiConfig.ts index f90d0a069f..d8824f5f68 100644 --- a/lib/web3/wagmiConfig.ts +++ b/lib/web3/wagmiConfig.ts @@ -1,38 +1,49 @@ import { defaultWagmiConfig } from '@web3modal/wagmi/react/config'; import { http } from 'viem'; -import type { CreateConfigParameters } from 'wagmi'; +import { createConfig, type CreateConfigParameters } from 'wagmi'; import config from 'configs/app'; import currentChain from 'lib/web3/currentChain'; const feature = config.features.blockchainInteraction; const wagmiConfig = (() => { - try { - if (!feature.isEnabled) { - throw new Error(); - } + const chains: CreateConfigParameters['chains'] = [ currentChain ]; - const chains: CreateConfigParameters['chains'] = [ currentChain ]; - - const wagmiConfig = defaultWagmiConfig({ - chains, - multiInjectedProviderDiscovery: true, + if (!feature.isEnabled) { + const wagmiConfig = createConfig({ + chains: [ currentChain ], transports: { - [currentChain.id]: http(), - }, - projectId: feature.walletConnect.projectId, - metadata: { - name: `${ config.chain.name } explorer`, - description: `${ config.chain.name } explorer`, - url: config.app.baseUrl, - icons: [ config.UI.sidebar.icon.default ].filter(Boolean), + [currentChain.id]: http(config.chain.rpcUrl || `${ config.api.endpoint }/api/eth-rpc`), }, - enableEmail: true, ssr: true, + batch: { multicall: { wait: 100 } }, }); return wagmiConfig; - } catch (error) {} + } + + const wagmiConfig = defaultWagmiConfig({ + chains, + multiInjectedProviderDiscovery: true, + transports: { + [currentChain.id]: http(), + }, + projectId: feature.walletConnect.projectId, + metadata: { + name: `${ config.chain.name } explorer`, + description: `${ config.chain.name } explorer`, + url: config.app.baseUrl, + icons: [ config.UI.navigation.icon.default ].filter(Boolean), + }, + auth: { + email: true, + socials: [], + }, + ssr: true, + batch: { multicall: { wait: 100 } }, + }); + + return wagmiConfig; })(); export default wagmiConfig; diff --git a/middleware.ts b/middleware.ts index 17648c82f8..2f017d9cd5 100644 --- a/middleware.ts +++ b/middleware.ts @@ -19,8 +19,12 @@ export function middleware(req: NextRequest) { return accountResponse; } - const end = Date.now(); const res = NextResponse.next(); + + middlewares.colorTheme(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/address/address.ts b/mocks/address/address.ts index 7c7bc612cb..e58097c2f5 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, @@ -57,42 +75,54 @@ export const token: Address = { coin_balance: '1', creation_tx_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_tx_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', 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', @@ -110,20 +140,13 @@ export const validator: Address = { creation_tx_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', 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/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 f3fd58b8d5..c494c9bbe3 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'; @@ -119,35 +119,39 @@ export const erc404b: AddressTokenBalance = { token_id: null, }; -export const erc20List = { +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 = { +export const erc404List: AddressTokensResponse = { items: [ erc404a, erc404b, ], + next_page_params: null, }; export const nfts: AddressNFTsResponse = { 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 index 824a6fbe13..33457ddf2f 100644 --- a/mocks/apps/securityReports.ts +++ b/mocks/apps/securityReports.ts @@ -1,6 +1,8 @@ +import { apps } from './apps'; + export const securityReports = [ { - appName: 'token-approval-tracker', + appName: apps[0].id, doc: 'http://docs.li.fi/smart-contracts/deployments#mainnet', chainsData: { '1': { 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/blocks/block.ts b/mocks/blocks/block.ts index eb182d2ebf..281665fc01 100644 --- a/mocks/blocks/block.ts +++ b/mocks/blocks/block.ts @@ -1,6 +1,13 @@ /* eslint-disable max-len */ +import type { RpcBlock } from 'viem'; + import type { Block, BlocksResponse } 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', burnt_fees: '5449200000000000', @@ -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', @@ -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, @@ -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', @@ -135,6 +142,34 @@ 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 withBlobTxs: Block = { ...base, blob_gas_price: '21518435987', @@ -144,6 +179,11 @@ export const withBlobTxs: Block = { blob_tx_count: 1, }; +export const withWithdrawals: Block = { + ...base, + withdrawals_count: 2, +}; + export const baseListResponse: BlocksResponse = { items: [ base, @@ -151,3 +191,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/contract/info.ts b/mocks/contract/info.ts index a797383474..daedb99650 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, @@ -32,9 +33,25 @@ export const verified: Partial = { ], 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: [ @@ -45,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, @@ -53,36 +70,70 @@ 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 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 6c1bdf367e..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', @@ -213,6 +129,6 @@ export const write: Array = [ payable: false, stateMutability: 'nonpayable', type: 'function', - method_id: '0x06', + 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 bc3b4ecfb2..86a0a0855c 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', @@ -26,7 +26,7 @@ export const contract1: VerifiedContract = { export const contract2: VerifiedContract = { address: { hash: '0xB2218bdEbe8e90f80D04286772B0968ead666942', - implementation_name: null, + implementations: null, is_contract: true, is_verified: null, name: 'EternalStorageProxyWithSomeExternalLibrariesAndEvenMore', @@ -35,6 +35,7 @@ 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, 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/l2txnBatches/txnBatches.ts b/mocks/l2txnBatches/txnBatches.ts deleted file mode 100644 index f3c086c855..0000000000 --- a/mocks/l2txnBatches/txnBatches.ts +++ /dev/null @@ -1,33 +0,0 @@ -export const txnBatchesData = { - items: [ - { - l1_tx_hashes: [ - '0x5bc94d02b65743dfaa9e10a2d6e175aff2a05cce2128c8eaf848bd84ab9325c5', - '0x92a51bc623111dbb91f243e3452e60fab6f090710357f9d9b75ac8a0f67dfd9d', - ], - l1_timestamp: '2023-02-24T10:16:12.000000Z', - l2_block_number: 5902836, - tx_count: 0, - }, - { - l1_tx_hashes: [ - '0xc45f846ee28ce9ba116ce2d378d3dd00b55d324b833b3ecd4241c919c572c4aa', - ], - l1_timestamp: '2023-02-24T10:16:00.000000Z', - l2_block_number: 5902835, - tx_count: 0, - }, - { - 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..93860f4b44 --- /dev/null +++ b/mocks/metadata/address.ts @@ -0,0 +1,104 @@ +/* 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', + }, +}; 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..2d8218e884 --- /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', + tx_count: 3938, + }, + { + address: withoutName, + coin_balance: '0', + tx_count: 0, + }, + { + address: withoutName, + coin_balance: '0', + tx_count: 0, + }, + ], + next_page_params: { + items_count: 50, + world: '0x18f01f12ca21b6fc97b917c3e32f671f8a933caa', + }, +}; diff --git a/mocks/l2deposits/deposits.ts b/mocks/optimism/deposits.ts similarity index 100% rename from mocks/l2deposits/deposits.ts rename to mocks/optimism/deposits.ts 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/l2outputRoots/outputRoots.ts b/mocks/optimism/outputRoots.ts similarity index 100% rename from mocks/l2outputRoots/outputRoots.ts rename to mocks/optimism/outputRoots.ts diff --git a/mocks/optimism/txnBatches.ts b/mocks/optimism/txnBatches.ts new file mode 100644 index 0000000000..e7306c6ae3 --- /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_tx_hashes: [ + '0x9553351f6bd1577f4e782738c087be08697fb11f3b91745138d71ba166d62c3b', + ], + l2_block_end: 124882074, + l2_block_start: 124881833, + tx_count: 4011, + }, + { + batch_data_container: 'in_calldata', + internal_id: 260997, + l1_timestamp: '2022-11-03T11:20:59.000000Z', + l1_tx_hashes: [ + '0x80f5fba70d5685bc2b70df836942e892b24afa7bba289a2fac0ca8f4d554cc72', + ], + l2_block_end: 124881832, + l2_block_start: 124881613, + tx_count: 4206, + }, + { + internal_id: 260996, + l1_timestamp: '2024-09-03T11:14:23.000000Z', + l1_tx_hashes: [ + '0x39f4c46cae57bae936acb9159e367794f41f021ed3788adb80ad93830edb5f22', + ], + l2_block_end: 124881612, + l2_block_start: 124881380, + tx_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_tx_hashes: [ + '0x478c45f182631ae6f7249d40f31fdac36f41d88caa2e373fba35340a7345ca67', + ], + l2_block_end: 10146784, + l2_block_start: 10145379, + tx_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_tx_hashes: [ + '0x2bb0b96a8ba0f063a243ac3dee0b2f2d87edb2ba9ef44bfcbc8ed191af1c4c24', + ], + l2_block_end: 10935879, + l2_block_start: 10934514, + tx_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_tx_hashes: [ + '0x3870f136497e5501dc20d0974daf379c8636c958794d59a9c90d4f8a9f0ed20a', + ], + l2_block_end: 16291502, + l2_block_start: 16291373, + tx_count: 704, +}; diff --git a/mocks/optimism/withdrawals.ts b/mocks/optimism/withdrawals.ts new file mode 100644 index 0000000000..046b467402 --- /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_tx_hash: '0x1a235bee32ac10cb7efdad98415737484ca66386e491cde9e17d42b136dca684', + l2_timestamp: '2022-02-15T12:50:02.000000Z', + l2_tx_hash: '0x918cd8c5c24c17e06cd02b0379510c4ad56324bf153578fb9caaaa2fe4e7dc35', + msg_nonce: 396, + 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_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_version: 1, + status: 'Ready for relay', + }, + ], + next_page_params: { + items_count: 50, + nonce: '1766847064778384329583297500742918515827483896875618958121606201292620123', + }, +}; diff --git a/mocks/search/index.ts b/mocks/search/index.ts index 4009a6d460..e27b508962 100644 --- a/mocks/search/index.ts +++ b/mocks/search/index.ts @@ -7,6 +7,7 @@ import type { SearchResult, SearchResultUserOp, SearchResultBlob, + SearchResultDomain, } from 'types/api/search'; export const token1: SearchResultToken = { @@ -95,6 +96,15 @@ export const contract1: SearchResultAddressOrContractOrUniversalProfile = { url: '/address/0xb64a30399f7F6b0C154c2E7Af0a3ec7B0A5b131a', }; +export const contract2: SearchResultAddressOrContractOrUniversalProfile = { + 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', @@ -123,6 +133,20 @@ export const blob1: SearchResultBlob = { 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, @@ -132,6 +156,7 @@ export const baseResponse: SearchResult = { contract1, tx1, blob1, + domain1, ], next_page_params: null, }; diff --git a/mocks/shibarium/deposits.ts b/mocks/shibarium/deposits.ts index 98bf9d925d..7081042cd8 100644 --- a/mocks/shibarium/deposits.ts +++ b/mocks/shibarium/deposits.ts @@ -8,7 +8,7 @@ export const data: ShibariumDepositsResponse = { l1_transaction_hash: '0xaf3e5f4ef03eac22a622b3434c5dc9f4465aa291900a86bcf0ad9fb14429f05e', user: { hash: '0x6197d1eef304eb5284a0f6720f79403b4e9bf3a5', - implementation_name: null, + implementations: null, is_contract: false, is_verified: false, name: null, @@ -25,7 +25,7 @@ export const data: ShibariumDepositsResponse = { l1_transaction_hash: '0xaf3e5f4ef03eac22a622b3434c5dc9f4465aa291900a86bcf0ad9fb14429f05e', user: { hash: '0x6197d1eef304eb5284a0f6720f79403b4e9bf3a5', - implementation_name: null, + implementations: null, is_contract: false, is_verified: false, name: null, @@ -42,7 +42,7 @@ export const data: ShibariumDepositsResponse = { l1_transaction_hash: '0xaf3e5f4ef03eac22a622b3434c5dc9f4465aa291900a86bcf0ad9fb14429f05e', user: { hash: '0x6197d1eef304eb5284a0f6720f79403b4e9bf3a5', - implementation_name: null, + implementations: null, is_contract: false, is_verified: false, name: null, diff --git a/mocks/shibarium/withdrawals.ts b/mocks/shibarium/withdrawals.ts index 79851f3d1b..6c1e875264 100644 --- a/mocks/shibarium/withdrawals.ts +++ b/mocks/shibarium/withdrawals.ts @@ -8,7 +8,7 @@ export const data: ShibariumWithdrawalsResponse = { l1_transaction_hash: '0xaf3e5f4ef03eac22a622b3434c5dc9f4465aa291900a86bcf0ad9fb14429f05e', user: { hash: '0x6197d1eef304eb5284a0f6720f79403b4e9bf3a5', - implementation_name: null, + implementations: null, is_contract: false, is_verified: false, name: null, @@ -25,7 +25,7 @@ export const data: ShibariumWithdrawalsResponse = { l1_transaction_hash: '0xaf3e5f4ef03eac22a622b3434c5dc9f4465aa291900a86bcf0ad9fb14429f05e', user: { hash: '0x6197d1eef304eb5284a0f6720f79403b4e9bf3a5', - implementation_name: null, + implementations: null, is_contract: false, is_verified: false, name: null, @@ -42,7 +42,7 @@ export const data: ShibariumWithdrawalsResponse = { l1_transaction_hash: '0xaf3e5f4ef03eac22a622b3434c5dc9f4465aa291900a86bcf0ad9fb14429f05e', user: { hash: '0x6197d1eef304eb5284a0f6720f79403b4e9bf3a5', - implementation_name: null, + implementations: null, is_contract: false, is_verified: false, name: null, diff --git a/mocks/stats/index.ts b/mocks/stats/index.ts index d9d00e36e1..0203db5838 100644 --- a/mocks/stats/index.ts +++ b/mocks/stats/index.ts @@ -1,9 +1,12 @@ import _mapValues from 'lodash/mapValues'; +import type { HomeStats } from 'types/api/stats'; + export const base = { 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.39', @@ -46,25 +49,43 @@ export const withBtcLocked = { rootstock_locked_btc: '3337493406696977561374', }; -export const withoutFiatPrices = { +export const withoutFiatPrices: HomeStats = { ...base, gas_prices: _mapValues(base.gas_prices, (price) => price ? ({ ...price, fiat_price: null }) : null), }; -export const withoutGweiPrices = { +export const withoutGweiPrices: HomeStats = { ...base, gas_prices: _mapValues(base.gas_prices, (price) => price ? ({ ...price, price: null }) : null), }; -export const withoutBothPrices = { +export const withoutBothPrices: HomeStats = { ...base, gas_prices: _mapValues(base.gas_prices, (price) => price ? ({ ...price, price: null, fiat_price: null }) : null), }; -export const noChartData = { +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 index 47de79b184..799cff4c45 100644 --- a/mocks/stats/line.ts +++ b/mocks/stats/line.ts @@ -1,128 +1,161 @@ -export const averageGasPrice = { +import type * as stats from '@blockscout/stats-types'; + +export const averageGasPrice: stats.LineChart = { chart: [ { date: '2023-12-22', value: '37.7804422597599', + is_approximate: false, }, { date: '2023-12-23', value: '25.84889883009387', + is_approximate: false, }, { date: '2023-12-24', value: '25.818463227198574', + is_approximate: false, }, { date: '2023-12-25', value: '26.045513050051298', + is_approximate: false, }, { date: '2023-12-26', value: '21.42600692652399', + is_approximate: false, }, { date: '2023-12-27', value: '31.066730409846656', + is_approximate: false, }, { date: '2023-12-28', value: '33.63955781902089', + is_approximate: false, }, { date: '2023-12-29', value: '28.064736756058384', + is_approximate: false, }, { date: '2023-12-30', value: '23.074500869678175', + is_approximate: false, }, { date: '2023-12-31', value: '17.651005734615133', + is_approximate: false, }, { date: '2024-01-01', value: '14.906085174476441', + is_approximate: false, }, { date: '2024-01-02', value: '22.28459059038656', + is_approximate: false, }, { date: '2024-01-03', value: '39.8311646806592', + is_approximate: false, }, { date: '2024-01-04', value: '26.09989322256083', + is_approximate: false, }, { date: '2024-01-05', value: '22.821996688111998', + is_approximate: false, }, { date: '2024-01-06', value: '20.32680041262083', + is_approximate: false, }, { date: '2024-01-07', value: '32.535045831809704', + is_approximate: false, }, { date: '2024-01-08', value: '27.443477102139482', + is_approximate: false, }, { date: '2024-01-09', value: '20.7911332558055', + is_approximate: false, }, { date: '2024-01-10', value: '42.10740192523919', + is_approximate: false, }, { date: '2024-01-11', value: '35.75215680343582', + is_approximate: false, }, { date: '2024-01-12', value: '27.430414798093253', + is_approximate: false, }, { date: '2024-01-13', value: '20.170934096589875', + is_approximate: false, }, { date: '2024-01-14', value: '38.79660984371034', + is_approximate: false, }, { date: '2024-01-15', value: '26.140740484554204', + is_approximate: false, }, { date: '2024-01-16', value: '36.708543184194156', + is_approximate: false, }, { date: '2024-01-17', value: '40.325438794298876', + is_approximate: false, }, { date: '2024-01-18', value: '37.55145309930694', + is_approximate: false, }, { date: '2024-01-19', value: '33.271450114434664', + is_approximate: false, }, { date: '2024-01-20', value: '19.303304377685638', + is_approximate: false, }, { date: '2024-01-21', value: '14.375908594704976', + is_approximate: false, }, ], }; diff --git a/mocks/stats/lines.ts b/mocks/stats/lines.ts index cc55987919..9f8870249f 100644 --- a/mocks/stats/lines.ts +++ b/mocks/stats/lines.ts @@ -1,6 +1,6 @@ -import { currencyUnits } from '../../lib/units'; +import type * as stats from '@blockscout/stats-types'; -export const base = { +export const base: stats.LineCharts = { sections: [ { id: 'accounts', @@ -10,19 +10,19 @@ export const base = { id: 'accountsGrowth', title: 'Accounts growth', description: 'Cumulative accounts number per period', - units: null, + units: undefined, }, { id: 'activeAccounts', title: 'Active accounts', description: 'Active accounts number per period', - units: null, + units: undefined, }, { id: 'newAccounts', title: 'New accounts', description: 'New accounts number per day', - units: null, + units: undefined, }, ], }, @@ -33,32 +33,32 @@ export const base = { { id: 'averageTxnFee', title: 'Average transaction fee', - description: `The average amount in ${ currencyUnits.ether } spent per transaction`, - units: currencyUnits.ether, + description: 'The average amount in ETH spent per transaction', + units: 'ETH', }, { id: 'newTxns', title: 'New transactions', description: 'New transactions number', - units: null, + units: undefined, }, { id: 'txnsFee', title: 'Transactions fees', description: 'Amount of tokens paid as fees', - units: currencyUnits.ether, + units: 'ETH', }, { id: 'txnsGrowth', title: 'Transactions growth', description: 'Cumulative transactions number', - units: null, + units: undefined, }, { id: 'txnsSuccessRate', title: 'Transactions success rate', description: 'Successful transactions rate per day', - units: null, + units: undefined, }, ], }, @@ -70,7 +70,7 @@ export const base = { id: 'averageBlockRewards', title: 'Average block rewards', description: 'Average amount of distributed reward in tokens per day', - units: currencyUnits.ether, + units: 'ETH', }, { id: 'averageBlockSize', @@ -82,7 +82,7 @@ export const base = { id: 'newBlocks', title: 'New blocks', description: 'New blocks number', - units: null, + units: undefined, }, ], }, @@ -92,9 +92,9 @@ export const base = { charts: [ { id: 'newNativeCoinTransfers', - title: `New ${ currencyUnits.ether } transfers`, + title: 'New ETH transfers', description: 'New token transfers number for the period', - units: null, + units: undefined, }, ], }, @@ -106,7 +106,7 @@ export const base = { id: 'averageGasLimit', title: 'Average gas limit', description: 'Average gas limit per block for the period', - units: null, + units: undefined, }, { id: 'averageGasPrice', @@ -118,7 +118,7 @@ export const base = { id: 'gasUsedGrowth', title: 'Gas used growth', description: 'Cumulative gas used for the period', - units: null, + units: undefined, }, ], }, @@ -130,13 +130,13 @@ export const base = { id: 'newVerifiedContracts', title: 'New verified contracts', description: 'New verified contracts number for the period', - units: null, + units: undefined, }, { id: 'verifiedContractsGrowth', title: 'Verified contracts growth', description: 'Cumulative number verified contracts for the period', - units: null, + units: undefined, }, ], }, diff --git a/mocks/tokens/tokenInfo.ts b/mocks/tokens/tokenInfo.ts index a732712775..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,7 +27,7 @@ export const tokenInfoERC20a: TokenInfo<'ERC-20'> = { name: 'hyfi.token', symbol: 'HyFi', total_supply: '369000000000000000000000000', - type: 'ERC-20', + type: 'ERC-20' as const, icon_url: 'http://localhost:3000/token-icon.png', }; @@ -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,7 +170,7 @@ export const tokenInfoERC1155WithoutName: TokenInfo<'ERC-1155'> = { name: null, symbol: null, total_supply: '482', - type: 'ERC-1155', + type: 'ERC-1155' as const, icon_url: null, }; @@ -184,7 +184,7 @@ export const tokenInfoERC404: TokenInfo<'ERC-404'> = { name: 'OMNI404', symbol: 'O404', total_supply: '6482275000000000000', - type: 'ERC-404', + 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..712c466b7d 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: { diff --git a/mocks/tokens/tokenTransfer.ts b/mocks/tokens/tokenTransfer.ts index 08b87b6858..b88cd499ff 100644 --- a/mocks/tokens/tokenTransfer.ts +++ b/mocks/tokens/tokenTransfer.ts @@ -3,7 +3,7 @@ import type { TokenTransfer, TokenTransferResponse } from 'types/api/tokenTransf export const erc20: TokenTransfer = { from: { hash: '0xd789a607CEac2f0E14867de4EB15b15C9FFB5859', - implementation_name: null, + implementations: null, is_contract: true, is_verified: true, name: 'ArianeeStore', @@ -14,7 +14,7 @@ export const erc20: TokenTransfer = { }, to: { hash: '0x7d20a8D54F955b4483A66aB335635ab66e151c51', - implementation_name: null, + implementations: null, is_contract: true, is_verified: false, name: null, @@ -50,7 +50,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 +61,7 @@ export const erc721: TokenTransfer = { }, to: { hash: '0x47eE48AEBc4ab9Ed908b805b8c8dAAa71B31Db1A', - implementation_name: null, + implementations: null, is_contract: false, is_verified: false, name: null, @@ -96,7 +96,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 +107,7 @@ export const erc1155A: TokenTransfer = { }, to: { hash: '0xBb36c792B9B45Aaf8b848A1392B0d6559202729E', - implementation_name: null, + implementations: null, is_contract: false, is_verified: false, name: null, @@ -173,7 +173,7 @@ export const erc1155D: TokenTransfer = { export const erc404A: TokenTransfer = { from: { hash: '0x0000000000000000000000000000000000000000', - implementation_name: null, + implementations: null, is_contract: false, is_verified: false, name: null, @@ -184,7 +184,7 @@ export const erc404A: TokenTransfer = { }, to: { hash: '0xBb36c792B9B45Aaf8b848A1392B0d6559202729E', - implementation_name: null, + implementations: null, is_contract: false, is_verified: false, name: null, @@ -208,6 +208,7 @@ export const erc404A: TokenTransfer = { total: { value: '42000000000000000000000000', decimals: '18', + token_id: null, }, tx_hash: '0x05d6589367633c032d757a69c5fb16c0e33e3994b0d9d1483f82aeee1f05d746', type: 'token_transfer', diff --git a/mocks/txs/internalTxs.ts b/mocks/txs/internalTxs.ts index 07eb83dc5e..23fc4bf027 100644 --- a/mocks/txs/internalTxs.ts +++ b/mocks/txs/internalTxs.ts @@ -6,7 +6,7 @@ export const base: InternalTransaction = { 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 0b97508282..bae57c630e 100644 --- a/mocks/txs/tx.ts +++ b/mocks/txs/tx.ts @@ -1,6 +1,7 @@ /* 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'; @@ -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, @@ -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', @@ -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', @@ -166,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', @@ -283,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, @@ -299,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', @@ -308,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, @@ -321,12 +322,30 @@ 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 base2 = { ...base, hash: '0x02d597ebcf3e8d60096dd0363bc2f0f5e2df27ba1dacd696c51aa7c9409f3193', from: { ...base.from, - hash: '0xd789a607CEac2f0E14867de4EB15b15C9FFB5859', + hash: addressMock.hash, }, }; @@ -335,7 +354,7 @@ export const base3 = { hash: '0x12d597ebcf3e8d60096dd0363bc2f0f5e2df27ba1dacd696c51aa7c9409f3193', from: { ...base.from, - hash: '0xd789a607CEac2f0E14867de4EB15b15C9FFB5859', + hash: addressMock.hash, }, }; @@ -357,3 +376,23 @@ export const withBlob = { tx_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/userOps/userOp.ts b/mocks/userOps/userOp.ts index efb7517187..e7368c20d5 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,7 +34,7 @@ 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, @@ -47,7 +48,6 @@ export const userOpData: UserOp = { 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/index.ts b/mocks/validators/index.ts deleted file mode 100644 index 22081cae8c..0000000000 --- a/mocks/validators/index.ts +++ /dev/null @@ -1,33 +0,0 @@ -import type { Validator, ValidatorsCountersResponse, ValidatorsResponse } from 'types/api/validators'; - -import * as addressMock from '../address/address'; - -export const validator1: Validator = { - address: addressMock.withName, - blocks_validated_count: 7334224, - state: 'active', -}; - -export const validator2: Validator = { - address: addressMock.withEns, - blocks_validated_count: 8937453, - state: 'probation', -}; - -export const validator3: Validator = { - address: addressMock.withoutName, - blocks_validated_count: 1234, - state: 'inactive', -}; - -export const validatorsResponse: ValidatorsResponse = { - items: [ validator1, validator2, validator3 ], - next_page_params: null, -}; - -export const validatorsCountersResponse: ValidatorsCountersResponse = { - active_validators_counter: '42', - active_validators_percentage: 7.14, - 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..bcaf55d941 --- /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_tx_hash: '0x7ae010e9758441b302db10282807358af460f38c49c618d26a897592f64977f7', + state_root: '0x183b4a38a4a6027947ceb93b323cc94c548c8c05cf605d73ca88351d77cae1a3', + status: 'Finalized', + timestamp: '2023-10-20T10:08:18.000000Z', + transactions: [ + '0xb5d432c270057c223b973f3b5f00dbad32823d9ef26f3e8d97c819c7c573453a', + ], + verify_tx_hash: '0x6f7eeaa0eb966e63d127bba6bf8f9046d303c2a1185b542f0b5083f682a0e87f', +}; + +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/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/zkevmL2txnBatches/zkevmL2txnBatch.ts b/mocks/zkevmL2txnBatches/zkevmL2txnBatch.ts deleted file mode 100644 index 56a0f67c33..0000000000 --- a/mocks/zkevmL2txnBatches/zkevmL2txnBatch.ts +++ /dev/null @@ -1,15 +0,0 @@ -import type { ZkEvmL2TxnBatch } from 'types/api/zkEvmL2'; - -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 895f9a7744..0000000000 --- a/mocks/zkevmL2txnBatches/zkevmL2txnBatches.ts +++ /dev/null @@ -1,26 +0,0 @@ -import type { ZkEvmL2TxnBatchesResponse } from 'types/api/zkEvmL2'; - -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..f1c2e19694 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', @@ -45,7 +46,16 @@ const moduleExports = { output: 'standalone', productionBrowserSourceMaps: true, experimental: { - instrumentationHook: true, + instrumentationHook: process.env.NEXT_OPEN_TELEMETRY_ENABLED === 'true', + // disabled as it is not stable yet + // turbo: { + // rules: { + // '*.svg': { + // loaders: [ '@svgr/webpack' ], + // as: '*.js', + // }, + // }, + // }, }, }; diff --git a/nextjs/PageNextJs.tsx b/nextjs/PageNextJs.tsx index 1c15c9f419..89e066ca6d 100644 --- a/nextjs/PageNextJs.tsx +++ b/nextjs/PageNextJs.tsx @@ -2,21 +2,26 @@ 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 +34,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 fa505e3ddf..ba59176fe5 100644 --- a/nextjs/csp/generateCspPolicy.ts +++ b/nextjs/csp/generateCspPolicy.ts @@ -6,10 +6,12 @@ function generateCspPolicy() { descriptors.app(), descriptors.ad(), descriptors.cloudFlare(), + descriptors.gasHawk(), descriptors.googleAnalytics(), descriptors.googleFonts(), descriptors.googleReCaptcha(), descriptors.growthBook(), + descriptors.marketplace(), descriptors.mixpanel(), descriptors.monaco(), descriptors.safe(), diff --git a/nextjs/csp/policies/ad.ts b/nextjs/csp/policies/ad.ts index 55f23ff913..9b018aa019 100644 --- a/nextjs/csp/policies/ad.ts +++ b/nextjs/csp/policies/ad.ts @@ -13,12 +13,16 @@ export function ad(): CspDev.DirectiveDescriptor { '*.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', @@ -35,7 +39,8 @@ export function ad(): CspDev.DirectiveDescriptor { // 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', diff --git a/nextjs/csp/policies/app.ts b/nextjs/csp/policies/app.ts index d1596cb1c5..1b7a495284 100644 --- a/nextjs/csp/policies/app.ts +++ b/nextjs/csp/policies/app.ts @@ -30,9 +30,19 @@ const getCspReportUrl = () => { } }; -export function app(): CspDev.DirectiveDescriptor { - const marketplaceFeaturePayload = getFeaturePayload(config.features.marketplace); +const externalFontsDomains = (() => { + try { + 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 { 'default-src': [ // KEY_WORDS.NONE, @@ -56,7 +66,7 @@ export function app(): CspDev.DirectiveDescriptor { getFeaturePayload(config.features.verifiedTokens)?.api.endpoint, getFeaturePayload(config.features.addressVerification)?.api.endpoint, getFeaturePayload(config.features.nameService)?.api.endpoint, - marketplaceFeaturePayload && 'api' in marketplaceFeaturePayload ? marketplaceFeaturePayload.api.endpoint : '', + getFeaturePayload(config.features.addressMetadata)?.api.endpoint, // chain RPC server config.chain.rpcUrl, @@ -74,8 +84,9 @@ 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=\'', ], 'style-src': [ @@ -117,6 +128,7 @@ export function app(): CspDev.DirectiveDescriptor { 'font-src': [ KEY_WORDS.DATA, ...MAIN_DOMAINS, + ...(externalFontsDomains || []), ], 'object-src': [ @@ -132,6 +144,10 @@ export function app(): CspDev.DirectiveDescriptor { '*', ], + 'frame-ancestors': [ + KEY_WORDS.SELF, + ], + ...((() => { if (!config.features.sentry.isEnabled) { return {}; 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/index.ts b/nextjs/csp/policies/index.ts index 10fd95a484..30bd992ba5 100644 --- a/nextjs/csp/policies/index.ts +++ b/nextjs/csp/policies/index.ts @@ -1,10 +1,12 @@ 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 { marketplace } from './marketplace'; export { mixpanel } from './mixpanel'; export { monaco } from './monaco'; export { safe } from './safe'; 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/walletConnect.ts b/nextjs/csp/policies/walletConnect.ts index 41cb948066..03f2dc0825 100644 --- a/nextjs/csp/policies/walletConnect.ts +++ b/nextjs/csp/policies/walletConnect.ts @@ -12,10 +12,16 @@ export function walletConnect(): CspDev.DirectiveDescriptor { return { 'connect-src': [ '*.web3modal.com', + '*.web3modal.org', '*.walletconnect.com', + '*.walletconnect.org', 'wss://relay.walletconnect.com', '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 6b3d959ab2..c9933fb679 100644 --- a/nextjs/getServerSideProps.ts +++ b/nextjs/getServerSideProps.ts @@ -1,23 +1,28 @@ -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'; import isNeedProxy from 'lib/api/isNeedProxy'; const rollupFeature = config.features.rollup; const adBannerFeature = config.features.adsBanner; +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: 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) { @@ -28,20 +33,16 @@ export const base: GetServerSideProps = async({ req, query }) => { return adBannerFeature.provider; } } - return ''; + 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: adBannerProvider, + apiData: null, }, }; }; @@ -66,8 +67,9 @@ export const verifiedAddresses: GetServerSideProps = async(context) => { return account(context); }; +const DEPOSITS_ROLLUP_TYPES: Array = [ 'optimistic', 'shibarium', 'zkEvm', 'arbitrum' ]; export const deposits: GetServerSideProps = async(context) => { - if (!(rollupFeature.isEnabled && (rollupFeature.type === 'optimistic' || rollupFeature.type === 'shibarium'))) { + if (!(rollupFeature.isEnabled && DEPOSITS_ROLLUP_TYPES.includes(rollupFeature.type))) { return { notFound: true, }; @@ -76,10 +78,11 @@ export const deposits: GetServerSideProps = async(context) => { return base(context); }; +const WITHDRAWALS_ROLLUP_TYPES: Array = [ 'optimistic', 'shibarium', 'zkEvm', 'arbitrum' ]; export const withdrawals: GetServerSideProps = async(context) => { if ( !config.features.beaconChain.isEnabled && - !(rollupFeature.isEnabled && (rollupFeature.type === 'optimistic' || rollupFeature.type === 'shibarium')) + !(rollupFeature.isEnabled && WITHDRAWALS_ROLLUP_TYPES.includes(rollupFeature.type)) ) { return { notFound: true, @@ -109,8 +112,9 @@ export const optimisticRollup: GetServerSideProps = async(context) => { return base(context); }; +const BATCH_ROLLUP_TYPES: Array = [ 'zkEvm', 'zkSync', 'arbitrum', 'optimistic' ]; export const batch: GetServerSideProps = async(context) => { - if (!(rollupFeature.isEnabled && (rollupFeature.type === 'zkEvm' || rollupFeature.type === 'zkSync'))) { + if (!(rollupFeature.isEnabled && BATCH_ROLLUP_TYPES.includes(rollupFeature.type))) { return { notFound: true, }; @@ -119,14 +123,15 @@ export const batch: GetServerSideProps = async(context) => { return base(context); }; -export const marketplace: GetServerSideProps = async(context) => { +export const marketplace = async (context: GetServerSidePropsContext): +Promise>> => { if (!config.features.marketplace.isEnabled) { return { notFound: true, }; } - return base(context); + return base(context); }; export const apiDocs: GetServerSideProps = async(context) => { @@ -139,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 { @@ -189,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 { @@ -239,3 +264,44 @@ export const login: GetServerSideProps = async(context) => { 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); +}; 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..b9466373a3 100644 --- a/nextjs/middlewares/index.ts +++ b/nextjs/middlewares/index.ts @@ -1 +1,2 @@ export { account } from './account'; +export { default as colorTheme } from './colorTheme'; diff --git a/nextjs/nextjs-routes.d.ts b/nextjs/nextjs-routes.d.ts index ee87eb5dc1..2d947f73ec 100644 --- a/nextjs/nextjs-routes.d.ts +++ b/nextjs/nextjs-routes.d.ts @@ -9,17 +9,22 @@ declare module "nextjs-routes" { | StaticRoute<"/404"> | StaticRoute<"/account/api-key"> | StaticRoute<"/account/custom-abi"> - | StaticRoute<"/account/public-tags-request"> | 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<"/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"> @@ -30,20 +35,26 @@ declare module "nextjs-routes" { | 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"> | StaticRoute<"/deposits"> + | StaticRoute<"/dispute-games"> | StaticRoute<"/gas-tracker"> | StaticRoute<"/graphiql"> | StaticRoute<"/"> | StaticRoute<"/login"> + | StaticRoute<"/mud-worlds"> | DynamicRoute<"/name-domains/[name]", { "name": string }> | StaticRoute<"/name-domains"> | DynamicRoute<"/op/[hash]", { "hash": string }> | StaticRoute<"/ops"> | StaticRoute<"/output-roots"> + | StaticRoute<"/public-tags/submit"> | StaticRoute<"/search-results"> + | StaticRoute<"/sprite"> | StaticRoute<"/stats"> | DynamicRoute<"/token/[hash]", { "hash": string }> | DynamicRoute<"/token/[hash]/instance/[id]", { "hash": string; "id": string }> diff --git a/nextjs/redirects.js b/nextjs/redirects.js index 3a0317d0e4..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 diff --git a/nextjs/types.ts b/nextjs/types.ts index 60c007daff..c0366ae090 100644 --- a/nextjs/types.ts +++ b/nextjs/types.ts @@ -1,6 +1,13 @@ import type { NextPage } from 'next'; +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..1eecf6333a --- /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..63eff42384 --- /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; + } | { + 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); + 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..0081781215 --- /dev/null +++ b/nextjs/utils/fetchProxy.ts @@ -0,0 +1,57 @@ +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: 'API fetch via Next.js proxy', + url, + // 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/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 eed616c134..ddbec9098b 100644 --- a/package.json +++ b/package.json @@ -10,7 +10,9 @@ "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": "NODE_OPTIONS=--max-old-space-size=6144 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", @@ -30,9 +32,14 @@ "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": "1.6.0", + "@blockscout/visualizer-types": "0.2.0", "@chakra-ui/react": "2.7.1", "@chakra-ui/theme-tools": "^2.0.18", "@emotion/react": "^11.10.4", @@ -43,36 +50,37 @@ "@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", + "@next/bundle-analyzer": "14.2.3", + "@opentelemetry/auto-instrumentations-node": "0.43.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", "@sentry/cli": "^2.21.2", "@sentry/react": "7.24.0", "@sentry/tracing": "7.24.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": "4.1.3", "algoliasearch": "^4.20.0", + "@web3modal/wagmi": "5.1.7", + "airtable": "^0.12.2", "bignumber.js": "^9.1.0", "blo": "^1.1.1", "chakra-react-select": "^4.4.3", "crypto-js": "^4.2.0", "d3": "^7.6.1", - "dappscout-iframe": "0.2.1", + "dappscout-iframe": "0.2.2", "dayjs": "^1.11.5", "dom-to-image": "^2.6.0", "focus-visible": "^5.2.0", "framer-motion": "^6.5.1", "getit-sdk": "^1.0.4", - "gradient-avatar": "^1.0.2", + "gradient-avatar": "git+https://github.com/blockscout/gradient-avatar.git", "graphiql": "^2.2.0", "graphql": "^16.8.1", "graphql-ws": "^5.11.3", @@ -81,19 +89,20 @@ "magic-bytes.js": "1.8.0", "mixpanel-browser": "^2.47.0", "monaco-editor": "^0.34.1", - "next": "13.5.4", + "next": "14.2.13", "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-device-detect": "^2.2.3", "react-dom": "18.2.0", - "react-google-recaptcha": "^2.1.0", + "react-google-recaptcha": "^3.1.0", "react-hook-form": "^7.33.1", "react-identicons": "^1.2.5", "react-intersection-observer": "^9.5.2", @@ -102,8 +111,9 @@ "react-scroll": "^1.8.7", "swagger-ui-react": "^5.9.0", "use-font-face-observer": "^1.2.1", - "viem": "2.9.6", - "wagmi": "2.5.16", + "valibot": "0.38.0", + "viem": "2.21.5", + "wagmi": "2.12.10", "xss": "^1.0.14" }, "devDependencies": { @@ -154,7 +164,7 @@ "typescript": "5.4.2", "vite-plugin-svgr": "^2.2.2", "vite-tsconfig-paths": "^3.5.2", - "ws": "^8.11.0" + "ws": "^8.17.1" }, "lint-staged": { "*.{js,jsx,ts,tsx}": "eslint --cache --fix" diff --git a/pages/_app.tsx b/pages/_app.tsx index 15fc5edd91..475700fbf5 100644 --- a/pages/_app.tsx +++ b/pages/_app.tsx @@ -1,4 +1,4 @@ -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'; @@ -12,13 +12,14 @@ 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 { ScrollDirectionProvider } from 'lib/contexts/scrollDirection'; import { growthBook } from 'lib/growthbook/init'; import useLoadFeatures from 'lib/growthbook/useLoadFeatures'; import useNotifyOnNavigation from 'lib/hooks/useNotifyOnNavigation'; import { SocketProvider } from 'lib/socket/context'; -import theme from 'theme'; 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'; @@ -38,7 +39,7 @@ 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 }, }; @@ -56,10 +57,11 @@ function MyApp({ Component, pageProps }: AppPropsWithLayout) { const getLayout = Component.getLayout ?? ((page) => { page }); return ( - + @@ -67,7 +69,9 @@ function MyApp({ Component, pageProps }: AppPropsWithLayout) { - { getLayout() } + + { getLayout() } + diff --git a/pages/_document.tsx b/pages/_document.tsx index 74ebe08fb5..2e879910dd 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,23 +36,23 @@ 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 ( - - - - - - - - - - ); -}; - -const HypeBannerWithWalletAddress = ({ className }: { className?: string }) => { +const HypeBanner = ({ className, platform }: BannerProps) => { const { address } = useAccount(); + React.useEffect(() => { if (address) { setWalletAddresses([ address ]); } }, [ address ]); - return ; -}; - -const HypeBanner = ({ className }: { className?: string }) => { - - const fallback = React.useCallback(() => { - return ; - }, [ className ]); + const banner = (() => { + switch (platform) { + case 'desktop': { + return ( + + + + ); + } + case 'mobile': { + return ( + + + + ); + } + default: { + return ( + <> + + + + + + + + ); + } + } + })(); return ( - - - + <> + + { banner } + ); }; 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..01549d84b4 100644 --- a/ui/shared/ad/TextAd.tsx +++ b/ui/shared/ad/TextAd.tsx @@ -10,7 +10,7 @@ import CoinzillaTextAd from './CoinzillaTextAd'; 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 81b057e679..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 (!('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/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 5024c657da..0a5d0efbd3 100644 --- a/ui/shared/address/AddressFromTo.tsx +++ b/ui/shared/address/AddressFromTo.tsx @@ -80,7 +80,7 @@ const AddressFromTo = ({ from, to, current, mode: modeProp, className, isLoading const iconSize = 20; return ( - + { - 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/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..2209eeeda7 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/blob/BlobDataType.pw.tsx b/ui/shared/blob/BlobDataType.pw.tsx index a5e5e18018..69b6a0e6ef 100644 --- a/ui/shared/blob/BlobDataType.pw.tsx +++ b/ui/shared/blob/BlobDataType.pw.tsx @@ -1,35 +1,22 @@ -import { test, expect } from '@playwright/experimental-ct-react'; import React from 'react'; -import TestApp from 'playwright/TestApp'; +import { test, expect } from 'playwright/lib'; import BlobDataType from './BlobDataType'; test.use({ viewport: { width: 100, height: 50 } }); -test('image data', async({ mount }) => { - const component = await mount( - - - , - ); +test('image data', async({ render }) => { + const component = await render(); await expect(component).toHaveScreenshot(); }); -test('raw data', async({ mount }) => { - const component = await mount( - - - , - ); +test('raw data', async({ render }) => { + const component = await render(); await expect(component).toHaveScreenshot(); }); -test('text data', async({ mount }) => { - const component = await mount( - - - , - ); +test('text data', async({ render }) => { + const component = await render(); await expect(component).toHaveScreenshot(); }); 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/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/ChartLine.tsx b/ui/shared/chart/ChartLine.tsx index a96b7e84c5..2d34add516 100644 --- a/ui/shared/chart/ChartLine.tsx +++ b/ui/shared/chart/ChartLine.tsx @@ -3,56 +3,38 @@ import React from 'react'; import type { TimeChartItem } from 'ui/shared/chart/types'; +import type { AnimationType } from './utils/animations'; +import { ANIMATIONS } from './utils/animations'; +import { getIncompleteDataLineSource } from './utils/formatters'; + interface Props extends React.SVGProps { xScale: d3.ScaleTime | d3.ScaleLinear; yScale: d3.ScaleTime | d3.ScaleLinear; data: Array; - animation: 'left' | 'fadeIn' | 'none'; + animation: AnimationType; } const ChartLine = ({ xScale, yScale, data, animation, ...props }: Props) => { - const ref = React.useRef(null); - - // Define different types of animation that we can use - const animateLeft = React.useCallback(() => { - const totalLength = ref.current?.getTotalLength() || 0; - d3.select(ref.current) - .attr('opacity', 1) - .attr('stroke-dasharray', `${ totalLength },${ totalLength }`) - .attr('stroke-dashoffset', totalLength) - .transition() - .duration(750) - .ease(d3.easeLinear) - .attr('stroke-dashoffset', 0); - }, []); - - const animateFadeIn = React.useCallback(() => { - d3.select(ref.current) - .transition() - .duration(750) - .ease(d3.easeLinear) - .attr('opacity', 1); - }, []); - - const noneAnimation = React.useCallback(() => { - d3.select(ref.current).attr('opacity', 1); - }, []); + const dataPathRef = React.useRef(null); + const incompleteDataPathRef = React.useRef(null); React.useEffect(() => { - const ANIMATIONS = { - left: animateLeft, - fadeIn: animateFadeIn, - none: noneAnimation, - }; const animationFn = ANIMATIONS[animation]; - window.setTimeout(animationFn, 100); - }, [ animateLeft, animateFadeIn, noneAnimation, animation ]); + const timeoutId = window.setTimeout(() => { + dataPathRef.current && animationFn(dataPathRef.current); + incompleteDataPathRef.current && animationFn(incompleteDataPathRef.current); + }, 100); + + return () => { + window.clearTimeout(timeoutId); + }; + }, [ animation ]); // Recalculate line length if scale has changed React.useEffect(() => { if (animation === 'left') { - const totalLength = ref.current?.getTotalLength(); - d3.select(ref.current).attr( + const totalLength = dataPathRef.current?.getTotalLength(); + d3.select(dataPathRef.current).attr( 'stroke-dasharray', `${ totalLength },${ totalLength }`, ); @@ -65,15 +47,27 @@ const ChartLine = ({ xScale, yScale, data, animation, ...props }: Props) => { .curve(d3.curveMonotoneX); return ( - + <> + + !isApproximate)) || undefined } + strokeWidth={ 1 } + strokeLinecap="round" + fill="none" + opacity={ 0 } + { ...props } + /> + ); }; diff --git a/ui/shared/chart/ChartTooltip.tsx b/ui/shared/chart/ChartTooltip.tsx index e257b5a519..71e51ab738 100644 --- a/ui/shared/chart/ChartTooltip.tsx +++ b/ui/shared/chart/ChartTooltip.tsx @@ -1,12 +1,16 @@ -import { useToken, useColorModeValue } from '@chakra-ui/react'; import * as d3 from 'd3'; import React from 'react'; -import type { TimeChartItem, TimeChartData } from 'ui/shared/chart/types'; +import type { TimeChartData } from 'ui/shared/chart/types'; -import computeTooltipPosition from 'ui/shared/chart/utils/computeTooltipPosition'; -import type { Pointer } from 'ui/shared/chart/utils/pointerTracker'; -import { trackPointer } from 'ui/shared/chart/utils/pointerTracker'; +import ChartTooltipBackdrop, { useRenderBackdrop } from './tooltip/ChartTooltipBackdrop'; +import ChartTooltipContent, { useRenderContent } from './tooltip/ChartTooltipContent'; +import ChartTooltipLine, { useRenderLine } from './tooltip/ChartTooltipLine'; +import ChartTooltipPoint, { useRenderPoints } from './tooltip/ChartTooltipPoint'; +import ChartTooltipRow, { useRenderRows } from './tooltip/ChartTooltipRow'; +import ChartTooltipTitle, { useRenderTitle } from './tooltip/ChartTooltipTitle'; +import { trackPointer } from './tooltip/pointerTracker'; +import type { Pointer } from './tooltip/pointerTracker'; interface Props { width?: number; @@ -16,151 +20,62 @@ interface Props { xScale: d3.ScaleTime; yScale: d3.ScaleLinear; anchorEl: SVGRectElement | null; + noAnimation?: boolean; } -const TEXT_LINE_HEIGHT = 12; -const PADDING = 16; -const LINE_SPACE = 10; -const POINT_SIZE = 16; -const LABEL_WIDTH = 80; - -const ChartTooltip = ({ xScale, yScale, width, tooltipWidth = 200, height, data, anchorEl, ...props }: Props) => { - const lineColor = useToken('colors', 'gray.400'); - const titleColor = useToken('colors', 'blue.100'); - const textColor = useToken('colors', 'white'); - const markerBgColor = useToken('colors', useColorModeValue('black', 'white')); - const markerBorderColor = useToken('colors', useColorModeValue('white', 'black')); - const bgColor = useToken('colors', 'blackAlpha.900'); - - const ref = React.useRef(null); +const ChartTooltip = ({ xScale, yScale, width, tooltipWidth = 200, height, data, anchorEl, noAnimation, ...props }: Props) => { + const ref = React.useRef(null); const trackerId = React.useRef(); const isVisible = React.useRef(false); - const drawLine = React.useCallback( - (x: number) => { - d3.select(ref.current) - .select('.ChartTooltip__line') - .attr('x1', x) - .attr('x2', x) - .attr('y1', 0) - .attr('y2', height || 0); - }, - [ ref, height ], - ); - - const drawContent = React.useCallback( - (x: number, y: number) => { - const tooltipContent = d3.select(ref.current).select('.ChartTooltip__content'); - - tooltipContent.attr('transform', (cur, i, nodes) => { - const node = nodes[i] as SVGGElement | null; - const { width: nodeWidth, height: nodeHeight } = node?.getBoundingClientRect() || { width: 0, height: 0 }; - const [ translateX, translateY ] = computeTooltipPosition({ - canvasWidth: width || 0, - canvasHeight: height || 0, - nodeWidth, - nodeHeight, - pointX: x, - pointY: y, - offset: POINT_SIZE, - }); - return `translate(${ translateX }, ${ translateY })`; - }); - - const date = xScale.invert(x); - const dateLabel = data[0].items.find((item) => item.date.getTime() === date.getTime())?.dateLabel; - - tooltipContent - .select('.ChartTooltip__contentDate') - .text(dateLabel || d3.timeFormat('%e %b %Y')(xScale.invert(x))); - }, - [ xScale, data, width, height ], - ); - - const updateDisplayedValue = React.useCallback((d: TimeChartItem, i: number) => { - const nodes = d3.select(ref.current) - .selectAll('.ChartTooltip__value') - .filter((td, tIndex) => tIndex === i) - .text( - (data[i].valueFormatter?.(d.value) || d.value.toLocaleString(undefined, { minimumSignificantDigits: 1 })) + - (data[i].units ? ` ${ data[i].units }` : ''), - ) - .nodes(); - - const widthLimit = tooltipWidth - 2 * PADDING - LABEL_WIDTH; - const width = nodes.map((node) => node?.getBoundingClientRect?.().width); - const maxNodeWidth = Math.max(...width); - d3.select(ref.current) - .select('.ChartTooltip__contentBg') - .attr('width', tooltipWidth + Math.max(0, (maxNodeWidth - widthLimit))); - - }, [ data, tooltipWidth ]); - - const drawPoints = React.useCallback((x: number) => { - const xDate = xScale.invert(x); - const bisectDate = d3.bisector((d) => d.date).left; - let baseXPos = 0; - let baseYPos = 0; + const transitionDuration = !noAnimation ? 100 : null; - d3.select(ref.current) - .selectAll('.ChartTooltip__point') - .attr('transform', (cur, i) => { - const index = bisectDate(data[i].items, xDate, 1); - const d0 = data[i].items[index - 1] as TimeChartItem | undefined; - const d1 = data[i].items[index] as TimeChartItem | undefined; - const d = (() => { - if (!d0) { - return d1; - } - if (!d1) { - return d0; - } - return xDate.getTime() - d0.date.getTime() > d1.date.getTime() - xDate.getTime() ? d1 : d0; - })(); - - if (d?.date === undefined && d?.value === undefined) { - // move point out of container - return 'translate(-100,-100)'; - } - - const xPos = xScale(d.date); - const yPos = yScale(d.value); - - if (i === 0) { - baseXPos = xPos; - baseYPos = yPos; - } - - updateDisplayedValue(d, i); - - return `translate(${ xPos }, ${ yPos })`; - }); - - return [ baseXPos, baseYPos ]; - }, [ data, updateDisplayedValue, xScale, yScale ]); + const renderLine = useRenderLine(ref, height); + const renderContent = useRenderContent(ref, { chart: { width, height }, transitionDuration }); + const renderPoints = useRenderPoints(ref, { data, xScale, yScale }); + const renderTitle = useRenderTitle(ref); + const renderRows = useRenderRows(ref, { data, xScale, minWidth: tooltipWidth }); + const renderBackdrop = useRenderBackdrop(ref, { seriesNum: data.length, transitionDuration }); const draw = React.useCallback((pointer: Pointer) => { if (pointer.point) { - const [ baseXPos, baseYPos ] = drawPoints(pointer.point[0]); - drawLine(baseXPos); - drawContent(baseXPos, baseYPos); + const { x, y, currentPoints } = renderPoints(pointer.point[0]); + const isIncompleteData = currentPoints.some(({ item }) => item.isApproximate); + renderLine(x); + renderContent(x, y); + renderTitle(isIncompleteData); + const { width } = renderRows(x, currentPoints); + renderBackdrop(width, isIncompleteData); } - }, [ drawPoints, drawLine, drawContent ]); + }, [ renderPoints, renderLine, renderContent, renderTitle, renderRows, renderBackdrop ]); const showContent = React.useCallback(() => { if (!isVisible.current) { - d3.select(ref.current).attr('opacity', 1); - d3.select(ref.current) - .selectAll('.ChartTooltip__point') - .attr('opacity', 1); + if (transitionDuration) { + d3.select(ref.current) + .transition() + .delay(transitionDuration) + .attr('opacity', 1); + } else { + d3.select(ref.current) + .attr('opacity', 1); + } isVisible.current = true; } - }, []); + }, [ transitionDuration ]); const hideContent = React.useCallback(() => { - d3.select(ref.current).attr('opacity', 0); + if (transitionDuration) { + d3.select(ref.current) + .transition() + .delay(transitionDuration) + .attr('opacity', 0); + } else { + d3.select(ref.current) + .attr('opacity', 0); + } isVisible.current = false; - }, []); + }, [ transitionDuration ]); const createPointerTracker = React.useCallback((event: PointerEvent, isSubsequentCall?: boolean) => { let isPressed = event.pointerType === 'mouse' && event.type === 'pointerdown' && !isSubsequentCall; @@ -224,73 +139,21 @@ const ChartTooltip = ({ xScale, yScale, width, tooltipWidth = 200, height, data, }, [ anchorEl, createPointerTracker, draw, hideContent, showContent ]); return ( - - - { data.map(({ name }) => ( - - )) } - - - - - Date - - - - { data.map(({ name }, index) => ( - - - { name } - - - - )) } - + + + { data.map(({ name }) => ) } + + + + + { data.map(({ name }, index) => ) } + ); }; diff --git a/ui/shared/chart/ChartWidget.pw.tsx b/ui/shared/chart/ChartWidget.pw.tsx index 4100c69801..21fc07272f 100644 --- a/ui/shared/chart/ChartWidget.pw.tsx +++ b/ui/shared/chart/ChartWidget.pw.tsx @@ -1,7 +1,8 @@ -import { test, expect } from '@playwright/experimental-ct-react'; import React from 'react'; -import TestApp from 'playwright/TestApp'; +import type { TimeChartItem } from './types'; + +import { test, expect } from 'playwright/lib'; import type { Props } from './ChartWidget'; import ChartWidget from './ChartWidget'; @@ -27,14 +28,12 @@ const props: Props = { units: 'ETH', isLoading: false, isError: false, + noAnimation: true, }; -test('base view +@dark-mode', async({ mount, page }) => { - const component = await mount( - - - , - ); +test('base view +@dark-mode', async({ render, page }) => { + const component = await render(); + await page.waitForFunction(() => { return document.querySelector('path[data-name="chart-Nativecoincirculatingsupply-small"]')?.getAttribute('opacity') === '1'; }); @@ -45,6 +44,7 @@ test('base view +@dark-mode', async({ mount, page }) => { await page.mouse.move(0, 0); await page.mouse.click(0, 0); + await page.mouse.move(80, 150); await page.mouse.move(100, 150); await expect(component).toHaveScreenshot(); @@ -54,27 +54,17 @@ test('base view +@dark-mode', async({ mount, page }) => { await expect(component).toHaveScreenshot(); }); -test('loading', async({ mount }) => { - const component = await mount( - - - , - ); - +test('loading', async({ render }) => { + const component = await render(); await expect(component).toHaveScreenshot(); }); -test('error', async({ mount }) => { - const component = await mount( - - - , - ); - +test('error', async({ render }) => { + const component = await render(); await expect(component).toHaveScreenshot(); }); -test('small values', async({ mount, page }) => { +test('small values', async({ render, page }) => { const modifiedProps = { ...props, items: [ @@ -92,18 +82,14 @@ test('small values', async({ mount, page }) => { ], }; - const component = await mount( - - - , - ); + const component = await render(); await page.waitForFunction(() => { return document.querySelector('path[data-name="chart-Nativecoincirculatingsupply-small"]')?.getAttribute('opacity') === '1'; }); await expect(component).toHaveScreenshot(); }); -test('small variations in big values', async({ mount, page }) => { +test('small variations in big values', async({ render, page }) => { const modifiedProps = { ...props, items: [ @@ -121,13 +107,30 @@ test('small variations in big values', async({ mount, page }) => { ], }; - const component = await mount( - - - , - ); + const component = await render(); + await page.waitForFunction(() => { + return document.querySelector('path[data-name="chart-Nativecoincirculatingsupply-small"]')?.getAttribute('opacity') === '1'; + }); + await expect(component).toHaveScreenshot(); +}); + +test('incomplete day', async({ render, page }) => { + const modifiedProps = { + ...props, + items: [ + ...props.items as Array, + { date: new Date('2023-02-24'), value: 25136740.887217894 / 4, isApproximate: true }, + ], + }; + + const component = await render(); await page.waitForFunction(() => { return document.querySelector('path[data-name="chart-Nativecoincirculatingsupply-small"]')?.getAttribute('opacity') === '1'; }); await expect(component).toHaveScreenshot(); + + await page.hover('.ChartOverlay', { position: { x: 120, y: 120 } }); + await page.hover('.ChartOverlay', { position: { x: 320, y: 120 } }); + await expect(page.getByText('Incomplete day')).toBeVisible(); + await expect(component).toHaveScreenshot(); }); diff --git a/ui/shared/chart/ChartWidget.tsx b/ui/shared/chart/ChartWidget.tsx index 86cc601221..e32a1e29ae 100644 --- a/ui/shared/chart/ChartWidget.tsx +++ b/ui/shared/chart/ChartWidget.tsx @@ -4,7 +4,6 @@ import { chakra, Flex, IconButton, Link, - Menu, MenuButton, MenuItem, MenuList, @@ -22,6 +21,7 @@ import type { TimeChartItem } from './types'; import dayjs from 'lib/date/dayjs'; import { apos } from 'lib/html-entities'; import saveAsCSV from 'lib/saveAsCSV'; +import Menu from 'ui/shared/chakra/Menu'; import IconSvg from 'ui/shared/IconSvg'; import ChartWidgetGraph from './ChartWidgetGraph'; @@ -35,11 +35,13 @@ export type Props = { isLoading: boolean; className?: string; isError: boolean; + emptyText?: string; + noAnimation?: boolean; } const DOWNLOAD_IMAGE_SCALE = 5; -const ChartWidget = ({ items, title, description, isLoading, className, isError, units }: Props) => { +const ChartWidget = ({ items, title, description, isLoading, className, isError, units, emptyText, noAnimation }: Props) => { const ref = useRef(null); const [ isFullscreen, setIsFullscreen ] = useState(false); const [ isZoomResetInitial, setIsZoomResetInitial ] = React.useState(true); @@ -134,7 +136,7 @@ const ChartWidget = ({ items, title, description, isLoading, className, isError, if (!hasItems) { return (
- No data + { emptyText || 'No data' }
); } @@ -147,6 +149,7 @@ const ChartWidget = ({ items, title, description, isLoading, className, isError, isZoomResetInitial={ isZoomResetInitial } title={ title } units={ units } + noAnimation={ noAnimation } /> ); @@ -197,7 +200,7 @@ const ChartWidget = ({ items, title, description, isLoading, className, isError, size="sm" variant="outline" onClick={ handleZoomResetClick } - icon={ } + icon={ } /> diff --git a/ui/shared/chart/ChartWidgetGraph.tsx b/ui/shared/chart/ChartWidgetGraph.tsx index 7dbce5bb55..ce1ee8e4c9 100644 --- a/ui/shared/chart/ChartWidgetGraph.tsx +++ b/ui/shared/chart/ChartWidgetGraph.tsx @@ -23,13 +23,14 @@ interface Props { onZoom: () => void; isZoomResetInitial: boolean; margin?: ChartMargin; + noAnimation?: boolean; } // temporarily turn off the data aggregation, we need a better algorithm for that const MAX_SHOW_ITEMS = 100_000_000_000; const DEFAULT_CHART_MARGIN = { bottom: 20, left: 10, right: 20, top: 10 }; -const ChartWidgetGraph = ({ isEnlarged, items, onZoom, isZoomResetInitial, title, margin: marginProps, units }: Props) => { +const ChartWidgetGraph = ({ isEnlarged, items, onZoom, isZoomResetInitial, title, margin: marginProps, units, noAnimation }: Props) => { const isMobile = useIsMobile(); const color = useToken('colors', 'blue.200'); const chartId = `chart-${ title.split(' ').join('') }-${ isEnlarged ? 'fullscreen' : 'small' }`; @@ -99,7 +100,7 @@ const ChartWidgetGraph = ({ isEnlarged, items, onZoom, isZoomResetInitial, title scale={ axes.y.scale } ticks={ axesConfig.y.ticks } size={ innerWidth } - disableAnimation + noAnimation /> @@ -146,6 +148,7 @@ const ChartWidgetGraph = ({ isEnlarged, items, onZoom, isZoomResetInitial, title xScale={ axes.x.scale } yScale={ axes.y.scale } data={ chartData } + noAnimation={ noAnimation } /> } + leftIcon={ } colorScheme="blue" gridColumn={ 2 } justifySelf="end" diff --git a/ui/shared/chart/__screenshots__/ChartWidget.pw.tsx_default_incomplete-day-1.png b/ui/shared/chart/__screenshots__/ChartWidget.pw.tsx_default_incomplete-day-1.png new file mode 100644 index 0000000000..b031715818 Binary files /dev/null and b/ui/shared/chart/__screenshots__/ChartWidget.pw.tsx_default_incomplete-day-1.png differ diff --git a/ui/shared/chart/__screenshots__/ChartWidget.pw.tsx_default_incomplete-day-2.png b/ui/shared/chart/__screenshots__/ChartWidget.pw.tsx_default_incomplete-day-2.png new file mode 100644 index 0000000000..073dfce3a1 Binary files /dev/null and b/ui/shared/chart/__screenshots__/ChartWidget.pw.tsx_default_incomplete-day-2.png differ diff --git a/ui/shared/chart/tooltip/ChartTooltipBackdrop.tsx b/ui/shared/chart/tooltip/ChartTooltipBackdrop.tsx new file mode 100644 index 0000000000..67ddee6bf8 --- /dev/null +++ b/ui/shared/chart/tooltip/ChartTooltipBackdrop.tsx @@ -0,0 +1,46 @@ +import { useToken } from '@chakra-ui/react'; +import * as d3 from 'd3'; +import React from 'react'; + +import { calculateContainerHeight } from './utils'; + +const ChartTooltipBackdrop = () => { + const bgColor = useToken('colors', 'blackAlpha.900'); + + return ( + + ); +}; + +export default React.memo(ChartTooltipBackdrop); + +interface UseRenderBackdropParams { + seriesNum: number; + transitionDuration: number | null; +} + +export function useRenderBackdrop(ref: React.RefObject, { seriesNum, transitionDuration }: UseRenderBackdropParams) { + return React.useCallback((width: number, isIncompleteData: boolean) => { + const height = calculateContainerHeight(seriesNum, isIncompleteData); + + if (transitionDuration) { + d3.select(ref.current) + .select('.ChartTooltip__backdrop') + .transition() + .duration(transitionDuration) + .ease(d3.easeLinear) + .attr('width', width) + .attr('height', height); + } else { + d3.select(ref.current) + .select('.ChartTooltip__backdrop') + .attr('width', width) + .attr('height', height); + } + }, [ ref, seriesNum, transitionDuration ]); +} diff --git a/ui/shared/chart/tooltip/ChartTooltipContent.tsx b/ui/shared/chart/tooltip/ChartTooltipContent.tsx new file mode 100644 index 0000000000..da1a62a5d6 --- /dev/null +++ b/ui/shared/chart/tooltip/ChartTooltipContent.tsx @@ -0,0 +1,101 @@ +import * as d3 from 'd3'; +import _clamp from 'lodash/clamp'; +import React from 'react'; + +import { POINT_SIZE } from './utils'; + +interface Props { + children: React.ReactNode; +} + +const ChartTooltipContent = ({ children }: Props) => { + return { children }; +}; + +export default React.memo(ChartTooltipContent); + +interface UseRenderContentParams { + chart: { + width?: number; + height?: number; + }; + transitionDuration: number | null; +} + +export function useRenderContent(ref: React.RefObject, { chart, transitionDuration }: UseRenderContentParams) { + return React.useCallback((x: number, y: number) => { + const tooltipContent = d3.select(ref.current).select('.ChartTooltip__content'); + + const transformAttributeFn: d3.ValueFn = (cur, i, nodes) => { + const node = nodes[i] as SVGGElement | null; + const { width: nodeWidth, height: nodeHeight } = node?.getBoundingClientRect() || { width: 0, height: 0 }; + const [ translateX, translateY ] = calculatePosition({ + canvasWidth: chart.width || 0, + canvasHeight: chart.height || 0, + nodeWidth, + nodeHeight, + pointX: x, + pointY: y, + offset: POINT_SIZE, + }); + return `translate(${ translateX }, ${ translateY })`; + }; + + if (transitionDuration) { + tooltipContent + .transition() + .duration(transitionDuration) + .ease(d3.easeLinear) + .attr('transform', transformAttributeFn); + } else { + tooltipContent + .attr('transform', transformAttributeFn); + } + + }, [ chart.height, chart.width, ref, transitionDuration ]); +} + +interface CalculatePositionParams { + pointX: number; + pointY: number; + offset: number; + nodeWidth: number; + nodeHeight: number; + canvasWidth: number; + canvasHeight: number; +} + +function calculatePosition({ pointX, pointY, canvasWidth, canvasHeight, nodeWidth, nodeHeight, offset }: CalculatePositionParams): [ number, number ] { + // right + if (pointX + offset + nodeWidth <= canvasWidth) { + const x = pointX + offset; + const y = _clamp(pointY - nodeHeight / 2, 0, canvasHeight - nodeHeight); + return [ x, y ]; + } + + // left + if (nodeWidth + offset <= pointX) { + const x = pointX - offset - nodeWidth; + const y = _clamp(pointY - nodeHeight / 2, 0, canvasHeight - nodeHeight); + return [ x, y ]; + } + + // top + if (nodeHeight + offset <= pointY) { + const x = _clamp(pointX - nodeWidth / 2, 0, canvasWidth - nodeWidth); + const y = pointY - offset - nodeHeight; + return [ x, y ]; + } + + // bottom + if (pointY + offset + nodeHeight <= canvasHeight) { + const x = _clamp(pointX - nodeWidth / 2, 0, canvasWidth - nodeWidth); + const y = pointY + offset; + return [ x, y ]; + } + + const x = _clamp(pointX / 2, 0, canvasWidth - nodeWidth); + const y = _clamp(pointY / 2, 0, canvasHeight - nodeHeight); + + return [ x, y ]; +} diff --git a/ui/shared/chart/tooltip/ChartTooltipLine.tsx b/ui/shared/chart/tooltip/ChartTooltipLine.tsx new file mode 100644 index 0000000000..7397365945 --- /dev/null +++ b/ui/shared/chart/tooltip/ChartTooltipLine.tsx @@ -0,0 +1,21 @@ +import { useToken } from '@chakra-ui/react'; +import * as d3 from 'd3'; +import React from 'react'; + +const ChartTooltipLine = () => { + const lineColor = useToken('colors', 'gray.400'); + return ; +}; + +export default React.memo(ChartTooltipLine); + +export function useRenderLine(ref: React.RefObject, chartHeight: number | undefined) { + return React.useCallback((x: number) => { + d3.select(ref.current) + .select('.ChartTooltip__line') + .attr('x1', x) + .attr('x2', x) + .attr('y1', 0) + .attr('y2', chartHeight || 0); + }, [ ref, chartHeight ]); +} diff --git a/ui/shared/chart/tooltip/ChartTooltipPoint.tsx b/ui/shared/chart/tooltip/ChartTooltipPoint.tsx new file mode 100644 index 0000000000..c6dd1052f4 --- /dev/null +++ b/ui/shared/chart/tooltip/ChartTooltipPoint.tsx @@ -0,0 +1,93 @@ +import { useColorModeValue, useToken } from '@chakra-ui/react'; +import * as d3 from 'd3'; +import React from 'react'; + +import type { TimeChartData, TimeChartItem } from 'ui/shared/chart/types'; + +import { POINT_SIZE } from './utils'; + +const ChartTooltipPoint = () => { + const bgColor = useToken('colors', useColorModeValue('black', 'white')); + const borderColor = useToken('colors', useColorModeValue('white', 'black')); + + return ( + + ); +}; + +export default React.memo(ChartTooltipPoint); + +interface UseRenderPointsParams { + data: TimeChartData; + xScale: d3.ScaleTime; + yScale: d3.ScaleLinear; +} + +export interface CurrentPoint { + datumIndex: number; + item: TimeChartItem; +} + +interface RenderPointsReturnType{ + x: number; + y: number; + currentPoints: Array; +} + +export function useRenderPoints(ref: React.RefObject, params: UseRenderPointsParams) { + return React.useCallback((x: number): RenderPointsReturnType => { + const xDate = params.xScale.invert(x); + const bisectDate = d3.bisector((d) => d.date).left; + let baseXPos = 0; + let baseYPos = 0; + const currentPoints: Array = []; + + d3.select(ref.current) + .selectAll('.ChartTooltip__point') + .attr('transform', (cur, elementIndex) => { + const datum = params.data[elementIndex]; + const index = bisectDate(datum.items, xDate, 1); + const d0 = datum.items[index - 1] as TimeChartItem | undefined; + const d1 = datum.items[index] as TimeChartItem | undefined; + const d = (() => { + if (!d0) { + return d1; + } + if (!d1) { + return d0; + } + return xDate.getTime() - d0.date.getTime() > d1.date.getTime() - xDate.getTime() ? d1 : d0; + })(); + + if (d?.date === undefined && d?.value === undefined) { + // move point out of container + return 'translate(-100,-100)'; + } + + const xPos = params.xScale(d.date); + const yPos = params.yScale(d.value); + + if (elementIndex === 0) { + baseXPos = xPos; + baseYPos = yPos; + } + + currentPoints.push({ item: d, datumIndex: elementIndex }); + + return `translate(${ xPos }, ${ yPos })`; + }); + + return { + x: baseXPos, + y: baseYPos, + currentPoints, + }; + }, [ ref, params ]); +} diff --git a/ui/shared/chart/tooltip/ChartTooltipRow.tsx b/ui/shared/chart/tooltip/ChartTooltipRow.tsx new file mode 100644 index 0000000000..a35dcb25f1 --- /dev/null +++ b/ui/shared/chart/tooltip/ChartTooltipRow.tsx @@ -0,0 +1,96 @@ +import { useToken } from '@chakra-ui/react'; +import * as d3 from 'd3'; +import React from 'react'; + +import type { TimeChartData } from '../types'; + +import type { CurrentPoint } from './ChartTooltipPoint'; +import { calculateRowTransformValue, LABEL_WIDTH, PADDING } from './utils'; + +type Props = { + lineNum: number; +} & ({ label: string; children?: never } | { children: React.ReactNode; label?: never }) + +const ChartTooltipRow = ({ label, lineNum, children }: Props) => { + const labelColor = useToken('colors', 'blue.100'); + const textColor = useToken('colors', 'white'); + + return ( + + { children || ( + <> + + { label } + + + + ) } + + ); +}; + +export default React.memo(ChartTooltipRow); + +interface UseRenderRowsParams { + data: TimeChartData; + xScale: d3.ScaleTime; + minWidth: number; +} + +interface UseRenderRowsReturnType { + width: number; +} + +export function useRenderRows(ref: React.RefObject, { data, xScale, minWidth }: UseRenderRowsParams) { + return React.useCallback((x: number, currentPoints: Array): UseRenderRowsReturnType => { + + // update "transform" prop of all rows + const isIncompleteData = currentPoints.some(({ item }) => item.isApproximate); + d3.select(ref.current) + .selectAll('.ChartTooltip__row') + .attr('transform', (datum, index) => { + return calculateRowTransformValue(index - (isIncompleteData ? 0 : 1)); + }); + + // update date and indicators value + // here we assume that the first value element contains the date + const valueNodes = d3.select(ref.current) + .selectAll('.ChartTooltip__value') + .text((_, index) => { + if (index === 0) { + const date = xScale.invert(x); + const dateValue = data[0].items.find((item) => item.date.getTime() === date.getTime())?.dateLabel; + const dateValueFallback = d3.timeFormat('%e %b %Y')(xScale.invert(x)); + return dateValue || dateValueFallback; + } + + const { datumIndex, item } = currentPoints.find(({ datumIndex }) => datumIndex === index - 1) || {}; + if (datumIndex === undefined || !item) { + return null; + } + + const value = data[datumIndex]?.valueFormatter?.(item.value) ?? item.value.toLocaleString(undefined, { minimumSignificantDigits: 1 }); + const units = data[datumIndex]?.units ? ` ${ data[datumIndex]?.units }` : ''; + + return value + units; + }) + .nodes(); + + const valueWidths = valueNodes.map((node) => node?.getBoundingClientRect?.().width); + const maxValueWidth = Math.max(...valueWidths); + const maxRowWidth = Math.max(minWidth, 2 * PADDING + LABEL_WIDTH + maxValueWidth); + + return { width: maxRowWidth }; + + }, [ data, minWidth, ref, xScale ]); +} diff --git a/ui/shared/chart/tooltip/ChartTooltipTitle.tsx b/ui/shared/chart/tooltip/ChartTooltipTitle.tsx new file mode 100644 index 0000000000..93ab2e9943 --- /dev/null +++ b/ui/shared/chart/tooltip/ChartTooltipTitle.tsx @@ -0,0 +1,33 @@ +import { useToken } from '@chakra-ui/react'; +import * as d3 from 'd3'; +import React from 'react'; + +import ChartTooltipRow from './ChartTooltipRow'; + +const ChartTooltipTitle = () => { + const titleColor = useToken('colors', 'yellow.300'); + + return ( + + + Incomplete day + + + ); +}; + +export default React.memo(ChartTooltipTitle); + +export function useRenderTitle(ref: React.RefObject) { + return React.useCallback((isVisible: boolean) => { + d3.select(ref.current) + .select('.ChartTooltip__title') + .attr('opacity', isVisible ? 1 : 0); + }, [ ref ]); +} diff --git a/ui/shared/chart/utils/pointerTracker.tsx b/ui/shared/chart/tooltip/pointerTracker.ts similarity index 100% rename from ui/shared/chart/utils/pointerTracker.tsx rename to ui/shared/chart/tooltip/pointerTracker.ts diff --git a/ui/shared/chart/tooltip/utils.ts b/ui/shared/chart/tooltip/utils.ts new file mode 100644 index 0000000000..8b11b366a9 --- /dev/null +++ b/ui/shared/chart/tooltip/utils.ts @@ -0,0 +1,16 @@ +export const TEXT_LINE_HEIGHT = 12; +export const PADDING = 16; +export const LINE_SPACE = 10; +export const POINT_SIZE = 16; +export const LABEL_WIDTH = 80; + +export const calculateContainerHeight = (seriesNum: number, isIncomplete?: boolean) => { + const linesNum = isIncomplete ? seriesNum + 2 : seriesNum + 1; + + return 2 * PADDING + linesNum * TEXT_LINE_HEIGHT + (linesNum - 1) * LINE_SPACE; +}; + +export const calculateRowTransformValue = (rowNum: number) => { + const top = Math.max(0, PADDING + rowNum * (LINE_SPACE + TEXT_LINE_HEIGHT)); + return `translate(${ PADDING },${ top })`; +}; diff --git a/ui/shared/chart/types.tsx b/ui/shared/chart/types.tsx index 5810fe2fb5..c1c02b1b12 100644 --- a/ui/shared/chart/types.tsx +++ b/ui/shared/chart/types.tsx @@ -8,6 +8,7 @@ export interface TimeChartItem { date: Date; dateLabel?: string; value: number; + isApproximate?: boolean; } export interface ChartMargin { diff --git a/ui/shared/chart/utils/animations.ts b/ui/shared/chart/utils/animations.ts new file mode 100644 index 0000000000..2f873f4a69 --- /dev/null +++ b/ui/shared/chart/utils/animations.ts @@ -0,0 +1,33 @@ +import * as d3 from 'd3'; + +export type AnimationType = 'left' | 'fadeIn' | 'none'; + +export const animateLeft = (path: SVGPathElement) => { + const totalLength = path.getTotalLength() || 0; + d3.select(path) + .attr('opacity', 1) + .attr('stroke-dasharray', `${ totalLength },${ totalLength }`) + .attr('stroke-dashoffset', totalLength) + .transition() + .duration(750) + .ease(d3.easeLinear) + .attr('stroke-dashoffset', 0); +}; + +export const animateFadeIn = (path: SVGPathElement) => { + d3.select(path) + .transition() + .duration(750) + .ease(d3.easeLinear) + .attr('opacity', 1); +}; + +export const noneAnimation = (path: SVGPathElement) => { + d3.select(path).attr('opacity', 1); +}; + +export const ANIMATIONS: Record void> = { + left: animateLeft, + fadeIn: animateFadeIn, + none: noneAnimation, +}; diff --git a/ui/shared/chart/utils/computeTooltipPosition.ts b/ui/shared/chart/utils/computeTooltipPosition.ts deleted file mode 100644 index 88356d34cd..0000000000 --- a/ui/shared/chart/utils/computeTooltipPosition.ts +++ /dev/null @@ -1,46 +0,0 @@ -import _clamp from 'lodash/clamp'; - -interface Params { - pointX: number; - pointY: number; - offset: number; - nodeWidth: number; - nodeHeight: number; - canvasWidth: number; - canvasHeight: number; -} - -export default function computeTooltipPosition({ pointX, pointY, canvasWidth, canvasHeight, nodeWidth, nodeHeight, offset }: Params): [ number, number ] { - // right - if (pointX + offset + nodeWidth <= canvasWidth) { - const x = pointX + offset; - const y = _clamp(pointY - nodeHeight / 2, 0, canvasHeight - nodeHeight); - return [ x, y ]; - } - - // left - if (nodeWidth + offset <= pointX) { - const x = pointX - offset - nodeWidth; - const y = _clamp(pointY - nodeHeight / 2, 0, canvasHeight - nodeHeight); - return [ x, y ]; - } - - // top - if (nodeHeight + offset <= pointY) { - const x = _clamp(pointX - nodeWidth / 2, 0, canvasWidth - nodeWidth); - const y = pointY - offset - nodeHeight; - return [ x, y ]; - } - - // bottom - if (pointY + offset + nodeHeight <= canvasHeight) { - const x = _clamp(pointX - nodeWidth / 2, 0, canvasWidth - nodeWidth); - const y = pointY + offset; - return [ x, y ]; - } - - const x = _clamp(pointX / 2, 0, canvasWidth - nodeWidth); - const y = _clamp(pointY / 2, 0, canvasHeight - nodeHeight); - - return [ x, y ]; -} diff --git a/ui/shared/chart/utils/formatters.ts b/ui/shared/chart/utils/formatters.ts new file mode 100644 index 0000000000..9d0d13a7d2 --- /dev/null +++ b/ui/shared/chart/utils/formatters.ts @@ -0,0 +1,19 @@ +import type { TimeChartItem } from '../types'; + +export const getIncompleteDataLineSource = (data: Array): Array => { + const result: Array = []; + + for (let index = 0; index < data.length; index++) { + const current = data[index]; + if (current.isApproximate) { + const prev = data[index - 1]; + const next = data[index + 1]; + + prev && !prev.isApproximate && result.push(prev); + result.push(current); + next && !next.isApproximate && result.push(next); + } + } + + return result; +}; diff --git a/ui/shared/entities/address/AddressEntity.pw.tsx b/ui/shared/entities/address/AddressEntity.pw.tsx index 91ffb6d093..a06d3a6092 100644 --- a/ui/shared/entities/address/AddressEntity.pw.tsx +++ b/ui/shared/entities/address/AddressEntity.pw.tsx @@ -1,27 +1,26 @@ import { Box } from '@chakra-ui/react'; -import { test, expect } from '@playwright/experimental-ct-react'; import React from 'react'; import { AddressHighlightProvider } from 'lib/contexts/addressHighlight'; import * as addressMock from 'mocks/address/address'; -import TestApp from 'playwright/TestApp'; +import * as implementationsMock from 'mocks/address/implementations'; +import * as metadataMock from 'mocks/metadata/address'; +import { test, expect } from 'playwright/lib'; import AddressEntity from './AddressEntity'; -const iconSizes = [ 'md', 'lg' ]; +const iconSizes = [ 'md', 'lg' ] as const; test.use({ viewport: { width: 180, height: 140 } }); test.describe('icon size', () => { iconSizes.forEach((size) => { - test(size, async({ mount }) => { - const component = await mount( - - - , + test(size, async({ render }) => { + const component = await render( + , ); await expect(component).toHaveScreenshot(); @@ -30,54 +29,110 @@ test.describe('icon size', () => { }); test.describe('contract', () => { - test('unverified', async({ mount, page }) => { - const component = await mount( - - - , + test('unverified', async({ render, page }) => { + const component = await render( + , ); await component.getByText(/eternal/i).hover(); await expect(page).toHaveScreenshot(); }); - test('verified', async({ mount }) => { - const component = await mount( - - - , + test('verified', async({ render }) => { + const component = await render( + , ); await expect(component).toHaveScreenshot(); }); }); +test.describe('proxy contract', () => { + test.use({ viewport: { width: 500, height: 300 } }); + + test('with implementation name', async({ render, page }) => { + const component = await render( + , + ); + + await component.getByText(/home/i).hover(); + await expect(page.getByText('Proxy contract')).toBeVisible(); + await expect(page).toHaveScreenshot(); + }); + + test('without implementation name', async({ render, page }) => { + const component = await render( + , + ); + + await component.getByText(/eternal/i).hover(); + await expect(page.getByText('Proxy contract')).toBeVisible(); + await expect(page).toHaveScreenshot(); + }); + + test('without any name', async({ render, page }) => { + const component = await render( + , + ); + + await component.getByText(addressMock.contract.hash.slice(0, 4)).hover(); + await expect(page.getByText('Proxy contract')).toBeVisible(); + await expect(page).toHaveScreenshot(); + }); + + test('with multiple implementations', async({ render, page }) => { + const component = await render( + , + ); + + await component.getByText(/eternal/i).hover(); + await expect(page.getByText('Proxy contract')).toBeVisible(); + await expect(page).toHaveScreenshot(); + }); + + test('with name tag', async({ render, page }) => { + const component = await render( + , + ); + + await component.getByText(/quack/i).hover(); + await expect(page.getByText('Proxy contract')).toBeVisible(); + await expect(page).toHaveScreenshot(); + }); +}); + test.describe('loading', () => { - test('without alias', async({ mount }) => { - const component = await mount( - - - , + test('without alias', async({ render }) => { + const component = await render( + , ); await expect(component).toHaveScreenshot(); }); - test('with alias', async({ mount }) => { - const component = await mount( - - - , + test('with alias', async({ render }) => { + const component = await render( + , ); await expect(component).toHaveScreenshot(); @@ -85,71 +140,71 @@ test.describe('loading', () => { }); -test('with ENS', async({ mount }) => { - const component = await mount( - - - , +test('with ENS', async({ render }) => { + const component = await render( + , ); await expect(component).toHaveScreenshot(); }); -test('external link', async({ mount }) => { - const component = await mount( - - - , +test('with name tag', async({ render }) => { + const component = await render( + , ); await expect(component).toHaveScreenshot(); }); -test('no link', async({ mount }) => { - const component = await mount( - - - , +test('external link', async({ render }) => { + const component = await render( + , ); await expect(component).toHaveScreenshot(); }); -test('customization', async({ mount }) => { - const component = await mount( - - - , +test('no link', async({ render }) => { + const component = await render( + , ); await expect(component).toHaveScreenshot(); }); -test('hover', async({ page, mount }) => { - const component = await mount( - - - - - - - , +test('customization', async({ render }) => { + const component = await render( + , + ); + + await expect(component).toHaveScreenshot(); +}); + +test('hover', async({ page, render }) => { + const component = await render( + + + + + , ); await component.getByText(addressMock.hash.slice(0, 4)).hover(); diff --git a/ui/shared/entities/address/AddressEntity.tsx b/ui/shared/entities/address/AddressEntity.tsx index 1c5889ec86..16ae04d95a 100644 --- a/ui/shared/entities/address/AddressEntity.tsx +++ b/ui/shared/entities/address/AddressEntity.tsx @@ -1,6 +1,5 @@ import type { As } from '@chakra-ui/react'; import { Box, Flex, Skeleton, Tooltip, chakra, VStack } from '@chakra-ui/react'; -import _omit from 'lodash/omit'; import React from 'react'; import type { AddressParam } from 'types/api/addressParams'; @@ -10,7 +9,8 @@ import { route } from 'nextjs-routes'; import { useAddressHighlightContext } from 'lib/contexts/addressHighlight'; import * as EntityBase from 'ui/shared/entities/base/components'; -import { getIconProps } from '../base/utils'; +import { distributeEntityProps, getIconProps } from '../base/utils'; +import AddressEntityContentProxy from './AddressEntityContentProxy'; import AddressIdenticon from './AddressIdenticon'; import { formattedLuksoName, useUniversalProfile, IdenticonUniversalProfile } from './IdenticonUniversalProfileQuery'; if (process.browser) { @@ -32,9 +32,7 @@ const Link = chakra((props: LinkProps) => { ); }); -type IconProps = Pick & { - asProp?: As; -}; +type IconProps = Pick & EntityBase.IconBaseProps; const Icon = (props: IconProps) => { if (props.noIcon) { @@ -42,8 +40,8 @@ const Icon = (props: IconProps) => { } const styles = { - ...getIconProps(props.iconSize), - marginRight: 2, + ...getIconProps(props.size), + marginRight: props.marginRight ?? 2, }; if (props.isLoading) { @@ -60,27 +58,18 @@ const Icon = (props: IconProps) => { ); } - if (props.address.is_verified) { - return ( - - - - - - ); - } + const isProxy = Boolean(props.address.implementations?.length); + const isVerified = isProxy ? props.address.is_verified && props.address.implementations?.every(({ name }) => Boolean(name)) : props.address.is_verified; + const contractIconName: EntityBase.IconBaseProps['name'] = props.address.is_verified ? 'contracts/verified' : 'contracts/regular'; + const label = (isVerified ? 'verified ' : '') + (isProxy ? 'proxy contract' : 'contract'); const ContractIcon = ( - + @@ -91,33 +80,39 @@ const Icon = (props: IconProps) => { } return ( - - - - - + + + ); }; -type ContentProps = Omit & Pick; +export type ContentProps = Omit & Pick; const Content = chakra((props: ContentProps) => { - if (props.address.name || props.address.ens_domain_name) { - const text = props.address.ens_domain_name || props.address.name; + const nameTag = props.address.metadata?.tags.find(tag => tag.tagType === 'name')?.name; + const nameText = nameTag || props.address.ens_domain_name || props.address.name; + + const isProxy = props.address.implementations && props.address.implementations.length > 0; + + if (isProxy) { + return ; + } + + if (nameText) { const label = ( - { text } + { nameText } { props.address.hash } ); return ( - + - { text } + { nameText } ); @@ -147,16 +142,19 @@ const Copy = (props: CopyProps) => { const Container = EntityBase.Container; +interface AddressProp extends Partial { + hash: string; +} + export interface EntityProps extends EntityBase.EntityBaseProps { - address: Pick; + address: AddressProp; isSafeAddress?: boolean; + noHighlight?: boolean; } const AddressEntry = (props: EntityProps) => { - const linkProps = _omit(props, [ 'className' ]); - const partsProps = _omit(props, [ 'className', 'onClick' ]); - - const context = useAddressHighlightContext(); + const partsProps = distributeEntityProps(props); + const context = useAddressHighlightContext(props.noHighlight); return ( { onMouseEnter={ context?.onMouseEnter } onMouseLeave={ context?.onMouseLeave } position="relative" + zIndex={ 0 } > - - - + + + - + ); }; -export default React.memo(chakra(AddressEntry)); +export default React.memo(chakra(AddressEntry)); export { Container, diff --git a/ui/shared/entities/address/AddressEntityContentProxy.tsx b/ui/shared/entities/address/AddressEntityContentProxy.tsx new file mode 100644 index 0000000000..66fbb4146f --- /dev/null +++ b/ui/shared/entities/address/AddressEntityContentProxy.tsx @@ -0,0 +1,76 @@ +import { Box, DarkMode, PopoverBody, PopoverContent, PopoverTrigger, Portal, useColorModeValue, Flex, PopoverArrow } from '@chakra-ui/react'; +import React from 'react'; + +import Popover from 'ui/shared/chakra/Popover'; +import * as EntityBase from 'ui/shared/entities/base/components'; + +import type { ContentProps } from './AddressEntity'; +import AddressEntity from './AddressEntity'; + +const AddressEntityContentProxy = (props: ContentProps) => { + const bgColor = useColorModeValue('gray.700', 'gray.900'); + + const implementations = props.address.implementations; + + const handleClick = React.useCallback((event: React.MouseEvent) => { + event.stopPropagation(); + }, []); + + if (!implementations || implementations.length === 0) { + return null; + } + + const colNum = Math.min(implementations.length, 3); + const nameTag = props.address.metadata?.tags.find(tag => tag.tagType === 'name')?.name; + + const implementationName = implementations.length === 1 && implementations[0].name ? implementations[0].name : undefined; + + return ( + + + + + + + + + + + + + Proxy contract + { props.address.name ? ` (${ props.address.name })` : '' } + + + + Implementation{ implementations.length > 1 ? 's' : '' } + { implementationName ? ` (${ implementationName })` : '' } + + + { implementations.map((item) => ( + + )) } + + + + + + + ); +}; + +export default React.memo(AddressEntityContentProxy); diff --git a/ui/shared/entities/address/AddressIdenticon.tsx b/ui/shared/entities/address/AddressIdenticon.tsx index a08fa5a4a9..3de31738fb 100644 --- a/ui/shared/entities/address/AddressIdenticon.tsx +++ b/ui/shared/entities/address/AddressIdenticon.tsx @@ -54,7 +54,7 @@ const Icon = dynamic( // eslint-disable-next-line react/display-name return (props: IconProps) => { - const svg = GradientAvatar(props.hash, props.size); + const svg = GradientAvatar(props.hash, props.size, 'circle'); return
; }; } diff --git a/ui/shared/entities/address/__screenshots__/AddressEntity.pw.tsx_default_contract-unverified-1.png b/ui/shared/entities/address/__screenshots__/AddressEntity.pw.tsx_default_contract-unverified-1.png index 5af8e38ece..a18893d2ea 100644 Binary files a/ui/shared/entities/address/__screenshots__/AddressEntity.pw.tsx_default_contract-unverified-1.png and b/ui/shared/entities/address/__screenshots__/AddressEntity.pw.tsx_default_contract-unverified-1.png differ diff --git a/ui/shared/entities/address/__screenshots__/AddressEntity.pw.tsx_default_external-link-1.png b/ui/shared/entities/address/__screenshots__/AddressEntity.pw.tsx_default_external-link-1.png index 5d824ab018..654d190ae1 100644 Binary files a/ui/shared/entities/address/__screenshots__/AddressEntity.pw.tsx_default_external-link-1.png and b/ui/shared/entities/address/__screenshots__/AddressEntity.pw.tsx_default_external-link-1.png differ diff --git a/ui/shared/entities/address/__screenshots__/AddressEntity.pw.tsx_default_proxy-contract-with-implementation-name-1.png b/ui/shared/entities/address/__screenshots__/AddressEntity.pw.tsx_default_proxy-contract-with-implementation-name-1.png new file mode 100644 index 0000000000..e5eee1c270 Binary files /dev/null and b/ui/shared/entities/address/__screenshots__/AddressEntity.pw.tsx_default_proxy-contract-with-implementation-name-1.png differ diff --git a/ui/shared/entities/address/__screenshots__/AddressEntity.pw.tsx_default_proxy-contract-with-multiple-implementations-1.png b/ui/shared/entities/address/__screenshots__/AddressEntity.pw.tsx_default_proxy-contract-with-multiple-implementations-1.png new file mode 100644 index 0000000000..0b4b672cf7 Binary files /dev/null and b/ui/shared/entities/address/__screenshots__/AddressEntity.pw.tsx_default_proxy-contract-with-multiple-implementations-1.png differ diff --git a/ui/shared/entities/address/__screenshots__/AddressEntity.pw.tsx_default_proxy-contract-with-name-tag-1.png b/ui/shared/entities/address/__screenshots__/AddressEntity.pw.tsx_default_proxy-contract-with-name-tag-1.png new file mode 100644 index 0000000000..6ce9f48437 Binary files /dev/null and b/ui/shared/entities/address/__screenshots__/AddressEntity.pw.tsx_default_proxy-contract-with-name-tag-1.png differ diff --git a/ui/shared/entities/address/__screenshots__/AddressEntity.pw.tsx_default_proxy-contract-without-any-name-1.png b/ui/shared/entities/address/__screenshots__/AddressEntity.pw.tsx_default_proxy-contract-without-any-name-1.png new file mode 100644 index 0000000000..e4b4af7bbd Binary files /dev/null and b/ui/shared/entities/address/__screenshots__/AddressEntity.pw.tsx_default_proxy-contract-without-any-name-1.png differ diff --git a/ui/shared/entities/address/__screenshots__/AddressEntity.pw.tsx_default_proxy-contract-without-implementation-name-1.png b/ui/shared/entities/address/__screenshots__/AddressEntity.pw.tsx_default_proxy-contract-without-implementation-name-1.png new file mode 100644 index 0000000000..cb310563fe Binary files /dev/null and b/ui/shared/entities/address/__screenshots__/AddressEntity.pw.tsx_default_proxy-contract-without-implementation-name-1.png differ diff --git a/ui/shared/entities/address/__screenshots__/AddressEntity.pw.tsx_default_with-name-tag-1.png b/ui/shared/entities/address/__screenshots__/AddressEntity.pw.tsx_default_with-name-tag-1.png new file mode 100644 index 0000000000..b3b745e458 Binary files /dev/null and b/ui/shared/entities/address/__screenshots__/AddressEntity.pw.tsx_default_with-name-tag-1.png differ diff --git a/ui/shared/entities/base/components.tsx b/ui/shared/entities/base/components.tsx index d10907b5f6..b754365c28 100644 --- a/ui/shared/entities/base/components.tsx +++ b/ui/shared/entities/base/components.tsx @@ -8,8 +8,8 @@ import HashStringShorten from 'ui/shared/HashStringShorten'; import HashStringShortenDynamic from 'ui/shared/HashStringShortenDynamic'; import type { IconName } from 'ui/shared/IconSvg'; import IconSvg from 'ui/shared/IconSvg'; -import LinkExternal from 'ui/shared/LinkExternal'; -import LinkInternal from 'ui/shared/LinkInternal'; +import LinkExternal from 'ui/shared/links/LinkExternal'; +import LinkInternal from 'ui/shared/links/LinkInternal'; import { getIconProps, type IconSize } from './utils'; @@ -18,8 +18,7 @@ export type Truncation = 'constant' | 'constant_long' | 'dynamic' | 'tail' | 'no export interface EntityBaseProps { className?: string; href?: string; - iconSize?: IconSize; - iconColor?: IconProps['color']; + icon?: EntityIconProps; isExternal?: boolean; isLoading?: boolean; noCopy?: boolean; @@ -80,29 +79,31 @@ const Link = chakra(({ isLoading, children, isExternal, onClick, href, noLink }: ); }); -export interface IconBaseProps extends Pick { - name: IconName; - color?: IconProps['color']; - borderRadius?: IconProps['borderRadius']; +interface EntityIconProps extends Pick { + name?: IconName; + size?: IconSize; } -const Icon = ({ isLoading, iconSize, noIcon, name, iconColor, color, borderRadius }: IconBaseProps) => { +export interface IconBaseProps extends Pick, EntityIconProps { +} + +const Icon = ({ isLoading, noIcon, size, name, color, borderRadius, marginRight, boxSize }: IconBaseProps) => { const defaultColor = useColorModeValue('gray.500', 'gray.400'); - if (noIcon) { + if (noIcon || !name) { return null; } - const styles = getIconProps(iconSize); + const styles = getIconProps(size); return ( @@ -112,9 +113,10 @@ const Icon = ({ isLoading, iconSize, noIcon, name, iconColor, color, borderRadiu export interface ContentBaseProps extends Pick { asProp?: As; text: string; + isTooltipDisabled?: boolean; } -const Content = chakra(({ className, isLoading, asProp, text, truncation = 'dynamic', tailLength }: ContentBaseProps) => { +const Content = chakra(({ className, isLoading, asProp, text, truncation = 'dynamic', tailLength, isTooltipDisabled }: ContentBaseProps) => { const children = (() => { switch (truncation) { @@ -124,6 +126,7 @@ const Content = chakra(({ className, isLoading, asProp, text, truncation = 'dyna hash={ text } as={ asProp } type="long" + isTooltipDisabled={ isTooltipDisabled } /> ); case 'constant': @@ -131,6 +134,7 @@ const Content = chakra(({ className, isLoading, asProp, text, truncation = 'dyna ); case 'dynamic': @@ -141,8 +145,10 @@ const Content = chakra(({ className, isLoading, asProp, text, truncation = 'dyna hash={ text } as={ asProp } tailLength={ tailLength } + isTooltipDisabled={ isTooltipDisabled } /> ); + case 'tail': case 'none': return { text }; } @@ -154,6 +160,7 @@ const Content = chakra(({ className, isLoading, asProp, text, truncation = 'dyna isLoaded={ !isLoading } overflow="hidden" whiteSpace="nowrap" + textOverflow={ truncation === 'tail' ? 'ellipsis' : undefined } > { children } diff --git a/ui/shared/entities/base/utils.ts b/ui/shared/entities/base/utils.ts index 6763b9dd56..bba8cc3e57 100644 --- a/ui/shared/entities/base/utils.ts +++ b/ui/shared/entities/base/utils.ts @@ -1,3 +1,5 @@ +import type { EntityBaseProps } from './components'; + export type IconSize = 'md' | 'lg'; export function getIconProps(size: IconSize = 'md') { @@ -14,3 +16,16 @@ export function getIconProps(size: IconSize = 'md') { } } } + +export function distributeEntityProps(props: Props) { + const { className, onClick, icon, ...restProps } = props; + + return { + container: { className }, + icon: { ...restProps, ...icon }, + link: { ...restProps, onClick }, + content: restProps, + symbol: restProps, + copy: restProps, + }; +} diff --git a/ui/shared/entities/blob/BlobEntity.tsx b/ui/shared/entities/blob/BlobEntity.tsx index 13953eb86f..7ca61da9e9 100644 --- a/ui/shared/entities/blob/BlobEntity.tsx +++ b/ui/shared/entities/blob/BlobEntity.tsx @@ -1,11 +1,13 @@ +import type { As } from '@chakra-ui/react'; import { chakra } from '@chakra-ui/react'; -import _omit from 'lodash/omit'; import React from 'react'; import { route } from 'nextjs-routes'; import * as EntityBase from 'ui/shared/entities/base/components'; +import { distributeEntityProps } from '../base/utils'; + type LinkProps = EntityBase.LinkBaseProps & Pick; const Link = chakra((props: LinkProps) => { @@ -21,11 +23,7 @@ const Link = chakra((props: LinkProps) => { ); }); -type IconProps = Omit & { - name?: EntityBase.IconBaseProps['name']; -}; - -const Icon = (props: IconProps) => { +const Icon = (props: EntityBase.IconBaseProps) => { return ( { - const linkProps = _omit(props, [ 'className' ]); - const partsProps = _omit(props, [ 'className', 'onClick' ]); + const partsProps = distributeEntityProps(props); return ( - - - - + + + + - + ); }; -export default React.memo(chakra(BlobEntity)); +export default React.memo(chakra(BlobEntity)); export { Container, diff --git a/ui/shared/entities/blob/BlobEntityL1.tsx b/ui/shared/entities/blob/BlobEntityL1.tsx new file mode 100644 index 0000000000..3282feeb4d --- /dev/null +++ b/ui/shared/entities/blob/BlobEntityL1.tsx @@ -0,0 +1,27 @@ +import { chakra } from '@chakra-ui/react'; +import React from 'react'; + +import { route } from 'nextjs-routes'; + +import config from 'configs/app'; + +import * as BlobEntity from './BlobEntity'; + +const rollupFeature = config.features.rollup; + +const BlobEntityL1 = (props: BlobEntity.EntityProps) => { + if (!rollupFeature.isEnabled) { + return null; + } + + const defaultHref = rollupFeature.L1BaseUrl + route({ + pathname: '/blobs/[hash]', + query: { hash: props.hash }, + }); + + return ( + + ); +}; + +export default chakra(BlobEntityL1); diff --git a/ui/shared/entities/block/BatchEntityL2.tsx b/ui/shared/entities/block/BatchEntityL2.tsx index a0572b3b7d..dd4032b731 100644 --- a/ui/shared/entities/block/BatchEntityL2.tsx +++ b/ui/shared/entities/block/BatchEntityL2.tsx @@ -1,5 +1,4 @@ import { chakra } from '@chakra-ui/react'; -import _omit from 'lodash/omit'; import React from 'react'; import { route } from 'nextjs-routes'; @@ -11,23 +10,18 @@ import * as BlockEntity from './BlockEntity'; const rollupFeature = config.features.rollup; const BatchEntityL2 = (props: BlockEntity.EntityProps) => { - const linkProps = _omit(props, [ 'className' ]); - const partsProps = _omit(props, [ 'className', 'onClick' ]); - if (!rollupFeature.isEnabled) { return null; } + const defaultHref = route({ pathname: '/batches/[number]', query: { number: props.number.toString() } }); + return ( - - - - - - + ); }; diff --git a/ui/shared/entities/block/BlockEntity.pw.tsx b/ui/shared/entities/block/BlockEntity.pw.tsx index 0098f03b05..138f42f87d 100644 --- a/ui/shared/entities/block/BlockEntity.pw.tsx +++ b/ui/shared/entities/block/BlockEntity.pw.tsx @@ -1,24 +1,21 @@ -import { test, expect } from '@playwright/experimental-ct-react'; import React from 'react'; -import TestApp from 'playwright/TestApp'; +import { test, expect } from 'playwright/lib'; import BlockEntity from './BlockEntity'; -const iconSizes = [ 'md', 'lg' ]; +const iconSizes = [ 'md', 'lg' ] as const; test.use({ viewport: { width: 180, height: 30 } }); test.describe('icon sizes', () => { iconSizes.forEach((size) => { - test(size, async({ mount }) => { - const component = await mount( - - - , + test(size, async({ render }) => { + const component = await render( + , ); await expect(component).toHaveScreenshot(); @@ -26,27 +23,23 @@ test.describe('icon sizes', () => { }); }); -test('loading', async({ mount }) => { - const component = await mount( - - - , +test('loading', async({ render }) => { + const component = await render( + , ); await expect(component).toHaveScreenshot(); }); -test('external link +@dark-mode', async({ mount }) => { - const component = await mount( - - - , +test('external link +@dark-mode', async({ render }) => { + const component = await render( + , ); await component.getByText('17943507').hover(); @@ -54,28 +47,24 @@ test('external link +@dark-mode', async({ mount }) => { await expect(component).toHaveScreenshot(); }); -test('long number', async({ mount }) => { - const component = await mount( - - - , +test('long number', async({ render }) => { + const component = await render( + , ); await expect(component).toHaveScreenshot(); }); -test('customization', async({ mount }) => { - const component = await mount( - - - , +test('customization', async({ render }) => { + const component = await render( + , ); await expect(component).toHaveScreenshot(); diff --git a/ui/shared/entities/block/BlockEntity.tsx b/ui/shared/entities/block/BlockEntity.tsx index 35ca18b72f..6ef37b8290 100644 --- a/ui/shared/entities/block/BlockEntity.tsx +++ b/ui/shared/entities/block/BlockEntity.tsx @@ -1,12 +1,14 @@ +import type { As } from '@chakra-ui/react'; import { chakra } from '@chakra-ui/react'; -import _omit from 'lodash/omit'; import React from 'react'; import { route } from 'nextjs-routes'; import * as EntityBase from 'ui/shared/entities/base/components'; -type LinkProps = EntityBase.LinkBaseProps & Pick; +import { distributeEntityProps } from '../base/utils'; + +type LinkProps = EntityBase.LinkBaseProps & Partial>; const Link = chakra((props: LinkProps) => { const heightOrHash = props.hash ?? String(props.number); @@ -22,11 +24,7 @@ const Link = chakra((props: LinkProps) => { ); }); -type IconProps = Omit & { - name?: EntityBase.IconBaseProps['name']; -}; - -const Icon = (props: IconProps) => { +const Icon = (props: EntityBase.IconBaseProps) => { return ( { const Container = EntityBase.Container; export interface EntityProps extends EntityBase.EntityBaseProps { - number: number; + number: number | string; hash?: string; } const BlockEntity = (props: EntityProps) => { - const linkProps = _omit(props, [ 'className' ]); - const partsProps = _omit(props, [ 'className', 'onClick' ]); + const partsProps = distributeEntityProps(props); return ( - - - - + + + + ); }; -export default React.memo(chakra(BlockEntity)); +export default React.memo(chakra(BlockEntity)); export { Container, diff --git a/ui/shared/entities/block/BlockEntityL1.tsx b/ui/shared/entities/block/BlockEntityL1.tsx index a364685b29..93f4b6a301 100644 --- a/ui/shared/entities/block/BlockEntityL1.tsx +++ b/ui/shared/entities/block/BlockEntityL1.tsx @@ -1,5 +1,4 @@ import { chakra } from '@chakra-ui/react'; -import _omit from 'lodash/omit'; import React from 'react'; import { route } from 'nextjs-routes'; @@ -11,25 +10,16 @@ import * as BlockEntity from './BlockEntity'; const rollupFeature = config.features.rollup; const BlockEntityL1 = (props: BlockEntity.EntityProps) => { - const linkProps = _omit(props, [ 'className' ]); - const partsProps = _omit(props, [ 'className', 'onClick' ]); - if (!rollupFeature.isEnabled) { return null; } - return ( - - - - - - - ); + const defaultHref = rollupFeature.L1BaseUrl + route({ + pathname: '/block/[height_or_hash]', + query: { height_or_hash: props.hash ?? String(props.number) }, + }); + + return ; }; export default chakra(BlockEntityL1); diff --git a/ui/shared/entities/block/BlockEntityL2.tsx b/ui/shared/entities/block/BlockEntityL2.tsx index 1a0c3e06a8..c078495d0e 100644 --- a/ui/shared/entities/block/BlockEntityL2.tsx +++ b/ui/shared/entities/block/BlockEntityL2.tsx @@ -1,5 +1,4 @@ import { chakra } from '@chakra-ui/react'; -import _omit from 'lodash/omit'; import React from 'react'; import config from 'configs/app'; @@ -9,21 +8,11 @@ import * as BlockEntity from './BlockEntity'; const rollupFeature = config.features.rollup; const BlockEntityL2 = (props: BlockEntity.EntityProps) => { - const linkProps = _omit(props, [ 'className' ]); - const partsProps = _omit(props, [ 'className', 'onClick' ]); - if (!rollupFeature.isEnabled) { return null; } - return ( - - - - - - - ); + return ; }; export default chakra(BlockEntityL2); diff --git a/ui/shared/entities/block/__screenshots__/BlockEntity.pw.tsx_dark-color-mode_external-link-dark-mode-1.png b/ui/shared/entities/block/__screenshots__/BlockEntity.pw.tsx_dark-color-mode_external-link-dark-mode-1.png index af7a2cbe1a..e3c51c495e 100644 Binary files a/ui/shared/entities/block/__screenshots__/BlockEntity.pw.tsx_dark-color-mode_external-link-dark-mode-1.png and b/ui/shared/entities/block/__screenshots__/BlockEntity.pw.tsx_dark-color-mode_external-link-dark-mode-1.png differ diff --git a/ui/shared/entities/ens/EnsEntity.pw.tsx b/ui/shared/entities/ens/EnsEntity.pw.tsx index fc6d5c01c7..42c445fe22 100644 --- a/ui/shared/entities/ens/EnsEntity.pw.tsx +++ b/ui/shared/entities/ens/EnsEntity.pw.tsx @@ -1,25 +1,23 @@ -import { test, expect } from '@playwright/experimental-ct-react'; import React from 'react'; -import TestApp from 'playwright/TestApp'; +import * as domainMock from 'mocks/ens/domain'; +import { test, expect } from 'playwright/lib'; import EnsEntity from './EnsEntity'; const name = 'cat.eth'; -const iconSizes = [ 'md', 'lg' ]; +const iconSizes = [ 'md', 'lg' ] as const; test.use({ viewport: { width: 180, height: 30 } }); test.describe('icon size', () => { iconSizes.forEach((size) => { - test(size, async({ mount }) => { - const component = await mount( - - - , + test(size, async({ render }) => { + const component = await render( + , ); await expect(component).toHaveScreenshot(); @@ -27,26 +25,22 @@ test.describe('icon size', () => { }); }); -test('loading', async({ mount }) => { - const component = await mount( - - - , +test('loading', async({ render }) => { + const component = await render( + , ); await expect(component).toHaveScreenshot(); }); -test('with long name', async({ mount }) => { - const component = await mount( - - - , +test('with long name', async({ render }) => { + const component = await render( + , ); await component.getByText(name.slice(0, 4)).hover(); @@ -54,17 +48,34 @@ test('with long name', async({ mount }) => { await expect(component).toHaveScreenshot(); }); -test('customization', async({ mount }) => { - const component = await mount( - - - , +test('customization', async({ render }) => { + const component = await render( + , ); await expect(component).toHaveScreenshot(); }); + +test.describe('', () => { + test.use({ viewport: { width: 300, height: 400 } }); + test('with protocol info', async({ render, page, mockAssetResponse }) => { + await mockAssetResponse(domainMock.ensDomainA.protocol?.icon_url as string, './playwright/mocks/image_s.jpg'); + + const component = await render( + , + ); + + await component.getByAltText(`${ domainMock.protocolA.title } protocol icon`).first().hover(); + + await expect(page.getByText(domainMock.protocolA.description)).toBeVisible(); + await expect(page).toHaveScreenshot(); + }); +}); diff --git a/ui/shared/entities/ens/EnsEntity.tsx b/ui/shared/entities/ens/EnsEntity.tsx index c0623426c1..8371f295d5 100644 --- a/ui/shared/entities/ens/EnsEntity.tsx +++ b/ui/shared/entities/ens/EnsEntity.tsx @@ -1,16 +1,23 @@ -import { chakra } from '@chakra-ui/react'; -import _omit from 'lodash/omit'; +import type { As } from '@chakra-ui/react'; +import { Box, chakra, Flex, Image, PopoverBody, PopoverContent, PopoverTrigger, Portal, Skeleton, Text } from '@chakra-ui/react'; import React from 'react'; +import type * as bens from '@blockscout/bens-types'; + import { route } from 'nextjs-routes'; +import Popover from 'ui/shared/chakra/Popover'; import * as EntityBase from 'ui/shared/entities/base/components'; +import IconSvg from 'ui/shared/IconSvg'; +import LinkExternal from 'ui/shared/links/LinkExternal'; import TruncatedValue from 'ui/shared/TruncatedValue'; -type LinkProps = EntityBase.LinkBaseProps & Pick; +import { distributeEntityProps, getIconProps } from '../base/utils'; + +type LinkProps = EntityBase.LinkBaseProps & Pick; const Link = chakra((props: LinkProps) => { - const defaultHref = route({ pathname: '/name-domains/[name]', query: { name: props.name } }); + const defaultHref = route({ pathname: '/name-domains/[name]', query: { name: props.domain } }); return ( { ); }); -type IconProps = Omit & { - iconName?: EntityBase.IconBaseProps['name']; -}; +type IconProps = Pick & EntityBase.IconBaseProps; const Icon = (props: IconProps) => { - return ( - - ); + const icon = ; + + if (props.protocol) { + const styles = getIconProps(props.size); + + if (props.isLoading) { + return ; + } + + return ( + + + + { + + + + + + + { +
+ { props.protocol.short_name } + { props.protocol.tld_list.map((tld) => `.${ tld }`).join((' ')) } +
+
+ { props.protocol.description } + { props.protocol.docs_url && ( + + + Documentation + + ) } +
+
+
+
+ ); + } + + return icon; }; -type ContentProps = Omit & Pick; +type ContentProps = Omit & Pick; const Content = chakra((props: ContentProps) => { return ( ); }); -type CopyProps = Omit & Pick; +type CopyProps = Omit & Pick; const Copy = (props: CopyProps) => { return ( ); }; @@ -60,25 +121,25 @@ const Copy = (props: CopyProps) => { const Container = EntityBase.Container; export interface EntityProps extends EntityBase.EntityBaseProps { - name: string; + domain: string; + protocol?: bens.ProtocolInfo | null; } const EnsEntity = (props: EntityProps) => { - const linkProps = _omit(props, [ 'className' ]); - const partsProps = _omit(props, [ 'className', 'onClick' ]); + const partsProps = distributeEntityProps(props); return ( - - - - + + + + - + ); }; -export default React.memo(chakra(EnsEntity)); +export default React.memo(chakra(EnsEntity)); export { Container, diff --git a/ui/shared/entities/ens/__screenshots__/EnsEntity.pw.tsx_default_with-protocol-info-1.png b/ui/shared/entities/ens/__screenshots__/EnsEntity.pw.tsx_default_with-protocol-info-1.png new file mode 100644 index 0000000000..6cd4eaab03 Binary files /dev/null and b/ui/shared/entities/ens/__screenshots__/EnsEntity.pw.tsx_default_with-protocol-info-1.png differ diff --git a/ui/shared/entities/nft/NftEntity.pw.tsx b/ui/shared/entities/nft/NftEntity.pw.tsx index c426b8b8fa..9e055129d1 100644 --- a/ui/shared/entities/nft/NftEntity.pw.tsx +++ b/ui/shared/entities/nft/NftEntity.pw.tsx @@ -1,26 +1,23 @@ -import { test, expect } from '@playwright/experimental-ct-react'; import React from 'react'; -import TestApp from 'playwright/TestApp'; +import { test, expect } from 'playwright/lib'; import NftEntity from './NftEntity'; -const iconSizes = [ 'md', 'lg' ]; +const iconSizes = [ 'md', 'lg' ] as const; const hash = '0xd789a607CEac2f0E14867de4EB15b15C9FFB5859'; test.use({ viewport: { width: 180, height: 30 } }); test.describe('icon sizes', () => { iconSizes.forEach((size) => { - test(size, async({ mount }) => { - const component = await mount( - - - , + test(size, async({ render }) => { + const component = await render( + , ); await expect(component).toHaveScreenshot(); @@ -28,44 +25,38 @@ test.describe('icon sizes', () => { }); }); -test('loading', async({ mount }) => { - const component = await mount( - - - , +test('loading', async({ render }) => { + const component = await render( + , ); await expect(component).toHaveScreenshot(); }); -test('long id', async({ mount }) => { - const component = await mount( - - - , +test('long id', async({ render }) => { + const component = await render( + , ); await expect(component).toHaveScreenshot(); }); -test('customization', async({ mount }) => { - const component = await mount( - - - , +test('customization', async({ render }) => { + const component = await render( + , ); await expect(component).toHaveScreenshot(); diff --git a/ui/shared/entities/nft/NftEntity.tsx b/ui/shared/entities/nft/NftEntity.tsx index a2605e7b94..420353bfa2 100644 --- a/ui/shared/entities/nft/NftEntity.tsx +++ b/ui/shared/entities/nft/NftEntity.tsx @@ -1,5 +1,5 @@ +import type { As } from '@chakra-ui/react'; import { chakra } from '@chakra-ui/react'; -import _omit from 'lodash/omit'; import React from 'react'; import { route } from 'nextjs-routes'; @@ -7,13 +7,11 @@ import { route } from 'nextjs-routes'; import * as EntityBase from 'ui/shared/entities/base/components'; import TruncatedValue from 'ui/shared/TruncatedValue'; -const Container = EntityBase.Container; +import { distributeEntityProps } from '../base/utils'; -type IconProps = Pick & { - name?: EntityBase.IconBaseProps['name']; -}; +const Container = EntityBase.Container; -const Icon = (props: IconProps) => { +const Icon = (props: EntityBase.IconBaseProps) => { if (props.noIcon) { return null; } @@ -21,7 +19,7 @@ const Icon = (props: IconProps) => { return ( ); @@ -59,20 +57,19 @@ export interface EntityProps extends EntityBase.EntityBaseProps { } const NftEntity = (props: EntityProps) => { - const linkProps = _omit(props, [ 'className' ]); - const partsProps = _omit(props, [ 'className', 'onClick' ]); + const partsProps = distributeEntityProps(props); return ( - - - - + + + + ); }; -export default React.memo(chakra(NftEntity)); +export default React.memo(chakra(NftEntity)); export { Container, diff --git a/ui/shared/entities/token/TokenEntity.pw.tsx b/ui/shared/entities/token/TokenEntity.pw.tsx index 670148d9d2..680eb917a1 100644 --- a/ui/shared/entities/token/TokenEntity.pw.tsx +++ b/ui/shared/entities/token/TokenEntity.pw.tsx @@ -1,26 +1,23 @@ import { Box } from '@chakra-ui/react'; -import { test, expect } from '@playwright/experimental-ct-react'; import React from 'react'; import * as tokenMock from 'mocks/tokens/tokenInfo'; -import TestApp from 'playwright/TestApp'; +import { test, expect } from 'playwright/lib'; import TokenEntity from './TokenEntity'; -const iconSizes = [ 'md', 'lg' ]; +const iconSizes = [ 'md', 'lg' ] as const; test.use({ viewport: { width: 300, height: 100 } }); test.describe('icon size', () => { iconSizes.forEach((size) => { - test(size, async({ mount }) => { - const component = await mount( - - - , + test(size, async({ render }) => { + const component = await render( + , ); await expect(component).toHaveScreenshot(); @@ -28,7 +25,7 @@ test.describe('icon size', () => { }); }); -test('with logo, long name and symbol', async({ page, mount }) => { +test('with logo, long name and symbol', async({ page, render }) => { const LOGO_URL = 'https://example.com/logo.png'; await page.route(LOGO_URL, (route) => { return route.fulfill({ @@ -37,17 +34,16 @@ test('with logo, long name and symbol', async({ page, mount }) => { }); }); - await mount( - - - , + await render( + , ); await page.getByText(/this/i).hover(); @@ -57,35 +53,31 @@ test('with logo, long name and symbol', async({ page, mount }) => { await expect(page).toHaveScreenshot(); }); -test('loading', async({ mount }) => { - const component = await mount( - - - , +test('loading', async({ render }) => { + const component = await render( + , ); await expect(component).toHaveScreenshot(); }); -test('customization', async({ mount }) => { - const component = await mount( - - { + const component = await render( + + - - - , + borderColor="blue.700" + /> + , ); await expect(component).toHaveScreenshot(); diff --git a/ui/shared/entities/token/TokenEntity.tsx b/ui/shared/entities/token/TokenEntity.tsx index d7cd9f7500..491f43569e 100644 --- a/ui/shared/entities/token/TokenEntity.tsx +++ b/ui/shared/entities/token/TokenEntity.tsx @@ -1,6 +1,5 @@ -import type { ChakraProps } from '@chakra-ui/react'; +import type { As } from '@chakra-ui/react'; import { Image, Skeleton, chakra } from '@chakra-ui/react'; -import _omit from 'lodash/omit'; import React from 'react'; import type { TokenInfo } from 'types/api/token'; @@ -11,7 +10,7 @@ import * as EntityBase from 'ui/shared/entities/base/components'; import TokenLogoPlaceholder from 'ui/shared/TokenLogoPlaceholder'; import TruncatedTextTooltip from 'ui/shared/TruncatedTextTooltip'; -import { getIconProps } from '../base/utils'; +import { distributeEntityProps, getIconProps } from '../base/utils'; type LinkProps = EntityBase.LinkBaseProps & Pick; @@ -28,10 +27,7 @@ const Link = chakra((props: LinkProps) => { ); }); -type IconProps = Pick & { - marginRight?: ChakraProps['marginRight']; - boxSize?: ChakraProps['boxSize']; -}; +type IconProps = Pick & EntityBase.IconBaseProps; const Icon = (props: IconProps) => { if (props.noIcon) { @@ -40,7 +36,7 @@ const Icon = (props: IconProps) => { const styles = { marginRight: props.marginRight ?? 2, - boxSize: props.boxSize ?? getIconProps(props.iconSize).boxSize, + boxSize: props.boxSize ?? getIconProps(props.size).boxSize, borderRadius: 'base', flexShrink: 0, }; @@ -143,22 +139,21 @@ export interface EntityProps extends EntityBase.EntityBaseProps { } const TokenEntity = (props: EntityProps) => { - const linkProps = _omit(props, [ 'className' ]); - const partsProps = _omit(props, [ 'className', 'onClick' ]); + const partsProps = distributeEntityProps(props); return ( - - - - + + + + - - + + ); }; -export default React.memo(chakra(TokenEntity)); +export default React.memo(chakra(TokenEntity)); export { Container, diff --git a/ui/shared/entities/token/__screenshots__/TokenEntity.pw.tsx_default_icon-size-lg-1.png b/ui/shared/entities/token/__screenshots__/TokenEntity.pw.tsx_default_icon-size-lg-1.png index 6fd082b290..19f534970c 100644 Binary files a/ui/shared/entities/token/__screenshots__/TokenEntity.pw.tsx_default_icon-size-lg-1.png and b/ui/shared/entities/token/__screenshots__/TokenEntity.pw.tsx_default_icon-size-lg-1.png differ diff --git a/ui/shared/entities/token/__screenshots__/TokenEntity.pw.tsx_default_icon-size-md-1.png b/ui/shared/entities/token/__screenshots__/TokenEntity.pw.tsx_default_icon-size-md-1.png index e6b102f066..85500c1d02 100644 Binary files a/ui/shared/entities/token/__screenshots__/TokenEntity.pw.tsx_default_icon-size-md-1.png and b/ui/shared/entities/token/__screenshots__/TokenEntity.pw.tsx_default_icon-size-md-1.png differ diff --git a/ui/shared/entities/token/__screenshots__/TokenEntity.pw.tsx_default_with-logo-long-name-and-symbol-1.png b/ui/shared/entities/token/__screenshots__/TokenEntity.pw.tsx_default_with-logo-long-name-and-symbol-1.png index 038508436e..a92738d281 100644 Binary files a/ui/shared/entities/token/__screenshots__/TokenEntity.pw.tsx_default_with-logo-long-name-and-symbol-1.png and b/ui/shared/entities/token/__screenshots__/TokenEntity.pw.tsx_default_with-logo-long-name-and-symbol-1.png differ diff --git a/ui/shared/entities/token/__screenshots__/TokenEntity.pw.tsx_default_with-logo-long-name-and-symbol-2.png b/ui/shared/entities/token/__screenshots__/TokenEntity.pw.tsx_default_with-logo-long-name-and-symbol-2.png index d311239584..2c9714ddc1 100644 Binary files a/ui/shared/entities/token/__screenshots__/TokenEntity.pw.tsx_default_with-logo-long-name-and-symbol-2.png and b/ui/shared/entities/token/__screenshots__/TokenEntity.pw.tsx_default_with-logo-long-name-and-symbol-2.png differ diff --git a/ui/shared/entities/tx/TxEntity.pw.tsx b/ui/shared/entities/tx/TxEntity.pw.tsx index bd971ca18a..2dc5a9a9b6 100644 --- a/ui/shared/entities/tx/TxEntity.pw.tsx +++ b/ui/shared/entities/tx/TxEntity.pw.tsx @@ -1,25 +1,22 @@ -import { test, expect } from '@playwright/experimental-ct-react'; import React from 'react'; -import TestApp from 'playwright/TestApp'; +import { test, expect } from 'playwright/lib'; import TxEntity from './TxEntity'; const hash = '0x376db52955d5bce114d0ccea2dcf22289b4eae1b86bcae5a59bb5fdbfef48899'; -const iconSizes = [ 'md', 'lg' ]; +const iconSizes = [ 'md', 'lg' ] as const; test.use({ viewport: { width: 180, height: 30 } }); test.describe('icon size', () => { iconSizes.forEach((size) => { - test(size, async({ mount }) => { - const component = await mount( - - - , + test(size, async({ render }) => { + const component = await render( + , ); await expect(component).toHaveScreenshot(); @@ -27,40 +24,34 @@ test.describe('icon size', () => { }); }); -test('loading', async({ mount }) => { - const component = await mount( - - - , +test('loading', async({ render }) => { + const component = await render( + , ); await expect(component).toHaveScreenshot(); }); -test('external link', async({ mount }) => { - const component = await mount( - - - , +test('external link', async({ render }) => { + const component = await render( + , ); await expect(component).toHaveScreenshot(); }); -test('with copy +@dark-mode', async({ mount }) => { - const component = await mount( - - - , +test('with copy +@dark-mode', async({ render }) => { + const component = await render( + , ); await component.getByText(hash.slice(0, 4)).hover(); @@ -68,17 +59,15 @@ test('with copy +@dark-mode', async({ mount }) => { await expect(component).toHaveScreenshot(); }); -test('customization', async({ mount }) => { - const component = await mount( - - - , +test('customization', async({ render }) => { + const component = await render( + , ); await expect(component).toHaveScreenshot(); diff --git a/ui/shared/entities/tx/TxEntity.tsx b/ui/shared/entities/tx/TxEntity.tsx index 796d305ded..8cb00de261 100644 --- a/ui/shared/entities/tx/TxEntity.tsx +++ b/ui/shared/entities/tx/TxEntity.tsx @@ -1,11 +1,13 @@ +import type { As } from '@chakra-ui/react'; import { chakra } from '@chakra-ui/react'; -import _omit from 'lodash/omit'; import React from 'react'; import { route } from 'nextjs-routes'; import * as EntityBase from 'ui/shared/entities/base/components'; +import { distributeEntityProps } from '../base/utils'; + type LinkProps = EntityBase.LinkBaseProps & Pick; const Link = chakra((props: LinkProps) => { @@ -21,11 +23,7 @@ const Link = chakra((props: LinkProps) => { ); }); -type IconProps = Omit & { - name?: EntityBase.IconBaseProps['name']; -}; - -const Icon = (props: IconProps) => { +const Icon = (props: EntityBase.IconBaseProps) => { return ( { - const linkProps = _omit(props, [ 'className' ]); - const partsProps = _omit(props, [ 'className', 'onClick' ]); + const partsProps = distributeEntityProps(props); return ( - - - - + + + + - + ); }; -export default React.memo(chakra(TxEntity)); +export default React.memo(chakra(TxEntity)); export { Container, diff --git a/ui/shared/entities/tx/TxEntityL1.tsx b/ui/shared/entities/tx/TxEntityL1.tsx index 635116dd0a..cf8baf4dfb 100644 --- a/ui/shared/entities/tx/TxEntityL1.tsx +++ b/ui/shared/entities/tx/TxEntityL1.tsx @@ -1,5 +1,4 @@ import { chakra } from '@chakra-ui/react'; -import _omit from 'lodash/omit'; import React from 'react'; import { route } from 'nextjs-routes'; @@ -11,26 +10,16 @@ import * as TxEntity from './TxEntity'; const rollupFeature = config.features.rollup; const TxEntityL1 = (props: TxEntity.EntityProps) => { - const partsProps = _omit(props, [ 'className', 'onClick' ]); - const linkProps = _omit(props, [ 'className' ]); - if (!rollupFeature.isEnabled) { return null; } - return ( - - - - - - - - ); + const defaultHref = rollupFeature.L1BaseUrl + route({ + pathname: '/tx/[hash]', + query: { hash: props.hash }, + }); + + return ; }; export default chakra(TxEntityL1); diff --git a/ui/shared/entities/tx/__screenshots__/TxEntity.pw.tsx_default_external-link-1.png b/ui/shared/entities/tx/__screenshots__/TxEntity.pw.tsx_default_external-link-1.png index be6e12356d..372809e264 100644 Binary files a/ui/shared/entities/tx/__screenshots__/TxEntity.pw.tsx_default_external-link-1.png and b/ui/shared/entities/tx/__screenshots__/TxEntity.pw.tsx_default_external-link-1.png differ diff --git a/ui/shared/entities/userOp/UserOpEntity.pw.tsx b/ui/shared/entities/userOp/UserOpEntity.pw.tsx index c4a8faa3f0..d08ce6a3ba 100644 --- a/ui/shared/entities/userOp/UserOpEntity.pw.tsx +++ b/ui/shared/entities/userOp/UserOpEntity.pw.tsx @@ -1,25 +1,22 @@ -import { test, expect } from '@playwright/experimental-ct-react'; import React from 'react'; -import TestApp from 'playwright/TestApp'; +import { test, expect } from 'playwright/lib'; import UserOpEntity from './UserOpEntity'; const hash = '0x376db52955d5bce114d0ccea2dcf22289b4eae1b86bcae5a59bb5fdbfef48899'; -const iconSizes = [ 'md', 'lg' ]; +const iconSizes = [ 'md', 'lg' ] as const; test.use({ viewport: { width: 180, height: 30 } }); test.describe('icon size', () => { iconSizes.forEach((size) => { - test(size, async({ mount }) => { - const component = await mount( - - - , + test(size, async({ render }) => { + const component = await render( + , ); await expect(component).toHaveScreenshot(); @@ -27,27 +24,23 @@ test.describe('icon size', () => { }); }); -test('loading', async({ mount }) => { - const component = await mount( - - - , +test('loading', async({ render }) => { + const component = await render( + , ); await expect(component).toHaveScreenshot(); }); -test('with copy +@dark-mode', async({ mount }) => { - const component = await mount( - - - , +test('with copy +@dark-mode', async({ render }) => { + const component = await render( + , ); await component.getByText(hash.slice(0, 4)).hover(); @@ -55,17 +48,15 @@ test('with copy +@dark-mode', async({ mount }) => { await expect(component).toHaveScreenshot(); }); -test('customization', async({ mount }) => { - const component = await mount( - - - , +test('customization', async({ render }) => { + const component = await render( + , ); await expect(component).toHaveScreenshot(); diff --git a/ui/shared/entities/userOp/UserOpEntity.tsx b/ui/shared/entities/userOp/UserOpEntity.tsx index c4a9022d31..718ce7988e 100644 --- a/ui/shared/entities/userOp/UserOpEntity.tsx +++ b/ui/shared/entities/userOp/UserOpEntity.tsx @@ -1,11 +1,13 @@ +import type { As } from '@chakra-ui/react'; import { chakra } from '@chakra-ui/react'; -import _omit from 'lodash/omit'; import React from 'react'; import { route } from 'nextjs-routes'; import * as EntityBase from 'ui/shared/entities/base/components'; +import { distributeEntityProps } from '../base/utils'; + type LinkProps = EntityBase.LinkBaseProps & Pick; const Link = chakra((props: LinkProps) => { @@ -21,11 +23,7 @@ const Link = chakra((props: LinkProps) => { ); }); -type IconProps = Omit & { - name?: EntityBase.IconBaseProps['name']; -}; - -const Icon = (props: IconProps) => { +const Icon = (props: EntityBase.IconBaseProps) => { return ( { - const linkProps = _omit(props, [ 'className' ]); - const partsProps = _omit(props, [ 'className', 'onClick' ]); + const partsProps = distributeEntityProps(props); return ( - - - - + + + + - + ); }; -export default React.memo(chakra(UserOpEntity)); +export default React.memo(chakra(UserOpEntity)); export { Container, diff --git a/ui/shared/filters/FilterButton.tsx b/ui/shared/filters/FilterButton.tsx index a61246587c..67bad9989d 100644 --- a/ui/shared/filters/FilterButton.tsx +++ b/ui/shared/filters/FilterButton.tsx @@ -22,19 +22,38 @@ const FilterButton = ({ isActive, isLoading, appliedFiltersNum, onClick, as }: P return ; } + const num = ( + + { appliedFiltersNum } + + ); + return ( + + ); +}; + +const TableColumnFilter = ({ columnName, isActive, className, isLoading, ...props }: Props) => { + return ( + + + + ); +}; + +export default chakra(TableColumnFilter); diff --git a/ui/shared/filters/TableColumnFilterWrapper.tsx b/ui/shared/filters/TableColumnFilterWrapper.tsx new file mode 100644 index 0000000000..bd67f93a7c --- /dev/null +++ b/ui/shared/filters/TableColumnFilterWrapper.tsx @@ -0,0 +1,62 @@ +import { + PopoverTrigger, + PopoverContent, + PopoverBody, + useDisclosure, + IconButton, + chakra, + Portal, +} 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; +} + +const TableColumnFilterWrapper = ({ columnName, isActive, className, children, isLoading }: Props) => { + const { isOpen, onToggle, onClose } = useDisclosure(); + + const child = React.Children.only(children) as React.ReactElement & { + ref?: React.Ref; + }; + + const modifiedChildren = React.cloneElement( + child, + { onClose }, + ); + + return ( + + + } + isActive={ isActive } + isDisabled={ isLoading } + borderRadius="4px" + color="text_secondary" + /> + + + + + { modifiedChildren } + + + + + ); +}; + +export default chakra(TableColumnFilterWrapper); diff --git a/ui/shared/filters/TokenTypeFilter.tsx b/ui/shared/filters/TokenTypeFilter.tsx index 9d80814284..85b4ec2432 100644 --- a/ui/shared/filters/TokenTypeFilter.tsx +++ b/ui/shared/filters/TokenTypeFilter.tsx @@ -3,7 +3,8 @@ 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; @@ -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/DragAndDropArea.tsx b/ui/shared/forms/DragAndDropArea.tsx index 51cab94cec..1b2d573729 100644 --- a/ui/shared/forms/DragAndDropArea.tsx +++ b/ui/shared/forms/DragAndDropArea.tsx @@ -9,9 +9,10 @@ interface Props { 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/FileSnippet.tsx b/ui/shared/forms/FileSnippet.tsx index 172c045ed9..73656e46d8 100644 --- a/ui/shared/forms/FileSnippet.tsx +++ b/ui/shared/forms/FileSnippet.tsx @@ -76,7 +76,7 @@ const FileSnippet = ({ file, className, index, onRemove, isDisabled, error }: Pr diff --git a/ui/shared/forms/fields/FormFieldReCaptcha.tsx b/ui/shared/forms/fields/FormFieldReCaptcha.tsx new file mode 100644 index 0000000000..2b26552fff --- /dev/null +++ b/ui/shared/forms/fields/FormFieldReCaptcha.tsx @@ -0,0 +1,58 @@ +import React from 'react'; +import ReCaptcha from 'react-google-recaptcha'; +import { useFormContext } from 'react-hook-form'; + +import config from 'configs/app'; + +interface Props { + disabledFeatureMessage?: JSX.Element; +} + +const FormFieldReCaptcha = ({ disabledFeatureMessage }: Props) => { + + const { register, unregister, trigger, clearErrors, setValue, resetField, setError, formState } = useFormContext(); + const ref = React.useRef(null); + + React.useEffect(() => { + register('reCaptcha', { required: true, shouldUnregister: true }); + + return () => { + unregister('reCaptcha'); + }; + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + + React.useEffect(() => { + ref.current?.reset(); + trigger('reCaptcha'); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [ formState.submitCount ]); + + const handleReCaptchaChange = React.useCallback((token: string | null) => { + if (token) { + clearErrors('reCaptcha'); + setValue('reCaptcha', token, { shouldValidate: true }); + } + }, [ clearErrors, setValue ]); + + const handleReCaptchaExpire = React.useCallback(() => { + resetField('reCaptcha'); + setError('reCaptcha', { type: 'required' }); + }, [ resetField, setError ]); + + if (!config.services.reCaptcha.siteKey) { + return disabledFeatureMessage ?? null; + } + + return ( + + ); +}; + +export default FormFieldReCaptcha; diff --git a/ui/shared/forms/utils/files.ts b/ui/shared/forms/utils/files.ts index 57a52d9800..f5e0327258 100644 --- a/ui/shared/forms/utils/files.ts +++ b/ui/shared/forms/utils/files.ts @@ -1,3 +1,5 @@ +import stripLeadingSlash from 'lib/stripLeadingSlash'; + // Function to get all files in drop directory export async function getAllFileEntries(dataTransferItemList: DataTransferItemList): Promise> { const fileEntries: Array = []; @@ -54,11 +56,13 @@ async function readEntriesPromise(directoryReader: DirectoryReader): Promise { +export function convertFileEntryToFile(entry: FileSystemFileEntry, fullFilePath?: boolean): Promise { return new Promise((resolve) => { entry.file(async(file: File) => { - // const newFile = new File([ file ], entry.fullPath, { lastModified: file.lastModified, type: file.type }); - resolve(file); + const newFile = fullFilePath ? + new File([ file ], stripLeadingSlash(entry.fullPath), { lastModified: file.lastModified, type: file.type }) : + file; + resolve(newFile); }); }); } diff --git a/ui/shared/gas/GasInfoTooltip.pw.tsx b/ui/shared/gas/GasInfoTooltip.pw.tsx index b59a026f56..0be7c3b3fb 100644 --- a/ui/shared/gas/GasInfoTooltip.pw.tsx +++ b/ui/shared/gas/GasInfoTooltip.pw.tsx @@ -1,10 +1,10 @@ -import { test, expect } from '@playwright/experimental-ct-react'; import React from 'react'; +import type { GasPriceInfo } from 'types/api/stats'; + import { SECOND } from 'lib/consts'; import * as statsMock from 'mocks/stats/index'; -import contextWithEnvs from 'playwright/fixtures/contextWithEnvs'; -import TestApp from 'playwright/TestApp'; +import { test, expect } from 'playwright/lib'; import GasInfoTooltip from './GasInfoTooltip'; import GasPrice from './GasPrice'; @@ -13,13 +13,11 @@ const dataUpdatedAt = Date.now() - 30 * SECOND; test.use({ viewport: { width: 300, height: 300 } }); -test('all data', async({ mount, page }) => { - await mount( - - - Gas - - , +test('all data', async({ render, page }) => { + await render( + + Gas + , ); // await page.getByText(/gas/i).hover(); @@ -27,13 +25,11 @@ test('all data', async({ mount, page }) => { await expect(page).toHaveScreenshot(); }); -test('without primary unit price', async({ mount, page }) => { - await mount( - - - Gas: - - , +test('without primary unit price', async({ render, page }) => { + await render( + + Gas: + , ); // await page.getByText(/gas/i).hover(); @@ -41,13 +37,11 @@ test('without primary unit price', async({ mount, page }) => { await expect(page).toHaveScreenshot(); }); -test('without secondary unit price', async({ mount, page }) => { - await mount( - - - Gas: - - , +test('without secondary unit price', async({ render, page }) => { + await render( + + Gas: + , ); // await page.getByText(/gas/i).hover(); @@ -55,13 +49,11 @@ test('without secondary unit price', async({ mount, page }) => { await expect(page).toHaveScreenshot(); }); -test('no data', async({ mount, page }) => { - await mount( - - - Gas: - - , +test('no data', async({ render, page }) => { + await render( + + Gas: + , ); // await page.getByText(/gas/i).hover(); @@ -69,21 +61,18 @@ test('no data', async({ mount, page }) => { await expect(page).toHaveScreenshot(); }); -const oneUnitTest = test.extend({ - context: contextWithEnvs([ - { name: 'NEXT_PUBLIC_GAS_TRACKER_UNITS', value: '["gwei"]' }, - // eslint-disable-next-line @typescript-eslint/no-explicit-any - ]) as any, -}); +test.describe('one unit', () => { + test.beforeEach(async({ mockEnvs }) => { + await mockEnvs([ + [ 'NEXT_PUBLIC_GAS_TRACKER_UNITS', '["gwei"]' ], + ]); + }); -oneUnitTest.describe('one unit', () => { - oneUnitTest('with data', async({ mount, page }) => { - await mount( - - - Gas: - - , + test('with data', async({ render, page }) => { + await render( + + Gas: + , ); // await page.getByText(/gas/i).hover(); @@ -91,13 +80,11 @@ oneUnitTest.describe('one unit', () => { await expect(page).toHaveScreenshot(); }); - oneUnitTest('without data', async({ mount, page }) => { - await mount( - - - Gas: - - , + test('without data', async({ render, page }) => { + await render( + + Gas: + , ); // await page.getByText(/gas/i).hover(); diff --git a/ui/shared/gas/GasInfoTooltip.tsx b/ui/shared/gas/GasInfoTooltip.tsx index de1edcecaa..4c81e45776 100644 --- a/ui/shared/gas/GasInfoTooltip.tsx +++ b/ui/shared/gas/GasInfoTooltip.tsx @@ -1,4 +1,16 @@ -import { Box, DarkMode, Flex, Grid, Popover, PopoverBody, PopoverContent, PopoverTrigger, Portal, useColorModeValue } from '@chakra-ui/react'; +import type { + PlacementWithLogical } from '@chakra-ui/react'; +import { + Box, + DarkMode, + Flex, + Grid, + PopoverBody, + PopoverContent, + PopoverTrigger, + Portal, + useColorModeValue, +} from '@chakra-ui/react'; import React from 'react'; import type { HomeStats } from 'types/api/stats'; @@ -7,7 +19,8 @@ import { route } from 'nextjs-routes'; import config from 'configs/app'; import dayjs from 'lib/date/dayjs'; -import LinkInternal from 'ui/shared/LinkInternal'; +import Popover from 'ui/shared/chakra/Popover'; +import LinkInternal from 'ui/shared/links/LinkInternal'; import GasInfoTooltipRow from './GasInfoTooltipRow'; import GasInfoUpdateTimer from './GasInfoUpdateTimer'; @@ -17,12 +30,12 @@ interface Props { data: HomeStats; dataUpdatedAt: number; isOpen?: boolean; // for testing purposes only; the tests were flaky, i couldn't find a better way + placement?: PlacementWithLogical; } -const POPOVER_OFFSET: [ number, number ] = [ 0, 10 ]; const feature = config.features.gasTracker; -const GasInfoTooltip = ({ children, data, dataUpdatedAt, isOpen }: Props) => { +const GasInfoTooltip = ({ children, data, dataUpdatedAt, isOpen, placement }: Props) => { const tooltipBg = useColorModeValue('gray.700', 'gray.900'); if (!data.gas_prices) { @@ -36,7 +49,7 @@ const GasInfoTooltip = ({ children, data, dataUpdatedAt, isOpen }: Props) => { 3 : 2; return ( - + { children } diff --git a/ui/shared/gas/GasInfoTooltipRow.tsx b/ui/shared/gas/GasInfoTooltipRow.tsx index 4df03189ac..3aae5220fb 100644 --- a/ui/shared/gas/GasInfoTooltipRow.tsx +++ b/ui/shared/gas/GasInfoTooltipRow.tsx @@ -16,7 +16,7 @@ const GasInfoTooltipRow = ({ name, info }: Props) => { <> { name } - { info && info.time && ( + { info && typeof info.time === 'number' && info.time > 0 && ( { space }{ (info.time / 1000).toLocaleString(undefined, { maximumFractionDigits: 1 }) }s diff --git a/ui/shared/gas/formatGasValue.ts b/ui/shared/gas/formatGasValue.ts index c55d75cd23..8c824ca742 100644 --- a/ui/shared/gas/formatGasValue.ts +++ b/ui/shared/gas/formatGasValue.ts @@ -9,6 +9,11 @@ export default function formatGasValue(data: GasPriceInfo, unit: GasUnit) { if (!data.price) { return `N/A ${ currencyUnits.gwei }`; } + + if (Number(data.price) < 0.1) { + return `< 0.1 ${ currencyUnits.gwei }`; + } + return `${ Number(data.price).toLocaleString(undefined, { maximumFractionDigits: 1 }) } ${ currencyUnits.gwei }`; } diff --git a/ui/shared/layout/Layout.pw.tsx b/ui/shared/layout/Layout.pw.tsx index 3295dccd1c..4f5fa38fe7 100644 --- a/ui/shared/layout/Layout.pw.tsx +++ b/ui/shared/layout/Layout.pw.tsx @@ -1,35 +1,36 @@ -import { test as base, expect } from '@playwright/experimental-ct-react'; import React from 'react'; -import contextWithEnvs from 'playwright/fixtures/contextWithEnvs'; -import TestApp from 'playwright/TestApp'; -import buildApiUrl from 'playwright/utils/buildApiUrl'; +import { indexingStatus } from 'mocks/stats/index'; +import { test, expect } from 'playwright/lib'; +import * as pwConfig from 'playwright/utils/config'; import Layout from './Layout'; -const API_URL = buildApiUrl('homepage_indexing_status'); - -const test = base.extend({ - context: contextWithEnvs([ - { - name: 'NEXT_PUBLIC_MAINTENANCE_ALERT_MESSAGE', - value: 'We are currently lacking pictures of ducks. Please send us one.', - }, - // eslint-disable-next-line @typescript-eslint/no-explicit-any - ]) as any, +test('base view +@mobile', async({ render, mockEnvs, mockApiResponse }) => { + await mockEnvs([ + [ + 'NEXT_PUBLIC_MAINTENANCE_ALERT_MESSAGE', + 'We are currently lacking pictures of ducks. Please send us one.', + ], + ]); + await mockApiResponse('homepage_indexing_status', indexingStatus); + const component = await render(Page Content); + await expect(component).toHaveScreenshot(); }); -test('base view +@mobile', async({ mount, page }) => { - await page.route(API_URL, (route) => route.fulfill({ - status: 200, - body: JSON.stringify({ finished_indexing_blocks: false, indexed_blocks_ratio: 0.1 }), - })); +test.describe('xxl screen', () => { + test.use({ viewport: pwConfig.viewport.xxl }); - const component = await mount( - - Page Content - , - ); + test('vertical navigation', async({ render }) => { + const component = await render(Page Content); + await expect(component).toHaveScreenshot(); + }); - await expect(component).toHaveScreenshot(); + test('horizontal navigation', async({ render, mockEnvs }) => { + await mockEnvs([ + [ 'NEXT_PUBLIC_NAVIGATION_LAYOUT', 'horizontal' ], + ]); + const component = await render(Page Content); + await expect(component).toHaveScreenshot(); + }); }); diff --git a/ui/shared/layout/Layout.tsx b/ui/shared/layout/Layout.tsx index 20a853939a..5a37df0047 100644 --- a/ui/shared/layout/Layout.tsx +++ b/ui/shared/layout/Layout.tsx @@ -13,6 +13,7 @@ const LayoutDefault = ({ children }: Props) => { return ( + diff --git a/ui/shared/layout/LayoutApp.tsx b/ui/shared/layout/LayoutApp.tsx index 8eaebf342a..3ac8e463d8 100644 --- a/ui/shared/layout/LayoutApp.tsx +++ b/ui/shared/layout/LayoutApp.tsx @@ -3,30 +3,33 @@ import React from 'react'; import type { Props } from './types'; import AppErrorBoundary from 'ui/shared/AppError/AppErrorBoundary'; -import HeaderDesktop from 'ui/snippets/header/HeaderDesktop'; import HeaderMobile from 'ui/snippets/header/HeaderMobile'; import * as Layout from './components'; const LayoutDefault = ({ children }: Props) => { return ( - + - - + + - - + { children } - ); }; diff --git a/ui/shared/layout/LayoutError.pw.tsx b/ui/shared/layout/LayoutError.pw.tsx index 6fe13e10bd..b5608d645b 100644 --- a/ui/shared/layout/LayoutError.pw.tsx +++ b/ui/shared/layout/LayoutError.pw.tsx @@ -1,27 +1,17 @@ import { Box } from '@chakra-ui/react'; -import { test, expect } from '@playwright/experimental-ct-react'; import React from 'react'; -import TestApp from 'playwright/TestApp'; -import buildApiUrl from 'playwright/utils/buildApiUrl'; +import { indexingStatus } from 'mocks/stats/index'; +import { test, expect } from 'playwright/lib'; import LayoutHome from './LayoutHome'; -const API_URL = buildApiUrl('homepage_indexing_status'); - -test('base view +@mobile', async({ mount, page }) => { - await page.route(API_URL, (route) => route.fulfill({ - status: 200, - body: JSON.stringify({ finished_indexing_blocks: false, indexed_blocks_ratio: 0.1 }), - })); - - const component = await mount( - - - Error - - , +test('base view +@mobile', async({ render, mockApiResponse }) => { + await mockApiResponse('homepage_indexing_status', indexingStatus); + const component = await render( + + Error + , ); - await expect(component).toHaveScreenshot(); }); diff --git a/ui/shared/layout/LayoutError.tsx b/ui/shared/layout/LayoutError.tsx index 626be13d47..b404cfd2ba 100644 --- a/ui/shared/layout/LayoutError.tsx +++ b/ui/shared/layout/LayoutError.tsx @@ -13,6 +13,7 @@ const LayoutError = ({ children }: Props) => { return ( + diff --git a/ui/shared/layout/LayoutHome.pw.tsx b/ui/shared/layout/LayoutHome.pw.tsx index 94b7b0cdcd..aca7c54997 100644 --- a/ui/shared/layout/LayoutHome.pw.tsx +++ b/ui/shared/layout/LayoutHome.pw.tsx @@ -1,24 +1,12 @@ -import { test, expect } from '@playwright/experimental-ct-react'; import React from 'react'; -import TestApp from 'playwright/TestApp'; -import buildApiUrl from 'playwright/utils/buildApiUrl'; +import { indexingStatus } from 'mocks/stats/index'; +import { test, expect } from 'playwright/lib'; import LayoutHome from './LayoutHome'; -const API_URL = buildApiUrl('homepage_indexing_status'); - -test('base view +@mobile', async({ mount, page }) => { - await page.route(API_URL, (route) => route.fulfill({ - status: 200, - body: JSON.stringify({ finished_indexing_blocks: false, indexed_blocks_ratio: 0.1 }), - })); - - const component = await mount( - - Page Content - , - ); - +test('base view +@mobile', async({ render, mockApiResponse }) => { + await mockApiResponse('homepage_indexing_status', indexingStatus); + const component = await render(Page Content); await expect(component).toHaveScreenshot(); }); diff --git a/ui/shared/layout/LayoutHome.tsx b/ui/shared/layout/LayoutHome.tsx index 6274afeeea..ef2c6c4d07 100644 --- a/ui/shared/layout/LayoutHome.tsx +++ b/ui/shared/layout/LayoutHome.tsx @@ -12,7 +12,8 @@ const LayoutHome = ({ children }: Props) => { return ( - + + { return ( + { children } ); diff --git a/ui/shared/layout/__screenshots__/Layout.pw.tsx_default_xxl-screen-horizontal-navigation-1.png b/ui/shared/layout/__screenshots__/Layout.pw.tsx_default_xxl-screen-horizontal-navigation-1.png new file mode 100644 index 0000000000..05f4bce2e4 Binary files /dev/null and b/ui/shared/layout/__screenshots__/Layout.pw.tsx_default_xxl-screen-horizontal-navigation-1.png differ diff --git a/ui/shared/layout/__screenshots__/Layout.pw.tsx_default_xxl-screen-vertical-navigation-1.png b/ui/shared/layout/__screenshots__/Layout.pw.tsx_default_xxl-screen-vertical-navigation-1.png new file mode 100644 index 0000000000..f4e2c356fd Binary files /dev/null and b/ui/shared/layout/__screenshots__/Layout.pw.tsx_default_xxl-screen-vertical-navigation-1.png differ diff --git a/ui/shared/layout/components/Container.tsx b/ui/shared/layout/components/Container.tsx index 68991b10de..62678c70d0 100644 --- a/ui/shared/layout/components/Container.tsx +++ b/ui/shared/layout/components/Container.tsx @@ -1,16 +1,24 @@ -import { Box } from '@chakra-ui/react'; +import { Box, chakra, useColorModeValue } from '@chakra-ui/react'; import React from 'react'; interface Props { children: React.ReactNode; + className?: string; } -const Container = ({ children }: Props) => { +const Container = ({ children, className }: Props) => { + const bgColor = useColorModeValue('white', 'black'); + return ( - + { children } ); }; -export default React.memo(Container); +export default React.memo(chakra(Container)); diff --git a/ui/shared/layout/components/Content.tsx b/ui/shared/layout/components/Content.tsx index 4f6ddf23c1..ffb215371c 100644 --- a/ui/shared/layout/components/Content.tsx +++ b/ui/shared/layout/components/Content.tsx @@ -8,7 +8,7 @@ interface Props { const Content = ({ children, className }: Props) => { return ( - + { children } ); diff --git a/ui/shared/layout/components/MainArea.tsx b/ui/shared/layout/components/MainArea.tsx index 804577425f..632939e5db 100644 --- a/ui/shared/layout/components/MainArea.tsx +++ b/ui/shared/layout/components/MainArea.tsx @@ -1,16 +1,34 @@ -import { Flex } from '@chakra-ui/react'; +import { Flex, chakra } from '@chakra-ui/react'; import React from 'react'; +import config from 'configs/app'; + +import { CONTENT_MAX_WIDTH } from '../utils'; + interface Props { children: React.ReactNode; + className?: string; } -const MainArea = ({ children }: Props) => { +const TOP_BAR_HEIGHT = 36; +const HORIZONTAL_NAV_BAR_HEIGHT = config.UI.navigation.layout === 'horizontal' ? 49 : 0; + +const MainArea = ({ children, className }: Props) => { return ( - + { children } ); }; -export default React.memo(MainArea); +export default React.memo(chakra(MainArea)); diff --git a/ui/shared/layout/components/MainColumn.tsx b/ui/shared/layout/components/MainColumn.tsx index 57e9caeeec..75133c9651 100644 --- a/ui/shared/layout/components/MainColumn.tsx +++ b/ui/shared/layout/components/MainColumn.tsx @@ -1,6 +1,8 @@ import { Flex, chakra } from '@chakra-ui/react'; import React from 'react'; +import config from 'configs/app'; + interface Props { className?: string; children: React.ReactNode; @@ -12,10 +14,11 @@ const MainColumn = ({ children, className }: Props) => { className={ className } flexDir="column" flexGrow={ 1 } - w={{ base: '100%', lg: 'auto' }} - paddingX={{ base: 4, lg: 12 }} + w={{ base: '100%', lg: config.UI.navigation.layout === 'horizontal' ? '100%' : 'auto' }} + paddingX={{ base: 3, lg: config.UI.navigation.layout === 'horizontal' ? 6 : 12 }} + paddingRight={{ '2xl': 6 }} paddingTop={{ base: `${ 12 + 52 }px`, lg: 6 }} // 12px is top padding of content area, 52px is search bar height - paddingBottom={ 10 } + paddingBottom={ 8 } > { children } diff --git a/ui/shared/layout/components/NavBar.tsx b/ui/shared/layout/components/NavBar.tsx new file mode 100644 index 0000000000..62986eae92 --- /dev/null +++ b/ui/shared/layout/components/NavBar.tsx @@ -0,0 +1,6 @@ +import config from 'configs/app'; +import NavigationDesktop from 'ui/snippets/navigation/horizontal/NavigationDesktop'; + +const EmptyComponent = () => null; + +export default config.UI.navigation.layout === 'horizontal' ? NavigationDesktop : EmptyComponent; diff --git a/ui/shared/layout/components/SideBar.tsx b/ui/shared/layout/components/SideBar.tsx index 577c21bbf7..840d1d6a5a 100644 --- a/ui/shared/layout/components/SideBar.tsx +++ b/ui/shared/layout/components/SideBar.tsx @@ -1,3 +1,6 @@ -import NavigationDesktop from 'ui/snippets/navigation/NavigationDesktop'; +import config from 'configs/app'; +import NavigationDesktop from 'ui/snippets/navigation/vertical/NavigationDesktop'; -export default NavigationDesktop; +const EmptyComponent = () => null; + +export default config.UI.navigation.layout === 'horizontal' ? EmptyComponent : NavigationDesktop; diff --git a/ui/shared/layout/components/index.tsx b/ui/shared/layout/components/index.tsx index 2818e9c5c0..67f2dfaf5e 100644 --- a/ui/shared/layout/components/index.tsx +++ b/ui/shared/layout/components/index.tsx @@ -5,6 +5,7 @@ import Container from './Container'; import Content from './Content'; import MainArea from './MainArea'; import MainColumn from './MainColumn'; +import NavBar from './NavBar'; import SideBar from './SideBar'; export { @@ -12,6 +13,7 @@ export { Content, MainArea, SideBar, + NavBar, MainColumn, Footer, TopRow, diff --git a/ui/shared/layout/utils.ts b/ui/shared/layout/utils.ts new file mode 100644 index 0000000000..e0b6e2c1a3 --- /dev/null +++ b/ui/shared/layout/utils.ts @@ -0,0 +1,6 @@ +import config from 'configs/app'; + +const maxWidthVerticalNavigation = config.UI.maxContentWidth ? 1_920 : 10_000; +const maxWidthHorizontalNavigation = config.UI.maxContentWidth ? 1_440 : 10_000; + +export const CONTENT_MAX_WIDTH = config.UI.navigation.layout === 'horizontal' ? maxWidthHorizontalNavigation : maxWidthVerticalNavigation; diff --git a/ui/shared/links/LinkExternal.tsx b/ui/shared/links/LinkExternal.tsx new file mode 100644 index 0000000000..7070f81981 --- /dev/null +++ b/ui/shared/links/LinkExternal.tsx @@ -0,0 +1,54 @@ +import type { LinkProps } from '@chakra-ui/react'; +import { Link, chakra, Box, Skeleton } from '@chakra-ui/react'; +import React from 'react'; + +import IconSvg from 'ui/shared/IconSvg'; + +import type { Variants } from './useLinkStyles'; +import { useLinkStyles } from './useLinkStyles'; + +interface Props { + href: string; + className?: string; + children: React.ReactNode; + isLoading?: boolean; + variant?: Variants; + iconColor?: LinkProps['color']; + onClick?: LinkProps['onClick']; +} + +const LinkExternal = ({ href, children, className, isLoading, variant, iconColor, onClick }: Props) => { + const commonProps = { + display: 'inline-block', + alignItems: 'center', + }; + + const styleProps = useLinkStyles(commonProps, variant); + + if (isLoading) { + if (variant === 'subtle') { + return ( + + { children } + + + ); + } + + return ( + + { children } + + + ); + } + + return ( + + { children } + + + ); +}; + +export default React.memo(chakra(LinkExternal)); diff --git a/ui/shared/links/LinkInternal.tsx b/ui/shared/links/LinkInternal.tsx new file mode 100644 index 0000000000..7f0055a815 --- /dev/null +++ b/ui/shared/links/LinkInternal.tsx @@ -0,0 +1,34 @@ +import type { LinkProps, FlexProps } from '@chakra-ui/react'; +import { Flex, Link } from '@chakra-ui/react'; +import type { LinkProps as NextLinkProps } from 'next/link'; +import NextLink from 'next/link'; +import type { LegacyRef } from 'react'; +import React from 'react'; + +import type { Variants } from './useLinkStyles'; +import { useLinkStyles } from './useLinkStyles'; + +type Props = LinkProps & { + variant?: Variants; + isLoading?: boolean; +} + +const LinkInternal = ({ isLoading, variant, ...props }: Props, ref: LegacyRef) => { + const styleProps = useLinkStyles({}, variant); + + if (isLoading) { + return { props.children }; + } + + if (!props.href) { + return ; + } + + return ( + + + + ); +}; + +export default React.memo(React.forwardRef(LinkInternal)); diff --git a/ui/shared/links/LinkNewTab.tsx b/ui/shared/links/LinkNewTab.tsx new file mode 100644 index 0000000000..7aafc2959a --- /dev/null +++ b/ui/shared/links/LinkNewTab.tsx @@ -0,0 +1,36 @@ +import { chakra, IconButton, Tooltip, useColorModeValue } from '@chakra-ui/react'; +import React from 'react'; + +import IconSvg from '../IconSvg'; + +interface Props { + className?: string; + label?: string; + href: string; +} + +const LinkNewTab = ({ className, label, href }: Props) => { + const iconColor = useColorModeValue('gray.400', 'gray.500'); + + return ( + + } + w="20px" + h="20px" + color={ iconColor } + variant="simple" + display="inline-block" + flexShrink={ 0 } + as="a" + href={ href } + target="_blank" + className={ className } + borderRadius={ 0 } + /> + + ); +}; + +export default React.memo(chakra(LinkNewTab)); diff --git a/ui/shared/links/useLinkStyles.ts b/ui/shared/links/useLinkStyles.ts new file mode 100644 index 0000000000..b44ca5052f --- /dev/null +++ b/ui/shared/links/useLinkStyles.ts @@ -0,0 +1,24 @@ +import type { ChakraProps } from '@chakra-ui/react'; +import { useColorModeValue } from '@chakra-ui/react'; + +export type Variants = 'subtle' + +export function useLinkStyles(commonProps: ChakraProps, variant?: Variants) { + const subtleLinkBg = useColorModeValue('gray.100', 'gray.700'); + + switch (variant) { + case 'subtle': { + return { + ...commonProps, + px: '10px', + py: '6px', + bgColor: subtleLinkBg, + borderRadius: 'base', + }; + } + + default:{ + return commonProps; + } + } +} diff --git a/ui/shared/logs/LogDecodedInputData.pw.tsx b/ui/shared/logs/LogDecodedInputData.pw.tsx index ea58b4212e..d5cd7b2201 100644 --- a/ui/shared/logs/LogDecodedInputData.pw.tsx +++ b/ui/shared/logs/LogDecodedInputData.pw.tsx @@ -1,25 +1,16 @@ -import { test, expect } from '@playwright/experimental-ct-react'; import React from 'react'; import * as mocks from 'mocks/txs/decodedInputData'; -import TestApp from 'playwright/TestApp'; +import { test, expect } from 'playwright/lib'; import LogDecodedInputData from './LogDecodedInputData'; -test('with indexed fields +@mobile +@dark-mode', async({ mount }) => { - const component = await mount( - - - , - ); +test('with indexed fields +@mobile +@dark-mode', async({ render }) => { + const component = await render(); await expect(component).toHaveScreenshot(); }); -test('without indexed fields +@mobile', async({ mount }) => { - const component = await mount( - - - , - ); +test('without indexed fields +@mobile', async({ render }) => { + const component = await render(); await expect(component).toHaveScreenshot(); }); diff --git a/ui/shared/logs/LogDecodedInputData.tsx b/ui/shared/logs/LogDecodedInputData.tsx index a1022b94a6..a616e16290 100644 --- a/ui/shared/logs/LogDecodedInputData.tsx +++ b/ui/shared/logs/LogDecodedInputData.tsx @@ -2,17 +2,27 @@ import React from 'react'; import type { DecodedInput } from 'types/api/decodedInput'; +import useIsMobile from 'lib/hooks/useIsMobile'; + import LogDecodedInputDataHeader from './LogDecodedInputDataHeader'; import LogDecodedInputDataTable from './LogDecodedInputDataTable'; interface Props { data: DecodedInput; isLoading?: boolean; + rightSlot?: React.ReactNode; } -const LogDecodedInputData = ({ data, isLoading }: Props) => { +const LogDecodedInputData = ({ data, isLoading, rightSlot }: Props) => { + const isMobile = useIsMobile(); return ( <> - + { isMobile ? rightSlot : null } + { data.parameters.length > 0 && } ); diff --git a/ui/shared/logs/LogDecodedInputDataHeader.tsx b/ui/shared/logs/LogDecodedInputDataHeader.tsx index 653382b18b..aaa4528e2f 100644 --- a/ui/shared/logs/LogDecodedInputDataHeader.tsx +++ b/ui/shared/logs/LogDecodedInputDataHeader.tsx @@ -1,39 +1,52 @@ import { Divider, Flex, Skeleton, VStack } from '@chakra-ui/react'; import React from 'react'; +import Tag from 'ui/shared/chakra/Tag'; + interface Props { methodId: string; methodCall: string; isLoading?: boolean; + rightSlot?: React.ReactNode; } -const Item = ({ label, text, isLoading }: { label: string; text: string; isLoading?: boolean}) => { +const Item = ({ label, children, isLoading }: { label: string; children: React.ReactNode; isLoading?: boolean}) => { return ( { label } - { text } + { children } ); }; -const LogDecodedInputDataHeader = ({ methodId, methodCall, isLoading }: Props) => { +const LogDecodedInputDataHeader = ({ methodId, methodCall, isLoading, rightSlot }: Props) => { return ( } fontSize="sm" lineHeight={ 5 } + flexGrow={ 1 } + w="100%" > - - + + + { methodId } + + { rightSlot } + + + { methodCall } + ); }; diff --git a/ui/shared/logs/LogDecodedInputDataTable.tsx b/ui/shared/logs/LogDecodedInputDataTable.tsx index f712836a50..07505d8803 100644 --- a/ui/shared/logs/LogDecodedInputDataTable.tsx +++ b/ui/shared/logs/LogDecodedInputDataTable.tsx @@ -33,7 +33,7 @@ const Row = ({ name, type, indexed, value, isLoading }: ArrayElement ); diff --git a/ui/shared/logs/LogItem.pw.tsx b/ui/shared/logs/LogItem.pw.tsx index d537307308..3c7b59b993 100644 --- a/ui/shared/logs/LogItem.pw.tsx +++ b/ui/shared/logs/LogItem.pw.tsx @@ -1,9 +1,8 @@ -import { test, expect } from '@playwright/experimental-ct-react'; import React from 'react'; import * as addressMocks from 'mocks/address/address'; import * as inputDataMocks from 'mocks/txs/decodedInputData'; -import TestApp from 'playwright/TestApp'; +import { test, expect } from 'playwright/lib'; import LogItem from './LogItem'; @@ -15,36 +14,32 @@ const TOPICS = [ ]; const DATA = '0x0000000000000000000000000000000000000000000000000070265bf0112cee'; -test('with decoded input data +@mobile +@dark-mode', async({ mount }) => { - const component = await mount( - - - , +test('with decoded input data +@mobile +@dark-mode', async({ render }) => { + const component = await render( + , ); await expect(component).toHaveScreenshot(); }); -test('without decoded input data +@mobile', async({ mount }) => { - const component = await mount( - - - , +test('without decoded input data +@mobile', async({ render }) => { + const component = await render( + , ); await expect(component).toHaveScreenshot(); }); diff --git a/ui/shared/logs/LogItem.tsx b/ui/shared/logs/LogItem.tsx index 184c1b8a60..f74fbf1300 100644 --- a/ui/shared/logs/LogItem.tsx +++ b/ui/shared/logs/LogItem.tsx @@ -53,7 +53,7 @@ const LogItem = ({ address, index, topics, data, decoded, type, tx_hash: txHash, ) } { hasTxInfo ? Transaction : Address } - { type === 'address' ? ( + { type === 'address' && txHash ? ( */ } - diff --git a/ui/shared/logs/LogTopic.pw.tsx b/ui/shared/logs/LogTopic.pw.tsx index 9664f2671a..5f38d8c44d 100644 --- a/ui/shared/logs/LogTopic.pw.tsx +++ b/ui/shared/logs/LogTopic.pw.tsx @@ -1,27 +1,18 @@ -import { test, expect } from '@playwright/experimental-ct-react'; import React from 'react'; -import TestApp from 'playwright/TestApp'; +import { test, expect } from 'playwright/lib'; import LogTopic from './LogTopic'; -test('address view +@mobile -@default', async({ mount }) => { - const component = await mount( - - - , - ); +test('address view +@mobile -@default', async({ render }) => { + const component = await render(); await component.locator('select[aria-label="Data type"]').selectOption('address'); await expect(component).toHaveScreenshot(); }); -test('hex view +@mobile -@default', async({ mount }) => { - const component = await mount( - - - , - ); +test('hex view +@mobile -@default', async({ render }) => { + const component = await render(); await component.locator('select[aria-label="Data type"]').selectOption('hex'); await expect(component).toHaveScreenshot(); diff --git a/ui/shared/logs/LogTopic.tsx b/ui/shared/logs/LogTopic.tsx index bb8e7fdc22..971063ec4e 100644 --- a/ui/shared/logs/LogTopic.tsx +++ b/ui/shared/logs/LogTopic.tsx @@ -51,7 +51,7 @@ const LogTopic = ({ hex, index, isLoading }: Props) => { case 'address': { return ( ); @@ -62,7 +62,7 @@ const LogTopic = ({ hex, index, isLoading }: Props) => { return ( - diff --git a/ui/shared/logs/__screenshots__/LogDecodedInputData.pw.tsx_dark-color-mode_with-indexed-fields-mobile-dark-mode-1.png b/ui/shared/logs/__screenshots__/LogDecodedInputData.pw.tsx_dark-color-mode_with-indexed-fields-mobile-dark-mode-1.png index 98428228b9..00b9b91666 100644 Binary files a/ui/shared/logs/__screenshots__/LogDecodedInputData.pw.tsx_dark-color-mode_with-indexed-fields-mobile-dark-mode-1.png and b/ui/shared/logs/__screenshots__/LogDecodedInputData.pw.tsx_dark-color-mode_with-indexed-fields-mobile-dark-mode-1.png differ diff --git a/ui/shared/logs/__screenshots__/LogDecodedInputData.pw.tsx_default_with-indexed-fields-mobile-dark-mode-1.png b/ui/shared/logs/__screenshots__/LogDecodedInputData.pw.tsx_default_with-indexed-fields-mobile-dark-mode-1.png index 0e81521fd8..d7b0124d5c 100644 Binary files a/ui/shared/logs/__screenshots__/LogDecodedInputData.pw.tsx_default_with-indexed-fields-mobile-dark-mode-1.png and b/ui/shared/logs/__screenshots__/LogDecodedInputData.pw.tsx_default_with-indexed-fields-mobile-dark-mode-1.png differ diff --git a/ui/shared/logs/__screenshots__/LogDecodedInputData.pw.tsx_default_without-indexed-fields-mobile-1.png b/ui/shared/logs/__screenshots__/LogDecodedInputData.pw.tsx_default_without-indexed-fields-mobile-1.png index 5b5efd2b32..0d23e44363 100644 Binary files a/ui/shared/logs/__screenshots__/LogDecodedInputData.pw.tsx_default_without-indexed-fields-mobile-1.png and b/ui/shared/logs/__screenshots__/LogDecodedInputData.pw.tsx_default_without-indexed-fields-mobile-1.png differ diff --git a/ui/shared/logs/__screenshots__/LogDecodedInputData.pw.tsx_mobile_with-indexed-fields-mobile-dark-mode-1.png b/ui/shared/logs/__screenshots__/LogDecodedInputData.pw.tsx_mobile_with-indexed-fields-mobile-dark-mode-1.png index 173ef0fc4a..a406c70799 100644 Binary files a/ui/shared/logs/__screenshots__/LogDecodedInputData.pw.tsx_mobile_with-indexed-fields-mobile-dark-mode-1.png and b/ui/shared/logs/__screenshots__/LogDecodedInputData.pw.tsx_mobile_with-indexed-fields-mobile-dark-mode-1.png differ diff --git a/ui/shared/logs/__screenshots__/LogDecodedInputData.pw.tsx_mobile_without-indexed-fields-mobile-1.png b/ui/shared/logs/__screenshots__/LogDecodedInputData.pw.tsx_mobile_without-indexed-fields-mobile-1.png index 7bde892cd2..6f9b592c45 100644 Binary files a/ui/shared/logs/__screenshots__/LogDecodedInputData.pw.tsx_mobile_without-indexed-fields-mobile-1.png and b/ui/shared/logs/__screenshots__/LogDecodedInputData.pw.tsx_mobile_without-indexed-fields-mobile-1.png differ diff --git a/ui/shared/logs/__screenshots__/LogItem.pw.tsx_dark-color-mode_with-decoded-input-data-mobile-dark-mode-1.png b/ui/shared/logs/__screenshots__/LogItem.pw.tsx_dark-color-mode_with-decoded-input-data-mobile-dark-mode-1.png index 3902449e9a..eae3ae1e2c 100644 Binary files a/ui/shared/logs/__screenshots__/LogItem.pw.tsx_dark-color-mode_with-decoded-input-data-mobile-dark-mode-1.png and b/ui/shared/logs/__screenshots__/LogItem.pw.tsx_dark-color-mode_with-decoded-input-data-mobile-dark-mode-1.png differ diff --git a/ui/shared/logs/__screenshots__/LogItem.pw.tsx_default_with-decoded-input-data-mobile-dark-mode-1.png b/ui/shared/logs/__screenshots__/LogItem.pw.tsx_default_with-decoded-input-data-mobile-dark-mode-1.png index 3063b71f5d..93855cd754 100644 Binary files a/ui/shared/logs/__screenshots__/LogItem.pw.tsx_default_with-decoded-input-data-mobile-dark-mode-1.png and b/ui/shared/logs/__screenshots__/LogItem.pw.tsx_default_with-decoded-input-data-mobile-dark-mode-1.png differ diff --git a/ui/shared/logs/__screenshots__/LogItem.pw.tsx_mobile_with-decoded-input-data-mobile-dark-mode-1.png b/ui/shared/logs/__screenshots__/LogItem.pw.tsx_mobile_with-decoded-input-data-mobile-dark-mode-1.png index 3834fa7b4f..7ae98dec50 100644 Binary files a/ui/shared/logs/__screenshots__/LogItem.pw.tsx_mobile_with-decoded-input-data-mobile-dark-mode-1.png and b/ui/shared/logs/__screenshots__/LogItem.pw.tsx_mobile_with-decoded-input-data-mobile-dark-mode-1.png differ diff --git a/ui/shared/nft/NftImageFullscreen.tsx b/ui/shared/nft/NftImageFullscreen.tsx index 9d35c313d4..b839b13aa2 100644 --- a/ui/shared/nft/NftImageFullscreen.tsx +++ b/ui/shared/nft/NftImageFullscreen.tsx @@ -13,11 +13,11 @@ interface Props { const NftImageFullscreen = ({ src, isOpen, onClose }: Props) => { const imgRef = React.useRef(null); - const [ hasDimentions, setHasDimentions ] = React.useState(true); + const [ hasDimensions, setHasDimensions ] = React.useState(true); const checkWidth = React.useCallback(() => { if (imgRef.current?.getBoundingClientRect().width === 0) { - setHasDimentions(false); + setHasDimensions(false); } }, [ ]); @@ -30,7 +30,7 @@ const NftImageFullscreen = ({ src, isOpen, onClose }: Props) => { maxW="90vw" ref={ imgRef } onLoad={ checkWidth } - sx={ hasDimentions ? {} : { width: '90vw', height: '90vh' } } + { ...(hasDimensions ? {} : { width: '90vw', height: '90vh' }) } /> ); diff --git a/ui/shared/nft/NftMedia.pw.tsx b/ui/shared/nft/NftMedia.pw.tsx index 4b91def420..b709b17c85 100644 --- a/ui/shared/nft/NftMedia.pw.tsx +++ b/ui/shared/nft/NftMedia.pw.tsx @@ -1,42 +1,25 @@ -import { test, expect } from '@playwright/experimental-ct-react'; +import { Box } from '@chakra-ui/react'; import React from 'react'; -import TestApp from 'playwright/TestApp'; +import { test, expect } from 'playwright/lib'; import NftMedia from './NftMedia'; test.describe('no url', () => { test.use({ viewport: { width: 250, height: 250 } }); - test('preview +@dark-mode', async({ mount }) => { - const component = await mount( - - - , - ); - + test('preview +@dark-mode', async({ render }) => { + const component = await render(); await expect(component).toHaveScreenshot(); }); - test('with fallback', async({ mount, page }) => { + test('with fallback', async({ render, mockAssetResponse }) => { const IMAGE_URL = 'https://localhost:3000/my-image.jpg'; - - await page.route(IMAGE_URL, (route) => { - return route.fulfill({ - status: 200, - path: './playwright/mocks/image_long.jpg', - }); - }); - - const component = await mount( - - - , - ); - + await mockAssetResponse(IMAGE_URL, './playwright/mocks/image_long.jpg'); + const component = await render(); await expect(component).toHaveScreenshot(); }); - test('non-media url and fallback', async({ mount, page }) => { + test('non-media url and fallback', async({ render, page, mockAssetResponse }) => { const ANIMATION_URL = 'https://localhost:3000/my-animation.m3u8'; const ANIMATION_MEDIA_TYPE_API_URL = `/node-api/media-type?url=${ encodeURIComponent(ANIMATION_URL) }`; const IMAGE_URL = 'https://localhost:3000/my-image.jpg'; @@ -47,88 +30,40 @@ test.describe('no url', () => { body: JSON.stringify({ type: undefined }), }); }); + await mockAssetResponse(IMAGE_URL, './playwright/mocks/image_long.jpg'); - await page.route(IMAGE_URL, (route) => { - return route.fulfill({ - status: 200, - path: './playwright/mocks/image_long.jpg', - }); - }); - - const component = await mount( - - - , - ); - + const component = await render(); await expect(component).toHaveScreenshot(); }); }); test.describe('image', () => { - test.use({ viewport: { width: 250, height: 250 } }); - const MEDIA_URL = 'https://localhost:3000/my-image.jpg'; - test.beforeEach(async({ page }) => { - await page.route(MEDIA_URL, (route) => { - return route.fulfill({ - status: 200, - path: './playwright/mocks/image_long.jpg', - }); - }); + test.beforeEach(async({ mockAssetResponse }) => { + await mockAssetResponse(MEDIA_URL, './playwright/mocks/image_long.jpg'); }); - test('preview +@dark-mode', async({ mount }) => { - const component = await mount( - + test('preview +@dark-mode', async({ render, page }) => { + await render( + - , + , ); - - await expect(component).toHaveScreenshot(); + await expect(page).toHaveScreenshot({ clip: { x: 0, y: 0, width: 250, height: 250 } }); }); -}); - -test('image preview hover', async({ mount, page }) => { - const MEDIA_URL = 'https://localhost:3000/my-image.jpg'; - await page.route(MEDIA_URL, (route) => { - return route.fulfill({ - status: 200, - path: './playwright/mocks/image_long.jpg', - }); + test('preview hover', async({ render, page }) => { + const component = await render(); + await component.getByAltText('Token instance image').hover(); + await expect(page).toHaveScreenshot({ clip: { x: 0, y: 0, width: 250, height: 250 } }); }); - const component = await mount( - - - , - ); - - await component.getByAltText('Token instance image').hover(); - - await expect(page).toHaveScreenshot({ clip: { x: 0, y: 0, width: 250, height: 250 } }); -}); - -test('image fullscreen +@dark-mode +@mobile', async({ mount, page }) => { - const MEDIA_URL = 'https://localhost:3000/my-image.jpg'; - - await page.route(MEDIA_URL, (route) => { - return route.fulfill({ - status: 200, - path: './playwright/mocks/image_long.jpg', - }); + test('fullscreen +@dark-mode +@mobile', async({ render, page }) => { + const component = await render(); + await component.getByAltText('Token instance image').click(); + await expect(page).toHaveScreenshot(); }); - const component = await mount( - - - , - ); - - await component.getByAltText('Token instance image').click(); - - await expect(page).toHaveScreenshot(); }); test.describe('page', () => { @@ -137,27 +72,16 @@ test.describe('page', () => { const MEDIA_URL = 'https://localhost:3000/page.html'; const MEDIA_TYPE_API_URL = `/node-api/media-type?url=${ encodeURIComponent(MEDIA_URL) }`; - test.beforeEach(async({ page }) => { - - await page.route(MEDIA_URL, (route) => { - return route.fulfill({ - status: 200, - path: './playwright/mocks/page.html', - }); - }); + test.beforeEach(async({ page, mockAssetResponse }) => { + await mockAssetResponse(MEDIA_URL, './playwright/mocks/page.html'); await page.route(MEDIA_TYPE_API_URL, (route) => route.fulfill({ status: 200, body: JSON.stringify({ type: 'html' }), })); }); - test('preview +@dark-mode', async({ mount }) => { - const component = await mount( - - - , - ); - + test('preview +@dark-mode', async({ render }) => { + const component = await render(); await expect(component).toHaveScreenshot(); }); }); diff --git a/ui/shared/nft/NftMedia.tsx b/ui/shared/nft/NftMedia.tsx index 51dd03b6e5..cc572fce4e 100644 --- a/ui/shared/nft/NftMedia.tsx +++ b/ui/shared/nft/NftMedia.tsx @@ -18,9 +18,10 @@ interface Props { className?: string; isLoading?: boolean; withFullscreen?: boolean; + autoplayVideo?: boolean; } -const NftMedia = ({ imageUrl, animationUrl, className, isLoading, withFullscreen }: Props) => { +const NftMedia = ({ imageUrl, animationUrl, className, isLoading, withFullscreen, autoplayVideo }: Props) => { const [ isMediaLoading, setIsMediaLoading ] = React.useState(true); const [ isLoadingError, setIsLoadingError ] = React.useState(false); @@ -71,7 +72,7 @@ const NftMedia = ({ imageUrl, animationUrl, className, isLoading, withFullscreen switch (type) { case 'video': - return ; + return ; case 'html': return ; case 'image': diff --git a/ui/shared/nft/NftVideo.tsx b/ui/shared/nft/NftVideo.tsx index a58cefbbda..6a3a710dfa 100644 --- a/ui/shared/nft/NftVideo.tsx +++ b/ui/shared/nft/NftVideo.tsx @@ -5,20 +5,37 @@ import { mediaStyleProps, videoPlayProps } from './utils'; interface Props { src: string; + poster?: string; + autoPlay?: boolean; onLoad: () => void; onError: () => void; onClick?: () => void; } -const NftVideo = ({ src, onLoad, onError, onClick }: Props) => { +const NftVideo = ({ src, poster, autoPlay = true, onLoad, onError, onClick }: Props) => { + const ref = React.useRef(null); + + const handleMouseEnter = React.useCallback(() => { + !autoPlay && ref.current?.play(); + }, [ autoPlay ]); + + const handleMouseLeave = React.useCallback(() => { + !autoPlay && ref.current?.pause(); + }, [ autoPlay ]); + return ( ); diff --git a/ui/shared/nft/NftVideoFullscreen.tsx b/ui/shared/nft/NftVideoFullscreen.tsx index 07fa250b1e..26a7081a41 100644 --- a/ui/shared/nft/NftVideoFullscreen.tsx +++ b/ui/shared/nft/NftVideoFullscreen.tsx @@ -18,6 +18,7 @@ const NftVideoFullscreen = ({ src, isOpen, onClose }: Props) => { src={ src } maxH="90vh" maxW="90vw" + autoPlay={ true } /> ); diff --git a/ui/shared/nft/__screenshots__/NftMedia.pw.tsx_dark-color-mode_image-preview-dark-mode-1.png b/ui/shared/nft/__screenshots__/NftMedia.pw.tsx_dark-color-mode_image-preview-dark-mode-1.png index b60b305a6c..59ba859811 100644 Binary files a/ui/shared/nft/__screenshots__/NftMedia.pw.tsx_dark-color-mode_image-preview-dark-mode-1.png and b/ui/shared/nft/__screenshots__/NftMedia.pw.tsx_dark-color-mode_image-preview-dark-mode-1.png differ diff --git a/ui/shared/nft/__screenshots__/NftMedia.pw.tsx_default_image-preview-dark-mode-1.png b/ui/shared/nft/__screenshots__/NftMedia.pw.tsx_default_image-preview-dark-mode-1.png index 2a4baa8e3a..4273139faf 100644 Binary files a/ui/shared/nft/__screenshots__/NftMedia.pw.tsx_default_image-preview-dark-mode-1.png and b/ui/shared/nft/__screenshots__/NftMedia.pw.tsx_default_image-preview-dark-mode-1.png differ diff --git a/ui/shared/nft/useNftMediaInfo.tsx b/ui/shared/nft/useNftMediaInfo.tsx index 7c36f828e9..d040c609fb 100644 --- a/ui/shared/nft/useNftMediaInfo.tsx +++ b/ui/shared/nft/useNftMediaInfo.tsx @@ -24,7 +24,7 @@ interface ReturnType { export default function useNftMediaInfo({ imageUrl, animationUrl, isEnabled }: Params): ReturnType | null { const primaryQuery = useNftMediaTypeQuery(animationUrl, isEnabled); - const secondaryQuery = useNftMediaTypeQuery(imageUrl, !primaryQuery.isPending && !primaryQuery.data); + const secondaryQuery = useNftMediaTypeQuery(imageUrl, isEnabled && !primaryQuery.isPending && !primaryQuery.data); return React.useMemo(() => { if (primaryQuery.isPending) { diff --git a/ui/shared/nft/utils.ts b/ui/shared/nft/utils.ts index 90410798f6..3c0ce90bac 100644 --- a/ui/shared/nft/utils.ts +++ b/ui/shared/nft/utils.ts @@ -41,7 +41,6 @@ export const mediaStyleProps = { }; export const videoPlayProps = { - autoPlay: true, disablePictureInPicture: true, loop: true, muted: true, diff --git a/ui/shared/pagination/Pagination.pw.tsx b/ui/shared/pagination/Pagination.pw.tsx index e962550667..14e8276ded 100644 --- a/ui/shared/pagination/Pagination.pw.tsx +++ b/ui/shared/pagination/Pagination.pw.tsx @@ -1,15 +1,14 @@ -import { test, expect } from '@playwright/experimental-ct-react'; import React from 'react'; import type { PaginationParams } from './types'; -import TestApp from 'playwright/TestApp'; +import { test, expect } from 'playwright/lib'; import Pagination from './Pagination'; test.use({ viewport: { width: 250, height: 50 } }); -test('default view', async({ mount }) => { +test('default view', async({ render }) => { const props: PaginationParams = { page: 2, isVisible: true, @@ -21,11 +20,6 @@ test('default view', async({ mount }) => { onPrevPageClick: () => {}, resetPage: () => {}, }; - const component = await mount( - - - , - ); - + const component = await render(); await expect(component).toHaveScreenshot(); }); diff --git a/ui/shared/pagination/Pagination.tsx b/ui/shared/pagination/Pagination.tsx index 67c2c0593d..203eb9ebf3 100644 --- a/ui/shared/pagination/Pagination.tsx +++ b/ui/shared/pagination/Pagination.tsx @@ -48,7 +48,7 @@ const Pagination = ({ page, onNextPageClick, onPrevPageClick, resetPage, hasPage - + ); }; -export default React.forwardRef(SolidityscanReportButton); +export default chakra(React.forwardRef(SolidityscanReportButton)); diff --git a/ui/shared/solidityscanReport/SolidityscanReportDetails.tsx b/ui/shared/solidityscanReport/SolidityscanReportDetails.tsx index 2d2a5cf140..5f6e1a5942 100644 --- a/ui/shared/solidityscanReport/SolidityscanReportDetails.tsx +++ b/ui/shared/solidityscanReport/SolidityscanReportDetails.tsx @@ -1,10 +1,10 @@ import { Box, Flex, Text, Grid, useColorModeValue, chakra } from '@chakra-ui/react'; import React from 'react'; -import type { SolidityscanReport } from 'types/api/contract'; +import type { SolidityScanReportSeverityDistribution } from 'lib/solidityScan/schema'; type DistributionItem = { - id: keyof SolidityscanReport['scan_report']['scan_summary']['issue_severity_distribution']; + id: keyof SolidityScanReportSeverityDistribution; name: string; color: string; } @@ -19,29 +19,34 @@ const DISTRIBUTION_ITEMS: Array = [ ]; interface Props { - vulnerabilities: SolidityscanReport['scan_report']['scan_summary']['issue_severity_distribution']; + vulnerabilities: SolidityScanReportSeverityDistribution; vulnerabilitiesCount: number; } type ItemProps = { item: DistributionItem; - vulnerabilities: SolidityscanReport['scan_report']['scan_summary']['issue_severity_distribution']; + 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] } + 0 ? 'text' : yetAnotherGrayColor }>{ vulnerabilities[item.id] } - + ); 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..b05f5bf1ce --- /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/Option.tsx b/ui/shared/sort/Option.tsx new file mode 100644 index 0000000000..c24eaada0b --- /dev/null +++ b/ui/shared/sort/Option.tsx @@ -0,0 +1,47 @@ +import { + useRadio, + Box, + useColorModeValue, +} from '@chakra-ui/react'; +import type { useRadioGroup } from '@chakra-ui/react'; +import React from 'react'; + +import IconSvg from 'ui/shared/IconSvg'; + +export interface TOption { + id: Sort | undefined; + title: string; +} + +type OptionProps = ReturnType['getRadioProps']>; + +const Option = (props: OptionProps) => { + const { getInputProps, getRadioProps } = useRadio(props); + + const input = getInputProps(); + const checkbox = getRadioProps(); + const bgColorHover = useColorModeValue('blue.50', 'whiteAlpha.100'); + + return ( + + + + { props.children } + + { props.isChecked && } + + ); +}; + +export default Option; diff --git a/ui/shared/sort/Sort.tsx b/ui/shared/sort/Sort.tsx index 37b378abf4..76f22c9f86 100644 --- a/ui/shared/sort/Sort.tsx +++ b/ui/shared/sort/Sort.tsx @@ -1,58 +1,70 @@ import { - chakra, - Menu, - MenuButton, - MenuList, - MenuOptionGroup, - MenuItemOption, + PopoverTrigger, + PopoverContent, + PopoverBody, useDisclosure, + useRadioGroup, + chakra, } from '@chakra-ui/react'; import React from 'react'; -import SortButton from './SortButton'; +import useIsMobile from 'lib/hooks/useIsMobile'; +import Popover from 'ui/shared/chakra/Popover'; -export interface Option { - title: string; - id: Sort | undefined; -} +import SortButtonDesktop from './ButtonDesktop'; +import SortButtonMobile from './ButtonMobile'; +import Option from './Option'; +import type { TOption } from './Option'; interface Props { - options: Array>; - sort: Sort | undefined; - setSort: (value: Sort | undefined) => void; + name: string; + options: Array>; + defaultValue?: Sort; isLoading?: boolean; + onChange: (value: Sort | undefined) => void; } -const Sort = ({ sort, setSort, options, isLoading }: Props) => { - const { isOpen, onToggle } = useDisclosure(); +const Sort = ({ name, options, isLoading, onChange, defaultValue }: Props) => { + const isMobile = useIsMobile(false); + const { isOpen, onToggle, onClose } = useDisclosure(); + + const handleChange = (value: Sort) => { + onChange(value); + onClose(); + }; + + const { value, getRootProps, getRadioProps } = useRadioGroup({ + name, + defaultValue, + onChange: handleChange, + }); - const setSortingFromMenu = React.useCallback((val: string | Array) => { - const value = val as Sort | Array; - setSort(Array.isArray(value) ? value[0] : value); - }, [ setSort ]); + const root = getRootProps(); return ( - - - - - - - { options.map((option) => ( - - { option.title } - - )) } - - - + + + { isMobile ? ( + + ) : ( + + { options.find((option: TOption) => option.id === value || (!option.id && !value))?.title } + + ) } + + + + { options.map((option, index) => { + const radio = getRadioProps({ value: option.id }); + return ( + + ); + }) } + + + ); }; 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..a6cfcc4f21 100644 --- a/ui/shared/sort/getNextSortValue.ts +++ b/ui/shared/sort/getNextSortValue.ts @@ -3,8 +3,17 @@ export default function getNextSortValue { 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..efb07981ce 100644 --- a/ui/shared/sort/getSortValueFromQuery.ts +++ b/ui/shared/sort/getSortValueFromQuery.ts @@ -1,8 +1,8 @@ import type { Query } from 'nextjs-routes'; -import type { Option } from 'ui/shared/sort/Sort'; +import type { TOption } from 'ui/shared/sort/Option'; -export default function getSortValueFromQuery(query: Query, sortOptions: Array>) { +export default function getSortValueFromQuery(query: Query, sortOptions: Array>) { if (!query.sort || !query.order) { return undefined; } 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 index f95220bac9..156daff197 100644 --- a/ui/shared/stats/StatsWidget.tsx +++ b/ui/shared/stats/StatsWidget.tsx @@ -1,67 +1,130 @@ -import { Box, Flex, Text, Skeleton, useColorModeValue } from '@chakra-ui/react'; +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'; -type Props = { +export type Props = { + className?: string; label: string; - value: string; - hint?: 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 StatsWidget = ({ label, value, isLoading, hint, diff, diffPeriod = '24h', diffFormatted }: Props) => { - const bgColor = useColorModeValue('blue.50', 'blue.800'); +const Container = ({ href, children }: { href?: Route; children: 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 ( - - - - { label } - - - { value } - { diff && Number(diff) > 0 && ( - <> - - +{ diffFormatted || Number(diff).toLocaleString() } - - ({ diffPeriod }) - - ) } - - - { hint && ( - - - - ) } - + + + { 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 StatsWidget; +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..2555a4d6b1 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..c1ff32c324 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..48342ff201 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..84da508e51 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/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 fb9d59a97d..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/ValidatorStatus.tsx b/ui/shared/statusTag/ValidatorStatus.tsx deleted file mode 100644 index 4386b55af5..0000000000 --- a/ui/shared/statusTag/ValidatorStatus.tsx +++ /dev/null @@ -1,23 +0,0 @@ -import React from 'react'; - -import type { Validator } from 'types/api/validators'; - -import StatusTag from './StatusTag'; - -interface Props { - state: Validator['state']; - isLoading?: boolean; -} - -const ValidatorStatus = ({ state, isLoading }: Props) => { - switch (state) { - case 'active': - return ; - case 'probation': - return ; - case 'inactive': - return ; - } -}; - -export default React.memo(ValidatorStatus); 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..4bf53b2ce1 --- /dev/null +++ b/ui/shared/tagGroupSelect/TagGroupSelect.tsx @@ -0,0 +1,56 @@ +import { HStack, Tag } from '@chakra-ui/react'; +import React from 'react'; + +type Props = { + items: Array<{ id: T; title: string }>; +} & ( + { + value: T; + onChange: (value: T) => void; + isMulti?: false; + } | { + value: Array; + onChange: (value: Array) => void; + isMulti: true; + } +) + +const TagGroupSelect = ({ items, value, isMulti, onChange }: 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..4684f4fcdc 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..b6a14300fc 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..c96be42fd5 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..801e03bc5c 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..5011b6a77e 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..fcea676ba9 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 index 3f71f9f3e5..479bcee08b 100644 --- a/ui/shared/tx/interpretation/TxInterpretation.tsx +++ b/ui/shared/tx/interpretation/TxInterpretation.tsx @@ -2,6 +2,7 @@ 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, @@ -23,12 +24,15 @@ import { extractVariables, getStringChunks, fillStringVariables, checkSummary, N type Props = { summary?: TxInterpretationSummary; isLoading?: boolean; + addressDataMap?: Record; className?: string; } type NonStringTxInterpretationVariable = Exclude -const TxInterpretationElementByType = ({ variable }: { variable?: NonStringTxInterpretationVariable }) => { +const TxInterpretationElementByType = ( + { variable, addressDataMap }: { variable?: NonStringTxInterpretationVariable; addressDataMap?: Record }, +) => { const onAddressClick = React.useCallback(() => { mixpanel.logEvent(mixpanel.EventTypes.TX_INTERPRETATION_INTERACTION, { Type: 'Address click' }); }, []); @@ -51,7 +55,8 @@ const TxInterpretationElementByType = ({ variable }: { variable?: NonStringTxInt return ( { +const TxInterpretation = ({ summary, isLoading, addressDataMap, className }: Props) => { if (!summary) { return null; } @@ -142,7 +149,7 @@ const TxInterpretation = ({ summary, isLoading, className }: Props) => { return ( - + { chunks.map((chunk, index) => { return ( @@ -151,7 +158,12 @@ const TxInterpretation = ({ summary, isLoading, className }: Props) => { { index < variablesNames.length && ( variablesNames[index] === NATIVE_COIN_SYMBOL_VAR_NAME ? { currencyUnits.ether + ' ' } : - + ( + + ) ) } ); diff --git a/ui/shared/verificationSteps/VerificationStep.tsx b/ui/shared/verificationSteps/VerificationStep.tsx index 17a7c98632..0111928788 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/snippets/footer/Footer.pw.tsx b/ui/snippets/footer/Footer.pw.tsx index a3dc45f5c4..82d7c8ede4 100644 --- a/ui/snippets/footer/Footer.pw.tsx +++ b/ui/snippets/footer/Footer.pw.tsx @@ -1,55 +1,28 @@ -import { test as base, expect } from '@playwright/experimental-ct-react'; import React from 'react'; -import type { WalletProvider } from 'types/web3'; - -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, JSON.stringify(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 WalletProvider; - }); - - 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( - -