diff --git a/app/_locales/en/messages.json b/app/_locales/en/messages.json
index 2656432d2a58..7beb4b8b946c 100644
--- a/app/_locales/en/messages.json
+++ b/app/_locales/en/messages.json
@@ -635,6 +635,9 @@
"newRPC": {
"message": "New RPC URL"
},
+ "optionalChainId": {
+ "message": "ChainId (optional)"
+ },
"next": {
"message": "Next"
},
@@ -817,6 +820,9 @@
"ropsten": {
"message": "Ropsten Test Network"
},
+ "classic": {
+ "message": "Ethereum Classic Network"
+ },
"rpc": {
"message": "Custom RPC"
},
@@ -835,6 +841,9 @@
"connectingToRinkeby": {
"message": "Connecting to Rinkeby Test Network"
},
+ "connectingToClassic": {
+ "message": "Connecting to Ethereum Classic Network"
+ },
"connectingToUnknown": {
"message": "Connecting to Unknown Network"
},
@@ -1138,6 +1147,10 @@
"usedByClients": {
"message": "Used by a variety of different clients"
},
+ "useMultiChainMenu": {
+ "message": "Use MultiChain menu",
+ "description": "show available MultiChains in the dropdown menu"
+ },
"useOldUI": {
"message": "Use old UI"
},
diff --git a/app/images/etc_logo.svg b/app/images/etc_logo.svg
new file mode 100644
index 000000000000..13bc35429fe0
--- /dev/null
+++ b/app/images/etc_logo.svg
@@ -0,0 +1,20 @@
+
+
diff --git a/app/scripts/controllers/currency.js b/app/scripts/controllers/currency.js
index d5bc5fe2bb9a..b80e1344b190 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({ ticker: fromCurrency, 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/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 c1667d9a677d..c3f80e9a3060 100644
--- a/app/scripts/controllers/network/network.js
+++ b/app/scripts/controllers/network/network.js
@@ -11,15 +11,19 @@ const createInfuraClient = require('./createInfuraClient')
const createJsonRpcClient = require('./createJsonRpcClient')
const createLocalhostClient = require('./createLocalhostClient')
const { createSwappableProxy, createEventEmitterProxy } = require('swappable-obj-proxy')
+const networks = require('./networks')
+const extend = require('xtend')
const {
ROPSTEN,
RINKEBY,
KOVAN,
MAINNET,
+ CLASSIC,
LOCALHOST,
} = require('./enums')
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
@@ -29,6 +33,10 @@ const defaultProviderConfig = {
type: testMode ? RINKEBY : MAINNET,
}
+const defaultNetworkConfig = {
+ ticker: 'ETH',
+}
+
module.exports = class NetworkController extends EventEmitter {
constructor (opts = {}) {
@@ -39,7 +47,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 })
this.on('networkDidChange', this.lookupNetwork)
// provider and block tracker
this._provider = null
@@ -51,8 +60,8 @@ 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.lookupNetwork()
}
@@ -72,7 +81,20 @@ module.exports = class NetworkController extends EventEmitter {
return this.networkStore.getState()
}
- setNetworkState (network) {
+ getNetworkConfig () {
+ return this.networkConfig.getState()
+ }
+
+ 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
return this.networkStore.putState(network)
}
@@ -85,25 +107,27 @@ module.exports = class NetworkController extends EventEmitter {
if (!this._provider) {
return log.warn('NetworkController - lookupNetwork aborted due to missing provider')
}
+ var { type } = this.providerStore.getState()
const ethQuery = new EthQuery(this._provider)
ethQuery.sendAsync({ method: 'net_version' }, (err, network) => {
if (err) return this.setNetworkState('loading')
log.info('web3.getNetwork returned ' + network)
- this.setNetworkState(network)
+ this.setNetworkState(network, type)
})
}
- 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
}
@@ -132,17 +156,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._configureLocalhostProvider()
// 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}"`)
}
@@ -152,6 +179,11 @@ module.exports = class NetworkController extends EventEmitter {
log.info('NetworkController - configureInfuraProvider', type)
const networkClient = createInfuraClient({ network: type })
this._setNetworkClient(networkClient)
+ // setup networkConfig
+ var settings = {
+ ticker: 'ETH',
+ }
+ this.networkConfig.putState(settings)
}
_configureLocalhostProvider () {
@@ -160,9 +192,34 @@ module.exports = class NetworkController extends EventEmitter {
this._setNetworkClient(networkClient)
}
- _configureStandardProvider ({ rpcUrl }) {
+ _configurePredefinedProvider ({ type }) {
+ log.info('NetworkController - configurePredefinedProvider', type)
+ // setup networkConfig
+ var settings = {
+ network: networks.networkList[type].chainId,
+ }
+ settings = extend(settings, networks.networkList[type])
+ const rpcUrl = networks.networkList[type].rpcUrl
+ const networkClient = createJsonRpcClient({ rpcUrl })
+ this.networkConfig.putState(settings)
+ this._setNetworkClient(networkClient)
+ }
+
+ _configureStandardProvider ({ rpcUrl, chainId }) {
log.info('NetworkController - configureStandardProvider', rpcUrl)
const networkClient = createJsonRpcClient({ rpcUrl })
+ // hack to add a 'rpc' network with chainId
+ networks.networkList['rpc'] = {
+ chainId: chainId,
+ rpcUrl,
+ ticker: 'ETH',
+ }
+ // setup networkConfig
+ var settings = {
+ network: chainId,
+ }
+ settings = extend(settings, networks.networkList['rpc'])
+ this.networkConfig.putState(settings)
this._setNetworkClient(networkClient)
}
diff --git a/app/scripts/controllers/network/networks.js b/app/scripts/controllers/network/networks.js
new file mode 100644
index 000000000000..9b188980df95
--- /dev/null
+++ b/app/scripts/controllers/network/networks.js
@@ -0,0 +1,23 @@
+'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': 'ETC Cooperative',
+ 'rpcUrl': 'https://ethereumclassic.network',
+ 'exchanges': ['ShapeShift'],
+ 'buyUrl': '',
+ },
+}
+
+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/controllers/preferences.js b/app/scripts/controllers/preferences.js
index 1b85e4fd152c..b30642e344df 100644
--- a/app/scripts/controllers/preferences.js
+++ b/app/scripts/controllers/preferences.js
@@ -23,7 +23,7 @@ class PreferencesController {
*/
constructor (opts = {}) {
const initState = extend({
- frequentRpcList: [],
+ frequentRpcListDetail: [],
currentAccountTab: 'history',
accountTokens: {},
tokens: [],
@@ -61,6 +61,27 @@ class PreferencesController {
return this.store.getState().useBlockie
}
+ /**
+ * Setter for the `useMultiChain` property
+ *
+ * @param {boolean} val Whether or not the user prefers multichain menu
+ *
+ */
+ setUseMultiChain (val) {
+ this.store.updateState({ useMultiChain: val })
+ }
+
+ /**
+ * Getter for the `useMultiChain` property
+ *
+ * @returns {boolean} this.store.useMultiChain
+ *
+ */
+ getUseMultiChain () {
+ return this.store.getState().useMultiChain
+ }
+
+
/**
* Setter for the `currentLocale` property
*
@@ -298,10 +319,10 @@ class PreferencesController {
* @returns {Promise} Promise resolves with undefined
*
*/
- updateFrequentRpcList (_url) {
- return this.addToFrequentRpcList(_url)
+ updateFrequentRpcList (_url, chainId) {
+ return this.addToFrequentRpcList(_url, chainId)
.then((rpcList) => {
- this.store.updateState({ frequentRpcList: rpcList })
+ this.store.updateState({ frequentRpcListDetail: rpcList })
return Promise.resolve()
})
}
@@ -329,14 +350,14 @@ class PreferencesController {
* @returns {Promise} The updated frequentRpcList.
*
*/
- addToFrequentRpcList (_url) {
- const rpcList = this.getFrequentRpcList()
- const index = rpcList.findIndex((element) => { return element === _url })
+ addToFrequentRpcList (_url, chainId) {
+ const rpcList = this.getFrequentRpcListDetail()
+ const index = rpcList.findIndex((element) => { return element.rpcUrl === _url })
if (index !== -1) {
rpcList.splice(index, 1)
}
if (_url !== 'http://localhost:8545') {
- rpcList.push(_url)
+ rpcList.push({rpcUrl : _url, chainId })
}
if (rpcList.length > 3) {
rpcList.shift()
@@ -345,13 +366,13 @@ class PreferencesController {
}
/**
- * Getter for the `frequentRpcList` property.
+ * Getter for the `frequentRpcListDetail` property.
*
- * @returns {array} An array of one or two rpc urls.
+ * @returns {array} An array of rpc urls.
*
*/
- getFrequentRpcList () {
- return this.store.getState().frequentRpcList
+ getFrequentRpcListDetail () {
+ return this.store.getState().frequentRpcListDetail
}
/**
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 a6215d51be2e..865e86ff511f 100644
--- a/app/scripts/metamask-controller.js
+++ b/app/scripts/metamask-controller.js
@@ -193,6 +193,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()
@@ -349,6 +351,7 @@ module.exports = class MetamaskController extends EventEmitter {
getState: (cb) => cb(null, this.getState()),
setCurrentCurrency: this.setCurrentCurrency.bind(this),
setUseBlockie: this.setUseBlockie.bind(this),
+ setUseMultiChain: this.setUseMultiChain.bind(this),
setCurrentLocale: this.setCurrentLocale.bind(this),
markAccountsFound: this.markAccountsFound.bind(this),
markPasswordForgotten: this.markPasswordForgotten.bind(this),
@@ -1368,10 +1371,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 || 'ETH',
conversionRate: this.currencyController.getConversionRate(),
currentCurrency: this.currencyController.getCurrentCurrency(),
conversionDate: this.currencyController.getConversionDate(),
@@ -1392,7 +1398,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 })
}
@@ -1412,9 +1419,9 @@ 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)
- await this.preferencesController.updateFrequentRpcList(rpcTarget)
+ async setCustomRpc (rpcTarget, chainId) {
+ this.networkController.setRpcTarget(rpcTarget, chainId)
+ await this.preferencesController.updateFrequentRpcList(rpcTarget, chainId)
return rpcTarget
}
@@ -1432,6 +1439,20 @@ module.exports = class MetamaskController extends EventEmitter {
}
}
+ /**
+ * Sets whether or not to use the multichain dropdown-menu.
+ * @param {boolean} val - True to show multichains, false to disable multichain menu.
+ * @param {Function} cb - A callback function called when complete.
+ */
+ setUseMultiChain (val, cb) {
+ try {
+ this.preferencesController.setUseMultiChain(val)
+ cb(null)
+ } catch (err) {
+ cb(err)
+ }
+ }
+
/**
* A method for setting a user's current locale, affecting the language rendered.
* @param {string} key - Locale identifier.
diff --git a/old-ui/app/app.js b/old-ui/app/app.js
index d3e9e823b0d5..d2689330d20a 100644
--- a/old-ui/app/app.js
+++ b/old-ui/app/app.js
@@ -72,7 +72,7 @@ function mapStateToProps (state) {
forgottenPassword: state.appState.forgottenPassword,
nextUnreadNotice: state.metamask.nextUnreadNotice,
lostAccounts: state.metamask.lostAccounts,
- frequentRpcList: state.metamask.frequentRpcList || [],
+ frequentRpcListDetail: state.metamask.frequentRpcListDetail || [],
featureFlags,
// state needed to get account dropdown temporarily rendering from app bar
@@ -298,6 +298,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/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/app-bar.js b/old-ui/app/components/app-bar.js
index 234c06a01791..6053c5e84441 100644
--- a/old-ui/app/components/app-bar.js
+++ b/old-ui/app/components/app-bar.js
@@ -17,7 +17,7 @@ module.exports = class AppBar extends Component {
static propTypes = {
dispatch: PropTypes.func.isRequired,
- frequentRpcList: PropTypes.array.isRequired,
+ frequentRpcListDetail: PropTypes.array.isRequired,
isMascara: PropTypes.bool.isRequired,
isOnboarding: PropTypes.bool.isRequired,
identities: PropTypes.any.isRequired,
@@ -26,6 +26,7 @@ module.exports = class AppBar extends Component {
network: PropTypes.any.isRequired,
keyrings: PropTypes.any.isRequired,
provider: PropTypes.any.isRequired,
+ useMultiChain: PropTypes.bool.isRequired,
}
static renderSpace () {
@@ -196,7 +197,8 @@ module.exports = class AppBar extends Component {
renderNetworkDropdown () {
const {
dispatch,
- frequentRpcList: rpcList,
+ useMultiChain,
+ frequentRpcListDetail: rpcList,
provider,
} = this.props
const {
@@ -205,6 +207,8 @@ module.exports = class AppBar extends Component {
} = provider
const isOpen = this.state.isNetworkMenuOpen
+ const showMultiChain = useMultiChain ? 'flex' : 'none'
+
return h(Dropdown, {
useCssTransition: true,
isOpen,
@@ -287,6 +291,21 @@ module.exports = class AppBar extends Component {
? h('.check', '✓')
: null,
]),
+ h(DropdownMenuItem, {
+ key: 'classic',
+ closeMenu: () => this.setState({ isNetworkMenuOpen: !isOpen }),
+ onClick: () => dispatch(actions.setProviderType('classic')),
+ style: {
+ fontSize: '18px',
+ display: showMultiChain,
+ },
+ }, [
+ h('.menu-icon.diamond'),
+ 'Ethereum Classic Network',
+ providerType === 'classic'
+ ? h('.check', '✓')
+ : null,
+ ]),
h(DropdownMenuItem, {
key: 'default',
closeMenu: () => this.setState({ isNetworkMenuOpen: !isOpen }),
@@ -322,7 +341,7 @@ module.exports = class AppBar extends Component {
}
renderCustomOption ({ rpcTarget, type }) {
- const {dispatch} = this.props
+ const {dispatch, network} = this.props
if (type !== 'rpc') {
return null
@@ -340,7 +359,7 @@ module.exports = class AppBar extends Component {
default:
return h(DropdownMenuItem, {
key: rpcTarget,
- onClick: () => dispatch(actions.setRpcTarget(rpcTarget)),
+ onClick: () => dispatch(actions.setRpcTarget(rpcTarget, network)),
closeMenu: () => this.setState({ isNetworkMenuOpen: false }),
}, [
h('i.fa.fa-question-circle.fa-lg.menu-icon'),
@@ -354,7 +373,8 @@ module.exports = class AppBar extends Component {
const {dispatch} = this.props
const reversedRpcList = rpcList.slice().reverse()
- return reversedRpcList.map((rpc) => {
+ return reversedRpcList.map((entry) => {
+ const rpc = entry.rpcUrl
const currentRpcTarget = provider.type === 'rpc' && rpc === provider.rpcTarget
if ((rpc === LOCALHOST_RPC_URL) || currentRpcTarget) {
@@ -363,7 +383,7 @@ module.exports = class AppBar extends Component {
return h(DropdownMenuItem, {
key: `common${rpc}`,
closeMenu: () => this.setState({ isNetworkMenuOpen: false }),
- onClick: () => dispatch(actions.setRpcTarget(rpc)),
+ onClick: () => dispatch(actions.setRpcTarget(rpc, entry.chainId)),
}, [
h('i.fa.fa-question-circle.fa-lg.menu-icon'),
rpc,
diff --git a/old-ui/app/components/balance.js b/old-ui/app/components/balance.js
index 57ca845649ee..d1fbc2c78b7a 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.ticker,
+ }
+}
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/buy-button-subview.js b/old-ui/app/components/buy-button-subview.js
index 8bb73ae3e9c3..a7e29859c569 100644
--- a/old-ui/app/components/buy-button-subview.js
+++ b/old-ui/app/components/buy-button-subview.js
@@ -20,6 +20,8 @@ function mapStateToProps (state) {
buyView: state.appState.buyView,
network: state.metamask.network,
provider: state.metamask.provider,
+ ticker: state.metamask.ticker,
+ settings: state.metamask.settings,
context: state.appState.currentView.context,
isSubLoading: state.appState.isSubLoading,
}
@@ -170,15 +172,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.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 +224,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 +249,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 +269,11 @@ BuyButtonSubview.prototype.backButtonContext = function () {
}
BuyButtonSubview.prototype.radioHandler = function (event) {
+ const ticker = this.props.ticker
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/eth-balance.js b/old-ui/app/components/eth-balance.js
index 4f538fd31f31..38219d00ebea 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.ticker,
+ }
+}
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/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/components/shapeshift-form.js b/old-ui/app/components/shapeshift-form.js
index 14de309aba74..e17f53ff9c93 100644
--- a/old-ui/app/components/shapeshift-form.js
+++ b/old-ui/app/components/shapeshift-form.js
@@ -10,6 +10,7 @@ function mapStateToProps (state) {
return {
warning: state.appState.warning,
isSubLoading: state.appState.isSubLoading,
+ ticker: state.metamask.ticker,
}
}
@@ -237,7 +238,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))
}
}
@@ -249,7 +250,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 5454a90bc438..517219d23bc7 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
@@ -18,6 +18,7 @@ function mapStateToProps (state) {
return {
conversionRate: state.metamask.conversionRate,
currentCurrency: state.metamask.currentCurrency,
+ ticker: state.metamask.ticker,
}
}
@@ -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/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 6ecf7d193c08..d9b8257bccb4 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 () {
@@ -88,7 +94,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/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/test/unit/app/controllers/network-contoller-test.js b/test/unit/app/controllers/network-contoller-test.js
index 822311931e67..2721898bdb01 100644
--- a/test/unit/app/controllers/network-contoller-test.js
+++ b/test/unit/app/controllers/network-contoller-test.js
@@ -47,7 +47,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')
})
@@ -80,6 +80,9 @@ describe('Network utils', () => {
}, {
input: 42,
expected: 'Kovan',
+ }, {
+ input: 61,
+ expected: 'Ethereum Classic',
}, {
input: 'ropsten',
expected: 'Ropsten',
@@ -89,6 +92,9 @@ describe('Network utils', () => {
}, {
input: 'kovan',
expected: 'Kovan',
+ }, {
+ input: 'classic',
+ expected: 'Ethereum Classic',
}, {
input: 'mainnet',
expected: 'Main Ethereum Network',
diff --git a/test/unit/components/balance-component-test.js b/test/unit/components/balance-component-test.js
index 81e6fdf9eb37..0b3fe7c61dd7 100644
--- a/test/unit/components/balance-component-test.js
+++ b/test/unit/components/balance-component-test.js
@@ -8,6 +8,7 @@ const mockState = {
accounts: { abc: {} },
network: 1,
selectedAddress: 'abc',
+ ticker: 'ETH',
},
}
diff --git a/ui/app/actions.js b/ui/app/actions.js
index 6bcc64e17aae..0f29f8ab54dd 100644
--- a/ui/app/actions.js
+++ b/ui/app/actions.js
@@ -288,6 +288,9 @@ var actions = {
SET_USE_BLOCKIE: 'SET_USE_BLOCKIE',
setUseBlockie,
+ SET_USE_MULTICHAIN: 'SET_USE_MULTICHAIN',
+ setUseMultiChain,
+
// locale
SET_CURRENT_LOCALE: 'SET_CURRENT_LOCALE',
SET_LOCALE_MESSAGES: 'SET_LOCALE_MESSAGES',
@@ -1751,10 +1754,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!'))
@@ -2015,11 +2018,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({
@@ -2032,8 +2041,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) => {
@@ -2088,10 +2100,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 = [
@@ -2259,6 +2271,23 @@ function setUseBlockie (val) {
}
}
+function setUseMultiChain (val) {
+ return (dispatch) => {
+ dispatch(actions.showLoadingIndication())
+ log.debug(`background.setUseMultiChain`)
+ background.setUseMultiChain(val, (err) => {
+ dispatch(actions.hideLoadingIndication())
+ if (err) {
+ return dispatch(actions.displayWarning(err.message))
+ }
+ })
+ dispatch({
+ type: actions.SET_USE_MULTICHAIN,
+ value: val,
+ })
+ }
+}
+
function updateCurrentLocale (key) {
return (dispatch) => {
dispatch(actions.showLoadingIndication())
diff --git a/ui/app/app.js b/ui/app/app.js
index 4fcf092caf77..979d8a25c173 100644
--- a/ui/app/app.js
+++ b/ui/app/app.js
@@ -99,7 +99,7 @@ class App extends Component {
network,
isMouseUser,
provider,
- frequentRpcList,
+ frequentRpcListDetail,
currentView,
setMouseUserState,
} = this.props
@@ -139,7 +139,7 @@ class App extends Component {
// network dropdown
h(NetworkDropdown, {
provider,
- frequentRpcList,
+ frequentRpcListDetail,
}, []),
h(AccountMenu),
@@ -228,6 +228,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')
}
@@ -249,6 +251,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')
}
@@ -265,7 +269,7 @@ App.propTypes = {
alertMessage: PropTypes.string,
network: PropTypes.string,
provider: PropTypes.object,
- frequentRpcList: PropTypes.array,
+ frequentRpcListDetail: PropTypes.array,
currentView: PropTypes.object,
sidebarOpen: PropTypes.bool,
alertOpen: PropTypes.bool,
@@ -357,7 +361,7 @@ function mapStateToProps (state) {
forgottenPassword: state.appState.forgottenPassword,
nextUnreadNotice,
lostAccounts,
- frequentRpcList: state.metamask.frequentRpcList || [],
+ frequentRpcListDetail: state.metamask.frequentRpcListDetail || [],
currentCurrency: state.metamask.currentCurrency,
isMouseUser: state.appState.isMouseUser,
betaUI: state.metamask.featureFlags.betaUI,
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 bcada41e3887..3f1e9c9871a0 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.ticker,
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..6472ea58cac1 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.ticker,
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/buy-button-subview.js b/ui/app/components/buy-button-subview.js
index c6957d2aaa3e..b1a2ad0b94f1 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,
+ ticker: state.metamask.ticker,
isSubLoading: state.appState.isSubLoading,
}
}
@@ -258,10 +259,11 @@ BuyButtonSubview.prototype.backButtonContext = function () {
}
BuyButtonSubview.prototype.radioHandler = function (event) {
+ const ticker = this.props.ticker
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/dropdowns/components/account-dropdowns.js b/ui/app/components/dropdowns/components/account-dropdowns.js
index 179b6617f96f..8c8a53d67b91 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.ticker,
keyrings: state.metamask.keyrings,
sidebarOpen: state.appState.sidebarOpen,
+ settings: state.metamask.settings,
}
}
diff --git a/ui/app/components/dropdowns/network-dropdown.js b/ui/app/components/dropdowns/network-dropdown.js
index 63a30dd82f7d..e94ebd028a0e 100644
--- a/ui/app/components/dropdowns/network-dropdown.js
+++ b/ui/app/components/dropdowns/network-dropdown.js
@@ -24,8 +24,10 @@ const notToggleElementClassnames = [
function mapStateToProps (state) {
return {
provider: state.metamask.provider,
- frequentRpcList: state.metamask.frequentRpcList || [],
+ multichain: state.metamask.useMultiChain,
+ frequentRpcListDetail: state.metamask.frequentRpcListDetail || [],
networkDropdownOpen: state.appState.networkDropdownOpen,
+ network: state.metamask.network,
}
}
@@ -40,8 +42,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()),
@@ -68,7 +70,7 @@ module.exports = compose(
NetworkDropdown.prototype.render = function () {
const props = this.props
const { provider: { type: providerType, rpcTarget: activeNetwork } } = props
- const rpcList = props.frequentRpcList
+ const rpcListDetail = props.frequentRpcListDetail
const isOpen = this.props.networkDropdownOpen
const dropdownMenuItemStyle = {
fontSize: '16px',
@@ -76,6 +78,13 @@ NetworkDropdown.prototype.render = function () {
padding: '12px 0',
}
+ const dropdownMenuItemMultiChainStyle = {
+ fontSize: '16px',
+ lineHeight: '20px',
+ padding: '12px 0',
+ display: props.multichain ? 'flex' : 'none',
+ }
+
return h(Dropdown, {
isOpen,
onClickOutside: (event) => {
@@ -199,6 +208,28 @@ NetworkDropdown.prototype.render = function () {
]
),
+ h(
+ DropdownMenuItem,
+ {
+ key: 'classic',
+ closeMenu: () => this.props.hideNetworkDropdown(),
+ onClick: () => props.setProviderType('classic'),
+ style: dropdownMenuItemMultiChainStyle,
+ },
+ [
+ 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,
{
@@ -222,7 +253,7 @@ NetworkDropdown.prototype.render = function () {
),
this.renderCustomOption(props.provider),
- this.renderCommonRpc(rpcList, props.provider),
+ this.renderCommonRpc(rpcListDetail, props.provider),
h(
DropdownMenuItem,
@@ -263,6 +294,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')
}
@@ -270,22 +303,25 @@ NetworkDropdown.prototype.getNetworkName = function () {
return name
}
-NetworkDropdown.prototype.renderCommonRpc = function (rpcList, provider) {
+NetworkDropdown.prototype.renderCommonRpc = function (rpcListDetail, provider) {
const props = this.props
- const reversedRpcList = rpcList.slice().reverse()
+ const reversedRpcListDetail = rpcListDetail.slice().reverse()
+ const network = props.network
- return reversedRpcList.map((rpc) => {
+ return reversedRpcListDetail.map((entry) => {
+ const rpc = entry.rpcUrl
const currentRpcTarget = provider.type === 'rpc' && rpc === provider.rpcTarget
if ((rpc === 'http://localhost:8545') || currentRpcTarget) {
return null
} else {
+ const chainId = entry.chainId || network
return h(
DropdownMenuItem,
{
key: `common${rpc}`,
closeMenu: () => this.props.hideNetworkDropdown(),
- onClick: () => props.setRpcTarget(rpc),
+ onClick: () => props.setRpcTarget(rpc, chainId),
style: {
fontSize: '16px',
lineHeight: '20px',
@@ -309,6 +345,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 +359,7 @@ NetworkDropdown.prototype.renderCustomOption = function (provider) {
DropdownMenuItem,
{
key: rpcTarget,
- onClick: () => props.setRpcTarget(rpcTarget),
+ onClick: () => props.setRpcTarget(rpcTarget, network),
closeMenu: () => this.props.hideNetworkDropdown(),
style: {
fontSize: '16px',
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..dcf865535c1a 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.ticker,
+ }
+}
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..bfbeb110942e 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.ticker,
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 && 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 bc577fda05de..1f56f89e5066 100644
--- a/ui/app/components/modals/account-details-modal.js
+++ b/ui/app/components/modals/account-details-modal.js
@@ -15,6 +15,7 @@ function mapStateToProps (state) {
network: state.metamask.network,
selectedIdentity: getSelectedIdentity(state),
keyrings: state.metamask.keyrings,
+ settings: state.metamask.settings,
}
}
@@ -65,6 +66,11 @@ AccountDetailsModal.prototype.render = function () {
exportPrivateKeyFeatureEnabled = false
}
+ let link
+ if (this.props.settings && this.props.settings.blockExplorerAddr) {
+ link = this.props.settings.blockExplorerAddr
+ }
+
return h(AccountModalContainer, {}, [
h(EditableLabel, {
className: 'account-modal__name',
@@ -81,7 +87,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/modals/buy-options-modal.js b/ui/app/components/modals/buy-options-modal.js
index c70510b5fcde..a4b8458faadf 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.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..d24a326a5a27 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.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.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/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/home.js b/ui/app/components/pages/home.js
index 5e3fdc9af36f..0263924ef231 100644
--- a/ui/app/components/pages/home.js
+++ b/ui/app/components/pages/home.js
@@ -141,7 +141,7 @@ Home.propTypes = {
loadingMessage: PropTypes.string,
network: PropTypes.string,
provider: PropTypes.object,
- frequentRpcList: PropTypes.array,
+ frequentRpcListDetail: PropTypes.array,
currentView: PropTypes.object,
sidebarOpen: PropTypes.bool,
isMascara: PropTypes.bool,
@@ -220,7 +220,7 @@ function mapStateToProps (state) {
forgottenPassword: state.appState.forgottenPassword,
nextUnreadNotice,
lostAccounts,
- frequentRpcList: state.metamask.frequentRpcList || [],
+ frequentRpcListDetail: state.metamask.frequentRpcListDetail || [],
currentCurrency: state.metamask.currentCurrency,
isMouseUser: state.appState.isMouseUser,
isRevealingSeedWords: state.metamask.isRevealingSeedWords,
diff --git a/ui/app/components/pages/settings/settings.js b/ui/app/components/pages/settings/settings.js
index ff42a13dee27..dc1b6e113593 100644
--- a/ui/app/components/pages/settings/settings.js
+++ b/ui/app/components/pages/settings/settings.js
@@ -66,6 +66,26 @@ class Settings extends Component {
])
}
+ renderMultiChainOptIn () {
+ const { metamask: { useMultiChain }, setUseMultiChain } = this.props
+
+ return h('div.settings__content-row', [
+ h('div.settings__content-item', [
+ h('span', this.context.t('useMultiChainMenu')),
+ ]),
+ h('div.settings__content-item', [
+ h('div.settings__content-item-col', [
+ h(ToggleButton, {
+ value: useMultiChain,
+ onToggle: (value) => setUseMultiChain(!value),
+ activeLabel: '',
+ inactiveLabel: '',
+ }),
+ ]),
+ ]),
+ ])
+ }
+
renderCurrentConversion () {
const { metamask: { currentCurrency, conversionDate }, setCurrentCurrency } = this.props
@@ -161,6 +181,14 @@ class Settings extends Component {
}
renderNewRpcUrl () {
+ const { metamask: { provider = {} } } = this.props
+ let rpcTarget = ''
+ let chainId = ''
+ if (provider.type === 'rpc') {
+ rpcTarget = provider.rpcTarget
+ chainId = provider.chainId || ''
+ }
+
return (
h('div.settings__content-row', [
h('div.settings__content-item', [
@@ -169,18 +197,29 @@ class Settings extends Component {
h('div.settings__content-item', [
h('div.settings__content-item-col', [
h('input.settings__input', {
+ defaultValue: rpcTarget,
placeholder: this.context.t('newRPC'),
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', {
+ defaultValue: chainId,
+ 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', {
+ h('button.btn-primary.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 +228,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}`
@@ -301,6 +340,7 @@ class Settings extends Component {
this.renderCurrentConversion(),
this.renderCurrentLocale(),
// this.renderCurrentProvider(),
+ this.renderMultiChainOptIn(),
this.renderNewRpcUrl(),
this.renderStateLogs(),
this.renderSeedWords(),
@@ -317,6 +357,7 @@ Settings.propTypes = {
setUseBlockie: PropTypes.func,
setCurrentCurrency: PropTypes.func,
setRpcTarget: PropTypes.func,
+ setUseMultiChain: PropTypes.func,
displayWarning: PropTypes.func,
revealSeedConfirmation: PropTypes.func,
setFeatureFlagToBeta: PropTypes.func,
@@ -341,10 +382,11 @@ 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)),
+ setUseMultiChain: value => dispatch(actions.setUseMultiChain(value)),
updateCurrentLocale: key => dispatch(actions.updateCurrentLocale(key)),
setFeatureFlagToBeta: () => {
return dispatch(actions.setFeatureFlag('betaUI', false, 'OLD_UI_NOTIFICATION_MODAL'))
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/shapeshift-form.js b/ui/app/components/shapeshift-form.js
index 2c4ba40bf576..b9cb92afd8c0 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.ticker
+ 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 4334aacba56a..5071e6395c7b 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
@@ -25,6 +25,7 @@ function mapStateToProps (state) {
selectedAddress: state.metamask.selectedAddress,
conversionRate: state.metamask.conversionRate,
currentCurrency: state.metamask.currentCurrency,
+ ticker: state.metamask.ticker,
}
}
@@ -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',
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 474d62638053..46eb9ef889f3 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.ticker,
currentCurrency: getCurrentCurrency(state),
contractExchangeRates: state.metamask.contractExchangeRates,
selectedAddressTxList: state.metamask.selectedAddressTxList,
@@ -110,6 +111,7 @@ TxListItem.prototype.getSendEtherTotal = function () {
const {
transactionAmount,
conversionRate,
+ ticker,
address,
currentCurrency,
} = this.props
@@ -121,7 +123,7 @@ TxListItem.prototype.getSendEtherTotal = function () {
const totalInFiat = conversionUtil(transactionAmount, {
fromNumericBase: 'hex',
toNumericBase: 'dec',
- fromCurrency: 'ETH',
+ fromCurrency: ticker,
toCurrency: currentCurrency,
fromDenomination: 'WEI',
numberOfDecimals: 2,
@@ -130,15 +132,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 d8c4a9d19793..51c302641a2a 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),
selectedAddress: selectors.getSelectedAddress(state),
@@ -155,7 +156,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)
}
@@ -165,7 +170,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/app/css/itcss/components/settings.scss b/ui/app/css/itcss/components/settings.scss
index 0dd61ac5eb41..a82520120bf5 100644
--- a/ui/app/css/itcss/components/settings.scss
+++ b/ui/app/css/itcss/components/settings.scss
@@ -48,7 +48,6 @@
display: flex;
flex-direction: column;
padding: 0 5px;
- height: 71px;
@media screen and (max-width: 575px) {
height: initial;
@@ -78,10 +77,12 @@
}
.settings__input {
- padding-left: 10px;
+ padding-left: 15px;
font-size: 14px;
- height: 40px;
+ height: 56px;
border: 1px solid $alto;
+ margin-bottom: 3px;
+ border-radius: 2px;
}
.settings__input::-webkit-input-placeholder {
@@ -124,9 +125,8 @@
.settings__rpc-save-button {
align-self: flex-end;
- padding: 5px;
text-transform: uppercase;
- color: $dusty-gray;
+ width: 30%;
cursor: pointer;
}
diff --git a/ui/app/reducers/metamask.js b/ui/app/reducers/metamask.js
index 3f1d3394f22f..6ab6caf72253 100644
--- a/ui/app/reducers/metamask.js
+++ b/ui/app/reducers/metamask.js
@@ -46,6 +46,7 @@ function reduceMetamask (state, action) {
},
coinOptions: {},
useBlockie: false,
+ useMultiChain: false,
featureFlags: {},
networkEndpointType: OLD_UI_NETWORK_TYPE,
isRevealingSeedWords: false,
@@ -334,6 +335,11 @@ function reduceMetamask (state, action) {
useBlockie: action.value,
})
+ case actions.SET_USE_MULTICHAIN:
+ return extend(metamaskState, {
+ useMultiChain: action.value,
+ })
+
case actions.UPDATE_FEATURE_FLAGS:
return extend(metamaskState, {
featureFlags: action.value,
diff --git a/ui/lib/account-link.js b/ui/lib/account-link.js
index 037d990fa223..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,6 +18,10 @@ module.exports = function (address, network) {
link = `https://kovan.etherscan.io/address/${address}`
break
default:
+ 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}`
+}