diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index d1cb0ba9dc..cc48cec34c 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -1,6 +1,6 @@ { "name": "blockscout dev", - "image": "mcr.microsoft.com/devcontainers/typescript-node:18", + "image": "mcr.microsoft.com/devcontainers/typescript-node:20", "forwardPorts": [ 3000 ], "customizations": { "vscode": { diff --git a/.env.example b/.env.example index ce97e7621c..e56bd6211b 100644 --- a/.env.example +++ b/.env.example @@ -4,5 +4,6 @@ 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 diff --git a/.github/workflows/checks.yml b/.github/workflows/checks.yml index eb0810c8f5..9496bb37e6 100644 --- a/.github/workflows/checks.yml +++ b/.github/workflows/checks.yml @@ -25,16 +25,16 @@ jobs: if: ${{ !contains(github.event.pull_request.labels.*.name, 'skip checks') && !(github.event.action == 'unlabeled' && github.event.label.name != 'skip checks') }} steps: - name: Checkout repo - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Setup node - uses: actions/setup-node@v3 + uses: actions/setup-node@v4 with: - node-version: 18 + node-version: 20.11.0 cache: 'yarn' - name: Cache node_modules - uses: actions/cache@v3 + uses: actions/cache@v4 id: cache-node-modules with: path: | @@ -57,16 +57,16 @@ jobs: needs: [ code_quality ] steps: - name: Checkout repo - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Setup node - uses: actions/setup-node@v3 + uses: actions/setup-node@v4 with: - node-version: 18 + node-version: 20.11.0 cache: 'yarn' - name: Cache node_modules - uses: actions/cache@v3 + uses: actions/cache@v4 id: cache-node-modules with: path: | @@ -94,16 +94,18 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout repo - uses: actions/checkout@v3 + uses: actions/checkout@v4 + with: + fetch-depth: 0 - name: Setup node - uses: actions/setup-node@v3 + uses: actions/setup-node@v4 with: - node-version: 18 + node-version: 20.11.0 cache: 'yarn' - name: Cache node_modules - uses: actions/cache@v3 + uses: actions/cache@v4 id: cache-node-modules with: path: | @@ -115,14 +117,61 @@ jobs: run: yarn --frozen-lockfile --ignore-optional - name: Run Jest - run: yarn test:jest + run: yarn test:jest ${{ github.event_name == 'pull_request' && '--changedSince=origin/main' || '' }} --passWithNoTests + + pw_affected_tests: + name: Resolve affected Playwright tests + runs-on: ubuntu-latest + needs: [ code_quality, envs_validation ] + if: github.event_name == 'pull_request' + steps: + - name: Checkout repo + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Setup node + uses: actions/setup-node@v4 + with: + node-version: 20.11.0 + cache: 'yarn' + + - name: Cache node_modules + uses: actions/cache@v4 + id: cache-node-modules + with: + path: | + node_modules + key: node_modules-${{ runner.os }}-${{ hashFiles('yarn.lock') }} + + - name: Install dependencies + if: steps.cache-node-modules.outputs.cache-hit != 'true' + run: yarn --frozen-lockfile --ignore-optional + + - name: Install script dependencies + run: cd ./deploy/tools/affected-tests && yarn --frozen-lockfile + + - name: Run script + run: yarn test:pw:detect-affected + + - name: Upload result file + uses: actions/upload-artifact@v4 + with: + name: playwright-affected-tests + path: ./playwright/affected-tests.txt + retention-days: 3 pw_tests: name: 'Playwright tests / Project: ${{ matrix.project }}' - needs: [ code_quality, envs_validation ] + needs: [ code_quality, envs_validation, pw_affected_tests ] + if: | + always() && + needs.code_quality.result == 'success' && + needs.envs_validation.result == 'success' && + (needs.pw_affected_tests.result == 'success' || needs.pw_affected_tests.result == 'skipped') runs-on: ubuntu-latest container: - image: mcr.microsoft.com/playwright:v1.35.1-focal + image: mcr.microsoft.com/playwright:v1.41.1-focal strategy: fail-fast: false @@ -134,18 +183,18 @@ jobs: run: apt-get update && apt-get install git-lfs - name: Checkout repo - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: lfs: 'true' - name: Setup node - uses: actions/setup-node@v3 + uses: actions/setup-node@v4 with: - node-version: 18 + node-version: 20.11.0 cache: 'yarn' - name: Cache node_modules - uses: actions/cache@v3 + uses: actions/cache@v4 id: cache-node-modules with: path: | @@ -156,15 +205,23 @@ jobs: if: steps.cache-node-modules.outputs.cache-hit != 'true' run: yarn --frozen-lockfile --ignore-optional + - name: Download affected tests list + if: ${{ needs.pw_affected_tests.result == 'success' }} + uses: actions/download-artifact@v4 + continue-on-error: true + with: + name: playwright-affected-tests + path: ./playwright + - name: Run PlayWright - run: yarn test:pw:ci + run: yarn test:pw:ci --affected=${{ github.event_name == 'pull_request' }} --pass-with-no-tests env: HOME: /root PW_PROJECT: ${{ matrix.project }} - name: Upload test results if: always() - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: playwright-report-${{ matrix.project }} path: playwright-report diff --git a/.github/workflows/deploy-main.yml b/.github/workflows/deploy-main.yml index d8cf2b79b3..087934c334 100644 --- a/.github/workflows/deploy-main.yml +++ b/.github/workflows/deploy-main.yml @@ -4,6 +4,7 @@ on: push: branches: - main + workflow_dispatch: concurrency: group: ${{ github.workflow }}-${{ github.ref }} diff --git a/.github/workflows/label-issues-in-release.yml b/.github/workflows/label-issues-in-release.yml index 4bc9861b0d..b95fd45616 100644 --- a/.github/workflows/label-issues-in-release.yml +++ b/.github/workflows/label-issues-in-release.yml @@ -59,7 +59,7 @@ jobs: steps: - name: Getting tags of the two latestest releases id: tags - uses: actions/github-script@v6 + uses: actions/github-script@v7 env: TAG: ${{ inputs.tag }} with: @@ -108,7 +108,7 @@ jobs: - name: Looking for commits between two releases id: commits - uses: actions/github-script@v6 + uses: actions/github-script@v7 env: PREVIOUS_TAG: ${{ steps.tags.outputs.previous }} LATEST_TAG: ${{ steps.tags.outputs.latest }} @@ -137,7 +137,7 @@ jobs: - name: Looking for issues linked to commits id: linked_issues - uses: actions/github-script@v6 + uses: actions/github-script@v7 env: COMMITS: ${{ steps.commits.outputs.result }} with: @@ -225,7 +225,7 @@ jobs: - name: Creating label id: label_creating - uses: actions/github-script@v6 + uses: actions/github-script@v7 env: LABEL_NAME: ${{ inputs.label_name }} LABEL_COLOR: ${{ inputs.label_color }} @@ -253,7 +253,7 @@ jobs: - name: Adding label to issues id: labeling_issues - uses: actions/github-script@v6 + uses: actions/github-script@v7 env: LABEL_NAME: ${{ inputs.label_name }} ISSUES: ${{ steps.linked_issues.outputs.result }} diff --git a/.github/workflows/pre-release.yml b/.github/workflows/pre-release.yml index e0cb8684b8..c45cb9249d 100644 --- a/.github/workflows/pre-release.yml +++ b/.github/workflows/pre-release.yml @@ -32,7 +32,7 @@ jobs: steps: - name: Determine if it is the initial version of the pre-release id: is_initial - uses: actions/github-script@v6 + uses: actions/github-script@v7 env: TAG: ${{ github.ref_name }} with: diff --git a/.github/workflows/project-management.yml b/.github/workflows/project-management.yml index 791515b07d..ce53245267 100644 --- a/.github/workflows/project-management.yml +++ b/.github/workflows/project-management.yml @@ -37,7 +37,7 @@ jobs: steps: - name: Fetching issues linked to pull request id: linked_issues - uses: actions/github-script@v6 + uses: actions/github-script@v7 env: PULL_REQUEST_NUMBER: ${{ github.event.pull_request.number }} with: diff --git a/.github/workflows/publish-image.yml b/.github/workflows/publish-image.yml index b1fdabff64..ba0382b078 100644 --- a/.github/workflows/publish-image.yml +++ b/.github/workflows/publish-image.yml @@ -7,12 +7,22 @@ on: description: Image tags required: false type: string + platforms: + description: Image platforms (you can specify multiple platforms separated by comma) + required: false + type: string + default: linux/amd64 workflow_call: inputs: tags: description: Image tags required: false type: string + platforms: + description: Image platforms (you can specify multiple platforms separated by comma) + required: false + type: string + default: linux/amd64 jobs: run: @@ -20,15 +30,15 @@ jobs: runs-on: ubuntu-latest steps: - name: Check out the repo - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v2 + uses: docker/setup-buildx-action@v3 # Will automatically make nice tags, see the table here https://github.com/docker/metadata-action#basic - name: Docker meta id: meta - uses: docker/metadata-action@v4 + uses: docker/metadata-action@v5 with: images: ghcr.io/blockscout/frontend @@ -51,13 +61,14 @@ jobs: echo "ref_name: $REF_NAME" - name: Build and push - uses: docker/build-push-action@v3 + uses: docker/build-push-action@v5 with: context: . file: ./Dockerfile push: true cache-from: type=gha tags: ${{ inputs.tags || steps.meta.outputs.tags }} + platforms: ${{ inputs.platforms }} labels: ${{ steps.meta.outputs.labels }} build-args: | GIT_COMMIT_SHA=${{ env.SHORT_SHA }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index bbb3f07448..fb0c863799 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -12,7 +12,7 @@ jobs: steps: - name: Remove label id: tags - uses: actions/github-script@v6 + uses: actions/github-script@v7 env: LABEL_NAME: pre-release with: @@ -78,6 +78,8 @@ jobs: name: Publish Docker image uses: './.github/workflows/publish-image.yml' secrets: inherit + with: + platforms: linux/amd64,linux/arm64/v8 upload_source_maps: name: Upload source maps to Sentry diff --git a/.github/workflows/update-project-cards.yml b/.github/workflows/update-project-cards.yml index 98ffbed2bb..b071ba61da 100644 --- a/.github/workflows/update-project-cards.yml +++ b/.github/workflows/update-project-cards.yml @@ -48,7 +48,7 @@ jobs: steps: - name: Getting project info id: project_info - uses: actions/github-script@v6 + uses: actions/github-script@v7 env: PROJECT_NAME: ${{ inputs.project_name }} FIELD_NAME: ${{ inputs.field_name }} @@ -131,7 +131,7 @@ jobs: - name: Getting project items that linked to the issues id: items - uses: actions/github-script@v6 + uses: actions/github-script@v7 env: ISSUES: ${{ inputs.issues }} with: @@ -193,7 +193,7 @@ jobs: - name: Updating field value of the project items id: updating_items - uses: actions/github-script@v6 + uses: actions/github-script@v7 env: ITEMS: ${{ steps.items.outputs.result }} PROJECT_ID: ${{ steps.project_info.outputs.id }} diff --git a/.github/workflows/upload-source-maps.yml b/.github/workflows/upload-source-maps.yml index 0c284672a1..55b18067fe 100644 --- a/.github/workflows/upload-source-maps.yml +++ b/.github/workflows/upload-source-maps.yml @@ -16,16 +16,16 @@ jobs: if: ${{ github.ref_type == 'tag' }} steps: - name: Checkout repo - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Setup node - uses: actions/setup-node@v3 + uses: actions/setup-node@v4 with: - node-version: 18 + node-version: 20.11.0 cache: 'yarn' - name: Cache node_modules - uses: actions/cache@v3 + uses: actions/cache@v4 id: cache-node-modules with: path: | diff --git a/.gitignore b/.gitignore index f677256817..4ee6adeee0 100644 --- a/.gitignore +++ b/.gitignore @@ -54,5 +54,6 @@ yarn-error.log* /playwright/.cache/ /playwright/.browser/ /playwright/envs.js +/playwright/affected-tests.txt **.dec** \ No newline at end of file diff --git a/.nvmrc b/.nvmrc index 3c032078a4..8b0beab16a 100644 --- a/.nvmrc +++ b/.nvmrc @@ -1 +1 @@ -18 +20.11.0 diff --git a/.vscode/tasks.json b/.vscode/tasks.json index cd1be4e2b7..ad9f96bd7f 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -155,6 +155,27 @@ "instanceLimit": 1 } }, + { + "type": "shell", + "command": "yarn test:pw:detect-affected", + "problemMatcher": [], + "label": "pw: detect affected", + "detail": "detect PW tests affected by changes in current branch", + "presentation": { + "reveal": "always", + "panel": "shared", + "focus": true, + "close": false, + "revealProblems": "onProblem", + }, + "icon": { + "color": "terminal.ansiBlue", + "id": "diff" + }, + "runOptions": { + "instanceLimit": 1 + }, + }, // JEST TESTS { @@ -305,6 +326,7 @@ "options": [ "", "--update-snapshots", + "--update-snapshots --affected", "--ui", ], "default": "" @@ -316,13 +338,16 @@ "options": [ "main", "main.L2", - "poa_core", + "eth", "eth_goerli", "sepolia", - "eth", - "rootstock", "polygon", + "zkevm", + "zksync", "gnosis", + "rootstock", + "stability", + "poa_core", "localhost", ], "default": "main" diff --git a/Dockerfile b/Dockerfile index 465491b3c4..f38d68ebaa 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,7 +1,7 @@ # ***************************** # *** STAGE 1: Dependencies *** # ***************************** -FROM node:18-alpine AS deps +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 @@ -10,7 +10,7 @@ RUN apk add --no-cache libc6-compat WORKDIR /app COPY package.json yarn.lock ./ RUN apk add git -RUN yarn --frozen-lockfile --ignore-optional +RUN yarn --frozen-lockfile ### FEATURE REPORTER @@ -30,7 +30,7 @@ RUN yarn --frozen-lockfile # ***************************** # ****** STAGE 2: Build ******* # ***************************** -FROM node:18-alpine AS builder +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 @@ -78,7 +78,7 @@ RUN cd ./deploy/tools/envs-validator && yarn build # ******* STAGE 3: Run ******** # ***************************** # Production image, copy all the files and run next -FROM node:18-alpine AS runner +FROM node:20.11.0-alpine AS runner RUN apk add --no-cache --upgrade bash curl jq unzip ### APP diff --git a/configs/app/app.ts b/configs/app/app.ts index f1b87b601d..b0403fc673 100644 --- a/configs/app/app.ts +++ b/configs/app/app.ts @@ -9,7 +9,7 @@ const baseUrl = [ appHost, appPort && ':' + appPort, ].filter(Boolean).join(''); -const isDev = getEnvValue('NODE_ENV') === 'development'; +const isDev = getEnvValue('NEXT_PUBLIC_APP_ENV') === 'development'; const app = Object.freeze({ isDev, diff --git a/configs/app/chain.ts b/configs/app/chain.ts index b65fbeb384..1ec156e4e0 100644 --- a/configs/app/chain.ts +++ b/configs/app/chain.ts @@ -8,6 +8,7 @@ const chain = Object.freeze({ shortName: getEnvValue('NEXT_PUBLIC_NETWORK_SHORT_NAME'), currency: { name: getEnvValue('NEXT_PUBLIC_NETWORK_CURRENCY_NAME'), + weiName: getEnvValue('NEXT_PUBLIC_NETWORK_CURRENCY_WEI_NAME'), symbol: getEnvValue('NEXT_PUBLIC_NETWORK_CURRENCY_SYMBOL'), decimals: Number(getEnvValue('NEXT_PUBLIC_NETWORK_CURRENCY_DECIMALS')) || DEFAULT_CURRENCY_DECIMALS, }, diff --git a/configs/app/features/adsBanner.ts b/configs/app/features/adsBanner.ts index 0785ab160f..f9860d1afc 100644 --- a/configs/app/features/adsBanner.ts +++ b/configs/app/features/adsBanner.ts @@ -1,7 +1,7 @@ import type { Feature } from './types'; import type { AdButlerConfig } from 'types/client/adButlerConfig'; import { SUPPORTED_AD_BANNER_PROVIDERS } from 'types/client/adProviders'; -import type { AdBannerProviders } from 'types/client/adProviders'; +import type { AdBannerProviders, AdBannerAdditionalProviders } from 'types/client/adProviders'; import { getEnvValue, parseEnvJson } from '../utils'; @@ -11,6 +11,8 @@ const provider: AdBannerProviders = (() => { return envValue && SUPPORTED_AD_BANNER_PROVIDERS.includes(envValue) ? envValue : 'slise'; })(); +const additionalProvider = getEnvValue('NEXT_PUBLIC_AD_BANNER_ADDITIONAL_PROVIDER') as AdBannerAdditionalProviders; + const title = 'Banner ads'; type AdsBannerFeaturePayload = { @@ -23,6 +25,15 @@ type AdsBannerFeaturePayload = { mobile: AdButlerConfig; }; }; +} | { + provider: Exclude; + additionalProvider: 'adbutler'; + adButler: { + config: { + desktop: AdButlerConfig; + mobile: AdButlerConfig; + }; + }; } const config: Feature = (() => { @@ -44,6 +55,24 @@ const config: Feature = (() => { }); } } else if (provider !== 'none') { + + if (additionalProvider === 'adbutler') { + const desktopConfig = parseEnvJson(getEnvValue('NEXT_PUBLIC_AD_ADBUTLER_CONFIG_DESKTOP')); + const mobileConfig = parseEnvJson(getEnvValue('NEXT_PUBLIC_AD_ADBUTLER_CONFIG_MOBILE')); + + return Object.freeze({ + title, + isEnabled: true, + provider, + additionalProvider, + adButler: { + config: { + desktop: desktopConfig, + mobile: mobileConfig, + }, + }, + }); + } return Object.freeze({ title, isEnabled: true, diff --git a/configs/app/features/adsText.ts b/configs/app/features/adsText.ts index b44fa59e85..82946f984f 100644 --- a/configs/app/features/adsText.ts +++ b/configs/app/features/adsText.ts @@ -1,12 +1,12 @@ import type { Feature } from './types'; -import { SUPPORTED_AD_BANNER_PROVIDERS } from 'types/client/adProviders'; +import { SUPPORTED_AD_TEXT_PROVIDERS } from 'types/client/adProviders'; import type { AdTextProviders } from 'types/client/adProviders'; import { getEnvValue } from '../utils'; const provider: AdTextProviders = (() => { const envValue = getEnvValue('NEXT_PUBLIC_AD_TEXT_PROVIDER') as AdTextProviders; - return envValue && SUPPORTED_AD_BANNER_PROVIDERS.includes(envValue) ? envValue : 'coinzilla'; + return envValue && SUPPORTED_AD_TEXT_PROVIDERS.includes(envValue) ? envValue : 'coinzilla'; })(); const title = 'Text ads'; diff --git a/configs/app/features/dataAvailability.ts b/configs/app/features/dataAvailability.ts new file mode 100644 index 0000000000..add9e5fec5 --- /dev/null +++ b/configs/app/features/dataAvailability.ts @@ -0,0 +1,21 @@ +import type { Feature } from './types'; + +import { getEnvValue } from '../utils'; + +const title = 'Data availability'; + +const config: Feature<{ isEnabled: true }> = (() => { + if (getEnvValue('NEXT_PUBLIC_DATA_AVAILABILITY_ENABLED') === 'true') { + return Object.freeze({ + title, + isEnabled: true, + }); + } + + return Object.freeze({ + title, + isEnabled: false, + }); +})(); + +export default config; diff --git a/configs/app/features/gasTracker.ts b/configs/app/features/gasTracker.ts new file mode 100644 index 0000000000..c20242e602 --- /dev/null +++ b/configs/app/features/gasTracker.ts @@ -0,0 +1,37 @@ +import type { Feature } from './types'; +import { GAS_UNITS } from 'types/client/gasTracker'; +import type { GasUnit } from 'types/client/gasTracker'; + +import { getEnvValue, parseEnvJson } from '../utils'; + +const isDisabled = getEnvValue('NEXT_PUBLIC_GAS_TRACKER_ENABLED') === 'false'; + +const units = ((): Array => { + const envValue = getEnvValue('NEXT_PUBLIC_GAS_TRACKER_UNITS'); + if (!envValue) { + return [ 'usd', 'gwei' ]; + } + + const units = parseEnvJson>(envValue)?.filter((type) => GAS_UNITS.includes(type)) || []; + + return units; +})(); + +const title = 'Gas tracker'; + +const config: Feature<{ units: Array }> = (() => { + if (!isDisabled && units.length > 0) { + return Object.freeze({ + title, + isEnabled: true, + units, + }); + } + + return Object.freeze({ + title, + isEnabled: false, + }); +})(); + +export default config; diff --git a/configs/app/features/growthBook.ts b/configs/app/features/growthBook.ts new file mode 100644 index 0000000000..af672c5ac9 --- /dev/null +++ b/configs/app/features/growthBook.ts @@ -0,0 +1,24 @@ +import type { Feature } from './types'; + +import { getEnvValue } from '../utils'; + +const clientKey = getEnvValue('NEXT_PUBLIC_GROWTH_BOOK_CLIENT_KEY'); + +const title = 'GrowthBook feature flagging and A/B testing'; + +const config: Feature<{ clientKey: string }> = (() => { + if (clientKey) { + return Object.freeze({ + title, + isEnabled: true, + clientKey, + }); + } + + return Object.freeze({ + title, + isEnabled: false, + }); +})(); + +export default config; diff --git a/configs/app/features/index.ts b/configs/app/features/index.ts index 0879fb17d5..fbe1a6a949 100644 --- a/configs/app/features/index.ts +++ b/configs/app/features/index.ts @@ -6,18 +6,25 @@ export { default as beaconChain } from './beaconChain'; 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 gasTracker } from './gasTracker'; 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 nameService } from './nameService'; export { default as restApiDocs } from './restApiDocs'; -export { default as optimisticRollup } from './optimisticRollup'; +export { default as rollup } from './rollup'; export { default as safe } from './safe'; export { default as sentry } from './sentry'; export { default as sol2uml } from './sol2uml'; export { default as stats } from './stats'; export { default as suave } from './suave'; +export { default as swapButton } from './swapButton'; export { default as txInterpretation } from './txInterpretation'; -export { default as web3Wallet } from './web3Wallet'; +export { default as userOps } from './userOps'; +export { default as validators } from './validators'; export { default as verifiedTokens } from './verifiedTokens'; -export { default as zkEvmRollup } from './zkEvmRollup'; +export { default as web3Wallet } from './web3Wallet'; diff --git a/configs/app/features/marketplace.ts b/configs/app/features/marketplace.ts index 5ea76352f2..b136ce0d09 100644 --- a/configs/app/features/marketplace.ts +++ b/configs/app/features/marketplace.ts @@ -4,23 +4,51 @@ import chain from '../chain'; import { getEnvValue, getExternalAssetFilePath } from '../utils'; // config file will be downloaded at run-time and saved in the public folder +const enabled = getEnvValue('NEXT_PUBLIC_MARKETPLACE_ENABLED'); const configUrl = getExternalAssetFilePath('NEXT_PUBLIC_MARKETPLACE_CONFIG_URL'); const submitFormUrl = getEnvValue('NEXT_PUBLIC_MARKETPLACE_SUBMIT_FORM'); +const suggestIdeasFormUrl = getEnvValue('NEXT_PUBLIC_MARKETPLACE_SUGGEST_IDEAS_FORM'); +const categoriesUrl = getExternalAssetFilePath('NEXT_PUBLIC_MARKETPLACE_CATEGORIES_URL'); +const adminServiceApiHost = getEnvValue('NEXT_PUBLIC_ADMIN_SERVICE_API_HOST'); +const securityReportsUrl = getExternalAssetFilePath('NEXT_PUBLIC_MARKETPLACE_SECURITY_REPORTS_URL'); const title = 'Marketplace'; -const config: Feature<{ configUrl: string; submitFormUrl: string }> = (() => { - if ( - chain.rpcUrl && - configUrl && - submitFormUrl - ) { - return Object.freeze({ - title, - isEnabled: true, - configUrl, - submitFormUrl, - }); +const config: Feature<( + { configUrl: string } | + { api: { endpoint: string; basePath: string } } +) & { + submitFormUrl: string; + categoriesUrl: string | undefined; + suggestIdeasFormUrl: string | undefined; + securityReportsUrl: string | undefined; +} +> = (() => { + if (enabled === 'true' && chain.rpcUrl && submitFormUrl) { + if (configUrl) { + return Object.freeze({ + title, + isEnabled: true, + configUrl, + submitFormUrl, + categoriesUrl, + suggestIdeasFormUrl, + securityReportsUrl, + }); + } else if (adminServiceApiHost) { + return Object.freeze({ + title, + isEnabled: true, + submitFormUrl, + categoriesUrl, + suggestIdeasFormUrl, + securityReportsUrl, + api: { + endpoint: adminServiceApiHost, + basePath: '', + }, + }); + } } return Object.freeze({ diff --git a/configs/app/features/metasuites.ts b/configs/app/features/metasuites.ts new file mode 100644 index 0000000000..333e7d5a8a --- /dev/null +++ b/configs/app/features/metasuites.ts @@ -0,0 +1,21 @@ +import type { Feature } from './types'; + +import { getEnvValue } from '../utils'; + +const title = 'MetaSuites extension'; + +const config: Feature<{ isEnabled: true }> = (() => { + if (getEnvValue('NEXT_PUBLIC_METASUITES_ENABLED') === 'true') { + return Object.freeze({ + title, + isEnabled: true, + }); + } + + return Object.freeze({ + title, + isEnabled: false, + }); +})(); + +export default config; diff --git a/configs/app/features/nameService.ts b/configs/app/features/nameService.ts new file mode 100644 index 0000000000..3af536bbe2 --- /dev/null +++ b/configs/app/features/nameService.ts @@ -0,0 +1,27 @@ +import type { Feature } from './types'; + +import { getEnvValue } from '../utils'; + +const apiHost = getEnvValue('NEXT_PUBLIC_NAME_SERVICE_API_HOST'); + +const title = 'Name service integration'; + +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/optimisticRollup.ts b/configs/app/features/optimisticRollup.ts deleted file mode 100644 index 6b4f646d59..0000000000 --- a/configs/app/features/optimisticRollup.ts +++ /dev/null @@ -1,30 +0,0 @@ -import type { Feature } from './types'; - -import { getEnvValue } from '../utils'; - -const title = 'Rollup (L2) chain'; - -const config: Feature<{ L1BaseUrl: string; withdrawalUrl: string }> = (() => { - const L1BaseUrl = getEnvValue('NEXT_PUBLIC_L1_BASE_URL'); - const withdrawalUrl = getEnvValue('NEXT_PUBLIC_OPTIMISTIC_L2_WITHDRAWAL_URL'); - - if ( - getEnvValue('NEXT_PUBLIC_IS_OPTIMISTIC_L2_NETWORK') === 'true' && - L1BaseUrl && - withdrawalUrl - ) { - return Object.freeze({ - title, - isEnabled: true, - L1BaseUrl, - withdrawalUrl, - }); - } - - return Object.freeze({ - title, - isEnabled: false, - }); -})(); - -export default config; diff --git a/configs/app/features/rollup.ts b/configs/app/features/rollup.ts new file mode 100644 index 0000000000..2560734097 --- /dev/null +++ b/configs/app/features/rollup.ts @@ -0,0 +1,35 @@ +import type { Feature } from './types'; +import type { RollupType } from 'types/client/rollup'; +import { ROLLUP_TYPES } from 'types/client/rollup'; + +import { getEnvValue } from '../utils'; + +const type = (() => { + const envValue = getEnvValue('NEXT_PUBLIC_ROLLUP_TYPE'); + return ROLLUP_TYPES.find((type) => type === envValue); +})(); + +const L1BaseUrl = getEnvValue('NEXT_PUBLIC_ROLLUP_L1_BASE_URL'); +const L2WithdrawalUrl = getEnvValue('NEXT_PUBLIC_ROLLUP_L2_WITHDRAWAL_URL'); + +const title = 'Rollup (L2) chain'; + +const config: Feature<{ type: RollupType; L1BaseUrl: string; L2WithdrawalUrl?: string }> = (() => { + + if (type && L1BaseUrl) { + return Object.freeze({ + title, + isEnabled: true, + type, + L1BaseUrl, + L2WithdrawalUrl, + }); + } + + return Object.freeze({ + title, + isEnabled: false, + }); +})(); + +export default config; diff --git a/configs/app/features/safe.ts b/configs/app/features/safe.ts index bed8bf14a6..b2762a78da 100644 --- a/configs/app/features/safe.ts +++ b/configs/app/features/safe.ts @@ -1,35 +1,14 @@ import type { Feature } from './types'; -import chain from '../chain'; - -// https://docs.safe.global/safe-core-api/available-services -const SAFE_API_MAP: Record = { - '42161': 'https://safe-transaction-arbitrum.safe.global', - '1313161554': 'https://safe-transaction-aurora.safe.global', - '43114': 'https://safe-transaction-avalanche.safe.global', - '8453': 'https://safe-transaction-base.safe.global', - '84531': 'https://safe-transaction-base-testnet.safe.global', - '56': 'https://safe-transaction-bsc.safe.global', - '42220': 'https://safe-transaction-celo.safe.global', - '1': 'https://safe-transaction-mainnet.safe.global', - '100': 'https://safe-transaction-gnosis-chain.safe.global', - '5': 'https://safe-transaction-goerli.safe.global', - '10': 'https://safe-transaction-optimism.safe.global', - '137': 'https://safe-transaction-polygon.safe.global', -}; +import { getEnvValue } from '../utils'; function getApiUrl(): string | undefined { - if (!chain.id) { - return; - } - - const apiHost = SAFE_API_MAP[chain.id]; - - if (!apiHost) { + try { + const envValue = getEnvValue('NEXT_PUBLIC_SAFE_TX_SERVICE_URL'); + return new URL('/api/v1/safes', envValue).toString(); + } catch (error) { return; } - - return `${ apiHost }/api/v1/safes/`; } const title = 'Safe address tags'; diff --git a/configs/app/features/swapButton.ts b/configs/app/features/swapButton.ts new file mode 100644 index 0000000000..415a4af2c9 --- /dev/null +++ b/configs/app/features/swapButton.ts @@ -0,0 +1,42 @@ +import type { Feature } from './types'; + +import { getEnvValue } from '../utils'; +import marketplace from './marketplace'; + +const value = getEnvValue('NEXT_PUBLIC_SWAP_BUTTON_URL'); + +const title = 'Swap button'; + +function isValidUrl(string: string) { + try { + new URL(string); + return true; + } catch (error) { + return false; + } +} + +const config: Feature<{ dappId: string } | { url: string }> = (() => { + if (value) { + if (isValidUrl(value)) { + return Object.freeze({ + title, + isEnabled: true, + url: value, + }); + } else if (marketplace.isEnabled) { + return Object.freeze({ + title, + isEnabled: true, + dappId: value, + }); + } + } + + return Object.freeze({ + title, + isEnabled: false, + }); +})(); + +export default config; diff --git a/configs/app/features/userOps.ts b/configs/app/features/userOps.ts new file mode 100644 index 0000000000..0e127f62f6 --- /dev/null +++ b/configs/app/features/userOps.ts @@ -0,0 +1,21 @@ +import type { Feature } from './types'; + +import { getEnvValue } from '../utils'; + +const title = 'User operations'; + +const config: Feature<{ isEnabled: true }> = (() => { + if (getEnvValue('NEXT_PUBLIC_HAS_USER_OPS') === 'true') { + return Object.freeze({ + title, + isEnabled: true, + }); + } + + return Object.freeze({ + title, + isEnabled: false, + }); +})(); + +export default config; diff --git a/configs/app/features/validators.ts b/configs/app/features/validators.ts new file mode 100644 index 0000000000..668501e28c --- /dev/null +++ b/configs/app/features/validators.ts @@ -0,0 +1,29 @@ +import type { Feature } from './types'; +import { VALIDATORS_CHAIN_TYPE } from 'types/client/validators'; +import type { ValidatorsChainType } from 'types/client/validators'; + +import { getEnvValue } from '../utils'; + +const chainType = ((): ValidatorsChainType | undefined => { + const envValue = getEnvValue('NEXT_PUBLIC_VALIDATORS_CHAIN_TYPE') as ValidatorsChainType | undefined; + return envValue && VALIDATORS_CHAIN_TYPE.includes(envValue) ? envValue : undefined; +})(); + +const title = 'Validators list'; + +const config: Feature<{ chainType: ValidatorsChainType }> = (() => { + if (chainType) { + return Object.freeze({ + title, + isEnabled: true, + chainType, + }); + } + + return Object.freeze({ + title, + isEnabled: false, + }); +})(); + +export default config; diff --git a/configs/app/features/zkEvmRollup.ts b/configs/app/features/zkEvmRollup.ts deleted file mode 100644 index 7fda84b09f..0000000000 --- a/configs/app/features/zkEvmRollup.ts +++ /dev/null @@ -1,25 +0,0 @@ -import type { Feature } from './types'; - -import { getEnvValue } from '../utils'; - -const title = 'ZkEVM rollup (L2) chain'; - -const config: Feature<{ L1BaseUrl: string; withdrawalUrl?: string }> = (() => { - const L1BaseUrl = getEnvValue('NEXT_PUBLIC_L1_BASE_URL'); - const isZkEvm = getEnvValue('NEXT_PUBLIC_IS_ZKEVM_L2_NETWORK') === 'true'; - - if (isZkEvm && L1BaseUrl) { - return Object.freeze({ - title, - isEnabled: true, - L1BaseUrl, - }); - } - - return Object.freeze({ - title, - isEnabled: false, - }); -})(); - -export default config; diff --git a/configs/app/ui.ts b/configs/app/ui.ts index 9542917fe9..7f6832bf1f 100644 --- a/configs/app/ui.ts +++ b/configs/app/ui.ts @@ -1,3 +1,4 @@ +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 type { NetworkExplorer } from 'types/networks'; @@ -48,7 +49,6 @@ const UI = Object.freeze({ background: getEnvValue('NEXT_PUBLIC_HOMEPAGE_PLATE_BACKGROUND') || HOMEPAGE_PLATE_BACKGROUND_DEFAULT, textColor: getEnvValue('NEXT_PUBLIC_HOMEPAGE_PLATE_TEXT_COLOR') || 'white', }, - showGasTracker: getEnvValue('NEXT_PUBLIC_HOMEPAGE_SHOW_GAS_TRACKER') === 'false' ? false : true, showAvgBlockTime: getEnvValue('NEXT_PUBLIC_HOMEPAGE_SHOW_AVG_BLOCK_TIME') === 'false' ? false : true, }, views, @@ -66,6 +66,10 @@ const UI = Object.freeze({ explorers: { items: parseEnvJson>(getEnvValue('NEXT_PUBLIC_NETWORK_EXPLORERS')) || [], }, + ides: { + items: parseEnvJson>(getEnvValue('NEXT_PUBLIC_CONTRACT_CODE_IDES')) || [], + }, + hasContractAuditReports: getEnvValue('NEXT_PUBLIC_HAS_CONTRACT_AUDIT_REPORTS') === 'true' ? true : false, }); export default UI; diff --git a/configs/envs/.env.eth b/configs/envs/.env.eth index 75ef364321..2715974d4b 100644 --- a/configs/envs/.env.eth +++ b/configs/envs/.env.eth @@ -29,7 +29,7 @@ NEXT_PUBLIC_FEATURED_NETWORKS=https://raw.githubusercontent.com/blockscout/front ##views NEXT_PUBLIC_VIEWS_NFT_MARKETPLACES=[{'name':'OpenSea','collection_url':'https://opensea.io/assets/ethereum/{hash}','instance_url':'https://opensea.io/assets/ethereum/{hash}/{id}','logo_url':'https://opensea.io/static/images/logos/opensea-logo.svg'},{'name':'LooksRare','collection_url':'https://looksrare.org/collections/{hash}','instance_url':'https://looksrare.org/collections/{hash}/{id}','logo_url':'https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/nft-marketplace-logos/looks-rare.png'}] ## misc -NEXT_PUBLIC_NETWORK_EXPLORERS=[{'title':'Etherscan','baseUrl':'https://etherscan.io/','paths':{'tx':'/tx','address':'/address','token':'/token','block':'/block'}}] +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 @@ -38,11 +38,20 @@ NEXT_PUBLIC_HAS_BEACON_CHAIN=true NEXT_PUBLIC_IS_ACCOUNT_SUPPORTED=true NEXT_PUBLIC_AUTH_URL=http://localhost:3000 NEXT_PUBLIC_LOGOUT_URL=https://blockscoutcom.us.auth0.com/v2/logout -NEXT_PUBLIC_STATS_API_HOST=https://stats-eth-main.k8s.blockscout.com +NEXT_PUBLIC_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_MARKETPLACE_CATEGORIES_URL=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/marketplace-categories/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_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 diff --git a/configs/envs/.env.eth_goerli b/configs/envs/.env.eth_goerli index da1fac4d97..997d31c125 100644 --- a/configs/envs/.env.eth_goerli +++ b/configs/envs/.env.eth_goerli @@ -33,6 +33,7 @@ NEXT_PUBLIC_NETWORK_ICON=https://raw.githubusercontent.com/blockscout/frontend-c NEXT_PUBLIC_VIEWS_NFT_MARKETPLACES=[{'name':'LooksRare','collection_url':'https://goerli.looksrare.org/collections/{hash}','instance_url':'https://goerli.looksrare.org/collections/{hash}/{id}','logo_url':'https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/nft-marketplace-logos/looks-rare.png'}] ## misc NEXT_PUBLIC_NETWORK_EXPLORERS=[{'title':'Bitquery','baseUrl':'https://explorer.bitquery.io/','paths':{'tx':'/goerli/tx','address':'/goerli/address','token':'/goerli/token','block':'/goerli/block'}},{'title':'Etherscan','baseUrl':'https://goerli.etherscan.io/','paths':{'tx':'/tx','address':'/address','token':'/token','block':'/block'}}] +NEXT_PUBLIC_HAS_CONTRACT_AUDIT_REPORTS=true # app features NEXT_PUBLIC_APP_ENV=development @@ -46,8 +47,13 @@ NEXT_PUBLIC_STATS_API_HOST=https://stats-goerli.k8s-dev.blockscout.com NEXT_PUBLIC_VISUALIZE_API_HOST=https://visualizer.services.blockscout.com NEXT_PUBLIC_CONTRACT_INFO_API_HOST=https://contracts-info.services.blockscout.com NEXT_PUBLIC_ADMIN_SERVICE_API_HOST=https://admin-rs.services.blockscout.com +NEXT_PUBLIC_NAME_SERVICE_API_HOST=https://bens-rs-test.k8s-dev.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_CONTRACT_CODE_IDES=[{'title':'Remix IDE','url':'https://remix.blockscout.com/?address={hash}&blockscout=eth-goerli.blockscout.com','icon_url':'https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/ide-icons/remix.png'}] +NEXT_PUBLIC_TRANSACTION_INTERPRETATION_PROVIDER=blockscout #meta -NEXT_PUBLIC_OG_IMAGE_URL=https://github.com/blockscout/frontend-configs/blob/main/configs/og-images/eth-goerli.png?raw=true +NEXT_PUBLIC_OG_IMAGE_URL=https://github.com/blockscout/frontend-configs/blob/main/configs/og-images/eth-goerli.png?raw=true \ No newline at end of file diff --git a/configs/envs/.env.jest b/configs/envs/.env.jest index 7a14a10b01..e1f80c7e75 100644 --- a/configs/envs/.env.jest +++ b/configs/envs/.env.jest @@ -25,7 +25,6 @@ NEXT_PUBLIC_API_BASE_PATH=/ ## homepage NEXT_PUBLIC_HOMEPAGE_CHARTS=['daily_txs','coin_price','market_cap'] NEXT_PUBLIC_HOMEPAGE_SHOW_AVG_BLOCK_TIME=true -NEXT_PUBLIC_HOMEPAGE_SHOW_GAS_TRACKER=true NEXT_PUBLIC_HOMEPAGE_PLATE_BACKGROUND= ## sidebar NEXT_PUBLIC_NETWORK_LOGO= @@ -44,8 +43,6 @@ NEXT_PUBLIC_APP_INSTANCE=jest NEXT_PUBLIC_APP_ENV=testing NEXT_PUBLIC_MARKETPLACE_CONFIG_URL=https://localhost:3000/marketplace-config.json NEXT_PUBLIC_MARKETPLACE_SUBMIT_FORM=https://localhost:3000/marketplace-submit-form -NEXT_PUBLIC_IS_OPTIMISTIC_L2_NETWORK=false -NEXT_PUBLIC_IS_ZKEVM_L2_NETWORK=false NEXT_PUBLIC_IS_ACCOUNT_SUPPORTED=true NEXT_PUBLIC_AUTH_URL=http://localhost:3100 NEXT_PUBLIC_LOGOUT_URL=https://blockscoutcom.us.auth0.com/v2/logout diff --git a/configs/envs/.env.main b/configs/envs/.env.main index 749f1ec8fd..48eee58328 100644 --- a/configs/envs/.env.main +++ b/configs/envs/.env.main @@ -45,9 +45,14 @@ NEXT_PUBLIC_GRAPHIQL_TRANSACTION=0xf7d4972356e6ae44ae948d0cf19ef2beaf0e574c18099 NEXT_PUBLIC_IS_ACCOUNT_SUPPORTED=true NEXT_PUBLIC_AUTH_URL=http://localhost:3000 NEXT_PUBLIC_LOGOUT_URL=https://blockscoutcom.us.auth0.com/v2/logout +NEXT_PUBLIC_MARKETPLACE_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_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 diff --git a/configs/envs/.env.main.L2 b/configs/envs/.env.main.L2 index fb9359be95..e410b29b8e 100644 --- a/configs/envs/.env.main.L2 +++ b/configs/envs/.env.main.L2 @@ -47,6 +47,6 @@ NEXT_PUBLIC_LOGOUT_URL=https://blockscoutcom.us.auth0.com/v2/logout NEXT_PUBLIC_MARKETPLACE_CONFIG_URL=https://raw.githubusercontent.com/blockscout/frontend-configs/dev/configs/marketplace/base-goerli.json NEXT_PUBLIC_MARKETPLACE_SUBMIT_FORM=https://airtable.com/shrqUAcjgGJ4jU88C NEXT_PUBLIC_STATS_API_HOST=https://stats-optimism-goerli.k8s-dev.blockscout.com -NEXT_PUBLIC_IS_OPTIMISTIC_L2_NETWORK=true -NEXT_PUBLIC_L1_BASE_URL=https://blockscout-main.k8s-dev.blockscout.com -NEXT_PUBLIC_OPTIMISTIC_L2_WITHDRAWAL_URL=https://app.optimism.io/bridge/withdraw +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_goerli b/configs/envs/.env.optimism_goerli new file mode 100644 index 0000000000..c20a941ab9 --- /dev/null +++ b/configs/envs/.env.optimism_goerli @@ -0,0 +1,48 @@ +# Set of ENVs for zkevm (dev only) +# 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='OP Goerli' +NEXT_PUBLIC_NETWORK_SHORT_NAME='OP Goerli' +NEXT_PUBLIC_NETWORK_ID=420 +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.optimism.io + +# api configuration +NEXT_PUBLIC_API_HOST=optimism-goerli.blockscout.com +NEXT_PUBLIC_API_PORT=80 +NEXT_PUBLIC_API_PROTOCOL=http +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 +## 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_ROLLUP_TYPE='optimistic' +NEXT_PUBLIC_ROLLUP_L2_WITHDRAWAL_URL=https://app.optimism.io/bridge/withdraw +NEXT_PUBLIC_ROLLUP_L1_BASE_URL=https://eth-goerli.blockscout.com/ \ No newline at end of file diff --git a/configs/envs/.env.pw b/configs/envs/.env.pw index cdd8edce29..223abf5f32 100644 --- a/configs/envs/.env.pw +++ b/configs/envs/.env.pw @@ -17,6 +17,7 @@ NEXT_PUBLIC_IS_TESTNET=true NEXT_PUBLIC_NETWORK_VERIFICATION_TYPE=validation # api configuration +NEXT_PUBLIC_API_PROTOCOL=http NEXT_PUBLIC_API_HOST=localhost NEXT_PUBLIC_API_PORT=3003 NEXT_PUBLIC_API_BASE_PATH=/ @@ -25,7 +26,6 @@ NEXT_PUBLIC_API_BASE_PATH=/ ## homepage NEXT_PUBLIC_HOMEPAGE_CHARTS=['daily_txs','coin_price','market_cap'] NEXT_PUBLIC_HOMEPAGE_SHOW_AVG_BLOCK_TIME=true -NEXT_PUBLIC_HOMEPAGE_SHOW_GAS_TRACKER=true ## sidebar ## footer NEXT_PUBLIC_GIT_TAG=v1.0.11 @@ -33,21 +33,23 @@ NEXT_PUBLIC_GIT_TAG=v1.0.11 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':'http://localhost:3000/nft-marketplace-logo.png'},{'name':'LooksRare','collection_url':'https://looksrare.org/collections/{hash}','instance_url':'https://looksrare.org/collections/{hash}/{id}','logo_url':'http://localhost:3000/nft-marketplace-logo.png'}] ## misc NEXT_PUBLIC_NETWORK_EXPLORERS=[{'title':'Bitquery','baseUrl':'https://explorer.bitquery.io/','paths':{'tx':'/goerli/tx','address':'/goerli/address','token':'/goerli/token','block':'/goerli/block'}},{'title':'Etherscan','baseUrl':'https://goerli.etherscan.io/','paths':{'tx':'/tx','address':'/address','token':'/token','block':'/block'}}] +NEXT_PUBLIC_CONTRACT_CODE_IDES=[{'title':'Remix IDE','url':'https://remix.blockscout.com/%23address={hash}&blockscout=eth-goerli.blockscout.com'}] NEXT_PUBLIC_MAINTENANCE_ALERT_MESSAGE= # app features NEXT_PUBLIC_APP_ENV=testing NEXT_PUBLIC_APP_INSTANCE=pw +NEXT_PUBLIC_MARKETPLACE_ENABLED=true NEXT_PUBLIC_MARKETPLACE_CONFIG_URL=https://localhost:3000/marketplace-config.json NEXT_PUBLIC_MARKETPLACE_SUBMIT_FORM=https://localhost:3000/marketplace-submit-form -NEXT_PUBLIC_IS_OPTIMISTIC_L2_NETWORK=false -NEXT_PUBLIC_IS_ZKEVM_L2_NETWORK=false +NEXT_PUBLIC_MARKETPLACE_SUGGEST_IDEAS_FORM=https://localhost:3000/marketplace-suggest-ideas-form NEXT_PUBLIC_AD_BANNER_PROVIDER=slise NEXT_PUBLIC_IS_ACCOUNT_SUPPORTED=true NEXT_PUBLIC_AUTH_URL=http://localhost:3100 NEXT_PUBLIC_LOGOUT_URL=https://blockscoutcom.us.auth0.com/v2/logout NEXT_PUBLIC_AUTH0_CLIENT_ID=xxx -NEXT_PUBLIC_STATS_API_HOST=https://localhost:3004 -NEXT_PUBLIC_CONTRACT_INFO_API_HOST=https://localhost:3005 -NEXT_PUBLIC_ADMIN_SERVICE_API_HOST=https://localhost:3006 +NEXT_PUBLIC_STATS_API_HOST=http://localhost:3004 +NEXT_PUBLIC_CONTRACT_INFO_API_HOST=http://localhost:3005 +NEXT_PUBLIC_ADMIN_SERVICE_API_HOST=http://localhost:3006 NEXT_PUBLIC_RE_CAPTCHA_APP_SITE_KEY=xxx +NEXT_PUBLIC_WALLET_CONNECT_PROJECT_ID=xxx \ No newline at end of file diff --git a/configs/envs/.env.sepolia b/configs/envs/.env.sepolia index b54adc0775..72a98f40a4 100644 --- a/configs/envs/.env.sepolia +++ b/configs/envs/.env.sepolia @@ -55,6 +55,9 @@ 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 b/configs/envs/.env.stability new file mode 100644 index 0000000000..a395322118 --- /dev/null +++ b/configs/envs/.env.stability @@ -0,0 +1,60 @@ +# 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=Stability Testnet +NEXT_PUBLIC_NETWORK_SHORT_NAME=Stability +NEXT_PUBLIC_NETWORK_ID=20180427 +NEXT_PUBLIC_NETWORK_CURRENCY_NAME=FREE +NEXT_PUBLIC_NETWORK_CURRENCY_SYMBOL=FREE +NEXT_PUBLIC_NETWORK_CURRENCY_DECIMALS=18 +NEXT_PUBLIC_NETWORK_VERIFICATION_TYPE=validation +NEXT_PUBLIC_NETWORK_RPC_URL=https://free.testnet.stabilityprotocol.com +NEXT_PUBLIC_IS_TESTNET=true + +# api configuration +NEXT_PUBLIC_API_HOST=stability-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)" +NEXT_PUBLIC_HOMEPAGE_PLATE_BACKGROUND="rgba(46, 51, 81, 1)" +NEXT_PUBLIC_HOMEPAGE_PLATE_TEXT_COLOR="rgba(122, 235, 246, 1)" +## 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/stability.svg +NEXT_PUBLIC_NETWORK_LOGO_DARK=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/network-logos/stability-dark.svg +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 +## footer +## views +NEXT_PUBLIC_VIEWS_ADDRESS_HIDDEN_VIEWS="['top_accounts']" +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_VIEWS_BLOCK_HIDDEN_FIELDS="['burnt_fees','total_reward']" +## misc + +# app features +NEXT_PUBLIC_APP_ENV=development +NEXT_PUBLIC_HAS_BEACON_CHAIN=false +NEXT_PUBLIC_STATS_API_HOST=https://stats-stability-testnet.k8s.blockscout.com +NEXT_PUBLIC_VISUALIZE_API_HOST=https://visualizer.services.blockscout.com +NEXT_PUBLIC_ADMIN_SERVICE_API_HOST=https://admin-rs.services.blockscout.com +NEXT_PUBLIC_CONTRACT_CODE_IDES="[{'title':'Remix IDE','url':'https://remix.blockscout.com/?address={hash}&blockscout={domain}','icon_url':'https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/ide-icons/remix.png'}]" +NEXT_PUBLIC_MARKETPLACE_ENABLED=true +NEXT_PUBLIC_MARKETPLACE_CATEGORIES_URL=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/marketplace-categories/default.json +NEXT_PUBLIC_MARKETPLACE_SUBMIT_FORM=https://airtable.com/shrqUAcjgGJ4jU88C +NEXT_PUBLIC_MARKETPLACE_SUGGEST_IDEAS_FORM=https://airtable.com/appiy5yijZpMMSKjT/pag3t82DUCyhGRZZO/form +NEXT_PUBLIC_CONTRACT_INFO_API_HOST=https://contracts-info.services.blockscout.com/ +NEXT_PUBLIC_GAS_TRACKER_ENABLED=false +NEXT_PUBLIC_VALIDATORS_CHAIN_TYPE='stability' + +#meta +NEXT_PUBLIC_OG_IMAGE_URL=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/og-images/stability.png diff --git a/configs/envs/.env.zkevm b/configs/envs/.env.zkevm index 91d7172d3f..9d1ef2487a 100644 --- a/configs/envs/.env.zkevm +++ b/configs/envs/.env.zkevm @@ -14,10 +14,10 @@ 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 +NEXT_PUBLIC_NETWORK_RPC_URL=https://zkevm-rpc.com # api configuration -NEXT_PUBLIC_API_HOST=65.109.173.70 +NEXT_PUBLIC_API_HOST=zkevm.blockscout.com NEXT_PUBLIC_API_PORT=80 NEXT_PUBLIC_API_PROTOCOL=http NEXT_PUBLIC_API_BASE_PATH=/ @@ -26,7 +26,11 @@ NEXT_PUBLIC_API_BASE_PATH=/ ## homepage NEXT_PUBLIC_HOMEPAGE_CHARTS=['daily_txs'] ## sidebar -NEXT_PUBLIC_FEATURED_NETWORKS=https://raw.githubusercontent.com/blockscout/frontend-configs/dev/configs/featured-networks/eth.json +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_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'}}] @@ -34,7 +38,6 @@ NEXT_PUBLIC_NETWORK_EXPLORERS=[{'title':'Etherscan','baseUrl':'https://etherscan NEXT_PUBLIC_APP_INSTANCE=local NEXT_PUBLIC_APP_ENV=development NEXT_PUBLIC_GRAPHIQL_TRANSACTION=0xf7d4972356e6ae44ae948d0cf19ef2beaf0e574c180997e969a2837da15e349d -NEXT_PUBLIC_HAS_BEACON_CHAIN=true NEXT_PUBLIC_IS_ACCOUNT_SUPPORTED=true # NEXT_PUBLIC_AUTH_URL=http://localhost:3000 NEXT_PUBLIC_API_WEBSOCKET_PROTOCOL=ws @@ -44,5 +47,5 @@ NEXT_PUBLIC_VISUALIZE_API_HOST=https://visualizer.services.blockscout.com NEXT_PUBLIC_CONTRACT_INFO_API_HOST=https://contracts-info.services.blockscout.com NEXT_PUBLIC_ADMIN_SERVICE_API_HOST=https://admin-rs.services.blockscout.com # rollup -NEXT_PUBLIC_IS_ZKEVM_L2_NETWORK=true -NEXT_PUBLIC_L1_BASE_URL=http://65.109.173.70:81 +NEXT_PUBLIC_ROLLUP_TYPE=zkEvm +NEXT_PUBLIC_ROLLUP_L1_BASE_URL=https://polygon.blockscout.com diff --git a/configs/envs/.env.zksync b/configs/envs/.env.zksync new file mode 100644 index 0000000000..6693f0ca4e --- /dev/null +++ b/configs/envs/.env.zksync @@ -0,0 +1,59 @@ +# Set of ENVs for zkSync (dev only) +# https://zksync.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=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 + +# api configuration +NEXT_PUBLIC_API_HOST=zksync.blockscout.com +NEXT_PUBLIC_API_PORT=80 +NEXT_PUBLIC_API_PROTOCOL=http +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_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_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_ROLLUP_TYPE=zkSync +NEXT_PUBLIC_ROLLUP_L1_BASE_URL=https://eth.blockscout.com diff --git a/deploy/scripts/download_assets.sh b/deploy/scripts/download_assets.sh index 93ef31f491..cc9f4a09a7 100755 --- a/deploy/scripts/download_assets.sh +++ b/deploy/scripts/download_assets.sh @@ -15,6 +15,8 @@ ASSETS_DIR="$1" # Define a list of environment variables containing URLs of external assets ASSETS_ENVS=( "NEXT_PUBLIC_MARKETPLACE_CONFIG_URL" + "NEXT_PUBLIC_MARKETPLACE_CATEGORIES_URL" + "NEXT_PUBLIC_MARKETPLACE_SECURITY_REPORTS_URL" "NEXT_PUBLIC_FEATURED_NETWORKS" "NEXT_PUBLIC_FOOTER_LINKS" "NEXT_PUBLIC_NETWORK_LOGO" @@ -36,7 +38,7 @@ get_target_filename() { local name_prefix="${env_var#NEXT_PUBLIC_}" local name_suffix="${name_prefix%_URL}" local name_lc="$(echo "$name_suffix" | tr '[:upper:]' '[:lower:]')" - + # Check if the URL starts with "file://" if [[ "$url" == file://* ]]; then # Extract the local file path @@ -54,7 +56,7 @@ get_target_filename() { # Convert the extension to lowercase extension=$(echo "$extension" | tr '[:upper:]' '[:lower:]') - + # Construct the custom file name echo "$name_lc.$extension" } diff --git a/deploy/tools/affected-tests/.gitignore b/deploy/tools/affected-tests/.gitignore new file mode 100644 index 0000000000..30bc162798 --- /dev/null +++ b/deploy/tools/affected-tests/.gitignore @@ -0,0 +1 @@ +/node_modules \ No newline at end of file diff --git a/deploy/tools/affected-tests/index.js b/deploy/tools/affected-tests/index.js new file mode 100644 index 0000000000..f2fc450eb6 --- /dev/null +++ b/deploy/tools/affected-tests/index.js @@ -0,0 +1,208 @@ +/* eslint-disable no-console */ +const { execSync } = require('child_process'); +const dependencyTree = require('dependency-tree'); +const fs = require('fs'); +const path = require('path'); + +const ROOT_DIR = path.resolve(__dirname, '../../../'); + +const TARGET_FILE = path.resolve(ROOT_DIR, './playwright/affected-tests.txt'); + +const NON_EXISTENT_DEPS = []; + +const DIRECTORIES_WITH_TESTS = [ + path.resolve(ROOT_DIR, './ui'), +]; +const VISITED = {}; + +function getAllPwFilesInDirectory(directory) { + const files = fs.readdirSync(directory, { recursive: true }); + return files + .filter((file) => file.endsWith('.pw.tsx')) + .map((file) => path.join(directory, file)); +} + +function getFileDeps(filename, changedNpmModules) { + return dependencyTree.toList({ + filename, + directory: ROOT_DIR, + filter: (path) => { + if (path.indexOf('node_modules') === -1) { + return true; + } + + if (changedNpmModules.some((module) => path.startsWith(module))) { + return true; + } + + return false; + }, + tsConfig: path.resolve(ROOT_DIR, './tsconfig.json'), + nonExistent: NON_EXISTENT_DEPS, + visited: VISITED, + }); +} + +async function getChangedFiles() { + const command = process.env.CI ? + `git diff --name-only origin/${ process.env.GITHUB_BASE_REF } ${ process.env.GITHUB_SHA } -- ${ ROOT_DIR }` : + `git diff --name-only main $(git branch --show-current) -- ${ ROOT_DIR }`; + + console.log('Executing command: ', command); + const files = execSync(command) + .toString() + .trim() + .split('\n') + .filter(Boolean); + + return files.map((file) => path.join(ROOT_DIR, file)); +} + +function checkChangesInChakraTheme(changedFiles) { + const themeDir = path.resolve(ROOT_DIR, './theme'); + return changedFiles.some((file) => file.startsWith(themeDir)); +} + +function checkChangesInSvgSprite(changedFiles) { + const iconDir = path.resolve(ROOT_DIR, './icons'); + const areIconsChanged = changedFiles.some((file) => file.startsWith(iconDir)); + + if (!areIconsChanged) { + return false; + } + + const svgNamesFile = path.resolve(ROOT_DIR, './public/icons/name.d.ts'); + const areSvgNamesChanged = changedFiles.some((file) => file === svgNamesFile); + + if (!areSvgNamesChanged) { + // If only the icons have changed and not the names in the SVG file, we will need to run all tests. + // This is because we cannot correctly identify the test files that depend on these changes. + return true; + } + + // If the icon names have changed, then there should be changes in the components that use them. + // Otherwise, typescript would complain about that. + return false; +} + +function createTargetFile(content) { + fs.writeFileSync(TARGET_FILE, content); +} + +function getPackageJsonUpdatedProps(packageJsonFile) { + const command = process.env.CI ? + `git diff --unified=0 origin/${ process.env.GITHUB_BASE_REF } ${ process.env.GITHUB_SHA } -- ${ packageJsonFile }` : + `git diff --unified=0 main $(git branch --show-current) -- ${ packageJsonFile }`; + + console.log('Executing command: ', command); + const changedLines = execSync(command) + .toString() + .trim() + .split('\n') + .filter(Boolean) + .filter((line) => line.startsWith('+ ') || line.startsWith('- ')); + + const changedProps = [ ...new Set( + changedLines + .map((line) => line.replaceAll(' ', '').replaceAll('+', '').replaceAll('-', '')) + .map((line) => line.split(':')[0].replaceAll('"', '')), + ) ]; + + return changedProps; +} + +function getUpdatedNpmModules(changedFiles) { + const packageJsonFile = path.resolve(ROOT_DIR, './package.json'); + + if (!changedFiles.includes(packageJsonFile)) { + return []; + } + + try { + const packageJsonContent = JSON.parse(fs.readFileSync(packageJsonFile, 'utf-8')); + const usedNpmModules = [ + ...Object.keys(packageJsonContent.dependencies || {}), + ...Object.keys(packageJsonContent.devDependencies || {}), + ]; + const updatedProps = getPackageJsonUpdatedProps(packageJsonFile); + + return updatedProps.filter((prop) => usedNpmModules.includes(prop)); + } catch (error) {} +} + +async function run() { + // NOTES: + // - The absence of TARGET_FILE implies that all tests should be run. + // - The empty TARGET_FILE implies that no tests should be run. + + const start = Date.now(); + + fs.unlink(TARGET_FILE, () => {}); + + const changedFiles = await getChangedFiles(); + + if (!changedFiles.length) { + createTargetFile(''); + console.log('No changed files found. Exiting...'); + return; + } + + console.log('Changed files in the branch: ', changedFiles); + + if (checkChangesInChakraTheme(changedFiles)) { + console.log('Changes in Chakra theme detected. It is advisable to run all test suites. Exiting...'); + return; + } + + if (checkChangesInSvgSprite(changedFiles)) { + console.log('There are some changes in the SVG sprite that cannot be linked to a specific component. It is advisable to run all test suites. Exiting...'); + return; + } + + let changedNpmModules = getUpdatedNpmModules(changedFiles); + + if (!changedNpmModules) { + console.log('Some error occurred while detecting changed NPM modules. It is advisable to run all test suites. Exiting...'); + return; + } + + console.log('Changed NPM modules in the branch: ', changedNpmModules); + + changedNpmModules = [ + ...changedNpmModules, + ...changedNpmModules.map((module) => `@types/${ module }`), // there are some deps that are resolved to .d.ts files + ].map((module) => path.resolve(ROOT_DIR, `./node_modules/${ module }`)); + + const allTestFiles = DIRECTORIES_WITH_TESTS.reduce((acc, dir) => { + return acc.concat(getAllPwFilesInDirectory(dir)); + }, []); + + const isDepChanged = (dep) => changedFiles.includes(dep) || changedNpmModules.some((module) => dep.startsWith(module)); + + const testFilesToRun = allTestFiles + .map((file) => ({ file, deps: getFileDeps(file, changedNpmModules) })) + .filter(({ deps }) => deps.some(isDepChanged)); + const testFileNamesToRun = testFilesToRun.map(({ file }) => path.relative(ROOT_DIR, file)); + + if (!testFileNamesToRun.length) { + createTargetFile(''); + console.log('No tests to run. Exiting...'); + return; + } + + createTargetFile(testFileNamesToRun.join('\n')); + + const end = Date.now(); + + const testFilesToRunWithFilteredDeps = testFilesToRun.map(({ file, deps }) => ({ + file, + deps: deps.filter(isDepChanged), + })); + + console.log('Total time: ', ((end - start) / 1_000).toLocaleString()); + console.log('Total test to run: ', testFileNamesToRun.length); + console.log('Tests to run with changed deps: ', testFilesToRunWithFilteredDeps); + console.log('Non existent deps: ', NON_EXISTENT_DEPS); +} + +run(); diff --git a/deploy/tools/affected-tests/package.json b/deploy/tools/affected-tests/package.json new file mode 100644 index 0000000000..bfba5734fa --- /dev/null +++ b/deploy/tools/affected-tests/package.json @@ -0,0 +1,10 @@ +{ + "name": "affected-tests", + "version": "1.0.0", + "main": "index.js", + "author": "Vasilii (tom) Goriunov ", + "license": "MIT", + "dependencies": { + "dependency-tree": "10.0.9" + } +} diff --git a/deploy/tools/affected-tests/yarn.lock b/deploy/tools/affected-tests/yarn.lock new file mode 100644 index 0000000000..e160e0a2c6 --- /dev/null +++ b/deploy/tools/affected-tests/yarn.lock @@ -0,0 +1,716 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +"@babel/parser@^7.21.8": + version "7.23.9" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.23.9.tgz#7b903b6149b0f8fa7ad564af646c4c38a77fc44b" + integrity sha512-9tcKgqKbs3xGJ+NtKF2ndOBBLVwPjl1SHxPQkd36r3Dlirw3xWUeGaTbqr7uGZcTaxkVNwc+03SVP7aCdWrTlA== + +"@dependents/detective-less@^4.1.0": + version "4.1.0" + resolved "https://registry.yarnpkg.com/@dependents/detective-less/-/detective-less-4.1.0.tgz#4a979ee7a6a79eb33602862d6a1263e30f98002e" + integrity sha512-KrkT6qO5NxqNfy68sBl6CTSoJ4SNDIS5iQArkibhlbGU4LaDukZ3q2HIkh8aUKDio6o4itU4xDR7t82Y2eP1Bg== + dependencies: + gonzales-pe "^4.3.0" + node-source-walk "^6.0.1" + +"@nodelib/fs.scandir@2.1.5": + version "2.1.5" + resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz#7619c2eb21b25483f6d167548b4cfd5a7488c3d5" + integrity sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g== + dependencies: + "@nodelib/fs.stat" "2.0.5" + run-parallel "^1.1.9" + +"@nodelib/fs.stat@2.0.5", "@nodelib/fs.stat@^2.0.2": + version "2.0.5" + resolved "https://registry.yarnpkg.com/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz#5bd262af94e9d25bd1e71b05deed44876a222e8b" + integrity sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A== + +"@nodelib/fs.walk@^1.2.3": + version "1.2.8" + resolved "https://registry.yarnpkg.com/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz#e95737e8bb6746ddedf69c556953494f196fe69a" + integrity sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg== + dependencies: + "@nodelib/fs.scandir" "2.1.5" + fastq "^1.6.0" + +"@typescript-eslint/types@5.62.0": + version "5.62.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.62.0.tgz#258607e60effa309f067608931c3df6fed41fd2f" + integrity sha512-87NVngcbVXUahrRTqIK27gD2t5Cu1yuCXxbLcFtCzZGlfyVWWh8mLHkoxzjsB6DDNnvdL+fW8MiwPEJyGJQDgQ== + +"@typescript-eslint/typescript-estree@^5.59.5": + version "5.62.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-5.62.0.tgz#7d17794b77fabcac615d6a48fb143330d962eb9b" + integrity sha512-CmcQ6uY7b9y694lKdRB8FEel7JbU/40iSAPomu++SjLMntB+2Leay2LO6i8VnJk58MtE9/nQSFIH6jpyRWyYzA== + dependencies: + "@typescript-eslint/types" "5.62.0" + "@typescript-eslint/visitor-keys" "5.62.0" + debug "^4.3.4" + globby "^11.1.0" + is-glob "^4.0.3" + semver "^7.3.7" + tsutils "^3.21.0" + +"@typescript-eslint/visitor-keys@5.62.0": + version "5.62.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-5.62.0.tgz#2174011917ce582875954ffe2f6912d5931e353e" + integrity sha512-07ny+LHRzQXepkGg6w0mFY41fVUNBrL2Roj/++7V1txKugfjm/Ci/qSND03r2RhlJhJYMcTn9AhhSSqQp0Ysyw== + dependencies: + "@typescript-eslint/types" "5.62.0" + eslint-visitor-keys "^3.3.0" + +app-module-path@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/app-module-path/-/app-module-path-2.2.0.tgz#641aa55dfb7d6a6f0a8141c4b9c0aa50b6c24dd5" + integrity sha512-gkco+qxENJV+8vFcDiiFhuoSvRXb2a/QPqpSoWhVz829VNJfOTnELbBmPmNKFxf3xdNnw4DWCkzkDaavcX/1YQ== + +array-union@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/array-union/-/array-union-2.1.0.tgz#b798420adbeb1de828d84acd8a2e23d3efe85e8d" + integrity sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw== + +ast-module-types@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/ast-module-types/-/ast-module-types-5.0.0.tgz#32b2b05c56067ff38e95df66f11d6afd6c9ba16b" + integrity sha512-JvqziE0Wc0rXQfma0HZC/aY7URXHFuZV84fJRtP8u+lhp0JYCNd5wJzVXP45t0PH0Mej3ynlzvdyITYIu0G4LQ== + +balanced-match@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" + integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== + +brace-expansion@^1.1.7: + version "1.1.11" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" + integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA== + dependencies: + balanced-match "^1.0.0" + concat-map "0.0.1" + +braces@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107" + integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A== + dependencies: + fill-range "^7.0.1" + +color-name@^1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" + integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== + +commander@^10.0.1: + version "10.0.1" + resolved "https://registry.yarnpkg.com/commander/-/commander-10.0.1.tgz#881ee46b4f77d1c1dccc5823433aa39b022cbe06" + integrity sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug== + +concat-map@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" + integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg== + +debug@^4.3.4: + version "4.3.4" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865" + integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ== + dependencies: + ms "2.1.2" + +dependency-tree@10.0.9: + version "10.0.9" + resolved "https://registry.yarnpkg.com/dependency-tree/-/dependency-tree-10.0.9.tgz#0c6c0dbeb0c5ec2cf83bf755f30e9cb12e7b4ac7" + integrity sha512-dwc59FRIsht+HfnTVM0BCjJaEWxdq2YAvEDy4/Hn6CwS3CBWMtFnL3aZGAkQn3XCYxk/YcTDE4jX2Q7bFTwCjA== + dependencies: + commander "^10.0.1" + filing-cabinet "^4.1.6" + precinct "^11.0.5" + typescript "^5.0.4" + +detective-amd@^5.0.2: + version "5.0.2" + resolved "https://registry.yarnpkg.com/detective-amd/-/detective-amd-5.0.2.tgz#579900f301c160efe037a6377ec7e937434b2793" + integrity sha512-XFd/VEQ76HSpym80zxM68ieB77unNuoMwopU2TFT/ErUk5n4KvUTwW4beafAVUugrjV48l4BmmR0rh2MglBaiA== + dependencies: + ast-module-types "^5.0.0" + escodegen "^2.0.0" + get-amd-module-type "^5.0.1" + node-source-walk "^6.0.1" + +detective-cjs@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/detective-cjs/-/detective-cjs-5.0.1.tgz#836ad51c6de4863efc7c419ec243694f760ff8b2" + integrity sha512-6nTvAZtpomyz/2pmEmGX1sXNjaqgMplhQkskq2MLrar0ZAIkHMrDhLXkRiK2mvbu9wSWr0V5/IfiTrZqAQMrmQ== + dependencies: + ast-module-types "^5.0.0" + node-source-walk "^6.0.0" + +detective-es6@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/detective-es6/-/detective-es6-4.0.1.tgz#38d5d49a6d966e992ef8f2d9bffcfe861a58a88a" + integrity sha512-k3Z5tB4LQ8UVHkuMrFOlvb3GgFWdJ9NqAa2YLUU/jTaWJIm+JJnEh4PsMc+6dfT223Y8ACKOaC0qcj7diIhBKw== + dependencies: + node-source-walk "^6.0.1" + +detective-postcss@^6.1.3: + version "6.1.3" + resolved "https://registry.yarnpkg.com/detective-postcss/-/detective-postcss-6.1.3.tgz#51a2d4419327ad85d0af071c7054c79fafca7e73" + integrity sha512-7BRVvE5pPEvk2ukUWNQ+H2XOq43xENWbH0LcdCE14mwgTBEAMoAx+Fc1rdp76SmyZ4Sp48HlV7VedUnP6GA1Tw== + dependencies: + is-url "^1.2.4" + postcss "^8.4.23" + postcss-values-parser "^6.0.2" + +detective-sass@^5.0.3: + version "5.0.3" + resolved "https://registry.yarnpkg.com/detective-sass/-/detective-sass-5.0.3.tgz#63e54bc9b32f4bdbd9d5002308f9592a3d3a508f" + integrity sha512-YsYT2WuA8YIafp2RVF5CEfGhhyIVdPzlwQgxSjK+TUm3JoHP+Tcorbk3SfG0cNZ7D7+cYWa0ZBcvOaR0O8+LlA== + dependencies: + gonzales-pe "^4.3.0" + node-source-walk "^6.0.1" + +detective-scss@^4.0.3: + version "4.0.3" + resolved "https://registry.yarnpkg.com/detective-scss/-/detective-scss-4.0.3.tgz#79758baa0158f72bfc4481eb7e21cc3b5f1ea6eb" + integrity sha512-VYI6cHcD0fLokwqqPFFtDQhhSnlFWvU614J42eY6G0s8c+MBhi9QAWycLwIOGxlmD8I/XvGSOUV1kIDhJ70ZPg== + dependencies: + gonzales-pe "^4.3.0" + node-source-walk "^6.0.1" + +detective-stylus@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/detective-stylus/-/detective-stylus-4.0.0.tgz#ce97b6499becdc291de7b3c11df8c352c1eee46e" + integrity sha512-TfPotjhszKLgFBzBhTOxNHDsutIxx9GTWjrL5Wh7Qx/ydxKhwUrlSFeLIn+ZaHPF+h0siVBkAQSuy6CADyTxgQ== + +detective-typescript@^11.1.0: + version "11.1.0" + resolved "https://registry.yarnpkg.com/detective-typescript/-/detective-typescript-11.1.0.tgz#2deea5364cae1f0d9d3688bc596e662b049438cc" + integrity sha512-Mq8egjnW2NSCkzEb/Az15/JnBI/Ryyl6Po0Y+0mABTFvOS6DAyUGRZqz1nyhu4QJmWWe0zaGs/ITIBeWkvCkGw== + dependencies: + "@typescript-eslint/typescript-estree" "^5.59.5" + ast-module-types "^5.0.0" + node-source-walk "^6.0.1" + typescript "^5.0.4" + +dir-glob@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/dir-glob/-/dir-glob-3.0.1.tgz#56dbf73d992a4a93ba1584f4534063fd2e41717f" + integrity sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA== + dependencies: + path-type "^4.0.0" + +enhanced-resolve@^5.14.1: + version "5.15.0" + resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-5.15.0.tgz#1af946c7d93603eb88e9896cee4904dc012e9c35" + integrity sha512-LXYT42KJ7lpIKECr2mAXIaMldcNCh/7E0KBKOu4KSfkHmP+mZmSs+8V5gBAqisWBy0OO4W5Oyys0GO1Y8KtdKg== + dependencies: + graceful-fs "^4.2.4" + tapable "^2.2.0" + +escodegen@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/escodegen/-/escodegen-2.1.0.tgz#ba93bbb7a43986d29d6041f99f5262da773e2e17" + integrity sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w== + dependencies: + esprima "^4.0.1" + estraverse "^5.2.0" + esutils "^2.0.2" + optionalDependencies: + source-map "~0.6.1" + +eslint-visitor-keys@^3.3.0: + version "3.4.3" + resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz#0cd72fe8550e3c2eae156a96a4dddcd1c8ac5800" + integrity sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag== + +esprima@^4.0.0, esprima@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71" + integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A== + +estraverse@^5.2.0: + version "5.3.0" + resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-5.3.0.tgz#2eea5290702f26ab8fe5370370ff86c965d21123" + integrity sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA== + +esutils@^2.0.2: + version "2.0.3" + resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64" + integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g== + +fast-glob@^3.2.9: + version "3.3.2" + resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.3.2.tgz#a904501e57cfdd2ffcded45e99a54fef55e46129" + integrity sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow== + dependencies: + "@nodelib/fs.stat" "^2.0.2" + "@nodelib/fs.walk" "^1.2.3" + glob-parent "^5.1.2" + merge2 "^1.3.0" + micromatch "^4.0.4" + +fastq@^1.6.0: + version "1.17.0" + resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.17.0.tgz#ca5e1a90b5e68f97fc8b61330d5819b82f5fab03" + integrity sha512-zGygtijUMT7jnk3h26kUms3BkSDp4IfIKjmnqI2tvx6nuBfiF1UqOxbnLfzdv+apBy+53oaImsKtMw/xYbW+1w== + dependencies: + reusify "^1.0.4" + +filing-cabinet@^4.1.6: + version "4.1.6" + resolved "https://registry.yarnpkg.com/filing-cabinet/-/filing-cabinet-4.1.6.tgz#8d6d12cf3a84365bbd94e1cbf07d71c113420dd2" + integrity sha512-C+HZbuQTER36sKzGtUhrAPAoK6+/PrrUhYDBQEh3kBRdsyEhkLbp1ML8S0+6e6gCUrUlid+XmubxJrhvL2g/Zw== + dependencies: + app-module-path "^2.2.0" + commander "^10.0.1" + enhanced-resolve "^5.14.1" + is-relative-path "^1.0.2" + module-definition "^5.0.1" + module-lookup-amd "^8.0.5" + resolve "^1.22.3" + resolve-dependency-path "^3.0.2" + sass-lookup "^5.0.1" + stylus-lookup "^5.0.1" + tsconfig-paths "^4.2.0" + typescript "^5.0.4" + +fill-range@^7.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== + dependencies: + to-regex-range "^5.0.1" + +fs.realpath@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" + integrity sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw== + +function-bind@^1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.2.tgz#2c02d864d97f3ea6c8830c464cbd11ab6eab7a1c" + integrity sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA== + +get-amd-module-type@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/get-amd-module-type/-/get-amd-module-type-5.0.1.tgz#bef38ea3674e1aa1bda9c59c8b0da598582f73f2" + integrity sha512-jb65zDeHyDjFR1loOVk0HQGM5WNwoGB8aLWy3LKCieMKol0/ProHkhO2X1JxojuN10vbz1qNn09MJ7tNp7qMzw== + dependencies: + ast-module-types "^5.0.0" + node-source-walk "^6.0.1" + +get-own-enumerable-property-symbols@^3.0.0: + version "3.0.2" + resolved "https://registry.yarnpkg.com/get-own-enumerable-property-symbols/-/get-own-enumerable-property-symbols-3.0.2.tgz#b5fde77f22cbe35f390b4e089922c50bce6ef664" + integrity sha512-I0UBV/XOz1XkIJHEUDMZAbzCThU/H8DxmSfmdGcKPnVhu2VfFqr34jr9777IyaTYvxjedWhqVIilEDsCdP5G6g== + +glob-parent@^5.1.2: + version "5.1.2" + resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4" + integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow== + dependencies: + is-glob "^4.0.1" + +glob@^7.2.3: + version "7.2.3" + resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.3.tgz#b8df0fb802bbfa8e89bd1d938b4e16578ed44f2b" + integrity sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q== + dependencies: + fs.realpath "^1.0.0" + inflight "^1.0.4" + inherits "2" + minimatch "^3.1.1" + once "^1.3.0" + path-is-absolute "^1.0.0" + +globby@^11.1.0: + version "11.1.0" + resolved "https://registry.yarnpkg.com/globby/-/globby-11.1.0.tgz#bd4be98bb042f83d796f7e3811991fbe82a0d34b" + integrity sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g== + dependencies: + array-union "^2.1.0" + dir-glob "^3.0.1" + fast-glob "^3.2.9" + ignore "^5.2.0" + merge2 "^1.4.1" + slash "^3.0.0" + +gonzales-pe@^4.3.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/gonzales-pe/-/gonzales-pe-4.3.0.tgz#fe9dec5f3c557eead09ff868c65826be54d067b3" + integrity sha512-otgSPpUmdWJ43VXyiNgEYE4luzHCL2pz4wQ0OnDluC6Eg4Ko3Vexy/SrSynglw/eR+OhkzmqFCZa/OFa/RgAOQ== + dependencies: + minimist "^1.2.5" + +graceful-fs@^4.2.4: + version "4.2.11" + resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.11.tgz#4183e4e8bf08bb6e05bbb2f7d2e0c8f712ca40e3" + integrity sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ== + +hasown@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/hasown/-/hasown-2.0.0.tgz#f4c513d454a57b7c7e1650778de226b11700546c" + integrity sha512-vUptKVTpIJhcczKBbgnS+RtcuYMB8+oNzPK2/Hp3hanz8JmpATdmmgLgSaadVREkDm+e2giHwY3ZRkyjSIDDFA== + dependencies: + function-bind "^1.1.2" + +ignore@^5.2.0: + version "5.3.1" + resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.3.1.tgz#5073e554cd42c5b33b394375f538b8593e34d4ef" + integrity sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw== + +inflight@^1.0.4: + version "1.0.6" + resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" + integrity sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA== + dependencies: + once "^1.3.0" + wrappy "1" + +inherits@2: + version "2.0.4" + resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" + integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== + +is-core-module@^2.13.0: + version "2.13.1" + resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.13.1.tgz#ad0d7532c6fea9da1ebdc82742d74525c6273384" + integrity sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw== + dependencies: + hasown "^2.0.0" + +is-extglob@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" + integrity sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ== + +is-glob@^4.0.1, is-glob@^4.0.3: + version "4.0.3" + resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084" + integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg== + dependencies: + is-extglob "^2.1.1" + +is-number@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b" + integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== + +is-obj@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/is-obj/-/is-obj-1.0.1.tgz#3e4729ac1f5fde025cd7d83a896dab9f4f67db0f" + integrity sha512-l4RyHgRqGN4Y3+9JHVrNqO+tN0rV5My76uW5/nuO4K1b6vw5G8d/cmFjP9tRfEsdhZNt0IFdZuK/c2Vr4Nb+Qg== + +is-regexp@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-regexp/-/is-regexp-1.0.0.tgz#fd2d883545c46bac5a633e7b9a09e87fa2cb5069" + integrity sha512-7zjFAPO4/gwyQAAgRRmqeEeyIICSdmCqa3tsVHMdBzaXXRiqopZL4Cyghg/XulGWrtABTpbnYYzzIRffLkP4oA== + +is-relative-path@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/is-relative-path/-/is-relative-path-1.0.2.tgz#091b46a0d67c1ed0fe85f1f8cfdde006bb251d46" + integrity sha512-i1h+y50g+0hRbBD+dbnInl3JlJ702aar58snAeX+MxBAPvzXGej7sYoPMhlnykabt0ZzCJNBEyzMlekuQZN7fA== + +is-url-superb@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/is-url-superb/-/is-url-superb-4.0.0.tgz#b54d1d2499bb16792748ac967aa3ecb41a33a8c2" + integrity sha512-GI+WjezhPPcbM+tqE9LnmsY5qqjwHzTvjJ36wxYX5ujNXefSUJ/T17r5bqDV8yLhcgB59KTPNOc9O9cmHTPWsA== + +is-url@^1.2.4: + version "1.2.4" + resolved "https://registry.yarnpkg.com/is-url/-/is-url-1.2.4.tgz#04a4df46d28c4cff3d73d01ff06abeb318a1aa52" + integrity sha512-ITvGim8FhRiYe4IQ5uHSkj7pVaPDrCTkNd3yq3cV7iZAcJdHTUMPMEHcqSOy9xZ9qFenQCvi+2wjH9a1nXqHww== + +json5@^2.2.2: + version "2.2.3" + resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.3.tgz#78cd6f1a19bdc12b73db5ad0c61efd66c1e29283" + integrity sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg== + +lru-cache@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-6.0.0.tgz#6d6fe6570ebd96aaf90fcad1dafa3b2566db3a94" + integrity sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA== + dependencies: + yallist "^4.0.0" + +merge2@^1.3.0, merge2@^1.4.1: + version "1.4.1" + resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae" + integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg== + +micromatch@^4.0.4: + version "4.0.5" + resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.5.tgz#bc8999a7cbbf77cdc89f132f6e467051b49090c6" + integrity sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA== + dependencies: + braces "^3.0.2" + picomatch "^2.3.1" + +minimatch@^3.1.1: + version "3.1.2" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b" + integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw== + dependencies: + brace-expansion "^1.1.7" + +minimist@^1.2.5, minimist@^1.2.6: + version "1.2.8" + resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.8.tgz#c1a464e7693302e082a075cee0c057741ac4772c" + integrity sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA== + +module-definition@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/module-definition/-/module-definition-5.0.1.tgz#62d1194e5d5ea6176b7dc7730f818f466aefa32f" + integrity sha512-kvw3B4G19IXk+BOXnYq/D/VeO9qfHaapMeuS7w7sNUqmGaA6hywdFHMi+VWeR9wUScXM7XjoryTffCZ5B0/8IA== + dependencies: + ast-module-types "^5.0.0" + node-source-walk "^6.0.1" + +module-lookup-amd@^8.0.5: + version "8.0.5" + resolved "https://registry.yarnpkg.com/module-lookup-amd/-/module-lookup-amd-8.0.5.tgz#aaeea41979105b49339380ca3f7d573db78c32a5" + integrity sha512-vc3rYLjDo5Frjox8NZpiyLXsNWJ5BWshztc/5KSOMzpg9k5cHH652YsJ7VKKmtM4SvaxuE9RkrYGhiSjH3Ehow== + dependencies: + commander "^10.0.1" + glob "^7.2.3" + requirejs "^2.3.6" + requirejs-config-file "^4.0.0" + +ms@2.1.2: + version "2.1.2" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" + integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== + +nanoid@^3.3.7: + version "3.3.7" + resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.7.tgz#d0c301a691bc8d54efa0a2226ccf3fe2fd656bd8" + integrity sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g== + +node-source-walk@^6.0.0, node-source-walk@^6.0.1, node-source-walk@^6.0.2: + version "6.0.2" + resolved "https://registry.yarnpkg.com/node-source-walk/-/node-source-walk-6.0.2.tgz#ba81bc4bc0f6f05559b084bea10be84c3f87f211" + integrity sha512-jn9vOIK/nfqoFCcpK89/VCVaLg1IHE6UVfDOzvqmANaJ/rWCTEdH8RZ1V278nv2jr36BJdyQXIAavBLXpzdlag== + dependencies: + "@babel/parser" "^7.21.8" + +once@^1.3.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" + integrity sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w== + dependencies: + wrappy "1" + +path-is-absolute@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" + integrity sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg== + +path-parse@^1.0.7: + version "1.0.7" + resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735" + integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw== + +path-type@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/path-type/-/path-type-4.0.0.tgz#84ed01c0a7ba380afe09d90a8c180dcd9d03043b" + integrity sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw== + +picocolors@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.0.0.tgz#cb5bdc74ff3f51892236eaf79d68bc44564ab81c" + integrity sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ== + +picomatch@^2.3.1: + version "2.3.1" + resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42" + integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== + +postcss-values-parser@^6.0.2: + version "6.0.2" + resolved "https://registry.yarnpkg.com/postcss-values-parser/-/postcss-values-parser-6.0.2.tgz#636edc5b86c953896f1bb0d7a7a6615df00fb76f" + integrity sha512-YLJpK0N1brcNJrs9WatuJFtHaV9q5aAOj+S4DI5S7jgHlRfm0PIbDCAFRYMQD5SHq7Fy6xsDhyutgS0QOAs0qw== + dependencies: + color-name "^1.1.4" + is-url-superb "^4.0.0" + quote-unquote "^1.0.0" + +postcss@^8.4.23: + version "8.4.33" + resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.33.tgz#1378e859c9f69bf6f638b990a0212f43e2aaa742" + integrity sha512-Kkpbhhdjw2qQs2O2DGX+8m5OVqEcbB9HRBvuYM9pgrjEFUg30A9LmXNlTAUj4S9kgtGyrMbTzVjH7E+s5Re2yg== + dependencies: + nanoid "^3.3.7" + picocolors "^1.0.0" + source-map-js "^1.0.2" + +precinct@^11.0.5: + version "11.0.5" + resolved "https://registry.yarnpkg.com/precinct/-/precinct-11.0.5.tgz#3e15b3486670806f18addb54b8533e23596399ff" + integrity sha512-oHSWLC8cL/0znFhvln26D14KfCQFFn4KOLSw6hmLhd+LQ2SKt9Ljm89but76Pc7flM9Ty1TnXyrA2u16MfRV3w== + dependencies: + "@dependents/detective-less" "^4.1.0" + commander "^10.0.1" + detective-amd "^5.0.2" + detective-cjs "^5.0.1" + detective-es6 "^4.0.1" + detective-postcss "^6.1.3" + detective-sass "^5.0.3" + detective-scss "^4.0.3" + detective-stylus "^4.0.0" + detective-typescript "^11.1.0" + module-definition "^5.0.1" + node-source-walk "^6.0.2" + +queue-microtask@^1.2.2: + version "1.2.3" + resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243" + integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A== + +quote-unquote@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/quote-unquote/-/quote-unquote-1.0.0.tgz#67a9a77148effeaf81a4d428404a710baaac8a0b" + integrity sha512-twwRO/ilhlG/FIgYeKGFqyHhoEhqgnKVkcmqMKi2r524gz3ZbDTcyFt38E9xjJI2vT+KbRNHVbnJ/e0I25Azwg== + +requirejs-config-file@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/requirejs-config-file/-/requirejs-config-file-4.0.0.tgz#4244da5dd1f59874038cc1091d078d620abb6ebc" + integrity sha512-jnIre8cbWOyvr8a5F2KuqBnY+SDA4NXr/hzEZJG79Mxm2WiFQz2dzhC8ibtPJS7zkmBEl1mxSwp5HhC1W4qpxw== + dependencies: + esprima "^4.0.0" + stringify-object "^3.2.1" + +requirejs@^2.3.6: + version "2.3.6" + resolved "https://registry.yarnpkg.com/requirejs/-/requirejs-2.3.6.tgz#e5093d9601c2829251258c0b9445d4d19fa9e7c9" + integrity sha512-ipEzlWQe6RK3jkzikgCupiTbTvm4S0/CAU5GlgptkN5SO6F3u0UD0K18wy6ErDqiCyP4J4YYe1HuAShvsxePLg== + +resolve-dependency-path@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/resolve-dependency-path/-/resolve-dependency-path-3.0.2.tgz#012816717bcbe8b846835da11af9d2beb5acef50" + integrity sha512-Tz7zfjhLfsvR39ADOSk9us4421J/1ztVBo4rWUkF38hgHK5m0OCZ3NxFVpqHRkjctnwVa15igEUHFJp8MCS7vA== + +resolve@^1.22.3: + version "1.22.8" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.8.tgz#b6c87a9f2aa06dfab52e3d70ac8cde321fa5a48d" + integrity sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw== + dependencies: + is-core-module "^2.13.0" + path-parse "^1.0.7" + supports-preserve-symlinks-flag "^1.0.0" + +reusify@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.0.4.tgz#90da382b1e126efc02146e90845a88db12925d76" + integrity sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw== + +run-parallel@^1.1.9: + version "1.2.0" + resolved "https://registry.yarnpkg.com/run-parallel/-/run-parallel-1.2.0.tgz#66d1368da7bdf921eb9d95bd1a9229e7f21a43ee" + integrity sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA== + dependencies: + queue-microtask "^1.2.2" + +sass-lookup@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/sass-lookup/-/sass-lookup-5.0.1.tgz#1f01d7ff21e09d8c9dcf8d05b3fca28f2f96e6ed" + integrity sha512-t0X5PaizPc2H4+rCwszAqHZRtr4bugo4pgiCvrBFvIX0XFxnr29g77LJcpyj9A0DcKf7gXMLcgvRjsonYI6x4g== + dependencies: + commander "^10.0.1" + +semver@^7.3.7: + version "7.5.4" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.5.4.tgz#483986ec4ed38e1c6c48c34894a9182dbff68a6e" + integrity sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA== + dependencies: + lru-cache "^6.0.0" + +slash@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634" + integrity sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q== + +source-map-js@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.0.2.tgz#adbc361d9c62df380125e7f161f71c826f1e490c" + integrity sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw== + +source-map@~0.6.1: + version "0.6.1" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" + integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== + +stringify-object@^3.2.1: + version "3.3.0" + resolved "https://registry.yarnpkg.com/stringify-object/-/stringify-object-3.3.0.tgz#703065aefca19300d3ce88af4f5b3956d7556629" + integrity sha512-rHqiFh1elqCQ9WPLIC8I0Q/g/wj5J1eMkyoiD6eoQApWHP0FtlK7rqnhmabL5VUY9JQCcqwwvlOaSuutekgyrw== + dependencies: + get-own-enumerable-property-symbols "^3.0.0" + is-obj "^1.0.1" + is-regexp "^1.0.0" + +strip-bom@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-3.0.0.tgz#2334c18e9c759f7bdd56fdef7e9ae3d588e68ed3" + integrity sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA== + +stylus-lookup@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/stylus-lookup/-/stylus-lookup-5.0.1.tgz#3c4d116c3b1e8e1a8169c0d9cd20e608595560f4" + integrity sha512-tLtJEd5AGvnVy4f9UHQMw4bkJJtaAcmo54N+ovQBjDY3DuWyK9Eltxzr5+KG0q4ew6v2EHyuWWNnHeiw/Eo7rQ== + dependencies: + commander "^10.0.1" + +supports-preserve-symlinks-flag@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09" + integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w== + +tapable@^2.2.0: + version "2.2.1" + resolved "https://registry.yarnpkg.com/tapable/-/tapable-2.2.1.tgz#1967a73ef4060a82f12ab96af86d52fdb76eeca0" + integrity sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ== + +to-regex-range@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4" + integrity sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ== + dependencies: + is-number "^7.0.0" + +tsconfig-paths@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/tsconfig-paths/-/tsconfig-paths-4.2.0.tgz#ef78e19039133446d244beac0fd6a1632e2d107c" + integrity sha512-NoZ4roiN7LnbKn9QqE1amc9DJfzvZXxF4xDavcOWt1BPkdx+m+0gJuPM+S0vCe7zTJMYUP0R8pO2XMr+Y8oLIg== + dependencies: + json5 "^2.2.2" + minimist "^1.2.6" + strip-bom "^3.0.0" + +tslib@^1.8.1: + version "1.14.1" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00" + integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg== + +tsutils@^3.21.0: + version "3.21.0" + resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-3.21.0.tgz#b48717d394cea6c1e096983eed58e9d61715b623" + integrity sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA== + dependencies: + tslib "^1.8.1" + +typescript@^5.0.4: + version "5.3.3" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.3.3.tgz#b3ce6ba258e72e6305ba66f5c9b452aaee3ffe37" + integrity sha512-pXWcraxM0uxAS+tN0AG/BF2TyqmHO014Z070UsJ+pFvYuRSq8KH8DmWpnbXe0pEPDHXZV3FcAbJkijJ5oNEnWw== + +wrappy@1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" + integrity sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ== + +yallist@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72" + integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A== diff --git a/deploy/tools/envs-validator/index.ts b/deploy/tools/envs-validator/index.ts index dc5106045b..9770ea6399 100644 --- a/deploy/tools/envs-validator/index.ts +++ b/deploy/tools/envs-validator/index.ts @@ -36,11 +36,15 @@ async function validateEnvs(appEnvs: Record) { const envsWithJsonConfig = [ 'NEXT_PUBLIC_FEATURED_NETWORKS', 'NEXT_PUBLIC_MARKETPLACE_CONFIG_URL', + 'NEXT_PUBLIC_MARKETPLACE_CATEGORIES_URL', + 'NEXT_PUBLIC_MARKETPLACE_SECURITY_REPORTS_URL', 'NEXT_PUBLIC_FOOTER_LINKS', ]; for await (const envName of envsWithJsonConfig) { - appEnvs[envName] = await(appEnvs[envName] ? getExternalJsonContent(envName) : Promise.resolve()) || '[]'; + if (appEnvs[envName]) { + appEnvs[envName] = await getExternalJsonContent(envName) || '[]'; + } } await schema.validate(appEnvs, { stripUnknown: false, abortEarly: false }); @@ -99,7 +103,7 @@ async function checkPlaceholdersCongruity(envsMap: Record) { inconsistencies.forEach((env) => { console.log(` ${ env }`); }); - console.log(` They are either deprecated or running the app with them may lead to unexpected behavior. + console.log(` They are either deprecated or running the app with them may lead to unexpected behavior. Please check the documentation for more details - https://github.com/blockscout/frontend/blob/main/docs/ENVS.md `); throw new Error(); diff --git a/deploy/tools/envs-validator/schema.ts b/deploy/tools/envs-validator/schema.ts index aaab91c31d..c9e3bf5aa5 100644 --- a/deploy/tools/envs-validator/schema.ts +++ b/deploy/tools/envs-validator/schema.ts @@ -9,13 +9,19 @@ declare module 'yup' { import * as yup from 'yup'; import type { AdButlerConfig } from '../../../types/client/adButlerConfig'; -import { SUPPORTED_AD_TEXT_PROVIDERS, SUPPORTED_AD_BANNER_PROVIDERS } from '../../../types/client/adProviders'; -import type { AdTextProviders, AdBannerProviders } from '../../../types/client/adProviders'; -import type { MarketplaceAppOverview } from '../../../types/client/marketplace'; +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 { 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 { ROLLUP_TYPES } from '../../../types/client/rollup'; import type { BridgedTokenChain, TokenBridge } from '../../../types/client/token'; import { PROVIDERS as TX_INTERPRETATION_PROVIDERS } from '../../../types/client/txInterpretation'; +import { VALIDATORS_CHAIN_TYPE } from '../../../types/client/validators'; +import type { ValidatorsChainType } from '../../../types/client/validators'; import type { WalletType } from '../../../types/client/wallets'; import { SUPPORTED_WALLETS } from '../../../types/client/wallets'; import type { CustomLink, CustomLinksGroup } from '../../../types/footerLinks'; @@ -69,22 +75,124 @@ const marketplaceAppSchema: yup.ObjectSchema = yup site: yup.string().test(urlTest), twitter: yup.string().test(urlTest), telegram: yup.string().test(urlTest), - github: yup.string().test(urlTest), + github: yup.lazy(value => + Array.isArray(value) ? + yup.array().of(yup.string().required().test(urlTest)) : + yup.string().test(urlTest), + ), + discord: yup.string().test(urlTest), + internalWallet: yup.boolean(), + priority: yup.number(), + }); + +const issueSeverityDistributionSchema: yup.ObjectSchema = yup + .object({ + critical: yup.number().required(), + gas: yup.number().required(), + high: yup.number().required(), + informational: yup.number().required(), + low: yup.number().required(), + medium: yup.number().required(), + }); + +const solidityscanReportSchema: yup.ObjectSchema = yup + .object({ + contractname: yup.string().required(), + scan_status: yup.string().required(), + scan_summary: yup + .object({ + issue_severity_distribution: issueSeverityDistributionSchema.required(), + lines_analyzed_count: yup.number().required(), + scan_time_taken: yup.number().required(), + score: yup.string().required(), + score_v2: yup.string().required(), + threat_score: yup.string().required(), + }) + .required(), + scanner_reference_url: yup.string().test(urlTest).required(), + }); + +const contractDataSchema: yup.ObjectSchema = yup + .object({ + address: yup.string().required(), + isVerified: yup.boolean().required(), + solidityScanReport: solidityscanReportSchema.nullable().notRequired(), + }); + +const chainsDataSchema = yup.lazy((objValue) => { + let schema = yup.object(); + Object.keys(objValue).forEach((key) => { + schema = schema.shape({ + [key]: yup.object({ + overallInfo: yup.object({ + verifiedNumber: yup.number().required(), + totalContractsNumber: yup.number().required(), + solidityScanContractsNumber: yup.number().required(), + securityScore: yup.number().required(), + issueSeverityDistribution: issueSeverityDistributionSchema.required(), + }).required(), + contractsData: yup.array().of(contractDataSchema).required(), + }), + }); + }); + return schema; +}); + +const securityReportSchema: yup.ObjectSchema = yup + .object({ + appName: yup.string().required(), + chainsData: chainsDataSchema, }); const marketplaceSchema = yup .object() .shape({ + NEXT_PUBLIC_MARKETPLACE_ENABLED: yup.boolean(), NEXT_PUBLIC_MARKETPLACE_CONFIG_URL: yup .array() .json() - .of(marketplaceAppSchema), + .of(marketplaceAppSchema) + .when('NEXT_PUBLIC_MARKETPLACE_ENABLED', { + is: true, + then: (schema) => schema, + // eslint-disable-next-line max-len + otherwise: (schema) => schema.max(-1, 'NEXT_PUBLIC_MARKETPLACE_CONFIG_URL cannot not be used without NEXT_PUBLIC_MARKETPLACE_ENABLED'), + }), + NEXT_PUBLIC_MARKETPLACE_CATEGORIES_URL: yup + .array() + .json() + .of(yup.string()) + .when('NEXT_PUBLIC_MARKETPLACE_ENABLED', { + is: true, + then: (schema) => schema, + // eslint-disable-next-line max-len + otherwise: (schema) => schema.max(-1, 'NEXT_PUBLIC_MARKETPLACE_CATEGORIES_URL cannot not be used without NEXT_PUBLIC_MARKETPLACE_ENABLED'), + }), NEXT_PUBLIC_MARKETPLACE_SUBMIT_FORM: yup .string() - .when('NEXT_PUBLIC_MARKETPLACE_CONFIG_URL', { - is: (value: Array) => value.length > 0, + .when('NEXT_PUBLIC_MARKETPLACE_ENABLED', { + is: true, then: (schema) => schema.test(urlTest).required(), - otherwise: (schema) => schema.max(-1, 'NEXT_PUBLIC_MARKETPLACE_SUBMIT_FORM cannot not be used without NEXT_PUBLIC_MARKETPLACE_CONFIG_URL'), + // eslint-disable-next-line max-len + otherwise: (schema) => schema.max(-1, 'NEXT_PUBLIC_MARKETPLACE_SUBMIT_FORM cannot not be used without NEXT_PUBLIC_MARKETPLACE_ENABLED'), + }), + NEXT_PUBLIC_MARKETPLACE_SUGGEST_IDEAS_FORM: yup + .string() + .when('NEXT_PUBLIC_MARKETPLACE_ENABLED', { + is: true, + then: (schema) => schema.test(urlTest), + // eslint-disable-next-line max-len + otherwise: (schema) => schema.max(-1, 'NEXT_PUBLIC_MARKETPLACE_SUGGEST_IDEAS_FORM cannot not be used without NEXT_PUBLIC_MARKETPLACE_ENABLED'), + }), + NEXT_PUBLIC_MARKETPLACE_SECURITY_REPORTS_URL: yup + .array() + .json() + .of(securityReportSchema) + .when('NEXT_PUBLIC_MARKETPLACE_ENABLED', { + is: true, + then: (schema) => schema, + // eslint-disable-next-line max-len + otherwise: (schema) => schema.max(-1, 'NEXT_PUBLIC_MARKETPLACE_SECURITY_REPORTS_URL cannot not be used without NEXT_PUBLIC_MARKETPLACE_ENABLED'), }), }); @@ -107,23 +215,20 @@ const beaconChainSchema = yup const rollupSchema = yup .object() .shape({ - NEXT_PUBLIC_IS_OPTIMISTIC_L2_NETWORK: yup.boolean(), - NEXT_PUBLIC_OPTIMISTIC_L2_WITHDRAWAL_URL: yup + NEXT_PUBLIC_ROLLUP_TYPE: yup.string().oneOf(ROLLUP_TYPES), + NEXT_PUBLIC_ROLLUP_L1_BASE_URL: yup .string() - .when('NEXT_PUBLIC_IS_OPTIMISTIC_L2_NETWORK', { + .when('NEXT_PUBLIC_ROLLUP_TYPE', { is: (value: string) => value, then: (schema) => schema.test(urlTest).required(), - // eslint-disable-next-line max-len - otherwise: (schema) => schema.max(-1, 'NEXT_PUBLIC_OPTIMISTIC_L2_WITHDRAWAL_URL cannot not be used if NEXT_PUBLIC_IS_OPTIMISTIC_L2_NETWORK is not set to "true"'), + otherwise: (schema) => schema.max(-1, 'NEXT_PUBLIC_ROLLUP_L1_BASE_URL cannot not be used if NEXT_PUBLIC_ROLLUP_TYPE is not defined'), }), - NEXT_PUBLIC_IS_ZKEVM_L2_NETWORK: yup.boolean(), - NEXT_PUBLIC_L1_BASE_URL: yup + NEXT_PUBLIC_ROLLUP_L2_WITHDRAWAL_URL: yup .string() - .when([ 'NEXT_PUBLIC_IS_OPTIMISTIC_L2_NETWORK', 'NEXT_PUBLIC_IS_ZKEVM_L2_NETWORK' ], { - is: (isOptimistic?: boolean, isZk?: boolean) => isOptimistic || isZk, + .when('NEXT_PUBLIC_ROLLUP_TYPE', { + is: (value: string) => value === 'optimistic', then: (schema) => schema.test(urlTest).required(), - // eslint-disable-next-line max-len - otherwise: (schema) => schema.max(-1, 'NEXT_PUBLIC_L1_BASE_URL cannot not be used if NEXT_PUBLIC_IS_OPTIMISTIC_L2_NETWORK or NEXT_PUBLIC_IS_ZKEVM_L2_NETWORK is not set to "true"'), + otherwise: (schema) => schema.max(-1, 'NEXT_PUBLIC_ROLLUP_L2_WITHDRAWAL_URL cannot not be used if NEXT_PUBLIC_ROLLUP_TYPE is not defined'), }), }); @@ -140,12 +245,23 @@ const adButlerConfigSchema = yup height: yup.number().positive().required(), }) .required(), + }) + .when('NEXT_PUBLIC_AD_BANNER_ADDITIONAL_PROVIDER', { + is: (value: AdBannerProviders) => value === 'adbutler', + then: (schema) => schema + .shape({ + id: yup.string().required(), + width: yup.number().positive().required(), + height: yup.number().positive().required(), + }) + .required(), }); const adsBannerSchema = yup .object() .shape({ NEXT_PUBLIC_AD_BANNER_PROVIDER: yup.string().oneOf(SUPPORTED_AD_BANNER_PROVIDERS), + NEXT_PUBLIC_AD_BANNER_ADDITIONAL_PROVIDER: yup.string().oneOf(SUPPORTED_AD_BANNER_ADDITIONAL_PROVIDERS), NEXT_PUBLIC_AD_ADBUTLER_CONFIG_DESKTOP: adButlerConfigSchema, NEXT_PUBLIC_AD_ADBUTLER_CONFIG_MOBILE: adButlerConfigSchema, }); @@ -252,6 +368,7 @@ const footerLinkGroupSchema: yup.ObjectSchema = yup const networkExplorerSchema: yup.ObjectSchema = yup .object({ title: yup.string().required(), + logo: yup.string().test(urlTest), baseUrl: yup.string().test(urlTest).required(), paths: yup .object() @@ -263,6 +380,13 @@ const networkExplorerSchema: yup.ObjectSchema = yup }), }); +const contractCodeIdeSchema: yup.ObjectSchema = yup + .object({ + title: yup.string().required(), + url: yup.string().test(urlTest).required(), + icon_url: yup.string().test(urlTest).required(), + }); + const nftMarketplaceSchema: yup.ObjectSchema = yup .object({ name: yup.string().required(), @@ -330,6 +454,7 @@ const schema = yup NEXT_PUBLIC_NETWORK_ID: yup.number().positive().integer().required(), NEXT_PUBLIC_NETWORK_RPC_URL: yup.string().test(urlTest), NEXT_PUBLIC_NETWORK_CURRENCY_NAME: yup.string(), + 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(), @@ -352,7 +477,6 @@ const schema = yup .of(yup.string().oneOf([ 'daily_txs', 'coin_price', 'market_cap', 'tvl' ])), NEXT_PUBLIC_HOMEPAGE_PLATE_TEXT_COLOR: yup.string(), NEXT_PUBLIC_HOMEPAGE_PLATE_BACKGROUND: yup.string(), - NEXT_PUBLIC_HOMEPAGE_SHOW_GAS_TRACKER: yup.boolean(), NEXT_PUBLIC_HOMEPAGE_SHOW_AVG_BLOCK_TIME: yup.boolean(), // b. sidebar @@ -416,6 +540,12 @@ const schema = yup .transform(replaceQuotes) .json() .of(networkExplorerSchema), + NEXT_PUBLIC_CONTRACT_CODE_IDES: yup + .array() + .transform(replaceQuotes) + .json() + .of(contractCodeIdeSchema), + NEXT_PUBLIC_HAS_CONTRACT_AUDIT_REPORTS: yup.boolean(), NEXT_PUBLIC_HIDE_INDEXING_ALERT_BLOCKS: yup.boolean(), NEXT_PUBLIC_HIDE_INDEXING_ALERT_INT_TXS: yup.boolean(), NEXT_PUBLIC_MAINTENANCE_ALERT_MESSAGE: yup.string(), @@ -425,6 +555,7 @@ const schema = yup NEXT_PUBLIC_STATS_API_HOST: yup.string().test(urlTest), NEXT_PUBLIC_VISUALIZE_API_HOST: yup.string().test(urlTest), 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_WEB3_WALLETS: yup .mixed() @@ -444,7 +575,15 @@ 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_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_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(), // 6. External services envs NEXT_PUBLIC_WALLET_CONNECT_PROJECT_ID: yup.string(), @@ -455,6 +594,7 @@ const schema = yup NEXT_PUBLIC_ALGOLIA_APP_ID: yup.string(), NEXT_PUBLIC_ALGOLIA_API_KEY: yup.string(), NEXT_PUBLIC_ALGOLIA_INDEX_NAME: yup.string(), + NEXT_PUBLIC_GROWTH_BOOK_CLIENT_KEY: yup.string(), // Misc NEXT_PUBLIC_USE_NEXT_JS_PROXY: yup.boolean(), diff --git a/deploy/tools/envs-validator/test/.env.adbutler_add b/deploy/tools/envs-validator/test/.env.adbutler_add new file mode 100644 index 0000000000..7f1968e4bb --- /dev/null +++ b/deploy/tools/envs-validator/test/.env.adbutler_add @@ -0,0 +1,4 @@ +NEXT_PUBLIC_AD_BANNER_PROVIDER='slise' +NEXT_PUBLIC_AD_BANNER_ADDITIONAL_PROVIDER='adbutler' +NEXT_PUBLIC_AD_ADBUTLER_CONFIG_DESKTOP={'id':'123456','width':'728','height':'90'} +NEXT_PUBLIC_AD_ADBUTLER_CONFIG_MOBILE={'id':'654321','width':'300','height':'100'} \ No newline at end of file diff --git a/deploy/tools/envs-validator/test/.env.base b/deploy/tools/envs-validator/test/.env.base index 578d5f91f6..559c63fc79 100644 --- a/deploy/tools/envs-validator/test/.env.base +++ b/deploy/tools/envs-validator/test/.env.base @@ -9,7 +9,9 @@ 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_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_FOOTER_LINKS=https://example.com NEXT_PUBLIC_GRAPHIQL_TRANSACTION=0xf7d4972356e6ae44ae948d0cf19ef2beaf0e574c180997e969a2837da15e349d @@ -18,12 +20,12 @@ NEXT_PUBLIC_HIDE_INDEXING_ALERT_INT_TXS=false NEXT_PUBLIC_HOMEPAGE_CHARTS=['daily_txs'] NEXT_PUBLIC_HOMEPAGE_PLATE_TEXT_COLOR='#fff' NEXT_PUBLIC_HOMEPAGE_PLATE_BACKGROUND='rgb(255, 145, 0)' -NEXT_PUBLIC_HOMEPAGE_SHOW_GAS_TRACKER=true NEXT_PUBLIC_HOMEPAGE_SHOW_AVG_BLOCK_TIME=true +NEXT_PUBLIC_GAS_TRACKER_ENABLED=true +NEXT_PUBLIC_GAS_TRACKER_UNITS=['gwei'] NEXT_PUBLIC_IS_TESTNET=true -NEXT_PUBLIC_MARKETPLACE_CONFIG_URL=https://example.com -NEXT_PUBLIC_MARKETPLACE_SUBMIT_FORM=https://example.com NEXT_PUBLIC_MAINTENANCE_ALERT_MESSAGE='Hello' +NEXT_PUBLIC_METASUITES_ENABLED=true NEXT_PUBLIC_NETWORK_CURRENCY_DECIMALS=18 NEXT_PUBLIC_NETWORK_CURRENCY_NAME=Ether NEXT_PUBLIC_NETWORK_CURRENCY_SYMBOL=ETH @@ -40,6 +42,7 @@ NEXT_PUBLIC_OG_DESCRIPTION='Hello world!' NEXT_PUBLIC_OG_IMAGE_URL=https://example.com/image.png 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_USE_NEXT_JS_PROXY=false NEXT_PUBLIC_VIEWS_ADDRESS_IDENTICON_TYPE=gradient_avatar @@ -50,4 +53,6 @@ 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_WEB3_DISABLE_ADD_TOKEN_TO_WALLET=false -NEXT_PUBLIC_WEB3_WALLETS=['coinbase','metamask','token_pocket'] \ No newline at end of file +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 diff --git a/deploy/tools/envs-validator/test/.env.marketplace b/deploy/tools/envs-validator/test/.env.marketplace new file mode 100644 index 0000000000..eaf12c6dc9 --- /dev/null +++ b/deploy/tools/envs-validator/test/.env.marketplace @@ -0,0 +1,7 @@ +NEXT_PUBLIC_MARKETPLACE_ENABLED=true +NEXT_PUBLIC_MARKETPLACE_CONFIG_URL=https://example.com +NEXT_PUBLIC_MARKETPLACE_CATEGORIES_URL=https://example.com +NEXT_PUBLIC_MARKETPLACE_SUBMIT_FORM=https://example.com +NEXT_PUBLIC_MARKETPLACE_SUGGEST_IDEAS_FORM=https://example.com +NEXT_PUBLIC_MARKETPLACE_SECURITY_REPORTS_URL=https://example.com +NEXT_PUBLIC_ADMIN_SERVICE_API_HOST=https://example.com diff --git a/deploy/tools/envs-validator/test/.env.rollup b/deploy/tools/envs-validator/test/.env.rollup index 8534221d99..1cb3e1af88 100644 --- a/deploy/tools/envs-validator/test/.env.rollup +++ b/deploy/tools/envs-validator/test/.env.rollup @@ -1,3 +1,3 @@ -NEXT_PUBLIC_IS_OPTIMISTIC_L2_NETWORK=true -NEXT_PUBLIC_L1_BASE_URL=https://example.com -NEXT_PUBLIC_OPTIMISTIC_L2_WITHDRAWAL_URL=https://example.com \ No newline at end of file +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 diff --git a/deploy/tools/envs-validator/test/assets/marketplace_categories.json b/deploy/tools/envs-validator/test/assets/marketplace_categories.json new file mode 100644 index 0000000000..15b31a5557 --- /dev/null +++ b/deploy/tools/envs-validator/test/assets/marketplace_categories.json @@ -0,0 +1,5 @@ +[ + "Swaps", + "Bridges", + "NFT" +] diff --git a/deploy/tools/envs-validator/test/assets/marketplace_security_reports.json b/deploy/tools/envs-validator/test/assets/marketplace_security_reports.json new file mode 100644 index 0000000000..cf0f481ae3 --- /dev/null +++ b/deploy/tools/envs-validator/test/assets/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/values/l2-optimism-goerli/values.yaml b/deploy/values/l2-optimism-goerli/values.yaml index 1d695580d4..afd8dcc1a0 100644 --- a/deploy/values/l2-optimism-goerli/values.yaml +++ b/deploy/values/l2-optimism-goerli/values.yaml @@ -179,9 +179,12 @@ frontend: 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']" @@ -190,10 +193,11 @@ frontend: NEXT_PUBLIC_VISUALIZE_API_HOST: https://visualizer-test.k8s-dev.blockscout.com NEXT_PUBLIC_CONTRACT_INFO_API_HOST: https://contracts-info-test.k8s-dev.blockscout.com NEXT_PUBLIC_ADMIN_SERVICE_API_HOST: https://admin-rs-test.k8s-dev.blockscout.com - NEXT_PUBLIC_IS_OPTIMISTIC_L2_NETWORK: "true" - NEXT_PUBLIC_L1_BASE_URL: https://eth-goerli.blockscout.com/ - NEXT_PUBLIC_OPTIMISTIC_L2_WITHDRAWAL_URL: https://app.optimism.io/bridge/withdraw + NEXT_PUBLIC_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 diff --git a/deploy/values/main/values.yaml b/deploy/values/main/values.yaml index 65f36f4dcd..493941ad0a 100644 --- a/deploy/values/main/values.yaml +++ b/deploy/values/main/values.yaml @@ -78,7 +78,9 @@ blockscout: INDEXER_DISABLE_EMPTY_BLOCKS_SANITIZER: 'true' INDEXER_DISABLE_PENDING_TRANSACTIONS_FETCHER: 'true' INDEXER_RECEIPTS_BATCH_SIZE: 50 - INDEXER_COIN_BALANCES_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' @@ -146,20 +148,26 @@ frontend: 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 diff --git a/deploy/values/review-l2/values.yaml.gotmpl b/deploy/values/review-l2/values.yaml.gotmpl index e55c460edf..eea25de5e8 100644 --- a/deploy/values/review-l2/values.yaml.gotmpl +++ b/deploy/values/review-l2/values.yaml.gotmpl @@ -30,10 +30,12 @@ frontend: kubernetes.io/ingress.class: internal-and-public nginx.ingress.kubernetes.io/proxy-body-size: 500m nginx.ingress.kubernetes.io/client-max-body-size: "500M" - nginx.ingress.kubernetes.io/proxy-buffering: "off" + nginx.ingress.kubernetes.io/proxy-buffering: "on" nginx.ingress.kubernetes.io/proxy-connect-timeout: "15m" nginx.ingress.kubernetes.io/proxy-send-timeout: "15m" nginx.ingress.kubernetes.io/proxy-read-timeout: "15m" + nginx.ingress.kubernetes.io/proxy-buffer-size: "128k" + nginx.ingress.kubernetes.io/proxy-buffers-number: "8" cert-manager.io/cluster-issuer: "zerossl-prod" hostname: review-l2-{{ requiredEnv "GITHUB_REF_NAME_SLUG" }}.k8s-dev.blockscout.com @@ -52,10 +54,13 @@ frontend: 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_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_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']" @@ -64,11 +69,12 @@ frontend: NEXT_PUBLIC_VISUALIZE_API_HOST: https://visualizer-optimism-goerli.k8s-dev.blockscout.com NEXT_PUBLIC_CONTRACT_INFO_API_HOST: https://contracts-info-test.k8s-dev.blockscout.com NEXT_PUBLIC_ADMIN_SERVICE_API_HOST: https://admin-rs-test.k8s-dev.blockscout.com - NEXT_PUBLIC_IS_OPTIMISTIC_L2_NETWORK: "true" - NEXT_PUBLIC_OPTIMISTIC_L2_WITHDRAWAL_URL: https://app.optimism.io/bridge/withdraw - NEXT_PUBLIC_L1_BASE_URL: https://blockscout-main.k8s-dev.blockscout.com + NEXT_PUBLIC_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_GRAPHIQL_TRANSACTION: 0x4a0ed8ddf751a7cb5297f827699117b0f6d21a0b2907594d300dc9fed75c7e62 NEXT_PUBLIC_USE_NEXT_JS_PROXY: true + NEXT_PUBLIC_SWAP_BUTTON_URL: sushiswap 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 diff --git a/deploy/values/review/values.yaml.gotmpl b/deploy/values/review/values.yaml.gotmpl index dbfd815a82..7f6418f43f 100644 --- a/deploy/values/review/values.yaml.gotmpl +++ b/deploy/values/review/values.yaml.gotmpl @@ -4,7 +4,7 @@ imagePullSecrets: - name: regcred config: network: - id: 5 + id: 11155111 name: Blockscout shortname: Blockscout currency: @@ -30,10 +30,12 @@ frontend: kubernetes.io/ingress.class: internal-and-public nginx.ingress.kubernetes.io/proxy-body-size: 500m nginx.ingress.kubernetes.io/client-max-body-size: "500M" - nginx.ingress.kubernetes.io/proxy-buffering: "off" + nginx.ingress.kubernetes.io/proxy-buffering: "on" nginx.ingress.kubernetes.io/proxy-connect-timeout: "15m" nginx.ingress.kubernetes.io/proxy-send-timeout: "15m" nginx.ingress.kubernetes.io/proxy-read-timeout: "15m" + nginx.ingress.kubernetes.io/proxy-buffer-size: "128k" + nginx.ingress.kubernetes.io/proxy-buffers-number: "8" cert-manager.io/cluster-issuer: "zerossl-prod" hostname: review-{{ requiredEnv "GITHUB_REF_NAME_SLUG" }}.k8s-dev.blockscout.com @@ -51,18 +53,24 @@ frontend: NEXT_PUBLIC_FEATURED_NETWORKS: https://raw.githubusercontent.com/blockscout/frontend-configs/dev/configs/featured-networks/eth-goerli.json NEXT_PUBLIC_NETWORK_LOGO: https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/network-logos/goerli.svg NEXT_PUBLIC_NETWORK_ICON: https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/network-icons/goerli.svg - NEXT_PUBLIC_API_HOST: eth-goerli.blockscout.com + NEXT_PUBLIC_API_HOST: eth-sepolia.blockscout.com NEXT_PUBLIC_STATS_API_HOST: https://stats-goerli.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_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://rpc.ankr.com/eth_goerli - NEXT_PUBLIC_NETWORK_EXPLORERS: "[{'title':'Bitquery','baseUrl':'https://explorer.bitquery.io/','paths':{'tx':'/goerli/tx','address':'/goerli/address','token':'/goerli/token','block':'/goerli/block'}},{'title':'Etherscan','baseUrl':'https://goerli.etherscan.io/','paths':{'tx':'/tx','address':'/address','token':'/token','block':'/block'}}]" - NEXT_PUBLIC_MARKETPLACE_CONFIG_URL: https://raw.githubusercontent.com/blockscout/frontend-configs/dev/configs/marketplace/eth-goerli.json + NEXT_PUBLIC_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 @@ -72,9 +80,16 @@ frontend: 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'}]" - OTEL_SDK_ENABLED: true - OTEL_EXPORTER_OTLP_ENDPOINT: http://jaeger-collector.jaeger.svc.cluster.local:4318 - NEXT_OTEL_VERBOSE: 1 + 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_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_DATA_AVAILABILITY_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 @@ -83,3 +98,5 @@ frontend: NEXT_PUBLIC_RE_CAPTCHA_APP_SITE_KEY: ref+vault://deployment-values/blockscout/dev/review?token_env=VAULT_TOKEN&address=https://vault.k8s.blockscout.com#/NEXT_PUBLIC_RE_CAPTCHA_APP_SITE_KEY NEXT_PUBLIC_GOOGLE_ANALYTICS_PROPERTY_ID: ref+vault://deployment-values/blockscout/dev/review?token_env=VAULT_TOKEN&address=https://vault.k8s.blockscout.com#/NEXT_PUBLIC_GOOGLE_ANALYTICS_PROPERTY_ID FAVICON_GENERATOR_API_KEY: ref+vault://deployment-values/blockscout/common?token_env=VAULT_TOKEN&address=https://vault.k8s.blockscout.com#/NEXT_PUBLIC_FAVICON_GENERATOR_API_KEY + NEXT_PUBLIC_GROWTH_BOOK_CLIENT_KEY: ref+vault://deployment-values/blockscout/dev/review?token_env=VAULT_TOKEN&address=https://vault.k8s.blockscout.com#/NEXT_PUBLIC_GROWTH_BOOK_CLIENT_KEY + NEXT_PUBLIC_MIXPANEL_PROJECT_TOKEN: ref+vault://deployment-values/blockscout/common?token_env=VAULT_TOKEN&address=https://vault.k8s.blockscout.com#/NEXT_PUBLIC_MIXPANEL_PROJECT_TOKEN diff --git a/docs/CONTRIBUTING.md b/docs/CONTRIBUTING.md index 578cbd2129..58dcae80c9 100644 --- a/docs/CONTRIBUTING.md +++ b/docs/CONTRIBUTING.md @@ -14,7 +14,7 @@ Thanks for showing interest to contribute to Blockscout. The following steps wil cd ``` -3. Make sure you're running Node.js 18+ and NPM 8+; if not, upgrade it accordingly, for example using [nvm](https://github.com/nvm-sh/nvm). +3. Make sure you're running Node.js 20+ and NPM 10+; if not, upgrade it accordingly, for example using [nvm](https://github.com/nvm-sh/nvm). ```sh node -v npm -v @@ -57,7 +57,7 @@ B. Pre-defined configuration: 1. Optionally, clone `.env.example` file into `configs/envs/.env.secrets`. Fill it with necessary secrets for integrating with [external services](./ENVS.md#external-services-configuration). Include only secrets your need. 2. Choose one of the predefined configurations located in the `/configs/envs` folder. -3. Start your local dev server using the `yarn dev:` command. +3. Start your local dev server using the `yarn dev:preset ` command. 4. Open your browser and navigate to the URL provided in the command line output (by default, it is `http://localhost:3000`). @@ -79,18 +79,21 @@ These are the steps that you have to follow to make everything work: 2. Make sure that you have added a property to React app config (`configs/app/index.ts`) in appropriate section that is associated with this variable; do not use ENV variable values directly in the application code; decide where this variable belongs to and place it under the certain section: - `app` - the front-end app itself - `api` - the main API configuration + - `chain` - the Blockchain parameters - `UI` - the app UI customization + - `meta` - SEO and meta-tags customization - `features` - the particular feature of the app - - `services` - some 3rd party service integration which is not related to one particular feature -3. For local development purposes add the variable with its appropriate values to pre-defined ENV configs `configs/envs` where it is needed -4. Add the variable to CI configs where it is needed + - `services` - some 3rd party service integration which is not related to one particular feature +3. If a new variable is meant to store the URL of an external API service, remember to include its value in the Content-Security-Policy document header. Refer to `nextjs/csp/policies/app.ts` for details. +4. For local development purposes add the variable with its appropriate values to pre-defined ENV configs `configs/envs` where it is needed +5. Add the variable to CI configs where it is needed - `deploy/values/review/values.yaml.gotmpl` - review development environment - `deploy/values/main/values.yaml` - main development environment - `deploy/values/review-l2/values.yaml.gotmpl` - review development environment for L2 networks - `deploy/values/l2-optimism-goerli/values.yaml` - main development environment -5. If your variable is meant to receive a link to some external resource (image or JSON-config file), extend the array `ASSETS_ENVS` in `deploy/scripts/download_assets.sh` with your variable name -6. Add validation schema for the new variable into the file `deploy/tools/envs-validator/schema.ts` -7. Check if modified validation schema is valid by doing the following steps: +6. If your variable is meant to receive a link to some external resource (image or JSON-config file), extend the array `ASSETS_ENVS` in `deploy/scripts/download_assets.sh` with your variable name +7. Add validation schema for the new variable into the file `deploy/tools/envs-validator/schema.ts` +8. Check if modified validation schema is valid by doing the following steps: - change your current directory to `deploy/tools/envs-validator` - install deps with `yarn` command - add your variable into `./test/.env.base` test preset or create a new test preset if needed @@ -98,7 +101,7 @@ These are the steps that you have to follow to make everything work: - add example of file content into `./test/assets` directory; the file name should be constructed by stripping away prefix `NEXT_PUBLIC_` and postfix `_URL` if any, and converting the remaining string to lowercase (for example, `NEXT_PUBLIC_MARKETPLACE_CONFIG_URL` will become `marketplace_config.json`) - in the main script `index.ts` extend array `envsWithJsonConfig` with your variable name - run `yarn test` command to see the validation result -8. Don't forget to mention in the PR notes that new ENV variable was added +9. Don't forget to mention in the PR notes that new ENV variable was added   diff --git a/docs/DEPRECATED_ENVS.md b/docs/DEPRECATED_ENVS.md new file mode 100644 index 0000000000..b46ea04108 --- /dev/null +++ b/docs/DEPRECATED_ENVS.md @@ -0,0 +1,9 @@ +# 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 diff --git a/docs/ENVS.md b/docs/ENVS.md index 5398faa46d..3058167161 100644 --- a/docs/ENVS.md +++ b/docs/ENVS.md @@ -28,16 +28,18 @@ Please be aware that all environment variables prefixed with `NEXT_PUBLIC_` will - [Misc](ENVS.md#misc) - [App features](ENVS.md#app-features) - [My account](ENVS.md#my-account) + - [Gas tracker](ENVS.md#gas-tracker) - [Address verification](ENVS.md#address-verification-in-my-account) in "My account" - [Blockchain interaction](ENVS.md#blockchain-interaction-writing-to-contract-etc) (writing to contract, etc.) - [Banner ads](ENVS.md#banner-ads) - [Text ads](ENVS.md#text-ads) - [Beacon chain](ENVS.md#beacon-chain) - - [Optimistic rollup (L2) chain](ENVS.md#optimistic-rollup-l2-chain) - - [ZkEvm rollup (L2) chain](NVS.md#zkevm-rollup-l2-chain) + - [User operations](ENVS.md#user-operations-feature-erc-4337) + - [Rollup chain](ENVS.md#rollup-chain) - [Export data to CSV file](ENVS.md#export-data-to-csv-file) - [Google analytics](ENVS.md#google-analytics) - [Mixpanel analytics](ENVS.md#mixpanel-analytics) + - [GrowthBook feature flagging and A/B testing](ENVS.md#growthbook-feature-flagging-and-ab-testing) - [GraphQL API documentation](ENVS.md#graphql-api-documentation) - [REST API documentation](ENVS.md#rest-api-documentation) - [Marketplace](ENVS.md#marketplace) @@ -46,11 +48,14 @@ Please be aware that all environment variables prefixed with `NEXT_PUBLIC_` will - [Web3 wallet integration](ENVS.md#web3-wallet-integration-add-token-or-network-to-the-wallet) (add token or network to the wallet) - [Transaction interpretation](ENVS.md#transaction-interpretation) - [Verified tokens info](ENVS.md#verified-tokens-info) + - [Name service integration](ENVS.md#name-service-integration) - [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) - [Sentry error monitoring](ENVS.md#sentry-error-monitoring) - [OpenTelemetry](ENVS.md#opentelemetry) + - [Swap button](ENVS.md#swap-button) - [3rd party services configuration](ENVS.md#external-services-configuration)   @@ -75,6 +80,7 @@ Please be aware that all environment variables prefixed with `NEXT_PUBLIC_` will | 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` | @@ -104,7 +110,6 @@ Please be aware that all environment variables prefixed with `NEXT_PUBLIC_` will | NEXT_PUBLIC_HOMEPAGE_CHARTS | `Array<'daily_txs' \| 'coin_price' \| 'market_cap' \| 'tvl'>` | List of charts displayed on the home page | - | - | `['daily_txs','coin_price','market_cap']` | | NEXT_PUBLIC_HOMEPAGE_PLATE_TEXT_COLOR | `string` | Text color of the hero plate on the homepage (escape "#" symbol if you use HEX color codes or use rgba-value instead) | - | `white` | `\#DCFE76` | | NEXT_PUBLIC_HOMEPAGE_PLATE_BACKGROUND | `string` | Background css value for hero plate on the homepage (escape "#" symbol if you use HEX color codes or use rgba-value instead) | - | `radial-gradient(103.03% 103.03% at 0% 0%, rgba(183, 148, 244, 0.8) 0%, rgba(0, 163, 196, 0.8) 100%), var(--chakra-colors-blue-400)` | `radial-gradient(at 15% 86%, hsla(350,65%,70%,1) 0px, transparent 50%)` \| `no-repeat bottom 20% right 0px/100% url(https://placekitten/1400/200)` | -| NEXT_PUBLIC_HOMEPAGE_SHOW_GAS_TRACKER | `boolean` | Set to false if network doesn't have gas tracker | - | `true` | `false` | | NEXT_PUBLIC_HOMEPAGE_SHOW_AVG_BLOCK_TIME | `boolean` | Set to false if average block time is useless for the network | - | `true` | `false` |   @@ -204,6 +209,7 @@ Settings for meta tags and OG tags | 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` | ##### Address views list | Id | Description | @@ -260,6 +266,8 @@ Settings for meta tags and OG tags | 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! 🤪` | @@ -268,12 +276,21 @@ Settings for meta tags and OG tags | Variable | Type| Description | Compulsoriness | Default value | Example value | | --- | --- | --- | --- | --- | --- | +| logo | `string` | URL to explorer logo file. Should be at least 40x40. | - | - | `'https://foo.app/icon.png'` | | title | `string` | Displayed name of the explorer | Required | - | `Anyblock` | | baseUrl | `string` | Base url of the explorer | Required | - | `https://explorer.anyblock.tools` | | paths | `Record<'tx' \| 'block' \| 'address' \| 'token', string>` | Map of explorer entities and their paths | Required | - | `{'tx':'/ethereum/poa/core/tx'}` | *Note* The url of an entity will be constructed as `]>`, e.g `https://explorer.anyblock.tools/ethereum/poa/core/tx/` +#### Contract code IDE configuration properties + +| Variable | Type| Description | Compulsoriness | Default value | Example value | +| --- | --- | --- | --- | --- | --- | +| title | `string` | Displayed name of the IDE | Required | - | `Remix IDE` | +| 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` | +   ## App features @@ -291,6 +308,17 @@ Settings for meta tags and OG tags   +### Gas tracker + +This feature is **enabled by default**. To switch it off pass `NEXT_PUBLIC_GAS_TRACKER_ENABLED=false`. + +| Variable | Type| Description | Compulsoriness | Default value | Example value | +| --- | --- | --- | --- | --- | --- | +| 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' ]` | + +  + ### Address verification in "My account" *Note* all ENV variables required for [My account](ENVS.md#my-account) feature should be passed alongside the following ones: @@ -306,7 +334,7 @@ Settings for meta tags and OG tags | Variable | Type| Description | Compulsoriness | Default value | Example value | | --- | --- | --- | --- | --- | --- | -| NEXT_PUBLIC_WALLET_CONNECT_PROJECT_ID | `string` | Project id for [WalletConnect](https://docs.walletconnect.com/2.0/web3modal/react/installation#obtain-project-id) integration | Required | - | `` | +| NEXT_PUBLIC_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` | @@ -322,7 +350,8 @@ This feature is **enabled by default** with the `slise` ads provider. To switch | Variable | Type| Description | Compulsoriness | Default value | Example value | | --- | --- | --- | --- | --- | --- | -| NEXT_PUBLIC_AD_BANNER_PROVIDER | `slise` \| `adbutler` \| `coinzilla` \| `none` | Ads provider | - | `slise` | `coinzilla` | +| 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'}` | @@ -347,22 +376,21 @@ This feature is **enabled by default** with the `coinzilla` ads provider. To swi   -### Optimistic rollup (L2) chain +### User operations feature (ERC-4337) | Variable | Type| Description | Compulsoriness | Default value | Example value | | --- | --- | --- | --- | --- | --- | -| NEXT_PUBLIC_IS_OPTIMISTIC_L2_NETWORK | `boolean` | Set to true for optimistic L2 solutions | Required | - | `true` | -| NEXT_PUBLIC_OPTIMISTIC_L2_WITHDRAWAL_URL | `string` | URL for optimistic L2 -> L1 withdrawals | Required | - | `https://app.optimism.io/bridge/withdraw` | -| NEXT_PUBLIC_L1_BASE_URL | `string` | Blockscout base URL for L1 network | Required | - | `'http://eth-goerli.blockscout.com'` | +| NEXT_PUBLIC_HAS_USER_OPS | `boolean` | Set to true to show user operations related data and pages | - | - | `true` |   -### ZkEvm rollup (L2) chain +### Rollup chain + | Variable | Type| Description | Compulsoriness | Default value | Example value | | --- | --- | --- | --- | --- | --- | -| NEXT_PUBLIC_IS_ZKEVM_L2_NETWORK | `boolean` | Set to true for zkevm L2 solutions | Required | - | `true` | -| NEXT_PUBLIC_L1_BASE_URL | `string` | Blockscout base URL for L1 network | Required | - | `'http://eth-goerli.blockscout.com'` | - +| 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` |   @@ -390,6 +418,14 @@ This feature is **enabled by default** with the `coinzilla` ads provider. To swi   +### GrowthBook feature flagging and A/B testing + +| Variable | Type| Description | Compulsoriness | Default value | Example value | +| --- | --- | --- | --- | --- | --- | +| NEXT_PUBLIC_GROWTH_BOOK_CLIENT_KEY | `string` | Client SDK key for [GrowthBook](https://www.growthbook.io/) service | true | - | `` | + +  + ### GraphQL API documentation This feature is **always enabled**, but you can configure its behavior by passing the following variables. @@ -412,9 +448,14 @@ This feature is **always enabled**, but you can configure its behavior by passin | Variable | Type| Description | Compulsoriness | Default value | Example value | | --- | --- | --- | --- | --- | --- | -| NEXT_PUBLIC_MARKETPLACE_CONFIG_URL | `string` | URL of configuration file (`.json` format only) which contains list of apps that will be shown on the marketplace page. See [below](#marketplace-app-configuration-properties) list of available properties for an app | Required | - | `https://example.com/marketplace_config.json` | +| NEXT_PUBLIC_MARKETPLACE_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` | #### Marketplace app configuration properties @@ -433,6 +474,8 @@ This feature is **always enabled**, but you can configure its behavior by passin | twitter | `string` | Displayed twitter link | - | `'https://twitter.com/blockscoutcom'` | | telegram | `string` | Displayed telegram link | - | `'https://t.me/poa_network'` | | github | `string` | Displayed github link | - | `'https://github.com/blockscout'` | +| 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 @@ -482,7 +525,7 @@ This feature is **enabled by default** with the `['metamask']` value. To switch | Variable | Type| Description | Compulsoriness | Default value | Example value | | --- | --- | --- | --- | --- | --- | -| NEXT_PUBLIC_TRANSACTION_INTERPRETATION_PROVIDER | `blockscout` \| `none` | Transaction interpretation provider that displays human readable transaction description | - | `none` | `blockscout` | +| NEXT_PUBLIC_TRANSACTION_INTERPRETATION_PROVIDER | `blockscout` \| `noves` \| `none` | Transaction interpretation provider that displays human readable transaction description | - | `none` | `blockscout` |   @@ -494,6 +537,26 @@ This feature is **enabled by default** with the `['metamask']` value. To switch   +### Name service integration + +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` | + +  + +### Data Availability + +This feature enables views related to blob transactions (EIP-4844), such as the Blob Txns tab on the Transactions page and the Blob details page. + +| Variable | Type| Description | Compulsoriness | Default value | Example value | +| --- | --- | --- | --- | --- | --- | +| NEXT_PUBLIC_DATA_AVAILABILITY_ENABLED | `boolean` | Set to true to enable blob transactions views. | Required | - | `true` | + +  + ### Bridged tokens 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. @@ -526,7 +589,11 @@ This feature allows users to view tokens that have been bridged from other EVM c ### Safe{Core} address tags -For the smart contract addresses which are [Safe{Core} accounts](https://safe.global/) public tag "Multisig: Safe" will be displayed in the address page header alongside to Safe logo. The Safe service is available only for certain networks, see full list [here](https://docs.safe.global/safe-core-api/available-services). Based on provided value of `NEXT_PUBLIC_NETWORK_ID`, the feature will be enabled or disabled. +For the smart contract addresses which are [Safe{Core} accounts](https://safe.global/) public tag "Multisig: Safe" will be displayed in the address page header alongside to Safe logo. + +| Variable | Type| Description | Compulsoriness | Default value | Example value | +| --- | --- | --- | --- | --- | --- | +| 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` |   @@ -540,6 +607,26 @@ For blockchains that implement SUAVE architecture additional fields will be show   +### 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 | +| --- | --- | --- | --- | --- | --- | +| NEXT_PUBLIC_METASUITES_ENABLED | `boolean` | Set to true to enable integration | Required | - | `true` | + +  + +### 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 | +| --- | --- | --- | --- | --- | --- | +| NEXT_PUBLIC_VALIDATORS_CHAIN_TYPE | `'stability'` | Chain type | Required | - | `'stability'` | + +  + ### Sentry error monitoring | Variable | Type| Description | Compulsoriness | Default value | Example value | @@ -562,6 +649,16 @@ OpenTelemetry SDK for Node.js app could be enabled by passing `OTEL_SDK_ENABLED=   +### Swap button + +If the feature is enabled, a Swap button will be displayed at the top of the explorer page, which will take you to the specified application in the marketplace or to an external site. + +| Variable | Type| Description | Compulsoriness | Default value | Example value | +| --- | --- | --- | --- | --- | --- | +| NEXT_PUBLIC_SWAP_BUTTON_URL | `string` | Application ID in the marketplace or website URL | - | - | `uniswap` | + +  + ## External services configuration ### Google ReCaptcha diff --git a/global.d.ts b/global.d.ts index 58230cb3ca..591cf3882d 100644 --- a/global.d.ts +++ b/global.d.ts @@ -1,6 +1,7 @@ import type { LuksoProfile } from '@lukso/web-components/dist/components/lukso-profile'; import type * as React from 'react'; -import type { WindowProvider } from 'wagmi'; + +import type { WalletProvider } from 'types/web3'; type CPreferences = { zone: string; @@ -15,7 +16,7 @@ type WebComponent = declare global { export interface Window { - ethereum?: WindowProvider; + ethereum?: WalletProvider | undefined; coinzilla_display: Array; ga?: { getAll: () => Array<{ get: (prop: string) => string }>; @@ -40,3 +41,5 @@ declare global { } } } + +export {}; diff --git a/icons/ABI_slim.svg b/icons/ABI_slim.svg new file mode 100644 index 0000000000..89532207b1 --- /dev/null +++ b/icons/ABI_slim.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/icons/ENS.svg b/icons/ENS.svg new file mode 100644 index 0000000000..9832944dab --- /dev/null +++ b/icons/ENS.svg @@ -0,0 +1,3 @@ + + + diff --git a/icons/ENS_slim.svg b/icons/ENS_slim.svg new file mode 100644 index 0000000000..cd999b523a --- /dev/null +++ b/icons/ENS_slim.svg @@ -0,0 +1,4 @@ + + + + diff --git a/icons/apps_list.svg b/icons/apps_list.svg new file mode 100644 index 0000000000..62cb5020d6 --- /dev/null +++ b/icons/apps_list.svg @@ -0,0 +1,3 @@ + + + diff --git a/icons/apps_xs.svg b/icons/apps_xs.svg new file mode 100644 index 0000000000..4daa74955d --- /dev/null +++ b/icons/apps_xs.svg @@ -0,0 +1,3 @@ + + + diff --git a/icons/beta.svg b/icons/beta.svg new file mode 100644 index 0000000000..bba1309f3a --- /dev/null +++ b/icons/beta.svg @@ -0,0 +1,4 @@ + + + + diff --git a/icons/beta_xs.svg b/icons/beta_xs.svg new file mode 100644 index 0000000000..a6dc48ee4e --- /dev/null +++ b/icons/beta_xs.svg @@ -0,0 +1,4 @@ + + + + diff --git a/icons/blob.svg b/icons/blob.svg new file mode 100644 index 0000000000..9dc2b542ff --- /dev/null +++ b/icons/blob.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/icons/blobs/image.svg b/icons/blobs/image.svg new file mode 100644 index 0000000000..be08dd269c --- /dev/null +++ b/icons/blobs/image.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/icons/blobs/raw.svg b/icons/blobs/raw.svg new file mode 100644 index 0000000000..8a97401ff5 --- /dev/null +++ b/icons/blobs/raw.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/icons/blobs/text.svg b/icons/blobs/text.svg new file mode 100644 index 0000000000..08ec8801bf --- /dev/null +++ b/icons/blobs/text.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/icons/brands/safe.svg b/icons/brands/safe.svg index 9e596a3821..8369513837 100644 --- a/icons/brands/safe.svg +++ b/icons/brands/safe.svg @@ -1,3 +1,3 @@ - - + + diff --git a/icons/brands/solidity_scan.svg b/icons/brands/solidity_scan.svg new file mode 100644 index 0000000000..ac5747c69a --- /dev/null +++ b/icons/brands/solidity_scan.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/icons/contracts.svg b/icons/contracts.svg new file mode 100644 index 0000000000..1f0b62afd2 --- /dev/null +++ b/icons/contracts.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/icons/contracts_verified.svg b/icons/contracts_verified.svg new file mode 100644 index 0000000000..2a004f596d --- /dev/null +++ b/icons/contracts_verified.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/icons/dots.svg b/icons/dots.svg new file mode 100644 index 0000000000..1ea165f92f --- /dev/null +++ b/icons/dots.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/icons/gas_xl.svg b/icons/gas_xl.svg new file mode 100644 index 0000000000..5a3913ac16 --- /dev/null +++ b/icons/gas_xl.svg @@ -0,0 +1,3 @@ + + + diff --git a/icons/gear_slim.svg b/icons/gear_slim.svg new file mode 100644 index 0000000000..abc14e6a78 --- /dev/null +++ b/icons/gear_slim.svg @@ -0,0 +1,3 @@ + + + diff --git a/icons/integration/full.svg b/icons/integration/full.svg new file mode 100644 index 0000000000..0ac50a345b --- /dev/null +++ b/icons/integration/full.svg @@ -0,0 +1,3 @@ + + + diff --git a/icons/integration/partial.svg b/icons/integration/partial.svg new file mode 100644 index 0000000000..2f7a76969f --- /dev/null +++ b/icons/integration/partial.svg @@ -0,0 +1,3 @@ + + + diff --git a/icons/lightning.svg b/icons/lightning.svg index 91b1ae92ca..03fea73d75 100644 --- a/icons/lightning.svg +++ b/icons/lightning.svg @@ -1,3 +1,3 @@ - - + + diff --git a/icons/rocket_xl.svg b/icons/rocket_xl.svg new file mode 100644 index 0000000000..8b3f4ccdbf --- /dev/null +++ b/icons/rocket_xl.svg @@ -0,0 +1,3 @@ + + + diff --git a/icons/star_filled.svg b/icons/star_filled.svg index 10a3cfb0c8..2bdea23a41 100644 --- a/icons/star_filled.svg +++ b/icons/star_filled.svg @@ -1,3 +1,3 @@ - - + + diff --git a/icons/star_outline.svg b/icons/star_outline.svg index e6fd05339c..bf2eca9845 100644 --- a/icons/star_outline.svg +++ b/icons/star_outline.svg @@ -1,3 +1,3 @@ - - + + diff --git a/icons/swap.svg b/icons/swap.svg new file mode 100644 index 0000000000..63d915c99b --- /dev/null +++ b/icons/swap.svg @@ -0,0 +1,3 @@ + + + diff --git a/icons/up.svg b/icons/up.svg new file mode 100644 index 0000000000..375381a790 --- /dev/null +++ b/icons/up.svg @@ -0,0 +1,3 @@ + + + diff --git a/icons/user_op.svg b/icons/user_op.svg new file mode 100644 index 0000000000..02bc701401 --- /dev/null +++ b/icons/user_op.svg @@ -0,0 +1,4 @@ + + + + diff --git a/icons/user_op_slim.svg b/icons/user_op_slim.svg new file mode 100644 index 0000000000..d8c64b52b8 --- /dev/null +++ b/icons/user_op_slim.svg @@ -0,0 +1,3 @@ + + + diff --git a/icons/validator.svg b/icons/validator.svg new file mode 100644 index 0000000000..e77bb0ba5d --- /dev/null +++ b/icons/validator.svg @@ -0,0 +1,3 @@ + + + diff --git a/icons/vertical_dots.svg b/icons/vertical_dots.svg deleted file mode 100644 index 0d4cf417f4..0000000000 --- a/icons/vertical_dots.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/jest/lib.tsx b/jest/lib.tsx index 2c590932cf..048f26733a 100644 --- a/jest/lib.tsx +++ b/jest/lib.tsx @@ -1,4 +1,5 @@ import { ChakraProvider } from '@chakra-ui/react'; +import { GrowthBookProvider } from '@growthbook/growthbook-react'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import type { RenderOptions } from '@testing-library/react'; import { render } from '@testing-library/react'; @@ -19,6 +20,8 @@ const PAGE_PROPS = { hash: '', number: '', q: '', + name: '', + adBannerProvider: '', }; const TestApp = ({ children }: {children: React.ReactNode}) => { @@ -36,9 +39,11 @@ const TestApp = ({ children }: {children: React.ReactNode}) => { - - { children } - + + + { children } + + diff --git a/lib/api/buildUrl.test.ts b/lib/api/buildUrl.test.ts new file mode 100644 index 0000000000..29ea283a95 --- /dev/null +++ b/lib/api/buildUrl.test.ts @@ -0,0 +1,43 @@ +import buildUrl from './buildUrl'; + +test('builds URL for resource without path params', () => { + const url = buildUrl('config_backend_version'); + expect(url).toBe('https://localhost:3003/api/v2/config/backend-version'); +}); + +test('builds URL for resource with path params', () => { + const url = buildUrl('block', { height_or_hash: '42' }); + expect(url).toBe('https://localhost:3003/api/v2/blocks/42'); +}); + +describe('falsy query parameters', () => { + test('leaves "false" as query parameter', () => { + const url = buildUrl('block', { height_or_hash: '42' }, { includeTx: false }); + expect(url).toBe('https://localhost:3003/api/v2/blocks/42?includeTx=false'); + }); + + test('leaves "null" as query parameter', () => { + const url = buildUrl('block', { height_or_hash: '42' }, { includeTx: null }); + expect(url).toBe('https://localhost:3003/api/v2/blocks/42?includeTx=null'); + }); + + test('strips out empty string as query parameter', () => { + const url = buildUrl('block', { height_or_hash: '42' }, { includeTx: null, sort: '' }); + expect(url).toBe('https://localhost:3003/api/v2/blocks/42?includeTx=null'); + }); + + test('strips out "undefined" as query parameter', () => { + const url = buildUrl('block', { height_or_hash: '42' }, { includeTx: null, sort: undefined }); + expect(url).toBe('https://localhost:3003/api/v2/blocks/42?includeTx=null'); + }); +}); + +test('builds URL with array-like query parameters', () => { + const url = buildUrl('block', { height_or_hash: '42' }, { includeTx: [ '0x11', '0x22' ], sort: 'asc' }); + expect(url).toBe('https://localhost:3003/api/v2/blocks/42?includeTx=0x11%2C0x22&sort=asc'); +}); + +test('builds URL for resource with custom API endpoint', () => { + const url = buildUrl('token_verified_info', { chainId: '42', hash: '0x11' }); + expect(url).toBe('https://localhost:3005/api/v1/chains/42/token-infos/0x11'); +}); diff --git a/lib/api/buildUrl.ts b/lib/api/buildUrl.ts index 2feb358e1c..07805dbd9c 100644 --- a/lib/api/buildUrl.ts +++ b/lib/api/buildUrl.ts @@ -9,7 +9,7 @@ import type { ApiResource, ResourceName, ResourcePathParams } from './resources' export default function buildUrl( resourceName: R, pathParams?: ResourcePathParams, - queryParams?: Record | number | null | undefined>, + queryParams?: Record | number | boolean | null | undefined>, ): string { const resource: ApiResource = RESOURCES[resourceName]; const baseUrl = isNeedProxy() ? config.app.baseUrl : (resource.endpoint || config.api.endpoint); diff --git a/lib/api/resources.ts b/lib/api/resources.ts index 21d1798204..ae6547ae87 100644 --- a/lib/api/resources.ts +++ b/lib/api/resources.ts @@ -31,20 +31,42 @@ import type { AddressNFTTokensFilter, } from 'types/api/address'; import type { AddressesResponse } from 'types/api/addresses'; +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 { SmartContract, SmartContractReadMethod, SmartContractWriteMethod, SmartContractVerificationConfig, SolidityscanReport } from 'types/api/contract'; +import type { + SmartContract, + SmartContractReadMethod, + SmartContractWriteMethod, + SmartContractVerificationConfig, + SolidityscanReport, + 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 { L2DepositsResponse, L2DepositsItem } from 'types/api/l2Deposits'; -import type { L2OutputRootsResponse } from 'types/api/l2OutputRoots'; -import type { L2TxnBatchesResponse } from 'types/api/l2TxnBatches'; -import type { L2WithdrawalsResponse } from 'types/api/l2Withdrawals'; import type { LogsResponseTx, LogsResponseAddress } from 'types/api/log'; +import type { NovesAccountHistoryResponse, NovesDescribeTxsResponse, NovesResponseData } from 'types/api/noves'; +import type { + OptimisticL2DepositsResponse, + OptimisticL2DepositsItem, + OptimisticL2OutputRootsResponse, + OptimisticL2TxnBatchesResponse, + OptimisticL2WithdrawalsResponse, +} 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 { TokenCounters, @@ -64,24 +86,33 @@ import type { Transaction, TransactionsResponseWatchlist, TransactionsSorting, + TransactionsResponseWithBlobs, } from 'types/api/transaction'; import type { TxInterpretationResponse } from 'types/api/txInterpretation'; -import type { TTxsFilters } from 'types/api/txsFilters'; +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 { 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/zkEvmL2TxnBatches'; +import type { ZkEvmL2TxnBatch, ZkEvmL2TxnBatchesItem, ZkEvmL2TxnBatchesResponse, ZkEvmL2TxnBatchTxs } from 'types/api/zkEvmL2'; +import type { ZkSyncBatch, ZkSyncBatchesResponse, ZkSyncBatchTxs } from 'types/api/zkSyncL2'; +import type { MarketplaceAppOverview } from 'types/client/marketplace'; import type { ArrayElement } from 'types/utils'; import config from 'configs/app'; +const marketplaceFeature = getFeaturePayload(config.features.marketplace); +const marketplaceApi = marketplaceFeature && 'api' in marketplaceFeature ? marketplaceFeature.api : undefined; + export interface ApiResource { path: ResourcePath; endpoint?: string; basePath?: string; pathParams?: Array; needAuth?: boolean; // for external APIs which require authentication + headers?: RequestInit['headers']; } export const SORTING_FIELDS = [ 'sort', 'order' ]; @@ -158,7 +189,7 @@ export const RESOURCES = { needAuth: true, }, - // STATS + // STATS MICROSERVICE API stats_counters: { path: '/api/v1/counters', endpoint: getFeaturePayload(config.features.stats)?.api.endpoint, @@ -176,6 +207,34 @@ export const RESOURCES = { basePath: getFeaturePayload(config.features.stats)?.api.basePath, }, + // NAME SERVICE + addresses_lookup: { + path: '/api/v1/:chainId/addresses\\:lookup', + pathParams: [ 'chainId' as const ], + endpoint: getFeaturePayload(config.features.nameService)?.api.endpoint, + basePath: getFeaturePayload(config.features.nameService)?.api.basePath, + filterFields: [ 'address' as const, 'resolved_to' as const, 'owned_by' as const, 'only_active' as const ], + }, + domain_info: { + path: '/api/v1/:chainId/domains/:name', + pathParams: [ 'chainId' as const, 'name' as const ], + endpoint: getFeaturePayload(config.features.nameService)?.api.endpoint, + basePath: getFeaturePayload(config.features.nameService)?.api.basePath, + }, + domain_events: { + path: '/api/v1/:chainId/domains/:name/events', + pathParams: [ 'chainId' as const, 'name' as const ], + endpoint: getFeaturePayload(config.features.nameService)?.api.endpoint, + basePath: getFeaturePayload(config.features.nameService)?.api.basePath, + }, + domains_lookup: { + path: '/api/v1/:chainId/domains\\:lookup', + pathParams: [ 'chainId' as const ], + endpoint: getFeaturePayload(config.features.nameService)?.api.endpoint, + basePath: getFeaturePayload(config.features.nameService)?.api.basePath, + filterFields: [ 'name' as const, 'only_active' as const ], + }, + // VISUALIZATION visualize_sol2uml: { path: '/api/v1/solidity\\:visualize-contracts', @@ -183,6 +242,20 @@ export const RESOURCES = { basePath: getFeaturePayload(config.features.sol2uml)?.api.basePath, }, + // MARKETPLACE + marketplace_dapps: { + path: '/api/v1/chains/:chainId/marketplace/dapps', + pathParams: [ 'chainId' as const ], + endpoint: marketplaceApi?.endpoint, + basePath: marketplaceApi?.basePath, + }, + marketplace_dapp: { + path: '/api/v1/chains/:chainId/marketplace/dapps/:dappId', + pathParams: [ 'chainId' as const, 'dappId' as const ], + endpoint: marketplaceApi?.endpoint, + basePath: marketplaceApi?.basePath, + }, + // BLOCKS, TXS blocks: { path: '/api/v2/blocks', @@ -195,7 +268,7 @@ export const RESOURCES = { block_txs: { path: '/api/v2/blocks/:height_or_hash/transactions', pathParams: [ 'height_or_hash' as const ], - filterFields: [], + filterFields: [ 'type' as const ], }, block_withdrawals: { path: '/api/v2/blocks/:height_or_hash/withdrawals', @@ -210,6 +283,10 @@ export const RESOURCES = { path: '/api/v2/transactions', filterFields: [ 'filter' as const, 'type' as const, 'method' as const ], }, + txs_with_blobs: { + path: '/api/v2/transactions', + filterFields: [ 'type' as const ], + }, txs_watchlist: { path: '/api/v2/transactions/watchlist', filterFields: [ ], @@ -247,6 +324,10 @@ export const RESOURCES = { pathParams: [ 'hash' as const ], filterFields: [], }, + tx_blobs: { + path: '/api/v2/transactions/:hash/blobs', + pathParams: [ 'hash' as const ], + }, tx_interpretation: { path: '/api/v2/transactions/:hash/summary', pathParams: [ 'hash' as const ], @@ -373,6 +454,10 @@ export const RESOURCES = { path: '/api/v2/smart-contracts/:hash/solidityscan-report', pathParams: [ 'hash' as const ], }, + contract_security_audits: { + path: '/api/v2/smart-contracts/:hash/audit-reports', + pathParams: [ 'hash' as const ], + }, verified_contracts: { path: '/api/v2/smart-contracts', @@ -441,16 +526,21 @@ export const RESOURCES = { filterFields: [], }, - // HOMEPAGE - homepage_stats: { + // APP STATS + stats: { path: '/api/v2/stats', + headers: { + 'updated-gas-oracle': 'true', + }, }, - homepage_chart_txs: { + stats_charts_txs: { path: '/api/v2/stats/charts/transactions', }, - homepage_chart_market: { + stats_charts_market: { path: '/api/v2/stats/charts/market', }, + + // HOMEPAGE homepage_blocks: { path: '/api/v2/main-page/blocks', }, @@ -472,6 +562,9 @@ export const RESOURCES = { homepage_zkevm_latest_batch: { path: '/api/v2/main-page/zkevm/batches/latest-number', }, + homepage_zksync_latest_batch: { + path: '/api/v2/main-page/zksync/batches/latest-number', + }, // SEARCH quick_search: { @@ -486,7 +579,7 @@ export const RESOURCES = { path: '/api/v2/search/check-redirect', }, - // L2 + // optimistic L2 l2_deposits: { path: '/api/v2/optimism/deposits', filterFields: [], @@ -523,6 +616,7 @@ export const RESOURCES = { path: '/api/v2/optimism/txn-batches/count', }, + // zkEvm L2 zkevm_l2_txn_batches: { path: '/api/v2/zkevm/batches', filterFields: [], @@ -536,12 +630,102 @@ export const RESOURCES = { path: '/api/v2/zkevm/batches/:number', pathParams: [ 'number' as const ], }, + zkevm_l2_txn_batch_txs: { path: '/api/v2/transactions/zkevm-batch/:number', pathParams: [ 'number' as const ], filterFields: [], }, + // zkSync L2 + zksync_l2_txn_batches: { + path: '/api/v2/zksync/batches', + filterFields: [], + }, + + zksync_l2_txn_batches_count: { + path: '/api/v2/zksync/batches/count', + }, + + zksync_l2_txn_batch: { + path: '/api/v2/zksync/batches/:number', + pathParams: [ 'number' as const ], + }, + + zksync_l2_txn_batch_txs: { + path: '/api/v2/transactions/zksync-batch/:number', + pathParams: [ 'number' as const ], + filterFields: [], + }, + + // SHIBARIUM L2 + shibarium_deposits: { + path: '/api/v2/shibarium/deposits', + filterFields: [], + }, + + shibarium_deposits_count: { + path: '/api/v2/shibarium/deposits/count', + }, + + shibarium_withdrawals: { + path: '/api/v2/shibarium/withdrawals', + filterFields: [], + }, + + shibarium_withdrawals_count: { + path: '/api/v2/shibarium/withdrawals/count', + }, + + // NOVES-FI + noves_transaction: { + path: '/api/v2/proxy/noves-fi/transactions/:hash', + pathParams: [ 'hash' as const ], + }, + noves_address_history: { + path: '/api/v2/proxy/noves-fi/addresses/:address/transactions', + pathParams: [ 'address' as const ], + filterFields: [], + }, + noves_describe_txs: { + path: '/api/v2/proxy/noves-fi/transaction-descriptions', + }, + + // USER OPS + user_ops: { + path: '/api/v2/proxy/account-abstraction/operations', + filterFields: [ 'transaction_hash' as const, 'sender' as const ], + }, + user_op: { + path: '/api/v2/proxy/account-abstraction/operations/:hash', + pathParams: [ 'hash' as const ], + }, + user_ops_account: { + path: '/api/v2/proxy/account-abstraction/accounts/:hash', + pathParams: [ 'hash' as const ], + }, + user_op_interpretation: { + path: '/api/v2/proxy/account-abstraction/operations/:hash/summary', + pathParams: [ 'hash' as const ], + }, + + // VALIDATORS + validators: { + path: '/api/v2/validators/:chainType', + pathParams: [ 'chainType' as const ], + filterFields: [ 'address_hash' as const, 'state_filter' as const ], + }, + validators_counters: { + path: '/api/v2/validators/:chainType/counters', + pathParams: [ 'chainType' as const ], + }, + + // BLOBS + blob: { + path: '/api/v2/blobs/:hash', + pathParams: [ 'hash' as const ], + }, + // CONFIGS config_backend_version: { path: '/api/v2/config/backend-version', @@ -604,8 +788,8 @@ export interface ResourceError { export type ResourceErrorAccount = ResourceError<{ errors: T }> export type PaginatedResources = 'blocks' | 'block_txs' | -'txs_validated' | 'txs_pending' | 'txs_watchlist' | 'txs_execution_node' | -'tx_internal_txs' | 'tx_logs' | 'tx_token_transfers' | 'tx_state_changes' | +'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' | 'address_txs' | 'address_internal_txs' | 'address_token_transfers' | 'address_blocks_validated' | 'address_coin_balance' | 'search' | @@ -614,14 +798,20 @@ export type PaginatedResources = 'blocks' | 'block_txs' | 'token_instance_transfers' | 'token_instance_holders' | 'verified_contracts' | 'l2_output_roots' | 'l2_withdrawals' | 'l2_txn_batches' | 'l2_deposits' | +'shibarium_deposits' | 'shibarium_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'; +'watchlist' | 'private_tags_address' | 'private_tags_tx' | +'domains_lookup' | 'addresses_lookup' | 'user_ops' | 'validators' | 'noves_address_history'; export type PaginatedResponse = ResourcePayload; /* eslint-disable @typescript-eslint/indent */ -export type ResourcePayload = +// !!! IMPORTANT !!! +// Don't add any new types here because TypeScript cannot handle it properly +// use ResourcePayloadB instead +export type ResourcePayloadA = Q extends 'user_info' ? UserInfo : Q extends 'custom_abi' ? CustomAbis : Q extends 'public_tags' ? PublicTags : @@ -632,16 +822,17 @@ Q extends 'watchlist' ? WatchlistResponse : Q extends 'verified_addresses' ? VerifiedAddressResponse : Q extends 'token_info_applications_config' ? TokenInfoApplicationConfig : Q extends 'token_info_applications' ? TokenInfoApplications : -Q extends 'homepage_stats' ? HomeStats : -Q extends 'homepage_chart_txs' ? ChartTransactionResponse : -Q extends 'homepage_chart_market' ? ChartMarketResponse : +Q extends 'stats' ? HomeStats : +Q extends 'stats_charts_txs' ? ChartTransactionResponse : +Q extends 'stats_charts_market' ? ChartMarketResponse : Q extends 'homepage_blocks' ? Array : Q extends 'homepage_txs' ? Array : Q extends 'homepage_txs_watchlist' ? Array : -Q extends 'homepage_deposits' ? Array : +Q extends 'homepage_deposits' ? Array : Q extends 'homepage_zkevm_l2_batches' ? { items: Array } : Q extends 'homepage_indexing_status' ? IndexingStatus : Q extends 'homepage_zkevm_latest_batch' ? number : +Q extends 'homepage_zksync_latest_batch' ? number : Q extends 'stats_counters' ? Counters : Q extends 'stats_lines' ? StatsCharts : Q extends 'stats_line' ? StatsChart : @@ -651,6 +842,7 @@ Q extends 'block_txs' ? BlockTransactionsResponse : Q extends 'block_withdrawals' ? BlockWithdrawalsResponse : Q extends 'txs_validated' ? TransactionsResponseValidated : Q extends 'txs_pending' ? TransactionsResponsePending : +Q extends 'txs_with_blobs' ? TransactionsResponseWithBlobs : Q extends 'txs_watchlist' ? TransactionsResponseWatchlist : Q extends 'txs_execution_node' ? TransactionsResponseValidated : Q extends 'tx' ? Transaction : @@ -659,6 +851,7 @@ Q extends 'tx_logs' ? LogsResponseTx : Q extends 'tx_token_transfers' ? TokenTransferResponse : Q extends 'tx_raw_trace' ? RawTracesResponse : Q extends 'tx_state_changes' ? TxStateChanges : +Q extends 'tx_blobs' ? TxBlobs : Q extends 'tx_interpretation' ? TxInterpretationResponse : Q extends 'addresses' ? AddressesResponse : Q extends 'address' ? Address : @@ -703,10 +896,10 @@ Q extends 'visualize_sol2uml' ? VisualizedContract : Q extends 'contract_verification_config' ? SmartContractVerificationConfig : Q extends 'withdrawals' ? WithdrawalsResponse : Q extends 'withdrawals_counters' ? WithdrawalsCounters : -Q extends 'l2_output_roots' ? L2OutputRootsResponse : -Q extends 'l2_withdrawals' ? L2WithdrawalsResponse : -Q extends 'l2_deposits' ? L2DepositsResponse : -Q extends 'l2_txn_batches' ? L2TxnBatchesResponse : +Q extends 'l2_output_roots' ? 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 : @@ -717,12 +910,52 @@ Q extends 'zkevm_l2_txn_batch' ? ZkEvmL2TxnBatch : Q extends 'zkevm_l2_txn_batch_txs' ? ZkEvmL2TxnBatchTxs : Q extends 'config_backend_version' ? BackendVersionConfig : never; +// !!! IMPORTANT !!! +// See comment above /* eslint-enable @typescript-eslint/indent */ +/* eslint-disable @typescript-eslint/indent */ +export type ResourcePayloadB = +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 'shibarium_withdrawals' ? ShibariumWithdrawalsResponse : +Q extends 'shibarium_deposits' ? ShibariumDepositsResponse : +Q extends 'shibarium_withdrawals_count' ? number : +Q extends 'shibarium_deposits_count' ? number : +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 'user_ops' ? UserOpsResponse : +Q extends 'user_op' ? UserOp : +Q extends 'user_ops_account' ? UserOpsAccount : +Q extends 'user_op_interpretation'? TxInterpretationResponse : +Q extends 'noves_transaction' ? NovesResponseData : +Q extends 'noves_address_history' ? NovesAccountHistoryResponse : +Q extends 'noves_describe_txs' ? NovesDescribeTxsResponse : +never; +/* eslint-enable @typescript-eslint/indent */ + +export type ResourcePayload = ResourcePayloadA | ResourcePayloadB; +export type PaginatedResponseItems = Q extends PaginatedResources ? ResourcePayloadA['items'] | ResourcePayloadB['items'] : never; +export type PaginatedResponseNextPageParams = Q extends PaginatedResources ? + ResourcePayloadA['next_page_params'] | ResourcePayloadB['next_page_params'] : + never; + /* eslint-disable @typescript-eslint/indent */ export type PaginationFilters = Q extends 'blocks' ? BlockFilters : +Q extends 'block_txs' ? TTxsWithBlobsFilters : Q extends 'txs_validated' | 'txs_pending' ? TTxsFilters : +Q extends 'txs_with_blobs' ? TTxsWithBlobsFilters : Q extends 'tx_token_transfers' ? TokenTransferFilters : Q extends 'token_transfers' ? TokenTransferFilters : Q extends 'address_txs' | 'address_internal_txs' ? AddressTxsFilters : @@ -735,6 +968,10 @@ Q extends 'token_inventory' ? TokenInventoryFilters : Q extends 'tokens' ? TokensFilters : Q extends 'tokens_bridged' ? TokensBridgedFilters : Q extends 'verified_contracts' ? VerifiedContractsFilters : +Q extends 'addresses_lookup' ? EnsAddressLookupFilters : +Q extends 'domains_lookup' ? EnsDomainLookupFilters : +Q extends 'user_ops' ? UserOpsFilters : +Q extends 'validators' ? ValidatorsFilters : never; /* eslint-enable @typescript-eslint/indent */ @@ -744,5 +981,8 @@ Q extends 'tokens' ? TokensSorting : Q extends 'tokens_bridged' ? TokensSorting : Q extends 'verified_contracts' ? VerifiedContractsSorting : Q extends 'address_txs' ? TransactionsSorting : +Q extends 'addresses_lookup' ? EnsLookupSorting : +Q extends 'domains_lookup' ? EnsLookupSorting : +Q extends 'validators' ? ValidatorsSorting : never; /* eslint-enable @typescript-eslint/indent */ diff --git a/lib/api/useApiFetch.tsx b/lib/api/useApiFetch.tsx index 453f3dc753..85da773ab1 100644 --- a/lib/api/useApiFetch.tsx +++ b/lib/api/useApiFetch.tsx @@ -1,4 +1,5 @@ import { useQueryClient } from '@tanstack/react-query'; +import _omit from 'lodash/omit'; import _pickBy from 'lodash/pickBy'; import React from 'react'; @@ -18,8 +19,8 @@ import type { ApiResource, ResourceName, ResourcePathParams } from './resources' export interface Params { pathParams?: ResourcePathParams; - queryParams?: Record | number | undefined>; - fetchParams?: Pick; + queryParams?: Record | number | boolean | undefined>; + fetchParams?: Pick; } export default function useApiFetch() { @@ -40,6 +41,8 @@ export default function useApiFetch() { 'x-endpoint': resource.endpoint && isNeedProxy() ? resource.endpoint : undefined, Authorization: resource.endpoint && resource.needAuth ? apiToken : undefined, 'x-csrf-token': withBody && csrfToken ? csrfToken : undefined, + ...resource.headers, + ...fetchParams?.headers, }, Boolean) as HeadersInit; return fetch( @@ -51,10 +54,11 @@ export default function useApiFetch() { // change condition here if something is changed credentials: config.features.account.isEnabled ? 'include' : 'same-origin', headers, - ...fetchParams, + ..._omit(fetchParams, 'headers'), }, { resource: resource.path, + omitSentryErrorLog: true, // disable logging of API errors to Sentry }, ); }, [ fetch, csrfToken ]); diff --git a/lib/api/useQueryClientConfig.tsx b/lib/api/useQueryClientConfig.tsx index 6ff2fed457..b7a8fd2550 100644 --- a/lib/api/useQueryClientConfig.tsx +++ b/lib/api/useQueryClientConfig.tsx @@ -4,20 +4,22 @@ import React from 'react'; import getErrorObjPayload from 'lib/errors/getErrorObjPayload'; import getErrorObjStatusCode from 'lib/errors/getErrorObjStatusCode'; +export const retry = (failureCount: number, error: unknown) => { + const errorPayload = getErrorObjPayload<{ status: number }>(error); + const status = errorPayload?.status || getErrorObjStatusCode(error); + if (status && status >= 400 && status < 500) { + // don't do retry for client error responses + return false; + } + return failureCount < 2; +}; + export default function useQueryClientConfig() { const [ queryClient ] = React.useState(() => new QueryClient({ defaultOptions: { queries: { refetchOnWindowFocus: false, - retry: (failureCount, error) => { - const errorPayload = getErrorObjPayload<{ status: number }>(error); - const status = errorPayload?.status || getErrorObjStatusCode(error); - if (status && status >= 400 && status < 500) { - // don't do retry for client error responses - return false; - } - return failureCount < 2; - }, + retry, throwOnError: (error) => { const status = getErrorObjStatusCode(error); // don't catch error for "Too many requests" response diff --git a/lib/blob/guessDataType.ts b/lib/blob/guessDataType.ts new file mode 100644 index 0000000000..fb409019e3 --- /dev/null +++ b/lib/blob/guessDataType.ts @@ -0,0 +1,12 @@ +import filetype from 'magic-bytes.js'; + +import hexToBytes from 'lib/hexToBytes'; + +import removeNonSignificantZeroBytes from './removeNonSignificantZeroBytes'; + +export default function guessDataType(data: string) { + const bytes = new Uint8Array(hexToBytes(data)); + const filteredBytes = removeNonSignificantZeroBytes(bytes); + + return filetype(filteredBytes)[0]; +} diff --git a/lib/blob/index.ts b/lib/blob/index.ts new file mode 100644 index 0000000000..ab178e8231 --- /dev/null +++ b/lib/blob/index.ts @@ -0,0 +1 @@ +export { default as guessDataType } from './guessDataType'; diff --git a/lib/blob/removeNonSignificantZeroBytes.ts b/lib/blob/removeNonSignificantZeroBytes.ts new file mode 100644 index 0000000000..9b25287478 --- /dev/null +++ b/lib/blob/removeNonSignificantZeroBytes.ts @@ -0,0 +1,20 @@ +export default function removeNonSignificantZeroBytes(bytes: Uint8Array) { + return shouldRemoveBytes(bytes) ? bytes.filter((item, index) => index % 32) : bytes; +} + +// check if every 0, 32, 64, etc byte is 0 in the provided array +function shouldRemoveBytes(bytes: Uint8Array) { + let result = true; + + for (let index = 0; index < bytes.length; index += 32) { + const element = bytes[index]; + if (element === 0) { + continue; + } else { + result = false; + break; + } + } + + return result; +} diff --git a/lib/bytesToBase64.ts b/lib/bytesToBase64.ts new file mode 100644 index 0000000000..60b23ad437 --- /dev/null +++ b/lib/bytesToBase64.ts @@ -0,0 +1,10 @@ +export default function bytesToBase64(bytes: Uint8Array) { + let binary = ''; + for (const byte of bytes) { + binary += String.fromCharCode(byte); + } + + const base64String = btoa(binary); + + return base64String; +} diff --git a/lib/contexts/addressHighlight.tsx b/lib/contexts/addressHighlight.tsx new file mode 100644 index 0000000000..84f5f896ec --- /dev/null +++ b/lib/contexts/addressHighlight.tsx @@ -0,0 +1,69 @@ +import React from 'react'; + +interface AddressHighlightProviderProps { + children: React.ReactNode; +} + +interface TAddressHighlightContext { + onMouseEnter: (event: React.MouseEvent) => void; + onMouseLeave: (event: React.MouseEvent) => void; +} + +export const AddressHighlightContext = React.createContext(null); + +export function AddressHighlightProvider({ children }: AddressHighlightProviderProps) { + const timeoutId = React.useRef(null); + const hashRef = React.useRef(null); + + const onMouseEnter = React.useCallback((event: React.MouseEvent) => { + const hash = event.currentTarget.getAttribute('data-hash'); + if (hash) { + hashRef.current = hash; + timeoutId.current = window.setTimeout(() => { + // for better performance we update DOM-nodes directly bypassing React reconciliation + const nodes = window.document.querySelectorAll(`[data-hash="${ hashRef.current }"]`); + for (const node of nodes) { + node.classList.add('address-entity_highlighted'); + } + }, 100); + } + }, []); + + const onMouseLeave = React.useCallback(() => { + if (hashRef.current) { + const nodes = window.document.querySelectorAll(`[data-hash="${ hashRef.current }"]`); + for (const node of nodes) { + node.classList.remove('address-entity_highlighted'); + } + hashRef.current = null; + } + typeof timeoutId.current === 'number' && window.clearTimeout(timeoutId.current); + }, []); + + const value = React.useMemo(() => { + return { + onMouseEnter, + onMouseLeave, + }; + }, [ onMouseEnter, onMouseLeave ]); + + React.useEffect(() => { + return () => { + typeof timeoutId.current === 'number' && window.clearTimeout(timeoutId.current); + }; + }, []); + + return ( + + { children } + + ); +} + +export function useAddressHighlightContext() { + const context = React.useContext(AddressHighlightContext); + if (context === undefined) { + return null; + } + return context; +} diff --git a/lib/contexts/app.tsx b/lib/contexts/app.tsx index f0a1eec14e..1d7c7bf8c3 100644 --- a/lib/contexts/app.tsx +++ b/lib/contexts/app.tsx @@ -15,6 +15,8 @@ const AppContext = createContext({ hash: '', number: '', q: '', + name: '', + adBannerProvider: '', }); export function AppContextProvider({ children, pageProps }: Props) { diff --git a/lib/contracts/licenses.ts b/lib/contracts/licenses.ts new file mode 100644 index 0000000000..123149e294 --- /dev/null +++ b/lib/contracts/licenses.ts @@ -0,0 +1,88 @@ +import type { ContractLicense } from 'types/client/contract'; + +export const CONTRACT_LICENSES: Array = [ + { + type: 'none', + label: 'None', + title: 'No License', + url: 'https://choosealicense.com/no-permission/', + }, + { + type: 'unlicense', + label: 'Unlicense', + title: 'The Unlicense', + url: 'https://choosealicense.com/licenses/unlicense/', + }, + { + type: 'mit', + label: 'MIT', + title: 'MIT License', + url: 'https://choosealicense.com/licenses/mit/', + }, + { + type: 'gnu_gpl_v2', + label: 'GNU GPLv2', + title: 'GNU General Public License v2.0', + url: 'https://choosealicense.com/licenses/gpl-2.0/', + }, + { + type: 'gnu_gpl_v3', + label: 'GNU GPLv3', + title: 'GNU General Public License v3.0', + url: 'https://choosealicense.com/licenses/gpl-3.0/', + }, + { + type: 'gnu_lgpl_v2_1', + label: 'GNU LGPLv2.1', + title: 'GNU Lesser General Public License v2.1', + url: 'https://choosealicense.com/licenses/lgpl-2.1/', + }, + { + type: 'gnu_lgpl_v3', + label: 'GNU LGPLv3', + title: 'GNU Lesser General Public License v3.0', + url: 'https://choosealicense.com/licenses/lgpl-3.0/', + }, + { + type: 'bsd_2_clause', + label: 'BSD-2-Clause', + title: 'BSD 2-clause "Simplified" license', + url: 'https://choosealicense.com/licenses/bsd-2-clause/', + }, + { + type: 'bsd_3_clause', + label: 'BSD-3-Clause', + title: 'BSD 3-clause "New" Or "Revised" license', + url: 'https://choosealicense.com/licenses/bsd-3-clause/', + }, + { + type: 'mpl_2_0', + label: 'MPL-2.0', + title: 'Mozilla Public License 2.0', + url: 'https://choosealicense.com/licenses/mpl-2.0/', + }, + { + type: 'osl_3_0', + label: 'OSL-3.0', + title: 'Open Software License 3.0', + url: 'https://choosealicense.com/licenses/osl-3.0/', + }, + { + type: 'apache_2_0', + label: 'Apache', + title: 'Apache 2.0', + url: 'https://choosealicense.com/licenses/apache-2.0/', + }, + { + type: 'gnu_agpl_v3', + label: 'GNU AGPLv3', + title: 'GNU Affero General Public License', + url: 'https://choosealicense.com/licenses/agpl-3.0/', + }, + { + type: 'bsl_1_1', + label: 'BSL 1.1', + title: 'Business Source License', + url: 'https://mariadb.com/bsl11/', + }, +]; diff --git a/lib/cookies.ts b/lib/cookies.ts index 4e2d494102..cef6a38a42 100644 --- a/lib/cookies.ts +++ b/lib/cookies.ts @@ -10,10 +10,12 @@ export enum NAMES { TXS_SORT='txs_sort', COLOR_MODE='chakra-ui-color-mode', COLOR_MODE_HEX='chakra-ui-color-mode-hex', + ADDRESS_IDENTICON_TYPE='address_identicon_type', INDEXING_ALERT='indexing_alert', ADBLOCK_DETECTED='adblock_detected', MIXPANEL_DEBUG='_mixpanel_debug', - ADDRESS_NFT_DISPLAY_TYPE='address_nft_display_type' + ADDRESS_NFT_DISPLAY_TYPE='address_nft_display_type', + UUID='uuid', } export function get(name?: NAMES | undefined | null, serverCookie?: string) { diff --git a/lib/errors/throwOnAbsentParamError.ts b/lib/errors/throwOnAbsentParamError.ts new file mode 100644 index 0000000000..5978b600a5 --- /dev/null +++ b/lib/errors/throwOnAbsentParamError.ts @@ -0,0 +1,5 @@ +export default function throwOnAbsentParamError(param: unknown) { + if (!param) { + throw new Error('Required param not provided', { cause: { status: 404 } }); + } +} diff --git a/lib/errors/throwOnResourceLoadError.ts b/lib/errors/throwOnResourceLoadError.ts new file mode 100644 index 0000000000..32e5169fc7 --- /dev/null +++ b/lib/errors/throwOnResourceLoadError.ts @@ -0,0 +1,19 @@ +import type { ResourceError, ResourceName } from 'lib/api/resources'; + +type Params = ({ + isError: true; + error: ResourceError; +} | { + isError: false; + error: null; +}) & { + resource?: ResourceName; +} + +export const RESOURCE_LOAD_ERROR_MESSAGE = 'Resource load error'; + +export default function throwOnResourceLoadError({ isError, error, resource }: Params) { + if (isError) { + throw Error(RESOURCE_LOAD_ERROR_MESSAGE, { cause: { ...error, resource } as unknown as Error }); + } +} diff --git a/lib/growthbook/consts.ts b/lib/growthbook/consts.ts new file mode 100644 index 0000000000..b687eedbb3 --- /dev/null +++ b/lib/growthbook/consts.ts @@ -0,0 +1,2 @@ +export const STORAGE_KEY = 'growthbook:experiments'; +export const STORAGE_LIMIT = 20; diff --git a/lib/growthbook/init.ts b/lib/growthbook/init.ts new file mode 100644 index 0000000000..10674a4fcc --- /dev/null +++ b/lib/growthbook/init.ts @@ -0,0 +1,70 @@ +import { GrowthBook } from '@growthbook/growthbook-react'; + +import config from 'configs/app'; +import * as mixpanel from 'lib/mixpanel'; + +import { STORAGE_KEY, STORAGE_LIMIT } from './consts'; + +export interface GrowthBookFeatures { + test_value: string; + security_score_exp: boolean; +} + +export const growthBook = (() => { + const feature = config.features.growthBook; + + if (!feature.isEnabled) { + return; + } + + return new GrowthBook({ + apiHost: 'https://cdn.growthbook.io', + clientKey: feature.clientKey, + enableDevMode: config.app.isDev, + attributes: { + id: mixpanel.getUuid(), + chain_id: config.chain.id, + }, + trackingCallback: (experiment, result) => { + if (isExperimentStarted(experiment.key)) { + return; + } + + saveExperimentInStorage(experiment.key); + mixpanel.logEvent(mixpanel.EventTypes.EXPERIMENT_STARTED, { + 'Experiment name': experiment.key, + 'Variant name': result.value, + Source: 'growthbook', + }); + }, + }); +})(); + +function getStorageValue(): Array | undefined { + const item = window.localStorage.getItem(STORAGE_KEY); + if (!item) { + return; + } + + try { + const parsedValue = JSON.parse(item); + if (Array.isArray(parsedValue)) { + return parsedValue; + } + } catch { + return; + } +} + +function isExperimentStarted(key: string): boolean { + const items = getStorageValue() ?? []; + return items.some((item) => item === key); +} + +function saveExperimentInStorage(key: string) { + const items = getStorageValue() ?? []; + const newItems = [ key, ...items ].slice(0, STORAGE_LIMIT); + try { + window.localStorage.setItem(STORAGE_KEY, JSON.stringify(newItems)); + } catch (error) {} +} diff --git a/lib/growthbook/useFeatureValue.ts b/lib/growthbook/useFeatureValue.ts new file mode 100644 index 0000000000..7dde7e8522 --- /dev/null +++ b/lib/growthbook/useFeatureValue.ts @@ -0,0 +1,14 @@ +import type { WidenPrimitives } from '@growthbook/growthbook'; +import { useFeatureValue, useGrowthBook } from '@growthbook/growthbook-react'; + +import type { GrowthBookFeatures } from './init'; + +export default function useGbFeatureValue( + name: Name, + fallback: GrowthBookFeatures[Name], +): { value: WidenPrimitives; isLoading: boolean } { + const value = useFeatureValue(name, fallback); + const growthBook = useGrowthBook(); + + return { value, isLoading: !(growthBook?.ready ?? true) }; +} diff --git a/lib/growthbook/useLoadFeatures.ts b/lib/growthbook/useLoadFeatures.ts new file mode 100644 index 0000000000..993e84505c --- /dev/null +++ b/lib/growthbook/useLoadFeatures.ts @@ -0,0 +1,17 @@ +import React from 'react'; + +import { SECOND } from 'lib/consts'; + +import { growthBook } from './init'; + +export default function useLoadFeatures() { + React.useEffect(() => { + growthBook?.setAttributes({ + ...growthBook.getAttributes(), + timezone: Intl.DateTimeFormat().resolvedOptions().timeZone, + language: window.navigator.language, + }); + + growthBook?.loadFeatures({ timeout: SECOND }); + }, []); +} diff --git a/lib/hexToBase64.ts b/lib/hexToBase64.ts new file mode 100644 index 0000000000..5b1366a6da --- /dev/null +++ b/lib/hexToBase64.ts @@ -0,0 +1,8 @@ +import bytesToBase64 from './bytesToBase64'; +import hexToBytes from './hexToBytes'; + +export default function hexToBase64(hex: string) { + const bytes = new Uint8Array(hexToBytes(hex)); + + return bytesToBase64(bytes); +} diff --git a/lib/hexToBytes.ts b/lib/hexToBytes.ts index 382fd777d3..e34435fbf4 100644 --- a/lib/hexToBytes.ts +++ b/lib/hexToBytes.ts @@ -1,6 +1,8 @@ +// hex can be with prefix - `0x{string}` - or without it - `{string}` export default function hexToBytes(hex: string) { const bytes = []; - for (let c = 0; c < hex.length; c += 2) { + const startIndex = hex.startsWith('0x') ? 2 : 0; + for (let c = startIndex; c < hex.length; c += 2) { bytes.push(parseInt(hex.substring(c, c + 2), 16)); } return bytes; diff --git a/lib/hexToDecimal.ts b/lib/hexToDecimal.ts new file mode 100644 index 0000000000..43bf1be78b --- /dev/null +++ b/lib/hexToDecimal.ts @@ -0,0 +1,4 @@ +export default function hetToDecimal(hex: string) { + const strippedHex = hex.startsWith('0x') ? hex.slice(2) : hex; + return parseInt(strippedHex, 16); +} diff --git a/lib/hooks/useLazyRenderedList.tsx b/lib/hooks/useLazyRenderedList.tsx new file mode 100644 index 0000000000..245d8a0b0b --- /dev/null +++ b/lib/hooks/useLazyRenderedList.tsx @@ -0,0 +1,23 @@ +import _clamp from 'lodash/clamp'; +import React from 'react'; +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); + const { ref, inView } = useInView({ + rootMargin: '200px', + triggerOnce: false, + skip: !isEnabled || list.length <= MIN_ITEMS_NUM, + }); + + React.useEffect(() => { + if (inView) { + setRenderedItemsNum((prev) => _clamp(prev + STEP, 0, list.length)); + } + }, [ inView, list.length ]); + + return { cutRef: ref, renderedItemsNum }; +} diff --git a/lib/hooks/useNavItems.tsx b/lib/hooks/useNavItems.tsx index f330a53c07..f3f40fc038 100644 --- a/lib/hooks/useNavItems.tsx +++ b/lib/hooks/useNavItems.tsx @@ -46,6 +46,13 @@ export default function useNavItems(): ReturnType { icon: 'transactions', isActive: pathname === '/txs' || pathname === '/tx/[hash]', }; + const userOps: NavItem | null = config.features.userOps.isEnabled ? { + text: 'User operations', + nextRoute: { pathname: '/ops' as const }, + icon: 'user_op', + isActive: pathname === '/ops' || pathname === '/op/[hash]', + } : null; + const verifiedContracts: NavItem | null = { text: 'Verified contracts', @@ -53,51 +60,111 @@ export default function useNavItems(): ReturnType { icon: 'verified', isActive: pathname === '/verified-contracts', }; + const ensLookup = config.features.nameService.isEnabled ? { + text: 'Name services lookup', + nextRoute: { pathname: '/name-domains' as const }, + icon: 'ENS', + isActive: pathname === '/name-domains' || pathname === '/name-domains/[name]', + } : null; + const validators = config.features.validators.isEnabled ? { + text: 'Top validators', + nextRoute: { pathname: '/validators' as const }, + icon: 'validator', + isActive: pathname === '/validators', + } : null; + + const rollupFeature = config.features.rollup; - if (config.features.zkEvmRollup.isEnabled) { + if (rollupFeature.isEnabled && rollupFeature.type === 'zkEvm') { blockchainNavItems = [ [ txs, + userOps, blocks, { text: 'Txn batches', - nextRoute: { pathname: '/zkevm-l2-txn-batches' as const }, + nextRoute: { pathname: '/batches' as const }, icon: 'txn_batches', - isActive: pathname === '/zkevm-l2-txn-batches' || pathname === '/zkevm-l2-txn-batch/[number]', + isActive: pathname === '/batches' || pathname === '/batches/[number]', }, - ], + ].filter(Boolean), [ topAccounts, + validators, verifiedContracts, + ensLookup, ].filter(Boolean), ]; - } else if (config.features.optimisticRollup.isEnabled) { + } else if (rollupFeature.isEnabled && rollupFeature.type === 'optimistic') { blockchainNavItems = [ [ txs, // eslint-disable-next-line max-len - { text: `Deposits (L1${ rightLineArrow }L2)`, nextRoute: { pathname: '/l2-deposits' as const }, icon: 'arrows/south-east', isActive: pathname === '/l2-deposits' }, + { 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: '/l2-withdrawals' as const }, icon: 'arrows/north-east', isActive: pathname === '/l2-withdrawals' }, + { text: `Withdrawals (L2${ rightLineArrow }L1)`, nextRoute: { pathname: '/withdrawals' as const }, icon: 'arrows/north-east', isActive: pathname === '/withdrawals' }, ], [ blocks, // eslint-disable-next-line max-len - { text: 'Txn batches', nextRoute: { pathname: '/l2-txn-batches' as const }, icon: 'txn_batches', isActive: pathname === '/l2-txn-batches' }, + { 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' }, + ], + [ + userOps, + topAccounts, + validators, + verifiedContracts, + ensLookup, + ].filter(Boolean), + ]; + } else if (rollupFeature.isEnabled && rollupFeature.type === 'shibarium') { + 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: 'Output roots', nextRoute: { pathname: '/l2-output-roots' as const }, icon: 'output_roots', isActive: pathname === '/l2-output-roots' }, + { text: `Withdrawals (L2${ rightLineArrow }L1)`, nextRoute: { pathname: '/withdrawals' as const }, icon: 'arrows/north-east', isActive: pathname === '/withdrawals' }, ], + [ + blocks, + userOps, + topAccounts, + verifiedContracts, + ensLookup, + ].filter(Boolean), + ]; + } else if (rollupFeature.isEnabled && rollupFeature.type === 'zkSync') { + 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 { blockchainNavItems = [ txs, + userOps, blocks, topAccounts, + validators, verifiedContracts, + ensLookup, config.features.beaconChain.isEnabled && { text: 'Withdrawals', nextRoute: { pathname: '/withdrawals' as const }, @@ -146,7 +213,7 @@ export default function useNavItems(): ReturnType { isActive: pathname.startsWith('/token'), }, config.features.marketplace.isEnabled ? { - text: 'Apps', + text: 'DApps', nextRoute: { pathname: '/apps' as const }, icon: 'apps', isActive: pathname.startsWith('/app'), @@ -172,8 +239,13 @@ export default function useNavItems(): ReturnType { 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), }, ].filter(Boolean); diff --git a/lib/hooks/useNotifyOnNavigation.tsx b/lib/hooks/useNotifyOnNavigation.tsx new file mode 100644 index 0000000000..a3b7a7c92f --- /dev/null +++ b/lib/hooks/useNotifyOnNavigation.tsx @@ -0,0 +1,24 @@ +import { usePathname } from 'next/navigation'; +import { useRouter } from 'next/router'; +import React from 'react'; + +import config from 'configs/app'; +import getQueryParamString from 'lib/router/getQueryParamString'; + +export default function useNotifyOnNavigation() { + const router = useRouter(); + const pathname = usePathname(); + const tab = getQueryParamString(router.query.tab); + + React.useEffect(() => { + if (config.features.metasuites.isEnabled) { + window.postMessage({ source: 'APP_ROUTER', type: 'PATHNAME_CHANGED' }, window.location.origin); + } + }, [ pathname ]); + + React.useEffect(() => { + if (config.features.metasuites.isEnabled) { + window.postMessage({ source: 'APP_ROUTER', type: 'TAB_CHANGED' }, window.location.origin); + } + }, [ tab ]); +} diff --git a/lib/metadata/getPageOgType.ts b/lib/metadata/getPageOgType.ts index 36e70e9fee..ef7ef24e26 100644 --- a/lib/metadata/getPageOgType.ts +++ b/lib/metadata/getPageOgType.ts @@ -33,13 +33,18 @@ const OG_TYPE_DICT: Record = { '/withdrawals': 'Root page', '/visualize/sol2uml': 'Regular page', '/csv-export': 'Regular page', - '/l2-deposits': 'Root page', - '/l2-output-roots': 'Root page', - '/l2-txn-batches': 'Root page', - '/l2-withdrawals': 'Root page', - '/zkevm-l2-txn-batches': 'Root page', - '/zkevm-l2-txn-batch/[number]': 'Regular page', + '/deposits': 'Root page', + '/output-roots': 'Root page', + '/batches': 'Root page', + '/batches/[number]': 'Regular page', + '/blobs/[hash]': 'Regular page', + '/ops': 'Root page', + '/op/[hash]': 'Regular page', '/404': 'Regular page', + '/name-domains': 'Root page', + '/name-domains/[name]': 'Regular page', + '/validators': 'Root page', + '/gas-tracker': 'Root page', // service routes, added only to make typescript happy '/login': 'Regular page', diff --git a/lib/metadata/templates/description.ts b/lib/metadata/templates/description.ts index 175e118f13..c064709b6f 100644 --- a/lib/metadata/templates/description.ts +++ b/lib/metadata/templates/description.ts @@ -36,13 +36,18 @@ const TEMPLATE_MAP: Record = { '/withdrawals': DEFAULT_TEMPLATE, '/visualize/sol2uml': DEFAULT_TEMPLATE, '/csv-export': DEFAULT_TEMPLATE, - '/l2-deposits': DEFAULT_TEMPLATE, - '/l2-output-roots': DEFAULT_TEMPLATE, - '/l2-txn-batches': DEFAULT_TEMPLATE, - '/l2-withdrawals': DEFAULT_TEMPLATE, - '/zkevm-l2-txn-batches': DEFAULT_TEMPLATE, - '/zkevm-l2-txn-batch/[number]': DEFAULT_TEMPLATE, + '/deposits': DEFAULT_TEMPLATE, + '/output-roots': DEFAULT_TEMPLATE, + '/batches': DEFAULT_TEMPLATE, + '/batches/[number]': DEFAULT_TEMPLATE, + '/blobs/[hash]': DEFAULT_TEMPLATE, + '/ops': DEFAULT_TEMPLATE, + '/op/[hash]': DEFAULT_TEMPLATE, '/404': DEFAULT_TEMPLATE, + '/name-domains': DEFAULT_TEMPLATE, + '/name-domains/[name]': DEFAULT_TEMPLATE, + '/validators': DEFAULT_TEMPLATE, + '/gas-tracker': DEFAULT_TEMPLATE, // service routes, added only to make typescript happy '/login': DEFAULT_TEMPLATE, diff --git a/lib/metadata/templates/title.ts b/lib/metadata/templates/title.ts index 5a9e48eebb..9a667bb9ce 100644 --- a/lib/metadata/templates/title.ts +++ b/lib/metadata/templates/title.ts @@ -14,7 +14,7 @@ const TEMPLATE_MAP: Record = { '/address/[hash]/contract-verification': 'contract verification for %hash%', '/tokens': 'tokens', '/token/[hash]': '%symbol% token details', - '/token/[hash]/instance/[id]': 'token instance for %symbol%', + '/token/[hash]/instance/[id]': 'NFT instance', '/apps': 'apps marketplace', '/apps/[id]': '- %app_name%', '/stats': 'statistics', @@ -31,13 +31,18 @@ const TEMPLATE_MAP: Record = { '/withdrawals': 'withdrawals', '/visualize/sol2uml': 'Solidity UML diagram', '/csv-export': 'export data to CSV', - '/l2-deposits': 'deposits (L1 > L2)', - '/l2-output-roots': 'output roots', - '/l2-txn-batches': 'Tx batches (L2 blocks)', - '/l2-withdrawals': 'withdrawals (L2 > L1)', - '/zkevm-l2-txn-batches': 'zkEvm L2 Tx batches', - '/zkevm-l2-txn-batch/[number]': 'zkEvm L2 Tx batch %number%', + '/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', // service routes, added only to make typescript happy '/login': 'login', diff --git a/lib/mixpanel/getGoogleAnalyticsClientId.ts b/lib/mixpanel/getGoogleAnalyticsClientId.ts deleted file mode 100644 index 704cf14f2f..0000000000 --- a/lib/mixpanel/getGoogleAnalyticsClientId.ts +++ /dev/null @@ -1,3 +0,0 @@ -export default function getGoogleAnalyticsClientId() { - return window.ga?.getAll()[0].get('clientId'); -} diff --git a/lib/mixpanel/getPageType.ts b/lib/mixpanel/getPageType.ts index 4ff5e22aa2..fb4c48e7fe 100644 --- a/lib/mixpanel/getPageType.ts +++ b/lib/mixpanel/getPageType.ts @@ -15,8 +15,8 @@ export const PAGE_TYPE_DICT: Record = { '/tokens': 'Tokens', '/token/[hash]': 'Token details', '/token/[hash]/instance/[id]': 'Token Instance', - '/apps': 'Apps', - '/apps/[id]': 'App', + '/apps': 'DApps', + '/apps/[id]': 'DApp', '/stats': 'Stats', '/api-docs': 'REST API', '/graphiql': 'GraphQL', @@ -31,13 +31,18 @@ export const PAGE_TYPE_DICT: Record = { '/withdrawals': 'Withdrawals', '/visualize/sol2uml': 'Solidity UML diagram', '/csv-export': 'Export data to CSV file', - '/l2-deposits': 'Deposits (L1 > L2)', - '/l2-output-roots': 'Output roots', - '/l2-txn-batches': 'Tx batches (L2 blocks)', - '/l2-withdrawals': 'Withdrawals (L2 > L1)', - '/zkevm-l2-txn-batches': 'ZkEvm L2 Tx batches', - '/zkevm-l2-txn-batch/[number]': 'ZkEvm L2 Tx batch details', + '/deposits': 'Deposits (L1 > L2)', + '/output-roots': 'Output roots', + '/batches': 'Tx batches (L2 blocks)', + '/batches/[number]': 'L2 tx batch details', + '/blobs/[hash]': 'Blob details', + '/ops': 'User operations', + '/op/[hash]': 'User operation details', '/404': '404', + '/name-domains': 'Domains search and resolve', + '/name-domains/[name]': 'Domain details', + '/validators': 'Validators list', + '/gas-tracker': 'Gas tracker', // service routes, added only to make typescript happy '/login': 'Login', diff --git a/lib/mixpanel/getUuid.ts b/lib/mixpanel/getUuid.ts new file mode 100644 index 0000000000..797034ceae --- /dev/null +++ b/lib/mixpanel/getUuid.ts @@ -0,0 +1,20 @@ +import * as cookies from 'lib/cookies'; +import * as growthBook from 'lib/growthbook/consts'; +import isBrowser from 'lib/isBrowser'; + +export default function getUuid() { + const cookie = cookies.get(cookies.NAMES.UUID); + + if (cookie) { + return cookie; + } + + const uuid = crypto.randomUUID(); + cookies.set(cookies.NAMES.UUID, uuid); + + if (isBrowser()) { + window.localStorage.removeItem(growthBook.STORAGE_KEY); + } + + return uuid; +} diff --git a/lib/mixpanel/index.ts b/lib/mixpanel/index.ts index 646a13d3e9..492269c5ea 100644 --- a/lib/mixpanel/index.ts +++ b/lib/mixpanel/index.ts @@ -1,4 +1,5 @@ import getPageType from './getPageType'; +import getUuid from './getUuid'; import logEvent from './logEvent'; import useInit from './useInit'; import useLogPageView from './useLogPageView'; @@ -9,4 +10,5 @@ export { useLogPageView, logEvent, getPageType, + getUuid, }; diff --git a/lib/mixpanel/isGoogleAnalyticsLoaded.ts b/lib/mixpanel/isGoogleAnalyticsLoaded.ts deleted file mode 100644 index 08667749b1..0000000000 --- a/lib/mixpanel/isGoogleAnalyticsLoaded.ts +++ /dev/null @@ -1,9 +0,0 @@ -import config from 'configs/app'; -import delay from 'lib/delay'; - -export default function isGoogleAnalyticsLoaded(retries = 3): Promise { - if (!retries || !config.features.googleAnalytics.isEnabled) { - return Promise.resolve(false); - } - return typeof window.ga?.getAll === 'function' ? Promise.resolve(true) : delay(500).then(() => isGoogleAnalyticsLoaded(retries - 1)); -} diff --git a/lib/mixpanel/useInit.tsx b/lib/mixpanel/useInit.tsx index b7a203ecce..0be8371066 100644 --- a/lib/mixpanel/useInit.tsx +++ b/lib/mixpanel/useInit.tsx @@ -9,8 +9,7 @@ import config from 'configs/app'; import * as cookies from 'lib/cookies'; import getQueryParamString from 'lib/router/getQueryParamString'; -import getGoogleAnalyticsClientId from './getGoogleAnalyticsClientId'; -import isGoogleAnalyticsLoaded from './isGoogleAnalyticsLoaded'; +import getUuid from './getUuid'; export default function useMixpanelInit() { const [ isInited, setIsInited ] = React.useState(false); @@ -18,36 +17,34 @@ export default function useMixpanelInit() { const debugFlagQuery = React.useRef(getQueryParamString(router.query._mixpanel_debug)); React.useEffect(() => { - isGoogleAnalyticsLoaded().then((isGALoaded) => { - const feature = config.features.mixpanel; - if (!feature.isEnabled) { - return; - } - - const debugFlagCookie = cookies.get(cookies.NAMES.MIXPANEL_DEBUG); - - const mixpanelConfig: Partial = { - debug: Boolean(debugFlagQuery.current || debugFlagCookie), - }; - const isAuth = Boolean(cookies.get(cookies.NAMES.API_TOKEN)); - - mixpanel.init(feature.projectToken, mixpanelConfig); - mixpanel.register({ - 'Chain id': config.chain.id, - Environment: config.app.isDev ? 'Dev' : 'Prod', - Authorized: isAuth, - 'Viewport width': window.innerWidth, - 'Viewport height': window.innerHeight, - Language: window.navigator.language, - 'User id': isGALoaded ? getGoogleAnalyticsClientId() : undefined, - 'Device type': _capitalize(deviceType), - }); - - setIsInited(true); - if (debugFlagQuery.current && !debugFlagCookie) { - cookies.set(cookies.NAMES.MIXPANEL_DEBUG, 'true'); - } + const feature = config.features.mixpanel; + if (!feature.isEnabled) { + return; + } + + const debugFlagCookie = cookies.get(cookies.NAMES.MIXPANEL_DEBUG); + + const mixpanelConfig: Partial = { + debug: Boolean(debugFlagQuery.current || debugFlagCookie), + }; + const isAuth = Boolean(cookies.get(cookies.NAMES.API_TOKEN)); + + mixpanel.init(feature.projectToken, mixpanelConfig); + mixpanel.register({ + 'Chain id': config.chain.id, + Environment: config.app.isDev ? 'Dev' : 'Prod', + Authorized: isAuth, + 'Viewport width': window.innerWidth, + 'Viewport height': window.innerHeight, + Language: window.navigator.language, + 'Device type': _capitalize(deviceType), + 'User id': getUuid(), }); + + setIsInited(true); + if (debugFlagQuery.current && !debugFlagCookie) { + cookies.set(cookies.NAMES.MIXPANEL_DEBUG, 'true'); + } }, []); return isInited; diff --git a/lib/mixpanel/utils.ts b/lib/mixpanel/utils.ts index b886f32249..20c4b92970 100644 --- a/lib/mixpanel/utils.ts +++ b/lib/mixpanel/utils.ts @@ -3,17 +3,22 @@ import type { WalletType } from 'types/client/wallets'; export enum EventTypes { PAGE_VIEW = 'Page view', SEARCH_QUERY = 'Search query', + LOCAL_SEARCH = 'Local search', ADD_TO_WALLET = 'Add to wallet', ACCOUNT_ACCESS = 'Account access', PRIVATE_TAG = 'Private tag', VERIFY_ADDRESS = 'Verify address', VERIFY_TOKEN = 'Verify token', WALLET_CONNECT = 'Wallet connect', + WALLET_ACTION = 'Wallet action', CONTRACT_INTERACTION = 'Contract interaction', CONTRACT_VERIFICATION = 'Contract verification', QR_CODE = 'QR code', PAGE_WIDGET = 'Page widget', - TX_INTERPRETATION_INTERACTION = 'Transaction interpratetion interaction' + TX_INTERPRETATION_INTERACTION = 'Transaction interpretation interaction', + EXPERIMENT_STARTED = 'Experiment started', + FILTERS = 'Filters', + BUTTON_CLICK = 'Button click', } /* eslint-disable @typescript-eslint/indent */ @@ -30,6 +35,10 @@ Type extends EventTypes.SEARCH_QUERY ? { 'Source page type': string; 'Result URL': string; } : +Type extends EventTypes.LOCAL_SEARCH ? { + 'Search query': string; + 'Source': 'Marketplace'; +} : Type extends EventTypes.ADD_TO_WALLET ? ( { 'Wallet': WalletType; @@ -62,9 +71,18 @@ Type extends EventTypes.VERIFY_TOKEN ? { 'Action': 'Form opened' | 'Submit'; } : Type extends EventTypes.WALLET_CONNECT ? { - 'Source': 'Header' | 'Smart contracts'; + 'Source': 'Header' | 'Smart contracts' | 'Swap button'; 'Status': 'Started' | 'Connected'; } : +Type extends EventTypes.WALLET_ACTION ? ( + { + 'Action': 'Open' | 'Address click'; + } | { + 'Action': 'Send Transaction' | 'Sign Message' | 'Sign Typed Data'; + 'Address': string | undefined; + 'AppId': string; + } +) : Type extends EventTypes.CONTRACT_INTERACTION ? { 'Method type': 'Read' | 'Write'; 'Method name': string; @@ -76,11 +94,33 @@ Type extends EventTypes.CONTRACT_VERIFICATION ? { Type extends EventTypes.QR_CODE ? { 'Page type': string; } : -Type extends EventTypes.PAGE_WIDGET ? { - 'Type': 'Tokens dropdown' | 'Tokens show all (icon)' | 'Add to watchlist' | 'Address actions (more button)'; -} : +Type extends EventTypes.PAGE_WIDGET ? ( + { + 'Type': 'Tokens dropdown' | 'Tokens show all (icon)' | 'Add to watchlist' | 'Address actions (more button)'; + } | { + 'Type': 'Favorite app' | 'More button' | 'Security score' | 'Total contracts' | 'Verified contracts' | 'Analyzed contracts'; + 'Info': string; + 'Source': 'Discovery view' | 'Security view' | 'App modal' | 'App page' | 'Security score popup'; + } | { + 'Type': 'Security score'; + 'Source': 'Analyzed contracts popup'; + } +) : Type extends EventTypes.TX_INTERPRETATION_INTERACTION ? { - 'Type': 'Address click' | 'Token click'; + 'Type': 'Address click' | 'Token click' | 'Domain click'; +} : +Type extends EventTypes.EXPERIMENT_STARTED ? { + 'Experiment name': string; + 'Variant name': string; + 'Source': 'growthbook'; +} : +Type extends EventTypes.FILTERS ? { + 'Source': 'Marketplace'; + 'Filter name': string; +} : +Type extends EventTypes.BUTTON_CLICK ? { + 'Content': 'Swap button'; + 'Source': string; } : undefined; /* eslint-enable @typescript-eslint/indent */ diff --git a/lib/router/removeQueryParam.ts b/lib/router/removeQueryParam.ts new file mode 100644 index 0000000000..83daa6bd28 --- /dev/null +++ b/lib/router/removeQueryParam.ts @@ -0,0 +1,8 @@ +import type { NextRouter } from 'next/router'; + +export default function removeQueryParam(router: NextRouter, param: string) { + const { pathname, query } = router; + const newQuery = { ...query }; + delete newQuery[param]; + router.replace({ pathname, query: newQuery }, undefined, { shallow: true }); +} diff --git a/lib/router/updateQueryParam.ts b/lib/router/updateQueryParam.ts new file mode 100644 index 0000000000..de9fadce8b --- /dev/null +++ b/lib/router/updateQueryParam.ts @@ -0,0 +1,8 @@ +import type { NextRouter } from 'next/router'; + +export default function updateQueryParam(router: NextRouter, param: string, newValue: string) { + const { pathname, query } = router; + const newQuery = { ...query }; + newQuery[param] = newValue; + router.replace({ pathname, query: newQuery }, undefined, { shallow: true }); +} diff --git a/lib/sentry/config.ts b/lib/sentry/config.ts index d52c20e382..f619c9346c 100644 --- a/lib/sentry/config.ts +++ b/lib/sentry/config.ts @@ -2,6 +2,7 @@ import * as Sentry from '@sentry/react'; import { BrowserTracing } from '@sentry/tracing'; import appConfig from 'configs/app'; +import { RESOURCE_LOAD_ERROR_MESSAGE } from 'lib/errors/throwOnResourceLoadError'; const feature = appConfig.features.sentry; @@ -59,6 +60,9 @@ export const config: Sentry.BrowserOptions | undefined = (() => { 'The quota has been exceeded', 'Attempt to connect to relay via', 'WebSocket connection failed for URL: wss://relay.walletconnect.com', + + // API errors + RESOURCE_LOAD_ERROR_MESSAGE, ], denyUrls: [ // Facebook flakiness diff --git a/lib/shortenString.ts b/lib/shortenString.ts index 28a7f67411..4125ec06f2 100644 --- a/lib/shortenString.ts +++ b/lib/shortenString.ts @@ -1,11 +1,11 @@ -export default function shortenString(string: string | null) { +export default function shortenString(string: string | null, charNumber: number | undefined = 8) { if (!string) { return ''; } - if (string.length <= 7) { + if (string.length <= charNumber) { return string; } - return string.slice(0, 4) + '...' + string.slice(-4); + return string.slice(0, charNumber - 4) + '...' + string.slice(-4); } diff --git a/lib/socket/types.ts b/lib/socket/types.ts index 4d05a1acb7..b2d8d7301a 100644 --- a/lib/socket/types.ts +++ b/lib/socket/types.ts @@ -6,7 +6,7 @@ import type { SmartContractVerificationResponse } from 'types/api/contract'; import type { RawTracesResponse } from 'types/api/rawTrace'; import type { TokenTransfer } from 'types/api/tokenTransfer'; import type { Transaction } from 'types/api/transaction'; -import type { NewZkEvmBatchSocketResponse } from 'types/api/zkEvmL2TxnBatches'; +import type { NewZkEvmBatchSocketResponse } from 'types/api/zkEvmL2'; export type SocketMessageParams = SocketMessage.NewBlock | SocketMessage.BlocksIndexStatus | @@ -22,6 +22,7 @@ SocketMessage.AddressTokenBalance | SocketMessage.AddressTokenBalancesErc20 | SocketMessage.AddressTokenBalancesErc721 | SocketMessage.AddressTokenBalancesErc1155 | +SocketMessage.AddressTokenBalancesErc404 | SocketMessage.AddressCoinBalance | SocketMessage.AddressTxs | SocketMessage.AddressTxsPending | @@ -57,6 +58,7 @@ export namespace SocketMessage { export type AddressTokenBalancesErc20 = SocketMessageParamsGeneric<'updated_token_balances_erc_20', AddressTokensBalancesSocketMessage>; export type AddressTokenBalancesErc721 = SocketMessageParamsGeneric<'updated_token_balances_erc_721', AddressTokensBalancesSocketMessage>; export type AddressTokenBalancesErc1155 = SocketMessageParamsGeneric<'updated_token_balances_erc_1155', AddressTokensBalancesSocketMessage>; + export type AddressTokenBalancesErc404 = SocketMessageParamsGeneric<'updated_token_balances_erc_404', AddressTokensBalancesSocketMessage>; export type AddressCoinBalance = SocketMessageParamsGeneric<'coin_balance', { coin_balance: AddressCoinBalanceHistoryItem }>; export type AddressTxs = SocketMessageParamsGeneric<'transaction', { transactions: Array }>; export type AddressTxsPending = SocketMessageParamsGeneric<'pending_transaction', { transactions: Array }>; diff --git a/lib/token/metadata/attributesParser.ts b/lib/token/metadata/attributesParser.ts index c863000366..00119abe90 100644 --- a/lib/token/metadata/attributesParser.ts +++ b/lib/token/metadata/attributesParser.ts @@ -19,7 +19,7 @@ function formatValue(value: string | number, display: string | undefined, trait: } case 'date': { return { - value: dayjs(value).format('YYYY-MM-DD'), + value: dayjs(Number(value) * 1000).format('YYYY-MM-DD'), }; } default: { diff --git a/lib/token/tokenTypes.ts b/lib/token/tokenTypes.ts index 5246fc2418..4b7fabf9ea 100644 --- a/lib/token/tokenTypes.ts +++ b/lib/token/tokenTypes.ts @@ -3,6 +3,7 @@ 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 }> = [ diff --git a/lib/units.ts b/lib/units.ts new file mode 100644 index 0000000000..546e4308ee --- /dev/null +++ b/lib/units.ts @@ -0,0 +1,11 @@ +import type { Unit } from 'types/unit'; + +import config from 'configs/app'; + +const weiName = config.chain.currency.weiName || 'wei'; + +export const currencyUnits: Record = { + wei: weiName, + gwei: `G${ weiName }`, + ether: config.chain.currency.symbol || 'ETH', +}; diff --git a/lib/web3/client.ts b/lib/web3/client.ts new file mode 100644 index 0000000000..8188f22869 --- /dev/null +++ b/lib/web3/client.ts @@ -0,0 +1,19 @@ +import { createPublicClient, http } from 'viem'; + +import currentChain from './currentChain'; + +export const publicClient = (() => { + if (currentChain.rpcUrls.default.http.filter(Boolean).length === 0) { + return; + } + + try { + return createPublicClient({ + chain: currentChain, + transport: http(), + batch: { + multicall: true, + }, + }); + } catch (error) {} +})(); diff --git a/lib/web3/currentChain.ts b/lib/web3/currentChain.ts new file mode 100644 index 0000000000..dd3892859f --- /dev/null +++ b/lib/web3/currentChain.ts @@ -0,0 +1,27 @@ +import { type Chain } from 'viem'; + +import config from 'configs/app'; + +const currentChain = { + id: Number(config.chain.id), + name: config.chain.name ?? '', + nativeCurrency: { + decimals: config.chain.currency.decimals, + name: config.chain.currency.name ?? '', + symbol: config.chain.currency.symbol ?? '', + }, + rpcUrls: { + 'default': { + http: [ config.chain.rpcUrl ?? '' ], + }, + }, + blockExplorers: { + 'default': { + name: 'Blockscout', + url: config.app.baseUrl, + }, + }, + testnet: config.chain.isTestnet, +} as const satisfies Chain; + +export default currentChain; diff --git a/lib/web3/useProvider.tsx b/lib/web3/useProvider.tsx index 029eb24835..43cc7aa8fa 100644 --- a/lib/web3/useProvider.tsx +++ b/lib/web3/useProvider.tsx @@ -1,16 +1,14 @@ import React from 'react'; -import type { WindowProvider } from 'wagmi'; - -import 'wagmi/window'; import type { WalletType } from 'types/client/wallets'; +import type { WalletProvider } from 'types/web3'; import config from 'configs/app'; const feature = config.features.web3Wallet; export default function useProvider() { - const [ provider, setProvider ] = React.useState(); + const [ provider, setProvider ] = React.useState(); const [ wallet, setWallet ] = React.useState(); const initializeProvider = React.useMemo(() => async() => { diff --git a/lib/web3/wagmiConfig.ts b/lib/web3/wagmiConfig.ts new file mode 100644 index 0000000000..f90d0a069f --- /dev/null +++ b/lib/web3/wagmiConfig.ts @@ -0,0 +1,38 @@ +import { defaultWagmiConfig } from '@web3modal/wagmi/react/config'; +import { http } from 'viem'; +import 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 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.sidebar.icon.default ].filter(Boolean), + }, + enableEmail: true, + ssr: true, + }); + + return wagmiConfig; + } catch (error) {} +})(); + +export default wagmiConfig; diff --git a/mocks/ad/textAd.ts b/mocks/ad/textAd.ts index 6ee891bea8..0e5bb6feae 100644 --- a/mocks/ad/textAd.ts +++ b/mocks/ad/textAd.ts @@ -2,7 +2,7 @@ export const duck = { ad: { name: 'Hello utia!', description_short: 'Utia is the best! Go with utia! Utia is the best! Go with utia!', - thumbnail: 'https://utia.utia', + thumbnail: 'http://localhost:3100/utia.jpg', url: 'https://test.url', cta_button: 'Click me!', }, diff --git a/mocks/address/address.ts b/mocks/address/address.ts index fe1903164a..7c7bc612cb 100644 --- a/mocks/address/address.ts +++ b/mocks/address/address.ts @@ -15,6 +15,19 @@ export const withName: AddressParam = { private_tags: [], watchlist_names: [], public_tags: [], + ens_domain_name: null, +}; + +export const withEns: AddressParam = { + hash: hash, + implementation_name: null, + is_contract: false, + is_verified: null, + name: 'ArianeeStore', + private_tags: [], + watchlist_names: [], + public_tags: [], + ens_domain_name: 'kitty.kitty.kitty.cat.eth', }; export const withoutName: AddressParam = { @@ -26,6 +39,7 @@ export const withoutName: AddressParam = { private_tags: [], watchlist_names: [], public_tags: [], + ens_domain_name: null, }; export const token: Address = { @@ -56,6 +70,7 @@ export const token: Address = { has_token_transfers: true, has_tokens: true, has_validated_blocks: false, + ens_domain_name: null, }; export const contract: Address = { @@ -86,6 +101,7 @@ export const contract: Address = { token: null, watchlist_names: [ watchlistName ], watchlist_address_id: 42, + ens_domain_name: null, }; export const validator: Address = { @@ -116,4 +132,5 @@ export const validator: Address = { token: null, watchlist_names: [], watchlist_address_id: null, + ens_domain_name: null, }; diff --git a/mocks/address/tokens.ts b/mocks/address/tokens.ts index c8c7386134..f3fd58b8d5 100644 --- a/mocks/address/tokens.ts +++ b/mocks/address/tokens.ts @@ -38,6 +38,17 @@ export const erc20LongSymbol: AddressTokenBalance = { token_instance: null, }; +export const erc20BigAmount: AddressTokenBalance = { + token: { + ...tokens.tokenInfoERC20LongSymbol, + exchange_rate: '4200000000', + name: 'DuckDuckGoose Stable Coin', + }, + token_id: null, + value: '39000000000000000000', + token_instance: null, +}; + export const erc721a: AddressTokenBalance = { token: tokens.tokenInfoERC721a, token_id: null, @@ -94,6 +105,20 @@ export const erc1155LongId: AddressTokenBalance = { value: '42', }; +export const erc404a: AddressTokenBalance = { + token: tokens.tokenInfoERC404, + token_id: '42', + token_instance: tokenInstance.base, + value: '240000000000000', +}; + +export const erc404b: AddressTokenBalance = { + token: tokens.tokenInfoERC404, + token_instance: null, + value: '11', + token_id: null, +}; + export const erc20List = { items: [ erc20a, @@ -118,6 +143,13 @@ export const erc1155List = { ], }; +export const erc404List = { + items: [ + erc404a, + erc404b, + ], +}; + export const nfts: AddressNFTsResponse = { items: [ { @@ -132,6 +164,12 @@ export const nfts: AddressNFTsResponse = { token_type: 'ERC-721', value: '1', }, + { + ...tokenInstance.unique, + token: tokens.tokenInfoERC404, + token_type: 'ERC-404', + value: '11000', + }, ], next_page_params: null, }; diff --git a/mocks/apps/app.html b/mocks/apps/app.html new file mode 100644 index 0000000000..c7c675b977 --- /dev/null +++ b/mocks/apps/app.html @@ -0,0 +1,32 @@ + + + + + Mock HTML Content + + + +

Full view app

+ + diff --git a/mocks/apps/apps.ts b/mocks/apps/apps.ts index a8b27a70a4..2f748c625a 100644 --- a/mocks/apps/apps.ts +++ b/mocks/apps/apps.ts @@ -11,6 +11,9 @@ export const apps = [ description: 'Hop is a scalable rollup-to-rollup general token bridge. It allows users to send tokens from one rollup or sidechain to another almost immediately without having to wait for the networks challenge period.', external: true, url: 'https://goerli.hop.exchange/send?token=ETH&sourceNetwork=ethereum', + github: [ 'https://github.com/hop-protocol/hop', 'https://github.com/hop-protocol/hop-ui' ], + discord: 'https://discord.gg/hopprotocol', + twitter: 'https://twitter.com/HopProtocol', }, { author: 'Blockscout', diff --git a/mocks/apps/securityReports.ts b/mocks/apps/securityReports.ts new file mode 100644 index 0000000000..824a6fbe13 --- /dev/null +++ b/mocks/apps/securityReports.ts @@ -0,0 +1,58 @@ +export const securityReports = [ + { + appName: 'token-approval-tracker', + doc: 'http://docs.li.fi/smart-contracts/deployments#mainnet', + chainsData: { + '1': { + overallInfo: { + verifiedNumber: 1, + totalContractsNumber: 1, + solidityScanContractsNumber: 1, + securityScore: 87.5, + issueSeverityDistribution: { + critical: 4, + gas: 1, + high: 0, + informational: 4, + low: 2, + medium: 0, + }, + }, + contractsData: [ + { + address: '0x1231DEB6f5749EF6cE6943a275A1D3E7486F4EaE', + isVerified: true, + solidityScanReport: { + connection_id: '', + contract_address: '0x1231DEB6f5749EF6cE6943a275A1D3E7486F4EaE', + contract_chain: 'optimism', + contract_platform: 'blockscout', + contract_url: 'http://optimism.blockscout.com/address/0x1231DEB6f5749EF6cE6943a275A1D3E7486F4EaE', + contractname: 'LiFiDiamond', + is_quick_scan: true, + node_reference_id: null, + request_type: 'threat_scan', + scanner_reference_url: 'http://solidityscan.com/quickscan/0x1231DEB6f5749EF6cE6943a275A1D3E7486F4EaE/blockscout/eth?ref=blockscout', + scan_status: 'scan_done', + scan_summary: { + issue_severity_distribution: { + critical: 0, + gas: 1, + high: 0, + informational: 4, + low: 2, + medium: 0, + }, + lines_analyzed_count: 72, + scan_time_taken: 1, + score: '4.38', + score_v2: '87.50', + threat_score: '100.00', + }, + }, + }, + ], + }, + }, + }, +]; diff --git a/mocks/blobs/blobs.ts b/mocks/blobs/blobs.ts new file mode 100644 index 0000000000..24d25b465f --- /dev/null +++ b/mocks/blobs/blobs.ts @@ -0,0 +1,36 @@ +import type { Blob, TxBlobs } from 'types/api/blobs'; + +export const base1: Blob = { + blob_data: '0x004242004242004242004242004242004242', + hash: '0x016316f61a259aa607096440fc3eeb90356e079be01975d2fb18347bd50df33c', + kzg_commitment: '0xa95caabd009e189b9f205e0328ff847ad886e4f8e719bd7219875fbb9688fb3fbe7704bb1dfa7e2993a3dea8d0cf767d', + kzg_proof: '0x89cf91c4c8be6f2a390d4262425f79dffb74c174fb15a210182184543bf7394e5a7970a774ee8e0dabc315424c22df0f', + transaction_hashes: [ + { block_consensus: true, transaction_hash: '0x970d8c45c713a50a1fa351b00ca29a8890cac474c59cc8eee4eddec91a1729f0' }, + ], +}; + +export const base2: Blob = { + blob_data: '0x89504E470D0A1A0A0000000D494844520000003C0000003C0403', + hash: '0x0197fdb17195c176b23160f335daabd4b6a231aaaadd73ec567877c66a3affd1', + kzg_commitment: '0x89b0d8ac715ee134135471994a161ef068a784f51982fcd7161aa8e3e818eb83017ccfbfc30c89b796a2743d77554e2f', + kzg_proof: '0x8255a6c6a236483814b8e68992e70f3523f546866a9fed6b8e0ecfef314c65634113b8aa02d6c5c6e91b46e140f17a07', + transaction_hashes: [ + { block_consensus: true, transaction_hash: '0x22d597ebcf3e8d60096dd0363bc2f0f5e2df27ba1dacd696c51aa7c9409f3193' }, + ], +}; + +export const withoutData: Blob = { + blob_data: null, + hash: '0x0197fdb17195c176b23160f335daabd4b6a231aaaadd73ec567877c66a3affd3', + kzg_commitment: null, + kzg_proof: null, + transaction_hashes: [ + { block_consensus: true, transaction_hash: '0x22d597ebcf3e8d60096dd0363bc2f0f5e2df27ba1dacd696c51aa7c9409f3193' }, + ], +}; + +export const txBlobs: TxBlobs = { + items: [ base1, base2, withoutData ], + next_page_params: null, +}; diff --git a/mocks/blocks/block.ts b/mocks/blocks/block.ts index a1e9b31f0c..eb182d2ebf 100644 --- a/mocks/blocks/block.ts +++ b/mocks/blocks/block.ts @@ -22,6 +22,7 @@ export const base: Block = { private_tags: [], public_tags: [], watchlist_names: [], + ens_domain_name: null, }, nonce: '0x0000000000000000', parent_hash: '0x44125f0eb36a9d942e0c23bb4e8117f7ba86a9537a69b59c0025986ed2b7500f', @@ -71,6 +72,7 @@ export const genesis: Block = { private_tags: [], public_tags: [], watchlist_names: [], + ens_domain_name: 'kitty.kitty.cat.eth', }, nonce: '0x0000000000000000', parent_hash: '0x0000000000000000000000000000000000000000000000000000000000000000', @@ -99,6 +101,7 @@ export const base2: Block = { private_tags: [], public_tags: [], watchlist_names: [], + ens_domain_name: null, }, timestamp: '2022-11-11T11:46:05Z', tx_count: 253, @@ -132,6 +135,15 @@ export const rootstock: Block = { minimum_gas_price: '59240000', }; +export const withBlobTxs: Block = { + ...base, + blob_gas_price: '21518435987', + blob_gas_used: '393216', + burnt_blob_fees: '8461393325064192', + excess_blob_gas: '79429632', + blob_tx_count: 1, +}; + export const baseListResponse: BlocksResponse = { items: [ base, diff --git a/mocks/contract/audits.ts b/mocks/contract/audits.ts new file mode 100644 index 0000000000..a2a6644229 --- /dev/null +++ b/mocks/contract/audits.ts @@ -0,0 +1,16 @@ +import type { SmartContractSecurityAudits } from 'types/api/contract'; + +export const contractAudits: SmartContractSecurityAudits = { + items: [ + { + audit_company_name: 'OpenZeppelin', + audit_publish_date: '2023-03-01', + audit_report_url: 'https://blog.openzeppelin.com/eip-4337-ethereum-account-abstraction-incremental-audit', + }, + { + audit_company_name: 'OpenZeppelin', + audit_publish_date: '2023-03-01', + audit_report_url: 'https://blog.openzeppelin.com/eip-4337-ethereum-account-abstraction-incremental-audit', + }, + ], +}; diff --git a/mocks/contract/info.ts b/mocks/contract/info.ts index c5e1a7e040..a797383474 100644 --- a/mocks/contract/info.ts +++ b/mocks/contract/info.ts @@ -31,6 +31,7 @@ export const verified: Partial = { { address_hash: '0xa62744BeE8646e237441CDbfdedD3458861748A8', name: 'math' }, ], language: 'solidity', + license_type: 'gnu_gpl_v3', }; export const withMultiplePaths: Partial = { diff --git a/mocks/contract/methods.ts b/mocks/contract/methods.ts index 745c19770a..6c1bdf367e 100644 --- a/mocks/contract/methods.ts +++ b/mocks/contract/methods.ts @@ -132,6 +132,7 @@ export const write: Array = [ payable: false, stateMutability: 'nonpayable', type: 'function', + method_id: '0x01', }, { constant: false, @@ -146,6 +147,7 @@ export const write: Array = [ payable: true, stateMutability: 'payable', type: 'function', + method_id: '0x02', }, { stateMutability: 'payable', @@ -159,6 +161,7 @@ export const write: Array = [ payable: false, stateMutability: 'nonpayable', type: 'function', + method_id: '0x03', }, { constant: false, @@ -173,6 +176,7 @@ export const write: Array = [ payable: false, stateMutability: 'nonpayable', type: 'function', + method_id: '0x04', }, { constant: false, @@ -190,6 +194,7 @@ export const write: Array = [ payable: false, stateMutability: 'nonpayable', type: 'function', + method_id: '0x05', }, { constant: false, @@ -208,5 +213,6 @@ export const write: Array = [ payable: false, stateMutability: 'nonpayable', type: 'function', + method_id: '0x06', }, ]; diff --git a/mocks/contracts/index.ts b/mocks/contracts/index.ts index 219333ef2b..bc3b4ecfb2 100644 --- a/mocks/contracts/index.ts +++ b/mocks/contracts/index.ts @@ -10,6 +10,7 @@ export const contract1: VerifiedContract = { private_tags: [], public_tags: [], watchlist_names: [], + ens_domain_name: null, }, coin_balance: '2346534676900000008', compiler_version: 'v0.8.17+commit.8df45f5f', @@ -19,6 +20,7 @@ export const contract1: VerifiedContract = { optimization_enabled: false, tx_count: 7334224, verified_at: '2022-09-16T18:49:29.605179Z', + license_type: 'mit', }; export const contract2: VerifiedContract = { @@ -31,6 +33,7 @@ export const contract2: VerifiedContract = { private_tags: [], public_tags: [], watchlist_names: [], + ens_domain_name: null, }, coin_balance: '9078234570352343999', compiler_version: 'v0.3.1+commit.0463ea4c', @@ -40,6 +43,7 @@ export const contract2: VerifiedContract = { optimization_enabled: true, tx_count: 440, verified_at: '2021-09-07T20:01:56.076979Z', + license_type: 'bsd_3_clause', }; export const baseResponse: VerifiedContractsResponse = { diff --git a/mocks/ens/domain.ts b/mocks/ens/domain.ts new file mode 100644 index 0000000000..3126aaecf5 --- /dev/null +++ b/mocks/ens/domain.ts @@ -0,0 +1,91 @@ +import type { EnsDomainDetailed } from 'types/api/ens'; + +const domainTokenA = { + id: '97352314626701792030827861137068748433918254309635329404916858191911576754327', + contract_hash: '0x57f1887a8bf19b14fc0df6fd9b2acc9af147ea85', + type: 'NATIVE_DOMAIN_TOKEN' as const, +}; +const domainTokenB = { + id: '423546333', + contract_hash: '0x57f1887a8bf19b14fc0df6fd9b2acc9af147ea86', + type: 'WRAPPED_DOMAIN_TOKEN' as const, +}; + +export const ensDomainA: EnsDomainDetailed = { + id: '0xb140bf9645e54f02ed3c1bcc225566b515a98d1688f10494a5c3bc5b447936a7', + tokens: [ + domainTokenA, + domainTokenB, + ], + name: 'cat.eth', + registrant: { + hash: '0x114d4603199df73e7d157787f8778e21fcd13066', + }, + resolved_address: { + hash: '0xfe6ab8a0dafe7d41adf247c210451c264155c9b0', + }, + owner: { + hash: '0x114d4603199df73e7d157787f8778e21fcd13066', + }, + wrapped_owner: null, + registration_date: '2021-06-27T13:34:44.000Z', + expiry_date: '2025-03-01T14:20:24.000Z', + other_addresses: { + ETH: 'fe6ab8a0dafe7d41adf247c210451c264155c9b0', + GNO: 'DDAfbb505ad214D7b80b1f830fcCc89B60fb7A83', + NEAR: 'a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48.factory.bridge.near', + }, +}; + +export const ensDomainB: EnsDomainDetailed = { + id: '0x632ac7bec8e883416b371b36beaa822f4784208c99d063ee030020e2bd09b885', + tokens: [ domainTokenA ], + name: 'kitty.kitty.kitty.cat.eth', + resolved_address: null, + registrant: { + hash: '0x114d4603199df73e7d157787f8778e21fcd13066', + }, + owner: { + hash: '0x114d4603199df73e7d157787f8778e21fcd13066', + }, + wrapped_owner: null, + registration_date: '2023-08-13T13:01:12.000Z', + expiry_date: null, + other_addresses: {}, +}; + +export const ensDomainC: EnsDomainDetailed = { + id: '0xdb7f351de6d93bda077a9211bdc49f249326d87932e4787d109b0262e9d189ad', + tokens: [ domainTokenA ], + name: 'duck.duck.eth', + registrant: { + hash: '0x114d4603199df73e7d157787f8778e21fcd13066', + }, + resolved_address: { + hash: '0xfe6ab8a0dafe7d41adf247c210451c264155c9b0', + }, + owner: { + hash: '0x114d4603199df73e7d157787f8778e21fcd13066', + }, + wrapped_owner: null, + registration_date: '2022-04-24T07:34:44.000Z', + expiry_date: '2022-11-01T13:10:36.000Z', + other_addresses: {}, +}; + +export const ensDomainD: EnsDomainDetailed = { + id: '0xdb7f351de6d93bda077a9211bdc49f249326d87932e4787d109b0262e9d189ae', + tokens: [ domainTokenA ], + name: '🦆.duck.eth', + registrant: { + hash: '0x114d4603199df73e7d157787f8778e21fcd13066', + }, + resolved_address: { + hash: '0x114d4603199df73e7d157787f8778e21fcd13066', + }, + owner: null, + wrapped_owner: null, + registration_date: '2022-04-24T07:34:44.000Z', + expiry_date: '2027-09-23T13:10:36.000Z', + other_addresses: {}, +}; diff --git a/mocks/ens/events.ts b/mocks/ens/events.ts new file mode 100644 index 0000000000..2d60cc721a --- /dev/null +++ b/mocks/ens/events.ts @@ -0,0 +1,19 @@ +import type { EnsDomainEvent } from 'types/api/ens'; + +export const ensDomainEventA: EnsDomainEvent = { + transaction_hash: '0x86c66b9fad66e4f20d42a6eed4fe12a0f48a274786fd85e9d4aa6c60e84b5874', + timestamp: '2021-06-27T13:34:44.000000Z', + from_address: { + hash: '0xaa96a50a2f67111262fe24576bd85bb56ec65016', + }, + action: '0xf7a16963', +}; + +export const ensDomainEventB = { + transaction_hash: '0x150bf7d5cd42457dd9c799ddd9d4bf6c30c703e1954a88c6d4b668b23fe0fbf8', + timestamp: '2022-11-02T14:20:24.000000Z', + from_address: { + hash: '0xfe6ab8a0dafe7d41adf247c210451c264155c9b0', + }, + action: 'register', +}; diff --git a/mocks/l2txnBatches/txnBatches.ts b/mocks/l2txnBatches/txnBatches.ts index 4459c2b57f..f3c086c855 100644 --- a/mocks/l2txnBatches/txnBatches.ts +++ b/mocks/l2txnBatches/txnBatches.ts @@ -1,7 +1,6 @@ export const txnBatchesData = { items: [ { - epoch_number: 8547349, l1_tx_hashes: [ '0x5bc94d02b65743dfaa9e10a2d6e175aff2a05cce2128c8eaf848bd84ab9325c5', '0x92a51bc623111dbb91f243e3452e60fab6f090710357f9d9b75ac8a0f67dfd9d', @@ -11,7 +10,6 @@ export const txnBatchesData = { tx_count: 0, }, { - epoch_number: 8547348, l1_tx_hashes: [ '0xc45f846ee28ce9ba116ce2d378d3dd00b55d324b833b3ecd4241c919c572c4aa', ], @@ -20,7 +18,6 @@ export const txnBatchesData = { tx_count: 0, }, { - epoch_number: 8547348, l1_tx_hashes: [ '0x48139721f792d3a68c3781b4cf50e66e8fc7dbb38adff778e09066ea5be9adb8', ], diff --git a/mocks/noves/transaction.ts b/mocks/noves/transaction.ts new file mode 100644 index 0000000000..6feb72a564 --- /dev/null +++ b/mocks/noves/transaction.ts @@ -0,0 +1,103 @@ +import type { NovesResponseData } from 'types/api/noves'; + +import type { TokensData } from 'ui/tx/assetFlows/utils/getTokensData'; + +export const hash = '0x380400d04ebb4179a35b1d7fdef260776915f015e978f8587ef2704b843d4e53'; + +export const transaction: NovesResponseData = { + accountAddress: '0xef6595A423c99f3f2821190A4d96fcE4DcD89a80', + chain: 'eth-goerli', + classificationData: { + description: 'Called function \'stake\' on contract 0xef326CdAdA59D3A740A76bB5f4F88Fb2.', + protocol: { + name: null, + }, + received: [], + sent: [ + { + action: 'sent', + actionFormatted: 'Sent', + amount: '3000', + from: { + address: '0xef6595A423c99f3f2821190A4d96fcE4DcD89a80', + name: 'This wallet', + }, + to: { + address: '0xdD15D2650387Fb6FEDE27ae7392C402a393F8A37', + name: null, + }, + token: { + address: '0x1bfe4298796198f8664b18a98640cec7c89b5baa', + decimals: 18, + name: 'PQR-Test', + symbol: 'PQR', + }, + }, + { + action: 'paidGas', + actionFormatted: 'Paid Gas', + amount: '0.000395521502109448', + from: { + address: '0xef6595A423c99f3f2821190A4d96fcE4DcD89a80', + name: 'This wallet', + }, + to: { + address: null, + name: 'Validators', + }, + token: { + address: 'ETH', + decimals: 18, + name: 'ETH', + symbol: 'ETH', + }, + }, + ], + source: { + type: null, + }, + type: 'unclassified', + typeFormatted: 'Unclassified', + }, + rawTransactionData: { + blockNumber: 10388918, + fromAddress: '0xef6595A423c99f3f2821190A4d96fcE4DcD89a80', + gas: 275079, + gasPrice: 1500000008, + timestamp: 1705488588, + toAddress: '0xef326CdAdA59D3A740A76bB5f4F88Fb2f1076164', + transactionFee: { + amount: '395521502109448', + token: { + decimals: 18, + symbol: 'ETH', + }, + }, + transactionHash: '0x380400d04ebb4179a35b1d7fdef260776915f015e978f8587ef2704b843d4e53', + }, + txTypeVersion: 2, +}; + +export const tokenData: TokensData = { + nameList: [ 'PQR-Test', 'ETH' ], + symbolList: [ 'PQR' ], + idList: [], + byName: { + 'PQR-Test': { + name: 'PQR-Test', + symbol: 'PQR', + address: '0x1bfe4298796198f8664b18a98640cec7c89b5baa', + id: undefined, + }, + ETH: { name: 'ETH', symbol: null, address: '', id: undefined }, + }, + bySymbol: { + PQR: { + name: 'PQR-Test', + symbol: 'PQR', + address: '0x1bfe4298796198f8664b18a98640cec7c89b5baa', + id: undefined, + }, + 'null': { name: 'ETH', symbol: null, address: '', id: undefined }, + }, +}; diff --git a/mocks/search/index.ts b/mocks/search/index.ts index 9f8907fa71..4009a6d460 100644 --- a/mocks/search/index.ts +++ b/mocks/search/index.ts @@ -5,6 +5,8 @@ import type { SearchResultTx, SearchResultLabel, SearchResult, + SearchResultUserOp, + SearchResultBlob, } from 'types/api/search'; export const token1: SearchResultToken = { @@ -54,6 +56,15 @@ export const block2: SearchResultBlock = { url: '/block/0x1af31d7535dded06bab9a88eb40ee2f8d0529a60ab3b8a7be2ba69b008cacbd2', }; +export const block3: SearchResultBlock = { + block_hash: '0x1af31d7535dded06bab9a88eb40ee2f8d0529a60ab3b8a7be2ba69b008cacbd3', + block_number: 8198536, + block_type: 'uncle', + type: 'block' as const, + timestamp: '2022-12-11T18:11:11Z', + url: '/block/0x1af31d7535dded06bab9a88eb40ee2f8d0529a60ab3b8a7be2ba69b008cacbd3', +}; + export const address1: SearchResultAddressOrContractOrUniversalProfile = { address: '0xb64a30399f7F6b0C154c2E7Af0a3ec7B0A5b131a', name: null, @@ -62,6 +73,20 @@ export const address1: SearchResultAddressOrContractOrUniversalProfile = { url: '/address/0xb64a30399f7F6b0C154c2E7Af0a3ec7B0A5b131a', }; +export const address2: SearchResultAddressOrContractOrUniversalProfile = { + address: '0xb64a30399f7F6b0C154c2E7Af0a3ec7B0A5b131b', + name: null, + type: 'address' as const, + is_smart_contract_verified: false, + url: '/address/0xb64a30399f7F6b0C154c2E7Af0a3ec7B0A5b131b', + ens_info: { + address_hash: '0x1234567890123456789012345678901234567890', + expiry_date: '2022-12-11T17:55:20Z', + name: 'utko.eth', + names_count: 1, + }, +}; + export const contract1: SearchResultAddressOrContractOrUniversalProfile = { address: '0xb64a30399f7F6b0C154c2E7Af0a3ec7B0A5b131a', name: 'Unknown contract in this network', @@ -85,6 +110,19 @@ export const tx1: SearchResultTx = { url: '/tx/0x349d4025d03c6faec117ee10ac0bce7c7a805dd2cbff7a9f101304d9a8a525dd', }; +export const userOp1: SearchResultUserOp = { + timestamp: '2024-01-11T14:15:48.000000Z', + type: 'user_operation', + user_operation_hash: '0xcb560d77b0f3af074fa05c1e5c691bcdfe457e630062b5907e9e71fc74b2ec61', + url: '/op/0xcb560d77b0f3af074fa05c1e5c691bcdfe457e630062b5907e9e71fc74b2ec61', +}; + +export const blob1: SearchResultBlob = { + blob_hash: '0x0108dd3e414da9f3255f7a831afa606e8dfaea93d082dfa9b15305583cbbdbbe', + type: 'blob' as const, + timestamp: null, +}; + export const baseResponse: SearchResult = { items: [ token1, @@ -93,6 +131,7 @@ export const baseResponse: SearchResult = { address1, contract1, tx1, + blob1, ], next_page_params: null, }; diff --git a/mocks/shibarium/deposits.ts b/mocks/shibarium/deposits.ts new file mode 100644 index 0000000000..98bf9d925d --- /dev/null +++ b/mocks/shibarium/deposits.ts @@ -0,0 +1,61 @@ +import type { ShibariumDepositsResponse } from 'types/api/shibarium'; + +export const data: ShibariumDepositsResponse = { + items: [ + { + l1_block_number: 8382841, + timestamp: '2022-05-27T01:13:48.000000Z', + l1_transaction_hash: '0xaf3e5f4ef03eac22a622b3434c5dc9f4465aa291900a86bcf0ad9fb14429f05e', + user: { + hash: '0x6197d1eef304eb5284a0f6720f79403b4e9bf3a5', + implementation_name: null, + is_contract: false, + is_verified: false, + name: null, + private_tags: [], + public_tags: [], + watchlist_names: [], + ens_domain_name: null, + }, + l2_transaction_hash: '0xb9212c76069b926917816767e4c5a0ef80e519b1ac1c3d3fb5818078f4984667', + }, + { + l1_block_number: 8382841, + timestamp: '2022-05-27T01:13:48.000000Z', + l1_transaction_hash: '0xaf3e5f4ef03eac22a622b3434c5dc9f4465aa291900a86bcf0ad9fb14429f05e', + user: { + hash: '0x6197d1eef304eb5284a0f6720f79403b4e9bf3a5', + implementation_name: null, + is_contract: false, + is_verified: false, + name: null, + private_tags: [], + public_tags: [], + watchlist_names: [], + ens_domain_name: null, + }, + l2_transaction_hash: '0xb9212c76069b926917816767e4c5a0ef80e519b1ac1c3d3fb5818078f4984667', + }, + { + l1_block_number: 8382841, + timestamp: '2022-05-27T01:13:48.000000Z', + l1_transaction_hash: '0xaf3e5f4ef03eac22a622b3434c5dc9f4465aa291900a86bcf0ad9fb14429f05e', + user: { + hash: '0x6197d1eef304eb5284a0f6720f79403b4e9bf3a5', + implementation_name: null, + is_contract: false, + is_verified: false, + name: null, + private_tags: [], + public_tags: [], + watchlist_names: [], + ens_domain_name: null, + }, + l2_transaction_hash: '0xb9212c76069b926917816767e4c5a0ef80e519b1ac1c3d3fb5818078f4984667', + }, + ], + next_page_params: { + items_count: 50, + block_number: 8382363, + }, +}; diff --git a/mocks/shibarium/withdrawals.ts b/mocks/shibarium/withdrawals.ts new file mode 100644 index 0000000000..79851f3d1b --- /dev/null +++ b/mocks/shibarium/withdrawals.ts @@ -0,0 +1,61 @@ +import type { ShibariumWithdrawalsResponse } from 'types/api/shibarium'; + +export const data: ShibariumWithdrawalsResponse = { + items: [ + { + l2_block_number: 8382841, + timestamp: '2022-05-27T01:13:48.000000Z', + l1_transaction_hash: '0xaf3e5f4ef03eac22a622b3434c5dc9f4465aa291900a86bcf0ad9fb14429f05e', + user: { + hash: '0x6197d1eef304eb5284a0f6720f79403b4e9bf3a5', + implementation_name: null, + is_contract: false, + is_verified: false, + name: null, + private_tags: [], + public_tags: [], + watchlist_names: [], + ens_domain_name: null, + }, + l2_transaction_hash: '0xb9212c76069b926917816767e4c5a0ef80e519b1ac1c3d3fb5818078f4984667', + }, + { + l2_block_number: 8382841, + timestamp: '2022-05-27T01:13:48.000000Z', + l1_transaction_hash: '0xaf3e5f4ef03eac22a622b3434c5dc9f4465aa291900a86bcf0ad9fb14429f05e', + user: { + hash: '0x6197d1eef304eb5284a0f6720f79403b4e9bf3a5', + implementation_name: null, + is_contract: false, + is_verified: false, + name: null, + private_tags: [], + public_tags: [], + watchlist_names: [], + ens_domain_name: null, + }, + l2_transaction_hash: '0xb9212c76069b926917816767e4c5a0ef80e519b1ac1c3d3fb5818078f4984667', + }, + { + l2_block_number: 8382841, + timestamp: '2022-05-27T01:13:48.000000Z', + l1_transaction_hash: '0xaf3e5f4ef03eac22a622b3434c5dc9f4465aa291900a86bcf0ad9fb14429f05e', + user: { + hash: '0x6197d1eef304eb5284a0f6720f79403b4e9bf3a5', + implementation_name: null, + is_contract: false, + is_verified: false, + name: null, + private_tags: [], + public_tags: [], + watchlist_names: [], + ens_domain_name: null, + }, + l2_transaction_hash: '0xb9212c76069b926917816767e4c5a0ef80e519b1ac1c3d3fb5818078f4984667', + }, + ], + next_page_params: { + items_count: 50, + block_number: 8382363, + }, +}; diff --git a/mocks/stats/daily_txs.ts b/mocks/stats/daily_txs.ts index 03cb3f20ac..afb2dcca58 100644 --- a/mocks/stats/daily_txs.ts +++ b/mocks/stats/daily_txs.ts @@ -126,3 +126,24 @@ export const base = { }, ], }; + +export const partialData = { + chart_data: [ + { date: '2022-11-28', tx_count: 26815 }, + { date: '2022-11-27', tx_count: 34784 }, + { date: '2022-11-26', tx_count: 77527 }, + { date: '2022-11-25', tx_count: null }, + { date: '2022-11-24', tx_count: null }, + { date: '2022-11-23', tx_count: null }, + { date: '2022-11-22', tx_count: 63433 }, + { date: '2022-11-21', tx_count: null }, + ], +}; + +export const noData = { + chart_data: [ + { date: '2022-11-25', tx_count: null }, + { date: '2022-11-24', tx_count: null }, + { date: '2022-11-23', tx_count: null }, + ], +}; diff --git a/mocks/stats/index.ts b/mocks/stats/index.ts index 3bc757e155..d9d00e36e1 100644 --- a/mocks/stats/index.ts +++ b/mocks/stats/index.ts @@ -1,13 +1,34 @@ -import type { HomeStats } from 'types/api/stats'; +import _mapValues from 'lodash/mapValues'; -export const base: HomeStats = { +export const base = { average_block_time: 6212.0, coin_price: '0.00199678', + coin_price_change_percentage: -7.42, gas_prices: { - average: 48.0, - fast: 67.5, - slow: 48.0, + average: { + fiat_price: '1.39', + price: 23.75, + time: 12030.25, + base_fee: 2.22222, + priority_fee: 12.424242, + }, + fast: { + fiat_price: '1.74', + price: 29.72, + time: 8763.25, + base_fee: 4.44444, + priority_fee: 22.242424, + }, + slow: { + fiat_price: '1.35', + price: 23.04, + time: 20100.25, + base_fee: 1.11111, + priority_fee: 7.8909, + }, }, + gas_price_updated_at: '2022-11-11T11:09:49.051171Z', + gas_prices_update_in: 300000, gas_used_today: '4108680603', market_cap: '330809.96443288102524', network_utilization_percentage: 1.55372064, @@ -20,7 +41,30 @@ export const base: HomeStats = { tvl: '1767425.102766552', }; -export const withBtcLocked: HomeStats = { +export const withBtcLocked = { ...base, rootstock_locked_btc: '3337493406696977561374', }; + +export const withoutFiatPrices = { + ...base, + gas_prices: _mapValues(base.gas_prices, (price) => price ? ({ ...price, fiat_price: null }) : null), +}; + +export const withoutGweiPrices = { + ...base, + gas_prices: _mapValues(base.gas_prices, (price) => price ? ({ ...price, price: null }) : null), +}; + +export const withoutBothPrices = { + ...base, + gas_prices: _mapValues(base.gas_prices, (price) => price ? ({ ...price, price: null, fiat_price: null }) : null), +}; + +export const noChartData = { + ...base, + transactions_today: null, + coin_price: null, + market_cap: null, + tvl: null, +}; diff --git a/mocks/stats/line.ts b/mocks/stats/line.ts new file mode 100644 index 0000000000..47de79b184 --- /dev/null +++ b/mocks/stats/line.ts @@ -0,0 +1,128 @@ +export const averageGasPrice = { + chart: [ + { + date: '2023-12-22', + value: '37.7804422597599', + }, + { + date: '2023-12-23', + value: '25.84889883009387', + }, + { + date: '2023-12-24', + value: '25.818463227198574', + }, + { + date: '2023-12-25', + value: '26.045513050051298', + }, + { + date: '2023-12-26', + value: '21.42600692652399', + }, + { + date: '2023-12-27', + value: '31.066730409846656', + }, + { + date: '2023-12-28', + value: '33.63955781902089', + }, + { + date: '2023-12-29', + value: '28.064736756058384', + }, + { + date: '2023-12-30', + value: '23.074500869678175', + }, + { + date: '2023-12-31', + value: '17.651005734615133', + }, + { + date: '2024-01-01', + value: '14.906085174476441', + }, + { + date: '2024-01-02', + value: '22.28459059038656', + }, + { + date: '2024-01-03', + value: '39.8311646806592', + }, + { + date: '2024-01-04', + value: '26.09989322256083', + }, + { + date: '2024-01-05', + value: '22.821996688111998', + }, + { + date: '2024-01-06', + value: '20.32680041262083', + }, + { + date: '2024-01-07', + value: '32.535045831809704', + }, + { + date: '2024-01-08', + value: '27.443477102139482', + }, + { + date: '2024-01-09', + value: '20.7911332558055', + }, + { + date: '2024-01-10', + value: '42.10740192523919', + }, + { + date: '2024-01-11', + value: '35.75215680343582', + }, + { + date: '2024-01-12', + value: '27.430414798093253', + }, + { + date: '2024-01-13', + value: '20.170934096589875', + }, + { + date: '2024-01-14', + value: '38.79660984371034', + }, + { + date: '2024-01-15', + value: '26.140740484554204', + }, + { + date: '2024-01-16', + value: '36.708543184194156', + }, + { + date: '2024-01-17', + value: '40.325438794298876', + }, + { + date: '2024-01-18', + value: '37.55145309930694', + }, + { + date: '2024-01-19', + value: '33.271450114434664', + }, + { + date: '2024-01-20', + value: '19.303304377685638', + }, + { + date: '2024-01-21', + value: '14.375908594704976', + }, + ], +}; diff --git a/mocks/stats/lines.ts b/mocks/stats/lines.ts new file mode 100644 index 0000000000..51aca3c97d --- /dev/null +++ b/mocks/stats/lines.ts @@ -0,0 +1,142 @@ +export const base = { + sections: [ + { + id: 'accounts', + title: 'Accounts', + charts: [ + { + id: 'accountsGrowth', + title: 'Accounts growth', + description: 'Cumulative accounts number per period', + units: null, + }, + { + id: 'activeAccounts', + title: 'Active accounts', + description: 'Active accounts number per period', + units: null, + }, + { + id: 'newAccounts', + title: 'New accounts', + description: 'New accounts number per day', + units: null, + }, + ], + }, + { + id: 'transactions', + title: 'Transactions', + charts: [ + { + id: 'averageTxnFee', + title: 'Average transaction fee', + description: 'The average amount in ETH spent per transaction', + units: 'ETH', + }, + { + id: 'newTxns', + title: 'New transactions', + description: 'New transactions number', + units: null, + }, + { + id: 'txnsFee', + title: 'Transactions fees', + description: 'Amount of tokens paid as fees', + units: 'ETH', + }, + { + id: 'txnsGrowth', + title: 'Transactions growth', + description: 'Cumulative transactions number', + units: null, + }, + { + id: 'txnsSuccessRate', + title: 'Transactions success rate', + description: 'Successful transactions rate per day', + units: null, + }, + ], + }, + { + id: 'blocks', + title: 'Blocks', + charts: [ + { + id: 'averageBlockRewards', + title: 'Average block rewards', + description: 'Average amount of distributed reward in tokens per day', + units: 'ETH', + }, + { + id: 'averageBlockSize', + title: 'Average block size', + description: 'Average size of blocks in bytes', + units: 'Bytes', + }, + { + id: 'newBlocks', + title: 'New blocks', + description: 'New blocks number', + units: null, + }, + ], + }, + { + id: 'tokens', + title: 'Tokens', + charts: [ + { + id: 'newNativeCoinTransfers', + title: 'New ETH transfers', + description: 'New token transfers number for the period', + units: null, + }, + ], + }, + { + id: 'gas', + title: 'Gas', + charts: [ + { + id: 'averageGasLimit', + title: 'Average gas limit', + description: 'Average gas limit per block for the period', + units: null, + }, + { + id: 'averageGasPrice', + title: 'Average gas price', + description: 'Average gas price for the period (Gwei)', + units: 'Gwei', + }, + { + id: 'gasUsedGrowth', + title: 'Gas used growth', + description: 'Cumulative gas used for the period', + units: null, + }, + ], + }, + { + id: 'contracts', + title: 'Contracts', + charts: [ + { + id: 'newVerifiedContracts', + title: 'New verified contracts', + description: 'New verified contracts number for the period', + units: null, + }, + { + id: 'verifiedContractsGrowth', + title: 'Verified contracts growth', + description: 'Cumulative number verified contracts for the period', + units: null, + }, + ], + }, + ], +}; diff --git a/mocks/tokens/tokenHolders.ts b/mocks/tokens/tokenHolders.ts index 582476e903..89f2595482 100644 --- a/mocks/tokens/tokenHolders.ts +++ b/mocks/tokens/tokenHolders.ts @@ -2,18 +2,14 @@ import type { TokenHolders } from 'types/api/token'; import { withName, withoutName } from 'mocks/address/address'; -import { tokenInfoERC1155a, tokenInfoERC20a } from './tokenInfo'; - export const tokenHoldersERC20: TokenHolders = { items: [ { address: withName, - token: tokenInfoERC20a, value: '107014805905725000000', }, { address: withoutName, - token: tokenInfoERC20a, value: '207014805905725000000', }, ], @@ -27,13 +23,11 @@ export const tokenHoldersERC1155: TokenHolders = { items: [ { address: withName, - token: tokenInfoERC1155a, value: '107014805905725000000', token_id: '12345', }, { address: withoutName, - token: tokenInfoERC1155a, value: '207014805905725000000', token_id: '12345', }, diff --git a/mocks/tokens/tokenInfo.ts b/mocks/tokens/tokenInfo.ts index 1034b9bc64..a732712775 100644 --- a/mocks/tokens/tokenInfo.ts +++ b/mocks/tokens/tokenInfo.ts @@ -28,7 +28,7 @@ export const tokenInfoERC20a: TokenInfo<'ERC-20'> = { symbol: 'HyFi', total_supply: '369000000000000000000000000', type: 'ERC-20', - icon_url: 'https://example.com/token-icon.png', + icon_url: 'http://localhost:3000/token-icon.png', }; export const tokenInfoERC20b: TokenInfo<'ERC-20'> = { @@ -174,6 +174,19 @@ export const tokenInfoERC1155WithoutName: TokenInfo<'ERC-1155'> = { icon_url: null, }; +export const tokenInfoERC404: TokenInfo<'ERC-404'> = { + address: '0xB5C457dDB4cE3312a6C5a2b056a1652bd542a208', + circulating_market_cap: '0.0', + decimals: '18', + exchange_rate: '1484.13', + holders: '81', + icon_url: null, + name: 'OMNI404', + symbol: 'O404', + total_supply: '6482275000000000000', + type: 'ERC-404', +}; + export const bridgedTokenA: TokenInfo<'ERC-20'> = { ...tokenInfoERC20a, is_bridged: true, diff --git a/mocks/tokens/tokenTransfer.ts b/mocks/tokens/tokenTransfer.ts index 670f276867..08b87b6858 100644 --- a/mocks/tokens/tokenTransfer.ts +++ b/mocks/tokens/tokenTransfer.ts @@ -10,6 +10,7 @@ export const erc20: TokenTransfer = { private_tags: [], public_tags: [], watchlist_names: [], + ens_domain_name: null, }, to: { hash: '0x7d20a8D54F955b4483A66aB335635ab66e151c51', @@ -20,6 +21,7 @@ export const erc20: TokenTransfer = { private_tags: [], public_tags: [], watchlist_names: [], + ens_domain_name: 'kitty.kitty.cat.eth', }, token: { address: '0x55d536e4d6c1993d8ef2e2a4ef77f02088419420', @@ -55,6 +57,7 @@ export const erc721: TokenTransfer = { private_tags: [], public_tags: [], watchlist_names: [], + ens_domain_name: 'kitty.kitty.cat.eth', }, to: { hash: '0x47eE48AEBc4ab9Ed908b805b8c8dAAa71B31Db1A', @@ -65,6 +68,7 @@ export const erc721: TokenTransfer = { private_tags: [], public_tags: [], watchlist_names: [], + ens_domain_name: null, }, token: { address: '0x363574E6C5C71c343d7348093D84320c76d5Dd29', @@ -99,6 +103,7 @@ export const erc1155A: TokenTransfer = { private_tags: [], public_tags: [], watchlist_names: [], + ens_domain_name: null, }, to: { hash: '0xBb36c792B9B45Aaf8b848A1392B0d6559202729E', @@ -109,6 +114,7 @@ export const erc1155A: TokenTransfer = { private_tags: [], public_tags: [], watchlist_names: [], + ens_domain_name: 'kitty.kitty.cat.eth', }, token: { address: '0xF56b7693E4212C584de4a83117f805B8E89224CB', @@ -164,6 +170,63 @@ export const erc1155D: TokenTransfer = { total: { token_id: '456', value: '42', decimals: null }, }; +export const erc404A: TokenTransfer = { + from: { + hash: '0x0000000000000000000000000000000000000000', + implementation_name: null, + is_contract: false, + is_verified: false, + name: null, + private_tags: [], + public_tags: [], + watchlist_names: [], + ens_domain_name: null, + }, + to: { + hash: '0xBb36c792B9B45Aaf8b848A1392B0d6559202729E', + implementation_name: null, + is_contract: false, + is_verified: false, + name: null, + private_tags: [], + public_tags: [], + watchlist_names: [], + ens_domain_name: 'kitty.kitty.cat.eth', + }, + token: { + address: '0xF56b7693E4212C584de4a83117f805B8E89224CB', + circulating_market_cap: null, + decimals: null, + exchange_rate: null, + holders: '1', + name: null, + symbol: 'MY_SYMBOL_IS_VERY_LONG', + type: 'ERC-404', + total_supply: '0', + icon_url: null, + }, + total: { + value: '42000000000000000000000000', + decimals: '18', + }, + tx_hash: '0x05d6589367633c032d757a69c5fb16c0e33e3994b0d9d1483f82aeee1f05d746', + type: 'token_transfer', + method: 'swap', + timestamp: '2022-10-10T14:34:30.000000Z', + block_hash: '1', + log_index: '1', +}; + +export const erc404B: TokenTransfer = { + ...erc404A, + token: { + ...erc404A.token, + name: 'SastanaNFT', + symbol: 'ipfs://QmUpFUfVKDCWeZQk5pvDFUxnpQP9N6eLSHhNUy49T1JVtY', + }, + total: { token_id: '4625304364899952' }, +}; + export const mixTokens: TokenTransferResponse = { items: [ erc20, @@ -172,6 +235,8 @@ export const mixTokens: TokenTransferResponse = { erc1155B, erc1155C, erc1155D, + erc404A, + erc404B, ], next_page_params: null, }; diff --git a/mocks/txs/internalTxs.ts b/mocks/txs/internalTxs.ts index 8bd2773236..07eb83dc5e 100644 --- a/mocks/txs/internalTxs.ts +++ b/mocks/txs/internalTxs.ts @@ -13,6 +13,7 @@ export const base: InternalTransaction = { private_tags: [], public_tags: [], watchlist_names: [], + ens_domain_name: null, }, gas_limit: '757586', index: 1, @@ -27,6 +28,7 @@ export const base: InternalTransaction = { private_tags: [], public_tags: [], watchlist_names: [], + ens_domain_name: null, }, transaction_hash: '0xe9e27dfeb183066e26cfe556f74b7219b08df6951e25d14003d4fc7af8bbff61', type: 'call', @@ -61,6 +63,7 @@ export const withContractCreated: InternalTransaction = { private_tags: [], public_tags: [], watchlist_names: [], + ens_domain_name: null, }, value: '1420000000000000000', gas_limit: '5433', diff --git a/mocks/txs/state.ts b/mocks/txs/state.ts index 3192de294d..204fe1c867 100644 --- a/mocks/txs/state.ts +++ b/mocks/txs/state.ts @@ -10,6 +10,7 @@ export const mintToken: TxStateChange = { private_tags: [], public_tags: [], watchlist_names: [], + ens_domain_name: null, }, balance_after: null, balance_before: null, @@ -47,6 +48,7 @@ export const receiveMintedToken: TxStateChange = { private_tags: [], public_tags: [], watchlist_names: [], + ens_domain_name: null, }, balance_after: '1', balance_before: '0', @@ -84,6 +86,7 @@ export const transfer1155Token: TxStateChange = { private_tags: [], public_tags: [], watchlist_names: [], + ens_domain_name: null, }, balance_after: '1', balance_before: '0', @@ -115,6 +118,7 @@ export const receiveCoin: TxStateChange = { private_tags: [], public_tags: [], watchlist_names: [], + ens_domain_name: null, }, balance_after: '443787514723917012805', balance_before: '443787484997510408745', @@ -134,6 +138,7 @@ export const sendCoin: TxStateChange = { private_tags: [], public_tags: [], watchlist_names: [], + ens_domain_name: null, }, balance_after: '828282622733717191', balance_before: '832127467556437753', diff --git a/mocks/txs/tx.ts b/mocks/txs/tx.ts index c2d7d8ad0f..0b97508282 100644 --- a/mocks/txs/tx.ts +++ b/mocks/txs/tx.ts @@ -29,6 +29,7 @@ export const base: Transaction = { private_tags: [ ], public_tags: [ publicTag ], watchlist_names: [], + ens_domain_name: 'kitty.kitty.cat.eth', }, gas_limit: '800000', gas_price: '48000000000', @@ -54,6 +55,7 @@ export const base: Transaction = { private_tags: [ privateTag ], public_tags: [], watchlist_names: [ watchlistName ], + ens_domain_name: null, }, token_transfers: [], token_transfers_overflow: false, @@ -97,6 +99,7 @@ export const withContractCreation: Transaction = { private_tags: [], public_tags: [], watchlist_names: [], + ens_domain_name: null, }, tx_types: [ 'contract_creation', @@ -115,6 +118,7 @@ export const withTokenTransfer: Transaction = { private_tags: [ privateTag ], public_tags: [], watchlist_names: [ watchlistName ], + ens_domain_name: null, }, token_transfers: [ tokenTransferMock.erc20, @@ -123,6 +127,8 @@ export const withTokenTransfer: Transaction = { tokenTransferMock.erc1155B, tokenTransferMock.erc1155C, tokenTransferMock.erc1155D, + tokenTransferMock.erc404A, + tokenTransferMock.erc404B, ], token_transfers_overflow: true, tx_types: [ @@ -168,6 +174,7 @@ export const withRawRevertReason: Transaction = { private_tags: [ ], public_tags: [], watchlist_names: [ ], + ens_domain_name: null, }, }; @@ -283,6 +290,7 @@ export const stabilityTx: Transaction = { private_tags: [], public_tags: [], watchlist_names: [], + ens_domain_name: null, }, dapp_fee: '34381250000000', token: { @@ -307,6 +315,7 @@ export const stabilityTx: Transaction = { private_tags: [], public_tags: [], watchlist_names: [], + ens_domain_name: null, }, validator_fee: '34381250000000', }, @@ -334,3 +343,17 @@ export const base4 = { ...base, hash: '0x22d597ebcf3e8d60096dd0363bc2f0f5e2df27ba1dacd696c51aa7c9409f3193', }; + +export const withBlob = { + ...base, + blob_gas_price: '21518435987', + blob_gas_used: '131072', + blob_versioned_hashes: [ + '0x01a8c328b0370068aaaef49c107f70901cd79adcda81e3599a88855532122e09', + '0x0197fdb17195c176b23160f335daabd4b6a231aaaadd73ec567877c66a3affd1', + ], + burnt_blob_fee: '2820464441688064', + max_fee_per_blob_gas: '60000000000', + tx_types: [ 'blob_transaction' as const ], + type: 3, +}; diff --git a/mocks/txs/txInterpretation.ts b/mocks/txs/txInterpretation.ts index b351363a52..e9c1a43b85 100644 --- a/mocks/txs/txInterpretation.ts +++ b/mocks/txs/txInterpretation.ts @@ -33,6 +33,7 @@ export const txInterpretation: TxInterpretationResponse = { private_tags: [], public_tags: [], watchlist_names: [], + ens_domain_name: null, }, }, timestamp: { diff --git a/mocks/user/profile.ts b/mocks/user/profile.ts index e5caed3b63..955f872e01 100644 --- a/mocks/user/profile.ts +++ b/mocks/user/profile.ts @@ -4,3 +4,10 @@ export const base = { name: 'tom goriunov', nickname: 'tom2drum', }; + +export const withoutEmail = { + avatar: 'https://avatars.githubusercontent.com/u/22130104', + email: null, + name: 'tom goriunov', + nickname: 'tom2drum', +}; diff --git a/mocks/userOps/userOp.ts b/mocks/userOps/userOp.ts new file mode 100644 index 0000000000..efb7517187 --- /dev/null +++ b/mocks/userOps/userOp.ts @@ -0,0 +1,77 @@ +import type { UserOp } from 'types/api/userOps'; + +export const userOpData: UserOp = { + timestamp: '2024-01-19T12:42:12.000000Z', + transaction_hash: '0x715fe1474ac7bea3d6f4a03b1c5b6d626675fb0b103be29f849af65e9f1f9c6a', + user_logs_start_index: 40, + fee: '187125856691380', + call_gas_limit: '26624', + gas: '258875', + status: true, + aggregator_signature: null, + block_hash: '0xff5f41ec89e5fb3dfcf103bbbd67469fed491a7dd7cffdf00bd9e3bf45d8aeab', + pre_verification_gas: '48396', + factory: null, + signature: '0x2b95a173c1ea314d2c387e0d84194d221c14805e02157b7cefaf607a53e9081c0099ccbeaa1020ab91b862d4a4743dc1e20b4953f5bb6c13afeac760cef34fd11b', + verification_gas_limit: '61285', + max_fee_per_gas: '1575000898', + aggregator: null, + hash: '0xe72500491b3f2549ac53bd9de9dbb1d2edfc33cdddf5c079d6d64dfec650ef83', + gas_price: '1575000898', + user_logs_count: 1, + block_number: '10399597', + gas_used: '118810', + sender: { + ens_domain_name: null, + hash: '0xF0C14FF4404b188fAA39a3507B388998c10FE284', + implementation_name: null, + is_contract: true, + is_verified: null, + name: null, + }, + nonce: '0x000000000000000000000000000000000000000000000000000000000000004f', + entry_point: { + ens_domain_name: null, + hash: '0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789', + implementation_name: null, + is_contract: true, + is_verified: null, + name: null, + }, + sponsor_type: 'paymaster_sponsor', + raw: { + // eslint-disable-next-line max-len + call_data: '0xb61d27f600000000000000000000000059f6aa952df7f048fd076e33e0ea8bb552d5ffd8000000000000000000000000000000000000000000000000003f3d017500800000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000', + call_gas_limit: '26624', + init_code: '0x', + max_fee_per_gas: '1575000898', + max_priority_fee_per_gas: '1575000898', + nonce: '79', + // eslint-disable-next-line max-len + paymaster_and_data: '0x7cea357b5ac0639f89f9e378a1f03aa5005c0a250000000000000000000000000000000000000000000000000000000065b3a8800000000000000000000000000000000000000000000000000000000065aa6e0028fa4c57ac1141bc9ecd8c9243f618ade8ea1db10ab6c1d1798a222a824764ff2269a72ae7a3680fa8b03a80d8a00cdc710eaf37afdcc55f8c9c4defa3fdf2471b', + pre_verification_gas: '48396', + sender: '0xF0C14FF4404b188fAA39a3507B388998c10FE284', + signature: '0x2b95a173c1ea314d2c387e0d84194d221c14805e02157b7cefaf607a53e9081c0099ccbeaa1020ab91b862d4a4743dc1e20b4953f5bb6c13afeac760cef34fd11b', + verification_gas_limit: '61285', + }, + max_priority_fee_per_gas: '1575000898', + revert_reason: null, + bundler: { + ens_domain_name: null, + hash: '0xd53Eb5203e367BbDD4f72338938224881Fc501Ab', + implementation_name: null, + is_contract: false, + is_verified: null, + name: null, + }, + // eslint-disable-next-line max-len + call_data: '0xb61d27f600000000000000000000000059f6aa952df7f048fd076e33e0ea8bb552d5ffd8000000000000000000000000000000000000000000000000003f3d017500800000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000', + paymaster: { + ens_domain_name: null, + hash: '0x7ceA357B5AC0639F89F9e378a1f03Aa5005C0a25', + implementation_name: null, + is_contract: true, + is_verified: null, + name: null, + }, +}; diff --git a/mocks/userOps/userOps.ts b/mocks/userOps/userOps.ts new file mode 100644 index 0000000000..a58ff6ed74 --- /dev/null +++ b/mocks/userOps/userOps.ts @@ -0,0 +1,58 @@ +import type { UserOpsResponse } from 'types/api/userOps'; + +export const userOpsData: UserOpsResponse = { + items: [ + { + address: { + ens_domain_name: null, + hash: '0xF0C14FF4404b188fAA39a3507B388998c10FE284', + implementation_name: null, + is_contract: true, + is_verified: null, + name: null, + }, + block_number: '10399597', + fee: '187125856691380', + hash: '0xe72500491b3f2549ac53bd9de9dbb1d2edfc33cdddf5c079d6d64dfec650ef83', + status: true, + timestamp: '2022-01-19T12:42:12.000000Z', + transaction_hash: '0x715fe1474ac7bea3d6f4a03b1c5b6d626675fb0b103be29f849af65e9f1f9c6a', + }, + { + address: + { ens_domain_name: null, + hash: '0x2c298CcaFFD1549e1C21F46966A6c236fCC66dB2', + implementation_name: null, + is_contract: true, + is_verified: null, + name: null, + }, + block_number: '10399596', + fee: '381895502291373', + hash: '0xcb945ae86608bdc88c3318245403c81a880fcb1e49fef18ac59477761c056cea', + status: false, + timestamp: '2022-01-19T12:42:00.000000Z', + transaction_hash: '0x558d699e7cbc235461d48ed04b8c3892d598a4000f20851760d00dc3513c2e48', + }, + { + address: { + ens_domain_name: null, + hash: '0x2c298CcaFFD1549e1C21F46966A6c236fCC66dB2', + implementation_name: null, + is_contract: true, + is_verified: null, + name: null, + }, + block_number: '10399560', + fee: '165019501210143', + hash: '0x84c1270b12af3f0ffa204071f1bf503ebf9b1ccf6310680383be5a2b6fd1d8e5', + status: true, + timestamp: '2022-01-19T12:32:00.000000Z', + transaction_hash: '0xc4c1c38680ec63139411aa2193275e8de44be15217c4256db9473bf0ea2b6264', + }, + ], + next_page_params: { + page_size: 50, + page_token: '10396582,0x9bf4d2a28813c5c244884cb20cdfe01dabb3f927234ae961eab6e38502de7a28', + }, +}; diff --git a/mocks/validators/index.ts b/mocks/validators/index.ts new file mode 100644 index 0000000000..22081cae8c --- /dev/null +++ b/mocks/validators/index.ts @@ -0,0 +1,33 @@ +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/zkSync/zkSyncTxnBatch.ts b/mocks/zkSync/zkSyncTxnBatch.ts new file mode 100644 index 0000000000..ab865dc513 --- /dev/null +++ b/mocks/zkSync/zkSyncTxnBatch.ts @@ -0,0 +1,20 @@ +import type { ZkSyncBatch } from 'types/api/zkSyncL2'; + +export const base: ZkSyncBatch = { + commit_transaction_hash: '0x7cd80c88977c2b310f79196b0b2136da18012be015ce80d0d9e9fe6cfad52b16', + commit_transaction_timestamp: '2022-03-19T09:37:38.726996Z', + end_block: 1245490, + execute_transaction_hash: '0x110b9a19afbabd5818a996ab2b493a9b23c888d73d95f1ab5272dbae503e103a', + execute_transaction_timestamp: '2022-03-19T10:29:05.358066Z', + l1_gas_price: '4173068062', + l1_tx_count: 0, + l2_fair_gas_price: '100000000', + l2_tx_count: 287, + number: 8051, + prove_transaction_hash: '0xb424162ba5afe17c710dceb5fc8d15d7d46a66223454dae8c74aa39f6802625b', + prove_transaction_timestamp: '2022-03-19T10:29:05.279179Z', + root_hash: '0x108c635b94f941fcabcb85500daec2f6be4f0747dff649b1cdd9dd7a7a264792', + start_block: 1245209, + status: 'Executed on L1', + timestamp: '2022-03-19T09:05:49.000000Z', +}; diff --git a/mocks/zkSync/zkSyncTxnBatches.ts b/mocks/zkSync/zkSyncTxnBatches.ts new file mode 100644 index 0000000000..a717308641 --- /dev/null +++ b/mocks/zkSync/zkSyncTxnBatches.ts @@ -0,0 +1,49 @@ +import type { ZkSyncBatchesItem, ZkSyncBatchesResponse } from 'types/api/zkSyncL2'; + +export const sealed: ZkSyncBatchesItem = { + commit_transaction_hash: null, + commit_transaction_timestamp: null, + execute_transaction_hash: null, + execute_transaction_timestamp: null, + number: 8055, + prove_transaction_hash: null, + prove_transaction_timestamp: null, + status: 'Sealed on L2', + timestamp: '2022-03-19T12:53:36.000000Z', + tx_count: 738, +}; + +export const sent: ZkSyncBatchesItem = { + commit_transaction_hash: '0x262e7215739d6a7e33b2c20b45a838801a0f5f080f20bec8e54eb078420c4661', + commit_transaction_timestamp: '2022-03-19T13:09:07.357570Z', + execute_transaction_hash: null, + execute_transaction_timestamp: null, + number: 8054, + prove_transaction_hash: null, + prove_transaction_timestamp: null, + status: 'Sent to L1', + timestamp: '2022-03-19T11:36:45.000000Z', + tx_count: 766, +}; + +export const executed: ZkSyncBatchesItem = { + commit_transaction_hash: '0xa2628f477e1027ac1c60fa75c186b914647769ac1cb9c7e1cab50b13506a0035', + commit_transaction_timestamp: '2022-03-19T11:52:18.963659Z', + execute_transaction_hash: '0xb7bd6b2b17498c66d3f6e31ac3685133a81b7f728d4f6a6f42741daa257d0d68', + execute_transaction_timestamp: '2022-03-19T13:28:16.712656Z', + number: 8053, + prove_transaction_hash: '0x9d44f2b775bd771f8a53205755b3897929aa672d2cd419b3b988c16d41d4f21e', + prove_transaction_timestamp: '2022-03-19T13:28:16.603104Z', + status: 'Executed on L1', + timestamp: '2022-03-19T10:01:52.000000Z', + tx_count: 1071, +}; + +export const baseResponse: ZkSyncBatchesResponse = { + items: [ + sealed, + sent, + executed, + ], + next_page_params: null, +}; diff --git a/mocks/zkevmL2txnBatches/zkevmL2txnBatch.ts b/mocks/zkevmL2txnBatches/zkevmL2txnBatch.ts index 172481ce60..56a0f67c33 100644 --- a/mocks/zkevmL2txnBatches/zkevmL2txnBatch.ts +++ b/mocks/zkevmL2txnBatches/zkevmL2txnBatch.ts @@ -1,4 +1,4 @@ -import type { ZkEvmL2TxnBatch } from 'types/api/zkEvmL2TxnBatches'; +import type { ZkEvmL2TxnBatch } from 'types/api/zkEvmL2'; export const txnBatchData: ZkEvmL2TxnBatch = { acc_input_hash: '0x4bf88aabe33713b7817266d7860912c58272d808da7397cdc627ca53b296fad3', diff --git a/mocks/zkevmL2txnBatches/zkevmL2txnBatches.ts b/mocks/zkevmL2txnBatches/zkevmL2txnBatches.ts index 5d81a72686..895f9a7744 100644 --- a/mocks/zkevmL2txnBatches/zkevmL2txnBatches.ts +++ b/mocks/zkevmL2txnBatches/zkevmL2txnBatches.ts @@ -1,4 +1,4 @@ -import type { ZkEvmL2TxnBatchesResponse } from 'types/api/zkEvmL2TxnBatches'; +import type { ZkEvmL2TxnBatchesResponse } from 'types/api/zkEvmL2'; export const txnBatchesData: ZkEvmL2TxnBatchesResponse = { items: [ diff --git a/nextjs/csp/generateCspPolicy.ts b/nextjs/csp/generateCspPolicy.ts index 0a91366a98..fa505e3ddf 100644 --- a/nextjs/csp/generateCspPolicy.ts +++ b/nextjs/csp/generateCspPolicy.ts @@ -9,6 +9,7 @@ function generateCspPolicy() { descriptors.googleAnalytics(), descriptors.googleFonts(), descriptors.googleReCaptcha(), + descriptors.growthBook(), descriptors.mixpanel(), descriptors.monaco(), descriptors.safe(), diff --git a/nextjs/csp/policies/ad.ts b/nextjs/csp/policies/ad.ts index 2808dfb07c..55f23ff913 100644 --- a/nextjs/csp/policies/ad.ts +++ b/nextjs/csp/policies/ad.ts @@ -3,30 +3,57 @@ import sha256 from 'crypto-js/sha256'; import type CspDev from 'csp-dev'; import { connectAdbutler, placeAd } from 'ui/shared/ad/adbutlerScript'; +import { hypeInit } from 'ui/shared/ad/hypeBannerScript'; export function ad(): CspDev.DirectiveDescriptor { return { 'connect-src': [ + // coinzilla 'coinzilla.com', '*.coinzilla.com', 'https://request-global.czilladx.com', + + // slise '*.slise.xyz', + + // hype + 'api.hypelab.com', + '*.ixncdn.com', + + //getit + 'v1.getittech.io', + 'ipapi.co', ], 'frame-src': [ + // coinzilla 'https://request-global.czilladx.com', ], 'script-src': [ + // coinzilla 'coinzillatag.com', + + // adbutler 'servedbyadbutler.com', `'sha256-${ Base64.stringify(sha256(connectAdbutler)) }'`, `'sha256-${ Base64.stringify(sha256(placeAd ?? '')) }'`, + + // slise '*.slise.xyz', + + //hype + `'sha256-${ Base64.stringify(sha256(hypeInit ?? '')) }'`, + 'https://api.hypelab.com', + 'd1q98dzwj6s2rb.cloudfront.net', ], 'img-src': [ - 'servedbyadbutler.com', + // coinzilla 'cdn.coinzilla.io', + + // adbutler + 'servedbyadbutler.com', ], 'font-src': [ + // coinzilla 'https://request-global.czilladx.com', ], }; diff --git a/nextjs/csp/policies/app.ts b/nextjs/csp/policies/app.ts index 37822e4766..d1596cb1c5 100644 --- a/nextjs/csp/policies/app.ts +++ b/nextjs/csp/policies/app.ts @@ -31,6 +31,8 @@ const getCspReportUrl = () => { }; export function app(): CspDev.DirectiveDescriptor { + const marketplaceFeaturePayload = getFeaturePayload(config.features.marketplace); + return { 'default-src': [ // KEY_WORDS.NONE, @@ -53,6 +55,8 @@ export function app(): CspDev.DirectiveDescriptor { getFeaturePayload(config.features.sol2uml)?.api.endpoint, 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 : '', // chain RPC server config.chain.rpcUrl, diff --git a/nextjs/csp/policies/growthBook.ts b/nextjs/csp/policies/growthBook.ts new file mode 100644 index 0000000000..ae0f055fb8 --- /dev/null +++ b/nextjs/csp/policies/growthBook.ts @@ -0,0 +1,15 @@ +import type CspDev from 'csp-dev'; + +import config from 'configs/app'; + +export function growthBook(): CspDev.DirectiveDescriptor { + if (!config.features.growthBook.isEnabled) { + return {}; + } + + return { + 'connect-src': [ + 'cdn.growthbook.io', + ], + }; +} diff --git a/nextjs/csp/policies/index.ts b/nextjs/csp/policies/index.ts index 4ff00e072c..10fd95a484 100644 --- a/nextjs/csp/policies/index.ts +++ b/nextjs/csp/policies/index.ts @@ -4,6 +4,7 @@ export { cloudFlare } from './cloudFlare'; export { googleAnalytics } from './googleAnalytics'; export { googleFonts } from './googleFonts'; export { googleReCaptcha } from './googleReCaptcha'; +export { growthBook } from './growthBook'; export { mixpanel } from './mixpanel'; export { monaco } from './monaco'; export { safe } from './safe'; diff --git a/nextjs/getServerSideProps.ts b/nextjs/getServerSideProps.ts index 2f62492617..6b3d959ab2 100644 --- a/nextjs/getServerSideProps.ts +++ b/nextjs/getServerSideProps.ts @@ -1,6 +1,9 @@ import type { GetServerSideProps } from 'next'; import config from 'configs/app'; +import isNeedProxy from 'lib/api/isNeedProxy'; +const rollupFeature = config.features.rollup; +const adBannerFeature = config.features.adsBanner; export type Props = { cookies: string; @@ -10,9 +13,24 @@ export type Props = { hash: string; number: string; q: string; + name: string; + adBannerProvider: string; } export const base: GetServerSideProps = async({ req, query }) => { + const adBannerProvider = (() => { + if (adBannerFeature.isEnabled) { + if ('additionalProvider' in adBannerFeature && adBannerFeature.additionalProvider) { + // we need to get a random ad provider on the server side to keep it consistent with the client side + const randomIndex = Math.round(Math.random()); + return [ adBannerFeature.provider, adBannerFeature.additionalProvider ][randomIndex]; + } else { + return adBannerFeature.provider; + } + } + return ''; + })(); + return { props: { cookies: req.headers.cookie || '', @@ -22,6 +40,8 @@ export const base: GetServerSideProps = async({ req, query }) => { height_or_hash: query.height_or_hash?.toString() || '', number: query.number?.toString() || '', q: query.q?.toString() || '', + name: query.name?.toString() || '', + adBannerProvider, }, }; }; @@ -46,8 +66,31 @@ export const verifiedAddresses: GetServerSideProps = async(context) => { return account(context); }; -export const beaconChain: GetServerSideProps = async(context) => { - if (!config.features.beaconChain.isEnabled) { +export const deposits: GetServerSideProps = async(context) => { + if (!(rollupFeature.isEnabled && (rollupFeature.type === 'optimistic' || rollupFeature.type === 'shibarium'))) { + return { + notFound: true, + }; + } + + return base(context); +}; + +export const withdrawals: GetServerSideProps = async(context) => { + if ( + !config.features.beaconChain.isEnabled && + !(rollupFeature.isEnabled && (rollupFeature.type === 'optimistic' || rollupFeature.type === 'shibarium')) + ) { + return { + notFound: true, + }; + } + + return base(context); +}; + +export const rollup: GetServerSideProps = async(context) => { + if (!config.features.rollup.isEnabled) { return { notFound: true, }; @@ -56,8 +99,8 @@ export const beaconChain: GetServerSideProps = async(context) => { return base(context); }; -export const L2: GetServerSideProps = async(context) => { - if (!config.features.optimisticRollup.isEnabled) { +export const optimisticRollup: GetServerSideProps = async(context) => { + if (!(rollupFeature.isEnabled && rollupFeature.type === 'optimistic')) { return { notFound: true, }; @@ -66,8 +109,8 @@ export const L2: GetServerSideProps = async(context) => { return base(context); }; -export const zkEvmL2: GetServerSideProps = async(context) => { - if (!config.features.zkEvmRollup.isEnabled) { +export const batch: GetServerSideProps = async(context) => { + if (!(rollupFeature.isEnabled && (rollupFeature.type === 'zkEvm' || rollupFeature.type === 'zkSync'))) { return { notFound: true, }; @@ -126,6 +169,16 @@ export const suave: GetServerSideProps = async(context) => { return base(context); }; +export const nameService: GetServerSideProps = async(context) => { + if (!config.features.nameService.isEnabled) { + return { + notFound: true, + }; + } + + return base(context); +}; + export const accounts: GetServerSideProps = async(context) => { if (config.UI.views.address.hiddenViews?.top_accounts) { return { @@ -135,3 +188,54 @@ export const accounts: GetServerSideProps = async(context) => { return base(context); }; + +export const userOps: GetServerSideProps = async(context) => { + if (!config.features.userOps.isEnabled) { + return { + notFound: true, + }; + } + + return base(context); +}; + +export const validators: GetServerSideProps = async(context) => { + if (!config.features.validators.isEnabled) { + return { + notFound: true, + }; + } + + return base(context); +}; + +export const gasTracker: GetServerSideProps = async(context) => { + if (!config.features.gasTracker.isEnabled) { + return { + notFound: true, + }; + } + + return base(context); +}; + +export const dataAvailability: GetServerSideProps = async(context) => { + if (!config.features.dataAvailability.isEnabled) { + return { + notFound: true, + }; + } + + return base(context); +}; + +export const login: GetServerSideProps = async(context) => { + + if (!isNeedProxy()) { + return { + notFound: true, + }; + } + + return base(context); +}; diff --git a/nextjs/nextjs-routes.d.ts b/nextjs/nextjs-routes.d.ts index aa7c203b56..ee87eb5dc1 100644 --- a/nextjs/nextjs-routes.d.ts +++ b/nextjs/nextjs-routes.d.ts @@ -26,17 +26,23 @@ declare module "nextjs-routes" { | StaticRoute<"/auth/auth0"> | StaticRoute<"/auth/profile"> | StaticRoute<"/auth/unverified-email"> + | DynamicRoute<"/batches/[number]", { "number": string }> + | StaticRoute<"/batches"> + | DynamicRoute<"/blobs/[hash]", { "hash": string }> | DynamicRoute<"/block/[height_or_hash]", { "height_or_hash": string }> | StaticRoute<"/blocks"> | StaticRoute<"/contract-verification"> | StaticRoute<"/csv-export"> + | StaticRoute<"/deposits"> + | StaticRoute<"/gas-tracker"> | StaticRoute<"/graphiql"> | StaticRoute<"/"> - | StaticRoute<"/l2-deposits"> - | StaticRoute<"/l2-output-roots"> - | StaticRoute<"/l2-txn-batches"> - | StaticRoute<"/l2-withdrawals"> | StaticRoute<"/login"> + | DynamicRoute<"/name-domains/[name]", { "name": string }> + | StaticRoute<"/name-domains"> + | DynamicRoute<"/op/[hash]", { "hash": string }> + | StaticRoute<"/ops"> + | StaticRoute<"/output-roots"> | StaticRoute<"/search-results"> | StaticRoute<"/stats"> | DynamicRoute<"/token/[hash]", { "hash": string }> @@ -45,11 +51,10 @@ declare module "nextjs-routes" { | DynamicRoute<"/tx/[hash]", { "hash": string }> | StaticRoute<"/txs"> | DynamicRoute<"/txs/kettle/[hash]", { "hash": string }> + | StaticRoute<"/validators"> | StaticRoute<"/verified-contracts"> | StaticRoute<"/visualize/sol2uml"> - | StaticRoute<"/withdrawals"> - | DynamicRoute<"/zkevm-l2-txn-batch/[number]", { "number": string }> - | StaticRoute<"/zkevm-l2-txn-batches">; + | StaticRoute<"/withdrawals">; interface StaticRoute { pathname: Pathname; diff --git a/nextjs/redirects.js b/nextjs/redirects.js index 3c2ed3ba9a..3a0317d0e4 100644 --- a/nextjs/redirects.js +++ b/nextjs/redirects.js @@ -242,6 +242,32 @@ const oldUrls = [ source: '/token/:hash/write-proxy', destination: '/token/:hash?tab=write_proxy', }, + + // ROLLUPs + { + source: '/l2-txn-batches', + destination: '/batches', + }, + { + source: '/zkevm-l2-txn-batches', + destination: '/batches', + }, + { + source: '/zkevm-l2-txn-batch/:path*', + destination: '/batches/:path*', + }, + { + source: '/l2-deposits', + destination: '/deposits', + }, + { + source: '/l2-withdrawals', + destination: '/withdrawals', + }, + { + source: '/l2-output-roots', + destination: '/output-roots', + }, ]; async function redirects() { diff --git a/nextjs/utils/fetch.ts b/nextjs/utils/fetch.ts index f372d996bd..22bfc32ff6 100644 --- a/nextjs/utils/fetch.ts +++ b/nextjs/utils/fetch.ts @@ -1,4 +1,5 @@ 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'; @@ -14,16 +15,18 @@ export default function fetchFactory( // first arg can be only a string // FIXME migrate to RequestInfo later if needed return function fetch(url: string, init?: RequestInit): Promise { - const csrfToken = _req.headers['x-csrf-token']; - const authToken = _req.headers['Authorization']; 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 }` : '', - ...(csrfToken ? { 'x-csrf-token': String(csrfToken) } : {}), - ...(authToken ? { Authorization: String(authToken) } : {}), + ..._pick(_req.headers, [ + 'x-csrf-token', + 'Authorization', + // feature flags + 'updated-gas-oracle', + ]) as Record, }; httpLogger.logger.info({ diff --git a/package.json b/package.json index e8738214cf..eed616c134 100644 --- a/package.json +++ b/package.json @@ -4,8 +4,8 @@ "private": false, "homepage": "https://github.com/blockscout/frontend#readme", "engines": { - "node": "18", - "npm": "8" + "node": "20.11.0", + "npm": "10.2.4" }, "scripts": { "dev": "./tools/scripts/dev.sh", @@ -24,8 +24,10 @@ "svg:build-sprite": "icons build -i ./icons -o ./public/icons --optimize", "test:pw": "./tools/scripts/pw.sh", "test:pw:local": "export NODE_PATH=$(pwd)/node_modules && yarn test:pw", - "test:pw:docker": "docker run --rm --network host -v $(pwd):/work/ -w /work/ -it mcr.microsoft.com/playwright:v1.35.1-focal ./tools/scripts/pw.docker.sh", + "test:pw:docker": "docker run --rm --network host -v $(pwd):/work/ -w /work/ -it mcr.microsoft.com/playwright:v1.41.1-focal ./tools/scripts/pw.docker.sh", + "test:pw:docker:deps": "docker run --rm --network host -v $(pwd):/work/ -w /work/ -it mcr.microsoft.com/playwright:v1.41.1-focal ./tools/scripts/pw.docker.deps.sh", "test:pw:ci": "yarn test:pw --project=$PW_PROJECT", + "test:pw:detect-affected": "node ./deploy/tools/affected-tests/index.js", "test:jest": "jest", "test:jest:watch": "jest --watch", "favicon:generate:dev": "./tools/scripts/favicon-generator.dev.sh" @@ -36,6 +38,8 @@ "@emotion/react": "^11.10.4", "@emotion/styled": "^11.10.4", "@lukso/web-components": "^1.47.1", + "@growthbook/growthbook-react": "0.21.0", + "@hypelab/sdk-react": "^1.0.0", "@metamask/post-message-stream": "^7.0.0", "@metamask/providers": "^10.2.1", "@monaco-editor/react": "^4.4.6", @@ -55,25 +59,26 @@ "@tanstack/react-query-devtools": "^5.4.3", "@types/papaparse": "^5.3.5", "@types/react-scroll": "^1.8.4", - "@web3modal/wagmi": "3.5.0", - "@web3modal/ethereum": "^2.6.2", - "@web3modal/react": "^2.6.2", + "@web3modal/wagmi": "4.1.3", "algoliasearch": "^4.20.0", "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.1.0", + "dappscout-iframe": "0.2.1", "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", "graphiql": "^2.2.0", "graphql": "^16.8.1", "graphql-ws": "^5.11.3", "js-cookie": "^3.0.1", "lodash": "^4.0.0", + "magic-bytes.js": "1.8.0", "mixpanel-browser": "^2.47.0", "monaco-editor": "^0.34.1", "next": "13.5.4", @@ -97,13 +102,13 @@ "react-scroll": "^1.8.7", "swagger-ui-react": "^5.9.0", "use-font-face-observer": "^1.2.1", - "viem": "1.20.1", - "wagmi": "1.4.12", + "viem": "2.9.6", + "wagmi": "2.5.16", "xss": "^1.0.14" }, "devDependencies": { - "@playwright/experimental-ct-react": "1.35.1", - "@playwright/test": "^1.35.1", + "@playwright/experimental-ct-react": "1.41.1", + "@playwright/test": "1.41.1", "@svgr/webpack": "^6.5.1", "@tanstack/eslint-plugin-query": "^5.0.5", "@testing-library/react": "^14.0.0", @@ -115,7 +120,7 @@ "@types/jest": "^29.2.0", "@types/js-cookie": "^3.0.2", "@types/mixpanel-browser": "^2.38.1", - "@types/node": "18.11.18", + "@types/node": "20.11.0", "@types/phoenix": "^1.5.4", "@types/qrcode": "^1.5.0", "@types/react": "18.0.9", @@ -146,7 +151,7 @@ "svgo": "^2.8.0", "ts-jest": "^29.0.3", "ts-node": "^10.9.1", - "typescript": "^5.1.0", + "typescript": "5.4.2", "vite-plugin-svgr": "^2.2.2", "vite-tsconfig-paths": "^3.5.2", "ws": "^8.11.0" diff --git a/pages/_app.tsx b/pages/_app.tsx index 14a70d043f..15fc5edd91 100644 --- a/pages/_app.tsx +++ b/pages/_app.tsx @@ -1,4 +1,5 @@ import type { ChakraProps } from '@chakra-ui/react'; +import { GrowthBookProvider } from '@growthbook/growthbook-react'; import * as Sentry from '@sentry/react'; import { QueryClientProvider } from '@tanstack/react-query'; import { ReactQueryDevtools } from '@tanstack/react-query-devtools'; @@ -12,6 +13,9 @@ import useQueryClientConfig from 'lib/api/useQueryClientConfig'; import { AppContextProvider } from 'lib/contexts/app'; import { ChakraProvider } from 'lib/contexts/chakra'; 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'; @@ -20,6 +24,7 @@ import Layout from 'ui/shared/layout/Layout'; import Web3ModalProvider from 'ui/shared/Web3ModalProvider'; import 'lib/setLocale'; +// import 'focus-visible/dist/focus-visible'; type AppPropsWithLayout = AppProps & { Component: NextPageWithLayout; @@ -39,6 +44,9 @@ const ERROR_SCREEN_STYLES: ChakraProps = { function MyApp({ Component, pageProps }: AppPropsWithLayout) { + useLoadFeatures(); + useNotifyOnNavigation(); + const queryClient = useQueryClientConfig(); const handleError = React.useCallback((error: Error) => { @@ -56,11 +64,13 @@ function MyApp({ Component, pageProps }: AppPropsWithLayout) { - - - { getLayout() } - - + + + + { getLayout() } + + + diff --git a/pages/api/media-type.ts b/pages/api/media-type.ts index f64301f094..7489fecdf1 100644 --- a/pages/api/media-type.ts +++ b/pages/api/media-type.ts @@ -22,11 +22,13 @@ export default async function mediaTypeHandler(req: NextApiRequest, res: NextApi return 'video'; } + if (contentType?.startsWith('image')) { + return 'image'; + } + if (contentType?.startsWith('text/html')) { return 'html'; } - - return 'image'; })(); res.status(200).json({ type: mediaType }); } catch (error) { diff --git a/pages/apps/index.tsx b/pages/apps/index.tsx index 17f75586ac..aa5c0bae15 100644 --- a/pages/apps/index.tsx +++ b/pages/apps/index.tsx @@ -4,31 +4,13 @@ import React from 'react'; import PageNextJs from 'nextjs/PageNextJs'; -import config from 'configs/app'; -import LinkExternal from 'ui/shared/LinkExternal'; -import PageTitle from 'ui/shared/Page/PageTitle'; - -const feature = config.features.marketplace; - const Marketplace = dynamic(() => import('ui/pages/Marketplace'), { ssr: false }); -const Page: NextPage = () => { - return ( - - <> - - Submit app - - ) } - /> - - - - ); -}; +const Page: NextPage = () => ( + + + +); export default Page; diff --git a/pages/batches/[number].tsx b/pages/batches/[number].tsx new file mode 100644 index 0000000000..870a1cc7b4 --- /dev/null +++ b/pages/batches/[number].tsx @@ -0,0 +1,36 @@ +import type { NextPage } from 'next'; +import dynamic from 'next/dynamic'; +import React from 'react'; + +import type { Props } from 'nextjs/getServerSideProps'; +import PageNextJs from 'nextjs/PageNextJs'; + +import config from 'configs/app'; + +const rollupFeature = config.features.rollup; + +const Batch = dynamic(() => { + if (!rollupFeature.isEnabled) { + throw new Error('Rollup feature is not enabled.'); + } + + switch (rollupFeature.type) { + case 'zkEvm': + return import('ui/pages/ZkEvmL2TxnBatch'); + case 'zkSync': + return import('ui/pages/ZkSyncL2TxnBatch'); + } + throw new Error('Txn batches feature is not enabled.'); +}, { ssr: false }); + +const Page: NextPage = (props: Props) => { + return ( + + + + ); +}; + +export default Page; + +export { batch as getServerSideProps } from 'nextjs/getServerSideProps'; diff --git a/pages/batches/index.tsx b/pages/batches/index.tsx new file mode 100644 index 0000000000..f5bea98b4b --- /dev/null +++ b/pages/batches/index.tsx @@ -0,0 +1,36 @@ +import type { NextPage } from 'next'; +import dynamic from 'next/dynamic'; +import React from 'react'; + +import PageNextJs from 'nextjs/PageNextJs'; + +import config from 'configs/app'; +const rollupFeature = config.features.rollup; + +const Batches = dynamic(() => { + if (!rollupFeature.isEnabled) { + throw new Error('Rollup feature is not enabled.'); + } + + switch (rollupFeature.type) { + case 'zkEvm': + return import('ui/pages/ZkEvmL2TxnBatches'); + case 'zkSync': + return import('ui/pages/ZkSyncL2TxnBatches'); + case 'optimistic': + return import('ui/pages/OptimisticL2TxnBatches'); + } + throw new Error('Txn batches feature is not enabled.'); +}, { ssr: false }); + +const Page: NextPage = () => { + return ( + + + + ); +}; + +export default Page; + +export { rollup as getServerSideProps } from 'nextjs/getServerSideProps'; diff --git a/pages/blobs/[hash].tsx b/pages/blobs/[hash].tsx new file mode 100644 index 0000000000..f1056d36fa --- /dev/null +++ b/pages/blobs/[hash].tsx @@ -0,0 +1,20 @@ +import type { NextPage } from 'next'; +import dynamic from 'next/dynamic'; +import React from 'react'; + +import type { Props } from 'nextjs/getServerSideProps'; +import PageNextJs from 'nextjs/PageNextJs'; + +const Blob = dynamic(() => import('ui/pages/Blob'), { ssr: false }); + +const Page: NextPage = (props: Props) => { + return ( + + + + ); +}; + +export default Page; + +export { dataAvailability as getServerSideProps } from 'nextjs/getServerSideProps'; diff --git a/pages/deposits/index.tsx b/pages/deposits/index.tsx new file mode 100644 index 0000000000..789dd39657 --- /dev/null +++ b/pages/deposits/index.tsx @@ -0,0 +1,32 @@ +import type { NextPage } from 'next'; +import dynamic from 'next/dynamic'; +import React from 'react'; + +import PageNextJs from 'nextjs/PageNextJs'; + +import config from 'configs/app'; +const rollupFeature = config.features.rollup; + +const Deposits = dynamic(() => { + if (rollupFeature.isEnabled && rollupFeature.type === 'optimistic') { + return import('ui/pages/OptimisticL2Deposits'); + } + + if (rollupFeature.isEnabled && rollupFeature.type === 'shibarium') { + return import('ui/pages/ShibariumDeposits'); + } + + throw new Error('Withdrawals feature is not enabled.'); +}, { ssr: false }); + +const Page: NextPage = () => { + return ( + + + + ); +}; + +export default Page; + +export { deposits as getServerSideProps } from 'nextjs/getServerSideProps'; diff --git a/pages/l2-txn-batches.tsx b/pages/gas-tracker.tsx similarity index 51% rename from pages/l2-txn-batches.tsx rename to pages/gas-tracker.tsx index 391a77c283..e407fa5cb5 100644 --- a/pages/l2-txn-batches.tsx +++ b/pages/gas-tracker.tsx @@ -4,16 +4,16 @@ import React from 'react'; import PageNextJs from 'nextjs/PageNextJs'; -const L2TxnBatches = dynamic(() => import('ui/pages/L2TxnBatches'), { ssr: false }); +const GasTracker = dynamic(() => import('ui/pages/GasTracker'), { ssr: false }); const Page: NextPage = () => { return ( - - + + ); }; export default Page; -export { L2 as getServerSideProps } from 'nextjs/getServerSideProps'; +export { gasTracker as getServerSideProps } from 'nextjs/getServerSideProps'; diff --git a/pages/l2-output-roots.tsx b/pages/l2-output-roots.tsx deleted file mode 100644 index 06a84a5c9b..0000000000 --- a/pages/l2-output-roots.tsx +++ /dev/null @@ -1,19 +0,0 @@ -import type { NextPage } from 'next'; -import dynamic from 'next/dynamic'; -import React from 'react'; - -import PageNextJs from 'nextjs/PageNextJs'; - -const L2OutputRoots = dynamic(() => import('ui/pages/L2OutputRoots'), { ssr: false }); - -const Page: NextPage = () => { - return ( - - - - ); -}; - -export default Page; - -export { L2 as getServerSideProps } from 'nextjs/getServerSideProps'; diff --git a/pages/login.tsx b/pages/login.tsx index a74410f613..11562f856b 100644 --- a/pages/login.tsx +++ b/pages/login.tsx @@ -15,4 +15,4 @@ const Page: NextPage = () => { export default Page; -export { base as getServerSideProps } from 'nextjs/getServerSideProps'; +export { login as getServerSideProps } from 'nextjs/getServerSideProps'; diff --git a/pages/name-domains/[name].tsx b/pages/name-domains/[name].tsx new file mode 100644 index 0000000000..7a01829baa --- /dev/null +++ b/pages/name-domains/[name].tsx @@ -0,0 +1,20 @@ +import type { NextPage } from 'next'; +import dynamic from 'next/dynamic'; +import React from 'react'; + +import type { Props } from 'nextjs/getServerSideProps'; +import PageNextJs from 'nextjs/PageNextJs'; + +const NameDomain = dynamic(() => import('ui/pages/NameDomain'), { ssr: false }); + +const Page: NextPage = (props: Props) => { + return ( + + + + ); +}; + +export default Page; + +export { nameService as getServerSideProps } from 'nextjs/getServerSideProps'; diff --git a/pages/withdrawals.tsx b/pages/name-domains/index.tsx similarity index 58% rename from pages/withdrawals.tsx rename to pages/name-domains/index.tsx index f8df33177f..f00e06e9ba 100644 --- a/pages/withdrawals.tsx +++ b/pages/name-domains/index.tsx @@ -4,16 +4,16 @@ import React from 'react'; import PageNextJs from 'nextjs/PageNextJs'; -const Withdrawals = dynamic(() => import('ui/pages/Withdrawals'), { ssr: false }); +const NameDomains = dynamic(() => import('ui/pages/NameDomains'), { ssr: false }); const Page: NextPage = () => { return ( - - + + ); }; export default Page; -export { beaconChain as getServerSideProps } from 'nextjs/getServerSideProps'; +export { nameService as getServerSideProps } from 'nextjs/getServerSideProps'; diff --git a/pages/zkevm-l2-txn-batch/[number].tsx b/pages/op/[hash].tsx similarity index 56% rename from pages/zkevm-l2-txn-batch/[number].tsx rename to pages/op/[hash].tsx index b128b8d070..63080f817c 100644 --- a/pages/zkevm-l2-txn-batch/[number].tsx +++ b/pages/op/[hash].tsx @@ -5,16 +5,16 @@ import React from 'react'; import type { Props } from 'nextjs/getServerSideProps'; import PageNextJs from 'nextjs/PageNextJs'; -const ZkEvmL2TxnBatch = dynamic(() => import('ui/pages/ZkEvmL2TxnBatch'), { ssr: false }); +const UserOp = dynamic(() => import('ui/pages/UserOp'), { ssr: false }); const Page: NextPage = (props: Props) => { return ( - - + + ); }; export default Page; -export { zkEvmL2 as getServerSideProps } from 'nextjs/getServerSideProps'; +export { userOps as getServerSideProps } from 'nextjs/getServerSideProps'; diff --git a/pages/l2-withdrawals.tsx b/pages/ops.tsx similarity index 51% rename from pages/l2-withdrawals.tsx rename to pages/ops.tsx index 0814bf4a10..6d70165262 100644 --- a/pages/l2-withdrawals.tsx +++ b/pages/ops.tsx @@ -4,16 +4,16 @@ import React from 'react'; import PageNextJs from 'nextjs/PageNextJs'; -const L2Withdrawals = dynamic(() => import('ui/pages/L2Withdrawals'), { ssr: false }); +const UserOps = dynamic(() => import('ui/pages/UserOps'), { ssr: false }); const Page: NextPage = () => { return ( - - + + ); }; export default Page; -export { L2 as getServerSideProps } from 'nextjs/getServerSideProps'; +export { userOps as getServerSideProps } from 'nextjs/getServerSideProps'; diff --git a/pages/output-roots/index.tsx b/pages/output-roots/index.tsx new file mode 100644 index 0000000000..4ce87bec5a --- /dev/null +++ b/pages/output-roots/index.tsx @@ -0,0 +1,19 @@ +import type { NextPage } from 'next'; +import dynamic from 'next/dynamic'; +import React from 'react'; + +import PageNextJs from 'nextjs/PageNextJs'; + +const OutputRoots = dynamic(() => import('ui/pages/OptimisticL2OutputRoots'), { ssr: false }); + +const Page: NextPage = () => { + return ( + + + + ); +}; + +export default Page; + +export { optimisticRollup as getServerSideProps } from 'nextjs/getServerSideProps'; diff --git a/pages/l2-deposits.tsx b/pages/validators.tsx similarity index 55% rename from pages/l2-deposits.tsx rename to pages/validators.tsx index 0c445b4706..42409f3fd3 100644 --- a/pages/l2-deposits.tsx +++ b/pages/validators.tsx @@ -4,16 +4,16 @@ import React from 'react'; import PageNextJs from 'nextjs/PageNextJs'; -const L2Deposits = dynamic(() => import('ui/pages/L2Deposits'), { ssr: false }); +const Validators = dynamic(() => import('ui/pages/Validators'), { ssr: false }); const Page: NextPage = () => { return ( - - + + ); }; export default Page; -export { L2 as getServerSideProps } from 'nextjs/getServerSideProps'; +export { validators as getServerSideProps } from 'nextjs/getServerSideProps'; diff --git a/pages/withdrawals/index.tsx b/pages/withdrawals/index.tsx new file mode 100644 index 0000000000..661ada21eb --- /dev/null +++ b/pages/withdrawals/index.tsx @@ -0,0 +1,37 @@ +import type { NextPage } from 'next'; +import dynamic from 'next/dynamic'; +import React from 'react'; + +import PageNextJs from 'nextjs/PageNextJs'; + +import config from 'configs/app'; +const rollupFeature = config.features.rollup; +const beaconChainFeature = config.features.beaconChain; + +const Withdrawals = dynamic(() => { + if (rollupFeature.isEnabled && rollupFeature.type === 'optimistic') { + return import('ui/pages/OptimisticL2Withdrawals'); + } + + if (rollupFeature.isEnabled && rollupFeature.type === 'shibarium') { + return import('ui/pages/ShibariumWithdrawals'); + } + + if (beaconChainFeature.isEnabled) { + return import('ui/pages/BeaconChainWithdrawals'); + } + + throw new Error('Withdrawals feature is not enabled.'); +}, { ssr: false }); + +const Page: NextPage = () => { + return ( + + + + ); +}; + +export default Page; + +export { withdrawals as getServerSideProps } from 'nextjs/getServerSideProps'; diff --git a/pages/zkevm-l2-txn-batches.tsx b/pages/zkevm-l2-txn-batches.tsx deleted file mode 100644 index 36b8139216..0000000000 --- a/pages/zkevm-l2-txn-batches.tsx +++ /dev/null @@ -1,19 +0,0 @@ -import type { NextPage } from 'next'; -import dynamic from 'next/dynamic'; -import React from 'react'; - -import PageNextJs from 'nextjs/PageNextJs'; - -const ZkEvmL2TxnBatches = dynamic(() => import('ui/pages/ZkEvmL2TxnBatches'), { ssr: false }); - -const Page: NextPage = () => { - return ( - - - - ); -}; - -export default Page; - -export { zkEvmL2 as getServerSideProps } from 'nextjs/getServerSideProps'; diff --git a/playwright-ct.config.ts b/playwright-ct.config.ts index eccae16aa5..d6c7c6c231 100644 --- a/playwright-ct.config.ts +++ b/playwright-ct.config.ts @@ -4,6 +4,8 @@ import react from '@vitejs/plugin-react'; import svgr from 'vite-plugin-svgr'; import tsconfigPaths from 'vite-tsconfig-paths'; +import appConfig from 'configs/app'; + /** * See https://playwright.dev/docs/test-configuration. */ @@ -26,14 +28,17 @@ const config: PlaywrightTestConfig = defineConfig({ /* Retry on CI only */ retries: process.env.CI ? 2 : 0, - /* Opt out of parallel tests on CI. */ - workers: process.env.CI ? 1 : undefined, + /* Opt out of parallel tests. */ + // on non-performant local machines some tests may fail due to lack of resources + // so we opt out of parallel tests in any environment + workers: 1, /* Reporter to use. See https://playwright.dev/docs/test-reporters */ reporter: 'html', /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */ use: { + baseURL: appConfig.app.baseUrl, /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */ trace: 'on-first-retry', @@ -58,14 +63,26 @@ const config: PlaywrightTestConfig = defineConfig({ minify: false, }, resolve: { - alias: { + alias: [ // There is an issue with building these package using vite that I cannot resolve // The solution described here - https://github.com/vitejs/vite/issues/9703#issuecomment-1216662109 // doesn't seam to work well with our setup // so for now we just mock these modules in tests - '@metamask/post-message-stream': './playwright/mocks/modules/@metamask/post-message-stream.js', - '@metamask/providers': './playwright/mocks/modules/@metamask/providers.js', - }, + { find: '@metamask/post-message-stream', replacement: './playwright/mocks/modules/@metamask/post-message-stream.js' }, + { find: '@metamask/providers', replacement: './playwright/mocks/modules/@metamask/providers.js' }, + + // '@metamask/sdk imports the browser module as UMD, but @wagmi/connectors expects it to be ESM + // so we do a little remapping here + { find: '@metamask/sdk', replacement: './node_modules/@metamask/sdk/dist/browser/es/metamask-sdk.js' }, + + // Mock for growthbook to test feature flags + { find: 'lib/growthbook/useFeatureValue', replacement: './playwright/mocks/lib/growthbook/useFeatureValue.js' }, + + // The createWeb3Modal() function from web3modal/wagmi/react somehow pollutes the global styles which causes the tests to fail + // We don't call this function in TestApp and since we use useWeb3Modal() and useWeb3ModalState() hooks in the code, we have to mock the module + // Otherwise it will complain that createWeb3Modal() is no called before the hooks are used + { find: /^@web3modal\/wagmi\/react$/, replacement: './playwright/mocks/modules/@web3modal/wagmi/react.js' }, + ], }, define: { 'process.env': '__envs', // Port process.env over window.__envs diff --git a/playwright/TestApp.tsx b/playwright/TestApp.tsx index 748e52c052..8db3aee3b2 100644 --- a/playwright/TestApp.tsx +++ b/playwright/TestApp.tsx @@ -1,18 +1,18 @@ import { ChakraProvider } from '@chakra-ui/react'; +import { GrowthBookProvider } from '@growthbook/growthbook-react'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; -import { createWeb3Modal, defaultWagmiConfig } from '@web3modal/wagmi/react'; import React from 'react'; -import { WagmiConfig } from 'wagmi'; -import { mainnet } from 'wagmi/chains'; +import { WagmiProvider } from 'wagmi'; import type { Props as PageProps } from 'nextjs/getServerSideProps'; import { AppContextProvider } from 'lib/contexts/app'; import { SocketProvider } from 'lib/socket/context'; +import wagmiConfig from 'lib/web3/wagmiConfig'; import * as app from 'playwright/utils/app'; import theme from 'theme'; -type Props = { +export type Props = { children: React.ReactNode; withSocket?: boolean; appContext?: { @@ -29,25 +29,11 @@ const defaultAppContext = { hash: '', number: '', q: '', + name: '', + adBannerProvider: 'slise', }, }; -// >>> Web3 stuff -const chains = [ mainnet ]; -const WALLET_CONNECT_PROJECT_ID = 'PROJECT_ID'; - -const wagmiConfig = defaultWagmiConfig({ - chains, - projectId: WALLET_CONNECT_PROJECT_ID, -}); - -createWeb3Modal({ - wagmiConfig, - projectId: WALLET_CONNECT_PROJECT_ID, - chains, -}); -// <<<< - const TestApp = ({ children, withSocket, appContext = defaultAppContext }: Props) => { const [ queryClient ] = React.useState(() => new QueryClient({ defaultOptions: { @@ -63,9 +49,11 @@ const TestApp = ({ children, withSocket, appContext = defaultAppContext }: Props - - { children } - + + + { children } + + diff --git a/playwright/fixtures/contextWithEnvs.ts b/playwright/fixtures/contextWithEnvs.ts index 5281646245..e703a922fb 100644 --- a/playwright/fixtures/contextWithEnvs.ts +++ b/playwright/fixtures/contextWithEnvs.ts @@ -1,7 +1,6 @@ import type { test } from '@playwright/experimental-ct-react'; -import type { Browser } from '@playwright/test'; -import * as app from 'playwright/utils/app'; +import createContextWithStorage from './createContextWithStorage'; interface Env { name: string; @@ -11,20 +10,9 @@ interface Env { // keep in mind that all passed variables here should be present in env config files (.env.pw or .env.poa) export default function contextWithEnvsFixture(envs: Array): Parameters[0]['context'] { return async({ browser }, use) => { - const context = await createContextWithEnvs(browser, envs); + const context = await createContextWithStorage(browser, envs); await use(context); await context.close(); }; } - -export async function createContextWithEnvs(browser: Browser, envs: Array) { - return browser.newContext({ - storageState: { - origins: [ - { origin: app.url, localStorage: envs }, - ], - cookies: [], - }, - }); -} diff --git a/playwright/fixtures/contextWithFeatures.ts b/playwright/fixtures/contextWithFeatures.ts new file mode 100644 index 0000000000..a9c7836937 --- /dev/null +++ b/playwright/fixtures/contextWithFeatures.ts @@ -0,0 +1,18 @@ +import type { test } from '@playwright/experimental-ct-react'; + +import createContextWithStorage from './createContextWithStorage'; + +interface Feature { + id: string; + value: unknown; +} + +export default function contextWithFeaturesFixture(envs: Array): Parameters[0]['context'] { + return async({ browser }, use) => { + const storageItems = envs.map(({ id, value }) => ({ name: `pw_feature:${ id }`, value: JSON.stringify(value) })); + const context = await createContextWithStorage(browser, storageItems); + + await use(context); + await context.close(); + }; +} diff --git a/playwright/fixtures/createContextWithStorage.ts b/playwright/fixtures/createContextWithStorage.ts new file mode 100644 index 0000000000..f2555b37c0 --- /dev/null +++ b/playwright/fixtures/createContextWithStorage.ts @@ -0,0 +1,14 @@ +import type { Browser } from '@playwright/test'; + +import * as app from 'playwright/utils/app'; + +export default async function createContextWithEnvs(browser: Browser, localStorage: Array<{ name: string; value: string }>) { + return browser.newContext({ + storageState: { + origins: [ + { origin: app.url, localStorage }, + ], + cookies: [], + }, + }); +} diff --git a/playwright/fixtures/mockApiResponse.ts b/playwright/fixtures/mockApiResponse.ts new file mode 100644 index 0000000000..595b623ae9 --- /dev/null +++ b/playwright/fixtures/mockApiResponse.ts @@ -0,0 +1,26 @@ +import type { TestFixture, Page } from '@playwright/test'; + +import buildUrl from 'lib/api/buildUrl'; +import type { ResourceName, ResourcePayload } from 'lib/api/resources'; + +interface Options { + pathParams?: Parameters>[1]; + queryParams?: Parameters>[2]; +} + +export type MockApiResponseFixture = (resourceName: R, responseMock: ResourcePayload, options?: Options) => Promise; + +const fixture: TestFixture = async({ page }, use) => { + await use(async(resourceName, responseMock, options) => { + const apiUrl = buildUrl(resourceName, options?.pathParams, options?.queryParams); + + await page.route(apiUrl, (route) => route.fulfill({ + status: 200, + body: JSON.stringify(responseMock), + })); + + return apiUrl; + }); +}; + +export default fixture; diff --git a/playwright/fixtures/mockAssetResponse.ts b/playwright/fixtures/mockAssetResponse.ts new file mode 100644 index 0000000000..8da0b75fea --- /dev/null +++ b/playwright/fixtures/mockAssetResponse.ts @@ -0,0 +1,15 @@ +import type { TestFixture, Page } from '@playwright/test'; + +export type MockAssetResponseFixture = (url: string, path: string) => Promise; + +const fixture: TestFixture = async({ page }, use) => { + await use(async(url, path) => { + + await page.route(url, (route) => route.fulfill({ + status: 200, + path, + })); + }); +}; + +export default fixture; diff --git a/playwright/fixtures/render.tsx b/playwright/fixtures/render.tsx new file mode 100644 index 0000000000..88f7fe669f --- /dev/null +++ b/playwright/fixtures/render.tsx @@ -0,0 +1,35 @@ +import type { MountOptions } from '@playwright/experimental-ct-react'; +import type { Locator, TestFixture } from '@playwright/test'; +import type router from 'next/router'; +import React from 'react'; + +import type { JsonObject } from '@playwright/experimental-ct-core/types/component'; + +import type { Props as TestAppProps } from 'playwright/TestApp'; +import TestApp from 'playwright/TestApp'; + +interface MountResult extends Locator { + unmount(): Promise; + update(component: JSX.Element): Promise; +} + +type Mount = (component: JSX.Element, options?: MountOptions) => Promise; + +interface Options extends JsonObject { + hooksConfig?: { + router: Partial>; + }; +} + +export type RenderFixture = (component: JSX.Element, options?: Options, props?: Omit) => Promise + +const fixture: TestFixture = async({ mount }, use) => { + await use((component, options, props) => { + return mount( + { component }, + options, + ); + }); +}; + +export default fixture; diff --git a/playwright/fixtures/socketServer.ts b/playwright/fixtures/socketServer.ts index 43c3726371..f630cdd012 100644 --- a/playwright/fixtures/socketServer.ts +++ b/playwright/fixtures/socketServer.ts @@ -10,16 +10,16 @@ import type { Transaction } from 'types/api/transaction'; import * as app from 'playwright/utils/app'; -type ReturnType = () => Promise; +export type CreateSocketFixture = () => Promise; type Channel = [string, string, string]; export interface SocketServerFixture { - createSocket: ReturnType; + createSocket: CreateSocketFixture; } // eslint-disable-next-line @typescript-eslint/no-unused-vars -export const createSocket: TestFixture = async({ page }, use) => { +export const createSocket: TestFixture = async({ page }, use) => { const socketServer = new WebSocketServer({ port: app.socketPort }); const connectionPromise = new Promise((resolve) => { @@ -62,6 +62,7 @@ export function sendMessage(socket: WebSocket, channel: Channel, msg: 'token_bal export function sendMessage(socket: WebSocket, channel: Channel, msg: 'updated_token_balances_erc_20', payload: AddressTokensBalancesSocketMessage): void; export function sendMessage(socket: WebSocket, channel: Channel, msg: 'updated_token_balances_erc_721', payload: AddressTokensBalancesSocketMessage): void; export function sendMessage(socket: WebSocket, channel: Channel, msg: 'updated_token_balances_erc_1155', payload: AddressTokensBalancesSocketMessage): void; +export function sendMessage(socket: WebSocket, channel: Channel, msg: 'updated_token_balances_erc_404', payload: AddressTokensBalancesSocketMessage): void; export function sendMessage(socket: WebSocket, channel: Channel, msg: 'transaction', payload: { transaction: number }): void; export function sendMessage(socket: WebSocket, channel: Channel, msg: 'transaction', payload: { transactions: Array }): void; export function sendMessage(socket: WebSocket, channel: Channel, msg: 'pending_transaction', payload: { pending_transaction: number }): void; diff --git a/playwright/index.ts b/playwright/index.ts index 879cfa0127..e0e5b4be99 100644 --- a/playwright/index.ts +++ b/playwright/index.ts @@ -9,6 +9,7 @@ const NEXT_ROUTER_MOCK = { query: {}, pathname: '', push: () => Promise.resolve(), + replace: () => Promise.resolve(), }; beforeMount(async({ hooksConfig }) => { diff --git a/playwright/lib.tsx b/playwright/lib.tsx new file mode 100644 index 0000000000..215f71d17e --- /dev/null +++ b/playwright/lib.tsx @@ -0,0 +1,64 @@ +/* eslint-disable no-console */ +import { test as base } from '@playwright/experimental-ct-react'; + +import * as textAdMock from 'mocks/ad/textAd'; + +import type { MockApiResponseFixture } from './fixtures/mockApiResponse'; +import mockApiResponseFixture from './fixtures/mockApiResponse'; +import type { MockAssetResponseFixture } from './fixtures/mockAssetResponse'; +import mockAssetResponseFixture from './fixtures/mockAssetResponse'; +import type { RenderFixture } from './fixtures/render'; +import renderFixture from './fixtures/render'; +import type { CreateSocketFixture } from './fixtures/socketServer'; +import { createSocket as createSocketFixture } from './fixtures/socketServer'; + +interface Fixtures { + render: RenderFixture; + mockApiResponse: MockApiResponseFixture; + mockAssetResponse: MockAssetResponseFixture; + createSocket: CreateSocketFixture; +} + +const test = base.extend({ + render: renderFixture, + mockApiResponse: mockApiResponseFixture, + mockAssetResponse: mockAssetResponseFixture, + createSocket: createSocketFixture, +}); + +test.beforeEach(async({ page }) => { + // debug + const isDebug = process.env.PWDEBUG === '1'; + + if (isDebug) { + page.on('console', msg => console.log(msg.text())); + page.on('request', request => console.info('\x1b[34m%s\x1b[0m', '>>', request.method(), request.url())); + page.on('response', response => console.info('\x1b[35m%s\x1b[0m', '<<', String(response.status()), response.url())); + } + + // Abort all other requests to external resources + await page.route('**', (route) => { + if (!route.request().url().startsWith('http://localhost')) { + isDebug && console.info('Aborting request to', route.request().url()); + route.abort(); + } else { + route.continue(); + } + }); + + // with few exceptions: + // 1. mock text AD requests + await page.route('https://request-global.czilladx.com/serve/native.php?z=19260bf627546ab7242', (route) => route.fulfill({ + status: 200, + body: JSON.stringify(textAdMock.duck), + })); + await page.route(textAdMock.duck.ad.thumbnail, (route) => { + return route.fulfill({ + status: 200, + path: './playwright/mocks/image_s.jpg', + }); + }); +}); + +export * from '@playwright/experimental-ct-react'; +export { test }; diff --git a/playwright/mocks/lib/growthbook/useFeatureValue.js b/playwright/mocks/lib/growthbook/useFeatureValue.js new file mode 100644 index 0000000000..e3c810f70b --- /dev/null +++ b/playwright/mocks/lib/growthbook/useFeatureValue.js @@ -0,0 +1,10 @@ +const useFeatureValue = (name, fallback) => { + try { + const value = JSON.parse(localStorage.getItem(`pw_feature:${ name }`)); + return { isLoading: false, value }; + } catch (error) { + return { isLoading: false, value: fallback }; + } +}; + +export default useFeatureValue; diff --git a/playwright/mocks/modules/@web3modal/wagmi/react.js b/playwright/mocks/modules/@web3modal/wagmi/react.js new file mode 100644 index 0000000000..7765272c71 --- /dev/null +++ b/playwright/mocks/modules/@web3modal/wagmi/react.js @@ -0,0 +1,26 @@ +function useWeb3Modal() { + return { + open: () => {}, + }; +} + +function useWeb3ModalState() { + return { + isOpen: false, + }; +} + +function useWeb3ModalTheme() { + return { + setThemeMode: () => {}, + }; +} + +function createWeb3Modal() {} + +export { + createWeb3Modal, + useWeb3Modal, + useWeb3ModalState, + useWeb3ModalTheme, +}; diff --git a/playwright/utils/buildApiUrl.ts b/playwright/utils/buildApiUrl.ts index 4279efb71d..f8ce6bc063 100644 --- a/playwright/utils/buildApiUrl.ts +++ b/playwright/utils/buildApiUrl.ts @@ -1,11 +1,22 @@ import { compile } from 'path-to-regexp'; +import config from 'configs/app'; import type { ResourceName, ResourcePathParams } from 'lib/api/resources'; import { RESOURCES } from 'lib/api/resources'; +// DEPRECATED + +/** + * @deprecated please use fixture mockApiResponse from playwright/lib.tsx for rendering test suite + * + * @export + * @template R + * @param {R} resourceName + * @param {ResourcePathParams} [pathParams] + * @return {*} string + */ export default function buildApiUrl(resourceName: R, pathParams?: ResourcePathParams) { const resource = RESOURCES[resourceName]; - const defaultApi = 'https://' + process.env.NEXT_PUBLIC_API_HOST + ':' + process.env.NEXT_PUBLIC_API_PORT; - const origin = 'endpoint' in resource && resource.endpoint ? resource.endpoint + (resource.basePath ?? '') : defaultApi; + const origin = 'endpoint' in resource && resource.endpoint ? resource.endpoint + (resource.basePath ?? '') : config.api.endpoint; return origin + compile(resource.path)(pathParams); } diff --git a/playwright/utils/configs.ts b/playwright/utils/configs.ts index 4d9bc8802f..d0a7ac6da9 100644 --- a/playwright/utils/configs.ts +++ b/playwright/utils/configs.ts @@ -3,6 +3,7 @@ import { devices } from '@playwright/test'; export const viewport = { mobile: devices['iPhone 13 Pro'].viewport, + md: { width: 1001, height: 800 }, xl: { width: 1600, height: 1000 }, }; @@ -15,9 +16,13 @@ export const featureEnvs = { { name: 'NEXT_PUBLIC_HAS_BEACON_CHAIN', value: 'true' }, ], optimisticRollup: [ - { name: 'NEXT_PUBLIC_IS_OPTIMISTIC_L2_NETWORK', value: 'true' }, - { name: 'NEXT_PUBLIC_L1_BASE_URL', value: 'https://localhost:3101' }, - { name: 'NEXT_PUBLIC_OPTIMISTIC_L2_WITHDRAWAL_URL', value: 'https://localhost:3102' }, + { name: 'NEXT_PUBLIC_ROLLUP_TYPE', value: 'optimistic' }, + { name: 'NEXT_PUBLIC_ROLLUP_L1_BASE_URL', value: 'https://localhost:3101' }, + { name: 'NEXT_PUBLIC_ROLLUP_L2_WITHDRAWAL_URL', value: 'https://localhost:3102' }, + ], + shibariumRollup: [ + { name: 'NEXT_PUBLIC_ROLLUP_TYPE', value: 'shibarium' }, + { name: 'NEXT_PUBLIC_ROLLUP_L1_BASE_URL', value: 'https://localhost:3101' }, ], bridgedTokens: [ { @@ -32,9 +37,19 @@ export const featureEnvs = { txInterpretation: [ { name: 'NEXT_PUBLIC_TRANSACTION_INTERPRETATION_PROVIDER', value: 'blockscout' }, ], - zkRollup: [ - { name: 'NEXT_PUBLIC_IS_ZKEVM_L2_NETWORK', value: 'true' }, - { name: 'NEXT_PUBLIC_L1_BASE_URL', value: 'https://localhost:3101' }, + zkEvmRollup: [ + { name: 'NEXT_PUBLIC_ROLLUP_TYPE', value: 'zkEvm' }, + { name: 'NEXT_PUBLIC_ROLLUP_L1_BASE_URL', value: 'https://localhost:3101' }, + ], + zkSyncRollup: [ + { name: 'NEXT_PUBLIC_ROLLUP_TYPE', value: 'zkSync' }, + { name: 'NEXT_PUBLIC_ROLLUP_L1_BASE_URL', value: 'https://localhost:3101' }, + ], + userOps: [ + { name: 'NEXT_PUBLIC_HAS_USER_OPS', value: 'true' }, + ], + validators: [ + { name: 'NEXT_PUBLIC_VALIDATORS_CHAIN_TYPE', value: 'stability' }, ], }; @@ -46,6 +61,12 @@ export const viewsEnvs = { }, }; +export const UIEnvs = { + hasContractAuditReports: [ + { name: 'NEXT_PUBLIC_HAS_CONTRACT_AUDIT_REPORTS', value: 'true' }, + ], +}; + export const stabilityEnvs = [ { name: 'NEXT_PUBLIC_VIEWS_ADDRESS_HIDDEN_VIEWS', value: '["top_accounts"]' }, { name: 'NEXT_PUBLIC_VIEWS_TX_HIDDEN_FIELDS', value: '["value","fee_currency","gas_price","gas_fees","burnt_fees"]' }, diff --git a/public/icons/name.d.ts b/public/icons/name.d.ts index 31e3f50b9d..6f6bc7c55b 100644 --- a/public/icons/name.d.ts +++ b/public/icons/name.d.ts @@ -1,8 +1,11 @@ // This file is generated by npm run build:icons export type IconName = - | "ABI" + | "ABI_slim" + | "ABI" | "API" + | "apps_list" + | "apps_xs" | "apps" | "arrows/down-right" | "arrows/east-mini" @@ -10,9 +13,16 @@ | "arrows/north-east" | "arrows/south-east" | "arrows/up-down" + | "beta_xs" + | "beta" + | "blob" + | "blobs/image" + | "blobs/raw" + | "blobs/text" | "block_slim" | "block" | "brands/safe" + | "brands/solidity_scan" | "burger" | "check" | "clock-light" @@ -21,16 +31,21 @@ | "collection" | "contract_verified" | "contract" + | "contracts_verified" + | "contracts" | "copy" | "cross" | "delete" | "discussions" | "docs" | "donate" + | "dots" | "edit" | "email-sent" | "email" | "empty_search_result" + | "ENS_slim" + | "ENS" | "error-pages/404" | "error-pages/422" | "error-pages/429" @@ -45,12 +60,16 @@ | "filter" | "finalized" | "flame" + | "gas_xl" | "gas" + | "gear_slim" | "gear" | "globe-b" | "globe" | "graphQL" | "info" + | "integration/full" + | "integration/partial" | "key" | "lightning" | "link" @@ -76,6 +95,7 @@ | "qr_code" | "repeat_arrow" | "restAPI" + | "rocket_xl" | "rocket" | "RPC" | "scope" @@ -109,6 +129,7 @@ | "status/success" | "status/warning" | "sun" + | "swap" | "testnet" | "token-placeholder" | "token" @@ -121,10 +142,13 @@ | "txn_batches" | "unfinalized" | "uniswap" + | "up" + | "user_op_slim" + | "user_op" + | "validator" | "verified_token" | "verified" | "verify-contract" - | "vertical_dots" | "wallet" | "wallets/coinbase" | "wallets/metamask" diff --git a/public/static/contract_star.png b/public/static/contract_star.png new file mode 100644 index 0000000000..32d635ef60 Binary files /dev/null and b/public/static/contract_star.png differ diff --git a/public/static/identicon_logos/blockies.png b/public/static/identicon_logos/blockies.png new file mode 100644 index 0000000000..d9ccead353 Binary files /dev/null and b/public/static/identicon_logos/blockies.png differ diff --git a/public/static/identicon_logos/github.png b/public/static/identicon_logos/github.png new file mode 100644 index 0000000000..346f9d212e Binary files /dev/null and b/public/static/identicon_logos/github.png differ diff --git a/public/static/identicon_logos/gradient_avatar.png b/public/static/identicon_logos/gradient_avatar.png new file mode 100644 index 0000000000..396ee27848 Binary files /dev/null and b/public/static/identicon_logos/gradient_avatar.png differ diff --git a/public/static/identicon_logos/jazzicon.png b/public/static/identicon_logos/jazzicon.png new file mode 100644 index 0000000000..c06bc880ca Binary files /dev/null and b/public/static/identicon_logos/jazzicon.png differ diff --git a/stubs/ENS.ts b/stubs/ENS.ts new file mode 100644 index 0000000000..4fbd654adf --- /dev/null +++ b/stubs/ENS.ts @@ -0,0 +1,32 @@ +import type { EnsDomainDetailed, EnsDomainEvent } from 'types/api/ens'; + +import { ADDRESS_PARAMS, ADDRESS_HASH } from './addressParams'; +import { TX_HASH } from './tx'; + +export const ENS_DOMAIN: EnsDomainDetailed = { + id: '0x126d74db13895f8d3a1d362410212731d1e1d9be8add83e388385f93d84c8c84', + name: 'kitty.cat.eth', + tokens: [ + { + id: '973523146267017920308', + contract_hash: ADDRESS_HASH, + type: 'NATIVE_DOMAIN_TOKEN', + }, + ], + owner: ADDRESS_PARAMS, + wrapped_owner: null, + resolved_address: ADDRESS_PARAMS, + registrant: ADDRESS_PARAMS, + registration_date: '2023-12-20T01:29:12.000Z', + expiry_date: '2099-01-02T01:29:12.000Z', + other_addresses: { + ETH: ADDRESS_HASH, + }, +}; + +export const ENS_DOMAIN_EVENT: EnsDomainEvent = { + transaction_hash: TX_HASH, + timestamp: '2022-06-06T08:43:15.000000Z', + from_address: ADDRESS_PARAMS, + action: '0xf7a16963', +}; diff --git a/stubs/L2.ts b/stubs/L2.ts index b10eb4db0e..21346e0ef5 100644 --- a/stubs/L2.ts +++ b/stubs/L2.ts @@ -1,12 +1,14 @@ -import type { L2DepositsItem } from 'types/api/l2Deposits'; -import type { L2OutputRootsItem } from 'types/api/l2OutputRoots'; -import type { L2TxnBatchesItem } from 'types/api/l2TxnBatches'; -import type { L2WithdrawalsItem } from 'types/api/l2Withdrawals'; +import type { + OptimisticL2DepositsItem, + OptimisticL2OutputRootsItem, + OptimisticL2TxnBatchesItem, + OptimisticL2WithdrawalsItem, +} from 'types/api/optimisticL2'; import { ADDRESS_HASH, ADDRESS_PARAMS } from './addressParams'; import { TX_HASH } from './tx'; -export const L2_DEPOSIT_ITEM: L2DepositsItem = { +export const L2_DEPOSIT_ITEM: OptimisticL2DepositsItem = { l1_block_number: 9045233, l1_block_timestamp: '2023-05-22T18:00:36.000000Z', l1_tx_hash: TX_HASH, @@ -15,7 +17,7 @@ export const L2_DEPOSIT_ITEM: L2DepositsItem = { l2_tx_hash: TX_HASH, }; -export const L2_WITHDRAWAL_ITEM: L2WithdrawalsItem = { +export const L2_WITHDRAWAL_ITEM: OptimisticL2WithdrawalsItem = { challenge_period_end: null, from: ADDRESS_PARAMS, l1_tx_hash: TX_HASH, @@ -26,8 +28,7 @@ export const L2_WITHDRAWAL_ITEM: L2WithdrawalsItem = { status: 'Ready to prove', }; -export const L2_TXN_BATCHES_ITEM: L2TxnBatchesItem = { - epoch_number: 9103513, +export const L2_TXN_BATCHES_ITEM: OptimisticL2TxnBatchesItem = { l1_timestamp: '2023-06-01T14:46:48.000000Z', l1_tx_hashes: [ TX_HASH, @@ -36,7 +37,7 @@ export const L2_TXN_BATCHES_ITEM: L2TxnBatchesItem = { tx_count: 9, }; -export const L2_OUTPUT_ROOTS_ITEM: L2OutputRootsItem = { +export const L2_OUTPUT_ROOTS_ITEM: OptimisticL2OutputRootsItem = { l1_block_number: 9103684, l1_timestamp: '2023-06-01T15:26:12.000000Z', l1_tx_hash: TX_HASH, diff --git a/stubs/RPC.ts b/stubs/RPC.ts new file mode 100644 index 0000000000..0da0947c7c --- /dev/null +++ b/stubs/RPC.ts @@ -0,0 +1,94 @@ +import type { Chain, GetBlockReturnType, GetTransactionReturnType, TransactionReceipt, Withdrawal } from 'viem'; + +import { ADDRESS_HASH } from './addressParams'; +import { BLOCK_HASH } from './block'; +import { TX_HASH } from './tx'; + +export const WITHDRAWAL: Withdrawal = { + index: '0x1af95d9', + validatorIndex: '0x7d748', + address: '0x9b52b9033ecbb6635f1c31a646d5691b282878aa', + amount: '0x29e16b', +}; + +export const GET_TRANSACTION: GetTransactionReturnType = { + blockHash: BLOCK_HASH, + blockNumber: BigInt(10361367), + from: ADDRESS_HASH, + gas: BigInt(800000), + maxPriorityFeePerGas: BigInt(2), + maxFeePerGas: BigInt(14), + hash: TX_HASH, + input: '0x7898e0', + nonce: 117694, + to: ADDRESS_HASH, + transactionIndex: 60, + value: BigInt(42), + type: 'eip1559', + accessList: [], + chainId: 5, + v: BigInt(0), + r: '0x2c5022ff7f78a22f1a99afbd568f75cb52812189ed8c264c8310e0b8dba2c8a8', + s: '0x50938f87c92b9eeb9777507ca8f7397840232d00d1dbac3edac6c115b4656763', + yParity: 1, + typeHex: '0x2', +}; + +export const GET_TRANSACTION_RECEIPT: TransactionReceipt = { + blockHash: BLOCK_HASH, + blockNumber: BigInt(10361367), + contractAddress: null, + cumulativeGasUsed: BigInt(39109), + effectiveGasPrice: BigInt(13), + from: ADDRESS_HASH, + gasUsed: BigInt(39109), + logs: [], + logsBloom: '0x0', + status: 'success', + to: ADDRESS_HASH, + transactionHash: TX_HASH, + transactionIndex: 60, + type: '0x2', +}; + +export const GET_TRANSACTION_CONFIRMATIONS = BigInt(420); + +export const GET_BALANCE = BigInt(42_000_000_000_000); + +export const GET_TRANSACTIONS_COUNT = 42; + +export const GET_BLOCK: GetBlockReturnType = { + baseFeePerGas: BigInt(11), + difficulty: BigInt(111), + extraData: '0xd8830', + gasLimit: BigInt(800000), + gasUsed: BigInt(42000), + hash: BLOCK_HASH, + logsBloom: '0x008000', + miner: ADDRESS_HASH, + mixHash: BLOCK_HASH, + nonce: '0x0000000000000000', + number: BigInt(10361367), + parentHash: BLOCK_HASH, + receiptsRoot: BLOCK_HASH, + sha3Uncles: BLOCK_HASH, + size: BigInt(88), + stateRoot: BLOCK_HASH, + timestamp: BigInt(1628580000), + totalDifficulty: BigInt(10361367), + transactions: [ + TX_HASH, + ], + transactionsRoot: TX_HASH, + uncles: [], + withdrawals: Array(10).fill(WITHDRAWAL), + withdrawalsRoot: TX_HASH, + sealFields: [ '0x00' ], + blobGasUsed: BigInt(0), + excessBlobGas: BigInt(0), +}; + +export const GET_BLOCK_WITH_TRANSACTIONS: GetBlockReturnType = { + ...GET_BLOCK, + transactions: Array(50).fill(GET_TRANSACTION), +}; diff --git a/stubs/address.ts b/stubs/address.ts index c2e0ffd7c6..983565ca24 100644 --- a/stubs/address.ts +++ b/stubs/address.ts @@ -10,7 +10,7 @@ import type { import type { AddressesItem } from 'types/api/addresses'; import { ADDRESS_HASH } from './addressParams'; -import { TOKEN_INFO_ERC_1155, TOKEN_INFO_ERC_20, TOKEN_INFO_ERC_721, TOKEN_INSTANCE } from './token'; +import { TOKEN_INFO_ERC_1155, TOKEN_INFO_ERC_20, TOKEN_INFO_ERC_721, TOKEN_INFO_ERC_404, TOKEN_INSTANCE } from './token'; import { TX_HASH } from './tx'; export const ADDRESS_INFO: Address = { @@ -41,6 +41,7 @@ export const ADDRESS_INFO: Address = { public_tags: [], watchlist_names: [], watchlist_address_id: null, + ens_domain_name: null, }; export const ADDRESS_COUNTERS: AddressCounters = { @@ -71,6 +72,7 @@ export const TOP_ADDRESS: AddressesItem = { private_tags: [], public_tags: [ ], watchlist_names: [], + ens_domain_name: null, }; export const ADDRESS_COIN_BALANCE: AddressCoinBalanceHistoryItem = { @@ -102,6 +104,13 @@ export const ADDRESS_NFT_1155: AddressNFT = { ...TOKEN_INSTANCE, }; +export const ADDRESS_NFT_404: AddressNFT = { + token_type: 'ERC-404', + token: TOKEN_INFO_ERC_404, + value: '10', + ...TOKEN_INSTANCE, +}; + export const ADDRESS_COLLECTION: AddressCollection = { token: TOKEN_INFO_ERC_1155, amount: '4', diff --git a/stubs/addressParams.ts b/stubs/addressParams.ts index a5f7374bde..819a71402b 100644 --- a/stubs/addressParams.ts +++ b/stubs/addressParams.ts @@ -11,4 +11,5 @@ export const ADDRESS_PARAMS: AddressParam = { private_tags: [], public_tags: [], watchlist_names: [], + ens_domain_name: null, }; diff --git a/stubs/blobs.ts b/stubs/blobs.ts new file mode 100644 index 0000000000..89ca5c4cb4 --- /dev/null +++ b/stubs/blobs.ts @@ -0,0 +1,20 @@ +import type { Blob, TxBlob } from 'types/api/blobs'; + +import { TX_HASH } from './tx'; + +const BLOB_HASH = '0x0137cd898a9aaa92bbe94999d2a98241f5eabc829d9354160061789963f85995'; +const BLOB_PROOF = '0x82683d5d6e58a76f2a607b8712cad113621d46cb86a6bcfcffb1e274a70c7308b3243c6075ee22d904fecf8d4c147c6f'; + +export const TX_BLOB: TxBlob = { + blob_data: '0x010203040506070809101112', + hash: BLOB_HASH, + kzg_commitment: BLOB_PROOF, + kzg_proof: BLOB_PROOF, +}; + +export const BLOB: Blob = { + ...TX_BLOB, + transaction_hashes: [ + { block_consensus: true, transaction_hash: TX_HASH }, + ], +}; diff --git a/stubs/contract.ts b/stubs/contract.ts index 36d7b13ed2..d242609f97 100644 --- a/stubs/contract.ts +++ b/stubs/contract.ts @@ -1,5 +1,5 @@ import type { SmartContract, SolidityscanReport } from 'types/api/contract'; -import type { VerifiedContract } from 'types/api/contracts'; +import type { VerifiedContract, VerifiedContractsCounters } from 'types/api/contracts'; import { ADDRESS_PARAMS } from './addressParams'; @@ -40,6 +40,7 @@ export const CONTRACT_CODE_VERIFIED = { optimization_runs: 200, source_code: 'source_code', verified_at: '2023-02-21T14:39:16.906760Z', + license_type: 'mit', } as unknown as SmartContract; export const VERIFIED_CONTRACT_INFO: VerifiedContract = { @@ -52,10 +53,19 @@ export const VERIFIED_CONTRACT_INFO: VerifiedContract = { optimization_enabled: false, tx_count: 565058, verified_at: '2023-04-10T13:16:33.884921Z', + license_type: 'mit', +}; + +export const VERIFIED_CONTRACTS_COUNTERS: VerifiedContractsCounters = { + smart_contracts: '123456789', + new_smart_contracts_24h: '12345', + verified_smart_contracts: '654321', + new_verified_smart_contracts_24h: '1234', }; export const SOLIDITYSCAN_REPORT: SolidityscanReport = { scan_report: { + contractname: 'BullRunners', scan_status: 'scan_done', scan_summary: { issue_severity_distribution: { diff --git a/stubs/marketplace.ts b/stubs/marketplace.ts index cbb786a74e..0671368937 100644 --- a/stubs/marketplace.ts +++ b/stubs/marketplace.ts @@ -15,3 +15,5 @@ export const MARKETPLACE_APP: MarketplaceAppOverview = { external: true, url: 'https://example.com', }; + +export const CATEGORIES: Array = Array(9).fill('Bridge').map((c, i) => c + i); diff --git a/stubs/noves/NovesTranslate.ts b/stubs/noves/NovesTranslate.ts new file mode 100644 index 0000000000..848ed6dab9 --- /dev/null +++ b/stubs/noves/NovesTranslate.ts @@ -0,0 +1,43 @@ +import type { NovesResponseData, NovesClassificationData, NovesRawTransactionData } from 'types/api/noves'; + +const NOVES_TRANSLATE_CLASSIFIED: NovesClassificationData = { + description: 'Sent 0.04 ETH', + received: [ { + action: 'Sent Token', + actionFormatted: 'Sent Token', + amount: '45', + from: { name: '', address: '0xa0393A76b132526a70450273CafeceB45eea6dEE' }, + to: { name: '', address: '0xa0393A76b132526a70450273CafeceB45eea6dEE' }, + token: { + address: '', + name: 'ETH', + symbol: 'ETH', + decimals: 18, + }, + } ], + sent: [], + source: { + type: '', + }, + type: '0x2', + typeFormatted: 'Send NFT', +}; + +const NOVES_TRANSLATE_RAW: NovesRawTransactionData = { + blockNumber: 1, + fromAddress: '0xCFC123a23dfeD71bDAE054e487989d863C525C73', + gas: 2, + gasPrice: 3, + timestamp: 20000, + toAddress: '0xCFC123a23dfeD71bDAE054e487989d863C525C73', + transactionFee: 2, + transactionHash: '0x128b79937a0eDE33258992c9668455f997f1aF24', +}; + +export const NOVES_TRANSLATE: NovesResponseData = { + accountAddress: '0x2b824349b320cfa72f292ab26bf525adb00083ba9fa097141896c3c8c74567cc', + chain: 'base', + txTypeVersion: 2, + rawTransactionData: NOVES_TRANSLATE_RAW, + classificationData: NOVES_TRANSLATE_CLASSIFIED, +}; diff --git a/stubs/shibarium.ts b/stubs/shibarium.ts new file mode 100644 index 0000000000..3003be7110 --- /dev/null +++ b/stubs/shibarium.ts @@ -0,0 +1,20 @@ +import type { ShibariumDepositsItem, ShibariumWithdrawalsItem } from 'types/api/shibarium'; + +import { ADDRESS_PARAMS } from './addressParams'; +import { TX_HASH } from './tx'; + +export const SHIBARIUM_DEPOSIT_ITEM: ShibariumDepositsItem = { + l1_block_number: 9045233, + l1_transaction_hash: TX_HASH, + l2_transaction_hash: TX_HASH, + timestamp: '2023-05-22T18:00:36.000000Z', + user: ADDRESS_PARAMS, +}; + +export const SHIBARIUM_WITHDRAWAL_ITEM: ShibariumWithdrawalsItem = { + l2_block_number: 9045233, + l1_transaction_hash: TX_HASH, + l2_transaction_hash: TX_HASH, + timestamp: '2023-05-22T18:00:36.000000Z', + user: ADDRESS_PARAMS, +}; diff --git a/stubs/stats.ts b/stubs/stats.ts index bbe8623ffe..d21ba588fd 100644 --- a/stubs/stats.ts +++ b/stubs/stats.ts @@ -3,11 +3,32 @@ import type { Counter, HomeStats, StatsChartsSection } from 'types/api/stats'; export const HOMEPAGE_STATS: HomeStats = { average_block_time: 14346, coin_price: '1807.68', + coin_price_change_percentage: 42, gas_prices: { - average: 0.1, - fast: 0.11, - slow: 0.1, + average: { + fiat_price: '1.01', + price: 20.41, + time: 12283, + base_fee: 2.22222, + priority_fee: 12.424242, + }, + fast: { + fiat_price: '1.26', + price: 25.47, + time: 9321, + base_fee: 4.44444, + priority_fee: 22.242424, + }, + slow: { + fiat_price: '0.97', + price: 19.55, + time: 24543, + base_fee: 1.11111, + priority_fee: 7.8909, + }, }, + gas_price_updated_at: '2022-11-11T11:09:49.051171Z', + gas_prices_update_in: 300000, gas_used_today: '0', market_cap: '0', network_utilization_percentage: 22.56, diff --git a/stubs/token.ts b/stubs/token.ts index e60e004bad..1c90f3d16a 100644 --- a/stubs/token.ts +++ b/stubs/token.ts @@ -1,4 +1,13 @@ -import type { TokenCounters, TokenHolder, TokenInfo, TokenInstance, TokenType } from 'types/api/token'; +import type { + TokenCounters, + TokenHolder, + TokenHolders, + TokenHoldersPagination, + TokenInfo, + TokenInstance, + TokenType, +} from 'types/api/token'; +import type { TokenInstanceTransferPagination, TokenInstanceTransferResponse } from 'types/api/tokens'; import type { TokenTransfer, TokenTransferPagination, TokenTransferResponse } from 'types/api/tokenTransfer'; import { ADDRESS_PARAMS, ADDRESS_HASH } from './addressParams'; @@ -31,6 +40,12 @@ export const TOKEN_INFO_ERC_1155: TokenInfo<'ERC-1155'> = { type: 'ERC-1155', }; +export const TOKEN_INFO_ERC_404: TokenInfo<'ERC-404'> = { + ...TOKEN_INFO_ERC_20, + circulating_market_cap: null, + type: 'ERC-404', +}; + export const TOKEN_COUNTERS: TokenCounters = { token_holders_count: '123456', transfers_count: '123456', @@ -38,17 +53,41 @@ export const TOKEN_COUNTERS: TokenCounters = { export const TOKEN_HOLDER_ERC_20: TokenHolder = { address: ADDRESS_PARAMS, - token: TOKEN_INFO_ERC_20, value: '1021378038331138520', }; export const TOKEN_HOLDER_ERC_1155: TokenHolder = { address: ADDRESS_PARAMS, - token: TOKEN_INFO_ERC_1155, token_id: '12345', value: '1021378038331138520', }; +export const getTokenHoldersStub = (type?: TokenType, pagination: TokenHoldersPagination | null = null): TokenHolders => { + switch (type) { + case 'ERC-721': + return generateListStub<'token_holders'>(TOKEN_HOLDER_ERC_20, 50, { next_page_params: pagination }); + case 'ERC-1155': + return generateListStub<'token_holders'>(TOKEN_HOLDER_ERC_1155, 50, { next_page_params: pagination }); + case 'ERC-404': + return generateListStub<'token_holders'>(TOKEN_HOLDER_ERC_1155, 50, { next_page_params: pagination }); + default: + return generateListStub<'token_holders'>(TOKEN_HOLDER_ERC_20, 50, { next_page_params: pagination }); + } +}; + +export const getTokenInstanceHoldersStub = (type?: TokenType, pagination: TokenHoldersPagination | null = null): TokenHolders => { + switch (type) { + case 'ERC-721': + return generateListStub<'token_instance_holders'>(TOKEN_HOLDER_ERC_20, 10, { next_page_params: pagination }); + case 'ERC-1155': + return generateListStub<'token_instance_holders'>(TOKEN_HOLDER_ERC_1155, 10, { next_page_params: pagination }); + case 'ERC-404': + return generateListStub<'token_instance_holders'>(TOKEN_HOLDER_ERC_1155, 10, { next_page_params: pagination }); + default: + return generateListStub<'token_instance_holders'>(TOKEN_HOLDER_ERC_20, 10, { next_page_params: pagination }); + } +}; + export const TOKEN_TRANSFER_ERC_20: TokenTransfer = { block_hash: BLOCK_HASH, from: ADDRESS_PARAMS, @@ -83,17 +122,42 @@ export const TOKEN_TRANSFER_ERC_1155: TokenTransfer = { token: TOKEN_INFO_ERC_1155, }; +export const TOKEN_TRANSFER_ERC_404: TokenTransfer = { + ...TOKEN_TRANSFER_ERC_20, + total: { + token_id: '35870', + value: '123', + decimals: '18', + }, + token: TOKEN_INFO_ERC_404, +}; + export const getTokenTransfersStub = (type?: TokenType, pagination: TokenTransferPagination | null = null): TokenTransferResponse => { switch (type) { case 'ERC-721': return generateListStub<'token_transfers'>(TOKEN_TRANSFER_ERC_721, 50, { next_page_params: pagination }); case 'ERC-1155': return generateListStub<'token_transfers'>(TOKEN_TRANSFER_ERC_1155, 50, { next_page_params: pagination }); + case 'ERC-404': + return generateListStub<'token_transfers'>(TOKEN_TRANSFER_ERC_404, 50, { next_page_params: pagination }); default: return generateListStub<'token_transfers'>(TOKEN_TRANSFER_ERC_20, 50, { next_page_params: pagination }); } }; +export const getTokenInstanceTransfersStub = (type?: TokenType, pagination: TokenInstanceTransferPagination | null = null): TokenInstanceTransferResponse => { + switch (type) { + case 'ERC-721': + return generateListStub<'token_instance_transfers'>(TOKEN_TRANSFER_ERC_721, 10, { next_page_params: pagination }); + case 'ERC-1155': + return generateListStub<'token_instance_transfers'>(TOKEN_TRANSFER_ERC_1155, 10, { next_page_params: pagination }); + case 'ERC-404': + return generateListStub<'token_instance_transfers'>(TOKEN_TRANSFER_ERC_404, 10, { next_page_params: pagination }); + default: + return generateListStub<'token_instance_transfers'>(TOKEN_TRANSFER_ERC_20, 10, { next_page_params: pagination }); + } +}; + export const TOKEN_INSTANCE: TokenInstance = { animation_url: null, external_app_url: 'https://vipsland.com/nft/collections/genesis/188882', diff --git a/stubs/userOps.ts b/stubs/userOps.ts new file mode 100644 index 0000000000..3e2fcbd1c7 --- /dev/null +++ b/stubs/userOps.ts @@ -0,0 +1,66 @@ +import type { UserOpsItem, UserOp, UserOpsAccount } from 'types/api/userOps'; + +import { ADDRESS_HASH } from './addressParams'; +import { BLOCK_HASH } from './block'; +import { TX_HASH } from './tx'; + +const USER_OP_HASH = '0xb94fab8f31f83001a23e20b2ce3cdcfb284c57a64b9a073e0e09c018bc701978'; + +export const USER_OPS_ITEM: UserOpsItem = { + hash: USER_OP_HASH, + block_number: '10356381', + transaction_hash: TX_HASH, + address: ADDRESS_HASH, + timestamp: '2023-12-18T10:48:49.000000Z', + status: true, + fee: '48285720012071430', +}; + +export const USER_OP: UserOp = { + hash: USER_OP_HASH, + sender: ADDRESS_HASH, + nonce: '0x00b', + call_data: '0x123', + call_gas_limit: '71316', + verification_gas_limit: '91551', + pre_verification_gas: '53627', + max_fee_per_gas: '100000020', + max_priority_fee_per_gas: '100000000', + signature: '0x000', + aggregator: null, + aggregator_signature: null, + entry_point: ADDRESS_HASH, + transaction_hash: TX_HASH, + block_number: '10358181', + block_hash: BLOCK_HASH, + bundler: ADDRESS_HASH, + factory: null, + paymaster: ADDRESS_HASH, + status: true, + revert_reason: null, + gas: '399596', + gas_price: '1575000898', + gas_used: '118810', + sponsor_type: 'paymaster_sponsor', + fee: '17927001792700', + timestamp: '2023-12-18T10:48:49.000000Z', + user_logs_count: 1, + user_logs_start_index: 2, + raw: { + sender: ADDRESS_HASH, + nonce: '1', + init_code: '0x', + call_data: '0x345', + call_gas_limit: '29491', + verification_gas_limit: '80734', + pre_verification_gas: '3276112', + max_fee_per_gas: '309847206', + max_priority_fee_per_gas: '100000000', + paymaster_and_data: '0x', + signature: '0x000', + }, +}; + +export const USER_OPS_ACCOUNT: UserOpsAccount = { + total_ops: 1, +}; diff --git a/stubs/utils.ts b/stubs/utils.ts index 858f7874ca..ef825f9252 100644 --- a/stubs/utils.ts +++ b/stubs/utils.ts @@ -1,9 +1,9 @@ import type { ArrayElement } from 'types/utils'; -import type { PaginatedResources, PaginatedResponse } from 'lib/api/resources'; +import type { PaginatedResources, PaginatedResponse, PaginatedResponseItems } from 'lib/api/resources'; export function generateListStub( - stub: ArrayElement['items']>, + stub: ArrayElement>, num = 50, rest: Omit, 'items'>, ) { diff --git a/stubs/validators.ts b/stubs/validators.ts new file mode 100644 index 0000000000..1717a59ec0 --- /dev/null +++ b/stubs/validators.ts @@ -0,0 +1,16 @@ +import type { Validator, ValidatorsCountersResponse } from 'types/api/validators'; + +import { ADDRESS_PARAMS } from './addressParams'; + +export const VALIDATOR: Validator = { + address: ADDRESS_PARAMS, + blocks_validated_count: 25987, + state: 'active', +}; + +export const VALIDATORS_COUNTERS: ValidatorsCountersResponse = { + active_validators_counter: '42', + active_validators_percentage: 7.14, + new_validators_counter_24h: '11', + validators_counter: '140', +}; diff --git a/stubs/zkEvmL2.ts b/stubs/zkEvmL2.ts index d68f1ac9d3..7dea0f57b8 100644 --- a/stubs/zkEvmL2.ts +++ b/stubs/zkEvmL2.ts @@ -1,4 +1,4 @@ -import type { ZkEvmL2TxnBatch, ZkEvmL2TxnBatchesItem } from 'types/api/zkEvmL2TxnBatches'; +import type { ZkEvmL2TxnBatch, ZkEvmL2TxnBatchesItem } from 'types/api/zkEvmL2'; import { TX_HASH } from './tx'; diff --git a/stubs/zkSyncL2.ts b/stubs/zkSyncL2.ts new file mode 100644 index 0000000000..9d3782abcf --- /dev/null +++ b/stubs/zkSyncL2.ts @@ -0,0 +1,27 @@ +import type { ZkSyncBatch, ZkSyncBatchesItem } from 'types/api/zkSyncL2'; + +import { TX_HASH } from './tx'; + +export const ZKSYNC_L2_TXN_BATCHES_ITEM: ZkSyncBatchesItem = { + commit_transaction_hash: TX_HASH, + commit_transaction_timestamp: '2022-03-17T19:33:04.519145Z', + execute_transaction_hash: TX_HASH, + execute_transaction_timestamp: '2022-03-17T20:49:48.856345Z', + number: 8002, + prove_transaction_hash: TX_HASH, + prove_transaction_timestamp: '2022-03-17T20:49:48.772442Z', + status: 'Executed on L1', + timestamp: '2022-03-17T17:00:11.000000Z', + tx_count: 1215, +}; + +export const ZKSYNC_L2_TXN_BATCH: ZkSyncBatch = { + ...ZKSYNC_L2_TXN_BATCHES_ITEM, + start_block: 1245209, + end_block: 1245490, + l1_gas_price: '4173068062', + l1_tx_count: 0, + l2_fair_gas_price: '100000000', + l2_tx_count: 287, + root_hash: '0x108c635b94f941fcabcb85500daec2f6be4f0747dff649b1cdd9dd7a7a264792', +}; diff --git a/theme/components/Alert/Alert.ts b/theme/components/Alert/Alert.ts index c8dfa125b1..457511c2d7 100644 --- a/theme/components/Alert/Alert.ts +++ b/theme/components/Alert/Alert.ts @@ -56,10 +56,10 @@ const variantSubtle = definePartsStyle((props) => { return { container: { [$fg.variable]: colorScheme === 'gray' ? 'colors.blackAlpha.800' : `colors.${ colorScheme }.500`, - [$bg.variable]: colorScheme === 'gray' ? 'colors.gray.100' : bg.light, + [$bg.variable]: colorScheme === 'gray' ? 'colors.blackAlpha.100' : bg.light, _dark: { [$fg.variable]: colorScheme === 'gray' ? 'colors.whiteAlpha.800' : `colors.${ colorScheme }.200`, - [$bg.variable]: colorScheme === 'gray' ? 'colors.gray.800' : bg.dark, + [$bg.variable]: colorScheme === 'gray' ? 'colors.whiteAlpha.200' : bg.dark, }, }, }; diff --git a/theme/components/FormLabel.ts b/theme/components/FormLabel.ts index 03f86537d6..c95f0e8e7a 100644 --- a/theme/components/FormLabel.ts +++ b/theme/components/FormLabel.ts @@ -1,5 +1,5 @@ import { defineStyle, defineStyleConfig } from '@chakra-ui/styled-system'; -import { getColor, mode } from '@chakra-ui/theme-tools'; +import { getColor } from '@chakra-ui/theme-tools'; import getDefaultFormColors from '../utils/getDefaultFormColors'; @@ -20,7 +20,7 @@ const baseStyle = defineStyle({ const variantFloating = defineStyle((props) => { const { theme, backgroundColor } = props; const { focusPlaceholderColor } = getDefaultFormColors(props); - const bc = backgroundColor || mode('white', 'black')(props); + const bc = backgroundColor || 'transparent'; return { left: '2px', diff --git a/theme/global.ts b/theme/global.ts index 5ddff6bcf7..e50b7eeaf6 100644 --- a/theme/global.ts +++ b/theme/global.ts @@ -2,6 +2,7 @@ import type { StyleFunctionProps } from '@chakra-ui/theme-tools'; import { mode } from '@chakra-ui/theme-tools'; import scrollbar from './foundations/scrollbar'; +import addressEntity from './globals/address-entity'; import getDefaultTransitionProps from './utils/getDefaultTransitionProps'; const global = (props: StyleFunctionProps) => ({ @@ -23,6 +24,7 @@ const global = (props: StyleFunctionProps) => ({ w: '100%', }, ...scrollbar(props), + ...addressEntity(props), }); export default global; diff --git a/theme/globals/address-entity.ts b/theme/globals/address-entity.ts new file mode 100644 index 0000000000..25641c3020 --- /dev/null +++ b/theme/globals/address-entity.ts @@ -0,0 +1,37 @@ +import { mode } from '@chakra-ui/theme-tools'; +import type { StyleFunctionProps } from '@chakra-ui/theme-tools'; + +const styles = (props: StyleFunctionProps) => { + return { + '.address-entity': { + '&.address-entity_highlighted': { + _before: { + content: `" "`, + position: 'absolute', + py: 1, + pl: 1, + pr: 0, + top: '-5px', + left: '-5px', + width: `100%`, + height: '100%', + borderRadius: 'base', + borderColor: mode('blue.200', 'blue.600')(props), + borderWidth: '1px', + borderStyle: 'dashed', + bgColor: mode('blue.50', 'blue.900')(props), + zIndex: -1, + }, + }, + }, + '.address-entity_no-copy': { + '&.address-entity_highlighted': { + _before: { + pr: 2, + }, + }, + }, + }; +}; + +export default styles; diff --git a/theme/utils/getOutlinedFieldStyles.ts b/theme/utils/getOutlinedFieldStyles.ts index cae5060e4e..443249ecd1 100644 --- a/theme/utils/getOutlinedFieldStyles.ts +++ b/theme/utils/getOutlinedFieldStyles.ts @@ -49,6 +49,13 @@ export default function getOutlinedFieldStyles(props: StyleFunctionProps) { }, // not filled input ':placeholder-shown:not(:focus-visible):not(:hover):not([aria-invalid=true])': { borderColor: borderColor || mode('gray.100', 'gray.700')(props) }, + + // not filled input with type="date" + ':not(:placeholder-shown)[value=""]:not(:focus-visible):not(:hover):not([aria-invalid=true])': { + borderColor: borderColor || mode('gray.100', 'gray.700')(props), + color: 'gray.500', + }, + ':-webkit-autofill': { transition: 'background-color 5000s ease-in-out 0s' }, ':-webkit-autofill:hover': { transition: 'background-color 5000s ease-in-out 0s' }, ':-webkit-autofill:focus': { transition: 'background-color 5000s ease-in-out 0s' }, diff --git a/tools/scripts/pw.docker.deps.sh b/tools/scripts/pw.docker.deps.sh new file mode 100755 index 0000000000..719930361f --- /dev/null +++ b/tools/scripts/pw.docker.deps.sh @@ -0,0 +1,3 @@ +#!/bin/bash + +yarn install --modules-folder node_modules_linux diff --git a/tools/scripts/pw.docker.sh b/tools/scripts/pw.docker.sh index 30179e8c4c..49c763f9ad 100755 --- a/tools/scripts/pw.docker.sh +++ b/tools/scripts/pw.docker.sh @@ -1,7 +1,5 @@ #!/bin/bash -yarn install --modules-folder node_modules_linux - export NODE_PATH=$(pwd)/node_modules_linux yarn test:pw "$@" diff --git a/tools/scripts/pw.sh b/tools/scripts/pw.sh index 22aee344a2..0754f0be1e 100755 --- a/tools/scripts/pw.sh +++ b/tools/scripts/pw.sh @@ -10,7 +10,73 @@ dotenv \ yarn svg:build-sprite +# Check if the "--affected" argument is present in the script args +check_affected_flag() { + local affected_flag=false + + for arg in "$@"; do + if [[ "$arg" = "--affected"* ]]; then + # Extract the value after the equals sign + is_affected_value=${is_affected_arg#*=} + + if [ "$is_affected_value" != "false" ]; then + affected_flag=true + fi + + break + fi + done + + echo "$affected_flag" +} + +# Remove the "--affected" argument from the script args +filter_arguments() { + local args=() + + for arg in "$@"; do + if [[ "$arg" != "--affected"* ]]; then + args+=("$arg") + fi + done + + echo "${args[@]}" +} + +get_files_to_run() { + local is_affected=$1 + local files_to_run="" + + if [ "$is_affected" = true ]; then + affected_tests_file="./playwright/affected-tests.txt" + + if [ -f "$affected_tests_file" ]; then + file_content=$(<"$affected_tests_file") + files_to_run="${file_content//$'\n'/$' '}" + + if [ -z "$files_to_run" ]; then + exit 1 + fi + fi + fi + + echo "$files_to_run" +} + +args=$(filter_arguments "$@") +affected_flag=$(check_affected_flag "$@") +files_to_run=$(get_files_to_run "$affected_flag") +if [ $? -eq 1 ]; then + echo "No affected tests found in the file. Exiting..." + exit 0 +fi + +echo "Running Playwright tests with the following arguments: $args" +echo "Affected flag: $affected_flag" +echo "Files to run: $files_to_run" + dotenv \ -v NODE_OPTIONS=\"--max-old-space-size=4096\" \ -e $config_file \ - -- playwright test -c playwright-ct.config.ts "$@" + -- playwright test -c playwright-ct.config.ts $files_to_run $args + diff --git a/types/api/account.ts b/types/api/account.ts index 101b746ed0..0ae2eeb664 100644 --- a/types/api/account.ts +++ b/types/api/account.ts @@ -69,7 +69,7 @@ export type Transactions = Array export interface UserInfo { name?: string; nickname?: string; - email: string; + email: string | null; avatar?: string; } diff --git a/types/api/address.ts b/types/api/address.ts index 7edd55659e..37955b7f28 100644 --- a/types/api/address.ts +++ b/types/api/address.ts @@ -12,6 +12,7 @@ export interface Address extends UserTags { creator_address_hash: string | null; creation_tx_hash: string | null; exchange_rate: string | null; + ens_domain_name: string | null; // TODO: if we are happy with tabs-counters method, should we delete has_something fields? has_beacon_chain_withdrawals?: boolean; has_custom_methods_read: boolean; @@ -38,7 +39,7 @@ export interface Address extends UserTags { export interface AddressCounters { transactions_count: string; token_transfers_count: string; - gas_usage_count: string; + gas_usage_count: string | null; validations_count: string | null; } diff --git a/types/api/addressParams.ts b/types/api/addressParams.ts index b2d8aa1ce2..fe8cb3d90c 100644 --- a/types/api/addressParams.ts +++ b/types/api/addressParams.ts @@ -15,10 +15,13 @@ export interface UserTags { public_tags: Array | null; } -export interface AddressParam extends UserTags { +export type AddressParamBasic = { hash: string; implementation_name: string | null; name: string | null; is_contract: boolean; is_verified: boolean | null; + ens_domain_name: string | null; } + +export type AddressParam = UserTags & AddressParamBasic; diff --git a/types/api/blobs.ts b/types/api/blobs.ts new file mode 100644 index 0000000000..7b8abb55fd --- /dev/null +++ b/types/api/blobs.ts @@ -0,0 +1,18 @@ +export interface TxBlob { + hash: string; + blob_data: string | null; + kzg_commitment: string | null; + kzg_proof: string | null; +} + +export type TxBlobs = { + items: Array; + next_page_params: null; +}; + +export interface Blob extends TxBlob { + transaction_hashes: Array<{ + block_consensus: boolean; + transaction_hash: string; + }>; +} diff --git a/types/api/block.ts b/types/api/block.ts index 9ab826c8c4..2fc14d2745 100644 --- a/types/api/block.ts +++ b/types/api/block.ts @@ -2,6 +2,8 @@ import type { AddressParam } from 'types/api/addressParams'; import type { Reward } from 'types/api/reward'; import type { Transaction } from 'types/api/transaction'; +import type { ZkSyncBatchesItem } from './zkSyncL2'; + export type BlockType = 'block' | 'reorg' | 'uncle'; export interface Block { @@ -13,7 +15,7 @@ export interface Block { hash: string; parent_hash: string; difficulty: string; - total_difficulty: string; + total_difficulty: string | null; gas_used: string | null; gas_limit: string; nonce: string; @@ -36,6 +38,16 @@ export interface Block { bitcoin_merged_mining_merkle_proof?: string | null; hash_for_merged_mining?: string | null; minimum_gas_price?: string | null; + // BLOB FIELDS + blob_gas_price?: string; + blob_gas_used?: string; + burnt_blob_fees?: string; + excess_blob_gas?: string; + blob_tx_count?: number; + // ZKSYNC FIELDS + zksync?: Omit & { + 'batch_number': number | null; + }; } export interface BlocksResponse { @@ -69,7 +81,7 @@ export type BlockWithdrawalsResponse = { next_page_params: { index: number; items_count: number; - }; + } | null; } export type BlockWithdrawalsItem = { diff --git a/types/api/charts.ts b/types/api/charts.ts index 5414f68e60..5d74b38410 100644 --- a/types/api/charts.ts +++ b/types/api/charts.ts @@ -1,12 +1,12 @@ export interface ChartTransactionItem { date: string; - tx_count: number; + tx_count: number | null; } export interface ChartMarketItem { date: string; - closing_price: string; - market_cap?: string; + closing_price: string | null; + market_cap?: string | null; tvl?: string | null; } diff --git a/types/api/contract.ts b/types/api/contract.ts index dd66d0380f..fb85eb9bdf 100644 --- a/types/api/contract.ts +++ b/types/api/contract.ts @@ -3,6 +3,22 @@ import type { Abi, AbiType } from 'abitype'; export type SmartContractMethodArgType = AbiType; export type SmartContractMethodStateMutability = 'view' | 'nonpayable' | 'payable'; +export type SmartContractLicenseType = +'none' | +'unlicense' | +'mit' | +'gnu_gpl_v2' | +'gnu_gpl_v3' | +'gnu_lgpl_v2_1' | +'gnu_lgpl_v3' | +'bsd_2_clause' | +'bsd_3_clause' | +'mpl_2_0' | +'osl_3_0' | +'apache_2_0' | +'gnu_agpl_v3' | +'bsl_1_1'; + export interface SmartContract { deployed_bytecode: string | null; creation_bytecode: string | null; @@ -37,6 +53,7 @@ export interface SmartContract { verified_twin_address_hash: string | null; minimal_proxy_address_hash: string | null; language: string | null; + license_type: SmartContractLicenseType | null; } export type SmartContractDecodedConstructorArg = [ @@ -55,19 +72,18 @@ export interface SmartContractExternalLibrary { export interface SmartContractMethodBase { inputs: Array; - outputs: Array; + outputs?: Array; constant: boolean; name: string; stateMutability: SmartContractMethodStateMutability; type: 'function'; payable: boolean; error?: string; -} - -export interface SmartContractReadMethod extends SmartContractMethodBase { method_id: string; } +export type SmartContractReadMethod = SmartContractMethodBase; + export interface SmartContractWriteFallback { payable?: true; stateMutability: 'payable'; @@ -85,7 +101,7 @@ export type SmartContractWriteMethod = SmartContractMethodBase | SmartContractWr export type SmartContractMethod = SmartContractReadMethod | SmartContractWriteMethod; export interface SmartContractMethodInput { - internalType?: SmartContractMethodArgType; + internalType?: string; // there could be any string, e.g "enum MyEnum" name: string; type: SmartContractMethodArgType; components?: Array; @@ -137,6 +153,7 @@ export interface SmartContractVerificationConfigRaw { vyper_compiler_versions: Array; vyper_evm_versions: Array; is_rust_verifier_microservice_enabled: boolean; + license_types: Record; } export interface SmartContractVerificationConfig extends SmartContractVerificationConfigRaw { @@ -161,6 +178,7 @@ export interface SmartContractVerificationError { export type SolidityscanReport = { scan_report: { + contractname: string; scan_status: string; scan_summary: { issue_severity_distribution: { @@ -180,3 +198,26 @@ export type SolidityscanReport = { scanner_reference_url: string; }; } + +type SmartContractSecurityAudit = { + audit_company_name: string; + audit_publish_date: string; + audit_report_url: string; +} + +export type SmartContractSecurityAudits = { + items: Array; +} + +export type SmartContractSecurityAuditSubmission = { + 'address_hash': string; + 'submitter_name': string; + 'submitter_email': string; + 'is_project_owner': boolean; + 'project_name': string; + 'project_url': string; + 'audit_company_name': string; + 'audit_report_url': string; + 'audit_publish_date': string; + 'comment'?: string; +} diff --git a/types/api/contracts.ts b/types/api/contracts.ts index e075f038f0..65fe537568 100644 --- a/types/api/contracts.ts +++ b/types/api/contracts.ts @@ -1,4 +1,5 @@ import type { AddressParam } from './addressParams'; +import type { SmartContractLicenseType } from './contract'; export interface VerifiedContract { address: AddressParam; @@ -10,6 +11,7 @@ export interface VerifiedContract { tx_count: number | null; verified_at: string; market_cap: string | null; + license_type: SmartContractLicenseType | null; } export interface VerifiedContractsResponse { diff --git a/types/api/ens.ts b/types/api/ens.ts new file mode 100644 index 0000000000..6ee12f846b --- /dev/null +++ b/types/api/ens.ts @@ -0,0 +1,71 @@ +export interface EnsDomain { + id: string; + name: string; + resolved_address: { + hash: string; + } | null; + owner: { + hash: string; + } | null; + wrapped_owner: { + hash: string; + } | null; + registration_date?: string; + expiry_date: string | null; +} + +export interface EnsDomainDetailed extends EnsDomain { + tokens: Array<{ id: string; contract_hash: string; type: 'NATIVE_DOMAIN_TOKEN' | 'WRAPPED_DOMAIN_TOKEN' }>; + registrant: { + hash: string; + } | null; + other_addresses: Record; +} + +export interface EnsDomainEvent { + transaction_hash: string; + timestamp: string; + from_address: { + hash: string; + } | null; + action?: string; +} + +export interface EnsAddressLookupResponse { + items: Array; + next_page_params: { + page_token: string; + page_size: number; + } | null; +} + +export interface EnsDomainEventsResponse { + items: Array; +} + +export interface EnsDomainLookupResponse { + items: Array; + next_page_params: { + page_token: string; + page_size: number; + } | null; +} + +export interface EnsAddressLookupFilters { + address: string | null; + resolved_to: boolean; + owned_by: boolean; + only_active: boolean; +} + +export interface EnsDomainLookupFilters { + name: string | null; + only_active: boolean; +} + +export interface EnsLookupSorting { + sort: 'registration_date'; + order: 'ASC' | 'DESC'; +} + +export type EnsDomainLookupFiltersOptions = Array<'resolved_to' | 'owned_by' | 'with_inactive'>; diff --git a/types/api/l2Deposits.ts b/types/api/l2Deposits.ts deleted file mode 100644 index 1704414da5..0000000000 --- a/types/api/l2Deposits.ts +++ /dev/null @@ -1,17 +0,0 @@ -export type L2DepositsItem = { - l1_block_number: number; - l1_tx_hash: string; - l1_block_timestamp: string; - l1_tx_origin: string; - l2_tx_gas_limit: string; - l2_tx_hash: string; -} - -export type L2DepositsResponse = { - items: Array; - next_page_params: { - items_count: number; - l1_block_number: number; - tx_hash: string; - }; -} diff --git a/types/api/l2OutputRoots.ts b/types/api/l2OutputRoots.ts deleted file mode 100644 index 8bcfe07d75..0000000000 --- a/types/api/l2OutputRoots.ts +++ /dev/null @@ -1,16 +0,0 @@ -export type L2OutputRootsItem = { - l1_block_number: number; - l1_timestamp: string; - l1_tx_hash: string; - l2_block_number: number; - l2_output_index: number; - output_root: string; -} - -export type L2OutputRootsResponse = { - items: Array; - next_page_params: { - index: number; - items_count: number; - }; -} diff --git a/types/api/l2TxnBatches.ts b/types/api/l2TxnBatches.ts deleted file mode 100644 index 62f320e14e..0000000000 --- a/types/api/l2TxnBatches.ts +++ /dev/null @@ -1,15 +0,0 @@ -export type L2TxnBatchesItem = { - epoch_number: number; - l1_tx_hashes: Array; - l1_timestamp: string; - l2_block_number: number; - tx_count: number; -} - -export type L2TxnBatchesResponse = { - items: Array; - next_page_params: { - block_number: number; - items_count: number; - }; -} diff --git a/types/api/l2Withdrawals.ts b/types/api/l2Withdrawals.ts deleted file mode 100644 index e9a9103e79..0000000000 --- a/types/api/l2Withdrawals.ts +++ /dev/null @@ -1,30 +0,0 @@ -import type { AddressParam } from './addressParams'; - -export type L2WithdrawalsItem = { - 'challenge_period_end': string | null; - 'from': AddressParam | null; - 'l1_tx_hash': string | null; - 'l2_timestamp': string | null; - 'l2_tx_hash': string; - 'msg_nonce': number; - 'msg_nonce_version': number; - 'status': string; -} - -export const WITHDRAWAL_STATUSES = [ - 'Waiting for state root', - 'Ready to prove', - 'In challenge period', - 'Ready for relay', - 'Relayed', -] as const; - -export type L2WithdrawalStatus = typeof WITHDRAWAL_STATUSES[number]; - -export type L2WithdrawalsResponse = { - items: Array; - 'next_page_params': { - 'items_count': number; - 'nonce': string; - }; -} diff --git a/types/api/noves.ts b/types/api/noves.ts new file mode 100644 index 0000000000..1d4b396716 --- /dev/null +++ b/types/api/noves.ts @@ -0,0 +1,124 @@ +export interface NovesResponseData { + txTypeVersion: number; + chain: string; + accountAddress: string; + classificationData: NovesClassificationData; + rawTransactionData: NovesRawTransactionData; +} + +export interface NovesClassificationData { + type: string; + typeFormatted?: string; + description: string; + sent: Array; + received: Array; + approved?: Approved; + protocol?: { + name: string | null; + }; + source: { + type: string | null; + }; + message?: string; +} + +export interface Approved { + amount: string; + spender: string; + token?: NovesToken; + nft?: NovesNft; +} + +export interface NovesSentReceived { + action: string; + actionFormatted?: string; + amount: string; + to: NovesTo; + from: NovesFrom; + token?: NovesToken; + nft?: NovesNft; +} + +export interface NovesToken { + symbol: string; + name: string; + decimals: number; + address: string; + id?: string; +} + +export interface NovesNft { + name: string; + id: string; + symbol: string; + address: string; +} + +export interface NovesFrom { + name: string | null; + address: string; +} + +export interface NovesTo { + name: string | null; + address: string | null; +} + +export interface NovesRawTransactionData { + transactionHash: string; + fromAddress: string; + toAddress: string; + blockNumber: number; + gas: number; + gasPrice: number; + transactionFee: NovesTransactionFee | number; + timestamp: number; +} + +export interface NovesTransactionFee { + amount: string; + currency?: string; + token?: { + decimals: number; + symbol: string; + }; +} + +export interface NovesAccountHistoryResponse { + hasNextPage: boolean; + items: Array; + pageNumber: number; + pageSize: number; + next_page_params?: { + startBlock: string; + endBlock: string; + pageNumber: number; + pageSize: number; + ignoreTransactions: string; + viewAsAccountAddress: string; + }; +} + +export const NovesHistoryFilterValues = [ 'received', 'sent' ] as const; + +export type NovesHistoryFilterValue = typeof NovesHistoryFilterValues[number] | undefined; + +export interface NovesHistoryFilters { + filter?: NovesHistoryFilterValue; +} + +export interface NovesDescribeResponse { + type: string; + description: string; +} + +export interface NovesDescribeTxsResponse { + txHash: string; + type: string; + description: string; +}[]; + +export interface NovesTxTranslation { + data?: NovesDescribeTxsResponse; + isLoading: boolean; +} diff --git a/types/api/optimisticL2.ts b/types/api/optimisticL2.ts new file mode 100644 index 0000000000..0d9496d69c --- /dev/null +++ b/types/api/optimisticL2.ts @@ -0,0 +1,80 @@ +import type { AddressParam } from './addressParams'; + +export type OptimisticL2DepositsItem = { + l1_block_number: number; + l1_tx_hash: string; + l1_block_timestamp: string; + l1_tx_origin: string; + l2_tx_gas_limit: string; + l2_tx_hash: string; +} + +export type OptimisticL2DepositsResponse = { + items: Array; + next_page_params: { + items_count: number; + l1_block_number: number; + tx_hash: string; + }; +} + +export type OptimisticL2OutputRootsItem = { + l1_block_number: number; + l1_timestamp: string; + l1_tx_hash: string; + l2_block_number: number; + l2_output_index: number; + output_root: string; +} + +export type OptimisticL2OutputRootsResponse = { + items: Array; + next_page_params: { + index: number; + items_count: number; + }; +} + +export type OptimisticL2TxnBatchesItem = { + l1_tx_hashes: Array; + l1_timestamp: string; + l2_block_number: number; + tx_count: number; +} + +export type OptimisticL2TxnBatchesResponse = { + items: Array; + next_page_params: { + block_number: number; + items_count: number; + }; +} + +export type OptimisticL2WithdrawalsItem = { + 'challenge_period_end': string | null; + 'from': AddressParam | null; + 'l1_tx_hash': string | null; + 'l2_timestamp': string | null; + 'l2_tx_hash': string; + 'msg_nonce': number; + 'msg_nonce_version': number; + 'status': string; +} + +export const WITHDRAWAL_STATUSES = [ + 'Waiting for state root', + 'Ready to prove', + 'In challenge period', + 'Ready for relay', + 'Relayed', +] as const; + +export type OptimisticL2WithdrawalStatus = typeof WITHDRAWAL_STATUSES[number]; + +export type OptimisticL2WithdrawalsResponse = { + items: Array; + 'next_page_params': { + 'items_count': number; + 'nonce': string; + }; +} diff --git a/types/api/search.ts b/types/api/search.ts index 46467c3e39..aee756023a 100644 --- a/types/api/search.ts +++ b/types/api/search.ts @@ -23,6 +23,12 @@ export interface SearchResultAddressOrContractOrUniversalProfile { address: string; is_smart_contract_verified: boolean; url?: string; // not used by the frontend, we build the url ourselves + ens_info?: { + address_hash: string; + expiry_date?: string; + name: string; + names_count: number; + }; } export interface SearchResultLabel { @@ -35,7 +41,7 @@ export interface SearchResultLabel { export interface SearchResultBlock { type: 'block'; - block_type?: 'block' | 'reorg'; + block_type?: 'block' | 'reorg' | 'uncle'; block_number: number | string; block_hash: string; timestamp: string; @@ -49,12 +55,26 @@ export interface SearchResultTx { url?: string; // not used by the frontend, we build the url ourselves } -export type SearchResultItem = - SearchResultToken | - SearchResultAddressOrContractOrUniversalProfile | - SearchResultBlock | - SearchResultTx | - SearchResultLabel +export interface SearchResultBlob { + type: 'blob'; + blob_hash: string; + timestamp: null; +} + +export interface SearchResultUserOp { + type: 'user_operation'; + user_operation_hash: string; + timestamp: string; + url?: string; // not used by the frontend, we build the url ourselves +} + +export type SearchResultItem = SearchResultToken | +SearchResultAddressOrContractOrUniversalProfile | +SearchResultBlock | +SearchResultTx | +SearchResultLabel | +SearchResultUserOp | +SearchResultBlob; export interface SearchResult { items: Array; @@ -78,5 +98,5 @@ export interface SearchResultFilters { export interface SearchRedirectResult { parameter: string | null; redirect: boolean; - type: 'address' | 'block' | 'transaction' | null; + type: 'address' | 'block' | 'transaction' | 'user_operation' | 'blob' | null; } diff --git a/types/api/shibarium.ts b/types/api/shibarium.ts new file mode 100644 index 0000000000..e23d9cf811 --- /dev/null +++ b/types/api/shibarium.ts @@ -0,0 +1,33 @@ +import type { AddressParam } from './addressParams'; + +export type ShibariumDepositsItem = { + l1_block_number: number; + l1_transaction_hash: string; + l2_transaction_hash: string; + timestamp: string; + user: AddressParam | string; +} + +export type ShibariumDepositsResponse = { + items: Array; + next_page_params: { + items_count: number; + block_number: number; + }; +} + +export type ShibariumWithdrawalsItem = { + l1_transaction_hash: string; + l2_block_number: number; + l2_transaction_hash: string; + timestamp: string; + user: AddressParam | string; +} + +export type ShibariumWithdrawalsResponse = { + items: Array; + next_page_params: { + items_count: number; + block_number: number; + }; +} diff --git a/types/api/stats.ts b/types/api/stats.ts index 7ce179368f..40fe7d34e2 100644 --- a/types/api/stats.ts +++ b/types/api/stats.ts @@ -4,10 +4,13 @@ export type HomeStats = { total_transactions: string; average_block_time: number; coin_price: string | null; + coin_price_change_percentage: number | null; // e.g -6.22 total_gas_used: string; transactions_today: string; gas_used_today: string; gas_prices: GasPrices | null; + gas_price_updated_at: string | null; + gas_prices_update_in: number; static_gas_price: string | null; market_cap: string; network_utilization_percentage: number; @@ -16,9 +19,17 @@ export type HomeStats = { } export type GasPrices = { - average: number | null; - fast: number | null; - slow: number | null; + average: GasPriceInfo | null; + fast: GasPriceInfo | null; + slow: GasPriceInfo | null; +} + +export interface GasPriceInfo { + fiat_price: string | null; + price: number | null; + time: number | null; + base_fee: number | null; + priority_fee: number | null; } export type Counters = { diff --git a/types/api/token.ts b/types/api/token.ts index 417c74aab6..7cae408f7d 100644 --- a/types/api/token.ts +++ b/types/api/token.ts @@ -1,7 +1,7 @@ import type { TokenInfoApplication } from './account'; import type { AddressParam } from './addressParams'; -export type NFTTokenType = 'ERC-721' | 'ERC-1155'; +export type NFTTokenType = 'ERC-721' | 'ERC-1155' | 'ERC-404'; export type TokenType = 'ERC-20' | NFTTokenType; export interface TokenInfo { @@ -39,12 +39,9 @@ export type TokenHolderBase = { value: string; } -export type TokenHolderERC20ERC721 = TokenHolderBase & { - token: TokenInfo<'ERC-20'> | TokenInfo<'ERC-721'>; -} +export type TokenHolderERC20ERC721 = TokenHolderBase export type TokenHolderERC1155 = TokenHolderBase & { - token: TokenInfo<'ERC-1155'>; token_id: string; } diff --git a/types/api/tokenTransfer.ts b/types/api/tokenTransfer.ts index b1100db636..d0feaa7e2d 100644 --- a/types/api/tokenTransfer.ts +++ b/types/api/tokenTransfer.ts @@ -7,15 +7,22 @@ export type Erc20TotalPayload = { } export type Erc721TotalPayload = { - token_id: string; + token_id: string | null; } export type Erc1155TotalPayload = { decimals: string | null; value: string; - token_id: string; + token_id: string | null; } +export type Erc404TotalPayload = { + decimals: string | null; + value: string | null; +} | { + token_id: string | null; +}; + export type TokenTransfer = ( { token: TokenInfo<'ERC-20'>; @@ -28,6 +35,10 @@ export type TokenTransfer = ( { token: TokenInfo<'ERC-1155'>; total: Erc1155TotalPayload; + } | + { + token: TokenInfo<'ERC-404'>; + total: Erc404TotalPayload; } ) & TokenTransferBase diff --git a/types/api/transaction.ts b/types/api/transaction.ts index c76f2f9a19..8f1fee017e 100644 --- a/types/api/transaction.ts +++ b/types/api/transaction.ts @@ -2,10 +2,12 @@ import type { AddressParam } from './addressParams'; import type { BlockTransactionsResponse } from './block'; import type { DecodedInput } from './decodedInput'; import type { Fee } from './fee'; -import type { L2WithdrawalStatus } from './l2Withdrawals'; +import type { NovesTxTranslation } from './noves'; +import type { OptimisticL2WithdrawalStatus } from './optimisticL2'; import type { TokenInfo } from './token'; import type { TokenTransfer } from './tokenTransfer'; import type { TxAction } from './txAction'; +import type { ZkSyncBatchesItem } from './zkSyncL2'; export type TransactionRevertReason = { raw: string; @@ -14,20 +16,26 @@ export type TransactionRevertReason = { type WrappedTransactionFields = 'decoded_input' | 'fee' | 'gas_limit' | 'gas_price' | 'hash' | 'max_fee_per_gas' | 'max_priority_fee_per_gas' | 'method' | 'nonce' | 'raw_input' | 'to' | 'type' | 'value'; +export interface OpWithdrawal { + l1_transaction_hash: string; + nonce: number; + status: OptimisticL2WithdrawalStatus; +} + export type Transaction = { to: AddressParam | null; created_contract: AddressParam | null; hash: string; result: string; confirmations: number; - status: 'ok' | 'error' | null; + status: 'ok' | 'error' | null | undefined; block: number | null; timestamp: string | null; - confirmation_duration: Array; + confirmation_duration: Array | null; from: AddressParam; value: string; fee: Fee; - gas_price: string; + gas_price: string | null; type: number | null; gas_used: string | null; gas_limit: string; @@ -43,7 +51,7 @@ export type Transaction = { decoded_input: DecodedInput | null; token_transfers: Array | null; token_transfers_overflow: boolean; - exchange_rate: string; + exchange_rate: string | null; method: string | null; tx_types: Array; tx_tag: string | null; @@ -54,8 +62,7 @@ export type Transaction = { l1_gas_used?: string; has_error_in_internal_txs: boolean | null; // optimism fields - op_withdrawal_status?: L2WithdrawalStatus; - op_l1_transaction_hash?: string; + op_withdrawals?: Array; // SUAVE fields execution_node?: AddressParam | null; allowed_peekers?: Array; @@ -74,6 +81,18 @@ export type Transaction = { zkevm_batch_number?: number; zkevm_status?: typeof ZKEVM_L2_TX_STATUSES[number]; zkevm_sequence_hash?: string; + // zkSync FIELDS + zksync?: Omit & { + 'batch_number': number | null; + }; + // blob tx fields + blob_versioned_hashes?: Array; + blob_gas_used?: string; + blob_gas_price?: string; + burnt_blob_fee?: string; + max_fee_per_blob_gas?: string; + // Noves-fi + translation?: NovesTxTranslation; } export const ZKEVM_L2_TX_STATUSES = [ 'Confirmed by Sequencer', 'L1 Confirmed' ]; @@ -99,6 +118,15 @@ export interface TransactionsResponsePending { } | null; } +export interface TransactionsResponseWithBlobs { + items: Array; + next_page_params: { + block_number: number; + index: number; + items_count: number; + } | null; +} + export interface TransactionsResponseWatchlist { items: Array; next_page_params: { @@ -114,7 +142,8 @@ export type TransactionType = 'rootstock_remasc' | 'contract_creation' | 'contract_call' | 'token_creation' | -'coin_transfer' +'coin_transfer' | +'blob_transaction' export type TxsResponse = TransactionsResponseValidated | TransactionsResponsePending | BlockTransactionsResponse; diff --git a/types/api/txInterpretation.ts b/types/api/txInterpretation.ts index be9393203f..87ef1d0dae 100644 --- a/types/api/txInterpretation.ts +++ b/types/api/txInterpretation.ts @@ -17,9 +17,11 @@ export type TxInterpretationVariable = TxInterpretationVariableCurrency | TxInterpretationVariableTimestamp | TxInterpretationVariableToken | - TxInterpretationVariableAddress; + TxInterpretationVariableAddress | + TxInterpretationVariableDomain | + TxInterpretationVariableMethod; -export type TxInterpretationVariableType = 'string' | 'currency' | 'timestamp' | 'token' | 'address'; +export type TxInterpretationVariableType = 'string' | 'currency' | 'timestamp' | 'token' | 'address' | 'domain' | 'method'; export type TxInterpretationVariableString = { type: 'string'; @@ -45,3 +47,13 @@ export type TxInterpretationVariableAddress = { type: 'address'; value: AddressParam; } + +export type TxInterpretationVariableDomain = { + type: 'domain'; + value: string; +} + +export type TxInterpretationVariableMethod = { + type: 'method'; + value: string; +} diff --git a/types/api/txStateChanges.ts b/types/api/txStateChanges.ts index 459f196aff..284a921699 100644 --- a/types/api/txStateChanges.ts +++ b/types/api/txStateChanges.ts @@ -41,6 +41,13 @@ export interface TxStateChangeTokenErc1155 { token_id: string; } +export interface TxStateChangeTokenErc404 { + type: 'token'; + token: TokenInfo<'ERC-404'>; + change: string; + token_id: string; +} + export type TxStateChanges = { items: Array; next_page_params: { diff --git a/types/api/txsFilters.ts b/types/api/txsFilters.ts index e34cef83f8..17347c9cdc 100644 --- a/types/api/txsFilters.ts +++ b/types/api/txsFilters.ts @@ -4,6 +4,10 @@ export type TTxsFilters = { method?: Array; } -export type TypeFilter = 'token_transfer' | 'contract_creation' | 'contract_call' | 'coin_transfer' | 'token_creation'; +export type TTxsWithBlobsFilters = { + type: 'blob_transaction'; +} + +export type TypeFilter = 'token_transfer' | 'contract_creation' | 'contract_call' | 'coin_transfer' | 'token_creation' | 'blob_transaction'; export type MethodFilter = 'approve' | 'transfer' | 'multicall' | 'mint' | 'commit'; diff --git a/types/api/userOps.ts b/types/api/userOps.ts new file mode 100644 index 0000000000..7223f7f68e --- /dev/null +++ b/types/api/userOps.ts @@ -0,0 +1,77 @@ +import type { AddressParamBasic } from './addressParams'; + +export type UserOpsItem = { + hash: string; + block_number: string; + transaction_hash: string; + address: string | AddressParamBasic; + timestamp: string; + status: boolean; + fee: string; +} + +export type UserOpsResponse = { + items: Array; + next_page_params: { + page_token: string; + page_size: number; + } | null; +} + +export type UserOpSponsorType = 'paymaster_hybrid' | 'paymaster_sponsor' | 'wallet_balance' | 'wallet_deposit'; + +export type UserOp = { + hash: string; + sender: string | AddressParamBasic; + status: boolean; + revert_reason: string | null; + timestamp: string | null; + fee: string; + gas: string; + transaction_hash: string; + block_number: string; + block_hash: string; + entry_point: string | AddressParamBasic; + call_gas_limit: string; + verification_gas_limit: string; + pre_verification_gas: string; + max_fee_per_gas: string; + max_priority_fee_per_gas: string; + aggregator: string | null; + aggregator_signature: string | null; + bundler: string | AddressParamBasic; + factory: string | null; + paymaster: string | AddressParamBasic | null; + sponsor_type: UserOpSponsorType; + signature: string; + nonce: string; + call_data: string; + user_logs_start_index: number; + user_logs_count: number; + raw: { + account_gas_limits?: string; + call_data: string; + call_gas_limit: string; + gas_fees?: string; + init_code: string; + max_fee_per_gas: string; + max_priority_fee_per_gas: string; + nonce: string; + paymaster_and_data: string; + pre_verification_gas: string; + sender: string; + signature: string; + verification_gas_limit: string; + }; + gas_price: string; + gas_used: string; +} + +export type UserOpsFilters = { + transaction_hash?: string; + sender?: string; +} + +export type UserOpsAccount = { + total_ops: number; +} diff --git a/types/api/validators.ts b/types/api/validators.ts new file mode 100644 index 0000000000..ad6e33de96 --- /dev/null +++ b/types/api/validators.ts @@ -0,0 +1,38 @@ +import type { AddressParam } from './addressParams'; + +export interface Validator { + address: AddressParam; + blocks_validated_count: number; + state: 'active' | 'probation' | 'inactive'; +} + +export interface ValidatorsResponse { + items: Array; + next_page_params: { + 'address_hash': string; + 'blocks_validated': string; + 'items_count': string; + 'state': Validator['state']; + } | null; +} + +export interface ValidatorsCountersResponse { + active_validators_counter: string; + active_validators_percentage: number; + new_validators_counter_24h: string; + validators_counter: string; +} + +export interface ValidatorsFilters { + // address_hash: string | undefined; // right now API doesn't support filtering by address_hash + state_filter: Validator['state'] | undefined; +} + +export interface ValidatorsSorting { + sort: 'state' | 'blocks_validated'; + order: 'asc' | 'desc'; +} + +export type ValidatorsSortingField = ValidatorsSorting['sort']; + +export type ValidatorsSortingValue = `${ ValidatorsSortingField }-${ ValidatorsSorting['order'] }`; diff --git a/types/api/zkEvmL2TxnBatches.ts b/types/api/zkEvmL2.ts similarity index 94% rename from types/api/zkEvmL2TxnBatches.ts rename to types/api/zkEvmL2.ts index 3a82b375ae..df80185cf6 100644 --- a/types/api/zkEvmL2TxnBatches.ts +++ b/types/api/zkEvmL2.ts @@ -5,7 +5,7 @@ export type ZkEvmL2TxnBatchesItem = { verify_tx_hash: string | null; sequence_tx_hash: string | null; status: string; - timestamp: string; + timestamp: string | null; tx_count: number; } @@ -26,7 +26,7 @@ export type ZkEvmL2TxnBatch = { sequence_tx_hash: string; state_root: string; status: typeof ZKEVM_L2_TX_BATCH_STATUSES[number]; - timestamp: string; + timestamp: string | null; transactions: Array; verify_tx_hash: string; } diff --git a/types/api/zkSyncL2.ts b/types/api/zkSyncL2.ts new file mode 100644 index 0000000000..4d038477ff --- /dev/null +++ b/types/api/zkSyncL2.ts @@ -0,0 +1,52 @@ +import type { Transaction } from './transaction'; + +export const ZKSYNC_L2_TX_BATCH_STATUSES = [ + 'Processed on L2' as const, + 'Sealed on L2' as const, + 'Sent to L1' as const, + 'Validated on L1' as const, + 'Executed on L1' as const, +]; + +export type ZkSyncBatchStatus = typeof ZKSYNC_L2_TX_BATCH_STATUSES[number]; + +export interface ZkSyncBatchesItem { + commit_transaction_hash: string | null; + commit_transaction_timestamp: string | null; + execute_transaction_hash: string | null; + execute_transaction_timestamp: string | null; + number: number; + prove_transaction_hash: string | null; + prove_transaction_timestamp: string | null; + status: ZkSyncBatchStatus; + timestamp: string; + tx_count: number; +} + +export type ZkSyncBatchesResponse = { + items: Array; + next_page_params: { + number: number; + items_count: number; + } | null; +} + +export interface ZkSyncBatch extends Omit { + start_block: number; + end_block: number; + l1_gas_price: string; + l1_tx_count: number; + l2_fair_gas_price: string; + l2_tx_count: number; + root_hash: string; +} + +export type ZkSyncBatchTxs = { + items: Array; + next_page_params: { + batch_number: string; + block_number: number; + index: number; + items_count: number; + } | null; +} diff --git a/types/client/adProviders.ts b/types/client/adProviders.ts index 0ae135eff9..ac0418496d 100644 --- a/types/client/adProviders.ts +++ b/types/client/adProviders.ts @@ -1,7 +1,10 @@ import type { ArrayElement } from 'types/utils'; -export const SUPPORTED_AD_BANNER_PROVIDERS = [ 'slise', 'adbutler', 'coinzilla', 'none' ] as const; +export const SUPPORTED_AD_BANNER_PROVIDERS = [ 'slise', 'adbutler', 'coinzilla', 'hype', 'getit', 'none' ] as const; export type AdBannerProviders = ArrayElement; +export const SUPPORTED_AD_BANNER_ADDITIONAL_PROVIDERS = [ 'adbutler' ] as const; +export type AdBannerAdditionalProviders = ArrayElement; + export const SUPPORTED_AD_TEXT_PROVIDERS = [ 'coinzilla', 'none' ] as const; export type AdTextProviders = ArrayElement; diff --git a/types/client/contract.ts b/types/client/contract.ts new file mode 100644 index 0000000000..63116a4072 --- /dev/null +++ b/types/client/contract.ts @@ -0,0 +1,14 @@ +import type { SmartContractLicenseType } from 'types/api/contract'; + +export interface ContractCodeIde { + title: string; + url: string; + icon_url: string; +} + +export interface ContractLicense { + type: SmartContractLicenseType; + url: string; + label: string; + title: string; +} diff --git a/types/client/gasTracker.ts b/types/client/gasTracker.ts new file mode 100644 index 0000000000..f998202e95 --- /dev/null +++ b/types/client/gasTracker.ts @@ -0,0 +1,6 @@ +export const GAS_UNITS = [ + 'usd', + 'gwei', +] as const; + +export type GasUnit = typeof GAS_UNITS[number]; diff --git a/types/client/marketplace.ts b/types/client/marketplace.ts index 766e2e4885..9063f538af 100644 --- a/types/client/marketplace.ts +++ b/types/client/marketplace.ts @@ -1,3 +1,5 @@ +import type { SolidityscanReport } from 'types/api/contract'; + export type MarketplaceAppPreview = { id: string; external?: boolean; @@ -7,18 +9,62 @@ export type MarketplaceAppPreview = { shortDescription: string; categories: Array; url: string; + internalWallet?: boolean; + priority?: number; +} + +export type MarketplaceAppSocialInfo = { + twitter?: string; + telegram?: string; + github?: string | Array; + discord?: string; } -export type MarketplaceAppOverview = MarketplaceAppPreview & { +export type MarketplaceAppOverview = MarketplaceAppPreview & MarketplaceAppSocialInfo & { author: string; description: string; site?: string; - twitter?: string; - telegram?: string; - github?: string; +} + +export type MarketplaceAppWithSecurityReport = MarketplaceAppOverview & { + securityReport?: MarketplaceAppSecurityReport; } export enum MarketplaceCategory { - ALL = 'All apps', + ALL = 'All', FAVORITES = 'Favorites', } + +export enum ContractListTypes { + ANALYZED = 'Analyzed', + ALL = 'All', + VERIFIED = 'Verified', +} + +export enum MarketplaceDisplayType { + DEFAULT = 'default', + SCORES = 'scores', +} + +export type MarketplaceAppSecurityReport = { + overallInfo: { + verifiedNumber: number; + totalContractsNumber: number; + solidityScanContractsNumber: number; + securityScore: number; + totalIssues?: number; + issueSeverityDistribution: SolidityscanReport['scan_report']['scan_summary']['issue_severity_distribution']; + }; + contractsData: Array<{ + address: string; + isVerified: boolean; + solidityScanReport?: SolidityscanReport['scan_report'] | null; + }>; +} + +export type MarketplaceAppSecurityReportRaw = { + appName: string; + chainsData: { + [chainId: string]: MarketplaceAppSecurityReport; + }; +} diff --git a/types/client/rollup.ts b/types/client/rollup.ts new file mode 100644 index 0000000000..3e5e5da91d --- /dev/null +++ b/types/client/rollup.ts @@ -0,0 +1,10 @@ +import type { ArrayElement } from 'types/utils'; + +export const ROLLUP_TYPES = [ + 'optimistic', + 'shibarium', + 'zkEvm', + 'zkSync', +] as const; + +export type RollupType = ArrayElement; diff --git a/types/client/txInterpretation.ts b/types/client/txInterpretation.ts index e264b267bc..23f55ed217 100644 --- a/types/client/txInterpretation.ts +++ b/types/client/txInterpretation.ts @@ -2,6 +2,7 @@ import type { ArrayElement } from 'types/utils'; export const PROVIDERS = [ 'blockscout', + 'noves', 'none', ] as const; diff --git a/types/client/validators.ts b/types/client/validators.ts new file mode 100644 index 0000000000..9fdc949d5d --- /dev/null +++ b/types/client/validators.ts @@ -0,0 +1,7 @@ +import type { ArrayElement } from 'types/utils'; + +export const VALIDATORS_CHAIN_TYPE = [ + 'stability', +] as const; + +export type ValidatorsChainType = ArrayElement; diff --git a/types/networks.ts b/types/networks.ts index dbc505d51a..159f506634 100644 --- a/types/networks.ts +++ b/types/networks.ts @@ -13,6 +13,7 @@ export interface FeaturedNetwork { } export interface NetworkExplorer { + logo?: string; title: string; baseUrl: string; paths: { diff --git a/types/utils.ts b/types/utils.ts index 6a41f16f0c..06bb70df3e 100644 --- a/types/utils.ts +++ b/types/utils.ts @@ -7,3 +7,7 @@ export type ExcludeNull = T extends null ? never : T; export type ExcludeUndefined = T extends undefined ? never : T; export type KeysOfObjectOrNull = keyof ExcludeNull; + +/** Combines members of an intersection into a readable type. */ +// https://twitter.com/mattpocockuk/status/1622730173446557697?s=20&t=NdpAcmEFXY01xkqU3KO0Mg +export type Evaluate = { [key in keyof Type]: Type[key] } & unknown diff --git a/types/web3.ts b/types/web3.ts new file mode 100644 index 0000000000..1eab7a8cfa --- /dev/null +++ b/types/web3.ts @@ -0,0 +1,65 @@ +// copied from node_modules/@wagmi/core/src/connectors/injected.ts +import type { EIP1193Provider } from 'viem'; + +import type { Evaluate } from './utils'; + +type WalletProviderFlags = + | 'isApexWallet' + | 'isAvalanche' + | 'isBackpack' + | 'isBifrost' + | 'isBitKeep' + | 'isBitski' + | 'isBlockWallet' + | 'isBraveWallet' + | 'isCoinbaseWallet' + | 'isDawn' + | 'isEnkrypt' + | 'isExodus' + | 'isFrame' + | 'isFrontier' + | 'isGamestop' + | 'isHyperPay' + | 'isImToken' + | 'isKuCoinWallet' + | 'isMathWallet' + | 'isMetaMask' + | 'isOkxWallet' + | 'isOKExWallet' + | 'isOneInchAndroidWallet' + | 'isOneInchIOSWallet' + | 'isOpera' + | 'isPhantom' + | 'isPortal' + | 'isRabby' + | 'isRainbow' + | 'isStatus' + | 'isTally' + | 'isTokenPocket' + | 'isTokenary' + | 'isTrust' + | 'isTrustWallet' + | 'isXDEFI' + | 'isZerion' + +export type WalletProvider = Evaluate< +EIP1193Provider & { + [key in WalletProviderFlags]?: true | undefined +} & { + providers?: Array | undefined; + + /** Only exists in MetaMask as of 2022/04/03 */ + _events?: { connect?: (() => void) | undefined } | undefined; + + /** Only exists in MetaMask as of 2022/04/03 */ + _state?: + | { + accounts?: Array; + initialized?: boolean; + isConnected?: boolean; + isPermanentlyDisconnected?: boolean; + isUnlocked?: boolean; + } + | undefined; +} +> diff --git a/ui/address/AddressAccountHistory.tsx b/ui/address/AddressAccountHistory.tsx new file mode 100644 index 0000000000..5b7cf3aa7e --- /dev/null +++ b/ui/address/AddressAccountHistory.tsx @@ -0,0 +1,125 @@ +import { Box, Hide, Show, Table, + Tbody, Th, Tr } from '@chakra-ui/react'; +import { useRouter } from 'next/router'; +import React from 'react'; + +import type { NovesHistoryFilterValue } from 'types/api/noves'; +import { NovesHistoryFilterValues } from 'types/api/noves'; + +import getFilterValueFromQuery from 'lib/getFilterValueFromQuery'; +import getQueryParamString from 'lib/router/getQueryParamString'; +import { NOVES_TRANSLATE } from 'stubs/noves/NovesTranslate'; +import { generateListStub } from 'stubs/utils'; +import AddressAccountHistoryTableItem from 'ui/address/accountHistory/AddressAccountHistoryTableItem'; +import ActionBar from 'ui/shared/ActionBar'; +import DataListDisplay from 'ui/shared/DataListDisplay'; +import { getFromToValue } from 'ui/shared/Noves/utils'; +import Pagination from 'ui/shared/pagination/Pagination'; +import useQueryWithPages from 'ui/shared/pagination/useQueryWithPages'; +import TheadSticky from 'ui/shared/TheadSticky'; + +import AddressAccountHistoryListItem from './accountHistory/AddressAccountHistoryListItem'; +import AccountHistoryFilter from './AddressAccountHistoryFilter'; + +const getFilterValue = (getFilterValueFromQuery).bind(null, NovesHistoryFilterValues); + +type Props = { + scrollRef?: React.RefObject; +} + +const AddressAccountHistory = ({ scrollRef }: Props) => { + const router = useRouter(); + + const currentAddress = getQueryParamString(router.query.hash).toLowerCase(); + + const [ filterValue, setFilterValue ] = React.useState(getFilterValue(router.query.filter)); + + const { data, isError, pagination, isPlaceholderData } = useQueryWithPages({ + resourceName: 'noves_address_history', + pathParams: { address: currentAddress }, + scrollRef, + options: { + placeholderData: generateListStub<'noves_address_history'>(NOVES_TRANSLATE, 10, { hasNextPage: false, pageNumber: 1, pageSize: 10 }), + }, + }); + + const handleFilterChange = React.useCallback((val: string | Array) => { + + const newVal = getFilterValue(val); + setFilterValue(newVal); + }, [ ]); + + const actionBar = ( + + + + + + ); + + const filteredData = isPlaceholderData ? data?.items : data?.items.filter(i => filterValue ? getFromToValue(i, currentAddress) === filterValue : i); + + const content = ( + + + { filteredData?.map((item, i) => ( + + )) } + + + + + + + + + + + + + { filteredData?.map((item, i) => ( + + )) } + +
+ Age + + Action + + From/To +
+
+
+ ); + + return ( + + ); +}; + +export default AddressAccountHistory; diff --git a/ui/address/AddressAccountHistoryFilter.tsx b/ui/address/AddressAccountHistoryFilter.tsx new file mode 100644 index 0000000000..d66519d635 --- /dev/null +++ b/ui/address/AddressAccountHistoryFilter.tsx @@ -0,0 +1,55 @@ +import { + Menu, + MenuButton, + MenuList, + MenuOptionGroup, + MenuItemOption, + useDisclosure, +} from '@chakra-ui/react'; +import React from 'react'; + +import type { NovesHistoryFilterValue } from 'types/api/noves'; + +import useIsInitialLoading from 'lib/hooks/useIsInitialLoading'; +import FilterButton from 'ui/shared/filters/FilterButton'; + +interface Props { + isActive: boolean; + defaultFilter: NovesHistoryFilterValue; + onFilterChange: (nextValue: string | Array) => void; + isLoading?: boolean; +} + +const AccountHistoryFilter = ({ onFilterChange, defaultFilter, isActive, isLoading }: Props) => { + const { isOpen, onToggle } = useDisclosure(); + const isInitialLoading = useIsInitialLoading(isLoading); + + const onCloseMenu = React.useCallback(() => { + if (isOpen) { + onToggle(); + } + }, [ isOpen, onToggle ]); + + return ( + + + + + + + All + Received from + Sent to + + + + ); +}; + +export default React.memo(AccountHistoryFilter); diff --git a/ui/address/AddressBlocksValidated.tsx b/ui/address/AddressBlocksValidated.tsx index 2d65532d8e..b72ef4776e 100644 --- a/ui/address/AddressBlocksValidated.tsx +++ b/ui/address/AddressBlocksValidated.tsx @@ -10,6 +10,7 @@ import config from 'configs/app'; import { getResourceKey } from 'lib/api/useApiQuery'; import useSocketChannel from 'lib/socket/useSocketChannel'; import useSocketMessage from 'lib/socket/useSocketMessage'; +import { currencyUnits } from 'lib/units'; import { BLOCK } from 'stubs/block'; import { generateListStub } from 'stubs/utils'; import ActionBar from 'ui/shared/ActionBar'; @@ -95,7 +96,7 @@ const AddressBlocksValidated = ({ scrollRef }: Props) => { Txn Gas used { !config.UI.views.block.hiddenFields?.total_reward && - Reward { config.chain.currency.symbol } } + Reward { currencyUnits.ether } } diff --git a/ui/address/AddressDetails.pw.tsx b/ui/address/AddressDetails.pw.tsx index 2b7d3ce97e..310c175141 100644 --- a/ui/address/AddressDetails.pw.tsx +++ b/ui/address/AddressDetails.pw.tsx @@ -1,11 +1,8 @@ import { test, expect } from '@playwright/experimental-ct-react'; -import type { UseQueryResult } from '@tanstack/react-query'; import React from 'react'; -import type { WindowProvider } from 'wagmi'; -import type { Address } from 'types/api/address'; +import type { WalletProvider } from 'types/web3'; -import type { ResourceError } from 'lib/api/resources'; import * as addressMock from 'mocks/address/address'; import * as countersMock from 'mocks/address/counters'; import * as tokensMock from 'mocks/address/tokens'; @@ -15,6 +12,7 @@ import * as configs from 'playwright/utils/configs'; import AddressDetails from './AddressDetails'; import MockAddressPage from './testUtils/MockAddressPage'; +import type { AddressQuery } from './utils/useAddressQuery'; const ADDRESS_HASH = addressMock.hash; const API_URL_ADDRESS = buildApiUrl('address', { hash: ADDRESS_HASH }); @@ -22,6 +20,7 @@ const API_URL_COUNTERS = buildApiUrl('address_counters', { hash: ADDRESS_HASH }) const API_URL_TOKENS_ERC20 = buildApiUrl('address_tokens', { hash: ADDRESS_HASH }) + '?type=ERC-20'; const API_URL_TOKENS_ERC721 = buildApiUrl('address_tokens', { hash: ADDRESS_HASH }) + '?type=ERC-721'; const API_URL_TOKENS_ER1155 = buildApiUrl('address_tokens', { hash: ADDRESS_HASH }) + '?type=ERC-1155'; +const API_URL_TOKENS_ERC404 = buildApiUrl('address_tokens', { hash: ADDRESS_HASH }) + '?type=ERC-404'; const hooksConfig = { router: { query: { hash: ADDRESS_HASH }, @@ -40,7 +39,7 @@ test('contract +@mobile', async({ mount, page }) => { const component = await mount( - }/> + , { hooksConfig }, ); @@ -72,17 +71,21 @@ test('token', async({ mount, page }) => { status: 200, body: JSON.stringify(tokensMock.erc1155List), }), { times: 1 }); + await page.route(API_URL_TOKENS_ERC404, async(route) => route.fulfill({ + status: 200, + body: JSON.stringify(tokensMock.erc404List), + }), { times: 1 }); await page.evaluate(() => { window.ethereum = { providers: [ { isMetaMask: true, _events: {} } ], - }as WindowProvider; + } as WalletProvider; }); const component = await mount( - }/> + , { hooksConfig }, @@ -106,7 +109,7 @@ test('validator +@mobile', async({ mount, page }) => { const component = await mount( - }/> + , { hooksConfig }, ); diff --git a/ui/address/AddressDetails.tsx b/ui/address/AddressDetails.tsx index c0e690b889..662fd86a70 100644 --- a/ui/address/AddressDetails.tsx +++ b/ui/address/AddressDetails.tsx @@ -1,15 +1,12 @@ import { Box, Text, Grid } from '@chakra-ui/react'; -import type { UseQueryResult } from '@tanstack/react-query'; import { useRouter } from 'next/router'; import React from 'react'; -import type { Address as TAddress } from 'types/api/address'; - -import type { ResourceError } from 'lib/api/resources'; -import useApiQuery from 'lib/api/useApiQuery'; +import throwOnResourceLoadError from 'lib/errors/throwOnResourceLoadError'; import getQueryParamString from 'lib/router/getQueryParamString'; -import { ADDRESS_COUNTERS } from 'stubs/address'; import AddressCounterItem from 'ui/address/details/AddressCounterItem'; +import ServiceDegradationWarning from 'ui/shared/alerts/ServiceDegradationWarning'; +import isCustomAppError from 'ui/shared/AppError/isCustomAppError'; import DataFetchAlert from 'ui/shared/DataFetchAlert'; import DetailsInfoItem from 'ui/shared/DetailsInfoItem'; import DetailsSponsoredItem from 'ui/shared/DetailsSponsoredItem'; @@ -20,9 +17,11 @@ import TxEntity from 'ui/shared/entities/tx/TxEntity'; import AddressBalance from './details/AddressBalance'; import AddressNameInfo from './details/AddressNameInfo'; import TokenSelect from './tokenSelect/TokenSelect'; +import useAddressCountersQuery from './utils/useAddressCountersQuery'; +import type { AddressQuery } from './utils/useAddressQuery'; interface Props { - addressQuery: UseQueryResult; + addressQuery: AddressQuery; scrollRef?: React.RefObject; } @@ -31,12 +30,9 @@ const AddressDetails = ({ addressQuery, scrollRef }: Props) => { const addressHash = getQueryParamString(router.query.hash); - const countersQuery = useApiQuery('address_counters', { - pathParams: { hash: addressHash }, - queryOptions: { - enabled: Boolean(addressHash) && Boolean(addressQuery.data), - placeholderData: ADDRESS_COUNTERS, - }, + const countersQuery = useAddressCountersQuery({ + hash: addressHash, + addressQuery, }); const handleCounterItemClick = React.useCallback(() => { @@ -46,7 +42,7 @@ const AddressDetails = ({ addressQuery, scrollRef }: Props) => { }, 500); }, [ scrollRef ]); - const errorData = React.useMemo(() => ({ + const error404Data = React.useMemo(() => ({ hash: addressHash || '', is_contract: false, implementation_name: null, @@ -64,153 +60,163 @@ const AddressDetails = ({ addressQuery, scrollRef }: Props) => { has_validated_blocks: false, }), [ addressHash ]); - const is404Error = addressQuery.isError && 'status' in addressQuery.error && addressQuery.error.status === 404; - const is422Error = addressQuery.isError && 'status' in addressQuery.error && addressQuery.error.status === 422; - - if (addressQuery.isError && is422Error) { - throw Error('Address fetch error', { cause: addressQuery.error as unknown as Error }); + // error handling (except 404 codes) + if (addressQuery.isError) { + if (isCustomAppError(addressQuery.error)) { + const is404Error = addressQuery.isError && 'status' in addressQuery.error && addressQuery.error.status === 404; + if (!is404Error) { + throwOnResourceLoadError(addressQuery); + } + } else { + return ; + } } - if (addressQuery.isError && !is404Error) { - return ; - } - - const data = addressQuery.isError ? errorData : addressQuery.data; + const data = addressQuery.isError ? error404Data : addressQuery.data; if (!data) { return null; } return ( - - - { data.is_contract && data.creation_tx_hash && data.creator_address_hash && ( - - - at txn - - - ) } - { data.is_contract && data.implementation_address && ( - - - - ) } - - { data.has_tokens && ( - - { addressQuery.data ? : 0 } - - ) } - + { addressQuery.isDegradedData && } + - { addressQuery.data ? ( - - ) : - 0 } - - { data.has_token_transfers && ( - - { addressQuery.data ? ( - + { data.is_contract && data.creation_tx_hash && data.creator_address_hash && ( + + - ) : - 0 } - - ) } - - { addressQuery.data ? ( - - ) : - 0 } - - { data.has_validated_blocks && ( + at txn + + + ) } + { data.is_contract && data.implementation_address && ( + + + + ) } + + { data.has_tokens && ( + + { addressQuery.data ? : 0 } + + ) } { addressQuery.data ? ( ) : 0 } - ) } - { data.block_number_balance_updated_at && ( - - + { addressQuery.data ? ( + + ) : + 0 } + + ) } + { countersQuery.data?.gas_usage_count && ( + + { addressQuery.data ? ( + + ) : + 0 } + + ) } + { data.has_validated_blocks && ( + + { addressQuery.data ? ( + + ) : + 0 } + + ) } + { data.block_number_balance_updated_at && ( + - - ) } - - + > + + + ) } + + + ); }; diff --git a/ui/address/AddressTokens.pw.tsx b/ui/address/AddressTokens.pw.tsx index 7ee5556986..514b3aa7ad 100644 --- a/ui/address/AddressTokens.pw.tsx +++ b/ui/address/AddressTokens.pw.tsx @@ -37,6 +37,10 @@ const test = base.extend({ items: [ tokensMock.erc1155a, tokensMock.erc1155b ], next_page_params: nextPageParams, }; + const response404 = { + items: [ tokensMock.erc404a, tokensMock.erc404b ], + next_page_params: nextPageParams, + }; await page.route(API_URL_ADDRESS, (route) => route.fulfill({ status: 200, @@ -54,6 +58,10 @@ const test = base.extend({ status: 200, body: JSON.stringify(response1155), })); + await page.route(API_URL_TOKENS + '?type=ERC-404', (route) => route.fulfill({ + status: 200, + body: JSON.stringify(response404), + })); await page.route(API_URL_NFT, (route) => route.fulfill({ status: 200, body: JSON.stringify(tokensMock.nfts), @@ -217,6 +225,10 @@ base.describe('update balances via socket', () => { items: [ tokensMock.erc1155a ], next_page_params: null, }; + const response404 = { + items: [ tokensMock.erc404a ], + next_page_params: null, + }; await page.route(API_URL_ADDRESS, (route) => route.fulfill({ status: 200, @@ -234,6 +246,10 @@ base.describe('update balances via socket', () => { status: 200, body: JSON.stringify(response1155), })); + await page.route(API_URL_TOKENS + '?type=ERC-404', (route) => route.fulfill({ + status: 200, + body: JSON.stringify(response404), + })); const component = await mount( @@ -248,6 +264,7 @@ base.describe('update balances via socket', () => { await page.waitForResponse(API_URL_TOKENS + '?type=ERC-20'); await page.waitForResponse(API_URL_TOKENS + '?type=ERC-721'); await page.waitForResponse(API_URL_TOKENS + '?type=ERC-1155'); + await page.waitForResponse(API_URL_TOKENS + '?type=ERC-404'); await expect(component).toHaveScreenshot(); diff --git a/ui/address/AddressTxs.tsx b/ui/address/AddressTxs.tsx index cc21c355df..d272982c71 100644 --- a/ui/address/AddressTxs.tsx +++ b/ui/address/AddressTxs.tsx @@ -20,6 +20,7 @@ import Pagination from 'ui/shared/pagination/Pagination'; import useQueryWithPages from 'ui/shared/pagination/useQueryWithPages'; import getSortParamsFromValue from 'ui/shared/sort/getSortParamsFromValue'; import getSortValueFromQuery from 'ui/shared/sort/getSortValueFromQuery'; +import { sortTxsFromSocket } from 'ui/txs/sortTxs'; import TxsWithAPISorting from 'ui/txs/TxsWithAPISorting'; import { SORT_OPTIONS } from 'ui/txs/useTxsSort'; @@ -85,7 +86,7 @@ const AddressTxs = ({ scrollRef, overloadCount = OVERLOAD_COUNT }: Props) => { addressTxsQuery.onFilterChange({ filter: newVal }); }, [ addressTxsQuery ]); - const handleNewSocketMessage: SocketMessage.AddressTxs['handler'] = (payload) => { + const handleNewSocketMessage: SocketMessage.AddressTxs['handler'] = React.useCallback((payload) => { setSocketAlert(''); queryClient.setQueryData( @@ -123,10 +124,10 @@ const AddressTxs = ({ scrollRef, overloadCount = OVERLOAD_COUNT }: Props) => { items: [ ...newItems, ...prevData.items, - ], + ].sort(sortTxsFromSocket(sort)), }; }); - }; + }, [ currentAddress, filterValue, overloadCount, queryClient, sort ]); const handleSocketClose = React.useCallback(() => { setSocketAlert('Connection is lost. Please refresh the page to load new transactions.'); diff --git a/ui/address/AddressTxsFilter.tsx b/ui/address/AddressTxsFilter.tsx index 1b8ead77ff..c566f1d91e 100644 --- a/ui/address/AddressTxsFilter.tsx +++ b/ui/address/AddressTxsFilter.tsx @@ -38,8 +38,8 @@ const AddressTxsFilter = ({ onFilterChange, defaultFilter, isActive, isLoading } All - From - To + Outgoing transactions + Incoming transactions diff --git a/ui/address/AddressUserOps.tsx b/ui/address/AddressUserOps.tsx new file mode 100644 index 0000000000..7c93c83b47 --- /dev/null +++ b/ui/address/AddressUserOps.tsx @@ -0,0 +1,35 @@ +import { useRouter } from 'next/router'; +import React from 'react'; + +import getQueryParamString from 'lib/router/getQueryParamString'; +import { USER_OPS_ITEM } from 'stubs/userOps'; +import { generateListStub } from 'stubs/utils'; +import useQueryWithPages from 'ui/shared/pagination/useQueryWithPages'; +import UserOpsContent from 'ui/userOps/UserOpsContent'; + +type Props = { + scrollRef?: React.RefObject; +} + +const AddressUserOps = ({ scrollRef }: Props) => { + const router = useRouter(); + + const hash = getQueryParamString(router.query.hash); + + const userOpsQuery = useQueryWithPages({ + resourceName: 'user_ops', + scrollRef, + options: { + enabled: Boolean(hash), + placeholderData: generateListStub<'user_ops'>(USER_OPS_ITEM, 50, { next_page_params: { + page_token: '10355938,0x5956a847d8089e254e02e5111cad6992b99ceb9e5c2dc4343fd53002834c4dc6', + page_size: 50, + } }), + }, + filters: { sender: hash }, + }); + + return ; +}; + +export default AddressUserOps; diff --git a/ui/address/AddressWithdrawals.tsx b/ui/address/AddressWithdrawals.tsx index 5deaedc52d..bbc61e8730 100644 --- a/ui/address/AddressWithdrawals.tsx +++ b/ui/address/AddressWithdrawals.tsx @@ -9,8 +9,8 @@ import ActionBar from 'ui/shared/ActionBar'; import DataListDisplay from 'ui/shared/DataListDisplay'; import Pagination from 'ui/shared/pagination/Pagination'; import useQueryWithPages from 'ui/shared/pagination/useQueryWithPages'; -import WithdrawalsListItem from 'ui/withdrawals/WithdrawalsListItem'; -import WithdrawalsTable from 'ui/withdrawals/WithdrawalsTable'; +import BeaconChainWithdrawalsListItem from 'ui/withdrawals/beaconChain/BeaconChainWithdrawalsListItem'; +import BeaconChainWithdrawalsTable from 'ui/withdrawals/beaconChain/BeaconChainWithdrawalsTable'; const AddressWithdrawals = ({ scrollRef }: {scrollRef?: React.RefObject}) => { const router = useRouter(); @@ -32,7 +32,7 @@ const AddressWithdrawals = ({ scrollRef }: {scrollRef?: React.RefObject { data.items.map((item, index) => ( - - + ) : null ; diff --git a/ui/address/SolidityscanReport.tsx b/ui/address/SolidityscanReport.tsx index 8fe817d4fd..7032ffcd99 100644 --- a/ui/address/SolidityscanReport.tsx +++ b/ui/address/SolidityscanReport.tsx @@ -1,72 +1,22 @@ -import { - Box, - Flex, - Text, - Grid, - Button, - chakra, - Popover, - PopoverTrigger, - PopoverBody, - PopoverContent, - useDisclosure, - Skeleton, - Center, - useColorModeValue, -} from '@chakra-ui/react'; +import { Box, Text, chakra, Icon, Popover, PopoverTrigger, PopoverContent, PopoverBody, useDisclosure } from '@chakra-ui/react'; import React from 'react'; -import { SolidityscanReport } from 'types/api/contract'; - +// This icon doesn't work properly when it is in the sprite +// Probably because of the gradient +// eslint-disable-next-line no-restricted-imports +import solidityScanIcon from 'icons/brands/solidity_scan.svg'; import useApiQuery from 'lib/api/useApiQuery'; import { SOLIDITYSCAN_REPORT } from 'stubs/contract'; -import IconSvg from 'ui/shared/IconSvg'; import LinkExternal from 'ui/shared/LinkExternal'; - -type DistributionItem = { - id: keyof SolidityscanReport['scan_report']['scan_summary']['issue_severity_distribution']; - name: string; - color: string; -} - -const DISTRIBUTION_ITEMS: Array = [ - { id: 'critical', name: 'Critical', color: '#891F11' }, - { id: 'high', name: 'High', color: '#EC672C' }, - { id: 'medium', name: 'Medium', color: '#FBE74D' }, - { id: 'low', name: 'Low', color: '#68C88E' }, - { id: 'informational', name: 'Informational', color: '#A3AEBE' }, - { id: 'gas', name: 'Gas', color: '#A47585' }, -]; +import SolidityscanReportButton from 'ui/shared/solidityscanReport/SolidityscanReportButton'; +import SolidityscanReportDetails from 'ui/shared/solidityscanReport/SolidityscanReportDetails'; +import SolidityscanReportScore from 'ui/shared/solidityscanReport/SolidityscanReportScore'; interface Props { className?: string; hash: string; } -type ItemProps = { - item: DistributionItem; - vulnerabilities: SolidityscanReport['scan_report']['scan_summary']['issue_severity_distribution']; - vulnerabilitiesCount: number; -} - -const SolidityScanReportItem = ({ item, vulnerabilities, vulnerabilitiesCount }: ItemProps) => { - const bgBar = useColorModeValue('blackAlpha.50', 'whiteAlpha.50'); - const yetAnotherGrayColor = useColorModeValue('gray.400', 'gray.500'); - - return ( - <> - - - { item.name } - 0 ? 'text' : yetAnotherGrayColor }>{ vulnerabilities[item.id] } - - - - - - ); -}; - const SolidityscanReport = ({ className, hash }: Props) => { const { isOpen, onToggle, onClose } = useDisclosure(); @@ -80,31 +30,10 @@ const SolidityscanReport = ({ className, hash }: Props) => { const score = Number(data?.scan_report.scan_summary.score_v2); - const chartGrayColor = useColorModeValue('gray.100', 'gray.700'); - const yetAnotherGrayColor = useColorModeValue('gray.400', 'gray.500'); - const popoverBgColor = useColorModeValue('white', 'gray.900'); - - const greatScoreColor = useColorModeValue('green.600', 'green.400'); - const averageScoreColor = useColorModeValue('purple.600', 'purple.400'); - const lowScoreColor = useColorModeValue('red.600', 'red.400'); - if (isError || !score) { return null; } - let scoreColor; - let scoreLevel; - if (score >= 80) { - scoreColor = greatScoreColor; - scoreLevel = 'GREAT'; - } else if (score >= 30) { - scoreColor = averageScoreColor; - scoreLevel = 'AVERAGE'; - } else { - scoreColor = lowScoreColor; - scoreLevel = 'LOW'; - } - const vulnerabilities = data?.scan_report.scan_summary.issue_severity_distribution; const vulnerabilitiesCounts = vulnerabilities ? Object.values(vulnerabilities) : []; const vulnerabilitiesCount = vulnerabilitiesCounts.reduce((acc, val) => acc + val, 0); @@ -112,57 +41,25 @@ const SolidityscanReport = ({ className, hash }: Props) => { return ( - - - + - Contract analyzed for 140+ vulnerability patterns by SolidityScan - - -
- -
-
- - - { score } - / 100 - - Security score is { scoreLevel } - -
+ + Contract analyzed for 140+ vulnerability patterns by + + SolidityScan + + { vulnerabilities && vulnerabilitiesCount > 0 && ( Vulnerabilities distribution - - { DISTRIBUTION_ITEMS.map(item => ( - - )) } - + ) } View full report diff --git a/ui/address/__screenshots__/AddressCoinBalance.pw.tsx_dark-color-mode_base-view-dark-mode-mobile-1.png b/ui/address/__screenshots__/AddressCoinBalance.pw.tsx_dark-color-mode_base-view-dark-mode-mobile-1.png index 39d86ce69b..bb79528b20 100644 Binary files a/ui/address/__screenshots__/AddressCoinBalance.pw.tsx_dark-color-mode_base-view-dark-mode-mobile-1.png and b/ui/address/__screenshots__/AddressCoinBalance.pw.tsx_dark-color-mode_base-view-dark-mode-mobile-1.png differ diff --git a/ui/address/__screenshots__/AddressCoinBalance.pw.tsx_default_base-view-dark-mode-mobile-1.png b/ui/address/__screenshots__/AddressCoinBalance.pw.tsx_default_base-view-dark-mode-mobile-1.png index 2e8c7bf712..da77d32fc9 100644 Binary files a/ui/address/__screenshots__/AddressCoinBalance.pw.tsx_default_base-view-dark-mode-mobile-1.png and b/ui/address/__screenshots__/AddressCoinBalance.pw.tsx_default_base-view-dark-mode-mobile-1.png differ diff --git a/ui/address/__screenshots__/AddressCoinBalance.pw.tsx_mobile_base-view-dark-mode-mobile-1.png b/ui/address/__screenshots__/AddressCoinBalance.pw.tsx_mobile_base-view-dark-mode-mobile-1.png index 931f7e7b41..fad359546d 100644 Binary files a/ui/address/__screenshots__/AddressCoinBalance.pw.tsx_mobile_base-view-dark-mode-mobile-1.png and b/ui/address/__screenshots__/AddressCoinBalance.pw.tsx_mobile_base-view-dark-mode-mobile-1.png differ diff --git a/ui/address/__screenshots__/AddressDetails.pw.tsx_default_token-1.png b/ui/address/__screenshots__/AddressDetails.pw.tsx_default_token-1.png index 28d70e3dfe..a41b419d9b 100644 Binary files a/ui/address/__screenshots__/AddressDetails.pw.tsx_default_token-1.png and b/ui/address/__screenshots__/AddressDetails.pw.tsx_default_token-1.png differ diff --git a/ui/address/__screenshots__/AddressInternalTxs.pw.tsx_default_base-view-mobile-1.png b/ui/address/__screenshots__/AddressInternalTxs.pw.tsx_default_base-view-mobile-1.png index 2d36b1a5a8..4f76f75b60 100644 Binary files a/ui/address/__screenshots__/AddressInternalTxs.pw.tsx_default_base-view-mobile-1.png and b/ui/address/__screenshots__/AddressInternalTxs.pw.tsx_default_base-view-mobile-1.png differ diff --git a/ui/address/__screenshots__/AddressInternalTxs.pw.tsx_mobile_base-view-mobile-1.png b/ui/address/__screenshots__/AddressInternalTxs.pw.tsx_mobile_base-view-mobile-1.png index 51fdeb8eb8..46777342c1 100644 Binary files a/ui/address/__screenshots__/AddressInternalTxs.pw.tsx_mobile_base-view-mobile-1.png and b/ui/address/__screenshots__/AddressInternalTxs.pw.tsx_mobile_base-view-mobile-1.png differ diff --git a/ui/address/__screenshots__/AddressTokenTransfers.pw.tsx_default_mobile-with-token-filter-and-no-pagination-1.png b/ui/address/__screenshots__/AddressTokenTransfers.pw.tsx_default_mobile-with-token-filter-and-no-pagination-1.png index 53d53b249c..9a5fc4ea5d 100644 Binary files a/ui/address/__screenshots__/AddressTokenTransfers.pw.tsx_default_mobile-with-token-filter-and-no-pagination-1.png and b/ui/address/__screenshots__/AddressTokenTransfers.pw.tsx_default_mobile-with-token-filter-and-no-pagination-1.png differ diff --git a/ui/address/__screenshots__/AddressTokenTransfers.pw.tsx_default_mobile-with-token-filter-and-pagination-1.png b/ui/address/__screenshots__/AddressTokenTransfers.pw.tsx_default_mobile-with-token-filter-and-pagination-1.png index ebb49117dd..bd3982620b 100644 Binary files a/ui/address/__screenshots__/AddressTokenTransfers.pw.tsx_default_mobile-with-token-filter-and-pagination-1.png and b/ui/address/__screenshots__/AddressTokenTransfers.pw.tsx_default_mobile-with-token-filter-and-pagination-1.png differ diff --git a/ui/address/__screenshots__/AddressTokenTransfers.pw.tsx_default_with-token-filter-and-no-pagination-1.png b/ui/address/__screenshots__/AddressTokenTransfers.pw.tsx_default_with-token-filter-and-no-pagination-1.png index c7d9422137..dcdcb70660 100644 Binary files a/ui/address/__screenshots__/AddressTokenTransfers.pw.tsx_default_with-token-filter-and-no-pagination-1.png and b/ui/address/__screenshots__/AddressTokenTransfers.pw.tsx_default_with-token-filter-and-no-pagination-1.png differ diff --git a/ui/address/__screenshots__/AddressTokenTransfers.pw.tsx_default_with-token-filter-and-pagination-1.png b/ui/address/__screenshots__/AddressTokenTransfers.pw.tsx_default_with-token-filter-and-pagination-1.png index 6ce1cc6b65..0356e2f168 100644 Binary files a/ui/address/__screenshots__/AddressTokenTransfers.pw.tsx_default_with-token-filter-and-pagination-1.png and b/ui/address/__screenshots__/AddressTokenTransfers.pw.tsx_default_with-token-filter-and-pagination-1.png differ diff --git a/ui/address/__screenshots__/AddressTokens.pw.tsx_dark-color-mode_collections-dark-mode-1.png b/ui/address/__screenshots__/AddressTokens.pw.tsx_dark-color-mode_collections-dark-mode-1.png index 2cfbdaf3a7..1d67d2892f 100644 Binary files a/ui/address/__screenshots__/AddressTokens.pw.tsx_dark-color-mode_collections-dark-mode-1.png and b/ui/address/__screenshots__/AddressTokens.pw.tsx_dark-color-mode_collections-dark-mode-1.png differ diff --git a/ui/address/__screenshots__/AddressTokens.pw.tsx_dark-color-mode_erc20-dark-mode-1.png b/ui/address/__screenshots__/AddressTokens.pw.tsx_dark-color-mode_erc20-dark-mode-1.png index 988d310f09..e76a9f9d38 100644 Binary files a/ui/address/__screenshots__/AddressTokens.pw.tsx_dark-color-mode_erc20-dark-mode-1.png and b/ui/address/__screenshots__/AddressTokens.pw.tsx_dark-color-mode_erc20-dark-mode-1.png differ diff --git a/ui/address/__screenshots__/AddressTokens.pw.tsx_dark-color-mode_nfts-dark-mode-1.png b/ui/address/__screenshots__/AddressTokens.pw.tsx_dark-color-mode_nfts-dark-mode-1.png index ff3f91748f..b1f612387b 100644 Binary files a/ui/address/__screenshots__/AddressTokens.pw.tsx_dark-color-mode_nfts-dark-mode-1.png and b/ui/address/__screenshots__/AddressTokens.pw.tsx_dark-color-mode_nfts-dark-mode-1.png differ diff --git a/ui/address/__screenshots__/AddressTokens.pw.tsx_default_collections-dark-mode-1.png b/ui/address/__screenshots__/AddressTokens.pw.tsx_default_collections-dark-mode-1.png index 185fa05a5c..a114a18ba5 100644 Binary files a/ui/address/__screenshots__/AddressTokens.pw.tsx_default_collections-dark-mode-1.png and b/ui/address/__screenshots__/AddressTokens.pw.tsx_default_collections-dark-mode-1.png differ diff --git a/ui/address/__screenshots__/AddressTokens.pw.tsx_default_erc20-dark-mode-1.png b/ui/address/__screenshots__/AddressTokens.pw.tsx_default_erc20-dark-mode-1.png index 5cb20256d2..e44974d63e 100644 Binary files a/ui/address/__screenshots__/AddressTokens.pw.tsx_default_erc20-dark-mode-1.png and b/ui/address/__screenshots__/AddressTokens.pw.tsx_default_erc20-dark-mode-1.png differ diff --git a/ui/address/__screenshots__/AddressTokens.pw.tsx_default_mobile-collections-1.png b/ui/address/__screenshots__/AddressTokens.pw.tsx_default_mobile-collections-1.png index d2aacbe810..0fcb08e5b0 100644 Binary files a/ui/address/__screenshots__/AddressTokens.pw.tsx_default_mobile-collections-1.png and b/ui/address/__screenshots__/AddressTokens.pw.tsx_default_mobile-collections-1.png differ diff --git a/ui/address/__screenshots__/AddressTokens.pw.tsx_default_mobile-erc20-1.png b/ui/address/__screenshots__/AddressTokens.pw.tsx_default_mobile-erc20-1.png index 9325e2a5b4..ac6aa9b71b 100644 Binary files a/ui/address/__screenshots__/AddressTokens.pw.tsx_default_mobile-erc20-1.png and b/ui/address/__screenshots__/AddressTokens.pw.tsx_default_mobile-erc20-1.png differ diff --git a/ui/address/__screenshots__/AddressTokens.pw.tsx_default_mobile-nfts-1.png b/ui/address/__screenshots__/AddressTokens.pw.tsx_default_mobile-nfts-1.png index cb01e73aa2..eca7f88c7f 100644 Binary files a/ui/address/__screenshots__/AddressTokens.pw.tsx_default_mobile-nfts-1.png and b/ui/address/__screenshots__/AddressTokens.pw.tsx_default_mobile-nfts-1.png differ diff --git a/ui/address/__screenshots__/AddressTokens.pw.tsx_default_nfts-dark-mode-1.png b/ui/address/__screenshots__/AddressTokens.pw.tsx_default_nfts-dark-mode-1.png index f4ca1946ec..875ce58c10 100644 Binary files a/ui/address/__screenshots__/AddressTokens.pw.tsx_default_nfts-dark-mode-1.png and b/ui/address/__screenshots__/AddressTokens.pw.tsx_default_nfts-dark-mode-1.png differ diff --git a/ui/address/__screenshots__/AddressTokens.pw.tsx_default_update-balances-via-socket-1.png b/ui/address/__screenshots__/AddressTokens.pw.tsx_default_update-balances-via-socket-1.png index fab130576d..f911c3a3b4 100644 Binary files a/ui/address/__screenshots__/AddressTokens.pw.tsx_default_update-balances-via-socket-1.png and b/ui/address/__screenshots__/AddressTokens.pw.tsx_default_update-balances-via-socket-1.png differ diff --git a/ui/address/__screenshots__/AddressTokens.pw.tsx_default_update-balances-via-socket-2.png b/ui/address/__screenshots__/AddressTokens.pw.tsx_default_update-balances-via-socket-2.png index 4ada42569e..d879fbdd4b 100644 Binary files a/ui/address/__screenshots__/AddressTokens.pw.tsx_default_update-balances-via-socket-2.png and b/ui/address/__screenshots__/AddressTokens.pw.tsx_default_update-balances-via-socket-2.png differ diff --git a/ui/address/__screenshots__/AddressTxs.pw.tsx_default_base-view-mobile-1.png b/ui/address/__screenshots__/AddressTxs.pw.tsx_default_base-view-mobile-1.png index e993699729..26e56490ac 100644 Binary files a/ui/address/__screenshots__/AddressTxs.pw.tsx_default_base-view-mobile-1.png and b/ui/address/__screenshots__/AddressTxs.pw.tsx_default_base-view-mobile-1.png differ diff --git a/ui/address/__screenshots__/AddressTxs.pw.tsx_default_base-view-screen-xl-1.png b/ui/address/__screenshots__/AddressTxs.pw.tsx_default_base-view-screen-xl-1.png index f6e0c639a8..7432110133 100644 Binary files a/ui/address/__screenshots__/AddressTxs.pw.tsx_default_base-view-screen-xl-1.png and b/ui/address/__screenshots__/AddressTxs.pw.tsx_default_base-view-screen-xl-1.png differ diff --git a/ui/address/__screenshots__/AddressTxs.pw.tsx_mobile_base-view-mobile-1.png b/ui/address/__screenshots__/AddressTxs.pw.tsx_mobile_base-view-mobile-1.png index 9ec178775e..03bc8552cd 100644 Binary files a/ui/address/__screenshots__/AddressTxs.pw.tsx_mobile_base-view-mobile-1.png and b/ui/address/__screenshots__/AddressTxs.pw.tsx_mobile_base-view-mobile-1.png differ diff --git a/ui/address/__screenshots__/SolidityscanReport.pw.tsx_dark-color-mode_average-report-dark-mode-mobile-1.png b/ui/address/__screenshots__/SolidityscanReport.pw.tsx_dark-color-mode_average-report-dark-mode-mobile-1.png index f193eca4d4..cb130891fb 100644 Binary files a/ui/address/__screenshots__/SolidityscanReport.pw.tsx_dark-color-mode_average-report-dark-mode-mobile-1.png and b/ui/address/__screenshots__/SolidityscanReport.pw.tsx_dark-color-mode_average-report-dark-mode-mobile-1.png differ diff --git a/ui/address/__screenshots__/SolidityscanReport.pw.tsx_dark-color-mode_average-report-dark-mode-mobile-2.png b/ui/address/__screenshots__/SolidityscanReport.pw.tsx_dark-color-mode_average-report-dark-mode-mobile-2.png index b0bb931d9e..d78deab48d 100644 Binary files a/ui/address/__screenshots__/SolidityscanReport.pw.tsx_dark-color-mode_average-report-dark-mode-mobile-2.png and b/ui/address/__screenshots__/SolidityscanReport.pw.tsx_dark-color-mode_average-report-dark-mode-mobile-2.png differ diff --git a/ui/address/__screenshots__/SolidityscanReport.pw.tsx_default_average-report-dark-mode-mobile-1.png b/ui/address/__screenshots__/SolidityscanReport.pw.tsx_default_average-report-dark-mode-mobile-1.png index 671a01cb38..0462c76ca1 100644 Binary files a/ui/address/__screenshots__/SolidityscanReport.pw.tsx_default_average-report-dark-mode-mobile-1.png and b/ui/address/__screenshots__/SolidityscanReport.pw.tsx_default_average-report-dark-mode-mobile-1.png differ diff --git a/ui/address/__screenshots__/SolidityscanReport.pw.tsx_default_average-report-dark-mode-mobile-2.png b/ui/address/__screenshots__/SolidityscanReport.pw.tsx_default_average-report-dark-mode-mobile-2.png index e3a2f18abc..276b22360a 100644 Binary files a/ui/address/__screenshots__/SolidityscanReport.pw.tsx_default_average-report-dark-mode-mobile-2.png and b/ui/address/__screenshots__/SolidityscanReport.pw.tsx_default_average-report-dark-mode-mobile-2.png differ diff --git a/ui/address/__screenshots__/SolidityscanReport.pw.tsx_default_great-report-1.png b/ui/address/__screenshots__/SolidityscanReport.pw.tsx_default_great-report-1.png index f75f69bc5b..2cf3772046 100644 Binary files a/ui/address/__screenshots__/SolidityscanReport.pw.tsx_default_great-report-1.png and b/ui/address/__screenshots__/SolidityscanReport.pw.tsx_default_great-report-1.png differ diff --git a/ui/address/__screenshots__/SolidityscanReport.pw.tsx_default_great-report-2.png b/ui/address/__screenshots__/SolidityscanReport.pw.tsx_default_great-report-2.png index 3668c60f8f..d971302d11 100644 Binary files a/ui/address/__screenshots__/SolidityscanReport.pw.tsx_default_great-report-2.png and b/ui/address/__screenshots__/SolidityscanReport.pw.tsx_default_great-report-2.png differ diff --git a/ui/address/__screenshots__/SolidityscanReport.pw.tsx_default_low-report-1.png b/ui/address/__screenshots__/SolidityscanReport.pw.tsx_default_low-report-1.png index 69e2567395..89796e448b 100644 Binary files a/ui/address/__screenshots__/SolidityscanReport.pw.tsx_default_low-report-1.png and b/ui/address/__screenshots__/SolidityscanReport.pw.tsx_default_low-report-1.png differ diff --git a/ui/address/__screenshots__/SolidityscanReport.pw.tsx_default_low-report-2.png b/ui/address/__screenshots__/SolidityscanReport.pw.tsx_default_low-report-2.png index 3017e1181c..d504869f2c 100644 Binary files a/ui/address/__screenshots__/SolidityscanReport.pw.tsx_default_low-report-2.png and b/ui/address/__screenshots__/SolidityscanReport.pw.tsx_default_low-report-2.png differ diff --git a/ui/address/__screenshots__/SolidityscanReport.pw.tsx_mobile_average-report-dark-mode-mobile-1.png b/ui/address/__screenshots__/SolidityscanReport.pw.tsx_mobile_average-report-dark-mode-mobile-1.png index 5e7f44de24..9441baa4ca 100644 Binary files a/ui/address/__screenshots__/SolidityscanReport.pw.tsx_mobile_average-report-dark-mode-mobile-1.png and b/ui/address/__screenshots__/SolidityscanReport.pw.tsx_mobile_average-report-dark-mode-mobile-1.png differ diff --git a/ui/address/__screenshots__/SolidityscanReport.pw.tsx_mobile_average-report-dark-mode-mobile-2.png b/ui/address/__screenshots__/SolidityscanReport.pw.tsx_mobile_average-report-dark-mode-mobile-2.png index 4c8850cc5f..766d68a40e 100644 Binary files a/ui/address/__screenshots__/SolidityscanReport.pw.tsx_mobile_average-report-dark-mode-mobile-2.png and b/ui/address/__screenshots__/SolidityscanReport.pw.tsx_mobile_average-report-dark-mode-mobile-2.png differ diff --git a/ui/address/accountHistory/AddressAccountHistoryListItem.tsx b/ui/address/accountHistory/AddressAccountHistoryListItem.tsx new file mode 100644 index 0000000000..46cf969932 --- /dev/null +++ b/ui/address/accountHistory/AddressAccountHistoryListItem.tsx @@ -0,0 +1,66 @@ +import { Box, Flex, Skeleton, Text } from '@chakra-ui/react'; +import React, { useMemo } from 'react'; + +import type { NovesResponseData } from 'types/api/noves'; + +import dayjs from 'lib/date/dayjs'; +import IconSvg from 'ui/shared/IconSvg'; +import LinkInternal from 'ui/shared/LinkInternal'; +import ListItemMobile from 'ui/shared/ListItemMobile/ListItemMobile'; +import NovesFromTo from 'ui/shared/Noves/NovesFromTo'; + +type Props = { + isPlaceholderData: boolean; + tx: NovesResponseData; + currentAddress: string; +}; + +const AddressAccountHistoryListItem = (props: Props) => { + + const parsedDescription = useMemo(() => { + const description = props.tx.classificationData.description; + + return description.endsWith('.') ? description.substring(0, description.length - 1) : description; + }, [ props.tx.classificationData.description ]); + + return ( + + + + + + + + Action + + + + { dayjs(props.tx.rawTransactionData.timestamp * 1000).fromNow() } + + + + + + { parsedDescription } + + + + + + + + ); +}; + +export default React.memo(AddressAccountHistoryListItem); diff --git a/ui/address/accountHistory/AddressAccountHistoryTableItem.tsx b/ui/address/accountHistory/AddressAccountHistoryTableItem.tsx new file mode 100644 index 0000000000..c3aa61a283 --- /dev/null +++ b/ui/address/accountHistory/AddressAccountHistoryTableItem.tsx @@ -0,0 +1,66 @@ +import { Td, Tr, Skeleton, Text, Box } from '@chakra-ui/react'; +import React, { useMemo } from 'react'; + +import type { NovesResponseData } from 'types/api/noves'; + +import dayjs from 'lib/date/dayjs'; +import IconSvg from 'ui/shared/IconSvg'; +import LinkInternal from 'ui/shared/LinkInternal'; +import NovesFromTo from 'ui/shared/Noves/NovesFromTo'; + +type Props = { + isPlaceholderData: boolean; + tx: NovesResponseData; + currentAddress: string; +}; + +const AddressAccountHistoryTableItem = (props: Props) => { + + const parsedDescription = useMemo(() => { + const description = props.tx.classificationData.description; + + return description.endsWith('.') ? description.substring(0, description.length - 1) : description; + }, [ props.tx.classificationData.description ]); + + return ( + + + + + { dayjs(props.tx.rawTransactionData.timestamp * 1000).fromNow() } + + + + + + + + + + { parsedDescription } + + + + + + + + + + + ); +}; + +export default React.memo(AddressAccountHistoryTableItem); diff --git a/ui/address/blocksValidated/AddressBlocksValidatedListItem.tsx b/ui/address/blocksValidated/AddressBlocksValidatedListItem.tsx index 2db323a79c..6627e72d61 100644 --- a/ui/address/blocksValidated/AddressBlocksValidatedListItem.tsx +++ b/ui/address/blocksValidated/AddressBlocksValidatedListItem.tsx @@ -7,6 +7,7 @@ import type { Block } from 'types/api/block'; import config from 'configs/app'; import getBlockTotalReward from 'lib/block/getBlockTotalReward'; import useTimeAgoIncrement from 'lib/hooks/useTimeAgoIncrement'; +import { currencyUnits } from 'lib/units'; import BlockEntity from 'ui/shared/entities/block/BlockEntity'; import ListItemMobile from 'ui/shared/ListItemMobile/ListItemMobile'; import Utilization from 'ui/shared/Utilization/Utilization'; @@ -42,15 +43,17 @@ const AddressBlocksValidatedListItem = (props: Props) => { Gas used { BigNumber(props.gas_used || 0).toFormat() } - + { props.gas_used && props.gas_used !== '0' && ( + + ) } { !config.UI.views.block.hiddenFields?.total_reward && ( - Reward { config.chain.currency.symbol } + Reward { currencyUnits.ether } { totalReward.toFixed() } ) } diff --git a/ui/address/blocksValidated/AddressBlocksValidatedTableItem.tsx b/ui/address/blocksValidated/AddressBlocksValidatedTableItem.tsx index b2ca7b4e79..d6e68dfded 100644 --- a/ui/address/blocksValidated/AddressBlocksValidatedTableItem.tsx +++ b/ui/address/blocksValidated/AddressBlocksValidatedTableItem.tsx @@ -46,11 +46,13 @@ const AddressBlocksValidatedTableItem = (props: Props) => { { BigNumber(props.gas_used || 0).toFormat() } - + { props.gas_used && props.gas_used !== '0' && ( + + ) } { !config.UI.views.block.hiddenFields?.total_reward && ( diff --git a/ui/address/coinBalance/AddressCoinBalanceChart.tsx b/ui/address/coinBalance/AddressCoinBalanceChart.tsx index 483de97796..4e551f1e8b 100644 --- a/ui/address/coinBalance/AddressCoinBalanceChart.tsx +++ b/ui/address/coinBalance/AddressCoinBalanceChart.tsx @@ -3,6 +3,7 @@ import React from 'react'; import config from 'configs/app'; import useApiQuery from 'lib/api/useApiQuery'; +import { currencyUnits } from 'lib/units'; import ChartWidget from 'ui/shared/chart/ChartWidget'; interface Props { @@ -26,7 +27,7 @@ const AddressCoinBalanceChart = ({ addressHash }: Props) => { items={ items } isLoading={ isPending } h="300px" - units={ config.chain.currency.symbol } + units={ currencyUnits.ether } /> ); }; diff --git a/ui/address/coinBalance/AddressCoinBalanceHistory.tsx b/ui/address/coinBalance/AddressCoinBalanceHistory.tsx index 8672520b18..31be2c4006 100644 --- a/ui/address/coinBalance/AddressCoinBalanceHistory.tsx +++ b/ui/address/coinBalance/AddressCoinBalanceHistory.tsx @@ -5,8 +5,8 @@ import React from 'react'; import type { AddressCoinBalanceHistoryResponse } from 'types/api/address'; import type { PaginationParams } from 'ui/shared/pagination/types'; -import config from 'configs/app'; import type { ResourceError } from 'lib/api/resources'; +import { currencyUnits } from 'lib/units'; import ActionBar from 'ui/shared/ActionBar'; import DataListDisplay from 'ui/shared/DataListDisplay'; import Pagination from 'ui/shared/pagination/Pagination'; @@ -32,7 +32,7 @@ const AddressCoinBalanceHistory = ({ query }: Props) => { Block Txn Age - Balance { config.chain.currency.symbol } + Balance { currencyUnits.ether } Delta diff --git a/ui/address/coinBalance/AddressCoinBalanceListItem.tsx b/ui/address/coinBalance/AddressCoinBalanceListItem.tsx index f92ce41215..19ebf6f420 100644 --- a/ui/address/coinBalance/AddressCoinBalanceListItem.tsx +++ b/ui/address/coinBalance/AddressCoinBalanceListItem.tsx @@ -4,9 +4,9 @@ import React from 'react'; import type { AddressCoinBalanceHistoryItem } from 'types/api/address'; -import config from 'configs/app'; import { WEI, ZERO } from 'lib/consts'; import useTimeAgoIncrement from 'lib/hooks/useTimeAgoIncrement'; +import { currencyUnits } from 'lib/units'; import BlockEntity from 'ui/shared/entities/block/BlockEntity'; import TxEntity from 'ui/shared/entities/tx/TxEntity'; import ListItemMobile from 'ui/shared/ListItemMobile/ListItemMobile'; @@ -25,7 +25,7 @@ const AddressCoinBalanceListItem = (props: Props) => { - { BigNumber(props.value).div(WEI).dp(8).toFormat() } { config.chain.currency.symbol } + { BigNumber(props.value).div(WEI).dp(8).toFormat() } { currencyUnits.ether } diff --git a/ui/address/contract/ContractCode.pw.tsx b/ui/address/contract/ContractCode.pw.tsx index 52c7fc9682..cbd660a941 100644 --- a/ui/address/contract/ContractCode.pw.tsx +++ b/ui/address/contract/ContractCode.pw.tsx @@ -2,16 +2,20 @@ import { test as base, expect } from '@playwright/experimental-ct-react'; import React from 'react'; import * as addressMock from 'mocks/address/address'; +import { contractAudits } from 'mocks/contract/audits'; import * as contractMock from 'mocks/contract/info'; +import contextWithEnvs from 'playwright/fixtures/contextWithEnvs'; import * as socketServer from 'playwright/fixtures/socketServer'; import TestApp from 'playwright/TestApp'; import buildApiUrl from 'playwright/utils/buildApiUrl'; +import * as configs from 'playwright/utils/configs'; import MockAddressPage from 'ui/address/testUtils/MockAddressPage'; import ContractCode from './ContractCode'; const addressHash = 'hash'; const CONTRACT_API_URL = buildApiUrl('contract', { hash: addressHash }); +const CONTRACT_AUDITS_API_URL = buildApiUrl('contract_security_audits', { hash: addressHash }); const hooksConfig = { router: { query: { hash: addressHash }, @@ -122,6 +126,9 @@ test('verified with multiple sources', async({ mount, page }) => { await page.getByRole('button', { name: 'View external libraries' }).click(); await expect(section).toHaveScreenshot(); + + await page.getByRole('button', { name: 'Open source code in IDE' }).click(); + await expect(section).toHaveScreenshot(); }); test('verified via sourcify', async({ mount, page }) => { @@ -226,3 +233,54 @@ test('non verified', async({ mount, page }) => { await expect(component).toHaveScreenshot(); }); + +test.describe('with audits feature', () => { + + const withAuditsTest = test.extend({ + // eslint-disable-next-line @typescript-eslint/no-explicit-any + context: contextWithEnvs(configs.UIEnvs.hasContractAuditReports) as any, + }); + + withAuditsTest('no audits', async({ mount, page }) => { + await page.route(CONTRACT_API_URL, (route) => route.fulfill({ + status: 200, + body: JSON.stringify(contractMock.verified), + })); + await page.route(CONTRACT_AUDITS_API_URL, (route) => route.fulfill({ + status: 200, + body: JSON.stringify({ items: [] }), + })); + await page.route('https://cdn.jsdelivr.net/npm/monaco-editor@0.33.0/**', (route) => route.abort()); + + const component = await mount( + + + , + { hooksConfig }, + ); + + await expect(component).toHaveScreenshot(); + }); + + withAuditsTest('has audits', async({ mount, page }) => { + await page.route(CONTRACT_API_URL, (route) => route.fulfill({ + status: 200, + body: JSON.stringify(contractMock.verified), + })); + await page.route(CONTRACT_AUDITS_API_URL, (route) => route.fulfill({ + status: 200, + body: JSON.stringify(contractAudits), + })); + + await page.route('https://cdn.jsdelivr.net/npm/monaco-editor@0.33.0/**', (route) => route.abort()); + + const component = await mount( + + + , + { hooksConfig }, + ); + + await expect(component).toHaveScreenshot(); + }); +}); diff --git a/ui/address/contract/ContractCode.tsx b/ui/address/contract/ContractCode.tsx index cfa2e5f003..7b77ebe3b5 100644 --- a/ui/address/contract/ContractCode.tsx +++ b/ui/address/contract/ContractCode.tsx @@ -7,7 +7,9 @@ import type { Address as AddressInfo } from 'types/api/address'; import { route } from 'nextjs-routes'; +import config from 'configs/app'; import useApiQuery, { getResourceKey } from 'lib/api/useApiQuery'; +import { CONTRACT_LICENSES } from 'lib/contracts/licenses'; import dayjs from 'lib/date/dayjs'; import useSocketChannel from 'lib/socket/useSocketChannel'; import useSocketMessage from 'lib/socket/useSocketMessage'; @@ -18,6 +20,7 @@ import LinkExternal from 'ui/shared/LinkExternal'; import LinkInternal from 'ui/shared/LinkInternal'; import RawDataSnippet from 'ui/shared/RawDataSnippet'; +import ContractSecurityAudits from './ContractSecurityAudits'; import ContractSourceCode from './ContractSourceCode'; type Props = { @@ -26,10 +29,17 @@ type Props = { noSocket?: boolean; } -const InfoItem = chakra(({ label, value, className, isLoading }: { label: string; value: string; className?: string; isLoading: boolean }) => ( +type InfoItemProps = { + label: string; + content: string | React.ReactNode; + className?: string; + isLoading: boolean; +} + +const InfoItem = chakra(({ label, content, className, isLoading }: InfoItemProps) => ( { label } - { value } + { content } )); @@ -109,6 +119,23 @@ const ContractCode = ({ addressHash, noSocket }: Props) => { ); + const licenseLink = (() => { + if (!data?.license_type) { + return null; + } + + const license = CONTRACT_LICENSES.find((license) => license.type === data.license_type); + if (!license || license.type === 'none') { + return null; + } + + return ( + + { license.label } + + ); + })(); + const constructorArgs = (() => { if (!data?.decoded_constructor_args) { return data?.constructor_args; @@ -221,15 +248,23 @@ const ContractCode = ({ addressHash, noSocket }: Props) => { { data?.is_verified && ( - { data.name && } - { data.compiler_version && } - { data.evm_version && } + { data.name && } + { data.compiler_version && } + { data.evm_version && } + { licenseLink && } { typeof data.optimization_enabled === 'boolean' && - } - { data.optimization_runs && } + } + { data.optimization_runs && } { data.verified_at && - } - { data.file_path && } + } + { data.file_path && } + { config.UI.hasContractAuditReports && ( + } + isLoading={ isPlaceholderData } + /> + ) } ) } diff --git a/ui/address/contract/ContractCodeIdes.tsx b/ui/address/contract/ContractCodeIdes.tsx new file mode 100644 index 0000000000..5207f74c68 --- /dev/null +++ b/ui/address/contract/ContractCodeIdes.tsx @@ -0,0 +1,75 @@ +import { Flex, Button, chakra, Popover, PopoverTrigger, PopoverBody, PopoverContent, useDisclosure, Image, useColorModeValue } from '@chakra-ui/react'; +import React from 'react'; + +import config from 'configs/app'; +import IconSvg from 'ui/shared/IconSvg'; +import LinkExternal from 'ui/shared/LinkExternal'; + +interface Props { + className?: string; + hash: string; +} + +const ContractCodeIde = ({ className, hash }: Props) => { + const { isOpen, onToggle, onClose } = useDisclosure(); + const defaultIconColor = useColorModeValue('gray.600', 'gray.500'); + + const ideLinks = React.useMemo(() => { + return config.UI.ides.items + .map((ide) => { + const url = decodeURIComponent(ide.url.replace('{hash}', hash).replace('{domain}', config.app.host || '')); + const icon = 'icon_url' in ide ? + { : + ; + + return ( + + { icon } + { ide.title } + + ); + }); + }, [ defaultIconColor, hash ]); + + if (ideLinks.length === 0) { + return null; + } + + return ( + + + + + + + Redactors + + { ideLinks } + + + + + ); +}; + +export default React.memo(chakra(ContractCodeIde)); diff --git a/ui/address/contract/ContractConnectWallet.tsx b/ui/address/contract/ContractConnectWallet.tsx index 1673030a76..9263495bce 100644 --- a/ui/address/contract/ContractConnectWallet.tsx +++ b/ui/address/contract/ContractConnectWallet.tsx @@ -1,47 +1,25 @@ import { Alert, Button, Flex } from '@chakra-ui/react'; -import { useWeb3Modal, useWeb3ModalState } from '@web3modal/wagmi/react'; import React from 'react'; -import { useAccount, useDisconnect } from 'wagmi'; import useIsMobile from 'lib/hooks/useIsMobile'; -import * as mixpanel from 'lib/mixpanel/index'; import AddressEntity from 'ui/shared/entities/address/AddressEntity'; +import useWallet from 'ui/snippets/walletMenu/useWallet'; const ContractConnectWallet = () => { - const { open } = useWeb3Modal(); - const { open: isOpen } = useWeb3ModalState(); - const { disconnect } = useDisconnect(); + const { isModalOpening, isModalOpen, connect, disconnect, address, isWalletConnected } = useWallet({ source: 'Smart contracts' }); const isMobile = useIsMobile(); - const [ isModalOpening, setIsModalOpening ] = React.useState(false); - - const handleConnect = React.useCallback(async() => { - setIsModalOpening(true); - await open(); - setIsModalOpening(false); - mixpanel.logEvent(mixpanel.EventTypes.WALLET_CONNECT, { Source: 'Smart contracts', Status: 'Started' }); - }, [ open ]); - - const handleAccountConnected = React.useCallback(({ isReconnected }: { isReconnected: boolean }) => { - !isReconnected && mixpanel.logEvent(mixpanel.EventTypes.WALLET_CONNECT, { Source: 'Smart contracts', Status: 'Connected' }); - }, []); - - const handleDisconnect = React.useCallback(() => { - disconnect(); - }, [ disconnect ]); - - const { address, isDisconnected } = useAccount({ onConnect: handleAccountConnected }); const content = (() => { - if (isDisconnected || !address) { + if (!isWalletConnected) { return ( <> Disconnected + ); })(); diff --git a/ui/address/contract/ContractMethodCallable.tsx b/ui/address/contract/ContractMethodCallable.tsx deleted file mode 100644 index 2009317ffc..0000000000 --- a/ui/address/contract/ContractMethodCallable.tsx +++ /dev/null @@ -1,187 +0,0 @@ -import { Box, Button, chakra, Flex } from '@chakra-ui/react'; -import React from 'react'; -import type { SubmitHandler } from 'react-hook-form'; -import { useForm, FormProvider } from 'react-hook-form'; - -import type { MethodFormFields, ContractMethodCallResult } from './types'; -import type { SmartContractMethodInput, SmartContractMethod } from 'types/api/contract'; - -import config from 'configs/app'; -import * as mixpanel from 'lib/mixpanel/index'; -import IconSvg from 'ui/shared/IconSvg'; - -import ContractMethodCallableRow from './ContractMethodCallableRow'; -import { formatFieldValues, transformFieldsToArgs } from './utils'; - -interface ResultComponentProps { - item: T; - result: ContractMethodCallResult; - onSettle: () => void; -} - -interface Props { - data: T; - onSubmit: (data: T, args: Array>) => Promise>; - resultComponent: (props: ResultComponentProps) => JSX.Element | null; - isWrite?: boolean; -} - -// groupName%groupIndex:inputName%inputIndex -const getFormFieldName = (input: { index: number; name: string }, group?: { index: number; name: string }) => - `${ group ? `${ group.name }%${ group.index }:` : '' }${ input.name || 'input' }%${ input.index }`; - -const ContractMethodCallable = ({ data, onSubmit, resultComponent: ResultComponent, isWrite }: Props) => { - - const [ result, setResult ] = React.useState>(); - const [ isLoading, setLoading ] = React.useState(false); - - const inputs: Array = React.useMemo(() => { - return [ - ...('inputs' in data ? data.inputs : []), - ...('stateMutability' in data && data.stateMutability === 'payable' ? [ { - name: `Send native ${ config.chain.currency.symbol }`, - type: 'uint256' as const, - internalType: 'uint256' as const, - fieldType: 'native_coin' as const, - } ] : []), - ]; - }, [ data ]); - - const formApi = useForm({ - mode: 'onBlur', - }); - - const handleTxSettle = React.useCallback(() => { - setLoading(false); - }, []); - - const handleFormChange = React.useCallback(() => { - result && setResult(undefined); - }, [ result ]); - - const onFormSubmit: SubmitHandler = React.useCallback(async(formData) => { - const formattedData = formatFieldValues(formData, inputs); - const args = transformFieldsToArgs(formattedData); - - setResult(undefined); - setLoading(true); - - onSubmit(data, args) - .then((result) => { - setResult(result); - }) - .catch((error) => { - setResult(error?.error || error?.data || (error?.reason && { message: error.reason }) || error); - setLoading(false); - }) - .finally(() => { - mixpanel.logEvent(mixpanel.EventTypes.CONTRACT_INTERACTION, { - 'Method type': isWrite ? 'Write' : 'Read', - 'Method name': 'name' in data ? data.name : 'Fallback', - }); - }); - }, [ inputs, onSubmit, data, isWrite ]); - - return ( - - - - - { inputs.map((input, index) => { - const fieldName = getFormFieldName({ name: input.name, index }); - - if (input.type === 'tuple' && input.components) { - return ( - - { index !== 0 && <>
} - - { input.name } ({ input.type }) - - { input.components.map((component, componentIndex) => { - const fieldName = getFormFieldName( - { name: component.name, index: componentIndex }, - { name: input.name, index }, - ); - - return ( - - ); - }) } - { index !== inputs.length - 1 && <>
} - - ); - } - - return ( - 1 } - onChange={ handleFormChange } - /> - ); - }) } - - - - - { 'outputs' in data && !isWrite && data.outputs.length > 0 && ( - - -

- { data.outputs.map(({ type, name }, index) => { - return ( - <> - { name } - { name ? `(${ type })` : type } - { index < data.outputs.length - 1 && , } - - ); - }) } -

-
- ) } - { result && } - - ); -}; - -export default React.memo(ContractMethodCallable) as typeof ContractMethodCallable; diff --git a/ui/address/contract/ContractMethodCallableRow.tsx b/ui/address/contract/ContractMethodCallableRow.tsx deleted file mode 100644 index 2cc01d7861..0000000000 --- a/ui/address/contract/ContractMethodCallableRow.tsx +++ /dev/null @@ -1,84 +0,0 @@ -import { Box, Flex, useColorModeValue } from '@chakra-ui/react'; -import React from 'react'; -import { useFormContext } from 'react-hook-form'; - -import type { MethodFormFields } from './types'; -import type { SmartContractMethodArgType, SmartContractMethodInput } from 'types/api/contract'; - -import ContractMethodField from './ContractMethodField'; -import ContractMethodFieldArray from './ContractMethodFieldArray'; -import { ARRAY_REGEXP } from './utils'; - -interface Props { - fieldName: string; - fieldType?: SmartContractMethodInput['fieldType']; - argName: string; - argType: SmartContractMethodArgType; - onChange: () => void; - isDisabled: boolean; - isGrouped?: boolean; - isOptional?: boolean; -} - -const ContractMethodCallableRow = ({ argName, fieldName, fieldType, argType, onChange, isDisabled, isGrouped, isOptional }: Props) => { - const { control, getValues, setValue } = useFormContext(); - const arrayTypeMatch = argType.match(ARRAY_REGEXP); - const nativeCoinFieldBgColor = useColorModeValue('blackAlpha.50', 'whiteAlpha.100'); - - const content = arrayTypeMatch ? ( - - ) : ( - - ); - - const isNativeCoinField = fieldType === 'native_coin'; - - return ( - - - { argName }{ isOptional ? '' : '*' } ({ argType }) - - { content } - - ); -}; - -export default React.memo(ContractMethodCallableRow); diff --git a/ui/address/contract/ContractMethodConstant.tsx b/ui/address/contract/ContractMethodConstant.tsx index 568f4b2ee5..016668e89b 100644 --- a/ui/address/contract/ContractMethodConstant.tsx +++ b/ui/address/contract/ContractMethodConstant.tsx @@ -2,11 +2,12 @@ import { Checkbox, Flex, chakra } from '@chakra-ui/react'; import BigNumber from 'bignumber.js'; import type { ChangeEvent } from 'react'; import React from 'react'; +import { getAddress } from 'viem'; import type { SmartContractMethodOutput } from 'types/api/contract'; -import config from 'configs/app'; import { WEI } from 'lib/consts'; +import { currencyUnits } from 'lib/units'; import AddressEntity from 'ui/shared/entities/address/AddressEntity'; function castValueToString(value: number | string | boolean | object | bigint | undefined): string { @@ -32,17 +33,17 @@ interface Props { const ContractMethodStatic = ({ data }: Props) => { const [ value, setValue ] = React.useState(castValueToString(data.value)); - const [ label, setLabel ] = React.useState('WEI'); + const [ label, setLabel ] = React.useState(currencyUnits.wei.toUpperCase()); const handleCheckboxChange = React.useCallback((event: ChangeEvent) => { const initialValue = castValueToString(data.value); if (event.target.checked) { setValue(BigNumber(initialValue).div(WEI).toFixed()); - setLabel(config.chain.currency.symbol || 'ETH'); + setLabel(currencyUnits.ether.toUpperCase()); } else { setValue(BigNumber(initialValue).toFixed()); - setLabel('WEI'); + setLabel(currencyUnits.wei.toUpperCase()); } }, [ data.value ]); @@ -50,7 +51,7 @@ const ContractMethodStatic = ({ data }: Props) => { if (typeof data.value === 'string' && data.type === 'address' && data.value) { return ( ); diff --git a/ui/address/contract/ContractMethodField.tsx b/ui/address/contract/ContractMethodField.tsx deleted file mode 100644 index 47837114f6..0000000000 --- a/ui/address/contract/ContractMethodField.tsx +++ /dev/null @@ -1,192 +0,0 @@ -import { - Box, - FormControl, - Input, - InputGroup, - InputRightElement, - useColorModeValue, -} from '@chakra-ui/react'; -import React from 'react'; -import type { Control, ControllerRenderProps, FieldError, UseFormGetValues, UseFormSetValue, UseFormStateReturn } from 'react-hook-form'; -import { Controller } from 'react-hook-form'; -import { NumericFormat } from 'react-number-format'; -import { isAddress, isHex, getAddress } from 'viem'; - -import type { MethodFormFields } from './types'; -import type { SmartContractMethodArgType } from 'types/api/contract'; - -import ClearButton from 'ui/shared/ClearButton'; - -import ContractMethodFieldZeroes from './ContractMethodFieldZeroes'; -import { INT_REGEXP, BYTES_REGEXP, getIntBoundaries, formatBooleanValue } from './utils'; - -interface Props { - name: string; - index?: number; - groupName?: string; - placeholder: string; - argType: SmartContractMethodArgType; - control: Control; - setValue: UseFormSetValue; - getValues: UseFormGetValues; - isDisabled: boolean; - isOptional?: boolean; - onChange: () => void; -} - -const ContractMethodField = ({ control, name, groupName, index, argType, placeholder, setValue, getValues, isDisabled, isOptional, onChange }: Props) => { - const ref = React.useRef(null); - const bgColor = useColorModeValue('white', 'black'); - - const handleClear = React.useCallback(() => { - setValue(name, ''); - onChange(); - ref.current?.focus(); - }, [ name, onChange, setValue ]); - - const handleAddZeroesClick = React.useCallback((power: number) => { - const value = groupName && index !== undefined ? getValues()[groupName][index] : getValues()[name]; - const zeroes = Array(power).fill('0').join(''); - const newValue = value ? value + zeroes : '1' + zeroes; - setValue(name, newValue); - onChange(); - }, [ getValues, groupName, index, name, onChange, setValue ]); - - const intMatch = React.useMemo(() => { - const match = argType.match(INT_REGEXP); - if (!match) { - return null; - } - - const [ , isUnsigned, power = '256' ] = match; - const [ min, max ] = getIntBoundaries(Number(power), Boolean(isUnsigned)); - - return { isUnsigned, power, min, max }; - }, [ argType ]); - - const bytesMatch = React.useMemo(() => { - return argType.match(BYTES_REGEXP); - }, [ argType ]); - - const renderInput = React.useCallback(( - { field, formState }: { field: ControllerRenderProps; formState: UseFormStateReturn }, - ) => { - const error: FieldError | undefined = index !== undefined && groupName !== undefined ? - (formState.errors[groupName] as unknown as Array)?.[index] : - formState.errors[name]; - - // show control for all inputs which allows to insert 10^18 or greater numbers - const hasZerosControl = intMatch && Number(intMatch.power) >= 64; - - return ( - - - - - - { typeof field.value === 'string' && field.value.replace('\n', '') && } - { hasZerosControl && } - - - - { error && { error.message } } - - ); - }, [ index, groupName, name, intMatch, isDisabled, isOptional, placeholder, bgColor, handleClear, handleAddZeroesClick ]); - - const validate = React.useCallback((_value: string | Array | undefined) => { - if (typeof _value === 'object' || !_value) { - return; - } - - const value = _value.replace('\n', ''); - - if (!value && !isOptional) { - return 'Field is required'; - } - - if (argType === 'address') { - if (!isAddress(value)) { - return 'Invalid address format'; - } - - // all lowercase addresses are valid - const isInLowerCase = value === value.toLowerCase(); - if (isInLowerCase) { - return true; - } - - // check if address checksum is valid - return getAddress(value) === value ? true : 'Invalid address checksum'; - } - - if (intMatch) { - const formattedValue = Number(value.replace(/\s/g, '')); - - if (Object.is(formattedValue, NaN)) { - return 'Invalid integer format'; - } - - if (formattedValue > intMatch.max || formattedValue < intMatch.min) { - const lowerBoundary = intMatch.isUnsigned ? '0' : `-1 * 2 ^ ${ Number(intMatch.power) / 2 }`; - const upperBoundary = intMatch.isUnsigned ? `2 ^ ${ intMatch.power } - 1` : `2 ^ ${ Number(intMatch.power) / 2 } - 1`; - return `Value should be in range from "${ lowerBoundary }" to "${ upperBoundary }" inclusively`; - } - - return true; - } - - if (argType === 'bool') { - const formattedValue = formatBooleanValue(value); - if (formattedValue === undefined) { - return 'Invalid boolean format. Allowed values: 0, 1, true, false'; - } - } - - if (bytesMatch) { - const [ , length ] = bytesMatch; - - if (!isHex(value)) { - return 'Invalid bytes format'; - } - - if (length) { - const valueLengthInBytes = value.replace('0x', '').length / 2; - return valueLengthInBytes !== Number(length) ? `Value should be ${ length } bytes in length` : true; - } - - return true; - } - - return true; - }, [ isOptional, argType, intMatch, bytesMatch ]); - - return ( - - ); -}; - -export default React.memo(ContractMethodField); diff --git a/ui/address/contract/ContractMethodFieldArray.tsx b/ui/address/contract/ContractMethodFieldArray.tsx deleted file mode 100644 index 35018b2f7f..0000000000 --- a/ui/address/contract/ContractMethodFieldArray.tsx +++ /dev/null @@ -1,106 +0,0 @@ -import { Flex, IconButton } from '@chakra-ui/react'; -import React from 'react'; -import type { Control, UseFormGetValues, UseFormSetValue } from 'react-hook-form'; -import { useFieldArray } from 'react-hook-form'; - -import type { MethodFormFields } from './types'; -import type { SmartContractMethodArgType } from 'types/api/contract'; - -import IconSvg from 'ui/shared/IconSvg'; - -import ContractMethodField from './ContractMethodField'; - -interface Props { - name: string; - size: number; - argType: SmartContractMethodArgType; - control: Control; - setValue: UseFormSetValue; - getValues: UseFormGetValues; - isDisabled: boolean; - onChange: () => void; -} - -const ContractMethodFieldArray = ({ control, name, setValue, getValues, isDisabled, argType, onChange, size }: Props) => { - const { fields, append, remove } = useFieldArray({ - name: name as never, - control, - }); - - React.useEffect(() => { - if (fields.length === 0) { - if (size === Infinity) { - append(''); - } else { - for (let i = 0; i < size - 1; i++) { - // a little hack to append multiple empty fields in the array - // had to adjust code in ContractMethodField as well - append('\n'); - } - } - } - - }, [ fields.length, append, size ]); - - const handleAddButtonClick = React.useCallback(() => { - append(''); - }, [ append ]); - - const handleRemoveButtonClick = React.useCallback((event: React.MouseEvent) => { - const itemIndex = event.currentTarget.getAttribute('data-index'); - if (itemIndex) { - remove(Number(itemIndex)); - } - }, [ remove ]); - - return ( - - { fields.map((field, index, array) => { - return ( - - - { array.length > 1 && size === Infinity && ( - } - isDisabled={ isDisabled } - /> - ) } - { index === array.length - 1 && size === Infinity && ( - } - isDisabled={ isDisabled } - /> - ) } - - ); - }) } - - ); -}; - -export default React.memo(ContractMethodFieldArray); diff --git a/ui/address/contract/ContractMethodsAccordionItem.tsx b/ui/address/contract/ContractMethodsAccordionItem.tsx index d69222cdad..b30c0d998b 100644 --- a/ui/address/contract/ContractMethodsAccordionItem.tsx +++ b/ui/address/contract/ContractMethodsAccordionItem.tsx @@ -45,50 +45,54 @@ const ContractMethodsAccordionItem = ({ data, ind return ( - - - { 'method_id' in data && ( - - - + { ({ isExpanded }) => ( + <> + + + { 'method_id' in data && ( + + + + + + ) } + + { index + 1 }. { data.type === 'fallback' || data.type === 'receive' ? data.type : data.name } - - ) } - - { index + 1 }. { data.type === 'fallback' || data.type === 'receive' ? data.type : data.name } - - { data.type === 'fallback' && ( - - ) } - { data.type === 'receive' && ( - + ) } + { data.type === 'receive' && ( + - ) } - - - - - { renderContent(data, index, id) } - + }/> + ) } + + + + + { renderContent(data, index, id) } + + + ) } ); }; diff --git a/ui/address/contract/ContractRead.pw.tsx b/ui/address/contract/ContractRead.pw.tsx index d540c1c07e..1dc215d070 100644 --- a/ui/address/contract/ContractRead.pw.tsx +++ b/ui/address/contract/ContractRead.pw.tsx @@ -37,7 +37,7 @@ test('base view +@mobile +@dark-mode', async({ mount, page }) => { await expect(component).toHaveScreenshot(); - await component.getByPlaceholder(/address/i).type('0xa113Ce24919C08a26C952E81681dAc861d6a2466'); + await component.getByPlaceholder(/address/i).fill('0xa113Ce24919C08a26C952E81681dAc861d6a2466'); await component.getByText(/read/i).click(); await component.getByText(/wei/i).click(); diff --git a/ui/address/contract/ContractRead.tsx b/ui/address/contract/ContractRead.tsx index 88330196ea..7a2fd94ded 100644 --- a/ui/address/contract/ContractRead.tsx +++ b/ui/address/contract/ContractRead.tsx @@ -14,9 +14,9 @@ import DataFetchAlert from 'ui/shared/DataFetchAlert'; import ContractConnectWallet from './ContractConnectWallet'; import ContractCustomAbiAlert from './ContractCustomAbiAlert'; import ContractImplementationAddress from './ContractImplementationAddress'; -import ContractMethodCallable from './ContractMethodCallable'; import ContractMethodConstant from './ContractMethodConstant'; import ContractReadResult from './ContractReadResult'; +import ContractMethodForm from './methodForm/ContractMethodForm'; import useWatchAccount from './useWatchAccount'; const ContractRead = () => { @@ -40,7 +40,7 @@ const ContractRead = () => { }, }); - const handleMethodFormSubmit = React.useCallback(async(item: SmartContractReadMethod, args: Array>) => { + const handleMethodFormSubmit = React.useCallback(async(item: SmartContractReadMethod, args: Array) => { return apiFetch<'contract_method_query', SmartContractQueryMethodRead>('contract_method_query', { pathParams: { hash: addressHash }, queryParams: { @@ -63,7 +63,7 @@ const ContractRead = () => { return { item.error }; } - if (item.outputs.some(({ value }) => value !== undefined && value !== null)) { + if (item.outputs?.some(({ value }) => value !== undefined && value !== null)) { return ( { item.outputs.map((output, index) => ) } @@ -72,11 +72,12 @@ const ContractRead = () => { } return ( - ); }, [ handleMethodFormSubmit ]); diff --git a/ui/address/contract/ContractSecurityAudits.tsx b/ui/address/contract/ContractSecurityAudits.tsx new file mode 100644 index 0000000000..cac7c356b0 --- /dev/null +++ b/ui/address/contract/ContractSecurityAudits.tsx @@ -0,0 +1,81 @@ +import { Box, Button, useDisclosure } from '@chakra-ui/react'; +import React from 'react'; + +import type { SmartContractSecurityAuditSubmission } from 'types/api/contract'; + +import useApiQuery from 'lib/api/useApiQuery'; +import dayjs from 'lib/date/dayjs'; +import ContainerWithScrollY from 'ui/shared/ContainerWithScrollY'; +import FormModal from 'ui/shared/FormModal'; +import LinkExternal from 'ui/shared/LinkExternal'; + +import ContractSubmitAuditForm from './contractSubmitAuditForm/ContractSubmitAuditForm'; + +const SCROLL_GRADIENT_HEIGHT = 24; + +type Props = { + addressHash?: string; +} + +const ContractSecurityAudits = ({ addressHash }: Props) => { + const { data, isPlaceholderData } = useApiQuery('contract_security_audits', { + pathParams: { hash: addressHash }, + queryOptions: { + refetchOnMount: false, + placeholderData: { items: [] }, + enabled: Boolean(addressHash), + }, + }); + + const containerRef = React.useRef(null); + const [ hasScroll, setHasScroll ] = React.useState(false); + + React.useEffect(() => { + if (!containerRef.current) { + return; + } + + setHasScroll(containerRef.current.scrollHeight >= containerRef.current.clientHeight + SCROLL_GRADIENT_HEIGHT / 2); + }, []); + + const formTitle = 'Submit audit'; + + const modalProps = useDisclosure(); + + const renderForm = React.useCallback(() => { + return ; + }, [ addressHash, modalProps.onClose ]); + + return ( + <> + + { data?.items && data.items.length > 0 && ( + + + { data.items.map(item => ( + + { `${ item.audit_company_name }, ${ dayjs(item.audit_publish_date).format('MMM DD, YYYY') }` } + + )) } + + + ) } + + isOpen={ modalProps.isOpen } + onClose={ modalProps.onClose } + title={ formTitle } + renderForm={ renderForm } + /> + + ); +}; + +export default React.memo(ContractSecurityAudits); diff --git a/ui/address/contract/ContractSourceCode.tsx b/ui/address/contract/ContractSourceCode.tsx index 1130b47a2b..d1ca1cba9f 100644 --- a/ui/address/contract/ContractSourceCode.tsx +++ b/ui/address/contract/ContractSourceCode.tsx @@ -13,6 +13,7 @@ import LinkInternal from 'ui/shared/LinkInternal'; import CodeEditor from 'ui/shared/monaco/CodeEditor'; import formatFilePath from 'ui/shared/monaco/utils/formatFilePath'; +import ContractCodeIdes from './ContractCodeIdes'; import ContractExternalLibraries from './ContractExternalLibraries'; const SOURCE_CODE_OPTIONS = [ @@ -118,6 +119,8 @@ const ContractSourceCode = ({ address, implementationAddress }: Props) => { : null; + const ides = sourceType === 'secondary' ? : ; + const handleSelectChange = React.useCallback((event: React.ChangeEvent) => { setSourceType(event.target.value as SourceCodeType); }, []); @@ -162,6 +165,7 @@ const ContractSourceCode = ({ address, implementationAddress }: Props) => { libraries={ primaryContractQuery.data?.external_libraries ?? undefined } language={ primaryContractQuery.data?.language ?? undefined } mainFile={ primaryEditorData[0]?.file_path } + contractName={ primaryContractQuery.data?.name || undefined } /> { secondaryEditorData && ( @@ -172,6 +176,7 @@ const ContractSourceCode = ({ address, implementationAddress }: Props) => { libraries={ secondaryContractQuery.data?.external_libraries ?? undefined } language={ secondaryContractQuery.data?.language ?? undefined } mainFile={ secondaryEditorData?.[0]?.file_path } + contractName={ secondaryContractQuery.data?.name || undefined } /> ) } @@ -190,6 +195,7 @@ const ContractSourceCode = ({ address, implementationAddress }: Props) => { { editorSourceTypeSelector } { externalLibraries } { diagramLink } + { ides } { copyToClipboard } { content } diff --git a/ui/address/contract/ContractWrite.tsx b/ui/address/contract/ContractWrite.tsx index b199b99291..1b282fb508 100644 --- a/ui/address/contract/ContractWrite.tsx +++ b/ui/address/contract/ContractWrite.tsx @@ -1,6 +1,6 @@ import { useRouter } from 'next/router'; import React from 'react'; -import { useAccount, useWalletClient, useNetwork, useSwitchNetwork } from 'wagmi'; +import { useAccount, useWalletClient, useSwitchChain } from 'wagmi'; import type { SmartContractWriteMethod } from 'types/api/contract'; @@ -14,16 +14,15 @@ import DataFetchAlert from 'ui/shared/DataFetchAlert'; import ContractConnectWallet from './ContractConnectWallet'; import ContractCustomAbiAlert from './ContractCustomAbiAlert'; import ContractImplementationAddress from './ContractImplementationAddress'; -import ContractMethodCallable from './ContractMethodCallable'; import ContractWriteResult from './ContractWriteResult'; +import ContractMethodForm from './methodForm/ContractMethodForm'; import useContractAbi from './useContractAbi'; import { getNativeCoinValue, prepareAbi } from './utils'; const ContractWrite = () => { const { data: walletClient } = useWalletClient(); - const { isConnected } = useAccount(); - const { chain } = useNetwork(); - const { switchNetworkAsync } = useSwitchNetwork(); + const { isConnected, chainId } = useAccount(); + const { switchChainAsync } = useSwitchChain(); const router = useRouter(); @@ -39,18 +38,19 @@ const ContractWrite = () => { }, queryOptions: { enabled: Boolean(addressHash), + refetchOnMount: false, }, }); const contractAbi = useContractAbi({ addressHash, isProxy, isCustomAbi }); - const handleMethodFormSubmit = React.useCallback(async(item: SmartContractWriteMethod, args: Array>) => { + const handleMethodFormSubmit = React.useCallback(async(item: SmartContractWriteMethod, args: Array) => { if (!isConnected) { throw new Error('Wallet is not connected'); } - if (chain?.id && String(chain.id) !== config.chain.id) { - await switchNetworkAsync?.(Number(config.chain.id)); + if (chainId && String(chainId) !== config.chain.id) { + await switchChainAsync?.({ chainId: Number(config.chain.id) }); } if (!contractAbi) { @@ -66,34 +66,35 @@ const ContractWrite = () => { return { hash }; } - const _args = 'stateMutability' in item && item.stateMutability === 'payable' ? args.slice(0, -1) : args; - const value = 'stateMutability' in item && item.stateMutability === 'payable' ? getNativeCoinValue(args[args.length - 1]) : undefined; const methodName = item.name; if (!methodName) { throw new Error('Method name is not defined'); } + const _args = args.slice(0, item.inputs.length); + const value = getNativeCoinValue(args[item.inputs.length]); const abi = prepareAbi(contractAbi, item); + const hash = await walletClient?.writeContract({ args: _args, abi, functionName: methodName, address: addressHash as `0x${ string }`, - value: value as undefined, + value, }); return { hash }; - }, [ isConnected, chain, contractAbi, walletClient, addressHash, switchNetworkAsync ]); + }, [ isConnected, chainId, contractAbi, walletClient, addressHash, switchChainAsync ]); const renderItemContent = React.useCallback((item: SmartContractWriteMethod, index: number, id: number) => { return ( - ); }, [ handleMethodFormSubmit ]); diff --git a/ui/address/contract/ContractWriteResult.tsx b/ui/address/contract/ContractWriteResult.tsx index 9fed0e271e..3cc755d131 100644 --- a/ui/address/contract/ContractWriteResult.tsx +++ b/ui/address/contract/ContractWriteResult.tsx @@ -1,22 +1,19 @@ import React from 'react'; -import { useWaitForTransaction } from 'wagmi'; +import { useWaitForTransactionReceipt } from 'wagmi'; +import type { ResultComponentProps } from './methodForm/types'; import type { ContractMethodWriteResult } from './types'; +import type { SmartContractWriteMethod } from 'types/api/contract'; import ContractWriteResultDumb from './ContractWriteResultDumb'; -interface Props { - result: ContractMethodWriteResult; - onSettle: () => void; -} - -const ContractWriteResult = ({ result, onSettle }: Props) => { +const ContractWriteResult = ({ result, onSettle }: ResultComponentProps) => { const txHash = result && 'hash' in result ? result.hash as `0x${ string }` : undefined; - const txInfo = useWaitForTransaction({ + const txInfo = useWaitForTransactionReceipt({ hash: txHash, }); - return ; + return ; }; -export default React.memo(ContractWriteResult); +export default React.memo(ContractWriteResult) as typeof ContractWriteResult; diff --git a/ui/address/contract/ContractWriteResultDumb.pw.tsx b/ui/address/contract/ContractWriteResultDumb.pw.tsx index e082542ff0..e916ab90c6 100644 --- a/ui/address/contract/ContractWriteResultDumb.pw.tsx +++ b/ui/address/contract/ContractWriteResultDumb.pw.tsx @@ -8,7 +8,7 @@ import ContractWriteResultDumb from './ContractWriteResultDumb'; test('loading', async({ mount }) => { const props = { txInfo: { - status: 'loading' as const, + status: 'pending' as const, error: null, }, result: { diff --git a/ui/address/contract/ContractWriteResultDumb.tsx b/ui/address/contract/ContractWriteResultDumb.tsx index 6e734f93a1..3b898fb570 100644 --- a/ui/address/contract/ContractWriteResultDumb.tsx +++ b/ui/address/contract/ContractWriteResultDumb.tsx @@ -11,7 +11,7 @@ interface Props { result: ContractMethodWriteResult; onSettle: () => void; txInfo: { - status: 'loading' | 'success' | 'error' | 'idle'; + status: 'loading' | 'success' | 'error' | 'idle' | 'pending'; error: Error | null; }; } @@ -20,7 +20,7 @@ const ContractWriteResultDumb = ({ result, onSettle, txInfo }: Props) => { const txHash = result && 'hash' in result ? result.hash : undefined; React.useEffect(() => { - if (txInfo.status !== 'loading') { + if (txInfo.status !== 'pending') { onSettle(); } }, [ onSettle, txInfo.status ]); @@ -55,7 +55,7 @@ const ContractWriteResultDumb = ({ result, onSettle, txInfo }: Props) => { ); } - case 'loading': { + case 'pending': { return ( <> diff --git a/ui/address/contract/__screenshots__/ContractCode.pw.tsx_dark-color-mode_full-view-mobile-dark-mode-1.png b/ui/address/contract/__screenshots__/ContractCode.pw.tsx_dark-color-mode_full-view-mobile-dark-mode-1.png index 83a535fb47..d4e7526c59 100644 Binary files a/ui/address/contract/__screenshots__/ContractCode.pw.tsx_dark-color-mode_full-view-mobile-dark-mode-1.png and b/ui/address/contract/__screenshots__/ContractCode.pw.tsx_dark-color-mode_full-view-mobile-dark-mode-1.png differ diff --git a/ui/address/contract/__screenshots__/ContractCode.pw.tsx_default_full-view-mobile-dark-mode-1.png b/ui/address/contract/__screenshots__/ContractCode.pw.tsx_default_full-view-mobile-dark-mode-1.png index feebd79abc..f11a52a153 100644 Binary files a/ui/address/contract/__screenshots__/ContractCode.pw.tsx_default_full-view-mobile-dark-mode-1.png and b/ui/address/contract/__screenshots__/ContractCode.pw.tsx_default_full-view-mobile-dark-mode-1.png differ diff --git a/ui/address/contract/__screenshots__/ContractCode.pw.tsx_default_verified-with-changed-byte-code-socket-1.png b/ui/address/contract/__screenshots__/ContractCode.pw.tsx_default_verified-with-changed-byte-code-socket-1.png index ebfb1ecda6..6c88ba36e0 100644 Binary files a/ui/address/contract/__screenshots__/ContractCode.pw.tsx_default_verified-with-changed-byte-code-socket-1.png and b/ui/address/contract/__screenshots__/ContractCode.pw.tsx_default_verified-with-changed-byte-code-socket-1.png differ diff --git a/ui/address/contract/__screenshots__/ContractCode.pw.tsx_default_verified-with-multiple-sources-1.png b/ui/address/contract/__screenshots__/ContractCode.pw.tsx_default_verified-with-multiple-sources-1.png index cf4254a29e..1fa16c4e84 100644 Binary files a/ui/address/contract/__screenshots__/ContractCode.pw.tsx_default_verified-with-multiple-sources-1.png and b/ui/address/contract/__screenshots__/ContractCode.pw.tsx_default_verified-with-multiple-sources-1.png differ diff --git a/ui/address/contract/__screenshots__/ContractCode.pw.tsx_default_verified-with-multiple-sources-2.png b/ui/address/contract/__screenshots__/ContractCode.pw.tsx_default_verified-with-multiple-sources-2.png index ebd69ae75f..1305877878 100644 Binary files a/ui/address/contract/__screenshots__/ContractCode.pw.tsx_default_verified-with-multiple-sources-2.png and b/ui/address/contract/__screenshots__/ContractCode.pw.tsx_default_verified-with-multiple-sources-2.png differ diff --git a/ui/address/contract/__screenshots__/ContractCode.pw.tsx_default_verified-with-multiple-sources-3.png b/ui/address/contract/__screenshots__/ContractCode.pw.tsx_default_verified-with-multiple-sources-3.png new file mode 100644 index 0000000000..ff7ca931ce Binary files /dev/null and b/ui/address/contract/__screenshots__/ContractCode.pw.tsx_default_verified-with-multiple-sources-3.png differ diff --git a/ui/address/contract/__screenshots__/ContractCode.pw.tsx_default_with-audits-feature-has-audits-1.png b/ui/address/contract/__screenshots__/ContractCode.pw.tsx_default_with-audits-feature-has-audits-1.png new file mode 100644 index 0000000000..191d800a72 Binary files /dev/null and b/ui/address/contract/__screenshots__/ContractCode.pw.tsx_default_with-audits-feature-has-audits-1.png differ diff --git a/ui/address/contract/__screenshots__/ContractCode.pw.tsx_default_with-audits-feature-no-audits-1.png b/ui/address/contract/__screenshots__/ContractCode.pw.tsx_default_with-audits-feature-no-audits-1.png new file mode 100644 index 0000000000..ab936fc8f6 Binary files /dev/null and b/ui/address/contract/__screenshots__/ContractCode.pw.tsx_default_with-audits-feature-no-audits-1.png differ diff --git a/ui/address/contract/__screenshots__/ContractCode.pw.tsx_mobile_full-view-mobile-dark-mode-1.png b/ui/address/contract/__screenshots__/ContractCode.pw.tsx_mobile_full-view-mobile-dark-mode-1.png index 132e7a0f89..539f531700 100644 Binary files a/ui/address/contract/__screenshots__/ContractCode.pw.tsx_mobile_full-view-mobile-dark-mode-1.png and b/ui/address/contract/__screenshots__/ContractCode.pw.tsx_mobile_full-view-mobile-dark-mode-1.png differ diff --git a/ui/address/contract/__screenshots__/ContractRead.pw.tsx_dark-color-mode_base-view-mobile-dark-mode-1.png b/ui/address/contract/__screenshots__/ContractRead.pw.tsx_dark-color-mode_base-view-mobile-dark-mode-1.png index 57d28fdef2..f19ba84553 100644 Binary files a/ui/address/contract/__screenshots__/ContractRead.pw.tsx_dark-color-mode_base-view-mobile-dark-mode-1.png and b/ui/address/contract/__screenshots__/ContractRead.pw.tsx_dark-color-mode_base-view-mobile-dark-mode-1.png differ diff --git a/ui/address/contract/__screenshots__/ContractRead.pw.tsx_dark-color-mode_base-view-mobile-dark-mode-2.png b/ui/address/contract/__screenshots__/ContractRead.pw.tsx_dark-color-mode_base-view-mobile-dark-mode-2.png index 89504d9220..9402cd11fc 100644 Binary files a/ui/address/contract/__screenshots__/ContractRead.pw.tsx_dark-color-mode_base-view-mobile-dark-mode-2.png and b/ui/address/contract/__screenshots__/ContractRead.pw.tsx_dark-color-mode_base-view-mobile-dark-mode-2.png differ diff --git a/ui/address/contract/__screenshots__/ContractRead.pw.tsx_default_base-view-mobile-dark-mode-1.png b/ui/address/contract/__screenshots__/ContractRead.pw.tsx_default_base-view-mobile-dark-mode-1.png index 17168a8434..82900c2f57 100644 Binary files a/ui/address/contract/__screenshots__/ContractRead.pw.tsx_default_base-view-mobile-dark-mode-1.png and b/ui/address/contract/__screenshots__/ContractRead.pw.tsx_default_base-view-mobile-dark-mode-1.png differ diff --git a/ui/address/contract/__screenshots__/ContractRead.pw.tsx_default_base-view-mobile-dark-mode-2.png b/ui/address/contract/__screenshots__/ContractRead.pw.tsx_default_base-view-mobile-dark-mode-2.png index 3a22929ac6..635406e6f5 100644 Binary files a/ui/address/contract/__screenshots__/ContractRead.pw.tsx_default_base-view-mobile-dark-mode-2.png and b/ui/address/contract/__screenshots__/ContractRead.pw.tsx_default_base-view-mobile-dark-mode-2.png differ diff --git a/ui/address/contract/__screenshots__/ContractRead.pw.tsx_mobile_base-view-mobile-dark-mode-1.png b/ui/address/contract/__screenshots__/ContractRead.pw.tsx_mobile_base-view-mobile-dark-mode-1.png index 0da704eaba..930ca02a9e 100644 Binary files a/ui/address/contract/__screenshots__/ContractRead.pw.tsx_mobile_base-view-mobile-dark-mode-1.png and b/ui/address/contract/__screenshots__/ContractRead.pw.tsx_mobile_base-view-mobile-dark-mode-1.png differ diff --git a/ui/address/contract/__screenshots__/ContractRead.pw.tsx_mobile_base-view-mobile-dark-mode-2.png b/ui/address/contract/__screenshots__/ContractRead.pw.tsx_mobile_base-view-mobile-dark-mode-2.png index 3a72b3d0b0..b5d534a0d0 100644 Binary files a/ui/address/contract/__screenshots__/ContractRead.pw.tsx_mobile_base-view-mobile-dark-mode-2.png and b/ui/address/contract/__screenshots__/ContractRead.pw.tsx_mobile_base-view-mobile-dark-mode-2.png differ diff --git a/ui/address/contract/__screenshots__/ContractWrite.pw.tsx_default_base-view-mobile-1.png b/ui/address/contract/__screenshots__/ContractWrite.pw.tsx_default_base-view-mobile-1.png index eee1a2a500..e58e9c4755 100644 Binary files a/ui/address/contract/__screenshots__/ContractWrite.pw.tsx_default_base-view-mobile-1.png and b/ui/address/contract/__screenshots__/ContractWrite.pw.tsx_default_base-view-mobile-1.png differ diff --git a/ui/address/contract/__screenshots__/ContractWrite.pw.tsx_mobile_base-view-mobile-1.png b/ui/address/contract/__screenshots__/ContractWrite.pw.tsx_mobile_base-view-mobile-1.png index 5724e9b234..c9a99a0bfb 100644 Binary files a/ui/address/contract/__screenshots__/ContractWrite.pw.tsx_mobile_base-view-mobile-1.png and b/ui/address/contract/__screenshots__/ContractWrite.pw.tsx_mobile_base-view-mobile-1.png differ diff --git a/ui/address/contract/contractSubmitAuditForm/ContractSubmitAuditForm.pw.tsx b/ui/address/contract/contractSubmitAuditForm/ContractSubmitAuditForm.pw.tsx new file mode 100644 index 0000000000..d82adea521 --- /dev/null +++ b/ui/address/contract/contractSubmitAuditForm/ContractSubmitAuditForm.pw.tsx @@ -0,0 +1,18 @@ +import { test, expect } from '@playwright/experimental-ct-react'; +import React from 'react'; + +import TestApp from 'playwright/TestApp'; + +import ContractSubmitAuditForm from './ContractSubmitAuditForm'; + +test('base view', async({ mount }) => { + + const component = await mount( + + { /* eslint-disable-next-line react/jsx-no-bind */ } + {} }/> + , + ); + + await expect(component).toHaveScreenshot(); +}); diff --git a/ui/address/contract/contractSubmitAuditForm/ContractSubmitAuditForm.tsx b/ui/address/contract/contractSubmitAuditForm/ContractSubmitAuditForm.tsx new file mode 100644 index 0000000000..b03d4884cc --- /dev/null +++ b/ui/address/contract/contractSubmitAuditForm/ContractSubmitAuditForm.tsx @@ -0,0 +1,124 @@ +import { Button, VStack } from '@chakra-ui/react'; +import React from 'react'; +import type { SubmitHandler } from 'react-hook-form'; +import { useForm } from 'react-hook-form'; + +import type { SmartContractSecurityAuditSubmission } from 'types/api/contract'; + +import type { ResourceError } from 'lib/api/resources'; +import useApiFetch from 'lib/api/useApiFetch'; +import useToast from 'lib/hooks/useToast'; + +import AuditComment from './fields/AuditComment'; +import AuditCompanyName from './fields/AuditCompanyName'; +import AuditProjectName from './fields/AuditProjectName'; +import AuditProjectUrl from './fields/AuditProjectUrl'; +import AuditReportDate from './fields/AuditReportDate'; +import AuditReportUrl from './fields/AuditReportUrl'; +import AuditSubmitterEmail from './fields/AuditSubmitterEmail'; +import AuditSubmitterIsOwner from './fields/AuditSubmitterIsOwner'; +import AuditSubmitterName from './fields/AuditSubmitterName'; + +interface Props { + address?: string; + onSuccess: () => void; +} + +export type Inputs = { + submitter_name: string; + submitter_email: string; + is_project_owner: boolean; + project_name: string; + project_url: string; + audit_company_name: string; + audit_report_url: string; + audit_publish_date: string; + comment?: string; +} + +type AuditSubmissionErrors = { + errors: Record>; +} + +const ContractSubmitAuditForm = ({ address, onSuccess }: Props) => { + const containerRef = React.useRef(null); + + const apiFetch = useApiFetch(); + const toast = useToast(); + + const { handleSubmit, formState, control, setError } = useForm({ + mode: 'onTouched', + defaultValues: { is_project_owner: false }, + }); + + const onFormSubmit: SubmitHandler = React.useCallback(async(data) => { + try { + await apiFetch<'contract_security_audits', SmartContractSecurityAuditSubmission, AuditSubmissionErrors>('contract_security_audits', { + pathParams: { hash: address }, + fetchParams: { + method: 'POST', + body: data, + }, + }); + + toast({ + position: 'top-right', + title: 'Success', + description: 'Your audit report has been successfully submitted for review', + status: 'success', + variant: 'subtle', + isClosable: true, + }); + + onSuccess(); + + } catch (_error) { + const error = _error as ResourceError; + // add scroll to the error field + const errorMap = error?.payload?.errors; + if (errorMap && Object.keys(errorMap).length) { + (Object.keys(errorMap) as Array).forEach((errorField) => { + setError(errorField, { type: 'custom', message: errorMap[errorField].join(', ') }); + }); + } else { + toast({ + position: 'top-right', + title: 'Error', + description: (_error as ResourceError<{ message: string }>)?.payload?.message || 'Something went wrong. Try again later.', + status: 'error', + variant: 'subtle', + isClosable: true, + }); + } + } + }, [ apiFetch, address, toast, setError, onSuccess ]); + + return ( +
+ + + + + + + + + + + + + +
+ ); +}; + +export default React.memo(ContractSubmitAuditForm); diff --git a/ui/address/contract/contractSubmitAuditForm/__screenshots__/ContractSubmitAuditForm.pw.tsx_default_base-view-1.png b/ui/address/contract/contractSubmitAuditForm/__screenshots__/ContractSubmitAuditForm.pw.tsx_default_base-view-1.png new file mode 100644 index 0000000000..c5a18bad26 Binary files /dev/null and b/ui/address/contract/contractSubmitAuditForm/__screenshots__/ContractSubmitAuditForm.pw.tsx_default_base-view-1.png differ diff --git a/ui/address/contract/contractSubmitAuditForm/fields/AuditComment.tsx b/ui/address/contract/contractSubmitAuditForm/fields/AuditComment.tsx new file mode 100644 index 0000000000..918a496dea --- /dev/null +++ b/ui/address/contract/contractSubmitAuditForm/fields/AuditComment.tsx @@ -0,0 +1,40 @@ +import { FormControl, Textarea } from '@chakra-ui/react'; +import React from 'react'; +import type { Control, ControllerProps } from 'react-hook-form'; +import { Controller } from 'react-hook-form'; + +import InputPlaceholder from 'ui/shared/InputPlaceholder'; + +import type { Inputs } from '../ContractSubmitAuditForm'; + +interface Props { + control: Control; +} + +const AuditComment = ({ control }: Props) => { + const renderControl: ControllerProps['render'] = React.useCallback(({ field, fieldState }) => { + return ( + +