From c2b8dada91c90788dcd81a0318c52a66b4b769d1 Mon Sep 17 00:00:00 2001 From: Sergey Ukustov Date: Fri, 29 Sep 2017 19:24:08 +0300 Subject: [PATCH 01/47] Add eth_signTypedData handler --- app/scripts/background.js | 3 +- app/scripts/lib/typed-message-manager.js | 108 ++++++++++++++++++ app/scripts/metamask-controller.js | 52 +++++++++ ui/app/actions.js | 27 +++++ .../components/pending-typed-msg-details.js | 59 ++++++++++ ui/app/components/pending-typed-msg.js | 46 ++++++++ ui/app/components/typed-message-renderer.js | 42 +++++++ ui/app/conf-tx.js | 25 +++- ui/app/reducers/app.js | 4 +- ui/index.js | 2 +- ui/lib/tx-helper.js | 13 ++- 11 files changed, 372 insertions(+), 9 deletions(-) create mode 100644 app/scripts/lib/typed-message-manager.js create mode 100644 ui/app/components/pending-typed-msg-details.js create mode 100644 ui/app/components/pending-typed-msg.js create mode 100644 ui/app/components/typed-message-renderer.js diff --git a/app/scripts/background.js b/app/scripts/background.js index 195881e15..3e560d302 100644 --- a/app/scripts/background.js +++ b/app/scripts/background.js @@ -124,7 +124,8 @@ function setupController (initState) { var unapprovedTxCount = controller.txController.getUnapprovedTxCount() var unapprovedMsgCount = controller.messageManager.unapprovedMsgCount var unapprovedPersonalMsgs = controller.personalMessageManager.unapprovedPersonalMsgCount - var count = unapprovedTxCount + unapprovedMsgCount + unapprovedPersonalMsgs + var unapprovedTypedMsgs = controller.typedMessageManager.unapprovedTypedMessagesCount + var count = unapprovedTxCount + unapprovedMsgCount + unapprovedPersonalMsgs + unapprovedTypedMsgs if (count) { label = String(count) } diff --git a/app/scripts/lib/typed-message-manager.js b/app/scripts/lib/typed-message-manager.js new file mode 100644 index 000000000..e3efdb45d --- /dev/null +++ b/app/scripts/lib/typed-message-manager.js @@ -0,0 +1,108 @@ +const EventEmitter = require('events') +const ObservableStore = require('obs-store') +const createId = require('./random-id') + + +module.exports = class TypedMessageManager extends EventEmitter { + constructor (opts) { + super() + this.memStore = new ObservableStore({ + unapprovedTypedMessages: {}, + unapprovedTypedMessagesCount: 0, + }) + this.messages = [] + } + + get unapprovedTypedMessagesCount () { + return Object.keys(this.getUnapprovedMsgs()).length + } + + getUnapprovedMsgs () { + return this.messages.filter(msg => msg.status === 'unapproved') + .reduce((result, msg) => { result[msg.id] = msg; return result }, {}) + } + + addUnapprovedMessage (msgParams) { + log.debug(`TypedMessageManager addUnapprovedMessage: ${JSON.stringify(msgParams)}`) + // create txData obj with parameters and meta data + var time = (new Date()).getTime() + var msgId = createId() + var msgData = { + id: msgId, + msgParams: msgParams, + time: time, + status: 'unapproved', + type: 'eth_signTypedData', + } + this.addMsg(msgData) + + // signal update + this.emit('update') + return msgId + } + + addMsg (msg) { + this.messages.push(msg) + this._saveMsgList() + } + + getMsg (msgId) { + return this.messages.find(msg => msg.id === msgId) + } + + approveMessage (msgParams) { + this.setMsgStatusApproved(msgParams.metamaskId) + return this.prepMsgForSigning(msgParams) + } + + setMsgStatusApproved (msgId) { + this._setMsgStatus(msgId, 'approved') + } + + setMsgStatusSigned (msgId, rawSig) { + const msg = this.getMsg(msgId) + msg.rawSig = rawSig + this._updateMsg(msg) + this._setMsgStatus(msgId, 'signed') + } + + prepMsgForSigning (msgParams) { + delete msgParams.metamaskId + return Promise.resolve(msgParams) + } + + rejectMsg (msgId) { + this._setMsgStatus(msgId, 'rejected') + } + + // + // PRIVATE METHODS + // + + _setMsgStatus (msgId, status) { + const msg = this.getMsg(msgId) + if (!msg) throw new Error('TypedMessageManager - Message not found for id: "${msgId}".') + msg.status = status + this._updateMsg(msg) + this.emit(`${msgId}:${status}`, msg) + if (status === 'rejected' || status === 'signed') { + this.emit(`${msgId}:finished`, msg) + } + } + + _updateMsg (msg) { + const index = this.messages.findIndex((message) => message.id === msg.id) + if (index !== -1) { + this.messages[index] = msg + } + this._saveMsgList() + } + + _saveMsgList () { + const unapprovedTypedMessages = this.getUnapprovedMsgs() + const unapprovedTypedMessagesCount = Object.keys(unapprovedTypedMessages).length + this.memStore.updateState({ unapprovedTypedMessages, unapprovedTypedMessagesCount }) + this.emit('updateBadge') + } + +} diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index 5b3161bc6..0eeb708fc 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -25,6 +25,7 @@ const InfuraController = require('./controllers/infura') const BlacklistController = require('./controllers/blacklist') const MessageManager = require('./lib/message-manager') const PersonalMessageManager = require('./lib/personal-message-manager') +const TypedMessageManager = require('./lib/typed-message-manager') const TransactionController = require('./controllers/transactions') const BalancesController = require('./controllers/computed-balances') const ConfigManager = require('./lib/config-manager') @@ -154,6 +155,7 @@ module.exports = class MetamaskController extends EventEmitter { this.networkController.lookupNetwork() this.messageManager = new MessageManager() this.personalMessageManager = new PersonalMessageManager() + this.typedMessageManager = new TypedMessageManager() this.publicConfigStore = this.initPublicConfigStore() // manual disk state subscriptions @@ -195,6 +197,7 @@ module.exports = class MetamaskController extends EventEmitter { this.balancesController.store.subscribe(this.sendUpdate.bind(this)) this.messageManager.memStore.subscribe(this.sendUpdate.bind(this)) this.personalMessageManager.memStore.subscribe(this.sendUpdate.bind(this)) + this.typedMessageManager.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)) @@ -234,6 +237,7 @@ module.exports = class MetamaskController extends EventEmitter { processMessage: this.newUnsignedMessage.bind(this), // personal_sign msg signing processPersonalMessage: this.newUnsignedPersonalMessage.bind(this), + processTypedMessage: this.newUnsignedTypedMessage.bind(this), }) } @@ -276,6 +280,7 @@ module.exports = class MetamaskController extends EventEmitter { this.txController.memStore.getState(), this.messageManager.memStore.getState(), this.personalMessageManager.memStore.getState(), + this.typedMessageManager.memStore.getState(), this.keyringController.memStore.getState(), this.balancesController.store.getState(), this.preferencesController.store.getState(), @@ -354,6 +359,10 @@ module.exports = class MetamaskController extends EventEmitter { signPersonalMessage: nodeify(this.signPersonalMessage, this), cancelPersonalMessage: this.cancelPersonalMessage.bind(this), + // personalMessageManager + signTypedMessage: nodeify(this.signTypedMessage, this), + cancelTypedMessage: this.cancelTypedMessage.bind(this), + // notices checkNotices: noticeController.updateNoticesList.bind(noticeController), markNoticeRead: noticeController.markNoticeRead.bind(noticeController), @@ -546,6 +555,23 @@ module.exports = class MetamaskController extends EventEmitter { }) } + newUnsignedTypedMessage (msgParams, cb) { + const msgId = this.typedMessageManager.addUnapprovedMessage(msgParams) + this.sendUpdate() + this.opts.showUnconfirmedMessage() + this.typedMessageManager.once(`${msgId}:finished`, (data) => { + console.log(data) + switch (data.status) { + case 'signed': + return cb(null, data.rawSig) + case 'rejected': + return cb(new Error('MetaMask Message Signature: User denied message signature.')) + default: + return cb(new Error(`MetaMask Message Signature: Unknown problem: ${JSON.stringify(msgParams)}`)) + } + }) + } + signMessage (msgParams, cb) { log.info('MetaMaskController - signMessage') const msgId = msgParams.metamaskId @@ -608,6 +634,24 @@ module.exports = class MetamaskController extends EventEmitter { }) } + signTypedMessage (msgParams) { + log.info('MetaMaskController - signTypedMessage') + const msgId = msgParams.metamaskId + // sets the status op the message to 'approved' + // and removes the metamaskId for signing + return this.typedMessageManager.approveMessage(msgParams) + .then((cleanMsgParams) => { + // signs the message + return this.keyringController.signTypedMessage(cleanMsgParams) + }) + .then((rawSig) => { + // tells the listener that the message has been signed + // and can be returned to the dapp + this.typedMessageManager.setMsgStatusSigned(msgId, rawSig) + return this.getState() + }) + } + cancelPersonalMessage (msgId, cb) { const messageManager = this.personalMessageManager messageManager.rejectMsg(msgId) @@ -616,6 +660,14 @@ module.exports = class MetamaskController extends EventEmitter { } } + cancelTypedMessage (msgId, cb) { + const messageManager = this.typedMessageManager + messageManager.rejectMsg(msgId) + if (cb && typeof cb === 'function') { + cb(null, this.getState()) + } + } + markAccountsFound (cb) { this.configManager.setLostAccounts([]) this.sendUpdate() diff --git a/ui/app/actions.js b/ui/app/actions.js index e793e6a21..84a1b8dcc 100644 --- a/ui/app/actions.js +++ b/ui/app/actions.js @@ -97,6 +97,8 @@ var actions = { cancelMsg: cancelMsg, signPersonalMsg, cancelPersonalMsg, + signTypedMsg, + cancelTypedMsg, signTx: signTx, updateAndApproveTx, cancelTx: cancelTx, @@ -395,6 +397,25 @@ function signPersonalMsg (msgData) { } } +function signTypedMsg (msgData) { + log.debug('action - signTypedMsg') + return (dispatch) => { + dispatch(actions.showLoadingIndication()) + + log.debug(`actions calling background.signTypedMessage`) + background.signTypedMessage(msgData, (err, newState) => { + log.debug('signTypedMessage called back') + dispatch(actions.updateMetamaskState(newState)) + dispatch(actions.hideLoadingIndication()) + + if (err) log.error(err) + if (err) return dispatch(actions.displayWarning(err.message)) + + dispatch(actions.completedTx(msgData.metamaskId)) + }) + } +} + function signTx (txData) { return (dispatch) => { dispatch(actions.showLoadingIndication()) @@ -449,6 +470,12 @@ function cancelPersonalMsg (msgData) { return actions.completedTx(id) } +function cancelTypedMsg (msgData) { + const id = msgData.id + background.cancelTypedMessage(id) + return actions.completedTx(id) +} + function cancelTx (txData) { return (dispatch) => { log.debug(`background.cancelTransaction`) diff --git a/ui/app/components/pending-typed-msg-details.js b/ui/app/components/pending-typed-msg-details.js new file mode 100644 index 000000000..b5fd29f71 --- /dev/null +++ b/ui/app/components/pending-typed-msg-details.js @@ -0,0 +1,59 @@ +const Component = require('react').Component +const h = require('react-hyperscript') +const inherits = require('util').inherits + +const AccountPanel = require('./account-panel') +const TypedMessageRenderer = require('./typed-message-renderer') + +module.exports = PendingMsgDetails + +inherits(PendingMsgDetails, Component) +function PendingMsgDetails () { + Component.call(this) +} + +PendingMsgDetails.prototype.render = function () { + var state = this.props + var msgData = state.txData + + var msgParams = msgData.msgParams || {} + var address = msgParams.from || state.selectedAddress + var identity = state.identities[address] || { address: address } + var account = state.accounts[address] || { address: address } + + var { data } = msgParams + + return ( + h('div', { + key: msgData.id, + style: { + margin: '10px 20px', + }, + }, [ + + // account that will sign + h(AccountPanel, { + showFullAddress: true, + identity: identity, + account: account, + imageifyIdenticons: state.imageifyIdenticons, + }), + + // message data + h('div', { + style: { + height: '260px', + }, + }, [ + h('label.font-small', { style: { display: 'block' } }, 'YOU ARE SIGNING'), + h(TypedMessageRenderer, { + value: data, + style: { + height: '215px', + }, + }), + ]), + + ]) + ) +} diff --git a/ui/app/components/pending-typed-msg.js b/ui/app/components/pending-typed-msg.js new file mode 100644 index 000000000..f8926d0a3 --- /dev/null +++ b/ui/app/components/pending-typed-msg.js @@ -0,0 +1,46 @@ +const Component = require('react').Component +const h = require('react-hyperscript') +const inherits = require('util').inherits +const PendingTxDetails = require('./pending-typed-msg-details') + +module.exports = PendingMsg + +inherits(PendingMsg, Component) +function PendingMsg () { + Component.call(this) +} + +PendingMsg.prototype.render = function () { + var state = this.props + var msgData = state.txData + + return ( + + h('div', { + key: msgData.id, + }, [ + + // header + h('h3', { + style: { + fontWeight: 'bold', + textAlign: 'center', + }, + }, 'Sign Message'), + + // message details + h(PendingTxDetails, state), + + // sign + cancel + h('.flex-row.flex-space-around', [ + h('button', { + onClick: state.cancelTypedMessage, + }, 'Cancel'), + h('button', { + onClick: state.signTypedMessage, + }, 'Sign'), + ]), + ]) + + ) +} diff --git a/ui/app/components/typed-message-renderer.js b/ui/app/components/typed-message-renderer.js new file mode 100644 index 000000000..50e8da02c --- /dev/null +++ b/ui/app/components/typed-message-renderer.js @@ -0,0 +1,42 @@ +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 = TypedMessageRenderer + +inherits(TypedMessageRenderer, Component) +function TypedMessageRenderer () { + Component.call(this) +} + +TypedMessageRenderer.prototype.render = function () { + const props = this.props + const { value, style } = props + const text = renderTypedData(value) + + const defaultStyle = extend({ + width: '315px', + maxHeight: '210px', + resize: 'none', + border: 'none', + background: 'white', + padding: '3px', + }, style) + + return ( + h('div.font-small', { + style: defaultStyle, + }, text) + ) +} + +function renderTypedData(values) { + return values.map(function (value) { + return h('div', {}, [ + h('strong', {style: {display: 'block', fontWeight: 'bold', textTransform: 'capitalize'}}, String(value.name) + ':'), + h('div', {}, value.value) + ]) + }) +} \ No newline at end of file diff --git a/ui/app/conf-tx.js b/ui/app/conf-tx.js index 15fb9a59f..f93fc2373 100644 --- a/ui/app/conf-tx.js +++ b/ui/app/conf-tx.js @@ -10,6 +10,7 @@ const isPopupOrNotification = require('../../app/scripts/lib/is-popup-or-notific const PendingTx = require('./components/pending-tx') const PendingMsg = require('./components/pending-msg') const PendingPersonalMsg = require('./components/pending-personal-msg') +const PendingTypedMsg = require('./components/pending-typed-msg') const Loading = require('./components/loading') module.exports = connect(mapStateToProps)(ConfirmTxScreen) @@ -22,6 +23,7 @@ function mapStateToProps (state) { unapprovedTxs: state.metamask.unapprovedTxs, unapprovedMsgs: state.metamask.unapprovedMsgs, unapprovedPersonalMsgs: state.metamask.unapprovedPersonalMsgs, + unapprovedTypedMessages: state.metamask.unapprovedTypedMessages, index: state.appState.currentView.context, warning: state.appState.warning, network: state.metamask.network, @@ -41,9 +43,9 @@ function ConfirmTxScreen () { ConfirmTxScreen.prototype.render = function () { const props = this.props const { network, provider, unapprovedTxs, currentCurrency, computedBalances, - unapprovedMsgs, unapprovedPersonalMsgs, conversionRate, blockGasLimit } = props + unapprovedMsgs, unapprovedPersonalMsgs, unapprovedTypedMessages, conversionRate, blockGasLimit } = props - var unconfTxList = txHelper(unapprovedTxs, unapprovedMsgs, unapprovedPersonalMsgs, network) + var unconfTxList = txHelper(unapprovedTxs, unapprovedMsgs, unapprovedPersonalMsgs, unapprovedTypedMessages, network) var txData = unconfTxList[props.index] || {} var txParams = txData.params || {} @@ -112,8 +114,10 @@ ConfirmTxScreen.prototype.render = function () { cancelAllTransactions: this.cancelAllTransactions.bind(this, unconfTxList), signMessage: this.signMessage.bind(this, txData), signPersonalMessage: this.signPersonalMessage.bind(this, txData), + signTypedMessage: this.signTypedMessage.bind(this, txData), cancelMessage: this.cancelMessage.bind(this, txData), cancelPersonalMessage: this.cancelPersonalMessage.bind(this, txData), + cancelTypedMessage: this.cancelTypedMessage.bind(this, txData) }), ]) ) @@ -136,6 +140,9 @@ function currentTxView (opts) { } else if (type === 'personal_sign') { log.debug('rendering personal_sign message') return h(PendingPersonalMsg, opts) + } else if (type === 'eth_signTypedData') { + log.debug('rendering eth_signTypedData message') + return h(PendingTypedMsg, opts) } } } @@ -184,6 +191,14 @@ ConfirmTxScreen.prototype.signPersonalMessage = function (msgData, event) { this.props.dispatch(actions.signPersonalMsg(params)) } +ConfirmTxScreen.prototype.signTypedMessage = function (msgData, event) { + log.info('conf-tx.js: signing typed message') + var params = msgData.msgParams + params.metamaskId = msgData.id + this.stopPropagation(event) + this.props.dispatch(actions.signTypedMsg(params)) +} + ConfirmTxScreen.prototype.cancelMessage = function (msgData, event) { log.info('canceling message') this.stopPropagation(event) @@ -196,6 +211,12 @@ ConfirmTxScreen.prototype.cancelPersonalMessage = function (msgData, event) { this.props.dispatch(actions.cancelPersonalMsg(msgData)) } +ConfirmTxScreen.prototype.cancelTypedMessage = function (msgData, event) { + log.info('canceling typed message') + this.stopPropagation(event) + this.props.dispatch(actions.cancelTypedMsg(msgData)) +} + ConfirmTxScreen.prototype.goHome = function (event) { this.stopPropagation(event) this.props.dispatch(actions.goHome()) diff --git a/ui/app/reducers/app.js b/ui/app/reducers/app.js index 3a98d53a9..349c25b96 100644 --- a/ui/app/reducers/app.js +++ b/ui/app/reducers/app.js @@ -574,9 +574,9 @@ function checkUnconfActions (state) { function getUnconfActionList (state) { const { unapprovedTxs, unapprovedMsgs, - unapprovedPersonalMsgs, network } = state.metamask + unapprovedPersonalMsgs, unapprovedTypedMessages, network } = state.metamask - const unconfActionList = txHelper(unapprovedTxs, unapprovedMsgs, unapprovedPersonalMsgs, network) + const unconfActionList = txHelper(unapprovedTxs, unapprovedMsgs, unapprovedPersonalMsgs, unapprovedTypedMessages, network) return unconfActionList } diff --git a/ui/index.js b/ui/index.js index a729138d3..ae05cbe67 100644 --- a/ui/index.js +++ b/ui/index.js @@ -37,7 +37,7 @@ function startApp (metamaskState, accountManager, opts) { }) // if unconfirmed txs, start on txConf page - const unapprovedTxsAll = txHelper(metamaskState.unapprovedTxs, metamaskState.unapprovedMsgs, metamaskState.unapprovedPersonalMsgs, metamaskState.network) + const unapprovedTxsAll = txHelper(metamaskState.unapprovedTxs, metamaskState.unapprovedMsgs, metamaskState.unapprovedPersonalMsgs, metamaskState.unapprovedTypedMessages, metamaskState.network) if (unapprovedTxsAll.length > 0) { store.dispatch(actions.showConfTxPage()) } diff --git a/ui/lib/tx-helper.js b/ui/lib/tx-helper.js index 5def23e51..341567e2f 100644 --- a/ui/lib/tx-helper.js +++ b/ui/lib/tx-helper.js @@ -1,20 +1,27 @@ const valuesFor = require('../app/util').valuesFor -module.exports = function (unapprovedTxs, unapprovedMsgs, personalMsgs, network) { +module.exports = function (unapprovedTxs, unapprovedMsgs, personalMsgs, typedMessages, network) { log.debug('tx-helper called with params:') - log.debug({ unapprovedTxs, unapprovedMsgs, personalMsgs, network }) + log.debug({ unapprovedTxs, unapprovedMsgs, personalMsgs, typedMessages, network }) const txValues = network ? valuesFor(unapprovedTxs).filter(txMeta => txMeta.metamaskNetworkId === network) : valuesFor(unapprovedTxs) log.debug(`tx helper found ${txValues.length} unapproved txs`) + const msgValues = valuesFor(unapprovedMsgs) log.debug(`tx helper found ${msgValues.length} unsigned messages`) let allValues = txValues.concat(msgValues) + const personalValues = valuesFor(personalMsgs) log.debug(`tx helper found ${personalValues.length} unsigned personal messages`) allValues = allValues.concat(personalValues) + + const typedValues = valuesFor(typedMessages) + log.debug(`tx helper found ${typedValues.length} unsigned typed messages`) + allValues = allValues.concat(typedValues) + allValues = allValues.sort((a, b) => { return a.time > b.time }) return allValues -} +} \ No newline at end of file From 82d1f391986ac6f7cb7d9029f50d44b5a8c9442b Mon Sep 17 00:00:00 2001 From: Sergey Ukustov Date: Fri, 29 Sep 2017 19:47:40 +0300 Subject: [PATCH 02/47] Respect code style --- ui/app/components/typed-message-renderer.js | 3 +-- ui/app/conf-tx.js | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/ui/app/components/typed-message-renderer.js b/ui/app/components/typed-message-renderer.js index 50e8da02c..b7d1572c2 100644 --- a/ui/app/components/typed-message-renderer.js +++ b/ui/app/components/typed-message-renderer.js @@ -1,7 +1,6 @@ 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 = TypedMessageRenderer @@ -36,7 +35,7 @@ function renderTypedData(values) { return values.map(function (value) { return h('div', {}, [ h('strong', {style: {display: 'block', fontWeight: 'bold', textTransform: 'capitalize'}}, String(value.name) + ':'), - h('div', {}, value.value) + h('div', {}, value.value), ]) }) } \ No newline at end of file diff --git a/ui/app/conf-tx.js b/ui/app/conf-tx.js index f93fc2373..cb1afedfe 100644 --- a/ui/app/conf-tx.js +++ b/ui/app/conf-tx.js @@ -117,7 +117,7 @@ ConfirmTxScreen.prototype.render = function () { signTypedMessage: this.signTypedMessage.bind(this, txData), cancelMessage: this.cancelMessage.bind(this, txData), cancelPersonalMessage: this.cancelPersonalMessage.bind(this, txData), - cancelTypedMessage: this.cancelTypedMessage.bind(this, txData) + cancelTypedMessage: this.cancelTypedMessage.bind(this, txData), }), ]) ) From 27c72ee565f02e319af2a4e2c03a885ae633ae71 Mon Sep 17 00:00:00 2001 From: Kevin Serrano Date: Wed, 4 Oct 2017 12:10:01 -0700 Subject: [PATCH 03/47] Revert to normal balances. --- ui/app/account-detail.js | 4 ++-- ui/app/components/pending-tx.js | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/ui/app/account-detail.js b/ui/app/account-detail.js index 90724dc3f..a844daf88 100644 --- a/ui/app/account-detail.js +++ b/ui/app/account-detail.js @@ -46,7 +46,7 @@ AccountDetailScreen.prototype.render = function () { var selected = props.address || Object.keys(props.accounts)[0] var checksumAddress = selected && ethUtil.toChecksumAddress(selected) var identity = props.identities[selected] - var account = props.computedBalances[selected] + var account = props.accounts[selected] const { network, conversionRate, currentCurrency } = props return ( @@ -181,7 +181,7 @@ AccountDetailScreen.prototype.render = function () { }, [ h(EthBalance, { - value: account && account.ethBalance, + value: account && account.balance, conversionRate, currentCurrency, style: { diff --git a/ui/app/components/pending-tx.js b/ui/app/components/pending-tx.js index 6f8c19a3c..c3350fcc1 100644 --- a/ui/app/components/pending-tx.js +++ b/ui/app/components/pending-tx.js @@ -33,7 +33,7 @@ function PendingTx () { PendingTx.prototype.render = function () { const props = this.props - const { currentCurrency, blockGasLimit, computedBalances } = props + const { currentCurrency, blockGasLimit } = props const conversionRate = props.conversionRate const txMeta = this.gatherTxMeta() @@ -42,8 +42,8 @@ PendingTx.prototype.render = function () { // Account Details const address = txParams.from || props.selectedAddress const identity = props.identities[address] || { address: address } - const account = computedBalances[address] - const balance = account ? account.ethBalance : '0x0' + const account = props.accounts[address] + const balance = account ? account.balance : '0x0' // recipient check const isValidAddress = !txParams.to || util.isValidAddress(txParams.to) From 88686a39681ef84f2b503f25b94607d96657a190 Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Wed, 4 Oct 2017 15:35:46 -0700 Subject: [PATCH 04/47] Enforce 0x prefix on accounts with new hd keyring --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 03d095228..298691588 100644 --- a/package.json +++ b/package.json @@ -69,7 +69,7 @@ "eth-bin-to-ops": "^1.0.1", "eth-block-tracker": "^2.2.0", "eth-contract-metadata": "^1.1.4", - "eth-hd-keyring": "^1.1.1", + "eth-hd-keyring": "^1.2.1", "eth-json-rpc-filters": "^1.2.2", "eth-keyring-controller": "^2.0.0", "eth-phishing-detect": "^1.1.4", From e7589a099fa592b930458a16d7346ff20070a6e8 Mon Sep 17 00:00:00 2001 From: frankiebee Date: Wed, 4 Oct 2017 22:09:37 -0700 Subject: [PATCH 05/47] mascara:exampl/app - add a send tx button --- mascara/example/app.js | 28 ++++++++++++++++++++-------- mascara/example/app/index.html | 2 ++ 2 files changed, 22 insertions(+), 8 deletions(-) diff --git a/mascara/example/app.js b/mascara/example/app.js index d0cb6ba83..598e2c84c 100644 --- a/mascara/example/app.js +++ b/mascara/example/app.js @@ -7,20 +7,32 @@ async function loadProvider() { const ethereumProvider = window.metamask.createDefaultProvider({ host: 'http://localhost:9001' }) const ethQuery = new EthQuery(ethereumProvider) const accounts = await ethQuery.accounts() - logToDom(accounts.length ? accounts[0] : 'LOCKED or undefined') - setupButton(ethQuery) + window.METAMASK_ACCOUNT = accounts[0] || 'locked' + logToDom(accounts.length ? accounts[0] : 'LOCKED or undefined', 'account') + setupButtons(ethQuery) } -function logToDom(message){ - document.getElementById('account').innerText = message +function logToDom(message, context){ + document.getElementById(context).innerText = message console.log(message) } -function setupButton (ethQuery) { - const button = document.getElementById('action-button-1') - button.addEventListener('click', async () => { +function setupButtons (ethQuery) { + const accountButton = document.getElementById('action-button-1') + accountButton.addEventListener('click', async () => { const accounts = await ethQuery.accounts() - logToDom(accounts.length ? accounts[0] : 'LOCKED or undefined') + window.METAMASK_ACCOUNT = accounts[0] || 'locked' + logToDom(accounts.length ? accounts[0] : 'LOCKED or undefined', 'account') + }) + const txButton = document.getElementById('action-button-2') + txButton.addEventListener('click', async () => { + if (!window.METAMASK_ACCOUNT || window.METAMASK_ACCOUNT === 'locked') return + const txHash = await ethQuery.sendTransaction({ + from: window.METAMASK_ACCOUNT, + to: window.METAMASK_ACCOUNT, + data: '', + }) + logToDom(txHash, 'cb-value') }) } \ No newline at end of file diff --git a/mascara/example/app/index.html b/mascara/example/app/index.html index f3e38877c..8afb6f3f2 100644 --- a/mascara/example/app/index.html +++ b/mascara/example/app/index.html @@ -10,6 +10,8 @@
+ +
\ No newline at end of file From 245ab70881d2ddabf14a6dc13d75c3f1789ab804 Mon Sep 17 00:00:00 2001 From: frankiebee Date: Wed, 4 Oct 2017 22:10:30 -0700 Subject: [PATCH 06/47] add /mascara to linting --- gulpfile.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gulpfile.js b/gulpfile.js index ac36cf983..c18fd5949 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -151,7 +151,7 @@ gulp.task('copy:watch', function(){ gulp.task('lint', function () { // Ignoring node_modules, dist/firefox, and docs folders: - return gulp.src(['app/**/*.js', 'ui/**/*.js', '!node_modules/**', '!dist/firefox/**', '!docs/**', '!app/scripts/chromereload.js']) + return gulp.src(['app/**/*.js', 'ui/**/*.js', 'mascara/**/*.js', '!node_modules/**', '!dist/firefox/**', '!docs/**', '!app/scripts/chromereload.js']) .pipe(eslint(fs.readFileSync(path.join(__dirname, '.eslintrc')))) // eslint.format() outputs the lint results to the console. // Alternatively use eslint.formatEach() (see Docs). From 2dba03ffc530e3e2a176aea9712ffc3e189bebc2 Mon Sep 17 00:00:00 2001 From: frankiebee Date: Wed, 4 Oct 2017 22:30:28 -0700 Subject: [PATCH 07/47] dont lint jquery --- gulpfile.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gulpfile.js b/gulpfile.js index c18fd5949..557b58a68 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -151,7 +151,7 @@ gulp.task('copy:watch', function(){ gulp.task('lint', function () { // Ignoring node_modules, dist/firefox, and docs folders: - return gulp.src(['app/**/*.js', 'ui/**/*.js', 'mascara/**/*.js', '!node_modules/**', '!dist/firefox/**', '!docs/**', '!app/scripts/chromereload.js']) + return gulp.src(['app/**/*.js', 'ui/**/*.js', 'mascara/src/*.js', 'mascara/server/*.js', '!node_modules/**', '!dist/firefox/**', '!docs/**', '!app/scripts/chromereload.js', '!mascara/test/jquery-3.1.0.min.js']) .pipe(eslint(fs.readFileSync(path.join(__dirname, '.eslintrc')))) // eslint.format() outputs the lint results to the console. // Alternatively use eslint.formatEach() (see Docs). From 7a9e2aa4f0fe8a12d1fac838f62ca4fbf6763af0 Mon Sep 17 00:00:00 2001 From: frankiebee Date: Wed, 4 Oct 2017 23:03:47 -0700 Subject: [PATCH 08/47] mascara: linting and code clean up --- mascara/server/index.js | 8 ++--- mascara/server/util.js | 10 +++--- mascara/src/background.js | 67 +++++++++++++++------------------------ mascara/src/proxy.js | 4 +-- mascara/src/ui.js | 10 +++--- 5 files changed, 41 insertions(+), 58 deletions(-) diff --git a/mascara/server/index.js b/mascara/server/index.js index 14e3fa18e..12b527e5d 100644 --- a/mascara/server/index.js +++ b/mascara/server/index.js @@ -5,7 +5,7 @@ const serveBundle = require('./util').serveBundle module.exports = createMetamascaraServer -function createMetamascaraServer(){ +function createMetamascaraServer () { // start bundlers const metamascaraBundle = createBundle(__dirname + '/../src/mascara.js') @@ -17,13 +17,13 @@ function createMetamascaraServer(){ const server = express() // ui window serveBundle(server, '/ui.js', uiBundle) - server.use(express.static(__dirname+'/../ui/')) - server.use(express.static(__dirname+'/../../dist/chrome')) + server.use(express.static(__dirname + '/../ui/')) + server.use(express.static(__dirname + '/../../dist/chrome')) // metamascara serveBundle(server, '/metamascara.js', metamascaraBundle) // proxy serveBundle(server, '/proxy/proxy.js', proxyBundle) - server.use('/proxy/', express.static(__dirname+'/../proxy')) + server.use('/proxy/', express.static(__dirname + '/../proxy')) // background serveBundle(server, '/background.js', backgroundBuild) diff --git a/mascara/server/util.js b/mascara/server/util.js index 6e25b35d8..6ab41b729 100644 --- a/mascara/server/util.js +++ b/mascara/server/util.js @@ -7,14 +7,14 @@ module.exports = { } -function serveBundle(server, path, bundle){ - server.get(path, function(req, res){ +function serveBundle (server, path, bundle) { + server.get(path, function (req, res) { res.setHeader('Content-Type', 'application/javascript; charset=UTF-8') res.send(bundle.latest) }) } -function createBundle(entryPoint){ +function createBundle (entryPoint) { var bundleContainer = {} @@ -30,8 +30,8 @@ function createBundle(entryPoint){ return bundleContainer - function bundle() { - bundler.bundle(function(err, result){ + function bundle () { + bundler.bundle(function (err, result) { if (err) { console.log(`Bundle failed! (${entryPoint})`) console.error(err) diff --git a/mascara/src/background.js b/mascara/src/background.js index 5ba865ad8..79bf9d378 100644 --- a/mascara/src/background.js +++ b/mascara/src/background.js @@ -1,44 +1,37 @@ global.window = global -const self = global -const pipe = require('pump') const SwGlobalListener = require('sw-stream/lib/sw-global-listener.js') -const connectionListener = new SwGlobalListener(self) +const connectionListener = new SwGlobalListener(global) const setupMultiplex = require('../../app/scripts/lib/stream-utils.js').setupMultiplex -const PortStream = require('../../app/scripts/lib/port-stream.js') const DbController = require('idb-global') const SwPlatform = require('../../app/scripts/platforms/sw') const MetamaskController = require('../../app/scripts/metamask-controller') -const extension = {} //require('../../app/scripts/lib/extension') -const storeTransform = require('obs-store/lib/transform') const Migrator = require('../../app/scripts/lib/migrator/') const migrations = require('../../app/scripts/migrations/') const firstTimeState = require('../../app/scripts/first-time-state') const STORAGE_KEY = 'metamask-config' const METAMASK_DEBUG = process.env.METAMASK_DEBUG -let popupIsOpen = false -let connectedClientCount = 0 +global.metamaskPopupIsOpen = false const log = require('loglevel') global.log = log log.setDefaultLevel(METAMASK_DEBUG ? 'debug' : 'warn') -self.addEventListener('install', function(event) { - event.waitUntil(self.skipWaiting()) +global.addEventListener('install', function (event) { + event.waitUntil(global.skipWaiting()) }) -self.addEventListener('activate', function(event) { - event.waitUntil(self.clients.claim()) +global.addEventListener('activate', function (event) { + event.waitUntil(global.clients.claim()) }) console.log('inside:open') // // state persistence -let diskStore const dbController = new DbController({ key: STORAGE_KEY, }) @@ -47,23 +40,18 @@ loadStateFromPersistence() .then(() => console.log('MetaMask initialization complete.')) .catch((err) => console.error('WHILE SETTING UP:', err)) -// initialization flow - // // State and Persistence // -function loadStateFromPersistence() { +async function loadStateFromPersistence () { // migrations - let migrator = new Migrator({ migrations }) + const migrator = new Migrator({ migrations }) const initialState = migrator.generateInitialState(firstTimeState) dbController.initialState = initialState - return dbController.open() - .then((versionedData) => migrator.migrateData(versionedData)) - .then((versionedData) => { - dbController.put(versionedData) - return Promise.resolve(versionedData) - }) - .then((versionedData) => Promise.resolve(versionedData.data)) + const versionedData = await dbController.open() + const migratedData = await migrator.migrateData(versionedData) + await dbController.put(migratedData) + return migratedData.data } function setupController (initState, client) { @@ -89,15 +77,16 @@ function setupController (initState, client) { controller.store.subscribe((state) => { versionifyData(state) .then((versionedData) => dbController.put(versionedData)) - .catch((err) => {console.error(err)}) + .catch((err) => { console.error(err) }) }) - function versionifyData(state) { + function versionifyData (state) { return dbController.get() .then((rawData) => { return Promise.resolve({ data: state, meta: rawData.meta, - })} + }) +} ) } @@ -107,7 +96,6 @@ function setupController (initState, client) { connectionListener.on('remote', (portStream, messageEvent) => { console.log('REMOTE CONECTION FOUND***********') - connectedClientCount += 1 connectRemote(portStream, messageEvent.data.context) }) @@ -116,7 +104,7 @@ function setupController (initState, client) { if (isMetaMaskInternalProcess) { // communication with popup controller.setupTrustedCommunication(connectionStream, 'MetaMask') - popupIsOpen = true + global.metamaskPopupIsOpen = true } else { // communication with page setupUntrustedCommunication(connectionStream, context) @@ -131,24 +119,19 @@ function setupController (initState, client) { controller.setupPublicConfig(mx.createStream('publicConfig')) } - function setupTrustedCommunication (connectionStream, originDomain) { - // setup multiplexing - var mx = setupMultiplex(connectionStream) - // connect features - controller.setupProviderConnection(mx.createStream('provider'), originDomain) - } // // User Interface setup // return Promise.resolve() } +// // this will be useful later but commented out for linting for now (liiiinting) +// function sendMessageToAllClients (message) { +// global.clients.matchAll().then(function (clients) { +// clients.forEach(function (client) { +// client.postMessage(message) +// }) +// }) +// } -function sendMessageToAllClients (message) { - self.clients.matchAll().then(function(clients) { - clients.forEach(function(client) { - client.postMessage(message) - }) - }) -} function noop () {} diff --git a/mascara/src/proxy.js b/mascara/src/proxy.js index 07c5b0e3c..54c5d5cf4 100644 --- a/mascara/src/proxy.js +++ b/mascara/src/proxy.js @@ -2,7 +2,7 @@ const createParentStream = require('iframe-stream').ParentStream const SWcontroller = require('client-sw-ready-event/lib/sw-client.js') const SwStream = require('sw-stream/lib/sw-stream.js') -let intervalDelay = Math.floor(Math.random() * (30000 - 1000)) + 1000 +const intervalDelay = Math.floor(Math.random() * (30000 - 1000)) + 1000 const background = new SWcontroller({ fileName: '/background.js', letBeIdle: false, @@ -12,7 +12,7 @@ const background = new SWcontroller({ const pageStream = createParentStream() background.on('ready', () => { - let swStream = SwStream({ + const swStream = SwStream({ serviceWorker: background.controller, context: 'dapp', }) diff --git a/mascara/src/ui.js b/mascara/src/ui.js index 2f940ad1a..b272a2e06 100644 --- a/mascara/src/ui.js +++ b/mascara/src/ui.js @@ -17,17 +17,17 @@ var name = 'popup' window.METAMASK_UI_TYPE = name window.METAMASK_PLATFORM_TYPE = 'mascara' -let intervalDelay = Math.floor(Math.random() * (30000 - 1000)) + 1000 +const intervalDelay = Math.floor(Math.random() * (30000 - 1000)) + 1000 const background = new SWcontroller({ fileName: '/background.js', letBeIdle: false, intervalDelay, - wakeUpInterval: 20000 + wakeUpInterval: 20000, }) // Setup listener for when the service worker is read const connectApp = function (readSw) { - let connectionStream = SwStream({ + const connectionStream = SwStream({ serviceWorker: background.controller, context: name, }) @@ -57,7 +57,7 @@ background.on('updatefound', windowReload) background.startWorker() -function windowReload() { +function windowReload () { if (window.METAMASK_SKIP_RELOAD) return window.location.reload() } @@ -66,4 +66,4 @@ function timeout (time) { return new Promise((resolve) => { setTimeout(resolve, time || 1500) }) -} \ No newline at end of file +} From 5eca4223b2986e101f64d71916aa7ce5a66064fc Mon Sep 17 00:00:00 2001 From: frankiebee Date: Wed, 4 Oct 2017 23:07:40 -0700 Subject: [PATCH 09/47] use log.debug --- mascara/src/background.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/mascara/src/background.js b/mascara/src/background.js index 79bf9d378..cf043a4f0 100644 --- a/mascara/src/background.js +++ b/mascara/src/background.js @@ -28,7 +28,7 @@ global.addEventListener('activate', function (event) { event.waitUntil(global.clients.claim()) }) -console.log('inside:open') +log.debug('inside:open') // // state persistence @@ -37,7 +37,7 @@ const dbController = new DbController({ }) loadStateFromPersistence() .then((initState) => setupController(initState)) -.then(() => console.log('MetaMask initialization complete.')) +.then(() => log.debug('MetaMask initialization complete.')) .catch((err) => console.error('WHILE SETTING UP:', err)) // @@ -95,7 +95,7 @@ function setupController (initState, client) { // connectionListener.on('remote', (portStream, messageEvent) => { - console.log('REMOTE CONECTION FOUND***********') + log.debug('REMOTE CONECTION FOUND***********') connectRemote(portStream, messageEvent.data.context) }) From 7ec068d279276a7e0fcc862d18e3fd3e1648728b Mon Sep 17 00:00:00 2001 From: frankiebee Date: Wed, 4 Oct 2017 23:21:30 -0700 Subject: [PATCH 10/47] mascara/background: use async await --- mascara/src/background.js | 33 +++++++++++++-------------------- 1 file changed, 13 insertions(+), 20 deletions(-) diff --git a/mascara/src/background.js b/mascara/src/background.js index cf043a4f0..8aa1d8fe2 100644 --- a/mascara/src/background.js +++ b/mascara/src/background.js @@ -54,7 +54,7 @@ async function loadStateFromPersistence () { return migratedData.data } -function setupController (initState, client) { +async function setupController (initState, client) { // // MetaMask Controller @@ -74,20 +74,19 @@ function setupController (initState, client) { }) global.metamaskController = controller - controller.store.subscribe((state) => { - versionifyData(state) - .then((versionedData) => dbController.put(versionedData)) - .catch((err) => { console.error(err) }) + controller.store.subscribe(async (state) => { + try { + const versionedData = await versionifyData(state) + await dbController.put(versionedData) + } catch (e) { console.error('METAMASK Error:', e) } }) - function versionifyData (state) { - return dbController.get() - .then((rawData) => { - return Promise.resolve({ - data: state, - meta: rawData.meta, - }) -} - ) + + async function versionifyData (state) { + const rawData = await dbController.get() + return { + data: state, + meta: rawData.meta, + } } // @@ -118,12 +117,6 @@ function setupController (initState, client) { controller.setupProviderConnection(mx.createStream('provider'), originDomain) controller.setupPublicConfig(mx.createStream('publicConfig')) } - - // - // User Interface setup - // - return Promise.resolve() - } // // this will be useful later but commented out for linting for now (liiiinting) // function sendMessageToAllClients (message) { From ec9c5283135d2cc3dcb633f19dd89ea79267f34a Mon Sep 17 00:00:00 2001 From: frankiebee Date: Thu, 5 Oct 2017 10:54:28 -0700 Subject: [PATCH 11/47] pending-tx - check time stamp instead of block number for resubmit --- app/scripts/controllers/transactions.js | 2 +- app/scripts/lib/pending-tx-tracker.js | 7 ++++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/app/scripts/controllers/transactions.js b/app/scripts/controllers/transactions.js index 94e04c429..a0f983deb 100644 --- a/app/scripts/controllers/transactions.js +++ b/app/scripts/controllers/transactions.js @@ -59,7 +59,7 @@ module.exports = class TransactionController extends EventEmitter { this.pendingTxTracker = new PendingTransactionTracker({ provider: this.provider, nonceTracker: this.nonceTracker, - retryLimit: 3500, // Retry 3500 blocks, or about 1 day. + retryTimePeriod: 86400000, // Retry 3500 blocks, or about 1 day. publishTransaction: (rawTx) => this.query.sendRawTransaction(rawTx), getPendingTransactions: this.txStateManager.getPendingTransactions.bind(this.txStateManager), }) diff --git a/app/scripts/lib/pending-tx-tracker.js b/app/scripts/lib/pending-tx-tracker.js index 6f1601586..3463d45bf 100644 --- a/app/scripts/lib/pending-tx-tracker.js +++ b/app/scripts/lib/pending-tx-tracker.js @@ -22,7 +22,8 @@ module.exports = class PendingTransactionTracker extends EventEmitter { super() this.query = new EthQuery(config.provider) this.nonceTracker = config.nonceTracker - this.retryLimit = config.retryLimit || Infinity + // default is one day + this.retryTimePeriod = config.retryTimePeriod || 86400000 this.getPendingTransactions = config.getPendingTransactions this.publishTransaction = config.publishTransaction } @@ -99,8 +100,8 @@ module.exports = class PendingTransactionTracker extends EventEmitter { } async _resubmitTx (txMeta) { - if (txMeta.retryCount > this.retryLimit) { - const err = new Error(`Gave up submitting after ${this.retryLimit} blocks un-mined.`) + if (Date.now() > txMeta.time + this.retryTimePeriod) { + const err = new Error(`Gave up submitting after ${this.retryTimePeriod / 3.6e+6} hours.`) return this.emit('tx:failed', txMeta.id, err) } From 3cb9da2ae56ce0b3162e64dbcf69f5f9e39ff4e8 Mon Sep 17 00:00:00 2001 From: frankiebee Date: Thu, 5 Oct 2017 11:42:01 -0700 Subject: [PATCH 12/47] "fix" hours for message --- app/scripts/lib/pending-tx-tracker.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/scripts/lib/pending-tx-tracker.js b/app/scripts/lib/pending-tx-tracker.js index 3463d45bf..8a626e222 100644 --- a/app/scripts/lib/pending-tx-tracker.js +++ b/app/scripts/lib/pending-tx-tracker.js @@ -101,7 +101,8 @@ module.exports = class PendingTransactionTracker extends EventEmitter { async _resubmitTx (txMeta) { if (Date.now() > txMeta.time + this.retryTimePeriod) { - const err = new Error(`Gave up submitting after ${this.retryTimePeriod / 3.6e+6} hours.`) + const hours = (this.retryTimePeriod / 3.6e+6).toFixed(1) + const err = new Error(`Gave up submitting after ${hours} hours.`) return this.emit('tx:failed', txMeta.id, err) } From 554a3ac4e4f4493f318753410bb4332204f2efb7 Mon Sep 17 00:00:00 2001 From: frankiebee Date: Thu, 5 Oct 2017 12:12:31 -0700 Subject: [PATCH 13/47] add to CHANGELOG.md --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 069602915..7fc74a9d7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ ## Current Master +- Only rebrodcast transactions for a day not a days worth of blocks - Remove Slack link from info page, since it is a big phishing target. ## 3.10.8 2017-9-28 From 8d45b96db6cfe4b13cf91601e3698fda7621a76b Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Thu, 5 Oct 2017 12:44:48 -0700 Subject: [PATCH 14/47] Version 3.10.9 --- CHANGELOG.md | 3 +++ app/manifest.json | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7fc74a9d7..8e9fb2530 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,8 +2,11 @@ ## Current Master +## 3.10.9 2017-10-5 + - Only rebrodcast transactions for a day not a days worth of blocks - Remove Slack link from info page, since it is a big phishing target. +- Stop computing balance based on pending transactions, to avoid edge case where users are unable to send transactions. ## 3.10.8 2017-9-28 diff --git a/app/manifest.json b/app/manifest.json index 0fc43c7d4..c253a5c2b 100644 --- a/app/manifest.json +++ b/app/manifest.json @@ -1,7 +1,7 @@ { "name": "MetaMask", "short_name": "Metamask", - "version": "3.10.8", + "version": "3.10.9", "manifest_version": 2, "author": "https://metamask.io", "description": "Ethereum Browser Extension", From 176d03b2e8061c50108b2024f7716885097e82fd Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Thu, 5 Oct 2017 14:39:23 -0700 Subject: [PATCH 15/47] Require keyring-controller 2.1.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 298691588..1e379493d 100644 --- a/package.json +++ b/package.json @@ -71,7 +71,7 @@ "eth-contract-metadata": "^1.1.4", "eth-hd-keyring": "^1.2.1", "eth-json-rpc-filters": "^1.2.2", - "eth-keyring-controller": "^2.0.0", + "eth-keyring-controller": "^2.1.0", "eth-phishing-detect": "^1.1.4", "eth-query": "^2.1.2", "eth-sig-util": "^1.2.2", From 9bc80d998eda937e3a8f95fa5e04fcba66e8a6f8 Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Thu, 5 Oct 2017 14:39:35 -0700 Subject: [PATCH 16/47] Add signTypedData input validations --- app/scripts/lib/typed-message-manager.js | 11 +++++++++++ app/scripts/metamask-controller.js | 13 +++++++++---- 2 files changed, 20 insertions(+), 4 deletions(-) diff --git a/app/scripts/lib/typed-message-manager.js b/app/scripts/lib/typed-message-manager.js index e3efdb45d..e041ae9f3 100644 --- a/app/scripts/lib/typed-message-manager.js +++ b/app/scripts/lib/typed-message-manager.js @@ -1,6 +1,7 @@ const EventEmitter = require('events') const ObservableStore = require('obs-store') const createId = require('./random-id') +const assert = require('assert') module.exports = class TypedMessageManager extends EventEmitter { @@ -23,6 +24,8 @@ module.exports = class TypedMessageManager extends EventEmitter { } addUnapprovedMessage (msgParams) { + this.validateParams(msgParams) + log.debug(`TypedMessageManager addUnapprovedMessage: ${JSON.stringify(msgParams)}`) // create txData obj with parameters and meta data var time = (new Date()).getTime() @@ -41,6 +44,14 @@ module.exports = class TypedMessageManager extends EventEmitter { return msgId } + validateParams (params) { + assert.equal(typeof params, 'object', 'Params should ben an object.') + assert.ok('data' in params, 'Params must include a data field.') + assert.ok('from' in params, 'Params must include a from field.') + assert.ok(Array.isArray(params.data), 'Data should be an array.') + assert.equal(typeof params.from, 'string', 'From field must be a string.') + } + addMsg (msg) { this.messages.push(msg) this._saveMsgList() diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index 8f773a72b..727f48f1c 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -566,11 +566,16 @@ module.exports = class MetamaskController extends EventEmitter { } newUnsignedTypedMessage (msgParams, cb) { - const msgId = this.typedMessageManager.addUnapprovedMessage(msgParams) - this.sendUpdate() - this.opts.showUnconfirmedMessage() + let msgId + try { + msgId = this.typedMessageManager.addUnapprovedMessage(msgParams) + this.sendUpdate() + this.opts.showUnconfirmedMessage() + } catch (e) { + return cb(e) + } + this.typedMessageManager.once(`${msgId}:finished`, (data) => { - console.log(data) switch (data.status) { case 'signed': return cb(null, data.rawSig) From c821a6b93a5a8e0f564b69d493c350e3763e749b Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Thu, 5 Oct 2017 14:48:40 -0700 Subject: [PATCH 17/47] Bump provider-engine for better sender validations --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 1e379493d..c846bae41 100644 --- a/package.json +++ b/package.json @@ -141,7 +141,7 @@ "valid-url": "^1.0.9", "vreme": "^3.0.2", "web3": "^0.20.1", - "web3-provider-engine": "^13.3.1", + "web3-provider-engine": "^13.3.2", "web3-stream-provider": "^3.0.1", "xtend": "^4.0.1" }, From 52bfed5d13846326fbd8940dbb7c91a4f399b190 Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Thu, 5 Oct 2017 14:53:22 -0700 Subject: [PATCH 18/47] Bump changelog --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8e9fb2530..505c04169 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ## Current Master +- Add new support for new eth_signTypedData method per EIP 712. + ## 3.10.9 2017-10-5 - Only rebrodcast transactions for a day not a days worth of blocks From e6a618b82d5ab75920763875cd0487e4431321a2 Mon Sep 17 00:00:00 2001 From: Kevin Serrano Date: Thu, 5 Oct 2017 15:26:03 -0700 Subject: [PATCH 19/47] Fix precision to account for small wei increase. --- ui/app/components/bn-as-decimal-input.js | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/ui/app/components/bn-as-decimal-input.js b/ui/app/components/bn-as-decimal-input.js index f3ace4720..d84834d06 100644 --- a/ui/app/components/bn-as-decimal-input.js +++ b/ui/app/components/bn-as-decimal-input.js @@ -31,7 +31,7 @@ BnAsDecimalInput.prototype.render = function () { const suffix = props.suffix const style = props.style const valueString = value.toString(10) - const newValue = this.downsize(valueString, scale, precision) + const newValue = this.downsize(valueString, scale) return ( h('.flex-column', [ @@ -145,14 +145,17 @@ BnAsDecimalInput.prototype.constructWarning = function () { } -BnAsDecimalInput.prototype.downsize = function (number, scale, precision) { +BnAsDecimalInput.prototype.downsize = function (number, scale) { // if there is no scaling, simply return the number if (scale === 0) { return Number(number) } else { // if the scale is the same as the precision, account for this edge case. - var decimals = (scale === precision) ? -1 : scale - precision - return Number(number.slice(0, -scale) + '.' + number.slice(-scale, decimals)) + var adjustedNumber = number + while (adjustedNumber.length < scale) { + adjustedNumber = '0' + adjustedNumber + } + return Number(adjustedNumber.slice(0, -scale) + '.' + adjustedNumber.slice(-scale)) } } From 0146b55d6d7bd8717b3f3ad071c64744e21a93fd Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Fri, 6 Oct 2017 11:33:14 -0700 Subject: [PATCH 20/47] Check status of pending transactions on startup Fixes #1531 --- CHANGELOG.md | 2 ++ app/scripts/lib/pending-tx-tracker.js | 1 + 2 files changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8e9fb2530..c037508e1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ## Current Master +- Fix bug where some transactions would be shown as pending forever, even after successfully mined. + ## 3.10.9 2017-10-5 - Only rebrodcast transactions for a day not a days worth of blocks diff --git a/app/scripts/lib/pending-tx-tracker.js b/app/scripts/lib/pending-tx-tracker.js index 8a626e222..5049cc4b4 100644 --- a/app/scripts/lib/pending-tx-tracker.js +++ b/app/scripts/lib/pending-tx-tracker.js @@ -26,6 +26,7 @@ module.exports = class PendingTransactionTracker extends EventEmitter { this.retryTimePeriod = config.retryTimePeriod || 86400000 this.getPendingTransactions = config.getPendingTransactions this.publishTransaction = config.publishTransaction + this._checkPendingTxs() } // checks if a signed tx is in a block and From a32d71e8ed4c91c8ad73f4a7afc52e506ccf5247 Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Fri, 6 Oct 2017 12:29:27 -0700 Subject: [PATCH 21/47] Add failing test for issue #2294 --- test/unit/pending-tx-test.js | 53 +++++++++++++++++++++++++++++++++++- 1 file changed, 52 insertions(+), 1 deletion(-) diff --git a/test/unit/pending-tx-test.js b/test/unit/pending-tx-test.js index 6b62bb5b1..554bd5591 100644 --- a/test/unit/pending-tx-test.js +++ b/test/unit/pending-tx-test.js @@ -5,6 +5,8 @@ const ObservableStore = require('obs-store') const clone = require('clone') const { createStubedProvider } = require('../stub/provider') const PendingTransactionTracker = require('../../app/scripts/lib/pending-tx-tracker') +const MockTxGen = require('../lib/mock-tx-gen') +const sinon = require('sinon') const noop = () => true const currentNetworkId = 42 const otherNetworkId = 36 @@ -50,6 +52,55 @@ describe('PendingTransactionTracker', function () { }) }) + describe('_checkPendingTx state management', function () { + let stub + + afterEach(function () { + if (stub) { + stub.restore() + } + }) + + it('should become failed if another tx with the same nonce succeeds', async function () { + + // SETUP + const txGen = new MockTxGen() + + txGen.generate({ + id: '456', + value: '0x01', + hash: '0xbad', + status: 'confirmed', + nonce: '0x01', + }, { count: 1 }) + + const pending = txGen.generate({ + id: '123', + value: '0x02', + hash: '0xfad', + status: 'submitted', + nonce: '0x01', + }, { count: 1 })[0] + + stub = sinon.stub(pendingTxTracker, 'getPendingTransactions') + .returns(txGen.txs) + + // THE EXPECTATION + const spy = sinon.spy() + pendingTxTracker.on('tx:failed', (txId, err) => { + assert.equal(txId, pending.id, 'should fail the pending tx') + assert.equal(err.name, 'NonceTakenErr', 'should emit a nonce taken error.') + spy(txId, err) + }) + + // THE METHOD + await pendingTxTracker._checkPendingTx(pending) + + // THE ASSERTION + return sinon.assert.calledWith(spy, pending.id, 'tx failed should be emitted') + }) + }) + describe('#checkForTxInBlock', function () { it('should return if no pending transactions', function () { // throw a type error if it trys to do anything on the block @@ -239,4 +290,4 @@ describe('PendingTransactionTracker', function () { }) }) }) -}) \ No newline at end of file +}) From be4f7b33f4f0885f2c0f5f4d537f6e9793f3fa30 Mon Sep 17 00:00:00 2001 From: kumavis Date: Fri, 6 Oct 2017 12:36:08 -0700 Subject: [PATCH 22/47] nodeify - allow callback to be optional --- app/scripts/lib/nodeify.js | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/app/scripts/lib/nodeify.js b/app/scripts/lib/nodeify.js index 832d6c6d3..19c3c8337 100644 --- a/app/scripts/lib/nodeify.js +++ b/app/scripts/lib/nodeify.js @@ -1,10 +1,18 @@ const promiseToCallback = require('promise-to-callback') +const noop = function(){} module.exports = function nodeify (fn, context) { return function(){ const args = [].slice.call(arguments) - const callback = args.pop() - if (typeof callback !== 'function') throw new Error('callback is not a function') + const lastArg = args[args.length-1] + const lastArgIsCallback = typeof lastArg === 'function' + let callback + if (lastArgIsCallback) { + callback = lastArg + args.pop() + } else { + callback = noop + } promiseToCallback(fn.apply(context, args))(callback) } } From 94513cae7bf3c8310ae6a248e12a9b7dd73e306f Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Fri, 6 Oct 2017 12:50:33 -0700 Subject: [PATCH 23/47] Provide method for tx tracker to refer to all txs --- app/scripts/controllers/transactions.js | 1 + app/scripts/lib/tx-state-manager.js | 8 +++++++- test/unit/pending-tx-test.js | 5 +++-- 3 files changed, 11 insertions(+), 3 deletions(-) diff --git a/app/scripts/controllers/transactions.js b/app/scripts/controllers/transactions.js index a0f983deb..ef659a300 100644 --- a/app/scripts/controllers/transactions.js +++ b/app/scripts/controllers/transactions.js @@ -62,6 +62,7 @@ module.exports = class TransactionController extends EventEmitter { retryTimePeriod: 86400000, // Retry 3500 blocks, or about 1 day. publishTransaction: (rawTx) => this.query.sendRawTransaction(rawTx), getPendingTransactions: this.txStateManager.getPendingTransactions.bind(this.txStateManager), + getCompletedTransactions: this.txStateManager.getConfirmedTransactions.bind(this.txStateManager), }) this.txStateManager.store.subscribe(() => this.emit('update:badge')) diff --git a/app/scripts/lib/tx-state-manager.js b/app/scripts/lib/tx-state-manager.js index cf8117864..2250403f6 100644 --- a/app/scripts/lib/tx-state-manager.js +++ b/app/scripts/lib/tx-state-manager.js @@ -46,6 +46,12 @@ module.exports = class TransactionStateManger extends EventEmitter { return this.getFilteredTxList(opts) } + getConfirmedTransactions (address) { + const opts = { status: 'confirmed' } + if (address) opts.from = address + return this.getFilteredTxList(opts) + } + addTx (txMeta) { this.once(`${txMeta.id}:signed`, function (txId) { this.removeAllListeners(`${txMeta.id}:rejected`) @@ -242,4 +248,4 @@ module.exports = class TransactionStateManger extends EventEmitter { _saveTxList (transactions) { this.store.updateState({ transactions }) } -} \ No newline at end of file +} diff --git a/test/unit/pending-tx-test.js b/test/unit/pending-tx-test.js index 554bd5591..32421a44f 100644 --- a/test/unit/pending-tx-test.js +++ b/test/unit/pending-tx-test.js @@ -48,6 +48,7 @@ describe('PendingTransactionTracker', function () { } }, getPendingTransactions: () => {return []}, + getCompletedTransactions: () => {return []}, publishTransaction: () => {}, }) }) @@ -82,7 +83,7 @@ describe('PendingTransactionTracker', function () { nonce: '0x01', }, { count: 1 })[0] - stub = sinon.stub(pendingTxTracker, 'getPendingTransactions') + stub = sinon.stub(pendingTxTracker, 'getCompletedTransactions') .returns(txGen.txs) // THE EXPECTATION @@ -97,7 +98,7 @@ describe('PendingTransactionTracker', function () { await pendingTxTracker._checkPendingTx(pending) // THE ASSERTION - return sinon.assert.calledWith(spy, pending.id, 'tx failed should be emitted') + assert.ok(spy.calledWith(pending.id), 'tx failed should be emitted') }) }) From a417fab0ebd71d22f51a8e30590c259b32164fd2 Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Fri, 6 Oct 2017 12:51:13 -0700 Subject: [PATCH 24/47] When checking pending txs, check for successful txs with same nonce. If a successful tx with the same nonce exists, transition tx to the failed state. Fixes #2294 --- app/scripts/lib/pending-tx-tracker.js | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/app/scripts/lib/pending-tx-tracker.js b/app/scripts/lib/pending-tx-tracker.js index 8a626e222..2d8f22ae8 100644 --- a/app/scripts/lib/pending-tx-tracker.js +++ b/app/scripts/lib/pending-tx-tracker.js @@ -25,6 +25,7 @@ module.exports = class PendingTransactionTracker extends EventEmitter { // default is one day this.retryTimePeriod = config.retryTimePeriod || 86400000 this.getPendingTransactions = config.getPendingTransactions + this.getCompletedTransactions = config.getCompletedTransactions this.publishTransaction = config.publishTransaction } @@ -120,6 +121,7 @@ module.exports = class PendingTransactionTracker extends EventEmitter { async _checkPendingTx (txMeta) { const txHash = txMeta.hash const txId = txMeta.id + // extra check in case there was an uncaught error during the // signature and submission process if (!txHash) { @@ -128,6 +130,15 @@ module.exports = class PendingTransactionTracker extends EventEmitter { this.emit('tx:failed', txId, noTxHashErr) return } + + // If another tx with the same nonce is mined, set as failed. + const taken = await this._checkIfNonceIsTaken(txMeta) + if (taken) { + const nonceTakenErr = new Error('Another transaction with this nonce has been mined.') + nonceTakenErr.name = 'NonceTakenErr' + return this.emit('tx:failed', txId, nonceTakenErr) + } + // get latest transaction status let txParams try { @@ -159,4 +170,13 @@ module.exports = class PendingTransactionTracker extends EventEmitter { } nonceGlobalLock.releaseLock() } + + async _checkIfNonceIsTaken (txMeta) { + const completed = this.getCompletedTransactions() + const sameNonce = completed.filter((otherMeta) => { + return otherMeta.txParams.nonce === txMeta.txParams.nonce + }) + return sameNonce.length > 0 + } + } From bc396a7417ecfe9855ec84af0cb08fd033c42bf5 Mon Sep 17 00:00:00 2001 From: kumavis Date: Fri, 6 Oct 2017 13:02:34 -0700 Subject: [PATCH 25/47] lint fix - nodeify --- app/scripts/lib/nodeify.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/scripts/lib/nodeify.js b/app/scripts/lib/nodeify.js index 19c3c8337..d24e92206 100644 --- a/app/scripts/lib/nodeify.js +++ b/app/scripts/lib/nodeify.js @@ -4,7 +4,7 @@ const noop = function(){} module.exports = function nodeify (fn, context) { return function(){ const args = [].slice.call(arguments) - const lastArg = args[args.length-1] + const lastArg = args[args.length - 1] const lastArgIsCallback = typeof lastArg === 'function' let callback if (lastArgIsCallback) { From 3b3120c5f83d0971747abc28e9a3ddfc3da34be3 Mon Sep 17 00:00:00 2001 From: kumavis Date: Fri, 6 Oct 2017 13:16:44 -0700 Subject: [PATCH 26/47] nodeify - fix test --- test/unit/nodeify-test.js | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/test/unit/nodeify-test.js b/test/unit/nodeify-test.js index 537dae605..c7b127889 100644 --- a/test/unit/nodeify-test.js +++ b/test/unit/nodeify-test.js @@ -18,14 +18,13 @@ describe('nodeify', function () { }) }) - it('should throw if the last argument is not a function', function (done) { + it('should allow the last argument to not be a function', function (done) { const nodified = nodeify(obj.promiseFunc, obj) try { nodified('baz') - done(new Error('should have thrown if the last argument is not a function')) - } catch (err) { - assert.equal(err.message, 'callback is not a function') done() + } catch (err) { + done(new Error('should not have thrown if the last argument is not a function')) } }) }) From a1696f89a8764f17c10298a45160abf8fc7dce5e Mon Sep 17 00:00:00 2001 From: Sergey Ukustov Date: Sat, 7 Oct 2017 00:38:13 +0300 Subject: [PATCH 27/47] Validate data format for eth_signTypedData --- app/scripts/lib/typed-message-manager.js | 4 ++++ package.json | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/app/scripts/lib/typed-message-manager.js b/app/scripts/lib/typed-message-manager.js index e041ae9f3..8b760790e 100644 --- a/app/scripts/lib/typed-message-manager.js +++ b/app/scripts/lib/typed-message-manager.js @@ -2,6 +2,7 @@ const EventEmitter = require('events') const ObservableStore = require('obs-store') const createId = require('./random-id') const assert = require('assert') +const sigUtil = require('eth-sig-util') module.exports = class TypedMessageManager extends EventEmitter { @@ -50,6 +51,9 @@ module.exports = class TypedMessageManager extends EventEmitter { assert.ok('from' in params, 'Params must include a from field.') assert.ok(Array.isArray(params.data), 'Data should be an array.') assert.equal(typeof params.from, 'string', 'From field must be a string.') + assert.doesNotThrow(() => { + sigUtil.typedSignatureHash(params.data) + }, 'Expected EIP712 typed data') } addMsg (msg) { diff --git a/package.json b/package.json index c846bae41..225742487 100644 --- a/package.json +++ b/package.json @@ -74,7 +74,7 @@ "eth-keyring-controller": "^2.1.0", "eth-phishing-detect": "^1.1.4", "eth-query": "^2.1.2", - "eth-sig-util": "^1.2.2", + "eth-sig-util": "^1.4.0", "eth-simple-keyring": "^1.1.1", "eth-token-tracker": "^1.1.4", "ethereumjs-tx": "^1.3.0", From 53bb4bebb11b355f2655b2be0116005df573e907 Mon Sep 17 00:00:00 2001 From: Sergey Ukustov Date: Sat, 7 Oct 2017 23:25:33 +0300 Subject: [PATCH 28/47] More appropriate styling --- ui/app/components/typed-message-renderer.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ui/app/components/typed-message-renderer.js b/ui/app/components/typed-message-renderer.js index b7d1572c2..a042b57be 100644 --- a/ui/app/components/typed-message-renderer.js +++ b/ui/app/components/typed-message-renderer.js @@ -22,6 +22,7 @@ TypedMessageRenderer.prototype.render = function () { border: 'none', background: 'white', padding: '3px', + overflow: 'scroll', }, style) return ( @@ -34,7 +35,7 @@ TypedMessageRenderer.prototype.render = function () { function renderTypedData(values) { return values.map(function (value) { return h('div', {}, [ - h('strong', {style: {display: 'block', fontWeight: 'bold', textTransform: 'capitalize'}}, String(value.name) + ':'), + h('strong', {style: {display: 'block', fontWeight: 'bold'}}, String(value.name) + ':'), h('div', {}, value.value), ]) }) From 6f0c0e83744514c7fe70838097d96b5e3c2778ae Mon Sep 17 00:00:00 2001 From: Kevin Serrano Date: Mon, 9 Oct 2017 12:12:54 -0700 Subject: [PATCH 29/47] Add test to look for wei precision. --- .../components/bn-as-decimal-input-test.js | 36 +++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/test/unit/components/bn-as-decimal-input-test.js b/test/unit/components/bn-as-decimal-input-test.js index 106b3a871..d74e0fa2e 100644 --- a/test/unit/components/bn-as-decimal-input-test.js +++ b/test/unit/components/bn-as-decimal-input-test.js @@ -48,4 +48,40 @@ describe('BnInput', function () { checkValidity () { return true } }, }) }) + + it('can tolerate wei precision', function (done) { + const renderer = ReactTestUtils.createRenderer() + + let valueStr = '1000000000000000000' + + const value = new BN(valueStr, 10) + + const inputStr = '1000000000.000000001' + + let targetStr = '1000000000000000001' + + const target = new BN(targetStr, 10) + + const precision = 9 // ether precision + const scale = 9 + + const props = { + value, + scale, + precision, + onChange: (newBn) => { + assert.equal(newBn.toString(), target.toString(), 'should tolerate increase') + done() + }, + } + + const inputComponent = h(BnInput, props) + const component = additions.renderIntoDocument(inputComponent) + renderer.render(inputComponent) + const input = additions.find(component, 'input.hex-input')[0] + ReactTestUtils.Simulate.change(input, { preventDefault () {}, target: { + value: inputStr, + checkValidity () { return true } }, + }) + }) }) From c12d56063da5ed533ba63cf6e0843631659de0d3 Mon Sep 17 00:00:00 2001 From: Kevin Serrano Date: Mon, 9 Oct 2017 13:01:58 -0700 Subject: [PATCH 30/47] Fix to actually fail in earlier versions. --- test/unit/components/bn-as-decimal-input-test.js | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/test/unit/components/bn-as-decimal-input-test.js b/test/unit/components/bn-as-decimal-input-test.js index d74e0fa2e..81a8caa45 100644 --- a/test/unit/components/bn-as-decimal-input-test.js +++ b/test/unit/components/bn-as-decimal-input-test.js @@ -48,21 +48,21 @@ describe('BnInput', function () { checkValidity () { return true } }, }) }) - + it('can tolerate wei precision', function (done) { const renderer = ReactTestUtils.createRenderer() - let valueStr = '1000000000000000000' + let valueStr = '1000000000' const value = new BN(valueStr, 10) + const inputStr = '1.000000001' - const inputStr = '1000000000.000000001' - let targetStr = '1000000000000000001' + let targetStr = '1000000001' const target = new BN(targetStr, 10) - const precision = 9 // ether precision + const precision = 9 // gwei precision const scale = 9 const props = { @@ -71,6 +71,8 @@ describe('BnInput', function () { precision, onChange: (newBn) => { assert.equal(newBn.toString(), target.toString(), 'should tolerate increase') + const reInput = BnInput.prototype.downsize(newBn.toString(), 9, 9) + assert.equal(reInput.toString(), target.toString(), 'should tolerate increase') done() }, } From d82d9215fbe593293a6badc523218878cfb13dd2 Mon Sep 17 00:00:00 2001 From: Kevin Serrano Date: Mon, 9 Oct 2017 13:02:52 -0700 Subject: [PATCH 31/47] Make modification --- test/unit/components/bn-as-decimal-input-test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/unit/components/bn-as-decimal-input-test.js b/test/unit/components/bn-as-decimal-input-test.js index 81a8caa45..58ecc9c89 100644 --- a/test/unit/components/bn-as-decimal-input-test.js +++ b/test/unit/components/bn-as-decimal-input-test.js @@ -72,7 +72,7 @@ describe('BnInput', function () { onChange: (newBn) => { assert.equal(newBn.toString(), target.toString(), 'should tolerate increase') const reInput = BnInput.prototype.downsize(newBn.toString(), 9, 9) - assert.equal(reInput.toString(), target.toString(), 'should tolerate increase') + assert.equal(reInput.toString(), inputStr, 'should tolerate increase') done() }, } From e79037261ec4b232299dbef14e6c30fc46c48ac7 Mon Sep 17 00:00:00 2001 From: kumavis Date: Tue, 10 Oct 2017 10:26:59 -0700 Subject: [PATCH 32/47] metamask controller - breakout getAccounts method --- app/scripts/metamask-controller.js | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index 727f48f1c..840012e81 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -225,19 +225,9 @@ module.exports = class MetamaskController extends EventEmitter { web3_clientVersion: `MetaMask/v${version}`, }, // account mgmt - getAccounts: (cb) => { - const isUnlocked = this.keyringController.memStore.getState().isUnlocked - const result = [] - const selectedAddress = this.preferencesController.getSelectedAddress() - - // only show address if account is unlocked - if (isUnlocked && selectedAddress) { - result.push(selectedAddress) - } - cb(null, result) - }, + getAccounts: nodeify(this.getAccounts, this), // tx signing - processTransaction: nodeify(async (txParams) => await this.txController.newUnapprovedTransaction(txParams), this), + processTransaction: nodeify(this.txController.newUnapprovedTransaction, this.txController), // old style msg signing processMessage: this.newUnsignedMessage.bind(this), // personal_sign msg signing @@ -483,6 +473,18 @@ module.exports = class MetamaskController extends EventEmitter { // Opinionated Keyring Management // + async getAccounts () { + const isUnlocked = this.keyringController.memStore.getState().isUnlocked + const result = [] + const selectedAddress = this.preferencesController.getSelectedAddress() + + // only show address if account is unlocked + if (isUnlocked && selectedAddress) { + result.push(selectedAddress) + } + return result + }, + addNewAccount (cb) { const primaryKeyring = this.keyringController.getKeyringsByType('HD Key Tree')[0] if (!primaryKeyring) return cb(new Error('MetamaskController - No HD Key Tree found')) From f7c1bc804d12cfae4ff99b958b793a6fb68f4aa0 Mon Sep 17 00:00:00 2001 From: kumavis Date: Tue, 10 Oct 2017 10:39:31 -0700 Subject: [PATCH 33/47] metamask controller - simplify provider init --- app/scripts/metamask-controller.js | 38 +++++++++++++----------------- 1 file changed, 16 insertions(+), 22 deletions(-) diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index 840012e81..df5784571 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -81,8 +81,22 @@ module.exports = class MetamaskController extends EventEmitter { }) this.blacklistController.scheduleUpdates() - // rpc provider - this.provider = this.initializeProvider() + // rpc provider and block tracker + this.provider = this.networkController.initializeProvider({ + static: { + eth_syncing: false, + web3_clientVersion: `MetaMask/v${version}`, + }, + // account mgmt + getAccounts: nodeify(this.getAccounts, this), + // tx signing + processTransaction: nodeify(this.txController.newUnapprovedTransaction, this.txController), + // old style msg signing + processMessage: this.newUnsignedMessage.bind(this), + // personal_sign msg signing + processPersonalMessage: this.newUnsignedPersonalMessage.bind(this), + processTypedMessage: this.newUnsignedTypedMessage.bind(this), + }) this.blockTracker = this.provider._blockTracker // eth data query tools @@ -218,26 +232,6 @@ module.exports = class MetamaskController extends EventEmitter { // Constructor helpers // - initializeProvider () { - const providerOpts = { - static: { - eth_syncing: false, - web3_clientVersion: `MetaMask/v${version}`, - }, - // account mgmt - getAccounts: nodeify(this.getAccounts, this), - // tx signing - processTransaction: nodeify(this.txController.newUnapprovedTransaction, this.txController), - // old style msg signing - processMessage: this.newUnsignedMessage.bind(this), - // personal_sign msg signing - processPersonalMessage: this.newUnsignedPersonalMessage.bind(this), - processTypedMessage: this.newUnsignedTypedMessage.bind(this), - } - const providerProxy = this.networkController.initializeProvider(providerOpts) - return providerProxy - } - initPublicConfigStore () { // get init state const publicConfigStore = new ObservableStore() From ff4e9a0d1122db83221bc956f11c9520bf0e008c Mon Sep 17 00:00:00 2001 From: kumavis Date: Tue, 10 Oct 2017 10:50:45 -0700 Subject: [PATCH 34/47] metamask controller - define this.newTransaction to ease instantiation order --- app/scripts/metamask-controller.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index df5784571..1292d2a1e 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -90,7 +90,7 @@ module.exports = class MetamaskController extends EventEmitter { // account mgmt getAccounts: nodeify(this.getAccounts, this), // tx signing - processTransaction: nodeify(this.txController.newUnapprovedTransaction, this.txController), + processTransaction: nodeify(this.newTransaction, this), // old style msg signing processMessage: this.newUnsignedMessage.bind(this), // personal_sign msg signing @@ -525,6 +525,11 @@ module.exports = class MetamaskController extends EventEmitter { // Identity Management // + // this function wrappper lets us pass the fn reference before txController is instantiated + async newTransaction (txParams) { + return await this.txController.newUnapprovedTransaction(txParams) + } + newUnsignedMessage (msgParams, cb) { const msgId = this.messageManager.addUnapprovedMessage(msgParams) this.sendUpdate() From efa92a7fc5925533f72e876c9bf84df0a6258d4a Mon Sep 17 00:00:00 2001 From: kumavis Date: Tue, 10 Oct 2017 14:13:12 -0700 Subject: [PATCH 35/47] network controller - refactor to use eth-rpc-client --- app/scripts/controllers/network.js | 52 ++++++++++++++++-------------- app/scripts/metamask-controller.js | 7 ++-- package.json | 2 ++ 3 files changed, 34 insertions(+), 27 deletions(-) diff --git a/app/scripts/controllers/network.js b/app/scripts/controllers/network.js index 0f9db4d53..412967dbe 100644 --- a/app/scripts/controllers/network.js +++ b/app/scripts/controllers/network.js @@ -5,6 +5,7 @@ const ObservableStore = require('obs-store') const ComposedStore = require('obs-store/lib/composed') const extend = require('xtend') const EthQuery = require('eth-query') +const createEthRpcClient = require('eth-rpc-client') const createEventEmitterProxy = require('../lib/events-proxy.js') const RPC_ADDRESS_LIST = require('../config.js').network const DEFAULT_RPC = RPC_ADDRESS_LIST['rinkeby'] @@ -17,7 +18,8 @@ module.exports = class NetworkController extends EventEmitter { this.networkStore = new ObservableStore('loading') this.providerStore = new ObservableStore(config.provider) this.store = new ComposedStore({ provider: this.providerStore, network: this.networkStore }) - this._proxy = createEventEmitterProxy() + this.providerProxy = createEventEmitterProxy() + this.blockTrackerProxy = createEventEmitterProxy() this.on('networkDidChange', this.lookupNetwork) } @@ -25,12 +27,11 @@ module.exports = class NetworkController extends EventEmitter { initializeProvider (_providerParams) { this._baseProviderParams = _providerParams const rpcUrl = this.getCurrentRpcAddress() - this._configureStandardProvider({ rpcUrl }) - this._proxy.on('block', this._logBlock.bind(this)) - this._proxy.on('error', this.verifyNetwork.bind(this)) - this.ethQuery = new EthQuery(this._proxy) + this._configureStandardClient({ rpcUrl }) + this.providerProxy.on('block', this._logBlock.bind(this)) + this.providerProxy.on('error', this.verifyNetwork.bind(this)) + this.ethQuery = new EthQuery(this.providerProxy) this.lookupNetwork() - return this._proxy } verifyNetwork () { @@ -76,8 +77,10 @@ module.exports = class NetworkController extends EventEmitter { assert(type !== 'rpc', `NetworkController.setProviderType - cannot connect by type "rpc"`) // skip if type already matches if (type === this.getProviderConfig().type) return + // lookup rpcTarget for type const rpcTarget = this.getRpcAddressForType(type) assert(rpcTarget, `NetworkController - unknown rpc address for type "${type}"`) + // update connection this.providerStore.updateState({ type, rpcTarget }) this._switchNetwork({ rpcUrl: rpcTarget }) } @@ -97,32 +100,33 @@ module.exports = class NetworkController extends EventEmitter { _switchNetwork (providerParams) { this.setNetworkState('loading') - this._configureStandardProvider(providerParams) + this._configureStandardClient(providerParams) this.emit('networkDidChange') } - _configureStandardProvider(_providerParams) { + _configureStandardClient(_providerParams) { const providerParams = extend(this._baseProviderParams, _providerParams) - const provider = createMetamaskProvider(providerParams) - this._setProvider(provider) + const client = createEthRpcClient(providerParams) + this._setClient(client) } - _setProvider (provider) { - // collect old block tracker events - const oldProvider = this._provider - let blockTrackerHandlers - if (oldProvider) { - // capture old block handlers - blockTrackerHandlers = oldProvider._blockTracker.proxyEventHandlers - // tear down - oldProvider.removeAllListeners() - oldProvider.stop() + _createMetamaskProvider(providerParams) { + const { provider, blockTracker } = createEthRpcClient(providerParams) + } + + _setClient (newClient) { + // teardown old client + const oldClient = this._currentClient + if (oldClient) { + oldClient.blockTracker.stop() + // asyncEventEmitter lacks a "removeAllListeners" method + // oldClient.blockTracker.removeAllListeners + oldClient.blockTracker._events = {} } - // override block tracler - provider._blockTracker = createEventEmitterProxy(provider._blockTracker, blockTrackerHandlers) // set as new provider - this._provider = provider - this._proxy.setTarget(provider) + this._currentClient = newClient + this.providerProxy.setTarget(newClient.provider) + this.blockTrackerProxy.setTarget(newClient.blockTracker) } _logBlock (block) { diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index 1292d2a1e..67bbdc15a 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -82,8 +82,8 @@ module.exports = class MetamaskController extends EventEmitter { this.blacklistController.scheduleUpdates() // rpc provider and block tracker - this.provider = this.networkController.initializeProvider({ - static: { + this.networkController.initializeProvider({ + scaffold: { eth_syncing: false, web3_clientVersion: `MetaMask/v${version}`, }, @@ -97,7 +97,8 @@ module.exports = class MetamaskController extends EventEmitter { processPersonalMessage: this.newUnsignedPersonalMessage.bind(this), processTypedMessage: this.newUnsignedTypedMessage.bind(this), }) - this.blockTracker = this.provider._blockTracker + this.provider = this.networkController.providerProxy + this.blockTracker = this.networkController.blockTrackerProxy // eth data query tools this.ethQuery = new EthQuery(this.provider) diff --git a/package.json b/package.json index 225742487..3a6be4c61 100644 --- a/package.json +++ b/package.json @@ -71,9 +71,11 @@ "eth-contract-metadata": "^1.1.4", "eth-hd-keyring": "^1.2.1", "eth-json-rpc-filters": "^1.2.2", + "eth-json-rpc-middleware": "^1.4.1", "eth-keyring-controller": "^2.1.0", "eth-phishing-detect": "^1.1.4", "eth-query": "^2.1.2", + "eth-rpc-client": "^1.0.0", "eth-sig-util": "^1.4.0", "eth-simple-keyring": "^1.1.1", "eth-token-tracker": "^1.1.4", From 4d273d3ceade861d24dceed96a0f5d5f3dc229ae Mon Sep 17 00:00:00 2001 From: kumavis Date: Tue, 10 Oct 2017 14:14:43 -0700 Subject: [PATCH 36/47] lint fixes --- app/scripts/controllers/network.js | 9 ++------- app/scripts/metamask-controller.js | 2 +- 2 files changed, 3 insertions(+), 8 deletions(-) diff --git a/app/scripts/controllers/network.js b/app/scripts/controllers/network.js index 412967dbe..f4665baf8 100644 --- a/app/scripts/controllers/network.js +++ b/app/scripts/controllers/network.js @@ -1,6 +1,5 @@ const assert = require('assert') const EventEmitter = require('events') -const createMetamaskProvider = require('web3-provider-engine/zero.js') const ObservableStore = require('obs-store') const ComposedStore = require('obs-store/lib/composed') const extend = require('xtend') @@ -77,10 +76,10 @@ module.exports = class NetworkController extends EventEmitter { assert(type !== 'rpc', `NetworkController.setProviderType - cannot connect by type "rpc"`) // skip if type already matches if (type === this.getProviderConfig().type) return - // lookup rpcTarget for type + // lookup rpcTarget for typecreateMetamaskProvider const rpcTarget = this.getRpcAddressForType(type) assert(rpcTarget, `NetworkController - unknown rpc address for type "${type}"`) - // update connection + // update connectioncreateMetamaskProvider this.providerStore.updateState({ type, rpcTarget }) this._switchNetwork({ rpcUrl: rpcTarget }) } @@ -110,10 +109,6 @@ module.exports = class NetworkController extends EventEmitter { this._setClient(client) } - _createMetamaskProvider(providerParams) { - const { provider, blockTracker } = createEthRpcClient(providerParams) - } - _setClient (newClient) { // teardown old client const oldClient = this._currentClient diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index 67bbdc15a..a742f3cba 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -478,7 +478,7 @@ module.exports = class MetamaskController extends EventEmitter { result.push(selectedAddress) } return result - }, + } addNewAccount (cb) { const primaryKeyring = this.keyringController.getKeyringsByType('HD Key Tree')[0] From e32d75965f848f8b26868b6476265e61b791c768 Mon Sep 17 00:00:00 2001 From: kumavis Date: Tue, 10 Oct 2017 17:15:14 -0700 Subject: [PATCH 37/47] events-proxy - clean up --- app/scripts/lib/events-proxy.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/app/scripts/lib/events-proxy.js b/app/scripts/lib/events-proxy.js index d1199a278..840b06b1a 100644 --- a/app/scripts/lib/events-proxy.js +++ b/app/scripts/lib/events-proxy.js @@ -1,6 +1,5 @@ -module.exports = function createEventEmitterProxy(eventEmitter, listeners) { +module.exports = function createEventEmitterProxy(eventEmitter, eventHandlers = {}) { let target = eventEmitter - const eventHandlers = listeners || {} const proxy = new Proxy({}, { get: (obj, name) => { // intercept listeners @@ -14,9 +13,12 @@ module.exports = function createEventEmitterProxy(eventEmitter, listeners) { return true }, }) + proxy.setTarget(eventEmitter) + return proxy + function setTarget (eventEmitter) { target = eventEmitter - // migrate listeners + // migrate eventHandlers Object.keys(eventHandlers).forEach((name) => { eventHandlers[name].forEach((handler) => target.on(name, handler)) }) @@ -26,6 +28,4 @@ module.exports = function createEventEmitterProxy(eventEmitter, listeners) { eventHandlers[name].push(handler) target.on(name, handler) } - if (listeners) proxy.setTarget(eventEmitter) - return proxy } \ No newline at end of file From 7d50a56198f2992e908bc97b871210ec2b52123a Mon Sep 17 00:00:00 2001 From: kumavis Date: Tue, 10 Oct 2017 17:15:52 -0700 Subject: [PATCH 38/47] util - add obj-proxy --- app/scripts/lib/obj-proxy.js | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 app/scripts/lib/obj-proxy.js diff --git a/app/scripts/lib/obj-proxy.js b/app/scripts/lib/obj-proxy.js new file mode 100644 index 000000000..29ca1269f --- /dev/null +++ b/app/scripts/lib/obj-proxy.js @@ -0,0 +1,19 @@ +module.exports = function createObjectProxy(obj) { + let target = obj + const proxy = new Proxy({}, { + get: (obj, name) => { + // intercept setTarget + if (name === 'setTarget') return setTarget + return target[name] + }, + set: (obj, name, value) => { + target[name] = value + return true + }, + }) + return proxy + + function setTarget (obj) { + target = obj + } +} \ No newline at end of file From 0f8d7dacb1bada269f38b3f0f73df9e8347bc492 Mon Sep 17 00:00:00 2001 From: kumavis Date: Tue, 10 Oct 2017 17:26:44 -0700 Subject: [PATCH 39/47] network-controller - use obj-proxy for providerProxy --- app/scripts/controllers/network.js | 7 ++++--- package.json | 4 ++-- test/unit/network-contoller-test.js | 25 +++++-------------------- 3 files changed, 11 insertions(+), 25 deletions(-) diff --git a/app/scripts/controllers/network.js b/app/scripts/controllers/network.js index f4665baf8..64ed4b7c2 100644 --- a/app/scripts/controllers/network.js +++ b/app/scripts/controllers/network.js @@ -6,6 +6,7 @@ const extend = require('xtend') const EthQuery = require('eth-query') const createEthRpcClient = require('eth-rpc-client') const createEventEmitterProxy = require('../lib/events-proxy.js') +const createObjectProxy = require('../lib/obj-proxy.js') const RPC_ADDRESS_LIST = require('../config.js').network const DEFAULT_RPC = RPC_ADDRESS_LIST['rinkeby'] @@ -17,7 +18,7 @@ module.exports = class NetworkController extends EventEmitter { this.networkStore = new ObservableStore('loading') this.providerStore = new ObservableStore(config.provider) this.store = new ComposedStore({ provider: this.providerStore, network: this.networkStore }) - this.providerProxy = createEventEmitterProxy() + this.providerProxy = createObjectProxy() this.blockTrackerProxy = createEventEmitterProxy() this.on('networkDidChange', this.lookupNetwork) @@ -27,8 +28,8 @@ module.exports = class NetworkController extends EventEmitter { this._baseProviderParams = _providerParams const rpcUrl = this.getCurrentRpcAddress() this._configureStandardClient({ rpcUrl }) - this.providerProxy.on('block', this._logBlock.bind(this)) - this.providerProxy.on('error', this.verifyNetwork.bind(this)) + this.blockTrackerProxy.on('block', this._logBlock.bind(this)) + this.blockTrackerProxy.on('error', this.verifyNetwork.bind(this)) this.ethQuery = new EthQuery(this.providerProxy) this.lookupNetwork() } diff --git a/package.json b/package.json index 3a6be4c61..a5a473bed 100644 --- a/package.json +++ b/package.json @@ -71,11 +71,11 @@ "eth-contract-metadata": "^1.1.4", "eth-hd-keyring": "^1.2.1", "eth-json-rpc-filters": "^1.2.2", - "eth-json-rpc-middleware": "^1.4.1", + "eth-json-rpc-middleware": "^1.4.2", "eth-keyring-controller": "^2.1.0", "eth-phishing-detect": "^1.1.4", "eth-query": "^2.1.2", - "eth-rpc-client": "^1.0.0", + "eth-rpc-client": "^1.0.3", "eth-sig-util": "^1.4.0", "eth-simple-keyring": "^1.1.1", "eth-token-tracker": "^1.1.4", diff --git a/test/unit/network-contoller-test.js b/test/unit/network-contoller-test.js index 0b3b5adeb..42ca40c56 100644 --- a/test/unit/network-contoller-test.js +++ b/test/unit/network-contoller-test.js @@ -14,15 +14,15 @@ describe('# Network Controller', function () { }, }) - networkController.initializeProvider(networkControllerProviderInit, dummyProviderConstructor) + networkController.initializeProvider(networkControllerProviderInit) }) describe('network', function () { describe('#provider', function () { it('provider should be updatable without reassignment', function () { - networkController.initializeProvider(networkControllerProviderInit, dummyProviderConstructor) - const proxy = networkController._proxy - proxy.setTarget({ test: true, on: () => {} }) - assert.ok(proxy.test) + networkController.initializeProvider(networkControllerProviderInit) + const providerProxy = networkController.providerProxy + providerProxy.setTarget({ test: true }) + assert.ok(providerProxy.test) }) }) describe('#getNetworkState', function () { @@ -66,19 +66,4 @@ describe('# Network Controller', function () { }) }) -function dummyProviderConstructor() { - return { - // provider - sendAsync: noop, - // block tracker - _blockTracker: {}, - start: noop, - stop: noop, - on: noop, - addListener: noop, - once: noop, - removeAllListeners: noop, - } -} - function noop() {} \ No newline at end of file From 4096ec9f693f668edfba73a98e91a4d0ef3f3e98 Mon Sep 17 00:00:00 2001 From: kumavis Date: Tue, 10 Oct 2017 20:20:12 -0700 Subject: [PATCH 40/47] deps - bump eth-json-rpc-middleware for fix --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index a5a473bed..9874f035e 100644 --- a/package.json +++ b/package.json @@ -71,7 +71,7 @@ "eth-contract-metadata": "^1.1.4", "eth-hd-keyring": "^1.2.1", "eth-json-rpc-filters": "^1.2.2", - "eth-json-rpc-middleware": "^1.4.2", + "eth-json-rpc-middleware": "^1.4.3", "eth-keyring-controller": "^2.1.0", "eth-phishing-detect": "^1.1.4", "eth-query": "^2.1.2", From d31c746210cd3130e3fee467f925987eb9578a73 Mon Sep 17 00:00:00 2001 From: kumavis Date: Tue, 10 Oct 2017 21:10:35 -0700 Subject: [PATCH 41/47] test - integration - intercept reload attempts --- test/integration/lib/first-time.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/test/integration/lib/first-time.js b/test/integration/lib/first-time.js index cedb14f6e..ee49d0901 100644 --- a/test/integration/lib/first-time.js +++ b/test/integration/lib/first-time.js @@ -3,6 +3,9 @@ const PASSWORD = 'password123' QUnit.module('first time usage') QUnit.test('render init screen', (assert) => { + // intercept reload attempts + window.onbeforeunload = () => true + const done = assert.async() runFirstTimeUsageTest(assert).then(done).catch((err) => { assert.notOk(err, `Error was thrown: ${err.stack}`) From f0713d4b1a28d608ddca6e251e947d1b2e14b6d3 Mon Sep 17 00:00:00 2001 From: kumavis Date: Wed, 11 Oct 2017 01:01:29 -0700 Subject: [PATCH 43/47] ui - network - fix localhost active status --- 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 613577913..30d3766ab 100644 --- a/ui/app/app.js +++ b/ui/app/app.js @@ -319,7 +319,7 @@ App.prototype.renderNetworkDropdown = function () { [ h('i.fa.fa-question-circle.fa-lg.menu-icon'), 'Localhost 8545', - activeNetwork === 'http://localhost:8545' ? h('.check', '✓') : null, + providerType === 'localhost' ? h('.check', '✓') : null, ] ), From 9f063c320c334c997d0ce10ccad1ff323dac128a Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Wed, 11 Oct 2017 15:15:31 -0400 Subject: [PATCH 44/47] Fix link to token support page --- CHANGELOG.md | 1 + ui/app/add-token.js | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a8bb48e9f..42ded93b6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ - Add new support for new eth_signTypedData method per EIP 712. - Fix bug where some transactions would be shown as pending forever, even after successfully mined. - Fix bug where a transaction might be shown as pending forever if another tx with the same nonce was mined. +- Fix link to support article on token addresses. ## 3.10.9 2017-10-5 diff --git a/ui/app/add-token.js b/ui/app/add-token.js index 18adc7eb5..9354a4cad 100644 --- a/ui/app/add-token.js +++ b/ui/app/add-token.js @@ -73,7 +73,7 @@ AddTokenScreen.prototype.render = function () { }, [ h('a', { style: { fontWeight: 'bold', paddingRight: '10px'}, - href: 'https://consensyssupport.happyfox.com/staff/kb/article/24-what-is-a-token-contract-address', + href: 'https://support.metamask.io/kb/article/24-what-is-a-token-contract-address', target: '_blank', }, [ h('span', 'Token Contract Address '), From 4ed00ea2d8db6649144e7ae37672a1edefa1169e Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Wed, 11 Oct 2017 15:54:17 -0400 Subject: [PATCH 45/47] Version 3.11.0 --- CHANGELOG.md | 4 +++- app/manifest.json | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 42ded93b6..ab5b3cb9f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,9 @@ ## Current Master -- Add new support for new eth_signTypedData method per EIP 712. +## 3.11.0 2017-10-11 + +- Add support for new eth_signTypedData method per EIP 712. - Fix bug where some transactions would be shown as pending forever, even after successfully mined. - Fix bug where a transaction might be shown as pending forever if another tx with the same nonce was mined. - Fix link to support article on token addresses. diff --git a/app/manifest.json b/app/manifest.json index c253a5c2b..a0f449c68 100644 --- a/app/manifest.json +++ b/app/manifest.json @@ -1,7 +1,7 @@ { "name": "MetaMask", "short_name": "Metamask", - "version": "3.10.9", + "version": "3.11.0", "manifest_version": 2, "author": "https://metamask.io", "description": "Ethereum Browser Extension", From dcf10f3d7559b428cc189fb4406580d816eae365 Mon Sep 17 00:00:00 2001 From: kumavis Date: Wed, 11 Oct 2017 18:33:02 -0700 Subject: [PATCH 46/47] nonce-tracker - use blockTracker directly --- app/scripts/controllers/transactions.js | 1 + app/scripts/lib/nonce-tracker.js | 10 +++------- test/unit/nonce-tracker-test.js | 7 ++++--- 3 files changed, 8 insertions(+), 10 deletions(-) diff --git a/app/scripts/controllers/transactions.js b/app/scripts/controllers/transactions.js index ef659a300..d46dee230 100644 --- a/app/scripts/controllers/transactions.js +++ b/app/scripts/controllers/transactions.js @@ -46,6 +46,7 @@ module.exports = class TransactionController extends EventEmitter { this.txStateManager.on('tx:status-update', this.emit.bind(this, 'tx:status-update')) this.nonceTracker = new NonceTracker({ provider: this.provider, + blockTracker: this.blockTracker, getPendingTransactions: this.txStateManager.getPendingTransactions.bind(this.txStateManager), getConfirmedTransactions: (address) => { return this.txStateManager.getFilteredTxList({ diff --git a/app/scripts/lib/nonce-tracker.js b/app/scripts/lib/nonce-tracker.js index 0029ac953..2af40a27f 100644 --- a/app/scripts/lib/nonce-tracker.js +++ b/app/scripts/lib/nonce-tracker.js @@ -4,8 +4,9 @@ const Mutex = require('await-semaphore').Mutex class NonceTracker { - constructor ({ provider, getPendingTransactions, getConfirmedTransactions }) { + constructor ({ provider, blockTracker, getPendingTransactions, getConfirmedTransactions }) { this.provider = provider + this.blockTracker = blockTracker this.ethQuery = new EthQuery(provider) this.getPendingTransactions = getPendingTransactions this.getConfirmedTransactions = getConfirmedTransactions @@ -53,7 +54,7 @@ class NonceTracker { } async _getCurrentBlock () { - const blockTracker = this._getBlockTracker() + const blockTracker = this.blockTracker const currentBlock = blockTracker.getCurrentBlock() if (currentBlock) return currentBlock return await Promise((reject, resolve) => { @@ -139,11 +140,6 @@ class NonceTracker { return { name: 'local', nonce: highest, details: { startPoint, highest } } } - // this is a hotfix for the fact that the blockTracker will - // change when the network changes - _getBlockTracker () { - return this.provider._blockTracker - } } module.exports = NonceTracker diff --git a/test/unit/nonce-tracker-test.js b/test/unit/nonce-tracker-test.js index 8970cf84d..77af2a21c 100644 --- a/test/unit/nonce-tracker-test.js +++ b/test/unit/nonce-tracker-test.js @@ -190,12 +190,13 @@ function generateNonceTrackerWith (pending, confirmed, providerStub = '0x0') { providerResultStub.result = providerStub const provider = { sendAsync: (_, cb) => { cb(undefined, providerResultStub) }, - _blockTracker: { - getCurrentBlock: () => '0x11b568', - }, + } + const blockTracker = { + getCurrentBlock: () => '0x11b568', } return new NonceTracker({ provider, + blockTracker, getPendingTransactions, getConfirmedTransactions, }) From 5c5f9297f78c965b38087b960acfec472e16818b Mon Sep 17 00:00:00 2001 From: kumavis Date: Wed, 11 Oct 2017 18:36:25 -0700 Subject: [PATCH 47/47] deps - bump eth-rpc-client for fix --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 9874f035e..2b7b2056a 100644 --- a/package.json +++ b/package.json @@ -75,7 +75,7 @@ "eth-keyring-controller": "^2.1.0", "eth-phishing-detect": "^1.1.4", "eth-query": "^2.1.2", - "eth-rpc-client": "^1.0.3", + "eth-rpc-client": "^1.1.3", "eth-sig-util": "^1.4.0", "eth-simple-keyring": "^1.1.1", "eth-token-tracker": "^1.1.4",