From e09e54bebb55735de777811fde37dbece6dad0f5 Mon Sep 17 00:00:00 2001 From: Jenny Pollack Date: Mon, 25 Mar 2019 23:27:36 -1000 Subject: remove user actions controller --- app/scripts/controllers/user-actions.js | 17 ----------------- 1 file changed, 17 deletions(-) delete mode 100644 app/scripts/controllers/user-actions.js (limited to 'app') 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 -- cgit v1.2.3 From 89693a23151d4ff37295f14b04d7c64297d842c5 Mon Sep 17 00:00:00 2001 From: kumavis Date: Thu, 4 Apr 2019 22:11:28 +0800 Subject: metamask-controller - use improved provider-as-middleware utility --- app/scripts/lib/createProviderMiddleware.js | 16 ---------------- app/scripts/metamask-controller.js | 4 ++-- 2 files changed, 2 insertions(+), 18 deletions(-) delete mode 100644 app/scripts/lib/createProviderMiddleware.js (limited to 'app') diff --git a/app/scripts/lib/createProviderMiddleware.js b/app/scripts/lib/createProviderMiddleware.js deleted file mode 100644 index 8a939ba4e..000000000 --- a/app/scripts/lib/createProviderMiddleware.js +++ /dev/null @@ -1,16 +0,0 @@ -module.exports = createProviderMiddleware - -/** - * Forwards an HTTP request to the current Web3 provider - * - * @param {{ provider: Object }} config Configuration containing current Web3 provider - */ -function createProviderMiddleware ({ provider }) { - return (req, res, next, end) => { - provider.sendAsync(req, (err, _res) => { - if (err) return end(err) - res.result = _res.result - end() - }) - } -} diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index 4108ed4c0..d2a55acaa 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -18,7 +18,7 @@ const createFilterMiddleware = require('eth-json-rpc-filters') const createSubscriptionManager = require('eth-json-rpc-filters/subscriptionManager') const createOriginMiddleware = require('./lib/createOriginMiddleware') const createLoggerMiddleware = require('./lib/createLoggerMiddleware') -const createProviderMiddleware = require('./lib/createProviderMiddleware') +const providerAsMiddleware = require('eth-json-rpc-middleware/providerAsMiddleware') const {setupMultiplex} = require('./lib/stream-utils.js') const KeyringController = require('eth-keyring-controller') const NetworkController = require('./controllers/network') @@ -1373,7 +1373,7 @@ module.exports = class MetamaskController extends EventEmitter { // watch asset engine.push(this.preferencesController.requestWatchAsset.bind(this.preferencesController)) // forward to metamask primary provider - engine.push(createProviderMiddleware({ provider })) + engine.push(providerAsMiddleware(provider)) // setup connection const providerStream = createEngineStream({ engine }) -- cgit v1.2.3 From 79804ec79bda1cffa530743ddb8d299c257092a2 Mon Sep 17 00:00:00 2001 From: Dan J Miller Date: Mon, 8 Apr 2019 15:52:23 -0230 Subject: Version 6.3.2 (#6418) --- app/manifest.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'app') diff --git a/app/manifest.json b/app/manifest.json index 941842636..5e464dc2f 100644 --- a/app/manifest.json +++ b/app/manifest.json @@ -1,7 +1,7 @@ { "name": "__MSG_appName__", "short_name": "__MSG_appName__", - "version": "6.3.1", + "version": "6.3.2", "manifest_version": 2, "author": "https://metamask.io", "description": "__MSG_appDescription__", -- cgit v1.2.3 From 24761326de652e14533c8f51498a25e875ab429b Mon Sep 17 00:00:00 2001 From: Whymarrh Whitby Date: Tue, 9 Apr 2019 14:14:04 -0230 Subject: Don't inject web3 on sharefile.com --- app/scripts/contentscript.js | 1 + 1 file changed, 1 insertion(+) (limited to 'app') diff --git a/app/scripts/contentscript.js b/app/scripts/contentscript.js index 68b6117e5..2325cecdd 100644 --- a/app/scripts/contentscript.js +++ b/app/scripts/contentscript.js @@ -275,6 +275,7 @@ function blacklistedDomainCheck () { 'harbourair.com', 'ani.gamer.com.tw', 'blueskybooking.com', + 'sharefile.com', ] const currentUrl = window.location.href let currentRegex -- cgit v1.2.3 From d7a2ea9a2b28678bf46dfb606187f4bbb7d22453 Mon Sep 17 00:00:00 2001 From: Thomas Huang Date: Wed, 10 Apr 2019 16:34:13 -0500 Subject: Add Localhost 8545 for network dropdown names --- app/_locales/en/messages.json | 3 +++ 1 file changed, 3 insertions(+) (limited to 'app') diff --git a/app/_locales/en/messages.json b/app/_locales/en/messages.json index 154925d1a..f9d23f69e 100644 --- a/app/_locales/en/messages.json +++ b/app/_locales/en/messages.json @@ -308,6 +308,9 @@ "connectingToRinkeby": { "message": "Connecting to Rinkeby Test Network" }, + "connectingToLocalhost": { + "message": "Connecting to Localhost 8545" + }, "connectingToUnknown": { "message": "Connecting to Unknown Network" }, -- cgit v1.2.3 From a973a7420abed55998b88c339d1c6a1c53bd63e2 Mon Sep 17 00:00:00 2001 From: Paul Bouchon Date: Wed, 10 Apr 2019 18:37:15 -0400 Subject: feature: switch token pricing to CoinGecko API (#6424) --- app/scripts/controllers/token-rates.js | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) (limited to 'app') diff --git a/app/scripts/controllers/token-rates.js b/app/scripts/controllers/token-rates.js index 867d36433..4e396bb59 100644 --- a/app/scripts/controllers/token-rates.js +++ b/app/scripts/controllers/token-rates.js @@ -28,16 +28,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()] + contractExchangeRates[normalizeAddress(token.address)] = price ? price[nativeCurrency] : 0 }) } catch (error) { log.warn(`MetaMask - TokenRatesController exchange rate fetch failed.`, error) -- cgit v1.2.3 From 33836c04638bd0ef023ed913e4c8ec976ad3caae Mon Sep 17 00:00:00 2001 From: Thomas Huang Date: Tue, 16 Apr 2019 11:15:53 -0600 Subject: Set rpcTarget, nickname, and ticker when selecting one of the default networks --- app/scripts/controllers/network/network.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'app') diff --git a/app/scripts/controllers/network/network.js b/app/scripts/controllers/network/network.js index 47432c1e2..ab1198dd3 100644 --- a/app/scripts/controllers/network/network.js +++ b/app/scripts/controllers/network/network.js @@ -140,10 +140,10 @@ module.exports = class NetworkController extends EventEmitter { 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 } -- cgit v1.2.3 From 0db0a187c8f30dcf332cf4d41a2f957171b25630 Mon Sep 17 00:00:00 2001 From: Paul Bouchon Date: Wed, 17 Apr 2019 13:34:49 -0400 Subject: feature: add Goerli support (#6459) --- app/_locales/en/messages.json | 6 ++++++ app/scripts/controllers/network/createInfuraClient.js | 4 ++++ app/scripts/controllers/network/enums.js | 6 ++++++ app/scripts/controllers/network/network.js | 3 ++- app/scripts/controllers/network/util.js | 5 +++++ app/scripts/controllers/recent-blocks.js | 3 ++- app/scripts/lib/buy-eth-url.js | 4 ++++ 7 files changed, 29 insertions(+), 2 deletions(-) (limited to 'app') diff --git a/app/_locales/en/messages.json b/app/_locales/en/messages.json index f9d23f69e..b0cd1ca8b 100644 --- a/app/_locales/en/messages.json +++ b/app/_locales/en/messages.json @@ -311,6 +311,9 @@ "connectingToLocalhost": { "message": "Connecting to Localhost 8545" }, + "connectingToGoerli": { + "message": "Connecting to Goerli Test Network" + }, "connectingToUnknown": { "message": "Connecting to Unknown Network" }, @@ -1229,6 +1232,9 @@ "ropsten": { "message": "Ropsten Test Network" }, + "goerli": { + "message": "Goerli Test Network" + }, "rpc": { "message": "Custom RPC" }, diff --git a/app/scripts/controllers/network/createInfuraClient.js b/app/scripts/controllers/network/createInfuraClient.js index 884b94db3..70b332867 100644 --- a/app/scripts/controllers/network/createInfuraClient.js +++ b/app/scripts/controllers/network/createInfuraClient.js @@ -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/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 ab1198dd3..c00ac7e6a 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 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/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/lib/buy-eth-url.js b/app/scripts/lib/buy-eth-url.js index fbe6c6c9e..5cae83a9f 100644 --- a/app/scripts/lib/buy-eth-url.js +++ b/app/scripts/lib/buy-eth-url.js @@ -28,6 +28,8 @@ function getBuyEthUrl ({ network, amount, address, service }) { return 'https://www.rinkeby.io/' case 'kovan-faucet': return 'https://github.com/kovan-testnet/faucet' + case 'goerli-faucet': + return 'https://goerli-faucet.slock.it/' } throw new Error(`Unknown cryptocurrency exchange or faucet: "${service}"`) } @@ -42,6 +44,8 @@ function getDefaultServiceForNetwork (network) { return 'rinkeby-faucet' case '42': return 'kovan-faucet' + case '5': + return 'goerli-faucet' } throw new Error(`No default cryptocurrency exchange or faucet for networkId: "${network}"`) } -- cgit v1.2.3 From 931aaeb7003f175374a06eb949cd47a12ebc8bbf Mon Sep 17 00:00:00 2001 From: Chi Kei Chan Date: Wed, 17 Apr 2019 12:15:13 -0700 Subject: Add token selection to the send screen (#6445) * Move send to pages/ * Fix unit tests * Finish UI * Integrate asset dropdown to send actions * Remove console.log * Hide asset change during edit * Enable switch from send token to seand eth * Enable switching from token to eth when editing * Fix linter * Fixing test * Fix unit tests * Fix linter * Fix react warning; remove console.log * fix flat test * Add metrics * Address code review comments * Consistent spacing between send screen form rows. * Reduce height of gas buttons on send screen. * Make send screen gas button height dependent on size of contents. --- app/_locales/en/messages.json | 6 ++++++ 1 file changed, 6 insertions(+) (limited to 'app') diff --git a/app/_locales/en/messages.json b/app/_locales/en/messages.json index b0cd1ca8b..184507cbb 100644 --- a/app/_locales/en/messages.json +++ b/app/_locales/en/messages.json @@ -139,6 +139,9 @@ "approved": { "message": "Approved" }, + "asset": { + "message": "Asset" + }, "attemptingConnect": { "message": "Attempting to connect to blockchain." }, @@ -1351,6 +1354,9 @@ "selectAnAccountHelp": { "message": "Select the account to view in MetaMask" }, + "selectAnAsset": { + "message": "Select an Asset" + }, "selectAHigherGasFee": { "message": "Select a higher gas fee to accelerate the processing of your transaction.*" }, -- cgit v1.2.3 From 49d77415db2f0d4ee410b41015026269b5d8c286 Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Wed, 17 Apr 2019 12:23:03 -0700 Subject: Version 6.4.0 RC1 --- app/manifest.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'app') diff --git a/app/manifest.json b/app/manifest.json index 5e464dc2f..3dc815c51 100644 --- a/app/manifest.json +++ b/app/manifest.json @@ -1,7 +1,7 @@ { "name": "__MSG_appName__", "short_name": "__MSG_appName__", - "version": "6.3.2", + "version": "6.4.0", "manifest_version": 2, "author": "https://metamask.io", "description": "__MSG_appDescription__", -- cgit v1.2.3 From 6a60562d6649d88f24bd849b325871bb256a0001 Mon Sep 17 00:00:00 2001 From: Sneh Koul <35871990+Sneh1999@users.noreply.github.com> Date: Wed, 24 Apr 2019 10:49:38 -0700 Subject: =?UTF-8?q?Opens=20the=20original=20webpage=20from=20where=20insta?= =?UTF-8?q?llation=20of=20MetaMask=20was=20re=E2=80=A6=20(#6272)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Opens the original webpage from where installation of MetaMask was requested * Asking for dynamic permissions * code for forwarder/without extra permissions * Lint fix for onboardingComplete message sending code. --- app/scripts/inpage.js | 6 ++++++ app/scripts/metamask-controller.js | 1 + 2 files changed, 7 insertions(+) (limited to 'app') diff --git a/app/scripts/inpage.js b/app/scripts/inpage.js index 68394d1ae..71cfb875c 100644 --- a/app/scripts/inpage.js +++ b/app/scripts/inpage.js @@ -218,6 +218,12 @@ inpageProvider.publicConfigStore.subscribe(function (state) { web3.eth.defaultAccount = state.selectedAddress }) +inpageProvider.publicConfigStore.subscribe(function (state) { + if (state.onboardingcomplete) { + window.postMessage('onboardingcomplete', '*') + } +}) + // need to make sure we aren't affected by overlapping namespaces // and that we dont affect the app with our namespace // mostly a fix for web3's BigNumber if AMD's "define" is defined... diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index d2a55acaa..0506e3116 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -320,6 +320,7 @@ module.exports = class MetamaskController extends EventEmitter { const result = { selectedAddress: memState.isUnlocked ? memState.selectedAddress : undefined, networkVersion: memState.network, + onboardingcomplete: memState.completedOnboarding, } return result } -- cgit v1.2.3 From a56fc14122898d82b2ab6b28c2f7a68816c83a86 Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Fri, 26 Apr 2019 10:28:39 -0700 Subject: Version 6.4.1 RC1 --- app/manifest.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'app') diff --git a/app/manifest.json b/app/manifest.json index 3dc815c51..bd10f60da 100644 --- a/app/manifest.json +++ b/app/manifest.json @@ -1,7 +1,7 @@ { "name": "__MSG_appName__", "short_name": "__MSG_appName__", - "version": "6.4.0", + "version": "6.4.1", "manifest_version": 2, "author": "https://metamask.io", "description": "__MSG_appDescription__", -- cgit v1.2.3 From 4fea9d0cc2ec9c6914931d5e310665aca8e273b6 Mon Sep 17 00:00:00 2001 From: Dan J Miller Date: Mon, 29 Apr 2019 03:48:40 -0230 Subject: Send metrics event from backend for on chain transaction failures (#6500) * Send metrics event from backend for on chain transaction failures * Passes state object to backEndMetaMetricsEvent, and adds getMetaMetricState selector --- app/scripts/controllers/preferences.js | 8 ++++++++ app/scripts/lib/backend-metametrics.js | 26 ++++++++++++++++++++++++++ app/scripts/metamask-controller.js | 19 ++++++++++++++++++- 3 files changed, 52 insertions(+), 1 deletion(-) create mode 100644 app/scripts/lib/backend-metametrics.js (limited to 'app') diff --git a/app/scripts/controllers/preferences.js b/app/scripts/controllers/preferences.js index 737411890..9fe8bee4b 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 }) } diff --git a/app/scripts/lib/backend-metametrics.js b/app/scripts/lib/backend-metametrics.js new file mode 100644 index 000000000..e3c163c1a --- /dev/null +++ b/app/scripts/lib/backend-metametrics.js @@ -0,0 +1,26 @@ +const { + getMetaMetricState, +} = require('../../../ui/app/selectors/selectors') +const { + sendMetaMetricsEvent, +} = require('../../../ui/app/helpers/utils/metametrics.util') + +const inDevelopment = process.env.NODE_ENV === 'development' + +const METAMETRICS_TRACKING_URL = inDevelopment + ? 'http://www.metamask.io/metametrics' + : 'http://www.metamask.io/metametrics-prod' + +function backEndMetaMetricsEvent (metaMaskState, eventData) { + const stateEventData = getMetaMetricState({ metamask: metaMaskState }) + + if (stateEventData.participateInMetaMetrics) { + sendMetaMetricsEvent({ + ...stateEventData, + ...eventData, + url: METAMETRICS_TRACKING_URL + '/backend', + }) + } +} + +module.exports = backEndMetaMetricsEvent diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index 0506e3116..be2090f63 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -54,6 +54,7 @@ const EthQuery = require('eth-query') const ethUtil = require('ethereumjs-util') const sigUtil = require('eth-sig-util') const { AddressBookController } = require('gaba') +const backEndMetaMetricsEvent = require('./lib/backend-metametrics') module.exports = class MetamaskController extends EventEmitter { @@ -190,10 +191,26 @@ module.exports = class MetamaskController extends EventEmitter { }) this.txController.on('newUnapprovedTx', () => opts.showUnapprovedTx()) - this.txController.on(`tx:status-update`, (txId, status) => { + this.txController.on(`tx:status-update`, async (txId, status) => { if (status === 'confirmed' || status === 'failed') { const txMeta = this.txController.txStateManager.getTx(txId) this.platform.showTransactionNotification(txMeta) + + const { txReceipt } = txMeta + const participateInMetaMetrics = this.preferencesController.getParticipateInMetaMetrics() + if (txReceipt && txReceipt.status === '0x0' && participateInMetaMetrics) { + const metamaskState = await this.getState() + backEndMetaMetricsEvent(metamaskState, { + customVariables: { + errorMessage: txMeta.simulationFails.reason, + }, + eventOpts: { + category: 'backend', + action: 'Transactions', + name: 'On Chain Failure', + }, + }) + } } }) -- cgit v1.2.3 From c7492f4f54acdab3b763b273481406c885199369 Mon Sep 17 00:00:00 2001 From: Thomas Huang Date: Tue, 30 Apr 2019 09:49:58 -0700 Subject: Include token checksum address in prices lookup for token rates (#6526) --- app/scripts/controllers/token-rates.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'app') diff --git a/app/scripts/controllers/token-rates.js b/app/scripts/controllers/token-rates.js index 4e396bb59..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 @@ -36,7 +38,7 @@ class TokenRatesController { 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()] + const price = prices[token.address.toLowerCase()] || prices[ethUtil.toChecksumAddress(token.address)] contractExchangeRates[normalizeAddress(token.address)] = price ? price[nativeCurrency] : 0 }) } catch (error) { -- cgit v1.2.3 From 2845398c3d824e5da1830ba7905ffdbf8149cf9e Mon Sep 17 00:00:00 2001 From: kumavis Date: Sat, 4 May 2019 01:32:05 +0800 Subject: Refactor ProviderApprovalController to use rpc and publicConfigStore (#6410) * Ensure home screen does not render if there are unapproved txs (#6501) * Ensure that the confirm screen renders before the home screen if there are unapproved txs. * Only render confirm screen before home screen on mount. * inpage - revert _metamask api to isEnabled isApproved isUnlocked --- app/scripts/contentscript.js | 213 +++++++++------------ .../controllers/network/createBlockTracker.js | 19 -- .../controllers/network/createInfuraClient.js | 6 +- .../controllers/network/createJsonRpcClient.js | 6 +- .../controllers/network/createLocalhostClient.js | 6 +- app/scripts/controllers/network/network.js | 9 +- app/scripts/controllers/provider-approval.js | 123 ++++-------- app/scripts/createStandardProvider.js | 29 +-- app/scripts/inpage.js | 122 +++--------- app/scripts/lib/createDnodeRemoteGetter.js | 16 ++ app/scripts/metamask-controller.js | 102 +++++++--- app/scripts/platforms/extension.js | 14 -- 12 files changed, 278 insertions(+), 387 deletions(-) delete mode 100644 app/scripts/controllers/network/createBlockTracker.js create mode 100644 app/scripts/lib/createDnodeRemoteGetter.js (limited to 'app') diff --git a/app/scripts/contentscript.js b/app/scripts/contentscript.js index 2325cecdd..0c55ae39f 100644 --- a/app/scripts/contentscript.js +++ b/app/scripts/contentscript.js @@ -1,18 +1,17 @@ const fs = require('fs') const path = require('path') const pump = require('pump') +const log = require('loglevel') +const Dnode = require('dnode') const querystring = require('querystring') const LocalMessageDuplexStream = require('post-message-stream') -const PongStream = require('ping-pong-stream/pong') const ObjectMultiplex = require('obj-multiplex') const extension = require('extensionizer') const PortStream = require('extension-port-stream') -const {Transform: TransformStream} = require('stream') const inpageContent = fs.readFileSync(path.join(__dirname, '..', '..', 'dist', 'chrome', 'inpage.js')).toString() const inpageSuffix = '//# sourceURL=' + extension.extension.getURL('inpage.js') + '\n' const inpageBundle = inpageContent + inpageSuffix -let isEnabled = false // Eventually this streaming injection could be replaced with: // https://developer.mozilla.org/en-US/docs/Mozilla/Tech/XPCOM/Language_Bindings/Components.utils.exportFunction @@ -23,9 +22,7 @@ let isEnabled = false if (shouldInjectWeb3()) { injectScript(inpageBundle) - setupStreams() - listenForProviderRequest() - checkPrivacyMode() + start() } /** @@ -46,149 +43,108 @@ function injectScript (content) { } } +/** + * Sets up the stream communication and submits site metadata + * + */ +async function start () { + await setupStreams() + await domIsReady() +} + /** * Sets up two-way communication streams between the - * browser extension and local per-page browser context + * browser extension and local per-page browser context. + * */ -function setupStreams () { - // setup communication to page and plugin +async function setupStreams () { + // the transport-specific streams for communication between inpage and background const pageStream = new LocalMessageDuplexStream({ name: 'contentscript', target: 'inpage', }) - const pluginPort = extension.runtime.connect({ name: 'contentscript' }) - const pluginStream = new PortStream(pluginPort) + const extensionPort = extension.runtime.connect({ name: 'contentscript' }) + const extensionStream = new PortStream(extensionPort) - // Filter out selectedAddress until this origin is enabled - const approvalTransform = new TransformStream({ - objectMode: true, - transform: (data, _, done) => { - if (typeof data === 'object' && data.name && data.name === 'publicConfig' && !isEnabled) { - data.data.selectedAddress = undefined - } - done(null, { ...data }) - }, - }) + // create and connect channel muxers + // so we can handle the channels individually + const pageMux = new ObjectMultiplex() + pageMux.setMaxListeners(25) + const extensionMux = new ObjectMultiplex() + extensionMux.setMaxListeners(25) - // forward communication plugin->inpage pump( + pageMux, pageStream, - pluginStream, - approvalTransform, - pageStream, - (err) => logStreamDisconnectWarning('MetaMask Contentscript Forwarding', err) + pageMux, + (err) => logStreamDisconnectWarning('MetaMask Inpage Multiplex', err) ) - - // setup local multistream channels - const mux = new ObjectMultiplex() - mux.setMaxListeners(25) - pump( - mux, - pageStream, - mux, - (err) => logStreamDisconnectWarning('MetaMask Inpage', err) - ) - pump( - mux, - pluginStream, - mux, - (err) => logStreamDisconnectWarning('MetaMask Background', err) + extensionMux, + extensionStream, + extensionMux, + (err) => logStreamDisconnectWarning('MetaMask Background Multiplex', err) ) - // connect ping stream - const pongStream = new PongStream({ objectMode: true }) - pump( - mux, - pongStream, - mux, - (err) => logStreamDisconnectWarning('MetaMask PingPongStream', err) - ) + // forward communication across inpage-background for these channels only + forwardTrafficBetweenMuxers('provider', pageMux, extensionMux) + forwardTrafficBetweenMuxers('publicConfig', pageMux, extensionMux) - // connect phishing warning stream - const phishingStream = mux.createStream('phishing') + // connect "phishing" channel to warning system + const phishingStream = extensionMux.createStream('phishing') phishingStream.once('data', redirectToPhishingWarning) - // ignore unused channels (handled by background, inpage) - mux.ignoreStream('provider') - mux.ignoreStream('publicConfig') + // connect "publicApi" channel to submit page metadata + const publicApiStream = extensionMux.createStream('publicApi') + const background = await setupPublicApi(publicApiStream) + + return { background } } -/** - * Establishes listeners for requests to fully-enable the provider from the dapp context - * and for full-provider approvals and rejections from the background script context. Dapps - * should not post messages directly and should instead call provider.enable(), which - * handles posting these messages internally. - */ -function listenForProviderRequest () { - window.addEventListener('message', ({ source, data }) => { - if (source !== window || !data || !data.type) { return } - switch (data.type) { - case 'ETHEREUM_ENABLE_PROVIDER': - extension.runtime.sendMessage({ - action: 'init-provider-request', - force: data.force, - origin: source.location.hostname, - siteImage: getSiteIcon(source), - siteTitle: getSiteName(source), - }) - break - case 'ETHEREUM_IS_APPROVED': - extension.runtime.sendMessage({ - action: 'init-is-approved', - origin: source.location.hostname, - }) - break - case 'METAMASK_IS_UNLOCKED': - extension.runtime.sendMessage({ - action: 'init-is-unlocked', - }) - break - } - }) +function forwardTrafficBetweenMuxers (channelName, muxA, muxB) { + const channelA = muxA.createStream(channelName) + const channelB = muxB.createStream(channelName) + pump( + channelA, + channelB, + channelA, + (err) => logStreamDisconnectWarning(`MetaMask muxed traffic for channel "${channelName}" failed.`, err) + ) +} - extension.runtime.onMessage.addListener(({ action = '', isApproved, caching, isUnlocked, selectedAddress }) => { - switch (action) { - case 'approve-provider-request': - isEnabled = true - window.postMessage({ type: 'ethereumprovider', selectedAddress }, '*') - break - case 'approve-legacy-provider-request': - isEnabled = true - window.postMessage({ type: 'ethereumproviderlegacy', selectedAddress }, '*') - break - case 'reject-provider-request': - window.postMessage({ type: 'ethereumprovider', error: 'User denied account authorization' }, '*') - break - case 'answer-is-approved': - window.postMessage({ type: 'ethereumisapproved', isApproved, caching }, '*') - break - case 'answer-is-unlocked': - window.postMessage({ type: 'metamaskisunlocked', isUnlocked }, '*') - break - case 'metamask-set-locked': - isEnabled = false - window.postMessage({ type: 'metamasksetlocked' }, '*') - break - case 'ethereum-ping-success': - window.postMessage({ type: 'ethereumpingsuccess' }, '*') - break - case 'ethereum-ping-error': - window.postMessage({ type: 'ethereumpingerror' }, '*') +async function setupPublicApi (outStream) { + const api = { + getSiteMetadata: (cb) => cb(null, getSiteMetadata()), + } + const dnode = Dnode(api) + pump( + outStream, + dnode, + outStream, + (err) => { + // report any error + if (err) log.error(err) } - }) + ) + const background = await new Promise(resolve => dnode.once('remote', resolve)) + return background } /** - * Checks if MetaMask is currently operating in "privacy mode", meaning - * dapps must call ethereum.enable in order to access user accounts + * Gets site metadata and returns it + * */ -function checkPrivacyMode () { - extension.runtime.sendMessage({ action: 'init-privacy-request' }) +function getSiteMetadata () { + // get metadata + const metadata = { + name: getSiteName(window), + icon: getSiteIcon(window), + } + return metadata } /** - * Error handler for page to plugin stream disconnections + * Error handler for page to extension stream disconnections * * @param {string} remoteLabel Remote stream name * @param {Error} err Stream connection error @@ -301,6 +257,10 @@ function redirectToPhishingWarning () { })}` } + +/** + * Extracts a name for the site from the DOM + */ function getSiteName (window) { const document = window.document const siteName = document.querySelector('head > meta[property="og:site_name"]') @@ -316,6 +276,9 @@ function getSiteName (window) { return document.title } +/** + * Extracts an icon for the site from the DOM + */ function getSiteIcon (window) { const document = window.document @@ -333,3 +296,13 @@ function getSiteIcon (window) { return null } + +/** + * Returns a promise that resolves when the DOM is loaded (does not wait for images to load) + */ +async function domIsReady () { + // already loaded + if (['interactive', 'complete'].includes(document.readyState)) return + // wait for load + await new Promise(resolve => window.addEventListener('DOMContentLoaded', resolve, { once: true })) +} 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 70b332867..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 }), 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/network.js b/app/scripts/controllers/network/network.js index c00ac7e6a..fc8e0df5d 100644 --- a/app/scripts/controllers/network/network.js +++ b/app/scripts/controllers/network/network.js @@ -46,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 @@ -190,7 +189,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 = { @@ -201,13 +200,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/provider-approval.js b/app/scripts/controllers/provider-approval.js index 2c9182b52..8206b2f8a 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,43 @@ 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 + if (this.shouldExposeAccounts(origin)) { + 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 +66,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 +114,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/createStandardProvider.js b/app/scripts/createStandardProvider.js index a5f9c5d03..2059b9b3a 100644 --- a/app/scripts/createStandardProvider.js +++ b/app/scripts/createStandardProvider.js @@ -4,18 +4,10 @@ class StandardProvider { constructor (provider) { this._provider = provider - this._onMessage('ethereumpingerror', this._onClose.bind(this)) - this._onMessage('ethereumpingsuccess', this._onConnect.bind(this)) - window.addEventListener('load', () => { - this._subscribe() - this._ping() - }) - } - - _onMessage (type, handler) { - window.addEventListener('message', function ({ data }) { - if (!data || data.type !== type) return - handler.apply(this, arguments) + this._subscribe() + // indicate that we've connected, mostly just for standard compliance + setTimeout(() => { + this._onConnect() }) } @@ -34,15 +26,6 @@ class StandardProvider { this._isConnected = true } - async _ping () { - try { - await this.send('net_version') - window.postMessage({ type: 'ethereumpingsuccess' }, '*') - } catch (error) { - window.postMessage({ type: 'ethereumpingerror' }, '*') - } - } - _subscribe () { this._provider.on('data', (error, { method, params }) => { if (!error && method === 'eth_subscription') { @@ -59,11 +42,9 @@ class StandardProvider { * @returns {Promise<*>} Promise resolving to the result if successful */ send (method, params = []) { - if (method === 'eth_requestAccounts') return this._provider.enable() - return new Promise((resolve, reject) => { try { - this._provider.sendAsync({ method, params, beta: true }, (error, response) => { + this._provider.sendAsync({ id: 1, jsonrpc: '2.0', method, params }, (error, response) => { error = error || response.error error ? reject(error) : resolve(response) }) diff --git a/app/scripts/inpage.js b/app/scripts/inpage.js index 71cfb875c..a4fb552f1 100644 --- a/app/scripts/inpage.js +++ b/app/scripts/inpage.js @@ -7,32 +7,12 @@ const setupDappAutoReload = require('./lib/auto-reload.js') const MetamaskInpageProvider = require('metamask-inpage-provider') const createStandardProvider = require('./createStandardProvider').default -let isEnabled = false let warned = false -let providerHandle -let isApprovedHandle -let isUnlockedHandle restoreContextAfterImports() log.setDefaultLevel(process.env.METAMASK_DEBUG ? 'debug' : 'warn') -/** - * Adds a postMessage listener for a specific message type - * - * @param {string} messageType - postMessage type to listen for - * @param {Function} handler - event handler - * @param {boolean} remove - removes this handler after being triggered - */ -function onMessage (messageType, callback, remove) { - const handler = function ({ data }) { - if (!data || data.type !== messageType) { return } - remove && window.removeEventListener('message', handler) - callback.apply(window, arguments) - } - window.addEventListener('message', handler) -} - // // setup plugin communication // @@ -49,45 +29,16 @@ const inpageProvider = new MetamaskInpageProvider(metamaskStream) // set a high max listener count to avoid unnecesary warnings inpageProvider.setMaxListeners(100) -// set up a listener for when MetaMask is locked -onMessage('metamasksetlocked', () => { isEnabled = false }) - -// set up a listener for privacy mode responses -onMessage('ethereumproviderlegacy', ({ data: { selectedAddress } }) => { - isEnabled = true - setTimeout(() => { - inpageProvider.publicConfigStore.updateState({ selectedAddress }) - }, 0) -}, true) - // augment the provider with its enable method inpageProvider.enable = function ({ force } = {}) { return new Promise((resolve, reject) => { - providerHandle = ({ data: { error, selectedAddress } }) => { - if (typeof error !== 'undefined') { - reject({ - message: error, - code: 4001, - }) + inpageProvider.sendAsync({ method: 'eth_requestAccounts', params: [force] }, (error, response) => { + if (error) { + reject(error) } else { - window.removeEventListener('message', providerHandle) - setTimeout(() => { - inpageProvider.publicConfigStore.updateState({ selectedAddress }) - }, 0) - - // wait for the background to update with an account - inpageProvider.sendAsync({ method: 'eth_accounts', params: [] }, (error, response) => { - if (error) { - reject(error) - } else { - isEnabled = true - resolve(response.result) - } - }) + resolve(response.result) } - } - onMessage('ethereumprovider', providerHandle, true) - window.postMessage({ type: 'ETHEREUM_ENABLE_PROVIDER', force }, '*') + }) }) } @@ -98,31 +49,23 @@ inpageProvider.autoRefreshOnNetworkChange = true // add metamask-specific convenience methods inpageProvider._metamask = new Proxy({ /** - * Determines if this domain is currently enabled + * Synchronously determines if this domain is currently enabled, with a potential false negative if called to soon * - * @returns {boolean} - true if this domain is currently enabled + * @returns {boolean} - returns true if this domain is currently enabled */ isEnabled: function () { - return isEnabled + const { isEnabled } = inpageProvider.publicConfigStore.getState() + return Boolean(isEnabled) }, /** - * Determines if this domain has been previously approved + * Asynchronously determines if this domain is currently enabled * - * @returns {Promise} - Promise resolving to true if this domain has been previously approved + * @returns {Promise} - Promise resolving to true if this domain is currently enabled */ - isApproved: function () { - return new Promise((resolve) => { - isApprovedHandle = ({ data: { caching, isApproved } }) => { - if (caching) { - resolve(!!isApproved) - } else { - resolve(false) - } - } - onMessage('ethereumisapproved', isApprovedHandle, true) - window.postMessage({ type: 'ETHEREUM_IS_APPROVED' }, '*') - }) + isApproved: async function () { + const { isEnabled } = await getPublicConfigWhenReady() + return Boolean(isEnabled) }, /** @@ -130,14 +73,9 @@ inpageProvider._metamask = new Proxy({ * * @returns {Promise} - Promise resolving to true if MetaMask is currently unlocked */ - isUnlocked: function () { - return new Promise((resolve) => { - isUnlockedHandle = ({ data: { isUnlocked } }) => { - resolve(!!isUnlocked) - } - onMessage('metamaskisunlocked', isUnlockedHandle, true) - window.postMessage({ type: 'METAMASK_IS_UNLOCKED' }, '*') - }) + isUnlocked: async function () { + const { isUnlocked } = await getPublicConfigWhenReady() + return Boolean(isUnlocked) }, }, { get: function (obj, prop) { @@ -149,6 +87,19 @@ inpageProvider._metamask = new Proxy({ }, }) +// publicConfig isn't populated until we get a message from background. +// Using this getter will ensure the state is available +async function getPublicConfigWhenReady () { + const store = inpageProvider.publicConfigStore + let state = store.getState() + // if state is missing, wait for first update + if (!state.networkVersion) { + state = await new Promise(resolve => store.once('update', resolve)) + console.log('new state', state) + } + return state +} + // Work around for web3@1.0 deleting the bound `sendAsync` but not the unbound // `sendAsync` method on the prototype, causing `this` reference issues with drizzle const proxiedInpageProvider = new Proxy(inpageProvider, { @@ -159,19 +110,6 @@ const proxiedInpageProvider = new Proxy(inpageProvider, { window.ethereum = createStandardProvider(proxiedInpageProvider) -// detect eth_requestAccounts and pipe to enable for now -function detectAccountRequest (method) { - const originalMethod = inpageProvider[method] - inpageProvider[method] = function ({ method }) { - if (method === 'eth_requestAccounts') { - return window.ethereum.enable() - } - return originalMethod.apply(this, arguments) - } -} -detectAccountRequest('send') -detectAccountRequest('sendAsync') - // // setup web3 // diff --git a/app/scripts/lib/createDnodeRemoteGetter.js b/app/scripts/lib/createDnodeRemoteGetter.js new file mode 100644 index 000000000..b70d218f3 --- /dev/null +++ b/app/scripts/lib/createDnodeRemoteGetter.js @@ -0,0 +1,16 @@ +module.exports = createDnodeRemoteGetter + +function createDnodeRemoteGetter (dnode) { + let remote + + dnode.once('remote', (_remote) => { + remote = _remote + }) + + async function getRemote () { + if (remote) return remote + return await new Promise(resolve => dnode.once('remote', resolve)) + } + + return getRemote +} diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index be2090f63..7d666ae88 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -7,8 +7,10 @@ const EventEmitter = require('events') const pump = require('pump') const Dnode = require('dnode') +const pify = require('pify') const ObservableStore = require('obs-store') const ComposableObservableStore = require('./lib/ComposableObservableStore') +const createDnodeRemoteGetter = require('./lib/createDnodeRemoteGetter') const asStream = require('obs-store/lib/asStream') const AccountTracker = require('./lib/account-tracker') const RpcEngine = require('json-rpc-engine') @@ -87,7 +89,7 @@ module.exports = class MetamaskController extends EventEmitter { this.createVaultMutex = new Mutex() // network store - this.networkController = new NetworkController(initState.NetworkController, this.platform) + this.networkController = new NetworkController(initState.NetworkController) // preferences controller this.preferencesController = new PreferencesController({ @@ -235,15 +237,17 @@ module.exports = class MetamaskController extends EventEmitter { this.messageManager = new MessageManager() this.personalMessageManager = new PersonalMessageManager() this.typedMessageManager = new TypedMessageManager({ networkController: this.networkController }) - this.publicConfigStore = this.initPublicConfigStore() + + // ensure isClientOpenAndUnlocked is updated when memState updates + this.on('update', (memState) => { + this.isClientOpenAndUnlocked = memState.isUnlocked && this._isClientOpen + }) this.providerApprovalController = new ProviderApprovalController({ closePopup: opts.closePopup, keyringController: this.keyringController, openPopup: opts.openPopup, - platform: opts.platform, preferencesController: this.preferencesController, - publicConfigStore: this.publicConfigStore, }) this.store.updateStructure({ @@ -322,22 +326,32 @@ module.exports = class MetamaskController extends EventEmitter { * Constructor helper: initialize a public config store. * This store is used to make some config info available to Dapps synchronously. */ - initPublicConfigStore () { - // get init state + createPublicConfigStore ({ checkIsEnabled }) { + // subset of state for metamask inpage provider const publicConfigStore = new ObservableStore() - // memStore -> transform -> publicConfigStore - this.on('update', (memState) => { - this.isClientOpenAndUnlocked = memState.isUnlocked && this._isClientOpen + // setup memStore subscription hooks + this.on('update', updatePublicConfigStore) + updatePublicConfigStore(this.getState()) + + publicConfigStore.destroy = () => { + this.removeEventListener('update', updatePublicConfigStore) + } + + function updatePublicConfigStore (memState) { const publicState = selectPublicState(memState) publicConfigStore.putState(publicState) - }) + } - function selectPublicState (memState) { + function selectPublicState ({ isUnlocked, selectedAddress, network, completedOnboarding }) { + const isEnabled = checkIsEnabled() + const isReady = isUnlocked && isEnabled const result = { - selectedAddress: memState.isUnlocked ? memState.selectedAddress : undefined, - networkVersion: memState.network, - onboardingcomplete: memState.completedOnboarding, + isUnlocked, + isEnabled, + selectedAddress: isReady ? selectedAddress : undefined, + networkVersion: network, + onboardingcomplete: completedOnboarding, } return result } @@ -477,9 +491,10 @@ module.exports = class MetamaskController extends EventEmitter { signTypedMessage: nodeify(this.signTypedMessage, this), cancelTypedMessage: this.cancelTypedMessage.bind(this), - approveProviderRequest: providerApprovalController.approveProviderRequest.bind(providerApprovalController), + // provider approval + approveProviderRequestByOrigin: providerApprovalController.approveProviderRequestByOrigin.bind(providerApprovalController), + rejectProviderRequestByOrigin: providerApprovalController.rejectProviderRequestByOrigin.bind(providerApprovalController), clearApprovedOrigins: providerApprovalController.clearApprovedOrigins.bind(providerApprovalController), - rejectProviderRequest: providerApprovalController.rejectProviderRequest.bind(providerApprovalController), } } @@ -1296,8 +1311,9 @@ module.exports = class MetamaskController extends EventEmitter { // setup multiplexing const mux = setupMultiplex(connectionStream) // connect features - this.setupProviderConnection(mux.createStream('provider'), originDomain) - this.setupPublicConfig(mux.createStream('publicConfig')) + const publicApi = this.setupPublicApi(mux.createStream('publicApi'), originDomain) + this.setupProviderConnection(mux.createStream('provider'), originDomain, publicApi) + this.setupPublicConfig(mux.createStream('publicConfig'), originDomain) } /** @@ -1370,7 +1386,7 @@ module.exports = class MetamaskController extends EventEmitter { * @param {*} outStream - The stream to provide over. * @param {string} origin - The URI of the requesting resource. */ - setupProviderConnection (outStream, origin) { + setupProviderConnection (outStream, origin, publicApi) { // setup json rpc engine stack const engine = new RpcEngine() const provider = this.provider @@ -1390,6 +1406,11 @@ module.exports = class MetamaskController extends EventEmitter { engine.push(subscriptionManager.middleware) // watch asset engine.push(this.preferencesController.requestWatchAsset.bind(this.preferencesController)) + // requestAccounts + engine.push(this.providerApprovalController.createMiddleware({ + origin, + getSiteMetadata: publicApi && publicApi.getSiteMetadata, + })) // forward to metamask primary provider engine.push(providerAsMiddleware(provider)) @@ -1418,18 +1439,56 @@ module.exports = class MetamaskController extends EventEmitter { * * @param {*} outStream - The stream to provide public config over. */ - setupPublicConfig (outStream) { - const configStream = asStream(this.publicConfigStore) + setupPublicConfig (outStream, originDomain) { + const configStore = this.createPublicConfigStore({ + // check the providerApprovalController's approvedOrigins + checkIsEnabled: () => this.providerApprovalController.shouldExposeAccounts(originDomain), + }) + const configStream = asStream(configStore) + pump( configStream, outStream, (err) => { + configStore.destroy() configStream.destroy() if (err) log.error(err) } ) } + /** + * A method for providing our public api over a stream. + * This includes a method for setting site metadata like title and image + * + * @param {*} outStream - The stream to provide the api over. + */ + setupPublicApi (outStream, originDomain) { + const dnode = Dnode() + // connect dnode api to remote connection + pump( + outStream, + dnode, + outStream, + (err) => { + // report any error + if (err) log.error(err) + } + ) + + const getRemote = createDnodeRemoteGetter(dnode) + + const publicApi = { + // wrap with an await remote + getSiteMetadata: async () => { + const remote = await getRemote() + return await pify(remote.getSiteMetadata)() + }, + } + + return publicApi + } + /** * Handle a KeyringController update * @param {object} state the KC state @@ -1734,7 +1793,6 @@ module.exports = class MetamaskController extends EventEmitter { * Locks MetaMask */ setLocked () { - this.providerApprovalController.setLocked() return this.keyringController.setLocked() } } diff --git a/app/scripts/platforms/extension.js b/app/scripts/platforms/extension.js index 099b0d7ea..0c2d222b8 100644 --- a/app/scripts/platforms/extension.js +++ b/app/scripts/platforms/extension.js @@ -60,20 +60,6 @@ class ExtensionPlatform { } } - addMessageListener (cb) { - extension.runtime.onMessage.addListener(cb) - } - - sendMessage (message, query = {}) { - const id = query.id - delete query.id - extension.tabs.query({ ...query }, tabs => { - tabs.forEach(tab => { - extension.tabs.sendMessage(id || tab.id, message) - }) - }) - } - _showConfirmedTransaction (txMeta) { this._subscribeToNotificationClicked() -- cgit v1.2.3 From 56ed189aeb4ebc8e5dff7a6886e07791ce6569bb Mon Sep 17 00:00:00 2001 From: Chi Kei Chan Date: Wed, 8 May 2019 11:57:21 -0700 Subject: Auto logout after specific time (#6558) * Add i18n strings * Finish Auto timeout * Fix linter * Fix copies * Add unit test to Advanced Tab component * Add back actions and container * Add basic test to ensure container completeness * No zero, fix linters * restrict negative in input --- app/_locales/en/messages.json | 6 ++++++ 1 file changed, 6 insertions(+) (limited to 'app') diff --git a/app/_locales/en/messages.json b/app/_locales/en/messages.json index 184507cbb..7a5f9297c 100644 --- a/app/_locales/en/messages.json +++ b/app/_locales/en/messages.json @@ -154,6 +154,12 @@ "attributions": { "message": "Attributions" }, + "autoLogoutTimeLimit": { + "message": "Auto-Logout Timer (minutes)" + }, + "autoLogoutTimeLimitDescription": { + "message": "Set the number of idle time in minutes before Metamask automatically log out" + }, "available": { "message": "Available" }, -- cgit v1.2.3 From ef8a07c2ce2b1c5fc4ef18f48592b2e7da178c44 Mon Sep 17 00:00:00 2001 From: Dan J Miller Date: Wed, 8 May 2019 16:48:33 -0230 Subject: Adds a transactionCategory to txMeta for use in UI (#6567) * Adds a transactionCategory to txMeta for use in UI * Update transaction controller and tx-gas-util documentation on new code param in multiple functions. --- app/scripts/controllers/transactions/index.js | 55 ++++++++++++++++++++-- .../controllers/transactions/tx-gas-utils.js | 11 +++-- 2 files changed, 59 insertions(+), 7 deletions(-) (limited to 'app') diff --git a/app/scripts/controllers/transactions/index.js b/app/scripts/controllers/transactions/index.js index 2ce736beb..dc6a043e4 100644 --- a/app/scripts/controllers/transactions/index.js +++ b/app/scripts/controllers/transactions/index.js @@ -3,6 +3,17 @@ 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') @@ -180,9 +191,11 @@ class TransactionController extends EventEmitter { } txUtils.validateTxParams(normalizedTxParams) // construct txMeta + const { transactionCategory, code } = 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, code) } catch (error) { log.warn(error) txMeta.loadingDefaults = false @@ -211,7 +224,7 @@ class TransactionController extends EventEmitter { @param txMeta {Object} - the txMeta object @returns {Promise} resolves with txMeta */ - async addTxGasDefaults (txMeta) { + async addTxGasDefaults (txMeta, code) { 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, code) } /** @@ -555,6 +568,42 @@ 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) { + 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, 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/tx-gas-utils.js b/app/scripts/controllers/transactions/tx-gas-utils.js index 765551167..2dc461f48 100644 --- a/app/scripts/controllers/transactions/tx-gas-utils.js +++ b/app/scripts/controllers/transactions/tx-gas-utils.js @@ -4,6 +4,7 @@ const { BnMultiplyByFraction, bnToHex, } = require('../../lib/util') +const log = require('loglevel') const { addHexPrefix } = require('ethereumjs-util') const SIMPLE_GAS_COST = '0x5208' // Hex for 21000, cost of a simple send. @@ -24,14 +25,16 @@ class TxGasUtil { /** @param txMeta {Object} - the txMeta object + @param code {string} - the code at the txs address, as returned by this.query.getCode(to) @returns {object} the txMeta object with the gas written to the txParams */ - async analyzeGasUsage (txMeta) { + async analyzeGasUsage (txMeta, code) { 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, code) } catch (err) { + log.warn(err) txMeta.simulationFails = { reason: err.message, errorKey: err.errorKey, @@ -52,9 +55,10 @@ class TxGasUtil { Estimates the tx's gas usage @param txMeta {Object} - the txMeta object @param blockGasLimitHex {string} - hex string of the block's gas limit + @param code {string} - the code at the txs address, as returned by this.query.getCode(to) @returns {string} the estimated gas limit as a hex string */ - async estimateTxGas (txMeta, blockGasLimitHex) { + async estimateTxGas (txMeta, blockGasLimitHex, code) { const txParams = txMeta.txParams // check if gasLimit is already specified @@ -70,7 +74,6 @@ 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' -- cgit v1.2.3 From 094e4cf555c698bfef50ca6679cd1e98f4ea9aa1 Mon Sep 17 00:00:00 2001 From: Whymarrh Whitby Date: Wed, 8 May 2019 17:21:33 -0230 Subject: Check for unused function arguments (#6583) * eslint: Check for unused function arguments * eslint: Ignore unused '_' in argument list Also allow any number of '_' e.g., '__' or '___' which is to be used sparingly * Remove and rename unused arguments --- app/scripts/controllers/balance.js | 2 +- app/scripts/controllers/preferences.js | 6 +++--- app/scripts/controllers/shapeshift.js | 2 +- app/scripts/controllers/transactions/tx-state-manager.js | 4 ++-- app/scripts/lib/message-manager.js | 2 +- app/scripts/lib/personal-message-manager.js | 2 +- app/scripts/metamask-controller.js | 9 ++++----- app/scripts/migrations/024.js | 2 +- app/scripts/migrations/025.js | 2 +- 9 files changed, 15 insertions(+), 16 deletions(-) (limited to 'app') 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/preferences.js b/app/scripts/controllers/preferences.js index 9fe8bee4b..bbb13bd8e 100644 --- a/app/scripts/controllers/preferences.js +++ b/app/scripts/controllers/preferences.js @@ -339,7 +339,7 @@ class PreferencesController { } removeSuggestedTokens () { - return new Promise((resolve, reject) => { + return new Promise((resolve) => { this.store.updateState({ suggestedTokens: {} }) resolve({}) }) @@ -396,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) @@ -461,7 +461,7 @@ class PreferencesController { * */ setCurrentAccountTab (currentAccountTab) { - return new Promise((resolve, reject) => { + return new Promise((resolve) => { this.store.updateState({ currentAccountTab }) resolve() }) diff --git a/app/scripts/controllers/shapeshift.js b/app/scripts/controllers/shapeshift.js index b2a1462c2..9b0287007 100644 --- a/app/scripts/controllers/shapeshift.js +++ b/app/scripts/controllers/shapeshift.js @@ -136,7 +136,7 @@ class ShapeshiftController { * @param {ShapeShiftTx} tx The tx to remove * */ - removeShapeShiftTx (tx) { + removeShapeShiftTx () { const { shapeShiftTxList } = this.store.getState() const index = shapeShiftTxList.indexOf(index) if (index !== -1) { 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/lib/message-manager.js b/app/scripts/lib/message-manager.js index e86629590..ac41de523 100644 --- a/app/scripts/lib/message-manager.js +++ b/app/scripts/lib/message-manager.js @@ -34,7 +34,7 @@ module.exports = class MessageManager extends EventEmitter { * @property {array} messages Holds all messages that have been created by this MessageManager * */ - constructor (opts) { + constructor () { super() this.memStore = new ObservableStore({ unapprovedMsgs: {}, diff --git a/app/scripts/lib/personal-message-manager.js b/app/scripts/lib/personal-message-manager.js index fdb94f5ec..7c13e521a 100644 --- a/app/scripts/lib/personal-message-manager.js +++ b/app/scripts/lib/personal-message-manager.js @@ -36,7 +36,7 @@ module.exports = class PersonalMessageManager extends EventEmitter { * @property {array} messages Holds all messages that have been created by this PersonalMessageManager * */ - constructor (opts) { + constructor () { super() this.memStore = new ObservableStore({ unapprovedPersonalMsgs: {}, diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index 7d666ae88..b190dd452 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -1222,9 +1222,8 @@ module.exports = class MetamaskController extends EventEmitter { * with higher gas. * * @param {string} txId - The ID of the transaction to speed up. - * @param {Function} cb - The callback function called with a full state update. */ - async retryTransaction (txId, gasPrice, cb) { + async retryTransaction (txId, gasPrice) { await this.txController.retryTransaction(txId, gasPrice) const state = await this.getState() return state @@ -1237,7 +1236,7 @@ module.exports = class MetamaskController extends EventEmitter { * @param {string=} customGasPrice - the hex value to use for the cancel transaction * @returns {object} MetaMask state */ - async createCancelTransaction (originalTxId, customGasPrice, cb) { + async createCancelTransaction (originalTxId, customGasPrice) { try { await this.txController.createCancelTransaction(originalTxId, customGasPrice) const state = await this.getState() @@ -1247,7 +1246,7 @@ module.exports = class MetamaskController extends EventEmitter { } } - async createSpeedUpTransaction (originalTxId, customGasPrice, cb) { + async createSpeedUpTransaction (originalTxId, customGasPrice) { await this.txController.createSpeedUpTransaction(originalTxId, customGasPrice) const state = await this.getState() return state @@ -1463,7 +1462,7 @@ module.exports = class MetamaskController extends EventEmitter { * * @param {*} outStream - The stream to provide the api over. */ - setupPublicApi (outStream, originDomain) { + setupPublicApi (outStream) { const dnode = Dnode() // connect dnode api to remote connection pump( diff --git a/app/scripts/migrations/024.js b/app/scripts/migrations/024.js index d0b276a79..6239bab13 100644 --- a/app/scripts/migrations/024.js +++ b/app/scripts/migrations/024.js @@ -27,7 +27,7 @@ function transformState (state) { const newState = state if (!newState.TransactionController) return newState const transactions = newState.TransactionController.transactions - newState.TransactionController.transactions = transactions.map((txMeta, _, txList) => { + newState.TransactionController.transactions = transactions.map((txMeta, _) => { if ( txMeta.status === 'unapproved' && txMeta.txParams && diff --git a/app/scripts/migrations/025.js b/app/scripts/migrations/025.js index fc3b20a44..fd4faa782 100644 --- a/app/scripts/migrations/025.js +++ b/app/scripts/migrations/025.js @@ -43,7 +43,7 @@ function normalizeTxParams (txParams) { // functions that handle normalizing of that key in txParams const whiteList = { from: from => ethUtil.addHexPrefix(from).toLowerCase(), - to: to => ethUtil.addHexPrefix(txParams.to).toLowerCase(), + to: () => ethUtil.addHexPrefix(txParams.to).toLowerCase(), nonce: nonce => ethUtil.addHexPrefix(nonce), value: value => ethUtil.addHexPrefix(value), data: data => ethUtil.addHexPrefix(data), -- cgit v1.2.3 From 77d3bc252d5c4d223e9de3c6700750d1895bb257 Mon Sep 17 00:00:00 2001 From: bitpshr Date: Mon, 6 May 2019 12:12:52 -0400 Subject: feature: integrate gaba/PhishingController --- app/scripts/controllers/blacklist.js | 136 ----------------------------------- app/scripts/metamask-controller.js | 15 ++-- 2 files changed, 8 insertions(+), 143 deletions(-) delete mode 100644 app/scripts/controllers/blacklist.js (limited to 'app') 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} 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/metamask-controller.js b/app/scripts/metamask-controller.js index b190dd452..447d8b626 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -28,7 +28,6 @@ const PreferencesController = require('./controllers/preferences') const CurrencyController = require('./controllers/currency') const ShapeShiftController = require('./controllers/shapeshift') const InfuraController = require('./controllers/infura') -const BlacklistController = require('./controllers/blacklist') const CachedBalancesController = require('./controllers/cached-balances') const RecentBlocksController = require('./controllers/recent-blocks') const MessageManager = require('./lib/message-manager') @@ -55,7 +54,10 @@ const HW_WALLETS_KEYRINGS = [TrezorKeyring.type, LedgerBridgeKeyring.type] const EthQuery = require('eth-query') const ethUtil = require('ethereumjs-util') const sigUtil = require('eth-sig-util') -const { AddressBookController } = require('gaba') +const { + AddressBookController, + PhishingController, +} = require('gaba') const backEndMetaMetricsEvent = require('./lib/backend-metametrics') @@ -112,8 +114,7 @@ module.exports = class MetamaskController extends EventEmitter { }) this.infuraController.scheduleInfuraNetworkCheck() - this.blacklistController = new BlacklistController() - this.blacklistController.scheduleUpdates() + this.phishingController = new PhishingController() // rpc provider this.initializeProvider() @@ -1301,7 +1302,7 @@ module.exports = class MetamaskController extends EventEmitter { */ setupUntrustedCommunication (connectionStream, originDomain) { // Check if new connection is blacklisted - if (this.blacklistController.checkForPhishing(originDomain)) { + if (this.phishingController.test(originDomain)) { log.debug('MetaMask - sending phishing warning for', originDomain) this.sendPhishingWarning(connectionStream, originDomain) return @@ -1781,11 +1782,11 @@ module.exports = class MetamaskController extends EventEmitter { */ /** - * Adds a domain to the {@link BlacklistController} whitelist + * Adds a domain to the PhishingController whitelist * @param {string} hostname the domain to whitelist */ whitelistPhishingDomain (hostname) { - return this.blacklistController.whitelistDomain(hostname) + return this.phishingController.bypass(hostname) } /** -- cgit v1.2.3 From 13be683701bc46d8f1bcbaa301e2b7f01a34e29c Mon Sep 17 00:00:00 2001 From: Dan J Miller Date: Thu, 9 May 2019 14:57:14 -0230 Subject: New settings custom rpc form (#6490) * Add networks tab to settings, with header. * Adds network list to settings network tab. * Adds form to settings networks tab and connects it to network list. * Network tab: form adding and editing working * Settings network form properly handles input errors * Add translations for settings network form * Clean up styles of settings network tab. * Add popup-view styles and behaviour to settings network tab. * Fix save button on settings network form * Adds 'Add Network' button and addMode to settings networks tab * Lint fix for settings networks tab addition * Fix navigation in settings networks tab. * Editing an rpcurl in networks tab does not create new network, just changes rpc of old * Fix layout of settings tabs other than network * Networks dropdown 'Custom Rpc' item links to networks tab in settings. * Update settings sidebar networks subheader. * Make networks tab buttons width consistent with input widths in extension view. * Fix settings screen subheader height in popup view * Fix height of add networks button in popup view * Add optional label to chainId and symbol form labels in networks setting tab * Style fixes for networks tab headers * Add ability to customize block explorer used by custom rpc * Stylistic improvements+fixes to custom rpc form. * Hide cancel button. * Highlight and show network form of provider by default. * Standardize network subheader name to 'Networks' * Update e2e tests for new settings network form * Update unit tests for new rpcPrefs prop * Extract blockexplorer url construction into method. * Fix broken styles on non-network tabs in popup mode * Fix block explorer url links for cases when provider in state has not been updated. * Fix vertical spacing of network form * Don't allow click of save button on network form if nothing has changed * Ensure add network button is shown in popup view * Lint fix for networks tab * Fix block explorer url preference setting. * Fix e2e tests for custom blockexplorer in account details modal changes. * Update integration test states to include frequentRpcList property * Fix some capitalizations in en/messages.json * Remove some console.logs added during custom rpc form work * Fix external account link text and url for modal and dropdown. * Documentation, url validation, proptype required additions and lint fixes on network tab and form. --- app/_locales/en/messages.json | 36 +++++++++++++++++++++++++++++- app/scripts/controllers/network/network.js | 3 ++- app/scripts/controllers/preferences.js | 32 +++++++++++++------------- app/scripts/metamask-controller.js | 14 ++++++------ 4 files changed, 60 insertions(+), 25 deletions(-) (limited to 'app') diff --git a/app/_locales/en/messages.json b/app/_locales/en/messages.json index 7a5f9297c..254bfdfb9 100644 --- a/app/_locales/en/messages.json +++ b/app/_locales/en/messages.json @@ -83,6 +83,9 @@ "address": { "message": "Address" }, + "addNetwork": { + "message": "Add Network" + }, "advanced": { "message": "Advanced" }, @@ -191,6 +194,13 @@ "message": "must be greater than or equal to $1 and less than or equal to $2.", "description": "helper for inputting hex as decimal input" }, + "blockExplorerUrl": { + "message": "Block Explorer" + }, + "blockExplorerView": { + "message": "View account at $1", + "description": "$1 replaced by URL for custom block explorer" + }, "blockiesIdenticon": { "message": "Use Blockies Identicon" }, @@ -230,6 +240,9 @@ "ok": { "message": "Ok" }, + "optionalBlockExplorerUrl": { + "message": "Block Explorer URL (optional)" + }, "cancel": { "message": "Cancel" }, @@ -245,6 +258,9 @@ "cancelN": { "message": "Cancel all $1 transactions" }, + "chainId": { + "message": "Chain ID" + }, "classicInterface": { "message": "Use classic interface" }, @@ -502,6 +518,9 @@ "edit": { "message": "Edit" }, + "editNetwork": { + "message": "Edit Network" + }, "editAccountName": { "message": "Edit Account Name" }, @@ -934,9 +953,15 @@ "negativeETH": { "message": "Can not send negative amounts of ETH." }, + "networkName": { + "message": "Network Name" + }, "networks": { "message": "Networks" }, + "networkSettingsDescription": { + "message": "Add and edit custom RPC networks" + }, "nevermind": { "message": "Nevermind" }, @@ -977,7 +1002,7 @@ "protectYourKeysMessage2": { "message": "Keep your phrase safe. If you see something fishy, or you’re uncertain about a website, email support@metamask.io" }, - "rpcURL": { + "rpcUrl": { "message": "New RPC URL" }, "showAdvancedOptions": { @@ -1492,6 +1517,9 @@ "supportCenter": { "message": "Visit our Support Center" }, + "symbol": { + "message": "Symbol" + }, "symbolBetweenZeroTwelve": { "message": "Symbol must be between 0 and 12 characters." }, @@ -1714,9 +1742,15 @@ "viewAccount": { "message": "View Account" }, + "viewOnCustomBlockExplorer": { + "message": "View at $1" + }, "viewOnEtherscan": { "message": "View on Etherscan" }, + "viewNetworkInfo": { + "message": "View Network Info" + }, "visitWebSite": { "message": "Visit our web site" }, diff --git a/app/scripts/controllers/network/network.js b/app/scripts/controllers/network/network.js index fc8e0df5d..2c68e4378 100644 --- a/app/scripts/controllers/network/network.js +++ b/app/scripts/controllers/network/network.js @@ -129,13 +129,14 @@ 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 } diff --git a/app/scripts/controllers/preferences.js b/app/scripts/controllers/preferences.js index bbb13bd8e..acf952bb1 100644 --- a/app/scripts/controllers/preferences.js +++ b/app/scripts/controllers/preferences.js @@ -488,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) } @@ -503,22 +503,22 @@ class PreferencesController { * @returns {Promise} 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/metamask-controller.js b/app/scripts/metamask-controller.js index b190dd452..cc9d51d3c 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -1633,9 +1633,9 @@ module.exports = class MetamaskController extends EventEmitter { * @returns {Promise} - The RPC Target URL confirmed. */ - async updateAndSetCustomRpc (rpcUrl, chainId, ticker = 'ETH', nickname) { - await this.preferencesController.updateRpc({ rpcUrl, chainId, ticker, nickname }) - this.networkController.setRpcTarget(rpcUrl, chainId, ticker, nickname) + async updateAndSetCustomRpc (rpcUrl, chainId, ticker = 'ETH', nickname, rpcPrefs) { + await this.preferencesController.updateRpc({ rpcUrl, chainId, ticker, nickname, rpcPrefs }) + this.networkController.setRpcTarget(rpcUrl, chainId, ticker, nickname, rpcPrefs) return rpcUrl } @@ -1648,15 +1648,15 @@ module.exports = class MetamaskController extends EventEmitter { * @param {string} nickname - Optional nickname of the selected network. * @returns {Promise} - The RPC Target URL confirmed. */ - async setCustomRpc (rpcTarget, chainId, ticker = 'ETH', nickname = '') { + async setCustomRpc (rpcTarget, chainId, ticker = 'ETH', nickname = '', rpcPrefs = {}) { const frequentRpcListDetail = this.preferencesController.getFrequentRpcListDetail() const rpcSettings = frequentRpcListDetail.find((rpc) => rpcTarget === rpc.rpcUrl) if (rpcSettings) { - this.networkController.setRpcTarget(rpcSettings.rpcUrl, rpcSettings.chainId, rpcSettings.ticker, rpcSettings.nickname) + this.networkController.setRpcTarget(rpcSettings.rpcUrl, rpcSettings.chainId, rpcSettings.ticker, rpcSettings.nickname, rpcPrefs) } else { - this.networkController.setRpcTarget(rpcTarget, chainId, ticker, nickname) - await this.preferencesController.addToFrequentRpcList(rpcTarget, chainId, ticker, nickname) + this.networkController.setRpcTarget(rpcTarget, chainId, ticker, nickname, rpcPrefs) + await this.preferencesController.addToFrequentRpcList(rpcTarget, chainId, ticker, nickname, rpcPrefs) } return rpcTarget } -- cgit v1.2.3 From 25323c8e18b5f568737486bb2da4e8877df9a7b3 Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Fri, 10 May 2019 15:38:32 -0700 Subject: Version 6.5.0 RC1 --- app/manifest.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'app') diff --git a/app/manifest.json b/app/manifest.json index bd10f60da..1d194a154 100644 --- a/app/manifest.json +++ b/app/manifest.json @@ -1,7 +1,7 @@ { "name": "__MSG_appName__", "short_name": "__MSG_appName__", - "version": "6.4.1", + "version": "6.5.0", "manifest_version": 2, "author": "https://metamask.io", "description": "__MSG_appDescription__", -- cgit v1.2.3 From 28c4001f5269eb170f14bd2829698ee402b2cfbf Mon Sep 17 00:00:00 2001 From: Whymarrh Whitby Date: Mon, 13 May 2019 13:46:09 -0230 Subject: Update auto-logout to recognize idle time in background (#6593) * Fix wording of autoLogoutTimeLimitDescription * AppStateController and update auto-logout functionality --- app/_locales/en/messages.json | 2 +- app/scripts/controllers/app-state.js | 73 ++++++++++++++++++++++++++++++++++++ app/scripts/metamask-controller.js | 12 ++++++ 3 files changed, 86 insertions(+), 1 deletion(-) create mode 100644 app/scripts/controllers/app-state.js (limited to 'app') diff --git a/app/_locales/en/messages.json b/app/_locales/en/messages.json index 254bfdfb9..bef278f79 100644 --- a/app/_locales/en/messages.json +++ b/app/_locales/en/messages.json @@ -161,7 +161,7 @@ "message": "Auto-Logout Timer (minutes)" }, "autoLogoutTimeLimitDescription": { - "message": "Set the number of idle time in minutes before Metamask automatically log out" + "message": "Set the idle time in minutes before MetaMask will automatically log out" }, "available": { "message": "Available" 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/metamask-controller.js b/app/scripts/metamask-controller.js index bdfff9827..07054f84b 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -25,6 +25,7 @@ const {setupMultiplex} = require('./lib/stream-utils.js') const KeyringController = require('eth-keyring-controller') const NetworkController = require('./controllers/network') const PreferencesController = require('./controllers/preferences') +const AppStateController = require('./controllers/app-state') const CurrencyController = require('./controllers/currency') const ShapeShiftController = require('./controllers/shapeshift') const InfuraController = require('./controllers/infura') @@ -101,6 +102,12 @@ module.exports = class MetamaskController extends EventEmitter { network: this.networkController, }) + // app-state controller + this.appStateController = new AppStateController({ + preferencesStore: this.preferencesController.store, + onInactiveTimeout: () => this.setLocked(), + }) + // currency controller this.currencyController = new CurrencyController({ initState: initState.CurrencyController, @@ -252,6 +259,7 @@ module.exports = class MetamaskController extends EventEmitter { }) this.store.updateStructure({ + AppStateController: this.appStateController.store, TransactionController: this.txController.store, KeyringController: this.keyringController.store, PreferencesController: this.preferencesController.store, @@ -264,6 +272,7 @@ module.exports = class MetamaskController extends EventEmitter { }) this.memStore = new ComposableObservableStore(null, { + AppStateController: this.appStateController.store, NetworkController: this.networkController.store, AccountTracker: this.accountTracker.store, TxController: this.txController.memStore, @@ -462,6 +471,9 @@ module.exports = class MetamaskController extends EventEmitter { // AddressController setAddressBook: this.addressBookController.set.bind(this.addressBookController), + // AppStateController + setLastActiveTime: nodeify(this.appStateController.setLastActiveTime, this.appStateController), + // KeyringController setLocked: nodeify(this.setLocked, this), createNewVaultAndKeychain: nodeify(this.createNewVaultAndKeychain, this), -- cgit v1.2.3 From b81c4e5c98cdf3f5e6ebb05e57c2be993cdf5da0 Mon Sep 17 00:00:00 2001 From: Dan J Miller Date: Tue, 14 May 2019 15:44:07 -0230 Subject: Set a default value for code in _determineTransactionCategory (#6604) * Set a default value for code in _determineTransactionCategory * Adds e2e tests that fail when token txs without gas param are not properly handled. * Adds unit tests for _determineTransactionCategory * Base error throwing and simple gas setting in estimateTxGas on transactionCategory --- app/scripts/controllers/transactions/index.js | 11 ++++++----- app/scripts/controllers/transactions/tx-gas-utils.js | 15 +++++++-------- 2 files changed, 13 insertions(+), 13 deletions(-) (limited to 'app') diff --git a/app/scripts/controllers/transactions/index.js b/app/scripts/controllers/transactions/index.js index dc6a043e4..79dba7833 100644 --- a/app/scripts/controllers/transactions/index.js +++ b/app/scripts/controllers/transactions/index.js @@ -191,7 +191,7 @@ class TransactionController extends EventEmitter { } txUtils.validateTxParams(normalizedTxParams) // construct txMeta - const { transactionCategory, code } = await this._determineTransactionCategory(txParams) + const { transactionCategory, getCodeResponse } = await this._determineTransactionCategory(txParams) let txMeta = this.txStateManager.generateTxMeta({ txParams: normalizedTxParams, type: TRANSACTION_TYPE_STANDARD, @@ -204,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, code) + txMeta = await this.addTxGasDefaults(txMeta, getCodeResponse) } catch (error) { log.warn(error) txMeta.loadingDefaults = false @@ -224,7 +224,7 @@ class TransactionController extends EventEmitter { @param txMeta {Object} - the txMeta object @returns {Promise} resolves with txMeta */ - async addTxGasDefaults (txMeta, code) { + async addTxGasDefaults (txMeta, getCodeResponse) { const txParams = txMeta.txParams // ensure value txParams.value = txParams.value ? ethUtil.addHexPrefix(txParams.value) : '0x0' @@ -235,7 +235,7 @@ class TransactionController extends EventEmitter { } txParams.gasPrice = ethUtil.addHexPrefix(gasPrice.toString(16)) // set gasLimit - return await this.txGasUtil.analyzeGasUsage(txMeta, code) + return await this.txGasUtil.analyzeGasUsage(txMeta, getCodeResponse) } /** @@ -593,6 +593,7 @@ class TransactionController extends EventEmitter { 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' @@ -601,7 +602,7 @@ class TransactionController extends EventEmitter { result = codeIsEmpty ? SEND_ETHER_ACTION_KEY : CONTRACT_INTERACTION_KEY } - return { transactionCategory: result, code } + return { transactionCategory: result, getCodeResponse: code } } /** diff --git a/app/scripts/controllers/transactions/tx-gas-utils.js b/app/scripts/controllers/transactions/tx-gas-utils.js index 2dc461f48..287fb6f44 100644 --- a/app/scripts/controllers/transactions/tx-gas-utils.js +++ b/app/scripts/controllers/transactions/tx-gas-utils.js @@ -6,6 +6,7 @@ const { } = 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' @@ -25,14 +26,13 @@ class TxGasUtil { /** @param txMeta {Object} - the txMeta object - @param code {string} - the code at the txs address, as returned by this.query.getCode(to) @returns {object} the txMeta object with the gas written to the txParams */ - async analyzeGasUsage (txMeta, code) { + async analyzeGasUsage (txMeta, getCodeResponse) { const block = await this.query.getBlockByNumber('latest', false) let estimatedGasHex try { - estimatedGasHex = await this.estimateTxGas(txMeta, block.gasLimit, code) + estimatedGasHex = await this.estimateTxGas(txMeta, block.gasLimit, getCodeResponse) } catch (err) { log.warn(err) txMeta.simulationFails = { @@ -55,10 +55,9 @@ class TxGasUtil { Estimates the tx's gas usage @param txMeta {Object} - the txMeta object @param blockGasLimitHex {string} - hex string of the block's gas limit - @param code {string} - the code at the txs address, as returned by this.query.getCode(to) @returns {string} the estimated gas limit as a hex string */ - async estimateTxGas (txMeta, blockGasLimitHex, code) { + async estimateTxGas (txMeta, blockGasLimitHex, getCodeResponse) { const txParams = txMeta.txParams // check if gasLimit is already specified @@ -75,9 +74,9 @@ class TxGasUtil { // see if we can set the gas based on the recipient if (hasRecipient) { // 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') @@ -85,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 } -- cgit v1.2.3 From 3d715e5cf935ba0e2ced7caa8d8d0b26d3c983d4 Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Tue, 14 May 2019 09:40:03 -0700 Subject: Version 6.5.1 RC1 --- app/manifest.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'app') diff --git a/app/manifest.json b/app/manifest.json index 1d194a154..570e5b6cb 100644 --- a/app/manifest.json +++ b/app/manifest.json @@ -1,7 +1,7 @@ { "name": "__MSG_appName__", "short_name": "__MSG_appName__", - "version": "6.5.0", + "version": "6.5.1", "manifest_version": 2, "author": "https://metamask.io", "description": "__MSG_appDescription__", -- cgit v1.2.3 From e57ffbed593599cbf84d91144c7f208ee1298226 Mon Sep 17 00:00:00 2001 From: Paul Bouchon Date: Tue, 14 May 2019 14:38:55 -0400 Subject: feature: integrate gaba/ShapeShiftController (#6569) --- app/scripts/controllers/shapeshift.js | 180 ---------------------------------- app/scripts/metamask-controller.js | 12 +-- 2 files changed, 5 insertions(+), 187 deletions(-) delete mode 100644 app/scripts/controllers/shapeshift.js (limited to 'app') diff --git a/app/scripts/controllers/shapeshift.js b/app/scripts/controllers/shapeshift.js deleted file mode 100644 index 9b0287007..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} - * - */ - 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} 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 () { - 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/metamask-controller.js b/app/scripts/metamask-controller.js index 07054f84b..55ca96ad4 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -27,7 +27,6 @@ const NetworkController = require('./controllers/network') const PreferencesController = require('./controllers/preferences') const AppStateController = require('./controllers/app-state') const CurrencyController = require('./controllers/currency') -const ShapeShiftController = require('./controllers/shapeshift') const InfuraController = require('./controllers/infura') const CachedBalancesController = require('./controllers/cached-balances') const RecentBlocksController = require('./controllers/recent-blocks') @@ -57,6 +56,7 @@ const ethUtil = require('ethereumjs-util') const sigUtil = require('eth-sig-util') const { AddressBookController, + ShapeShiftController, PhishingController, } = require('gaba') const backEndMetaMetricsEvent = require('./lib/backend-metametrics') @@ -237,9 +237,7 @@ module.exports = class MetamaskController extends EventEmitter { }) this.balancesController.updateAllBalances() - this.shapeshiftController = new ShapeShiftController({ - initState: initState.ShapeShiftController, - }) + this.shapeshiftController = new ShapeShiftController(undefined, initState.ShapeShiftController) this.networkController.lookupNetwork() this.messageManager = new MessageManager() @@ -265,7 +263,7 @@ module.exports = class MetamaskController extends EventEmitter { PreferencesController: this.preferencesController.store, AddressBookController: this.addressBookController, CurrencyController: this.currencyController.store, - ShapeShiftController: this.shapeshiftController.store, + ShapeShiftController: this.shapeshiftController, NetworkController: this.networkController.store, InfuraController: this.infuraController.store, CachedBalancesController: this.cachedBalancesController.store, @@ -287,7 +285,7 @@ module.exports = class MetamaskController extends EventEmitter { RecentBlocksController: this.recentBlocksController.store, AddressBookController: this.addressBookController, CurrencyController: this.currencyController.store, - ShapeshiftController: this.shapeshiftController.store, + ShapeshiftController: this.shapeshiftController, InfuraController: this.infuraController.store, ProviderApprovalController: this.providerApprovalController.store, }) @@ -1633,7 +1631,7 @@ module.exports = class MetamaskController extends EventEmitter { * @property {string} depositType - An abbreviation of the type of crypto currency to be deposited. */ createShapeShiftTx (depositAddress, depositType) { - this.shapeshiftController.createShapeShiftTx(depositAddress, depositType) + this.shapeshiftController.createTransaction(depositAddress, depositType) } // network -- cgit v1.2.3 From 1d2cf52b649866a15ea00733739cc0212a53e509 Mon Sep 17 00:00:00 2001 From: Bruno Barbieri Date: Wed, 15 May 2019 04:53:09 -0400 Subject: Fixes bugs in 6.5.1 (#6613) * fix bg error * fix ui exception --- app/scripts/metamask-controller.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'app') diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index 55ca96ad4..242028c92 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -343,7 +343,7 @@ module.exports = class MetamaskController extends EventEmitter { updatePublicConfigStore(this.getState()) publicConfigStore.destroy = () => { - this.removeEventListener('update', updatePublicConfigStore) + this.removeEventListener && this.removeEventListener('update', updatePublicConfigStore) } function updatePublicConfigStore (memState) { -- cgit v1.2.3 From 7ff1857156a166f1b2fba943e1676d986771c410 Mon Sep 17 00:00:00 2001 From: Thomas Huang Date: Wed, 15 May 2019 02:16:05 -0700 Subject: Version Bump --- app/manifest.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'app') diff --git a/app/manifest.json b/app/manifest.json index 570e5b6cb..2002dee76 100644 --- a/app/manifest.json +++ b/app/manifest.json @@ -1,7 +1,7 @@ { "name": "__MSG_appName__", "short_name": "__MSG_appName__", - "version": "6.5.1", + "version": "6.5.2", "manifest_version": 2, "author": "https://metamask.io", "description": "__MSG_appDescription__", -- cgit v1.2.3 From c043132b00801f81d467748bebe147cae6893719 Mon Sep 17 00:00:00 2001 From: Dan J Miller Date: Wed, 15 May 2019 14:10:11 -0230 Subject: Adds e2e test for removing imported accounts. (#6615) --- app/_locales/en/messages.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'app') diff --git a/app/_locales/en/messages.json b/app/_locales/en/messages.json index bef278f79..47c1cfde8 100644 --- a/app/_locales/en/messages.json +++ b/app/_locales/en/messages.json @@ -1249,7 +1249,7 @@ "message": "Revert" }, "remove": { - "message": "remove" + "message": "Remove" }, "removeAccount": { "message": "Remove account" -- cgit v1.2.3 From a34103987a5a3b75588eb23ab4e1a73d6669cbc2 Mon Sep 17 00:00:00 2001 From: Frankie Date: Thu, 16 May 2019 07:36:53 +0200 Subject: drop transactions who's nonce is lower then the known network nonce but were not included in a block (#6388) * transactions/pending - check nonce against the network and mark as dropped if not included in a block * transactions/pending - unifiy "dropped" txs * transactions/pending - test - fix for new expected behavior * fix comment * transactions/pending - clean up dropped event * fix spelling Co-Authored-By: frankiebee * nit fix * test/tx-pending - clarify test description --- app/scripts/controllers/transactions/index.js | 1 + .../controllers/transactions/pending-tx-tracker.js | 53 ++++++++++++++++++---- 2 files changed, 46 insertions(+), 8 deletions(-) (limited to 'app') diff --git a/app/scripts/controllers/transactions/index.js b/app/scripts/controllers/transactions/index.js index 79dba7833..dd497a11e 100644 --- a/app/scripts/controllers/transactions/index.js +++ b/app/scripts/controllers/transactions/index.js @@ -555,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 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 -- cgit v1.2.3 From 8c0bc7b3e2ecf945aa0a2de721a11e725e531a05 Mon Sep 17 00:00:00 2001 From: bitpshr Date: Thu, 16 May 2019 10:53:37 -0400 Subject: bugfix: show extension window if locked regardless of approval --- app/scripts/controllers/provider-approval.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'app') diff --git a/app/scripts/controllers/provider-approval.js b/app/scripts/controllers/provider-approval.js index 8206b2f8a..06c499780 100644 --- a/app/scripts/controllers/provider-approval.js +++ b/app/scripts/controllers/provider-approval.js @@ -38,7 +38,8 @@ class ProviderApprovalController extends SafeEventEmitter { // only handle requestAccounts if (req.method !== 'eth_requestAccounts') return next() // if already approved or privacy mode disabled, return early - if (this.shouldExposeAccounts(origin)) { + const isUnlocked = this.keyringController.memStore.getState().isUnlocked + if (this.shouldExposeAccounts(origin) && isUnlocked) { res.result = [this.preferencesController.getSelectedAddress()] return } -- cgit v1.2.3 From 8bd27f50b05541e15aa9e811a435d7f1d0539a55 Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Thu, 16 May 2019 08:54:43 -0700 Subject: Version 6.5.3 RC1 --- app/manifest.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'app') diff --git a/app/manifest.json b/app/manifest.json index 2002dee76..8471fffac 100644 --- a/app/manifest.json +++ b/app/manifest.json @@ -1,7 +1,7 @@ { "name": "__MSG_appName__", "short_name": "__MSG_appName__", - "version": "6.5.2", + "version": "6.5.3", "manifest_version": 2, "author": "https://metamask.io", "description": "__MSG_appDescription__", -- cgit v1.2.3 From cfee7e8bb4ebccc6806ec766b09a4edae81133ee Mon Sep 17 00:00:00 2001 From: matteopey Date: Mon, 20 May 2019 18:52:05 +0200 Subject: Update translation (#6628) --- app/_locales/it/messages.json | 234 +++++++++++++++++++++++++++++++++++++++++- 1 file changed, 230 insertions(+), 4 deletions(-) (limited to 'app') diff --git a/app/_locales/it/messages.json b/app/_locales/it/messages.json index 9e0f1f06d..09ba045b9 100644 --- a/app/_locales/it/messages.json +++ b/app/_locales/it/messages.json @@ -11,6 +11,9 @@ "exposeDescription": { "message": "Esporre gli account al sito Web corrente. Utile per dapps legacy." }, + "chartOnlyAvailableEth": { + "message": "Grafico disponibile solo per le reti Ethereum." + }, "confirmExpose": { "message": "Sei sicuro di voler esporre i tuoi account al sito web corrente?" }, @@ -41,6 +44,15 @@ "providerRequestInfo": { "message": "Il dominio elencato di seguito sta tentando di richiedere l'accesso all'API Ethereum in modo che possa interagire con la blockchain di Ethereum. Controlla sempre di essere sul sito corretto prima di approvare l'accesso a Ethereum." }, + "about": { + "message": "Informazioni" + }, + "aboutSettingsDescription": { + "message": "Version, centro di supporto e contatti." + }, + "aboutUs": { + "message": "Chi siamo" + }, "accept": { "message": "Accetta" }, @@ -71,6 +83,15 @@ "address": { "message": "Indirizzo" }, + "addNetwork": { + "message": "Aggiungi Rete" + }, + "advanced": { + "message": "Avanzate" + }, + "advancedSettingsDescription": { + "message": "Accedi alle funzionalità sviluppatore, download dei log di Stato, Reset Account, imposta reti di test e RPC personalizzata." + }, "advancedOptions": { "message": "Opzioni Avanzate" }, @@ -89,8 +110,14 @@ "addAcquiredTokens": { "message": "Aggiungi i token che hai acquistato usando MetaMask" }, - "advanced": { - "message": "Avanzato" + "agreeTermsOfService": { + "message": "Accetto i termini di servizio" + }, + "allDone": { + "message": "Tutto Fatto" + }, + "alreadyHaveSeedPhrase": { + "message": "No, ho già una frase seed" }, "amount": { "message": "Importo" @@ -115,6 +142,9 @@ "approved": { "message": "Approvato" }, + "asset": { + "message": "Asset" + }, "attemptingConnect": { "message": "Tentativo di connessione alla blockchain." }, @@ -127,6 +157,12 @@ "attributions": { "message": "Attribuzioni" }, + "autoLogoutTimeLimit": { + "message": "Timer di Logout Automatico (minuti)" + }, + "autoLogoutTimeLimitDescription": { + "message": "Imposta il tempo di inattività dopo il quale MetaMask fa il log out automaticamente" + }, "available": { "message": "Disponibile" }, @@ -158,6 +194,13 @@ "message": "deve essere maggiore o uguale a $1 e minore o uguale a $2.", "description": "aiuto per inserire un input esadecimale come decimale" }, + "blockExplorerUrl": { + "message": "Block Explorer" + }, + "blockExplorerView": { + "message": "Visualizza account su $1", + "description": "$1 replaced by URL for custom block explorer" + }, "blockiesIdenticon": { "message": "Usa le icone Blockie" }, @@ -179,6 +222,12 @@ "buyCoinbaseExplainer": { "message": "Coinbase è il servizio più popolare al mondo per comprare e vendere Bitcoin, Ethereum e Litecoin." }, + "buyWithWyre": { + "message": "Compra ETH con Wyre" + }, + "buyWithWyreDescription": { + "message": "Wyre ti consente di usare la carta di credito per depositare ETH direttamente nel tuo account MetaMask." + }, "buyCoinSwitch": { "message": "Compra su CoinSwitch" }, @@ -191,6 +240,9 @@ "ok": { "message": "Ok" }, + "optionalBlockExplorerUrl": { + "message": "URL del Block Explorer (opzionale)" + }, "cancel": { "message": "Annulla" }, @@ -206,6 +258,9 @@ "cancelN": { "message": "Annulla tutte le transazioni relative a $1" }, + "chainId": { + "message": "Chain ID" + }, "classicInterface": { "message": "Usa l'interfaccia classica" }, @@ -224,6 +279,9 @@ "chromeRequiredForHardwareWallets": { "message": "Devi usare MetaMask con Google Chrome per connettere il tuo Portafoglio Hardware" }, + "company": { + "message": "Azienda" + }, "confirm": { "message": "Conferma" }, @@ -245,6 +303,9 @@ "confirmTransaction": { "message": "Conferma Transazione" }, + "congratulations": { + "message": "Congratulazioni" + }, "connectHardwareWallet": { "message": "Connetti Portafoglio Hardware" }, @@ -272,6 +333,12 @@ "connectingToRinkeby": { "message": "Connessione alla Rete di test Rinkeby" }, + "connectingToLocalhost": { + "message": "Connessione a Localhost 8545" + }, + "connectingToGoerli": { + "message": "Connessione alla Rete di Test Goerli" + }, "connectingToUnknown": { "message": "Connessione ad una Rete Sconosciuta" }, @@ -287,6 +354,9 @@ "continueToCoinbase": { "message": "Continua su Coinbase" }, + "continueToWyre": { + "message": "Continua su Wyre" + }, "continueToCoinSwitch": { "message": "Continua su CoinSwitch" }, @@ -314,6 +384,12 @@ "copyAddress": { "message": "Copia l'indirizzo" }, + "copyTransactionId": { + "message": "Copia ID Transazione" + }, + "copiedTransactionId": { + "message": "ID Transazione Copiato" + }, "copyToClipboard": { "message": "Copia negli appunti" }, @@ -329,6 +405,9 @@ "createAccount": { "message": "Crea Account" }, + "createAWallet": { + "message": "Crea un Wallet" + }, "createDen": { "message": "Crea" }, @@ -439,6 +518,9 @@ "edit": { "message": "Modifica" }, + "editNetwork": { + "message": "Modifica Rete" + }, "editAccountName": { "message": "Modifica Nome Account" }, @@ -451,6 +533,30 @@ "encryptNewDen": { "message": "Cripta il tuo nuovo DEN" }, + "endOfFlowMessage1": { + "message": "Hai passato il test - tieni la tua frase seed al sicuro, è tua responsabilità!" + }, + "endOfFlowMessage2": { + "message": "Suggerimenti su come tenerla al sicuro" + }, + "endOfFlowMessage3": { + "message": "Salva un backup in più di un posto." + }, + "endOfFlowMessage4": { + "message": "Non condividerla mai con nessuno." + }, + "endOfFlowMessage5": { + "message": "Stai attento al phishing! MetaMask non ti chiederà mai spontaneamente la tua frase seed." + }, + "endOfFlowMessage6": { + "message": "Se vorrai fare nuovamente un backup della frase, la puoi trovare in Impostazioni -> Sicurezza & Privacy." + }, + "endOfFlowMessage7": { + "message": "Se hai delle domande o vedi delle attività sospette, manda una mail a support@metamask.io." + }, + "endOfFlowMessage8": { + "message": "MetaMask non può recuperare la tua frase seed. Impara di più." + }, "ensNameNotFound": { "message": "Nome ENS non trovato" }, @@ -571,6 +677,12 @@ "gasPriceRequired": { "message": "Prezzo Gas Richiesto" }, + "general": { + "message": "Generale" + }, + "generalSettingsDescription": { + "message": "Conversione moneta, moneta primaria, lingua, icone blockie" + }, "generatingTransaction": { "message": "Generando la transazione" }, @@ -584,10 +696,16 @@ "getHelp": { "message": "Aiuto." }, + "getStarted": { + "message": "Inizia" + }, "greaterThanMin": { "message": "deve essere maggiore o uguale a $1.", "description": "aiuto per inserire un input esadecimale come decimale" }, + "happyToSeeYou": { + "message": "Siamo contenti di vederti." + }, "hardware": { "message": "hardware" }, @@ -650,6 +768,12 @@ "importDen": { "message": "Importa un DEN Esistente" }, + "importWallet": { + "message": "Importa Portafoglio" + }, + "importYourExisting": { + "message": "Importa il tuo portafoglio esistente usando la tua frase seed a 12 parole" + }, "imported": { "message": "Importato", "description": "stato che conferma che un account è stato totalmente caricato nel portachiavi" @@ -687,6 +811,9 @@ "knownAddressRecipient": { "message": "Indirizzo del contratto conosciuto." }, + "invalidAddressRecipientNotEthNetwork": { + "message": "Non rete ETH, inserisci caratteri minuscoli" + }, "invalidGasParams": { "message": "Parametri del Gas non validi" }, @@ -727,10 +854,16 @@ "ledgerAccountRestriction": { "message": "E' necessario utilizzare l'ultimo account prima di poterne aggiungere uno nuovo." }, + "legal": { + "message": "Informazioni Legali" + }, "lessThanMax": { "message": "deve essere minore o uguale a $1.", "description": "aiuto per inserire un input esadecimale come decimale" }, + "letsGoSetUp": { + "message": "Si, iniziamo!" + }, "likeToAddTokens": { "message": "Vorresti aggiungere questi token?" }, @@ -794,6 +927,12 @@ "minutesShorthand": { "message": "Min" }, + "mobileSyncTitle": { + "message": "Sincronizza account con il dispositivo mobile" + }, + "mobileSyncText": { + "message": "Per favore inserisci la password per confermare che sei te!" + }, "myAccounts": { "message": "Miei Account" }, @@ -814,9 +953,15 @@ "negativeETH": { "message": "Non puoi inviare una quantità di ETH negativa." }, + "networkName": { + "message": "Nome Rete" + }, "networks": { "message": "Reti" }, + "networkSettingsDescription": { + "message": "Aggiungi e modifica reti RPC personalizzate" + }, "nevermind": { "message": "Non importa" }, @@ -842,7 +987,22 @@ "newNetwork": { "message": "Nuova Rete" }, - "rpcURL": { + "newToMetaMask": { + "message": "Nuovo a MetaMask?" + }, + "noAlreadyHaveSeed": { + "message": "No, ho già una frase seed" + }, + "protectYourKeys": { + "message": "Proteggi le tue chiavi!" + }, + "protectYourKeysMessage1": { + "message": "Stai attento con la tua frase seed - ci sono stati report di siti web che hanno tentato di imitare MetaMask. MetaMask non ti chiederà mai la tua frase seed!" + }, + "protectYourKeysMessage2": { + "message": "Tieni la tua frase al sicuro. Se vedi qualcosa di sospetto, o non sei sicuro di un sito web, manda una mail a support@metamask.io" + }, + "rpcUrl": { "message": "Nuovo URL RPC" }, "showAdvancedOptions": { @@ -884,6 +1044,9 @@ "noTransactions": { "message": "Nessuna Transazione" }, + "notEnoughGas": { + "message": "Gas Non Sufficiente" + }, "notFound": { "message": "Non Trovata" }, @@ -934,6 +1097,12 @@ "originalTotal": { "message": "Totale Precedente" }, + "participateInMetaMetrics": { + "message": "Participa in MetaMetrics" + }, + "participateInMetaMetricsDescription": { + "message": "Participa in MetaMetrics per aiutarci a rendere MetaMask migliore" + }, "password": { "message": "Password" }, @@ -1097,6 +1266,9 @@ "ropsten": { "message": "Rete di test Ropsten" }, + "goerli": { + "message": "Rete di test Goerli" + }, "rpc": { "message": "RPC Personalizzata" }, @@ -1147,6 +1319,12 @@ "secretPhrase": { "message": "Inserisci la tua frase segreta di dodici parole per ripristinare la cassaforte." }, + "securityAndPrivacy": { + "message": "Sicurezza & Privacy" + }, + "securitySettingsDescription": { + "message": "Impostazioni sulla Privacy e sulla frase seed del portafoglio" + }, "secondsShorthand": { "message": "Sec" }, @@ -1207,6 +1385,9 @@ "selectAnAccountHelp": { "message": "Selezione l'account da visualizzare in MetaMask" }, + "selectAnAsset": { + "message": "Seleziona un Asset" + }, "selectAHigherGasFee": { "message": "Seleziona un costo in gas maggiore per accelerare l'elaborazione della transazione.*" }, @@ -1229,7 +1410,13 @@ "message": "Controlli gas avanzati" }, "showAdvancedGasInlineDescription": { - "message": "Seleziona qui per visualizzare i controlli su prezzo e limite del gas nelle schermate di invio e conferma." + "message": "Seleziona per visualizzare i controlli su prezzo e limite del gas nelle schermate di invio e conferma." + }, + "showFiatConversionInTestnets": { + "message": "Mostra conversione nelle reti di test" + }, + "showFiatConversionInTestnetsDescription": { + "message": "Seleziona se vuoi vedere la conversione in valuta fiat nelle reti di test" }, "showPrivateKeys": { "message": "Mostra Chiave Privata" @@ -1330,9 +1517,33 @@ "supportCenter": { "message": "Visita il nostro Centro di Supporto" }, + "symbol": { + "message": "Simbolo" + }, "symbolBetweenZeroTwelve": { "message": "Il simbolo deve essere lungo tra 0 e 12 caratteri." }, + "syncWithMobile": { + "message": "Sincronizza con dispositivo mobile" + }, + "syncWithMobileTitle": { + "message": "Sincronizza con dispositivo mobile" + }, + "syncWithMobileDesc": { + "message": "Puoi sincronizzare i tuoi account e le tue informazioni con il tuo dispositivo mobile. Apri l'app di MetaMask, vai su \"Impostazioni\" e tocca \"Sincronizza da Estensione sul Browser\"" + }, + "syncWithMobileDescNewUsers": { + "message": "Se hai appena aperto l'app di MetaMask per la prima volta, segui i passaggi sul tuo telefono." + }, + "syncWithMobileScanThisCode": { + "message": "Scansiona questo codice con l'app di MetaMask" + }, + "syncWithMobileBeCareful": { + "message": "Assicurati che nessun'altro stia guardando al tuo schermo quando scansioni questo codice" + }, + "syncWithMobileComplete": { + "message": "I tuoi dati sono stati sincronizzati con successo. Goditi l'app di MetaMask!" + }, "takesTooLong": { "message": "Ci sta mettendo troppo?" }, @@ -1342,6 +1553,9 @@ "testFaucet": { "message": "Prova Faucet" }, + "thisWillCreate": { + "message": "Questo creerà un nuovo portafoglio e frase seed" + }, "tips": { "message": "Suggerimenti" }, @@ -1364,6 +1578,9 @@ "tokenBalance": { "message": "Bilancio Token:" }, + "tokenContractAddress": { + "message": "Indirizzo Contratto Token" + }, "tokenSelection": { "message": "Cerca un token o seleziona dalla lista di token più popolari." }, @@ -1525,9 +1742,15 @@ "viewAccount": { "message": "Vedi Account" }, + "viewOnCustomBlockExplorer": { + "message": "Vedi su $1" + }, "viewOnEtherscan": { "message": "Vedi su Etherscan" }, + "viewNetworkInfo": { + "message": "Visualizza Informazioni Rete" + }, "visitWebSite": { "message": "Visita il nostro sito web" }, @@ -1573,6 +1796,9 @@ "yourUniqueAccountImageDescription2": { "message": "Vedrai questa immagine ogni volta che dovrai confermare una transazione." }, + "yourUniqueAccountImageDescription3": { + "message": "MetaMask non ti chiederà mai la tua frase seed!" + }, "zeroGasPriceOnSpeedUpError": { "message": "Prezzo del gas maggiore di zero" } -- cgit v1.2.3 From e0c11371a9d5f419d91a6d30d2cfda1743d2a967 Mon Sep 17 00:00:00 2001 From: Chi Kei Chan Date: Tue, 21 May 2019 05:59:09 -0700 Subject: Fix grammatical error in i18n endOfFlowMessage6 (#6633) --- app/_locales/en/messages.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'app') diff --git a/app/_locales/en/messages.json b/app/_locales/en/messages.json index 47c1cfde8..41ba7ef7a 100644 --- a/app/_locales/en/messages.json +++ b/app/_locales/en/messages.json @@ -549,7 +549,7 @@ "message": "Be careful of phishing! MetaMask will never spontaneously ask for your seed phrase." }, "endOfFlowMessage6": { - "message": "If you need to back your up seed phrase again, you can find it in Settings -> Security." + "message": "If you need to back up your seed phrase again, you can find it in Settings -> Security." }, "endOfFlowMessage7": { "message": "If you ever have questions or see something fishy, email support@metamask.io." -- cgit v1.2.3 From 2b5c7b82a95dea4ddb50917daa52fae07715e210 Mon Sep 17 00:00:00 2001 From: Frankie Date: Tue, 21 May 2019 17:17:09 +0200 Subject: transactions/deps - use broken out nonce-tracker module (#6555) --- app/scripts/controllers/transactions/index.js | 2 +- .../controllers/transactions/nonce-tracker.js | 161 --------------------- 2 files changed, 1 insertion(+), 162 deletions(-) delete mode 100644 app/scripts/controllers/transactions/nonce-tracker.js (limited to 'app') diff --git a/app/scripts/controllers/transactions/index.js b/app/scripts/controllers/transactions/index.js index dd497a11e..1ae925835 100644 --- a/app/scripts/controllers/transactions/index.js +++ b/app/scripts/controllers/transactions/index.js @@ -17,7 +17,7 @@ const { 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') 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} 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} - */ - 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 -- cgit v1.2.3