mirror of
https://github.com/luxfi/explore.git
synced 2026-07-27 05:54:15 +00:00
beacon deposits (#2947)
* beacon deposits start * some fixes * test * counters * conflict fix * fix mock
This commit is contained in:
@@ -4,8 +4,9 @@ import { getEnvValue } from '../utils';
|
||||
|
||||
const title = 'Beacon chain';
|
||||
|
||||
const config: Feature<{ currency: { symbol: string } }> = (() => {
|
||||
const config: Feature<{ currency: { symbol: string }; validatorUrlTemplate: string | undefined }> = (() => {
|
||||
if (getEnvValue('NEXT_PUBLIC_HAS_BEACON_CHAIN') === 'true') {
|
||||
const validatorUrlTemplate = getEnvValue('NEXT_PUBLIC_BEACON_CHAIN_VALIDATOR_URL_TEMPLATE');
|
||||
return Object.freeze({
|
||||
title,
|
||||
isEnabled: true,
|
||||
@@ -15,6 +16,7 @@ const config: Feature<{ currency: { symbol: string } }> = (() => {
|
||||
getEnvValue('NEXT_PUBLIC_NETWORK_CURRENCY_SYMBOL') ||
|
||||
'', // maybe we need some other default value here
|
||||
},
|
||||
validatorUrlTemplate,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -27,6 +27,7 @@ NEXT_PUBLIC_FOOTER_LINKS=https://raw.githubusercontent.com/blockscout/frontend-c
|
||||
NEXT_PUBLIC_GAME_BADGE_CLAIM_LINK=https://badges.blockscout.com/mint/sherblockHolmesBadge
|
||||
NEXT_PUBLIC_GRAPHIQL_TRANSACTION=0xbf69c7abc4fee283b59a9633dadfdaedde5c5ee0fba3e80a08b5b8a3acbd4363
|
||||
NEXT_PUBLIC_HAS_BEACON_CHAIN=true
|
||||
NEXT_PUBLIC_BEACON_CHAIN_VALIDATOR_URL_TEMPLATE=https://light-sepolia.beaconcha.in/validator/{pk}
|
||||
NEXT_PUBLIC_HAS_USER_OPS=true
|
||||
NEXT_PUBLIC_HOMEPAGE_CHARTS=['daily_txs']
|
||||
NEXT_PUBLIC_HOMEPAGE_HERO_BANNER_CONFIG={'background':['rgba(51, 53, 67, 1)'],'text_color':['rgba(165, 252, 122, 1)']}
|
||||
|
||||
@@ -193,6 +193,13 @@ const beaconChainSchema = yup
|
||||
'NEXT_PUBLIC_BEACON_CHAIN_CURRENCY_SYMBOL cannot not be used if NEXT_PUBLIC_HAS_BEACON_CHAIN is not set to "true"',
|
||||
),
|
||||
}),
|
||||
NEXT_PUBLIC_BEACON_CHAIN_VALIDATOR_URL_TEMPLATE: yup
|
||||
.string()
|
||||
.when('NEXT_PUBLIC_HAS_BEACON_CHAIN', {
|
||||
is: (value: boolean) => value,
|
||||
then: (schema) => schema,
|
||||
otherwise: (schema) => schema.max(-1, 'NEXT_PUBLIC_BEACON_CHAIN_VALIDATOR_URL_TEMPLATE cannot not be used if NEXT_PUBLIC_HAS_BEACON_CHAIN is not set to "true"'),
|
||||
}),
|
||||
});
|
||||
|
||||
const tacSchema = yup
|
||||
|
||||
@@ -482,6 +482,7 @@ Ads are enabled by default on all self-hosted instances. If you would like to di
|
||||
| --- | --- | --- | --- | --- | --- | --- |
|
||||
| NEXT_PUBLIC_HAS_BEACON_CHAIN | `boolean` | Set to true for networks with the beacon chain | Required | - | `true` | v1.0.x+ |
|
||||
| NEXT_PUBLIC_BEACON_CHAIN_CURRENCY_SYMBOL | `string` | Beacon network currency symbol | - | `NEXT_PUBLIC_NETWORK_CURRENCY_SYMBOL` | `ETH` | v1.0.x+ |
|
||||
| NEXT_PUBLIC_BEACON_CHAIN_VALIDATOR_URL_TEMPLATE | `string` | Url template to build a link to validator. Should contain `{pk}` string that will be replaced with the validator's public key | - | - | `https://example.com/beacon/{pk}/validator` | v2.3.0+ |
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -21,6 +21,7 @@ import type {
|
||||
AddressNFTTokensFilter,
|
||||
} from 'types/api/address';
|
||||
import type { AddressesMetadataSearchFilters, AddressesMetadataSearchResult, AddressesResponse } from 'types/api/addresses';
|
||||
import type { DepositsResponse } from 'types/api/deposits';
|
||||
import type { LogsResponseAddress } from 'types/api/log';
|
||||
import type { TransactionsSorting } from 'types/api/transaction';
|
||||
|
||||
@@ -109,6 +110,12 @@ export const GENERAL_API_ADDRESS_RESOURCES = {
|
||||
filterFields: [ 'type' as const ],
|
||||
paginated: true,
|
||||
},
|
||||
address_deposits: {
|
||||
path: '/api/v2/addresses/:hash/beacon/deposits',
|
||||
pathParams: [ 'hash' as const ],
|
||||
filterFields: [],
|
||||
paginated: true,
|
||||
},
|
||||
address_withdrawals: {
|
||||
path: '/api/v2/addresses/:hash/withdrawals',
|
||||
pathParams: [ 'hash' as const ],
|
||||
@@ -174,6 +181,7 @@ R extends 'general:address_tokens' ? AddressTokensResponse :
|
||||
R extends 'general:address_nfts' ? AddressNFTsResponse :
|
||||
R extends 'general:address_collections' ? AddressCollectionsResponse :
|
||||
R extends 'general:address_withdrawals' ? AddressWithdrawalsResponse :
|
||||
R extends 'general:address_deposits' ? DepositsResponse :
|
||||
R extends 'general:address_epoch_rewards' ? AddressEpochRewardsResponse :
|
||||
R extends 'general:address_xstar_score' ? AddressXStarResponse :
|
||||
R extends 'general:address_3rd_party_info' ? unknown :
|
||||
|
||||
@@ -8,6 +8,7 @@ import type {
|
||||
BlockCountdownResponse,
|
||||
BlockInternalTransactionsResponse,
|
||||
} from 'types/api/block';
|
||||
import type { DepositsResponse } from 'types/api/deposits';
|
||||
import type { TTxsWithBlobsFilters } from 'types/api/txsFilters';
|
||||
|
||||
export const GENERAL_API_BLOCK_RESOURCES = {
|
||||
@@ -31,6 +32,12 @@ export const GENERAL_API_BLOCK_RESOURCES = {
|
||||
pathParams: [ 'height_or_hash' as const ],
|
||||
paginated: true,
|
||||
},
|
||||
block_deposits: {
|
||||
path: '/api/v2/blocks/:height_or_hash/beacon/deposits',
|
||||
pathParams: [ 'height_or_hash' as const ],
|
||||
filterFields: [],
|
||||
paginated: true,
|
||||
},
|
||||
block_withdrawals: {
|
||||
path: '/api/v2/blocks/:height_or_hash/withdrawals',
|
||||
pathParams: [ 'height_or_hash' as const ],
|
||||
@@ -49,6 +56,7 @@ R extends 'general:block_countdown' ? BlockCountdownResponse :
|
||||
R extends 'general:block_txs' ? BlockTransactionsResponse :
|
||||
R extends 'general:block_internal_txs' ? BlockInternalTransactionsResponse :
|
||||
R extends 'general:block_withdrawals' ? BlockWithdrawalsResponse :
|
||||
R extends 'general:block_deposits' ? DepositsResponse :
|
||||
never;
|
||||
/* eslint-enable @stylistic/indent */
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ import type { Blob } from 'types/api/blobs';
|
||||
import type { Block } from 'types/api/block';
|
||||
import type { ChartMarketResponse, ChartSecondaryCoinPriceResponse, ChartTransactionResponse } from 'types/api/charts';
|
||||
import type { BackendVersionConfig, CeloConfig, CsvExportConfig } from 'types/api/configs';
|
||||
import type { DepositsResponse, DepositsCounters } from 'types/api/deposits';
|
||||
import type { CeloEpochDetails, CeloEpochElectionRewardDetailsResponse, CeloEpochListResponse } from 'types/api/epochs';
|
||||
import type { IndexingStatus } from 'types/api/indexingStatus';
|
||||
import type { NovesAccountHistoryResponse, NovesDescribeTxsResponse, NovesResponseData } from 'types/api/noves';
|
||||
@@ -48,6 +49,16 @@ export const GENERAL_API_MISC_RESOURCES = {
|
||||
path: '/api/v2/withdrawals/counters',
|
||||
},
|
||||
|
||||
// DEPOSITS
|
||||
deposits: {
|
||||
path: '/api/v2/beacon/deposits',
|
||||
filterFields: [],
|
||||
paginated: true,
|
||||
},
|
||||
deposits_counters: {
|
||||
path: '/api/v2/beacon/deposits/count',
|
||||
},
|
||||
|
||||
// APP STATS
|
||||
stats: {
|
||||
path: '/api/v2/stats',
|
||||
@@ -296,6 +307,8 @@ R extends 'general:noves_address_history' ? NovesAccountHistoryResponse :
|
||||
R extends 'general:noves_describe_txs' ? NovesDescribeTxsResponse :
|
||||
R extends 'general:withdrawals' ? WithdrawalsResponse :
|
||||
R extends 'general:withdrawals_counters' ? WithdrawalsCounters :
|
||||
R extends 'general:deposits' ? DepositsResponse :
|
||||
R extends 'general:deposits_counters' ? DepositsCounters :
|
||||
R extends 'general:advanced_filter' ? AdvancedFilterResponse :
|
||||
R extends 'general:advanced_filter_methods' ? AdvancedFilterMethodsResponse :
|
||||
never;
|
||||
|
||||
@@ -207,6 +207,12 @@ export default function useNavItems(): ReturnType {
|
||||
validators,
|
||||
verifiedContracts,
|
||||
ensLookup,
|
||||
config.features.beaconChain.isEnabled && {
|
||||
text: 'Deposits',
|
||||
nextRoute: { pathname: '/deposits' as const },
|
||||
icon: 'arrows/south-east',
|
||||
isActive: pathname === '/deposits',
|
||||
},
|
||||
config.features.beaconChain.isEnabled && {
|
||||
text: 'Withdrawals',
|
||||
nextRoute: { pathname: '/withdrawals' as const },
|
||||
|
||||
@@ -39,7 +39,7 @@ const TEMPLATE_MAP: Record<Route['pathname'], string> = {
|
||||
'/txn-withdrawals': '%network_name% L2 to L1 message relayer',
|
||||
'/visualize/sol2uml': '%network_name% Solidity UML diagram',
|
||||
'/csv-export': '%network_name% export data to CSV',
|
||||
'/deposits': '%network_name% deposits (L1 > L2)',
|
||||
'/deposits': '%network_name% deposits - track on %network_name% explorer',
|
||||
'/output-roots': '%network_name% output roots',
|
||||
'/dispute-games': '%network_name% dispute games',
|
||||
'/batches': '%network_name% txn batches',
|
||||
|
||||
@@ -37,7 +37,7 @@ export const PAGE_TYPE_DICT: Record<Route['pathname'], string> = {
|
||||
'/txn-withdrawals': 'Txn withdrawals',
|
||||
'/visualize/sol2uml': 'Solidity UML diagram',
|
||||
'/csv-export': 'Export data to CSV file',
|
||||
'/deposits': 'Deposits (L1 > L2)',
|
||||
'/deposits': 'Deposits',
|
||||
'/output-roots': 'Output roots',
|
||||
'/dispute-games': 'Dispute games',
|
||||
'/batches': 'Txn batches',
|
||||
|
||||
@@ -8,4 +8,5 @@ export const base: AddressTabsCounters = {
|
||||
transactions_count: 51,
|
||||
validations_count: 42,
|
||||
withdrawals_count: 11,
|
||||
beacon_deposits_count: 10,
|
||||
};
|
||||
|
||||
@@ -0,0 +1,86 @@
|
||||
import type { AddressParam } from 'types/api/addressParams';
|
||||
import type { DepositsResponse } from 'types/api/deposits';
|
||||
|
||||
export const data: DepositsResponse = {
|
||||
items: [
|
||||
{
|
||||
amount: '192175000000000',
|
||||
block_number: 43242,
|
||||
index: 11688,
|
||||
pubkey: '0xf97e180c050e5Ab072211Ad2C213Eb5AEE4DF134',
|
||||
signature: '0xf97e180c050e5Ab072211Ad2C213Eb5AEE4DF134',
|
||||
status: 'completed',
|
||||
from_address: {
|
||||
hash: '0xf97e180c050e5Ab072211Ad2C213Eb5AEE4DF134',
|
||||
implementations: null,
|
||||
is_contract: false,
|
||||
is_verified: null,
|
||||
name: null,
|
||||
} as AddressParam,
|
||||
block_hash: '0xf97e180c050e5Ab072211Ad2C213Eb5AEE4DF134',
|
||||
block_timestamp: '2022-06-07T18:12:24.000000Z',
|
||||
transaction_hash: '0xf97e180c050e5Ab072211Ad2C213Eb5AEE4DF134',
|
||||
withdrawal_address: {
|
||||
hash: '0xf97e180c050e5Ab072211Ad2C213Eb5AEE4DF134',
|
||||
implementations: null,
|
||||
is_contract: false,
|
||||
is_verified: null,
|
||||
name: null,
|
||||
} as AddressParam,
|
||||
},
|
||||
{
|
||||
amount: '192175000000000',
|
||||
block_number: 43242,
|
||||
index: 11687,
|
||||
pubkey: '0xf97e180c050e5Ab072211Ad2C213Eb5AEE4DF134',
|
||||
signature: '0xf97e180c050e5Ab072211Ad2C213Eb5AEE4DF134',
|
||||
status: 'pending',
|
||||
from_address: {
|
||||
hash: '0xf97e987c050e5Ab072211Ad2C213Eb5AEE4DF134',
|
||||
implementations: null,
|
||||
is_contract: false,
|
||||
is_verified: null,
|
||||
name: null,
|
||||
} as AddressParam,
|
||||
block_hash: '0xf97e180c050e5Ab072211Ad2C213Eb5AEE4DF134',
|
||||
block_timestamp: '2022-05-07T18:12:24.000000Z',
|
||||
transaction_hash: '0xf97e180c050e5Ab072211Ad2C213Eb5AEE4DF134',
|
||||
withdrawal_address: {
|
||||
hash: '0xf97e180c050e5Ab072211Ad2C213Eb5AEE4DF134',
|
||||
implementations: null,
|
||||
is_contract: false,
|
||||
is_verified: null,
|
||||
name: null,
|
||||
} as AddressParam,
|
||||
},
|
||||
{
|
||||
amount: '182773000000000',
|
||||
block_number: 43242,
|
||||
index: 11686,
|
||||
pubkey: '0xf97e180c050e5Ab072211Ad2C213Eb5AEE4DF134',
|
||||
signature: '0xf97e180c050e5Ab072211Ad2C213Eb5AEE4DF134',
|
||||
status: 'invalid',
|
||||
from_address: {
|
||||
hash: '0xf97e123c050e5Ab072211Ad2C213Eb5AEE4DF134',
|
||||
implementations: null,
|
||||
is_contract: false,
|
||||
is_verified: null,
|
||||
name: null,
|
||||
} as AddressParam,
|
||||
block_hash: '0xf97e180c050e5Ab072211Ad2C213Eb5AEE4DF134',
|
||||
block_timestamp: '2022-04-07T18:12:24.000000Z',
|
||||
transaction_hash: '0xf97e180c050e5Ab072211Ad2C213Eb5AEE4DF134',
|
||||
withdrawal_address: {
|
||||
hash: '0xf97e180c050e5Ab072211Ad2C213Eb5AEE4DF134',
|
||||
implementations: null,
|
||||
is_contract: false,
|
||||
is_verified: null,
|
||||
name: null,
|
||||
} as AddressParam,
|
||||
},
|
||||
],
|
||||
next_page_params: {
|
||||
index: 11639,
|
||||
items_count: 50,
|
||||
},
|
||||
};
|
||||
@@ -184,7 +184,9 @@ export const rollup: Guard = (chainConfig: typeof config) => async() => {
|
||||
const DEPOSITS_ROLLUP_TYPES: Array<RollupType> = [ 'optimistic', 'shibarium', 'zkEvm', 'arbitrum', 'scroll' ];
|
||||
export const deposits: Guard = (chainConfig: typeof config) => async() => {
|
||||
const rollupFeature = chainConfig.features.rollup;
|
||||
if (!(rollupFeature.isEnabled && DEPOSITS_ROLLUP_TYPES.includes(rollupFeature.type))) {
|
||||
if (
|
||||
!chainConfig.features.beaconChain.isEnabled &&
|
||||
!(rollupFeature.isEnabled && DEPOSITS_ROLLUP_TYPES.includes(rollupFeature.type))) {
|
||||
return {
|
||||
notFound: true,
|
||||
};
|
||||
|
||||
@@ -6,6 +6,7 @@ import PageNextJs from 'nextjs/PageNextJs';
|
||||
|
||||
import config from 'configs/app';
|
||||
const rollupFeature = config.features.rollup;
|
||||
const beaconChainFeature = config.features.beaconChain;
|
||||
|
||||
const Deposits = dynamic(() => {
|
||||
if (rollupFeature.isEnabled && rollupFeature.type === 'optimistic') {
|
||||
@@ -28,6 +29,10 @@ const Deposits = dynamic(() => {
|
||||
return import('ui/pages/ScrollL2Deposits');
|
||||
}
|
||||
|
||||
if (beaconChainFeature.isEnabled) {
|
||||
return import('ui/pages/BeaconChainDeposits');
|
||||
}
|
||||
|
||||
throw new Error('Deposits feature is not enabled.');
|
||||
}, { ssr: false });
|
||||
|
||||
|
||||
@@ -68,6 +68,7 @@ export const ENVS_MAP: Record<string, Array<[string, string]>> = {
|
||||
],
|
||||
beaconChain: [
|
||||
[ 'NEXT_PUBLIC_HAS_BEACON_CHAIN', 'true' ],
|
||||
[ 'NEXT_PUBLIC_BEACON_CHAIN_VALIDATOR_URL_TEMPLATE', 'https://beaconcha.in/validator/{pk}' ],
|
||||
],
|
||||
txInterpretation: [
|
||||
[ 'NEXT_PUBLIC_TRANSACTION_INTERPRETATION_PROVIDER', 'blockscout' ],
|
||||
|
||||
@@ -55,6 +55,7 @@ export const ADDRESS_TABS_COUNTERS: AddressTabsCounters = {
|
||||
transactions_count: 10,
|
||||
validations_count: 10,
|
||||
withdrawals_count: 10,
|
||||
beacon_deposits_count: 10,
|
||||
};
|
||||
|
||||
export const TOP_ADDRESS: AddressesItem = {
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
import type { DepositsItem } from 'types/api/deposits';
|
||||
|
||||
import { ADDRESS_PARAMS } from './addressParams';
|
||||
import { TX_HASH } from './tx';
|
||||
|
||||
export const DEPOSIT: DepositsItem = {
|
||||
amount: '12565723',
|
||||
index: 1,
|
||||
block_number: 1231111111,
|
||||
block_hash: '0x1234567890',
|
||||
block_timestamp: '2023-05-12T19:29:12.000000Z',
|
||||
pubkey: '0x1234567890123456789012345678901234567890',
|
||||
status: 'pending',
|
||||
from_address: ADDRESS_PARAMS,
|
||||
transaction_hash: TX_HASH,
|
||||
withdrawal_address: ADDRESS_PARAMS,
|
||||
signature: '0x1234567890123456789012345678901234567890',
|
||||
};
|
||||
@@ -201,6 +201,7 @@ export type AddressTabsCounters = {
|
||||
transactions_count: number | null;
|
||||
validations_count: number | null;
|
||||
withdrawals_count: number | null;
|
||||
beacon_deposits_count: number | null;
|
||||
celo_election_rewards_count?: number | null;
|
||||
};
|
||||
|
||||
|
||||
@@ -44,6 +44,7 @@ export interface Block {
|
||||
transaction_fees: string | null;
|
||||
uncles_hashes: Array<string>;
|
||||
withdrawals_count?: number;
|
||||
beacon_deposits_count?: number;
|
||||
// ROOTSTOCK FIELDS
|
||||
bitcoin_merged_mining_coinbase_transaction?: string | null;
|
||||
bitcoin_merged_mining_header?: string | null;
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
import type { AddressParam } from './addressParams';
|
||||
|
||||
export type DepositStatus = 'pending' | 'invalid' | 'completed';
|
||||
|
||||
export type DepositsResponse = {
|
||||
items: Array<DepositsItem>;
|
||||
next_page_params: {
|
||||
index: number;
|
||||
items_count: number;
|
||||
} | null;
|
||||
};
|
||||
|
||||
export type DepositsItem = {
|
||||
amount: string;
|
||||
block_number: number;
|
||||
block_hash: string;
|
||||
block_timestamp: string;
|
||||
index: number;
|
||||
pubkey: string;
|
||||
signature: string;
|
||||
status: DepositStatus;
|
||||
from_address: AddressParam;
|
||||
transaction_hash: string;
|
||||
withdrawal_address: AddressParam;
|
||||
};
|
||||
|
||||
export type DepositsCounters = {
|
||||
deposits_count: string;
|
||||
};
|
||||
@@ -0,0 +1,83 @@
|
||||
import { Box } from '@chakra-ui/react';
|
||||
import { useRouter } from 'next/router';
|
||||
import React from 'react';
|
||||
|
||||
import useIsMounted from 'lib/hooks/useIsMounted';
|
||||
import getQueryParamString from 'lib/router/getQueryParamString';
|
||||
import { DEPOSIT } from 'stubs/deposits';
|
||||
import { generateListStub } from 'stubs/utils';
|
||||
import BeaconChainDepositsListItem from 'ui/deposits/beaconChain/BeaconChainDepositsListItem';
|
||||
import BeaconChainDepositsTable from 'ui/deposits/beaconChain/BeaconChainDepositsTable';
|
||||
import ActionBar, { ACTION_BAR_HEIGHT_DESKTOP } from 'ui/shared/ActionBar';
|
||||
import DataListDisplay from 'ui/shared/DataListDisplay';
|
||||
import Pagination from 'ui/shared/pagination/Pagination';
|
||||
import useQueryWithPages from 'ui/shared/pagination/useQueryWithPages';
|
||||
|
||||
type Props = {
|
||||
shouldRender?: boolean;
|
||||
isQueryEnabled?: boolean;
|
||||
};
|
||||
const AddressDeposits = ({ shouldRender = true, isQueryEnabled = true }: Props) => {
|
||||
const router = useRouter();
|
||||
const isMounted = useIsMounted();
|
||||
|
||||
const hash = getQueryParamString(router.query.hash);
|
||||
|
||||
const { data, isPlaceholderData, isError, pagination } = useQueryWithPages({
|
||||
resourceName: 'general:address_deposits',
|
||||
pathParams: { hash },
|
||||
options: {
|
||||
enabled: isQueryEnabled,
|
||||
placeholderData: generateListStub<'general:address_deposits'>(DEPOSIT, 50, { next_page_params: {
|
||||
index: 5,
|
||||
items_count: 50,
|
||||
} }),
|
||||
},
|
||||
});
|
||||
|
||||
if (!isMounted || !shouldRender) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const content = data?.items ? (
|
||||
<>
|
||||
<Box hideFrom="lg">
|
||||
{ data.items.map((item, index) => (
|
||||
<BeaconChainDepositsListItem
|
||||
key={ item.index + Number(isPlaceholderData ? index : '') }
|
||||
item={ item }
|
||||
view="address"
|
||||
isLoading={ isPlaceholderData }
|
||||
/>
|
||||
)) }
|
||||
</Box>
|
||||
<Box hideBelow="lg">
|
||||
<BeaconChainDepositsTable
|
||||
items={ data.items }
|
||||
view="address"
|
||||
top={ pagination.isVisible ? ACTION_BAR_HEIGHT_DESKTOP : 0 }
|
||||
isLoading={ isPlaceholderData }
|
||||
/>
|
||||
</Box>
|
||||
</>
|
||||
) : null ;
|
||||
|
||||
const actionBar = pagination.isVisible ? (
|
||||
<ActionBar mt={ -6 }>
|
||||
<Pagination ml="auto" { ...pagination }/>
|
||||
</ActionBar>
|
||||
) : null;
|
||||
|
||||
return (
|
||||
<DataListDisplay
|
||||
isError={ isError }
|
||||
itemsNum={ data?.items?.length }
|
||||
emptyText="There are no deposits for this address."
|
||||
actionBar={ actionBar }
|
||||
>
|
||||
{ content }
|
||||
</DataListDisplay>
|
||||
);
|
||||
};
|
||||
|
||||
export default AddressDeposits;
|
||||
@@ -0,0 +1,46 @@
|
||||
import { Box } from '@chakra-ui/react';
|
||||
import React from 'react';
|
||||
|
||||
import BeaconChainDepositsList from 'ui/deposits/beaconChain/BeaconChainDepositsList';
|
||||
import BeaconChainDepositsTable from 'ui/deposits/beaconChain/BeaconChainDepositsTable';
|
||||
import DataListDisplay from 'ui/shared/DataListDisplay';
|
||||
import type { QueryWithPagesResult } from 'ui/shared/pagination/useQueryWithPages';
|
||||
|
||||
type Props = {
|
||||
blockDepositsQuery: QueryWithPagesResult<'general:block_deposits'>;
|
||||
};
|
||||
const TABS_HEIGHT = 88;
|
||||
|
||||
const BlockDeposits = ({ blockDepositsQuery }: Props) => {
|
||||
const content = blockDepositsQuery.data?.items ? (
|
||||
<>
|
||||
<Box hideFrom="lg">
|
||||
<BeaconChainDepositsList
|
||||
items={ blockDepositsQuery.data.items }
|
||||
isLoading={ blockDepositsQuery.isPlaceholderData }
|
||||
view="block"
|
||||
/>
|
||||
</Box>
|
||||
<Box hideBelow="lg">
|
||||
<BeaconChainDepositsTable
|
||||
items={ blockDepositsQuery.data.items }
|
||||
isLoading={ blockDepositsQuery.isPlaceholderData }
|
||||
top={ blockDepositsQuery.pagination.isVisible ? TABS_HEIGHT : 0 }
|
||||
view="block"
|
||||
/>
|
||||
</Box>
|
||||
</>
|
||||
) : null ;
|
||||
|
||||
return (
|
||||
<DataListDisplay
|
||||
isError={ blockDepositsQuery.isError }
|
||||
itemsNum={ blockDepositsQuery.data?.items?.length }
|
||||
emptyText="There are no deposits for this block."
|
||||
>
|
||||
{ content }
|
||||
</DataListDisplay>
|
||||
);
|
||||
};
|
||||
|
||||
export default BlockDeposits;
|
||||
@@ -0,0 +1,41 @@
|
||||
import config from 'configs/app';
|
||||
import { DEPOSIT } from 'stubs/deposits';
|
||||
import { generateListStub } from 'stubs/utils';
|
||||
import type { QueryWithPagesResult } from 'ui/shared/pagination/useQueryWithPages';
|
||||
import useQueryWithPages from 'ui/shared/pagination/useQueryWithPages';
|
||||
|
||||
import type { BlockQuery } from './useBlockQuery';
|
||||
|
||||
export type BlockDepositsQuery = QueryWithPagesResult<'general:block_deposits'> & {
|
||||
isDegradedData: boolean;
|
||||
};
|
||||
|
||||
interface Params {
|
||||
heightOrHash: string;
|
||||
blockQuery: BlockQuery;
|
||||
tab: string;
|
||||
}
|
||||
|
||||
// No deposits data in RPC, so we use API only
|
||||
export default function useBlockDepositsQuery({ heightOrHash, blockQuery, tab }: Params): BlockDepositsQuery {
|
||||
const apiQuery = useQueryWithPages({
|
||||
resourceName: 'general:block_deposits',
|
||||
pathParams: { height_or_hash: heightOrHash },
|
||||
options: {
|
||||
enabled:
|
||||
tab === 'deposits' &&
|
||||
config.features.beaconChain.isEnabled &&
|
||||
!blockQuery.isPlaceholderData && !blockQuery.isDegradedData,
|
||||
placeholderData: generateListStub<'general:block_deposits'>(DEPOSIT, 50, { next_page_params: {
|
||||
index: 5,
|
||||
items_count: 50,
|
||||
} }),
|
||||
refetchOnMount: false,
|
||||
},
|
||||
});
|
||||
|
||||
return {
|
||||
...apiQuery,
|
||||
isDegradedData: false,
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
import { Box } from '@chakra-ui/react';
|
||||
import React from 'react';
|
||||
|
||||
import type { DepositsItem } from 'types/api/deposits';
|
||||
|
||||
import useLazyRenderedList from 'lib/hooks/useLazyRenderedList';
|
||||
|
||||
import BeaconChainDepositsListItem from './BeaconChainDepositsListItem';
|
||||
|
||||
type Props = {
|
||||
isLoading?: boolean;
|
||||
} & ({
|
||||
items: Array<DepositsItem>;
|
||||
view: 'list' | 'block' | 'address';
|
||||
});
|
||||
|
||||
const DepositsList = ({ items, view, isLoading }: Props) => {
|
||||
const { cutRef, renderedItemsNum } = useLazyRenderedList(items, !isLoading);
|
||||
|
||||
return (
|
||||
<Box>
|
||||
{ items.slice(0, renderedItemsNum).map((item, index) => {
|
||||
|
||||
const key = item.index + (isLoading ? String(index) : '');
|
||||
return (
|
||||
<BeaconChainDepositsListItem
|
||||
key={ key }
|
||||
item={ item as DepositsItem }
|
||||
view={ view }
|
||||
isLoading={ isLoading }
|
||||
/>
|
||||
);
|
||||
}) }
|
||||
<div ref={ cutRef }/>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default React.memo(DepositsList);
|
||||
@@ -0,0 +1,97 @@
|
||||
import React from 'react';
|
||||
|
||||
import type { DepositsItem } from 'types/api/deposits';
|
||||
|
||||
import config from 'configs/app';
|
||||
import { currencyUnits } from 'lib/units';
|
||||
import BeaconChainDepositSignature from 'ui/shared/beacon/BeaconChainDepositSignature';
|
||||
import BeaconChainDepositStatusTag from 'ui/shared/beacon/BeaconChainDepositStatusTag';
|
||||
import BeaconChainValidatorLink from 'ui/shared/beacon/BeaconChainValidatorLink';
|
||||
import CurrencyValue from 'ui/shared/CurrencyValue';
|
||||
import AddressEntity from 'ui/shared/entities/address/AddressEntity';
|
||||
import BlockEntity from 'ui/shared/entities/block/BlockEntity';
|
||||
import TxEntity from 'ui/shared/entities/tx/TxEntity';
|
||||
import ListItemMobileGrid from 'ui/shared/ListItemMobile/ListItemMobileGrid';
|
||||
import TimeWithTooltip from 'ui/shared/time/TimeWithTooltip';
|
||||
|
||||
const feature = config.features.beaconChain;
|
||||
|
||||
type Props = {
|
||||
item: DepositsItem;
|
||||
view: 'list' | 'address' | 'block';
|
||||
isLoading?: boolean;
|
||||
};
|
||||
|
||||
const BeaconChainDepositsListItem = ({ item, isLoading, view }: Props) => {
|
||||
if (!feature.isEnabled) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<ListItemMobileGrid.Container gridTemplateColumns="120px auto">
|
||||
|
||||
<ListItemMobileGrid.Label isLoading={ isLoading }>Transaction hash</ListItemMobileGrid.Label>
|
||||
<ListItemMobileGrid.Value>
|
||||
<TxEntity hash={ item.transaction_hash } isLoading={ isLoading } truncation="constant_long"/>
|
||||
</ListItemMobileGrid.Value>
|
||||
|
||||
{ view !== 'block' && (
|
||||
<>
|
||||
<ListItemMobileGrid.Label isLoading={ isLoading }>Block</ListItemMobileGrid.Label>
|
||||
<ListItemMobileGrid.Value>
|
||||
<BlockEntity
|
||||
number={ item.block_number }
|
||||
hash={ item.block_hash }
|
||||
isLoading={ isLoading }
|
||||
/>
|
||||
</ListItemMobileGrid.Value>
|
||||
|
||||
<ListItemMobileGrid.Label isLoading={ isLoading }>Age</ListItemMobileGrid.Label>
|
||||
<ListItemMobileGrid.Value>
|
||||
<TimeWithTooltip
|
||||
timestamp={ item.block_timestamp }
|
||||
isLoading={ isLoading }
|
||||
display="inline-block"
|
||||
/>
|
||||
</ListItemMobileGrid.Value>
|
||||
</>
|
||||
) }
|
||||
|
||||
<ListItemMobileGrid.Label isLoading={ isLoading }>Value</ListItemMobileGrid.Label>
|
||||
<ListItemMobileGrid.Value>
|
||||
<CurrencyValue value={ item.amount } currency={ currencyUnits.ether } isLoading={ isLoading }/>
|
||||
</ListItemMobileGrid.Value>
|
||||
|
||||
{ view !== 'address' && (
|
||||
<>
|
||||
<ListItemMobileGrid.Label isLoading={ isLoading }>From</ListItemMobileGrid.Label>
|
||||
<ListItemMobileGrid.Value>
|
||||
<AddressEntity
|
||||
address={ item.from_address }
|
||||
isLoading={ isLoading }
|
||||
truncation="constant"
|
||||
/>
|
||||
</ListItemMobileGrid.Value>
|
||||
</>
|
||||
) }
|
||||
|
||||
<ListItemMobileGrid.Label isLoading={ isLoading }>PubKey</ListItemMobileGrid.Label>
|
||||
<ListItemMobileGrid.Value>
|
||||
<BeaconChainValidatorLink pubkey={ item.pubkey } isLoading={ isLoading }/>
|
||||
</ListItemMobileGrid.Value>
|
||||
|
||||
<ListItemMobileGrid.Label isLoading={ isLoading }>Signature</ListItemMobileGrid.Label>
|
||||
<ListItemMobileGrid.Value>
|
||||
<BeaconChainDepositSignature signature={ item.signature } isLoading={ Boolean(isLoading) }/>
|
||||
</ListItemMobileGrid.Value>
|
||||
|
||||
<ListItemMobileGrid.Label isLoading={ isLoading }>Status</ListItemMobileGrid.Label>
|
||||
<ListItemMobileGrid.Value>
|
||||
<BeaconChainDepositStatusTag status={ item.status } isLoading={ Boolean(isLoading) }/>
|
||||
</ListItemMobileGrid.Value>
|
||||
|
||||
</ListItemMobileGrid.Container>
|
||||
);
|
||||
};
|
||||
|
||||
export default BeaconChainDepositsListItem;
|
||||
@@ -0,0 +1,52 @@
|
||||
import React from 'react';
|
||||
|
||||
import type { DepositsItem } from 'types/api/deposits';
|
||||
|
||||
import config from 'configs/app';
|
||||
import useLazyRenderedList from 'lib/hooks/useLazyRenderedList';
|
||||
import { TableBody, TableColumnHeader, TableHeaderSticky, TableRoot, TableRow } from 'toolkit/chakra/table';
|
||||
import TimeFormatToggle from 'ui/shared/time/TimeFormatToggle';
|
||||
|
||||
import BeaconChainDepositsTableItem from './BeaconChainDepositsTableItem';
|
||||
|
||||
const feature = config.features.beaconChain;
|
||||
|
||||
type Props = {
|
||||
top: number;
|
||||
isLoading?: boolean;
|
||||
items: Array<DepositsItem>;
|
||||
view: 'list' | 'address' | 'block';
|
||||
};
|
||||
|
||||
const BeaconChainDepositsTable = ({ items, isLoading, top, view }: Props) => {
|
||||
const { cutRef, renderedItemsNum } = useLazyRenderedList(items, !isLoading);
|
||||
|
||||
if (!feature.isEnabled) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<TableRoot style={{ tableLayout: 'auto' }} minW="950px">
|
||||
<TableHeaderSticky top={ top }>
|
||||
<TableRow>
|
||||
<TableColumnHeader>Transaction hash</TableColumnHeader>
|
||||
{ view !== 'block' && <TableColumnHeader>Block</TableColumnHeader> }
|
||||
{ view !== 'block' && <TableColumnHeader>Timestamp<TimeFormatToggle/></TableColumnHeader> }
|
||||
<TableColumnHeader>{ `Value ${ feature.currency.symbol }` }</TableColumnHeader>
|
||||
{ view !== 'address' && <TableColumnHeader>From</TableColumnHeader> }
|
||||
<TableColumnHeader>PubKey</TableColumnHeader>
|
||||
<TableColumnHeader>Signature</TableColumnHeader>
|
||||
<TableColumnHeader>Status</TableColumnHeader>
|
||||
</TableRow>
|
||||
</TableHeaderSticky>
|
||||
<TableBody>
|
||||
{ items.slice(0, renderedItemsNum).map((item, index) => (
|
||||
<BeaconChainDepositsTableItem key={ item.index + (isLoading ? String(index) : '') } item={ item } view={ view } isLoading={ isLoading }/>
|
||||
)) }
|
||||
<TableRow ref={ cutRef }/>
|
||||
</TableBody>
|
||||
</TableRoot>
|
||||
);
|
||||
};
|
||||
|
||||
export default BeaconChainDepositsTable;
|
||||
@@ -0,0 +1,79 @@
|
||||
import React from 'react';
|
||||
|
||||
import type { DepositsItem } from 'types/api/deposits';
|
||||
|
||||
import { TableCell, TableRow } from 'toolkit/chakra/table';
|
||||
import BeaconChainDepositSignature from 'ui/shared/beacon/BeaconChainDepositSignature';
|
||||
import BeaconChainDepositStatusTag from 'ui/shared/beacon/BeaconChainDepositStatusTag';
|
||||
import BeaconChainValidatorLink from 'ui/shared/beacon/BeaconChainValidatorLink';
|
||||
import CurrencyValue from 'ui/shared/CurrencyValue';
|
||||
import AddressEntity from 'ui/shared/entities/address/AddressEntity';
|
||||
import BlockEntity from 'ui/shared/entities/block/BlockEntity';
|
||||
import TxEntity from 'ui/shared/entities/tx/TxEntity';
|
||||
import TimeWithTooltip from 'ui/shared/time/TimeWithTooltip';
|
||||
|
||||
type Props = {
|
||||
item: DepositsItem;
|
||||
view: 'list' | 'address' | 'block';
|
||||
isLoading?: boolean;
|
||||
};
|
||||
|
||||
const BeaconChainDepositsTableItem = ({ item, view, isLoading }: Props) => {
|
||||
return (
|
||||
<TableRow>
|
||||
<TableCell verticalAlign="middle">
|
||||
<TxEntity
|
||||
hash={ item.transaction_hash }
|
||||
isLoading={ isLoading }
|
||||
truncation="constant_long"
|
||||
noIcon
|
||||
noCopy={ false }
|
||||
/>
|
||||
</TableCell>
|
||||
{ view !== 'block' && (
|
||||
<TableCell verticalAlign="middle">
|
||||
<BlockEntity
|
||||
number={ item.block_number }
|
||||
hash={ item.block_hash }
|
||||
isLoading={ isLoading }
|
||||
textStyle="sm"
|
||||
noIcon
|
||||
/>
|
||||
</TableCell>
|
||||
) }
|
||||
{ view !== 'block' && (
|
||||
<TableCell verticalAlign="middle">
|
||||
<TimeWithTooltip
|
||||
timestamp={ item.block_timestamp }
|
||||
isLoading={ isLoading }
|
||||
color="text.secondary"
|
||||
display="inline-block"
|
||||
/>
|
||||
</TableCell>
|
||||
) }
|
||||
<TableCell verticalAlign="middle">
|
||||
<CurrencyValue value={ item.amount } isLoading={ isLoading }/>
|
||||
</TableCell>
|
||||
{ view !== 'address' && (
|
||||
<TableCell verticalAlign="middle">
|
||||
<AddressEntity
|
||||
address={ item.from_address }
|
||||
isLoading={ isLoading }
|
||||
truncation="constant"
|
||||
/>
|
||||
</TableCell>
|
||||
) }
|
||||
<TableCell verticalAlign="middle" maxW="200px" overflow="hidden">
|
||||
<BeaconChainValidatorLink pubkey={ item.pubkey } isLoading={ isLoading }/>
|
||||
</TableCell>
|
||||
<TableCell verticalAlign="middle" maxW="200px" overflow="hidden">
|
||||
<BeaconChainDepositSignature signature={ item.signature } isLoading={ Boolean(isLoading) }/>
|
||||
</TableCell>
|
||||
<TableCell verticalAlign="middle">
|
||||
<BeaconChainDepositStatusTag status={ item.status } isLoading={ Boolean(isLoading) }/>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
);
|
||||
};
|
||||
|
||||
export default BeaconChainDepositsTableItem;
|
||||
@@ -27,6 +27,7 @@ import AddressAccountHistory from 'ui/address/AddressAccountHistory';
|
||||
import AddressBlocksValidated from 'ui/address/AddressBlocksValidated';
|
||||
import AddressCoinBalance from 'ui/address/AddressCoinBalance';
|
||||
import AddressContract from 'ui/address/AddressContract';
|
||||
import AddressDeposits from 'ui/address/AddressDeposits';
|
||||
import AddressDetails from 'ui/address/AddressDetails';
|
||||
import AddressEpochRewards from 'ui/address/AddressEpochRewards';
|
||||
import AddressInternalTxs from 'ui/address/AddressInternalTxs';
|
||||
@@ -224,6 +225,14 @@ const AddressPageContent = () => {
|
||||
component: <AddressUserOps shouldRender={ !isTabsLoading } isQueryEnabled={ areQueriesEnabled }/>,
|
||||
} :
|
||||
undefined,
|
||||
config.features.beaconChain.isEnabled && addressTabsCountersQuery.data?.beacon_deposits_count ?
|
||||
{
|
||||
id: 'deposits',
|
||||
title: 'Deposits',
|
||||
count: addressTabsCountersQuery.data?.beacon_deposits_count,
|
||||
component: <AddressDeposits shouldRender={ !isTabsLoading } isQueryEnabled={ areQueriesEnabled }/>,
|
||||
} :
|
||||
undefined,
|
||||
config.features.beaconChain.isEnabled && addressTabsCountersQuery.data?.withdrawals_count ?
|
||||
{
|
||||
id: 'withdrawals',
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
import React from 'react';
|
||||
|
||||
import { data as depositsData } from 'mocks/deposits/deposits';
|
||||
import { ENVS_MAP } from 'playwright/fixtures/mockEnvs';
|
||||
import { test, expect, devices } from 'playwright/lib';
|
||||
|
||||
import BeaconChainDeposits from './BeaconChainDeposits';
|
||||
|
||||
test('base view', async({ render, mockEnvs, mockTextAd, mockApiResponse }) => {
|
||||
await mockEnvs(ENVS_MAP.beaconChain);
|
||||
await mockTextAd();
|
||||
await mockApiResponse('general:deposits', depositsData);
|
||||
await mockApiResponse('general:deposits_counters', { deposits_count: '111111' });
|
||||
const component = await render(<BeaconChainDeposits/>);
|
||||
await expect(component).toHaveScreenshot();
|
||||
});
|
||||
|
||||
test.describe('mobile', () => {
|
||||
test.use({ viewport: devices['iPhone 13 Pro'].viewport });
|
||||
test('base view', async({ render, mockEnvs, mockTextAd, mockApiResponse }) => {
|
||||
await mockEnvs(ENVS_MAP.beaconChain);
|
||||
await mockTextAd();
|
||||
await mockApiResponse('general:deposits', depositsData);
|
||||
await mockApiResponse('general:deposits_counters', { deposits_count: '111111' });
|
||||
const component = await render(<BeaconChainDeposits/>);
|
||||
await expect(component).toHaveScreenshot();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,98 @@
|
||||
import { Box, Text } from '@chakra-ui/react';
|
||||
import BigNumber from 'bignumber.js';
|
||||
import React from 'react';
|
||||
|
||||
import config from 'configs/app';
|
||||
import useApiQuery from 'lib/api/useApiQuery';
|
||||
import { DEPOSIT } from 'stubs/deposits';
|
||||
import { generateListStub } from 'stubs/utils';
|
||||
import { Skeleton } from 'toolkit/chakra/skeleton';
|
||||
import BeaconChainDepositsListItem from 'ui/deposits/beaconChain/BeaconChainDepositsListItem';
|
||||
import BeaconChainDepositsTable from 'ui/deposits/beaconChain/BeaconChainDepositsTable';
|
||||
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 feature = config.features.beaconChain;
|
||||
|
||||
const Deposits = () => {
|
||||
const { data, isError, isPlaceholderData, pagination } = useQueryWithPages({
|
||||
resourceName: 'general:deposits',
|
||||
options: {
|
||||
placeholderData: generateListStub<'general:deposits'>(DEPOSIT, 50, { next_page_params: {
|
||||
index: 5,
|
||||
items_count: 50,
|
||||
} }),
|
||||
},
|
||||
});
|
||||
|
||||
const countersQuery = useApiQuery('general:deposits_counters', {
|
||||
queryOptions: {
|
||||
placeholderData: {
|
||||
deposits_count: '19091878',
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const content = data?.items ? (
|
||||
<>
|
||||
<Box hideFrom="lg">
|
||||
{ data.items.map(((item, index) => (
|
||||
<BeaconChainDepositsListItem
|
||||
key={ item.index + (isPlaceholderData ? String(index) : '') }
|
||||
item={ item }
|
||||
view="list"
|
||||
isLoading={ isPlaceholderData }
|
||||
/>
|
||||
))) }
|
||||
</Box>
|
||||
<Box hideBelow="lg">
|
||||
<BeaconChainDepositsTable
|
||||
items={ data.items }
|
||||
view="list"
|
||||
top={ pagination.isVisible ? ACTION_BAR_HEIGHT_DESKTOP : 0 }
|
||||
isLoading={ isPlaceholderData }
|
||||
/>
|
||||
</Box>
|
||||
</>
|
||||
) : null;
|
||||
|
||||
const text = (() => {
|
||||
if (countersQuery.isError || !feature.isEnabled) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<Skeleton loading={ countersQuery.isPlaceholderData || isPlaceholderData } display="flex" flexWrap="wrap">
|
||||
{ countersQuery.data && (
|
||||
<Text lineHeight={{ base: '24px', lg: '32px' }}>
|
||||
{ BigNumber(countersQuery.data.deposits_count).toFormat() } deposits processed
|
||||
</Text>
|
||||
) }
|
||||
</Skeleton>
|
||||
);
|
||||
})();
|
||||
|
||||
const actionBar = <StickyPaginationWithText text={ text } pagination={ pagination }/>;
|
||||
|
||||
return (
|
||||
<>
|
||||
<PageTitle
|
||||
title={ config.meta.seo.enhancedDataEnabled ? `${ config.chain.name } deposits` : 'Deposits' }
|
||||
withTextAd
|
||||
/>
|
||||
<DataListDisplay
|
||||
isError={ isError }
|
||||
itemsNum={ data?.items.length }
|
||||
emptyText="There are no deposits."
|
||||
actionBar={ actionBar }
|
||||
>
|
||||
{ content }
|
||||
</DataListDisplay>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default Deposits;
|
||||
+18
-1
@@ -18,10 +18,12 @@ import getQueryParamString from 'lib/router/getQueryParamString';
|
||||
import { Skeleton } from 'toolkit/chakra/skeleton';
|
||||
import RoutedTabs from 'toolkit/components/RoutedTabs/RoutedTabs';
|
||||
import BlockCeloEpochTag from 'ui/block/BlockCeloEpochTag';
|
||||
import BlockDeposits from 'ui/block/BlockDeposits';
|
||||
import BlockDetails from 'ui/block/BlockDetails';
|
||||
import BlockInternalTxs from 'ui/block/BlockInternalTxs';
|
||||
import BlockWithdrawals from 'ui/block/BlockWithdrawals';
|
||||
import useBlockBlobTxsQuery from 'ui/block/useBlockBlobTxsQuery';
|
||||
import useBlockDepositsQuery from 'ui/block/useBlockDepositsQuery';
|
||||
import useBlockInternalTxsQuery from 'ui/block/useBlockInternalTxsQuery';
|
||||
import useBlockQuery from 'ui/block/useBlockQuery';
|
||||
import useBlockTxsQuery from 'ui/block/useBlockTxsQuery';
|
||||
@@ -53,12 +55,14 @@ const BlockPageContent = () => {
|
||||
const blockQuery = useBlockQuery({ heightOrHash });
|
||||
const blockTxsQuery = useBlockTxsQuery({ heightOrHash, blockQuery, tab });
|
||||
const blockWithdrawalsQuery = useBlockWithdrawalsQuery({ heightOrHash, blockQuery, tab });
|
||||
const blockDepositsQuery = useBlockDepositsQuery({ heightOrHash, blockQuery, tab });
|
||||
const blockBlobTxsQuery = useBlockBlobTxsQuery({ heightOrHash, blockQuery, tab });
|
||||
const blockInternalTxsQuery = useBlockInternalTxsQuery({ heightOrHash, blockQuery, tab });
|
||||
|
||||
const hasPagination = !isMobile && (
|
||||
(tab === 'txs' && blockTxsQuery.pagination.isVisible) ||
|
||||
(tab === 'withdrawals' && blockWithdrawalsQuery.pagination.isVisible) ||
|
||||
(tab === 'deposits' && blockDepositsQuery.pagination.isVisible) ||
|
||||
(tab === 'internal_txs' && blockInternalTxsQuery.pagination.isVisible)
|
||||
);
|
||||
|
||||
@@ -101,6 +105,17 @@ const BlockPageContent = () => {
|
||||
<TxsWithFrontendSorting query={ blockBlobTxsQuery } showBlockInfo={ false }/>
|
||||
),
|
||||
} : null,
|
||||
config.features.beaconChain.isEnabled && Boolean(blockQuery.data?.beacon_deposits_count) ?
|
||||
{
|
||||
id: 'deposits',
|
||||
title: 'Deposits',
|
||||
component: (
|
||||
<>
|
||||
{ blockDepositsQuery.isDegradedData && <ServiceDegradationWarning isLoading={ blockDepositsQuery.isPlaceholderData } mb={ 6 }/> }
|
||||
<BlockDeposits blockDepositsQuery={ blockDepositsQuery }/>
|
||||
</>
|
||||
),
|
||||
} : null,
|
||||
config.features.beaconChain.isEnabled && Boolean(blockQuery.data?.withdrawals_count) ?
|
||||
{
|
||||
id: 'withdrawals',
|
||||
@@ -112,13 +127,15 @@ const BlockPageContent = () => {
|
||||
</>
|
||||
),
|
||||
} : null,
|
||||
].filter(Boolean)), [ blockBlobTxsQuery, blockInternalTxsQuery, blockQuery, blockTxsQuery, blockWithdrawalsQuery, hasPagination ]);
|
||||
].filter(Boolean)), [ blockBlobTxsQuery, blockDepositsQuery, blockInternalTxsQuery, blockQuery, blockTxsQuery, blockWithdrawalsQuery, hasPagination ]);
|
||||
|
||||
let pagination;
|
||||
if (tab === 'txs') {
|
||||
pagination = blockTxsQuery.pagination;
|
||||
} else if (tab === 'withdrawals') {
|
||||
pagination = blockWithdrawalsQuery.pagination;
|
||||
} else if (tab === 'deposits') {
|
||||
pagination = blockDepositsQuery.pagination;
|
||||
} else if (tab === 'internal_txs') {
|
||||
pagination = blockInternalTxsQuery.pagination;
|
||||
}
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 46 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 79 KiB |
@@ -0,0 +1,20 @@
|
||||
import { chakra, Text } from '@chakra-ui/react';
|
||||
import React from 'react';
|
||||
|
||||
import { Skeleton } from 'toolkit/chakra/skeleton';
|
||||
import { TruncatedTextTooltip } from 'toolkit/components/truncation/TruncatedTextTooltip';
|
||||
|
||||
import CopyToClipboard from '../CopyToClipboard';
|
||||
|
||||
const BeaconChainDepositSignature = ({ signature, isLoading }: { signature: string; isLoading: boolean }) => {
|
||||
return (
|
||||
<Skeleton loading={ isLoading } display="grid" gridTemplateColumns="1fr 24px" overflow="hidden">
|
||||
<TruncatedTextTooltip label={ signature }>
|
||||
<Text overflow="hidden" textOverflow="ellipsis" whiteSpace="nowrap">{ signature }</Text>
|
||||
</TruncatedTextTooltip>
|
||||
<CopyToClipboard text={ signature }/>
|
||||
</Skeleton>
|
||||
);
|
||||
};
|
||||
|
||||
export default React.memo(chakra(BeaconChainDepositSignature));
|
||||
@@ -0,0 +1,25 @@
|
||||
import { capitalize } from 'es-toolkit';
|
||||
import React from 'react';
|
||||
|
||||
import type { DepositsItem } from 'types/api/deposits';
|
||||
|
||||
import StatusTag from '../statusTag/StatusTag';
|
||||
|
||||
const BeaconChainDepositStatusTag = ({ status, isLoading }: { status: DepositsItem['status']; isLoading: boolean }) => {
|
||||
const statusValue = (() => {
|
||||
switch (status) {
|
||||
case 'pending':
|
||||
return 'pending';
|
||||
case 'completed':
|
||||
return 'ok';
|
||||
case 'invalid':
|
||||
return 'error';
|
||||
default:
|
||||
return 'pending';
|
||||
}
|
||||
})();
|
||||
|
||||
return <StatusTag type={ statusValue } text={ capitalize(status) } loading={ isLoading }/>;
|
||||
};
|
||||
|
||||
export default BeaconChainDepositStatusTag;
|
||||
@@ -0,0 +1,66 @@
|
||||
import { Text } from '@chakra-ui/react';
|
||||
import React from 'react';
|
||||
|
||||
import config from 'configs/app';
|
||||
import { Link } from 'toolkit/chakra/link';
|
||||
import { Skeleton } from 'toolkit/chakra/skeleton';
|
||||
import { TruncatedTextTooltip } from 'toolkit/components/truncation/TruncatedTextTooltip';
|
||||
import CopyToClipboard from 'ui/shared/CopyToClipboard';
|
||||
|
||||
const feature = config.features.beaconChain;
|
||||
|
||||
const BeaconChainValidatorLink = ({ pubkey, isLoading }: { pubkey: string; isLoading?: boolean }) => {
|
||||
if (!feature.isEnabled) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let content;
|
||||
|
||||
if (!feature.validatorUrlTemplate) {
|
||||
content = (
|
||||
<Text
|
||||
display="inline-block"
|
||||
textOverflow="ellipsis"
|
||||
overflow="hidden"
|
||||
whiteSpace="nowrap"
|
||||
maxW="100%"
|
||||
>
|
||||
{ pubkey }
|
||||
</Text>
|
||||
);
|
||||
} else {
|
||||
content = (
|
||||
<Link
|
||||
href={ feature.validatorUrlTemplate.replace('{pk}', pubkey) }
|
||||
external
|
||||
loading={ isLoading }
|
||||
overflow="hidden"
|
||||
display="grid"
|
||||
gridTemplateColumns="auto 20px"
|
||||
>
|
||||
<TruncatedTextTooltip label={ pubkey }>
|
||||
<Text overflow="hidden" textOverflow="ellipsis" whiteSpace="nowrap">{ pubkey }</Text>
|
||||
</TruncatedTextTooltip>
|
||||
</Link>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<Skeleton
|
||||
display="grid"
|
||||
overflow="hidden"
|
||||
gridTemplateColumns="auto 24px"
|
||||
alignItems="center"
|
||||
loading={ isLoading }
|
||||
>
|
||||
{ content }
|
||||
<CopyToClipboard
|
||||
text={ pubkey }
|
||||
type="text"
|
||||
isLoading={ isLoading }
|
||||
ml={ 1 }
|
||||
/>
|
||||
</Skeleton>
|
||||
);
|
||||
};
|
||||
|
||||
export default React.memo(BeaconChainValidatorLink);
|
||||
Reference in New Issue
Block a user