Commit 2a76c7cf authored by Max Alekseenko's avatar Max Alekseenko

Merge branch 'main' into ens-in-wallet-menu

parents 1ec77495 0684b062
name: Publish Chakra theme package to NPM
on:
workflow_dispatch:
inputs:
version:
description: Package version
type: string
required: true
workflow_call:
inputs:
version:
description: Package version
type: string
required: true
jobs:
publish:
runs-on: ubuntu-latest
name: Publish package to NPM registry
permissions:
id-token: write
steps:
- name: Checkout repo
uses: actions/checkout@v4
# Also it will setup .npmrc file to publish to npm
- name: Setup node
uses: actions/setup-node@v4
with:
node-version: '20.x'
registry-url: 'https://registry.npmjs.org'
- name: Update package version
run: |
cd ./theme
npm version ${{ inputs.version }}
- name: Build the package
run: |
cd ./theme
yarn
yarn build
- name: Publish to NPM registry
run: |
cd ./theme
npm publish --provenance --access public
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
...@@ -11,6 +11,7 @@ on: ...@@ -11,6 +11,7 @@ on:
options: options:
- none - none
- arbitrum - arbitrum
- arbitrum_nova
- base - base
- celo_alfajores - celo_alfajores
- garnet - garnet
......
...@@ -11,6 +11,7 @@ on: ...@@ -11,6 +11,7 @@ on:
options: options:
- none - none
- arbitrum - arbitrum
- arbitrum_nova
- base - base
- celo_alfajores - celo_alfajores
- garnet - garnet
......
import type { RollupType } from 'types/client/rollup';
import type { NetworkVerificationType, NetworkVerificationTypeEnvs } from 'types/networks';
import { getEnvValue } from './utils'; import { getEnvValue } from './utils';
const DEFAULT_CURRENCY_DECIMALS = 18; const DEFAULT_CURRENCY_DECIMALS = 18;
const rollupType = getEnvValue('NEXT_PUBLIC_ROLLUP_TYPE') as RollupType;
const verificationType: NetworkVerificationType = (() => {
if (rollupType === 'arbitrum') {
return 'posting';
}
if (rollupType === 'zkEvm') {
return 'sequencing';
}
return getEnvValue('NEXT_PUBLIC_NETWORK_VERIFICATION_TYPE') as NetworkVerificationTypeEnvs || 'mining';
})();
const chain = Object.freeze({ const chain = Object.freeze({
id: getEnvValue('NEXT_PUBLIC_NETWORK_ID'), id: getEnvValue('NEXT_PUBLIC_NETWORK_ID'),
name: getEnvValue('NEXT_PUBLIC_NETWORK_NAME'), name: getEnvValue('NEXT_PUBLIC_NETWORK_NAME'),
...@@ -19,7 +34,7 @@ const chain = Object.freeze({ ...@@ -19,7 +34,7 @@ const chain = Object.freeze({
tokenStandard: getEnvValue('NEXT_PUBLIC_NETWORK_TOKEN_STANDARD_NAME') || 'ERC', tokenStandard: getEnvValue('NEXT_PUBLIC_NETWORK_TOKEN_STANDARD_NAME') || 'ERC',
rpcUrl: getEnvValue('NEXT_PUBLIC_NETWORK_RPC_URL'), rpcUrl: getEnvValue('NEXT_PUBLIC_NETWORK_RPC_URL'),
isTestnet: getEnvValue('NEXT_PUBLIC_IS_TESTNET') === 'true', isTestnet: getEnvValue('NEXT_PUBLIC_IS_TESTNET') === 'true',
verificationType: getEnvValue('NEXT_PUBLIC_NETWORK_VERIFICATION_TYPE') || 'mining', verificationType,
}); });
export default chain; export default chain;
...@@ -5,7 +5,7 @@ export const replaceQuotes = (value: string | undefined) => value?.replaceAll('\ ...@@ -5,7 +5,7 @@ export const replaceQuotes = (value: string | undefined) => value?.replaceAll('\
export const getEnvValue = (envName: string) => { export const getEnvValue = (envName: string) => {
// eslint-disable-next-line no-restricted-properties // eslint-disable-next-line no-restricted-properties
const envs = isBrowser() ? window.__envs : process.env; const envs = (isBrowser() ? window.__envs : process.env) ?? {};
if (isBrowser() && envs.NEXT_PUBLIC_APP_INSTANCE === 'pw') { if (isBrowser() && envs.NEXT_PUBLIC_APP_INSTANCE === 'pw') {
const storageValue = localStorage.getItem(envName); const storageValue = localStorage.getItem(envName);
......
# Set of ENVs for Arbitrum One network explorer
# https://arbitrum.blockscout.com
# This is an auto-generated file. To update all values, run "yarn preset:sync --name=arbitrum"
# Local ENVs
NEXT_PUBLIC_APP_PROTOCOL=http
NEXT_PUBLIC_APP_HOST=localhost
NEXT_PUBLIC_APP_PORT=3000
NEXT_PUBLIC_APP_ENV=development
NEXT_PUBLIC_API_WEBSOCKET_PROTOCOL=ws
# Instance ENVs
NEXT_PUBLIC_ADMIN_SERVICE_API_HOST=https://admin-rs.services.blockscout.com
NEXT_PUBLIC_API_BASE_PATH=/
NEXT_PUBLIC_API_HOST=arbitrum-nova.blockscout.com
NEXT_PUBLIC_API_SPEC_URL=https://raw.githubusercontent.com/blockscout/blockscout-api-v2-swagger/main/swagger.yaml
NEXT_PUBLIC_CONTRACT_CODE_IDES=[{'title':'Remix IDE','url':'https://remix.ethereum.org/?address={hash}&blockscout={domain}','icon_url':'https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/ide-icons/remix.png'}]
NEXT_PUBLIC_CONTRACT_INFO_API_HOST=https://contracts-info.services.blockscout.com
NEXT_PUBLIC_GRAPHIQL_TRANSACTION=0x37c798810d49ba132b40efe7f4fdf6806a8fc58226bb5e185ddc91f896577abf
NEXT_PUBLIC_HOMEPAGE_CHARTS=['daily_txs']
NEXT_PUBLIC_HOMEPAGE_PLATE_BACKGROUND=rgba(27, 74, 221, 1)
NEXT_PUBLIC_IS_ACCOUNT_SUPPORTED=true
NEXT_PUBLIC_LOGOUT_URL=https://blockscout-arbitrum.us.auth0.com/v2/logout
NEXT_PUBLIC_MARKETPLACE_ENABLED=false
NEXT_PUBLIC_METADATA_SERVICE_API_HOST=https://metadata.services.blockscout.com
NEXT_PUBLIC_NETWORK_CURRENCY_DECIMALS=18
NEXT_PUBLIC_NETWORK_CURRENCY_NAME=ETH
NEXT_PUBLIC_NETWORK_CURRENCY_SYMBOL=ETH
NEXT_PUBLIC_NETWORK_EXPLORERS=[{'title':'GeckoTerminal','logo':'https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/explorer-logos/geckoterminal.png','baseUrl':'https://www.geckoterminal.com/','paths':{'token':'/arbitrum/pools'}}]
NEXT_PUBLIC_NETWORK_ICON=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/network-icons/arbitrum-nova-icon.svg
NEXT_PUBLIC_NETWORK_ID=42170
NEXT_PUBLIC_NETWORK_LOGO=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/network-icons/arbitrum-nova.svg
NEXT_PUBLIC_NETWORK_NAME=Arbitrum Nova
NEXT_PUBLIC_NETWORK_RPC_URL=https://arbitrum.llamarpc.com
NEXT_PUBLIC_NETWORK_SHORT_NAME=Arbitrum Nova
NEXT_PUBLIC_OG_ENHANCED_DATA_ENABLED=true
NEXT_PUBLIC_OG_IMAGE_URL=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/og-images/arbitrum-nova.png
NEXT_PUBLIC_ROLLUP_L1_BASE_URL=https://eth.blockscout.com
NEXT_PUBLIC_ROLLUP_TYPE=arbitrum
NEXT_PUBLIC_TRANSACTION_INTERPRETATION_PROVIDER=blockscout
NEXT_PUBLIC_VIEWS_CONTRACT_SOLIDITYSCAN_ENABLED=true
NEXT_PUBLIC_VISUALIZE_API_HOST=https://visualizer.services.blockscout.com
\ No newline at end of file
...@@ -10,6 +10,8 @@ NEXT_PUBLIC_APP_ENV=development ...@@ -10,6 +10,8 @@ NEXT_PUBLIC_APP_ENV=development
NEXT_PUBLIC_API_WEBSOCKET_PROTOCOL=ws NEXT_PUBLIC_API_WEBSOCKET_PROTOCOL=ws
# Instance ENVs # Instance ENVs
NEXT_PUBLIC_VIEWS_CONTRACT_EXTRA_VERIFICATION_METHODS=none
NEXT_PUBLIC_ADMIN_SERVICE_API_HOST=https://admin-rs.services.blockscout.com NEXT_PUBLIC_ADMIN_SERVICE_API_HOST=https://admin-rs.services.blockscout.com
NEXT_PUBLIC_API_BASE_PATH=/ NEXT_PUBLIC_API_BASE_PATH=/
NEXT_PUBLIC_API_HOST=zksync.blockscout.com NEXT_PUBLIC_API_HOST=zksync.blockscout.com
......
...@@ -33,7 +33,7 @@ releases: ...@@ -33,7 +33,7 @@ releases:
type: kubernetes.io/dockerconfigjson type: kubernetes.io/dockerconfigjson
- name: bs-stack - name: bs-stack
chart: blockscout/blockscout-stack chart: blockscout/blockscout-stack
version: 1.2.* version: 1.*.*
namespace: review-l2-{{ requiredEnv "GITHUB_REF_NAME_SLUG" }} namespace: review-l2-{{ requiredEnv "GITHUB_REF_NAME_SLUG" }}
labels: labels:
app: review-l2-{{ requiredEnv "GITHUB_REF_NAME_SLUG" }} app: review-l2-{{ requiredEnv "GITHUB_REF_NAME_SLUG" }}
...@@ -58,7 +58,7 @@ releases: ...@@ -58,7 +58,7 @@ releases:
type: kubernetes.io/dockerconfigjson type: kubernetes.io/dockerconfigjson
- name: bs-stack - name: bs-stack
chart: blockscout/blockscout-stack chart: blockscout/blockscout-stack
version: 1.2.* version: 1.*.*
namespace: review-{{ requiredEnv "GITHUB_REF_NAME_SLUG" }} namespace: review-{{ requiredEnv "GITHUB_REF_NAME_SLUG" }}
labels: labels:
app: review-{{ requiredEnv "GITHUB_REF_NAME_SLUG" }} app: review-{{ requiredEnv "GITHUB_REF_NAME_SLUG" }}
......
...@@ -31,7 +31,7 @@ import { SUPPORTED_WALLETS } from '../../../types/client/wallets'; ...@@ -31,7 +31,7 @@ import { SUPPORTED_WALLETS } from '../../../types/client/wallets';
import type { CustomLink, CustomLinksGroup } from '../../../types/footerLinks'; import type { CustomLink, CustomLinksGroup } from '../../../types/footerLinks';
import { CHAIN_INDICATOR_IDS } from '../../../types/homepage'; import { CHAIN_INDICATOR_IDS } from '../../../types/homepage';
import type { ChainIndicatorId } from '../../../types/homepage'; import type { ChainIndicatorId } from '../../../types/homepage';
import { type NetworkVerificationType, type NetworkExplorer, type FeaturedNetwork, NETWORK_GROUPS } from '../../../types/networks'; import { type NetworkVerificationTypeEnvs, type NetworkExplorer, type FeaturedNetwork, NETWORK_GROUPS } from '../../../types/networks';
import { COLOR_THEME_IDS } from '../../../types/settings'; import { COLOR_THEME_IDS } from '../../../types/settings';
import type { AddressViewId } from '../../../types/views/address'; import type { AddressViewId } from '../../../types/views/address';
import { ADDRESS_VIEWS_IDS, IDENTICON_TYPES } from '../../../types/views/address'; import { ADDRESS_VIEWS_IDS, IDENTICON_TYPES } from '../../../types/views/address';
...@@ -510,7 +510,17 @@ const schema = yup ...@@ -510,7 +510,17 @@ const schema = yup
NEXT_PUBLIC_NETWORK_CURRENCY_DECIMALS: yup.number().integer().positive(), NEXT_PUBLIC_NETWORK_CURRENCY_DECIMALS: yup.number().integer().positive(),
NEXT_PUBLIC_NETWORK_SECONDARY_COIN_SYMBOL: yup.string(), NEXT_PUBLIC_NETWORK_SECONDARY_COIN_SYMBOL: yup.string(),
NEXT_PUBLIC_NETWORK_MULTIPLE_GAS_CURRENCIES: yup.boolean(), NEXT_PUBLIC_NETWORK_MULTIPLE_GAS_CURRENCIES: yup.boolean(),
NEXT_PUBLIC_NETWORK_VERIFICATION_TYPE: yup.string<NetworkVerificationType>().oneOf([ 'validation', 'mining' ]), NEXT_PUBLIC_NETWORK_VERIFICATION_TYPE: yup
.string<NetworkVerificationTypeEnvs>().oneOf([ 'validation', 'mining' ])
.when('NEXT_PUBLIC_ROLLUP_TYPE', {
is: (value: string) => value === 'arbitrum' || value === 'zkEvm',
then: (schema) => schema.test(
'not-exist',
'NEXT_PUBLIC_NETWORK_VERIFICATION_TYPE can not be set for Arbitrum and ZkEVM rollups',
value => value === undefined,
),
otherwise: (schema) => schema,
}),
NEXT_PUBLIC_NETWORK_TOKEN_STANDARD_NAME: yup.string(), NEXT_PUBLIC_NETWORK_TOKEN_STANDARD_NAME: yup.string(),
NEXT_PUBLIC_IS_TESTNET: yup.boolean(), NEXT_PUBLIC_IS_TESTNET: yup.boolean(),
......
...@@ -49,7 +49,6 @@ frontend: ...@@ -49,7 +49,6 @@ frontend:
env: env:
NEXT_PUBLIC_APP_ENV: development NEXT_PUBLIC_APP_ENV: development
NEXT_PUBLIC_APP_INSTANCE: review_L2 NEXT_PUBLIC_APP_INSTANCE: review_L2
NEXT_PUBLIC_NETWORK_VERIFICATION_TYPE: validation
NEXT_PUBLIC_NETWORK_LOGO: https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/network-logos/base.svg NEXT_PUBLIC_NETWORK_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_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-mainnet.json NEXT_PUBLIC_FEATURED_NETWORKS: https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/featured-networks/base-mainnet.json
......
...@@ -49,7 +49,6 @@ frontend: ...@@ -49,7 +49,6 @@ frontend:
env: env:
NEXT_PUBLIC_APP_ENV: development NEXT_PUBLIC_APP_ENV: development
NEXT_PUBLIC_APP_INSTANCE: review NEXT_PUBLIC_APP_INSTANCE: review
NEXT_PUBLIC_NETWORK_VERIFICATION_TYPE: validation
NEXT_PUBLIC_FEATURED_NETWORKS: https://raw.githubusercontent.com/blockscout/frontend-configs/dev/configs/featured-networks/eth-sepolia.json NEXT_PUBLIC_FEATURED_NETWORKS: https://raw.githubusercontent.com/blockscout/frontend-configs/dev/configs/featured-networks/eth-sepolia.json
NEXT_PUBLIC_NETWORK_LOGO: https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/network-logos/sepolia.svg NEXT_PUBLIC_NETWORK_LOGO: https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/network-logos/sepolia.svg
NEXT_PUBLIC_NETWORK_ICON: https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/network-icons/sepolia.png NEXT_PUBLIC_NETWORK_ICON: https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/network-icons/sepolia.png
...@@ -61,18 +60,10 @@ frontend: ...@@ -61,18 +60,10 @@ frontend:
NEXT_PUBLIC_NAME_SERVICE_API_HOST: https://bens-rs-test.k8s-dev.blockscout.com NEXT_PUBLIC_NAME_SERVICE_API_HOST: https://bens-rs-test.k8s-dev.blockscout.com
NEXT_PUBLIC_METADATA_SERVICE_API_HOST: https://metadata-test.k8s-dev.blockscout.com NEXT_PUBLIC_METADATA_SERVICE_API_HOST: https://metadata-test.k8s-dev.blockscout.com
NEXT_PUBLIC_AUTH_URL: https://blockscout-main.k8s-dev.blockscout.com NEXT_PUBLIC_AUTH_URL: https://blockscout-main.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_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_LOGOUT_URL: https://blockscoutcom.us.auth0.com/v2/logout
NEXT_PUBLIC_HOMEPAGE_CHARTS: "['daily_txs','coin_price','market_cap']" NEXT_PUBLIC_HOMEPAGE_CHARTS: "['daily_txs','coin_price','market_cap']"
NEXT_PUBLIC_NETWORK_RPC_URL: https://eth-sepolia.public.blastapi.io NEXT_PUBLIC_NETWORK_RPC_URL: https://eth-sepolia.public.blastapi.io
NEXT_PUBLIC_NETWORK_EXPLORERS: "[{'title':'Bitquery','baseUrl':'https://explorer.bitquery.io/','paths':{'tx':'/goerli/tx','address':'/goerli/address','token':'/goerli/token','block':'/goerli/block'}},{'title':'Etherscan','logo':'https://github.com/blockscout/frontend-configs/blob/main/configs/explorer-logos/etherscan.png?raw=true','baseUrl':'https://goerli.etherscan.io/','paths':{'tx':'/tx','address':'/address','token':'/token','block':'/block'}}]" NEXT_PUBLIC_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_MARKETPLACE_FEATURED_APP: zkbob-wallet
NEXT_PUBLIC_MARKETPLACE_BANNER_CONTENT_URL: https://gist.githubusercontent.com/maxaleks/36f779fd7d74877b57ec7a25a9a3a6c9/raw/746a8a59454c0537235ee44616c4690ce3bbf3c8/banner.html
NEXT_PUBLIC_MARKETPLACE_BANNER_LINK_URL: https://www.basename.app
NEXT_PUBLIC_GRAPHIQL_TRANSACTION: 0xf7d4972356e6ae44ae948d0cf19ef2beaf0e574c180997e969a2837da15e349d NEXT_PUBLIC_GRAPHIQL_TRANSACTION: 0xf7d4972356e6ae44ae948d0cf19ef2beaf0e574c180997e969a2837da15e349d
NEXT_PUBLIC_WEB3_WALLETS: "['token_pocket','coinbase','metamask']" NEXT_PUBLIC_WEB3_WALLETS: "['token_pocket','coinbase','metamask']"
NEXT_PUBLIC_VIEWS_ADDRESS_IDENTICON_TYPE: gradient_avatar NEXT_PUBLIC_VIEWS_ADDRESS_IDENTICON_TYPE: gradient_avatar
...@@ -86,6 +77,7 @@ frontend: ...@@ -86,6 +77,7 @@ frontend:
NEXT_PUBLIC_AD_BANNER_PROVIDER: slise NEXT_PUBLIC_AD_BANNER_PROVIDER: slise
NEXT_PUBLIC_DATA_AVAILABILITY_ENABLED: true NEXT_PUBLIC_DATA_AVAILABILITY_ENABLED: true
NEXT_PUBLIC_NAVIGATION_HIGHLIGHTED_ROUTES: "['/apps']" NEXT_PUBLIC_NAVIGATION_HIGHLIGHTED_ROUTES: "['/apps']"
PROMETHEUS_METRICS_ENABLED: true
envFromSecret: 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 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 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
...@@ -96,4 +88,3 @@ frontend: ...@@ -96,4 +88,3 @@ frontend:
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 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_GROWTH_BOOK_CLIENT_KEY: ref+vault://deployment-values/blockscout/dev/review?token_env=VAULT_TOKEN&address=https://vault.k8s.blockscout.com#/NEXT_PUBLIC_GROWTH_BOOK_CLIENT_KEY
NEXT_PUBLIC_MIXPANEL_PROJECT_TOKEN: ref+vault://deployment-values/blockscout/common?token_env=VAULT_TOKEN&address=https://vault.k8s.blockscout.com#/NEXT_PUBLIC_MIXPANEL_PROJECT_TOKEN NEXT_PUBLIC_MIXPANEL_PROJECT_TOKEN: ref+vault://deployment-values/blockscout/common?token_env=VAULT_TOKEN&address=https://vault.k8s.blockscout.com#/NEXT_PUBLIC_MIXPANEL_PROJECT_TOKEN
NEXT_PUBLIC_MARKETPLACE_RATING_AIRTABLE_API_KEY: ref+vault://deployment-values/blockscout/dev/common?token_env=VAULT_TOKEN&address=https://vault.k8s.blockscout.com#/NEXT_PUBLIC_MARKETPLACE_RATING_AIRTABLE_API_KEY
...@@ -93,7 +93,7 @@ Please be aware that all environment variables prefixed with `NEXT_PUBLIC_` will ...@@ -93,7 +93,7 @@ Please be aware that all environment variables prefixed with `NEXT_PUBLIC_` will
| NEXT_PUBLIC_NETWORK_CURRENCY_DECIMALS | `string` | Network currency decimals | - | `18` | `6` | v1.0.x+ | | NEXT_PUBLIC_NETWORK_CURRENCY_DECIMALS | `string` | Network currency decimals | - | `18` | `6` | v1.0.x+ |
| NEXT_PUBLIC_NETWORK_SECONDARY_COIN_SYMBOL | `string` | Network secondary coin symbol. | - | - | `GNO` | v1.29.0+ | | NEXT_PUBLIC_NETWORK_SECONDARY_COIN_SYMBOL | `string` | Network secondary coin symbol. | - | - | `GNO` | v1.29.0+ |
| NEXT_PUBLIC_NETWORK_MULTIPLE_GAS_CURRENCIES | `boolean` | Set to `true` for networks where users can pay transaction fees in either the native coin or ERC-20 tokens. | - | `false` | `true` | v1.33.0+ | | NEXT_PUBLIC_NETWORK_MULTIPLE_GAS_CURRENCIES | `boolean` | Set to `true` for networks where users can pay transaction fees in either the native coin or ERC-20 tokens. | - | `false` | `true` | v1.33.0+ |
| NEXT_PUBLIC_NETWORK_VERIFICATION_TYPE | `validation` or `mining` | Verification type in the network | - | `mining` | `validation` | v1.0.x+ | | NEXT_PUBLIC_NETWORK_VERIFICATION_TYPE | `validation` | `mining` | Verification type in the network. Irrelevant for Arbitrum (verification type is always `posting`) and ZkEvm (verification type is always `sequencing`) L2s | - | `mining` | `validation` | v1.0.x+ |
| NEXT_PUBLIC_NETWORK_TOKEN_STANDARD_NAME | `string` | Name of the standard for creating tokens | - | `ERC` | `BEP` | v1.31.0+ | | NEXT_PUBLIC_NETWORK_TOKEN_STANDARD_NAME | `string` | Name of the standard for creating tokens | - | `ERC` | `BEP` | v1.31.0+ |
| NEXT_PUBLIC_IS_TESTNET | `boolean`| Set to true if network is testnet | - | `false` | `true` | v1.0.x+ | | NEXT_PUBLIC_IS_TESTNET | `boolean`| Set to true if network is testnet | - | `false` | `true` | v1.0.x+ |
......
...@@ -8,7 +8,7 @@ import React from 'react'; ...@@ -8,7 +8,7 @@ import React from 'react';
import { AppContextProvider } from 'lib/contexts/app'; import { AppContextProvider } from 'lib/contexts/app';
import { ScrollDirectionProvider } from 'lib/contexts/scrollDirection'; import { ScrollDirectionProvider } from 'lib/contexts/scrollDirection';
import { SocketProvider } from 'lib/socket/context'; import { SocketProvider } from 'lib/socket/context';
import theme from 'theme'; import theme from 'theme/theme';
import 'lib/setLocale'; import 'lib/setLocale';
......
...@@ -10,11 +10,12 @@ export default function buildUrl<R extends ResourceName>( ...@@ -10,11 +10,12 @@ export default function buildUrl<R extends ResourceName>(
resourceName: R, resourceName: R,
pathParams?: ResourcePathParams<R>, pathParams?: ResourcePathParams<R>,
queryParams?: Record<string, string | Array<string> | number | boolean | null | undefined>, queryParams?: Record<string, string | Array<string> | number | boolean | null | undefined>,
noProxy?: boolean,
): string { ): string {
const resource: ApiResource = RESOURCES[resourceName]; const resource: ApiResource = RESOURCES[resourceName];
const baseUrl = isNeedProxy() ? config.app.baseUrl : (resource.endpoint || config.api.endpoint); const baseUrl = !noProxy && isNeedProxy() ? config.app.baseUrl : (resource.endpoint || config.api.endpoint);
const basePath = resource.basePath !== undefined ? resource.basePath : config.api.basePath; const basePath = resource.basePath !== undefined ? resource.basePath : config.api.basePath;
const path = isNeedProxy() ? '/node-api/proxy' + basePath + resource.path : basePath + resource.path; const path = !noProxy && isNeedProxy() ? '/node-api/proxy' + basePath + resource.path : basePath + resource.path;
const url = new URL(compile(path)(pathParams), baseUrl); const url = new URL(compile(path)(pathParams), baseUrl);
queryParams && Object.entries(queryParams).forEach(([ key, value ]) => { queryParams && Object.entries(queryParams).forEach(([ key, value ]) => {
......
...@@ -43,10 +43,12 @@ import type { AddressesResponse } from 'types/api/addresses'; ...@@ -43,10 +43,12 @@ import type { AddressesResponse } from 'types/api/addresses';
import type { AddressMetadataInfo, PublicTagTypesResponse } from 'types/api/addressMetadata'; import type { AddressMetadataInfo, PublicTagTypesResponse } from 'types/api/addressMetadata';
import type { import type {
ArbitrumL2MessagesResponse, ArbitrumL2MessagesResponse,
ArbitrumL2MessagesItem,
ArbitrumL2TxnBatch, ArbitrumL2TxnBatch,
ArbitrumL2TxnBatchesResponse, ArbitrumL2TxnBatchesResponse,
ArbitrumL2BatchTxs, ArbitrumL2BatchTxs,
ArbitrumL2BatchBlocks, ArbitrumL2BatchBlocks,
ArbitrumL2TxnBatchesItem,
} from 'types/api/arbitrumL2'; } from 'types/api/arbitrumL2';
import type { TxBlobs, Blob } from 'types/api/blobs'; import type { TxBlobs, Blob } from 'types/api/blobs';
import type { BlocksResponse, BlockTransactionsResponse, Block, BlockFilters, BlockWithdrawalsResponse, BlockCountdownResponse } from 'types/api/block'; import type { BlocksResponse, BlockTransactionsResponse, Block, BlockFilters, BlockWithdrawalsResponse, BlockCountdownResponse } from 'types/api/block';
...@@ -55,7 +57,6 @@ import type { BackendVersionConfig } from 'types/api/configs'; ...@@ -55,7 +57,6 @@ import type { BackendVersionConfig } from 'types/api/configs';
import type { import type {
SmartContract, SmartContract,
SmartContractVerificationConfigRaw, SmartContractVerificationConfigRaw,
SolidityscanReport,
SmartContractSecurityAudits, SmartContractSecurityAudits,
} from 'types/api/contract'; } from 'types/api/contract';
import type { VerifiedContractsResponse, VerifiedContractsFilters, VerifiedContractsCounters } from 'types/api/contracts'; import type { VerifiedContractsResponse, VerifiedContractsFilters, VerifiedContractsCounters } from 'types/api/contracts';
...@@ -484,7 +485,7 @@ export const RESOURCES = { ...@@ -484,7 +485,7 @@ export const RESOURCES = {
path: '/api/v2/smart-contracts/:hash/verification/via/:method', path: '/api/v2/smart-contracts/:hash/verification/via/:method',
pathParams: [ 'hash' as const, 'method' as const ], pathParams: [ 'hash' as const, 'method' as const ],
}, },
contract_solidityscan_report: { contract_solidity_scan_report: {
path: '/api/v2/smart-contracts/:hash/solidityscan-report', path: '/api/v2/smart-contracts/:hash/solidityscan-report',
pathParams: [ 'hash' as const ], pathParams: [ 'hash' as const ],
}, },
...@@ -586,15 +587,21 @@ export const RESOURCES = { ...@@ -586,15 +587,21 @@ export const RESOURCES = {
homepage_blocks: { homepage_blocks: {
path: '/api/v2/main-page/blocks', path: '/api/v2/main-page/blocks',
}, },
homepage_deposits: { homepage_optimistic_deposits: {
path: '/api/v2/main-page/optimism-deposits', path: '/api/v2/main-page/optimism-deposits',
}, },
homepage_arbitrum_deposits: {
path: '/api/v2/main-page/arbitrum/messages/to-rollup',
},
homepage_txs: { homepage_txs: {
path: '/api/v2/main-page/transactions', path: '/api/v2/main-page/transactions',
}, },
homepage_zkevm_l2_batches: { homepage_zkevm_l2_batches: {
path: '/api/v2/main-page/zkevm/batches/confirmed', path: '/api/v2/main-page/zkevm/batches/confirmed',
}, },
homepage_arbitrum_l2_batches: {
path: '/api/v2/main-page/arbitrum/batches/committed',
},
homepage_txs_watchlist: { homepage_txs_watchlist: {
path: '/api/v2/main-page/transactions/watchlist', path: '/api/v2/main-page/transactions/watchlist',
}, },
...@@ -607,6 +614,9 @@ export const RESOURCES = { ...@@ -607,6 +614,9 @@ export const RESOURCES = {
homepage_zksync_latest_batch: { homepage_zksync_latest_batch: {
path: '/api/v2/main-page/zksync/batches/latest-number', path: '/api/v2/main-page/zksync/batches/latest-number',
}, },
homepage_arbitrum_latest_batch: {
path: '/api/v2/main-page/arbitrum/batches/latest-number',
},
// SEARCH // SEARCH
quick_search: { quick_search: {
...@@ -972,11 +982,14 @@ Q extends 'stats_charts_secondary_coin_price' ? ChartSecondaryCoinPriceResponse ...@@ -972,11 +982,14 @@ Q extends 'stats_charts_secondary_coin_price' ? ChartSecondaryCoinPriceResponse
Q extends 'homepage_blocks' ? Array<Block> : Q extends 'homepage_blocks' ? Array<Block> :
Q extends 'homepage_txs' ? Array<Transaction> : Q extends 'homepage_txs' ? Array<Transaction> :
Q extends 'homepage_txs_watchlist' ? Array<Transaction> : Q extends 'homepage_txs_watchlist' ? Array<Transaction> :
Q extends 'homepage_deposits' ? Array<OptimisticL2DepositsItem> : Q extends 'homepage_optimistic_deposits' ? Array<OptimisticL2DepositsItem> :
Q extends 'homepage_arbitrum_deposits' ? { items: Array<ArbitrumL2MessagesItem> } :
Q extends 'homepage_zkevm_l2_batches' ? { items: Array<ZkEvmL2TxnBatchesItem> } : Q extends 'homepage_zkevm_l2_batches' ? { items: Array<ZkEvmL2TxnBatchesItem> } :
Q extends 'homepage_arbitrum_l2_batches' ? { items: Array<ArbitrumL2TxnBatchesItem>} :
Q extends 'homepage_indexing_status' ? IndexingStatus : Q extends 'homepage_indexing_status' ? IndexingStatus :
Q extends 'homepage_zkevm_latest_batch' ? number : Q extends 'homepage_zkevm_latest_batch' ? number :
Q extends 'homepage_zksync_latest_batch' ? number : Q extends 'homepage_zksync_latest_batch' ? number :
Q extends 'homepage_arbitrum_latest_batch' ? number :
Q extends 'stats_counters' ? stats.Counters : Q extends 'stats_counters' ? stats.Counters :
Q extends 'stats_lines' ? stats.LineCharts : Q extends 'stats_lines' ? stats.LineCharts :
Q extends 'stats_line' ? stats.LineChart : Q extends 'stats_line' ? stats.LineChart :
...@@ -1030,13 +1043,11 @@ Q extends 'quick_search' ? Array<SearchResultItem> : ...@@ -1030,13 +1043,11 @@ Q extends 'quick_search' ? Array<SearchResultItem> :
Q extends 'search' ? SearchResult : Q extends 'search' ? SearchResult :
Q extends 'search_check_redirect' ? SearchRedirectResult : Q extends 'search_check_redirect' ? SearchRedirectResult :
Q extends 'contract' ? SmartContract : Q extends 'contract' ? SmartContract :
Q extends 'contract_solidityscan_report' ? SolidityscanReport : Q extends 'contract_solidity_scan_report' ? unknown :
Q extends 'verified_contracts' ? VerifiedContractsResponse : Q extends 'verified_contracts' ? VerifiedContractsResponse :
Q extends 'verified_contracts_counters' ? VerifiedContractsCounters : Q extends 'verified_contracts_counters' ? VerifiedContractsCounters :
Q extends 'visualize_sol2uml' ? visualizer.VisualizeResponse : Q extends 'visualize_sol2uml' ? visualizer.VisualizeResponse :
Q extends 'contract_verification_config' ? SmartContractVerificationConfigRaw : Q extends 'contract_verification_config' ? SmartContractVerificationConfigRaw :
Q extends 'withdrawals' ? WithdrawalsResponse :
Q extends 'withdrawals_counters' ? WithdrawalsCounters :
Q extends 'optimistic_l2_output_roots' ? OptimisticL2OutputRootsResponse : Q extends 'optimistic_l2_output_roots' ? OptimisticL2OutputRootsResponse :
Q extends 'optimistic_l2_withdrawals' ? OptimisticL2WithdrawalsResponse : Q extends 'optimistic_l2_withdrawals' ? OptimisticL2WithdrawalsResponse :
Q extends 'optimistic_l2_deposits' ? OptimisticL2DepositsResponse : Q extends 'optimistic_l2_deposits' ? OptimisticL2DepositsResponse :
...@@ -1104,6 +1115,8 @@ Q extends 'address_mud_tables' ? AddressMudTables : ...@@ -1104,6 +1115,8 @@ Q extends 'address_mud_tables' ? AddressMudTables :
Q extends 'address_mud_tables_count' ? number : Q extends 'address_mud_tables_count' ? number :
Q extends 'address_mud_records' ? AddressMudRecords : Q extends 'address_mud_records' ? AddressMudRecords :
Q extends 'address_mud_record' ? AddressMudRecord : Q extends 'address_mud_record' ? AddressMudRecord :
Q extends 'withdrawals' ? WithdrawalsResponse :
Q extends 'withdrawals_counters' ? WithdrawalsCounters :
never; never;
/* eslint-enable @typescript-eslint/indent */ /* eslint-enable @typescript-eslint/indent */
......
import type { UseQueryOptions } from '@tanstack/react-query'; import type { QueryKey, UseQueryOptions } from '@tanstack/react-query';
import { useQuery } from '@tanstack/react-query'; import { useQuery } from '@tanstack/react-query';
import type { ResourceError, ResourceName, ResourcePayload } from './resources'; import type { Params as FetchParams } from 'lib/hooks/useFetch';
import type { Params as ApiFetchParams } from './useApiFetch';
import type { ResourceError, ResourceName, ResourcePathParams, ResourcePayload } from './resources';
import useApiFetch from './useApiFetch'; import useApiFetch from './useApiFetch';
export interface Params<R extends ResourceName, E = unknown, D = ResourcePayload<R>> extends ApiFetchParams<R> { export interface Params<R extends ResourceName, E = unknown, D = ResourcePayload<R>> {
pathParams?: ResourcePathParams<R>;
queryParams?: Record<string, string | Array<string> | number | boolean | undefined>;
fetchParams?: Pick<FetchParams, 'body' | 'method' | 'headers'>;
queryOptions?: Omit<UseQueryOptions<ResourcePayload<R>, ResourceError<E>, D>, 'queryKey' | 'queryFn'>; queryOptions?: Omit<UseQueryOptions<ResourcePayload<R>, ResourceError<E>, D>, 'queryKey' | 'queryFn'>;
queryKey?: QueryKey;
} }
export function getResourceKey<R extends ResourceName>(resource: R, { pathParams, queryParams }: Params<R> = {}) { export function getResourceKey<R extends ResourceName>(resource: R, { pathParams, queryParams }: Params<R> = {}) {
...@@ -19,18 +24,18 @@ export function getResourceKey<R extends ResourceName>(resource: R, { pathParams ...@@ -19,18 +24,18 @@ export function getResourceKey<R extends ResourceName>(resource: R, { pathParams
export default function useApiQuery<R extends ResourceName, E = unknown, D = ResourcePayload<R>>( export default function useApiQuery<R extends ResourceName, E = unknown, D = ResourcePayload<R>>(
resource: R, resource: R,
{ queryOptions, pathParams, queryParams, fetchParams }: Params<R, E, D> = {}, { queryOptions, pathParams, queryParams, queryKey, fetchParams }: Params<R, E, D> = {},
) { ) {
const apiFetch = useApiFetch(); const apiFetch = useApiFetch();
return useQuery<ResourcePayload<R>, ResourceError<E>, D>({ return useQuery<ResourcePayload<R>, ResourceError<E>, D>({
// eslint-disable-next-line @tanstack/query/exhaustive-deps // eslint-disable-next-line @tanstack/query/exhaustive-deps
queryKey: getResourceKey(resource, { pathParams, queryParams }), queryKey: queryKey || getResourceKey(resource, { pathParams, queryParams }),
queryFn: async() => { queryFn: async({ signal }) => {
// all errors and error typing is handled by react-query // all errors and error typing is handled by react-query
// so error response will never go to the data // so error response will never go to the data
// that's why we are safe here to do type conversion "as Promise<ResourcePayload<R>>" // that's why we are safe here to do type conversion "as Promise<ResourcePayload<R>>"
return apiFetch(resource, { pathParams, queryParams, fetchParams }) as Promise<ResourcePayload<R>>; return apiFetch(resource, { pathParams, queryParams, fetchParams: { ...fetchParams, signal } }) as Promise<ResourcePayload<R>>;
}, },
...queryOptions, ...queryOptions,
}); });
......
...@@ -6,7 +6,7 @@ import { ...@@ -6,7 +6,7 @@ import {
import type { ChakraProviderProps } from '@chakra-ui/react'; import type { ChakraProviderProps } from '@chakra-ui/react';
import React from 'react'; import React from 'react';
import theme from 'theme'; import theme from 'theme/theme';
interface Props extends ChakraProviderProps { interface Props extends ChakraProviderProps {
cookies?: string; cookies?: string;
......
...@@ -57,11 +57,11 @@ export default function useNewTxsSocket() { ...@@ -57,11 +57,11 @@ export default function useNewTxsSocket() {
}, [ setNum ]); }, [ setNum ]);
const handleSocketClose = React.useCallback(() => { const handleSocketClose = React.useCallback(() => {
setSocketAlert('Connection is lost. Please reload page.'); setSocketAlert('Connection is lost. Please reload the page.');
}, []); }, []);
const handleSocketError = React.useCallback(() => { const handleSocketError = React.useCallback(() => {
setSocketAlert('An error has occurred while fetching new transactions. Please reload page.'); setSocketAlert('An error has occurred while fetching new transactions. Please reload the page.');
}, []); }, []);
const channel = useSocketChannel({ const channel = useSocketChannel({
......
...@@ -19,7 +19,7 @@ function getUnits(diff: number) { ...@@ -19,7 +19,7 @@ function getUnits(diff: number) {
return [ DAY, 2 * DAY ]; return [ DAY, 2 * DAY ];
} }
function getUpdateParams(ts: string) { function getUpdateParams(ts: string | number) {
const timeDiff = Date.now() - new Date(ts).getTime(); const timeDiff = Date.now() - new Date(ts).getTime();
const [ unit, higherUnit ] = getUnits(timeDiff); const [ unit, higherUnit ] = getUnits(timeDiff);
...@@ -41,7 +41,7 @@ function getUpdateParams(ts: string) { ...@@ -41,7 +41,7 @@ function getUpdateParams(ts: string) {
}; };
} }
export default function useTimeAgoIncrement(ts: string | null, isEnabled?: boolean) { export default function useTimeAgoIncrement(ts: string | number | null, isEnabled?: boolean) {
const [ value, setValue ] = React.useState(ts ? dayjs(ts).fromNow() : null); const [ value, setValue ] = React.useState(ts ? dayjs(ts).fromNow() : null);
React.useEffect(() => { React.useEffect(() => {
......
...@@ -54,6 +54,7 @@ const OG_TYPE_DICT: Record<Route['pathname'], OGPageType> = { ...@@ -54,6 +54,7 @@ const OG_TYPE_DICT: Record<Route['pathname'], OGPageType> = {
'/login': 'Regular page', '/login': 'Regular page',
'/sprite': 'Regular page', '/sprite': 'Regular page',
'/api/metrics': 'Regular page', '/api/metrics': 'Regular page',
'/api/monitoring/invalid-api-schema': 'Regular page',
'/api/log': 'Regular page', '/api/log': 'Regular page',
'/api/media-type': 'Regular page', '/api/media-type': 'Regular page',
'/api/proxy': 'Regular page', '/api/proxy': 'Regular page',
......
...@@ -58,6 +58,7 @@ const TEMPLATE_MAP: Record<Route['pathname'], string> = { ...@@ -58,6 +58,7 @@ const TEMPLATE_MAP: Record<Route['pathname'], string> = {
'/login': DEFAULT_TEMPLATE, '/login': DEFAULT_TEMPLATE,
'/sprite': DEFAULT_TEMPLATE, '/sprite': DEFAULT_TEMPLATE,
'/api/metrics': DEFAULT_TEMPLATE, '/api/metrics': DEFAULT_TEMPLATE,
'/api/monitoring/invalid-api-schema': DEFAULT_TEMPLATE,
'/api/log': DEFAULT_TEMPLATE, '/api/log': DEFAULT_TEMPLATE,
'/api/media-type': DEFAULT_TEMPLATE, '/api/media-type': DEFAULT_TEMPLATE,
'/api/proxy': DEFAULT_TEMPLATE, '/api/proxy': DEFAULT_TEMPLATE,
......
...@@ -54,6 +54,7 @@ const TEMPLATE_MAP: Record<Route['pathname'], string> = { ...@@ -54,6 +54,7 @@ const TEMPLATE_MAP: Record<Route['pathname'], string> = {
'/login': '%network_name% login', '/login': '%network_name% login',
'/sprite': '%network_name% SVG sprite', '/sprite': '%network_name% SVG sprite',
'/api/metrics': '%network_name% node API prometheus metrics', '/api/metrics': '%network_name% node API prometheus metrics',
'/api/monitoring/invalid-api-schema': '%network_name% node API prometheus metrics',
'/api/log': '%network_name% node API request log', '/api/log': '%network_name% node API request log',
'/api/media-type': '%network_name% node API media type', '/api/media-type': '%network_name% node API media type',
'/api/proxy': '%network_name% node API proxy', '/api/proxy': '%network_name% node API proxy',
......
...@@ -52,6 +52,7 @@ export const PAGE_TYPE_DICT: Record<Route['pathname'], string> = { ...@@ -52,6 +52,7 @@ export const PAGE_TYPE_DICT: Record<Route['pathname'], string> = {
'/login': 'Login', '/login': 'Login',
'/sprite': 'Sprite', '/sprite': 'Sprite',
'/api/metrics': 'Node API: Prometheus metrics', '/api/metrics': 'Node API: Prometheus metrics',
'/api/monitoring/invalid-api-schema': 'Node API: Prometheus metrics',
'/api/log': 'Node API: Request log', '/api/log': 'Node API: Request log',
'/api/media-type': 'Node API: Media type', '/api/media-type': 'Node API: Media type',
'/api/proxy': 'Node API: Proxy', '/api/proxy': 'Node API: Proxy',
......
...@@ -8,6 +8,12 @@ const metrics = (() => { ...@@ -8,6 +8,12 @@ const metrics = (() => {
promClient.register.clear(); promClient.register.clear();
const invalidApiSchema = new promClient.Counter({
name: 'invalid_api_schema',
help: 'Number of invalid external API schema events',
labelNames: [ 'resource', 'url' ] as const,
});
const socialPreviewBotRequests = new promClient.Counter({ const socialPreviewBotRequests = new promClient.Counter({
name: 'social_preview_bot_requests_total', name: 'social_preview_bot_requests_total',
help: 'Number of incoming requests from social preview bots', help: 'Number of incoming requests from social preview bots',
...@@ -27,7 +33,7 @@ const metrics = (() => { ...@@ -27,7 +33,7 @@ const metrics = (() => {
buckets: [ 0.2, 0.5, 1, 3, 10 ], buckets: [ 0.2, 0.5, 1, 3, 10 ],
}); });
return { socialPreviewBotRequests, searchEngineBotRequests, apiRequestDuration }; return { invalidApiSchema, socialPreviewBotRequests, searchEngineBotRequests, apiRequestDuration };
})(); })();
export default metrics; export default metrics;
import config from 'configs/app';
export default function getNetworkValidationActionText() {
switch (config.chain.verificationType) {
case 'validation': {
return 'validated';
}
case 'mining': {
return 'mined';
}
case 'posting': {
return 'posted';
}
case 'sequencing': {
return 'sequenced';
}
default: {
return 'miner';
}
}
}
import config from 'configs/app'; import config from 'configs/app';
export default function getNetworkValidatorTitle() { export default function getNetworkValidatorTitle() {
return config.chain.verificationType === 'validation' ? 'validator' : 'miner'; switch (config.chain.verificationType) {
case 'validation': {
return 'validator';
}
case 'mining': {
return 'miner';
}
case 'posting': {
return 'poster';
}
case 'sequencing': {
return 'sequencer';
}
default: {
return 'miner';
}
}
} }
import type { Channel } from 'phoenix'; import type { Channel } from 'phoenix';
import type { AddressCoinBalanceHistoryItem, AddressTokensBalancesSocketMessage } from 'types/api/address'; import type { AddressCoinBalanceHistoryItem, AddressTokensBalancesSocketMessage } from 'types/api/address';
import type { NewArbitrumBatchSocketResponse } from 'types/api/arbitrumL2';
import type { NewBlockSocketResponse } from 'types/api/block'; import type { NewBlockSocketResponse } from 'types/api/block';
import type { SmartContractVerificationResponse } from 'types/api/contract'; import type { SmartContractVerificationResponse } from 'types/api/contract';
import type { RawTracesResponse } from 'types/api/rawTrace'; import type { RawTracesResponse } from 'types/api/rawTrace';
...@@ -16,7 +17,8 @@ SocketMessage.TxStatusUpdate | ...@@ -16,7 +17,8 @@ SocketMessage.TxStatusUpdate |
SocketMessage.TxRawTrace | SocketMessage.TxRawTrace |
SocketMessage.NewTx | SocketMessage.NewTx |
SocketMessage.NewPendingTx | SocketMessage.NewPendingTx |
SocketMessage.NewDeposits | SocketMessage.NewOptimisticDeposits |
SocketMessage.NewArbitrumDeposits |
SocketMessage.AddressBalance | SocketMessage.AddressBalance |
SocketMessage.AddressCurrentCoinBalance | SocketMessage.AddressCurrentCoinBalance |
SocketMessage.AddressTokenBalance | SocketMessage.AddressTokenBalance |
...@@ -36,6 +38,7 @@ SocketMessage.TokenTotalSupply | ...@@ -36,6 +38,7 @@ SocketMessage.TokenTotalSupply |
SocketMessage.TokenInstanceMetadataFetched | SocketMessage.TokenInstanceMetadataFetched |
SocketMessage.ContractVerification | SocketMessage.ContractVerification |
SocketMessage.NewZkEvmL2Batch | SocketMessage.NewZkEvmL2Batch |
SocketMessage.NewArbitrumL2Batch |
SocketMessage.Unknown; SocketMessage.Unknown;
interface SocketMessageParamsGeneric<Event extends string | undefined, Payload extends object | unknown> { interface SocketMessageParamsGeneric<Event extends string | undefined, Payload extends object | unknown> {
...@@ -53,7 +56,8 @@ export namespace SocketMessage { ...@@ -53,7 +56,8 @@ export namespace SocketMessage {
export type TxRawTrace = SocketMessageParamsGeneric<'raw_trace', RawTracesResponse>; export type TxRawTrace = SocketMessageParamsGeneric<'raw_trace', RawTracesResponse>;
export type NewTx = SocketMessageParamsGeneric<'transaction', { transaction: number }>; export type NewTx = SocketMessageParamsGeneric<'transaction', { transaction: number }>;
export type NewPendingTx = SocketMessageParamsGeneric<'pending_transaction', { pending_transaction: number }>; export type NewPendingTx = SocketMessageParamsGeneric<'pending_transaction', { pending_transaction: number }>;
export type NewDeposits = SocketMessageParamsGeneric<'deposits', { deposits: number }>; export type NewOptimisticDeposits = SocketMessageParamsGeneric<'deposits', { deposits: number }>;
export type NewArbitrumDeposits = SocketMessageParamsGeneric<'new_messages_to_rollup_amount', { new_messages_to_rollup_amount: number }>;
export type AddressBalance = SocketMessageParamsGeneric<'balance', { balance: string; block_number: number; exchange_rate: string }>; export type AddressBalance = SocketMessageParamsGeneric<'balance', { balance: string; block_number: number; exchange_rate: string }>;
export type AddressCurrentCoinBalance = export type AddressCurrentCoinBalance =
SocketMessageParamsGeneric<'current_coin_balance', { coin_balance: string; block_number: number; exchange_rate: string }>; SocketMessageParamsGeneric<'current_coin_balance', { coin_balance: string; block_number: number; exchange_rate: string }>;
...@@ -74,5 +78,6 @@ export namespace SocketMessage { ...@@ -74,5 +78,6 @@ export namespace SocketMessage {
export type TokenInstanceMetadataFetched = SocketMessageParamsGeneric<'fetched_token_instance_metadata', TokenInstanceMetadataSocketMessage>; export type TokenInstanceMetadataFetched = SocketMessageParamsGeneric<'fetched_token_instance_metadata', TokenInstanceMetadataSocketMessage>;
export type ContractVerification = SocketMessageParamsGeneric<'verification_result', SmartContractVerificationResponse>; export type ContractVerification = SocketMessageParamsGeneric<'verification_result', SmartContractVerificationResponse>;
export type NewZkEvmL2Batch = SocketMessageParamsGeneric<'new_zkevm_confirmed_batch', NewZkEvmBatchSocketResponse>; export type NewZkEvmL2Batch = SocketMessageParamsGeneric<'new_zkevm_confirmed_batch', NewZkEvmBatchSocketResponse>;
export type NewArbitrumL2Batch = SocketMessageParamsGeneric<'new_arbitrum_batch', NewArbitrumBatchSocketResponse>;
export type Unknown = SocketMessageParamsGeneric<undefined, unknown>; export type Unknown = SocketMessageParamsGeneric<undefined, unknown>;
} }
import * as v from 'valibot';
export const SolidityScanIssueSeverityDistributionSchema = v.object({
critical: v.number(),
gas: v.number(),
high: v.number(),
informational: v.number(),
low: v.number(),
medium: v.number(),
});
export const SolidityScanSchema = v.object({
scan_report: v.object({
contractname: v.string(),
scan_status: v.string(),
scan_summary: v.object({
score_v2: v.string(),
issue_severity_distribution: SolidityScanIssueSeverityDistributionSchema,
}),
scanner_reference_url: v.string(),
}),
});
export type SolidityScanReport = v.InferOutput<typeof SolidityScanSchema>;
export type SolidityScanReportSeverityDistribution = v.InferOutput<typeof SolidityScanIssueSeverityDistributionSchema>;
import React from 'react';
import * as v from 'valibot';
import buildUrl from 'lib/api/buildUrl';
import useApiQuery from 'lib/api/useApiQuery';
import { SOLIDITY_SCAN_REPORT } from 'stubs/contract';
import { SolidityScanSchema } from './schema';
interface Params {
hash: string;
}
const RESOURCE_NAME = 'contract_solidity_scan_report';
const ERROR_NAME = 'Invalid response schema';
export default function useFetchReport({ hash }: Params) {
const query = useApiQuery(RESOURCE_NAME, {
pathParams: { hash },
queryOptions: {
select: (response) => {
const parsedResponse = v.safeParse(SolidityScanSchema, response);
if (!parsedResponse.success) {
throw Error(ERROR_NAME);
}
return parsedResponse.output;
},
enabled: Boolean(hash),
placeholderData: SOLIDITY_SCAN_REPORT,
retry: 0,
},
});
const errorMessage = query.error && 'message' in query.error ? query.error.message : undefined;
React.useEffect(() => {
if (errorMessage === ERROR_NAME) {
fetch('/node-api/monitoring/invalid-api-schema', {
method: 'POST',
body: JSON.stringify({
resource: RESOURCE_NAME,
url: buildUrl(RESOURCE_NAME, { hash }, undefined, true),
}),
});
}
}, [ errorMessage, hash ]);
return query;
}
/* eslint-disable max-len */
import type { ArbitrumMessageStatus } from 'types/api/transaction';
export const MESSAGE_DESCRIPTIONS: Record<ArbitrumMessageStatus, string> = {
'Syncing with base layer': 'The incoming message was discovered on the rollup, but the corresponding message on L1 has not yet been found',
'Settlement pending': 'The transaction with the message was included in a rollup block, but there is no batch on L1 containing the block yet',
'Waiting for confirmation': 'The rollup block with the transaction containing the message was included in a batch on L1, but it is still waiting for the expiration of the fraud proof countdown',
'Ready for relay': 'The rollup state was confirmed successfully, and the message can be executed—funds can be claimed on L1',
Relayed: '',
};
/* eslint-disable max-len */
import type { ArbitrumL2TxnBatch } from 'types/api/arbitrumL2'; import type { ArbitrumL2TxnBatch } from 'types/api/arbitrumL2';
import { finalized } from './txnBatches'; import { finalized } from './txnBatches';
...@@ -8,4 +9,32 @@ export const batchData: ArbitrumL2TxnBatch = { ...@@ -8,4 +9,32 @@ export const batchData: ArbitrumL2TxnBatch = {
before_acc: '0x2ed7c4985eb778d76ec400a43805e7feecc8c2afcdb492dbe5caf227de6d37bc', before_acc: '0x2ed7c4985eb778d76ec400a43805e7feecc8c2afcdb492dbe5caf227de6d37bc',
start_block: 1245209, start_block: 1245209,
end_block: 1245490, end_block: 1245490,
data_availability: {
batch_data_container: 'in_blob4844',
},
};
export const batchDataAnytrust: ArbitrumL2TxnBatch = {
...finalized,
after_acc: '0xcd064f3409015e8e6407e492e5275a185e492c6b43ccf127f22092d8057a9ffb',
before_acc: '0x2ed7c4985eb778d76ec400a43805e7feecc8c2afcdb492dbe5caf227de6d37bc',
start_block: 1245209,
end_block: 1245490,
data_availability: {
batch_data_container: 'in_anytrust',
bls_signature: '0x142577943e30b1ad1b4e40a1c08e00c24a68d6c366f953e361048b7127e327b5bdb8f168ba986beae40cfaf79ea2788004d750555684751e361d6f6445e5c521b45ac93a76da24add241a4a5410ca3a09fa82cf0aafd78801cbd0ad99d5be6b3',
data_hash: '0x4ffada101d8185bcba227f2cff9e0ea0a4deeb08f328601a898131429a436ebe',
timeout: '2024-08-22T12:39:22Z',
signers: [
{
key: '0x0c6694955b524d718ca445831c5375393773401f33725a79661379dddabd5fff28619dc070befd9ed73d699e5c236c1a163be58ba81002b6130709bc064af5d7ba947130b72056bf17263800f1a3ab2269c6a510ef8e7412fd56d1ef1b916a1306e3b1d9c82c099371bd9861582acaada3a16e9dfee5d0ebce61096598a82f112d0a935e8cab5c48d82e3104b0c7ba79157dad1a019a3e7f6ad077b8e6308b116fec0f58239622463c3631fa01e2b4272409215b8009422c16715dbede590906',
proof: '0x06dcb5e56764bb72e6a45e6deb301ca85d8c4315c1da2efa29927f2ac8fb25571ce31d2d603735fe03196f6d56bcbf9a1999a89a74d5369822c4445d676c15ed52e5008daa775dc9a839c99ff963a19946ac740579874dac4f639907ae1bc69f',
trusted: false,
},
{
key: '0x0ee5aaeabd57313285207eb89366b411286cf3f1c5e30eb7e355f55385308b91d5807284323ee89a9743c70676f4949504ced3ed41612cbfda06ad55200c1c77d3fb3700059befd64c44bc4a57cb567ec1481ee564cf6cd6cf1f2f4a2dee6db00c547c38400ab118dedae8afd5bab93b703f76a0991baa5d43fbb125194c06b5461f8c738a3c4278a3d98e5456aec0720883c0d28919537a36e2ffd5f731e742b6653557d154c164e068ef983b367ef626faaed46f4eadecbb12b7e55f23175d',
trusted: true,
},
],
},
}; };
...@@ -10,6 +10,7 @@ export const finalized: ArbitrumL2TxnBatchesItem = { ...@@ -10,6 +10,7 @@ export const finalized: ArbitrumL2TxnBatchesItem = {
hash: '0x262e7215739d6a7e33b2c20b45a838801a0f5f080f20bec8e54eb078420c4661', hash: '0x262e7215739d6a7e33b2c20b45a838801a0f5f080f20bec8e54eb078420c4661',
status: 'finalized', status: 'finalized',
}, },
batch_data_container: 'in_blob4844',
}; };
export const unfinalized: ArbitrumL2TxnBatchesItem = { export const unfinalized: ArbitrumL2TxnBatchesItem = {
...@@ -22,6 +23,8 @@ export const unfinalized: ArbitrumL2TxnBatchesItem = { ...@@ -22,6 +23,8 @@ export const unfinalized: ArbitrumL2TxnBatchesItem = {
hash: '0x262e7215739d6a7e33b2c20b45a838801a0f5f080f20bec8e54eb078420c4661', hash: '0x262e7215739d6a7e33b2c20b45a838801a0f5f080f20bec8e54eb078420c4661',
status: 'unfinalized', status: 'unfinalized',
}, },
batch_data_container: null,
}; };
export const baseResponse: ArbitrumL2TxnBatchesResponse = { export const baseResponse: ArbitrumL2TxnBatchesResponse = {
......
...@@ -99,6 +99,13 @@ export const withChangedByteCode: SmartContract = { ...@@ -99,6 +99,13 @@ export const withChangedByteCode: SmartContract = {
is_blueprint: true, is_blueprint: true,
}; };
export const zkSync: SmartContract = {
...verified,
zk_compiler_version: 'v1.2.5',
optimization_enabled: true,
optimization_runs: 's',
};
export const nonVerified: SmartContract = { export const nonVerified: SmartContract = {
is_verified: false, is_verified: false,
is_blueprint: false, is_blueprint: false,
......
import type { SolidityscanReport } from 'types/api/contract'; import type { SolidityScanReport } from 'lib/solidityScan/schema';
export const solidityscanReportAverage: SolidityscanReport = { export const solidityscanReportAverage: SolidityScanReport = {
scan_report: { scan_report: {
contractname: 'foo', contractname: 'foo',
scan_status: 'scan_done', scan_status: 'scan_done',
...@@ -13,17 +13,13 @@ export const solidityscanReportAverage: SolidityscanReport = { ...@@ -13,17 +13,13 @@ export const solidityscanReportAverage: SolidityscanReport = {
low: 2, low: 2,
medium: 0, medium: 0,
}, },
lines_analyzed_count: 18,
scan_time_taken: 1,
score: '3.61',
score_v2: '72.22', score_v2: '72.22',
threat_score: '94.74',
}, },
scanner_reference_url: 'https://solidityscan.com/quickscan/0xc1EF7811FF2ebFB74F80ed7423f2AdAA37454be2/blockscout/eth-goerli?ref=blockscout', scanner_reference_url: 'https://solidityscan.com/quickscan/0xc1EF7811FF2ebFB74F80ed7423f2AdAA37454be2/blockscout/eth-goerli?ref=blockscout',
}, },
}; };
export const solidityscanReportGreat: SolidityscanReport = { export const solidityscanReportGreat: SolidityScanReport = {
scan_report: { scan_report: {
contractname: 'foo', contractname: 'foo',
scan_status: 'scan_done', scan_status: 'scan_done',
...@@ -36,17 +32,13 @@ export const solidityscanReportGreat: SolidityscanReport = { ...@@ -36,17 +32,13 @@ export const solidityscanReportGreat: SolidityscanReport = {
low: 0, low: 0,
medium: 0, medium: 0,
}, },
lines_analyzed_count: 18,
scan_time_taken: 1,
score: '3.61',
score_v2: '100', score_v2: '100',
threat_score: '94.74',
}, },
scanner_reference_url: 'https://solidityscan.com/quickscan/0xc1EF7811FF2ebFB74F80ed7423f2AdAA37454be2/blockscout/eth-goerli?ref=blockscout', scanner_reference_url: 'https://solidityscan.com/quickscan/0xc1EF7811FF2ebFB74F80ed7423f2AdAA37454be2/blockscout/eth-goerli?ref=blockscout',
}, },
}; };
export const solidityscanReportLow: SolidityscanReport = { export const solidityscanReportLow: SolidityScanReport = {
scan_report: { scan_report: {
contractname: 'foo', contractname: 'foo',
scan_status: 'scan_done', scan_status: 'scan_done',
...@@ -59,11 +51,7 @@ export const solidityscanReportLow: SolidityscanReport = { ...@@ -59,11 +51,7 @@ export const solidityscanReportLow: SolidityscanReport = {
low: 2, low: 2,
medium: 10, medium: 10,
}, },
lines_analyzed_count: 18,
scan_time_taken: 1,
score: '3.61',
score_v2: '22.22', score_v2: '22.22',
threat_score: '94.74',
}, },
scanner_reference_url: 'https://solidityscan.com/quickscan/0xc1EF7811FF2ebFB74F80ed7423f2AdAA37454be2/blockscout/eth-goerli?ref=blockscout', scanner_reference_url: 'https://solidityscan.com/quickscan/0xc1EF7811FF2ebFB74F80ed7423f2AdAA37454be2/blockscout/eth-goerli?ref=blockscout',
}, },
......
...@@ -63,6 +63,14 @@ export const ensDomainA: bens.DetailedDomain = { ...@@ -63,6 +63,14 @@ export const ensDomainA: bens.DetailedDomain = {
NEAR: 'a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48.factory.bridge.near', NEAR: 'a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48.factory.bridge.near',
}, },
protocol: protocolA, protocol: protocolA,
resolver_address: {
hash: '0xD578780f1dA7404d9CC0eEbC9D684c140CC4b638',
},
resolved_with_wildcard: true,
stored_offchain: true,
wrapped_owner: {
hash: '0xD4416b13d2b3a9aBae7AcD5D6C2BbDBE25686401',
},
}; };
export const ensDomainB: bens.DetailedDomain = { export const ensDomainB: bens.DetailedDomain = {
...@@ -81,6 +89,8 @@ export const ensDomainB: bens.DetailedDomain = { ...@@ -81,6 +89,8 @@ export const ensDomainB: bens.DetailedDomain = {
expiry_date: undefined, expiry_date: undefined,
other_addresses: {}, other_addresses: {},
protocol: undefined, protocol: undefined,
resolved_with_wildcard: false,
stored_offchain: false,
}; };
export const ensDomainC: bens.DetailedDomain = { export const ensDomainC: bens.DetailedDomain = {
...@@ -101,6 +111,8 @@ export const ensDomainC: bens.DetailedDomain = { ...@@ -101,6 +111,8 @@ export const ensDomainC: bens.DetailedDomain = {
expiry_date: '2022-11-01T13:10:36.000Z', expiry_date: '2022-11-01T13:10:36.000Z',
other_addresses: {}, other_addresses: {},
protocol: undefined, protocol: undefined,
resolved_with_wildcard: false,
stored_offchain: false,
}; };
export const ensDomainD: bens.DetailedDomain = { export const ensDomainD: bens.DetailedDomain = {
...@@ -119,4 +131,6 @@ export const ensDomainD: bens.DetailedDomain = { ...@@ -119,4 +131,6 @@ export const ensDomainD: bens.DetailedDomain = {
expiry_date: '2027-09-23T13:10:36.000Z', expiry_date: '2027-09-23T13:10:36.000Z',
other_addresses: {}, other_addresses: {},
protocol: undefined, protocol: undefined,
resolved_with_wildcard: false,
stored_offchain: false,
}; };
/* eslint-disable max-len */ /* eslint-disable max-len */
import type { Transaction } from 'types/api/transaction'; import type { Transaction } from 'types/api/transaction';
import * as addressMock from 'mocks/address/address';
import { publicTag, privateTag, watchlistName } from 'mocks/address/tag'; import { publicTag, privateTag, watchlistName } from 'mocks/address/tag';
import * as tokenTransferMock from 'mocks/tokens/tokenTransfer'; import * as tokenTransferMock from 'mocks/tokens/tokenTransfer';
import * as decodedInputDataMock from 'mocks/txs/decodedInputData'; import * as decodedInputDataMock from 'mocks/txs/decodedInputData';
...@@ -47,7 +48,7 @@ export const base: Transaction = { ...@@ -47,7 +48,7 @@ export const base: Transaction = {
status: 'ok', status: 'ok',
timestamp: '2022-10-10T14:34:30.000000Z', timestamp: '2022-10-10T14:34:30.000000Z',
to: { to: {
hash: '0xd789a607CEac2f0E14867de4EB15b15C9FFB5859', hash: addressMock.hash,
implementations: null, implementations: null,
is_contract: false, is_contract: false,
is_verified: true, is_verified: true,
...@@ -110,7 +111,7 @@ export const withTokenTransfer: Transaction = { ...@@ -110,7 +111,7 @@ export const withTokenTransfer: Transaction = {
...base, ...base,
hash: '0x62d597ebcf3e8d60096dd0363bc2f0f5e2df27ba1dacd696c51aa7c9409f3196', hash: '0x62d597ebcf3e8d60096dd0363bc2f0f5e2df27ba1dacd696c51aa7c9409f3196',
to: { to: {
hash: '0xd789a607CEac2f0E14867de4EB15b15C9FFB5859', hash: addressMock.hash,
implementations: null, implementations: null,
is_contract: true, is_contract: true,
is_verified: true, is_verified: true,
...@@ -166,7 +167,7 @@ export const withRawRevertReason: Transaction = { ...@@ -166,7 +167,7 @@ export const withRawRevertReason: Transaction = {
raw: '4f6e6c79206368616972706572736f6e2063616e206769766520726967687420746f20766f74652e', raw: '4f6e6c79206368616972706572736f6e2063616e206769766520726967687420746f20766f74652e',
}, },
to: { to: {
hash: '0xd789a607CEac2f0E14867de4EB15b15C9FFB5859', hash: addressMock.hash,
implementations: null, implementations: null,
is_verified: true, is_verified: true,
is_contract: true, is_contract: true,
...@@ -344,7 +345,7 @@ export const base2 = { ...@@ -344,7 +345,7 @@ export const base2 = {
hash: '0x02d597ebcf3e8d60096dd0363bc2f0f5e2df27ba1dacd696c51aa7c9409f3193', hash: '0x02d597ebcf3e8d60096dd0363bc2f0f5e2df27ba1dacd696c51aa7c9409f3193',
from: { from: {
...base.from, ...base.from,
hash: '0xd789a607CEac2f0E14867de4EB15b15C9FFB5859', hash: addressMock.hash,
}, },
}; };
...@@ -353,7 +354,7 @@ export const base3 = { ...@@ -353,7 +354,7 @@ export const base3 = {
hash: '0x12d597ebcf3e8d60096dd0363bc2f0f5e2df27ba1dacd696c51aa7c9409f3193', hash: '0x12d597ebcf3e8d60096dd0363bc2f0f5e2df27ba1dacd696c51aa7c9409f3193',
from: { from: {
...base.from, ...base.from,
hash: '0xd789a607CEac2f0E14867de4EB15b15C9FFB5859', hash: addressMock.hash,
}, },
}; };
...@@ -375,3 +376,18 @@ export const withBlob = { ...@@ -375,3 +376,18 @@ export const withBlob = {
tx_types: [ 'blob_transaction' as const ], tx_types: [ 'blob_transaction' as const ],
type: 3, type: 3,
}; };
export const withRecipientName = {
...base,
to: addressMock.withName,
};
export const withRecipientEns = {
...base,
to: addressMock.withEns,
};
export const withRecipientNameTag = {
...withRecipientEns,
to: addressMock.withNameTag,
};
import type { TxInterpretationResponse } from 'types/api/txInterpretation'; import type { TxInterpretationResponse } from 'types/api/txInterpretation';
import { hash } from 'mocks/address/address';
export const txInterpretation: TxInterpretationResponse = { export const txInterpretation: TxInterpretationResponse = {
data: { data: {
summaries: [ { summaries: [ {
...@@ -25,7 +27,7 @@ export const txInterpretation: TxInterpretationResponse = { ...@@ -25,7 +27,7 @@ export const txInterpretation: TxInterpretationResponse = {
to_address: { to_address: {
type: 'address', type: 'address',
value: { value: {
hash: '0x48c04ed5691981C42154C6167398f95e8f38a7fF', hash: hash,
implementations: null, implementations: null,
is_contract: false, is_contract: false,
is_verified: false, is_verified: false,
......
...@@ -12,6 +12,7 @@ export function walletConnect(): CspDev.DirectiveDescriptor { ...@@ -12,6 +12,7 @@ export function walletConnect(): CspDev.DirectiveDescriptor {
return { return {
'connect-src': [ 'connect-src': [
'*.web3modal.com', '*.web3modal.com',
'*.web3modal.org',
'*.walletconnect.com', '*.walletconnect.com',
'wss://relay.walletconnect.com', 'wss://relay.walletconnect.com',
'wss://www.walletlink.org', 'wss://www.walletlink.org',
......
...@@ -21,6 +21,7 @@ declare module "nextjs-routes" { ...@@ -21,6 +21,7 @@ declare module "nextjs-routes" {
| StaticRoute<"/api/log"> | StaticRoute<"/api/log">
| StaticRoute<"/api/media-type"> | StaticRoute<"/api/media-type">
| StaticRoute<"/api/metrics"> | StaticRoute<"/api/metrics">
| StaticRoute<"/api/monitoring/invalid-api-schema">
| StaticRoute<"/api/proxy"> | StaticRoute<"/api/proxy">
| StaticRoute<"/api/sprite"> | StaticRoute<"/api/sprite">
| StaticRoute<"/api-docs"> | StaticRoute<"/api-docs">
......
...@@ -36,7 +36,7 @@ ...@@ -36,7 +36,7 @@
"monitoring:grafana:local": "docker run -d -p 4000:3000 --name=blockscout_grafana --user $(id -u) --volume $(pwd)/grafana:/var/lib/grafana grafana/grafana-enterprise" "monitoring:grafana:local": "docker run -d -p 4000:3000 --name=blockscout_grafana --user $(id -u) --volume $(pwd)/grafana:/var/lib/grafana grafana/grafana-enterprise"
}, },
"dependencies": { "dependencies": {
"@blockscout/bens-types": "1.3.4", "@blockscout/bens-types": "1.4.1",
"@blockscout/stats-types": "1.6.0", "@blockscout/stats-types": "1.6.0",
"@blockscout/visualizer-types": "0.2.0", "@blockscout/visualizer-types": "0.2.0",
"@chakra-ui/react": "2.7.1", "@chakra-ui/react": "2.7.1",
...@@ -108,6 +108,7 @@ ...@@ -108,6 +108,7 @@
"react-scroll": "^1.8.7", "react-scroll": "^1.8.7",
"swagger-ui-react": "^5.9.0", "swagger-ui-react": "^5.9.0",
"use-font-face-observer": "^1.2.1", "use-font-face-observer": "^1.2.1",
"valibot": "0.38.0",
"viem": "2.10.9", "viem": "2.10.9",
"wagmi": "2.9.2", "wagmi": "2.9.2",
"xss": "^1.0.14" "xss": "^1.0.14"
......
...@@ -6,7 +6,7 @@ import React from 'react'; ...@@ -6,7 +6,7 @@ import React from 'react';
import logRequestFromBot from 'nextjs/utils/logRequestFromBot'; import logRequestFromBot from 'nextjs/utils/logRequestFromBot';
import * as serverTiming from 'nextjs/utils/serverTiming'; import * as serverTiming from 'nextjs/utils/serverTiming';
import theme from 'theme'; import theme from 'theme/theme';
import * as svgSprite from 'ui/shared/IconSvg'; import * as svgSprite from 'ui/shared/IconSvg';
class MyDocument extends Document { class MyDocument extends Document {
......
import type { NextApiRequest, NextApiResponse } from 'next';
import * as v from 'valibot';
import metrics from 'lib/monitoring/metrics';
const PayloadSchema = v.object({
resource: v.string(),
url: v.string(),
});
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
try {
const payload = JSON.parse(req.body);
metrics?.invalidApiSchema.inc(v.parse(PayloadSchema, payload));
} catch (error) {
}
res.status(200).json({ status: 'ok' });
}
...@@ -12,7 +12,7 @@ import config from 'configs/app'; ...@@ -12,7 +12,7 @@ import config from 'configs/app';
import { AppContextProvider } from 'lib/contexts/app'; import { AppContextProvider } from 'lib/contexts/app';
import { SocketProvider } from 'lib/socket/context'; import { SocketProvider } from 'lib/socket/context';
import currentChain from 'lib/web3/currentChain'; import currentChain from 'lib/web3/currentChain';
import theme from 'theme'; import theme from 'theme/theme';
import { port as socketPort } from './utils/socket'; import { port as socketPort } from './utils/socket';
......
...@@ -37,6 +37,7 @@ export const ENVS_MAP: Record<string, Array<[string, string]>> = { ...@@ -37,6 +37,7 @@ export const ENVS_MAP: Record<string, Array<[string, string]>> = {
zkSyncRollup: [ zkSyncRollup: [
[ 'NEXT_PUBLIC_ROLLUP_TYPE', 'zkSync' ], [ 'NEXT_PUBLIC_ROLLUP_TYPE', 'zkSync' ],
[ 'NEXT_PUBLIC_ROLLUP_L1_BASE_URL', 'https://localhost:3101' ], [ 'NEXT_PUBLIC_ROLLUP_L1_BASE_URL', 'https://localhost:3101' ],
[ 'NEXT_PUBLIC_VIEWS_CONTRACT_EXTRA_VERIFICATION_METHODS', 'none' ],
], ],
bridgedTokens: [ bridgedTokens: [
[ 'NEXT_PUBLIC_BRIDGED_TOKENS_CHAINS', '[{"id":"1","title":"Ethereum","short_title":"ETH","base_url":"https://eth.blockscout.com/token/"},{"id":"56","title":"Binance Smart Chain","short_title":"BSC","base_url":"https://bscscan.com/token/"},{"id":"99","title":"POA","short_title":"POA","base_url":"https://blockscout.com/poa/core/token/"}]' ], [ 'NEXT_PUBLIC_BRIDGED_TOKENS_CHAINS', '[{"id":"1","title":"Ethereum","short_title":"ETH","base_url":"https://eth.blockscout.com/token/"},{"id":"56","title":"Binance Smart Chain","short_title":"BSC","base_url":"https://bscscan.com/token/"},{"id":"99","title":"POA","short_title":"POA","base_url":"https://blockscout.com/poa/core/token/"}]' ],
......
...@@ -22,6 +22,8 @@ export const ENS_DOMAIN: bens.DetailedDomain = { ...@@ -22,6 +22,8 @@ export const ENS_DOMAIN: bens.DetailedDomain = {
ETH: ADDRESS_HASH, ETH: ADDRESS_HASH,
}, },
protocol: undefined, protocol: undefined,
resolved_with_wildcard: false,
stored_offchain: false,
}; };
export const ENS_DOMAIN_EVENT: bens.DomainEvent = { export const ENS_DOMAIN_EVENT: bens.DomainEvent = {
......
...@@ -23,6 +23,7 @@ export const ARBITRUM_L2_TXN_BATCHES_ITEM: ArbitrumL2TxnBatchesItem = { ...@@ -23,6 +23,7 @@ export const ARBITRUM_L2_TXN_BATCHES_ITEM: ArbitrumL2TxnBatchesItem = {
hash: TX_HASH, hash: TX_HASH,
status: 'finalized', status: 'finalized',
}, },
batch_data_container: 'in_blob4844',
}; };
export const ARBITRUM_L2_TXN_BATCH: ArbitrumL2TxnBatch = { export const ARBITRUM_L2_TXN_BATCH: ArbitrumL2TxnBatch = {
...@@ -31,4 +32,7 @@ export const ARBITRUM_L2_TXN_BATCH: ArbitrumL2TxnBatch = { ...@@ -31,4 +32,7 @@ export const ARBITRUM_L2_TXN_BATCH: ArbitrumL2TxnBatch = {
before_acc: '0x2ed7c4985eb778d76ec400a43805e7feecc8c2afcdb492dbe5caf227de6d37bc', before_acc: '0x2ed7c4985eb778d76ec400a43805e7feecc8c2afcdb492dbe5caf227de6d37bc',
start_block: 1245209, start_block: 1245209,
end_block: 1245490, end_block: 1245490,
data_availability: {
batch_data_container: 'in_blob4844',
},
}; };
import type { SmartContract, SolidityscanReport } from 'types/api/contract'; import type { SmartContract } from 'types/api/contract';
import type { VerifiedContract, VerifiedContractsCounters } from 'types/api/contracts'; import type { VerifiedContract, VerifiedContractsCounters } from 'types/api/contracts';
import type { SolidityScanReport } from 'lib/solidityScan/schema';
import { ADDRESS_PARAMS } from './addressParams'; import { ADDRESS_PARAMS } from './addressParams';
export const CONTRACT_CODE_UNVERIFIED = { export const CONTRACT_CODE_UNVERIFIED = {
...@@ -78,7 +80,7 @@ export const VERIFIED_CONTRACTS_COUNTERS: VerifiedContractsCounters = { ...@@ -78,7 +80,7 @@ export const VERIFIED_CONTRACTS_COUNTERS: VerifiedContractsCounters = {
new_verified_smart_contracts_24h: '1234', new_verified_smart_contracts_24h: '1234',
}; };
export const SOLIDITYSCAN_REPORT: SolidityscanReport = { export const SOLIDITY_SCAN_REPORT: SolidityScanReport = {
scan_report: { scan_report: {
contractname: 'BullRunners', contractname: 'BullRunners',
scan_status: 'scan_done', scan_status: 'scan_done',
...@@ -91,11 +93,7 @@ export const SOLIDITYSCAN_REPORT: SolidityscanReport = { ...@@ -91,11 +93,7 @@ export const SOLIDITYSCAN_REPORT: SolidityscanReport = {
low: 2, low: 2,
medium: 0, medium: 0,
}, },
lines_analyzed_count: 18,
scan_time_taken: 1,
score: '3.61',
score_v2: '72.22', score_v2: '72.22',
threat_score: '94.74',
}, },
scanner_reference_url: 'https://solidityscan.com/quickscan/0xc1EF7811FF2ebFB74F80ed7423f2AdAA37454be2/blockscout/eth-goerli?ref=blockscout', scanner_reference_url: 'https://solidityscan.com/quickscan/0xc1EF7811FF2ebFB74F80ed7423f2AdAA37454be2/blockscout/eth-goerli?ref=blockscout',
}, },
......
/node_modules
/dist
\ No newline at end of file
This diff is collapsed.
{
"name": "@blockscout/chakra-theme",
"version": "1.32.0",
"main": "./dist/index.js",
"license": "MIT",
"scripts": {
"build": "yarn webpack-cli -c ./webpack.config.js"
},
"devDependencies": {
"ts-loader": "^9.4.4",
"webpack": "^5.88.2",
"webpack-cli": "^5.1.4",
"tsconfig-paths-webpack-plugin": "^4.1.0"
},
"files": [
"/dist"
]
}
{
"extends": "../tsconfig.json",
"compilerOptions": {
"noEmit": false,
"target": "es2016",
"module": "CommonJS",
"moduleResolution": "node",
"paths": {
"nextjs-routes": ["./nextjs/nextjs-routes.d.ts"],
}
},
"include": [
"../types/**/*.ts",
"../configs/app/**/*.ts",
"../global.d.ts",
"./theme.ts"
],
"tsc-alias": {
"verbose": true,
"resolveFullPaths": true,
}
}
\ No newline at end of file
const path = require('path');
const TsconfigPathsPlugin = require('tsconfig-paths-webpack-plugin');
const config = {
mode: 'production',
entry: path.resolve(__dirname) + '/theme.ts',
module: {
rules: [
{
test: /\.tsx?$/,
use: 'ts-loader',
exclude: /node_modules/,
},
],
},
resolve: {
extensions: [ '.tsx', '.ts', '.js' ],
plugins: [ new TsconfigPathsPlugin({ configFile: './tsconfig.json' }) ],
},
output: {
filename: 'index.js',
path: path.resolve(__dirname) + '/dist',
library: {
type: 'commonjs',
},
},
optimization: {
minimize: false,
},
};
module.exports = config;
This diff is collapsed.
...@@ -33,11 +33,14 @@ type ArbitrumL2BatchCommitmentTx = { ...@@ -33,11 +33,14 @@ type ArbitrumL2BatchCommitmentTx = {
timestamp: string; timestamp: string;
} }
type BatchDataContainer = 'in_blob4844' | 'in_calldata' | 'in_anytrust' | 'in_celestia' | null;
export type ArbitrumL2TxnBatchesItem = { export type ArbitrumL2TxnBatchesItem = {
blocks_count: number; blocks_count: number;
commitment_transaction: ArbitrumL2BatchCommitmentTx; commitment_transaction: ArbitrumL2BatchCommitmentTx;
number: number; number: number;
transactions_count: number; transactions_count: number;
batch_data_container: BatchDataContainer;
} }
export type ArbitrumL2TxnBatchesResponse = { export type ArbitrumL2TxnBatchesResponse = {
...@@ -48,6 +51,22 @@ export type ArbitrumL2TxnBatchesResponse = { ...@@ -48,6 +51,22 @@ export type ArbitrumL2TxnBatchesResponse = {
} | null; } | null;
} }
export type ArbitrumL2TxnBatchDAAnytrust = {
batch_data_container: 'in_anytrust';
bls_signature: string;
data_hash: string;
timeout: string;
signers: Array<{
key: string;
trusted: boolean;
proof?: string;
}>;
}
export type ArbitrumL2TxnBatchDataAvailability = ArbitrumL2TxnBatchDAAnytrust | {
batch_data_container: Exclude<BatchDataContainer, 'in_anytrust'>;
}
export type ArbitrumL2TxnBatch = { export type ArbitrumL2TxnBatch = {
after_acc: string; after_acc: string;
before_acc: string; before_acc: string;
...@@ -56,6 +75,7 @@ export type ArbitrumL2TxnBatch = { ...@@ -56,6 +75,7 @@ export type ArbitrumL2TxnBatch = {
start_block: number; start_block: number;
number: number; number: number;
transactions_count: number; transactions_count: number;
data_availability: ArbitrumL2TxnBatchDataAvailability;
} }
export type ArbitrumL2BatchTxs = { export type ArbitrumL2BatchTxs = {
...@@ -84,3 +104,5 @@ export const ARBITRUM_L2_TX_BATCH_STATUSES = [ ...@@ -84,3 +104,5 @@ export const ARBITRUM_L2_TX_BATCH_STATUSES = [
]; ];
export type ArbitrumBatchStatus = typeof ARBITRUM_L2_TX_BATCH_STATUSES[number]; export type ArbitrumBatchStatus = typeof ARBITRUM_L2_TX_BATCH_STATUSES[number];
export type NewArbitrumBatchSocketResponse = { batch: ArbitrumL2TxnBatchesItem }
...@@ -27,7 +27,7 @@ export interface SmartContract { ...@@ -27,7 +27,7 @@ export interface SmartContract {
compiler_version: string | null; compiler_version: string | null;
evm_version: string | null; evm_version: string | null;
optimization_enabled: boolean | null; optimization_enabled: boolean | null;
optimization_runs: number | null; optimization_runs: number | string | null;
name: string | null; name: string | null;
verified_at: string | null; verified_at: string | null;
is_blueprint: boolean | null; is_blueprint: boolean | null;
...@@ -57,6 +57,7 @@ export interface SmartContract { ...@@ -57,6 +57,7 @@ export interface SmartContract {
language: string | null; language: string | null;
license_type: SmartContractLicenseType | null; license_type: SmartContractLicenseType | null;
certified?: boolean; certified?: boolean;
zk_compiler_version?: string;
} }
export type SmartContractDecodedConstructorArg = [ export type SmartContractDecodedConstructorArg = [
...@@ -86,6 +87,8 @@ export interface SmartContractVerificationConfigRaw { ...@@ -86,6 +87,8 @@ export interface SmartContractVerificationConfigRaw {
vyper_evm_versions: Array<string>; vyper_evm_versions: Array<string>;
is_rust_verifier_microservice_enabled: boolean; is_rust_verifier_microservice_enabled: boolean;
license_types: Record<SmartContractLicenseType, number>; license_types: Record<SmartContractLicenseType, number>;
zk_compiler_versions?: Array<string>;
zk_optimization_modes?: Array<string>;
} }
export type SmartContractVerificationResponse = { export type SmartContractVerificationResponse = {
...@@ -104,29 +107,6 @@ export interface SmartContractVerificationError { ...@@ -104,29 +107,6 @@ export interface SmartContractVerificationError {
name?: Array<string>; name?: Array<string>;
} }
export type SolidityscanReport = {
scan_report: {
contractname: string;
scan_status: string;
scan_summary: {
issue_severity_distribution: {
critical: number;
gas: number;
high: number;
informational: number;
low: number;
medium: number;
};
lines_analyzed_count: number;
scan_time_taken: number;
score: string;
score_v2: string;
threat_score: string;
};
scanner_reference_url: string;
};
}
type SmartContractSecurityAudit = { type SmartContractSecurityAudit = {
audit_company_name: string; audit_company_name: string;
audit_publish_date: string; audit_publish_date: string;
......
...@@ -13,6 +13,7 @@ export interface VerifiedContract { ...@@ -13,6 +13,7 @@ export interface VerifiedContract {
verified_at: string; verified_at: string;
market_cap: string | null; market_cap: string | null;
license_type: SmartContractLicenseType | null; license_type: SmartContractLicenseType | null;
zk_compiler_version?: string;
} }
export interface VerifiedContractsResponse { export interface VerifiedContractsResponse {
......
...@@ -111,8 +111,14 @@ type ArbitrumTransactionData = { ...@@ -111,8 +111,14 @@ type ArbitrumTransactionData = {
network_fee: string; network_fee: string;
poster_fee: string; poster_fee: string;
status: ArbitrumBatchStatus; status: ArbitrumBatchStatus;
message_related_info: {
associated_l1_transaction: string | null;
message_status: ArbitrumMessageStatus;
};
} }
export type ArbitrumMessageStatus = 'Relayed' | 'Syncing with base layer' | 'Waiting for confirmation' | 'Ready for relay' | 'Settlement pending';
export const ZKEVM_L2_TX_STATUSES = [ 'Confirmed by Sequencer', 'L1 Confirmed' ]; export const ZKEVM_L2_TX_STATUSES = [ 'Confirmed by Sequencer', 'L1 Confirmed' ];
export interface TransactionsStats { export interface TransactionsStats {
......
import type { SolidityscanReport } from 'types/api/contract'; import type { SolidityScanReport, SolidityScanReportSeverityDistribution } from 'lib/solidityScan/schema';
export type MarketplaceAppPreview = { export type MarketplaceAppPreview = {
id: string; id: string;
...@@ -54,12 +54,12 @@ export type MarketplaceAppSecurityReport = { ...@@ -54,12 +54,12 @@ export type MarketplaceAppSecurityReport = {
solidityScanContractsNumber: number; solidityScanContractsNumber: number;
securityScore: number; securityScore: number;
totalIssues?: number; totalIssues?: number;
issueSeverityDistribution: SolidityscanReport['scan_report']['scan_summary']['issue_severity_distribution']; issueSeverityDistribution: SolidityScanReportSeverityDistribution;
}; };
contractsData: Array<{ contractsData: Array<{
address: string; address: string;
isVerified: boolean; isVerified: boolean;
solidityScanReport?: SolidityscanReport['scan_report'] | null; solidityScanReport?: SolidityScanReport['scan_report'] | null;
}>; }>;
} }
......
...@@ -24,4 +24,6 @@ export interface NetworkExplorer { ...@@ -24,4 +24,6 @@ export interface NetworkExplorer {
}; };
} }
export type NetworkVerificationType = 'mining' | 'validation'; export type NetworkVerificationTypeEnvs = 'mining' | 'validation';
export type NetworkVerificationTypeComputed = 'posting' | 'sequencing';
export type NetworkVerificationType = NetworkVerificationTypeEnvs | NetworkVerificationTypeComputed;
...@@ -18,12 +18,14 @@ import ActionBar, { ACTION_BAR_HEIGHT_DESKTOP } from 'ui/shared/ActionBar'; ...@@ -18,12 +18,14 @@ import ActionBar, { ACTION_BAR_HEIGHT_DESKTOP } from 'ui/shared/ActionBar';
import DataListDisplay from 'ui/shared/DataListDisplay'; import DataListDisplay from 'ui/shared/DataListDisplay';
import Pagination from 'ui/shared/pagination/Pagination'; import Pagination from 'ui/shared/pagination/Pagination';
import useQueryWithPages from 'ui/shared/pagination/useQueryWithPages'; import useQueryWithPages from 'ui/shared/pagination/useQueryWithPages';
import SocketAlert from 'ui/shared/SocketAlert'; import * as SocketNewItemsNotice from 'ui/shared/SocketNewItemsNotice';
import { default as Thead } from 'ui/shared/TheadSticky'; import { default as Thead } from 'ui/shared/TheadSticky';
import AddressBlocksValidatedListItem from './blocksValidated/AddressBlocksValidatedListItem'; import AddressBlocksValidatedListItem from './blocksValidated/AddressBlocksValidatedListItem';
import AddressBlocksValidatedTableItem from './blocksValidated/AddressBlocksValidatedTableItem'; import AddressBlocksValidatedTableItem from './blocksValidated/AddressBlocksValidatedTableItem';
const OVERLOAD_COUNT = 75;
interface Props { interface Props {
scrollRef?: React.RefObject<HTMLDivElement>; scrollRef?: React.RefObject<HTMLDivElement>;
shouldRender?: boolean; shouldRender?: boolean;
...@@ -31,7 +33,9 @@ interface Props { ...@@ -31,7 +33,9 @@ interface Props {
} }
const AddressBlocksValidated = ({ scrollRef, shouldRender = true, isQueryEnabled = true }: Props) => { const AddressBlocksValidated = ({ scrollRef, shouldRender = true, isQueryEnabled = true }: Props) => {
const [ socketAlert, setSocketAlert ] = React.useState(false); const [ socketAlert, setSocketAlert ] = React.useState('');
const [ newItemsCount, setNewItemsCount ] = React.useState(0);
const queryClient = useQueryClient(); const queryClient = useQueryClient();
const router = useRouter(); const router = useRouter();
const isMounted = useIsMounted(); const isMounted = useIsMounted();
...@@ -57,11 +61,11 @@ const AddressBlocksValidated = ({ scrollRef, shouldRender = true, isQueryEnabled ...@@ -57,11 +61,11 @@ const AddressBlocksValidated = ({ scrollRef, shouldRender = true, isQueryEnabled
}); });
const handleSocketError = React.useCallback(() => { const handleSocketError = React.useCallback(() => {
setSocketAlert(true); setSocketAlert('An error has occurred while fetching new blocks. Please refresh the page to load new blocks.');
}, []); }, []);
const handleNewSocketMessage: SocketMessage.NewBlock['handler'] = React.useCallback((payload) => { const handleNewSocketMessage: SocketMessage.NewBlock['handler'] = React.useCallback((payload) => {
setSocketAlert(false); setSocketAlert('');
queryClient.setQueryData( queryClient.setQueryData(
getResourceKey('address_blocks_validated', { pathParams: { hash: addressHash } }), getResourceKey('address_blocks_validated', { pathParams: { hash: addressHash } }),
...@@ -70,6 +74,11 @@ const AddressBlocksValidated = ({ scrollRef, shouldRender = true, isQueryEnabled ...@@ -70,6 +74,11 @@ const AddressBlocksValidated = ({ scrollRef, shouldRender = true, isQueryEnabled
return; return;
} }
if (prevData.items.length >= OVERLOAD_COUNT) {
setNewItemsCount(prev => prev + 1);
return prevData;
}
return { return {
...prevData, ...prevData,
items: [ payload.block, ...prevData.items ], items: [ payload.block, ...prevData.items ],
...@@ -95,20 +104,26 @@ const AddressBlocksValidated = ({ scrollRef, shouldRender = true, isQueryEnabled ...@@ -95,20 +104,26 @@ const AddressBlocksValidated = ({ scrollRef, shouldRender = true, isQueryEnabled
const content = query.data?.items ? ( const content = query.data?.items ? (
<> <>
{ socketAlert && <SocketAlert mb={ 6 }/> }
<Hide below="lg" ssr={ false }> <Hide below="lg" ssr={ false }>
<Table variant="simple" size="sm"> <Table variant="simple" size="sm" style={{ tableLayout: 'auto' }}>
<Thead top={ query.pagination.isVisible ? ACTION_BAR_HEIGHT_DESKTOP : 0 }> <Thead top={ query.pagination.isVisible ? ACTION_BAR_HEIGHT_DESKTOP : 0 }>
<Tr> <Tr>
<Th width="17%">Block</Th> <Th>Block</Th>
<Th width="17%">Age</Th> <Th>Age</Th>
<Th width="16%">Txn</Th> <Th>Txn</Th>
<Th width="25%">Gas used</Th> <Th>Gas used</Th>
{ !config.UI.views.block.hiddenFields?.total_reward && { !config.UI.views.block.hiddenFields?.total_reward && !config.features.rollup.isEnabled &&
<Th width="25%" isNumeric>Reward { currencyUnits.ether }</Th> } <Th isNumeric>Reward { currencyUnits.ether }</Th> }
</Tr> </Tr>
</Thead> </Thead>
<Tbody> <Tbody>
<SocketNewItemsNotice.Desktop
url={ window.location.href }
num={ newItemsCount }
alert={ socketAlert }
type="block"
isLoading={ query.isPlaceholderData }
/>
{ query.data.items.map((item, index) => ( { query.data.items.map((item, index) => (
<AddressBlocksValidatedTableItem <AddressBlocksValidatedTableItem
key={ item.height + (query.isPlaceholderData ? String(index) : '') } key={ item.height + (query.isPlaceholderData ? String(index) : '') }
...@@ -121,6 +136,15 @@ const AddressBlocksValidated = ({ scrollRef, shouldRender = true, isQueryEnabled ...@@ -121,6 +136,15 @@ const AddressBlocksValidated = ({ scrollRef, shouldRender = true, isQueryEnabled
</Table> </Table>
</Hide> </Hide>
<Show below="lg" ssr={ false }> <Show below="lg" ssr={ false }>
{ query.pagination.page === 1 && (
<SocketNewItemsNotice.Mobile
url={ window.location.href }
num={ newItemsCount }
alert={ socketAlert }
type="block"
isLoading={ query.isPlaceholderData }
/>
) }
{ query.data.items.map((item, index) => ( { query.data.items.map((item, index) => (
<AddressBlocksValidatedListItem <AddressBlocksValidatedListItem
key={ item.height + (query.isPlaceholderData ? String(index) : '') } key={ item.height + (query.isPlaceholderData ? String(index) : '') }
......
...@@ -9,7 +9,7 @@ const addressHash = 'hash'; ...@@ -9,7 +9,7 @@ const addressHash = 'hash';
test('average report +@dark-mode +@mobile', async({ render, mockApiResponse, page }) => { test('average report +@dark-mode +@mobile', async({ render, mockApiResponse, page }) => {
await mockApiResponse( await mockApiResponse(
'contract_solidityscan_report', 'contract_solidity_scan_report',
solidityscanReportMock.solidityscanReportAverage, solidityscanReportMock.solidityscanReportAverage,
{ pathParams: { hash: addressHash } }, { pathParams: { hash: addressHash } },
); );
...@@ -23,7 +23,7 @@ test('average report +@dark-mode +@mobile', async({ render, mockApiResponse, pag ...@@ -23,7 +23,7 @@ test('average report +@dark-mode +@mobile', async({ render, mockApiResponse, pag
test('great report', async({ render, mockApiResponse, page }) => { test('great report', async({ render, mockApiResponse, page }) => {
await mockApiResponse( await mockApiResponse(
'contract_solidityscan_report', 'contract_solidity_scan_report',
solidityscanReportMock.solidityscanReportGreat, solidityscanReportMock.solidityscanReportGreat,
{ pathParams: { hash: addressHash } }, { pathParams: { hash: addressHash } },
); );
...@@ -41,7 +41,7 @@ test('great report', async({ render, mockApiResponse, page }) => { ...@@ -41,7 +41,7 @@ test('great report', async({ render, mockApiResponse, page }) => {
test('low report', async({ render, mockApiResponse, page }) => { test('low report', async({ render, mockApiResponse, page }) => {
await mockApiResponse( await mockApiResponse(
'contract_solidityscan_report', 'contract_solidity_scan_report',
solidityscanReportMock.solidityscanReportLow, solidityscanReportMock.solidityscanReportLow,
{ pathParams: { hash: addressHash } }, { pathParams: { hash: addressHash } },
); );
......
...@@ -5,8 +5,7 @@ import React from 'react'; ...@@ -5,8 +5,7 @@ import React from 'react';
// Probably because of the gradient // Probably because of the gradient
// eslint-disable-next-line no-restricted-imports // eslint-disable-next-line no-restricted-imports
import solidityScanIcon from 'icons/brands/solidity_scan.svg'; import solidityScanIcon from 'icons/brands/solidity_scan.svg';
import useApiQuery from 'lib/api/useApiQuery'; import useFetchReport from 'lib/solidityScan/useFetchReport';
import { SOLIDITYSCAN_REPORT } from 'stubs/contract';
import Popover from 'ui/shared/chakra/Popover'; import Popover from 'ui/shared/chakra/Popover';
import LinkExternal from 'ui/shared/links/LinkExternal'; import LinkExternal from 'ui/shared/links/LinkExternal';
import SolidityscanReportButton from 'ui/shared/solidityscanReport/SolidityscanReportButton'; import SolidityscanReportButton from 'ui/shared/solidityscanReport/SolidityscanReportButton';
...@@ -20,22 +19,19 @@ interface Props { ...@@ -20,22 +19,19 @@ interface Props {
const SolidityscanReport = ({ hash }: Props) => { const SolidityscanReport = ({ hash }: Props) => {
const { isOpen, onToggle, onClose } = useDisclosure(); const { isOpen, onToggle, onClose } = useDisclosure();
const { data, isPlaceholderData, isError } = useApiQuery('contract_solidityscan_report', { const { data, isPlaceholderData, isError } = useFetchReport({ hash });
pathParams: { hash },
queryOptions: {
enabled: Boolean(hash),
placeholderData: SOLIDITYSCAN_REPORT,
retry: 0,
},
});
const score = Number(data?.scan_report.scan_summary.score_v2); if (isError || !data) {
return null;
}
const score = Number(data.scan_report.scan_summary.score_v2);
if (isError || !score) { if (!score) {
return null; return null;
} }
const vulnerabilities = data?.scan_report.scan_summary.issue_severity_distribution; const vulnerabilities = data.scan_report.scan_summary.issue_severity_distribution;
const vulnerabilitiesCounts = vulnerabilities ? Object.values(vulnerabilities) : []; const vulnerabilitiesCounts = vulnerabilities ? Object.values(vulnerabilities) : [];
const vulnerabilitiesCount = vulnerabilitiesCounts.reduce((acc, val) => acc + val, 0); const vulnerabilitiesCount = vulnerabilitiesCounts.reduce((acc, val) => acc + val, 0);
...@@ -63,7 +59,7 @@ const SolidityscanReport = ({ hash }: Props) => { ...@@ -63,7 +59,7 @@ const SolidityscanReport = ({ hash }: Props) => {
<SolidityscanReportDetails vulnerabilities={ vulnerabilities } vulnerabilitiesCount={ vulnerabilitiesCount }/> <SolidityscanReportDetails vulnerabilities={ vulnerabilities } vulnerabilitiesCount={ vulnerabilitiesCount }/>
</Box> </Box>
) } ) }
<LinkExternal href={ data?.scan_report.scanner_reference_url }>View full report</LinkExternal> <LinkExternal href={ data.scan_report.scanner_reference_url }>View full report</LinkExternal>
</PopoverBody> </PopoverBody>
</PopoverContent> </PopoverContent>
</Popover> </Popover>
......
...@@ -41,7 +41,7 @@ const AddressAccountHistoryListItem = (props: Props) => { ...@@ -41,7 +41,7 @@ const AddressAccountHistoryListItem = (props: Props) => {
</Text> </Text>
</Flex> </Flex>
<TimeAgoWithTooltip <TimeAgoWithTooltip
timestamp={ (props.tx.rawTransactionData.timestamp * 1000).toString() } timestamp={ props.tx.rawTransactionData.timestamp * 1000 }
color="text_secondary" color="text_secondary"
borderRadius="sm" borderRadius="sm"
fontWeight={ 500 } fontWeight={ 500 }
......
...@@ -26,7 +26,7 @@ const AddressAccountHistoryTableItem = (props: Props) => { ...@@ -26,7 +26,7 @@ const AddressAccountHistoryTableItem = (props: Props) => {
<Tr> <Tr>
<Td px={ 3 } py="18px" fontSize="sm" > <Td px={ 3 } py="18px" fontSize="sm" >
<TimeAgoWithTooltip <TimeAgoWithTooltip
timestamp={ (props.tx.rawTransactionData.timestamp * 1000).toString() } timestamp={ props.tx.rawTransactionData.timestamp * 1000 }
isLoading={ props.isPlaceholderData } isLoading={ props.isPlaceholderData }
color="text_secondary" color="text_secondary"
borderRadius="sm" borderRadius="sm"
......
...@@ -52,7 +52,7 @@ const AddressBlocksValidatedListItem = (props: Props) => { ...@@ -52,7 +52,7 @@ const AddressBlocksValidatedListItem = (props: Props) => {
isLoading={ props.isLoading } isLoading={ props.isLoading }
/> />
</Flex> </Flex>
{ !config.UI.views.block.hiddenFields?.total_reward && ( { !config.UI.views.block.hiddenFields?.total_reward && !config.features.rollup.isEnabled && (
<Flex columnGap={ 2 } w="100%"> <Flex columnGap={ 2 } w="100%">
<Skeleton isLoaded={ !props.isLoading } fontWeight={ 500 } flexShrink={ 0 }>Reward { currencyUnits.ether }</Skeleton> <Skeleton isLoaded={ !props.isLoading } fontWeight={ 500 } flexShrink={ 0 }>Reward { currencyUnits.ether }</Skeleton>
<Skeleton isLoaded={ !props.isLoading } color="text_secondary">{ totalReward.toFixed() }</Skeleton> <Skeleton isLoaded={ !props.isLoading } color="text_secondary">{ totalReward.toFixed() }</Skeleton>
......
...@@ -56,7 +56,7 @@ const AddressBlocksValidatedTableItem = (props: Props) => { ...@@ -56,7 +56,7 @@ const AddressBlocksValidatedTableItem = (props: Props) => {
/> />
</Flex> </Flex>
</Td> </Td>
{ !config.UI.views.block.hiddenFields?.total_reward && ( { !config.UI.views.block.hiddenFields?.total_reward && !config.features.rollup.isEnabled && (
<Td isNumeric display="flex" justifyContent="end"> <Td isNumeric display="flex" justifyContent="end">
<Skeleton isLoaded={ !props.isLoading } display="inline-block"> <Skeleton isLoaded={ !props.isLoading } display="inline-block">
<span>{ totalReward.toFixed() }</span> <span>{ totalReward.toFixed() }</span>
......
...@@ -126,6 +126,14 @@ test('non verified', async({ render, mockApiResponse }) => { ...@@ -126,6 +126,14 @@ test('non verified', async({ render, mockApiResponse }) => {
await expect(component).toHaveScreenshot(); await expect(component).toHaveScreenshot();
}); });
test('zkSync contract', async({ render, mockApiResponse, page, mockEnvs }) => {
await mockEnvs(ENVS_MAP.zkSyncRollup);
await mockApiResponse('contract', contractMock.zkSync, { pathParams: { hash: addressMock.contract.hash } });
await render(<ContractCode/>, { hooksConfig }, { withSocket: true });
await expect(page).toHaveScreenshot({ clip: { x: 0, y: 0, width: 1200, height: 300 } });
});
test.describe('with audits feature', () => { test.describe('with audits feature', () => {
test.beforeEach(async({ mockEnvs }) => { test.beforeEach(async({ mockEnvs }) => {
......
...@@ -60,6 +60,8 @@ const InfoItem = chakra(({ label, content, hint, className, isLoading }: InfoIte ...@@ -60,6 +60,8 @@ const InfoItem = chakra(({ label, content, hint, className, isLoading }: InfoIte
</GridItem> </GridItem>
)); ));
const rollupFeature = config.features.rollup;
const ContractCode = ({ addressHash, contractQuery, channel }: Props) => { const ContractCode = ({ addressHash, contractQuery, channel }: Props) => {
const [ isChangedBytecodeSocket, setIsChangedBytecodeSocket ] = React.useState<boolean>(); const [ isChangedBytecodeSocket, setIsChangedBytecodeSocket ] = React.useState<boolean>();
...@@ -266,6 +268,7 @@ const ContractCode = ({ addressHash, contractQuery, channel }: Props) => { ...@@ -266,6 +268,7 @@ const ContractCode = ({ addressHash, contractQuery, channel }: Props) => {
<Grid templateColumns={{ base: '1fr', lg: '1fr 1fr' }} rowGap={ 4 } columnGap={ 6 } mb={ 8 }> <Grid templateColumns={{ base: '1fr', lg: '1fr 1fr' }} rowGap={ 4 } columnGap={ 6 } mb={ 8 }>
{ data.name && <InfoItem label="Contract name" content={ contractNameWithCertifiedIcon } isLoading={ isPlaceholderData }/> } { data.name && <InfoItem label="Contract name" content={ contractNameWithCertifiedIcon } isLoading={ isPlaceholderData }/> }
{ data.compiler_version && <InfoItem label="Compiler version" content={ data.compiler_version } isLoading={ isPlaceholderData }/> } { data.compiler_version && <InfoItem label="Compiler version" content={ data.compiler_version } isLoading={ isPlaceholderData }/> }
{ data.zk_compiler_version && <InfoItem label="ZK compiler version" content={ data.zk_compiler_version } isLoading={ isPlaceholderData }/> }
{ data.evm_version && <InfoItem label="EVM version" content={ data.evm_version } textTransform="capitalize" isLoading={ isPlaceholderData }/> } { data.evm_version && <InfoItem label="EVM version" content={ data.evm_version } textTransform="capitalize" isLoading={ isPlaceholderData }/> }
{ licenseLink && ( { licenseLink && (
<InfoItem <InfoItem
...@@ -277,8 +280,13 @@ const ContractCode = ({ addressHash, contractQuery, channel }: Props) => { ...@@ -277,8 +280,13 @@ const ContractCode = ({ addressHash, contractQuery, channel }: Props) => {
) } ) }
{ typeof data.optimization_enabled === 'boolean' && { typeof data.optimization_enabled === 'boolean' &&
<InfoItem label="Optimization enabled" content={ data.optimization_enabled ? 'true' : 'false' } isLoading={ isPlaceholderData }/> } <InfoItem label="Optimization enabled" content={ data.optimization_enabled ? 'true' : 'false' } isLoading={ isPlaceholderData }/> }
{ data.optimization_runs !== null && { data.optimization_runs !== null && (
<InfoItem label="Optimization runs" content={ String(data.optimization_runs) } isLoading={ isPlaceholderData }/> } <InfoItem
label={ rollupFeature.isEnabled && rollupFeature.type === 'zkSync' ? 'Optimization mode' : 'Optimization runs' }
content={ String(data.optimization_runs) }
isLoading={ isPlaceholderData }
/>
) }
{ data.verified_at && { data.verified_at &&
<InfoItem label="Verified at" content={ dayjs(data.verified_at).format('llll') } wordBreak="break-word" isLoading={ isPlaceholderData }/> } <InfoItem label="Verified at" content={ dayjs(data.verified_at).format('llll') } wordBreak="break-word" isLoading={ isPlaceholderData }/> }
{ data.file_path && <InfoItem label="Contract file path" content={ data.file_path } wordBreak="break-word" isLoading={ isPlaceholderData }/> } { data.file_path && <InfoItem label="Contract file path" content={ data.file_path } wordBreak="break-word" isLoading={ isPlaceholderData }/> }
......
...@@ -31,7 +31,7 @@ import PopoverTriggerTooltip from 'ui/shared/PopoverTriggerTooltip'; ...@@ -31,7 +31,7 @@ import PopoverTriggerTooltip from 'ui/shared/PopoverTriggerTooltip';
interface Props { interface Props {
query: UseQueryResult<bens.LookupAddressResponse, ResourceError<unknown>>; query: UseQueryResult<bens.LookupAddressResponse, ResourceError<unknown>>;
addressHash: string; addressHash: string;
mainDomainName: string | null; mainDomainName: string | null | undefined;
} }
const DomainsGrid = ({ data }: { data: Array<bens.Domain> }) => { const DomainsGrid = ({ data }: { data: Array<bens.Domain> }) => {
...@@ -64,9 +64,9 @@ const AddressEnsDomains = ({ query, addressHash, mainDomainName }: Props) => { ...@@ -64,9 +64,9 @@ const AddressEnsDomains = ({ query, addressHash, mainDomainName }: Props) => {
return null; return null;
} }
const mainDomain = data.items.find((domain) => domain.name === mainDomainName); const mainDomain = data.items.find((domain) => mainDomainName && domain.name === mainDomainName);
const ownedDomains = data.items.filter((domain) => { const ownedDomains = data.items.filter((domain) => {
if (domain.name === mainDomainName) { if (mainDomainName && domain.name === mainDomainName) {
return false; return false;
} }
......
...@@ -10,6 +10,7 @@ import { route } from 'nextjs-routes'; ...@@ -10,6 +10,7 @@ import { route } from 'nextjs-routes';
import capitalizeFirstLetter from 'lib/capitalizeFirstLetter'; import capitalizeFirstLetter from 'lib/capitalizeFirstLetter';
import dayjs from 'lib/date/dayjs'; import dayjs from 'lib/date/dayjs';
import useIsMobile from 'lib/hooks/useIsMobile'; import useIsMobile from 'lib/hooks/useIsMobile';
import CopyToClipboard from 'ui/shared/CopyToClipboard';
import IconSvg from 'ui/shared/IconSvg'; import IconSvg from 'ui/shared/IconSvg';
import LinkInternal from 'ui/shared/links/LinkInternal'; import LinkInternal from 'ui/shared/links/LinkInternal';
import { default as Thead } from 'ui/shared/TheadSticky'; import { default as Thead } from 'ui/shared/TheadSticky';
...@@ -29,7 +30,7 @@ type Props = { ...@@ -29,7 +30,7 @@ type Props = {
toggleSorting: (key: AddressMudRecordsSorting['sort']) => void; toggleSorting: (key: AddressMudRecordsSorting['sort']) => void;
setFilters: React.Dispatch<React.SetStateAction<AddressMudRecordsFilter>>; setFilters: React.Dispatch<React.SetStateAction<AddressMudRecordsFilter>>;
filters: AddressMudRecordsFilter; filters: AddressMudRecordsFilter;
toggleTableHasHorisontalScroll: () => void; toggleTableHasHorizontalScroll: () => void;
scrollRef?: React.RefObject<HTMLDivElement>; scrollRef?: React.RefObject<HTMLDivElement>;
hash: string; hash: string;
} }
...@@ -41,7 +42,7 @@ const AddressMudRecordsTable = ({ ...@@ -41,7 +42,7 @@ const AddressMudRecordsTable = ({
toggleSorting, toggleSorting,
filters, filters,
setFilters, setFilters,
toggleTableHasHorisontalScroll, toggleTableHasHorizontalScroll,
scrollRef, scrollRef,
hash, hash,
}: Props) => { }: Props) => {
...@@ -59,8 +60,8 @@ const AddressMudRecordsTable = ({ ...@@ -59,8 +60,8 @@ const AddressMudRecordsTable = ({
const toggleIsOpen = React.useCallback(() => { const toggleIsOpen = React.useCallback(() => {
isOpened && tableRef.current?.scroll({ left: 0 }); isOpened && tableRef.current?.scroll({ left: 0 });
setIsOpened.toggle(); setIsOpened.toggle();
toggleTableHasHorisontalScroll(); toggleTableHasHorizontalScroll();
}, [ setIsOpened, toggleTableHasHorisontalScroll, isOpened ]); }, [ setIsOpened, toggleTableHasHorizontalScroll, isOpened ]);
const onRecordClick = React.useCallback((e: React.MouseEvent) => { const onRecordClick = React.useCallback((e: React.MouseEvent) => {
if (e.metaKey || e.ctrlKey) { if (e.metaKey || e.ctrlKey) {
...@@ -109,18 +110,19 @@ const AddressMudRecordsTable = ({ ...@@ -109,18 +110,19 @@ const AddressMudRecordsTable = ({
const colW = isMobile ? COL_MIN_WIDTH_MOBILE : COL_MIN_WIDTH; const colW = isMobile ? COL_MIN_WIDTH_MOBILE : COL_MIN_WIDTH;
const keys = (isOpened || !hasCut) ? data.schema.key_names : data.schema.key_names.slice(0, colsCutCount);
const values = (isOpened || !hasCut) ? data.schema.value_names : data.schema.value_names.slice(0, colsCutCount - data.schema.key_names.length);
const colsCount = (isOpened || !hasCut) ? totalColsCut : colsCutCount;
const tdStyles: StyleProps = { const tdStyles: StyleProps = {
wordBreak: 'break-word', wordBreak: 'break-word',
whiteSpace: 'normal', whiteSpace: 'normal',
minW: `${ colW }px`, minW: `${ colW }px`,
w: `${ colW }px`, w: `${ 100 / colsCount }%`,
verticalAlign: 'top', verticalAlign: 'top',
lineHeight: '20px', lineHeight: '20px',
}; };
const keys = (isOpened || !hasCut) ? data.schema.key_names : data.schema.key_names.slice(0, colsCutCount);
const values = (isOpened || !hasCut) ? data.schema.value_names : data.schema.value_names.slice(0, colsCutCount - data.schema.key_names.length);
const hasHorizontalScroll = isMobile || isOpened; const hasHorizontalScroll = isMobile || isOpened;
if (hasCut && !colsCutCount) { if (hasCut && !colsCutCount) {
...@@ -136,7 +138,7 @@ const AddressMudRecordsTable = ({ ...@@ -136,7 +138,7 @@ const AddressMudRecordsTable = ({
); );
return ( return (
// can't implement both horisontal table scroll and sticky header // can't implement both horizontal table scroll and sticky header
<Box maxW="100%" overflowX={ hasHorizontalScroll ? 'scroll' : 'unset' } whiteSpace="nowrap" ref={ tableRef }> <Box maxW="100%" overflowX={ hasHorizontalScroll ? 'scroll' : 'unset' } whiteSpace="nowrap" ref={ tableRef }>
<Table variant="simple" size="sm" style={{ tableLayout: 'fixed' }}> <Table variant="simple" size="sm" style={{ tableLayout: 'fixed' }}>
<Thead top={ hasHorizontalScroll ? 0 : top } display={ hasHorizontalScroll ? 'table' : 'table-header-group' } w="100%"> <Thead top={ hasHorizontalScroll ? 0 : top } display={ hasHorizontalScroll ? 'table' : 'table-header-group' } w="100%">
...@@ -185,7 +187,7 @@ const AddressMudRecordsTable = ({ ...@@ -185,7 +187,7 @@ const AddressMudRecordsTable = ({
</Th> </Th>
)) } )) }
{ hasCut && !isOpened && cutButton } { hasCut && !isOpened && cutButton }
<Th { ...tdStyles }>Modified</Th> <Th { ...tdStyles } w={ `${ colW }px` }>Modified</Th>
{ hasCut && isOpened && cutButton } { hasCut && isOpened && cutButton }
</Tr> </Tr>
</Thead> </Thead>
...@@ -204,12 +206,13 @@ const AddressMudRecordsTable = ({ ...@@ -204,12 +206,13 @@ const AddressMudRecordsTable = ({
{ getValueString(item.decoded[keyName]) } { getValueString(item.decoded[keyName]) }
</LinkInternal> </LinkInternal>
) : getValueString(item.decoded[keyName]) } ) : getValueString(item.decoded[keyName]) }
<CopyToClipboard text={ item.decoded[keyName] }/>
</Td> </Td>
)) } )) }
{ values.map((valName) => { values.map((valName) =>
<Td key={ valName } { ...tdStyles }>{ getValueString(item.decoded[valName]) }</Td>) } <Td key={ valName } { ...tdStyles }>{ getValueString(item.decoded[valName]) }</Td>) }
{ hasCut && !isOpened && <Td width={ `${ CUT_COL_WIDTH }px ` }></Td> } { hasCut && !isOpened && <Td width={ `${ CUT_COL_WIDTH }px ` }></Td> }
<Td { ...tdStyles } color="text_secondary">{ dayjs(item.timestamp).format('lll') }</Td> <Td { ...tdStyles } color="text_secondary" w={ `${ colW }px` }>{ dayjs(item.timestamp).format('lll') }</Td>
{ hasCut && isOpened && <Td width={ `${ CUT_COL_WIDTH }px ` }></Td> } { hasCut && isOpened && <Td width={ `${ CUT_COL_WIDTH }px ` }></Td> }
</Tr> </Tr>
)) } )) }
......
...@@ -36,7 +36,7 @@ const AddressMudTable = ({ scrollRef, tableId, isQueryEnabled = true }: Props) = ...@@ -36,7 +36,7 @@ const AddressMudTable = ({ scrollRef, tableId, isQueryEnabled = true }: Props) =
React.useState<AddressMudRecordsSorting | undefined>(getSortParamsFromQuery<AddressMudRecordsSorting>(router.query, SORT_SEQUENCE)); React.useState<AddressMudRecordsSorting | undefined>(getSortParamsFromQuery<AddressMudRecordsSorting>(router.query, SORT_SEQUENCE));
const [ filters, setFilters ] = React.useState<AddressMudRecordsFilter>({}); const [ filters, setFilters ] = React.useState<AddressMudRecordsFilter>({});
const isMobile = useIsMobile(); const isMobile = useIsMobile();
const [ tableHasHorisontalScroll, setTableHasHorisontalScroll ] = useBoolean(isMobile); const [ tableHasHorizontalScroll, setTableHasHorizontalScroll ] = useBoolean(isMobile);
const hash = getQueryParamString(router.query.hash); const hash = getQueryParamString(router.query.hash);
...@@ -109,7 +109,7 @@ const AddressMudTable = ({ scrollRef, tableId, isQueryEnabled = true }: Props) = ...@@ -109,7 +109,7 @@ const AddressMudTable = ({ scrollRef, tableId, isQueryEnabled = true }: Props) =
) : null; ) : null;
const actionBar = (!isMobile || hasActiveFilters || pagination.isVisible) && ( const actionBar = (!isMobile || hasActiveFilters || pagination.isVisible) && (
<ActionBar mt={ -6 } showShadow={ tableHasHorisontalScroll } justifyContent="space-between" alignItems={ hasActiveFilters ? 'start' : 'center' }> <ActionBar mt={ -6 } showShadow={ tableHasHorizontalScroll } justifyContent="space-between" alignItems={ hasActiveFilters ? 'start' : 'center' }>
<Box> <Box>
{ !isMobile && breadcrumbs } { !isMobile && breadcrumbs }
{ filtersTags } { filtersTags }
...@@ -126,7 +126,7 @@ const AddressMudTable = ({ scrollRef, tableId, isQueryEnabled = true }: Props) = ...@@ -126,7 +126,7 @@ const AddressMudTable = ({ scrollRef, tableId, isQueryEnabled = true }: Props) =
toggleSorting={ toggleSorting } toggleSorting={ toggleSorting }
setFilters={ setFilters } setFilters={ setFilters }
filters={ filters } filters={ filters }
toggleTableHasHorisontalScroll={ setTableHasHorisontalScroll.toggle } toggleTableHasHorizontalScroll={ setTableHasHorizontalScroll.toggle }
scrollRef={ scrollRef } scrollRef={ scrollRef }
hash={ hash } hash={ hash }
/> />
......
...@@ -43,7 +43,7 @@ const AddressMudTablesTableItem = ({ item, isLoading, scrollRef, hash }: Props) ...@@ -43,7 +43,7 @@ const AddressMudTablesTableItem = ({ item, isLoading, scrollRef, hash }: Props)
return ( return (
<> <>
<Tr borderStyle={ isOpened ? 'hidden' : 'unset' }> <Tr borderBottomStyle={ isOpened ? 'hidden' : 'unset' }>
<Td verticalAlign="middle"> <Td verticalAlign="middle">
<Skeleton isLoaded={ !isLoading }> <Skeleton isLoaded={ !isLoading }>
<Link display="block"> <Link display="block">
......
...@@ -25,8 +25,7 @@ const AddressesTable = ({ items, totalSupply, pageStartIndex, top, isLoading }: ...@@ -25,8 +25,7 @@ const AddressesTable = ({ items, totalSupply, pageStartIndex, top, isLoading }:
<Thead top={ top }> <Thead top={ top }>
<Tr> <Tr>
<Th width="64px">Rank</Th> <Th width="64px">Rank</Th>
<Th width={ hasPercentage ? '30%' : '40%' }>Address</Th> <Th width={ hasPercentage ? '50%' : '60%' }>Address</Th>
<Th width="20%" pl={ 10 }>Public tag</Th>
<Th width={ hasPercentage ? '20%' : '25%' } isNumeric>{ `Balance ${ currencyUnits.ether }` }</Th> <Th width={ hasPercentage ? '20%' : '25%' } isNumeric>{ `Balance ${ currencyUnits.ether }` }</Th>
{ hasPercentage && <Th width="15%" isNumeric>Percentage</Th> } { hasPercentage && <Th width="15%" isNumeric>Percentage</Th> }
<Th width="15%" isNumeric>Txn count</Th> <Th width="15%" isNumeric>Txn count</Th>
......
import { Tr, Td, Text, Skeleton } from '@chakra-ui/react'; import { Tr, Td, Text, Skeleton, Flex } from '@chakra-ui/react';
import BigNumber from 'bignumber.js'; import BigNumber from 'bignumber.js';
import React from 'react'; import React from 'react';
...@@ -35,17 +35,17 @@ const AddressesTableItem = ({ ...@@ -35,17 +35,17 @@ const AddressesTableItem = ({
</Skeleton> </Skeleton>
</Td> </Td>
<Td> <Td>
<Flex alignItems="center" columnGap={ 2 }>
<AddressEntity <AddressEntity
address={ item } address={ item }
isLoading={ isLoading } isLoading={ isLoading }
fontWeight={ 700 } fontWeight={ 700 }
my="2px" my="2px"
/> />
</Td>
<Td pl={ 10 }>
{ item.public_tags && item.public_tags.length ? item.public_tags.map(tag => ( { item.public_tags && item.public_tags.length ? item.public_tags.map(tag => (
<Tag key={ tag.label } isLoading={ isLoading } isTruncated>{ tag.display_name }</Tag> <Tag key={ tag.label } isLoading={ isLoading } isTruncated>{ tag.display_name }</Tag>
)) : null } )) : null }
</Flex>
</Td> </Td>
<Td isNumeric> <Td isNumeric>
<Skeleton isLoaded={ !isLoading } display="inline-block" maxW="100%"> <Skeleton isLoaded={ !isLoading } display="inline-block" maxW="100%">
......
...@@ -15,6 +15,7 @@ import getBlockReward from 'lib/block/getBlockReward'; ...@@ -15,6 +15,7 @@ import getBlockReward from 'lib/block/getBlockReward';
import { GWEI, WEI, WEI_IN_GWEI, ZERO } from 'lib/consts'; import { GWEI, WEI, WEI_IN_GWEI, ZERO } from 'lib/consts';
import getArbitrumVerificationStepStatus from 'lib/getArbitrumVerificationStepStatus'; import getArbitrumVerificationStepStatus from 'lib/getArbitrumVerificationStepStatus';
import { space } from 'lib/html-entities'; import { space } from 'lib/html-entities';
import getNetworkValidationActionText from 'lib/networks/getNetworkValidationActionText';
import getNetworkValidatorTitle from 'lib/networks/getNetworkValidatorTitle'; import getNetworkValidatorTitle from 'lib/networks/getNetworkValidatorTitle';
import getQueryParamString from 'lib/router/getQueryParamString'; import getQueryParamString from 'lib/router/getQueryParamString';
import { currencyUnits } from 'lib/units'; import { currencyUnits } from 'lib/units';
...@@ -114,13 +115,7 @@ const BlockDetails = ({ query }: Props) => { ...@@ -114,13 +115,7 @@ const BlockDetails = ({ query }: Props) => {
); );
})(); })();
const verificationTitle = (() => { const verificationTitle = `${ capitalize(getNetworkValidationActionText()) } by`;
if (rollupFeature.isEnabled && rollupFeature.type === 'zkEvm') {
return 'Sequenced by';
}
return config.chain.verificationType === 'validation' ? 'Validated by' : 'Mined by';
})();
const txsNum = (() => { const txsNum = (() => {
const blockTxsNum = ( const blockTxsNum = (
......
...@@ -2,6 +2,7 @@ import React from 'react'; ...@@ -2,6 +2,7 @@ import React from 'react';
import type { SmartContractVerificationConfig } from 'types/client/contract'; import type { SmartContractVerificationConfig } from 'types/client/contract';
import { ENVS_MAP } from 'playwright/fixtures/mockEnvs';
import * as socketServer from 'playwright/fixtures/socketServer'; import * as socketServer from 'playwright/fixtures/socketServer';
import { test, expect } from 'playwright/lib'; import { test, expect } from 'playwright/lib';
...@@ -215,3 +216,17 @@ test('solidity-foundry method', async({ render, page }) => { ...@@ -215,3 +216,17 @@ test('solidity-foundry method', async({ render, page }) => {
await expect(component).toHaveScreenshot(); await expect(component).toHaveScreenshot();
}); });
test('verification of zkSync contract', async({ render, mockEnvs }) => {
const zkSyncFormConfig: SmartContractVerificationConfig = {
...formConfig,
verification_options: [ 'standard-input' ],
zk_compiler_versions: [ 'v1.4.1', 'v1.4.0', 'v1.3.23', 'v1.3.22' ],
zk_optimization_modes: [ '0', '1', '2', '3', 's', 'z' ],
};
await mockEnvs(ENVS_MAP.zkSyncRollup);
const component = await render(<ContractVerificationForm config={ zkSyncFormConfig } hash={ hash }/>, { hooksConfig });
await expect(component).toHaveScreenshot();
});
...@@ -41,7 +41,7 @@ interface Props { ...@@ -41,7 +41,7 @@ interface Props {
const ContractVerificationForm = ({ method: methodFromQuery, config, hash }: Props) => { const ContractVerificationForm = ({ method: methodFromQuery, config, hash }: Props) => {
const formApi = useForm<FormFields>({ const formApi = useForm<FormFields>({
mode: 'onBlur', mode: 'onBlur',
defaultValues: methodFromQuery ? getDefaultValues(methodFromQuery, config, hash, null) : undefined, defaultValues: getDefaultValues(methodFromQuery, config, hash, null),
}); });
const { control, handleSubmit, watch, formState, setError, reset, getFieldState } = formApi; const { control, handleSubmit, watch, formState, setError, reset, getFieldState } = formApi;
const submitPromiseResolver = React.useRef<(value: unknown) => void>(); const submitPromiseResolver = React.useRef<(value: unknown) => void>();
......
...@@ -4,14 +4,15 @@ import React from 'react'; ...@@ -4,14 +4,15 @@ import React from 'react';
interface Props { interface Props {
title: string; title: string;
children: React.ReactNode; children: React.ReactNode;
disableScroll?: boolean;
} }
const ContractVerificationMethod = ({ title, children }: Props) => { const ContractVerificationMethod = ({ title, children, disableScroll }: Props) => {
const ref = React.useRef<HTMLDivElement>(null); const ref = React.useRef<HTMLDivElement>(null);
React.useEffect(() => { React.useEffect(() => {
ref.current?.scrollIntoView({ behavior: 'smooth' }); !disableScroll && ref.current?.scrollIntoView({ behavior: 'smooth' });
}, []); }, [ disableScroll ]);
return ( return (
<section ref={ ref }> <section ref={ ref }>
......
...@@ -51,6 +51,7 @@ const ContractVerificationFieldMethod = ({ control, isDisabled, methods }: Props ...@@ -51,6 +51,7 @@ const ContractVerificationFieldMethod = ({ control, isDisabled, methods }: Props
isDisabled={ isDisabled } isDisabled={ isDisabled }
isRequired isRequired
isAsync={ false } isAsync={ false }
isReadOnly={ options.length === 1 }
/> />
); );
}, [ isDisabled, isMobile, options ]); }, [ isDisabled, isMobile, options ]);
......
import { Box, Link } from '@chakra-ui/react';
import { useQueryClient } from '@tanstack/react-query';
import React from 'react';
import type { ControllerRenderProps } from 'react-hook-form';
import { useFormContext, Controller } from 'react-hook-form';
import type { FormFields } from '../types';
import type { SmartContractVerificationConfig } from 'types/client/contract';
import { getResourceKey } from 'lib/api/useApiQuery';
import useIsMobile from 'lib/hooks/useIsMobile';
import FancySelect from 'ui/shared/FancySelect/FancySelect';
import IconSvg from 'ui/shared/IconSvg';
import ContractVerificationFormRow from '../ContractVerificationFormRow';
const OPTIONS_LIMIT = 50;
const ContractVerificationFieldZkCompiler = () => {
const { formState, control } = useFormContext<FormFields>();
const isMobile = useIsMobile();
const queryClient = useQueryClient();
const config = queryClient.getQueryData<SmartContractVerificationConfig>(getResourceKey('contract_verification_config'));
const options = React.useMemo(() => (
config?.zk_compiler_versions?.map((option) => ({ label: option, value: option })) || []
), [ config?.zk_compiler_versions ]);
const loadOptions = React.useCallback(async(inputValue: string) => {
return options
.filter(({ label }) => !inputValue || label.toLowerCase().includes(inputValue.toLowerCase()))
.slice(0, OPTIONS_LIMIT);
}, [ options ]);
const renderControl = React.useCallback(({ field }: {field: ControllerRenderProps<FormFields, 'zk_compiler'>}) => {
const error = 'zk_compiler' in formState.errors ? formState.errors.zk_compiler : undefined;
return (
<FancySelect
{ ...field }
loadOptions={ loadOptions }
defaultOptions
size={ isMobile ? 'md' : 'lg' }
placeholder="ZK compiler (enter version or use the dropdown)"
placeholderIcon={ <IconSvg name="search"/> }
isDisabled={ formState.isSubmitting }
error={ error }
isRequired
isAsync
/>
);
}, [ formState.errors, formState.isSubmitting, isMobile, loadOptions ]);
return (
<ContractVerificationFormRow>
<Controller
name="zk_compiler"
control={ control }
render={ renderControl }
rules={{ required: true }}
/>
<Box>
<Link isExternal href="https://docs.zksync.io/zk-stack/components/compiler/specification#glossary">zksolc</Link>
<span> compiler version.</span>
</Box>
</ContractVerificationFormRow>
);
};
export default React.memo(ContractVerificationFieldZkCompiler);
import { Flex, Select } from '@chakra-ui/react';
import React from 'react';
import type { ControllerRenderProps } from 'react-hook-form';
import { Controller, useFormContext } from 'react-hook-form';
import type { FormFields } from '../types';
import type { SmartContractVerificationConfig } from 'types/client/contract';
import CheckboxInput from 'ui/shared/CheckboxInput';
import ContractVerificationFormRow from '../ContractVerificationFormRow';
interface Props {
config: SmartContractVerificationConfig;
}
const ContractVerificationFieldZkOptimization = ({ config }: Props) => {
const [ isEnabled, setIsEnabled ] = React.useState(false);
const { formState, control } = useFormContext<FormFields>();
const error = 'optimization_mode' in formState.errors ? formState.errors.optimization_mode : undefined;
const handleCheckboxChange = React.useCallback(() => {
setIsEnabled(prev => !prev);
}, []);
const renderCheckboxControl = React.useCallback(({ field }: {field: ControllerRenderProps<FormFields, 'is_optimization_enabled'>}) => (
<Flex flexShrink={ 0 }>
<CheckboxInput<FormFields, 'is_optimization_enabled'>
text="Optimization enabled"
field={ field }
onChange={ handleCheckboxChange }
isDisabled={ formState.isSubmitting }
/>
</Flex>
), [ formState.isSubmitting, handleCheckboxChange ]);
const renderInputControl = React.useCallback(({ field }: {field: ControllerRenderProps<FormFields, 'optimization_mode'>}) => {
return (
<Select
size="xs"
{ ...field }
w="auto"
borderRadius="base"
isDisabled={ formState.isSubmitting }
placeholder="Optimization mode"
isInvalid={ Boolean(error) }
>
{ config.zk_optimization_modes?.map((value) => (
<option key={ value } value={ value }>
{ value }
</option>
)) }
</Select>
);
}, [ config.zk_optimization_modes, error, formState.isSubmitting ]);
return (
<ContractVerificationFormRow>
<Flex columnGap={ 5 } rowGap={ 2 } h={{ base: 'auto', lg: '32px' }} flexDir={{ base: 'column', lg: 'row' }}>
<Controller
name="is_optimization_enabled"
control={ control }
render={ renderCheckboxControl }
/>
{ isEnabled && (
<Controller
name="optimization_mode"
control={ control }
render={ renderInputControl }
/>
) }
</Flex>
</ContractVerificationFormRow>
);
};
export default React.memo(ContractVerificationFieldZkOptimization);
...@@ -2,25 +2,32 @@ import React from 'react'; ...@@ -2,25 +2,32 @@ import React from 'react';
import type { SmartContractVerificationConfig } from 'types/client/contract'; import type { SmartContractVerificationConfig } from 'types/client/contract';
import config from 'configs/app';
import ContractVerificationMethod from '../ContractVerificationMethod'; import ContractVerificationMethod from '../ContractVerificationMethod';
import ContractVerificationFieldAutodetectArgs from '../fields/ContractVerificationFieldAutodetectArgs'; import ContractVerificationFieldAutodetectArgs from '../fields/ContractVerificationFieldAutodetectArgs';
import ContractVerificationFieldCompiler from '../fields/ContractVerificationFieldCompiler'; import ContractVerificationFieldCompiler from '../fields/ContractVerificationFieldCompiler';
import ContractVerificationFieldName from '../fields/ContractVerificationFieldName'; import ContractVerificationFieldName from '../fields/ContractVerificationFieldName';
import ContractVerificationFieldSources from '../fields/ContractVerificationFieldSources'; import ContractVerificationFieldSources from '../fields/ContractVerificationFieldSources';
import ContractVerificationFieldZkCompiler from '../fields/ContractVerificationFieldZkCompiler';
import ContractVerificationFieldZkOptimization from '../fields/ContractVerificationFieldZkOptimization';
const FILE_TYPES = [ '.json' as const ]; const FILE_TYPES = [ '.json' as const ];
const rollupFeature = config.features.rollup;
const ContractVerificationStandardInput = ({ config }: { config: SmartContractVerificationConfig }) => { const ContractVerificationStandardInput = ({ config }: { config: SmartContractVerificationConfig }) => {
return ( return (
<ContractVerificationMethod title="Contract verification via Solidity (standard JSON input) "> <ContractVerificationMethod title="Contract verification via Solidity (standard JSON input) " disableScroll={ config.verification_options.length === 1 }>
{ !config?.is_rust_verifier_microservice_enabled && <ContractVerificationFieldName/> } { !config?.is_rust_verifier_microservice_enabled && <ContractVerificationFieldName/> }
<ContractVerificationFieldCompiler/> <ContractVerificationFieldCompiler/>
{ rollupFeature.isEnabled && rollupFeature.type === 'zkSync' && <ContractVerificationFieldZkCompiler/> }
<ContractVerificationFieldSources <ContractVerificationFieldSources
fileTypes={ FILE_TYPES } fileTypes={ FILE_TYPES }
title="Standard Input JSON" title="Standard Input JSON"
hint="Upload the standard input JSON file created during contract compilation." hint="Upload the standard input JSON file created during contract compilation."
required required
/> />
{ rollupFeature.isEnabled && rollupFeature.type === 'zkSync' && <ContractVerificationFieldZkOptimization config={ config }/> }
{ !config?.is_rust_verifier_microservice_enabled && <ContractVerificationFieldAutodetectArgs/> } { !config?.is_rust_verifier_microservice_enabled && <ContractVerificationFieldAutodetectArgs/> }
</ContractVerificationMethod> </ContractVerificationMethod>
); );
......
...@@ -44,6 +44,20 @@ export interface FormFieldsStandardInput { ...@@ -44,6 +44,20 @@ export interface FormFieldsStandardInput {
license_type: LicenseOption | null; license_type: LicenseOption | null;
} }
export interface FormFieldsStandardInputZk {
address: string;
method: MethodOption;
name: string;
compiler: Option | null;
zk_compiler: Option | null;
sources: Array<File>;
autodetect_constructor_args: boolean;
constructor_args: string;
license_type: LicenseOption | null;
is_optimization_enabled: boolean;
optimization_mode: string | undefined;
}
export interface FormFieldsSourcify { export interface FormFieldsSourcify {
address: string; address: string;
method: MethodOption; method: MethodOption;
...@@ -93,5 +107,5 @@ export interface FormFieldsVyperStandardInput { ...@@ -93,5 +107,5 @@ export interface FormFieldsVyperStandardInput {
license_type: LicenseOption | null; license_type: LicenseOption | null;
} }
export type FormFields = FormFieldsFlattenSourceCode | FormFieldsStandardInput | FormFieldsSourcify | export type FormFields = FormFieldsFlattenSourceCode | FormFieldsStandardInput | FormFieldsStandardInputZk | FormFieldsSourcify |
FormFieldsMultiPartFile | FormFieldsVyperContract | FormFieldsVyperMultiPartFile | FormFieldsVyperStandardInput; FormFieldsMultiPartFile | FormFieldsVyperContract | FormFieldsVyperMultiPartFile | FormFieldsVyperStandardInput;
...@@ -7,6 +7,7 @@ import type { ...@@ -7,6 +7,7 @@ import type {
FormFieldsMultiPartFile, FormFieldsMultiPartFile,
FormFieldsSourcify, FormFieldsSourcify,
FormFieldsStandardInput, FormFieldsStandardInput,
FormFieldsStandardInputZk,
FormFieldsVyperContract, FormFieldsVyperContract,
FormFieldsVyperMultiPartFile, FormFieldsVyperMultiPartFile,
FormFieldsVyperStandardInput, FormFieldsVyperStandardInput,
...@@ -155,11 +156,18 @@ export const DEFAULT_VALUES: Record<SmartContractVerificationMethod, FormFields> ...@@ -155,11 +156,18 @@ export const DEFAULT_VALUES: Record<SmartContractVerificationMethod, FormFields>
}; };
export function getDefaultValues( export function getDefaultValues(
method: SmartContractVerificationMethod, methodParam: SmartContractVerificationMethod | undefined,
config: SmartContractVerificationConfig, config: SmartContractVerificationConfig,
hash: string | undefined, hash: string | undefined,
licenseType: FormFields['license_type'], licenseType: FormFields['license_type'],
) { ) {
const singleMethod = config.verification_options.length === 1 ? config.verification_options[0] : undefined;
const method = singleMethod || methodParam;
if (!method) {
return;
}
const defaultValues: FormFields = { ...DEFAULT_VALUES[method], address: hash || '', license_type: licenseType }; const defaultValues: FormFields = { ...DEFAULT_VALUES[method], address: hash || '', license_type: licenseType };
if ('evm_version' in defaultValues) { if ('evm_version' in defaultValues) {
...@@ -179,6 +187,13 @@ export function getDefaultValues( ...@@ -179,6 +187,13 @@ export function getDefaultValues(
} }
} }
if (singleMethod) {
defaultValues.method = {
label: METHOD_LABELS[config.verification_options[0]],
value: config.verification_options[0],
};
}
return defaultValues; return defaultValues;
} }
...@@ -223,7 +238,7 @@ export function prepareRequestBody(data: FormFields): FetchParams['body'] { ...@@ -223,7 +238,7 @@ export function prepareRequestBody(data: FormFields): FetchParams['body'] {
} }
case 'standard-input': { case 'standard-input': {
const _data = data as FormFieldsStandardInput; const _data = data as (FormFieldsStandardInput | FormFieldsStandardInputZk);
const body = new FormData(); const body = new FormData();
_data.compiler && body.set('compiler_version', _data.compiler.value); _data.compiler && body.set('compiler_version', _data.compiler.value);
...@@ -233,6 +248,15 @@ export function prepareRequestBody(data: FormFields): FetchParams['body'] { ...@@ -233,6 +248,15 @@ export function prepareRequestBody(data: FormFields): FetchParams['body'] {
body.set('constructor_args', _data.constructor_args); body.set('constructor_args', _data.constructor_args);
addFilesToFormData(body, _data.sources, 'files'); addFilesToFormData(body, _data.sources, 'files');
// zkSync fields
'zk_compiler' in _data && _data.zk_compiler && body.set('zk_compiler_version', _data.zk_compiler.value);
if ('is_optimization_enabled' in _data) {
body.set('is_optimization_enabled', String(Boolean(_data.is_optimization_enabled)));
if (_data.is_optimization_enabled && 'optimization_mode' in _data && _data.optimization_mode) {
body.set('optimization_runs', _data.optimization_mode);
}
}
return body; return body;
} }
......
...@@ -69,7 +69,7 @@ const LatestBlocks = () => { ...@@ -69,7 +69,7 @@ const LatestBlocks = () => {
let content; let content;
if (isError) { if (isError) {
content = <Text>No data. Please reload page.</Text>; content = <Text>No data. Please reload the page.</Text>;
} }
if (data) { if (data) {
......
...@@ -26,7 +26,7 @@ const LatestTransactions = () => { ...@@ -26,7 +26,7 @@ const LatestTransactions = () => {
const { num, socketAlert } = useNewTxsSocket(); const { num, socketAlert } = useNewTxsSocket();
if (isError) { if (isError) {
return <Text mt={ 4 }>No data. Please reload page.</Text>; return <Text mt={ 4 }>No data. Please reload the page.</Text>;
} }
if (data) { if (data) {
......
...@@ -23,7 +23,7 @@ const LatestWatchlistTxs = () => { ...@@ -23,7 +23,7 @@ const LatestWatchlistTxs = () => {
}); });
if (isError) { if (isError) {
return <Text mt={ 4 }>No data. Please reload page.</Text>; return <Text mt={ 4 }>No data. Please reload the page.</Text>;
} }
if (!data?.length) { if (!data?.length) {
......
...@@ -46,13 +46,21 @@ const Stats = () => { ...@@ -46,13 +46,21 @@ const Stats = () => {
}, },
}); });
if (isError || zkEvmLatestBatchQuery.isError || zkSyncLatestBatchQuery.isError) { const arbitrumLatestBatchQuery = useApiQuery('homepage_arbitrum_latest_batch', {
queryOptions: {
placeholderData: 12345,
enabled: rollupFeature.isEnabled && rollupFeature.type === 'arbitrum',
},
});
if (isError || zkEvmLatestBatchQuery.isError || zkSyncLatestBatchQuery.isError || arbitrumLatestBatchQuery.isError) {
return null; return null;
} }
const isLoading = isPlaceholderData || const isLoading = isPlaceholderData ||
(rollupFeature.isEnabled && rollupFeature.type === 'zkEvm' && zkEvmLatestBatchQuery.isPlaceholderData) || (rollupFeature.isEnabled && rollupFeature.type === 'zkEvm' && zkEvmLatestBatchQuery.isPlaceholderData) ||
(rollupFeature.isEnabled && rollupFeature.type === 'zkSync' && zkSyncLatestBatchQuery.isPlaceholderData); (rollupFeature.isEnabled && rollupFeature.type === 'zkSync' && zkSyncLatestBatchQuery.isPlaceholderData) ||
(rollupFeature.isEnabled && rollupFeature.type === 'arbitrum' && arbitrumLatestBatchQuery.isPlaceholderData);
const content = (() => { const content = (() => {
if (!data) { if (!data) {
...@@ -72,22 +80,21 @@ const Stats = () => { ...@@ -72,22 +80,21 @@ const Stats = () => {
</GasInfoTooltip> </GasInfoTooltip>
) : null; ) : null;
const hasBatches = rollupFeature.isEnabled && (rollupFeature.type === 'zkEvm' || rollupFeature.type === 'zkSync' || rollupFeature.type === 'arbitrum');
const latestBatch =
(hasBatches && rollupFeature.type === 'zkEvm' ? zkEvmLatestBatchQuery.data : null) ||
(hasBatches && rollupFeature.type === 'zkSync' ? zkSyncLatestBatchQuery.data : null) ||
(hasBatches && rollupFeature.type === 'arbitrum' ? arbitrumLatestBatchQuery.data : null) || 0;
const items: Array<StatsWidgetProps> = [ const items: Array<StatsWidgetProps> = [
rollupFeature.isEnabled && rollupFeature.type === 'zkEvm' && { hasBatches && {
icon: 'txn_batches_slim' as const,
label: 'Latest batch',
value: (zkEvmLatestBatchQuery.data || 0).toLocaleString(),
href: { pathname: '/batches' as const },
isLoading,
},
rollupFeature.isEnabled && rollupFeature.type === 'zkSync' && {
icon: 'txn_batches_slim' as const, icon: 'txn_batches_slim' as const,
label: 'Latest batch', label: 'Latest batch',
value: (zkSyncLatestBatchQuery.data || 0).toLocaleString(), value: latestBatch.toLocaleString(),
href: { pathname: '/batches' as const }, href: { pathname: '/batches' as const },
isLoading, isLoading,
}, },
!(rollupFeature.isEnabled && (rollupFeature.type === 'zkEvm' || rollupFeature.type === 'zkSync')) && { !hasBatches && {
icon: 'block_slim' as const, icon: 'block_slim' as const,
label: 'Total blocks', label: 'Total blocks',
value: Number(data.total_blocks).toLocaleString(), value: Number(data.total_blocks).toLocaleString(),
......
...@@ -3,10 +3,13 @@ import React from 'react'; ...@@ -3,10 +3,13 @@ import React from 'react';
import config from 'configs/app'; import config from 'configs/app';
import useHasAccount from 'lib/hooks/useHasAccount'; import useHasAccount from 'lib/hooks/useHasAccount';
import LatestDeposits from 'ui/home/LatestDeposits'; import LatestOptimisticDeposits from 'ui/home/latestDeposits/LatestOptimisticDeposits';
import LatestTxs from 'ui/home/LatestTxs'; import LatestTxs from 'ui/home/LatestTxs';
import LatestWatchlistTxs from 'ui/home/LatestWatchlistTxs'; import LatestWatchlistTxs from 'ui/home/LatestWatchlistTxs';
import TabsWithScroll from 'ui/shared/Tabs/TabsWithScroll'; import TabsWithScroll from 'ui/shared/Tabs/TabsWithScroll';
import LatestArbitrumDeposits from './latestDeposits/LatestArbitrumDeposits';
const rollupFeature = config.features.rollup; const rollupFeature = config.features.rollup;
const TAB_LIST_PROPS = { const TAB_LIST_PROPS = {
...@@ -15,10 +18,13 @@ const TAB_LIST_PROPS = { ...@@ -15,10 +18,13 @@ const TAB_LIST_PROPS = {
const TransactionsHome = () => { const TransactionsHome = () => {
const hasAccount = useHasAccount(); const hasAccount = useHasAccount();
if ((rollupFeature.isEnabled && rollupFeature.type === 'optimistic') || hasAccount) { if ((rollupFeature.isEnabled && (rollupFeature.type === 'optimistic' || rollupFeature.type === 'arbitrum')) || hasAccount) {
const tabs = [ const tabs = [
{ id: 'txn', title: 'Latest txn', component: <LatestTxs/> }, { id: 'txn', title: 'Latest txn', component: <LatestTxs/> },
rollupFeature.isEnabled && rollupFeature.type === 'optimistic' && { id: 'deposits', title: 'Deposits (L1→L2 txn)', component: <LatestDeposits/> }, rollupFeature.isEnabled && rollupFeature.type === 'optimistic' &&
{ id: 'deposits', title: 'Deposits (L1→L2 txn)', component: <LatestOptimisticDeposits/> },
rollupFeature.isEnabled && rollupFeature.type === 'arbitrum' &&
{ id: 'deposits', title: 'Deposits (L1→L2 txn)', component: <LatestArbitrumDeposits/> },
hasAccount && { id: 'watchlist', title: 'Watch list', component: <LatestWatchlistTxs/> }, hasAccount && { id: 'watchlist', title: 'Watch list', component: <LatestWatchlistTxs/> },
].filter(Boolean); ].filter(Boolean);
return ( return (
......
import React from 'react';
import { finalized, unfinalized } from 'mocks/arbitrum/txnBatches';
import { ENVS_MAP } from 'playwright/fixtures/mockEnvs';
import { test, expect } from 'playwright/lib';
import LatestArbitrumL2Batches from './LatestArbitrumL2Batches';
test('default view +@mobile +@dark-mode', async({ render, mockEnvs, mockApiResponse }) => {
await mockEnvs(ENVS_MAP.arbitrumRollup);
await mockApiResponse('homepage_arbitrum_l2_batches', { items: [ finalized, unfinalized ] });
const component = await render(<LatestArbitrumL2Batches/>);
await expect(component).toHaveScreenshot();
});
import { Box, Heading, Flex, Text, VStack } from '@chakra-ui/react';
import { useQueryClient } from '@tanstack/react-query';
import { AnimatePresence } from 'framer-motion';
import React from 'react';
import type { SocketMessage } from 'lib/socket/types';
import type { ArbitrumL2TxnBatchesItem } from 'types/api/arbitrumL2';
import { route } from 'nextjs-routes';
import useApiQuery, { getResourceKey } from 'lib/api/useApiQuery';
import useIsMobile from 'lib/hooks/useIsMobile';
import useSocketChannel from 'lib/socket/useSocketChannel';
import useSocketMessage from 'lib/socket/useSocketMessage';
import { ARBITRUM_L2_TXN_BATCHES_ITEM } from 'stubs/arbitrumL2';
import LinkInternal from 'ui/shared/links/LinkInternal';
import LatestBatchItem from './LatestBatchItem';
const LatestArbitrumL2Batches = () => {
const isMobile = useIsMobile();
const batchesMaxCount = isMobile ? 2 : 5;
const queryClient = useQueryClient();
const { data, isPlaceholderData, isError } = useApiQuery('homepage_arbitrum_l2_batches', {
queryOptions: {
placeholderData: { items: Array(batchesMaxCount).fill(ARBITRUM_L2_TXN_BATCHES_ITEM) },
},
});
const handleNewBatchMessage: SocketMessage.NewArbitrumL2Batch['handler'] = React.useCallback((payload) => {
queryClient.setQueryData(getResourceKey('homepage_arbitrum_l2_batches'), (prevData: { items: Array<ArbitrumL2TxnBatchesItem> } | undefined) => {
const newItems = prevData?.items ? [ ...prevData.items ] : [];
if (newItems.some((batch => batch.number === payload.batch.number))) {
return { items: newItems };
}
return { items: [ payload.batch, ...newItems ].sort((b1, b2) => b2.number - b1.number).slice(0, batchesMaxCount) };
});
}, [ queryClient, batchesMaxCount ]);
const channel = useSocketChannel({
topic: 'arbitrum:new_batch',
isDisabled: isPlaceholderData || isError,
});
useSocketMessage({
channel,
event: 'new_arbitrum_batch',
handler: handleNewBatchMessage,
});
let content;
if (isError) {
content = <Text>No data. Please reload the page.</Text>;
}
if (data) {
const dataToShow = data.items.slice(0, batchesMaxCount);
content = (
<>
<VStack spacing={ 2 } mb={ 3 } overflow="hidden" alignItems="stretch">
<AnimatePresence initial={ false } >
{ dataToShow.map(((batch, index) => (
<LatestBatchItem
key={ batch.number + (isPlaceholderData ? String(index) : '') }
number={ batch.number }
timestamp={ batch.commitment_transaction.timestamp }
txCount={ batch.transactions_count }
isLoading={ isPlaceholderData }
/>
))) }
</AnimatePresence>
</VStack>
<Flex justifyContent="center">
<LinkInternal fontSize="sm" href={ route({ pathname: '/batches' }) }>View all batches</LinkInternal>
</Flex>
</>
);
}
return (
<Box width={{ base: '100%', lg: '280px' }} flexShrink={ 0 }>
<Heading as="h4" size="sm" mb={ 3 }>Latest batches</Heading>
{ content }
</Box>
);
};
export default LatestArbitrumL2Batches;
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment