diff options
44 files changed, 1322 insertions, 907 deletions
@@ -1,4 +1,4 @@ { - "presets": [["env", { "debug": true }], "react", "stage-0"], + "presets": [["env"], "react", "stage-0"], "plugins": ["transform-runtime", "transform-async-to-generator", "transform-class-properties"] } diff --git a/.gitignore b/.gitignore index e53133361..af86113fb 100644 --- a/.gitignore +++ b/.gitignore @@ -9,6 +9,7 @@ package # IDEs .idea .vscode +.sublime-project # VIM *.swp @@ -34,6 +35,7 @@ test/bundle.js test/test-bundle.js test-artifacts +test-builds #ignore css output and sourcemaps ui/app/css/output/ diff --git a/app/scripts/background.js b/app/scripts/background.js index 7eb7b1255..4cd6dac4c 100644 --- a/app/scripts/background.js +++ b/app/scripts/background.js @@ -19,7 +19,7 @@ const PortStream = require('./lib/port-stream.js') const createStreamSink = require('./lib/createStreamSink') const NotificationManager = require('./lib/notification-manager.js') const MetamaskController = require('./metamask-controller') -const firstTimeState = require('./first-time-state') +const rawFirstTimeState = require('./first-time-state') const setupRaven = require('./lib/setupRaven') const reportFailedTxToSentry = require('./lib/reportFailedTxToSentry') const setupMetamaskMeshMetrics = require('./lib/setupMetamaskMeshMetrics') @@ -34,6 +34,9 @@ const { ENVIRONMENT_TYPE_FULLSCREEN, } = require('./lib/enums') +// METAMASK_TEST_CONFIG is used in e2e tests to set the default network to localhost +const firstTimeState = Object.assign({}, rawFirstTimeState, global.METAMASK_TEST_CONFIG) + const STORAGE_KEY = 'metamask-config' const METAMASK_DEBUG = process.env.METAMASK_DEBUG diff --git a/app/scripts/controllers/balance.js b/app/scripts/controllers/balance.js index 4c97810a3..465751e61 100644 --- a/app/scripts/controllers/balance.js +++ b/app/scripts/controllers/balance.js @@ -80,7 +80,7 @@ class BalanceController { } }) this.accountTracker.store.subscribe(update) - this.blockTracker.on('block', update) + this.blockTracker.on('latest', update) } /** diff --git a/app/scripts/controllers/currency.js b/app/scripts/controllers/currency.js index a93aff49b..d5bc5fe2b 100644 --- a/app/scripts/controllers/currency.js +++ b/app/scripts/controllers/currency.js @@ -1,4 +1,4 @@ - const ObservableStore = require('obs-store') +const ObservableStore = require('obs-store') const extend = require('xtend') const log = require('loglevel') diff --git a/app/scripts/controllers/network/createInfuraClient.js b/app/scripts/controllers/network/createInfuraClient.js new file mode 100644 index 000000000..41af4d9f9 --- /dev/null +++ b/app/scripts/controllers/network/createInfuraClient.js @@ -0,0 +1,25 @@ +const mergeMiddleware = require('json-rpc-engine/src/mergeMiddleware') +const createBlockReEmitMiddleware = require('eth-json-rpc-middleware/block-reemit') +const createBlockCacheMiddleware = require('eth-json-rpc-middleware/block-cache') +const createInflightMiddleware = require('eth-json-rpc-middleware/inflight-cache') +const createBlockTrackerInspectorMiddleware = require('eth-json-rpc-middleware/block-tracker-inspector') +const providerFromMiddleware = require('eth-json-rpc-middleware/providerFromMiddleware') +const createInfuraMiddleware = require('eth-json-rpc-infura') +const BlockTracker = require('eth-block-tracker') + +module.exports = createInfuraClient + +function createInfuraClient ({ network }) { + const infuraMiddleware = createInfuraMiddleware({ network }) + const blockProvider = providerFromMiddleware(infuraMiddleware) + const blockTracker = new BlockTracker({ provider: blockProvider }) + + const networkMiddleware = mergeMiddleware([ + createBlockCacheMiddleware({ blockTracker }), + createInflightMiddleware(), + createBlockReEmitMiddleware({ blockTracker, provider: blockProvider }), + createBlockTrackerInspectorMiddleware({ blockTracker }), + infuraMiddleware, + ]) + return { networkMiddleware, blockTracker } +} diff --git a/app/scripts/controllers/network/createJsonRpcClient.js b/app/scripts/controllers/network/createJsonRpcClient.js new file mode 100644 index 000000000..40c353f7f --- /dev/null +++ b/app/scripts/controllers/network/createJsonRpcClient.js @@ -0,0 +1,25 @@ +const mergeMiddleware = require('json-rpc-engine/src/mergeMiddleware') +const createFetchMiddleware = require('eth-json-rpc-middleware/fetch') +const createBlockRefMiddleware = require('eth-json-rpc-middleware/block-ref') +const createBlockCacheMiddleware = require('eth-json-rpc-middleware/block-cache') +const createInflightMiddleware = require('eth-json-rpc-middleware/inflight-cache') +const createBlockTrackerInspectorMiddleware = require('eth-json-rpc-middleware/block-tracker-inspector') +const providerFromMiddleware = require('eth-json-rpc-middleware/providerFromMiddleware') +const BlockTracker = require('eth-block-tracker') + +module.exports = createJsonRpcClient + +function createJsonRpcClient ({ rpcUrl }) { + const fetchMiddleware = createFetchMiddleware({ rpcUrl }) + const blockProvider = providerFromMiddleware(fetchMiddleware) + const blockTracker = new BlockTracker({ provider: blockProvider }) + + const networkMiddleware = mergeMiddleware([ + createBlockRefMiddleware({ blockTracker }), + createBlockCacheMiddleware({ blockTracker }), + createInflightMiddleware(), + createBlockTrackerInspectorMiddleware({ blockTracker }), + fetchMiddleware, + ]) + return { networkMiddleware, blockTracker } +} diff --git a/app/scripts/controllers/network/createLocalhostClient.js b/app/scripts/controllers/network/createLocalhostClient.js new file mode 100644 index 000000000..fecc512e8 --- /dev/null +++ b/app/scripts/controllers/network/createLocalhostClient.js @@ -0,0 +1,21 @@ +const mergeMiddleware = require('json-rpc-engine/src/mergeMiddleware') +const createFetchMiddleware = require('eth-json-rpc-middleware/fetch') +const createBlockRefMiddleware = require('eth-json-rpc-middleware/block-ref') +const createBlockTrackerInspectorMiddleware = require('eth-json-rpc-middleware/block-tracker-inspector') +const providerFromMiddleware = require('eth-json-rpc-middleware/providerFromMiddleware') +const BlockTracker = require('eth-block-tracker') + +module.exports = createLocalhostClient + +function createLocalhostClient () { + const fetchMiddleware = createFetchMiddleware({ rpcUrl: 'http://localhost:8545/' }) + const blockProvider = providerFromMiddleware(fetchMiddleware) + const blockTracker = new BlockTracker({ provider: blockProvider, pollingInterval: 1000 }) + + const networkMiddleware = mergeMiddleware([ + createBlockRefMiddleware({ blockTracker }), + createBlockTrackerInspectorMiddleware({ blockTracker }), + fetchMiddleware, + ]) + return { networkMiddleware, blockTracker } +} diff --git a/app/scripts/controllers/network/createMetamaskMiddleware.js b/app/scripts/controllers/network/createMetamaskMiddleware.js new file mode 100644 index 000000000..8b17829b7 --- /dev/null +++ b/app/scripts/controllers/network/createMetamaskMiddleware.js @@ -0,0 +1,43 @@ +const mergeMiddleware = require('json-rpc-engine/src/mergeMiddleware') +const createScaffoldMiddleware = require('json-rpc-engine/src/createScaffoldMiddleware') +const createAsyncMiddleware = require('json-rpc-engine/src/createAsyncMiddleware') +const createWalletSubprovider = require('eth-json-rpc-middleware/wallet') + +module.exports = createMetamaskMiddleware + +function createMetamaskMiddleware ({ + version, + getAccounts, + processTransaction, + processEthSignMessage, + processTypedMessage, + processPersonalMessage, + getPendingNonce, +}) { + const metamaskMiddleware = mergeMiddleware([ + createScaffoldMiddleware({ + // staticSubprovider + eth_syncing: false, + web3_clientVersion: `MetaMask/v${version}`, + }), + createWalletSubprovider({ + getAccounts, + processTransaction, + processEthSignMessage, + processTypedMessage, + processPersonalMessage, + }), + createPendingNonceMiddleware({ getPendingNonce }), + ]) + return metamaskMiddleware +} + +function createPendingNonceMiddleware ({ getPendingNonce }) { + return createAsyncMiddleware(async (req, res, next) => { + if (req.method !== 'eth_getTransactionCount') return next() + const address = req.params[0] + const blockRef = req.params[1] + if (blockRef !== 'pending') return next() + req.result = await getPendingNonce(address) + }) +} diff --git a/app/scripts/controllers/network/network.js b/app/scripts/controllers/network/network.js index b6f7705b5..628e32fa4 100644 --- a/app/scripts/controllers/network/network.js +++ b/app/scripts/controllers/network/network.js @@ -1,15 +1,17 @@ const assert = require('assert') const EventEmitter = require('events') -const createMetamaskProvider = require('web3-provider-engine/zero.js') -const SubproviderFromProvider = require('web3-provider-engine/subproviders/provider.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') const EthQuery = require('eth-query') -const createEventEmitterProxy = require('../../lib/events-proxy.js') +const JsonRpcEngine = require('json-rpc-engine') +const providerFromEngine = require('eth-json-rpc-middleware/providerFromEngine') const log = require('loglevel') -const urlUtil = require('url') +const createMetamaskMiddleware = require('./createMetamaskMiddleware') +const createInfuraClient = require('./createInfuraClient') +const createJsonRpcClient = require('./createJsonRpcClient') +const createLocalhostClient = require('./createLocalhostClient') +const { createSwappableProxy, createEventEmitterProxy } = require('swappable-obj-proxy') + const { ROPSTEN, RINKEBY, @@ -17,7 +19,6 @@ const { MAINNET, LOCALHOST, } = require('./enums') -const LOCALHOST_RPC_URL = 'http://localhost:8545' const INFURA_PROVIDER_TYPES = [ROPSTEN, RINKEBY, KOVAN, MAINNET] const env = process.env.METAMASK_ENV @@ -39,21 +40,27 @@ module.exports = class NetworkController extends EventEmitter { this.providerStore = new ObservableStore(providerConfig) this.networkStore = new ObservableStore('loading') this.store = new ComposedStore({ provider: this.providerStore, network: this.networkStore }) - // create event emitter proxy - this._proxy = createEventEmitterProxy() - this.on('networkDidChange', this.lookupNetwork) + // provider and block tracker + this._provider = null + this._blockTracker = null + // provider and block tracker proxies - because the network changes + this._providerProxy = null + this._blockTrackerProxy = null } - initializeProvider (_providerParams) { - this._baseProviderParams = _providerParams + initializeProvider (providerParams) { + this._baseProviderParams = providerParams const { type, rpcTarget } = this.providerStore.getState() this._configureProvider({ type, rpcTarget }) - this._proxy.on('block', this._logBlock.bind(this)) - this._proxy.on('error', this.verifyNetwork.bind(this)) - this.ethQuery = new EthQuery(this._proxy) this.lookupNetwork() - return this._proxy + } + + // return the proxies so the references will always be good + getProviderAndBlockTracker () { + const provider = this._providerProxy + const blockTracker = this._blockTracker + return { provider, blockTracker } } verifyNetwork () { @@ -75,10 +82,11 @@ module.exports = class NetworkController extends EventEmitter { lookupNetwork () { // Prevent firing when provider is not defined. - if (!this.ethQuery || !this.ethQuery.sendAsync) { - return log.warn('NetworkController - lookupNetwork aborted due to missing ethQuery') + if (!this._provider) { + return log.warn('NetworkController - lookupNetwork aborted due to missing provider') } - this.ethQuery.sendAsync({ method: 'net_version' }, (err, network) => { + const ethQuery = new EthQuery(this._provider) + ethQuery.sendAsync({ method: 'net_version' }, (err, network) => { if (err) return this.setNetworkState('loading') log.info('web3.getNetwork returned ' + network) this.setNetworkState(network) @@ -131,7 +139,7 @@ module.exports = class NetworkController extends EventEmitter { this._configureInfuraProvider(opts) // other type-based rpc endpoints } else if (type === LOCALHOST) { - this._configureStandardProvider({ rpcUrl: LOCALHOST_RPC_URL }) + this._configureLocalhostProvider() // url-based rpc endpoints } else if (type === 'rpc') { this._configureStandardProvider({ rpcUrl: rpcTarget }) @@ -141,49 +149,47 @@ module.exports = class NetworkController extends EventEmitter { } _configureInfuraProvider ({ type }) { - log.info('_configureInfuraProvider', type) - const infuraProvider = createInfuraProvider({ network: type }) - const infuraSubprovider = new SubproviderFromProvider(infuraProvider) - const providerParams = extend(this._baseProviderParams, { - engineParams: { - pollingInterval: 8000, - blockTrackerProvider: infuraProvider, - }, - dataSubprovider: infuraSubprovider, - }) - const provider = createMetamaskProvider(providerParams) - this._setProvider(provider) + log.info('NetworkController - configureInfuraProvider', type) + const networkClient = createInfuraClient({ network: type }) + this._setNetworkClient(networkClient) + } + + _configureLocalhostProvider () { + log.info('NetworkController - configureLocalhostProvider') + const networkClient = createLocalhostClient() + this._setNetworkClient(networkClient) } _configureStandardProvider ({ rpcUrl }) { - // urlUtil handles malformed urls - rpcUrl = urlUtil.parse(rpcUrl).format() - const providerParams = extend(this._baseProviderParams, { - rpcUrl, - engineParams: { - pollingInterval: 8000, - }, - }) - const provider = createMetamaskProvider(providerParams) - this._setProvider(provider) - } - - _setProvider (provider) { - // collect old block tracker events - const oldProvider = this._provider - let blockTrackerHandlers - if (oldProvider) { - // capture old block handlers - blockTrackerHandlers = oldProvider._blockTracker.proxyEventHandlers - // tear down - oldProvider.removeAllListeners() - oldProvider.stop() + log.info('NetworkController - configureStandardProvider', rpcUrl) + const networkClient = createJsonRpcClient({ rpcUrl }) + this._setNetworkClient(networkClient) + } + + _setNetworkClient ({ networkMiddleware, blockTracker }) { + const metamaskMiddleware = createMetamaskMiddleware(this._baseProviderParams) + const engine = new JsonRpcEngine() + engine.push(metamaskMiddleware) + engine.push(networkMiddleware) + const provider = providerFromEngine(engine) + this._setProviderAndBlockTracker({ provider, blockTracker }) + } + + _setProviderAndBlockTracker ({ provider, blockTracker }) { + // update or intialize proxies + if (this._providerProxy) { + this._providerProxy.setTarget(provider) + } else { + this._providerProxy = createSwappableProxy(provider) + } + if (this._blockTrackerProxy) { + this._blockTrackerProxy.setTarget(blockTracker) + } else { + this._blockTrackerProxy = createEventEmitterProxy(blockTracker) } - // override block tracler - provider._blockTracker = createEventEmitterProxy(provider._blockTracker, blockTrackerHandlers) - // set as new provider + // set new provider and blockTracker this._provider = provider - this._proxy.setTarget(provider) + this._blockTracker = blockTracker } _logBlock (block) { diff --git a/app/scripts/controllers/recent-blocks.js b/app/scripts/controllers/recent-blocks.js index 926268691..d270f6f44 100644 --- a/app/scripts/controllers/recent-blocks.js +++ b/app/scripts/controllers/recent-blocks.js @@ -1,14 +1,14 @@ const ObservableStore = require('obs-store') const extend = require('xtend') -const BN = require('ethereumjs-util').BN const EthQuery = require('eth-query') const log = require('loglevel') +const pify = require('pify') class RecentBlocksController { /** * Controller responsible for storing, updating and managing the recent history of blocks. Blocks are back filled - * upon the controller's construction and then the list is updated when the given block tracker gets a 'block' event + * upon the controller's construction and then the list is updated when the given block tracker gets a 'latest' event * (indicating that there is a new block to process). * * @typedef {Object} RecentBlocksController @@ -16,7 +16,7 @@ class RecentBlocksController { * @param {BlockTracker} opts.blockTracker Contains objects necessary for tracking blocks and querying the blockchain * @param {BlockTracker} opts.provider The provider used to create a new EthQuery instance. * @property {BlockTracker} blockTracker Points to the passed BlockTracker. On RecentBlocksController construction, - * listens for 'block' events so that new blocks can be processed and added to storage. + * listens for 'latest' events so that new blocks can be processed and added to storage. * @property {EthQuery} ethQuery Points to the EthQuery instance created with the passed provider * @property {number} historyLength The maximum length of blocks to track * @property {object} store Stores the recentBlocks @@ -34,7 +34,13 @@ class RecentBlocksController { }, opts.initState) this.store = new ObservableStore(initState) - this.blockTracker.on('block', this.processBlock.bind(this)) + this.blockTracker.on('latest', async (newBlockNumberHex) => { + try { + await this.processBlock(newBlockNumberHex) + } catch (err) { + log.error(err) + } + }) this.backfill() } @@ -55,7 +61,11 @@ class RecentBlocksController { * @param {object} newBlock The new block to modify and add to the recentBlocks array * */ - processBlock (newBlock) { + async processBlock (newBlockNumberHex) { + const newBlockNumber = Number.parseInt(newBlockNumberHex, 16) + const newBlock = await this.getBlockByNumber(newBlockNumber, true) + if (!newBlock) return + const block = this.mapTransactionsToPrices(newBlock) const state = this.store.getState() @@ -108,9 +118,9 @@ class RecentBlocksController { } /** - * On this.blockTracker's first 'block' event after this RecentBlocksController's instantiation, the store.recentBlocks + * On this.blockTracker's first 'latest' event after this RecentBlocksController's instantiation, the store.recentBlocks * array is populated with this.historyLength number of blocks. The block number of the this.blockTracker's first - * 'block' event is used to iteratively generate all the numbers of the previous blocks, which are obtained by querying + * 'latest' event is used to iteratively generate all the numbers of the previous blocks, which are obtained by querying * the blockchain. These blocks are backfilled so that the recentBlocks array is ordered from oldest to newest. * * Each iteration over the block numbers is delayed by 100 milliseconds. @@ -118,18 +128,17 @@ class RecentBlocksController { * @returns {Promise<void>} Promises undefined */ async backfill () { - this.blockTracker.once('block', async (block) => { - const currentBlockNumber = Number.parseInt(block.number, 16) + this.blockTracker.once('latest', async (blockNumberHex) => { + const currentBlockNumber = Number.parseInt(blockNumberHex, 16) const blocksToFetch = Math.min(currentBlockNumber, this.historyLength) const prevBlockNumber = currentBlockNumber - 1 const targetBlockNumbers = Array(blocksToFetch).fill().map((_, index) => prevBlockNumber - index) await Promise.all(targetBlockNumbers.map(async (targetBlockNumber) => { try { - const newBlock = await this.getBlockByNumber(targetBlockNumber) + const newBlock = await this.getBlockByNumber(targetBlockNumber, true) + if (!newBlock) return - if (newBlock) { - this.backfillBlock(newBlock) - } + this.backfillBlock(newBlock) } catch (e) { log.error(e) } @@ -138,18 +147,6 @@ class RecentBlocksController { } /** - * A helper for this.backfill. Provides an easy way to ensure a 100 millisecond delay using await - * - * @returns {Promise<void>} Promises undefined - * - */ - async wait () { - return new Promise((resolve) => { - setTimeout(resolve, 100) - }) - } - - /** * Uses EthQuery to get a block that has a given block number. * * @param {number} number The number of the block to get @@ -157,13 +154,8 @@ class RecentBlocksController { * */ async getBlockByNumber (number) { - const bn = new BN(number) - return new Promise((resolve, reject) => { - this.ethQuery.getBlockByNumber('0x' + bn.toString(16), true, (err, block) => { - if (err) reject(err) - resolve(block) - }) - }) + const blockNumberHex = '0x' + number.toString(16) + return await pify(this.ethQuery.getBlockByNumber).call(this.ethQuery, blockNumberHex, true) } } diff --git a/app/scripts/controllers/transactions/index.js b/app/scripts/controllers/transactions/index.js index 8e2288aed..5d7d6d6da 100644 --- a/app/scripts/controllers/transactions/index.js +++ b/app/scripts/controllers/transactions/index.js @@ -65,6 +65,7 @@ class TransactionController extends EventEmitter { this.store = this.txStateManager.store this.nonceTracker = new NonceTracker({ provider: this.provider, + blockTracker: this.blockTracker, getPendingTransactions: this.txStateManager.getPendingTransactions.bind(this.txStateManager), getConfirmedTransactions: this.txStateManager.getConfirmedTransactions.bind(this.txStateManager), }) @@ -78,13 +79,17 @@ class TransactionController extends EventEmitter { }) this.txStateManager.store.subscribe(() => this.emit('update:badge')) - this._setupListners() + this._setupListeners() // memstore is computed from a few different stores this._updateMemstore() this.txStateManager.store.subscribe(() => this._updateMemstore()) this.networkStore.subscribe(() => this._updateMemstore()) this.preferencesStore.subscribe(() => this._updateMemstore()) + + // request state update to finalize initialization + this._updatePendingTxsAfterFirstBlock() } + /** @returns {number} the chainId*/ getChainId () { const networkState = this.networkStore.getState() @@ -311,6 +316,11 @@ class TransactionController extends EventEmitter { this.txStateManager.setTxStatusSubmitted(txId) } + confirmTransaction (txId) { + this.txStateManager.setTxStatusConfirmed(txId) + this._markNonceDuplicatesDropped(txId) + } + /** Convenience method for the ui thats sets the transaction to rejected @param txId {number} - the tx's Id @@ -354,6 +364,14 @@ class TransactionController extends EventEmitter { this.getFilteredTxList = (opts) => this.txStateManager.getFilteredTxList(opts) } + // called once on startup + async _updatePendingTxsAfterFirstBlock () { + // wait for first block so we know we're ready + await this.blockTracker.getLatestBlock() + // get status update for all pending transactions (for the current network) + await this.pendingTxTracker.updatePendingTxs() + } + /** If transaction controller was rebooted with transactions that are uncompleted in steps of the transaction signing or user confirmation process it will either @@ -386,14 +404,14 @@ class TransactionController extends EventEmitter { is called in constructor applies the listeners for pendingTxTracker txStateManager and blockTracker */ - _setupListners () { + _setupListeners () { this.txStateManager.on('tx:status-update', this.emit.bind(this, 'tx:status-update')) + this._setupBlockTrackerListener() this.pendingTxTracker.on('tx:warning', (txMeta) => { this.txStateManager.updateTx(txMeta, 'transactions/pending-tx-tracker#event: tx:warning') }) - this.pendingTxTracker.on('tx:confirmed', (txId) => this.txStateManager.setTxStatusConfirmed(txId)) - this.pendingTxTracker.on('tx:confirmed', (txId) => this._markNonceDuplicatesDropped(txId)) this.pendingTxTracker.on('tx:failed', this.txStateManager.setTxStatusFailed.bind(this.txStateManager)) + this.pendingTxTracker.on('tx:confirmed', (txId) => this.confirmTransaction(txId)) this.pendingTxTracker.on('tx:block-update', (txMeta, latestBlockNumber) => { if (!txMeta.firstRetryBlockNumber) { txMeta.firstRetryBlockNumber = latestBlockNumber @@ -405,13 +423,6 @@ class TransactionController extends EventEmitter { txMeta.retryCount++ 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 - this.blockTracker.on('latest', this.pendingTxTracker.resubmitPendingTxs.bind(this.pendingTxTracker)) - this.blockTracker.on('sync', this.pendingTxTracker.queryPendingTxs.bind(this.pendingTxTracker)) - } /** @@ -435,6 +446,40 @@ class TransactionController extends EventEmitter { }) } + _setupBlockTrackerListener () { + let listenersAreActive = false + const latestBlockHandler = this._onLatestBlock.bind(this) + const blockTracker = this.blockTracker + const txStateManager = this.txStateManager + + txStateManager.on('tx:status-update', updateSubscription) + updateSubscription() + + function updateSubscription () { + const pendingTxs = txStateManager.getPendingTransactions() + if (!listenersAreActive && pendingTxs.length > 0) { + blockTracker.on('latest', latestBlockHandler) + listenersAreActive = true + } else if (listenersAreActive && !pendingTxs.length) { + blockTracker.removeListener('latest', latestBlockHandler) + listenersAreActive = false + } + } + } + + async _onLatestBlock (blockNumber) { + try { + await this.pendingTxTracker.updatePendingTxs() + } catch (err) { + log.error(err) + } + try { + await this.pendingTxTracker.resubmitPendingTxs(blockNumber) + } catch (err) { + log.error(err) + } + } + /** Updates the memStore in transaction controller */ diff --git a/app/scripts/controllers/transactions/nonce-tracker.js b/app/scripts/controllers/transactions/nonce-tracker.js index 06f336eaa..421036368 100644 --- a/app/scripts/controllers/transactions/nonce-tracker.js +++ b/app/scripts/controllers/transactions/nonce-tracker.js @@ -12,8 +12,9 @@ const Mutex = require('await-semaphore').Mutex */ class NonceTracker { - constructor ({ provider, getPendingTransactions, getConfirmedTransactions }) { + constructor ({ provider, blockTracker, getPendingTransactions, getConfirmedTransactions }) { this.provider = provider + this.blockTracker = blockTracker this.ethQuery = new EthQuery(provider) this.getPendingTransactions = getPendingTransactions this.getConfirmedTransactions = getConfirmedTransactions @@ -34,7 +35,7 @@ class NonceTracker { * @typedef NonceDetails * @property {number} highestLocallyConfirmed - A hex string of the highest nonce on a confirmed transaction. * @property {number} nextNetworkNonce - The next nonce suggested by the eth_getTransactionCount method. - * @property {number} highetSuggested - The maximum between the other two, the number returned. + * @property {number} highestSuggested - The maximum between the other two, the number returned. */ /** @@ -80,15 +81,6 @@ class NonceTracker { } } - async _getCurrentBlock () { - const blockTracker = this._getBlockTracker() - const currentBlock = blockTracker.getCurrentBlock() - if (currentBlock) return currentBlock - return await new Promise((reject, resolve) => { - blockTracker.once('latest', resolve) - }) - } - async _globalMutexFree () { const globalMutex = this._lookupMutex('global') const releaseLock = await globalMutex.acquire() @@ -114,9 +106,8 @@ class NonceTracker { // calculate next nonce // we need to make sure our base count // and pending count are from the same block - const currentBlock = await this._getCurrentBlock() - const blockNumber = currentBlock.blockNumber - const baseCountBN = await this.ethQuery.getTransactionCount(address, blockNumber || 'latest') + const blockNumber = await this.blockTracker.getLatestBlock() + const baseCountBN = await this.ethQuery.getTransactionCount(address, blockNumber) const baseCount = baseCountBN.toNumber() assert(Number.isInteger(baseCount), `nonce-tracker - baseCount is not an integer - got: (${typeof baseCount}) "${baseCount}"`) const nonceDetails = { blockNumber, baseCount } @@ -165,15 +156,6 @@ class NonceTracker { return { name: 'local', nonce: highest, details: { startPoint, highest } } } - // this is a hotfix for the fact that the blockTracker will - // change when the network changes - - /** - @returns {Object} the current blockTracker - */ - _getBlockTracker () { - return this.provider._blockTracker - } } module.exports = NonceTracker diff --git a/app/scripts/controllers/transactions/pending-tx-tracker.js b/app/scripts/controllers/transactions/pending-tx-tracker.js index 4e41cdaf8..70cac096b 100644 --- a/app/scripts/controllers/transactions/pending-tx-tracker.js +++ b/app/scripts/controllers/transactions/pending-tx-tracker.js @@ -1,6 +1,7 @@ const EventEmitter = require('events') const log = require('loglevel') const EthQuery = require('ethjs-query') + /** Event emitter utility class for tracking the transactions as they<br> @@ -23,55 +24,26 @@ class PendingTransactionTracker extends EventEmitter { super() this.query = new EthQuery(config.provider) this.nonceTracker = config.nonceTracker - // default is one day this.getPendingTransactions = config.getPendingTransactions this.getCompletedTransactions = config.getCompletedTransactions this.publishTransaction = config.publishTransaction - this._checkPendingTxs() + this.confirmTransaction = config.confirmTransaction } /** - checks if a signed tx is in a block and - if it is included emits tx status as 'confirmed' - @param block {object}, a full block - @emits tx:confirmed - @emits tx:failed - */ - checkForTxInBlock (block) { - const signedTxList = this.getPendingTransactions() - if (!signedTxList.length) return - signedTxList.forEach((txMeta) => { - const txHash = txMeta.hash - const txId = txMeta.id - - if (!txHash) { - const noTxHashErr = new Error('We had an error while submitting this transaction, please try again.') - noTxHashErr.name = 'NoTxHashError' - this.emit('tx:failed', txId, noTxHashErr) - return - } - - - block.transactions.forEach((tx) => { - if (tx.hash === txHash) this.emit('tx:confirmed', txId) - }) - }) - } - - /** - asks the network for the transaction to see if a block number is included on it - if we have skipped/missed blocks - @param object - oldBlock newBlock + checks the network for signed txs and releases the nonce global lock if it is */ - queryPendingTxs ({ oldBlock, newBlock }) { - // check pending transactions on start - if (!oldBlock) { - this._checkPendingTxs() - return + async updatePendingTxs () { + // in order to keep the nonceTracker accurate we block it while updating pending transactions + const nonceGlobalLock = await this.nonceTracker.getGlobalLock() + try { + const pendingTxs = this.getPendingTransactions() + await Promise.all(pendingTxs.map((txMeta) => this._checkPendingTx(txMeta))) + } catch (err) { + log.error('PendingTransactionTracker - Error updating pending transactions') + log.error(err) } - // if we synced by more than one block, check for missed pending transactions - const diff = Number.parseInt(newBlock.number, 16) - Number.parseInt(oldBlock.number, 16) - if (diff > 1) this._checkPendingTxs() + nonceGlobalLock.releaseLock() } /** @@ -79,11 +51,11 @@ class PendingTransactionTracker extends EventEmitter { @param block {object} - a block object @emits tx:warning */ - resubmitPendingTxs (block) { + resubmitPendingTxs (blockNumber) { const pending = this.getPendingTransactions() // only try resubmitting if their are transactions to resubmit if (!pending.length) return - pending.forEach((txMeta) => this._resubmitTx(txMeta, block.number).catch((err) => { + pending.forEach((txMeta) => this._resubmitTx(txMeta, blockNumber).catch((err) => { /* Dont marked as failed if the error is a "known" transaction warning "there is already a transaction with the same sender-nonce @@ -145,6 +117,7 @@ class PendingTransactionTracker extends EventEmitter { this.emit('tx:retry', txMeta) return txHash } + /** Ask the network for the transaction to see if it has been include in a block @param txMeta {Object} - the txMeta object @@ -174,9 +147,8 @@ class PendingTransactionTracker extends EventEmitter { } // get latest transaction status - let txParams try { - txParams = await this.query.getTransactionByHash(txHash) + const txParams = await this.query.getTransactionByHash(txHash) if (!txParams) return if (txParams.blockNumber) { this.emit('tx:confirmed', txId) @@ -191,26 +163,12 @@ class PendingTransactionTracker extends EventEmitter { } /** - checks the network for signed txs and releases the nonce global lock if it is - */ - async _checkPendingTxs () { - const signedTxList = this.getPendingTransactions() - // in order to keep the nonceTracker accurate we block it while updating pending transactions - const { releaseLock } = await this.nonceTracker.getGlobalLock() - try { - await Promise.all(signedTxList.map((txMeta) => this._checkPendingTx(txMeta))) - } catch (err) { - log.error('PendingTransactionWatcher - Error updating pending transactions') - log.error(err) - } - releaseLock() - } - - /** checks to see if a confirmed txMeta has the same nonce @param txMeta {Object} - txMeta object @returns {boolean} */ + + async _checkIfNonceIsTaken (txMeta) { const address = txMeta.txParams.from const completed = this.getCompletedTransactions(address) diff --git a/app/scripts/controllers/transactions/tx-gas-utils.js b/app/scripts/controllers/transactions/tx-gas-utils.js index 5cd0f5407..3dd45507f 100644 --- a/app/scripts/controllers/transactions/tx-gas-utils.js +++ b/app/scripts/controllers/transactions/tx-gas-utils.js @@ -25,7 +25,7 @@ class TxGasUtil { @returns {object} the txMeta object with the gas written to the txParams */ async analyzeGasUsage (txMeta) { - const block = await this.query.getBlockByNumber('latest', true) + const block = await this.query.getBlockByNumber('latest', false) let estimatedGasHex try { estimatedGasHex = await this.estimateTxGas(txMeta, block.gasLimit) diff --git a/app/scripts/lib/account-tracker.js b/app/scripts/lib/account-tracker.js index 0f7b3d865..b7e2c7cbe 100644 --- a/app/scripts/lib/account-tracker.js +++ b/app/scripts/lib/account-tracker.js @@ -7,14 +7,13 @@ * on each new block. */ -const async = require('async') const EthQuery = require('eth-query') const ObservableStore = require('obs-store') -const EventEmitter = require('events').EventEmitter -function noop () {} +const log = require('loglevel') +const pify = require('pify') -class AccountTracker extends EventEmitter { +class AccountTracker { /** * This module is responsible for tracking any number of accounts and caching their current balances & transaction @@ -35,8 +34,6 @@ class AccountTracker extends EventEmitter { * */ constructor (opts = {}) { - super() - const initState = { accounts: {}, currentBlockGasLimit: '', @@ -44,12 +41,12 @@ class AccountTracker extends EventEmitter { this.store = new ObservableStore(initState) this._provider = opts.provider - this._query = new EthQuery(this._provider) + this._query = pify(new EthQuery(this._provider)) this._blockTracker = opts.blockTracker // subscribe to latest block - this._blockTracker.on('block', this._updateForBlock.bind(this)) + this._blockTracker.on('latest', this._updateForBlock.bind(this)) // blockTracker.currentBlock may be null - this._currentBlockNumber = this._blockTracker.currentBlock + this._currentBlockNumber = this._blockTracker.getCurrentBlock() } /** @@ -67,49 +64,57 @@ class AccountTracker extends EventEmitter { const accounts = this.store.getState().accounts const locals = Object.keys(accounts) - const toAdd = [] + const accountsToAdd = [] addresses.forEach((upstream) => { if (!locals.includes(upstream)) { - toAdd.push(upstream) + accountsToAdd.push(upstream) } }) - const toRemove = [] + const accountsToRemove = [] locals.forEach((local) => { if (!addresses.includes(local)) { - toRemove.push(local) + accountsToRemove.push(local) } }) - toAdd.forEach(upstream => this.addAccount(upstream)) - toRemove.forEach(local => this.removeAccount(local)) - this._updateAccounts() + this.addAccounts(accountsToAdd) + this.removeAccount(accountsToRemove) } /** - * Adds a new address to this AccountTracker's accounts object, which points to an empty object. This object will be + * Adds new addresses to track the balances of * given a balance as long this._currentBlockNumber is defined. * - * @param {string} address A hex address of a new account to store in this AccountTracker's accounts object + * @param {array} addresses An array of hex addresses of new accounts to track * */ - addAccount (address) { + addAccounts (addresses) { const accounts = this.store.getState().accounts - accounts[address] = {} + // add initial state for addresses + addresses.forEach(address => { + accounts[address] = {} + }) + // save accounts state this.store.updateState({ accounts }) + // fetch balances for the accounts if there is block number ready if (!this._currentBlockNumber) return - this._updateAccount(address) + addresses.forEach(address => this._updateAccount(address)) } /** - * Removes an account from this AccountTracker's accounts object + * Removes accounts from being tracked * - * @param {string} address A hex address of a the account to remove + * @param {array} an array of hex addresses to stop tracking * */ - removeAccount (address) { + removeAccount (addresses) { const accounts = this.store.getState().accounts - delete accounts[address] + // remove each state object + addresses.forEach(address => { + delete accounts[address] + }) + // save accounts state this.store.updateState({ accounts }) } @@ -118,71 +123,56 @@ class AccountTracker extends EventEmitter { * via EthQuery * * @private - * @param {object} block Data about the block that contains the data to update to. + * @param {number} blockNumber the block number to update to. * @fires 'block' The updated state, if all account updates are successful * */ - _updateForBlock (block) { - this._currentBlockNumber = block.number - const currentBlockGasLimit = block.gasLimit + async _updateForBlock (blockNumber) { + this._currentBlockNumber = blockNumber + // block gasLimit polling shouldn't be in account-tracker shouldn't be here... + const currentBlock = await this._query.getBlockByNumber(blockNumber, false) + if (!currentBlock) return + const currentBlockGasLimit = currentBlock.gasLimit this.store.updateState({ currentBlockGasLimit }) - async.parallel([ - this._updateAccounts.bind(this), - ], (err) => { - if (err) return console.error(err) - this.emit('block', this.store.getState()) - }) + try { + await this._updateAccounts() + } catch (err) { + log.error(err) + } } /** * Calls this._updateAccount for each account in this.store * - * @param {Function} cb A callback to pass to this._updateAccount, called after each account is successfully updated + * @returns {Promise} after all account balances updated * */ - _updateAccounts (cb = noop) { + async _updateAccounts () { const accounts = this.store.getState().accounts const addresses = Object.keys(accounts) - async.each(addresses, this._updateAccount.bind(this), cb) + await Promise.all(addresses.map(this._updateAccount.bind(this))) } /** - * Updates the current balance of an account. Gets an updated balance via this._getAccount. + * Updates the current balance of an account. * * @private * @param {string} address A hex address of a the account to be updated - * @param {Function} cb A callback to call once the account at address is successfully update + * @returns {Promise} after the account balance is updated * */ - _updateAccount (address, cb = noop) { - this._getAccount(address, (err, result) => { - if (err) return cb(err) - result.address = address - const accounts = this.store.getState().accounts - // only populate if the entry is still present - if (accounts[address]) { - accounts[address] = result - this.store.updateState({ accounts }) - } - cb(null, result) - }) - } - - /** - * Gets the current balance of an account via EthQuery. - * - * @private - * @param {string} address A hex address of a the account to query - * @param {Function} cb A callback to call once the account at address is successfully update - * - */ - _getAccount (address, cb = noop) { - const query = this._query - async.parallel({ - balance: query.getBalance.bind(query, address), - }, cb) + async _updateAccount (address) { + // query balance + const balance = await this._query.getBalance(address) + const result = { address, balance } + // update accounts state + const { accounts } = this.store.getState() + // only populate if the entry is still present + if (!accounts[address]) return + accounts[address] = result + this.store.updateState({ accounts }) } } diff --git a/app/scripts/lib/events-proxy.js b/app/scripts/lib/events-proxy.js deleted file mode 100644 index f83773ccc..000000000 --- a/app/scripts/lib/events-proxy.js +++ /dev/null @@ -1,42 +0,0 @@ -/** - * Returns an EventEmitter that proxies events from the given event emitter - * @param {any} eventEmitter - * @param {object} listeners - The listeners to proxy to - * @returns {any} - */ -module.exports = function createEventEmitterProxy (eventEmitter, listeners) { - let target = eventEmitter - const eventHandlers = listeners || {} - const proxy = /** @type {any} */ (new Proxy({}, { - get: (_, name) => { - // intercept listeners - if (name === 'on') return addListener - if (name === 'setTarget') return setTarget - if (name === 'proxyEventHandlers') return eventHandlers - return (/** @type {any} */ (target))[name] - }, - set: (_, name, value) => { - target[name] = value - return true - }, - })) - function setTarget (/** @type {EventEmitter} */ eventEmitter) { - target = eventEmitter - // migrate listeners - Object.keys(eventHandlers).forEach((name) => { - /** @type {Array<Function>} */ (eventHandlers[name]).forEach((handler) => target.on(name, handler)) - }) - } - /** - * Attaches a function to be called whenever the specified event is emitted - * @param {string} name - * @param {Function} handler - */ - function addListener (name, handler) { - if (!eventHandlers[name]) eventHandlers[name] = [] - eventHandlers[name].push(handler) - target.on(name, handler) - } - if (listeners) proxy.setTarget(eventEmitter) - return proxy -} diff --git a/app/scripts/lib/message-manager.js b/app/scripts/lib/message-manager.js index 901367f04..47925b94b 100644 --- a/app/scripts/lib/message-manager.js +++ b/app/scripts/lib/message-manager.js @@ -69,10 +69,39 @@ module.exports = class MessageManager extends EventEmitter { * new Message to this.messages, and to save the unapproved Messages from that list to this.memStore. * * @param {Object} msgParams The params for the eth_sign call to be made after the message is approved. + * @param {Object} req (optional) The original request object possibly containing the origin + * @returns {promise} after signature has been + * + */ + addUnapprovedMessageAsync (msgParams, req) { + return new Promise((resolve, reject) => { + const msgId = this.addUnapprovedMessage(msgParams, req) + // await finished + this.once(`${msgId}:finished`, (data) => { + switch (data.status) { + case 'signed': + return resolve(data.rawSig) + case 'rejected': + return reject(new Error('MetaMask Message Signature: User denied message signature.')) + default: + return reject(new Error(`MetaMask Message Signature: Unknown problem: ${JSON.stringify(msgParams)}`)) + } + }) + }) + } + + /** + * Creates a new Message with an 'unapproved' status using the passed msgParams. this.addMsg is called to add the + * new Message to this.messages, and to save the unapproved Messages from that list to this.memStore. + * + * @param {Object} msgParams The params for the eth_sign call to be made after the message is approved. + * @param {Object} req (optional) The original request object where the origin may be specificied * @returns {number} The id of the newly created message. * */ - addUnapprovedMessage (msgParams) { + addUnapprovedMessage (msgParams, req) { + // add origin from request + if (req) msgParams.origin = req.origin msgParams.data = normalizeMsgData(msgParams.data) // create txData obj with parameters and meta data var time = (new Date()).getTime() diff --git a/app/scripts/lib/personal-message-manager.js b/app/scripts/lib/personal-message-manager.js index e96ced1f2..fc2cccdf1 100644 --- a/app/scripts/lib/personal-message-manager.js +++ b/app/scripts/lib/personal-message-manager.js @@ -73,11 +73,43 @@ module.exports = class PersonalMessageManager extends EventEmitter { * this.memStore. * * @param {Object} msgParams The params for the eth_sign call to be made after the message is approved. + * @param {Object} req (optional) The original request object possibly containing the origin + * @returns {promise} When the message has been signed or rejected + * + */ + addUnapprovedMessageAsync (msgParams, req) { + return new Promise((resolve, reject) => { + if (!msgParams.from) { + reject(new Error('MetaMask Message Signature: from field is required.')) + } + const msgId = this.addUnapprovedMessage(msgParams, req) + this.once(`${msgId}:finished`, (data) => { + switch (data.status) { + case 'signed': + return resolve(data.rawSig) + case 'rejected': + return reject(new Error('MetaMask Message Signature: User denied message signature.')) + default: + return reject(new Error(`MetaMask Message Signature: Unknown problem: ${JSON.stringify(msgParams)}`)) + } + }) + }) + } + + /** + * Creates a new PersonalMessage with an 'unapproved' status using the passed msgParams. this.addMsg is called to add + * the new PersonalMessage to this.messages, and to save the unapproved PersonalMessages from that list to + * this.memStore. + * + * @param {Object} msgParams The params for the eth_sign call to be made after the message is approved. + * @param {Object} req (optional) The original request object possibly containing the origin * @returns {number} The id of the newly created PersonalMessage. * */ - addUnapprovedMessage (msgParams) { + addUnapprovedMessage (msgParams, req) { log.debug(`PersonalMessageManager addUnapprovedMessage: ${JSON.stringify(msgParams)}`) + // add origin from request + if (req) msgParams.origin = req.origin msgParams.data = this.normalizeMsgData(msgParams.data) // create txData obj with parameters and meta data var time = (new Date()).getTime() @@ -257,4 +289,3 @@ module.exports = class PersonalMessageManager extends EventEmitter { } } - diff --git a/app/scripts/lib/setupRaven.js b/app/scripts/lib/setupRaven.js index e657e278f..9aa2dec0a 100644 --- a/app/scripts/lib/setupRaven.js +++ b/app/scripts/lib/setupRaven.js @@ -70,11 +70,11 @@ function simplifyErrorMessages (report) { function rewriteErrorMessages (report, rewriteFn) { // rewrite top level message - if (report.message) report.message = rewriteFn(report.message) + if (typeof report.message === 'string') report.message = rewriteFn(report.message) // rewrite each exception message if (report.exception && report.exception.values) { report.exception.values.forEach(item => { - item.value = rewriteFn(item.value) + if (typeof item.value === 'string') item.value = rewriteFn(item.value) }) } } diff --git a/app/scripts/lib/typed-message-manager.js b/app/scripts/lib/typed-message-manager.js index c58921610..e5e1c94b3 100644 --- a/app/scripts/lib/typed-message-manager.js +++ b/app/scripts/lib/typed-message-manager.js @@ -72,11 +72,40 @@ module.exports = class TypedMessageManager extends EventEmitter { * this.memStore. Before any of this is done, msgParams are validated * * @param {Object} msgParams The params for the eth_sign call to be made after the message is approved. + * @param {Object} req (optional) The original request object possibly containing the origin + * @returns {promise} When the message has been signed or rejected + * + */ + addUnapprovedMessageAsync (msgParams, req) { + return new Promise((resolve, reject) => { + const msgId = this.addUnapprovedMessage(msgParams, req) + this.once(`${msgId}:finished`, (data) => { + switch (data.status) { + case 'signed': + return resolve(data.rawSig) + case 'rejected': + return reject(new Error('MetaMask Message Signature: User denied message signature.')) + default: + return reject(new Error(`MetaMask Message Signature: Unknown problem: ${JSON.stringify(msgParams)}`)) + } + }) + }) + } + + /** + * Creates a new TypedMessage with an 'unapproved' status using the passed msgParams. this.addMsg is called to add + * the new TypedMessage to this.messages, and to save the unapproved TypedMessages from that list to + * this.memStore. Before any of this is done, msgParams are validated + * + * @param {Object} msgParams The params for the eth_sign call to be made after the message is approved. + * @param {Object} req (optional) The original request object possibly containing the origin * @returns {number} The id of the newly created TypedMessage. * */ - addUnapprovedMessage (msgParams) { + addUnapprovedMessage (msgParams, req) { this.validateParams(msgParams) + // add origin from request + if (req) msgParams.origin = req.origin log.debug(`TypedMessageManager addUnapprovedMessage: ${JSON.stringify(msgParams)}`) // create txData obj with parameters and meta data diff --git a/app/scripts/lib/util.js b/app/scripts/lib/util.js index 51e9036cc..2b3fe3d6e 100644 --- a/app/scripts/lib/util.js +++ b/app/scripts/lib/util.js @@ -99,7 +99,21 @@ function BnMultiplyByFraction (targetBN, numerator, denominator) { return targetBN.mul(numBN).div(denomBN) } +function applyListeners (listeners, emitter) { + Object.keys(listeners).forEach((key) => { + emitter.on(key, listeners[key]) + }) +} + +function removeListeners (listeners, emitter) { + Object.keys(listeners).forEach((key) => { + emitter.removeListener(key, listeners[key]) + }) +} + module.exports = { + removeListeners, + applyListeners, getStack, getEnvironmentType, sufficientBalance, diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index bcc7075c2..12daf1603 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -46,7 +46,6 @@ const BN = require('ethereumjs-util').BN const GWEI_BN = new BN('1000000000') 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') @@ -106,8 +105,9 @@ module.exports = class MetamaskController extends EventEmitter { this.blacklistController.scheduleUpdates() // rpc provider - this.provider = this.initializeProvider() - this.blockTracker = this.provider._blockTracker + this.initializeProvider() + this.provider = this.networkController.getProviderAndBlockTracker().provider + this.blockTracker = this.networkController.getProviderAndBlockTracker().blockTracker // token exchange rate tracker this.tokenRatesController = new TokenRatesController({ @@ -251,28 +251,22 @@ module.exports = class MetamaskController extends EventEmitter { static: { eth_syncing: false, web3_clientVersion: `MetaMask/v${version}`, - eth_sendTransaction: (payload, next, end) => { - const origin = payload.origin - const txParams = payload.params[0] - nodeify(this.txController.newUnapprovedTransaction, this.txController)(txParams, { origin }, end) - }, }, // account mgmt - getAccounts: (cb) => { + getAccounts: async () => { 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) + return [selectedAddress] + } else { + return [] } - cb(null, result) }, // tx signing - // old style msg signing - processMessage: this.newUnsignedMessage.bind(this), - // personal_sign msg signing + processTransaction: this.newUnapprovedTransaction.bind(this), + // msg signing + processEthSignMessage: this.newUnsignedMessage.bind(this), processPersonalMessage: this.newUnsignedPersonalMessage.bind(this), processTypedMessage: this.newUnsignedTypedMessage.bind(this), } @@ -808,6 +802,18 @@ module.exports = class MetamaskController extends EventEmitter { // --------------------------------------------------------------------------- // Identity Management (signature operations) + /** + * Called when a Dapp suggests a new tx to be signed. + * this wrapper needs to exist so we can provide a reference to + * "newUnapprovedTransaction" before "txController" is instantiated + * + * @param {Object} msgParams - The params passed to eth_sign. + * @param {Object} req - (optional) the original request, containing the origin + */ + async newUnapprovedTransaction (txParams, req) { + return await this.txController.newUnapprovedTransaction(txParams, req) + } + // eth_sign methods: /** @@ -819,20 +825,11 @@ module.exports = class MetamaskController extends EventEmitter { * @param {Object} msgParams - The params passed to eth_sign. * @param {Function} cb = The callback function called with the signature. */ - newUnsignedMessage (msgParams, cb) { - const msgId = this.messageManager.addUnapprovedMessage(msgParams) + newUnsignedMessage (msgParams, req) { + const promise = this.messageManager.addUnapprovedMessageAsync(msgParams, req) this.sendUpdate() this.opts.showUnconfirmedMessage() - this.messageManager.once(`${msgId}:finished`, (data) => { - switch (data.status) { - case 'signed': - return cb(null, data.rawSig) - case 'rejected': - return cb(cleanErrorStack(new Error('MetaMask Message Signature: User denied message signature.'))) - default: - return cb(cleanErrorStack(new Error(`MetaMask Message Signature: Unknown problem: ${JSON.stringify(msgParams)}`))) - } - }) + return promise } /** @@ -886,24 +883,11 @@ module.exports = class MetamaskController extends EventEmitter { * @param {Function} cb - The callback function called with the signature. * Passed back to the requesting Dapp. */ - newUnsignedPersonalMessage (msgParams, cb) { - if (!msgParams.from) { - return cb(cleanErrorStack(new Error('MetaMask Message Signature: from field is required.'))) - } - - const msgId = this.personalMessageManager.addUnapprovedMessage(msgParams) + async newUnsignedPersonalMessage (msgParams, req) { + const promise = this.personalMessageManager.addUnapprovedMessageAsync(msgParams, req) this.sendUpdate() this.opts.showUnconfirmedMessage() - this.personalMessageManager.once(`${msgId}:finished`, (data) => { - switch (data.status) { - case 'signed': - return cb(null, data.rawSig) - case 'rejected': - return cb(cleanErrorStack(new Error('MetaMask Message Signature: User denied message signature.'))) - default: - return cb(cleanErrorStack(new Error(`MetaMask Message Signature: Unknown problem: ${JSON.stringify(msgParams)}`))) - } - }) + return promise } /** @@ -952,26 +936,11 @@ module.exports = class MetamaskController extends EventEmitter { * @param {Object} msgParams - The params passed to eth_signTypedData. * @param {Function} cb - The callback function, called with the signature. */ - newUnsignedTypedMessage (msgParams, cb) { - let msgId - try { - msgId = this.typedMessageManager.addUnapprovedMessage(msgParams) - this.sendUpdate() - this.opts.showUnconfirmedMessage() - } catch (e) { - return cb(e) - } - - this.typedMessageManager.once(`${msgId}:finished`, (data) => { - switch (data.status) { - case 'signed': - return cb(null, data.rawSig) - case 'rejected': - return cb(cleanErrorStack(new Error('MetaMask Message Signature: User denied message signature.'))) - default: - return cb(cleanErrorStack(new Error(`MetaMask Message Signature: Unknown problem: ${JSON.stringify(msgParams)}`))) - } - }) + newUnsignedTypedMessage (msgParams, req) { + const promise = this.typedMessageManager.addUnapprovedMessageAsync(msgParams, req) + this.sendUpdate() + this.opts.showUnconfirmedMessage() + return promise } /** @@ -1236,7 +1205,7 @@ module.exports = class MetamaskController extends EventEmitter { // create filter polyfill middleware const filterMiddleware = createFilterMiddleware({ provider: this.provider, - blockTracker: this.provider._blockTracker, + blockTracker: this.blockTracker, }) engine.push(createOriginMiddleware({ origin })) diff --git a/docs/developing-on-deps.md b/docs/developing-on-deps.md index 7de3f67a8..e2e07cb4e 100644 --- a/docs/developing-on-deps.md +++ b/docs/developing-on-deps.md @@ -1,10 +1,9 @@ ### Developing on Dependencies -To enjoy the live-reloading that `gulp dev` offers while working on the `web3-provider-engine` or other dependencies: +To enjoy the live-reloading that `gulp dev` offers while working on the dependencies: 1. Clone the dependency locally. 2. `npm install` in its folder. 3. Run `npm link` in its folder. 4. Run `npm link $DEP_NAME` in this project folder. 5. Next time you `npm start` it will watch the dependency for changes as well! - diff --git a/docs/porting_to_new_environment.md b/docs/porting_to_new_environment.md index 3b1e46052..983c81f3e 100644 --- a/docs/porting_to_new_environment.md +++ b/docs/porting_to_new_environment.md @@ -89,8 +89,6 @@ MetaMask has two kinds of [duplex stream APIs](https://github.com/substack/strea If you are making a MetaMask-powered browser for a new platform, one of the trickiest tasks will be injecting the Web3 API into websites that are visited. On WebExtensions, we actually have to pipe data through a total of three JS contexts just to let sites talk to our background process (site -> contentscript -> background). -To make this as easy as possible, we use one of our favorite internal tools, [web3-provider-engine](https://www.npmjs.com/package/web3-provider-engine) to construct a custom web3 provider object whose source of truth is a stream that we connect to remotely. - To see how we do that, you can refer to the [inpage script](https://github.com/MetaMask/metamask-extension/blob/master/app/scripts/inpage.js) that we inject into every website. There you can see it creates a multiplex stream to the background, and uses it to initialize what we call the [inpage-provider](https://github.com/MetaMask/metamask-extension/blob/master/app/scripts/lib/inpage-provider.js), which you can see stubs a few methods out, but mostly just passes calls to `sendAsync` through the stream it's passed! That's really all the magic that's needed to create a web3-like API in a remote context, once you have a stream to MetaMask available. In `inpage.js` you can see we create a `PortStream`, that's just a class we use to wrap WebExtension ports as streams, so we can reuse our favorite stream abstraction over the more irregular API surface of the WebExtension. In a new platform, you will probably need to construct this stream differently. The key is that you need to construct a stream that talks from the site context to the background. Once you have that set up, it works like magic! diff --git a/old-ui/app/components/notice.js b/old-ui/app/components/notice.js index 09d461c7b..1ec254555 100644 --- a/old-ui/app/components/notice.js +++ b/old-ui/app/components/notice.js @@ -116,12 +116,25 @@ Notice.prototype.render = function () { ) } +Notice.prototype.setInitialDisclaimerState = function () { + if (document.getElementsByClassName('notice-box')[0].clientHeight < 310) { + this.setState({disclaimerDisabled: false}) + } +} + Notice.prototype.componentDidMount = function () { // eslint-disable-next-line react/no-find-dom-node var node = findDOMNode(this) linker.setupListener(node) - if (document.getElementsByClassName('notice-box')[0].clientHeight < 310) { - this.setState({disclaimerDisabled: false}) + this.setInitialDisclaimerState() +} + +Notice.prototype.componentDidUpdate = function (prevProps) { + const { notice: { id } = {} } = this.props + const { notice: { id: prevNoticeId } = {} } = prevProps + + if (id !== prevNoticeId) { + this.setInitialDisclaimerState() } } diff --git a/package-lock.json b/package-lock.json index 074e90cce..6e8c0ed73 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2068,7 +2068,7 @@ "anymatch": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-1.3.2.tgz", - "integrity": "sha512-0XNayC8lTHQ2OI8aljNCN3sSx6hsr/1+rlcDAotXJR7C1oZZHCNsfpbKwMjRA3Uqb5tF1Rae2oloTr4xpq+WjA==", + "integrity": "sha1-VT3Lj5HjyImEXf26NMd3IbkLnXo=", "requires": { "micromatch": "^2.1.5", "normalize-path": "^2.0.0" @@ -2085,7 +2085,7 @@ "aproba": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz", - "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==" + "integrity": "sha1-aALmJk79GMeQobDVF/DyYnvyyUo=" }, "arch": { "version": "2.1.0", @@ -2140,7 +2140,7 @@ "arr-flatten": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/arr-flatten/-/arr-flatten-1.1.0.tgz", - "integrity": "sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg==" + "integrity": "sha1-NgSLv/TntH4TZkQxbJlmnqWukfE=" }, "arr-map": { "version": "2.0.2", @@ -2425,7 +2425,8 @@ "async-limiter": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.0.tgz", - "integrity": "sha512-jp/uFnooOiO+L211eZOoSyzpOITMXx1rBITauYykG3BRYPu8h0UcxsPNB04RR5vo4Tyz3+ay17tR6JVf9qzYWg==" + "integrity": "sha512-jp/uFnooOiO+L211eZOoSyzpOITMXx1rBITauYykG3BRYPu8h0UcxsPNB04RR5vo4Tyz3+ay17tR6JVf9qzYWg==", + "dev": true }, "async-reduce": { "version": "0.0.1", @@ -2546,7 +2547,7 @@ "await-semaphore": { "version": "0.1.3", "resolved": "https://registry.npmjs.org/await-semaphore/-/await-semaphore-0.1.3.tgz", - "integrity": "sha512-d1W2aNSYcz/sxYO4pMGX9vq65qOTu0P800epMud+6cYYX0QcT7zyqcxec3VWzpgvdXo57UWmVbZpLMjX2m1I7Q==" + "integrity": "sha1-K4gBjMjCjgYWeuHN/wJQTx+WiNM=" }, "aws-sign2": { "version": "0.7.0", @@ -3863,7 +3864,7 @@ "babylon": { "version": "6.18.0", "resolved": "https://registry.npmjs.org/babylon/-/babylon-6.18.0.tgz", - "integrity": "sha512-q/UEjfGJ2Cm3oKV71DJz9d25TPnq5rhBVL2Q4fA5wcC3jcrdn7+SssEybFIxwAvvP+YCsCYNKughoF33GxgycQ==" + "integrity": "sha1-ry87iPpvXB5MY00aD46sT1WzleM=" }, "bach": { "version": "1.2.0", @@ -3908,6 +3909,7 @@ "version": "2.5.0", "resolved": "https://registry.npmjs.org/backoff/-/backoff-2.5.0.tgz", "integrity": "sha1-9hbtqdPktmuMp/ynn2lXIsX44m8=", + "dev": true, "requires": { "precond": "0.2" } @@ -4133,12 +4135,12 @@ "bindings": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.3.0.tgz", - "integrity": "sha512-DpLh5EzMR2kzvX1KIlVC0VkC3iZtHKTgdtZ0a3pglBZdaQFjt5S9g9xd1lE+YvXyfd6mtCeRnrUfOLYiTMlNSw==" + "integrity": "sha1-s0b27PapX1qBXFg5/HzbIlAvHtc=" }, "bip39": { "version": "2.4.0", "resolved": "https://registry.npmjs.org/bip39/-/bip39-2.4.0.tgz", - "integrity": "sha512-1++HywqIyPtWDo7gm4v0ylYbwkLvHkuwVSKbBlZBbTCP/mnkyrlARBny906VLAwxJbC5xw9EvuJasHFIZaIFMQ==", + "integrity": "sha1-oLitvxY/U0lfAPBdnt58JTaczxM=", "requires": { "create-hash": "^1.1.0", "pbkdf2": "^3.0.9", @@ -4195,7 +4197,7 @@ "bn.js": { "version": "4.11.8", "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.8.tgz", - "integrity": "sha512-ItfYfPLkWHUjckQCk8xC+LwxgK8NYcXywGigJgSwOP8Y2iyWT4f2vsZnoOXTTbo+o5yXmIUJ4gn5538SO5S3gA==" + "integrity": "sha1-LN4J617jQfSEdGuwMJsyU7GxRC8=" }, "body-parser": { "version": "1.18.2", @@ -4548,7 +4550,7 @@ "browserify-aes": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/browserify-aes/-/browserify-aes-1.1.1.tgz", - "integrity": "sha512-UGnTYAnB2a3YuYKIRy1/4FB2HdM866E0qC46JXvVTYKlBlZlnvfpSfY6OKfXZAkv70eJ2a1SqzpAo5CRhZGDFg==", + "integrity": "sha1-OLerVe24Bv8tzaGn8WIHc6R3xJ8=", "requires": { "buffer-xor": "^1.0.3", "cipher-base": "^1.0.0", @@ -5184,7 +5186,7 @@ "cipher-base": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/cipher-base/-/cipher-base-1.0.4.tgz", - "integrity": "sha512-Kkht5ye6ZGmwv40uUDZztayT2ThLQGfnj/T71N/XzeZeo3nf8foyW7zGTsPYkEya3m5f3cAypH+qe7YOrM1U2Q==", + "integrity": "sha1-h2Dk7MJy9MNjUy+SbYdKriwTl94=", "requires": { "inherits": "^2.0.1", "safe-buffer": "^5.0.1" @@ -6013,7 +6015,7 @@ "copy-to-clipboard": { "version": "3.0.8", "resolved": "https://registry.npmjs.org/copy-to-clipboard/-/copy-to-clipboard-3.0.8.tgz", - "integrity": "sha512-c3GdeY8qxCHGezVb1EFQfHYK/8NZRemgcTIzPq7PuxjHAf/raKibn2QdhHPb/y6q74PMgH6yizaDZlRmw6QyKw==", + "integrity": "sha1-9OgvSogw3ORma3643tDJvMMTq6k=", "requires": { "toggle-selection": "^1.0.3" } @@ -6062,7 +6064,7 @@ "coveralls": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/coveralls/-/coveralls-3.0.0.tgz", - "integrity": "sha512-ZppXR9y5PraUOrf/DzHJY6gzNUhXYE3b9D43xEXs4QYZ7/Oe0Gy0CS+IPKWFfvQFXB3RG9QduaQUFehzSpGAFw==", + "integrity": "sha1-Iu9zAzBTgIDSm4wVHckUav3oipk=", "dev": true, "requires": { "js-yaml": "^3.6.1", @@ -6137,6 +6139,7 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-2.1.0.tgz", "integrity": "sha512-FTIt2WK44RiafWQ62xIvd+oBoVd392abh1lF872trLlA74JCR1s4oTHlixwoIKy44ehn8WbQ0Ds2P16sw7ZQxg==", + "dev": true, "requires": { "node-fetch": "2.1.1", "whatwg-fetch": "2.0.3" @@ -6145,7 +6148,8 @@ "node-fetch": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.1.1.tgz", - "integrity": "sha1-NpynC4L1DIZJYQSmx3bSdPTkotQ=" + "integrity": "sha1-NpynC4L1DIZJYQSmx3bSdPTkotQ=", + "dev": true } } }, @@ -6706,7 +6710,7 @@ "debug": { "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "integrity": "sha1-XRKFFd8TT/Mn6QpMk/Tgd6U2NB8=", "requires": { "ms": "2.0.0" } @@ -7184,7 +7188,7 @@ "disc": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/disc/-/disc-1.3.3.tgz", - "integrity": "sha512-ui/kegr2k3tDr2EU7cA9Ag+YofgmB3shwSFJuuf6r6Epom2cyHhd5jBtCOhwXKSDFMlYEMeSadujjRS2uSqRsw==", + "integrity": "sha1-YdRVGAwqEVRou4UBWjPnGoL8AsI=", "requires": { "bl": "^1.2.0", "browser-unpack": "^1.2.0", @@ -7561,7 +7565,7 @@ "duplexify": { "version": "3.5.1", "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-3.5.1.tgz", - "integrity": "sha512-j5goxHTwVED1Fpe5hh3q9R93Kip0Bg2KVAt4f8CEYM3UEwYcPSvWbXaUQOzdX/HtiNomipv+gU7ASQPDbV7pGQ==", + "integrity": "sha1-ThUWvmiDi8kKSZlPCzmm5ZYL780=", "requires": { "end-of-stream": "^1.0.0", "inherits": "^2.0.1", @@ -7810,7 +7814,7 @@ "envify": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/envify/-/envify-4.1.0.tgz", - "integrity": "sha512-IKRVVoAYr4pIx4yIWNsz9mOsboxlNXiu7TNBnem/K/uTHdkyzXWDzHCK7UTolqBbgaBz0tQHsD3YNls0uIIjiw==", + "integrity": "sha1-85rT251oAbTmtHi2ECjT8LaBn34=", "dev": true, "requires": { "esprima": "^4.0.0", @@ -8295,42 +8299,62 @@ } }, "eth-block-tracker": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/eth-block-tracker/-/eth-block-tracker-2.3.0.tgz", - "integrity": "sha512-yrNyBIBKC7WfUjrXSG/CZVy0gW2aF8+MnjnrkOxkZOR+BAtL6JgYOnzVnrU8KE6mKJETlA/1dYMygvLXWyJGGw==", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/eth-block-tracker/-/eth-block-tracker-4.0.1.tgz", + "integrity": "sha512-ytJxddJ0TMcJHYxPlgGhMyr5EH6/Kyp3bg0WsjXgY9X0uYX3xVHTTeU5WVX6KX+9oJ37ZLUjh5PZ6VYnF1Fx/Q==", "requires": { - "async-eventemitter": "github:ahultgren/async-eventemitter#fa06e39e56786ba541c180061dbf2c0a5bbf951c", + "eth-json-rpc-infura": "^3.1.0", "eth-query": "^2.1.0", - "ethereumjs-tx": "^1.3.3", - "ethereumjs-util": "^5.1.3", - "ethjs-util": "^0.1.3", - "json-rpc-engine": "^3.6.0", - "pify": "^2.3.0", - "tape": "^4.6.3" + "pify": "^3.0.0" }, "dependencies": { - "async-eventemitter": { - "version": "github:ahultgren/async-eventemitter#fa06e39e56786ba541c180061dbf2c0a5bbf951c", - "from": "async-eventemitter@github:ahultgren/async-eventemitter#fa06e39e56786ba541c180061dbf2c0a5bbf951c", + "cross-fetch": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-2.2.2.tgz", + "integrity": "sha1-pH/09/xxLauo9qaVoRyUhEDUVyM=", "requires": { - "async": "^2.4.0" + "node-fetch": "2.1.2", + "whatwg-fetch": "2.0.4" } }, - "babelify": { - "version": "7.3.0", - "resolved": "https://registry.npmjs.org/babelify/-/babelify-7.3.0.tgz", - "integrity": "sha1-qlau3nBn/XvVSWZu4W3ChQh+iOU=", + "eth-json-rpc-infura": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/eth-json-rpc-infura/-/eth-json-rpc-infura-3.1.2.tgz", + "integrity": "sha512-IuK5Iowfs6taluA/3Okmu6EfZcFMq6MQuyrUL1PrCoJstuuBr3TvVeSy3keDyxfbrjFB34nCo538I8G+qMtsbw==", "requires": { - "babel-core": "^6.0.14", - "object-assign": "^4.0.0" + "cross-fetch": "^2.1.1", + "eth-json-rpc-middleware": "^1.5.0", + "json-rpc-engine": "^3.4.0", + "json-rpc-error": "^2.0.0", + "tape": "^4.8.0" + } + }, + "eth-json-rpc-middleware": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/eth-json-rpc-middleware/-/eth-json-rpc-middleware-1.6.0.tgz", + "integrity": "sha512-tDVCTlrUvdqHKqivYMjtFZsdD7TtpNLBCfKAcOpaVs7orBMS/A8HWro6dIzNtTZIR05FAbJ3bioFOnZpuCew9Q==", + "requires": { + "async": "^2.5.0", + "eth-query": "^2.1.2", + "eth-tx-summary": "^3.1.2", + "ethereumjs-block": "^1.6.0", + "ethereumjs-tx": "^1.3.3", + "ethereumjs-util": "^5.1.2", + "ethereumjs-vm": "^2.1.0", + "fetch-ponyfill": "^4.0.0", + "json-rpc-engine": "^3.6.0", + "json-rpc-error": "^2.0.0", + "json-stable-stringify": "^1.0.1", + "promise-to-callback": "^1.0.0", + "tape": "^4.6.3" } }, "ethereumjs-util": { - "version": "5.1.3", - "resolved": "https://registry.npmjs.org/ethereumjs-util/-/ethereumjs-util-5.1.3.tgz", - "integrity": "sha512-U/wmHagElZVxnpo3bFsvk5beFADegUcEzqtA/NfQbitAPOs6JoYq8M4SY9NfH4HD8236i63UOkkXafd7bqBL9A==", + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ethereumjs-util/-/ethereumjs-util-5.2.0.tgz", + "integrity": "sha512-CJAKdI0wgMbQFLlLRtZKGcy/L6pzVRgelIZqRqNbuVFM3K9VEnyfbcvz0ncWMRNCe4kaHWjwRYQcYMucmwsnWA==", "requires": { - "bn.js": "^4.8.0", + "bn.js": "^4.11.0", "create-hash": "^1.1.2", "ethjs-util": "^0.1.3", "keccak": "^1.0.2", @@ -8339,22 +8363,15 @@ "secp256k1": "^3.0.1" } }, - "json-rpc-engine": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/json-rpc-engine/-/json-rpc-engine-3.6.0.tgz", - "integrity": "sha512-QGEIIPMaG4lQ8iKQgzKq7Ra6hscqSL+6S+xiUFbNAoVaZII8iyN1l6tJHmUWIdbnl2o0rbwCnOPFAhTn9AJObw==", - "requires": { - "async": "^2.0.1", - "babel-preset-env": "^1.3.2", - "babelify": "^7.3.0", - "json-rpc-error": "^2.0.0", - "promise-to-callback": "^1.0.0" - } + "node-fetch": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.1.2.tgz", + "integrity": "sha1-q4hOjn5X44qUR1POxwb3iNF2i7U=" }, - "pify": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", - "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=" + "whatwg-fetch": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-2.0.4.tgz", + "integrity": "sha512-dcQ1GWpOD/eEQ97k66aiEVpNnapVj90/+R+SXTPYGHpYBBypfKJEQjLrvMZ7YXbKm21gXd4NcuxUTjiv1YtLng==" } } }, @@ -8428,13 +8445,70 @@ } }, "eth-json-rpc-filters": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/eth-json-rpc-filters/-/eth-json-rpc-filters-1.2.6.tgz", - "integrity": "sha512-6G9t43s3lxJckeSfNduc3Ww/40BGm1Cf8MU1nL8rrumZbEg44ZSexWUowB00D4kJ9qSOH+CbzdI+m3oVMi4xFw==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/eth-json-rpc-filters/-/eth-json-rpc-filters-2.1.1.tgz", + "integrity": "sha512-FSFcCMJ1lRS8az1LhIK5Klima8Hyfv4keKSdW2MA3zwiN/wX1NKb/sQSEFO3nstPUqR2Aa02lVokp85UnnrBlA==", "requires": { "await-semaphore": "^0.1.1", + "eth-json-rpc-middleware": "^1.6.0", + "ethjs-query": "^0.3.6", "json-rpc-engine": "^3.4.0", "lodash.flatmap": "^4.5.0" + }, + "dependencies": { + "eth-json-rpc-middleware": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/eth-json-rpc-middleware/-/eth-json-rpc-middleware-1.6.0.tgz", + "integrity": "sha512-tDVCTlrUvdqHKqivYMjtFZsdD7TtpNLBCfKAcOpaVs7orBMS/A8HWro6dIzNtTZIR05FAbJ3bioFOnZpuCew9Q==", + "requires": { + "async": "^2.5.0", + "eth-query": "^2.1.2", + "eth-tx-summary": "^3.1.2", + "ethereumjs-block": "^1.6.0", + "ethereumjs-tx": "^1.3.3", + "ethereumjs-util": "^5.1.2", + "ethereumjs-vm": "^2.1.0", + "fetch-ponyfill": "^4.0.0", + "json-rpc-engine": "^3.6.0", + "json-rpc-error": "^2.0.0", + "json-stable-stringify": "^1.0.1", + "promise-to-callback": "^1.0.0", + "tape": "^4.6.3" + } + }, + "ethereumjs-util": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ethereumjs-util/-/ethereumjs-util-5.2.0.tgz", + "integrity": "sha512-CJAKdI0wgMbQFLlLRtZKGcy/L6pzVRgelIZqRqNbuVFM3K9VEnyfbcvz0ncWMRNCe4kaHWjwRYQcYMucmwsnWA==", + "requires": { + "bn.js": "^4.11.0", + "create-hash": "^1.1.2", + "ethjs-util": "^0.1.3", + "keccak": "^1.0.2", + "rlp": "^2.0.0", + "safe-buffer": "^5.1.1", + "secp256k1": "^3.0.1" + } + }, + "ethjs-query": { + "version": "0.3.8", + "resolved": "https://registry.npmjs.org/ethjs-query/-/ethjs-query-0.3.8.tgz", + "integrity": "sha512-/J5JydqrOzU8O7VBOwZKUWXxHDGr46VqNjBCJgBVNNda+tv7Xc8Y2uJc6aMHHVbeN3YOQ7YRElgIc0q1CI02lQ==", + "requires": { + "babel-runtime": "^6.26.0", + "ethjs-format": "0.2.7", + "ethjs-rpc": "0.2.0", + "promise-to-callback": "^1.0.0" + } + }, + "ethjs-rpc": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/ethjs-rpc/-/ethjs-rpc-0.2.0.tgz", + "integrity": "sha512-RINulkNZTKnj4R/cjYYtYMnFFaBcVALzbtEJEONrrka8IeoarNB9Jbzn+2rT00Cv8y/CxAI+GgY1d0/i2iQeOg==", + "requires": { + "promise-to-callback": "^1.0.0" + } + } } }, "eth-json-rpc-infura": { @@ -8446,24 +8520,63 @@ "json-rpc-engine": "^3.4.0", "json-rpc-error": "^2.0.0", "tape": "^4.8.0" + }, + "dependencies": { + "eth-json-rpc-middleware": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/eth-json-rpc-middleware/-/eth-json-rpc-middleware-1.6.0.tgz", + "integrity": "sha512-tDVCTlrUvdqHKqivYMjtFZsdD7TtpNLBCfKAcOpaVs7orBMS/A8HWro6dIzNtTZIR05FAbJ3bioFOnZpuCew9Q==", + "requires": { + "async": "^2.5.0", + "eth-query": "^2.1.2", + "eth-tx-summary": "^3.1.2", + "ethereumjs-block": "^1.6.0", + "ethereumjs-tx": "^1.3.3", + "ethereumjs-util": "^5.1.2", + "ethereumjs-vm": "^2.1.0", + "fetch-ponyfill": "^4.0.0", + "json-rpc-engine": "^3.6.0", + "json-rpc-error": "^2.0.0", + "json-stable-stringify": "^1.0.1", + "promise-to-callback": "^1.0.0", + "tape": "^4.6.3" + } + }, + "ethereumjs-util": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ethereumjs-util/-/ethereumjs-util-5.2.0.tgz", + "integrity": "sha512-CJAKdI0wgMbQFLlLRtZKGcy/L6pzVRgelIZqRqNbuVFM3K9VEnyfbcvz0ncWMRNCe4kaHWjwRYQcYMucmwsnWA==", + "requires": { + "bn.js": "^4.11.0", + "create-hash": "^1.1.2", + "ethjs-util": "^0.1.3", + "keccak": "^1.0.2", + "rlp": "^2.0.0", + "safe-buffer": "^5.1.1", + "secp256k1": "^3.0.1" + } + } } }, "eth-json-rpc-middleware": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/eth-json-rpc-middleware/-/eth-json-rpc-middleware-1.6.0.tgz", - "integrity": "sha512-tDVCTlrUvdqHKqivYMjtFZsdD7TtpNLBCfKAcOpaVs7orBMS/A8HWro6dIzNtTZIR05FAbJ3bioFOnZpuCew9Q==", + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/eth-json-rpc-middleware/-/eth-json-rpc-middleware-2.4.0.tgz", + "integrity": "sha512-KUxyz7pr6MZmFlsym8EObWwrFHVxRCLHGfl8H2V7D4ZjK0yhoPaA94jSXAumUbfx2AmyYEtG9j2xmU1P83m7OQ==", "requires": { "async": "^2.5.0", + "clone": "^2.1.1", "eth-query": "^2.1.2", + "eth-sig-util": "^1.4.2", "eth-tx-summary": "^3.1.2", "ethereumjs-block": "^1.6.0", "ethereumjs-tx": "^1.3.3", "ethereumjs-util": "^5.1.2", "ethereumjs-vm": "^2.1.0", "fetch-ponyfill": "^4.0.0", - "json-rpc-engine": "^3.6.0", + "json-rpc-engine": "^3.6.3", "json-rpc-error": "^2.0.0", "json-stable-stringify": "^1.0.1", + "pify": "^3.0.0", "promise-to-callback": "^1.0.0", "tape": "^4.6.3" }, @@ -8485,17 +8598,16 @@ } }, "eth-keyring-controller": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/eth-keyring-controller/-/eth-keyring-controller-4.0.0.tgz", - "integrity": "sha512-D3Uj0b97vzEl/zXvrwYjFUYsz5gB4tnl/iMWqOm8jsvaREuHHbxRkm3iU/LG4fT8NGwS+fG8sLRPNBPu2/wRsA==", - "dev": true, + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/eth-keyring-controller/-/eth-keyring-controller-3.3.0.tgz", + "integrity": "sha512-ZQtWxg0mNSAeQ9JcOaeTKaN5vfr+MbjRhSfeaF1VIWZsQgULpHrbjT6m94qiAFdh7ZngoR7OEpK9PATBH7JNYw==", "requires": { "bip39": "^2.4.0", "bluebird": "^3.5.0", "browser-passworder": "^2.0.3", - "eth-hd-keyring": "^2.0.0", + "eth-hd-keyring": "^1.2.2", "eth-sig-util": "^1.4.0", - "eth-simple-keyring": "^2.0.0", + "eth-simple-keyring": "^1.3.0", "ethereumjs-util": "^5.1.2", "loglevel": "^1.5.0", "obs-store": "^2.4.1", @@ -8506,17 +8618,28 @@ "version": "7.3.0", "resolved": "https://registry.npmjs.org/babelify/-/babelify-7.3.0.tgz", "integrity": "sha1-qlau3nBn/XvVSWZu4W3ChQh+iOU=", - "dev": true, "requires": { "babel-core": "^6.0.14", "object-assign": "^4.0.0" } }, + "eth-hd-keyring": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/eth-hd-keyring/-/eth-hd-keyring-1.2.2.tgz", + "integrity": "sha1-rV9HkHRDapO0ObC5XHkJXCh5GII=", + "requires": { + "bip39": "^2.2.0", + "eth-sig-util": "^1.4.2", + "ethereumjs-util": "^5.1.1", + "ethereumjs-wallet": "^0.6.0", + "events": "^1.1.1", + "xtend": "^4.0.1" + } + }, "ethereumjs-util": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/ethereumjs-util/-/ethereumjs-util-5.2.0.tgz", "integrity": "sha512-CJAKdI0wgMbQFLlLRtZKGcy/L6pzVRgelIZqRqNbuVFM3K9VEnyfbcvz0ncWMRNCe4kaHWjwRYQcYMucmwsnWA==", - "dev": true, "requires": { "bn.js": "^4.11.0", "create-hash": "^1.1.2", @@ -8531,7 +8654,6 @@ "version": "2.4.1", "resolved": "https://registry.npmjs.org/obs-store/-/obs-store-2.4.1.tgz", "integrity": "sha512-wpA8G4uSn8cnCKZ0pFTvqsamvy0Sm1hR2ot0Qonbfj5yBMwdAp/eD4vDI+U/ZCbV1hb2V5GapL8YKUdGCvahgg==", - "dev": true, "requires": { "babel-preset-es2015": "^6.22.0", "babelify": "^7.3.0", @@ -8662,7 +8784,7 @@ "eth-phishing-detect": { "version": "1.1.12", "resolved": "https://registry.npmjs.org/eth-phishing-detect/-/eth-phishing-detect-1.1.12.tgz", - "integrity": "sha512-wzEqAB4mUY0gkrn+ZOlzyxHmsouKT6rrzYIxy/FFalqoZVvX/9McPdFwWkHCYrv4KzTKgJJh8tKzvMnTae8Naw==", + "integrity": "sha1-PbfojHVFEMlOZzbbhRCLkOIn/kE=", "requires": { "fast-levenshtein": "^2.0.6" } @@ -8681,6 +8803,7 @@ "resolved": "https://registry.npmjs.org/eth-sig-util/-/eth-sig-util-1.4.2.tgz", "integrity": "sha1-jZWCAsftuq6Dlwf7pvCf8ydgYhA=", "requires": { + "ethereumjs-abi": "git+https://github.com/ethereumjs/ethereumjs-abi.git#00ba8463a7f7a67fcad737ff9c2ebd95643427f7", "ethereumjs-util": "^5.1.1" }, "dependencies": { @@ -8709,13 +8832,11 @@ } }, "eth-simple-keyring": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/eth-simple-keyring/-/eth-simple-keyring-2.0.0.tgz", - "integrity": "sha512-4dMbkIy2k1qotDTjWINvXG+7tBmofp0YUhlXgcG0+I3w684V46+MAHEkBtD2Y09iEeIB07RDXrezKP9WxOpynA==", - "dev": true, + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/eth-simple-keyring/-/eth-simple-keyring-1.3.0.tgz", + "integrity": "sha512-KNB3WXHyY/NfUiVTllsGScJ8AFTR6OGcrjKn4oSYG+HIvhkOoBqMAJ94BR3zGTvzyg5rHywHwT4L2K4+ZPFp5Q==", "requires": { - "eth-sig-util": "^2.0.1", - "ethereumjs-abi": "^0.6.5", + "eth-sig-util": "^1.4.2", "ethereumjs-util": "^5.1.1", "ethereumjs-wallet": "^0.6.0", "events": "^1.1.1", @@ -8723,31 +8844,26 @@ }, "dependencies": { "eth-sig-util": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/eth-sig-util/-/eth-sig-util-2.0.1.tgz", - "integrity": "sha512-lxHZOQspexk3DaGj4RBbWy4C/qNOWRnxpaJzNnYD3WEmC8shcJ4tHs7Xv878rzvILfJnSFSCCiKQhng1m80oBQ==", - "dev": true, + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/eth-sig-util/-/eth-sig-util-1.4.2.tgz", + "integrity": "sha1-jZWCAsftuq6Dlwf7pvCf8ydgYhA=", "requires": { "ethereumjs-abi": "git+https://github.com/ethereumjs/ethereumjs-abi.git#00ba8463a7f7a67fcad737ff9c2ebd95643427f7", "ethereumjs-util": "^5.1.1" - }, - "dependencies": { - "ethereumjs-abi": { - "version": "git+https://github.com/ethereumjs/ethereumjs-abi.git#00ba8463a7f7a67fcad737ff9c2ebd95643427f7", - "from": "git+https://github.com/ethereumjs/ethereumjs-abi.git", - "dev": true, - "requires": { - "bn.js": "^4.10.0", - "ethereumjs-util": "^5.0.0" - } - } + } + }, + "ethereumjs-abi": { + "version": "git+https://github.com/ethereumjs/ethereumjs-abi.git#00ba8463a7f7a67fcad737ff9c2ebd95643427f7", + "from": "git+https://github.com/ethereumjs/ethereumjs-abi.git", + "requires": { + "bn.js": "^4.10.0", + "ethereumjs-util": "^5.0.0" } }, "ethereumjs-util": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/ethereumjs-util/-/ethereumjs-util-5.2.0.tgz", "integrity": "sha512-CJAKdI0wgMbQFLlLRtZKGcy/L6pzVRgelIZqRqNbuVFM3K9VEnyfbcvz0ncWMRNCe4kaHWjwRYQcYMucmwsnWA==", - "dev": true, "requires": { "bn.js": "^4.11.0", "create-hash": "^1.1.2", @@ -8790,7 +8906,7 @@ "eth-block-tracker": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/eth-block-tracker/-/eth-block-tracker-1.1.3.tgz", - "integrity": "sha512-gDIknKCbY9npDA0JmBYCMDPLBj6GUe7xHYI2YTOQVuM8et6N2FxqrS1KhtThPWAeTgFPFkvyOj4eSBaJR0Oekg==", + "integrity": "sha1-xGoPK87ZtJuIx/ORiFbX7Ff73Ck=", "requires": { "async-eventemitter": "^0.2.2", "babelify": "^7.3.0", @@ -8987,9 +9103,9 @@ } }, "eth-tx-summary": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/eth-tx-summary/-/eth-tx-summary-3.2.1.tgz", - "integrity": "sha512-mu8g5tDkQxlFah58ggFhTzolE4OnYTj6j8SVsnGsiWT7WxN722RwnEsk/bco2foy+PLSEF2Mnoiw+wCqKoY72A==", + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/eth-tx-summary/-/eth-tx-summary-3.2.3.tgz", + "integrity": "sha512-1gZpA5fKarJOVSb5OUlPnhDQuIazqAkI61zlVvf5LdG47nEgw+/qhyZnuj3CUdE/TLTKuRzPLeyXLjaB4qWTRQ==", "requires": { "async": "^2.1.2", "bn.js": "^4.11.8", @@ -9000,30 +9116,54 @@ "ethereumjs-block": "^1.4.1", "ethereumjs-tx": "^1.1.1", "ethereumjs-util": "^5.0.1", - "ethereumjs-vm": "^2.3.4", + "ethereumjs-vm": "2.3.4", "through2": "^2.0.3", "treeify": "^1.0.1", "web3-provider-engine": "^13.3.2" }, "dependencies": { - "ethereumjs-util": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ethereumjs-util/-/ethereumjs-util-5.2.0.tgz", - "integrity": "sha512-CJAKdI0wgMbQFLlLRtZKGcy/L6pzVRgelIZqRqNbuVFM3K9VEnyfbcvz0ncWMRNCe4kaHWjwRYQcYMucmwsnWA==", + "eth-block-tracker": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/eth-block-tracker/-/eth-block-tracker-2.3.1.tgz", + "integrity": "sha512-NamWuMBIl8kmkJFVj8WzGatySTzQPQag4Xr677yFxdVtIxACFbL/dQowk0MzEqIKk93U1TwY3MjVU6mOcwZnKA==", "requires": { - "bn.js": "^4.11.0", - "create-hash": "^1.1.2", + "async-eventemitter": "github:ahultgren/async-eventemitter#fa06e39e56786ba541c180061dbf2c0a5bbf951c", + "eth-query": "^2.1.0", + "ethereumjs-tx": "^1.3.3", + "ethereumjs-util": "^5.1.3", "ethjs-util": "^0.1.3", - "keccak": "^1.0.2", - "rlp": "^2.0.0", - "safe-buffer": "^5.1.1", - "secp256k1": "^3.0.1" + "json-rpc-engine": "^3.6.0", + "pify": "^2.3.0", + "tape": "^4.6.3" + }, + "dependencies": { + "async-eventemitter": { + "version": "github:ahultgren/async-eventemitter#fa06e39e56786ba541c180061dbf2c0a5bbf951c", + "from": "github:ahultgren/async-eventemitter#fa06e39e56786ba541c180061dbf2c0a5bbf951c", + "requires": { + "async": "^2.4.0" + } + }, + "ethereumjs-util": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ethereumjs-util/-/ethereumjs-util-5.2.0.tgz", + "integrity": "sha512-CJAKdI0wgMbQFLlLRtZKGcy/L6pzVRgelIZqRqNbuVFM3K9VEnyfbcvz0ncWMRNCe4kaHWjwRYQcYMucmwsnWA==", + "requires": { + "bn.js": "^4.11.0", + "create-hash": "^1.1.2", + "ethjs-util": "^0.1.3", + "keccak": "^1.0.2", + "rlp": "^2.0.0", + "safe-buffer": "^5.1.1", + "secp256k1": "^3.0.1" + } + } } }, "ethereumjs-vm": { - "version": "2.3.5", - "resolved": "https://registry.npmjs.org/ethereumjs-vm/-/ethereumjs-vm-2.3.5.tgz", - "integrity": "sha512-AJ7x44+xqyE5+UO3Nns19WkTdZfyqFZ+sEjIEpvme7Ipbe3iBU1uwCcHEdiu/yY9bdhr3IfSa/NfIKNeXPaRVQ==", + "version": "2.3.4", + "resolved": "https://registry.npmjs.org/ethereumjs-vm/-/ethereumjs-vm-2.3.4.tgz", + "integrity": "sha512-Y4SlzNDqxrCO58jhp98HdnZVdjOqB+HC0hoU+N/DEp1aU+hFkRX/nru5F7/HkQRPIlA6aJlQp/xIA6xZs1kspw==", "requires": { "async": "^2.1.2", "async-eventemitter": "^0.2.2", @@ -9054,6 +9194,11 @@ } } }, + "pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=" + }, "web3-provider-engine": { "version": "13.8.0", "resolved": "https://registry.npmjs.org/web3-provider-engine/-/web3-provider-engine-13.8.0.tgz", @@ -9708,7 +9853,7 @@ "evp_bytestokey": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/evp_bytestokey/-/evp_bytestokey-1.0.3.tgz", - "integrity": "sha512-/f2Go4TognH/KvCISP7OUsHn85hT9nUkxxA9BEWxFn+Oj9o8ZNLm/40hdlgSLyuOimsrTKLUMEorQexp/aPQeA==", + "integrity": "sha1-f8vbGY3HGVlDLv4ThCaE4FJaywI=", "requires": { "md5.js": "^1.3.4", "safe-buffer": "^5.1.1" @@ -11236,15 +11381,25 @@ "dev": true }, "fs-extra": { - "version": "0.30.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-0.30.0.tgz", - "integrity": "sha1-8jP/zAjU2n1DLapEl3aYnbHfk/A=", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-6.0.1.tgz", + "integrity": "sha512-GnyIkKhhzXZUWFCaJzvyDLEEgDkPfb4/TPvJCJVuS8MWZgoSsErf++QpiAlDnKFcqhRlm+tIOcencCjyJE6ZCA==", + "dev": true, "requires": { "graceful-fs": "^4.1.2", - "jsonfile": "^2.1.0", - "klaw": "^1.0.0", - "path-is-absolute": "^1.0.0", - "rimraf": "^2.2.8" + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + }, + "dependencies": { + "jsonfile": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", + "integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=", + "dev": true, + "requires": { + "graceful-fs": "^4.1.6" + } + } } }, "fs-mkdirp-stream": { @@ -11853,7 +12008,7 @@ "function-bind": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" + "integrity": "sha1-pWiZ0+o8m6uHS7l3O3xe3pL0iV0=" }, "function.prototype.name": { "version": "1.1.0", @@ -12032,6 +12187,46 @@ "node-fetch": "2.1.2", "whatwg-fetch": "2.0.4" } + }, + "eth-json-rpc-middleware": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/eth-json-rpc-middleware/-/eth-json-rpc-middleware-1.6.0.tgz", + "integrity": "sha512-tDVCTlrUvdqHKqivYMjtFZsdD7TtpNLBCfKAcOpaVs7orBMS/A8HWro6dIzNtTZIR05FAbJ3bioFOnZpuCew9Q==", + "dev": true, + "requires": { + "async": "^2.5.0", + "eth-query": "^2.1.2", + "eth-tx-summary": "^3.1.2", + "ethereumjs-block": "^1.6.0", + "ethereumjs-tx": "^1.3.3", + "ethereumjs-util": "^5.1.2", + "ethereumjs-vm": "^2.1.0", + "fetch-ponyfill": "^4.0.0", + "json-rpc-engine": "^3.6.0", + "json-rpc-error": "^2.0.0", + "json-stable-stringify": "^1.0.1", + "promise-to-callback": "^1.0.0", + "tape": "^4.6.3" + } + }, + "ethereum-common": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/ethereum-common/-/ethereum-common-0.2.0.tgz", + "integrity": "sha512-XOnAR/3rntJgbCdGhqdaLIxDLWKLmsZOGhHdBKadEr6gEnJLH52k93Ou+TUdFaPN3hJc3isBZBal3U/XZ15abA==", + "dev": true + }, + "ethereumjs-block": { + "version": "1.7.1", + "resolved": "https://registry.npmjs.org/ethereumjs-block/-/ethereumjs-block-1.7.1.tgz", + "integrity": "sha512-B+sSdtqm78fmKkBq78/QLKJbu/4Ts4P2KFISdgcuZUPDm9x+N7qgBPIIFUGbaakQh8bzuquiRVbdmvPKqbILRg==", + "dev": true, + "requires": { + "async": "^2.0.1", + "ethereum-common": "0.2.0", + "ethereumjs-tx": "^1.2.2", + "ethereumjs-util": "^5.0.0", + "merkle-patricia-tree": "^2.1.2" + } } } }, @@ -12167,6 +12362,19 @@ "yargs": "^4.7.1" }, "dependencies": { + "fs-extra": { + "version": "0.30.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-0.30.0.tgz", + "integrity": "sha1-8jP/zAjU2n1DLapEl3aYnbHfk/A=", + "dev": true, + "requires": { + "graceful-fs": "^4.1.2", + "jsonfile": "^2.1.0", + "klaw": "^1.0.0", + "path-is-absolute": "^1.0.0", + "rimraf": "^2.2.8" + } + }, "yargs": { "version": "4.8.1", "resolved": "https://registry.npmjs.org/yargs/-/yargs-4.8.1.tgz", @@ -12872,7 +13080,7 @@ "globals": { "version": "9.18.0", "resolved": "https://registry.npmjs.org/globals/-/globals-9.18.0.tgz", - "integrity": "sha512-S0nG3CLEQiY/ILxqtztTWH/3iRRdyBLw6KMDxnKMchrtbj2OFmehVh0WUCfW3DUrIgx/qFrJPICrq4Z4sTR9UQ==" + "integrity": "sha1-qjiWs+abSH8X4x7SFD1pqOMMLYo=" }, "globby": { "version": "5.0.0", @@ -13001,13 +13209,13 @@ "dev": true }, "gulp": { - "version": "github:gulpjs/gulp#71c094a51c7972d26f557899ddecab0210ef3776", + "version": "github:gulpjs/gulp#6d71a658c61edb3090221579d8f97dbe086ba2ed", "from": "github:gulpjs/gulp#4.0", "requires": { - "glob-watcher": "^4.0.0", - "gulp-cli": "^2.0.0", + "glob-watcher": "^3.0.0", + "gulp-cli": "^1.0.0", "undertaker": "^1.0.0", - "vinyl-fs": "^3.0.0" + "vinyl-fs": "^2.0.0" }, "dependencies": { "gulp-cli": { @@ -13224,7 +13432,7 @@ "gulp-eslint": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/gulp-eslint/-/gulp-eslint-4.0.0.tgz", - "integrity": "sha512-+qsePo04v1O3JshpNvww9+bOgZEJ6Cc2/w3mEktfKz0NL0zsh1SWzjyIL2FIM2zzy6IYQYv+j8REZORF8dKX4g==", + "integrity": "sha1-FtnqTWlue3qdZe6xqlvEugoix/c=", "requires": { "eslint": "^4.0.0", "gulp-util": "^3.0.8" @@ -14537,7 +14745,7 @@ "hash.js": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.3.tgz", - "integrity": "sha512-/UETyP0W22QILqS+6HowevwhEFJ3MBJnwTf75Qob9Wz9t0DPuisL8kW8YZMK62dHAKE1c1p+gY1TtOLY+USEHA==", + "integrity": "sha1-NA3tvmKQGHFRweodd3o0SJNd+EY=", "requires": { "inherits": "^2.0.3", "minimalistic-assert": "^1.0.0" @@ -15193,7 +15401,7 @@ "idb-global": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/idb-global/-/idb-global-2.1.0.tgz", - "integrity": "sha512-tJPsvisI6A1xQ6y+orXavjgm/7O6v0YT4wKfw8rwv635pIhsc1Wi2ZhcS+6nYmpyyeaTBC/xG0MWcD9iwCD3xg==", + "integrity": "sha1-Kj4J0e2d86g21ZruqZv3QEe4zI0=", "requires": { "obs-store": "^2.4.1" }, @@ -15224,7 +15432,7 @@ "identicon.js": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/identicon.js/-/identicon.js-2.3.1.tgz", - "integrity": "sha512-PsxOTpq2Mwj2dgpHW50vcBdSebozcL9xKLIqRVkh2c4lqbCB75pkpdDKoKkVtTfpha/rl4BubXm3Q90vxlmUxQ==" + "integrity": "sha1-Dxag3V5h4aiWmUAMwZKvREVQbls=" }, "idna-uts46": { "version": "1.1.0", @@ -15966,7 +16174,7 @@ "is-plain-object": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", - "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", + "integrity": "sha1-LBY7P6+xtgbZ0Xko8FwqHDjgdnc=", "requires": { "isobject": "^3.0.1" }, @@ -16592,7 +16800,7 @@ "escodegen": { "version": "1.9.0", "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.9.0.tgz", - "integrity": "sha512-v0MYvNQ32bzwoG2OSFzWAkuahDQHK92JBN0pTAALJ4RIxEZe766QJPDR8Hqy7XNUy5K3fnVL76OqYAdc4TZEIw==", + "integrity": "sha1-mBGi8mXcHNOJRCDuNxcGS2MriFI=", "dev": true, "requires": { "esprima": "^3.1.3", @@ -16742,13 +16950,14 @@ "dev": true }, "json-rpc-engine": { - "version": "3.6.1", - "resolved": "https://registry.npmjs.org/json-rpc-engine/-/json-rpc-engine-3.6.1.tgz", - "integrity": "sha512-xYuD9M1pcld5OKPzVAoEG5MKtnR8iKMyNzRpeS3/mCJ7dcAcS67vqfOmYLoaIQfVRU5uClThbjri3VFR0vEwYg==", + "version": "3.7.3", + "resolved": "https://registry.npmjs.org/json-rpc-engine/-/json-rpc-engine-3.7.3.tgz", + "integrity": "sha512-+FO3UWu/wafh/+MZ6BXy0HZU+f5plwUn82FgxpC0scJkEh5snOjFrAAtqCITPDfvfLHRUFOG5pQDUx2pspfERQ==", "requires": { "async": "^2.0.1", "babel-preset-env": "^1.3.2", "babelify": "^7.3.0", + "clone": "^2.1.1", "json-rpc-error": "^2.0.0", "promise-to-callback": "^1.0.0" }, @@ -16775,7 +16984,7 @@ "json-rpc-middleware-stream": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/json-rpc-middleware-stream/-/json-rpc-middleware-stream-1.0.1.tgz", - "integrity": "sha512-IR6cOO6B21NdLpiYblueB3O+g3UAYLIZd6ZgZfddVPl0z6vSECcpuiYnV5MmIMJY3D0fLYpJqOxYaEmLYQqTtA==", + "integrity": "sha1-ybigBcgK8y5t+LuI5r3RMASEpO0=", "requires": { "end-of-stream": "^1.4.0", "eth-block-tracker": "^2.1.2", @@ -16784,11 +16993,47 @@ "readable-stream": "^2.3.3" }, "dependencies": { + "async-eventemitter": { + "version": "github:ahultgren/async-eventemitter#fa06e39e56786ba541c180061dbf2c0a5bbf951c", + "from": "github:ahultgren/async-eventemitter#fa06e39e56786ba541c180061dbf2c0a5bbf951c", + "requires": { + "async": "^2.4.0" + } + }, "bn.js": { "version": "4.11.6", "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.6.tgz", "integrity": "sha1-UzRK2xRhehP26N0s4okF0cC6MhU=" }, + "eth-block-tracker": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/eth-block-tracker/-/eth-block-tracker-2.3.1.tgz", + "integrity": "sha512-NamWuMBIl8kmkJFVj8WzGatySTzQPQag4Xr677yFxdVtIxACFbL/dQowk0MzEqIKk93U1TwY3MjVU6mOcwZnKA==", + "requires": { + "async-eventemitter": "github:ahultgren/async-eventemitter#fa06e39e56786ba541c180061dbf2c0a5bbf951c", + "eth-query": "^2.1.0", + "ethereumjs-tx": "^1.3.3", + "ethereumjs-util": "^5.1.3", + "ethjs-util": "^0.1.3", + "json-rpc-engine": "^3.6.0", + "pify": "^2.3.0", + "tape": "^4.6.3" + } + }, + "ethereumjs-util": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ethereumjs-util/-/ethereumjs-util-5.2.0.tgz", + "integrity": "sha512-CJAKdI0wgMbQFLlLRtZKGcy/L6pzVRgelIZqRqNbuVFM3K9VEnyfbcvz0ncWMRNCe4kaHWjwRYQcYMucmwsnWA==", + "requires": { + "bn.js": "^4.11.0", + "create-hash": "^1.1.2", + "ethjs-util": "^0.1.3", + "keccak": "^1.0.2", + "rlp": "^2.0.0", + "safe-buffer": "^5.1.1", + "secp256k1": "^3.0.1" + } + }, "ethjs-format": { "version": "0.2.2", "resolved": "https://registry.npmjs.org/ethjs-format/-/ethjs-format-0.2.2.tgz", @@ -16824,6 +17069,11 @@ "is-hex-prefixed": "1.0.0", "strip-hex-prefix": "1.0.0" } + }, + "pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=" } } }, @@ -17592,7 +17842,7 @@ "karma-chrome-launcher": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/karma-chrome-launcher/-/karma-chrome-launcher-2.2.0.tgz", - "integrity": "sha512-uf/ZVpAabDBPvdPdveyk1EPgbnloPvFFGgmRhYLTDH7gEB4nZdSBk8yTU47w1g/drLSx5uMOkjKk7IWKfWg/+w==", + "integrity": "sha1-zxudBxNswY/iOTJ9JGVMPbw2is8=", "dev": true, "requires": { "fs-access": "^1.0.0", @@ -19914,7 +20164,7 @@ "minimatch": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", - "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "integrity": "sha1-UWbihkV/AzBgZL5Ul+jbsMPTIIM=", "requires": { "brace-expansion": "^1.1.7" } @@ -20063,7 +20313,7 @@ "debug": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", - "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "integrity": "sha1-W7WgZyYotkFJVmuhaBnmFRjGcmE=", "dev": true, "requires": { "ms": "2.0.0" @@ -20072,7 +20322,7 @@ "supports-color": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-4.4.0.tgz", - "integrity": "sha512-rKC3+DyXWgK0ZLKwmRsrkyHVZAjNkfzeehuFWdGGcqGDTZFH73+RH6S/RDAAxl9GusSjZSUWYLmT9N5pzXFOXQ==", + "integrity": "sha1-iD992rwWUUKyphQn8zUt7RldGj4=", "dev": true, "requires": { "has-flag": "^2.0.0" @@ -20083,7 +20333,7 @@ "mocha-eslint": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/mocha-eslint/-/mocha-eslint-4.1.0.tgz", - "integrity": "sha512-y+TIaoozAiuksnsr/7GVw7F2nAqotrZ06SHIw8wMR6PVWipXre5Hz59bsqLX1n2Lqu2YDebUX1A4qF/rtmWsYQ==", + "integrity": "sha1-0I66mGZffOTr7w0nw6I1QJ67uK0=", "dev": true, "requires": { "chalk": "^1.1.0", @@ -20901,7 +21151,7 @@ "npmlog": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-4.1.2.tgz", - "integrity": "sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==", + "integrity": "sha1-CKfyqL9zRgR3mp76StXMcXq7lUs=", "requires": { "are-we-there-yet": "~1.1.2", "console-control-strings": "~1.1.0", @@ -23190,7 +23440,7 @@ "pbkdf2": { "version": "3.0.14", "resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.0.14.tgz", - "integrity": "sha512-gjsZW9O34fm0R7PaLHRJmLLVfSoesxztjPjE9o6R+qtVJij90ltg1joIovN9GKrRW3t1PzhDDG3UMEMFfZ+1wA==", + "integrity": "sha1-o14TxkeZsGzhUyD0WcIw5o5zut4=", "requires": { "create-hash": "^1.1.2", "create-hmac": "^1.1.4", @@ -24955,7 +25205,8 @@ "precond": { "version": "0.2.3", "resolved": "https://registry.npmjs.org/precond/-/precond-0.2.3.tgz", - "integrity": "sha1-qpWRvKokkj8eD0hJ0kD0fvwQdaw=" + "integrity": "sha1-qpWRvKokkj8eD0hJ0kD0fvwQdaw=", + "dev": true }, "prelude-ls": { "version": "1.1.2", @@ -25023,7 +25274,7 @@ "private": { "version": "0.1.8", "resolved": "https://registry.npmjs.org/private/-/private-0.1.8.tgz", - "integrity": "sha512-VvivMrbvd2nKkiG38qjULzlc+4Vx4wm/whI9pQD35YrARNnhxeiRktSOhSukRLFNlzg6Br/cJPet5J/u19r/mg==" + "integrity": "sha1-I4Hts2ifelPWUxkAYPz4ItLzaP8=" }, "process": { "version": "0.5.2", @@ -25043,7 +25294,7 @@ "promise": { "version": "7.3.1", "resolved": "https://registry.npmjs.org/promise/-/promise-7.3.1.tgz", - "integrity": "sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg==", + "integrity": "sha1-BktyYCsY+Q8pGSuLG8QY/9Hr078=", "requires": { "asap": "~2.0.3" } @@ -25356,7 +25607,7 @@ "qs": { "version": "6.5.1", "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.1.tgz", - "integrity": "sha512-eRzhrN1WSINYCDCbrz796z37LOe3m5tmW7RQf6oBntukAG1nmovJvhnwHHRMAfeoItc1m2Hk02WER2aQ/iqs+A==" + "integrity": "sha1-NJzfbu+J7EXBLX1es/wMhwNDptg=" }, "query-string": { "version": "5.1.1", @@ -25616,7 +25867,7 @@ "randombytes": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.0.5.tgz", - "integrity": "sha512-8T7Zn1AhMsQ/HI1SjcCfT/t4ii3eAqco3yOcSzS4mozsOz69lHLsoMXmF9nZgnFanYscnSlUSgs8uZyKzpE6kg==", + "integrity": "sha1-3ACaJGuNCaF3tLegrne8Vw9LG3k=", "requires": { "safe-buffer": "^5.1.0" } @@ -25717,7 +25968,7 @@ "react-transition-group": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-1.2.1.tgz", - "integrity": "sha512-CWaL3laCmgAFdxdKbhhps+c0HRGF4c+hdM4H23+FI1QBNUyx/AMeIJGWorehPNSaKnQNOAxL7PQmqMu78CDj3Q==", + "integrity": "sha1-4R9yslf5IbITIpp3TfRmEjRsfKY=", "requires": { "chain-function": "^1.0.0", "dom-helpers": "^3.2.0", @@ -26020,7 +26271,7 @@ "react-redux": { "version": "5.0.6", "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-5.0.6.tgz", - "integrity": "sha512-8taaaGu+J7PMJQDJrk/xiWEYQmdo3mkXw6wPr3K3LxvXis3Fymiq7c13S+Tpls/AyNUAsoONkU81AP0RA6y6Vw==", + "integrity": "sha1-I+06T5hjWdaLUhLqqmgeYNZXSUY=", "requires": { "hoist-non-react-statics": "^2.2.1", "invariant": "^2.0.0", @@ -26307,7 +26558,7 @@ "readable-stream": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.3.tgz", - "integrity": "sha512-m+qzzcn7KUxEmd1gMbchF+Y2eIUbieUaxkWtptyHywrX0rE8QEYqPC07Vuy4Wm32/xE16NcdBctb8S0Xe/5IeQ==", + "integrity": "sha1-No8lEtefnUb9/HE0mueHi7weuVw=", "requires": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", @@ -26398,7 +26649,7 @@ "recompose": { "version": "0.25.1", "resolved": "https://registry.npmjs.org/recompose/-/recompose-0.25.1.tgz", - "integrity": "sha512-EwFAv6UBrHbLIsIKHUZJ+BKdjTmyEsIrRlGO3R7PKu0S7hkgNznVDRvb+1upQUntURtBvxhYnTVQ3AcWOlsmWA==", + "integrity": "sha1-XrnWz24lqf+tc8u65WWLW1XW5yg=", "requires": { "change-emitter": "^0.1.2", "fbjs": "^0.8.1", @@ -26496,7 +26747,7 @@ "redux": { "version": "3.7.2", "resolved": "https://registry.npmjs.org/redux/-/redux-3.7.2.tgz", - "integrity": "sha512-pNqnf9q1hI5HHZRBkj3bAngGZW/JMCmexDlOxw4XagXY2o1327nHH54LoTjiPJ0gizoqPDRqWyX/00g0hD6w+A==", + "integrity": "sha1-BrcxIyFZAdJdBlvjQusCa8HIU3s=", "requires": { "lodash": "^4.2.1", "lodash-es": "^4.2.1", @@ -26535,7 +26786,7 @@ "regenerate": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.3.3.tgz", - "integrity": "sha512-jVpo1GadrDAK59t/0jRx5VxYWQEDkkEKi6+HjE3joFVLfDOh9Xrdh0dF1eSq+BI/SwvTQ44gSscJ8N5zYL61sg==" + "integrity": "sha1-DDNtOYBVPXVcObWGrjsgqknIK38=" }, "regenerator-runtime": { "version": "0.11.1", @@ -26545,7 +26796,7 @@ "regenerator-transform": { "version": "0.10.1", "resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.10.1.tgz", - "integrity": "sha512-PJepbvDbuK1xgIgnau7Y90cwaAmO/LCLMI2mPvaXq2heGMR3aWW5/BQvYrhJ8jgmQjXewXvBjzfqKcVOmhjZ6Q==", + "integrity": "sha1-HkmWg3Ix2ot/PPQRTXG1aRoGgN0=", "requires": { "babel-runtime": "^6.18.0", "babel-types": "^6.19.0", @@ -26555,7 +26806,7 @@ "regex-cache": { "version": "0.4.4", "resolved": "https://registry.npmjs.org/regex-cache/-/regex-cache-0.4.4.tgz", - "integrity": "sha512-nVIZwtCjkC9YgvWkpM55B5rBhBYRZhAaJbgcFYXXsHnbZ9UZI9nnVWYZpBlCqv9ho2eZryPnWrZGsOdPwVWXWQ==", + "integrity": "sha1-db3FiioUls7EihKDW8VMjVYjNt0=", "requires": { "is-equal-shallow": "^0.1.3" } @@ -27220,7 +27471,7 @@ "safe-buffer": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz", - "integrity": "sha512-kKvNJn6Mm93gAczWVJg7wH+wGYWNrDHdWvpUmHyEsgCtIwwo3bqPtV4tR5tuPaUhTOo/kvhVwd8XwwOllGYkbg==" + "integrity": "sha1-iTMSr2myEj3vcfV4iQAWce6yyFM=" }, "safe-regex": { "version": "1.1.0", @@ -27550,12 +27801,12 @@ "semaphore": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/semaphore/-/semaphore-1.1.0.tgz", - "integrity": "sha512-O4OZEaNtkMd/K0i6js9SL+gqy0ZCBMgUvlSqHKi4IBdjhe7wB8pwztUk1BbZ1fmrvpwFrPbHzqd2w5pTcJH6LA==" + "integrity": "sha1-qq2LhrIP6OmzKxbcLuaCqM0mqKo=" }, "semver": { "version": "5.4.1", "resolved": "https://registry.npmjs.org/semver/-/semver-5.4.1.tgz", - "integrity": "sha512-WfG/X9+oATh81XtllIo/I8gOiY9EXRdv1cQdyykeXK17YcUW3EXUAi2To4pcH6nZtJPr7ZOpM5OMyWJZm+8Rsg==" + "integrity": "sha1-4FnAnYVx8FQII3M0M1BdOi8AsY4=" }, "semver-diff": { "version": "2.1.0", @@ -27678,7 +27929,7 @@ "sha.js": { "version": "2.4.9", "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.9.tgz", - "integrity": "sha512-G8zektVqbiPHrylgew9Zg1VRB1L/DtXNUVAM6q4QLy8NE3qtHlFXTf8VLL4k1Yl6c7NMjtZUTdXV+X44nFaT6A==", + "integrity": "sha1-mPZIgEdLdPSji42p08Dy0QRjPn0=", "requires": { "inherits": "^2.0.1", "safe-buffer": "^5.0.1" @@ -28287,9 +28538,9 @@ } }, "solc": { - "version": "0.4.23", - "resolved": "https://registry.npmjs.org/solc/-/solc-0.4.23.tgz", - "integrity": "sha512-AT7anLHY6uIRg2It6N0UlCHeZ7YeecIkUhnlirrCgCPCUevtnoN48BxvgigN/4jJTRljv5oFhAJtI6gvHzT5DQ==", + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/solc/-/solc-0.4.24.tgz", + "integrity": "sha512-2xd7Cf1HeVwrIb6Bu1cwY2/TaLRodrppCq3l7rhLimFQgmxptXhTC3+/wesVLpB09F1A2kZgvbMOgH7wvhFnBQ==", "requires": { "fs-extra": "^0.30.0", "memorystream": "^0.3.1", @@ -28298,6 +28549,18 @@ "yargs": "^4.7.1" }, "dependencies": { + "fs-extra": { + "version": "0.30.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-0.30.0.tgz", + "integrity": "sha1-8jP/zAjU2n1DLapEl3aYnbHfk/A=", + "requires": { + "graceful-fs": "^4.1.2", + "jsonfile": "^2.1.0", + "klaw": "^1.0.0", + "path-is-absolute": "^1.0.0", + "rimraf": "^2.2.8" + } + }, "yargs": { "version": "4.8.1", "resolved": "https://registry.npmjs.org/yargs/-/yargs-4.8.1.tgz", @@ -28742,7 +29005,7 @@ "stream-exhaust": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/stream-exhaust/-/stream-exhaust-1.0.2.tgz", - "integrity": "sha512-b/qaq/GlBK5xaq1yrK9/zFcyRSTNxmcZwFLGSTG0mXgZl/4Z6GgiyYOXOvY7N3eEvFRAG1bkDRz5EPGSvPYQlw==" + "integrity": "sha1-rNrI2lnvK8HheiwMz2wyDRIOVV0=" }, "stream-http": { "version": "2.7.2", @@ -29608,6 +29871,18 @@ "resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz", "integrity": "sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo=" }, + "fs-extra": { + "version": "0.30.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-0.30.0.tgz", + "integrity": "sha1-8jP/zAjU2n1DLapEl3aYnbHfk/A=", + "requires": { + "graceful-fs": "^4.1.2", + "jsonfile": "^2.1.0", + "klaw": "^1.0.0", + "path-is-absolute": "^1.0.0", + "rimraf": "^2.2.8" + } + }, "isarray": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", @@ -29739,6 +30014,11 @@ } } }, + "swappable-obj-proxy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/swappable-obj-proxy/-/swappable-obj-proxy-1.0.2.tgz", + "integrity": "sha512-IDrfIgZr09yK9j8XSoeHACf9IaM03izjIiNBq7lZrXQYr2eXwjcRXJUcUmkOkTs3QrXigAGbVgaq86hsRH9DAg==" + }, "swarm-js": { "version": "0.1.37", "resolved": "https://registry.npmjs.org/swarm-js/-/swarm-js-0.1.37.tgz", @@ -29954,7 +30234,7 @@ "tape": { "version": "4.8.0", "resolved": "https://registry.npmjs.org/tape/-/tape-4.8.0.tgz", - "integrity": "sha512-TWILfEnvO7I8mFe35d98F6T5fbLaEtbFTG/lxWvid8qDfFTxt19EBijWmB4j3+Hoh5TfHE2faWs73ua+EphuBA==", + "integrity": "sha1-9qn+xBzFCh3lD6M2A6tYCZH2Bo4=", "requires": { "deep-equal": "~1.0.1", "defined": "~1.0.0", @@ -30724,7 +31004,7 @@ "ua-parser-js": { "version": "0.7.17", "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.17.tgz", - "integrity": "sha512-uRdSdu1oA1rncCQL7sCj8vSyZkgtL7faaw9Tc9rZ3mGgraQ7+Pdx7w5mnOSF3gw9ZNG6oc+KXfkon3bKuROm0g==" + "integrity": "sha1-6exflJi57JEOeuOsYmqAXE0J7Kw=" }, "uglify-js": { "version": "2.8.29", @@ -32082,107 +32362,6 @@ "web3-utils": "1.0.0-beta.34" } }, - "web3-provider-engine": { - "version": "14.0.5", - "resolved": "https://registry.npmjs.org/web3-provider-engine/-/web3-provider-engine-14.0.5.tgz", - "integrity": "sha512-1W/ue7VOwOMnmKgMY3HCpbixi6qhfl4r1dK8W597AwJLbrQ+twJKwWlFAedDpJjCc9MwRCCB3pyexW4HJVSiBg==", - "requires": { - "async": "^2.5.0", - "backoff": "^2.5.0", - "clone": "^2.0.0", - "cross-fetch": "^2.1.0", - "eth-block-tracker": "^3.0.0", - "eth-json-rpc-infura": "^3.1.0", - "eth-sig-util": "^1.4.2", - "ethereumjs-block": "^1.2.2", - "ethereumjs-tx": "^1.2.0", - "ethereumjs-util": "^5.1.5", - "ethereumjs-vm": "^2.3.4", - "json-rpc-error": "^2.0.0", - "json-stable-stringify": "^1.0.1", - "promise-to-callback": "^1.0.0", - "readable-stream": "^2.2.9", - "request": "^2.67.0", - "semaphore": "^1.0.3", - "tape": "^4.4.0", - "ws": "^5.1.1", - "xhr": "^2.2.0", - "xtend": "^4.0.1" - }, - "dependencies": { - "eth-block-tracker": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/eth-block-tracker/-/eth-block-tracker-3.0.0.tgz", - "integrity": "sha512-Lhhu/+1GOeekMRDRhUcM7VSJRmX279DByrwzEbmG0JL1tcT3xRo6GLNXnidyJ7ahHJm+0JFhw/RqtTeIxagQwA==", - "requires": { - "eth-query": "^2.1.0", - "ethereumjs-tx": "^1.3.3", - "ethereumjs-util": "^5.1.3", - "ethjs-util": "^0.1.3", - "json-rpc-engine": "^3.6.0", - "pify": "^2.3.0", - "tape": "^4.6.3" - } - }, - "eth-json-rpc-infura": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/eth-json-rpc-infura/-/eth-json-rpc-infura-3.1.0.tgz", - "integrity": "sha512-uMYkEP6fga8CyNo8TMoA/7cxi6bL3V8pTvjKQikOi9iYl6/AO5xlfgniyAMElSiq2mmXz3lYa/9VYDMzt/J5aA==", - "requires": { - "cross-fetch": "^2.1.0", - "eth-json-rpc-middleware": "^1.5.0", - "json-rpc-engine": "^3.4.0", - "json-rpc-error": "^2.0.0", - "tape": "^4.8.0" - } - }, - "ethereumjs-util": { - "version": "5.1.5", - "resolved": "https://registry.npmjs.org/ethereumjs-util/-/ethereumjs-util-5.1.5.tgz", - "integrity": "sha512-xPaSEATYJpMTCGowIt0oMZwFP4R1bxd6QsWgkcDvFL0JtXsr39p32WEcD14RscCjfP41YXZPCVWA4yAg0nrJmw==", - "requires": { - "bn.js": "^4.11.0", - "create-hash": "^1.1.2", - "ethjs-util": "^0.1.3", - "keccak": "^1.0.2", - "rlp": "^2.0.0", - "safe-buffer": "^5.1.1", - "secp256k1": "^3.0.1" - } - }, - "ethereumjs-vm": { - "version": "2.3.5", - "resolved": "https://registry.npmjs.org/ethereumjs-vm/-/ethereumjs-vm-2.3.5.tgz", - "integrity": "sha512-AJ7x44+xqyE5+UO3Nns19WkTdZfyqFZ+sEjIEpvme7Ipbe3iBU1uwCcHEdiu/yY9bdhr3IfSa/NfIKNeXPaRVQ==", - "requires": { - "async": "^2.1.2", - "async-eventemitter": "^0.2.2", - "ethereum-common": "0.2.0", - "ethereumjs-account": "^2.0.3", - "ethereumjs-block": "~1.7.0", - "ethereumjs-util": "^5.1.3", - "fake-merkle-patricia-tree": "^1.0.1", - "functional-red-black-tree": "^1.0.1", - "merkle-patricia-tree": "^2.1.2", - "rustbn.js": "~0.1.1", - "safe-buffer": "^5.1.1" - } - }, - "pify": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", - "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=" - }, - "ws": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/ws/-/ws-5.1.1.tgz", - "integrity": "sha512-bOusvpCb09TOBLbpMKszd45WKC2KPtxiyiHanv+H2DE3Az+1db5a/L7sVJZVDPUC1Br8f0SKRr1KjLpD1U/IAw==", - "requires": { - "async-limiter": "~1.0.0" - } - } - } - }, "web3-providers-http": { "version": "1.0.0-beta.34", "resolved": "https://registry.npmjs.org/web3-providers-http/-/web3-providers-http-1.0.0-beta.34.tgz", @@ -33299,7 +33478,7 @@ "which": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/which/-/which-1.3.0.tgz", - "integrity": "sha512-xcJpopdamTuY5duC/KnTTNBraPK54YwpenP4lzxU8H91GudWpFv38u0CKjclE1Wi2EH2EDz5LRcHcKbCIzqGyg==", + "integrity": "sha1-/wS9/AEO5UfXgL7DjhrBwnd9JTo=", "requires": { "isexe": "^2.0.0" } @@ -33312,7 +33491,7 @@ "wide-align": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.2.tgz", - "integrity": "sha512-ijDLlyQ7s6x1JgCLur53osjm/UXUYD9+0PbYKrBsYisYXzCxN+HC3mYDNy/dWdmf3AwqwU3CXwDCvsNgGK1S0w==", + "integrity": "sha1-Vx4PGwYEY268DfwhsDObvjE0FxA=", "requires": { "string-width": "^1.0.2" } diff --git a/package.json b/package.json index 96b0829db..d7f126cb4 100644 --- a/package.json +++ b/package.json @@ -36,7 +36,7 @@ "test:mascara:build:locales": "mkdirp dist/chrome && cp -R app/_locales dist/chrome/_locales", "test:mascara:build:background": "browserify mascara/src/background.js -o dist/mascara/background.js", "test:mascara:build:tests": "browserify test/integration/lib/first-time.js -o dist/mascara/tests.js", - "ganache:start": "ganache-cli -m 'phrase upgrade clock rough situate wedding elder clever doctor stamp excess tent'", + "ganache:start": "ganache-cli --noVMErrorsOnRPCResponse -m 'phrase upgrade clock rough situate wedding elder clever doctor stamp excess tent'", "sentry:publish": "node ./development/sentry-publish.js", "lint": "eslint .", "lint:fix": "eslint . --fix", @@ -103,10 +103,13 @@ "ensnare": "^1.0.0", "eslint-plugin-react": "^7.4.0", "eth-bin-to-ops": "^1.0.1", + "eth-block-tracker": "^4.0.1", "eth-contract-metadata": "github:MetaMask/eth-contract-metadata#master", + "eth-json-rpc-filters": "^2.1.1", + "eth-json-rpc-middleware": "^2.4.0", + "eth-keyring-controller": "^3.1.4", "eth-ens-namehash": "^2.0.8", "eth-hd-keyring": "^2.0.0", - "eth-json-rpc-filters": "^1.2.6", "eth-json-rpc-infura": "^3.0.0", "eth-method-registry": "^1.0.0", "eth-phishing-detect": "^1.1.4", @@ -143,7 +146,7 @@ "iframe-stream": "^3.0.0", "inject-css": "^0.1.1", "jazzicon": "^1.2.0", - "json-rpc-engine": "^3.6.1", + "json-rpc-engine": "^3.7.3", "json-rpc-middleware-stream": "^1.0.1", "lodash.debounce": "^4.0.8", "lodash.memoize": "^4.1.2", @@ -201,11 +204,11 @@ "superstatic": "^5.0.2", "sw-controller": "^1.0.3", "sw-stream": "^2.0.2", + "swappable-obj-proxy": "^1.0.2", "textarea-caret": "^3.0.1", "valid-url": "^1.0.9", "vreme": "^3.0.2", "web3": "^0.20.1", - "web3-provider-engine": "^14.0.5", "web3-stream-provider": "^3.0.1", "xtend": "^4.0.1" }, @@ -247,6 +250,7 @@ "eth-json-rpc-middleware": "^1.6.0", "eth-keyring-controller": "^4.0.0", "file-loader": "^1.1.11", + "fs-extra": "^6.0.1", "fs-promise": "^2.0.3", "ganache-cli": "^6.1.0", "ganache-core": "^2.1.5", @@ -291,6 +295,7 @@ "open": "0.0.5", "path": "^0.12.7", "png-file-stream": "^1.0.0", + "prepend-file": "^1.3.1", "prompt": "^1.0.0", "proxyquire": "2.0.1", "qs": "^6.2.0", diff --git a/test/e2e/beta/from-import-beta-ui.spec.js b/test/e2e/beta/from-import-beta-ui.spec.js index 06e669e5b..532bc1ef1 100644 --- a/test/e2e/beta/from-import-beta-ui.spec.js +++ b/test/e2e/beta/from-import-beta-ui.spec.js @@ -4,11 +4,9 @@ const webdriver = require('selenium-webdriver') const { By, Key, until } = webdriver const { delay, - buildChromeWebDriver, - buildFirefoxWebdriver, - installWebExt, - getExtensionIdChrome, - getExtensionIdFirefox, + createModifiedTestBuild, + setupBrowserAndExtension, + verboseReportOnFailure, } = require('../func') const { checkBrowserForConsoleErrors, @@ -21,8 +19,9 @@ const { describe('Using MetaMask with an existing account', function () { - let extensionId + const browser = process.env.SELENIUM_BROWSER let driver + let extensionUri const testSeedPhrase = 'phrase upgrade clock rough situate wedding elder clever doctor stamp excess tent' const testAddress = '0xE18035BF8712672935FDB4e5e431b1a0183d2DFC' @@ -30,35 +29,24 @@ describe('Using MetaMask with an existing account', function () { const tinyDelayMs = 500 const regularDelayMs = 1000 const largeDelayMs = regularDelayMs * 2 + const waitingNewPageDelayMs = regularDelayMs * 10 this.timeout(0) this.bail(true) before(async function () { - switch (process.env.SELENIUM_BROWSER) { - case 'chrome': { - const extensionPath = path.resolve('dist/chrome') - driver = buildChromeWebDriver(extensionPath) - extensionId = await getExtensionIdChrome(driver) - await driver.get(`chrome-extension://${extensionId}/popup.html`) - await delay(regularDelayMs) - break - } - case 'firefox': { - const extensionPath = path.resolve('dist/firefox') - driver = buildFirefoxWebdriver() - await installWebExt(driver, extensionPath) - await delay(regularDelayMs) - extensionId = await getExtensionIdFirefox(driver) - await driver.get(`moz-extension://${extensionId}/popup.html`) - await delay(regularDelayMs) - break - } - } + const srcPath = path.resolve(`dist/${browser}`) + const { extPath } = await createModifiedTestBuild({ browser, srcPath }) + const installResult = await setupBrowserAndExtension({ browser, extPath }) + driver = installResult.driver + extensionUri = installResult.extensionUri + + await driver.get(extensionUri) + await delay(300) }) afterEach(async function () { - if (process.env.SELENIUM_BROWSER === 'chrome') { + if (browser === 'chrome') { const errors = await checkBrowserForConsoleErrors(driver) if (errors.length) { const errorReports = errors.map(err => err.message) @@ -67,7 +55,7 @@ describe('Using MetaMask with an existing account', function () { } } if (this.currentTest.state === 'failed') { - await verboseReportOnFailure(driver, this.currentTest) + await verboseReportOnFailure({ browser, driver, title: this.currentTest.title }) } }) @@ -316,22 +304,109 @@ describe('Using MetaMask with an existing account', function () { }) }) - describe('Imports an account with private key', () => { - it('choose Create Account from the account menu', async () => { - await driver.findElement(By.css('.account-menu__icon')).click() + describe('Send ETH from Faucet', () => { + it('starts a send transaction inside Faucet', async () => { + await driver.executeScript('window.open("https://faucet.metamask.io")') + await delay(waitingNewPageDelayMs) + + const [extension, faucet] = await driver.getAllWindowHandles() + await driver.switchTo().window(faucet) + await delay(regularDelayMs) + + const send1eth = await findElement(driver, By.xpath(`//button[contains(text(), '10 ether')]`), 14000) + await send1eth.click() + await delay(regularDelayMs) + + await driver.switchTo().window(extension) + await driver.get(extensionUri) + await delay(regularDelayMs) + + const confirmButton = await findElement(driver, By.xpath(`//button[contains(text(), 'Confirm')]`), 14000) + await confirmButton.click() + await delay(regularDelayMs) + + await driver.switchTo().window(faucet) + await delay(regularDelayMs) + await driver.close() + await delay(regularDelayMs) + await driver.switchTo().window(extension) + await delay(regularDelayMs) + await driver.get(extensionUri) + await delay(regularDelayMs) + }) + }) + + describe('Add existing token using search', () => { + it('clicks on the Add Token button', async () => { + const addToken = await findElement(driver, By.xpath(`//button[contains(text(), 'Add Token')]`)) + await addToken.click() + await delay(regularDelayMs) + }) + + it('picks an existing token', async () => { + const tokenSearch = await findElement(driver, By.css('#search-tokens')) + await tokenSearch.sendKeys('BAT') + await delay(regularDelayMs) + + const token = await findElement(driver, By.xpath("//span[contains(text(), 'BAT')]")) + await token.click() + await delay(regularDelayMs) + + const nextScreen = await findElement(driver, By.xpath(`//button[contains(text(), 'Next')]`)) + await nextScreen.click() await delay(regularDelayMs) const [importAccount] = await findElements(driver, By.xpath(`//div[contains(text(), 'Import Account')]`)) await importAccount.click() await delay(regularDelayMs) }) + }) + + describe('Add a custom token from TokenFactory', () => { + let extension, tokenFactory + + it('creates a new token', async () => { + await driver.executeScript('window.open("https://tokenfactory.surge.sh/#/factory")') + await delay(waitingNewPageDelayMs) + + const windowHandles = await driver.getAllWindowHandles() + extension = windowHandles[0] + tokenFactory = windowHandles[1] + + await driver.switchTo().window(tokenFactory) + const [ + totalSupply, + tokenName, + tokenDecimal, + tokenSymbol, + ] = await findElements(driver, By.css('.form-control')) + + await totalSupply.sendKeys('100') + await tokenName.sendKeys('Test') + await tokenDecimal.sendKeys('0') + await tokenSymbol.sendKeys('TST') + + const createToken = await findElement(driver, By.xpath(`//button[contains(text(), 'Create Token')]`)) + await createToken.click() + await delay(regularDelayMs) + + await driver.switchTo().window(extension) + await driver.get(extensionUri) + await delay(regularDelayMs) + }) it('enter private key', async () => { const privateKeyInput = await findElement(driver, By.css('#private-key-box')) await privateKeyInput.sendKeys(testPrivateKey2) await delay(regularDelayMs) - const importButtons = await findElements(driver, By.xpath(`//button[contains(text(), 'Import')]`)) - await importButtons[0].click() + + await driver.switchTo().window(tokenFactory) + await delay(regularDelayMs) + const tokenContactAddress = await driver.findElement(By.css('div > div > div:nth-child(2) > span:nth-child(3)')) + await tokenContactAddress.getText() + await driver.close() + await driver.switchTo().window(extension) + await driver.get(extensionUri) await delay(regularDelayMs) }) diff --git a/test/e2e/beta/helpers.js b/test/e2e/beta/helpers.js index 828f87db7..84e578d3e 100644 --- a/test/e2e/beta/helpers.js +++ b/test/e2e/beta/helpers.js @@ -2,8 +2,8 @@ const fs = require('fs') const mkdirp = require('mkdirp') const pify = require('pify') const assert = require('assert') -const {until} = require('selenium-webdriver') const { delay } = require('../func') +const { until } = require('selenium-webdriver') module.exports = { assertElementNotPresent, diff --git a/test/e2e/beta/metamask-beta-ui.spec.js b/test/e2e/beta/metamask-beta-ui.spec.js index 98132e68c..98b4a2791 100644 --- a/test/e2e/beta/metamask-beta-ui.spec.js +++ b/test/e2e/beta/metamask-beta-ui.spec.js @@ -1026,4 +1026,4 @@ describe('MetaMask', function () { await delay(regularDelayMs) }) }) -}) +})
\ No newline at end of file diff --git a/test/e2e/func.js b/test/e2e/func.js index 7b1730959..13dfb82f9 100644 --- a/test/e2e/func.js +++ b/test/e2e/func.js @@ -1,14 +1,19 @@ require('chromedriver') require('geckodriver') -const fs = require('fs') +const fs = require('fs-extra') const os = require('os') const path = require('path') +const pify = require('pify') +const prependFile = pify(require('prepend-file')) const webdriver = require('selenium-webdriver') const Command = require('selenium-webdriver/lib/command').Command const By = webdriver.By module.exports = { delay, + createModifiedTestBuild, + setupBrowserAndExtension, + verboseReportOnFailure, buildChromeWebDriver, buildFirefoxWebdriver, installWebExt, @@ -20,6 +25,37 @@ function delay (time) { return new Promise(resolve => setTimeout(resolve, time)) } +async function createModifiedTestBuild ({ browser, srcPath }) { + // copy build to test-builds directory + const extPath = path.resolve(`test-builds/${browser}`) + await fs.ensureDir(extPath) + await fs.copy(srcPath, extPath) + // inject METAMASK_TEST_CONFIG setting default test network + const config = { NetworkController: { provider: { type: 'localhost' } } } + await prependFile(`${extPath}/background.js`, `window.METAMASK_TEST_CONFIG=${JSON.stringify(config)};\n`) + return { extPath } +} + +async function setupBrowserAndExtension ({ browser, extPath }) { + let driver, extensionId, extensionUri + + if (browser === 'chrome') { + driver = buildChromeWebDriver(extPath) + extensionId = await getExtensionIdChrome(driver) + extensionUri = `chrome-extension://${extensionId}/home.html` + } else if (browser === 'firefox') { + driver = buildFirefoxWebdriver() + await installWebExt(driver, extPath) + await delay(700) + extensionId = await getExtensionIdFirefox(driver) + extensionUri = `moz-extension://${extensionId}/home.html` + } else { + throw new Error(`Unknown Browser "${browser}"`) + } + + return { driver, extensionId, extensionUri } +} + function buildChromeWebDriver (extPath) { const tmpProfile = fs.mkdtempSync(path.join(os.tmpdir(), 'mm-chrome-profile')) return new webdriver.Builder() @@ -61,3 +97,13 @@ async function installWebExt (driver, extension) { return await driver.schedule(cmd, 'installWebExt(' + extension + ')') } + +async function verboseReportOnFailure ({ browser, driver, title }) { + const artifactDir = `./test-artifacts/${browser}/${title}` + const filepathBase = `${artifactDir}/test-failure` + await fs.ensureDir(artifactDir) + const screenshot = await driver.takeScreenshot() + await fs.writeFile(`${filepathBase}-screenshot.png`, screenshot, { encoding: 'base64' }) + const htmlSource = await driver.getPageSource() + await fs.writeFile(`${filepathBase}-dom.html`, htmlSource) +} diff --git a/test/e2e/metamask.spec.js b/test/e2e/metamask.spec.js index ac7600f09..c59983c79 100644 --- a/test/e2e/metamask.spec.js +++ b/test/e2e/metamask.spec.js @@ -1,38 +1,29 @@ -const fs = require('fs') -const mkdirp = require('mkdirp') const path = require('path') const assert = require('assert') -const pify = require('pify') -const webdriver = require('selenium-webdriver') -const { By, Key, until } = webdriver -const { delay, buildChromeWebDriver, buildFirefoxWebdriver, installWebExt, getExtensionIdChrome, getExtensionIdFirefox } = require('./func') +const { By, Key, until } = require('selenium-webdriver') +const { delay, createModifiedTestBuild, setupBrowserAndExtension, verboseReportOnFailure } = require('./func') describe('Metamask popup page', function () { - let driver, accountAddress, tokenAddress, extensionId + const browser = process.env.SELENIUM_BROWSER + let driver, accountAddress, tokenAddress, extensionUri this.timeout(0) before(async function () { - if (process.env.SELENIUM_BROWSER === 'chrome') { - const extPath = path.resolve('dist/chrome') - driver = buildChromeWebDriver(extPath) - extensionId = await getExtensionIdChrome(driver) - await driver.get(`chrome-extension://${extensionId}/popup.html`) - - } else if (process.env.SELENIUM_BROWSER === 'firefox') { - const extPath = path.resolve('dist/firefox') - driver = buildFirefoxWebdriver() - await installWebExt(driver, extPath) - await delay(700) - extensionId = await getExtensionIdFirefox(driver) - await driver.get(`moz-extension://${extensionId}/popup.html`) - } + const srcPath = path.resolve(`dist/${browser}`) + const { extPath } = await createModifiedTestBuild({ browser, srcPath }) + const installResult = await setupBrowserAndExtension({ browser, extPath }) + driver = installResult.driver + extensionUri = installResult.extensionUri + + await driver.get(extensionUri) + await delay(300) }) afterEach(async function () { // logs command not supported in firefox // https://github.com/SeleniumHQ/selenium/issues/2910 - if (process.env.SELENIUM_BROWSER === 'chrome') { + if (browser === 'chrome') { // check for console errors const errors = await checkBrowserForConsoleErrors() if (errors.length) { @@ -43,7 +34,7 @@ describe('Metamask popup page', function () { } // gather extra data if test failed if (this.currentTest.state === 'failed') { - await verboseReportOnFailure(this.currentTest) + await verboseReportOnFailure({ browser, driver, title: this.currentTest.title }) } }) @@ -54,7 +45,6 @@ describe('Metamask popup page', function () { describe('Setup', function () { it('switches to Chrome extensions list', async function () { - await delay(300) const windowHandles = await driver.getAllWindowHandles() await driver.switchTo().window(windowHandles[0]) }) @@ -98,6 +88,7 @@ describe('Metamask popup page', function () { it('allows the button to be clicked when scrolled to the bottom of TOU', async () => { const button = await driver.findElement(By.css('#app-content > div > div.app-primary.from-right > div > div.flex-column.flex-center.flex-grow > button')) await button.click() + await delay(300) }) it('shows privacy notice', async () => { @@ -108,7 +99,6 @@ describe('Metamask popup page', function () { }) it('shows phishing notice', async () => { - await delay(300) const noticeHeader = await driver.findElement(By.css('.terms-header')).getText() assert.equal(noticeHeader, 'PHISHING WARNING', 'shows phishing warning') const element = await driver.findElement(By.css('.markdown')) @@ -295,11 +285,7 @@ describe('Metamask popup page', function () { }) it('navigates back to MetaMask popup in the tab', async function () { - if (process.env.SELENIUM_BROWSER === 'chrome') { - await driver.get(`chrome-extension://${extensionId}/popup.html`) - } else if (process.env.SELENIUM_BROWSER === 'firefox') { - await driver.get(`moz-extension://${extensionId}/popup.html`) - } + await driver.get(extensionUri) await delay(700) }) }) @@ -362,21 +348,4 @@ describe('Metamask popup page', function () { return matchedErrorObjects } - async function verboseReportOnFailure (test) { - let artifactDir - if (process.env.SELENIUM_BROWSER === 'chrome') { - artifactDir = `./test-artifacts/chrome/${test.title}` - } else if (process.env.SELENIUM_BROWSER === 'firefox') { - artifactDir = `./test-artifacts/firefox/${test.title}` - } - const filepathBase = `${artifactDir}/test-failure` - await pify(mkdirp)(artifactDir) - // capture screenshot - const screenshot = await driver.takeScreenshot() - await pify(fs.writeFile)(`${filepathBase}-screenshot.png`, screenshot, { encoding: 'base64' }) - // capture dom source - const htmlSource = await driver.getPageSource() - await pify(fs.writeFile)(`${filepathBase}-dom.html`, htmlSource) - } - }) diff --git a/test/helper.js b/test/helper.js index a3abbebf2..51f28de17 100644 --- a/test/helper.js +++ b/test/helper.js @@ -1,10 +1,21 @@ +const Ganache = require('ganache-core') +const nock = require('nock') import Enzyme from 'enzyme' import Adapter from 'enzyme-adapter-react-15' +nock.disableNetConnect() +nock.enableNetConnect('localhost') + Enzyme.configure({ adapter: new Adapter() }) // disallow promises from swallowing errors enableFailureOnUnhandledPromiseRejection() +// ganache server +const server = Ganache.server() +server.listen(8545, () => { + console.log('Ganache Testrpc is running on "http://localhost:8545"') +}) + // logging util var log = require('loglevel') log.setDefaultLevel(5) @@ -14,6 +25,9 @@ global.log = log // polyfills // +// fetch +global.fetch = require('isomorphic-fetch') + // dom require('jsdom-global')() diff --git a/test/lib/createTxMeta.js b/test/lib/createTxMeta.js new file mode 100644 index 000000000..0e88e3cfb --- /dev/null +++ b/test/lib/createTxMeta.js @@ -0,0 +1,16 @@ +const txStateHistoryHelper = require('../../app/scripts/controllers/transactions/lib/tx-state-history-helper') + +module.exports = createTxMeta + +function createTxMeta (partialMeta) { + const txMeta = Object.assign({ + status: 'unapproved', + txParams: {}, + }, partialMeta) + // initialize history + txMeta.history = [] + // capture initial snapshot of txMeta for history + const snapshot = txStateHistoryHelper.snapshotFromTxMeta(txMeta) + txMeta.history.push(snapshot) + return txMeta +} diff --git a/test/unit/app/controllers/currency-controller-test.js b/test/unit/app/controllers/currency-controller-test.js index 1941d1c43..7c4644d9f 100644 --- a/test/unit/app/controllers/currency-controller-test.js +++ b/test/unit/app/controllers/currency-controller-test.js @@ -1,6 +1,3 @@ -// polyfill fetch -global.fetch = global.fetch || require('isomorphic-fetch') - const assert = require('assert') const nock = require('nock') const CurrencyController = require('../../../../app/scripts/controllers/currency') diff --git a/test/unit/app/controllers/detect-tokens-test.js b/test/unit/app/controllers/detect-tokens-test.js index 426ffe23a..4ee73599d 100644 --- a/test/unit/app/controllers/detect-tokens-test.js +++ b/test/unit/app/controllers/detect-tokens-test.js @@ -1,19 +1,41 @@ const assert = require('assert') const sinon = require('sinon') +const nock = require('nock') const ObservableStore = require('obs-store') const DetectTokensController = require('../../../../app/scripts/controllers/detect-tokens') const NetworkController = require('../../../../app/scripts/controllers/network/network') const PreferencesController = require('../../../../app/scripts/controllers/preferences') describe('DetectTokensController', () => { - const sandbox = sinon.createSandbox() - let clock - let keyringMemStore - before(async () => { - keyringMemStore = new ObservableStore({ isUnlocked: false}) - }) - after(() => { - sandbox.restore() + + let clock, network, preferences, controller, keyringMemStore + + const sandbox = sinon.createSandbox() + + const noop = () => {} + + const networkControllerProviderConfig = { + getAccounts: noop, + } + + beforeEach(async () => { + + nock('https://api.infura.io') + .get(/.*/) + .reply(200) + + keyringMemStore = new ObservableStore({ isUnlocked: false}) + network = new NetworkController() + preferences = new PreferencesController() + controller = new DetectTokensController({ preferences: preferences, network: network, keyringMemStore: keyringMemStore }) + + network.initializeProvider(networkControllerProviderConfig) + + }) + + after(() => { + sandbox.restore() + nock.cleanAll() }) it('should poll on correct interval', async () => { @@ -26,6 +48,7 @@ describe('DetectTokensController', () => { it('should be called on every polling period', async () => { clock = sandbox.useFakeTimers() const network = new NetworkController() + network.initializeProvider(networkControllerProviderConfig) network.setProviderType('mainnet') const preferences = new PreferencesController() const controller = new DetectTokensController({ preferences: preferences, network: network, keyringMemStore: keyringMemStore }) @@ -45,10 +68,7 @@ describe('DetectTokensController', () => { }) it('should not check tokens while in test network', async () => { - const network = new NetworkController() network.setProviderType('rinkeby') - const preferences = new PreferencesController() - const controller = new DetectTokensController({ preferences: preferences, network: network, keyringMemStore: keyringMemStore }) controller.isOpen = true controller.isUnlocked = true @@ -61,10 +81,7 @@ describe('DetectTokensController', () => { }) it('should only check and add tokens while in main network', async () => { - const network = new NetworkController() network.setProviderType('mainnet') - const preferences = new PreferencesController() - const controller = new DetectTokensController({ preferences: preferences, network: network, keyringMemStore: keyringMemStore }) controller.isOpen = true controller.isUnlocked = true @@ -80,10 +97,7 @@ describe('DetectTokensController', () => { }) it('should not detect same token while in main network', async () => { - const network = new NetworkController() network.setProviderType('mainnet') - const preferences = new PreferencesController() - preferences.addToken('0x0d262e5dc4a06a0f1c90ce79c7a60c09dfc884e4', 'J8T', 8) const controller = new DetectTokensController({ preferences: preferences, network: network, keyringMemStore: keyringMemStore }) controller.isOpen = true controller.isUnlocked = true @@ -100,10 +114,7 @@ describe('DetectTokensController', () => { }) it('should trigger detect new tokens when change address', async () => { - const network = new NetworkController() network.setProviderType('mainnet') - const preferences = new PreferencesController() - const controller = new DetectTokensController({ preferences: preferences, network: network, keyringMemStore: keyringMemStore }) controller.isOpen = true controller.isUnlocked = true var stub = sandbox.stub(controller, 'detectNewTokens') @@ -112,10 +123,7 @@ describe('DetectTokensController', () => { }) it('should trigger detect new tokens when submit password', async () => { - const network = new NetworkController() network.setProviderType('mainnet') - const preferences = new PreferencesController() - const controller = new DetectTokensController({ preferences: preferences, network: network, keyringMemStore: keyringMemStore }) controller.isOpen = true controller.selectedAddress = '0x0' var stub = sandbox.stub(controller, 'detectNewTokens') @@ -124,10 +132,7 @@ describe('DetectTokensController', () => { }) it('should not trigger detect new tokens when not open or not unlocked', async () => { - const network = new NetworkController() network.setProviderType('mainnet') - const preferences = new PreferencesController() - const controller = new DetectTokensController({ preferences: preferences, network: network, keyringMemStore: keyringMemStore }) controller.isOpen = true controller.isUnlocked = false var stub = sandbox.stub(controller, 'detectTokenBalance') diff --git a/test/unit/app/controllers/metamask-controller-test.js b/test/unit/app/controllers/metamask-controller-test.js index 9164fe246..471321b9b 100644 --- a/test/unit/app/controllers/metamask-controller-test.js +++ b/test/unit/app/controllers/metamask-controller-test.js @@ -3,9 +3,10 @@ const sinon = require('sinon') const clone = require('clone') const nock = require('nock') const createThoughStream = require('through2').obj -const MetaMaskController = require('../../../../app/scripts/metamask-controller') const blacklistJSON = require('eth-phishing-detect/src/config') -const firstTimeState = require('../../../../app/scripts/first-time-state') +const MetaMaskController = require('../../../../app/scripts/metamask-controller') +const firstTimeState = require('../../../unit/localhostState') +const createTxMeta = require('../../../lib/createTxMeta') const currentNetworkId = 42 const DEFAULT_LABEL = 'Account 1' @@ -13,6 +14,7 @@ const TEST_SEED = 'debris dizzy just program just float decrease vacant alarm re const TEST_ADDRESS = '0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc' const TEST_SEED_ALT = 'setup olympic issue mobile velvet surge alcohol burger horse view reopen gentle' const TEST_ADDRESS_ALT = '0xc42edfcc21ed14dda456aa0756c153f7985d8813' +const CUSTOM_RPC_URL = 'http://localhost:8545' describe('MetaMaskController', function () { let metamaskController @@ -346,29 +348,19 @@ describe('MetaMaskController', function () { }) describe('#setCustomRpc', function () { - const customRPC = 'https://custom.rpc/' let rpcTarget beforeEach(function () { - - nock('https://custom.rpc') - .post('/') - .reply(200) - - rpcTarget = metamaskController.setCustomRpc(customRPC) - }) - - afterEach(function () { - nock.cleanAll() + rpcTarget = metamaskController.setCustomRpc(CUSTOM_RPC_URL) }) it('returns custom RPC that when called', async function () { - assert.equal(await rpcTarget, customRPC) + assert.equal(await rpcTarget, CUSTOM_RPC_URL) }) it('changes the network controller rpc', function () { const networkControllerState = metamaskController.networkController.store.getState() - assert.equal(networkControllerState.provider.rpcTarget, customRPC) + assert.equal(networkControllerState.provider.rpcTarget, CUSTOM_RPC_URL) }) }) @@ -473,9 +465,10 @@ describe('MetaMaskController', function () { getNetworkstub.returns(42) metamaskController.txController.txStateManager._saveTxList([ - { id: 1, status: 'unapproved', metamaskNetworkId: currentNetworkId, txParams: {from: '0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc'} }, - { id: 2, status: 'rejected', metamaskNetworkId: 32, txParams: {} }, - { id: 3, status: 'submitted', metamaskNetworkId: currentNetworkId, txParams: {from: '0xB09d8505E1F4EF1CeA089D47094f5DD3464083d4'} }, + createTxMeta({ id: 1, status: 'unapproved', metamaskNetworkId: currentNetworkId, txParams: {from: '0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc'} }), + createTxMeta({ id: 1, status: 'unapproved', metamaskNetworkId: currentNetworkId, txParams: {from: '0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc'} }), + createTxMeta({ id: 2, status: 'rejected', metamaskNetworkId: 32 }), + createTxMeta({ id: 3, status: 'submitted', metamaskNetworkId: currentNetworkId, txParams: {from: '0xB09d8505E1F4EF1CeA089D47094f5DD3464083d4'} }), ]) }) @@ -552,14 +545,14 @@ describe('MetaMaskController', function () { }) - describe('#newUnsignedMessage', function () { + describe('#newUnsignedMessage', () => { let msgParams, metamaskMsgs, messages, msgId const address = '0xc42edfcc21ed14dda456aa0756c153f7985d8813' const data = '0x43727970746f6b697474696573' - beforeEach(async function () { + beforeEach(async () => { await metamaskController.createNewVaultAndRestore('foobar1337', TEST_SEED_ALT) @@ -568,7 +561,10 @@ describe('MetaMaskController', function () { 'data': data, } - metamaskController.newUnsignedMessage(msgParams, noop) + const promise = metamaskController.newUnsignedMessage(msgParams) + // handle the promise so it doesn't throw an unhandledRejection + promise.then(noop).catch(noop) + metamaskMsgs = metamaskController.messageManager.getUnapprovedMsgs() messages = metamaskController.messageManager.messages msgId = Object.keys(metamaskMsgs)[0] @@ -608,13 +604,16 @@ describe('MetaMaskController', function () { describe('#newUnsignedPersonalMessage', function () { - it('errors with no from in msgParams', function () { + it('errors with no from in msgParams', async () => { const msgParams = { 'data': data, } - metamaskController.newUnsignedPersonalMessage(msgParams, function (error) { + try { + await metamaskController.newUnsignedPersonalMessage(msgParams) + assert.fail('should have thrown') + } catch (error) { assert.equal(error.message, 'MetaMask Message Signature: from field is required.') - }) + } }) let msgParams, metamaskPersonalMsgs, personalMessages, msgId @@ -631,7 +630,10 @@ describe('MetaMaskController', function () { 'data': data, } - metamaskController.newUnsignedPersonalMessage(msgParams, noop) + const promise = metamaskController.newUnsignedPersonalMessage(msgParams) + // handle the promise so it doesn't throw an unhandledRejection + promise.then(noop).catch(noop) + metamaskPersonalMsgs = metamaskController.personalMessageManager.getUnapprovedMsgs() personalMessages = metamaskController.personalMessageManager.messages msgId = Object.keys(metamaskPersonalMsgs)[0] @@ -670,22 +672,27 @@ describe('MetaMaskController', function () { describe('#setupUntrustedCommunication', function () { let streamTest - const phishingUrl = 'decentral.market' + const phishingUrl = 'myethereumwalletntw.com' afterEach(function () { streamTest.end() }) - it('sets up phishing stream for untrusted communication ', async function () { + it('sets up phishing stream for untrusted communication ', async () => { await metamaskController.blacklistController.updatePhishingList() + console.log(blacklistJSON.blacklist.includes(phishingUrl)) + + const { promise, resolve } = deferredPromise() streamTest = createThoughStream((chunk, enc, cb) => { - assert.equal(chunk.name, 'phishing') + if (chunk.name !== 'phishing') return cb() assert.equal(chunk.data.hostname, phishingUrl) - cb() - }) - // console.log(streamTest) - metamaskController.setupUntrustedCommunication(streamTest, phishingUrl) + resolve() + cb() + }) + metamaskController.setupUntrustedCommunication(streamTest, phishingUrl) + + await promise }) }) @@ -732,3 +739,9 @@ describe('MetaMaskController', function () { }) }) + +function deferredPromise () { + let resolve + const promise = new Promise(_resolve => { resolve = _resolve }) + return { promise, resolve } +} diff --git a/test/unit/app/controllers/network-contoller-test.js b/test/unit/app/controllers/network-contoller-test.js index e16fb104e..822311931 100644 --- a/test/unit/app/controllers/network-contoller-test.js +++ b/test/unit/app/controllers/network-contoller-test.js @@ -32,9 +32,10 @@ describe('# Network Controller', function () { describe('#provider', function () { it('provider should be updatable without reassignment', function () { networkController.initializeProvider(networkControllerProviderConfig) - const proxy = networkController._proxy - proxy.setTarget({ test: true, on: () => {} }) - assert.ok(proxy.test) + const providerProxy = networkController.getProviderAndBlockTracker().provider + assert.equal(providerProxy.test, undefined) + providerProxy.setTarget({ test: true }) + assert.equal(providerProxy.test, true) }) }) describe('#getNetworkState', function () { diff --git a/test/unit/app/controllers/transactions/nonce-tracker-test.js b/test/unit/app/controllers/transactions/nonce-tracker-test.js index 6c0ac759f..51ac390e9 100644 --- a/test/unit/app/controllers/transactions/nonce-tracker-test.js +++ b/test/unit/app/controllers/transactions/nonce-tracker-test.js @@ -224,14 +224,15 @@ function generateNonceTrackerWith (pending, confirmed, providerStub = '0x0') { providerResultStub.result = providerStub const provider = { sendAsync: (_, cb) => { cb(undefined, providerResultStub) }, - _blockTracker: { - getCurrentBlock: () => '0x11b568', - }, + } + const blockTracker = { + getCurrentBlock: () => '0x11b568', + getLatestBlock: async () => '0x11b568', } return new NonceTracker({ provider, + blockTracker, getPendingTransactions, getConfirmedTransactions, }) } - diff --git a/test/unit/app/controllers/transactions/pending-tx-test.js b/test/unit/app/controllers/transactions/pending-tx-test.js index 8bf2da6f8..ba15f1953 100644 --- a/test/unit/app/controllers/transactions/pending-tx-test.js +++ b/test/unit/app/controllers/transactions/pending-tx-test.js @@ -9,6 +9,7 @@ describe('PendingTransactionTracker', function () { let pendingTxTracker, txMeta, txMetaNoHash, providerResultStub, provider, txMeta3, txList, knownErrors this.timeout(10000) + beforeEach(function () { txMeta = { id: 1, @@ -40,7 +41,10 @@ describe('PendingTransactionTracker', function () { getPendingTransactions: () => { return [] }, getCompletedTransactions: () => { return [] }, publishTransaction: () => {}, + confirmTransaction: () => {}, }) + + pendingTxTracker._getBlock = (blockNumber) => { return {number: blockNumber, transactions: []} } }) describe('_checkPendingTx state management', function () { @@ -92,58 +96,6 @@ describe('PendingTransactionTracker', function () { }) }) - describe('#checkForTxInBlock', function () { - it('should return if no pending transactions', function () { - // throw a type error if it trys to do anything on the block - // thus failing the test - const block = Proxy.revocable({}, {}).revoke() - pendingTxTracker.checkForTxInBlock(block) - }) - it('should emit \'tx:failed\' if the txMeta does not have a hash', function (done) { - const block = Proxy.revocable({}, {}).revoke() - pendingTxTracker.getPendingTransactions = () => [txMetaNoHash] - pendingTxTracker.once('tx:failed', (txId, err) => { - assert(txId, txMetaNoHash.id, 'should pass txId') - done() - }) - pendingTxTracker.checkForTxInBlock(block) - }) - it('should emit \'txConfirmed\' if the tx is in the block', function (done) { - const block = { transactions: [txMeta]} - pendingTxTracker.getPendingTransactions = () => [txMeta] - pendingTxTracker.once('tx:confirmed', (txId) => { - assert(txId, txMeta.id, 'should pass txId') - done() - }) - pendingTxTracker.once('tx:failed', (_, err) => { done(err) }) - pendingTxTracker.checkForTxInBlock(block) - }) - }) - describe('#queryPendingTxs', function () { - it('should call #_checkPendingTxs if their is no oldBlock', function (done) { - let oldBlock - const newBlock = { number: '0x01' } - pendingTxTracker._checkPendingTxs = done - pendingTxTracker.queryPendingTxs({ oldBlock, newBlock }) - }) - it('should call #_checkPendingTxs if oldBlock and the newBlock have a diff of greater then 1', function (done) { - const oldBlock = { number: '0x01' } - const newBlock = { number: '0x03' } - pendingTxTracker._checkPendingTxs = done - pendingTxTracker.queryPendingTxs({ oldBlock, newBlock }) - }) - it('should not call #_checkPendingTxs if oldBlock and the newBlock have a diff of 1 or less', function (done) { - const oldBlock = { number: '0x1' } - const newBlock = { number: '0x2' } - pendingTxTracker._checkPendingTxs = () => { - const err = new Error('should not call #_checkPendingTxs if oldBlock and the newBlock have a diff of 1 or less') - done(err) - } - pendingTxTracker.queryPendingTxs({ oldBlock, newBlock }) - done() - }) - }) - describe('#_checkPendingTx', function () { it('should emit \'tx:failed\' if the txMeta does not have a hash', function (done) { pendingTxTracker.once('tx:failed', (txId, err) => { @@ -157,16 +109,6 @@ describe('PendingTransactionTracker', function () { providerResultStub.eth_getTransactionByHash = null pendingTxTracker._checkPendingTx(txMeta) }) - - it('should emit \'txConfirmed\'', function (done) { - providerResultStub.eth_getTransactionByHash = {blockNumber: '0x01'} - pendingTxTracker.once('tx:confirmed', (txId) => { - assert(txId, txMeta.id, 'should pass txId') - done() - }) - pendingTxTracker.once('tx:failed', (_, err) => { done(err) }) - pendingTxTracker._checkPendingTx(txMeta) - }) }) describe('#_checkPendingTxs', function () { @@ -180,19 +122,19 @@ describe('PendingTransactionTracker', function () { }) }) - it('should warp all txMeta\'s in #_checkPendingTx', function (done) { + it('should warp all txMeta\'s in #updatePendingTxs', function (done) { pendingTxTracker.getPendingTransactions = () => txList pendingTxTracker._checkPendingTx = (tx) => { tx.resolve(tx) } Promise.all(txList.map((tx) => tx.processed)) .then((txCompletedList) => done()) .catch(done) - pendingTxTracker._checkPendingTxs() + pendingTxTracker.updatePendingTxs() }) }) describe('#resubmitPendingTxs', function () { - const blockStub = { number: '0x0' } + const blockNumberStub = '0x0' beforeEach(function () { const txMeta2 = txMeta3 = txMeta txList = [txMeta, txMeta2, txMeta3].map((tx) => { @@ -210,7 +152,7 @@ describe('PendingTransactionTracker', function () { Promise.all(txList.map((tx) => tx.processed)) .then((txCompletedList) => done()) .catch(done) - pendingTxTracker.resubmitPendingTxs(blockStub) + pendingTxTracker.resubmitPendingTxs(blockNumberStub) }) it('should not emit \'tx:failed\' if the txMeta throws a known txError', function (done) { knownErrors = [ @@ -237,7 +179,7 @@ describe('PendingTransactionTracker', function () { .then((txCompletedList) => done()) .catch(done) - pendingTxTracker.resubmitPendingTxs(blockStub) + pendingTxTracker.resubmitPendingTxs(blockNumberStub) }) it('should emit \'tx:warning\' if it encountered a real error', function (done) { pendingTxTracker.once('tx:warning', (txMeta, err) => { @@ -255,7 +197,7 @@ describe('PendingTransactionTracker', function () { .then((txCompletedList) => done()) .catch(done) - pendingTxTracker.resubmitPendingTxs(blockStub) + pendingTxTracker.resubmitPendingTxs(blockNumberStub) }) }) describe('#_resubmitTx', function () { diff --git a/test/unit/app/controllers/transactions/tx-controller-test.js b/test/unit/app/controllers/transactions/tx-controller-test.js index 26dc7b656..5ac813b49 100644 --- a/test/unit/app/controllers/transactions/tx-controller-test.js +++ b/test/unit/app/controllers/transactions/tx-controller-test.js @@ -1,4 +1,5 @@ const assert = require('assert') +const EventEmitter = require('events') const ethUtil = require('ethereumjs-util') const EthTx = require('ethereumjs-tx') const ObservableStore = require('obs-store') @@ -22,12 +23,14 @@ describe('Transaction Controller', function () { } provider = createTestProviderTools({ scaffold: providerResultStub }).provider fromAccount = getTestAccounts()[0] - + const blockTrackerStub = new EventEmitter() + blockTrackerStub.getCurrentBlock = noop + blockTrackerStub.getLatestBlock = noop txController = new TransactionController({ provider, networkStore: new ObservableStore(currentNetworkId), txHistoryLimit: 10, - blockTracker: { getCurrentBlock: noop, on: noop, once: noop }, + blockTracker: blockTrackerStub, signTransaction: (ethTx) => new Promise((resolve) => { ethTx.sign(fromAccount.key) resolve() @@ -49,9 +52,9 @@ describe('Transaction Controller', function () { describe('#getUnapprovedTxCount', function () { it('should return the number of unapproved txs', function () { txController.txStateManager._saveTxList([ - { id: 1, status: 'unapproved', metamaskNetworkId: currentNetworkId, txParams: {} }, - { id: 2, status: 'unapproved', metamaskNetworkId: currentNetworkId, txParams: {} }, - { id: 3, status: 'unapproved', metamaskNetworkId: currentNetworkId, txParams: {} }, + { id: 1, status: 'unapproved', metamaskNetworkId: currentNetworkId, txParams: {}, history: [] }, + { id: 2, status: 'unapproved', metamaskNetworkId: currentNetworkId, txParams: {}, history: [] }, + { id: 3, status: 'unapproved', metamaskNetworkId: currentNetworkId, txParams: {}, history: [] }, ]) const unapprovedTxCount = txController.getUnapprovedTxCount() assert.equal(unapprovedTxCount, 3, 'should be 3') @@ -61,9 +64,9 @@ describe('Transaction Controller', function () { describe('#getPendingTxCount', function () { it('should return the number of pending txs', function () { txController.txStateManager._saveTxList([ - { id: 1, status: 'submitted', metamaskNetworkId: currentNetworkId, txParams: {} }, - { id: 2, status: 'submitted', metamaskNetworkId: currentNetworkId, txParams: {} }, - { id: 3, status: 'submitted', metamaskNetworkId: currentNetworkId, txParams: {} }, + { id: 1, status: 'submitted', metamaskNetworkId: currentNetworkId, txParams: {}, history: [] }, + { id: 2, status: 'submitted', metamaskNetworkId: currentNetworkId, txParams: {}, history: [] }, + { id: 3, status: 'submitted', metamaskNetworkId: currentNetworkId, txParams: {}, history: [] }, ]) const pendingTxCount = txController.getPendingTxCount() assert.equal(pendingTxCount, 3, 'should be 3') @@ -79,15 +82,15 @@ describe('Transaction Controller', function () { 'to': '0xc684832530fcbddae4b4230a47e991ddcec2831d', } txController.txStateManager._saveTxList([ - {id: 0, status: 'confirmed', metamaskNetworkId: currentNetworkId, txParams}, - {id: 1, status: 'confirmed', metamaskNetworkId: currentNetworkId, txParams}, - {id: 2, status: 'confirmed', metamaskNetworkId: currentNetworkId, txParams}, - {id: 3, status: 'unapproved', metamaskNetworkId: currentNetworkId, txParams}, - {id: 4, status: 'rejected', metamaskNetworkId: currentNetworkId, txParams}, - {id: 5, status: 'approved', metamaskNetworkId: currentNetworkId, txParams}, - {id: 6, status: 'signed', metamaskNetworkId: currentNetworkId, txParams}, - {id: 7, status: 'submitted', metamaskNetworkId: currentNetworkId, txParams}, - {id: 8, status: 'failed', metamaskNetworkId: currentNetworkId, txParams}, + {id: 0, status: 'confirmed', metamaskNetworkId: currentNetworkId, txParams, history: [] }, + {id: 1, status: 'confirmed', metamaskNetworkId: currentNetworkId, txParams, history: [] }, + {id: 2, status: 'confirmed', metamaskNetworkId: currentNetworkId, txParams, history: [] }, + {id: 3, status: 'unapproved', metamaskNetworkId: currentNetworkId, txParams, history: [] }, + {id: 4, status: 'rejected', metamaskNetworkId: currentNetworkId, txParams, history: [] }, + {id: 5, status: 'approved', metamaskNetworkId: currentNetworkId, txParams, history: [] }, + {id: 6, status: 'signed', metamaskNetworkId: currentNetworkId, txParams, history: [] }, + {id: 7, status: 'submitted', metamaskNetworkId: currentNetworkId, txParams, history: [] }, + {id: 8, status: 'failed', metamaskNetworkId: currentNetworkId, txParams, history: [] }, ]) }) @@ -201,24 +204,22 @@ describe('Transaction Controller', function () { }) describe('#addTxGasDefaults', function () { - it('should add the tx defaults if their are none', function (done) { + it('should add the tx defaults if their are none', async () => { const txMeta = { - 'txParams': { - 'from': '0xc684832530fcbddae4b4230a47e991ddcec2831d', - 'to': '0xc684832530fcbddae4b4230a47e991ddcec2831d', + txParams: { + from: '0xc684832530fcbddae4b4230a47e991ddcec2831d', + to: '0xc684832530fcbddae4b4230a47e991ddcec2831d', }, + history: [], } - providerResultStub.eth_gasPrice = '4a817c800' - providerResultStub.eth_getBlockByNumber = { gasLimit: '47b784' } - providerResultStub.eth_estimateGas = '5209' - txController.addTxGasDefaults(txMeta) - .then((txMetaWithDefaults) => { - assert(txMetaWithDefaults.txParams.value, '0x0', 'should have added 0x0 as the value') - assert(txMetaWithDefaults.txParams.gasPrice, 'should have added the gas price') - assert(txMetaWithDefaults.txParams.gas, 'should have added the gas field') - done() - }) - .catch(done) + providerResultStub.eth_gasPrice = '4a817c800' + providerResultStub.eth_getBlockByNumber = { gasLimit: '47b784' } + providerResultStub.eth_estimateGas = '5209' + + const txMetaWithDefaults = await txController.addTxGasDefaults(txMeta) + assert(txMetaWithDefaults.txParams.value, '0x0', 'should have added 0x0 as the value') + assert(txMetaWithDefaults.txParams.gasPrice, 'should have added the gas price') + assert(txMetaWithDefaults.txParams.gas, 'should have added the gas field') }) }) @@ -381,8 +382,9 @@ describe('Transaction Controller', function () { }) it('should publish a tx, updates the rawTx when provided a one', async function () { + const rawTx = '0x477b2e6553c917af0db0388ae3da62965ff1a184558f61b749d1266b2e6d024c' txController.txStateManager.addTx(txMeta) - await txController.publishTransaction(txMeta.id) + await txController.publishTransaction(txMeta.id, rawTx) const publishedTx = txController.txStateManager.getTx(1) assert.equal(publishedTx.hash, hash) assert.equal(publishedTx.status, 'submitted') @@ -398,7 +400,7 @@ describe('Transaction Controller', function () { data: '0x0', } txController.txStateManager._saveTxList([ - { id: 1, status: 'submitted', metamaskNetworkId: currentNetworkId, txParams }, + { id: 1, status: 'submitted', metamaskNetworkId: currentNetworkId, txParams, history: [] }, ]) txController.retryTransaction(1) .then((txMeta) => { diff --git a/test/unit/config-manager-test.js b/test/unit/config-manager-test.js index b710e2dfb..df0c549d0 100644 --- a/test/unit/config-manager-test.js +++ b/test/unit/config-manager-test.js @@ -1,6 +1,3 @@ -// polyfill fetch -global.fetch = global.fetch || require('isomorphic-fetch') - const assert = require('assert') const configManagerGen = require('../lib/mock-config-manager') diff --git a/test/unit/localhostState.js b/test/unit/localhostState.js new file mode 100644 index 000000000..f9fa157d7 --- /dev/null +++ b/test/unit/localhostState.js @@ -0,0 +1,21 @@ + +/** + * @typedef {Object} FirstTimeState + * @property {Object} config Initial configuration parameters + * @property {Object} NetworkController Network controller state + */ + +/** + * @type {FirstTimeState} + */ +const initialState = { + config: {}, + NetworkController: { + provider: { + type: 'rpc', + rpcTarget: 'http://localhost:8545', + }, + }, +} + +module.exports = initialState |