diff --git a/packages/apps/src/Root.tsx b/packages/apps/src/Root.tsx index eab753d5f5..de47a857de 100644 --- a/packages/apps/src/Root.tsx +++ b/packages/apps/src/Root.tsx @@ -43,13 +43,13 @@ function Root ({ isElectron, store }: Props): React.ReactElement { return ( - - - + + + @@ -61,9 +61,9 @@ function Root ({ isElectron, store }: Props): React.ReactElement { - - - + + + ); diff --git a/packages/page-bounties/test/pages/bountiesPage.tsx b/packages/page-bounties/test/pages/bountiesPage.tsx index eeb17db1e8..a90a993740 100644 --- a/packages/page-bounties/test/pages/bountiesPage.tsx +++ b/packages/page-bounties/test/pages/bountiesPage.tsx @@ -111,6 +111,10 @@ export class BountiesPage { } } }, + isApiConnected: true, + isApiInitialized: true, + isApiReady: true, + isEthereum: false, systemName: 'substrate' } as unknown as ApiProps; @@ -124,15 +128,15 @@ export class BountiesPage {
- - - - + + + + - - - - + + + + diff --git a/packages/react-components/src/InputAddress/createItem.tsx b/packages/react-components/src/InputAddress/createItem.tsx index 6f8ecba7fb..685b48ac68 100644 --- a/packages/react-components/src/InputAddress/createItem.tsx +++ b/packages/react-components/src/InputAddress/createItem.tsx @@ -6,9 +6,22 @@ import type { Option } from './types'; import React from 'react'; +import { keyring } from '@polkadot/ui-keyring'; +import { decodeAddress } from '@polkadot/util-crypto'; + import KeyPair from './KeyPair'; -export default function createItem (option: KeyringSectionOption, isUppercase = true): Option { +export default function createItem (option: KeyringSectionOption, isUppercase = true): Option | null { + try { + const u8a = decodeAddress(option.key); + + if (u8a.length !== (keyring.keyring.type === 'ethereum' ? 20 : 32)) { + return null; + } + } catch { + return null; + } + return { ...option, text: ( diff --git a/packages/react-components/src/InputAddress/index.tsx b/packages/react-components/src/InputAddress/index.tsx index 630bb7ab9a..bf421468f9 100644 --- a/packages/react-components/src/InputAddress/index.tsx +++ b/packages/react-components/src/InputAddress/index.tsx @@ -45,7 +45,7 @@ interface Props { } type ExportedType = React.ComponentType & { - createOption: (option: KeyringSectionOption, isUppercase?: boolean) => Option; + createOption: (option: KeyringSectionOption, isUppercase?: boolean) => Option | null; setLastValue: (type: KeyringOption$Type, value: string) => void; }; @@ -80,7 +80,7 @@ function transformToAccountId (value: string): string | null { : accountId; } -function createOption (address: string): Option { +function createOption (address: string): Option | null { let isRecent: boolean | undefined; const pair = keyring.getAccount(address); let name: string | undefined; @@ -177,9 +177,13 @@ class InputAddress extends React.PureComponent { : (lastOption && lastOption.value) ); const actualOptions: Option[] = options - ? dedupe(options.map((o) => createItem(o))) + ? dedupe( + options + .map((o) => createItem(o)) + .filter((o): o is Option => !!o) + ) : isDisabled && actualValue - ? [createOption(actualValue)] + ? [createOption(actualValue)].filter((o): o is Option => !!o) : actualValue ? this.addActual(actualValue) : this.getFiltered(); @@ -225,7 +229,7 @@ class InputAddress extends React.PureComponent { return this.hasValue(actualValue) ? base - : base.concat(createOption(actualValue)); + : base.concat(...[createOption(actualValue)].filter((o): o is Option => !!o)); } private renderLabel = ({ value }: KeyringSectionOption): React.ReactNode => { @@ -377,11 +381,13 @@ const ExportedComponent = withMulti( propName: 'optionsAll', transform: (optionsAll: KeyringOptions): Record => Object.entries(optionsAll).reduce((result: Record, [type, options]): Record => { - result[type] = options.map((option): Option | React.ReactNode => - option.value === null - ? createHeader(option) - : createItem(option) - ); + result[type] = options + .map((option): Option | React.ReactNode | null => + option.value === null + ? createHeader(option) + : createItem(option) + ) + .filter((o): o is Option | React.ReactNode => !!o); return result; }, {}) diff --git a/packages/react-hooks/src/ctx/Keyring.tsx b/packages/react-hooks/src/ctx/Keyring.tsx index a292f9f07e..ae13179625 100644 --- a/packages/react-hooks/src/ctx/Keyring.tsx +++ b/packages/react-hooks/src/ctx/Keyring.tsx @@ -11,6 +11,8 @@ import { keyring } from '@polkadot/ui-keyring'; import { u8aToHex } from '@polkadot/util'; import { decodeAddress } from '@polkadot/util-crypto'; +import { useApi } from '../useApi'; + interface Props { children?: React.ReactNode; } @@ -38,10 +40,24 @@ export const KeyringCtx = React.createContext(EMPTY); * The first check ensures that we never have dupes in the original. The second * ensures that e.g. an address is not also available as an account **/ -function filter (items: string[], others: string[] = []): string[] { +function filter (isEthereum: boolean, items: string[], others: string[] = []): string[] { + const allowedLength = isEthereum + ? 20 + : 32; + return items.reduce((result, a) => { if (!result.includes(a) && !others.includes(a)) { - result.push(a); + try { + const u8a = decodeAddress(a); + + if (u8a.length === allowedLength) { + result.push(a); + } else { + console.warn(`Not adding address ${a}, not in correct format for chain (requires ${allowedLength}-byte publickey)`); + } + } catch { + console.error(a, allowedLength); + } } return result; @@ -75,8 +91,8 @@ function createCheck (items: string[]): Accounts['isAccount'] { !!a && items.includes(a.toString()); } -function extractAccounts (accounts: SubjectInfo = {}): Accounts { - const allAccounts = filter(Object.keys(accounts)); +function extractAccounts (isEthereum: boolean, accounts: SubjectInfo = {}): Accounts { + const allAccounts = filter(isEthereum, Object.keys(accounts)); return { allAccounts, @@ -87,8 +103,8 @@ function extractAccounts (accounts: SubjectInfo = {}): Accounts { }; } -function extractAddresses (addresses: SubjectInfo = {}, accounts: string[]): Addresses { - const allAddresses = filter(Object.keys(addresses), accounts); +function extractAddresses (isEthereum: boolean, addresses: SubjectInfo = {}, accounts: string[]): Addresses { + const allAddresses = filter(isEthereum, Object.keys(addresses), accounts); return { allAddresses, @@ -100,27 +116,34 @@ function extractAddresses (addresses: SubjectInfo = {}, accounts: string[]): Add } export function KeyringCtxRoot ({ children }: Props): React.ReactElement { + const { isApiReady, isEthereum } = useApi(); const [state, setState] = useState(EMPTY); useEffect((): () => void => { - const sub = combineLatest([ - keyring.accounts.subject.pipe( - map((accInfo) => extractAccounts(accInfo)) - ), - keyring.addresses.subject - ]) - .pipe( - map(([accounts, addrInfo]): State => ({ - accounts, - addresses: extractAddresses(addrInfo, accounts.allAccounts) - })) - ) - .subscribe((state) => setState(state)); + let sub: null | { unsubscribe: () => void } = null; + + // Defer keyring injection until the API is ready - we need to have the chain + // info to determine which type of addresses we can use (before subscribing) + if (isApiReady) { + sub = combineLatest([ + keyring.accounts.subject.pipe( + map((accInfo) => extractAccounts(isEthereum, accInfo)) + ), + keyring.addresses.subject + ]) + .pipe( + map(([accounts, addrInfo]): State => ({ + accounts, + addresses: extractAddresses(isEthereum, addrInfo, accounts.allAccounts) + })) + ) + .subscribe((state) => setState(state)); + } return (): void => { sub && sub.unsubscribe(); }; - }, []); + }, [isApiReady, isEthereum]); return ( diff --git a/packages/test-support/src/pages/Page.tsx b/packages/test-support/src/pages/Page.tsx index 3880a3e52f..3fd22abdab 100644 --- a/packages/test-support/src/pages/Page.tsx +++ b/packages/test-support/src/pages/Page.tsx @@ -197,6 +197,10 @@ export abstract class Page { ...api, isReady: Promise.resolve(api) }, + isApiConnected: true, + isApiInitialized: true, + isApiReady: true, + isEthereum: false, systemName: 'substrate' } as unknown as ApiProps;