diff --git a/packages/react-components/src/IdentityIcon/index.tsx b/packages/react-components/src/IdentityIcon/index.tsx index 374c7b83a9..e000857b68 100644 --- a/packages/react-components/src/IdentityIcon/index.tsx +++ b/packages/react-components/src/IdentityIcon/index.tsx @@ -18,6 +18,7 @@ import RoboHash from './RoboHash'; interface Props { className?: string; + forceIconType?: 'ethereum' | 'substrate'; prefix?: IdentityProps['prefix']; size?: number; theme?: IdentityProps['theme'] | 'robohash'; @@ -32,11 +33,12 @@ function isCodec (value?: AccountId | AccountIndex | Address | string | Uint8Arr return !!(value && (value as AccountId).toHuman); } -function IdentityIcon ({ className = '', prefix, size = 24, theme, value }: Props): React.ReactElement { +function IdentityIcon ({ className = '', forceIconType, prefix, size = 24, theme, value }: Props): React.ReactElement { const { isEthereum, specName, systemName } = useApi(); const { t } = useTranslation(); const { queueAction } = useQueue(); const thisTheme = theme || getIdentityTheme(systemName, specName); + const Custom = thisTheme === 'robohash' ? RoboHash : undefined; @@ -58,7 +60,7 @@ function IdentityIcon ({ className = '', prefix, size = 24, theme, value }: Prop onCopy={_onCopy} prefix={prefix} size={size} - theme={isEthereum ? 'ethereum' : thisTheme as 'substrate'} + theme={forceIconType || (isEthereum ? 'ethereum' : thisTheme as 'substrate')} value={isCodec(value) ? value.toString() : value} /> ); diff --git a/packages/react-components/src/InputAddressSimple.tsx b/packages/react-components/src/InputAddressSimple.tsx index 85577133bc..bb0cd7619f 100644 --- a/packages/react-components/src/InputAddressSimple.tsx +++ b/packages/react-components/src/InputAddressSimple.tsx @@ -13,6 +13,7 @@ interface Props { children?: React.ReactNode; className?: string; defaultValue?: string | null; + forceIconType?: 'ethereum' | 'substrate'; help?: React.ReactNode; isDisabled?: boolean; isError?: boolean; @@ -24,7 +25,7 @@ interface Props { onEscape?: () => void; } -function InputAddressSimple ({ autoFocus, children, className = '', defaultValue, help, isDisabled, isError, isFull, label, noConvert, onChange, onEnter, onEscape }: Props): React.ReactElement { +function InputAddressSimple ({ autoFocus, children, className = '', defaultValue, forceIconType, help, isDisabled, isError, isFull, label, noConvert, onChange, onEnter, onEscape }: Props): React.ReactElement { const [address, setAddress] = useState(defaultValue || null); const _onChange = useCallback( @@ -43,7 +44,7 @@ function InputAddressSimple ({ autoFocus, children, className = '', defaultValue ); return ( -
+
diff --git a/packages/react-hooks/src/useAccounts.ts b/packages/react-hooks/src/useAccounts.ts index 24d31e209f..0040ee8dd0 100644 --- a/packages/react-hooks/src/useAccounts.ts +++ b/packages/react-hooks/src/useAccounts.ts @@ -9,9 +9,7 @@ import { KeyringCtx } from './ctx/Keyring'; import { createNamedHook } from './createNamedHook'; function useAccountsImpl (): Accounts { - const { accounts } = useContext(KeyringCtx); - - return accounts; + return useContext(KeyringCtx).accounts; } export const useAccounts = createNamedHook('useAccounts', useAccountsImpl); diff --git a/packages/react-hooks/src/useAddresses.ts b/packages/react-hooks/src/useAddresses.ts index eea701756b..29f310fa31 100644 --- a/packages/react-hooks/src/useAddresses.ts +++ b/packages/react-hooks/src/useAddresses.ts @@ -9,9 +9,7 @@ import { KeyringCtx } from './ctx/Keyring'; import { createNamedHook } from './createNamedHook'; function useAddressesImpl (): Addresses { - const { addresses } = useContext(KeyringCtx); - - return addresses; + return useContext(KeyringCtx).addresses; } export const useAddresses = createNamedHook('useAddresses', useAddressesImpl); diff --git a/packages/react-params/src/Holder.tsx b/packages/react-params/src/Holder.tsx index 15fa7c264f..fc554d45c0 100644 --- a/packages/react-params/src/Holder.tsx +++ b/packages/react-params/src/Holder.tsx @@ -41,10 +41,13 @@ export default React.memo(styled(Holder)` padding-left: 4rem; } - .ui--Param-composite .ui--row > .ui--Labelled > label { - text-transform: none !important; - font: var(--font-mono); - font-size: var(--font-size-label); + .ui--Param-composite .ui--row, + .ui--Param-composite .ui--row .ui--InputAddressSimple { + & > .ui--Labelled > label { + text-transform: none !important; + font: var(--font-mono); + font-size: var(--font-size-label); + } } .ui--row { diff --git a/packages/react-params/src/Param/BasicAccountId20.tsx b/packages/react-params/src/Param/BasicAccountId20.tsx new file mode 100644 index 0000000000..1736f3196b --- /dev/null +++ b/packages/react-params/src/Param/BasicAccountId20.tsx @@ -0,0 +1,19 @@ +// Copyright 2017-2023 @polkadot/react-params authors & contributors +// SPDX-License-Identifier: Apache-2.0 + +import type { Props } from '../types'; + +import React from 'react'; + +import BasicAccountIdBase from './BasicAccountIdBase'; + +function BasicAccountId20 (props: Props): React.ReactElement { + return ( + + ); +} + +export default React.memo(BasicAccountId20); diff --git a/packages/react-params/src/Param/BasicAccountId32.tsx b/packages/react-params/src/Param/BasicAccountId32.tsx new file mode 100644 index 0000000000..5ba577bc03 --- /dev/null +++ b/packages/react-params/src/Param/BasicAccountId32.tsx @@ -0,0 +1,19 @@ +// Copyright 2017-2023 @polkadot/react-params authors & contributors +// SPDX-License-Identifier: Apache-2.0 + +import type { Props } from '../types'; + +import React from 'react'; + +import BasicAccountIdBase from './BasicAccountIdBase'; + +function BasicAccountId32 (props: Props): React.ReactElement { + return ( + + ); +} + +export default React.memo(BasicAccountId32); diff --git a/packages/react-params/src/Param/BasicAccountIdBase.tsx b/packages/react-params/src/Param/BasicAccountIdBase.tsx new file mode 100644 index 0000000000..f59d5e5dba --- /dev/null +++ b/packages/react-params/src/Param/BasicAccountIdBase.tsx @@ -0,0 +1,70 @@ +// Copyright 2017-2023 @polkadot/react-params authors & contributors +// SPDX-License-Identifier: Apache-2.0 + +import type { Props as BaseProps } from '../types'; + +import React, { useCallback, useState } from 'react'; + +import { InputAddressSimple } from '@polkadot/react-components'; +import { useApi } from '@polkadot/react-hooks'; +import { isEthereumAddress, validateAddress } from '@polkadot/util-crypto'; + +import Bare from './Bare'; + +interface Props extends BaseProps { + bytesLength: 20 | 32; +} + +function isValidAddress (value: string | null | undefined, isEthereum: boolean): boolean { + if (value) { + try { + if (isEthereum) { + return isEthereumAddress(value); + } else { + return validateAddress(value); + } + + return true; + } catch (err) { + console.error(err); + } + } + + return false; +} + +function BasicAccountIdBase (props: Props): React.ReactElement { + const { isEthereum } = useApi(); + const { bytesLength, className = '', defaultValue: { value }, isDisabled, isError, isInOption, label, onChange, withLabel } = props; + const [defaultValue] = useState(() => (value as string)?.toString()); + + const _onChange = useCallback( + (value?: string | null) => + onChange && onChange({ + isValid: isValidAddress(value, bytesLength === 20), + value + }), + [bytesLength, onChange] + ); + + return ( + + + + ); +} + +export default React.memo(BasicAccountIdBase); diff --git a/packages/react-params/src/Param/findComponent.ts b/packages/react-params/src/Param/findComponent.ts index f55ceee05d..fca27739bf 100644 --- a/packages/react-params/src/Param/findComponent.ts +++ b/packages/react-params/src/Param/findComponent.ts @@ -11,6 +11,8 @@ import { isBn } from '@polkadot/util'; import Account from './Account'; import Amount from './Amount'; import Balance from './Balance'; +import AccountId20 from './BasicAccountId20'; +import AccountId32 from './BasicAccountId32'; import Bool from './Bool'; import Bytes from './Bytes'; import Call from './Call'; @@ -43,12 +45,12 @@ interface TypeToComponent { t: string[]; } -const SPECIAL_TYPES = ['AccountId', 'AccountId32', 'AccountIndex', 'Address', 'Balance', 'BalanceOf', 'Vec']; +const SPECIAL_TYPES = ['AccountId', 'AccountId20', 'AccountId32', 'AccountIndex', 'Address', 'Balance', 'BalanceOf', 'Vec']; const DISPATCH_ERROR = ['DispatchError', 'SpRuntimeDispatchError']; const componentDef: TypeToComponent[] = [ - { c: Account, t: ['AccountId', 'AccountId32', 'Address', 'LookupSource', 'MultiAddress'] }, + { c: Account, t: ['AccountId', 'Address', 'LookupSource', 'MultiAddress'] }, { c: Amount, t: ['AccountIndex', 'i8', 'i16', 'i32', 'i64', 'i128', 'u8', 'u16', 'u32', 'u64', 'u128', 'u256'] }, { c: Balance, t: ['Amount', 'Balance', 'BalanceOf'] }, { c: Bool, t: ['bool'] }, @@ -142,6 +144,20 @@ function fromDef ({ displayName, info, lookupName, sub, type }: TypeDef): string } export default function findComponent (registry: Registry, def: TypeDef, overrides: ComponentMap = {}): React.ComponentType { + // Explicit/special handling for Account20/32 types where they don't match + // the actual chain we are connected to + if (['AccountId20', 'AccountId32'].includes(def.type)) { + const defType = `AccountId${registry.createType('AccountId').length}`; + + if (def.type !== defType) { + if (def.type === 'AccountId20') { + return AccountId20; + } else { + return AccountId32; + } + } + } + const findOne = (type?: string): React.ComponentType | null => type ? overrides[type] || components[type]