diff --git a/CHANGELOG.md b/CHANGELOG.md index 603a92df5..bf03cf41e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ## Current Master + +- Add two most recently used custom RPCs to network dropdown menu. - Add personal_sign method support. - Add ability to customize gas and gasPrice on the transaction approval screen. - Increase default gas buffer to 1.5x estimated gas value. diff --git a/app/scripts/lib/controllers/currency.js b/app/scripts/controllers/currency.js similarity index 100% rename from app/scripts/lib/controllers/currency.js rename to app/scripts/controllers/currency.js diff --git a/app/scripts/controllers/preferences.js b/app/scripts/controllers/preferences.js new file mode 100644 index 000000000..18fccf11b --- /dev/null +++ b/app/scripts/controllers/preferences.js @@ -0,0 +1,63 @@ +const ObservableStore = require('obs-store') +const normalizeAddress = require('eth-sig-util').normalize +const extend = require('xtend') + +class PreferencesController { + + constructor (opts = {}) { + const initState = extend({ frequentRpcList: [] }, opts.initState) + this.store = new ObservableStore(initState) + } + + // + // PUBLIC METHODS + // + + setSelectedAddress (_address) { + return new Promise((resolve, reject) => { + const address = normalizeAddress(_address) + this.store.updateState({ selectedAddress: address }) + resolve() + }) + } + + getSelectedAddress (_address) { + return this.store.getState().selectedAddress + } + + updateFrequentRpcList (_url) { + return this.addToFrequentRpcList(_url) + .then((rpcList) => { + this.store.updateState({ frequentRpcList: rpcList }) + return Promise.resolve() + }) + } + + addToFrequentRpcList (_url) { + let rpcList = this.getFrequentRpcList() + let index = rpcList.findIndex((element) => { return element === _url }) + if (index !== -1) { + rpcList.splice(index, 1) + } + if (_url !== 'http://localhost:8545') { + rpcList.push(_url) + } + if (rpcList.length > 2) { + rpcList.shift() + } + return Promise.resolve(rpcList) + } + + getFrequentRpcList () { + return this.store.getState().frequentRpcList + } + + // + // PRIVATE METHODS + // + + + +} + +module.exports = PreferencesController diff --git a/app/scripts/lib/controllers/shapeshift.js b/app/scripts/controllers/shapeshift.js similarity index 100% rename from app/scripts/lib/controllers/shapeshift.js rename to app/scripts/controllers/shapeshift.js diff --git a/app/scripts/lib/controllers/preferences.js b/app/scripts/lib/controllers/preferences.js deleted file mode 100644 index c5e93a5b9..000000000 --- a/app/scripts/lib/controllers/preferences.js +++ /dev/null @@ -1,33 +0,0 @@ -const ObservableStore = require('obs-store') -const normalizeAddress = require('eth-sig-util').normalize - -class PreferencesController { - - constructor (opts = {}) { - const initState = opts.initState || {} - this.store = new ObservableStore(initState) - } - - // - // PUBLIC METHODS - // - - setSelectedAddress(_address) { - return new Promise((resolve, reject) => { - const address = normalizeAddress(_address) - this.store.updateState({ selectedAddress: address }) - resolve() - }) - } - - getSelectedAddress(_address) { - return this.store.getState().selectedAddress - } - - // - // PRIVATE METHODS - // - -} - -module.exports = PreferencesController diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index 3dddf7065..536891dc6 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -11,10 +11,10 @@ const streamIntoProvider = require('web3-stream-provider/handler') const MetaMaskProvider = require('web3-provider-engine/zero.js') const setupMultiplex = require('./lib/stream-utils.js').setupMultiplex const KeyringController = require('./keyring-controller') -const PreferencesController = require('./lib/controllers/preferences') -const CurrencyController = require('./lib/controllers/currency') +const PreferencesController = require('./controllers/preferences') +const CurrencyController = require('./controllers/currency') const NoticeController = require('./notice-controller') -const ShapeShiftController = require('./lib/controllers/shapeshift') +const ShapeShiftController = require('./controllers/shapeshift') const MessageManager = require('./lib/message-manager') const PersonalMessageManager = require('./lib/personal-message-manager') const TxManager = require('./transaction-manager') @@ -244,7 +244,6 @@ module.exports = class MetamaskController extends EventEmitter { return { // etc getState: (cb) => cb(null, this.getState()), - setRpcTarget: this.setRpcTarget.bind(this), setProviderType: this.setProviderType.bind(this), useEtherscanProvider: this.useEtherscanProvider.bind(this), setCurrentCurrency: this.setCurrentCurrency.bind(this), @@ -265,6 +264,8 @@ module.exports = class MetamaskController extends EventEmitter { // PreferencesController setSelectedAddress: nodeify(preferencesController.setSelectedAddress).bind(preferencesController), + setDefaultRpc: nodeify(this.setDefaultRpc).bind(this), + setCustomRpc: nodeify(this.setCustomRpc).bind(this), // KeyringController setLocked: nodeify(keyringController.setLocked).bind(keyringController), @@ -661,10 +662,21 @@ module.exports = class MetamaskController extends EventEmitter { if (this.isNetworkLoading()) this.lookupNetwork() } - setRpcTarget (rpcTarget) { - this.configManager.setRpcTarget(rpcTarget) + setDefaultRpc () { + this.configManager.setRpcTarget('http://localhost:8545') extension.runtime.reload() this.lookupNetwork() + return Promise.resolve('http://localhost:8545') + } + + setCustomRpc (rpcTarget, rpcList) { + this.configManager.setRpcTarget(rpcTarget) + return this.preferencesController.updateFrequentRpcList(rpcTarget) + .then(() => { + extension.runtime.reload() + this.lookupNetwork() + return Promise.resolve(rpcTarget) + }) } setProviderType (type) { diff --git a/development/states/first-time.json b/development/states/first-time.json index 108af9117..3554ee911 100644 --- a/development/states/first-time.json +++ b/development/states/first-time.json @@ -4,6 +4,7 @@ "isUnlocked": false, "rpcTarget": "https://rawtestrpc.metamask.io/", "identities": {}, + "frequentRpcList": [], "unapprovedTxs": {}, "currentFiat": "USD", "conversionRate": 12.7527416, diff --git a/test/unit/actions/config_test.js b/test/unit/actions/config_test.js index f851e4102..14198fa8a 100644 --- a/test/unit/actions/config_test.js +++ b/test/unit/actions/config_test.js @@ -11,6 +11,7 @@ describe ('config view actions', function() { var initialState = { metamask: { rpcTarget: 'foo', + frequentRpcList: [] }, appState: { currentView: { @@ -32,13 +33,13 @@ describe ('config view actions', function() { it('sets the state.metamask.rpcTarget property of the state to the action.value', function() { const action = { type: actions.SET_RPC_TARGET, - value: 'bar', + value: 'foo', } var result = reducers(initialState, action) assert.equal(result.metamask.provider.type, 'rpc') - assert.equal(result.metamask.provider.rpcTarget, action.value) + assert.equal(result.metamask.provider.rpcTarget, 'foo') }) }) -}) +}) diff --git a/test/unit/currency-controller-test.js b/test/unit/currency-controller-test.js index c57b522c7..dd7fa91e0 100644 --- a/test/unit/currency-controller-test.js +++ b/test/unit/currency-controller-test.js @@ -5,7 +5,7 @@ const assert = require('assert') const extend = require('xtend') const rp = require('request-promise') const nock = require('nock') -const CurrencyController = require('../../app/scripts/lib/controllers/currency') +const CurrencyController = require('../../app/scripts/controllers/currency') describe('config-manager', function() { var currencyController diff --git a/test/unit/notice-controller-test.js b/test/unit/notice-controller-test.js index cf00daeba..73fdb2f2e 100644 --- a/test/unit/notice-controller-test.js +++ b/test/unit/notice-controller-test.js @@ -4,7 +4,7 @@ const rp = require('request-promise') const nock = require('nock') const configManagerGen = require('../lib/mock-config-manager') const NoticeController = require('../../app/scripts/notice-controller') -const STORAGE_KEY = 'metamask-persistance-key' +const STORAGE_KEY = 'metamask-persistence-key' describe('notice-controller', function() { var noticeController diff --git a/ui/app/actions.js b/ui/app/actions.js index 7f972fb37..d4fd7553b 100644 --- a/ui/app/actions.js +++ b/ui/app/actions.js @@ -112,11 +112,13 @@ var actions = { // config screen SHOW_CONFIG_PAGE: 'SHOW_CONFIG_PAGE', SET_RPC_TARGET: 'SET_RPC_TARGET', + SET_DEFAULT_RPC_TARGET: 'SET_DEFAULT_RPC_TARGET', SET_PROVIDER_TYPE: 'SET_PROVIDER_TYPE', USE_ETHERSCAN_PROVIDER: 'USE_ETHERSCAN_PROVIDER', useEtherscanProvider: useEtherscanProvider, showConfigPage: showConfigPage, setRpcTarget: setRpcTarget, + setDefaultRpcTarget: setDefaultRpcTarget, setProviderType: setProviderType, // loading overlay SHOW_LOADING: 'SHOW_LOADING_INDICATION', @@ -669,12 +671,28 @@ function markAccountsFound() { // config // +// default rpc target refers to localhost:8545 in this instance. +function setDefaultRpcTarget (rpcList) { + log.debug(`background.setDefaultRpcTarget`) + return (dispatch) => { + background.setDefaultRpc((err, result) => { + if (err) { + log.error(err) + return dispatch(self.displayWarning('Had a problem changing networks.')) + } + }) + } +} + function setRpcTarget (newRpc) { log.debug(`background.setRpcTarget`) - background.setRpcTarget(newRpc) - return { - type: actions.SET_RPC_TARGET, - value: newRpc, + return (dispatch) => { + background.setCustomRpc(newRpc, (err, result) => { + if (err) { + log.error(err) + return dispatch(self.displayWarning('Had a problem changing networks!')) + } + }) } } @@ -750,7 +768,7 @@ function exportAccount (address) { dispatch(self.hideLoadingIndication()) if (err) { - console.error(err) + log.error(err) return dispatch(self.displayWarning('Had a problem exporting the account.')) } diff --git a/ui/app/app.js b/ui/app/app.js index 63fab5db8..2bc92b54c 100644 --- a/ui/app/app.js +++ b/ui/app/app.js @@ -58,6 +58,7 @@ function mapStateToProps (state) { forgottenPassword: state.appState.forgottenPassword, lastUnreadNotice: state.metamask.lastUnreadNotice, lostAccounts: state.metamask.lostAccounts, + frequentRpcList: state.metamask.frequentRpcList || [], } } @@ -211,6 +212,7 @@ App.prototype.renderAppBar = function () { App.prototype.renderNetworkDropdown = function () { const props = this.props + const rpcList = props.frequentRpcList const state = this.state || {} const isOpen = state.isNetworkMenuOpen @@ -256,12 +258,13 @@ App.prototype.renderNetworkDropdown = function () { h(DropMenuItem, { label: 'Localhost 8545', closeMenu: () => this.setState({ isNetworkMenuOpen: false }), - action: () => props.dispatch(actions.setRpcTarget('http://localhost:8545')), + action: () => props.dispatch(actions.setDefaultRpcTarget(rpcList)), icon: h('i.fa.fa-question-circle.fa-lg'), activeNetworkRender: props.provider.rpcTarget, }), this.renderCustomOption(props.provider), + this.renderCommonRpc(rpcList, props.provider), props.isUnlocked && h(DropMenuItem, { label: 'Custom RPC', @@ -496,6 +499,12 @@ App.prototype.renderCustomOption = function (provider) { const { rpcTarget, type } = provider if (type !== 'rpc') return null + // Concatenate long URLs + let label = rpcTarget + if (rpcTarget.length > 31) { + label = label.substr(0, 34) + '...' + } + switch (rpcTarget) { case 'http://localhost:8545': @@ -503,10 +512,32 @@ App.prototype.renderCustomOption = function (provider) { default: return h(DropMenuItem, { - label: `${rpcTarget}`, + label, + key: rpcTarget, closeMenu: () => this.setState({ isNetworkMenuOpen: false }), icon: h('i.fa.fa-question-circle.fa-lg'), activeNetworkRender: 'custom', }) } } + +App.prototype.renderCommonRpc = function (rpcList, provider) { + const { rpcTarget } = provider + const props = this.props + + return rpcList.map((rpc) => { + if ((rpc === 'http://localhost:8545') || (rpc === rpcTarget)) { + return null + } else { + return h(DropMenuItem, { + label: rpc, + key: rpc, + closeMenu: () => this.setState({ isNetworkMenuOpen: false }), + action: () => props.dispatch(actions.setRpcTarget(rpc)), + icon: h('i.fa.fa-question-circle.fa-lg'), + activeNetworkRender: rpc, + }) + } + }) + +} diff --git a/ui/app/config.js b/ui/app/config.js index 65b1ed712..00a4cba88 100644 --- a/ui/app/config.js +++ b/ui/app/config.js @@ -5,6 +5,7 @@ const connect = require('react-redux').connect const actions = require('./actions') const currencies = require('./conversion.json').rows const validUrl = require('valid-url') + module.exports = connect(mapStateToProps)(ConfigScreen) function mapStateToProps (state) { diff --git a/ui/app/reducers/metamask.js b/ui/app/reducers/metamask.js index 3875cf6d1..a3c07d977 100644 --- a/ui/app/reducers/metamask.js +++ b/ui/app/reducers/metamask.js @@ -18,6 +18,7 @@ function reduceMetamask (state, action) { conversionDate: 'N/A', noActiveNotices: true, lastUnreadNotice: undefined, + frequentRpcList: [], }, state.metamask) switch (action.type) { @@ -53,6 +54,11 @@ function reduceMetamask (state, action) { isUnlocked: false, }) + case actions.SET_RPC_LIST: + return extend(metamaskState, { + frequentRpcList: action.value, + }) + case actions.SET_RPC_TARGET: return extend(metamaskState, { provider: {