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;