Support ethereum accounts (#560)
* refactor restoration * support eth icon * hack with as any * remove hack * bump keyring * recode and correctly import Json * create and restore by seed * lint * glitch * cleanup * remove restoration and creation * fix yarn.lock * round icons Co-authored-by: Github Actions <action@github.com> Co-authored-by: Jaco Greeff <jacogr@gmail.com>
This commit is contained in:
parent
e98ce05f6f
commit
3c43132cf0
@ -26,9 +26,10 @@ const SEED_LENGTHS = [12, 15, 18, 21, 24];
|
|||||||
const registry = new TypeRegistry();
|
const registry = new TypeRegistry();
|
||||||
|
|
||||||
function transformAccounts (accounts: SubjectInfo): AccountJson[] {
|
function transformAccounts (accounts: SubjectInfo): AccountJson[] {
|
||||||
return Object.values(accounts).map(({ json: { address, meta } }): AccountJson => ({
|
return Object.values(accounts).map(({ json: { address, meta }, type }): AccountJson => ({
|
||||||
address,
|
address,
|
||||||
...meta
|
...meta,
|
||||||
|
type
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -256,12 +257,13 @@ export default class Extension {
|
|||||||
|
|
||||||
private jsonGetAccountInfo (json: KeyringPair$Json): ResponseJsonGetAccountInfo {
|
private jsonGetAccountInfo (json: KeyringPair$Json): ResponseJsonGetAccountInfo {
|
||||||
try {
|
try {
|
||||||
const pair = keyring.createFromJson(json);
|
const { address, meta: { genesisHash, name }, type } = keyring.createFromJson(json);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
address: pair.address,
|
address,
|
||||||
genesisHash: pair.meta.genesisHash,
|
genesisHash,
|
||||||
name: pair.meta.name
|
name,
|
||||||
|
type
|
||||||
} as ResponseJsonGetAccountInfo;
|
} as ResponseJsonGetAccountInfo;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(e);
|
console.error(e);
|
||||||
|
|||||||
@ -36,6 +36,7 @@ export interface AccountJson extends KeyringPair$Meta {
|
|||||||
name?: string;
|
name?: string;
|
||||||
parentAddress?: string;
|
parentAddress?: string;
|
||||||
suri?: string;
|
suri?: string;
|
||||||
|
type?: KeypairType;
|
||||||
whenCreated?: number;
|
whenCreated?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -367,6 +368,7 @@ export interface ResponseJsonGetAccountInfo {
|
|||||||
address: string;
|
address: string;
|
||||||
name: string;
|
name: string;
|
||||||
genesisHash: string;
|
genesisHash: string;
|
||||||
|
type: KeypairType;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ResponseAuthorizeList {
|
export interface ResponseAuthorizeList {
|
||||||
|
|||||||
@ -23,7 +23,7 @@ interface EditState {
|
|||||||
toggleActions: number;
|
toggleActions: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
function Account ({ address, className, genesisHash, isExternal, isHidden, parentName, suri }: Props): React.ReactElement<Props> {
|
function Account ({ address, className, genesisHash, isExternal, isHidden, parentName, suri, type }: Props): React.ReactElement<Props> {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const onAction = useContext(ActionContext);
|
const onAction = useContext(ActionContext);
|
||||||
const [{ isEditing, toggleActions }, setEditing] = useState<EditState>({ isEditing: false, toggleActions: 0 });
|
const [{ isEditing, toggleActions }, setEditing] = useState<EditState>({ isEditing: false, toggleActions: 0 });
|
||||||
@ -62,7 +62,7 @@ function Account ({ address, className, genesisHash, isExternal, isHidden, paren
|
|||||||
>
|
>
|
||||||
{t<string>('Rename')}
|
{t<string>('Rename')}
|
||||||
</Link>
|
</Link>
|
||||||
{!isExternal && (
|
{!isExternal && type !== 'ethereum' && (
|
||||||
<Link
|
<Link
|
||||||
className='menuItem'
|
className='menuItem'
|
||||||
to={`/account/derive/${address}/locked`}
|
to={`/account/derive/${address}/locked`}
|
||||||
@ -98,7 +98,7 @@ function Account ({ address, className, genesisHash, isExternal, isHidden, paren
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
), [_onChangeGenesis, _toggleEdit, address, genesisHash, isExternal, t]);
|
), [_onChangeGenesis, _toggleEdit, address, genesisHash, isExternal, t, type]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={className}>
|
<div className={className}>
|
||||||
|
|||||||
@ -7,6 +7,7 @@ import { ActionContext, Address, Loading } from '../../components';
|
|||||||
import useTranslation from '../../hooks/useTranslation';
|
import useTranslation from '../../hooks/useTranslation';
|
||||||
import { createAccountSuri, createSeed } from '../../messaging';
|
import { createAccountSuri, createSeed } from '../../messaging';
|
||||||
import { HeaderWithSteps } from '../../partials';
|
import { HeaderWithSteps } from '../../partials';
|
||||||
|
import { DEFAULT_TYPE } from '../../util/defaultType';
|
||||||
import AccountName from './AccountName';
|
import AccountName from './AccountName';
|
||||||
import Mnemonic from './Mnemonic';
|
import Mnemonic from './Mnemonic';
|
||||||
|
|
||||||
@ -16,13 +17,14 @@ export default function CreateAccount (): React.ReactElement {
|
|||||||
const [isBusy, setIsBusy] = useState(false);
|
const [isBusy, setIsBusy] = useState(false);
|
||||||
const [step, setStep] = useState(1);
|
const [step, setStep] = useState(1);
|
||||||
const [account, setAccount] = useState<null | { address: string; seed: string }>(null);
|
const [account, setAccount] = useState<null | { address: string; seed: string }>(null);
|
||||||
|
const type = DEFAULT_TYPE;
|
||||||
const [name, setName] = useState('');
|
const [name, setName] = useState('');
|
||||||
|
|
||||||
useEffect((): void => {
|
useEffect((): void => {
|
||||||
createSeed()
|
createSeed(undefined, type)
|
||||||
.then(setAccount)
|
.then(setAccount)
|
||||||
.catch((error: Error) => console.error(error));
|
.catch((error: Error) => console.error(error));
|
||||||
}, []);
|
}, [type]);
|
||||||
|
|
||||||
// FIXME Duplicated between here and Import.tsx
|
// FIXME Duplicated between here and Import.tsx
|
||||||
const _onCreate = useCallback(
|
const _onCreate = useCallback(
|
||||||
|
|||||||
@ -1,18 +1,19 @@
|
|||||||
// Copyright 2019-2020 @polkadot/extension-ui authors & contributors
|
// Copyright 2019-2020 @polkadot/extension-ui authors & contributors
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
import type { ResponseJsonGetAccountInfo } from '@polkadot/extension-base/background/types';
|
||||||
import type { KeyringPair$Json } from '@polkadot/keyring/types';
|
import type { KeyringPair$Json } from '@polkadot/keyring/types';
|
||||||
|
|
||||||
import React, { useCallback, useContext, useEffect, useState } from 'react';
|
import React, { useCallback, useContext, useEffect, useState } from 'react';
|
||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
|
|
||||||
import { ResponseJsonGetAccountInfo } from '@polkadot/extension-base/background/types';
|
|
||||||
import { u8aToString } from '@polkadot/util';
|
import { u8aToString } from '@polkadot/util';
|
||||||
|
|
||||||
import { AccountContext, ActionContext, Address, Button, InputFileWithLabel, InputWithLabel, Warning } from '../components';
|
import { AccountContext, ActionContext, Address, Button, InputFileWithLabel, InputWithLabel, Warning } from '../components';
|
||||||
import useTranslation from '../hooks/useTranslation';
|
import useTranslation from '../hooks/useTranslation';
|
||||||
import { jsonGetAccountInfo, jsonRestore } from '../messaging';
|
import { jsonGetAccountInfo, jsonRestore } from '../messaging';
|
||||||
import { Header } from '../partials';
|
import { Header } from '../partials';
|
||||||
|
import { DEFAULT_TYPE } from '../util/defaultType';
|
||||||
|
|
||||||
const acceptedFormats = ['application/json', 'text/plain'].join(', ');
|
const acceptedFormats = ['application/json', 'text/plain'].join(', ');
|
||||||
|
|
||||||
@ -25,7 +26,7 @@ function Upload ({ className } : Props): React.ReactElement {
|
|||||||
const { accounts } = useContext(AccountContext);
|
const { accounts } = useContext(AccountContext);
|
||||||
const onAction = useContext(ActionContext);
|
const onAction = useContext(ActionContext);
|
||||||
const [isBusy, setIsBusy] = useState(false);
|
const [isBusy, setIsBusy] = useState(false);
|
||||||
const [{ address, genesisHash, name }, setAccountInfo] = useState<ResponseJsonGetAccountInfo>({ address: '', genesisHash: '', name: '' });
|
const [{ address, genesisHash, name, type }, setAccountInfo] = useState<ResponseJsonGetAccountInfo>({ address: '', genesisHash: '', name: '', type: DEFAULT_TYPE });
|
||||||
const [password, setPassword] = useState<string>('');
|
const [password, setPassword] = useState<string>('');
|
||||||
const [isFileError, setFileError] = useState(false);
|
const [isFileError, setFileError] = useState(false);
|
||||||
const [isPasswordError, setIsPasswordError] = useState(false);
|
const [isPasswordError, setIsPasswordError] = useState(false);
|
||||||
@ -97,6 +98,7 @@ function Upload ({ className } : Props): React.ReactElement {
|
|||||||
address={address}
|
address={address}
|
||||||
genesisHash={genesisHash}
|
genesisHash={genesisHash}
|
||||||
name={name}
|
name={name}
|
||||||
|
type={type}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<InputFileWithLabel
|
<InputFileWithLabel
|
||||||
|
|||||||
@ -3,7 +3,9 @@
|
|||||||
|
|
||||||
import type { AccountJson, AccountWithChildren } from '@polkadot/extension-base/background/types';
|
import type { AccountJson, AccountWithChildren } from '@polkadot/extension-base/background/types';
|
||||||
import type { Chain } from '@polkadot/extension-chains/types';
|
import type { Chain } from '@polkadot/extension-chains/types';
|
||||||
|
import type { IconTheme } from '@polkadot/react-identicon/types';
|
||||||
import type { SettingsStruct } from '@polkadot/ui-settings/types';
|
import type { SettingsStruct } from '@polkadot/ui-settings/types';
|
||||||
|
import type { KeypairType } from '@polkadot/util-crypto/types';
|
||||||
import type { ThemeProps } from '../types';
|
import type { ThemeProps } from '../types';
|
||||||
|
|
||||||
import { faCopy, faEye, faEyeSlash } from '@fortawesome/free-regular-svg-icons';
|
import { faCopy, faEye, faEyeSlash } from '@fortawesome/free-regular-svg-icons';
|
||||||
@ -22,6 +24,7 @@ import useOutsideClick from '../hooks/useOutsideClick';
|
|||||||
import useToast from '../hooks/useToast';
|
import useToast from '../hooks/useToast';
|
||||||
import useTranslation from '../hooks/useTranslation';
|
import useTranslation from '../hooks/useTranslation';
|
||||||
import { showAccount } from '../messaging';
|
import { showAccount } from '../messaging';
|
||||||
|
import { DEFAULT_TYPE } from '../util/defaultType';
|
||||||
import { AccountContext, SettingsContext } from './contexts';
|
import { AccountContext, SettingsContext } from './contexts';
|
||||||
import Identicon from './Identicon';
|
import Identicon from './Identicon';
|
||||||
import Menu from './Menu';
|
import Menu from './Menu';
|
||||||
@ -39,17 +42,19 @@ interface Props {
|
|||||||
parentName?: string | null;
|
parentName?: string | null;
|
||||||
suri?: string;
|
suri?: string;
|
||||||
toggleActions?: number;
|
toggleActions?: number;
|
||||||
|
type?: KeypairType;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface Recoded {
|
interface Recoded {
|
||||||
account: AccountJson | null;
|
account: AccountJson | null;
|
||||||
formatted: string | null;
|
formatted: string | null;
|
||||||
genesisHash?: string | null;
|
genesisHash?: string | null;
|
||||||
prefix: number;
|
prefix?: number;
|
||||||
|
type: KeypairType;
|
||||||
}
|
}
|
||||||
|
|
||||||
// find an account in our list
|
// find an account in our list
|
||||||
function findAccount (accounts: AccountJson[], publicKey: Uint8Array): AccountJson | null {
|
function findSubstrateAccount (accounts: AccountJson[], publicKey: Uint8Array): AccountJson | null {
|
||||||
const pkStr = publicKey.toString();
|
const pkStr = publicKey.toString();
|
||||||
|
|
||||||
return accounts.find(({ address }): boolean =>
|
return accounts.find(({ address }): boolean =>
|
||||||
@ -57,13 +62,20 @@ function findAccount (accounts: AccountJson[], publicKey: Uint8Array): AccountJs
|
|||||||
) || null;
|
) || null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// find an account in our list
|
||||||
|
function findAccountByAddress (accounts: AccountJson[], _address: string): AccountJson | null {
|
||||||
|
return accounts.find(({ address }): boolean =>
|
||||||
|
address === _address
|
||||||
|
) || null;
|
||||||
|
}
|
||||||
|
|
||||||
// recodes an supplied address using the prefix/genesisHash, include the actual saved account & chain
|
// recodes an supplied address using the prefix/genesisHash, include the actual saved account & chain
|
||||||
function recodeAddress (address: string, accounts: AccountWithChildren[], chain: Chain | null, settings: SettingsStruct): Recoded {
|
function recodeAddress (address: string, accounts: AccountWithChildren[], chain: Chain | null, settings: SettingsStruct): Recoded {
|
||||||
// decode and create a shortcut for the encoded address
|
// decode and create a shortcut for the encoded address
|
||||||
const publicKey = decodeAddress(address);
|
const publicKey = decodeAddress(address);
|
||||||
|
|
||||||
// find our account using the actual publicKey, and then find the associated chain
|
// find our account using the actual publicKey, and then find the associated chain
|
||||||
const account = findAccount(accounts, publicKey);
|
const account = findSubstrateAccount(accounts, publicKey);
|
||||||
const prefix = chain ? chain.ss58Format : (settings.prefix === -1 ? 42 : settings.prefix);
|
const prefix = chain ? chain.ss58Format : (settings.prefix === -1 ? 42 : settings.prefix);
|
||||||
|
|
||||||
// always allow the actual settings to override the display
|
// always allow the actual settings to override the display
|
||||||
@ -71,19 +83,20 @@ function recodeAddress (address: string, accounts: AccountWithChildren[], chain:
|
|||||||
account,
|
account,
|
||||||
formatted: encodeAddress(publicKey, prefix),
|
formatted: encodeAddress(publicKey, prefix),
|
||||||
genesisHash: account?.genesisHash,
|
genesisHash: account?.genesisHash,
|
||||||
prefix
|
prefix,
|
||||||
|
type: account?.type || DEFAULT_TYPE
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const ACCOUNTS_SCREEN_HEIGHT = 550;
|
const ACCOUNTS_SCREEN_HEIGHT = 550;
|
||||||
|
const defaultRecoded = { account: null, formatted: null, prefix: 42, type: DEFAULT_TYPE };
|
||||||
|
|
||||||
function Address ({ actions, address, children, className, genesisHash, isExternal, isHidden, name, parentName, suri, toggleActions }: Props): React.ReactElement<Props> {
|
function Address ({ actions, address, children, className, genesisHash, isExternal, isHidden, name, parentName, suri, toggleActions, type: givenType }: Props): React.ReactElement<Props> {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { accounts } = useContext(AccountContext);
|
const { accounts } = useContext(AccountContext);
|
||||||
const settings = useContext(SettingsContext);
|
const settings = useContext(SettingsContext);
|
||||||
const [{ account, formatted, genesisHash: recodedGenesis, prefix }, setRecoded] = useState<Recoded>({ account: null, formatted: null, prefix: 42 });
|
const [{ account, formatted, genesisHash: recodedGenesis, prefix, type }, setRecoded] = useState<Recoded>(defaultRecoded);
|
||||||
const chain = useMetadata(genesisHash || recodedGenesis, true);
|
const chain = useMetadata(genesisHash || recodedGenesis, true);
|
||||||
|
|
||||||
const [showActionsMenu, setShowActionsMenu] = useState(false);
|
const [showActionsMenu, setShowActionsMenu] = useState(false);
|
||||||
const [moveMenuUp, setIsMovedMenu] = useState(false);
|
const [moveMenuUp, setIsMovedMenu] = useState(false);
|
||||||
const actionsRef = useRef<HTMLDivElement>(null);
|
const actionsRef = useRef<HTMLDivElement>(null);
|
||||||
@ -92,10 +105,17 @@ function Address ({ actions, address, children, className, genesisHash, isExtern
|
|||||||
useOutsideClick(actionsRef, () => (showActionsMenu && setShowActionsMenu(!showActionsMenu)));
|
useOutsideClick(actionsRef, () => (showActionsMenu && setShowActionsMenu(!showActionsMenu)));
|
||||||
|
|
||||||
useEffect((): void => {
|
useEffect((): void => {
|
||||||
address && setRecoded(
|
if (!address) {
|
||||||
recodeAddress(address, accounts, chain, settings)
|
return;
|
||||||
);
|
}
|
||||||
}, [accounts, address, chain, settings]);
|
|
||||||
|
const accountByAddress = findAccountByAddress(accounts, address);
|
||||||
|
const recoded = (accountByAddress?.type === 'ethereum' || (!accountByAddress && givenType === 'ethereum'))
|
||||||
|
? { account: accountByAddress, formatted: address, type: 'ethereum' } as Recoded
|
||||||
|
: recodeAddress(address, accounts, chain, settings);
|
||||||
|
|
||||||
|
setRecoded(recoded || defaultRecoded);
|
||||||
|
}, [accounts, address, chain, givenType, settings]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!showActionsMenu) {
|
if (!showActionsMenu) {
|
||||||
@ -113,7 +133,14 @@ function Address ({ actions, address, children, className, genesisHash, isExtern
|
|||||||
setShowActionsMenu(false);
|
setShowActionsMenu(false);
|
||||||
}, [toggleActions]);
|
}, [toggleActions]);
|
||||||
|
|
||||||
const theme = ((chain && chain.icon) || 'polkadot') as 'polkadot';
|
const theme = (
|
||||||
|
chain?.icon
|
||||||
|
? chain.icon
|
||||||
|
: type === 'ethereum'
|
||||||
|
? 'ethereum'
|
||||||
|
: 'polkadot'
|
||||||
|
) as IconTheme;
|
||||||
|
|
||||||
const _onClick = useCallback((): void => setShowActionsMenu(!showActionsMenu), [showActionsMenu]);
|
const _onClick = useCallback((): void => setShowActionsMenu(!showActionsMenu), [showActionsMenu]);
|
||||||
const _onCopy = useCallback((): void => show(t('Copied')), [show, t]);
|
const _onCopy = useCallback((): void => show(t('Copied')), [show, t]);
|
||||||
const _toggleVisibility = useCallback(
|
const _toggleVisibility = useCallback(
|
||||||
@ -202,7 +229,7 @@ function Address ({ actions, address, children, className, genesisHash, isExtern
|
|||||||
className='fullAddress'
|
className='fullAddress'
|
||||||
data-field='address'
|
data-field='address'
|
||||||
>
|
>
|
||||||
{formatted || t('<unknown>')}
|
{formatted || address || t('<unknown>')}
|
||||||
</div>
|
</div>
|
||||||
<CopyToClipboard text={(formatted && formatted) || ''} >
|
<CopyToClipboard text={(formatted && formatted) || ''} >
|
||||||
<FontAwesomeIcon
|
<FontAwesomeIcon
|
||||||
@ -333,6 +360,12 @@ export default styled(Address)(({ theme }: ThemeProps) => `
|
|||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
img {
|
||||||
|
max-width: 50px;
|
||||||
|
max-height: 50px;
|
||||||
|
border-radius: 50%;
|
||||||
|
}
|
||||||
|
|
||||||
.name {
|
.name {
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
line-height: 22px;
|
line-height: 22px;
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
// Copyright 2019-2020 @polkadot/extension-ui authors & contributors
|
// Copyright 2019-2020 @polkadot/extension-ui authors & contributors
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
import type { IconTheme } from '@polkadot/react-identicon/types';
|
||||||
import type { ThemeProps } from '../types';
|
import type { ThemeProps } from '../types';
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
@ -10,7 +11,7 @@ import Icon from '@polkadot/react-identicon';
|
|||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
className?: string;
|
className?: string;
|
||||||
iconTheme?: 'beachball' | 'empty' | 'jdenticon' | 'polkadot' | 'substrate';
|
iconTheme?: IconTheme;
|
||||||
isExternal?: boolean | null;
|
isExternal?: boolean | null;
|
||||||
onCopy?: () => void;
|
onCopy?: () => void;
|
||||||
prefix?: number;
|
prefix?: number;
|
||||||
|
|||||||
6
packages/extension-ui/src/util/defaultType.ts
Normal file
6
packages/extension-ui/src/util/defaultType.ts
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
// Copyright 2019-2020 @polkadot/extension-ui authors & contributors
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
import { KeypairType } from '@polkadot/util-crypto/types';
|
||||||
|
|
||||||
|
export const DEFAULT_TYPE = 'sr25519' as KeypairType;
|
||||||
Loading…
Reference in New Issue
Block a user