Files
explore/ui/block/BlockDetails.tsx
zeekayandHanzo Dev 56c0e129b2 fix(explore): a block's fee label must follow the policy at THAT block
The fee-destination gate landed head-anchored: useFeeDestination() read
reading.status, which deriveStatus() computes against the HEAD block's
timestamp, and every per-block surface then rendered that one verdict.

Head status answers "is the split active NOW". A block page asks "was THIS
block's fee burned". They agree only while the split has never been active —
which is why this is invisible today: mainnet 96369 has no feeSplitTimestamp
at all (eth_getChainConfig answers, chainId 96369 present, and feeSplit
appears nowhere in it including upgrades/precompileUpgrades/stateUpgrades),
so every block correctly reads "Fees to coinbase".

The day feeSplitTimestamp is set and passes, all ~1.1M pre-activation blocks
would be relabelled "Burnt fees" with a burn ratio — the same false deflation
claim this module exists to remove, moved into history, and this time about
blocks whose fees demonstrably went to the coinbase 0x0100..0000 (3868.48 LUX
and climbing, measured at head 1098308).

So the destination is derived per block:

  feeDestinationAt(reading, blockTime)  pure; re-derives the gate against the
                                        block's own time via deriveStatus
  useFeeDestination(blockTime?)         one-line wrapper over it
  blockTimeSeconds(apiTimestamp)        ISO -> seconds, undefined when absent

Passed at the four surfaces that name one block (block details, blocks table
row, blocks list row, tx details). BlocksTable keeps the head reading with a
comment saying why: a column header spans many blocks, so it cannot be a
per-block claim, and the per-row label and ratio are each gated individually.
A missing timestamp falls back to the head reading, so a loading row keeps
today's behaviour rather than flashing "destination unknown".

Decomplected on purpose: the decision is a pure function of (reading,
blockTime), so it is tested without a React tree. Reverting the body to
deriveFeeDestination(reading.status) fails "does NOT report a pre-activation
block as burned" — the regression is pinned, not asserted.

32/32 vitest green (5 new cases); tsc reports 0 errors in all 7 touched files
and eslint is clean on them (control: 232 pre-existing tsc errors, none in
these files).

Co-authored-by: Hanzo Dev <[email protected]>
2026-07-28 01:09:44 -07:00

758 lines
28 KiB
TypeScript

