Skip to content

Commit

Permalink
Merge pull request #2139 from blockscout/fe-2100
Browse files Browse the repository at this point in the history
arbitrum latest batches and deposits
  • Loading branch information
isstuev authored Aug 13, 2024
2 parents a2bb162 + 7d70f11 commit df2f9d9
Show file tree
Hide file tree
Showing 26 changed files with 457 additions and 90 deletions.
22 changes: 18 additions & 4 deletions lib/api/resources.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,10 +43,12 @@ import type { AddressesResponse } from 'types/api/addresses';
import type { AddressMetadataInfo, PublicTagTypesResponse } from 'types/api/addressMetadata';
import type {
ArbitrumL2MessagesResponse,
ArbitrumL2MessagesItem,
ArbitrumL2TxnBatch,
ArbitrumL2TxnBatchesResponse,
ArbitrumL2BatchTxs,
ArbitrumL2BatchBlocks,
ArbitrumL2TxnBatchesItem,
} from 'types/api/arbitrumL2';
import type { TxBlobs, Blob } from 'types/api/blobs';
import type { BlocksResponse, BlockTransactionsResponse, Block, BlockFilters, BlockWithdrawalsResponse, BlockCountdownResponse } from 'types/api/block';
Expand Down Expand Up @@ -580,15 +582,21 @@ export const RESOURCES = {
homepage_blocks: {
path: '/api/v2/main-page/blocks',
},
homepage_deposits: {
homepage_optimistic_deposits: {
path: '/api/v2/main-page/optimism-deposits',
},
homepage_arbitrum_deposits: {
path: '/api/v2/main-page/arbitrum/messages/to-rollup',
},
homepage_txs: {
path: '/api/v2/main-page/transactions',
},
homepage_zkevm_l2_batches: {
path: '/api/v2/main-page/zkevm/batches/confirmed',
},
homepage_arbitrum_l2_batches: {
path: '/api/v2/main-page/arbitrum/batches/committed',
},
homepage_txs_watchlist: {
path: '/api/v2/main-page/transactions/watchlist',
},
Expand All @@ -601,6 +609,9 @@ export const RESOURCES = {
homepage_zksync_latest_batch: {
path: '/api/v2/main-page/zksync/batches/latest-number',
},
homepage_arbitrum_latest_batch: {
path: '/api/v2/main-page/arbitrum/batches/latest-number',
},

// SEARCH
quick_search: {
Expand Down Expand Up @@ -966,11 +977,14 @@ Q extends 'stats_charts_secondary_coin_price' ? ChartSecondaryCoinPriceResponse
Q extends 'homepage_blocks' ? Array<Block> :
Q extends 'homepage_txs' ? Array<Transaction> :
Q extends 'homepage_txs_watchlist' ? Array<Transaction> :
Q extends 'homepage_deposits' ? Array<OptimisticL2DepositsItem> :
Q extends 'homepage_optimistic_deposits' ? Array<OptimisticL2DepositsItem> :
Q extends 'homepage_arbitrum_deposits' ? { items: Array<ArbitrumL2MessagesItem> } :
Q extends 'homepage_zkevm_l2_batches' ? { items: Array<ZkEvmL2TxnBatchesItem> } :
Q extends 'homepage_arbitrum_l2_batches' ? { items: Array<ArbitrumL2TxnBatchesItem>} :
Q extends 'homepage_indexing_status' ? IndexingStatus :
Q extends 'homepage_zkevm_latest_batch' ? number :
Q extends 'homepage_zksync_latest_batch' ? number :
Q extends 'homepage_arbitrum_latest_batch' ? number :
Q extends 'stats_counters' ? stats.Counters :
Q extends 'stats_lines' ? stats.LineCharts :
Q extends 'stats_line' ? stats.LineChart :
Expand Down Expand Up @@ -1029,8 +1043,6 @@ Q extends 'verified_contracts' ? VerifiedContractsResponse :
Q extends 'verified_contracts_counters' ? VerifiedContractsCounters :
Q extends 'visualize_sol2uml' ? visualizer.VisualizeResponse :
Q extends 'contract_verification_config' ? SmartContractVerificationConfigRaw :
Q extends 'withdrawals' ? WithdrawalsResponse :
Q extends 'withdrawals_counters' ? WithdrawalsCounters :
Q extends 'optimistic_l2_output_roots' ? OptimisticL2OutputRootsResponse :
Q extends 'optimistic_l2_withdrawals' ? OptimisticL2WithdrawalsResponse :
Q extends 'optimistic_l2_deposits' ? OptimisticL2DepositsResponse :
Expand Down Expand Up @@ -1097,6 +1109,8 @@ Q extends 'address_mud_tables' ? AddressMudTables :
Q extends 'address_mud_tables_count' ? number :
Q extends 'address_mud_records' ? AddressMudRecords :
Q extends 'address_mud_record' ? AddressMudRecord :
Q extends 'withdrawals' ? WithdrawalsResponse :
Q extends 'withdrawals_counters' ? WithdrawalsCounters :
never;
/* eslint-enable @typescript-eslint/indent */

Expand Down
9 changes: 7 additions & 2 deletions lib/socket/types.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import type { Channel } from 'phoenix';

import type { AddressCoinBalanceHistoryItem, AddressTokensBalancesSocketMessage } from 'types/api/address';
import type { NewArbitrumBatchSocketResponse } from 'types/api/arbitrumL2';
import type { NewBlockSocketResponse } from 'types/api/block';
import type { SmartContractVerificationResponse } from 'types/api/contract';
import type { RawTracesResponse } from 'types/api/rawTrace';
Expand All @@ -16,7 +17,8 @@ SocketMessage.TxStatusUpdate |
SocketMessage.TxRawTrace |
SocketMessage.NewTx |
SocketMessage.NewPendingTx |
SocketMessage.NewDeposits |
SocketMessage.NewOptimisticDeposits |
SocketMessage.NewArbitrumDeposits |
SocketMessage.AddressBalance |
SocketMessage.AddressCurrentCoinBalance |
SocketMessage.AddressTokenBalance |
Expand All @@ -36,6 +38,7 @@ SocketMessage.TokenTotalSupply |
SocketMessage.TokenInstanceMetadataFetched |
SocketMessage.ContractVerification |
SocketMessage.NewZkEvmL2Batch |
SocketMessage.NewArbitrumL2Batch |
SocketMessage.Unknown;

interface SocketMessageParamsGeneric<Event extends string | undefined, Payload extends object | unknown> {
Expand All @@ -53,7 +56,8 @@ export namespace SocketMessage {
export type TxRawTrace = SocketMessageParamsGeneric<'raw_trace', RawTracesResponse>;
export type NewTx = SocketMessageParamsGeneric<'transaction', { transaction: number }>;
export type NewPendingTx = SocketMessageParamsGeneric<'pending_transaction', { pending_transaction: number }>;
export type NewDeposits = SocketMessageParamsGeneric<'deposits', { deposits: number }>;
export type NewOptimisticDeposits = SocketMessageParamsGeneric<'deposits', { deposits: number }>;
export type NewArbitrumDeposits = SocketMessageParamsGeneric<'new_messages_to_rollup_amount', { new_messages_to_rollup_amount: number }>;
export type AddressBalance = SocketMessageParamsGeneric<'balance', { balance: string; block_number: number; exchange_rate: string }>;
export type AddressCurrentCoinBalance =
SocketMessageParamsGeneric<'current_coin_balance', { coin_balance: string; block_number: number; exchange_rate: string }>;
Expand All @@ -74,5 +78,6 @@ export namespace SocketMessage {
export type TokenInstanceMetadataFetched = SocketMessageParamsGeneric<'fetched_token_instance_metadata', TokenInstanceMetadataSocketMessage>;
export type ContractVerification = SocketMessageParamsGeneric<'verification_result', SmartContractVerificationResponse>;
export type NewZkEvmL2Batch = SocketMessageParamsGeneric<'new_zkevm_confirmed_batch', NewZkEvmBatchSocketResponse>;
export type NewArbitrumL2Batch = SocketMessageParamsGeneric<'new_arbitrum_batch', NewArbitrumBatchSocketResponse>;
export type Unknown = SocketMessageParamsGeneric<undefined, unknown>;
}
2 changes: 2 additions & 0 deletions types/api/arbitrumL2.ts
Original file line number Diff line number Diff line change
Expand Up @@ -104,3 +104,5 @@ export const ARBITRUM_L2_TX_BATCH_STATUSES = [
];

export type ArbitrumBatchStatus = typeof ARBITRUM_L2_TX_BATCH_STATUSES[number];

export type NewArbitrumBatchSocketResponse = { batch: ArbitrumL2TxnBatchesItem }
31 changes: 19 additions & 12 deletions ui/home/Stats.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -46,13 +46,21 @@ const Stats = () => {
},
});

if (isError || zkEvmLatestBatchQuery.isError || zkSyncLatestBatchQuery.isError) {
const arbitrumLatestBatchQuery = useApiQuery('homepage_arbitrum_latest_batch', {
queryOptions: {
placeholderData: 12345,
enabled: rollupFeature.isEnabled && rollupFeature.type === 'arbitrum',
},
});

if (isError || zkEvmLatestBatchQuery.isError || zkSyncLatestBatchQuery.isError || arbitrumLatestBatchQuery.isError) {
return null;
}

const isLoading = isPlaceholderData ||
(rollupFeature.isEnabled && rollupFeature.type === 'zkEvm' && zkEvmLatestBatchQuery.isPlaceholderData) ||
(rollupFeature.isEnabled && rollupFeature.type === 'zkSync' && zkSyncLatestBatchQuery.isPlaceholderData);
(rollupFeature.isEnabled && rollupFeature.type === 'zkSync' && zkSyncLatestBatchQuery.isPlaceholderData) ||
(rollupFeature.isEnabled && rollupFeature.type === 'arbitrum' && arbitrumLatestBatchQuery.isPlaceholderData);

const content = (() => {
if (!data) {
Expand All @@ -72,22 +80,21 @@ const Stats = () => {
</GasInfoTooltip>
) : null;

const hasBatches = rollupFeature.isEnabled && (rollupFeature.type === 'zkEvm' || rollupFeature.type === 'zkSync' || rollupFeature.type === 'arbitrum');
const latestBatch =
(hasBatches && rollupFeature.type === 'zkEvm' ? zkEvmLatestBatchQuery.data : null) ||
(hasBatches && rollupFeature.type === 'zkSync' ? zkSyncLatestBatchQuery.data : null) ||
(hasBatches && rollupFeature.type === 'arbitrum' ? arbitrumLatestBatchQuery.data : null) || 0;

const items: Array<StatsWidgetProps> = [
rollupFeature.isEnabled && rollupFeature.type === 'zkEvm' && {
icon: 'txn_batches_slim' as const,
label: 'Latest batch',
value: (zkEvmLatestBatchQuery.data || 0).toLocaleString(),
href: { pathname: '/batches' as const },
isLoading,
},
rollupFeature.isEnabled && rollupFeature.type === 'zkSync' && {
hasBatches && {
icon: 'txn_batches_slim' as const,
label: 'Latest batch',
value: (zkSyncLatestBatchQuery.data || 0).toLocaleString(),
value: latestBatch.toLocaleString(),
href: { pathname: '/batches' as const },
isLoading,
},
!(rollupFeature.isEnabled && (rollupFeature.type === 'zkEvm' || rollupFeature.type === 'zkSync')) && {
!hasBatches && {
icon: 'block_slim' as const,
label: 'Total blocks',
value: Number(data.total_blocks).toLocaleString(),
Expand Down
12 changes: 9 additions & 3 deletions ui/home/Transactions.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,13 @@ import React from 'react';

import config from 'configs/app';
import useHasAccount from 'lib/hooks/useHasAccount';
import LatestDeposits from 'ui/home/LatestDeposits';
import LatestOptimisticDeposits from 'ui/home/latestDeposits/LatestOptimisticDeposits';
import LatestTxs from 'ui/home/LatestTxs';
import LatestWatchlistTxs from 'ui/home/LatestWatchlistTxs';
import TabsWithScroll from 'ui/shared/Tabs/TabsWithScroll';

import LatestArbitrumDeposits from './latestDeposits/LatestArbitrumDeposits';

const rollupFeature = config.features.rollup;

const TAB_LIST_PROPS = {
Expand All @@ -15,10 +18,13 @@ const TAB_LIST_PROPS = {

const TransactionsHome = () => {
const hasAccount = useHasAccount();
if ((rollupFeature.isEnabled && rollupFeature.type === 'optimistic') || hasAccount) {
if ((rollupFeature.isEnabled && (rollupFeature.type === 'optimistic' || rollupFeature.type === 'arbitrum')) || hasAccount) {
const tabs = [
{ id: 'txn', title: 'Latest txn', component: <LatestTxs/> },
rollupFeature.isEnabled && rollupFeature.type === 'optimistic' && { id: 'deposits', title: 'Deposits (L1→L2 txn)', component: <LatestDeposits/> },
rollupFeature.isEnabled && rollupFeature.type === 'optimistic' &&
{ id: 'deposits', title: 'Deposits (L1→L2 txn)', component: <LatestOptimisticDeposits/> },
rollupFeature.isEnabled && rollupFeature.type === 'arbitrum' &&
{ id: 'deposits', title: 'Deposits (L1→L2 txn)', component: <LatestArbitrumDeposits/> },
hasAccount && { id: 'watchlist', title: 'Watch list', component: <LatestWatchlistTxs/> },
].filter(Boolean);
return (
Expand Down
Binary file not shown.
15 changes: 15 additions & 0 deletions ui/home/latestBatches/LatestArbitrumL2Batches.pw.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import React from 'react';

import { finalized, unfinalized } from 'mocks/arbitrum/txnBatches';
import { ENVS_MAP } from 'playwright/fixtures/mockEnvs';
import { test, expect } from 'playwright/lib';

import LatestArbitrumL2Batches from './LatestArbitrumL2Batches';

test('default view +@mobile +@dark-mode', async({ render, mockEnvs, mockApiResponse }) => {
await mockEnvs(ENVS_MAP.arbitrumRollup);
await mockApiResponse('homepage_arbitrum_l2_batches', { items: [ finalized, unfinalized ] });

const component = await render(<LatestArbitrumL2Batches/>);
await expect(component).toHaveScreenshot();
});
92 changes: 92 additions & 0 deletions ui/home/latestBatches/LatestArbitrumL2Batches.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
import { Box, Heading, Flex, Text, VStack } from '@chakra-ui/react';
import { useQueryClient } from '@tanstack/react-query';
import { AnimatePresence } from 'framer-motion';
import React from 'react';

import type { SocketMessage } from 'lib/socket/types';
import type { ArbitrumL2TxnBatchesItem } from 'types/api/arbitrumL2';

import { route } from 'nextjs-routes';

import useApiQuery, { getResourceKey } from 'lib/api/useApiQuery';
import useIsMobile from 'lib/hooks/useIsMobile';
import useSocketChannel from 'lib/socket/useSocketChannel';
import useSocketMessage from 'lib/socket/useSocketMessage';
import { ARBITRUM_L2_TXN_BATCHES_ITEM } from 'stubs/arbitrumL2';
import LinkInternal from 'ui/shared/links/LinkInternal';

import LatestBatchItem from './LatestBatchItem';

const LatestArbitrumL2Batches = () => {
const isMobile = useIsMobile();
const batchesMaxCount = isMobile ? 2 : 5;
const queryClient = useQueryClient();

const { data, isPlaceholderData, isError } = useApiQuery('homepage_arbitrum_l2_batches', {
queryOptions: {
placeholderData: { items: Array(batchesMaxCount).fill(ARBITRUM_L2_TXN_BATCHES_ITEM) },
},
});

const handleNewBatchMessage: SocketMessage.NewArbitrumL2Batch['handler'] = React.useCallback((payload) => {
queryClient.setQueryData(getResourceKey('homepage_arbitrum_l2_batches'), (prevData: { items: Array<ArbitrumL2TxnBatchesItem> } | undefined) => {
const newItems = prevData?.items ? [ ...prevData.items ] : [];

if (newItems.some((batch => batch.number === payload.batch.number))) {
return { items: newItems };
}

return { items: [ payload.batch, ...newItems ].sort((b1, b2) => b2.number - b1.number).slice(0, batchesMaxCount) };
});
}, [ queryClient, batchesMaxCount ]);

const channel = useSocketChannel({
topic: 'arbitrum:new_batch',
isDisabled: isPlaceholderData || isError,
});
useSocketMessage({
channel,
event: 'new_arbitrum_batch',
handler: handleNewBatchMessage,
});

let content;

if (isError) {
content = <Text>No data. Please reload page.</Text>;
}

if (data) {
const dataToShow = data.items.slice(0, batchesMaxCount);

content = (
<>
<VStack spacing={ 2 } mb={ 3 } overflow="hidden" alignItems="stretch">
<AnimatePresence initial={ false } >
{ dataToShow.map(((batch, index) => (
<LatestBatchItem
key={ batch.number + (isPlaceholderData ? String(index) : '') }
number={ batch.number }
timestamp={ batch.commitment_transaction.timestamp }
txCount={ batch.transactions_count }
isLoading={ isPlaceholderData }
/>
))) }
</AnimatePresence>
</VStack>
<Flex justifyContent="center">
<LinkInternal fontSize="sm" href={ route({ pathname: '/batches' }) }>View all batches</LinkInternal>
</Flex>
</>
);
}

return (
<Box width={{ base: '100%', lg: '280px' }} flexShrink={ 0 }>
<Heading as="h4" size="sm" mb={ 3 }>Latest batches</Heading>
{ content }
</Box>
);
};

export default LatestArbitrumL2Batches;
Original file line number Diff line number Diff line change
Expand Up @@ -6,21 +6,21 @@ import {
import { motion } from 'framer-motion';
import React from 'react';

import type { ZkEvmL2TxnBatchesItem } from 'types/api/zkEvmL2';

import { route } from 'nextjs-routes';

import BatchEntityL2 from 'ui/shared/entities/block/BatchEntityL2';
import LinkInternal from 'ui/shared/links/LinkInternal';
import ZkEvmL2TxnBatchStatus from 'ui/shared/statusTag/ZkEvmL2TxnBatchStatus';
import TimeAgoWithTooltip from 'ui/shared/TimeAgoWithTooltip';

type Props = {
batch: ZkEvmL2TxnBatchesItem;
isLoading?: boolean;
number: number;
timestamp: string | null;
txCount: number;
status?: React.ReactNode;
isLoading: boolean;
}

const LatestZkevmL2BatchItem = ({ batch, isLoading }: Props) => {
const LatestBatchItem = ({ number, timestamp, txCount, status, isLoading }: Props) => {
return (
<Box
as={ motion.div }
Expand All @@ -37,15 +37,15 @@ const LatestZkevmL2BatchItem = ({ batch, isLoading }: Props) => {
<Flex alignItems="center" overflow="hidden" w="100%" mb={ 3 }>
<BatchEntityL2
isLoading={ isLoading }
number={ batch.number }
number={ number }
tailLength={ 2 }
fontSize="xl"
lineHeight={ 7 }
fontWeight={ 500 }
mr="auto"
/>
<TimeAgoWithTooltip
timestamp={ batch.timestamp }
timestamp={ timestamp }
enableIncrement={ !isLoading }
isLoading={ isLoading }
color="text_secondary"
Expand All @@ -60,18 +60,18 @@ const LatestZkevmL2BatchItem = ({ batch, isLoading }: Props) => {
<Flex alignItems="center">
<Skeleton isLoaded={ !isLoading } mr={ 2 }>Txn</Skeleton>
<LinkInternal
href={ route({ pathname: '/batches/[number]', query: { number: batch.number.toString(), tab: 'txs' } }) }
href={ route({ pathname: '/batches/[number]', query: { number: number.toString(), tab: 'txs' } }) }
isLoading={ isLoading }
>
<Skeleton isLoaded={ !isLoading }>
{ batch.tx_count }
{ txCount }
</Skeleton>
</LinkInternal>
</Flex>
<ZkEvmL2TxnBatchStatus status={ batch.status } isLoading={ isLoading }/>
{ status }
</Flex>
</Box>
);
};

export default LatestZkevmL2BatchItem;
export default LatestBatchItem;
File renamed without changes.
Loading

0 comments on commit df2f9d9

Please sign in to comment.