Merge branch 'dev' into TxManager

feature/default_network_editable
Frankie 8 years ago
commit fa3e708f34
  1. 49
      app/scripts/keyring-controller.js
  2. 2
      app/scripts/lib/config-manager.js
  3. 7
      app/scripts/lib/idStore-migrator.js
  4. 3
      app/scripts/lib/idStore.js
  5. 4
      app/scripts/lib/inpage-provider.js
  6. 80
      app/scripts/metamask-controller.js
  7. 91
      development/states/lost-accounts.json
  8. 2
      test/integration/lib/first-time.js
  9. 74
      test/integration/lib/idStore-migrator-test.js
  10. 126
      test/integration/lib/keyring-controller-test.js
  11. 2
      test/unit/idStore-migration-test.js
  12. 12
      ui/app/actions.js
  13. 18
      ui/app/app.js
  14. 20
      ui/app/components/notice.js
  15. 23
      ui/lib/lost-accounts-notice.js

@ -6,7 +6,6 @@ const encryptor = require('browser-passworder')
const normalize = require('./lib/sig-util').normalize const normalize = require('./lib/sig-util').normalize
const messageManager = require('./lib/message-manager') const messageManager = require('./lib/message-manager')
const IdStoreMigrator = require('./lib/idStore-migrator')
const BN = ethUtil.BN const BN = ethUtil.BN
// Keyrings: // Keyrings:
@ -40,11 +39,6 @@ module.exports = class KeyringController extends EventEmitter {
this._unconfMsgCbs = {} this._unconfMsgCbs = {}
this.getNetwork = opts.getNetwork this.getNetwork = opts.getNetwork
// TEMPORARY UNTIL FULL DEPRECATION:
this.idStoreMigrator = new IdStoreMigrator({
configManager: this.configManager,
})
} }
// Set Store // Set Store
@ -107,7 +101,6 @@ module.exports = class KeyringController extends EventEmitter {
conversionDate: this.configManager.getConversionDate(), conversionDate: this.configManager.getConversionDate(),
keyringTypes: this.keyringTypes.map(krt => krt.type), keyringTypes: this.keyringTypes.map(krt => krt.type),
identities: this.identities, identities: this.identities,
lostAccounts: this.configManager.getLostAccounts(),
} }
} }
@ -215,10 +208,7 @@ module.exports = class KeyringController extends EventEmitter {
// Temporarily also migrates any old-style vaults first, as well. // Temporarily also migrates any old-style vaults first, as well.
// (Pre MetaMask 3.0.0) // (Pre MetaMask 3.0.0)
submitPassword (password) { submitPassword (password) {
return this.migrateOldVaultIfAny(password)
.then(() => {
return this.unlockKeyrings(password) return this.unlockKeyrings(password)
})
.then((keyrings) => { .then((keyrings) => {
this.keyrings = keyrings this.keyrings = keyrings
return this.fullUpdate() return this.fullUpdate()
@ -420,41 +410,6 @@ module.exports = class KeyringController extends EventEmitter {
// THESE METHODS ARE ONLY USED INTERNALLY TO THE KEYRING-CONTROLLER // THESE METHODS ARE ONLY USED INTERNALLY TO THE KEYRING-CONTROLLER
// AND SO MAY BE CHANGED MORE LIBERALLY THAN THE ABOVE METHODS. // 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 // Create First Key Tree
// returns @Promise // returns @Promise
// //
@ -575,6 +530,10 @@ module.exports = class KeyringController extends EventEmitter {
// initializing the persisted keyrings to RAM. // initializing the persisted keyrings to RAM.
unlockKeyrings (password) { unlockKeyrings (password) {
const encryptedVault = this.configManager.getVault() const encryptedVault = this.configManager.getVault()
if (!encryptedVault) {
throw new Error('Cannot unlock without a previous vault.')
}
return this.encryptor.decrypt(password, encryptedVault) return this.encryptor.decrypt(password, encryptedVault)
.then((vault) => { .then((vault) => {
this.password = password this.password = password

@ -118,7 +118,7 @@ ConfigManager.prototype.setVault = function (encryptedString) {
ConfigManager.prototype.getVault = function () { ConfigManager.prototype.getVault = function () {
var data = this.getData() var data = this.getData()
return ('vault' in data) && data.vault return data.vault
} }
ConfigManager.prototype.getKeychains = function () { ConfigManager.prototype.getKeychains = function () {

@ -63,7 +63,12 @@ module.exports = class IdentityStoreMigrator {
return { return {
serialized, serialized,
lostAccounts, lostAccounts: lostAccounts.map((address) => {
return {
address,
privateKey: this.idStore.exportAccount(address),
}
}),
} }
}) })
} }

@ -190,7 +190,8 @@ IdentityStore.prototype.submitPassword = function (password, cb) {
IdentityStore.prototype.exportAccount = function (address, cb) { IdentityStore.prototype.exportAccount = function (address, cb) {
var privateKey = this._idmgmt.exportPrivateKey(address) var privateKey = this._idmgmt.exportPrivateKey(address)
cb(null, privateKey) if (cb) cb(null, privateKey)
return privateKey
} }
// private // private

@ -72,13 +72,13 @@ MetamaskInpageProvider.prototype.send = function (payload) {
case 'eth_accounts': case 'eth_accounts':
// read from localStorage // read from localStorage
selectedAccount = self.publicConfigStore.get('selectedAddress') selectedAccount = self.publicConfigStore.get('selectedAccount')
result = selectedAccount ? [selectedAccount] : [] result = selectedAccount ? [selectedAccount] : []
break break
case 'eth_coinbase': case 'eth_coinbase':
// read from localStorage // read from localStorage
selectedAccount = self.publicConfigStore.get('selectedAddress') selectedAccount = self.publicConfigStore.get('selectedAccount')
result = selectedAccount || '0x0000000000000000000000000000000000000000' result = selectedAccount || '0x0000000000000000000000000000000000000000'
break break

@ -11,6 +11,7 @@ const ConfigManager = require('./lib/config-manager')
const extension = require('./lib/extension') const extension = require('./lib/extension')
const autoFaucet = require('./lib/auto-faucet') const autoFaucet = require('./lib/auto-faucet')
const nodeify = require('./lib/nodeify') const nodeify = require('./lib/nodeify')
const IdStoreMigrator = require('./lib/idStore-migrator')
module.exports = class MetamaskController { module.exports = class MetamaskController {
@ -54,6 +55,11 @@ module.exports = class MetamaskController {
this.checkTOSChange() this.checkTOSChange()
this.scheduleConversionInterval() this.scheduleConversionInterval()
// TEMPORARY UNTIL FULL DEPRECATION:
this.idStoreMigrator = new IdStoreMigrator({
configManager: this.configManager,
})
} }
getState () { getState () {
@ -63,7 +69,9 @@ module.exports = class MetamaskController {
this.configManager.getConfig(), this.configManager.getConfig(),
this.keyringController.getState(), this.keyringController.getState(),
this.txManager.getState(), this.txManager.getState(),
this.noticeController.getState() this.noticeController.getState(), {
lostAccounts: this.configManager.getLostAccounts(),
}
) )
} }
@ -83,6 +91,7 @@ module.exports = class MetamaskController {
setTOSHash: this.setTOSHash.bind(this), setTOSHash: this.setTOSHash.bind(this),
checkTOSChange: this.checkTOSChange.bind(this), checkTOSChange: this.checkTOSChange.bind(this),
setGasMultiplier: this.setGasMultiplier.bind(this), setGasMultiplier: this.setGasMultiplier.bind(this),
markAccountsFound: this.markAccountsFound.bind(this),
// forward directly to keyringController // forward directly to keyringController
createNewVaultAndKeychain: nodeify(keyringController.createNewVaultAndKeychain).bind(keyringController), createNewVaultAndKeychain: nodeify(keyringController.createNewVaultAndKeychain).bind(keyringController),
@ -90,7 +99,12 @@ module.exports = class MetamaskController {
placeSeedWords: nodeify(keyringController.placeSeedWords).bind(keyringController), placeSeedWords: nodeify(keyringController.placeSeedWords).bind(keyringController),
clearSeedWordCache: nodeify(keyringController.clearSeedWordCache).bind(keyringController), clearSeedWordCache: nodeify(keyringController.clearSeedWordCache).bind(keyringController),
setLocked: nodeify(keyringController.setLocked).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), addNewKeyring: nodeify(keyringController.addNewKeyring).bind(keyringController),
addNewAccount: nodeify(keyringController.addNewAccount).bind(keyringController), addNewAccount: nodeify(keyringController.addNewAccount).bind(keyringController),
setSelectedAccount: nodeify(keyringController.setSelectedAccount).bind(keyringController), setSelectedAccount: nodeify(keyringController.setSelectedAccount).bind(keyringController),
@ -429,4 +443,66 @@ module.exports = class MetamaskController {
getStateNetwork () { getStateNetwork () {
return this.state.network 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,
})
}
} }

@ -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": {}
}

@ -79,7 +79,7 @@ QUnit.test('agree to terms', function (assert) {
var createButton = app.find('button.primary')[0] var createButton = app.find('button.primary')[0]
createButton.click() createButton.click()
return wait(1500) return wait(1000)
}).then(function() { }).then(function() {
var detail = app.find('.account-detail-section')[0] var detail = app.find('.account-detail-section')[0]

@ -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()
})
})

@ -1,126 +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' },
txManager: {
getTxList: () => [],
getUnapprovedTxList: () => []
},
})
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()
})
})

@ -87,7 +87,7 @@ describe('IdentityStore to KeyringController migration', function() {
keyringController.configManager.setWallet('something') keyringController.configManager.setWallet('something')
const state = keyringController.getState() const state = keyringController.getState()
assert(state.isInitialized, 'old vault counted as initialized.') assert(state.isInitialized, 'old vault counted as initialized.')
assert.equal(state.lostAccounts.length, 0, 'no lost accounts') assert(!state.lostAccounts, 'no lost accounts')
}) })
}) })
}) })

