Commit beebb071 authored by isstuev's avatar isstuev

shibarium deposits and withdrawals

parent e5224836
......@@ -369,7 +369,7 @@ This feature is **enabled by default** with the `coinzilla` ads provider. To swi
| Variable | Type| Description | Compulsoriness | Default value | Example value |
| --- | --- | --- | --- | --- | --- |
| NEXT_PUBLIC_ROLLUP_TYPE | `'optimistic' \| 'zkEvm' ` | Rollup chain type | Required | - | `'optimistic'` |
| NEXT_PUBLIC_ROLLUP_TYPE | `'optimistic' \| 'shibarium' \| 'zkEvm' ` | Rollup chain type | Required | - | `'optimistic'` |
| NEXT_PUBLIC_ROLLUP_L1_BASE_URL | `string` | Blockscout base URL for L1 network | Required | - | `'http://eth-goerli.blockscout.com'` |
| NEXT_PUBLIC_ROLLUP_L2_WITHDRAWAL_URL | `string` | URL for L2 -> L1 withdrawals | - | - | `https://app.optimism.io/bridge/withdraw` |
......
......@@ -57,6 +57,7 @@ import type {
} from 'types/api/optimisticL2';
import type { RawTracesResponse } from 'types/api/rawTrace';
import type { SearchRedirectResult, SearchResult, SearchResultFilters, SearchResultItem } from 'types/api/search';
import type { ShibariumWithdrawalsResponse, ShibariumDepositsResponse } from 'types/api/shibarium';
import type { Counters, StatsCharts, StatsChart, HomeStats } from 'types/api/stats';
import type {
TokenCounters,
......@@ -602,6 +603,25 @@ export const RESOURCES = {
filterFields: [],
},
// SHIBARIUM L2
shibarium_deposits: {
path: '/api/v2/shibarium/deposits',
filterFields: [],
},
shibarium_deposits_count: {
path: '/api/v2/shibarium/deposits/count',
},
shibarium_withdrawals: {
path: '/api/v2/shibarium/withdrawals',
filterFields: [],
},
shibarium_withdrawals_count: {
path: '/api/v2/shibarium/withdrawals/count',
},
// USER OPS
user_ops: {
path: '/api/v2/proxy/account-abstraction/operations',
......@@ -696,6 +716,7 @@ export type PaginatedResources = 'blocks' | 'block_txs' |
'token_instance_transfers' | 'token_instance_holders' |
'verified_contracts' |
'l2_output_roots' | 'l2_withdrawals' | 'l2_txn_batches' | 'l2_deposits' |
'shibarium_deposits' | 'shibarium_withdrawals' |
'zkevm_l2_txn_batches' | 'zkevm_l2_txn_batch_txs' |
'withdrawals' | 'address_withdrawals' | 'block_withdrawals' |
'watchlist' | 'private_tags_address' | 'private_tags_tx' |
......@@ -819,13 +840,14 @@ Q extends 'marketplace_dapps' ? Array<MarketplaceAppOverview> :
Q extends 'marketplace_dapp' ? MarketplaceAppOverview :
Q extends 'validators' ? ValidatorsResponse :
Q extends 'validators_counters' ? ValidatorsCountersResponse :
Q extends 'shibarium_withdrawals' ? ShibariumWithdrawalsResponse :
Q extends 'shibarium_deposits' ? ShibariumDepositsResponse :
Q extends 'shibarium_withdrawals_count' ? number :
Q extends 'shibarium_deposits_count' ? number :
never;
/* eslint-enable @typescript-eslint/indent */
export type ResourcePayload<Q extends ResourceName> = ResourcePayloadA<Q> | ResourcePayloadB<Q>;
// Right now there is no paginated resources in B-part
// Add "| ResourcePayloadB<Q>[...]" if it is not true anymore
export type PaginatedResponseItems<Q extends ResourceName> = Q extends PaginatedResources ? ResourcePayloadA<Q>['items'] | ResourcePayloadB<Q>['items'] : never;
export type PaginatedResponseNextPageParams<Q extends ResourceName> = Q extends PaginatedResources ?
ResourcePayloadA<Q>['next_page_params'] | ResourcePayloadB<Q>['next_page_params'] :
......
......@@ -119,6 +119,23 @@ export default function useNavItems(): ReturnType {
ensLookup,
].filter(Boolean),
];
} else if (rollupFeature.isEnabled && rollupFeature.type === 'shibarium') {
blockchainNavItems = [
[
txs,
// eslint-disable-next-line max-len
{ text: `Deposits (L1${ rightLineArrow }L2)`, nextRoute: { pathname: '/deposits' as const }, icon: 'arrows/south-east', isActive: pathname === '/deposits' },
// eslint-disable-next-line max-len
{ text: `Withdrawals (L2${ rightLineArrow }L1)`, nextRoute: { pathname: '/withdrawals' as const }, icon: 'arrows/north-east', isActive: pathname === '/withdrawals' },
],
[
blocks,
userOps,
topAccounts,
verifiedContracts,
ensLookup,
].filter(Boolean),
];
} else {
blockchainNavItems = [
txs,
......
import type { ShibariumDepositsResponse } from 'types/api/shibarium';
export const data: ShibariumDepositsResponse = {
items: [
{
l1_block_number: 8382841,
timestamp: '2022-05-27T01:13:48.000000Z',
l1_transaction_hash: '0xaf3e5f4ef03eac22a622b3434c5dc9f4465aa291900a86bcf0ad9fb14429f05e',
user: {
hash: '0x6197d1eef304eb5284a0f6720f79403b4e9bf3a5',
implementation_name: null,
is_contract: false,
is_verified: false,
name: null,
private_tags: [],
public_tags: [],
watchlist_names: [],
ens_domain_name: null,
},
l2_transaction_hash: '0xb9212c76069b926917816767e4c5a0ef80e519b1ac1c3d3fb5818078f4984667',
},
{
l1_block_number: 8382841,
timestamp: '2022-05-27T01:13:48.000000Z',
l1_transaction_hash: '0xaf3e5f4ef03eac22a622b3434c5dc9f4465aa291900a86bcf0ad9fb14429f05e',
user: {
hash: '0x6197d1eef304eb5284a0f6720f79403b4e9bf3a5',
implementation_name: null,
is_contract: false,
is_verified: false,
name: null,
private_tags: [],
public_tags: [],
watchlist_names: [],
ens_domain_name: null,
},
l2_transaction_hash: '0xb9212c76069b926917816767e4c5a0ef80e519b1ac1c3d3fb5818078f4984667',
},
{
l1_block_number: 8382841,
timestamp: '2022-05-27T01:13:48.000000Z',
l1_transaction_hash: '0xaf3e5f4ef03eac22a622b3434c5dc9f4465aa291900a86bcf0ad9fb14429f05e',
user: {
hash: '0x6197d1eef304eb5284a0f6720f79403b4e9bf3a5',
implementation_name: null,
is_contract: false,
is_verified: false,
name: null,
private_tags: [],
public_tags: [],
watchlist_names: [],
ens_domain_name: null,
},
l2_transaction_hash: '0xb9212c76069b926917816767e4c5a0ef80e519b1ac1c3d3fb5818078f4984667',
},
],
next_page_params: {
items_count: 50,
block_number: 8382363,
},
};
import type { ShibariumWithdrawalsResponse } from 'types/api/shibarium';
export const data: ShibariumWithdrawalsResponse = {
items: [
{
l2_block_number: 8382841,
timestamp: '2022-05-27T01:13:48.000000Z',
l1_transaction_hash: '0xaf3e5f4ef03eac22a622b3434c5dc9f4465aa291900a86bcf0ad9fb14429f05e',
user: {
hash: '0x6197d1eef304eb5284a0f6720f79403b4e9bf3a5',
implementation_name: null,
is_contract: false,
is_verified: false,
name: null,
private_tags: [],
public_tags: [],
watchlist_names: [],
ens_domain_name: null,
},
l2_transaction_hash: '0xb9212c76069b926917816767e4c5a0ef80e519b1ac1c3d3fb5818078f4984667',
},
{
l2_block_number: 8382841,
timestamp: '2022-05-27T01:13:48.000000Z',
l1_transaction_hash: '0xaf3e5f4ef03eac22a622b3434c5dc9f4465aa291900a86bcf0ad9fb14429f05e',
user: {
hash: '0x6197d1eef304eb5284a0f6720f79403b4e9bf3a5',
implementation_name: null,
is_contract: false,
is_verified: false,
name: null,
private_tags: [],
public_tags: [],
watchlist_names: [],
ens_domain_name: null,
},
l2_transaction_hash: '0xb9212c76069b926917816767e4c5a0ef80e519b1ac1c3d3fb5818078f4984667',
},
{
l2_block_number: 8382841,
timestamp: '2022-05-27T01:13:48.000000Z',
l1_transaction_hash: '0xaf3e5f4ef03eac22a622b3434c5dc9f4465aa291900a86bcf0ad9fb14429f05e',
user: {
hash: '0x6197d1eef304eb5284a0f6720f79403b4e9bf3a5',
implementation_name: null,
is_contract: false,
is_verified: false,
name: null,
private_tags: [],
public_tags: [],
watchlist_names: [],
ens_domain_name: null,
},
l2_transaction_hash: '0xb9212c76069b926917816767e4c5a0ef80e519b1ac1c3d3fb5818078f4984667',
},
],
next_page_params: {
items_count: 50,
block_number: 8382363,
},
};
......@@ -49,10 +49,20 @@ export const verifiedAddresses: GetServerSideProps<Props> = async(context) => {
return account(context);
};
export const deposits: GetServerSideProps<Props> = async(context) => {
if (!(rollupFeature.isEnabled && (rollupFeature.type === 'optimistic' || rollupFeature.type === 'shibarium'))) {
return {
notFound: true,
};
}
return base(context);
};
export const withdrawals: GetServerSideProps<Props> = async(context) => {
if (
!config.features.beaconChain.isEnabled &&
!(rollupFeature.isEnabled && rollupFeature.type === 'optimistic')
!(rollupFeature.isEnabled && (rollupFeature.type === 'optimistic' || rollupFeature.type === 'shibarium'))
) {
return {
notFound: true,
......
......@@ -18,6 +18,7 @@ const Batches = dynamic(() => {
case 'optimistic':
return import('ui/pages/OptimisticL2TxnBatches');
}
throw new Error('Deposits feature is not enabled.');
}, { ssr: false });
const Page: NextPage = () => {
......
......@@ -4,7 +4,20 @@ import React from 'react';
import PageNextJs from 'nextjs/PageNextJs';
const Deposits = dynamic(() => import('ui/pages/OptimisticL2Deposits'), { ssr: false });
import config from 'configs/app';
const rollupFeature = config.features.rollup;
const Deposits = dynamic(() => {
if (rollupFeature.isEnabled && rollupFeature.type === 'optimistic') {
return import('ui/pages/OptimisticL2Deposits');
}
if (rollupFeature.isEnabled && rollupFeature.type === 'shibarium') {
return import('ui/pages/ShibariumDeposits');
}
throw new Error('Withdrawals feature is not enabled.');
}, { ssr: false });
const Page: NextPage = () => {
return (
......@@ -16,4 +29,4 @@ const Page: NextPage = () => {
export default Page;
export { optimisticRollup as getServerSideProps } from 'nextjs/getServerSideProps';
export { deposits as getServerSideProps } from 'nextjs/getServerSideProps';
......@@ -12,6 +12,11 @@ const Withdrawals = dynamic(() => {
if (rollupFeature.isEnabled && rollupFeature.type === 'optimistic') {
return import('ui/pages/OptimisticL2Withdrawals');
}
if (rollupFeature.isEnabled && rollupFeature.type === 'shibarium') {
return import('ui/pages/ShibariumWithdrawals');
}
if (beaconChainFeature.isEnabled) {
return import('ui/pages/BeaconChainWithdrawals');
}
......
......@@ -19,6 +19,10 @@ export const featureEnvs = {
{ name: 'NEXT_PUBLIC_ROLLUP_L1_BASE_URL', value: 'https://localhost:3101' },
{ name: 'NEXT_PUBLIC_ROLLUP_L2_WITHDRAWAL_URL', value: 'https://localhost:3102' },
],
shibariumRollup: [
{ name: 'NEXT_PUBLIC_ROLLUP_TYPE', value: 'shibarium' },
{ name: 'NEXT_PUBLIC_ROLLUP_L1_BASE_URL', value: 'https://localhost:3101' },
],
bridgedTokens: [
{
name: 'NEXT_PUBLIC_BRIDGED_TOKENS_CHAINS',
......
import type { ShibariumDepositsItem, ShibariumWithdrawalsItem } from 'types/api/shibarium';
import { ADDRESS_PARAMS } from './addressParams';
import { TX_HASH } from './tx';
export const SHIBARIUM_DEPOSIT_ITEM: ShibariumDepositsItem = {
l1_block_number: 9045233,
l1_transaction_hash: TX_HASH,
l2_transaction_hash: TX_HASH,
timestamp: '2023-05-22T18:00:36.000000Z',
user: ADDRESS_PARAMS,
};
export const SHIBARIUM_WITHDRAWAL_ITEM: ShibariumWithdrawalsItem = {
l2_block_number: 9045233,
l1_transaction_hash: TX_HASH,
l2_transaction_hash: TX_HASH,
timestamp: '2023-05-22T18:00:36.000000Z',
user: ADDRESS_PARAMS,
};
import type { AddressParam } from './addressParams';
export type ShibariumDepositsItem = {
l1_block_number: number;
l1_transaction_hash: string;
l2_transaction_hash: string;
timestamp: string;
user: AddressParam | string;
}
export type ShibariumDepositsResponse = {
items: Array<ShibariumDepositsItem>;
next_page_params: {
items_count: number;
block_number: number;
};
}
export type ShibariumWithdrawalsItem = {
l1_transaction_hash: string;
l2_block_number: number;
l2_transaction_hash: string;
timestamp: string;
user: AddressParam | string;
}
export type ShibariumWithdrawalsResponse = {
items: Array<ShibariumWithdrawalsItem>;
next_page_params: {
items_count: number;
block_number: number;
};
}
......@@ -2,6 +2,7 @@ import type { ArrayElement } from 'types/utils';
export const ROLLUP_TYPES = [
'optimistic',
'shibarium',
'zkEvm',
] as const;
......
import { Skeleton } from '@chakra-ui/react';
import React from 'react';
import type { ShibariumDepositsItem } from 'types/api/shibarium';
import config from 'configs/app';
import dayjs from 'lib/date/dayjs';
import AddressStringOrParam from 'ui/shared/entities/address/AddressStringOrParam';
import BlockEntityL1 from 'ui/shared/entities/block/BlockEntityL1';
import TxEntity from 'ui/shared/entities/tx/TxEntity';
import TxEntityL1 from 'ui/shared/entities/tx/TxEntityL1';
import ListItemMobileGrid from 'ui/shared/ListItemMobile/ListItemMobileGrid';
const feature = config.features.rollup;
type Props = { item: ShibariumDepositsItem; isLoading?: boolean };
const DepositsListItem = ({ item, isLoading }: Props) => {
const timeAgo = dayjs(item.timestamp).fromNow();
if (!(feature.isEnabled && feature.type === 'shibarium')) {
return null;
}
return (
<ListItemMobileGrid.Container>
<ListItemMobileGrid.Label isLoading={ isLoading }>L1 block No</ListItemMobileGrid.Label>
<ListItemMobileGrid.Value>
<BlockEntityL1
number={ item.l1_block_number }
isLoading={ isLoading }
fontSize="sm"
lineHeight={ 5 }
fontWeight={ 600 }
/>
</ListItemMobileGrid.Value>
<ListItemMobileGrid.Label isLoading={ isLoading }>L1 txn hash</ListItemMobileGrid.Label>
<ListItemMobileGrid.Value>
<TxEntityL1
isLoading={ isLoading }
hash={ item.l1_transaction_hash }
fontSize="sm"
lineHeight={ 5 }
truncation="constant_long"
/>
</ListItemMobileGrid.Value>
<ListItemMobileGrid.Label isLoading={ isLoading }>L2 txn hash</ListItemMobileGrid.Label>
<ListItemMobileGrid.Value>
<TxEntity
isLoading={ isLoading }
hash={ item.l2_transaction_hash }
fontSize="sm"
lineHeight={ 5 }
truncation="constant_long"
/>
</ListItemMobileGrid.Value>
<ListItemMobileGrid.Label isLoading={ isLoading }>User</ListItemMobileGrid.Label>
<ListItemMobileGrid.Value>
<AddressStringOrParam
address={ item.user }
isLoading={ isLoading }
noCopy
truncation="constant"
/>
</ListItemMobileGrid.Value>
<ListItemMobileGrid.Label isLoading={ isLoading }>Age</ListItemMobileGrid.Label>
<ListItemMobileGrid.Value>
<Skeleton isLoaded={ !isLoading } display="inline-block">{ timeAgo }</Skeleton>
</ListItemMobileGrid.Value>
</ListItemMobileGrid.Container>
);
};
export default DepositsListItem;
import { Table, Tbody, Th, Tr } from '@chakra-ui/react';
import React from 'react';
import type { ShibariumDepositsItem } from 'types/api/shibarium';
import { default as Thead } from 'ui/shared/TheadSticky';
import DepositsTableItem from './DepositsTableItem';
type Props = {
items: Array<ShibariumDepositsItem>;
top: number;
isLoading?: boolean;
}
const DepositsTable = ({ items, top, isLoading }: Props) => {
return (
<Table variant="simple" size="sm" style={{ tableLayout: 'auto' }} minW="950px">
<Thead top={ top }>
<Tr>
<Th>L1 block No</Th>
<Th>L1 txn hash</Th>
<Th>L2 txn hash</Th>
<Th>User</Th>
<Th>Age</Th>
</Tr>
</Thead>
<Tbody>
{ items.map((item, index) => (
<DepositsTableItem key={ item.l2_transaction_hash + (isLoading ? index : '') } item={ item } isLoading={ isLoading }/>
)) }
</Tbody>
</Table>
);
};
export default DepositsTable;
import { Td, Tr, Skeleton } from '@chakra-ui/react';
import React from 'react';
import type { ShibariumDepositsItem } from 'types/api/shibarium';
import config from 'configs/app';
import dayjs from 'lib/date/dayjs';
import AddressStringOrParam from 'ui/shared/entities/address/AddressStringOrParam';
import BlockEntityL1 from 'ui/shared/entities/block/BlockEntityL1';
import TxEntity from 'ui/shared/entities/tx/TxEntity';
import TxEntityL1 from 'ui/shared/entities/tx/TxEntityL1';
const feature = config.features.rollup;
type Props = { item: ShibariumDepositsItem; isLoading?: boolean };
const DepositsTableItem = ({ item, isLoading }: Props) => {
const timeAgo = dayjs(item.timestamp).fromNow();
if (!(feature.isEnabled && feature.type === 'shibarium')) {
return null;
}
return (
<Tr>
<Td verticalAlign="middle">
<BlockEntityL1
number={ item.l1_block_number }
isLoading={ isLoading }
fontSize="sm"
lineHeight={ 5 }
fontWeight={ 600 }
/>
</Td>
<Td verticalAlign="middle">
<TxEntityL1
isLoading={ isLoading }
hash={ item.l1_transaction_hash }
truncation="constant_long"
fontSize="sm"
lineHeight={ 5 }
/>
</Td>
<Td verticalAlign="middle">
<TxEntity
isLoading={ isLoading }
hash={ item.l2_transaction_hash }
fontSize="sm"
lineHeight={ 5 }
truncation="constant_long"
/>
</Td>
<Td verticalAlign="middle">
<AddressStringOrParam
address={ item.user }
isLoading={ isLoading }
truncation="constant"
noCopy
/>
</Td>
<Td verticalAlign="middle" pr={ 12 }>
<Skeleton isLoaded={ !isLoading } color="text_secondary" display="inline-block"><span>{ timeAgo }</span></Skeleton>
</Td>
</Tr>
);
};
export default DepositsTableItem;
import { test as base, expect } from '@playwright/experimental-ct-react';
import React from 'react';
import { data as depositsData } from 'mocks/shibarium/deposits';
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 ShibariumDeposits from './ShibariumDeposits';
const DEPOSITS_API_URL = buildApiUrl('shibarium_deposits');
const DEPOSITS_COUNT_API_URL = buildApiUrl('shibarium_deposits_count');
const test = base.extend({
// eslint-disable-next-line @typescript-eslint/no-explicit-any
context: contextWithEnvs(configs.featureEnvs.shibariumRollup) as any,
});
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>
<ShibariumDeposits/>
</TestApp>,
);
await expect(component).toHaveScreenshot();
});
import { Hide, Show, Skeleton } from '@chakra-ui/react';
import React from 'react';
import useApiQuery from 'lib/api/useApiQuery';
import { rightLineArrow, nbsp } from 'lib/html-entities';
import { SHIBARIUM_DEPOSIT_ITEM } from 'stubs/shibarium';
import { generateListStub } from 'stubs/utils';
import DepositsListItem from 'ui/deposits/shibarium/DepositsListItem';
import DepositsTable from 'ui/deposits/shibarium/DepositsTable';
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';
const L2Deposits = () => {
const { data, isError, isPlaceholderData, pagination } = useQueryWithPages({
resourceName: 'shibarium_deposits',
options: {
placeholderData: generateListStub<'shibarium_deposits'>(
SHIBARIUM_DEPOSIT_ITEM,
50,
{
next_page_params: {
items_count: 50,
block_number: 9045200,
},
},
),
},
});
const countersQuery = useApiQuery('shibarium_deposits_count', {
queryOptions: {
placeholderData: 1927029,
},
});
const content = data?.items ? (
<>
<Show below="lg" ssr={ false }>
{ data.items.map(((item, index) => (
<DepositsListItem
key={ item.l2_transaction_hash + (isPlaceholderData ? index : '') }
isLoading={ isPlaceholderData }
item={ item }
/>
))) }
</Show>
<Hide below="lg" ssr={ false }>
<DepositsTable items={ data.items } top={ pagination.isVisible ? 80 : 0 } isLoading={ isPlaceholderData }/>
</Hide>
</>
) : null;
const text = (() => {
if (countersQuery.isError) {
return null;
}
return (
<Skeleton
isLoaded={ !countersQuery.isPlaceholderData }
display="inline-block"
>
A total of { countersQuery.data?.toLocaleString() } deposits found
</Skeleton>
);
})();
const actionBar = <StickyPaginationWithText text={ text } pagination={ pagination }/>;
return (
<>
<PageTitle title={ `Deposits (L1${ nbsp }${ rightLineArrow }${ nbsp }L2)` } withTextAd/>
<DataListDisplay
isError={ isError }
items={ data?.items }
emptyText="There are no withdrawals."
content={ content }
actionBar={ actionBar }
/>
</>
);
};
export default L2Deposits;
import { test as base, expect } from '@playwright/experimental-ct-react';
import React from 'react';
import { data as withdrawalsData } from 'mocks/shibarium/withdrawals';
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 ShibariuWithdrawals from './ShibariumWithdrawals';
const test = base.extend({
// eslint-disable-next-line @typescript-eslint/no-explicit-any
context: contextWithEnvs(configs.featureEnvs.shibariumRollup) as any,
});
const WITHDRAWALS_API_URL = buildApiUrl('shibarium_withdrawals');
const WITHDRAWALS_COUNT_API_URL = buildApiUrl('shibarium_withdrawals_count');
test('base view +@mobile', async({ mount, page }) => {
// test on mobile is flaky
// my assumption is there is not enough time to calculate hashes truncation so component is unstable
// so I raised the test timeout to check if it helps
test.slow();
await page.route('https://request-global.czilladx.com/serve/native.php?z=19260bf627546ab7242', (route) => route.fulfill({
status: 200,
body: '',
}));
await page.route(WITHDRAWALS_API_URL, (route) => route.fulfill({
status: 200,
body: JSON.stringify(withdrawalsData),
}));
await page.route(WITHDRAWALS_COUNT_API_URL, (route) => route.fulfill({
status: 200,
body: '397',
}));
const component = await mount(
<TestApp>
<ShibariuWithdrawals/>
</TestApp>,
);
await expect(component).toHaveScreenshot({ timeout: 10_000 });
});
import { Hide, Show, Skeleton } from '@chakra-ui/react';
import React from 'react';
import useApiQuery from 'lib/api/useApiQuery';
import { rightLineArrow, nbsp } from 'lib/html-entities';
import { SHIBARIUM_WITHDRAWAL_ITEM } from 'stubs/shibarium';
import { generateListStub } from 'stubs/utils';
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 WithdrawalsListItem from 'ui/withdrawals/shibarium/WithdrawalsListItem';
import WithdrawalsTable from 'ui/withdrawals/shibarium/WithdrawalsTable';
const L2Withdrawals = () => {
const { data, isError, isPlaceholderData, pagination } = useQueryWithPages({
resourceName: 'shibarium_withdrawals',
options: {
placeholderData: generateListStub<'shibarium_withdrawals'>(
SHIBARIUM_WITHDRAWAL_ITEM,
50,
{
next_page_params: {
items_count: 50,
block_number: 123,
},
},
),
},
});
const countersQuery = useApiQuery('shibarium_withdrawals_count', {
queryOptions: {
placeholderData: 23700,
},
});
const content = data?.items ? (
<>
<Show below="lg" ssr={ false }>{ data.items.map(((item, index) => (
<WithdrawalsListItem
key={ item.l2_transaction_hash + (isPlaceholderData ? index : '') }
item={ item }
isLoading={ isPlaceholderData }
/>
))) }</Show>
<Hide below="lg" ssr={ false }>
<WithdrawalsTable items={ data.items } top={ pagination.isVisible ? 80 : 0 } isLoading={ isPlaceholderData }/>
</Hide>
</>
) : null;
const text = (() => {
if (countersQuery.isError) {
return null;
}
return (
<Skeleton
isLoaded={ !countersQuery.isPlaceholderData }
display="inline-block"
>
A total of { countersQuery.data?.toLocaleString() } withdrawals found
</Skeleton>
);
})();
const actionBar = <StickyPaginationWithText text={ text } pagination={ pagination }/>;
return (
<>
<PageTitle title={ `Withdrawals (L2${ nbsp }${ rightLineArrow }${ nbsp }L1)` } withTextAd/>
<DataListDisplay
isError={ isError }
items={ data?.items }
emptyText="There are no withdrawals."
content={ content }
actionBar={ actionBar }
/>
</>
);
};
export default L2Withdrawals;
......@@ -2,14 +2,14 @@ import React from 'react';
import type { AddressParamBasic } from 'types/api/addressParams';
import AddressEntity from '../entities/address/AddressEntity';
import type { EntityProps } from '../entities/address/AddressEntity';
import AddressEntity from './AddressEntity';
import type { EntityProps } from './AddressEntity';
type Props = Omit<EntityProps, 'address'> & {
address: string | AddressParamBasic;
}
const UserOpsAddress = ({ address, ...props }: Props) => {
const AddressStringOrParam = ({ address, ...props }: Props) => {
let addressParam;
if (typeof address === 'string') {
addressParam = { hash: address };
......@@ -20,4 +20,4 @@ const UserOpsAddress = ({ address, ...props }: Props) => {
return <AddressEntity address={ addressParam } { ...props }/>;
};
export default UserOpsAddress;
export default AddressStringOrParam;
......@@ -17,11 +17,11 @@ import DataFetchAlert from 'ui/shared/DataFetchAlert';
import DetailsInfoItem from 'ui/shared/DetailsInfoItem';
import DetailsInfoItemDivider from 'ui/shared/DetailsInfoItemDivider';
import DetailsTimestamp from 'ui/shared/DetailsTimestamp';
import UserOpsAddress from 'ui/shared/entities/address/AddressStringOrParam';
import BlockEntity from 'ui/shared/entities/block/BlockEntity';
import TxEntity from 'ui/shared/entities/tx/TxEntity';
import UserOpEntity from 'ui/shared/entities/userOp/UserOpEntity';
import RawInputData from 'ui/shared/RawInputData';
import UserOpsAddress from 'ui/shared/userOps/UserOpsAddress';
import UserOpSponsorType from 'ui/shared/userOps/UserOpSponsorType';
import UserOpStatus from 'ui/shared/userOps/UserOpStatus';
import Utilization from 'ui/shared/Utilization/Utilization';
......
......@@ -6,11 +6,11 @@ import type { UserOpsItem } from 'types/api/userOps';
import config from 'configs/app';
import dayjs from 'lib/date/dayjs';
import CurrencyValue from 'ui/shared/CurrencyValue';
import AddressStringOrParam from 'ui/shared/entities/address/AddressStringOrParam';
import BlockEntity from 'ui/shared/entities/block/BlockEntity';
import TxEntity from 'ui/shared/entities/tx/TxEntity';
import UserOpEntity from 'ui/shared/entities/userOp/UserOpEntity';
import ListItemMobileGrid from 'ui/shared/ListItemMobile/ListItemMobileGrid';
import UserOpsAddress from 'ui/shared/userOps/UserOpsAddress';
import UserOpStatus from 'ui/shared/userOps/UserOpStatus';
type Props = {
......@@ -45,7 +45,7 @@ const UserOpsListItem = ({ item, isLoading, showTx, showSender }: Props) => {
<>
<ListItemMobileGrid.Label isLoading={ isLoading }>Sender</ListItemMobileGrid.Label>
<ListItemMobileGrid.Value>
<UserOpsAddress
<AddressStringOrParam
address={ item.address }
isLoading={ isLoading }
truncation="constant"
......
......@@ -6,10 +6,10 @@ import type { UserOpsItem } from 'types/api/userOps';
import config from 'configs/app';
import dayjs from 'lib/date/dayjs';
import CurrencyValue from 'ui/shared/CurrencyValue';
import AddressStringOrParam from 'ui/shared/entities/address/AddressStringOrParam';
import BlockEntity from 'ui/shared/entities/block/BlockEntity';
import TxEntity from 'ui/shared/entities/tx/TxEntity';
import UserOpEntity from 'ui/shared/entities/userOp/UserOpEntity';
import UserOpsAddress from 'ui/shared/userOps/UserOpsAddress';
import UserOpStatus from 'ui/shared/userOps/UserOpStatus';
type Props = {
......@@ -35,7 +35,7 @@ const UserOpsTableItem = ({ item, isLoading, showTx, showSender }: Props) => {
</Td>
{ showSender && (
<Td verticalAlign="middle">
<UserOpsAddress
<AddressStringOrParam
address={ item.address }
isLoading={ isLoading }
truncation="constant"
......
import { Skeleton } from '@chakra-ui/react';
import React from 'react';
import type { ShibariumWithdrawalsItem } from 'types/api/shibarium';
import config from 'configs/app';
import dayjs from 'lib/date/dayjs';
import AddressStringOrParam from 'ui/shared/entities/address/AddressStringOrParam';
import BlockEntity from 'ui/shared/entities/block/BlockEntity';
import TxEntity from 'ui/shared/entities/tx/TxEntity';
import TxEntityL1 from 'ui/shared/entities/tx/TxEntityL1';
import ListItemMobileGrid from 'ui/shared/ListItemMobile/ListItemMobileGrid';
const feature = config.features.rollup;
type Props = { item: ShibariumWithdrawalsItem; isLoading?: boolean };
const WithdrawalsListItem = ({ item, isLoading }: Props) => {
const timeAgo = item.timestamp ? dayjs(item.timestamp).fromNow() : null;
if (!(feature.isEnabled && feature.type === 'shibarium')) {
return null;
}
return (
<ListItemMobileGrid.Container>
<ListItemMobileGrid.Label isLoading={ isLoading }>L2 block No</ListItemMobileGrid.Label>
<ListItemMobileGrid.Value>
<BlockEntity
number={ item.l2_block_number }
isLoading={ isLoading }
fontSize="sm"
lineHeight={ 5 }
fontWeight={ 600 }
/>
</ListItemMobileGrid.Value>
<ListItemMobileGrid.Label isLoading={ isLoading }>L2 txn hash</ListItemMobileGrid.Label>
<ListItemMobileGrid.Value>
<TxEntity
isLoading={ isLoading }
hash={ item.l2_transaction_hash }
fontSize="sm"
lineHeight={ 5 }
truncation="constant_long"
/>
</ListItemMobileGrid.Value>
<ListItemMobileGrid.Label isLoading={ isLoading }>L1 txn hash</ListItemMobileGrid.Label>
<ListItemMobileGrid.Value>
<TxEntityL1
isLoading={ isLoading }
hash={ item.l1_transaction_hash }
fontSize="sm"
lineHeight={ 5 }
truncation="constant_long"
/>
</ListItemMobileGrid.Value>
<ListItemMobileGrid.Label isLoading={ isLoading }>User</ListItemMobileGrid.Label>
<ListItemMobileGrid.Value>
<AddressStringOrParam
address={ item.user }
isLoading={ isLoading }
noCopy
truncation="constant"
/>
</ListItemMobileGrid.Value>
<ListItemMobileGrid.Label isLoading={ isLoading }>Age</ListItemMobileGrid.Label>
<ListItemMobileGrid.Value>
<Skeleton isLoaded={ !isLoading } display="inline-block">{ timeAgo }</Skeleton>
</ListItemMobileGrid.Value>
</ListItemMobileGrid.Container>
);
};
export default WithdrawalsListItem;
import { Table, Tbody, Th, Tr } from '@chakra-ui/react';
import React from 'react';
import type { ShibariumWithdrawalsItem } from 'types/api/shibarium';
import { default as Thead } from 'ui/shared/TheadSticky';
import WithdrawalsTableItem from './WithdrawalsTableItem';
type Props = {
items: Array<ShibariumWithdrawalsItem>;
top: number;
isLoading?: boolean;
}
const WithdrawalsTable = ({ items, top, isLoading }: Props) => {
return (
<Table variant="simple" size="sm" style={{ tableLayout: 'auto' }} minW="950px">
<Thead top={ top }>
<Tr>
<Th>L2 block No</Th>
<Th>L2 txn hash</Th>
<Th>L1 txn hash</Th>
<Th>User</Th>
<Th>Age</Th>
</Tr>
</Thead>
<Tbody>
{ items.map((item, index) => (
<WithdrawalsTableItem key={ item.l2_transaction_hash + (isLoading ? index : '') } item={ item } isLoading={ isLoading }/>
)) }
</Tbody>
</Table>
);
};
export default WithdrawalsTable;
import { Td, Tr, Skeleton } from '@chakra-ui/react';
import React from 'react';
import type { ShibariumWithdrawalsItem } from 'types/api/shibarium';
import config from 'configs/app';
import dayjs from 'lib/date/dayjs';
import AddressStringOrParam from 'ui/shared/entities/address/AddressStringOrParam';
import BlockEntity from 'ui/shared/entities/block/BlockEntity';
import TxEntity from 'ui/shared/entities/tx/TxEntity';
import TxEntityL1 from 'ui/shared/entities/tx/TxEntityL1';
const feature = config.features.rollup;
type Props = { item: ShibariumWithdrawalsItem; isLoading?: boolean };
const WithdrawalsTableItem = ({ item, isLoading }: Props) => {
const timeAgo = dayjs(item.timestamp).fromNow();
if (!(feature.isEnabled && feature.type === 'shibarium')) {
return null;
}
return (
<Tr>
<Td verticalAlign="middle">
<BlockEntity
number={ item.l2_block_number }
isLoading={ isLoading }
fontSize="sm"
lineHeight={ 5 }
fontWeight={ 600 }
/>
</Td>
<Td verticalAlign="middle">
<TxEntity
isLoading={ isLoading }
hash={ item.l2_transaction_hash }
fontSize="sm"
lineHeight={ 5 }
truncation="constant_long"
/>
</Td>
<Td verticalAlign="middle">
<TxEntityL1
isLoading={ isLoading }
hash={ item.l1_transaction_hash }
truncation="constant_long"
fontSize="sm"
lineHeight={ 5 }
/>
</Td>
<Td verticalAlign="middle">
<AddressStringOrParam
address={ item.user }
isLoading={ isLoading }
truncation="constant"
noCopy
/>
</Td>
<Td verticalAlign="middle" pr={ 12 }>
<Skeleton isLoaded={ !isLoading } color="text_secondary" display="inline-block"><span>{ timeAgo }</span></Skeleton>
</Td>
</Tr>
);
};
export default WithdrawalsTableItem;
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