Merge branch 'master' into NewUI-flat

feature/default_network_editable
Chi Kei Chan 7 years ago committed by GitHub
commit bd99bc2e88
  1. 9
      CHANGELOG.md
  2. 5
      app/manifest.json
  3. 2
      app/scripts/config.js
  4. 2
      app/scripts/controllers/currency.js
  5. 49
      app/scripts/controllers/network.js
  6. 27
      app/scripts/controllers/transactions.js
  7. 27
      app/scripts/lib/pending-tx-tracker.js
  8. 10
      app/scripts/lib/tx-state-history-helper.js
  9. 10
      app/scripts/lib/tx-state-manager.js
  10. 44
      app/scripts/metamask-controller.js
  11. 84
      development/index.html
  12. 6
      package.json
  13. 44
      test/unit/pending-tx-test.js
  14. 25
      test/unit/tx-controller-test.js
  15. 23
      test/unit/tx-state-history-helper.js
  16. 51
      ui-dev.js
  17. 16
      ui/app/actions.js
  18. 1
      ui/app/app.js
  19. 28
      ui/app/components/transaction-list-item.js
  20. 7
      ui/app/info.js

@ -2,6 +2,15 @@
## Current Master ## Current Master
- Remove Slack link from info page, since it is a big phishing target.
## 3.10.8 2017-9-28
- Fixed usage of new currency fetching API.
## 3.10.7 2017-9-28
- Fixed bug where sometimes the current account was not correctly set and exposed to web apps.
- Added AUD, HKD, SGD, IDR, PHP to currency conversion list - Added AUD, HKD, SGD, IDR, PHP to currency conversion list
## 3.10.6 2017-9-27 ## 3.10.6 2017-9-27

