diff --git a/configs/app/chain.ts b/configs/app/chain.ts index 0ee6d2de6f..56971abda6 100644 --- a/configs/app/chain.ts +++ b/configs/app/chain.ts @@ -1,7 +1,22 @@ +import type { RollupType } from 'types/client/rollup'; +import type { NetworkVerificationType, NetworkVerificationTypeEnvs } from 'types/networks'; + import { getEnvValue } from './utils'; const DEFAULT_CURRENCY_DECIMALS = 18; +const rollupType = getEnvValue('NEXT_PUBLIC_ROLLUP_TYPE') as RollupType; + +const verificationType: NetworkVerificationType = (() => { + if (rollupType === 'arbitrum') { + return 'posting'; + } + if (rollupType === 'zkEvm') { + return 'sequencing'; + } + return getEnvValue('NEXT_PUBLIC_NETWORK_VERIFICATION_TYPE') as NetworkVerificationTypeEnvs || 'mining'; +})(); + const chain = Object.freeze({ id: getEnvValue('NEXT_PUBLIC_NETWORK_ID'), name: getEnvValue('NEXT_PUBLIC_NETWORK_NAME'), @@ -19,7 +34,7 @@ const chain = Object.freeze({ tokenStandard: getEnvValue('NEXT_PUBLIC_NETWORK_TOKEN_STANDARD_NAME') || 'ERC', rpcUrl: getEnvValue('NEXT_PUBLIC_NETWORK_RPC_URL'), isTestnet: getEnvValue('NEXT_PUBLIC_IS_TESTNET') === 'true', - verificationType: getEnvValue('NEXT_PUBLIC_NETWORK_VERIFICATION_TYPE') || 'mining', + verificationType, }); export default chain; diff --git a/deploy/tools/envs-validator/schema.ts b/deploy/tools/envs-validator/schema.ts index b8adbc940a..bbad89aa7e 100644 --- a/deploy/tools/envs-validator/schema.ts +++ b/deploy/tools/envs-validator/schema.ts @@ -29,7 +29,7 @@ import { SUPPORTED_WALLETS } from '../../../types/client/wallets'; import type { CustomLink, CustomLinksGroup } from '../../../types/footerLinks'; import { CHAIN_INDICATOR_IDS } 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 type { AddressViewId } from '../../../types/views/address'; import { ADDRESS_VIEWS_IDS, IDENTICON_TYPES } from '../../../types/views/address'; @@ -508,7 +508,17 @@ const schema = yup NEXT_PUBLIC_NETWORK_CURRENCY_DECIMALS: yup.number().integer().positive(), NEXT_PUBLIC_NETWORK_SECONDARY_COIN_SYMBOL: yup.string(), NEXT_PUBLIC_NETWORK_MULTIPLE_GAS_CURRENCIES: yup.boolean(), - NEXT_PUBLIC_NETWORK_VERIFICATION_TYPE: yup.string().oneOf([ 'validation', 'mining' ]), + NEXT_PUBLIC_NETWORK_VERIFICATION_TYPE: yup + .string().oneOf([ 'validation', 'mining' ]) + .when('NEXT_PUBLIC_ROLLUP_TYPE', { + is: (value: string) => value === 'arbitrum' || value === 'zkEvm', + then: (schema) => schema.test( + 'not-exist', + 'NEXT_PUBLIC_NETWORK_VERIFICATION_TYPE can not be set for Arbitrum and ZkEVM rollups', + value => value === undefined, + ), + otherwise: (schema) => schema, + }), NEXT_PUBLIC_NETWORK_TOKEN_STANDARD_NAME: yup.string(), NEXT_PUBLIC_IS_TESTNET: yup.boolean(), diff --git a/deploy/values/review-l2/values.yaml.gotmpl b/deploy/values/review-l2/values.yaml.gotmpl index 5647779fde..07e9d535d2 100644 --- a/deploy/values/review-l2/values.yaml.gotmpl +++ b/deploy/values/review-l2/values.yaml.gotmpl @@ -49,7 +49,6 @@ frontend: env: NEXT_PUBLIC_APP_ENV: development NEXT_PUBLIC_APP_INSTANCE: review_L2 - NEXT_PUBLIC_NETWORK_VERIFICATION_TYPE: validation NEXT_PUBLIC_NETWORK_LOGO: https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/network-logos/base.svg NEXT_PUBLIC_NETWORK_ICON: https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/network-icons/base.svg NEXT_PUBLIC_FEATURED_NETWORKS: https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/featured-networks/base-mainnet.json diff --git a/deploy/values/review/values.yaml.gotmpl b/deploy/values/review/values.yaml.gotmpl index c37f4ee6c6..04eae136f2 100644 --- a/deploy/values/review/values.yaml.gotmpl +++ b/deploy/values/review/values.yaml.gotmpl @@ -49,7 +49,6 @@ frontend: env: NEXT_PUBLIC_APP_ENV: development NEXT_PUBLIC_APP_INSTANCE: review - NEXT_PUBLIC_NETWORK_VERIFICATION_TYPE: validation NEXT_PUBLIC_FEATURED_NETWORKS: https://raw.githubusercontent.com/blockscout/frontend-configs/dev/configs/featured-networks/eth-sepolia.json NEXT_PUBLIC_NETWORK_LOGO: https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/network-logos/sepolia.svg NEXT_PUBLIC_NETWORK_ICON: https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/network-icons/sepolia.png @@ -61,18 +60,10 @@ frontend: NEXT_PUBLIC_NAME_SERVICE_API_HOST: https://bens-rs-test.k8s-dev.blockscout.com NEXT_PUBLIC_METADATA_SERVICE_API_HOST: https://metadata-test.k8s-dev.blockscout.com NEXT_PUBLIC_AUTH_URL: https://blockscout-main.k8s-dev.blockscout.com - NEXT_PUBLIC_MARKETPLACE_ENABLED: true - NEXT_PUBLIC_MARKETPLACE_SUBMIT_FORM: https://airtable.com/shrqUAcjgGJ4jU88C - NEXT_PUBLIC_MARKETPLACE_SUGGEST_IDEAS_FORM: https://airtable.com/appiy5yijZpMMSKjT/pag3t82DUCyhGRZZO/form - NEXT_PUBLIC_MARKETPLACE_SECURITY_REPORTS_URL: https://gist.githubusercontent.com/maxaleks/ce5c7e3de53e8f5b240b88265daf5839/raw/328383c958a8f7ecccf6d50c953bcdf8ab3faa0a/security_reports_goerli_test.json NEXT_PUBLIC_LOGOUT_URL: https://blockscoutcom.us.auth0.com/v2/logout NEXT_PUBLIC_HOMEPAGE_CHARTS: "['daily_txs','coin_price','market_cap']" NEXT_PUBLIC_NETWORK_RPC_URL: https://eth-sepolia.public.blastapi.io NEXT_PUBLIC_NETWORK_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_WEB3_WALLETS: "['token_pocket','coinbase','metamask']" NEXT_PUBLIC_VIEWS_ADDRESS_IDENTICON_TYPE: gradient_avatar @@ -96,4 +87,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 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_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 diff --git a/docs/ENVS.md b/docs/ENVS.md index 706a9fd58e..a763311d01 100644 --- a/docs/ENVS.md +++ b/docs/ENVS.md @@ -92,7 +92,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_SECONDARY_COIN_SYMBOL | `string` | Network secondary coin symbol. | - | - | `GNO` | v1.29.0+ | | NEXT_PUBLIC_NETWORK_MULTIPLE_GAS_CURRENCIES | `boolean` | Set to `true` for networks where users can pay transaction fees in either the native coin or ERC-20 tokens. | - | `false` | `true` | v1.33.0+ | -| NEXT_PUBLIC_NETWORK_VERIFICATION_TYPE | `validation` 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_IS_TESTNET | `boolean`| Set to true if network is testnet | - | `false` | `true` | v1.0.x+ | diff --git a/lib/networks/getNetworkValidationActionText.ts b/lib/networks/getNetworkValidationActionText.ts new file mode 100644 index 0000000000..c6a36f1a3d --- /dev/null +++ b/lib/networks/getNetworkValidationActionText.ts @@ -0,0 +1,21 @@ +import config from 'configs/app'; + +export default function getNetworkValidationActionText() { + switch (config.chain.verificationType) { + case 'validation': { + return 'validated'; + } + case 'mining': { + return 'mined'; + } + case 'posting': { + return 'posted'; + } + case 'sequencing': { + return 'sequenced'; + } + default: { + return 'miner'; + } + } +} diff --git a/lib/networks/getNetworkValidatorTitle.ts b/lib/networks/getNetworkValidatorTitle.ts index 7435ee0293..a0c7977cd6 100644 --- a/lib/networks/getNetworkValidatorTitle.ts +++ b/lib/networks/getNetworkValidatorTitle.ts @@ -1,5 +1,21 @@ import config from 'configs/app'; export default function getNetworkValidatorTitle() { - return config.chain.verificationType === 'validation' ? 'validator' : 'miner'; + switch (config.chain.verificationType) { + case 'validation': { + return 'validator'; + } + case 'mining': { + return 'miner'; + } + case 'posting': { + return 'poster'; + } + case 'sequencing': { + return 'sequencer'; + } + default: { + return 'miner'; + } + } } diff --git a/types/networks.ts b/types/networks.ts index 159f506634..fb729eb891 100644 --- a/types/networks.ts +++ b/types/networks.ts @@ -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; diff --git a/ui/address/AddressBlocksValidated.tsx b/ui/address/AddressBlocksValidated.tsx index 18d68e00ff..d102266802 100644 --- a/ui/address/AddressBlocksValidated.tsx +++ b/ui/address/AddressBlocksValidated.tsx @@ -18,12 +18,14 @@ import ActionBar, { ACTION_BAR_HEIGHT_DESKTOP } from 'ui/shared/ActionBar'; import DataListDisplay from 'ui/shared/DataListDisplay'; import Pagination from 'ui/shared/pagination/Pagination'; import useQueryWithPages from 'ui/shared/pagination/useQueryWithPages'; -import SocketAlert from 'ui/shared/SocketAlert'; +import * as SocketNewItemsNotice from 'ui/shared/SocketNewItemsNotice'; import { default as Thead } from 'ui/shared/TheadSticky'; import AddressBlocksValidatedListItem from './blocksValidated/AddressBlocksValidatedListItem'; import AddressBlocksValidatedTableItem from './blocksValidated/AddressBlocksValidatedTableItem'; +const OVERLOAD_COUNT = 75; + interface Props { scrollRef?: React.RefObject; shouldRender?: boolean; @@ -31,7 +33,9 @@ interface 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 router = useRouter(); const isMounted = useIsMounted(); @@ -57,11 +61,11 @@ const AddressBlocksValidated = ({ scrollRef, shouldRender = true, isQueryEnabled }); 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) => { - setSocketAlert(false); + setSocketAlert(''); queryClient.setQueryData( getResourceKey('address_blocks_validated', { pathParams: { hash: addressHash } }), @@ -70,6 +74,11 @@ const AddressBlocksValidated = ({ scrollRef, shouldRender = true, isQueryEnabled return; } + if (prevData.items.length >= OVERLOAD_COUNT) { + setNewItemsCount(prev => prev + 1); + return prevData; + } + return { ...prevData, items: [ payload.block, ...prevData.items ], @@ -95,20 +104,26 @@ const AddressBlocksValidated = ({ scrollRef, shouldRender = true, isQueryEnabled const content = query.data?.items ? ( <> - { socketAlert && } - +
- - - - - { !config.UI.views.block.hiddenFields?.total_reward && - } + + + + + { !config.UI.views.block.hiddenFields?.total_reward && !config.features.rollup.isEnabled && + } + { query.data.items.map((item, index) => ( + { query.pagination.page === 1 && ( + + ) } { query.data.items.map((item, index) => ( { isLoading={ props.isLoading } /> - { !config.UI.views.block.hiddenFields?.total_reward && ( + { !config.UI.views.block.hiddenFields?.total_reward && !config.features.rollup.isEnabled && ( Reward { currencyUnits.ether } { totalReward.toFixed() } diff --git a/ui/address/blocksValidated/AddressBlocksValidatedTableItem.tsx b/ui/address/blocksValidated/AddressBlocksValidatedTableItem.tsx index e08dbc7db7..9ee38b297e 100644 --- a/ui/address/blocksValidated/AddressBlocksValidatedTableItem.tsx +++ b/ui/address/blocksValidated/AddressBlocksValidatedTableItem.tsx @@ -56,7 +56,7 @@ const AddressBlocksValidatedTableItem = (props: Props) => { /> - { !config.UI.views.block.hiddenFields?.total_reward && ( + { !config.UI.views.block.hiddenFields?.total_reward && !config.features.rollup.isEnabled && (
BlockAgeTxnGas usedReward { currencyUnits.ether }BlockAgeTxnGas usedReward { currencyUnits.ether }
{ totalReward.toFixed() } diff --git a/ui/block/BlockDetails.tsx b/ui/block/BlockDetails.tsx index 68b1f5bd6a..b0e68fec3e 100644 --- a/ui/block/BlockDetails.tsx +++ b/ui/block/BlockDetails.tsx @@ -15,6 +15,7 @@ import getBlockReward from 'lib/block/getBlockReward'; import { GWEI, WEI, WEI_IN_GWEI, ZERO } from 'lib/consts'; import getArbitrumVerificationStepStatus from 'lib/getArbitrumVerificationStepStatus'; import { space } from 'lib/html-entities'; +import getNetworkValidationActionText from 'lib/networks/getNetworkValidationActionText'; import getNetworkValidatorTitle from 'lib/networks/getNetworkValidatorTitle'; import getQueryParamString from 'lib/router/getQueryParamString'; import { currencyUnits } from 'lib/units'; @@ -114,13 +115,7 @@ const BlockDetails = ({ query }: Props) => { ); })(); - const verificationTitle = (() => { - if (rollupFeature.isEnabled && rollupFeature.type === 'zkEvm') { - return 'Sequenced by'; - } - - return config.chain.verificationType === 'validation' ? 'Validated by' : 'Mined by'; - })(); + const verificationTitle = `${ capitalize(getNetworkValidationActionText()) } by`; const txsNum = (() => { const blockTxsNum = ( diff --git a/ui/pages/Address.tsx b/ui/pages/Address.tsx index 075ba7144e..17e36fa630 100644 --- a/ui/pages/Address.tsx +++ b/ui/pages/Address.tsx @@ -11,6 +11,7 @@ import useApiQuery from 'lib/api/useApiQuery'; import { useAppContext } from 'lib/contexts/app'; import useContractTabs from 'lib/hooks/useContractTabs'; import useIsSafeAddress from 'lib/hooks/useIsSafeAddress'; +import getNetworkValidationActionText from 'lib/networks/getNetworkValidationActionText'; import getQueryParamString from 'lib/router/getQueryParamString'; import useSocketChannel from 'lib/socket/useSocketChannel'; import useSocketMessage from 'lib/socket/useSocketMessage'; @@ -174,10 +175,10 @@ const AddressPageContent = () => { title: 'Coin balance history', component: , }, - config.chain.verificationType === 'validation' && addressTabsCountersQuery.data?.validations_count ? + addressTabsCountersQuery.data?.validations_count ? { id: 'blocks_validated', - title: 'Blocks validated', + title: `Blocks ${ getNetworkValidationActionText() }`, count: addressTabsCountersQuery.data?.validations_count, component: , } : diff --git a/ui/pages/Block.tsx b/ui/pages/Block.tsx index a732f50d7c..29cd7f2e8e 100644 --- a/ui/pages/Block.tsx +++ b/ui/pages/Block.tsx @@ -1,4 +1,5 @@ import { chakra, Skeleton } from '@chakra-ui/react'; +import capitalize from 'lodash/capitalize'; import { useRouter } from 'next/router'; import React from 'react'; @@ -10,6 +11,7 @@ import { useAppContext } from 'lib/contexts/app'; import throwOnAbsentParamError from 'lib/errors/throwOnAbsentParamError'; import throwOnResourceLoadError from 'lib/errors/throwOnResourceLoadError'; import useIsMobile from 'lib/hooks/useIsMobile'; +import getNetworkValidationActionText from 'lib/networks/getNetworkValidationActionText'; import getQueryParamString from 'lib/router/getQueryParamString'; import BlockDetails from 'ui/block/BlockDetails'; import BlockWithdrawals from 'ui/block/BlockWithdrawals'; @@ -149,7 +151,7 @@ const BlockPageContent = () => { fontWeight={ 500 } > - { config.chain.verificationType === 'validation' ? 'Validated by' : 'Mined by' } + { `${ capitalize(getNetworkValidationActionText()) } by` } diff --git a/ui/pages/Transactions.tsx b/ui/pages/Transactions.tsx index 60f0387b54..b76c2fbfe8 100644 --- a/ui/pages/Transactions.tsx +++ b/ui/pages/Transactions.tsx @@ -1,3 +1,4 @@ +import capitalize from 'lodash/capitalize'; import { useRouter } from 'next/router'; import React from 'react'; @@ -7,6 +8,7 @@ import config from 'configs/app'; import useHasAccount from 'lib/hooks/useHasAccount'; import useIsMobile from 'lib/hooks/useIsMobile'; import useNewTxsSocket from 'lib/hooks/useNewTxsSocket'; +import getNetworkValidationActionText from 'lib/networks/getNetworkValidationActionText'; import getQueryParamString from 'lib/router/getQueryParamString'; import { TX } from 'stubs/tx'; import { generateListStub } from 'stubs/utils'; @@ -27,7 +29,7 @@ const TAB_LIST_PROPS = { const TABS_HEIGHT = 88; const Transactions = () => { - const verifiedTitle = config.chain.verificationType === 'validation' ? 'Validated' : 'Mined'; + const verifiedTitle = capitalize(getNetworkValidationActionText()); const router = useRouter(); const isMobile = useIsMobile(); const tab = getQueryParamString(router.query.tab);