Skip to content

Commit 934319c

Browse files
joelamouchejacogr
andauthored
Add derivation eth options (#4863)
* readd eth to dev mode * added specific eth derivation path component * fix derivation and add address list * add maxindex for dropdown * optimize address list * lint * add custom index instead * cleanup and fix * Apply suggestions from code review Co-authored-by: Jaco Greeff <[email protected]> * iterate on suggestions * Apply suggestions from code review Co-authored-by: Jaco Greeff <[email protected]> * update * remove address generation in dropdown * lint Co-authored-by: Jaco Greeff <[email protected]>
1 parent 00dbb0a commit 934319c

File tree

3 files changed

+209
-80
lines changed

3 files changed

+209
-80
lines changed

packages/page-accounts/src/modals/Create.tsx

+49-80
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33

44
import type { ActionStatus } from '@polkadot/react-components/Status/types';
55
import type { CreateResult } from '@polkadot/ui-keyring/types';
6-
import type { ModalProps } from '../types';
6+
import type { AddressState, CreateOptions, CreateProps, DeriveValidationOutput, PairType, SeedType } from '../types';
77

88
import FileSaver from 'file-saver';
99
import React, { useCallback, useRef, useState } from 'react';
@@ -20,45 +20,11 @@ import { hdLedger, hdValidatePath, keyExtractSuri, mnemonicGenerate, mnemonicVal
2020

2121
import { useTranslation } from '../translate';
2222
import CreateConfirmation from './CreateConfirmation';
23+
import CreateEthDerivationPath, { ETH_DEFAULT_PATH } from './CreateEthDerivationPath';
2324
import CreateSuriLedger from './CreateSuriLedger';
2425
import ExternalWarning from './ExternalWarning';
2526
import PasswordInput from './PasswordInput';
2627

27-
const ETH_DEFAULT_PATH = "m/44'/60'/0'/0/0";
28-
29-
type PairType = 'ecdsa' | 'ed25519' | 'ed25519-ledger' | 'ethereum' | 'sr25519';
30-
31-
interface Props extends ModalProps {
32-
className?: string;
33-
onClose: () => void;
34-
onStatusChange: (status: ActionStatus) => void;
35-
seed?: string;
36-
type?: PairType;
37-
}
38-
39-
type SeedType = 'bip' | 'raw' | 'dev';
40-
41-
interface AddressState {
42-
address: string | null;
43-
derivePath: string;
44-
deriveValidation?: DeriveValidationOutput
45-
isSeedValid: boolean;
46-
pairType: PairType;
47-
seed: string;
48-
seedType: SeedType;
49-
}
50-
51-
interface CreateOptions {
52-
genesisHash?: string;
53-
name: string;
54-
tags?: string[];
55-
}
56-
57-
interface DeriveValidationOutput {
58-
error?: string;
59-
warning?: string;
60-
}
61-
6228
const DEFAULT_PAIR_TYPE = 'sr25519';
6329
const STEPS_COUNT = 3;
6430

@@ -199,7 +165,7 @@ function createAccount (seed: string, derivePath: string, pairType: PairType, {
199165
return status;
200166
}
201167

202-
function Create ({ className = '', onClose, onStatusChange, seed: propsSeed, type: propsType }: Props): React.ReactElement<Props> {
168+
function Create ({ className = '', onClose, onStatusChange, seed: propsSeed, type: propsType }: CreateProps): React.ReactElement<CreateProps> {
203169
const { t } = useTranslation();
204170
const { api, isDevelopment, isEthereum } = useApi();
205171
const { isLedgerEnabled } = useLedger();
@@ -354,65 +320,68 @@ function Create ({ className = '', onClose, onStatusChange, seed: propsSeed, typ
354320
isPadded
355321
summary={t<string>('Advanced creation options')}
356322
>
357-
<Modal.Columns hint={t<string>('If you are moving accounts between applications, ensure that you use the correct type.')}>
358-
<Dropdown
359-
defaultValue={pairType}
360-
help={t<string>('Determines what cryptography will be used to create this account. Note that to validate on Polkadot, the session account must use "ed25519".')}
361-
label={t<string>('keypair crypto type')}
362-
onChange={_onChangePairType}
363-
options={
364-
isEthereum
365-
? settings.availableCryptosEth
366-
: isLedgerEnabled
367-
? settings.availableCryptosLedger
368-
: settings.availableCryptos
369-
}
370-
tabIndex={-1}
371-
/>
372-
</Modal.Columns>
323+
{
324+
pairType !== 'ethereum' && <Modal.Columns hint={t<string>('If you are moving accounts between applications, ensure that you use the correct type.')}>
325+
<Dropdown
326+
defaultValue={pairType}
327+
help={t<string>('Determines what cryptography will be used to create this account. Note that to validate on Polkadot, the session account must use "ed25519".')}
328+
label={t<string>('keypair crypto type')}
329+
onChange={_onChangePairType}
330+
options={
331+
isEthereum
332+
? settings.availableCryptosEth
333+
: isLedgerEnabled
334+
? settings.availableCryptosLedger
335+
: settings.availableCryptos
336+
}
337+
tabIndex={-1}
338+
/>
339+
</Modal.Columns>}
373340
{pairType === 'ed25519-ledger'
374341
? (
375342
<CreateSuriLedger
376343
onChange={_onChangePath}
377344
seedType={seedType}
378345
/>
379346
)
380-
: (
381-
<Modal.Columns hint={
382-
pairType === 'ethereum' && seedType === 'raw'
383-
? t<string>('The derivation path is only relevant when deriving keys from a mnemonic.')
384-
: t<string>('The derivation path allows you to create different accounts from the same base mnemonic.')
385-
}>
386-
{(pairType !== 'ethereum' || seedType !== 'raw') && (
347+
: pairType === 'ethereum'
348+
? (
349+
<CreateEthDerivationPath
350+
derivePath={derivePath}
351+
deriveValidation={deriveValidation}
352+
onChange={_onChangePath}
353+
seed={seed}
354+
seedType={seedType}
355+
/>
356+
)
357+
: (
358+
<Modal.Columns hint={t<string>('The derivation path allows you to create different accounts from the same base mnemonic.')}>
387359
<Input
388-
help={(pairType === 'ethereum' ? t<string>('You can set a custom derivation path for this account using the following syntax "m/<purpose>/<coin_type>/<account>/<change>/<address_index>') : t<string>('You can set a custom derivation path for this account using the following syntax "/<soft-key>//<hard-key>". The "/<soft-key>" and "//<hard-key>" may be repeated and mixed`. An optional "///<password>" can be used with a mnemonic seed, and may only be specified once.'))}
389-
isDisabled={pairType === 'ethereum' && seedType === 'raw'}
360+
help={(t<string>('You can set a custom derivation path for this account using the following syntax "/<soft-key>//<hard-key>". The "/<soft-key>" and "//<hard-key>" may be repeated and mixed`. An optional "///<password>" can be used with a mnemonic seed, and may only be specified once.'))}
361+
isDisabled={seedType === 'raw'}
390362
isError={!!deriveValidation?.error}
391363
label={t<string>('secret derivation path')}
392364
onChange={_onChangePath}
393365
placeholder={
394-
pairType === 'ethereum'
395-
? ETH_DEFAULT_PATH
396-
: seedType === 'raw'
397-
? pairType === 'sr25519'
398-
? t<string>('//hard/soft')
399-
: t<string>('//hard')
400-
: pairType === 'sr25519'
401-
? t<string>('//hard/soft///password')
402-
: t<string>('//hard///password')
366+
seedType === 'raw'
367+
? pairType === 'sr25519'
368+
? t<string>('//hard/soft')
369+
: t<string>('//hard')
370+
: pairType === 'sr25519'
371+
? t<string>('//hard/soft///password')
372+
: t<string>('//hard///password')
403373
}
404374
tabIndex={-1}
405375
value={derivePath}
406376
/>
407-
)}
408-
{deriveValidation?.error && (
409-
<MarkError content={errorIndex.current[deriveValidation.error] || deriveValidation.error} />
410-
)}
411-
{deriveValidation?.warning && (
412-
<MarkWarning content={errorIndex.current[deriveValidation.warning]} />
413-
)}
414-
</Modal.Columns>
415-
)}
377+
{deriveValidation?.error && (
378+
<MarkError content={errorIndex.current[deriveValidation.error] || deriveValidation.error} />
379+
)}
380+
{deriveValidation?.warning && (
381+
<MarkWarning content={errorIndex.current[deriveValidation.warning]} />
382+
)}
383+
</Modal.Columns>
384+
)}
416385
</Expander>
417386
<Modal.Columns>
418387
<ExternalWarning />
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
// Copyright 2017-2021 @polkadot/app-accounts authors & contributors
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
import BN from 'bn.js';
5+
import React, { ReactNode, useEffect, useRef, useState } from 'react';
6+
7+
import { Checkbox, Dropdown, Input, InputNumber, MarkError, MarkWarning, Modal } from '@polkadot/react-components';
8+
import { useToggle } from '@polkadot/react-hooks';
9+
10+
import { useTranslation } from '../translate';
11+
import { DeriveValidationOutput } from '../types';
12+
13+
interface Props {
14+
className?: string;
15+
onChange: (string: string) => void;
16+
seedType: string;
17+
derivePath: string;
18+
deriveValidation: DeriveValidationOutput | undefined;
19+
seed: string;
20+
}
21+
22+
export const ETH_DEFAULT_PATH = "m/44'/60'/0'/0/0";
23+
24+
function CreateEthDerivationPath ({ className,
25+
derivePath,
26+
deriveValidation,
27+
onChange,
28+
seedType }: Props): React.ReactElement<Props> {
29+
const { t } = useTranslation();
30+
const [addIndex, setAddIndex] = useState(0);
31+
const [customIndex, setCustomIndex] = useState(new BN(0));
32+
const [addressList] = useState<{ key: number; text: ReactNode; value: number; }[]>(new Array(10).fill(0).map((_, i) => ({
33+
key: i,
34+
text: t('Address index {{index}}', {
35+
replace: { index: i }
36+
}),
37+
value: i
38+
})));
39+
const [useCustomPath, toggleCustomPath] = useToggle();
40+
const [useCustomIndex, toggleCustomIndex] = useToggle();
41+
42+
const errorIndex = useRef<Record<string, string>>({
43+
INVALID_DERIVATION_PATH: t<string>('This is an invalid derivation path.'),
44+
PASSWORD_IGNORED: t<string>('Password are ignored for hex seed'),
45+
SOFT_NOT_ALLOWED: t<string>('Soft derivation paths are not allowed on ed25519'),
46+
WARNING_SLASH_PASSWORD: t<string>('Your password contains at least one "/" character. Disregard this warning if it is intended.')
47+
});
48+
49+
useEffect((): void => {
50+
onChange(`m/44'/60'/0'/0/${useCustomIndex ? Number(customIndex) : addIndex}`);
51+
}, [customIndex, onChange, useCustomIndex, addIndex]);
52+
53+
return (
54+
<Modal.Columns
55+
className={className}
56+
hint={
57+
seedType === 'raw'
58+
? t<string>('The derivation path is only relevant when deriving keys from a mnemonic.')
59+
: t<string>('The derivation path allows you to create different accounts from the same base mnemonic.')
60+
}
61+
>
62+
{seedType === 'bip'
63+
? (
64+
<>
65+
<div className='saveToggle'>
66+
<Checkbox
67+
label={<>{t<string>('Use custom address index')}</>}
68+
onChange={toggleCustomIndex}
69+
value={useCustomIndex}
70+
/>
71+
</div>
72+
{useCustomIndex
73+
? (
74+
<InputNumber
75+
help={t<string>('You can set a custom derivation index for this account')}
76+
isDecimal={false}
77+
label={t<string>('Custom index')}
78+
onChange={setCustomIndex}
79+
value={customIndex}
80+
/>
81+
)
82+
: (
83+
<Dropdown
84+
help={t('The address index (derivation on account) to use')}
85+
label={t('address index')}
86+
onChange={setAddIndex}
87+
options={addressList}
88+
value={addIndex}
89+
/>
90+
)}
91+
<div className='saveToggle'>
92+
<Checkbox
93+
label={<>{t<string>('Use custom derivation path')}</>}
94+
onChange={toggleCustomPath}
95+
value={useCustomPath}
96+
/>
97+
</div>
98+
{useCustomPath
99+
? (
100+
<Input
101+
help={t<string>(
102+
'You can set a custom derivation path for this account using the following syntax "m/<purpose>/<coin_type>/<account>/<change>/<address_index>'
103+
)}
104+
isError={!!deriveValidation?.error}
105+
label={t<string>('secret derivation path')}
106+
onChange={onChange}
107+
placeholder={ETH_DEFAULT_PATH}
108+
tabIndex={-1}
109+
value={derivePath}
110+
/>
111+
)
112+
: null}
113+
</>
114+
)
115+
: (
116+
<MarkWarning content={t<string>('The derivation path is only relevant when deriving keys from a mnemonic.')} />
117+
)}
118+
119+
{deriveValidation?.error && (
120+
<MarkError content={errorIndex.current[deriveValidation.error] || deriveValidation.error} />
121+
)}
122+
{deriveValidation?.warning && <MarkWarning content={errorIndex.current[deriveValidation.warning]} />}
123+
</Modal.Columns>
124+
);
125+
}
126+
127+
export default React.memo(CreateEthDerivationPath);

packages/page-accounts/src/types.ts

+33
Original file line numberDiff line numberDiff line change
@@ -32,3 +32,36 @@ export interface SortedAccount {
3232
delegation?: Delegation;
3333
isFavorite: boolean;
3434
}
35+
36+
export type PairType = 'ecdsa' | 'ed25519' | 'ed25519-ledger' | 'ethereum' | 'sr25519';
37+
38+
export interface CreateProps extends ModalProps {
39+
className?: string;
40+
onClose: () => void;
41+
onStatusChange: (status: ActionStatus) => void;
42+
seed?: string;
43+
type?: PairType;
44+
}
45+
46+
export type SeedType = 'bip' | 'raw' | 'dev';
47+
48+
export interface AddressState {
49+
address: string | null;
50+
derivePath: string;
51+
deriveValidation?: DeriveValidationOutput
52+
isSeedValid: boolean;
53+
pairType: PairType;
54+
seed: string;
55+
seedType: SeedType;
56+
}
57+
58+
export interface CreateOptions {
59+
genesisHash?: string;
60+
name: string;
61+
tags?: string[];
62+
}
63+
64+
export interface DeriveValidationOutput {
65+
error?: string;
66+
warning?: string;
67+
}

0 commit comments

Comments
 (0)