Derive.tsx 7.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235
  1. // Copyright 2017-2019 @polkadot/app-accounts authors & contributors
  2. // This software may be modified and distributed under the terms
  3. // of the Apache-2.0 license. See the LICENSE file for details.
  4. import { KeyringPair } from '@polkadot/keyring/types';
  5. import { ActionStatus } from '@polkadot/react-components/Status/types';
  6. import { I18nProps } from '@polkadot/react-components/types';
  7. import { KeypairType } from '@polkadot/util-crypto/types';
  8. import React, { useContext, useEffect, useState } from 'react';
  9. import { AddressRow, Button, Input, InputAddress, Modal, Password, StatusContext } from '@polkadot/react-components';
  10. import { useDebounce } from '@polkadot/react-components/hooks';
  11. import keyring from '@polkadot/ui-keyring';
  12. import { keyExtractPath } from '@polkadot/util-crypto';
  13. import translate from '../translate';
  14. import { downloadAccount } from './Create';
  15. import CreateConfirmation from './CreateConfirmation';
  16. interface Props extends I18nProps {
  17. from: string;
  18. onClose: () => void;
  19. }
  20. interface DerivedAddress {
  21. address: string | null;
  22. deriveError: string | null;
  23. }
  24. function deriveValidate (suri: string, pairType: KeypairType): string | null {
  25. try {
  26. const { path } = keyExtractPath(suri);
  27. // we don't allow soft for ed25519
  28. if (pairType === 'ed25519' && path.some(({ isSoft }): boolean => isSoft)) {
  29. return 'Soft derivation paths are not allowed on ed25519';
  30. }
  31. } catch (error) {
  32. return error.message;
  33. }
  34. return null;
  35. }
  36. function createAccount (source: KeyringPair, suri: string, name: string, password: string, success: string): ActionStatus {
  37. // we will fill in all the details below
  38. const status = { action: 'create' } as ActionStatus;
  39. try {
  40. const derived = source.derive(suri);
  41. derived.setMeta({ ...derived.meta, name, tags: [] });
  42. const result = keyring.addPair(derived, password || '');
  43. const { address } = result.pair;
  44. status.account = address;
  45. status.status = 'success';
  46. status.message = success;
  47. downloadAccount(result);
  48. } catch (error) {
  49. status.status = 'error';
  50. status.message = error.message;
  51. }
  52. return status;
  53. }
  54. function Derive ({ className, from, onClose, t }: Props): React.ReactElement {
  55. const { queueAction } = useContext(StatusContext);
  56. const [source] = useState(keyring.getPair(from));
  57. const [{ address, deriveError }, setDerived] = useState<DerivedAddress>({ address: null, deriveError: null });
  58. const [isConfirmationOpen, setIsConfirmationOpen] = useState(false);
  59. const [isLocked, setIsLocked] = useState(source.isLocked);
  60. const [{ isNameValid, name }, setName] = useState({ isNameValid: false, name: '' });
  61. const [{ isPassValid, password }, setPassword] = useState({ isPassValid: false, password: '' });
  62. const [rootPass, setRootPass] = useState('');
  63. const [suri, setSuri] = useState('');
  64. const debouncedSuri = useDebounce(suri);
  65. const isValid = !!address && !deriveError && isNameValid && isPassValid;
  66. useEffect((): void => {
  67. setIsLocked(source.isLocked);
  68. }, [source]);
  69. useEffect((): void => {
  70. setDerived((): DerivedAddress => {
  71. let address: string | null = null;
  72. const deriveError = deriveValidate(debouncedSuri, source.type);
  73. if (!deriveError) {
  74. const result = source.derive(suri);
  75. address = result.address;
  76. }
  77. return { address, deriveError };
  78. });
  79. }, [debouncedSuri]);
  80. const _onChangeName = (name: string): void => setName({ isNameValid: !!name.trim(), name });
  81. const _onChangePass = (password: string): void => setPassword({ isPassValid: keyring.isPassValid(password), password });
  82. const _toggleConfirmation = (): void => setIsConfirmationOpen(!isConfirmationOpen);
  83. const _onUnlock = (): void => {
  84. try {
  85. source.decodePkcs8(rootPass);
  86. } catch (error) {
  87. console.error(error);
  88. }
  89. setIsLocked(source.isLocked);
  90. };
  91. const _onCommit = (): void => {
  92. if (!isValid) {
  93. return;
  94. }
  95. const status = createAccount(source, suri, name, password, t('created account'));
  96. _toggleConfirmation();
  97. queueAction(status);
  98. onClose();
  99. };
  100. const sourceStatic = (
  101. <InputAddress
  102. help={t('The selected account to perform the derivation on.')}
  103. isDisabled
  104. label={t('derive root account')}
  105. value={from}
  106. />
  107. );
  108. return (
  109. <Modal
  110. className={className}
  111. dimmer='inverted'
  112. open
  113. >
  114. <Modal.Header>{t('Derive account from pair')}</Modal.Header>
  115. {address && isConfirmationOpen && (
  116. <CreateConfirmation
  117. address={address}
  118. name={name}
  119. onCommit={_onCommit}
  120. onClose={_toggleConfirmation}
  121. />
  122. )}
  123. <Modal.Content>
  124. {isLocked && (
  125. <>
  126. {sourceStatic}
  127. <Password
  128. autoFocus
  129. help={t('The password to unlock the selected account.')}
  130. label={t('password')}
  131. onChange={setRootPass}
  132. value={rootPass}
  133. />
  134. </>
  135. )}
  136. {!isLocked && (
  137. <AddressRow
  138. defaultName={name}
  139. noDefaultNameOpacity
  140. value={deriveError ? '' : address}
  141. >
  142. {sourceStatic}
  143. <Input
  144. autoFocus
  145. help={t('You can set a custom derivation path for this account using the following syntax "/<soft-key>//<hard-key>///<password>". The "/<soft-key>" and "//<hard-key>" may be repeated and mixed`. The "///password" is optional and should only occur once.')}
  146. label={t('derivation path')}
  147. onChange={setSuri}
  148. placeholder={t('//hard/soft')}
  149. />
  150. <Input
  151. className='full'
  152. help={t('Name given to this account. You can edit it. To use the account to validate or nominate, it is a good practice to append the function of the account in the name, e.g "name_you_want - stash".')}
  153. isError={!isNameValid}
  154. label={t('name')}
  155. onChange={_onChangeName}
  156. onEnter={_onCommit}
  157. placeholder={t('new account')}
  158. value={name}
  159. />
  160. <Password
  161. className='full'
  162. help={t('This password is used to encrypt your private key. It must be strong and unique! You will need it to sign transactions with this account. You can recover this account using this password together with the backup file (generated in the next step).')}
  163. isError={!isPassValid}
  164. label={t('password')}
  165. onChange={_onChangePass}
  166. onEnter={_onCommit}
  167. value={password}
  168. />
  169. </AddressRow>
  170. )}
  171. </Modal.Content>
  172. <Modal.Actions>
  173. <Button.Group>
  174. <Button
  175. icon='cancel'
  176. isNegative
  177. label={t('Cancel')}
  178. onClick={onClose}
  179. />
  180. <Button.Or />
  181. {isLocked
  182. ? (
  183. <Button
  184. icon='lock'
  185. isDisabled={!rootPass}
  186. isPrimary
  187. label={t('Unlock')}
  188. onClick={_onUnlock}
  189. />
  190. )
  191. : (
  192. <Button
  193. icon='plus'
  194. isDisabled={!isValid}
  195. isPrimary
  196. label={t('Save')}
  197. onClick={_toggleConfirmation}
  198. />
  199. )
  200. }
  201. </Button.Group>
  202. </Modal.Actions>
  203. </Modal>
  204. );
  205. }
  206. export default translate(Derive);