aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDan Finlay <flyswatter@users.noreply.github.com>2016-12-23 06:43:43 +0800
committerGitHub <noreply@github.com>2016-12-23 06:43:43 +0800
commit898e96fd6ae905932a60d60c42e2c3bddab65556 (patch)
tree23fba23503db1da05c1b397487aa71e3d9cb7d58
parent98527c1c254fe2d438191c73053dcf3223062ef3 (diff)
parentd3b2698f341e1d0dda86612cdf331e51067719c5 (diff)
downloadtangerine-wallet-browser-898e96fd6ae905932a60d60c42e2c3bddab65556.tar
tangerine-wallet-browser-898e96fd6ae905932a60d60c42e2c3bddab65556.tar.gz
tangerine-wallet-browser-898e96fd6ae905932a60d60c42e2c3bddab65556.tar.bz2
tangerine-wallet-browser-898e96fd6ae905932a60d60c42e2c3bddab65556.tar.lz
tangerine-wallet-browser-898e96fd6ae905932a60d60c42e2c3bddab65556.tar.xz
tangerine-wallet-browser-898e96fd6ae905932a60d60c42e2c3bddab65556.tar.zst
tangerine-wallet-browser-898e96fd6ae905932a60d60c42e2c3bddab65556.zip
Merge pull request #948 from MetaMask/RecoverLostAccounts
Auto-Recover accounts lost to BIP44 derivation fix
-rw-r--r--app/scripts/keyring-controller.js51
-rw-r--r--app/scripts/lib/config-manager.js2
-rw-r--r--app/scripts/lib/idStore-migrator.js7
-rw-r--r--app/scripts/lib/idStore.js3
-rw-r--r--app/scripts/metamask-controller.js80
-rw-r--r--development/states/lost-accounts.json91
-rw-r--r--test/integration/lib/first-time.js2
-rw-r--r--test/integration/lib/idStore-migrator-test.js74
-rw-r--r--test/integration/lib/keyring-controller-test.js122
-rw-r--r--test/unit/idStore-migration-test.js2
-rw-r--r--ui/app/actions.js12
-rw-r--r--ui/app/app.js18
-rw-r--r--ui/app/components/notice.js (renamed from ui/app/notice.js)20
-rw-r--r--ui/lib/lost-accounts-notice.js23
14 files changed, 316 insertions, 191 deletions
diff --git a/app/scripts/keyring-controller.js b/app/scripts/keyring-controller.js
index 2888e58a9..4e9193ab2 100644
--- a/app/scripts/keyring-controller.js
+++ b/app/scripts/keyring-controller.js
@@ -9,7 +9,6 @@ const encryptor = require('browser-passworder')
const normalize = require('./lib/sig-util').normalize
const messageManager = require('./lib/message-manager')
-const IdStoreMigrator = require('./lib/idStore-migrator')
const BN = ethUtil.BN
// Keyrings:
@@ -45,11 +44,6 @@ module.exports = class KeyringController extends EventEmitter {
this._unconfMsgCbs = {}
this.getNetwork = opts.getNetwork
-
- // TEMPORARY UNTIL FULL DEPRECATION:
- this.idStoreMigrator = new IdStoreMigrator({
- configManager: this.configManager,
- })
}
// Set Store
@@ -114,7 +108,6 @@ module.exports = class KeyringController extends EventEmitter {
conversionDate: this.configManager.getConversionDate(),
keyringTypes: this.keyringTypes.map(krt => krt.type),
identities: this.identities,
- lostAccounts: this.configManager.getLostAccounts(),
}
}
@@ -222,10 +215,7 @@ module.exports = class KeyringController extends EventEmitter {
// Temporarily also migrates any old-style vaults first, as well.
// (Pre MetaMask 3.0.0)
submitPassword (password) {
- return this.migrateOldVaultIfAny(password)
- .then(() => {
- return this.unlockKeyrings(password)
- })
+ return this.unlockKeyrings(password)
.then((keyrings) => {
this.keyrings = keyrings
return this.fullUpdate()
@@ -611,41 +601,6 @@ module.exports = class KeyringController extends EventEmitter {
// THESE METHODS ARE ONLY USED INTERNALLY TO THE KEYRING-CONTROLLER
// AND SO MAY BE CHANGED MORE LIBERALLY THAN THE ABOVE METHODS.
- // Migrate Old Vault If Any
- // @string password
- //
- // returns Promise()
- //
- // Temporary step used when logging in.
- // Checks if old style (pre-3.0.0) Metamask Vault exists.
- // If so, persists that vault in the new vault format
- // with the provided password, so the other unlock steps
- // may be completed without interruption.
- migrateOldVaultIfAny (password) {
- const shouldMigrate = !!this.configManager.getWallet() && !this.configManager.getVault()
- if (!shouldMigrate) {
- return Promise.resolve()
- }
-
- return this.idStoreMigrator.migratedVaultForPassword(password)
- .then((result) => {
- this.password = password
-
- if (result && shouldMigrate) {
- const { serialized, lostAccounts } = result
- this.configManager.setLostAccounts(lostAccounts)
- return this.restoreKeyring(serialized)
- .then(keyring => keyring.getAccounts())
- .then((accounts) => {
- this.configManager.setSelectedAccount(accounts[0])
- return this.persistAllKeyrings()
- })
- } else {
- return Promise.resolve()
- }
- })
- }
-
// Create First Key Tree
// returns @Promise
//
@@ -766,6 +721,10 @@ module.exports = class KeyringController extends EventEmitter {
// initializing the persisted keyrings to RAM.
unlockKeyrings (password) {
const encryptedVault = this.configManager.getVault()
+ if (!encryptedVault) {
+ throw new Error('Cannot unlock without a previous vault.')
+ }
+
return this.encryptor.decrypt(password, encryptedVault)
.then((vault) => {
this.password = password
diff --git a/app/scripts/lib/config-manager.js b/app/scripts/lib/config-manager.js
index d36ccf0db..ede877b76 100644
--- a/app/scripts/lib/config-manager.js
+++ b/app/scripts/lib/config-manager.js
@@ -121,7 +121,7 @@ ConfigManager.prototype.setVault = function (encryptedString) {
ConfigManager.prototype.getVault = function () {
var data = this.getData()
- return ('vault' in data) && data.vault
+ return data.vault
}
ConfigManager.prototype.getKeychains = function () {
diff --git a/app/scripts/lib/idStore-migrator.js b/app/scripts/lib/idStore-migrator.js
index 2e9418376..655aed0af 100644
--- a/app/scripts/lib/idStore-migrator.js
+++ b/app/scripts/lib/idStore-migrator.js
@@ -63,7 +63,12 @@ module.exports = class IdentityStoreMigrator {
return {
serialized,
- lostAccounts,
+ lostAccounts: lostAccounts.map((address) => {
+ return {
+ address,
+ privateKey: this.idStore.exportAccount(address),
+ }
+ }),
}
})
}
diff --git a/app/scripts/lib/idStore.js b/app/scripts/lib/idStore.js
index cf4353e48..66e5d966c 100644
--- a/app/scripts/lib/idStore.js
+++ b/app/scripts/lib/idStore.js
@@ -202,7 +202,8 @@ IdentityStore.prototype.submitPassword = function (password, cb) {
IdentityStore.prototype.exportAccount = function (address, cb) {
var privateKey = this._idmgmt.exportPrivateKey(address)
- cb(null, privateKey)
+ if (cb) cb(null, privateKey)
+ return privateKey
}
//
diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js
index 4b8fa4323..983a590d7 100644
--- a/app/scripts/metamask-controller.js
+++ b/app/scripts/metamask-controller.js
@@ -10,6 +10,7 @@ const ConfigManager = require('./lib/config-manager')
const extension = require('./lib/extension')
const autoFaucet = require('./lib/auto-faucet')
const nodeify = require('./lib/nodeify')
+const IdStoreMigrator = require('./lib/idStore-migrator')
module.exports = class MetamaskController {
@@ -44,6 +45,11 @@ module.exports = class MetamaskController {
this.checkTOSChange()
this.scheduleConversionInterval()
+
+ // TEMPORARY UNTIL FULL DEPRECATION:
+ this.idStoreMigrator = new IdStoreMigrator({
+ configManager: this.configManager,
+ })
}
getState () {
@@ -52,7 +58,9 @@ module.exports = class MetamaskController {
this.ethStore.getState(),
this.configManager.getConfig(),
this.keyringController.getState(),
- this.noticeController.getState()
+ this.noticeController.getState(), {
+ lostAccounts: this.configManager.getLostAccounts(),
+ }
)
}
@@ -71,6 +79,7 @@ module.exports = class MetamaskController {
setTOSHash: this.setTOSHash.bind(this),
checkTOSChange: this.checkTOSChange.bind(this),
setGasMultiplier: this.setGasMultiplier.bind(this),
+ markAccountsFound: this.markAccountsFound.bind(this),
// forward directly to keyringController
createNewVaultAndKeychain: nodeify(keyringController.createNewVaultAndKeychain).bind(keyringController),
@@ -78,7 +87,12 @@ module.exports = class MetamaskController {
placeSeedWords: nodeify(keyringController.placeSeedWords).bind(keyringController),
clearSeedWordCache: nodeify(keyringController.clearSeedWordCache).bind(keyringController),
setLocked: nodeify(keyringController.setLocked).bind(keyringController),
- submitPassword: nodeify(keyringController.submitPassword).bind(keyringController),
+ submitPassword: (password, cb) => {
+ this.migrateOldVaultIfAny(password)
+ .then(keyringController.submitPassword.bind(keyringController))
+ .then((newState) => { cb(null, newState) })
+ .catch((reason) => { cb(reason) })
+ },
addNewKeyring: nodeify(keyringController.addNewKeyring).bind(keyringController),
addNewAccount: nodeify(keyringController.addNewAccount).bind(keyringController),
setSelectedAccount: nodeify(keyringController.setSelectedAccount).bind(keyringController),
@@ -410,4 +424,66 @@ module.exports = class MetamaskController {
getStateNetwork () {
return this.state.network
}
+
+ markAccountsFound(cb) {
+ this.configManager.setLostAccounts([])
+ this.sendUpdate()
+ cb(null, this.getState())
+ }
+
+ // Migrate Old Vault If Any
+ // @string password
+ //
+ // returns Promise()
+ //
+ // Temporary step used when logging in.
+ // Checks if old style (pre-3.0.0) Metamask Vault exists.
+ // If so, persists that vault in the new vault format
+ // with the provided password, so the other unlock steps
+ // may be completed without interruption.
+ migrateOldVaultIfAny (password) {
+
+ if (!this.checkIfShouldMigrate()) {
+ return Promise.resolve(password)
+ }
+
+ const keyringController = this.keyringController
+
+ return this.idStoreMigrator.migratedVaultForPassword(password)
+ .then(this.restoreOldVaultAccounts.bind(this))
+ .then(this.restoreOldLostAccounts.bind(this))
+ .then(keyringController.persistAllKeyrings.bind(keyringController))
+ .then(() => password)
+ }
+
+ checkIfShouldMigrate() {
+ return !!this.configManager.getWallet() && !this.configManager.getVault()
+ }
+
+ restoreOldVaultAccounts(migratorOutput) {
+ const { serialized } = migratorOutput
+ return this.keyringController.restoreKeyring(serialized)
+ .then(() => migratorOutput)
+ }
+
+ restoreOldLostAccounts(migratorOutput) {
+ const { lostAccounts } = migratorOutput
+ if (lostAccounts) {
+ this.configManager.setLostAccounts(lostAccounts.map(acct => acct.address))
+ return this.importLostAccounts(migratorOutput)
+ }
+ return Promise.resolve(migratorOutput)
+ }
+
+ // IMPORT LOST ACCOUNTS
+ // @Object with key lostAccounts: @Array accounts <{ address, privateKey }>
+ // Uses the array's private keys to create a new Simple Key Pair keychain
+ // and add it to the keyring controller.
+ importLostAccounts ({ lostAccounts }) {
+ const privKeys = lostAccounts.map(acct => acct.privateKey)
+ return this.keyringController.restoreKeyring({
+ type: 'Simple Key Pair',
+ data: privKeys,
+ })
+ }
}
diff --git a/development/states/lost-accounts.json b/development/states/lost-accounts.json
new file mode 100644
index 000000000..9f1764c82
--- /dev/null
+++ b/development/states/lost-accounts.json
@@ -0,0 +1,91 @@
+{
+ "metamask": {
+ "currentFiat": "USD",
+ "lostAccounts": [
+ "0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc",
+ "0xec1adf982415d2ef5ec55899b9bfb8bc0f29251b"
+ ],
+ "conversionRate": 11.06608791,
+ "conversionDate": 1470421024,
+ "isInitialized": true,
+ "isUnlocked": true,
+ "currentDomain": "example.com",
+ "rpcTarget": "https://rawtestrpc.metamask.io/",
+ "identities": {
+ "0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc": {
+ "name": "Wallet 1",
+ "address": "0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc",
+ "mayBeFauceting": false
+ },
+ "0xec1adf982415d2ef5ec55899b9bfb8bc0f29251b": {
+ "name": "Wallet 2",
+ "address": "0xec1adf982415d2ef5ec55899b9bfb8bc0f29251b",
+ "mayBeFauceting": false
+ },
+ "0xeb9e64b93097bc15f01f13eae97015c57ab64823": {
+ "name": "Wallet 3",
+ "address": "0xeb9e64b93097bc15f01f13eae97015c57ab64823",
+ "mayBeFauceting": false
+ },
+ "0x704107d04affddd9b66ab9de3dd7b095852e9b69": {
+ "name": "Wallet 4",
+ "address": "0x704107d04affddd9b66ab9de3dd7b095852e9b69",
+ "mayBeFauceting": false
+ }
+ },
+ "unconfTxs": {},
+ "accounts": {
+ "0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc": {
+ "code": "0x",
+ "balance": "0x100000000000",
+ "nonce": "0x0",
+ "address": "0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc"
+ },
+ "0xec1adf982415d2ef5ec55899b9bfb8bc0f29251b": {
+ "code": "0x",
+ "nonce": "0x0",
+ "balance": "0x100000000000",
+ "address": "0xec1adf982415d2ef5ec55899b9bfb8bc0f29251b"
+ },
+ "0xeb9e64b93097bc15f01f13eae97015c57ab64823": {
+ "code": "0x",
+ "nonce": "0x0",
+ "balance": "0x100000000000",
+ "address": "0xeb9e64b93097bc15f01f13eae97015c57ab64823"
+ },
+ "0x704107d04affddd9b66ab9de3dd7b095852e9b69": {
+ "code": "0x",
+ "balance": "0x0",
+ "nonce": "0x0",
+ "address": "0x704107d04affddd9b66ab9de3dd7b095852e9b69"
+ }
+ },
+ "transactions": [],
+ "selectedAccount": "0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc",
+ "network": "2",
+ "seedWords": null,
+ "isDisclaimerConfirmed": true,
+ "unconfMsgs": {},
+ "messages": [],
+ "provider": {
+ "type": "testnet"
+ },
+ "selectedAccount": "0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc"
+ },
+ "appState": {
+ "menuOpen": false,
+ "currentView": {
+ "name": "accountDetail",
+ "detailView": null,
+ "context": "0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc"
+ },
+ "accountDetail": {
+ "subview": "transactions"
+ },
+ "currentDomain": "127.0.0.1:9966",
+ "transForward": true,
+ "isLoading": false,
+ "warning": null
+ },
+ "identities": {}
+}
diff --git a/test/integration/lib/first-time.js b/test/integration/lib/first-time.js
index 12c573db1..1811ccbd4 100644
--- a/test/integration/lib/first-time.js
+++ b/test/integration/lib/first-time.js
@@ -79,7 +79,7 @@ QUnit.test('agree to terms', function (assert) {
var createButton = app.find('button.primary')[0]
createButton.click()
- return wait(1500)
+ return wait(1000)
}).then(function() {
var detail = app.find('.account-detail-section')[0]
diff --git a/test/integration/lib/idStore-migrator-test.js b/test/integration/lib/idStore-migrator-test.js
new file mode 100644
index 000000000..338896171
--- /dev/null
+++ b/test/integration/lib/idStore-migrator-test.js
@@ -0,0 +1,74 @@
+var KeyringController = require('../../../app/scripts/keyring-controller')
+var ConfigManager = require('../../../app/scripts/lib/config-manager')
+var IdStoreMigrator = require('../../../app/scripts/lib/idStore-migrator')
+
+var oldStyleVault = require('../mocks/oldVault.json')
+var badStyleVault = require('../mocks/badVault.json')
+
+var STORAGE_KEY = 'metamask-config'
+var PASSWORD = '12345678'
+var FIRST_ADDRESS = '0x4dd5d356c5A016A220bCD69e82e5AF680a430d00'.toLowerCase()
+var SEED = 'fringe damage bounce extend tunnel afraid alert sound all soldier all dinner'
+
+var BAD_STYLE_FIRST_ADDRESS = '0xac39b311dceb2a4b2f5d8461c1cdaf756f4f7ae9'
+
+QUnit.module('Old Style Vaults', {
+ beforeEach: function () {
+ window.localStorage[STORAGE_KEY] = JSON.stringify(oldStyleVault)
+
+ this.configManager = new ConfigManager({
+ loadData: () => { return JSON.parse(window.localStorage[STORAGE_KEY]) },
+ setData: (data) => { window.localStorage[STORAGE_KEY] = JSON.stringify(data) },
+ })
+
+ this.migrator = new IdStoreMigrator({
+ configManager: this.configManager,
+ })
+ }
+})
+
+QUnit.test('migrator:isInitialized', function (assert) {
+ assert.ok(this.migrator)
+})
+
+QUnit.test('migrator:migratedVaultForPassword', function (assert) {
+ var done = assert.async()
+
+ this.migrator.migratedVaultForPassword(PASSWORD)
+ .then((result) => {
+ const { serialized, lostAccounts } = result
+ assert.equal(serialized.data.mnemonic, SEED, 'seed phrase recovered')
+ assert.equal(lostAccounts.length, 0, 'no lost accounts')
+ done()
+ })
+})
+
+QUnit.module('Old Style Vaults with bad HD seed', {
+ beforeEach: function () {
+ window.localStorage[STORAGE_KEY] = JSON.stringify(badStyleVault)
+
+ this.configManager = new ConfigManager({
+ loadData: () => { return JSON.parse(window.localStorage[STORAGE_KEY]) },
+ setData: (data) => { window.localStorage[STORAGE_KEY] = JSON.stringify(data) },
+ })
+
+ this.migrator = new IdStoreMigrator({
+ configManager: this.configManager,
+ })
+ }
+})
+
+QUnit.test('migrator:migratedVaultForPassword', function (assert) {
+ var done = assert.async()
+
+ this.migrator.migratedVaultForPassword(PASSWORD)
+ .then((result) => {
+ const { serialized, lostAccounts } = result
+
+ assert.equal(lostAccounts.length, 1, 'one lost account')
+ assert.equal(lostAccounts[0].address, '0xe15D894BeCB0354c501AE69429B05143679F39e0'.toLowerCase())
+ assert.ok(lostAccounts[0].privateKey, 'private key exported')
+ done()
+ })
+})
+
diff --git a/test/integration/lib/keyring-controller-test.js b/test/integration/lib/keyring-controller-test.js
deleted file mode 100644
index 666795a6d..000000000
--- a/test/integration/lib/keyring-controller-test.js
+++ /dev/null
@@ -1,122 +0,0 @@
-var KeyringController = require('../../../app/scripts/keyring-controller')
-var ConfigManager = require('../../../app/scripts/lib/config-manager')
-
-var oldStyleVault = require('../mocks/oldVault.json')
-var badStyleVault = require('../mocks/badVault.json')
-
-var STORAGE_KEY = 'metamask-config'
-var PASSWORD = '12345678'
-var FIRST_ADDRESS = '0x4dd5d356c5A016A220bCD69e82e5AF680a430d00'.toLowerCase()
-
-var BAD_STYLE_FIRST_ADDRESS = '0xac39b311dceb2a4b2f5d8461c1cdaf756f4f7ae9'
-
-
-QUnit.module('Old Style Vaults', {
- beforeEach: function () {
- window.localStorage[STORAGE_KEY] = JSON.stringify(oldStyleVault)
-
- this.configManager = new ConfigManager({
- loadData: () => { return JSON.parse(window.localStorage[STORAGE_KEY]) },
- setData: (data) => { window.localStorage[STORAGE_KEY] = JSON.stringify(data) },
- })
-
- this.keyringController = new KeyringController({
- configManager: this.configManager,
- getNetwork: () => { return '2' },
- })
-
- this.ethStore = {
- addAccount: () => {},
- removeAccount: () => {},
- }
-
- this.keyringController.setStore(this.ethStore)
- }
-})
-
-QUnit.test('keyringController:isInitialized', function (assert) {
- assert.ok(this.keyringController.getState().isInitialized)
-})
-
-QUnit.test('keyringController:submitPassword', function (assert) {
- var done = assert.async()
-
- this.keyringController.submitPassword(PASSWORD)
- .then((state) => {
- assert.ok(state.identities[FIRST_ADDRESS])
- assert.equal(state.lostAccounts.length, 0, 'no lost accounts')
- done()
- })
-})
-
-QUnit.test('keyringController:setLocked', function (assert) {
- var done = assert.async()
- var self = this
-
- this.keyringController.setLocked()
- .then(function() {
- assert.notOk(self.keyringController.password, 'password should be deallocated')
- assert.deepEqual(self.keyringController.keyrings, [], 'keyrings should be deallocated')
- done()
- })
- .catch((reason) => {
- assert.ifError(reason)
- done()
- })
-})
-
-QUnit.module('Old Style Vaults with bad HD seed', {
- beforeEach: function () {
- window.localStorage[STORAGE_KEY] = JSON.stringify(badStyleVault)
-
- this.configManager = new ConfigManager({
- loadData: () => { return JSON.parse(window.localStorage[STORAGE_KEY]) },
- setData: (data) => { window.localStorage[STORAGE_KEY] = JSON.stringify(data) },
- })
-
- this.keyringController = new KeyringController({
- configManager: this.configManager,
- getNetwork: () => { return '2' },
- })
-
- this.ethStore = {
- addAccount: () => {},
- removeAccount: () => {},
- }
-
- this.keyringController.setStore(this.ethStore)
- }
-})
-
-QUnit.test('keyringController:isInitialized', function (assert) {
- assert.ok(this.keyringController.getState().isInitialized, 'vault is initialized')
-})
-
-QUnit.test('keyringController:submitPassword', function (assert) {
- var done = assert.async()
-
- this.keyringController.submitPassword(PASSWORD)
- .then((state) => {
- assert.ok(state.identities[BAD_STYLE_FIRST_ADDRESS])
- assert.equal(state.lostAccounts.length, 1, 'one lost account')
- assert.equal(state.lostAccounts[0], '0xe15D894BeCB0354c501AE69429B05143679F39e0'.toLowerCase())
- assert.deepEqual(this.configManager.getLostAccounts(), state.lostAccounts, 'persisted')
- done()
- })
-})
-
-QUnit.test('keyringController:setLocked', function (assert) {
- var done = assert.async()
- var self = this
-
- this.keyringController.setLocked()
- .then(function() {
- assert.notOk(self.keyringController.password, 'password should be deallocated')
- assert.deepEqual(self.keyringController.keyrings, [], 'keyrings should be deallocated')
- done()
- })
- .catch((reason) => {
- assert.ifError(reason)
- done()
- })
-})
diff --git a/test/unit/idStore-migration-test.js b/test/unit/idStore-migration-test.js
index 66dd4683d..8532ac914 100644
--- a/test/unit/idStore-migration-test.js
+++ b/test/unit/idStore-migration-test.js
@@ -83,7 +83,7 @@ describe('IdentityStore to KeyringController migration', function() {
keyringController.configManager.setWallet('something')
const state = keyringController.getState()
assert(state.isInitialized, 'old vault counted as initialized.')
- assert.equal(state.lostAccounts.length, 0, 'no lost accounts')
+ assert(!state.lostAccounts, 'no lost accounts')
})
})
})
diff --git a/ui/app/actions.js b/ui/app/actions.js
index 1c32c9bb1..606460314 100644
--- a/ui/app/actions.js
+++ b/ui/app/actions.js
@@ -20,6 +20,7 @@ var actions = {
showNotice: showNotice,
CLEAR_NOTICES: 'CLEAR_NOTICES',
clearNotices: clearNotices,
+ markAccountsFound,
// intialize screen
AGREE_TO_DISCLAIMER: 'AGREE_TO_DISCLAIMER',
agreeToDisclaimer: agreeToDisclaimer,
@@ -591,6 +592,17 @@ function clearNotices () {
}
}
+function markAccountsFound() {
+ return (dispatch) => {
+ dispatch(this.showLoadingIndication())
+ background.markAccountsFound((err, newState) => {
+ dispatch(this.hideLoadingIndication())
+ if (err) return dispatch(this.showWarning(err.message))
+ dispatch(actions.updateMetamaskState(newState))
+ })
+ }
+}
+
//
// config
//
diff --git a/ui/app/app.js b/ui/app/app.js
index 2fa6415dd..6da0c3b1d 100644
--- a/ui/app/app.js
+++ b/ui/app/app.js
@@ -16,7 +16,8 @@ const AccountDetailScreen = require('./account-detail')
const SendTransactionScreen = require('./send')
const ConfirmTxScreen = require('./conf-tx')
// notice
-const NoticeScreen = require('./notice')
+const NoticeScreen = require('./components/notice')
+const generateLostAccountsNotice = require('../lib/lost-accounts-notice')
// other views
const ConfigScreen = require('./config')
const InfoScreen = require('./info')
@@ -55,6 +56,8 @@ function mapStateToProps (state) {
network: state.metamask.network,
provider: state.metamask.provider,
forgottenPassword: state.appState.forgottenPassword,
+ lastUnreadNotice: state.metamask.lastUnreadNotice,
+ lostAccounts: state.metamask.lostAccounts,
}
}
@@ -366,8 +369,19 @@ App.prototype.renderPrimary = function () {
}
}
+ // notices
if (!props.noActiveNotices) {
- return h(NoticeScreen, {key: 'NoticeScreen'})
+ return h(NoticeScreen, {
+ notice: props.lastUnreadNotice,
+ key: 'NoticeScreen',
+ onConfirm: () => props.dispatch(actions.markNoticeRead(props.lastUnreadNotice)),
+ })
+ } else if (props.lostAccounts && props.lostAccounts.length > 0) {
+ return h(NoticeScreen, {
+ notice: generateLostAccountsNotice(props.lostAccounts),
+ key: 'LostAccountsNotice',
+ onConfirm: () => props.dispatch(actions.markAccountsFound()),
+ })
}
// show current view
diff --git a/ui/app/notice.js b/ui/app/components/notice.js
index 3c2c746f2..00db734d7 100644
--- a/ui/app/notice.js
+++ b/ui/app/components/notice.js
@@ -2,18 +2,10 @@ const inherits = require('util').inherits
const Component = require('react').Component
const h = require('react-hyperscript')
const ReactMarkdown = require('react-markdown')
-const connect = require('react-redux').connect
-const actions = require('./actions')
const linker = require('extension-link-enabler')
const findDOMNode = require('react-dom').findDOMNode
-module.exports = connect(mapStateToProps)(Notice)
-
-function mapStateToProps (state) {
- return {
- lastUnreadNotice: state.metamask.lastUnreadNotice,
- }
-}
+module.exports = Notice
inherits(Notice, Component)
function Notice () {
@@ -21,9 +13,8 @@ function Notice () {
}
Notice.prototype.render = function () {
- const props = this.props
- const title = props.lastUnreadNotice.title
- const date = props.lastUnreadNotice.date
+ const { notice, onConfirm } = this.props
+ const { title, date, body } = notice
return (
h('.flex-column.flex-center.flex-grow', [
@@ -59,6 +50,7 @@ Notice.prototype.render = function () {
.markdown {
overflow-x: hidden;
}
+
.markdown h1, .markdown h2, .markdown h3 {
margin: 10px 0;
font-weight: bold;
@@ -92,13 +84,13 @@ Notice.prototype.render = function () {
},
}, [
h(ReactMarkdown, {
- source: props.lastUnreadNotice.body,
+ source: body,
skipHtml: true,
}),
]),
h('button', {
- onClick: () => props.dispatch(actions.markNoticeRead(props.lastUnreadNotice)),
+ onClick: onConfirm,
style: {
marginTop: '18px',
},
diff --git a/ui/lib/lost-accounts-notice.js b/ui/lib/lost-accounts-notice.js
new file mode 100644
index 000000000..948b13db6
--- /dev/null
+++ b/ui/lib/lost-accounts-notice.js
@@ -0,0 +1,23 @@
+const summary = require('../app/util').addressSummary
+
+module.exports = function (lostAccounts) {
+ return {
+ date: new Date().toDateString(),
+ title: 'Account Problem Caught',
+ body: `MetaMask has fixed a bug where some accounts were previously mis-generated. This was a rare issue, but you were affected!
+
+We have successfully imported the accounts that were mis-generated, but they will no longer be recovered with your normal seed phrase.
+
+We have marked the affected accounts as "Loose", and recommend you transfer ether and tokens away from those accounts, or export & back them up elsewhere.
+
+Your affected accounts are:
+${lostAccounts.map(acct => ` - ${summary(acct)}`).join('\n')}
+
+These accounts have been marked as "Loose" so they will be easy to recognize in the account list.
+
+For more information, please read [our blog post.][1]
+
+[1]: https://medium.com/metamask/metamask-3-migration-guide-914b79533cdd#.7d8ktj4h3
+ `,
+ }
+}