From b891daaa1f1b3bcdded91b62ec1e623afc9b9dbb Mon Sep 17 00:00:00 2001 From: Sebastien Guillemot Date: Tue, 4 Aug 2020 03:53:18 +0900 Subject: [PATCH] restoration of 15-word shelley wallets --- app/actions/common/wallet-restore-actions.js | 30 +++++-- .../option-dialog/WalletEraOptionDialog.js | 83 +++++++++++++++++++ app/config.js | 34 ++++---- .../transfer/DaedalusTransferPage.js | 11 +-- .../transfer/DaedalusTransferPage.stories.js | 12 +-- app/containers/transfer/YoroiPlatePage.js | 11 ++- app/containers/transfer/YoroiTransferPage.js | 11 +-- .../transfer/YoroiTransferPage.stories.js | 12 +-- app/containers/wallet/WalletAddPage.js | 29 +++++-- .../wallet/WalletAddPage.stories.js | 14 ++-- .../dialogs/WalletEraOptionDialogContainer.js | 27 ++++++ .../dialogs/WalletRestoreDialogContainer.js | 10 +-- app/i18n/locales/en-US.json | 9 ++ app/stores/ada/AdaWalletRestoreStore.js | 36 +++----- app/stores/ada/AdaWalletsStore.js | 1 - app/stores/ergo/ErgoRestoreStore.js | 15 ++-- .../JormungandrWalletRestoreStore.js | 19 ++--- app/stores/stateless/modeInfo.js | 21 ----- app/stores/toplevel/WalletRestoreStore.js | 69 ++++++++++----- 19 files changed, 287 insertions(+), 167 deletions(-) create mode 100644 app/components/wallet/add/option-dialog/WalletEraOptionDialog.js create mode 100644 app/containers/wallet/dialogs/WalletEraOptionDialogContainer.js delete mode 100644 app/stores/stateless/modeInfo.js diff --git a/app/actions/common/wallet-restore-actions.js b/app/actions/common/wallet-restore-actions.js index 05e6db2bc5..8901dd008b 100644 --- a/app/actions/common/wallet-restore-actions.js +++ b/app/actions/common/wallet-restore-actions.js @@ -1,6 +1,7 @@ // @flow import { AsyncAction, Action } from '../lib/Action'; +import config from '../../config'; export type WalletRestoreMeta = {| recoveryPhrase: string, @@ -9,13 +10,28 @@ export type WalletRestoreMeta = {| paperPassword: string, |}; -export const RestoreMode = Object.freeze({ - UNSET: -1, - REGULAR_15: 0, - REGULAR_24: 2, - PAPER: 1, -}); -export type RestoreModeType = $Values; +export type RestoreModeType = {| + type: 'bip44', + extra: void, + length: ( + typeof config.wallets.WALLET_RECOVERY_PHRASE_WORD_COUNT | + typeof config.wallets.DAEDALUS_RECOVERY_PHRASE_WORD_COUNT + ), +|} | {| + type: 'cip1852', + extra: void, + length: ( + typeof config.wallets.WALLET_RECOVERY_PHRASE_WORD_COUNT | + typeof config.wallets.DAEDALUS_SHELLEY_RECOVERY_PHRASE_WORD_COUNT + ), +|} | {| + type: 'bip44', + extra: 'paper', + length: ( + typeof config.wallets.YOROI_PAPER_RECOVERY_PHRASE_WORD_COUNT | + typeof config.wallets.DAEDALUS_PAPER_RECOVERY_PHRASE_WORD_COUNT + ), +|}; export default class WalletRestoreActions { submitFields: Action = new Action(); diff --git a/app/components/wallet/add/option-dialog/WalletEraOptionDialog.js b/app/components/wallet/add/option-dialog/WalletEraOptionDialog.js new file mode 100644 index 0000000000..52d607def3 --- /dev/null +++ b/app/components/wallet/add/option-dialog/WalletEraOptionDialog.js @@ -0,0 +1,83 @@ +// @flow +import type { Node } from 'react'; +import React, { Component } from 'react'; +import { observer } from 'mobx-react'; +import { defineMessages, intlShape } from 'react-intl'; + +import Dialog from '../../../widgets/Dialog'; +import DialogCloseButton from '../../../widgets/DialogCloseButton'; +import OptionBlock from '../../../widgets/options/OptionBlock'; +import type { $npm$ReactIntl$IntlFormat } from 'react-intl'; +import styles from '../../../widgets/options/OptionListWrapperStyle.scss'; +import DialogBackButton from '../../../widgets/DialogBackButton'; + +const messages = defineMessages({ + dialogTitle: { + id: 'wallet.add.optionDialog.walletEra.dialogTitle', + defaultMessage: '!!!Choose era', + }, + restoreByronEraWalletTitle: { + id: 'wallet.add.optionDialog.walletEra.byronEra.title', + defaultMessage: '!!!Byron-era (read-only) wallet', + }, + restoreByronEraWalletDescription: { + id: 'wallet.add.optionDialog.walletEra.byronEra.description', + defaultMessage: '!!!Wallets created before July 29th, 2020 are Byron-era wallets and cannot delegate.', + }, + restoreShelleyEraWalletTitle: { + id: 'wallet.add.optionDialog.walletEra.shelleyEra.title', + defaultMessage: '!!!Shelley-era wallet', + }, + restoreShelleyEraWalletDescription: { + id: 'wallet.add.optionDialog.walletEra.shelleyEra.description', + defaultMessage: '!!!Shelley-era wallets support delegation to stake pools.', + }, +}); + +type Props = {| + +onCancel: void => void, + +onByron: void => void, + +onShelley: void => void, + +onBack: void => void, +|}; + +@observer +export default class WalletEraOptionDialog extends Component { + static contextTypes: {|intl: $npm$ReactIntl$IntlFormat|} = { + intl: intlShape.isRequired, + }; + + render(): Node { + const { intl } = this.context; + + return ( + } + className="WalletEraOptionDialog" + backButton={} + > +
+
    + + +
