aboutsummaryrefslogtreecommitdiffstats
path: root/app/scripts
diff options
context:
space:
mode:
authorEsteban MIno <efmino@uc.cl>2018-08-04 02:56:02 +0800
committerEsteban MIno <efmino@uc.cl>2018-08-04 02:56:02 +0800
commit21a61f2987ae83a48bb1f7256ec9c34978413eb2 (patch)
tree3bc9ef3d342f17269942cd3fe58da1f3b97ef214 /app/scripts
parent0481335dda447ba4c228d146834952bac0ad641b (diff)
parentfa4423bab2886017996955f809b6e3102cbf1781 (diff)
downloadtangerine-wallet-browser-21a61f2987ae83a48bb1f7256ec9c34978413eb2.tar
tangerine-wallet-browser-21a61f2987ae83a48bb1f7256ec9c34978413eb2.tar.gz
tangerine-wallet-browser-21a61f2987ae83a48bb1f7256ec9c34978413eb2.tar.bz2
tangerine-wallet-browser-21a61f2987ae83a48bb1f7256ec9c34978413eb2.tar.lz
tangerine-wallet-browser-21a61f2987ae83a48bb1f7256ec9c34978413eb2.tar.xz
tangerine-wallet-browser-21a61f2987ae83a48bb1f7256ec9c34978413eb2.tar.zst
tangerine-wallet-browser-21a61f2987ae83a48bb1f7256ec9c34978413eb2.zip
merge develop
Diffstat (limited to 'app/scripts')
-rw-r--r--app/scripts/background.js16
-rw-r--r--app/scripts/contentscript.js12
-rw-r--r--app/scripts/controllers/balance.js4
-rw-r--r--app/scripts/controllers/blacklist.js2
-rw-r--r--app/scripts/controllers/computed-balances.js2
-rw-r--r--app/scripts/controllers/currency.js8
-rw-r--r--app/scripts/controllers/detect-tokens.js130
-rw-r--r--app/scripts/controllers/network/enums.js3
-rw-r--r--app/scripts/controllers/network/network.js5
-rw-r--r--app/scripts/controllers/preferences.js30
-rw-r--r--app/scripts/controllers/recent-blocks.js2
-rw-r--r--app/scripts/controllers/transactions/nonce-tracker.js13
-rw-r--r--app/scripts/controllers/transactions/tx-gas-utils.js12
-rw-r--r--app/scripts/controllers/transactions/tx-state-manager.js6
-rw-r--r--app/scripts/lib/cleanErrorStack.js2
-rw-r--r--app/scripts/lib/contracts/registrar.js1
-rw-r--r--app/scripts/lib/contracts/resolver.js2
-rw-r--r--app/scripts/lib/createErrorMiddleware.js2
-rw-r--r--app/scripts/lib/createStreamSink.js2
-rw-r--r--app/scripts/lib/diagnostics-reporter.js6
-rw-r--r--app/scripts/lib/extractEthjsErrorMessage.js4
-rw-r--r--app/scripts/lib/get-first-preferred-lang-code.js2
-rw-r--r--app/scripts/lib/getObjStructure.js6
-rw-r--r--app/scripts/lib/inpage-provider.js5
-rw-r--r--app/scripts/lib/ipfsContent.js44
-rw-r--r--app/scripts/lib/local-store.js12
-rw-r--r--app/scripts/lib/notification-manager.js10
-rw-r--r--app/scripts/lib/port-stream.js2
-rw-r--r--app/scripts/lib/reportFailedTxToSentry.js2
-rw-r--r--app/scripts/lib/resolver.js71
-rw-r--r--app/scripts/lib/setupMetamaskMeshMetrics.js2
-rw-r--r--app/scripts/lib/setupRaven.js22
-rw-r--r--app/scripts/lib/util.js2
-rw-r--r--app/scripts/metamask-controller.js175
-rw-r--r--app/scripts/migrations/013.js2
-rw-r--r--app/scripts/migrations/023.js4
-rw-r--r--app/scripts/migrations/027.js35
-rw-r--r--app/scripts/migrations/index.js1
-rw-r--r--app/scripts/platforms/extension.js67
-rw-r--r--app/scripts/popup-core.js2
-rw-r--r--app/scripts/ui.js3
41 files changed, 644 insertions, 89 deletions
diff --git a/app/scripts/background.js b/app/scripts/background.js
index 2be600c4b..c0b00730d 100644
--- a/app/scripts/background.js
+++ b/app/scripts/background.js
@@ -26,6 +26,8 @@ const setupMetamaskMeshMetrics = require('./lib/setupMetamaskMeshMetrics')
const EdgeEncryptor = require('./edge-encryptor')
const getFirstPreferredLangCode = require('./lib/get-first-preferred-lang-code')
const getObjStructure = require('./lib/getObjStructure')
+const ipfsContent = require('./lib/ipfsContent.js')
+
const {
ENVIRONMENT_TYPE_POPUP,
ENVIRONMENT_TYPE_NOTIFICATION,
@@ -42,8 +44,8 @@ const notificationManager = new NotificationManager()
global.METAMASK_NOTIFIER = notificationManager
// setup sentry error reporting
-const release = platform.getVersion()
-const raven = setupRaven({ release })
+const releaseVersion = platform.getVersion()
+const raven = setupRaven({ releaseVersion })
// browser check if it is Edge - https://stackoverflow.com/questions/9847580/how-to-detect-safari-chrome-ie-firefox-and-opera-browser
// Internet Explorer 6-11
@@ -51,6 +53,7 @@ const isIE = !!document.documentMode
// Edge 20+
const isEdge = !isIE && !!window.StyleMedia
+let ipfsHandle
let popupIsOpen = false
let notificationIsOpen = false
const openMetamaskTabsIDs = {}
@@ -66,6 +69,7 @@ initialize().catch(log.error)
// setup metamask mesh testing container
setupMetamaskMeshMetrics()
+
/**
* An object representing a transaction, in whatever state it is in.
* @typedef TransactionMeta
@@ -155,6 +159,7 @@ async function initialize () {
const initLangCode = await getFirstPreferredLangCode()
await setupController(initState, initLangCode)
log.debug('MetaMask initialization complete.')
+ ipfsHandle = ipfsContent(initState.NetworkController.provider)
}
//
@@ -259,6 +264,11 @@ function setupController (initState, initLangCode) {
})
global.metamaskController = controller
+ controller.networkController.on('networkDidChange', () => {
+ ipfsHandle && ipfsHandle.remove()
+ ipfsHandle = ipfsContent(controller.networkController.providerStore.getState())
+ })
+
// report failed transactions to Sentry
controller.txController.on(`tx:status-update`, (txId, status) => {
if (status !== 'failed') return
@@ -379,7 +389,7 @@ function setupController (initState, initLangCode) {
}
// communication with page or other extension
- function connectExternal(remotePort) {
+ function connectExternal (remotePort) {
const originDomain = urlUtil.parse(remotePort.sender.url).hostname
const portStream = new PortStream(remotePort)
controller.setupUntrustedCommunication(portStream, originDomain)
diff --git a/app/scripts/contentscript.js b/app/scripts/contentscript.js
index 75e0a95b3..b7496f318 100644
--- a/app/scripts/contentscript.js
+++ b/app/scripts/contentscript.js
@@ -115,8 +115,8 @@ function logStreamDisconnectWarning (remoteLabel, err) {
* @returns {boolean} {@code true} if Web3 should be injected
*/
function shouldInjectWeb3 () {
- return doctypeCheck() && suffixCheck()
- && documentElementCheck() && !blacklistedDomainCheck()
+ return doctypeCheck() && suffixCheck() &&
+ documentElementCheck() && !blacklistedDomainCheck()
}
/**
@@ -177,6 +177,9 @@ function blacklistedDomainCheck () {
'cdn.shopify.com/s/javascripts/tricorder/xtld-read-only-frame.html',
'adyen.com',
'gravityforms.com',
+ 'harbourair.com',
+ 'ani.gamer.com.tw',
+ 'blueskybooking.com',
]
var currentUrl = window.location.href
var currentRegex
@@ -194,6 +197,7 @@ function blacklistedDomainCheck () {
* Redirects the current page to a phishing information page
*/
function redirectToPhishingWarning () {
- console.log('MetaMask - redirecting to phishing warning')
- window.location.href = 'https://metamask.io/phishing.html'
+ console.log('MetaMask - routing to Phishing Warning component')
+ let extensionURL = extension.runtime.getURL('phishing.html')
+ window.location.href = extensionURL
}
diff --git a/app/scripts/controllers/balance.js b/app/scripts/controllers/balance.js
index 86619fce1..4c97810a3 100644
--- a/app/scripts/controllers/balance.js
+++ b/app/scripts/controllers/balance.js
@@ -60,7 +60,7 @@ class BalanceController {
* Sets up listeners and subscriptions which should trigger an update of ethBalance. These updates include:
* - when a transaction changes state to 'submitted', 'confirmed' or 'failed'
* - when the current account changes (i.e. a new account is selected)
- * - when there is a block update
+ * - when there is a block update
*
* @private
*
@@ -100,7 +100,7 @@ class BalanceController {
/**
* Gets the pending transactions (i.e. those with a 'submitted' status). These are accessed from the
- * TransactionController passed to this BalanceController during construction.
+ * TransactionController passed to this BalanceController during construction.
*
* @private
* @returns {Promise<array>} Promises an array of transaction objects.
diff --git a/app/scripts/controllers/blacklist.js b/app/scripts/controllers/blacklist.js
index f100c4525..1d2191433 100644
--- a/app/scripts/controllers/blacklist.js
+++ b/app/scripts/controllers/blacklist.js
@@ -87,7 +87,7 @@ class BlacklistController {
*
* @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)
diff --git a/app/scripts/controllers/computed-balances.js b/app/scripts/controllers/computed-balances.js
index 1a6802f9a..e04ce2ef7 100644
--- a/app/scripts/controllers/computed-balances.js
+++ b/app/scripts/controllers/computed-balances.js
@@ -18,7 +18,7 @@ class ComputedbalancesController {
/**
* Creates a new controller instance
*
- * @param {ComputedBalancesOptions} [opts] Controller configuration parameters
+ * @param {ComputedBalancesOptions} [opts] Controller configuration parameters
*/
constructor (opts = {}) {
const { accountTracker, txController, blockTracker } = opts
diff --git a/app/scripts/controllers/currency.js b/app/scripts/controllers/currency.js
index 480c08b1c..a93aff49b 100644
--- a/app/scripts/controllers/currency.js
+++ b/app/scripts/controllers/currency.js
@@ -16,9 +16,9 @@ class CurrencyController {
* currentCurrency, conversionRate and conversionDate properties
* @property {string} currentCurrency A 2-4 character shorthand that describes a specific currency, currently
* selected by the user
- * @property {number} conversionRate The conversion rate from ETH to the selected currency.
+ * @property {number} conversionRate The conversion rate from ETH to the selected currency.
* @property {string} conversionDate The date at which the conversion rate was set. Expressed in in milliseconds
- * since midnight of January 1, 1970
+ * since midnight of January 1, 1970
* @property {number} conversionInterval The id of the interval created by the scheduleConversionInterval method.
* Used to clear an existing interval on subsequent calls of that method.
*
@@ -59,7 +59,7 @@ class CurrencyController {
/**
* A getter for the conversionRate property
*
- * @returns {string} The conversion rate from ETH to the selected currency.
+ * @returns {string} The conversion rate from ETH to the selected currency.
*
*/
getConversionRate () {
@@ -80,7 +80,7 @@ class CurrencyController {
* A getter for the conversionDate property
*
* @returns {string} The date at which the conversion rate was set. Expressed in milliseconds since midnight of
- * January 1, 1970
+ * January 1, 1970
*
*/
getConversionDate () {
diff --git a/app/scripts/controllers/detect-tokens.js b/app/scripts/controllers/detect-tokens.js
new file mode 100644
index 000000000..195ec918a
--- /dev/null
+++ b/app/scripts/controllers/detect-tokens.js
@@ -0,0 +1,130 @@
+const Web3 = require('web3')
+const contracts = require('eth-contract-metadata')
+const { warn } = require('loglevel')
+const { MAINNET } = require('./network/enums')
+// By default, poll every 3 minutes
+const DEFAULT_INTERVAL = 180 * 1000
+const ERC20_ABI = [{'constant': true, 'inputs': [{'name': '_owner', 'type': 'address'}], 'name': 'balanceOf', 'outputs': [{'name': 'balance', 'type': 'uint256'}], 'payable': false, 'type': 'function'}]
+
+/**
+ * A controller that polls for token exchange
+ * rates based on a user's current token list
+ */
+class DetectTokensController {
+ /**
+ * Creates a DetectTokensController
+ *
+ * @param {Object} [config] - Options to configure controller
+ */
+ constructor ({ interval = DEFAULT_INTERVAL, preferences, network, keyringMemStore } = {}) {
+ this.preferences = preferences
+ this.interval = interval
+ this.network = network
+ this.keyringMemStore = keyringMemStore
+ }
+
+ /**
+ * For each token in eth-contract-metada, find check selectedAddress balance.
+ *
+ */
+ async detectNewTokens () {
+ if (!this.isActive) { return }
+ if (this._network.store.getState().provider.type !== MAINNET) { return }
+ this.web3.setProvider(this._network._provider)
+ for (const contractAddress in contracts) {
+ if (contracts[contractAddress].erc20 && !(this.tokenAddresses.includes(contractAddress.toLowerCase()))) {
+ this.detectTokenBalance(contractAddress)
+ }
+ }
+ }
+
+ /**
+ * Find if selectedAddress has tokens with contract in contractAddress.
+ *
+ * @param {string} contractAddress Hex address of the token contract to explore.
+ * @returns {boolean} If balance is detected, token is added.
+ *
+ */
+ async detectTokenBalance (contractAddress) {
+ const ethContract = this.web3.eth.contract(ERC20_ABI).at(contractAddress)
+ ethContract.balanceOf(this.selectedAddress, (error, result) => {
+ if (!error) {
+ if (!result.isZero()) {
+ this._preferences.addToken(contractAddress, contracts[contractAddress].symbol, contracts[contractAddress].decimals)
+ }
+ } else {
+ warn(`MetaMask - DetectTokensController balance fetch failed for ${contractAddress}.`, error)
+ }
+ })
+ }
+
+ /**
+ * Restart token detection polling period and call detectNewTokens
+ * in case of address change or user session initialization.
+ *
+ */
+ restartTokenDetection () {
+ if (!(this.isActive && this.selectedAddress)) { return }
+ this.detectNewTokens()
+ this.interval = DEFAULT_INTERVAL
+ }
+
+ /**
+ * @type {Number}
+ */
+ set interval (interval) {
+ this._handle && clearInterval(this._handle)
+ if (!interval) { return }
+ this._handle = setInterval(() => { this.detectNewTokens() }, interval)
+ }
+
+ /**
+ * In setter when selectedAddress is changed, detectNewTokens and restart polling
+ * @type {Object}
+ */
+ set preferences (preferences) {
+ if (!preferences) { return }
+ this._preferences = preferences
+ preferences.store.subscribe(({ tokens }) => { this.tokenAddresses = tokens.map((obj) => { return obj.address }) })
+ preferences.store.subscribe(({ selectedAddress }) => {
+ if (this.selectedAddress !== selectedAddress) {
+ this.selectedAddress = selectedAddress
+ this.restartTokenDetection()
+ }
+ })
+ }
+
+ /**
+ * @type {Object}
+ */
+ set network (network) {
+ if (!network) { return }
+ this._network = network
+ this.web3 = new Web3(network._provider)
+ }
+
+ /**
+ * In setter when isUnlocked is updated to true, detectNewTokens and restart polling
+ * @type {Object}
+ */
+ set keyringMemStore (keyringMemStore) {
+ if (!keyringMemStore) { return }
+ this._keyringMemStore = keyringMemStore
+ this._keyringMemStore.subscribe(({ isUnlocked }) => {
+ if (this.isUnlocked !== isUnlocked) {
+ this.isUnlocked = isUnlocked
+ if (isUnlocked) { this.restartTokenDetection() }
+ }
+ })
+ }
+
+ /**
+ * Internal isActive state
+ * @type {Object}
+ */
+ get isActive () {
+ return this.isOpen && this.isUnlocked
+ }
+}
+
+module.exports = DetectTokensController
diff --git a/app/scripts/controllers/network/enums.js b/app/scripts/controllers/network/enums.js
index 9da7f309c..3190eb37c 100644
--- a/app/scripts/controllers/network/enums.js
+++ b/app/scripts/controllers/network/enums.js
@@ -4,6 +4,7 @@ const KOVAN = 'kovan'
const MAINNET = 'mainnet'
const LOCALHOST = 'localhost'
+const MAINNET_CODE = 1
const ROPSTEN_CODE = 3
const RINKEYBY_CODE = 4
const KOVAN_CODE = 42
@@ -13,13 +14,13 @@ const RINKEBY_DISPLAY_NAME = 'Rinkeby'
const KOVAN_DISPLAY_NAME = 'Kovan'
const MAINNET_DISPLAY_NAME = 'Main Ethereum Network'
-
module.exports = {
ROPSTEN,
RINKEBY,
KOVAN,
MAINNET,
LOCALHOST,
+ MAINNET_CODE,
ROPSTEN_CODE,
RINKEYBY_CODE,
KOVAN_CODE,
diff --git a/app/scripts/controllers/network/network.js b/app/scripts/controllers/network/network.js
index 5e0c63e7d..b6f7705b5 100644
--- a/app/scripts/controllers/network/network.js
+++ b/app/scripts/controllers/network/network.js
@@ -9,6 +9,7 @@ const extend = require('xtend')
const EthQuery = require('eth-query')
const createEventEmitterProxy = require('../../lib/events-proxy.js')
const log = require('loglevel')
+const urlUtil = require('url')
const {
ROPSTEN,
RINKEBY,
@@ -132,7 +133,7 @@ module.exports = class NetworkController extends EventEmitter {
} else if (type === LOCALHOST) {
this._configureStandardProvider({ rpcUrl: LOCALHOST_RPC_URL })
// url-based rpc endpoints
- } else if (type === 'rpc'){
+ } else if (type === 'rpc') {
this._configureStandardProvider({ rpcUrl: rpcTarget })
} else {
throw new Error(`NetworkController - _configureProvider - unknown type "${type}"`)
@@ -155,6 +156,8 @@ module.exports = class NetworkController extends EventEmitter {
}
_configureStandardProvider ({ rpcUrl }) {
+ // urlUtil handles malformed urls
+ rpcUrl = urlUtil.parse(rpcUrl).format()
const providerParams = extend(this._baseProviderParams, {
rpcUrl,
engineParams: {
diff --git a/app/scripts/controllers/preferences.js b/app/scripts/controllers/preferences.js
index f1bd66889..8a4a63bb6 100644
--- a/app/scripts/controllers/preferences.js
+++ b/app/scripts/controllers/preferences.js
@@ -127,6 +127,30 @@ class PreferencesController {
}
/**
+ * Removes an address from state
+ *
+ * @param {string} address A hex address
+ * @returns {string} the address that was removed
+ */
+ removeAddress (address) {
+ const identities = this.store.getState().identities
+ if (!identities[address]) {
+ throw new Error(`${address} can't be deleted cause it was not found`)
+ }
+ delete identities[address]
+ this.store.updateState({ identities })
+
+ // If the selected account is no longer valid,
+ // select an arbitrary other account:
+ if (address === this.getSelectedAddress()) {
+ const selected = Object.keys(identities)[0]
+ this.setSelectedAddress(selected)
+ }
+ return address
+ }
+
+
+ /**
* Adds addresses to the identities object without removing identities
*
* @param {string[]} addresses An array of hex addresses
@@ -152,9 +176,9 @@ class PreferencesController {
* @returns {Promise<string>} selectedAddress the selected address.
*/
syncAddresses (addresses) {
- let { identities, lostIdentities } = this.store.getState()
+ const { identities, lostIdentities } = this.store.getState()
- let newlyLost = {}
+ const newlyLost = {}
Object.keys(identities).forEach((identity) => {
if (!addresses.includes(identity)) {
newlyLost[identity] = identities[identity]
@@ -169,7 +193,7 @@ class PreferencesController {
if (this.diagnostics) this.diagnostics.reportOrphans(newlyLost)
// store lost accounts
- for (let key in newlyLost) {
+ for (const key in newlyLost) {
lostIdentities[key] = newlyLost[key]
}
}
diff --git a/app/scripts/controllers/recent-blocks.js b/app/scripts/controllers/recent-blocks.js
index 033ef1d7e..926268691 100644
--- a/app/scripts/controllers/recent-blocks.js
+++ b/app/scripts/controllers/recent-blocks.js
@@ -117,7 +117,7 @@ class RecentBlocksController {
*
* @returns {Promise<void>} Promises undefined
*/
- async backfill() {
+ async backfill () {
this.blockTracker.once('block', async (block) => {
const currentBlockNumber = Number.parseInt(block.number, 16)
const blocksToFetch = Math.min(currentBlockNumber, this.historyLength)
diff --git a/app/scripts/controllers/transactions/nonce-tracker.js b/app/scripts/controllers/transactions/nonce-tracker.js
index 35ca08d6c..06f336eaa 100644
--- a/app/scripts/controllers/transactions/nonce-tracker.js
+++ b/app/scripts/controllers/transactions/nonce-tracker.js
@@ -129,19 +129,6 @@ class NonceTracker {
return Number.isInteger(highest) ? highest + 1 : 0
}
- _reduceTxListToUniqueNonces (txList) {
- const reducedTxList = txList.reduce((reducedList, txMeta, index) => {
- if (!index) return [txMeta]
- const nonceMatches = txList.filter((txData) => {
- return txMeta.txParams.nonce === txData.txParams.nonce
- })
- if (nonceMatches.length > 1) return reducedList
- reducedList.push(txMeta)
- return reducedList
- }, [])
- return reducedTxList
- }
-
_getHighestNonce (txList) {
const nonces = txList.map((txMeta) => {
const nonce = txMeta.txParams.nonce
diff --git a/app/scripts/controllers/transactions/tx-gas-utils.js b/app/scripts/controllers/transactions/tx-gas-utils.js
index 36b5cdbc9..5cd0f5407 100644
--- a/app/scripts/controllers/transactions/tx-gas-utils.js
+++ b/app/scripts/controllers/transactions/tx-gas-utils.js
@@ -30,14 +30,10 @@ class TxGasUtil {
try {
estimatedGasHex = await this.estimateTxGas(txMeta, block.gasLimit)
} catch (err) {
- const simulationFailed = (
- err.message.includes('Transaction execution error.') ||
- err.message.includes('gas required exceeds allowance or always failing transaction')
- )
- if (simulationFailed) {
- txMeta.simulationFails = true
- return txMeta
+ txMeta.simulationFails = {
+ reason: err.message,
}
+ return txMeta
}
this.setTxGas(txMeta, block.gasLimit, estimatedGasHex)
return txMeta
@@ -126,4 +122,4 @@ class TxGasUtil {
}
}
-module.exports = TxGasUtil \ No newline at end of file
+module.exports = TxGasUtil
diff --git a/app/scripts/controllers/transactions/tx-state-manager.js b/app/scripts/controllers/transactions/tx-state-manager.js
index 0aae4774b..28a18ca2e 100644
--- a/app/scripts/controllers/transactions/tx-state-manager.js
+++ b/app/scripts/controllers/transactions/tx-state-manager.js
@@ -288,6 +288,7 @@ class TransactionStateManager extends EventEmitter {
*/
setTxStatusRejected (txId) {
this._setTxStatus(txId, 'rejected')
+ this._removeTx(txId)
}
/**
@@ -422,6 +423,11 @@ class TransactionStateManager extends EventEmitter {
_saveTxList (transactions) {
this.store.updateState({ transactions })
}
+
+ _removeTx (txId) {
+ const transactionList = this.getFullTxList()
+ this._saveTxList(transactionList.filter((txMeta) => txMeta.id !== txId))
+ }
}
module.exports = TransactionStateManager
diff --git a/app/scripts/lib/cleanErrorStack.js b/app/scripts/lib/cleanErrorStack.js
index fe1bfb0ce..8adf55db7 100644
--- a/app/scripts/lib/cleanErrorStack.js
+++ b/app/scripts/lib/cleanErrorStack.js
@@ -3,7 +3,7 @@
* @param {Error} err - error
* @returns {Error} Error with clean stack trace.
*/
-function cleanErrorStack(err){
+function cleanErrorStack (err) {
var name = err.name
name = (name === undefined) ? 'Error' : String(name)
diff --git a/app/scripts/lib/contracts/registrar.js b/app/scripts/lib/contracts/registrar.js
new file mode 100644
index 000000000..99ca24458
--- /dev/null
+++ b/app/scripts/lib/contracts/registrar.js
@@ -0,0 +1 @@
+module.exports = [{'constant': true, 'inputs': [{'name': 'node', 'type': 'bytes32'}], 'name': 'resolver', 'outputs': [{'name': '', 'type': 'address'}], 'payable': false, 'type': 'function'}, {'constant': true, 'inputs': [{'name': 'node', 'type': 'bytes32'}], 'name': 'owner', 'outputs': [{'name': '', 'type': 'address'}], 'payable': false, 'type': 'function'}, {'constant': false, 'inputs': [{'name': 'node', 'type': 'bytes32'}, {'name': 'label', 'type': 'bytes32'}, {'name': 'owner', 'type': 'address'}], 'name': 'setSubnodeOwner', 'outputs': [], 'payable': false, 'type': 'function'}, {'constant': false, 'inputs': [{'name': 'node', 'type': 'bytes32'}, {'name': 'ttl', 'type': 'uint64'}], 'name': 'setTTL', 'outputs': [], 'payable': false, 'type': 'function'}, {'constant': true, 'inputs': [{'name': 'node', 'type': 'bytes32'}], 'name': 'ttl', 'outputs': [{'name': '', 'type': 'uint64'}], 'payable': false, 'type': 'function'}, {'constant': false, 'inputs': [{'name': 'node', 'type': 'bytes32'}, {'name': 'resolver', 'type': 'address'}], 'name': 'setResolver', 'outputs': [], 'payable': false, 'type': 'function'}, {'constant': false, 'inputs': [{'name': 'node', 'type': 'bytes32'}, {'name': 'owner', 'type': 'address'}], 'name': 'setOwner', 'outputs': [], 'payable': false, 'type': 'function'}, {'anonymous': false, 'inputs': [{'indexed': true, 'name': 'node', 'type': 'bytes32'}, {'indexed': false, 'name': 'owner', 'type': 'address'}], 'name': 'Transfer', 'type': 'event'}, {'anonymous': false, 'inputs': [{'indexed': true, 'name': 'node', 'type': 'bytes32'}, {'indexed': true, 'name': 'label', 'type': 'bytes32'}, {'indexed': false, 'name': 'owner', 'type': 'address'}], 'name': 'NewOwner', 'type': 'event'}, {'anonymous': false, 'inputs': [{'indexed': true, 'name': 'node', 'type': 'bytes32'}, {'indexed': false, 'name': 'resolver', 'type': 'address'}], 'name': 'NewResolver', 'type': 'event'}, {'anonymous': false, 'inputs': [{'indexed': true, 'name': 'node', 'type': 'bytes32'}, {'indexed': false, 'name': 'ttl', 'type': 'uint64'}], 'name': 'NewTTL', 'type': 'event'}]
diff --git a/app/scripts/lib/contracts/resolver.js b/app/scripts/lib/contracts/resolver.js
new file mode 100644
index 000000000..1bf3f90ce
--- /dev/null
+++ b/app/scripts/lib/contracts/resolver.js
@@ -0,0 +1,2 @@
+module.exports =
+[{'constant': true, 'inputs': [{'name': 'interfaceID', 'type': 'bytes4'}], 'name': 'supportsInterface', 'outputs': [{'name': '', 'type': 'bool'}], 'payable': false, 'type': 'function'}, {'constant': true, 'inputs': [{'name': 'node', 'type': 'bytes32'}, {'name': 'contentTypes', 'type': 'uint256'}], 'name': 'ABI', 'outputs': [{'name': 'contentType', 'type': 'uint256'}, {'name': 'data', 'type': 'bytes'}], 'payable': false, 'type': 'function'}, {'constant': false, 'inputs': [{'name': 'node', 'type': 'bytes32'}, {'name': 'x', 'type': 'bytes32'}, {'name': 'y', 'type': 'bytes32'}], 'name': 'setPubkey', 'outputs': [], 'payable': false, 'type': 'function'}, {'constant': true, 'inputs': [{'name': 'node', 'type': 'bytes32'}], 'name': 'content', 'outputs': [{'name': 'ret', 'type': 'bytes32'}], 'payable': false, 'type': 'function'}, {'constant': true, 'inputs': [{'name': 'node', 'type': 'bytes32'}], 'name': 'addr', 'outputs': [{'name': 'ret', 'type': 'address'}], 'payable': false, 'type': 'function'}, {'constant': false, 'inputs': [{'name': 'node', 'type': 'bytes32'}, {'name': 'contentType', 'type': 'uint256'}, {'name': 'data', 'type': 'bytes'}], 'name': 'setABI', 'outputs': [], 'payable': false, 'type': 'function'}, {'constant': true, 'inputs': [{'name': 'node', 'type': 'bytes32'}], 'name': 'name', 'outputs': [{'name': 'ret', 'type': 'string'}], 'payable': false, 'type': 'function'}, {'constant': false, 'inputs': [{'name': 'node', 'type': 'bytes32'}, {'name': 'name', 'type': 'string'}], 'name': 'setName', 'outputs': [], 'payable': false, 'type': 'function'}, {'constant': false, 'inputs': [{'name': 'node', 'type': 'bytes32'}, {'name': 'hash', 'type': 'bytes32'}], 'name': 'setContent', 'outputs': [], 'payable': false, 'type': 'function'}, {'constant': true, 'inputs': [{'name': 'node', 'type': 'bytes32'}], 'name': 'pubkey', 'outputs': [{'name': 'x', 'type': 'bytes32'}, {'name': 'y', 'type': 'bytes32'}], 'payable': false, 'type': 'function'}, {'constant': false, 'inputs': [{'name': 'node', 'type': 'bytes32'}, {'name': 'addr', 'type': 'address'}], 'name': 'setAddr', 'outputs': [], 'payable': false, 'type': 'function'}, {'inputs': [{'name': 'ensAddr', 'type': 'address'}], 'payable': false, 'type': 'constructor'}, {'anonymous': false, 'inputs': [{'indexed': true, 'name': 'node', 'type': 'bytes32'}, {'indexed': false, 'name': 'a', 'type': 'address'}], 'name': 'AddrChanged', 'type': 'event'}, {'anonymous': false, 'inputs': [{'indexed': true, 'name': 'node', 'type': 'bytes32'}, {'indexed': false, 'name': 'hash', 'type': 'bytes32'}], 'name': 'ContentChanged', 'type': 'event'}, {'anonymous': false, 'inputs': [{'indexed': true, 'name': 'node', 'type': 'bytes32'}, {'indexed': false, 'name': 'name', 'type': 'string'}], 'name': 'NameChanged', 'type': 'event'}, {'anonymous': false, 'inputs': [{'indexed': true, 'name': 'node', 'type': 'bytes32'}, {'indexed': true, 'name': 'contentType', 'type': 'uint256'}], 'name': 'ABIChanged', 'type': 'event'}, {'anonymous': false, 'inputs': [{'indexed': true, 'name': 'node', 'type': 'bytes32'}, {'indexed': false, 'name': 'x', 'type': 'bytes32'}, {'indexed': false, 'name': 'y', 'type': 'bytes32'}], 'name': 'PubkeyChanged', 'type': 'event'}]
diff --git a/app/scripts/lib/createErrorMiddleware.js b/app/scripts/lib/createErrorMiddleware.js
index c70beddfd..7f6a4bd73 100644
--- a/app/scripts/lib/createErrorMiddleware.js
+++ b/app/scripts/lib/createErrorMiddleware.js
@@ -64,4 +64,4 @@ function createErrorMiddleware ({ override = true } = {}) {
}
}
-module.exports = createErrorMiddleware \ No newline at end of file
+module.exports = createErrorMiddleware
diff --git a/app/scripts/lib/createStreamSink.js b/app/scripts/lib/createStreamSink.js
index cf9416fea..b93dbc089 100644
--- a/app/scripts/lib/createStreamSink.js
+++ b/app/scripts/lib/createStreamSink.js
@@ -4,7 +4,7 @@ const promiseToCallback = require('promise-to-callback')
module.exports = createStreamSink
-function createStreamSink(asyncWriteFn, _opts) {
+function createStreamSink (asyncWriteFn, _opts) {
return new AsyncWritableStream(asyncWriteFn, _opts)
}
diff --git a/app/scripts/lib/diagnostics-reporter.js b/app/scripts/lib/diagnostics-reporter.js
index aa4ca6e26..569eb3268 100644
--- a/app/scripts/lib/diagnostics-reporter.js
+++ b/app/scripts/lib/diagnostics-reporter.js
@@ -5,7 +5,7 @@ class DiagnosticsReporter {
this.version = version
}
- async reportOrphans(orphans) {
+ async reportOrphans (orphans) {
try {
return await this.submit({
accounts: Object.keys(orphans),
@@ -19,7 +19,7 @@ class DiagnosticsReporter {
}
}
- async reportMultipleKeyrings(rawKeyrings) {
+ async reportMultipleKeyrings (rawKeyrings) {
try {
const keyrings = await Promise.all(rawKeyrings.map(async (keyring, index) => {
return {
@@ -55,7 +55,7 @@ class DiagnosticsReporter {
}
-function postData(data) {
+function postData (data) {
const uri = 'https://diagnostics.metamask.io/v1/orphanedAccounts'
return fetch(uri, {
body: JSON.stringify(data), // must match 'Content-Type' header
diff --git a/app/scripts/lib/extractEthjsErrorMessage.js b/app/scripts/lib/extractEthjsErrorMessage.js
index 0f100756f..4891075c3 100644
--- a/app/scripts/lib/extractEthjsErrorMessage.js
+++ b/app/scripts/lib/extractEthjsErrorMessage.js
@@ -10,13 +10,13 @@ module.exports = extractEthjsErrorMessage
*
* @param {string} errorMessage The error message to parse
* @returns {string} Returns an error message, either the same as was passed, or the ending message portion of an isEthjsRpcError
- *
+ *
* @example
* // returns 'Transaction Failed: replacement transaction underpriced'
* extractEthjsErrorMessage(`Error: [ethjs-rpc] rpc error with payload {"id":3947817945380,"jsonrpc":"2.0","params":["0xf8eb8208708477359400830398539406012c8cf97bead5deae237070f9587f8e7a266d80b8843d7d3f5a0000000000000000000000000000000000000000000000000000000000081d1a000000000000000000000000000000000000000000000000001ff973cafa800000000000000000000000000000000000000000000000000000038d7ea4c68000000000000000000000000000000000000000000000000000000000000003f48025a04c32a9b630e0d9e7ff361562d850c86b7a884908135956a7e4a336fa0300d19ca06830776423f25218e8d19b267161db526e66895567147015b1f3fc47aef9a3c7"],"method":"eth_sendRawTransaction"} Error: replacement transaction underpriced`)
*
*/
-function extractEthjsErrorMessage(errorMessage) {
+function extractEthjsErrorMessage (errorMessage) {
const isEthjsRpcError = errorMessage.includes(ethJsRpcSlug)
if (isEthjsRpcError) {
const payloadAndError = errorMessage.slice(ethJsRpcSlug.length)
diff --git a/app/scripts/lib/get-first-preferred-lang-code.js b/app/scripts/lib/get-first-preferred-lang-code.js
index 41a886d74..170d508c1 100644
--- a/app/scripts/lib/get-first-preferred-lang-code.js
+++ b/app/scripts/lib/get-first-preferred-lang-code.js
@@ -28,7 +28,7 @@ async function getFirstPreferredLangCode () {
// safeguard for Brave Browser until they implement chrome.i18n.getAcceptLanguages
// https://github.com/MetaMask/metamask-extension/issues/4270
- if (!userPreferredLocaleCodes){
+ if (!userPreferredLocaleCodes) {
userPreferredLocaleCodes = []
}
diff --git a/app/scripts/lib/getObjStructure.js b/app/scripts/lib/getObjStructure.js
index 52250d3fb..9c92879fb 100644
--- a/app/scripts/lib/getObjStructure.js
+++ b/app/scripts/lib/getObjStructure.js
@@ -18,12 +18,12 @@ module.exports = getObjStructure
* Creates an object that represents the structure of the given object. It replaces all values with the result of their
* type.
*
- * @param {object} obj The object for which a 'structure' will be returned. Usually a plain object and not a class.
+ * @param {object} obj The object for which a 'structure' will be returned. Usually a plain object and not a class.
* @returns {object} The "mapped" version of a deep clone of the passed object, with each non-object property value
* replaced with the javascript type of that value.
*
*/
-function getObjStructure(obj) {
+function getObjStructure (obj) {
const structure = clone(obj)
return deepMap(structure, (value) => {
return value === null ? 'null' : typeof value
@@ -38,7 +38,7 @@ function getObjStructure(obj) {
* @param {Function} visit The modifier to apply to each non-object property value
* @returns {object} The modified object
*/
-function deepMap(target = {}, visit) {
+function deepMap (target = {}, visit) {
Object.entries(target).forEach(([key, value]) => {
if (typeof value === 'object' && value !== null) {
target[key] = deepMap(value, visit)
diff --git a/app/scripts/lib/inpage-provider.js b/app/scripts/lib/inpage-provider.js
index 4e65f0a23..6ef511453 100644
--- a/app/scripts/lib/inpage-provider.js
+++ b/app/scripts/lib/inpage-provider.js
@@ -54,6 +54,11 @@ function MetamaskInpageProvider (connectionStream) {
// also remap ids inbound and outbound
MetamaskInpageProvider.prototype.sendAsync = function (payload, cb) {
const self = this
+
+ if (payload.method === 'eth_signTypedData') {
+ console.warn('MetaMask: This experimental version of eth_signTypedData will be deprecated in the next release in favor of the standard as defined in EIP-712. See https://git.io/fNzPl for more information on the new standard.')
+ }
+
self.rpcEngine.handle(payload, cb)
}
diff --git a/app/scripts/lib/ipfsContent.js b/app/scripts/lib/ipfsContent.js
new file mode 100644
index 000000000..5222151ea
--- /dev/null
+++ b/app/scripts/lib/ipfsContent.js
@@ -0,0 +1,44 @@
+const extension = require('extensionizer')
+const resolver = require('./resolver.js')
+
+module.exports = function (provider) {
+ function ipfsContent (details) {
+ const name = details.url.substring(7, details.url.length - 1)
+ let clearTime = null
+ extension.tabs.getSelected(null, tab => {
+ extension.tabs.update(tab.id, { url: 'loading.html' })
+
+ clearTime = setTimeout(() => {
+ return extension.tabs.update(tab.id, { url: '404.html' })
+ }, 60000)
+
+ resolver.resolve(name, provider).then(ipfsHash => {
+ clearTimeout(clearTime)
+ let url = 'https://ipfs.infura.io/ipfs/' + ipfsHash
+ return fetch(url, { method: 'HEAD' }).then(response => response.status).then(statusCode => {
+ if (statusCode !== 200) return extension.tabs.update(tab.id, { url: '404.html' })
+ extension.tabs.update(tab.id, { url: url })
+ })
+ .catch(err => {
+ url = 'https://ipfs.infura.io/ipfs/' + ipfsHash
+ extension.tabs.update(tab.id, {url: url})
+ return err
+ })
+ })
+ .catch(err => {
+ clearTimeout(clearTime)
+ const url = err === 'unsupport' ? 'unsupport' : 'error'
+ extension.tabs.update(tab.id, {url: `${url}.html?name=${name}`})
+ })
+ })
+ return { cancel: true }
+ }
+
+ extension.webRequest.onBeforeRequest.addListener(ipfsContent, {urls: ['*://*.eth/', '*://*.test/']})
+
+ return {
+ remove () {
+ extension.webRequest.onBeforeRequest.removeListener(ipfsContent)
+ },
+ }
+}
diff --git a/app/scripts/lib/local-store.js b/app/scripts/lib/local-store.js
index 139ff86bd..fbcba09cd 100644
--- a/app/scripts/lib/local-store.js
+++ b/app/scripts/lib/local-store.js
@@ -8,7 +8,7 @@ module.exports = class ExtensionStore {
/**
* @constructor
*/
- constructor() {
+ constructor () {
this.isSupported = !!(extension.storage.local)
if (!this.isSupported) {
log.error('Storage local API not available.')
@@ -19,7 +19,7 @@ module.exports = class ExtensionStore {
* Returns all of the keys currently saved
* @return {Promise<*>}
*/
- async get() {
+ async get () {
if (!this.isSupported) return undefined
const result = await this._get()
// extension.storage.local always returns an obj
@@ -36,7 +36,7 @@ module.exports = class ExtensionStore {
* @param {object} state - The state to set
* @return {Promise<void>}
*/
- async set(state) {
+ async set (state) {
return this._set(state)
}
@@ -45,7 +45,7 @@ module.exports = class ExtensionStore {
* @private
* @return {object} the key-value map from local storage
*/
- _get() {
+ _get () {
const local = extension.storage.local
return new Promise((resolve, reject) => {
local.get(null, (/** @type {any} */ result) => {
@@ -65,7 +65,7 @@ module.exports = class ExtensionStore {
* @return {Promise<void>}
* @private
*/
- _set(obj) {
+ _set (obj) {
const local = extension.storage.local
return new Promise((resolve, reject) => {
local.set(obj, () => {
@@ -85,6 +85,6 @@ module.exports = class ExtensionStore {
* @param {object} obj - The object to check
* @returns {boolean}
*/
-function isEmpty(obj) {
+function isEmpty (obj) {
return Object.keys(obj).length === 0
}
diff --git a/app/scripts/lib/notification-manager.js b/app/scripts/lib/notification-manager.js
index 5dfb42078..969a9459a 100644
--- a/app/scripts/lib/notification-manager.js
+++ b/app/scripts/lib/notification-manager.js
@@ -26,13 +26,15 @@ class NotificationManager {
// bring focus to existing chrome popup
extension.windows.update(popup.id, { focused: true })
} else {
+ const cb = (currentPopup) => { this._popupId = currentPopup.id }
// create new notification popup
- extension.windows.create({
+ const creation = extension.windows.create({
url: 'notification.html',
type: 'popup',
width,
height,
- })
+ }, cb)
+ creation && creation.then && creation.then(cb)
}
})
}
@@ -84,7 +86,7 @@ class NotificationManager {
}
/**
- * Given an array of windows, returns the first that has a 'popup' type, or null if no such window exists.
+ * Given an array of windows, returns the 'popup' that has been opened by MetaMask, or null if no such window exists.
*
* @private
* @param {array} windows An array of objects containing data about the open MetaMask extension windows.
@@ -93,7 +95,7 @@ class NotificationManager {
_getPopupIn (windows) {
return windows ? windows.find((win) => {
// Returns notification popup
- return (win && win.type === 'popup')
+ return (win && win.type === 'popup' && win.id === this._popupId)
}) : null
}
diff --git a/app/scripts/lib/port-stream.js b/app/scripts/lib/port-stream.js
index 5c4224fd9..fd65d94f3 100644
--- a/app/scripts/lib/port-stream.js
+++ b/app/scripts/lib/port-stream.js
@@ -58,7 +58,7 @@ PortDuplexStream.prototype._read = noop
/**
* Called internally when data should be written to
* this writable stream.
- *
+ *
* @private
* @param {*} msg Arbitrary object to write
* @param {string} encoding Encoding to use when writing payload
diff --git a/app/scripts/lib/reportFailedTxToSentry.js b/app/scripts/lib/reportFailedTxToSentry.js
index e09f4f1f8..df5661e59 100644
--- a/app/scripts/lib/reportFailedTxToSentry.js
+++ b/app/scripts/lib/reportFailedTxToSentry.js
@@ -7,7 +7,7 @@ module.exports = reportFailedTxToSentry
// for sending to sentry
//
-function reportFailedTxToSentry({ raven, txMeta }) {
+function reportFailedTxToSentry ({ raven, txMeta }) {
const errorMessage = 'Transaction Failed: ' + extractEthjsErrorMessage(txMeta.err.message)
raven.captureMessage(errorMessage, {
// "extra" key is required by Sentry
diff --git a/app/scripts/lib/resolver.js b/app/scripts/lib/resolver.js
new file mode 100644
index 000000000..ff0fed161
--- /dev/null
+++ b/app/scripts/lib/resolver.js
@@ -0,0 +1,71 @@
+const namehash = require('eth-ens-namehash')
+const multihash = require('multihashes')
+const HttpProvider = require('ethjs-provider-http')
+const Eth = require('ethjs-query')
+const EthContract = require('ethjs-contract')
+const registrarAbi = require('./contracts/registrar')
+const resolverAbi = require('./contracts/resolver')
+
+function ens (name, provider) {
+ const eth = new Eth(new HttpProvider(getProvider(provider.type)))
+ const hash = namehash.hash(name)
+ const contract = new EthContract(eth)
+ const Registrar = contract(registrarAbi).at(getRegistrar(provider.type))
+ return new Promise((resolve, reject) => {
+ if (provider.type === 'mainnet' || provider.type === 'ropsten') {
+ Registrar.resolver(hash).then((address) => {
+ if (address === '0x0000000000000000000000000000000000000000') {
+ reject(null)
+ } else {
+ const Resolver = contract(resolverAbi).at(address['0'])
+ return Resolver.content(hash)
+ }
+ }).then((contentHash) => {
+ if (contentHash['0'] === '0x0000000000000000000000000000000000000000000000000000000000000000') reject(null)
+ if (contentHash.ret !== '0x') {
+ const hex = contentHash['0'].substring(2)
+ const buf = multihash.fromHexString(hex)
+ resolve(multihash.toB58String(multihash.encode(buf, 'sha2-256')))
+ } else {
+ reject(null)
+ }
+ })
+ } else {
+ return reject('unsupport')
+ }
+ })
+}
+
+function getProvider (type) {
+ switch (type) {
+ case 'mainnet':
+ return 'https://mainnet.infura.io/'
+ case 'ropsten':
+ return 'https://ropsten.infura.io/'
+ default:
+ return 'http://localhost:8545/'
+ }
+}
+
+function getRegistrar (type) {
+ switch (type) {
+ case 'mainnet':
+ return '0x314159265dd8dbb310642f98f50c066173c1259b'
+ case 'ropsten':
+ return '0x112234455c3a32fd11230c42e7bccd4a84e02010'
+ default:
+ return '0x0000000000000000000000000000000000000000'
+ }
+}
+
+module.exports.resolve = function (name, provider) {
+ const path = name.split('.')
+ const topLevelDomain = path[path.length - 1]
+ if (topLevelDomain === 'eth' || topLevelDomain === 'test') {
+ return ens(name, provider)
+ } else {
+ return new Promise((resolve, reject) => {
+ reject(null)
+ })
+ }
+}
diff --git a/app/scripts/lib/setupMetamaskMeshMetrics.js b/app/scripts/lib/setupMetamaskMeshMetrics.js
index 02690a948..fd3b93fc4 100644
--- a/app/scripts/lib/setupMetamaskMeshMetrics.js
+++ b/app/scripts/lib/setupMetamaskMeshMetrics.js
@@ -4,7 +4,7 @@ module.exports = setupMetamaskMeshMetrics
/**
* Injects an iframe into the current document for testing
*/
-function setupMetamaskMeshMetrics() {
+function setupMetamaskMeshMetrics () {
const testingContainer = document.createElement('iframe')
testingContainer.src = 'https://metamask.github.io/mesh-testing/'
console.log('Injecting MetaMask Mesh testing client')
diff --git a/app/scripts/lib/setupRaven.js b/app/scripts/lib/setupRaven.js
index 77aefb00a..e657e278f 100644
--- a/app/scripts/lib/setupRaven.js
+++ b/app/scripts/lib/setupRaven.js
@@ -7,9 +7,11 @@ const DEV = 'https://f59f3dd640d2429d9d0e2445a87ea8e1@sentry.io/273496'
module.exports = setupRaven
// Setup raven / sentry remote error reporting
-function setupRaven(opts) {
- const { release } = opts
+function setupRaven (opts) {
+ const { releaseVersion } = opts
let ravenTarget
+ // detect brave
+ const isBrave = Boolean(window.chrome.ipcRenderer)
if (METAMASK_DEBUG) {
console.log('Setting up Sentry Remote Error Reporting: DEV')
@@ -20,9 +22,11 @@ function setupRaven(opts) {
}
const client = Raven.config(ravenTarget, {
- release,
- transport: function(opts) {
+ releaseVersion,
+ transport: function (opts) {
+ opts.data.extra.isBrave = isBrave
const report = opts.data
+
try {
// handle error-like non-error exceptions
rewriteErrorLikeExceptions(report)
@@ -42,7 +46,7 @@ function setupRaven(opts) {
return Raven
}
-function rewriteErrorLikeExceptions(report) {
+function rewriteErrorLikeExceptions (report) {
// handle errors that lost their error-ness in serialization (e.g. dnode)
rewriteErrorMessages(report, (errorMessage) => {
if (!errorMessage.includes('Non-Error exception captured with keys:')) return errorMessage
@@ -51,7 +55,7 @@ function rewriteErrorLikeExceptions(report) {
})
}
-function simplifyErrorMessages(report) {
+function simplifyErrorMessages (report) {
rewriteErrorMessages(report, (errorMessage) => {
// simplify ethjs error messages
errorMessage = extractEthjsErrorMessage(errorMessage)
@@ -64,7 +68,7 @@ function simplifyErrorMessages(report) {
})
}
-function rewriteErrorMessages(report, rewriteFn) {
+function rewriteErrorMessages (report, rewriteFn) {
// rewrite top level message
if (report.message) report.message = rewriteFn(report.message)
// rewrite each exception message
@@ -75,7 +79,7 @@ function rewriteErrorMessages(report, rewriteFn) {
}
}
-function rewriteReportUrls(report) {
+function rewriteReportUrls (report) {
// update request url
report.request.url = toMetamaskUrl(report.request.url)
// update exception stack trace
@@ -88,7 +92,7 @@ function rewriteReportUrls(report) {
}
}
-function toMetamaskUrl(origUrl) {
+function toMetamaskUrl (origUrl) {
const filePath = origUrl.split(location.origin)[1]
if (!filePath) return origUrl
const metamaskUrl = `metamask${filePath}`
diff --git a/app/scripts/lib/util.js b/app/scripts/lib/util.js
index 431d1e59c..51e9036cc 100644
--- a/app/scripts/lib/util.js
+++ b/app/scripts/lib/util.js
@@ -28,7 +28,7 @@ function getStack () {
*
*/
const getEnvironmentType = (url = window.location.href) => {
- if (url.match(/popup.html(?:\?.+)*$/)) {
+ if (url.match(/popup.html(?:#.*)*$/)) {
return ENVIRONMENT_TYPE_POPUP
} else if (url.match(/home.html(?:\?.+)*$/) || url.match(/home.html(?:#.*)*$/)) {
return ENVIRONMENT_TYPE_FULLSCREEN
diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js
index d5627a0d1..e843ec660 100644
--- a/app/scripts/metamask-controller.js
+++ b/app/scripts/metamask-controller.js
@@ -35,6 +35,7 @@ const TypedMessageManager = require('./lib/typed-message-manager')
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')
@@ -47,6 +48,7 @@ const percentile = require('percentile')
const seedPhraseVerifier = require('./lib/seed-phrase-verifier')
const cleanErrorStack = require('./lib/cleanErrorStack')
const log = require('loglevel')
+const TrezorKeyring = require('eth-trezor-keyring')
module.exports = class MetamaskController extends EventEmitter {
@@ -125,7 +127,9 @@ module.exports = class MetamaskController extends EventEmitter {
})
// key mgmt
+ const additionalKeyrings = [TrezorKeyring]
this.keyringController = new KeyringController({
+ keyringTypes: additionalKeyrings,
initState: initState.KeyringController,
getNetwork: this.networkController.getNetworkState.bind(this.networkController),
encryptor: opts.encryptor || undefined,
@@ -145,6 +149,13 @@ module.exports = class MetamaskController extends EventEmitter {
this.accountTracker.syncWithAddresses(addresses)
})
+ // detect tokens controller
+ this.detectTokensController = new DetectTokensController({
+ preferences: this.preferencesController,
+ network: this.networkController,
+ keyringMemStore: this.keyringController.memStore,
+ })
+
// address book controller
this.addressBookController = new AddressBookController({
initState: initState.AddressBookController,
@@ -165,6 +176,13 @@ module.exports = class MetamaskController extends EventEmitter {
})
this.txController.on('newUnapprovedTx', opts.showUnapprovedTx.bind(opts))
+ this.txController.on(`tx:status-update`, (txId, status) => {
+ if (status === 'confirmed' || status === 'failed') {
+ const txMeta = this.txController.txStateManager.getTx(txId)
+ this.platform.showTransactionNotification(txMeta)
+ }
+ })
+
// computed balances (accounting for pending transactions)
this.balancesController = new BalancesController({
accountTracker: this.accountTracker,
@@ -339,6 +357,7 @@ module.exports = class MetamaskController extends EventEmitter {
markAccountsFound: this.markAccountsFound.bind(this),
markPasswordForgotten: this.markPasswordForgotten.bind(this),
unMarkPasswordForgotten: this.unMarkPasswordForgotten.bind(this),
+ getGasPrice: (cb) => cb(null, this.getGasPrice()),
// coinbase
buyEth: this.buyEth.bind(this),
@@ -351,8 +370,17 @@ module.exports = class MetamaskController extends EventEmitter {
verifySeedPhrase: nodeify(this.verifySeedPhrase, this),
clearSeedWordCache: this.clearSeedWordCache.bind(this),
resetAccount: nodeify(this.resetAccount, this),
+ removeAccount: nodeify(this.removeAccount, this),
importAccountWithStrategy: nodeify(this.importAccountWithStrategy, this),
+ // hardware wallets
+ connectHardware: nodeify(this.connectHardware, this),
+ forgetDevice: nodeify(this.forgetDevice, this),
+ checkHardwareStatus: nodeify(this.checkHardwareStatus, this),
+
+ // TREZOR
+ unlockTrezorAccount: nodeify(this.unlockTrezorAccount, this),
+
// vault management
submitPassword: nodeify(this.submitPassword, this),
@@ -406,7 +434,6 @@ module.exports = class MetamaskController extends EventEmitter {
}
-
//=============================================================================
// VAULT / KEYRING RELATED METHODS
//=============================================================================
@@ -510,6 +537,127 @@ module.exports = class MetamaskController extends EventEmitter {
}
//
+ // Hardware
+ //
+
+ /**
+ * Fetch account list from a trezor device.
+ *
+ * @returns [] accounts
+ */
+ async connectHardware (deviceName, page) {
+
+ switch (deviceName) {
+ case 'trezor':
+ const keyringController = this.keyringController
+ const oldAccounts = await keyringController.getAccounts()
+ let keyring = await keyringController.getKeyringsByType(
+ 'Trezor Hardware'
+ )[0]
+ if (!keyring) {
+ keyring = await this.keyringController.addNewKeyring('Trezor Hardware')
+ }
+ let accounts = []
+
+ switch (page) {
+ case -1:
+ accounts = await keyring.getPreviousPage()
+ break
+ case 1:
+ accounts = await keyring.getNextPage()
+ break
+ default:
+ accounts = await keyring.getFirstPage()
+ }
+
+ // Merge with existing accounts
+ // and make sure addresses are not repeated
+ const accountsToTrack = [...new Set(oldAccounts.concat(accounts.map(a => a.address.toLowerCase())))]
+ this.accountTracker.syncWithAddresses(accountsToTrack)
+ return accounts
+
+ default:
+ throw new Error('MetamaskController:connectHardware - Unknown device')
+ }
+ }
+
+ /**
+ * Check if the device is unlocked
+ *
+ * @returns {Promise<boolean>}
+ */
+ async checkHardwareStatus (deviceName) {
+
+ switch (deviceName) {
+ case 'trezor':
+ const keyringController = this.keyringController
+ const keyring = await keyringController.getKeyringsByType(
+ 'Trezor Hardware'
+ )[0]
+ if (!keyring) {
+ return false
+ }
+ return keyring.isUnlocked()
+ default:
+ throw new Error('MetamaskController:checkHardwareStatus - Unknown device')
+ }
+ }
+
+ /**
+ * Clear
+ *
+ * @returns {Promise<boolean>}
+ */
+ async forgetDevice (deviceName) {
+
+ switch (deviceName) {
+ case 'trezor':
+ const keyringController = this.keyringController
+ const keyring = await keyringController.getKeyringsByType(
+ 'Trezor Hardware'
+ )[0]
+ if (!keyring) {
+ throw new Error('MetamaskController:forgetDevice - Trezor Hardware keyring not found')
+ }
+ keyring.forgetDevice()
+ return true
+ default:
+ throw new Error('MetamaskController:forgetDevice - Unknown device')
+ }
+ }
+
+ /**
+ * Imports an account from a trezor device.
+ *
+ * @returns {} keyState
+ */
+ async unlockTrezorAccount (index) {
+ const keyringController = this.keyringController
+ const keyring = await keyringController.getKeyringsByType(
+ 'Trezor Hardware'
+ )[0]
+ if (!keyring) {
+ throw new Error('MetamaskController - No Trezor Hardware Keyring found')
+ }
+
+ keyring.setAccountToUnlock(index)
+ const oldAccounts = await keyringController.getAccounts()
+ const keyState = await keyringController.addNewAccount(keyring)
+ const newAccounts = await keyringController.getAccounts()
+ this.preferencesController.setAddresses(newAccounts)
+ newAccounts.forEach(address => {
+ if (!oldAccounts.includes(address)) {
+ this.preferencesController.setAccountLabel(address, `TREZOR #${parseInt(index, 10) + 1}`)
+ this.preferencesController.setSelectedAddress(address)
+ }
+ })
+
+ const { identities } = this.preferencesController.store.getState()
+ return { ...keyState, identities }
+ }
+
+
+ //
// Account Management
//
@@ -622,6 +770,23 @@ module.exports = class MetamaskController extends EventEmitter {
}
/**
+ * Removes an account from state / storage.
+ *
+ * @param {string[]} address A hex address
+ *
+ */
+ async removeAccount (address) {
+ // Remove account from the preferences controller
+ this.preferencesController.removeAddress(address)
+ // Remove account from the account tracker controller
+ this.accountTracker.removeAccount(address)
+ // Remove account from the keyring
+ await this.keyringController.removeAccount(address)
+ return address
+ }
+
+
+ /**
* Imports an account with the specified import strategy.
* These are defined in app/scripts/account-import-strategies
* Each strategy represents a different way of serializing an Ethereum key pair.
@@ -963,7 +1128,7 @@ module.exports = class MetamaskController extends EventEmitter {
* Allows a user to begin the seed phrase recovery process.
* @param {Function} cb - A callback function called when complete.
*/
- markPasswordForgotten(cb) {
+ markPasswordForgotten (cb) {
this.configManager.setPasswordForgotten(true)
this.sendUpdate()
cb()
@@ -973,7 +1138,7 @@ module.exports = class MetamaskController extends EventEmitter {
* Allows a user to end the seed phrase recovery process.
* @param {Function} cb - A callback function called when complete.
*/
- unMarkPasswordForgotten(cb) {
+ unMarkPasswordForgotten (cb) {
this.configManager.setPasswordForgotten(false)
this.sendUpdate()
cb()
@@ -1269,10 +1434,12 @@ module.exports = class MetamaskController extends EventEmitter {
set isClientOpen (open) {
this._isClientOpen = open
this.isClientOpenAndUnlocked = this.getState().isUnlocked && open
+ this.detectTokensController.isOpen = open
}
/**
- * A method for activating the retrieval of price data, which should only be fetched when the UI is visible.
+ * A method for activating the retrieval of price data and auto detect tokens,
+ * which should only be fetched when the UI is visible.
* @private
* @param {boolean} active - True if price data should be getting fetched.
*/
diff --git a/app/scripts/migrations/013.js b/app/scripts/migrations/013.js
index 15a9b28d4..fb7131f8e 100644
--- a/app/scripts/migrations/013.js
+++ b/app/scripts/migrations/013.js
@@ -28,7 +28,7 @@ module.exports = {
function transformState (state) {
const newState = state
const { config } = newState
- if ( config && config.provider ) {
+ if (config && config.provider) {
if (config.provider.type === 'testnet') {
newState.config.provider.type = 'ropsten'
}
diff --git a/app/scripts/migrations/023.js b/app/scripts/migrations/023.js
index 151496b06..18493a789 100644
--- a/app/scripts/migrations/023.js
+++ b/app/scripts/migrations/023.js
@@ -35,10 +35,10 @@ function transformState (state) {
if (transactions.length <= 40) return newState
- let reverseTxList = transactions.reverse()
+ const reverseTxList = transactions.reverse()
let stripping = true
while (reverseTxList.length > 40 && stripping) {
- let txIndex = reverseTxList.findIndex((txMeta) => {
+ const txIndex = reverseTxList.findIndex((txMeta) => {
return (txMeta.status === 'failed' ||
txMeta.status === 'rejected' ||
txMeta.status === 'confirmed' ||
diff --git a/app/scripts/migrations/027.js b/app/scripts/migrations/027.js
new file mode 100644
index 000000000..d6ebef580
--- /dev/null
+++ b/app/scripts/migrations/027.js
@@ -0,0 +1,35 @@
+// next version number
+const version = 27
+
+/*
+
+normalizes txParams on unconfirmed txs
+
+*/
+const clone = require('clone')
+
+module.exports = {
+ version,
+
+ migrate: async function (originalVersionedData) {
+ const versionedData = clone(originalVersionedData)
+ versionedData.meta.version = version
+ const state = versionedData.data
+ const newState = transformState(state)
+ versionedData.data = newState
+ return versionedData
+ },
+}
+
+function transformState (state) {
+ const newState = state
+
+ if (newState.TransactionController) {
+ if (newState.TransactionController.transactions) {
+ const transactions = newState.TransactionController.transactions
+ newState.TransactionController.transactions = transactions.filter((txMeta) => txMeta.status !== 'rejected')
+ }
+ }
+
+ return newState
+}
diff --git a/app/scripts/migrations/index.js b/app/scripts/migrations/index.js
index 04d90bfff..bd0005221 100644
--- a/app/scripts/migrations/index.js
+++ b/app/scripts/migrations/index.js
@@ -37,4 +37,5 @@ module.exports = [
require('./024'),
require('./025'),
require('./026'),
+ require('./027'),
]
diff --git a/app/scripts/platforms/extension.js b/app/scripts/platforms/extension.js
index f5cc255d1..0803164e8 100644
--- a/app/scripts/platforms/extension.js
+++ b/app/scripts/platforms/extension.js
@@ -1,4 +1,5 @@
const extension = require('extensionizer')
+const explorerLink = require('etherscan-link').createExplorerLink
class ExtensionPlatform {
@@ -13,12 +14,21 @@ class ExtensionPlatform {
extension.tabs.create({ url })
}
+ closeCurrentWindow () {
+ return extension.windows.getCurrent((windowDetails) => {
+ return extension.windows.remove(windowDetails.id)
+ })
+ }
+
getVersion () {
return extension.runtime.getManifest().version
}
- openExtensionInBrowser () {
- const extensionURL = extension.runtime.getURL('home.html')
+ openExtensionInBrowser (route = null) {
+ let extensionURL = extension.runtime.getURL('home.html')
+ if (route) {
+ extensionURL += `#${route}`
+ }
this.openWindow({ url: extensionURL })
}
@@ -31,6 +41,59 @@ class ExtensionPlatform {
cb(e)
}
}
+
+ showTransactionNotification (txMeta) {
+
+ const status = txMeta.status
+ if (status === 'confirmed') {
+ this._showConfirmedTransaction(txMeta)
+ } else if (status === 'failed') {
+ this._showFailedTransaction(txMeta)
+ }
+ }
+
+ _showConfirmedTransaction (txMeta) {
+
+ this._subscribeToNotificationClicked()
+
+ const url = explorerLink(txMeta.hash, parseInt(txMeta.metamaskNetworkId))
+ const nonce = parseInt(txMeta.txParams.nonce, 16)
+
+ const title = 'Confirmed transaction'
+ const message = `Transaction ${nonce} confirmed! View on EtherScan`
+ this._showNotification(title, message, url)
+ }
+
+ _showFailedTransaction (txMeta) {
+
+ const nonce = parseInt(txMeta.txParams.nonce, 16)
+ const title = 'Failed transaction'
+ const message = `Transaction ${nonce} failed! ${txMeta.err.message}`
+ this._showNotification(title, message)
+ }
+
+ _showNotification (title, message, url) {
+ extension.notifications.create(
+ url,
+ {
+ 'type': 'basic',
+ 'title': title,
+ 'iconUrl': extension.extension.getURL('../../images/icon-64.png'),
+ 'message': message,
+ })
+ }
+
+ _subscribeToNotificationClicked () {
+ if (!extension.notifications.onClicked.hasListener(this._viewOnEtherScan)) {
+ extension.notifications.onClicked.addListener(this._viewOnEtherScan)
+ }
+ }
+
+ _viewOnEtherScan (txId) {
+ if (txId.startsWith('http://')) {
+ global.metamaskController.platform.openWindow({ url: txId })
+ }
+ }
}
module.exports = ExtensionPlatform
diff --git a/app/scripts/popup-core.js b/app/scripts/popup-core.js
index 6325b8a8d..db885ec93 100644
--- a/app/scripts/popup-core.js
+++ b/app/scripts/popup-core.js
@@ -12,7 +12,7 @@ module.exports = initializePopup
/**
* Asynchronously initializes the MetaMask popup UI
*
- * @param {{ container: Element, connectionStream: * }} config Popup configuration object
+ * @param {{ container: Element, connectionStream: * }} config Popup configuration object
* @param {Function} cb Called when initialization is complete
*/
function initializePopup ({ container, connectionStream }, cb) {
diff --git a/app/scripts/ui.js b/app/scripts/ui.js
index bdab29c1e..da100f928 100644
--- a/app/scripts/ui.js
+++ b/app/scripts/ui.js
@@ -14,7 +14,7 @@ const log = require('loglevel')
start().catch(log.error)
-async function start() {
+async function start () {
// create platform global
global.platform = new ExtensionPlatform()
@@ -64,7 +64,6 @@ async function start() {
css = betaUIState ? NewMetaMaskUiCss() : OldMetaMaskUiCss()
deleteInjectedCss = injectCss(css)
}
- if (state.appState.shouldClose) notificationManager.closePopup()
})
})