diff --git a/CHANGELOG.md b/CHANGELOG.md index ea53da490..1c0d3e5da 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ ## Current Master +- [#389](https://github.com/poanetwork/nifty-wallet/pull/389) - (Feature) Support 24 words mnemonic phrase - [#388](https://github.com/poanetwork/nifty-wallet/pull/388) - (Feature) "Send all" option for simple coin transfers - [#385](https://github.com/poanetwork/nifty-wallet/pull/385) - (Feature) Display value of current pending tx's nonce on send tx screen - [#384](https://github.com/poanetwork/nifty-wallet/pull/384) - (Fix) placement of HW Connect button title diff --git a/mascara/src/app/first-time/import-seed-phrase-screen.js b/mascara/src/app/first-time/import-seed-phrase-screen.js index b47a30920..f7cf76b29 100644 --- a/mascara/src/app/first-time/import-seed-phrase-screen.js +++ b/mascara/src/app/first-time/import-seed-phrase-screen.js @@ -41,7 +41,8 @@ class ImportSeedPhraseScreen extends Component { let seedPhraseError = null if (seedPhrase) { - if (this.parseSeedPhrase(seedPhrase).split(' ').length !== 12) { + const wordsCount = this.parseSeedPhrase(seedPhrase).split(' ').length + if (wordsCount !== 12 && wordsCount !== 24) { seedPhraseError = this.context.t('seedPhraseReq') } else if (!validateMnemonic(seedPhrase)) { seedPhraseError = this.context.t('invalidSeedPhrase') diff --git a/old-ui/app/keychains/hd/create-vault-complete.js b/old-ui/app/keychains/hd/create-vault-complete.js index f845695d2..f9c4f4033 100644 --- a/old-ui/app/keychains/hd/create-vault-complete.js +++ b/old-ui/app/keychains/hd/create-vault-complete.js @@ -1,15 +1,83 @@ -const inherits = require('util').inherits -const Component = require('react').Component -const connect = require('react-redux').connect +import { Component } from 'react' +import { connect } from 'react-redux' +import PropTypes from 'prop-types' const h = require('react-hyperscript') -const actions = require('../../../../ui/app/actions') -const exportAsFile = require('../../util').exportAsFile +const { confirmSeedWords, showAccountDetail } = require('../../../../ui/app/actions') +const { exportAsFile } = require('../../util') -module.exports = connect(mapStateToProps)(CreateVaultCompleteScreen) +class CreateVaultCompleteScreen extends Component { -inherits(CreateVaultCompleteScreen, Component) -function CreateVaultCompleteScreen () { - Component.call(this) + static propTypes = { + seed: PropTypes.string, + cachedSeed: PropTypes.string, + confirmSeedWords: PropTypes.func, + showAccountDetail: PropTypes.func, + }; + + render () { + const state = this.props + const seed = state.seed || state.cachedSeed || '' + const wordsCount = seed.split(' ').length + + return ( + + h('.initialize-screen.flex-column.flex-center.flex-grow', [ + + h('h3.flex-center.section-title', { + style: { + background: '#ffffff', + color: '#333333', + marginBottom: 8, + width: '100%', + padding: '30px 6px 6px 6px', + }, + }, [ + 'Vault Created', + ]), + + h('div', { + style: { + fontSize: '1em', + margin: '10px 30px', + textAlign: 'center', + }, + }, [ + h('div.error', `These ${wordsCount} words are the only way to restore your Nifty Wallet accounts.\nSave them somewhere safe and secret.`), + ]), + + h('textarea.twelve-word-phrase', { + readOnly: true, + value: seed, + }), + + h('button', { + onClick: () => this.confirmSeedWords() + .then(account => this.showAccountDetail(account)), + style: { + margin: '24px', + fontSize: '0.9em', + marginBottom: '10px', + }, + }, 'I\'ve copied it somewhere safe'), + + h('button', { + onClick: () => exportAsFile(`Nifty Wallet Seed Words`, seed), + style: { + margin: '10px', + fontSize: '0.9em', + }, + }, 'Save Seed Words As File'), + ]) + ) + } + + confirmSeedWords () { + return this.props.confirmSeedWords() + } + + showAccountDetail (account) { + return this.props.showAccountDetail(account) + } } function mapStateToProps (state) { @@ -19,71 +87,11 @@ function mapStateToProps (state) { } } -CreateVaultCompleteScreen.prototype.render = function () { - const state = this.props - const seed = state.seed || state.cachedSeed || '' - - return ( - - h('.initialize-screen.flex-column.flex-center.flex-grow', [ - - // // subtitle and nav - // h('.section-title.flex-row.flex-center', [ - // h('h2.page-subtitle', 'Vault Created'), - // ]), - - h('h3.flex-center.section-title', { - style: { - background: '#ffffff', - color: '#333333', - marginBottom: 8, - width: '100%', - padding: '30px 6px 6px 6px', - }, - }, [ - 'Vault Created', - ]), - - h('div', { - style: { - fontSize: '1em', - margin: '10px 30px', - textAlign: 'center', - }, - }, [ - h('div.error', 'These 12 words are the only way to restore your Nifty Wallet accounts.\nSave them somewhere safe and secret.'), - ]), - - h('textarea.twelve-word-phrase', { - readOnly: true, - value: seed, - }), - - h('button', { - onClick: () => this.confirmSeedWords() - .then(account => this.showAccountDetail(account)), - style: { - margin: '24px', - fontSize: '0.9em', - marginBottom: '10px', - }, - }, 'I\'ve copied it somewhere safe'), - - h('button', { - onClick: () => exportAsFile(`Nifty Wallet Seed Words`, seed), - style: { - margin: '10px', - fontSize: '0.9em', - }, - }, 'Save Seed Words As File'), - ]) - ) +function mapDispatchToProps (dispatch) { + return { + confirmSeedWords: () => dispatch(confirmSeedWords()), + showAccountDetail: (account) => dispatch(showAccountDetail(account)), + } } -CreateVaultCompleteScreen.prototype.confirmSeedWords = function () { - return this.props.dispatch(actions.confirmSeedWords()) -} - -CreateVaultCompleteScreen.prototype.showAccountDetail = function (account) { - return this.props.dispatch(actions.showAccountDetail(account)) -} +module.exports = connect(mapStateToProps, mapDispatchToProps)(CreateVaultCompleteScreen) diff --git a/old-ui/app/keychains/hd/restore-vault.js b/old-ui/app/keychains/hd/restore-vault.js index e9aa22741..25c092cc4 100644 --- a/old-ui/app/keychains/hd/restore-vault.js +++ b/old-ui/app/keychains/hd/restore-vault.js @@ -179,8 +179,9 @@ RestoreVaultScreen.prototype.createNewVaultAndRestore = function () { this.props.dispatch(actions.displayWarning(this.warning)) return } - if (seed.split(' ').length !== 12) { - this.warning = 'seed phrases are 12 words long' + const wordsCount = seed.split(' ').length + if (wordsCount !== 12 && wordsCount !== 24) { + this.warning = 'seed phrases are 12 or 24 words long' this.props.dispatch(actions.displayWarning(this.warning)) return } diff --git a/package-lock.json b/package-lock.json index a02b551ad..23a1ce76f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -25149,7 +25149,8 @@ "dependencies": { "mkdirp": { "version": "0.5.1", - "resolved": "", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", + "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", "dev": true, "requires": { "minimist": "0.0.8" @@ -26507,7 +26508,8 @@ }, "decompress": { "version": "4.2.0", - "resolved": "", + "resolved": "https://registry.npmjs.org/decompress/-/decompress-4.2.0.tgz", + "integrity": "sha1-eu3YVCflqS2s/lVnSnxQXpbQH50=", "requires": { "decompress-tar": "^4.0.0", "decompress-tarbz2": "^4.0.0", @@ -35601,7 +35603,8 @@ "dependencies": { "minimist": { "version": "1.2.0", - "resolved": "", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", "dev": true } } @@ -35622,7 +35625,8 @@ "dependencies": { "mkdirp": { "version": "0.5.1", - "resolved": "", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", + "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", "requires": { "minimist": "0.0.8" } diff --git a/test/e2e/test-cases/import-ganache-seed-phrase.spec.js b/test/e2e/test-cases/import-ganache-seed-phrase.spec.js index abb13995e..9b8bb95f5 100644 --- a/test/e2e/test-cases/import-ganache-seed-phrase.spec.js +++ b/test/e2e/test-cases/import-ganache-seed-phrase.spec.js @@ -1,6 +1,7 @@ const assert = require('assert') const { screens, menus, NETWORKS } = require('../elements') const testSeedPhrase = 'horn among position unable audit puzzle cannon apology gun autumn plug parrot' +const test24SeedPhrase = 'gravity trophy shrimp suspect sheriff avocado label trust dove tragic pitch title network myself spell task protect smooth sword diary brain blossom under bulb' const importGanacheSeedPhrase = async (f, account2, password) => { it('logs out', async () => { @@ -12,6 +13,38 @@ const importGanacheSeedPhrase = async (f, account2, password) => { await logOut.click() }) + it('restores from 24 seed phrase', async () => { + const restoreSeedLink = await f.waitUntilShowUp(screens.lock.linkRestore) + assert.equal(await restoreSeedLink.getText(), screens.lock.linkRestoreText) + await restoreSeedLink.click() + }) + + it('adds 24 words seed phrase', async () => { + const seedTextArea = await f.waitUntilShowUp(screens.restoreVault.textArea) + await seedTextArea.sendKeys(test24SeedPhrase) + + let field = await f.driver.findElement(screens.restoreVault.fieldPassword) + await field.sendKeys(password) + field = await f.driver.findElement(screens.restoreVault.fieldPasswordConfirm) + await field.sendKeys(password) + field = await f.waitUntilShowUp(screens.restoreVault.buttos.ok) + await f.click(field) + }) + + it('balance renders', async () => { + const balance = await f.waitUntilShowUp(screens.main.balance) + assert.equal(await balance.getText(), '0', "balance isn't correct") + }) + + it('logs out', async () => { + await f.setProvider(NETWORKS.LOCALHOST) + const menu = await f.waitUntilShowUp(menus.sandwich.menu) + await menu.click() + const logOut = await f.waitUntilShowUp(menus.sandwich.logOut) + assert.equal(await logOut.getText(), menus.sandwich.textLogOut) + await logOut.click() + }) + it('restores from seed phrase', async () => { const restoreSeedLink = await f.waitUntilShowUp(screens.lock.linkRestore) assert.equal(await restoreSeedLink.getText(), screens.lock.linkRestoreText)