Commit b5757e40 authored by tom goriunov's avatar tom goriunov Committed by GitHub

Details page: fix line height for rows (#1949)

* refactor block page

* token details

* token instance details

* name domain details

* blob details

* user ops details

* zkEvm and zkSync batch details

* tx details

* address details

* update screenshots
parent 86e33cae
......@@ -10,7 +10,7 @@ import AddressCounterItem from 'ui/address/details/AddressCounterItem';
import ServiceDegradationWarning from 'ui/shared/alerts/ServiceDegradationWarning';
import isCustomAppError from 'ui/shared/AppError/isCustomAppError';
import DataFetchAlert from 'ui/shared/DataFetchAlert';
import DetailsInfoItem from 'ui/shared/DetailsInfoItem';
import * as DetailsInfoItem from 'ui/shared/DetailsInfoItem';
import DetailsSponsoredItem from 'ui/shared/DetailsSponsoredItem';
import AddressEntity from 'ui/shared/entities/address/AddressEntity';
import BlockEntity from 'ui/shared/entities/block/BlockEntity';
......@@ -92,12 +92,16 @@ const AddressDetails = ({ addressQuery, scrollRef }: Props) => {
templateColumns={{ base: 'minmax(0, 1fr)', lg: 'auto minmax(0, 1fr)' }} overflow="hidden"
>
<AddressNameInfo data={ data } isLoading={ addressQuery.isPlaceholderData }/>
{ data.is_contract && data.creation_tx_hash && data.creator_address_hash && (
<DetailsInfoItem
title="Creator"
<>
<DetailsInfoItem.Label
hint="Transaction and address of creation"
isLoading={ addressQuery.isPlaceholderData }
>
Creator
</DetailsInfoItem.Label>
<DetailsInfoItem.Value>
<AddressEntity
address={{ hash: data.creator_address_hash }}
truncation="constant"
......@@ -105,48 +109,63 @@ const AddressDetails = ({ addressQuery, scrollRef }: Props) => {
/>
<Text whiteSpace="pre"> at txn </Text>
<TxEntity hash={ data.creation_tx_hash } truncation="constant" noIcon noCopy={ false }/>
</DetailsInfoItem>
</DetailsInfoItem.Value>
</>
) }
{ data.is_contract && data.implementation_address && (
<DetailsInfoItem
title="Implementation"
<>
<DetailsInfoItem.Label
hint="Implementation address of the proxy contract"
columnGap={ 1 }
>
Implementation
</DetailsInfoItem.Label>
<DetailsInfoItem.Value>
<AddressEntity
address={{ hash: data.implementation_address, name: data.implementation_name, is_contract: true }}
isLoading={ addressQuery.isPlaceholderData }
noIcon
/>
</DetailsInfoItem>
</DetailsInfoItem.Value>
</>
) }
<AddressBalance data={ data } isLoading={ addressQuery.isPlaceholderData }/>
{ data.has_tokens && (
<DetailsInfoItem
title="Tokens"
<>
<DetailsInfoItem.Label
hint="All tokens in the account and total value"
alignSelf="center"
py={ 0 }
>
{ addressQuery.data ? <TokenSelect onClick={ handleCounterItemClick }/> : <Box py="6px">0</Box> }
</DetailsInfoItem>
Tokens
</DetailsInfoItem.Label>
<DetailsInfoItem.Value py={ addressQuery.data ? 0 : undefined }>
{ addressQuery.data ? <TokenSelect onClick={ handleCounterItemClick }/> : <Box>0</Box> }
</DetailsInfoItem.Value>
</>
) }
{ (config.features.multichainButton.isEnabled || (data.exchange_rate && data.has_tokens)) && (
<DetailsInfoItem
title="Net worth"
<>
<DetailsInfoItem.Label
hint="Total net worth in USD of all tokens for the address"
alignSelf="center"
isLoading={ addressQuery.isPlaceholderData }
>
Net worth
</DetailsInfoItem.Label>
<DetailsInfoItem.Value alignSelf="center">
<AddressNetWorth addressData={ addressQuery.data } addressHash={ addressHash } isLoading={ addressQuery.isPlaceholderData }/>
</DetailsInfoItem>
</DetailsInfoItem.Value>
</>
)
}
<DetailsInfoItem
title="Transactions"
<DetailsInfoItem.Label
hint="Number of transactions related to this address"
isLoading={ addressQuery.isPlaceholderData || countersQuery.isPlaceholderData }
>
Transactions
</DetailsInfoItem.Label>
<DetailsInfoItem.Value>
{ addressQuery.data ? (
<AddressCounterItem
prop="transactions_count"
......@@ -158,13 +177,17 @@ const AddressDetails = ({ addressQuery, scrollRef }: Props) => {
/>
) :
0 }
</DetailsInfoItem>
</DetailsInfoItem.Value>
{ data.has_token_transfers && (
<DetailsInfoItem
title="Transfers"
<>
<DetailsInfoItem.Label
hint="Number of transfers to/from this address"
isLoading={ addressQuery.isPlaceholderData || countersQuery.isPlaceholderData }
>
Transfers
</DetailsInfoItem.Label>
<DetailsInfoItem.Value>
{ addressQuery.data ? (
<AddressCounterItem
prop="token_transfers_count"
......@@ -176,14 +199,19 @@ const AddressDetails = ({ addressQuery, scrollRef }: Props) => {
/>
) :
0 }
</DetailsInfoItem>
</DetailsInfoItem.Value>
</>
) }
{ countersQuery.data?.gas_usage_count && (
<DetailsInfoItem
title="Gas used"
<>
<DetailsInfoItem.Label
hint="Gas used by the address"
isLoading={ addressQuery.isPlaceholderData || countersQuery.isPlaceholderData }
>
Gas used
</DetailsInfoItem.Label>
<DetailsInfoItem.Value>
{ addressQuery.data ? (
<AddressCounterItem
prop="gas_usage_count"
......@@ -195,14 +223,19 @@ const AddressDetails = ({ addressQuery, scrollRef }: Props) => {
/>
) :
0 }
</DetailsInfoItem>
</DetailsInfoItem.Value>
</>
) }
{ data.has_validated_blocks && (
<DetailsInfoItem
title="Blocks validated"
<>
<DetailsInfoItem.Label
hint="Number of blocks validated by this validator"
isLoading={ addressQuery.isPlaceholderData || countersQuery.isPlaceholderData }
>
Blocks validated
</DetailsInfoItem.Label>
<DetailsInfoItem.Value>
{ addressQuery.data ? (
<AddressCounterItem
prop="validations_count"
......@@ -214,22 +247,27 @@ const AddressDetails = ({ addressQuery, scrollRef }: Props) => {
/>
) :
0 }
</DetailsInfoItem>
</DetailsInfoItem.Value>
</>
) }
{ data.block_number_balance_updated_at && (
<DetailsInfoItem
title="Last balance update"
<>
<DetailsInfoItem.Label
hint="Block number in which the address was updated"
alignSelf="center"
py={{ base: '2px', lg: 1 }}
isLoading={ addressQuery.isPlaceholderData }
>
Last balance update
</DetailsInfoItem.Label>
<DetailsInfoItem.Value>
<BlockEntity
number={ data.block_number_balance_updated_at }
isLoading={ addressQuery.isPlaceholderData }
/>
</DetailsInfoItem>
</DetailsInfoItem.Value>
</>
) }
<DetailsSponsoredItem isLoading={ addressQuery.isPlaceholderData }/>
</Grid>
</>
......
......@@ -10,7 +10,7 @@ import useSocketChannel from 'lib/socket/useSocketChannel';
import useSocketMessage from 'lib/socket/useSocketMessage';
import { currencyUnits } from 'lib/units';
import CurrencyValue from 'ui/shared/CurrencyValue';
import DetailsInfoItem from 'ui/shared/DetailsInfoItem';
import * as DetailsInfoItem from 'ui/shared/DetailsInfoItem';
import NativeTokenIcon from 'ui/shared/NativeTokenIcon';
interface Props {
......@@ -66,13 +66,14 @@ const AddressBalance = ({ data, isLoading }: Props) => {
});
return (
<DetailsInfoItem
title={ `${ currencyUnits.ether } balance` }
hint={ `Address balance in ${ currencyUnits.ether }. Doesn't include ERC20, ERC721 and ERC1155 tokens` }
flexWrap="nowrap"
alignSelf="center"
<>
<DetailsInfoItem.Label
hint={ `${ currencyUnits.ether } balance` }
isLoading={ isLoading }
>
Balance
</DetailsInfoItem.Label>
<DetailsInfoItem.Value alignSelf="center" flexWrap="nowrap">
<NativeTokenIcon boxSize={ 6 } mr={ 2 } isLoading={ isLoading }/>
<CurrencyValue
value={ data.coin_balance || '0' }
......@@ -84,7 +85,8 @@ const AddressBalance = ({ data, isLoading }: Props) => {
flexWrap="wrap"
isLoading={ isLoading }
/>
</DetailsInfoItem>
</DetailsInfoItem.Value>
</>
);
};
......
......@@ -3,7 +3,7 @@ import React from 'react';
import type { Address } from 'types/api/address';
import DetailsInfoItem from 'ui/shared/DetailsInfoItem';
import * as DetailsInfoItem from 'ui/shared/DetailsInfoItem';
import TokenEntity from 'ui/shared/entities/token/TokenEntity';
interface Props {
......@@ -14,46 +14,58 @@ interface Props {
const AddressNameInfo = ({ data, isLoading }: Props) => {
if (data.token) {
return (
<DetailsInfoItem
title="Token name"
<>
<DetailsInfoItem.Label
hint="Token name and symbol"
isLoading={ isLoading }
>
Token name
</DetailsInfoItem.Label>
<DetailsInfoItem.Value>
<TokenEntity
token={ data.token }
isLoading={ isLoading }
noIcon
noCopy
/>
</DetailsInfoItem>
</DetailsInfoItem.Value>
</>
);
}
if (data.is_contract && data.name) {
return (
<DetailsInfoItem
title="Contract name"
<>
<DetailsInfoItem.Label
hint="The name found in the source code of the Contract"
isLoading={ isLoading }
>
Contract name
</DetailsInfoItem.Label>
<DetailsInfoItem.Value>
<Skeleton isLoaded={ !isLoading }>
{ data.name }
</Skeleton>
</DetailsInfoItem>
</DetailsInfoItem.Value>
</>
);
}
if (data.name) {
return (
<DetailsInfoItem
title="Validator name"
<>
<DetailsInfoItem.Label
hint="The name of the validator"
isLoading={ isLoading }
>
Validator name
</DetailsInfoItem.Label>
<DetailsInfoItem.Value>
<Skeleton isLoaded={ !isLoading }>
{ data.name }
</Skeleton>
</DetailsInfoItem>
</DetailsInfoItem.Value>
</>
);
}
......
......@@ -55,7 +55,7 @@ const TokenSelect = ({ onClick }: Props) => {
}
return (
<Flex columnGap={ 3 } mt={{ base: '6px', lg: 0 }}>
<Flex columnGap={ 3 } mt={{ base: 1, lg: 0 }}>
{ isMobile ?
<TokenSelectMobile data={ data } isLoading={ tokensIsFetching === 1 }/> :
<TokenSelectDesktop data={ data } isLoading={ tokensIsFetching === 1 }/>
......
......@@ -4,7 +4,7 @@ import React from 'react';
import type { Blob } from 'types/api/blobs';
import CopyToClipboard from 'ui/shared/CopyToClipboard';
import DetailsInfoItem from 'ui/shared/DetailsInfoItem';
import * as DetailsInfoItem from 'ui/shared/DetailsInfoItem';
import DetailsInfoItemDivider from 'ui/shared/DetailsInfoItemDivider';
import DetailsSponsoredItem from 'ui/shared/DetailsSponsoredItem';
import TxEntity from 'ui/shared/entities/tx/TxEntity';
......@@ -30,53 +30,73 @@ const BlobInfo = ({ data, isLoading }: Props) => {
</Skeleton>
</GridItem>
) }
{ data.kzg_proof && (
<DetailsInfoItem
title="Proof"
<>
<DetailsInfoItem.Label
hint="Zero knowledge proof. Allows for quick verification of commitment"
isLoading={ isLoading }
>
Proof
</DetailsInfoItem.Label>
<DetailsInfoItem.Value>
<Skeleton isLoaded={ !isLoading } overflow="hidden" whiteSpace="pre-wrap" wordBreak="break-all" lineHeight={ 6 } my="-2px">
{ data.kzg_proof }
<CopyToClipboard text={ data.kzg_proof } isLoading={ isLoading }/>
</Skeleton>
</DetailsInfoItem>
</DetailsInfoItem.Value>
</>
) }
{ data.kzg_commitment && (
<DetailsInfoItem
title="Commitment"
<>
<DetailsInfoItem.Label
hint="Commitment to the data in the blob"
isLoading={ isLoading }
>
Commitment
</DetailsInfoItem.Label>
<DetailsInfoItem.Value>
<Skeleton isLoaded={ !isLoading } overflow="hidden" whiteSpace="pre-wrap" wordBreak="break-all" lineHeight={ 6 } my="-2px">
{ data.kzg_commitment }
<CopyToClipboard text={ data.kzg_commitment } isLoading={ isLoading }/>
</Skeleton>
</DetailsInfoItem>
</DetailsInfoItem.Value>
</>
) }
{ data.blob_data && (
<DetailsInfoItem
title="Size, bytes"
<>
<DetailsInfoItem.Label
hint="Blob size in bytes"
isLoading={ isLoading }
>
Size, bytes
</DetailsInfoItem.Label>
<DetailsInfoItem.Value>
<Skeleton isLoaded={ !isLoading } overflow="hidden" whiteSpace="pre-wrap" wordBreak="break-all">
{ (data.blob_data.replace('0x', '').length / 2).toLocaleString() }
</Skeleton>
</DetailsInfoItem>
</DetailsInfoItem.Value>
</>
) }
{ data.blob_data && <DetailsInfoItemDivider/> }
{ data.transaction_hashes[0] && (
<DetailsInfoItem
title="Transaction hash"
<>
<DetailsInfoItem.Label
hint="Hash of the transaction with this blob"
isLoading={ isLoading }
>
Transaction hash
</DetailsInfoItem.Label>
<DetailsInfoItem.Value>
<TxEntity hash={ data.transaction_hashes[0].transaction_hash } isLoading={ isLoading } noIcon noCopy={ false }/>
</DetailsInfoItem>
</DetailsInfoItem.Value>
</>
) }
<DetailsSponsoredItem isLoading={ isLoading }/>
{ data.blob_data && (
......
......@@ -17,7 +17,7 @@ import getNetworkValidatorTitle from 'lib/networks/getNetworkValidatorTitle';
import getQueryParamString from 'lib/router/getQueryParamString';
import { currencyUnits } from 'lib/units';
import CopyToClipboard from 'ui/shared/CopyToClipboard';
import DetailsInfoItem from 'ui/shared/DetailsInfoItem';
import * as DetailsInfoItem from 'ui/shared/DetailsInfoItem';
import DetailsInfoItemDivider from 'ui/shared/DetailsInfoItemDivider';
import DetailsTimestamp from 'ui/shared/DetailsTimestamp';
import AddressEntity from 'ui/shared/entities/address/AddressEntity';
......@@ -163,11 +163,13 @@ const BlockDetails = ({ query }: Props) => {
templateColumns={{ base: 'minmax(0, 1fr)', lg: 'minmax(min-content, 200px) minmax(0, 1fr)' }}
overflow="hidden"
>
<DetailsInfoItem
title={ `${ blockTypeLabel } height` }
<DetailsInfoItem.Label
hint="The block height of a particular block is defined as the number of blocks preceding it in the blockchain"
isLoading={ isPlaceholderData }
>
{ blockTypeLabel } height
</DetailsInfoItem.Label>
<DetailsInfoItem.Value>
<Skeleton isLoaded={ !isPlaceholderData }>
{ data.height }
</Skeleton>
......@@ -180,122 +182,151 @@ const BlockDetails = ({ query }: Props) => {
isPrevDisabled={ data.height === 0 }
isLoading={ isPlaceholderData }
/>
</DetailsInfoItem>
<DetailsInfoItem
title="Size"
</DetailsInfoItem.Value>
<DetailsInfoItem.Label
hint="Size of the block in bytes"
isLoading={ isPlaceholderData }
>
Size
</DetailsInfoItem.Label>
<DetailsInfoItem.Value>
<Skeleton isLoaded={ !isPlaceholderData }>
{ data.size.toLocaleString() }
</Skeleton>
</DetailsInfoItem>
<DetailsInfoItem
title="Timestamp"
</DetailsInfoItem.Value>
<DetailsInfoItem.Label
hint="Date & time at which block was produced."
isLoading={ isPlaceholderData }
>
Timestamp
</DetailsInfoItem.Label>
<DetailsInfoItem.Value>
<DetailsTimestamp timestamp={ data.timestamp } isLoading={ isPlaceholderData }/>
</DetailsInfoItem>
<DetailsInfoItem
title="Transactions"
</DetailsInfoItem.Value>
<DetailsInfoItem.Label
hint="The number of transactions in the block"
isLoading={ isPlaceholderData }
>
Transactions
</DetailsInfoItem.Label>
<DetailsInfoItem.Value>
<Skeleton isLoaded={ !isPlaceholderData }>
{ txsNum }
</Skeleton>
</DetailsInfoItem>
</DetailsInfoItem.Value>
{ config.features.beaconChain.isEnabled && Boolean(data.withdrawals_count) && (
<DetailsInfoItem
title="Withdrawals"
<>
<DetailsInfoItem.Label
hint="The number of beacon withdrawals in the block"
isLoading={ isPlaceholderData }
>
Withdrawals
</DetailsInfoItem.Label>
<DetailsInfoItem.Value>
<Skeleton isLoaded={ !isPlaceholderData }>
<LinkInternal href={ route({ pathname: '/block/[height_or_hash]', query: { height_or_hash: heightOrHash, tab: 'withdrawals' } }) }>
{ data.withdrawals_count } withdrawal{ data.withdrawals_count === 1 ? '' : 's' }
</LinkInternal>
</Skeleton>
</DetailsInfoItem>
</DetailsInfoItem.Value>
</>
) }
{ rollupFeature.isEnabled && rollupFeature.type === 'zkSync' && data.zksync && !config.UI.views.block.hiddenFields?.batch && (
<DetailsInfoItem
title="Batch"
<>
<DetailsInfoItem.Label
hint="Batch number"
isLoading={ isPlaceholderData }
>
{ data.zksync.batch_number ? (
<BatchEntityL2
isLoading={ isPlaceholderData }
number={ data.zksync.batch_number }
/>
) : <Skeleton isLoaded={ !isPlaceholderData }>Pending</Skeleton> }
</DetailsInfoItem>
Batch
</DetailsInfoItem.Label>
<DetailsInfoItem.Value>
{ data.zksync.batch_number ?
<BatchEntityL2 isLoading={ isPlaceholderData } number={ data.zksync.batch_number }/> :
<Skeleton isLoaded={ !isPlaceholderData }>Pending</Skeleton> }
</DetailsInfoItem.Value>
</>
) }
{ rollupFeature.isEnabled && rollupFeature.type === 'zkSync' && data.zksync && !config.UI.views.block.hiddenFields?.L1_status && (
<DetailsInfoItem
title="Status"
<>
<DetailsInfoItem.Label
hint="Status is the short interpretation of the batch lifecycle"
isLoading={ isPlaceholderData }
>
Status
</DetailsInfoItem.Label>
<DetailsInfoItem.Value>
<VerificationSteps steps={ ZKSYNC_L2_TX_BATCH_STATUSES } currentStep={ data.zksync.status } isLoading={ isPlaceholderData }/>
</DetailsInfoItem>
</DetailsInfoItem.Value>
</>
) }
{ !config.UI.views.block.hiddenFields?.miner && (
<DetailsInfoItem
title={ verificationTitle }
<>
<DetailsInfoItem.Label
hint="A block producer who successfully included the block onto the blockchain"
columnGap={ 1 }
isLoading={ isPlaceholderData }
>
{ verificationTitle }
</DetailsInfoItem.Label>
<DetailsInfoItem.Value>
<AddressEntity
address={ data.miner }
isLoading={ isPlaceholderData }
/>
{ /* api doesn't return the block processing time yet */ }
{ /* <Text>{ dayjs.duration(block.minedIn, 'second').humanize(true) }</Text> */ }
</DetailsInfoItem>
</DetailsInfoItem.Value>
</>
) }
{ !rollupFeature.isEnabled && !totalReward.isEqualTo(ZERO) && !config.UI.views.block.hiddenFields?.total_reward && (
<DetailsInfoItem
title="Block reward"
<>
<DetailsInfoItem.Label
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`
}
columnGap={ 1 }
isLoading={ isPlaceholderData }
>
Block reward
</DetailsInfoItem.Label>
<DetailsInfoItem.Value columnGap={ 1 }>
<Skeleton isLoaded={ !isPlaceholderData }>
{ totalReward.dividedBy(WEI).toFixed() } { currencyUnits.ether }
</Skeleton>
{ rewardBreakDown }
</DetailsInfoItem>
</DetailsInfoItem.Value>
</>
) }
{ data.rewards
?.filter(({ type }) => type !== 'Validator Reward' && type !== 'Miner Reward')
.map(({ type, reward }) => (
<DetailsInfoItem
key={ type }
title={ type }
// is this text correct for validators?
<React.Fragment key={ type }>
<DetailsInfoItem.Label
hint={ `Amount of distributed reward. ${ capitalize(validatorTitle) }s receive a static block reward + Tx fees + uncle fees` }
>
{ type }
</DetailsInfoItem.Label>
<DetailsInfoItem.Value>
{ BigNumber(reward).dividedBy(WEI).toFixed() } { currencyUnits.ether }
</DetailsInfoItem>
</DetailsInfoItem.Value>
</React.Fragment>
))
}
<DetailsInfoItemDivider/>
<DetailsInfoItem
title="Gas used"
<DetailsInfoItem.Label
hint="The total gas amount used in the block and its percentage of gas filled in the block"
isLoading={ isPlaceholderData }
>
Gas used
</DetailsInfoItem.Label>
<DetailsInfoItem.Value>
<Skeleton isLoaded={ !isPlaceholderData }>
{ BigNumber(data.gas_used || 0).toFormat() }
</Skeleton>
......@@ -311,33 +342,45 @@ const BlockDetails = ({ query }: Props) => {
<GasUsedToTargetRatio value={ data.gas_target_percentage } isLoading={ isPlaceholderData }/>
</>
) }
</DetailsInfoItem>
<DetailsInfoItem
title="Gas limit"
</DetailsInfoItem.Value>
<DetailsInfoItem.Label
hint="Total gas limit provided by all transactions in the block"
isLoading={ isPlaceholderData }
>
Gas limit
</DetailsInfoItem.Label>
<DetailsInfoItem.Value>
<Skeleton isLoaded={ !isPlaceholderData }>
{ BigNumber(data.gas_limit).toFormat() }
</Skeleton>
</DetailsInfoItem>
</DetailsInfoItem.Value>
{ data.minimum_gas_price && (
<DetailsInfoItem
title="Minimum gas price"
<>
<DetailsInfoItem.Label
hint="The minimum gas price a transaction should have in order to be included in this block"
isLoading={ isPlaceholderData }
>
Minimum gas price
</DetailsInfoItem.Label>
<DetailsInfoItem.Value>
<Skeleton isLoaded={ !isPlaceholderData }>
{ BigNumber(data.minimum_gas_price).dividedBy(GWEI).toFormat() } { currencyUnits.gwei }
</Skeleton>
</DetailsInfoItem>
</DetailsInfoItem.Value>
</>
) }
{ data.base_fee_per_gas && (
<DetailsInfoItem
title="Base fee per gas"
<>
<DetailsInfoItem.Label
hint="Minimum fee required per unit of gas. Fee adjusts based on network congestion"
isLoading={ isPlaceholderData }
>
Base fee per gas
</DetailsInfoItem.Label>
<DetailsInfoItem.Value>
{ isPlaceholderData ? (
<Skeleton isLoaded={ !isPlaceholderData } h="20px" maxW="380px" w="100%"/>
) : (
......@@ -348,18 +391,22 @@ const BlockDetails = ({ query }: Props) => {
</Text>
</>
) }
</DetailsInfoItem>
</DetailsInfoItem.Value>
</>
) }
{ !config.UI.views.block.hiddenFields?.burnt_fees && !burntFees.isEqualTo(ZERO) && (
<DetailsInfoItem
title="Burnt fees"
<>
<DetailsInfoItem.Label
hint={
`Amount of ${ config.chain.currency.symbol || 'native token' } burned from transactions included in the block.
Equals Block Base Fee per Gas * Gas Used`
}
isLoading={ isPlaceholderData }
>
Burnt fees
</DetailsInfoItem.Label>
<DetailsInfoItem.Value>
<IconSvg name="flame" boxSize={ 5 } color="gray.500" isLoading={ isPlaceholderData }/>
<Skeleton isLoaded={ !isPlaceholderData } ml={ 2 }>
{ burntFees.dividedBy(WEI).toFixed() } { currencyUnits.ether }
......@@ -375,27 +422,25 @@ const BlockDetails = ({ query }: Props) => {
</Box>
</Tooltip>
) }
</DetailsInfoItem>
</DetailsInfoItem.Value>
</>
) }
{ data.priority_fee !== null && BigNumber(data.priority_fee).gt(ZERO) && (
<DetailsInfoItem
title="Priority fee / Tip"
<>
<DetailsInfoItem.Label
hint="User-defined tips sent to validator for transaction priority/inclusion"
isLoading={ isPlaceholderData }
>
Priority fee / Tip
</DetailsInfoItem.Label>
<DetailsInfoItem.Value>
<Skeleton isLoaded={ !isPlaceholderData }>
{ BigNumber(data.priority_fee).dividedBy(WEI).toFixed() } { currencyUnits.ether }
</Skeleton>
</DetailsInfoItem>
</DetailsInfoItem.Value>
</>
) }
{ /* api doesn't support extra data yet */ }
{ /* <DetailsInfoItem
title="Extra data"
hint={ `Any data that can be included by the ${ validatorTitle } in the block` }
>
<Text whiteSpace="pre">{ data.extra_data } </Text>
<Text variant="secondary">(Hex: { data.extra_data })</Text>
</DetailsInfoItem> */ }
{ /* CUT */ }
<GridItem colSpan={{ base: undefined, lg: 2 }}>
......@@ -424,9 +469,13 @@ const BlockDetails = ({ query }: Props) => {
{ !isPlaceholderData && <BlockDetailsBlobInfo data={ data }/> }
{ data.bitcoin_merged_mining_header && (
<DetailsInfoItem
title="Bitcoin merged mining header"
<>
<DetailsInfoItem.Label
hint="Merged-mining field: Bitcoin header"
>
Bitcoin merged mining header
</DetailsInfoItem.Label>
<DetailsInfoItem.Value
flexWrap="nowrap"
alignSelf="flex-start"
>
......@@ -434,38 +483,54 @@ const BlockDetails = ({ query }: Props) => {
<HashStringShortenDynamic hash={ data.bitcoin_merged_mining_header }/>
</Box>
<CopyToClipboard text={ data.bitcoin_merged_mining_header }/>
</DetailsInfoItem>
</DetailsInfoItem.Value>
</>
) }
{ data.bitcoin_merged_mining_coinbase_transaction && (
<DetailsInfoItem
title="Bitcoin merged mining coinbase transaction"
<>
<DetailsInfoItem.Label
hint="Merged-mining field: Coinbase transaction"
>
Bitcoin merged mining coinbase transaction
</DetailsInfoItem.Label>
<DetailsInfoItem.Value>
<RawDataSnippet
data={ data.bitcoin_merged_mining_coinbase_transaction }
isLoading={ isPlaceholderData }
showCopy={ false }
textareaMaxHeight="100px"
/>
</DetailsInfoItem>
</DetailsInfoItem.Value>
</>
) }
{ data.bitcoin_merged_mining_merkle_proof && (
<DetailsInfoItem
title="Bitcoin merged mining Merkle proof"
<>
<DetailsInfoItem.Label
hint="Merged-mining field: Merkle proof"
>
Bitcoin merged mining Merkle proof
</DetailsInfoItem.Label>
<DetailsInfoItem.Value>
<RawDataSnippet
data={ data.bitcoin_merged_mining_merkle_proof }
isLoading={ isPlaceholderData }
showCopy={ false }
textareaMaxHeight="100px"
/>
</DetailsInfoItem>
</DetailsInfoItem.Value>
</>
) }
{ data.hash_for_merged_mining && (
<DetailsInfoItem
title="Hash for merged mining"
<>
<DetailsInfoItem.Label
hint="Merged-mining field: Rootstock block header hash"
>
Hash for merged mining
</DetailsInfoItem.Label>
<DetailsInfoItem.Value
flexWrap="nowrap"
alignSelf="flex-start"
>
......@@ -473,46 +538,54 @@ const BlockDetails = ({ query }: Props) => {
<HashStringShortenDynamic hash={ data.hash_for_merged_mining }/>
</Box>
<CopyToClipboard text={ data.hash_for_merged_mining }/>
</DetailsInfoItem>
</DetailsInfoItem.Value>
</>
) }
<DetailsInfoItem
title="Difficulty"
<DetailsInfoItem.Label
hint={ `Block difficulty for ${ validatorTitle }, used to calibrate block generation time` }
>
<Box whiteSpace="nowrap" overflow="hidden">
Difficulty
</DetailsInfoItem.Label>
<DetailsInfoItem.Value overflow="hidden">
<HashStringShortenDynamic hash={ BigNumber(data.difficulty).toFormat() }/>
</Box>
</DetailsInfoItem>
</DetailsInfoItem.Value>
{ data.total_difficulty && (
<DetailsInfoItem
title="Total difficulty"
<>
<DetailsInfoItem.Label
hint="Total difficulty of the chain until this block"
>
<Box whiteSpace="nowrap" overflow="hidden">
Total difficulty
</DetailsInfoItem.Label>
<DetailsInfoItem.Value overflow="hidden">
<HashStringShortenDynamic hash={ BigNumber(data.total_difficulty).toFormat() }/>
</Box>
</DetailsInfoItem>
</DetailsInfoItem.Value>
</>
) }
<DetailsInfoItemDivider/>
<DetailsInfoItem
title="Hash"
<DetailsInfoItem.Label
hint="The SHA256 hash of the block"
flexWrap="nowrap"
>
<Box overflow="hidden">
Hash
</DetailsInfoItem.Label>
<DetailsInfoItem.Value flexWrap="nowrap">
<Box overflow="hidden" >
<HashStringShortenDynamic hash={ data.hash }/>
</Box>
<CopyToClipboard text={ data.hash }/>
</DetailsInfoItem>
</DetailsInfoItem.Value>
{ data.height > 0 && (
<DetailsInfoItem
title="Parent hash"
<>
<DetailsInfoItem.Label
hint="The hash of the block from which this block was generated"
flexWrap="nowrap"
>
Parent hash
</DetailsInfoItem.Label>
<DetailsInfoItem.Value flexWrap="nowrap">
<LinkInternal
href={ route({ pathname: '/block/[height_or_hash]', query: { height_or_hash: String(data.height - 1) } }) }
overflow="hidden"
......@@ -523,22 +596,21 @@ const BlockDetails = ({ query }: Props) => {
/>
</LinkInternal>
<CopyToClipboard text={ data.parent_hash }/>
</DetailsInfoItem>
</DetailsInfoItem.Value>
</>
) }
{ /* api doesn't support state root yet */ }
{ /* <DetailsInfoItem
title="State root"
hint="The root of the state trie"
>
<Text wordBreak="break-all" whiteSpace="break-spaces">{ data.state_root }</Text>
</DetailsInfoItem> */ }
{ !config.UI.views.block.hiddenFields?.nonce && (
<DetailsInfoItem
title="Nonce"
<>
<DetailsInfoItem.Label
hint="Block nonce is a value used during mining to demonstrate proof of work for a block"
>
Nonce
</DetailsInfoItem.Label>
<DetailsInfoItem.Value>
{ data.nonce }
</DetailsInfoItem>
</DetailsInfoItem.Value>
</>
) }
</>
) }
......
......@@ -7,7 +7,7 @@ import type { Block } from 'types/api/block';
import { WEI, WEI_IN_GWEI, ZERO } from 'lib/consts';
import { space } from 'lib/html-entities';
import { currencyUnits } from 'lib/units';
import DetailsInfoItem from 'ui/shared/DetailsInfoItem';
import * as DetailsInfoItem from 'ui/shared/DetailsInfoItem';
import DetailsInfoItemDivider from 'ui/shared/DetailsInfoItemDivider';
import IconSvg from 'ui/shared/IconSvg';
import Utilization from 'ui/shared/Utilization/Utilization';
......@@ -33,30 +33,41 @@ const BlockDetailsBlobInfo = ({ data }: Props) => {
<>
{ data.blob_gas_price && (
<DetailsInfoItem
title="Blob gas price"
<>
<DetailsInfoItem.Label
// eslint-disable-next-line max-len
hint="Price per unit of gas used for for blob deployment. Blob gas is independent of normal gas. Both gas prices can affect the priority of transaction execution."
>
Blob gas price
</DetailsInfoItem.Label>
<DetailsInfoItem.Value>
<Text>{ BigNumber(data.blob_gas_price).dividedBy(WEI).toFixed() } { currencyUnits.ether } </Text>
<Text variant="secondary" whiteSpace="pre">
{ space }({ BigNumber(data.blob_gas_price).dividedBy(WEI_IN_GWEI).toFixed() } { currencyUnits.gwei })
</Text>
</DetailsInfoItem>
</DetailsInfoItem.Value>
</>
) }
{ data.blob_gas_used && (
<DetailsInfoItem
title="Blob gas used"
<>
<DetailsInfoItem.Label
hint="Actual amount of gas used by the blobs in this block"
>
Blob gas used
</DetailsInfoItem.Label>
<DetailsInfoItem.Value>
<Text>{ BigNumber(data.blob_gas_used).toFormat() }</Text>
</DetailsInfoItem>
</DetailsInfoItem.Value>
</>
) }
{ !burntBlobFees.isEqualTo(ZERO) && (
<DetailsInfoItem
title="Blob burnt fees"
<>
<DetailsInfoItem.Label
hint={ `Amount of ${ currencyUnits.ether } used for blobs in this block` }
>
Blob burnt fees
</DetailsInfoItem.Label>
<DetailsInfoItem.Value>
<IconSvg name="flame" boxSize={ 5 } color="gray.500" mr={ 2 }/>
{ burntBlobFees.dividedBy(WEI).toFixed() } { currencyUnits.ether }
{ !blobFees.isEqualTo(ZERO) && (
......@@ -66,18 +77,23 @@ const BlockDetailsBlobInfo = ({ data }: Props) => {
</div>
</Tooltip>
) }
</DetailsInfoItem>
</DetailsInfoItem.Value>
</>
) }
{ data.excess_blob_gas && (
<DetailsInfoItem
title="Excess blob gas"
<>
<DetailsInfoItem.Label
hint="A running total of blob gas consumed in excess of the target, prior to the block."
>
Excess blob gas
</DetailsInfoItem.Label>
<DetailsInfoItem.Value>
<Text>{ BigNumber(data.excess_blob_gas).dividedBy(WEI).toFixed() } { currencyUnits.ether } </Text>
<Text variant="secondary" whiteSpace="pre">
{ space }({ BigNumber(data.excess_blob_gas).dividedBy(WEI_IN_GWEI).toFixed() } { currencyUnits.gwei })
</Text>
</DetailsInfoItem>
</DetailsInfoItem.Value>
</>
) }
<DetailsInfoItemDivider/>
</>
......
......@@ -8,7 +8,7 @@ import { route } from 'nextjs-routes';
import type { ResourceError } from 'lib/api/resources';
import dayjs from 'lib/date/dayjs';
import DetailsInfoItem from 'ui/shared/DetailsInfoItem';
import * as DetailsInfoItem from 'ui/shared/DetailsInfoItem';
import AddressEntity from 'ui/shared/entities/address/AddressEntity';
import NftEntity from 'ui/shared/entities/nft/NftEntity';
import IconSvg from 'ui/shared/IconSvg';
......@@ -30,25 +30,32 @@ const NameDomainDetails = ({ query }: Props) => {
return (
<Grid columnGap={ 8 } rowGap={ 3 } templateColumns={{ base: 'minmax(0, 1fr)', lg: 'max-content minmax(728px, auto)' }}>
{ query.data?.registration_date && (
<DetailsInfoItem
title="Registration date"
<>
<DetailsInfoItem.Label
hint="The date the name was registered"
isLoading={ isLoading }
>
Registration date
</DetailsInfoItem.Label>
<DetailsInfoItem.Value>
<IconSvg name="clock" boxSize={ 5 } color="gray.500" verticalAlign="middle" isLoading={ isLoading } mr={ 2 }/>
<Skeleton isLoaded={ !isLoading } display="inline" whiteSpace="pre-wrap" lineHeight="20px">
{ dayjs(query.data.registration_date).format('llll') }
</Skeleton>
</DetailsInfoItem>
</DetailsInfoItem.Value>
</>
) }
{ query.data?.expiry_date && (
<DetailsInfoItem
title="Expiration date"
<>
<DetailsInfoItem.Label
// eslint-disable-next-line max-len
hint="The date the name expires, upon which there is a 90 day grace period for the owner to renew. After the 90 days, the name is released to the market"
isLoading={ isLoading }
display="inline-block"
>
Expiration date
</DetailsInfoItem.Label>
<DetailsInfoItem.Value>
<IconSvg name="clock" boxSize={ 5 } color="gray.500" verticalAlign="middle" isLoading={ isLoading } mr={ 2 } mt="-2px"/>
{ hasExpired && (
<>
......@@ -65,13 +72,19 @@ const NameDomainDetails = ({ query }: Props) => {
<Skeleton isLoaded={ !isLoading } color="text_secondary" display="inline">
<NameDomainExpiryStatus date={ query.data?.expiry_date }/>
</Skeleton>
</DetailsInfoItem>
</DetailsInfoItem.Value>
</>
) }
{ query.data?.registrant && (
<DetailsInfoItem
title="Registrant"
<>
<DetailsInfoItem.Label
hint="The account that owns the domain name and has the rights to edit its ownership and records"
isLoading={ isLoading }
>
Registrant
</DetailsInfoItem.Label>
<DetailsInfoItem.Value
columnGap={ 2 }
flexWrap="nowrap"
>
......@@ -88,13 +101,19 @@ const NameDomainDetails = ({ query }: Props) => {
<IconSvg name="search" boxSize={ 5 } isLoading={ isLoading }/>
</LinkInternal>
</Tooltip>
</DetailsInfoItem>
</DetailsInfoItem.Value>
</>
) }
{ query.data?.owner && (
<DetailsInfoItem
title="Owner"
<>
<DetailsInfoItem.Label
hint="The account that owns the rights to edit the records of this domain name"
isLoading={ isLoading }
>
Owner
</DetailsInfoItem.Label>
<DetailsInfoItem.Value
columnGap={ 2 }
flexWrap="nowrap"
>
......@@ -111,13 +130,19 @@ const NameDomainDetails = ({ query }: Props) => {
<IconSvg name="search" boxSize={ 5 } isLoading={ isLoading }/>
</LinkInternal>
</Tooltip>
</DetailsInfoItem>
</DetailsInfoItem.Value>
</>
) }
{ query.data?.wrapped_owner && (
<DetailsInfoItem
title="Manager"
<>
<DetailsInfoItem.Label
hint="Owner of this NFT domain in NameWrapper contract"
isLoading={ isLoading }
>
Manager
</DetailsInfoItem.Label>
<DetailsInfoItem.Value
columnGap={ 2 }
flexWrap="nowrap"
>
......@@ -134,25 +159,36 @@ const NameDomainDetails = ({ query }: Props) => {
<IconSvg name="search" boxSize={ 5 } isLoading={ isLoading }/>
</LinkInternal>
</Tooltip>
</DetailsInfoItem>
</DetailsInfoItem.Value>
</>
) }
{ query.data?.tokens.map((token) => (
<DetailsInfoItem
key={ token.type }
title={ token.type === 'WRAPPED_DOMAIN_TOKEN' ? 'Wrapped token ID' : 'Token ID' }
<React.Fragment key={ token.type }>
<DetailsInfoItem.Label
hint={ `The ${ token.type === 'WRAPPED_DOMAIN_TOKEN' ? 'wrapped ' : '' }token ID of this domain name NFT` }
isLoading={ isLoading }
>
{ token.type === 'WRAPPED_DOMAIN_TOKEN' ? 'Wrapped token ID' : 'Token ID' }
</DetailsInfoItem.Label>
<DetailsInfoItem.Value
wordBreak="break-all"
whiteSpace="pre-wrap"
>
<NftEntity hash={ token.contract_hash } id={ token.id } isLoading={ isLoading } noIcon/>
</DetailsInfoItem>
</DetailsInfoItem.Value>
</React.Fragment>
)) }
{ otherAddresses.length > 0 && (
<DetailsInfoItem
title="Other addresses"
<>
<DetailsInfoItem.Label
hint="Other cryptocurrency addresses added to this domain name"
isLoading={ isLoading }
>
Other addresses
</DetailsInfoItem.Label>
<DetailsInfoItem.Value
flexDir="column"
alignItems="flex-start"
>
......@@ -167,7 +203,8 @@ const NameDomainDetails = ({ query }: Props) => {
/>
</Flex>
)) }
</DetailsInfoItem>
</DetailsInfoItem.Value>
</>
) }
</Grid>
);
......
import { Text } from '@chakra-ui/react';
import React from 'react';
import ContainerWithScrollY from 'ui/shared/ContainerWithScrollY';
import DetailsInfoItem from 'ui/shared/DetailsInfoItem';
import * as DetailsInfoItem from 'ui/shared/DetailsInfoItem';
export const TX_ACTIONS_BLOCK_ID = 'tx-actions';
const SCROLL_GRADIENT_HEIGHT = 48;
......@@ -25,12 +26,16 @@ const DetailsActionsWrapper = ({ children, isLoading, type }: Props) => {
}, []);
return (
<DetailsInfoItem
title={ `${ type === 'tx' ? 'Transaction' : 'User operation' } action` }
<>
<DetailsInfoItem.Label
hint={ `Highlighted events of the ${ type === 'tx' ? 'transaction' : 'user operation' }` }
note={ hasScroll ? 'Scroll to see more' : undefined }
position="relative"
isLoading={ isLoading }
>
<span>{ `${ type === 'tx' ? 'Transaction' : 'User operation' } action` }</span>
{ hasScroll && <Text fontWeight={ 500 } variant="secondary" fontSize="xs" className="note" align="right">Scroll to see more</Text> }
</DetailsInfoItem.Label>
<DetailsInfoItem.Value
position="relative"
>
<ContainerWithScrollY
containerId={ TX_ACTIONS_BLOCK_ID }
......@@ -44,7 +49,9 @@ const DetailsActionsWrapper = ({ children, isLoading, type }: Props) => {
>
{ children }
</ContainerWithScrollY>
</DetailsInfoItem>
</DetailsInfoItem.Value>
</>
);
};
......
import { GridItem, Flex, Text, Skeleton } from '@chakra-ui/react';
import type { HTMLChakraProps } from '@chakra-ui/system';
import { chakra, GridItem, Flex, Skeleton } from '@chakra-ui/react';
import React from 'react';
import Hint from 'ui/shared/Hint';
interface Props extends Omit<HTMLChakraProps<'div'>, 'title'> {
title: React.ReactNode;
interface LabelProps {
hint?: string;
children: React.ReactNode;
note?: string;
isLoading?: boolean;
className?: string;
id?: string;
}
const DetailsInfoItem = ({ title, hint, note, children, id, isLoading, ...styles }: Props) => {
const Label = chakra(({ hint, children, isLoading, id, className }: LabelProps) => {
return (
<>
<GridItem py={{ base: 1, lg: 2 }} id={ id } lineHeight={ 5 } { ...styles } _notFirst={{ mt: { base: 3, lg: 0 } }}>
<GridItem
id={ id }
className={ className }
py={ 1 }
lineHeight={{ base: 5, lg: 6 }}
_notFirst={{ mt: { base: 3, lg: 0 } }}
>
<Flex columnGap={ 2 } alignItems="flex-start">
{ hint && <Hint label={ hint } isLoading={ isLoading }/> }
<Skeleton isLoaded={ !isLoading }>
<Text fontWeight={{ base: 700, lg: 500 }}>
{ title }
{ note && <Text fontWeight={ 500 } variant="secondary" fontSize="xs" className="note" align="right">{ note }</Text> }
</Text>
{ hint && <Hint label={ hint } isLoading={ isLoading } my={{ lg: '2px' }}/> }
<Skeleton isLoaded={ !isLoading } fontWeight={{ base: 700, lg: 500 }}>
{ children }
</Skeleton>
</Flex>
</GridItem>
);
});
interface ValueProps {
children: React.ReactNode;
className?: string;
}
const Value = chakra(({ children, className }: ValueProps) => {
return (
<GridItem
className={ className }
display="flex"
alignItems="center"
flexWrap="wrap"
rowGap={ 3 }
pl={{ base: 7, lg: 0 }}
py={{ base: 1, lg: 2 }}
lineHeight={ 5 }
py={ 1 }
lineHeight={{ base: 5, lg: 6 }}
whiteSpace="nowrap"
{ ...styles }
>
{ children }
</GridItem>
</>
);
};
});
export default DetailsInfoItem;
export {
Label,
Value,
};
......@@ -5,7 +5,7 @@ import config from 'configs/app';
import * as cookies from 'lib/cookies';
import useIsMobile from 'lib/hooks/useIsMobile';
import AdBanner from 'ui/shared/ad/AdBanner';
import DetailsInfoItem from 'ui/shared/DetailsInfoItem';
import * as DetailsInfoItem from 'ui/shared/DetailsInfoItem';
const feature = config.features.adsBanner;
......@@ -30,13 +30,17 @@ const DetailsSponsoredItem = ({ isLoading }: Props) => {
}
return (
<DetailsInfoItem
title="Sponsored"
<>
<DetailsInfoItem.Label
hint="Sponsored banner advertisement"
isLoading={ isLoading }
>
Sponsored
</DetailsInfoItem.Label>
<DetailsInfoItem.Value>
<AdBanner isLoading={ isLoading }/>
</DetailsInfoItem>
</DetailsInfoItem.Value>
</>
);
};
......
......@@ -32,7 +32,7 @@ const PrevNext = ({ className, onClick, prevLabel, nextLabel, isPrevDisabled, is
}
return (
<Box className={ className }>
<Box className={ className } display="flex">
<Tooltip label={ prevLabel }>
<IconButton
aria-label="prev"
......
......@@ -18,7 +18,7 @@ import { TOKEN_COUNTERS } from 'stubs/token';
import type { TokenTabs } from 'ui/pages/Token';
import AppActionButton from 'ui/shared/AppActionButton/AppActionButton';
import useAppActionData from 'ui/shared/AppActionButton/useAppActionData';
import DetailsInfoItem from 'ui/shared/DetailsInfoItem';
import * as DetailsInfoItem from 'ui/shared/DetailsInfoItem';
import DetailsSponsoredItem from 'ui/shared/DetailsSponsoredItem';
import TruncatedValue from 'ui/shared/TruncatedValue';
......@@ -102,77 +102,96 @@ const TokenDetails = ({ tokenQuery }: Props) => {
<Grid
columnGap={ 8 }
rowGap={{ base: 1, lg: 3 }}
templateColumns={{ base: 'minmax(0, 1fr)', lg: 'auto minmax(0, 1fr)' }} overflow="hidden"
templateColumns={{ base: 'minmax(0, 1fr)', lg: 'auto minmax(728px, auto)' }} overflow="hidden"
>
{ exchangeRate && (
<DetailsInfoItem
title="Price"
<>
<DetailsInfoItem.Label
hint="Price per token on the exchanges"
alignSelf="center"
isLoading={ tokenQuery.isPlaceholderData }
>
Price
</DetailsInfoItem.Label>
<DetailsInfoItem.Value>
<Skeleton isLoaded={ !tokenQuery.isPlaceholderData } display="inline-block">
<span>{ `$${ Number(exchangeRate).toLocaleString(undefined, { minimumSignificantDigits: 4 }) }` }</span>
</Skeleton>
</DetailsInfoItem>
</DetailsInfoItem.Value>
</>
) }
{ marketCap && (
<DetailsInfoItem
title="Fully diluted market cap"
<>
<DetailsInfoItem.Label
hint="Total supply * Price"
alignSelf="center"
isLoading={ tokenQuery.isPlaceholderData }
>
Fully diluted market cap
</DetailsInfoItem.Label>
<DetailsInfoItem.Value>
<Skeleton isLoaded={ !tokenQuery.isPlaceholderData } display="inline-block">
<span>{ `$${ BigNumber(marketCap).toFormat() }` }</span>
</Skeleton>
</DetailsInfoItem>
</DetailsInfoItem.Value>
</>
) }
<DetailsInfoItem
title="Max total supply"
<DetailsInfoItem.Label
hint="The total amount of tokens issued"
isLoading={ tokenQuery.isPlaceholderData }
>
Max total supply
</DetailsInfoItem.Label>
<DetailsInfoItem.Value
alignSelf="center"
wordBreak="break-word"
whiteSpace="pre-wrap"
isLoading={ tokenQuery.isPlaceholderData }
>
<Skeleton isLoaded={ !tokenQuery.isPlaceholderData } w="100%" display="flex">
<TruncatedValue value={ totalSupplyValue || '0' } maxW="80%" flexShrink={ 0 }/>
<Box flexShrink={ 0 }> </Box>
<TruncatedValue value={ symbol || '' }/>
</Skeleton>
</DetailsInfoItem>
<DetailsInfoItem
title="Holders"
</DetailsInfoItem.Value>
<DetailsInfoItem.Label
hint="Number of accounts holding the token"
alignSelf="center"
isLoading={ tokenQuery.isPlaceholderData }
>
Holders
</DetailsInfoItem.Label>
<DetailsInfoItem.Value>
<Skeleton isLoaded={ !tokenCountersQuery.isPlaceholderData }>
{ countersItem('token_holders_count') }
</Skeleton>
</DetailsInfoItem>
<DetailsInfoItem
title="Transfers"
</DetailsInfoItem.Value>
<DetailsInfoItem.Label
hint="Number of transfer for the token"
alignSelf="center"
isLoading={ tokenQuery.isPlaceholderData }
>
Transfers
</DetailsInfoItem.Label>
<DetailsInfoItem.Value>
<Skeleton isLoaded={ !tokenCountersQuery.isPlaceholderData }>
{ countersItem('transfers_count') }
</Skeleton>
</DetailsInfoItem>
</DetailsInfoItem.Value>
{ decimals && (
<DetailsInfoItem
title="Decimals"
<>
<DetailsInfoItem.Label
hint="Number of digits that come after the decimal place when displaying token value"
alignSelf="center"
isLoading={ tokenQuery.isPlaceholderData }
>
Decimals
</DetailsInfoItem.Label>
<DetailsInfoItem.Value>
<Skeleton isLoaded={ !tokenQuery.isPlaceholderData } minW={ 6 }>
{ decimals }
</Skeleton>
</DetailsInfoItem>
</DetailsInfoItem.Value>
</>
) }
{ type !== 'ERC-20' && (
......@@ -186,14 +205,18 @@ const TokenDetails = ({ tokenQuery }: Props) => {
) }
{ (type !== 'ERC-20' && config.UI.views.nft.marketplaces.length === 0 && appActionData && isActionButtonExperiment) && (
<DetailsInfoItem
title="Dapp"
<>
<DetailsInfoItem.Label
hint="Link to the dapp"
alignSelf="center"
py={ 1 }
>
Dapp
</DetailsInfoItem.Label>
<DetailsInfoItem.Value
py="1px"
>
<AppActionButton data={ appActionData } height="30px" source="NFT collection"/>
</DetailsInfoItem>
</DetailsInfoItem.Value>
</>
) }
<DetailsSponsoredItem isLoading={ tokenQuery.isPlaceholderData }/>
......
......@@ -5,7 +5,7 @@ import type { AddressMetadataTagFormatted } from 'types/client/addressMetadata';
import config from 'configs/app';
import AppActionButton from 'ui/shared/AppActionButton/AppActionButton';
import DetailsInfoItem from 'ui/shared/DetailsInfoItem';
import * as DetailsInfoItem from 'ui/shared/DetailsInfoItem';
import TextSeparator from 'ui/shared/TextSeparator';
interface Props {
......@@ -23,12 +23,15 @@ const TokenNftMarketplaces = ({ hash, id, isLoading, appActionData, source, isAc
}
return (
<DetailsInfoItem
title="Marketplaces"
<>
<DetailsInfoItem.Label
hint="Marketplaces trading this NFT"
alignSelf="center"
isLoading={ isLoading }
py={ (appActionData && isActionButtonExperiment) ? 1 : { base: 1, lg: 2 } }
>
Marketplaces
</DetailsInfoItem.Label>
<DetailsInfoItem.Value
py={ (appActionData && isActionButtonExperiment) ? '1px' : '6px' }
>
<Skeleton isLoaded={ !isLoading } display="flex" columnGap={ 3 } flexWrap="wrap" alignItems="center">
{ config.UI.views.nft.marketplaces.map((item) => {
......@@ -56,7 +59,8 @@ const TokenNftMarketplaces = ({ hash, id, isLoading, appActionData, source, isAc
</>
) }
</Skeleton>
</DetailsInfoItem>
</DetailsInfoItem.Value>
</>
);
};
......
......@@ -8,7 +8,7 @@ import useFeatureValue from 'lib/growthbook/useFeatureValue';
import AppActionButton from 'ui/shared/AppActionButton/AppActionButton';
import useAppActionData from 'ui/shared/AppActionButton/useAppActionData';
import CopyToClipboard from 'ui/shared/CopyToClipboard';
import DetailsInfoItem from 'ui/shared/DetailsInfoItem';
import * as DetailsInfoItem from 'ui/shared/DetailsInfoItem';
import DetailsInfoItemDivider from 'ui/shared/DetailsInfoItemDivider';
import DetailsSponsoredItem from 'ui/shared/DetailsSponsoredItem';
import AddressEntity from 'ui/shared/entities/address/AddressEntity';
......@@ -53,31 +53,41 @@ const TokenInstanceDetails = ({ data, token, scrollRef, isLoading }: Props) => {
overflow="hidden"
>
{ data.is_unique && data.owner && (
<DetailsInfoItem
title="Owner"
<>
<DetailsInfoItem.Label
hint="Current owner of this token instance"
isLoading={ isLoading }
>
Owner
</DetailsInfoItem.Label>
<DetailsInfoItem.Value>
<AddressEntity
address={ data.owner }
isLoading={ isLoading }
/>
</DetailsInfoItem>
</DetailsInfoItem.Value>
</>
) }
<TokenInstanceCreatorAddress hash={ isLoading ? '' : token.address }/>
<DetailsInfoItem
title="Token ID"
<DetailsInfoItem.Label
hint="This token instance unique token ID"
isLoading={ isLoading }
>
Token ID
</DetailsInfoItem.Label>
<DetailsInfoItem.Value>
<Flex alignItems="center" overflow="hidden">
<Skeleton isLoaded={ !isLoading } overflow="hidden" display="inline-block" w="100%">
<HashStringShortenDynamic hash={ data.id }/>
</Skeleton>
<CopyToClipboard text={ data.id } isLoading={ isLoading }/>
</Flex>
</DetailsInfoItem>
</DetailsInfoItem.Value>
<TokenInstanceTransfersCount hash={ isLoading ? '' : token.address } id={ isLoading ? '' : data.id } onClick={ handleCounterItemClick }/>
<TokenNftMarketplaces
isLoading={ isLoading }
hash={ token.address }
......@@ -86,15 +96,18 @@ const TokenInstanceDetails = ({ data, token, scrollRef, isLoading }: Props) => {
source="NFT item"
isActionButtonExperiment={ isActionButtonExperiment }
/>
{ (config.UI.views.nft.marketplaces.length === 0 && appActionData && isActionButtonExperiment) && (
<DetailsInfoItem
title="Dapp"
<>
<DetailsInfoItem.Label
hint="Link to the dapp"
alignSelf="center"
py={ 1 }
>
Dapp
</DetailsInfoItem.Label>
<DetailsInfoItem.Value py="1px">
<AppActionButton data={ appActionData } height="30px" source="NFT item"/>
</DetailsInfoItem>
</DetailsInfoItem.Value>
</>
) }
</Grid>
<NftMedia
......
......@@ -2,7 +2,7 @@ import React from 'react';
import useApiQuery from 'lib/api/useApiQuery';
import { ADDRESS_INFO } from 'stubs/address';
import DetailsInfoItem from 'ui/shared/DetailsInfoItem';
import * as DetailsInfoItem from 'ui/shared/DetailsInfoItem';
import AddressEntity from 'ui/shared/entities/address/AddressEntity';
interface Props {
......@@ -33,16 +33,20 @@ const TokenInstanceCreatorAddress = ({ hash }: Props) => {
};
return (
<DetailsInfoItem
title="Creator"
<>
<DetailsInfoItem.Label
hint="Address that deployed this token contract"
isLoading={ addressQuery.isPlaceholderData }
>
Creator
</DetailsInfoItem.Label>
<DetailsInfoItem.Value>
<AddressEntity
address={ creatorAddress }
isLoading={ addressQuery.isPlaceholderData }
/>
</DetailsInfoItem>
</DetailsInfoItem.Value>
</>
);
};
......
......@@ -5,7 +5,7 @@ import type { TokenInstance } from 'types/api/token';
import type { MetadataAttributes } from 'types/client/token';
import parseMetadata from 'lib/token/parseMetadata';
import DetailsInfoItem from 'ui/shared/DetailsInfoItem';
import * as DetailsInfoItem from 'ui/shared/DetailsInfoItem';
import DetailsInfoItemDivider from 'ui/shared/DetailsInfoItemDivider';
import LinkExternal from 'ui/shared/links/LinkExternal';
import TruncatedValue from 'ui/shared/TruncatedValue';
......@@ -74,42 +74,55 @@ const TokenInstanceMetadataInfo = ({ data, isLoading }: Props) => {
<>
<DetailsInfoItemDivider/>
{ metadata?.name && (
<DetailsInfoItem
title="Name"
<>
<DetailsInfoItem.Label
hint="NFT name"
isLoading={ isLoading }
>
Name
</DetailsInfoItem.Label>
<DetailsInfoItem.Value
whiteSpace="normal"
wordBreak="break-word"
isLoading={ isLoading }
>
<Skeleton isLoaded={ !isLoading }>
{ metadata.name }
</Skeleton>
</DetailsInfoItem>
</DetailsInfoItem.Value>
</>
) }
{ metadata?.description && (
<DetailsInfoItem
title="Description"
<>
<DetailsInfoItem.Label
hint="NFT description"
isLoading={ isLoading }
>
Description
</DetailsInfoItem.Label>
<DetailsInfoItem.Value
whiteSpace="normal"
wordBreak="break-word"
isLoading={ isLoading }
>
<Skeleton isLoaded={ !isLoading }>
{ metadata.description }
</Skeleton>
</DetailsInfoItem>
</DetailsInfoItem.Value>
</>
) }
{ metadata?.attributes && (
<DetailsInfoItem
title="Attributes"
<>
<DetailsInfoItem.Label
hint="NFT attributes"
whiteSpace="normal"
isLoading={ isLoading }
>
<Grid gap={ 2 } templateColumns="repeat(auto-fill,minmax(160px, 1fr))" w="100%">
Attributes
</DetailsInfoItem.Label>
<DetailsInfoItem.Value>
<Grid gap={ 2 } templateColumns="repeat(auto-fill,minmax(160px, 1fr))" w="100%" whiteSpace="normal">
{ metadata.attributes.map((attribute, index) => <Item key={ index } data={ attribute } isLoading={ isLoading }/>) }
</Grid>
</DetailsInfoItem>
</DetailsInfoItem.Value>
</>
) }
</>
);
......
......@@ -4,7 +4,7 @@ import React from 'react';
import { route } from 'nextjs-routes';
import useApiQuery from 'lib/api/useApiQuery';
import DetailsInfoItem from 'ui/shared/DetailsInfoItem';
import * as DetailsInfoItem from 'ui/shared/DetailsInfoItem';
import LinkInternal from 'ui/shared/links/LinkInternal';
interface Props {
......@@ -37,11 +37,14 @@ const TokenInstanceTransfersCount = ({ hash, id, onClick }: Props) => {
undefined;
return (
<DetailsInfoItem
title="Transfers"
<>
<DetailsInfoItem.Label
hint="Number of transfer for the token instance"
isLoading={ transfersCountQuery.isPlaceholderData }
>
Transfers
</DetailsInfoItem.Label>
<DetailsInfoItem.Value>
<Skeleton isLoaded={ !transfersCountQuery.isPlaceholderData } display="inline-block">
<LinkInternal
href={ url }
......@@ -50,7 +53,8 @@ const TokenInstanceTransfersCount = ({ hash, id, onClick }: Props) => {
{ transfersCountQuery.data.transfers_count.toLocaleString() }
</LinkInternal>
</Skeleton>
</DetailsInfoItem>
</DetailsInfoItem.Value>
</>
);
};
......
import { Flex, Link, useBoolean } from '@chakra-ui/react';
import React from 'react';
import DetailsInfoItem from 'ui/shared/DetailsInfoItem';
import * as DetailsInfoItem from 'ui/shared/DetailsInfoItem';
import AddressEntity from 'ui/shared/entities/address/AddressEntity';
interface Props {
......@@ -14,10 +14,13 @@ const TxAllowedPeekers = ({ items }: Props) => {
const [ isExpanded, expand ] = useBoolean(false);
return (
<DetailsInfoItem
title="Allowed peekers"
<>
<DetailsInfoItem.Label
hint="Smart contracts allowed to interact with confidential data"
>
Allowed peekers
</DetailsInfoItem.Label>
<DetailsInfoItem.Value>
<Flex flexDir="column" rowGap={ 3 } w="100%">
{ items
.slice(0, isExpanded ? undefined : CUT_LENGTH)
......@@ -34,7 +37,8 @@ const TxAllowedPeekers = ({ items }: Props) => {
{ isExpanded ? 'Hide' : 'Show all' }
</Link>
) }
</DetailsInfoItem>
</DetailsInfoItem.Value>
</>
);
};
......
......@@ -8,7 +8,7 @@ import type { ExcludeUndefined } from 'types/utils';
import { currencyUnits } from 'lib/units';
import Tag from 'ui/shared/chakra/Tag';
import CurrencyValue from 'ui/shared/CurrencyValue';
import DetailsInfoItem from 'ui/shared/DetailsInfoItem';
import * as DetailsInfoItem from 'ui/shared/DetailsInfoItem';
import DetailsInfoItemDivider from 'ui/shared/DetailsInfoItemDivider';
import AddressEntity from 'ui/shared/entities/address/AddressEntity';
import TxEntity from 'ui/shared/entities/tx/TxEntity';
......@@ -24,85 +24,110 @@ interface Props {
const TxDetailsWrapped = ({ data }: Props) => {
return (
<Grid columnGap={ 8 } rowGap={{ base: 3, lg: 3 }} templateColumns={{ base: 'minmax(0, 1fr)', lg: 'auto minmax(0, 1fr)' }}>
<DetailsInfoItem
title="Transaction hash"
<DetailsInfoItem.Label
hint="Unique character string (TxID) assigned to every verified transaction"
flexWrap="nowrap"
>
Transaction hash
</DetailsInfoItem.Label>
<DetailsInfoItem.Value flexWrap="nowrap">
<TxEntity hash={ data.hash } noIcon noLink noCopy={ false }/>
</DetailsInfoItem>
<DetailsInfoItem
title="Method"
</DetailsInfoItem.Value>
<DetailsInfoItem.Label
hint="Transaction method name"
>
Method
</DetailsInfoItem.Label>
<DetailsInfoItem.Value>
<Tag colorScheme="gray">
{ data.method }
</Tag>
</DetailsInfoItem>
</DetailsInfoItem.Value>
<DetailsInfoItemDivider/>
<DetailsInfoItem
title={ data.to?.is_contract ? 'Interacted with contract' : 'To' }
<DetailsInfoItem.Label
hint="Address (external or contract) receiving the transaction"
flexWrap={{ base: 'wrap', lg: 'nowrap' }}
columnGap={ 3 }
>
{ data.to?.is_contract ? 'Interacted with contract' : 'To' }
</DetailsInfoItem.Label>
<DetailsInfoItem.Value>
<Flex flexWrap="nowrap" alignItems="center" maxW="100%">
<AddressEntity address={ data.to }/>
</Flex>
</DetailsInfoItem>
</DetailsInfoItem.Value>
<DetailsInfoItemDivider/>
<DetailsInfoItem
title="Value"
<DetailsInfoItem.Label
hint="Value sent in the native token (and USD) if applicable"
>
Value
</DetailsInfoItem.Label>
<DetailsInfoItem.Value>
<CurrencyValue
value={ data.value }
currency={ currencyUnits.ether }
flexWrap="wrap"
/>
</DetailsInfoItem>
</DetailsInfoItem.Value>
{ data.fee.value !== null && (
<DetailsInfoItem
title="Transaction fee"
<>
<DetailsInfoItem.Label
hint="Total transaction fee"
>
Transaction fee
</DetailsInfoItem.Label>
<DetailsInfoItem.Value>
<CurrencyValue
value={ data.fee.value }
currency={ currencyUnits.ether }
flexWrap="wrap"
/>
</DetailsInfoItem>
</DetailsInfoItem.Value>
</>
) }
<TxDetailsGasPrice gasPrice={ data.gas_price }/>
{ data.gas_limit && (
<DetailsInfoItem
title="Gas limit"
<>
<DetailsInfoItem.Label
hint="Maximum amount of gas that can be used by the transaction"
>
Gas limit
</DetailsInfoItem.Label>
<DetailsInfoItem.Value>
{ BigNumber(data.gas_limit).toFormat() }
</DetailsInfoItem>
</DetailsInfoItem.Value>
</>
) }
<DetailsInfoItemDivider/>
<TxDetailsOther type={ data.type } nonce={ data.nonce } position={ null }/>
<DetailsInfoItem
title="Raw input"
<DetailsInfoItem.Label
hint="Binary data included with the transaction. See logs tab for additional info"
>
Raw input
</DetailsInfoItem.Label>
<DetailsInfoItem.Value>
<RawInputData hex={ data.raw_input }/>
</DetailsInfoItem>
</DetailsInfoItem.Value>
{ data.decoded_input && (
<DetailsInfoItem
title="Decoded input data"
<>
<DetailsInfoItem.Label
hint="Decoded input data"
>
Decoded input data
</DetailsInfoItem.Label>
<DetailsInfoItem.Value>
<LogDecodedInputData data={ data.decoded_input }/>
</DetailsInfoItem>
</DetailsInfoItem.Value>
</>
) }
</Grid>
);
......
......@@ -7,7 +7,7 @@ import config from 'configs/app';
import { ZERO } from 'lib/consts';
import { currencyUnits } from 'lib/units';
import CurrencyValue from 'ui/shared/CurrencyValue';
import DetailsInfoItem from 'ui/shared/DetailsInfoItem';
import * as DetailsInfoItem from 'ui/shared/DetailsInfoItem';
import IconSvg from 'ui/shared/IconSvg';
const rollupFeature = config.features.rollup;
......@@ -30,14 +30,17 @@ const TxDetailsBurntFees = ({ data, isLoading }: Props) => {
}
return (
<DetailsInfoItem
title="Burnt fees"
<>
<DetailsInfoItem.Label
hint={ `
Amount of ${ currencyUnits.ether } burned for this transaction. Equals Block Base Fee per Gas * Gas Used
${ data.blob_gas_price && data.blob_gas_used ? ' + Blob Gas Price * Blob Gas Used' : '' }
` }
isLoading={ isLoading }
>
Burnt fees
</DetailsInfoItem.Label>
<DetailsInfoItem.Value>
<IconSvg name="flame" boxSize={ 5 } color="gray.500" isLoading={ isLoading }/>
<CurrencyValue
value={ value.toString() }
......@@ -47,7 +50,8 @@ const TxDetailsBurntFees = ({ data, isLoading }: Props) => {
ml={ 2 }
isLoading={ isLoading }
/>
</DetailsInfoItem>
</DetailsInfoItem.Value>
</>
);
};
......
......@@ -4,7 +4,7 @@ import React from 'react';
import config from 'configs/app';
import { currencyUnits } from 'lib/units';
import DetailsInfoItem from 'ui/shared/DetailsInfoItem';
import * as DetailsInfoItem from 'ui/shared/DetailsInfoItem';
interface Props {
txFee: string | null;
......@@ -18,16 +18,20 @@ const TxDetailsFeePerGas = ({ txFee, gasUsed, isLoading }: Props) => {
}
return (
<DetailsInfoItem
title="Fee per gas"
<>
<DetailsInfoItem.Label
hint="Fee per gas"
isLoading={ isLoading }
>
Fee per gas
</DetailsInfoItem.Label>
<DetailsInfoItem.Value>
<Skeleton isLoaded={ !isLoading } mr={ 1 }>
{ BigNumber(txFee).dividedBy(10 ** config.chain.currency.decimals).dividedBy(gasUsed).toFixed() }
{ config.UI.views.tx.hiddenFields?.fee_currency ? '' : ` ${ currencyUnits.ether }` }
</Skeleton>
</DetailsInfoItem>
</DetailsInfoItem.Value>
</>
);
};
......
......@@ -5,7 +5,7 @@ import React from 'react';
import config from 'configs/app';
import { WEI, WEI_IN_GWEI } from 'lib/consts';
import { currencyUnits } from 'lib/units';
import DetailsInfoItem from 'ui/shared/DetailsInfoItem';
import * as DetailsInfoItem from 'ui/shared/DetailsInfoItem';
interface Props {
gasPrice: string | null;
......@@ -18,18 +18,22 @@ const TxDetailsGasPrice = ({ gasPrice, isLoading }: Props) => {
}
return (
<DetailsInfoItem
title="Gas price"
<>
<DetailsInfoItem.Label
hint="Price per unit of gas specified by the sender. Higher gas prices can prioritize transaction inclusion during times of high usage"
isLoading={ isLoading }
>
Gas price
</DetailsInfoItem.Label>
<DetailsInfoItem.Value>
<Skeleton isLoaded={ !isLoading } mr={ 1 }>
{ BigNumber(gasPrice).dividedBy(WEI).toFixed() } { currencyUnits.ether }
</Skeleton>
<Skeleton isLoaded={ !isLoading } color="text_secondary">
<span>({ BigNumber(gasPrice).dividedBy(WEI_IN_GWEI).toFixed() } { currencyUnits.gwei })</span>
</Skeleton>
</DetailsInfoItem>
</DetailsInfoItem.Value>
</>
);
};
......
......@@ -3,17 +3,20 @@ import React from 'react';
import type { Transaction } from 'types/api/transaction';
import DetailsInfoItem from 'ui/shared/DetailsInfoItem';
import * as DetailsInfoItem from 'ui/shared/DetailsInfoItem';
import TextSeparator from 'ui/shared/TextSeparator';
type Props = Pick<Transaction, 'nonce' | 'type' | 'position'>
const TxDetailsOther = ({ nonce, type, position }: Props) => {
return (
<DetailsInfoItem
title="Other"
<>
<DetailsInfoItem.Label
hint="Other data related to this transaction"
>
Other
</DetailsInfoItem.Label>
<DetailsInfoItem.Value>
{
[
typeof type === 'number' && (
......@@ -43,7 +46,8 @@ const TxDetailsOther = ({ nonce, type, position }: Props) => {
</>
))
}
</DetailsInfoItem>
</DetailsInfoItem.Value>
</>
);
};
......
......@@ -5,7 +5,7 @@ import type { TokenTransfer } from 'types/api/tokenTransfer';
import { route } from 'nextjs-routes';
import DetailsInfoItem from 'ui/shared/DetailsInfoItem';
import * as DetailsInfoItem from 'ui/shared/DetailsInfoItem';
import IconSvg from 'ui/shared/IconSvg';
import LinkInternal from 'ui/shared/links/LinkInternal';
......@@ -40,12 +40,13 @@ const TxDetailsTokenTransfers = ({ data, txHash, isOverflow }: Props) => {
}
return (
<DetailsInfoItem
key={ type }
title={ title }
<React.Fragment key={ type }>
<DetailsInfoItem.Label
hint={ hint }
position="relative"
>
{ title }
</DetailsInfoItem.Label>
<DetailsInfoItem.Value position="relative">
<Flex
flexDirection="column"
alignItems="flex-start"
......@@ -55,7 +56,8 @@ const TxDetailsTokenTransfers = ({ data, txHash, isOverflow }: Props) => {
>
{ items.map((item, index) => <TxDetailsTokenTransfer key={ index } data={ item }/>) }
</Flex>
</DetailsInfoItem>
</DetailsInfoItem.Value>
</React.Fragment>
);
}) }
{ isOverflow && (
......
......@@ -29,7 +29,7 @@ import { currencyUnits } from 'lib/units';
import Tag from 'ui/shared/chakra/Tag';
import CopyToClipboard from 'ui/shared/CopyToClipboard';
import CurrencyValue from 'ui/shared/CurrencyValue';
import DetailsInfoItem from 'ui/shared/DetailsInfoItem';
import * as DetailsInfoItem from 'ui/shared/DetailsInfoItem';
import DetailsInfoItemDivider from 'ui/shared/DetailsInfoItemDivider';
import DetailsSponsoredItem from 'ui/shared/DetailsSponsoredItem';
import DetailsTimestamp from 'ui/shared/DetailsTimestamp';
......@@ -125,12 +125,14 @@ const TxInfo = ({ data, isLoading, socketStatus }: Props) => {
<TxSocketAlert status={ socketStatus }/>
</GridItem>
) }
<DetailsInfoItem
title="Transaction hash"
<DetailsInfoItem.Label
hint="Unique character string (TxID) assigned to every verified transaction"
flexWrap="nowrap"
isLoading={ isLoading }
>
Transaction hash
</DetailsInfoItem.Label>
<DetailsInfoItem.Value flexWrap="nowrap">
{ data.status === null && <Spinner mr={ 2 } size="sm" flexShrink={ 0 }/> }
<Skeleton isLoaded={ !isLoading } overflow="hidden">
<HashStringShortenDynamic hash={ data.hash }/>
......@@ -143,29 +145,36 @@ const TxInfo = ({ data, isLoading, socketStatus }: Props) => {
<Box display="none" flexShrink={ 0 } id="meta-suites__tx-explorer-link"/>
</>
) }
</DetailsInfoItem>
<DetailsInfoItem
title={
</DetailsInfoItem.Value>
<DetailsInfoItem.Label
hint="Current transaction state: Success, Failed (Error), or Pending (In Process)"
isLoading={ isLoading }
>
{
rollupFeature.isEnabled && (rollupFeature.type === 'zkEvm' || rollupFeature.type === 'zkSync') ?
'L2 status and method' :
'Status and method'
}
hint="Current transaction state: Success, Failed (Error), or Pending (In Process)"
isLoading={ isLoading }
>
</DetailsInfoItem.Label>
<DetailsInfoItem.Value>
<TxStatus status={ data.status } errorText={ data.status === 'error' ? data.result : undefined } isLoading={ isLoading }/>
{ data.method && (
<Tag colorScheme={ data.method === 'Multicall' ? 'teal' : 'gray' } isLoading={ isLoading } isTruncated ml={ 3 }>
{ data.method }
</Tag>
) }
</DetailsInfoItem>
</DetailsInfoItem.Value>
{ rollupFeature.isEnabled && rollupFeature.type === 'optimistic' && data.op_withdrawals && data.op_withdrawals.length > 0 &&
!config.UI.views.tx.hiddenFields?.L1_status && (
<DetailsInfoItem
title="Withdrawal status"
<>
<DetailsInfoItem.Label
hint="Detailed status progress of the transaction"
>
Withdrawal status
</DetailsInfoItem.Label>
<DetailsInfoItem.Value>
<Flex flexDir="column" rowGap={ 2 }>
{ data.op_withdrawals.map((withdrawal) => (
<Box key={ withdrawal.nonce }>
......@@ -180,39 +189,58 @@ const TxInfo = ({ data, isLoading, socketStatus }: Props) => {
</Box>
)) }
</Flex>
</DetailsInfoItem>
</DetailsInfoItem.Value>
</>
) }
{ data.zkevm_status && !config.UI.views.tx.hiddenFields?.L1_status && (
<DetailsInfoItem
title="Confirmation status"
<>
<DetailsInfoItem.Label
hint="Status of the transaction confirmation path to L1"
isLoading={ isLoading }
>
Confirmation status
</DetailsInfoItem.Label>
<DetailsInfoItem.Value>
<VerificationSteps currentStep={ data.zkevm_status } steps={ ZKEVM_L2_TX_STATUSES } isLoading={ isLoading }/>
</DetailsInfoItem>
</DetailsInfoItem.Value>
</>
) }
{ data.revert_reason && (
<DetailsInfoItem
title="Revert reason"
<>
<DetailsInfoItem.Label
hint="The revert reason of the transaction"
>
Revert reason
</DetailsInfoItem.Label>
<DetailsInfoItem.Value>
<TxRevertReason { ...data.revert_reason }/>
</DetailsInfoItem>
</DetailsInfoItem.Value>
</>
) }
{ data.zksync && !config.UI.views.tx.hiddenFields?.L1_status && (
<DetailsInfoItem
title="L1 status"
<>
<DetailsInfoItem.Label
hint="Status is the short interpretation of the batch lifecycle"
isLoading={ isLoading }
>
L1 status
</DetailsInfoItem.Label>
<DetailsInfoItem.Value>
<VerificationSteps steps={ ZKSYNC_L2_TX_BATCH_STATUSES } currentStep={ data.zksync.status } isLoading={ isLoading }/>
</DetailsInfoItem>
</DetailsInfoItem.Value>
</>
) }
<DetailsInfoItem
title="Block"
<DetailsInfoItem.Label
hint="Block number containing the transaction"
isLoading={ isLoading }
>
Block
</DetailsInfoItem.Label>
<DetailsInfoItem.Value>
{ data.block === null ?
<Text>Pending</Text> : (
<BlockEntity
......@@ -229,39 +257,53 @@ const TxInfo = ({ data, isLoading, socketStatus }: Props) => {
</Skeleton>
</>
) }
</DetailsInfoItem>
</DetailsInfoItem.Value>
{ data.zkevm_batch_number && !config.UI.views.tx.hiddenFields?.batch && (
<DetailsInfoItem
title="Tx batch"
<>
<DetailsInfoItem.Label
hint="Batch index for this transaction"
isLoading={ isLoading }
>
Tx batch
</DetailsInfoItem.Label>
<DetailsInfoItem.Value>
<BatchEntityL2
isLoading={ isLoading }
number={ data.zkevm_batch_number }
/>
</DetailsInfoItem>
</DetailsInfoItem.Value>
</>
) }
{ data.zksync && !config.UI.views.tx.hiddenFields?.batch && (
<DetailsInfoItem
title="Batch"
<>
<DetailsInfoItem.Label
hint="Batch number"
isLoading={ isLoading }
>
Batch
</DetailsInfoItem.Label>
<DetailsInfoItem.Value>
{ data.zksync.batch_number ? (
<BatchEntityL2
isLoading={ isLoading }
number={ data.zksync.batch_number }
/>
) : <Skeleton isLoaded={ !isLoading }>Pending</Skeleton> }
</DetailsInfoItem>
</DetailsInfoItem.Value>
</>
) }
{ data.timestamp && (
<DetailsInfoItem
title="Timestamp"
<>
<DetailsInfoItem.Label
hint="Date & time of transaction inclusion, including length of time for confirmation"
isLoading={ isLoading }
>
Timestamp
</DetailsInfoItem.Label>
<DetailsInfoItem.Value>
<DetailsTimestamp timestamp={ data.timestamp } isLoading={ isLoading }/>
{ data.confirmation_duration && (
<>
......@@ -271,35 +313,44 @@ const TxInfo = ({ data, isLoading, socketStatus }: Props) => {
</Skeleton>
</>
) }
</DetailsInfoItem>
</DetailsInfoItem.Value>
</>
) }
{ data.execution_node && (
<DetailsInfoItem
title="Kettle"
<>
<DetailsInfoItem.Label
hint="Node that carried out the confidential computation"
isLoading={ isLoading }
>
Kettle
</DetailsInfoItem.Label>
<DetailsInfoItem.Value>
<AddressEntity
address={ data.execution_node }
href={ route({ pathname: '/txs/kettle/[hash]', query: { hash: data.execution_node.hash } }) }
/>
</DetailsInfoItem>
</DetailsInfoItem.Value>
</>
) }
{ data.allowed_peekers && data.allowed_peekers.length > 0 && (
<TxAllowedPeekers items={ data.allowed_peekers }/>
) }
<DetailsSponsoredItem isLoading={ isLoading }/>
<DetailsInfoItemDivider/>
<TxDetailsActions hash={ data.hash } actions={ data.actions } isTxDataLoading={ isLoading }/>
<DetailsInfoItem
title="From"
<DetailsInfoItem.Label
hint="Address (external or contract) sending the transaction"
isLoading={ isLoading }
columnGap={ 3 }
>
From
</DetailsInfoItem.Label>
<DetailsInfoItem.Value columnGap={ 3 }>
<AddressEntity
address={ data.from }
isLoading={ isLoading }
......@@ -310,11 +361,15 @@ const TxInfo = ({ data, isLoading, socketStatus }: Props) => {
{ addressFromTags }
</Flex>
) }
</DetailsInfoItem>
<DetailsInfoItem
title={ data.to?.is_contract ? 'Interacted with contract' : 'To' }
</DetailsInfoItem.Value>
<DetailsInfoItem.Label
hint="Address (external or contract) receiving the transaction"
isLoading={ isLoading }
>
{ data.to?.is_contract ? 'Interacted with contract' : 'To' }
</DetailsInfoItem.Label>
<DetailsInfoItem.Value
flexWrap={{ base: 'wrap', lg: 'nowrap' }}
columnGap={ 3 }
>
......@@ -351,47 +406,56 @@ const TxInfo = ({ data, isLoading, socketStatus }: Props) => {
) : (
<span>[ Contract creation ]</span>
) }
</DetailsInfoItem>
</DetailsInfoItem.Value>
{ data.token_transfers && <TxDetailsTokenTransfers data={ data.token_transfers } txHash={ data.hash } isOverflow={ data.token_transfers_overflow }/> }
<DetailsInfoItemDivider/>
{ data.zkevm_sequence_hash && (
<DetailsInfoItem
title="Sequence tx hash"
flexWrap="nowrap"
<>
<DetailsInfoItem.Label
isLoading={ isLoading }
>
Sequence tx hash
</DetailsInfoItem.Label>
<DetailsInfoItem.Value flexWrap="nowrap">
<Skeleton isLoaded={ !isLoading } overflow="hidden">
<HashStringShortenDynamic hash={ data.zkevm_sequence_hash }/>
</Skeleton>
<CopyToClipboard text={ data.zkevm_sequence_hash } isLoading={ isLoading }/>
</DetailsInfoItem>
</DetailsInfoItem.Value>
</>
) }
{ data.zkevm_verify_hash && (
<DetailsInfoItem
title="Verify tx hash"
flexWrap="nowrap"
<>
<DetailsInfoItem.Label
isLoading={ isLoading }
>
Verify tx hash
</DetailsInfoItem.Label>
<DetailsInfoItem.Value flexWrap="nowrap">
<Skeleton isLoaded={ !isLoading } overflow="hidden">
<HashStringShortenDynamic hash={ data.zkevm_verify_hash }/>
</Skeleton>
<CopyToClipboard text={ data.zkevm_verify_hash } isLoading={ isLoading }/>
</DetailsInfoItem>
</DetailsInfoItem.Value>
</>
) }
{ (data.zkevm_batch_number || data.zkevm_verify_hash) && <DetailsInfoItemDivider/> }
{ !config.UI.views.tx.hiddenFields?.value && (
<DetailsInfoItem
title="Value"
<>
<DetailsInfoItem.Label
hint="Value sent in the native token (and USD) if applicable"
isLoading={ isLoading }
>
Value
</DetailsInfoItem.Label>
<DetailsInfoItem.Value>
<CurrencyValue
value={ data.value }
currency={ currencyUnits.ether }
......@@ -399,14 +463,19 @@ const TxInfo = ({ data, isLoading, socketStatus }: Props) => {
isLoading={ isLoading }
flexWrap="wrap"
/>
</DetailsInfoItem>
</DetailsInfoItem.Value>
</>
) }
{ !config.UI.views.tx.hiddenFields?.tx_fee && (
<DetailsInfoItem
title="Transaction fee"
<>
<DetailsInfoItem.Label
hint={ data.blob_gas_used ? 'Transaction fee without blob fee' : 'Total transaction fee' }
isLoading={ isLoading }
>
Transaction fee
</DetailsInfoItem.Label>
<DetailsInfoItem.Value>
{ data.stability_fee ? (
<TxFeeStability data={ data.stability_fee } isLoading={ isLoading }/>
) : (
......@@ -418,27 +487,31 @@ const TxInfo = ({ data, isLoading, socketStatus }: Props) => {
isLoading={ isLoading }
/>
) }
</DetailsInfoItem>
</DetailsInfoItem.Value>
</>
) }
<TxDetailsGasPrice gasPrice={ data.gas_price } isLoading={ isLoading }/>
<TxDetailsFeePerGas txFee={ data.fee.value } gasUsed={ data.gas_used } isLoading={ isLoading }/>
<DetailsInfoItem
title="Gas usage & limit by txn"
<DetailsInfoItem.Label
hint="Actual gas amount used by the transaction"
isLoading={ isLoading }
>
Gas usage & limit by txn
</DetailsInfoItem.Label>
<DetailsInfoItem.Value>
<Skeleton isLoaded={ !isLoading }>{ BigNumber(data.gas_used || 0).toFormat() }</Skeleton>
<TextSeparator/>
<Skeleton isLoaded={ !isLoading }>{ BigNumber(data.gas_limit).toFormat() }</Skeleton>
<Utilization ml={ 4 } value={ BigNumber(data.gas_used || 0).dividedBy(BigNumber(data.gas_limit)).toNumber() } isLoading={ isLoading }/>
</DetailsInfoItem>
</DetailsInfoItem.Value>
{ !config.UI.views.tx.hiddenFields?.gas_fees &&
(data.base_fee_per_gas || data.max_fee_per_gas || data.max_priority_fee_per_gas) && (
<DetailsInfoItem
title={ `Gas fees (${ currencyUnits.gwei })` }
<>
<DetailsInfoItem.Label
// eslint-disable-next-line max-len
hint={ `
Base Fee refers to the network Base Fee at the time of the block,
......@@ -447,6 +520,9 @@ const TxInfo = ({ data, isLoading, socketStatus }: Props) => {
` }
isLoading={ isLoading }
>
{ `Gas fees (${ currencyUnits.gwei })` }
</DetailsInfoItem.Label>
<DetailsInfoItem.Value>
{ data.base_fee_per_gas && (
<Skeleton isLoaded={ !isLoading }>
<Text as="span" fontWeight="500">Base: </Text>
......@@ -467,56 +543,79 @@ const TxInfo = ({ data, isLoading, socketStatus }: Props) => {
<Text fontWeight="600" as="span">{ BigNumber(data.max_priority_fee_per_gas).dividedBy(WEI_IN_GWEI).toFixed() }</Text>
</Skeleton>
) }
</DetailsInfoItem>
</DetailsInfoItem.Value>
</>
) }
<TxDetailsBurntFees data={ data } isLoading={ isLoading }/>
{ rollupFeature.isEnabled && rollupFeature.type === 'optimistic' && (
<>
{ data.l1_gas_used && (
<DetailsInfoItem
title="L1 gas used by txn"
<>
<DetailsInfoItem.Label
hint="L1 gas used by transaction"
isLoading={ isLoading }
>
L1 gas used by txn
</DetailsInfoItem.Label>
<DetailsInfoItem.Value>
<Text>{ BigNumber(data.l1_gas_used).toFormat() }</Text>
</DetailsInfoItem>
</DetailsInfoItem.Value>
</>
) }
{ data.l1_gas_price && (
<DetailsInfoItem
title="L1 gas price"
<>
<DetailsInfoItem.Label
hint="L1 gas price"
isLoading={ isLoading }
>
L1 gas price
</DetailsInfoItem.Label>
<DetailsInfoItem.Value>
<Text mr={ 1 }>{ BigNumber(data.l1_gas_price).dividedBy(WEI).toFixed() } { currencyUnits.ether }</Text>
<Text variant="secondary">({ BigNumber(data.l1_gas_price).dividedBy(WEI_IN_GWEI).toFixed() } { currencyUnits.gwei })</Text>
</DetailsInfoItem>
</DetailsInfoItem.Value>
</>
) }
{ data.l1_fee && (
<DetailsInfoItem
title="L1 fee"
<>
<DetailsInfoItem.Label
// eslint-disable-next-line max-len
hint={ `L1 Data Fee which is used to cover the L1 "security" cost from the batch submission mechanism. In combination with L2 execution fee, L1 fee makes the total amount of fees that a transaction pays.` }
isLoading={ isLoading }
>
L1 fee
</DetailsInfoItem.Label>
<DetailsInfoItem.Value>
<CurrencyValue
value={ data.l1_fee }
currency={ currencyUnits.ether }
exchangeRate={ data.exchange_rate }
flexWrap="wrap"
/>
</DetailsInfoItem>
</DetailsInfoItem.Value>
</>
) }
{ data.l1_fee_scalar && (
<DetailsInfoItem
title="L1 fee scalar"
<>
<DetailsInfoItem.Label
hint="A Dynamic overhead (fee scalar) premium, which serves as a buffer in case L1 prices rapidly increase."
isLoading={ isLoading }
>
L1 fee scalar
</DetailsInfoItem.Label>
<DetailsInfoItem.Value>
<Text>{ data.l1_fee_scalar }</Text>
</DetailsInfoItem>
</DetailsInfoItem.Value>
</>
) }
</>
) }
<GridItem colSpan={{ base: undefined, lg: 2 }}>
<Element name="TxInfo__cutLink">
<Skeleton isLoaded={ !isLoading } mt={ 6 } display="inline-block">
......@@ -532,16 +631,20 @@ const TxInfo = ({ data, isLoading, socketStatus }: Props) => {
</Skeleton>
</Element>
</GridItem>
{ isExpanded && (
<>
<GridItem colSpan={{ base: undefined, lg: 2 }} mt={{ base: 1, lg: 4 }}/>
{ (data.blob_gas_used || data.max_fee_per_blob_gas || data.blob_gas_price) && (
<>
{ data.blob_gas_used && data.blob_gas_price && (
<DetailsInfoItem
title="Blob fee"
<>
<DetailsInfoItem.Label
hint="Blob fee for this transaction"
>
Blob fee
</DetailsInfoItem.Label>
<DetailsInfoItem.Value>
<CurrencyValue
value={ BigNumber(data.blob_gas_used).multipliedBy(data.blob_gas_price).toString() }
currency={ config.UI.views.tx.hiddenFields?.fee_currency ? '' : currencyUnits.ether }
......@@ -549,21 +652,31 @@ const TxInfo = ({ data, isLoading, socketStatus }: Props) => {
flexWrap="wrap"
isLoading={ isLoading }
/>
</DetailsInfoItem>
</DetailsInfoItem.Value>
</>
) }
{ data.blob_gas_used && (
<DetailsInfoItem
title="Blob gas usage"
<>
<DetailsInfoItem.Label
hint="Amount of gas used by the blobs in this transaction"
>
Blob gas usage
</DetailsInfoItem.Label>
<DetailsInfoItem.Value>
{ BigNumber(data.blob_gas_used).toFormat() }
</DetailsInfoItem>
</DetailsInfoItem.Value>
</>
) }
{ (data.max_fee_per_blob_gas || data.blob_gas_price) && (
<DetailsInfoItem
title={ `Blob gas fees (${ currencyUnits.gwei })` }
<>
<DetailsInfoItem.Label
hint={ `Amount of ${ currencyUnits.ether } used for blobs in this transaction` }
>
{ `Blob gas fees (${ currencyUnits.gwei })` }
</DetailsInfoItem.Label>
<DetailsInfoItem.Value>
{ data.blob_gas_price && (
<Text fontWeight="600" as="span">{ BigNumber(data.blob_gas_price).dividedBy(WEI_IN_GWEI).toFixed() }</Text>
) }
......@@ -574,26 +687,37 @@ const TxInfo = ({ data, isLoading, socketStatus }: Props) => {
<Text fontWeight="600" as="span">{ BigNumber(data.max_fee_per_blob_gas).dividedBy(WEI_IN_GWEI).toFixed() }</Text>
</>
) }
</DetailsInfoItem>
</DetailsInfoItem.Value>
</>
) }
<DetailsInfoItemDivider/>
</>
) }
<TxDetailsOther nonce={ data.nonce } type={ data.type } position={ data.position }/>
<DetailsInfoItem
title="Raw input"
<DetailsInfoItem.Label
hint="Binary data included with the transaction. See logs tab for additional info"
>
Raw input
</DetailsInfoItem.Label>
<DetailsInfoItem.Value>
<RawInputData hex={ data.raw_input }/>
</DetailsInfoItem>
</DetailsInfoItem.Value>
{ data.decoded_input && (
<DetailsInfoItem
title="Decoded input data"
<>
<DetailsInfoItem.Label
hint="Decoded input data"
>
Decoded input data
</DetailsInfoItem.Label>
<DetailsInfoItem.Value>
<LogDecodedInputData data={ data.decoded_input }/>
</DetailsInfoItem>
</DetailsInfoItem.Value>
</>
) }
{ data.zksync && <ZkSyncL2TxnBatchHashesInfo data={ data.zksync } isLoading={ isLoading }/> }
</>
) }
......
......@@ -13,7 +13,7 @@ import throwOnResourceLoadError from 'lib/errors/throwOnResourceLoadError';
import isCustomAppError from 'ui/shared/AppError/isCustomAppError';
import CopyToClipboard from 'ui/shared/CopyToClipboard';
import DataFetchAlert from 'ui/shared/DataFetchAlert';
import DetailsInfoItem from 'ui/shared/DetailsInfoItem';
import * as DetailsInfoItem from 'ui/shared/DetailsInfoItem';
import DetailsInfoItemDivider from 'ui/shared/DetailsInfoItemDivider';
import DetailsTimestamp from 'ui/shared/DetailsTimestamp';
import TxEntityL1 from 'ui/shared/entities/tx/TxEntityL1';
......@@ -61,10 +61,12 @@ const ZkEvmL2TxnBatchDetails = ({ query }: Props) => {
templateColumns={{ base: 'minmax(0, 1fr)', lg: 'minmax(min-content, 200px) minmax(0, 1fr)' }}
overflow="hidden"
>
<DetailsInfoItem
title="Tx batch number"
<DetailsInfoItem.Label
isLoading={ isPlaceholderData }
>
Tx batch number
</DetailsInfoItem.Label>
<DetailsInfoItem.Value>
<Skeleton isLoaded={ !isPlaceholderData }>
{ data.number }
</Skeleton>
......@@ -76,23 +78,32 @@ const ZkEvmL2TxnBatchDetails = ({ query }: Props) => {
isPrevDisabled={ data.number === 0 }
isLoading={ isPlaceholderData }
/>
</DetailsInfoItem>
<DetailsInfoItem
title="Status"
</DetailsInfoItem.Value>
<DetailsInfoItem.Label
isLoading={ isPlaceholderData }
>
Status
</DetailsInfoItem.Label>
<DetailsInfoItem.Value>
<VerificationSteps steps={ ZKEVM_L2_TX_BATCH_STATUSES } currentStep={ data.status } isLoading={ isPlaceholderData }/>
</DetailsInfoItem>
<DetailsInfoItem
title="Timestamp"
</DetailsInfoItem.Value>
<DetailsInfoItem.Label
isLoading={ isPlaceholderData }
>
Timestamp
</DetailsInfoItem.Label>
<DetailsInfoItem.Value>
{ data.timestamp ? <DetailsTimestamp timestamp={ data.timestamp } isLoading={ isPlaceholderData }/> : 'Undefined' }
</DetailsInfoItem>
<DetailsInfoItem
title="Verify tx hash"
</DetailsInfoItem.Value>
<DetailsInfoItem.Label
isLoading={ isPlaceholderData }
>
Verify tx hash
</DetailsInfoItem.Label>
<DetailsInfoItem.Value>
{ data.verify_tx_hash ? (
<TxEntityL1
isLoading={ isPlaceholderData }
......@@ -100,44 +111,57 @@ const ZkEvmL2TxnBatchDetails = ({ query }: Props) => {
maxW="100%"
/>
) : <Text>Pending</Text> }
</DetailsInfoItem>
<DetailsInfoItem
title="Transactions"
</DetailsInfoItem.Value>
<DetailsInfoItem.Label
isLoading={ isPlaceholderData }
>
Transactions
</DetailsInfoItem.Label>
<DetailsInfoItem.Value>
<Skeleton isLoaded={ !isPlaceholderData }>
<LinkInternal href={ route({ pathname: '/batches/[number]', query: { number: data.number.toString(), tab: 'txs' } }) }>
{ data.transactions.length } transaction{ data.transactions.length === 1 ? '' : 's' }
</LinkInternal>
</Skeleton>
</DetailsInfoItem>
</DetailsInfoItem.Value>
<DetailsInfoItemDivider/>
<DetailsInfoItem
title="Global exit root"
<DetailsInfoItem.Label
isLoading={ isPlaceholderData }
>
Global exit root
</DetailsInfoItem.Label>
<DetailsInfoItem.Value
flexWrap="nowrap"
>
<Skeleton isLoaded={ !isPlaceholderData } overflow="hidden">
<HashStringShortenDynamic hash={ data.global_exit_root }/>
</Skeleton>
<CopyToClipboard text={ data.global_exit_root } isLoading={ isPlaceholderData }/>
</DetailsInfoItem>
<DetailsInfoItem
title="Acc input hash"
</DetailsInfoItem.Value>
<DetailsInfoItem.Label
isLoading={ isPlaceholderData }
>
Acc input hash
</DetailsInfoItem.Label>
<DetailsInfoItem.Value
flexWrap="nowrap"
>
<Skeleton isLoaded={ !isPlaceholderData } overflow="hidden">
<HashStringShortenDynamic hash={ data.acc_input_hash }/>
</Skeleton>
<CopyToClipboard text={ data.acc_input_hash } isLoading={ isPlaceholderData }/>
</DetailsInfoItem>
<DetailsInfoItem
title="Sequence tx hash"
</DetailsInfoItem.Value>
<DetailsInfoItem.Label
isLoading={ isPlaceholderData }
>
Sequence tx hash
</DetailsInfoItem.Label>
<DetailsInfoItem.Value>
{ data.sequence_tx_hash ? (
<TxEntityL1
isLoading={ isPlaceholderData }
......@@ -145,17 +169,21 @@ const ZkEvmL2TxnBatchDetails = ({ query }: Props) => {
maxW="100%"
/>
) : <Text>Pending</Text> }
</DetailsInfoItem>
<DetailsInfoItem
title="State root"
</DetailsInfoItem.Value>
<DetailsInfoItem.Label
isLoading={ isPlaceholderData }
>
State root
</DetailsInfoItem.Label>
<DetailsInfoItem.Value
flexWrap="nowrap"
>
<Skeleton isLoaded={ !isPlaceholderData } overflow="hidden">
<HashStringShortenDynamic hash={ data.state_root }/>
</Skeleton>
<CopyToClipboard text={ data.state_root } isLoading={ isPlaceholderData }/>
</DetailsInfoItem>
</DetailsInfoItem.Value>
</Grid>
);
};
......
......@@ -16,7 +16,7 @@ import { currencyUnits } from 'lib/units';
import isCustomAppError from 'ui/shared/AppError/isCustomAppError';
import CopyToClipboard from 'ui/shared/CopyToClipboard';
import DataFetchAlert from 'ui/shared/DataFetchAlert';
import DetailsInfoItem from 'ui/shared/DetailsInfoItem';
import * as DetailsInfoItem from 'ui/shared/DetailsInfoItem';
import DetailsInfoItemDivider from 'ui/shared/DetailsInfoItemDivider';
import DetailsTimestamp from 'ui/shared/DetailsTimestamp';
import LinkInternal from 'ui/shared/links/LinkInternal';
......@@ -76,11 +76,13 @@ const ZkSyncL2TxnBatchDetails = ({ query }: Props) => {
templateColumns={{ base: 'minmax(0, 1fr)', lg: 'minmax(min-content, 200px) minmax(0, 1fr)' }}
overflow="hidden"
>
<DetailsInfoItem
title="Tx batch number"
<DetailsInfoItem.Label
hint="Batch number indicates the length of batches produced by grouping L2 blocks to be proven on Ethereum."
isLoading={ isPlaceholderData }
>
Tx batch number
</DetailsInfoItem.Label>
<DetailsInfoItem.Value>
<Skeleton isLoaded={ !isPlaceholderData }>
{ data.number }
</Skeleton>
......@@ -92,35 +94,41 @@ const ZkSyncL2TxnBatchDetails = ({ query }: Props) => {
isPrevDisabled={ data.number === 0 }
isLoading={ isPlaceholderData }
/>
</DetailsInfoItem>
</DetailsInfoItem.Value>
<DetailsInfoItem
title="Status"
<DetailsInfoItem.Label
hint="Status is the short interpretation of the batch lifecycle"
isLoading={ isPlaceholderData }
>
Status
</DetailsInfoItem.Label>
<DetailsInfoItem.Value>
<VerificationSteps steps={ ZKSYNC_L2_TX_BATCH_STATUSES.slice(1) } currentStep={ data.status } isLoading={ isPlaceholderData }/>
</DetailsInfoItem>
</DetailsInfoItem.Value>
<DetailsInfoItem
title="Timestamp"
<DetailsInfoItem.Label
hint="Date and time at which batch is produced"
isLoading={ isPlaceholderData }
>
Timestamp
</DetailsInfoItem.Label>
<DetailsInfoItem.Value>
{ data.timestamp ? <DetailsTimestamp timestamp={ data.timestamp } isLoading={ isPlaceholderData }/> : 'Undefined' }
</DetailsInfoItem>
</DetailsInfoItem.Value>
<DetailsInfoItem
title="Transactions"
<DetailsInfoItem.Label
hint="Number of transactions inside the batch."
isLoading={ isPlaceholderData }
>
Transactions
</DetailsInfoItem.Label>
<DetailsInfoItem.Value>
<Skeleton isLoaded={ !isPlaceholderData }>
<LinkInternal href={ route({ pathname: '/batches/[number]', query: { number: data.number.toString(), tab: 'txs' } }) }>
{ txNum } transaction{ txNum === 1 ? '' : 's' }
</LinkInternal>
</Skeleton>
</DetailsInfoItem>
</DetailsInfoItem.Value>
<DetailsInfoItemDivider/>
......@@ -146,31 +154,38 @@ const ZkSyncL2TxnBatchDetails = ({ query }: Props) => {
<>
<GridItem colSpan={{ base: undefined, lg: 2 }} mt={{ base: 1, lg: 4 }}/>
<DetailsInfoItem
title="Root hash"
<DetailsInfoItem.Label
hint="L1 batch root is a hash that summarizes batch data and submitted to the L1"
>
Root hash
</DetailsInfoItem.Label>
<DetailsInfoItem.Value
flexWrap="nowrap"
alignSelf="flex-start"
>
<TruncatedValue value={ data.root_hash }/>
<CopyToClipboard text={ data.root_hash }/>
</DetailsInfoItem>
</DetailsInfoItem.Value>
<DetailsInfoItem
title="L1 gas price"
<DetailsInfoItem.Label
hint="Gas price for the batch settlement transaction on L1"
>
L1 gas price
</DetailsInfoItem.Label>
<DetailsInfoItem.Value>
<Text mr={ 1 }>{ BigNumber(data.l1_gas_price).dividedBy(WEI).toFixed() } { currencyUnits.ether }</Text>
<Text variant="secondary">({ BigNumber(data.l1_gas_price).dividedBy(WEI_IN_GWEI).toFixed() } { currencyUnits.gwei })</Text>
</DetailsInfoItem>
</DetailsInfoItem.Value>
<DetailsInfoItem
title="L2 fair gas price"
<DetailsInfoItem.Label
hint={ 'The gas price below which the "baseFee" of the batch should not fall' }
>
L2 fair gas price
</DetailsInfoItem.Label>
<DetailsInfoItem.Value>
<Text mr={ 1 }>{ BigNumber(data.l2_fair_gas_price).dividedBy(WEI).toFixed() } { currencyUnits.ether }</Text>
<Text variant="secondary">({ BigNumber(data.l2_fair_gas_price).dividedBy(WEI_IN_GWEI).toFixed() } { currencyUnits.gwei })</Text>
</DetailsInfoItem>
</DetailsInfoItem.Value>
</>
) }
</Grid>
......
......@@ -3,7 +3,7 @@ import React from 'react';
import type { ZkSyncBatch } from 'types/api/zkSyncL2';
import DetailsInfoItem from 'ui/shared/DetailsInfoItem';
import * as DetailsInfoItem from 'ui/shared/DetailsInfoItem';
import DetailsTimestamp from 'ui/shared/DetailsTimestamp';
import TxEntityL1 from 'ui/shared/entities/tx/TxEntityL1';
......@@ -23,10 +23,13 @@ interface Props {
const ZkSyncL2TxnBatchHashesInfo = ({ isLoading, data }: Props) => {
return (
<>
<DetailsInfoItem
title="Commit tx hash"
<DetailsInfoItem.Label
hint="Hash of L1 tx on which the batch was committed"
isLoading={ isLoading }
>
Commit tx hash
</DetailsInfoItem.Label>
<DetailsInfoItem.Value
flexDir="column"
alignItems="flex-start"
>
......@@ -45,12 +48,15 @@ const ZkSyncL2TxnBatchHashesInfo = ({ isLoading, data }: Props) => {
) }
</>
) : <Skeleton isLoaded={ !isLoading }>Pending</Skeleton> }
</DetailsInfoItem>
</DetailsInfoItem.Value>
<DetailsInfoItem
title="Prove tx hash"
<DetailsInfoItem.Label
hint="Hash of L1 tx on which the batch was proven"
isLoading={ isLoading }
>
Prove tx hash
</DetailsInfoItem.Label>
<DetailsInfoItem.Value
flexDir="column"
alignItems="flex-start"
>
......@@ -69,12 +75,15 @@ const ZkSyncL2TxnBatchHashesInfo = ({ isLoading, data }: Props) => {
) }
</>
) : <Skeleton isLoaded={ !isLoading }>Pending</Skeleton> }
</DetailsInfoItem>
</DetailsInfoItem.Value>
<DetailsInfoItem
title="Execute tx hash"
<DetailsInfoItem.Label
hint="Hash of L1 tx on which the batch was executed and finalized"
isLoading={ isLoading }
>
Execute tx hash
</DetailsInfoItem.Label>
<DetailsInfoItem.Value
flexDir="column"
alignItems="flex-start"
>
......@@ -93,7 +102,7 @@ const ZkSyncL2TxnBatchHashesInfo = ({ isLoading, data }: Props) => {
) }
</>
) : <Skeleton isLoaded={ !isLoading }>Pending</Skeleton> }
</DetailsInfoItem>
</DetailsInfoItem.Value>
</>
);
};
......
......@@ -2,7 +2,7 @@ import React from 'react';
import type { UserOp } from 'types/api/userOps';
import DetailsInfoItem from 'ui/shared/DetailsInfoItem';
import * as DetailsInfoItem from 'ui/shared/DetailsInfoItem';
import RawInputData from 'ui/shared/RawInputData';
import UserOpCallDataSwitch from './UserOpCallDataSwitch';
......@@ -33,12 +33,16 @@ const UserOpDecodedCallData = ({ data }: Props) => {
) : null;
return (
<DetailsInfoItem
title="Call data"
<>
<DetailsInfoItem.Label
hint="Data that’s passed to the sender for execution"
>
Call data
</DetailsInfoItem.Label>
<DetailsInfoItem.Value>
<RawInputData hex={ callData } rightSlot={ toggler }/>
</DetailsInfoItem>
</DetailsInfoItem.Value>
</>
);
};
......
......@@ -2,7 +2,7 @@ import React from 'react';
import type { UserOp } from 'types/api/userOps';
import DetailsInfoItem from 'ui/shared/DetailsInfoItem';
import * as DetailsInfoItem from 'ui/shared/DetailsInfoItem';
import LogDecodedInputData from 'ui/shared/logs/LogDecodedInputData';
import UserOpCallDataSwitch from './UserOpCallDataSwitch';
......@@ -33,14 +33,19 @@ const UserOpDecodedCallData = ({ data }: Props) => {
) : null;
return (
<DetailsInfoItem
title="Decoded call data"
<>
<DetailsInfoItem.Label
hint="Decoded call data"
>
Decoded call data
</DetailsInfoItem.Label>
<DetailsInfoItem.Value
flexDir={{ base: 'column', lg: 'row' }}
alignItems={{ base: 'flex-start', lg: 'center' }}
>
<LogDecodedInputData data={ callData } rightSlot={ toggler }/>
</DetailsInfoItem>
</DetailsInfoItem.Value>
</>
);
};
......
......@@ -15,7 +15,7 @@ import { currencyUnits } from 'lib/units';
import isCustomAppError from 'ui/shared/AppError/isCustomAppError';
import CurrencyValue from 'ui/shared/CurrencyValue';
import DataFetchAlert from 'ui/shared/DataFetchAlert';
import DetailsInfoItem from 'ui/shared/DetailsInfoItem';
import * as DetailsInfoItem from 'ui/shared/DetailsInfoItem';
import DetailsInfoItemDivider from 'ui/shared/DetailsInfoItemDivider';
import DetailsTimestamp from 'ui/shared/DetailsTimestamp';
import AddressStringOrParam from 'ui/shared/entities/address/AddressStringOrParam';
......@@ -68,78 +68,108 @@ const UserOpDetails = ({ query }: Props) => {
templateColumns={{ base: 'minmax(0, 1fr)', lg: 'minmax(min-content, 220px) minmax(0, 1fr)' }}
overflow="hidden"
>
<DetailsInfoItem
title="User operation hash"
<DetailsInfoItem.Label
hint="Unique character string assigned to every User operation"
isLoading={ isPlaceholderData }
>
User operation hash
</DetailsInfoItem.Label>
<DetailsInfoItem.Value>
<Skeleton isLoaded={ !isPlaceholderData } overflow="hidden">
<UserOpEntity hash={ data.hash } noIcon noLink noCopy={ false }/>
</Skeleton>
</DetailsInfoItem>
<DetailsInfoItem
title="Sender"
</DetailsInfoItem.Value>
<DetailsInfoItem.Label
hint="The address of the smart contract account"
isLoading={ isPlaceholderData }
>
Sender
</DetailsInfoItem.Label>
<DetailsInfoItem.Value>
<AddressStringOrParam address={ data.sender } isLoading={ isPlaceholderData }/>
</DetailsInfoItem>
<DetailsInfoItem
title="Status"
</DetailsInfoItem.Value>
<DetailsInfoItem.Label
hint="Current User operation state"
isLoading={ isPlaceholderData }
>
Status
</DetailsInfoItem.Label>
<DetailsInfoItem.Value>
<UserOpStatus status={ data.status } isLoading={ isPlaceholderData }/>
</DetailsInfoItem>
</DetailsInfoItem.Value>
{ data.revert_reason && (
<DetailsInfoItem
title="Revert reason"
<>
<DetailsInfoItem.Label
hint="The revert reason of the User operation"
isLoading={ isPlaceholderData }
>
Revert reason
</DetailsInfoItem.Label>
<DetailsInfoItem.Value
wordBreak="break-all"
whiteSpace="normal"
>
<Skeleton isLoaded={ !isPlaceholderData }>
{ data.revert_reason }
</Skeleton>
</DetailsInfoItem>
</DetailsInfoItem.Value>
</>
) }
{ data.timestamp && (
<DetailsInfoItem
title="Timestamp"
<>
<DetailsInfoItem.Label
hint="Date and time of User operation"
isLoading={ isPlaceholderData }
>
Timestamp
</DetailsInfoItem.Label>
<DetailsInfoItem.Value>
<DetailsTimestamp timestamp={ data.timestamp } isLoading={ isPlaceholderData }/>
</DetailsInfoItem>
</DetailsInfoItem.Value>
</>
) }
{ !config.UI.views.tx.hiddenFields?.tx_fee && (
<DetailsInfoItem
title="Fee"
<>
<DetailsInfoItem.Label
hint="Total User operation fee"
isLoading={ isPlaceholderData }
>
Fee
</DetailsInfoItem.Label>
<DetailsInfoItem.Value>
<CurrencyValue
value={ data.fee }
currency={ currencyUnits.ether }
isLoading={ isPlaceholderData }
/>
</DetailsInfoItem>
</DetailsInfoItem.Value>
</>
) }
<DetailsInfoItem
title="Gas limit"
<DetailsInfoItem.Label
hint="Gas limit for the User operation"
isLoading={ isPlaceholderData }
>
Gas limit
</DetailsInfoItem.Label>
<DetailsInfoItem.Value>
<Skeleton isLoaded={ !isPlaceholderData }>
{ BigNumber(data.gas).toFormat() }
</Skeleton>
</DetailsInfoItem>
<DetailsInfoItem
title="Gas used"
</DetailsInfoItem.Value>
<DetailsInfoItem.Label
hint="Actual gas amount used by the User operation"
isLoading={ isPlaceholderData }
>
Gas used
</DetailsInfoItem.Label>
<DetailsInfoItem.Value>
<Skeleton isLoaded={ !isPlaceholderData }>
{ BigNumber(data.gas_used).toFormat() }
</Skeleton>
......@@ -149,28 +179,37 @@ const UserOpDetails = ({ query }: Props) => {
value={ BigNumber(data.gas_used).dividedBy(BigNumber(data.gas)).toNumber() }
isLoading={ isPlaceholderData }
/>
</DetailsInfoItem>
<DetailsInfoItem
title="Transaction hash"
</DetailsInfoItem.Value>
<DetailsInfoItem.Label
hint="Hash of the transaction this User operation belongs to"
isLoading={ isPlaceholderData }
>
Transaction hash
</DetailsInfoItem.Label>
<DetailsInfoItem.Value>
<TxEntity hash={ data.transaction_hash } isLoading={ isPlaceholderData } noCopy={ false }/>
</DetailsInfoItem>
<DetailsInfoItem
title="Block"
</DetailsInfoItem.Value>
<DetailsInfoItem.Label
hint="Block number containing this User operation"
isLoading={ isPlaceholderData }
>
Block
</DetailsInfoItem.Label>
<DetailsInfoItem.Value>
<BlockEntity number={ data.block_number } isLoading={ isPlaceholderData }/>
</DetailsInfoItem>
<DetailsInfoItem
title="Entry point"
</DetailsInfoItem.Value>
<DetailsInfoItem.Label
hint="Contract that executes bundles of User operations"
isLoading={ isPlaceholderData }
>
Entry point
</DetailsInfoItem.Label>
<DetailsInfoItem.Value>
<AddressStringOrParam address={ data.entry_point } isLoading={ isPlaceholderData }/>
</DetailsInfoItem>
</DetailsInfoItem.Value>
{ config.features.txInterpretation.isEnabled && <UserOpDetailsActions hash={ data.hash } isUserOpDataLoading={ isPlaceholderData }/> }
......@@ -195,112 +234,158 @@ const UserOpDetails = ({ query }: Props) => {
<>
<GridItem colSpan={{ base: undefined, lg: 2 }} mt={{ base: 1, lg: 4 }}/>
<DetailsInfoItem
title="Call gas limit"
<DetailsInfoItem.Label
hint="Gas limit for execution phase"
>
Call gas limit
</DetailsInfoItem.Label>
<DetailsInfoItem.Value>
{ BigNumber(data.call_gas_limit).toFormat() }
</DetailsInfoItem>
<DetailsInfoItem
title="Verification gas limit"
</DetailsInfoItem.Value>
<DetailsInfoItem.Label
hint="Gas limit for verification phase"
>
Verification gas limit
</DetailsInfoItem.Label>
<DetailsInfoItem.Value>
{ BigNumber(data.verification_gas_limit).toFormat() }
</DetailsInfoItem>
<DetailsInfoItem
title="Pre-verification gas"
</DetailsInfoItem.Value>
<DetailsInfoItem.Label
hint="Gas to compensate the bundler"
>
Pre-verification gas
</DetailsInfoItem.Label>
<DetailsInfoItem.Value>
{ BigNumber(data.pre_verification_gas).toFormat() }
</DetailsInfoItem>
</DetailsInfoItem.Value>
{ !config.UI.views.tx.hiddenFields?.gas_fees && (
<>
<DetailsInfoItem
title="Max fee per gas"
<DetailsInfoItem.Label
hint="Maximum fee per gas "
>
Max fee per gas
</DetailsInfoItem.Label>
<DetailsInfoItem.Value>
<Text>{ BigNumber(data.max_fee_per_gas).dividedBy(WEI).toFixed() } { currencyUnits.ether } </Text>
<Text variant="secondary" whiteSpace="pre">
{ space }({ BigNumber(data.max_fee_per_gas).dividedBy(WEI_IN_GWEI).toFixed() } { currencyUnits.gwei })
</Text>
</DetailsInfoItem><DetailsInfoItem
title="Max priority fee per gas"
</DetailsInfoItem.Value>
<DetailsInfoItem.Label
hint="Maximum priority fee per gas"
>
Max priority fee per gas
</DetailsInfoItem.Label>
<DetailsInfoItem.Value>
<Text>{ BigNumber(data.max_priority_fee_per_gas).dividedBy(WEI).toFixed() } { currencyUnits.ether } </Text>
<Text variant="secondary" whiteSpace="pre">
{ space }({ BigNumber(data.max_priority_fee_per_gas).dividedBy(WEI_IN_GWEI).toFixed() } { currencyUnits.gwei })
</Text>
</DetailsInfoItem>
</DetailsInfoItem.Value>
</>
) }
<DetailsInfoItemDivider/>
{ data.aggregator && (
<DetailsInfoItem
title="Aggregator"
<>
<DetailsInfoItem.Label
hint="Helper contract to validate an aggregated signature"
>
Aggregator
</DetailsInfoItem.Label>
<DetailsInfoItem.Value>
<AddressStringOrParam address={ data.aggregator }/>
</DetailsInfoItem>
</DetailsInfoItem.Value>
</>
) }
{ data.aggregator_signature && (
<DetailsInfoItem
title="Aggregator signature"
<>
<DetailsInfoItem.Label
hint="Aggregator signature"
>
Aggregator signature
</DetailsInfoItem.Label>
<DetailsInfoItem.Value>
{ data.aggregator_signature }
</DetailsInfoItem>
</DetailsInfoItem.Value>
</>
) }
<DetailsInfoItem
title="Bundler"
<DetailsInfoItem.Label
hint="A node (block builder) that handles User operations"
>
Bundler
</DetailsInfoItem.Label>
<DetailsInfoItem.Value>
<AddressStringOrParam address={ data.bundler }/>
</DetailsInfoItem>
</DetailsInfoItem.Value>
{ data.factory && (
<DetailsInfoItem
title="Factory"
<>
<DetailsInfoItem.Label
hint="Smart contract that deploys new smart contract wallets for users"
>
Factory
</DetailsInfoItem.Label>
<DetailsInfoItem.Value>
<AddressStringOrParam address={ data.factory }/>
</DetailsInfoItem>
</DetailsInfoItem.Value>
</>
) }
{ data.paymaster && (
<DetailsInfoItem
title="Paymaster"
<>
<DetailsInfoItem.Label
hint="Contract to sponsor the gas fees for User operations"
>
Paymaster
</DetailsInfoItem.Label>
<DetailsInfoItem.Value>
<AddressStringOrParam address={ data.paymaster }/>
</DetailsInfoItem>
</DetailsInfoItem.Value>
</>
) }
<DetailsInfoItem
title="Sponsor type"
<DetailsInfoItem.Label
hint="Type of the gas fees sponsor"
>
Sponsor type
</DetailsInfoItem.Label>
<DetailsInfoItem.Value>
<UserOpSponsorType sponsorType={ data.sponsor_type }/>
</DetailsInfoItem>
</DetailsInfoItem.Value>
<DetailsInfoItemDivider/>
<DetailsInfoItem
title="Signature"
<DetailsInfoItem.Label
hint="Used to validate a User operation along with the nonce during verification"
>
Signature
</DetailsInfoItem.Label>
<DetailsInfoItem.Value
wordBreak="break-all"
whiteSpace="normal"
>
{ data.signature }
</DetailsInfoItem>
<DetailsInfoItem
title="Nonce"
</DetailsInfoItem.Value>
<DetailsInfoItem.Label
hint="Anti-replay protection; also used as the salt for first-time account creation"
>
Nonce
</DetailsInfoItem.Label>
<DetailsInfoItem.Value
wordBreak="break-all"
whiteSpace="normal"
>
{ data.nonce }
</DetailsInfoItem>
</DetailsInfoItem.Value>
<UserOpCallData data={ data }/>
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment