diff options
Diffstat (limited to 'app/scripts/controllers')
21 files changed, 300 insertions, 663 deletions
diff --git a/app/scripts/controllers/app-state.js b/app/scripts/controllers/app-state.js new file mode 100644 index 000000000..9533fd458 --- /dev/null +++ b/app/scripts/controllers/app-state.js @@ -0,0 +1,73 @@ +const ObservableStore = require('obs-store') +const extend = require('xtend') + +class AppStateController { + /** + * @constructor + * @param opts + */ + constructor (opts = {}) { + const {initState, onInactiveTimeout, preferencesStore} = opts + const {preferences} = preferencesStore.getState() + + this.onInactiveTimeout = onInactiveTimeout || (() => {}) + this.store = new ObservableStore(extend({ + timeoutMinutes: 0, + }, initState)) + this.timer = null + + preferencesStore.subscribe(state => { + this._setInactiveTimeout(state.preferences.autoLogoutTimeLimit) + }) + + this._setInactiveTimeout(preferences.autoLogoutTimeLimit) + } + + /** + * Sets the last active time to the current time + * @return {void} + */ + setLastActiveTime () { + this._resetTimer() + } + + /** + * Sets the inactive timeout for the app + * @param {number} timeoutMinutes the inactive timeout in minutes + * @return {void} + * @private + */ + _setInactiveTimeout (timeoutMinutes) { + this.store.putState({ + timeoutMinutes, + }) + + this._resetTimer() + } + + /** + * Resets the internal inactive timer + * + * If the {@code timeoutMinutes} state is falsy (i.e., zero) then a new + * timer will not be created. + * + * @return {void} + * @private + */ + _resetTimer () { + const {timeoutMinutes} = this.store.getState() + + if (this.timer) { + clearTimeout(this.timer) + } + + if (!timeoutMinutes) { + return + } + + this.timer = setTimeout(() => this.onInactiveTimeout(), timeoutMinutes * 60 * 1000) + } +} + +module.exports = AppStateController + diff --git a/app/scripts/controllers/balance.js b/app/scripts/controllers/balance.js index 465751e61..b227d5d0a 100644 --- a/app/scripts/controllers/balance.js +++ b/app/scripts/controllers/balance.js @@ -68,7 +68,7 @@ class BalanceController { _registerUpdates () { const update = this.updateBalance.bind(this) - this.txController.on('tx:status-update', (txId, status) => { + this.txController.on('tx:status-update', (_, status) => { switch (status) { case 'submitted': case 'confirmed': diff --git a/app/scripts/controllers/blacklist.js b/app/scripts/controllers/blacklist.js deleted file mode 100644 index e55b09d03..000000000 --- a/app/scripts/controllers/blacklist.js +++ /dev/null @@ -1,136 +0,0 @@ -const ObservableStore = require('obs-store') -const extend = require('xtend') -const PhishingDetector = require('eth-phishing-detect/src/detector') -const log = require('loglevel') - -// compute phishing lists -const PHISHING_DETECTION_CONFIG = require('eth-phishing-detect/src/config.json') -// every four minutes -const POLLING_INTERVAL = 4 * 60 * 1000 - -class BlacklistController { - - /** - * Responsible for polling for and storing an up to date 'eth-phishing-detect' config.json file, while - * exposing a method that can check whether a given url is a phishing attempt. The 'eth-phishing-detect' - * config.json file contains a fuzzylist, whitelist and blacklist. - * - * - * @typedef {Object} BlacklistController - * @param {object} opts Overrides the defaults for the initial state of this.store - * @property {object} store The the store of the current phishing config - * @property {object} store.phishing Contains fuzzylist, whitelist and blacklist arrays. @see - * {@link https://github.com/MetaMask/eth-phishing-detect/blob/master/src/config.json} - * @property {object} _phishingDetector The PhishingDetector instantiated by passing store.phishing to - * PhishingDetector. - * @property {object} _phishingUpdateIntervalRef Id of the interval created to periodically update the blacklist - * - */ - constructor (opts = {}) { - const initState = extend({ - phishing: PHISHING_DETECTION_CONFIG, - whitelist: [], - }, opts.initState) - this.store = new ObservableStore(initState) - // phishing detector - this._phishingDetector = null - this._setupPhishingDetector(initState.phishing) - // polling references - this._phishingUpdateIntervalRef = null - } - - /** - * Adds the given hostname to the runtime whitelist - * @param {string} hostname the hostname to whitelist - */ - whitelistDomain (hostname) { - if (!hostname) { - return - } - - const { whitelist } = this.store.getState() - this.store.updateState({ - whitelist: [...new Set([hostname, ...whitelist])], - }) - } - - /** - * Given a url, returns the result of checking if that url is in the store.phishing blacklist - * - * @param {string} hostname The hostname portion of a url; the one that will be checked against the white and - * blacklists of store.phishing - * @returns {boolean} Whether or not the passed hostname is on our phishing blacklist - * - */ - checkForPhishing (hostname) { - if (!hostname) return false - - const { whitelist } = this.store.getState() - if (whitelist.some((e) => e === hostname)) { - return false - } - - const { result } = this._phishingDetector.check(hostname) - return result - } - - /** - * Queries `https://api.infura.io/v2/blacklist` for an updated blacklist config. This is passed to this._phishingDetector - * to update our phishing detector instance, and is updated in the store. The new phishing config is returned - * - * - * @returns {Promise<object>} Promises the updated blacklist config for the phishingDetector - * - */ - async updatePhishingList () { - // make request - let response - try { - response = await fetch('https://api.infura.io/v2/blacklist') - } catch (err) { - log.error(new Error(`BlacklistController - failed to fetch blacklist:\n${err.stack}`)) - return - } - // parse response - let rawResponse - let phishing - try { - const rawResponse = await response.text() - phishing = JSON.parse(rawResponse) - } catch (err) { - log.error(new Error(`BlacklistController - failed to parse blacklist:\n${rawResponse}`)) - return - } - // update current blacklist - this.store.updateState({ phishing }) - this._setupPhishingDetector(phishing) - return phishing - } - - /** - * Initiates the updating of the local blacklist at a set interval. The update is done via this.updatePhishingList(). - * Also, this method store a reference to that interval at this._phishingUpdateIntervalRef - * - */ - scheduleUpdates () { - if (this._phishingUpdateIntervalRef) return - this.updatePhishingList() - this._phishingUpdateIntervalRef = setInterval(() => { - this.updatePhishingList() - }, POLLING_INTERVAL) - } - - /** - * Sets this._phishingDetector to a new PhishingDetector instance. - * @see {@link https://github.com/MetaMask/eth-phishing-detect} - * - * @private - * @param {object} config A config object like that found at {@link https://github.com/MetaMask/eth-phishing-detect/blob/master/src/config.json} - * - */ - _setupPhishingDetector (config) { - this._phishingDetector = new PhishingDetector(config) - } -} - -module.exports = BlacklistController diff --git a/app/scripts/controllers/network/createBlockTracker.js b/app/scripts/controllers/network/createBlockTracker.js deleted file mode 100644 index 6573b18a1..000000000 --- a/app/scripts/controllers/network/createBlockTracker.js +++ /dev/null @@ -1,19 +0,0 @@ -const BlockTracker = require('eth-block-tracker') - -/** - * Creates a block tracker that sends platform events on success and failure - */ -module.exports = function createBlockTracker (args, platform) { - const blockTracker = new BlockTracker(args) - blockTracker.on('latest', () => { - if (platform && platform.sendMessage) { - platform.sendMessage({ action: 'ethereum-ping-success' }) - } - }) - blockTracker.on('error', () => { - if (platform && platform.sendMessage) { - platform.sendMessage({ action: 'ethereum-ping-error' }) - } - }) - return blockTracker -} diff --git a/app/scripts/controllers/network/createInfuraClient.js b/app/scripts/controllers/network/createInfuraClient.js index 884b94db3..0a6e9ecb0 100644 --- a/app/scripts/controllers/network/createInfuraClient.js +++ b/app/scripts/controllers/network/createInfuraClient.js @@ -7,14 +7,14 @@ const createInflightMiddleware = require('eth-json-rpc-middleware/inflight-cache const createBlockTrackerInspectorMiddleware = require('eth-json-rpc-middleware/block-tracker-inspector') const providerFromMiddleware = require('eth-json-rpc-middleware/providerFromMiddleware') const createInfuraMiddleware = require('eth-json-rpc-infura') -const createBlockTracker = require('./createBlockTracker') +const BlockTracker = require('eth-block-tracker') module.exports = createInfuraClient -function createInfuraClient ({ network, platform }) { +function createInfuraClient ({ network }) { const infuraMiddleware = createInfuraMiddleware({ network, maxAttempts: 5, source: 'metamask' }) const infuraProvider = providerFromMiddleware(infuraMiddleware) - const blockTracker = createBlockTracker({ provider: infuraProvider }, platform) + const blockTracker = new BlockTracker({ provider: infuraProvider }) const networkMiddleware = mergeMiddleware([ createNetworkAndChainIdMiddleware({ network }), @@ -49,6 +49,10 @@ function createNetworkAndChainIdMiddleware ({ network }) { netId = '42' chainId = '0x2a' break + case 'goerli': + netId = '5' + chainId = '0x05' + break default: throw new Error(`createInfuraClient - unknown network "${network}"`) } diff --git a/app/scripts/controllers/network/createJsonRpcClient.js b/app/scripts/controllers/network/createJsonRpcClient.js index 369dcd299..a8cbf2aaf 100644 --- a/app/scripts/controllers/network/createJsonRpcClient.js +++ b/app/scripts/controllers/network/createJsonRpcClient.js @@ -5,14 +5,14 @@ const createBlockCacheMiddleware = require('eth-json-rpc-middleware/block-cache' const createInflightMiddleware = require('eth-json-rpc-middleware/inflight-cache') const createBlockTrackerInspectorMiddleware = require('eth-json-rpc-middleware/block-tracker-inspector') const providerFromMiddleware = require('eth-json-rpc-middleware/providerFromMiddleware') -const createBlockTracker = require('./createBlockTracker') +const BlockTracker = require('eth-block-tracker') module.exports = createJsonRpcClient -function createJsonRpcClient ({ rpcUrl, platform }) { +function createJsonRpcClient ({ rpcUrl }) { const fetchMiddleware = createFetchMiddleware({ rpcUrl }) const blockProvider = providerFromMiddleware(fetchMiddleware) - const blockTracker = createBlockTracker({ provider: blockProvider }, platform) + const blockTracker = new BlockTracker({ provider: blockProvider }) const networkMiddleware = mergeMiddleware([ createBlockRefRewriteMiddleware({ blockTracker }), diff --git a/app/scripts/controllers/network/createLocalhostClient.js b/app/scripts/controllers/network/createLocalhostClient.js index 36593dc70..09b1d3c1c 100644 --- a/app/scripts/controllers/network/createLocalhostClient.js +++ b/app/scripts/controllers/network/createLocalhostClient.js @@ -3,14 +3,14 @@ const createFetchMiddleware = require('eth-json-rpc-middleware/fetch') const createBlockRefRewriteMiddleware = require('eth-json-rpc-middleware/block-ref-rewrite') const createBlockTrackerInspectorMiddleware = require('eth-json-rpc-middleware/block-tracker-inspector') const providerFromMiddleware = require('eth-json-rpc-middleware/providerFromMiddleware') -const createBlockTracker = require('./createBlockTracker') +const BlockTracker = require('eth-block-tracker') module.exports = createLocalhostClient -function createLocalhostClient ({ platform }) { +function createLocalhostClient () { const fetchMiddleware = createFetchMiddleware({ rpcUrl: 'http://localhost:8545/' }) const blockProvider = providerFromMiddleware(fetchMiddleware) - const blockTracker = createBlockTracker({ provider: blockProvider, pollingInterval: 1000 }, platform) + const blockTracker = new BlockTracker({ provider: blockProvider, pollingInterval: 1000 }) const networkMiddleware = mergeMiddleware([ createBlockRefRewriteMiddleware({ blockTracker }), diff --git a/app/scripts/controllers/network/enums.js b/app/scripts/controllers/network/enums.js index 3190eb37c..2f7025392 100644 --- a/app/scripts/controllers/network/enums.js +++ b/app/scripts/controllers/network/enums.js @@ -3,16 +3,19 @@ const RINKEBY = 'rinkeby' const KOVAN = 'kovan' const MAINNET = 'mainnet' const LOCALHOST = 'localhost' +const GOERLI = 'goerli' const MAINNET_CODE = 1 const ROPSTEN_CODE = 3 const RINKEYBY_CODE = 4 const KOVAN_CODE = 42 +const GOERLI_CODE = 5 const ROPSTEN_DISPLAY_NAME = 'Ropsten' const RINKEBY_DISPLAY_NAME = 'Rinkeby' const KOVAN_DISPLAY_NAME = 'Kovan' const MAINNET_DISPLAY_NAME = 'Main Ethereum Network' +const GOERLI_DISPLAY_NAME = 'Goerli' module.exports = { ROPSTEN, @@ -20,12 +23,15 @@ module.exports = { KOVAN, MAINNET, LOCALHOST, + GOERLI, MAINNET_CODE, ROPSTEN_CODE, RINKEYBY_CODE, KOVAN_CODE, + GOERLI_CODE, ROPSTEN_DISPLAY_NAME, RINKEBY_DISPLAY_NAME, KOVAN_DISPLAY_NAME, MAINNET_DISPLAY_NAME, + GOERLI_DISPLAY_NAME, } diff --git a/app/scripts/controllers/network/network.js b/app/scripts/controllers/network/network.js index 47432c1e2..2c68e4378 100644 --- a/app/scripts/controllers/network/network.js +++ b/app/scripts/controllers/network/network.js @@ -20,8 +20,9 @@ const { KOVAN, MAINNET, LOCALHOST, + GOERLI, } = require('./enums') -const INFURA_PROVIDER_TYPES = [ROPSTEN, RINKEBY, KOVAN, MAINNET] +const INFURA_PROVIDER_TYPES = [ROPSTEN, RINKEBY, KOVAN, MAINNET, GOERLI] const env = process.env.METAMASK_ENV const METAMASK_DEBUG = process.env.METAMASK_DEBUG @@ -45,9 +46,8 @@ const defaultNetworkConfig = { module.exports = class NetworkController extends EventEmitter { - constructor (opts = {}, platform) { + constructor (opts = {}) { super() - this.platform = platform // parse options const providerConfig = opts.provider || defaultProviderConfig @@ -129,21 +129,22 @@ module.exports = class NetworkController extends EventEmitter { }) } - setRpcTarget (rpcTarget, chainId, ticker = 'ETH', nickname = '') { + setRpcTarget (rpcTarget, chainId, ticker = 'ETH', nickname = '', rpcPrefs) { const providerConfig = { type: 'rpc', rpcTarget, chainId, ticker, nickname, + rpcPrefs, } this.providerConfig = providerConfig } - async setProviderType (type) { + async setProviderType (type, rpcTarget = '', ticker = 'ETH', nickname = '') { 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}"`) - const providerConfig = { type } + const providerConfig = { type, rpcTarget, ticker, nickname } this.providerConfig = providerConfig } @@ -189,7 +190,7 @@ module.exports = class NetworkController extends EventEmitter { _configureInfuraProvider ({ type }) { log.info('NetworkController - configureInfuraProvider', type) - const networkClient = createInfuraClient({ network: type, platform: this.platform }) + const networkClient = createInfuraClient({ network: type }) this._setNetworkClient(networkClient) // setup networkConfig var settings = { @@ -200,13 +201,13 @@ module.exports = class NetworkController extends EventEmitter { _configureLocalhostProvider () { log.info('NetworkController - configureLocalhostProvider') - const networkClient = createLocalhostClient({ platform: this.platform }) + const networkClient = createLocalhostClient() this._setNetworkClient(networkClient) } _configureStandardProvider ({ rpcUrl, chainId, ticker, nickname }) { log.info('NetworkController - configureStandardProvider', rpcUrl) - const networkClient = createJsonRpcClient({ rpcUrl, platform: this.platform }) + const networkClient = createJsonRpcClient({ rpcUrl }) // hack to add a 'rpc' network with chainId networks.networkList['rpc'] = { chainId: chainId, diff --git a/app/scripts/controllers/network/util.js b/app/scripts/controllers/network/util.js index 261dae721..b0afccd7e 100644 --- a/app/scripts/controllers/network/util.js +++ b/app/scripts/controllers/network/util.js @@ -3,13 +3,16 @@ const { RINKEBY, KOVAN, MAINNET, + GOERLI, ROPSTEN_CODE, RINKEYBY_CODE, KOVAN_CODE, + GOERLI_CODE, ROPSTEN_DISPLAY_NAME, RINKEBY_DISPLAY_NAME, KOVAN_DISPLAY_NAME, MAINNET_DISPLAY_NAME, + GOERLI_DISPLAY_NAME, } = require('./enums') const networkToNameMap = { @@ -17,9 +20,11 @@ const networkToNameMap = { [RINKEBY]: RINKEBY_DISPLAY_NAME, [KOVAN]: KOVAN_DISPLAY_NAME, [MAINNET]: MAINNET_DISPLAY_NAME, + [GOERLI]: GOERLI_DISPLAY_NAME, [ROPSTEN_CODE]: ROPSTEN_DISPLAY_NAME, [RINKEYBY_CODE]: RINKEBY_DISPLAY_NAME, [KOVAN_CODE]: KOVAN_DISPLAY_NAME, + [GOERLI_CODE]: GOERLI_DISPLAY_NAME, } const getNetworkDisplayName = key => networkToNameMap[key] diff --git a/app/scripts/controllers/preferences.js b/app/scripts/controllers/preferences.js index 737411890..acf952bb1 100644 --- a/app/scripts/controllers/preferences.js +++ b/app/scripts/controllers/preferences.js @@ -117,6 +117,14 @@ class PreferencesController { return metaMetricsId } + getMetaMetricsId () { + return this.store.getState().metaMetricsId + } + + getParticipateInMetaMetrics () { + return this.store.getState().participateInMetaMetrics + } + setMetaMetricsSendCount (val) { this.store.updateState({ metaMetricsSendCount: val }) } @@ -331,7 +339,7 @@ class PreferencesController { } removeSuggestedTokens () { - return new Promise((resolve, reject) => { + return new Promise((resolve) => { this.store.updateState({ suggestedTokens: {} }) resolve({}) }) @@ -388,7 +396,7 @@ class PreferencesController { const newEntry = { address, symbol, decimals } const tokens = this.store.getState().tokens const assetImages = this.getAssetImages() - const previousEntry = tokens.find((token, index) => { + const previousEntry = tokens.find((token) => { return token.address === address }) const previousIndex = tokens.indexOf(previousEntry) @@ -453,7 +461,7 @@ class PreferencesController { * */ setCurrentAccountTab (currentAccountTab) { - return new Promise((resolve, reject) => { + return new Promise((resolve) => { this.store.updateState({ currentAccountTab }) resolve() }) @@ -480,8 +488,8 @@ class PreferencesController { rpcList[index] = updatedRpc this.store.updateState({ frequentRpcListDetail: rpcList }) } else { - const { rpcUrl, chainId, ticker, nickname } = newRpcDetails - return this.addToFrequentRpcList(rpcUrl, chainId, ticker, nickname) + const { rpcUrl, chainId, ticker, nickname, rpcPrefs = {} } = newRpcDetails + return this.addToFrequentRpcList(rpcUrl, chainId, ticker, nickname, rpcPrefs) } return Promise.resolve(rpcList) } @@ -495,22 +503,22 @@ class PreferencesController { * @returns {Promise<array>} Promise resolving to updated frequentRpcList. * */ - 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') { - let checkedChainId - if (!!chainId && !Number.isNaN(parseInt(chainId))) { - checkedChainId = chainId + addToFrequentRpcList (url, chainId, ticker = 'ETH', nickname = '', rpcPrefs = {}) { + const rpcList = this.getFrequentRpcListDetail() + const index = rpcList.findIndex((element) => { return element.rpcUrl === url }) + if (index !== -1) { + rpcList.splice(index, 1) } - rpcList.push({ rpcUrl: url, chainId: checkedChainId, ticker, nickname }) + if (url !== 'http://localhost:8545') { + let checkedChainId + if (!!chainId && !Number.isNaN(parseInt(chainId))) { + checkedChainId = chainId + } + rpcList.push({ rpcUrl: url, chainId: checkedChainId, ticker, nickname, rpcPrefs }) + } + this.store.updateState({ frequentRpcListDetail: rpcList }) + return Promise.resolve(rpcList) } - this.store.updateState({ frequentRpcListDetail: rpcList }) - return Promise.resolve(rpcList) - } /** * Removes custom RPC url from state. diff --git a/app/scripts/controllers/provider-approval.js b/app/scripts/controllers/provider-approval.js index 2c9182b52..06c499780 100644 --- a/app/scripts/controllers/provider-approval.js +++ b/app/scripts/controllers/provider-approval.js @@ -1,9 +1,11 @@ const ObservableStore = require('obs-store') +const SafeEventEmitter = require('safe-event-emitter') +const createAsyncMiddleware = require('json-rpc-engine/src/createAsyncMiddleware') /** * A controller that services user-approved requests for a full Ethereum provider API */ -class ProviderApprovalController { +class ProviderApprovalController extends SafeEventEmitter { /** * Determines if caching is enabled */ @@ -14,38 +16,44 @@ class ProviderApprovalController { * * @param {Object} [config] - Options to configure controller */ - constructor ({ closePopup, keyringController, openPopup, platform, preferencesController, publicConfigStore } = {}) { + constructor ({ closePopup, keyringController, openPopup, preferencesController } = {}) { + super() this.approvedOrigins = {} this.closePopup = closePopup this.keyringController = keyringController this.openPopup = openPopup - this.platform = platform this.preferencesController = preferencesController - this.publicConfigStore = publicConfigStore this.store = new ObservableStore({ providerRequests: [], }) + } - if (platform && platform.addMessageListener) { - platform.addMessageListener(({ action = '', force, origin, siteTitle, siteImage }, { tab }) => { - if (tab && tab.id) { - switch (action) { - case 'init-provider-request': - this._handleProviderRequest(origin, siteTitle, siteImage, force, tab.id) - break - case 'init-is-approved': - this._handleIsApproved(origin, tab.id) - break - case 'init-is-unlocked': - this._handleIsUnlocked(tab.id) - break - case 'init-privacy-request': - this._handlePrivacyRequest(tab.id) - break - } - } - }) - } + /** + * Called when a user approves access to a full Ethereum provider API + * + * @param {object} opts - opts for the middleware contains the origin for the middleware + */ + createMiddleware ({ origin, getSiteMetadata }) { + return createAsyncMiddleware(async (req, res, next) => { + // only handle requestAccounts + if (req.method !== 'eth_requestAccounts') return next() + // if already approved or privacy mode disabled, return early + const isUnlocked = this.keyringController.memStore.getState().isUnlocked + if (this.shouldExposeAccounts(origin) && isUnlocked) { + res.result = [this.preferencesController.getSelectedAddress()] + return + } + // register the provider request + const metadata = await getSiteMetadata(origin) + this._handleProviderRequest(origin, metadata.name, metadata.icon, false, null) + // wait for resolution of request + const approved = await new Promise(resolve => this.once(`resolvedRequest:${origin}`, ({ approved }) => resolve(approved))) + if (approved) { + res.result = [this.preferencesController.getSelectedAddress()] + } else { + throw new Error('User denied account authorization') + } + }) } /** @@ -59,79 +67,37 @@ class ProviderApprovalController { this.store.updateState({ providerRequests: [{ origin, siteTitle, siteImage, tabID }] }) const isUnlocked = this.keyringController.memStore.getState().isUnlocked if (!force && this.approvedOrigins[origin] && this.caching && isUnlocked) { - this.approveProviderRequest(tabID) return } this.openPopup && this.openPopup() } /** - * Called by a tab to determine if an origin has been approved in the past - * - * @param {string} origin - Origin of the window - */ - _handleIsApproved (origin, tabID) { - this.platform && this.platform.sendMessage({ - action: 'answer-is-approved', - isApproved: this.approvedOrigins[origin] && this.caching, - caching: this.caching, - }, { id: tabID }) - } - - /** - * Called by a tab to determine if MetaMask is currently locked or unlocked - */ - _handleIsUnlocked (tabID) { - const isUnlocked = this.keyringController.memStore.getState().isUnlocked - this.platform && this.platform.sendMessage({ action: 'answer-is-unlocked', isUnlocked }, { id: tabID }) - } - - /** - * Called to check privacy mode; if privacy mode is off, this will automatically enable the provider (legacy behavior) - */ - _handlePrivacyRequest (tabID) { - const privacyMode = this.preferencesController.getFeatureFlags().privacyMode - if (!privacyMode) { - this.platform && this.platform.sendMessage({ - action: 'approve-legacy-provider-request', - selectedAddress: this.publicConfigStore.getState().selectedAddress, - }, { id: tabID }) - this.publicConfigStore.emit('update', this.publicConfigStore.getState()) - } - } - - /** * Called when a user approves access to a full Ethereum provider API * - * @param {string} tabID - ID of the target window that approved provider access + * @param {string} origin - origin of the domain that had provider access approved */ - approveProviderRequest (tabID) { + approveProviderRequestByOrigin (origin) { this.closePopup && this.closePopup() const requests = this.store.getState().providerRequests - const origin = requests.find(request => request.tabID === tabID).origin - this.platform && this.platform.sendMessage({ - action: 'approve-provider-request', - selectedAddress: this.publicConfigStore.getState().selectedAddress, - }, { id: tabID }) - this.publicConfigStore.emit('update', this.publicConfigStore.getState()) - const providerRequests = requests.filter(request => request.tabID !== tabID) + const providerRequests = requests.filter(request => request.origin !== origin) this.store.updateState({ providerRequests }) this.approvedOrigins[origin] = true + this.emit(`resolvedRequest:${origin}`, { approved: true }) } /** * Called when a tab rejects access to a full Ethereum provider API * - * @param {string} tabID - ID of the target window that rejected provider access + * @param {string} origin - origin of the domain that had provider access approved */ - rejectProviderRequest (tabID) { + rejectProviderRequestByOrigin (origin) { this.closePopup && this.closePopup() const requests = this.store.getState().providerRequests - const origin = requests.find(request => request.tabID === tabID).origin - this.platform && this.platform.sendMessage({ action: 'reject-provider-request' }, { id: tabID }) - const providerRequests = requests.filter(request => request.tabID !== tabID) + const providerRequests = requests.filter(request => request.origin !== origin) this.store.updateState({ providerRequests }) delete this.approvedOrigins[origin] + this.emit(`resolvedRequest:${origin}`, { approved: false }) } /** @@ -149,16 +115,10 @@ class ProviderApprovalController { */ shouldExposeAccounts (origin) { const privacyMode = this.preferencesController.getFeatureFlags().privacyMode - return !privacyMode || this.approvedOrigins[origin] + const result = !privacyMode || Boolean(this.approvedOrigins[origin]) + return result } - /** - * Tells all tabs that MetaMask is now locked. This is primarily used to set - * internal flags in the contentscript and inpage script. - */ - setLocked () { - this.platform.sendMessage({ action: 'metamask-set-locked' }) - } } module.exports = ProviderApprovalController diff --git a/app/scripts/controllers/recent-blocks.js b/app/scripts/controllers/recent-blocks.js index 982ad2aa4..a2b5d1bae 100644 --- a/app/scripts/controllers/recent-blocks.js +++ b/app/scripts/controllers/recent-blocks.js @@ -8,8 +8,9 @@ const { RINKEBY, KOVAN, MAINNET, + GOERLI, } = require('./network/enums') -const INFURA_PROVIDER_TYPES = [ROPSTEN, RINKEBY, KOVAN, MAINNET] +const INFURA_PROVIDER_TYPES = [ROPSTEN, RINKEBY, KOVAN, MAINNET, GOERLI] class RecentBlocksController { diff --git a/app/scripts/controllers/shapeshift.js b/app/scripts/controllers/shapeshift.js deleted file mode 100644 index b2a1462c2..000000000 --- a/app/scripts/controllers/shapeshift.js +++ /dev/null @@ -1,180 +0,0 @@ -const ObservableStore = require('obs-store') -const extend = require('xtend') -const log = require('loglevel') - -// every three seconds when an incomplete tx is waiting -const POLLING_INTERVAL = 3000 - -class ShapeshiftController { - - /** - * Controller responsible for managing the list of shapeshift transactions. On construction, it initiates a poll - * that queries a shapeshift.io API for updates to any pending shapeshift transactions - * - * @typedef {Object} ShapeshiftController - * @param {object} opts Overrides the defaults for the initial state of this.store - * @property {array} opts.initState initializes the the state of the ShapeshiftController. Can contain an - * shapeShiftTxList array. - * @property {array} shapeShiftTxList An array of ShapeShiftTx objects - * - */ - constructor (opts = {}) { - const initState = extend({ - shapeShiftTxList: [], - }, opts.initState) - this.store = new ObservableStore(initState) - this.pollForUpdates() - } - - /** - * Represents, and contains data about, a single shapeshift transaction. - * @typedef {Object} ShapeShiftTx - * @property {string} depositAddress - An address at which to send a crypto deposit, so that eth can be sent to the - * user's Metamask account - * @property {string} depositType - An abbreviation of the type of crypto currency to be deposited. - * @property {string} key - The 'shapeshift' key differentiates this from other types of txs in Metamask - * @property {number} time - The time at which the tx was created - * @property {object} response - Initiated as an empty object, which will be replaced by a Response object. @see {@link - * https://developer.mozilla.org/en-US/docs/Web/API/Response} - */ - - // - // PUBLIC METHODS - // - - /** - * A getter for the shapeShiftTxList property - * - * @returns {array<ShapeShiftTx>} - * - */ - getShapeShiftTxList () { - const shapeShiftTxList = this.store.getState().shapeShiftTxList - return shapeShiftTxList - } - - /** - * A getter for all ShapeShiftTx in the shapeShiftTxList that have not successfully completed a deposit. - * - * @returns {array<ShapeShiftTx>} Only includes ShapeShiftTx which has a response property with a status !== complete - * - */ - getPendingTxs () { - const txs = this.getShapeShiftTxList() - const pending = txs.filter(tx => tx.response && tx.response.status !== 'complete') - return pending - } - - /** - * A poll that exists as long as there are pending transactions. Each call attempts to update the data of any - * pendingTxs, and then calls itself again. If there are no pending txs, the recursive call is not made and - * the polling stops. - * - * this.updateTx is used to attempt the update to the pendingTxs in the ShapeShiftTxList, and that updated data - * is saved with saveTx. - * - */ - pollForUpdates () { - const pendingTxs = this.getPendingTxs() - - if (pendingTxs.length === 0) { - return - } - - Promise.all(pendingTxs.map((tx) => { - return this.updateTx(tx) - })) - .then((results) => { - results.forEach(tx => this.saveTx(tx)) - this.timeout = setTimeout(this.pollForUpdates.bind(this), POLLING_INTERVAL) - }) - } - - /** - * Attempts to update a ShapeShiftTx with data from a shapeshift.io API. Both the response and time properties - * can be updated. The response property is updated with every call, but the time property is only updated when - * the response status updates to 'complete'. This will occur once the user makes a deposit as the ShapeShiftTx - * depositAddress - * - * @param {ShapeShiftTx} tx The tx to update - * - */ - async updateTx (tx) { - try { - const url = `https://shapeshift.io/txStat/${tx.depositAddress}` - const response = await fetch(url) - const json = await response.json() - tx.response = json - if (tx.response.status === 'complete') { - tx.time = new Date().getTime() - } - return tx - } catch (err) { - log.warn(err) - } - } - - /** - * Saves an updated to a ShapeShiftTx in the shapeShiftTxList. If the passed ShapeShiftTx is not in the - * shapeShiftTxList, nothing happens. - * - * @param {ShapeShiftTx} tx The updated tx to save, if it exists in the current shapeShiftTxList - * - */ - saveTx (tx) { - const { shapeShiftTxList } = this.store.getState() - const index = shapeShiftTxList.indexOf(tx) - if (index !== -1) { - shapeShiftTxList[index] = tx - this.store.updateState({ shapeShiftTxList }) - } - } - - /** - * Removes a ShapeShiftTx from the shapeShiftTxList - * - * @param {ShapeShiftTx} tx The tx to remove - * - */ - removeShapeShiftTx (tx) { - const { shapeShiftTxList } = this.store.getState() - const index = shapeShiftTxList.indexOf(index) - if (index !== -1) { - shapeShiftTxList.splice(index, 1) - } - this.updateState({ shapeShiftTxList }) - } - - /** - * Creates a new ShapeShiftTx, adds it to the shapeShiftTxList, and initiates a new poll for updates of pending txs - * - * @param {string} depositAddress - An address at which to send a crypto deposit, so that eth can be sent to the - * user's Metamask account - * @param {string} depositType - An abbreviation of the type of crypto currency to be deposited. - * - */ - createShapeShiftTx (depositAddress, depositType) { - const state = this.store.getState() - let { shapeShiftTxList } = state - - var shapeShiftTx = { - depositAddress, - depositType, - key: 'shapeshift', - time: new Date().getTime(), - response: {}, - } - - if (!shapeShiftTxList) { - shapeShiftTxList = [shapeShiftTx] - } else { - shapeShiftTxList.push(shapeShiftTx) - } - - this.store.updateState({ shapeShiftTxList }) - this.pollForUpdates() - } - -} - -module.exports = ShapeshiftController diff --git a/app/scripts/controllers/token-rates.js b/app/scripts/controllers/token-rates.js index 867d36433..6b6265dba 100644 --- a/app/scripts/controllers/token-rates.js +++ b/app/scripts/controllers/token-rates.js @@ -1,6 +1,8 @@ const ObservableStore = require('obs-store') const log = require('loglevel') const normalizeAddress = require('eth-sig-util').normalize +const ethUtil = require('ethereumjs-util') + // By default, poll every 3 minutes const DEFAULT_INTERVAL = 180 * 1000 @@ -28,16 +30,16 @@ class TokenRatesController { async updateExchangeRates () { if (!this.isActive) { return } const contractExchangeRates = {} - const nativeCurrency = this.currency ? this.currency.getState().nativeCurrency.toUpperCase() : 'ETH' - const pairs = this._tokens.map(token => `pairs[]=${token.address}/${nativeCurrency}`) - const query = pairs.join('&') + const nativeCurrency = this.currency ? this.currency.getState().nativeCurrency.toLowerCase() : 'eth' + const pairs = this._tokens.map(token => token.address).join(',') + const query = `contract_addresses=${pairs}&vs_currencies=${nativeCurrency}` if (this._tokens.length > 0) { try { - const response = await fetch(`https://exchanges.balanc3.net/pie?${query}&autoConversion=false`) - const { prices = [] } = await response.json() - prices.forEach(({ pair, price }) => { - const address = pair.split('/')[0] - contractExchangeRates[normalizeAddress(address)] = typeof price === 'number' ? price : 0 + const response = await fetch(`https://api.coingecko.com/api/v3/simple/token_price/ethereum?${query}`) + const prices = await response.json() + this._tokens.forEach(token => { + const price = prices[token.address.toLowerCase()] || prices[ethUtil.toChecksumAddress(token.address)] + contractExchangeRates[normalizeAddress(token.address)] = price ? price[nativeCurrency] : 0 }) } catch (error) { log.warn(`MetaMask - TokenRatesController exchange rate fetch failed.`, error) diff --git a/app/scripts/controllers/transactions/index.js b/app/scripts/controllers/transactions/index.js index 2ce736beb..1ae925835 100644 --- a/app/scripts/controllers/transactions/index.js +++ b/app/scripts/controllers/transactions/index.js @@ -3,10 +3,21 @@ const ObservableStore = require('obs-store') const ethUtil = require('ethereumjs-util') const Transaction = require('ethereumjs-tx') const EthQuery = require('ethjs-query') +const abi = require('human-standard-token-abi') +const abiDecoder = require('abi-decoder') +abiDecoder.addABI(abi) +const { + TOKEN_METHOD_APPROVE, + TOKEN_METHOD_TRANSFER, + TOKEN_METHOD_TRANSFER_FROM, + SEND_ETHER_ACTION_KEY, + DEPLOY_CONTRACT_ACTION_KEY, + CONTRACT_INTERACTION_KEY, +} = require('../../../../ui/app/helpers/constants/transactions.js') const TransactionStateManager = require('./tx-state-manager') const TxGasUtil = require('./tx-gas-utils') const PendingTransactionTracker = require('./pending-tx-tracker') -const NonceTracker = require('./nonce-tracker') +const NonceTracker = require('nonce-tracker') const txUtils = require('./lib/util') const cleanErrorStack = require('../../lib/cleanErrorStack') const log = require('loglevel') @@ -180,9 +191,11 @@ class TransactionController extends EventEmitter { } txUtils.validateTxParams(normalizedTxParams) // construct txMeta + const { transactionCategory, getCodeResponse } = await this._determineTransactionCategory(txParams) let txMeta = this.txStateManager.generateTxMeta({ txParams: normalizedTxParams, type: TRANSACTION_TYPE_STANDARD, + transactionCategory, }) this.addTx(txMeta) this.emit('newUnapprovedTx', txMeta) @@ -191,7 +204,7 @@ class TransactionController extends EventEmitter { // check whether recipient account is blacklisted recipientBlacklistChecker.checkAccount(txMeta.metamaskNetworkId, normalizedTxParams.to) // add default tx params - txMeta = await this.addTxGasDefaults(txMeta) + txMeta = await this.addTxGasDefaults(txMeta, getCodeResponse) } catch (error) { log.warn(error) txMeta.loadingDefaults = false @@ -211,7 +224,7 @@ class TransactionController extends EventEmitter { @param txMeta {Object} - the txMeta object @returns {Promise<object>} resolves with txMeta */ - async addTxGasDefaults (txMeta) { + async addTxGasDefaults (txMeta, getCodeResponse) { const txParams = txMeta.txParams // ensure value txParams.value = txParams.value ? ethUtil.addHexPrefix(txParams.value) : '0x0' @@ -222,7 +235,7 @@ class TransactionController extends EventEmitter { } txParams.gasPrice = ethUtil.addHexPrefix(gasPrice.toString(16)) // set gasLimit - return await this.txGasUtil.analyzeGasUsage(txMeta) + return await this.txGasUtil.analyzeGasUsage(txMeta, getCodeResponse) } /** @@ -542,6 +555,7 @@ class TransactionController extends EventEmitter { }) this.pendingTxTracker.on('tx:failed', this.txStateManager.setTxStatusFailed.bind(this.txStateManager)) this.pendingTxTracker.on('tx:confirmed', (txId) => this.confirmTransaction(txId)) + this.pendingTxTracker.on('tx:dropped', this.txStateManager.setTxStatusDropped.bind(this.txStateManager)) this.pendingTxTracker.on('tx:block-update', (txMeta, latestBlockNumber) => { if (!txMeta.firstRetryBlockNumber) { txMeta.firstRetryBlockNumber = latestBlockNumber @@ -556,6 +570,43 @@ class TransactionController extends EventEmitter { } /** + Returns a "type" for a transaction out of the following list: simpleSend, tokenTransfer, tokenApprove, + contractDeployment, contractMethodCall + */ + async _determineTransactionCategory (txParams) { + const { data, to } = txParams + const { name } = data && abiDecoder.decodeMethod(data) || {} + const tokenMethodName = [ + TOKEN_METHOD_APPROVE, + TOKEN_METHOD_TRANSFER, + TOKEN_METHOD_TRANSFER_FROM, + ].find(tokenMethodName => tokenMethodName === name && name.toLowerCase()) + + let result + let code + if (!txParams.data) { + result = SEND_ETHER_ACTION_KEY + } else if (tokenMethodName) { + result = tokenMethodName + } else if (!to) { + result = DEPLOY_CONTRACT_ACTION_KEY + } else { + try { + code = await this.query.getCode(to) + } catch (e) { + code = null + log.warn(e) + } + // For an address with no code, geth will return '0x', and ganache-core v2.2.1 will return '0x0' + const codeIsEmpty = !code || code === '0x' || code === '0x0' + + result = codeIsEmpty ? SEND_ETHER_ACTION_KEY : CONTRACT_INTERACTION_KEY + } + + return { transactionCategory: result, getCodeResponse: code } + } + + /** Sets other txMeta statuses to dropped if the txMeta that has been confirmed has other transactions in the list have the same nonce diff --git a/app/scripts/controllers/transactions/nonce-tracker.js b/app/scripts/controllers/transactions/nonce-tracker.js deleted file mode 100644 index 421036368..000000000 --- a/app/scripts/controllers/transactions/nonce-tracker.js +++ /dev/null @@ -1,161 +0,0 @@ -const EthQuery = require('ethjs-query') -const assert = require('assert') -const Mutex = require('await-semaphore').Mutex -/** - @param opts {Object} - @param {Object} opts.provider a ethereum provider - @param {Function} opts.getPendingTransactions a function that returns an array of txMeta - whosee status is `submitted` - @param {Function} opts.getConfirmedTransactions a function that returns an array of txMeta - whose status is `confirmed` - @class -*/ -class NonceTracker { - - constructor ({ provider, blockTracker, getPendingTransactions, getConfirmedTransactions }) { - this.provider = provider - this.blockTracker = blockTracker - this.ethQuery = new EthQuery(provider) - this.getPendingTransactions = getPendingTransactions - this.getConfirmedTransactions = getConfirmedTransactions - this.lockMap = {} - } - - /** - @returns {Promise<Object>} with the key releaseLock (the gloabl mutex) - */ - async getGlobalLock () { - const globalMutex = this._lookupMutex('global') - // await global mutex free - const releaseLock = await globalMutex.acquire() - return { releaseLock } - } - - /** - * @typedef NonceDetails - * @property {number} highestLocallyConfirmed - A hex string of the highest nonce on a confirmed transaction. - * @property {number} nextNetworkNonce - The next nonce suggested by the eth_getTransactionCount method. - * @property {number} highestSuggested - The maximum between the other two, the number returned. - */ - - /** - this will return an object with the `nextNonce` `nonceDetails` of type NonceDetails, and the releaseLock - Note: releaseLock must be called after adding a signed tx to pending transactions (or discarding). - - @param address {string} the hex string for the address whose nonce we are calculating - @returns {Promise<NonceDetails>} - */ - async getNonceLock (address) { - // await global mutex free - await this._globalMutexFree() - // await lock free, then take lock - const releaseLock = await this._takeMutex(address) - try { - // evaluate multiple nextNonce strategies - const nonceDetails = {} - const networkNonceResult = await this._getNetworkNextNonce(address) - const highestLocallyConfirmed = this._getHighestLocallyConfirmed(address) - const nextNetworkNonce = networkNonceResult.nonce - const highestSuggested = Math.max(nextNetworkNonce, highestLocallyConfirmed) - - const pendingTxs = this.getPendingTransactions(address) - const localNonceResult = this._getHighestContinuousFrom(pendingTxs, highestSuggested) || 0 - - nonceDetails.params = { - highestLocallyConfirmed, - highestSuggested, - nextNetworkNonce, - } - nonceDetails.local = localNonceResult - nonceDetails.network = networkNonceResult - - const nextNonce = Math.max(networkNonceResult.nonce, localNonceResult.nonce) - assert(Number.isInteger(nextNonce), `nonce-tracker - nextNonce is not an integer - got: (${typeof nextNonce}) "${nextNonce}"`) - - // return nonce and release cb - return { nextNonce, nonceDetails, releaseLock } - } catch (err) { - // release lock if we encounter an error - releaseLock() - throw err - } - } - - async _globalMutexFree () { - const globalMutex = this._lookupMutex('global') - const releaseLock = await globalMutex.acquire() - releaseLock() - } - - async _takeMutex (lockId) { - const mutex = this._lookupMutex(lockId) - const releaseLock = await mutex.acquire() - return releaseLock - } - - _lookupMutex (lockId) { - let mutex = this.lockMap[lockId] - if (!mutex) { - mutex = new Mutex() - this.lockMap[lockId] = mutex - } - return mutex - } - - async _getNetworkNextNonce (address) { - // calculate next nonce - // we need to make sure our base count - // and pending count are from the same block - const blockNumber = await this.blockTracker.getLatestBlock() - const baseCountBN = await this.ethQuery.getTransactionCount(address, blockNumber) - const baseCount = baseCountBN.toNumber() - assert(Number.isInteger(baseCount), `nonce-tracker - baseCount is not an integer - got: (${typeof baseCount}) "${baseCount}"`) - const nonceDetails = { blockNumber, baseCount } - return { name: 'network', nonce: baseCount, details: nonceDetails } - } - - _getHighestLocallyConfirmed (address) { - const confirmedTransactions = this.getConfirmedTransactions(address) - const highest = this._getHighestNonce(confirmedTransactions) - return Number.isInteger(highest) ? highest + 1 : 0 - } - - _getHighestNonce (txList) { - const nonces = txList.map((txMeta) => { - const nonce = txMeta.txParams.nonce - assert(typeof nonce, 'string', 'nonces should be hex strings') - return parseInt(nonce, 16) - }) - const highestNonce = Math.max.apply(null, nonces) - return highestNonce - } - - /** - @typedef {object} highestContinuousFrom - @property {string} - name the name for how the nonce was calculated based on the data used - @property {number} - nonce the next suggested nonce - @property {object} - details the provided starting nonce that was used (for debugging) - */ - /** - @param txList {array} - list of txMeta's - @param startPoint {number} - the highest known locally confirmed nonce - @returns {highestContinuousFrom} - */ - _getHighestContinuousFrom (txList, startPoint) { - const nonces = txList.map((txMeta) => { - const nonce = txMeta.txParams.nonce - assert(typeof nonce, 'string', 'nonces should be hex strings') - return parseInt(nonce, 16) - }) - - let highest = startPoint - while (nonces.includes(highest)) { - highest++ - } - - return { name: 'local', nonce: highest, details: { startPoint, highest } } - } - -} - -module.exports = NonceTracker diff --git a/app/scripts/controllers/transactions/pending-tx-tracker.js b/app/scripts/controllers/transactions/pending-tx-tracker.js index 4bf40b1db..bc11f6633 100644 --- a/app/scripts/controllers/transactions/pending-tx-tracker.js +++ b/app/scripts/controllers/transactions/pending-tx-tracker.js @@ -22,6 +22,7 @@ const EthQuery = require('ethjs-query') class PendingTransactionTracker extends EventEmitter { constructor (config) { super() + this.droppedBuffer = {} this.query = new EthQuery(config.provider) this.nonceTracker = config.nonceTracker this.getPendingTransactions = config.getPendingTransactions @@ -139,22 +140,42 @@ class PendingTransactionTracker extends EventEmitter { const noTxHashErr = new Error('We had an error while submitting this transaction, please try again.') noTxHashErr.name = 'NoTxHashError' this.emit('tx:failed', txId, noTxHashErr) + return } - // If another tx with the same nonce is mined, set as failed. + // If another tx with the same nonce is mined, set as dropped. const taken = await this._checkIfNonceIsTaken(txMeta) - if (taken) { - const nonceTakenErr = new Error('Another transaction with this nonce has been mined.') - nonceTakenErr.name = 'NonceTakenErr' - return this.emit('tx:failed', txId, nonceTakenErr) + let dropped + try { + // check the network if the nonce is ahead the tx + // and the tx has not been mined into a block + + dropped = await this._checkIftxWasDropped(txMeta) + // the dropped buffer is in case we ask a node for the tx + // that is behind the node we asked for tx count + // IS A SECURITY FOR HITTING NODES IN INFURA THAT COULD GO OUT + // OF SYNC. + // on the next block event it will return fire as dropped + if (dropped && !this.droppedBuffer[txHash]) { + this.droppedBuffer[txHash] = true + dropped = false + } else if (dropped && this.droppedBuffer[txHash]) { + // clean up + delete this.droppedBuffer[txHash] + } + + } catch (e) { + log.error(e) + } + if (taken || dropped) { + return this.emit('tx:dropped', txId) } // get latest transaction status try { - const txParams = await this.query.getTransactionByHash(txHash) - if (!txParams) return - if (txParams.blockNumber) { + const { blockNumber } = await this.query.getTransactionByHash(txHash) || {} + if (blockNumber) { this.emit('tx:confirmed', txId) } } catch (err) { @@ -165,6 +186,22 @@ class PendingTransactionTracker extends EventEmitter { this.emit('tx:warning', txMeta, err) } } + /** + checks to see if if the tx's nonce has been used by another transaction + @param txMeta {Object} - txMeta object + @emits tx:dropped + @returns {boolean} + */ + + async _checkIftxWasDropped (txMeta) { + const { txParams: { nonce, from }, hash } = txMeta + const nextNonce = await this.query.getTransactionCount(from) + const { blockNumber } = await this.query.getTransactionByHash(hash) || {} + if (!blockNumber && parseInt(nextNonce) > parseInt(nonce)) { + return true + } + return false + } /** checks to see if a confirmed txMeta has the same nonce diff --git a/app/scripts/controllers/transactions/tx-gas-utils.js b/app/scripts/controllers/transactions/tx-gas-utils.js index 765551167..287fb6f44 100644 --- a/app/scripts/controllers/transactions/tx-gas-utils.js +++ b/app/scripts/controllers/transactions/tx-gas-utils.js @@ -4,7 +4,9 @@ const { BnMultiplyByFraction, bnToHex, } = require('../../lib/util') +const log = require('loglevel') const { addHexPrefix } = require('ethereumjs-util') +const { SEND_ETHER_ACTION_KEY } = require('../../../../ui/app/helpers/constants/transactions.js') const SIMPLE_GAS_COST = '0x5208' // Hex for 21000, cost of a simple send. import { TRANSACTION_NO_CONTRACT_ERROR_KEY } from '../../../../ui/app/helpers/constants/error-keys' @@ -26,12 +28,13 @@ class TxGasUtil { @param txMeta {Object} - the txMeta object @returns {object} the txMeta object with the gas written to the txParams */ - async analyzeGasUsage (txMeta) { + async analyzeGasUsage (txMeta, getCodeResponse) { const block = await this.query.getBlockByNumber('latest', false) let estimatedGasHex try { - estimatedGasHex = await this.estimateTxGas(txMeta, block.gasLimit) + estimatedGasHex = await this.estimateTxGas(txMeta, block.gasLimit, getCodeResponse) } catch (err) { + log.warn(err) txMeta.simulationFails = { reason: err.message, errorKey: err.errorKey, @@ -54,7 +57,7 @@ class TxGasUtil { @param blockGasLimitHex {string} - hex string of the block's gas limit @returns {string} the estimated gas limit as a hex string */ - async estimateTxGas (txMeta, blockGasLimitHex) { + async estimateTxGas (txMeta, blockGasLimitHex, getCodeResponse) { const txParams = txMeta.txParams // check if gasLimit is already specified @@ -70,11 +73,10 @@ class TxGasUtil { // see if we can set the gas based on the recipient if (hasRecipient) { - const code = await this.query.getCode(recipient) // For an address with no code, geth will return '0x', and ganache-core v2.2.1 will return '0x0' - const codeIsEmpty = !code || code === '0x' || code === '0x0' + const categorizedAsSimple = txMeta.transactionCategory === SEND_ETHER_ACTION_KEY - if (codeIsEmpty) { + if (categorizedAsSimple) { // if there's data in the params, but there's no contract code, it's not a valid transaction if (txParams.data) { const err = new Error('TxGasUtil - Trying to call a function on a non-contract address') @@ -82,7 +84,7 @@ class TxGasUtil { err.errorKey = TRANSACTION_NO_CONTRACT_ERROR_KEY // set the response on the error so that we can see in logs what the actual response was - err.getCodeResponse = code + err.getCodeResponse = getCodeResponse throw err } diff --git a/app/scripts/controllers/transactions/tx-state-manager.js b/app/scripts/controllers/transactions/tx-state-manager.js index 420191d9c..1a2cb5dee 100644 --- a/app/scripts/controllers/transactions/tx-state-manager.js +++ b/app/scripts/controllers/transactions/tx-state-manager.js @@ -126,10 +126,10 @@ class TransactionStateManager extends EventEmitter { @returns {object} the txMeta */ addTx (txMeta) { - this.once(`${txMeta.id}:signed`, function (txId) { + this.once(`${txMeta.id}:signed`, function () { this.removeAllListeners(`${txMeta.id}:rejected`) }) - this.once(`${txMeta.id}:rejected`, function (txId) { + this.once(`${txMeta.id}:rejected`, function () { this.removeAllListeners(`${txMeta.id}:signed`) }) // initialize history diff --git a/app/scripts/controllers/user-actions.js b/app/scripts/controllers/user-actions.js deleted file mode 100644 index f777054b8..000000000 --- a/app/scripts/controllers/user-actions.js +++ /dev/null @@ -1,17 +0,0 @@ -const MessageManager = require('./lib/message-manager') -const PersonalMessageManager = require('./lib/personal-message-manager') -const TypedMessageManager = require('./lib/typed-message-manager') - -class UserActionController { - - constructor (opts = {}) { - - this.messageManager = new MessageManager() - this.personalMessageManager = new PersonalMessageManager() - this.typedMessageManager = new TypedMessageManager() - - } - -} - -module.exports = UserActionController |