+
+
+ ); + } +} diff --git a/app/config.js b/app/config.js index a76d7a7992..7239a99634 100644 --- a/app/config.js +++ b/app/config.js @@ -1,18 +1,18 @@ // @flow -export default { - wallets: { - ADDRESS_COPY_TOOLTIP_NOTIFICATION_DURATION: 2, - WALLET_CREATED_NOTIFICATION_DURATION: 8, - WALLET_RESTORED_NOTIFICATION_DURATION: 8, - MAX_ALLOWED_UNUSED_ADDRESSES: 20, - TRANSACTION_REQUEST_SIZE: 50, - DAEDALUS_RECOVERY_PHRASE_WORD_COUNT: 12, - DAEDALUS_PAPER_RECOVERY_PHRASE_WORD_COUNT: 27, - MAX_RECOVERY_PHRASE_WORD_COUNT: 24, - WALLET_RECOVERY_PHRASE_WORD_COUNT: 15, - DAEDALUS_SHELLEY_RECOVERY_PHRASE_WORD_COUNT: 24, - YOROI_PAPER_RECOVERY_PHRASE_WORD_COUNT: 21, +export default Object.freeze({ + wallets: Object.freeze({ + ADDRESS_COPY_TOOLTIP_NOTIFICATION_DURATION: (2: 2), + WALLET_CREATED_NOTIFICATION_DURATION: (8: 8), + WALLET_RESTORED_NOTIFICATION_DURATION: (8: 8), + MAX_ALLOWED_UNUSED_ADDRESSES: (20: 20), + TRANSACTION_REQUEST_SIZE: (50: 50), + DAEDALUS_RECOVERY_PHRASE_WORD_COUNT: (12: 12), + DAEDALUS_PAPER_RECOVERY_PHRASE_WORD_COUNT: (27: 27), + MAX_RECOVERY_PHRASE_WORD_COUNT: (24: 24), + WALLET_RECOVERY_PHRASE_WORD_COUNT: (15: 15), + DAEDALUS_SHELLEY_RECOVERY_PHRASE_WORD_COUNT: (24: 24), + YOROI_PAPER_RECOVERY_PHRASE_WORD_COUNT: (21: 21), hardwareWallet: { trezorT: { VENDOR: 'trezor.io', @@ -31,8 +31,8 @@ export default { VENDOR: 'ledger.com', } } - }, - forms: { + }), + forms: Object.freeze({ FORM_VALIDATION_DEBOUNCE_WAIT: 500 - }, -}; + }), +}); diff --git a/app/containers/transfer/DaedalusTransferPage.js b/app/containers/transfer/DaedalusTransferPage.js index b3fa7e6164..e130608fb0 100644 --- a/app/containers/transfer/DaedalusTransferPage.js +++ b/app/containers/transfer/DaedalusTransferPage.js @@ -20,7 +20,7 @@ import type { $npm$ReactIntl$IntlFormat } from 'react-intl'; import { SelectedExplorer } from '../../domain/SelectedExplorer'; import type { UnitOfAccountSettingType } from '../../types/unitOfAccountType'; import { PublicDeriver } from '../../api/ada/lib/storage/models/PublicDeriver/index'; -import { RestoreMode } from '../../actions/common/wallet-restore-actions'; +import type { RestoreModeType } from '../../actions/common/wallet-restore-actions'; import { formattedWalletAmount } from '../../utils/formatters'; import { ROUTES } from '../../routes-config'; import { ApiOptions, getApiForNetwork, getApiMeta } from '../../api/common/utils'; @@ -140,8 +140,7 @@ export default class DaedalusTransferPage extends Component this.generated.stores.walletRestore.isValidMnemonic({ mnemonic, - numberOfWords: config.wallets.DAEDALUS_RECOVERY_PHRASE_WORD_COUNT, - mode: RestoreMode.REGULAR_15, + mode: { type: 'bip44', extra: undefined, length: config.wallets.DAEDALUS_RECOVERY_PHRASE_WORD_COUNT }, })} validWords={validWords} mnemonicLength={config.wallets.DAEDALUS_RECOVERY_PHRASE_WORD_COUNT} @@ -155,8 +154,7 @@ export default class DaedalusTransferPage extends Component this.generated.stores.walletRestore.isValidMnemonic({ mnemonic, - numberOfWords: config.wallets.DAEDALUS_PAPER_RECOVERY_PHRASE_WORD_COUNT, - mode: RestoreMode.PAPER, + mode: { type: 'bip44', extra: 'paper', length: config.wallets.DAEDALUS_PAPER_RECOVERY_PHRASE_WORD_COUNT }, })} validWords={validWords} mnemonicLength={config.wallets.DAEDALUS_PAPER_RECOVERY_PHRASE_WORD_COUNT} @@ -269,8 +267,7 @@ export default class DaedalusTransferPage extends Component | $PropertyType| $PropertyType, + mode: RestoreModeType, |}) => boolean, |}, daedalusTransfer: {| diff --git a/app/containers/transfer/DaedalusTransferPage.stories.js b/app/containers/transfer/DaedalusTransferPage.stories.js index 15fea32ca7..f0c7f7f02e 100644 --- a/app/containers/transfer/DaedalusTransferPage.stories.js +++ b/app/containers/transfer/DaedalusTransferPage.stories.js @@ -29,7 +29,6 @@ import { NotEnoughMoneyToSendError, } from '../../api/common/errors'; import AdaApi from '../../api/ada/index'; -import { RestoreMode } from '../../actions/common/wallet-restore-actions'; export default { title: `${__filename.split('.')[0]}`, @@ -58,14 +57,11 @@ const genBaseProps: {| }, walletRestore: { isValidMnemonic: (isValidRequest) => { - const { mnemonic, numberOfWords } = isValidRequest; - if ( - (isValidRequest.mode === RestoreMode.REGULAR_15) || - (isValidRequest.mode === RestoreMode.REGULAR_24) - ) { - return AdaApi.isValidMnemonic({ mnemonic, numberOfWords }); + const { mnemonic, mode } = isValidRequest; + if (isValidRequest.mode.extra === 'paper') { + return AdaApi.prototype.isValidPaperMnemonic({ mnemonic, numberOfWords: mode.length }); } - return AdaApi.prototype.isValidPaperMnemonic({ mnemonic, numberOfWords }); + return AdaApi.isValidMnemonic({ mnemonic, numberOfWords: mode.length }); }, }, daedalusTransfer: { diff --git a/app/containers/transfer/YoroiPlatePage.js b/app/containers/transfer/YoroiPlatePage.js index 75deea34b0..b020a8d7e5 100644 --- a/app/containers/transfer/YoroiPlatePage.js +++ b/app/containers/transfer/YoroiPlatePage.js @@ -14,7 +14,6 @@ import { import type { PlateResponse } from '../../api/common/lib/crypto/plate'; import { TransferKind, TransferSource } from '../../types/TransferTypes'; import { generatePlates } from '../../stores/toplevel/WalletRestoreStore'; -import { RestoreMode } from '../../actions/common/wallet-restore-actions'; import { SelectedExplorer } from '../../domain/SelectedExplorer'; import type { TransferKindType, TransferSourceType, } from '../../types/TransferTypes'; import type { Notification } from '../../types/notificationType'; @@ -46,19 +45,19 @@ export default class YoroiPlatePage extends Component { const getRestoreMode = () => { if (yoroiTransfer.transferKind === TransferKind.PAPER) { - return RestoreMode.PAPER; + return { type: 'bip44', extra: 'paper', length: 21 }; } if (yoroiTransfer.transferSource === TransferSource.BYRON) { - return RestoreMode.REGULAR_15; + return { type: 'bip44', extra: undefined, length: 15 }; } if (yoroiTransfer.transferSource === TransferSource.JORMUNGANDR_UTXO) { - return RestoreMode.REGULAR_15; + return { type: 'cip1852', extra: undefined, length: 15 }; } if (yoroiTransfer.transferSource === TransferSource.JORMUNGANDR_CHIMERIC_ACCOUNT) { - return RestoreMode.REGULAR_15; + return { type: 'cip1852', extra: undefined, length: 15 }; } if (yoroiTransfer.transferSource === TransferSource.SHELLEY) { - return RestoreMode.REGULAR_24; + return { type: 'cip1852', extra: undefined, length: 15 }; } throw new Error(`${nameof(YoroiPlatePage)} unknown mode`); }; diff --git a/app/containers/transfer/YoroiTransferPage.js b/app/containers/transfer/YoroiTransferPage.js index 4654b25e3b..fa6631c521 100644 --- a/app/containers/transfer/YoroiTransferPage.js +++ b/app/containers/transfer/YoroiTransferPage.js @@ -30,7 +30,7 @@ import type { $npm$ReactIntl$IntlFormat } from 'react-intl'; import type { GeneratedData as YoroiPlateData } from './YoroiPlatePage'; import { SelectedExplorer } from '../../domain/SelectedExplorer'; import type { UnitOfAccountSettingType } from '../../types/unitOfAccountType'; -import { RestoreMode } from '../../actions/common/wallet-restore-actions'; +import type { RestoreModeType } from '../../actions/common/wallet-restore-actions'; import { ApiOptions, getApiMeta, getApiForNetwork, } from '../../api/common/utils'; // Stay this long on the success page, then jump to the wallet transactions page @@ -163,8 +163,7 @@ export default class YoroiTransferPage extends Component this.generated.stores.walletRestore.isValidMnemonic({ mnemonic, - numberOfWords: config.wallets.WALLET_RECOVERY_PHRASE_WORD_COUNT, - mode: RestoreMode.REGULAR_15, + mode: { type: 'bip44', extra: undefined, length: config.wallets.WALLET_RECOVERY_PHRASE_WORD_COUNT }, })} validWords={validWords} mnemonicLength={config.wallets.WALLET_RECOVERY_PHRASE_WORD_COUNT} @@ -178,8 +177,7 @@ export default class YoroiTransferPage extends Component this.generated.stores.walletRestore.isValidMnemonic({ mnemonic, - numberOfWords: config.wallets.YOROI_PAPER_RECOVERY_PHRASE_WORD_COUNT, - mode: RestoreMode.PAPER, + mode: { type: 'bip44', extra: 'paper', length: config.wallets.YOROI_PAPER_RECOVERY_PHRASE_WORD_COUNT }, })} validWords={validWords} mnemonicLength={config.wallets.YOROI_PAPER_RECOVERY_PHRASE_WORD_COUNT} @@ -339,8 +337,7 @@ export default class YoroiTransferPage extends Component | $PropertyType | $PropertyType, + mode: RestoreModeType, |}) => boolean, |}, wallets: {| diff --git a/app/containers/transfer/YoroiTransferPage.stories.js b/app/containers/transfer/YoroiTransferPage.stories.js index 20f36c755d..5775f2d3f0 100644 --- a/app/containers/transfer/YoroiTransferPage.stories.js +++ b/app/containers/transfer/YoroiTransferPage.stories.js @@ -30,7 +30,6 @@ import { WalletChangedError, } from '../../stores/toplevel/YoroiTransferStore'; import AdaApi from '../../api/ada/index'; -import { RestoreMode } from '../../actions/common/wallet-restore-actions'; import { HARD_DERIVATION_START, } from '../../config/numbersConfig'; @@ -61,15 +60,12 @@ const genBaseProps: {| walletRestore: { selectedAccount: 0 + HARD_DERIVATION_START, isValidMnemonic: (isValidRequest) => { - const { mnemonic, numberOfWords } = isValidRequest; + const { mnemonic, mode } = isValidRequest; - if ( - (isValidRequest.mode === RestoreMode.REGULAR_15) || - (isValidRequest.mode === RestoreMode.REGULAR_24) - ) { - return AdaApi.isValidMnemonic({ mnemonic, numberOfWords }); + if (isValidRequest.mode.extra === 'paper') { + return AdaApi.prototype.isValidPaperMnemonic({ mnemonic, numberOfWords: mode.length }); } - return AdaApi.prototype.isValidPaperMnemonic({ mnemonic, numberOfWords }); + return AdaApi.isValidMnemonic({ mnemonic, numberOfWords: mode.length }); }, }, wallets: { diff --git a/app/containers/wallet/WalletAddPage.js b/app/containers/wallet/WalletAddPage.js index a34bd9143d..858232a6be 100644 --- a/app/containers/wallet/WalletAddPage.js +++ b/app/containers/wallet/WalletAddPage.js @@ -37,6 +37,7 @@ import type { GeneratedData as WalletTrezorConnectDialogContainerData } from './ import WalletLedgerConnectDialogContainer from './dialogs/WalletLedgerConnectDialogContainer'; import type { GeneratedData as WalletLedgerConnectDialogContainerData } from './dialogs/WalletLedgerConnectDialogContainer'; +import WalletEraOptionDialogContainer from './dialogs/WalletEraOptionDialogContainer'; import WalletCreateOptionDialog from '../../components/wallet/add/option-dialog/WalletCreateOptionDialog'; import WalletCreateOptionDialogContainer from './dialogs/WalletCreateOptionDialogContainer'; import WalletPaperDialog from '../../components/wallet/WalletPaperDialog'; @@ -54,7 +55,6 @@ import NavBar from '../../components/topbar/NavBar'; import NavBarTitle from '../../components/topbar/NavBarTitle'; import type { RestoreModeType } from '../../actions/common/wallet-restore-actions'; -import { RestoreMode } from '../../actions/common/wallet-restore-actions'; import type { $npm$ReactIntl$IntlFormat } from 'react-intl'; import { getApiForNetwork, ApiOptions } from '../../api/common/utils'; import { PublicDeriver } from '../../api/ada/lib/storage/models/PublicDeriver/index'; @@ -183,14 +183,13 @@ export default class WalletAddPage extends Component { actions.dialogs.open.trigger({ - dialog: WalletRestoreDialog, - params: { restoreType: (RestoreMode.REGULAR_15: RestoreModeType) } + dialog: WalletEraOptionDialogContainer, })} onRestore24={isJormungandr(selectedNetwork) ? undefined : () => actions.dialogs.open.trigger({ dialog: WalletRestoreDialog, - params: { restoreType: (RestoreMode.REGULAR_24: RestoreModeType) } + params: { restoreType: { type: 'cip1852', extra: undefined, length: 24 } } }) } onPaperRestore={ @@ -198,11 +197,31 @@ export default class WalletAddPage extends Component { ? undefined : () => actions.dialogs.open.trigger({ dialog: WalletRestoreDialog, - params: { restoreType: (RestoreMode.PAPER: RestoreModeType) } + params: { restoreType: { type: 'bip44', extra: 'paper', length: 21 } } }) } /> ); + } else if (uiDialogs.isOpen(WalletEraOptionDialogContainer)) { + if (selectedNetwork === undefined) { + throw new Error(`${nameof(WalletAddPage)} no API selected`); + } + activeDialog = ( + actions.dialogs.open.trigger({ + dialog: WalletRestoreDialog, + params: { restoreType: { type: 'bip44', extra: undefined, length: 15 } } + })} + onShelley={() => actions.dialogs.open.trigger({ + dialog: WalletRestoreDialog, + params: { restoreType: { type: 'cip1852', extra: undefined, length: 15 } } + })} + onBack={() => actions.dialogs.open.trigger({ + dialog: WalletRestoreOptionDialog, + })} + /> + ); } else if (uiDialogs.isOpen(WalletRestoreDialog)) { const mode = uiDialogs.getParam('restoreType'); if (mode == null) throw new Error(`${nameof(WalletAddPage)} no mode for restoration selected`); diff --git a/app/containers/wallet/WalletAddPage.stories.js b/app/containers/wallet/WalletAddPage.stories.js index ae0d1a7478..bdf7d095f4 100644 --- a/app/containers/wallet/WalletAddPage.stories.js +++ b/app/containers/wallet/WalletAddPage.stories.js @@ -32,7 +32,6 @@ import { defaultToSelectedExplorer } from '../../domain/SelectedExplorer'; import { StepState } from '../../components/widgets/ProgressSteps'; import { ProgressStep } from '../../types/HWConnectStoreTypes'; import { RestoreSteps, generatePlates } from '../../stores/toplevel/WalletRestoreStore'; -import { RestoreMode } from '../../actions/common/wallet-restore-actions'; import WalletCreateDialog from '../../components/wallet/WalletCreateDialog'; import WalletBackupDialog from '../../components/wallet/WalletBackupDialog'; import WalletRestoreDialog from '../../components/wallet/WalletRestoreDialog'; @@ -478,14 +477,11 @@ const restoreWalletProps: {| walletRestoreMeta: request.walletRestoreMeta, recoveryResult: request.recoveryResult, isValidMnemonic: (isValidRequest) => { - const { mnemonic, numberOfWords } = isValidRequest; - if ( - (isValidRequest.mode === RestoreMode.REGULAR_15) || - (isValidRequest.mode === RestoreMode.REGULAR_24) - ) { - return AdaApi.isValidMnemonic({ mnemonic, numberOfWords }); + const { mnemonic, mode } = isValidRequest; + if (isValidRequest.mode.extra === 'paper') { + return AdaApi.prototype.isValidPaperMnemonic({ mnemonic, numberOfWords: mode.length }); } - return AdaApi.prototype.isValidPaperMnemonic({ mnemonic, numberOfWords }); + return AdaApi.isValidMnemonic({ mnemonic, numberOfWords: mode.length }); }, }, yoroiTransfer: { @@ -600,7 +596,7 @@ export const RestoreWalletStart = (): Node => { })(), walletName: select('walletName', nameCases, nameCases.None), walletPassword: select('walletPassword', password, password.Empty), - paperPassword: getRestoreMode() === RestoreMode.PAPER + paperPassword: getRestoreMode().extra === 'paper' ? select('paperPassword', paperPassword, paperPassword.Empty) : '', }, diff --git a/app/containers/wallet/dialogs/WalletEraOptionDialogContainer.js b/app/containers/wallet/dialogs/WalletEraOptionDialogContainer.js new file mode 100644 index 0000000000..1a568ebb0f --- /dev/null +++ b/app/containers/wallet/dialogs/WalletEraOptionDialogContainer.js @@ -0,0 +1,27 @@ +// @flow +import type { Node } from 'react'; +import React, { Component } from 'react'; +import { observer } from 'mobx-react'; +import WalletEraOptionDialog from '../../../components/wallet/add/option-dialog/WalletEraOptionDialog'; + +type Props = {| + +onClose: void => void, + +onByron: void => void, + +onShelley: void => void, + +onBack: void => void, +|}; + +@observer +export default class WalletEraOptionDialogContainer extends Component { + + render(): Node { + return ( + + ); + } +} diff --git a/app/containers/wallet/dialogs/WalletRestoreDialogContainer.js b/app/containers/wallet/dialogs/WalletRestoreDialogContainer.js index 02b74aeb66..c9392ea314 100644 --- a/app/containers/wallet/dialogs/WalletRestoreDialogContainer.js +++ b/app/containers/wallet/dialogs/WalletRestoreDialogContainer.js @@ -16,7 +16,6 @@ import { NoInputsError, } from '../../../api/common/errors'; import type { RestoreModeType, WalletRestoreMeta } from '../../../actions/common/wallet-restore-actions'; -import { RestoreMode } from '../../../actions/common/wallet-restore-actions'; import { RestoreSteps } from '../../../stores/toplevel/WalletRestoreStore'; import { defineMessages, intlShape } from 'react-intl'; import YoroiTransferWaitingPage from '../../transfer/YoroiTransferWaitingPage'; @@ -37,7 +36,6 @@ import type { NetworkRow, } from '../../../api/ada/lib/storage/database/primitives/tables'; import { isJormungandr } from '../../../api/ada/lib/storage/database/prepackaged/networks'; -import { isPaperMode, getWordsCount } from '../../../stores/stateless/modeInfo'; const messages = defineMessages({ walletUpgradeNoop: { @@ -105,8 +103,8 @@ export default class WalletRestoreDialogContainer extends Component { const { restoreRequest } = wallets; const mode = this.props.mode; - const isPaper = isPaperMode(this.props.mode); - const wordsCount = getWordsCount(this.props.mode); + const isPaper = mode.extra === 'paper'; + const wordsCount = mode.length; const tooltipNotification = { duration: config.wallets.ADDRESS_COPY_TOOLTIP_NOTIFICATION_DURATION, @@ -119,7 +117,6 @@ export default class WalletRestoreDialogContainer extends Component { mnemonicValidator={mnemonic => ( this.generated.stores.walletRestore.isValidMnemonic({ mnemonic, - numberOfWords: wordsCount, mode, }) )} @@ -344,8 +341,7 @@ export default class WalletRestoreDialogContainer extends Component { walletRestoreMeta: void | WalletRestoreMeta, isValidMnemonic: ({| mnemonic: string, - numberOfWords: number, - mode: $PropertyType | $PropertyType | $PropertyType, + mode: RestoreModeType, |}) => boolean, |}, yoroiTransfer: {| diff --git a/app/i18n/locales/en-US.json b/app/i18n/locales/en-US.json index f35394b790..344b2dddb8 100644 --- a/app/i18n/locales/en-US.json +++ b/app/i18n/locales/en-US.json @@ -324,10 +324,17 @@ "wallet.add.optionDialog.create.normalWallet.description": "A standard wallet backed by a recovery phrase.", "wallet.add.optionDialog.create.paperWallet.description": "Paper wallets can be created even on devices not connected to the internet which makes them well-suited for single-use cold storage.", "wallet.add.optionDialog.restore.dialogTitle": "Restore wallet", + "wallet.add.optionDialog.restore.normal24Wallet.description": "If you have a recovery phrase consisting of 24 words, choose this option to restore your wallet.", + "wallet.add.optionDialog.restore.normal24Wallet.title": "Enter a 24-word recovery phrase", "wallet.add.optionDialog.restore.normalWallet.description": "If you have a Yoroi recovery phrase consisting of 15 words generated when you created a Yoroi Wallet, choose this option to restore your wallet.", "wallet.add.optionDialog.restore.normalWallet.title": "Enter a 15-word recovery phrase", "wallet.add.optionDialog.restore.paperWallet.description": "If you have generated a Yoroi paper wallet (which is usually printed and kept offline), you can choose this option to import the funds from your Yoroi paper wallet.", "wallet.add.optionDialog.restore.paperWallet.title": "Paper Wallet", + "wallet.add.optionDialog.walletEra.byronEra.description": "Wallets created before July 29th, 2020 are Byron-era wallets and cannot delegate.", + "wallet.add.optionDialog.walletEra.byronEra.title": "Byron-era (read-only) wallet", + "wallet.add.optionDialog.walletEra.dialogTitle": "Choose era", + "wallet.add.optionDialog.walletEra.shelleyEra.description": "Shelley-era wallets support delegation to stake pools.", + "wallet.add.optionDialog.walletEra.shelleyEra.title": "Shelley-era wallet", "wallet.add.page.create.title": "Create wallet", "wallet.add.page.create.tooltip": "Generate a new 15-word recovery phrase
and create a Yoroi wallet.", "wallet.add.page.daedalusTransfer.title": "Transfer funds from a Daedalus wallet to Yoroi", @@ -537,6 +544,7 @@ "wallet.restore.dialog.title.verify.paper.label": "Verify Yoroi Paper wallet", "wallet.restore.dialog.upgrade.noop": "Your wallet did not need to be upgraded", "wallet.restore.dialog.verify.accountId.byron.label": "Byron account checksum:", + "wallet.restore.dialog.verify.accountId.itn.label": "ITN account checksum:", "wallet.restore.dialog.verify.accountId.label": "Your Wallet Account checksum:", "wallet.restore.dialog.verify.accountId.shelley.label": "Shelley account checksum:", "wallet.restore.dialog.verify.addressesLabel": "Your Wallet address[es]:", @@ -545,6 +553,7 @@ "wallet.restore.dialog.verify.intro.line2": "Make sure account checksum and icon match what you remember.", "wallet.restore.dialog.verify.intro.line3": "Make sure addresses match what you remember.", "wallet.restore.dialog.verify.intro.line4": "If you've entered wrong mnemonics or a wrong paper wallet password - you will just open another empty wallet with wrong account checksum and wrong addresses.", + "wallet.restore.dialog.verify.itn.addressesLabel": "ITN Wallet address[es]:", "wallet.restore.dialog.verify.shelley.addressesLabel": "Shelley Wallet address[es]:", "wallet.restore.dialog.verify.title": "Verify Restored Wallet", "wallet.restore.dialog.verify.wallet.button.label": "Verify wallet", diff --git a/app/stores/ada/AdaWalletRestoreStore.js b/app/stores/ada/AdaWalletRestoreStore.js index 135d7b4fe9..91ab9b8994 100644 --- a/app/stores/ada/AdaWalletRestoreStore.js +++ b/app/stores/ada/AdaWalletRestoreStore.js @@ -3,7 +3,7 @@ import { action, runInAction, } from 'mobx'; import Store from '../base/Store'; -import { RestoreMode } from '../../actions/common/wallet-restore-actions'; +import type { RestoreModeType } from '../../actions/common/wallet-restore-actions'; import { RustModule } from '../../api/ada/lib/cardanoCrypto/rustLoader'; import { TransferSource } from '../../types/TransferTypes'; import { @@ -137,22 +137,11 @@ export default class AdaWalletRestoreStore extends Store { const { selectedNetwork } = this.stores.profile; if (selectedNetwork == null) throw new Error(`${nameof(this._restoreToDb)} no network selected`); - const walletType = (() => { - const { mode } = this.stores.walletRestore; - if (mode === RestoreMode.PAPER) { - return 'bip44'; - } - if (mode === RestoreMode.REGULAR_15) { - return 'bip44'; // TODO: it's more complicated than this - } - if (mode === RestoreMode.REGULAR_24) { - return 'cip1852'; - } - throw new Error(`${nameof(this._restoreToDb)} Unknown restoration type`); - })(); + const { mode } = this.stores.walletRestore; + if (mode == null) throw new Error(`${nameof(this._restoreToDb)} Unknown restoration type`); await this.stores.wallets.restoreRequest.execute(async () => { const wallet = await this.api.ada.restoreWallet({ - mode: walletType, + mode: mode.type, db: persistentDb, recoveryPhrase: phrase, walletName, @@ -178,16 +167,15 @@ export default class AdaWalletRestoreStore extends Store { isValidMnemonic: {| mnemonic: string, - numberOfWords: number, - mode: $PropertyType | $PropertyType | $PropertyType, + mode: RestoreModeType, |} => boolean = request => { - const { mnemonic, numberOfWords } = request; - if (request.mode === RestoreMode.PAPER) { - return this.api.ada.isValidPaperMnemonic({ mnemonic, numberOfWords }); + const { mnemonic } = request; + if (request.mode.extra === 'paper') { + return this.api.ada.isValidPaperMnemonic({ mnemonic, numberOfWords: request.mode.length }); } - if (request.mode === RestoreMode.REGULAR_15 || request.mode === RestoreMode.REGULAR_24) { - return this.api.ada.constructor.isValidMnemonic({ mnemonic, numberOfWords }); - } - throw new Error(`${nameof(this.isValidMnemonic)} unexpected mode ${request.mode}`); + return this.api.ada.constructor.isValidMnemonic({ + mnemonic, + numberOfWords: request.mode.length + }); } } diff --git a/app/stores/ada/AdaWalletsStore.js b/app/stores/ada/AdaWalletsStore.js index d59edd2a28..d36b601cb7 100644 --- a/app/stores/ada/AdaWalletsStore.js +++ b/app/stores/ada/AdaWalletsStore.js @@ -7,7 +7,6 @@ import { stringifyError } from '../../utils/logging'; import Request from '../lib/LocalizedRequest'; -import { RestoreMode } from '../../actions/common/wallet-restore-actions'; import type { GenerateWalletRecoveryPhraseFunc } from '../../api/ada/index'; diff --git a/app/stores/ergo/ErgoRestoreStore.js b/app/stores/ergo/ErgoRestoreStore.js index bdd24ea323..ae18730987 100644 --- a/app/stores/ergo/ErgoRestoreStore.js +++ b/app/stores/ergo/ErgoRestoreStore.js @@ -3,7 +3,7 @@ import { action, } from 'mobx'; import Store from '../base/Store'; import { getApiForNetwork, ApiOptions } from '../../api/common/utils'; -import { RestoreMode } from '../../actions/common/wallet-restore-actions'; +import type { RestoreModeType } from '../../actions/common/wallet-restore-actions'; import { buildCheckAndCall, } from '../lib/check'; @@ -33,14 +33,13 @@ export default class ErgoRestoreStore extends Store { isValidMnemonic: {| mnemonic: string, - numberOfWords: number, - mode: $PropertyType | $PropertyType | $PropertyType, + mode: RestoreModeType, |} => boolean = request => { - const { mnemonic, numberOfWords } = request; - if (request.mode === RestoreMode.REGULAR_15 || request.mode === RestoreMode.REGULAR_24) { - return this.api.ergo.constructor.isValidMnemonic({ mnemonic, numberOfWords }); - } - throw new Error(`${nameof(this.isValidMnemonic)} unexpected mode ${request.mode}`); + const { mnemonic } = request; + return this.api.ergo.constructor.isValidMnemonic({ + mnemonic, + numberOfWords: request.mode.length + }); } _restoreToDb: void => Promise = async () => { diff --git a/app/stores/jormungandr/JormungandrWalletRestoreStore.js b/app/stores/jormungandr/JormungandrWalletRestoreStore.js index 5ec0cfb17f..f5d10c6bb3 100644 --- a/app/stores/jormungandr/JormungandrWalletRestoreStore.js +++ b/app/stores/jormungandr/JormungandrWalletRestoreStore.js @@ -4,7 +4,7 @@ import { action, runInAction, } from 'mobx'; import Store from '../base/Store'; import environment from '../../environment'; -import { RestoreMode } from '../../actions/common/wallet-restore-actions'; +import type { RestoreModeType } from '../../actions/common/wallet-restore-actions'; import { RustModule } from '../../api/ada/lib/cardanoCrypto/rustLoader'; import { TransferSource } from '../../types/TransferTypes'; import { @@ -154,17 +154,16 @@ export default class JormungandrWalletRestoreStore extends Store { isValidMnemonic: {| mnemonic: string, - numberOfWords: number, - mode: $PropertyType | $PropertyType | $PropertyType, + mode: RestoreModeType, |} => boolean = request => { - const { mnemonic, numberOfWords } = request; - if (request.mode === RestoreMode.PAPER) { + const { mnemonic } = request; + if (request.mode.extra === 'paper') { // note: validate with ADA since Jormungandr doesn't itself use paper wallets - return this.api.ada.isValidPaperMnemonic({ mnemonic, numberOfWords }); + return this.api.ada.isValidPaperMnemonic({ mnemonic, numberOfWords: request.mode.length }); } - if (request.mode === RestoreMode.REGULAR_15) { - return this.api.jormungandr.constructor.isValidMnemonic({ mnemonic, numberOfWords }); - } - throw new Error(`${nameof(this.isValidMnemonic)} unexpected mode ${request.mode}`); + return this.api.jormungandr.constructor.isValidMnemonic({ + mnemonic, + numberOfWords: request.mode.length + }); } } diff --git a/app/stores/stateless/modeInfo.js b/app/stores/stateless/modeInfo.js deleted file mode 100644 index 290c59be5c..0000000000 --- a/app/stores/stateless/modeInfo.js +++ /dev/null @@ -1,21 +0,0 @@ -// @flow - -import type { RestoreModeType, } from '../../actions/common/wallet-restore-actions'; -import { RestoreMode } from '../../actions/common/wallet-restore-actions'; -import config from '../../config'; - -export function isPaperMode(mode: RestoreModeType): boolean { - return mode === RestoreMode.PAPER; -} - -export function getWordsCount(mode: RestoreModeType): number { - switch (mode) { - case (RestoreMode.PAPER): - return config.wallets.YOROI_PAPER_RECOVERY_PHRASE_WORD_COUNT; - case (RestoreMode.REGULAR_24): - return config.wallets.DAEDALUS_SHELLEY_RECOVERY_PHRASE_WORD_COUNT; - case (RestoreMode.REGULAR_15): - default: - return config.wallets.WALLET_RECOVERY_PHRASE_WORD_COUNT; - } -} diff --git a/app/stores/toplevel/WalletRestoreStore.js b/app/stores/toplevel/WalletRestoreStore.js index 056d3de910..398efb2e67 100644 --- a/app/stores/toplevel/WalletRestoreStore.js +++ b/app/stores/toplevel/WalletRestoreStore.js @@ -8,7 +8,6 @@ import type { WalletRestoreMeta, RestoreModeType, } from '../../actions/common/wallet-restore-actions'; -import { RestoreMode } from '../../actions/common/wallet-restore-actions'; import type { PlateResponse } from '../../api/common/lib/crypto/plate'; import { unscramblePaperAdaMnemonic, @@ -26,7 +25,6 @@ import { import { v4Bip32PrivateToV3, } from '../../api/jormungandr/lib/crypto/utils'; -import { getWordsCount } from '../stateless/modeInfo'; import { RustModule } from '../../api/ada/lib/cardanoCrypto/rustLoader'; import { generateWalletRootKey, @@ -55,7 +53,7 @@ export default class AdaWalletRestoreStore extends Store { // only to handle the back button @observable walletRestoreMeta: void | WalletRestoreMeta; - @observable mode: RestoreModeType; + @observable mode: void | RestoreModeType; @observable recoveryResult: void | {| phrase: string, @@ -91,16 +89,16 @@ export default class AdaWalletRestoreStore extends Store { this.walletRestoreMeta = restoreMeta; this.step = RestoreSteps.VERIFY_MNEMONIC; - const wordCount = getWordsCount(this.mode); - let resolvedRecoveryPhrase = restoreMeta.recoveryPhrase; - if (this.mode === RestoreMode.UNSET) { + if (this.mode === undefined) { throw new Error( `${nameof(this._processRestoreMeta)} ${nameof(this.mode)} unset` ); } - if (this.mode === RestoreMode.PAPER) { + const mode = this.mode; + const wordCount = mode.length; + if (mode.type === 'byron-paper') { const [newPhrase] = unscramblePaperAdaMnemonic( restoreMeta.recoveryPhrase, wordCount, @@ -119,7 +117,7 @@ export default class AdaWalletRestoreStore extends Store { const { byronPlate, shelleyPlate, jormungandrPlate } = generatePlates( rootPk, this.selectedAccount, - this.mode, + mode, selectedNetwork, ); @@ -152,7 +150,7 @@ export default class AdaWalletRestoreStore extends Store { @action.bound reset(): void { - this.mode = RestoreMode.UNSET; + this.mode = undefined; this.step = RestoreSteps.START; this.walletRestoreMeta = undefined; this.recoveryResult = undefined; @@ -161,8 +159,7 @@ export default class AdaWalletRestoreStore extends Store { isValidMnemonic: {| mnemonic: string, - numberOfWords: number, - mode: $PropertyType | $PropertyType | $PropertyType, + mode: RestoreModeType, |} => boolean = request => { const { selectedNetwork } = this.stores.profile; if (selectedNetwork == null) throw new Error(`${nameof(this.isValidMnemonic)} no API selected`); @@ -181,14 +178,21 @@ export function generatePlates( shelleyPlate: void | PlateResponse, jormungandrPlate: void | PlateResponse, |} { - const addressCount = mode === RestoreMode.PAPER + if (mode == null) throw new Error(`${nameof(generatePlates)} restore mode unset`); + const addressCount = mode.extra === 'paper' ? NUMBER_OF_VERIFIED_ADDRESSES_PAPER : NUMBER_OF_VERIFIED_ADDRESSES; - const byronPlate = (mode === RestoreMode.REGULAR_15 || mode === RestoreMode.PAPER) && ( - isCardanoHaskell(network) || - isJormungandr(network) - ) + const shouldShowByronPlate = () => { + if (isCardanoHaskell(network) && mode.length === 15) { + return true; + } + if (isJormungandr(network)) { + return true; + } + return false; + }; + const byronPlate = shouldShowByronPlate() ? generateByronPlate( rootPk, accountIndex - HARD_DERIVATION_START, @@ -205,8 +209,7 @@ export function generatePlates( const shelleyPlate = ( isCardanoHaskell(network) && !isJormungandr(network) && - // TODO: needs to be change to "is cip1852" instead. - mode === RestoreMode.REGULAR_24 + mode.type === 'cip1852' ) ? generateShelleyPlate( rootPk, @@ -220,10 +223,32 @@ export function generatePlates( })() ) : undefined; - // TODO: we disable shelley restoration information for paper wallet restoration - // this is because we've temporarily disabled paper wallet creation for Shelley - // so no point in showing the Shelley checksum - const jormungandrPlate = isJormungandr(network) && mode === RestoreMode.REGULAR_15 + + const shouldShowJormungandrPlate = () => { + // TODO: we disable shelley restoration information for paper wallet restoration + // this is because we've temporarily disabled paper wallet creation for Shelley + // so no point in showing the Shelley checksum + if (mode.extra === 'paper') { + return false; + } + + if ( + isJormungandr(network) && + mode.type === 'cip1852' + ) { + return true; + } + if ( + isCardanoHaskell(network) && + mode.type === 'cip1852' && + mode.length === 15 + ) { + return true; + } + + return false; + }; + const jormungandrPlate = shouldShowJormungandrPlate() ? generateJormungandrPlate( v4Bip32PrivateToV3(rootPk), accountIndex - HARD_DERIVATION_START,