Commit b9230bfe authored by isstuev's avatar isstuev Committed by isstuev

tx batches page

parent 88277c92
...@@ -19,3 +19,4 @@ export { default as stats } from './stats'; ...@@ -19,3 +19,4 @@ export { default as stats } from './stats';
export { default as suave } from './suave'; export { default as suave } from './suave';
export { default as web3Wallet } from './web3Wallet'; export { default as web3Wallet } from './web3Wallet';
export { default as verifiedTokens } from './verifiedTokens'; export { default as verifiedTokens } from './verifiedTokens';
export { default as zkEvmRollup } from './zkEvmRollup';
import type { Feature } from './types';
import { getEnvValue } from '../utils';
const title = 'ZkEVM rollup (L2) chain';
const config: Feature<{ L1BaseUrl: string; withdrawalUrl?: string }> = (() => {
const L1BaseUrl = getEnvValue(process.env.NEXT_PUBLIC_L1_BASE_URL);
const isZkEvm = getEnvValue(process.env.NEXT_PUBLIC_IS_ZKEVM_L2_NETWORK) === 'true';
if (isZkEvm && L1BaseUrl) {
return Object.freeze({
title,
isEnabled: true,
L1BaseUrl,
});
}
return Object.freeze({
title,
isEnabled: false,
});
})();
export default config;
...@@ -59,6 +59,7 @@ import type { TTxsFilters } from 'types/api/txsFilters'; ...@@ -59,6 +59,7 @@ import type { TTxsFilters } from 'types/api/txsFilters';
import type { TxStateChanges } from 'types/api/txStateChanges'; import type { TxStateChanges } from 'types/api/txStateChanges';
import type { VisualizedContract } from 'types/api/visualization'; import type { VisualizedContract } from 'types/api/visualization';
import type { WithdrawalsResponse, WithdrawalsCounters } from 'types/api/withdrawals'; import type { WithdrawalsResponse, WithdrawalsCounters } from 'types/api/withdrawals';
import type { ZkEvmL2TxnBatchesResponse } from 'types/api/zkEvml2TxnBatches';
import type { ArrayElement } from 'types/utils'; import type { ArrayElement } from 'types/utils';
import config from 'configs/app'; import config from 'configs/app';
...@@ -483,6 +484,15 @@ export const RESOURCES = { ...@@ -483,6 +484,15 @@ export const RESOURCES = {
path: '/api/v2/optimism/txn-batches/count', path: '/api/v2/optimism/txn-batches/count',
}, },
zkevm_l2_txn_batches: {
path: '/api/v2/zkevm/batches',
filterFields: [],
},
zkevm_l2_txn_batches_count: {
path: '/api/v2/zkevm/batches/count',
},
// CONFIGS // CONFIGS
config_backend_version: { config_backend_version: {
path: '/api/v2/config/backend-version', path: '/api/v2/config/backend-version',
...@@ -552,6 +562,7 @@ export type PaginatedResources = 'blocks' | 'block_txs' | ...@@ -552,6 +562,7 @@ export type PaginatedResources = 'blocks' | 'block_txs' |
'token_instance_transfers' | 'token_instance_holders' | 'token_instance_transfers' | 'token_instance_holders' |
'verified_contracts' | 'verified_contracts' |
'l2_output_roots' | 'l2_withdrawals' | 'l2_txn_batches' | 'l2_deposits' | 'l2_output_roots' | 'l2_withdrawals' | 'l2_txn_batches' | 'l2_deposits' |
'zkevm_l2_txn_batches' |
'withdrawals' | 'address_withdrawals' | 'block_withdrawals'; 'withdrawals' | 'address_withdrawals' | 'block_withdrawals';
export type PaginatedResponse<Q extends PaginatedResources> = ResourcePayload<Q>; export type PaginatedResponse<Q extends PaginatedResources> = ResourcePayload<Q>;
...@@ -640,6 +651,8 @@ Q extends 'l2_output_roots_count' ? number : ...@@ -640,6 +651,8 @@ Q extends 'l2_output_roots_count' ? number :
Q extends 'l2_withdrawals_count' ? number : Q extends 'l2_withdrawals_count' ? number :
Q extends 'l2_deposits_count' ? number : Q extends 'l2_deposits_count' ? number :
Q extends 'l2_txn_batches_count' ? number : Q extends 'l2_txn_batches_count' ? number :
Q extends 'zkevm_l2_txn_batches' ? ZkEvmL2TxnBatchesResponse :
Q extends 'zkevm_l2_txn_batches_count' ? number :
Q extends 'config_backend_version' ? BackendVersionConfig : Q extends 'config_backend_version' ? BackendVersionConfig :
never; never;
/* eslint-enable @typescript-eslint/indent */ /* eslint-enable @typescript-eslint/indent */
......
...@@ -71,7 +71,20 @@ export default function useNavItems(): ReturnType { ...@@ -71,7 +71,20 @@ export default function useNavItems(): ReturnType {
// eslint-disable-next-line max-len // eslint-disable-next-line max-len
{ text: 'Verified contracts', nextRoute: { pathname: '/verified-contracts' as const }, icon: verifiedIcon, isActive: pathname === '/verified-contracts' }; { text: 'Verified contracts', nextRoute: { pathname: '/verified-contracts' as const }, icon: verifiedIcon, isActive: pathname === '/verified-contracts' };
if (config.features.rollup.isEnabled) { if (config.features.zkEvmRollup.isEnabled) {
blockchainNavItems = [
[
txs,
blocks,
// eslint-disable-next-line max-len
{ text: 'Txn batches', nextRoute: { pathname: '/zkevm-l2-txn-batches' as const }, icon: txnBatchIcon, isActive: pathname === '/zkevm-l2-txn-batches' || pathname === '/batch/[number]' },
],
[
topAccounts,
verifiedContracts,
],
];
} else if (config.features.rollup.isEnabled) {
blockchainNavItems = [ blockchainNavItems = [
[ [
txs, txs,
......
...@@ -64,6 +64,16 @@ export const L2: GetServerSideProps<Props> = async(context) => { ...@@ -64,6 +64,16 @@ export const L2: GetServerSideProps<Props> = async(context) => {
return base(context); return base(context);
}; };
export const zkEvmL2: GetServerSideProps<Props> = async(context) => {
if (!config.features.zkEvmRollup.isEnabled) {
return {
notFound: true,
};
}
return base(context);
};
export const marketplace: GetServerSideProps<Props> = async(context) => { export const marketplace: GetServerSideProps<Props> = async(context) => {
if (!config.features.marketplace.isEnabled) { if (!config.features.marketplace.isEnabled) {
return { return {
......
...@@ -46,7 +46,8 @@ declare module "nextjs-routes" { ...@@ -46,7 +46,8 @@ declare module "nextjs-routes" {
| StaticRoute<"/txs"> | StaticRoute<"/txs">
| StaticRoute<"/verified-contracts"> | StaticRoute<"/verified-contracts">
| StaticRoute<"/visualize/sol2uml"> | StaticRoute<"/visualize/sol2uml">
| StaticRoute<"/withdrawals">; | StaticRoute<"/withdrawals">
| StaticRoute<"/zkevm-l2-txn-batches">;
interface StaticRoute<Pathname> { interface StaticRoute<Pathname> {
pathname: Pathname; pathname: Pathname;
......
import type { NextPage } from 'next';
import dynamic from 'next/dynamic';
import React from 'react';
import PageNextJs from 'nextjs/PageNextJs';
const ZkEvmL2TxnBatches = dynamic(() => import('ui/pages/ZkEvmL2TxnBatches'), { ssr: false });
const Page: NextPage = () => {
return (
<PageNextJs pathname="/zkevm-l2-txn-batches">
<ZkEvmL2TxnBatches/>
</PageNextJs>
);
};
export default Page;
export { L2 as getServerSideProps } from 'nextjs/getServerSideProps';
import type { ZkEvmL2TxnBatchesItem } from 'types/api/zkEvml2TxnBatches';
import { TX_HASH } from './tx';
export const ZKEVM_L2_TXN_BATCHES_ITEM: ZkEvmL2TxnBatchesItem = {
timestamp: '2023-06-01T14:46:48.000000Z',
status: 'Finalized',
verify_tx_hash: TX_HASH,
sequence_tx_hash: TX_HASH,
number: 5218590,
tx_count: 9,
};
export type ZkEvmL2TxnBatchesItem = {
number: number;
verify_tx_hash: string;
sequence_tx_hash: string;
status: string;
timestamp: string;
tx_count: number;
}
export type ZkEvmL2TxnBatchesResponse = {
items: Array<ZkEvmL2TxnBatchesItem>;
next_page_params: {
number: number;
items_count: number;
} | null;
}
import { test as base, expect } from '@playwright/experimental-ct-react';
import React from 'react';
import { txnBatchesData } from 'mocks/zkevmL2txnBatches/zkevmL2txnBatches';
import contextWithEnvs from 'playwright/fixtures/contextWithEnvs';
import TestApp from 'playwright/TestApp';
import buildApiUrl from 'playwright/utils/buildApiUrl';
import * as configs from 'playwright/utils/configs';
import ZkEvmL2TxnBatches from './ZkEvmL2TxnBatches';
const test = base.extend({
// eslint-disable-next-line @typescript-eslint/no-explicit-any
context: contextWithEnvs(configs.featureEnvs.zkRollup) as any,
});
const BATCHES_API_URL = buildApiUrl('zkevm_l2_txn_batches');
const BATCHES_COUNTERS_API_URL = buildApiUrl('zkevm_l2_txn_batches_count');
test('base view +@mobile', async({ mount, page }) => {
await page.route('https://request-global.czilladx.com/serve/native.php?z=19260bf627546ab7242', (route) => route.fulfill({
status: 200,
body: '',
}));
await page.route(BATCHES_API_URL, (route) => route.fulfill({
status: 200,
body: JSON.stringify(txnBatchesData),
}));
await page.route(BATCHES_COUNTERS_API_URL, (route) => route.fulfill({
status: 200,
body: '9927',
}));
const component = await mount(
<TestApp>
<ZkEvmL2TxnBatches/>
</TestApp>,
);
await expect(component).toHaveScreenshot();
});
import { Hide, Show, Skeleton, Text } from '@chakra-ui/react';
import React from 'react';
import useApiQuery from 'lib/api/useApiQuery';
import { generateListStub } from 'stubs/utils';
import { ZKEVM_L2_TXN_BATCHES_ITEM } from 'stubs/zkEvmL2';
import DataListDisplay from 'ui/shared/DataListDisplay';
import PageTitle from 'ui/shared/Page/PageTitle';
import useQueryWithPages from 'ui/shared/pagination/useQueryWithPages';
import StickyPaginationWithText from 'ui/shared/StickyPaginationWithText';
import ZkEvmTxnBatchesListItem from 'ui/zkEvmL2TxnBatches/ZkEvmTxnBatchesListItem';
import ZkEvmTxnBatchesTable from 'ui/zkEvmL2TxnBatches/ZkEvmTxnBatchesTable';
const ZkEvmL2TxnBatches = () => {
const { data, isError, isPlaceholderData, pagination } = useQueryWithPages({
resourceName: 'zkevm_l2_txn_batches',
options: {
placeholderData: generateListStub<'zkevm_l2_txn_batches'>(
ZKEVM_L2_TXN_BATCHES_ITEM,
50,
{
next_page_params: {
items_count: 50,
number: 9045200,
},
},
),
},
});
const countersQuery = useApiQuery('zkevm_l2_txn_batches_count', {
queryOptions: {
placeholderData: 5231746,
},
});
const content = data?.items ? (
<>
<Show below="lg" ssr={ false }>
{ data.items.map(((item, index) => (
<ZkEvmTxnBatchesListItem
key={ item.number + (isPlaceholderData ? String(index) : '') }
item={ item }
isLoading={ isPlaceholderData }
/>
))) }
</Show>
<Hide below="lg" ssr={ false }><ZkEvmTxnBatchesTable items={ data.items } top={ pagination.isVisible ? 80 : 0 } isLoading={ isPlaceholderData }/></Hide>
</>
) : null;
const text = (() => {
if (countersQuery.isError || isError || !data?.items.length) {
return null;
}
return (
<Skeleton isLoaded={ !countersQuery.isPlaceholderData && !isPlaceholderData } display="flex" flexWrap="wrap">
Tx batch
<Text fontWeight={ 600 } whiteSpace="pre"> #{ data.items[0].number } </Text>to
<Text fontWeight={ 600 } whiteSpace="pre"> #{ data.items[data.items.length - 1].number } </Text>
(total of { countersQuery.data?.toLocaleString() } batches)
</Skeleton>
);
})();
const actionBar = <StickyPaginationWithText text={ text } pagination={ pagination }/>;
return (
<>
<PageTitle title="Tx batches" withTextAd/>
<DataListDisplay
isError={ isError }
items={ data?.items }
emptyText="There are no tx batches."
content={ content }
actionBar={ actionBar }
/>
</>
);
};
export default ZkEvmL2TxnBatches;
import { Skeleton, Text } from '@chakra-ui/react';
import React from 'react';
import type { ZkEvmL2TxnBatchesItem } from 'types/api/zkEvml2TxnBatches';
// import { route } from 'nextjs-routes';
import config from 'configs/app';
import dayjs from 'lib/date/dayjs';
import BlockEntityL2 from 'ui/shared/entities/block/BlockEntityL2';
import TxEntityL1 from 'ui/shared/entities/tx/TxEntityL1';
import LinkInternal from 'ui/shared/LinkInternal';
import ListItemMobileGrid from 'ui/shared/ListItemMobile/ListItemMobileGrid';
const feature = config.features.rollup;
type Props = { item: ZkEvmL2TxnBatchesItem; isLoading?: boolean };
const ZkEvmTxnBatchesListItem = ({ item, isLoading }: Props) => {
const timeAgo = dayjs(item.timestamp).fromNow();
if (!feature.isEnabled) {
return null;
}
return (
<ListItemMobileGrid.Container gridTemplateColumns="110px auto">
<ListItemMobileGrid.Label isLoading={ isLoading }>Batch #</ListItemMobileGrid.Label>
<ListItemMobileGrid.Value>
<BlockEntityL2
isLoading={ isLoading }
number={ item.number }
fontSize="sm"
lineHeight={ 5 }
fontWeight={ 600 }
// fix after implementing batch page
href="#"
/>
</ListItemMobileGrid.Value>
<ListItemMobileGrid.Label isLoading={ isLoading }>Status</ListItemMobileGrid.Label>
<ListItemMobileGrid.Value>
<Skeleton isLoaded={ !isLoading }>
{ /* Not sertain how to display status */ }
{ item.status }
</Skeleton>
</ListItemMobileGrid.Value>
<ListItemMobileGrid.Label isLoading={ isLoading }>Age</ListItemMobileGrid.Label>
<ListItemMobileGrid.Value>
<Skeleton isLoaded={ !isLoading } display="inline-block">{ timeAgo }</Skeleton>
</ListItemMobileGrid.Value>
<ListItemMobileGrid.Label isLoading={ isLoading }>Txn count</ListItemMobileGrid.Label>
<ListItemMobileGrid.Value>
<LinkInternal
// fix after implementing batch page
href="#"
// href={ route({ pathname: '/block/[height_or_hash]', query: { height_or_hash: item.l2_block_number.toString(), tab: 'txs' } }) }
isLoading={ isLoading }
fontWeight={ 600 }
>
<Skeleton isLoaded={ !isLoading } minW="40px">
{ item.tx_count }
</Skeleton>
</LinkInternal>
</ListItemMobileGrid.Value>
<ListItemMobileGrid.Label isLoading={ isLoading }>Verify Tx Has</ListItemMobileGrid.Label>
<ListItemMobileGrid.Value>
{ item.verify_tx_hash ? (
<TxEntityL1
isLoading={ isLoading }
hash={ item.verify_tx_hash }
fontSize="sm"
lineHeight={ 5 }
maxW="100%"
/>
) : <Text>pending</Text> }
{ /* Not sertain how to display pending state */ }
</ListItemMobileGrid.Value>
<ListItemMobileGrid.Label isLoading={ isLoading }>Sequence hash</ListItemMobileGrid.Label>
<ListItemMobileGrid.Value>
{ item.sequence_tx_hash ? (
<TxEntityL1
isLoading={ isLoading }
hash={ item.sequence_tx_hash }
fontSize="sm"
lineHeight={ 5 }
maxW="100%"
/>
) : <Text>pending</Text> }
{ /* Not sertain how to display pending state */ }
</ListItemMobileGrid.Value>
</ListItemMobileGrid.Container>
);
};
export default ZkEvmTxnBatchesListItem;
import { Table, Tbody, Th, Tr } from '@chakra-ui/react';
import React from 'react';
import type { ZkEvmL2TxnBatchesItem } from 'types/api/zkEvml2TxnBatches';
import { default as Thead } from 'ui/shared/TheadSticky';
import ZkEvmTxnBatchesTableItem from './ZkEvmTxnBatchesTableItem';
type Props = {
items: Array<ZkEvmL2TxnBatchesItem>;
top: number;
isLoading?: boolean;
}
const TxnBatchesTable = ({ items, top, isLoading }: Props) => {
return (
<Table variant="simple" size="sm" minW="850px">
<Thead top={ top }>
<Tr>
<Th width="170px">Batch #</Th>
<Th width="150px">Status</Th>
<Th width="150px">Age</Th>
<Th width="170px">Txn count</Th>
<Th width="50%">Verify Tx Has</Th>
<Th width="50%">Sequence hash</Th>
</Tr>
</Thead>
<Tbody>
{ items.map((item, index) => (
<ZkEvmTxnBatchesTableItem
key={ item.number + (isLoading ? String(index) : '') }
item={ item }
isLoading={ isLoading }
/>
)) }
</Tbody>
</Table>
);
};
export default TxnBatchesTable;
import { Td, Tr, Text, Skeleton } from '@chakra-ui/react';
import React from 'react';
import type { ZkEvmL2TxnBatchesItem } from 'types/api/zkEvml2TxnBatches';
// import { route } from 'nextjs-routes';
import config from 'configs/app';
import dayjs from 'lib/date/dayjs';
import BlockEntityL2 from 'ui/shared/entities/block/BlockEntityL2';
import TxEntityL1 from 'ui/shared/entities/tx/TxEntityL1';
import LinkInternal from 'ui/shared/LinkInternal';
const feature = config.features.rollup;
type Props = { item: ZkEvmL2TxnBatchesItem; isLoading?: boolean };
const TxnBatchesTableItem = ({ item, isLoading }: Props) => {
const timeAgo = dayjs(item.timestamp).fromNow();
if (!feature.isEnabled) {
return null;
}
return (
<Tr>
<Td>
<BlockEntityL2
isLoading={ isLoading }
number={ item.number }
fontSize="sm"
lineHeight={ 5 }
fontWeight={ 600 }
/>
</Td>
<Td>
<Skeleton isLoaded={ !isLoading }>
<span>{ item.status }</span>
</Skeleton>
</Td>
<Td>
<Skeleton isLoaded={ !isLoading } color="text_secondary">
<span>{ timeAgo }</span>
</Skeleton>
</Td>
<Td>
<LinkInternal
// href={ route({ pathname: '/block/[height_or_hash]', query: { height_or_hash: item.l2_block_number.toString(), tab: 'txs' } }) }
href="#"
isLoading={ isLoading }
>
<Skeleton isLoaded={ !isLoading } minW="40px" my={ 1 }>
{ item.tx_count }
</Skeleton>
</LinkInternal>
</Td>
<Td pr={ 12 }>
{ item.verify_tx_hash ? (
<TxEntityL1
isLoading={ isLoading }
hash={ item.verify_tx_hash }
fontSize="sm"
lineHeight={ 5 }
maxW="100%"
/>
) : <Text>pending</Text> }
</Td>
<Td pr={ 12 }>
{ item.sequence_tx_hash ? (
<TxEntityL1
isLoading={ isLoading }
hash={ item.sequence_tx_hash }
fontSize="sm"
lineHeight={ 5 }
maxW="100%"
/>
) : <Text>pending</Text> }
</Td>
</Tr>
);
};
export default TxnBatchesTableItem;
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