Skip to content

Commit

Permalink
Add support for RPC endpoints with custom chain IDs (#5134)
Browse files Browse the repository at this point in the history
  • Loading branch information
hackmod authored and whymarrh committed Oct 26, 2018
1 parent eaca9a0 commit 54a8ade
Show file tree
Hide file tree
Showing 69 changed files with 586 additions and 217 deletions.
22 changes: 20 additions & 2 deletions app/_locales/en/messages.json
Original file line number Diff line number Diff line change
Expand Up @@ -692,9 +692,27 @@
"newRecipient": {
"message": "New Recipient"
},
"newRPC": {
"newNetwork": {
"message": "New Network"
},
"rpcURL": {
"message": "New RPC URL"
},
"showAdvancedOptions": {
"message": "Show Advanced Options"
},
"hideAdvancedOptions": {
"message": "Hide Advanced Options"
},
"optionalChainId": {
"message": "ChainID (optional)"
},
"optionalSymbol": {
"message": "Symbol (optional)"
},
"optionalNickname": {
"message": "Nickname (optional)"
},
"next": {
"message": "Next"
},
Expand Down Expand Up @@ -803,7 +821,7 @@
"message": "Primary Currency"
},
"primaryCurrencySettingDescription": {
"message": "Select ETH to prioritize displaying values in ETH. Select Fiat to prioritize displaying values in your selected currency."
"message": "Select native to prioritize displaying values in the native currency of the chain (e.g. ETH). Select Fiat to prioritize displaying values in your selected fiat currency."
},
"privacyMsg": {
"message": "Privacy Policy"
Expand Down
52 changes: 47 additions & 5 deletions app/scripts/controllers/currency.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,15 @@ class CurrencyController {
* since midnight of January 1, 1970
* @property {number} conversionInterval The id of the interval created by the scheduleConversionInterval method.
* Used to clear an existing interval on subsequent calls of that method.
* @property {string} nativeCurrency The ticker/symbol of the native chain currency
*
*/
constructor (opts = {}) {
const initState = extend({
currentCurrency: 'usd',
conversionRate: 0,
conversionDate: 'N/A',
nativeCurrency: 'ETH',
}, opts.initState)
this.store = new ObservableStore(initState)
}
Expand All @@ -36,6 +38,29 @@ class CurrencyController {
// PUBLIC METHODS
//

/**
* A getter for the nativeCurrency property
*
* @returns {string} A 2-4 character shorthand that describes the specific currency
*
*/
getNativeCurrency () {
return this.store.getState().nativeCurrency
}

/**
* A setter for the nativeCurrency property
*
* @param {string} nativeCurrency The new currency to set as the nativeCurrency in the store
*
*/
setNativeCurrency (nativeCurrency) {
this.store.updateState({
nativeCurrency,
ticker: nativeCurrency,
})
}

/**
* A getter for the currentCurrency property
*
Expand Down Expand Up @@ -104,15 +129,32 @@ class CurrencyController {
*
*/
async updateConversionRate () {
let currentCurrency
let currentCurrency, nativeCurrency
try {
currentCurrency = this.getCurrentCurrency()
const response = await fetch(`https://api.infura.io/v1/ticker/eth${currentCurrency.toLowerCase()}`)
nativeCurrency = this.getNativeCurrency()
let apiUrl
if (nativeCurrency === 'ETH') {
apiUrl = `https://api.infura.io/v1/ticker/eth${currentCurrency.toLowerCase()}`
} else {
apiUrl = `https://min-api.cryptocompare.com/data/price?fsym=${nativeCurrency.toUpperCase()}&tsyms=${currentCurrency.toUpperCase()}`
}
const response = await fetch(apiUrl)
const parsedResponse = await response.json()
this.setConversionRate(Number(parsedResponse.bid))
this.setConversionDate(Number(parsedResponse.timestamp))
if (nativeCurrency === 'ETH') {
this.setConversionRate(Number(parsedResponse.bid))
this.setConversionDate(Number(parsedResponse.timestamp))
} else {
if (parsedResponse[currentCurrency.toUpperCase()]) {
this.setConversionRate(Number(parsedResponse[currentCurrency.toUpperCase()]))
this.setConversionDate(parseInt((new Date()).getTime() / 1000))
} else {
this.setConversionRate(0)
this.setConversionDate('N/A')
}
}
} catch (err) {
log.warn(`MetaMask - Failed to query currency conversion:`, currentCurrency, err)
log.warn(`MetaMask - Failed to query currency conversion:`, nativeCurrency, currentCurrency, err)
this.setConversionRate(0)
this.setConversionDate('N/A')
}
Expand Down
60 changes: 51 additions & 9 deletions app/scripts/controllers/network/network.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ const createInfuraClient = require('./createInfuraClient')
const createJsonRpcClient = require('./createJsonRpcClient')
const createLocalhostClient = require('./createLocalhostClient')
const { createSwappableProxy, createEventEmitterProxy } = require('swappable-obj-proxy')
const extend = require('extend')
const networks = { networkList: {} }

const {
ROPSTEN,
Expand All @@ -29,6 +31,10 @@ const defaultProviderConfig = {
type: testMode ? RINKEBY : MAINNET,
}

const defaultNetworkConfig = {
ticker: 'ETH',
}

module.exports = class NetworkController extends EventEmitter {

constructor (opts = {}) {
Expand All @@ -39,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 })
this.on('networkDidChange', this.lookupNetwork)
// provider and block tracker
this._provider = null
Expand All @@ -51,8 +58,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, ticker, nickname } = this.providerStore.getState()
this._configureProvider({ type, rpcTarget, chainId, ticker, nickname })
this.lookupNetwork()
}

Expand All @@ -72,7 +79,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)
}

Expand All @@ -85,18 +105,22 @@ 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, ticker = 'ETH', nickname = '') {
const providerConfig = {
type: 'rpc',
rpcTarget,
chainId,
ticker,
nickname,
}
this.providerConfig = providerConfig
}
Expand Down Expand Up @@ -132,7 +156,7 @@ module.exports = class NetworkController extends EventEmitter {
}

_configureProvider (opts) {
const { type, rpcTarget } = opts
const { type, rpcTarget, chainId, ticker, nickname } = opts
// infura type-based endpoints
const isInfura = INFURA_PROVIDER_TYPES.includes(type)
if (isInfura) {
Expand All @@ -142,7 +166,7 @@ module.exports = class NetworkController extends EventEmitter {
this._configureLocalhostProvider()
// url-based rpc endpoints
} else if (type === 'rpc') {
this._configureStandardProvider({ rpcUrl: rpcTarget })
this._configureStandardProvider({ rpcUrl: rpcTarget, chainId, ticker, nickname })
} else {
throw new Error(`NetworkController - _configureProvider - unknown type "${type}"`)
}
Expand All @@ -152,6 +176,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 () {
Expand All @@ -160,9 +189,22 @@ module.exports = class NetworkController extends EventEmitter {
this._setNetworkClient(networkClient)
}

_configureStandardProvider ({ rpcUrl }) {
_configureStandardProvider ({ rpcUrl, chainId, ticker, nickname }) {
log.info('NetworkController - configureStandardProvider', rpcUrl)
const networkClient = createJsonRpcClient({ rpcUrl })
// hack to add a 'rpc' network with chainId
networks.networkList['rpc'] = {
chainId: chainId,
rpcUrl,
ticker: ticker || 'ETH',
nickname,
}
// setup networkConfig
var settings = {
network: chainId,
}
settings = extend(settings, networks.networkList['rpc'])
this.networkConfig.putState(settings)
this._setNetworkClient(networkClient)
}

Expand Down
31 changes: 17 additions & 14 deletions app/scripts/controllers/preferences.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ class PreferencesController {
*/
constructor (opts = {}) {
const initState = extend({
frequentRpcList: [],
frequentRpcListDetail: [],
currentAccountTab: 'history',
accountTokens: {},
assetImages: {},
Expand All @@ -39,7 +39,7 @@ class PreferencesController {
seedWords: null,
forgottenPassword: false,
preferences: {
useETHAsPrimaryCurrency: true,
useNativeCurrencyAsPrimaryCurrency: true,
},
}, opts.initState)

Expand Down Expand Up @@ -392,19 +392,22 @@ class PreferencesController {
* Adds custom RPC url to state.
*
* @param {string} url The RPC url to add to frequentRpcList.
* @param {number} chainId Optional chainId of the selected network.
* @param {string} ticker Optional ticker symbol of the selected network.
* @param {string} nickname Optional nickname of the selected network.
* @returns {Promise<array>} Promise resolving to updated frequentRpcList.
*
*/
addToFrequentRpcList (url) {
const rpcList = this.getFrequentRpcList()
const index = rpcList.findIndex((element) => { return element === url })
addToFrequentRpcList (url, chainId, ticker = 'ETH', nickname = '') {
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, ticker, nickname })
}
this.store.updateState({ frequentRpcList: rpcList })
this.store.updateState({ frequentRpcListiDetail: rpcList })
return Promise.resolve(rpcList)
}

Expand All @@ -416,23 +419,23 @@ class PreferencesController {
*
*/
removeFromFrequentRpcList (url) {
const rpcList = this.getFrequentRpcList()
const index = rpcList.findIndex((element) => { return element === url })
const rpcList = this.getFrequentRpcListDetail()
const index = rpcList.findIndex((element) => { return element.rpcUrl === url })
if (index !== -1) {
rpcList.splice(index, 1)
}
this.store.updateState({ frequentRpcList: rpcList })
this.store.updateState({ frequentRpcListDetail: rpcList })
return Promise.resolve(rpcList)
}

/**
* Getter for the `frequentRpcList` property.
* Getter for the `frequentRpcListDetail` property.
*
* @returns {array<string>} An array of one or two rpc urls.
* @returns {array<array>} An array of rpc urls.
*
*/
getFrequentRpcList () {
return this.store.getState().frequentRpcList
getFrequentRpcListDetail () {
return this.store.getState().frequentRpcListDetail
}

/**
Expand Down
18 changes: 13 additions & 5 deletions app/scripts/metamask-controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -138,12 +138,12 @@ module.exports = class MetamaskController extends EventEmitter {
this.accountTracker.stop()
}
})

// ensure accountTracker updates balances after network change
this.networkController.on('networkDidChange', () => {
this.accountTracker._updateAccounts()
})

// key mgmt
const additionalKeyrings = [TrezorKeyring, LedgerBridgeKeyring]
this.keyringController = new KeyringController({
Expand Down Expand Up @@ -197,6 +197,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()

Expand Down Expand Up @@ -1412,10 +1414,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.setNativeCurrency(ticker)
this.currencyController.setCurrentCurrency(currencyCode)
this.currencyController.updateConversionRate()
const data = {
nativeCurrency: ticker || 'ETH',
conversionRate: this.currencyController.getConversionRate(),
currentCurrency: this.currencyController.getCurrentCurrency(),
conversionDate: this.currencyController.getConversionDate(),
Expand Down Expand Up @@ -1454,11 +1459,14 @@ module.exports = class MetamaskController extends EventEmitter {
/**
* A method for selecting a custom URL for an ethereum RPC provider.
* @param {string} rpcTarget - A URL for a valid Ethereum RPC API.
* @param {number} chainId - The chainId of the selected network.
* @param {string} ticker - The ticker symbol of the selected network.
* @param {string} nickname - Optional nickname of the selected network.
* @returns {Promise<String>} - The RPC Target URL confirmed.
*/
async setCustomRpc (rpcTarget) {
this.networkController.setRpcTarget(rpcTarget)
await this.preferencesController.addToFrequentRpcList(rpcTarget)
async setCustomRpc (rpcTarget, chainId, ticker = 'ETH', nickname = '') {
this.networkController.setRpcTarget(rpcTarget, chainId, ticker, nickname)
await this.preferencesController.addToFrequentRpcList(rpcTarget, chainId, ticker, nickname)
return rpcTarget
}

Expand Down
Loading

0 comments on commit 54a8ade

Please sign in to comment.