Links to parachains (?rpc=) & best block (#4395)

* Links to parachains (?rpc=) & best block

* Adjust

* Add known types to ParaApi
This commit is contained in:
Jaco Greeff 2021-01-13 17:20:25 +01:00 committed by GitHub
parent a642db2f0d
commit f93cc49db6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 150 additions and 18 deletions

View File

@ -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'

View File

@ -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<string, string>;
text: React.ReactNode;
}

View File

@ -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
};

View File

@ -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;
}

View File

@ -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<Props> {
function getChainLink (endpoints: LinkOption[]): React.ReactNode {
if (!endpoints.length) {
return null;
}
const { text, value } = endpoints[endpoints.length - 1];
return <a href={`${window.location.origin}${window.location.pathname}?rpc=${encodeURIComponent(value)}`}>{text}</a>;
}
function Parachain ({ bestNumberFinalized, className = '', id }: Props): React.ReactElement<Props> {
const { api } = useApi();
const { api: paraApi, endpoints } = useParaApi(id);
const headHex = useCall<string | null>(api.query.paras.heads, [id], transformHead);
const watermark = useCall<BlockNumber | null>(api.query.hrmp?.hrmpWatermarks, [id], transformMark);
const paraBest = useCall<BlockNumber>(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 (
<tr className={className}>
<td className='number'><h1>{formatNumber(id)}</h1></td>
<td />
<td className='together'>{chainLink}</td>
<td className='all start together hash'>{headHex}</td>
<td className='number'>{formatNumber(watermark)}</td>
<td className='number'>{blockDelay && <BlockToTime blocks={blockDelay} />}</td>
<td className='number'>{formatNumber(watermark)}</td>
<td className='number'>{paraBest && formatNumber(paraBest)}</td>
</tr>
);
}

View File

@ -19,12 +19,13 @@ interface Props {
function ParachainList ({ ids }: Props): React.ReactElement<Props> {
const { t } = useTranslation();
const { api } = useApi();
const bestNumber = useCall<BN>(api.derive.chain.bestNumber);
const bestNumberFinalized = useCall<BN>(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<Props> {
>
{ids?.map((id): React.ReactNode => (
<Parachain
bestNumber={bestNumber}
bestNumberFinalized={bestNumberFinalized}
id={id}
key={id.toString()}
/>

View File

@ -49,9 +49,9 @@ function Summary ({ parachainCount, proposalCount, upcomingCount }: Props): Reac
<section>
<CardSummary
className='media--800'
label={t<string>('best block')}
label={t<string>('finalized block')}
>
<BestNumber />
<BestNumber isFinalized />
</CardSummary>
<SummarySession withEra={false} />
</section>

View File

@ -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';

View File

@ -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<ApiPromise | null>(null);
const mountedRef = useIsMountedRef();
const endpoints = useParaEndpoints(paraId);
const [state, setState] = useState<Result>({ 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;
}

View File

@ -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]
);
}

View File

@ -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<Props> {
function BestNumber ({ children, className = '', isFinalized, label, withPound }: Props): React.ReactElement<Props> {
const { api, isApiReady } = useApi();
const bestNumber = useCall<BlockNumber>(isApiReady && api.derive.chain.bestNumber);
const bestNumber = useCall<BlockNumber>(isApiReady && (isFinalized ? api.derive.chain.bestNumberFinalized : api.derive.chain.bestNumber));
return (
<div className={className}>