Commit 7b6d8b2b authored by isstuev's avatar isstuev

deposits

parent 0425a416
...@@ -18,6 +18,7 @@ import type { BlocksResponse, BlockTransactionsResponse, Block, BlockFilters } f ...@@ -18,6 +18,7 @@ import type { BlocksResponse, BlockTransactionsResponse, Block, BlockFilters } f
import type { ChartMarketResponse, ChartTransactionResponse } from 'types/api/charts'; import type { ChartMarketResponse, ChartTransactionResponse } from 'types/api/charts';
import type { SmartContract, SmartContractReadMethod, SmartContractWriteMethod, SmartContractVerificationConfig } from 'types/api/contract'; import type { SmartContract, SmartContractReadMethod, SmartContractWriteMethod, SmartContractVerificationConfig } from 'types/api/contract';
import type { VerifiedContractsResponse, VerifiedContractsFilters, VerifiedContractsCounters } from 'types/api/contracts'; import type { VerifiedContractsResponse, VerifiedContractsFilters, VerifiedContractsCounters } from 'types/api/contracts';
import type { DepositsResponse } from 'types/api/deposits';
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';
...@@ -366,6 +367,16 @@ export const RESOURCES = { ...@@ -366,6 +367,16 @@ export const RESOURCES = {
}, },
// L2 // L2
deposits: {
path: '/api/v2/optimism/deposits',
paginationFields: [ 'nonce' as const, 'items_count' as const ],
filterFields: [],
},
deposits_count: {
path: '/api/v2/optimism/deposits-count',
},
withdrawals: { withdrawals: {
path: '/api/v2/optimism/withdrawals', path: '/api/v2/optimism/withdrawals',
paginationFields: [ 'nonce' as const, 'items_count' as const ], paginationFields: [ 'nonce' as const, 'items_count' as const ],
...@@ -455,7 +466,7 @@ export type PaginatedResources = 'blocks' | 'block_txs' | ...@@ -455,7 +466,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' | 'txn_batches'; 'output_roots' | 'withdrawals' | 'txn_batches' | 'deposits';
export type PaginatedResponse<Q extends PaginatedResources> = ResourcePayload<Q>; export type PaginatedResponse<Q extends PaginatedResources> = ResourcePayload<Q>;
...@@ -519,9 +530,11 @@ Q extends 'visualize_sol2uml' ? VisualizedContract : ...@@ -519,9 +530,11 @@ 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 'deposits' ? DepositsResponse :
Q extends 'txn_batches' ? TxnBatchesResponse : Q extends 'txn_batches' ? TxnBatchesResponse :
Q extends 'output_roots_count' ? number : Q extends 'output_roots_count' ? number :
Q extends 'withdrawals_count' ? number : Q extends 'withdrawals_count' ? number :
Q extends 'deposits_count' ? number :
Q extends 'txn_batches_count' ? number : Q extends 'txn_batches_count' ? number :
never; never;
/* eslint-enable @typescript-eslint/indent */ /* eslint-enable @typescript-eslint/indent */
......
...@@ -7,6 +7,7 @@ import abiIcon from 'icons/ABI.svg'; ...@@ -7,6 +7,7 @@ import abiIcon from 'icons/ABI.svg';
import apiKeysIcon from 'icons/API.svg'; import apiKeysIcon from 'icons/API.svg';
import appsIcon from 'icons/apps.svg'; import appsIcon from 'icons/apps.svg';
import withdrawalsIcon from 'icons/arrows/north-east.svg'; import withdrawalsIcon from 'icons/arrows/north-east.svg';
import depositsIcon from 'icons/arrows/south-east.svg';
import blocksIcon from 'icons/block.svg'; import blocksIcon from 'icons/block.svg';
import gearIcon from 'icons/gear.svg'; import gearIcon from 'icons/gear.svg';
import globeIcon from 'icons/globe-b.svg'; import globeIcon from 'icons/globe-b.svg';
...@@ -21,7 +22,6 @@ import statsIcon from 'icons/stats.svg'; ...@@ -21,7 +22,6 @@ import statsIcon from 'icons/stats.svg';
import tokensIcon from 'icons/token.svg'; import tokensIcon from 'icons/token.svg';
import topAccountsIcon from 'icons/top-accounts.svg'; import topAccountsIcon from 'icons/top-accounts.svg';
import transactionsIcon from 'icons/transactions.svg'; import transactionsIcon from 'icons/transactions.svg';
// import depositsIcon from 'icons/arrows/south-east.svg';
import txnBatchIcon from 'icons/txn_batches.svg'; import txnBatchIcon from 'icons/txn_batches.svg';
import verifiedIcon from 'icons/verified.svg'; import verifiedIcon from 'icons/verified.svg';
import watchlistIcon from 'icons/watchlist.svg'; import watchlistIcon from 'icons/watchlist.svg';
...@@ -103,7 +103,7 @@ export default function useNavItems(): ReturnType { ...@@ -103,7 +103,7 @@ export default function useNavItems(): ReturnType {
[ [
txs, txs,
// eslint-disable-next-line max-len // eslint-disable-next-line max-len
// { text: `Deposits (L1${ rightLineArrow }L2)`, nextRoute: { pathname: '/deposits' as const }, icon: depositsIcon, isActive: pathname === '/deposits', isNewUi: true }, { text: `Deposits (L1${ rightLineArrow }L2)`, nextRoute: { pathname: '/deposits' as const }, icon: depositsIcon, isActive: pathname === '/deposits', isNewUi: true },
// eslint-disable-next-line max-len // eslint-disable-next-line max-len
{ text: `Withdrawals (L2${ rightLineArrow }L1)`, nextRoute: { pathname: '/withdrawals' as const }, icon: withdrawalsIcon, isActive: pathname === '/withdrawals', isNewUi: true }, { text: `Withdrawals (L2${ rightLineArrow }L1)`, nextRoute: { pathname: '/withdrawals' as const }, icon: withdrawalsIcon, isActive: pathname === '/withdrawals', isNewUi: true },
], ],
......
export const data = {
items: [
{
l1_block_number: 8382841,
l1_block_timestamp: '2022-05-27T01:13:48.000000Z',
l1_tx_hash: '0xaf3e5f4ef03eac22a622b3434c5dc9f4465aa291900a86bcf0ad9fb14429f05e',
l1_tx_origin: '0x6197d1eef304eb5284a0f6720f79403b4e9bf3a5',
l2_tx_gas_limit: '2156928',
l2_tx_hash: '0xb9212c76069b926917816767e4c5a0ef80e519b1ac1c3d3fb5818078f4984667',
},
{
l1_block_number: 8382841,
l1_block_timestamp: '2022-05-27T01:13:48.000000Z',
l1_tx_hash: '0xa280f18cc72f9ad904087eb262c236048e935ad184a85bbd042d544c172c10bf',
l1_tx_origin: '0x6197d1eef304eb5284a0f6720f79403b4e9bf3a5',
l2_tx_gas_limit: '1216064',
l2_tx_hash: '0xaaaeb47a78b5c42d870f8d831a683a7cefe1b031a992170b28b43b82bd08318c',
},
{
l1_block_number: 8382834,
l1_block_timestamp: '2022-06-27T01:11:48.000000Z',
l1_tx_hash: '0xfca8cc5440bffa8b975873c02bba3ff3380dd75fbc3260d10179e282cf72d6d4',
l1_tx_origin: '0x6197d1eef304eb5284a0f6720f79403b4e9bf3a5',
l2_tx_gas_limit: '405824',
l2_tx_hash: '0xa0604ebf2614ad708aeefa83f766fb25928dadb5ffb2f45028f5b4f1fa4d9358',
},
],
next_page_params: {
items_count: 50,
l1_block_number: 8382363,
tx_hash: '0x2012f0ce966ce6573e7826e9235f227edf5a2f8382b8d646c979f85a77e15c05',
},
};
import type { NextPage } from 'next';
import Head from 'next/head';
import React from 'react';
import getNetworkTitle from 'lib/networks/getNetworkTitle';
import Deposits from 'ui/pages/Deposits';
const DepositsPage: NextPage = () => {
const title = getNetworkTitle();
return (
<>
<Head>
<title>{ title }</title>
</Head>
<Deposits/>
</>
);
};
export default DepositsPage;
export { getServerSideProps } from 'lib/next/getServerSidePropsL2';
export type DepositsItem = {
l1_block_number: number;
l1_tx_hash: string;
l1_block_timestamp: string;
l1_tx_origin: string;
l2_tx_gas_limit: string;
l2_tx_hash: string;
}
export type DepositsResponse = {
items: Array<DepositsItem>;
next_page_params: {
items_count: number;
l1_block_number: number;
tx_hash: string;
};
total: number;
}
...@@ -25,6 +25,7 @@ declare module "nextjs-routes" { ...@@ -25,6 +25,7 @@ declare module "nextjs-routes" {
| DynamicRoute<"/block/[height]", { "height": string }> | DynamicRoute<"/block/[height]", { "height": string }>
| StaticRoute<"/blocks"> | StaticRoute<"/blocks">
| StaticRoute<"/csv-export"> | StaticRoute<"/csv-export">
| StaticRoute<"/deposits">
| StaticRoute<"/graph"> | StaticRoute<"/graph">
| StaticRoute<"/graphiql"> | StaticRoute<"/graphiql">
| StaticRoute<"/"> | StaticRoute<"/">
......
import { Box, Icon } from '@chakra-ui/react';
import BigNumber from 'bignumber.js';
import { route } from 'nextjs-routes';
import React from 'react';
import type { DepositsItem } from 'types/api/deposits';
import appConfig from 'configs/app/config';
import blockIcon from 'icons/block.svg';
import txIcon from 'icons/transactions.svg';
import dayjs from 'lib/date/dayjs';
import AddressIcon from 'ui/shared/address/AddressIcon';
import HashStringShortenDynamic from 'ui/shared/HashStringShortenDynamic';
import LinkExternal from 'ui/shared/LinkExternal';
import LinkInternal from 'ui/shared/LinkInternal';
import ListItemMobileGrid from 'ui/shared/ListItemMobile/ListItemMobileGrid';
type Props = { item: DepositsItem };
const DepositsListItem = ({ item }: Props) => {
const timeAgo = dayjs(item.l1_block_timestamp).fromNow();
const items = [
{
name: 'L1 block No',
value: (
<LinkExternal
href={ appConfig.L2.L1BaseUrl + route({ pathname: '/block/[height]', query: { height: item.l1_block_number.toString() } }) }
fontWeight={ 600 }
display="inline-flex"
>
<Icon as={ blockIcon } boxSize={ 6 } mr={ 1 }/>
{ item.l1_block_number }
</LinkExternal>
),
},
{
name: 'L2 txn hash',
value: (
<LinkInternal
href={ route({ pathname: '/tx/[hash]', query: { hash: item.l2_tx_hash } }) }
display="flex"
width="fit-content"
alignItems="center"
overflow="hidden"
w="100%"
>
<Icon as={ txIcon } boxSize={ 6 } mr={ 1 }/>
<Box w="calc(100% - 36px)" overflow="hidden" whiteSpace="nowrap"><HashStringShortenDynamic hash={ item.l2_tx_hash }/></Box>
</LinkInternal>
),
},
{
name: 'Age',
value: timeAgo,
},
{
name: 'L1 txn hash',
value: (
<LinkExternal
href={ appConfig.L2.L1BaseUrl + route({ pathname: '/tx/[hash]', query: { hash: item.l1_tx_hash } }) }
maxW="100%"
display="inline-flex"
overflow="hidden"
>
<Icon as={ txIcon } boxSize={ 6 } mr={ 1 }/>
<Box w="calc(100% - 44px)" overflow="hidden" whiteSpace="nowrap"><HashStringShortenDynamic hash={ item.l1_tx_hash }/></Box>
</LinkExternal>
),
},
{
name: 'L1 txn origin',
value: (
<LinkExternal
href={ appConfig.L2.L1BaseUrl + route({ pathname: '/address/[hash]', query: { hash: item.l1_tx_origin } }) }
maxW="100%"
display="inline-flex"
overflow="hidden"
>
<AddressIcon address={{ hash: item.l1_tx_origin, is_contract: false, implementation_name: '' }} mr={ 2 }/>
<Box w="calc(100% - 44px)" overflow="hidden" whiteSpace="nowrap"><HashStringShortenDynamic hash={ item.l1_tx_origin }/></Box>
</LinkExternal>
),
},
{
name: 'Gas limit',
value: BigNumber(item.l2_tx_gas_limit).toFormat(),
},
];
return <ListItemMobileGrid items={ items } gridTemplateColumns="92px auto"/>;
};
export default DepositsListItem;
import { Table, Tbody, Th, Tr } from '@chakra-ui/react';
import React from 'react';
import type { DepositsItem } from 'types/api/deposits';
import { default as Thead } from 'ui/shared/TheadSticky';
import DepositsTableItem from './DepositsTableItem';
type Props = {
items: Array<DepositsItem>;
top: number;
}
const DepositsTable = ({ items, top }: Props) => {
return (
<Table variant="simple" size="sm" style={{ tableLayout: 'auto' }} minW="950px">
<Thead top={ top }>
<Tr>
<Th>L1 block No</Th>
<Th>L2 txn hash</Th>
<Th>Age</Th>
<Th>L1 txn hash</Th>
<Th>L1 txn origin</Th>
<Th isNumeric>Gas limit</Th>
</Tr>
</Thead>
<Tbody>
{ items.map((item) => (
<DepositsTableItem key={ item.l2_tx_hash } item={ item }/>
)) }
</Tbody>
</Table>
);
};
export default DepositsTable;
import { Box, Td, Tr, Text, Icon } from '@chakra-ui/react';
import BigNumber from 'bignumber.js';
import { route } from 'nextjs-routes';
import React from 'react';
import type { DepositsItem } from 'types/api/deposits';
import appConfig from 'configs/app/config';
import blockIcon from 'icons/block.svg';
import txIcon from 'icons/transactions.svg';
import dayjs from 'lib/date/dayjs';
import AddressIcon from 'ui/shared/address/AddressIcon';
import HashStringShorten from 'ui/shared/HashStringShorten';
import LinkExternal from 'ui/shared/LinkExternal';
import LinkInternal from 'ui/shared/LinkInternal';
type Props = { item: DepositsItem };
const WithdrawalsTableItem = ({ item }: Props) => {
const timeAgo = dayjs(item.l1_block_timestamp).fromNow();
return (
<Tr>
<Td verticalAlign="middle" fontWeight={ 600 }>
<LinkExternal
href={ appConfig.L2.L1BaseUrl + route({ pathname: '/block/[height]', query: { height: item.l1_block_number.toString() } }) }
fontWeight={ 600 }
display="inline-flex"
>
<Icon as={ blockIcon } boxSize={ 6 } mr={ 1 }/>
{ item.l1_block_number }
</LinkExternal>
</Td>
<Td verticalAlign="middle">
<LinkInternal
href={ route({ pathname: '/tx/[hash]', query: { hash: item.l2_tx_hash } }) }
display="flex"
width="fit-content"
alignItems="center"
overflow="hidden"
w="100%"
>
<Icon as={ txIcon } boxSize={ 6 } mr={ 1 }/>
<Box w="calc(100% - 36px)" overflow="hidden" whiteSpace="nowrap"><HashStringShorten hash={ item.l2_tx_hash }/></Box>
</LinkInternal>
</Td>
<Td verticalAlign="middle" pr={ 12 }>
<Text variant="secondary">{ timeAgo }</Text>
</Td>
<Td verticalAlign="middle">
<LinkExternal
href={ appConfig.L2.L1BaseUrl + route({ pathname: '/tx/[hash]', query: { hash: item.l1_tx_hash } }) }
maxW="100%"
display="inline-flex"
overflow="hidden"
>
<Icon as={ txIcon } boxSize={ 6 } mr={ 1 }/>
<Box w="calc(100% - 44px)" overflow="hidden" whiteSpace="nowrap"><HashStringShorten hash={ item.l1_tx_hash }/></Box>
</LinkExternal>
</Td>
<Td verticalAlign="middle">
<LinkExternal
href={ appConfig.L2.L1BaseUrl + route({ pathname: '/address/[hash]', query: { hash: item.l1_tx_origin } }) }
maxW="100%"
display="inline-flex"
overflow="hidden"
>
<AddressIcon address={{ hash: item.l1_tx_origin, is_contract: false, implementation_name: '' }} mr={ 2 }/>
<Box w="calc(100% - 44px)" overflow="hidden" whiteSpace="nowrap"><HashStringShorten hash={ item.l1_tx_origin }/></Box>
</LinkExternal>
</Td>
<Td verticalAlign="middle" isNumeric>
<Text variant="secondary">{ BigNumber(item.l2_tx_gas_limit).toFormat() }</Text>
</Td>
</Tr>
);
};
export default WithdrawalsTableItem;
import { test, expect } from '@playwright/experimental-ct-react';
import React from 'react';
import { data as depositsData } from 'mocks/deposits/deposits';
import TestApp from 'playwright/TestApp';
import buildApiUrl from 'playwright/utils/buildApiUrl';
import Deposits from './Deposits';
const DEPOSITS_API_URL = buildApiUrl('deposits');
const DEPOSITS_COUNT_API_URL = buildApiUrl('deposits_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(DEPOSITS_API_URL, (route) => route.fulfill({
status: 200,
body: JSON.stringify(depositsData),
}));
await page.route(DEPOSITS_COUNT_API_URL, (route) => route.fulfill({
status: 200,
body: '3971111',
}));
const component = await mount(
<TestApp>
<Deposits/>
</TestApp>,
);
await expect(component.locator('main')).toHaveScreenshot();
});
import { Flex, Hide, Show, Skeleton, Text } from '@chakra-ui/react';
import React from 'react';
import useApiQuery from 'lib/api/useApiQuery';
import useIsMobile from 'lib/hooks/useIsMobile';
import useQueryWithPages from 'lib/hooks/useQueryWithPages';
import { rightLineArrow, nbsp } from 'lib/html-entities';
import DepositsListItem from 'ui/deposits/DepositsListItem';
import DepositsTable from 'ui/deposits/DepositsTable';
import ActionBar from 'ui/shared/ActionBar';
import DataListDisplay from 'ui/shared/DataListDisplay';
import Page from 'ui/shared/Page/Page';
import PageTitle from 'ui/shared/Page/PageTitle';
import Pagination from 'ui/shared/Pagination';
const Deposits = () => {
const isMobile = useIsMobile();
const { data, isError, isLoading, isPaginationVisible, pagination } = useQueryWithPages({
resourceName: 'deposits',
});
const countersQuery = useApiQuery('deposits_count');
const content = data?.items ? (
<>
<Show below="lg" ssr={ false }>{ data.items.map((item => <DepositsListItem key={ item.l2_tx_hash } item={ item }/>)) }</Show>
<Hide below="lg" ssr={ false }><DepositsTable items={ data.items } top={ isPaginationVisible ? 80 : 0 }/></Hide>
</>
) : null;
const text = (() => {
if (countersQuery.isLoading) {
return <Skeleton w={{ base: '100%', lg: '320px' }} h="26px" mb={ 6 } mt={{ base: 0, lg: 6 }}/>;
}
if (countersQuery.isError) {
return null;
}
return <Text mb={{ base: 6, lg: isPaginationVisible ? 0 : 6 }}>A total of { countersQuery.data.toLocaleString('en') } deposits found</Text>;
})();
const actionBar = (
<>
{ (isMobile || !isPaginationVisible) && text }
{ isPaginationVisible && (
<ActionBar mt={ -6 }>
<Flex alignItems="center" justifyContent="space-between" w="100%">
{ !isMobile && text }
<Pagination ml="auto" { ...pagination }/>
</Flex>
</ActionBar>
) }
</>
);
return (
<Page>
<PageTitle text={ `Deposits (L1${ nbsp }${ rightLineArrow }${ nbsp }L2)` } withTextAd/>
<DataListDisplay
isError={ isError }
isLoading={ isLoading }
items={ data?.items }
skeletonProps={{ skeletonDesktopColumns: Array(7).fill(`${ 100 / 7 }%`), skeletonDesktopMinW: '950px' }}
emptyText="There are no withdrawals."
content={ content }
actionBar={ actionBar }
/>
</Page>
);
};
export default Deposits;
...@@ -70,7 +70,8 @@ const WithdrawalsListItem = ({ item }: Props) => { ...@@ -70,7 +70,8 @@ const WithdrawalsListItem = ({ item }: Props) => {
display="inline-flex" display="inline-flex"
overflow="hidden" overflow="hidden"
> >
<Box w="calc(100% - 16px)" overflow="hidden" whiteSpace="nowrap"><HashStringShortenDynamic hash={ item.l1_tx_hash }/></Box> <Icon as={ txIcon } boxSize={ 6 } mr={ 1 }/>
<Box w="calc(100% - 44px)" overflow="hidden" whiteSpace="nowrap"><HashStringShortenDynamic hash={ item.l1_tx_hash }/></Box>
</LinkExternal> </LinkExternal>
) : null, ) : null,
}, },
......
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