From f023a1834bc116b5700e54b79cea4853e79c3e25 Mon Sep 17 00:00:00 2001
From: isstuev <isstuev@gmail.com>
Date: Thu, 25 Jul 2024 13:43:53 +0200
Subject: [PATCH] change verification type and validator title for Arbitrum and
 zkEvm

---
 configs/app/chain.ts                          | 17 ++++++-
 deploy/tools/envs-validator/schema.ts         | 14 +++++-
 docs/ENVS.md                                  |  2 +-
 .../getNetworkValidationActionText.ts         | 21 ++++++++
 lib/networks/getNetworkValidatorTitle.ts      | 18 ++++++-
 types/networks.ts                             |  4 +-
 ui/address/AddressBlocksValidated.tsx         | 48 ++++++++++++++-----
 .../AddressBlocksValidatedListItem.tsx        |  2 +-
 .../AddressBlocksValidatedTableItem.tsx       |  2 +-
 ui/block/BlockDetails.tsx                     |  9 +---
 ui/pages/Address.tsx                          |  5 +-
 ui/pages/Block.tsx                            |  4 +-
 ui/pages/Transactions.tsx                     |  4 +-
 13 files changed, 119 insertions(+), 31 deletions(-)
 create mode 100644 lib/networks/getNetworkValidationActionText.ts

diff --git a/configs/app/chain.ts b/configs/app/chain.ts
index 0ee6d2de6f..a55c7baa64 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, NetworkVerificationTypeL1 } 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 NetworkVerificationTypeL1 || '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..6120eb209f 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 NetworkVerificationTypeL1, 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<NetworkVerificationType>().oneOf([ 'validation', 'mining' ]),
+    NEXT_PUBLIC_NETWORK_VERIFICATION_TYPE: yup
+      .string<NetworkVerificationTypeL1>().oneOf([ 'validation', 'mining' ])
+      .when('NEXT_PUBLIC_ROLLUP_TYPE', {
+        is: (value: string) => value !== 'arbitrum' && value !== 'zkevm',
+        then: (schema) => schema,
+        otherwise: (schema) => schema.test(
+          'not-exist',
+          'NEXT_PUBLIC_NETWORK_VERIFICATION_TYPE can not be set for Arbitrum and ZkEVM rollups',
+          value => value === undefined,
+        ),
+      }),
     NEXT_PUBLIC_NETWORK_TOKEN_STANDARD_NAME: yup.string(),
     NEXT_PUBLIC_IS_TESTNET: yup.boolean(),
 
diff --git a/docs/ENVS.md b/docs/ENVS.md
index 706a9fd58e..9c752fc7b3 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` | `posting` | Verification type in the network | - | `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..cca0167265 100644
--- a/types/networks.ts
+++ b/types/networks.ts
@@ -24,4 +24,6 @@ export interface NetworkExplorer {
   };
 }
 
-export type NetworkVerificationType = 'mining' | 'validation';
+export type NetworkVerificationTypeL1 = 'mining' | 'validation';
+export type NetworkVerificationTypeL2 = 'posting' | 'sequencing';
+export type NetworkVerificationType = NetworkVerificationTypeL1 | NetworkVerificationTypeL2;
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<HTMLDivElement>;
   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 && <SocketAlert mb={ 6 }/> }
       <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 }>
             <Tr>
-              <Th width="17%">Block</Th>
-              <Th width="17%">Age</Th>
-              <Th width="16%">Txn</Th>
-              <Th width="25%">Gas used</Th>
-              { !config.UI.views.block.hiddenFields?.total_reward &&
-              <Th width="25%" isNumeric>Reward { currencyUnits.ether }</Th> }
+              <Th>Block</Th>
+              <Th>Age</Th>
+              <Th>Txn</Th>
+              <Th>Gas used</Th>
+              { !config.UI.views.block.hiddenFields?.total_reward && !config.features.rollup.isEnabled &&
+              <Th isNumeric>Reward { currencyUnits.ether }</Th> }
             </Tr>
           </Thead>
           <Tbody>
+            <SocketNewItemsNotice.Desktop
+              url={ window.location.href }
+              num={ newItemsCount }
+              alert={ socketAlert }
+              type="block"
+              isLoading={ query.isPlaceholderData }
+            />
             { query.data.items.map((item, index) => (
               <AddressBlocksValidatedTableItem
                 key={ item.height + (query.isPlaceholderData ? String(index) : '') }
@@ -121,6 +136,15 @@ const AddressBlocksValidated = ({ scrollRef, shouldRender = true, isQueryEnabled
         </Table>
       </Hide>
       <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) => (
           <AddressBlocksValidatedListItem
             key={ item.height + (query.isPlaceholderData ? String(index) : '') }
diff --git a/ui/address/blocksValidated/AddressBlocksValidatedListItem.tsx b/ui/address/blocksValidated/AddressBlocksValidatedListItem.tsx
index 81065d7f27..1ec811a683 100644
--- a/ui/address/blocksValidated/AddressBlocksValidatedListItem.tsx
+++ b/ui/address/blocksValidated/AddressBlocksValidatedListItem.tsx
@@ -52,7 +52,7 @@ const AddressBlocksValidatedListItem = (props: Props) => {
           isLoading={ props.isLoading }
         />
       </Flex>
-      { !config.UI.views.block.hiddenFields?.total_reward && (
+      { !config.UI.views.block.hiddenFields?.total_reward && !config.features.rollup.isEnabled && (
         <Flex columnGap={ 2 } w="100%">
           <Skeleton isLoaded={ !props.isLoading } fontWeight={ 500 } flexShrink={ 0 }>Reward { currencyUnits.ether }</Skeleton>
           <Skeleton isLoaded={ !props.isLoading } color="text_secondary">{ totalReward.toFixed() }</Skeleton>
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) => {
           />
         </Flex>
       </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">
           <Skeleton isLoaded={ !props.isLoading } display="inline-block">
             <span>{ totalReward.toFixed() }</span>
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: <AddressCoinBalance shouldRender={ !isTabsLoading } isQueryEnabled={ areQueriesEnabled }/>,
       },
-      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: <AddressBlocksValidated scrollRef={ tabsScrollRef } shouldRender={ !isTabsLoading } isQueryEnabled={ areQueriesEnabled }/>,
         } :
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 }
         >
           <chakra.span flexShrink={ 0 }>
-            { config.chain.verificationType === 'validation' ? 'Validated by' : 'Mined by' }
+            { `${ capitalize(getNetworkValidationActionText()) } by` }
           </chakra.span>
           <AddressEntity address={ blockQuery.data?.miner }/>
         </Skeleton>
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);