Commit 555748cd authored by isstuev's avatar isstuev

OP Fault Proofs support

parent 86e33cae
import type { Feature } from './types';
import { getEnvValue } from '../utils';
import rollup from './rollup';
const title = 'Fault proof system';
const config: Feature<{ isEnabled: true }> = (() => {
if (rollup.isEnabled && rollup.type === 'optimistic' && getEnvValue('NEXT_PUBLIC_FAULT_PROOF_ENABLED') === 'true') {
return Object.freeze({
title,
isEnabled: true,
});
}
return Object.freeze({
title,
isEnabled: false,
});
})();
export default config;
......@@ -8,6 +8,7 @@ export { default as bridgedTokens } from './bridgedTokens';
export { default as blockchainInteraction } from './blockchainInteraction';
export { default as csvExport } from './csvExport';
export { default as dataAvailability } from './dataAvailability';
export { default as faultProofSystem } from './faultProofSystem';
export { default as gasTracker } from './gasTracker';
export { default as googleAnalytics } from './googleAnalytics';
export { default as graphqlApiDocs } from './graphqlApiDocs';
......
......@@ -61,3 +61,4 @@ NEXT_PUBLIC_VIEWS_CONTRACT_SOLIDITYSCAN_ENABLED=true
NEXT_PUBLIC_ROLLUP_TYPE=optimistic
NEXT_PUBLIC_ROLLUP_L2_WITHDRAWAL_URL=https://app.optimism.io/bridge/withdraw
NEXT_PUBLIC_ROLLUP_L1_BASE_URL=https://eth-sepolia.blockscout.com/
NEXT_PUBLIC_FAULT_PROOF_ENABLED=true
......@@ -629,6 +629,16 @@ const schema = yup
NEXT_PUBLIC_GAS_TRACKER_ENABLED: yup.boolean(),
NEXT_PUBLIC_GAS_TRACKER_UNITS: yup.array().transform(replaceQuotes).json().of(yup.string<GasUnit>().oneOf(GAS_UNITS)),
NEXT_PUBLIC_DATA_AVAILABILITY_ENABLED: yup.boolean(),
NEXT_PUBLIC_FAULT_PROOF_ENABLED: yup.boolean()
.when('NEXT_PUBLIC_ROLLUP_TYPE', {
is: 'optimistic',
then: (schema) => schema,
otherwise: (schema) => schema.test(
'not-exist',
'NEXT_PUBLIC_FAULT_PROOF_ENABLED can only be used with NEXT_PUBLIC_ROLLUP_TYPE=optimistic',
value => value === undefined,
),
}),
// 6. External services envs
NEXT_PUBLIC_WALLET_CONNECT_PROJECT_ID: yup.string(),
......
NEXT_PUBLIC_ROLLUP_TYPE=optimistic
NEXT_PUBLIC_ROLLUP_L1_BASE_URL=https://example.com
NEXT_PUBLIC_ROLLUP_L2_WITHDRAWAL_URL=https://example.com
\ No newline at end of file
NEXT_PUBLIC_ROLLUP_L2_WITHDRAWAL_URL=https://example.com
NEXT_PUBLIC_FAULT_PROOF_ENABLED=true
\ No newline at end of file
......@@ -36,6 +36,7 @@ Please be aware that all environment variables prefixed with `NEXT_PUBLIC_` will
- [Beacon chain](ENVS.md#beacon-chain)
- [User operations](ENVS.md#user-operations-feature-erc-4337)
- [Rollup chain](ENVS.md#rollup-chain)
- [Fault proof system](ENVS.md#fault-proof-system)
- [Export data to CSV file](ENVS.md#export-data-to-csv-file)
- [Google analytics](ENVS.md#google-analytics)
- [Mixpanel analytics](ENVS.md#mixpanel-analytics)
......@@ -401,6 +402,14 @@ This feature is **enabled by default** with the `coinzilla` ads provider. To swi
&nbsp;
### Fault proof system
| Variable | Type| Description | Compulsoriness | Default value | Example value |
| --- | --- | --- | --- | --- | --- |
| NEXT_PUBLIC_FAULT_PROOF_ENABLED | `boolean` | Set to `true` for chains with fault proof system enabled (OP stack only) | - | - | `true` |
&nbsp;
### Export data to CSV file
| Variable | Type| Description | Compulsoriness | Default value | Example value |
......
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 30 30">
<path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.6" d="M24.24 18.89v-8.582c0-.28-.072-.556-.206-.8a1.576 1.576 0 0 0-.56-.587L16.16 4.476A2.232 2.232 0 0 0 15 4.149c-.408 0-.809.113-1.161.327L6.525 8.92a1.576 1.576 0 0 0-.56.587c-.134.244-.204.52-.205.8v8.581c0 .281.071.557.205.801s.327.446.56.588l7.315 4.445c.352.214.753.327 1.16.327.408 0 .809-.113 1.161-.327l7.315-4.445c.232-.142.425-.345.559-.588.134-.244.204-.52.204-.8Z"/>
<path stroke="transparent" stroke-linecap="round" stroke-linejoin="round" stroke-opacity=".2" stroke-width="1.6" d="M24.24 18.89v-8.582c0-.28-.072-.556-.206-.8a1.576 1.576 0 0 0-.56-.587L16.16 4.476A2.232 2.232 0 0 0 15 4.149c-.408 0-.809.113-1.161.327L6.525 8.92a1.576 1.576 0 0 0-.56.587c-.134.244-.204.52-.205.8v8.581c0 .281.071.557.205.801s.327.446.56.588l7.315 4.445c.352.214.753.327 1.16.327.408 0 .809-.113 1.161-.327l7.315-4.445c.232-.142.425-.345.559-.588.134-.244.204-.52.204-.8Z"/>
<path fill="currentColor" fill-rule="evenodd" d="M6.42 8.792a.8.8 0 1 0-.838 1.364l8.618 5.293v9.602a.8.8 0 1 0 1.6 0V15.45l8.617-5.293a.8.8 0 0 0-.837-1.364L15 14.062l-8.58-5.27Z" clip-rule="evenodd"/>
<path fill="transparent" fill-opacity=".2" fill-rule="evenodd" d="M6.42 8.792a.8.8 0 1 0-.838 1.364l8.618 5.293v9.602a.8.8 0 1 0 1.6 0V15.45l8.617-5.293a.8.8 0 0 0-.837-1.364L15 14.062l-8.58-5.27Z" clip-rule="evenodd"/>
<path fill="currentColor" fill-rule="evenodd" d="M15 10.177c.638 0 1.155-.36 1.155-.804 0-.444-.517-.804-1.155-.804-.637 0-1.155.36-1.155.804 0 .444.518.804 1.155.804Zm-2.9 7.735c.497 0 .9-.54.9-1.206 0-.666-.403-1.206-.9-1.206s-.9.54-.9 1.206c0 .666.403 1.206.9 1.206Zm-3 .306c0 .666-.403 1.206-.9 1.206s-.9-.54-.9-1.206c0-.667.403-1.207.9-1.207s.9.54.9 1.207Zm8.458-.404c.496.029.93-.487.968-1.152.04-.665-.331-1.227-.828-1.256-.496-.03-.93.486-.968 1.15-.04.666.331 1.228.828 1.258Zm5.068-3.305c-.039.665-.472 1.18-.969 1.152-.496-.03-.866-.592-.828-1.257.04-.665.473-1.18.97-1.151.496.029.866.591.827 1.256Zm-5.069 7.352c.497.029.93-.487.97-1.152.038-.665-.332-1.227-.829-1.256-.496-.03-.93.486-.969 1.151-.039.665.332 1.227.828 1.257Zm5.07-3.591c-.04.665-.473 1.18-.97 1.151-.496-.029-.866-.591-.828-1.256.04-.665.473-1.18.97-1.152.496.03.866.592.827 1.257Z" clip-rule="evenodd"/>
<path fill="transparent" fill-opacity=".2" fill-rule="evenodd" d="M15 10.177c.638 0 1.155-.36 1.155-.804 0-.444-.517-.804-1.155-.804-.637 0-1.155.36-1.155.804 0 .444.518.804 1.155.804Zm-2.9 7.735c.497 0 .9-.54.9-1.206 0-.666-.403-1.206-.9-1.206s-.9.54-.9 1.206c0 .666.403 1.206.9 1.206Zm-3 .306c0 .666-.403 1.206-.9 1.206s-.9-.54-.9-1.206c0-.667.403-1.207.9-1.207s.9.54.9 1.207Zm8.458-.404c.496.029.93-.487.968-1.152.04-.665-.331-1.227-.828-1.256-.496-.03-.93.486-.968 1.15-.04.666.331 1.228.828 1.258Zm5.068-3.305c-.039.665-.472 1.18-.969 1.152-.496-.03-.866-.592-.828-1.257.04-.665.473-1.18.97-1.151.496.029.866.591.827 1.256Zm-5.069 7.352c.497.029.93-.487.97-1.152.038-.665-.332-1.227-.829-1.256-.496-.03-.93.486-.969 1.151-.039.665.332 1.227.828 1.257Zm5.07-3.591c-.04.665-.473 1.18-.97 1.151-.496-.029-.866-.591-.828-1.256.04-.665.473-1.18.97-1.152.496.03.866.592.827 1.257Z" clip-rule="evenodd"/>
</svg>
......@@ -64,6 +64,7 @@ import type {
OptimisticL2OutputRootsResponse,
OptimisticL2TxnBatchesResponse,
OptimisticL2WithdrawalsResponse,
OptimisticL2DisputeGamesResponse,
} from 'types/api/optimisticL2';
import type { RawTracesResponse } from 'types/api/rawTrace';
import type { SearchRedirectResult, SearchResult, SearchResultFilters, SearchResultItem } from 'types/api/search';
......@@ -650,6 +651,15 @@ export const RESOURCES = {
path: '/api/v2/optimism/txn-batches/count',
},
optimistic_l2_dispute_games: {
path: '/api/v2/optimism/games',
filterFields: [],
},
optimistic_l2_dispute_games_count: {
path: '/api/v2/optimism/games/count',
},
// zkEvm L2
zkevm_l2_deposits: {
path: '/api/v2/zkevm/deposits',
......@@ -853,6 +863,7 @@ export type PaginatedResources = 'blocks' | 'block_txs' |
'token_instance_transfers' | 'token_instance_holders' |
'verified_contracts' |
'optimistic_l2_output_roots' | 'optimistic_l2_withdrawals' | 'optimistic_l2_txn_batches' | 'optimistic_l2_deposits' |
'optimistic_l2_dispute_games' |
'shibarium_deposits' | 'shibarium_withdrawals' |
'zkevm_l2_deposits' | 'zkevm_l2_withdrawals' | 'zkevm_l2_txn_batches' | 'zkevm_l2_txn_batch_txs' |
'zksync_l2_txn_batches' | 'zksync_l2_txn_batch_txs' |
......@@ -955,13 +966,12 @@ Q extends 'optimistic_l2_output_roots' ? OptimisticL2OutputRootsResponse :
Q extends 'optimistic_l2_withdrawals' ? OptimisticL2WithdrawalsResponse :
Q extends 'optimistic_l2_deposits' ? OptimisticL2DepositsResponse :
Q extends 'optimistic_l2_txn_batches' ? OptimisticL2TxnBatchesResponse :
Q extends 'optimistic_l2_dispute_games' ? OptimisticL2DisputeGamesResponse :
Q extends 'optimistic_l2_output_roots_count' ? number :
Q extends 'optimistic_l2_withdrawals_count' ? number :
Q extends 'optimistic_l2_deposits_count' ? number :
Q extends 'optimistic_l2_txn_batches_count' ? number :
Q extends 'config_backend_version' ? BackendVersionConfig :
Q extends 'address_metadata_info' ? AddressMetadataInfo :
Q extends 'address_metadata_tag_types' ? PublicTagTypesResponse :
Q extends 'optimistic_l2_dispute_games_count' ? number :
never;
// !!! IMPORTANT !!!
// See comment above
......@@ -969,6 +979,9 @@ never;
/* eslint-disable @typescript-eslint/indent */
export type ResourcePayloadB<Q extends ResourceName> =
Q extends 'config_backend_version' ? BackendVersionConfig :
Q extends 'address_metadata_info' ? AddressMetadataInfo :
Q extends 'address_metadata_tag_types' ? PublicTagTypesResponse :
Q extends 'blob' ? Blob :
Q extends 'marketplace_dapps' ? Array<MarketplaceAppOverview> :
Q extends 'marketplace_dapp' ? MarketplaceAppOverview :
......
......@@ -96,6 +96,12 @@ export default function useNavItems(): ReturnType {
icon: 'output_roots',
isActive: pathname === '/output-roots',
};
const rollupDisputeGames = config.features.faultProofSystem.isEnabled ? {
text: 'Dispute games',
nextRoute: { pathname: '/dispute-games' as const },
icon: 'games',
isActive: pathname === '/dispute-games',
} : null;
const rollupFeature = config.features.rollup;
......@@ -109,6 +115,7 @@ export default function useNavItems(): ReturnType {
[
blocks,
rollupTxnBatches,
rollupDisputeGames,
rollupFeature.type === 'optimistic' ? rollupOutputRoots : undefined,
].filter(Boolean),
[
......
......@@ -35,6 +35,7 @@ const OG_TYPE_DICT: Record<Route['pathname'], OGPageType> = {
'/csv-export': 'Regular page',
'/deposits': 'Root page',
'/output-roots': 'Root page',
'/dispute-games': 'Root page',
'/batches': 'Root page',
'/batches/[number]': 'Regular page',
'/blobs/[hash]': 'Regular page',
......
......@@ -39,6 +39,7 @@ const TEMPLATE_MAP: Record<Route['pathname'], string> = {
'/csv-export': DEFAULT_TEMPLATE,
'/deposits': DEFAULT_TEMPLATE,
'/output-roots': DEFAULT_TEMPLATE,
'/dispute-games': DEFAULT_TEMPLATE,
'/batches': DEFAULT_TEMPLATE,
'/batches/[number]': DEFAULT_TEMPLATE,
'/blobs/[hash]': DEFAULT_TEMPLATE,
......
......@@ -33,6 +33,7 @@ const TEMPLATE_MAP: Record<Route['pathname'], string> = {
'/csv-export': 'export data to CSV',
'/deposits': 'deposits (L1 > L2)',
'/output-roots': 'output roots',
'/dispute-games': 'dispute games',
'/batches': 'tx batches (L2 blocks)',
'/batches/[number]': 'L2 tx batch %number%',
'/blobs/[hash]': 'blob %hash% details',
......
......@@ -33,6 +33,7 @@ export const PAGE_TYPE_DICT: Record<Route['pathname'], string> = {
'/csv-export': 'Export data to CSV file',
'/deposits': 'Deposits (L1 > L2)',
'/output-roots': 'Output roots',
'/dispute-games': 'Dispute games',
'/batches': 'Tx batches (L2 blocks)',
'/batches/[number]': 'L2 tx batch details',
'/blobs/[hash]': 'Blob details',
......
export const data = {
items: [
{
contract_address: '0x5cbe1b88b6357e6a8f0821bea72cc0b88c231f1c',
created_at: '2022-05-27T01:13:48.000000Z',
game_type: 0,
index: 6662,
l2_block_number: 12542890,
resolved_at: null,
status: 'In progress',
},
{
contract_address: '0x5cbe1b88b6357e6a8f0821bea72cc0b88c231f1c',
created_at: '2022-05-27T01:13:48.000000Z',
game_type: 0,
index: 6662,
l2_block_number: 12542890,
resolved_at: '2022-05-27T01:13:48.000000Z',
status: 'Defender wins',
},
],
next_page_params: {
items_count: 50,
index: 8382363,
},
};
......@@ -251,3 +251,13 @@ export const publicTagsSubmit: GetServerSideProps<Props> = async(context) => {
return base(context);
};
export const disputeGames: GetServerSideProps<Props> = async(context) => {
if (!config.features.faultProofSystem.isEnabled) {
return {
notFound: true,
};
}
return base(context);
};
......@@ -35,6 +35,7 @@ declare module "nextjs-routes" {
| StaticRoute<"/contract-verification">
| StaticRoute<"/csv-export">
| StaticRoute<"/deposits">
| StaticRoute<"/dispute-games">
| StaticRoute<"/gas-tracker">
| StaticRoute<"/graphiql">
| StaticRoute<"/">
......
import type { NextPage } from 'next';
import dynamic from 'next/dynamic';
import React from 'react';
import PageNextJs from 'nextjs/PageNextJs';
const DisputeGames = dynamic(() => import('ui/pages/OptimisticL2DisputeGames'), { ssr: false });
const Page: NextPage = () => {
return (
<PageNextJs pathname="/dispute-games">
<DisputeGames/>
</PageNextJs>
);
};
export default Page;
export { disputeGames as getServerSideProps } from 'nextjs/getServerSideProps';
......@@ -20,6 +20,7 @@ export const ENVS_MAP: Record<string, Array<[string, string]>> = {
[ 'NEXT_PUBLIC_ROLLUP_TYPE', 'optimistic' ],
[ 'NEXT_PUBLIC_ROLLUP_L1_BASE_URL', 'https://localhost:3101' ],
[ 'NEXT_PUBLIC_ROLLUP_L2_WITHDRAWAL_URL', 'https://localhost:3102' ],
[ 'NEXT_PUBLIC_FAULT_PROOF_ENABLED', 'true' ],
],
shibariumRollup: [
[ 'NEXT_PUBLIC_ROLLUP_TYPE', 'shibarium' ],
......
......@@ -61,6 +61,7 @@
| "filter"
| "finalized"
| "flame"
| "games"
| "gas_xl"
| "gas"
| "gear_slim"
......
import type {
OptimisticL2DepositsItem,
OptimisticL2DisputeGamesItem,
OptimisticL2OutputRootsItem,
OptimisticL2TxnBatchesItem,
OptimisticL2WithdrawalsItem,
......@@ -45,3 +46,13 @@ export const L2_OUTPUT_ROOTS_ITEM: OptimisticL2OutputRootsItem = {
l2_output_index: 50655,
output_root: TX_HASH,
};
export const L2_DISPUTE_GAMES_ITEM: OptimisticL2DisputeGamesItem = {
contract_address: ADDRESS_HASH,
created_at: '2023-06-01T15:26:12.000000Z',
game_type: 0,
index: 6594,
l2_block_number: 50655,
resolved_at: null,
status: 'In progress',
};
......@@ -61,15 +61,15 @@ export type OptimisticL2WithdrawalsItem = {
'status': string;
}
export const WITHDRAWAL_STATUSES = [
'Waiting for state root',
'Ready to prove',
'In challenge period',
'Ready for relay',
'Relayed',
] as const;
export type OptimisticL2WithdrawalStatus = typeof WITHDRAWAL_STATUSES[number];
export type OptimisticL2WithdrawalStatus =
'Waiting for state root' |
'Ready to prove' |
'In challenge period' |
'Waiting a game to resolve' |
'Ready to prove' |
'Proven' |
'Ready for relay' |
'Relayed';
export type OptimisticL2WithdrawalsResponse = {
items: Array<OptimisticL2WithdrawalsItem>;
......@@ -78,3 +78,21 @@ export type OptimisticL2WithdrawalsResponse = {
'nonce': string;
};
}
export type OptimisticL2DisputeGamesResponse = {
items: Array<OptimisticL2DisputeGamesItem>;
'next_page_params': {
'items_count': number;
'index': number;
};
}
export type OptimisticL2DisputeGamesItem = {
contract_address: string;
created_at: string;
game_type: number;
index: number;
l2_block_number: number;
resolved_at: string | null;
status: string;
}
import { Skeleton } from '@chakra-ui/react';
import React from 'react';
import type { OptimisticL2DisputeGamesItem } from 'types/api/optimisticL2';
import config from 'configs/app';
import dayjs from 'lib/date/dayjs';
import CopyToClipboard from 'ui/shared/CopyToClipboard';
import BlockEntityL2 from 'ui/shared/entities/block/BlockEntityL2';
import HashStringShorten from 'ui/shared/HashStringShorten';
import ListItemMobileGrid from 'ui/shared/ListItemMobile/ListItemMobileGrid';
const rollupFeature = config.features.rollup;
type Props = { item: OptimisticL2DisputeGamesItem; isLoading?: boolean };
const OptimisticL2DisputeGamesListItem = ({ item, isLoading }: Props) => {
if (!rollupFeature.isEnabled || rollupFeature.type !== 'optimistic') {
return null;
}
return (
<ListItemMobileGrid.Container>
<ListItemMobileGrid.Label isLoading={ isLoading }>Index</ListItemMobileGrid.Label>
<ListItemMobileGrid.Value fontWeight={ 600 } color="text">
<Skeleton isLoaded={ !isLoading } display="inline-block">{ item.index }</Skeleton>
</ListItemMobileGrid.Value>
<ListItemMobileGrid.Label isLoading={ isLoading }>Game type</ListItemMobileGrid.Label>
<ListItemMobileGrid.Value>
<Skeleton isLoaded={ !isLoading } display="inline-block">{ item.game_type }</Skeleton>
</ListItemMobileGrid.Value>
<ListItemMobileGrid.Label isLoading={ isLoading }>Address</ListItemMobileGrid.Label>
<ListItemMobileGrid.Value color="text">
<Skeleton isLoaded={ !isLoading } display="flex" overflow="hidden" w="100%" alignItems="center">
<HashStringShorten hash={ item.contract_address } type="long"/>
<CopyToClipboard text={ item.contract_address } ml={ 2 } isLoading={ isLoading }/>
</Skeleton>
</ListItemMobileGrid.Value>
<ListItemMobileGrid.Label isLoading={ isLoading }>L2 block #</ListItemMobileGrid.Label>
<ListItemMobileGrid.Value>
<BlockEntityL2
isLoading={ isLoading }
number={ item.l2_block_number }
fontSize="sm"
lineHeight={ 5 }
noIcon
/>
</ListItemMobileGrid.Value>
<ListItemMobileGrid.Label isLoading={ isLoading }>Age</ListItemMobileGrid.Label>
<ListItemMobileGrid.Value>
<Skeleton isLoaded={ !isLoading } display="inline-block">{ dayjs(item.created_at).fromNow() }</Skeleton>
</ListItemMobileGrid.Value>
<ListItemMobileGrid.Label isLoading={ isLoading }>Status</ListItemMobileGrid.Label>
<ListItemMobileGrid.Value color="text">
<Skeleton isLoaded={ !isLoading } display="inline-block">{ item.status }</Skeleton>
</ListItemMobileGrid.Value>
{ item.resolved_at && (
<>
<ListItemMobileGrid.Label isLoading={ isLoading }>Resolution age</ListItemMobileGrid.Label><ListItemMobileGrid.Value>
<Skeleton isLoaded={ !isLoading } display="inline-block">{ dayjs(item.resolved_at).fromNow() }</Skeleton>
</ListItemMobileGrid.Value>
</>
) }
</ListItemMobileGrid.Container>
);
};
export default OptimisticL2DisputeGamesListItem;
import { Table, Tbody, Th, Tr } from '@chakra-ui/react';
import React from 'react';
import type { OptimisticL2DisputeGamesItem } from 'types/api/optimisticL2';
import { default as Thead } from 'ui/shared/TheadSticky';
import OptimisticL2DisputeGamesTableItem from './OptimisticL2DisputeGamesTableItem';
type Props = {
items: Array<OptimisticL2DisputeGamesItem>;
top: number;
isLoading?: boolean;
}
const OptimisticL2DisputeGamesTable = ({ items, top, isLoading }: Props) => {
return (
<Table variant="simple" size="sm" style={{ tableLayout: 'auto' }} minW="950px">
<Thead top={ top }>
<Tr>
<Th>Index</Th>
<Th>Game type</Th>
<Th>Address</Th>
<Th>L2 block #</Th>
<Th>Age</Th>
<Th>Status</Th>
<Th>Resolution age</Th>
</Tr>
</Thead>
<Tbody>
{ items.map((item, index) => (
<OptimisticL2DisputeGamesTableItem
key={ String(item.index) + (isLoading ? index : '') }
item={ item }
isLoading={ isLoading }
/>
)) }
</Tbody>
</Table>
);
};
export default OptimisticL2DisputeGamesTable;
import { Flex, Td, Tr, Skeleton } from '@chakra-ui/react';
import React from 'react';
import type { OptimisticL2DisputeGamesItem } from 'types/api/optimisticL2';
import config from 'configs/app';
import dayjs from 'lib/date/dayjs';
import CopyToClipboard from 'ui/shared/CopyToClipboard';
import BlockEntityL2 from 'ui/shared/entities/block/BlockEntityL2';
import HashStringShorten from 'ui/shared/HashStringShorten';
const faultProofSystemFeature = config.features.faultProofSystem;
type Props = { item: OptimisticL2DisputeGamesItem; isLoading?: boolean };
const OptimisticL2DisputeGamesTableItem = ({ item, isLoading }: Props) => {
if (!faultProofSystemFeature.isEnabled) {
return null;
}
return (
<Tr>
<Td verticalAlign="middle">
<Skeleton isLoaded={ !isLoading } display="inline-block">{ item.index }</Skeleton>
</Td>
<Td verticalAlign="middle">
<Skeleton isLoaded={ !isLoading } display="inline-block">{ item.game_type }</Skeleton>
</Td>
<Td verticalAlign="middle">
<Flex overflow="hidden" w="100%" alignItems="center">
<Skeleton isLoaded={ !isLoading }>
<HashStringShorten hash={ item.contract_address } type="long"/>
</Skeleton>
<CopyToClipboard text={ item.contract_address } ml={ 2 } isLoading={ isLoading }/>
</Flex>
</Td>
<Td verticalAlign="middle">
<BlockEntityL2
isLoading={ isLoading }
number={ item.l2_block_number }
fontSize="sm"
lineHeight={ 5 }
noIcon
/>
</Td>
<Td verticalAlign="middle">
<Skeleton isLoaded={ !isLoading } display="inline-block">{ dayjs(item.created_at).fromNow() }</Skeleton>
</Td>
<Td verticalAlign="middle">
<Skeleton isLoaded={ !isLoading } display="inline-block">{ item.status }</Skeleton>
</Td>
<Td>
<Skeleton isLoaded={ !isLoading } display="inline-block">
{ item.resolved_at ? dayjs(item.resolved_at).fromNow() : 'N/A' }
</Skeleton>
</Td>
</Tr>
);
};
export default OptimisticL2DisputeGamesTableItem;
import React from 'react';
import { data as disputeGamesData } from 'mocks/l2disputeGames/disputeGames';
import { ENVS_MAP } from 'playwright/fixtures/mockEnvs';
import { test, expect } from 'playwright/lib';
import OptimisticL2DisputeGames from './OptimisticL2DisputeGames';
test('base view +@mobile', async({ render, mockEnvs, mockTextAd, mockApiResponse }) => {
test.slow();
await mockEnvs(ENVS_MAP.optimisticRollup);
await mockTextAd();
await mockApiResponse('optimistic_l2_dispute_games', disputeGamesData);
await mockApiResponse('optimistic_l2_dispute_games_count', 3971111);
const component = await render(<OptimisticL2DisputeGames/>);
await expect(component).toHaveScreenshot();
});
import { Hide, Show, Skeleton, Text } from '@chakra-ui/react';
import React from 'react';
import useApiQuery from 'lib/api/useApiQuery';
import { L2_DISPUTE_GAMES_ITEM } from 'stubs/L2';
import { generateListStub } from 'stubs/utils';
import OptimisticL2DisputeGamesListItem from 'ui/disputeGames/optimisticL2/OptimisticL2DisputeGamesListItem';
import OptimisticL2DisputeGamesTable from 'ui/disputeGames/optimisticL2/OptimisticL2DisputeGamesTable';
import { ACTION_BAR_HEIGHT_DESKTOP } from 'ui/shared/ActionBar';
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 OptimisticL2DisputeGames = () => {
const { data, isError, isPlaceholderData, pagination } = useQueryWithPages({
resourceName: 'optimistic_l2_dispute_games',
options: {
placeholderData: generateListStub<'optimistic_l2_dispute_games'>(
L2_DISPUTE_GAMES_ITEM,
50,
{
next_page_params: {
items_count: 50,
index: 9045200,
},
},
),
},
});
const countersQuery = useApiQuery('optimistic_l2_dispute_games_count', {
queryOptions: {
placeholderData: 50617,
},
});
const content = data?.items ? (
<>
<Show below="lg" ssr={ false }>
{ data.items.map(((item, index) => (
<OptimisticL2DisputeGamesListItem
key={ item.index + (isPlaceholderData ? String(index) : '') }
item={ item }
isLoading={ isPlaceholderData }
/>
))) }
</Show>
<Hide below="lg" ssr={ false }>
<OptimisticL2DisputeGamesTable items={ data.items } top={ pagination.isVisible ? ACTION_BAR_HEIGHT_DESKTOP : 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">
Dispute game index
<Text fontWeight={ 600 } whiteSpace="pre"> #{ data.items[0].index } </Text>to
<Text fontWeight={ 600 } whiteSpace="pre"> #{ data.items[data.items.length - 1].index } </Text>
(total of { countersQuery.data?.toLocaleString() } games)
</Skeleton>
);
})();
const actionBar = <StickyPaginationWithText text={ text } pagination={ pagination }/>;
return (
<>
<PageTitle title="Dispute games" withTextAd/>
<DataListDisplay
isError={ isError }
items={ data?.items }
emptyText="There are no dispute games."
content={ content }
actionBar={ actionBar }
/>
</>
);
};
export default OptimisticL2DisputeGames;
......@@ -2,7 +2,6 @@ import { Button } from '@chakra-ui/react';
import React from 'react';
import type { OptimisticL2WithdrawalStatus } from 'types/api/optimisticL2';
import { WITHDRAWAL_STATUSES } from 'types/api/optimisticL2';
import config from 'configs/app';
import TxEntityL1 from 'ui/shared/entities/tx/TxEntityL1';
......@@ -13,10 +12,34 @@ interface Props {
l1TxHash: string | undefined;
}
const WITHDRAWAL_STATUS_STEPS: Array<OptimisticL2WithdrawalStatus> = [
'Waiting for state root',
'Ready to prove',
'In challenge period',
'Ready for relay',
'Relayed',
];
const WITHDRAWAL_STATUS_ORDER_PROVEN: Array<OptimisticL2WithdrawalStatus> = [
'Waiting for state root',
'Ready to prove',
'Proven',
'Relayed',
];
const WITHDRAWAL_STATUS_ORDER_GAME: Array<OptimisticL2WithdrawalStatus> = [
'Waiting for state root',
'Ready to prove',
'Waiting a game to resolve',
'In challenge period',
'Ready for relay',
'Relayed',
];
const rollupFeature = config.features.rollup;
const TxDetailsWithdrawalStatus = ({ status, l1TxHash }: Props) => {
if (!status || !WITHDRAWAL_STATUSES.includes(status) || !rollupFeature.isEnabled || rollupFeature.type !== 'optimistic') {
if (!status || !rollupFeature.isEnabled || rollupFeature.type !== 'optimistic') {
return null;
}
......@@ -25,10 +48,14 @@ const TxDetailsWithdrawalStatus = ({ status, l1TxHash }: Props) => {
const steps = (() => {
switch (status) {
case 'Ready for relay':
return WITHDRAWAL_STATUSES.slice(0, -1);
return WITHDRAWAL_STATUS_STEPS.slice(0, -1);
case 'Proven':
return WITHDRAWAL_STATUS_ORDER_PROVEN;
case 'Waiting a game to resolve':
return WITHDRAWAL_STATUS_ORDER_GAME;
case 'Relayed': {
if (l1TxHash) {
return WITHDRAWAL_STATUSES.map((status) => {
return WITHDRAWAL_STATUS_STEPS.map((status) => {
return status === 'Relayed' ? {
content: <TxEntityL1 hash={ l1TxHash } truncation="constant" text="Relayed" noIcon/>,
label: status,
......@@ -36,11 +63,11 @@ const TxDetailsWithdrawalStatus = ({ status, l1TxHash }: Props) => {
});
}
return WITHDRAWAL_STATUSES;
return WITHDRAWAL_STATUS_STEPS;
}
default:
return WITHDRAWAL_STATUSES;
return WITHDRAWAL_STATUS_STEPS;
}
})();
......
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