aboutsummaryrefslogtreecommitdiffstats
path: root/app/scripts
diff options
context:
space:
mode:
Diffstat (limited to 'app/scripts')
-rw-r--r--app/scripts/background.js10
-rw-r--r--app/scripts/contentscript.js25
-rw-r--r--app/scripts/controllers/blacklist.js22
-rw-r--r--app/scripts/controllers/network/createInfuraClient.js10
-rw-r--r--app/scripts/controllers/network/createJsonRpcClient.js4
-rw-r--r--app/scripts/controllers/network/createLocalhostClient.js4
-rw-r--r--app/scripts/controllers/network/createMetamaskMiddleware.js2
-rw-r--r--app/scripts/controllers/preferences.js31
-rw-r--r--app/scripts/controllers/transactions/enums.js12
-rw-r--r--app/scripts/controllers/transactions/index.js90
-rw-r--r--app/scripts/controllers/transactions/tx-state-manager.js6
-rw-r--r--app/scripts/inpage.js36
-rw-r--r--app/scripts/lib/account-tracker.js3
-rw-r--r--app/scripts/lib/auto-reload.js6
-rw-r--r--app/scripts/lib/config-manager.js254
-rw-r--r--app/scripts/lib/ipfsContent.js4
-rw-r--r--app/scripts/lib/message-manager.js2
-rw-r--r--app/scripts/lib/personal-message-manager.js2
-rw-r--r--app/scripts/lib/typed-message-manager.js74
-rw-r--r--app/scripts/metamask-controller.js206
-rw-r--r--app/scripts/migrations/_multi-keyring.js50
-rw-r--r--app/scripts/notice-controller.js2
-rw-r--r--app/scripts/phishing-detect.js59
23 files changed, 478 insertions, 436 deletions
diff --git a/app/scripts/background.js b/app/scripts/background.js
index e13d2842a..b91021c6e 100644
--- a/app/scripts/background.js
+++ b/app/scripts/background.js
@@ -38,7 +38,6 @@ const {
const firstTimeState = Object.assign({}, rawFirstTimeState, global.METAMASK_TEST_CONFIG)
const STORAGE_KEY = 'metamask-config'
-const METAMASK_DEBUG = process.env.METAMASK_DEBUG
log.setDefaultLevel(process.env.METAMASK_DEBUG ? 'debug' : 'warn')
@@ -414,6 +413,7 @@ function setupController (initState, initLangCode) {
controller.txController.on('update:badge', updateBadge)
controller.messageManager.on('updateBadge', updateBadge)
controller.personalMessageManager.on('updateBadge', updateBadge)
+ controller.typedMessageManager.on('updateBadge', updateBadge)
/**
* Updates the Web Extension's "badge" number, on the little fox in the toolbar.
@@ -469,11 +469,3 @@ function showWatchAssetUi () {
}
)
}
-
-// On first install, open a window to MetaMask website to how-it-works.
-extension.runtime.onInstalled.addListener(function (details) {
- if ((details.reason === 'install') && (!METAMASK_DEBUG)) {
- extension.tabs.create({url: 'https://metamask.io/#how-it-works'})
- }
-})
-
diff --git a/app/scripts/contentscript.js b/app/scripts/contentscript.js
index 6eee1987a..33523eb46 100644
--- a/app/scripts/contentscript.js
+++ b/app/scripts/contentscript.js
@@ -1,6 +1,7 @@
const fs = require('fs')
const path = require('path')
const pump = require('pump')
+const querystring = require('querystring')
const LocalMessageDuplexStream = require('post-message-stream')
const PongStream = require('ping-pong-stream/pong')
const ObjectMultiplex = require('obj-multiplex')
@@ -134,17 +135,22 @@ function doctypeCheck () {
}
/**
- * Checks the current document extension
+ * Returns whether or not the extension (suffix) of the current document is prohibited
*
- * @returns {boolean} {@code true} if the current extension is not prohibited
+ * This checks {@code window.location.pathname} against a set of file extensions
+ * that should not have web3 injected into them. This check is indifferent of query parameters
+ * in the location.
+ *
+ * @returns {boolean} whether or not the extension of the current document is prohibited
*/
function suffixCheck () {
- var prohibitedTypes = ['xml', 'pdf']
- var currentUrl = window.location.href
- var currentRegex
+ const prohibitedTypes = [
+ /\.xml$/,
+ /\.pdf$/,
+ ]
+ const currentUrl = window.location.pathname
for (let i = 0; i < prohibitedTypes.length; i++) {
- currentRegex = new RegExp(`\\.${prohibitedTypes[i]}$`)
- if (currentRegex.test(currentUrl)) {
+ if (prohibitedTypes[i].test(currentUrl)) {
return false
}
}
@@ -199,5 +205,8 @@ function blacklistedDomainCheck () {
function redirectToPhishingWarning () {
console.log('MetaMask - routing to Phishing Warning component')
const extensionURL = extension.runtime.getURL('phishing.html')
- window.location.href = extensionURL
+ window.location.href = `${extensionURL}#${querystring.stringify({
+ hostname: window.location.hostname,
+ href: window.location.href,
+ })}`
}
diff --git a/app/scripts/controllers/blacklist.js b/app/scripts/controllers/blacklist.js
index 1d2191433..89c7cc888 100644
--- a/app/scripts/controllers/blacklist.js
+++ b/app/scripts/controllers/blacklist.js
@@ -29,6 +29,7 @@ class BlacklistController {
constructor (opts = {}) {
const initState = extend({
phishing: PHISHING_DETECTION_CONFIG,
+ whitelist: [],
}, opts.initState)
this.store = new ObservableStore(initState)
// phishing detector
@@ -39,6 +40,21 @@ class BlacklistController {
}
/**
+ * 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
@@ -48,6 +64,12 @@ class BlacklistController {
*/
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
}
diff --git a/app/scripts/controllers/network/createInfuraClient.js b/app/scripts/controllers/network/createInfuraClient.js
index 41af4d9f9..326bcb355 100644
--- a/app/scripts/controllers/network/createInfuraClient.js
+++ b/app/scripts/controllers/network/createInfuraClient.js
@@ -1,5 +1,6 @@
const mergeMiddleware = require('json-rpc-engine/src/mergeMiddleware')
-const createBlockReEmitMiddleware = require('eth-json-rpc-middleware/block-reemit')
+const createBlockReRefMiddleware = require('eth-json-rpc-middleware/block-ref')
+const createRetryOnEmptyMiddleware = require('eth-json-rpc-middleware/retryOnEmpty')
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')
@@ -11,13 +12,14 @@ module.exports = createInfuraClient
function createInfuraClient ({ network }) {
const infuraMiddleware = createInfuraMiddleware({ network })
- const blockProvider = providerFromMiddleware(infuraMiddleware)
- const blockTracker = new BlockTracker({ provider: blockProvider })
+ const infuraProvider = providerFromMiddleware(infuraMiddleware)
+ const blockTracker = new BlockTracker({ provider: infuraProvider })
const networkMiddleware = mergeMiddleware([
createBlockCacheMiddleware({ blockTracker }),
createInflightMiddleware(),
- createBlockReEmitMiddleware({ blockTracker, provider: blockProvider }),
+ createBlockReRefMiddleware({ blockTracker, provider: infuraProvider }),
+ createRetryOnEmptyMiddleware({ blockTracker, provider: infuraProvider }),
createBlockTrackerInspectorMiddleware({ blockTracker }),
infuraMiddleware,
])
diff --git a/app/scripts/controllers/network/createJsonRpcClient.js b/app/scripts/controllers/network/createJsonRpcClient.js
index 40c353f7f..a8cbf2aaf 100644
--- a/app/scripts/controllers/network/createJsonRpcClient.js
+++ b/app/scripts/controllers/network/createJsonRpcClient.js
@@ -1,6 +1,6 @@
const mergeMiddleware = require('json-rpc-engine/src/mergeMiddleware')
const createFetchMiddleware = require('eth-json-rpc-middleware/fetch')
-const createBlockRefMiddleware = require('eth-json-rpc-middleware/block-ref')
+const createBlockRefRewriteMiddleware = require('eth-json-rpc-middleware/block-ref-rewrite')
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')
@@ -15,7 +15,7 @@ function createJsonRpcClient ({ rpcUrl }) {
const blockTracker = new BlockTracker({ provider: blockProvider })
const networkMiddleware = mergeMiddleware([
- createBlockRefMiddleware({ blockTracker }),
+ createBlockRefRewriteMiddleware({ blockTracker }),
createBlockCacheMiddleware({ blockTracker }),
createInflightMiddleware(),
createBlockTrackerInspectorMiddleware({ blockTracker }),
diff --git a/app/scripts/controllers/network/createLocalhostClient.js b/app/scripts/controllers/network/createLocalhostClient.js
index fecc512e8..09b1d3c1c 100644
--- a/app/scripts/controllers/network/createLocalhostClient.js
+++ b/app/scripts/controllers/network/createLocalhostClient.js
@@ -1,6 +1,6 @@
const mergeMiddleware = require('json-rpc-engine/src/mergeMiddleware')
const createFetchMiddleware = require('eth-json-rpc-middleware/fetch')
-const createBlockRefMiddleware = require('eth-json-rpc-middleware/block-ref')
+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 BlockTracker = require('eth-block-tracker')
@@ -13,7 +13,7 @@ function createLocalhostClient () {
const blockTracker = new BlockTracker({ provider: blockProvider, pollingInterval: 1000 })
const networkMiddleware = mergeMiddleware([
- createBlockRefMiddleware({ blockTracker }),
+ createBlockRefRewriteMiddleware({ blockTracker }),
createBlockTrackerInspectorMiddleware({ blockTracker }),
fetchMiddleware,
])
diff --git a/app/scripts/controllers/network/createMetamaskMiddleware.js b/app/scripts/controllers/network/createMetamaskMiddleware.js
index 8b17829b7..9e6a45888 100644
--- a/app/scripts/controllers/network/createMetamaskMiddleware.js
+++ b/app/scripts/controllers/network/createMetamaskMiddleware.js
@@ -38,6 +38,6 @@ function createPendingNonceMiddleware ({ getPendingNonce }) {
const address = req.params[0]
const blockRef = req.params[1]
if (blockRef !== 'pending') return next()
- req.result = await getPendingNonce(address)
+ res.result = await getPendingNonce(address)
})
}
diff --git a/app/scripts/controllers/preferences.js b/app/scripts/controllers/preferences.js
index 464a37017..fd6a4866d 100644
--- a/app/scripts/controllers/preferences.js
+++ b/app/scripts/controllers/preferences.js
@@ -36,6 +36,8 @@ class PreferencesController {
currentLocale: opts.initLangCode,
identities: {},
lostIdentities: {},
+ seedWords: null,
+ forgottenPassword: false,
}, opts.initState)
this.diagnostics = opts.diagnostics
@@ -47,6 +49,22 @@ class PreferencesController {
// PUBLIC METHODS
/**
+ * Sets the {@code forgottenPassword} state property
+ * @param {boolean} forgottenPassword whether or not the user has forgotten their password
+ */
+ setPasswordForgotten (forgottenPassword) {
+ this.store.updateState({ forgottenPassword })
+ }
+
+ /**
+ * Sets the {@code seedWords} seed words
+ * @param {string|null} seedWords the seed words
+ */
+ setSeedWords (seedWords) {
+ this.store.updateState({ seedWords })
+ }
+
+ /**
* Setter for the `useBlockie` property
*
* @param {boolean} val Whether or not the user prefers blockie indicators
@@ -357,11 +375,12 @@ class PreferencesController {
* Gets an updated rpc list from this.addToFrequentRpcList() and sets the `frequentRpcList` to this update list.
*
* @param {string} _url The the new rpc url to add to the updated list
+ * @param {bool} remove Remove selected url
* @returns {Promise<void>} Promise resolves with undefined
*
*/
- updateFrequentRpcList (_url) {
- return this.addToFrequentRpcList(_url)
+ updateFrequentRpcList (_url, remove = false) {
+ return this.addToFrequentRpcList(_url, remove)
.then((rpcList) => {
this.store.updateState({ frequentRpcList: rpcList })
return Promise.resolve()
@@ -388,21 +407,19 @@ class PreferencesController {
* end of the list. The current list is modified and returned as a promise.
*
* @param {string} _url The rpc url to add to the frequentRpcList.
+ * @param {bool} remove Remove selected url
* @returns {Promise<array>} The updated frequentRpcList.
*
*/
- addToFrequentRpcList (_url) {
+ addToFrequentRpcList (_url, remove = false) {
const rpcList = this.getFrequentRpcList()
const index = rpcList.findIndex((element) => { return element === _url })
if (index !== -1) {
rpcList.splice(index, 1)
}
- if (_url !== 'http://localhost:8545') {
+ if (!remove && _url !== 'http://localhost:8545') {
rpcList.push(_url)
}
- if (rpcList.length > 3) {
- rpcList.shift()
- }
return Promise.resolve(rpcList)
}
diff --git a/app/scripts/controllers/transactions/enums.js b/app/scripts/controllers/transactions/enums.js
new file mode 100644
index 000000000..be6f16e0d
--- /dev/null
+++ b/app/scripts/controllers/transactions/enums.js
@@ -0,0 +1,12 @@
+const TRANSACTION_TYPE_CANCEL = 'cancel'
+const TRANSACTION_TYPE_RETRY = 'retry'
+const TRANSACTION_TYPE_STANDARD = 'standard'
+
+const TRANSACTION_STATUS_APPROVED = 'approved'
+
+module.exports = {
+ TRANSACTION_TYPE_CANCEL,
+ TRANSACTION_TYPE_RETRY,
+ TRANSACTION_TYPE_STANDARD,
+ TRANSACTION_STATUS_APPROVED,
+}
diff --git a/app/scripts/controllers/transactions/index.js b/app/scripts/controllers/transactions/index.js
index 5d7d6d6da..9f2290924 100644
--- a/app/scripts/controllers/transactions/index.js
+++ b/app/scripts/controllers/transactions/index.js
@@ -11,6 +11,14 @@ const txUtils = require('./lib/util')
const cleanErrorStack = require('../../lib/cleanErrorStack')
const log = require('loglevel')
const recipientBlacklistChecker = require('./lib/recipient-blacklist-checker')
+const {
+ TRANSACTION_TYPE_CANCEL,
+ TRANSACTION_TYPE_RETRY,
+ TRANSACTION_TYPE_STANDARD,
+ TRANSACTION_STATUS_APPROVED,
+} = require('./enums')
+
+const { hexToBn, bnToHex, BnMultiplyByFraction } = require('../../lib/util')
/**
Transaction Controller is an aggregate of sub-controllers and trackers
@@ -158,9 +166,16 @@ class TransactionController extends EventEmitter {
async addUnapprovedTransaction (txParams) {
// validate
const normalizedTxParams = txUtils.normalizeTxParams(txParams)
+ // Assert the from address is the selected address
+ if (normalizedTxParams.from !== this.getSelectedAddress()) {
+ throw new Error(`Transaction from address isn't valid for this account`)
+ }
txUtils.validateTxParams(normalizedTxParams)
// construct txMeta
- let txMeta = this.txStateManager.generateTxMeta({ txParams: normalizedTxParams })
+ let txMeta = this.txStateManager.generateTxMeta({
+ txParams: normalizedTxParams,
+ type: TRANSACTION_TYPE_STANDARD,
+ })
this.addTx(txMeta)
this.emit('newUnapprovedTx', txMeta)
@@ -214,6 +229,7 @@ class TransactionController extends EventEmitter {
txParams: originalTxMeta.txParams,
lastGasPrice,
loadingDefaults: false,
+ type: TRANSACTION_TYPE_RETRY,
})
this.addTx(txMeta)
this.emit('newUnapprovedTx', txMeta)
@@ -221,6 +237,40 @@ class TransactionController extends EventEmitter {
}
/**
+ * Creates a new approved transaction to attempt to cancel a previously submitted transaction. The
+ * new transaction contains the same nonce as the previous, is a basic ETH transfer of 0x value to
+ * the sender's address, and has a higher gasPrice than that of the previous transaction.
+ * @param {number} originalTxId - the id of the txMeta that you want to attempt to cancel
+ * @param {string=} customGasPrice - the hex value to use for the cancel transaction
+ * @returns {txMeta}
+ */
+ async createCancelTransaction (originalTxId, customGasPrice) {
+ const originalTxMeta = this.txStateManager.getTx(originalTxId)
+ const { txParams } = originalTxMeta
+ const { gasPrice: lastGasPrice, from, nonce } = txParams
+
+ const newGasPrice = customGasPrice || bnToHex(BnMultiplyByFraction(hexToBn(lastGasPrice), 11, 10))
+ const newTxMeta = this.txStateManager.generateTxMeta({
+ txParams: {
+ from,
+ to: from,
+ nonce,
+ gas: '0x5208',
+ value: '0x0',
+ gasPrice: newGasPrice,
+ },
+ lastGasPrice,
+ loadingDefaults: false,
+ status: TRANSACTION_STATUS_APPROVED,
+ type: TRANSACTION_TYPE_CANCEL,
+ })
+
+ this.addTx(newTxMeta)
+ await this.approveTransaction(newTxMeta.id)
+ return newTxMeta
+ }
+
+ /**
updates the txMeta in the txStateManager
@param txMeta {Object} - the updated txMeta
*/
@@ -316,7 +366,40 @@ class TransactionController extends EventEmitter {
this.txStateManager.setTxStatusSubmitted(txId)
}
- confirmTransaction (txId) {
+ /**
+ * Sets the status of the transaction to confirmed and sets the status of nonce duplicates as
+ * dropped if the txParams have data it will fetch the txReceipt
+ * @param {number} txId - The tx's ID
+ * @returns {Promise<void>}
+ */
+ async confirmTransaction (txId) {
+ // get the txReceipt before marking the transaction confirmed
+ // to ensure the receipt is gotten before the ui revives the tx
+ const txMeta = this.txStateManager.getTx(txId)
+
+ if (!txMeta) {
+ return
+ }
+
+ try {
+ const txReceipt = await this.query.getTransactionReceipt(txMeta.hash)
+
+ // It seems that sometimes the numerical values being returned from
+ // this.query.getTransactionReceipt are BN instances and not strings.
+ const gasUsed = typeof txReceipt.gasUsed !== 'string'
+ ? txReceipt.gasUsed.toString(16)
+ : txReceipt.gasUsed
+
+ txMeta.txReceipt = {
+ ...txReceipt,
+ gasUsed,
+ }
+
+ this.txStateManager.updateTx(txMeta, 'transactions#confirmTransaction - add txReceipt')
+ } catch (err) {
+ log.error(err)
+ }
+
this.txStateManager.setTxStatusConfirmed(txId)
this._markNonceDuplicatesDropped(txId)
}
@@ -393,7 +476,7 @@ class TransactionController extends EventEmitter {
})
this.txStateManager.getFilteredTxList({
- status: 'approved',
+ status: TRANSACTION_STATUS_APPROVED,
}).forEach((txMeta) => {
const txSignError = new Error('Transaction found as "approved" during boot - possibly stuck during signing')
this.txStateManager.setTxStatusFailed(txMeta.id, txSignError)
@@ -484,6 +567,7 @@ class TransactionController extends EventEmitter {
Updates the memStore in transaction controller
*/
_updateMemstore () {
+ this.pendingTxTracker.updatePendingTxs()
const unapprovedTxs = this.txStateManager.getUnapprovedTxList()
const selectedAddressTxList = this.txStateManager.getFilteredTxList({
from: this.getSelectedAddress(),
diff --git a/app/scripts/controllers/transactions/tx-state-manager.js b/app/scripts/controllers/transactions/tx-state-manager.js
index 28a18ca2e..58c48e34e 100644
--- a/app/scripts/controllers/transactions/tx-state-manager.js
+++ b/app/scripts/controllers/transactions/tx-state-manager.js
@@ -353,6 +353,7 @@ class TransactionStateManager extends EventEmitter {
const txMeta = this.getTx(txId)
txMeta.err = {
message: err.toString(),
+ rpc: err.value,
stack: err.stack,
}
this.updateTx(txMeta)
@@ -399,6 +400,11 @@ class TransactionStateManager extends EventEmitter {
*/
_setTxStatus (txId, status) {
const txMeta = this.getTx(txId)
+
+ if (!txMeta) {
+ return
+ }
+
txMeta.status = status
setTimeout(() => {
try {
diff --git a/app/scripts/inpage.js b/app/scripts/inpage.js
index 1a170c617..431702d63 100644
--- a/app/scripts/inpage.js
+++ b/app/scripts/inpage.js
@@ -5,10 +5,16 @@ const log = require('loglevel')
const LocalMessageDuplexStream = require('post-message-stream')
const setupDappAutoReload = require('./lib/auto-reload.js')
const MetamaskInpageProvider = require('metamask-inpage-provider')
+
restoreContextAfterImports()
log.setDefaultLevel(process.env.METAMASK_DEBUG ? 'debug' : 'warn')
+console.warn('ATTENTION: In an effort to improve user privacy, MetaMask will ' +
+'stop exposing user accounts to dapps by default beginning November 2nd, 2018. ' +
+'Dapps should call provider.enable() in order to view and use accounts. Please see ' +
+'https://bit.ly/2QQHXvF for complete information and up-to-date example code.')
+
//
// setup plugin communication
//
@@ -22,6 +28,33 @@ var metamaskStream = new LocalMessageDuplexStream({
// compose the inpage provider
var inpageProvider = new MetamaskInpageProvider(metamaskStream)
+// Augment the provider with its enable method
+inpageProvider.enable = function (options = {}) {
+ return new Promise((resolve, reject) => {
+ if (options.mockRejection) {
+ reject('User rejected account access')
+ } else {
+ inpageProvider.sendAsync({ method: 'eth_accounts', params: [] }, (error, response) => {
+ if (error) {
+ reject(error)
+ } else {
+ resolve(response.result)
+ }
+ })
+ }
+ })
+}
+
+// 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, {
+ // straight up lie that we deleted the property so that it doesnt
+ // throw an error in strict mode
+ deleteProperty: () => true,
+})
+
+window.ethereum = proxiedInpageProvider
+
//
// setup web3
//
@@ -33,7 +66,8 @@ if (typeof window.web3 !== 'undefined') {
or MetaMask and another web3 extension. Please remove one
and try again.`)
}
-var web3 = new Web3(inpageProvider)
+
+var web3 = new Web3(proxiedInpageProvider)
web3.setProvider = function () {
log.debug('MetaMask - overrode web3.setProvider')
}
diff --git a/app/scripts/lib/account-tracker.js b/app/scripts/lib/account-tracker.js
index 3a52d5e8d..2e9340018 100644
--- a/app/scripts/lib/account-tracker.js
+++ b/app/scripts/lib/account-tracker.js
@@ -45,6 +45,9 @@ class AccountTracker {
this._blockTracker = opts.blockTracker
// blockTracker.currentBlock may be null
this._currentBlockNumber = this._blockTracker.getCurrentBlock()
+ this._blockTracker.once('latest', blockNumber => {
+ this._currentBlockNumber = blockNumber
+ })
// bind function for easier listener syntax
this._updateForBlock = this._updateForBlock.bind(this)
}
diff --git a/app/scripts/lib/auto-reload.js b/app/scripts/lib/auto-reload.js
index cce31c3d2..558391a06 100644
--- a/app/scripts/lib/auto-reload.js
+++ b/app/scripts/lib/auto-reload.js
@@ -2,18 +2,12 @@ module.exports = setupDappAutoReload
function setupDappAutoReload (web3, observable) {
// export web3 as a global, checking for usage
- let hasBeenWarned = false
let reloadInProgress = false
let lastTimeUsed
let lastSeenNetwork
global.web3 = new Proxy(web3, {
get: (_web3, key) => {
- // show warning once on web3 access
- if (!hasBeenWarned && key !== 'currentProvider') {
- console.warn('MetaMask: web3 will be deprecated in the near future in favor of the ethereumProvider \nhttps://github.com/MetaMask/faq/blob/master/detecting_metamask.md#web3-deprecation')
- hasBeenWarned = true
- }
// get the time of use
lastTimeUsed = Date.now()
// return value normally
diff --git a/app/scripts/lib/config-manager.js b/app/scripts/lib/config-manager.js
deleted file mode 100644
index 221746467..000000000
--- a/app/scripts/lib/config-manager.js
+++ /dev/null
@@ -1,254 +0,0 @@
-const ethUtil = require('ethereumjs-util')
-const normalize = require('eth-sig-util').normalize
-const {
- MAINNET_RPC_URL,
- ROPSTEN_RPC_URL,
- KOVAN_RPC_URL,
- RINKEBY_RPC_URL,
-} = require('../controllers/network/enums')
-
-/* The config-manager is a convenience object
- * wrapping a pojo-migrator.
- *
- * It exists mostly to allow the creation of
- * convenience methods to access and persist
- * particular portions of the state.
- */
-module.exports = ConfigManager
-function ConfigManager (opts) {
- // ConfigManager is observable and will emit updates
- this._subs = []
- this.store = opts.store
-}
-
-ConfigManager.prototype.setConfig = function (config) {
- var data = this.getData()
- data.config = config
- this.setData(data)
- this._emitUpdates(config)
-}
-
-ConfigManager.prototype.getConfig = function () {
- var data = this.getData()
- return data.config
-}
-
-ConfigManager.prototype.setData = function (data) {
- this.store.putState(data)
-}
-
-ConfigManager.prototype.getData = function () {
- return this.store.getState()
-}
-
-ConfigManager.prototype.setPasswordForgotten = function (passwordForgottenState) {
- const data = this.getData()
- data.forgottenPassword = passwordForgottenState
- this.setData(data)
-}
-
-ConfigManager.prototype.getPasswordForgotten = function (passwordForgottenState) {
- const data = this.getData()
- return data.forgottenPassword
-}
-
-ConfigManager.prototype.setWallet = function (wallet) {
- var data = this.getData()
- data.wallet = wallet
- this.setData(data)
-}
-
-ConfigManager.prototype.setVault = function (encryptedString) {
- var data = this.getData()
- data.vault = encryptedString
- this.setData(data)
-}
-
-ConfigManager.prototype.getVault = function () {
- var data = this.getData()
- return data.vault
-}
-
-ConfigManager.prototype.getKeychains = function () {
- return this.getData().keychains || []
-}
-
-ConfigManager.prototype.setKeychains = function (keychains) {
- var data = this.getData()
- data.keychains = keychains
- this.setData(data)
-}
-
-ConfigManager.prototype.getSelectedAccount = function () {
- var config = this.getConfig()
- return config.selectedAccount
-}
-
-ConfigManager.prototype.setSelectedAccount = function (address) {
- var config = this.getConfig()
- config.selectedAccount = ethUtil.addHexPrefix(address)
- this.setConfig(config)
-}
-
-ConfigManager.prototype.getWallet = function () {
- return this.getData().wallet
-}
-
-// Takes a boolean
-ConfigManager.prototype.setShowSeedWords = function (should) {
- var data = this.getData()
- data.showSeedWords = should
- this.setData(data)
-}
-
-
-ConfigManager.prototype.getShouldShowSeedWords = function () {
- var data = this.getData()
- return data.showSeedWords
-}
-
-ConfigManager.prototype.setSeedWords = function (words) {
- var data = this.getData()
- data.seedWords = words
- this.setData(data)
-}
-
-ConfigManager.prototype.getSeedWords = function () {
- var data = this.getData()
- return data.seedWords
-}
-ConfigManager.prototype.setRpcTarget = function (rpcUrl) {
- var config = this.getConfig()
- config.provider = {
- type: 'rpc',
- rpcTarget: rpcUrl,
- }
- this.setConfig(config)
-}
-
-ConfigManager.prototype.setProviderType = function (type) {
- var config = this.getConfig()
- config.provider = {
- type: type,
- }
- this.setConfig(config)
-}
-
-ConfigManager.prototype.useEtherscanProvider = function () {
- var config = this.getConfig()
- config.provider = {
- type: 'etherscan',
- }
- this.setConfig(config)
-}
-
-ConfigManager.prototype.getProvider = function () {
- var config = this.getConfig()
- return config.provider
-}
-
-ConfigManager.prototype.getCurrentRpcAddress = function () {
- var provider = this.getProvider()
- if (!provider) return null
- switch (provider.type) {
-
- case 'mainnet':
- return MAINNET_RPC_URL
-
- case 'ropsten':
- return ROPSTEN_RPC_URL
-
- case 'kovan':
- return KOVAN_RPC_URL
-
- case 'rinkeby':
- return RINKEBY_RPC_URL
-
- default:
- return provider && provider.rpcTarget ? provider.rpcTarget : RINKEBY_RPC_URL
- }
-}
-
-//
-// Tx
-//
-
-ConfigManager.prototype.getTxList = function () {
- var data = this.getData()
- if (data.transactions !== undefined) {
- return data.transactions
- } else {
- return []
- }
-}
-
-ConfigManager.prototype.setTxList = function (txList) {
- var data = this.getData()
- data.transactions = txList
- this.setData(data)
-}
-
-
-// wallet nickname methods
-
-ConfigManager.prototype.getWalletNicknames = function () {
- var data = this.getData()
- const nicknames = ('walletNicknames' in data) ? data.walletNicknames : {}
- return nicknames
-}
-
-ConfigManager.prototype.nicknameForWallet = function (account) {
- const address = normalize(account)
- const nicknames = this.getWalletNicknames()
- return nicknames[address]
-}
-
-ConfigManager.prototype.setNicknameForWallet = function (account, nickname) {
- const address = normalize(account)
- const nicknames = this.getWalletNicknames()
- nicknames[address] = nickname
- var data = this.getData()
- data.walletNicknames = nicknames
- this.setData(data)
-}
-
-// observable
-
-ConfigManager.prototype.getSalt = function () {
- var data = this.getData()
- return data.salt
-}
-
-ConfigManager.prototype.setSalt = function (salt) {
- var data = this.getData()
- data.salt = salt
- this.setData(data)
-}
-
-ConfigManager.prototype.subscribe = function (fn) {
- this._subs.push(fn)
- var unsubscribe = this.unsubscribe.bind(this, fn)
- return unsubscribe
-}
-
-ConfigManager.prototype.unsubscribe = function (fn) {
- var index = this._subs.indexOf(fn)
- if (index !== -1) this._subs.splice(index, 1)
-}
-
-ConfigManager.prototype._emitUpdates = function (state) {
- this._subs.forEach(function (handler) {
- handler(state)
- })
-}
-
-ConfigManager.prototype.setLostAccounts = function (lostAccounts) {
- var data = this.getData()
- data.lostAccounts = lostAccounts
- this.setData(data)
-}
-
-ConfigManager.prototype.getLostAccounts = function () {
- var data = this.getData()
- return data.lostAccounts || []
-}
diff --git a/app/scripts/lib/ipfsContent.js b/app/scripts/lib/ipfsContent.js
index 38682b916..8b08453c4 100644
--- a/app/scripts/lib/ipfsContent.js
+++ b/app/scripts/lib/ipfsContent.js
@@ -5,6 +5,8 @@ module.exports = function (provider) {
function ipfsContent (details) {
const name = details.url.substring(7, details.url.length - 1)
let clearTime = null
+ if (/^.+\.eth$/.test(name) === false) return
+
extension.tabs.query({active: true}, tab => {
extension.tabs.update(tab.id, { url: 'loading.html' })
@@ -34,7 +36,7 @@ module.exports = function (provider) {
return { cancel: true }
}
- extension.webRequest.onErrorOccurred.addListener(ipfsContent, {urls: ['*://*.eth/']})
+ extension.webRequest.onErrorOccurred.addListener(ipfsContent, {urls: ['*://*.eth/'], types: ['main_frame']})
return {
remove () {
diff --git a/app/scripts/lib/message-manager.js b/app/scripts/lib/message-manager.js
index 47925b94b..e86629590 100644
--- a/app/scripts/lib/message-manager.js
+++ b/app/scripts/lib/message-manager.js
@@ -272,6 +272,6 @@ function normalizeMsgData (data) {
return data
} else {
// data is unicode, convert to hex
- return ethUtil.bufferToHex(new Buffer(data, 'utf8'))
+ return ethUtil.bufferToHex(Buffer.from(data, 'utf8'))
}
}
diff --git a/app/scripts/lib/personal-message-manager.js b/app/scripts/lib/personal-message-manager.js
index fc2cccdf1..fdb94f5ec 100644
--- a/app/scripts/lib/personal-message-manager.js
+++ b/app/scripts/lib/personal-message-manager.js
@@ -285,7 +285,7 @@ module.exports = class PersonalMessageManager extends EventEmitter {
log.debug(`Message was not hex encoded, interpreting as utf8.`)
}
- return ethUtil.bufferToHex(new Buffer(data, 'utf8'))
+ return ethUtil.bufferToHex(Buffer.from(data, 'utf8'))
}
}
diff --git a/app/scripts/lib/typed-message-manager.js b/app/scripts/lib/typed-message-manager.js
index e5e1c94b3..b10145f3b 100644
--- a/app/scripts/lib/typed-message-manager.js
+++ b/app/scripts/lib/typed-message-manager.js
@@ -4,6 +4,7 @@ const createId = require('./random-id')
const assert = require('assert')
const sigUtil = require('eth-sig-util')
const log = require('loglevel')
+const jsonschema = require('jsonschema')
/**
* Represents, and contains data about, an 'eth_signTypedData' type signature request. These are created when a
@@ -17,7 +18,7 @@ const log = require('loglevel')
* @property {Object} msgParams.from The address that is making the signature request.
* @property {string} msgParams.data A hex string conversion of the raw buffer data of the signature request
* @property {number} time The epoch time at which the this message was created
- * @property {string} status Indicates whether the signature request is 'unapproved', 'approved', 'signed' or 'rejected'
+ * @property {string} status Indicates whether the signature request is 'unapproved', 'approved', 'signed', 'rejected', or 'errored'
* @property {string} type The json-prc signing method for which a signature request has been made. A 'Message' will
* always have a 'eth_signTypedData' type.
*
@@ -26,17 +27,10 @@ const log = require('loglevel')
module.exports = class TypedMessageManager extends EventEmitter {
/**
* Controller in charge of managing - storing, adding, removing, updating - TypedMessage.
- *
- * @typedef {Object} TypedMessage
- * @param {Object} opts @deprecated
- * @property {Object} memStore The observable store where TypedMessage are saved.
- * @property {Object} memStore.unapprovedTypedMessages A collection of all TypedMessages in the 'unapproved' state
- * @property {number} memStore.unapprovedTypedMessagesCount The count of all TypedMessages in this.memStore.unapprobedMsgs
- * @property {array} messages Holds all messages that have been created by this TypedMessage
- *
*/
- constructor (opts) {
+ constructor ({ networkController }) {
super()
+ this.networkController = networkController
this.memStore = new ObservableStore({
unapprovedTypedMessages: {},
unapprovedTypedMessagesCount: 0,
@@ -76,15 +70,17 @@ module.exports = class TypedMessageManager extends EventEmitter {
* @returns {promise} When the message has been signed or rejected
*
*/
- addUnapprovedMessageAsync (msgParams, req) {
+ addUnapprovedMessageAsync (msgParams, req, version) {
return new Promise((resolve, reject) => {
- const msgId = this.addUnapprovedMessage(msgParams, req)
+ const msgId = this.addUnapprovedMessage(msgParams, req, version)
this.once(`${msgId}:finished`, (data) => {
switch (data.status) {
case 'signed':
return resolve(data.rawSig)
case 'rejected':
return reject(new Error('MetaMask Message Signature: User denied message signature.'))
+ case 'errored':
+ return reject(new Error(`MetaMask Message Signature: ${data.error}`))
default:
return reject(new Error(`MetaMask Message Signature: Unknown problem: ${JSON.stringify(msgParams)}`))
}
@@ -102,7 +98,8 @@ module.exports = class TypedMessageManager extends EventEmitter {
* @returns {number} The id of the newly created TypedMessage.
*
*/
- addUnapprovedMessage (msgParams, req) {
+ addUnapprovedMessage (msgParams, req, version) {
+ msgParams.version = version
this.validateParams(msgParams)
// add origin from request
if (req) msgParams.origin = req.origin
@@ -132,14 +129,33 @@ module.exports = class TypedMessageManager extends EventEmitter {
*
*/
validateParams (params) {
- assert.equal(typeof params, 'object', 'Params should ben an object.')
- assert.ok('data' in params, 'Params must include a data field.')
- assert.ok('from' in params, 'Params must include a from field.')
- assert.ok(Array.isArray(params.data), 'Data should be an array.')
- assert.equal(typeof params.from, 'string', 'From field must be a string.')
- assert.doesNotThrow(() => {
- sigUtil.typedSignatureHash(params.data)
- }, 'Expected EIP712 typed data')
+ switch (params.version) {
+ case 'V1':
+ assert.equal(typeof params, 'object', 'Params should ben an object.')
+ assert.ok('data' in params, 'Params must include a data field.')
+ assert.ok('from' in params, 'Params must include a from field.')
+ assert.ok(Array.isArray(params.data), 'Data should be an array.')
+ assert.equal(typeof params.from, 'string', 'From field must be a string.')
+ assert.doesNotThrow(() => {
+ sigUtil.typedSignatureHash(params.data)
+ }, 'Expected EIP712 typed data')
+ break
+ case 'V3':
+ let data
+ assert.equal(typeof params, 'object', 'Params should be an object.')
+ assert.ok('data' in params, 'Params must include a data field.')
+ assert.ok('from' in params, 'Params must include a from field.')
+ assert.equal(typeof params.from, 'string', 'From field must be a string.')
+ assert.equal(typeof params.data, 'string', 'Data must be passed as a valid JSON string.')
+ assert.doesNotThrow(() => { data = JSON.parse(params.data) }, 'Data must be passed as a valid JSON string.')
+ const validation = jsonschema.validate(data, sigUtil.TYPED_MESSAGE_SCHEMA)
+ assert.ok(data.primaryType in data.types, `Primary type of "${data.primaryType}" has no type definition.`)
+ assert.equal(validation.errors.length, 0, 'Data must conform to EIP-712 schema. See https://git.io/fNtcx.')
+ const chainId = data.domain.chainId
+ const activeChainId = parseInt(this.networkController.getNetworkState())
+ chainId && assert.equal(chainId, activeChainId, `Provided chainId (${chainId}) must match the active chainId (${activeChainId})`)
+ break
+ }
}
/**
@@ -214,6 +230,7 @@ module.exports = class TypedMessageManager extends EventEmitter {
*/
prepMsgForSigning (msgParams) {
delete msgParams.metamaskId
+ delete msgParams.version
return Promise.resolve(msgParams)
}
@@ -227,6 +244,19 @@ module.exports = class TypedMessageManager extends EventEmitter {
this._setMsgStatus(msgId, 'rejected')
}
+ /**
+ * Sets a TypedMessage status to 'errored' via a call to this._setMsgStatus.
+ *
+ * @param {number} msgId The id of the TypedMessage to error
+ *
+ */
+ errorMessage (msgId, error) {
+ const msg = this.getMsg(msgId)
+ msg.error = error
+ this._updateMsg(msg)
+ this._setMsgStatus(msgId, 'errored')
+ }
+
//
// PRIVATE METHODS
//
@@ -250,7 +280,7 @@ module.exports = class TypedMessageManager extends EventEmitter {
msg.status = status
this._updateMsg(msg)
this.emit(`${msgId}:${status}`, msg)
- if (status === 'rejected' || status === 'signed') {
+ if (status === 'rejected' || status === 'signed' || status === 'errored') {
this.emit(`${msgId}:finished`, msg)
}
}
diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js
index 98cb62bfa..493877345 100644
--- a/app/scripts/metamask-controller.js
+++ b/app/scripts/metamask-controller.js
@@ -15,6 +15,7 @@ const RpcEngine = require('json-rpc-engine')
const debounce = require('debounce')
const createEngineStream = require('json-rpc-middleware-stream/engineStream')
const createFilterMiddleware = require('eth-json-rpc-filters')
+const createSubscriptionManager = require('eth-json-rpc-filters/subscriptionManager')
const createOriginMiddleware = require('./lib/createOriginMiddleware')
const createLoggerMiddleware = require('./lib/createLoggerMiddleware')
const createProviderMiddleware = require('./lib/createProviderMiddleware')
@@ -36,7 +37,6 @@ const TransactionController = require('./controllers/transactions')
const BalancesController = require('./controllers/computed-balances')
const TokenRatesController = require('./controllers/token-rates')
const DetectTokensController = require('./controllers/detect-tokens')
-const ConfigManager = require('./lib/config-manager')
const nodeify = require('./lib/nodeify')
const accountImporter = require('./account-import-strategies')
const getBuyEthUrl = require('./lib/buy-eth-url')
@@ -50,6 +50,8 @@ const log = require('loglevel')
const TrezorKeyring = require('eth-trezor-keyring')
const LedgerBridgeKeyring = require('eth-ledger-bridge-keyring')
const EthQuery = require('eth-query')
+const ethUtil = require('ethereumjs-util')
+const sigUtil = require('eth-sig-util')
module.exports = class MetamaskController extends EventEmitter {
@@ -83,11 +85,6 @@ module.exports = class MetamaskController extends EventEmitter {
// network store
this.networkController = new NetworkController(initState.NetworkController)
- // config manager
- this.configManager = new ConfigManager({
- store: this.store,
- })
-
// preferences controller
this.preferencesController = new PreferencesController({
initState: initState.PreferencesController,
@@ -177,7 +174,7 @@ module.exports = class MetamaskController extends EventEmitter {
blockTracker: this.blockTracker,
getGasPrice: this.getGasPrice.bind(this),
})
- this.txController.on('newUnapprovedTx', opts.showUnapprovedTx.bind(opts))
+ this.txController.on('newUnapprovedTx', () => opts.showUnapprovedTx())
this.txController.on(`tx:status-update`, (txId, status) => {
if (status === 'confirmed' || status === 'failed') {
@@ -211,7 +208,7 @@ module.exports = class MetamaskController extends EventEmitter {
this.networkController.lookupNetwork()
this.messageManager = new MessageManager()
this.personalMessageManager = new PersonalMessageManager()
- this.typedMessageManager = new TypedMessageManager()
+ this.typedMessageManager = new TypedMessageManager({ networkController: this.networkController })
this.publicConfigStore = this.initPublicConfigStore()
this.store.updateStructure({
@@ -256,6 +253,7 @@ module.exports = class MetamaskController extends EventEmitter {
eth_syncing: false,
web3_clientVersion: `MetaMask/v${version}`,
},
+ version,
// account mgmt
getAccounts: async () => {
const isUnlocked = this.keyringController.memStore.getState().isUnlocked
@@ -272,7 +270,7 @@ module.exports = class MetamaskController extends EventEmitter {
// msg signing
processEthSignMessage: this.newUnsignedMessage.bind(this),
processPersonalMessage: this.newUnsignedPersonalMessage.bind(this),
- processTypedMessage: this.newUnsignedTypedMessage.bind(this),
+ getPendingNonce: this.getPendingNonce.bind(this),
}
const providerProxy = this.networkController.initializeProvider(providerOpts)
return providerProxy
@@ -314,18 +312,15 @@ module.exports = class MetamaskController extends EventEmitter {
* @returns {Object} status
*/
getState () {
- const wallet = this.configManager.getWallet()
const vault = this.keyringController.store.getState().vault
- const isInitialized = (!!wallet || !!vault)
+ const isInitialized = !!vault
return {
...{ isInitialized },
...this.memStore.getFlatState(),
- ...this.configManager.getConfig(),
...{
- lostAccounts: this.configManager.getLostAccounts(),
- seedWords: this.configManager.getSeedWords(),
- forgottenPassword: this.configManager.getPasswordForgotten(),
+ // TODO: Remove usages of lost accounts
+ lostAccounts: [],
},
}
}
@@ -382,6 +377,7 @@ module.exports = class MetamaskController extends EventEmitter {
// network management
setProviderType: nodeify(networkController.setProviderType, networkController),
setCustomRpc: nodeify(this.setCustomRpc, this),
+ delCustomRpc: nodeify(this.delCustomRpc, this),
// PreferencesController
setSelectedAddress: nodeify(preferencesController.setSelectedAddress, preferencesController),
@@ -392,6 +388,9 @@ module.exports = class MetamaskController extends EventEmitter {
setAccountLabel: nodeify(preferencesController.setAccountLabel, preferencesController),
setFeatureFlag: nodeify(preferencesController.setFeatureFlag, preferencesController),
+ // BlacklistController
+ whitelistPhishingDomain: this.whitelistPhishingDomain.bind(this),
+
// AddressController
setAddressBook: nodeify(addressBookController.setAddressBook, addressBookController),
@@ -407,6 +406,7 @@ module.exports = class MetamaskController extends EventEmitter {
updateTransaction: nodeify(txController.updateTransaction, txController),
updateAndApproveTransaction: nodeify(txController.updateAndApproveTransaction, txController),
retryTransaction: nodeify(this.retryTransaction, this),
+ createCancelTransaction: nodeify(this.createCancelTransaction, this),
getFilteredTxList: nodeify(txController.getFilteredTxList, txController),
isNonceTaken: nodeify(txController.isNonceTaken, txController),
estimateGas: nodeify(this.estimateGas, this),
@@ -556,6 +556,8 @@ module.exports = class MetamaskController extends EventEmitter {
}
await this.preferencesController.syncAddresses(accounts)
+ await this.balancesController.updateAllBalances()
+ await this.txController.pendingTxTracker.updatePendingTxs()
return this.keyringController.fullUpdate()
}
@@ -726,7 +728,7 @@ module.exports = class MetamaskController extends EventEmitter {
this.verifySeedPhrase()
.then((seedWords) => {
- this.configManager.setSeedWords(seedWords)
+ this.preferencesController.setSeedWords(seedWords)
return cb(null, seedWords)
})
.catch((err) => {
@@ -775,7 +777,7 @@ module.exports = class MetamaskController extends EventEmitter {
* @param {function} cb Callback function called with the current address.
*/
clearSeedWordCache (cb) {
- this.configManager.setSeedWords(null)
+ this.preferencesController.setSeedWords(null)
cb(null, this.preferencesController.getSelectedAddress())
}
@@ -983,22 +985,31 @@ module.exports = class MetamaskController extends EventEmitter {
* @param {Object} msgParams - The params passed to eth_signTypedData.
* @returns {Object} Full state update.
*/
- signTypedMessage (msgParams) {
- log.info('MetaMaskController - signTypedMessage')
+ async signTypedMessage (msgParams) {
+ log.info('MetaMaskController - eth_signTypedData')
const msgId = msgParams.metamaskId
- // sets the status op the message to 'approved'
- // and removes the metamaskId for signing
- return this.typedMessageManager.approveMessage(msgParams)
- .then((cleanMsgParams) => {
- // signs the message
- return this.keyringController.signTypedMessage(cleanMsgParams)
- })
- .then((rawSig) => {
- // tells the listener that the message has been signed
- // and can be returned to the dapp
- this.typedMessageManager.setMsgStatusSigned(msgId, rawSig)
- return this.getState()
- })
+ const version = msgParams.version
+ try {
+ const cleanMsgParams = await this.typedMessageManager.approveMessage(msgParams)
+ const address = sigUtil.normalize(cleanMsgParams.from)
+ const keyring = await this.keyringController.getKeyringForAccount(address)
+ const wallet = keyring._getWalletForAccount(address)
+ const privKey = ethUtil.toBuffer(wallet.getPrivateKey())
+ let signature
+ switch (version) {
+ case 'V1':
+ signature = sigUtil.signTypedDataLegacy(privKey, { data: cleanMsgParams.data })
+ break
+ case 'V3':
+ signature = sigUtil.signTypedData(privKey, { data: JSON.parse(cleanMsgParams.data) })
+ break
+ }
+ this.typedMessageManager.setMsgStatusSigned(msgId, signature)
+ return this.getState()
+ } catch (error) {
+ log.info('MetaMaskController - eth_signTypedData failed.', error)
+ this.typedMessageManager.errorMessage(msgId, error)
+ }
}
/**
@@ -1036,36 +1047,17 @@ module.exports = class MetamaskController extends EventEmitter {
/**
* A legacy method used to record user confirmation that they understand
* that some of their accounts have been recovered but should be backed up.
+ * This function no longer does anything and will be removed.
*
* @deprecated
* @param {Function} cb - A callback function called with a full state update.
*/
markAccountsFound (cb) {
- this.configManager.setLostAccounts([])
- this.sendUpdate()
+ // TODO Remove me
cb(null, this.getState())
}
/**
- * A legacy method (probably dead code) that was used when we swapped out our
- * key management library that we depended on.
- *
- * Described in:
- * https://medium.com/metamask/metamask-3-migration-guide-914b79533cdd
- *
- * @deprecated
- * @param {} migratorOutput
- */
- restoreOldLostAccounts (migratorOutput) {
- const { lostAccounts } = migratorOutput
- if (lostAccounts) {
- this.configManager.setLostAccounts(lostAccounts.map(acct => acct.address))
- return this.importLostAccounts(migratorOutput)
- }
- return Promise.resolve(migratorOutput)
- }
-
- /**
* An account object
* @typedef Account
* @property string privateKey - The private key of the account.
@@ -1109,6 +1101,19 @@ module.exports = class MetamaskController extends EventEmitter {
return state
}
+ /**
+ * Allows a user to attempt to cancel a previously submitted transaction by creating a new
+ * transaction.
+ * @param {number} originalTxId - the id of the txMeta that you want to attempt to cancel
+ * @param {string=} customGasPrice - the hex value to use for the cancel transaction
+ * @returns {object} MetaMask state
+ */
+ async createCancelTransaction (originalTxId, customGasPrice, cb) {
+ await this.txController.createCancelTransaction(originalTxId, customGasPrice)
+ const state = await this.getState()
+ return state
+ }
+
estimateGas (estimateGasParams) {
return new Promise((resolve, reject) => {
return this.txController.txGasUtil.query.estimateGas(estimateGasParams, (err, res) => {
@@ -1130,7 +1135,7 @@ module.exports = class MetamaskController extends EventEmitter {
* @param {Function} cb - A callback function called when complete.
*/
markPasswordForgotten (cb) {
- this.configManager.setPasswordForgotten(true)
+ this.preferencesController.setPasswordForgotten(true)
this.sendUpdate()
cb()
}
@@ -1140,7 +1145,7 @@ module.exports = class MetamaskController extends EventEmitter {
* @param {Function} cb - A callback function called when complete.
*/
unMarkPasswordForgotten (cb) {
- this.configManager.setPasswordForgotten(false)
+ this.preferencesController.setPasswordForgotten(false)
this.sendUpdate()
cb()
}
@@ -1229,8 +1234,10 @@ module.exports = class MetamaskController extends EventEmitter {
)
dnode.on('remote', (remote) => {
// push updates to popup
- const sendUpdate = remote.sendUpdate.bind(remote)
+ const sendUpdate = (update) => remote.sendUpdate(update)
this.on('update', sendUpdate)
+ // remove update listener once the connection ends
+ dnode.on('end', () => this.removeListener('update', sendUpdate))
})
}
@@ -1242,21 +1249,33 @@ module.exports = class MetamaskController extends EventEmitter {
setupProviderConnection (outStream, origin) {
// setup json rpc engine stack
const engine = new RpcEngine()
+ const provider = this.provider
+ const blockTracker = this.blockTracker
// create filter polyfill middleware
- const filterMiddleware = createFilterMiddleware({
- provider: this.provider,
- blockTracker: this.blockTracker,
- })
+ const filterMiddleware = createFilterMiddleware({ provider, blockTracker })
+ // create subscription polyfill middleware
+ const subscriptionManager = createSubscriptionManager({ provider, blockTracker })
+ subscriptionManager.events.on('notification', (message) => engine.emit('notification', message))
+ // metadata
engine.push(createOriginMiddleware({ origin }))
engine.push(createLoggerMiddleware({ origin }))
+ // filter and subscription polyfills
engine.push(filterMiddleware)
+ engine.push(subscriptionManager.middleware)
+ // watch asset
engine.push(this.preferencesController.requestWatchAsset.bind(this.preferencesController))
- engine.push(createProviderMiddleware({ provider: this.provider }))
+ // sign typed data middleware
+ engine.push(this.createTypedDataMiddleware('eth_signTypedData', 'V1').bind(this))
+ engine.push(this.createTypedDataMiddleware('eth_signTypedData_v1', 'V1').bind(this))
+ engine.push(this.createTypedDataMiddleware('eth_signTypedData_v3', 'V3', true).bind(this))
+ // forward to metamask primary provider
+ engine.push(createProviderMiddleware({ provider }))
// setup connection
const providerStream = createEngineStream({ engine })
+
pump(
outStream,
providerStream,
@@ -1280,10 +1299,12 @@ module.exports = class MetamaskController extends EventEmitter {
* @param {*} outStream - The stream to provide public config over.
*/
setupPublicConfig (outStream) {
+ const configStream = asStream(this.publicConfigStore)
pump(
- asStream(this.publicConfigStore),
+ configStream,
outStream,
(err) => {
+ configStream.destroy()
if (err) log.error(err)
}
)
@@ -1354,11 +1375,24 @@ module.exports = class MetamaskController extends EventEmitter {
})
.map(number => number.div(GWEI_BN).toNumber())
- const percentileNum = percentile(50, lowestPrices)
+ const percentileNum = percentile(65, lowestPrices)
const percentileNumBn = new BN(percentileNum)
return '0x' + percentileNumBn.mul(GWEI_BN).toString(16)
}
+ /**
+ * Returns the nonce that will be associated with a transaction once approved
+ * @param address {string} - The hex string address for the transaction
+ * @returns Promise<number>
+ */
+ async getPendingNonce (address) {
+ const { nonceDetails, releaseLock} = await this.txController.nonceTracker.getNonceLock(address)
+ const pendingNonce = nonceDetails.params.highestSuggested
+
+ releaseLock()
+ return pendingNonce
+ }
+
//=============================================================================
// CONFIG
//=============================================================================
@@ -1422,6 +1456,14 @@ module.exports = class MetamaskController extends EventEmitter {
}
/**
+ * A method for deleting a selected custom URL.
+ * @param {string} rpcTarget - A RPC URL to delete.
+ */
+ async delCustomRpc (rpcTarget) {
+ await this.preferencesController.updateFrequentRpcList(rpcTarget, true)
+ }
+
+ /**
* Sets whether or not to use the blockie identicon format.
* @param {boolean} val - True for bockie, false for jazzicon.
* @param {Function} cb - A callback function called when complete.
@@ -1484,4 +1526,42 @@ module.exports = class MetamaskController extends EventEmitter {
set isClientOpenAndUnlocked (active) {
this.tokenRatesController.isActive = active
}
+
+ /**
+ * Creates RPC engine middleware for processing eth_signTypedData requests
+ *
+ * @param {Object} req - request object
+ * @param {Object} res - response object
+ * @param {Function} - next
+ * @param {Function} - end
+ */
+ createTypedDataMiddleware (methodName, version, reverse) {
+ return async (req, res, next, end) => {
+ const { method, params } = req
+ if (method === methodName) {
+ const promise = this.typedMessageManager.addUnapprovedMessageAsync({
+ data: reverse ? params[1] : params[0],
+ from: reverse ? params[0] : params[1],
+ }, req, version)
+ this.sendUpdate()
+ this.opts.showUnconfirmedMessage()
+ try {
+ res.result = await promise
+ end()
+ } catch (error) {
+ end(error)
+ }
+ } else {
+ next()
+ }
+ }
+ }
+
+ /**
+ * Adds a domain to the {@link BlacklistController} whitelist
+ * @param {string} hostname the domain to whitelist
+ */
+ whitelistPhishingDomain (hostname) {
+ return this.blacklistController.whitelistDomain(hostname)
+ }
}
diff --git a/app/scripts/migrations/_multi-keyring.js b/app/scripts/migrations/_multi-keyring.js
deleted file mode 100644
index 7a4578ea7..000000000
--- a/app/scripts/migrations/_multi-keyring.js
+++ /dev/null
@@ -1,50 +0,0 @@
-const version = 5
-
-/*
-
-This is an incomplete migration bc it requires post-decrypted data
-which we dont have access to at the time of this writing.
-
-*/
-
-const ObservableStore = require('obs-store')
-const ConfigManager = require('../../app/scripts/lib/config-manager')
-const IdentityStoreMigrator = require('../../app/scripts/lib/idStore-migrator')
-const KeyringController = require('eth-keyring-controller')
-
-const password = 'obviously not correct'
-
-module.exports = {
- version,
-
- migrate: function (versionedData) {
- versionedData.meta.version = version
-
- const store = new ObservableStore(versionedData.data)
- const configManager = new ConfigManager({ store })
- const idStoreMigrator = new IdentityStoreMigrator({ configManager })
- const keyringController = new KeyringController({
- configManager: configManager,
- })
-
- // attempt to migrate to multiVault
- return idStoreMigrator.migratedVaultForPassword(password)
- .then((result) => {
- // skip if nothing to migrate
- if (!result) return Promise.resolve(versionedData)
- delete versionedData.data.wallet
- // create new keyrings
- const privKeys = result.lostAccounts.map(acct => acct.privateKey)
- return Promise.all([
- keyringController.restoreKeyring(result.serialized),
- keyringController.restoreKeyring({ type: 'Simple Key Pair', data: privKeys }),
- ]).then(() => {
- return keyringController.persistAllKeyrings(password)
- }).then(() => {
- // copy result on to state object
- versionedData.data = store.get()
- return Promise.resolve(versionedData)
- })
- })
- },
-}
diff --git a/app/scripts/notice-controller.js b/app/scripts/notice-controller.js
index 2def4371e..ce686d9d1 100644
--- a/app/scripts/notice-controller.js
+++ b/app/scripts/notice-controller.js
@@ -7,7 +7,7 @@ const uniqBy = require('lodash.uniqby')
module.exports = class NoticeController extends EventEmitter {
- constructor (opts) {
+ constructor (opts = {}) {
super()
this.noticePoller = null
this.firstVersion = opts.firstVersion
diff --git a/app/scripts/phishing-detect.js b/app/scripts/phishing-detect.js
new file mode 100644
index 000000000..0889c831e
--- /dev/null
+++ b/app/scripts/phishing-detect.js
@@ -0,0 +1,59 @@
+window.onload = function () {
+ if (window.location.pathname === '/phishing.html') {
+ const {hostname} = parseHash()
+ document.getElementById('esdbLink').innerHTML = '<b>To read more about this scam, navigate to: <a href="https://etherscamdb.info/domain/' + hostname + '"> https://etherscamdb.info/domain/' + hostname + '</a></b>'
+ }
+}
+
+const querystring = require('querystring')
+const dnode = require('dnode')
+const { EventEmitter } = require('events')
+const PortStream = require('extension-port-stream')
+const extension = require('extensionizer')
+const setupMultiplex = require('./lib/stream-utils.js').setupMultiplex
+const { getEnvironmentType } = require('./lib/util')
+const ExtensionPlatform = require('./platforms/extension')
+
+document.addEventListener('DOMContentLoaded', start)
+
+function start () {
+ const windowType = getEnvironmentType(window.location.href)
+
+ global.platform = new ExtensionPlatform()
+ global.METAMASK_UI_TYPE = windowType
+
+ const extensionPort = extension.runtime.connect({ name: windowType })
+ const connectionStream = new PortStream(extensionPort)
+ const mx = setupMultiplex(connectionStream)
+ setupControllerConnection(mx.createStream('controller'), (err, metaMaskController) => {
+ if (err) {
+ return
+ }
+
+ const suspect = parseHash()
+ const unsafeContinue = () => {
+ window.location.href = suspect.href
+ }
+ const continueLink = document.getElementById('unsafe-continue')
+ continueLink.addEventListener('click', () => {
+ metaMaskController.whitelistPhishingDomain(suspect.hostname)
+ unsafeContinue()
+ })
+ })
+}
+
+function setupControllerConnection (connectionStream, cb) {
+ const eventEmitter = new EventEmitter()
+ const accountManagerDnode = dnode({
+ sendUpdate (state) {
+ eventEmitter.emit('update', state)
+ },
+ })
+ connectionStream.pipe(accountManagerDnode).pipe(connectionStream)
+ accountManagerDnode.once('remote', (accountManager) => cb(null, accountManager))
+}
+
+function parseHash () {
+ const hash = window.location.hash.substring(1)
+ return querystring.parse(hash)
+}