aboutsummaryrefslogtreecommitdiffstats
path: root/app
diff options
context:
space:
mode:
authorDan <danjm.com@gmail.com>2017-12-21 06:46:12 +0800
committerDan <danjm.com@gmail.com>2017-12-21 06:46:12 +0800
commit7915e62d65de1bbc3419cba58f69e6d55a4df3ea (patch)
treeb1b9c006bac35381619eccde235d66e0a4c0bf1f /app
parentbccbf14b39ab2b1670c9c30b276404fe4f949cd7 (diff)
parentd3f5ad874e4bf90cda4e440f9b0635c6ac416382 (diff)
downloadtangerine-wallet-browser-7915e62d65de1bbc3419cba58f69e6d55a4df3ea.tar
tangerine-wallet-browser-7915e62d65de1bbc3419cba58f69e6d55a4df3ea.tar.gz
tangerine-wallet-browser-7915e62d65de1bbc3419cba58f69e6d55a4df3ea.tar.bz2
tangerine-wallet-browser-7915e62d65de1bbc3419cba58f69e6d55a4df3ea.tar.lz
tangerine-wallet-browser-7915e62d65de1bbc3419cba58f69e6d55a4df3ea.tar.xz
tangerine-wallet-browser-7915e62d65de1bbc3419cba58f69e6d55a4df3ea.tar.zst
tangerine-wallet-browser-7915e62d65de1bbc3419cba58f69e6d55a4df3ea.zip
Merge branch 'master' into NewUI-flat
Diffstat (limited to 'app')
-rw-r--r--app/scripts/contentscript.js12
-rw-r--r--app/scripts/controllers/network.js61
-rw-r--r--app/scripts/controllers/preferences.js8
-rw-r--r--app/scripts/controllers/recent-blocks.js44
-rw-r--r--app/scripts/controllers/transactions.js29
-rw-r--r--app/scripts/lib/account-tracker.js2
-rw-r--r--app/scripts/lib/tx-gas-utils.js6
-rw-r--r--app/scripts/lib/tx-state-manager.js6
-rw-r--r--app/scripts/metamask-controller.js48
9 files changed, 179 insertions, 37 deletions
diff --git a/app/scripts/contentscript.js b/app/scripts/contentscript.js
index ffbbc73cc..2ed7c87b6 100644
--- a/app/scripts/contentscript.js
+++ b/app/scripts/contentscript.js
@@ -96,7 +96,7 @@ function logStreamDisconnectWarning (remoteLabel, err) {
}
function shouldInjectWeb3 () {
- return doctypeCheck() || suffixCheck()
+ return doctypeCheck() && suffixCheck() && documentElementCheck()
}
function doctypeCheck () {
@@ -104,7 +104,7 @@ function doctypeCheck () {
if (doctype) {
return doctype.name === 'html'
} else {
- return false
+ return true
}
}
@@ -121,6 +121,14 @@ function suffixCheck () {
return true
}
+function documentElementCheck () {
+ var documentElement = document.documentElement.nodeName
+ if (documentElement) {
+ return documentElement.toLowerCase() === 'html'
+ }
+ return true
+}
+
function redirectToPhishingWarning () {
console.log('MetaMask - redirecting to phishing warning')
window.location.href = 'https://metamask.io/phishing.html'
diff --git a/app/scripts/controllers/network.js b/app/scripts/controllers/network.js
index 045bfcc5d..377ba6eca 100644
--- a/app/scripts/controllers/network.js
+++ b/app/scripts/controllers/network.js
@@ -1,6 +1,7 @@
const assert = require('assert')
const EventEmitter = require('events')
const createMetamaskProvider = require('web3-provider-engine/zero.js')
+const createInfuraProvider = require('eth-json-rpc-infura/src/createProvider')
const ObservableStore = require('obs-store')
const ComposedStore = require('obs-store/lib/composed')
const extend = require('xtend')
@@ -8,6 +9,7 @@ const EthQuery = require('eth-query')
const createEventEmitterProxy = require('../lib/events-proxy.js')
const RPC_ADDRESS_LIST = require('../config.js').network
const DEFAULT_RPC = RPC_ADDRESS_LIST['rinkeby']
+const INFURA_PROVIDER_TYPES = ['ropsten', 'rinkeby', 'kovan', 'mainnet']
module.exports = class NetworkController extends EventEmitter {
@@ -24,8 +26,13 @@ module.exports = class NetworkController extends EventEmitter {
initializeProvider (_providerParams) {
this._baseProviderParams = _providerParams
- const rpcUrl = this.getCurrentRpcAddress()
- this._configureStandardProvider({ rpcUrl })
+ const { type, rpcTarget } = this.providerStore.getState()
+ // map rpcTarget to rpcUrl
+ const opts = {
+ type,
+ rpcUrl: rpcTarget,
+ }
+ this._configureProvider(opts)
this._proxy.on('block', this._logBlock.bind(this))
this._proxy.on('error', this.verifyNetwork.bind(this))
this.ethQuery = new EthQuery(this._proxy)
@@ -83,7 +90,7 @@ module.exports = class NetworkController extends EventEmitter {
const rpcTarget = this.getRpcAddressForType(type)
assert(rpcTarget, `NetworkController - unknown rpc address for type "${type}"`)
this.providerStore.updateState({ type, rpcTarget })
- this._switchNetwork({ rpcUrl: rpcTarget })
+ this._switchNetwork({ type })
}
getProviderConfig () {
@@ -99,14 +106,54 @@ module.exports = class NetworkController extends EventEmitter {
// Private
//
- _switchNetwork (providerParams) {
+ _switchNetwork (opts) {
this.setNetworkState('loading')
- this._configureStandardProvider(providerParams)
+ this._configureProvider(opts)
this.emit('networkDidChange')
}
- _configureStandardProvider (_providerParams) {
- const providerParams = extend(this._baseProviderParams, _providerParams)
+ _configureProvider (opts) {
+ // type-based rpc endpoints
+ const { type } = opts
+ if (type) {
+ // type-based infura rpc endpoints
+ const isInfura = INFURA_PROVIDER_TYPES.includes(type)
+ opts.rpcUrl = this.getRpcAddressForType(type)
+ if (isInfura) {
+ this._configureInfuraProvider(opts)
+ // other type-based rpc endpoints
+ } else {
+ this._configureStandardProvider(opts)
+ }
+ // url-based rpc endpoints
+ } else {
+ this._configureStandardProvider(opts)
+ }
+ }
+
+ _configureInfuraProvider (opts) {
+ log.info('_configureInfuraProvider', opts)
+ const blockTrackerProvider = createInfuraProvider({
+ network: opts.type,
+ })
+ const providerParams = extend(this._baseProviderParams, {
+ rpcUrl: opts.rpcUrl,
+ engineParams: {
+ pollingInterval: 8000,
+ blockTrackerProvider,
+ },
+ })
+ const provider = createMetamaskProvider(providerParams)
+ this._setProvider(provider)
+ }
+
+ _configureStandardProvider ({ rpcUrl }) {
+ const providerParams = extend(this._baseProviderParams, {
+ rpcUrl,
+ engineParams: {
+ pollingInterval: 8000,
+ },
+ })
const provider = createMetamaskProvider(providerParams)
this._setProvider(provider)
}
diff --git a/app/scripts/controllers/preferences.js b/app/scripts/controllers/preferences.js
index de9006044..39d15fd83 100644
--- a/app/scripts/controllers/preferences.js
+++ b/app/scripts/controllers/preferences.js
@@ -36,22 +36,24 @@ class PreferencesController {
return this.store.getState().selectedAddress
}
- addToken (rawAddress, symbol, decimals) {
+ async addToken (rawAddress, symbol, decimals) {
const address = normalizeAddress(rawAddress)
const newEntry = { address, symbol, decimals }
const tokens = this.store.getState().tokens
- const previousIndex = tokens.find((token, index) => {
+ const previousEntry = tokens.find((token, index) => {
return token.address === address
})
+ const previousIndex = tokens.indexOf(previousEntry)
- if (previousIndex) {
+ if (previousEntry) {
tokens[previousIndex] = newEntry
} else {
tokens.push(newEntry)
}
this.store.updateState({ tokens })
+
return Promise.resolve(tokens)
}
diff --git a/app/scripts/controllers/recent-blocks.js b/app/scripts/controllers/recent-blocks.js
new file mode 100644
index 000000000..4a906261e
--- /dev/null
+++ b/app/scripts/controllers/recent-blocks.js
@@ -0,0 +1,44 @@
+const ObservableStore = require('obs-store')
+const extend = require('xtend')
+
+class RecentBlocksController {
+
+ constructor (opts = {}) {
+ const { blockTracker } = opts
+ this.blockTracker = blockTracker
+ this.historyLength = opts.historyLength || 40
+
+ const initState = extend({
+ recentBlocks: [],
+ }, opts.initState)
+ this.store = new ObservableStore(initState)
+
+ this.blockTracker.on('block', this.processBlock.bind(this))
+ }
+
+ resetState () {
+ this.store.updateState({
+ recentBlocks: [],
+ })
+ }
+
+ processBlock (newBlock) {
+ const block = extend(newBlock, {
+ gasPrices: newBlock.transactions.map((tx) => {
+ return tx.gasPrice
+ }),
+ })
+ delete block.transactions
+
+ const state = this.store.getState()
+ state.recentBlocks.push(block)
+
+ while (state.recentBlocks.length > this.historyLength) {
+ state.recentBlocks.shift()
+ }
+
+ this.store.updateState(state)
+ }
+}
+
+module.exports = RecentBlocksController
diff --git a/app/scripts/controllers/transactions.js b/app/scripts/controllers/transactions.js
index ce709bd28..f95b5e39a 100644
--- a/app/scripts/controllers/transactions.js
+++ b/app/scripts/controllers/transactions.js
@@ -138,18 +138,20 @@ module.exports = class TransactionController extends EventEmitter {
async newUnapprovedTransaction (txParams) {
log.debug(`MetaMaskController newUnapprovedTransaction ${JSON.stringify(txParams)}`)
- const txMeta = await this.addUnapprovedTransaction(txParams)
- this.emit('newUnapprovedTx', txMeta)
+ const initialTxMeta = await this.addUnapprovedTransaction(txParams)
+ this.emit('newUnapprovedTx', initialTxMeta)
// listen for tx completion (success, fail)
return new Promise((resolve, reject) => {
- this.txStateManager.once(`${txMeta.id}:finished`, (completedTx) => {
- switch (completedTx.status) {
+ this.txStateManager.once(`${initialTxMeta.id}:finished`, (finishedTxMeta) => {
+ switch (finishedTxMeta.status) {
case 'submitted':
- return resolve(completedTx.hash)
+ return resolve(finishedTxMeta.hash)
case 'rejected':
return reject(new Error('MetaMask Tx Signature: User denied transaction signature.'))
+ case 'failed':
+ return reject(new Error(finishedTxMeta.err.message))
default:
- return reject(new Error(`MetaMask Tx Signature: Unknown problem: ${JSON.stringify(completedTx.txParams)}`))
+ return reject(new Error(`MetaMask Tx Signature: Unknown problem: ${JSON.stringify(finishedTxMeta.txParams)}`))
}
})
})
@@ -177,6 +179,7 @@ module.exports = class TransactionController extends EventEmitter {
const txParams = txMeta.txParams
// ensure value
txMeta.gasPriceSpecified = Boolean(txParams.gasPrice)
+ txMeta.nonceSpecified = Boolean(txParams.nonce)
const gasPrice = txParams.gasPrice || await this.query.gasPrice()
txParams.gasPrice = ethUtil.addHexPrefix(gasPrice.toString(16))
txParams.value = txParams.value || '0x0'
@@ -184,6 +187,13 @@ module.exports = class TransactionController extends EventEmitter {
return await this.txGasUtil.analyzeGasUsage(txMeta)
}
+ async retryTransaction (txId) {
+ this.txStateManager.setTxStatusUnapproved(txId)
+ const txMeta = this.txStateManager.getTx(txId)
+ txMeta.lastGasPrice = txMeta.txParams.gasPrice
+ this.txStateManager.updateTx(txMeta, 'retryTransaction: manual retry')
+ }
+
async updateAndApproveTransaction (txMeta) {
this.txStateManager.updateTx(txMeta, 'confTx: user approved transaction')
await this.approveTransaction(txMeta.id)
@@ -200,7 +210,12 @@ module.exports = class TransactionController extends EventEmitter {
// wait for a nonce
nonceLock = await this.nonceTracker.getNonceLock(fromAddress)
// add nonce to txParams
- txMeta.txParams.nonce = ethUtil.addHexPrefix(nonceLock.nextNonce.toString(16))
+ const nonce = txMeta.nonceSpecified ? txMeta.txParams.nonce : nonceLock.nextNonce
+ if (nonce > nonceLock.nextNonce) {
+ const message = `Specified nonce may not be larger than account's next valid nonce.`
+ throw new Error(message)
+ }
+ txMeta.txParams.nonce = ethUtil.addHexPrefix(nonce.toString(16))
// add nonce debugging information to txMeta
txMeta.nonceDetails = nonceLock.nonceDetails
this.txStateManager.updateTx(txMeta, 'transactions#approveTransaction')
diff --git a/app/scripts/lib/account-tracker.js b/app/scripts/lib/account-tracker.js
index ce6642150..8c3dd8c71 100644
--- a/app/scripts/lib/account-tracker.js
+++ b/app/scripts/lib/account-tracker.js
@@ -117,8 +117,6 @@ class AccountTracker extends EventEmitter {
const query = this._query
async.parallel({
balance: query.getBalance.bind(query, address),
- nonce: query.getTransactionCount.bind(query, address),
- code: query.getCode.bind(query, address),
}, cb)
}
diff --git a/app/scripts/lib/tx-gas-utils.js b/app/scripts/lib/tx-gas-utils.js
index 7e72ea71d..56bee19f7 100644
--- a/app/scripts/lib/tx-gas-utils.js
+++ b/app/scripts/lib/tx-gas-utils.js
@@ -22,7 +22,11 @@ module.exports = class txProvideUtil {
try {
estimatedGasHex = await this.estimateTxGas(txMeta, block.gasLimit)
} catch (err) {
- if (err.message.includes('Transaction execution error.')) {
+ 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
}
diff --git a/app/scripts/lib/tx-state-manager.js b/app/scripts/lib/tx-state-manager.js
index 0fd6bed4b..a8ef39891 100644
--- a/app/scripts/lib/tx-state-manager.js
+++ b/app/scripts/lib/tx-state-manager.js
@@ -187,6 +187,10 @@ module.exports = class TransactionStateManger extends EventEmitter {
this._setTxStatus(txId, 'rejected')
}
+ // should update the status of the tx to 'unapproved'.
+ setTxStatusUnapproved (txId) {
+ this._setTxStatus(txId, 'unapproved')
+ }
// should update the status of the tx to 'approved'.
setTxStatusApproved (txId) {
this._setTxStatus(txId, 'approved')
@@ -236,7 +240,7 @@ module.exports = class TransactionStateManger extends EventEmitter {
txMeta.status = status
this.emit(`${txMeta.id}:${status}`, txId)
this.emit(`tx:status-update`, txId, status)
- if (status === 'submitted' || status === 'rejected') {
+ if (['submitted', 'rejected', 'failed'].includes(status)) {
this.emit(`${txMeta.id}:finished`, txMeta)
}
this.updateTx(txMeta, `txStateManager: setting status to ${status}`)
diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js
index 018eb2c76..54e73d180 100644
--- a/app/scripts/metamask-controller.js
+++ b/app/scripts/metamask-controller.js
@@ -23,6 +23,7 @@ const ShapeShiftController = require('./controllers/shapeshift')
const AddressBookController = require('./controllers/address-book')
const InfuraController = require('./controllers/infura')
const BlacklistController = require('./controllers/blacklist')
+const RecentBlocksController = require('./controllers/recent-blocks')
const MessageManager = require('./lib/message-manager')
const PersonalMessageManager = require('./lib/personal-message-manager')
const TypedMessageManager = require('./lib/typed-message-manager')
@@ -91,6 +92,10 @@ module.exports = class MetamaskController extends EventEmitter {
this.provider = this.initializeProvider()
this.blockTracker = this.provider._blockTracker
+ this.recentBlocksController = new RecentBlocksController({
+ blockTracker: this.blockTracker,
+ })
+
// eth data query tools
this.ethQuery = new EthQuery(this.provider)
// account tracker watches balances, nonces, and any code at their address.
@@ -196,25 +201,30 @@ module.exports = class MetamaskController extends EventEmitter {
this.blacklistController.store.subscribe((state) => {
this.store.updateState({ BlacklistController: state })
})
+ this.recentBlocksController.store.subscribe((state) => {
+ this.store.updateState({ RecentBlocks: state })
+ })
this.infuraController.store.subscribe((state) => {
this.store.updateState({ InfuraController: state })
})
// manual mem state subscriptions
- this.networkController.store.subscribe(this.sendUpdate.bind(this))
- this.accountTracker.store.subscribe(this.sendUpdate.bind(this))
- this.txController.memStore.subscribe(this.sendUpdate.bind(this))
- this.balancesController.store.subscribe(this.sendUpdate.bind(this))
- this.messageManager.memStore.subscribe(this.sendUpdate.bind(this))
- this.personalMessageManager.memStore.subscribe(this.sendUpdate.bind(this))
- this.typedMessageManager.memStore.subscribe(this.sendUpdate.bind(this))
- this.keyringController.memStore.subscribe(this.sendUpdate.bind(this))
- this.preferencesController.store.subscribe(this.sendUpdate.bind(this))
- this.addressBookController.store.subscribe(this.sendUpdate.bind(this))
- this.currencyController.store.subscribe(this.sendUpdate.bind(this))
- this.noticeController.memStore.subscribe(this.sendUpdate.bind(this))
- this.shapeshiftController.store.subscribe(this.sendUpdate.bind(this))
- this.infuraController.store.subscribe(this.sendUpdate.bind(this))
+ const sendUpdate = this.sendUpdate.bind(this)
+ this.networkController.store.subscribe(sendUpdate)
+ this.accountTracker.store.subscribe(sendUpdate)
+ this.txController.memStore.subscribe(sendUpdate)
+ this.balancesController.store.subscribe(sendUpdate)
+ this.messageManager.memStore.subscribe(sendUpdate)
+ this.personalMessageManager.memStore.subscribe(sendUpdate)
+ this.typedMessageManager.memStore.subscribe(sendUpdate)
+ this.keyringController.memStore.subscribe(sendUpdate)
+ this.preferencesController.store.subscribe(sendUpdate)
+ this.recentBlocksController.store.subscribe(sendUpdate)
+ this.addressBookController.store.subscribe(sendUpdate)
+ this.currencyController.store.subscribe(sendUpdate)
+ this.noticeController.memStore.subscribe(sendUpdate)
+ this.shapeshiftController.store.subscribe(sendUpdate)
+ this.infuraController.store.subscribe(sendUpdate)
}
//
@@ -298,6 +308,7 @@ module.exports = class MetamaskController extends EventEmitter {
this.currencyController.store.getState(),
this.noticeController.memStore.getState(),
this.infuraController.store.getState(),
+ this.recentBlocksController.store.getState(),
// config manager
this.configManager.getConfig(),
this.shapeshiftController.store.getState(),
@@ -366,6 +377,7 @@ module.exports = class MetamaskController extends EventEmitter {
// txController
cancelTransaction: nodeify(txController.cancelTransaction, txController),
updateAndApproveTransaction: nodeify(txController.updateAndApproveTransaction, txController),
+ retryTransaction: nodeify(this.retryTransaction, this),
// messageManager
signMessage: nodeify(this.signMessage, this),
@@ -576,6 +588,14 @@ module.exports = class MetamaskController extends EventEmitter {
//
// Identity Management
//
+ //
+
+ async retryTransaction (txId, cb) {
+ await this.txController.retryTransaction(txId)
+ const state = await this.getState()
+ return state
+ }
+
newUnsignedMessage (msgParams, cb) {
const msgId = this.messageManager.addUnapprovedMessage(msgParams)