From 1ca6fff31719c4ff8d155dc9f7c88663a6719046 Mon Sep 17 00:00:00 2001 From: Kevin Serrano Date: Thu, 9 Mar 2017 11:31:00 -0800 Subject: [PATCH 01/47] Display owned addresses in datalist. --- ui/app/components/ens-input.js | 10 ++++++++++ ui/app/send.js | 2 ++ 2 files changed, 12 insertions(+) diff --git a/ui/app/components/ens-input.js b/ui/app/components/ens-input.js index ffc4eab4a..80c8deb22 100644 --- a/ui/app/components/ens-input.js +++ b/ui/app/components/ens-input.js @@ -21,6 +21,7 @@ function EnsInput () { EnsInput.prototype.render = function () { const props = this.props const opts = extend(props, { + list: 'addresses', onChange: () => { const network = this.props.network let resolverAddress = networkResolvers[network] @@ -46,6 +47,15 @@ EnsInput.prototype.render = function () { style: { width: '100%' }, }, [ h('input.large-input', opts), + h('datalist', + { + id: 'addresses', + }, + [ + Object.keys(props.identities).map((key) => { + return h('option', props.identities[key].address) + }), + ]), this.ensIcon(), ]) } diff --git a/ui/app/send.js b/ui/app/send.js index a281a5fcf..a2ce696cf 100644 --- a/ui/app/send.js +++ b/ui/app/send.js @@ -44,6 +44,7 @@ SendTransactionScreen.prototype.render = function () { var account = state.account var identity = state.identity var network = state.network + var identities = state.identities return ( @@ -153,6 +154,7 @@ SendTransactionScreen.prototype.render = function () { placeholder: 'Recipient Address', onChange: this.recipientDidChange.bind(this), network, + identities, }), ]), From d270cbc9d2f45b6dae184efbe6c405889ee8cba5 Mon Sep 17 00:00:00 2001 From: Kevin Serrano Date: Thu, 9 Mar 2017 13:07:38 -0800 Subject: [PATCH 02/47] Create distinct labels and names for addresses. --- app/scripts/controllers/preferences.js | 4 +++- ui/app/components/ens-input.js | 6 +++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/app/scripts/controllers/preferences.js b/app/scripts/controllers/preferences.js index 18fccf11b..c7f675a41 100644 --- a/app/scripts/controllers/preferences.js +++ b/app/scripts/controllers/preferences.js @@ -5,7 +5,9 @@ const extend = require('xtend') class PreferencesController { constructor (opts = {}) { - const initState = extend({ frequentRpcList: [] }, opts.initState) + const initState = extend({ + frequentRpcList: [], + }, opts.initState) this.store = new ObservableStore(initState) } diff --git a/ui/app/components/ens-input.js b/ui/app/components/ens-input.js index 80c8deb22..2b224fa3e 100644 --- a/ui/app/components/ens-input.js +++ b/ui/app/components/ens-input.js @@ -53,7 +53,11 @@ EnsInput.prototype.render = function () { }, [ Object.keys(props.identities).map((key) => { - return h('option', props.identities[key].address) + let identity = props.identities[key] + return h('option', { + value: identity.address, + label: identity.name, + }) }), ]), this.ensIcon(), From 9f6c04055419f54a730bcbd3f1da4c5f992db94d Mon Sep 17 00:00:00 2001 From: Kevin Serrano Date: Thu, 9 Mar 2017 13:58:42 -0800 Subject: [PATCH 03/47] Create persistence address book. --- app/scripts/controllers/address-book.js | 46 +++++++++++++++++++++++++ app/scripts/metamask-controller.js | 11 ++++++ ui/app/reducers/metamask.js | 1 + 3 files changed, 58 insertions(+) create mode 100644 app/scripts/controllers/address-book.js diff --git a/app/scripts/controllers/address-book.js b/app/scripts/controllers/address-book.js new file mode 100644 index 000000000..824a256d1 --- /dev/null +++ b/app/scripts/controllers/address-book.js @@ -0,0 +1,46 @@ +const ObservableStore = require('obs-store') +const extend = require('xtend') + +class AddressBookController { + + constructor (opts = {}) { + const initState = extend({ + addressBook: [], + }, opts.initState) + this.store = new ObservableStore(initState) + } + + // + // PUBLIC METHODS + // + + setAddressList (address, name) { + return this.addToAddressList(address, name) + .then((addressBook) => { + this.store.updateState({ + addressBook, + }) + return Promise.resolve() + }) + } + + addToAddressList (address, name) { + let addressBook = this.getAddressList() + let index = addressBook.findIndex((element) => { return element.address === address }) + if (index !== -1) { + addressBook.splice(index, 1) + } + addressBook.push({ + address, + name, + }) + return Promise.resolve(addressBook) + } + + getAddressList () { + return this.store.getState().addressBook + } + +} + +module.exports = AddressBookController diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index 536891dc6..6cdd8e96f 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -15,6 +15,7 @@ const PreferencesController = require('./controllers/preferences') const CurrencyController = require('./controllers/currency') const NoticeController = require('./notice-controller') const ShapeShiftController = require('./controllers/shapeshift') +const AddressBookController = require('./controllers/address-book') const MessageManager = require('./lib/message-manager') const PersonalMessageManager = require('./lib/personal-message-manager') const TxManager = require('./transaction-manager') @@ -50,6 +51,11 @@ module.exports = class MetamaskController extends EventEmitter { initState: initState.PreferencesController, }) + // address book controller + this.addressBookController = new AddressBookController({ + initState: initState.AddressBookController, + }) + // currency controller this.currencyController = new CurrencyController({ initState: initState.CurrencyController, @@ -124,6 +130,9 @@ module.exports = class MetamaskController extends EventEmitter { this.preferencesController.store.subscribe((state) => { this.store.updateState({ PreferencesController: state }) }) + this.addressBookController.store.subscribe((state) => { + this.store.updateState({ AddressBookController: state }) + }) this.currencyController.store.subscribe((state) => { this.store.updateState({ CurrencyController: state }) }) @@ -142,6 +151,7 @@ module.exports = class MetamaskController extends EventEmitter { this.personalMessageManager.memStore.subscribe(this.sendUpdate.bind(this)) this.keyringController.memStore.subscribe(this.sendUpdate.bind(this)) this.preferencesController.store.subscribe(this.sendUpdate.bind(this)) + this.addressBookController.store.subscribe(this.sendUpdate.bind(this)) this.currencyController.store.subscribe(this.sendUpdate.bind(this)) this.noticeController.memStore.subscribe(this.sendUpdate.bind(this)) this.shapeshiftController.store.subscribe(this.sendUpdate.bind(this)) @@ -219,6 +229,7 @@ module.exports = class MetamaskController extends EventEmitter { this.personalMessageManager.memStore.getState(), this.keyringController.memStore.getState(), this.preferencesController.store.getState(), + this.addressBookController.store.getState(), this.currencyController.store.getState(), this.noticeController.memStore.getState(), // config manager diff --git a/ui/app/reducers/metamask.js b/ui/app/reducers/metamask.js index a3c07d977..10d3b0461 100644 --- a/ui/app/reducers/metamask.js +++ b/ui/app/reducers/metamask.js @@ -19,6 +19,7 @@ function reduceMetamask (state, action) { noActiveNotices: true, lastUnreadNotice: undefined, frequentRpcList: [], + addressBook: [], }, state.metamask) switch (action.type) { From b296640f1b2e8cb808297715146a9370ff4f41ec Mon Sep 17 00:00:00 2001 From: Kevin Serrano Date: Thu, 9 Mar 2017 15:09:50 -0800 Subject: [PATCH 04/47] Rename functions for consistency. --- app/scripts/controllers/address-book.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/app/scripts/controllers/address-book.js b/app/scripts/controllers/address-book.js index 824a256d1..3c2a73dd7 100644 --- a/app/scripts/controllers/address-book.js +++ b/app/scripts/controllers/address-book.js @@ -14,8 +14,8 @@ class AddressBookController { // PUBLIC METHODS // - setAddressList (address, name) { - return this.addToAddressList(address, name) + setAddressBook (address, name) { + return this.addToAddressBook(address, name) .then((addressBook) => { this.store.updateState({ addressBook, @@ -24,9 +24,9 @@ class AddressBookController { }) } - addToAddressList (address, name) { - let addressBook = this.getAddressList() - let index = addressBook.findIndex((element) => { return element.address === address }) + addToAddressBook (address, name) { + let addressBook = this.getAddressBook() + let index = addressBook.findIndex((element) => { return element.address === address || element.name === name }) if (index !== -1) { addressBook.splice(index, 1) } @@ -37,7 +37,7 @@ class AddressBookController { return Promise.resolve(addressBook) } - getAddressList () { + getAddressBook () { return this.store.getState().addressBook } From b34ee4daa145c1d4eea2da6fd0cba0763e5c6483 Mon Sep 17 00:00:00 2001 From: Kevin Serrano Date: Thu, 9 Mar 2017 15:10:27 -0800 Subject: [PATCH 05/47] Allow for adding recently used addresses to address book. --- app/scripts/metamask-controller.js | 4 ++++ ui/app/actions.js | 14 ++++++++++++++ ui/app/components/ens-input.js | 13 +++++++++++-- ui/app/send.js | 13 +++++++++++-- 4 files changed, 40 insertions(+), 4 deletions(-) diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index 6cdd8e96f..45905db72 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -251,6 +251,7 @@ module.exports = class MetamaskController extends EventEmitter { const preferencesController = this.preferencesController const txManager = this.txManager const noticeController = this.noticeController + const addressBookController = this.addressBookController return { // etc @@ -278,6 +279,9 @@ module.exports = class MetamaskController extends EventEmitter { setDefaultRpc: nodeify(this.setDefaultRpc).bind(this), setCustomRpc: nodeify(this.setCustomRpc).bind(this), + // AddressController + setAddressBook: nodeify(addressBookController.setAddressBook).bind(addressBookController), + // KeyringController setLocked: nodeify(keyringController.setLocked).bind(keyringController), createNewVaultAndKeychain: nodeify(keyringController.createNewVaultAndKeychain).bind(keyringController), diff --git a/ui/app/actions.js b/ui/app/actions.js index d4fd7553b..e21b6257d 100644 --- a/ui/app/actions.js +++ b/ui/app/actions.js @@ -75,6 +75,8 @@ var actions = { // account detail screen SHOW_SEND_PAGE: 'SHOW_SEND_PAGE', showSendPage: showSendPage, + ADD_TO_ADDRESS_BOOK: 'ADD_TO_ADDRESS_BOOK', + addToAddressBook: addToAddressBook, REQUEST_ACCOUNT_EXPORT: 'REQUEST_ACCOUNT_EXPORT', requestExportAccount: requestExportAccount, EXPORT_ACCOUNT: 'EXPORT_ACCOUNT', @@ -696,6 +698,18 @@ function setRpcTarget (newRpc) { } } +function addToAddressBook (recipient, nickname) { + log.debug(`background.addToAddressBook`) + return (dispatch) => { + background.setAddressBook(recipient, nickname, (err, result) => { + if (err) { + log.error(err) + return dispatch(self.displayWarning('Address book failed to update')) + } + }) + } +} + function setProviderType (type) { log.debug(`background.setProviderType`) background.setProviderType(type) diff --git a/ui/app/components/ens-input.js b/ui/app/components/ens-input.js index 2b224fa3e..06efe6652 100644 --- a/ui/app/components/ens-input.js +++ b/ui/app/components/ens-input.js @@ -59,6 +59,12 @@ EnsInput.prototype.render = function () { label: identity.name, }) }), + props.addressBook.map((identity) => { + return h('option', { + value: identity.address, + label: identity.name, + }) + }), ]), this.ensIcon(), ]) @@ -94,11 +100,13 @@ EnsInput.prototype.lookupEnsName = function () { this.setState({ loadingEns: false, ensResolution: address, + nickname: recipient.trim(), hoverText: address + '\nClick to Copy', }) } }) .catch((reason) => { + log.error(reason) return this.setState({ loadingEns: false, ensFailure: true, @@ -109,10 +117,11 @@ EnsInput.prototype.lookupEnsName = function () { EnsInput.prototype.componentDidUpdate = function (prevProps, prevState) { const state = this.state || {} - const { ensResolution } = state + const ensResolution = state.ensResolution + const nickname = state.nickname || ' ' if (ensResolution && this.props.onChange && ensResolution !== prevState.ensResolution) { - this.props.onChange(ensResolution) + this.props.onChange(ensResolution, nickname) } } diff --git a/ui/app/send.js b/ui/app/send.js index a2ce696cf..eb32d5e06 100644 --- a/ui/app/send.js +++ b/ui/app/send.js @@ -20,6 +20,7 @@ function mapStateToProps (state) { identities: state.metamask.identities, warning: state.appState.warning, network: state.metamask.network, + addressBook: state.metamask.addressBook, } result.error = result.warning && result.warning.split('.')[0] @@ -45,6 +46,7 @@ SendTransactionScreen.prototype.render = function () { var identity = state.identity var network = state.network var identities = state.identities + var addressBook = state.addressBook return ( @@ -155,6 +157,7 @@ SendTransactionScreen.prototype.render = function () { onChange: this.recipientDidChange.bind(this), network, identities, + addressBook, }), ]), @@ -224,13 +227,17 @@ SendTransactionScreen.prototype.back = function () { this.props.dispatch(actions.backToAccountDetail(address)) } -SendTransactionScreen.prototype.recipientDidChange = function (recipient) { - this.setState({ recipient }) +SendTransactionScreen.prototype.recipientDidChange = function (recipient, nickname) { + this.setState({ + recipient: recipient, + nickname: nickname, + }) } SendTransactionScreen.prototype.onSubmit = function () { const state = this.state || {} const recipient = state.recipient || document.querySelector('input[name="address"]').value + const nickname = state.nickname || ' ' const input = document.querySelector('input[name="amount"]').value const value = util.normalizeEthStringToWei(input) const txData = document.querySelector('input[name="txData"]').value @@ -259,6 +266,8 @@ SendTransactionScreen.prototype.onSubmit = function () { this.props.dispatch(actions.hideWarning()) + this.props.dispatch(actions.addToAddressBook(recipient, nickname)) + var txParams = { from: this.props.address, value: '0x' + value.toString(16), From 2ab86b001dfc4ade4fc6df030175e64359b757e6 Mon Sep 17 00:00:00 2001 From: Kevin Serrano Date: Fri, 10 Mar 2017 09:34:13 -0800 Subject: [PATCH 06/47] Add comments. --- app/scripts/controllers/address-book.js | 23 +++++++++++++++++++---- ui/app/actions.js | 1 + ui/app/components/ens-input.js | 5 +++++ 3 files changed, 25 insertions(+), 4 deletions(-) diff --git a/app/scripts/controllers/address-book.js b/app/scripts/controllers/address-book.js index 3c2a73dd7..a75ef06ce 100644 --- a/app/scripts/controllers/address-book.js +++ b/app/scripts/controllers/address-book.js @@ -3,6 +3,10 @@ const extend = require('xtend') class AddressBookController { + + // Controller in charge of managing the address book functionality from the + // recipients field on the send screen. Manages a history of all saved + // addresses and all currently owned addresses. constructor (opts = {}) { const initState = extend({ addressBook: [], @@ -14,8 +18,9 @@ class AddressBookController { // PUBLIC METHODS // + // Sets a new address book in store by accepting a new address and nickname. setAddressBook (address, name) { - return this.addToAddressBook(address, name) + return this._addToAddressBook(address, name) .then((addressBook) => { this.store.updateState({ addressBook, @@ -24,8 +29,16 @@ class AddressBookController { }) } - addToAddressBook (address, name) { - let addressBook = this.getAddressBook() + // + // PRIVATE METHODS + // + + + // Performs the logic to add the address and name into the address book. The + // pushed object is an object of two fields. Current behavior does not set an + // upper limit to the number of addresses. + _addToAddressBook (address, name) { + let addressBook = this._getAddressBook() let index = addressBook.findIndex((element) => { return element.address === address || element.name === name }) if (index !== -1) { addressBook.splice(index, 1) @@ -37,7 +50,9 @@ class AddressBookController { return Promise.resolve(addressBook) } - getAddressBook () { + // Internal method to get the address book. Current persistence behavior + // should not require that this method be called from the UI directly. + _getAddressBook () { return this.store.getState().addressBook } diff --git a/ui/app/actions.js b/ui/app/actions.js index e21b6257d..4e0435ade 100644 --- a/ui/app/actions.js +++ b/ui/app/actions.js @@ -698,6 +698,7 @@ function setRpcTarget (newRpc) { } } +// Calls the addressBookController to add a new address. function addToAddressBook (recipient, nickname) { log.debug(`background.addToAddressBook`) return (dispatch) => { diff --git a/ui/app/components/ens-input.js b/ui/app/components/ens-input.js index 06efe6652..d5348ea62 100644 --- a/ui/app/components/ens-input.js +++ b/ui/app/components/ens-input.js @@ -47,11 +47,13 @@ EnsInput.prototype.render = function () { style: { width: '100%' }, }, [ h('input.large-input', opts), + // The address book functionality. h('datalist', { id: 'addresses', }, [ + // Corresponds to the addresses owned. Object.keys(props.identities).map((key) => { let identity = props.identities[key] return h('option', { @@ -59,6 +61,7 @@ EnsInput.prototype.render = function () { label: identity.name, }) }), + // Corresponds to previously sent-to addresses. props.addressBook.map((identity) => { return h('option', { value: identity.address, @@ -118,6 +121,8 @@ EnsInput.prototype.lookupEnsName = function () { EnsInput.prototype.componentDidUpdate = function (prevProps, prevState) { const state = this.state || {} const ensResolution = state.ensResolution + // If an address is sent without a nickname, meaning not from ENS or from + // the user's own accounts, a default of a one-space string is used. const nickname = state.nickname || ' ' if (ensResolution && this.props.onChange && ensResolution !== prevState.ensResolution) { From dc2d614da68ca851c3b1c0c108e7c2f22185751c Mon Sep 17 00:00:00 2001 From: Kevin Serrano Date: Fri, 10 Mar 2017 09:52:05 -0800 Subject: [PATCH 07/47] Add basic tests. --- test/unit/address-book-controller.js | 28 +++++++++++++++++++++++++++ test/unit/currency-controller-test.js | 2 +- 2 files changed, 29 insertions(+), 1 deletion(-) create mode 100644 test/unit/address-book-controller.js diff --git a/test/unit/address-book-controller.js b/test/unit/address-book-controller.js new file mode 100644 index 000000000..f00547167 --- /dev/null +++ b/test/unit/address-book-controller.js @@ -0,0 +1,28 @@ +const assert = require('assert') +const extend = require('xtend') +const AddressBookController = require('../../app/scripts/controllers/address-book') + +describe('address-book-controller', function() { + var addressBookController + + beforeEach(function() { + addressBookController = new AddressBookController() + }) + + describe('addres book management', function () { + describe('#_getAddressBook', function () { + it('should be empty by default.', function () { + assert.equal(addressBookController._getAddressBook().length, 0) + }) + }) + describe('#setAddressBook', function () { + it('should properly set a new address.', function () { + addressBookController.setAddressBook('0x01234', 'test') + var addressBook = addressBookController._getAddressBook() + assert.equal(addressBook.length, 1, 'incorrect address book length.') + assert.equal(addressBook[0].address, '0x01234', 'incorrect addresss') + assert.equal(addressBook[0].name, 'test', 'incorrect nickname') + }) + }) + }) +}) diff --git a/test/unit/currency-controller-test.js b/test/unit/currency-controller-test.js index dd7fa91e0..079f8b488 100644 --- a/test/unit/currency-controller-test.js +++ b/test/unit/currency-controller-test.js @@ -7,7 +7,7 @@ const rp = require('request-promise') const nock = require('nock') const CurrencyController = require('../../app/scripts/controllers/currency') -describe('config-manager', function() { +describe('currency-controller', function() { var currencyController beforeEach(function() { From 7182a2be4332e7802c8d2375b9380490aed7ca0b Mon Sep 17 00:00:00 2001 From: Kevin Serrano Date: Fri, 10 Mar 2017 10:05:10 -0800 Subject: [PATCH 08/47] Improve duplicate checking in address book. --- app/scripts/controllers/address-book.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/scripts/controllers/address-book.js b/app/scripts/controllers/address-book.js index a75ef06ce..914fbded2 100644 --- a/app/scripts/controllers/address-book.js +++ b/app/scripts/controllers/address-book.js @@ -39,12 +39,12 @@ class AddressBookController { // upper limit to the number of addresses. _addToAddressBook (address, name) { let addressBook = this._getAddressBook() - let index = addressBook.findIndex((element) => { return element.address === address || element.name === name }) + let index = addressBook.findIndex((element) => { return element.address.toLowerCase() === address.toLowerCase() || element.name === name }) if (index !== -1) { addressBook.splice(index, 1) } addressBook.push({ - address, + address: address, name, }) return Promise.resolve(addressBook) From c47f7f6a765e748c33ffde6ff38e881199251eda Mon Sep 17 00:00:00 2001 From: Kevin Serrano Date: Fri, 10 Mar 2017 10:34:18 -0800 Subject: [PATCH 09/47] Add another test for duplicates. --- test/unit/address-book-controller.js | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/test/unit/address-book-controller.js b/test/unit/address-book-controller.js index f00547167..51dc898b8 100644 --- a/test/unit/address-book-controller.js +++ b/test/unit/address-book-controller.js @@ -23,6 +23,13 @@ describe('address-book-controller', function() { assert.equal(addressBook[0].address, '0x01234', 'incorrect addresss') assert.equal(addressBook[0].name, 'test', 'incorrect nickname') }) + + it('should reject duplicates.', function () { + addressBookController.setAddressBook('0x01234', 'test') + addressBookController.setAddressBook('0x01234', 'test') + var addressBook = addressBookController._getAddressBook() + assert.equal(addressBook.length, 1, 'incorrect address book length.') + }) }) }) }) From 7dcab52a9e6aa0532a712cb4e502846a383efc94 Mon Sep 17 00:00:00 2001 From: Kevin Serrano Date: Fri, 10 Mar 2017 10:34:46 -0800 Subject: [PATCH 10/47] Connect keyring controller to address book to prevent additional duplicates. --- app/scripts/controllers/address-book.js | 22 ++++++++++++++++++---- app/scripts/metamask-controller.js | 10 +++++----- 2 files changed, 23 insertions(+), 9 deletions(-) diff --git a/app/scripts/controllers/address-book.js b/app/scripts/controllers/address-book.js index 914fbded2..0e97cc477 100644 --- a/app/scripts/controllers/address-book.js +++ b/app/scripts/controllers/address-book.js @@ -7,11 +7,12 @@ class AddressBookController { // Controller in charge of managing the address book functionality from the // recipients field on the send screen. Manages a history of all saved // addresses and all currently owned addresses. - constructor (opts = {}) { + constructor (opts = {}, keyringController) { const initState = extend({ addressBook: [], }, opts.initState) this.store = new ObservableStore(initState) + this.keyringController = keyringController } // @@ -39,9 +40,16 @@ class AddressBookController { // upper limit to the number of addresses. _addToAddressBook (address, name) { let addressBook = this._getAddressBook() - let index = addressBook.findIndex((element) => { return element.address.toLowerCase() === address.toLowerCase() || element.name === name }) - if (index !== -1) { - addressBook.splice(index, 1) + let identities = this._getIdentities() + + let addressBookIndex = addressBook.findIndex((element) => { return element.address.toLowerCase() === address.toLowerCase() || element.name === name }) + let identitiesIndex = Object.keys(identities).findIndex((element) => { return element.toLowerCase() === address.toLowerCase() }) + // trigger this condition if we own this address--no need to overwrite. + if (identitiesIndex !== -1) { + return Promise.resolve(addressBook) + // trigger this condition if we've seen this address before--may need to update nickname. + } else if (addressBookIndex !== -1) { + addressBook.splice(addressBookIndex, 1) } addressBook.push({ address: address, @@ -56,6 +64,12 @@ class AddressBookController { return this.store.getState().addressBook } + // Retrieves identities from the keyring controller in order to avoid + // duplication + _getIdentities () { + return this.keyringController.memStore.getState().identities + } + } module.exports = AddressBookController diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index 45905db72..1fcee61da 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -51,11 +51,6 @@ module.exports = class MetamaskController extends EventEmitter { initState: initState.PreferencesController, }) - // address book controller - this.addressBookController = new AddressBookController({ - initState: initState.AddressBookController, - }) - // currency controller this.currencyController = new CurrencyController({ initState: initState.CurrencyController, @@ -86,6 +81,11 @@ module.exports = class MetamaskController extends EventEmitter { autoFaucet(address) }) + // address book controller + this.addressBookController = new AddressBookController({ + initState: initState.AddressBookController, + }, this.keyringController) + // tx mgmt this.txManager = new TxManager({ initState: initState.TransactionManager, From d85cc7ec4ef4e93d424ef4debbca1bb27b952a4e Mon Sep 17 00:00:00 2001 From: Kevin Serrano Date: Fri, 10 Mar 2017 10:48:07 -0800 Subject: [PATCH 11/47] Add test to account for prevention of identities duplication. --- test/unit/address-book-controller.js | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/test/unit/address-book-controller.js b/test/unit/address-book-controller.js index 51dc898b8..f345b0328 100644 --- a/test/unit/address-book-controller.js +++ b/test/unit/address-book-controller.js @@ -2,11 +2,27 @@ const assert = require('assert') const extend = require('xtend') const AddressBookController = require('../../app/scripts/controllers/address-book') +const mockKeyringController = { + memStore: { + getState: function () { + return { + identities: { + '0x0aaa' : { + address: '0x0aaa', + name: 'owned', + } + } + } + } + } +} + + describe('address-book-controller', function() { var addressBookController beforeEach(function() { - addressBookController = new AddressBookController() + addressBookController = new AddressBookController({}, mockKeyringController) }) describe('addres book management', function () { @@ -30,6 +46,11 @@ describe('address-book-controller', function() { var addressBook = addressBookController._getAddressBook() assert.equal(addressBook.length, 1, 'incorrect address book length.') }) + it('should not add any identities that are under user control', function () { + addressBookController.setAddressBook('0x0aaa', ' ') + var addressBook = addressBookController._getAddressBook() + assert.equal(addressBook.length, 0, 'incorrect address book length.') + }) }) }) }) From 4933e2e2eefa103d6b0d3f3ce264aaf661b50291 Mon Sep 17 00:00:00 2001 From: Kevin Serrano Date: Mon, 13 Mar 2017 16:37:53 -0700 Subject: [PATCH 12/47] Limit the number of addresses stored in our book. --- app/scripts/controllers/address-book.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/app/scripts/controllers/address-book.js b/app/scripts/controllers/address-book.js index 0e97cc477..c66eb2bd4 100644 --- a/app/scripts/controllers/address-book.js +++ b/app/scripts/controllers/address-book.js @@ -50,7 +50,11 @@ class AddressBookController { // trigger this condition if we've seen this address before--may need to update nickname. } else if (addressBookIndex !== -1) { addressBook.splice(addressBookIndex, 1) + } else if (addressBook.length > 15) { + addressBook.shift() } + + addressBook.push({ address: address, name, From e4feb50f6f909082b6a65351bbb9bfaeebaf9028 Mon Sep 17 00:00:00 2001 From: Kevin Serrano Date: Mon, 13 Mar 2017 16:41:25 -0700 Subject: [PATCH 13/47] Display Custom RPC on lock screen. --- ui/app/app.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/app/app.js b/ui/app/app.js index 2bc92b54c..b3e86220a 100644 --- a/ui/app/app.js +++ b/ui/app/app.js @@ -266,7 +266,7 @@ App.prototype.renderNetworkDropdown = function () { this.renderCustomOption(props.provider), this.renderCommonRpc(rpcList, props.provider), - props.isUnlocked && h(DropMenuItem, { + h(DropMenuItem, { label: 'Custom RPC', closeMenu: () => this.setState({ isNetworkMenuOpen: false }), action: () => this.props.dispatch(actions.showConfigPage()), From b10f370c7463c177df16650a80a57f9875db7685 Mon Sep 17 00:00:00 2001 From: Kevin Serrano Date: Mon, 13 Mar 2017 16:43:34 -0700 Subject: [PATCH 14/47] Render config screen from unlock. --- ui/app/app.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/ui/app/app.js b/ui/app/app.js index b3e86220a..9c1ba8a3a 100644 --- a/ui/app/app.js +++ b/ui/app/app.js @@ -400,6 +400,10 @@ App.prototype.renderPrimary = function () { log.debug('rendering restore vault screen') return h(HDRestoreVaultScreen, {key: 'HDRestoreVaultScreen'}) + case 'config': + log.debug('rendering config screen from unlock screen.') + return h(ConfigScreen, {key: 'config'}) + default: log.debug('rendering locked screen') return h(UnlockScreen, {key: 'locked'}) From 7d0bfdf076fbc05d276b7a26df4cb745ca117691 Mon Sep 17 00:00:00 2001 From: Kevin Serrano Date: Mon, 13 Mar 2017 16:46:33 -0700 Subject: [PATCH 15/47] Changelog addition. --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0f070d9a9..c8e73c286 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ ## Current Master - Allow sending to ENS names in send form on Ropsten. +- Can now change network to custom RPC URL from lock screen. ## 3.4.0 2017-3-8 From c136cb6cd428b54f1a2da36b60f164c6ec3410b2 Mon Sep 17 00:00:00 2001 From: Kevin Serrano Date: Tue, 14 Mar 2017 11:44:54 -0700 Subject: [PATCH 16/47] Add to changelog. --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0f070d9a9..45030cc15 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ ## Current Master - Allow sending to ENS names in send form on Ropsten. +- Added an address book functionality that remembers the last 15 unique addresses sent to. ## 3.4.0 2017-3-8 From 1ec7930c75f10390e3e4ab553e1032056d1d2c2f Mon Sep 17 00:00:00 2001 From: Kevin Serrano Date: Tue, 14 Mar 2017 14:04:52 -0700 Subject: [PATCH 17/47] Minor change in removing opts object. --- ui/app/components/ens-input.js | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/ui/app/components/ens-input.js b/ui/app/components/ens-input.js index d5348ea62..facf29d97 100644 --- a/ui/app/components/ens-input.js +++ b/ui/app/components/ens-input.js @@ -48,10 +48,7 @@ EnsInput.prototype.render = function () { }, [ h('input.large-input', opts), // The address book functionality. - h('datalist', - { - id: 'addresses', - }, + h('datalist#addresses', [ // Corresponds to the addresses owned. Object.keys(props.identities).map((key) => { From 956482be96cb125a19ab73ce633b375fb97f95e9 Mon Sep 17 00:00:00 2001 From: Kevin Serrano Date: Tue, 14 Mar 2017 17:05:27 -0700 Subject: [PATCH 18/47] Fix development to use new currency state. --- development/states/account-detail-with-shapeshift-tx.json | 2 +- development/states/account-detail-with-transaction-history.json | 2 +- development/states/account-detail.json | 2 +- development/states/account-list-with-imported.json | 2 +- development/states/accounts-loose.json | 2 +- development/states/accounts.json | 2 +- development/states/compilation-bug.json | 2 +- development/states/conf-tx.json | 2 +- development/states/config.json | 2 +- development/states/first-time.json | 2 +- development/states/import-private-key-warning.json | 2 +- development/states/import-private-key.json | 2 +- development/states/locked.json | 2 +- development/states/lost-accounts.json | 2 +- development/states/new-account.json | 2 +- development/states/notice.json | 2 +- development/states/pending-signature.json | 2 +- development/states/pending-tx-insufficient.json | 2 +- development/states/personal-sign.json | 2 +- development/states/private-network.json | 2 +- development/states/restore-vault.json | 2 +- development/states/send.json | 2 +- development/states/shapeshift.json | 2 +- development/states/terms-and-conditions.json | 2 +- 24 files changed, 24 insertions(+), 24 deletions(-) diff --git a/development/states/account-detail-with-shapeshift-tx.json b/development/states/account-detail-with-shapeshift-tx.json index c14062350..97d5e9f06 100644 --- a/development/states/account-detail-with-shapeshift-tx.json +++ b/development/states/account-detail-with-shapeshift-tx.json @@ -1,6 +1,6 @@ { "metamask": { - "currentFiat": "USD", + "currentCurrency": "USD", "conversionRate": 11.06608791, "conversionDate": 1470421024, "isInitialized": true, diff --git a/development/states/account-detail-with-transaction-history.json b/development/states/account-detail-with-transaction-history.json index 8cb495656..a6bcc2658 100644 --- a/development/states/account-detail-with-transaction-history.json +++ b/development/states/account-detail-with-transaction-history.json @@ -1,6 +1,6 @@ { "metamask": { - "currentFiat": "USD", + "currentCurrency": "USD", "conversionRate": 11.06608791, "conversionDate": 1470421024, "isInitialized": true, diff --git a/development/states/account-detail.json b/development/states/account-detail.json index 644e3674d..6d11c1deb 100644 --- a/development/states/account-detail.json +++ b/development/states/account-detail.json @@ -1,6 +1,6 @@ { "metamask": { - "currentFiat": "USD", + "currentCurrency": "USD", "conversionRate": 11.06608791, "conversionDate": 1470421024, "isInitialized": true, diff --git a/development/states/account-list-with-imported.json b/development/states/account-list-with-imported.json index b450b4fb8..41d586db6 100644 --- a/development/states/account-list-with-imported.json +++ b/development/states/account-list-with-imported.json @@ -14,7 +14,7 @@ } }, "unconfTxs": {}, - "currentFiat": "USD", + "currentCurrency": "USD", "conversionRate": 10.19458075, "conversionDate": 1484696373, "noActiveNotices": true, diff --git a/development/states/accounts-loose.json b/development/states/accounts-loose.json index 542e207d4..df51f0d7e 100644 --- a/development/states/accounts-loose.json +++ b/development/states/accounts-loose.json @@ -30,7 +30,7 @@ } }, "unconfTxs": {}, - "currentFiat": "USD", + "currentCurrency": "USD", "conversionRate": 0, "conversionDate": "N/A", "noActiveNotices": true, diff --git a/development/states/accounts.json b/development/states/accounts.json index 4eef1145f..c8ff40ed9 100644 --- a/development/states/accounts.json +++ b/development/states/accounts.json @@ -41,7 +41,7 @@ } }, "unconfTxs": {}, - "currentFiat": "USD", + "currentCurrency": "USD", "conversionRate": 11.84461814, "conversionDate": 1476226414, "accounts": { diff --git a/development/states/compilation-bug.json b/development/states/compilation-bug.json index b33e59c5c..588d069d4 100644 --- a/development/states/compilation-bug.json +++ b/development/states/compilation-bug.json @@ -41,7 +41,7 @@ "simulationFails": true } }, - "currentFiat": "USD", + "currentCurrency": "USD", "conversionRate": 7.69158136, "conversionDate": 1482279663, "noActiveNotices": true, diff --git a/development/states/conf-tx.json b/development/states/conf-tx.json index b44d23ad8..0f1a13751 100644 --- a/development/states/conf-tx.json +++ b/development/states/conf-tx.json @@ -48,7 +48,7 @@ "gasPrice": "4a817c800" } }, - "currentFiat": "USD", + "currentCurrency": "USD", "conversionRate": 12.7200827, "conversionDate": 1487363041, "noActiveNotices": true, diff --git a/development/states/config.json b/development/states/config.json index de1df1831..551c0e3fa 100644 --- a/development/states/config.json +++ b/development/states/config.json @@ -1,6 +1,6 @@ { "metamask": { - "currentFiat": "USD", + "currentCurrency": "USD", "conversionRate": 11.06608791, "conversionDate": 1470421024, "isInitialized": true, diff --git a/development/states/first-time.json b/development/states/first-time.json index 3554ee911..683a61fdf 100644 --- a/development/states/first-time.json +++ b/development/states/first-time.json @@ -6,7 +6,7 @@ "identities": {}, "frequentRpcList": [], "unapprovedTxs": {}, - "currentFiat": "USD", + "currentCurrency": "USD", "conversionRate": 12.7527416, "conversionDate": 1487624341, "noActiveNotices": false, diff --git a/development/states/import-private-key-warning.json b/development/states/import-private-key-warning.json index a2d33ed7f..80ebc650d 100644 --- a/development/states/import-private-key-warning.json +++ b/development/states/import-private-key-warning.json @@ -10,7 +10,7 @@ } }, "unconfTxs": {}, - "currentFiat": "USD", + "currentCurrency": "USD", "conversionRate": 10.1219126, "conversionDate": 1484695442, "noActiveNotices": true, diff --git a/development/states/import-private-key.json b/development/states/import-private-key.json index 73fbd68db..bd455c6d5 100644 --- a/development/states/import-private-key.json +++ b/development/states/import-private-key.json @@ -10,7 +10,7 @@ } }, "unconfTxs": {}, - "currentFiat": "USD", + "currentCurrency": "USD", "conversionRate": 10.10788584, "conversionDate": 1484694362, "noActiveNotices": true, diff --git a/development/states/locked.json b/development/states/locked.json index 8e6ed7860..866394e86 100644 --- a/development/states/locked.json +++ b/development/states/locked.json @@ -6,7 +6,7 @@ "rpcTarget": "https://rawtestrpc.metamask.io/", "identities": {}, "unconfTxs": {}, - "currentFiat": "USD", + "currentCurrency": "USD", "conversionRate": 11.4379398, "conversionDate": 1473358355, "accounts": {}, diff --git a/development/states/lost-accounts.json b/development/states/lost-accounts.json index dcba7a764..4f50092b0 100644 --- a/development/states/lost-accounts.json +++ b/development/states/lost-accounts.json @@ -1,6 +1,6 @@ { "metamask": { - "currentFiat": "USD", + "currentCurrency": "USD", "lostAccounts": [ "0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc", "0xec1adf982415d2ef5ec55899b9bfb8bc0f29251b" diff --git a/development/states/new-account.json b/development/states/new-account.json index a056e14e7..dc2341b0e 100644 --- a/development/states/new-account.json +++ b/development/states/new-account.json @@ -14,7 +14,7 @@ } }, "unconfTxs": {}, - "currentFiat": "USD", + "currentCurrency": "USD", "conversionRate": 10.92067835, "conversionDate": 1478282884, "network": null, diff --git a/development/states/notice.json b/development/states/notice.json index 1cdcefcfb..efeffab08 100644 --- a/development/states/notice.json +++ b/development/states/notice.json @@ -9,7 +9,7 @@ } }, "unconfTxs": {}, - "currentFiat": "USD", + "currentCurrency": "USD", "conversionRate": 8.3533002, "conversionDate": 1481671082, "noActiveNotices": false, diff --git a/development/states/pending-signature.json b/development/states/pending-signature.json index 0d49e0229..7deaee8f7 100644 --- a/development/states/pending-signature.json +++ b/development/states/pending-signature.json @@ -22,7 +22,7 @@ } }, "unconfTxs": {}, - "currentFiat": "USD", + "currentCurrency": "USD", "conversionRate": 11.02269525, "conversionDate": 1472076963, "accounts": { diff --git a/development/states/pending-tx-insufficient.json b/development/states/pending-tx-insufficient.json index 10ecc826d..18be84089 100644 --- a/development/states/pending-tx-insufficient.json +++ b/development/states/pending-tx-insufficient.json @@ -31,7 +31,7 @@ "maxCost": "de234b52e4a0800" } }, - "currentFiat": "USD", + "currentCurrency": "USD", "conversionRate": 12.59854817, "conversionDate": 1487662141, "noActiveNotices": true, diff --git a/development/states/personal-sign.json b/development/states/personal-sign.json index 2fc71f448..8ded6205c 100644 --- a/development/states/personal-sign.json +++ b/development/states/personal-sign.json @@ -18,7 +18,7 @@ } }, "unapprovedTxs": {}, - "currentFiat": "USD", + "currentCurrency": "USD", "conversionRate": 13.2126613, "conversionDate": 1487888522, "noActiveNotices": true, diff --git a/development/states/private-network.json b/development/states/private-network.json index 155aa6584..8cdcb4eeb 100644 --- a/development/states/private-network.json +++ b/development/states/private-network.json @@ -16,7 +16,7 @@ } }, "unconfTxs": {}, - "currentFiat": "USD", + "currentCurrency": "USD", "conversionRate": 9.52855776, "conversionDate": 1479756513, "accounts": { diff --git a/development/states/restore-vault.json b/development/states/restore-vault.json index ad136c78e..0d6c2610d 100644 --- a/development/states/restore-vault.json +++ b/development/states/restore-vault.json @@ -6,7 +6,7 @@ "rpcTarget": "https://rawtestrpc.metamask.io/", "identities": {}, "unconfTxs": {}, - "currentFiat": "USD", + "currentCurrency": "USD", "conversionRate": 0, "conversionDate": "N/A", "accounts": {}, diff --git a/development/states/send.json b/development/states/send.json index 3f52185af..73ac62f65 100644 --- a/development/states/send.json +++ b/development/states/send.json @@ -22,7 +22,7 @@ } }, "unapprovedTxs": {}, - "currentFiat": "USD", + "currentCurrency": "USD", "conversionRate": 16.88200327, "conversionDate": 1489013762, "noActiveNotices": true, diff --git a/development/states/shapeshift.json b/development/states/shapeshift.json index bc10143d0..bfd4b7c16 100644 --- a/development/states/shapeshift.json +++ b/development/states/shapeshift.json @@ -22,7 +22,7 @@ } }, "unconfTxs": {}, - "currentFiat": "USD", + "currentCurrency": "USD", "conversionRate": 11.21274318, "conversionDate": 1472159644, "accounts": { diff --git a/development/states/terms-and-conditions.json b/development/states/terms-and-conditions.json index b995e446f..f5ebe8254 100644 --- a/development/states/terms-and-conditions.json +++ b/development/states/terms-and-conditions.json @@ -5,7 +5,7 @@ "rpcTarget": "https://rawtestrpc.metamask.io/", "identities": {}, "unconfTxs": {}, - "currentFiat": "USD", + "currentCurrency": "USD", "conversionRate": 8.18703468, "conversionDate": 1481755832, "network": "3", From 177f2dc948f794f6796d74e3ef2a581048625bb1 Mon Sep 17 00:00:00 2001 From: Kevin Serrano Date: Tue, 14 Mar 2017 17:05:47 -0700 Subject: [PATCH 19/47] Fix description of some migrations. --- app/scripts/migrations/010.js | 2 +- app/scripts/migrations/011.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/scripts/migrations/010.js b/app/scripts/migrations/010.js index 48a841bc1..c0cc56ae4 100644 --- a/app/scripts/migrations/010.js +++ b/app/scripts/migrations/010.js @@ -2,7 +2,7 @@ const version = 10 /* -This migration breaks out the CurrencyController substate +This migration breaks out the ShapeShiftController substate */ diff --git a/app/scripts/migrations/011.js b/app/scripts/migrations/011.js index bf283ef98..0d5d6d307 100644 --- a/app/scripts/migrations/011.js +++ b/app/scripts/migrations/011.js @@ -2,7 +2,7 @@ const version = 11 /* -This migration breaks out the CurrencyController substate +This migration removes the discaimer state from our app, which was integrated into our notices. */ From 37ffcfcf0e518ed8843022cd079ce09d0b2239ff Mon Sep 17 00:00:00 2001 From: Kevin Serrano Date: Tue, 14 Mar 2017 17:06:16 -0700 Subject: [PATCH 20/47] Rename variables to proper currency state variables. --- app/scripts/metamask-controller.js | 2 +- ui/app/actions.js | 8 ++++---- ui/app/components/fiat-value.js | 4 ++-- ui/app/config.js | 12 ++++++------ ui/app/reducers/metamask.js | 5 +---- 5 files changed, 14 insertions(+), 17 deletions(-) diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index 536891dc6..2eaa53200 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -621,7 +621,7 @@ module.exports = class MetamaskController extends EventEmitter { this.currencyController.updateConversionRate() const data = { conversionRate: this.currencyController.getConversionRate(), - currentFiat: this.currencyController.getCurrentCurrency(), + currentCurrency: this.currencyController.getCurrentCurrency(), conversionDate: this.currencyController.getConversionDate(), } cb(null, data) diff --git a/ui/app/actions.js b/ui/app/actions.js index d4fd7553b..0027a5f67 100644 --- a/ui/app/actions.js +++ b/ui/app/actions.js @@ -71,7 +71,7 @@ var actions = { SHOW_CONF_TX_PAGE: 'SHOW_CONF_TX_PAGE', SHOW_CONF_MSG_PAGE: 'SHOW_CONF_MSG_PAGE', SET_CURRENT_FIAT: 'SET_CURRENT_FIAT', - setCurrentFiat: setCurrentFiat, + setCurrentCurrency: setCurrentCurrency, // account detail screen SHOW_SEND_PAGE: 'SHOW_SEND_PAGE', showSendPage: showSendPage, @@ -328,10 +328,10 @@ function showInfoPage () { } } -function setCurrentFiat (currencyCode) { +function setCurrentCurrency (currencyCode) { return (dispatch) => { dispatch(this.showLoadingIndication()) - log.debug(`background.setCurrentFiat`) + log.debug(`background.setCurrentCurrency`) background.setCurrentCurrency(currencyCode, (err, data) => { dispatch(this.hideLoadingIndication()) if (err) { @@ -341,7 +341,7 @@ function setCurrentFiat (currencyCode) { dispatch({ type: this.SET_CURRENT_FIAT, value: { - currentFiat: data.currentFiat, + currentCurrency: data.currentCurrency, conversionRate: data.conversionRate, conversionDate: data.conversionDate, }, diff --git a/ui/app/components/fiat-value.js b/ui/app/components/fiat-value.js index 13ee48245..298809b30 100644 --- a/ui/app/components/fiat-value.js +++ b/ui/app/components/fiat-value.js @@ -9,7 +9,7 @@ module.exports = connect(mapStateToProps)(FiatValue) function mapStateToProps (state) { return { conversionRate: state.metamask.conversionRate, - currentFiat: state.metamask.currentFiat, + currentCurrency: state.metamask.currentCurrency, } } @@ -34,7 +34,7 @@ FiatValue.prototype.render = function () { fiatTooltipNumber = 'Unknown' } - var fiatSuffix = props.currentFiat + var fiatSuffix = props.currentCurrency return fiatDisplay(fiatDisplayNumber, fiatSuffix) } diff --git a/ui/app/config.js b/ui/app/config.js index 00a4cba88..3f0507f48 100644 --- a/ui/app/config.js +++ b/ui/app/config.js @@ -125,19 +125,19 @@ function rpcValidation (newRpc, state) { } function currentConversionInformation (metamaskState, state) { - var currentFiat = metamaskState.currentFiat + var currentCurrency = metamaskState.currentCurrency var conversionDate = metamaskState.conversionDate return h('div', [ h('span', {style: { fontWeight: 'bold', paddingRight: '10px'}}, 'Current Conversion'), h('span', {style: { fontWeight: 'bold', paddingRight: '10px', fontSize: '13px'}}, `Updated ${Date(conversionDate)}`), - h('select#currentFiat', { + h('select#currentCurrency', { onChange (event) { event.preventDefault() - var element = document.getElementById('currentFiat') - var newFiat = element.value - state.dispatch(actions.setCurrentFiat(newFiat)) + var element = document.getElementById('currentCurrency') + var newCurrency = element.value + state.dispatch(actions.setCurrentCurrency(newCurrency)) }, - defaultValue: currentFiat, + defaultValue: currentCurrency, }, currencies.map((currency) => { return h('option', {key: currency.code, value: currency.code}, `${currency.code} - ${currency.name}`) }) diff --git a/ui/app/reducers/metamask.js b/ui/app/reducers/metamask.js index a3c07d977..269f8d272 100644 --- a/ui/app/reducers/metamask.js +++ b/ui/app/reducers/metamask.js @@ -13,9 +13,6 @@ function reduceMetamask (state, action) { rpcTarget: 'https://rawtestrpc.metamask.io/', identities: {}, unapprovedTxs: {}, - currentFiat: 'USD', - conversionRate: 0, - conversionDate: 'N/A', noActiveNotices: true, lastUnreadNotice: undefined, frequentRpcList: [], @@ -126,7 +123,7 @@ function reduceMetamask (state, action) { case actions.SET_CURRENT_FIAT: return extend(metamaskState, { - currentFiat: action.value.currentFiat, + currentCurrency: action.value.currentCurrency, conversionRate: action.value.conversionRate, conversionDate: action.value.conversionDate, }) From b6e2eaf7b1d14fcca8ed614791937a5ccbfc00dd Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Thu, 16 Mar 2017 11:16:03 -0700 Subject: [PATCH 21/47] Remove eth-lightwallet --- CHANGELOG.md | 1 + app/scripts/lib/id-management.js | 90 ----- app/scripts/lib/idStore-migrator.js | 80 ---- app/scripts/lib/idStore.js | 343 ------------------ app/scripts/metamask-controller.js | 38 +- package.json | 1 - test/integration/lib/idStore-migrator-test.js | 92 ----- test/unit/id-management-test.js | 35 -- test/unit/idStore-migration-test.js | 83 ----- test/unit/idStore-test.js | 142 -------- 10 files changed, 2 insertions(+), 903 deletions(-) delete mode 100644 app/scripts/lib/id-management.js delete mode 100644 app/scripts/lib/idStore-migrator.js delete mode 100644 app/scripts/lib/idStore.js delete mode 100644 test/integration/lib/idStore-migrator-test.js delete mode 100644 test/unit/id-management-test.js delete mode 100644 test/unit/idStore-migration-test.js delete mode 100644 test/unit/idStore-test.js diff --git a/CHANGELOG.md b/CHANGELOG.md index 77dcc53c6..3cd7b90ff 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ - Allow sending to ENS names in send form on Ropsten. - Added an address book functionality that remembers the last 15 unique addresses sent to. - Can now change network to custom RPC URL from lock screen. +- Removed support for old, lightwallet based vault. Users who have not opened app in over a month will need to recover with their seed phrase. This will allow Firefox support sooner. ## 3.4.0 2017-3-8 diff --git a/app/scripts/lib/id-management.js b/app/scripts/lib/id-management.js deleted file mode 100644 index 90b3fdb13..000000000 --- a/app/scripts/lib/id-management.js +++ /dev/null @@ -1,90 +0,0 @@ -/* ID Management - * - * This module exists to hold the decrypted credentials for the current session. - * It therefore exposes sign methods, because it is able to perform these - * with noa dditional authentication, because its very instantiation - * means the vault is unlocked. - */ - -const ethUtil = require('ethereumjs-util') -const Transaction = require('ethereumjs-tx') - -module.exports = IdManagement - -function IdManagement (opts) { - if (!opts) opts = {} - - this.keyStore = opts.keyStore - this.derivedKey = opts.derivedKey - this.configManager = opts.configManager - this.hdPathString = "m/44'/60'/0'/0" - - this.getAddresses = function () { - return this.keyStore.getAddresses(this.hdPathString).map(function (address) { return '0x' + address }) - } - - this.signTx = function (txParams) { - - // normalize values - txParams.gasPrice = ethUtil.intToHex(txParams.gasPrice) - txParams.to = ethUtil.addHexPrefix(txParams.to) - txParams.from = ethUtil.addHexPrefix(txParams.from.toLowerCase()) - txParams.value = ethUtil.addHexPrefix(txParams.value) - txParams.data = ethUtil.addHexPrefix(txParams.data) - txParams.gasLimit = ethUtil.addHexPrefix(txParams.gasLimit || txParams.gas) - txParams.nonce = ethUtil.addHexPrefix(txParams.nonce) - var tx = new Transaction(txParams) - - // sign tx - var privKeyHex = this.exportPrivateKey(txParams.from) - var privKey = ethUtil.toBuffer(privKeyHex) - tx.sign(privKey) - - // Add the tx hash to the persisted meta-tx object - var txHash = ethUtil.bufferToHex(tx.hash()) - var metaTx = this.configManager.getTx(txParams.metamaskId) - metaTx.hash = txHash - this.configManager.updateTx(metaTx) - - // return raw serialized tx - var rawTx = ethUtil.bufferToHex(tx.serialize()) - return rawTx - } - - this.signMsg = function (address, message) { - // sign message - var privKeyHex = this.exportPrivateKey(address.toLowerCase()) - var privKey = ethUtil.toBuffer(privKeyHex) - var msgSig = ethUtil.ecsign(new Buffer(message.replace('0x', ''), 'hex'), privKey) - var rawMsgSig = ethUtil.bufferToHex(concatSig(msgSig.v, msgSig.r, msgSig.s)) - return rawMsgSig - } - - this.getSeed = function () { - return this.keyStore.getSeed(this.derivedKey) - } - - this.exportPrivateKey = function (address) { - var privKeyHex = ethUtil.addHexPrefix(this.keyStore.exportPrivateKey(address, this.derivedKey, this.hdPathString)) - return privKeyHex - } -} - -function padWithZeroes (number, length) { - var myString = '' + number - while (myString.length < length) { - myString = '0' + myString - } - return myString -} - -function concatSig (v, r, s) { - const rSig = ethUtil.fromSigned(r) - const sSig = ethUtil.fromSigned(s) - const vSig = ethUtil.bufferToInt(v) - const rStr = padWithZeroes(ethUtil.toUnsigned(rSig).toString('hex'), 64) - const sStr = padWithZeroes(ethUtil.toUnsigned(sSig).toString('hex'), 64) - const vStr = ethUtil.stripHexPrefix(ethUtil.intToHex(vSig)) - return ethUtil.addHexPrefix(rStr.concat(sStr, vStr)).toString('hex') -} - diff --git a/app/scripts/lib/idStore-migrator.js b/app/scripts/lib/idStore-migrator.js deleted file mode 100644 index 62d21eee7..000000000 --- a/app/scripts/lib/idStore-migrator.js +++ /dev/null @@ -1,80 +0,0 @@ -const IdentityStore = require('./idStore') -const HdKeyring = require('eth-hd-keyring') -const sigUtil = require('eth-sig-util') -const normalize = sigUtil.normalize -const denodeify = require('denodeify') - -module.exports = class IdentityStoreMigrator { - - constructor ({ configManager }) { - this.configManager = configManager - const hasOldVault = this.hasOldVault() - if (!hasOldVault) { - this.idStore = new IdentityStore({ configManager }) - } - } - - migratedVaultForPassword (password) { - const hasOldVault = this.hasOldVault() - const configManager = this.configManager - - if (!this.idStore) { - this.idStore = new IdentityStore({ configManager }) - } - - if (!hasOldVault) { - return Promise.resolve(null) - } - - const idStore = this.idStore - const submitPassword = denodeify(idStore.submitPassword.bind(idStore)) - - return submitPassword(password) - .then(() => { - const serialized = this.serializeVault() - return this.checkForLostAccounts(serialized) - }) - } - - serializeVault () { - const mnemonic = this.idStore._idmgmt.getSeed() - const numberOfAccounts = this.idStore._getAddresses().length - - return { - type: 'HD Key Tree', - data: { mnemonic, numberOfAccounts }, - } - } - - checkForLostAccounts (serialized) { - const hd = new HdKeyring() - return hd.deserialize(serialized.data) - .then((hexAccounts) => { - const newAccounts = hexAccounts.map(normalize) - const oldAccounts = this.idStore._getAddresses().map(normalize) - const lostAccounts = oldAccounts.reduce((result, account) => { - if (newAccounts.includes(account)) { - return result - } else { - result.push(account) - return result - } - }, []) - - return { - serialized, - lostAccounts: lostAccounts.map((address) => { - return { - address, - privateKey: this.idStore.exportAccount(address), - } - }), - } - }) - } - - hasOldVault () { - const wallet = this.configManager.getWallet() - return wallet - } -} diff --git a/app/scripts/lib/idStore.js b/app/scripts/lib/idStore.js deleted file mode 100644 index 01474035e..000000000 --- a/app/scripts/lib/idStore.js +++ /dev/null @@ -1,343 +0,0 @@ -const EventEmitter = require('events').EventEmitter -const inherits = require('util').inherits -const ethUtil = require('ethereumjs-util') -const KeyStore = require('eth-lightwallet').keystore -const clone = require('clone') -const extend = require('xtend') -const autoFaucet = require('./auto-faucet') -const DEFAULT_RPC = 'https://testrpc.metamask.io/' -const IdManagement = require('./id-management') - - -module.exports = IdentityStore - -inherits(IdentityStore, EventEmitter) -function IdentityStore (opts = {}) { - EventEmitter.call(this) - - // we just use the ethStore to auto-add accounts - this._ethStore = opts.ethStore - this.configManager = opts.configManager - // lightwallet key store - this._keyStore = null - // lightwallet wrapper - this._idmgmt = null - - this.hdPathString = "m/44'/60'/0'/0" - - this._currentState = { - selectedAddress: null, - identities: {}, - } - // not part of serilized metamask state - only kept in memory -} - -// -// public -// - -IdentityStore.prototype.createNewVault = function (password, cb) { - delete this._keyStore - var serializedKeystore = this.configManager.getWallet() - - if (serializedKeystore) { - this.configManager.setData({}) - } - - this.purgeCache() - this._createVault(password, null, (err) => { - if (err) return cb(err) - - this._autoFaucet() - - this.configManager.setShowSeedWords(true) - var seedWords = this._idmgmt.getSeed() - - this._loadIdentities() - - cb(null, seedWords) - }) -} - -IdentityStore.prototype.recoverSeed = function (cb) { - this.configManager.setShowSeedWords(true) - if (!this._idmgmt) return cb(new Error('Unauthenticated. Please sign in.')) - var seedWords = this._idmgmt.getSeed() - cb(null, seedWords) -} - -IdentityStore.prototype.recoverFromSeed = function (password, seed, cb) { - this.purgeCache() - - this._createVault(password, seed, (err) => { - if (err) return cb(err) - - this._loadIdentities() - cb(null, this.getState()) - }) -} - -IdentityStore.prototype.setStore = function (store) { - this._ethStore = store -} - -IdentityStore.prototype.clearSeedWordCache = function (cb) { - const configManager = this.configManager - configManager.setShowSeedWords(false) - cb(null, configManager.getSelectedAccount()) -} - -IdentityStore.prototype.getState = function () { - const configManager = this.configManager - var seedWords = this.getSeedIfUnlocked() - return clone(extend(this._currentState, { - isInitialized: !!configManager.getWallet() && !seedWords, - isUnlocked: this._isUnlocked(), - seedWords: seedWords, - selectedAddress: configManager.getSelectedAccount(), - })) -} - -IdentityStore.prototype.getSeedIfUnlocked = function () { - const configManager = this.configManager - var showSeed = configManager.getShouldShowSeedWords() - var idmgmt = this._idmgmt - var shouldShow = showSeed && !!idmgmt - var seedWords = shouldShow ? idmgmt.getSeed() : null - return seedWords -} - -IdentityStore.prototype.getSelectedAddress = function () { - const configManager = this.configManager - return configManager.getSelectedAccount() -} - -IdentityStore.prototype.setSelectedAddressSync = function (address) { - const configManager = this.configManager - if (!address) { - var addresses = this._getAddresses() - address = addresses[0] - } - - configManager.setSelectedAccount(address) - return address -} - -IdentityStore.prototype.setSelectedAddress = function (address, cb) { - const resultAddress = this.setSelectedAddressSync(address) - if (cb) return cb(null, resultAddress) -} - -IdentityStore.prototype.revealAccount = function (cb) { - const derivedKey = this._idmgmt.derivedKey - const keyStore = this._keyStore - const configManager = this.configManager - - keyStore.setDefaultHdDerivationPath(this.hdPathString) - keyStore.generateNewAddress(derivedKey, 1) - const addresses = keyStore.getAddresses() - const address = addresses[ addresses.length - 1 ] - - this._ethStore.addAccount(ethUtil.addHexPrefix(address)) - - configManager.setWallet(keyStore.serialize()) - - this._loadIdentities() - this._didUpdate() - cb(null) -} - -IdentityStore.prototype.getNetwork = function (err) { - if (err) { - this._currentState.network = 'loading' - this._didUpdate() - } - - this.web3.version.getNetwork((err, network) => { - if (err) { - this._currentState.network = 'loading' - return this._didUpdate() - } - if (global.METAMASK_DEBUG) { - console.log('web3.getNetwork returned ' + network) - } - this._currentState.network = network - this._didUpdate() - }) -} - -IdentityStore.prototype.setLocked = function (cb) { - delete this._keyStore - delete this._idmgmt - cb() -} - -IdentityStore.prototype.submitPassword = function (password, cb) { - const configManager = this.configManager - this.tryPassword(password, (err) => { - if (err) return cb(err) - // load identities before returning... - this._loadIdentities() - cb(null, configManager.getSelectedAccount()) - }) -} - -IdentityStore.prototype.exportAccount = function (address, cb) { - var privateKey = this._idmgmt.exportPrivateKey(address) - if (cb) cb(null, privateKey) - return privateKey -} - -// private -// - -IdentityStore.prototype._didUpdate = function () { - this.emit('update', this.getState()) -} - -IdentityStore.prototype._isUnlocked = function () { - var result = Boolean(this._keyStore) && Boolean(this._idmgmt) - return result -} - -// load identities from keyStoreet -IdentityStore.prototype._loadIdentities = function () { - const configManager = this.configManager - if (!this._isUnlocked()) throw new Error('not unlocked') - - var addresses = this._getAddresses() - addresses.forEach((address, i) => { - // // add to ethStore - if (this._ethStore) { - this._ethStore.addAccount(ethUtil.addHexPrefix(address)) - } - // add to identities - const defaultLabel = 'Account ' + (i + 1) - const nickname = configManager.nicknameForWallet(address) - var identity = { - name: nickname || defaultLabel, - address: address, - mayBeFauceting: this._mayBeFauceting(i), - } - this._currentState.identities[address] = identity - }) - this._didUpdate() -} - -IdentityStore.prototype.saveAccountLabel = function (account, label, cb) { - const configManager = this.configManager - configManager.setNicknameForWallet(account, label) - this._loadIdentities() - cb(null, label) -} - -// mayBeFauceting -// If on testnet, index 0 may be fauceting. -// The UI will have to check the balance to know. -// If there is no balance and it mayBeFauceting, -// then it is in fact fauceting. -IdentityStore.prototype._mayBeFauceting = function (i) { - const configManager = this.configManager - var config = configManager.getProvider() - if (i === 0 && - config.type === 'rpc' && - config.rpcTarget === DEFAULT_RPC) { - return true - } - return false -} - -// -// keyStore managment - unlocking + deserialization -// - -IdentityStore.prototype.tryPassword = function (password, cb) { - var serializedKeystore = this.configManager.getWallet() - var keyStore = KeyStore.deserialize(serializedKeystore) - - keyStore.keyFromPassword(password, (err, pwDerivedKey) => { - if (err) return cb(err) - - const isCorrect = keyStore.isDerivedKeyCorrect(pwDerivedKey) - if (!isCorrect) return cb(new Error('Lightwallet - password incorrect')) - - this._keyStore = keyStore - this._createIdMgmt(pwDerivedKey) - cb() - }) -} - -IdentityStore.prototype._createVault = function (password, seedPhrase, cb) { - const opts = { - password, - hdPathString: this.hdPathString, - } - - if (seedPhrase) { - opts.seedPhrase = seedPhrase - } - - KeyStore.createVault(opts, (err, keyStore) => { - if (err) return cb(err) - - this._keyStore = keyStore - - keyStore.keyFromPassword(password, (err, derivedKey) => { - if (err) return cb(err) - - this.purgeCache() - - keyStore.addHdDerivationPath(this.hdPathString, derivedKey, {curve: 'secp256k1', purpose: 'sign'}) - - this._createFirstWallet(derivedKey) - this._createIdMgmt(derivedKey) - this.setSelectedAddressSync() - - cb() - }) - }) -} - -IdentityStore.prototype._createIdMgmt = function (derivedKey) { - this._idmgmt = new IdManagement({ - keyStore: this._keyStore, - derivedKey: derivedKey, - configManager: this.configManager, - }) -} - -IdentityStore.prototype.purgeCache = function () { - this._currentState.identities = {} - let accounts - try { - accounts = Object.keys(this._ethStore._currentState.accounts) - } catch (e) { - accounts = [] - } - accounts.forEach((address) => { - this._ethStore.removeAccount(address) - }) -} - -IdentityStore.prototype._createFirstWallet = function (derivedKey) { - const keyStore = this._keyStore - keyStore.setDefaultHdDerivationPath(this.hdPathString) - keyStore.generateNewAddress(derivedKey, 1) - this.configManager.setWallet(keyStore.serialize()) - var addresses = keyStore.getAddresses() - this._ethStore.addAccount(ethUtil.addHexPrefix(addresses[0])) -} - -// get addresses and normalize address hexString -IdentityStore.prototype._getAddresses = function () { - return this._keyStore.getAddresses(this.hdPathString).map((address) => { - return ethUtil.addHexPrefix(address) - }) -} - -IdentityStore.prototype._autoFaucet = function () { - var addresses = this._getAddresses() - autoFaucet(addresses[0]) -} - -// util diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index 25f9d9e5d..92533e022 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -23,7 +23,6 @@ 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') const accountImporter = require('./account-import-strategies') const version = require('../manifest.json').version @@ -115,11 +114,6 @@ module.exports = class MetamaskController extends EventEmitter { this.personalMessageManager = new PersonalMessageManager() this.publicConfigStore = this.initPublicConfigStore() - // TEMPORARY UNTIL FULL DEPRECATION: - this.idStoreMigrator = new IdStoreMigrator({ - configManager: this.configManager, - }) - // manual disk state subscriptions this.txManager.store.subscribe((state) => { this.store.updateState({ TransactionManager: state }) @@ -366,8 +360,7 @@ module.exports = class MetamaskController extends EventEmitter { // submitPassword (password, cb) { - this.migrateOldVaultIfAny(password) - .then(this.keyringController.submitPassword.bind(this.keyringController, password)) + return this.keyringController.submitPassword(password) .then((newState) => { cb(null, newState) }) .catch((reason) => { cb(reason) }) } @@ -562,35 +555,6 @@ module.exports = class MetamaskController extends EventEmitter { 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, password)) - .then(() => password) - } - - checkIfShouldMigrate() { - return !!this.configManager.getWallet() && !this.configManager.getVault() - } - restoreOldVaultAccounts(migratorOutput) { const { serialized } = migratorOutput return this.keyringController.restoreKeyring(serialized) diff --git a/package.json b/package.json index 96d58fa1b..72f3cbb3d 100644 --- a/package.json +++ b/package.json @@ -53,7 +53,6 @@ "ensnare": "^1.0.0", "eth-bin-to-ops": "^1.0.1", "eth-hd-keyring": "^1.1.1", - "eth-lightwallet": "^2.3.3", "eth-query": "^1.0.3", "eth-sig-util": "^1.1.1", "eth-simple-keyring": "^1.1.1", diff --git a/test/integration/lib/idStore-migrator-test.js b/test/integration/lib/idStore-migrator-test.js deleted file mode 100644 index 290216ae8..000000000 --- a/test/integration/lib/idStore-migrator-test.js +++ /dev/null @@ -1,92 +0,0 @@ -const ObservableStore = require('obs-store') -const ConfigManager = require('../../../app/scripts/lib/config-manager') -const IdStoreMigrator = require('../../../app/scripts/lib/idStore-migrator') -const SimpleKeyring = require('eth-simple-keyring') -const normalize = require('eth-sig-util').normalize - -const oldStyleVault = require('../mocks/oldVault.json').data -const badStyleVault = require('../mocks/badVault.json').data - -const PASSWORD = '12345678' -const FIRST_ADDRESS = '0x4dd5d356c5A016A220bCD69e82e5AF680a430d00'.toLowerCase() -const BAD_STYLE_FIRST_ADDRESS = '0xac39b311dceb2a4b2f5d8461c1cdaf756f4f7ae9' -const SEED = 'fringe damage bounce extend tunnel afraid alert sound all soldier all dinner' - -QUnit.module('Old Style Vaults', { - beforeEach: function () { - let managers = managersFromInitState(oldStyleVault) - - this.configManager = managers.configManager - this.migrator = managers.migrator - } -}) - -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) => { - assert.ok(result, 'migratedVaultForPassword returned 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 () { - let managers = managersFromInitState(badStyleVault) - - this.configManager = managers.configManager - this.migrator = managers.migrator - } -}) - -QUnit.test('migrator:migratedVaultForPassword', function (assert) { - var done = assert.async() - - this.migrator.migratedVaultForPassword(PASSWORD) - .then((result) => { - assert.ok(result, 'migratedVaultForPassword returned 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') - - var lostAccount = lostAccounts[0] - var privateKey = lostAccount.privateKey - - var simple = new SimpleKeyring() - simple.deserialize([privateKey]) - .then(() => { - return simple.getAccounts() - }) - .then((accounts) => { - assert.equal(normalize(accounts[0]), lostAccount.address, 'recovered address.') - done() - }) - .catch((reason) => { - assert.ifError(reason) - done(reason) - }) - }) -}) - -function managersFromInitState(initState){ - - let configManager = new ConfigManager({ - store: new ObservableStore(initState), - }) - - let migrator = new IdStoreMigrator({ - configManager: configManager, - }) - - return { configManager, migrator } -} diff --git a/test/unit/id-management-test.js b/test/unit/id-management-test.js deleted file mode 100644 index cbc6403bc..000000000 --- a/test/unit/id-management-test.js +++ /dev/null @@ -1,35 +0,0 @@ -var assert = require('assert') -var IdManagement = require('../../app/scripts/lib/id-management') -var sinon = require('sinon') - -describe('IdManagement', function() { - - beforeEach(function() { - // sinon allows stubbing methods that are easily verified - this.sinon = sinon.sandbox.create() - window.localStorage = {} // Hacking localStorage support into JSDom - }) - - afterEach(function() { - // sinon requires cleanup otherwise it will overwrite context - this.sinon.restore() - }) - - describe('#signMsg', function () { - it('passes the dennis test', function() { - const address = '0x9858e7d8b79fc3e6d989636721584498926da38a' - const message = '0x879a053d4800c6354e76c7985a865d2922c82fb5b3f4577b2fe08b998954f2e0' - const privateKey = '0x7dd98753d7b4394095de7d176c58128e2ed6ee600abe97c9f6d9fd65015d9b18' - const expectedResult = '0x28fcb6768e5110144a55b2e6ce9d1ea5a58103033632d272d2b5cf506906f7941a00b539383fd872109633d8c71c404e13dba87bc84166ee31b0e36061a69e161c' - - const idManagement = new IdManagement() - const exportKeyStub = sinon.stub(idManagement, 'exportPrivateKey', (addr) => { - assert.equal(addr, address) - return privateKey - }) - - const result = idManagement.signMsg(address, message) - assert.equal(result, expectedResult) - }) - }) -}) diff --git a/test/unit/idStore-migration-test.js b/test/unit/idStore-migration-test.js deleted file mode 100644 index 81a99ef63..000000000 --- a/test/unit/idStore-migration-test.js +++ /dev/null @@ -1,83 +0,0 @@ -const async = require('async') -const assert = require('assert') -const ObservableStore = require('obs-store') -const ethUtil = require('ethereumjs-util') -const BN = ethUtil.BN -const ConfigManager = require('../../app/scripts/lib/config-manager') -const firstTimeState = require('../../app/scripts/first-time-state') -const delegateCallCode = require('../lib/example-code.json').delegateCallCode -const clone = require('clone') - -// The old way: -const IdentityStore = require('../../app/scripts/lib/idStore') -const STORAGE_KEY = 'metamask-config' - -// The new ways: -var KeyringController = require('../../app/scripts/keyring-controller') -const mockEncryptor = require('../lib/mock-encryptor') -const MockSimpleKeychain = require('../lib/mock-simple-keychain') -const sinon = require('sinon') - -const mockVault = { - seed: 'picnic injury awful upper eagle junk alert toss flower renew silly vague', - account: '0x5d8de92c205279c10e5669f797b853ccef4f739a', -} - -const badVault = { - seed: 'radar blur cabbage chef fix engine embark joy scheme fiction master release', -} - -describe('IdentityStore to KeyringController migration', function() { - - // The stars of the show: - let idStore, keyringController, seedWords, configManager - - let password = 'password123' - let entropy = 'entripppppyy duuude' - let accounts = [] - let newAccounts = [] - let originalKeystore - - // This is a lot of setup, I know! - // We have to create an old style vault, populate it, - // and THEN create a new one, before we can run tests on it. - beforeEach(function(done) { - this.sinon = sinon.sandbox.create() - let store = new ObservableStore(clone(firstTimeState)) - configManager = new ConfigManager({ store }) - - idStore = new IdentityStore({ - configManager: configManager, - ethStore: { - addAccount(acct) { accounts.push(ethUtil.addHexPrefix(acct)) }, - del(acct) { delete accounts[acct] }, - }, - }) - - idStore._createVault(password, mockVault.seed, (err) => { - assert.ifError(err, 'createNewVault threw error') - originalKeystore = idStore._idmgmt.keyStore - - idStore.setLocked((err) => { - assert.ifError(err, 'createNewVault threw error') - keyringController = new KeyringController({ - configManager, - ethStore: { - addAccount(acct) { newAccounts.push(ethUtil.addHexPrefix(acct)) }, - del(acct) { delete newAccounts[acct] }, - }, - txManager: { - getTxList: () => [], - getUnapprovedTxList: () => [] - }, - }) - - // Stub out the browser crypto for a mock encryptor. - // Browser crypto is tested in the integration test suite. - keyringController.encryptor = mockEncryptor - done() - }) - }) - }) - -}) diff --git a/test/unit/idStore-test.js b/test/unit/idStore-test.js deleted file mode 100644 index 000c58a82..000000000 --- a/test/unit/idStore-test.js +++ /dev/null @@ -1,142 +0,0 @@ -const async = require('async') -const assert = require('assert') -const ethUtil = require('ethereumjs-util') -const BN = ethUtil.BN -const configManagerGen = require('../lib/mock-config-manager') -const delegateCallCode = require('../lib/example-code.json').delegateCallCode -const IdentityStore = require('../../app/scripts/lib/idStore') - -describe('IdentityStore', function() { - - describe('#createNewVault', function () { - let idStore - let password = 'password123' - let seedWords - let accounts = [] - let originalKeystore - - before(function(done) { - window.localStorage = {} // Hacking localStorage support into JSDom - - idStore = new IdentityStore({ - configManager: configManagerGen(), - ethStore: { - addAccount(acct) { accounts.push(ethUtil.addHexPrefix(acct)) }, - }, - }) - - idStore.createNewVault(password, (err, seeds) => { - assert.ifError(err, 'createNewVault threw error') - seedWords = seeds - originalKeystore = idStore._idmgmt.keyStore - done() - }) - }) - - describe('#recoverFromSeed', function() { - let newAccounts = [] - - before(function() { - window.localStorage = {} // Hacking localStorage support into JSDom - - idStore = new IdentityStore({ - configManager: configManagerGen(), - ethStore: { - addAccount(acct) { newAccounts.push(ethUtil.addHexPrefix(acct)) }, - }, - }) - }) - - it('should return the expected keystore', function (done) { - - idStore.recoverFromSeed(password, seedWords, (err) => { - assert.ifError(err) - - let newKeystore = idStore._idmgmt.keyStore - assert.equal(newAccounts[0], accounts[0]) - done() - }) - }) - }) - }) - - describe('#recoverFromSeed BIP44 compliance', function() { - const salt = 'lightwalletSalt' - - let password = 'secret!' - let accounts = {} - let idStore - - var assertions = [ - { - seed: 'picnic injury awful upper eagle junk alert toss flower renew silly vague', - account: '0x5d8de92c205279c10e5669f797b853ccef4f739a', - }, - { - seed: 'radar blur cabbage chef fix engine embark joy scheme fiction master release', - account: '0xe15d894becb0354c501ae69429b05143679f39e0', - }, - { - seed: 'phone coyote caught pattern found table wedding list tumble broccoli chief swing', - account: '0xb0e868f24bc7fec2bce2efc2b1c344d7569cd9d2', - }, - { - seed: 'recycle tag bird palace blue village anxiety census cook soldier example music', - account: '0xab34a45920afe4af212b96ec51232aaa6a33f663', - }, - { - seed: 'half glimpse tape cute harvest sweet bike voyage actual floor poet lazy', - account: '0x28e9044597b625ac4beda7250011670223de43b2', - }, - { - seed: 'flavor tiger carpet motor angry hungry document inquiry large critic usage liar', - account: '0xb571be96558940c4e9292e1999461aa7499fb6cd', - }, - ] - - before(function() { - window.localStorage = {} // Hacking localStorage support into JSDom - - idStore = new IdentityStore({ - configManager: configManagerGen(), - ethStore: { - addAccount(acct) { accounts[acct] = acct}, - del(acct) { delete accounts[acct] }, - }, - }) - }) - - it('should enforce seed compliance with TestRPC', function (done) { - this.timeout(10000) - const tests = assertions.map((assertion) => { - return function (cb) { - - idStore.recoverFromSeed(password, assertion.seed, (err) => { - assert.ifError(err) - - var expected = assertion.account.toLowerCase() - var received = accounts[expected].toLowerCase() - assert.equal(received, expected) - - idStore.tryPassword(password, function (err) { - - assert.ok(idStore._isUnlocked(), 'should unlock the id store') - - idStore.submitPassword(password, function(err, account) { - assert.ifError(err) - assert.equal(account, expected) - assert.equal(Object.keys(idStore._getAddresses()).length, 1, 'only one account on restore') - cb() - }) - }) - }) - } - }) - - async.series(tests, function(err, results) { - assert.ifError(err) - done() - }) - }) - }) -}) From 35c05607b091f6064be6802a2eb1023d837c9c85 Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Thu, 16 Mar 2017 12:22:09 -0700 Subject: [PATCH 22/47] Improve personal_sign style textarea was not resizing the way I'd expected, so made it permanently larger, to accomodate larger messages. --- ui/app/components/binary-renderer.js | 21 +++++++++++-------- .../pending-personal-msg-details.js | 13 ++++++++++-- 2 files changed, 23 insertions(+), 11 deletions(-) diff --git a/ui/app/components/binary-renderer.js b/ui/app/components/binary-renderer.js index a9d49b128..0b6a1f5c2 100644 --- a/ui/app/components/binary-renderer.js +++ b/ui/app/components/binary-renderer.js @@ -2,6 +2,7 @@ const Component = require('react').Component const h = require('react-hyperscript') const inherits = require('util').inherits const ethUtil = require('ethereumjs-util') +const extend = require('xtend') module.exports = BinaryRenderer @@ -12,20 +13,22 @@ function BinaryRenderer () { BinaryRenderer.prototype.render = function () { const props = this.props - const { value } = props + const { value, style } = props const text = this.hexToText(value) + const defaultStyle = extend({ + width: '315px', + maxHeight: '210px', + resize: 'none', + border: 'none', + background: 'white', + padding: '3px', + }, style) + return ( h('textarea.font-small', { readOnly: true, - style: { - width: '315px', - maxHeight: '210px', - resize: 'none', - border: 'none', - background: 'white', - padding: '3px', - }, + style: defaultStyle, defaultValue: text, }) ) diff --git a/ui/app/components/pending-personal-msg-details.js b/ui/app/components/pending-personal-msg-details.js index fa2c6416c..1050513f2 100644 --- a/ui/app/components/pending-personal-msg-details.js +++ b/ui/app/components/pending-personal-msg-details.js @@ -40,9 +40,18 @@ PendingMsgDetails.prototype.render = function () { }), // message data - h('div', [ + h('div', { + style: { + height: '260px', + }, + }, [ h('label.font-small', { style: { display: 'block' } }, 'MESSAGE'), - h(BinaryRenderer, { value: data }), + h(BinaryRenderer, { + value: data, + style: { + height: '215px', + }, + }), ]), ]) From 889132b16cbafbb5a8e35dd2cf6acc9a7fe3d5fa Mon Sep 17 00:00:00 2001 From: Kevin Serrano Date: Tue, 21 Mar 2017 06:57:49 -0700 Subject: [PATCH 23/47] Add action to hide loading indication on an incorrect pw. --- ui/app/actions.js | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/ui/app/actions.js b/ui/app/actions.js index b09021577..13a9cf547 100644 --- a/ui/app/actions.js +++ b/ui/app/actions.js @@ -269,7 +269,10 @@ function requestRevealSeed (password) { dispatch(actions.showLoadingIndication()) log.debug(`background.submitPassword`) background.submitPassword(password, (err) => { - if (err) return dispatch(actions.displayWarning(err.message)) + if (err) { + dispatch(actions.hideLoadingIndication()) + return dispatch(actions.displayWarning(err.message)) + } log.debug(`background.placeSeedWords`) background.placeSeedWords((err) => { if (err) return dispatch(actions.displayWarning(err.message)) @@ -698,7 +701,7 @@ function setRpcTarget (newRpc) { } } -// Calls the addressBookController to add a new address. +// Calls the addressBookController to add a new address. function addToAddressBook (recipient, nickname) { log.debug(`background.addToAddressBook`) return (dispatch) => { From 10040bee497e3c9b3775dc444b3e8a7b30fab536 Mon Sep 17 00:00:00 2001 From: Kevin Serrano Date: Tue, 21 Mar 2017 06:59:08 -0700 Subject: [PATCH 24/47] Changelog addition. --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3cd7b90ff..c081147b1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ - Added an address book functionality that remembers the last 15 unique addresses sent to. - Can now change network to custom RPC URL from lock screen. - Removed support for old, lightwallet based vault. Users who have not opened app in over a month will need to recover with their seed phrase. This will allow Firefox support sooner. +- Fixed bug where spinner wouldn't disappear on incorrect password submission on seed word reveal. ## 3.4.0 2017-3-8 From a7b7be8309b5b81514b9ef748e315f01d02bc178 Mon Sep 17 00:00:00 2001 From: Kevin Serrano Date: Tue, 21 Mar 2017 08:52:28 -0700 Subject: [PATCH 25/47] Add new development states for UI development. --- .../states/private-key-export-success.json | 70 +++++++++++++++++++ development/states/private-key-export.json | 69 ++++++++++++++++++ 2 files changed, 139 insertions(+) create mode 100644 development/states/private-key-export-success.json create mode 100644 development/states/private-key-export.json diff --git a/development/states/private-key-export-success.json b/development/states/private-key-export-success.json new file mode 100644 index 000000000..2ff3c4d17 --- /dev/null +++ b/development/states/private-key-export-success.json @@ -0,0 +1,70 @@ +{ + "metamask": { + "isInitialized": true, + "isUnlocked": true, + "rpcTarget": "https://rawtestrpc.metamask.io/", + "identities": { + "0x07284e146926a4facd0ea60598dc4f001ad620f1": { + "address": "0x07284e146926a4facd0ea60598dc4f001ad620f1", + "name": "Account 1" + } + }, + "unapprovedTxs": {}, + "noActiveNotices": true, + "frequentRpcList": [], + "addressBook": [], + "network": "3", + "accounts": { + "0x07284e146926a4facd0ea60598dc4f001ad620f1": { + "code": "0x", + "nonce": "0x0", + "balance": "0x0", + "address": "0x07284e146926a4facd0ea60598dc4f001ad620f1" + } + }, + "transactions": {}, + "selectedAddressTxList": [], + "unapprovedMsgs": {}, + "unapprovedMsgCount": 0, + "unapprovedPersonalMsgs": {}, + "unapprovedPersonalMsgCount": 0, + "keyringTypes": [ + "Simple Key Pair", + "HD Key Tree" + ], + "keyrings": [ + { + "type": "HD Key Tree", + "accounts": [ + "07284e146926a4facd0ea60598dc4f001ad620f1" + ] + } + ], + "selectedAddress": "0x07284e146926a4facd0ea60598dc4f001ad620f1", + "currentCurrency": "USD", + "conversionRate": 43.35903476, + "conversionDate": 1490105102, + "provider": { + "type": "testnet" + }, + "shapeShiftTxList": [], + "lostAccounts": [], + "seedWords": null + }, + "appState": { + "menuOpen": false, + "currentView": { + "name": "accountDetail", + "context": "0x07284e146926a4facd0ea60598dc4f001ad620f1" + }, + "accountDetail": { + "subview": "export", + "accountExport": "completed", + "privateKey": "549c9638ad06432568969accacad4a02f8548cc358085938071745138ec134b7" + }, + "transForward": true, + "isLoading": false, + "warning": null + }, + "identities": {} +} diff --git a/development/states/private-key-export.json b/development/states/private-key-export.json new file mode 100644 index 000000000..db7a53e22 --- /dev/null +++ b/development/states/private-key-export.json @@ -0,0 +1,69 @@ +{ + "metamask": { + "isInitialized": true, + "isUnlocked": true, + "rpcTarget": "https://rawtestrpc.metamask.io/", + "identities": { + "0x07284e146926a4facd0ea60598dc4f001ad620f1": { + "address": "0x07284e146926a4facd0ea60598dc4f001ad620f1", + "name": "Account 1" + } + }, + "unapprovedTxs": {}, + "noActiveNotices": true, + "frequentRpcList": [], + "addressBook": [], + "network": "3", + "accounts": { + "0x07284e146926a4facd0ea60598dc4f001ad620f1": { + "code": "0x", + "nonce": "0x0", + "balance": "0x0", + "address": "0x07284e146926a4facd0ea60598dc4f001ad620f1" + } + }, + "transactions": {}, + "selectedAddressTxList": [], + "unapprovedMsgs": {}, + "unapprovedMsgCount": 0, + "unapprovedPersonalMsgs": {}, + "unapprovedPersonalMsgCount": 0, + "keyringTypes": [ + "Simple Key Pair", + "HD Key Tree" + ], + "keyrings": [ + { + "type": "HD Key Tree", + "accounts": [ + "07284e146926a4facd0ea60598dc4f001ad620f1" + ] + } + ], + "selectedAddress": "0x07284e146926a4facd0ea60598dc4f001ad620f1", + "currentCurrency": "USD", + "conversionRate": 43.35903476, + "conversionDate": 1490105102, + "provider": { + "type": "testnet" + }, + "shapeShiftTxList": [], + "lostAccounts": [], + "seedWords": null + }, + "appState": { + "menuOpen": false, + "currentView": { + "name": "accountDetail", + "context": "0x07284e146926a4facd0ea60598dc4f001ad620f1" + }, + "accountDetail": { + "subview": "export", + "accountExport": "requested" + }, + "transForward": true, + "isLoading": false, + "warning": null + }, + "identities": {} +} From 0e43606b162db28c72ea0b76a14a92f88c8f2109 Mon Sep 17 00:00:00 2001 From: Kevin Serrano Date: Tue, 21 Mar 2017 08:53:34 -0700 Subject: [PATCH 26/47] Adjust private key confirmation style and logic. --- ui/app/actions.js | 26 +++++--- ui/app/components/account-export.js | 92 ++++++++++++++++++----------- ui/app/css/index.css | 3 +- 3 files changed, 75 insertions(+), 46 deletions(-) diff --git a/ui/app/actions.js b/ui/app/actions.js index b09021577..9d6676d01 100644 --- a/ui/app/actions.js +++ b/ui/app/actions.js @@ -698,7 +698,7 @@ function setRpcTarget (newRpc) { } } -// Calls the addressBookController to add a new address. +// Calls the addressBookController to add a new address. function addToAddressBook (recipient, nickname) { log.debug(`background.addToAddressBook`) return (dispatch) => { @@ -772,22 +772,30 @@ function requestExportAccount () { } } -function exportAccount (address) { +function exportAccount (password, address) { var self = this return function (dispatch) { dispatch(self.showLoadingIndication()) - log.debug(`background.exportAccount`) - background.exportAccount(address, function (err, result) { - dispatch(self.hideLoadingIndication()) - + log.debug(`background.submitPassword`) + background.submitPassword(password, function (err) { if (err) { - log.error(err) - return dispatch(self.displayWarning('Had a problem exporting the account.')) + log.error('Error in submiting password.') + dispatch(self.hideLoadingIndication()) + return dispatch(self.displayWarning('Incorrect Password.')) } + log.debug(`background.exportAccount`) + background.exportAccount(address, function (err, result) { + dispatch(self.hideLoadingIndication()) - dispatch(self.showPrivateKey(result)) + if (err) { + log.error(err) + return dispatch(self.displayWarning('Had a problem exporting the account.')) + } + + dispatch(self.showPrivateKey(result)) + }) }) } } diff --git a/ui/app/components/account-export.js b/ui/app/components/account-export.js index 6d8b099a5..38a1d28ef 100644 --- a/ui/app/components/account-export.js +++ b/ui/app/components/account-export.js @@ -4,14 +4,21 @@ const inherits = require('util').inherits const copyToClipboard = require('copy-to-clipboard') const actions = require('../actions') const ethUtil = require('ethereumjs-util') +const connect = require('react-redux').connect -module.exports = ExportAccountView +module.exports = connect(mapStateToProps)(ExportAccountView) inherits(ExportAccountView, Component) function ExportAccountView () { Component.call(this) } +function mapStateToProps (state) { + return { + warning: state.appState.warning, + } +} + ExportAccountView.prototype.render = function () { console.log('EXPORT VIEW') console.dir(this.props) @@ -28,35 +35,57 @@ ExportAccountView.prototype.render = function () { if (notExporting) return h('div') if (exportRequested) { - var warning = `Exporting your private key is very dangerous, - and you should only do it if you know what you're doing.` - var confirmation = `If you're absolutely sure, type "I understand" below and - submit.` + var warning = `Export private keys at your own risk.` return ( - h('div', { - key: 'exporting', style: { - margin: '0 20px', + display: 'inline-block', + textAlign: 'center', }, - }, [ - h('p.error', warning), - h('p', confirmation), - h('input#exportAccount.sizing-input', { - onKeyPress: this.onExportKeyPress.bind(this), - style: { - position: 'relative', - top: '1.5px', + }, + [ + h('div', { + key: 'exporting', + style: { + margin: '0 20px', + }, + }, [ + h('p.error', warning), + h('input#exportAccount.sizing-input', { + placeholder: 'confirm password', + onKeyPress: this.onExportKeyPress.bind(this), + style: { + position: 'relative', + top: '1.5px', + marginBottom: '7px', + }, + }), + ]), + h('div', { + key: 'buttons', + style: { + margin: '0 20px', + }, }, - }), - h('button', { - onClick: () => this.onExportKeyPress({ key: 'Enter', preventDefault: () => {} }), - }, 'Submit'), - h('button', { - onClick: () => this.props.dispatch(actions.backToAccountDetail(this.props.address)), - }, 'Cancel'), - ]) - + [ + h('button', { + onClick: () => this.onExportKeyPress({ key: 'Enter', preventDefault: () => {} }), + style: { + marginRight: '10px', + }, + }, 'Submit'), + h('button', { + onClick: () => this.props.dispatch(actions.backToAccountDetail(this.props.address)), + }, 'Cancel'), + ]), + (this.props.warning) && ( + h('span.error', { + style: { + margin: '20px', + }, + }, this.props.warning.split('-')) + ), + ]) ) } @@ -89,15 +118,6 @@ ExportAccountView.prototype.onExportKeyPress = function (event) { if (event.key !== 'Enter') return event.preventDefault() - var input = document.getElementById('exportAccount') - if (input.value === 'I understand') { - this.props.dispatch(actions.exportAccount(this.props.address)) - } else { - input.value = '' - input.placeholder = 'Please retype "I understand" exactly.' - } -} - -ExportAccountView.prototype.exportAccount = function (address) { - this.props.dispatch(actions.exportAccount(address)) + var input = document.getElementById('exportAccount').value + this.props.dispatch(actions.exportAccount(input, this.props.address)) } diff --git a/ui/app/css/index.css b/ui/app/css/index.css index 8c6ff29d3..3ec0ac5c5 100644 --- a/ui/app/css/index.css +++ b/ui/app/css/index.css @@ -266,8 +266,9 @@ app sections } .sizing-input{ - font-size: 1em; + font-size: 14px; height: 30px; + padding-left: 5px; } .editable-label{ display: flex; From 6e6fa703cc3512e3d998aeca32e08f477ae31971 Mon Sep 17 00:00:00 2001 From: Kevin Serrano Date: Tue, 21 Mar 2017 08:57:19 -0700 Subject: [PATCH 27/47] Add to changelog. --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3cd7b90ff..d7e1dc4fc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ - Added an address book functionality that remembers the last 15 unique addresses sent to. - Can now change network to custom RPC URL from lock screen. - Removed support for old, lightwallet based vault. Users who have not opened app in over a month will need to recover with their seed phrase. This will allow Firefox support sooner. +- Polish the private key UI. ## 3.4.0 2017-3-8 From 605c2a7404ba235aa4894a5b6f4b6afa6b41004f Mon Sep 17 00:00:00 2001 From: Kevin Serrano Date: Tue, 21 Mar 2017 09:15:12 -0700 Subject: [PATCH 28/47] Remove redundant removal of loading indicator. Integrate loading indicator disappear with display warning. --- ui/app/actions.js | 7 ++----- ui/app/reducers/app.js | 1 + 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/ui/app/actions.js b/ui/app/actions.js index 13a9cf547..ac54158d0 100644 --- a/ui/app/actions.js +++ b/ui/app/actions.js @@ -215,7 +215,7 @@ function confirmSeedWords () { dispatch(actions.showLoadingIndication()) log.debug(`background.clearSeedWordCache`) background.clearSeedWordCache((err, account) => { - dispatch(actions.hideLoadingIndication()) + // dispatch(actions.hideLoadingIndication()) if (err) { return dispatch(actions.displayWarning(err.message)) } @@ -231,7 +231,7 @@ function createNewVaultAndRestore (password, seed) { dispatch(actions.showLoadingIndication()) log.debug(`background.createNewVaultAndRestore`) background.createNewVaultAndRestore(password, seed, (err) => { - dispatch(actions.hideLoadingIndication()) + // dispatch(actions.hideLoadingIndication()) if (err) return dispatch(actions.displayWarning(err.message)) dispatch(actions.showAccountsPage()) }) @@ -270,13 +270,11 @@ function requestRevealSeed (password) { log.debug(`background.submitPassword`) background.submitPassword(password, (err) => { if (err) { - dispatch(actions.hideLoadingIndication()) return dispatch(actions.displayWarning(err.message)) } log.debug(`background.placeSeedWords`) background.placeSeedWords((err) => { if (err) return dispatch(actions.displayWarning(err.message)) - dispatch(actions.hideLoadingIndication()) }) }) } @@ -299,7 +297,6 @@ function importNewAccount (strategy, args) { dispatch(actions.showLoadingIndication('This may take a while, be patient.')) log.debug(`background.importAccountWithStrategy`) background.importAccountWithStrategy(strategy, args, (err) => { - dispatch(actions.hideLoadingIndication()) if (err) return dispatch(actions.displayWarning(err.message)) log.debug(`background.getState`) background.getState((err, newState) => { diff --git a/ui/app/reducers/app.js b/ui/app/reducers/app.js index 7ea1e1d7c..b9e3f7b16 100644 --- a/ui/app/reducers/app.js +++ b/ui/app/reducers/app.js @@ -426,6 +426,7 @@ function reduceApp (state, action) { case actions.DISPLAY_WARNING: return extend(appState, { warning: action.value, + isLoading: false, }) case actions.HIDE_WARNING: From b0c0c30689773113ce592b0540de10696e54d372 Mon Sep 17 00:00:00 2001 From: Kevin Serrano Date: Tue, 21 Mar 2017 13:38:27 -0700 Subject: [PATCH 29/47] Uncomment lines relating to showing indicator. --- ui/app/actions.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ui/app/actions.js b/ui/app/actions.js index ac54158d0..2fefccf9f 100644 --- a/ui/app/actions.js +++ b/ui/app/actions.js @@ -215,7 +215,7 @@ function confirmSeedWords () { dispatch(actions.showLoadingIndication()) log.debug(`background.clearSeedWordCache`) background.clearSeedWordCache((err, account) => { - // dispatch(actions.hideLoadingIndication()) + dispatch(actions.hideLoadingIndication()) if (err) { return dispatch(actions.displayWarning(err.message)) } @@ -231,7 +231,7 @@ function createNewVaultAndRestore (password, seed) { dispatch(actions.showLoadingIndication()) log.debug(`background.createNewVaultAndRestore`) background.createNewVaultAndRestore(password, seed, (err) => { - // dispatch(actions.hideLoadingIndication()) + dispatch(actions.hideLoadingIndication()) if (err) return dispatch(actions.displayWarning(err.message)) dispatch(actions.showAccountsPage()) }) From f2e40e85b7751e891ce04935ad85a984076495fd Mon Sep 17 00:00:00 2001 From: Kevin Serrano Date: Wed, 22 Mar 2017 12:18:13 -0400 Subject: [PATCH 30/47] Add one more loading indication. --- ui/app/actions.js | 1 + 1 file changed, 1 insertion(+) diff --git a/ui/app/actions.js b/ui/app/actions.js index 2fefccf9f..3b1fc0435 100644 --- a/ui/app/actions.js +++ b/ui/app/actions.js @@ -300,6 +300,7 @@ function importNewAccount (strategy, args) { if (err) return dispatch(actions.displayWarning(err.message)) log.debug(`background.getState`) background.getState((err, newState) => { + dispatch(actions.hideLoadingIndication()) if (err) { return dispatch(actions.displayWarning(err.message)) } From 046774e76877f8f833445c76e2013d7f2c9bfff3 Mon Sep 17 00:00:00 2001 From: Kevin Serrano Date: Wed, 22 Mar 2017 12:38:04 -0400 Subject: [PATCH 31/47] Require redux-logger of 2.10.2 and up (#1235) * Require redux-logger of 2.8.1 and up * Bump to 2.10.2 and above, 2.10.1 had critical bug. --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 72f3cbb3d..488e7e90d 100644 --- a/package.json +++ b/package.json @@ -98,7 +98,7 @@ "react-tooltip-component": "^0.3.0", "readable-stream": "^2.1.2", "redux": "^3.0.5", - "redux-logger": "^2.3.1", + "redux-logger": "^2.10.2", "redux-thunk": "^1.0.2", "request-promise": "^4.1.1", "sandwich-expando": "^1.0.5", From 8c7be4340349032655503b4137147e1978b0f0bd Mon Sep 17 00:00:00 2001 From: frankiebee Date: Wed, 22 Mar 2017 09:48:41 -0700 Subject: [PATCH 32/47] Have better error messages --- app/scripts/transaction-manager.js | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/app/scripts/transaction-manager.js b/app/scripts/transaction-manager.js index c6cfdf11d..31c1c8431 100644 --- a/app/scripts/transaction-manager.js +++ b/app/scripts/transaction-manager.js @@ -172,7 +172,10 @@ module.exports = class TransactionManager extends EventEmitter { ], (err) => { self.nonceLock.leave() if (err) { - this.setTxStatusFailed(txId) + this.setTxStatusFailed(txId, { + errCode: err.errCode || err, + message: err.message || 'Transaction failed during approval', + }) return cb(err) } cb() @@ -291,7 +294,10 @@ module.exports = class TransactionManager extends EventEmitter { this._setTxStatus(txId, 'confirmed') } - setTxStatusFailed (txId) { + setTxStatusFailed (txId, reason) { + let txMeta = this.getTx(txId) + txMeta.err = reason + this.updateTx(txMeta) this._setTxStatus(txId, 'failed') } @@ -312,12 +318,11 @@ module.exports = class TransactionManager extends EventEmitter { var txHash = txMeta.hash var txId = txMeta.id if (!txHash) { - txMeta.err = { + let errReason = { errCode: 'No hash was provided', message: 'We had an error while submitting this transaction, please try again.', } - this.updateTx(txMeta) - return this.setTxStatusFailed(txId) + return this.setTxStatusFailed(txId, errReason) } this.txProviderUtils.query.getTransactionByHash(txHash, (err, txParams) => { if (err || !txParams) { From 2ffd42f9f06e1b89b49c12f8ded1c12ecb249c57 Mon Sep 17 00:00:00 2001 From: frankiebee Date: Wed, 22 Mar 2017 09:49:44 -0700 Subject: [PATCH 33/47] Add to CHANGELOG --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index d7e1dc4fc..6621d89f4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ ## Current Master +- Add better error messages for when a transaction fails on approval - Allow sending to ENS names in send form on Ropsten. - Added an address book functionality that remembers the last 15 unique addresses sent to. - Can now change network to custom RPC URL from lock screen. From 9906da86a4669c1eaf7f693398de380b2973bfe8 Mon Sep 17 00:00:00 2001 From: Kevin Serrano Date: Wed, 22 Mar 2017 14:25:56 -0400 Subject: [PATCH 34/47] Modify logic for injection conditions. --- app/scripts/contentscript.js | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/app/scripts/contentscript.js b/app/scripts/contentscript.js index ab64dc9fa..020208ceb 100644 --- a/app/scripts/contentscript.js +++ b/app/scripts/contentscript.js @@ -69,14 +69,10 @@ function shouldInjectWeb3 () { } function isAllowedSuffix (testCase) { - var prohibitedTypes = ['xml', 'pdf'] - var currentUrl = window.location.href - var currentRegex - for (let i = 0; i < prohibitedTypes.length; i++) { - currentRegex = new RegExp(`\.${prohibitedTypes[i]}$`) - if (currentRegex.test(currentUrl)) { - return false - } + const doctype = window.document.doctype + if (doctype) { + return doctype.name === 'html' + } else { + return false } - return true } From c5f96be98d7fe36a369c48b775d0b1fe857844c6 Mon Sep 17 00:00:00 2001 From: Kevin Serrano Date: Wed, 22 Mar 2017 15:59:46 -0400 Subject: [PATCH 35/47] Add kovan to config file. --- app/scripts/config.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/scripts/config.js b/app/scripts/config.js index b4541a04a..ec421744d 100644 --- a/app/scripts/config.js +++ b/app/scripts/config.js @@ -1,5 +1,6 @@ const MAINET_RPC_URL = 'https://mainnet.infura.io/metamask' const TESTNET_RPC_URL = 'https://ropsten.infura.io/metamask' +const KOVAN_RPC_URL = 'https://kovan.infura.io/metamask' const DEFAULT_RPC_URL = TESTNET_RPC_URL global.METAMASK_DEBUG = 'GULP_METAMASK_DEBUG' @@ -10,5 +11,6 @@ module.exports = { mainnet: MAINET_RPC_URL, testnet: TESTNET_RPC_URL, morden: TESTNET_RPC_URL, + kovan: KOVAN_RPC_URL, }, } From 33dd7954a72797d23a0d8ed652a626addb1af55d Mon Sep 17 00:00:00 2001 From: Kevin Serrano Date: Wed, 22 Mar 2017 16:00:50 -0400 Subject: [PATCH 36/47] Add kovan config settings to config manager. --- app/scripts/lib/config-manager.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/app/scripts/lib/config-manager.js b/app/scripts/lib/config-manager.js index 6868637e5..e31cb45ed 100644 --- a/app/scripts/lib/config-manager.js +++ b/app/scripts/lib/config-manager.js @@ -5,6 +5,7 @@ const normalize = require('eth-sig-util').normalize const TESTNET_RPC = MetamaskConfig.network.testnet const MAINNET_RPC = MetamaskConfig.network.mainnet const MORDEN_RPC = MetamaskConfig.network.morden +const KOVAN_RPC = MetamaskConfig.network.kovan /* The config-manager is a convenience object * wrapping a pojo-migrator. @@ -150,6 +151,9 @@ ConfigManager.prototype.getCurrentRpcAddress = function () { case 'morden': return MORDEN_RPC + case 'kovan': + return KOVAN_RPC + default: return provider && provider.rpcTarget ? provider.rpcTarget : TESTNET_RPC } From 4116b37d32e467bbd4cbb113851667115d28cf64 Mon Sep 17 00:00:00 2001 From: Kevin Serrano Date: Wed, 22 Mar 2017 16:01:38 -0400 Subject: [PATCH 37/47] Modify css rule for unused hollow diamond. --- ui/app/css/lib.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/app/css/lib.css b/ui/app/css/lib.css index 99c6f1b9d..670dc9fd0 100644 --- a/ui/app/css/lib.css +++ b/ui/app/css/lib.css @@ -188,7 +188,7 @@ hr.horizontal-line { .hollow-diamond { transform: rotate(45deg); - border: 1px solid #038789; + border: 3px solid #690496; } .pending-dot { From c00544de91caec4d76f34aeaf91f025292ae0384 Mon Sep 17 00:00:00 2001 From: Kevin Serrano Date: Wed, 22 Mar 2017 16:02:17 -0400 Subject: [PATCH 38/47] Add conditional kovan logic to etherscan link generators. --- ui/lib/account-link.js | 4 +++- ui/lib/explorer-link.js | 5 ++++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/ui/lib/account-link.js b/ui/lib/account-link.js index 77db0851d..948f32da1 100644 --- a/ui/lib/account-link.js +++ b/ui/lib/account-link.js @@ -1,7 +1,6 @@ module.exports = function (address, network) { const net = parseInt(network) let link - switch (net) { case 1: // main net link = `http://etherscan.io/address/${address}` @@ -12,6 +11,9 @@ module.exports = function (address, network) { case 3: // ropsten test net link = `http://testnet.etherscan.io/address/${address}` break + case 42: // kovan test net + link = `http://kovan.etherscan.io/address/${address}` + break default: link = '' break diff --git a/ui/lib/explorer-link.js b/ui/lib/explorer-link.js index dc6be2984..7ae19cca0 100644 --- a/ui/lib/explorer-link.js +++ b/ui/lib/explorer-link.js @@ -5,9 +5,12 @@ module.exports = function (hash, network) { case 1: // main net prefix = '' break - case 3: // morden test net + case 3: // ropsten test net prefix = 'testnet.' break + case 42: // kovan test net + prefix = 'kovan.' + break default: prefix = '' } From b3dfc4e639bf4e73784d8a53227cfc0bc0a650fd Mon Sep 17 00:00:00 2001 From: Kevin Serrano Date: Wed, 22 Mar 2017 16:03:02 -0400 Subject: [PATCH 39/47] Add kovan conditional to config screen. --- ui/app/config.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/ui/app/config.js b/ui/app/config.js index 3f0507f48..444365de2 100644 --- a/ui/app/config.js +++ b/ui/app/config.js @@ -161,6 +161,11 @@ function currentProviderDisplay (metamaskState) { value = 'Ropsten Test Network' break + case 'kovan': + title = 'Current Network' + value = 'Kovan Test Network' + break + default: title = 'Current RPC' value = metamaskState.provider.rpcTarget From 4757858df0ea63952ec8e435749a0f2df0782612 Mon Sep 17 00:00:00 2001 From: Kevin Serrano Date: Wed, 22 Mar 2017 16:03:51 -0400 Subject: [PATCH 40/47] Add conditional kovan to current network component. --- ui/app/components/network.js | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/ui/app/components/network.js b/ui/app/components/network.js index 77805fd57..d9045167f 100644 --- a/ui/app/components/network.js +++ b/ui/app/components/network.js @@ -40,6 +40,9 @@ Network.prototype.render = function () { } else if (parseInt(networkNumber) === 3) { hoverText = 'Ropsten Test Network' iconName = 'ropsten-test-network' + } else if (providerName === 'kovan') { + hoverText = 'Kovan Test Network' + iconName = 'kovan-test-network' } else { hoverText = 'Unknown Private Network' iconName = 'unknown-private-network' @@ -70,6 +73,15 @@ Network.prototype.render = function () { }}, 'Ropsten Test Net'), ]) + case 'kovan-test-network': + return h('.network-indicator', [ + h('.menu-icon.hollow-diamond'), + h('.network-name', { + style: { + color: '#690496', + }}, + 'Kovan Test Net'), + ]) default: return h('.network-indicator', [ h('i.fa.fa-question-circle.fa-lg', { From 34f3889eb00d41920c67ec2d4845626b5b480eb5 Mon Sep 17 00:00:00 2001 From: Kevin Serrano Date: Wed, 22 Mar 2017 16:04:28 -0400 Subject: [PATCH 41/47] Add kovan to drop-menu-item --- ui/app/components/drop-menu-item.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/ui/app/components/drop-menu-item.js b/ui/app/components/drop-menu-item.js index 9f002234e..3eb6ec876 100644 --- a/ui/app/components/drop-menu-item.js +++ b/ui/app/components/drop-menu-item.js @@ -42,7 +42,10 @@ DropMenuItem.prototype.activeNetworkRender = function () { if (providerType === 'mainnet') return h('.check', '✓') break case 'Ropsten Test Network': - if (provider.type === 'testnet') return h('.check', '✓') + if (providerType === 'testnet') return h('.check', '✓') + break + case 'Kovan Test Network': + if (providerType === 'kovan') return h('.check', '✓') break case 'Localhost 8545': if (activeNetwork === 'http://localhost:8545') return h('.check', '✓') From b26c53452e2817f4f00a4770eae8828731e97808 Mon Sep 17 00:00:00 2001 From: Kevin Serrano Date: Wed, 22 Mar 2017 16:05:04 -0400 Subject: [PATCH 42/47] Add Kovan test network to our application. --- ui/app/app.js | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/ui/app/app.js b/ui/app/app.js index 9c1ba8a3a..5a7596aca 100644 --- a/ui/app/app.js +++ b/ui/app/app.js @@ -255,6 +255,15 @@ App.prototype.renderNetworkDropdown = function () { provider: props.provider, }), + h(DropMenuItem, { + label: 'Kovan Test Network', + closeMenu: () => this.setState({ isNetworkMenuOpen: false}), + action: () => props.dispatch(actions.setProviderType('kovan')), + icon: h('.menu-icon.hollow-diamond'), + activeNetworkRender: props.network, + provider: props.provider, + }), + h(DropMenuItem, { label: 'Localhost 8545', closeMenu: () => this.setState({ isNetworkMenuOpen: false }), From 1c7e04db4a2997ec6a62558d331b7e23190cebdc Mon Sep 17 00:00:00 2001 From: Kevin Serrano Date: Wed, 22 Mar 2017 16:06:48 -0400 Subject: [PATCH 43/47] Changelog bump. --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6621d89f4..34ab63ab4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ - Can now change network to custom RPC URL from lock screen. - Removed support for old, lightwallet based vault. Users who have not opened app in over a month will need to recover with their seed phrase. This will allow Firefox support sooner. - Polish the private key UI. +- Add Kovan as an option on our network list. ## 3.4.0 2017-3-8 From 1b7326048db25afc36baea84ebcf9ece0847f3c3 Mon Sep 17 00:00:00 2001 From: Kevin Serrano Date: Wed, 22 Mar 2017 16:41:19 -0400 Subject: [PATCH 44/47] Add current block number and hash to the state. --- app/scripts/lib/eth-store.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/app/scripts/lib/eth-store.js b/app/scripts/lib/eth-store.js index 8812a507b..243253df2 100644 --- a/app/scripts/lib/eth-store.js +++ b/app/scripts/lib/eth-store.js @@ -19,6 +19,8 @@ class EthereumStore extends ObservableStore { super({ accounts: {}, transactions: {}, + currentBlockNumber: '0', + currentBlockHash: '', }) this._provider = opts.provider this._query = new EthQuery(this._provider) @@ -69,6 +71,8 @@ class EthereumStore extends ObservableStore { _updateForBlock (block) { const blockNumber = '0x' + block.number.toString('hex') this._currentBlockNumber = blockNumber + this.updateState({ currentBlockNumber: parseInt(blockNumber) }) + this.updateState({ currentBlockHash: `0x${block.hash.toString('hex')}`}) async.parallel([ this._updateAccounts.bind(this), this._updateTransactions.bind(this, blockNumber), @@ -129,4 +133,4 @@ class EthereumStore extends ObservableStore { } -module.exports = EthereumStore \ No newline at end of file +module.exports = EthereumStore From 5d149258427b25f34dca89e5dfaba57a4f1cb95b Mon Sep 17 00:00:00 2001 From: Kevin Serrano Date: Wed, 22 Mar 2017 16:54:10 -0400 Subject: [PATCH 45/47] Fix styling of error message. --- ui/app/accounts/import/json.js | 3 +-- ui/app/accounts/import/private-key.js | 3 +-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/ui/app/accounts/import/json.js b/ui/app/accounts/import/json.js index 1c2b331d4..5ed31ab0a 100644 --- a/ui/app/accounts/import/json.js +++ b/ui/app/accounts/import/json.js @@ -60,7 +60,7 @@ JsonImportSubview.prototype.render = function () { }, }, 'Import'), - error ? h('span.warning', error) : null, + error ? h('span.error', error) : null, ]) ) } @@ -95,4 +95,3 @@ JsonImportSubview.prototype.createNewKeychain = function () { this.props.dispatch(actions.importNewAccount('JSON File', [ fileContents, password ])) } - diff --git a/ui/app/accounts/import/private-key.js b/ui/app/accounts/import/private-key.js index b139a0374..68ccee58e 100644 --- a/ui/app/accounts/import/private-key.js +++ b/ui/app/accounts/import/private-key.js @@ -48,7 +48,7 @@ PrivateKeyImportView.prototype.render = function () { }, }, 'Import'), - error ? h('span.warning', error) : null, + error ? h('span.error', error) : null, ]) ) } @@ -65,4 +65,3 @@ PrivateKeyImportView.prototype.createNewKeychain = function () { const privateKey = input.value this.props.dispatch(actions.importNewAccount('Private Key', [ privateKey ])) } - From 41e276b0367281bb3e6481d0e7c6af89b7319f90 Mon Sep 17 00:00:00 2001 From: Kevin Serrano Date: Wed, 22 Mar 2017 17:46:51 -0400 Subject: [PATCH 46/47] Hide the password in the private key retrieval screen. --- ui/app/components/account-export.js | 1 + 1 file changed, 1 insertion(+) diff --git a/ui/app/components/account-export.js b/ui/app/components/account-export.js index 38a1d28ef..888196c5d 100644 --- a/ui/app/components/account-export.js +++ b/ui/app/components/account-export.js @@ -52,6 +52,7 @@ ExportAccountView.prototype.render = function () { }, [ h('p.error', warning), h('input#exportAccount.sizing-input', { + type: 'password', placeholder: 'confirm password', onKeyPress: this.onExportKeyPress.bind(this), style: { From 525c32ae604a148b803be307b6ac3cb7f9b8617a Mon Sep 17 00:00:00 2001 From: Kevin Serrano Date: Thu, 23 Mar 2017 11:26:39 -0400 Subject: [PATCH 47/47] Enable etherscan linking on Kovan transaction list items. --- ui/app/components/transaction-list-item.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/app/components/transaction-list-item.js b/ui/app/components/transaction-list-item.js index 44d2dc587..ee32be7d3 100644 --- a/ui/app/components/transaction-list-item.js +++ b/ui/app/components/transaction-list-item.js @@ -28,7 +28,7 @@ TransactionListItem.prototype.render = function () { let isLinkable = false const numericNet = parseInt(network) - isLinkable = numericNet === 1 || numericNet === 3 + isLinkable = numericNet === 1 || numericNet === 3 || numericNet === 42 var isMsg = ('msgParams' in transaction) var isTx = ('txParams' in transaction)