Correctly use chain type in account creation (#882)

* Correctly use chain type in account creation

* CHANGELOG
This commit is contained in:
Jaco 2021-11-08 11:09:50 +01:00 committed by GitHub
parent 822b2dc6cd
commit cf503c6662
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 153 additions and 66 deletions

View File

@ -1,5 +1,21 @@
# CHANGELOG
## 0.41.1 Nov 8, 2021
Contributed:
- Add search functionality (Thanks to https://github.com/Tbaut)
Changes:
- Detect Ascii bytes (& display) when signing
- Correctly detect and create Ethereum-compatible chain accounts
- Ensure site authorization toggle is saved
- Optimize metadata conversion process
- Bump `@polkadot/util` to 7.8.2
- Bump `@polkadot/api` to 6.7.1
## 0.40.4 Oct 25, 2021
**Important** Not published to the stores, aligns with latest released packages.

View File

@ -5,6 +5,7 @@ import type { MetadataDef } from '@polkadot/extension-inject/types';
import type { KeyringPair, KeyringPair$Json, KeyringPair$Meta } from '@polkadot/keyring/types';
import type { SignerPayloadJSON, SignerPayloadRaw } from '@polkadot/types/types';
import type { SubjectInfo } from '@polkadot/ui-keyring/observable/types';
import type { KeypairType } from '@polkadot/util-crypto/types';
import type { AccountJson, AllowedPath, AuthorizeRequest, MessageTypes, MetadataRequest, RequestAccountBatchExport, RequestAccountChangePassword, RequestAccountCreateExternal, RequestAccountCreateHardware, RequestAccountCreateSuri, RequestAccountEdit, RequestAccountExport, RequestAccountForget, RequestAccountShow, RequestAccountTie, RequestAccountValidate, RequestAuthorizeApprove, RequestAuthorizeReject, RequestBatchRestore, RequestDeriveCreate, RequestDeriveValidate, RequestJsonRestore, RequestMetadataApprove, RequestMetadataReject, RequestSeedCreate, RequestSeedValidate, RequestSigningApprovePassword, RequestSigningApproveSignature, RequestSigningCancel, RequestSigningIsLocked, RequestTypes, ResponseAccountExport, ResponseAccountsExport, ResponseAuthorizeList, ResponseDeriveValidate, ResponseJsonGetAccountInfo, ResponseSeedCreate, ResponseSeedValidate, ResponseSigningIsLocked, ResponseType, SigningRequest } from '../types';
import { ALLOWED_PATH, PASSWORD_EXPIRY_MS } from '@polkadot/extension-base/defaults';
@ -22,10 +23,17 @@ type CachedUnlocks = Record<string, number>;
const SEED_DEFAULT_LENGTH = 12;
const SEED_LENGTHS = [12, 15, 18, 21, 24];
const ETH_DERIVE_DEFAULT = "/m/44'/60'/0'/0/0";
// a global registry to use internally
const registry = new TypeRegistry();
function getSuri (seed: string, type?: KeypairType): string {
return type === 'ethereum'
? `${seed}${ETH_DERIVE_DEFAULT}`
: seed;
}
function transformAccounts (accounts: SubjectInfo): AccountJson[] {
return Object.values(accounts).map(({ json: { address, meta }, type }): AccountJson => ({
address,
@ -61,7 +69,7 @@ export default class Extension {
}
private accountsCreateSuri ({ genesisHash, name, password, suri, type }: RequestAccountCreateSuri): boolean {
keyring.addUri(suri, password, { genesisHash, name }, type);
keyring.addUri(getSuri(suri, type), password, { genesisHash, name }, type);
return true;
}
@ -300,7 +308,7 @@ export default class Extension {
const seed = mnemonicGenerate(length);
return {
address: keyring.createFromUri(seed, {}, type).address,
address: keyring.createFromUri(getSuri(seed, type), {}, type).address,
seed
};
}
@ -317,7 +325,7 @@ export default class Extension {
}
return {
address: keyring.createFromUri(suri, {}, type).address,
address: keyring.createFromUri(getSuri(suri, type), {}, type).address,
suri
};
}

View File

@ -7,8 +7,9 @@ import styled from 'styled-components';
import { ActionContext, Address, Dropdown, Loading } from '../../components';
import AccountNamePasswordCreation from '../../components/AccountNamePasswordCreation';
import useGenesisHashOptions from '../../hooks/useGenesisHashOptions';
import useMetadata from '../../hooks/useMetadata';
import useTranslation from '../../hooks/useTranslation';
import { createAccountSuri, createSeed } from '../../messaging';
import { createAccountSuri, createSeed, validateSeed } from '../../messaging';
import { HeaderWithSteps } from '../../partials';
import { DEFAULT_TYPE } from '../../util/defaultType';
import Mnemonic from './Mnemonic';
@ -22,25 +23,44 @@ function CreateAccount ({ className }: Props): React.ReactElement {
const onAction = useContext(ActionContext);
const [isBusy, setIsBusy] = useState(false);
const [step, setStep] = useState(1);
const [account, setAccount] = useState<null | { address: string; seed: string }>(null);
const [address, setAddress] = useState<null | string>(null);
const [seed, setSeed] = useState<null | string>(null);
const [type, setType] = useState(DEFAULT_TYPE);
const [name, setName] = useState('');
const options = useGenesisHashOptions();
const [genesisHash, setGenesis] = useState('');
const chain = useMetadata(genesisHash, true);
useEffect((): void => {
createSeed(undefined, type)
.then(setAccount)
.catch((error: Error) => console.error(error));
}, [type]);
createSeed(undefined)
.then(({ address, seed }): void => {
setAddress(address);
setSeed(seed);
})
.catch(console.error);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
useEffect((): void => {
if (seed) {
const type = chain && chain.definition.chainType === 'ethereum'
? 'ethereum'
: DEFAULT_TYPE;
setType(type);
validateSeed(seed, type)
.then(({ address }) => setAddress(address))
.catch(console.error);
}
}, [seed, chain]);
const _onCreate = useCallback(
(name: string, password: string): void => {
// this should always be the case
if (name && password && account) {
if (name && password && seed) {
setIsBusy(true);
createAccountSuri(name, password, account.seed, type, genesisHash)
createAccountSuri(name, password, seed, type, genesisHash)
.then(() => onAction('/'))
.catch((error: Error): void => {
setIsBusy(false);
@ -48,23 +68,23 @@ function CreateAccount ({ className }: Props): React.ReactElement {
});
}
},
[account, genesisHash, onAction, type]
[genesisHash, onAction, seed, type]
);
const _onNextStep = useCallback(() => setStep((step) => step + 1), []);
const _onPreviousStep = useCallback(() => setStep((step) => step - 1), []);
const _onNextStep = useCallback(
() => setStep((step) => step + 1),
[]
);
const _onChangeNetwork = useCallback((newGenesisHash: string) => {
const chain = options.find(({ value }) => {
return newGenesisHash === value;
});
const _onPreviousStep = useCallback(
() => setStep((step) => step - 1),
[]
);
if (chain?.text === 'Moonbase Alpha') { // TODO: use list
setType('ethereum');
}
setGenesis(newGenesisHash);
}, [options]);
const _onChangeNetwork = useCallback(
(newGenesisHash: string) => setGenesis(newGenesisHash),
[]
);
return (
<>
@ -75,17 +95,17 @@ function CreateAccount ({ className }: Props): React.ReactElement {
<Loading>
<div>
<Address
address={account?.address}
address={address}
genesisHash={genesisHash}
name={name}
/>
</div>
{account && (
{seed && (
step === 1
? (
<Mnemonic
onNextStep={_onNextStep}
seed={account.seed}
seed={seed}
/>
)
: (

View File

@ -1,6 +1,7 @@
// Copyright 2019-2021 @polkadot/extension-ui authors & contributors
// SPDX-License-Identifier: Apache-2.0
import type { KeypairType } from '@polkadot/util-crypto/types';
import type { ThemeProps } from '../../types';
import type { AccountInfo } from '.';
@ -10,6 +11,7 @@ import React, { useCallback, useEffect, useState } from 'react';
import styled from 'styled-components';
import { validateSeed } from '@polkadot/extension-ui/messaging';
import { objectSpread } from '@polkadot/util';
import { ButtonArea, Dropdown, InputWithLabel, NextStepButton, TextAreaWithLabel, VerticalSpace, Warning } from '../../components';
import useGenesisHashOptions from '../../hooks/useGenesisHashOptions';
@ -19,9 +21,10 @@ interface Props {
className?: string;
onNextStep: () => void;
onAccountChange: (account: AccountInfo | null) => void;
type: KeypairType;
}
function SeedAndPath ({ className, onAccountChange, onNextStep }: Props): React.ReactElement {
function SeedAndPath ({ className, onAccountChange, onNextStep, type }: Props): React.ReactElement {
const { t } = useTranslation();
const genesisOptions = useGenesisHashOptions();
const [address, setAddress] = useState('');
@ -42,22 +45,23 @@ function SeedAndPath ({ className, onAccountChange, onNextStep }: Props): React.
const suri = `${seed || ''}${path || ''}`;
validateSeed(suri).then((validatedAccount) => {
setError('');
setAddress(validatedAccount.address);
// a spread operator here breaks tests, using Object.assign as a workaround
const newAccount: AccountInfo = Object.assign(validatedAccount, { genesis });
onAccountChange(newAccount);
}).catch(() => {
setAddress('');
onAccountChange(null);
setError(path
? t<string>('Invalid mnemonic seed or derivation path')
: t<string>('Invalid mnemonic seed')
);
});
}, [t, seed, path, onAccountChange, genesis]);
validateSeed(suri, type)
.then((validatedAccount) => {
setError('');
setAddress(validatedAccount.address);
onAccountChange(
objectSpread<AccountInfo>({}, validatedAccount, { genesis, type })
);
})
.catch(() => {
setAddress('');
onAccountChange(null);
setError(path
? t<string>('Invalid mnemonic seed or derivation path')
: t<string>('Invalid mnemonic seed')
);
});
}, [t, genesis, seed, path, onAccountChange, type]);
const _onToggleAdvanced = useCallback(() => {
setAdvances(!advanced);

View File

@ -5,9 +5,11 @@ import React, { useCallback, useContext, useEffect, useState } from 'react';
import { AccountContext, ActionContext, Address } from '../../components';
import AccountNamePasswordCreation from '../../components/AccountNamePasswordCreation';
import useMetadata from '../../hooks/useMetadata';
import useTranslation from '../../hooks/useTranslation';
import { createAccountSuri } from '../../messaging';
import { HeaderWithSteps } from '../../partials';
import { DEFAULT_TYPE } from '../../util/defaultType';
import SeedAndPath from './SeedAndPath';
export interface AccountInfo {
@ -24,27 +26,44 @@ function ImportSeed (): React.ReactElement {
const [account, setAccount] = useState<AccountInfo | null>(null);
const [name, setName] = useState<string | null>(null);
const [step1, setStep1] = useState(true);
const [type, setType] = useState(DEFAULT_TYPE);
const chain = useMetadata(account && account.genesis, true);
useEffect((): void => {
!accounts.length && onAction();
}, [accounts, onAction]);
useEffect((): void => {
setType(
chain && chain.definition.chainType === 'ethereum'
? 'ethereum'
: DEFAULT_TYPE
);
}, [chain]);
const _onCreate = useCallback((name: string, password: string): void => {
// this should always be the case
if (name && password && account) {
setIsBusy(true);
createAccountSuri(name, password, account.suri, undefined, account.genesis)
createAccountSuri(name, password, account.suri, type, account.genesis)
.then(() => onAction('/'))
.catch((error): void => {
setIsBusy(false);
console.error(error);
});
}
}, [account, onAction]);
}, [account, onAction, type]);
const _onNextStep = useCallback(() => { setStep1(false); }, []);
const _onBackClick = useCallback(() => { setStep1(true); }, []);
const _onNextStep = useCallback(
() => setStep1(false),
[]
);
const _onBackClick = useCallback(
() => setStep1(true),
[]
);
return (
<>
@ -64,6 +83,7 @@ function ImportSeed (): React.ReactElement {
<SeedAndPath
onAccountChange={setAccount}
onNextStep={_onNextStep}
type={type}
/>
)
: (

View File

@ -83,7 +83,9 @@ function recodeAddress (address: string, accounts: AccountWithChildren[], chain:
// always allow the actual settings to override the display
return {
account,
formatted: encodeAddress(publicKey, prefix),
formatted: account?.type === 'ethereum'
? address
: encodeAddress(publicKey, prefix),
genesisHash: account?.genesisHash,
prefix,
type: account?.type || DEFAULT_TYPE
@ -116,11 +118,25 @@ function Address ({ actions, address, children, className, genesisHash, isExtern
const accountByAddress = findAccountByAddress(accounts, address);
const recoded = (chain?.definition.chainType === 'ethereum' || accountByAddress?.type === 'ethereum' || (!accountByAddress && givenType === 'ethereum'))
? { account: accountByAddress, formatted: address, type: 'ethereum' } as Recoded
: recodeAddress(address, accounts, chain, settings);
console.log(
address,
(
chain?.definition.chainType === 'ethereum' ||
accountByAddress?.type === 'ethereum' ||
(!accountByAddress && givenType === 'ethereum')
)
? { account: accountByAddress, formatted: address, type: 'ethereum' }
: recodeAddress(address, accounts, chain, settings)
);
setRecoded(recoded || defaultRecoded);
setRecoded(
(
chain?.definition.chainType === 'ethereum' ||
accountByAddress?.type === 'ethereum' ||
(!accountByAddress && givenType === 'ethereum')
)
? { account: accountByAddress, formatted: address, type: 'ethereum' }
: recodeAddress(address, accounts, chain, settings));
}, [accounts, address, chain, givenType, settings]);
useEffect(() => {
@ -140,20 +156,23 @@ function Address ({ actions, address, children, className, genesisHash, isExtern
}, [toggleActions]);
const theme = (
chain?.icon
? chain.icon
: type === 'ethereum'
? 'ethereum'
: 'polkadot'
type === 'ethereum'
? 'ethereum'
: (chain?.icon || 'polkadot')
) as IconTheme;
const _onClick = useCallback((): void => setShowActionsMenu(!showActionsMenu), [showActionsMenu]);
const _onCopy = useCallback((): void => show(t('Copied')), [show, t]);
const _onClick = useCallback(
() => setShowActionsMenu(!showActionsMenu),
[showActionsMenu]
);
const _onCopy = useCallback(
() => show(t('Copied')),
[show, t]
);
const _toggleVisibility = useCallback(
(): void => {
address && showAccount(address, isHidden || false)
.catch(console.error);
},
() => address && showAccount(address, isHidden || false).catch(console.error),
[address, isHidden]
);

View File

@ -1,6 +1,6 @@
// Copyright 2019-2021 @polkadot/extension-ui authors & contributors
// SPDX-License-Identifier: Apache-2.0
import { KeypairType } from '@polkadot/util-crypto/types';
import type { KeypairType } from '@polkadot/util-crypto/types';
export const DEFAULT_TYPE = 'sr25519' as KeypairType;
export const DEFAULT_TYPE: KeypairType = 'sr25519';