Commit 35b715cd authored by isstuev's avatar isstuev

add blackfort validators

parent 99446524
# Set of ENVs for BXN Testnet network explorer
# https://blackfort-testnet.blockscout.com
# This is an auto-generated file. To update all values, run "yarn dev:preset:sync --name=blackfort_testnet"
# Local ENVs
NEXT_PUBLIC_APP_PROTOCOL=http
NEXT_PUBLIC_APP_HOST=localhost
NEXT_PUBLIC_APP_PORT=3000
NEXT_PUBLIC_APP_ENV=development
NEXT_PUBLIC_API_WEBSOCKET_PROTOCOL=ws
# Instance ENVs
NEXT_PUBLIC_ADMIN_SERVICE_API_HOST=https://admin-rs.services.blockscout.com
NEXT_PUBLIC_API_BASE_PATH=/
NEXT_PUBLIC_API_HOST=blackfort-testnet.blockscout.com
NEXT_PUBLIC_API_SPEC_URL=https://raw.githubusercontent.com/blockscout/blockscout-api-v2-swagger/main/swagger.yaml
NEXT_PUBLIC_CONTRACT_CODE_IDES=[{'title':'Remix IDE','url':'https://remix.ethereum.org/?address={hash}&blockscout={domain}','icon_url':'https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/ide-icons/remix.png'}]
NEXT_PUBLIC_CONTRACT_INFO_API_HOST=https://contracts-info.services.blockscout.com
NEXT_PUBLIC_FEATURED_NETWORKS=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/featured-networks/blackfort-testnet.json
NEXT_PUBLIC_FOOTER_LINKS=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/footer-links/blackfort.json
NEXT_PUBLIC_GRAPHIQL_TRANSACTION=0xcb4140e22cde3412eb5aecdedf2403032c7a251f5c96b11122aca5b1b88ed953
NEXT_PUBLIC_HOMEPAGE_CHARTS=['daily_txs']
NEXT_PUBLIC_HOMEPAGE_PLATE_BACKGROUND=linear-gradient(92deg, rgb(3, 150, 254) 0.24%, rgb(36, 209, 245) 98.31%)
NEXT_PUBLIC_IS_TESTNET=true
NEXT_PUBLIC_MARKETPLACE_ENABLED=false
NEXT_PUBLIC_METADATA_SERVICE_API_HOST=https://metadata.services.blockscout.com
NEXT_PUBLIC_NETWORK_CURRENCY_DECIMALS=18
NEXT_PUBLIC_NETWORK_CURRENCY_NAME=TBXN
NEXT_PUBLIC_NETWORK_CURRENCY_SYMBOL=TBXN
NEXT_PUBLIC_NETWORK_ICON=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/network-icons/blackfort.svg
NEXT_PUBLIC_NETWORK_ICON_DARK=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/network-icons/blackfort-dark.svg
NEXT_PUBLIC_NETWORK_ID=4777
NEXT_PUBLIC_NETWORK_LOGO=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/network-logos/blackfort.svg
NEXT_PUBLIC_NETWORK_LOGO_DARK=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/network-logos/blackfort-dark.svg
NEXT_PUBLIC_NETWORK_NAME=BXN Testnet
NEXT_PUBLIC_NETWORK_RPC_URL=https://testnet.blackfort.network/rpc
NEXT_PUBLIC_NETWORK_SHORT_NAME=BXN Testnet
NEXT_PUBLIC_OG_ENHANCED_DATA_ENABLED=true
NEXT_PUBLIC_OG_IMAGE_URL=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/og-images/blackfort.png
NEXT_PUBLIC_STATS_API_HOST=https://stats-blackfort-testnet.k8s.blockscout.com
NEXT_PUBLIC_TRANSACTION_INTERPRETATION_PROVIDER=blockscout
NEXT_PUBLIC_VIEWS_CONTRACT_SOLIDITYSCAN_ENABLED=true
NEXT_PUBLIC_VISUALIZE_API_HOST=https://visualizer.services.blockscout.com
NEXT_PUBLIC_VALIDATORS_CHAIN_TYPE=blackfort
\ No newline at end of file
......@@ -679,7 +679,7 @@ The feature enables the Validators page which provides detailed information abou
| Variable | Type| Description | Compulsoriness | Default value | Example value | Version |
| --- | --- | --- | --- | --- | --- | --- |
| NEXT_PUBLIC_VALIDATORS_CHAIN_TYPE | `'stability'` | Chain type | Required | - | `'stability'` | v1.25.0+ |
| NEXT_PUBLIC_VALIDATORS_CHAIN_TYPE | `'stability' \| 'blackfort'` | Chain type | Required | - | `'stability'` | v1.25.0+ |
 