@ -20,6 +20,7 @@ var actions = {
showNotice: showNotice, showNotice: showNotice,
CLEAR_NOTICES: 'CLEAR_NOTICES', CLEAR_NOTICES: 'CLEAR_NOTICES',
clearNotices: clearNotices, clearNotices: clearNotices,
markAccountsFound,
// intialize screen // intialize screen
AGREE_TO_DISCLAIMER: 'AGREE_TO_DISCLAIMER', AGREE_TO_DISCLAIMER: 'AGREE_TO_DISCLAIMER',
agreeToDisclaimer: agreeToDisclaimer, 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 // config
// //

@ -16,7 +16,8 @@ const AccountDetailScreen = require('./account-detail')
const SendTransactionScreen = require('./send') const SendTransactionScreen = require('./send')
const ConfirmTxScreen = require('./conf-tx') const ConfirmTxScreen = require('./conf-tx')
// notice // notice
const NoticeScreen = require('./notice') const NoticeScreen = require('./components/notice')
const generateLostAccountsNotice = require('../lib/lost-accounts-notice')
// other views // other views
const ConfigScreen = require('./config') const ConfigScreen = require('./config')
const InfoScreen = require('./info') const InfoScreen = require('./info')
@ -55,6 +56,8 @@ function mapStateToProps (state) {
network: state.metamask.network, network: state.metamask.network,
provider: state.metamask.provider, provider: state.metamask.provider,
forgottenPassword: state.appState.forgottenPassword, forgottenPassword: state.appState.forgottenPassword,
lastUnreadNotice: state.metamask.lastUnreadNotice,
lostAccounts: state.metamask.lostAccounts,
} }
} }
@ -366,8 +369,19 @@ App.prototype.renderPrimary = function () {
} }
} }
// notices
if (!props.noActiveNotices) { 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 // show current view

@ -2,18 +2,10 @@ const inherits = require('util').inherits
const Component = require('react').Component const Component = require('react').Component
const h = require('react-hyperscript') const h = require('react-hyperscript')
const ReactMarkdown = require('react-markdown') const ReactMarkdown = require('react-markdown')
const connect = require('react-redux').connect
const actions = require('./actions')
const linker = require('extension-link-enabler') const linker = require('extension-link-enabler')
const findDOMNode = require('react-dom').findDOMNode const findDOMNode = require('react-dom').findDOMNode
module.exports = connect(mapStateToProps)(Notice) module.exports = Notice
function mapStateToProps (state) {
return {
lastUnreadNotice: state.metamask.lastUnreadNotice,
}
}
inherits(Notice, Component) inherits(Notice, Component)
function Notice () { function Notice () {
@ -21,9 +13,8 @@ function Notice () {
} }
Notice.prototype.render = function () { Notice.prototype.render = function () {
const props = this.props const { notice, onConfirm } = this.props
const title = props.lastUnreadNotice.title const { title, date, body } = notice
const date = props.lastUnreadNotice.date
return ( return (
h('.flex-column.flex-center.flex-grow', [ h('.flex-column.flex-center.flex-grow', [
@ -59,6 +50,7 @@ Notice.prototype.render = function () {
.markdown { .markdown {
overflow-x: hidden; overflow-x: hidden;
} }
.markdown h1, .markdown h2, .markdown h3 { .markdown h1, .markdown h2, .markdown h3 {
margin: 10px 0; margin: 10px 0;
font-weight: bold; font-weight: bold;
@ -92,13 +84,13 @@ Notice.prototype.render = function () {
}, },
}, [ }, [
h(ReactMarkdown, { h(ReactMarkdown, {
source: props.lastUnreadNotice.body, source: body,
skipHtml: true, skipHtml: true,
}), }),
]), ]),
h('button', { h('button', {
onClick: () => props.dispatch(actions.markNoticeRead(props.lastUnreadNotice)), onClick: onConfirm,
style: { style: {
marginTop: '18px', marginTop: '18px',
}, },

@ -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
`,
}
}
Loading…
Cancel
Save