mirror of
https://github.com/luxfi/explore.git
synced 2026-09-24 00:28:18 +00:00
960 lines
36 KiB
TypeScript
960 lines
36 KiB
TypeScript
import BigNumber from 'bignumber.js';
|
|
import React from 'react';
|
|
|
|
import type * as tac from '@luxfi/tac-operation-lifecycle-types';
|
|
import { SCROLL_L2_BLOCK_STATUSES } from 'types/api/scrollL2';
|
|
import type { Transaction } from 'types/api/transaction';
|
|
import { ZKEVM_L2_TX_STATUSES } from 'types/api/transaction';
|
|
import { ZKSYNC_L2_TX_BATCH_STATUSES } from 'types/api/zkSyncL2';
|
|
|
|
import { route } from 'nextjs-routes';
|
|
|
|
import config from 'configs/app';
|
|
import useApiQuery from 'lib/api/useApiQuery';
|
|
import useIsMobile from 'lib/hooks/useIsMobile';
|
|
import getNetworkValidatorTitle from 'lib/networks/getNetworkValidatorTitle';
|
|
import * as arbitrum from 'lib/rollups/arbitrum';
|
|
import { formatZkEvmTxStatus, formatZkSyncL2TxnBatchStatus, layerLabels } from 'lib/rollups/utils';
|
|
import getConfirmationDuration from 'lib/tx/getConfirmationDuration';
|
|
import { currencyUnits } from 'lib/units';
|
|
import { Badge } from '@luxfi/ui/badge';
|
|
import { CollapsibleDetails } from '@luxfi/ui/collapsible';
|
|
import { Link } from 'toolkit/next/link';
|
|
import { Skeleton } from '@luxfi/ui/skeleton';
|
|
import { Tooltip } from '@luxfi/ui/tooltip';
|
|
import CopyToClipboard from 'ui/shared/CopyToClipboard';
|
|
import * as DetailedInfo from 'ui/shared/DetailedInfo/DetailedInfo';
|
|
import DetailedInfoNativeCoinValue from 'ui/shared/DetailedInfo/DetailedInfoNativeCoinValue';
|
|
import DetailedInfoSponsoredItem from 'ui/shared/DetailedInfo/DetailedInfoSponsoredItem';
|
|
import DetailedInfoTimestamp from 'ui/shared/DetailedInfo/DetailedInfoTimestamp';
|
|
import AddressEntity from 'ui/shared/entities/address/AddressEntity';
|
|
import AddressEntityInterop from 'ui/shared/entities/address/AddressEntityInterop';
|
|
import BatchEntityL2 from 'ui/shared/entities/block/BatchEntityL2';
|
|
import BlockEntity from 'ui/shared/entities/block/BlockEntity';
|
|
import TxEntityL1 from 'ui/shared/entities/tx/TxEntityL1';
|
|
import HashStringShortenDynamic from 'ui/shared/HashStringShortenDynamic';
|
|
import IconSvg from 'ui/shared/IconSvg';
|
|
import LogDecodedInputData from 'ui/shared/logs/LogDecodedInputData';
|
|
import RawInputData from 'ui/shared/RawInputData';
|
|
import StatusTag from 'ui/shared/statusTag/StatusTag';
|
|
import TxStatus from 'ui/shared/statusTag/TxStatus';
|
|
import TextSeparator from 'ui/shared/TextSeparator';
|
|
import Utilization from 'ui/shared/Utilization/Utilization';
|
|
import GasPriceValue from 'ui/shared/value/GasPriceValue';
|
|
import NativeCoinValue from 'ui/shared/value/NativeCoinValue';
|
|
import VerificationSteps from 'ui/shared/verificationSteps/VerificationSteps';
|
|
import TxDetailsActions from 'ui/tx/details/txDetailsActions/TxDetailsActions';
|
|
import TxDetailsBurntFees from 'ui/tx/details/TxDetailsBurntFees';
|
|
import TxDetailsFeePerGas from 'ui/tx/details/TxDetailsFeePerGas';
|
|
import TxDetailsGasPrice from 'ui/tx/details/TxDetailsGasPrice';
|
|
import TxDetailsOther from 'ui/tx/details/TxDetailsOther';
|
|
import TxDetailsTokenTransfers from 'ui/tx/details/TxDetailsTokenTransfers';
|
|
import TxDetailsWithdrawalStatusOptimistic from 'ui/tx/details/TxDetailsWithdrawalStatusOptimistic';
|
|
import TxRevertReason from 'ui/tx/details/TxRevertReason';
|
|
import TxAllowedPeekers from 'ui/tx/TxAllowedPeekers';
|
|
import TxExternalTxs from 'ui/tx/TxExternalTxs';
|
|
import TxSocketAlert from 'ui/tx/TxSocketAlert';
|
|
import ZkSyncL2TxnBatchHashesInfo from 'ui/txnBatches/zkSyncL2/ZkSyncL2TxnBatchHashesInfo';
|
|
|
|
import TxDetailsCrossChainMessages from './TxDetailsCrossChainMessages';
|
|
import TxDetailsCrossChainTransfers from './TxDetailsCrossChainTransfers';
|
|
import TxDetailsGasUsage from './TxDetailsGasUsage';
|
|
import TxDetailsInterop from './TxDetailsInterop';
|
|
import TxDetailsSetMaxGasLimit from './TxDetailsSetMaxGasLimit';
|
|
import TxDetailsTacOperation from './TxDetailsTacOperation';
|
|
import TxDetailsTxFee from './TxDetailsTxFee';
|
|
import TxDetailsWithdrawalStatusArbitrum from './TxDetailsWithdrawalStatusArbitrum';
|
|
import TxInfoScrollFees from './TxInfoScrollFees';
|
|
|
|
interface Props {
|
|
data: Transaction | undefined;
|
|
tacOperations?: Array<tac.OperationDetails>;
|
|
isLoading: boolean;
|
|
socketStatus?: 'close' | 'error';
|
|
noTxActions?: boolean;
|
|
}
|
|
|
|
const externalTxFeature = config.features.externalTxs;
|
|
const rollupFeature = config.features.rollup;
|
|
|
|
const TxInfo = ({ data, tacOperations, isLoading, socketStatus, noTxActions }: Props) => {
|
|
const [ isExpanded, setIsExpanded ] = React.useState(false);
|
|
|
|
const isMobile = useIsMobile();
|
|
|
|
const externalTxsQuery = useApiQuery('general:tx_external_transactions', {
|
|
pathParams: {
|
|
hash: data?.hash,
|
|
},
|
|
queryOptions: {
|
|
enabled: externalTxFeature.isEnabled,
|
|
placeholderData: [ '1', '2', '3' ],
|
|
},
|
|
});
|
|
|
|
const handleCutLinkClick = React.useCallback(() => {
|
|
setIsExpanded((flag) => !flag);
|
|
}, []);
|
|
|
|
const showAssociatedL1Tx = React.useCallback(() => {
|
|
setIsExpanded(true);
|
|
}, []);
|
|
|
|
if (!data) {
|
|
return null;
|
|
}
|
|
|
|
const addressFromTags = [
|
|
...data.from.private_tags || [],
|
|
...data.from.public_tags || [],
|
|
...data.from.watchlist_names || [],
|
|
].map((tag) => <Badge key={ tag.label }>{ tag.display_name }</Badge>);
|
|
|
|
const toAddress = data.to ? data.to : data.created_contract;
|
|
const addressToTags = [
|
|
...toAddress?.private_tags || [],
|
|
...toAddress?.public_tags || [],
|
|
...toAddress?.watchlist_names || [],
|
|
].map((tag) => <Badge key={ tag.label }>{ tag.display_name }</Badge>);
|
|
|
|
const executionSuccessBadge = toAddress?.is_contract && data.result === 'success' ? (
|
|
<Tooltip content="Contract execution completed">
|
|
<span>
|
|
<IconSvg name="status/success" className="text-[var(--color-blackAlpha-800)] dark:text-[var(--color-whiteAlpha-800)]"/>
|
|
</span>
|
|
</Tooltip>
|
|
) : null;
|
|
const executionFailedBadge = toAddress?.is_contract && Boolean(data.status) && data.result !== 'success' ? (
|
|
<Tooltip content="Error occurred during contract execution">
|
|
<span>
|
|
<IconSvg name="status/error" className="text-[var(--color-text-error)]"/>
|
|
</span>
|
|
</Tooltip>
|
|
) : null;
|
|
|
|
const hasInterop = rollupFeature.isEnabled && rollupFeature.interopEnabled && data.op_interop_messages && data.op_interop_messages.length > 0;
|
|
|
|
return (
|
|
<DetailedInfo.Container>
|
|
|
|
{ config.features.metasuites.isEnabled && (
|
|
<>
|
|
<p id="meta-suites__tx-info-label" data-status={ data.status } data-ready={ !isLoading }/>
|
|
<p id="meta-suites__tx-info-value"/>
|
|
<div id="meta-suites__details-info-item-divider" className="col-span-1 lg:col-span-2 mt-2 lg:mt-3 mb-0 lg:mb-3 border-b border-[var(--color-border-divider)]"/>
|
|
</>
|
|
) }
|
|
|
|
{ socketStatus && (
|
|
<div className="lg:col-span-2">
|
|
<TxSocketAlert status={ socketStatus }/>
|
|
</div>
|
|
) }
|
|
|
|
{ tacOperations && tacOperations.length > 0 && <TxDetailsTacOperation tacOperations={ tacOperations } isLoading={ isLoading } txHash={ data.hash }/> }
|
|
|
|
{ data.op_interop_messages ? data.op_interop_messages.map((message) => (
|
|
<TxDetailsInterop key={ message.nonce } data={ message } isLoading={ isLoading }/>
|
|
)) : null }
|
|
|
|
{ config.features.crossChainTxs.isEnabled && <TxDetailsCrossChainMessages hash={ data.hash } isLoading={ isLoading }/> }
|
|
|
|
<DetailedInfo.ItemLabel
|
|
hint="Unique character string (TxID) assigned to every verified transaction"
|
|
isLoading={ isLoading }
|
|
>
|
|
Transaction hash
|
|
</DetailedInfo.ItemLabel>
|
|
<DetailedInfo.ItemValue multiRow={ config.features.externalTxs.isEnabled && externalTxsQuery.data && externalTxsQuery.data.length > 0 }>
|
|
<div>
|
|
{ data.status === null && <div className="animate-spin rounded-full border-2 border-current border-t-transparent h-4 w-4 mr-2 shrink-0"/> }
|
|
<Skeleton loading={ isLoading } className="overflow-hidden">
|
|
<HashStringShortenDynamic hash={ data.hash }/>
|
|
</Skeleton>
|
|
<CopyToClipboard text={ data.hash } isLoading={ isLoading }/>
|
|
{ config.features.metasuites.isEnabled && (
|
|
<>
|
|
<TextSeparator id="meta-suites__tx-explorer-separator"/>
|
|
<div id="meta-suites__tx-explorer-link"/>
|
|
</>
|
|
) }
|
|
</div>
|
|
{ config.features.externalTxs.isEnabled && externalTxsQuery.data && externalTxsQuery.data.length > 0 && (
|
|
<Skeleton loading={ isLoading || externalTxsQuery.isPlaceholderData } className="block lg:inline-flex lg:items-center">
|
|
{ !isMobile && <TextSeparator/> }
|
|
<TxExternalTxs data={ externalTxsQuery.data }/>
|
|
</Skeleton>
|
|
) }
|
|
</DetailedInfo.ItemValue>
|
|
|
|
<DetailedInfo.ItemLabel
|
|
hint="Current transaction state: Success, Failed (Error), or Pending (In Process)"
|
|
isLoading={ isLoading }
|
|
>
|
|
{
|
|
rollupFeature.isEnabled &&
|
|
(rollupFeature.type === 'zkEvm' || rollupFeature.type === 'zkSync' || rollupFeature.type === 'arbitrum' || rollupFeature.type === 'scroll') ?
|
|
`${ layerLabels.current } status and method` :
|
|
'Status and method'
|
|
}
|
|
</DetailedInfo.ItemLabel>
|
|
<DetailedInfo.ItemValue>
|
|
<TxStatus status={ data.status } errorText={ data.status === 'error' ? data.result : undefined } isLoading={ isLoading }/>
|
|
{ data.method && (
|
|
<Badge colorPalette={ data.method === 'Multicall' ? 'teal' : 'gray' } loading={ isLoading } truncated className="ml-3">
|
|
{ data.method }
|
|
</Badge>
|
|
) }
|
|
{ data.arbitrum?.contains_message && (
|
|
<Skeleton loading={ isLoading } onClick={ showAssociatedL1Tx }>
|
|
<Link className="truncate ml-3">
|
|
{ data.arbitrum?.contains_message === 'incoming' ? 'Incoming message' : 'Outgoing message' }
|
|
</Link>
|
|
</Skeleton>
|
|
) }
|
|
</DetailedInfo.ItemValue>
|
|
|
|
{ rollupFeature.isEnabled && rollupFeature.type === 'optimistic' && data.op_withdrawals && data.op_withdrawals.length > 0 &&
|
|
!config.UI.views.tx.hiddenFields?.L1_status && (
|
|
<>
|
|
<DetailedInfo.ItemLabel
|
|
hint="Detailed status progress of the transaction"
|
|
>
|
|
Withdrawal status
|
|
</DetailedInfo.ItemLabel>
|
|
<DetailedInfo.ItemValue>
|
|
<div>
|
|
{ data.op_withdrawals.map((withdrawal) => (
|
|
<div key={ withdrawal.nonce }>
|
|
<div>
|
|
<span>Nonce: </span>
|
|
<span>{ withdrawal.nonce }</span>
|
|
</div>
|
|
<TxDetailsWithdrawalStatusOptimistic data={ withdrawal } txHash={ data.hash } from={ data.from }/>
|
|
</div>
|
|
)) }
|
|
</div>
|
|
</DetailedInfo.ItemValue>
|
|
</>
|
|
) }
|
|
|
|
{ data.zkevm_status && !config.UI.views.tx.hiddenFields?.L1_status && (
|
|
<>
|
|
<DetailedInfo.ItemLabel
|
|
hint={ `Status of the transaction confirmation path to ${ layerLabels.parent }` }
|
|
isLoading={ isLoading }
|
|
>
|
|
Confirmation status
|
|
</DetailedInfo.ItemLabel>
|
|
<DetailedInfo.ItemValue>
|
|
<VerificationSteps
|
|
currentStep={ formatZkEvmTxStatus(data.zkevm_status) }
|
|
steps={ ZKEVM_L2_TX_STATUSES.map(formatZkEvmTxStatus) }
|
|
isLoading={ isLoading }
|
|
/>
|
|
</DetailedInfo.ItemValue>
|
|
</>
|
|
) }
|
|
|
|
{ data.arbitrum?.status && !config.UI.views.tx.hiddenFields?.L1_status && (
|
|
<>
|
|
<DetailedInfo.ItemLabel
|
|
hint={ `Status of the transaction confirmation path to ${ layerLabels.parent }` }
|
|
isLoading={ isLoading }
|
|
>
|
|
{ layerLabels.parent } status
|
|
</DetailedInfo.ItemLabel>
|
|
<DetailedInfo.ItemValue>
|
|
<VerificationSteps
|
|
currentStep={ arbitrum.VERIFICATION_STEPS_MAP[data.arbitrum.status] }
|
|
currentStepPending={ arbitrum.getVerificationStepStatus(data.arbitrum) === 'pending' }
|
|
steps={ arbitrum.verificationSteps }
|
|
isLoading={ isLoading }
|
|
/>
|
|
</DetailedInfo.ItemValue>
|
|
</>
|
|
) }
|
|
|
|
{ data.revert_reason && (
|
|
<>
|
|
<DetailedInfo.ItemLabel
|
|
hint="The revert reason of the transaction"
|
|
>
|
|
Revert reason
|
|
</DetailedInfo.ItemLabel>
|
|
<DetailedInfo.ItemValue>
|
|
<TxRevertReason { ...data.revert_reason }/>
|
|
</DetailedInfo.ItemValue>
|
|
</>
|
|
) }
|
|
|
|
{ data.zksync && !config.UI.views.tx.hiddenFields?.L1_status && (
|
|
<>
|
|
<DetailedInfo.ItemLabel
|
|
hint="Status is the short interpretation of the batch lifecycle"
|
|
isLoading={ isLoading }
|
|
>
|
|
{ layerLabels.parent } status
|
|
</DetailedInfo.ItemLabel>
|
|
<DetailedInfo.ItemValue>
|
|
<VerificationSteps
|
|
steps={ ZKSYNC_L2_TX_BATCH_STATUSES.map(formatZkSyncL2TxnBatchStatus) }
|
|
currentStep={ formatZkSyncL2TxnBatchStatus(data.zksync.status) }
|
|
isLoading={ isLoading }
|
|
/>
|
|
</DetailedInfo.ItemValue>
|
|
</>
|
|
) }
|
|
|
|
<DetailedInfo.ItemLabel
|
|
hint="Block number containing the transaction"
|
|
isLoading={ isLoading }
|
|
>
|
|
Block
|
|
</DetailedInfo.ItemLabel>
|
|
<DetailedInfo.ItemValue multiRow={ Boolean(data.scroll?.l2_block_status) }>
|
|
{ data.block_number === null ?
|
|
<span>Pending</span> : (
|
|
<BlockEntity
|
|
isLoading={ isLoading }
|
|
number={ data.block_number }
|
|
noIcon
|
|
/>
|
|
) }
|
|
{ Boolean(data.confirmations) && (
|
|
<>
|
|
<TextSeparator/>
|
|
<Skeleton loading={ isLoading } color="text.secondary">
|
|
<span>{ data.confirmations } Block confirmations</span>
|
|
</Skeleton>
|
|
</>
|
|
) }
|
|
{ data.scroll?.l2_block_status && (
|
|
<>
|
|
<TextSeparator/>
|
|
<VerificationSteps steps={ SCROLL_L2_BLOCK_STATUSES } currentStep={ data.scroll.l2_block_status } isLoading={ isLoading }/>
|
|
</>
|
|
) }
|
|
</DetailedInfo.ItemValue>
|
|
|
|
{ data.zkevm_batch_number && !config.UI.views.tx.hiddenFields?.batch && (
|
|
<>
|
|
<DetailedInfo.ItemLabel
|
|
hint="Batch index for this transaction"
|
|
isLoading={ isLoading }
|
|
>
|
|
Txn batch
|
|
</DetailedInfo.ItemLabel>
|
|
<DetailedInfo.ItemValue>
|
|
<BatchEntityL2
|
|
isLoading={ isLoading }
|
|
number={ data.zkevm_batch_number }
|
|
/>
|
|
</DetailedInfo.ItemValue>
|
|
</>
|
|
) }
|
|
|
|
{ data.zksync && !config.UI.views.tx.hiddenFields?.batch && (
|
|
<>
|
|
<DetailedInfo.ItemLabel
|
|
hint="Batch number"
|
|
isLoading={ isLoading }
|
|
>
|
|
Batch
|
|
</DetailedInfo.ItemLabel>
|
|
<DetailedInfo.ItemValue>
|
|
{ data.zksync.batch_number ? (
|
|
<BatchEntityL2
|
|
isLoading={ isLoading }
|
|
number={ data.zksync.batch_number }
|
|
/>
|
|
) : <Skeleton loading={ isLoading }>Pending</Skeleton> }
|
|
</DetailedInfo.ItemValue>
|
|
</>
|
|
) }
|
|
|
|
{ data.arbitrum && !config.UI.views.tx.hiddenFields?.batch && (
|
|
<>
|
|
<DetailedInfo.ItemLabel
|
|
hint="Index of the batch containing this transaction"
|
|
isLoading={ isLoading }
|
|
>
|
|
Batch
|
|
</DetailedInfo.ItemLabel>
|
|
<DetailedInfo.ItemValue>
|
|
{ data.arbitrum.batch_number ?
|
|
<BatchEntityL2 isLoading={ isLoading } number={ data.arbitrum.batch_number }/> :
|
|
<Skeleton loading={ isLoading }>Pending</Skeleton> }
|
|
</DetailedInfo.ItemValue>
|
|
</>
|
|
) }
|
|
|
|
{ data.timestamp && (
|
|
<>
|
|
<DetailedInfo.ItemLabel
|
|
hint="Date & time of transaction inclusion, including length of time for confirmation"
|
|
isLoading={ isLoading }
|
|
>
|
|
Timestamp
|
|
</DetailedInfo.ItemLabel>
|
|
<DetailedInfo.ItemValue multiRow>
|
|
<DetailedInfoTimestamp timestamp={ data.timestamp } isLoading={ isLoading }/>
|
|
{ data.confirmation_duration && (
|
|
<div>
|
|
<TextSeparator className="hidden lg:block"/>
|
|
<Skeleton loading={ isLoading } color="text.secondary">
|
|
<span>{ getConfirmationDuration(data.confirmation_duration) }</span>
|
|
</Skeleton>
|
|
</div>
|
|
) }
|
|
</DetailedInfo.ItemValue>
|
|
</>
|
|
) }
|
|
|
|
{ data.execution_node && (
|
|
<>
|
|
<DetailedInfo.ItemLabel
|
|
hint="Node that carried out the confidential computation"
|
|
isLoading={ isLoading }
|
|
>
|
|
Kettle
|
|
</DetailedInfo.ItemLabel>
|
|
<DetailedInfo.ItemValue>
|
|
<AddressEntity
|
|
address={ data.execution_node }
|
|
href={ route({ pathname: '/txs/kettle/[hash]', query: { hash: data.execution_node.hash } }) }
|
|
/>
|
|
</DetailedInfo.ItemValue>
|
|
</>
|
|
) }
|
|
|
|
{ data.allowed_peekers && data.allowed_peekers.length > 0 && (
|
|
<TxAllowedPeekers items={ data.allowed_peekers }/>
|
|
) }
|
|
|
|
<DetailedInfoSponsoredItem isLoading={ isLoading }/>
|
|
|
|
<DetailedInfo.ItemDivider/>
|
|
|
|
{ !noTxActions && <TxDetailsActions hash={ data.hash } actions={ data.actions } isTxDataLoading={ isLoading }/> }
|
|
|
|
<DetailedInfo.ItemLabel
|
|
hint="Address (external or contract) sending the transaction"
|
|
isLoading={ isLoading }
|
|
>
|
|
From
|
|
</DetailedInfo.ItemLabel>
|
|
<DetailedInfo.ItemValue>
|
|
<AddressEntity
|
|
address={ data.from }
|
|
isLoading={ isLoading }
|
|
/>
|
|
{ data.from.name && <span>{ data.from.name }</span> }
|
|
{ addressFromTags.length > 0 && (
|
|
<div>
|
|
{ addressFromTags }
|
|
</div>
|
|
) }
|
|
</DetailedInfo.ItemValue>
|
|
|
|
<DetailedInfo.ItemLabel
|
|
hint="Address (external or contract) receiving the transaction"
|
|
isLoading={ isLoading }
|
|
>
|
|
{ data.to?.is_contract ? 'Interacted with contract' : 'To' }
|
|
</DetailedInfo.ItemLabel>
|
|
<DetailedInfo.ItemValue
|
|
>
|
|
{ toAddress ? (
|
|
<>
|
|
{ data.to && data.to.hash ? (
|
|
<div>
|
|
<AddressEntity
|
|
address={ toAddress }
|
|
isLoading={ isLoading }
|
|
/>
|
|
{ executionSuccessBadge }
|
|
{ executionFailedBadge }
|
|
</div>
|
|
) : (
|
|
<div className="w-full">
|
|
<span>[Contract </span>
|
|
<AddressEntity
|
|
address={ toAddress }
|
|
isLoading={ isLoading }
|
|
noIcon
|
|
/>
|
|
<span>created]</span>
|
|
{ executionSuccessBadge }
|
|
{ executionFailedBadge }
|
|
</div>
|
|
) }
|
|
{ addressToTags.length > 0 && (
|
|
<div>
|
|
{ addressToTags }
|
|
</div>
|
|
) }
|
|
</>
|
|
) : (
|
|
<span>[ Contract creation ]</span>
|
|
) }
|
|
</DetailedInfo.ItemValue>
|
|
|
|
{ data.token_transfers && <TxDetailsTokenTransfers data={ data.token_transfers } txHash={ data.hash } isOverflow={ data.token_transfers_overflow }/> }
|
|
|
|
{ config.features.crossChainTxs.isEnabled && <TxDetailsCrossChainTransfers hash={ data.hash } isLoading={ isLoading }/> }
|
|
|
|
{ hasInterop && data.op_interop_messages?.some(message => message.target_address_hash) && (
|
|
<>
|
|
<DetailedInfo.ItemLabel
|
|
isLoading={ isLoading }
|
|
hint="The target address where this cross-chain transaction is executed"
|
|
>
|
|
Interop target
|
|
</DetailedInfo.ItemLabel>
|
|
<DetailedInfo.ItemValue>
|
|
<div>
|
|
{ data.op_interop_messages
|
|
.filter((message) => message.target_address_hash)
|
|
.map((message) => {
|
|
return message.relay_chain !== undefined ? (
|
|
<AddressEntityInterop
|
|
chain={ message.relay_chain }
|
|
address={{ hash: message.target_address_hash }}
|
|
isLoading={ isLoading }
|
|
truncation="dynamic"
|
|
className="w-full"
|
|
/>
|
|
) : (
|
|
<AddressEntity address={{ hash: message.target_address_hash }} isLoading={ isLoading } truncation="dynamic" className="w-full"/>
|
|
);
|
|
}) }
|
|
</div>
|
|
</DetailedInfo.ItemValue>
|
|
</>
|
|
) }
|
|
|
|
<DetailedInfo.ItemDivider/>
|
|
|
|
{ (data.arbitrum?.commitment_transaction.hash || data.arbitrum?.confirmation_transaction.hash) &&
|
|
(
|
|
<>
|
|
{ data.arbitrum?.commitment_transaction.hash && (
|
|
<>
|
|
<DetailedInfo.ItemLabel
|
|
hint={ `${ layerLabels.parent } transaction containing this batch commitment` }
|
|
isLoading={ isLoading }
|
|
>
|
|
Commitment tx
|
|
</DetailedInfo.ItemLabel>
|
|
<DetailedInfo.ItemValue>
|
|
<TxEntityL1 hash={ data.arbitrum?.commitment_transaction.hash } isLoading={ isLoading }/>
|
|
{ data.arbitrum?.commitment_transaction.status === 'finalized' && <StatusTag type="ok" text="Finalized" className="ml-2"/> }
|
|
</DetailedInfo.ItemValue>
|
|
</>
|
|
) }
|
|
{ data.arbitrum?.confirmation_transaction.hash && (
|
|
<>
|
|
<DetailedInfo.ItemLabel
|
|
hint={ `${ layerLabels.parent } transaction containing confirmation of this batch` }
|
|
isLoading={ isLoading }
|
|
>
|
|
Confirmation tx
|
|
</DetailedInfo.ItemLabel>
|
|
<DetailedInfo.ItemValue>
|
|
<TxEntityL1 hash={ data.arbitrum?.confirmation_transaction.hash } isLoading={ isLoading }/>
|
|
{ data.arbitrum?.commitment_transaction.status === 'finalized' && <StatusTag type="ok" text="Finalized" className="ml-2"/> }
|
|
</DetailedInfo.ItemValue>
|
|
</>
|
|
) }
|
|
<DetailedInfo.ItemDivider/>
|
|
</>
|
|
) }
|
|
|
|
{ data.zkevm_sequence_hash && (
|
|
<>
|
|
<DetailedInfo.ItemLabel
|
|
isLoading={ isLoading }
|
|
>
|
|
Sequence tx hash
|
|
</DetailedInfo.ItemLabel>
|
|
<DetailedInfo.ItemValue>
|
|
<Skeleton loading={ isLoading } className="overflow-hidden">
|
|
<HashStringShortenDynamic hash={ data.zkevm_sequence_hash }/>
|
|
</Skeleton>
|
|
<CopyToClipboard text={ data.zkevm_sequence_hash } isLoading={ isLoading }/>
|
|
</DetailedInfo.ItemValue>
|
|
</>
|
|
|
|
) }
|
|
|
|
{ data.zkevm_verify_hash && (
|
|
<>
|
|
<DetailedInfo.ItemLabel
|
|
isLoading={ isLoading }
|
|
>
|
|
Verify tx hash
|
|
</DetailedInfo.ItemLabel>
|
|
<DetailedInfo.ItemValue>
|
|
<Skeleton loading={ isLoading } className="overflow-hidden">
|
|
<HashStringShortenDynamic hash={ data.zkevm_verify_hash }/>
|
|
</Skeleton>
|
|
<CopyToClipboard text={ data.zkevm_verify_hash } isLoading={ isLoading }/>
|
|
</DetailedInfo.ItemValue>
|
|
</>
|
|
) }
|
|
|
|
{ (data.zkevm_batch_number || data.zkevm_verify_hash) && <DetailedInfo.ItemDivider/> }
|
|
|
|
{ !config.UI.views.tx.hiddenFields?.value && (
|
|
<>
|
|
<DetailedInfo.ItemLabel
|
|
hint="Value sent in the native token (and USD) if applicable"
|
|
isLoading={ isLoading }
|
|
>
|
|
Value
|
|
</DetailedInfo.ItemLabel>
|
|
<DetailedInfoNativeCoinValue
|
|
amount={ data.value }
|
|
exchangeRate={ data.exchange_rate }
|
|
historicalExchangeRate={ data.historic_exchange_rate }
|
|
hasExchangeRateToggle
|
|
loading={ isLoading }
|
|
/>
|
|
</>
|
|
) }
|
|
|
|
<TxDetailsTxFee isLoading={ isLoading } data={ data }/>
|
|
|
|
{ rollupFeature.isEnabled && rollupFeature.type === 'optimistic' && data.operator_fee && (
|
|
<>
|
|
<DetailedInfo.ItemLabel
|
|
hint="A fee set by the chain operator to cover extra costs of additional services"
|
|
>
|
|
Operator fee
|
|
</DetailedInfo.ItemLabel>
|
|
<DetailedInfoNativeCoinValue
|
|
amount={ data.operator_fee }
|
|
exchangeRate={ data.exchange_rate }
|
|
historicalExchangeRate={ data.historic_exchange_rate }
|
|
hasExchangeRateToggle
|
|
loading={ isLoading }
|
|
/>
|
|
</>
|
|
) }
|
|
|
|
{ rollupFeature.isEnabled && rollupFeature.type === 'arbitrum' && data.arbitrum && (
|
|
<>
|
|
<DetailedInfo.ItemLabel
|
|
hint={ `Fee paid to the poster for ${ layerLabels.parent } resources` }
|
|
isLoading={ isLoading }
|
|
>
|
|
Poster fee
|
|
</DetailedInfo.ItemLabel>
|
|
<DetailedInfoNativeCoinValue
|
|
amount={ data.arbitrum.poster_fee }
|
|
exchangeRate={ data.exchange_rate }
|
|
historicalExchangeRate={ data.historic_exchange_rate }
|
|
hasExchangeRateToggle
|
|
loading={ isLoading }
|
|
/>
|
|
|
|
<DetailedInfo.ItemLabel
|
|
hint={ `Fee paid to the network for ${ layerLabels.current } resources` }
|
|
isLoading={ isLoading }
|
|
>
|
|
Network fee
|
|
</DetailedInfo.ItemLabel>
|
|
<DetailedInfoNativeCoinValue
|
|
amount={ data.arbitrum.network_fee }
|
|
exchangeRate={ data.exchange_rate }
|
|
historicalExchangeRate={ data.historic_exchange_rate }
|
|
hasExchangeRateToggle
|
|
loading={ isLoading }
|
|
/>
|
|
</>
|
|
) }
|
|
|
|
<TxDetailsGasPrice gasPrice={ data.gas_price } gasToken={ data.celo?.gas_token } isLoading={ isLoading }/>
|
|
|
|
<TxDetailsFeePerGas txFee={ data.fee?.value } gasUsed={ data.gas_used } isLoading={ isLoading }/>
|
|
|
|
{ !config.UI.views.tx.additionalFields?.set_max_gas_limit && <TxDetailsGasUsage isLoading={ isLoading } data={ data }/> }
|
|
|
|
{ rollupFeature.isEnabled && rollupFeature.type === 'arbitrum' && data.arbitrum && data.gas_used && (
|
|
<>
|
|
<DetailedInfo.ItemLabel
|
|
hint={ `${ layerLabels.current } gas set aside for ${ layerLabels.parent } data charges` }
|
|
isLoading={ isLoading }
|
|
>
|
|
Gas used for { layerLabels.parent }
|
|
</DetailedInfo.ItemLabel>
|
|
<DetailedInfo.ItemValue>
|
|
<Skeleton loading={ isLoading }>{ BigNumber(data.arbitrum.gas_used_for_l1 || 0).toFormat() }</Skeleton>
|
|
<TextSeparator/>
|
|
<Utilization
|
|
value={ BigNumber(data.arbitrum.gas_used_for_l1 || 0).dividedBy(BigNumber(data.gas_used)).toNumber() }
|
|
isLoading={ isLoading }
|
|
/>
|
|
</DetailedInfo.ItemValue>
|
|
|
|
<DetailedInfo.ItemLabel
|
|
hint={ `${ layerLabels.current } gas spent on ${ layerLabels.current } resources` }
|
|
isLoading={ isLoading }
|
|
>
|
|
Gas used for { layerLabels.current }
|
|
</DetailedInfo.ItemLabel>
|
|
<DetailedInfo.ItemValue>
|
|
<Skeleton loading={ isLoading }>{ BigNumber(data.arbitrum.gas_used_for_l2 || 0).toFormat() }</Skeleton>
|
|
<TextSeparator/>
|
|
<Utilization
|
|
value={ BigNumber(data.arbitrum.gas_used_for_l2 || 0).dividedBy(BigNumber(data.gas_used)).toNumber() }
|
|
isLoading={ isLoading }
|
|
/>
|
|
</DetailedInfo.ItemValue>
|
|
</>
|
|
) }
|
|
|
|
{ data.scroll?.l1_gas_used !== undefined && (
|
|
<>
|
|
<DetailedInfo.ItemLabel
|
|
hint={ `Total gas used on ${ layerLabels.parent }` }
|
|
isLoading={ isLoading }
|
|
>
|
|
{ layerLabels.parent } Gas used
|
|
</DetailedInfo.ItemLabel>
|
|
<DetailedInfo.ItemValue>
|
|
<Skeleton loading={ isLoading }>{ BigNumber(data.scroll?.l1_gas_used || 0).toFormat() }</Skeleton>
|
|
</DetailedInfo.ItemValue>
|
|
</>
|
|
) }
|
|
|
|
{ !config.UI.views.tx.hiddenFields?.gas_fees &&
|
|
(data.base_fee_per_gas || data.max_fee_per_gas || data.max_priority_fee_per_gas) && (
|
|
<>
|
|
<DetailedInfo.ItemLabel
|
|
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 })` }
|
|
</DetailedInfo.ItemLabel>
|
|
<DetailedInfo.ItemValue multiRow>
|
|
{ data.base_fee_per_gas && (
|
|
<NativeCoinValue
|
|
amount={ data.base_fee_per_gas }
|
|
units="gwei"
|
|
unitsTooltip="wei"
|
|
noSymbol
|
|
loading={ isLoading }
|
|
startElement={ <span>Base: </span> }
|
|
endElement={ (data.max_fee_per_gas || data.max_priority_fee_per_gas) && <TextSeparator/> }
|
|
/>
|
|
) }
|
|
{ data.max_fee_per_gas && (
|
|
<NativeCoinValue
|
|
amount={ data.max_fee_per_gas }
|
|
units="gwei"
|
|
unitsTooltip="wei"
|
|
noSymbol
|
|
loading={ isLoading }
|
|
startElement={ <span>Max: </span> }
|
|
endElement={ data.max_priority_fee_per_gas && <TextSeparator/> }
|
|
/>
|
|
) }
|
|
{ data.max_priority_fee_per_gas && (
|
|
<NativeCoinValue
|
|
amount={ data.max_priority_fee_per_gas }
|
|
units="gwei"
|
|
unitsTooltip="wei"
|
|
noSymbol
|
|
loading={ isLoading }
|
|
startElement={ <span>Max priority: </span> }
|
|
/>
|
|
) }
|
|
</DetailedInfo.ItemValue>
|
|
</>
|
|
) }
|
|
|
|
<TxDetailsBurntFees data={ data } isLoading={ isLoading }/>
|
|
|
|
{ rollupFeature.isEnabled && rollupFeature.type === 'optimistic' && (
|
|
<>
|
|
{ data.l1_gas_used && (
|
|
<>
|
|
<DetailedInfo.ItemLabel
|
|
hint={ `${ layerLabels.parent } gas used by transaction` }
|
|
isLoading={ isLoading }
|
|
>
|
|
{ layerLabels.parent } gas used by txn
|
|
</DetailedInfo.ItemLabel>
|
|
<DetailedInfo.ItemValue>
|
|
<span>{ BigNumber(data.l1_gas_used).toFormat() }</span>
|
|
</DetailedInfo.ItemValue>
|
|
</>
|
|
) }
|
|
|
|
{ data.l1_gas_price && (
|
|
<>
|
|
<DetailedInfo.ItemLabel
|
|
hint={ `${ layerLabels.parent } gas price` }
|
|
isLoading={ isLoading }
|
|
>
|
|
{ layerLabels.parent } gas price
|
|
</DetailedInfo.ItemLabel>
|
|
<GasPriceValue
|
|
amount={ data.l1_gas_price }
|
|
asset={ rollupFeature.parentChain.currency?.symbol || currencyUnits.ether }
|
|
loading={ isLoading }
|
|
/>
|
|
</>
|
|
) }
|
|
|
|
{ data.l1_fee && (
|
|
<>
|
|
<DetailedInfo.ItemLabel
|
|
// eslint-disable-next-line max-len
|
|
hint={ `${ layerLabels.parent } Data Fee which is used to cover the ${ layerLabels.parent } "security" cost from the batch submission mechanism. In combination with ${ layerLabels.current } execution fee, ${ layerLabels.parent } fee makes the total amount of fees that a transaction pays.` }
|
|
isLoading={ isLoading }
|
|
>
|
|
{ layerLabels.parent } fee
|
|
</DetailedInfo.ItemLabel>
|
|
<DetailedInfoNativeCoinValue
|
|
amount={ data.l1_fee }
|
|
asset={ rollupFeature.parentChain.currency?.symbol || currencyUnits.ether }
|
|
decimals={ rollupFeature.parentChain.currency?.decimals ?? config.chain.currency.decimals }
|
|
exchangeRate={ data.exchange_rate }
|
|
historicalExchangeRate={ data.historic_exchange_rate }
|
|
hasExchangeRateToggle
|
|
loading={ isLoading }
|
|
/>
|
|
</>
|
|
) }
|
|
|
|
{ data.l1_fee_scalar && (
|
|
<>
|
|
<DetailedInfo.ItemLabel
|
|
hint={ `A Dynamic overhead (fee scalar) premium, which serves as a buffer in case ${ layerLabels.parent } prices rapidly increase.` }
|
|
isLoading={ isLoading }
|
|
>
|
|
{ layerLabels.parent } fee scalar
|
|
</DetailedInfo.ItemLabel>
|
|
<DetailedInfo.ItemValue>
|
|
<span>{ data.l1_fee_scalar }</span>
|
|
</DetailedInfo.ItemValue>
|
|
</>
|
|
) }
|
|
</>
|
|
) }
|
|
<TxInfoScrollFees data={ data } isLoading={ isLoading }/>
|
|
|
|
<CollapsibleDetails loading={ isLoading } className="mt-6 lg:col-span-2" isExpanded={ isExpanded } onClick={ handleCutLinkClick }>
|
|
<div className="lg:col-span-2"/>
|
|
|
|
<TxDetailsSetMaxGasLimit data={ data }/>
|
|
|
|
<TxDetailsWithdrawalStatusArbitrum data={ data }/>
|
|
|
|
{ (data.blob_gas_used || data.max_fee_per_blob_gas || data.blob_gas_price) && (
|
|
<>
|
|
{ data.blob_gas_used && data.blob_gas_price && (
|
|
<>
|
|
<DetailedInfo.ItemLabel
|
|
hint="Blob fee for this transaction"
|
|
>
|
|
Blob fee
|
|
</DetailedInfo.ItemLabel>
|
|
<DetailedInfoNativeCoinValue
|
|
amount={ BigNumber(data.blob_gas_used).multipliedBy(data.blob_gas_price).toString() }
|
|
noSymbol={ config.UI.views.tx.hiddenFields?.fee_currency }
|
|
exchangeRate={ data.exchange_rate }
|
|
historicalExchangeRate={ data.historic_exchange_rate }
|
|
hasExchangeRateToggle
|
|
loading={ isLoading }
|
|
/>
|
|
</>
|
|
) }
|
|
|
|
{ data.blob_gas_used && (
|
|
<>
|
|
<DetailedInfo.ItemLabel
|
|
hint="Amount of gas used by the blobs in this transaction"
|
|
>
|
|
Blob gas usage
|
|
</DetailedInfo.ItemLabel>
|
|
<DetailedInfo.ItemValue>
|
|
{ BigNumber(data.blob_gas_used).toFormat() }
|
|
</DetailedInfo.ItemValue>
|
|
</>
|
|
) }
|
|
|
|
{ (data.max_fee_per_blob_gas || data.blob_gas_price) && (
|
|
<>
|
|
<DetailedInfo.ItemLabel
|
|
hint={ `Amount of ${ currencyUnits.ether } used for blobs in this transaction` }
|
|
>
|
|
{ `Blob gas fees (${ currencyUnits.gwei })` }
|
|
</DetailedInfo.ItemLabel>
|
|
<DetailedInfo.ItemValue>
|
|
{ data.blob_gas_price && (
|
|
<NativeCoinValue
|
|
amount={ data.blob_gas_price }
|
|
units="gwei"
|
|
unitsTooltip="wei"
|
|
noSymbol
|
|
loading={ isLoading }
|
|
/>
|
|
) }
|
|
{ (data.max_fee_per_blob_gas && data.blob_gas_price) && <TextSeparator/> }
|
|
{ data.max_fee_per_blob_gas && (
|
|
<NativeCoinValue
|
|
amount={ data.max_fee_per_blob_gas }
|
|
units="gwei"
|
|
unitsTooltip="wei"
|
|
noSymbol
|
|
loading={ isLoading }
|
|
startElement={ <span>Max: </span> }
|
|
/>
|
|
) }
|
|
</DetailedInfo.ItemValue>
|
|
</>
|
|
) }
|
|
<DetailedInfo.ItemDivider/>
|
|
</>
|
|
) }
|
|
|
|
<TxDetailsOther nonce={ data.nonce } type={ data.type } position={ data.position } queueIndex={ data.scroll?.queue_index }/>
|
|
|
|
<DetailedInfo.ItemLabel
|
|
hint="Binary data included with the transaction. See logs tab for additional info"
|
|
>
|
|
Raw input
|
|
</DetailedInfo.ItemLabel>
|
|
<DetailedInfo.ItemValue>
|
|
<RawInputData hex={ data.raw_input } defaultDataType={ data.zilliqa?.is_scilla ? 'UTF-8' : 'Hex' }/>
|
|
</DetailedInfo.ItemValue>
|
|
|
|
{ data.decoded_input && (
|
|
<>
|
|
<DetailedInfo.ItemLabel
|
|
hint="Decoded input data"
|
|
>
|
|
Decoded input data
|
|
</DetailedInfo.ItemLabel>
|
|
<DetailedInfo.ItemValue>
|
|
<LogDecodedInputData data={ data.decoded_input }/>
|
|
</DetailedInfo.ItemValue>
|
|
</>
|
|
) }
|
|
|
|
{ data.zksync && <ZkSyncL2TxnBatchHashesInfo data={ data.zksync } isLoading={ isLoading }/> }
|
|
</CollapsibleDetails>
|
|
</DetailedInfo.Container>
|
|
);
|
|
};
|
|
|
|
export default TxInfo;
|