aboutsummaryrefslogtreecommitdiffstats
path: root/app
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 /app
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)
Diffstat (limited to 'app')
-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
28 files changed, 573 insertions, 769 deletions
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()