From 4873e6faa3f1c413311f06c9f82259627a8a80b9 Mon Sep 17 00:00:00 2001 From: hackyminer Date: Wed, 6 Jun 2018 02:45:14 +0900 Subject: [PATCH 1/6] support chainid * fixed custom Rpc form to support optional chainid * experimental ETC support added --- app/_locales/en/messages.json | 9 +++ app/scripts/controllers/network/enums.js | 6 ++ app/scripts/controllers/network/network.js | 68 +++++++++++++++---- app/scripts/controllers/network/networks.js | 21 ++++++ app/scripts/controllers/network/util.js | 5 ++ app/scripts/metamask-controller.js | 4 +- old-ui/app/app.js | 19 ++++++ old-ui/app/components/network.js | 13 ++++ old-ui/app/config.js | 35 ++++++++-- ui/app/actions.js | 4 +- ui/app/app.js | 4 ++ .../components/dropdowns/network-dropdown.js | 35 ++++++++-- ui/app/components/network-display/index.scss | 8 +++ .../network-display.component.js | 2 + ui/app/components/network.js | 13 ++++ ui/app/components/pages/settings/settings.js | 19 ++++-- ui/lib/account-link.js | 1 + 17 files changed, 236 insertions(+), 30 deletions(-) create mode 100644 app/scripts/controllers/network/networks.js diff --git a/app/_locales/en/messages.json b/app/_locales/en/messages.json index 8d65bc59668f..56228210ae3d 100644 --- a/app/_locales/en/messages.json +++ b/app/_locales/en/messages.json @@ -629,6 +629,9 @@ "newRPC": { "message": "New RPC URL" }, + "optionalChainId": { + "message": "ChainId (optional)" + }, "next": { "message": "Next" }, @@ -805,6 +808,9 @@ "ropsten": { "message": "Ropsten Test Network" }, + "classic": { + "message": "Ethereum Classic Network" + }, "rpc": { "message": "Custom RPC" }, @@ -823,6 +829,9 @@ "connectingToRinkeby": { "message": "Connecting to Rinkeby Test Network" }, + "connectingToClassic": { + "message": "Connecting to Ethereum Classic Network" + }, "connectingToUnknown": { "message": "Connecting to Unknown Network" }, diff --git a/app/scripts/controllers/network/enums.js b/app/scripts/controllers/network/enums.js index 3190eb37c717..f0ef73f0f469 100644 --- a/app/scripts/controllers/network/enums.js +++ b/app/scripts/controllers/network/enums.js @@ -3,29 +3,35 @@ const RINKEBY = 'rinkeby' const KOVAN = 'kovan' const MAINNET = 'mainnet' const LOCALHOST = 'localhost' +const CLASSIC = 'classic' const MAINNET_CODE = 1 const ROPSTEN_CODE = 3 const RINKEYBY_CODE = 4 const KOVAN_CODE = 42 +const CLASSIC_CODE = 61 const ROPSTEN_DISPLAY_NAME = 'Ropsten' const RINKEBY_DISPLAY_NAME = 'Rinkeby' const KOVAN_DISPLAY_NAME = 'Kovan' const MAINNET_DISPLAY_NAME = 'Main Ethereum Network' +const CLASSIC_DISPLAY_NAME = 'Ethereum Classic' module.exports = { ROPSTEN, RINKEBY, KOVAN, MAINNET, + CLASSIC, LOCALHOST, MAINNET_CODE, ROPSTEN_CODE, RINKEYBY_CODE, KOVAN_CODE, + CLASSIC_CODE, ROPSTEN_DISPLAY_NAME, RINKEBY_DISPLAY_NAME, KOVAN_DISPLAY_NAME, MAINNET_DISPLAY_NAME, + CLASSIC_DISPLAY_NAME, } diff --git a/app/scripts/controllers/network/network.js b/app/scripts/controllers/network/network.js index a50f6dc451eb..521224169f48 100644 --- a/app/scripts/controllers/network/network.js +++ b/app/scripts/controllers/network/network.js @@ -9,15 +9,19 @@ const extend = require('xtend') const EthQuery = require('eth-query') const createEventEmitterProxy = require('../../lib/events-proxy.js') const log = require('loglevel') +const networks = require('./networks') + const { ROPSTEN, RINKEBY, KOVAN, MAINNET, + CLASSIC, LOCALHOST, } = require('./enums') const LOCALHOST_RPC_URL = 'http://localhost:8545' const INFURA_PROVIDER_TYPES = [ROPSTEN, RINKEBY, KOVAN, MAINNET] +const ALL_PROVIDER_TYPES = [ROPSTEN, RINKEBY, KOVAN, MAINNET, CLASSIC] const env = process.env.METAMASK_ENV const METAMASK_DEBUG = process.env.METAMASK_DEBUG @@ -46,25 +50,36 @@ module.exports = class NetworkController extends EventEmitter { initializeProvider (_providerParams) { this._baseProviderParams = _providerParams - const { type, rpcTarget } = this.providerStore.getState() - this._configureProvider({ type, rpcTarget }) + const { type, rpcTarget, chainId } = this.providerStore.getState() + this._configureProvider({ type, rpcTarget, chainId }) this._proxy.on('block', this._logBlock.bind(this)) this._proxy.on('error', this.verifyNetwork.bind(this)) this.ethQuery = new EthQuery(this._proxy) - this.lookupNetwork() + this.lookupNetwork(type) return this._proxy } verifyNetwork () { // Check network when restoring connectivity: - if (this.isNetworkLoading()) this.lookupNetwork() + const { type } = this.providerStore.getState() + if (this.isNetworkLoading()) this.lookupNetwork(type) } getNetworkState () { return this.networkStore.getState() } - setNetworkState (network) { + setNetworkState (network, type) { + if (network === 'loading') { + return this.networkStore.putState(network) + } + + // type must be defined + if (!type) { + return + } + network = networks.networkList[type] && networks.networkList[type].chainId ? networks.networkList[type].chainId : network + console.info('type = ' + type + ', network (chainId) = ' + network) return this.networkStore.putState(network) } @@ -72,29 +87,36 @@ module.exports = class NetworkController extends EventEmitter { return this.getNetworkState() === 'loading' } - lookupNetwork () { + lookupNetwork (newtype) { // Prevent firing when provider is not defined. if (!this.ethQuery || !this.ethQuery.sendAsync) { return log.warn('NetworkController - lookupNetwork aborted due to missing ethQuery') } + var { type } = this.providerStore.getState() + if (!newtype) { + newtype = type + } + this.ethQuery.sendAsync({ method: 'net_version' }, (err, network) => { if (err) return this.setNetworkState('loading') log.info('web3.getNetwork returned ' + network) - this.setNetworkState(network) + console.info('type = ' + newtype + ' / web3.getNetwork returned ' + network) + this.setNetworkState(network, newtype) }) } - setRpcTarget (rpcTarget) { + setRpcTarget (rpcTarget, chainId) { const providerConfig = { type: 'rpc', rpcTarget, + chainId, } this.providerConfig = providerConfig } async setProviderType (type) { assert.notEqual(type, 'rpc', `NetworkController - cannot call "setProviderType" with type 'rpc'. use "setRpcTarget"`) - assert(INFURA_PROVIDER_TYPES.includes(type) || type === LOCALHOST, `NetworkController - Unknown rpc type "${type}"`) + assert(ALL_PROVIDER_TYPES.includes(type) || type === LOCALHOST, `NetworkController - Unknown rpc type "${type}"`) const providerConfig = { type } this.providerConfig = providerConfig } @@ -123,17 +145,20 @@ module.exports = class NetworkController extends EventEmitter { } _configureProvider (opts) { - const { type, rpcTarget } = opts + const { type, rpcTarget, chainId } = opts // infura type-based endpoints const isInfura = INFURA_PROVIDER_TYPES.includes(type) if (isInfura) { this._configureInfuraProvider(opts) + // other predefined endpoints + } else if (ALL_PROVIDER_TYPES.includes(type)){ + this._configurePredefinedProvider(opts) // other type-based rpc endpoints } else if (type === LOCALHOST) { this._configureStandardProvider({ rpcUrl: LOCALHOST_RPC_URL }) // url-based rpc endpoints } else if (type === 'rpc') { - this._configureStandardProvider({ rpcUrl: rpcTarget }) + this._configureStandardProvider({ rpcUrl: rpcTarget, chainId }) } else { throw new Error(`NetworkController - _configureProvider - unknown type "${type}"`) } @@ -154,13 +179,32 @@ module.exports = class NetworkController extends EventEmitter { this._setProvider(provider) } - _configureStandardProvider ({ rpcUrl }) { + _configurePredefinedProvider ({ type }) { + log.info('_configurePredefinedProvider', type) + const providerParams = extend(this._baseProviderParams, { + type, + rpcUrl: networks.networkList[type].rpcUrl, + engineParams: { + pollingInterval: 8000, + }, + }) + const provider = createMetamaskProvider(providerParams) + this._setProvider(provider) + } + + _configureStandardProvider ({ rpcUrl, chainId }) { const providerParams = extend(this._baseProviderParams, { rpcUrl, engineParams: { pollingInterval: 8000, }, }) + // hack to add a 'rpc' network with chainId + networks.networkList['rpc'] = { + chainId: chainId, + rpcUrl, + ticker: 'ETH*', + } const provider = createMetamaskProvider(providerParams) this._setProvider(provider) } diff --git a/app/scripts/controllers/network/networks.js b/app/scripts/controllers/network/networks.js new file mode 100644 index 000000000000..f96556ce1e87 --- /dev/null +++ b/app/scripts/controllers/network/networks.js @@ -0,0 +1,21 @@ +'use strict' +var networks = function() {} + +const { + CLASSIC, + CLASSIC_CODE, +} = require('./enums') + +networks.networkList = { + [CLASSIC]: { + 'chainId': CLASSIC_CODE, + 'ticker': 'ETC', + 'blockExplorerTx': 'https://gastracker.io/tx/[[txHash]]', + 'blockExplorerAddr': 'https://gastracker.io/addr/[[address]]', + 'blockExplorerToken': 'https://gastracker.io/token/[[tokenAddress]]/[[address]]', + 'service': 'Ethereum Commonwealth', + 'rpcUrl': 'https://etc-geth.0xinfra.com', + }, +} + +module.exports = networks diff --git a/app/scripts/controllers/network/util.js b/app/scripts/controllers/network/util.js index 261dae7211cd..3d5059db4812 100644 --- a/app/scripts/controllers/network/util.js +++ b/app/scripts/controllers/network/util.js @@ -3,13 +3,16 @@ const { RINKEBY, KOVAN, MAINNET, + CLASSIC, ROPSTEN_CODE, RINKEYBY_CODE, KOVAN_CODE, + CLASSIC_CODE, ROPSTEN_DISPLAY_NAME, RINKEBY_DISPLAY_NAME, KOVAN_DISPLAY_NAME, MAINNET_DISPLAY_NAME, + CLASSIC_DISPLAY_NAME, } = require('./enums') const networkToNameMap = { @@ -17,9 +20,11 @@ const networkToNameMap = { [RINKEBY]: RINKEBY_DISPLAY_NAME, [KOVAN]: KOVAN_DISPLAY_NAME, [MAINNET]: MAINNET_DISPLAY_NAME, + [CLASSIC]: CLASSIC_DISPLAY_NAME, [ROPSTEN_CODE]: ROPSTEN_DISPLAY_NAME, [RINKEYBY_CODE]: RINKEBY_DISPLAY_NAME, [KOVAN_CODE]: KOVAN_DISPLAY_NAME, + [CLASSIC_CODE]: CLASSIC_DISPLAY_NAME, } const getNetworkDisplayName = key => networkToNameMap[key] diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index 6f5908414fa6..8f7b430a18b4 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -1369,8 +1369,8 @@ module.exports = class MetamaskController extends EventEmitter { * @param {string} rpcTarget - A URL for a valid Ethereum RPC API. * @returns {Promise} - The RPC Target URL confirmed. */ - async setCustomRpc (rpcTarget) { - this.networkController.setRpcTarget(rpcTarget) + async setCustomRpc (rpcTarget, chainId) { + this.networkController.setRpcTarget(rpcTarget, chainId) await this.preferencesController.updateFrequentRpcList(rpcTarget) return rpcTarget } diff --git a/old-ui/app/app.js b/old-ui/app/app.js index 0637e3b5b109..649ee29aab32 100644 --- a/old-ui/app/app.js +++ b/old-ui/app/app.js @@ -325,6 +325,23 @@ App.prototype.renderNetworkDropdown = function () { ] ), + h( + DropdownMenuItem, + { + key: 'classic', + closeMenu: () => this.setState({ isNetworkMenuOpen: !isOpen }), + onClick: () => props.dispatch(actions.setProviderType('classic')), + style: { + fontSize: '18px', + }, + }, + [ + h('.menu-icon.diamond'), + 'Ethereum Classic Network', + providerType === 'classic' ? h('.check', '✓') : null, + ] + ), + h( DropdownMenuItem, { @@ -672,6 +689,8 @@ App.prototype.getNetworkName = function () { name = 'Kovan Test Network' } else if (providerName === 'rinkeby') { name = 'Rinkeby Test Network' + } else if (providerName === 'classic') { + name = 'Ethereum Classic Network' } else { name = 'Unknown Private Network' } diff --git a/old-ui/app/components/network.js b/old-ui/app/components/network.js index 59596dabd31e..4e12e2733ca5 100644 --- a/old-ui/app/components/network.js +++ b/old-ui/app/components/network.js @@ -55,6 +55,9 @@ Network.prototype.render = function () { } else if (providerName === 'rinkeby') { hoverText = 'Rinkeby Test Network' iconName = 'rinkeby-test-network' + } else if (providerName === 'classic') { + hoverText = 'Ethereum Classic' + iconName = 'ethereum-classic-network' } else { hoverText = 'Unknown Private Network' iconName = 'unknown-private-network' @@ -108,6 +111,16 @@ Network.prototype.render = function () { 'Rinkeby Test Net'), props.onClick && h('i.fa.fa-caret-down.fa-lg'), ]) + case 'ethereum-classic-network': + return h('.network-indicator', [ + h('.menu-icon.diamond'), + h('.network-name', { + style: { + color: '#267f00', + }}, + 'Ethereum Classic Network'), + props.onClick && h('i.fa.fa-caret-down.fa-lg'), + ]) default: return h('.network-indicator', [ h('i.fa.fa-question-circle.fa-lg', { diff --git a/old-ui/app/config.js b/old-ui/app/config.js index 392a6dba7c1b..1e7fa6a3fe35 100644 --- a/old-ui/app/config.js +++ b/old-ui/app/config.js @@ -68,7 +68,7 @@ ConfigScreen.prototype.render = function () { currentProviderDisplay(metamaskState), - h('div', { style: {display: 'flex'} }, [ + h('div', { style: {display: 'block'} }, [ h('input#new_rpc', { placeholder: 'New RPC URL', style: { @@ -81,7 +81,26 @@ ConfigScreen.prototype.render = function () { if (event.key === 'Enter') { var element = event.target var newRpc = element.value - rpcValidation(newRpc, state) + var chainid = document.querySelector('input#chainid') + rpcValidation(newRpc, chainid.value, state) + } + }, + }), + h('br'), + h('input#chainid', { + placeholder: 'ChainId (optional)', + style: { + width: 'inherit', + flex: '1 0 auto', + height: '30px', + margin: '8px', + }, + onKeyPress (event) { + if (event.key === 'Enter') { + var element = document.querySelector('input#new_rpc') + var newRpc = element.value + var chainid = document.querySelector('input#chainid') + rpcValidation(newRpc, chainid.value, state) } }, }), @@ -93,7 +112,8 @@ ConfigScreen.prototype.render = function () { event.preventDefault() var element = document.querySelector('input#new_rpc') var newRpc = element.value - rpcValidation(newRpc, state) + var chainid = document.querySelector('input#chainid') + rpcValidation(newRpc, chainid.value, state) }, }, 'Save'), ]), @@ -189,9 +209,9 @@ ConfigScreen.prototype.render = function () { ) } -function rpcValidation (newRpc, state) { +function rpcValidation (newRpc, chainid, state) { if (validUrl.isWebUri(newRpc)) { - state.dispatch(actions.setRpcTarget(newRpc)) + state.dispatch(actions.setRpcTarget(newRpc, chainid)) } else { var appendedRpc = `http://${newRpc}` if (validUrl.isWebUri(appendedRpc)) { @@ -249,6 +269,11 @@ function currentProviderDisplay (metamaskState) { value = 'Rinkeby Test Network' break + case 'classic': + title = 'Current Network' + value = 'Ethereum Classic Network' + break + default: title = 'Current RPC' value = metamaskState.provider.rpcTarget diff --git a/ui/app/actions.js b/ui/app/actions.js index 6c947fc35fd7..ceb12b60518f 100644 --- a/ui/app/actions.js +++ b/ui/app/actions.js @@ -1665,10 +1665,10 @@ function updateProviderType (type) { } } -function setRpcTarget (newRpc) { +function setRpcTarget (newRpc, chainId) { return (dispatch) => { log.debug(`background.setRpcTarget: ${newRpc}`) - background.setCustomRpc(newRpc, (err, result) => { + background.setCustomRpc(newRpc, chainId, (err, result) => { if (err) { log.error(err) return dispatch(self.displayWarning('Had a problem changing networks!')) diff --git a/ui/app/app.js b/ui/app/app.js index dbb6146d1a60..72fe08986717 100644 --- a/ui/app/app.js +++ b/ui/app/app.js @@ -229,6 +229,8 @@ class App extends Component { name = this.context.t('connectingToRopsten') } else if (providerName === 'rinkeby') { name = this.context.t('connectingToRinkeby') + } else if (providerName === 'classic') { + name = this.context.t('connectingToClassic') } else { name = this.context.t('connectingToUnknown') } @@ -250,6 +252,8 @@ class App extends Component { name = this.context.t('kovan') } else if (providerName === 'rinkeby') { name = this.context.t('rinkeby') + } else if (providerName === 'classic') { + name = this.context.t('classic') } else { name = this.context.t('unknownNetwork') } diff --git a/ui/app/components/dropdowns/network-dropdown.js b/ui/app/components/dropdowns/network-dropdown.js index dcd6b4370406..e4efbbd42a1f 100644 --- a/ui/app/components/dropdowns/network-dropdown.js +++ b/ui/app/components/dropdowns/network-dropdown.js @@ -26,6 +26,7 @@ function mapStateToProps (state) { provider: state.metamask.provider, frequentRpcList: state.metamask.frequentRpcList || [], networkDropdownOpen: state.appState.networkDropdownOpen, + network: state.metamask.network, } } @@ -40,8 +41,8 @@ function mapDispatchToProps (dispatch) { setDefaultRpcTarget: type => { dispatch(actions.setDefaultRpcTarget(type)) }, - setRpcTarget: (target) => { - dispatch(actions.setRpcTarget(target)) + setRpcTarget: (target, network) => { + dispatch(actions.setRpcTarget(target, network)) }, showNetworkDropdown: () => dispatch(actions.showNetworkDropdown()), hideNetworkDropdown: () => dispatch(actions.hideNetworkDropdown()), @@ -200,6 +201,28 @@ NetworkDropdown.prototype.render = function () { ] ), + h( + DropdownMenuItem, + { + key: 'classic', + closeMenu: () => this.props.hideNetworkDropdown(), + onClick: () => props.setProviderType('classic'), + style: dropdownMenuItemStyle, + }, + [ + providerType === 'classic' ? h('i.fa.fa-check') : h('.network-check__transparent', '✓'), + h(NetworkDropdownIcon, { + backgroundColor: '#228B22', // forest green + isSelected: providerType === 'classic', + }), + h('span.network-name-item', { + style: { + color: providerType === 'classic' ? '#ffffff' : '#9b9b9b', + }, + }, this.context.t('classic')), + ] + ), + h( DropdownMenuItem, { @@ -264,6 +287,8 @@ NetworkDropdown.prototype.getNetworkName = function () { name = this.context.t('kovan') } else if (providerName === 'rinkeby') { name = this.context.t('rinkeby') + } else if (providerName === 'classic') { + name = this.context.t('classic') } else { name = this.context.t('unknownNetwork') } @@ -274,6 +299,7 @@ NetworkDropdown.prototype.getNetworkName = function () { NetworkDropdown.prototype.renderCommonRpc = function (rpcList, provider) { const props = this.props const rpcTarget = provider.rpcTarget + const network = props.network return rpcList.map((rpc) => { if ((rpc === 'http://localhost:8545') || (rpc === rpcTarget)) { @@ -284,7 +310,7 @@ NetworkDropdown.prototype.renderCommonRpc = function (rpcList, provider) { { key: `common${rpc}`, closeMenu: () => this.props.hideNetworkDropdown(), - onClick: () => props.setRpcTarget(rpc), + onClick: () => props.setRpcTarget(rpc, network), style: { fontFamily: 'DIN OT', fontSize: '16px', @@ -309,6 +335,7 @@ NetworkDropdown.prototype.renderCommonRpc = function (rpcList, provider) { NetworkDropdown.prototype.renderCustomOption = function (provider) { const { rpcTarget, type } = provider const props = this.props + const network = props.network if (type !== 'rpc') return null @@ -322,7 +349,7 @@ NetworkDropdown.prototype.renderCustomOption = function (provider) { DropdownMenuItem, { key: rpcTarget, - onClick: () => props.setRpcTarget(rpcTarget), + onClick: () => props.setRpcTarget(rpcTarget, network), closeMenu: () => this.props.hideNetworkDropdown(), style: { fontFamily: 'DIN OT', diff --git a/ui/app/components/network-display/index.scss b/ui/app/components/network-display/index.scss index 2085cff67cc3..89d46a7b18f5 100644 --- a/ui/app/components/network-display/index.scss +++ b/ui/app/components/network-display/index.scss @@ -23,6 +23,10 @@ &--rinkeby { background-color: lighten($tulip-tree, 35%); } + + &--classic { + background-color: lighten($java, 45%); + } } &__name { @@ -50,5 +54,9 @@ &--rinkeby { background-color: $tulip-tree; } + + &--classic { + background-color: $java; + } } } diff --git a/ui/app/components/network-display/network-display.component.js b/ui/app/components/network-display/network-display.component.js index 38626af20667..64913bdae263 100644 --- a/ui/app/components/network-display/network-display.component.js +++ b/ui/app/components/network-display/network-display.component.js @@ -6,6 +6,7 @@ import { ROPSTEN_CODE, RINKEYBY_CODE, KOVAN_CODE, + CLASSIC_CODE, } from '../../../../app/scripts/controllers/network/enums' const networkToClassHash = { @@ -13,6 +14,7 @@ const networkToClassHash = { [ROPSTEN_CODE]: 'ropsten', [RINKEYBY_CODE]: 'rinkeby', [KOVAN_CODE]: 'kovan', + [CLASSIC_CODE]: 'classic', } export default class NetworkDisplay extends Component { diff --git a/ui/app/components/network.js b/ui/app/components/network.js index 83297c4f2251..832f5f1c8abc 100644 --- a/ui/app/components/network.js +++ b/ui/app/components/network.js @@ -63,6 +63,9 @@ Network.prototype.render = function () { } else if (providerName === 'rinkeby') { hoverText = context.t('rinkeby') iconName = 'rinkeby-test-network' + } else if (providerName === 'classic') { + hoverText = context.t('classic') + iconName = 'ethereum-classic-network' } else { hoverText = context.t('unknownNetwork') iconName = 'unknown-private-network' @@ -76,6 +79,7 @@ Network.prototype.render = function () { 'ropsten-test-network': providerName === 'ropsten' || parseInt(networkNumber) === 3, 'kovan-test-network': providerName === 'kovan', 'rinkeby-test-network': providerName === 'rinkeby', + 'ethereum-classic-network': providerName === 'classic', }), title: hoverText, onClick: (event) => { @@ -122,6 +126,15 @@ Network.prototype.render = function () { h('.network-name', context.t('rinkeby')), h('i.fa.fa-chevron-down.fa-lg.network-caret'), ]) + case 'ethereum-classic-network': + return h('.network-indicator', [ + h(NetworkDropdownIcon, { + backgroundColor: '#228B22', // green + nonSelectBackgroundColor: '#46893D', + }), + h('.network-name', context.t('classic')), + h('i.fa.fa-chevron-down.fa-lg.network-caret'), + ]) default: return h('.network-indicator', [ h('i.fa.fa-question-circle.fa-lg', { diff --git a/ui/app/components/pages/settings/settings.js b/ui/app/components/pages/settings/settings.js index ff42a13dee27..38d8729ac938 100644 --- a/ui/app/components/pages/settings/settings.js +++ b/ui/app/components/pages/settings/settings.js @@ -173,14 +173,23 @@ class Settings extends Component { onChange: event => this.setState({ newRpc: event.target.value }), onKeyPress: event => { if (event.key === 'Enter') { - this.validateRpc(this.state.newRpc) + this.validateRpc(this.state.newRpc, this.state.chainId) + } + }, + }), + h('input.settings__input', { + placeholder: this.context.t('optionalChainId'), + onChange: event => this.setState({ chainId: event.target.value }), + onKeyPress: event => { + if (event.key === 'Enter') { + this.validateRpc(this.state.newRpc, this.state.chainId) } }, }), h('div.settings__rpc-save-button', { onClick: event => { event.preventDefault() - this.validateRpc(this.state.newRpc) + this.validateRpc(this.state.newRpc, this.state.chainId) }, }, this.context.t('save')), ]), @@ -189,11 +198,11 @@ class Settings extends Component { ) } - validateRpc (newRpc) { + validateRpc (newRpc, chainId) { const { setRpcTarget, displayWarning } = this.props if (validUrl.isWebUri(newRpc)) { - setRpcTarget(newRpc) + setRpcTarget(newRpc, chainId) } else { const appendedRpc = `http://${newRpc}` @@ -341,7 +350,7 @@ const mapStateToProps = state => { const mapDispatchToProps = dispatch => { return { setCurrentCurrency: currency => dispatch(actions.setCurrentCurrency(currency)), - setRpcTarget: newRpc => dispatch(actions.setRpcTarget(newRpc)), + setRpcTarget: (newRpc, chainId) => dispatch(actions.setRpcTarget(newRpc, chainId)), displayWarning: warning => dispatch(actions.displayWarning(warning)), revealSeedConfirmation: () => dispatch(actions.revealSeedConfirmation()), setUseBlockie: value => dispatch(actions.setUseBlockie(value)), diff --git a/ui/lib/account-link.js b/ui/lib/account-link.js index 037d990fa223..f95980d01caf 100644 --- a/ui/lib/account-link.js +++ b/ui/lib/account-link.js @@ -18,6 +18,7 @@ module.exports = function (address, network) { link = `https://kovan.etherscan.io/address/${address}` break default: + // FIXME link = '' break } From 555a90aada5757ae913bd0e0e31030635aa8e328 Mon Sep 17 00:00:00 2001 From: hackyminer Date: Wed, 6 Jun 2018 03:11:58 +0900 Subject: [PATCH 2/6] update testcase --- test/unit/app/controllers/network-contoller-test.js | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/test/unit/app/controllers/network-contoller-test.js b/test/unit/app/controllers/network-contoller-test.js index e16fb104e86b..738a0f629ece 100644 --- a/test/unit/app/controllers/network-contoller-test.js +++ b/test/unit/app/controllers/network-contoller-test.js @@ -46,7 +46,7 @@ describe('# Network Controller', function () { describe('#setNetworkState', function () { it('should update the network', function () { - networkController.setNetworkState(1) + networkController.setNetworkState(1, 'rpc') const networkState = networkController.getNetworkState() assert.equal(networkState, 1, 'network is 1') }) @@ -79,6 +79,9 @@ describe('Network utils', () => { }, { input: 42, expected: 'Kovan', + }, { + input: 61, + expected: 'Ethereum Classic', }, { input: 'ropsten', expected: 'Ropsten', @@ -88,6 +91,9 @@ describe('Network utils', () => { }, { input: 'kovan', expected: 'Kovan', + }, { + input: 'classic', + expected: 'Ethereum Classic', }, { input: 'mainnet', expected: 'Main Ethereum Network', From 6d908be9e0761df47ae59f215319fe7e104c4473 Mon Sep 17 00:00:00 2001 From: hackyminer Date: Tue, 19 Jun 2018 00:05:00 +0900 Subject: [PATCH 3/6] setup settings and use correct ticker / blockexploer URLs * etc logo added * price info from https://min-api.cryptocompare.com/ --- app/images/etc_logo.svg | 468 ++++++++++++++++++ app/scripts/controllers/currency.js | 32 +- app/scripts/controllers/network/network.js | 37 +- app/scripts/metamask-controller.js | 5 + old-ui/app/components/account-dropdowns.js | 17 +- old-ui/app/components/balance.js | 13 +- old-ui/app/components/eth-balance.js | 14 +- old-ui/app/components/shift-list-item.js | 2 +- old-ui/app/components/token-cell.js | 32 +- .../app/components/transaction-list-item.js | 16 +- ui/app/components/account-dropdowns.js | 17 +- ui/app/components/account-menu/index.js | 7 +- ui/app/components/balance-component.js | 9 +- .../dropdowns/components/account-dropdowns.js | 18 +- .../dropdowns/token-menu-dropdown.js | 9 +- ui/app/components/eth-balance.js | 15 +- ui/app/components/identicon.js | 11 +- .../modals/account-details-modal.js | 8 +- .../send/currency-display/currency-display.js | 15 +- ui/app/components/shift-list-item.js | 2 +- ui/app/components/token-cell.js | 13 +- ui/app/components/tx-list-item.js | 10 +- ui/app/components/tx-list.js | 12 +- ui/lib/account-link.js | 7 +- ui/lib/explorer-link.js | 10 + 25 files changed, 744 insertions(+), 55 deletions(-) create mode 100644 app/images/etc_logo.svg create mode 100644 ui/lib/explorer-link.js diff --git a/app/images/etc_logo.svg b/app/images/etc_logo.svg new file mode 100644 index 000000000000..52701185c744 --- /dev/null +++ b/app/images/etc_logo.svg @@ -0,0 +1,468 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/scripts/controllers/currency.js b/app/scripts/controllers/currency.js index a93aff49bcea..fffb407fc767 100644 --- a/app/scripts/controllers/currency.js +++ b/app/scripts/controllers/currency.js @@ -25,6 +25,7 @@ class CurrencyController { */ constructor (opts = {}) { const initState = extend({ + fromCurrency: 'ETH', currentCurrency: 'usd', conversionRate: 0, conversionDate: 'N/A', @@ -36,6 +37,26 @@ class CurrencyController { // PUBLIC METHODS // + /** + * A getter for the fromCurrency property + * + * @returns {string} A 2-4 character shorthand that describes the specific currency + * + */ + getFromCurrency () { + return this.store.getState().fromCurrency + } + + /** + * A setter for the fromCurrency property + * + * @param {string} fromCurrency The new currency to set as the fromCurrency in the store + * + */ + setFromCurrency (fromCurrency) { + this.store.updateState({ fromCurrency }) + } + /** * A getter for the currentCurrency property * @@ -104,15 +125,16 @@ class CurrencyController { * */ async updateConversionRate () { - let currentCurrency + let currentCurrency, fromCurrency try { currentCurrency = this.getCurrentCurrency() - const response = await fetch(`https://api.infura.io/v1/ticker/eth${currentCurrency.toLowerCase()}`) + fromCurrency = this.getFromCurrency() + const response = await fetch(`https://min-api.cryptocompare.com/data/pricehistorical?fsym=${fromCurrency.toUpperCase()}&tsyms=${currentCurrency.toUpperCase()}`) const parsedResponse = await response.json() - this.setConversionRate(Number(parsedResponse.bid)) - this.setConversionDate(Number(parsedResponse.timestamp)) + this.setConversionRate(Number(parsedResponse[fromCurrency.toUpperCase()][currentCurrency.toUpperCase()])) + this.setConversionDate(parseInt(new Date().getTime() / 1000)) } catch (err) { - log.warn(`MetaMask - Failed to query currency conversion:`, currentCurrency, err) + log.warn(`MetaMask - Failed to query currency conversion:`, fromCurrency, currentCurrency, err) this.setConversionRate(0) this.setConversionDate('N/A') } diff --git a/app/scripts/controllers/network/network.js b/app/scripts/controllers/network/network.js index 521224169f48..30890e19ac18 100644 --- a/app/scripts/controllers/network/network.js +++ b/app/scripts/controllers/network/network.js @@ -31,6 +31,10 @@ const defaultProviderConfig = { type: testMode ? RINKEBY : MAINNET, } +const defaultNetworkConfig = { + type: 'mainnet', ticker: 'ETH', +} + module.exports = class NetworkController extends EventEmitter { constructor (opts = {}) { @@ -41,7 +45,8 @@ module.exports = class NetworkController extends EventEmitter { // create stores this.providerStore = new ObservableStore(providerConfig) this.networkStore = new ObservableStore('loading') - this.store = new ComposedStore({ provider: this.providerStore, network: this.networkStore }) + this.networkConfig = new ObservableStore(defaultNetworkConfig) + this.store = new ComposedStore({ provider: this.providerStore, network: this.networkStore, settings: this.networkConfig }) // create event emitter proxy this._proxy = createEventEmitterProxy() @@ -69,6 +74,10 @@ module.exports = class NetworkController extends EventEmitter { return this.networkStore.getState() } + getNetworkConfig () { + return this.networkConfig.getState() + } + setNetworkState (network, type) { if (network === 'loading') { return this.networkStore.putState(network) @@ -175,6 +184,12 @@ module.exports = class NetworkController extends EventEmitter { }, dataSubprovider: infuraSubprovider, }) + // setup networkConfig + var settings = { + type, + ticker: 'ETH', + } + this.networkConfig.putState(settings) const provider = createMetamaskProvider(providerParams) this._setProvider(provider) } @@ -185,9 +200,18 @@ module.exports = class NetworkController extends EventEmitter { type, rpcUrl: networks.networkList[type].rpcUrl, engineParams: { - pollingInterval: 8000, + pollingInterval: networks.networkList[type].pollingInterval || 8000, }, }) + // setup networkConfig + if (networks.networkList[type]) { + var settings = { + type, + network: networks.networkList[type].chainId, + } + settings = extend(settings, networks.networkList[type]) + this.networkConfig.putState(settings) + } const provider = createMetamaskProvider(providerParams) this._setProvider(provider) } @@ -203,8 +227,15 @@ module.exports = class NetworkController extends EventEmitter { networks.networkList['rpc'] = { chainId: chainId, rpcUrl, - ticker: 'ETH*', + ticker: 'ETH', + } + // setup networkConfig + var settings = { + type: 'rpc', + network: chainId, } + settings = extend(settings, networks.networkList['rpc']) + this.networkConfig.putState(settings) const provider = createMetamaskProvider(providerParams) this._setProvider(provider) } diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index 8f7b430a18b4..6dc5fcfd1a81 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -183,6 +183,8 @@ module.exports = class MetamaskController extends EventEmitter { }) this.networkController.on('networkDidChange', () => { this.balancesController.updateAllBalances() + var currentCurrency = this.currencyController.getCurrentCurrency() + this.setCurrentCurrency(currentCurrency, function() {}) }) this.balancesController.updateAllBalances() @@ -1325,10 +1327,13 @@ module.exports = class MetamaskController extends EventEmitter { * @param {Function} cb - A callback function returning currency info. */ setCurrentCurrency (currencyCode, cb) { + const { ticker } = this.networkController.getNetworkConfig() try { + this.currencyController.setFromCurrency(ticker) this.currencyController.setCurrentCurrency(currencyCode) this.currencyController.updateConversionRate() const data = { + fromCurrency: ticker, conversionRate: this.currencyController.getConversionRate(), currentCurrency: this.currencyController.getCurrentCurrency(), conversionDate: this.currencyController.getConversionDate(), diff --git a/old-ui/app/components/account-dropdowns.js b/old-ui/app/components/account-dropdowns.js index 262de66019aa..2b9284278b0d 100644 --- a/old-ui/app/components/account-dropdowns.js +++ b/old-ui/app/components/account-dropdowns.js @@ -2,7 +2,7 @@ const Component = require('react').Component const PropTypes = require('prop-types') const h = require('react-hyperscript') const actions = require('../../../ui/app/actions') -const genAccountLink = require('etherscan-link').createAccountLink +const genAccountLink = require('../../../ui/lib/account-link.js') const connect = require('react-redux').connect const Dropdown = require('./dropdown').Dropdown const DropdownMenuItem = require('./dropdown').DropdownMenuItem @@ -189,7 +189,11 @@ class AccountDropdowns extends Component { closeMenu: () => {}, onClick: () => { const { selected, network } = this.props - const url = genAccountLink(selected, network) + let url + if (this.props.settings && this.props.settings.blockExplorerAddr) { + url = this.props.settings.blockExplorerAddr + } + url = genAccountLink(selected, network, url) global.platform.openWindow({ url }) }, }, @@ -298,6 +302,7 @@ AccountDropdowns.propTypes = { keyrings: PropTypes.array, actions: PropTypes.objectOf(PropTypes.func), network: PropTypes.string, + settings: PropTypes.object, style: PropTypes.object, enableAccountOptions: PropTypes.bool, enableAccountsSelector: PropTypes.bool, @@ -316,6 +321,12 @@ const mapDispatchToProps = (dispatch) => { } } +function mapStateToProps (state) { + return { + settings: state.metamask.settings, + } +} + module.exports = { - AccountDropdowns: connect(null, mapDispatchToProps)(AccountDropdowns), + AccountDropdowns: connect(mapStateToProps, mapDispatchToProps)(AccountDropdowns), } diff --git a/old-ui/app/components/balance.js b/old-ui/app/components/balance.js index 57ca845649ee..cc4598235c92 100644 --- a/old-ui/app/components/balance.js +++ b/old-ui/app/components/balance.js @@ -1,12 +1,18 @@ const Component = require('react').Component const h = require('react-hyperscript') +const connect = require('react-redux').connect const inherits = require('util').inherits const formatBalance = require('../util').formatBalance const generateBalanceObject = require('../util').generateBalanceObject const Tooltip = require('./tooltip.js') const FiatValue = require('./fiat-value.js') -module.exports = EthBalanceComponent +module.exports = connect(mapStateToProps)(EthBalanceComponent) +function mapStateToProps (state) { + return { + ticker: state.metamask.settings && state.metamask.settings.ticker || 'ETH', + } +} inherits(EthBalanceComponent, Component) function EthBalanceComponent () { @@ -16,11 +22,16 @@ function EthBalanceComponent () { EthBalanceComponent.prototype.render = function () { var props = this.props let { value } = props + const { ticker } = props var style = props.style var needsParse = this.props.needsParse !== undefined ? this.props.needsParse : true value = value ? formatBalance(value, 6, needsParse) : '...' var width = props.width + if (ticker !== 'ETH') { + value = value.replace(/ETH/, ticker) + } + return ( h('.ether-balance.ether-balance-amount', { diff --git a/old-ui/app/components/eth-balance.js b/old-ui/app/components/eth-balance.js index 4f538fd31f31..25b7e9fa85d1 100644 --- a/old-ui/app/components/eth-balance.js +++ b/old-ui/app/components/eth-balance.js @@ -1,12 +1,18 @@ const Component = require('react').Component const h = require('react-hyperscript') +const connect = require('react-redux').connect const inherits = require('util').inherits const formatBalance = require('../util').formatBalance const generateBalanceObject = require('../util').generateBalanceObject const Tooltip = require('./tooltip.js') const FiatValue = require('./fiat-value.js') -module.exports = EthBalanceComponent +module.exports = connect(mapStateToProps)(EthBalanceComponent) +function mapStateToProps (state) { + return { + ticker: state.metamask.settings && state.metamask.settings.ticker || 'ETH', + } +} inherits(EthBalanceComponent, Component) function EthBalanceComponent () { @@ -16,10 +22,14 @@ function EthBalanceComponent () { EthBalanceComponent.prototype.render = function () { var props = this.props let { value } = props - const { style, width } = props + const { ticker, style, width } = props var needsParse = this.props.needsParse !== undefined ? this.props.needsParse : true value = value ? formatBalance(value, 6, needsParse) : '...' + if (ticker !== 'ETH') { + value = value.replace(/ETH/, ticker) + } + return ( h('.ether-balance.ether-balance-amount', { diff --git a/old-ui/app/components/shift-list-item.js b/old-ui/app/components/shift-list-item.js index 5454a90bc438..0f30132c7821 100644 --- a/old-ui/app/components/shift-list-item.js +++ b/old-ui/app/components/shift-list-item.js @@ -3,7 +3,7 @@ const Component = require('react').Component const h = require('react-hyperscript') const connect = require('react-redux').connect const vreme = new (require('vreme'))() -const explorerLink = require('etherscan-link').createExplorerLink +const explorerLink = require('../../../ui/lib/explorer-link.js') const actions = require('../../../ui/app/actions') const addressSummary = require('../util').addressSummary diff --git a/old-ui/app/components/token-cell.js b/old-ui/app/components/token-cell.js index 19d7139bb892..5c224c2a00c0 100644 --- a/old-ui/app/components/token-cell.js +++ b/old-ui/app/components/token-cell.js @@ -1,10 +1,16 @@ const Component = require('react').Component const h = require('react-hyperscript') +const connect = require('react-redux').connect const inherits = require('util').inherits const Identicon = require('./identicon') const prefixForNetwork = require('../../lib/etherscan-prefix-for-network') -module.exports = TokenCell +module.exports = connect(mapStateToProps)(TokenCell) +function mapStateToProps (state) { + return { + settings: state.metamask.settings, + } +} inherits(TokenCell, Component) function TokenCell () { @@ -44,14 +50,22 @@ TokenCell.prototype.render = function () { TokenCell.prototype.send = function (address, event) { event.preventDefault() event.stopPropagation() - const url = tokenFactoryFor(address) + let url + if (this.props.settings && this.props.settings.blockExplorerTokenFactory) { + url = this.props.settings.blockExplorerTokenFactory + } + url = tokenFactoryFor(address, url) if (url) { navigateTo(url) } } TokenCell.prototype.view = function (address, userAddress, network, event) { - const url = etherscanLinkFor(address, userAddress, network) + let url + if (this.props.settings && this.props.settings.blockExplorerToken) { + url = this.props.settings.blockExplorerToken + } + url = etherscanLinkFor(address, userAddress, network, url) if (url) { navigateTo(url) } @@ -61,12 +75,20 @@ function navigateTo (url) { global.platform.openWindow({ url }) } -function etherscanLinkFor (tokenAddress, address, network) { +function etherscanLinkFor (tokenAddress, address, network, url) { + if (url) { + return url.replace('[[tokenAddress]]', tokenAddress).replace('[[address]]', address) + } + const prefix = prefixForNetwork(network) return `https://${prefix}etherscan.io/token/${tokenAddress}?a=${address}` } -function tokenFactoryFor (tokenAddress) { +function tokenFactoryFor (tokenAddress, url) { + if (url) { + return url.replace('[[tokenAddress]]', tokenAddress) + } + return `https://tokenfactory.surge.sh/#/token/${tokenAddress}` } diff --git a/old-ui/app/components/transaction-list-item.js b/old-ui/app/components/transaction-list-item.js index e9280419abf8..5dd8c61b5db2 100644 --- a/old-ui/app/components/transaction-list-item.js +++ b/old-ui/app/components/transaction-list-item.js @@ -5,7 +5,7 @@ const connect = require('react-redux').connect const EthBalance = require('./eth-balance') const addressSummary = require('../util').addressSummary -const explorerLink = require('etherscan-link').createExplorerLink +const explorerLink = require('../../../ui/lib/explorer-link.js') const CopyButton = require('./copyButton') const vreme = new (require('vreme'))() const Tooltip = require('./tooltip') @@ -15,13 +15,19 @@ const actions = require('../../../ui/app/actions') const TransactionIcon = require('./transaction-list-item-icon') const ShiftListItem = require('./shift-list-item') +function mapStateToProps (state) { + return { + settings: state.metamask.settings, + } +} + const mapDispatchToProps = dispatch => { return { retryTransaction: transactionId => dispatch(actions.retryTransaction(transactionId)), } } -module.exports = connect(null, mapDispatchToProps)(TransactionListItem) +module.exports = connect(mapStateToProps, mapDispatchToProps)(TransactionListItem) inherits(TransactionListItem, Component) function TransactionListItem () { @@ -79,7 +85,11 @@ TransactionListItem.prototype.render = function () { } event.stopPropagation() if (!transaction.hash || !isLinkable) return - var url = explorerLink(transaction.hash, parseInt(network)) + let url + if (this.props.settings && this.props.settings.blockExplorerTx) { + url = this.props.settings.blockExplorerTx + } + url = explorerLink(transaction.hash, parseInt(network), url) global.platform.openWindow({ url }) }, style: { diff --git a/ui/app/components/account-dropdowns.js b/ui/app/components/account-dropdowns.js index 043008a36ef1..05300cd91e20 100644 --- a/ui/app/components/account-dropdowns.js +++ b/ui/app/components/account-dropdowns.js @@ -2,7 +2,7 @@ const Component = require('react').Component const PropTypes = require('prop-types') const h = require('react-hyperscript') const actions = require('../actions') -const genAccountLink = require('etherscan-link').createAccountLink +const genAccountLink = require('../../lib/account-link.js') const connect = require('react-redux').connect const Dropdown = require('./dropdown').Dropdown const DropdownMenuItem = require('./dropdown').DropdownMenuItem @@ -188,7 +188,11 @@ class AccountDropdowns extends Component { closeMenu: () => {}, onClick: () => { const { selected, network } = this.props - const url = genAccountLink(selected, network) + let url + if (this.props.settings && this.props.settings.blockExplorerAddr) { + url = this.props.settings.blockExplorerAddr + } + url = genAccountLink(selected, network, url) global.platform.openWindow({ url }) }, }, @@ -297,6 +301,7 @@ AccountDropdowns.propTypes = { actions: PropTypes.objectOf(PropTypes.func), network: PropTypes.string, style: PropTypes.object, + settings: PropTypes.object, enableAccountOptions: PropTypes.bool, enableAccountsSelector: PropTypes.bool, t: PropTypes.func, @@ -315,10 +320,16 @@ const mapDispatchToProps = (dispatch) => { } } +function mapStateToProps (state) { + return { + settings: state.metamask.settings, + } +} + AccountDropdowns.contextTypes = { t: PropTypes.func, } module.exports = { - AccountDropdowns: connect(null, mapDispatchToProps)(AccountDropdowns), + AccountDropdowns: connect(mapStateToProps, mapDispatchToProps)(AccountDropdowns), } diff --git a/ui/app/components/account-menu/index.js b/ui/app/components/account-menu/index.js index 9c063d31e6a1..2c19192d1e13 100644 --- a/ui/app/components/account-menu/index.js +++ b/ui/app/components/account-menu/index.js @@ -40,6 +40,7 @@ function mapStateToProps (state) { selectedAddress: state.metamask.selectedAddress, isAccountMenuOpen: state.metamask.isAccountMenuOpen, keyrings: state.metamask.keyrings, + ticker: state.metamask.settings && state.metamask.settings.ticker || 'ETH', identities: state.metamask.identities, accounts: state.metamask.accounts, } @@ -152,6 +153,7 @@ AccountMenu.prototype.renderAccounts = function () { identities, accounts, selectedAddress, + ticker, keyrings, showAccountDetail, } = this.props @@ -163,8 +165,11 @@ AccountMenu.prototype.renderAccounts = function () { const isSelected = identity.address === selectedAddress const balanceValue = accounts[address] ? accounts[address].balance : '' - const formattedBalance = balanceValue ? formatBalance(balanceValue, 6) : '...' const simpleAddress = identity.address.substring(2).toLowerCase() + let formattedBalance = balanceValue ? formatBalance(balanceValue, 6) : '...' + if (ticker !== 'ETH') { + formattedBalance = formattedBalance.replace(/ETH/, ticker) + } const keyring = keyrings.find((kr) => { return kr.accounts.includes(simpleAddress) || diff --git a/ui/app/components/balance-component.js b/ui/app/components/balance-component.js index e31552f2d8f5..425d950415d3 100644 --- a/ui/app/components/balance-component.js +++ b/ui/app/components/balance-component.js @@ -20,6 +20,7 @@ function mapStateToProps (state) { return { account, network, + ticker: state.metamask.settings && state.metamask.settings.ticker || 'ETH', conversionRate: state.metamask.conversionRate, currentCurrency: state.metamask.currentCurrency, } @@ -61,11 +62,15 @@ BalanceComponent.prototype.renderTokenBalance = function () { BalanceComponent.prototype.renderBalance = function () { const props = this.props - const { shorten, account } = props + const { shorten, account, ticker } = props const balanceValue = account && account.balance const needsParse = 'needsParse' in props ? props.needsParse : true - const formattedBalance = balanceValue ? formatBalance(balanceValue, 6, needsParse) : '...' const showFiat = 'showFiat' in props ? props.showFiat : true + let formattedBalance = balanceValue ? formatBalance(balanceValue, 6, needsParse) : '...' + + if (ticker !== 'ETH') { + formattedBalance = formattedBalance.replace(/ETH/, ticker) + } if (formattedBalance === 'None' || formattedBalance === '...') { return h('div.flex-column.balance-display', {}, [ diff --git a/ui/app/components/dropdowns/components/account-dropdowns.js b/ui/app/components/dropdowns/components/account-dropdowns.js index 179b6617f96f..78e5c2ebbece 100644 --- a/ui/app/components/dropdowns/components/account-dropdowns.js +++ b/ui/app/components/dropdowns/components/account-dropdowns.js @@ -26,15 +26,18 @@ class AccountDropdowns extends Component { } renderAccounts () { - const { identities, accounts, selected, menuItemStyles, actions, keyrings } = this.props + const { identities, accounts, selected, menuItemStyles, actions, keyrings, ticker } = this.props return Object.keys(identities).map((key, index) => { const identity = identities[key] const isSelected = identity.address === selected const balanceValue = accounts[key].balance - const formattedBalance = balanceValue ? formatBalance(balanceValue, 6) : '...' const simpleAddress = identity.address.substring(2).toLowerCase() + let formattedBalance = balanceValue ? formatBalance(balanceValue, 6) : '...' + if (ticker !== 'ETH') { + formattedBalance = formattedBalance.replace(/ETH/, ticker) + } const keyring = keyrings.find((kr) => { return kr.accounts.includes(simpleAddress) || @@ -253,6 +256,11 @@ class AccountDropdowns extends Component { padding: '8px', } + let link + if (this.props.settings && this.props.settings.blockExplorerAddr) { + link = this.props.settings.blockExplorerAddr + } + return h( Dropdown, { @@ -295,7 +303,7 @@ class AccountDropdowns extends Component { closeMenu: () => {}, onClick: () => { const { selected, network } = this.props - const url = genAccountLink(selected, network) + const url = genAccountLink(selected, network, link) global.platform.openWindow({ url }) }, style: Object.assign( @@ -421,6 +429,8 @@ AccountDropdowns.propTypes = { network: PropTypes.number, // actions.showExportPrivateKeyModal: , style: PropTypes.object, + settings: PropTypes.object, + ticker: PropTypes.string, enableAccountsSelector: PropTypes.bool, enableAccountOption: PropTypes.bool, enableAccountOptions: PropTypes.bool, @@ -458,8 +468,10 @@ const mapDispatchToProps = (dispatch) => { function mapStateToProps (state) { return { + ticker: state.metamask.settings && state.metamask.settings.ticker || 'ETH', keyrings: state.metamask.keyrings, sidebarOpen: state.appState.sidebarOpen, + settings: state.metamask.settings, } } diff --git a/ui/app/components/dropdowns/token-menu-dropdown.js b/ui/app/components/dropdowns/token-menu-dropdown.js index 5a794c7c186e..4ffab823d2ec 100644 --- a/ui/app/components/dropdowns/token-menu-dropdown.js +++ b/ui/app/components/dropdowns/token-menu-dropdown.js @@ -4,7 +4,7 @@ const h = require('react-hyperscript') const inherits = require('util').inherits const connect = require('react-redux').connect const actions = require('../../actions') -const genAccountLink = require('etherscan-link').createAccountLink +const genAccountLink = require('../../../lib/account-link.js') const copyToClipboard = require('copy-to-clipboard') const { Menu, Item, CloseArea } = require('./components/menu') @@ -17,6 +17,7 @@ module.exports = connect(mapStateToProps, mapDispatchToProps)(TokenMenuDropdown) function mapStateToProps (state) { return { network: state.metamask.network, + settings: state.metamask.settings, } } @@ -67,7 +68,11 @@ TokenMenuDropdown.prototype.render = function () { h(Item, { onClick: (e) => { e.stopPropagation() - const url = genAccountLink(this.props.token.address, this.props.network) + let url + if (this.props.settings && this.props.settings.blockExplorerAddr) { + url = this.props.settings.blockExplorerAddr + } + url = genAccountLink(this.props.token.address, this.props.network, url) global.platform.openWindow({ url }) this.props.onClose() }, diff --git a/ui/app/components/eth-balance.js b/ui/app/components/eth-balance.js index c3d084bdcb22..16e9cc32956b 100644 --- a/ui/app/components/eth-balance.js +++ b/ui/app/components/eth-balance.js @@ -1,5 +1,6 @@ const { Component } = require('react') const h = require('react-hyperscript') +const connect = require('react-redux').connect const { inherits } = require('util') const { formatBalance, @@ -8,7 +9,12 @@ const { const Tooltip = require('./tooltip.js') const FiatValue = require('./fiat-value.js') -module.exports = EthBalanceComponent +module.exports = connect(mapStateToProps)(EthBalanceComponent) +function mapStateToProps (state) { + return { + ticker: state.metamask.settings && state.metamask.settings.ticker || 'ETH', + } +} inherits(EthBalanceComponent, Component) function EthBalanceComponent () { @@ -17,9 +23,12 @@ function EthBalanceComponent () { EthBalanceComponent.prototype.render = function () { const props = this.props - const { value, style, width, needsParse = true } = props + const { ticker, value, style, width, needsParse = true } = props - const formattedValue = value ? formatBalance(value, 6, needsParse) : '...' + let formattedValue = value ? formatBalance(value, 6, needsParse) : '...' + if (ticker !== 'ETH') { + formattedValue = formattedValue.replace(/ETH/, ticker) + } return ( diff --git a/ui/app/components/identicon.js b/ui/app/components/identicon.js index 4240487451ed..72660914e0b6 100644 --- a/ui/app/components/identicon.js +++ b/ui/app/components/identicon.js @@ -20,15 +20,22 @@ function IdenticonComponent () { function mapStateToProps (state) { return { + ticker: state.metamask.settings && state.metamask.settings.ticker || 'ETH', useBlockie: state.metamask.useBlockie, } } IdenticonComponent.prototype.render = function () { var props = this.props - const { className = '', address } = props + const { className = '', address, ticker } = props var diameter = props.diameter || this.defaultDiameter + // default logo + var logo = './images/eth_logo.svg' + if (ticker !== 'ETH') { + logo = `./images/${ticker.toLowerCase()}_logo.svg` + } + return address ? ( h('div', { @@ -48,7 +55,7 @@ IdenticonComponent.prototype.render = function () { ) : ( h('img.balance-icon', { - src: './images/eth_logo.svg', + src: logo, style: { height: diameter, width: diameter, diff --git a/ui/app/components/modals/account-details-modal.js b/ui/app/components/modals/account-details-modal.js index 5607cf0512bf..d2e3f6a42585 100644 --- a/ui/app/components/modals/account-details-modal.js +++ b/ui/app/components/modals/account-details-modal.js @@ -14,6 +14,7 @@ function mapStateToProps (state) { return { network: state.metamask.network, selectedIdentity: getSelectedIdentity(state), + settings: state.metamask.settings, } } @@ -53,6 +54,11 @@ AccountDetailsModal.prototype.render = function () { } = this.props const { name, address } = selectedIdentity + let link + if (this.props.settings && this.props.settings.blockExplorerAddr) { + link = this.props.settings.blockExplorerAddr + } + return h(AccountModalContainer, {}, [ h(EditableLabel, { className: 'account-modal__name', @@ -69,7 +75,7 @@ AccountDetailsModal.prototype.render = function () { h('div.account-modal-divider'), h('button.btn-primary.account-modal__button', { - onClick: () => global.platform.openWindow({ url: genAccountLink(address, network) }), + onClick: () => global.platform.openWindow({ url: genAccountLink(address, network, link) }), }, this.context.t('etherscanView')), // Holding on redesign for Export Private Key functionality diff --git a/ui/app/components/send/currency-display/currency-display.js b/ui/app/components/send/currency-display/currency-display.js index 2b8eaa41f70f..b0e75cd754f1 100644 --- a/ui/app/components/send/currency-display/currency-display.js +++ b/ui/app/components/send/currency-display/currency-display.js @@ -1,5 +1,6 @@ const Component = require('react').Component const h = require('react-hyperscript') +const connect = require('react-redux').connect const inherits = require('util').inherits const { conversionUtil, multiplyCurrencies } = require('../../../conversion-util') const { removeLeadingZeroes } = require('../send.utils') @@ -12,7 +13,12 @@ CurrencyDisplay.contextTypes = { t: PropTypes.func, } -module.exports = CurrencyDisplay +module.exports = connect(mapStateToProps)(CurrencyDisplay) +function mapStateToProps (state) { + return { + fromCurrency: state.metamask.fromCurrency, + } +} inherits(CurrencyDisplay, Component) function CurrencyDisplay () { @@ -78,7 +84,7 @@ CurrencyDisplay.prototype.getValueToRender = function ({ selectedToken, conversi } CurrencyDisplay.prototype.getConvertedValueToRender = function (nonFormattedValue) { - const { primaryCurrency, convertedCurrency, conversionRate } = this.props + const { fromCurrency, primaryCurrency, convertedCurrency, conversionRate } = this.props if (conversionRate === 0 || conversionRate === null || conversionRate === undefined) { if (nonFormattedValue !== 0) { @@ -88,7 +94,7 @@ CurrencyDisplay.prototype.getConvertedValueToRender = function (nonFormattedValu let convertedValue = conversionUtil(nonFormattedValue, { fromNumericBase: 'dec', - fromCurrency: primaryCurrency, + fromCurrency: fromCurrency || primaryCurrency, toCurrency: convertedCurrency, numberOfDecimals: 2, conversionRate, @@ -132,6 +138,7 @@ CurrencyDisplay.prototype.render = function () { const { className = 'currency-display', primaryBalanceClassName = 'currency-display__input', + fromCurrency, primaryCurrency, readOnly = false, inError = false, @@ -174,7 +181,7 @@ CurrencyDisplay.prototype.render = function () { step, }), - h('span.currency-display__currency-symbol', primaryCurrency), + h('span.currency-display__currency-symbol', fromCurrency || primaryCurrency), ]), diff --git a/ui/app/components/shift-list-item.js b/ui/app/components/shift-list-item.js index 4334aacba56a..79c9d23dcb05 100644 --- a/ui/app/components/shift-list-item.js +++ b/ui/app/components/shift-list-item.js @@ -4,7 +4,7 @@ const PropTypes = require('prop-types') const h = require('react-hyperscript') const connect = require('react-redux').connect const vreme = new (require('vreme'))() -const explorerLink = require('etherscan-link').createExplorerLink +const explorerLink = require('../../lib/explorer-link.js') const actions = require('../actions') const addressSummary = require('../util').addressSummary diff --git a/ui/app/components/token-cell.js b/ui/app/components/token-cell.js index 4100d76a59eb..af11f35cbbc1 100644 --- a/ui/app/components/token-cell.js +++ b/ui/app/components/token-cell.js @@ -19,6 +19,7 @@ function mapStateToProps (state) { contractExchangeRates: state.metamask.contractExchangeRates, conversionRate: state.metamask.conversionRate, sidebarOpen: state.appState.sidebarOpen, + settings: state.metamask.settings, } } @@ -143,7 +144,11 @@ TokenCell.prototype.send = function (address, event) { } TokenCell.prototype.view = function (address, userAddress, network, event) { - const url = etherscanLinkFor(address, userAddress, network) + let url + if (this.props.settings && this.props.settings.blockExplorerToken) { + url = this.props.settings.blockExplorerToken + } + url = etherscanLinkFor(address, userAddress, network, url) if (url) { navigateTo(url) } @@ -153,7 +158,11 @@ function navigateTo (url) { global.platform.openWindow({ url }) } -function etherscanLinkFor (tokenAddress, address, network) { +function etherscanLinkFor (tokenAddress, address, network, url) { + if (url) { + return url.replace('[[tokenAddress]]', tokenAddress).replace('[[address]]', address) + } + const prefix = prefixForNetwork(network) return `https://${prefix}etherscan.io/token/${tokenAddress}?a=${address}` } diff --git a/ui/app/components/tx-list-item.js b/ui/app/components/tx-list-item.js index 0d693b8052d4..a376c6f86204 100644 --- a/ui/app/components/tx-list-item.js +++ b/ui/app/components/tx-list-item.js @@ -32,6 +32,7 @@ module.exports = compose( function mapStateToProps (state) { return { tokens: state.metamask.tokens, + ticker: state.metamask.settings && state.metamask.settings.ticker || 'ETH', currentCurrency: getCurrentCurrency(state), contractExchangeRates: state.metamask.contractExchangeRates, selectedAddressTxList: state.metamask.selectedAddressTxList, @@ -109,6 +110,7 @@ TxListItem.prototype.getSendEtherTotal = function () { const { transactionAmount, conversionRate, + ticker, address, currentCurrency, } = this.props @@ -120,7 +122,7 @@ TxListItem.prototype.getSendEtherTotal = function () { const totalInFiat = conversionUtil(transactionAmount, { fromNumericBase: 'hex', toNumericBase: 'dec', - fromCurrency: 'ETH', + fromCurrency: ticker, toCurrency: currentCurrency, fromDenomination: 'WEI', numberOfDecimals: 2, @@ -129,15 +131,15 @@ TxListItem.prototype.getSendEtherTotal = function () { const totalInETH = conversionUtil(transactionAmount, { fromNumericBase: 'hex', toNumericBase: 'dec', - fromCurrency: 'ETH', - toCurrency: 'ETH', + fromCurrency: ticker, + toCurrency: ticker, fromDenomination: 'WEI', conversionRate, numberOfDecimals: 6, }) return { - total: `${totalInETH} ETH`, + total: `${totalInETH} ${ticker}`, fiatTotal: `${totalInFiat} ${currentCurrency.toUpperCase()}`, } } diff --git a/ui/app/components/tx-list.js b/ui/app/components/tx-list.js index 554febcff7cd..9a549e285826 100644 --- a/ui/app/components/tx-list.js +++ b/ui/app/components/tx-list.js @@ -26,6 +26,7 @@ TxList.contextTypes = { function mapStateToProps (state) { return { + settings: state.metamask.settings, txsToRender: selectors.transactionsSelector(state), conversionRate: selectors.conversionRateSelector(state), } @@ -139,7 +140,11 @@ TxList.prototype.renderTransactionListItem = function (transaction, conversionRa } TxList.prototype.view = function (txHash, network) { - const url = etherscanLinkFor(txHash, network) + let url + if (this.props.settings && this.props.settings.blockExplorerTx) { + url = this.props.settings.blockExplorerTx + } + url = etherscanLinkFor(txHash, network, url) if (url) { navigateTo(url) } @@ -149,7 +154,10 @@ function navigateTo (url) { global.platform.openWindow({ url }) } -function etherscanLinkFor (txHash, network) { +function etherscanLinkFor (txHash, network, url) { const prefix = prefixForNetwork(network) + if (url) { + return url.replace('[[txHash]]', txHash) + } return `https://${prefix}etherscan.io/tx/${txHash}` } diff --git a/ui/lib/account-link.js b/ui/lib/account-link.js index f95980d01caf..ad6d38cfbd7d 100644 --- a/ui/lib/account-link.js +++ b/ui/lib/account-link.js @@ -1,4 +1,4 @@ -module.exports = function (address, network) { +module.exports = function (address, network, url) { const net = parseInt(network) let link switch (net) { @@ -18,7 +18,10 @@ module.exports = function (address, network) { link = `https://kovan.etherscan.io/address/${address}` break default: - // FIXME + if (url) { + return url.replace('[[address]]', address) + } + link = '' break } diff --git a/ui/lib/explorer-link.js b/ui/lib/explorer-link.js new file mode 100644 index 000000000000..ce38a02f3bfd --- /dev/null +++ b/ui/lib/explorer-link.js @@ -0,0 +1,10 @@ +const prefixForNetwork = require('./etherscan-prefix-for-network') + +module.exports = function (hash, network, url) { + if (url) { + return url.replace('[[txHash]]', hash) + } + + const prefix = prefixForNetwork(network) + return `https://${prefix}etherscan.io/tx/${hash}` +} From 72ded438c24805cacb7abe7bd8f3bf6748f9b0dc Mon Sep 17 00:00:00 2001 From: hackyminer Date: Fri, 22 Jun 2018 10:10:31 +0900 Subject: [PATCH 4/6] support shapeshift exchange for deposit * fixup for testnet faucet --- app/scripts/controllers/network/networks.js | 2 + app/scripts/lib/buy-eth-url.js | 10 +++- app/scripts/metamask-controller.js | 3 +- old-ui/app/components/buy-button-subview.js | 57 ++++++++++++------- old-ui/app/components/shapeshift-form.js | 5 +- old-ui/app/components/shift-list-item.js | 3 +- ui/app/actions.js | 19 +++++-- ui/app/components/buy-button-subview.js | 6 +- ui/app/components/modals/buy-options-modal.js | 13 ++++- .../components/modals/deposit-ether-modal.js | 29 ++++++++-- ui/app/components/shapeshift-form.js | 25 +++++--- ui/app/components/shift-list-item.js | 3 +- 12 files changed, 125 insertions(+), 50 deletions(-) diff --git a/app/scripts/controllers/network/networks.js b/app/scripts/controllers/network/networks.js index f96556ce1e87..5c53b9fcef8e 100644 --- a/app/scripts/controllers/network/networks.js +++ b/app/scripts/controllers/network/networks.js @@ -15,6 +15,8 @@ networks.networkList = { 'blockExplorerToken': 'https://gastracker.io/token/[[tokenAddress]]/[[address]]', 'service': 'Ethereum Commonwealth', 'rpcUrl': 'https://etc-geth.0xinfra.com', + 'exchanges': ['ShapeShift'], + 'buyUrl': '', }, } diff --git a/app/scripts/lib/buy-eth-url.js b/app/scripts/lib/buy-eth-url.js index 4e2d0bc79429..a1df018e8d89 100644 --- a/app/scripts/lib/buy-eth-url.js +++ b/app/scripts/lib/buy-eth-url.js @@ -11,7 +11,7 @@ module.exports = getBuyEthUrl * network does not match any of the specified cases, or if no network is given, returns undefined. * */ -function getBuyEthUrl ({ network, amount, address }) { +function getBuyEthUrl ({ network, amount, address, link }) { let url switch (network) { case '1': @@ -29,6 +29,14 @@ function getBuyEthUrl ({ network, amount, address }) { case '42': url = 'https://github.com/kovan-testnet/faucet' break + + default: + if (link) { + url = link.replace('[[amount]]', amount).replace('[[address]]', address) + } else { + url = '' + } + break } return url } diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index 6dc5fcfd1a81..99c3bb519731 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -1354,7 +1354,8 @@ module.exports = class MetamaskController extends EventEmitter { buyEth (address, amount) { if (!amount) amount = '5' const network = this.networkController.getNetworkState() - const url = getBuyEthUrl({ network, address, amount }) + const link = this.networkController.getNetworkConfig().buyUrl + const url = getBuyEthUrl({ network, address, amount, link }) if (url) this.platform.openWindow({ url }) } diff --git a/old-ui/app/components/buy-button-subview.js b/old-ui/app/components/buy-button-subview.js index 8bb73ae3e9c3..ea1dba971ab4 100644 --- a/old-ui/app/components/buy-button-subview.js +++ b/old-ui/app/components/buy-button-subview.js @@ -20,6 +20,7 @@ function mapStateToProps (state) { buyView: state.appState.buyView, network: state.metamask.network, provider: state.metamask.provider, + settings: state.metamask.settings, context: state.appState.currentView.context, isSubLoading: state.appState.isSubLoading, } @@ -170,15 +171,39 @@ BuyButtonSubview.prototype.primarySubview = function () { ) default: - return ( - h('h2.error', 'Unknown network ID') - ) - + return this.mainnetSubview() } } BuyButtonSubview.prototype.mainnetSubview = function () { const props = this.props + const network = parseInt(props.network) + + let selected + if (network === 1) { + selected = [ + 'Coinbase', + 'ShapeShift', + ] + } else { + selected = this.props.settings && this.props.settings.exchanges || [] + } + + const subtext = { + 'Coinbase': 'Crypto/FIAT (USA only)', + 'ShapeShift': 'Crypto', + } + + let texts = {} + selected.forEach(ex => { + texts[ex] = subtext[ex] + }) + + if (selected.length === 0) { + return ( + h('h2.error', 'No exchange supported') + ) + } return ( @@ -198,14 +223,8 @@ BuyButtonSubview.prototype.mainnetSubview = function () { }, [ h(RadioList, { defaultFocus: props.buyView.subview, - labels: [ - 'Coinbase', - 'ShapeShift', - ], - subtext: { - 'Coinbase': 'Crypto/FIAT (USA only)', - 'ShapeShift': 'Crypto', - }, + labels: selected, + subtext: texts, onClick: this.radioHandler.bind(this), }), ]), @@ -229,13 +248,10 @@ BuyButtonSubview.prototype.mainnetSubview = function () { } BuyButtonSubview.prototype.formVersionSubview = function () { - const network = this.props.network - if (network === '1') { - if (this.props.buyView.formView.coinbase) { - return h(CoinbaseForm, this.props) - } else if (this.props.buyView.formView.shapeshift) { - return h(ShapeshiftForm, this.props) - } + if (this.props.buyView.formView.coinbase) { + return h(CoinbaseForm, this.props) + } else if (this.props.buyView.formView.shapeshift) { + return h(ShapeshiftForm, this.props) } } @@ -252,10 +268,11 @@ BuyButtonSubview.prototype.backButtonContext = function () { } BuyButtonSubview.prototype.radioHandler = function (event) { + const ticker = this.props.settings && this.props.settings.ticker || 'ETH' switch (event.target.title) { case 'Coinbase': return this.props.dispatch(actions.coinBaseSubview()) case 'ShapeShift': - return this.props.dispatch(actions.shapeShiftSubview(this.props.provider.type)) + return this.props.dispatch(actions.shapeShiftSubview(this.props.provider.type, ticker)) } } diff --git a/old-ui/app/components/shapeshift-form.js b/old-ui/app/components/shapeshift-form.js index 97068db0a64e..d68886dc2fd8 100644 --- a/old-ui/app/components/shapeshift-form.js +++ b/old-ui/app/components/shapeshift-form.js @@ -12,6 +12,7 @@ function mapStateToProps (state) { warning: state.appState.warning, isSubLoading: state.appState.isSubLoading, qrRequested: state.appState.qrRequested, + ticker: state.metamask.settings && state.metamask.settings.ticker || 'ETH', } } @@ -239,7 +240,7 @@ ShapeshiftForm.prototype.updateCoin = function (event) { var message = 'Not a valid coin' return props.dispatch(actions.displayWarning(message)) } else { - return props.dispatch(actions.pairUpdate(coin)) + return props.dispatch(actions.pairUpdate(coin, props.ticker)) } } @@ -251,7 +252,7 @@ ShapeshiftForm.prototype.handleLiveInput = function () { if (!coinOptions[coin.toUpperCase()] || coin.toUpperCase() === 'ETH') { return null } else { - return props.dispatch(actions.pairUpdate(coin)) + return props.dispatch(actions.pairUpdate(coin, props.ticker)) } } diff --git a/old-ui/app/components/shift-list-item.js b/old-ui/app/components/shift-list-item.js index 0f30132c7821..6c3901201609 100644 --- a/old-ui/app/components/shift-list-item.js +++ b/old-ui/app/components/shift-list-item.js @@ -18,6 +18,7 @@ function mapStateToProps (state) { return { conversionRate: state.metamask.conversionRate, currentCurrency: state.metamask.currentCurrency, + ticker: state.metamask.settings && state.metamask.settings.ticker || 'ETH', } } @@ -79,7 +80,7 @@ ShiftListItem.prototype.renderUtilComponents = function () { title: 'QR Code', }, [ h('i.fa.fa-qrcode.pointer.pop-hover', { - onClick: () => props.dispatch(actions.reshowQrCode(props.depositAddress, props.depositType)), + onClick: () => props.dispatch(actions.reshowQrCode(props.depositAddress, props.depositType, props.ticker)), style: { margin: '5px', marginLeft: '23px', diff --git a/ui/app/actions.js b/ui/app/actions.js index ceb12b60518f..93136078b625 100644 --- a/ui/app/actions.js +++ b/ui/app/actions.js @@ -1911,11 +1911,17 @@ function coinBaseSubview () { } } -function pairUpdate (coin) { +function pairUpdate (coin, ticker) { + if (!ticker) { + ticker = 'eth' + } else { + ticker = ticker.toLowerCase() + } + return (dispatch) => { dispatch(actions.showSubLoadingIndication()) dispatch(actions.hideWarning()) - shapeShiftRequest('marketinfo', {pair: `${coin.toLowerCase()}_eth`}, (mktResponse) => { + shapeShiftRequest('marketinfo', {pair: `${coin.toLowerCase()}_${ticker}`}, (mktResponse) => { dispatch(actions.hideSubLoadingIndication()) if (mktResponse.error) return dispatch(actions.displayWarning(mktResponse.error)) dispatch({ @@ -1928,8 +1934,11 @@ function pairUpdate (coin) { } } -function shapeShiftSubview (network) { +function shapeShiftSubview (network, ticker) { var pair = 'btc_eth' + if (ticker) { + pair = `btc_${ticker.toLowerCase()}` + } return (dispatch) => { dispatch(actions.showSubLoadingIndication()) shapeShiftRequest('marketinfo', {pair}, (mktResponse) => { @@ -1984,10 +1993,10 @@ function showQrView (data, message) { }, } } -function reshowQrCode (data, coin) { +function reshowQrCode (data, coin, ticker) { return (dispatch) => { dispatch(actions.showLoadingIndication()) - shapeShiftRequest('marketinfo', {pair: `${coin.toLowerCase()}_eth`}, (mktResponse) => { + shapeShiftRequest('marketinfo', {pair: `${coin.toLowerCase()}_${ticker.toLowerCase()}`}, (mktResponse) => { if (mktResponse.error) return dispatch(actions.displayWarning(mktResponse.error)) var message = [ diff --git a/ui/app/components/buy-button-subview.js b/ui/app/components/buy-button-subview.js index c6957d2aaa3e..a3199d5eec9f 100644 --- a/ui/app/components/buy-button-subview.js +++ b/ui/app/components/buy-button-subview.js @@ -27,6 +27,7 @@ function mapStateToProps (state) { network: state.metamask.network, provider: state.metamask.provider, context: state.appState.currentView.context, + settings: state.metamask.settings, isSubLoading: state.appState.isSubLoading, } } @@ -258,10 +259,11 @@ BuyButtonSubview.prototype.backButtonContext = function () { } BuyButtonSubview.prototype.radioHandler = function (event) { + const ticker = this.props.settings && this.props.settings.ticker || 'ETH' switch (event.target.title) { case 'Coinbase': - return this.props.dispatch(actions.coinBaseSubview()) + return this.props.dispatch(actions.coinBaseSubview(ticker)) case 'ShapeShift': - return this.props.dispatch(actions.shapeShiftSubview(this.props.provider.type)) + return this.props.dispatch(actions.shapeShiftSubview(this.props.provider.type, ticker)) } } diff --git a/ui/app/components/modals/buy-options-modal.js b/ui/app/components/modals/buy-options-modal.js index c70510b5fcde..aac9a00d874e 100644 --- a/ui/app/components/modals/buy-options-modal.js +++ b/ui/app/components/modals/buy-options-modal.js @@ -10,6 +10,7 @@ function mapStateToProps (state) { return { network: state.metamask.network, address: state.metamask.selectedAddress, + settings: state.metamask.settings, } } @@ -24,7 +25,7 @@ function mapDispatchToProps (dispatch) { showAccountDetailModal: () => { dispatch(actions.showModal({ name: 'ACCOUNT_DETAILS' })) }, - toFaucet: network => dispatch(actions.buyEth({ network })), + toFaucet: (network, address, link) => dispatch(actions.buyEth({ network, address, amount: 0, link })), } } @@ -51,9 +52,15 @@ BuyOptions.prototype.renderModalContentOption = function (title, header, onClick BuyOptions.prototype.render = function () { const { network, toCoinbase, address, toFaucet } = this.props - const isTestNetwork = ['3', '4', '42'].find(n => n === network) + let isTestNetwork = ['3', '4', '42'].find(n => n === network) const networkName = getNetworkDisplayName(network) + let link + if (this.props.settings && this.props.settings.isTestNet) { + isTestNetwork = true + link = this.props.settings.buyUrl + } + return h('div', {}, [ h('div.buy-modal-content.transfers-subview', { }, [ @@ -69,7 +76,7 @@ BuyOptions.prototype.render = function () { h('div.buy-modal-content-options.flex-column.flex-center', {}, [ isTestNetwork - ? this.renderModalContentOption(networkName, this.context.t('testFaucet'), () => toFaucet(network)) + ? this.renderModalContentOption(networkName, this.context.t('testFaucet'), () => toFaucet(network, address, link)) : this.renderModalContentOption('Coinbase', this.context.t('depositFiat'), () => toCoinbase(address)), // h('div.buy-modal-content-option', {}, [ diff --git a/ui/app/components/modals/deposit-ether-modal.js b/ui/app/components/modals/deposit-ether-modal.js index 2daa7fa1d75a..9e53deaa9fca 100644 --- a/ui/app/components/modals/deposit-ether-modal.js +++ b/ui/app/components/modals/deposit-ether-modal.js @@ -19,6 +19,7 @@ function mapStateToProps (state) { return { network: state.metamask.network, address: state.metamask.selectedAddress, + settings: state.metamask.settings, } } @@ -36,7 +37,7 @@ function mapDispatchToProps (dispatch) { showAccountDetailModal: () => { dispatch(actions.showModal({ name: 'ACCOUNT_DETAILS' })) }, - toFaucet: network => dispatch(actions.buyEth({ network })), + toFaucet: (network, address, link) => dispatch(actions.buyEth({ network, address, amount: 0, link })), } } @@ -119,9 +120,25 @@ DepositEtherModal.prototype.renderRow = function ({ DepositEtherModal.prototype.render = function () { const { network, toCoinbase, address, toFaucet } = this.props - const { buyingWithShapeshift } = this.state + let { buyingWithShapeshift } = this.state + let isTestNetwork = ['3', '4', '42'].find(n => n === network) + let noCoinbase = false + let noShapeShift = false + + if (this.props.settings && this.props.settings.exchanges) { + if (this.props.settings.exchanges.indexOf('Coinbase') === -1) { + noCoinbase = true + } + if (this.props.settings.exchanges.indexOf('ShapeShift') === -1) { + noShapeShift = true + } + } + let link + if (this.props.settings && this.props.settings.isTestNet) { + isTestNetwork = true + link = this.props.settings.buyUrl + } - const isTestNetwork = ['3', '4', '42'].find(n => n === network) const networkName = getNetworkDisplayName(network) return h('div.page-container.page-container--full-width.page-container--full-height', {}, [ @@ -164,7 +181,7 @@ DepositEtherModal.prototype.render = function () { title: FAUCET_ROW_TITLE, text: this.facuetRowText(networkName), buttonLabel: this.context.t('getEther'), - onButtonClick: () => toFaucet(network), + onButtonClick: () => toFaucet(network, address, link), hide: !isTestNetwork || buyingWithShapeshift, }), @@ -179,7 +196,7 @@ DepositEtherModal.prototype.render = function () { text: COINBASE_ROW_TEXT, buttonLabel: this.context.t('continueToCoinbase'), onButtonClick: () => toCoinbase(address), - hide: isTestNetwork || buyingWithShapeshift, + hide: noCoinbase || isTestNetwork || buyingWithShapeshift, }), this.renderRow({ @@ -192,7 +209,7 @@ DepositEtherModal.prototype.render = function () { text: SHAPESHIFT_ROW_TEXT, buttonLabel: this.context.t('shapeshiftBuy'), onButtonClick: () => this.setState({ buyingWithShapeshift: true }), - hide: isTestNetwork, + hide: noShapeShift || isTestNetwork, hideButton: buyingWithShapeshift, hideTitle: buyingWithShapeshift, onBackClick: () => this.setState({ buyingWithShapeshift: false }), diff --git a/ui/app/components/shapeshift-form.js b/ui/app/components/shapeshift-form.js index 2c4ba40bf576..0fcdd019c9c0 100644 --- a/ui/app/components/shapeshift-form.js +++ b/ui/app/components/shapeshift-form.js @@ -16,19 +16,23 @@ function mapStateToProps (state) { selectedAddress, } = state.metamask const { warning } = state.appState + const ticker = state.metamask.settings && state.metamask.settings.ticker || 'ETH' + const provider = state.metamask.provider return { coinOptions, tokenExchangeRates, selectedAddress, + provider, + ticker, warning, } } function mapDispatchToProps (dispatch) { return { - shapeShiftSubview: () => dispatch(shapeShiftSubview()), - pairUpdate: coin => dispatch(pairUpdate(coin)), + shapeShiftSubview: (type, ticker) => dispatch(shapeShiftSubview(type, ticker)), + pairUpdate: (coin, ticker) => dispatch(pairUpdate(coin, ticker)), buyWithShapeShift: data => dispatch(buyWithShapeShift(data)), } } @@ -56,22 +60,27 @@ function ShapeshiftForm () { } ShapeshiftForm.prototype.getCoinPair = function () { - return `${this.state.depositCoin.toUpperCase()}_ETH` + const ticker = this.props.ticker + return `${this.state.depositCoin.toUpperCase()}_${ticker}` } ShapeshiftForm.prototype.componentWillMount = function () { - this.props.shapeShiftSubview() + const ticker = this.props.ticker + const type = this.props.provider.type + this.props.shapeShiftSubview(type, ticker) } ShapeshiftForm.prototype.onCoinChange = function (coin) { + const ticker = this.props.ticker this.setState({ depositCoin: coin, errorMessage: '', }) - this.props.pairUpdate(coin) + this.props.pairUpdate(coin, ticker) } ShapeshiftForm.prototype.onBuyWithShapeShift = function () { + const ticker = this.props.ticker this.setState({ isLoading: true, showQrCode: true, @@ -85,7 +94,7 @@ ShapeshiftForm.prototype.onBuyWithShapeShift = function () { refundAddress: returnAddress, depositCoin, } = this.state - const pair = `${depositCoin}_eth` + const pair = `${depositCoin}_${ticker.toLowerCase()}` const data = { withdrawal, pair, @@ -175,7 +184,7 @@ ShapeshiftForm.prototype.renderQrCode = function () { ShapeshiftForm.prototype.render = function () { const { coinOptions, btnClass, warning } = this.props const { errorMessage, showQrCode, depositAddress } = this.state - const { tokenExchangeRates } = this.props + const { tokenExchangeRates, ticker } = this.props const token = tokenExchangeRates[this.getCoinPair()] return h('div.shapeshift-form-wrapper', [ @@ -209,7 +218,7 @@ ShapeshiftForm.prototype.render = function () { this.context.t('receive'), ]), - h('div.shapeshift-form__selector-input', ['ETH']), + h('div.shapeshift-form__selector-input', [ticker]), ]), diff --git a/ui/app/components/shift-list-item.js b/ui/app/components/shift-list-item.js index 79c9d23dcb05..ad4edff3789f 100644 --- a/ui/app/components/shift-list-item.js +++ b/ui/app/components/shift-list-item.js @@ -25,6 +25,7 @@ function mapStateToProps (state) { selectedAddress: state.metamask.selectedAddress, conversionRate: state.metamask.conversionRate, currentCurrency: state.metamask.currentCurrency, + ticker: state.metamask.settings && state.metamask.settings.ticker || 'ETH', } } @@ -84,7 +85,7 @@ ShiftListItem.prototype.renderUtilComponents = function () { title: this.context.t('qrCode'), }, [ h('i.fa.fa-qrcode.pointer.pop-hover', { - onClick: () => props.dispatch(actions.reshowQrCode(props.depositAddress, props.depositType)), + onClick: () => props.dispatch(actions.reshowQrCode(props.depositAddress, props.depositType, props.ticker)), style: { margin: '5px', marginLeft: '23px', From 307ba9d67a7c894623e1553d64be339003b0c2ec Mon Sep 17 00:00:00 2001 From: hackyminer Date: Thu, 19 Jul 2018 03:23:32 +0900 Subject: [PATCH 5/6] customRpc: make explorerUrl, ticker symbol configurable --- app/_locales/en/messages.json | 6 ++ app/scripts/controllers/network/network.js | 22 +++++--- app/scripts/metamask-controller.js | 4 +- old-ui/app/app.js | 6 +- old-ui/app/components/network.js | 2 +- old-ui/app/config.js | 56 +++++++++++++++++-- ui/app/actions.js | 6 +- .../components/dropdowns/network-dropdown.js | 10 ++-- ui/app/components/network.js | 2 +- ui/app/components/pages/settings/settings.js | 30 ++++++++-- 10 files changed, 111 insertions(+), 33 deletions(-) diff --git a/app/_locales/en/messages.json b/app/_locales/en/messages.json index 56228210ae3d..d23f16dca293 100644 --- a/app/_locales/en/messages.json +++ b/app/_locales/en/messages.json @@ -632,6 +632,12 @@ "optionalChainId": { "message": "ChainId (optional)" }, + "optionalBlockExplorer": { + "message": "BlockExploer Url (optional)" + }, + "optionalSymbol": { + "message": "Symbol (optional)" + }, "next": { "message": "Next" }, diff --git a/app/scripts/controllers/network/network.js b/app/scripts/controllers/network/network.js index 30890e19ac18..c4581e52d9f2 100644 --- a/app/scripts/controllers/network/network.js +++ b/app/scripts/controllers/network/network.js @@ -55,8 +55,8 @@ module.exports = class NetworkController extends EventEmitter { initializeProvider (_providerParams) { this._baseProviderParams = _providerParams - const { type, rpcTarget, chainId } = this.providerStore.getState() - this._configureProvider({ type, rpcTarget, chainId }) + const { type, rpcTarget, chainId, explorerUrl, symbol } = this.providerStore.getState() + this._configureProvider({ type, rpcTarget, chainId, explorerUrl, symbol }) this._proxy.on('block', this._logBlock.bind(this)) this._proxy.on('error', this.verifyNetwork.bind(this)) this.ethQuery = new EthQuery(this._proxy) @@ -114,11 +114,13 @@ module.exports = class NetworkController extends EventEmitter { }) } - setRpcTarget (rpcTarget, chainId) { + setRpcTarget (rpcTarget, chainId, explorerUrl, symbol) { const providerConfig = { type: 'rpc', rpcTarget, chainId, + explorerUrl, + symbol, } this.providerConfig = providerConfig } @@ -154,7 +156,7 @@ module.exports = class NetworkController extends EventEmitter { } _configureProvider (opts) { - const { type, rpcTarget, chainId } = opts + const { type, rpcTarget, chainId, explorerUrl, symbol } = opts // infura type-based endpoints const isInfura = INFURA_PROVIDER_TYPES.includes(type) if (isInfura) { @@ -167,7 +169,7 @@ module.exports = class NetworkController extends EventEmitter { this._configureStandardProvider({ rpcUrl: LOCALHOST_RPC_URL }) // url-based rpc endpoints } else if (type === 'rpc') { - this._configureStandardProvider({ rpcUrl: rpcTarget, chainId }) + this._configureStandardProvider({ rpcUrl: rpcTarget, chainId, explorerUrl, symbol }) } else { throw new Error(`NetworkController - _configureProvider - unknown type "${type}"`) } @@ -216,7 +218,7 @@ module.exports = class NetworkController extends EventEmitter { this._setProvider(provider) } - _configureStandardProvider ({ rpcUrl, chainId }) { + _configureStandardProvider ({ rpcUrl, chainId, explorerUrl, symbol }) { const providerParams = extend(this._baseProviderParams, { rpcUrl, engineParams: { @@ -227,12 +229,18 @@ module.exports = class NetworkController extends EventEmitter { networks.networkList['rpc'] = { chainId: chainId, rpcUrl, - ticker: 'ETH', + ticker: symbol || 'ETH', } // setup networkConfig var settings = { type: 'rpc', network: chainId, + ticker: symbol || 'ETH', + } + if (explorerUrl) { + settings.blockExplorerTx = explorerUrl + '/tx/[[txHash]]' + settings.blockExplorerAddr = explorerUrl + '/addr/[[address]]' + settings.blockExplorerToken = explorerUrl + '/token/[[tokenAddress]]/[[address]]' } settings = extend(settings, networks.networkList['rpc']) this.networkConfig.putState(settings) diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index 99c3bb519731..cf41f13d0466 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -1375,8 +1375,8 @@ module.exports = class MetamaskController extends EventEmitter { * @param {string} rpcTarget - A URL for a valid Ethereum RPC API. * @returns {Promise} - The RPC Target URL confirmed. */ - async setCustomRpc (rpcTarget, chainId) { - this.networkController.setRpcTarget(rpcTarget, chainId) + async setCustomRpc (rpcTarget, chainId, explorerUrl, symbol) { + this.networkController.setRpcTarget(rpcTarget, chainId, explorerUrl, symbol) await this.preferencesController.updateFrequentRpcList(rpcTarget) return rpcTarget } diff --git a/old-ui/app/app.js b/old-ui/app/app.js index 649ee29aab32..fbbdbd848d9f 100644 --- a/old-ui/app/app.js +++ b/old-ui/app/app.js @@ -642,13 +642,13 @@ App.prototype.toggleMetamaskActive = function () { } App.prototype.renderCustomOption = function (provider) { - const { rpcTarget, type } = provider + const { rpcTarget, type, explorerUrl, symbol } = provider const props = this.props if (type !== 'rpc') return null // Concatenate long URLs - let label = rpcTarget + let label = symbol ? symbol + ' Network' : rpcTarget if (rpcTarget.length > 31) { label = label.substr(0, 34) + '...' } @@ -663,7 +663,7 @@ App.prototype.renderCustomOption = function (provider) { DropdownMenuItem, { key: rpcTarget, - onClick: () => props.dispatch(actions.setRpcTarget(rpcTarget)), + onClick: () => props.dispatch(actions.setRpcTarget(rpcTarget, type, explorerUrl, symbol)), closeMenu: () => this.setState({ isNetworkMenuOpen: false }), }, [ diff --git a/old-ui/app/components/network.js b/old-ui/app/components/network.js index 4e12e2733ca5..1adf365c3efe 100644 --- a/old-ui/app/components/network.js +++ b/old-ui/app/components/network.js @@ -134,7 +134,7 @@ Network.prototype.render = function () { style: { color: '#AEAEAE', }}, - 'Private Network'), + props.provider.symbol ? props.provider.symbol + ' Network' : 'Private Network'), props.onClick && h('i.fa.fa-caret-down.fa-lg'), ]) } diff --git a/old-ui/app/config.js b/old-ui/app/config.js index 1e7fa6a3fe35..35787de91964 100644 --- a/old-ui/app/config.js +++ b/old-ui/app/config.js @@ -82,7 +82,9 @@ ConfigScreen.prototype.render = function () { var element = event.target var newRpc = element.value var chainid = document.querySelector('input#chainid') - rpcValidation(newRpc, chainid.value, state) + var explorer = document.querySelector('input#explorer') + var symbol = document.querySelector('input#symbol') + rpcValidation(newRpc, chainid.value, explorer.value, symbol.value, state) } }, }), @@ -100,7 +102,49 @@ ConfigScreen.prototype.render = function () { var element = document.querySelector('input#new_rpc') var newRpc = element.value var chainid = document.querySelector('input#chainid') - rpcValidation(newRpc, chainid.value, state) + var explorer = document.querySelector('input#explorer') + var symbol = document.querySelector('input#symbol') + rpcValidation(newRpc, chainid.value, explorer.value, symbol.value, state) + } + }, + }), + h('br'), + h('input#explorer', { + placeholder: 'Block Explorer (optional)', + style: { + width: 'inherit', + flex: '1 0 auto', + height: '30px', + margin: '8px', + }, + onKeyPress (event) { + if (event.key === 'Enter') { + var element = document.querySelector('input#new_rpc') + var newRpc = element.value + var chainid = document.querySelector('input#chainid') + var explorer = document.querySelector('input#explorer') + var symbol = document.querySelector('input#symbol') + rpcValidation(newRpc, chainid.value, explorer.value, symbol.value, state) + } + }, + }), + h('br'), + h('input#symbol', { + placeholder: 'Symbol (optional)', + style: { + width: 'inherit', + flex: '1 0 auto', + height: '30px', + margin: '8px', + }, + onKeyPress (event) { + if (event.key === 'Enter') { + var element = document.querySelector('input#new_rpc') + var newRpc = element.value + var chainid = document.querySelector('input#chainid') + var explorer = document.querySelector('input#explorer') + var symbol = document.querySelector('input#symbol') + rpcValidation(newRpc, chainid.value, explorer.value, symbol.value, state) } }, }), @@ -113,7 +157,9 @@ ConfigScreen.prototype.render = function () { var element = document.querySelector('input#new_rpc') var newRpc = element.value var chainid = document.querySelector('input#chainid') - rpcValidation(newRpc, chainid.value, state) + var explorer = document.querySelector('input#explorer') + var symbol = document.querySelector('input#symbol') + rpcValidation(newRpc, chainid.value, explorer.value, symbol.value, state) }, }, 'Save'), ]), @@ -209,9 +255,9 @@ ConfigScreen.prototype.render = function () { ) } -function rpcValidation (newRpc, chainid, state) { +function rpcValidation (newRpc, chainid, explorer, symbol, state) { if (validUrl.isWebUri(newRpc)) { - state.dispatch(actions.setRpcTarget(newRpc, chainid)) + state.dispatch(actions.setRpcTarget(newRpc, chainid, explorer, symbol)) } else { var appendedRpc = `http://${newRpc}` if (validUrl.isWebUri(appendedRpc)) { diff --git a/ui/app/actions.js b/ui/app/actions.js index 93136078b625..1b609ff89714 100644 --- a/ui/app/actions.js +++ b/ui/app/actions.js @@ -1665,10 +1665,10 @@ function updateProviderType (type) { } } -function setRpcTarget (newRpc, chainId) { +function setRpcTarget (newRpc, chainId, explorerUrl, symbol) { return (dispatch) => { - log.debug(`background.setRpcTarget: ${newRpc}`) - background.setCustomRpc(newRpc, chainId, (err, result) => { + log.debug(`background.setRpcTarget: ${newRpc}, ${chainId}, ${explorerUrl}, ${symbol} `) + background.setCustomRpc(newRpc, chainId, explorerUrl, symbol, (err, result) => { if (err) { log.error(err) return dispatch(self.displayWarning('Had a problem changing networks!')) diff --git a/ui/app/components/dropdowns/network-dropdown.js b/ui/app/components/dropdowns/network-dropdown.js index e4efbbd42a1f..f4340c7a0636 100644 --- a/ui/app/components/dropdowns/network-dropdown.js +++ b/ui/app/components/dropdowns/network-dropdown.js @@ -41,8 +41,8 @@ function mapDispatchToProps (dispatch) { setDefaultRpcTarget: type => { dispatch(actions.setDefaultRpcTarget(type)) }, - setRpcTarget: (target, network) => { - dispatch(actions.setRpcTarget(target, network)) + setRpcTarget: (target, network, explorerUrl, symbol) => { + dispatch(actions.setRpcTarget(target, network, explorerUrl, symbol)) }, showNetworkDropdown: () => dispatch(actions.showNetworkDropdown()), hideNetworkDropdown: () => dispatch(actions.hideNetworkDropdown()), @@ -333,7 +333,7 @@ NetworkDropdown.prototype.renderCommonRpc = function (rpcList, provider) { } NetworkDropdown.prototype.renderCustomOption = function (provider) { - const { rpcTarget, type } = provider + const { rpcTarget, type, explorerUrl, symbol } = provider const props = this.props const network = props.network @@ -349,7 +349,7 @@ NetworkDropdown.prototype.renderCustomOption = function (provider) { DropdownMenuItem, { key: rpcTarget, - onClick: () => props.setRpcTarget(rpcTarget, network), + onClick: () => props.setRpcTarget(rpcTarget, network, explorerUrl, symbol), closeMenu: () => this.props.hideNetworkDropdown(), style: { fontFamily: 'DIN OT', @@ -365,7 +365,7 @@ NetworkDropdown.prototype.renderCustomOption = function (provider) { style: { color: '#ffffff', }, - }, rpcTarget), + }, symbol ? symbol + ' Network' : rpcTarget), ] ) } diff --git a/ui/app/components/network.js b/ui/app/components/network.js index 832f5f1c8abc..a40d0ad8a605 100644 --- a/ui/app/components/network.js +++ b/ui/app/components/network.js @@ -144,7 +144,7 @@ Network.prototype.render = function () { }, }), - h('.network-name', context.t('privateNetwork')), + h('.network-name', props.provider.symbol ? props.provider.symbol + ' Network' : context.t('privateNetwork')), h('i.fa.fa-chevron-down.fa-lg.network-caret'), ]) } diff --git a/ui/app/components/pages/settings/settings.js b/ui/app/components/pages/settings/settings.js index 38d8729ac938..de7d881dcecc 100644 --- a/ui/app/components/pages/settings/settings.js +++ b/ui/app/components/pages/settings/settings.js @@ -173,7 +173,7 @@ class Settings extends Component { onChange: event => this.setState({ newRpc: event.target.value }), onKeyPress: event => { if (event.key === 'Enter') { - this.validateRpc(this.state.newRpc, this.state.chainId) + this.validateRpc(this.state.newRpc, this.state.chainId, this.state.explorerUrl, this.state.symbol) } }, }), @@ -182,14 +182,32 @@ class Settings extends Component { onChange: event => this.setState({ chainId: event.target.value }), onKeyPress: event => { if (event.key === 'Enter') { - this.validateRpc(this.state.newRpc, this.state.chainId) + this.validateRpc(this.state.newRpc, this.state.chainId, this.state.explorerUrl, this.state.symbol) + } + }, + }), + h('input.settings__input', { + placeholder: this.context.t('optionalBlockExplorer'), + onChange: event => this.setState({ explorerUrl: event.target.value }), + onKeyPress: event => { + if (event.key === 'Enter') { + this.validateRpc(this.state.newRpc, this.state.chainId, this.state.explorerUrl, this.state.symbol) + } + }, + }), + h('input.settings__input', { + placeholder: this.context.t('optionalSymbol'), + onChange: event => this.setState({ symbol: event.target.value }), + onKeyPress: event => { + if (event.key === 'Enter') { + this.validateRpc(this.state.newRpc, this.state.chainId, this.state.explorerUrl, this.state.symbol) } }, }), h('div.settings__rpc-save-button', { onClick: event => { event.preventDefault() - this.validateRpc(this.state.newRpc, this.state.chainId) + this.validateRpc(this.state.newRpc, this.state.chainId, this.state.explorerUrl, this.state.symbol) }, }, this.context.t('save')), ]), @@ -198,11 +216,11 @@ class Settings extends Component { ) } - validateRpc (newRpc, chainId) { + validateRpc (newRpc, chainId, explorerUrl, symbol) { const { setRpcTarget, displayWarning } = this.props if (validUrl.isWebUri(newRpc)) { - setRpcTarget(newRpc, chainId) + setRpcTarget(newRpc, chainId, explorerUrl, symbol) } else { const appendedRpc = `http://${newRpc}` @@ -350,7 +368,7 @@ const mapStateToProps = state => { const mapDispatchToProps = dispatch => { return { setCurrentCurrency: currency => dispatch(actions.setCurrentCurrency(currency)), - setRpcTarget: (newRpc, chainId) => dispatch(actions.setRpcTarget(newRpc, chainId)), + setRpcTarget: (newRpc, chainId, explorer, symbol) => dispatch(actions.setRpcTarget(newRpc, chainId, explorer, symbol)), displayWarning: warning => dispatch(actions.displayWarning(warning)), revealSeedConfirmation: () => dispatch(actions.revealSeedConfirmation()), setUseBlockie: value => dispatch(actions.setUseBlockie(value)), From a19718d3f49f45574e5fbf64ab7708bb5be2663d Mon Sep 17 00:00:00 2001 From: hackyminer Date: Fri, 20 Jul 2018 22:13:47 +0900 Subject: [PATCH 6/6] save FrequentRpcList correctly. increse the size of the RpcList --- app/_locales/en/messages.json | 3 +++ app/scripts/controllers/preferences.js | 14 ++++++------- old-ui/app/app.js | 18 +++++++++++++---- .../components/dropdowns/network-dropdown.js | 20 +++++++++++++------ 4 files changed, 38 insertions(+), 17 deletions(-) diff --git a/app/_locales/en/messages.json b/app/_locales/en/messages.json index d23f16dca293..9b5236d7b5db 100644 --- a/app/_locales/en/messages.json +++ b/app/_locales/en/messages.json @@ -604,6 +604,9 @@ "negativeETH": { "message": "Can not send negative amounts of ETH." }, + "networkName": { + "message": "$1 Network" + }, "networks": { "message": "Networks" }, diff --git a/app/scripts/controllers/preferences.js b/app/scripts/controllers/preferences.js index f6250dc16b57..7077ea876a63 100644 --- a/app/scripts/controllers/preferences.js +++ b/app/scripts/controllers/preferences.js @@ -287,8 +287,8 @@ class PreferencesController { * @returns {Promise} Promise resolves with undefined * */ - updateFrequentRpcList (_url) { - return this.addToFrequentRpcList(_url) + updateFrequentRpcList (url, chainid, explorer, symbol) { + return this.addToFrequentRpcList(url, chainid, explorer, symbol) .then((rpcList) => { this.store.updateState({ frequentRpcList: rpcList }) return Promise.resolve() @@ -318,16 +318,16 @@ class PreferencesController { * @returns {Promise} The updated frequentRpcList. * */ - addToFrequentRpcList (_url) { + addToFrequentRpcList (url, chainid, explorer, symbol) { const rpcList = this.getFrequentRpcList() - const index = rpcList.findIndex((element) => { return element === _url }) + const index = rpcList.findIndex((element) => { return typeof element === 'object' ? element.url === url : element === url }) if (index !== -1) { rpcList.splice(index, 1) } - if (_url !== 'http://localhost:8545') { - rpcList.push(_url) + if (url !== 'http://localhost:8545') { + rpcList.push({ url, chainid, explorer, symbol }) } - if (rpcList.length > 2) { + if (rpcList.length > 3) { rpcList.shift() } return Promise.resolve(rpcList) diff --git a/old-ui/app/app.js b/old-ui/app/app.js index fbbdbd848d9f..41ca4d760ba6 100644 --- a/old-ui/app/app.js +++ b/old-ui/app/app.js @@ -649,7 +649,7 @@ App.prototype.renderCustomOption = function (provider) { // Concatenate long URLs let label = symbol ? symbol + ' Network' : rpcTarget - if (rpcTarget.length > 31) { + if (rpcTarget && rpcTarget.length > 31) { label = label.substr(0, 34) + '...' } @@ -702,7 +702,17 @@ App.prototype.renderCommonRpc = function (rpcList, provider) { const props = this.props const rpcTarget = provider.rpcTarget - return rpcList.map((rpc) => { + return rpcList.map((entry) => { + var rpc, chainid, explorer, symbol + if (typeof entry === 'object') { + rpc = entry.url + chainid = entry.chainid + explorer = entry.explorer + symbol = entry.symbol + } else { + rpc = entry + chainid = explorer = symbol = null + } if ((rpc === 'http://localhost:8545') || (rpc === rpcTarget)) { return null } else { @@ -711,11 +721,11 @@ App.prototype.renderCommonRpc = function (rpcList, provider) { { key: `common${rpc}`, closeMenu: () => this.setState({ isNetworkMenuOpen: false }), - onClick: () => props.dispatch(actions.setRpcTarget(rpc)), + onClick: () => props.dispatch(actions.setRpcTarget(rpc, chainid, explorer, symbol)), }, [ h('i.fa.fa-question-circle.fa-lg.menu-icon'), - rpc, + symbol ? symbol + ' Network' : rpc, rpcTarget === rpc ? h('.check', '✓') : null, ] ) diff --git a/ui/app/components/dropdowns/network-dropdown.js b/ui/app/components/dropdowns/network-dropdown.js index f4340c7a0636..67fd1e995256 100644 --- a/ui/app/components/dropdowns/network-dropdown.js +++ b/ui/app/components/dropdowns/network-dropdown.js @@ -299,9 +299,18 @@ NetworkDropdown.prototype.getNetworkName = function () { NetworkDropdown.prototype.renderCommonRpc = function (rpcList, provider) { const props = this.props const rpcTarget = provider.rpcTarget - const network = props.network - return rpcList.map((rpc) => { + return rpcList.map((entry) => { + var rpc, chainid, explorer, symbol + if (typeof entry === 'object') { + rpc = entry.url + chainid = entry.chainid + explorer = entry.explorer + symbol = entry.symbol + } else { + rpc = entry + chainid = explorer = symbol = null + } if ((rpc === 'http://localhost:8545') || (rpc === rpcTarget)) { return null } else { @@ -310,7 +319,7 @@ NetworkDropdown.prototype.renderCommonRpc = function (rpcList, provider) { { key: `common${rpc}`, closeMenu: () => this.props.hideNetworkDropdown(), - onClick: () => props.setRpcTarget(rpc, network), + onClick: () => props.setRpcTarget(rpc, chainid, explorer, symbol), style: { fontFamily: 'DIN OT', fontSize: '16px', @@ -325,7 +334,7 @@ NetworkDropdown.prototype.renderCommonRpc = function (rpcList, provider) { style: { color: rpcTarget === rpc ? '#ffffff' : '#9b9b9b', }, - }, rpc), + }, symbol ? this.context.t('networkName', [symbol]) : rpc), ] ) } @@ -335,7 +344,6 @@ NetworkDropdown.prototype.renderCommonRpc = function (rpcList, provider) { NetworkDropdown.prototype.renderCustomOption = function (provider) { const { rpcTarget, type, explorerUrl, symbol } = provider const props = this.props - const network = props.network if (type !== 'rpc') return null @@ -349,7 +357,7 @@ NetworkDropdown.prototype.renderCustomOption = function (provider) { DropdownMenuItem, { key: rpcTarget, - onClick: () => props.setRpcTarget(rpcTarget, network, explorerUrl, symbol), + onClick: () => props.setRpcTarget(rpcTarget, type, explorerUrl, symbol), closeMenu: () => this.props.hideNetworkDropdown(), style: { fontFamily: 'DIN OT',