aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDan Finlay <542863+danfinlay@users.noreply.github.com>2019-05-15 04:22:54 +0800
committerGitHub <noreply@github.com>2019-05-15 04:22:54 +0800
commitbf5b7f8e77684ce4a66d7e0301ed19d048a545f9 (patch)
tree9c68af7d6cb4b10410d5deeb33b9c9b61996cd02
parent7dcc707a4329d69e9d4d7c6befbfd228ed7d714b (diff)
parent985f51a764560311e8aa5bb149e34b3c25b78c92 (diff)
downloadtangerine-wallet-browser-bf5b7f8e77684ce4a66d7e0301ed19d048a545f9.tar
tangerine-wallet-browser-bf5b7f8e77684ce4a66d7e0301ed19d048a545f9.tar.gz
tangerine-wallet-browser-bf5b7f8e77684ce4a66d7e0301ed19d048a545f9.tar.bz2
tangerine-wallet-browser-bf5b7f8e77684ce4a66d7e0301ed19d048a545f9.tar.lz
tangerine-wallet-browser-bf5b7f8e77684ce4a66d7e0301ed19d048a545f9.tar.xz
tangerine-wallet-browser-bf5b7f8e77684ce4a66d7e0301ed19d048a545f9.tar.zst
tangerine-wallet-browser-bf5b7f8e77684ce4a66d7e0301ed19d048a545f9.zip
Merge pull request #6599 from MetaMask/develop
Master Version Bump (v6.5.0)
-rw-r--r--.eslintrc2
-rw-r--r--CHANGELOG.md18
-rw-r--r--app/_locales/en/messages.json42
-rw-r--r--app/manifest.json2
-rw-r--r--app/scripts/contentscript.js213
-rw-r--r--app/scripts/controllers/app-state.js73
-rw-r--r--app/scripts/controllers/balance.js2
-rw-r--r--app/scripts/controllers/blacklist.js136
-rw-r--r--app/scripts/controllers/network/createBlockTracker.js19
-rw-r--r--app/scripts/controllers/network/createInfuraClient.js6
-rw-r--r--app/scripts/controllers/network/createJsonRpcClient.js6
-rw-r--r--app/scripts/controllers/network/createLocalhostClient.js6
-rw-r--r--app/scripts/controllers/network/network.js12
-rw-r--r--app/scripts/controllers/preferences.js46
-rw-r--r--app/scripts/controllers/provider-approval.js123
-rw-r--r--app/scripts/controllers/shapeshift.js180
-rw-r--r--app/scripts/controllers/token-rates.js4
-rw-r--r--app/scripts/controllers/transactions/index.js56
-rw-r--r--app/scripts/controllers/transactions/tx-gas-utils.js16
-rw-r--r--app/scripts/controllers/transactions/tx-state-manager.js4
-rw-r--r--app/scripts/createStandardProvider.js29
-rw-r--r--app/scripts/inpage.js122
-rw-r--r--app/scripts/lib/backend-metametrics.js26
-rw-r--r--app/scripts/lib/createDnodeRemoteGetter.js16
-rw-r--r--app/scripts/lib/message-manager.js2
-rw-r--r--app/scripts/lib/personal-message-manager.js2
-rw-r--r--app/scripts/metamask-controller.js181
-rw-r--r--app/scripts/migrations/024.js2
-rw-r--r--app/scripts/migrations/025.js2
-rw-r--r--app/scripts/platforms/extension.js14
-rw-r--r--development/backGroundConnectionModifiers.js6
-rw-r--r--development/states/conf-tx.json3
-rw-r--r--development/states/confirm-new-ui.json4
-rw-r--r--development/states/confirm-sig-requests.json4
-rw-r--r--development/states/currency-localization.json4
-rw-r--r--development/states/send-edit.json4
-rw-r--r--development/states/send-new-ui.json4
-rw-r--r--development/states/send.json3
-rw-r--r--development/states/tx-list-items.json4
-rw-r--r--gentests.js6
-rw-r--r--gulpfile.js2
-rw-r--r--package-lock.json585
-rw-r--r--package.json12
-rw-r--r--test/e2e/beta/contract-test/contract.js37
-rw-r--r--test/e2e/beta/contract-test/index.html2
-rw-r--r--test/e2e/beta/metamask-beta-ui.spec.js167
-rw-r--r--test/integration/lib/confirm-sig-requests.js15
-rw-r--r--test/integration/lib/currency-localization.js13
-rw-r--r--test/integration/lib/send-new-ui.js5
-rw-r--r--test/integration/lib/tx-list-items.js13
-rw-r--r--test/lib/mock-encryptor.js6
-rw-r--r--test/lib/util.js2
-rw-r--r--test/unit/actions/tx_test.js4
-rw-r--r--test/unit/app/controllers/blacklist-controller-test.js56
-rw-r--r--test/unit/app/controllers/currency-controller-test.js2
-rw-r--r--test/unit/app/controllers/metamask-controller-test.js25
-rw-r--r--test/unit/app/controllers/preferences-controller-test.js6
-rw-r--r--test/unit/app/controllers/transactions/pending-tx-test.js12
-rw-r--r--test/unit/app/controllers/transactions/tx-controller-test.js87
-rw-r--r--test/unit/app/controllers/transactions/tx-gas-util-test.js2
-rw-r--r--test/unit/app/controllers/transactions/tx-state-history-helper-test.js2
-rw-r--r--test/unit/app/controllers/transactions/tx-state-manager-test.js2
-rw-r--r--test/unit/app/edge-encryptor-test.js2
-rw-r--r--test/unit/migrations/migrator-test.js2
-rw-r--r--test/unit/ui/app/actions.spec.js57
-rw-r--r--test/web3/web3.js2
-rw-r--r--ui/app/components/app/account-panel.js17
-rw-r--r--ui/app/components/app/bn-as-decimal-input.js2
-rw-r--r--ui/app/components/app/dropdowns/account-details-dropdown.js17
-rw-r--r--ui/app/components/app/dropdowns/network-dropdown.js10
-rw-r--r--ui/app/components/app/ens-input.js2
-rw-r--r--ui/app/components/app/gas-customization/advanced-gas-inputs/advanced-gas-inputs.component.js2
-rw-r--r--ui/app/components/app/gas-customization/advanced-gas-inputs/advanced-gas-inputs.container.js4
-rw-r--r--ui/app/components/app/gas-customization/gas-modal-page-container/advanced-tab-content/advanced-tab-content.component.js3
-rw-r--r--ui/app/components/app/gas-customization/gas-modal-page-container/gas-modal-page-container.component.js2
-rw-r--r--ui/app/components/app/gas-customization/gas-price-button-group/gas-price-button-group.component.js2
-rw-r--r--ui/app/components/app/gas-customization/gas-price-chart/gas-price-chart.utils.js6
-rw-r--r--ui/app/components/app/gas-customization/gas-price-chart/tests/gas-price-chart.component.test.js2
-rw-r--r--ui/app/components/app/modals/account-details-modal.js12
-rw-r--r--ui/app/components/app/modals/deposit-ether-modal.js2
-rw-r--r--ui/app/components/app/modals/export-private-key-modal.js4
-rw-r--r--ui/app/components/app/modals/metametrics-opt-in-modal/metametrics-opt-in-modal.container.js2
-rw-r--r--ui/app/components/app/modals/qr-scanner/qr-scanner.component.js2
-rw-r--r--ui/app/components/app/modals/reject-transactions/reject-transactions.container.js2
-rw-r--r--ui/app/components/app/provider-page-container/provider-page-container.component.js13
-rw-r--r--ui/app/components/app/token-cell.js2
-rw-r--r--ui/app/components/app/transaction-list-item-details/transaction-list-item-details.component.js15
-rw-r--r--ui/app/components/app/transaction-list-item/transaction-list-item.component.js3
-rw-r--r--ui/app/components/app/transaction-list-item/transaction-list-item.container.js5
-rw-r--r--ui/app/components/app/user-preferenced-currency-display/tests/user-preferenced-currency-display.container.test.js2
-rw-r--r--ui/app/components/app/user-preferenced-currency-display/user-preferenced-currency-display.container.js2
-rw-r--r--ui/app/components/ui/alert/index.js4
-rw-r--r--ui/app/components/ui/currency-display/currency-display.component.js2
-rw-r--r--ui/app/components/ui/currency-display/tests/currency-display.container.test.js2
-rw-r--r--ui/app/components/ui/currency-input/tests/currency-input.container.test.js2
-rw-r--r--ui/app/components/ui/text-field/text-field.component.js6
-rw-r--r--ui/app/components/ui/token-input/tests/token-input.container.test.js2
-rw-r--r--ui/app/components/ui/unit-input/unit-input.component.js2
-rw-r--r--ui/app/ducks/app/app.js12
-rw-r--r--ui/app/ducks/confirm-transaction/confirm-transaction.duck.js2
-rw-r--r--ui/app/ducks/confirm-transaction/confirm-transaction.duck.test.js2
-rw-r--r--ui/app/ducks/gas/gas-duck.test.js4
-rw-r--r--ui/app/helpers/constants/routes.js2
-rw-r--r--ui/app/helpers/higher-order-components/i18n-provider.js12
-rw-r--r--ui/app/helpers/higher-order-components/metametrics/metametrics.provider.js2
-rw-r--r--ui/app/helpers/higher-order-components/with-modal-props/tests/with-modal-props.test.js2
-rw-r--r--ui/app/helpers/utils/conversion-util.js2
-rw-r--r--ui/app/helpers/utils/metametrics.util.js6
-rw-r--r--ui/app/helpers/utils/transactions.util.js16
-rw-r--r--ui/app/helpers/utils/util.js9
-rw-r--r--ui/app/pages/confirm-transaction-base/confirm-transaction-base.component.js11
-rw-r--r--ui/app/pages/create-account/connect-hardware/account-list.js4
-rw-r--r--ui/app/pages/create-account/connect-hardware/connect-screen.js4
-rw-r--r--ui/app/pages/create-account/connect-hardware/index.js4
-rw-r--r--ui/app/pages/create-account/import-account/seed.js2
-rw-r--r--ui/app/pages/first-time-flow/metametrics-opt-in/metametrics-opt-in.component.js2
-rw-r--r--ui/app/pages/first-time-flow/seed-phrase/confirm-seed-phrase/confirm-seed-phrase.component.js191
-rw-r--r--ui/app/pages/first-time-flow/seed-phrase/confirm-seed-phrase/confirm-seed-phrase.state.js41
-rw-r--r--ui/app/pages/first-time-flow/seed-phrase/confirm-seed-phrase/draggable-seed.component.js126
-rw-r--r--ui/app/pages/first-time-flow/seed-phrase/confirm-seed-phrase/index.scss93
-rw-r--r--ui/app/pages/first-time-flow/seed-phrase/seed-phrase.component.js76
-rw-r--r--ui/app/pages/first-time-flow/seed-phrase/tests/confirm-seed-phrase-component.test.js169
-rw-r--r--ui/app/pages/home/home.component.js19
-rw-r--r--ui/app/pages/provider-approval/provider-approval.component.js10
-rw-r--r--ui/app/pages/provider-approval/provider-approval.container.js6
-rw-r--r--ui/app/pages/routes/index.js24
-rw-r--r--ui/app/pages/send/account-list-item/tests/account-list-item-container.test.js2
-rw-r--r--ui/app/pages/send/send-content/send-amount-row/amount-max-button/amount-max-button.component.js13
-rw-r--r--ui/app/pages/send/send-content/send-amount-row/amount-max-button/tests/amount-max-button-component.test.js7
-rw-r--r--ui/app/pages/send/send-content/send-row-wrapper/send-row-error-message/tests/send-row-error-message-container.test.js2
-rw-r--r--ui/app/pages/send/send-content/send-row-wrapper/send-row-warning-message/tests/send-row-warning-message-container.test.js2
-rw-r--r--ui/app/pages/send/send-content/send-to-row/send-to-row.utils.js5
-rw-r--r--ui/app/pages/send/send-content/send-to-row/tests/send-to-row-utils.test.js10
-rw-r--r--ui/app/pages/send/tests/send-container.test.js2
-rw-r--r--ui/app/pages/send/tests/send-utils.test.js4
-rw-r--r--ui/app/pages/send/to-autocomplete/to-autocomplete.js2
-rw-r--r--ui/app/pages/settings/advanced-tab/advanced-tab.component.js47
-rw-r--r--ui/app/pages/settings/advanced-tab/advanced-tab.container.js11
-rw-r--r--ui/app/pages/settings/advanced-tab/tests/advanced-tab-component.test.js44
-rw-r--r--ui/app/pages/settings/advanced-tab/tests/advanced-tab-container.test.js46
-rw-r--r--ui/app/pages/settings/index.scss60
-rw-r--r--ui/app/pages/settings/networks-tab/index.js1
-rw-r--r--ui/app/pages/settings/networks-tab/index.scss200
-rw-r--r--ui/app/pages/settings/networks-tab/network-form/index.js1
-rw-r--r--ui/app/pages/settings/networks-tab/network-form/network-form.component.js225
-rw-r--r--ui/app/pages/settings/networks-tab/networks-tab.component.js214
-rw-r--r--ui/app/pages/settings/networks-tab/networks-tab.constants.js50
-rw-r--r--ui/app/pages/settings/networks-tab/networks-tab.container.js77
-rw-r--r--ui/app/pages/settings/settings.component.js28
-rw-r--r--ui/app/selectors/selectors.js23
-rw-r--r--ui/app/store/actions.js122
-rw-r--r--ui/example.js2
-rw-r--r--ui/lib/account-link.js6
-rw-r--r--ui/lib/test-timeout.js2
154 files changed, 3403 insertions, 1337 deletions
diff --git a/.eslintrc b/.eslintrc
index ecf59c68c..53033b753 100644
--- a/.eslintrc
+++ b/.eslintrc
@@ -133,7 +133,7 @@
"no-unneeded-ternary": [2, { "defaultAssignment": false }],
"no-unreachable": 2,
"no-unsafe-finally": 2,
- "no-unused-vars": [2, { "vars": "all", "args": "none" }],
+ "no-unused-vars": [2, { "vars": "all", "args": "all", "argsIgnorePattern": "[_]+" }],
"no-useless-call": 2,
"no-useless-computed-key": 2,
"no-useless-constructor": 2,
diff --git a/CHANGELOG.md b/CHANGELOG.md
index d62a32a25..d3e333e23 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,13 +2,29 @@
## Current Develop Branch
+## 6.5.1 Tue May 14 2019
+
+- Fix bug where approve method would show a warning. #6602
+- [#6593](https://github.com/MetaMask/metamask-extension/pull/6593): Fix wording of autoLogoutTimeLimitDescription
+
+## 6.5.0 Fri May 10 2019
+
+- [#6568](https://github.com/MetaMask/metamask-extension/pull/6568): feature: integrate gaba/PhishingController
+- [#6490](https://github.com/MetaMask/metamask-extension/pull/6490): Redesign custom RPC form
+- [#6558](https://github.com/MetaMask/metamask-extension/pull/6558): Adds auto logout with customizable time frame
+- [#6578](https://github.com/MetaMask/metamask-extension/pull/6578): Fixes ability to send to token contract addresses
+- [#6557](https://github.com/MetaMask/metamask-extension/pull/6557): Adds drag and drop functionality to seed phrase entry.
+- [#6526](https://github.com/MetaMask/metamask-extension/pull/6526): Include token checksum address in prices lookup for token rates
+- [#6502](https://github.com/MetaMask/metamask-extension/pull/6502): Add subheader to all settings subviews
+- [#6501](https://github.com/MetaMask/metamask-extension/pull/6501): Improve confirm screen loading performance by fixing home screen rendering bug
+
## 6.4.1 Fri Apr 26 2019
- [#6521](https://github.com/MetaMask/metamask-extension/pull/6521): Revert "Adds 4byte registry fallback to getMethodData()" to fix stalling bug.
## 6.4.0 Wed Apr 17 2019
-- [#6445](https://github.com/MetaMask/metamask-extension/pull/6445): * Move send to pages/
+- [#6445](https://github.com/MetaMask/metamask-extension/pull/6445): * Move send to pages/
- [#6470](https://github.com/MetaMask/metamask-extension/pull/6470): update publishing.md with dev diagram
- [#6403](https://github.com/MetaMask/metamask-extension/pull/6403): Update to eth-method-registry@1.2.0
- [#6468](https://github.com/MetaMask/metamask-extension/pull/6468): Fix switcher height when Custom RPC is selected or loading
diff --git a/app/_locales/en/messages.json b/app/_locales/en/messages.json
index 184507cbb..bef278f79 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"
},
@@ -154,6 +157,12 @@
"attributions": {
"message": "Attributions"
},
+ "autoLogoutTimeLimit": {
+ "message": "Auto-Logout Timer (minutes)"
+ },
+ "autoLogoutTimeLimitDescription": {
+ "message": "Set the idle time in minutes before MetaMask will automatically log out"
+ },
"available": {
"message": "Available"
},
@@ -185,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"
},
@@ -224,6 +240,9 @@
"ok": {
"message": "Ok"
},
+ "optionalBlockExplorerUrl": {
+ "message": "Block Explorer URL (optional)"
+ },
"cancel": {
"message": "Cancel"
},
@@ -239,6 +258,9 @@
"cancelN": {
"message": "Cancel all $1 transactions"
},
+ "chainId": {
+ "message": "Chain ID"
+ },
"classicInterface": {
"message": "Use classic interface"
},
@@ -496,6 +518,9 @@
"edit": {
"message": "Edit"
},
+ "editNetwork": {
+ "message": "Edit Network"
+ },
"editAccountName": {
"message": "Edit Account Name"
},
@@ -928,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"
},
@@ -971,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": {
@@ -1486,6 +1517,9 @@
"supportCenter": {
"message": "Visit our Support Center"
},
+ "symbol": {
+ "message": "Symbol"
+ },
"symbolBetweenZeroTwelve": {
"message": "Symbol must be between 0 and 12 characters."
},
@@ -1708,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/manifest.json b/app/manifest.json
index bd10f60da..570e5b6cb 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.1",
"manifest_version": 2,
"author": "https://metamask.io",
"description": "__MSG_appDescription__",
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()
}
/**
@@ -47,148 +44,107 @@ 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/app-state.js b/app/scripts/controllers/app-state.js
new file mode 100644
index 000000000..9533fd458
--- /dev/null
+++ b/app/scripts/controllers/app-state.js
@@ -0,0 +1,73 @@
+const ObservableStore = require('obs-store')
+const extend = require('xtend')
+
+class AppStateController {
+ /**
+ * @constructor
+ * @param opts
+ */
+ constructor (opts = {}) {
+ const {initState, onInactiveTimeout, preferencesStore} = opts
+ const {preferences} = preferencesStore.getState()
+
+ this.onInactiveTimeout = onInactiveTimeout || (() => {})
+ this.store = new ObservableStore(extend({
+ timeoutMinutes: 0,
+ }, initState))
+ this.timer = null
+
+ preferencesStore.subscribe(state => {
+ this._setInactiveTimeout(state.preferences.autoLogoutTimeLimit)
+ })
+
+ this._setInactiveTimeout(preferences.autoLogoutTimeLimit)
+ }
+
+ /**
+ * Sets the last active time to the current time
+ * @return {void}
+ */
+ setLastActiveTime () {
+ this._resetTimer()
+ }
+
+ /**
+ * Sets the inactive timeout for the app
+ * @param {number} timeoutMinutes the inactive timeout in minutes
+ * @return {void}
+ * @private
+ */
+ _setInactiveTimeout (timeoutMinutes) {
+ this.store.putState({
+ timeoutMinutes,
+ })
+
+ this._resetTimer()
+ }
+
+ /**
+ * Resets the internal inactive timer
+ *
+ * If the {@code timeoutMinutes} state is falsy (i.e., zero) then a new
+ * timer will not be created.
+ *
+ * @return {void}
+ * @private
+ */
+ _resetTimer () {
+ const {timeoutMinutes} = this.store.getState()
+
+ if (this.timer) {
+ clearTimeout(this.timer)
+ }
+
+ if (!timeoutMinutes) {
+ return
+ }
+
+ this.timer = setTimeout(() => this.onInactiveTimeout(), timeoutMinutes * 60 * 1000)
+ }
+}
+
+module.exports = AppStateController
+
diff --git a/app/scripts/controllers/balance.js b/app/scripts/controllers/balance.js
index 465751e61..b227d5d0a 100644
--- a/app/scripts/controllers/balance.js
+++ b/app/scripts/controllers/balance.js
@@ -68,7 +68,7 @@ class BalanceController {
_registerUpdates () {
const update = this.updateBalance.bind(this)
- this.txController.on('tx:status-update', (txId, status) => {
+ this.txController.on('tx:status-update', (_, status) => {
switch (status) {
case 'submitted':
case 'confirmed':
diff --git a/app/scripts/controllers/blacklist.js b/app/scripts/controllers/blacklist.js
deleted file mode 100644
index e55b09d03..000000000
--- a/app/scripts/controllers/blacklist.js
+++ /dev/null
@@ -1,136 +0,0 @@
-const ObservableStore = require('obs-store')
-const extend = require('xtend')
-const PhishingDetector = require('eth-phishing-detect/src/detector')
-const log = require('loglevel')
-
-// compute phishing lists
-const PHISHING_DETECTION_CONFIG = require('eth-phishing-detect/src/config.json')
-// every four minutes
-const POLLING_INTERVAL = 4 * 60 * 1000
-
-class BlacklistController {
-
- /**
- * Responsible for polling for and storing an up to date 'eth-phishing-detect' config.json file, while
- * exposing a method that can check whether a given url is a phishing attempt. The 'eth-phishing-detect'
- * config.json file contains a fuzzylist, whitelist and blacklist.
- *
- *
- * @typedef {Object} BlacklistController
- * @param {object} opts Overrides the defaults for the initial state of this.store
- * @property {object} store The the store of the current phishing config
- * @property {object} store.phishing Contains fuzzylist, whitelist and blacklist arrays. @see
- * {@link https://github.com/MetaMask/eth-phishing-detect/blob/master/src/config.json}
- * @property {object} _phishingDetector The PhishingDetector instantiated by passing store.phishing to
- * PhishingDetector.
- * @property {object} _phishingUpdateIntervalRef Id of the interval created to periodically update the blacklist
- *
- */
- constructor (opts = {}) {
- const initState = extend({
- phishing: PHISHING_DETECTION_CONFIG,
- whitelist: [],
- }, opts.initState)
- this.store = new ObservableStore(initState)
- // phishing detector
- this._phishingDetector = null
- this._setupPhishingDetector(initState.phishing)
- // polling references
- this._phishingUpdateIntervalRef = null
- }
-
- /**
- * Adds the given hostname to the runtime whitelist
- * @param {string} hostname the hostname to whitelist
- */
- whitelistDomain (hostname) {
- if (!hostname) {
- return
- }
-
- const { whitelist } = this.store.getState()
- this.store.updateState({
- whitelist: [...new Set([hostname, ...whitelist])],
- })
- }
-
- /**
- * Given a url, returns the result of checking if that url is in the store.phishing blacklist
- *
- * @param {string} hostname The hostname portion of a url; the one that will be checked against the white and
- * blacklists of store.phishing
- * @returns {boolean} Whether or not the passed hostname is on our phishing blacklist
- *
- */
- checkForPhishing (hostname) {
- if (!hostname) return false
-
- const { whitelist } = this.store.getState()
- if (whitelist.some((e) => e === hostname)) {
- return false
- }
-
- const { result } = this._phishingDetector.check(hostname)
- return result
- }
-
- /**
- * Queries `https://api.infura.io/v2/blacklist` for an updated blacklist config. This is passed to this._phishingDetector
- * to update our phishing detector instance, and is updated in the store. The new phishing config is returned
- *
- *
- * @returns {Promise<object>} Promises the updated blacklist config for the phishingDetector
- *
- */
- async updatePhishingList () {
- // make request
- let response
- try {
- response = await fetch('https://api.infura.io/v2/blacklist')
- } catch (err) {
- log.error(new Error(`BlacklistController - failed to fetch blacklist:\n${err.stack}`))
- return
- }
- // parse response
- let rawResponse
- let phishing
- try {
- const rawResponse = await response.text()
- phishing = JSON.parse(rawResponse)
- } catch (err) {
- log.error(new Error(`BlacklistController - failed to parse blacklist:\n${rawResponse}`))
- return
- }
- // update current blacklist
- this.store.updateState({ phishing })
- this._setupPhishingDetector(phishing)
- return phishing
- }
-
- /**
- * Initiates the updating of the local blacklist at a set interval. The update is done via this.updatePhishingList().
- * Also, this method store a reference to that interval at this._phishingUpdateIntervalRef
- *
- */
- scheduleUpdates () {
- if (this._phishingUpdateIntervalRef) return
- this.updatePhishingList()
- this._phishingUpdateIntervalRef = setInterval(() => {
- this.updatePhishingList()
- }, POLLING_INTERVAL)
- }
-
- /**
- * Sets this._phishingDetector to a new PhishingDetector instance.
- * @see {@link https://github.com/MetaMask/eth-phishing-detect}
- *
- * @private
- * @param {object} config A config object like that found at {@link https://github.com/MetaMask/eth-phishing-detect/blob/master/src/config.json}
- *
- */
- _setupPhishingDetector (config) {
- this._phishingDetector = new PhishingDetector(config)
- }
-}
-
-module.exports = BlacklistController
diff --git a/app/scripts/controllers/network/createBlockTracker.js b/app/scripts/controllers/network/createBlockTracker.js
deleted file mode 100644
index 6573b18a1..000000000
--- a/app/scripts/controllers/network/createBlockTracker.js
+++ /dev/null
@@ -1,19 +0,0 @@
-const BlockTracker = require('eth-block-tracker')
-
-/**
- * Creates a block tracker that sends platform events on success and failure
- */
-module.exports = function createBlockTracker (args, platform) {
- const blockTracker = new BlockTracker(args)
- blockTracker.on('latest', () => {
- if (platform && platform.sendMessage) {
- platform.sendMessage({ action: 'ethereum-ping-success' })
- }
- })
- blockTracker.on('error', () => {
- if (platform && platform.sendMessage) {
- platform.sendMessage({ action: 'ethereum-ping-error' })
- }
- })
- return blockTracker
-}
diff --git a/app/scripts/controllers/network/createInfuraClient.js b/app/scripts/controllers/network/createInfuraClient.js
index 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..2c68e4378 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
@@ -130,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
}
@@ -190,7 +190,7 @@ module.exports = class NetworkController extends EventEmitter {
_configureInfuraProvider ({ type }) {
log.info('NetworkController - configureInfuraProvider', type)
- const networkClient = createInfuraClient({ network: type, platform: this.platform })
+ const networkClient = createInfuraClient({ network: type })
this._setNetworkClient(networkClient)
// setup networkConfig
var settings = {
@@ -201,13 +201,13 @@ module.exports = class NetworkController extends EventEmitter {
_configureLocalhostProvider () {
log.info('NetworkController - configureLocalhostProvider')
- const networkClient = createLocalhostClient({ platform: this.platform })
+ const networkClient = createLocalhostClient()
this._setNetworkClient(networkClient)
}
_configureStandardProvider ({ rpcUrl, chainId, ticker, nickname }) {
log.info('NetworkController - configureStandardProvider', rpcUrl)
- const networkClient = createJsonRpcClient({ rpcUrl, platform: this.platform })
+ const networkClient = createJsonRpcClient({ rpcUrl })
// hack to add a 'rpc' network with chainId
networks.networkList['rpc'] = {
chainId: chainId,
diff --git a/app/scripts/controllers/preferences.js b/app/scripts/controllers/preferences.js
index 737411890..acf952bb1 100644
--- a/app/scripts/controllers/preferences.js
+++ b/app/scripts/controllers/preferences.js
@@ -117,6 +117,14 @@ class PreferencesController {
return metaMetricsId
}
+ getMetaMetricsId () {
+ return this.store.getState().metaMetricsId
+ }
+
+ getParticipateInMetaMetrics () {
+ return this.store.getState().participateInMetaMetrics
+ }
+
setMetaMetricsSendCount (val) {
this.store.updateState({ metaMetricsSendCount: val })
}
@@ -331,7 +339,7 @@ class PreferencesController {
}
removeSuggestedTokens () {
- return new Promise((resolve, reject) => {
+ return new Promise((resolve) => {
this.store.updateState({ suggestedTokens: {} })
resolve({})
})
@@ -388,7 +396,7 @@ class PreferencesController {
const newEntry = { address, symbol, decimals }
const tokens = this.store.getState().tokens
const assetImages = this.getAssetImages()
- const previousEntry = tokens.find((token, index) => {
+ const previousEntry = tokens.find((token) => {
return token.address === address
})
const previousIndex = tokens.indexOf(previousEntry)
@@ -453,7 +461,7 @@ class PreferencesController {
*
*/
setCurrentAccountTab (currentAccountTab) {
- return new Promise((resolve, reject) => {
+ return new Promise((resolve) => {
this.store.updateState({ currentAccountTab })
resolve()
})
@@ -480,8 +488,8 @@ class PreferencesController {
rpcList[index] = updatedRpc
this.store.updateState({ frequentRpcListDetail: rpcList })
} else {
- const { rpcUrl, chainId, ticker, nickname } = newRpcDetails
- return this.addToFrequentRpcList(rpcUrl, chainId, ticker, nickname)
+ const { rpcUrl, chainId, ticker, nickname, rpcPrefs = {} } = newRpcDetails
+ return this.addToFrequentRpcList(rpcUrl, chainId, ticker, nickname, rpcPrefs)
}
return Promise.resolve(rpcList)
}
@@ -495,22 +503,22 @@ class PreferencesController {
* @returns {Promise<array>} Promise resolving to updated frequentRpcList.
*
*/
- addToFrequentRpcList (url, chainId, ticker = 'ETH', nickname = '') {
- const rpcList = this.getFrequentRpcListDetail()
- const index = rpcList.findIndex((element) => { return element.rpcUrl === url })
- if (index !== -1) {
- rpcList.splice(index, 1)
- }
- if (url !== 'http://localhost:8545') {
- let checkedChainId
- if (!!chainId && !Number.isNaN(parseInt(chainId))) {
- checkedChainId = chainId
+ addToFrequentRpcList (url, chainId, ticker = 'ETH', nickname = '', rpcPrefs = {}) {
+ const rpcList = this.getFrequentRpcListDetail()
+ const index = rpcList.findIndex((element) => { return element.rpcUrl === url })
+ if (index !== -1) {
+ rpcList.splice(index, 1)
}
- rpcList.push({ rpcUrl: url, chainId: checkedChainId, ticker, nickname })
+ if (url !== 'http://localhost:8545') {
+ let checkedChainId
+ if (!!chainId && !Number.isNaN(parseInt(chainId))) {
+ checkedChainId = chainId
+ }
+ rpcList.push({ rpcUrl: url, chainId: checkedChainId, ticker, nickname, rpcPrefs })
+ }
+ this.store.updateState({ frequentRpcListDetail: rpcList })
+ return Promise.resolve(rpcList)
}
- this.store.updateState({ frequentRpcListDetail: rpcList })
- return Promise.resolve(rpcList)
- }
/**
* Removes custom RPC url from state.
diff --git a/app/scripts/controllers/provider-approval.js b/app/scripts/controllers/provider-approval.js
index 2c9182b52..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/controllers/shapeshift.js b/app/scripts/controllers/shapeshift.js
deleted file mode 100644
index b2a1462c2..000000000
--- a/app/scripts/controllers/shapeshift.js
+++ /dev/null
@@ -1,180 +0,0 @@
-const ObservableStore = require('obs-store')
-const extend = require('xtend')
-const log = require('loglevel')
-
-// every three seconds when an incomplete tx is waiting
-const POLLING_INTERVAL = 3000
-
-class ShapeshiftController {
-
- /**
- * Controller responsible for managing the list of shapeshift transactions. On construction, it initiates a poll
- * that queries a shapeshift.io API for updates to any pending shapeshift transactions
- *
- * @typedef {Object} ShapeshiftController
- * @param {object} opts Overrides the defaults for the initial state of this.store
- * @property {array} opts.initState initializes the the state of the ShapeshiftController. Can contain an
- * shapeShiftTxList array.
- * @property {array} shapeShiftTxList An array of ShapeShiftTx objects
- *
- */
- constructor (opts = {}) {
- const initState = extend({
- shapeShiftTxList: [],
- }, opts.initState)
- this.store = new ObservableStore(initState)
- this.pollForUpdates()
- }
-
- /**
- * Represents, and contains data about, a single shapeshift transaction.
- * @typedef {Object} ShapeShiftTx
- * @property {string} depositAddress - An address at which to send a crypto deposit, so that eth can be sent to the
- * user's Metamask account
- * @property {string} depositType - An abbreviation of the type of crypto currency to be deposited.
- * @property {string} key - The 'shapeshift' key differentiates this from other types of txs in Metamask
- * @property {number} time - The time at which the tx was created
- * @property {object} response - Initiated as an empty object, which will be replaced by a Response object. @see {@link
- * https://developer.mozilla.org/en-US/docs/Web/API/Response}
- */
-
- //
- // PUBLIC METHODS
- //
-
- /**
- * A getter for the shapeShiftTxList property
- *
- * @returns {array<ShapeShiftTx>}
- *
- */
- getShapeShiftTxList () {
- const shapeShiftTxList = this.store.getState().shapeShiftTxList
- return shapeShiftTxList
- }
-
- /**
- * A getter for all ShapeShiftTx in the shapeShiftTxList that have not successfully completed a deposit.
- *
- * @returns {array<ShapeShiftTx>} Only includes ShapeShiftTx which has a response property with a status !== complete
- *
- */
- getPendingTxs () {
- const txs = this.getShapeShiftTxList()
- const pending = txs.filter(tx => tx.response && tx.response.status !== 'complete')
- return pending
- }
-
- /**
- * A poll that exists as long as there are pending transactions. Each call attempts to update the data of any
- * pendingTxs, and then calls itself again. If there are no pending txs, the recursive call is not made and
- * the polling stops.
- *
- * this.updateTx is used to attempt the update to the pendingTxs in the ShapeShiftTxList, and that updated data
- * is saved with saveTx.
- *
- */
- pollForUpdates () {
- const pendingTxs = this.getPendingTxs()
-
- if (pendingTxs.length === 0) {
- return
- }
-
- Promise.all(pendingTxs.map((tx) => {
- return this.updateTx(tx)
- }))
- .then((results) => {
- results.forEach(tx => this.saveTx(tx))
- this.timeout = setTimeout(this.pollForUpdates.bind(this), POLLING_INTERVAL)
- })
- }
-
- /**
- * Attempts to update a ShapeShiftTx with data from a shapeshift.io API. Both the response and time properties
- * can be updated. The response property is updated with every call, but the time property is only updated when
- * the response status updates to 'complete'. This will occur once the user makes a deposit as the ShapeShiftTx
- * depositAddress
- *
- * @param {ShapeShiftTx} tx The tx to update
- *
- */
- async updateTx (tx) {
- try {
- const url = `https://shapeshift.io/txStat/${tx.depositAddress}`
- const response = await fetch(url)
- const json = await response.json()
- tx.response = json
- if (tx.response.status === 'complete') {
- tx.time = new Date().getTime()
- }
- return tx
- } catch (err) {
- log.warn(err)
- }
- }
-
- /**
- * Saves an updated to a ShapeShiftTx in the shapeShiftTxList. If the passed ShapeShiftTx is not in the
- * shapeShiftTxList, nothing happens.
- *
- * @param {ShapeShiftTx} tx The updated tx to save, if it exists in the current shapeShiftTxList
- *
- */
- saveTx (tx) {
- const { shapeShiftTxList } = this.store.getState()
- const index = shapeShiftTxList.indexOf(tx)
- if (index !== -1) {
- shapeShiftTxList[index] = tx
- this.store.updateState({ shapeShiftTxList })
- }
- }
-
- /**
- * Removes a ShapeShiftTx from the shapeShiftTxList
- *
- * @param {ShapeShiftTx} tx The tx to remove
- *
- */
- removeShapeShiftTx (tx) {
- const { shapeShiftTxList } = this.store.getState()
- const index = shapeShiftTxList.indexOf(index)
- if (index !== -1) {
- shapeShiftTxList.splice(index, 1)
- }
- this.updateState({ shapeShiftTxList })
- }
-
- /**
- * Creates a new ShapeShiftTx, adds it to the shapeShiftTxList, and initiates a new poll for updates of pending txs
- *
- * @param {string} depositAddress - An address at which to send a crypto deposit, so that eth can be sent to the
- * user's Metamask account
- * @param {string} depositType - An abbreviation of the type of crypto currency to be deposited.
- *
- */
- createShapeShiftTx (depositAddress, depositType) {
- const state = this.store.getState()
- let { shapeShiftTxList } = state
-
- var shapeShiftTx = {
- depositAddress,
- depositType,
- key: 'shapeshift',
- time: new Date().getTime(),
- response: {},
- }
-
- if (!shapeShiftTxList) {
- shapeShiftTxList = [shapeShiftTx]
- } else {
- shapeShiftTxList.push(shapeShiftTx)
- }
-
- this.store.updateState({ shapeShiftTxList })
- this.pollForUpdates()
- }
-
-}
-
-module.exports = ShapeshiftController
diff --git a/app/scripts/controllers/token-rates.js b/app/scripts/controllers/token-rates.js
index 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) {
diff --git a/app/scripts/controllers/transactions/index.js b/app/scripts/controllers/transactions/index.js
index 2ce736beb..79dba7833 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, getCodeResponse } = await this._determineTransactionCategory(txParams)
let txMeta = this.txStateManager.generateTxMeta({
txParams: normalizedTxParams,
type: TRANSACTION_TYPE_STANDARD,
+ transactionCategory,
})
this.addTx(txMeta)
this.emit('newUnapprovedTx', txMeta)
@@ -191,7 +204,7 @@ class TransactionController extends EventEmitter {
// check whether recipient account is blacklisted
recipientBlacklistChecker.checkAccount(txMeta.metamaskNetworkId, normalizedTxParams.to)
// add default tx params
- txMeta = await this.addTxGasDefaults(txMeta)
+ txMeta = await this.addTxGasDefaults(txMeta, getCodeResponse)
} catch (error) {
log.warn(error)
txMeta.loadingDefaults = false
@@ -211,7 +224,7 @@ class TransactionController extends EventEmitter {
@param txMeta {Object} - the txMeta object
@returns {Promise<object>} resolves with txMeta
*/
- async addTxGasDefaults (txMeta) {
+ async addTxGasDefaults (txMeta, getCodeResponse) {
const txParams = txMeta.txParams
// ensure value
txParams.value = txParams.value ? ethUtil.addHexPrefix(txParams.value) : '0x0'
@@ -222,7 +235,7 @@ class TransactionController extends EventEmitter {
}
txParams.gasPrice = ethUtil.addHexPrefix(gasPrice.toString(16))
// set gasLimit
- return await this.txGasUtil.analyzeGasUsage(txMeta)
+ return await this.txGasUtil.analyzeGasUsage(txMeta, getCodeResponse)
}
/**
@@ -556,6 +569,43 @@ class TransactionController extends EventEmitter {
}
/**
+ Returns a "type" for a transaction out of the following list: simpleSend, tokenTransfer, tokenApprove,
+ contractDeployment, contractMethodCall
+ */
+ async _determineTransactionCategory (txParams) {
+ const { data, to } = txParams
+ const { name } = data && abiDecoder.decodeMethod(data) || {}
+ const tokenMethodName = [
+ TOKEN_METHOD_APPROVE,
+ TOKEN_METHOD_TRANSFER,
+ TOKEN_METHOD_TRANSFER_FROM,
+ ].find(tokenMethodName => tokenMethodName === name && name.toLowerCase())
+
+ let result
+ let code
+ if (!txParams.data) {
+ result = SEND_ETHER_ACTION_KEY
+ } else if (tokenMethodName) {
+ result = tokenMethodName
+ } else if (!to) {
+ result = DEPLOY_CONTRACT_ACTION_KEY
+ } else {
+ try {
+ code = await this.query.getCode(to)
+ } catch (e) {
+ code = null
+ log.warn(e)
+ }
+ // For an address with no code, geth will return '0x', and ganache-core v2.2.1 will return '0x0'
+ const codeIsEmpty = !code || code === '0x' || code === '0x0'
+
+ result = codeIsEmpty ? SEND_ETHER_ACTION_KEY : CONTRACT_INTERACTION_KEY
+ }
+
+ return { transactionCategory: result, getCodeResponse: code }
+ }
+
+ /**
Sets other txMeta statuses to dropped if the txMeta that has been confirmed has other transactions
in the list have the same nonce
diff --git a/app/scripts/controllers/transactions/tx-gas-utils.js b/app/scripts/controllers/transactions/tx-gas-utils.js
index 765551167..287fb6f44 100644
--- a/app/scripts/controllers/transactions/tx-gas-utils.js
+++ b/app/scripts/controllers/transactions/tx-gas-utils.js
@@ -4,7 +4,9 @@ const {
BnMultiplyByFraction,
bnToHex,
} = require('../../lib/util')
+const log = require('loglevel')
const { addHexPrefix } = require('ethereumjs-util')
+const { SEND_ETHER_ACTION_KEY } = require('../../../../ui/app/helpers/constants/transactions.js')
const SIMPLE_GAS_COST = '0x5208' // Hex for 21000, cost of a simple send.
import { TRANSACTION_NO_CONTRACT_ERROR_KEY } from '../../../../ui/app/helpers/constants/error-keys'
@@ -26,12 +28,13 @@ class TxGasUtil {
@param txMeta {Object} - the txMeta object
@returns {object} the txMeta object with the gas written to the txParams
*/
- async analyzeGasUsage (txMeta) {
+ async analyzeGasUsage (txMeta, getCodeResponse) {
const block = await this.query.getBlockByNumber('latest', false)
let estimatedGasHex
try {
- estimatedGasHex = await this.estimateTxGas(txMeta, block.gasLimit)
+ estimatedGasHex = await this.estimateTxGas(txMeta, block.gasLimit, getCodeResponse)
} catch (err) {
+ log.warn(err)
txMeta.simulationFails = {
reason: err.message,
errorKey: err.errorKey,
@@ -54,7 +57,7 @@ class TxGasUtil {
@param blockGasLimitHex {string} - hex string of the block's gas limit
@returns {string} the estimated gas limit as a hex string
*/
- async estimateTxGas (txMeta, blockGasLimitHex) {
+ async estimateTxGas (txMeta, blockGasLimitHex, getCodeResponse) {
const txParams = txMeta.txParams
// check if gasLimit is already specified
@@ -70,11 +73,10 @@ class TxGasUtil {
// see if we can set the gas based on the recipient
if (hasRecipient) {
- const code = await this.query.getCode(recipient)
// For an address with no code, geth will return '0x', and ganache-core v2.2.1 will return '0x0'
- const codeIsEmpty = !code || code === '0x' || code === '0x0'
+ const categorizedAsSimple = txMeta.transactionCategory === SEND_ETHER_ACTION_KEY
- if (codeIsEmpty) {
+ if (categorizedAsSimple) {
// if there's data in the params, but there's no contract code, it's not a valid transaction
if (txParams.data) {
const err = new Error('TxGasUtil - Trying to call a function on a non-contract address')
@@ -82,7 +84,7 @@ class TxGasUtil {
err.errorKey = TRANSACTION_NO_CONTRACT_ERROR_KEY
// set the response on the error so that we can see in logs what the actual response was
- err.getCodeResponse = code
+ err.getCodeResponse = getCodeResponse
throw err
}
diff --git a/app/scripts/controllers/transactions/tx-state-manager.js b/app/scripts/controllers/transactions/tx-state-manager.js
index 420191d9c..1a2cb5dee 100644
--- a/app/scripts/controllers/transactions/tx-state-manager.js
+++ b/app/scripts/controllers/transactions/tx-state-manager.js
@@ -126,10 +126,10 @@ class TransactionStateManager extends EventEmitter {
@returns {object} the txMeta
*/
addTx (txMeta) {
- this.once(`${txMeta.id}:signed`, function (txId) {
+ this.once(`${txMeta.id}:signed`, function () {
this.removeAllListeners(`${txMeta.id}:rejected`)
})
- this.once(`${txMeta.id}:rejected`, function (txId) {
+ this.once(`${txMeta.id}:rejected`, function () {
this.removeAllListeners(`${txMeta.id}:signed`)
})
// initialize history
diff --git a/app/scripts/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<boolean>} - Promise resolving to true if this domain has been previously approved
+ * @returns {Promise<boolean>} - 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<boolean>} - 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/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/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/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 0506e3116..55ca96ad4 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')
@@ -23,10 +25,9 @@ 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')
-const BlacklistController = require('./controllers/blacklist')
const CachedBalancesController = require('./controllers/cached-balances')
const RecentBlocksController = require('./controllers/recent-blocks')
const MessageManager = require('./lib/message-manager')
@@ -53,7 +54,12 @@ 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,
+ ShapeShiftController,
+ PhishingController,
+} = require('gaba')
+const backEndMetaMetricsEvent = require('./lib/backend-metametrics')
module.exports = class MetamaskController extends EventEmitter {
@@ -86,7 +92,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({
@@ -96,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,
@@ -109,8 +121,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()
@@ -190,10 +201,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',
+ },
+ })
+ }
}
})
@@ -210,38 +237,40 @@ 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()
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({
+ AppStateController: this.appStateController.store,
TransactionController: this.txController.store,
KeyringController: this.keyringController.store,
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,
})
this.memStore = new ComposableObservableStore(null, {
+ AppStateController: this.appStateController.store,
NetworkController: this.networkController.store,
AccountTracker: this.accountTracker.store,
TxController: this.txController.memStore,
@@ -256,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,
})
@@ -305,22 +334,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
}
@@ -430,6 +469,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),
@@ -460,9 +502,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),
}
}
@@ -1190,9 +1233,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
@@ -1205,7 +1247,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()
@@ -1215,7 +1257,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
@@ -1270,7 +1312,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
@@ -1279,8 +1321,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)
}
/**
@@ -1353,7 +1396,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
@@ -1373,6 +1416,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))
@@ -1401,12 +1449,18 @@ 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)
}
@@ -1414,6 +1468,38 @@ module.exports = class MetamaskController extends EventEmitter {
}
/**
+ * 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) {
+ 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
* @return {Promise<void>}
@@ -1545,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
@@ -1558,9 +1644,9 @@ module.exports = class MetamaskController extends EventEmitter {
* @returns {Promise<String>} - 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
}
@@ -1573,15 +1659,15 @@ module.exports = class MetamaskController extends EventEmitter {
* @param {string} nickname - Optional nickname of the selected network.
* @returns {Promise<String>} - 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
}
@@ -1706,18 +1792,17 @@ 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)
}
/**
* Locks MetaMask
*/
setLocked () {
- this.providerApprovalController.setLocked()
return this.keyringController.setLocked()
}
}
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),
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()
diff --git a/development/backGroundConnectionModifiers.js b/development/backGroundConnectionModifiers.js
index aee68854b..cf1a723d0 100644
--- a/development/backGroundConnectionModifiers.js
+++ b/development/backGroundConnectionModifiers.js
@@ -1,20 +1,20 @@
module.exports = {
'confirm sig requests': {
- signMessage: (msgData, cb) => {
+ signMessage: (_, cb) => {
const stateUpdate = {
unapprovedMsgs: {},
unapprovedMsgCount: 0,
}
return cb(null, stateUpdate)
},
- signPersonalMessage: (msgData, cb) => {
+ signPersonalMessage: (_, cb) => {
const stateUpdate = {
unapprovedPersonalMsgs: {},
unapprovedPersonalMsgCount: 0,
}
return cb(null, stateUpdate)
},
- signTypedMessage: (msgData, cb) => {
+ signTypedMessage: (_, cb) => {
const stateUpdate = {
unapprovedTypedMessages: {},
unapprovedTypedMessagesCount: 0,
diff --git a/development/states/conf-tx.json b/development/states/conf-tx.json
index d47b26fd4..7b278f331 100644
--- a/development/states/conf-tx.json
+++ b/development/states/conf-tx.json
@@ -192,7 +192,8 @@
"type": "testnet"
},
"shapeShiftTxList": [],
- "lostAccounts": []
+ "lostAccounts": [],
+ "frequentRpcListDetail": []
},
"appState": {
"menuOpen": false,
diff --git a/development/states/confirm-new-ui.json b/development/states/confirm-new-ui.json
index 4310ed5b7..c9340fc8f 100644
--- a/development/states/confirm-new-ui.json
+++ b/development/states/confirm-new-ui.json
@@ -133,7 +133,9 @@
"preferences": {
"useNativeCurrencyAsPrimaryCurrency": true,
"showFiatInTestnets": true
- }
+ },
+ "completedUiMigration": true,
+ "frequentRpcListDetail": []
},
"appState": {
"menuOpen": false,
diff --git a/development/states/confirm-sig-requests.json b/development/states/confirm-sig-requests.json
index aa3e8dfdf..d531b2ef7 100644
--- a/development/states/confirm-sig-requests.json
+++ b/development/states/confirm-sig-requests.json
@@ -156,7 +156,9 @@
"currentLocale": "en",
"preferences": {
"useNativeCurrencyAsPrimaryCurrency": true
- }
+ },
+ "completedUiMigration": true,
+ "frequentRpcListDetail": []
},
"appState": {
"menuOpen": false,
diff --git a/development/states/currency-localization.json b/development/states/currency-localization.json
index 8288b3020..a9a37ecd0 100644
--- a/development/states/currency-localization.json
+++ b/development/states/currency-localization.json
@@ -115,7 +115,9 @@
"preferences": {
"useNativeCurrencyAsPrimaryCurrency": true,
"showFiatInTestnets": true
- }
+ },
+ "completedUiMigration": true,
+ "frequentRpcListDetail": []
},
"appState": {
"menuOpen": false,
diff --git a/development/states/send-edit.json b/development/states/send-edit.json
index fda7d1a31..7c7e8f097 100644
--- a/development/states/send-edit.json
+++ b/development/states/send-edit.json
@@ -137,7 +137,9 @@
"preferences": {
"useNativeCurrencyAsPrimaryCurrency": true,
"showFiatInTestnets": true
- }
+ },
+ "completedUiMigration": true,
+ "frequentRpcListDetail": []
},
"appState": {
"menuOpen": false,
diff --git a/development/states/send-new-ui.json b/development/states/send-new-ui.json
index b8a3ff128..75982f318 100644
--- a/development/states/send-new-ui.json
+++ b/development/states/send-new-ui.json
@@ -116,7 +116,9 @@
"preferences": {
"useNativeCurrencyAsPrimaryCurrency": true,
"showFiatInTestnets": true
- }
+ },
+ "completedUiMigration": true,
+ "frequentRpcListDetail": []
},
"appState": {
"menuOpen": false,
diff --git a/development/states/send.json b/development/states/send.json
index 8ae385564..c71516edc 100644
--- a/development/states/send.json
+++ b/development/states/send.json
@@ -87,7 +87,8 @@
"type": "testnet"
},
"shapeShiftTxList": [],
- "lostAccounts": []
+ "lostAccounts": [],
+ "frequentRpcListDetail": []
},
"appState": {
"menuOpen": false,
diff --git a/development/states/tx-list-items.json b/development/states/tx-list-items.json
index d4e3f3860..4190ee149 100644
--- a/development/states/tx-list-items.json
+++ b/development/states/tx-list-items.json
@@ -1058,7 +1058,9 @@
"currentLocale": "en",
"preferences": {
"useNativeCurrencyAsPrimaryCurrency": true
- }
+ },
+ "completedUiMigration": true,
+ "frequentRpcListDetail": []
},
"appState": {
"menuOpen": false,
diff --git a/gentests.js b/gentests.js
index 9c591e98c..a84c2079d 100644
--- a/gentests.js
+++ b/gentests.js
@@ -48,11 +48,11 @@ async function start (fileRegEx, testGenerator) {
}
*/
-async function startContainer (fileRegEx, testGenerator) {
+async function startContainer (fileRegEx) {
const fileNames = await getAllFileNames('./ui/app')
const sFiles = fileNames.filter(name => name.match(fileRegEx))
- async.each(sFiles, async (sFile, cb) => {
+ async.each(sFiles, async (sFile) => {
console.log(`sFile`, sFile)
const [, sRootPath, sPath] = sFile.match(/^(.+\/)([^/]+)$/)
@@ -91,7 +91,7 @@ async function startContainer (fileRegEx, testGenerator) {
const proxyquireObject = ('{\n ' + result
.match(/import\s{[\s\S]+?}\sfrom\s.+/g)
.map(s => s.replace(/\n/g, ''))
- .map((s, i) => {
+ .map((s) => {
const proxyKeys = s.match(/{.+}/)[0].match(/\w+/g)
return '\'' + s.match(/'(.+)'/)[1] + '\': { ' + (proxyKeys.length > 1
? '\n ' + proxyKeys.join(': () => {},\n ') + ': () => {},\n '
diff --git a/gulpfile.js b/gulpfile.js
index caddb620a..35c6331e8 100644
--- a/gulpfile.js
+++ b/gulpfile.js
@@ -315,7 +315,7 @@ createTasksForBuildJsExtension({ buildJsFiles, taskPrefix: 'dev:test-extension:j
createTasksForBuildJsExtension({ buildJsFiles, taskPrefix: 'build:extension:js' })
createTasksForBuildJsExtension({ buildJsFiles, taskPrefix: 'build:test:extension:js', testing: 'true' })
-function createTasksForBuildJsUIDeps ({ dependenciesToBundle, filename }) {
+function createTasksForBuildJsUIDeps ({ filename }) {
const destinations = browserPlatforms.map(platform => `./dist/${platform}`)
diff --git a/package-lock.json b/package-lock.json
index 902dbe004..8844b21a5 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1746,6 +1746,16 @@
}
}
},
+ "@types/invariant": {
+ "version": "2.2.29",
+ "resolved": "https://registry.npmjs.org/@types/invariant/-/invariant-2.2.29.tgz",
+ "integrity": "sha512-lRVw09gOvgviOfeUrKc/pmTiRZ7g7oDOU6OAutyuSHpm1/o2RaBQvRhgK8QEdu+FFuw/wnWb29A/iuxv9i8OpQ=="
+ },
+ "@types/lodash": {
+ "version": "4.14.124",
+ "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.124.tgz",
+ "integrity": "sha512-6bKEUVbHJ8z34jisA7lseJZD2g31SIvee3cGX2KEZCS4XXWNbjPZpmO1/2rGNR9BhGtaYr6iYXPl1EzRrDAFTA=="
+ },
"@types/node": {
"version": "8.5.5",
"resolved": "https://registry.npmjs.org/@types/node/-/node-8.5.5.tgz",
@@ -1768,6 +1778,14 @@
"@types/react": "*"
}
},
+ "@types/redux": {
+ "version": "3.6.0",
+ "resolved": "https://registry.npmjs.org/@types/redux/-/redux-3.6.0.tgz",
+ "integrity": "sha1-8evh5UEVGAcuT9/KXHbhbnTBOZo=",
+ "requires": {
+ "redux": "*"
+ }
+ },
"@yarnpkg/lockfile": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@yarnpkg/lockfile/-/lockfile-1.1.0.tgz",
@@ -8281,6 +8299,55 @@
}
}
},
+ "disposables": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/disposables/-/disposables-1.0.2.tgz",
+ "integrity": "sha1-NsamdEdfVaLWkTVnpgFETkh7S24="
+ },
+ "dnd-core": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/dnd-core/-/dnd-core-3.0.2.tgz",
+ "integrity": "sha1-6UdXdiBTHH7jelGM1d3hfQ798PM=",
+ "requires": {
+ "@types/invariant": "^2.2.29",
+ "@types/lodash": "^4.14.107",
+ "@types/node": "^8.10.11",
+ "@types/redux": "^3.6.0",
+ "asap": "^2.0.6",
+ "invariant": "^2.0.0",
+ "lodash": "^4.2.0",
+ "redux": "^4.0.0"
+ },
+ "dependencies": {
+ "@types/node": {
+ "version": "8.10.48",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-8.10.48.tgz",
+ "integrity": "sha512-c35YEBTkL4rzXY2ucpSKy+UYHjUBIIkuJbWYbsGIrKLEWU5dgJMmLkkIb3qeC3O3Tpb1ZQCwecscvJTDjDjkRw=="
+ },
+ "loose-envify": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
+ "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",
+ "requires": {
+ "js-tokens": "^3.0.0 || ^4.0.0"
+ }
+ },
+ "redux": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/redux/-/redux-4.0.1.tgz",
+ "integrity": "sha512-R7bAtSkk7nY6O/OYMVR9RiBI+XghjF9rlbl5806HJbQph0LJVHZrU5oaO4q70eUKiqMRqm4y07KLTlMZ2BlVmg==",
+ "requires": {
+ "loose-envify": "^1.4.0",
+ "symbol-observable": "^1.2.0"
+ }
+ },
+ "symbol-observable": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-1.2.0.tgz",
+ "integrity": "sha512-e900nM8RRtGhlV36KGEU9k65K3mPb1WV70OdjfxlG2EAuM1noi/E/BaW/uMhL7bPEssK8QV57vN3esixjUvcXQ=="
+ }
+ }
+ },
"dnode": {
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/dnode/-/dnode-1.2.2.tgz",
@@ -9759,8 +9826,8 @@
}
},
"eth-contract-metadata": {
- "version": "github:MetaMask/eth-contract-metadata#41a14e8004bdd37eaba5af5f2bb1fc4f4ff7063f",
- "from": "github:MetaMask/eth-contract-metadata#41a14e8004bdd37eaba5af5f2bb1fc4f4ff7063f"
+ "version": "github:MetaMask/eth-contract-metadata#dc68506221859bc90792bc5e0279a6835f2484d8",
+ "from": "github:MetaMask/eth-contract-metadata#dc68506221859bc90792bc5e0279a6835f2484d8"
},
"eth-ens-namehash": {
"version": "2.0.8",
@@ -9796,12 +9863,12 @@
"resolved": "https://registry.npmjs.org/eth-sig-util/-/eth-sig-util-1.4.2.tgz",
"integrity": "sha1-jZWCAsftuq6Dlwf7pvCf8ydgYhA=",
"requires": {
- "ethereumjs-abi": "git+https://github.com/ethereumjs/ethereumjs-abi.git#d84a96796079c8595a0c78accd1e7709f2277215",
+ "ethereumjs-abi": "git+https://github.com/ethereumjs/ethereumjs-abi.git#8431eab7b3384e65e8126a4602520b78031666fb",
"ethereumjs-util": "^5.1.1"
},
"dependencies": {
"ethereumjs-abi": {
- "version": "git+https://github.com/ethereumjs/ethereumjs-abi.git#d84a96796079c8595a0c78accd1e7709f2277215",
+ "version": "git+https://github.com/ethereumjs/ethereumjs-abi.git#8431eab7b3384e65e8126a4602520b78031666fb",
"from": "git+https://github.com/ethereumjs/ethereumjs-abi.git",
"requires": {
"bn.js": "^4.11.8",
@@ -9859,9 +9926,9 @@
}
},
"eth-json-rpc-filters": {
- "version": "3.0.1",
- "resolved": "https://registry.npmjs.org/eth-json-rpc-filters/-/eth-json-rpc-filters-3.0.1.tgz",
- "integrity": "sha512-F/UbtD47UnZDFILYP5GJLklYQ7witEI9TdCLgw0r4iag8ZLzz5h4Q+9odg2ASVZKkm8E50mrb7PaYCK0thVxfw==",
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/eth-json-rpc-filters/-/eth-json-rpc-filters-3.0.3.tgz",
+ "integrity": "sha512-rX1EbEmRexMfbzntEUemevRM5qfpjschS/dsSqHYXyWnfAGVOegdSxLbLumiKpRBPMMTnQv6B9l6d/lGneXcaw==",
"requires": {
"await-semaphore": "^0.1.3",
"eth-json-rpc-middleware": "^2.6.0",
@@ -9910,50 +9977,32 @@
"resolved": "https://registry.npmjs.org/eth-sig-util/-/eth-sig-util-1.4.2.tgz",
"integrity": "sha1-jZWCAsftuq6Dlwf7pvCf8ydgYhA=",
"requires": {
- "ethereumjs-abi": "git+https://github.com/ethereumjs/ethereumjs-abi.git#d84a96796079c8595a0c78accd1e7709f2277215",
+ "ethereumjs-abi": "git+https://github.com/ethereumjs/ethereumjs-abi.git#8431eab7b3384e65e8126a4602520b78031666fb",
"ethereumjs-util": "^5.1.1"
- },
- "dependencies": {
- "ethereumjs-abi": {
- "version": "git+https://github.com/ethereumjs/ethereumjs-abi.git#d84a96796079c8595a0c78accd1e7709f2277215",
- "from": "git+https://github.com/ethereumjs/ethereumjs-abi.git",
- "requires": {
- "bn.js": "^4.11.8",
- "ethereumjs-util": "^6.0.0"
- },
- "dependencies": {
- "ethereumjs-util": {
- "version": "6.1.0",
- "resolved": "https://registry.npmjs.org/ethereumjs-util/-/ethereumjs-util-6.1.0.tgz",
- "integrity": "sha512-URESKMFbDeJxnAxPppnk2fN6Y3BIatn9fwn76Lm8bQlt+s52TpG8dN9M66MLPuRAiAOIqL3dfwqWJf0sd0fL0Q==",
- "requires": {
- "bn.js": "^4.11.0",
- "create-hash": "^1.1.2",
- "ethjs-util": "0.1.6",
- "keccak": "^1.0.2",
- "rlp": "^2.0.0",
- "safe-buffer": "^5.1.1",
- "secp256k1": "^3.0.1"
- }
- }
- }
- },
- "ethjs-util": {
- "version": "0.1.6",
- "resolved": "https://registry.npmjs.org/ethjs-util/-/ethjs-util-0.1.6.tgz",
- "integrity": "sha512-CUnVOQq7gSpDHZVVrQW8ExxUETWrnrvXYvYz55wOU8Uj4VCgw56XC2B/fVqQN+f7gmrnRHSLVnFAwsCuNwji8w==",
- "requires": {
- "is-hex-prefixed": "1.0.0",
- "strip-hex-prefix": "1.0.0"
- }
- }
}
},
"ethereumjs-abi": {
- "version": "git+https://github.com/ethereumjs/ethereumjs-abi.git#d84a96796079c8595a0c78accd1e7709f2277215",
+ "version": "git+https://github.com/ethereumjs/ethereumjs-abi.git#8431eab7b3384e65e8126a4602520b78031666fb",
"from": "git+https://github.com/ethereumjs/ethereumjs-abi.git",
"requires": {
- "bn.js": "^4.11.8"
+ "bn.js": "^4.11.8",
+ "ethereumjs-util": "^6.0.0"
+ },
+ "dependencies": {
+ "ethereumjs-util": {
+ "version": "6.1.0",
+ "resolved": "https://registry.npmjs.org/ethereumjs-util/-/ethereumjs-util-6.1.0.tgz",
+ "integrity": "sha512-URESKMFbDeJxnAxPppnk2fN6Y3BIatn9fwn76Lm8bQlt+s52TpG8dN9M66MLPuRAiAOIqL3dfwqWJf0sd0fL0Q==",
+ "requires": {
+ "bn.js": "^4.11.0",
+ "create-hash": "^1.1.2",
+ "ethjs-util": "0.1.6",
+ "keccak": "^1.0.2",
+ "rlp": "^2.0.0",
+ "safe-buffer": "^5.1.1",
+ "secp256k1": "^3.0.1"
+ }
+ }
}
},
"ethereumjs-util": {
@@ -9989,6 +10038,15 @@
"promise-to-callback": "^1.0.0"
}
},
+ "ethjs-util": {
+ "version": "0.1.6",
+ "resolved": "https://registry.npmjs.org/ethjs-util/-/ethjs-util-0.1.6.tgz",
+ "integrity": "sha512-CUnVOQq7gSpDHZVVrQW8ExxUETWrnrvXYvYz55wOU8Uj4VCgw56XC2B/fVqQN+f7gmrnRHSLVnFAwsCuNwji8w==",
+ "requires": {
+ "is-hex-prefixed": "1.0.0",
+ "strip-hex-prefix": "1.0.0"
+ }
+ },
"json-rpc-engine": {
"version": "3.8.0",
"resolved": "https://registry.npmjs.org/json-rpc-engine/-/json-rpc-engine-3.8.0.tgz",
@@ -10113,12 +10171,12 @@
"integrity": "sha1-jZWCAsftuq6Dlwf7pvCf8ydgYhA=",
"dev": true,
"requires": {
- "ethereumjs-abi": "git+https://github.com/ethereumjs/ethereumjs-abi.git#d84a96796079c8595a0c78accd1e7709f2277215",
+ "ethereumjs-abi": "git+https://github.com/ethereumjs/ethereumjs-abi.git#8431eab7b3384e65e8126a4602520b78031666fb",
"ethereumjs-util": "^5.1.1"
},
"dependencies": {
"ethereumjs-abi": {
- "version": "git+https://github.com/ethereumjs/ethereumjs-abi.git#d84a96796079c8595a0c78accd1e7709f2277215",
+ "version": "git+https://github.com/ethereumjs/ethereumjs-abi.git#8431eab7b3384e65e8126a4602520b78031666fb",
"from": "git+https://github.com/ethereumjs/ethereumjs-abi.git",
"dev": true,
"requires": {
@@ -10249,16 +10307,41 @@
"resolved": "https://registry.npmjs.org/eth-sig-util/-/eth-sig-util-1.4.2.tgz",
"integrity": "sha1-jZWCAsftuq6Dlwf7pvCf8ydgYhA=",
"requires": {
- "ethereumjs-abi": "git+https://github.com/ethereumjs/ethereumjs-abi.git#2863c40e0982acfc0b7163f0285d4c56427c7799",
+ "ethereumjs-abi": "git+https://github.com/ethereumjs/ethereumjs-abi.git#8431eab7b3384e65e8126a4602520b78031666fb",
"ethereumjs-util": "^5.1.1"
},
"dependencies": {
"ethereumjs-abi": {
- "version": "git+https://github.com/ethereumjs/ethereumjs-abi.git#2863c40e0982acfc0b7163f0285d4c56427c7799",
- "from": "git+https://github.com/ethereumjs/ethereumjs-abi.git#2863c40e0982acfc0b7163f0285d4c56427c7799",
+ "version": "git+https://github.com/ethereumjs/ethereumjs-abi.git#8431eab7b3384e65e8126a4602520b78031666fb",
+ "from": "git+https://github.com/ethereumjs/ethereumjs-abi.git",
+ "requires": {
+ "bn.js": "^4.11.8",
+ "ethereumjs-util": "^6.0.0"
+ },
+ "dependencies": {
+ "ethereumjs-util": {
+ "version": "6.1.0",
+ "resolved": "https://registry.npmjs.org/ethereumjs-util/-/ethereumjs-util-6.1.0.tgz",
+ "integrity": "sha512-URESKMFbDeJxnAxPppnk2fN6Y3BIatn9fwn76Lm8bQlt+s52TpG8dN9M66MLPuRAiAOIqL3dfwqWJf0sd0fL0Q==",
+ "requires": {
+ "bn.js": "^4.11.0",
+ "create-hash": "^1.1.2",
+ "ethjs-util": "0.1.6",
+ "keccak": "^1.0.2",
+ "rlp": "^2.0.0",
+ "safe-buffer": "^5.1.1",
+ "secp256k1": "^3.0.1"
+ }
+ }
+ }
+ },
+ "ethjs-util": {
+ "version": "0.1.6",
+ "resolved": "https://registry.npmjs.org/ethjs-util/-/ethjs-util-0.1.6.tgz",
+ "integrity": "sha512-CUnVOQq7gSpDHZVVrQW8ExxUETWrnrvXYvYz55wOU8Uj4VCgw56XC2B/fVqQN+f7gmrnRHSLVnFAwsCuNwji8w==",
"requires": {
- "bn.js": "^4.10.0",
- "ethereumjs-util": "^5.0.0"
+ "is-hex-prefixed": "1.0.0",
+ "strip-hex-prefix": "1.0.0"
}
}
}
@@ -10315,8 +10398,34 @@
"resolved": "https://registry.npmjs.org/eth-sig-util/-/eth-sig-util-1.4.2.tgz",
"integrity": "sha1-jZWCAsftuq6Dlwf7pvCf8ydgYhA=",
"requires": {
- "ethereumjs-abi": "git+https://github.com/ethereumjs/ethereumjs-abi.git#572d4bafe08a8a231137e1f9daeb0f8a23f197d2",
+ "ethereumjs-abi": "git+https://github.com/ethereumjs/ethereumjs-abi.git#8431eab7b3384e65e8126a4602520b78031666fb",
"ethereumjs-util": "^5.1.1"
+ },
+ "dependencies": {
+ "ethereumjs-abi": {
+ "version": "git+https://github.com/ethereumjs/ethereumjs-abi.git#8431eab7b3384e65e8126a4602520b78031666fb",
+ "from": "git+https://github.com/ethereumjs/ethereumjs-abi.git",
+ "requires": {
+ "bn.js": "^4.11.8",
+ "ethereumjs-util": "^6.0.0"
+ },
+ "dependencies": {
+ "ethereumjs-util": {
+ "version": "6.1.0",
+ "resolved": "https://registry.npmjs.org/ethereumjs-util/-/ethereumjs-util-6.1.0.tgz",
+ "integrity": "sha512-URESKMFbDeJxnAxPppnk2fN6Y3BIatn9fwn76Lm8bQlt+s52TpG8dN9M66MLPuRAiAOIqL3dfwqWJf0sd0fL0Q==",
+ "requires": {
+ "bn.js": "^4.11.0",
+ "create-hash": "^1.1.2",
+ "ethjs-util": "0.1.6",
+ "keccak": "^1.0.2",
+ "rlp": "^2.0.0",
+ "safe-buffer": "^5.1.1",
+ "secp256k1": "^3.0.1"
+ }
+ }
+ }
+ }
}
},
"ethereum-common": {
@@ -10558,16 +10667,41 @@
"resolved": "https://registry.npmjs.org/eth-sig-util/-/eth-sig-util-1.4.2.tgz",
"integrity": "sha1-jZWCAsftuq6Dlwf7pvCf8ydgYhA=",
"requires": {
- "ethereumjs-abi": "git+https://github.com/ethereumjs/ethereumjs-abi.git#2863c40e0982acfc0b7163f0285d4c56427c7799",
+ "ethereumjs-abi": "git+https://github.com/ethereumjs/ethereumjs-abi.git#8431eab7b3384e65e8126a4602520b78031666fb",
"ethereumjs-util": "^5.1.1"
},
"dependencies": {
"ethereumjs-abi": {
- "version": "git+https://github.com/ethereumjs/ethereumjs-abi.git#2863c40e0982acfc0b7163f0285d4c56427c7799",
- "from": "git+https://github.com/ethereumjs/ethereumjs-abi.git#2863c40e0982acfc0b7163f0285d4c56427c7799",
+ "version": "git+https://github.com/ethereumjs/ethereumjs-abi.git#8431eab7b3384e65e8126a4602520b78031666fb",
+ "from": "git+https://github.com/ethereumjs/ethereumjs-abi.git",
"requires": {
- "bn.js": "^4.10.0",
- "ethereumjs-util": "^5.0.0"
+ "bn.js": "^4.11.8",
+ "ethereumjs-util": "^6.0.0"
+ },
+ "dependencies": {
+ "ethereumjs-util": {
+ "version": "6.1.0",
+ "resolved": "https://registry.npmjs.org/ethereumjs-util/-/ethereumjs-util-6.1.0.tgz",
+ "integrity": "sha512-URESKMFbDeJxnAxPppnk2fN6Y3BIatn9fwn76Lm8bQlt+s52TpG8dN9M66MLPuRAiAOIqL3dfwqWJf0sd0fL0Q==",
+ "requires": {
+ "bn.js": "^4.11.0",
+ "create-hash": "^1.1.2",
+ "ethjs-util": "0.1.6",
+ "keccak": "^1.0.2",
+ "rlp": "^2.0.0",
+ "safe-buffer": "^5.1.1",
+ "secp256k1": "^3.0.1"
+ }
+ }
+ }
+ },
+ "ethjs-util": {
+ "version": "0.1.6",
+ "resolved": "https://registry.npmjs.org/ethjs-util/-/ethjs-util-0.1.6.tgz",
+ "integrity": "sha512-CUnVOQq7gSpDHZVVrQW8ExxUETWrnrvXYvYz55wOU8Uj4VCgw56XC2B/fVqQN+f7gmrnRHSLVnFAwsCuNwji8w==",
+ "requires": {
+ "is-hex-prefixed": "1.0.0",
+ "strip-hex-prefix": "1.0.0"
}
}
}
@@ -10766,8 +10900,34 @@
"resolved": "https://registry.npmjs.org/eth-sig-util/-/eth-sig-util-1.4.2.tgz",
"integrity": "sha1-jZWCAsftuq6Dlwf7pvCf8ydgYhA=",
"requires": {
- "ethereumjs-abi": "git+https://github.com/ethereumjs/ethereumjs-abi.git#572d4bafe08a8a231137e1f9daeb0f8a23f197d2",
+ "ethereumjs-abi": "git+https://github.com/ethereumjs/ethereumjs-abi.git#8431eab7b3384e65e8126a4602520b78031666fb",
"ethereumjs-util": "^5.1.1"
+ },
+ "dependencies": {
+ "ethereumjs-abi": {
+ "version": "git+https://github.com/ethereumjs/ethereumjs-abi.git#8431eab7b3384e65e8126a4602520b78031666fb",
+ "from": "git+https://github.com/ethereumjs/ethereumjs-abi.git",
+ "requires": {
+ "bn.js": "^4.11.8",
+ "ethereumjs-util": "^6.0.0"
+ },
+ "dependencies": {
+ "ethereumjs-util": {
+ "version": "6.1.0",
+ "resolved": "https://registry.npmjs.org/ethereumjs-util/-/ethereumjs-util-6.1.0.tgz",
+ "integrity": "sha512-URESKMFbDeJxnAxPppnk2fN6Y3BIatn9fwn76Lm8bQlt+s52TpG8dN9M66MLPuRAiAOIqL3dfwqWJf0sd0fL0Q==",
+ "requires": {
+ "bn.js": "^4.11.0",
+ "create-hash": "^1.1.2",
+ "ethjs-util": "0.1.6",
+ "keccak": "^1.0.2",
+ "rlp": "^2.0.0",
+ "safe-buffer": "^5.1.1",
+ "secp256k1": "^3.0.1"
+ }
+ }
+ }
+ }
}
},
"ethereum-common": {
@@ -11045,8 +11205,76 @@
"resolved": "https://registry.npmjs.org/eth-sig-util/-/eth-sig-util-1.4.2.tgz",
"integrity": "sha1-jZWCAsftuq6Dlwf7pvCf8ydgYhA=",
"requires": {
- "ethereumjs-abi": "git+https://github.com/ethereumjs/ethereumjs-abi.git#572d4bafe08a8a231137e1f9daeb0f8a23f197d2",
+ "ethereumjs-abi": "git+https://github.com/ethereumjs/ethereumjs-abi.git#8431eab7b3384e65e8126a4602520b78031666fb",
"ethereumjs-util": "^5.1.1"
+ },
+ "dependencies": {
+ "ethereumjs-abi": {
+ "version": "git+https://github.com/ethereumjs/ethereumjs-abi.git#8431eab7b3384e65e8126a4602520b78031666fb",
+ "from": "git+https://github.com/ethereumjs/ethereumjs-abi.git",
+ "requires": {
+ "bn.js": "^4.11.8",
+ "ethereumjs-util": "^6.0.0"
+ },
+ "dependencies": {
+ "ethereumjs-util": {
+ "version": "6.1.0",
+ "resolved": "https://registry.npmjs.org/ethereumjs-util/-/ethereumjs-util-6.1.0.tgz",
+ "integrity": "sha512-URESKMFbDeJxnAxPppnk2fN6Y3BIatn9fwn76Lm8bQlt+s52TpG8dN9M66MLPuRAiAOIqL3dfwqWJf0sd0fL0Q==",
+ "requires": {
+ "bn.js": "^4.11.0",
+ "create-hash": "^1.1.2",
+ "ethjs-util": "0.1.6",
+ "keccak": "^1.0.2",
+ "rlp": "^2.0.0",
+ "safe-buffer": "^5.1.1",
+ "secp256k1": "^3.0.1"
+ }
+ }
+ }
+ },
+ "ethjs-util": {
+ "version": "0.1.6",
+ "resolved": "https://registry.npmjs.org/ethjs-util/-/ethjs-util-0.1.6.tgz",
+ "integrity": "sha512-CUnVOQq7gSpDHZVVrQW8ExxUETWrnrvXYvYz55wOU8Uj4VCgw56XC2B/fVqQN+f7gmrnRHSLVnFAwsCuNwji8w==",
+ "requires": {
+ "is-hex-prefixed": "1.0.0",
+ "strip-hex-prefix": "1.0.0"
+ }
+ }
+ }
+ },
+ "ethereumjs-abi": {
+ "version": "git+https://github.com/ethereumjs/ethereumjs-abi.git#572d4bafe08a8a231137e1f9daeb0f8a23f197d2",
+ "from": "git+https://github.com/ethereumjs/ethereumjs-abi.git#572d4bafe08a8a231137e1f9daeb0f8a23f197d2",
+ "requires": {
+ "bn.js": "^4.11.8",
+ "ethereumjs-util": "^6.0.0"
+ },
+ "dependencies": {
+ "ethereumjs-util": {
+ "version": "6.1.0",
+ "resolved": "https://registry.npmjs.org/ethereumjs-util/-/ethereumjs-util-6.1.0.tgz",
+ "integrity": "sha512-URESKMFbDeJxnAxPppnk2fN6Y3BIatn9fwn76Lm8bQlt+s52TpG8dN9M66MLPuRAiAOIqL3dfwqWJf0sd0fL0Q==",
+ "requires": {
+ "bn.js": "^4.11.0",
+ "create-hash": "^1.1.2",
+ "ethjs-util": "0.1.6",
+ "keccak": "^1.0.2",
+ "rlp": "^2.0.0",
+ "safe-buffer": "^5.1.1",
+ "secp256k1": "^3.0.1"
+ }
+ },
+ "ethjs-util": {
+ "version": "0.1.6",
+ "resolved": "https://registry.npmjs.org/ethjs-util/-/ethjs-util-0.1.6.tgz",
+ "integrity": "sha512-CUnVOQq7gSpDHZVVrQW8ExxUETWrnrvXYvYz55wOU8Uj4VCgw56XC2B/fVqQN+f7gmrnRHSLVnFAwsCuNwji8w==",
+ "requires": {
+ "is-hex-prefixed": "1.0.0",
+ "strip-hex-prefix": "1.0.0"
+ }
+ }
}
},
"ethereumjs-util": {
@@ -13098,15 +13326,16 @@
"integrity": "sha1-8ESOgGmFW/Kj5oPNwdMg5+KgfvQ="
},
"gaba": {
- "version": "1.0.0-beta.65",
- "resolved": "https://registry.npmjs.org/gaba/-/gaba-1.0.0-beta.65.tgz",
- "integrity": "sha512-pX9hMd4RR5AXe7bwIamQEXLJe26fNvjOf7PjkHGKlRjKzBYmxZ03Y/Pa9nklNlG2Shc9sSgB6GXZpYlXNlJRIg==",
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/gaba/-/gaba-1.0.1.tgz",
+ "integrity": "sha512-67Zoaq6wnaBASIXGfu2L+jzx8m+l1tfn6FAEIZI/pMvn/ymk4V9raeqz73QQKq1fF4WcRy2H1Ru1r45J1tDQoQ==",
"dev": true,
"requires": {
"await-semaphore": "^0.1.3",
"eth-contract-metadata": "github:MetaMask/eth-contract-metadata#faa4f56fb17b3ae8579df68708be59d617732f31",
"eth-json-rpc-infura": "^3.1.2",
"eth-keyring-controller": "^4.0.0",
+ "eth-method-registry": "1.1.0",
"eth-phishing-detect": "^1.1.13",
"eth-query": "^2.1.2",
"eth-sig-util": "^2.1.0",
@@ -13183,12 +13412,12 @@
"integrity": "sha1-jZWCAsftuq6Dlwf7pvCf8ydgYhA=",
"dev": true,
"requires": {
- "ethereumjs-abi": "git+https://github.com/ethereumjs/ethereumjs-abi.git#572d4bafe08a8a231137e1f9daeb0f8a23f197d2",
+ "ethereumjs-abi": "git+https://github.com/ethereumjs/ethereumjs-abi.git#8431eab7b3384e65e8126a4602520b78031666fb",
"ethereumjs-util": "^5.1.1"
}
},
"ethereumjs-abi": {
- "version": "git+https://github.com/ethereumjs/ethereumjs-abi.git#572d4bafe08a8a231137e1f9daeb0f8a23f197d2",
+ "version": "git+https://github.com/ethereumjs/ethereumjs-abi.git#8431eab7b3384e65e8126a4602520b78031666fb",
"from": "git+https://github.com/ethereumjs/ethereumjs-abi.git",
"dev": true,
"requires": {
@@ -13225,6 +13454,15 @@
}
}
},
+ "eth-method-registry": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/eth-method-registry/-/eth-method-registry-1.1.0.tgz",
+ "integrity": "sha512-jGbbGYd19XJCtoGFtUD2qJYWefKCCbFcu7F/AQ5sJXvqTIVAHnFn3paaV2zhN5t7iyKYp1qxc+ugOky+72xcbg==",
+ "dev": true,
+ "requires": {
+ "ethjs": "^0.3.0"
+ }
+ },
"eth-phishing-detect": {
"version": "1.1.13",
"resolved": "https://registry.npmjs.org/eth-phishing-detect/-/eth-phishing-detect-1.1.13.tgz",
@@ -13277,6 +13515,119 @@
"secp256k1": "^3.0.1"
}
},
+ "ethjs": {
+ "version": "0.3.9",
+ "resolved": "https://registry.npmjs.org/ethjs/-/ethjs-0.3.9.tgz",
+ "integrity": "sha512-gOQzA3tDUjoLpNONSOALJ/rUFtHi5tXl2mholHasF1cvXhoddqi06yU4OJFJu9AGd6n9v9ywzHlYeIKg1t1hdw==",
+ "dev": true,
+ "requires": {
+ "bn.js": "4.11.6",
+ "ethjs-abi": "0.2.1",
+ "ethjs-contract": "0.2.2",
+ "ethjs-filter": "0.1.8",
+ "ethjs-provider-http": "0.1.6",
+ "ethjs-query": "0.3.7",
+ "ethjs-unit": "0.1.6",
+ "ethjs-util": "0.1.3",
+ "js-sha3": "0.5.5",
+ "number-to-bn": "1.7.0"
+ },
+ "dependencies": {
+ "bn.js": {
+ "version": "4.11.6",
+ "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.6.tgz",
+ "integrity": "sha1-UzRK2xRhehP26N0s4okF0cC6MhU=",
+ "dev": true
+ },
+ "ethjs-query": {
+ "version": "0.3.7",
+ "resolved": "https://registry.npmjs.org/ethjs-query/-/ethjs-query-0.3.7.tgz",
+ "integrity": "sha512-TZnKUwfkWjy0SowFdPLtmsytCorHi0i4vvkQn7Jg8rZt33cRzKhuzOwKr/G3vdigCc+ePXOhUGMcJSAPlOG44A==",
+ "dev": true,
+ "requires": {
+ "ethjs-format": "0.2.7",
+ "ethjs-rpc": "0.2.0",
+ "promise-to-callback": "^1.0.0"
+ }
+ },
+ "ethjs-util": {
+ "version": "0.1.3",
+ "resolved": "https://registry.npmjs.org/ethjs-util/-/ethjs-util-0.1.3.tgz",
+ "integrity": "sha1-39XqSkANxeQhqInK9H4IGtp4u1U=",
+ "dev": true,
+ "requires": {
+ "is-hex-prefixed": "1.0.0",
+ "strip-hex-prefix": "1.0.0"
+ }
+ }
+ }
+ },
+ "ethjs-abi": {
+ "version": "0.2.1",
+ "resolved": "https://registry.npmjs.org/ethjs-abi/-/ethjs-abi-0.2.1.tgz",
+ "integrity": "sha1-4KepOn6BFjqUR3utVu3lJKtt5TM=",
+ "dev": true,
+ "requires": {
+ "bn.js": "4.11.6",
+ "js-sha3": "0.5.5",
+ "number-to-bn": "1.7.0"
+ },
+ "dependencies": {
+ "bn.js": {
+ "version": "4.11.6",
+ "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.6.tgz",
+ "integrity": "sha1-UzRK2xRhehP26N0s4okF0cC6MhU=",
+ "dev": true
+ }
+ }
+ },
+ "ethjs-contract": {
+ "version": "0.2.2",
+ "resolved": "https://registry.npmjs.org/ethjs-contract/-/ethjs-contract-0.2.2.tgz",
+ "integrity": "sha512-xxPqEjsULQ/QNWuvX6Ako0PGs5RxALA8N/H3+boLvnaXDFZVGpD7H63H1gBCRTZyYqCldPpVlVHuw/rD45vazw==",
+ "dev": true,
+ "requires": {
+ "ethjs-abi": "0.2.0",
+ "ethjs-filter": "0.1.8",
+ "ethjs-util": "0.1.3",
+ "js-sha3": "0.5.5"
+ },
+ "dependencies": {
+ "bn.js": {
+ "version": "4.11.6",
+ "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.6.tgz",
+ "integrity": "sha1-UzRK2xRhehP26N0s4okF0cC6MhU=",
+ "dev": true
+ },
+ "ethjs-abi": {
+ "version": "0.2.0",
+ "resolved": "https://registry.npmjs.org/ethjs-abi/-/ethjs-abi-0.2.0.tgz",
+ "integrity": "sha1-0+LCIQEVIPxJm3FoIDbBT8wvWyU=",
+ "dev": true,
+ "requires": {
+ "bn.js": "4.11.6",
+ "js-sha3": "0.5.5",
+ "number-to-bn": "1.7.0"
+ }
+ },
+ "ethjs-util": {
+ "version": "0.1.3",
+ "resolved": "https://registry.npmjs.org/ethjs-util/-/ethjs-util-0.1.3.tgz",
+ "integrity": "sha1-39XqSkANxeQhqInK9H4IGtp4u1U=",
+ "dev": true,
+ "requires": {
+ "is-hex-prefixed": "1.0.0",
+ "strip-hex-prefix": "1.0.0"
+ }
+ }
+ }
+ },
+ "ethjs-filter": {
+ "version": "0.1.8",
+ "resolved": "https://registry.npmjs.org/ethjs-filter/-/ethjs-filter-0.1.8.tgz",
+ "integrity": "sha512-qTDPskDL2UadHwjvM8A+WG9HwM4/FoSY3p3rMJORkHltYcAuiQZd2otzOYKcL5w2Q3sbAkW/E3yt/FPFL/AVXA==",
+ "dev": true
+ },
"ethjs-query": {
"version": "0.3.8",
"resolved": "https://registry.npmjs.org/ethjs-query/-/ethjs-query-0.3.8.tgz",
@@ -13298,6 +13649,12 @@
"promise-to-callback": "^1.0.0"
}
},
+ "js-sha3": {
+ "version": "0.5.5",
+ "resolved": "https://registry.npmjs.org/js-sha3/-/js-sha3-0.5.5.tgz",
+ "integrity": "sha1-uvDA6MVK1ZA0R9+Wreekobynmko=",
+ "dev": true
+ },
"obs-store": {
"version": "2.4.1",
"resolved": "https://registry.npmjs.org/obs-store/-/obs-store-2.4.1.tgz",
@@ -19504,6 +19861,7 @@
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
"integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
"dev": true,
+ "optional": true,
"requires": {
"ms": "2.0.0"
}
@@ -29849,16 +30207,6 @@
"resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz",
"integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY="
},
- "ping-pong-stream": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/ping-pong-stream/-/ping-pong-stream-1.0.0.tgz",
- "integrity": "sha1-TF6wm6atsCGInawNyr+45XcGhUo=",
- "requires": {
- "end-of-stream": "^1.1.0",
- "readable-stream": "^2.1.5",
- "tape": "^4.6.2"
- }
- },
"pinkie": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz",
@@ -32546,6 +32894,84 @@
}
}
},
+ "react-dnd": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/react-dnd/-/react-dnd-3.0.2.tgz",
+ "integrity": "sha1-sMI9jYKWn1t740y8T4T6H/xcfdw=",
+ "requires": {
+ "disposables": "^1.0.1",
+ "dnd-core": "^3.0.2",
+ "hoist-non-react-statics": "^2.5.0",
+ "invariant": "^2.1.0",
+ "lodash": "^4.2.0",
+ "prop-types": "^15.5.10",
+ "shallowequal": "^1.0.2"
+ },
+ "dependencies": {
+ "hoist-non-react-statics": {
+ "version": "2.5.5",
+ "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-2.5.5.tgz",
+ "integrity": "sha512-rqcy4pJo55FTTLWt+bU8ukscqHeE/e9KWvsOW2b/a3afxQZhwkQdT1rPPCJ0rYXdj4vNcasY8zHTH+jF/qStxw=="
+ },
+ "shallowequal": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/shallowequal/-/shallowequal-1.1.0.tgz",
+ "integrity": "sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ=="
+ }
+ }
+ },
+ "react-dnd-html5-backend": {
+ "version": "7.4.4",
+ "resolved": "https://registry.npmjs.org/react-dnd-html5-backend/-/react-dnd-html5-backend-7.4.4.tgz",
+ "integrity": "sha512-X/lP92ateY0glHan8mU0JzjBuZL6VHv2Gc/H9OBBxaf/ZCN1oC16MLKdesqG4x1f/NWFTNtuG3W4B99r5gPVog==",
+ "requires": {
+ "dnd-core": "^7.4.4"
+ },
+ "dependencies": {
+ "dnd-core": {
+ "version": "7.4.4",
+ "resolved": "https://registry.npmjs.org/dnd-core/-/dnd-core-7.4.4.tgz",
+ "integrity": "sha512-xR8SINDCJG9AmKSjXUMJ1PEl8ih1+xSHH8x4DgBtzScXnEtpCnV1ibDZNV0uyps9VgkXTTbYYzJdF04y0v0e3Q==",
+ "requires": {
+ "asap": "^2.0.6",
+ "invariant": "^2.2.4",
+ "redux": "^4.0.1"
+ }
+ },
+ "invariant": {
+ "version": "2.2.4",
+ "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz",
+ "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==",
+ "requires": {
+ "loose-envify": "^1.0.0"
+ }
+ },
+ "redux": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/redux/-/redux-4.0.1.tgz",
+ "integrity": "sha512-R7bAtSkk7nY6O/OYMVR9RiBI+XghjF9rlbl5806HJbQph0LJVHZrU5oaO4q70eUKiqMRqm4y07KLTlMZ2BlVmg==",
+ "requires": {
+ "loose-envify": "^1.4.0",
+ "symbol-observable": "^1.2.0"
+ },
+ "dependencies": {
+ "loose-envify": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
+ "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",
+ "requires": {
+ "js-tokens": "^3.0.0 || ^4.0.0"
+ }
+ }
+ }
+ },
+ "symbol-observable": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-1.2.0.tgz",
+ "integrity": "sha512-e900nM8RRtGhlV36KGEU9k65K3mPb1WV70OdjfxlG2EAuM1noi/E/BaW/uMhL7bPEssK8QV57vN3esixjUvcXQ=="
+ }
+ }
+ },
"react-docgen": {
"version": "3.0.0-beta9",
"resolved": "https://registry.npmjs.org/react-docgen/-/react-docgen-3.0.0-beta9.tgz",
@@ -32666,6 +33092,11 @@
"react-icon-base": "2.1.0"
}
},
+ "react-idle-timer": {
+ "version": "4.2.5",
+ "resolved": "https://registry.npmjs.org/react-idle-timer/-/react-idle-timer-4.2.5.tgz",
+ "integrity": "sha512-8B/OwjG8E/DTx1fHYKTpZ4cnCbL9+LOc5I9t8SYe8tbEkP14KChiYg0xPIuyRpO33wUZHcgmQl93CVePaDhmRA=="
+ },
"react-input-autosize": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/react-input-autosize/-/react-input-autosize-2.1.2.tgz",
@@ -38915,12 +39346,12 @@
"integrity": "sha1-jZWCAsftuq6Dlwf7pvCf8ydgYhA=",
"dev": true,
"requires": {
- "ethereumjs-abi": "git+https://github.com/ethereumjs/ethereumjs-abi.git#d84a96796079c8595a0c78accd1e7709f2277215",
+ "ethereumjs-abi": "git+https://github.com/ethereumjs/ethereumjs-abi.git#8431eab7b3384e65e8126a4602520b78031666fb",
"ethereumjs-util": "^5.1.1"
}
},
"ethereumjs-abi": {
- "version": "git+https://github.com/ethereumjs/ethereumjs-abi.git#d84a96796079c8595a0c78accd1e7709f2277215",
+ "version": "git+https://github.com/ethereumjs/ethereumjs-abi.git#8431eab7b3384e65e8126a4602520b78031666fb",
"from": "git+https://github.com/ethereumjs/ethereumjs-abi.git",
"dev": true,
"requires": {
diff --git a/package.json b/package.json
index 2a02954d2..0ae402600 100644
--- a/package.json
+++ b/package.json
@@ -10,6 +10,8 @@
"start:test": "gulp dev:test",
"build:test": "gulp build:test",
"test": "npm run test:unit && npm run test:integration && npm run lint",
+ "dapp": "static-server test/e2e/beta/contract-test --port 8080",
+ "dapp-chain": "shell-parallel -s 'npm run ganache:start -- -b 2' -x 'sleep 5 && static-server test/e2e/beta/contract-test --port 8080'",
"watch:test:unit": "nodemon --exec \"npm run test:unit\" ./test ./app ./ui",
"test:unit": "cross-env METAMASK_ENV=test mocha --exit --require test/setup.js --recursive \"test/unit/**/*.js\" \"ui/app/**/*.test.js\"",
"test:single": "cross-env METAMASK_ENV=test mocha --require test/helper.js",
@@ -84,10 +86,10 @@
"ensnare": "^1.0.0",
"eth-bin-to-ops": "^1.0.1",
"eth-block-tracker": "^4.1.0",
- "eth-contract-metadata": "github:MetaMask/eth-contract-metadata#41a14e8004bdd37eaba5af5f2bb1fc4f4ff7063f",
+ "eth-contract-metadata": "github:MetaMask/eth-contract-metadata#dc68506221859bc90792bc5e0279a6835f2484d8",
"eth-ens-namehash": "^2.0.8",
"eth-hd-keyring": "^1.2.2",
- "eth-json-rpc-filters": "^3.0.1",
+ "eth-json-rpc-filters": "^3.0.3",
"eth-json-rpc-infura": "^3.0.0",
"eth-keyring-controller": "^3.3.1",
"eth-ledger-bridge-keyring": "^0.2.0",
@@ -136,7 +138,6 @@
"obs-store": "^3.0.2",
"percentile": "^1.2.0",
"pify": "^3.0.0",
- "ping-pong-stream": "^1.0.0",
"pojo-migrator": "^2.1.0",
"polyfill-crypto.getrandomvalues": "^1.0.0",
"post-message-stream": "^3.0.0",
@@ -150,8 +151,11 @@
"ramda": "^0.24.1",
"react": "^15.6.2",
"react-addons-css-transition-group": "^15.6.0",
+ "react-dnd": "^3.0.2",
+ "react-dnd-html5-backend": "^7.4.4",
"react-dom": "^15.6.2",
"react-hyperscript": "^3.0.0",
+ "react-idle-timer": "^4.2.5",
"react-inspector": "^2.3.0",
"react-markdown": "^3.0.0",
"react-media": "^1.8.0",
@@ -224,7 +228,7 @@
"file-loader": "^1.1.11",
"fs-extra": "^6.0.1",
"fs-promise": "^2.0.3",
- "gaba": "1.0.0-beta.65",
+ "gaba": "^1.0.1",
"ganache-cli": "^6.1.0",
"ganache-core": "^2.5.3",
"geckodriver": "^1.14.1",
diff --git a/test/e2e/beta/contract-test/contract.js b/test/e2e/beta/contract-test/contract.js
index 65fb9377f..e1f886c58 100644
--- a/test/e2e/beta/contract-test/contract.js
+++ b/test/e2e/beta/contract-test/contract.js
@@ -37,8 +37,10 @@ web3.currentProvider.enable().then(() => {
const createToken = document.getElementById('createToken')
const transferTokens = document.getElementById('transferTokens')
const approveTokens = document.getElementById('approveTokens')
+ const transferTokensWithoutGas = document.getElementById('transferTokensWithoutGas')
+ const approveTokensWithoutGas = document.getElementById('approveTokensWithoutGas')
- deployButton.addEventListener('click', async function (event) {
+ deployButton.addEventListener('click', async function () {
document.getElementById('contractStatus').innerHTML = 'Deploying'
var piggybank = await piggybankContract.new(
@@ -55,7 +57,7 @@ web3.currentProvider.enable().then(() => {
document.getElementById('contractStatus').innerHTML = 'Deployed'
- depositButton.addEventListener('click', function (event) {
+ depositButton.addEventListener('click', function () {
document.getElementById('contractStatus').innerHTML = 'Deposit initiated'
contract.deposit({ from: web3.eth.accounts[0], value: '0x3782dace9d900000' }, function (result) {
console.log(result)
@@ -63,7 +65,7 @@ web3.currentProvider.enable().then(() => {
})
})
- withdrawButton.addEventListener('click', function (event) {
+ withdrawButton.addEventListener('click', function () {
contract.withdraw('0xde0b6b3a7640000', { from: web3.eth.accounts[0] }, function (result) {
console.log(result)
document.getElementById('contractStatus').innerHTML = 'Withdrawn'
@@ -75,7 +77,7 @@ web3.currentProvider.enable().then(() => {
console.log(piggybank)
})
- sendButton.addEventListener('click', function (event) {
+ sendButton.addEventListener('click', function () {
web3.eth.sendTransaction({
from: web3.eth.accounts[0],
to: '0x2f318C334780961FB129D2a6c30D0763d9a5C970',
@@ -88,7 +90,7 @@ web3.currentProvider.enable().then(() => {
})
- createToken.addEventListener('click', async function (event) {
+ createToken.addEventListener('click', async function () {
var _initialAmount = 100
var _tokenName = 'TST'
var _decimalUnits = 0
@@ -124,7 +126,7 @@ web3.currentProvider.enable().then(() => {
})
})
- approveTokens.addEventListener('click', function (event) {
+ approveTokens.addEventListener('click', function () {
contract.approve('0x2f318C334780961FB129D2a6c30D0763d9a5C970', '7', {
from: web3.eth.accounts[0],
to: contract.address,
@@ -135,6 +137,29 @@ web3.currentProvider.enable().then(() => {
console.log(result)
})
})
+
+ transferTokensWithoutGas.addEventListener('click', function (event) {
+ console.log(`event`, event)
+ contract.transfer('0x2f318C334780961FB129D2a6c30D0763d9a5C970', '7', {
+ from: web3.eth.accounts[0],
+ to: contract.address,
+ data: '0xa9059cbb0000000000000000000000002f318C334780961FB129D2a6c30D0763d9a5C970000000000000000000000000000000000000000000000000000000000000000a',
+ gasPrice: '20000000000',
+ }, function (result) {
+ console.log('result', result)
+ })
+ })
+
+ approveTokensWithoutGas.addEventListener('click', function () {
+ contract.approve('0x2f318C334780961FB129D2a6c30D0763d9a5C970', '7', {
+ from: web3.eth.accounts[0],
+ to: contract.address,
+ data: '0x095ea7b30000000000000000000000002f318C334780961FB129D2a6c30D0763d9a5C9700000000000000000000000000000000000000000000000000000000000000005',
+ gasPrice: '20000000000',
+ }, function (result) {
+ console.log(result)
+ })
+ })
}
})
diff --git a/test/e2e/beta/contract-test/index.html b/test/e2e/beta/contract-test/index.html
index 0d422ef20..6e134dc36 100644
--- a/test/e2e/beta/contract-test/index.html
+++ b/test/e2e/beta/contract-test/index.html
@@ -27,6 +27,8 @@
<button id="createToken">Create Token</button>
<button id="transferTokens">Transfer Tokens</button>
<button id="approveTokens">Approve Tokens</button>
+ <button id="transferTokensWithoutGas">Transfer Tokens Without Gas</button>
+ <button id="approveTokensWithoutGas">Approve Tokens Without Gas</button>
</div>
</div>
diff --git a/test/e2e/beta/metamask-beta-ui.spec.js b/test/e2e/beta/metamask-beta-ui.spec.js
index 05ad84f24..06778ab99 100644
--- a/test/e2e/beta/metamask-beta-ui.spec.js
+++ b/test/e2e/beta/metamask-beta-ui.spec.js
@@ -812,10 +812,31 @@ describe('MetaMask', function () {
await delay(regularDelayMs)
const [gasPriceInput, gasLimitInput] = await findElements(driver, By.css('.advanced-tab__gas-edit-row__input'))
- await gasPriceInput.clear()
+ await gasPriceInput.sendKeys(Key.chord(Key.CONTROL, 'a'))
+ await delay(50)
+
+ await gasPriceInput.sendKeys(Key.BACK_SPACE)
+ await delay(50)
+ await gasPriceInput.sendKeys(Key.BACK_SPACE)
+ await delay(50)
await gasPriceInput.sendKeys('10')
- await gasLimitInput.clear()
+ await delay(50)
+ await gasLimitInput.sendKeys(Key.chord(Key.CONTROL, 'a'))
+ await delay(50)
+ await gasLimitInput.sendKeys(Key.BACK_SPACE)
+ await delay(50)
+ await gasLimitInput.sendKeys(Key.BACK_SPACE)
+ await delay(50)
+ await gasLimitInput.sendKeys(Key.BACK_SPACE)
+ await delay(50)
+ await gasLimitInput.sendKeys(Key.BACK_SPACE)
+ await delay(50)
+ await gasLimitInput.sendKeys(Key.BACK_SPACE)
+ await delay(50)
await gasLimitInput.sendKeys('60001')
+ await delay(50)
+ await gasLimitInput.sendKeys(Key.chord(Key.CONTROL, 'e'))
+ await delay(50)
const save = await findElement(driver, By.xpath(`//button[contains(text(), 'Save')]`))
await save.click()
@@ -1175,7 +1196,7 @@ describe('MetaMask', function () {
const transferTokens = await findElement(driver, By.xpath(`//button[contains(text(), 'Approve Tokens')]`))
await transferTokens.click()
- await closeAllWindowHandlesExcept(driver, extension)
+ await closeAllWindowHandlesExcept(driver, [extension, dapp])
await driver.switchTo().window(extension)
await delay(regularDelayMs)
@@ -1228,21 +1249,31 @@ describe('MetaMask', function () {
await delay(regularDelayMs)
const [gasPriceInput, gasLimitInput] = await findElements(driver, By.css('.advanced-tab__gas-edit-row__input'))
- await gasPriceInput.clear()
- await delay(tinyDelayMs)
+ await gasPriceInput.sendKeys(Key.chord(Key.CONTROL, 'a'))
+ await delay(50)
+
+ await gasPriceInput.sendKeys(Key.BACK_SPACE)
+ await delay(50)
+ await gasPriceInput.sendKeys(Key.BACK_SPACE)
+ await delay(50)
await gasPriceInput.sendKeys('10')
- await delay(tinyDelayMs)
- await gasLimitInput.clear()
- await delay(tinyDelayMs)
+ await delay(50)
await gasLimitInput.sendKeys(Key.chord(Key.CONTROL, 'a'))
- await gasLimitInput.sendKeys('60000')
+ await delay(50)
+ await gasLimitInput.sendKeys(Key.BACK_SPACE)
+ await delay(50)
+ await gasLimitInput.sendKeys(Key.BACK_SPACE)
+ await delay(50)
+ await gasLimitInput.sendKeys(Key.BACK_SPACE)
+ await delay(50)
+ await gasLimitInput.sendKeys(Key.BACK_SPACE)
+ await delay(50)
+ await gasLimitInput.sendKeys(Key.BACK_SPACE)
+ await delay(50)
+ await gasLimitInput.sendKeys('60001')
+ await delay(50)
await gasLimitInput.sendKeys(Key.chord(Key.CONTROL, 'e'))
-
- // Needed for different behaviour of input in different versions of firefox
- const gasLimitInputValue = await gasLimitInput.getAttribute('value')
- if (gasLimitInputValue === '600001') {
- await gasLimitInput.sendKeys(Key.BACK_SPACE)
- }
+ await delay(50)
const save = await findElement(driver, By.css('.page-container__footer-button'))
await save.click()
@@ -1271,6 +1302,105 @@ describe('MetaMask', function () {
})
})
+ describe('Tranfers a custom token from dapp when no gas value is specified', () => {
+ it('transfers an already created token, without specifying gas', async () => {
+ const windowHandles = await driver.getAllWindowHandles()
+ const extension = windowHandles[0]
+ const dapp = await switchToWindowWithTitle(driver, 'E2E Test Dapp', windowHandles)
+ await closeAllWindowHandlesExcept(driver, [extension, dapp])
+ await delay(regularDelayMs)
+
+ await driver.switchTo().window(dapp)
+
+ const transferTokens = await findElement(driver, By.xpath(`//button[contains(text(), 'Transfer Tokens Without Gas')]`))
+ await transferTokens.click()
+
+ await closeAllWindowHandlesExcept(driver, [extension, dapp])
+ await driver.switchTo().window(extension)
+ await delay(regularDelayMs)
+
+ await driver.wait(async () => {
+ const pendingTxes = await findElements(driver, By.css('.transaction-list__pending-transactions .transaction-list-item'))
+ return pendingTxes.length === 1
+ }, 10000)
+
+ const [txListItem] = await findElements(driver, By.css('.transaction-list-item'))
+ const [txListValue] = await findElements(driver, By.css('.transaction-list-item__amount--primary'))
+ await driver.wait(until.elementTextMatches(txListValue, /-7\s*TST/))
+ await txListItem.click()
+ await delay(regularDelayMs)
+ })
+
+ it('submits the transaction', async function () {
+ await delay(regularDelayMs)
+ const confirmButton = await findElement(driver, By.xpath(`//button[contains(text(), 'Confirm')]`))
+ await confirmButton.click()
+ await delay(regularDelayMs)
+ })
+
+ it('finds the transaction in the transactions list', async function () {
+ await driver.wait(async () => {
+ const confirmedTxes = await findElements(driver, By.css('.transaction-list__completed-transactions .transaction-list-item'))
+ return confirmedTxes.length === 4
+ }, 10000)
+
+ const txValues = await findElements(driver, By.css('.transaction-list-item__amount--primary'))
+ await driver.wait(until.elementTextMatches(txValues[0], /-7\s*TST/))
+ const txStatuses = await findElements(driver, By.css('.transaction-list-item__action'))
+ await driver.wait(until.elementTextMatches(txStatuses[0], /Sent Tokens/))
+ })
+ })
+
+ describe('Approves a custom token from dapp when no gas value is specified', () => {
+ it('approves an already created token', async () => {
+ const windowHandles = await driver.getAllWindowHandles()
+ const extension = windowHandles[0]
+ const dapp = await switchToWindowWithTitle(driver, 'E2E Test Dapp', windowHandles)
+ await closeAllWindowHandlesExcept(driver, [extension, dapp])
+ await delay(regularDelayMs)
+
+ await driver.switchTo().window(dapp)
+ await delay(tinyDelayMs)
+
+ const transferTokens = await findElement(driver, By.xpath(`//button[contains(text(), 'Approve Tokens Without Gas')]`))
+ await transferTokens.click()
+
+ await closeAllWindowHandlesExcept(driver, extension)
+ await driver.switchTo().window(extension)
+ await delay(regularDelayMs)
+
+ await driver.wait(async () => {
+ const pendingTxes = await findElements(driver, By.css('.transaction-list__pending-transactions .transaction-list-item'))
+ return pendingTxes.length === 1
+ }, 10000)
+
+ const [txListItem] = await findElements(driver, By.css('.transaction-list-item'))
+ const [txListValue] = await findElements(driver, By.css('.transaction-list-item__amount--primary'))
+ await driver.wait(until.elementTextMatches(txListValue, /-7\s*TST/))
+ await txListItem.click()
+ await delay(regularDelayMs)
+ })
+
+ it('submits the transaction', async function () {
+ await delay(regularDelayMs)
+ const confirmButton = await findElement(driver, By.xpath(`//button[contains(text(), 'Confirm')]`))
+ await confirmButton.click()
+ await delay(regularDelayMs)
+ })
+
+ it('finds the transaction in the transactions list', async function () {
+ await driver.wait(async () => {
+ const confirmedTxes = await findElements(driver, By.css('.transaction-list__completed-transactions .transaction-list-item'))
+ return confirmedTxes.length === 5
+ }, 10000)
+
+ const txValues = await findElements(driver, By.css('.transaction-list-item__amount--primary'))
+ await driver.wait(until.elementTextMatches(txValues[0], /-7\s*TST/))
+ const txStatuses = await findElements(driver, By.css('.transaction-list-item__action'))
+ await driver.wait(until.elementTextMatches(txStatuses[0], /Approve/))
+ })
+ })
+
describe('Hide token', () => {
it('hides the token when clicked', async () => {
const [hideTokenEllipsis] = await findElements(driver, By.css('.token-list-item__ellipsis'))
@@ -1341,11 +1471,14 @@ describe('MetaMask', function () {
await customRpcButton.click()
await delay(regularDelayMs)
- const customRpcInput = await findElement(driver, By.css('input[placeholder="New RPC URL"]'))
+ await findElement(driver, By.css('.settings-page__sub-header-text'))
+
+ const customRpcInputs = await findElements(driver, By.css('input[type="text"]'))
+ const customRpcInput = customRpcInputs[1]
await customRpcInput.clear()
await customRpcInput.sendKeys(customRpcUrl)
- const customRpcSave = await findElement(driver, By.css('.settings-tab__rpc-save-button'))
+ const customRpcSave = await findElement(driver, By.css('.page-container__footer-button'))
await customRpcSave.click()
await delay(largeDelayMs * 2)
})
diff --git a/test/integration/lib/confirm-sig-requests.js b/test/integration/lib/confirm-sig-requests.js
index c3b0dfcff..699527609 100644
--- a/test/integration/lib/confirm-sig-requests.js
+++ b/test/integration/lib/confirm-sig-requests.js
@@ -15,16 +15,25 @@ QUnit.test('successful confirmation of sig requests', (assert) => {
})
})
-async function runConfirmSigRequestsTest (assert, done) {
+global.ethQuery = global.ethQuery || {}
+
+async function runConfirmSigRequestsTest (assert) {
const selectState = await queryAsync($, 'select')
selectState.val('confirm sig requests')
reactTriggerChange(selectState[0])
+ const realFetch = window.fetch.bind(window)
global.fetch = (...args) => {
- if (args[0].match(/chromeextensionmm/)) {
+ if (args[0] === 'https://ethgasstation.info/json/ethgasAPI.json') {
+ return Promise.resolve({ json: () => Promise.resolve(JSON.parse(fetchMockResponses.ethGasBasic)) })
+ } else if (args[0] === 'https://ethgasstation.info/json/predictTable.json') {
+ return Promise.resolve({ json: () => Promise.resolve(JSON.parse(fetchMockResponses.ethGasPredictTable)) })
+ } else if (args[0] === 'https://dev.blockscale.net/api/gasexpress.json') {
+ return Promise.resolve({ json: () => Promise.resolve(JSON.parse(fetchMockResponses.gasExpress)) })
+ } else if (args[0].match(/chromeextensionmm/)) {
return Promise.resolve({ json: () => Promise.resolve(JSON.parse(fetchMockResponses.metametrics)) })
}
- return window.fetch(...args)
+ return realFetch.fetch(...args)
}
const pendingRequestItem = $.find('.transaction-list-item .transaction-list-item__grid')
diff --git a/test/integration/lib/currency-localization.js b/test/integration/lib/currency-localization.js
index cd10efa30..24c3a1a2d 100644
--- a/test/integration/lib/currency-localization.js
+++ b/test/integration/lib/currency-localization.js
@@ -16,16 +16,23 @@ QUnit.test('renders localized currency', (assert) => {
})
})
-async function runCurrencyLocalizationTest (assert, done) {
+async function runCurrencyLocalizationTest (assert) {
console.log('*** start runCurrencyLocalizationTest')
const selectState = await queryAsync($, 'select')
selectState.val('currency localization')
+ const realFetch = window.fetch.bind(window)
global.fetch = (...args) => {
- if (args[0].match(/chromeextensionmm/)) {
+ if (args[0] === 'https://ethgasstation.info/json/ethgasAPI.json') {
+ return Promise.resolve({ json: () => Promise.resolve(JSON.parse(fetchMockResponses.ethGasBasic)) })
+ } else if (args[0] === 'https://ethgasstation.info/json/predictTable.json') {
+ return Promise.resolve({ json: () => Promise.resolve(JSON.parse(fetchMockResponses.ethGasPredictTable)) })
+ } else if (args[0] === 'https://dev.blockscale.net/api/gasexpress.json') {
+ return Promise.resolve({ json: () => Promise.resolve(JSON.parse(fetchMockResponses.gasExpress)) })
+ } else if (args[0].match(/chromeextensionmm/)) {
return Promise.resolve({ json: () => Promise.resolve(JSON.parse(fetchMockResponses.metametrics)) })
}
- return window.fetch(...args)
+ return realFetch.fetch(...args)
}
await timeout(1000)
diff --git a/test/integration/lib/send-new-ui.js b/test/integration/lib/send-new-ui.js
index 6a58611d1..78014feef 100644
--- a/test/integration/lib/send-new-ui.js
+++ b/test/integration/lib/send-new-ui.js
@@ -22,9 +22,10 @@ global.ethQuery = {
global.ethereumProvider = {}
-async function runSendFlowTest (assert, done) {
+async function runSendFlowTest (assert) {
const tempFetch = global.fetch
+ const realFetch = window.fetch.bind(window)
global.fetch = (...args) => {
if (args[0] === 'https://ethgasstation.info/json/ethgasAPI.json') {
return Promise.resolve({ json: () => Promise.resolve(JSON.parse(fetchMockResponses.ethGasBasic)) })
@@ -35,7 +36,7 @@ async function runSendFlowTest (assert, done) {
} else if (args[0].match(/chromeextensionmm/)) {
return Promise.resolve({ json: () => Promise.resolve(JSON.parse(fetchMockResponses.metametrics)) })
}
- return window.fetch(...args)
+ return realFetch.fetch(...args)
}
console.log('*** start runSendFlowTest')
diff --git a/test/integration/lib/tx-list-items.js b/test/integration/lib/tx-list-items.js
index c0056dd22..e4478614e 100644
--- a/test/integration/lib/tx-list-items.js
+++ b/test/integration/lib/tx-list-items.js
@@ -20,17 +20,24 @@ global.ethQuery.getTransactionCount = (_, cb) => {
cb(null, '0x4')
}
-async function runTxListItemsTest (assert, done) {
+async function runTxListItemsTest (assert) {
console.log('*** start runTxListItemsTest')
const selectState = await queryAsync($, 'select')
selectState.val('tx list items')
reactTriggerChange(selectState[0])
+ const realFetch = window.fetch.bind(window)
global.fetch = (...args) => {
- if (args[0].match(/chromeextensionmm/)) {
+ if (args[0] === 'https://ethgasstation.info/json/ethgasAPI.json') {
+ return Promise.resolve({ json: () => Promise.resolve(JSON.parse(fetchMockResponses.ethGasBasic)) })
+ } else if (args[0] === 'https://ethgasstation.info/json/predictTable.json') {
+ return Promise.resolve({ json: () => Promise.resolve(JSON.parse(fetchMockResponses.ethGasPredictTable)) })
+ } else if (args[0] === 'https://dev.blockscale.net/api/gasexpress.json') {
+ return Promise.resolve({ json: () => Promise.resolve(JSON.parse(fetchMockResponses.gasExpress)) })
+ } else if (args[0].match(/chromeextensionmm/)) {
return Promise.resolve({ json: () => Promise.resolve(JSON.parse(fetchMockResponses.metametrics)) })
}
- return window.fetch(...args)
+ return realFetch.fetch(...args)
}
const metamaskLogo = await queryAsync($, '.app-header__logo-container')
diff --git a/test/lib/mock-encryptor.js b/test/lib/mock-encryptor.js
index 852c536c2..23ab2404f 100644
--- a/test/lib/mock-encryptor.js
+++ b/test/lib/mock-encryptor.js
@@ -4,12 +4,12 @@ let cacheVal
module.exports = {
- encrypt (password, dataObj) {
+ encrypt (_, dataObj) {
cacheVal = dataObj
return Promise.resolve(mockHex)
},
- decrypt (password, text) {
+ decrypt () {
return Promise.resolve(cacheVal || {})
},
@@ -21,7 +21,7 @@ module.exports = {
return this.decrypt(key, text)
},
- keyFromPassword (password) {
+ keyFromPassword () {
return Promise.resolve(mockKey)
},
diff --git a/test/lib/util.js b/test/lib/util.js
index 858565bb9..4c5d789d1 100644
--- a/test/lib/util.js
+++ b/test/lib/util.js
@@ -6,7 +6,7 @@ module.exports = {
}
function timeout (time) {
- return new Promise((resolve, reject) => {
+ return new Promise((resolve) => {
setTimeout(resolve, time || 1500)
})
}
diff --git a/test/unit/actions/tx_test.js b/test/unit/actions/tx_test.js
index 8c64d844f..f2f8f1d1c 100644
--- a/test/unit/actions/tx_test.js
+++ b/test/unit/actions/tx_test.js
@@ -33,8 +33,8 @@ describe('tx confirmation screen', function () {
describe('cancelTx', function () {
before(function (done) {
actions._setBackgroundConnection({
- approveTransaction (txId, cb) { cb('An error!') },
- cancelTransaction (txId, cb) { cb() },
+ approveTransaction (_, cb) { cb('An error!') },
+ cancelTransaction (_, cb) { cb() },
clearSeedWordCache (cb) { cb() },
getState (cb) { cb() },
})
diff --git a/test/unit/app/controllers/blacklist-controller-test.js b/test/unit/app/controllers/blacklist-controller-test.js
deleted file mode 100644
index 7a14c02cc..000000000
--- a/test/unit/app/controllers/blacklist-controller-test.js
+++ /dev/null
@@ -1,56 +0,0 @@
-const assert = require('assert')
-const BlacklistController = require('../../../../app/scripts/controllers/blacklist')
-
-describe('blacklist controller', function () {
- let blacklistController
-
- before(() => {
- blacklistController = new BlacklistController()
- })
-
- describe('whitelistDomain', function () {
- it('should add hostname to the runtime whitelist', function () {
- blacklistController.whitelistDomain('foo.com')
- assert.deepEqual(blacklistController.store.getState().whitelist, ['foo.com'])
-
- blacklistController.whitelistDomain('bar.com')
- assert.deepEqual(blacklistController.store.getState().whitelist, ['bar.com', 'foo.com'])
- })
- })
-
- describe('checkForPhishing', function () {
- it('should not flag whitelisted values', function () {
- const result = blacklistController.checkForPhishing('www.metamask.io')
- assert.equal(result, false)
- })
- it('should flag explicit values', function () {
- const result = blacklistController.checkForPhishing('metamask.com')
- assert.equal(result, true)
- })
- it('should flag levenshtein values', function () {
- const result = blacklistController.checkForPhishing('metmask.io')
- assert.equal(result, true)
- })
- it('should not flag not-even-close values', function () {
- const result = blacklistController.checkForPhishing('example.com')
- assert.equal(result, false)
- })
- it('should not flag the ropsten faucet domains', function () {
- const result = blacklistController.checkForPhishing('faucet.metamask.io')
- assert.equal(result, false)
- })
- it('should not flag the mascara domain', function () {
- const result = blacklistController.checkForPhishing('zero.metamask.io')
- assert.equal(result, false)
- })
- it('should not flag the mascara-faucet domain', function () {
- const result = blacklistController.checkForPhishing('zero-faucet.metamask.io')
- assert.equal(result, false)
- })
- it('should not flag whitelisted domain', function () {
- blacklistController.whitelistDomain('metamask.com')
- const result = blacklistController.checkForPhishing('metamask.com')
- assert.equal(result, false)
- })
- })
-})
diff --git a/test/unit/app/controllers/currency-controller-test.js b/test/unit/app/controllers/currency-controller-test.js
index 7c4644d9f..8b6fbb719 100644
--- a/test/unit/app/controllers/currency-controller-test.js
+++ b/test/unit/app/controllers/currency-controller-test.js
@@ -59,7 +59,7 @@ describe('currency-controller', function () {
var promise = new Promise(
- function (resolve, reject) {
+ function (resolve) {
currencyController.setCurrentCurrency('jpy')
currencyController.updateConversionRate().then(function () {
resolve()
diff --git a/test/unit/app/controllers/metamask-controller-test.js b/test/unit/app/controllers/metamask-controller-test.js
index 1ed6a95fb..a56b8adbd 100644
--- a/test/unit/app/controllers/metamask-controller-test.js
+++ b/test/unit/app/controllers/metamask-controller-test.js
@@ -49,7 +49,7 @@ describe('MetaMaskController', function () {
showUnapprovedTx: noop,
showUnconfirmedMessage: noop,
encryptor: {
- encrypt: function (password, object) {
+ encrypt: function (_, object) {
this.object = object
return Promise.resolve('mock-encrypted')
},
@@ -144,7 +144,7 @@ describe('MetaMaskController', function () {
sandbox.stub(metamaskController, 'getBalance')
metamaskController.getBalance.callsFake(() => { return Promise.resolve('0x0') })
- await metamaskController.createNewVaultAndRestore(password, TEST_SEED.slice(0, -1)).catch((e) => null)
+ await metamaskController.createNewVaultAndRestore(password, TEST_SEED.slice(0, -1)).catch(() => null)
await metamaskController.createNewVaultAndRestore(password, TEST_SEED)
assert(metamaskController.keyringController.createNewVaultAndRestore.calledTwice)
@@ -207,7 +207,7 @@ describe('MetaMaskController', function () {
const accounts = {}
const balance = '0x14ced5122ce0a000'
const ethQuery = new EthQuery()
- sinon.stub(ethQuery, 'getBalance').callsFake((account, callback) => {
+ sinon.stub(ethQuery, 'getBalance').callsFake((_, callback) => {
callback(undefined, balance)
})
@@ -295,7 +295,7 @@ describe('MetaMaskController', function () {
it('should add the Trezor Hardware keyring', async function () {
sinon.spy(metamaskController.keyringController, 'addNewKeyring')
- await metamaskController.connectHardware('trezor', 0).catch((e) => null)
+ await metamaskController.connectHardware('trezor', 0).catch(() => null)
const keyrings = await metamaskController.keyringController.getKeyringsByType(
'Trezor Hardware'
)
@@ -305,7 +305,7 @@ describe('MetaMaskController', function () {
it('should add the Ledger Hardware keyring', async function () {
sinon.spy(metamaskController.keyringController, 'addNewKeyring')
- await metamaskController.connectHardware('ledger', 0).catch((e) => null)
+ await metamaskController.connectHardware('ledger', 0).catch(() => null)
const keyrings = await metamaskController.keyringController.getKeyringsByType(
'Ledger Hardware'
)
@@ -325,7 +325,7 @@ describe('MetaMaskController', function () {
})
it('should be locked by default', async function () {
- await metamaskController.connectHardware('trezor', 0).catch((e) => null)
+ await metamaskController.connectHardware('trezor', 0).catch(() => null)
const status = await metamaskController.checkHardwareStatus('trezor')
assert.equal(status, false)
})
@@ -341,7 +341,7 @@ describe('MetaMaskController', function () {
})
it('should wipe all the keyring info', async function () {
- await metamaskController.connectHardware('trezor', 0).catch((e) => null)
+ await metamaskController.connectHardware('trezor', 0).catch(() => null)
await metamaskController.forgetDevice('trezor')
const keyrings = await metamaskController.keyringController.getKeyringsByType(
'Trezor Hardware'
@@ -376,7 +376,7 @@ describe('MetaMaskController', function () {
sinon.spy(metamaskController.preferencesController, 'setAddresses')
sinon.spy(metamaskController.preferencesController, 'setSelectedAddress')
sinon.spy(metamaskController.preferencesController, 'setAccountLabel')
- await metamaskController.connectHardware('trezor', 0, `m/44/0'/0'`).catch((e) => null)
+ await metamaskController.connectHardware('trezor', 0, `m/44/0'/0'`).catch(() => null)
await metamaskController.unlockHardwareWalletAccount(accountToUnlock, 'trezor', `m/44/0'/0'`)
})
@@ -464,7 +464,7 @@ describe('MetaMaskController', function () {
depositAddress = '3EevLFfB4H4XMWQwYCgjLie1qCAGpd2WBc'
depositType = 'ETH'
- shapeShiftTxList = metamaskController.shapeshiftController.store.getState().shapeShiftTxList
+ shapeShiftTxList = metamaskController.shapeshiftController.state.shapeShiftTxList
})
it('creates a shapeshift tx', async function () {
@@ -752,12 +752,11 @@ describe('MetaMaskController', function () {
})
it('sets up phishing stream for untrusted communication ', async () => {
- await metamaskController.blacklistController.updatePhishingList()
- console.log(blacklistJSON.blacklist.includes(phishingUrl))
+ await metamaskController.phishingController.updatePhishingLists()
const { promise, resolve } = deferredPromise()
- streamTest = createThoughStream((chunk, enc, cb) => {
+ streamTest = createThoughStream((chunk, _, cb) => {
if (chunk.name !== 'phishing') return cb()
assert.equal(chunk.data.hostname, phishingUrl)
resolve()
@@ -777,7 +776,7 @@ describe('MetaMaskController', function () {
})
it('sets up controller dnode api for trusted communication', function (done) {
- streamTest = createThoughStream((chunk, enc, cb) => {
+ streamTest = createThoughStream((chunk, _, cb) => {
assert.equal(chunk.name, 'controller')
cb()
done()
diff --git a/test/unit/app/controllers/preferences-controller-test.js b/test/unit/app/controllers/preferences-controller-test.js
index 558597ae7..81b152f3d 100644
--- a/test/unit/app/controllers/preferences-controller-test.js
+++ b/test/unit/app/controllers/preferences-controller-test.js
@@ -527,14 +527,14 @@ describe('preferences controller', function () {
it('should add custom RPC url to state', function () {
preferencesController.addToFrequentRpcList('rpc_url', 1)
preferencesController.addToFrequentRpcList('http://localhost:8545', 1)
- assert.deepEqual(preferencesController.store.getState().frequentRpcListDetail, [{ rpcUrl: 'rpc_url', chainId: 1, ticker: 'ETH', nickname: '' }])
+ assert.deepEqual(preferencesController.store.getState().frequentRpcListDetail, [{ rpcUrl: 'rpc_url', chainId: 1, ticker: 'ETH', nickname: '', rpcPrefs: {} }])
preferencesController.addToFrequentRpcList('rpc_url', 1)
- assert.deepEqual(preferencesController.store.getState().frequentRpcListDetail, [{ rpcUrl: 'rpc_url', chainId: 1, ticker: 'ETH', nickname: '' }])
+ assert.deepEqual(preferencesController.store.getState().frequentRpcListDetail, [{ rpcUrl: 'rpc_url', chainId: 1, ticker: 'ETH', nickname: '', rpcPrefs: {} }])
})
it('should remove custom RPC url from state', function () {
preferencesController.addToFrequentRpcList('rpc_url', 1)
- assert.deepEqual(preferencesController.store.getState().frequentRpcListDetail, [{ rpcUrl: 'rpc_url', chainId: 1, ticker: 'ETH', nickname: '' }])
+ assert.deepEqual(preferencesController.store.getState().frequentRpcListDetail, [{ rpcUrl: 'rpc_url', chainId: 1, ticker: 'ETH', nickname: '', rpcPrefs: {} }])
preferencesController.removeFromFrequentRpcList('other_rpc_url')
preferencesController.removeFromFrequentRpcList('http://localhost:8545')
preferencesController.removeFromFrequentRpcList('rpc_url')
diff --git a/test/unit/app/controllers/transactions/pending-tx-test.js b/test/unit/app/controllers/transactions/pending-tx-test.js
index 2988bf61f..1c5f59f5a 100644
--- a/test/unit/app/controllers/transactions/pending-tx-test.js
+++ b/test/unit/app/controllers/transactions/pending-tx-test.js
@@ -100,7 +100,7 @@ describe('PendingTransactionTracker', function () {
describe('#_checkPendingTx', function () {
it('should emit \'tx:failed\' if the txMeta does not have a hash', function (done) {
- pendingTxTracker.once('tx:failed', (txId, err) => {
+ pendingTxTracker.once('tx:failed', (txId) => {
assert(txId, txMetaNoHash.id, 'should pass txId')
done()
})
@@ -128,7 +128,7 @@ describe('PendingTransactionTracker', function () {
pendingTxTracker.getPendingTransactions = () => txList
pendingTxTracker._checkPendingTx = (tx) => { tx.resolve(tx) }
Promise.all(txList.map((tx) => tx.processed))
- .then((txCompletedList) => done())
+ .then(() => done())
.catch(done)
pendingTxTracker.updatePendingTxs()
@@ -152,7 +152,7 @@ describe('PendingTransactionTracker', function () {
pendingTxTracker.getPendingTransactions = () => txList
pendingTxTracker._resubmitTx = async (tx) => { tx.resolve(tx) }
Promise.all(txList.map((tx) => tx.processed))
- .then((txCompletedList) => done())
+ .then(() => done())
.catch(done)
pendingTxTracker.resubmitPendingTxs(blockNumberStub)
})
@@ -178,7 +178,7 @@ describe('PendingTransactionTracker', function () {
throw new Error(knownErrors.pop())
}
Promise.all(txList.map((tx) => tx.processed))
- .then((txCompletedList) => done())
+ .then(() => done())
.catch(done)
pendingTxTracker.resubmitPendingTxs(blockNumberStub)
@@ -194,9 +194,9 @@ describe('PendingTransactionTracker', function () {
})
pendingTxTracker.getPendingTransactions = () => txList
- pendingTxTracker._resubmitTx = async (tx) => { throw new TypeError('im some real error') }
+ pendingTxTracker._resubmitTx = async () => { throw new TypeError('im some real error') }
Promise.all(txList.map((tx) => tx.processed))
- .then((txCompletedList) => done())
+ .then(() => done())
.catch(done)
pendingTxTracker.resubmitPendingTxs(blockNumberStub)
diff --git a/test/unit/app/controllers/transactions/tx-controller-test.js b/test/unit/app/controllers/transactions/tx-controller-test.js
index 9000cd364..8ff409207 100644
--- a/test/unit/app/controllers/transactions/tx-controller-test.js
+++ b/test/unit/app/controllers/transactions/tx-controller-test.js
@@ -8,6 +8,13 @@ const TransactionController = require('../../../../../app/scripts/controllers/tr
const {
TRANSACTION_TYPE_RETRY,
} = require('../../../../../app/scripts/controllers/transactions/enums')
+const {
+ TOKEN_METHOD_APPROVE,
+ TOKEN_METHOD_TRANSFER,
+ SEND_ETHER_ACTION_KEY,
+ DEPLOY_CONTRACT_ACTION_KEY,
+ CONTRACT_INTERACTION_KEY,
+} = require('../../../../../ui/app/helpers/constants/transactions.js')
const { createTestProviderTools, getTestAccounts } = require('../../../../stub/provider')
const noop = () => true
@@ -537,6 +544,86 @@ describe('Transaction Controller', function () {
})
})
+ describe('#_determineTransactionCategory', function () {
+ it('should return a simple send transactionCategory when to is truthy but data is falsey', async function () {
+ const result = await txController._determineTransactionCategory({
+ to: '0xabc',
+ data: '',
+ })
+ assert.deepEqual(result, { transactionCategory: SEND_ETHER_ACTION_KEY, getCodeResponse: undefined })
+ })
+
+ it('should return a token transfer transactionCategory when data is for the respective method call', async function () {
+ const result = await txController._determineTransactionCategory({
+ to: '0xabc',
+ data: '0xa9059cbb0000000000000000000000002f318C334780961FB129D2a6c30D0763d9a5C970000000000000000000000000000000000000000000000000000000000000000a',
+ })
+ assert.deepEqual(result, { transactionCategory: TOKEN_METHOD_TRANSFER, getCodeResponse: undefined })
+ })
+
+ it('should return a token approve transactionCategory when data is for the respective method call', async function () {
+ const result = await txController._determineTransactionCategory({
+ to: '0xabc',
+ data: '0x095ea7b30000000000000000000000002f318C334780961FB129D2a6c30D0763d9a5C9700000000000000000000000000000000000000000000000000000000000000005',
+ })
+ assert.deepEqual(result, { transactionCategory: TOKEN_METHOD_APPROVE, getCodeResponse: undefined })
+ })
+
+ it('should return a contract deployment transactionCategory when to is falsey and there is data', async function () {
+ const result = await txController._determineTransactionCategory({
+ to: '',
+ data: '0xabd',
+ })
+ assert.deepEqual(result, { transactionCategory: DEPLOY_CONTRACT_ACTION_KEY, getCodeResponse: undefined })
+ })
+
+ it('should return a simple send transactionCategory with a 0x getCodeResponse when there is data and but the to address is not a contract address', async function () {
+ const result = await txController._determineTransactionCategory({
+ to: '0x9e673399f795D01116e9A8B2dD2F156705131ee9',
+ data: '0xabd',
+ })
+ assert.deepEqual(result, { transactionCategory: SEND_ETHER_ACTION_KEY, getCodeResponse: '0x' })
+ })
+
+ it('should return a simple send transactionCategory with a null getCodeResponse when to is truthy and there is data and but getCode returns an error', async function () {
+ const result = await txController._determineTransactionCategory({
+ to: '0xabc',
+ data: '0xabd',
+ })
+ assert.deepEqual(result, { transactionCategory: SEND_ETHER_ACTION_KEY, getCodeResponse: null })
+ })
+
+ it('should return a contract interaction transactionCategory with the correct getCodeResponse when to is truthy and there is data and it is not a token transaction', async function () {
+ const _providerResultStub = {
+ // 1 gwei
+ eth_gasPrice: '0x0de0b6b3a7640000',
+ // by default, all accounts are external accounts (not contracts)
+ eth_getCode: '0xa',
+ }
+ const _provider = createTestProviderTools({ scaffold: _providerResultStub }).provider
+ const _fromAccount = getTestAccounts()[0]
+ const _blockTrackerStub = new EventEmitter()
+ _blockTrackerStub.getCurrentBlock = noop
+ _blockTrackerStub.getLatestBlock = noop
+ const _txController = new TransactionController({
+ provider: _provider,
+ getGasPrice: function () { return '0xee6b2800' },
+ networkStore: new ObservableStore(currentNetworkId),
+ txHistoryLimit: 10,
+ blockTracker: _blockTrackerStub,
+ signTransaction: (ethTx) => new Promise((resolve) => {
+ ethTx.sign(_fromAccount.key)
+ resolve()
+ }),
+ })
+ const result = await _txController._determineTransactionCategory({
+ to: '0x9e673399f795D01116e9A8B2dD2F156705131ee9',
+ data: 'abd',
+ })
+ assert.deepEqual(result, { transactionCategory: CONTRACT_INTERACTION_KEY, getCodeResponse: '0x0a' })
+ })
+ })
+
describe('#getPendingTransactions', function () {
beforeEach(function () {
txController.txStateManager._saveTxList([
diff --git a/test/unit/app/controllers/transactions/tx-gas-util-test.js b/test/unit/app/controllers/transactions/tx-gas-util-test.js
index 31defd6ed..f92d95507 100644
--- a/test/unit/app/controllers/transactions/tx-gas-util-test.js
+++ b/test/unit/app/controllers/transactions/tx-gas-util-test.js
@@ -11,7 +11,7 @@ describe('txUtils', function () {
before(function () {
txUtils = new TxUtils(new Proxy({}, {
- get: (obj, name) => {
+ get: () => {
return () => {}
},
}))
diff --git a/test/unit/app/controllers/transactions/tx-state-history-helper-test.js b/test/unit/app/controllers/transactions/tx-state-history-helper-test.js
index fba0e7fda..328c2ac6f 100644
--- a/test/unit/app/controllers/transactions/tx-state-history-helper-test.js
+++ b/test/unit/app/controllers/transactions/tx-state-history-helper-test.js
@@ -29,7 +29,7 @@ describe('Transaction state history helper', function () {
describe('#migrateFromSnapshotsToDiffs', function () {
it('migrates history to diffs and can recover original values', function () {
- testVault.data.TransactionController.transactions.forEach((tx, index) => {
+ testVault.data.TransactionController.transactions.forEach((tx) => {
const newHistory = txStateHistoryHelper.migrateFromSnapshotsToDiffs(tx.history)
newHistory.forEach((newEntry, index) => {
if (index === 0) {
diff --git a/test/unit/app/controllers/transactions/tx-state-manager-test.js b/test/unit/app/controllers/transactions/tx-state-manager-test.js
index 88bdaa60e..4ccade2aa 100644
--- a/test/unit/app/controllers/transactions/tx-state-manager-test.js
+++ b/test/unit/app/controllers/transactions/tx-state-manager-test.js
@@ -55,7 +55,7 @@ describe('TransactionStateManager', function () {
it('should emit a rejected event to signal the exciton of callback', (done) => {
const tx = { id: 1, status: 'unapproved', metamaskNetworkId: currentNetworkId, txParams: {} }
txStateManager.addTx(tx)
- const noop = function (err, txId) {
+ const noop = function (err) {
if (err) {
console.log('Error: ', err)
}
diff --git a/test/unit/app/edge-encryptor-test.js b/test/unit/app/edge-encryptor-test.js
index 1a6255b36..52817cd09 100644
--- a/test/unit/app/edge-encryptor-test.js
+++ b/test/unit/app/edge-encryptor-test.js
@@ -83,7 +83,7 @@ describe('EdgeEncryptor', function () {
edgeEncryptor.encrypt(password, data)
.then(function (encryptedData) {
edgeEncryptor.decrypt('wrong password', encryptedData)
- .then(function (decryptedData) {
+ .then(function () {
assert.fail('could decrypt with wrong password')
done()
})
diff --git a/test/unit/migrations/migrator-test.js b/test/unit/migrations/migrator-test.js
index a9374dff1..693c5830d 100644
--- a/test/unit/migrations/migrator-test.js
+++ b/test/unit/migrations/migrator-test.js
@@ -61,7 +61,7 @@ describe('Migrator', () => {
const migrator = new Migrator({ migrations: [{ version: 1, migrate: async () => { throw new Error('test') } } ] })
migrator.on('error', () => done())
migrator.migrateData({ meta: {version: 0} })
- .then((migratedData) => {
+ .then(() => {
}).catch(done)
})
diff --git a/test/unit/ui/app/actions.spec.js b/test/unit/ui/app/actions.spec.js
index 86c3f8aff..34dd6a39b 100644
--- a/test/unit/ui/app/actions.spec.js
+++ b/test/unit/ui/app/actions.spec.js
@@ -44,7 +44,7 @@ describe('Actions', () => {
showUnapprovedTx: noop,
showUnconfirmedMessage: noop,
encryptor: {
- encrypt: function (password, object) {
+ encrypt: function (_, object) {
this.object = object
return Promise.resolve('mock-encrypted')
},
@@ -103,7 +103,7 @@ describe('Actions', () => {
submitPasswordSpy = sinon.stub(background, 'submitPassword')
- submitPasswordSpy.callsFake((password, callback) => {
+ submitPasswordSpy.callsFake((_, callback) => {
callback(new Error('error in submitPassword'))
})
@@ -235,7 +235,7 @@ describe('Actions', () => {
createNewVaultAndRestoreSpy = sinon.stub(background, 'createNewVaultAndRestore')
- createNewVaultAndRestoreSpy.callsFake((password, seed, callback) => {
+ createNewVaultAndRestoreSpy.callsFake((_, __, callback) => {
callback(new Error('error'))
})
@@ -279,7 +279,7 @@ describe('Actions', () => {
]
createNewVaultAndKeychainSpy = sinon.stub(background, 'createNewVaultAndKeychain')
- createNewVaultAndKeychainSpy.callsFake((password, callback) => {
+ createNewVaultAndKeychainSpy.callsFake((_, callback) => {
callback(new Error('error'))
})
@@ -342,7 +342,7 @@ describe('Actions', () => {
]
submitPasswordSpy = sinon.stub(background, 'submitPassword')
- submitPasswordSpy.callsFake((password, callback) => {
+ submitPasswordSpy.callsFake((_, callback) => {
callback(new Error('error'))
})
@@ -414,7 +414,7 @@ describe('Actions', () => {
it('displays warning error message when submitPassword in background errors', () => {
submitPasswordSpy = sinon.stub(background, 'submitPassword')
- submitPasswordSpy.callsFake((password, callback) => {
+ submitPasswordSpy.callsFake((_, callback) => {
callback(new Error('error'))
})
@@ -483,7 +483,7 @@ describe('Actions', () => {
{ type: 'DISPLAY_WARNING', value: 'error' },
]
removeAccountSpy = sinon.stub(background, 'removeAccount')
- removeAccountSpy.callsFake((address, callback) => {
+ removeAccountSpy.callsFake((_, callback) => {
callback(new Error('error'))
})
@@ -522,7 +522,7 @@ describe('Actions', () => {
{ type: 'DISPLAY_WARNING', value: 'error' },
]
- addNewKeyringSpy.callsFake((type, opts, callback) => {
+ addNewKeyringSpy.callsFake((_, __, callback) => {
callback(new Error('error'))
})
@@ -611,7 +611,7 @@ describe('Actions', () => {
]
importAccountWithStrategySpy = sinon.stub(background, 'importAccountWithStrategy')
- importAccountWithStrategySpy.callsFake((strategy, args, callback) => {
+ importAccountWithStrategySpy.callsFake((_, __, callback) => {
callback(new Error('error'))
})
@@ -668,7 +668,7 @@ describe('Actions', () => {
{ type: 'HIDE_LOADING_INDICATION' },
{ type: 'DISPLAY_WARNING', value: 'error' },
]
- setCurrentCurrencySpy.callsFake((currencyCode, callback) => {
+ setCurrentCurrencySpy.callsFake((_, callback) => {
callback(new Error('error'))
})
@@ -720,7 +720,7 @@ describe('Actions', () => {
]
signMessageSpy = sinon.stub(background, 'signMessage')
- signMessageSpy.callsFake((msgData, callback) => {
+ signMessageSpy.callsFake((_, callback) => {
callback(new Error('error'))
})
@@ -775,7 +775,7 @@ describe('Actions', () => {
]
signPersonalMessageSpy = sinon.stub(background, 'signPersonalMessage')
- signPersonalMessageSpy.callsFake((msgData, callback) => {
+ signPersonalMessageSpy.callsFake((_, callback) => {
callback(new Error('error'))
})
@@ -812,7 +812,7 @@ describe('Actions', () => {
{ type: 'DISPLAY_WARNING', value: 'error' },
{ type: 'SHOW_CONF_TX_PAGE', transForward: true, id: undefined },
]
- sendTransactionSpy.callsFake((txData, callback) => {
+ sendTransactionSpy.callsFake((_, callback) => {
callback(new Error('error'))
})
@@ -906,7 +906,7 @@ describe('Actions', () => {
{ type: 'DISPLAY_WARNING', value: 'error' },
]
- setSelectedAddressSpy.callsFake((address, callback) => {
+ setSelectedAddressSpy.callsFake((_, callback) => {
callback(new Error('error'))
})
@@ -941,7 +941,7 @@ describe('Actions', () => {
{ type: 'HIDE_LOADING_INDICATION' },
{ type: 'DISPLAY_WARNING', value: 'error' },
]
- setSelectedAddressSpy.callsFake((address, callback) => {
+ setSelectedAddressSpy.callsFake((_, callback) => {
callback(new Error('error'))
})
@@ -980,7 +980,7 @@ describe('Actions', () => {
{ type: 'UPDATE_TOKENS', newTokens: undefined },
]
- addTokenSpy.callsFake((address, symbol, decimals, image, callback) => {
+ addTokenSpy.callsFake((_, __, ___, ____, callback) => {
callback(new Error('error'))
})
@@ -1020,7 +1020,7 @@ describe('Actions', () => {
{ type: 'UPDATE_TOKENS', newTokens: undefined },
]
- removeTokenSpy.callsFake((address, callback) => {
+ removeTokenSpy.callsFake((_, callback) => {
callback(new Error('error'))
})
@@ -1054,7 +1054,7 @@ describe('Actions', () => {
{ type: 'DISPLAY_WARNING', value: 'Had a problem changing networks!' },
]
- setProviderTypeSpy.callsFake((type, callback) => {
+ setProviderTypeSpy.callsFake((_, callback) => {
callback(new Error('error'))
})
@@ -1087,7 +1087,7 @@ describe('Actions', () => {
{ type: 'DISPLAY_WARNING', value: 'Had a problem changing networks!' },
]
- setRpcTargetSpy.callsFake((newRpc, chainId, ticker, nickname, callback) => {
+ setRpcTargetSpy.callsFake((_, __, ___, ____, callback) => {
callback(new Error('error'))
})
@@ -1134,7 +1134,7 @@ describe('Actions', () => {
exportAccountSpy = sinon.spy(background, 'exportAccount')
return store.dispatch(actions.exportAccount(password, '0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc'))
- .then((result) => {
+ .then(() => {
assert(submitPasswordSpy.calledOnce)
assert(exportAccountSpy.calledOnce)
assert.deepEqual(store.getActions(), expectedActions)
@@ -1150,7 +1150,7 @@ describe('Actions', () => {
]
submitPasswordSpy = sinon.stub(background, 'submitPassword')
- submitPasswordSpy.callsFake((password, callback) => {
+ submitPasswordSpy.callsFake((_, callback) => {
callback(new Error('error'))
})
@@ -1169,7 +1169,7 @@ describe('Actions', () => {
]
exportAccountSpy = sinon.stub(background, 'exportAccount')
- exportAccountSpy.callsFake((address, callback) => {
+ exportAccountSpy.callsFake((_, callback) => {
callback(new Error('error'))
})
@@ -1196,7 +1196,6 @@ describe('Actions', () => {
describe('#pairUpdate', () => {
beforeEach(() => {
-
nock('https://shapeshift.io')
.defaultReplyHeaders({ 'access-control-allow-origin': '*' })
.get('/marketinfo/btc_eth')
@@ -1206,10 +1205,6 @@ describe('Actions', () => {
.defaultReplyHeaders({ 'access-control-allow-origin': '*' })
.get('/coins')
.reply(200)
- })
-
- afterEach(() => {
- nock.restore()
})
it('', () => {
@@ -1251,7 +1246,7 @@ describe('Actions', () => {
{ type: 'DISPLAY_WARNING', value: 'error' },
]
- setFeatureFlagSpy.callsFake((feature, activated, callback) => {
+ setFeatureFlagSpy.callsFake((_, __, callback) => {
callback(new Error('error'))
})
@@ -1305,7 +1300,7 @@ describe('Actions', () => {
]
getTransactionCountSpy = sinon.stub(global.ethQuery, 'getTransactionCount')
- getTransactionCountSpy.callsFake((address, callback) => {
+ getTransactionCountSpy.callsFake((_, callback) => {
callback(new Error('error'))
})
@@ -1343,7 +1338,7 @@ describe('Actions', () => {
{ type: 'SET_USE_BLOCKIE', value: undefined },
]
- setUseBlockieSpy.callsFake((val, callback) => {
+ setUseBlockieSpy.callsFake((_, callback) => {
callback(new Error('error'))
})
@@ -1390,7 +1385,7 @@ describe('Actions', () => {
{ type: 'DISPLAY_WARNING', value: 'error' },
]
setCurrentLocaleSpy = sinon.stub(background, 'setCurrentLocale')
- setCurrentLocaleSpy.callsFake((key, callback) => {
+ setCurrentLocaleSpy.callsFake((_, callback) => {
callback(new Error('error'))
})
diff --git a/test/web3/web3.js b/test/web3/web3.js
index 5c2de078d..0f7a4c3cd 100644
--- a/test/web3/web3.js
+++ b/test/web3/web3.js
@@ -12,7 +12,7 @@ web3.currentProvider.enable().then(() => {
Object.keys(methodGroup).forEach(methodKey => {
const methodButton = document.getElementById(methodKey)
- methodButton.addEventListener('click', function (event) {
+ methodButton.addEventListener('click', function () {
window.ethereum.sendAsync({
method: methodKey,
diff --git a/ui/app/components/app/account-panel.js b/ui/app/components/app/account-panel.js
index 79882f34a..e61cb8ad6 100644
--- a/ui/app/components/app/account-panel.js
+++ b/ui/app/components/app/account-panel.js
@@ -69,18 +69,9 @@ AccountPanel.prototype.render = function () {
)
}
-function balanceOrFaucetingIndication (account, isFauceting) {
- // Temporarily deactivating isFauceting indication
- // because it shows fauceting for empty restored accounts.
- if (/* isFauceting*/ false) {
- return {
- key: 'Account is auto-funding.',
- value: 'Please wait.',
- }
- } else {
- return {
- key: 'BALANCE',
- value: formatBalance(account.balance),
- }
+function balanceOrFaucetingIndication (account) {
+ return {
+ key: 'BALANCE',
+ value: formatBalance(account.balance),
}
}
diff --git a/ui/app/components/app/bn-as-decimal-input.js b/ui/app/components/app/bn-as-decimal-input.js
index 9a033f893..834bab0a4 100644
--- a/ui/app/components/app/bn-as-decimal-input.js
+++ b/ui/app/components/app/bn-as-decimal-input.js
@@ -116,7 +116,7 @@ BnAsDecimalInput.prototype.render = function () {
)
}
-BnAsDecimalInput.prototype.setValid = function (message) {
+BnAsDecimalInput.prototype.setValid = function () {
this.setState({ invalid: null })
}
diff --git a/ui/app/components/app/dropdowns/account-details-dropdown.js b/ui/app/components/app/dropdowns/account-details-dropdown.js
index 3d4598946..cbeccdd81 100644
--- a/ui/app/components/app/dropdowns/account-details-dropdown.js
+++ b/ui/app/components/app/dropdowns/account-details-dropdown.js
@@ -4,7 +4,7 @@ const h = require('react-hyperscript')
const inherits = require('util').inherits
const connect = require('react-redux').connect
const actions = require('../../../store/actions')
-const { getSelectedIdentity } = require('../../../selectors/selectors')
+const { getSelectedIdentity, getRpcPrefsForCurrentProvider } = require('../../../selectors/selectors')
const genAccountLink = require('../../../../lib/account-link.js')
const { Menu, Item, CloseArea } = require('./components/menu')
@@ -20,6 +20,7 @@ function mapStateToProps (state) {
selectedIdentity: getSelectedIdentity(state),
network: state.metamask.network,
keyrings: state.metamask.keyrings,
+ rpcPrefs: getRpcPrefsForCurrentProvider(state),
}
}
@@ -28,8 +29,8 @@ function mapDispatchToProps (dispatch) {
showAccountDetailModal: () => {
dispatch(actions.showModal({ name: 'ACCOUNT_DETAILS' }))
},
- viewOnEtherscan: (address, network) => {
- global.platform.openWindow({ url: genAccountLink(address, network) })
+ viewOnEtherscan: (address, network, rpcPrefs) => {
+ global.platform.openWindow({ url: genAccountLink(address, network, rpcPrefs) })
},
showRemoveAccountConfirmationModal: (identity) => {
return dispatch(actions.showModal({ name: 'CONFIRM_REMOVE_ACCOUNT', identity }))
@@ -56,7 +57,9 @@ AccountDetailsDropdown.prototype.render = function () {
keyrings,
showAccountDetailModal,
viewOnEtherscan,
- showRemoveAccountConfirmationModal } = this.props
+ showRemoveAccountConfirmationModal,
+ rpcPrefs,
+ } = this.props
const address = selectedIdentity.address
@@ -112,10 +115,12 @@ AccountDetailsDropdown.prototype.render = function () {
name: 'Clicked View on Etherscan',
},
})
- viewOnEtherscan(address, network)
+ viewOnEtherscan(address, network, rpcPrefs)
this.props.onClose()
},
- text: this.context.t('viewOnEtherscan'),
+ text: (rpcPrefs.blockExplorerUrl
+ ? this.context.t('blockExplorerView', [rpcPrefs.blockExplorerUrl.match(/^https?:\/\/(.+)/)[1]])
+ : this.context.t('viewOnEtherscan')),
icon: h(`img`, { src: 'images/open-etherscan.svg', style: { height: '15px' } }),
}),
isRemovable ? h(Item, {
diff --git a/ui/app/components/app/dropdowns/network-dropdown.js b/ui/app/components/app/dropdowns/network-dropdown.js
index dbe3f1bc8..378ad3ba6 100644
--- a/ui/app/components/app/dropdowns/network-dropdown.js
+++ b/ui/app/components/app/dropdowns/network-dropdown.js
@@ -10,7 +10,7 @@ const Dropdown = require('./components/dropdown').Dropdown
const DropdownMenuItem = require('./components/dropdown').DropdownMenuItem
const NetworkDropdownIcon = require('./components/network-dropdown-icon')
const R = require('ramda')
-const { ADVANCED_ROUTE } = require('../../../helpers/constants/routes')
+const { NETWORKS_ROUTE } = require('../../../helpers/constants/routes')
// classes from nodes of the toggle element.
const notToggleElementClassnames = [
@@ -49,6 +49,7 @@ function mapDispatchToProps (dispatch) {
},
showNetworkDropdown: () => dispatch(actions.showNetworkDropdown()),
hideNetworkDropdown: () => dispatch(actions.hideNetworkDropdown()),
+ setNetworksTabAddMode: isInAddMode => dispatch(actions.setNetworksTabAddMode(isInAddMode)),
}
}
@@ -72,7 +73,7 @@ module.exports = compose(
// TODO: specify default props and proptypes
NetworkDropdown.prototype.render = function () {
const props = this.props
- const { provider: { type: providerType, rpcTarget: activeNetwork } } = props
+ const { provider: { type: providerType, rpcTarget: activeNetwork }, setNetworksTabAddMode } = props
const rpcListDetail = props.frequentRpcListDetail
const isOpen = this.props.networkDropdownOpen
const dropdownMenuItemStyle = {
@@ -255,7 +256,10 @@ NetworkDropdown.prototype.render = function () {
DropdownMenuItem,
{
closeMenu: () => this.props.hideNetworkDropdown(),
- onClick: () => this.props.history.push(ADVANCED_ROUTE),
+ onClick: () => {
+ setNetworksTabAddMode(true)
+ this.props.history.push(NETWORKS_ROUTE)
+ },
style: dropdownMenuItemStyle,
},
[
diff --git a/ui/app/components/app/ens-input.js b/ui/app/components/app/ens-input.js
index 424c5061e..5eea0dd90 100644
--- a/ui/app/components/app/ens-input.js
+++ b/ui/app/components/app/ens-input.js
@@ -144,7 +144,7 @@ EnsInput.prototype.ensIcon = function (recipient) {
}, this.ensIconContents(recipient))
}
-EnsInput.prototype.ensIconContents = function (recipient) {
+EnsInput.prototype.ensIconContents = function () {
const { loadingEns, ensFailure, ensResolution, toError } = this.state || { ensResolution: ZERO_ADDRESS }
if (toError) return
diff --git a/ui/app/components/app/gas-customization/advanced-gas-inputs/advanced-gas-inputs.component.js b/ui/app/components/app/gas-customization/advanced-gas-inputs/advanced-gas-inputs.component.js
index 95894140c..d6c259033 100644
--- a/ui/app/components/app/gas-customization/advanced-gas-inputs/advanced-gas-inputs.component.js
+++ b/ui/app/components/app/gas-customization/advanced-gas-inputs/advanced-gas-inputs.component.js
@@ -58,7 +58,7 @@ export default class AdvancedTabContent extends Component {
}
}
- gasInput ({ labelKey, value, onChange, insufficientBalance, showGWEI, customPriceIsSafe, isSpeedUp }) {
+ gasInput ({ labelKey, value, onChange, insufficientBalance, customPriceIsSafe, isSpeedUp }) {
const {
isInError,
errorText,
diff --git a/ui/app/components/app/gas-customization/advanced-gas-inputs/advanced-gas-inputs.container.js b/ui/app/components/app/gas-customization/advanced-gas-inputs/advanced-gas-inputs.container.js
index 90fef1a1b..73bc13481 100644
--- a/ui/app/components/app/gas-customization/advanced-gas-inputs/advanced-gas-inputs.container.js
+++ b/ui/app/components/app/gas-customization/advanced-gas-inputs/advanced-gas-inputs.container.js
@@ -17,8 +17,8 @@ function convertGasLimitForInputs (gasLimitInHexWEI) {
const mapDispatchToProps = dispatch => {
return {
- showGasPriceInfoModal: modalName => dispatch(showModal({ name: 'GAS_PRICE_INFO_MODAL' })),
- showGasLimitInfoModal: modalName => dispatch(showModal({ name: 'GAS_LIMIT_INFO_MODAL' })),
+ showGasPriceInfoModal: () => dispatch(showModal({ name: 'GAS_PRICE_INFO_MODAL' })),
+ showGasLimitInfoModal: () => dispatch(showModal({ name: 'GAS_LIMIT_INFO_MODAL' })),
}
}
diff --git a/ui/app/components/app/gas-customization/gas-modal-page-container/advanced-tab-content/advanced-tab-content.component.js b/ui/app/components/app/gas-customization/gas-modal-page-container/advanced-tab-content/advanced-tab-content.component.js
index ad8628621..eab3434df 100644
--- a/ui/app/components/app/gas-customization/gas-modal-page-container/advanced-tab-content/advanced-tab-content.component.js
+++ b/ui/app/components/app/gas-customization/gas-modal-page-container/advanced-tab-content/advanced-tab-content.component.js
@@ -67,7 +67,7 @@ export default class AdvancedTabContent extends Component {
}
}
- gasInput ({ labelKey, value, onChange, insufficientBalance, showGWEI, customPriceIsSafe, isSpeedUp }) {
+ gasInput ({ labelKey, value, onChange, insufficientBalance, customPriceIsSafe, isSpeedUp }) {
const {
isInError,
errorText,
@@ -148,7 +148,6 @@ export default class AdvancedTabContent extends Component {
customGasPrice,
updateCustomGasPrice,
customGasLimit,
- updateCustomGasLimit,
insufficientBalance,
customPriceIsSafe,
isSpeedUp,
diff --git a/ui/app/components/app/gas-customization/gas-modal-page-container/gas-modal-page-container.component.js b/ui/app/components/app/gas-customization/gas-modal-page-container/gas-modal-page-container.component.js
index 8aaccafd5..e18c1067e 100644
--- a/ui/app/components/app/gas-customization/gas-modal-page-container/gas-modal-page-container.component.js
+++ b/ui/app/components/app/gas-customization/gas-modal-page-container/gas-modal-page-container.component.js
@@ -122,8 +122,6 @@ export default class GasModalPageContainer extends Component {
}
renderTabs ({
- originalTotalFiat,
- originalTotalEth,
newTotalFiat,
newTotalEth,
sendAmount,
diff --git a/ui/app/components/app/gas-customization/gas-price-button-group/gas-price-button-group.component.js b/ui/app/components/app/gas-customization/gas-price-button-group/gas-price-button-group.component.js
index 0456f5262..14952a49a 100644
--- a/ui/app/components/app/gas-customization/gas-price-button-group/gas-price-button-group.component.js
+++ b/ui/app/components/app/gas-customization/gas-price-button-group/gas-price-button-group.component.js
@@ -49,7 +49,7 @@ export default class GasPriceButtonGroup extends Component {
priceInHexWei,
...renderableGasInfo
}, {
- buttonDataLoading,
+ buttonDataLoading: _,
handleGasPriceSelection,
...buttonContentPropsAndFlags
}, index) {
diff --git a/ui/app/components/app/gas-customization/gas-price-chart/gas-price-chart.utils.js b/ui/app/components/app/gas-customization/gas-price-chart/gas-price-chart.utils.js
index f19dafcc1..55512ce09 100644
--- a/ui/app/components/app/gas-customization/gas-price-chart/gas-price-chart.utils.js
+++ b/ui/app/components/app/gas-customization/gas-price-chart/gas-price-chart.utils.js
@@ -68,7 +68,7 @@ export function handleChartUpdate ({ chart, gasPrices, newPrice, cssId }) {
export function getAdjacentGasPrices ({ gasPrices, priceToPosition }) {
const closestLowerValueIndex = gasPrices.findIndex((e, i, a) => e <= priceToPosition && a[i + 1] >= priceToPosition)
- const closestHigherValueIndex = gasPrices.findIndex((e, i, a) => e > priceToPosition)
+ const closestHigherValueIndex = gasPrices.findIndex((e) => e > priceToPosition)
return {
closestLowerValueIndex,
closestHigherValueIndex,
@@ -133,7 +133,7 @@ export function setTickPosition (axis, n, newPosition, secondNewPosition) {
d3.select('#chart')
.select(`.c3-axis-${axis}`)
.selectAll('.tick')
- .filter((d, i) => i === n)
+ .filter((_, i) => i === n)
.select('text')
.attr(positionToShift, 0)
.select('tspan')
@@ -284,7 +284,7 @@ export function generateChart (gasPrices, estimatedTimes, gasPricesMax, estimate
})
return text + '</table>' + "<div class='tooltip-arrow'></div>"
},
- position: function (data) {
+ position: function () {
if (d3.select('#overlayed-circle').empty()) {
return { top: -100, left: -100 }
}
diff --git a/ui/app/components/app/gas-customization/gas-price-chart/tests/gas-price-chart.component.test.js b/ui/app/components/app/gas-customization/gas-price-chart/tests/gas-price-chart.component.test.js
index 7dec7a85f..c960f49a7 100644
--- a/ui/app/components/app/gas-customization/gas-price-chart/tests/gas-price-chart.component.test.js
+++ b/ui/app/components/app/gas-customization/gas-price-chart/tests/gas-price-chart.component.test.js
@@ -6,7 +6,7 @@ import shallow from '../../../../../../lib/shallow-with-context'
import * as d3 from 'd3'
function timeout (time) {
- return new Promise((resolve, reject) => {
+ return new Promise((resolve) => {
setTimeout(resolve, time)
})
}
diff --git a/ui/app/components/app/modals/account-details-modal.js b/ui/app/components/app/modals/account-details-modal.js
index 1b1ca6b8e..6cffc918b 100644
--- a/ui/app/components/app/modals/account-details-modal.js
+++ b/ui/app/components/app/modals/account-details-modal.js
@@ -5,7 +5,7 @@ const inherits = require('util').inherits
const connect = require('react-redux').connect
const actions = require('../../../store/actions')
const AccountModalContainer = require('./account-modal-container')
-const { getSelectedIdentity } = require('../../../selectors/selectors')
+const { getSelectedIdentity, getRpcPrefsForCurrentProvider } = require('../../../selectors/selectors')
const genAccountLink = require('../../../../lib/account-link.js')
const QrView = require('../../ui/qr-code')
const EditableLabel = require('../../ui/editable-label')
@@ -17,6 +17,7 @@ function mapStateToProps (state) {
network: state.metamask.network,
selectedIdentity: getSelectedIdentity(state),
keyrings: state.metamask.keyrings,
+ rpcPrefs: getRpcPrefsForCurrentProvider(state),
}
}
@@ -54,6 +55,7 @@ AccountDetailsModal.prototype.render = function () {
showExportPrivateKeyModal,
setAccountLabel,
keyrings,
+ rpcPrefs,
} = this.props
const { name, address } = selectedIdentity
@@ -86,8 +88,12 @@ AccountDetailsModal.prototype.render = function () {
h(Button, {
type: 'secondary',
className: 'account-modal__button',
- onClick: () => global.platform.openWindow({ url: genAccountLink(address, network) }),
- }, this.context.t('etherscanView')),
+ onClick: () => {
+ global.platform.openWindow({ url: genAccountLink(address, network, rpcPrefs) })
+ },
+ }, (rpcPrefs.blockExplorerUrl
+ ? this.context.t('blockExplorerView', [rpcPrefs.blockExplorerUrl.match(/^https?:\/\/(.+)/)[1]])
+ : this.context.t('viewOnEtherscan'))),
// Holding on redesign for Export Private Key functionality
diff --git a/ui/app/components/app/modals/deposit-ether-modal.js b/ui/app/components/app/modals/deposit-ether-modal.js
index 8f7ef792c..f56069d65 100644
--- a/ui/app/components/app/modals/deposit-ether-modal.js
+++ b/ui/app/components/app/modals/deposit-ether-modal.js
@@ -48,7 +48,7 @@ function mapDispatchToProps (dispatch) {
}
inherits(DepositEtherModal, Component)
-function DepositEtherModal (props, context) {
+function DepositEtherModal (_, context) {
Component.call(this)
// need to set after i18n locale has loaded
diff --git a/ui/app/components/app/modals/export-private-key-modal.js b/ui/app/components/app/modals/export-private-key-modal.js
index 70987330a..c3098a16c 100644
--- a/ui/app/components/app/modals/export-private-key-modal.js
+++ b/ui/app/components/app/modals/export-private-key-modal.js
@@ -98,7 +98,7 @@ ExportPrivateKeyModal.prototype.renderPasswordInput = function (privateKey) {
})
}
-ExportPrivateKeyModal.prototype.renderButtons = function (privateKey, password, address, hideModal) {
+ExportPrivateKeyModal.prototype.renderButtons = function (privateKey, address, hideModal) {
return h('div.export-private-key-buttons', {}, [
!privateKey && h(Button, {
type: 'default',
@@ -171,7 +171,7 @@ ExportPrivateKeyModal.prototype.render = function () {
h('div.private-key-password-warning', this.context.t('privateKeyWarning')),
- this.renderButtons(privateKey, this.state.password, address, hideModal),
+ this.renderButtons(privateKey, address, hideModal),
])
}
diff --git a/ui/app/components/app/modals/metametrics-opt-in-modal/metametrics-opt-in-modal.container.js b/ui/app/components/app/modals/metametrics-opt-in-modal/metametrics-opt-in-modal.container.js
index 83595281f..ea7d71a73 100644
--- a/ui/app/components/app/modals/metametrics-opt-in-modal/metametrics-opt-in-modal.container.js
+++ b/ui/app/components/app/modals/metametrics-opt-in-modal/metametrics-opt-in-modal.container.js
@@ -4,7 +4,7 @@ import MetaMetricsOptInModal from './metametrics-opt-in-modal.component'
import withModalProps from '../../../../helpers/higher-order-components/with-modal-props'
import { setParticipateInMetaMetrics } from '../../../../store/actions'
-const mapStateToProps = (state, ownProps) => {
+const mapStateToProps = (_, ownProps) => {
const { unapprovedTxCount } = ownProps
return {
diff --git a/ui/app/components/app/modals/qr-scanner/qr-scanner.component.js b/ui/app/components/app/modals/qr-scanner/qr-scanner.component.js
index 20915b5f9..a83ba8f8e 100644
--- a/ui/app/components/app/modals/qr-scanner/qr-scanner.component.js
+++ b/ui/app/components/app/modals/qr-scanner/qr-scanner.component.js
@@ -71,7 +71,7 @@ export default class QrScanner extends Component {
initCamera () {
this.codeReader = new BrowserQRCodeReader()
this.codeReader.getVideoInputDevices()
- .then(videoInputDevices => {
+ .then(() => {
clearTimeout(this.permissionChecker)
this.checkPermisisions()
this.codeReader.decodeFromInputVideoDevice(undefined, 'video')
diff --git a/ui/app/components/app/modals/reject-transactions/reject-transactions.container.js b/ui/app/components/app/modals/reject-transactions/reject-transactions.container.js
index d2af05573..aa74fd800 100644
--- a/ui/app/components/app/modals/reject-transactions/reject-transactions.container.js
+++ b/ui/app/components/app/modals/reject-transactions/reject-transactions.container.js
@@ -3,7 +3,7 @@ import { compose } from 'recompose'
import RejectTransactionsModal from './reject-transactions.component'
import withModalProps from '../../../../helpers/higher-order-components/with-modal-props'
-const mapStateToProps = (state, ownProps) => {
+const mapStateToProps = (_, ownProps) => {
const { unapprovedTxCount } = ownProps
return {
diff --git a/ui/app/components/app/provider-page-container/provider-page-container.component.js b/ui/app/components/app/provider-page-container/provider-page-container.component.js
index 910def2a3..1c655d404 100644
--- a/ui/app/components/app/provider-page-container/provider-page-container.component.js
+++ b/ui/app/components/app/provider-page-container/provider-page-container.component.js
@@ -5,12 +5,11 @@ import { PageContainerFooter } from '../../ui/page-container'
export default class ProviderPageContainer extends PureComponent {
static propTypes = {
- approveProviderRequest: PropTypes.func.isRequired,
+ approveProviderRequestByOrigin: PropTypes.func.isRequired,
+ rejectProviderRequestByOrigin: PropTypes.func.isRequired,
origin: PropTypes.string.isRequired,
- rejectProviderRequest: PropTypes.func.isRequired,
siteImage: PropTypes.string,
siteTitle: PropTypes.string.isRequired,
- tabID: PropTypes.string.isRequired,
};
static contextTypes = {
@@ -29,7 +28,7 @@ export default class ProviderPageContainer extends PureComponent {
}
onCancel = () => {
- const { tabID, rejectProviderRequest } = this.props
+ const { origin, rejectProviderRequestByOrigin } = this.props
this.context.metricsEvent({
eventOpts: {
category: 'Auth',
@@ -37,11 +36,11 @@ export default class ProviderPageContainer extends PureComponent {
name: 'Canceled',
},
})
- rejectProviderRequest(tabID)
+ rejectProviderRequestByOrigin(origin)
}
onSubmit = () => {
- const { approveProviderRequest, tabID } = this.props
+ const { approveProviderRequestByOrigin, origin } = this.props
this.context.metricsEvent({
eventOpts: {
category: 'Auth',
@@ -49,7 +48,7 @@ export default class ProviderPageContainer extends PureComponent {
name: 'Confirmed',
},
})
- approveProviderRequest(tabID)
+ approveProviderRequestByOrigin(origin)
}
render () {
diff --git a/ui/app/components/app/token-cell.js b/ui/app/components/app/token-cell.js
index cef809e8a..495b9502b 100644
--- a/ui/app/components/app/token-cell.js
+++ b/ui/app/components/app/token-cell.js
@@ -155,7 +155,7 @@ TokenCell.prototype.send = function (address, event) {
}
}
-TokenCell.prototype.view = function (address, userAddress, network, event) {
+TokenCell.prototype.view = function (address, userAddress, network) {
const url = etherscanLinkFor(address, userAddress, network)
if (url) {
navigateTo(url)
diff --git a/ui/app/components/app/transaction-list-item-details/transaction-list-item-details.component.js b/ui/app/components/app/transaction-list-item-details/transaction-list-item-details.component.js
index 4a3b04998..72ca784e2 100644
--- a/ui/app/components/app/transaction-list-item-details/transaction-list-item-details.component.js
+++ b/ui/app/components/app/transaction-list-item-details/transaction-list-item-details.component.js
@@ -1,13 +1,15 @@
import React, { PureComponent } from 'react'
import PropTypes from 'prop-types'
import copyToClipboard from 'copy-to-clipboard'
+import {
+ getBlockExplorerUrlForTx,
+} from '../../../helpers/utils/transactions.util'
import SenderToRecipient from '../../ui/sender-to-recipient'
import { FLAT_VARIANT } from '../../ui/sender-to-recipient/sender-to-recipient.constants'
import TransactionActivityLog from '../transaction-activity-log'
import TransactionBreakdown from '../transaction-breakdown'
import Button from '../../ui/button'
import Tooltip from '../../ui/tooltip'
-import prefixForNetwork from '../../../../lib/etherscan-prefix-for-network'
export default class TransactionListItemDetails extends PureComponent {
static contextTypes = {
@@ -22,6 +24,7 @@ export default class TransactionListItemDetails extends PureComponent {
showRetry: PropTypes.bool,
cancelDisabled: PropTypes.bool,
transactionGroup: PropTypes.object,
+ rpcPrefs: PropTypes.object,
}
state = {
@@ -30,12 +33,9 @@ export default class TransactionListItemDetails extends PureComponent {
}
handleEtherscanClick = () => {
- const { transactionGroup: { primaryTransaction } } = this.props
+ const { transactionGroup: { primaryTransaction }, rpcPrefs } = this.props
const { hash, metamaskNetworkId } = primaryTransaction
- const prefix = prefixForNetwork(metamaskNetworkId)
- const etherscanUrl = `https://${prefix}etherscan.io/tx/${hash}`
-
this.context.metricsEvent({
eventOpts: {
category: 'Navigation',
@@ -44,7 +44,7 @@ export default class TransactionListItemDetails extends PureComponent {
},
})
- global.platform.openWindow({ url: etherscanUrl })
+ global.platform.openWindow({ url: getBlockExplorerUrlForTx(metamaskNetworkId, hash, rpcPrefs) })
}
handleCancel = event => {
@@ -125,6 +125,7 @@ export default class TransactionListItemDetails extends PureComponent {
showRetry,
onCancel,
onRetry,
+ rpcPrefs: { blockExplorerUrl } = {},
} = this.props
const { primaryTransaction: transaction } = transactionGroup
const { txParams: { to, from } = {} } = transaction
@@ -158,7 +159,7 @@ export default class TransactionListItemDetails extends PureComponent {
/>
</Button>
</Tooltip>
- <Tooltip title={t('viewOnEtherscan')}>
+ <Tooltip title={blockExplorerUrl ? t('viewOnCustomBlockExplorer', [blockExplorerUrl]) : t('viewOnEtherscan')}>
<Button
type="raised"
onClick={this.handleEtherscanClick}
diff --git a/ui/app/components/app/transaction-list-item/transaction-list-item.component.js b/ui/app/components/app/transaction-list-item/transaction-list-item.component.js
index c7d9dd7c7..0d4127b4f 100644
--- a/ui/app/components/app/transaction-list-item/transaction-list-item.component.js
+++ b/ui/app/components/app/transaction-list-item/transaction-list-item.component.js
@@ -33,6 +33,7 @@ export default class TransactionListItem extends PureComponent {
value: PropTypes.string,
fetchBasicGasAndTimeEstimates: PropTypes.func,
fetchGasEstimates: PropTypes.func,
+ rpcPrefs: PropTypes.object,
}
static defaultProps = {
@@ -161,6 +162,7 @@ export default class TransactionListItem extends PureComponent {
showRetry,
tokenData,
transactionGroup,
+ rpcPrefs,
} = this.props
const { txParams = {} } = transaction
const { showTransactionDetails } = this.state
@@ -216,6 +218,7 @@ export default class TransactionListItem extends PureComponent {
onCancel={this.handleCancel}
showCancel={showCancel}
cancelDisabled={!hasEnoughCancelGas}
+ rpcPrefs={rpcPrefs}
/>
</div>
)
diff --git a/ui/app/components/app/transaction-list-item/transaction-list-item.container.js b/ui/app/components/app/transaction-list-item/transaction-list-item.container.js
index a8fb8c246..5e88a2937 100644
--- a/ui/app/components/app/transaction-list-item/transaction-list-item.container.js
+++ b/ui/app/components/app/transaction-list-item/transaction-list-item.container.js
@@ -18,12 +18,14 @@ import { getIsMainnet, preferencesSelector, getSelectedAddress, conversionRateSe
import { isBalanceSufficient } from '../../../pages/send/send.utils'
const mapStateToProps = (state, ownProps) => {
- const { metamask: { knownMethodData, accounts } } = state
+ const { metamask: { knownMethodData, accounts, provider, frequentRpcListDetail } } = state
const { showFiatInTestnets } = preferencesSelector(state)
const isMainnet = getIsMainnet(state)
const { transactionGroup: { primaryTransaction } = {} } = ownProps
const { txParams: { gas: gasLimit, gasPrice } = {} } = primaryTransaction
const selectedAccountBalance = accounts[getSelectedAddress(state)].balance
+ const selectRpcInfo = frequentRpcListDetail.find(rpcInfo => rpcInfo.rpcUrl === provider.rpcTarget)
+ const { rpcPrefs } = selectRpcInfo || {}
const hasEnoughCancelGas = primaryTransaction.txParams && isBalanceSufficient({
amount: '0x0',
@@ -40,6 +42,7 @@ const mapStateToProps = (state, ownProps) => {
showFiat: (isMainnet || !!showFiatInTestnets),
selectedAccountBalance,
hasEnoughCancelGas,
+ rpcPrefs,
}
}
diff --git a/ui/app/components/app/user-preferenced-currency-display/tests/user-preferenced-currency-display.container.test.js b/ui/app/components/app/user-preferenced-currency-display/tests/user-preferenced-currency-display.container.test.js
index 88d63baae..4ecc0dabb 100644
--- a/ui/app/components/app/user-preferenced-currency-display/tests/user-preferenced-currency-display.container.test.js
+++ b/ui/app/components/app/user-preferenced-currency-display/tests/user-preferenced-currency-display.container.test.js
@@ -5,7 +5,7 @@ let mapStateToProps, mergeProps
proxyquire('../user-preferenced-currency-display.container.js', {
'react-redux': {
- connect: (ms, md, mp) => {
+ connect: (ms, _, mp) => {
mapStateToProps = ms
mergeProps = mp
return () => ({})
diff --git a/ui/app/components/app/user-preferenced-currency-display/user-preferenced-currency-display.container.js b/ui/app/components/app/user-preferenced-currency-display/user-preferenced-currency-display.container.js
index 42d156f92..2a4635955 100644
--- a/ui/app/components/app/user-preferenced-currency-display/user-preferenced-currency-display.container.js
+++ b/ui/app/components/app/user-preferenced-currency-display/user-preferenced-currency-display.container.js
@@ -3,7 +3,7 @@ import UserPreferencedCurrencyDisplay from './user-preferenced-currency-display.
import { preferencesSelector, getIsMainnet } from '../../../selectors/selectors'
import { ETH, PRIMARY, SECONDARY } from '../../../helpers/constants/common'
-const mapStateToProps = (state, ownProps) => {
+const mapStateToProps = (state) => {
const {
useNativeCurrencyAsPrimaryCurrency,
showFiatInTestnets,
diff --git a/ui/app/components/ui/alert/index.js b/ui/app/components/ui/alert/index.js
index 5620d847a..b1229f502 100644
--- a/ui/app/components/ui/alert/index.js
+++ b/ui/app/components/ui/alert/index.js
@@ -18,7 +18,7 @@ class Alert extends Component {
if (!this.props.visible && nextProps.visible) {
this.animateIn(nextProps)
} else if (this.props.visible && !nextProps.visible) {
- this.animateOut(nextProps)
+ this.animateOut()
}
}
@@ -30,7 +30,7 @@ class Alert extends Component {
})
}
- animateOut (props) {
+ animateOut () {
this.setState({
msg: null,
className: '.hidden',
diff --git a/ui/app/components/ui/currency-display/currency-display.component.js b/ui/app/components/ui/currency-display/currency-display.component.js
index 04dd89892..c15668da3 100644
--- a/ui/app/components/ui/currency-display/currency-display.component.js
+++ b/ui/app/components/ui/currency-display/currency-display.component.js
@@ -23,7 +23,7 @@ export default class CurrencyDisplay extends PureComponent {
render () {
const { className, displayValue, prefix, prefixComponent, style, suffix, hideTitle } = this.props
const text = `${prefix || ''}${displayValue}`
- const title = `${text} ${suffix}`
+ const title = suffix ? `${text} ${suffix}` : text
return (
<div
diff --git a/ui/app/components/ui/currency-display/tests/currency-display.container.test.js b/ui/app/components/ui/currency-display/tests/currency-display.container.test.js
index 9888c366e..182524e59 100644
--- a/ui/app/components/ui/currency-display/tests/currency-display.container.test.js
+++ b/ui/app/components/ui/currency-display/tests/currency-display.container.test.js
@@ -5,7 +5,7 @@ let mapStateToProps, mergeProps
proxyquire('../currency-display.container.js', {
'react-redux': {
- connect: (ms, md, mp) => {
+ connect: (ms, _, mp) => {
mapStateToProps = ms
mergeProps = mp
return () => ({})
diff --git a/ui/app/components/ui/currency-input/tests/currency-input.container.test.js b/ui/app/components/ui/currency-input/tests/currency-input.container.test.js
index 6109d29b6..259fe594a 100644
--- a/ui/app/components/ui/currency-input/tests/currency-input.container.test.js
+++ b/ui/app/components/ui/currency-input/tests/currency-input.container.test.js
@@ -5,7 +5,7 @@ let mapStateToProps, mergeProps
proxyquire('../currency-input.container.js', {
'react-redux': {
- connect: (ms, md, mp) => {
+ connect: (ms, _, mp) => {
mapStateToProps = ms
mergeProps = mp
return () => ({})
diff --git a/ui/app/components/ui/text-field/text-field.component.js b/ui/app/components/ui/text-field/text-field.component.js
index 2c72d8124..1153a595b 100644
--- a/ui/app/components/ui/text-field/text-field.component.js
+++ b/ui/app/components/ui/text-field/text-field.component.js
@@ -41,11 +41,11 @@ const styles = {
inputFocused: {},
inputRoot: {
'label + &': {
- marginTop: '8px',
+ marginTop: '9px',
},
- border: '1px solid #d2d8dd',
+ border: '2px solid #BBC0C5',
height: '48px',
- borderRadius: '4px',
+ borderRadius: '6px',
padding: '0 16px',
display: 'flex',
alignItems: 'center',
diff --git a/ui/app/components/ui/token-input/tests/token-input.container.test.js b/ui/app/components/ui/token-input/tests/token-input.container.test.js
index 2b1c102c8..6f87e64a5 100644
--- a/ui/app/components/ui/token-input/tests/token-input.container.test.js
+++ b/ui/app/components/ui/token-input/tests/token-input.container.test.js
@@ -5,7 +5,7 @@ let mapStateToProps, mergeProps
proxyquire('../token-input.container.js', {
'react-redux': {
- connect: (ms, md, mp) => {
+ connect: (ms, _, mp) => {
mapStateToProps = ms
mergeProps = mp
return () => ({})
diff --git a/ui/app/components/ui/unit-input/unit-input.component.js b/ui/app/components/ui/unit-input/unit-input.component.js
index c5f8350a6..6a53f4c6f 100644
--- a/ui/app/components/ui/unit-input/unit-input.component.js
+++ b/ui/app/components/ui/unit-input/unit-input.component.js
@@ -58,7 +58,7 @@ export default class UnitInput extends PureComponent {
this.props.onChange(value)
}
- handleBlur = event => {
+ handleBlur = () => {
const { onBlur } = this.props
typeof onBlur === 'function' && onBlur(this.state.value)
}
diff --git a/ui/app/ducks/app/app.js b/ui/app/ducks/app/app.js
index 295507d70..b181092c1 100644
--- a/ui/app/ducks/app/app.js
+++ b/ui/app/ducks/app/app.js
@@ -77,6 +77,8 @@ function reduceApp (state, action) {
ledger: `m/44'/60'/0'/0/0`,
},
lastSelectedProvider: null,
+ networksTabSelectedRpcUrl: '',
+ networksTabIsInAddMode: false,
}, state.appState)
switch (action.type) {
@@ -751,6 +753,16 @@ function reduceApp (state, action) {
lastSelectedProvider: action.value,
})
+ case actions.SET_SELECTED_SETTINGS_RPC_URL:
+ return extend(appState, {
+ networksTabSelectedRpcUrl: action.value,
+ })
+
+ case actions.SET_NETWORKS_TAB_ADD_MODE:
+ return extend(appState, {
+ networksTabIsInAddMode: action.value,
+ })
+
default:
return appState
}
diff --git a/ui/app/ducks/confirm-transaction/confirm-transaction.duck.js b/ui/app/ducks/confirm-transaction/confirm-transaction.duck.js
index 169c9d543..58b0ec8e8 100644
--- a/ui/app/ducks/confirm-transaction/confirm-transaction.duck.js
+++ b/ui/app/ducks/confirm-transaction/confirm-transaction.duck.js
@@ -375,7 +375,7 @@ export function setTransactionToConfirm (transactionId) {
dispatch(updateMethodData(methodData))
try {
- const toSmartContract = await isSmartContractAddress(to)
+ const toSmartContract = await isSmartContractAddress(to || '')
dispatch(updateToSmartContract(toSmartContract))
} catch (error) {
log.error(error)
diff --git a/ui/app/ducks/confirm-transaction/confirm-transaction.duck.test.js b/ui/app/ducks/confirm-transaction/confirm-transaction.duck.test.js
index 483f2f56d..d2e344663 100644
--- a/ui/app/ducks/confirm-transaction/confirm-transaction.duck.test.js
+++ b/ui/app/ducks/confirm-transaction/confirm-transaction.duck.test.js
@@ -494,7 +494,7 @@ describe('Confirm Transaction Duck', () => {
})
})
- describe('Thunk actions', done => {
+ describe('Thunk actions', () => {
beforeEach(() => {
global.eth = {
getCode: sinon.stub().callsFake(
diff --git a/ui/app/ducks/gas/gas-duck.test.js b/ui/app/ducks/gas/gas-duck.test.js
index c0152c74f..b7e83a81c 100644
--- a/ui/app/ducks/gas/gas-duck.test.js
+++ b/ui/app/ducks/gas/gas-duck.test.js
@@ -461,8 +461,8 @@ describe('Gas Duck', () => {
assert.equal(thirdDispatchCallType, SET_PRICE_AND_TIME_ESTIMATES)
assert(priceAndTimeEstimateResult.length < mockPredictTableResponse.length * 3 - 2)
assert(!priceAndTimeEstimateResult.find(d => d.expectedTime > 100))
- assert(!priceAndTimeEstimateResult.find((d, i, a) => a[a + 1] && d.expectedTime > a[a + 1].expectedTime))
- assert(!priceAndTimeEstimateResult.find((d, i, a) => a[a + 1] && d.gasprice > a[a + 1].gasprice))
+ assert(!priceAndTimeEstimateResult.find((d, _, a) => a[a + 1] && d.expectedTime > a[a + 1].expectedTime))
+ assert(!priceAndTimeEstimateResult.find((d, _, a) => a[a + 1] && d.gasprice > a[a + 1].gasprice))
assert.deepEqual(
mockDistpatch.getCall(3).args,
diff --git a/ui/app/helpers/constants/routes.js b/ui/app/helpers/constants/routes.js
index df35112d1..d906fc8e6 100644
--- a/ui/app/helpers/constants/routes.js
+++ b/ui/app/helpers/constants/routes.js
@@ -8,6 +8,7 @@ const ADVANCED_ROUTE = '/settings/advanced'
const SECURITY_ROUTE = '/settings/security'
const COMPANY_ROUTE = '/settings/company'
const ABOUT_US_ROUTE = '/settings/about-us'
+const NETWORKS_ROUTE = '/settings/networks'
const REVEAL_SEED_ROUTE = '/seed'
const MOBILE_SYNC_ROUTE = '/mobile-sync'
const CONFIRM_SEED_ROUTE = '/confirm-seed'
@@ -86,4 +87,5 @@ module.exports = {
COMPANY_ROUTE,
GENERAL_ROUTE,
ABOUT_US_ROUTE,
+ NETWORKS_ROUTE,
}
diff --git a/ui/app/helpers/higher-order-components/i18n-provider.js b/ui/app/helpers/higher-order-components/i18n-provider.js
index 0e34e17e0..5a6650147 100644
--- a/ui/app/helpers/higher-order-components/i18n-provider.js
+++ b/ui/app/helpers/higher-order-components/i18n-provider.js
@@ -15,11 +15,21 @@ class I18nProvider extends Component {
const { localeMessages } = this.props
const { current, en } = localeMessages
return {
+ /**
+ * Returns a localized message for the given key
+ * @param {string} key The message key
+ * @param {string[]} args A list of message substitution replacements
+ * @return {string|undefined|null} The localized message if available
+ */
t (key, ...args) {
+ if (key === undefined || key === null) {
+ return key
+ }
+
return t(current, key, ...args) || t(en, key, ...args) || `[${key}]`
},
tOrDefault: this.tOrDefault,
- tOrKey (key, ...args) {
+ tOrKey: (key, ...args) => {
return this.tOrDefault(key, key, ...args)
},
}
diff --git a/ui/app/helpers/higher-order-components/metametrics/metametrics.provider.js b/ui/app/helpers/higher-order-components/metametrics/metametrics.provider.js
index 6086e03fb..6281ddcc6 100644
--- a/ui/app/helpers/higher-order-components/metametrics/metametrics.provider.js
+++ b/ui/app/helpers/higher-order-components/metametrics/metametrics.provider.js
@@ -42,7 +42,7 @@ class MetaMetricsProvider extends Component {
currentPath: window.location.href,
}
- props.history.listen(locationObj => {
+ props.history.listen(() => {
this.setState({
previousPath: this.state.currentPath,
currentPath: window.location.href,
diff --git a/ui/app/helpers/higher-order-components/with-modal-props/tests/with-modal-props.test.js b/ui/app/helpers/higher-order-components/with-modal-props/tests/with-modal-props.test.js
index 654e7062a..81a3512d1 100644
--- a/ui/app/helpers/higher-order-components/with-modal-props/tests/with-modal-props.test.js
+++ b/ui/app/helpers/higher-order-components/with-modal-props/tests/with-modal-props.test.js
@@ -21,7 +21,7 @@ const mockState = {
describe('withModalProps', () => {
it('should return a component wrapped with modal state props', () => {
- const TestComponent = props => (
+ const TestComponent = () => (
<div className="test">Testing</div>
)
const WrappedComponent = withModalProps(TestComponent)
diff --git a/ui/app/helpers/utils/conversion-util.js b/ui/app/helpers/utils/conversion-util.js
index 8cc531773..affddade7 100644
--- a/ui/app/helpers/utils/conversion-util.js
+++ b/ui/app/helpers/utils/conversion-util.js
@@ -42,7 +42,7 @@ const convert = R.invoker(1, 'times')
const round = R.invoker(2, 'round')(R.__, BigNumber.ROUND_HALF_DOWN)
const roundDown = R.invoker(2, 'round')(R.__, BigNumber.ROUND_DOWN)
const invertConversionRate = conversionRate => () => new BigNumber(1.0).div(conversionRate)
-const decToBigNumberViaString = n => R.pipe(String, toBigNumber['dec'])
+const decToBigNumberViaString = () => R.pipe(String, toBigNumber['dec'])
// Setter Maps
const toBigNumber = {
diff --git a/ui/app/helpers/utils/metametrics.util.js b/ui/app/helpers/utils/metametrics.util.js
index 5ae3e8937..cafbd5c07 100644
--- a/ui/app/helpers/utils/metametrics.util.js
+++ b/ui/app/helpers/utils/metametrics.util.js
@@ -84,7 +84,7 @@ function composeParamAddition (paramValue, paramName) {
: `&${paramName}=${paramValue}`
}
-function composeUrl (config, permissionPreferences = {}) {
+function composeUrl (config) {
const {
eventOpts = {},
customVariables = '',
@@ -124,10 +124,10 @@ function composeUrl (config, permissionPreferences = {}) {
numberOfTokens: customVariables && customVariables.numberOfTokens || numberOfTokens,
numberOfAccounts: customVariables && customVariables.numberOfAccounts || numberOfAccounts,
}) : ''
- const url = configUrl || `&url=${encodeURIComponent(currentPath.replace(/chrome-extension:\/\/\w+/, METAMETRICS_TRACKING_URL))}`
+ const url = configUrl || currentPath ? `&url=${encodeURIComponent(currentPath.replace(/chrome-extension:\/\/\w+/, METAMETRICS_TRACKING_URL))}` : ''
const _id = metaMetricsId && !excludeMetaMetricsId ? `&_id=${metaMetricsId.slice(2, 18)}` : ''
const rand = `&rand=${String(Math.random()).slice(2)}`
- const pv_id = `&pv_id=${ethUtil.bufferToHex(ethUtil.sha3(url || currentPath.match(/chrome-extension:\/\/\w+\/(.+)/)[0])).slice(2, 8)}`
+ const pv_id = (url || currentPath) && `&pv_id=${ethUtil.bufferToHex(ethUtil.sha3(url || currentPath.match(/chrome-extension:\/\/\w+\/(.+)/)[0])).slice(2, 8)}` || ''
const uid = metaMetricsId && !excludeMetaMetricsId
? `&uid=${metaMetricsId.slice(2, 18)}`
: excludeMetaMetricsId
diff --git a/ui/app/helpers/utils/transactions.util.js b/ui/app/helpers/utils/transactions.util.js
index cb6c9536c..99ccc3478 100644
--- a/ui/app/helpers/utils/transactions.util.js
+++ b/ui/app/helpers/utils/transactions.util.js
@@ -6,6 +6,8 @@ import {
TRANSACTION_TYPE_CANCEL,
TRANSACTION_STATUS_CONFIRMED,
} from '../../../../app/scripts/controllers/transactions/enums'
+import prefixForNetwork from '../../../lib/etherscan-prefix-for-network'
+
import {
TOKEN_METHOD_TRANSFER,
@@ -188,3 +190,17 @@ export function getStatusKey (transaction) {
return transaction.status
}
+
+/**
+ * Returns an external block explorer URL at which a transaction can be viewed.
+ * @param {number} networkId
+ * @param {string} hash
+ * @param {Object} rpcPrefs
+ */
+export function getBlockExplorerUrlForTx (networkId, hash, rpcPrefs = {}) {
+ if (rpcPrefs.blockExplorerUrl) {
+ return `${rpcPrefs.blockExplorerUrl}/tx/${hash}`
+ }
+ const prefix = prefixForNetwork(networkId)
+ return `https://${prefix}etherscan.io/tx/${hash}`
+}
diff --git a/ui/app/helpers/utils/util.js b/ui/app/helpers/utils/util.js
index c50d7cbe5..94fa9ad42 100644
--- a/ui/app/helpers/utils/util.js
+++ b/ui/app/helpers/utils/util.js
@@ -92,7 +92,7 @@ function miniAddressSummary (address) {
return checked ? checked.slice(0, 4) + '...' + checked.slice(-4) : '...'
}
-function isValidAddress (address, network) {
+function isValidAddress (address) {
var prefixed = ethUtil.addHexPrefix(address)
if (address === '0x0000000000000000000000000000000000000000') return false
return (isAllOneCase(prefixed) && ethUtil.isValidAddress(prefixed)) || ethUtil.isValidChecksumAddress(prefixed)
@@ -268,7 +268,7 @@ function bnMultiplyByFraction (targetBN, numerator, denominator) {
return targetBN.mul(numBN).div(denomBN)
}
-function getTxFeeBn (gas, gasPrice = MIN_GAS_PRICE_BN.toString(16), blockGasLimit) {
+function getTxFeeBn (gas, gasPrice = MIN_GAS_PRICE_BN.toString(16)) {
const gasBn = hexToBn(gas)
const gasPriceBn = hexToBn(gasPrice)
const txFeeBn = gasBn.mul(gasPriceBn)
@@ -297,7 +297,7 @@ function exportAsFile (filename, data, type = 'text/csv') {
}
function allNull (obj) {
- return Object.entries(obj).every(([key, value]) => value === null)
+ return Object.entries(obj).every(([_, value]) => value === null)
}
function getTokenAddressFromTokenObject (token) {
@@ -308,11 +308,10 @@ function getTokenAddressFromTokenObject (token) {
* Safely checksumms a potentially-null address
*
* @param {String} [address] - address to checksum
- * @param {String} [network] - network id
* @returns {String} - checksummed address
*
*/
-function checksumAddress (address, network) {
+function checksumAddress (address) {
const checksummed = address ? ethUtil.toChecksumAddress(address) : ''
return checksummed
}
diff --git a/ui/app/pages/confirm-transaction-base/confirm-transaction-base.component.js b/ui/app/pages/confirm-transaction-base/confirm-transaction-base.component.js
index 1cbe5951d..3c4e6dcac 100644
--- a/ui/app/pages/confirm-transaction-base/confirm-transaction-base.component.js
+++ b/ui/app/pages/confirm-transaction-base/confirm-transaction-base.component.js
@@ -18,6 +18,7 @@ import AdvancedGasInputs from '../../components/app/gas-customization/advanced-g
export default class ConfirmTransactionBase extends Component {
static contextTypes = {
t: PropTypes.func,
+ tOrKey: PropTypes.func.isRequired,
metricsEvent: PropTypes.func,
}
@@ -99,15 +100,18 @@ export default class ConfirmTransactionBase extends Component {
submitError: null,
}
- componentDidUpdate () {
+ componentDidUpdate (prevProps) {
const {
transactionStatus,
showTransactionConfirmedModal,
history,
clearConfirmTransaction,
} = this.props
+ const { transactionStatus: prevTxStatus } = prevProps
+ const statusUpdated = transactionStatus !== prevTxStatus
+ const txDroppedOrConfirmed = transactionStatus === DROPPED_STATUS || transactionStatus === CONFIRMED_STATUS
- if (transactionStatus === DROPPED_STATUS || transactionStatus === CONFIRMED_STATUS) {
+ if (statusUpdated && txDroppedOrConfirmed) {
showTransactionConfirmedModal({
onSubmit: () => {
clearConfirmTransaction()
@@ -543,7 +547,8 @@ export default class ConfirmTransactionBase extends Component {
toName={toName}
toAddress={toAddress}
showEdit={onEdit && !isTxReprice}
- action={this.context.t(actionKey) || getMethodName(name) || this.context.t('contractInteraction')}
+ // In the event that the key is falsy (and inherently invalid), use a fallback string
+ action={this.context.tOrKey(actionKey) || getMethodName(name) || this.context.t('contractInteraction')}
title={title}
titleComponent={this.renderTitleComponent()}
subtitle={subtitle}
diff --git a/ui/app/pages/create-account/connect-hardware/account-list.js b/ui/app/pages/create-account/connect-hardware/account-list.js
index a521c7eaf..247c27a5d 100644
--- a/ui/app/pages/create-account/connect-hardware/account-list.js
+++ b/ui/app/pages/create-account/connect-hardware/account-list.js
@@ -6,10 +6,6 @@ const Select = require('react-select').default
import Button from '../../../components/ui/button'
class AccountList extends Component {
- constructor (props, context) {
- super(props)
- }
-
getHdPaths () {
return [
{
diff --git a/ui/app/pages/create-account/connect-hardware/connect-screen.js b/ui/app/pages/create-account/connect-hardware/connect-screen.js
index f5a83e6cf..a3b8ad246 100644
--- a/ui/app/pages/create-account/connect-hardware/connect-screen.js
+++ b/ui/app/pages/create-account/connect-hardware/connect-screen.js
@@ -4,7 +4,7 @@ const h = require('react-hyperscript')
import Button from '../../../components/ui/button'
class ConnectScreen extends Component {
- constructor (props, context) {
+ constructor (props) {
super(props)
this.state = {
selectedDevice: null,
@@ -103,7 +103,7 @@ class ConnectScreen extends Component {
}
- scrollToTutorial = (e) => {
+ scrollToTutorial = () => {
if (this.referenceNode) this.referenceNode.scrollIntoView({behavior: 'smooth'})
}
diff --git a/ui/app/pages/create-account/connect-hardware/index.js b/ui/app/pages/create-account/connect-hardware/index.js
index 1398fa680..5a91a2725 100644
--- a/ui/app/pages/create-account/connect-hardware/index.js
+++ b/ui/app/pages/create-account/connect-hardware/index.js
@@ -12,7 +12,7 @@ const { getPlatform } = require('../../../../../app/scripts/lib/util')
const { PLATFORM_FIREFOX } = require('../../../../../app/scripts/lib/enums')
class ConnectHardwareForm extends Component {
- constructor (props, context) {
+ constructor (props) {
super(props)
this.state = {
error: null,
@@ -101,7 +101,7 @@ class ConnectHardwareForm extends Component {
const newState = { unlocked: true, device, error: null }
// Default to the first account
if (this.state.selectedAccount === null) {
- accounts.forEach((a, i) => {
+ accounts.forEach((a) => {
if (a.address.toLowerCase() === this.props.address) {
newState.selectedAccount = a.index.toString()
}
diff --git a/ui/app/pages/create-account/import-account/seed.js b/ui/app/pages/create-account/import-account/seed.js
index d98909baa..73332f926 100644
--- a/ui/app/pages/create-account/import-account/seed.js
+++ b/ui/app/pages/create-account/import-account/seed.js
@@ -11,7 +11,7 @@ SeedImportSubview.contextTypes = {
module.exports = connect(mapStateToProps)(SeedImportSubview)
-function mapStateToProps (state) {
+function mapStateToProps () {
return {}
}
diff --git a/ui/app/pages/first-time-flow/metametrics-opt-in/metametrics-opt-in.component.js b/ui/app/pages/first-time-flow/metametrics-opt-in/metametrics-opt-in.component.js
index ffaff9acf..6b9d06cf9 100644
--- a/ui/app/pages/first-time-flow/metametrics-opt-in/metametrics-opt-in.component.js
+++ b/ui/app/pages/first-time-flow/metametrics-opt-in/metametrics-opt-in.component.js
@@ -119,7 +119,7 @@ export default class MetaMetricsOptIn extends Component {
hideCancel={false}
onSubmit={() => {
setParticipateInMetaMetrics(true)
- .then(([participateStatus, metaMetricsId]) => {
+ .then(([_, metaMetricsId]) => {
const promise = participateInMetaMetrics !== true
? metricsEvent({
eventOpts: {
diff --git a/ui/app/pages/first-time-flow/seed-phrase/confirm-seed-phrase/confirm-seed-phrase.component.js b/ui/app/pages/first-time-flow/seed-phrase/confirm-seed-phrase/confirm-seed-phrase.component.js
index f3bfc3171..4cfc38fdf 100644
--- a/ui/app/pages/first-time-flow/seed-phrase/confirm-seed-phrase/confirm-seed-phrase.component.js
+++ b/ui/app/pages/first-time-flow/seed-phrase/confirm-seed-phrase/confirm-seed-phrase.component.js
@@ -8,7 +8,9 @@ import {
INITIALIZE_SEED_PHRASE_ROUTE,
} from '../../../../helpers/constants/routes'
import { exportAsFile } from '../../../../helpers/utils/util'
-import { selectSeedWord, deselectSeedWord } from './confirm-seed-phrase.state'
+import DraggableSeed from './draggable-seed.component'
+
+const EMPTY_SEEDS = Array(12).fill(null)
export default class ConfirmSeedPhrase extends PureComponent {
static contextTypes = {
@@ -27,10 +29,32 @@ export default class ConfirmSeedPhrase extends PureComponent {
}
state = {
- selectedSeedWords: [],
+ selectedSeedIndices: [],
shuffledSeedWords: [],
- // Hash of shuffledSeedWords index {Number} to selectedSeedWords index {Number}
- selectedSeedWordsHash: {},
+ pendingSeedIndices: [],
+ draggingSeedIndex: -1,
+ hoveringIndex: -1,
+ isDragging: false,
+ }
+
+ shouldComponentUpdate (nextProps, nextState) {
+ const { seedPhrase } = this.props
+ const {
+ selectedSeedIndices,
+ shuffledSeedWords,
+ pendingSeedIndices,
+ draggingSeedIndex,
+ hoveringIndex,
+ isDragging,
+ } = this.state
+
+ return seedPhrase !== nextProps.seedPhrase ||
+ draggingSeedIndex !== nextState.draggingSeedIndex ||
+ isDragging !== nextState.isDragging ||
+ hoveringIndex !== nextState.hoveringIndex ||
+ selectedSeedIndices.join(' ') !== nextState.selectedSeedIndices.join(' ') ||
+ shuffledSeedWords.join(' ') !== nextState.shuffledSeedWords.join(' ') ||
+ pendingSeedIndices.join(' ') !== nextState.pendingSeedIndices.join(' ')
}
componentDidMount () {
@@ -39,6 +63,26 @@ export default class ConfirmSeedPhrase extends PureComponent {
this.setState({ shuffledSeedWords })
}
+ setDraggingSeedIndex = draggingSeedIndex => this.setState({ draggingSeedIndex })
+
+ setHoveringIndex = hoveringIndex => this.setState({ hoveringIndex })
+
+ onDrop = targetIndex => {
+ const {
+ selectedSeedIndices,
+ draggingSeedIndex,
+ } = this.state
+
+ const indices = insert(selectedSeedIndices, draggingSeedIndex, targetIndex, true)
+
+ this.setState({
+ selectedSeedIndices: indices,
+ pendingSeedIndices: indices,
+ draggingSeedIndex: -1,
+ hoveringIndex: -1,
+ })
+ }
+
handleExport = () => {
exportAsFile('MetaMask Secret Backup Phrase', this.props.seedPhrase, 'text/plain')
}
@@ -64,24 +108,35 @@ export default class ConfirmSeedPhrase extends PureComponent {
}
}
- handleSelectSeedWord = (word, shuffledIndex) => {
- this.setState(selectSeedWord(word, shuffledIndex))
+ handleSelectSeedWord = (shuffledIndex) => {
+ this.setState({
+ selectedSeedIndices: [...this.state.selectedSeedIndices, shuffledIndex],
+ pendingSeedIndices: [...this.state.pendingSeedIndices, shuffledIndex],
+ })
}
handleDeselectSeedWord = shuffledIndex => {
- this.setState(deselectSeedWord(shuffledIndex))
+ this.setState({
+ selectedSeedIndices: this.state.selectedSeedIndices.filter(i => shuffledIndex !== i),
+ pendingSeedIndices: this.state.pendingSeedIndices.filter(i => shuffledIndex !== i),
+ })
}
isValid () {
const { seedPhrase } = this.props
- const { selectedSeedWords } = this.state
+ const { selectedSeedIndices, shuffledSeedWords } = this.state
+ const selectedSeedWords = selectedSeedIndices.map(i => shuffledSeedWords[i])
return seedPhrase === selectedSeedWords.join(' ')
}
render () {
const { t } = this.context
const { history } = this.props
- const { selectedSeedWords, shuffledSeedWords, selectedSeedWordsHash } = this.state
+ const {
+ selectedSeedIndices,
+ shuffledSeedWords,
+ draggingSeedIndex,
+ } = this.state
return (
<div className="confirm-seed-phrase">
@@ -102,41 +157,39 @@ export default class ConfirmSeedPhrase extends PureComponent {
<div className="first-time-flow__text-block">
{ t('selectEachPhrase') }
</div>
- <div className="confirm-seed-phrase__selected-seed-words">
- {
- selectedSeedWords.map((word, index) => (
- <div
- key={index}
- className="confirm-seed-phrase__seed-word"
- >
- { word }
- </div>
- ))
- }
+ <div
+ className={classnames('confirm-seed-phrase__selected-seed-words', {
+ 'confirm-seed-phrase__selected-seed-words--dragging': draggingSeedIndex > -1,
+ })}
+ >
+ { this.renderPendingSeeds() }
+ { this.renderSelectedSeeds() }
</div>
<div className="confirm-seed-phrase__shuffled-seed-words">
{
shuffledSeedWords.map((word, index) => {
- const isSelected = index in selectedSeedWordsHash
+ const isSelected = selectedSeedIndices.includes(index)
return (
- <div
+ <DraggableSeed
key={index}
- className={classnames(
- 'confirm-seed-phrase__seed-word',
- 'confirm-seed-phrase__seed-word--shuffled',
- { 'confirm-seed-phrase__seed-word--selected': isSelected }
- )}
+ seedIndex={index}
+ index={index}
+ draggingSeedIndex={this.state.draggingSeedIndex}
+ setDraggingSeedIndex={this.setDraggingSeedIndex}
+ setHoveringIndex={this.setHoveringIndex}
+ onDrop={this.onDrop}
+ className="confirm-seed-phrase__seed-word--shuffled"
+ selected={isSelected}
onClick={() => {
if (!isSelected) {
- this.handleSelectSeedWord(word, index)
+ this.handleSelectSeedWord(index)
} else {
this.handleDeselectSeedWord(index)
}
}}
- >
- { word }
- </div>
+ word={word}
+ />
)
})
}
@@ -152,4 +205,80 @@ export default class ConfirmSeedPhrase extends PureComponent {
</div>
)
}
+
+ renderSelectedSeeds () {
+ const { shuffledSeedWords, selectedSeedIndices, draggingSeedIndex } = this.state
+ return EMPTY_SEEDS.map((_, index) => {
+ const seedIndex = selectedSeedIndices[index]
+ const word = shuffledSeedWords[seedIndex]
+
+ return (
+ <DraggableSeed
+ key={`selected-${seedIndex}-${index}`}
+ className="confirm-seed-phrase__selected-seed-words__selected-seed"
+ index={index}
+ seedIndex={seedIndex}
+ word={word}
+ draggingSeedIndex={draggingSeedIndex}
+ setDraggingSeedIndex={this.setDraggingSeedIndex}
+ setHoveringIndex={this.setHoveringIndex}
+ onDrop={this.onDrop}
+ draggable
+ />
+ )
+ })
+ }
+
+ renderPendingSeeds () {
+ const {
+ pendingSeedIndices,
+ shuffledSeedWords,
+ draggingSeedIndex,
+ hoveringIndex,
+ } = this.state
+
+ const indices = insert(pendingSeedIndices, draggingSeedIndex, hoveringIndex)
+
+ return EMPTY_SEEDS.map((_, index) => {
+ const seedIndex = indices[index]
+ const word = shuffledSeedWords[seedIndex]
+
+ return (
+ <DraggableSeed
+ key={`pending-${seedIndex}-${index}`}
+ index={index}
+ className={classnames('confirm-seed-phrase__selected-seed-words__pending-seed', {
+ 'confirm-seed-phrase__seed-word--hidden': draggingSeedIndex === seedIndex && index !== hoveringIndex,
+ })}
+ seedIndex={seedIndex}
+ word={word}
+ draggingSeedIndex={draggingSeedIndex}
+ setDraggingSeedIndex={this.setDraggingSeedIndex}
+ setHoveringIndex={this.setHoveringIndex}
+ onDrop={this.onDrop}
+ droppable={!!word}
+ />
+ )
+ })
+ }
+}
+
+function insert (list, value, target, removeOld) {
+ let nextList = [...list]
+
+ if (typeof list[target] === 'number') {
+ nextList = [...list.slice(0, target), value, ...list.slice(target)]
+ }
+
+ if (removeOld) {
+ nextList = nextList.filter((seed, i) => {
+ return seed !== value || i === target
+ })
+ }
+
+ if (nextList.length > 12) {
+ nextList.pop()
+ }
+
+ return nextList
}
diff --git a/ui/app/pages/first-time-flow/seed-phrase/confirm-seed-phrase/confirm-seed-phrase.state.js b/ui/app/pages/first-time-flow/seed-phrase/confirm-seed-phrase/confirm-seed-phrase.state.js
deleted file mode 100644
index f2476fc5c..000000000
--- a/ui/app/pages/first-time-flow/seed-phrase/confirm-seed-phrase/confirm-seed-phrase.state.js
+++ /dev/null
@@ -1,41 +0,0 @@
-export function selectSeedWord (word, shuffledIndex) {
- return function update (state) {
- const { selectedSeedWords, selectedSeedWordsHash } = state
- const nextSelectedIndex = selectedSeedWords.length
-
- return {
- selectedSeedWords: [ ...selectedSeedWords, word ],
- selectedSeedWordsHash: { ...selectedSeedWordsHash, [shuffledIndex]: nextSelectedIndex },
- }
- }
-}
-
-export function deselectSeedWord (shuffledIndex) {
- return function update (state) {
- const {
- selectedSeedWords: prevSelectedSeedWords,
- selectedSeedWordsHash: prevSelectedSeedWordsHash,
- } = state
-
- const selectedSeedWords = [...prevSelectedSeedWords]
- const indexToRemove = prevSelectedSeedWordsHash[shuffledIndex]
- selectedSeedWords.splice(indexToRemove, 1)
- const selectedSeedWordsHash = Object.keys(prevSelectedSeedWordsHash).reduce((acc, index) => {
- const output = { ...acc }
- const selectedSeedWordIndex = prevSelectedSeedWordsHash[index]
-
- if (selectedSeedWordIndex < indexToRemove) {
- output[index] = selectedSeedWordIndex
- } else if (selectedSeedWordIndex > indexToRemove) {
- output[index] = selectedSeedWordIndex - 1
- }
-
- return output
- }, {})
-
- return {
- selectedSeedWords,
- selectedSeedWordsHash,
- }
- }
-}
diff --git a/ui/app/pages/first-time-flow/seed-phrase/confirm-seed-phrase/draggable-seed.component.js b/ui/app/pages/first-time-flow/seed-phrase/confirm-seed-phrase/draggable-seed.component.js
new file mode 100644
index 000000000..cdb881921
--- /dev/null
+++ b/ui/app/pages/first-time-flow/seed-phrase/confirm-seed-phrase/draggable-seed.component.js
@@ -0,0 +1,126 @@
+import React, { Component } from 'react'
+import PropTypes from 'prop-types'
+import classnames from 'classnames'
+import { DragSource, DropTarget } from 'react-dnd'
+
+class DraggableSeed extends Component {
+
+ static propTypes = {
+ // React DnD Props
+ connectDragSource: PropTypes.func.isRequired,
+ connectDropTarget: PropTypes.func.isRequired,
+ isDragging: PropTypes.bool,
+ isOver: PropTypes.bool,
+ canDrop: PropTypes.bool,
+ // Own Props
+ onClick: PropTypes.func.isRequired,
+ setHoveringIndex: PropTypes.func.isRequired,
+ index: PropTypes.number,
+ draggingSeedIndex: PropTypes.number,
+ word: PropTypes.string,
+ className: PropTypes.string,
+ selected: PropTypes.bool,
+ droppable: PropTypes.bool,
+ }
+
+ static defaultProps = {
+ className: '',
+ onClick () {},
+ }
+
+ componentWillReceiveProps (nextProps) {
+ const { isOver, setHoveringIndex } = this.props
+ if (isOver && !nextProps.isOver) {
+ setHoveringIndex(-1)
+ }
+ }
+
+ render () {
+ const {
+ connectDragSource,
+ connectDropTarget,
+ isDragging,
+ index,
+ word,
+ selected,
+ className,
+ onClick,
+ isOver,
+ canDrop,
+ } = this.props
+
+ return connectDropTarget(connectDragSource(
+ <div
+ key={index}
+ className={classnames('btn-secondary confirm-seed-phrase__seed-word', className, {
+ 'confirm-seed-phrase__seed-word--selected btn-primary': selected,
+ 'confirm-seed-phrase__seed-word--dragging': isDragging,
+ 'confirm-seed-phrase__seed-word--empty': !word,
+ 'confirm-seed-phrase__seed-word--active-drop': !isOver && canDrop,
+ 'confirm-seed-phrase__seed-word--drop-hover': isOver && canDrop,
+ })}
+ onClick={onClick}
+ >
+ { word }
+ </div>
+ ))
+ }
+}
+
+const SEEDWORD = 'SEEDWORD'
+
+const seedSource = {
+ beginDrag (props) {
+ setTimeout(() => props.setDraggingSeedIndex(props.seedIndex), 0)
+ return {
+ seedIndex: props.seedIndex,
+ word: props.word,
+ }
+ },
+ canDrag (props) {
+ return props.draggable
+ },
+ endDrag (props, monitor) {
+ const dropTarget = monitor.getDropResult()
+
+ if (!dropTarget) {
+ setTimeout(() => props.setDraggingSeedIndex(-1), 0)
+ return
+ }
+
+ props.onDrop(dropTarget.targetIndex)
+ },
+}
+
+const seedTarget = {
+ drop (props) {
+ return {
+ targetIndex: props.index,
+ }
+ },
+ canDrop (props) {
+ return props.droppable
+ },
+ hover (props) {
+ props.setHoveringIndex(props.index)
+ },
+}
+
+const collectDrag = (connect, monitor) => {
+ return {
+ connectDragSource: connect.dragSource(),
+ isDragging: monitor.isDragging(),
+ }
+}
+
+const collectDrop = (connect, monitor) => {
+ return {
+ connectDropTarget: connect.dropTarget(),
+ isOver: monitor.isOver(),
+ canDrop: monitor.canDrop(),
+ }
+}
+
+export default DropTarget(SEEDWORD, seedTarget, collectDrop)(DragSource(SEEDWORD, seedSource, collectDrag)(DraggableSeed))
+
+
diff --git a/ui/app/pages/first-time-flow/seed-phrase/confirm-seed-phrase/index.scss b/ui/app/pages/first-time-flow/seed-phrase/confirm-seed-phrase/index.scss
index 93137618c..f025a503f 100644
--- a/ui/app/pages/first-time-flow/seed-phrase/confirm-seed-phrase/index.scss
+++ b/ui/app/pages/first-time-flow/seed-phrase/confirm-seed-phrase/index.scss
@@ -3,37 +3,58 @@
margin-bottom: 12px;
}
- &__selected-seed-words {
- min-height: 190px;
- max-width: 496px;
- border: 1px solid #CDCDCD;
- border-radius: 6px;
- background-color: $white;
- margin: 24px 0 36px;
- padding: 12px;
- }
-
&__shuffled-seed-words {
- max-width: 496px;
+ max-width: 575px;
}
&__seed-word {
- display: inline-block;
- color: #5B5D67;
- background-color: #E7E7E7;
+ display: inline-flex;
+ flex-flow: row nowrap;
+ align-items: center;
+ justify-content: center;
padding: 8px 18px;
- min-width: 64px;
+ width: 128px;
+ height: 41px;
margin: 4px;
text-align: center;
+ border-radius: 4px;
+ cursor: move;
+
+ &--shuffled {
+ cursor: pointer;
+ margin: 6px;
+ }
&--selected {
- background-color: #85D1CC;
color: $white;
}
- &--shuffled {
- cursor: pointer;
- margin: 6px;
+ &--dragging {
+ margin: 0;
+ }
+
+ &--empty {
+ background-color: transparent;
+ border-color: transparent;
+ cursor: default;
+
+ &:hover,
+ &:active {
+ background-color: transparent;
+ border-color: transparent;
+ cursor: default;
+ box-shadow: none !important;
+ }
+ }
+
+ &--hidden {
+ display: none !important;
+ }
+
+ &--drop-hover {
+ background-color: transparent;
+ border-color: transparent;
+ color: transparent;
}
@media screen and (max-width: 575px) {
@@ -42,7 +63,37 @@
}
}
- button {
- margin-top: 0xp;
+ &__selected-seed-words {
+ display: flex;
+ flex-flow: row wrap;
+ min-height: 161px;
+ max-width: 575px;
+ border: 1px solid #CDCDCD;
+ border-radius: 6px;
+ background-color: $white;
+ margin: 24px 0 36px;
+ padding: 12px;
+
+ &__pending-seed {
+ display: none;
+ }
+
+ &__selected-seed {
+ display: inline-flex;
+
+ &:hover {
+ box-shadow: 0 2px 4px rgba(0, 0, 0, 0.25);
+ }
+ }
+
+ &--dragging {
+ .confirm-seed-phrase__selected-seed-words__pending-seed {
+ display: inline-flex;
+ }
+
+ .confirm-seed-phrase__selected-seed-words__selected-seed {
+ display: none;
+ }
+ }
}
}
diff --git a/ui/app/pages/first-time-flow/seed-phrase/seed-phrase.component.js b/ui/app/pages/first-time-flow/seed-phrase/seed-phrase.component.js
index 9a9f84049..0b19af18c 100644
--- a/ui/app/pages/first-time-flow/seed-phrase/seed-phrase.component.js
+++ b/ui/app/pages/first-time-flow/seed-phrase/seed-phrase.component.js
@@ -8,6 +8,8 @@ import {
INITIALIZE_CONFIRM_SEED_PHRASE_ROUTE,
DEFAULT_ROUTE,
} from '../../../helpers/constants/routes'
+import HTML5Backend from 'react-dnd-html5-backend'
+import {DragDropContextProvider} from 'react-dnd'
export default class SeedPhrase extends PureComponent {
static propTypes = {
@@ -28,43 +30,45 @@ export default class SeedPhrase extends PureComponent {
const { seedPhrase } = this.props
return (
- <div className="first-time-flow__wrapper">
- <div className="app-header__logo-container">
- <img
- className="app-header__metafox-logo app-header__metafox-logo--horizontal"
- src="/images/logo/metamask-logo-horizontal.svg"
- height={30}
- />
- <img
- className="app-header__metafox-logo app-header__metafox-logo--icon"
- src="/images/logo/metamask-fox.svg"
- height={42}
- width={42}
- />
+ <DragDropContextProvider backend={HTML5Backend}>
+ <div className="first-time-flow__wrapper">
+ <div className="app-header__logo-container">
+ <img
+ className="app-header__metafox-logo app-header__metafox-logo--horizontal"
+ src="/images/logo/metamask-logo-horizontal.svg"
+ height={30}
+ />
+ <img
+ className="app-header__metafox-logo app-header__metafox-logo--icon"
+ src="/images/logo/metamask-fox.svg"
+ height={42}
+ width={42}
+ />
+ </div>
+ <Switch>
+ <Route
+ exact
+ path={INITIALIZE_CONFIRM_SEED_PHRASE_ROUTE}
+ render={props => (
+ <ConfirmSeedPhrase
+ { ...props }
+ seedPhrase={seedPhrase}
+ />
+ )}
+ />
+ <Route
+ exact
+ path={INITIALIZE_SEED_PHRASE_ROUTE}
+ render={props => (
+ <RevealSeedPhrase
+ { ...props }
+ seedPhrase={seedPhrase}
+ />
+ )}
+ />
+ </Switch>
</div>
- <Switch>
- <Route
- exact
- path={INITIALIZE_CONFIRM_SEED_PHRASE_ROUTE}
- render={props => (
- <ConfirmSeedPhrase
- { ...props }
- seedPhrase={seedPhrase}
- />
- )}
- />
- <Route
- exact
- path={INITIALIZE_SEED_PHRASE_ROUTE}
- render={props => (
- <RevealSeedPhrase
- { ...props }
- seedPhrase={seedPhrase}
- />
- )}
- />
- </Switch>
- </div>
+ </DragDropContextProvider>
)
}
}
diff --git a/ui/app/pages/first-time-flow/seed-phrase/tests/confirm-seed-phrase-component.test.js b/ui/app/pages/first-time-flow/seed-phrase/tests/confirm-seed-phrase-component.test.js
new file mode 100644
index 000000000..8339a6f6f
--- /dev/null
+++ b/ui/app/pages/first-time-flow/seed-phrase/tests/confirm-seed-phrase-component.test.js
@@ -0,0 +1,169 @@
+import React from 'react'
+import assert from 'assert'
+import { shallow } from 'enzyme'
+import sinon from 'sinon'
+import ConfirmSeedPhrase from '../confirm-seed-phrase/confirm-seed-phrase.component'
+
+function shallowRender (props = {}, context = {}) {
+ return shallow(
+ <ConfirmSeedPhrase {...props} />,
+ {
+ context: {
+ t: str => str + '_t',
+ ...context,
+ },
+ }
+ )
+}
+
+describe('ConfirmSeedPhrase Component', () => {
+ it('should render correctly', () => {
+ const root = shallowRender({
+ seedPhrase: '鼠 牛 虎 兔 龍 蛇 馬 羊 猴 雞 狗 豬',
+ })
+
+ assert.equal(
+ root.find('.confirm-seed-phrase__seed-word--shuffled').length,
+ 12,
+ 'should render 12 seed phrases'
+ )
+ })
+
+ it('should add/remove selected on click', () => {
+ const metricsEventSpy = sinon.spy()
+ const pushSpy = sinon.spy()
+ const root = shallowRender(
+ {
+ seedPhrase: '鼠 牛 虎 兔 龍 蛇 馬 羊 猴 雞 狗 豬',
+ history: { push: pushSpy },
+ },
+ {
+ metricsEvent: metricsEventSpy,
+ }
+ )
+
+ const seeds = root.find('.confirm-seed-phrase__seed-word--shuffled')
+
+ // Click on 3 seeds to add to selected
+ seeds.at(0).simulate('click')
+ seeds.at(1).simulate('click')
+ seeds.at(2).simulate('click')
+
+ assert.deepEqual(
+ root.state().selectedSeedIndices,
+ [0, 1, 2],
+ 'should add seed phrase to selected on click',
+ )
+
+ // Click on a selected seed to remove
+ root.state()
+ root.update()
+ root.state()
+ root.find('.confirm-seed-phrase__seed-word--shuffled').at(1).simulate('click')
+ assert.deepEqual(
+ root.state().selectedSeedIndices,
+ [0, 2],
+ 'should remove seed phrase from selected when click again',
+ )
+ })
+
+ it('should render correctly on hover', () => {
+ const metricsEventSpy = sinon.spy()
+ const pushSpy = sinon.spy()
+ const root = shallowRender(
+ {
+ seedPhrase: '鼠 牛 虎 兔 龍 蛇 馬 羊 猴 雞 狗 豬',
+ history: { push: pushSpy },
+ },
+ {
+ metricsEvent: metricsEventSpy,
+ }
+ )
+
+ const seeds = root.find('.confirm-seed-phrase__seed-word--shuffled')
+
+ // Click on 3 seeds to add to selected
+ seeds.at(0).simulate('click')
+ seeds.at(1).simulate('click')
+ seeds.at(2).simulate('click')
+
+ // Dragging Seed # 2 to 0 placeth
+ root.instance().setDraggingSeedIndex(2)
+ root.instance().setHoveringIndex(0)
+
+ root.update()
+
+ const pendingSeeds = root.find('.confirm-seed-phrase__selected-seed-words__pending-seed')
+
+ assert.equal(pendingSeeds.at(0).props().seedIndex, 2)
+ assert.equal(pendingSeeds.at(1).props().seedIndex, 0)
+ assert.equal(pendingSeeds.at(2).props().seedIndex, 1)
+ })
+
+ it('should insert seed in place on drop', () => {
+ const metricsEventSpy = sinon.spy()
+ const pushSpy = sinon.spy()
+ const root = shallowRender(
+ {
+ seedPhrase: '鼠 牛 虎 兔 龍 蛇 馬 羊 猴 雞 狗 豬',
+ history: { push: pushSpy },
+ },
+ {
+ metricsEvent: metricsEventSpy,
+ }
+ )
+
+ const seeds = root.find('.confirm-seed-phrase__seed-word--shuffled')
+
+ // Click on 3 seeds to add to selected
+ seeds.at(0).simulate('click')
+ seeds.at(1).simulate('click')
+ seeds.at(2).simulate('click')
+
+ // Drop Seed # 2 to 0 placeth
+ root.instance().setDraggingSeedIndex(2)
+ root.instance().setHoveringIndex(0)
+ root.instance().onDrop(0)
+
+ root.update()
+
+ assert.deepEqual(root.state().selectedSeedIndices, [2, 0, 1])
+ assert.deepEqual(root.state().pendingSeedIndices, [2, 0, 1])
+ })
+
+ it('should submit correctly', () => {
+ const originalSeed = ['鼠', '牛', '虎', '兔', '龍', '蛇', '馬', '羊', '猴', '雞', '狗', '豬']
+ const metricsEventSpy = sinon.spy()
+ const pushSpy = sinon.spy()
+ const root = shallowRender(
+ {
+ seedPhrase: '鼠 牛 虎 兔 龍 蛇 馬 羊 猴 雞 狗 豬',
+ history: { push: pushSpy },
+ },
+ {
+ metricsEvent: metricsEventSpy,
+ }
+ )
+
+ const shuffled = root.state().shuffledSeedWords
+ const seeds = root.find('.confirm-seed-phrase__seed-word--shuffled')
+
+
+ originalSeed.forEach(seed => {
+ const seedIndex = shuffled.findIndex(s => s === seed)
+ seeds.at(seedIndex).simulate('click')
+ })
+
+ root.update()
+
+ root.find('.first-time-flow__button').simulate('click')
+ assert.deepEqual(metricsEventSpy.args[0][0], {
+ eventOpts: {
+ category: 'Onboarding',
+ action: 'Seed Phrase Setup',
+ name: 'Verify Complete',
+ },
+ })
+ assert.equal(pushSpy.args[0][0], '/initialize/end-of-flow')
+ })
+})
diff --git a/ui/app/pages/home/home.component.js b/ui/app/pages/home/home.component.js
index 29d93a9fa..4d96c3131 100644
--- a/ui/app/pages/home/home.component.js
+++ b/ui/app/pages/home/home.component.js
@@ -23,21 +23,27 @@ export default class Home extends PureComponent {
providerRequests: PropTypes.array,
}
+ componentWillMount () {
+ const {
+ history,
+ unconfirmedTransactionsCount = 0,
+ } = this.props
+
+ if (unconfirmedTransactionsCount > 0) {
+ history.push(CONFIRM_TRANSACTION_ROUTE)
+ }
+ }
+
componentDidMount () {
const {
history,
suggestedTokens = {},
- unconfirmedTransactionsCount = 0,
} = this.props
// suggested new tokens
if (Object.keys(suggestedTokens).length > 0) {
history.push(CONFIRM_ADD_SUGGESTED_TOKEN_ROUTE)
}
-
- if (unconfirmedTransactionsCount > 0) {
- history.push(CONFIRM_TRANSACTION_ROUTE)
- }
}
render () {
@@ -45,6 +51,7 @@ export default class Home extends PureComponent {
forgottenPassword,
seedWords,
providerRequests,
+ history,
} = this.props
// seed words
@@ -69,7 +76,7 @@ export default class Home extends PureComponent {
query="(min-width: 576px)"
render={() => <WalletView />}
/>
- <TransactionView />
+ { !history.location.pathname.match(/^\/confirm-transaction/) ? <TransactionView /> : null }
</div>
</div>
)
diff --git a/ui/app/pages/provider-approval/provider-approval.component.js b/ui/app/pages/provider-approval/provider-approval.component.js
index 1f1d68da7..70d3d0007 100644
--- a/ui/app/pages/provider-approval/provider-approval.component.js
+++ b/ui/app/pages/provider-approval/provider-approval.component.js
@@ -4,9 +4,9 @@ import ProviderPageContainer from '../../components/app/provider-page-container'
export default class ProviderApproval extends Component {
static propTypes = {
- approveProviderRequest: PropTypes.func.isRequired,
+ approveProviderRequestByOrigin: PropTypes.func.isRequired,
+ rejectProviderRequestByOrigin: PropTypes.func.isRequired,
providerRequest: PropTypes.object.isRequired,
- rejectProviderRequest: PropTypes.func.isRequired,
};
static contextTypes = {
@@ -14,13 +14,13 @@ export default class ProviderApproval extends Component {
};
render () {
- const { approveProviderRequest, providerRequest, rejectProviderRequest } = this.props
+ const { approveProviderRequestByOrigin, providerRequest, rejectProviderRequestByOrigin } = this.props
return (
<ProviderPageContainer
- approveProviderRequest={approveProviderRequest}
+ approveProviderRequestByOrigin={approveProviderRequestByOrigin}
+ rejectProviderRequestByOrigin={rejectProviderRequestByOrigin}
origin={providerRequest.origin}
tabID={providerRequest.tabID}
- rejectProviderRequest={rejectProviderRequest}
siteImage={providerRequest.siteImage}
siteTitle={providerRequest.siteTitle}
/>
diff --git a/ui/app/pages/provider-approval/provider-approval.container.js b/ui/app/pages/provider-approval/provider-approval.container.js
index d53c0ae4d..1e167ddb7 100644
--- a/ui/app/pages/provider-approval/provider-approval.container.js
+++ b/ui/app/pages/provider-approval/provider-approval.container.js
@@ -1,11 +1,11 @@
import { connect } from 'react-redux'
import ProviderApproval from './provider-approval.component'
-import { approveProviderRequest, rejectProviderRequest } from '../../store/actions'
+import { approveProviderRequestByOrigin, rejectProviderRequestByOrigin } from '../../store/actions'
function mapDispatchToProps (dispatch) {
return {
- approveProviderRequest: tabID => dispatch(approveProviderRequest(tabID)),
- rejectProviderRequest: tabID => dispatch(rejectProviderRequest(tabID)),
+ approveProviderRequestByOrigin: origin => dispatch(approveProviderRequestByOrigin(origin)),
+ rejectProviderRequestByOrigin: origin => dispatch(rejectProviderRequestByOrigin(origin)),
}
}
diff --git a/ui/app/pages/routes/index.js b/ui/app/pages/routes/index.js
index e38a6d6ce..9eeac2da2 100644
--- a/ui/app/pages/routes/index.js
+++ b/ui/app/pages/routes/index.js
@@ -5,7 +5,8 @@ import { Route, Switch, withRouter, matchPath } from 'react-router-dom'
import { compose } from 'recompose'
import actions from '../../store/actions'
import log from 'loglevel'
-import { getMetaMaskAccounts, getNetworkIdentifier } from '../../selectors/selectors'
+import IdleTimer from 'react-idle-timer'
+import {getMetaMaskAccounts, getNetworkIdentifier, preferencesSelector} from '../../selectors/selectors'
// init
import FirstTimeFlow from '../first-time-flow'
@@ -98,7 +99,9 @@ class Routes extends Component {
}
renderRoutes () {
- return (
+ const { autoLogoutTimeLimit, setLastActiveTime } = this.props
+
+ const routes = (
<Switch>
<Route path={LOCK_ROUTE} component={Lock} exact />
<Route path={INITIALIZE_ROUTE} component={FirstTimeFlow} />
@@ -116,6 +119,16 @@ class Routes extends Component {
<Authenticated path={DEFAULT_ROUTE} component={Home} exact />
</Switch>
)
+
+ if (autoLogoutTimeLimit > 0) {
+ return (
+ <IdleTimer onAction={setLastActiveTime} throttle={1000}>
+ {routes}
+ </IdleTimer>
+ )
+ }
+
+ return routes
}
onInitializationUnlockPage () {
@@ -322,6 +335,7 @@ Routes.propTypes = {
networkDropdownOpen: PropTypes.bool,
showNetworkDropdown: PropTypes.func,
hideNetworkDropdown: PropTypes.func,
+ setLastActiveTime: PropTypes.func,
history: PropTypes.object,
location: PropTypes.object,
dispatch: PropTypes.func,
@@ -344,6 +358,7 @@ Routes.propTypes = {
t: PropTypes.func,
providerId: PropTypes.string,
providerRequests: PropTypes.array,
+ autoLogoutTimeLimit: PropTypes.number,
}
function mapStateToProps (state) {
@@ -358,6 +373,7 @@ function mapStateToProps (state) {
} = appState
const accounts = getMetaMaskAccounts(state)
+ const { autoLogoutTimeLimit = 0 } = preferencesSelector(state)
const {
identities,
@@ -409,6 +425,7 @@ function mapStateToProps (state) {
Qr: state.appState.Qr,
welcomeScreenSeen: state.metamask.welcomeScreenSeen,
providerId: getNetworkIdentifier(state),
+ autoLogoutTimeLimit,
// state needed to get account dropdown temporarily rendering from app bar
identities,
@@ -418,7 +435,7 @@ function mapStateToProps (state) {
}
}
-function mapDispatchToProps (dispatch, ownProps) {
+function mapDispatchToProps (dispatch) {
return {
dispatch,
hideSidebar: () => dispatch(actions.hideSidebar()),
@@ -427,6 +444,7 @@ function mapDispatchToProps (dispatch, ownProps) {
setCurrentCurrencyToUSD: () => dispatch(actions.setCurrentCurrency('usd')),
toggleAccountMenu: () => dispatch(actions.toggleAccountMenu()),
setMouseUserState: (isMouseUser) => dispatch(actions.setMouseUserState(isMouseUser)),
+ setLastActiveTime: () => dispatch(actions.setLastActiveTime()),
}
}
diff --git a/ui/app/pages/send/account-list-item/tests/account-list-item-container.test.js b/ui/app/pages/send/account-list-item/tests/account-list-item-container.test.js
index 33f932daf..1580fd497 100644
--- a/ui/app/pages/send/account-list-item/tests/account-list-item-container.test.js
+++ b/ui/app/pages/send/account-list-item/tests/account-list-item-container.test.js
@@ -5,7 +5,7 @@ let mapStateToProps
proxyquire('../account-list-item.container.js', {
'react-redux': {
- connect: (ms, md) => {
+ connect: (ms) => {
mapStateToProps = ms
return () => ({})
},
diff --git a/ui/app/pages/send/send-content/send-amount-row/amount-max-button/amount-max-button.component.js b/ui/app/pages/send/send-content/send-amount-row/amount-max-button/amount-max-button.component.js
index f17137c1e..e256d1442 100644
--- a/ui/app/pages/send/send-content/send-amount-row/amount-max-button/amount-max-button.component.js
+++ b/ui/app/pages/send/send-content/send-amount-row/amount-max-button/amount-max-button.component.js
@@ -15,6 +15,7 @@ export default class AmountMaxButton extends Component {
static contextTypes = {
t: PropTypes.func,
+ metricsEvent: PropTypes.func,
}
setMaxAmount () {
@@ -35,11 +36,15 @@ export default class AmountMaxButton extends Component {
}
onMaxClick = (event) => {
- const { setMaxModeTo, selectedToken } = this.props
+ const { setMaxModeTo } = this.props
+ const { metricsEvent } = this.context
- fetch('https://chromeextensionmm.innocraft.cloud/piwik.php?idsite=1&rec=1&e_c=send&e_a=amountMax&e_n=' + (selectedToken ? 'token' : 'eth'), {
- 'headers': {},
- 'method': 'GET',
+ metricsEvent({
+ eventOpts: {
+ category: 'Transactions',
+ action: 'Edit Screen',
+ name: 'Clicked "Amount Max"',
+ },
})
event.preventDefault()
diff --git a/ui/app/pages/send/send-content/send-amount-row/amount-max-button/tests/amount-max-button-component.test.js b/ui/app/pages/send/send-content/send-amount-row/amount-max-button/tests/amount-max-button-component.test.js
index b04d3897f..a6cb29d4c 100644
--- a/ui/app/pages/send/send-content/send-amount-row/amount-max-button/tests/amount-max-button-component.test.js
+++ b/ui/app/pages/send/send-content/send-amount-row/amount-max-button/tests/amount-max-button-component.test.js
@@ -26,7 +26,12 @@ describe('AmountMaxButton Component', function () {
setAmountToMax={propsMethodSpies.setAmountToMax}
setMaxModeTo={propsMethodSpies.setMaxModeTo}
tokenBalance={'mockTokenBalance'}
- />, { context: { t: str => str + '_t' } })
+ />, {
+ context: {
+ t: str => str + '_t',
+ metricsEvent: () => {},
+ },
+ })
instance = wrapper.instance()
})
diff --git a/ui/app/pages/send/send-content/send-row-wrapper/send-row-error-message/tests/send-row-error-message-container.test.js b/ui/app/pages/send/send-content/send-row-wrapper/send-row-error-message/tests/send-row-error-message-container.test.js
index eecff165d..2013e3200 100644
--- a/ui/app/pages/send/send-content/send-row-wrapper/send-row-error-message/tests/send-row-error-message-container.test.js
+++ b/ui/app/pages/send/send-content/send-row-wrapper/send-row-error-message/tests/send-row-error-message-container.test.js
@@ -5,7 +5,7 @@ let mapStateToProps
proxyquire('../send-row-error-message.container.js', {
'react-redux': {
- connect: (ms, md) => {
+ connect: (ms) => {
mapStateToProps = ms
return () => ({})
},
diff --git a/ui/app/pages/send/send-content/send-row-wrapper/send-row-warning-message/tests/send-row-warning-message-container.test.js b/ui/app/pages/send/send-content/send-row-wrapper/send-row-warning-message/tests/send-row-warning-message-container.test.js
index 225bf056c..6c0739f0e 100644
--- a/ui/app/pages/send/send-content/send-row-wrapper/send-row-warning-message/tests/send-row-warning-message-container.test.js
+++ b/ui/app/pages/send/send-content/send-row-wrapper/send-row-warning-message/tests/send-row-warning-message-container.test.js
@@ -5,7 +5,7 @@ let mapStateToProps
proxyquire('../send-row-warning-message.container.js', {
'react-redux': {
- connect: (ms, md) => {
+ connect: (ms) => {
mapStateToProps = ms
return () => ({})
},
diff --git a/ui/app/pages/send/send-content/send-to-row/send-to-row.utils.js b/ui/app/pages/send/send-content/send-to-row/send-to-row.utils.js
index d0a43f086..b3b0d2da3 100644
--- a/ui/app/pages/send/send-content/send-to-row/send-to-row.utils.js
+++ b/ui/app/pages/send/send-content/send-to-row/send-to-row.utils.js
@@ -10,16 +10,15 @@ import { checkExistingAddresses } from '../../../add-token/util'
const ethUtil = require('ethereumjs-util')
const contractMap = require('eth-contract-metadata')
-function getToErrorObject (to, toError = null, hasHexData = false, tokens = [], selectedToken = null, network) {
+function getToErrorObject (to, toError = null, hasHexData = false, _, __, network) {
if (!to) {
if (!hasHexData) {
toError = REQUIRED_ERROR
}
} else if (!isValidAddress(to, network) && !toError) {
toError = isEthNetwork(network) ? INVALID_RECIPIENT_ADDRESS_ERROR : INVALID_RECIPIENT_ADDRESS_NOT_ETH_NETWORK_ERROR
- } else if (selectedToken && (ethUtil.toChecksumAddress(to) in contractMap || checkExistingAddresses(to, tokens))) {
- toError = KNOWN_RECIPIENT_ADDRESS_ERROR
}
+
return { to: toError }
}
diff --git a/ui/app/pages/send/send-content/send-to-row/tests/send-to-row-utils.test.js b/ui/app/pages/send/send-content/send-to-row/tests/send-to-row-utils.test.js
index f29f5efec..f8a6dd96f 100644
--- a/ui/app/pages/send/send-content/send-to-row/tests/send-to-row-utils.test.js
+++ b/ui/app/pages/send/send-content/send-to-row/tests/send-to-row-utils.test.js
@@ -55,9 +55,9 @@ describe('send-to-row utils', () => {
})
})
- it('should return a known address recipient if to is truthy but part of state tokens', () => {
+ it('should return null if to is truthy but part of state tokens', () => {
assert.deepEqual(getToErrorObject('0xabc123', undefined, false, [{'address': '0xabc123'}], {'address': '0xabc123'}), {
- to: KNOWN_RECIPIENT_ADDRESS_ERROR,
+ to: null,
})
})
@@ -67,14 +67,14 @@ describe('send-to-row utils', () => {
})
})
- it('should return a known address recipient if to is truthy but part of contract metadata', () => {
+ it('should return null if to is truthy but part of contract metadata', () => {
assert.deepEqual(getToErrorObject('0x89d24a6b4ccb1b6faa2625fe562bdd9a23260359', undefined, false, [{'address': '0xabc123'}], {'address': '0xabc123'}), {
- to: KNOWN_RECIPIENT_ADDRESS_ERROR,
+ to: null,
})
})
it('should null if to is truthy part of contract metadata but selectedToken falsy', () => {
assert.deepEqual(getToErrorObject('0x89d24a6b4ccb1b6faa2625fe562bdd9a23260359', undefined, false, [{'address': '0xabc123'}], {'address': '0xabc123'}), {
- to: KNOWN_RECIPIENT_ADDRESS_ERROR,
+ to: null,
})
})
})
diff --git a/ui/app/pages/send/tests/send-container.test.js b/ui/app/pages/send/tests/send-container.test.js
index b3e202030..131c42f59 100644
--- a/ui/app/pages/send/tests/send-container.test.js
+++ b/ui/app/pages/send/tests/send-container.test.js
@@ -24,7 +24,7 @@ proxyquire('../send.container.js', {
},
},
'react-router-dom': { withRouter: () => {} },
- 'recompose': { compose: (arg1, arg2) => () => arg2() },
+ 'recompose': { compose: (_, arg2) => () => arg2() },
'./send.selectors': {
getAmountConversionRate: (s) => `mockAmountConversionRate:${s}`,
getBlockGasLimit: (s) => `mockBlockGasLimit:${s}`,
diff --git a/ui/app/pages/send/tests/send-utils.test.js b/ui/app/pages/send/tests/send-utils.test.js
index b19535b9e..bf9cba14a 100644
--- a/ui/app/pages/send/tests/send-utils.test.js
+++ b/ui/app/pages/send/tests/send-utils.test.js
@@ -17,12 +17,12 @@ const {
} = require('../send.constants')
const stubs = {
- addCurrencies: sinon.stub().callsFake((a, b, obj) => {
+ addCurrencies: sinon.stub().callsFake((a, b) => {
if (String(a).match(/^0x.+/)) a = Number(String(a).slice(2))
if (String(b).match(/^0x.+/)) b = Number(String(b).slice(2))
return a + b
}),
- conversionUtil: sinon.stub().callsFake((val, obj) => parseInt(val, 16)),
+ conversionUtil: sinon.stub().callsFake((val) => parseInt(val, 16)),
conversionGTE: sinon.stub().callsFake((obj1, obj2) => obj1.value >= obj2.value),
multiplyCurrencies: sinon.stub().callsFake((a, b) => `${a}x${b}`),
calcTokenAmount: sinon.stub().callsFake((a, d) => 'calc:' + a + d),
diff --git a/ui/app/pages/send/to-autocomplete/to-autocomplete.js b/ui/app/pages/send/to-autocomplete/to-autocomplete.js
index b246413fb..328a5b62b 100644
--- a/ui/app/pages/send/to-autocomplete/to-autocomplete.js
+++ b/ui/app/pages/send/to-autocomplete/to-autocomplete.js
@@ -84,7 +84,7 @@ ToAutoComplete.prototype.handleInputEvent = function (event = {}, cb) {
cb && cb(event.target.value)
}
-ToAutoComplete.prototype.componentDidUpdate = function (nextProps, nextState) {
+ToAutoComplete.prototype.componentDidUpdate = function (nextProps) {
if (this.props.to !== nextProps.to) {
this.handleInputEvent()
}
diff --git a/ui/app/pages/settings/advanced-tab/advanced-tab.component.js b/ui/app/pages/settings/advanced-tab/advanced-tab.component.js
index 14b9daae6..3d27fe349 100644
--- a/ui/app/pages/settings/advanced-tab/advanced-tab.component.js
+++ b/ui/app/pages/settings/advanced-tab/advanced-tab.component.js
@@ -24,6 +24,8 @@ export default class AdvancedTab extends PureComponent {
setAdvancedInlineGasFeatureFlag: PropTypes.func,
advancedInlineGas: PropTypes.bool,
showFiatInTestnets: PropTypes.bool,
+ autoLogoutTimeLimit: PropTypes.number,
+ setAutoLogoutTimeLimit: PropTypes.func.isRequired,
setShowFiatConversionOnTestnetsPreference: PropTypes.func.isRequired,
}
@@ -49,7 +51,7 @@ export default class AdvancedTab extends PureComponent {
<TextField
type="text"
id="new-rpc"
- placeholder={t('rpcURL')}
+ placeholder={t('rpcUrl')}
value={newRpc}
onChange={e => this.setState({ newRpc: e.target.value })}
onKeyPress={e => {
@@ -355,6 +357,48 @@ export default class AdvancedTab extends PureComponent {
)
}
+ renderAutoLogoutTimeLimit () {
+ const { t } = this.context
+ const {
+ autoLogoutTimeLimit,
+ setAutoLogoutTimeLimit,
+ } = this.props
+
+ return (
+ <div className="settings-page__content-row">
+ <div className="settings-page__content-item">
+ <span>{ t('autoLogoutTimeLimit') }</span>
+ <div className="settings-page__content-description">
+ { t('autoLogoutTimeLimitDescription') }
+ </div>
+ </div>
+ <div className="settings-page__content-item">
+ <div className="settings-page__content-item-col">
+ <TextField
+ type="number"
+ id="autoTimeout"
+ placeholder="5"
+ value={this.state.autoLogoutTimeLimit}
+ defaultValue={autoLogoutTimeLimit}
+ onChange={e => this.setState({ autoLogoutTimeLimit: Math.max(Number(e.target.value), 0) })}
+ fullWidth
+ margin="dense"
+ min={0}
+ />
+ <button
+ className="button btn-primary settings-tab__rpc-save-button"
+ onClick={() => {
+ setAutoLogoutTimeLimit(this.state.autoLogoutTimeLimit)
+ }}
+ >
+ { t('save') }
+ </button>
+ </div>
+ </div>
+ </div>
+ )
+ }
+
renderContent () {
const { warning } = this.props
@@ -368,6 +412,7 @@ export default class AdvancedTab extends PureComponent {
{ this.renderAdvancedGasInputInline() }
{ this.renderHexDataOptIn() }
{ this.renderShowConversionInTestnets() }
+ { this.renderAutoLogoutTimeLimit() }
</div>
)
}
diff --git a/ui/app/pages/settings/advanced-tab/advanced-tab.container.js b/ui/app/pages/settings/advanced-tab/advanced-tab.container.js
index 69d7e07e6..bcac55f5e 100644
--- a/ui/app/pages/settings/advanced-tab/advanced-tab.container.js
+++ b/ui/app/pages/settings/advanced-tab/advanced-tab.container.js
@@ -8,10 +8,11 @@ import {
setFeatureFlag,
showModal,
setShowFiatConversionOnTestnetsPreference,
+ setAutoLogoutTimeLimit,
} from '../../../store/actions'
import {preferencesSelector} from '../../../selectors/selectors'
-const mapStateToProps = state => {
+export const mapStateToProps = state => {
const { appState: { warning }, metamask } = state
const {
featureFlags: {
@@ -19,17 +20,18 @@ const mapStateToProps = state => {
advancedInlineGas,
} = {},
} = metamask
- const { showFiatInTestnets } = preferencesSelector(state)
+ const { showFiatInTestnets, autoLogoutTimeLimit } = preferencesSelector(state)
return {
warning,
sendHexData,
advancedInlineGas,
showFiatInTestnets,
+ autoLogoutTimeLimit,
}
}
-const mapDispatchToProps = dispatch => {
+export const mapDispatchToProps = dispatch => {
return {
setHexDataFeatureFlag: shouldShow => dispatch(setFeatureFlag('sendHexData', shouldShow)),
setRpcTarget: (newRpc, chainId, ticker, nickname) => dispatch(updateAndSetCustomRpc(newRpc, chainId, ticker, nickname)),
@@ -39,6 +41,9 @@ const mapDispatchToProps = dispatch => {
setShowFiatConversionOnTestnetsPreference: value => {
return dispatch(setShowFiatConversionOnTestnetsPreference(value))
},
+ setAutoLogoutTimeLimit: value => {
+ return dispatch(setAutoLogoutTimeLimit(value))
+ },
}
}
diff --git a/ui/app/pages/settings/advanced-tab/tests/advanced-tab-component.test.js b/ui/app/pages/settings/advanced-tab/tests/advanced-tab-component.test.js
new file mode 100644
index 000000000..f81329533
--- /dev/null
+++ b/ui/app/pages/settings/advanced-tab/tests/advanced-tab-component.test.js
@@ -0,0 +1,44 @@
+import React from 'react'
+import assert from 'assert'
+import sinon from 'sinon'
+import { shallow } from 'enzyme'
+import AdvancedTab from '../advanced-tab.component'
+import TextField from '../../../../components/ui/text-field'
+
+describe('AdvancedTab Component', () => {
+ it('should render correctly', () => {
+ const root = shallow(
+ <AdvancedTab />,
+ {
+ context: {
+ t: s => `_${s}`,
+ },
+ }
+ )
+
+ assert.equal(root.find('.settings-page__content-row').length, 8)
+ })
+
+ it('should update autoLogoutTimeLimit', () => {
+ const setAutoLogoutTimeLimitSpy = sinon.spy()
+ const root = shallow(
+ <AdvancedTab
+ setAutoLogoutTimeLimit={setAutoLogoutTimeLimitSpy}
+ />,
+ {
+ context: {
+ t: s => `_${s}`,
+ },
+ }
+ )
+
+ const autoTimeout = root.find('.settings-page__content-row').last()
+ const textField = autoTimeout.find(TextField)
+
+ textField.props().onChange({ target: { value: 1440 } })
+ assert.equal(root.state().autoLogoutTimeLimit, 1440)
+
+ autoTimeout.find('button').simulate('click')
+ assert.equal(setAutoLogoutTimeLimitSpy.args[0][0], 1440)
+ })
+})
diff --git a/ui/app/pages/settings/advanced-tab/tests/advanced-tab-container.test.js b/ui/app/pages/settings/advanced-tab/tests/advanced-tab-container.test.js
new file mode 100644
index 000000000..62122073d
--- /dev/null
+++ b/ui/app/pages/settings/advanced-tab/tests/advanced-tab-container.test.js
@@ -0,0 +1,46 @@
+import assert from 'assert'
+import { mapStateToProps, mapDispatchToProps } from '../advanced-tab.container'
+
+const defaultState = {
+ appState: {
+ warning: null,
+ },
+ metamask: {
+ featureFlags: {
+ sendHexData: false,
+ advancedInlineGas: false,
+ },
+ preferences: {
+ autoLogoutTimeLimit: 0,
+ showFiatInTestnets: false,
+ useNativeCurrencyAsPrimaryCurrency: true,
+ },
+ },
+}
+
+describe('AdvancedTab Container', () => {
+ it('should map state to props correctly', () => {
+ const props = mapStateToProps(defaultState)
+ const expected = {
+ warning: null,
+ sendHexData: false,
+ advancedInlineGas: false,
+ showFiatInTestnets: false,
+ autoLogoutTimeLimit: 0,
+ }
+
+ assert.deepEqual(props, expected)
+ })
+
+ it('should map dispatch to props correctly', () => {
+ const props = mapDispatchToProps(() => 'mockDispatch')
+
+ assert.ok(typeof props.setHexDataFeatureFlag === 'function')
+ assert.ok(typeof props.setRpcTarget === 'function')
+ assert.ok(typeof props.displayWarning === 'function')
+ assert.ok(typeof props.showResetAccountConfirmationModal === 'function')
+ assert.ok(typeof props.setAdvancedInlineGasFeatureFlag === 'function')
+ assert.ok(typeof props.setShowFiatConversionOnTestnetsPreference === 'function')
+ assert.ok(typeof props.setAutoLogoutTimeLimit === 'function')
+ })
+})
diff --git a/ui/app/pages/settings/index.scss b/ui/app/pages/settings/index.scss
index 52208dc85..66959ba93 100644
--- a/ui/app/pages/settings/index.scss
+++ b/ui/app/pages/settings/index.scss
@@ -1,5 +1,7 @@
@import 'info-tab/index';
+@import 'networks-tab/index';
+
@import 'settings-tab/index';
.settings-page {
@@ -13,7 +15,6 @@
flex-flow: row nowrap;
padding: 12px 24px;
align-items: center;
- border-bottom: 1px solid $alto;
flex: 0 0 auto;
&__title {
@@ -22,6 +23,45 @@
}
}
+ &__subheader {
+ padding: 16px 4px;
+ font-size: 20px;
+ border-bottom: 1px solid $alto;
+ margin-right: 24px;
+
+ @media screen and (max-width: 575px) {
+ display: none;
+ }
+ }
+
+ &__sub-header {
+ height: 72px;
+ border-bottom: 1px solid #D8D8D8;
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+
+ @media screen and (max-width: 575px) {
+ height: 69px;
+ position: relative;
+ text-align: center;
+ }
+ }
+
+ &__sub-header-text {
+ font-family: Roboto;
+ font-style: normal;
+ font-weight: normal;
+ font-size: 24px;
+ line-height: 24px;
+ color: black;
+
+ @media screen and (max-width: 575px) {
+ font-size: 16px;
+ width: 100%;
+ }
+ }
+
&__back-button {
display: none;
@@ -49,8 +89,9 @@
&__content {
display: flex;
flex-flow: row nowrap;
- height: auto;
+ height: 100%;
overflow: auto;
+ border-top: 1px solid #D8D8D8;
&__tabs {
display: flex;
@@ -58,9 +99,15 @@
flex: 1 1 auto;
@media screen and (min-width: 576px) {
- flex: 0 0 32%;
+ flex: 0 0 40%;
max-width: 210px;
- border-right: 1px solid $alto;
+ padding-top: 8px;
+ }
+
+ .tab-bar__tab {
+ @media screen and (min-width: 576px) {
+ padding: 16px 24px 0;
+ }
}
}
@@ -76,6 +123,10 @@
&__body {
padding: 12px 24px;
+
+ @media screen and (min-width: 576px) {
+ padding: 12px;
+ }
}
&__content-row {
@@ -89,7 +140,6 @@
min-width: 0;
display: flex;
flex-direction: column;
- padding: 0 5px;
min-height: 71px;
@media screen and (max-width: 575px) {
diff --git a/ui/app/pages/settings/networks-tab/index.js b/ui/app/pages/settings/networks-tab/index.js
new file mode 100644
index 000000000..362004498
--- /dev/null
+++ b/ui/app/pages/settings/networks-tab/index.js
@@ -0,0 +1 @@
+export { default } from './networks-tab.container'
diff --git a/ui/app/pages/settings/networks-tab/index.scss b/ui/app/pages/settings/networks-tab/index.scss
new file mode 100644
index 000000000..b0020437d
--- /dev/null
+++ b/ui/app/pages/settings/networks-tab/index.scss
@@ -0,0 +1,200 @@
+.networks-tab {
+ &__content {
+ margin-top: 24px;
+ display: flex;
+ height: 100%;
+ max-width: 739px;
+ justify-content: space-between;
+
+ @media screen and (max-width: 575px) {
+ margin-top: 0px;
+ }
+ }
+
+ &__body {
+ padding: 12px 24px;
+ height: 100%;
+ display: flex;
+ flex-direction: column;
+
+ @media screen and (max-width: 575px) {
+ padding: 0;
+ }
+ }
+
+ &__back-button {
+ display: none;
+
+ @media screen and (max-width: 575px) {
+ display: block;
+ background-image: url('/images/caret-left-black.svg');
+ width: 18px;
+ height: 18px;
+ opacity: .5;
+ background-size: contain;
+ background-repeat: no-repeat;
+ background-position: center;
+ margin-right: 16px;
+ cursor: pointer;
+ position: absolute;
+ margin-left: 10px;
+ }
+ }
+
+ &__network-form {
+ flex: 0.5 0 auto;
+ max-width: 343px;
+ max-height: 465px;
+ display: flex;
+ flex-direction: column;
+ justify-content: space-between;
+
+ .page-container__footer {
+ border-top: none;
+
+ @media screen and (max-width: 575px) {
+ width: 93%;
+ }
+
+ header {
+ padding: 10px 0px;
+ }
+ }
+
+ @media screen and (max-width: 575px) {
+ display: flex;
+ flex: auto;
+ max-width: 100%;
+ max-height: 100%;
+ align-items: center;
+ width: 100%;
+ margin-top: 10px;
+ }
+ }
+
+ &__network-form-row {
+ @media screen and (max-width: 575px) {
+ display: flex;
+ flex-direction: column;
+ width: 93%;
+ }
+ }
+
+ &__network-form-label {
+ font-family: Roboto;
+ font-style: normal;
+ font-weight: normal;
+ font-size: 14px;
+ line-height: 20px;
+ color: #000000;
+ }
+
+ &__networks-list {
+ flex: 0.5 0 auto;
+ max-width: 343px;
+
+ @media screen and (max-width: 575px) {
+ max-width: 100vw;
+ width: 100vw;
+ overflow-y: scroll;
+ }
+ }
+
+ &__add-network-button-wrapper {
+ display: none;
+
+ @media screen and (max-width: 575px) {
+ display: flex;
+ padding-top: 19px;
+ padding-bottom: 23px;
+ justify-content: center;
+ align-items: center;
+ border-top: 1px solid #D8D8D8;
+
+ .button {
+ width: 178px;
+ }
+ }
+ }
+
+ &__add-network-header-button-wrapper {
+ padding-top: 15px;
+ padding-bottom: 21px;
+ justify-content: center;
+
+ .button {
+ width: 178px;
+ }
+
+ @media screen and (max-width: 575px) {
+ display: none;
+ }
+ }
+
+ &__networks-list--selection {
+ @media screen and (max-width: 575px) {
+ display: none;
+ }
+ }
+
+ &__networks-list-item {
+ display: flex;
+ padding: 13px 0px 13px 17px;
+ position: relative;
+
+ .menu-icon-circle {
+ &:hover {
+ cursor: pointer;
+ }
+ }
+
+ @media screen and (max-width: 575px) {
+ padding: 20px 23px 21px 17px;
+ border-bottom: 1px solid #D8D8D8;
+ }
+ }
+
+ &__networks-list-item:last-of-type {
+ @media screen and (max-width: 575px) {
+ border-bottom: none;
+ }
+ }
+
+ &__networks-list-name {
+ margin-left: 11px;
+ font-family: Roboto;
+ font-style: normal;
+ font-weight: normal;
+ font-size: 16px;
+ line-height: 23px;
+ color: #6A737D;
+
+ &:hover {
+ cursor: pointer;
+ }
+ }
+
+ &__networks-list-arrow {
+ display: none;
+
+ @media screen and (max-width: 575px) {
+ display: block;
+ background-image: url('/images/caret-right.svg');
+ width: 24px;
+ height: 24px;
+ background-size: contain;
+ background-repeat: no-repeat;
+ background-position: center;
+ right: 10px;
+ cursor: pointer;
+ position: absolute;
+ width: 24px;
+ height: 24px;
+ }
+ }
+
+ &__networks-list-name--selected {
+ font-weight: bold;
+ color: #000000;
+ }
+} \ No newline at end of file
diff --git a/ui/app/pages/settings/networks-tab/network-form/index.js b/ui/app/pages/settings/networks-tab/network-form/index.js
new file mode 100644
index 000000000..89d9de42b
--- /dev/null
+++ b/ui/app/pages/settings/networks-tab/network-form/index.js
@@ -0,0 +1 @@
+export { default } from './network-form.component'
diff --git a/ui/app/pages/settings/networks-tab/network-form/network-form.component.js b/ui/app/pages/settings/networks-tab/network-form/network-form.component.js
new file mode 100644
index 000000000..5e455b65e
--- /dev/null
+++ b/ui/app/pages/settings/networks-tab/network-form/network-form.component.js
@@ -0,0 +1,225 @@
+import React, { PureComponent } from 'react'
+import PropTypes from 'prop-types'
+import validUrl from 'valid-url'
+import PageContainerFooter from '../../../../components/ui/page-container/page-container-footer'
+import TextField from '../../../../components/ui/text-field'
+
+export default class NetworksTab extends PureComponent {
+ static contextTypes = {
+ t: PropTypes.func.isRequired,
+ metricsEvent: PropTypes.func.isRequired,
+ }
+
+ static propTypes = {
+ editRpc: PropTypes.func.isRequired,
+ rpcUrl: PropTypes.string,
+ chainId: PropTypes.string,
+ ticker: PropTypes.string,
+ viewOnly: PropTypes.bool,
+ networkName: PropTypes.string,
+ onClear: PropTypes.func.isRequired,
+ setRpcTarget: PropTypes.func.isRequired,
+ networksTabIsInAddMode: PropTypes.bool,
+ blockExplorerUrl: PropTypes.string,
+ rpcPrefs: PropTypes.object,
+ }
+
+ state = {
+ rpcUrl: this.props.rpcUrl,
+ chainId: this.props.chainId,
+ ticker: this.props.ticker,
+ networkName: this.props.networkName,
+ blockExplorerUrl: this.props.blockExplorerUrl,
+ errors: {},
+ }
+
+ componentDidUpdate (prevProps) {
+ const { rpcUrl: prevRpcUrl, networksTabIsInAddMode: prevAddMode } = prevProps
+ const {
+ rpcUrl,
+ chainId,
+ ticker,
+ networkName,
+ networksTabIsInAddMode,
+ blockExplorerUrl,
+ } = this.props
+
+ if (!prevAddMode && networksTabIsInAddMode) {
+ this.setState({
+ rpcUrl: '',
+ chainId: '',
+ ticker: '',
+ networkName: '',
+ blockExplorerUrl: '',
+ errors: {},
+ })
+ } else if (prevRpcUrl !== rpcUrl) {
+ this.setState({ rpcUrl, chainId, ticker, networkName, blockExplorerUrl, errors: {} })
+ }
+ }
+
+ componentWillUnmount () {
+ this.props.onClear()
+ this.setState({
+ rpcUrl: '',
+ chainId: '',
+ ticker: '',
+ networkName: '',
+ blockExplorerUrl: '',
+ errors: {},
+ })
+ }
+
+ stateIsUnchanged () {
+ const {
+ rpcUrl,
+ chainId,
+ ticker,
+ networkName,
+ blockExplorerUrl,
+ } = this.props
+
+ const {
+ rpcUrl: stateRpcUrl,
+ chainId: stateChainId,
+ ticker: stateTicker,
+ networkName: stateNetworkName,
+ blockExplorerUrl: stateBlockExplorerUrl,
+ } = this.state
+
+ return (
+ stateRpcUrl === rpcUrl &&
+ stateChainId === chainId &&
+ stateTicker === ticker &&
+ stateNetworkName === networkName &&
+ stateBlockExplorerUrl === blockExplorerUrl
+ )
+ }
+
+ renderFormTextField (fieldKey, textFieldId, onChange, value, optionalTextFieldKey) {
+ const { errors } = this.state
+ const { viewOnly } = this.props
+
+ return (
+ <div className="networks-tab__network-form-row">
+ <div className="networks-tab__network-form-label">{this.context.t(optionalTextFieldKey || fieldKey)}</div>
+ <TextField
+ type="text"
+ id={textFieldId}
+ onChange={onChange}
+ fullWidth
+ margin="dense"
+ value={value}
+ disabled={viewOnly}
+ error={errors[fieldKey]}
+ />
+ </div>
+ )
+ }
+
+ setStateWithValue = (stateKey, validator) => {
+ return (e) => {
+ validator && validator(e.target.value, stateKey)
+ this.setState({ [stateKey]: e.target.value })
+ }
+ }
+
+ setErrorTo = (errorKey, errorVal) => {
+ this.setState({
+ errors: {
+ ...this.state.errors,
+ [errorKey]: errorVal,
+ },
+ })
+ }
+
+ validateChainId = (chainId) => {
+ this.setErrorTo('chainId', !!chainId && Number.isNaN(parseInt(chainId))
+ ? `${this.context.t('invalidInput')} chainId`
+ : ''
+ )
+ }
+
+ validateUrl = (url, stateKey) => {
+ if (validUrl.isWebUri(url)) {
+ this.setErrorTo(stateKey, '')
+ } else {
+ const appendedRpc = `http://${url}`
+ const validWhenAppended = validUrl.isWebUri(appendedRpc) && !url.match(/^https?:\/\/$/)
+
+ this.setErrorTo(stateKey, this.context.t(validWhenAppended ? 'uriErrorMsg' : 'invalidRPC'))
+ }
+ }
+
+ render () {
+ const { setRpcTarget, viewOnly, rpcUrl: propsRpcUrl, editRpc, rpcPrefs = {} } = this.props
+ const {
+ networkName,
+ rpcUrl,
+ chainId,
+ ticker,
+ blockExplorerUrl,
+ errors,
+ } = this.state
+
+
+ return (
+ <div className="networks-tab__network-form">
+ {this.renderFormTextField(
+ 'networkName',
+ 'network-name',
+ this.setStateWithValue('networkName'),
+ networkName,
+ )}
+ {this.renderFormTextField(
+ 'rpcUrl',
+ 'rpc-url',
+ this.setStateWithValue('rpcUrl', this.validateUrl),
+ rpcUrl,
+ )}
+ {this.renderFormTextField(
+ 'chainId',
+ 'chainId',
+ this.setStateWithValue('chainId', this.validateChainId),
+ chainId,
+ 'optionalChainId',
+ )}
+ {this.renderFormTextField(
+ 'symbol',
+ 'network-ticker',
+ this.setStateWithValue('ticker'),
+ ticker,
+ 'optionalSymbol',
+ )}
+ {this.renderFormTextField(
+ 'blockExplorerUrl',
+ 'block-explorer-url',
+ this.setStateWithValue('blockExplorerUrl', this.validateUrl),
+ blockExplorerUrl,
+ 'optionalBlockExplorerUrl',
+ )}
+ <PageContainerFooter
+ cancelText={this.context.t('cancel')}
+ hideCancel={true}
+ onSubmit={() => {
+ if (propsRpcUrl && rpcUrl !== propsRpcUrl) {
+ editRpc(propsRpcUrl, rpcUrl, chainId, ticker, networkName, {
+ blockExplorerUrl: blockExplorerUrl || rpcPrefs.blockExplorerUrl,
+ ...rpcPrefs,
+ })
+ } else {
+ setRpcTarget(rpcUrl, chainId, ticker, networkName, {
+ blockExplorerUrl: blockExplorerUrl || rpcPrefs.blockExplorerUrl,
+ ...rpcPrefs,
+ })
+ }
+ }}
+ submitText={this.context.t('save')}
+ submitButtonType={'confirm'}
+ disabled={viewOnly || this.stateIsUnchanged() || Object.values(errors).some(x => x) || !rpcUrl}
+ />
+ </div>
+ )
+ }
+
+}
diff --git a/ui/app/pages/settings/networks-tab/networks-tab.component.js b/ui/app/pages/settings/networks-tab/networks-tab.component.js
new file mode 100644
index 000000000..2f921a892
--- /dev/null
+++ b/ui/app/pages/settings/networks-tab/networks-tab.component.js
@@ -0,0 +1,214 @@
+import React, { PureComponent } from 'react'
+import PropTypes from 'prop-types'
+import { SETTINGS_ROUTE } from '../../../helpers/constants/routes'
+import { ENVIRONMENT_TYPE_POPUP } from '../../../../../app/scripts/lib/enums'
+import { getEnvironmentType } from '../../../../../app/scripts/lib/util'
+import classnames from 'classnames'
+import Button from '../../../components/ui/button'
+import NetworkForm from './network-form'
+import NetworkDropdownIcon from '../../../components/app/dropdowns/components/network-dropdown-icon'
+
+export default class NetworksTab extends PureComponent {
+ static contextTypes = {
+ t: PropTypes.func.isRequired,
+ metricsEvent: PropTypes.func.isRequired,
+ }
+
+ static propTypes = {
+ editRpc: PropTypes.func.isRequired,
+ history: PropTypes.object.isRequired,
+ location: PropTypes.object.isRequired,
+ networkIsSelected: PropTypes.bool,
+ networksTabIsInAddMode: PropTypes.bool,
+ networksToRender: PropTypes.array.isRequired,
+ selectedNetwork: PropTypes.object,
+ setNetworksTabAddMode: PropTypes.func.isRequired,
+ setRpcTarget: PropTypes.func.isRequired,
+ setSelectedSettingsRpcUrl: PropTypes.func.isRequired,
+ providerUrl: PropTypes.string,
+ providerType: PropTypes.string,
+ networkDefaultedToProvider: PropTypes.bool,
+ }
+
+ componentWillMount () {
+ this.props.setSelectedSettingsRpcUrl(null)
+ }
+
+ isCurrentPath (pathname) {
+ return this.props.location.pathname === pathname
+ }
+
+ renderSubHeader () {
+ const {
+ networkIsSelected,
+ setSelectedSettingsRpcUrl,
+ setNetworksTabAddMode,
+ networksTabIsInAddMode,
+ networkDefaultedToProvider,
+ } = this.props
+
+ return (
+ <div className="settings-page__sub-header">
+ <div
+ className="networks-tab__back-button"
+ onClick={(networkIsSelected && !networkDefaultedToProvider) || networksTabIsInAddMode
+ ? () => {
+ setNetworksTabAddMode(false)
+ setSelectedSettingsRpcUrl(null)
+ }
+ : () => this.props.history.push(SETTINGS_ROUTE)
+ }
+ />
+ <span className="settings-page__sub-header-text">{ this.context.t('networks') }</span>
+ <div className="networks-tab__add-network-header-button-wrapper">
+ <Button
+ type="primary"
+ onClick={event => {
+ event.preventDefault()
+ setSelectedSettingsRpcUrl(null)
+ setNetworksTabAddMode(true)
+ }}
+ >
+ { this.context.t('addNetwork') }
+ </Button>
+ </div>
+ </div>
+ )
+ }
+
+ renderNetworkListItem (network, selectRpcUrl) {
+ const {
+ setSelectedSettingsRpcUrl,
+ setNetworksTabAddMode,
+ networkIsSelected,
+ providerUrl,
+ providerType,
+ networksTabIsInAddMode,
+ } = this.props
+ const {
+ border,
+ iconColor,
+ label,
+ labelKey,
+ rpcUrl,
+ providerType: currentProviderType,
+ } = network
+
+ const listItemNetworkIsSelected = selectRpcUrl && selectRpcUrl === rpcUrl
+ const listItemUrlIsProviderUrl = rpcUrl === providerUrl
+ const listItemTypeIsProviderNonRpcType = providerType !== 'rpc' && currentProviderType === providerType
+ const listItemNetworkIsCurrentProvider = !networkIsSelected && !networksTabIsInAddMode && (listItemUrlIsProviderUrl || listItemTypeIsProviderNonRpcType)
+ const displayNetworkListItemAsSelected = listItemNetworkIsSelected || listItemNetworkIsCurrentProvider
+
+ return (
+ <div
+ key={'settings-network-list-item:' + rpcUrl}
+ className="networks-tab__networks-list-item"
+ onClick={ () => {
+ setNetworksTabAddMode(false)
+ setSelectedSettingsRpcUrl(rpcUrl)
+ }}
+ >
+ <NetworkDropdownIcon
+ backgroundColor={iconColor || 'white'}
+ innerBorder={border}
+ />
+ <div className={ classnames('networks-tab__networks-list-name', {
+ 'networks-tab__networks-list-name--selected': displayNetworkListItemAsSelected,
+ }) }>
+ { label || this.context.t(labelKey) }
+ </div>
+ <div className="networks-tab__networks-list-arrow" />
+ </div>
+ )
+ }
+
+ renderNetworksList () {
+ const { networksToRender, selectedNetwork, networkIsSelected, networksTabIsInAddMode, networkDefaultedToProvider } = this.props
+
+ return (
+ <div className={classnames('networks-tab__networks-list', {
+ 'networks-tab__networks-list--selection': (networkIsSelected && !networkDefaultedToProvider) || networksTabIsInAddMode,
+ })}>
+ { networksToRender.map(network => this.renderNetworkListItem(network, selectedNetwork.rpcUrl)) }
+ </div>
+ )
+ }
+
+ renderNetworksTabContent () {
+ const {
+ setRpcTarget,
+ setSelectedSettingsRpcUrl,
+ setNetworksTabAddMode,
+ selectedNetwork: {
+ labelKey,
+ label,
+ rpcUrl,
+ chainId,
+ ticker,
+ viewOnly,
+ rpcPrefs,
+ blockExplorerUrl,
+ },
+ networksTabIsInAddMode,
+ editRpc,
+ networkDefaultedToProvider,
+ } = this.props
+ const envIsPopup = getEnvironmentType() === ENVIRONMENT_TYPE_POPUP
+
+ return (
+ <div className="networks-tab__content">
+ {this.renderNetworksList()}
+ {networksTabIsInAddMode || !envIsPopup || (envIsPopup && !networkDefaultedToProvider)
+ ? <NetworkForm
+ setRpcTarget={setRpcTarget}
+ editRpc={editRpc}
+ networkName={label || labelKey && this.context.t(labelKey) || ''}
+ rpcUrl={rpcUrl}
+ chainId={chainId}
+ ticker={ticker}
+ onClear={() => {
+ setNetworksTabAddMode(false)
+ setSelectedSettingsRpcUrl(null)
+ }}
+ viewOnly={viewOnly}
+ networksTabIsInAddMode={networksTabIsInAddMode}
+ rpcPrefs={rpcPrefs}
+ blockExplorerUrl={blockExplorerUrl}
+ />
+ : null
+ }
+ </div>
+ )
+ }
+
+ renderContent () {
+ const { setNetworksTabAddMode, setSelectedSettingsRpcUrl, networkIsSelected, networksTabIsInAddMode } = this.props
+
+ return (
+ <div className="networks-tab__body">
+ {this.renderSubHeader()}
+ {this.renderNetworksTabContent()}
+ {!networkIsSelected && !networksTabIsInAddMode
+ ? <div className="networks-tab__add-network-button-wrapper">
+ <Button
+ type="primary"
+ onClick={event => {
+ event.preventDefault()
+ setSelectedSettingsRpcUrl(null)
+ setNetworksTabAddMode(true)
+ }}
+ >
+ { this.context.t('addNetwork') }
+ </Button>
+ </div>
+ : null
+ }
+ </div>
+ )
+ }
+
+ render () {
+ return this.renderContent()
+ }
+}
diff --git a/ui/app/pages/settings/networks-tab/networks-tab.constants.js b/ui/app/pages/settings/networks-tab/networks-tab.constants.js
new file mode 100644
index 000000000..d3d1a01cc
--- /dev/null
+++ b/ui/app/pages/settings/networks-tab/networks-tab.constants.js
@@ -0,0 +1,50 @@
+const defaultNetworksData = [
+ {
+ labelKey: 'mainnet',
+ iconColor: '#29B6AF',
+ providerType: 'mainnet',
+ rpcUrl: 'https://api.infura.io/v1/jsonrpc/mainnet',
+ chainId: '1',
+ ticker: 'ETH',
+ blockExplorerUrl: 'https://etherscan.io',
+ },
+ {
+ labelKey: 'ropsten',
+ iconColor: '#FF4A8D',
+ providerType: 'ropsten',
+ rpcUrl: 'https://api.infura.io/v1/jsonrpc/ropsten',
+ chainId: '3',
+ ticker: 'ETH',
+ blockExplorerUrl: 'https://ropsten.etherscan.io',
+ },
+ {
+ labelKey: 'kovan',
+ iconColor: '#9064FF',
+ providerType: 'kovan',
+ rpcUrl: 'https://api.infura.io/v1/jsonrpc/kovan',
+ chainId: '4',
+ ticker: 'ETH',
+ blockExplorerUrl: 'https://etherscan.io',
+ },
+ {
+ labelKey: 'rinkeby',
+ iconColor: '#F6C343',
+ providerType: 'rinkeby',
+ rpcUrl: 'https://api.infura.io/v1/jsonrpc/rinkeby',
+ chainId: '42',
+ ticker: 'ETH',
+ blockExplorerUrl: 'https://rinkeby.etherscan.io',
+ },
+ {
+ labelKey: 'localhost',
+ iconColor: 'white',
+ border: '1px solid #6A737D',
+ providerType: 'localhost',
+ rpcUrl: 'http://localhost:8545/',
+ blockExplorerUrl: 'https://etherscan.io',
+ },
+]
+
+export {
+ defaultNetworksData,
+}
diff --git a/ui/app/pages/settings/networks-tab/networks-tab.container.js b/ui/app/pages/settings/networks-tab/networks-tab.container.js
new file mode 100644
index 000000000..a5d71f714
--- /dev/null
+++ b/ui/app/pages/settings/networks-tab/networks-tab.container.js
@@ -0,0 +1,77 @@
+import NetworksTab from './networks-tab.component'
+import { compose } from 'recompose'
+import { connect } from 'react-redux'
+import { withRouter } from 'react-router-dom'
+import {
+ setSelectedSettingsRpcUrl,
+ updateAndSetCustomRpc,
+ displayWarning,
+ setNetworksTabAddMode,
+ editRpc,
+} from '../../../store/actions'
+import { defaultNetworksData } from './networks-tab.constants'
+const defaultNetworks = defaultNetworksData.map(network => ({ ...network, viewOnly: true }))
+
+const mapStateToProps = state => {
+ const {
+ frequentRpcListDetail,
+ provider,
+ } = state.metamask
+ const {
+ networksTabSelectedRpcUrl,
+ networksTabIsInAddMode,
+ } = state.appState
+
+ const frequentRpcNetworkListDetails = frequentRpcListDetail.map(rpc => {
+ return {
+ label: rpc.nickname,
+ iconColor: '#6A737D',
+ providerType: 'rpc',
+ rpcUrl: rpc.rpcUrl,
+ chainId: rpc.chainId,
+ ticker: rpc.ticker,
+ blockExplorerUrl: rpc.rpcPrefs && rpc.rpcPrefs.blockExplorerUrl || '',
+ }
+ })
+
+ const networksToRender = [ ...defaultNetworks, ...frequentRpcNetworkListDetails ]
+ let selectedNetwork = networksToRender.find(network => network.rpcUrl === networksTabSelectedRpcUrl) || {}
+ const networkIsSelected = Boolean(selectedNetwork.rpcUrl)
+
+ let networkDefaultedToProvider = false
+ if (!networkIsSelected && !networksTabIsInAddMode) {
+ selectedNetwork = networksToRender.find(network => {
+ return network.rpcUrl === provider.rpcTarget || network.providerType !== 'rpc' && network.providerType === provider.type
+ }) || {}
+ networkDefaultedToProvider = true
+ }
+
+ return {
+ selectedNetwork,
+ networksToRender,
+ networkIsSelected,
+ networksTabIsInAddMode,
+ providerType: provider.type,
+ providerUrl: provider.rpcTarget,
+ networkDefaultedToProvider,
+ }
+}
+
+const mapDispatchToProps = dispatch => {
+ return {
+ setSelectedSettingsRpcUrl: newRpcUrl => dispatch(setSelectedSettingsRpcUrl(newRpcUrl)),
+ setRpcTarget: (newRpc, chainId, ticker, nickname, rpcPrefs) => {
+ dispatch(updateAndSetCustomRpc(newRpc, chainId, ticker, nickname, rpcPrefs))
+ },
+ displayWarning: warning => dispatch(displayWarning(warning)),
+ setNetworksTabAddMode: isInAddMode => dispatch(setNetworksTabAddMode(isInAddMode)),
+ editRpc: (oldRpc, newRpc, chainId, ticker, nickname, rpcPrefs) => {
+ dispatch(editRpc(oldRpc, newRpc, chainId, ticker, nickname, rpcPrefs))
+ },
+ }
+}
+
+export default compose(
+ withRouter,
+ connect(mapStateToProps, mapDispatchToProps)
+)(NetworksTab)
diff --git a/ui/app/pages/settings/settings.component.js b/ui/app/pages/settings/settings.component.js
index 061e65060..a2f137264 100644
--- a/ui/app/pages/settings/settings.component.js
+++ b/ui/app/pages/settings/settings.component.js
@@ -1,11 +1,12 @@
import React, { PureComponent } from 'react'
import PropTypes from 'prop-types'
-import { Switch, Route, matchPath } from 'react-router-dom'
+import { Switch, Route, matchPath, withRouter } from 'react-router-dom'
import { ENVIRONMENT_TYPE_POPUP } from '../../../../app/scripts/lib/enums'
import { getEnvironmentType } from '../../../../app/scripts/lib/util'
import TabBar from '../../components/app/tab-bar'
import c from 'classnames'
import SettingsTab from './settings-tab'
+import NetworksTab from './networks-tab'
import AdvancedTab from './advanced-tab'
import InfoTab from './info-tab'
import SecurityTab from './security-tab'
@@ -16,6 +17,7 @@ import {
GENERAL_ROUTE,
ABOUT_US_ROUTE,
SETTINGS_ROUTE,
+ NETWORKS_ROUTE,
} from '../../helpers/constants/routes'
const ROUTES_TO_I18N_KEYS = {
@@ -25,7 +27,7 @@ const ROUTES_TO_I18N_KEYS = {
[ABOUT_US_ROUTE]: 'about',
}
-export default class SettingsPage extends PureComponent {
+class SettingsPage extends PureComponent {
static propTypes = {
location: PropTypes.object,
history: PropTypes.object,
@@ -55,7 +57,7 @@ export default class SettingsPage extends PureComponent {
>
<div className="settings-page__header">
{
- !this.isCurrentPath(SETTINGS_ROUTE) && (
+ !this.isCurrentPath(SETTINGS_ROUTE) && !this.isCurrentPath(NETWORKS_ROUTE) && (
<div
className="settings-page__back-button"
onClick={() => history.push(SETTINGS_ROUTE)}
@@ -75,6 +77,7 @@ export default class SettingsPage extends PureComponent {
{ this.renderTabs() }
</div>
<div className="settings-page__content__modules">
+ { this.renderSubHeader() }
{ this.renderContent() }
</div>
</div>
@@ -82,6 +85,17 @@ export default class SettingsPage extends PureComponent {
)
}
+ renderSubHeader () {
+ const { t } = this.context
+ const { location: { pathname } } = this.props
+
+ return (
+ <div className="settings-page__subheader">
+ {t(ROUTES_TO_I18N_KEYS[pathname] || 'general')}
+ </div>
+ )
+ }
+
renderTabs () {
const { history, location } = this.props
const { t } = this.context
@@ -92,6 +106,7 @@ export default class SettingsPage extends PureComponent {
{ content: t('general'), description: t('generalSettingsDescription'), key: GENERAL_ROUTE },
{ content: t('advanced'), description: t('advancedSettingsDescription'), key: ADVANCED_ROUTE },
{ content: t('securityAndPrivacy'), description: t('securitySettingsDescription'), key: SECURITY_ROUTE },
+ { content: t('networks'), description: t('networkSettingsDescription'), key: NETWORKS_ROUTE },
{ content: t('about'), description: t('aboutSettingsDescription'), key: ABOUT_US_ROUTE },
]}
isActive={key => {
@@ -125,6 +140,11 @@ export default class SettingsPage extends PureComponent {
/>
<Route
exact
+ path={NETWORKS_ROUTE}
+ component={NetworksTab}
+ />
+ <Route
+ exact
path={SECURITY_ROUTE}
component={SecurityTab}
/>
@@ -135,3 +155,5 @@ export default class SettingsPage extends PureComponent {
)
}
}
+
+export default withRouter(SettingsPage)
diff --git a/ui/app/selectors/selectors.js b/ui/app/selectors/selectors.js
index 2d25aa156..c7cb80024 100644
--- a/ui/app/selectors/selectors.js
+++ b/ui/app/selectors/selectors.js
@@ -48,6 +48,8 @@ const selectors = {
getNumberOfAccounts,
getNumberOfTokens,
isEthereumNetwork,
+ getMetaMetricState,
+ getRpcPrefsForCurrentProvider,
}
module.exports = selectors
@@ -165,7 +167,7 @@ function getSelectedToken (state) {
const tokens = state.metamask.tokens || []
const selectedTokenAddress = state.metamask.selectedTokenAddress
const selectedToken = tokens.filter(({ address }) => address === selectedTokenAddress)[0]
- const sendToken = state.metamask.send.token
+ const sendToken = state.metamask.send && state.metamask.send.token
return selectedToken || sendToken || null
}
@@ -314,3 +316,22 @@ function preferencesSelector ({ metamask }) {
function getAdvancedInlineGasShown (state) {
return Boolean(state.metamask.featureFlags.advancedInlineGas)
}
+
+function getMetaMetricState (state) {
+ return {
+ network: getCurrentNetworkId(state),
+ activeCurrency: getSelectedAsset(state),
+ accountType: getAccountType(state),
+ metaMetricsId: state.metamask.metaMetricsId,
+ numberOfTokens: getNumberOfTokens(state),
+ numberOfAccounts: getNumberOfAccounts(state),
+ participateInMetaMetrics: state.metamask.participateInMetaMetrics,
+ }
+}
+
+function getRpcPrefsForCurrentProvider (state) {
+ const { frequentRpcListDetail, provider } = state.metamask
+ const selectRpcInfo = frequentRpcListDetail.find(rpcInfo => rpcInfo.rpcUrl === provider.rpcTarget)
+ const { rpcPrefs = {} } = selectRpcInfo || {}
+ return rpcPrefs
+}
diff --git a/ui/app/store/actions.js b/ui/app/store/actions.js
index f594d9002..7f6cbea1f 100644
--- a/ui/app/store/actions.js
+++ b/ui/app/store/actions.js
@@ -239,6 +239,7 @@ var actions = {
updateAndSetCustomRpc: updateAndSetCustomRpc,
setRpcTarget: setRpcTarget,
delRpcTarget: delRpcTarget,
+ editRpc: editRpc,
setProviderType: setProviderType,
SET_HARDWARE_WALLET_DEFAULT_HD_PATH: 'SET_HARDWARE_WALLET_DEFAULT_HD_PATH',
setHardwareWalletDefaultHdPath,
@@ -316,6 +317,7 @@ var actions = {
UPDATE_PREFERENCES: 'UPDATE_PREFERENCES',
setUseNativeCurrencyAsPrimaryCurrencyPreference,
setShowFiatConversionOnTestnetsPreference,
+ setAutoLogoutTimeLimit,
// Migration of users to new UI
setCompletedUiMigration,
@@ -343,12 +345,21 @@ var actions = {
createCancelTransaction,
createSpeedUpTransaction,
- approveProviderRequest,
- rejectProviderRequest,
+ approveProviderRequestByOrigin,
+ rejectProviderRequestByOrigin,
clearApprovedOrigins,
setFirstTimeFlowType,
SET_FIRST_TIME_FLOW_TYPE: 'SET_FIRST_TIME_FLOW_TYPE',
+
+ SET_SELECTED_SETTINGS_RPC_URL: 'SET_SELECTED_SETTINGS_RPC_URL',
+ setSelectedSettingsRpcUrl,
+ SET_NETWORKS_TAB_ADD_MODE: 'SET_NETWORKS_TAB_ADD_MODE',
+ setNetworksTabAddMode,
+
+ // AppStateController-related actions
+ SET_LAST_ACTIVE_TIME: 'SET_LAST_ACTIVE_TIME',
+ setLastActiveTime,
}
module.exports = actions
@@ -761,7 +772,7 @@ function addNewAccount () {
function checkHardwareStatus (deviceName, hdPath) {
log.debug(`background.checkHardwareStatus`, deviceName, hdPath)
- return (dispatch, getState) => {
+ return (dispatch) => {
dispatch(actions.showLoadingIndication())
return new Promise((resolve, reject) => {
background.checkHardwareStatus(deviceName, hdPath, (err, unlocked) => {
@@ -782,10 +793,10 @@ function checkHardwareStatus (deviceName, hdPath) {
function forgetDevice (deviceName) {
log.debug(`background.forgetDevice`, deviceName)
- return (dispatch, getState) => {
+ return (dispatch) => {
dispatch(actions.showLoadingIndication())
return new Promise((resolve, reject) => {
- background.forgetDevice(deviceName, (err, response) => {
+ background.forgetDevice(deviceName, (err) => {
if (err) {
log.error(err)
dispatch(actions.displayWarning(err.message))
@@ -803,7 +814,7 @@ function forgetDevice (deviceName) {
function connectHardware (deviceName, page, hdPath) {
log.debug(`background.connectHardware`, deviceName, page, hdPath)
- return (dispatch, getState) => {
+ return (dispatch) => {
dispatch(actions.showLoadingIndication())
return new Promise((resolve, reject) => {
background.connectHardware(deviceName, page, hdPath, (err, accounts) => {
@@ -824,10 +835,10 @@ function connectHardware (deviceName, page, hdPath) {
function unlockHardwareWalletAccount (index, deviceName, hdPath) {
log.debug(`background.unlockHardwareWalletAccount`, index, deviceName, hdPath)
- return (dispatch, getState) => {
+ return (dispatch) => {
dispatch(actions.showLoadingIndication())
return new Promise((resolve, reject) => {
- background.unlockHardwareWalletAccount(index, deviceName, hdPath, (err, accounts) => {
+ background.unlockHardwareWalletAccount(index, deviceName, hdPath, (err) => {
if (err) {
log.error(err)
dispatch(actions.displayWarning(err.message))
@@ -848,7 +859,7 @@ function showInfoPage () {
}
function showQrScanner (ROUTE) {
- return (dispatch, getState) => {
+ return (dispatch) => {
return WebcamUtils.checkStatus()
.then(status => {
if (!status.environmentReady) {
@@ -987,7 +998,7 @@ function signTypedMsg (msgData) {
function signTx (txData) {
return (dispatch) => {
- global.ethQuery.sendTransaction(txData, (err, data) => {
+ global.ethQuery.sendTransaction(txData, (err) => {
if (err) {
return dispatch(actions.displayWarning(err.message))
}
@@ -1020,7 +1031,6 @@ function setGasTotal (gasTotal) {
function updateGasData ({
gasPrice,
blockGasLimit,
- recentBlocks,
selectedAddress,
selectedToken,
to,
@@ -1402,7 +1412,7 @@ function cancelTx (txData) {
* @return {function(*): Promise<void>}
*/
function cancelTxs (txDataList) {
- return async (dispatch, getState) => {
+ return async (dispatch) => {
window.onbeforeunload = null
dispatch(actions.showLoadingIndication())
const txIds = txDataList.map(({id}) => id)
@@ -1807,7 +1817,7 @@ function removeSuggestedTokens () {
return (dispatch) => {
dispatch(actions.showLoadingIndication())
window.onbeforeunload = null
- return new Promise((resolve, reject) => {
+ return new Promise((resolve) => {
background.removeSuggestedTokens((err, suggestedTokens) => {
dispatch(actions.hideLoadingIndication())
if (err) {
@@ -1826,7 +1836,7 @@ function removeSuggestedTokens () {
}
function addKnownMethodData (fourBytePrefix, methodData) {
- return (dispatch) => {
+ return () => {
background.addKnownMethodData(fourBytePrefix, methodData)
}
}
@@ -1931,7 +1941,7 @@ function setProviderType (type) {
return (dispatch, getState) => {
const { type: currentProviderType } = getState().metamask.provider
log.debug(`background.setProviderType`, type)
- background.setProviderType(type, (err, result) => {
+ background.setProviderType(type, (err) => {
if (err) {
log.error(err)
return dispatch(actions.displayWarning('Had a problem changing networks!'))
@@ -1958,10 +1968,10 @@ function setPreviousProvider (type) {
}
}
-function updateAndSetCustomRpc (newRpc, chainId, ticker = 'ETH', nickname) {
+function updateAndSetCustomRpc (newRpc, chainId, ticker = 'ETH', nickname, rpcPrefs) {
return (dispatch) => {
log.debug(`background.updateAndSetCustomRpc: ${newRpc} ${chainId} ${ticker} ${nickname}`)
- background.updateAndSetCustomRpc(newRpc, chainId, ticker, nickname || newRpc, (err, result) => {
+ background.updateAndSetCustomRpc(newRpc, chainId, ticker, nickname || newRpc, rpcPrefs, (err) => {
if (err) {
log.error(err)
return dispatch(actions.displayWarning('Had a problem changing networks!'))
@@ -1974,10 +1984,33 @@ function updateAndSetCustomRpc (newRpc, chainId, ticker = 'ETH', nickname) {
}
}
+function editRpc (oldRpc, newRpc, chainId, ticker = 'ETH', nickname, rpcPrefs) {
+ return (dispatch) => {
+ log.debug(`background.delRpcTarget: ${oldRpc}`)
+ background.delCustomRpc(oldRpc, (err) => {
+ if (err) {
+ log.error(err)
+ return dispatch(self.displayWarning('Had a problem removing network!'))
+ }
+ dispatch(actions.setSelectedToken())
+ background.updateAndSetCustomRpc(newRpc, chainId, ticker, nickname || newRpc, rpcPrefs, (err) => {
+ if (err) {
+ log.error(err)
+ return dispatch(actions.displayWarning('Had a problem changing networks!'))
+ }
+ dispatch({
+ type: actions.SET_RPC_TARGET,
+ value: newRpc,
+ })
+ })
+ })
+ }
+}
+
function setRpcTarget (newRpc, chainId, ticker = 'ETH', nickname) {
return (dispatch) => {
log.debug(`background.setRpcTarget: ${newRpc} ${chainId} ${ticker} ${nickname}`)
- background.setCustomRpc(newRpc, chainId, ticker, nickname || newRpc, (err, result) => {
+ background.setCustomRpc(newRpc, chainId, ticker, nickname || newRpc, (err) => {
if (err) {
log.error(err)
return dispatch(actions.displayWarning('Had a problem changing networks!'))
@@ -1990,7 +2023,7 @@ function setRpcTarget (newRpc, chainId, ticker = 'ETH', nickname) {
function delRpcTarget (oldRpc) {
return (dispatch) => {
log.debug(`background.delRpcTarget: ${oldRpc}`)
- background.delCustomRpc(oldRpc, (err, result) => {
+ background.delCustomRpc(oldRpc, (err) => {
if (err) {
log.error(err)
return dispatch(self.displayWarning('Had a problem removing network!'))
@@ -2000,11 +2033,12 @@ function delRpcTarget (oldRpc) {
}
}
+
// Calls the addressBookController to add a new address.
function addToAddressBook (recipient, nickname = '') {
log.debug(`background.addToAddressBook`)
return (dispatch) => {
- background.setAddressBook(recipient, nickname, (err, result) => {
+ background.setAddressBook(recipient, nickname, (err) => {
if (err) {
log.error(err)
return dispatch(self.displayWarning('Address book failed to update'))
@@ -2273,7 +2307,7 @@ function pairUpdate (coin) {
}
}
-function shapeShiftSubview (network) {
+function shapeShiftSubview () {
var pair = 'btc_eth'
return (dispatch) => {
dispatch(actions.showSubLoadingIndication())
@@ -2309,7 +2343,7 @@ function coinShiftRquest (data, marketData) {
}
function buyWithShapeShift (data) {
- return dispatch => new Promise((resolve, reject) => {
+ return () => new Promise((resolve, reject) => {
shapeShiftRequest('shift', { method: 'POST', data}, (response) => {
if (response.error) {
return reject(response.error)
@@ -2356,7 +2390,7 @@ function shapeShiftRequest (query, options, cb) {
!options ? options = {} : null
options.method ? method = options.method : method = 'GET'
- var requestListner = function (request) {
+ var requestListner = function () {
try {
queryResponse = JSON.parse(this.responseText)
cb ? cb(queryResponse) : null
@@ -2439,6 +2473,10 @@ function setShowFiatConversionOnTestnetsPreference (value) {
return setPreference('showFiatInTestnets', value)
}
+function setAutoLogoutTimeLimit (value) {
+ return setPreference('autoLogoutTimeLimit', value)
+}
+
function setCompletedOnboarding () {
return async dispatch => {
dispatch(actions.showLoadingIndication())
@@ -2680,20 +2718,20 @@ function setPendingTokens (pendingTokens) {
}
}
-function approveProviderRequest (tabID) {
- return (dispatch) => {
- background.approveProviderRequest(tabID)
+function approveProviderRequestByOrigin (origin) {
+ return () => {
+ background.approveProviderRequestByOrigin(origin)
}
}
-function rejectProviderRequest (tabID) {
- return (dispatch) => {
- background.rejectProviderRequest(tabID)
+function rejectProviderRequestByOrigin (origin) {
+ return () => {
+ background.rejectProviderRequestByOrigin(origin)
}
}
function clearApprovedOrigins () {
- return (dispatch) => {
+ return () => {
background.clearApprovedOrigins()
}
}
@@ -2712,3 +2750,27 @@ function setFirstTimeFlowType (type) {
})
}
}
+
+function setSelectedSettingsRpcUrl (newRpcUrl) {
+ return {
+ type: actions.SET_SELECTED_SETTINGS_RPC_URL,
+ value: newRpcUrl,
+ }
+}
+
+function setNetworksTabAddMode (isInAddMode) {
+ return {
+ type: actions.SET_NETWORKS_TAB_ADD_MODE,
+ value: isInAddMode,
+ }
+}
+
+function setLastActiveTime () {
+ return (dispatch) => {
+ background.setLastActiveTime((err) => {
+ if (err) {
+ return dispatch(actions.displayWarning(err.message))
+ }
+ })
+ }
+}
diff --git a/ui/example.js b/ui/example.js
index 4627c0e9c..d940d3bc8 100644
--- a/ui/example.js
+++ b/ui/example.js
@@ -91,7 +91,7 @@ accountManager.setSelectedAccount = function (address, cb) {
this._didUpdate()
}
-accountManager.signTransaction = function (txParams, cb) {
+accountManager.signTransaction = function () {
alert('signing tx....')
}
diff --git a/ui/lib/account-link.js b/ui/lib/account-link.js
index 3eaa7cf71..f1428ba92 100644
--- a/ui/lib/account-link.js
+++ b/ui/lib/account-link.js
@@ -1,4 +1,8 @@
-module.exports = function (address, network) {
+module.exports = function (address, network, rpcPrefs) {
+ if (rpcPrefs.blockExplorerUrl) {
+ return `${rpcPrefs.blockExplorerUrl}/address/${address}`
+ }
+
const net = parseInt(network)
let link
switch (net) {
diff --git a/ui/lib/test-timeout.js b/ui/lib/test-timeout.js
index 957b0fce2..7d825487f 100644
--- a/ui/lib/test-timeout.js
+++ b/ui/lib/test-timeout.js
@@ -1,5 +1,5 @@
export default function timeout (time) {
- return new Promise((resolve, reject) => {
+ return new Promise((resolve) => {
setTimeout(resolve, time || 1500)
})
}