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,61 +92,80 @@ 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"
hint="Transaction and address of creation"
isLoading={ addressQuery.isPlaceholderData }
>
<AddressEntity
address={{ hash: data.creator_address_hash }}
truncation="constant"
noIcon
/>
<Text whiteSpace="pre"> at txn </Text>
<TxEntity hash={ data.creation_tx_hash } truncation="constant" noIcon noCopy={ false }/>
</DetailsInfoItem>
<>
<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"
noIcon
/>
<Text whiteSpace="pre"> at txn </Text>
<TxEntity hash={ data.creation_tx_hash } truncation="constant" noIcon noCopy={ false }/>
</DetailsInfoItem.Value>
</>
) }
{ data.is_contract && data.implementation_address && (
<DetailsInfoItem
title="Implementation"
hint="Implementation address of the proxy contract"
columnGap={ 1 }
>
<AddressEntity
address={{ hash: data.implementation_address, name: data.implementation_name, is_contract: true }}
isLoading={ addressQuery.isPlaceholderData }
noIcon
/>
</DetailsInfoItem>
<>
<DetailsInfoItem.Label
hint="Implementation address of the proxy contract"
>
Implementation
</DetailsInfoItem.Label>
<DetailsInfoItem.Value>
<AddressEntity
address={{ hash: data.implementation_address, name: data.implementation_name, is_contract: true }}
isLoading={ addressQuery.isPlaceholderData }
noIcon
/>
</DetailsInfoItem.Value>
</>
) }
<AddressBalance data={ data } isLoading={ addressQuery.isPlaceholderData }/>
{ data.has_tokens && (
<DetailsInfoItem
title="Tokens"
hint="All tokens in the account and total value"
alignSelf="center"
py={ 0 }
>
{ addressQuery.data ? <TokenSelect onClick={ handleCounterItemClick }/> : <Box py="6px">0</Box> }
</DetailsInfoItem>
<>
<DetailsInfoItem.Label
hint="All tokens in the account and total value"
>
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"
hint="Total net worth in USD of all tokens for the address"
alignSelf="center"
isLoading={ addressQuery.isPlaceholderData }
>
<AddressNetWorth addressData={ addressQuery.data } addressHash={ addressHash } isLoading={ addressQuery.isPlaceholderData }/>
</DetailsInfoItem>
<>
<DetailsInfoItem.Label
hint="Total net worth in USD of all tokens for the address"
isLoading={ addressQuery.isPlaceholderData }
>
Net worth
</DetailsInfoItem.Label>
<DetailsInfoItem.Value alignSelf="center">
<AddressNetWorth addressData={ addressQuery.data } addressHash={ addressHash } isLoading={ addressQuery.isPlaceholderData }/>
</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,78 +177,97 @@ const AddressDetails = ({ addressQuery, scrollRef }: Props) => {
/>
) :
0 }
</DetailsInfoItem>
</DetailsInfoItem.Value>
{ data.has_token_transfers && (
<DetailsInfoItem
title="Transfers"
hint="Number of transfers to/from this address"
isLoading={ addressQuery.isPlaceholderData || countersQuery.isPlaceholderData }
>
{ addressQuery.data ? (
<AddressCounterItem
prop="token_transfers_count"
query={ countersQuery }
address={ data.hash }
onClick={ handleCounterItemClick }
isAddressQueryLoading={ addressQuery.isPlaceholderData }
isDegradedData={ addressQuery.isDegradedData }
/>
) :
0 }
</DetailsInfoItem>
<>
<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"
query={ countersQuery }
address={ data.hash }
onClick={ handleCounterItemClick }
isAddressQueryLoading={ addressQuery.isPlaceholderData }
isDegradedData={ addressQuery.isDegradedData }
/>
) :
0 }
</DetailsInfoItem.Value>
</>
) }
{ countersQuery.data?.gas_usage_count && (
<DetailsInfoItem
title="Gas used"
hint="Gas used by the address"
isLoading={ addressQuery.isPlaceholderData || countersQuery.isPlaceholderData }
>
{ addressQuery.data ? (
<AddressCounterItem
prop="gas_usage_count"
query={ countersQuery }
address={ data.hash }
onClick={ handleCounterItemClick }
isAddressQueryLoading={ addressQuery.isPlaceholderData }
isDegradedData={ addressQuery.isDegradedData }
/>
) :
0 }
</DetailsInfoItem>
<>
<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"
query={ countersQuery }
address={ data.hash }
onClick={ handleCounterItemClick }
isAddressQueryLoading={ addressQuery.isPlaceholderData }
isDegradedData={ addressQuery.isDegradedData }
/>
) :
0 }
</DetailsInfoItem.Value>
</>
) }
{ data.has_validated_blocks && (
<DetailsInfoItem
title="Blocks validated"
hint="Number of blocks validated by this validator"
isLoading={ addressQuery.isPlaceholderData || countersQuery.isPlaceholderData }
>
{ addressQuery.data ? (
<AddressCounterItem
prop="validations_count"
query={ countersQuery }
address={ data.hash }
onClick={ handleCounterItemClick }
isAddressQueryLoading={ addressQuery.isPlaceholderData }
isDegradedData={ addressQuery.isDegradedData }
/>
) :
0 }
</DetailsInfoItem>
<>
<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"
query={ countersQuery }
address={ data.hash }
onClick={ handleCounterItemClick }
isAddressQueryLoading={ addressQuery.isPlaceholderData }
isDegradedData={ addressQuery.isDegradedData }
/>
) :
0 }
</DetailsInfoItem.Value>
</>
) }
{ data.block_number_balance_updated_at && (
<DetailsInfoItem
title="Last balance update"
hint="Block number in which the address was updated"
alignSelf="center"
py={{ base: '2px', lg: 1 }}
isLoading={ addressQuery.isPlaceholderData }
>
<BlockEntity
number={ data.block_number_balance_updated_at }
<>
<DetailsInfoItem.Label
hint="Block number in which the address was updated"
isLoading={ addressQuery.isPlaceholderData }
/>
</DetailsInfoItem>
>
Last balance update
</DetailsInfoItem.Label>
<DetailsInfoItem.Value>
<BlockEntity
number={ data.block_number_balance_updated_at }
isLoading={ addressQuery.isPlaceholderData }
/>
</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,25 +66,27 @@ 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"
isLoading={ isLoading }
>
<NativeTokenIcon boxSize={ 6 } mr={ 2 } isLoading={ isLoading }/>
<CurrencyValue
value={ data.coin_balance || '0' }
exchangeRate={ data.exchange_rate }
decimals={ String(config.chain.currency.decimals) }
currency={ currencyUnits.ether }
accuracyUsd={ 2 }
accuracy={ 8 }
flexWrap="wrap"
<>
<DetailsInfoItem.Label
hint={ `${ currencyUnits.ether } balance` }
isLoading={ isLoading }
/>
</DetailsInfoItem>
>
Balance
</DetailsInfoItem.Label>
<DetailsInfoItem.Value alignSelf="center" flexWrap="nowrap">
<NativeTokenIcon boxSize={ 6 } mr={ 2 } isLoading={ isLoading }/>
<CurrencyValue
value={ data.coin_balance || '0' }
exchangeRate={ data.exchange_rate }
decimals={ String(config.chain.currency.decimals) }
currency={ currencyUnits.ether }
accuracyUsd={ 2 }
accuracy={ 8 }
flexWrap="wrap"
isLoading={ isLoading }
/>
</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"
hint="Token name and symbol"
isLoading={ isLoading }
>
<TokenEntity
token={ data.token }
<>
<DetailsInfoItem.Label
hint="Token name and symbol"
isLoading={ isLoading }
noIcon
noCopy
/>
</DetailsInfoItem>
>
Token name
</DetailsInfoItem.Label>
<DetailsInfoItem.Value>
<TokenEntity
token={ data.token }
isLoading={ isLoading }
noIcon
noCopy
/>
</DetailsInfoItem.Value>
</>
);
}
if (data.is_contract && data.name) {
return (
<DetailsInfoItem
title="Contract name"
hint="The name found in the source code of the Contract"
isLoading={ isLoading }
>
<Skeleton isLoaded={ !isLoading }>
{ data.name }
</Skeleton>
</DetailsInfoItem>
<>
<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.Value>
</>
);
}
if (data.name) {
return (
<DetailsInfoItem
title="Validator name"
hint="The name of the validator"
isLoading={ isLoading }
>
<Skeleton isLoaded={ !isLoading }>
{ data.name }
</Skeleton>
</DetailsInfoItem>
<>
<DetailsInfoItem.Label
hint="The name of the validator"
isLoading={ isLoading }
>
Validator name
</DetailsInfoItem.Label>
<DetailsInfoItem.Value>
<Skeleton isLoaded={ !isLoading }>
{ data.name }
</Skeleton>
</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"
hint="Zero knowledge proof. Allows for quick verification of commitment"
isLoading={ isLoading }
>
<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.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.Value>
</>
) }
{ data.kzg_commitment && (
<DetailsInfoItem
title="Commitment"
hint="Commitment to the data in the blob"
isLoading={ isLoading }
>
<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.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.Value>
</>
) }
{ data.blob_data && (
<DetailsInfoItem
title="Size, bytes"
hint="Blob size in bytes"
isLoading={ isLoading }
>
<Skeleton isLoaded={ !isLoading } overflow="hidden" whiteSpace="pre-wrap" wordBreak="break-all">
{ (data.blob_data.replace('0x', '').length / 2).toLocaleString() }
</Skeleton>
</DetailsInfoItem>
<>
<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.Value>
</>
) }
{ data.blob_data && <DetailsInfoItemDivider/> }
{ data.transaction_hashes[0] && (
<DetailsInfoItem
title="Transaction hash"
hint="Hash of the transaction with this blob"
isLoading={ isLoading }
>
<TxEntity hash={ data.transaction_hashes[0].transaction_hash } isLoading={ isLoading } noIcon noCopy={ false }/>
</DetailsInfoItem>
<>
<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.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"
hint="The number of beacon withdrawals in the block"
isLoading={ isPlaceholderData }
>
<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.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.Value>
</>
) }
{ rollupFeature.isEnabled && rollupFeature.type === 'zkSync' && data.zksync && !config.UI.views.block.hiddenFields?.batch && (
<DetailsInfoItem
title="Batch"
hint="Batch number"
isLoading={ isPlaceholderData }
>
{ data.zksync.batch_number ? (
<BatchEntityL2
isLoading={ isPlaceholderData }
number={ data.zksync.batch_number }
/>
) : <Skeleton isLoaded={ !isPlaceholderData }>Pending</Skeleton> }
</DetailsInfoItem>
<>
<DetailsInfoItem.Label
hint="Batch number"
isLoading={ isPlaceholderData }
>
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"
hint="Status is the short interpretation of the batch lifecycle"
isLoading={ isPlaceholderData }
>
<VerificationSteps steps={ ZKSYNC_L2_TX_BATCH_STATUSES } currentStep={ data.zksync.status } isLoading={ isPlaceholderData }/>
</DetailsInfoItem>
<>
<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.Value>
</>
) }
{ !config.UI.views.block.hiddenFields?.miner && (
<DetailsInfoItem
title={ verificationTitle }
hint="A block producer who successfully included the block onto the blockchain"
columnGap={ 1 }
isLoading={ isPlaceholderData }
>
<AddressEntity
address={ data.miner }
<>
<DetailsInfoItem.Label
hint="A block producer who successfully included the block onto the blockchain"
isLoading={ isPlaceholderData }
/>
{ /* api doesn't return the block processing time yet */ }
{ /* <Text>{ dayjs.duration(block.minedIn, 'second').humanize(true) }</Text> */ }
</DetailsInfoItem>
>
{ verificationTitle }
</DetailsInfoItem.Label>
<DetailsInfoItem.Value>
<AddressEntity
address={ data.miner }
isLoading={ isPlaceholderData }
/>
</DetailsInfoItem.Value>
</>
) }
{ !rollupFeature.isEnabled && !totalReward.isEqualTo(ZERO) && !config.UI.views.block.hiddenFields?.total_reward && (
<DetailsInfoItem
title="Block reward"
hint={
`For each block, the ${ validatorTitle } is rewarded with a finite amount of ${ config.chain.currency.symbol || 'native token' }
<>
<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 }
>
<Skeleton isLoaded={ !isPlaceholderData }>
{ totalReward.dividedBy(WEI).toFixed() } { currencyUnits.ether }
</Skeleton>
{ rewardBreakDown }
</DetailsInfoItem>
}
isLoading={ isPlaceholderData }
>
Block reward
</DetailsInfoItem.Label>
<DetailsInfoItem.Value columnGap={ 1 }>
<Skeleton isLoaded={ !isPlaceholderData }>
{ totalReward.dividedBy(WEI).toFixed() } { currencyUnits.ether }
</Skeleton>
{ rewardBreakDown }
</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?
hint={ `Amount of distributed reward. ${ capitalize(validatorTitle) }s receive a static block reward + Tx fees + uncle fees` }
>
{ BigNumber(reward).dividedBy(WEI).toFixed() } { currencyUnits.ether }
</DetailsInfoItem>
<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.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,91 +342,105 @@ 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"
hint="The minimum gas price a transaction should have in order to be included in this block"
isLoading={ isPlaceholderData }
>
<Skeleton isLoaded={ !isPlaceholderData }>
{ BigNumber(data.minimum_gas_price).dividedBy(GWEI).toFormat() } { currencyUnits.gwei }
</Skeleton>
</DetailsInfoItem>
<>
<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.Value>
</>
) }
{ data.base_fee_per_gas && (
<DetailsInfoItem
title="Base fee per gas"
hint="Minimum fee required per unit of gas. Fee adjusts based on network congestion"
isLoading={ isPlaceholderData }
>
{ isPlaceholderData ? (
<Skeleton isLoaded={ !isPlaceholderData } h="20px" maxW="380px" w="100%"/>
) : (
<>
<Text>{ BigNumber(data.base_fee_per_gas).dividedBy(WEI).toFixed() } { currencyUnits.ether } </Text>
<Text variant="secondary" whiteSpace="pre">
{ space }({ BigNumber(data.base_fee_per_gas).dividedBy(WEI_IN_GWEI).toFixed() } { currencyUnits.gwei })
</Text>
</>
) }
</DetailsInfoItem>
<>
<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%"/>
) : (
<>
<Text>{ BigNumber(data.base_fee_per_gas).dividedBy(WEI).toFixed() } { currencyUnits.ether } </Text>
<Text variant="secondary" whiteSpace="pre">
{ space }({ BigNumber(data.base_fee_per_gas).dividedBy(WEI_IN_GWEI).toFixed() } { currencyUnits.gwei })
</Text>
</>
) }
</DetailsInfoItem.Value>
</>
) }
{ !config.UI.views.block.hiddenFields?.burnt_fees && !burntFees.isEqualTo(ZERO) && (
<DetailsInfoItem
title="Burnt fees"
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 }
>
<IconSvg name="flame" boxSize={ 5 } color="gray.500" isLoading={ isPlaceholderData }/>
<Skeleton isLoaded={ !isPlaceholderData } ml={ 2 }>
{ burntFees.dividedBy(WEI).toFixed() } { currencyUnits.ether }
</Skeleton>
{ !txFees.isEqualTo(ZERO) && (
<Tooltip label="Burnt fees / Txn fees * 100%">
<Box>
<Utilization
ml={ 4 }
value={ burntFees.dividedBy(txFees).toNumber() }
isLoading={ isPlaceholderData }
/>
</Box>
</Tooltip>
) }
</DetailsInfoItem>
{ !config.UI.views.block.hiddenFields?.burnt_fees && !burntFees.isEqualTo(ZERO) && (
<>
<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 }
</Skeleton>
{ !txFees.isEqualTo(ZERO) && (
<Tooltip label="Burnt fees / Txn fees * 100%">
<Box>
<Utilization
ml={ 4 }
value={ burntFees.dividedBy(txFees).toNumber() }
isLoading={ isPlaceholderData }
/>
</Box>
</Tooltip>
) }
</DetailsInfoItem.Value>
</>
) }
{ data.priority_fee !== null && BigNumber(data.priority_fee).gt(ZERO) && (
<DetailsInfoItem
title="Priority fee / Tip"
hint="User-defined tips sent to validator for transaction priority/inclusion"
isLoading={ isPlaceholderData }
>
<Skeleton isLoaded={ !isPlaceholderData }>
{ BigNumber(data.priority_fee).dividedBy(WEI).toFixed() } { currencyUnits.ether }
</Skeleton>
</DetailsInfoItem>
<>
<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.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,121 +469,148 @@ const BlockDetails = ({ query }: Props) => {
{ !isPlaceholderData && <BlockDetailsBlobInfo data={ data }/> }
{ data.bitcoin_merged_mining_header && (
<DetailsInfoItem
title="Bitcoin merged mining header"
hint="Merged-mining field: Bitcoin header"
flexWrap="nowrap"
alignSelf="flex-start"
>
<Box whiteSpace="nowrap" overflow="hidden">
<HashStringShortenDynamic hash={ data.bitcoin_merged_mining_header }/>
</Box>
<CopyToClipboard text={ data.bitcoin_merged_mining_header }/>
</DetailsInfoItem>
<>
<DetailsInfoItem.Label
hint="Merged-mining field: Bitcoin header"
>
Bitcoin merged mining header
</DetailsInfoItem.Label>
<DetailsInfoItem.Value
flexWrap="nowrap"
alignSelf="flex-start"
>
<Box whiteSpace="nowrap" overflow="hidden">
<HashStringShortenDynamic hash={ data.bitcoin_merged_mining_header }/>
</Box>
<CopyToClipboard text={ data.bitcoin_merged_mining_header }/>
</DetailsInfoItem.Value>
</>
) }
{ data.bitcoin_merged_mining_coinbase_transaction && (
<DetailsInfoItem
title="Bitcoin merged mining coinbase transaction"
hint="Merged-mining field: Coinbase transaction"
>
<RawDataSnippet
data={ data.bitcoin_merged_mining_coinbase_transaction }
isLoading={ isPlaceholderData }
showCopy={ false }
textareaMaxHeight="100px"
/>
</DetailsInfoItem>
<>
<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.Value>
</>
) }
{ data.bitcoin_merged_mining_merkle_proof && (
<DetailsInfoItem
title="Bitcoin merged mining Merkle proof"
hint="Merged-mining field: Merkle proof"
>
<RawDataSnippet
data={ data.bitcoin_merged_mining_merkle_proof }
isLoading={ isPlaceholderData }
showCopy={ false }
textareaMaxHeight="100px"
/>
</DetailsInfoItem>
<>
<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.Value>
</>
) }
{ data.hash_for_merged_mining && (
<DetailsInfoItem
title="Hash for merged mining"
hint="Merged-mining field: Rootstock block header hash"
flexWrap="nowrap"
alignSelf="flex-start"
>
<Box whiteSpace="nowrap" overflow="hidden">
<HashStringShortenDynamic hash={ data.hash_for_merged_mining }/>
</Box>
<CopyToClipboard text={ data.hash_for_merged_mining }/>
</DetailsInfoItem>
<>
<DetailsInfoItem.Label
hint="Merged-mining field: Rootstock block header hash"
>
Hash for merged mining
</DetailsInfoItem.Label>
<DetailsInfoItem.Value
flexWrap="nowrap"
alignSelf="flex-start"
>
<Box whiteSpace="nowrap" overflow="hidden">
<HashStringShortenDynamic hash={ data.hash_for_merged_mining }/>
</Box>
<CopyToClipboard text={ data.hash_for_merged_mining }/>
</DetailsInfoItem.Value>
</>
) }
<DetailsInfoItem
title="Difficulty"
<DetailsInfoItem.Label
hint={ `Block difficulty for ${ validatorTitle }, used to calibrate block generation time` }
>
<Box whiteSpace="nowrap" overflow="hidden">
<HashStringShortenDynamic hash={ BigNumber(data.difficulty).toFormat() }/>
</Box>
</DetailsInfoItem>
Difficulty
</DetailsInfoItem.Label>
<DetailsInfoItem.Value overflow="hidden">
<HashStringShortenDynamic hash={ BigNumber(data.difficulty).toFormat() }/>
</DetailsInfoItem.Value>
{ data.total_difficulty && (
<DetailsInfoItem
title="Total difficulty"
hint="Total difficulty of the chain until this block"
>
<Box whiteSpace="nowrap" overflow="hidden">
<>
<DetailsInfoItem.Label
hint="Total difficulty of the chain until this block"
>
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"
hint="The hash of the block from which this block was generated"
flexWrap="nowrap"
>
<LinkInternal
href={ route({ pathname: '/block/[height_or_hash]', query: { height_or_hash: String(data.height - 1) } }) }
overflow="hidden"
whiteSpace="nowrap"
<>
<DetailsInfoItem.Label
hint="The hash of the block from which this block was generated"
>
<HashStringShortenDynamic
hash={ data.parent_hash }
/>
</LinkInternal>
<CopyToClipboard text={ data.parent_hash }/>
</DetailsInfoItem>
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"
whiteSpace="nowrap"
>
<HashStringShortenDynamic
hash={ data.parent_hash }
/>
</LinkInternal>
<CopyToClipboard text={ data.parent_hash }/>
</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"
hint="Block nonce is a value used during mining to demonstrate proof of work for a block"
>
{ data.nonce }
</DetailsInfoItem>
<>
<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.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,51 +33,67 @@ const BlockDetailsBlobInfo = ({ data }: Props) => {
<>
{ data.blob_gas_price && (
<DetailsInfoItem
title="Blob gas price"
// 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."
>
<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.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.Value>
</>
) }
{ data.blob_gas_used && (
<DetailsInfoItem
title="Blob gas used"
hint="Actual amount of gas used by the blobs in this block"
>
<Text>{ BigNumber(data.blob_gas_used).toFormat() }</Text>
</DetailsInfoItem>
<>
<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.Value>
</>
) }
{ !burntBlobFees.isEqualTo(ZERO) && (
<DetailsInfoItem
title="Blob burnt fees"
hint={ `Amount of ${ currencyUnits.ether } used for blobs in this block` }
>
<IconSvg name="flame" boxSize={ 5 } color="gray.500" mr={ 2 }/>
{ burntBlobFees.dividedBy(WEI).toFixed() } { currencyUnits.ether }
{ !blobFees.isEqualTo(ZERO) && (
<Tooltip label="Blob burnt fees / Txn fees * 100%">
<div>
<Utilization ml={ 4 } value={ burntBlobFees.dividedBy(blobFees).toNumber() }/>
</div>
</Tooltip>
) }
</DetailsInfoItem>
<>
<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) && (
<Tooltip label="Blob burnt fees / Txn fees * 100%">
<div>
<Utilization ml={ 4 } value={ burntBlobFees.dividedBy(blobFees).toNumber() }/>
</div>
</Tooltip>
) }
</DetailsInfoItem.Value>
</>
) }
{ data.excess_blob_gas && (
<DetailsInfoItem
title="Excess blob gas"
hint="A running total of blob gas consumed in excess of the target, prior to the block."
>
<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.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.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,144 +30,181 @@ 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"
hint="The date the name was registered"
isLoading={ isLoading }
>
<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.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.Value>
</>
) }
{ query.data?.expiry_date && (
<DetailsInfoItem
title="Expiration date"
// 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"
>
<IconSvg name="clock" boxSize={ 5 } color="gray.500" verticalAlign="middle" isLoading={ isLoading } mr={ 2 } mt="-2px"/>
{ hasExpired && (
<>
<Skeleton isLoaded={ !isLoading } display="inline" whiteSpace="pre-wrap" lineHeight="24px">
{ dayjs(query.data.expiry_date).fromNow() }
</Skeleton>
<TextSeparator color="gray.500"/>
</>
) }
<Skeleton isLoaded={ !isLoading } display="inline" whiteSpace="pre-wrap" lineHeight="24px">
{ dayjs(query.data.expiry_date).format('llll') }
</Skeleton>
<TextSeparator color="gray.500"/>
<Skeleton isLoaded={ !isLoading } color="text_secondary" display="inline">
<NameDomainExpiryStatus date={ query.data?.expiry_date }/>
</Skeleton>
</DetailsInfoItem>
<>
<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 }
>
Expiration date
</DetailsInfoItem.Label>
<DetailsInfoItem.Value>
<IconSvg name="clock" boxSize={ 5 } color="gray.500" verticalAlign="middle" isLoading={ isLoading } mr={ 2 } mt="-2px"/>
{ hasExpired && (
<>
<Skeleton isLoaded={ !isLoading } display="inline" whiteSpace="pre-wrap" lineHeight="24px">
{ dayjs(query.data.expiry_date).fromNow() }
</Skeleton>
<TextSeparator color="gray.500"/>
</>
) }
<Skeleton isLoaded={ !isLoading } display="inline" whiteSpace="pre-wrap" lineHeight="24px">
{ dayjs(query.data.expiry_date).format('llll') }
</Skeleton>
<TextSeparator color="gray.500"/>
<Skeleton isLoaded={ !isLoading } color="text_secondary" display="inline">
<NameDomainExpiryStatus date={ query.data?.expiry_date }/>
</Skeleton>
</DetailsInfoItem.Value>
</>
) }
{ query.data?.registrant && (
<DetailsInfoItem
title="Registrant"
hint="The account that owns the domain name and has the rights to edit its ownership and records"
isLoading={ isLoading }
columnGap={ 2 }
flexWrap="nowrap"
>
<AddressEntity
address={ query.data.registrant }
<>
<DetailsInfoItem.Label
hint="The account that owns the domain name and has the rights to edit its ownership and records"
isLoading={ isLoading }
/>
<Tooltip label="Lookup for related domain names">
<LinkInternal
flexShrink={ 0 }
display="inline-flex"
href={ route({ pathname: '/name-domains', query: { owned_by: 'true', resolved_to: 'true', address: query.data.registrant.hash } }) }
>
<IconSvg name="search" boxSize={ 5 } isLoading={ isLoading }/>
</LinkInternal>
</Tooltip>
</DetailsInfoItem>
>
Registrant
</DetailsInfoItem.Label>
<DetailsInfoItem.Value
columnGap={ 2 }
flexWrap="nowrap"
>
<AddressEntity
address={ query.data.registrant }
isLoading={ isLoading }
/>
<Tooltip label="Lookup for related domain names">
<LinkInternal
flexShrink={ 0 }
display="inline-flex"
href={ route({ pathname: '/name-domains', query: { owned_by: 'true', resolved_to: 'true', address: query.data.registrant.hash } }) }
>
<IconSvg name="search" boxSize={ 5 } isLoading={ isLoading }/>
</LinkInternal>
</Tooltip>
</DetailsInfoItem.Value>
</>
) }
{ query.data?.owner && (
<DetailsInfoItem
title="Owner"
hint="The account that owns the rights to edit the records of this domain name"
isLoading={ isLoading }
columnGap={ 2 }
flexWrap="nowrap"
>
<AddressEntity
address={ query.data.owner }
<>
<DetailsInfoItem.Label
hint="The account that owns the rights to edit the records of this domain name"
isLoading={ isLoading }
/>
<Tooltip label="Lookup for related domain names">
<LinkInternal
flexShrink={ 0 }
display="inline-flex"
href={ route({ pathname: '/name-domains', query: { owned_by: 'true', resolved_to: 'true', address: query.data.owner.hash } }) }
>
<IconSvg name="search" boxSize={ 5 } isLoading={ isLoading }/>
</LinkInternal>
</Tooltip>
</DetailsInfoItem>
>
Owner
</DetailsInfoItem.Label>
<DetailsInfoItem.Value
columnGap={ 2 }
flexWrap="nowrap"
>
<AddressEntity
address={ query.data.owner }
isLoading={ isLoading }
/>
<Tooltip label="Lookup for related domain names">
<LinkInternal
flexShrink={ 0 }
display="inline-flex"
href={ route({ pathname: '/name-domains', query: { owned_by: 'true', resolved_to: 'true', address: query.data.owner.hash } }) }
>
<IconSvg name="search" boxSize={ 5 } isLoading={ isLoading }/>
</LinkInternal>
</Tooltip>
</DetailsInfoItem.Value>
</>
) }
{ query.data?.wrapped_owner && (
<DetailsInfoItem
title="Manager"
hint="Owner of this NFT domain in NameWrapper contract"
isLoading={ isLoading }
columnGap={ 2 }
flexWrap="nowrap"
>
<AddressEntity
address={ query.data.wrapped_owner }
<>
<DetailsInfoItem.Label
hint="Owner of this NFT domain in NameWrapper contract"
isLoading={ isLoading }
/>
<Tooltip label="Lookup for related domain names">
<LinkInternal
flexShrink={ 0 }
display="inline-flex"
href={ route({ pathname: '/name-domains', query: { owned_by: 'true', resolved_to: 'true', address: query.data.wrapped_owner.hash } }) }
>
<IconSvg name="search" boxSize={ 5 } isLoading={ isLoading }/>
</LinkInternal>
</Tooltip>
</DetailsInfoItem>
>
Manager
</DetailsInfoItem.Label>
<DetailsInfoItem.Value
columnGap={ 2 }
flexWrap="nowrap"
>
<AddressEntity
address={ query.data.wrapped_owner }
isLoading={ isLoading }
/>
<Tooltip label="Lookup for related domain names">
<LinkInternal
flexShrink={ 0 }
display="inline-flex"
href={ route({ pathname: '/name-domains', query: { owned_by: 'true', resolved_to: 'true', address: query.data.wrapped_owner.hash } }) }
>
<IconSvg name="search" boxSize={ 5 } isLoading={ isLoading }/>
</LinkInternal>
</Tooltip>
</DetailsInfoItem.Value>
</>
) }
{ query.data?.tokens.map((token) => (
<DetailsInfoItem
key={ token.type }
title={ token.type === 'WRAPPED_DOMAIN_TOKEN' ? 'Wrapped token ID' : 'Token ID' }
hint={ `The ${ token.type === 'WRAPPED_DOMAIN_TOKEN' ? 'wrapped ' : '' }token ID of this domain name NFT` }
isLoading={ isLoading }
wordBreak="break-all"
whiteSpace="pre-wrap"
>
<NftEntity hash={ token.contract_hash } id={ token.id } isLoading={ isLoading } noIcon/>
</DetailsInfoItem>
<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.Value>
</React.Fragment>
)) }
{ otherAddresses.length > 0 && (
<DetailsInfoItem
title="Other addresses"
hint="Other cryptocurrency addresses added to this domain name"
isLoading={ isLoading }
flexDir="column"
alignItems="flex-start"
>
{ otherAddresses.map(([ type, address ]) => (
<Flex key={ type } columnGap={ 2 } minW="0" w="100%" overflow="hidden">
<Skeleton isLoaded={ !isLoading }>{ type }</Skeleton>
<AddressEntity
address={{ hash: address }}
isLoading={ isLoading }
noLink
noIcon
/>
</Flex>
)) }
</DetailsInfoItem>
<>
<DetailsInfoItem.Label
hint="Other cryptocurrency addresses added to this domain name"
isLoading={ isLoading }
>
Other addresses
</DetailsInfoItem.Label>
<DetailsInfoItem.Value
flexDir="column"
alignItems="flex-start"
>
{ otherAddresses.map(([ type, address ]) => (
<Flex key={ type } columnGap={ 2 } minW="0" w="100%" overflow="hidden">
<Skeleton isLoaded={ !isLoading }>{ type }</Skeleton>
<AddressEntity
address={{ hash: address }}
isLoading={ isLoading }
noLink
noIcon
/>
</Flex>
)) }
</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,26 +26,32 @@ const DetailsActionsWrapper = ({ children, isLoading, type }: Props) => {
}, []);
return (
<DetailsInfoItem
title={ `${ type === 'tx' ? 'Transaction' : 'User operation' } action` }
hint={ `Highlighted events of the ${ type === 'tx' ? 'transaction' : 'user operation' }` }
note={ hasScroll ? 'Scroll to see more' : undefined }
position="relative"
isLoading={ isLoading }
>
<ContainerWithScrollY
containerId={ TX_ACTIONS_BLOCK_ID }
gradientHeight={ SCROLL_GRADIENT_HEIGHT }
hasScroll={ hasScroll }
alignItems="stretch"
rowGap={ 5 }
w="100%"
maxH="200px"
ref={ containerRef }
<>
<DetailsInfoItem.Label
hint={ `Highlighted events of the ${ type === 'tx' ? 'transaction' : 'user operation' }` }
isLoading={ isLoading }
>
{ children }
</ContainerWithScrollY>
</DetailsInfoItem>
<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 }
gradientHeight={ SCROLL_GRADIENT_HEIGHT }
hasScroll={ hasScroll }
alignItems="stretch"
rowGap={ 5 }
w="100%"
maxH="200px"
ref={ containerRef }
>
{ children }
</ContainerWithScrollY>
</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 } }}>
<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>
</Skeleton>
</Flex>
</GridItem>
<GridItem
display="flex"
alignItems="center"
flexWrap="wrap"
rowGap={ 3 }
pl={{ base: 7, lg: 0 }}
py={{ base: 1, lg: 2 }}
lineHeight={ 5 }
whiteSpace="nowrap"
{ ...styles }
>
{ children }
</GridItem>
</>
<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 } my={{ lg: '2px' }}/> }
<Skeleton isLoaded={ !isLoading } fontWeight={{ base: 700, lg: 500 }}>
{ children }
</Skeleton>
</Flex>
</GridItem>
);
};
});
interface ValueProps {
children: React.ReactNode;
className?: string;
}
export default DetailsInfoItem;
const Value = chakra(({ children, className }: ValueProps) => {
return (
<GridItem
className={ className }
display="flex"
alignItems="center"
flexWrap="wrap"
rowGap={ 3 }
pl={{ base: 7, lg: 0 }}
py={ 1 }
lineHeight={{ base: 5, lg: 6 }}
whiteSpace="nowrap"
>
{ children }
</GridItem>
);
});
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"
hint="Sponsored banner advertisement"
isLoading={ isLoading }
>
<AdBanner isLoading={ isLoading }/>
</DetailsInfoItem>
<>
<DetailsInfoItem.Label
hint="Sponsored banner advertisement"
isLoading={ isLoading }
>
Sponsored
</DetailsInfoItem.Label>
<DetailsInfoItem.Value>
<AdBanner isLoading={ isLoading }/>
</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"
hint="Price per token on the exchanges"
alignSelf="center"
isLoading={ tokenQuery.isPlaceholderData }
>
<Skeleton isLoaded={ !tokenQuery.isPlaceholderData } display="inline-block">
<span>{ `$${ Number(exchangeRate).toLocaleString(undefined, { minimumSignificantDigits: 4 }) }` }</span>
</Skeleton>
</DetailsInfoItem>
<>
<DetailsInfoItem.Label
hint="Price per token on the exchanges"
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.Value>
</>
) }
{ marketCap && (
<DetailsInfoItem
title="Fully diluted market cap"
hint="Total supply * Price"
alignSelf="center"
isLoading={ tokenQuery.isPlaceholderData }
>
<Skeleton isLoaded={ !tokenQuery.isPlaceholderData } display="inline-block">
<span>{ `$${ BigNumber(marketCap).toFormat() }` }</span>
</Skeleton>
</DetailsInfoItem>
<>
<DetailsInfoItem.Label
hint="Total supply * Price"
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.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"
hint="Number of digits that come after the decimal place when displaying token value"
alignSelf="center"
isLoading={ tokenQuery.isPlaceholderData }
>
<Skeleton isLoaded={ !tokenQuery.isPlaceholderData } minW={ 6 }>
{ decimals }
</Skeleton>
</DetailsInfoItem>
<>
<DetailsInfoItem.Label
hint="Number of digits that come after the decimal place when displaying token value"
isLoading={ tokenQuery.isPlaceholderData }
>
Decimals
</DetailsInfoItem.Label>
<DetailsInfoItem.Value>
<Skeleton isLoaded={ !tokenQuery.isPlaceholderData } minW={ 6 }>
{ decimals }
</Skeleton>
</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"
hint="Link to the dapp"
alignSelf="center"
py={ 1 }
>
<AppActionButton data={ appActionData } height="30px" source="NFT collection"/>
</DetailsInfoItem>
<>
<DetailsInfoItem.Label
hint="Link to the dapp"
>
Dapp
</DetailsInfoItem.Label>
<DetailsInfoItem.Value
py="1px"
>
<AppActionButton data={ appActionData } height="30px" source="NFT collection"/>
</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,40 +23,44 @@ const TokenNftMarketplaces = ({ hash, id, isLoading, appActionData, source, isAc
}
return (
<DetailsInfoItem
title="Marketplaces"
hint="Marketplaces trading this NFT"
alignSelf="center"
isLoading={ isLoading }
py={ (appActionData && isActionButtonExperiment) ? 1 : { base: 1, lg: 2 } }
>
<Skeleton isLoaded={ !isLoading } display="flex" columnGap={ 3 } flexWrap="wrap" alignItems="center">
{ config.UI.views.nft.marketplaces.map((item) => {
const hrefTemplate = id ? item.instance_url : item.collection_url;
const href = hrefTemplate.replace('{id}', id || '').replace('{hash}', hash || '');
return (
<Tooltip label={ `View on ${ item.name }` } key={ item.name }>
<Link href={ href } target="_blank">
<Image
src={ item.logo_url }
alt={ `${ item.name } marketplace logo` }
boxSize={ 5 }
borderRadius="full"
/>
</Link>
</Tooltip>
);
}) }
{ (appActionData && isActionButtonExperiment) && (
<>
<TextSeparator color="gray.500" margin={ 0 }/>
<AppActionButton data={ appActionData } height="30px" source={ source }/>
</>
) }
</Skeleton>
</DetailsInfoItem>
<>
<DetailsInfoItem.Label
hint="Marketplaces trading this NFT"
isLoading={ isLoading }
>
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) => {
const hrefTemplate = id ? item.instance_url : item.collection_url;
const href = hrefTemplate.replace('{id}', id || '').replace('{hash}', hash || '');
return (
<Tooltip label={ `View on ${ item.name }` } key={ item.name }>
<Link href={ href } target="_blank">
<Image
src={ item.logo_url }
alt={ `${ item.name } marketplace logo` }
boxSize={ 5 }
borderRadius="full"
/>
</Link>
</Tooltip>
);
}) }
{ (appActionData && isActionButtonExperiment) && (
<>
<TextSeparator color="gray.500" margin={ 0 }/>
<AppActionButton data={ appActionData } height="30px" source={ source }/>
</>
) }
</Skeleton>
</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"
hint="Current owner of this token instance"
isLoading={ isLoading }
>
<AddressEntity
address={ data.owner }
<>
<DetailsInfoItem.Label
hint="Current owner of this token instance"
isLoading={ isLoading }
/>
</DetailsInfoItem>
>
Owner
</DetailsInfoItem.Label>
<DetailsInfoItem.Value>
<AddressEntity
address={ data.owner }
isLoading={ isLoading }
/>
</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"
hint="Link to the dapp"
alignSelf="center"
py={ 1 }
>
<AppActionButton data={ appActionData } height="30px" source="NFT item"/>
</DetailsInfoItem>
<>
<DetailsInfoItem.Label
hint="Link to the dapp"
>
Dapp
</DetailsInfoItem.Label>
<DetailsInfoItem.Value py="1px">
<AppActionButton data={ appActionData } height="30px" source="NFT item"/>
</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"
hint="Address that deployed this token contract"
isLoading={ addressQuery.isPlaceholderData }
>
<AddressEntity
address={ creatorAddress }
<>
<DetailsInfoItem.Label
hint="Address that deployed this token contract"
isLoading={ addressQuery.isPlaceholderData }
/>
</DetailsInfoItem>
>
Creator
</DetailsInfoItem.Label>
<DetailsInfoItem.Value>
<AddressEntity
address={ creatorAddress }
isLoading={ addressQuery.isPlaceholderData }
/>
</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"
hint="NFT name"
whiteSpace="normal"
wordBreak="break-word"
isLoading={ isLoading }
>
<Skeleton isLoaded={ !isLoading }>
{ metadata.name }
</Skeleton>
</DetailsInfoItem>
<>
<DetailsInfoItem.Label
hint="NFT name"
isLoading={ isLoading }
>
Name
</DetailsInfoItem.Label>
<DetailsInfoItem.Value
whiteSpace="normal"
wordBreak="break-word"
>
<Skeleton isLoaded={ !isLoading }>
{ metadata.name }
</Skeleton>
</DetailsInfoItem.Value>
</>
) }
{ metadata?.description && (
<DetailsInfoItem
title="Description"
hint="NFT description"
whiteSpace="normal"
wordBreak="break-word"
isLoading={ isLoading }
>
<Skeleton isLoaded={ !isLoading }>
{ metadata.description }
</Skeleton>
</DetailsInfoItem>
<>
<DetailsInfoItem.Label
hint="NFT description"
isLoading={ isLoading }
>
Description
</DetailsInfoItem.Label>
<DetailsInfoItem.Value
whiteSpace="normal"
wordBreak="break-word"
>
<Skeleton isLoaded={ !isLoading }>
{ metadata.description }
</Skeleton>
</DetailsInfoItem.Value>
</>
) }
{ metadata?.attributes && (
<DetailsInfoItem
title="Attributes"
hint="NFT attributes"
whiteSpace="normal"
isLoading={ isLoading }
>
<Grid gap={ 2 } templateColumns="repeat(auto-fill,minmax(160px, 1fr))" w="100%">
{ metadata.attributes.map((attribute, index) => <Item key={ index } data={ attribute } isLoading={ isLoading }/>) }
</Grid>
</DetailsInfoItem>
<>
<DetailsInfoItem.Label
hint="NFT attributes"
isLoading={ isLoading }
>
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.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,20 +37,24 @@ const TokenInstanceTransfersCount = ({ hash, id, onClick }: Props) => {
undefined;
return (
<DetailsInfoItem
title="Transfers"
hint="Number of transfer for the token instance"
isLoading={ transfersCountQuery.isPlaceholderData }
>
<Skeleton isLoaded={ !transfersCountQuery.isPlaceholderData } display="inline-block">
<LinkInternal
href={ url }
onClick={ transfersCountQuery.data.transfers_count > 0 ? onClick : undefined }
>
{ transfersCountQuery.data.transfers_count.toLocaleString() }
</LinkInternal>
</Skeleton>
</DetailsInfoItem>
<>
<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 }
onClick={ transfersCountQuery.data.transfers_count > 0 ? onClick : undefined }
>
{ transfersCountQuery.data.transfers_count.toLocaleString() }
</LinkInternal>
</Skeleton>
</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,27 +14,31 @@ const TxAllowedPeekers = ({ items }: Props) => {
const [ isExpanded, expand ] = useBoolean(false);
return (
<DetailsInfoItem
title="Allowed peekers"
hint="Smart contracts allowed to interact with confidential data"
>
<Flex flexDir="column" rowGap={ 3 } w="100%">
{ items
.slice(0, isExpanded ? undefined : CUT_LENGTH)
.map((item) => <AddressEntity key={ item } address={{ hash: item, is_contract: true }}/>) }
</Flex>
{ items.length > CUT_LENGTH && (
<Link
display="inline-block"
fontSize="sm"
textDecorationLine="underline"
textDecorationStyle="dashed"
onClick={ expand.toggle }
>
{ isExpanded ? 'Hide' : 'Show all' }
</Link>
) }
</DetailsInfoItem>
<>
<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)
.map((item) => <AddressEntity key={ item } address={{ hash: item, is_contract: true }}/>) }
</Flex>
{ items.length > CUT_LENGTH && (
<Link
display="inline-block"
fontSize="sm"
textDecorationLine="underline"
textDecorationStyle="dashed"
onClick={ expand.toggle }
>
{ isExpanded ? 'Hide' : 'Show all' }
</Link>
) }
</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"
hint="Total transaction fee"
>
<CurrencyValue
value={ data.fee.value }
currency={ currencyUnits.ether }
flexWrap="wrap"
/>
</DetailsInfoItem>
<>
<DetailsInfoItem.Label
hint="Total transaction fee"
>
Transaction fee
</DetailsInfoItem.Label>
<DetailsInfoItem.Value>
<CurrencyValue
value={ data.fee.value }
currency={ currencyUnits.ether }
flexWrap="wrap"
/>
</DetailsInfoItem.Value>
</>
) }
<TxDetailsGasPrice gasPrice={ data.gas_price }/>
{ data.gas_limit && (
<DetailsInfoItem
title="Gas limit"
hint="Maximum amount of gas that can be used by the transaction"
>
{ BigNumber(data.gas_limit).toFormat() }
</DetailsInfoItem>
<>
<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.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"
hint="Decoded input data"
>
<LogDecodedInputData data={ data.decoded_input }/>
</DetailsInfoItem>
<>
<DetailsInfoItem.Label
hint="Decoded input data"
>
Decoded input data
</DetailsInfoItem.Label>
<DetailsInfoItem.Value>
<LogDecodedInputData data={ data.decoded_input }/>
</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,24 +30,28 @@ const TxDetailsBurntFees = ({ data, isLoading }: Props) => {
}
return (
<DetailsInfoItem
title="Burnt fees"
hint={ `
<>
<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 }
>
<IconSvg name="flame" boxSize={ 5 } color="gray.500" isLoading={ isLoading }/>
<CurrencyValue
value={ value.toString() }
currency={ currencyUnits.ether }
exchangeRate={ data.exchange_rate }
flexWrap="wrap"
ml={ 2 }
isLoading={ isLoading }
/>
</DetailsInfoItem>
>
Burnt fees
</DetailsInfoItem.Label>
<DetailsInfoItem.Value>
<IconSvg name="flame" boxSize={ 5 } color="gray.500" isLoading={ isLoading }/>
<CurrencyValue
value={ value.toString() }
currency={ currencyUnits.ether }
exchangeRate={ data.exchange_rate }
flexWrap="wrap"
ml={ 2 }
isLoading={ isLoading }
/>
</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"
hint="Fee per gas"
isLoading={ isLoading }
>
<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.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.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"
hint="Price per unit of gas specified by the sender. Higher gas prices can prioritize transaction inclusion during times of high usage"
isLoading={ isLoading }
>
<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.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.Value>
</>
);
};
......
......@@ -3,47 +3,51 @@ 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"
hint="Other data related to this transaction"
>
{
[
typeof type === 'number' && (
<Box key="type">
<Text as="span" fontWeight="500">Txn type: </Text>
<Text fontWeight="600" as="span">{ type }</Text>
{ type === 2 && <Text fontWeight="400" as="span" ml={ 1 } variant="secondary">(EIP-1559)</Text> }
{ type === 3 && <Text fontWeight="400" as="span" ml={ 1 } variant="secondary">(EIP-4844)</Text> }
</Box>
),
<Box key="nonce">
<Text as="span" fontWeight="500">Nonce: </Text>
<Text fontWeight="600" as="span">{ nonce }</Text>
</Box>,
position !== null && position !== undefined && (
<Box key="position">
<Text as="span" fontWeight="500">Position: </Text>
<Text fontWeight="600" as="span">{ position }</Text>
</Box>
),
]
.filter(Boolean)
.map((item, index) => (
<>
{ index !== 0 && <TextSeparator/> }
{ item }
</>
))
}
</DetailsInfoItem>
<>
<DetailsInfoItem.Label
hint="Other data related to this transaction"
>
Other
</DetailsInfoItem.Label>
<DetailsInfoItem.Value>
{
[
typeof type === 'number' && (
<Box key="type">
<Text as="span" fontWeight="500">Txn type: </Text>
<Text fontWeight="600" as="span">{ type }</Text>
{ type === 2 && <Text fontWeight="400" as="span" ml={ 1 } variant="secondary">(EIP-1559)</Text> }
{ type === 3 && <Text fontWeight="400" as="span" ml={ 1 } variant="secondary">(EIP-4844)</Text> }
</Box>
),
<Box key="nonce">
<Text as="span" fontWeight="500">Nonce: </Text>
<Text fontWeight="600" as="span">{ nonce }</Text>
</Box>,
position !== null && position !== undefined && (
<Box key="position">
<Text as="span" fontWeight="500">Position: </Text>
<Text fontWeight="600" as="span">{ position }</Text>
</Box>
),
]
.filter(Boolean)
.map((item, index) => (
<>
{ index !== 0 && <TextSeparator/> }
{ item }
</>
))
}
</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,22 +40,24 @@ const TxDetailsTokenTransfers = ({ data, txHash, isOverflow }: Props) => {
}
return (
<DetailsInfoItem
key={ type }
title={ title }
hint={ hint }
position="relative"
>
<Flex
flexDirection="column"
alignItems="flex-start"
rowGap={ 5 }
w="100%"
overflow="hidden"
<React.Fragment key={ type }>
<DetailsInfoItem.Label
hint={ hint }
>
{ items.map((item, index) => <TxDetailsTokenTransfer key={ index } data={ item }/>) }
</Flex>
</DetailsInfoItem>
{ title }
</DetailsInfoItem.Label>
<DetailsInfoItem.Value position="relative">
<Flex
flexDirection="column"
alignItems="flex-start"
rowGap={ 5 }
w="100%"
overflow="hidden"
>
{ items.map((item, index) => <TxDetailsTokenTransfer key={ index } data={ item }/>) }
</Flex>
</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,76 +145,102 @@ 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"
hint="Detailed status progress of the transaction"
>
<Flex flexDir="column" rowGap={ 2 }>
{ data.op_withdrawals.map((withdrawal) => (
<Box key={ withdrawal.nonce }>
<Box mb={ 2 }>
<span>Nonce: </span>
<chakra.span fontWeight={ 600 }>{ withdrawal.nonce }</chakra.span>
<>
<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 }>
<Box mb={ 2 }>
<span>Nonce: </span>
<chakra.span fontWeight={ 600 }>{ withdrawal.nonce }</chakra.span>
</Box>
<TxDetailsWithdrawalStatus
status={ withdrawal.status }
l1TxHash={ withdrawal.l1_transaction_hash }
/>
</Box>
<TxDetailsWithdrawalStatus
status={ withdrawal.status }
l1TxHash={ withdrawal.l1_transaction_hash }
/>
</Box>
)) }
</Flex>
</DetailsInfoItem>
)) }
</Flex>
</DetailsInfoItem.Value>
</>
) }
{ data.zkevm_status && !config.UI.views.tx.hiddenFields?.L1_status && (
<DetailsInfoItem
title="Confirmation status"
hint="Status of the transaction confirmation path to L1"
isLoading={ isLoading }
>
<VerificationSteps currentStep={ data.zkevm_status } steps={ ZKEVM_L2_TX_STATUSES } isLoading={ isLoading }/>
</DetailsInfoItem>
<>
<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.Value>
</>
) }
{ data.revert_reason && (
<DetailsInfoItem
title="Revert reason"
hint="The revert reason of the transaction"
>
<TxRevertReason { ...data.revert_reason }/>
</DetailsInfoItem>
<>
<DetailsInfoItem.Label
hint="The revert reason of the transaction"
>
Revert reason
</DetailsInfoItem.Label>
<DetailsInfoItem.Value>
<TxRevertReason { ...data.revert_reason }/>
</DetailsInfoItem.Value>
</>
) }
{ data.zksync && !config.UI.views.tx.hiddenFields?.L1_status && (
<DetailsInfoItem
title="L1 status"
hint="Status is the short interpretation of the batch lifecycle"
isLoading={ isLoading }
>
<VerificationSteps steps={ ZKSYNC_L2_TX_BATCH_STATUSES } currentStep={ data.zksync.status } isLoading={ isLoading }/>
</DetailsInfoItem>
<>
<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.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,77 +257,100 @@ const TxInfo = ({ data, isLoading, socketStatus }: Props) => {
</Skeleton>
</>
) }
</DetailsInfoItem>
</DetailsInfoItem.Value>
{ data.zkevm_batch_number && !config.UI.views.tx.hiddenFields?.batch && (
<DetailsInfoItem
title="Tx batch"
hint="Batch index for this transaction"
isLoading={ isLoading }
>
<BatchEntityL2
<>
<DetailsInfoItem.Label
hint="Batch index for this transaction"
isLoading={ isLoading }
number={ data.zkevm_batch_number }
/>
</DetailsInfoItem>
) }
{ data.zksync && !config.UI.views.tx.hiddenFields?.batch && (
<DetailsInfoItem
title="Batch"
hint="Batch number"
isLoading={ isLoading }
>
{ data.zksync.batch_number ? (
>
Tx batch
</DetailsInfoItem.Label>
<DetailsInfoItem.Value>
<BatchEntityL2
isLoading={ isLoading }
number={ data.zksync.batch_number }
number={ data.zkevm_batch_number }
/>
) : <Skeleton isLoaded={ !isLoading }>Pending</Skeleton> }
</DetailsInfoItem>
</DetailsInfoItem.Value>
</>
) }
{ data.zksync && !config.UI.views.tx.hiddenFields?.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.Value>
</>
) }
{ data.timestamp && (
<DetailsInfoItem
title="Timestamp"
hint="Date & time of transaction inclusion, including length of time for confirmation"
isLoading={ isLoading }
>
<DetailsTimestamp timestamp={ data.timestamp } isLoading={ isLoading }/>
{ data.confirmation_duration && (
<>
<TextSeparator color="gray.500"/>
<Skeleton isLoaded={ !isLoading } color="text_secondary">
<span>{ getConfirmationDuration(data.confirmation_duration) }</span>
</Skeleton>
</>
) }
</DetailsInfoItem>
<>
<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 && (
<>
<TextSeparator color="gray.500"/>
<Skeleton isLoaded={ !isLoading } color="text_secondary">
<span>{ getConfirmationDuration(data.confirmation_duration) }</span>
</Skeleton>
</>
) }
</DetailsInfoItem.Value>
</>
) }
{ data.execution_node && (
<DetailsInfoItem
title="Kettle"
hint="Node that carried out the confidential computation"
isLoading={ isLoading }
>
<AddressEntity
address={ data.execution_node }
href={ route({ pathname: '/txs/kettle/[hash]', query: { hash: data.execution_node.hash } }) }
/>
</DetailsInfoItem>
<>
<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.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,172 +406,216 @@ 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.Value>
</>
isLoading={ isLoading }
>
<Skeleton isLoaded={ !isLoading } overflow="hidden">
<HashStringShortenDynamic hash={ data.zkevm_sequence_hash }/>
</Skeleton>
<CopyToClipboard text={ data.zkevm_sequence_hash } isLoading={ isLoading }/>
</DetailsInfoItem>
) }
{ data.zkevm_verify_hash && (
<DetailsInfoItem
title="Verify tx hash"
flexWrap="nowrap"
isLoading={ isLoading }
>
<Skeleton isLoaded={ !isLoading } overflow="hidden">
<HashStringShortenDynamic hash={ data.zkevm_verify_hash }/>
</Skeleton>
<CopyToClipboard text={ data.zkevm_verify_hash } isLoading={ isLoading }/>
</DetailsInfoItem>
{ data.zkevm_verify_hash && (
<>
<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.Value>
</>
) }
{ (data.zkevm_batch_number || data.zkevm_verify_hash) && <DetailsInfoItemDivider/> }
{ !config.UI.views.tx.hiddenFields?.value && (
<DetailsInfoItem
title="Value"
hint="Value sent in the native token (and USD) if applicable"
isLoading={ isLoading }
>
<CurrencyValue
value={ data.value }
currency={ currencyUnits.ether }
exchangeRate={ data.exchange_rate }
<>
<DetailsInfoItem.Label
hint="Value sent in the native token (and USD) if applicable"
isLoading={ isLoading }
flexWrap="wrap"
/>
</DetailsInfoItem>
) }
{ !config.UI.views.tx.hiddenFields?.tx_fee && (
<DetailsInfoItem
title="Transaction fee"
hint={ data.blob_gas_used ? 'Transaction fee without blob fee' : 'Total transaction fee' }
isLoading={ isLoading }
>
{ data.stability_fee ? (
<TxFeeStability data={ data.stability_fee } isLoading={ isLoading }/>
) : (
>
Value
</DetailsInfoItem.Label>
<DetailsInfoItem.Value>
<CurrencyValue
value={ data.fee.value }
currency={ config.UI.views.tx.hiddenFields?.fee_currency ? '' : currencyUnits.ether }
value={ data.value }
currency={ currencyUnits.ether }
exchangeRate={ data.exchange_rate }
flexWrap="wrap"
isLoading={ isLoading }
flexWrap="wrap"
/>
) }
</DetailsInfoItem>
</DetailsInfoItem.Value>
</>
) }
{ !config.UI.views.tx.hiddenFields?.tx_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 }/>
) : (
<CurrencyValue
value={ data.fee.value }
currency={ config.UI.views.tx.hiddenFields?.fee_currency ? '' : currencyUnits.ether }
exchangeRate={ data.exchange_rate }
flexWrap="wrap"
isLoading={ isLoading }
/>
) }
</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 })` }
// eslint-disable-next-line max-len
hint={ `
Base Fee refers to the network Base Fee at the time of the block,
while Max Fee & Max Priority Fee refer to the max amount a user is willing to pay
for their tx & to give to the ${ getNetworkValidatorTitle() } respectively
` }
isLoading={ isLoading }
>
{ data.base_fee_per_gas && (
<Skeleton isLoaded={ !isLoading }>
<Text as="span" fontWeight="500">Base: </Text>
<Text fontWeight="600" as="span">{ BigNumber(data.base_fee_per_gas).dividedBy(WEI_IN_GWEI).toFixed() }</Text>
{ (data.max_fee_per_gas || data.max_priority_fee_per_gas) && <TextSeparator/> }
</Skeleton>
) }
{ data.max_fee_per_gas && (
<Skeleton isLoaded={ !isLoading }>
<Text as="span" fontWeight="500">Max: </Text>
<Text fontWeight="600" as="span">{ BigNumber(data.max_fee_per_gas).dividedBy(WEI_IN_GWEI).toFixed() }</Text>
{ data.max_priority_fee_per_gas && <TextSeparator/> }
</Skeleton>
) }
{ data.max_priority_fee_per_gas && (
<Skeleton isLoaded={ !isLoading }>
<Text as="span" fontWeight="500">Max priority: </Text>
<Text fontWeight="600" as="span">{ BigNumber(data.max_priority_fee_per_gas).dividedBy(WEI_IN_GWEI).toFixed() }</Text>
</Skeleton>
) }
</DetailsInfoItem>
<>
<DetailsInfoItem.Label
// eslint-disable-next-line max-len
hint={ `
Base Fee refers to the network Base Fee at the time of the block,
while Max Fee & Max Priority Fee refer to the max amount a user is willing to pay
for their tx & to give to the ${ getNetworkValidatorTitle() } respectively
` }
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>
<Text fontWeight="600" as="span">{ BigNumber(data.base_fee_per_gas).dividedBy(WEI_IN_GWEI).toFixed() }</Text>
{ (data.max_fee_per_gas || data.max_priority_fee_per_gas) && <TextSeparator/> }
</Skeleton>
) }
{ data.max_fee_per_gas && (
<Skeleton isLoaded={ !isLoading }>
<Text as="span" fontWeight="500">Max: </Text>
<Text fontWeight="600" as="span">{ BigNumber(data.max_fee_per_gas).dividedBy(WEI_IN_GWEI).toFixed() }</Text>
{ data.max_priority_fee_per_gas && <TextSeparator/> }
</Skeleton>
) }
{ data.max_priority_fee_per_gas && (
<Skeleton isLoaded={ !isLoading }>
<Text as="span" fontWeight="500">Max priority: </Text>
<Text fontWeight="600" as="span">{ BigNumber(data.max_priority_fee_per_gas).dividedBy(WEI_IN_GWEI).toFixed() }</Text>
</Skeleton>
) }
</DetailsInfoItem.Value>
</>
) }
<TxDetailsBurntFees data={ data } isLoading={ isLoading }/>
{ rollupFeature.isEnabled && rollupFeature.type === 'optimistic' && (
<>
{ data.l1_gas_used && (
<DetailsInfoItem
title="L1 gas used by txn"
hint="L1 gas used by transaction"
isLoading={ isLoading }
>
<Text>{ BigNumber(data.l1_gas_used).toFormat() }</Text>
</DetailsInfoItem>
<>
<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.Value>
</>
) }
{ data.l1_gas_price && (
<DetailsInfoItem
title="L1 gas price"
hint="L1 gas price"
isLoading={ isLoading }
>
<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.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.Value>
</>
) }
{ data.l1_fee && (
<DetailsInfoItem
title="L1 fee"
// 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 }
>
<CurrencyValue
value={ data.l1_fee }
currency={ currencyUnits.ether }
exchangeRate={ data.exchange_rate }
flexWrap="wrap"
/>
</DetailsInfoItem>
<>
<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.Value>
</>
) }
{ data.l1_fee_scalar && (
<DetailsInfoItem
title="L1 fee scalar"
hint="A Dynamic overhead (fee scalar) premium, which serves as a buffer in case L1 prices rapidly increase."
isLoading={ isLoading }
>
<Text>{ data.l1_fee_scalar }</Text>
</DetailsInfoItem>
<>
<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.Value>
</>
) }
</>
) }
<GridItem colSpan={{ base: undefined, lg: 2 }}>
<Element name="TxInfo__cutLink">
<Skeleton isLoaded={ !isLoading } mt={ 6 } display="inline-block">
......@@ -532,68 +631,93 @@ 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"
hint="Blob fee for this transaction"
>
<CurrencyValue
value={ BigNumber(data.blob_gas_used).multipliedBy(data.blob_gas_price).toString() }
currency={ config.UI.views.tx.hiddenFields?.fee_currency ? '' : currencyUnits.ether }
exchangeRate={ data.exchange_rate }
flexWrap="wrap"
isLoading={ isLoading }
/>
</DetailsInfoItem>
<>
<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 }
exchangeRate={ data.exchange_rate }
flexWrap="wrap"
isLoading={ isLoading }
/>
</DetailsInfoItem.Value>
</>
) }
{ data.blob_gas_used && (
<DetailsInfoItem
title="Blob gas usage"
hint="Amount of gas used by the blobs in this transaction"
>
{ BigNumber(data.blob_gas_used).toFormat() }
</DetailsInfoItem>
<>
<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.Value>
</>
) }
{ (data.max_fee_per_blob_gas || data.blob_gas_price) && (
<DetailsInfoItem
title={ `Blob gas fees (${ currencyUnits.gwei })` }
hint={ `Amount of ${ currencyUnits.ether } used for blobs in this transaction` }
>
{ data.blob_gas_price && (
<Text fontWeight="600" as="span">{ BigNumber(data.blob_gas_price).dividedBy(WEI_IN_GWEI).toFixed() }</Text>
) }
{ (data.max_fee_per_blob_gas && data.blob_gas_price) && <TextSeparator/> }
{ data.max_fee_per_blob_gas && (
<>
<Text as="span" fontWeight="500" whiteSpace="pre">Max: </Text>
<Text fontWeight="600" as="span">{ BigNumber(data.max_fee_per_blob_gas).dividedBy(WEI_IN_GWEI).toFixed() }</Text>
</>
) }
</DetailsInfoItem>
<>
<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>
) }
{ (data.max_fee_per_blob_gas && data.blob_gas_price) && <TextSeparator/> }
{ data.max_fee_per_blob_gas && (
<>
<Text as="span" fontWeight="500" whiteSpace="pre">Max: </Text>
<Text fontWeight="600" as="span">{ BigNumber(data.max_fee_per_blob_gas).dividedBy(WEI_IN_GWEI).toFixed() }</Text>
</>
) }
</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"
hint="Decoded input data"
>
<LogDecodedInputData data={ data.decoded_input }/>
</DetailsInfoItem>
<>
<DetailsInfoItem.Label
hint="Decoded input data"
>
Decoded input data
</DetailsInfoItem.Label>
<DetailsInfoItem.Value>
<LogDecodedInputData data={ data.decoded_input }/>
</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"
hint="Data that’s passed to the sender for execution"
>
<RawInputData hex={ callData } rightSlot={ toggler }/>
</DetailsInfoItem>
<>
<DetailsInfoItem.Label
hint="Data that’s passed to the sender for execution"
>
Call data
</DetailsInfoItem.Label>
<DetailsInfoItem.Value>
<RawInputData hex={ callData } rightSlot={ toggler }/>
</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"
hint="Decoded call data"
flexDir={{ base: 'column', lg: 'row' }}
alignItems={{ base: 'flex-start', lg: 'center' }}
>
<LogDecodedInputData data={ callData } rightSlot={ toggler }/>
</DetailsInfoItem>
<>
<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.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"
hint="The revert reason of the User operation"
isLoading={ isPlaceholderData }
wordBreak="break-all"
whiteSpace="normal"
>
<Skeleton isLoaded={ !isPlaceholderData }>
{ data.revert_reason }
</Skeleton>
</DetailsInfoItem>
<>
<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.Value>
</>
) }
{ data.timestamp && (
<DetailsInfoItem
title="Timestamp"
hint="Date and time of User operation"
isLoading={ isPlaceholderData }
>
<DetailsTimestamp timestamp={ data.timestamp } isLoading={ isPlaceholderData }/>
</DetailsInfoItem>
<>
<DetailsInfoItem.Label
hint="Date and time of User operation"
isLoading={ isPlaceholderData }
>
Timestamp
</DetailsInfoItem.Label>
<DetailsInfoItem.Value>
<DetailsTimestamp timestamp={ data.timestamp } isLoading={ isPlaceholderData }/>
</DetailsInfoItem.Value>
</>
) }
{ !config.UI.views.tx.hiddenFields?.tx_fee && (
<DetailsInfoItem
title="Fee"
hint="Total User operation fee"
isLoading={ isPlaceholderData }
>
<CurrencyValue
value={ data.fee }
currency={ currencyUnits.ether }
<>
<DetailsInfoItem.Label
hint="Total User operation fee"
isLoading={ isPlaceholderData }
/>
</DetailsInfoItem>
>
Fee
</DetailsInfoItem.Label>
<DetailsInfoItem.Value>
<CurrencyValue
value={ data.fee }
currency={ currencyUnits.ether }
isLoading={ isPlaceholderData }
/>
</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"
hint="Helper contract to validate an aggregated signature"
>
<AddressStringOrParam address={ data.aggregator }/>
</DetailsInfoItem>
<>
<DetailsInfoItem.Label
hint="Helper contract to validate an aggregated signature"
>
Aggregator
</DetailsInfoItem.Label>
<DetailsInfoItem.Value>
<AddressStringOrParam address={ data.aggregator }/>
</DetailsInfoItem.Value>
</>
) }
{ data.aggregator_signature && (
<DetailsInfoItem
title="Aggregator signature"
hint="Aggregator signature"
>
{ data.aggregator_signature }
</DetailsInfoItem>
<>
<DetailsInfoItem.Label
hint="Aggregator signature"
>
Aggregator signature
</DetailsInfoItem.Label>
<DetailsInfoItem.Value>
{ data.aggregator_signature }
</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"
hint="Smart contract that deploys new smart contract wallets for users"
>
<AddressStringOrParam address={ data.factory }/>
</DetailsInfoItem>
<>
<DetailsInfoItem.Label
hint="Smart contract that deploys new smart contract wallets for users"
>
Factory
</DetailsInfoItem.Label>
<DetailsInfoItem.Value>
<AddressStringOrParam address={ data.factory }/>
</DetailsInfoItem.Value>
</>
) }
{ data.paymaster && (
<DetailsInfoItem
title="Paymaster"
hint="Contract to sponsor the gas fees for User operations"
>
<AddressStringOrParam address={ data.paymaster }/>
</DetailsInfoItem>
<>
<DetailsInfoItem.Label
hint="Contract to sponsor the gas fees for User operations"
>
Paymaster
</DetailsInfoItem.Label>
<DetailsInfoItem.Value>
<AddressStringOrParam address={ data.paymaster }/>
</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