diff --git a/CHANGELOG.md b/CHANGELOG.md index 0faf6fe85..069602915 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,15 @@ ## 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 ## 3.10.6 2017-9-27 diff --git a/app/manifest.json b/app/manifest.json index 639f3fb4b..0fc43c7d4 100644 --- a/app/manifest.json +++ b/app/manifest.json @@ -1,7 +1,7 @@ { "name": "MetaMask", "short_name": "Metamask", - "version": "3.10.6", + "version": "3.10.8", "manifest_version": 2, "author": "https://metamask.io", "description": "Ethereum Browser Extension", @@ -57,7 +57,8 @@ "permissions": [ "storage", "clipboardWrite", - "http://localhost:8545/" + "http://localhost:8545/", + "https://*.infura.io/" ], "web_accessible_resources": [ "scripts/inpage.js" diff --git a/app/scripts/config.js b/app/scripts/config.js index c5f260583..1d4ff7c0d 100644 --- a/app/scripts/config.js +++ b/app/scripts/config.js @@ -2,11 +2,13 @@ const MAINET_RPC_URL = 'https://mainnet.infura.io/metamask' const ROPSTEN_RPC_URL = 'https://ropsten.infura.io/metamask' const KOVAN_RPC_URL = 'https://kovan.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' module.exports = { network: { + localhost: LOCALHOST_RPC_URL, mainnet: MAINET_RPC_URL, ropsten: ROPSTEN_RPC_URL, kovan: KOVAN_RPC_URL, diff --git a/app/scripts/controllers/currency.js b/app/scripts/controllers/currency.js index 9e696ce55..25a7a942e 100644 --- a/app/scripts/controllers/currency.js +++ b/app/scripts/controllers/currency.js @@ -45,7 +45,7 @@ class CurrencyController { updateConversionRate () { 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((parsedResponse) => { this.setConversionRate(Number(parsedResponse.bid)) diff --git a/app/scripts/controllers/network.js b/app/scripts/controllers/network.js index 2a17cdae8..0f9db4d53 100644 --- a/app/scripts/controllers/network.js +++ b/app/scripts/controllers/network.js @@ -1,5 +1,6 @@ +const assert = require('assert') 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 ComposedStore = require('obs-store/lib/composed') const extend = require('xtend') @@ -9,6 +10,7 @@ const RPC_ADDRESS_LIST = require('../config.js').network const DEFAULT_RPC = RPC_ADDRESS_LIST['rinkeby'] module.exports = class NetworkController extends EventEmitter { + constructor (config) { super() config.provider.rpcTarget = this.getRpcAddressForType(config.provider.type, config.provider) @@ -18,13 +20,12 @@ module.exports = class NetworkController extends EventEmitter { this._proxy = createEventEmitterProxy() this.on('networkDidChange', this.lookupNetwork) - this.providerStore.subscribe((state) => this.switchNetwork({ rpcUrl: state.rpcTarget })) } - initializeProvider (opts, providerContructor = MetaMaskProvider) { - this._baseProviderParams = opts - const provider = providerContructor(opts) - this._setProvider(provider) + initializeProvider (_providerParams) { + this._baseProviderParams = _providerParams + const rpcUrl = this.getCurrentRpcAddress() + this._configureStandardProvider({ rpcUrl }) this._proxy.on('block', this._logBlock.bind(this)) this._proxy.on('error', this.verifyNetwork.bind(this)) this.ethQuery = new EthQuery(this._proxy) @@ -32,17 +33,8 @@ module.exports = class NetworkController extends EventEmitter { 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 () { - // Check network when restoring connectivity: + // Check network when restoring connectivity: if (this.isNetworkLoading()) this.lookupNetwork() } @@ -71,6 +63,7 @@ module.exports = class NetworkController extends EventEmitter { type: 'rpc', rpcTarget: rpcUrl, }) + this._switchNetwork({ rpcUrl }) } getCurrentRpcAddress () { @@ -79,10 +72,14 @@ module.exports = class NetworkController extends EventEmitter { 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 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 () { @@ -94,6 +91,22 @@ module.exports = class NetworkController extends EventEmitter { 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) { // collect old block tracker events const oldProvider = this._provider diff --git a/app/scripts/controllers/transactions.js b/app/scripts/controllers/transactions.js index 4f5c94675..94e04c429 100644 --- a/app/scripts/controllers/transactions.js +++ b/app/scripts/controllers/transactions.js @@ -32,7 +32,6 @@ module.exports = class TransactionController extends EventEmitter { this.provider = opts.provider this.blockTracker = opts.blockTracker this.signEthTx = opts.signTransaction - this.accountTracker = opts.accountTracker this.memStore = new ObservableStore({}) this.query = new EthQuery(this.provider) @@ -61,33 +60,27 @@ module.exports = class TransactionController extends EventEmitter { provider: this.provider, nonceTracker: this.nonceTracker, 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), getPendingTransactions: this.txStateManager.getPendingTransactions.bind(this.txStateManager), }) 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:confirmed', this.txStateManager.setTxStatusConfirmed.bind(this.txStateManager)) this.pendingTxTracker.on('tx:retry', (txMeta) => { if (!('retryCount' in txMeta)) txMeta.retryCount = 0 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 is a little messy but until ethstore has been either // removed or redone this is to guard against the race condition - // where accountTracker hasent been populated by the results yet - this.blockTracker.once('latest', () => { - this.blockTracker.on('latest', this.pendingTxTracker.resubmitPendingTxs.bind(this.pendingTxTracker)) - }) + this.blockTracker.on('latest', this.pendingTxTracker.resubmitPendingTxs.bind(this.pendingTxTracker)) this.blockTracker.on('sync', this.pendingTxTracker.queryPendingTxs.bind(this.pendingTxTracker)) // memstore is computed from a few different stores this._updateMemstore() @@ -177,14 +170,14 @@ module.exports = class TransactionController extends EventEmitter { const txParams = txMeta.txParams // ensure value const gasPrice = txParams.gasPrice || await this.query.gasPrice() - txParams.value = txParams.value || '0x0' txParams.gasPrice = ethUtil.addHexPrefix(gasPrice.toString(16)) + txParams.value = txParams.value || '0x0' // set gasLimit return await this.txGasUtil.analyzeGasUsage(txMeta) } async updateAndApproveTransaction (txMeta) { - this.txStateManager.updateTx(txMeta) + this.txStateManager.updateTx(txMeta, 'confTx: user approved transaction') await this.approveTransaction(txMeta.id) } @@ -202,7 +195,7 @@ module.exports = class TransactionController extends EventEmitter { txMeta.txParams.nonce = ethUtil.addHexPrefix(nonceLock.nextNonce.toString(16)) // add nonce debugging information to txMeta txMeta.nonceDetails = nonceLock.nonceDetails - this.txStateManager.updateTx(txMeta) + this.txStateManager.updateTx(txMeta, 'transactions#approveTransaction') // sign transaction const rawTx = await this.signTransaction(txId) await this.publishTransaction(txId, rawTx) @@ -233,7 +226,7 @@ module.exports = class TransactionController extends EventEmitter { async publishTransaction (txId, rawTx) { const txMeta = this.txStateManager.getTx(txId) txMeta.rawTx = rawTx - this.txStateManager.updateTx(txMeta) + this.txStateManager.updateTx(txMeta, 'transactions#publishTransaction') const txHash = await this.query.sendRawTransaction(rawTx) this.setTxHash(txId, txHash) this.txStateManager.setTxStatusSubmitted(txId) @@ -248,7 +241,7 @@ module.exports = class TransactionController extends EventEmitter { // Add the tx hash to the persisted meta-tx object const txMeta = this.txStateManager.getTx(txId) txMeta.hash = txHash - this.txStateManager.updateTx(txMeta) + this.txStateManager.updateTx(txMeta, 'transactions#setTxHash') } // diff --git a/app/scripts/lib/pending-tx-tracker.js b/app/scripts/lib/pending-tx-tracker.js index b97cec9ce..6f1601586 100644 --- a/app/scripts/lib/pending-tx-tracker.js +++ b/app/scripts/lib/pending-tx-tracker.js @@ -1,6 +1,5 @@ const EventEmitter = require('events') const EthQuery = require('ethjs-query') -const sufficientBalance = require('./util').sufficientBalance /* Utility class for tracking the transactions as they @@ -12,7 +11,6 @@ const sufficientBalance = require('./util').sufficientBalance requires a: { provider: //, nonceTracker: //see nonce tracker, - getBalnce: //(address) a function for getting balances, getPendingTransactions: //() a function for getting an array of 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.nonceTracker = config.nonceTracker this.retryLimit = config.retryLimit || Infinity - this.getBalance = config.getBalance this.getPendingTransactions = config.getPendingTransactions this.publishTransaction = config.publishTransaction } @@ -89,33 +86,24 @@ module.exports = class PendingTransactionTracker extends EventEmitter { // other || errorMessage.includes('gateway timeout') || errorMessage.includes('nonce too low') - || txMeta.retryCount > 1 ) // ignore resubmit warnings, return early if (isKnownTx) return // 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) { - const address = txMeta.txParams.from - const balance = this.getBalance(address) - if (balance === undefined) return - if (txMeta.retryCount > this.retryLimit) { const err = new Error(`Gave up submitting after ${this.retryLimit} blocks un-mined.`) 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: if (!('rawTx' in txMeta)) return @@ -148,11 +136,10 @@ module.exports = class PendingTransactionTracker extends EventEmitter { } } catch (err) { txMeta.warning = { - error: err, + error: err.message, message: 'There was a problem loading this transaction.', } - this.emit('tx:warning', txMeta) - throw err + this.emit('tx:warning', txMeta, err) } } diff --git a/app/scripts/lib/tx-state-history-helper.js b/app/scripts/lib/tx-state-history-helper.js index 304069d57..db6e3bc9f 100644 --- a/app/scripts/lib/tx-state-history-helper.js +++ b/app/scripts/lib/tx-state-history-helper.js @@ -20,11 +20,15 @@ function migrateFromSnapshotsToDiffs(longHistory) { ) } -function generateHistoryEntry(previousState, newState) { - return jsonDiffer.compare(previousState, newState) +function generateHistoryEntry(previousState, newState, note) { + 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) } diff --git a/app/scripts/lib/tx-state-manager.js b/app/scripts/lib/tx-state-manager.js index abb9d7910..cf8117864 100644 --- a/app/scripts/lib/tx-state-manager.js +++ b/app/scripts/lib/tx-state-manager.js @@ -82,7 +82,7 @@ module.exports = class TransactionStateManger extends EventEmitter { return txMeta } - updateTx (txMeta) { + updateTx (txMeta, note) { if (txMeta.txParams) { Object.keys(txMeta.txParams).forEach((key) => { let value = txMeta.txParams[key] @@ -96,8 +96,8 @@ module.exports = class TransactionStateManger extends EventEmitter { // recover previous tx state obj const previousState = txStateHistoryHelper.replayHistory(txMeta.history) // generate history entry and add to history - const entry = txStateHistoryHelper.generateHistoryEntry(previousState, currentState) - txMeta.history.push(entry) + const entry = txStateHistoryHelper.generateHistoryEntry(previousState, currentState, note) + txMeta.history.push(entry) // commit txMeta to state const txId = txMeta.id @@ -113,7 +113,7 @@ module.exports = class TransactionStateManger extends EventEmitter { updateTxParams (txId, txParams) { const txMeta = this.getTx(txId) 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') { this.emit(`${txMeta.id}:finished`, txMeta) } - this.updateTx(txMeta) + this.updateTx(txMeta, `txStateManager: setting status to ${status}`) this.emit('update:badge') } diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index b5c81c348..ef37a852b 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -100,6 +100,14 @@ module.exports = class MetamaskController extends EventEmitter { 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.preferencesController.setSelectedAddress(address) this.accountTracker.addAccount(address) @@ -124,7 +132,6 @@ module.exports = class MetamaskController extends EventEmitter { provider: this.provider, blockTracker: this.blockTracker, ethQuery: this.ethQuery, - accountTracker: this.accountTracker, }) this.txController.on('newUnaprovedTx', opts.showUnapprovedTx.bind(opts)) @@ -209,19 +216,17 @@ module.exports = class MetamaskController extends EventEmitter { // initializeProvider () { - return this.networkController.initializeProvider({ + const providerOpts = { static: { eth_syncing: false, web3_clientVersion: `MetaMask/v${version}`, }, - // rpc data source - rpcUrl: this.networkController.getCurrentRpcAddress(), - originHttpHeaderKey: 'X-Metamask-Origin', // account mgmt getAccounts: (cb) => { const isUnlocked = this.keyringController.memStore.getState().isUnlocked const result = [] const selectedAddress = this.preferencesController.getSelectedAddress() + // only show address if account is unlocked if (isUnlocked && selectedAddress) { result.push(selectedAddress) @@ -234,7 +239,9 @@ module.exports = class MetamaskController extends EventEmitter { processMessage: this.newUnsignedMessage.bind(this), // personal_sign msg signing processPersonalMessage: this.newUnsignedPersonalMessage.bind(this), - }) + } + const providerProxy = this.networkController.initializeProvider(providerOpts) + return providerProxy } initPublicConfigStore () { @@ -303,13 +310,14 @@ module.exports = class MetamaskController extends EventEmitter { const txController = this.txController const noticeController = this.noticeController const addressBookController = this.addressBookController + const networkController = this.networkController return { // etc getState: (cb) => cb(null, this.getState()), - setProviderType: this.networkController.setProviderType.bind(this.networkController), setCurrentCurrency: this.setCurrentCurrency.bind(this), markAccountsFound: this.markAccountsFound.bind(this), + // coinbase buyEth: this.buyEth.bind(this), // shapeshift @@ -324,13 +332,15 @@ module.exports = class MetamaskController extends EventEmitter { // vault management submitPassword: this.submitPassword.bind(this), + // network management + setProviderType: nodeify(networkController.setProviderType, networkController), + setCustomRpc: nodeify(this.setCustomRpc, this), + // PreferencesController setSelectedAddress: nodeify(preferencesController.setSelectedAddress, preferencesController), addToken: nodeify(preferencesController.addToken, preferencesController), removeToken: nodeify(preferencesController.removeToken, preferencesController), setCurrentAccountTab: nodeify(preferencesController.setCurrentAccountTab, preferencesController), - setDefaultRpc: nodeify(this.setDefaultRpc, this), - setCustomRpc: nodeify(this.setCustomRpc, this), // AddressController setAddressBook: nodeify(addressBookController.setAddressBook, addressBookController), @@ -681,19 +691,13 @@ module.exports = class MetamaskController extends EventEmitter { createShapeShiftTx (depositAddress, depositType) { this.shapeshiftController.createShapeShiftTx(depositAddress, depositType) } -// network - setDefaultRpc () { - this.networkController.setRpcTarget('http://localhost:8545') - return Promise.resolve('http://localhost:8545') - } + // network - setCustomRpc (rpcTarget, rpcList) { + async setCustomRpc (rpcTarget, rpcList) { this.networkController.setRpcTarget(rpcTarget) - - return this.preferencesController.updateFrequentRpcList(rpcTarget) - .then(() => { - return Promise.resolve(rpcTarget) - }) + await this.preferencesController.updateFrequentRpcList(rpcTarget) + return rpcTarget } + } diff --git a/development/index.html b/development/index.html index a0814cb55..e5a027447 100644 --- a/development/index.html +++ b/development/index.html @@ -3,62 +3,58 @@