......
......@@ -118,7 +118,15 @@ import type { TxInterpretationResponse } from 'types/api/txInterpretation';
import type { TTxsFilters, TTxsWithBlobsFilters } from 'types/api/txsFilters';
import type { TxStateChanges } from 'types/api/txStateChanges';
import type { UserOpsResponse, UserOp, UserOpsFilters, UserOpsAccount } from 'types/api/userOps';
import type { ValidatorsCountersResponse, ValidatorsFilters, ValidatorsResponse, ValidatorsSorting } from 'types/api/validators';
import type {
ValidatorsStabilityCountersResponse,
ValidatorsStabilityFilters,
ValidatorsStabilityResponse,
ValidatorsStabilitySorting,
ValidatorsBlackfortCountersResponse,
ValidatorsBlackfortResponse,
ValidatorsBlackfortSorting,
} from 'types/api/validators';
import type { VerifiedContractsSorting } from 'types/api/verifiedContracts';
import type { WithdrawalsResponse, WithdrawalsCounters } from 'types/api/withdrawals';
import type {
......@@ -897,14 +905,19 @@ export const RESOURCES = {
},
// VALIDATORS
validators: {
path: '/api/v2/validators/:chainType',
pathParams: [ 'chainType' as const ],
validators_stability: {
path: '/api/v2/validators/stability',
filterFields: [ 'address_hash' as const, 'state_filter' as const ],
},
validators_counters: {
path: '/api/v2/validators/:chainType/counters',
pathParams: [ 'chainType' as const ],
validators_stability_counters: {
path: '/api/v2/validators/stability/counters',
},
validators_blackfort: {
path: '/api/v2/validators/blackfort',
filterFields: [],
},
validators_blackfort_counters: {
path: '/api/v2/validators/blackfort/counters',
},
// BLOBS
......@@ -1002,7 +1015,7 @@ export type PaginatedResources = 'blocks' | 'block_txs' | 'block_election_reward
'zksync_l2_txn_batches' | 'zksync_l2_txn_batch_txs' |
'withdrawals' | 'address_withdrawals' | 'block_withdrawals' |
'watchlist' | 'private_tags_address' | 'private_tags_tx' |
'domains_lookup' | 'addresses_lookup' | 'user_ops' | 'validators' | 'noves_address_history';
'domains_lookup' | 'addresses_lookup' | 'user_ops' | 'validators_stability' | 'validators_blackfort' | 'noves_address_history';
export type PaginatedResponse<Q extends PaginatedResources> = ResourcePayload<Q>;
......@@ -1123,8 +1136,10 @@ Q extends 'address_metadata_tag_types' ? PublicTagTypesResponse :
Q extends 'blob' ? Blob :
Q extends 'marketplace_dapps' ? Array<MarketplaceAppOverview> :
Q extends 'marketplace_dapp' ? MarketplaceAppOverview :
Q extends 'validators' ? ValidatorsResponse :
Q extends 'validators_counters' ? ValidatorsCountersResponse :
Q extends 'validators_stability' ? ValidatorsStabilityResponse :
Q extends 'validators_stability_counters' ? ValidatorsStabilityCountersResponse :
Q extends 'validators_blackfort' ? ValidatorsBlackfortResponse :
Q extends 'validators_blackfort_counters' ? ValidatorsBlackfortCountersResponse :
Q extends 'shibarium_withdrawals' ? ShibariumWithdrawalsResponse :
Q extends 'shibarium_deposits' ? ShibariumDepositsResponse :
Q extends 'shibarium_withdrawals_count' ? number :
......@@ -1200,7 +1215,7 @@ Q extends 'verified_contracts' ? VerifiedContractsFilters :
Q extends 'addresses_lookup' ? EnsAddressLookupFilters :
Q extends 'domains_lookup' ? EnsDomainLookupFilters :
Q extends 'user_ops' ? UserOpsFilters :
Q extends 'validators' ? ValidatorsFilters :
Q extends 'validators_stability' ? ValidatorsStabilityFilters :
Q extends 'address_mud_tables' ? AddressMudTablesFilter :
Q extends 'address_mud_records' ? AddressMudRecordsFilter :
never;
......@@ -1214,7 +1229,8 @@ Q extends 'verified_contracts' ? VerifiedContractsSorting :
Q extends 'address_txs' ? TransactionsSorting :
Q extends 'addresses_lookup' ? EnsLookupSorting :
Q extends 'domains_lookup' ? EnsLookupSorting :
Q extends 'validators' ? ValidatorsSorting :
Q extends 'validators_stability' ? ValidatorsStabilitySorting :
Q extends 'validators_blackfort' ? ValidatorsBlackfortSorting :
Q extends 'address_mud_records' ? AddressMudRecordsSorting :
never;
/* eslint-enable @typescript-eslint/indent */
import type {
ValidatorBlackfort,
ValidatorsBlackfortCountersResponse,
ValidatorsBlackfortResponse,
} from 'types/api/validators';
import * as addressMock from '../address/address';
export const validator1: ValidatorBlackfort = {
address: addressMock.withName,
name: 'testnet-3',
commission: 10,
delegated_amount: '0',
self_bonded_amount: '10000',
};
export const validator2: ValidatorBlackfort = {
address: addressMock.withEns,
name: 'GooseGanG GooseGanG GooseGanG GooseGanG GooseGanG GooseGanG GooseGanG',
commission: 5000,
delegated_amount: '10000',
self_bonded_amount: '100',
};
export const validator3: ValidatorBlackfort = {
address: addressMock.withoutName,
name: 'testnet-1',
commission: 0,
delegated_amount: '0',
self_bonded_amount: '10000',
};
export const validatorsResponse: ValidatorsBlackfortResponse = {
items: [ validator1, validator2, validator3 ],
next_page_params: null,
};
export const validatorsCountersResponse: ValidatorsBlackfortCountersResponse = {
new_validators_counter_24h: '11',
validators_counter: '140',
};
import type { Validator, ValidatorsCountersResponse, ValidatorsResponse } from 'types/api/validators';
import type {
ValidatorStability,
ValidatorsStabilityCountersResponse,
ValidatorsStabilityResponse,
} from 'types/api/validators';
import * as addressMock from '../address/address';
export const validator1: Validator = {
export const validator1: ValidatorStability = {
address: addressMock.withName,
blocks_validated_count: 7334224,
state: 'active',
};
export const validator2: Validator = {
export const validator2: ValidatorStability = {
address: addressMock.withEns,
blocks_validated_count: 8937453,
state: 'probation',
};
export const validator3: Validator = {
export const validator3: ValidatorStability = {
address: addressMock.withoutName,
blocks_validated_count: 1234,
state: 'inactive',
};
export const validatorsResponse: ValidatorsResponse = {
export const validatorsResponse: ValidatorsStabilityResponse = {
items: [ validator1, validator2, validator3 ],
next_page_params: null,
};
export const validatorsCountersResponse: ValidatorsCountersResponse = {
export const validatorsCountersResponse: ValidatorsStabilityCountersResponse = {
active_validators_counter: '42',
active_validators_percentage: 7.14,
new_validators_counter_24h: '11',
......
......@@ -4,7 +4,21 @@ import React from 'react';
import PageNextJs from 'nextjs/PageNextJs';
const Validators = dynamic(() => import('ui/pages/Validators'), { ssr: false });
import config from 'configs/app';
const validatorsFeature = config.features.validators;
const Validators = dynamic(() => {
if (validatorsFeature.isEnabled && validatorsFeature.chainType === 'stability') {
return import('ui/pages/ValidatorsStability');
}
if (validatorsFeature.isEnabled && validatorsFeature.chainType === 'blackfort') {
return import('ui/pages/ValidatorsBlackfort');
}
throw new Error('Validators feature is not enabled.');
}, { ssr: false });
const Page: NextPage = () => {
return (
......
import type { Validator, ValidatorsCountersResponse } from 'types/api/validators';
import type {
ValidatorStability,
ValidatorsStabilityCountersResponse,
ValidatorBlackfort,
ValidatorsBlackfortCountersResponse,
} from 'types/api/validators';
import { ADDRESS_PARAMS } from './addressParams';
export const VALIDATOR: Validator = {
export const VALIDATOR_STABILITY: ValidatorStability = {
address: ADDRESS_PARAMS,
blocks_validated_count: 25987,
state: 'active',
};
export const VALIDATORS_COUNTERS: ValidatorsCountersResponse = {
export const VALIDATORS_STABILITY_COUNTERS: ValidatorsStabilityCountersResponse = {
active_validators_counter: '42',
active_validators_percentage: 7.14,
new_validators_counter_24h: '11',
validators_counter: '140',
};
export const VALIDATOR_BLACKFORT: ValidatorBlackfort = {
address: ADDRESS_PARAMS,
name: 'testnet-1',
commission: 10,
delegated_amount: '0',
self_bonded_amount: '10000',
};
export const VALIDATORS_BLACKFORT_COUNTERS: ValidatorsBlackfortCountersResponse = {
new_validators_counter_24h: '11',
validators_counter: '140',
};
......@@ -5,6 +5,7 @@ import path from 'path';
const PRESETS = {
arbitrum: 'https://arbitrum.blockscout.com',
base: 'https://base.blockscout.com',
blackfort_testnet: 'https://blackfort-testnet.blockscout.com',
celo_alfajores: 'https://celo-alfajores.blockscout.com',
eth: 'https://eth.blockscout.com',
eth_goerli: 'https://eth-goerli.blockscout.com',
......
import type { AddressParam } from './addressParams';
export interface Validator {
export interface ValidatorStability {
address: AddressParam;
blocks_validated_count: number;
state: 'active' | 'probation' | 'inactive';
}
export interface ValidatorsResponse {
items: Array<Validator>;
export interface ValidatorsStabilityResponse {
items: Array<ValidatorStability>;
next_page_params: {
'address_hash': string;
'blocks_validated': string;
'items_count': string;
'state': Validator['state'];
'state': ValidatorStability['state'];
} | null;
}
export interface ValidatorsCountersResponse {
export interface ValidatorsStabilityCountersResponse {
active_validators_counter: string;
active_validators_percentage: number;
new_validators_counter_24h: string;
validators_counter: string;
}
export interface ValidatorsFilters {
export interface ValidatorsStabilityFilters {
// address_hash: string | undefined; // right now API doesn't support filtering by address_hash
state_filter: Validator['state'] | undefined;
state_filter: ValidatorStability['state'] | undefined;
}
export interface ValidatorsSorting {
export interface ValidatorsStabilitySorting {
sort: 'state' | 'blocks_validated';
order: 'asc' | 'desc';
}
export type ValidatorsSortingField = ValidatorsSorting['sort'];
export type ValidatorsStabilitySortingField = ValidatorsStabilitySorting['sort'];
export type ValidatorsSortingValue = `${ ValidatorsSortingField }-${ ValidatorsSorting['order'] }`;
export type ValidatorsStabilitySortingValue = `${ ValidatorsStabilitySortingField }-${ ValidatorsStabilitySorting['order'] }`;
export interface ValidatorBlackfort {
address: AddressParam;
name: string;
commission: number;
delegated_amount: string;
self_bonded_amount: string;
}
export interface ValidatorsBlackfortResponse {
items: Array<ValidatorBlackfort>;
next_page_params: {
'address_hash': string;
} | null;
}
export interface ValidatorsBlackfortCountersResponse {
new_validators_counter_24h: string;
validators_counter: string;
}
export interface ValidatorsBlackfortSorting {
sort: 'address_hash';
order: 'asc' | 'desc';
}
export type ValidatorsBlackfortSortingField = ValidatorsBlackfortSorting['sort'];
export type ValidatorsBlackfortSortingValue = `${ ValidatorsBlackfortSortingField }-${ ValidatorsBlackfortSorting['order'] }`;
......@@ -2,6 +2,7 @@ import type { ArrayElement } from 'types/utils';
export const VALIDATORS_CHAIN_TYPE = [
'stability',
'blackfort',
] as const;
export type ValidatorsChainType = ArrayElement<typeof VALIDATORS_CHAIN_TYPE>;
import React from 'react';
import * as validatorsMock from 'mocks/validators/blackfort';
import { test, expect } from 'playwright/lib';
import Validators from './ValidatorsBlackfort';
const chainType = 'blackfort';
test('base view +@mobile', async({ render, mockApiResponse, mockEnvs, mockTextAd }) => {
await mockEnvs([
[ 'NEXT_PUBLIC_VALIDATORS_CHAIN_TYPE', chainType ],
]);
await mockApiResponse('validators_blackfort', validatorsMock.validatorsResponse);
await mockApiResponse('validators_blackfort_counters', validatorsMock.validatorsCountersResponse);
await mockTextAd();
const component = await render(<Validators/>);
await expect(component).toHaveScreenshot();
});
import { Box, Hide, HStack, Show } from '@chakra-ui/react';
import { useRouter } from 'next/router';
import React from 'react';
import type {
ValidatorsBlackfortSorting,
ValidatorsBlackfortSortingField,
ValidatorsBlackfortSortingValue,
} from 'types/api/validators';
import config from 'configs/app';
import { generateListStub } from 'stubs/utils';
import { VALIDATOR_BLACKFORT } from 'stubs/validators';
import ActionBar, { ACTION_BAR_HEIGHT_DESKTOP } from 'ui/shared/ActionBar';
import DataListDisplay from 'ui/shared/DataListDisplay';
import PageTitle from 'ui/shared/Page/PageTitle';
import Pagination from 'ui/shared/pagination/Pagination';
import useQueryWithPages from 'ui/shared/pagination/useQueryWithPages';
import getSortParamsFromValue from 'ui/shared/sort/getSortParamsFromValue';
import getSortValueFromQuery from 'ui/shared/sort/getSortValueFromQuery';
import Sort from 'ui/shared/sort/Sort';
import { VALIDATORS_BLACKFORT_SORT_OPTIONS } from 'ui/validatorsBlackfort/utils';
import ValidatorsCounters from 'ui/validatorsBlackfort/ValidatorsCounters';
import ValidatorsList from 'ui/validatorsBlackfort/ValidatorsList';
import ValidatorsTable from 'ui/validatorsBlackfort/ValidatorsTable';
const ValidatorsBlackfort = () => {
const router = useRouter();
const [ sort, setSort ] =
React.useState<ValidatorsBlackfortSortingValue | undefined>(
getSortValueFromQuery<ValidatorsBlackfortSortingValue>(router.query, VALIDATORS_BLACKFORT_SORT_OPTIONS),
);
const { isError, isPlaceholderData, data, pagination, onSortingChange } = useQueryWithPages({
resourceName: 'validators_blackfort',
sorting: getSortParamsFromValue<ValidatorsBlackfortSortingValue, ValidatorsBlackfortSortingField, ValidatorsBlackfortSorting['order']>(sort),
options: {
enabled: config.features.validators.isEnabled,
placeholderData: generateListStub<'validators_blackfort'>(
VALIDATOR_BLACKFORT,
50,
{ next_page_params: null },
),
},
});
const handleSortChange = React.useCallback((value?: ValidatorsBlackfortSortingValue) => {
setSort(value);
onSortingChange(getSortParamsFromValue(value));
}, [ onSortingChange ]);
const sortButton = (
<Sort
name="validators_sorting"
defaultValue={ sort }
options={ VALIDATORS_BLACKFORT_SORT_OPTIONS }
onChange={ handleSortChange }
/>
);
const actionBar = (
<>
<HStack spacing={ 3 } mb={ 6 } display={{ base: 'flex', lg: 'none' }}>
{ sortButton }
</HStack>
{ pagination.isVisible && (
<ActionBar mt={ -6 }>
<Pagination ml="auto" { ...pagination }/>
</ActionBar>
) }
</>
);
const content = data?.items ? (
<>
<Show below="lg" ssr={ false }>
<ValidatorsList data={ data.items } isLoading={ isPlaceholderData }/>
</Show>
<Hide below="lg" ssr={ false }>
<ValidatorsTable
data={ data.items }
sort={ sort }
setSorting={ handleSortChange }
isLoading={ isPlaceholderData }
top={ pagination.isVisible ? ACTION_BAR_HEIGHT_DESKTOP : 0 }
/>
</Hide>
</>
) : null;
return (
<Box>
<PageTitle title="Validators" withTextAd/>
<ValidatorsCounters/>
<DataListDisplay
isError={ isError }
items={ data?.items }
emptyText="There are no validators."
content={ content }
actionBar={ actionBar }
/>
</Box>
);
};
export default ValidatorsBlackfort;
import React from 'react';
import * as validatorsMock from 'mocks/validators/index';
import * as validatorsMock from 'mocks/validators/stability';
import { test, expect } from 'playwright/lib';
import Validators from './Validators';
import Validators from './ValidatorsStability';
const chainType = 'stability';
......@@ -11,8 +11,8 @@ test('base view +@mobile', async({ render, mockApiResponse, mockEnvs, mockTextAd
await mockEnvs([
[ 'NEXT_PUBLIC_VALIDATORS_CHAIN_TYPE', chainType ],
]);
await mockApiResponse('validators', validatorsMock.validatorsResponse, { pathParams: { chainType } });
await mockApiResponse('validators_counters', validatorsMock.validatorsCountersResponse, { pathParams: { chainType } });
await mockApiResponse('validators_stability', validatorsMock.validatorsResponse);
await mockApiResponse('validators_stability_counters', validatorsMock.validatorsCountersResponse);
await mockTextAd();
const component = await render(<Validators/>);
......
......@@ -2,8 +2,12 @@ import { Box, Hide, HStack, Show } from '@chakra-ui/react';
import { useRouter } from 'next/router';
import React from 'react';
import { getFeaturePayload } from 'configs/app/features/types';
import type { ValidatorsFilters, ValidatorsSorting, ValidatorsSortingField, ValidatorsSortingValue } from 'types/api/validators';
import type {
ValidatorsStabilityFilters,
ValidatorsStabilitySorting,
ValidatorsStabilitySortingField,
ValidatorsStabilitySortingValue,
} from 'types/api/validators';
import config from 'configs/app';
// import useDebounce from 'lib/hooks/useDebounce';
......@@ -11,7 +15,7 @@ import useIsMobile from 'lib/hooks/useIsMobile';
import { apos } from 'lib/html-entities';
import getQueryParamString from 'lib/router/getQueryParamString';
import { generateListStub } from 'stubs/utils';
import { VALIDATOR } from 'stubs/validators';
import { VALIDATOR_STABILITY } from 'stubs/validators';
import ActionBar from 'ui/shared/ActionBar';
import DataListDisplay from 'ui/shared/DataListDisplay';
// import FilterInput from 'ui/shared/filters/FilterInput';
......@@ -21,35 +25,36 @@ import useQueryWithPages from 'ui/shared/pagination/useQueryWithPages';
import getSortParamsFromValue from 'ui/shared/sort/getSortParamsFromValue';
import getSortValueFromQuery from 'ui/shared/sort/getSortValueFromQuery';
import Sort from 'ui/shared/sort/Sort';
import { SORT_OPTIONS } from 'ui/validators/utils';
import ValidatorsCounters from 'ui/validators/ValidatorsCounters';
import ValidatorsFilter from 'ui/validators/ValidatorsFilter';
import ValidatorsList from 'ui/validators/ValidatorsList';
import ValidatorsTable from 'ui/validators/ValidatorsTable';
import { VALIDATORS_STABILITY_SORT_OPTIONS } from 'ui/validatorsStability/utils';
import ValidatorsCounters from 'ui/validatorsStability/ValidatorsCounters';
import ValidatorsFilter from 'ui/validatorsStability/ValidatorsFilter';
import ValidatorsList from 'ui/validatorsStability/ValidatorsList';
import ValidatorsTable from 'ui/validatorsStability/ValidatorsTable';
const Validators = () => {
const ValidatorsStability = () => {
const router = useRouter();
// const [ searchTerm, setSearchTerm ] = React.useState(getQueryParamString(router.query.address_hash) || undefined);
const [ statusFilter, setStatusFilter ] = React.useState(getQueryParamString(router.query.state_filter) as ValidatorsFilters['state_filter'] || undefined);
const [ sort, setSort ] =
React.useState<ValidatorsSortingValue | undefined>(getSortValueFromQuery<ValidatorsSortingValue>(router.query, SORT_OPTIONS));
const [ statusFilter, setStatusFilter ] =
React.useState(getQueryParamString(router.query.state_filter) as ValidatorsStabilityFilters['state_filter'] || undefined);
const [ sort, setSort ] = React.useState<ValidatorsStabilitySortingValue | undefined>(
getSortValueFromQuery<ValidatorsStabilitySortingValue>(router.query, VALIDATORS_STABILITY_SORT_OPTIONS),
);
// const debouncedSearchTerm = useDebounce(searchTerm || '', 300);
const isMobile = useIsMobile();
const { isError, isPlaceholderData, data, pagination, onFilterChange, onSortingChange } = useQueryWithPages({
resourceName: 'validators',
pathParams: { chainType: getFeaturePayload(config.features.validators)?.chainType },
resourceName: 'validators_stability',
filters: {
// address_hash: debouncedSearchTerm,
state_filter: statusFilter,
},
sorting: getSortParamsFromValue<ValidatorsSortingValue, ValidatorsSortingField, ValidatorsSorting['order']>(sort),
sorting: getSortParamsFromValue<ValidatorsStabilitySortingValue, ValidatorsStabilitySortingField, ValidatorsStabilitySorting['order']>(sort),
options: {
enabled: config.features.validators.isEnabled,
placeholderData: generateListStub<'validators'>(
VALIDATOR,
placeholderData: generateListStub<'validators_stability'>(
VALIDATOR_STABILITY,
50,
{ next_page_params: null },
),
......@@ -69,7 +74,7 @@ const Validators = () => {
return;
}
const state = value === 'all' ? undefined : value as ValidatorsFilters['state_filter'];
const state = value === 'all' ? undefined : value as ValidatorsStabilityFilters['state_filter'];
onFilterChange({
// address_hash: debouncedSearchTerm,
......@@ -78,12 +83,13 @@ const Validators = () => {
setStatusFilter(state);
}, [ onFilterChange ]);
const handleSortChange = React.useCallback((value?: ValidatorsSortingValue) => {
const handleSortChange = React.useCallback((value?: ValidatorsStabilitySortingValue) => {
setSort(value);
onSortingChange(getSortParamsFromValue(value));
}, [ onSortingChange ]);
const filterMenu = <ValidatorsFilter onChange={ handleStateFilterChange } defaultValue={ statusFilter } hasActiveFilter={ Boolean(statusFilter) }/>;
const filterMenu =
<ValidatorsFilter onChange={ handleStateFilterChange } defaultValue={ statusFilter } hasActiveFilter={ Boolean(statusFilter) }/>;
// const filterInput = (
// <FilterInput
......@@ -99,7 +105,7 @@ const Validators = () => {
<Sort
name="validators_sorting"
defaultValue={ sort }
options={ SORT_OPTIONS }
options={ VALIDATORS_STABILITY_SORT_OPTIONS }
onChange={ handleSortChange }
/>
);
......@@ -141,7 +147,7 @@ const Validators = () => {
<DataListDisplay
isError={ isError }
items={ data?.items }
emptyText="There are no verified contracts."
emptyText="There are no validators."
filterProps={{
emptyFilteredText: `Couldn${ apos }t find any validator that matches your query.`,
hasActiveFilters: Boolean(
......@@ -156,4 +162,4 @@ const Validators = () => {
);
};
export default Validators;
export default ValidatorsStability;
import React from 'react';
import type { Validator } from 'types/api/validators';
import type { ValidatorStability } from 'types/api/validators';
import StatusTag from './StatusTag';
interface Props {
state: Validator['state'];
state: ValidatorStability['state'];
isLoading?: boolean;
}
const ValidatorStatus = ({ state, isLoading }: Props) => {
const ValidatorStabilityStatus = ({ state, isLoading }: Props) => {
switch (state) {
case 'active':
return <StatusTag type="ok" text="Active" isLoading={ isLoading }/>;
......@@ -20,4 +20,4 @@ const ValidatorStatus = ({ state, isLoading }: Props) => {
}
};
export default React.memo(ValidatorStatus);
export default React.memo(ValidatorStabilityStatus);
import { Box } from '@chakra-ui/react';
import React from 'react';
import config from 'configs/app';
import useApiQuery from 'lib/api/useApiQuery';
import { VALIDATORS_BLACKFORT_COUNTERS } from 'stubs/validators';
import StatsWidget from 'ui/shared/stats/StatsWidget';
const ValidatorsCounters = () => {
const countersQuery = useApiQuery('validators_blackfort_counters', {
queryOptions: {
enabled: config.features.validators.isEnabled,
placeholderData: VALIDATORS_BLACKFORT_COUNTERS,
},
});
if (!countersQuery.data) {
return null;
}
return (
<Box columnGap={ 3 } rowGap={ 3 } mb={ 6 } display="grid" gridTemplateColumns={{ base: '1fr', lg: 'repeat(2, 1fr)' }}>
<StatsWidget
label="Total validators"
value={ Number(countersQuery.data.validators_counter).toLocaleString() }
diff={ Number(countersQuery.data.new_validators_counter_24h).toLocaleString() }
isLoading={ countersQuery.isPlaceholderData }
/>
</Box>
);
};
export default React.memo(ValidatorsCounters);
import { Box } from '@chakra-ui/react';
import React from 'react';
import type { Validator } from 'types/api/validators';
import type { ValidatorBlackfort } from 'types/api/validators';
import ValidatorsListItem from './ValidatorsListItem';
const ValidatorsList = ({ data, isLoading }: { data: Array<Validator>; isLoading: boolean }) => {
const ValidatorsList = ({ data, isLoading }: { data: Array<ValidatorBlackfort>; isLoading: boolean }) => {
return (
<Box>
{ data.map((item, index) => (
......
import { Flex, Skeleton } from '@chakra-ui/react';
import BigNumber from 'bignumber.js';
import React from 'react';
import type { ValidatorBlackfort } from 'types/api/validators';
import config from 'configs/app';
import { currencyUnits } from 'lib/units';
import AddressEntity from 'ui/shared/entities/address/AddressEntity';
import ListItemMobileGrid from 'ui/shared/ListItemMobile/ListItemMobileGrid';
import TruncatedValue from 'ui/shared/TruncatedValue';
interface Props {
data: ValidatorBlackfort;
isLoading?: boolean;
}
const ValidatorsListItem = ({ data, isLoading }: Props) => {
return (
<ListItemMobileGrid.Container gridTemplateColumns="130px auto">
<ListItemMobileGrid.Label isLoading={ isLoading }>Address</ListItemMobileGrid.Label>
<ListItemMobileGrid.Value>
<AddressEntity
isLoading={ isLoading }
address={ data.address }
truncation="constant"
/>
</ListItemMobileGrid.Value>
<ListItemMobileGrid.Label isLoading={ isLoading }>Name</ListItemMobileGrid.Label>
<ListItemMobileGrid.Value>
<Flex><TruncatedValue value={ data.name } isLoading={ isLoading }/></Flex>
</ListItemMobileGrid.Value>
<ListItemMobileGrid.Label isLoading={ isLoading }>Commission</ListItemMobileGrid.Label>
<ListItemMobileGrid.Value>
<Skeleton isLoaded={ !isLoading } display="inline-block">
{ `${ data.commission / 100 }%` }
</Skeleton>
</ListItemMobileGrid.Value>
<ListItemMobileGrid.Label isLoading={ isLoading }>Self bonded</ListItemMobileGrid.Label>
<ListItemMobileGrid.Value>
<Skeleton isLoaded={ !isLoading } display="inline-block">
{ `${ BigNumber(data.self_bonded_amount).div(BigNumber(10 ** config.chain.currency.decimals)).toFormat() } ${ currencyUnits.ether }` }
</Skeleton>
</ListItemMobileGrid.Value>
<ListItemMobileGrid.Label isLoading={ isLoading }>Delegated amount</ListItemMobileGrid.Label>
<ListItemMobileGrid.Value>
<Skeleton isLoaded={ !isLoading } display="inline-block">
{ `${ BigNumber(data.delegated_amount).div(BigNumber(10 ** config.chain.currency.decimals)).toFormat() } ${ currencyUnits.ether }` }
</Skeleton>
</ListItemMobileGrid.Value>
</ListItemMobileGrid.Container>
);
};
export default React.memo(ValidatorsListItem);
import { Table, Tbody, Tr, Th, Link } from '@chakra-ui/react';
import React from 'react';
import type {
ValidatorBlackfort,
ValidatorsBlackfortSorting,
ValidatorsBlackfortSortingField,
ValidatorsBlackfortSortingValue,
} from 'types/api/validators';
import { currencyUnits } from 'lib/units';
import IconSvg from 'ui/shared/IconSvg';
import getNextSortValue from 'ui/shared/sort/getNextSortValue';
import { default as Thead } from 'ui/shared/TheadSticky';
import { VALIDATORS_BLACKFORT_SORT_SEQUENCE } from './utils';
import ValidatorsTableItem from './ValidatorsTableItem';
interface Props {
data: Array<ValidatorBlackfort>;
sort: ValidatorsBlackfortSortingValue | undefined;
setSorting: (val: ValidatorsBlackfortSortingValue | undefined) => void;
isLoading?: boolean;
top: number;
}
const ValidatorsTable = ({ data, sort, setSorting, isLoading, top }: Props) => {
const sortIconTransform = sort?.includes('asc' as ValidatorsBlackfortSorting['order']) ? 'rotate(-90deg)' : 'rotate(90deg)';
const onSortToggle = React.useCallback((field: ValidatorsBlackfortSortingField) => () => {
const value = getNextSortValue<ValidatorsBlackfortSortingField, ValidatorsBlackfortSortingValue>(VALIDATORS_BLACKFORT_SORT_SEQUENCE, field)(sort);
setSorting(value);
}, [ sort, setSorting ]);
return (
<Table variant="simple" size="sm">
<Thead top={ top }>
<Tr>
<Th>
<Link
display="flex"
alignItems="center"
onClick={ isLoading ? undefined : onSortToggle('address_hash') }
columnGap={ 1 }
>
{ sort?.includes('address') && <IconSvg name="arrows/east" boxSize={ 4 } transform={ sortIconTransform }/> }
Validator’s address
</Link>
</Th>
<Th>Name</Th>
<Th isNumeric>Commission</Th>
<Th isNumeric>{ `Self bonded ${ currencyUnits.ether }` }</Th>
<Th isNumeric>{ `Delegated amount ${ currencyUnits.ether }` }</Th>
</Tr>
</Thead>
<Tbody>
{ data.map((item, index) => (
<ValidatorsTableItem
key={ item.address.hash + (isLoading ? index : '') }
data={ item }
isLoading={ isLoading }/>
)) }
</Tbody>
</Table>
);
};
export default React.memo(ValidatorsTable);
import { Tr, Td, Skeleton, Flex } from '@chakra-ui/react';
import BigNumber from 'bignumber.js';
import React from 'react';
import type { ValidatorBlackfort } from 'types/api/validators';
import config from 'configs/app';
import AddressEntity from 'ui/shared/entities/address/AddressEntity';
import TruncatedValue from 'ui/shared/TruncatedValue';
interface Props {
data: ValidatorBlackfort;
isLoading?: boolean;
}
const ValidatorsTableItem = ({ data, isLoading }: Props) => {
return (
<Tr>
<Td verticalAlign="middle">
<AddressEntity
address={ data.address }
isLoading={ isLoading }
truncation="constant"
/>
</Td>
<Td verticalAlign="middle">
<Flex>
<TruncatedValue value={ data.name } isLoading={ isLoading }/>
</Flex>
</Td>
<Td verticalAlign="middle" isNumeric>
<Skeleton isLoaded={ !isLoading } display="inline-block">
{ `${ data.commission / 100 }%` }
</Skeleton>
</Td>
<Td verticalAlign="middle" isNumeric>
<Skeleton isLoaded={ !isLoading } display="inline-block">
{ BigNumber(data.self_bonded_amount).div(BigNumber(10 ** config.chain.currency.decimals)).toFormat() }
</Skeleton>
</Td>
<Td verticalAlign="middle" isNumeric>
<Skeleton isLoaded={ !isLoading } display="inline-block">
{ BigNumber(data.delegated_amount).div(BigNumber(10 ** config.chain.currency.decimals)).toFormat() }
</Skeleton>
</Td>
</Tr>
);
};
export default React.memo(ValidatorsTableItem);
import type {
ValidatorsBlackfortSortingValue,
ValidatorsBlackfortSortingField,
} from 'types/api/validators';
import type { TOption } from 'ui/shared/sort/Option';
export const VALIDATORS_BLACKFORT_SORT_OPTIONS: Array<TOption<ValidatorsBlackfortSortingValue>> = [
{ title: 'Default', id: undefined },
{ title: 'Address descending', id: 'address_hash-desc' },
{ title: 'Address ascending', id: 'address_hash-asc' },
];
export const VALIDATORS_BLACKFORT_SORT_SEQUENCE: Record<ValidatorsBlackfortSortingField, Array<ValidatorsBlackfortSortingValue | undefined>> = {
address_hash: [ 'address_hash-desc', 'address_hash-asc', undefined ],
};
import { Box } from '@chakra-ui/react';
import React from 'react';
import { getFeaturePayload } from 'configs/app/features/types';
import config from 'configs/app';
import useApiQuery from 'lib/api/useApiQuery';
import { VALIDATORS_COUNTERS } from 'stubs/validators';
import { VALIDATORS_STABILITY_COUNTERS } from 'stubs/validators';
import StatsWidget from 'ui/shared/stats/StatsWidget';
const ValidatorsCounters = () => {
const countersQuery = useApiQuery('validators_counters', {
pathParams: { chainType: getFeaturePayload(config.features.validators)?.chainType },
const countersQuery = useApiQuery('validators_stability_counters', {
queryOptions: {
enabled: config.features.validators.isEnabled,
placeholderData: VALIDATORS_COUNTERS,
placeholderData: VALIDATORS_STABILITY_COUNTERS,
},
});
......
import React from 'react';
import type { ValidatorsFilters } from 'types/api/validators';
import type { ValidatorsStabilityFilters } from 'types/api/validators';
import PopoverFilterRadio from 'ui/shared/filters/PopoverFilterRadio';
......@@ -13,7 +13,7 @@ const OPTIONS = [
interface Props {
hasActiveFilter: boolean;
defaultValue: ValidatorsFilters['state_filter'] | undefined;
defaultValue: ValidatorsStabilityFilters['state_filter'] | undefined;
onChange: (nextValue: string | Array<string>) => void;
}
......
import { Box } from '@chakra-ui/react';
import React from 'react';
import type { ValidatorStability } from 'types/api/validators';
import ValidatorsListItem from './ValidatorsListItem';
const ValidatorsList = ({ data, isLoading }: { data: Array<ValidatorStability>; isLoading: boolean }) => {
return (
<Box>
{ data.map((item, index) => (
<ValidatorsListItem
key={ item.address.hash + (isLoading ? index : '') }
data={ item }
isLoading={ isLoading }
/>
)) }
</Box>
);
};
export default React.memo(ValidatorsList);
import { Skeleton } from '@chakra-ui/react';
import React from 'react';
import type { Validator } from 'types/api/validators';
import type { ValidatorStability } from 'types/api/validators';
import AddressEntity from 'ui/shared/entities/address/AddressEntity';
import ListItemMobileGrid from 'ui/shared/ListItemMobile/ListItemMobileGrid';
import ValidatorStatus from 'ui/shared/statusTag/ValidatorStatus';
import ValidatorStatus from 'ui/shared/statusTag/ValidatorStabilityStatus';
interface Props {
data: Validator;
data: ValidatorStability;
isLoading?: boolean;
}
......
import { Table, Tbody, Tr, Th, Link } from '@chakra-ui/react';
import React from 'react';
import type { Validator, ValidatorsSorting, ValidatorsSortingField, ValidatorsSortingValue } from 'types/api/validators';
import type {
ValidatorStability,
ValidatorsStabilitySorting,
ValidatorsStabilitySortingField,
ValidatorsStabilitySortingValue,
} from 'types/api/validators';
import { ACTION_BAR_HEIGHT_DESKTOP } from 'ui/shared/ActionBar';
import IconSvg from 'ui/shared/IconSvg';
import getNextSortValue from 'ui/shared/sort/getNextSortValue';
import { default as Thead } from 'ui/shared/TheadSticky';
import { SORT_SEQUENCE } from './utils';
import { VALIDATORS_STABILITY_SORT_SEQUENCE } from './utils';
import ValidatorsTableItem from './ValidatorsTableItem';
interface Props {
data: Array<Validator>;
sort: ValidatorsSortingValue | undefined;
setSorting: (val: ValidatorsSortingValue | undefined) => void;
data: Array<ValidatorStability>;
sort: ValidatorsStabilitySortingValue | undefined;
setSorting: (val: ValidatorsStabilitySortingValue | undefined) => void;
isLoading?: boolean;
}
const ValidatorsTable = ({ data, sort, setSorting, isLoading }: Props) => {
const sortIconTransform = sort?.includes('asc' as ValidatorsSorting['order']) ? 'rotate(-90deg)' : 'rotate(90deg)';
const sortIconTransform = sort?.includes('asc' as ValidatorsStabilitySorting['order']) ? 'rotate(-90deg)' : 'rotate(90deg)';
const onSortToggle = React.useCallback((field: ValidatorsSortingField) => () => {
const value = getNextSortValue<ValidatorsSortingField, ValidatorsSortingValue>(SORT_SEQUENCE, field)(sort);
const onSortToggle = React.useCallback((field: ValidatorsStabilitySortingField) => () => {
const value = getNextSortValue<ValidatorsStabilitySortingField, ValidatorsStabilitySortingValue>(VALIDATORS_STABILITY_SORT_SEQUENCE, field)(sort);
setSorting(value);
}, [ sort, setSorting ]);
......
import { Tr, Td, Skeleton } from '@chakra-ui/react';
import React from 'react';
import type { Validator } from 'types/api/validators';
import type { ValidatorStability } from 'types/api/validators';
import AddressEntity from 'ui/shared/entities/address/AddressEntity';
import ValidatorStatus from 'ui/shared/statusTag/ValidatorStatus';
import ValidatorStatus from 'ui/shared/statusTag/ValidatorStabilityStatus';
interface Props {
data: Validator;
data: ValidatorStability;
isLoading?: boolean;
}
......
import type { ValidatorsSortingValue, ValidatorsSortingField } from 'types/api/validators';
import type {
ValidatorsStabilitySortingValue,
ValidatorsStabilitySortingField,
} from 'types/api/validators';
import type { TOption } from 'ui/shared/sort/Option';
export const SORT_OPTIONS: Array<TOption<ValidatorsSortingValue>> = [
export const VALIDATORS_STABILITY_SORT_OPTIONS: Array<TOption<ValidatorsStabilitySortingValue>> = [
{ title: 'Default', id: undefined },
{ title: 'Status descending', id: 'state-desc' },
{ title: 'Status ascending', id: 'state-asc' },
......@@ -10,7 +13,7 @@ export const SORT_OPTIONS: Array<TOption<ValidatorsSortingValue>> = [
{ title: 'Blocks validated ascending', id: 'blocks_validated-asc' },
];
export const SORT_SEQUENCE: Record<ValidatorsSortingField, Array<ValidatorsSortingValue | undefined>> = {
export const VALIDATORS_STABILITY_SORT_SEQUENCE: Record<ValidatorsStabilitySortingField, Array<ValidatorsStabilitySortingValue | undefined>> = {
state: [ 'state-desc', 'state-asc', undefined ],
blocks_validated: [ 'blocks_validated-desc', 'blocks_validated-asc', undefined ],
};
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