@ -1,7 +1,7 @@
{ {
"name": "MetaMask", "name": "MetaMask",
"short_name": "Metamask", "short_name": "Metamask",
"version": "3.10.6", "version": "3.10.8",
"manifest_version": 2, "manifest_version": 2,
"author": "https://metamask.io", "author": "https://metamask.io",
"description": "Ethereum Browser Extension", "description": "Ethereum Browser Extension",
@ -57,7 +57,8 @@
"permissions": [ "permissions": [
"storage", "storage",
"clipboardWrite", "clipboardWrite",
"http://localhost:8545/" "http://localhost:8545/",
"https://*.infura.io/"
], ],
"web_accessible_resources": [ "web_accessible_resources": [
"scripts/inpage.js" "scripts/inpage.js"

@ -2,11 +2,13 @@ const MAINET_RPC_URL = 'https://mainnet.infura.io/metamask'
const ROPSTEN_RPC_URL = 'https://ropsten.infura.io/metamask' const ROPSTEN_RPC_URL = 'https://ropsten.infura.io/metamask'
const KOVAN_RPC_URL = 'https://kovan.infura.io/metamask' const KOVAN_RPC_URL = 'https://kovan.infura.io/metamask'
const RINKEBY_RPC_URL = 'https://rinkeby.infura.io/metamask' const RINKEBY_RPC_URL = 'https://rinkeby.infura.io/metamask'
const LOCALHOST_RPC_URL = 'http://localhost:8545'
global.METAMASK_DEBUG = 'GULP_METAMASK_DEBUG' global.METAMASK_DEBUG = 'GULP_METAMASK_DEBUG'
module.exports = { module.exports = {
network: { network: {
localhost: LOCALHOST_RPC_URL,
mainnet: MAINET_RPC_URL, mainnet: MAINET_RPC_URL,
ropsten: ROPSTEN_RPC_URL, ropsten: ROPSTEN_RPC_URL,
kovan: KOVAN_RPC_URL, kovan: KOVAN_RPC_URL,

@ -45,7 +45,7 @@ class CurrencyController {
updateConversionRate () { updateConversionRate () {
const currentCurrency = this.getCurrentCurrency() const currentCurrency = this.getCurrentCurrency()
return fetch(`https://api.infura.io/v1/ticker/eth${currentCurrency}`) return fetch(`https://api.infura.io/v1/ticker/eth${currentCurrency.toLowerCase()}`)
.then(response => response.json()) .then(response => response.json())
.then((parsedResponse) => { .then((parsedResponse) => {
this.setConversionRate(Number(parsedResponse.bid)) this.setConversionRate(Number(parsedResponse.bid))

@ -1,5 +1,6 @@
const assert = require('assert')
const EventEmitter = require('events') const EventEmitter = require('events')
const MetaMaskProvider = require('web3-provider-engine/zero.js') const createMetamaskProvider = require('web3-provider-engine/zero.js')
const ObservableStore = require('obs-store') const ObservableStore = require('obs-store')
const ComposedStore = require('obs-store/lib/composed') const ComposedStore = require('obs-store/lib/composed')
const extend = require('xtend') const extend = require('xtend')
@ -9,6 +10,7 @@ const RPC_ADDRESS_LIST = require('../config.js').network
const DEFAULT_RPC = RPC_ADDRESS_LIST['rinkeby'] const DEFAULT_RPC = RPC_ADDRESS_LIST['rinkeby']
module.exports = class NetworkController extends EventEmitter { module.exports = class NetworkController extends EventEmitter {
constructor (config) { constructor (config) {
super() super()
config.provider.rpcTarget = this.getRpcAddressForType(config.provider.type, config.provider) config.provider.rpcTarget = this.getRpcAddressForType(config.provider.type, config.provider)
@ -18,13 +20,12 @@ module.exports = class NetworkController extends EventEmitter {
this._proxy = createEventEmitterProxy() this._proxy = createEventEmitterProxy()
this.on('networkDidChange', this.lookupNetwork) this.on('networkDidChange', this.lookupNetwork)
this.providerStore.subscribe((state) => this.switchNetwork({ rpcUrl: state.rpcTarget }))
} }
initializeProvider (opts, providerContructor = MetaMaskProvider) { initializeProvider (_providerParams) {
this._baseProviderParams = opts this._baseProviderParams = _providerParams
const provider = providerContructor(opts) const rpcUrl = this.getCurrentRpcAddress()
this._setProvider(provider) this._configureStandardProvider({ rpcUrl })
this._proxy.on('block', this._logBlock.bind(this)) this._proxy.on('block', this._logBlock.bind(this))
this._proxy.on('error', this.verifyNetwork.bind(this)) this._proxy.on('error', this.verifyNetwork.bind(this))
this.ethQuery = new EthQuery(this._proxy) this.ethQuery = new EthQuery(this._proxy)
@ -32,17 +33,8 @@ module.exports = class NetworkController extends EventEmitter {
return this._proxy return this._proxy
} }
switchNetwork (opts) {
this.setNetworkState('loading')
const providerParams = extend(this._baseProviderParams, opts)
this._baseProviderParams = providerParams
const provider = MetaMaskProvider(providerParams)
this._setProvider(provider)
this.emit('networkDidChange')
}
verifyNetwork () { verifyNetwork () {
// Check network when restoring connectivity: // Check network when restoring connectivity:
if (this.isNetworkLoading()) this.lookupNetwork() if (this.isNetworkLoading()) this.lookupNetwork()
} }
@ -71,6 +63,7 @@ module.exports = class NetworkController extends EventEmitter {
type: 'rpc', type: 'rpc',
rpcTarget: rpcUrl, rpcTarget: rpcUrl,
}) })
this._switchNetwork({ rpcUrl })
} }
getCurrentRpcAddress () { getCurrentRpcAddress () {
@ -79,10 +72,14 @@ module.exports = class NetworkController extends EventEmitter {
return this.getRpcAddressForType(provider.type) return this.getRpcAddressForType(provider.type)
} }
setProviderType (type) { async setProviderType (type) {
assert(type !== 'rpc', `NetworkController.setProviderType - cannot connect by type "rpc"`)
// skip if type already matches
if (type === this.getProviderConfig().type) return if (type === this.getProviderConfig().type) return
const rpcTarget = this.getRpcAddressForType(type) const rpcTarget = this.getRpcAddressForType(type)
this.providerStore.updateState({type, rpcTarget}) assert(rpcTarget, `NetworkController - unknown rpc address for type "${type}"`)
this.providerStore.updateState({ type, rpcTarget })
this._switchNetwork({ rpcUrl: rpcTarget })
} }
getProviderConfig () { getProviderConfig () {
@ -94,6 +91,22 @@ module.exports = class NetworkController extends EventEmitter {
return provider && provider.rpcTarget ? provider.rpcTarget : DEFAULT_RPC return provider && provider.rpcTarget ? provider.rpcTarget : DEFAULT_RPC
} }
//
// Private
//
_switchNetwork (providerParams) {
this.setNetworkState('loading')
this._configureStandardProvider(providerParams)
this.emit('networkDidChange')
}
_configureStandardProvider(_providerParams) {
const providerParams = extend(this._baseProviderParams, _providerParams)
const provider = createMetamaskProvider(providerParams)
this._setProvider(provider)
}
_setProvider (provider) { _setProvider (provider) {
// collect old block tracker events // collect old block tracker events
const oldProvider = this._provider const oldProvider = this._provider

@ -32,7 +32,6 @@ module.exports = class TransactionController extends EventEmitter {
this.provider = opts.provider this.provider = opts.provider
this.blockTracker = opts.blockTracker this.blockTracker = opts.blockTracker
this.signEthTx = opts.signTransaction this.signEthTx = opts.signTransaction
this.accountTracker = opts.accountTracker
this.memStore = new ObservableStore({}) this.memStore = new ObservableStore({})
this.query = new EthQuery(this.provider) this.query = new EthQuery(this.provider)
@ -61,33 +60,27 @@ module.exports = class TransactionController extends EventEmitter {
provider: this.provider, provider: this.provider,
nonceTracker: this.nonceTracker, nonceTracker: this.nonceTracker,
retryLimit: 3500, // Retry 3500 blocks, or about 1 day. retryLimit: 3500, // Retry 3500 blocks, or about 1 day.
getBalance: (address) => {
const account = this.accountTracker.store.getState().accounts[address]
if (!account) return
return account.balance
},
publishTransaction: (rawTx) => this.query.sendRawTransaction(rawTx), publishTransaction: (rawTx) => this.query.sendRawTransaction(rawTx),
getPendingTransactions: this.txStateManager.getPendingTransactions.bind(this.txStateManager), getPendingTransactions: this.txStateManager.getPendingTransactions.bind(this.txStateManager),
}) })
this.txStateManager.store.subscribe(() => this.emit('update:badge')) this.txStateManager.store.subscribe(() => this.emit('update:badge'))
this.pendingTxTracker.on('tx:warning', this.txStateManager.updateTx.bind(this.txStateManager)) this.pendingTxTracker.on('tx:warning', (txMeta) => {
this.txStateManager.updateTx(txMeta, 'transactions/pending-tx-tracker#event: tx:warning')
})
this.pendingTxTracker.on('tx:failed', this.txStateManager.setTxStatusFailed.bind(this.txStateManager)) this.pendingTxTracker.on('tx:failed', this.txStateManager.setTxStatusFailed.bind(this.txStateManager))
this.pendingTxTracker.on('tx:confirmed', this.txStateManager.setTxStatusConfirmed.bind(this.txStateManager)) this.pendingTxTracker.on('tx:confirmed', this.txStateManager.setTxStatusConfirmed.bind(this.txStateManager))
this.pendingTxTracker.on('tx:retry', (txMeta) => { this.pendingTxTracker.on('tx:retry', (txMeta) => {
if (!('retryCount' in txMeta)) txMeta.retryCount = 0 if (!('retryCount' in txMeta)) txMeta.retryCount = 0
txMeta.retryCount++ txMeta.retryCount++
this.txStateManager.updateTx(txMeta) this.txStateManager.updateTx(txMeta, 'transactions/pending-tx-tracker#event: tx:retry')
}) })
this.blockTracker.on('block', this.pendingTxTracker.checkForTxInBlock.bind(this.pendingTxTracker)) this.blockTracker.on('block', this.pendingTxTracker.checkForTxInBlock.bind(this.pendingTxTracker))
// this is a little messy but until ethstore has been either // this is a little messy but until ethstore has been either
// removed or redone this is to guard against the race condition // removed or redone this is to guard against the race condition
// where accountTracker hasent been populated by the results yet this.blockTracker.on('latest', this.pendingTxTracker.resubmitPendingTxs.bind(this.pendingTxTracker))
this.blockTracker.once('latest', () => {
this.blockTracker.on('latest', this.pendingTxTracker.resubmitPendingTxs.bind(this.pendingTxTracker))
})
this.blockTracker.on('sync', this.pendingTxTracker.queryPendingTxs.bind(this.pendingTxTracker)) this.blockTracker.on('sync', this.pendingTxTracker.queryPendingTxs.bind(this.pendingTxTracker))
// memstore is computed from a few different stores // memstore is computed from a few different stores
this._updateMemstore() this._updateMemstore()
@ -177,14 +170,14 @@ module.exports = class TransactionController extends EventEmitter {
const txParams = txMeta.txParams const txParams = txMeta.txParams
// ensure value // ensure value
const gasPrice = txParams.gasPrice || await this.query.gasPrice() const gasPrice = txParams.gasPrice || await this.query.gasPrice()
txParams.value = txParams.value || '0x0'
txParams.gasPrice = ethUtil.addHexPrefix(gasPrice.toString(16)) txParams.gasPrice = ethUtil.addHexPrefix(gasPrice.toString(16))
txParams.value = txParams.value || '0x0'
// set gasLimit // set gasLimit
return await this.txGasUtil.analyzeGasUsage(txMeta) return await this.txGasUtil.analyzeGasUsage(txMeta)
} }
async updateAndApproveTransaction (txMeta) { async updateAndApproveTransaction (txMeta) {
this.txStateManager.updateTx(txMeta) this.txStateManager.updateTx(txMeta, 'confTx: user approved transaction')
await this.approveTransaction(txMeta.id) await this.approveTransaction(txMeta.id)
} }
@ -202,7 +195,7 @@ module.exports = class TransactionController extends EventEmitter {
txMeta.txParams.nonce = ethUtil.addHexPrefix(nonceLock.nextNonce.toString(16)) txMeta.txParams.nonce = ethUtil.addHexPrefix(nonceLock.nextNonce.toString(16))
// add nonce debugging information to txMeta // add nonce debugging information to txMeta
txMeta.nonceDetails = nonceLock.nonceDetails txMeta.nonceDetails = nonceLock.nonceDetails
this.txStateManager.updateTx(txMeta) this.txStateManager.updateTx(txMeta, 'transactions#approveTransaction')
// sign transaction // sign transaction
const rawTx = await this.signTransaction(txId) const rawTx = await this.signTransaction(txId)
await this.publishTransaction(txId, rawTx) await this.publishTransaction(txId, rawTx)
@ -233,7 +226,7 @@ module.exports = class TransactionController extends EventEmitter {
async publishTransaction (txId, rawTx) { async publishTransaction (txId, rawTx) {
const txMeta = this.txStateManager.getTx(txId) const txMeta = this.txStateManager.getTx(txId)
txMeta.rawTx = rawTx txMeta.rawTx = rawTx
this.txStateManager.updateTx(txMeta) this.txStateManager.updateTx(txMeta, 'transactions#publishTransaction')
const txHash = await this.query.sendRawTransaction(rawTx) const txHash = await this.query.sendRawTransaction(rawTx)
this.setTxHash(txId, txHash) this.setTxHash(txId, txHash)
this.txStateManager.setTxStatusSubmitted(txId) this.txStateManager.setTxStatusSubmitted(txId)
@ -248,7 +241,7 @@ module.exports = class TransactionController extends EventEmitter {
// Add the tx hash to the persisted meta-tx object // Add the tx hash to the persisted meta-tx object
const txMeta = this.txStateManager.getTx(txId) const txMeta = this.txStateManager.getTx(txId)
txMeta.hash = txHash txMeta.hash = txHash
this.txStateManager.updateTx(txMeta) this.txStateManager.updateTx(txMeta, 'transactions#setTxHash')
} }
// //

@ -1,6 +1,5 @@
const EventEmitter = require('events') const EventEmitter = require('events')
const EthQuery = require('ethjs-query') const EthQuery = require('ethjs-query')
const sufficientBalance = require('./util').sufficientBalance
/* /*
Utility class for tracking the transactions as they Utility class for tracking the transactions as they
@ -12,7 +11,6 @@ const sufficientBalance = require('./util').sufficientBalance
requires a: { requires a: {
provider: //, provider: //,
nonceTracker: //see nonce tracker, nonceTracker: //see nonce tracker,
getBalnce: //(address) a function for getting balances,
getPendingTransactions: //() a function for getting an array of transactions, getPendingTransactions: //() a function for getting an array of transactions,
publishTransaction: //(rawTx) a async function for publishing raw transactions, publishTransaction: //(rawTx) a async function for publishing raw transactions,
} }
@ -25,7 +23,6 @@ module.exports = class PendingTransactionTracker extends EventEmitter {
this.query = new EthQuery(config.provider) this.query = new EthQuery(config.provider)
this.nonceTracker = config.nonceTracker this.nonceTracker = config.nonceTracker
this.retryLimit = config.retryLimit || Infinity this.retryLimit = config.retryLimit || Infinity
this.getBalance = config.getBalance
this.getPendingTransactions = config.getPendingTransactions this.getPendingTransactions = config.getPendingTransactions
this.publishTransaction = config.publishTransaction this.publishTransaction = config.publishTransaction
} }
@ -89,33 +86,24 @@ module.exports = class PendingTransactionTracker extends EventEmitter {
// other // other
|| errorMessage.includes('gateway timeout') || errorMessage.includes('gateway timeout')
|| errorMessage.includes('nonce too low') || errorMessage.includes('nonce too low')
|| txMeta.retryCount > 1
) )
// ignore resubmit warnings, return early // ignore resubmit warnings, return early
if (isKnownTx) return if (isKnownTx) return
// encountered real error - transition to error state // encountered real error - transition to error state
this.emit('tx:failed', txMeta.id, err) txMeta.warning = {
error: errorMessage,
message: 'There was an error when resubmitting this transaction.',
}
this.emit('tx:warning', txMeta, err)
})) }))
} }
async _resubmitTx (txMeta) { async _resubmitTx (txMeta) {
const address = txMeta.txParams.from
const balance = this.getBalance(address)
if (balance === undefined) return
if (txMeta.retryCount > this.retryLimit) { if (txMeta.retryCount > this.retryLimit) {
const err = new Error(`Gave up submitting after ${this.retryLimit} blocks un-mined.`) const err = new Error(`Gave up submitting after ${this.retryLimit} blocks un-mined.`)
return this.emit('tx:failed', txMeta.id, err) return this.emit('tx:failed', txMeta.id, err)
} }
// if the value of the transaction is greater then the balance, fail.
if (!sufficientBalance(txMeta.txParams, balance)) {
const insufficientFundsError = new Error('Insufficient balance during rebroadcast.')
this.emit('tx:failed', txMeta.id, insufficientFundsError)
log.error(insufficientFundsError)
return
}
// Only auto-submit already-signed txs: // Only auto-submit already-signed txs:
if (!('rawTx' in txMeta)) return if (!('rawTx' in txMeta)) return
@ -148,11 +136,10 @@ module.exports = class PendingTransactionTracker extends EventEmitter {
} }
} catch (err) { } catch (err) {
txMeta.warning = { txMeta.warning = {
error: err, error: err.message,
message: 'There was a problem loading this transaction.', message: 'There was a problem loading this transaction.',
} }
this.emit('tx:warning', txMeta) this.emit('tx:warning', txMeta, err)
throw err
} }
} }

@ -20,11 +20,15 @@ function migrateFromSnapshotsToDiffs(longHistory) {
) )
} }
function generateHistoryEntry(previousState, newState) { function generateHistoryEntry(previousState, newState, note) {
return jsonDiffer.compare(previousState, newState) const entry = jsonDiffer.compare(previousState, newState)
// Add a note to the first op, since it breaks if we append it to the entry
if (note && entry[0]) entry[0].note = note
return entry
} }
function replayHistory(shortHistory) { function replayHistory(_shortHistory) {
const shortHistory = clone(_shortHistory)
return shortHistory.reduce((val, entry) => jsonDiffer.applyPatch(val, entry).newDocument) return shortHistory.reduce((val, entry) => jsonDiffer.applyPatch(val, entry).newDocument)
} }

@ -82,7 +82,7 @@ module.exports = class TransactionStateManger extends EventEmitter {
return txMeta return txMeta
} }
updateTx (txMeta) { updateTx (txMeta, note) {
if (txMeta.txParams) { if (txMeta.txParams) {
Object.keys(txMeta.txParams).forEach((key) => { Object.keys(txMeta.txParams).forEach((key) => {
let value = txMeta.txParams[key] let value = txMeta.txParams[key]
@ -96,8 +96,8 @@ module.exports = class TransactionStateManger extends EventEmitter {
// recover previous tx state obj // recover previous tx state obj
const previousState = txStateHistoryHelper.replayHistory(txMeta.history) const previousState = txStateHistoryHelper.replayHistory(txMeta.history)
// generate history entry and add to history // generate history entry and add to history
const entry = txStateHistoryHelper.generateHistoryEntry(previousState, currentState) const entry = txStateHistoryHelper.generateHistoryEntry(previousState, currentState, note)
txMeta.history.push(entry) txMeta.history.push(entry)
// commit txMeta to state // commit txMeta to state
const txId = txMeta.id const txId = txMeta.id
@ -113,7 +113,7 @@ module.exports = class TransactionStateManger extends EventEmitter {
updateTxParams (txId, txParams) { updateTxParams (txId, txParams) {
const txMeta = this.getTx(txId) const txMeta = this.getTx(txId)
txMeta.txParams = extend(txMeta.txParams, txParams) txMeta.txParams = extend(txMeta.txParams, txParams)
this.updateTx(txMeta) this.updateTx(txMeta, `txStateManager#updateTxParams`)
} }
/* /*
@ -233,7 +233,7 @@ module.exports = class TransactionStateManger extends EventEmitter {
if (status === 'submitted' || status === 'rejected') { if (status === 'submitted' || status === 'rejected') {
this.emit(`${txMeta.id}:finished`, txMeta) this.emit(`${txMeta.id}:finished`, txMeta)
} }
this.updateTx(txMeta) this.updateTx(txMeta, `txStateManager: setting status to ${status}`)
this.emit('update:badge') this.emit('update:badge')
} }

@ -100,6 +100,14 @@ module.exports = class MetamaskController extends EventEmitter {
encryptor: opts.encryptor || undefined, encryptor: opts.encryptor || undefined,
}) })
// If only one account exists, make sure it is selected.
this.keyringController.store.subscribe((state) => {
const addresses = Object.keys(state.walletNicknames || {})
if (addresses.length === 1) {
const address = addresses[0]
this.preferencesController.setSelectedAddress(address)
}
})
this.keyringController.on('newAccount', (address) => { this.keyringController.on('newAccount', (address) => {
this.preferencesController.setSelectedAddress(address) this.preferencesController.setSelectedAddress(address)
this.accountTracker.addAccount(address) this.accountTracker.addAccount(address)
@ -124,7 +132,6 @@ module.exports = class MetamaskController extends EventEmitter {
provider: this.provider, provider: this.provider,
blockTracker: this.blockTracker, blockTracker: this.blockTracker,
ethQuery: this.ethQuery, ethQuery: this.ethQuery,
accountTracker: this.accountTracker,
}) })
this.txController.on('newUnaprovedTx', opts.showUnapprovedTx.bind(opts)) this.txController.on('newUnaprovedTx', opts.showUnapprovedTx.bind(opts))
@ -209,19 +216,17 @@ module.exports = class MetamaskController extends EventEmitter {
// //
initializeProvider () { initializeProvider () {
return this.networkController.initializeProvider({ const providerOpts = {
static: { static: {
eth_syncing: false, eth_syncing: false,
web3_clientVersion: `MetaMask/v${version}`, web3_clientVersion: `MetaMask/v${version}`,
}, },
// rpc data source
rpcUrl: this.networkController.getCurrentRpcAddress(),
originHttpHeaderKey: 'X-Metamask-Origin',
// account mgmt // account mgmt
getAccounts: (cb) => { getAccounts: (cb) => {
const isUnlocked = this.keyringController.memStore.getState().isUnlocked const isUnlocked = this.keyringController.memStore.getState().isUnlocked
const result = [] const result = []
const selectedAddress = this.preferencesController.getSelectedAddress() const selectedAddress = this.preferencesController.getSelectedAddress()
// only show address if account is unlocked // only show address if account is unlocked
if (isUnlocked && selectedAddress) { if (isUnlocked && selectedAddress) {
result.push(selectedAddress) result.push(selectedAddress)
@ -234,7 +239,9 @@ module.exports = class MetamaskController extends EventEmitter {
processMessage: this.newUnsignedMessage.bind(this), processMessage: this.newUnsignedMessage.bind(this),
// personal_sign msg signing // personal_sign msg signing
processPersonalMessage: this.newUnsignedPersonalMessage.bind(this), processPersonalMessage: this.newUnsignedPersonalMessage.bind(this),
}) }
const providerProxy = this.networkController.initializeProvider(providerOpts)
return providerProxy
} }
initPublicConfigStore () { initPublicConfigStore () {
@ -303,13 +310,14 @@ module.exports = class MetamaskController extends EventEmitter {
const txController = this.txController const txController = this.txController
const noticeController = this.noticeController const noticeController = this.noticeController
const addressBookController = this.addressBookController const addressBookController = this.addressBookController
const networkController = this.networkController
return { return {
// etc // etc
getState: (cb) => cb(null, this.getState()), getState: (cb) => cb(null, this.getState()),
setProviderType: this.networkController.setProviderType.bind(this.networkController),
setCurrentCurrency: this.setCurrentCurrency.bind(this), setCurrentCurrency: this.setCurrentCurrency.bind(this),
markAccountsFound: this.markAccountsFound.bind(this), markAccountsFound: this.markAccountsFound.bind(this),
// coinbase // coinbase
buyEth: this.buyEth.bind(this), buyEth: this.buyEth.bind(this),
// shapeshift // shapeshift
@ -324,13 +332,15 @@ module.exports = class MetamaskController extends EventEmitter {
// vault management // vault management
submitPassword: this.submitPassword.bind(this), submitPassword: this.submitPassword.bind(this),
// network management
setProviderType: nodeify(networkController.setProviderType, networkController),
setCustomRpc: nodeify(this.setCustomRpc, this),
// PreferencesController // PreferencesController
setSelectedAddress: nodeify(preferencesController.setSelectedAddress, preferencesController), setSelectedAddress: nodeify(preferencesController.setSelectedAddress, preferencesController),
addToken: nodeify(preferencesController.addToken, preferencesController), addToken: nodeify(preferencesController.addToken, preferencesController),
removeToken: nodeify(preferencesController.removeToken, preferencesController), removeToken: nodeify(preferencesController.removeToken, preferencesController),
setCurrentAccountTab: nodeify(preferencesController.setCurrentAccountTab, preferencesController), setCurrentAccountTab: nodeify(preferencesController.setCurrentAccountTab, preferencesController),
setDefaultRpc: nodeify(this.setDefaultRpc, this),
setCustomRpc: nodeify(this.setCustomRpc, this),
// AddressController // AddressController
setAddressBook: nodeify(addressBookController.setAddressBook, addressBookController), setAddressBook: nodeify(addressBookController.setAddressBook, addressBookController),
@ -681,19 +691,13 @@ module.exports = class MetamaskController extends EventEmitter {
createShapeShiftTx (depositAddress, depositType) { createShapeShiftTx (depositAddress, depositType) {
this.shapeshiftController.createShapeShiftTx(depositAddress, depositType) this.shapeshiftController.createShapeShiftTx(depositAddress, depositType)
} }
// network
setDefaultRpc () { // network
this.networkController.setRpcTarget('http://localhost:8545')
return Promise.resolve('http://localhost:8545')
}
setCustomRpc (rpcTarget, rpcList) { async setCustomRpc (rpcTarget, rpcList) {
this.networkController.setRpcTarget(rpcTarget) this.networkController.setRpcTarget(rpcTarget)
await this.preferencesController.updateFrequentRpcList(rpcTarget)
return this.preferencesController.updateFrequentRpcList(rpcTarget) return rpcTarget
.then(() => {
return Promise.resolve(rpcTarget)
})
} }
} }

@ -3,62 +3,58 @@
<head> <head>
<meta charset="utf-8"> <meta charset="utf-8">
<title>MetaMask</title> <title>MetaMask</title>
</head> </head>
<body> <body>
<!-- app content -->
<div id="app-content" style="height: 100%"></div>
<script src="./bundle.js" type="text/javascript" charset="utf-8"></script> <script src="./bundle.js" type="text/javascript" charset="utf-8"></script>
<style>
html, body, #test-container, .super-dev-container {
height: 100%;
width: 100%;
position: relative;
background: white;
}
#app-content {
background: #F7F7F7;
}
</style>
</body> <script>
liveReloadCode(Date.now(), 300)
function liveReloadCode(lastUpdate, updateRate) {
setTimeout(iter, updateRate)
<style> function iter() {
html, body, #test-container, .super-dev-container { var xhr = new XMLHttpRequest()
height: 100%;
width: 100%;
position: relative;
background: white;
}
#app-content {
background: #F7F7F7;
}
</style>
<script> xhr.open('GET', '/-/live-reload')
liveReloadCode(Date.now(), 300) xhr.onreadystatechange = function() {
function liveReloadCode(lastUpdate, updateRate) { if(xhr.readyState !== 4) {
setTimeout(iter, updateRate) return
}
function iter() { try {
var xhr = new XMLHttpRequest() var change = JSON.parse(xhr.responseText).lastUpdate
xhr.open('GET', '/-/live-reload') if(lastUpdate < change) {
xhr.onreadystatechange = function() { return reload()
if(xhr.readyState !== 4) { }
return } catch(err) {
} }
try { xhr =
var change = JSON.parse(xhr.responseText).lastUpdate xhr.onreadystatechange = null
setTimeout(iter, updateRate)
}
if(lastUpdate < change) { xhr.send(null)
return reload()
} }
} catch(err) {
} }
xhr = function reload() {
xhr.onreadystatechange = null window.location.reload()
setTimeout(iter, updateRate) }
} </script>
xhr.send(null)
}
}
function reload() { </body>
window.location.reload()
}
</script>
</html> </html>

@ -157,7 +157,7 @@
"valid-url": "^1.0.9", "valid-url": "^1.0.9",
"vreme": "^3.0.2", "vreme": "^3.0.2",
"web3": "^0.20.1", "web3": "^0.20.1",
"web3-provider-engine": "^13.2.12", "web3-provider-engine": "^13.3.1",
"web3-stream-provider": "^3.0.1", "web3-stream-provider": "^3.0.1",
"xtend": "^4.0.1" "xtend": "^4.0.1"
}, },
@ -174,7 +174,7 @@
"brfs": "^1.4.3", "brfs": "^1.4.3",
"browserify": "^14.4.0", "browserify": "^14.4.0",
"chai": "^4.1.0", "chai": "^4.1.0",
"coveralls": "^2.13.1", "coveralls": "^3.0.0",
"deep-freeze-strict": "^1.1.1", "deep-freeze-strict": "^1.1.1",
"del": "^3.0.0", "del": "^3.0.0",
"envify": "^4.0.0", "envify": "^4.0.0",
@ -204,7 +204,7 @@
"karma-firefox-launcher": "^1.0.1", "karma-firefox-launcher": "^1.0.1",
"karma-qunit": "^1.2.1", "karma-qunit": "^1.2.1",
"lodash.assign": "^4.0.6", "lodash.assign": "^4.0.6",
"mocha": "^3.4.2", "mocha": "^4.0.0",
"mocha-eslint": "^4.0.0", "mocha-eslint": "^4.0.0",
"mocha-jsdom": "^1.1.0", "mocha-jsdom": "^1.1.0",
"mocha-sinon": "^2.0.0", "mocha-sinon": "^2.0.0",

@ -40,14 +40,12 @@ describe('PendingTransactionTracker', function () {
pendingTxTracker = new PendingTransactionTracker({ pendingTxTracker = new PendingTransactionTracker({
provider, provider,
getBalance: () => {},
nonceTracker: { nonceTracker: {
getGlobalLock: async () => { getGlobalLock: async () => {
return { releaseLock: () => {} } return { releaseLock: () => {} }
} }
}, },
getPendingTransactions: () => {return []}, getPendingTransactions: () => {return []},
sufficientBalance: () => {},
publishTransaction: () => {}, publishTransaction: () => {},
}) })
}) })
@ -59,7 +57,7 @@ describe('PendingTransactionTracker', function () {
const block = Proxy.revocable({}, {}).revoke() const block = Proxy.revocable({}, {}).revoke()
pendingTxTracker.checkForTxInBlock(block) pendingTxTracker.checkForTxInBlock(block)
}) })
it('should emit \'txFailed\' if the txMeta does not have a hash', function (done) { it('should emit \'tx:failed\' if the txMeta does not have a hash', function (done) {
const block = Proxy.revocable({}, {}).revoke() const block = Proxy.revocable({}, {}).revoke()
pendingTxTracker.getPendingTransactions = () => [txMetaNoHash] pendingTxTracker.getPendingTransactions = () => [txMetaNoHash]
pendingTxTracker.once('tx:failed', (txId, err) => { pendingTxTracker.once('tx:failed', (txId, err) => {
@ -107,7 +105,7 @@ describe('PendingTransactionTracker', function () {
}) })
describe('#_checkPendingTx', function () { describe('#_checkPendingTx', function () {
it('should emit \'txFailed\' if the txMeta does not have a hash', function (done) { it('should emit \'tx:failed\' if the txMeta does not have a hash', function (done) {
pendingTxTracker.once('tx:failed', (txId, err) => { pendingTxTracker.once('tx:failed', (txId, err) => {
assert(txId, txMetaNoHash.id, 'should pass txId') assert(txId, txMetaNoHash.id, 'should pass txId')
done() done()
@ -174,7 +172,7 @@ describe('PendingTransactionTracker', function () {
.catch(done) .catch(done)
pendingTxTracker.resubmitPendingTxs() pendingTxTracker.resubmitPendingTxs()
}) })
it('should not emit \'txFailed\' if the txMeta throws a known txError', function (done) { it('should not emit \'tx:failed\' if the txMeta throws a known txError', function (done) {
knownErrors =[ knownErrors =[
// geth // geth
' Replacement transaction Underpriced ', ' Replacement transaction Underpriced ',
@ -201,8 +199,15 @@ describe('PendingTransactionTracker', function () {
pendingTxTracker.resubmitPendingTxs() pendingTxTracker.resubmitPendingTxs()
}) })
it('should emit \'txFailed\' if it encountered a real error', function (done) { it('should emit \'tx:warning\' if it encountered a real error', function (done) {
pendingTxTracker.once('tx:failed', (id, err) => err.message === 'im some real error' ? txList[id - 1].resolve() : done(err)) pendingTxTracker.once('tx:warning', (txMeta, err) => {
if (err.message === 'im some real error') {
const matchingTx = txList.find(tx => tx.id === txMeta.id)
matchingTx.resolve()
} else {
done(err)
}
})
pendingTxTracker.getPendingTransactions = () => txList pendingTxTracker.getPendingTransactions = () => txList
pendingTxTracker._resubmitTx = async (tx) => { throw new TypeError('im some real error') } pendingTxTracker._resubmitTx = async (tx) => { throw new TypeError('im some real error') }
@ -213,30 +218,7 @@ describe('PendingTransactionTracker', function () {
pendingTxTracker.resubmitPendingTxs() pendingTxTracker.resubmitPendingTxs()
}) })
}) })
describe('#_resubmitTx with a too-low balance', function () { describe('#_resubmitTx', function () {
it('should return before publishing the transaction because to low of balance', function (done) {
const lowBalance = '0x0'
pendingTxTracker.getBalance = (address) => {
assert.equal(address, txMeta.txParams.from, 'Should pass the address')
return lowBalance
}
pendingTxTracker.publishTransaction = async (rawTx) => {
done(new Error('tried to publish transaction'))
}
// Stubbing out current account state:
// Adding the fake tx:
pendingTxTracker.once('tx:failed', (txId, err) => {
assert(err, 'Should have a error')
done()
})
pendingTxTracker._resubmitTx(txMeta)
.catch((err) => {
assert.ifError(err, 'should not throw an error')
done(err)
})
})
it('should publishing the transaction', function (done) { it('should publishing the transaction', function (done) {
const enoughBalance = '0x100000' const enoughBalance = '0x100000'
pendingTxTracker.getBalance = (address) => { pendingTxTracker.getBalance = (address) => {

@ -25,7 +25,6 @@ describe('Transaction Controller', function () {
networkStore: new ObservableStore(currentNetworkId), networkStore: new ObservableStore(currentNetworkId),
txHistoryLimit: 10, txHistoryLimit: 10,
blockTracker: { getCurrentBlock: noop, on: noop, once: noop }, blockTracker: { getCurrentBlock: noop, on: noop, once: noop },
accountTracker: { store: { getState: noop } },
signTransaction: (ethTx) => new Promise((resolve) => { signTransaction: (ethTx) => new Promise((resolve) => {
ethTx.sign(privKey) ethTx.sign(privKey)
resolve() resolve()
@ -383,30 +382,6 @@ describe('Transaction Controller', function () {
}) })
}) })
describe('#getBalance', function () {
it('gets balance', function () {
sinon.stub(txController.accountTracker.store, 'getState').callsFake(() => {
return {
accounts: {
'0x1678a085c290ebd122dc42cba69373b5953b831d': {
address: '0x1678a085c290ebd122dc42cba69373b5953b831d',
balance: '0x00000000000000056bc75e2d63100000',
code: '0x',
nonce: '0x0',
},
'0xc684832530fcbddae4b4230a47e991ddcec2831d': {
address: '0xc684832530fcbddae4b4230a47e991ddcec2831d',
balance: '0x0',
code: '0x',
nonce: '0x0',
},
},
}
})
assert.equal(txController.pendingTxTracker.getBalance('0x1678a085c290ebd122dc42cba69373b5953b831d'), '0x00000000000000056bc75e2d63100000')
assert.equal(txController.pendingTxTracker.getBalance('0xc684832530fcbddae4b4230a47e991ddcec2831d'), '0x0')
})
})
describe('#getPendingTransactions', function () { describe('#getPendingTransactions', function () {
beforeEach(function () { beforeEach(function () {

@ -20,4 +20,27 @@ describe('tx-state-history-helper', function () {
}) })
}) })
}) })
it('replaying history does not mutate the original obj', function () {
const initialState = { test: true, message: 'hello', value: 1 }
const diff1 = [{
"op": "replace",
"path": "/message",
"value": "haay",
}]
const diff2 = [{
"op": "replace",
"path": "/value",
"value": 2,
}]
const history = [initialState, diff1, diff2]
const beforeStateSnapshot = JSON.stringify(initialState)
const latestState = txStateHistoryHelper.replayHistory(history)
const afterStateSnapshot = JSON.stringify(initialState)
assert.notEqual(initialState, latestState, 'initial state is not the same obj as the latest state')
assert.equal(beforeStateSnapshot, afterStateSnapshot, 'initial state is not modified during run')
})
}) })

@ -61,30 +61,37 @@ const actions = {
var css = MetaMaskUiCss() var css = MetaMaskUiCss()
injectCss(css) injectCss(css)
const container = document.querySelector('#test-container')
// parse opts // parse opts
var store = configureStore(states[selectedView]) var store = configureStore(states[selectedView])
// start app // start app
render( startApp()
h('.super-dev-container', [
function startApp(){
h(Selector, { actions, selectedKey: selectedView, states, store }), const body = document.body
const container = document.createElement('div')
h('#app-content', { container.id = 'test-container'
style: { body.appendChild(container)
height: '500px',
width: '360px', render(
boxShadow: 'grey 0px 2px 9px', h('.super-dev-container', [
margin: '20px',
}, h(Selector, { actions, selectedKey: selectedView, states, store }),
}, [
h(Root, { h('#app-content', {
store: store, style: {
}), height: '500px',
]), width: '360px',
boxShadow: 'grey 0px 2px 9px',
] margin: '20px',
), container) },
}, [
h(Root, {
store: store,
}),
]),
]
), container)
}

@ -145,8 +145,6 @@ var actions = {
SET_RPC_TARGET: 'SET_RPC_TARGET', SET_RPC_TARGET: 'SET_RPC_TARGET',
SET_DEFAULT_RPC_TARGET: 'SET_DEFAULT_RPC_TARGET', SET_DEFAULT_RPC_TARGET: 'SET_DEFAULT_RPC_TARGET',
SET_PROVIDER_TYPE: 'SET_PROVIDER_TYPE', SET_PROVIDER_TYPE: 'SET_PROVIDER_TYPE',
USE_ETHERSCAN_PROVIDER: 'USE_ETHERSCAN_PROVIDER',
useEtherscanProvider: useEtherscanProvider,
showConfigPage, showConfigPage,
SHOW_ADD_TOKEN_PAGE: 'SHOW_ADD_TOKEN_PAGE', SHOW_ADD_TOKEN_PAGE: 'SHOW_ADD_TOKEN_PAGE',
showAddTokenPage, showAddTokenPage,
@ -156,7 +154,6 @@ var actions = {
updateTokens, updateTokens,
UPDATE_TOKENS: 'UPDATE_TOKENS', UPDATE_TOKENS: 'UPDATE_TOKENS',
setRpcTarget: setRpcTarget, setRpcTarget: setRpcTarget,
setDefaultRpcTarget: setDefaultRpcTarget,
setProviderType: setProviderType, setProviderType: setProviderType,
// loading overlay // loading overlay
SHOW_LOADING: 'SHOW_LOADING_INDICATION', SHOW_LOADING: 'SHOW_LOADING_INDICATION',
@ -864,16 +861,19 @@ function markAccountsFound () {
// config // config
// //
// default rpc target refers to localhost:8545 in this instance. function setProviderType (type) {
function setDefaultRpcTarget () {
log.debug(`background.setDefaultRpcTarget`)
return (dispatch) => { return (dispatch) => {
background.setDefaultRpc((err, result) => { log.debug(`background.setProviderType`)
background.setProviderType(type, (err, result) => {
if (err) { if (err) {
log.error(err) log.error(err)
return dispatch(self.displayWarning('Had a problem changing networks.')) return dispatch(self.displayWarning('Had a problem changing networks!'))
} }
}) })
return {
type: actions.SET_PROVIDER_TYPE,
value: type,
}
} }
} }

@ -254,6 +254,7 @@ App.prototype.renderAppBar = function () {
) )
} }
App.prototype.renderBackButton = function (style, justArrow = false) { App.prototype.renderBackButton = function (style, justArrow = false) {
var props = this.props var props = this.props
return ( return (

@ -133,7 +133,7 @@ function recipientField (txParams, transaction, isTx, isMsg) {
}, },
}, [ }, [
message, message,
failIfFailed(transaction), renderErrorOrWarning(transaction),
]) ])
} }
@ -141,8 +141,11 @@ function formatDate (date) {
return vreme.format(new Date(date), 'March 16 2014 14:30') return vreme.format(new Date(date), 'March 16 2014 14:30')
} }
function failIfFailed (transaction) { function renderErrorOrWarning (transaction) {
if (transaction.status === 'rejected') { const { status, err, warning } = transaction
// show rejected
if (status === 'rejected') {
return h('span.error', ' (Rejected)') return h('span.error', ' (Rejected)')
} }
if (transaction.err || transaction.warning) { if (transaction.err || transaction.warning) {
@ -152,14 +155,27 @@ function failIfFailed (transaction) {
errFirst ? err.message : warning.message errFirst ? err.message : warning.message
// show error
if (err) {
const message = err.message || ''
return (
h(Tooltip, {
title: message,
position: 'bottom',
}, [
h(`span.error`, ` (Failed)`),
])
)
}
// show warning
if (warning) {
const message = warning.message
return h(Tooltip, { return h(Tooltip, {
title: message, title: message,
position: 'bottom', position: 'bottom',
}, [ }, [
h(`span.${errFirst ? 'error' : 'warning'}`, h(`span.warning`, ` (Warning)`),
` (${errFirst ? 'Failed' : 'Warning'})`
),
]) ])
} }
} }

@ -126,13 +126,6 @@ InfoScreen.prototype.render = function () {
]), ]),
]), ]),
h('div.fa.fa-slack', [
h('a.info', {
href: 'http://slack.metamask.io',
target: '_blank',
}, 'Join the conversation on Slack'),
]),
h('div', [ h('div', [
h('.fa.fa-twitter', [ h('.fa.fa-twitter', [
h('a.info', { h('a.info', {

Loading…
Cancel
Save