import { GridItem, Text, Box } from '@chakra-ui/react'; import BigNumber from 'bignumber.js'; import { capitalize } from 'es-toolkit'; import { useRouter } from 'next/router'; import React from 'react'; import { ZKSYNC_L2_TX_BATCH_STATUSES } from 'types/api/zkSyncL2'; import { route, routeParams } from 'nextjs/routes'; import config from 'configs/app'; import getBlockReward from 'lib/block/getBlockReward'; import { useMultichainContext } from 'lib/contexts/multichain'; import getNetworkValidatorTitle from 'lib/networks/getNetworkValidatorTitle'; import * as arbitrum from 'lib/rollups/arbitrum'; import { formatZkSyncL2TxnBatchStatus, layerLabels } from 'lib/rollups/utils'; import getQueryParamString from 'lib/router/getQueryParamString'; import { CollapsibleDetails } from 'toolkit/chakra/collapsible'; import { Link } from 'toolkit/chakra/link'; import { Skeleton } from 'toolkit/chakra/skeleton'; import { Tooltip } from 'toolkit/chakra/tooltip'; import { ZERO } from 'toolkit/utils/consts'; import { space } from 'toolkit/utils/htmlEntities'; import OptimisticL2TxnBatchDA from 'ui/shared/batch/OptimisticL2TxnBatchDA'; import BlockGasUsed from 'ui/shared/block/BlockGasUsed'; import CopyToClipboard from 'ui/shared/CopyToClipboard'; import * as DetailedInfo from 'ui/shared/DetailedInfo/DetailedInfo'; import DetailedInfoTimestamp from 'ui/shared/DetailedInfo/DetailedInfoTimestamp'; import AddressEntity from 'ui/shared/entities/address/AddressEntity'; import BatchEntityL2 from 'ui/shared/entities/block/BatchEntityL2'; import BlockEntityL1 from 'ui/shared/entities/block/BlockEntityL1'; import TxEntityL1 from 'ui/shared/entities/tx/TxEntityL1'; import HashStringShortenDynamic from 'ui/shared/HashStringShortenDynamic'; import IconSvg from 'ui/shared/IconSvg'; import PrevNext from 'ui/shared/PrevNext'; import RawDataSnippet from 'ui/shared/RawDataSnippet'; import StatusTag from 'ui/shared/statusTag/StatusTag'; import Utilization from 'ui/shared/Utilization/Utilization'; import GasPriceValue from 'ui/shared/value/GasPriceValue'; import NativeCoinValue from 'ui/shared/value/NativeCoinValue'; import { WEI } from 'ui/shared/value/utils'; import VerificationSteps from 'ui/shared/verificationSteps/VerificationSteps'; import ZkSyncL2TxnBatchHashesInfo from 'ui/txnBatches/zkSyncL2/ZkSyncL2TxnBatchHashesInfo'; import BlockDetailsBaseFeeCelo from './details/BlockDetailsBaseFeeCelo'; import BlockDetailsBlobInfo from './details/BlockDetailsBlobInfo'; import BlockDetailsZilliqaQuorumCertificate from './details/BlockDetailsZilliqaQuorumCertificate'; import type { BlockQuery } from './useBlockQuery'; const zkSyncVerificationSteps = ZKSYNC_L2_TX_BATCH_STATUSES.map(formatZkSyncL2TxnBatchStatus); interface Props { query: BlockQuery; } const rollupFeature = config.features.rollup; const BlockDetails = ({ query }: Props) => { const router = useRouter(); const heightOrHash = getQueryParamString(router.query.height_or_hash); const multichainContext = useMultichainContext(); const { data, isPlaceholderData } = query; const handlePrevNextClick = React.useCallback((direction: 'prev' | 'next') => { if (!data) { return; } const increment = direction === 'next' ? +1 : -1; const nextId = String(data.height + increment); router.push(routeParams({ pathname: '/block/[height_or_hash]', query: { height_or_hash: nextId } }, { chain: multichainContext?.chain })); }, [ data, multichainContext, router ]); if (!data) { return null; } const { totalReward, staticReward, burntFees, txFees } = getBlockReward(data); const validatorTitle = getNetworkValidatorTitle(); const rewardBreakDown = (() => { if (rollupFeature.isEnabled || totalReward.isEqualTo(ZERO) || txFees.isEqualTo(ZERO) || burntFees.isEqualTo(ZERO)) { return null; } if (isPlaceholderData) { return ; } return ( { staticReward.dividedBy(WEI).toFixed() } { !txFees.isEqualTo(ZERO) && ( <> { space }+{ space } { txFees.dividedBy(WEI).toFixed() } ) } { !burntFees.isEqualTo(ZERO) && ( <> { space }-{ space } { burntFees.dividedBy(WEI).toFixed() } ) } ); })(); const txsNum = (() => { const blockTxsNum = ( { data.transactions_count } txn{ data.transactions_count === 1 ? '' : 's' } ); const blockBlobTxsNum = (config.features.dataAvailability.isEnabled && data.blob_transactions_count) ? ( <> including { data.blob_transactions_count } blob txn{ data.blob_transactions_count === 1 ? '' : 's' } ) : null; return ( <> { blockTxsNum } { blockBlobTxsNum } in this block ); })(); const blockTypeLabel = (() => { switch (data.type) { case 'reorg': return 'Reorg'; case 'uncle': return 'Uncle'; default: return 'Block'; } })(); return ( { blockTypeLabel } height { data.height } { data.height === 0 && - Genesis Block } { rollupFeature.isEnabled && rollupFeature.type === 'arbitrum' && data.arbitrum && ( <> { layerLabels.parent } block height ) } { rollupFeature.isEnabled && rollupFeature.type === 'arbitrum' && data.arbitrum && !config.UI.views.block.hiddenFields?.batch && ( <> Batch { data.arbitrum.batch_number ? : Pending } ) } { rollupFeature.isEnabled && rollupFeature.type === 'optimistic' && data.optimism && !config.UI.views.block.hiddenFields?.batch && ( <> Batch { data.optimism.number ? : Pending } { data.optimism.batch_data_container && ( ) } ) } { typeof data.size === 'number' && ( <> Size { data.size.toLocaleString() } ) } Timestamp Transactions { txsNum } { config.features.beaconChain.isEnabled && Boolean(data.withdrawals_count) && ( <> Withdrawals { data.withdrawals_count } withdrawal{ data.withdrawals_count === 1 ? '' : 's' } ) } { rollupFeature.isEnabled && rollupFeature.type === 'zkSync' && data.zksync && !config.UI.views.block.hiddenFields?.batch && ( <> Batch { data.zksync.batch_number ? : Pending } ) } { !config.UI.views.block.hiddenFields?.L1_status && rollupFeature.isEnabled && ((rollupFeature.type === 'zkSync' && data.zksync) || (rollupFeature.type === 'arbitrum' && data.arbitrum)) && ( <> Status { rollupFeature.type === 'zkSync' && data.zksync && ( ) } { rollupFeature.type === 'arbitrum' && data.arbitrum && ( ) } ) } { !config.UI.views.block.hiddenFields?.miner && ( <> { capitalize(validatorTitle) } ) } { rollupFeature.isEnabled && rollupFeature.type === 'arbitrum' && (data.arbitrum?.commitment_transaction.hash || data.arbitrum?.confirmation_transaction.hash) && ( <> { data.arbitrum?.commitment_transaction.hash && ( <> Commitment tx { data.arbitrum?.commitment_transaction.status === 'finalized' && } ) } { data.arbitrum?.confirmation_transaction.hash && ( <> Confirmation tx { data.arbitrum?.commitment_transaction.status === 'finalized' && } ) } ) } { !rollupFeature.isEnabled && !totalReward.isEqualTo(ZERO) && !config.UI.views.block.hiddenFields?.total_reward && ( <> Block reward { rewardBreakDown } ) } { data.rewards ?.filter(({ type }) => type !== 'Validator Reward' && type !== 'Miner Reward') .map(({ type, reward }) => ( { type } )) } { typeof data.zilliqa?.view === 'number' && ( <> View { data.zilliqa.view } ) } { data.celo?.base_fee && } Gas used { BigNumber(data.gas_used || 0).toFormat() } Gas limit { BigNumber(data.gas_limit).toFormat() } { data.minimum_gas_price && ( <> Minimum gas price ) } { data.base_fee_per_gas && ( <> Base fee per gas ) } { !config.UI.views.block.hiddenFields?.burnt_fees && !burntFees.isEqualTo(ZERO) && ( <> Burnt fees } mr={ 4 } /> { !txFees.isEqualTo(ZERO) && ( ) } ) } { data.priority_fee !== null && BigNumber(data.priority_fee).gt(ZERO) && ( <> Priority fee / Tip ) } { /* ADDITIONAL INFO */ } { rollupFeature.isEnabled && rollupFeature.type === 'zkSync' && data.zksync && } { !isPlaceholderData && } { data.bitcoin_merged_mining_header && ( <> Bitcoin merged mining header ) } { data.bitcoin_merged_mining_coinbase_transaction && ( <> Bitcoin merged mining coinbase transaction ) } { data.bitcoin_merged_mining_merkle_proof && ( <> Bitcoin merged mining Merkle proof ) } { data.hash_for_merged_mining && ( <> Hash for merged mining ) } { data.difficulty && ( <> Difficulty ) } { data.total_difficulty && ( <> Total difficulty ) } Hash { data.height > 0 && ( <> Parent hash ) } { rollupFeature.isEnabled && rollupFeature.type === 'arbitrum' && data.arbitrum && data.arbitrum.send_count && ( <> Send count { data.arbitrum.send_count.toLocaleString() } Send root { data.arbitrum.send_root } Delayed messages { data.arbitrum.delayed_messages.toLocaleString() } ) } { !config.UI.views.block.hiddenFields?.nonce && ( <> Nonce { data.nonce } ) } { data.zilliqa && ( <> { data.zilliqa?.aggregate_quorum_certificate && ( <> ) } ) } ); }; export default BlockDetails;