diff --git a/packages/apps-config/src/endpoints/testingRococo.ts b/packages/apps-config/src/endpoints/testingRococo.ts index 66885958f6..1a79f4ed9d 100644 --- a/packages/apps-config/src/endpoints/testingRococo.ts +++ b/packages/apps-config/src/endpoints/testingRococo.ts @@ -16,6 +16,7 @@ import type { EndpointOption } from './types'; export function createRococo (t: TFunction): EndpointOption { return { dnslink: 'rococo', + genesisHash: '0x481550b70e974177e83cff4d554cea7166be3eaeb32f020b86542e76968ccd0e', info: 'rococo', text: t('rpc.rococo', 'Rococo', { ns: 'apps-config' }), providers: { @@ -27,6 +28,7 @@ export function createRococo (t: TFunction): EndpointOption { // these are the base chains { info: 'rococoTick', + paraId: 100, text: t('rpc.rococo.tick', 'Tick', { ns: 'apps-config' }), providers: { Parity: 'wss://tick-rpc.polkadot.io' @@ -34,6 +36,7 @@ export function createRococo (t: TFunction): EndpointOption { }, { info: 'rococoTrick', + paraId: 110, text: t('rpc.rococo.trick', 'Trick', { ns: 'apps-config' }), providers: { Parity: 'wss://trick-rpc.polkadot.io' @@ -41,6 +44,7 @@ export function createRococo (t: TFunction): EndpointOption { }, { info: 'rococoTrack', + paraId: 120, text: t('rpc.rococo.track', 'Track', { ns: 'apps-config' }), providers: { Parity: 'wss://track-rpc.polkadot.io' @@ -49,6 +53,7 @@ export function createRococo (t: TFunction): EndpointOption { // add any additional parachains here, alphabetical { info: 'rococoPlasm', + paraId: 5000, text: t('rpc.rococo.plasm', 'Plasm PC2', { ns: 'apps-config' }), providers: { PlasmNetwork: 'wss://rpc.rococo.plasmnet.io' diff --git a/packages/apps-config/src/endpoints/types.ts b/packages/apps-config/src/endpoints/types.ts index d2bce08e20..412a5b6a18 100644 --- a/packages/apps-config/src/endpoints/types.ts +++ b/packages/apps-config/src/endpoints/types.ts @@ -3,11 +3,13 @@ export interface EndpointOption { dnslink?: string; + genesisHash?: string; isChild?: boolean; isDevelopment?: boolean; isDisabled?: boolean; linked?: EndpointOption[]; info?: string; + paraId?: number; providers: Record; text: React.ReactNode; } diff --git a/packages/apps-config/src/endpoints/util.ts b/packages/apps-config/src/endpoints/util.ts index a27937149e..3aafd0e804 100644 --- a/packages/apps-config/src/endpoints/util.ts +++ b/packages/apps-config/src/endpoints/util.ts @@ -11,10 +11,11 @@ export function expandLinked (input: LinkOption[]): LinkOption[] { return entry.linked ? result.concat( - expandLinked(entry.linked).map((entry): LinkOption => { - entry.isChild = true; + expandLinked(entry.linked).map((child): LinkOption => { + child.genesisHashRelay = entry.genesisHash; + child.isChild = true; - return entry; + return child; }) ) : result; @@ -22,11 +23,13 @@ export function expandLinked (input: LinkOption[]): LinkOption[] { } export function expandEndpoint (t: TFunction, input: EndpointOption): LinkOption[] { - const { dnslink, info, isChild, isDisabled, linked, providers, text } = input; + const { dnslink, genesisHash, info, isChild, isDisabled, linked, paraId, providers, text } = input; const base = { + genesisHash, info, isChild, isDisabled, + paraId, text }; diff --git a/packages/apps-config/src/settings/types.ts b/packages/apps-config/src/settings/types.ts index 16f0476d08..7aee4758e9 100644 --- a/packages/apps-config/src/settings/types.ts +++ b/packages/apps-config/src/settings/types.ts @@ -11,8 +11,11 @@ export interface Option { export interface LinkOption extends Option { dnslink?: string; + genesisHash?: string; + genesisHashRelay?: string; isChild?: boolean; isDevelopment?: boolean; linked?: LinkOption[]; + paraId?: number; textBy: string; } diff --git a/packages/page-parachains/src/Overview/Parachain.tsx b/packages/page-parachains/src/Overview/Parachain.tsx index 06020c3f1f..23a995e9b4 100644 --- a/packages/page-parachains/src/Overview/Parachain.tsx +++ b/packages/page-parachains/src/Overview/Parachain.tsx @@ -1,20 +1,21 @@ // Copyright 2017-2021 @polkadot/app-parachains authors & contributors // SPDX-License-Identifier: Apache-2.0 +import type { LinkOption } from '@polkadot/apps-config/settings/types'; import type { Option } from '@polkadot/types'; import type { BlockNumber, HeadData, ParaId } from '@polkadot/types/interfaces'; import BN from 'bn.js'; import React, { useMemo } from 'react'; -import { useApi, useCall } from '@polkadot/react-hooks'; +import { useApi, useCall, useParaApi } from '@polkadot/react-hooks'; import { BlockToTime } from '@polkadot/react-query'; import { formatNumber } from '@polkadot/util'; import { sliceHex } from './util'; interface Props { - bestNumber?: BN; + bestNumberFinalized?: BN; className?: string; id: ParaId; } @@ -33,23 +34,42 @@ const transformMark = { : null }; -function Parachain ({ bestNumber, className = '', id }: Props): React.ReactElement { +function getChainLink (endpoints: LinkOption[]): React.ReactNode { + if (!endpoints.length) { + return null; + } + + const { text, value } = endpoints[endpoints.length - 1]; + + return {text}; +} + +function Parachain ({ bestNumberFinalized, className = '', id }: Props): React.ReactElement { const { api } = useApi(); + const { api: paraApi, endpoints } = useParaApi(id); const headHex = useCall(api.query.paras.heads, [id], transformHead); const watermark = useCall(api.query.hrmp?.hrmpWatermarks, [id], transformMark); + const paraBest = useCall(paraApi?.derive.chain.bestNumber); + // const endpoints = useParaEndpoints(id); const blockDelay = useMemo( - () => watermark && bestNumber && bestNumber.sub(watermark), - [bestNumber, watermark] + () => watermark && bestNumberFinalized && bestNumberFinalized.sub(watermark), + [bestNumberFinalized, watermark] + ); + + const chainLink = useMemo( + () => getChainLink(endpoints), + [endpoints] ); return (

{formatNumber(id)}

- + {chainLink} {headHex} - {formatNumber(watermark)} {blockDelay && } + {formatNumber(watermark)} + {paraBest && formatNumber(paraBest)} ); } diff --git a/packages/page-parachains/src/Overview/ParachainList.tsx b/packages/page-parachains/src/Overview/ParachainList.tsx index 6c7a9feaa2..291b9b4893 100644 --- a/packages/page-parachains/src/Overview/ParachainList.tsx +++ b/packages/page-parachains/src/Overview/ParachainList.tsx @@ -19,12 +19,13 @@ interface Props { function ParachainList ({ ids }: Props): React.ReactElement { const { t } = useTranslation(); const { api } = useApi(); - const bestNumber = useCall(api.derive.chain.bestNumber); + const bestNumberFinalized = useCall(api.derive.chain.bestNumberFinalized); const headerRef = useRef([ [t('parachains'), 'start', 2], [t('heads'), 'start'], - [t('watermark'), 2] + [t('watermark'), 'undefined', 2], + [t('chain best')] ]); return ( @@ -34,7 +35,7 @@ function ParachainList ({ ids }: Props): React.ReactElement { > {ids?.map((id): React.ReactNode => ( diff --git a/packages/page-parachains/src/Overview/Summary.tsx b/packages/page-parachains/src/Overview/Summary.tsx index a45a2034b8..ee70353945 100644 --- a/packages/page-parachains/src/Overview/Summary.tsx +++ b/packages/page-parachains/src/Overview/Summary.tsx @@ -49,9 +49,9 @@ function Summary ({ parachainCount, proposalCount, upcomingCount }: Props): Reac
('best block')} + label={t('finalized block')} > - +
diff --git a/packages/react-hooks/src/index.ts b/packages/react-hooks/src/index.ts index c29516aac8..9ae55afcb5 100644 --- a/packages/react-hooks/src/index.ts +++ b/packages/react-hooks/src/index.ts @@ -27,6 +27,8 @@ export { useNonZeroBn } from './useNonZeroBn'; export { useOwnEraRewards } from './useOwnEraRewards'; export { useOwnStashes, useOwnStashIds } from './useOwnStashes'; export { useOwnStashInfos } from './useOwnStashInfos'; +export { useParaApi } from './useParaApi'; +export { useParaEndpoints } from './useParaEndpoints'; export { usePassword } from './usePassword'; export { useRegistrars } from './useRegistrars'; export { useSavedFlags } from './useSavedFlags'; diff --git a/packages/react-hooks/src/useParaApi.ts b/packages/react-hooks/src/useParaApi.ts new file mode 100644 index 0000000000..cca14126d6 --- /dev/null +++ b/packages/react-hooks/src/useParaApi.ts @@ -0,0 +1,66 @@ +// Copyright 2017-2021 @polkadot/react-hooks authors & contributors +// SPDX-License-Identifier: Apache-2.0 + +import type BN from 'bn.js'; +import type { LinkOption } from '@polkadot/apps-config/settings/types'; + +import { useCallback, useEffect, useRef, useState } from 'react'; + +import { ApiPromise, WsProvider } from '@polkadot/api'; +import { typesBundle, typesChain, typesSpec } from '@polkadot/apps-config'; + +import { useIsMountedRef } from './useIsMountedRef'; +import { useParaEndpoints } from './useParaEndpoints'; + +interface Result { + api?: ApiPromise | null; + endpoints: LinkOption[]; +} + +function disconnect (api: ApiPromise | null): void { + api && api.disconnect().catch(console.error); +} + +export function useParaApi (paraId: BN | number): Result { + const apiRef = useRef(null); + const mountedRef = useIsMountedRef(); + const endpoints = useParaEndpoints(paraId); + const [state, setState] = useState({ api: null, endpoints }); + + useEffect((): () => void => { + return (): void => { + disconnect(apiRef.current); + apiRef.current = null; + }; + }, []); + + const _setApi = useCallback( + (api: ApiPromise | null): void => { + disconnect(apiRef.current); + + if (mountedRef.current) { + apiRef.current = api; + setState(({ endpoints }) => ({ api, endpoints })); + } + }, + [mountedRef] + ); + + useEffect((): void => { + if (endpoints.length) { + ApiPromise + .create({ + provider: new WsProvider(endpoints[endpoints.length - 1].value as string), + typesBundle, + typesChain, + typesSpec + }) + .then(_setApi) + .catch(console.error); + } else { + _setApi(null); + } + }, [_setApi, endpoints]); + + return state; +} diff --git a/packages/react-hooks/src/useParaEndpoints.ts b/packages/react-hooks/src/useParaEndpoints.ts new file mode 100644 index 0000000000..12b6b9a048 --- /dev/null +++ b/packages/react-hooks/src/useParaEndpoints.ts @@ -0,0 +1,29 @@ +// Copyright 2017-2021 @polkadot/react-hooks authors & contributors +// SPDX-License-Identifier: Apache-2.0 + +import type BN from 'bn.js'; +import type { LinkOption } from '@polkadot/apps-config/settings/types'; + +import { useMemo } from 'react'; + +import { createWsEndpoints } from '@polkadot/apps-config'; +import { bnToBn } from '@polkadot/util'; + +import { useApi } from './useApi'; + +function extractResults (genesisHash: string, paraId: BN | number): LinkOption[] { + const numId = bnToBn(paraId).toNumber(); + + return createWsEndpoints((key: string, value: string | undefined) => value || key).filter(({ genesisHashRelay, paraId }) => + paraId === numId && genesisHashRelay === genesisHash + ); +} + +export function useParaEndpoints (paraId: BN | number): LinkOption[] { + const { api } = useApi(); + + return useMemo( + () => extractResults(api.genesisHash.toHex(), paraId), + [api, paraId] + ); +} diff --git a/packages/react-query/src/BestNumber.tsx b/packages/react-query/src/BestNumber.tsx index 442acc7c6c..c1d9704b09 100644 --- a/packages/react-query/src/BestNumber.tsx +++ b/packages/react-query/src/BestNumber.tsx @@ -11,13 +11,14 @@ import { formatNumber } from '@polkadot/util'; interface Props { children?: React.ReactNode; className?: string; + isFinalized?: boolean; label?: React.ReactNode; withPound?: boolean; } -function BestNumber ({ children, className = '', label, withPound }: Props): React.ReactElement { +function BestNumber ({ children, className = '', isFinalized, label, withPound }: Props): React.ReactElement { const { api, isApiReady } = useApi(); - const bestNumber = useCall(isApiReady && api.derive.chain.bestNumber); + const bestNumber = useCall(isApiReady && (isFinalized ? api.derive.chain.bestNumberFinalized : api.derive.chain.bestNumber)); return (