Commit 29c84492 authored by tom goriunov's avatar tom goriunov Committed by GitHub

Block page: internal transactions tab (#2721)

* Block page: internal transactions tab

Fixes #2698

* add counter
parent 3fecb59b
<svg viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M8.05.194a10 10 0 1 1 3.903 19.614A10 10 0 0 1 8.049.194ZM10 1.539a8.462 8.462 0 1 0 0 16.925 8.462 8.462 0 0 0 0-16.925Zm0 1.538a.77.77 0 0 1 .77.77v5.838l4.007 4a.771.771 0 0 1 .169.841.772.772 0 0 1-1.01.417.77.77 0 0 1-.251-.165l-4.23-4.232A.77.77 0 0 1 9.23 10V3.847a.77.77 0 0 1 .77-.77Z" fill="currentColor"/>
<path d="M19.697 9.64a9.704 9.704 0 0 0-1.434-4.73l-.194-.303a9.705 9.705 0 0 0-4.026-3.43l-.33-.143A9.703 9.703 0 0 0 8.461.419l-.354.064a9.705 9.705 0 0 0-4.71 2.405l-.259.25a9.705 9.705 0 0 0-2.58 4.618l-.075.35a9.705 9.705 0 0 0 .552 5.607l.143.33a9.704 9.704 0 0 0 3.43 4.025l.303.194a9.706 9.706 0 0 0 5.09 1.442l.48-.012a9.704 9.704 0 0 0 6.38-2.83l.332-.349A9.706 9.706 0 0 0 19.703 10l-.006-.359Zm-1.518-.047a8.189 8.189 0 0 0-2.11-5.09l-.28-.293a8.188 8.188 0 0 0-5.383-2.389L10 1.811a8.19 8.19 0 0 0-4.294 1.217l-.256.163A8.19 8.19 0 0 0 2.556 6.59l-.121.278a8.19 8.19 0 0 0-.466 4.73l.065.297A8.188 8.188 0 0 0 4.21 15.79l.218.21a8.19 8.19 0 0 0 3.974 2.03l.298.054a8.19 8.19 0 0 0 4.433-.52l.278-.12a8.19 8.19 0 0 0 3.397-2.895l.164-.255a8.19 8.19 0 0 0 1.216-4.295l-.01-.406ZM10.61 3.937a.61.61 0 0 0-.102-.339l-.077-.092a.61.61 0 0 0-.311-.168L10 3.327a.61.61 0 0 0-.338.102l-.093.077a.61.61 0 0 0-.179.43V10c0 .08.015.16.045.235l.056.105a.618.618 0 0 0 .075.092l4.17 4.17c.056.055.123.1.198.13l.115.035c.039.008.079.01.118.01h.002l.119-.01a.615.615 0 0 0 .115-.035l.107-.056a.601.601 0 0 0 .092-.075l.077-.093a.609.609 0 0 0 .057-.106l.035-.114.011-.12c0-.041-.004-.082-.011-.12l-.035-.115a.61.61 0 0 0-.134-.198L10.61 9.75V3.937Zm7.869 6.376a8.486 8.486 0 0 1-1.255 4.136l-.17.264a8.486 8.486 0 0 1-3.52 2.999l-.287.126a8.486 8.486 0 0 1-4.594.538l-.309-.055a8.484 8.484 0 0 1-4.117-2.104L4 15.999a8.485 8.485 0 0 1-2.254-4.037l-.068-.307a8.486 8.486 0 0 1 .484-4.902l.126-.288a8.486 8.486 0 0 1 2.999-3.52l.263-.17A8.486 8.486 0 0 1 10 1.516l.42.01A8.485 8.485 0 0 1 16 4l.289.305A8.484 8.484 0 0 1 18.485 10l-.006.314Zm1.509.182a10.002 10.002 0 0 1-2.575 6.216l-.342.36a10.001 10.001 0 0 1-6.575 2.916L10 20a10 10 0 0 1-5.244-1.486l-.311-.2A10.002 10.002 0 0 1 .91 14.166l-.148-.34a10.001 10.001 0 0 1-.57-5.777l.079-.362A10.002 10.002 0 0 1 2.928 2.93l.268-.257A10.001 10.001 0 0 1 8.049.192l.365-.065a10 10 0 0 1 5.413.634l.34.148a10.003 10.003 0 0 1 4.148 3.535l.2.312A10.002 10.002 0 0 1 20 9.999l-.012.496Zm-9.081-.869 3.904 3.899a.907.907 0 0 1 .198.294l.03.085c.026.085.04.174.04.263l-.005.09a.903.903 0 0 1-.035.174l-.03.085a.908.908 0 0 1-.138.229l-.06.066a.896.896 0 0 1-.216.157l-.08.037a.907.907 0 0 1-.61.03l-.083-.03a.904.904 0 0 1-.295-.194l-4.17-4.169a.914.914 0 0 1-.157-.214l-.038-.081a.91.91 0 0 1-.068-.349V3.937c0-.24.096-.471.265-.64l.067-.06A.906.906 0 0 1 10 3.03l.09.005c.207.021.402.113.551.261l.06.066a.906.906 0 0 1 .206.575v5.69Z" fill="currentColor"/>
<path d="M19.697 9.64a9.704 9.704 0 0 0-1.434-4.73l-.194-.303a9.705 9.705 0 0 0-4.026-3.43l-.33-.143A9.703 9.703 0 0 0 8.461.419l-.354.064a9.705 9.705 0 0 0-4.71 2.405l-.259.25a9.705 9.705 0 0 0-2.58 4.618l-.075.35a9.705 9.705 0 0 0 .552 5.607l.143.33a9.704 9.704 0 0 0 3.43 4.025l.303.194a9.706 9.706 0 0 0 5.09 1.442l.48-.012a9.704 9.704 0 0 0 6.38-2.83l.332-.349A9.706 9.706 0 0 0 19.703 10l-.006-.359Zm-1.518-.047a8.189 8.189 0 0 0-2.11-5.09l-.28-.293a8.188 8.188 0 0 0-5.383-2.389L10 1.811a8.19 8.19 0 0 0-4.294 1.217l-.256.163A8.19 8.19 0 0 0 2.556 6.59l-.121.278a8.19 8.19 0 0 0-.466 4.73l.065.297A8.188 8.188 0 0 0 4.21 15.79l.218.21a8.19 8.19 0 0 0 3.974 2.03l.298.054a8.19 8.19 0 0 0 4.433-.52l.278-.12a8.19 8.19 0 0 0 3.397-2.895l.164-.255a8.19 8.19 0 0 0 1.216-4.295l-.01-.406ZM10.61 3.937a.61.61 0 0 0-.102-.339l-.077-.092a.61.61 0 0 0-.311-.168L10 3.327a.61.61 0 0 0-.338.102l-.093.077a.61.61 0 0 0-.179.43V10c0 .08.015.16.045.235l.056.105a.618.618 0 0 0 .075.092l4.17 4.17c.056.055.123.1.198.13l.115.035c.039.008.079.01.118.01h.002l.119-.01a.615.615 0 0 0 .115-.035l.107-.056a.601.601 0 0 0 .092-.075l.077-.093a.609.609 0 0 0 .057-.106l.035-.114.011-.12a.67.67 0 0 0-.011-.12l-.035-.115a.61.61 0 0 0-.134-.198L10.61 9.75V3.937Zm7.869 6.376a8.486 8.486 0 0 1-1.255 4.136l-.17.264a8.486 8.486 0 0 1-3.52 2.999l-.287.126a8.486 8.486 0 0 1-4.594.538l-.309-.055a8.484 8.484 0 0 1-4.117-2.104L4 15.999a8.485 8.485 0 0 1-2.254-4.037l-.068-.307a8.486 8.486 0 0 1 .484-4.902l.126-.288a8.486 8.486 0 0 1 2.999-3.52l.263-.17A8.486 8.486 0 0 1 10 1.516l.42.01A8.485 8.485 0 0 1 16 4l.289.305A8.484 8.484 0 0 1 18.485 10l-.006.314Zm1.509.182a10.002 10.002 0 0 1-2.575 6.216l-.342.36a10.001 10.001 0 0 1-6.575 2.916L10 20a10 10 0 0 1-5.244-1.486l-.311-.2A10.002 10.002 0 0 1 .91 14.166l-.148-.34a10.001 10.001 0 0 1-.57-5.777l.079-.362A10.002 10.002 0 0 1 2.928 2.93l.268-.257A10.001 10.001 0 0 1 8.049.192l.365-.065a10 10 0 0 1 5.413.634l.34.148a10.003 10.003 0 0 1 4.148 3.535l.2.312A10.002 10.002 0 0 1 20 9.999l-.012.496Zm-9.081-.869 3.904 3.899a.907.907 0 0 1 .198.294l.03.085c.026.085.04.174.04.263l-.005.09a.903.903 0 0 1-.035.174l-.03.085a.908.908 0 0 1-.138.229l-.06.066a.896.896 0 0 1-.216.157l-.08.037a.907.907 0 0 1-.61.03l-.083-.03a.904.904 0 0 1-.295-.194l-4.17-4.169a.914.914 0 0 1-.157-.214l-.038-.081a.91.91 0 0 1-.068-.349V3.937c0-.24.096-.471.265-.64l.067-.06A.906.906 0 0 1 10 3.03l.09.005a.914.914 0 0 1 .551.261l.06.066a.906.906 0 0 1 .206.575v5.69Z" fill="currentColor"/>
</svg>
......@@ -8,6 +8,7 @@ import type {
BlockCountdownResponse,
BlockEpoch,
BlockEpochElectionRewardDetailsResponse,
BlockInternalTransactionsResponse,
} from 'types/api/block';
import type { TTxsWithBlobsFilters } from 'types/api/txsFilters';
......@@ -27,6 +28,11 @@ export const GENERAL_API_BLOCK_RESOURCES = {
filterFields: [ 'type' as const ],
paginated: true,
},
block_internal_txs: {
path: '/api/v2/blocks/:height_or_hash/internal-transactions',
pathParams: [ 'height_or_hash' as const ],
paginated: true,
},
block_withdrawals: {
path: '/api/v2/blocks/:height_or_hash/withdrawals',
pathParams: [ 'height_or_hash' as const ],
......@@ -54,6 +60,7 @@ R extends 'general:blocks' ? BlocksResponse :
R extends 'general:block' ? Block :
R extends 'general:block_countdown' ? BlockCountdownResponse :
R extends 'general:block_txs' ? BlockTransactionsResponse :
R extends 'general:block_internal_txs' ? BlockInternalTransactionsResponse :
R extends 'general:block_withdrawals' ? BlockWithdrawalsResponse :
R extends 'general:block_epoch' ? BlockEpoch :
R extends 'general:block_election_rewards' ? BlockEpochElectionRewardDetailsResponse :
......
......@@ -53,6 +53,7 @@ export const base: Block = {
timestamp: '2022-11-11T11:59:35Z',
total_difficulty: '10258276095980170141167591583995189665817672619',
transactions_count: 5,
internal_transactions_count: 12,
transaction_fees: '26853607500000000',
type: 'block',
uncles_hashes: [],
......@@ -90,6 +91,7 @@ export const genesis: Block = {
timestamp: '2017-12-16T00:13:24.000000Z',
total_difficulty: '131072',
transactions_count: 0,
internal_transactions_count: 0,
transaction_fees: '0',
type: 'block',
uncles_hashes: [],
......
......@@ -32,6 +32,7 @@ export const BLOCK: Block = {
timestamp: '2023-05-12T19:29:12.000000Z',
total_difficulty: '10837812015930321201107455268036056402048391639',
transactions_count: 142,
internal_transactions_count: 42,
transaction_fees: '19241635547777613',
type: 'block',
uncles_hashes: [],
......
......@@ -3,6 +3,7 @@ import type { Reward } from 'types/api/reward';
import type { Transaction } from 'types/api/transaction';
import type { ArbitrumBatchStatus, ArbitrumL2TxData } from './arbitrumL2';
import type { InternalTransaction } from './internalTransaction';
import type { OptimisticL2BatchDataContainer, OptimisticL2BlobTypeEip4844, OptimisticL2BlobTypeCelestia } from './optimisticL2';
import type { TokenInfo } from './token';
import type { TokenTransfer } from './tokenTransfer';
......@@ -21,6 +22,7 @@ export interface Block {
height: number;
timestamp: string;
transactions_count: number;
internal_transactions_count: number;
miner: AddressParam;
size: number;
hash: string;
......@@ -125,6 +127,14 @@ export interface BlockTransactionsResponse {
} | null;
}
export interface BlockInternalTransactionsResponse {
items: Array<InternalTransaction>;
next_page_params: {
block_index: number;
items_count: number;
} | null;
}
export interface NewBlockSocketResponse {
average_block_time: string;
block: Block;
......
import { Box } from '@chakra-ui/react';
import React from 'react';
import InternalTxsList from 'ui/internalTxs/InternalTxsList';
import InternalTxsTable from 'ui/internalTxs/InternalTxsTable';
import DataListDisplay from 'ui/shared/DataListDisplay';
import type { QueryWithPagesResult } from 'ui/shared/pagination/useQueryWithPages';
interface Props {
query: QueryWithPagesResult<'general:block_internal_txs'>;
top?: number;
}
const BlockInternalTxs = ({ query, top }: Props) => {
const { data, isPlaceholderData, isError } = query;
const content = data?.items ? (
<>
<Box hideFrom="lg">
<InternalTxsList data={ data.items } isLoading={ isPlaceholderData } showBlockInfo={ false }/>
</Box>
<Box hideBelow="lg">
<InternalTxsTable data={ data.items } isLoading={ isPlaceholderData } top={ top } showBlockInfo={ false }/>
</Box>
</>
) : null;
return (
<DataListDisplay
isError={ isError }
itemsNum={ data?.items.length }
emptyText="There are no internal transactions."
>
{ content }
</DataListDisplay>
);
};
export default React.memo(BlockInternalTxs);
import { INTERNAL_TX } from 'stubs/internalTx';
import { generateListStub } from 'stubs/utils';
import useQueryWithPages from 'ui/shared/pagination/useQueryWithPages';
import type { BlockQuery } from './useBlockQuery';
interface Params {
heightOrHash: string;
blockQuery: BlockQuery;
tab: string;
}
export default function useBlockInternalTxsQuery({ heightOrHash, blockQuery, tab }: Params) {
const apiQuery = useQueryWithPages({
resourceName: 'general:block_internal_txs',
pathParams: { height_or_hash: heightOrHash },
options: {
enabled: Boolean(tab === 'internal_txs' && !blockQuery.isPlaceholderData && blockQuery.data?.internal_transactions_count),
placeholderData: generateListStub<'general:block_internal_txs'>(INTERNAL_TX, 10, { next_page_params: null }),
refetchOnMount: false,
},
});
return apiQuery;
}
......@@ -66,6 +66,7 @@ export default function useBlockQuery({ heightOrHash }: Params): BlockQuery {
height: Number(block.number),
timestamp: dayjs.unix(Number(block.timestamp)).format(),
transactions_count: block.transactions.length,
internal_transactions_count: 0,
miner: { ...unknownAddress, hash: block.miner },
size: Number(block.size),
hash: block.hash,
......
......@@ -9,9 +9,10 @@ type Props = {
data: Array<InternalTransaction>;
currentAddress?: string;
isLoading?: boolean;
showBlockInfo?: boolean;
};
const InternalTxsList = ({ data, currentAddress, isLoading }: Props) => {
const InternalTxsList = ({ data, currentAddress, isLoading, showBlockInfo = true }: Props) => {
return (
<Box>
{ data.map((item, index) => (
......@@ -20,6 +21,7 @@ const InternalTxsList = ({ data, currentAddress, isLoading }: Props) => {
{ ...item }
currentAddress={ currentAddress }
isLoading={ isLoading }
showBlockInfo={ showBlockInfo }
/>
)) }
</Box>
......
......@@ -16,7 +16,7 @@ import TxStatus from 'ui/shared/statusTag/TxStatus';
import TimeWithTooltip from 'ui/shared/time/TimeWithTooltip';
import { TX_INTERNALS_ITEMS } from 'ui/tx/internals/utils';
type Props = InternalTransaction & { currentAddress?: string; isLoading?: boolean };
type Props = InternalTransaction & { currentAddress?: string; isLoading?: boolean; showBlockInfo?: boolean };
const InternalTxsListItem = ({
type,
......@@ -31,6 +31,7 @@ const InternalTxsListItem = ({
timestamp,
currentAddress,
isLoading,
showBlockInfo = true,
}: Props) => {
const typeTitle = TX_INTERNALS_ITEMS.find(({ id }) => id === type)?.title;
const toData = to ? to : createdContract;
......@@ -56,15 +57,17 @@ const InternalTxsListItem = ({
fontSize="sm"
/>
</Flex>
<HStack gap={ 1 }>
<Skeleton loading={ isLoading } fontSize="sm" fontWeight={ 500 }>Block</Skeleton>
<BlockEntity
isLoading={ isLoading }
number={ blockNumber }
noIcon
textStyle="sm"
/>
</HStack>
{ showBlockInfo && (
<HStack gap={ 1 }>
<Skeleton loading={ isLoading } fontSize="sm" fontWeight={ 500 }>Block</Skeleton>
<BlockEntity
isLoading={ isLoading }
number={ blockNumber }
noIcon
textStyle="sm"
/>
</HStack>
) }
<AddressFromTo
from={ from }
to={ toData }
......
......@@ -13,20 +13,22 @@ interface Props {
data: Array<InternalTransaction>;
currentAddress?: string;
isLoading?: boolean;
top?: number;
showBlockInfo?: boolean;
}
const InternalTxsTable = ({ data, currentAddress, isLoading }: Props) => {
const InternalTxsTable = ({ data, currentAddress, isLoading, top, showBlockInfo = true }: Props) => {
return (
<AddressHighlightProvider>
<TableRoot minW="900px">
<TableHeaderSticky top={ 68 }>
<TableHeaderSticky top={ top ?? 68 }>
<TableRow>
<TableColumnHeader width="280px">
Parent txn hash
<TimeFormatToggle/>
</TableColumnHeader>
<TableColumnHeader width="15%">Type</TableColumnHeader>
<TableColumnHeader width="15%">Block</TableColumnHeader>
{ showBlockInfo && <TableColumnHeader width="15%">Block</TableColumnHeader> }
<TableColumnHeader width="50%">From/To</TableColumnHeader>
<TableColumnHeader width="20%" isNumeric>
Value { currencyUnits.ether }
......@@ -40,6 +42,7 @@ const InternalTxsTable = ({ data, currentAddress, isLoading }: Props) => {
{ ...item }
currentAddress={ currentAddress }
isLoading={ isLoading }
showBlockInfo={ showBlockInfo }
/>
)) }
</TableBody>
......
......@@ -15,7 +15,7 @@ import TimeWithTooltip from 'ui/shared/time/TimeWithTooltip';
import TruncatedValue from 'ui/shared/TruncatedValue';
import { TX_INTERNALS_ITEMS } from 'ui/tx/internals/utils';
type Props = InternalTransaction & { currentAddress?: string; isLoading?: boolean };
type Props = InternalTransaction & { currentAddress?: string; isLoading?: boolean; showBlockInfo?: boolean };
const InternalTxsTableItem = ({
type,
......@@ -30,6 +30,7 @@ const InternalTxsTableItem = ({
timestamp,
currentAddress,
isLoading,
showBlockInfo = true,
}: Props) => {
const typeTitle = TX_INTERNALS_ITEMS.find(({ id }) => id === type)?.title;
const toData = to ? to : createdContract;
......@@ -64,15 +65,17 @@ const InternalTxsTableItem = ({
<TxStatus status={ success ? 'ok' : 'error' } errorText={ error } isLoading={ isLoading }/>
</Flex>
</TableCell>
<TableCell verticalAlign="middle">
<BlockEntity
isLoading={ isLoading }
number={ blockNumber }
noIcon
textStyle="sm"
fontWeight={ 500 }
/>
</TableCell>
{ showBlockInfo && (
<TableCell verticalAlign="middle">
<BlockEntity
isLoading={ isLoading }
number={ blockNumber }
noIcon
textStyle="sm"
fontWeight={ 500 }
/>
</TableCell>
) }
<TableCell verticalAlign="middle">
<AddressFromTo
from={ from }
......
......@@ -18,8 +18,10 @@ import RoutedTabs from 'toolkit/components/RoutedTabs/RoutedTabs';
import BlockCeloEpochTag from 'ui/block/BlockCeloEpochTag';
import BlockDetails from 'ui/block/BlockDetails';
import BlockEpochRewards from 'ui/block/BlockEpochRewards';
import BlockInternalTxs from 'ui/block/BlockInternalTxs';
import BlockWithdrawals from 'ui/block/BlockWithdrawals';
import useBlockBlobTxsQuery from 'ui/block/useBlockBlobTxsQuery';
import useBlockInternalTxsQuery from 'ui/block/useBlockInternalTxsQuery';
import useBlockQuery from 'ui/block/useBlockQuery';
import useBlockTxsQuery from 'ui/block/useBlockTxsQuery';
import useBlockWithdrawalsQuery from 'ui/block/useBlockWithdrawalsQuery';
......@@ -50,10 +52,12 @@ const BlockPageContent = () => {
const blockTxsQuery = useBlockTxsQuery({ heightOrHash, blockQuery, tab });
const blockWithdrawalsQuery = useBlockWithdrawalsQuery({ heightOrHash, blockQuery, tab });
const blockBlobTxsQuery = useBlockBlobTxsQuery({ heightOrHash, blockQuery, tab });
const blockInternalTxsQuery = useBlockInternalTxsQuery({ heightOrHash, blockQuery, tab });
const hasPagination = !isMobile && (
(tab === 'txs' && blockTxsQuery.pagination.isVisible) ||
(tab === 'withdrawals' && blockWithdrawalsQuery.pagination.isVisible)
(tab === 'withdrawals' && blockWithdrawalsQuery.pagination.isVisible) ||
(tab === 'internal_txs' && blockInternalTxsQuery.pagination.isVisible)
);
const tabs: Array<TabItemRegular> = React.useMemo(() => ([
......@@ -77,6 +81,16 @@ const BlockPageContent = () => {
</>
),
},
blockQuery.data?.internal_transactions_count ? {
id: 'internal_txs',
title: 'Internal txns',
component: (
<>
{ blockTxsQuery.isDegradedData && <ServiceDegradationWarning isLoading={ blockTxsQuery.isPlaceholderData } mb={ 6 }/> }
<BlockInternalTxs query={ blockInternalTxsQuery } top={ hasPagination ? TABS_HEIGHT : 0 }/>
</>
),
} : null,
config.features.dataAvailability.isEnabled && blockQuery.data?.blob_transaction_count ?
{
id: 'blob_txs',
......@@ -101,13 +115,15 @@ const BlockPageContent = () => {
title: 'Epoch rewards',
component: <BlockEpochRewards heightOrHash={ heightOrHash }/>,
} : null,
].filter(Boolean)), [ blockBlobTxsQuery, blockQuery, blockTxsQuery, blockWithdrawalsQuery, hasPagination, heightOrHash ]);
].filter(Boolean)), [ blockBlobTxsQuery, blockInternalTxsQuery, blockQuery, blockTxsQuery, blockWithdrawalsQuery, hasPagination, heightOrHash ]);
let pagination;
if (tab === 'txs') {
pagination = blockTxsQuery.pagination;
} else if (tab === 'withdrawals') {
pagination = blockWithdrawalsQuery.pagination;
} else if (tab === 'internal_txs') {
pagination = blockInternalTxsQuery.pagination;
}
const backLink = React.useMemo(() => {
......
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