Commit 69fb150d authored by isstuev's avatar isstuev

add api

parent f0e39483
...@@ -29,5 +29,5 @@ NEXT_PUBLIC_STATS_API_HOST=https://stats-test.aws-k8s.blockscout.com ...@@ -29,5 +29,5 @@ NEXT_PUBLIC_STATS_API_HOST=https://stats-test.aws-k8s.blockscout.com
# l2 config # l2 config
NEXT_PUBLIC_IS_L2_NETWORK=true NEXT_PUBLIC_IS_L2_NETWORK=true
NEXT_PUBLIC_L1_BASE_URL=https://blockscout-main.test.aws-k8s.blockscout.com/ NEXT_PUBLIC_L1_BASE_URL=https://blockscout-main.test.aws-k8s.blockscout.com
NEXT_PUBLIC_L2_WITHDRAWAL_URL=https://app.optimism.io/bridge/withdraw NEXT_PUBLIC_L2_WITHDRAWAL_URL=https://app.optimism.io/bridge/withdraw
\ No newline at end of file
This diff is collapsed.
...@@ -320,7 +320,7 @@ frontend: ...@@ -320,7 +320,7 @@ frontend:
- "/csv-export" - "/csv-export"
- "/verified-contracts" - "/verified-contracts"
- "/graphiql" - "/graphiql"
- "/output-roots"
resources: resources:
limits: limits:
memory: memory:
......
...@@ -40,6 +40,7 @@ frontend: ...@@ -40,6 +40,7 @@ frontend:
- "/csv-export" - "/csv-export"
- "/verified-contracts" - "/verified-contracts"
- "/graphiql" - "/graphiql"
- "/output-roots"
resources: resources:
limits: limits:
......
...@@ -21,6 +21,7 @@ import type { VerifiedContractsResponse, VerifiedContractsFilters, VerifiedContr ...@@ -21,6 +21,7 @@ import type { VerifiedContractsResponse, VerifiedContractsFilters, VerifiedContr
import type { IndexingStatus } from 'types/api/indexingStatus'; import type { IndexingStatus } from 'types/api/indexingStatus';
import type { InternalTransactionsResponse } from 'types/api/internalTransaction'; import type { InternalTransactionsResponse } from 'types/api/internalTransaction';
import type { LogsResponseTx, LogsResponseAddress } from 'types/api/log'; import type { LogsResponseTx, LogsResponseAddress } from 'types/api/log';
import type { OutputRootsResponse } from 'types/api/outputRoots';
import type { RawTracesResponse } from 'types/api/rawTrace'; import type { RawTracesResponse } from 'types/api/rawTrace';
import type { SearchResult, SearchResultFilters } from 'types/api/search'; import type { SearchResult, SearchResultFilters } from 'types/api/search';
import type { Counters, StatsCharts, StatsChart, HomeStats } from 'types/api/stats'; import type { Counters, StatsCharts, StatsChart, HomeStats } from 'types/api/stats';
...@@ -360,6 +361,12 @@ export const RESOURCES = { ...@@ -360,6 +361,12 @@ export const RESOURCES = {
// GraphQL // GraphQL
graphql: { graphql: {
path: '/graphql', path: '/graphql',
},
output_roots: {
path: '/api/v2/optimism/output-roots',
paginationFields: [ 'index' as const, 'items_count' as const ],
filterFields: [],
}, },
// DEPRECATED // DEPRECATED
...@@ -420,7 +427,8 @@ export type PaginatedResources = 'blocks' | 'block_txs' | ...@@ -420,7 +427,8 @@ export type PaginatedResources = 'blocks' | 'block_txs' |
'address_logs' | 'address_tokens' | 'address_logs' | 'address_tokens' |
'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';
export type PaginatedResponse<Q extends PaginatedResources> = ResourcePayload<Q>; export type PaginatedResponse<Q extends PaginatedResources> = ResourcePayload<Q>;
...@@ -482,6 +490,7 @@ Q extends 'verified_contracts' ? VerifiedContractsResponse : ...@@ -482,6 +490,7 @@ Q extends 'verified_contracts' ? VerifiedContractsResponse :
Q extends 'verified_contracts_counters' ? VerifiedContractsCounters : Q extends 'verified_contracts_counters' ? VerifiedContractsCounters :
Q extends 'visualize_sol2uml' ? VisualizedContract : Q extends 'visualize_sol2uml' ? VisualizedContract :
Q extends 'contract_verification_config' ? SmartContractVerificationConfig : Q extends 'contract_verification_config' ? SmartContractVerificationConfig :
Q extends 'output_roots' ? OutputRootsResponse :
never; never;
/* eslint-enable @typescript-eslint/indent */ /* eslint-enable @typescript-eslint/indent */
......
export const outputRootsData = {
items: [
{
l1_block_number: 8456113,
l1_timestamp: '2022-02-08T12:08:48.000000Z',
l1_tx_hash: '0x19455a53758d5de89070164ff09c40d93f1b4447e721090f03aa150f6159265a',
l2_block_number: 5214988,
l2_output_index: 9926,
output_root: '0xa7de9bd3986ce5ca8de9f0ab6c7473f4cebe225fb13b57cc5c8472de84a8bab3',
},
{
l1_block_number: 8456099,
l1_timestamp: '2022-02-08T12:05:24.000000Z',
l1_tx_hash: '0x6aa081e8e33a085e4ec7124fcd8a5f7d36aac0828f176e80d4b70e313a11695b',
l2_block_number: 5214868,
l2_output_index: 9925,
output_root: '0x4ec2822d2f7b4f834d693d88f8a4cf15899882915980a21756d29cfd9f9f3898',
},
{
l1_block_number: 8456078,
l1_timestamp: '2022-02-08T12:00:48.000000Z',
l1_tx_hash: '0x4238988b0959e41a7b09cef67f58698e05e3bcc29b8d2f60e6c77dc68c91f16e',
l2_block_number: 5214748,
l2_output_index: 9924,
output_root: '0x78b2e13c20f4bbfb4a008127edaaf25aa476f933669edd4856305bf4ab64a92b',
},
],
next_page_params: {
index: 9877,
items_count: 50,
},
total: 9927,
};
...@@ -9,6 +9,7 @@ export type OutputRootsItem = { ...@@ -9,6 +9,7 @@ export type OutputRootsItem = {
export type OutputRootsResponse = { export type OutputRootsResponse = {
items: Array<OutputRootsItem>; items: Array<OutputRootsItem>;
total: number;
next_page_params: { next_page_params: {
index: number; index: number;
items_count: number; items_count: number;
......
...@@ -29,6 +29,7 @@ declare module "nextjs-routes" { ...@@ -29,6 +29,7 @@ declare module "nextjs-routes" {
| StaticRoute<"/graphiql"> | StaticRoute<"/graphiql">
| StaticRoute<"/"> | StaticRoute<"/">
| StaticRoute<"/login"> | StaticRoute<"/login">
| StaticRoute<"/output-roots">
| StaticRoute<"/search-results"> | StaticRoute<"/search-results">
| StaticRoute<"/stats"> | StaticRoute<"/stats">
| DynamicRoute<"/token/[hash]", { "hash": string }> | DynamicRoute<"/token/[hash]", { "hash": string }>
......
/* eslint-disable @typescript-eslint/naming-convention */ /* eslint-disable @typescript-eslint/naming-convention */
import { Box, Flex, Text, HStack, Icon } from '@chakra-ui/react'; import { Box, Flex, Text, HStack, Icon } from '@chakra-ui/react';
import { route } from 'nextjs-routes';
import React from 'react'; import React from 'react';
import type { OutputRootsItem } from 'types/api/outputRoots'; import type { OutputRootsItem } from 'types/api/outputRoots';
import appConfig from 'configs/app/config';
import txIcon from 'icons/transactions.svg'; import txIcon from 'icons/transactions.svg';
import useTimeAgoIncrement from 'lib/hooks/useTimeAgoIncrement'; import useTimeAgoIncrement from 'lib/hooks/useTimeAgoIncrement';
import link from 'lib/link/link';
import CopyToClipboard from 'ui/shared/CopyToClipboard'; import CopyToClipboard from 'ui/shared/CopyToClipboard';
import HashStringShortenDynamic from 'ui/shared/HashStringShortenDynamic'; import HashStringShortenDynamic from 'ui/shared/HashStringShortenDynamic';
import LinkExternal from 'ui/shared/LinkExternal'; import LinkExternal from 'ui/shared/LinkExternal';
...@@ -22,9 +23,6 @@ const OutputRootsListItem = ({ ...@@ -22,9 +23,6 @@ const OutputRootsListItem = ({
l1_tx_hash, l1_tx_hash,
output_root, output_root,
}: Props) => { }: Props) => {
const url = link('tx', { id: l1_tx_hash });
const timeAgo = useTimeAgoIncrement(l1_timestamp, false); const timeAgo = useTimeAgoIncrement(l1_timestamp, false);
return ( return (
...@@ -35,7 +33,7 @@ const OutputRootsListItem = ({ ...@@ -35,7 +33,7 @@ const OutputRootsListItem = ({
</Flex> </Flex>
<HStack spacing={ 3 }> <HStack spacing={ 3 }>
<Text fontSize="sm" fontWeight={ 500 }>L2 block</Text> <Text fontSize="sm" fontWeight={ 500 }>L2 block</Text>
<LinkInternal display="flex" alignItems="center" href={ link('block', { id: l2_block_number.toString() }) }> <LinkInternal display="flex" alignItems="center" href={ route({ pathname: '/block/[height]', query: { height: l2_block_number.toString() } }) }>
{ l2_block_number } { l2_block_number }
</LinkInternal> </LinkInternal>
</HStack> </HStack>
...@@ -46,7 +44,7 @@ const OutputRootsListItem = ({ ...@@ -46,7 +44,7 @@ const OutputRootsListItem = ({
<CopyToClipboard text={ output_root } ml={ 2 }/> <CopyToClipboard text={ output_root } ml={ 2 }/>
</Flex> </Flex>
</HStack> </HStack>
<LinkExternal href={ url } w="100%"> <LinkExternal w="100%" href={ appConfig.L2.L1BaseUrl + route({ pathname: '/tx/[hash]', query: { hash: l1_tx_hash } }) }>
<Icon as={ txIcon } boxSize={ 6 } mr={ 1 }/> <Icon as={ txIcon } boxSize={ 6 } mr={ 1 }/>
<Box w="calc(100% - 36px)" overflow="hidden" whiteSpace="nowrap"><HashStringShortenDynamic hash={ l1_tx_hash }/></Box> <Box w="calc(100% - 36px)" overflow="hidden" whiteSpace="nowrap"><HashStringShortenDynamic hash={ l1_tx_hash }/></Box>
</LinkExternal> </LinkExternal>
......
...@@ -14,14 +14,14 @@ type Props = { ...@@ -14,14 +14,14 @@ type Props = {
const OutputRootsTable = ({ items, top }: Props) => { const OutputRootsTable = ({ items, top }: Props) => {
return ( return (
<Table variant="simple" size="sm"> <Table variant="simple" size="sm" minW="900px">
<Thead top={ top }> <Thead top={ top }>
<Tr> <Tr>
<Th width="130px">L2 output index</Th> <Th width="140px">L2 output index</Th>
<Th width="120px">Age</Th> <Th width="20%">Age</Th>
<Th width="15%">L2 block #</Th> <Th width="20%">L2 block #</Th>
<Th width="45%">L1 txn hash</Th> <Th width="30%">L1 txn hash</Th>
<Th width="35%">Output root</Th> <Th width="30%">Output root</Th>
</Tr> </Tr>
</Thead> </Thead>
<Tbody> <Tbody>
......
/* eslint-disable @typescript-eslint/naming-convention */ /* eslint-disable @typescript-eslint/naming-convention */
import { Box, Flex, Td, Tr, Text, Icon } from '@chakra-ui/react'; import { Box, Flex, Td, Tr, Text, Icon } from '@chakra-ui/react';
import { route } from 'nextjs-routes';
import React from 'react'; import React from 'react';
import type { OutputRootsItem } from 'types/api/outputRoots'; import type { OutputRootsItem } from 'types/api/outputRoots';
...@@ -8,7 +9,6 @@ import appConfig from 'configs/app/config'; ...@@ -8,7 +9,6 @@ import appConfig from 'configs/app/config';
import txIcon from 'icons/transactions.svg'; import txIcon from 'icons/transactions.svg';
import txBatchIcon from 'icons/txBatch.svg'; import txBatchIcon from 'icons/txBatch.svg';
import useTimeAgoIncrement from 'lib/hooks/useTimeAgoIncrement'; import useTimeAgoIncrement from 'lib/hooks/useTimeAgoIncrement';
import link from 'lib/link/link';
import CopyToClipboard from 'ui/shared/CopyToClipboard'; import CopyToClipboard from 'ui/shared/CopyToClipboard';
import HashStringShortenDynamic from 'ui/shared/HashStringShortenDynamic'; import HashStringShortenDynamic from 'ui/shared/HashStringShortenDynamic';
import LinkExternal from 'ui/shared/LinkExternal'; import LinkExternal from 'ui/shared/LinkExternal';
...@@ -23,8 +23,6 @@ const OutputRootsTableItem = ({ ...@@ -23,8 +23,6 @@ const OutputRootsTableItem = ({
l1_tx_hash, l1_tx_hash,
output_root, output_root,
}: Props) => { }: Props) => {
const url = link('tx', { id: l1_tx_hash }, {}, appConfig.l1BaseUrl);
const timeAgo = useTimeAgoIncrement(l1_timestamp, false); const timeAgo = useTimeAgoIncrement(l1_timestamp, false);
return ( return (
...@@ -36,14 +34,19 @@ const OutputRootsTableItem = ({ ...@@ -36,14 +34,19 @@ const OutputRootsTableItem = ({
<Text variant="secondary">{ timeAgo }</Text> <Text variant="secondary">{ timeAgo }</Text>
</Td> </Td>
<Td verticalAlign="middle"> <Td verticalAlign="middle">
<LinkInternal fontWeight={ 600 } display="flex" alignItems="center" href={ link('block', { id: l2_block_number.toString() }) }> <LinkInternal
fontWeight={ 600 }
display="flex"
alignItems="center"
href={ route({ pathname: '/block/[height]', query: { height: l2_block_number.toString() } }) }
>
<Icon as={ txBatchIcon } boxSize={ 6 } mr={ 1 }/> <Icon as={ txBatchIcon } boxSize={ 6 } mr={ 1 }/>
{ l2_block_number } { l2_block_number }
</LinkInternal> </LinkInternal>
</Td> </Td>
<Td verticalAlign="middle" pr={ 12 }> <Td verticalAlign="middle" pr={ 12 }>
<Flex> <Flex>
<LinkExternal href={ url } w="100%"> <LinkExternal w="100%" href={ appConfig.L2.L1BaseUrl + route({ pathname: '/tx/[hash]', query: { hash: l1_tx_hash } }) }>
<Icon as={ txIcon } boxSize={ 6 } mr={ 1 }/> <Icon as={ txIcon } boxSize={ 6 } mr={ 1 }/>
<Box w="calc(100% - 36px)" overflow="hidden" whiteSpace="nowrap"><HashStringShortenDynamic hash={ l1_tx_hash }/></Box> <Box w="calc(100% - 36px)" overflow="hidden" whiteSpace="nowrap"><HashStringShortenDynamic hash={ l1_tx_hash }/></Box>
</LinkExternal> </LinkExternal>
......
import { test, expect } from '@playwright/experimental-ct-react';
import React from 'react';
import { outputRootsData } from 'mocks/outputRoots/outputRoots';
import TestApp from 'playwright/TestApp';
import buildApiUrl from 'playwright/utils/buildApiUrl';
import OutputRoots from './OutputRoots';
const OUTPUT_ROOTS_API_URL = buildApiUrl('output_roots');
test('base view +@dark-mode', 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(OUTPUT_ROOTS_API_URL, (route) => route.fulfill({
status: 200,
body: JSON.stringify(outputRootsData),
}));
const component = await mount(
<TestApp>
<OutputRoots/>
</TestApp>,
);
await expect(component.locator('main')).toHaveScreenshot();
});
import { Flex, Hide, Show, Skeleton, Text } from '@chakra-ui/react'; import { Flex, Hide, Show, Skeleton, Text } from '@chakra-ui/react';
import React from 'react'; import React from 'react';
import dataMock from 'data/outputRoots.json'; import useIsMobile from 'lib/hooks/useIsMobile';
import isBrowser from 'lib/isBrowser'; import useQueryWithPages from 'lib/hooks/useQueryWithPages';
import OutputRootsListItem from 'ui/outputRoots/OutputRootsListItem'; import OutputRootsListItem from 'ui/outputRoots/OutputRootsListItem';
import OutputRootsTable from 'ui/outputRoots/OutputRootsTable'; import OutputRootsTable from 'ui/outputRoots/OutputRootsTable';
import ActionBar from 'ui/shared/ActionBar';
import DataFetchAlert from 'ui/shared/DataFetchAlert';
import Page from 'ui/shared/Page/Page'; import Page from 'ui/shared/Page/Page';
import PageTitle from 'ui/shared/Page/PageTitle'; import PageTitle from 'ui/shared/Page/PageTitle';
import Pagination from 'ui/shared/Pagination';
import SkeletonList from 'ui/shared/skeletons/SkeletonList'; import SkeletonList from 'ui/shared/skeletons/SkeletonList';
import SkeletonTable from 'ui/shared/skeletons/SkeletonTable'; import SkeletonTable from 'ui/shared/skeletons/SkeletonTable';
const OutputRoots = () => { const OutputRoots = () => {
// request!! const isMobile = useIsMobile();
const [ isLoading, setIsLoading ] = React.useState(true);
React.useEffect(() => {
if (isBrowser()) {
setTimeout(() => setIsLoading(false), 2000);
}
}, []);
const data = dataMock; const { data, isError, isLoading, isPaginationVisible, pagination } = useQueryWithPages({
const isPaginationVisible = false; resourceName: 'output_roots',
});
const content = (() => { const content = (() => {
if (isError) {
return <DataFetchAlert/>;
}
if (isLoading) { if (isLoading) {
return ( 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' }}/> <SkeletonList display={{ base: 'block', lg: 'none' }}/>
<SkeletonTable display={{ base: 'none', lg: 'block' }} columns={ [ '130px', '120px', '15%', '45%', '35%' ] }/> <SkeletonTable minW="900px" display={{ base: 'none', lg: 'block' }} columns={ [ '140px', '20%', '20%', '30%', '30%' ] }/>
</> </>
); );
} }
const text = (
<Flex mb={{ base: 6, lg: 0 }} flexWrap="wrap">
L2 output index
<Text fontWeight={ 600 } whiteSpace="pre"> #{ data.items[0].l2_output_index } </Text>to
<Text fontWeight={ 600 } whiteSpace="pre"> #{ data.items[data.items.length - 1].l2_output_index } </Text>
(total of { data.total.toLocaleString('en') } roots)
</Flex>
);
return ( 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 => <OutputRootsListItem key={ item.l2_output_index } { ...item }/>)) }</Show> <Show below="lg" ssr={ false }>{ data.items.map((item => <OutputRootsListItem key={ item.l2_output_index } { ...item }/>)) }</Show>
<Hide below="lg" ssr={ false }><OutputRootsTable items={ data.items } top={ isPaginationVisible ? 80 : 0 }/></Hide> <Hide below="lg" ssr={ false }><OutputRootsTable items={ data.items } top={ isPaginationVisible ? 80 : 0 }/></Hide>
</> </>
...@@ -42,15 +64,6 @@ const OutputRoots = () => { ...@@ -42,15 +64,6 @@ const OutputRoots = () => {
return ( return (
<Page> <Page>
<PageTitle text="Output roots" withTextAd/> <PageTitle text="Output roots" withTextAd/>
{ isLoading ? <Skeleton w="400px" h="26px" mb={ 7 }/> : (
<Flex mb={ 7 } flexWrap="wrap">
L2 output index
<Text fontWeight={ 600 } whiteSpace="pre"> #{ data.items[0].l2_output_index } </Text>to
<Text fontWeight={ 600 } whiteSpace="pre"> #{ data.items[dataMock.items.length - 1].l2_output_index } </Text>
(total of { data.total.toLocaleString('en') } roots)
</Flex>
) }
{ /* Pagination */ }
{ content } { content }
</Page> </Page>
); );
......
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