import { CollapsibleDetails } from '@luxfi/ui/collapsible';
import { Skeleton } from '@luxfi/ui/skeleton';
import { Tooltip } from '@luxfi/ui/tooltip';
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 { blockTimeSeconds, feeDestinationCopy, useFeeDestination } from 'lib/api/cchain/useFeeSplit';
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 { Link } from 'toolkit/next/link';
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;
// Whether THIS block's base fee was destroyed, read live and gated on this
// block's own timestamp — not on the policy in force at the head. Never assumed.
const feeCopy = feeDestinationCopy(useFeeDestination(blockTimeSeconds(data?.timestamp)));
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 <Skeleton loading w="525px" h="20px"/>;
}
return (
<span className="text-[var(--color-text-secondary)]" style={{ whiteSpace: 'break-spaces' }}>
<Tooltip content="Static block reward">
<span>{ staticReward.dividedBy(WEI).toFixed() }</span>
</Tooltip>
{ !txFees.isEqualTo(ZERO) && (
<>
{ space }+{ space }
<Tooltip content="Txn fees">
<span>{ txFees.dividedBy(WEI).toFixed() }</span>
</Tooltip>
</>
) }
{ !burntFees.isEqualTo(ZERO) && (
<>
{ space }-{ space }
<Tooltip content={ feeCopy.label }>
<span>{ burntFees.dividedBy(WEI).toFixed() }</span>
</Tooltip>
</>
) }
</span>
);
})();
const txsNum = (() => {
const blockTxsNum = (
<Link href={ route({ pathname: '/block/[height_or_hash]', query: { height_or_hash: heightOrHash, tab: 'txs' } }, multichainContext) }>
{ data.transactions_count } txn{ data.transactions_count === 1 ? '' : 's' }
</Link>
);
const blockBlobTxsNum = (config.features.dataAvailability.isEnabled && data.blob_transactions_count) ? (
<>
<span> including </span>
<Link href={ route({ pathname: '/block/[height_or_hash]', query: { height_or_hash: heightOrHash, tab: 'blob_txs' } }, multichainContext) }>
{ data.blob_transactions_count } blob txn{ data.blob_transactions_count === 1 ? '' : 's' }
</Link>
</>
) : null;
return (
<>
{ blockTxsNum }
{ blockBlobTxsNum }
<span> in this block</span>
</>
);
})();
const blockTypeLabel = (() => {
switch (data.type) {
case 'reorg':
return 'Reorg';
case 'uncle':
return 'Uncle';
default:
return 'Block';
}
})();
return (
<DetailedInfo.Container templateColumns={{ base: 'minmax(0, 1fr)', lg: 'minmax(min-content, 200px) minmax(0, 1fr)' }} >
<DetailedInfo.ItemLabel
hint="The block height of a particular block is defined as the number of blocks preceding it in the blockchain"
isLoading={ isPlaceholderData }
>
{ blockTypeLabel } height
</DetailedInfo.ItemLabel>
<DetailedInfo.ItemValue>
<Skeleton loading={ isPlaceholderData }>
{ data.height }
</Skeleton>
{ data.height === 0 && <span className="whitespace-pre"> - Genesis Block</span> }
<PrevNext
className="ml-6"
onClick={ handlePrevNextClick }
prevLabel="View previous block"
nextLabel="View next block"
isPrevDisabled={ data.height === 0 }
isLoading={ isPlaceholderData }
/>
</DetailedInfo.ItemValue>
{ rollupFeature.isEnabled && rollupFeature.type === 'arbitrum' && data.arbitrum && (
<>
<DetailedInfo.ItemLabel
hint={ `The most recent ${ layerLabels.parent } block height as of this ${ layerLabels.current } block` }
isLoading={ isPlaceholderData }
>
{ layerLabels.parent } block height
</DetailedInfo.ItemLabel>
<DetailedInfo.ItemValue>
<BlockEntityL1 isLoading={ isPlaceholderData } number={ data.arbitrum.l1_block_number }/>
</DetailedInfo.ItemValue>
</>
) }
{ rollupFeature.isEnabled && rollupFeature.type === 'arbitrum' && data.arbitrum && !config.UI.views.block.hiddenFields?.batch && (
<>
<DetailedInfo.ItemLabel
hint="Batch number"
isLoading={ isPlaceholderData }
>
Batch
</DetailedInfo.ItemLabel>
<DetailedInfo.ItemValue>
{ data.arbitrum.batch_number ?
<BatchEntityL2 isLoading={ isPlaceholderData } number={ data.arbitrum.batch_number }/> :
<Skeleton loading={ isPlaceholderData }>Pending</Skeleton> }
</DetailedInfo.ItemValue>
</>
) }
{ rollupFeature.isEnabled && rollupFeature.type === 'optimistic' && data.optimism && !config.UI.views.block.hiddenFields?.batch && (
<>
<DetailedInfo.ItemLabel
hint="Batch number"
isLoading={ isPlaceholderData }
>
Batch
</DetailedInfo.ItemLabel>
<DetailedInfo.ItemValue columnGap={ 3 }>
{ data.optimism.number ?
<BatchEntityL2 isLoading={ isPlaceholderData } number={ data.optimism.number }/> :
<Skeleton loading={ isPlaceholderData }>Pending</Skeleton> }
{ data.optimism.batch_data_container && (
<OptimisticL2TxnBatchDA
container={ data.optimism.batch_data_container }
isLoading={ isPlaceholderData }
/>
) }
</DetailedInfo.ItemValue>
</>
) }
{ typeof data.size === 'number' && (
<>
<DetailedInfo.ItemLabel
hint="Size of the block in bytes"
isLoading={ isPlaceholderData }
>
Size
</DetailedInfo.ItemLabel>
<DetailedInfo.ItemValue>
<Skeleton loading={ isPlaceholderData }>
{ data.size.toLocaleString() }
</Skeleton>
</DetailedInfo.ItemValue>
</>
) }
<DetailedInfo.ItemLabel
hint="Date & time at which block was produced."
isLoading={ isPlaceholderData }
>
Timestamp
</DetailedInfo.ItemLabel>
<DetailedInfo.ItemValue>
<DetailedInfoTimestamp timestamp={ data.timestamp } isLoading={ isPlaceholderData }/>
</DetailedInfo.ItemValue>
<DetailedInfo.ItemLabel
hint="The number of transactions in the block"
isLoading={ isPlaceholderData }
>
Transactions
</DetailedInfo.ItemLabel>
<DetailedInfo.ItemValue>
<Skeleton loading={ isPlaceholderData }>
{ txsNum }
</Skeleton>
</DetailedInfo.ItemValue>
{ config.features.beaconChain.isEnabled && Boolean(data.withdrawals_count) && (
<>
<DetailedInfo.ItemLabel
hint="The number of beacon withdrawals in the block"
isLoading={ isPlaceholderData }
>
Withdrawals
</DetailedInfo.ItemLabel>
<DetailedInfo.ItemValue>
<Skeleton loading={ isPlaceholderData }>
<Link href={ route({ pathname: '/block/[height_or_hash]', query: { height_or_hash: heightOrHash, tab: 'withdrawals' } }, multichainContext) }>
{ data.withdrawals_count } withdrawal{ data.withdrawals_count === 1 ? '' : 's' }
</Link>
</Skeleton>
</DetailedInfo.ItemValue>
</>
) }
{ rollupFeature.isEnabled && rollupFeature.type === 'zkSync' && data.zksync && !config.UI.views.block.hiddenFields?.batch && (
<>
<DetailedInfo.ItemLabel
hint="Batch number"
isLoading={ isPlaceholderData }
>
Batch
</DetailedInfo.ItemLabel>
<DetailedInfo.ItemValue>
{ data.zksync.batch_number ?
<BatchEntityL2 isLoading={ isPlaceholderData } number={ data.zksync.batch_number }/> :
<Skeleton loading={ isPlaceholderData }>Pending</Skeleton> }
</DetailedInfo.ItemValue>
</>
) }
{ !config.UI.views.block.hiddenFields?.L1_status && rollupFeature.isEnabled &&
((rollupFeature.type === 'zkSync' && data.zksync) || (rollupFeature.type === 'arbitrum' && data.arbitrum)) &&
(
<>
<DetailedInfo.ItemLabel
hint="Status is the short interpretation of the batch lifecycle"
isLoading={ isPlaceholderData }
>
Status
</DetailedInfo.ItemLabel>
<DetailedInfo.ItemValue>
{ rollupFeature.type === 'zkSync' && data.zksync && (
<VerificationSteps
steps={ zkSyncVerificationSteps }
currentStep={ formatZkSyncL2TxnBatchStatus(data.zksync.status) }
isLoading={ isPlaceholderData }
/>
) }
{ rollupFeature.type === 'arbitrum' && data.arbitrum && (
<VerificationSteps
steps={ arbitrum.verificationSteps }
currentStep={ arbitrum.VERIFICATION_STEPS_MAP[data.arbitrum.status] }
currentStepPending={ arbitrum.getVerificationStepStatus(data.arbitrum) === 'pending' }
isLoading={ isPlaceholderData }
/>
) }
</DetailedInfo.ItemValue>
</>
) }
{ !config.UI.views.block.hiddenFields?.miner && (
<>
<DetailedInfo.ItemLabel
hint="A block producer who successfully included the block onto the blockchain"
isLoading={ isPlaceholderData }
>
{ capitalize(validatorTitle) }
</DetailedInfo.ItemLabel>
<DetailedInfo.ItemValue>
<AddressEntity
address={ data.miner }
isLoading={ isPlaceholderData }
/>
</DetailedInfo.ItemValue>
</>
) }
{ rollupFeature.isEnabled && rollupFeature.type === 'arbitrum' &&
(data.arbitrum?.commitment_transaction.hash || data.arbitrum?.confirmation_transaction.hash) &&
(
<>
<DetailedInfo.ItemDivider/>
{ data.arbitrum?.commitment_transaction.hash && (
<>
<DetailedInfo.ItemLabel
hint={ `${ layerLabels.parent } transaction containing this batch commitment` }
isLoading={ isPlaceholderData }
>
Commitment tx
</DetailedInfo.ItemLabel>
<DetailedInfo.ItemValue>
<TxEntityL1 hash={ data.arbitrum?.commitment_transaction.hash } isLoading={ isPlaceholderData }/>
{ data.arbitrum?.commitment_transaction.status === 'finalized' && <StatusTag type="ok" text="Finalized" className="ml-2"/> }
</DetailedInfo.ItemValue>
</>
) }
{ data.arbitrum?.confirmation_transaction.hash && (
<>
<DetailedInfo.ItemLabel
hint={ `${ layerLabels.parent } transaction containing confirmation of this batch` }
isLoading={ isPlaceholderData }
>
Confirmation tx
</DetailedInfo.ItemLabel>
<DetailedInfo.ItemValue>
<TxEntityL1 hash={ data.arbitrum?.confirmation_transaction.hash } isLoading={ isPlaceholderData }/>
{ data.arbitrum?.commitment_transaction.status === 'finalized' && <StatusTag type="ok" text="Finalized" className="ml-2"/> }
</DetailedInfo.ItemValue>
</>
) }
</>
) }
{ !rollupFeature.isEnabled && !totalReward.isEqualTo(ZERO) && !config.UI.views.block.hiddenFields?.total_reward && (
<>
<DetailedInfo.ItemLabel
hint={
`For each block, the ${ validatorTitle } is rewarded with a finite amount of ${ config.chain.currency.symbol || 'native token' }
on top of the fees paid for all transactions in the block`
}
isLoading={ isPlaceholderData }
>
Block reward
</DetailedInfo.ItemLabel>
<DetailedInfo.ItemValue columnGap={ 1 } multiRow>
<NativeCoinValue amount={ totalReward.toString() } accuracy={ 0 } loading={ isPlaceholderData }/>
{ rewardBreakDown }
</DetailedInfo.ItemValue>
</>
) }
{ data.rewards
?.filter(({ type }) => type !== 'Validator Reward' && type !== 'Miner Reward')
.map(({ type, reward }) => (
<React.Fragment key={ type }>
<DetailedInfo.ItemLabel
hint={ `Amount of distributed reward. ${ capitalize(validatorTitle) }s receive a static block reward + Tx fees + uncle fees` }
>
{ type }
</DetailedInfo.ItemLabel>
<DetailedInfo.ItemValue>
<NativeCoinValue amount={ reward.toString() } accuracy={ 0 }/>
</DetailedInfo.ItemValue>
</React.Fragment>
))
}
{ typeof data.zilliqa?.view === 'number' && (
<>
<DetailedInfo.ItemLabel
hint="The iteration of the consensus round in which the block was proposed"
isLoading={ isPlaceholderData }
>
View
</DetailedInfo.ItemLabel>
<DetailedInfo.ItemValue>
<Skeleton loading={ isPlaceholderData }>
{ data.zilliqa.view }
</Skeleton>
</DetailedInfo.ItemValue>
</>
) }
<DetailedInfo.ItemDivider/>
{ data.celo?.base_fee && <BlockDetailsBaseFeeCelo data={ data.celo.base_fee }/> }
<DetailedInfo.ItemLabel
hint="The total gas amount used in the block and its percentage of gas filled in the block"
isLoading={ isPlaceholderData }
>
Gas used
</DetailedInfo.ItemLabel>
<DetailedInfo.ItemValue>
<Skeleton loading={ isPlaceholderData }>
{ BigNumber(data.gas_used || 0).toFormat() }
</Skeleton>
<BlockGasUsed
gasUsed={ data.gas_used || undefined }
gasLimit={ data.gas_limit }
isLoading={ isPlaceholderData }
className="ml-4"
gasTarget={ data.gas_target_percentage || undefined }
/>
</DetailedInfo.ItemValue>
<DetailedInfo.ItemLabel
hint="Total gas limit provided by all transactions in the block"
isLoading={ isPlaceholderData }
>
Gas limit
</DetailedInfo.ItemLabel>
<DetailedInfo.ItemValue>
<Skeleton loading={ isPlaceholderData }>
{ BigNumber(data.gas_limit).toFormat() }
</Skeleton>
</DetailedInfo.ItemValue>
{ data.minimum_gas_price && (
<>
<DetailedInfo.ItemLabel
hint="The minimum gas price a transaction should have in order to be included in this block"
isLoading={ isPlaceholderData }
>
Minimum gas price
</DetailedInfo.ItemLabel>
<DetailedInfo.ItemValue>
<NativeCoinValue amount={ data.minimum_gas_price } units="gwei" loading={ isPlaceholderData }/>
</DetailedInfo.ItemValue>
</>
) }
{ data.base_fee_per_gas && (
<>
<DetailedInfo.ItemLabel
hint="Minimum fee required per unit of gas. Fee adjusts based on network congestion"
isLoading={ isPlaceholderData }
>
Base fee per gas
</DetailedInfo.ItemLabel>
<DetailedInfo.ItemValue multiRow>
<GasPriceValue
amount={ data.base_fee_per_gas }
loading={ isPlaceholderData }
/>
</DetailedInfo.ItemValue>
</>
) }
{ !config.UI.views.block.hiddenFields?.burnt_fees && !burntFees.isEqualTo(ZERO) && (
<>
<DetailedInfo.ItemLabel
hint={ feeCopy.hint }
isLoading={ isPlaceholderData }
>
{ feeCopy.label }
</DetailedInfo.ItemLabel>
<DetailedInfo.ItemValue multiRow>
<NativeCoinValue
amount={ burntFees.toString() }
accuracy={ 0 }
loading={ isPlaceholderData }
startElement={ <IconSvg name="flame" className="w-5 h-5 text-[var(--color-icon-primary)] mr-1 lg:mr-2" isLoading={ isPlaceholderData }/> }
mr={ 4 }
/>
{ feeCopy.showBurnRatio && !txFees.isEqualTo(ZERO) && (
<Tooltip content="Burnt fees / Txn fees * 100%">
<Utilization
value={ burntFees.dividedBy(txFees).toNumber() }
isLoading={ isPlaceholderData }
/>
</Tooltip>
) }
</DetailedInfo.ItemValue>
</>
) }
{ data.priority_fee !== null && BigNumber(data.priority_fee).gt(ZERO) && (
<>
<DetailedInfo.ItemLabel
hint="User-defined tips sent to validator for transaction priority/inclusion"
isLoading={ isPlaceholderData }
>
Priority fee / Tip
</DetailedInfo.ItemLabel>
<DetailedInfo.ItemValue>
<NativeCoinValue amount={ data.priority_fee.toString() } accuracy={ 0 } loading={ isPlaceholderData }/>
</DetailedInfo.ItemValue>
</>
) }
{ /* ADDITIONAL INFO */ }
<CollapsibleDetails loading={ isPlaceholderData } className="mt-6 lg:col-[1/3]">
<div className="lg:col-span-2 mt-1 lg:mt-4"/>
{ rollupFeature.isEnabled && rollupFeature.type === 'zkSync' && data.zksync &&
<ZkSyncL2TxnBatchHashesInfo data={ data.zksync } isLoading={ isPlaceholderData }/> }
{ !isPlaceholderData && <BlockDetailsBlobInfo data={ data }/> }
{ data.bitcoin_merged_mining_header && (
<>
<DetailedInfo.ItemLabel
hint="Merged-mining field: Bitcoin header"
>
Bitcoin merged mining header
</DetailedInfo.ItemLabel>
<DetailedInfo.ItemValue
flexWrap="nowrap"
alignSelf="flex-start"
>
<div className="whitespace-nowrap overflow-hidden">
<HashStringShortenDynamic hash={ data.bitcoin_merged_mining_header }/>
</div>
<CopyToClipboard text={ data.bitcoin_merged_mining_header }/>
</DetailedInfo.ItemValue>
</>
) }
{ data.bitcoin_merged_mining_coinbase_transaction && (
<>
<DetailedInfo.ItemLabel
hint="Merged-mining field: Coinbase transaction"
>
Bitcoin merged mining coinbase transaction
</DetailedInfo.ItemLabel>
<DetailedInfo.ItemValue>
<RawDataSnippet
data={ data.bitcoin_merged_mining_coinbase_transaction }
isLoading={ isPlaceholderData }
showCopy={ false }
textareaMaxHeight="100px"
/>
</DetailedInfo.ItemValue>
</>
) }
{ data.bitcoin_merged_mining_merkle_proof && (
<>
<DetailedInfo.ItemLabel
hint="Merged-mining field: Merkle proof"
>
Bitcoin merged mining Merkle proof
</DetailedInfo.ItemLabel>
<DetailedInfo.ItemValue>
<RawDataSnippet
data={ data.bitcoin_merged_mining_merkle_proof }
isLoading={ isPlaceholderData }
showCopy={ false }
textareaMaxHeight="100px"
/>
</DetailedInfo.ItemValue>
</>
) }
{ data.hash_for_merged_mining && (
<>
<DetailedInfo.ItemLabel
hint="Merged-mining field: Rootstock block header hash"
>
Hash for merged mining
</DetailedInfo.ItemLabel>
<DetailedInfo.ItemValue
flexWrap="nowrap"
alignSelf="flex-start"
>
<div className="whitespace-nowrap overflow-hidden">
<HashStringShortenDynamic hash={ data.hash_for_merged_mining }/>
</div>
<CopyToClipboard text={ data.hash_for_merged_mining }/>
</DetailedInfo.ItemValue>
</>
) }
{ data.difficulty && (
<>
<DetailedInfo.ItemLabel
hint={ `Block difficulty for ${ validatorTitle }, used to calibrate block generation time` }
>
Difficulty
</DetailedInfo.ItemLabel>
<DetailedInfo.ItemValue>
<div className="overflow-hidden">
<HashStringShortenDynamic hash={ BigNumber(data.difficulty).toFormat() }/>
</div>
</DetailedInfo.ItemValue>
</>
) }
{ data.total_difficulty && (
<>
<DetailedInfo.ItemLabel
hint="Total difficulty of the chain until this block"
>
Total difficulty
</DetailedInfo.ItemLabel>
<DetailedInfo.ItemValue>
<div className="overflow-hidden">
<HashStringShortenDynamic hash={ BigNumber(data.total_difficulty).toFormat() }/>
</div>
</DetailedInfo.ItemValue>
</>
) }
<DetailedInfo.ItemDivider/>
<DetailedInfo.ItemLabel
hint="The SHA256 hash of the block"
>
Hash
</DetailedInfo.ItemLabel>
<DetailedInfo.ItemValue flexWrap="nowrap">
<div className="overflow-hidden" >
<HashStringShortenDynamic hash={ data.hash }/>
</div>
<CopyToClipboard text={ data.hash }/>
</DetailedInfo.ItemValue>
{ data.height > 0 && (
<>
<DetailedInfo.ItemLabel
hint="The hash of the block from which this block was generated"
>
Parent hash
</DetailedInfo.ItemLabel>
<DetailedInfo.ItemValue flexWrap="nowrap">
<Link
href={ route({ pathname: '/block/[height_or_hash]', query: { height_or_hash: String(data.height - 1) } }, multichainContext) }
className="overflow-hidden whitespace-nowrap"
>
<HashStringShortenDynamic
hash={ data.parent_hash }
/>
</Link>
<CopyToClipboard text={ data.parent_hash }/>
</DetailedInfo.ItemValue>
</>
) }
{ rollupFeature.isEnabled && rollupFeature.type === 'arbitrum' && data.arbitrum && data.arbitrum.send_count && (
<>
<DetailedInfo.ItemLabel
hint={ `The cumulative number of ${ layerLabels.current } to ${ layerLabels.parent } transactions as of this block` }
isLoading={ isPlaceholderData }
>
Send count
</DetailedInfo.ItemLabel>
<DetailedInfo.ItemValue>
{ data.arbitrum.send_count.toLocaleString() }
</DetailedInfo.ItemValue>
<DetailedInfo.ItemLabel
hint={ `The root of the Merkle accumulator representing all ${ layerLabels.current } to ${ layerLabels.parent } transactions as of this block` }
isLoading={ isPlaceholderData }
>
Send root
</DetailedInfo.ItemLabel>
<DetailedInfo.ItemValue>
{ data.arbitrum.send_root }
</DetailedInfo.ItemValue>
<DetailedInfo.ItemLabel
hint={ `The number of delayed ${ layerLabels.parent } to ${ layerLabels.current } messages read as of this block` }
isLoading={ isPlaceholderData }
>
Delayed messages
</DetailedInfo.ItemLabel>
<DetailedInfo.ItemValue>
{ data.arbitrum.delayed_messages.toLocaleString() }
</DetailedInfo.ItemValue>
</>
) }
{ !config.UI.views.block.hiddenFields?.nonce && (
<>
<DetailedInfo.ItemLabel
hint="Block nonce is a value used during mining to demonstrate proof of work for a block"
>
Nonce
</DetailedInfo.ItemLabel>
<DetailedInfo.ItemValue>
{ data.nonce }
</DetailedInfo.ItemValue>
</>
) }
{ data.zilliqa && (
<>
<DetailedInfo.ItemDivider/>
<BlockDetailsZilliqaQuorumCertificate data={ data.zilliqa?.quorum_certificate }/>
{ data.zilliqa?.aggregate_quorum_certificate && (
<>
<div className="lg:col-span-2 mt-1 lg:mt-2"/>
<BlockDetailsZilliqaQuorumCertificate data={ data.zilliqa?.aggregate_quorum_certificate }/>
</>
) }
</>
) }
</CollapsibleDetails>
</DetailedInfo.Container>
);
};
export default BlockDetails;