Commit 08148d64 authored by isstuev's avatar isstuev

txBatches

parent aa932910
...@@ -36,6 +36,7 @@ import type { ...@@ -36,6 +36,7 @@ import type {
import type { TokensResponse, TokensFilters, TokenInstanceTransferResponse } from 'types/api/tokens'; import type { TokensResponse, TokensFilters, TokenInstanceTransferResponse } from 'types/api/tokens';
import type { TokenTransferResponse, TokenTransferFilters } from 'types/api/tokenTransfer'; import type { TokenTransferResponse, TokenTransferFilters } from 'types/api/tokenTransfer';
import type { TransactionsResponseValidated, TransactionsResponsePending, Transaction } from 'types/api/transaction'; import type { TransactionsResponseValidated, TransactionsResponsePending, Transaction } from 'types/api/transaction';
import type { TxnBatchesResponse } from 'types/api/txnBatches';
import type { TTxsFilters } from 'types/api/txsFilters'; import type { TTxsFilters } from 'types/api/txsFilters';
import type { VisualizedContract } from 'types/api/visualization'; import type { VisualizedContract } from 'types/api/visualization';
import type { WithdrawalsResponse } from 'types/api/withdrawals'; import type { WithdrawalsResponse } from 'types/api/withdrawals';
...@@ -377,6 +378,12 @@ export const RESOURCES = { ...@@ -377,6 +378,12 @@ export const RESOURCES = {
filterFields: [], filterFields: [],
}, },
txn_batches: {
path: '/api/v2/optimism/txn-batches',
paginationFields: [ 'block_number' as const, 'items_count' as const ],
filterFields: [],
},
// DEPRECATED // DEPRECATED
old_api: { old_api: {
path: '/api', path: '/api',
...@@ -436,7 +443,7 @@ export type PaginatedResources = 'blocks' | 'block_txs' | ...@@ -436,7 +443,7 @@ export type PaginatedResources = 'blocks' | 'block_txs' |
'token_transfers' | 'token_holders' | 'token_inventory' | 'tokens' | 'token_transfers' | 'token_holders' | 'token_inventory' | 'tokens' |
'token_instance_transfers' | 'token_instance_transfers' |
'verified_contracts' | 'verified_contracts' |
'output_roots' | 'withdrawals'; 'output_roots' | 'withdrawals' | 'txn_batches';
export type PaginatedResponse<Q extends PaginatedResources> = ResourcePayload<Q>; export type PaginatedResponse<Q extends PaginatedResources> = ResourcePayload<Q>;
...@@ -500,6 +507,7 @@ Q extends 'visualize_sol2uml' ? VisualizedContract : ...@@ -500,6 +507,7 @@ Q extends 'visualize_sol2uml' ? VisualizedContract :
Q extends 'contract_verification_config' ? SmartContractVerificationConfig : Q extends 'contract_verification_config' ? SmartContractVerificationConfig :
Q extends 'output_roots' ? OutputRootsResponse : Q extends 'output_roots' ? OutputRootsResponse :
Q extends 'withdrawals' ? WithdrawalsResponse : Q extends 'withdrawals' ? WithdrawalsResponse :
Q extends 'txn_batches' ? TxnBatchesResponse :
never; never;
/* eslint-enable @typescript-eslint/indent */ /* eslint-enable @typescript-eslint/indent */
......
export const txnBatchesData = {
items: [
{
epoch_number: 8547349,
l1_tx_hashes: [
'0x5bc94d02b65743dfaa9e10a2d6e175aff2a05cce2128c8eaf848bd84ab9325c5',
'0x92a51bc623111dbb91f243e3452e60fab6f090710357f9d9b75ac8a0f67dfd9d',
],
l1_tx_timestamp: '2023-02-24T10:16:12.000000Z',
l2_block_number: 5902836,
tx_count: 0,
},
{
epoch_number: 8547348,
l1_tx_hashes: [
'0xc45f846ee28ce9ba116ce2d378d3dd00b55d324b833b3ecd4241c919c572c4aa',
],
l1_tx_timestamp: '2023-02-24T10:16:00.000000Z',
l2_block_number: 5902835,
tx_count: 0,
},
{
epoch_number: 8547348,
l1_tx_hashes: [
'0x48139721f792d3a68c3781b4cf50e66e8fc7dbb38adff778e09066ea5be9adb8',
],
l1_tx_timestamp: '2023-02-24T10:16:00.000000Z',
l2_block_number: 5902834,
tx_count: 0,
},
],
next_page_params: {
block_number: 5902834,
items_count: 50,
},
total: 1235016,
};
import type { NextPage } from 'next';
import Head from 'next/head';
import React from 'react';
import getNetworkTitle from 'lib/networks/getNetworkTitle';
import TxnBatches from 'ui/pages/TxnBatches';
const TxnBatchesPage: NextPage = () => {
const title = getNetworkTitle();
return (
<>
<Head>
<title>{ title }</title>
</Head>
<TxnBatches/>
</>
);
};
export default TxnBatchesPage;
export { getServerSideProps } from 'lib/next/getServerSidePropsL2';
export type TxnBatchesItem = {
epoch_number: number;
l1_tx_hashes: Array<string>;
l1_tx_timestamp: string;
l2_block_number: number;
tx_count: number;
}
export type TxnBatchesResponse = {
items: Array<TxnBatchesItem>;
total: number;
next_page_params: {
index: number;
items_count: number;
};
}
...@@ -36,6 +36,7 @@ declare module "nextjs-routes" { ...@@ -36,6 +36,7 @@ declare module "nextjs-routes" {
| DynamicRoute<"/token/[hash]/instance/[id]", { "hash": string; "id": string }> | DynamicRoute<"/token/[hash]/instance/[id]", { "hash": string; "id": string }>
| StaticRoute<"/tokens"> | StaticRoute<"/tokens">
| DynamicRoute<"/tx/[hash]", { "hash": string }> | DynamicRoute<"/tx/[hash]", { "hash": string }>
| StaticRoute<"/txn-batches">
| StaticRoute<"/txs"> | StaticRoute<"/txs">
| StaticRoute<"/verified-contracts"> | StaticRoute<"/verified-contracts">
| StaticRoute<"/visualize/sol2uml"> | StaticRoute<"/visualize/sol2uml">
......
import { test, expect } from '@playwright/experimental-ct-react';
import React from 'react';
import { txnBatchesData } from 'mocks/txnBatches/txnBatches';
import TestApp from 'playwright/TestApp';
import buildApiUrl from 'playwright/utils/buildApiUrl';
import TxnBatches from './TxnBatches';
const TXN_BATCHES_API_URL = buildApiUrl('txn_batches');
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(TXN_BATCHES_API_URL, (route) => route.fulfill({
status: 200,
body: JSON.stringify(txnBatchesData),
}));
const component = await mount(
<TestApp>
<TxnBatches/>
</TestApp>,
);
await expect(component.locator('main')).toHaveScreenshot();
});
import { Flex, Hide, Show, Skeleton, Text } from '@chakra-ui/react';
import React from 'react';
import useIsMobile from 'lib/hooks/useIsMobile';
import useQueryWithPages from 'lib/hooks/useQueryWithPages';
import { nbsp } from 'lib/html-entities';
import ActionBar from 'ui/shared/ActionBar';
import DataFetchAlert from 'ui/shared/DataFetchAlert';
import Page from 'ui/shared/Page/Page';
import PageTitle from 'ui/shared/Page/PageTitle';
import Pagination from 'ui/shared/Pagination';
import SkeletonList from 'ui/shared/skeletons/SkeletonList';
import SkeletonTable from 'ui/shared/skeletons/SkeletonTable';
import TxnBatchesListItem from 'ui/txnBatches/TxnBatchesListItem';
import TxnBatchesTable from 'ui/txnBatches/TxnBatchesTable';
const TxnBatches = () => {
const isMobile = useIsMobile();
const { data, isError, isLoading, isPaginationVisible, pagination } = useQueryWithPages({
resourceName: 'txn_batches',
});
const content = (() => {
if (isError) {
return <DataFetchAlert/>;
}
if (isLoading) {
return (
<>
<Skeleton w={{ base: '100%', lg: '400px' }} h={{ base: '48px', lg: '26px' }} mb={{ base: 6, lg: 7 }} mt={{ base: 0, lg: 7 }}/>
<SkeletonList display={{ base: 'block', lg: 'none' }}/>
<SkeletonTable display={{ base: 'none', lg: 'block' }} columns={ [ '170px', '170px', '160px', '100%', '150px' ] }/>
</>
);
}
const text = (
<Flex mb={{ base: 6, lg: 0 }} flexWrap="wrap">
Tx batch (L2 block)
<Text fontWeight={ 600 } whiteSpace="pre"> #{ data.items[0].l2_block_number } </Text>to
<Text fontWeight={ 600 } whiteSpace="pre"> #{ data.items[data.items.length - 1].l2_block_number } </Text>
(total of { data.total.toLocaleString('en') } batches)
</Flex>
);
return (
<>
{ (isMobile || !isPaginationVisible) && text }
{ isPaginationVisible && (
<ActionBar mt={ -6 }>
<Flex alignItems="center" justifyContent="space-between" w="100%">
{ !isMobile && text }
<Pagination ml="auto" { ...pagination }/>
</Flex>
</ActionBar>
) }
<Show below="lg" ssr={ false }>{ data.items.map((item => <TxnBatchesListItem key={ item.l2_block_number } item={ item }/>)) }</Show>
<Hide below="lg" ssr={ false }><TxnBatchesTable items={ data.items } top={ isPaginationVisible ? 80 : 0 }/></Hide>
</>
);
})();
return (
<Page>
<PageTitle text={ `Tx batches (L2${ nbsp }blocks)` } withTextAd/>
{ content }
</Page>
);
};
export default TxnBatches;
import { Box, Flex, Text, HStack, Icon, VStack } from '@chakra-ui/react';
import { route } from 'nextjs-routes';
import React from 'react';
import type { TxnBatchesItem } from 'types/api/txnBatches';
import appConfig from 'configs/app/config';
import txIcon from 'icons/transactions.svg';
import txBatchIcon from 'icons/txBatch.svg';
import useTimeAgoIncrement from 'lib/hooks/useTimeAgoIncrement';
import HashStringShortenDynamic from 'ui/shared/HashStringShortenDynamic';
import LinkExternal from 'ui/shared/LinkExternal';
import LinkInternal from 'ui/shared/LinkInternal';
import ListItemMobile from 'ui/shared/ListItemMobile';
type Props = { item: TxnBatchesItem };
const TxnBatchesListItem = ({ item }: Props) => {
const timeAgo = useTimeAgoIncrement(item.l1_tx_timestamp, false);
return (
<ListItemMobile rowGap={ 3 }>
<Flex alignItems="center" justifyContent="space-between" w="100%">
<LinkInternal
fontWeight={ 600 }
display="flex"
width="fit-content"
alignItems="center"
href={ route({ pathname: '/block/[height]', query: { height: item.l2_block_number.toString() } }) }
>
<Icon as={ txBatchIcon } boxSize={ 6 } mr={ 1 }/>
{ item.l2_block_number }
</LinkInternal>
<Text variant="secondary" fontWeight="400" fontSize="sm">{ timeAgo }</Text>
</Flex>
<HStack spacing={ 3 } width="100%">
<Text fontSize="sm" fontWeight={ 500 } whiteSpace="nowrap">L2 block txn count</Text>
<LinkInternal href={ route({ pathname: '/block/[height]', query: { height: item.l2_block_number.toString(), tab: 'txs' } }) }>
{ item.tx_count }
</LinkInternal>
</HStack>
<HStack spacing={ 3 }>
<Text fontSize="sm" fontWeight={ 500 }>Epoch number</Text>
<LinkExternal href={ appConfig.L2.L1BaseUrl + route({ pathname: '/block/[height]', query: { height: item.epoch_number.toString() } }) }>
{ item.epoch_number }
</LinkExternal>
</HStack>
<VStack spacing={ 3 } w="100%">
{ item.l1_tx_hashes.map(hash => (
<LinkExternal
w="100%"
href={ appConfig.L2.L1BaseUrl + route({ pathname: '/tx/[hash]', query: { hash: hash } }) }
key={ hash }
>
<Icon as={ txIcon } boxSize={ 6 } mr={ 1 }/>
<Box w="calc(100% - 36px)" overflow="hidden" whiteSpace="nowrap"><HashStringShortenDynamic hash={ hash }/></Box>
</LinkExternal>
)) }
</VStack>
</ListItemMobile>
);
};
export default TxnBatchesListItem;
import { Table, Tbody, Th, Tr } from '@chakra-ui/react';
import React from 'react';
import type { TxnBatchesItem } from 'types/api/txnBatches';
import { default as Thead } from 'ui/shared/TheadSticky';
import TxnBatchesTableItem from './TxnBatchesTableItem';
type Props = {
items: Array<TxnBatchesItem>;
top: number;
}
const TxnBatchesTable = ({ items, top }: Props) => {
return (
<Table variant="simple" size="sm" minW="850px">
<Thead top={ top }>
<Tr>
<Th width="170px">L2 block #</Th>
<Th width="170px">L2 block txn count</Th>
<Th width="160px">Epoch number</Th>
<Th width="100%">L1 txn hash</Th>
<Th width="150px">Age</Th>
</Tr>
</Thead>
<Tbody>
{ items.map((item) => (
<TxnBatchesTableItem key={ item.l2_block_number } item={ item }/>
)) }
</Tbody>
</Table>
);
};
export default TxnBatchesTable;
import { Box, Td, Tr, Text, Icon, VStack } from '@chakra-ui/react';
import { route } from 'nextjs-routes';
import React from 'react';
import type { TxnBatchesItem } from 'types/api/txnBatches';
import appConfig from 'configs/app/config';
import txIcon from 'icons/transactions.svg';
import txBatchIcon from 'icons/txBatch.svg';
import useTimeAgoIncrement from 'lib/hooks/useTimeAgoIncrement';
import HashStringShortenDynamic from 'ui/shared/HashStringShortenDynamic';
import LinkExternal from 'ui/shared/LinkExternal';
import LinkInternal from 'ui/shared/LinkInternal';
type Props = { item: TxnBatchesItem };
const TxnBatchesTableItem = ({ item }: Props) => {
const timeAgo = useTimeAgoIncrement(item.l1_tx_timestamp, false);
return (
<Tr>
<Td>
<LinkInternal
fontWeight={ 600 }
display="flex"
width="fit-content"
alignItems="center"
href={ route({ pathname: '/block/[height]', query: { height: item.l2_block_number.toString() } }) }
>
<Icon as={ txBatchIcon } boxSize={ 6 } mr={ 1 }/>
{ item.l2_block_number }
</LinkInternal>
</Td>
<Td>
<LinkInternal
href={ route({ pathname: '/block/[height]', query: { height: item.l2_block_number.toString(), tab: 'txs' } }) }
lineHeight="24px"
>
{ item.tx_count }
</LinkInternal>
</Td>
<Td>
<LinkExternal
href={ appConfig.L2.L1BaseUrl + route({ pathname: '/block/[height]', query: { height: item.epoch_number.toString() } }) }
fontWeight={ 600 }
lineHeight="24px"
>
{ item.epoch_number }
</LinkExternal>
</Td>
<Td pr={ 12 }>
<VStack spacing={ 3 }>
{ item.l1_tx_hashes.map(hash => (
<LinkExternal w="100%" key={ hash } href={ appConfig.L2.L1BaseUrl + route({ pathname: '/tx/[hash]', query: { hash: hash } }) }>
<Icon as={ txIcon } boxSize={ 6 } mr={ 1 }/>
<Box w="calc(100% - 36px)" overflow="hidden" whiteSpace="nowrap"><HashStringShortenDynamic hash={ hash }/></Box>
</LinkExternal>
)) }
</VStack>
</Td>
<Td>
<Text variant="secondary" lineHeight="24px">{ timeAgo }</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