Allow xcm asset teleport (#5413)
* Allow xcm asset teleport * Should be operational... * lock cron * Teleport from parachain to relay * Only enable on relay * Disable para teleport
This commit is contained in:
parent
f35c0de405
commit
7148e7e6b6
2
.github/workflows/lock.yml
vendored
2
.github/workflows/lock.yml
vendored
@ -2,7 +2,7 @@ name: 'Lock Threads'
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: '4 4 * * *'
|
||||
- cron: '9 9 * * *'
|
||||
|
||||
jobs:
|
||||
lock:
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { TFunction } from 'i18next';
|
||||
import type { LinkOption } from '../settings/types';
|
||||
import type { LinkOption } from './types';
|
||||
|
||||
export const CUSTOM_ENDPOINT_KEY = 'polkadot-app-custom-endpoints';
|
||||
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { TFunction } from 'i18next';
|
||||
import type { LinkOption } from '../settings/types';
|
||||
import type { LinkOption } from './types';
|
||||
|
||||
import { createCustom, createDev, createOwn } from './development';
|
||||
import { createProduction } from './production';
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { TFunction } from 'i18next';
|
||||
import type { LinkOption } from '../settings/types';
|
||||
import type { LinkOption } from './types';
|
||||
|
||||
import { expandEndpoints } from './util';
|
||||
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { TFunction } from 'i18next';
|
||||
import type { LinkOption } from '../settings/types';
|
||||
import type { LinkOption } from './types';
|
||||
|
||||
import { createKusama } from './productionRelayKusama';
|
||||
import { createPolkadot } from './productionRelayPolkadot';
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { TFunction } from 'i18next';
|
||||
import type { LinkOption } from '../settings/types';
|
||||
import type { LinkOption } from './types';
|
||||
|
||||
import { expandEndpoints } from './util';
|
||||
|
||||
|
||||
@ -33,6 +33,7 @@ export function createWestend (t: TFunction): EndpointOption {
|
||||
// (2) common good, leave as second group
|
||||
{
|
||||
info: 'westmint',
|
||||
allowTeleport: true,
|
||||
paraId: 1000,
|
||||
text: t('rpc.westend.shell', 'Westmint', { ns: 'apps-config' }),
|
||||
providers: {
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { TFunction } from 'i18next';
|
||||
import type { LinkOption } from '../settings/types';
|
||||
import type { LinkOption } from './types';
|
||||
|
||||
import { createRococo } from './testingRelayRococo';
|
||||
import { createWestend } from './testingRelayWestend';
|
||||
|
||||
@ -1,7 +1,10 @@
|
||||
// Copyright 2017-2021 @polkadot/apps-config authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { Option } from '../settings/types';
|
||||
|
||||
export interface EndpointOption {
|
||||
allowTeleport?: boolean;
|
||||
dnslink?: string;
|
||||
genesisHash?: string;
|
||||
isChild?: boolean;
|
||||
@ -13,3 +16,17 @@ export interface EndpointOption {
|
||||
providers: Record<string, string>;
|
||||
text: React.ReactNode;
|
||||
}
|
||||
|
||||
export interface LinkOption extends Option {
|
||||
allowTeleport?: boolean;
|
||||
dnslink?: string;
|
||||
genesisHash?: string;
|
||||
genesisHashRelay?: string;
|
||||
isChild?: boolean;
|
||||
isDevelopment?: boolean;
|
||||
isRelay?: boolean;
|
||||
isSpaced?: boolean;
|
||||
linked?: LinkOption[];
|
||||
paraId?: number;
|
||||
textBy: string;
|
||||
}
|
||||
|
||||
@ -2,8 +2,7 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { TFunction } from 'i18next';
|
||||
import type { LinkOption } from '../settings/types';
|
||||
import type { EndpointOption } from './types';
|
||||
import type { EndpointOption, LinkOption } from './types';
|
||||
|
||||
export function expandLinked (input: LinkOption[]): LinkOption[] {
|
||||
return input.reduce((result: LinkOption[], entry): LinkOption[] => {
|
||||
@ -22,9 +21,9 @@ export function expandLinked (input: LinkOption[]): LinkOption[] {
|
||||
}, []);
|
||||
}
|
||||
|
||||
export function expandEndpoint (t: TFunction, input: EndpointOption): LinkOption[] {
|
||||
const { dnslink, genesisHash, info, isChild, isDisabled, linked, paraId, providers, text } = input;
|
||||
export function expandEndpoint (t: TFunction, { allowTeleport, dnslink, genesisHash, info, isChild, isDisabled, linked, paraId, providers, text }: EndpointOption): LinkOption[] {
|
||||
const base = {
|
||||
allowTeleport,
|
||||
genesisHash,
|
||||
info,
|
||||
isChild,
|
||||
@ -36,6 +35,7 @@ export function expandEndpoint (t: TFunction, input: EndpointOption): LinkOption
|
||||
const result = Object.entries(providers).map(([host, value], index): LinkOption => ({
|
||||
...base,
|
||||
dnslink: index === 0 ? dnslink : undefined,
|
||||
isRelay: false,
|
||||
textBy: t('rpc.hosted.by', 'hosted by {{host}}', { ns: 'apps-config', replace: { host } }),
|
||||
value
|
||||
}));
|
||||
@ -45,7 +45,15 @@ export function expandEndpoint (t: TFunction, input: EndpointOption): LinkOption
|
||||
const options: LinkOption[] = [];
|
||||
|
||||
linked.forEach((o) => options.push(...expandEndpoint(t, o)));
|
||||
last.isRelay = true;
|
||||
last.linked = options;
|
||||
|
||||
// if one of the children allows teleport, add it to the root as well
|
||||
const allowTeleport = options.some(({ allowTeleport }) => allowTeleport);
|
||||
|
||||
result.forEach((r): void => {
|
||||
r.allowTeleport = allowTeleport;
|
||||
});
|
||||
}
|
||||
|
||||
return expandLinked(result);
|
||||
|
||||
@ -8,15 +8,3 @@ export interface Option {
|
||||
text: React.ReactNode;
|
||||
value: string | number;
|
||||
}
|
||||
|
||||
export interface LinkOption extends Option {
|
||||
dnslink?: string;
|
||||
genesisHash?: string;
|
||||
genesisHashRelay?: string;
|
||||
isChild?: boolean;
|
||||
isDevelopment?: boolean;
|
||||
isSpaced?: boolean;
|
||||
linked?: LinkOption[];
|
||||
paraId?: number;
|
||||
textBy: string;
|
||||
}
|
||||
|
||||
@ -28,6 +28,7 @@ import staking from './staking';
|
||||
import storage from './storage';
|
||||
import sudo from './sudo';
|
||||
import techcomm from './techcomm';
|
||||
import teleport from './teleport';
|
||||
import transfer from './transfer';
|
||||
import treasury from './treasury';
|
||||
|
||||
@ -39,6 +40,7 @@ export default function create (t: TFunction): Routes {
|
||||
claims(t),
|
||||
poll(t),
|
||||
transfer(t),
|
||||
teleport(t),
|
||||
staking(t),
|
||||
democracy(t),
|
||||
council(t),
|
||||
|
||||
30
packages/apps-routing/src/teleport.ts
Normal file
30
packages/apps-routing/src/teleport.ts
Normal file
@ -0,0 +1,30 @@
|
||||
// Copyright 2017-2021 @polkadot/apps-routing authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { TFunction } from 'i18next';
|
||||
import type { Route } from './types';
|
||||
|
||||
import Modal from '@polkadot/app-parachains/Teleport';
|
||||
|
||||
export default function create (t: TFunction): Route {
|
||||
return {
|
||||
Component: Modal,
|
||||
Modal,
|
||||
display: {
|
||||
isHidden: false,
|
||||
needsAccounts: true,
|
||||
needsApi: [
|
||||
[
|
||||
'tx.xcm.teleportAssets',
|
||||
'tx.xcmPallet.teleportAssets',
|
||||
'tx.polkadotXcm.teleportAssets'
|
||||
]
|
||||
],
|
||||
needsTeleport: true
|
||||
},
|
||||
group: 'accounts',
|
||||
icon: 'share-square',
|
||||
name: 'teleport',
|
||||
text: t('nav.teleport', 'Teleport', { ns: 'apps-routing' })
|
||||
};
|
||||
}
|
||||
@ -11,14 +11,16 @@ export interface RouteProps extends AppProps, BareProps {
|
||||
}
|
||||
|
||||
export interface Route {
|
||||
Component: React.ComponentType<RouteProps>;
|
||||
Modal?: React.ComponentType<any>;
|
||||
// FIXME This is weird, we really expect the memo to be there...
|
||||
Component: React.ComponentType<RouteProps> | React.MemoExoticComponent<any>;
|
||||
Modal?: React.ComponentType<any> | React.MemoExoticComponent<any>;
|
||||
display: {
|
||||
isHidden?: boolean;
|
||||
isModal?: boolean;
|
||||
needsAccounts?: boolean;
|
||||
needsApi?: (string | string[])[];
|
||||
needsSudo?: boolean;
|
||||
needsTeleport?: boolean;
|
||||
};
|
||||
group: RouteGroup;
|
||||
icon: IconName;
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
// Copyright 2017-2021 @polkadot/apps authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { LinkOption } from '@polkadot/apps-config/settings/types';
|
||||
import type { LinkOption } from '@polkadot/apps-config/endpoints/types';
|
||||
import type { Group } from './types';
|
||||
|
||||
// ok, this seems to be an eslint bug, this _is_ a package import
|
||||
|
||||
@ -12,7 +12,7 @@ import { useLocation } from 'react-router-dom';
|
||||
import styled from 'styled-components';
|
||||
|
||||
import createRoutes from '@polkadot/apps-routing';
|
||||
import { useAccounts, useApi, useCall } from '@polkadot/react-hooks';
|
||||
import { useAccounts, useApi, useCall, useTeleport } from '@polkadot/react-hooks';
|
||||
|
||||
import { findMissingApis } from '../endpoint';
|
||||
import { useTranslation } from '../translate';
|
||||
@ -42,7 +42,7 @@ function createExternals (t: TFunction): ItemRoute[] {
|
||||
];
|
||||
}
|
||||
|
||||
function checkVisible ({ api, isApiConnected, isApiReady }: ApiProps, hasAccounts: boolean, hasSudo: boolean, { isHidden, needsAccounts, needsApi, needsSudo }: Route['display']): boolean {
|
||||
function checkVisible ({ api, isApiConnected, isApiReady }: ApiProps, allowTeleport: boolean, hasAccounts: boolean, hasSudo: boolean, { isHidden, needsAccounts, needsApi, needsSudo, needsTeleport }: Route['display']): boolean {
|
||||
if (isHidden) {
|
||||
return false;
|
||||
} else if (needsAccounts && !hasAccounts) {
|
||||
@ -53,12 +53,14 @@ function checkVisible ({ api, isApiConnected, isApiReady }: ApiProps, hasAccount
|
||||
return false;
|
||||
} else if (needsSudo && !hasSudo) {
|
||||
return false;
|
||||
} else if (needsTeleport && !allowTeleport) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return findMissingApis(api, needsApi).length === 0;
|
||||
}
|
||||
|
||||
function extractGroups (routing: Routes, groupNames: Record<string, string>, apiProps: ApiProps, hasAccounts: boolean, hasSudo: boolean): Group[] {
|
||||
function extractGroups (routing: Routes, groupNames: Record<string, string>, apiProps: ApiProps, allowTeleport: boolean, hasAccounts: boolean, hasSudo: boolean): Group[] {
|
||||
return Object
|
||||
.values(
|
||||
routing.reduce((all: Groups, route): Groups => {
|
||||
@ -77,7 +79,7 @@ function extractGroups (routing: Routes, groupNames: Record<string, string>, api
|
||||
.map(({ name, routes }): Group => ({
|
||||
name,
|
||||
routes: routes.filter(({ display }) =>
|
||||
checkVisible(apiProps, hasAccounts, hasSudo, display)
|
||||
checkVisible(apiProps, allowTeleport, hasAccounts, hasSudo, display)
|
||||
)
|
||||
}))
|
||||
.filter(({ routes }) => routes.length);
|
||||
@ -87,6 +89,7 @@ function Menu ({ className = '' }: Props): React.ReactElement<Props> {
|
||||
const { t } = useTranslation();
|
||||
const { allAccounts, hasAccounts } = useAccounts();
|
||||
const apiProps = useApi();
|
||||
const { allowTeleport } = useTeleport();
|
||||
const sudoKey = useCall<AccountId>(apiProps.isApiReady && apiProps.api.query.sudo?.key);
|
||||
const location = useLocation();
|
||||
|
||||
@ -107,8 +110,8 @@ function Menu ({ className = '' }: Props): React.ReactElement<Props> {
|
||||
);
|
||||
|
||||
const visibleGroups = useMemo(
|
||||
() => extractGroups(routeRef.current, groupRef.current, apiProps, hasAccounts, hasSudo),
|
||||
[apiProps, hasAccounts, hasSudo]
|
||||
() => extractGroups(routeRef.current, groupRef.current, apiProps, allowTeleport, hasAccounts, hasSudo),
|
||||
[allowTeleport, apiProps, hasAccounts, hasSudo]
|
||||
);
|
||||
|
||||
const activeRoute = useMemo(
|
||||
|
||||
124
packages/page-parachains/src/Teleport.tsx
Normal file
124
packages/page-parachains/src/Teleport.tsx
Normal file
@ -0,0 +1,124 @@
|
||||
// Copyright 2017-2021 @polkadot/app-parachains authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import BN from 'bn.js';
|
||||
import React, { useMemo, useState } from 'react';
|
||||
|
||||
import { Dropdown, InputAddress, InputBalance, Modal, TxButton } from '@polkadot/react-components';
|
||||
import { useApi, useTeleport } from '@polkadot/react-hooks';
|
||||
import { Available } from '@polkadot/react-query';
|
||||
import { BN_ZERO } from '@polkadot/util';
|
||||
|
||||
import { useTranslation } from './translate';
|
||||
|
||||
interface Props {
|
||||
onClose: () => void;
|
||||
}
|
||||
|
||||
const INVALID_PARAID = Number.MAX_SAFE_INTEGER;
|
||||
const DEFAULT_WEIGHT = 1_000_000_000;
|
||||
|
||||
function Teleport ({ onClose }: Props): React.ReactElement<Props> | null {
|
||||
const { t } = useTranslation();
|
||||
const { api } = useApi();
|
||||
const [amount, setAmount] = useState<BN | undefined>(BN_ZERO);
|
||||
const [hasAvailable] = useState(true);
|
||||
const [recipientId, setRecipientId] = useState<string | null>(null);
|
||||
const [senderId, setSenderId] = useState<string | null>(null);
|
||||
const [recipientParaId, setParaId] = useState(INVALID_PARAID);
|
||||
// FIXME We need to actually calculate the destination weight
|
||||
const [weight] = useState(DEFAULT_WEIGHT);
|
||||
const { allowTeleport, destinations, isParachain } = useTeleport();
|
||||
|
||||
const chainOpts = useMemo(
|
||||
() => destinations.map(({ paraId, text }, index) => ({ text, value: paraId || index })),
|
||||
[destinations]
|
||||
);
|
||||
|
||||
const params = useMemo(
|
||||
() => isParachain
|
||||
? [
|
||||
{ X1: 'Parent' },
|
||||
{ X1: { AccountId32: { id: recipientId, network: 'Any' } } },
|
||||
[{ ConcreteFungible: { amount, id: { X1: 'Parent' } } }],
|
||||
weight
|
||||
]
|
||||
: [
|
||||
{ X1: { ParaChain: recipientParaId } },
|
||||
{ X1: { AccountId32: { id: recipientId, network: 'Any' } } },
|
||||
[{ ConcreteFungible: { amount, id: 'Null' } }],
|
||||
weight
|
||||
],
|
||||
[amount, isParachain, recipientId, recipientParaId, weight]
|
||||
);
|
||||
|
||||
console.log('Teleport', allowTeleport, chainOpts);
|
||||
|
||||
if (!allowTeleport || !chainOpts.length) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<Modal
|
||||
header={t<string>('Teleport assets')}
|
||||
size='large'
|
||||
>
|
||||
<Modal.Content>
|
||||
<Modal.Columns hint={t<string>('The transferred balance will be subtracted (along with fees) from the sender account.')}>
|
||||
<InputAddress
|
||||
label={t<string>('send from account')}
|
||||
labelExtra={
|
||||
<Available
|
||||
label={t<string>('transferrable')}
|
||||
params={senderId}
|
||||
/>
|
||||
}
|
||||
onChange={setSenderId}
|
||||
type='account'
|
||||
/>
|
||||
</Modal.Columns>
|
||||
<Modal.Columns hint={t<string>('The destination chain for this asset teleport. The transferred value will appear on this chain.')}>
|
||||
<Dropdown
|
||||
defaultValue={chainOpts[0]?.value}
|
||||
label={t<string>('destination chain')}
|
||||
onChange={setParaId}
|
||||
options={chainOpts}
|
||||
/>
|
||||
</Modal.Columns>
|
||||
<Modal.Columns hint={t<string>('The beneficiary will have access to the transferred amount when the transaction is included in a block.')}>
|
||||
<InputAddress
|
||||
label={t<string>('send to address')}
|
||||
onChange={setRecipientId}
|
||||
type='allPlus'
|
||||
/>
|
||||
</Modal.Columns>
|
||||
<Modal.Columns hint={t<string>('If the recipient account is new, the balance needs to be more than the existential deposit on the recipient chain.')}>
|
||||
<InputBalance
|
||||
autoFocus
|
||||
isError={!hasAvailable}
|
||||
isZeroable
|
||||
label={t<string>('amount')}
|
||||
onChange={setAmount}
|
||||
/>
|
||||
</Modal.Columns>
|
||||
</Modal.Content>
|
||||
<Modal.Actions onCancel={onClose}>
|
||||
<TxButton
|
||||
accountId={senderId}
|
||||
icon='share-square'
|
||||
isDisabled={!hasAvailable || !recipientId || !amount || (!isParachain && recipientParaId === INVALID_PARAID)}
|
||||
label={t<string>('Teleport')}
|
||||
onStart={onClose}
|
||||
params={params}
|
||||
tx={
|
||||
(api.tx.xcm && api.tx.xcm.teleportAssets) ||
|
||||
(api.tx.xcmPallet && api.tx.xcmPallet.teleportAssets) ||
|
||||
(api.tx.polkadotXcm && api.tx.polkadotXcm.teleportAssets)
|
||||
}
|
||||
/>
|
||||
</Modal.Actions>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
|
||||
export default React.memo(Teleport);
|
||||
@ -186,8 +186,8 @@ function Api ({ children, store, url }: Props): React.ReactElement<Props> | null
|
||||
const [extensions, setExtensions] = useState<InjectedExtension[] | undefined>();
|
||||
|
||||
const value = useMemo<ApiProps>(
|
||||
() => ({ ...state, api, apiError, extensions, isApiConnected, isApiInitialized, isWaitingInjected: !extensions }),
|
||||
[apiError, extensions, isApiConnected, isApiInitialized, state]
|
||||
() => ({ ...state, api, apiError, apiUrl: url, extensions, isApiConnected, isApiInitialized, isWaitingInjected: !extensions }),
|
||||
[apiError, extensions, isApiConnected, isApiInitialized, state, url]
|
||||
);
|
||||
|
||||
// initial initialization
|
||||
|
||||
@ -31,6 +31,7 @@ export interface ApiState {
|
||||
export interface ApiProps extends ApiState {
|
||||
api: ApiPromise;
|
||||
apiError: string | null;
|
||||
apiUrl?: string;
|
||||
extensions?: InjectedExtension[];
|
||||
isApiConnected: boolean;
|
||||
isApiInitialized: boolean;
|
||||
|
||||
@ -40,6 +40,7 @@ export { useRegistrars } from './useRegistrars';
|
||||
export { useSavedFlags } from './useSavedFlags';
|
||||
export { useStepper } from './useStepper';
|
||||
export { useSudo } from './useSudo';
|
||||
export { useTeleport } from './useTeleport';
|
||||
export { useToggle } from './useToggle';
|
||||
export { useTreasury } from './useTreasury';
|
||||
export { useTxBatch } from './useTxBatch';
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type BN from 'bn.js';
|
||||
import type { LinkOption } from '@polkadot/apps-config/settings/types';
|
||||
import type { LinkOption } from '@polkadot/apps-config/endpoints/types';
|
||||
|
||||
import { useCallback, useEffect, useRef, useState } from 'react';
|
||||
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type BN from 'bn.js';
|
||||
import type { LinkOption } from '@polkadot/apps-config/settings/types';
|
||||
import type { LinkOption } from '@polkadot/apps-config/endpoints/types';
|
||||
|
||||
import { useMemo } from 'react';
|
||||
|
||||
@ -11,8 +11,10 @@ import { bnToBn } from '@polkadot/util';
|
||||
|
||||
import { useApi } from './useApi';
|
||||
|
||||
const endpoints = createWsEndpoints((key: string, value: string | undefined) => value || key);
|
||||
|
||||
function extractRelayEndpoints (genesisHash: string): LinkOption[] {
|
||||
return createWsEndpoints((key: string, value: string | undefined) => value || key).filter(({ genesisHashRelay }) =>
|
||||
return endpoints.filter(({ genesisHashRelay }) =>
|
||||
genesisHash === genesisHashRelay
|
||||
);
|
||||
}
|
||||
@ -23,7 +25,7 @@ function extractParaEndpoints (allEndpoints: LinkOption[], paraId: BN | number):
|
||||
return allEndpoints.filter(({ paraId }) => paraId === numId);
|
||||
}
|
||||
|
||||
function useRelayEndpoints (): LinkOption[] {
|
||||
export function useRelayEndpoints (): LinkOption[] {
|
||||
const { api } = useApi();
|
||||
|
||||
return useMemo(
|
||||
@ -46,7 +48,10 @@ export function useIsParasLinked (ids?: (BN | number)[] | null): Record<string,
|
||||
|
||||
return useMemo(
|
||||
() => ids
|
||||
? ids.reduce((all: Record<string, boolean>, id) => ({ ...all, [id.toString()]: extractParaEndpoints(endpoints, id).length !== 0 }), {})
|
||||
? ids.reduce((all: Record<string, boolean>, id) => ({
|
||||
...all,
|
||||
[id.toString()]: extractParaEndpoints(endpoints, id).length !== 0
|
||||
}), {})
|
||||
: {},
|
||||
[endpoints, ids]
|
||||
);
|
||||
|
||||
93
packages/react-hooks/src/useTeleport.ts
Normal file
93
packages/react-hooks/src/useTeleport.ts
Normal file
@ -0,0 +1,93 @@
|
||||
// Copyright 2017-2021 @polkadot/react-hooks authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { LinkOption } from '@polkadot/apps-config/endpoints/types';
|
||||
import type { ParaId } from '@polkadot/types/interfaces';
|
||||
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
import { createWsEndpoints } from '@polkadot/apps-config';
|
||||
|
||||
import { useApi } from './useApi';
|
||||
import { useCall } from './useCall';
|
||||
|
||||
interface Teleport {
|
||||
allowTeleport: boolean;
|
||||
destinations: LinkOption[];
|
||||
isParachain: boolean;
|
||||
paraId?: ParaId;
|
||||
}
|
||||
|
||||
const endpoints = createWsEndpoints((k: string, v: string | undefined) => v || k).filter(({ allowTeleport }) => allowTeleport);
|
||||
|
||||
function extractRelayDestinations (relayGenesis: string, filter: (l: LinkOption) => boolean): LinkOption[] {
|
||||
return endpoints
|
||||
.filter((l) =>
|
||||
(
|
||||
l.genesisHashRelay === relayGenesis ||
|
||||
l.genesisHash === relayGenesis
|
||||
) && filter(l)
|
||||
)
|
||||
.reduce((result: LinkOption[], curr): LinkOption[] => {
|
||||
if (!result.some(({ genesisHash, paraId }) => paraId === curr.paraId || genesisHash === curr.genesisHash)) {
|
||||
result.push(curr);
|
||||
}
|
||||
|
||||
return result;
|
||||
}, [])
|
||||
.sort((a, b) =>
|
||||
a.isRelay === b.isRelay
|
||||
? 0
|
||||
: a.isRelay
|
||||
? -1
|
||||
: 1
|
||||
);
|
||||
}
|
||||
|
||||
export function useTeleport (): Teleport {
|
||||
const { api, apiUrl, isApiReady } = useApi();
|
||||
const chainParaId = useCall<ParaId>(isApiReady && api.query.parachainInfo?.parachainId);
|
||||
const [state, setState] = useState<Teleport>(() => ({
|
||||
allowTeleport: false,
|
||||
destinations: [],
|
||||
isParachain: false
|
||||
}));
|
||||
|
||||
useEffect((): void => {
|
||||
if (isApiReady) {
|
||||
const relayGenesis = api.genesisHash.toHex();
|
||||
const destinations = extractRelayDestinations(relayGenesis, ({ genesisHash }) => relayGenesis !== genesisHash);
|
||||
|
||||
setState((prev) => ({
|
||||
...prev,
|
||||
allowTeleport: destinations.length !== 0,
|
||||
destinations
|
||||
}));
|
||||
}
|
||||
}, [api, isApiReady]);
|
||||
|
||||
useEffect((): void => {
|
||||
if (chainParaId) {
|
||||
const endpoint = endpoints.find(({ paraId, value }) =>
|
||||
chainParaId.eq(paraId) &&
|
||||
value === apiUrl
|
||||
);
|
||||
|
||||
setState((prev) => ({
|
||||
...prev,
|
||||
// FIXME Cannot quite get this working...
|
||||
allowTeleport: false, // !!endpoint,
|
||||
destinations: endpoint && endpoint.genesisHashRelay
|
||||
// FIXME we probably just want to check for !chainParaId.eq(paraId) || !!genesisHash (would affect Teleport modal)
|
||||
? extractRelayDestinations(endpoint.genesisHashRelay, ({ genesisHash }) => !!genesisHash)
|
||||
: [],
|
||||
isParachain: true,
|
||||
paraId: chainParaId
|
||||
}));
|
||||
}
|
||||
}, [apiUrl, chainParaId]);
|
||||
|
||||
console.log('useTeleport', JSON.stringify(state));
|
||||
|
||||
return state;
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user