diff --git a/CHANGELOG.md b/CHANGELOG.md index 516e8f9e8..1ba214cce 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,61 @@ ## Current Master +- Add ability to export private keys as a file. +- Add ability to export seed words as a file. +- Changed state logs to a file download than a clipboard copy. +- Fixed a long standing memory leak associated with filters installed by dapps +- Fix link to support center. + +## 3.10.0 2017-9-11 + +- Readded loose keyring label back into the account list. +- Remove cryptonator from chrome permissions. +- Add info on token contract addresses. +- Add validation preventing users from inputting their own addresses as token tracking addresses. +- Added button to reject all transactions (thanks to davidp94! https://github.com/davidp94) + +## 3.9.13 2017-9-8 + +- Changed the way we initialize the inpage provider to fix a bug affecting some developers. + +## 3.9.12 2017-9-6 + +- Fix bug that prevented Web3 1.0 compatibility +- Make eth_sign deprecation warning less noisy +- Add useful link to eth_sign deprecation warning. +- Fix bug with network version serialization over synchronous RPC +- Add MetaMask version to state logs. +- Add the total amount of tokens when multiple tokens are added under the token list +- Use HTTPS links for Etherscan. +- Update Support center link to new one with HTTPS. +- Make web3 deprecation notice more useful by linking to a descriptive article. + +## 3.9.11 2017-8-24 + +- Fix nonce calculation bug that would sometimes generate very wrong nonces. +- Give up resubmitting a transaction after 3500 blocks. + +## 3.9.10 2017-8-23 + +- Improve nonce calculation, to prevent bug where people are unable to send transactions reliably. +- Remove link to eth-tx-viz from identicons in tx history. + +## 3.9.9 2017-8-18 + +- Fix bug where some transaction submission errors would show an empty screen. +- Fix bug that could mis-render token balances when very small. +- Fix formatting of eth_sign "Sign Message" view. +- Add deprecation warning to eth_sign "Sign Message" view. + +## 3.9.8 2017-8-16 + +- Reenable token list. +- Remove default tokens. + +## 3.9.7 2017-8-15 + +- hotfix - disable token list - Added a deprecation warning for web3 https://github.com/ethereum/mist/releases/tag/v0.9.0 ## 3.9.6 2017-8-09 diff --git a/app/manifest.json b/app/manifest.json index f34bdcec3..bd25c1f6f 100644 --- a/app/manifest.json +++ b/app/manifest.json @@ -1,7 +1,7 @@ { "name": "MetaMask", "short_name": "Metamask", - "version": "3.9.6", + "version": "3.10.0", "manifest_version": 2, "author": "https://metamask.io", "description": "Ethereum Browser Extension", @@ -57,9 +57,8 @@ "permissions": [ "storage", "clipboardWrite", - "http://localhost:8545/", - "https://api.cryptonator.com/" - ], + "http://localhost:8545/" + ], "web_accessible_resources": [ "scripts/inpage.js" ], diff --git a/app/scripts/background.js b/app/scripts/background.js index f077ca7a8..1b96d68b5 100644 --- a/app/scripts/background.js +++ b/app/scripts/background.js @@ -1,6 +1,8 @@ const urlUtil = require('url') const endOfStream = require('end-of-stream') const pipe = require('pump') +const log = require('loglevel') +const extension = require('extensionizer') const LocalStorageStore = require('obs-store/lib/localStorage') const storeTransform = require('obs-store/lib/transform') const ExtensionPlatform = require('./platforms/extension') @@ -9,13 +11,11 @@ const migrations = require('./migrations/') const PortStream = require('./lib/port-stream.js') const NotificationManager = require('./lib/notification-manager.js') const MetamaskController = require('./metamask-controller') -const extension = require('extensionizer') const firstTimeState = require('./first-time-state') const STORAGE_KEY = 'metamask-config' const METAMASK_DEBUG = 'GULP_METAMASK_DEBUG' -const log = require('loglevel') window.log = log log.setDefaultLevel(METAMASK_DEBUG ? 'debug' : 'warn') @@ -29,12 +29,12 @@ let popupIsOpen = false const diskStore = new LocalStorageStore({ storageKey: STORAGE_KEY }) // initialization flow -initialize().catch(console.error) +initialize().catch(log.error) async function initialize () { const initState = await loadStateFromPersistence() await setupController(initState) - console.log('MetaMask initialization complete.') + log.debug('MetaMask initialization complete.') } // diff --git a/app/scripts/contentscript.js b/app/scripts/contentscript.js index acacf5d4c..90a0f1f22 100644 --- a/app/scripts/contentscript.js +++ b/app/scripts/contentscript.js @@ -1,11 +1,12 @@ -const LocalMessageDuplexStream = require('post-message-stream') -const PongStream = require('ping-pong-stream/pong') -const PortStream = require('./lib/port-stream.js') -const ObjectMultiplex = require('./lib/obj-multiplex') -const extension = require('extensionizer') - const fs = require('fs') const path = require('path') +const pump = require('pump') +const LocalMessageDuplexStream = require('post-message-stream') +const PongStream = require('ping-pong-stream/pong') +const ObjectMultiplex = require('obj-multiplex') +const extension = require('extensionizer') +const PortStream = require('./lib/port-stream.js') + const inpageText = fs.readFileSync(path.join(__dirname, 'inpage.js')).toString() // Eventually this streaming injection could be replaced with: @@ -50,22 +51,42 @@ function setupStreams () { pageStream.pipe(pluginStream).pipe(pageStream) // setup local multistream channels - const mx = ObjectMultiplex() - mx.on('error', console.error) - mx.pipe(pageStream).pipe(mx) - mx.pipe(pluginStream).pipe(mx) + const mux = new ObjectMultiplex() + pump( + mux, + pageStream, + mux, + (err) => logStreamDisconnectWarning('MetaMask Inpage', err) + ) + pump( + mux, + pluginStream, + mux, + (err) => logStreamDisconnectWarning('MetaMask Background', err) + ) // connect ping stream const pongStream = new PongStream({ objectMode: true }) - pongStream.pipe(mx.createStream('pingpong')).pipe(pongStream) + pump( + mux, + pongStream, + mux, + (err) => logStreamDisconnectWarning('MetaMask PingPongStream', err) + ) // connect phishing warning stream - const phishingStream = mx.createStream('phishing') + const phishingStream = mux.createStream('phishing') phishingStream.once('data', redirectToPhishingWarning) // ignore unused channels (handled by background, inpage) - mx.ignoreStream('provider') - mx.ignoreStream('publicConfig') + mux.ignoreStream('provider') + mux.ignoreStream('publicConfig') +} + +function logStreamDisconnectWarning (remoteLabel, err) { + let warningMsg = `MetamaskContentscript - lost connection to ${remoteLabel}` + if (err) warningMsg += '\n' + err.stack + console.warn(warningMsg) } function shouldInjectWeb3 () { diff --git a/app/scripts/controllers/transactions.js b/app/scripts/controllers/transactions.js index 58c468e22..fb3be6073 100644 --- a/app/scripts/controllers/transactions.js +++ b/app/scripts/controllers/transactions.js @@ -1,6 +1,5 @@ const EventEmitter = require('events') const extend = require('xtend') -const clone = require('clone') const ObservableStore = require('obs-store') const ethUtil = require('ethereumjs-util') const EthQuery = require('ethjs-query') @@ -8,6 +7,7 @@ const TxProviderUtil = require('../lib/tx-utils') const PendingTransactionTracker = require('../lib/pending-tx-tracker') const createId = require('../lib/random-id') const NonceTracker = require('../lib/nonce-tracker') +const txStateHistoryHelper = require('../lib/tx-state-history-helper') module.exports = class TransactionController extends EventEmitter { constructor (opts) { @@ -33,6 +33,17 @@ module.exports = class TransactionController extends EventEmitter { err: undefined, }) }, + getConfirmedTransactions: (address) => { + return this.getFilteredTxList({ + from: address, + status: 'confirmed', + err: undefined, + }) + }, + giveUpOnTransaction: (txId) => { + const msg = `Gave up submitting after 3500 blocks un-mined.` + this.setTxStatusFailed(txId, msg) + }, }) this.query = new EthQuery(this.provider) this.txProviderUtil = new TxProviderUtil(this.provider) @@ -128,19 +139,17 @@ module.exports = class TransactionController extends EventEmitter { updateTx (txMeta) { // create txMeta snapshot for history - const txMetaForHistory = clone(txMeta) - // dont include previous history in this snapshot - delete txMetaForHistory.history - // add snapshot to tx history - if (!txMeta.history) txMeta.history = [] - txMeta.history.push(txMetaForHistory) + const currentState = txStateHistoryHelper.snapshotFromTxMeta(txMeta) + // recover previous tx state obj + const previousState = txStateHistoryHelper.replayHistory(txMeta.history) + // generate history entry and add to history + const entry = txStateHistoryHelper.generateHistoryEntry(previousState, currentState) + txMeta.history.push(entry) + // commit txMeta to state const txId = txMeta.id const txList = this.getFullTxList() const index = txList.findIndex(txData => txData.id === txId) - if (!txMeta.history) txMeta.history = [] - txMeta.history.push(txMetaForHistory) - txList[index] = txMeta this._saveTxList(txList) this.emit('update') @@ -148,16 +157,22 @@ module.exports = class TransactionController extends EventEmitter { // Adds a tx to the txlist addTx (txMeta) { - const txCount = this.getTxCount() - const network = this.getNetwork() - const fullTxList = this.getFullTxList() - const txHistoryLimit = this.txHistoryLimit + // initialize history + txMeta.history = [] + // capture initial snapshot of txMeta for history + const snapshot = txStateHistoryHelper.snapshotFromTxMeta(txMeta) + txMeta.history.push(snapshot) // checks if the length of the tx history is // longer then desired persistence limit // and then if it is removes only confirmed // or rejected tx's. // not tx's that are pending or unapproved + const txCount = this.getTxCount() + const network = this.getNetwork() + const fullTxList = this.getFullTxList() + const txHistoryLimit = this.txHistoryLimit + if (txCount > txHistoryLimit - 1) { const index = fullTxList.findIndex((metaTx) => ((metaTx.status === 'confirmed' || metaTx.status === 'rejected') && network === txMeta.metamaskNetworkId)) fullTxList.splice(index, 1) @@ -206,7 +221,6 @@ module.exports = class TransactionController extends EventEmitter { status: 'unapproved', metamaskNetworkId: this.getNetwork(), txParams: txParams, - history: [], } // add default tx params await this.addTxDefaults(txMeta) @@ -441,4 +455,4 @@ module.exports = class TransactionController extends EventEmitter { }) this.memStore.updateState({ unapprovedTxs, selectedAddressTxList }) } -} \ No newline at end of file +} diff --git a/app/scripts/keyring-controller.js b/app/scripts/keyring-controller.js index 2edc8060e..fd57fac70 100644 --- a/app/scripts/keyring-controller.js +++ b/app/scripts/keyring-controller.js @@ -171,9 +171,9 @@ class KeyringController extends EventEmitter { return this.setupAccounts(checkedAccounts) }) .then(() => this.persistAllKeyrings()) + .then(() => this._updateMemStoreKeyrings()) .then(() => this.fullUpdate()) .then(() => { - this._updateMemStoreKeyrings() return keyring }) } @@ -208,6 +208,7 @@ class KeyringController extends EventEmitter { return selectedKeyring.addAccounts(1) .then(this.setupAccounts.bind(this)) .then(this.persistAllKeyrings.bind(this)) + .then(this._updateMemStoreKeyrings.bind(this)) .then(this.fullUpdate.bind(this)) } diff --git a/app/scripts/lib/auto-reload.js b/app/scripts/lib/auto-reload.js index 6abce73ea..cce31c3d2 100644 --- a/app/scripts/lib/auto-reload.js +++ b/app/scripts/lib/auto-reload.js @@ -2,33 +2,55 @@ module.exports = setupDappAutoReload function setupDappAutoReload (web3, observable) { // export web3 as a global, checking for usage + let hasBeenWarned = false + let reloadInProgress = false + let lastTimeUsed + let lastSeenNetwork + global.web3 = new Proxy(web3, { - get: (_web3, name) => { - // get the time of use - if (name !== '_used') { - console.warn('MetaMask: web3 will be deprecated in the near future in favor of the ethereumProvider \nhttps://github.com/ethereum/mist/releases/tag/v0.9.0') - _web3._used = Date.now() + get: (_web3, key) => { + // show warning once on web3 access + if (!hasBeenWarned && key !== 'currentProvider') { + console.warn('MetaMask: web3 will be deprecated in the near future in favor of the ethereumProvider \nhttps://github.com/MetaMask/faq/blob/master/detecting_metamask.md#web3-deprecation') + hasBeenWarned = true } - return _web3[name] + // get the time of use + lastTimeUsed = Date.now() + // return value normally + return _web3[key] }, - set: (_web3, name, value) => { - _web3[name] = value + set: (_web3, key, value) => { + // set value normally + _web3[key] = value }, }) - var networkVersion observable.subscribe(function (state) { - // get the initial network - const curentNetVersion = state.networkVersion - if (!networkVersion) networkVersion = curentNetVersion + // if reload in progress, no need to check reload logic + if (reloadInProgress) return - if (curentNetVersion !== networkVersion && web3._used) { - const timeSinceUse = Date.now() - web3._used - // if web3 was recently used then delay the reloading of the page - timeSinceUse > 500 ? triggerReset() : setTimeout(triggerReset, 500) - // prevent reentry into if statement if state updates again before - // reload - networkVersion = curentNetVersion + const currentNetwork = state.networkVersion + + // set the initial network + if (!lastSeenNetwork) { + lastSeenNetwork = currentNetwork + return + } + + // skip reload logic if web3 not used + if (!lastTimeUsed) return + + // if network did not change, exit + if (currentNetwork === lastSeenNetwork) return + + // initiate page reload + reloadInProgress = true + const timeSinceUse = Date.now() - lastTimeUsed + // if web3 was recently used then delay the reloading of the page + if (timeSinceUse > 500) { + triggerReset() + } else { + setTimeout(triggerReset, 500) } }) } diff --git a/app/scripts/lib/createLoggerMiddleware.js b/app/scripts/lib/createLoggerMiddleware.js new file mode 100644 index 000000000..b92a965de --- /dev/null +++ b/app/scripts/lib/createLoggerMiddleware.js @@ -0,0 +1,15 @@ +// log rpc activity +module.exports = createLoggerMiddleware + +function createLoggerMiddleware({ origin }) { + return function loggerMiddleware (req, res, next, end) { + next((cb) => { + if (res.error) { + log.error('Error in RPC response:\n', res) + } + if (req.isMetamaskInternal) return + log.info(`RPC (${origin}):`, req, '->', res) + cb() + }) + } +} \ No newline at end of file diff --git a/app/scripts/lib/createOriginMiddleware.js b/app/scripts/lib/createOriginMiddleware.js new file mode 100644 index 000000000..e1e097cc4 --- /dev/null +++ b/app/scripts/lib/createOriginMiddleware.js @@ -0,0 +1,9 @@ +// append dapp origin domain to request +module.exports = createOriginMiddleware + +function createOriginMiddleware({ origin }) { + return function originMiddleware (req, res, next, end) { + req.origin = origin + next() + } +} \ No newline at end of file diff --git a/app/scripts/lib/createProviderMiddleware.js b/app/scripts/lib/createProviderMiddleware.js new file mode 100644 index 000000000..6dd192411 --- /dev/null +++ b/app/scripts/lib/createProviderMiddleware.js @@ -0,0 +1,13 @@ + +module.exports = createProviderMiddleware + +// forward requests to provider +function createProviderMiddleware({ provider }) { + return (req, res, next, end) => { + provider.sendAsync(req, (err, _res) => { + if (err) return end(err) + res.result = _res.result + end() + }) + } +} \ No newline at end of file diff --git a/app/scripts/lib/inpage-provider.js b/app/scripts/lib/inpage-provider.js index fd032a673..da75c4be2 100644 --- a/app/scripts/lib/inpage-provider.js +++ b/app/scripts/lib/inpage-provider.js @@ -1,8 +1,9 @@ -const pipe = require('pump') -const StreamProvider = require('web3-stream-provider') +const pump = require('pump') +const RpcEngine = require('json-rpc-engine') +const createIdRemapMiddleware = require('json-rpc-engine/src/idRemapMiddleware') +const createStreamMiddleware = require('json-rpc-middleware-stream') const LocalStorageStore = require('obs-store') -const ObjectMultiplex = require('./obj-multiplex') -const createRandomId = require('./random-id') +const ObjectMultiplex = require('obj-multiplex') module.exports = MetamaskInpageProvider @@ -10,60 +11,49 @@ function MetamaskInpageProvider (connectionStream) { const self = this // setup connectionStream multiplexing - var multiStream = self.multiStream = ObjectMultiplex() - pipe( + const mux = self.mux = new ObjectMultiplex() + pump( connectionStream, - multiStream, + mux, connectionStream, (err) => logStreamDisconnectWarning('MetaMask', err) ) // subscribe to metamask public config (one-way) self.publicConfigStore = new LocalStorageStore({ storageKey: 'MetaMask-Config' }) - pipe( - multiStream.createStream('publicConfig'), + pump( + mux.createStream('publicConfig'), self.publicConfigStore, (err) => logStreamDisconnectWarning('MetaMask PublicConfigStore', err) ) // ignore phishing warning message (handled elsewhere) - multiStream.ignoreStream('phishing') + mux.ignoreStream('phishing') // connect to async provider - const asyncProvider = self.asyncProvider = new StreamProvider() - pipe( - asyncProvider, - multiStream.createStream('provider'), - asyncProvider, + const streamMiddleware = createStreamMiddleware() + pump( + streamMiddleware.stream, + mux.createStream('provider'), + streamMiddleware.stream, (err) => logStreamDisconnectWarning('MetaMask RpcProvider', err) ) - // start and stop polling to unblock first block lock - self.idMap = {} - // handle sendAsync requests via asyncProvider - self.sendAsync = function (payload, cb) { - // rewrite request ids - var request = eachJsonMessage(payload, (message) => { - var newId = createRandomId() - self.idMap[newId] = message.id - message.id = newId - return message - }) - // forward to asyncProvider - asyncProvider.sendAsync(request, function (err, res) { - if (err) return cb(err) - // transform messages to original ids - eachJsonMessage(res, (message) => { - var oldId = self.idMap[message.id] - delete self.idMap[message.id] - message.id = oldId - return message - }) - cb(null, res) - }) - } + // handle sendAsync requests via dapp-side rpc engine + const rpcEngine = new RpcEngine() + rpcEngine.push(createIdRemapMiddleware()) + rpcEngine.push(streamMiddleware) + self.rpcEngine = rpcEngine } +// handle sendAsync requests via asyncProvider +// also remap ids inbound and outbound +MetamaskInpageProvider.prototype.sendAsync = function (payload, cb) { + const self = this + self.rpcEngine.handle(payload, cb) +} + + MetamaskInpageProvider.prototype.send = function (payload) { const self = this @@ -80,7 +70,7 @@ MetamaskInpageProvider.prototype.send = function (payload) { case 'eth_coinbase': // read from localStorage selectedAddress = self.publicConfigStore.getState().selectedAddress - result = selectedAddress + result = selectedAddress || null break case 'eth_uninstallFilter': @@ -90,7 +80,7 @@ MetamaskInpageProvider.prototype.send = function (payload) { case 'net_version': const networkVersion = self.publicConfigStore.getState().networkVersion - result = networkVersion + result = networkVersion || null break // throw not-supported Error @@ -109,10 +99,6 @@ MetamaskInpageProvider.prototype.send = function (payload) { } } -MetamaskInpageProvider.prototype.sendAsync = function () { - throw new Error('MetamaskInpageProvider - sendAsync not overwritten') -} - MetamaskInpageProvider.prototype.isConnected = function () { return true } @@ -121,14 +107,6 @@ MetamaskInpageProvider.prototype.isMetaMask = true // util -function eachJsonMessage (payload, transformFn) { - if (Array.isArray(payload)) { - return payload.map(transformFn) - } else { - return transformFn(payload) - } -} - function logStreamDisconnectWarning (remoteLabel, err) { let warningMsg = `MetamaskInpageProvider - lost connection to ${remoteLabel}` if (err) warningMsg += '\n' + err.stack diff --git a/app/scripts/lib/nonce-tracker.js b/app/scripts/lib/nonce-tracker.js index 8328e81ec..0029ac953 100644 --- a/app/scripts/lib/nonce-tracker.js +++ b/app/scripts/lib/nonce-tracker.js @@ -1,13 +1,14 @@ -const EthQuery = require('eth-query') +const EthQuery = require('ethjs-query') const assert = require('assert') const Mutex = require('await-semaphore').Mutex class NonceTracker { - constructor ({ provider, getPendingTransactions }) { + constructor ({ provider, getPendingTransactions, getConfirmedTransactions }) { this.provider = provider this.ethQuery = new EthQuery(provider) this.getPendingTransactions = getPendingTransactions + this.getConfirmedTransactions = getConfirmedTransactions this.lockMap = {} } @@ -25,21 +26,28 @@ class NonceTracker { await this._globalMutexFree() // await lock free, then take lock const releaseLock = await this._takeMutex(address) - // 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 pendingTransactions = this.getPendingTransactions(address) - const pendingCount = pendingTransactions.length - assert(Number.isInteger(pendingCount), `nonce-tracker - pendingCount is not an integer - got: (${typeof pendingCount}) "${pendingCount}"`) - const baseCountHex = await this._getTxCount(address, currentBlock) - const baseCount = parseInt(baseCountHex, 16) - assert(Number.isInteger(baseCount), `nonce-tracker - baseCount is not an integer - got: (${typeof baseCount}) "${baseCount}"`) - const nextNonce = baseCount + pendingCount + // evaluate multiple nextNonce strategies + const nonceDetails = {} + const networkNonceResult = await this._getNetworkNextNonce(address) + const highestLocallyConfirmed = this._getHighestLocallyConfirmed(address) + const nextNetworkNonce = networkNonceResult.nonce + const highestLocalNonce = highestLocallyConfirmed + const highestSuggested = Math.max(nextNetworkNonce, highestLocalNonce) + + const pendingTxs = this.getPendingTransactions(address) + const localNonceResult = this._getHighestContinuousFrom(pendingTxs, highestSuggested) || 0 + + nonceDetails.params = { + highestLocalNonce, + highestSuggested, + nextNetworkNonce, + } + nonceDetails.local = localNonceResult + nonceDetails.network = networkNonceResult + + const nextNonce = Math.max(networkNonceResult.nonce, localNonceResult.nonce) assert(Number.isInteger(nextNonce), `nonce-tracker - nextNonce is not an integer - got: (${typeof nextNonce}) "${nextNonce}"`) - // collect the numbers used to calculate the nonce for debugging - const blockNumber = currentBlock.number - const nonceDetails = { blockNumber, baseCount, baseCountHex, pendingCount } + // return nonce and release cb return { nextNonce, nonceDetails, releaseLock } } @@ -53,15 +61,6 @@ class NonceTracker { }) } - async _getTxCount (address, currentBlock) { - const blockNumber = currentBlock.number - return new Promise((resolve, reject) => { - this.ethQuery.getTransactionCount(address, blockNumber, (err, result) => { - err ? reject(err) : resolve(result) - }) - }) - } - async _globalMutexFree () { const globalMutex = this._lookupMutex('global') const release = await globalMutex.acquire() @@ -83,12 +82,68 @@ class NonceTracker { return mutex } + async _getNetworkNextNonce (address) { + // 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 baseCount = baseCountBN.toNumber() + assert(Number.isInteger(baseCount), `nonce-tracker - baseCount is not an integer - got: (${typeof baseCount}) "${baseCount}"`) + const nonceDetails = { blockNumber, baseCount } + return { name: 'network', nonce: baseCount, details: nonceDetails } + } + + _getHighestLocallyConfirmed (address) { + const confirmedTransactions = this.getConfirmedTransactions(address) + const highest = this._getHighestNonce(confirmedTransactions) + return Number.isInteger(highest) ? highest + 1 : 0 + } + + _reduceTxListToUniqueNonces (txList) { + const reducedTxList = txList.reduce((reducedList, txMeta, index) => { + if (!index) return [txMeta] + const nonceMatches = txList.filter((txData) => { + return txMeta.txParams.nonce === txData.txParams.nonce + }) + if (nonceMatches.length > 1) return reducedList + reducedList.push(txMeta) + return reducedList + }, []) + return reducedTxList + } + + _getHighestNonce (txList) { + const nonces = txList.map((txMeta) => { + const nonce = txMeta.txParams.nonce + assert(typeof nonce, 'string', 'nonces should be hex strings') + return parseInt(nonce, 16) + }) + const highestNonce = Math.max.apply(null, nonces) + return highestNonce + } + + _getHighestContinuousFrom (txList, startPoint) { + const nonces = txList.map((txMeta) => { + const nonce = txMeta.txParams.nonce + assert(typeof nonce, 'string', 'nonces should be hex strings') + return parseInt(nonce, 16) + }) + + let highest = startPoint + while (nonces.includes(highest)) { + highest++ + } + + return { name: 'local', nonce: highest, details: { startPoint, highest } } + } + // this is a hotfix for the fact that the blockTracker will // change when the network changes _getBlockTracker () { return this.provider._blockTracker } - } module.exports = NonceTracker diff --git a/app/scripts/lib/obj-multiplex.js b/app/scripts/lib/obj-multiplex.js deleted file mode 100644 index 0034febe0..000000000 --- a/app/scripts/lib/obj-multiplex.js +++ /dev/null @@ -1,48 +0,0 @@ -const through = require('through2') - -module.exports = ObjectMultiplex - -function ObjectMultiplex (opts) { - opts = opts || {} - // create multiplexer - const mx = through.obj(function (chunk, enc, cb) { - const name = chunk.name - const data = chunk.data - if (!name) { - console.warn(`ObjectMultiplex - Malformed chunk without name "${chunk}"`) - return cb() - } - const substream = mx.streams[name] - if (!substream) { - console.warn(`ObjectMultiplex - orphaned data for stream "${name}"`) - } else { - if (substream.push) substream.push(data) - } - return cb() - }) - mx.streams = {} - // create substreams - mx.createStream = function (name) { - const substream = mx.streams[name] = through.obj(function (chunk, enc, cb) { - mx.push({ - name: name, - data: chunk, - }) - return cb() - }) - mx.on('end', function () { - return substream.emit('end') - }) - if (opts.error) { - mx.on('error', function () { - return substream.emit('error') - }) - } - return substream - } - // ignore streams (dont display orphaned data warning) - mx.ignoreStream = function (name) { - mx.streams[name] = true - } - return mx -} diff --git a/app/scripts/lib/pending-tx-tracker.js b/app/scripts/lib/pending-tx-tracker.js index 19720db3f..b90851b58 100644 --- a/app/scripts/lib/pending-tx-tracker.js +++ b/app/scripts/lib/pending-tx-tracker.js @@ -1,6 +1,7 @@ const EventEmitter = require('events') const EthQuery = require('ethjs-query') const sufficientBalance = require('./util').sufficientBalance +const RETRY_LIMIT = 3500 // Retry 3500 blocks, or about 1 day. /* Utility class for tracking the transactions as they @@ -28,6 +29,7 @@ module.exports = class PendingTransactionTracker extends EventEmitter { this.getBalance = config.getBalance this.getPendingTransactions = config.getPendingTransactions this.publishTransaction = config.publishTransaction + this.giveUpOnTransaction = config.giveUpOnTransaction } // checks if a signed tx is in a block and @@ -100,6 +102,10 @@ module.exports = class PendingTransactionTracker extends EventEmitter { if (balance === undefined) return if (!('retryCount' in txMeta)) txMeta.retryCount = 0 + if (txMeta.retryCount > RETRY_LIMIT) { + return this.giveUpOnTransaction(txMeta.id) + } + // if the value of the transaction is greater then the balance, fail. if (!sufficientBalance(txMeta.txParams, balance)) { const insufficientFundsError = new Error('Insufficient balance during rebroadcast.') @@ -160,4 +166,4 @@ module.exports = class PendingTransactionTracker extends EventEmitter { } nonceGlobalLock.releaseLock() } -} \ No newline at end of file +} diff --git a/app/scripts/lib/port-stream.js b/app/scripts/lib/port-stream.js index 607a9c9ed..648d88087 100644 --- a/app/scripts/lib/port-stream.js +++ b/app/scripts/lib/port-stream.js @@ -1,5 +1,6 @@ const Duplex = require('readable-stream').Duplex const inherits = require('util').inherits +const noop = function(){} module.exports = PortDuplexStream @@ -20,20 +21,14 @@ PortDuplexStream.prototype._onMessage = function (msg) { if (Buffer.isBuffer(msg)) { delete msg._isBuffer var data = new Buffer(msg) - // console.log('PortDuplexStream - saw message as buffer', data) this.push(data) } else { - // console.log('PortDuplexStream - saw message', msg) this.push(msg) } } PortDuplexStream.prototype._onDisconnect = function () { - try { - this.push(null) - } catch (err) { - this.emit('error', err) - } + this.destroy() } // stream plumbing @@ -45,19 +40,12 @@ PortDuplexStream.prototype._write = function (msg, encoding, cb) { if (Buffer.isBuffer(msg)) { var data = msg.toJSON() data._isBuffer = true - // console.log('PortDuplexStream - sent message as buffer', data) this._port.postMessage(data) } else { - // console.log('PortDuplexStream - sent message', msg) this._port.postMessage(msg) } } catch (err) { - // console.error(err) return cb(new Error('PortDuplexStream - disconnected')) } cb() } - -// util - -function noop () {} diff --git a/app/scripts/lib/stream-utils.js b/app/scripts/lib/stream-utils.js index ba79990cc..8bb0b4f3c 100644 --- a/app/scripts/lib/stream-utils.js +++ b/app/scripts/lib/stream-utils.js @@ -1,6 +1,6 @@ const Through = require('through2') -const endOfStream = require('end-of-stream') -const ObjectMultiplex = require('./obj-multiplex') +const ObjectMultiplex = require('obj-multiplex') +const pump = require('pump') module.exports = { jsonParseStream: jsonParseStream, @@ -23,14 +23,14 @@ function jsonStringifyStream () { } function setupMultiplex (connectionStream) { - var mx = ObjectMultiplex() - connectionStream.pipe(mx).pipe(connectionStream) - endOfStream(mx, function (err) { - if (err) console.error(err) - }) - endOfStream(connectionStream, function (err) { - if (err) console.error(err) - mx.destroy() - }) - return mx + const mux = new ObjectMultiplex() + pump( + connectionStream, + mux, + connectionStream, + (err) => { + if (err) console.error(err) + } + ) + return mux } diff --git a/app/scripts/lib/tx-state-history-helper.js b/app/scripts/lib/tx-state-history-helper.js new file mode 100644 index 000000000..304069d57 --- /dev/null +++ b/app/scripts/lib/tx-state-history-helper.js @@ -0,0 +1,37 @@ +const jsonDiffer = require('fast-json-patch') +const clone = require('clone') + +module.exports = { + generateHistoryEntry, + replayHistory, + snapshotFromTxMeta, + migrateFromSnapshotsToDiffs, +} + + +function migrateFromSnapshotsToDiffs(longHistory) { + return ( + longHistory + // convert non-initial history entries into diffs + .map((entry, index) => { + if (index === 0) return entry + return generateHistoryEntry(longHistory[index - 1], entry) + }) + ) +} + +function generateHistoryEntry(previousState, newState) { + return jsonDiffer.compare(previousState, newState) +} + +function replayHistory(shortHistory) { + return shortHistory.reduce((val, entry) => jsonDiffer.applyPatch(val, entry).newDocument) +} + +function snapshotFromTxMeta(txMeta) { + // create txMeta snapshot for history + const snapshot = clone(txMeta) + // dont include previous history in this snapshot + delete snapshot.history + return snapshot +} \ No newline at end of file diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index a007d6fc5..fef16c3a9 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -1,12 +1,18 @@ const EventEmitter = require('events') const extend = require('xtend') const promiseToCallback = require('promise-to-callback') -const pipe = require('pump') +const pump = require('pump') const Dnode = require('dnode') const ObservableStore = require('obs-store') const EthStore = require('./lib/eth-store') const EthQuery = require('eth-query') -const streamIntoProvider = require('web3-stream-provider/handler') +const RpcEngine = require('json-rpc-engine') +const debounce = require('debounce') +const createEngineStream = require('json-rpc-middleware-stream/engineStream') +const createFilterMiddleware = require('eth-json-rpc-filters') +const createOriginMiddleware = require('./lib/createOriginMiddleware') +const createLoggerMiddleware = require('./lib/createLoggerMiddleware') +const createProviderMiddleware = require('./lib/createProviderMiddleware') const setupMultiplex = require('./lib/stream-utils.js').setupMultiplex const KeyringController = require('./keyring-controller') const NetworkController = require('./controllers/network') @@ -24,8 +30,6 @@ const ConfigManager = require('./lib/config-manager') const nodeify = require('./lib/nodeify') const accountImporter = require('./account-import-strategies') const getBuyEthUrl = require('./lib/buy-eth-url') -const debounce = require('debounce') - const version = require('../manifest.json').version module.exports = class MetamaskController extends EventEmitter { @@ -77,12 +81,13 @@ module.exports = class MetamaskController extends EventEmitter { // rpc provider this.provider = this.initializeProvider() + this.blockTracker = this.provider // eth data query tools this.ethQuery = new EthQuery(this.provider) this.ethStore = new EthStore({ provider: this.provider, - blockTracker: this.provider, + blockTracker: this.blockTracker, }) // key mgmt @@ -109,7 +114,7 @@ module.exports = class MetamaskController extends EventEmitter { getNetwork: this.networkController.getNetworkState.bind(this), signTransaction: this.keyringController.signTransaction.bind(this.keyringController), provider: this.provider, - blockTracker: this.provider, + blockTracker: this.blockTracker, ethQuery: this.ethQuery, ethStore: this.ethStore, }) @@ -337,36 +342,43 @@ module.exports = class MetamaskController extends EventEmitter { setupUntrustedCommunication (connectionStream, originDomain) { // Check if new connection is blacklisted if (this.blacklistController.checkForPhishing(originDomain)) { - console.log('MetaMask - sending phishing warning for', originDomain) + log.debug('MetaMask - sending phishing warning for', originDomain) this.sendPhishingWarning(connectionStream, originDomain) return } // setup multiplexing - const mx = setupMultiplex(connectionStream) + const mux = setupMultiplex(connectionStream) // connect features - this.setupProviderConnection(mx.createStream('provider'), originDomain) - this.setupPublicConfig(mx.createStream('publicConfig')) + this.setupProviderConnection(mux.createStream('provider'), originDomain) + this.setupPublicConfig(mux.createStream('publicConfig')) } setupTrustedCommunication (connectionStream, originDomain) { // setup multiplexing - const mx = setupMultiplex(connectionStream) + const mux = setupMultiplex(connectionStream) // connect features - this.setupControllerConnection(mx.createStream('controller')) - this.setupProviderConnection(mx.createStream('provider'), originDomain) + this.setupControllerConnection(mux.createStream('controller')) + this.setupProviderConnection(mux.createStream('provider'), originDomain) } sendPhishingWarning (connectionStream, hostname) { - const mx = setupMultiplex(connectionStream) - const phishingStream = mx.createStream('phishing') + const mux = setupMultiplex(connectionStream) + const phishingStream = mux.createStream('phishing') phishingStream.write({ hostname }) } setupControllerConnection (outStream) { const api = this.getApi() const dnode = Dnode(api) - outStream.pipe(dnode).pipe(outStream) + pump( + outStream, + dnode, + outStream, + (err) => { + if (err) log.error(err) + } + ) dnode.on('remote', (remote) => { // push updates to popup const sendUpdate = remote.sendUpdate.bind(remote) @@ -374,27 +386,42 @@ module.exports = class MetamaskController extends EventEmitter { }) } - setupProviderConnection (outStream, originDomain) { - streamIntoProvider(outStream, this.provider, onRequest, onResponse) - // append dapp origin domain to request - function onRequest (request) { - request.origin = originDomain - } - // log rpc activity - function onResponse (err, request, response) { - if (err) return console.error(err) - if (response.error) { - console.error('Error in RPC response:\n', response) + setupProviderConnection (outStream, origin) { + // setup json rpc engine stack + const engine = new RpcEngine() + + // create filter polyfill middleware + const filterMiddleware = createFilterMiddleware({ + provider: this.provider, + blockTracker: this.blockTracker, + }) + + engine.push(createOriginMiddleware({ origin })) + engine.push(createLoggerMiddleware({ origin })) + engine.push(filterMiddleware) + engine.push(createProviderMiddleware({ provider: this.provider })) + + // setup connection + const providerStream = createEngineStream({ engine }) + pump( + outStream, + providerStream, + outStream, + (err) => { + // cleanup filter polyfill middleware + filterMiddleware.destroy() + if (err) log.error(err) } - if (request.isMetamaskInternal) return - log.info(`RPC (${originDomain}):`, request, '->', response) - } + ) } setupPublicConfig (outStream) { - pipe( + pump( this.publicConfigStore, - outStream + outStream, + (err) => { + if (err) log.error(err) + } ) } diff --git a/app/scripts/migrations/018.js b/app/scripts/migrations/018.js new file mode 100644 index 000000000..d27fe3f46 --- /dev/null +++ b/app/scripts/migrations/018.js @@ -0,0 +1,52 @@ +const version = 18 + +/* + +This migration updates "transaction state history" to diffs style + +*/ + +const clone = require('clone') +const txStateHistoryHelper = require('../lib/tx-state-history-helper') + + +module.exports = { + version, + + migrate: function (originalVersionedData) { + const versionedData = clone(originalVersionedData) + versionedData.meta.version = version + try { + const state = versionedData.data + const newState = transformState(state) + versionedData.data = newState + } catch (err) { + console.warn(`MetaMask Migration #${version}` + err.stack) + } + return Promise.resolve(versionedData) + }, +} + +function transformState (state) { + const newState = state + const transactions = newState.TransactionController.transactions + newState.TransactionController.transactions = transactions.map((txMeta) => { + // no history: initialize + if (!txMeta.history || txMeta.history.length === 0) { + const snapshot = txStateHistoryHelper.snapshotFromTxMeta(txMeta) + txMeta.history = [snapshot] + return txMeta + } + // has history: migrate + const newHistory = ( + txStateHistoryHelper.migrateFromSnapshotsToDiffs(txMeta.history) + // remove empty diffs + .filter((entry) => { + return !Array.isArray(entry) || entry.length > 0 + }) + ) + txMeta.history = newHistory + return txMeta + }) + return newState +} diff --git a/app/scripts/migrations/019.js b/app/scripts/migrations/019.js new file mode 100644 index 000000000..072c96370 --- /dev/null +++ b/app/scripts/migrations/019.js @@ -0,0 +1,83 @@ + +const version = 19 + +/* + +This migration sets transactions as failed +whos nonce is too high + +*/ + +const clone = require('clone') + +module.exports = { + version, + + migrate: function (originalVersionedData) { + const versionedData = clone(originalVersionedData) + versionedData.meta.version = version + try { + const state = versionedData.data + const newState = transformState(state) + versionedData.data = newState + } catch (err) { + console.warn(`MetaMask Migration #${version}` + err.stack) + } + return Promise.resolve(versionedData) + }, +} + +function transformState (state) { + const newState = state + const transactions = newState.TransactionController.transactions + + newState.TransactionController.transactions = transactions.map((txMeta, _, txList) => { + if (txMeta.status !== 'submitted') return txMeta + + const confirmedTxs = txList.filter((tx) => tx.status === 'confirmed') + .filter((tx) => tx.txParams.from === txMeta.txParams.from) + .filter((tx) => tx.metamaskNetworkId.from === txMeta.metamaskNetworkId.from) + const highestConfirmedNonce = getHighestNonce(confirmedTxs) + + const pendingTxs = txList.filter((tx) => tx.status === 'submitted') + .filter((tx) => tx.txParams.from === txMeta.txParams.from) + .filter((tx) => tx.metamaskNetworkId.from === txMeta.metamaskNetworkId.from) + const highestContinuousNonce = getHighestContinuousFrom(pendingTxs, highestConfirmedNonce) + + const maxNonce = Math.max(highestContinuousNonce, highestConfirmedNonce) + + if (parseInt(txMeta.txParams.nonce, 16) > maxNonce + 1) { + txMeta.status = 'failed' + txMeta.err = { + message: 'nonce too high', + note: 'migration 019 custom error', + } + } + return txMeta + }) + return newState +} + +function getHighestContinuousFrom (txList, startPoint) { + const nonces = txList.map((txMeta) => { + const nonce = txMeta.txParams.nonce + return parseInt(nonce, 16) + }) + + let highest = startPoint + while (nonces.includes(highest)) { + highest++ + } + + return highest +} + +function getHighestNonce (txList) { + const nonces = txList.map((txMeta) => { + const nonce = txMeta.txParams.nonce + return parseInt(nonce || '0x0', 16) + }) + const highestNonce = Math.max.apply(null, nonces) + return highestNonce +} + diff --git a/app/scripts/migrations/index.js b/app/scripts/migrations/index.js index f4c87499f..e9cbd7b98 100644 --- a/app/scripts/migrations/index.js +++ b/app/scripts/migrations/index.js @@ -28,4 +28,6 @@ module.exports = [ require('./015'), require('./016'), require('./017'), + require('./018'), + require('./019'), ] diff --git a/circle.yml b/circle.yml index 2ea60bb9d..f5da6857d 100644 --- a/circle.yml +++ b/circle.yml @@ -1,10 +1,17 @@ machine: node: version: 8.1.4 -dependencies: - pre: - - "npm i -g testem" - - "npm i -g mocha" test: override: - - "npm run ci" \ No newline at end of file + - "npm run ci" +dependencies: + pre: + - sudo apt-get update + # get latest stable firefox + - sudo apt-get install firefox + - firefox_cmd=`which firefox`; sudo rm -f $firefox_cmd; sudo ln -s `which firefox.ubuntu` $firefox_cmd + # get latest stable chrome + - wget -q -O - https://dl-ssl.google.com/linux/linux_signing_key.pub | sudo apt-key add - + - sudo sh -c 'echo "deb [arch=amd64] http://dl.google.com/linux/chrome/deb/ stable main" >> /etc/apt/sources.list.d/google.list' + - sudo apt-get update + - sudo apt-get install google-chrome-stable \ No newline at end of file diff --git a/development/uiStore.js b/development/uiStore.js index 1299ee1dc..c71d66d3b 100644 --- a/development/uiStore.js +++ b/development/uiStore.js @@ -1,7 +1,7 @@ const createStore = require('redux').createStore const applyMiddleware = require('redux').applyMiddleware -const thunkMiddleware = require('redux-thunk') -const createLogger = require('redux-logger') +const thunkMiddleware = require('redux-thunk').default +const createLogger = require('redux-logger').createLogger const rootReducer = require('../ui/app/reducers') module.exports = configureStore diff --git a/docs/add-to-firef.md b/docs/add-to-firefox.md similarity index 100% rename from docs/add-to-firef.md rename to docs/add-to-firefox.md diff --git a/karma.conf.js b/karma.conf.js new file mode 100644 index 000000000..8e6d55972 --- /dev/null +++ b/karma.conf.js @@ -0,0 +1,61 @@ +// Karma configuration +// Generated on Mon Sep 11 2017 18:45:48 GMT-0700 (PDT) + +module.exports = function(config) { + config.set({ + // base path that will be used to resolve all patterns (eg. files, exclude) + basePath: process.cwd(), + + browserConsoleLogOptions: { + terminal: false, + }, + + // frameworks to use + // available frameworks: https://npmjs.org/browse/keyword/karma-adapter + frameworks: ['qunit'], + + // list of files / patterns to load in the browser + files: [ + 'development/bundle.js', + 'test/integration/jquery-3.1.0.min.js', + 'test/integration/bundle.js', + { pattern: 'dist/chrome/images/**/*.*', watched: false, included: false, served: true }, + { pattern: 'dist/chrome/fonts/**/*.*', watched: false, included: false, served: true }, + ], + + proxies: { + '/images/': '/base/dist/chrome/images/', + '/fonts/': '/base/dist/chrome/fonts/', + }, + + // test results reporter to use + // possible values: 'dots', 'progress' + // available reporters: https://npmjs.org/browse/keyword/karma-reporter + reporters: ['progress'], + + // web server port + port: 9876, + + // enable / disable colors in the output (reporters and logs) + colors: true, + + // level of logging + // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG + logLevel: config.LOG_INFO, + + // enable / disable watching file and executing tests whenever any file changes + autoWatch: false, + + // start these browsers + // available browser launchers: https://npmjs.org/browse/keyword/karma-launcher + browsers: ['Chrome', 'Firefox'], + + // Continuous Integration mode + // if true, Karma captures browsers, runs the tests and exits + singleRun: true, + + // Concurrency level + // how many browser should be started simultaneous + concurrency: Infinity + }) +} diff --git a/mascara/src/lib/setup-iframe.js b/mascara/src/lib/setup-iframe.js index db67163df..dcf404574 100644 --- a/mascara/src/lib/setup-iframe.js +++ b/mascara/src/lib/setup-iframe.js @@ -1,5 +1,5 @@ const Iframe = require('iframe') -const IframeStream = require('iframe-stream').IframeStream +const createIframeStream = require('iframe-stream').IframeStream module.exports = setupIframe @@ -13,7 +13,7 @@ function setupIframe(opts) { }) var iframe = frame.iframe iframe.style.setProperty('display', 'none') - var iframeStream = new IframeStream(iframe) + var iframeStream = createIframeStream(iframe) return iframeStream } diff --git a/mascara/src/proxy.js b/mascara/src/proxy.js index eabc547b4..5b95175f1 100644 --- a/mascara/src/proxy.js +++ b/mascara/src/proxy.js @@ -1,4 +1,4 @@ -const ParentStream = require('iframe-stream').ParentStream +const createParentStream = require('iframe-stream').ParentStream const SWcontroller = require('client-sw-ready-event/lib/sw-client.js') const SwStream = require('sw-stream/lib/sw-stream.js') const SetupUntrustedComunication = ('./lib/setup-untrusted-connection.js') @@ -11,7 +11,7 @@ const background = new SWcontroller({ intervalDelay, }) -const pageStream = new ParentStream() +const pageStream = createParentStream() background.on('ready', (_) => { let swStream = SwStream({ serviceWorker: background.controller, diff --git a/mock-dev.js b/mock-dev.js index 8e1923a82..b6652bdf7 100644 --- a/mock-dev.js +++ b/mock-dev.js @@ -85,40 +85,47 @@ actions.update = function(stateName) { var css = MetaMaskUiCss() injectCss(css) -const container = document.querySelector('#app-content') - // parse opts var store = configureStore(firstState) // start app -render( - h('.super-dev-container', [ +startApp() - h('button', { - onClick: (ev) => { - ev.preventDefault() - store.dispatch(actions.update('terms')) - }, - style: { - margin: '19px 19px 0px 19px', - }, - }, 'Reset State'), +function startApp(){ + const body = document.body + const container = document.createElement('div') + container.id = 'app-content' + body.appendChild(container) + console.log('container', container) - h(Selector, { actions, selectedKey: selectedView, states, store }), + render( + h('.super-dev-container', [ - h('.mock-app-root', { - style: { - height: '500px', - width: '360px', - boxShadow: 'grey 0px 2px 9px', - margin: '20px', - }, - }, [ - h(Root, { - store: store, - }), - ]), + h('button', { + onClick: (ev) => { + ev.preventDefault() + store.dispatch(actions.update('terms')) + }, + style: { + margin: '19px 19px 0px 19px', + }, + }, 'Reset State'), - ] -), container) + h(Selector, { actions, selectedKey: selectedView, states, store }), + h('.mock-app-root', { + style: { + height: '500px', + width: '360px', + boxShadow: 'grey 0px 2px 9px', + margin: '20px', + }, + }, [ + h(Root, { + store: store, + }), + ]), + + ] + ), container) +} diff --git a/package.json b/package.json index 15cb058ec..9d72360df 100644 --- a/package.json +++ b/package.json @@ -12,8 +12,8 @@ "test": "npm run lint && npm run test-unit && npm run test-integration", "test-unit": "METAMASK_ENV=test mocha --require test/helper.js --recursive \"test/unit/**/*.js\"", "single-test": "METAMASK_ENV=test mocha --require test/helper.js", - "test-integration": "npm run buildMock && npm run buildCiUnits && testem ci -P 2", - "test-coverage": "nyc npm run test-unit && nyc report --reporter=text-lcov | coveralls", + "test-integration": "npm run buildMock && npm run buildCiUnits && karma start", + "test-coverage": "nyc npm run test-unit && if [ $COVERALLS_REPO_TOKEN ]; then nyc report --reporter=text-lcov | coveralls; fi", "ci": "npm run lint && npm run test-coverage && npm run test-integration", "lint": "gulp lint", "buildCiUnits": "node test/integration/index.js", @@ -22,7 +22,6 @@ "ui": "npm run genStates && beefy ui-dev.js:bundle.js --live --open --index=./development/index.html --cwd ./", "mock": "beefy mock-dev.js:bundle.js --live --open --index=./development/index.html --cwd ./", "buildMock": "npm run genStates && browserify ./mock-dev.js -o ./development/bundle.js", - "testem": "npm run buildMock && testem", "announce": "node development/announcer.js", "generateNotice": "node notices/notice-generator.js", "deleteNotice": "node notices/notice-delete.js", @@ -74,11 +73,12 @@ "eth-bin-to-ops": "^1.0.1", "eth-contract-metadata": "^1.1.4", "eth-hd-keyring": "^1.1.1", + "eth-json-rpc-filters": "^1.1.0", "eth-phishing-detect": "^1.1.4", "eth-query": "^2.1.2", "eth-sig-util": "^1.2.2", "eth-simple-keyring": "^1.1.1", - "eth-token-tracker": "^1.1.2", + "eth-token-tracker": "^1.1.3", "ethereumjs-tx": "^1.3.0", "ethereumjs-util": "github:ethereumjs/ethereumjs-util#ac5d0908536b447083ea422b435da27f26615de9", "ethereumjs-wallet": "^0.6.0", @@ -88,6 +88,7 @@ "express": "^4.14.0", "extension-link-enabler": "^1.0.0", "extensionizer": "^1.0.0", + "fast-json-patch": "^2.0.4", "fast-levenshtein": "^2.0.6", "gulp": "github:gulpjs/gulp#4.0", "gulp-autoprefixer": "^4.0.0", @@ -101,12 +102,15 @@ "iframe-stream": "^3.0.0", "inject-css": "^0.1.1", "jazzicon": "^1.2.0", + "json-rpc-engine": "^3.1.0", + "json-rpc-middleware-stream": "^1.0.0", "loglevel": "^1.4.1", "metamask-logo": "^2.1.2", "mississippi": "^1.2.0", "mkdirp": "^0.5.1", "multiplex": "^6.7.0", "number-to-bn": "^1.7.0", + "obj-multiplex": "^1.0.0", "obs-store": "^2.3.1", "once": "^1.3.3", "ping-pong-stream": "^1.0.0", @@ -130,7 +134,7 @@ "react-tooltip-component": "^0.3.0", "react-transition-group": "^2.2.0", "reactify": "^1.1.1", - "readable-stream": "^2.1.2", + "readable-stream": "^2.3.3", "redux": "^3.0.5", "redux-logger": "^3.0.6", "redux-thunk": "^2.2.0", @@ -150,7 +154,7 @@ }, "devDependencies": { "babel-core": "^6.24.1", - "babel-eslint": "^7.2.3", + "babel-eslint": "^8.0.0", "babel-plugin-transform-async-to-generator": "^6.24.1", "babel-plugin-transform-runtime": "^6.23.0", "babel-polyfill": "^6.23.0", @@ -186,6 +190,11 @@ "jsdom-global": "^3.0.2", "jshint-stylish": "~2.2.1", "json-rpc-engine": "^3.0.1", + "karma": "^1.7.1", + "karma-chrome-launcher": "^2.2.0", + "karma-cli": "^1.0.1", + "karma-firefox-launcher": "^1.0.1", + "karma-qunit": "^1.2.1", "lodash.assign": "^4.0.6", "mocha": "^3.4.2", "mocha-eslint": "^4.0.0", @@ -200,8 +209,8 @@ "react-addons-test-utils": "^15.5.1", "react-test-renderer": "^15.5.4", "react-testutils-additions": "^15.2.0", - "sinon": "^2.3.8", "stylelint-config-standard": "^17.0.0", + "sinon": "^3.2.0", "tape": "^4.5.1", "testem": "^1.10.3", "uglifyify": "^4.0.2", diff --git a/test/data/v17-long-history.json b/test/data/v17-long-history.json new file mode 100644 index 000000000..a33d425f8 --- /dev/null +++ b/test/data/v17-long-history.json @@ -0,0 +1,3053 @@ +{ + "meta": { + "version": 17 + }, + "data": { + "config": {}, + "NetworkController": { + "provider": { + "type": "ropsten", + "rpcTarget": "https://ropsten.infura.io/metamask" + }, + "network": "3" + }, + "NoticeController": { + "noticesList": [ + { + "read": true, + "date": "Thu Feb 09 2017", + "title": "Terms of Use", + "body": "", + "id": 0 + }, + { + "read": true, + "date": "Mon May 08 2017", + "title": "Privacy Notice", + "body": "", + "id": 2 + } + ] + }, + "CurrencyController": { + "currentCurrency": "USD", + "conversionRate": 295.81988556, + "conversionDate": 1502734981 + }, + "KeyringController": { + "vault": "{\"data\":\"fFwVD3Msyq1o9NsDbjMlyJ1ZfoMcqfTgjR9cium0C5Vnpk9IM6f/RTVXfk0J4c4UkbgbKd++q8t1S+D22s7Ddz/BT/fe0GrbwPvAYQi1oJuOI9/Lf7I0JbESGv4PheijCIH4h/FiO+tIAuqM0Co3PULM4mOHdzXD8SWmzxbDGx+4wG84EQE9a1NEbqEjyqrX02h3NwZsjrSeuV5TibpGJB9vnKNpDu9wF0DVKLtLCG5n67uoTI/ve9Z7hIDa03vNi/71iE4avFb6ogE2SAkFDncEcU0xXVkBMapBXjrpe5sIq08Ddo0Hhi4fkd4yFW77sAH4TKzd6bWSn2AK8HL8Gpcrk4R6Cvv8EtyjUqsOJfE4AmYI6rWfFutLqEAp\",\"iv\":\"9fJ/OGDVwUnu3H0U71qOGA==\",\"salt\":\"5yGOu/+yrrb3DyP+cvMKIZqjhSjrEY+bnceHnz9n8gM=\"}", + "walletNicknames": { + "0x3ae39e89dc7e736cce53091057a45bf44b1a566c": "Account 1", + "0xa7a467edcb16a51976418ec6133f14f7939dc378": "Account 2", + "0x03ce38bd04b4ad7581a7070570381a530951ebbe": "Account 3" + } + }, + "PreferencesController": { + "frequentRpcList": [], + "currentAccountTab": "history", + "tokens": [], + "selectedAddress": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c" + }, + "seedWords": null, + "InfuraController": { + "infuraNetworkStatus": { + "mainnet": "degraded", + "ropsten": "ok", + "kovan": "ok", + "rinkeby": "ok" + } + }, + "BlacklistController": { + "phishing": { + "version": 2, + "tolerance": 2, + "fuzzylist": [ + "metamask.io", + "myetherwallet.com" + ], + "whitelist": [ + "metamask.io", + "myetherwallet.com", + "ethereum.org", + "myetheroll.com", + "myetherapi.com", + "ledgerwallet.com", + "etherscan.io", + "etherid.org", + "ether.cards", + "etheroll.com", + "ethnews.com", + "ethex.market", + "ethereumdev.io", + "ethereumdev.kr", + "dether.io", + "ethermine.org", + "slaask.com", + "etherbtc.io", + "ethereal.capital", + "etherisc.com", + "m.famalk.net", + "etherecho.com", + "ethereum.os.tc", + "theethereum.wiki", + "metajack.im", + "etherhub.io", + "ethereum.network", + "ethereum.link", + "ethereum.com", + "prethereum.org", + "ethereumj.io", + "etheraus.com", + "ethereum.dev", + "1ethereum.ru", + "ethereum.nz", + "nethereum.com", + "metabank.com", + "metamas.com", + "metabase.com" + ], + "blacklist": [ + "numerai.tech", + "decentraiand.org", + "blockcrein.info", + "blockchealn.info", + "bllookchain.info", + "blockcbhain.info", + "myetherwallet.com.ethpromonodes.com", + "mettamask.io", + "tokenswap.org", + "netherum.com", + "etherexx.org", + "etherume.io", + "ethereum.plus", + "ehtereum.org", + "etereurm.org", + "etheream.com", + "ethererum.org", + "ethereum.io", + "0xtoken.com", + "cryptoalliance.herokuapp.com", + "bitspark2.com", + "indorsetoken.com", + "bittreat.com", + "iconexus.tk", + "iconexus.ml", + "iconexus.ga", + "iconexus.cf", + "etherwallet.online", + "wallet-ethereum.net", + "bitsdigit.com", + "etherswap.org", + "eos.ac", + "uasfwallet.com", + "ziber.io", + "multiply-ethereum.info", + "bittrex.comze.com", + "karbon.vacau.com", + "etherdelta.gitlhub.io", + "etherdelta.glthub.io", + "digitaldevelopersfund.vacau.com", + "district-0x.io", + "coin-dash.com", + "coindash.ru", + "district0x.net", + "aragonproject.io", + "coin-wallet.info", + "coinswallet.info", + "contribute-status.im", + "ether-api.com", + "ether-wall.com", + "mycoinwallet.net", + "ethereumchamber.com", + "ethereumchamber.net", + "ethereumchest.com", + "myetherweb.com.de", + "myetherieumwallet.com", + "myetehrwallet.com", + "myeterwalet.com", + "myetherwaiiet.com", + "myetherwallet.info", + "myetherwallet.ch", + "myetherwallet.om", + "myethervallet.com", + "myetherwallet.com.cm", + "myetherwallet.com.co", + "myetherwallet.com.de", + "myetherwallet.com.gl", + "myetherwallet.com.im", + "myetherwallet.com.ua", + "secure-myetherwallet.com", + "update-myetherwallet.com", + "wwwmyetherwallet.com", + "myeatherwallet.com", + "myetharwallet.com", + "myelherwallel.com", + "myetherwaillet.com", + "myetherwaliet.com", + "myetherwallel.com", + "myetherwallet.cam", + "myetherwallet.cc", + "myetherwallet.co", + "myetherwallet.cm", + "myetherwallet.cz", + "myetherwallet.org", + "myetherwallet.tech", + "myetherwallet.top", + "myetherwallet.net", + "etherclassicwallet.com", + "omg-omise.co", + "omise-go.com", + "tenx-tech.com", + "tokensale-tenx.tech", + "ubiqcoin.org", + "metamask.com", + "ethtrade.io", + "myetcwallet.com", + "account-kigo.net", + "bitcoin-wallet.net", + "blocklichan.info", + "bloclkicihan.info", + "coindash.ml", + "eos-bonus.com", + "eos-io.info", + "ether-wallet.net", + "ethereum-wallet.info", + "ethereum-wallet.net", + "ethereumchest.net", + "reservations-kigo.net", + "reservations-lodgix.com", + "secure-liverez.com", + "secure-onerooftop.com", + "settings-liverez.com", + "software-liverez.com", + "software-lodgix.com", + "unhackableetherwallets.com", + "www-myetherwallet.com", + "etherwallet.co.za", + "etherwalletchain.com", + "etherwallets.net", + "etherwallets.nl", + "my-ethwallet.com", + "my.ether-wallet.co", + "myetherwallet.com.am", + "myetherwallet.com.ht", + "myetherwalletcom.com", + "xn--myetherwalle-xoc.com", + "xn--myetherwalle-44i.com", + "xn--myetherwalle-xhk.com", + "xn--myetherwallt-cfb.com", + "xn--myetherwallt-6tb.com", + "xn--myetherwallt-xub.com", + "xn--myetherwallt-ovb.com", + "xn--myetherwallt-fwb.com", + "xn--myetherwallt-5wb.com", + "xn--myetherwallt-jzi.com", + "xn--myetherwallt-2ck.com", + "xn--myetherwallt-lok.com", + "xn--myetherwallt-lsl.com", + "xn--myetherwallt-ce6f.com", + "xn--myetherwalet-mcc.com", + "xn--myetherwalet-xhf.com", + "xn--myetherwalet-lcc.com", + "xn--myetherwaet-15ba.com", + "xn--myetherwalet-whf.com", + "xn--myetherwaet-v2ea.com", + "xn--myetherwllet-59a.com", + "xn--myetherwllet-jbb.com", + "xn--myetherwllet-wbb.com", + "xn--myetherwllet-9bb.com", + "xn--myetherwllet-ncb.com", + "xn--myetherwllet-0cb.com", + "xn--myetherwllet-5nb.com", + "xn--myetherwllet-ktd.com", + "xn--myetherwllet-mre.com", + "xn--myetherwllet-76e.com", + "xn--myetherwllet-o0l.com", + "xn--myetherwllet-c45f.com", + "xn--myetherallet-ejn.com", + "xn--myethewallet-4nf.com", + "xn--myethewallet-iof.com", + "xn--myethewallet-mpf.com", + "xn--myethewallet-6bk.com", + "xn--myethewallet-i31f.com", + "xn--myethrwallet-feb.com", + "xn--myethrwallt-fbbf.com", + "xn--myethrwallet-seb.com", + "xn--myethrwallt-rbbf.com", + "xn--myethrwallet-5eb.com", + "xn--myethrwallt-3bbf.com", + "xn--myethrwallet-0tb.com", + "xn--myethrwallt-tpbf.com", + "xn--myethrwallet-rub.com", + "xn--myethrwallt-iqbf.com", + "xn--myethrwallet-ivb.com", + "xn--myethrwallt-6qbf.com", + "xn--myethrwallet-8vb.com", + "xn--myethrwallt-vrbf.com", + "xn--myethrwallet-zwb.com", + "xn--myethrwallt-ksbf.com", + "xn--myethrwallet-dzi.com", + "xn--myethrwallt-wbif.com", + "xn--myethrwallet-wck.com", + "xn--myethrwallt-skjf.com", + "xn--myethrwallet-fok.com", + "xn--myethrwallt-fvjf.com", + "xn--myethrwallet-fsl.com", + "xn--myethrwallt-fwkf.com", + "xn--myethrwallet-5d6f.com", + "xn--myethrwallt-319ef.com", + "xn--myeterwallet-ufk.com", + "xn--myeterwallet-nrl.com", + "xn--myeterwallet-von.com", + "xn--myeterwallet-jl6c.com", + "xn--myeherwallet-ooc.com", + "xn--myeherwalle-6hci.com", + "xn--myeherwallet-v4i.com", + "xn--myeherwalle-zgii.com", + "xn--myeherwallet-ohk.com", + "xn--myeherwalle-6oji.com", + "xn--mytherwallet-ceb.com", + "xn--mythrwallet-cbbc.com", + "xn--mythrwallt-c7acf.com", + "xn--mytherwallet-peb.com", + "xn--mythrwallet-obbc.com", + "xn--mythrwallt-n7acf.com", + "xn--mytherwallet-2eb.com", + "xn--mythrwallet-0bbc.com", + "xn--mythrwallt-y7acf.com", + "xn--mytherwallet-xtb.com", + "xn--mythrwallet-qpbc.com", + "xn--mythrwallt-jlbcf.com", + "xn--mytherwallet-oub.com", + "xn--mythrwallet-fqbc.com", + "xn--mythrwallt-5lbcf.com", + "xn--mythrwallet-3qbc.com", + "xn--mythrwallt-smbcf.com", + "xn--mytherwallet-5vb.com", + "xn--mythrwallet-srbc.com", + "xn--mythrwallt-fnbcf.com", + "xn--mytherwallet-wwb.com", + "xn--mythrwallet-hsbc.com", + "xn--mythrwallt-1nbcf.com", + "xn--mytherwallet-9yi.com", + "xn--mythrwallet-tbic.com", + "xn--mythrwallt-dnhcf.com", + "xn--mytherwallet-tck.com", + "xn--mythrwallet-pkjc.com", + "xn--mythrwallt-lsicf.com", + "xn--mytherwallet-cok.com", + "xn--mythrwallet-cvjc.com", + "xn--mythrwallt-c2icf.com", + "xn--mytherwallet-csl.com", + "xn--mythrwallet-cwkc.com", + "xn--mythrwallt-c0jcf.com", + "xn--mytherwallet-2d6f.com", + "xn--mythrwallet-019ec.com", + "xn--mythrwallt-yq3ecf.com", + "xn--metherwallet-qlb.com", + "xn--metherwallet-1uf.com", + "xn--metherwallet-iyi.com", + "xn--metherwallet-zhk.com", + "xn--metherwallet-3ml.com", + "xn--mytherwallet-fvb.com", + "xn--myetherwallt-7db.com", + "xn--myetherwallt-leb.com", + "xn--myetherwallt-yeb.com", + "xn--yetherwallet-vjf.com", + "xn--yetherwallet-dfk.com", + "xn--yetherwallet-1t1f.com", + "xn--yetherwallet-634f.com" + ] + } + }, + "AddressBookController": { + "addressBook": [] + }, + "TransactionController": { + "transactions": [ + { + "id": 6616756286038869, + "time": 1502438908445, + "status": "confirmed", + "metamaskNetworkId": "3", + "txParams": { + "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", + "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", + "value": "0x0", + "gasPrice": "0x3b9aca00", + "gas": "0x7b0d", + "chainId": 3 + }, + "history": [ + { + "id": 6616756286038869, + "time": 1502438908445, + "status": "unapproved", + "metamaskNetworkId": "3", + "txParams": { + "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", + "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", + "value": "0x0", + "gasPrice": "3b9aca00", + "gas": "0x7b0d" + }, + "gasLimitSpecified": false, + "estimatedGas": "5209" + }, + { + "id": 6616756286038869, + "time": 1502438908445, + "status": "unapproved", + "metamaskNetworkId": "3", + "txParams": { + "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", + "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", + "value": "0x0", + "gasPrice": "3b9aca00", + "gas": "0x7b0d" + }, + "gasLimitSpecified": false, + "estimatedGas": "5209" + }, + { + "id": 6616756286038869, + "time": 1502438908445, + "status": "approved", + "metamaskNetworkId": "3", + "txParams": { + "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", + "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", + "value": "0x0", + "gasPrice": "3b9aca00", + "gas": "0x7b0d" + }, + "gasLimitSpecified": false, + "estimatedGas": "5209" + }, + { + "id": 6616756286038869, + "time": 1502438908445, + "status": "approved", + "metamaskNetworkId": "3", + "txParams": { + "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", + "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", + "value": "0x0", + "gasPrice": "3b9aca00", + "gas": "0x7b0d" + }, + "gasLimitSpecified": false, + "estimatedGas": "5209" + }, + { + "id": 6616756286038869, + "time": 1502438908445, + "status": "approved", + "metamaskNetworkId": "3", + "txParams": { + "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", + "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", + "value": "0x0", + "gasPrice": "3b9aca00", + "gas": "0x7b0d", + "nonce": 0 + }, + "gasLimitSpecified": false, + "estimatedGas": "5209", + "nonceDetails": { + "blockNumber": "0x16643c", + "baseCount": 0, + "baseCountHex": "0x0", + "pendingCount": 0 + } + }, + { + "id": 6616756286038869, + "time": 1502438908445, + "status": "approved", + "metamaskNetworkId": "3", + "txParams": { + "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", + "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", + "value": "0x0", + "gasPrice": "3b9aca00", + "gas": "0x7b0d", + "nonce": 0 + }, + "gasLimitSpecified": false, + "estimatedGas": "5209", + "nonceDetails": { + "blockNumber": "0x16643c", + "baseCount": 0, + "baseCountHex": "0x0", + "pendingCount": 0 + } + }, + { + "id": 6616756286038869, + "time": 1502438908445, + "status": "signed", + "metamaskNetworkId": "3", + "txParams": { + "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", + "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", + "value": "0x0", + "gasPrice": "0x3b9aca00", + "gas": "0x7b0d", + "chainId": 3 + }, + "gasLimitSpecified": false, + "estimatedGas": "5209", + "nonceDetails": { + "blockNumber": "0x16643c", + "baseCount": 0, + "baseCountHex": "0x0", + "pendingCount": 0 + } + }, + { + "id": 6616756286038869, + "time": 1502438908445, + "status": "signed", + "metamaskNetworkId": "3", + "txParams": { + "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", + "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", + "value": "0x0", + "gasPrice": "0x3b9aca00", + "gas": "0x7b0d", + "chainId": 3 + }, + "gasLimitSpecified": false, + "estimatedGas": "5209", + "nonceDetails": { + "blockNumber": "0x16643c", + "baseCount": 0, + "baseCountHex": "0x0", + "pendingCount": 0 + } + }, + { + "id": 6616756286038869, + "time": 1502438908445, + "status": "signed", + "metamaskNetworkId": "3", + "txParams": { + "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", + "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", + "value": "0x0", + "gasPrice": "0x3b9aca00", + "gas": "0x7b0d", + "chainId": 3 + }, + "gasLimitSpecified": false, + "estimatedGas": "5209", + "nonceDetails": { + "blockNumber": "0x16643c", + "baseCount": 0, + "baseCountHex": "0x0", + "pendingCount": 0 + }, + "rawTx": "0xf86380843b9aca00827b0d943ae39e89dc7e736cce53091057a45bf44b1a566c808029a02e45f61129f0c97634e37a1823b858df7b0dfc867a44949aae7dd9bcea1c1b5aa03b1f002cda0872d40517d5b26caefa3e407ec8fd03bc7dc2d995b84726961264" + }, + { + "id": 6616756286038869, + "time": 1502438908445, + "status": "signed", + "metamaskNetworkId": "3", + "txParams": { + "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", + "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", + "value": "0x0", + "gasPrice": "0x3b9aca00", + "gas": "0x7b0d", + "chainId": 3 + }, + "gasLimitSpecified": false, + "estimatedGas": "5209", + "nonceDetails": { + "blockNumber": "0x16643c", + "baseCount": 0, + "baseCountHex": "0x0", + "pendingCount": 0 + }, + "rawTx": "0xf86380843b9aca00827b0d943ae39e89dc7e736cce53091057a45bf44b1a566c808029a02e45f61129f0c97634e37a1823b858df7b0dfc867a44949aae7dd9bcea1c1b5aa03b1f002cda0872d40517d5b26caefa3e407ec8fd03bc7dc2d995b84726961264" + }, + { + "id": 6616756286038869, + "time": 1502438908445, + "status": "signed", + "metamaskNetworkId": "3", + "txParams": { + "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", + "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", + "value": "0x0", + "gasPrice": "0x3b9aca00", + "gas": "0x7b0d", + "chainId": 3 + }, + "gasLimitSpecified": false, + "estimatedGas": "5209", + "nonceDetails": { + "blockNumber": "0x16643c", + "baseCount": 0, + "baseCountHex": "0x0", + "pendingCount": 0 + }, + "rawTx": "0xf86380843b9aca00827b0d943ae39e89dc7e736cce53091057a45bf44b1a566c808029a02e45f61129f0c97634e37a1823b858df7b0dfc867a44949aae7dd9bcea1c1b5aa03b1f002cda0872d40517d5b26caefa3e407ec8fd03bc7dc2d995b84726961264", + "hash": "0x38c254639139c94303a3141aee041b15301509e743f08569ffac6aca17518012" + }, + { + "id": 6616756286038869, + "time": 1502438908445, + "status": "signed", + "metamaskNetworkId": "3", + "txParams": { + "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", + "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", + "value": "0x0", + "gasPrice": "0x3b9aca00", + "gas": "0x7b0d", + "chainId": 3 + }, + "gasLimitSpecified": false, + "estimatedGas": "5209", + "nonceDetails": { + "blockNumber": "0x16643c", + "baseCount": 0, + "baseCountHex": "0x0", + "pendingCount": 0 + }, + "rawTx": "0xf86380843b9aca00827b0d943ae39e89dc7e736cce53091057a45bf44b1a566c808029a02e45f61129f0c97634e37a1823b858df7b0dfc867a44949aae7dd9bcea1c1b5aa03b1f002cda0872d40517d5b26caefa3e407ec8fd03bc7dc2d995b84726961264", + "hash": "0x38c254639139c94303a3141aee041b15301509e743f08569ffac6aca17518012" + }, + { + "id": 6616756286038869, + "time": 1502438908445, + "status": "submitted", + "metamaskNetworkId": "3", + "txParams": { + "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", + "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", + "value": "0x0", + "gasPrice": "0x3b9aca00", + "gas": "0x7b0d", + "chainId": 3 + }, + "gasLimitSpecified": false, + "estimatedGas": "5209", + "nonceDetails": { + "blockNumber": "0x16643c", + "baseCount": 0, + "baseCountHex": "0x0", + "pendingCount": 0 + }, + "rawTx": "0xf86380843b9aca00827b0d943ae39e89dc7e736cce53091057a45bf44b1a566c808029a02e45f61129f0c97634e37a1823b858df7b0dfc867a44949aae7dd9bcea1c1b5aa03b1f002cda0872d40517d5b26caefa3e407ec8fd03bc7dc2d995b84726961264", + "hash": "0x38c254639139c94303a3141aee041b15301509e743f08569ffac6aca17518012" + }, + { + "id": 6616756286038869, + "time": 1502438908445, + "status": "submitted", + "metamaskNetworkId": "3", + "txParams": { + "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", + "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", + "value": "0x0", + "gasPrice": "0x3b9aca00", + "gas": "0x7b0d", + "chainId": 3 + }, + "gasLimitSpecified": false, + "estimatedGas": "5209", + "nonceDetails": { + "blockNumber": "0x16643c", + "baseCount": 0, + "baseCountHex": "0x0", + "pendingCount": 0 + }, + "rawTx": "0xf86380843b9aca00827b0d943ae39e89dc7e736cce53091057a45bf44b1a566c808029a02e45f61129f0c97634e37a1823b858df7b0dfc867a44949aae7dd9bcea1c1b5aa03b1f002cda0872d40517d5b26caefa3e407ec8fd03bc7dc2d995b84726961264", + "hash": "0x38c254639139c94303a3141aee041b15301509e743f08569ffac6aca17518012" + }, + { + "id": 6616756286038869, + "time": 1502438908445, + "status": "confirmed", + "metamaskNetworkId": "3", + "txParams": { + "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", + "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", + "value": "0x0", + "gasPrice": "0x3b9aca00", + "gas": "0x7b0d", + "chainId": 3 + }, + "gasLimitSpecified": false, + "estimatedGas": "5209", + "nonceDetails": { + "blockNumber": "0x16643c", + "baseCount": 0, + "baseCountHex": "0x0", + "pendingCount": 0 + }, + "rawTx": "0xf86380843b9aca00827b0d943ae39e89dc7e736cce53091057a45bf44b1a566c808029a02e45f61129f0c97634e37a1823b858df7b0dfc867a44949aae7dd9bcea1c1b5aa03b1f002cda0872d40517d5b26caefa3e407ec8fd03bc7dc2d995b84726961264", + "hash": "0x38c254639139c94303a3141aee041b15301509e743f08569ffac6aca17518012", + "retryCount": 1 + }, + { + "id": 6616756286038869, + "time": 1502438908445, + "status": "confirmed", + "metamaskNetworkId": "3", + "txParams": { + "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", + "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", + "value": "0x0", + "gasPrice": "0x3b9aca00", + "gas": "0x7b0d", + "chainId": 3 + }, + "gasLimitSpecified": false, + "estimatedGas": "5209", + "nonceDetails": { + "blockNumber": "0x16643c", + "baseCount": 0, + "baseCountHex": "0x0", + "pendingCount": 0 + }, + "rawTx": "0xf86380843b9aca00827b0d943ae39e89dc7e736cce53091057a45bf44b1a566c808029a02e45f61129f0c97634e37a1823b858df7b0dfc867a44949aae7dd9bcea1c1b5aa03b1f002cda0872d40517d5b26caefa3e407ec8fd03bc7dc2d995b84726961264", + "hash": "0x38c254639139c94303a3141aee041b15301509e743f08569ffac6aca17518012", + "retryCount": 1 + } + ], + "gasLimitSpecified": false, + "estimatedGas": "5209", + "nonceDetails": { + "blockNumber": "0x16643c", + "baseCount": 0, + "baseCountHex": "0x0", + "pendingCount": 0 + }, + "rawTx": "0xf86380843b9aca00827b0d943ae39e89dc7e736cce53091057a45bf44b1a566c808029a02e45f61129f0c97634e37a1823b858df7b0dfc867a44949aae7dd9bcea1c1b5aa03b1f002cda0872d40517d5b26caefa3e407ec8fd03bc7dc2d995b84726961264", + "hash": "0x38c254639139c94303a3141aee041b15301509e743f08569ffac6aca17518012", + "retryCount": 1 + }, + { + "id": 6616756286038870, + "time": 1502573153664, + "status": "rejected", + "metamaskNetworkId": "3", + "txParams": { + "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", + "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", + "value": "0x0", + "gasPrice": "28fa6ae00", + "gas": "0x7b0d" + }, + "history": [ + { + "id": 6616756286038870, + "time": 1502573153664, + "status": "rejected", + "metamaskNetworkId": "3", + "txParams": { + "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", + "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", + "value": "0x0", + "gasPrice": "28fa6ae00", + "gas": "0x7b0d" + }, + "gasLimitSpecified": false, + "estimatedGas": "5209" + }, + { + "id": 6616756286038870, + "time": 1502573153664, + "status": "rejected", + "metamaskNetworkId": "3", + "txParams": { + "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", + "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", + "value": "0x0", + "gasPrice": "28fa6ae00", + "gas": "0x7b0d" + }, + "gasLimitSpecified": false, + "estimatedGas": "5209" + } + ], + "gasLimitSpecified": false, + "estimatedGas": "5209" + }, + { + "id": 6616756286038871, + "time": 1502573157128, + "status": "confirmed", + "metamaskNetworkId": "3", + "txParams": { + "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", + "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", + "value": "0x0", + "gasPrice": "0x28fa6ae00", + "gas": "0x7b0d", + "nonce": "0x01", + "chainId": 3 + }, + "history": [ + { + "id": 6616756286038871, + "time": 1502573157128, + "status": "unapproved", + "metamaskNetworkId": "3", + "txParams": { + "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", + "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", + "value": "0x0", + "gasPrice": "28fa6ae00", + "gas": "0x7b0d" + }, + "gasLimitSpecified": false, + "estimatedGas": "5209" + }, + { + "id": 6616756286038871, + "time": 1502573157128, + "status": "unapproved", + "metamaskNetworkId": "3", + "txParams": { + "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", + "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", + "value": "0x0", + "gasPrice": "28fa6ae00", + "gas": "0x7b0d" + }, + "gasLimitSpecified": false, + "estimatedGas": "5209" + }, + { + "id": 6616756286038871, + "time": 1502573157128, + "status": "approved", + "metamaskNetworkId": "3", + "txParams": { + "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", + "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", + "value": "0x0", + "gasPrice": "28fa6ae00", + "gas": "0x7b0d" + }, + "gasLimitSpecified": false, + "estimatedGas": "5209" + }, + { + "id": 6616756286038871, + "time": 1502573157128, + "status": "approved", + "metamaskNetworkId": "3", + "txParams": { + "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", + "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", + "value": "0x0", + "gasPrice": "28fa6ae00", + "gas": "0x7b0d" + }, + "gasLimitSpecified": false, + "estimatedGas": "5209" + }, + { + "id": 6616756286038871, + "time": 1502573157128, + "status": "approved", + "metamaskNetworkId": "3", + "txParams": { + "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", + "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", + "value": "0x0", + "gasPrice": "28fa6ae00", + "gas": "0x7b0d", + "nonce": 1 + }, + "gasLimitSpecified": false, + "estimatedGas": "5209", + "nonceDetails": { + "blockNumber": "0x168739", + "baseCount": 1, + "baseCountHex": "0x1", + "pendingCount": 0 + } + }, + { + "id": 6616756286038871, + "time": 1502573157128, + "status": "approved", + "metamaskNetworkId": "3", + "txParams": { + "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", + "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", + "value": "0x0", + "gasPrice": "28fa6ae00", + "gas": "0x7b0d", + "nonce": 1 + }, + "gasLimitSpecified": false, + "estimatedGas": "5209", + "nonceDetails": { + "blockNumber": "0x168739", + "baseCount": 1, + "baseCountHex": "0x1", + "pendingCount": 0 + } + }, + { + "id": 6616756286038871, + "time": 1502573157128, + "status": "signed", + "metamaskNetworkId": "3", + "txParams": { + "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", + "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", + "value": "0x0", + "gasPrice": "0x28fa6ae00", + "gas": "0x7b0d", + "nonce": "0x01", + "chainId": 3 + }, + "gasLimitSpecified": false, + "estimatedGas": "5209", + "nonceDetails": { + "blockNumber": "0x168739", + "baseCount": 1, + "baseCountHex": "0x1", + "pendingCount": 0 + } + }, + { + "id": 6616756286038871, + "time": 1502573157128, + "status": "signed", + "metamaskNetworkId": "3", + "txParams": { + "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", + "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", + "value": "0x0", + "gasPrice": "0x28fa6ae00", + "gas": "0x7b0d", + "nonce": "0x01", + "chainId": 3 + }, + "gasLimitSpecified": false, + "estimatedGas": "5209", + "nonceDetails": { + "blockNumber": "0x168739", + "baseCount": 1, + "baseCountHex": "0x1", + "pendingCount": 0 + } + }, + { + "id": 6616756286038871, + "time": 1502573157128, + "status": "signed", + "metamaskNetworkId": "3", + "txParams": { + "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", + "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", + "value": "0x0", + "gasPrice": "0x28fa6ae00", + "gas": "0x7b0d", + "nonce": "0x01", + "chainId": 3 + }, + "gasLimitSpecified": false, + "estimatedGas": "5209", + "nonceDetails": { + "blockNumber": "0x168739", + "baseCount": 1, + "baseCountHex": "0x1", + "pendingCount": 0 + }, + "rawTx": "0xf8640185028fa6ae00827b0d943ae39e89dc7e736cce53091057a45bf44b1a566c808029a06261831b3d599a90dc24fac67bc648fd58cab2036e4e8dfbbb5c00c3fd9cf66ba00e2ea6ebc63ba715a94dc94e24120639c4ad60832d3285dd558929a61cc18cc0" + }, + { + "id": 6616756286038871, + "time": 1502573157128, + "status": "signed", + "metamaskNetworkId": "3", + "txParams": { + "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", + "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", + "value": "0x0", + "gasPrice": "0x28fa6ae00", + "gas": "0x7b0d", + "nonce": "0x01", + "chainId": 3 + }, + "gasLimitSpecified": false, + "estimatedGas": "5209", + "nonceDetails": { + "blockNumber": "0x168739", + "baseCount": 1, + "baseCountHex": "0x1", + "pendingCount": 0 + }, + "rawTx": "0xf8640185028fa6ae00827b0d943ae39e89dc7e736cce53091057a45bf44b1a566c808029a06261831b3d599a90dc24fac67bc648fd58cab2036e4e8dfbbb5c00c3fd9cf66ba00e2ea6ebc63ba715a94dc94e24120639c4ad60832d3285dd558929a61cc18cc0" + }, + { + "id": 6616756286038871, + "time": 1502573157128, + "status": "signed", + "metamaskNetworkId": "3", + "txParams": { + "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", + "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", + "value": "0x0", + "gasPrice": "0x28fa6ae00", + "gas": "0x7b0d", + "nonce": "0x01", + "chainId": 3 + }, + "gasLimitSpecified": false, + "estimatedGas": "5209", + "nonceDetails": { + "blockNumber": "0x168739", + "baseCount": 1, + "baseCountHex": "0x1", + "pendingCount": 0 + }, + "rawTx": "0xf8640185028fa6ae00827b0d943ae39e89dc7e736cce53091057a45bf44b1a566c808029a06261831b3d599a90dc24fac67bc648fd58cab2036e4e8dfbbb5c00c3fd9cf66ba00e2ea6ebc63ba715a94dc94e24120639c4ad60832d3285dd558929a61cc18cc0", + "hash": "0xeb1c57dec9df8410bcc65374c7f684fc8ebfcda6865a149e38bb000fa706a150" + }, + { + "id": 6616756286038871, + "time": 1502573157128, + "status": "signed", + "metamaskNetworkId": "3", + "txParams": { + "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", + "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", + "value": "0x0", + "gasPrice": "0x28fa6ae00", + "gas": "0x7b0d", + "nonce": "0x01", + "chainId": 3 + }, + "gasLimitSpecified": false, + "estimatedGas": "5209", + "nonceDetails": { + "blockNumber": "0x168739", + "baseCount": 1, + "baseCountHex": "0x1", + "pendingCount": 0 + }, + "rawTx": "0xf8640185028fa6ae00827b0d943ae39e89dc7e736cce53091057a45bf44b1a566c808029a06261831b3d599a90dc24fac67bc648fd58cab2036e4e8dfbbb5c00c3fd9cf66ba00e2ea6ebc63ba715a94dc94e24120639c4ad60832d3285dd558929a61cc18cc0", + "hash": "0xeb1c57dec9df8410bcc65374c7f684fc8ebfcda6865a149e38bb000fa706a150" + }, + { + "id": 6616756286038871, + "time": 1502573157128, + "status": "submitted", + "metamaskNetworkId": "3", + "txParams": { + "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", + "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", + "value": "0x0", + "gasPrice": "0x28fa6ae00", + "gas": "0x7b0d", + "nonce": "0x01", + "chainId": 3 + }, + "gasLimitSpecified": false, + "estimatedGas": "5209", + "nonceDetails": { + "blockNumber": "0x168739", + "baseCount": 1, + "baseCountHex": "0x1", + "pendingCount": 0 + }, + "rawTx": "0xf8640185028fa6ae00827b0d943ae39e89dc7e736cce53091057a45bf44b1a566c808029a06261831b3d599a90dc24fac67bc648fd58cab2036e4e8dfbbb5c00c3fd9cf66ba00e2ea6ebc63ba715a94dc94e24120639c4ad60832d3285dd558929a61cc18cc0", + "hash": "0xeb1c57dec9df8410bcc65374c7f684fc8ebfcda6865a149e38bb000fa706a150" + }, + { + "id": 6616756286038871, + "time": 1502573157128, + "status": "submitted", + "metamaskNetworkId": "3", + "txParams": { + "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", + "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", + "value": "0x0", + "gasPrice": "0x28fa6ae00", + "gas": "0x7b0d", + "nonce": "0x01", + "chainId": 3 + }, + "gasLimitSpecified": false, + "estimatedGas": "5209", + "nonceDetails": { + "blockNumber": "0x168739", + "baseCount": 1, + "baseCountHex": "0x1", + "pendingCount": 0 + }, + "rawTx": "0xf8640185028fa6ae00827b0d943ae39e89dc7e736cce53091057a45bf44b1a566c808029a06261831b3d599a90dc24fac67bc648fd58cab2036e4e8dfbbb5c00c3fd9cf66ba00e2ea6ebc63ba715a94dc94e24120639c4ad60832d3285dd558929a61cc18cc0", + "hash": "0xeb1c57dec9df8410bcc65374c7f684fc8ebfcda6865a149e38bb000fa706a150" + }, + { + "id": 6616756286038871, + "time": 1502573157128, + "status": "confirmed", + "metamaskNetworkId": "3", + "txParams": { + "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", + "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", + "value": "0x0", + "gasPrice": "0x28fa6ae00", + "gas": "0x7b0d", + "nonce": "0x01", + "chainId": 3 + }, + "gasLimitSpecified": false, + "estimatedGas": "5209", + "nonceDetails": { + "blockNumber": "0x168739", + "baseCount": 1, + "baseCountHex": "0x1", + "pendingCount": 0 + }, + "rawTx": "0xf8640185028fa6ae00827b0d943ae39e89dc7e736cce53091057a45bf44b1a566c808029a06261831b3d599a90dc24fac67bc648fd58cab2036e4e8dfbbb5c00c3fd9cf66ba00e2ea6ebc63ba715a94dc94e24120639c4ad60832d3285dd558929a61cc18cc0", + "hash": "0xeb1c57dec9df8410bcc65374c7f684fc8ebfcda6865a149e38bb000fa706a150", + "retryCount": 1 + }, + { + "id": 6616756286038871, + "time": 1502573157128, + "status": "confirmed", + "metamaskNetworkId": "3", + "txParams": { + "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", + "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", + "value": "0x0", + "gasPrice": "0x28fa6ae00", + "gas": "0x7b0d", + "nonce": "0x01", + "chainId": 3 + }, + "gasLimitSpecified": false, + "estimatedGas": "5209", + "nonceDetails": { + "blockNumber": "0x168739", + "baseCount": 1, + "baseCountHex": "0x1", + "pendingCount": 0 + }, + "rawTx": "0xf8640185028fa6ae00827b0d943ae39e89dc7e736cce53091057a45bf44b1a566c808029a06261831b3d599a90dc24fac67bc648fd58cab2036e4e8dfbbb5c00c3fd9cf66ba00e2ea6ebc63ba715a94dc94e24120639c4ad60832d3285dd558929a61cc18cc0", + "hash": "0xeb1c57dec9df8410bcc65374c7f684fc8ebfcda6865a149e38bb000fa706a150", + "retryCount": 1 + } + ], + "gasLimitSpecified": false, + "estimatedGas": "5209", + "nonceDetails": { + "blockNumber": "0x168739", + "baseCount": 1, + "baseCountHex": "0x1", + "pendingCount": 0 + }, + "rawTx": "0xf8640185028fa6ae00827b0d943ae39e89dc7e736cce53091057a45bf44b1a566c808029a06261831b3d599a90dc24fac67bc648fd58cab2036e4e8dfbbb5c00c3fd9cf66ba00e2ea6ebc63ba715a94dc94e24120639c4ad60832d3285dd558929a61cc18cc0", + "hash": "0xeb1c57dec9df8410bcc65374c7f684fc8ebfcda6865a149e38bb000fa706a150", + "retryCount": 1 + }, + { + "id": 6616756286038872, + "time": 1502734903652, + "status": "confirmed", + "metamaskNetworkId": "3", + "txParams": { + "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", + "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", + "value": "0x0", + "gasPrice": "0x4a817c800", + "gas": "0x7b0d", + "nonce": "0x02", + "chainId": 3 + }, + "history": [ + { + "id": 6616756286038872, + "time": 1502734903652, + "status": "unapproved", + "metamaskNetworkId": "3", + "txParams": { + "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", + "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", + "value": "0x0", + "gasPrice": "4a817c800", + "gas": "0x7b0d" + }, + "gasLimitSpecified": false, + "estimatedGas": "5209" + }, + { + "id": 6616756286038872, + "time": 1502734903652, + "status": "unapproved", + "metamaskNetworkId": "3", + "txParams": { + "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", + "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", + "value": "0x0", + "gasPrice": "4a817c800", + "gas": "0x7b0d" + }, + "gasLimitSpecified": false, + "estimatedGas": "5209" + }, + { + "id": 6616756286038872, + "time": 1502734903652, + "status": "approved", + "metamaskNetworkId": "3", + "txParams": { + "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", + "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", + "value": "0x0", + "gasPrice": "4a817c800", + "gas": "0x7b0d" + }, + "gasLimitSpecified": false, + "estimatedGas": "5209" + }, + { + "id": 6616756286038872, + "time": 1502734903652, + "status": "approved", + "metamaskNetworkId": "3", + "txParams": { + "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", + "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", + "value": "0x0", + "gasPrice": "4a817c800", + "gas": "0x7b0d" + }, + "gasLimitSpecified": false, + "estimatedGas": "5209" + }, + { + "id": 6616756286038872, + "time": 1502734903652, + "status": "approved", + "metamaskNetworkId": "3", + "txParams": { + "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", + "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", + "value": "0x0", + "gasPrice": "4a817c800", + "gas": "0x7b0d", + "nonce": 2 + }, + "gasLimitSpecified": false, + "estimatedGas": "5209", + "nonceDetails": { + "blockNumber": "0x16b066", + "baseCount": 2, + "baseCountHex": "0x2", + "pendingCount": 0 + } + }, + { + "id": 6616756286038872, + "time": 1502734903652, + "status": "approved", + "metamaskNetworkId": "3", + "txParams": { + "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", + "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", + "value": "0x0", + "gasPrice": "4a817c800", + "gas": "0x7b0d", + "nonce": 2 + }, + "gasLimitSpecified": false, + "estimatedGas": "5209", + "nonceDetails": { + "blockNumber": "0x16b066", + "baseCount": 2, + "baseCountHex": "0x2", + "pendingCount": 0 + } + }, + { + "id": 6616756286038872, + "time": 1502734903652, + "status": "signed", + "metamaskNetworkId": "3", + "txParams": { + "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", + "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", + "value": "0x0", + "gasPrice": "0x4a817c800", + "gas": "0x7b0d", + "nonce": "0x02", + "chainId": 3 + }, + "gasLimitSpecified": false, + "estimatedGas": "5209", + "nonceDetails": { + "blockNumber": "0x16b066", + "baseCount": 2, + "baseCountHex": "0x2", + "pendingCount": 0 + } + }, + { + "id": 6616756286038872, + "time": 1502734903652, + "status": "signed", + "metamaskNetworkId": "3", + "txParams": { + "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", + "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", + "value": "0x0", + "gasPrice": "0x4a817c800", + "gas": "0x7b0d", + "nonce": "0x02", + "chainId": 3 + }, + "gasLimitSpecified": false, + "estimatedGas": "5209", + "nonceDetails": { + "blockNumber": "0x16b066", + "baseCount": 2, + "baseCountHex": "0x2", + "pendingCount": 0 + } + }, + { + "id": 6616756286038872, + "time": 1502734903652, + "status": "signed", + "metamaskNetworkId": "3", + "txParams": { + "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", + "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", + "value": "0x0", + "gasPrice": "0x4a817c800", + "gas": "0x7b0d", + "nonce": "0x02", + "chainId": 3 + }, + "gasLimitSpecified": false, + "estimatedGas": "5209", + "nonceDetails": { + "blockNumber": "0x16b066", + "baseCount": 2, + "baseCountHex": "0x2", + "pendingCount": 0 + }, + "rawTx": "0xf864028504a817c800827b0d943ae39e89dc7e736cce53091057a45bf44b1a566c80802aa057380f9007a48d4bce31792859b1a25cb2b45ba615e7951d8e8a925360a0b301a042393e72d1a96a2605c0da95705c5f3f7c744f0affcac01e0a64721037f04adc" + }, + { + "id": 6616756286038872, + "time": 1502734903652, + "status": "signed", + "metamaskNetworkId": "3", + "txParams": { + "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", + "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", + "value": "0x0", + "gasPrice": "0x4a817c800", + "gas": "0x7b0d", + "nonce": "0x02", + "chainId": 3 + }, + "gasLimitSpecified": false, + "estimatedGas": "5209", + "nonceDetails": { + "blockNumber": "0x16b066", + "baseCount": 2, + "baseCountHex": "0x2", + "pendingCount": 0 + }, + "rawTx": "0xf864028504a817c800827b0d943ae39e89dc7e736cce53091057a45bf44b1a566c80802aa057380f9007a48d4bce31792859b1a25cb2b45ba615e7951d8e8a925360a0b301a042393e72d1a96a2605c0da95705c5f3f7c744f0affcac01e0a64721037f04adc" + }, + { + "id": 6616756286038872, + "time": 1502734903652, + "status": "signed", + "metamaskNetworkId": "3", + "txParams": { + "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", + "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", + "value": "0x0", + "gasPrice": "0x4a817c800", + "gas": "0x7b0d", + "nonce": "0x02", + "chainId": 3 + }, + "gasLimitSpecified": false, + "estimatedGas": "5209", + "nonceDetails": { + "blockNumber": "0x16b066", + "baseCount": 2, + "baseCountHex": "0x2", + "pendingCount": 0 + }, + "rawTx": "0xf864028504a817c800827b0d943ae39e89dc7e736cce53091057a45bf44b1a566c80802aa057380f9007a48d4bce31792859b1a25cb2b45ba615e7951d8e8a925360a0b301a042393e72d1a96a2605c0da95705c5f3f7c744f0affcac01e0a64721037f04adc", + "hash": "0xc28ceb1e2c4e5c61b805b181e3cc99dd7bade58935233fab76c63cedfd494270" + }, + { + "id": 6616756286038872, + "time": 1502734903652, + "status": "signed", + "metamaskNetworkId": "3", + "txParams": { + "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", + "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", + "value": "0x0", + "gasPrice": "0x4a817c800", + "gas": "0x7b0d", + "nonce": "0x02", + "chainId": 3 + }, + "gasLimitSpecified": false, + "estimatedGas": "5209", + "nonceDetails": { + "blockNumber": "0x16b066", + "baseCount": 2, + "baseCountHex": "0x2", + "pendingCount": 0 + }, + "rawTx": "0xf864028504a817c800827b0d943ae39e89dc7e736cce53091057a45bf44b1a566c80802aa057380f9007a48d4bce31792859b1a25cb2b45ba615e7951d8e8a925360a0b301a042393e72d1a96a2605c0da95705c5f3f7c744f0affcac01e0a64721037f04adc", + "hash": "0xc28ceb1e2c4e5c61b805b181e3cc99dd7bade58935233fab76c63cedfd494270" + }, + { + "id": 6616756286038872, + "time": 1502734903652, + "status": "submitted", + "metamaskNetworkId": "3", + "txParams": { + "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", + "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", + "value": "0x0", + "gasPrice": "0x4a817c800", + "gas": "0x7b0d", + "nonce": "0x02", + "chainId": 3 + }, + "gasLimitSpecified": false, + "estimatedGas": "5209", + "nonceDetails": { + "blockNumber": "0x16b066", + "baseCount": 2, + "baseCountHex": "0x2", + "pendingCount": 0 + }, + "rawTx": "0xf864028504a817c800827b0d943ae39e89dc7e736cce53091057a45bf44b1a566c80802aa057380f9007a48d4bce31792859b1a25cb2b45ba615e7951d8e8a925360a0b301a042393e72d1a96a2605c0da95705c5f3f7c744f0affcac01e0a64721037f04adc", + "hash": "0xc28ceb1e2c4e5c61b805b181e3cc99dd7bade58935233fab76c63cedfd494270" + }, + { + "id": 6616756286038872, + "time": 1502734903652, + "status": "submitted", + "metamaskNetworkId": "3", + "txParams": { + "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", + "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", + "value": "0x0", + "gasPrice": "0x4a817c800", + "gas": "0x7b0d", + "nonce": "0x02", + "chainId": 3 + }, + "gasLimitSpecified": false, + "estimatedGas": "5209", + "nonceDetails": { + "blockNumber": "0x16b066", + "baseCount": 2, + "baseCountHex": "0x2", + "pendingCount": 0 + }, + "rawTx": "0xf864028504a817c800827b0d943ae39e89dc7e736cce53091057a45bf44b1a566c80802aa057380f9007a48d4bce31792859b1a25cb2b45ba615e7951d8e8a925360a0b301a042393e72d1a96a2605c0da95705c5f3f7c744f0affcac01e0a64721037f04adc", + "hash": "0xc28ceb1e2c4e5c61b805b181e3cc99dd7bade58935233fab76c63cedfd494270" + }, + { + "id": 6616756286038872, + "time": 1502734903652, + "status": "confirmed", + "metamaskNetworkId": "3", + "txParams": { + "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", + "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", + "value": "0x0", + "gasPrice": "0x4a817c800", + "gas": "0x7b0d", + "nonce": "0x02", + "chainId": 3 + }, + "gasLimitSpecified": false, + "estimatedGas": "5209", + "nonceDetails": { + "blockNumber": "0x16b066", + "baseCount": 2, + "baseCountHex": "0x2", + "pendingCount": 0 + }, + "rawTx": "0xf864028504a817c800827b0d943ae39e89dc7e736cce53091057a45bf44b1a566c80802aa057380f9007a48d4bce31792859b1a25cb2b45ba615e7951d8e8a925360a0b301a042393e72d1a96a2605c0da95705c5f3f7c744f0affcac01e0a64721037f04adc", + "hash": "0xc28ceb1e2c4e5c61b805b181e3cc99dd7bade58935233fab76c63cedfd494270", + "retryCount": 1 + }, + { + "id": 6616756286038872, + "time": 1502734903652, + "status": "confirmed", + "metamaskNetworkId": "3", + "txParams": { + "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", + "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", + "value": "0x0", + "gasPrice": "0x4a817c800", + "gas": "0x7b0d", + "nonce": "0x02", + "chainId": 3 + }, + "gasLimitSpecified": false, + "estimatedGas": "5209", + "nonceDetails": { + "blockNumber": "0x16b066", + "baseCount": 2, + "baseCountHex": "0x2", + "pendingCount": 0 + }, + "rawTx": "0xf864028504a817c800827b0d943ae39e89dc7e736cce53091057a45bf44b1a566c80802aa057380f9007a48d4bce31792859b1a25cb2b45ba615e7951d8e8a925360a0b301a042393e72d1a96a2605c0da95705c5f3f7c744f0affcac01e0a64721037f04adc", + "hash": "0xc28ceb1e2c4e5c61b805b181e3cc99dd7bade58935233fab76c63cedfd494270", + "retryCount": 1 + } + ], + "gasLimitSpecified": false, + "estimatedGas": "5209", + "nonceDetails": { + "blockNumber": "0x16b066", + "baseCount": 2, + "baseCountHex": "0x2", + "pendingCount": 0 + }, + "rawTx": "0xf864028504a817c800827b0d943ae39e89dc7e736cce53091057a45bf44b1a566c80802aa057380f9007a48d4bce31792859b1a25cb2b45ba615e7951d8e8a925360a0b301a042393e72d1a96a2605c0da95705c5f3f7c744f0affcac01e0a64721037f04adc", + "hash": "0xc28ceb1e2c4e5c61b805b181e3cc99dd7bade58935233fab76c63cedfd494270", + "retryCount": 1 + }, + { + "id": 6616756286038873, + "time": 1502734910224, + "status": "confirmed", + "metamaskNetworkId": "3", + "txParams": { + "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", + "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", + "value": "0x0", + "gasPrice": "0x3b9aca00", + "gas": "0x7b0d", + "nonce": "0x03", + "chainId": 3 + }, + "history": [ + { + "id": 6616756286038873, + "time": 1502734910224, + "status": "unapproved", + "metamaskNetworkId": "3", + "txParams": { + "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", + "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", + "value": "0x0", + "gasPrice": "0x3b9aca00", + "gas": "0x7b0d" + }, + "gasLimitSpecified": false, + "estimatedGas": "5209" + }, + { + "id": 6616756286038873, + "time": 1502734910224, + "status": "unapproved", + "metamaskNetworkId": "3", + "txParams": { + "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", + "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", + "value": "0x0", + "gasPrice": "0x3b9aca00", + "gas": "0x7b0d" + }, + "gasLimitSpecified": false, + "estimatedGas": "5209" + }, + { + "id": 6616756286038873, + "time": 1502734910224, + "status": "approved", + "metamaskNetworkId": "3", + "txParams": { + "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", + "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", + "value": "0x0", + "gasPrice": "0x3b9aca00", + "gas": "0x7b0d" + }, + "gasLimitSpecified": false, + "estimatedGas": "5209" + }, + { + "id": 6616756286038873, + "time": 1502734910224, + "status": "approved", + "metamaskNetworkId": "3", + "txParams": { + "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", + "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", + "value": "0x0", + "gasPrice": "0x3b9aca00", + "gas": "0x7b0d" + }, + "gasLimitSpecified": false, + "estimatedGas": "5209" + }, + { + "id": 6616756286038873, + "time": 1502734910224, + "status": "approved", + "metamaskNetworkId": "3", + "txParams": { + "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", + "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", + "value": "0x0", + "gasPrice": "0x3b9aca00", + "gas": "0x7b0d", + "nonce": 3 + }, + "gasLimitSpecified": false, + "estimatedGas": "5209", + "nonceDetails": { + "blockNumber": "0x16b067", + "baseCount": 2, + "baseCountHex": "0x2", + "pendingCount": 1 + } + }, + { + "id": 6616756286038873, + "time": 1502734910224, + "status": "approved", + "metamaskNetworkId": "3", + "txParams": { + "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", + "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", + "value": "0x0", + "gasPrice": "0x3b9aca00", + "gas": "0x7b0d", + "nonce": 3 + }, + "gasLimitSpecified": false, + "estimatedGas": "5209", + "nonceDetails": { + "blockNumber": "0x16b067", + "baseCount": 2, + "baseCountHex": "0x2", + "pendingCount": 1 + } + }, + { + "id": 6616756286038873, + "time": 1502734910224, + "status": "signed", + "metamaskNetworkId": "3", + "txParams": { + "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", + "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", + "value": "0x0", + "gasPrice": "0x3b9aca00", + "gas": "0x7b0d", + "nonce": "0x03", + "chainId": 3 + }, + "gasLimitSpecified": false, + "estimatedGas": "5209", + "nonceDetails": { + "blockNumber": "0x16b067", + "baseCount": 2, + "baseCountHex": "0x2", + "pendingCount": 1 + } + }, + { + "id": 6616756286038873, + "time": 1502734910224, + "status": "signed", + "metamaskNetworkId": "3", + "txParams": { + "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", + "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", + "value": "0x0", + "gasPrice": "0x3b9aca00", + "gas": "0x7b0d", + "nonce": "0x03", + "chainId": 3 + }, + "gasLimitSpecified": false, + "estimatedGas": "5209", + "nonceDetails": { + "blockNumber": "0x16b067", + "baseCount": 2, + "baseCountHex": "0x2", + "pendingCount": 1 + } + }, + { + "id": 6616756286038873, + "time": 1502734910224, + "status": "signed", + "metamaskNetworkId": "3", + "txParams": { + "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", + "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", + "value": "0x0", + "gasPrice": "0x3b9aca00", + "gas": "0x7b0d", + "nonce": "0x03", + "chainId": 3 + }, + "gasLimitSpecified": false, + "estimatedGas": "5209", + "nonceDetails": { + "blockNumber": "0x16b067", + "baseCount": 2, + "baseCountHex": "0x2", + "pendingCount": 1 + }, + "rawTx": "0xf86303843b9aca00827b0d943ae39e89dc7e736cce53091057a45bf44b1a566c80802aa0e442afe9386066936f556d852a296d22b8392652aa2ddb26969d83d661759ee1a06dc55b164f666a37107e86d575d2701602fc100f0ef4875736d45995150d4897" + }, + { + "id": 6616756286038873, + "time": 1502734910224, + "status": "signed", + "metamaskNetworkId": "3", + "txParams": { + "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", + "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", + "value": "0x0", + "gasPrice": "0x3b9aca00", + "gas": "0x7b0d", + "nonce": "0x03", + "chainId": 3 + }, + "gasLimitSpecified": false, + "estimatedGas": "5209", + "nonceDetails": { + "blockNumber": "0x16b067", + "baseCount": 2, + "baseCountHex": "0x2", + "pendingCount": 1 + }, + "rawTx": "0xf86303843b9aca00827b0d943ae39e89dc7e736cce53091057a45bf44b1a566c80802aa0e442afe9386066936f556d852a296d22b8392652aa2ddb26969d83d661759ee1a06dc55b164f666a37107e86d575d2701602fc100f0ef4875736d45995150d4897" + }, + { + "id": 6616756286038873, + "time": 1502734910224, + "status": "signed", + "metamaskNetworkId": "3", + "txParams": { + "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", + "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", + "value": "0x0", + "gasPrice": "0x3b9aca00", + "gas": "0x7b0d", + "nonce": "0x03", + "chainId": 3 + }, + "gasLimitSpecified": false, + "estimatedGas": "5209", + "nonceDetails": { + "blockNumber": "0x16b067", + "baseCount": 2, + "baseCountHex": "0x2", + "pendingCount": 1 + }, + "rawTx": "0xf86303843b9aca00827b0d943ae39e89dc7e736cce53091057a45bf44b1a566c80802aa0e442afe9386066936f556d852a296d22b8392652aa2ddb26969d83d661759ee1a06dc55b164f666a37107e86d575d2701602fc100f0ef4875736d45995150d4897", + "hash": "0xfcc66b8002c64a5aaa076adea7f7e48d194de10e40eb64924c8de344805c8cb7" + }, + { + "id": 6616756286038873, + "time": 1502734910224, + "status": "signed", + "metamaskNetworkId": "3", + "txParams": { + "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", + "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", + "value": "0x0", + "gasPrice": "0x3b9aca00", + "gas": "0x7b0d", + "nonce": "0x03", + "chainId": 3 + }, + "gasLimitSpecified": false, + "estimatedGas": "5209", + "nonceDetails": { + "blockNumber": "0x16b067", + "baseCount": 2, + "baseCountHex": "0x2", + "pendingCount": 1 + }, + "rawTx": "0xf86303843b9aca00827b0d943ae39e89dc7e736cce53091057a45bf44b1a566c80802aa0e442afe9386066936f556d852a296d22b8392652aa2ddb26969d83d661759ee1a06dc55b164f666a37107e86d575d2701602fc100f0ef4875736d45995150d4897", + "hash": "0xfcc66b8002c64a5aaa076adea7f7e48d194de10e40eb64924c8de344805c8cb7" + }, + { + "id": 6616756286038873, + "time": 1502734910224, + "status": "submitted", + "metamaskNetworkId": "3", + "txParams": { + "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", + "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", + "value": "0x0", + "gasPrice": "0x3b9aca00", + "gas": "0x7b0d", + "nonce": "0x03", + "chainId": 3 + }, + "gasLimitSpecified": false, + "estimatedGas": "5209", + "nonceDetails": { + "blockNumber": "0x16b067", + "baseCount": 2, + "baseCountHex": "0x2", + "pendingCount": 1 + }, + "rawTx": "0xf86303843b9aca00827b0d943ae39e89dc7e736cce53091057a45bf44b1a566c80802aa0e442afe9386066936f556d852a296d22b8392652aa2ddb26969d83d661759ee1a06dc55b164f666a37107e86d575d2701602fc100f0ef4875736d45995150d4897", + "hash": "0xfcc66b8002c64a5aaa076adea7f7e48d194de10e40eb64924c8de344805c8cb7" + }, + { + "id": 6616756286038873, + "time": 1502734910224, + "status": "submitted", + "metamaskNetworkId": "3", + "txParams": { + "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", + "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", + "value": "0x0", + "gasPrice": "0x3b9aca00", + "gas": "0x7b0d", + "nonce": "0x03", + "chainId": 3 + }, + "gasLimitSpecified": false, + "estimatedGas": "5209", + "nonceDetails": { + "blockNumber": "0x16b067", + "baseCount": 2, + "baseCountHex": "0x2", + "pendingCount": 1 + }, + "rawTx": "0xf86303843b9aca00827b0d943ae39e89dc7e736cce53091057a45bf44b1a566c80802aa0e442afe9386066936f556d852a296d22b8392652aa2ddb26969d83d661759ee1a06dc55b164f666a37107e86d575d2701602fc100f0ef4875736d45995150d4897", + "hash": "0xfcc66b8002c64a5aaa076adea7f7e48d194de10e40eb64924c8de344805c8cb7" + }, + { + "id": 6616756286038873, + "time": 1502734910224, + "status": "confirmed", + "metamaskNetworkId": "3", + "txParams": { + "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", + "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", + "value": "0x0", + "gasPrice": "0x3b9aca00", + "gas": "0x7b0d", + "nonce": "0x03", + "chainId": 3 + }, + "gasLimitSpecified": false, + "estimatedGas": "5209", + "nonceDetails": { + "blockNumber": "0x16b067", + "baseCount": 2, + "baseCountHex": "0x2", + "pendingCount": 1 + }, + "rawTx": "0xf86303843b9aca00827b0d943ae39e89dc7e736cce53091057a45bf44b1a566c80802aa0e442afe9386066936f556d852a296d22b8392652aa2ddb26969d83d661759ee1a06dc55b164f666a37107e86d575d2701602fc100f0ef4875736d45995150d4897", + "hash": "0xfcc66b8002c64a5aaa076adea7f7e48d194de10e40eb64924c8de344805c8cb7", + "retryCount": 2 + }, + { + "id": 6616756286038873, + "time": 1502734910224, + "status": "confirmed", + "metamaskNetworkId": "3", + "txParams": { + "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", + "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", + "value": "0x0", + "gasPrice": "0x3b9aca00", + "gas": "0x7b0d", + "nonce": "0x03", + "chainId": 3 + }, + "gasLimitSpecified": false, + "estimatedGas": "5209", + "nonceDetails": { + "blockNumber": "0x16b067", + "baseCount": 2, + "baseCountHex": "0x2", + "pendingCount": 1 + }, + "rawTx": "0xf86303843b9aca00827b0d943ae39e89dc7e736cce53091057a45bf44b1a566c80802aa0e442afe9386066936f556d852a296d22b8392652aa2ddb26969d83d661759ee1a06dc55b164f666a37107e86d575d2701602fc100f0ef4875736d45995150d4897", + "hash": "0xfcc66b8002c64a5aaa076adea7f7e48d194de10e40eb64924c8de344805c8cb7", + "retryCount": 2 + } + ], + "gasLimitSpecified": false, + "estimatedGas": "5209", + "nonceDetails": { + "blockNumber": "0x16b067", + "baseCount": 2, + "baseCountHex": "0x2", + "pendingCount": 1 + }, + "rawTx": "0xf86303843b9aca00827b0d943ae39e89dc7e736cce53091057a45bf44b1a566c80802aa0e442afe9386066936f556d852a296d22b8392652aa2ddb26969d83d661759ee1a06dc55b164f666a37107e86d575d2701602fc100f0ef4875736d45995150d4897", + "hash": "0xfcc66b8002c64a5aaa076adea7f7e48d194de10e40eb64924c8de344805c8cb7", + "retryCount": 2 + }, + { + "id": 6616756286038874, + "time": 1502734917414, + "status": "confirmed", + "metamaskNetworkId": "3", + "txParams": { + "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", + "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", + "value": "0x0", + "gasPrice": "0x4a817c800", + "gas": "0x7b0d", + "nonce": "0x04", + "chainId": 3 + }, + "history": [ + { + "id": 6616756286038874, + "time": 1502734917414, + "status": "unapproved", + "metamaskNetworkId": "3", + "txParams": { + "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", + "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", + "value": "0x0", + "gasPrice": "4a817c800", + "gas": "0x7b0d" + }, + "gasLimitSpecified": false, + "estimatedGas": "5209" + }, + { + "id": 6616756286038874, + "time": 1502734917414, + "status": "unapproved", + "metamaskNetworkId": "3", + "txParams": { + "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", + "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", + "value": "0x0", + "gasPrice": "4a817c800", + "gas": "0x7b0d" + }, + "gasLimitSpecified": false, + "estimatedGas": "5209" + }, + { + "id": 6616756286038874, + "time": 1502734917414, + "status": "approved", + "metamaskNetworkId": "3", + "txParams": { + "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", + "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", + "value": "0x0", + "gasPrice": "4a817c800", + "gas": "0x7b0d" + }, + "gasLimitSpecified": false, + "estimatedGas": "5209" + }, + { + "id": 6616756286038874, + "time": 1502734917414, + "status": "approved", + "metamaskNetworkId": "3", + "txParams": { + "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", + "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", + "value": "0x0", + "gasPrice": "4a817c800", + "gas": "0x7b0d" + }, + "gasLimitSpecified": false, + "estimatedGas": "5209" + }, + { + "id": 6616756286038874, + "time": 1502734917414, + "status": "approved", + "metamaskNetworkId": "3", + "txParams": { + "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", + "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", + "value": "0x0", + "gasPrice": "4a817c800", + "gas": "0x7b0d", + "nonce": 4 + }, + "gasLimitSpecified": false, + "estimatedGas": "5209", + "nonceDetails": { + "blockNumber": "0x16b067", + "baseCount": 2, + "baseCountHex": "0x2", + "pendingCount": 2 + } + }, + { + "id": 6616756286038874, + "time": 1502734917414, + "status": "approved", + "metamaskNetworkId": "3", + "txParams": { + "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", + "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", + "value": "0x0", + "gasPrice": "4a817c800", + "gas": "0x7b0d", + "nonce": 4 + }, + "gasLimitSpecified": false, + "estimatedGas": "5209", + "nonceDetails": { + "blockNumber": "0x16b067", + "baseCount": 2, + "baseCountHex": "0x2", + "pendingCount": 2 + } + }, + { + "id": 6616756286038874, + "time": 1502734917414, + "status": "signed", + "metamaskNetworkId": "3", + "txParams": { + "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", + "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", + "value": "0x0", + "gasPrice": "0x4a817c800", + "gas": "0x7b0d", + "nonce": "0x04", + "chainId": 3 + }, + "gasLimitSpecified": false, + "estimatedGas": "5209", + "nonceDetails": { + "blockNumber": "0x16b067", + "baseCount": 2, + "baseCountHex": "0x2", + "pendingCount": 2 + } + }, + { + "id": 6616756286038874, + "time": 1502734917414, + "status": "signed", + "metamaskNetworkId": "3", + "txParams": { + "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", + "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", + "value": "0x0", + "gasPrice": "0x4a817c800", + "gas": "0x7b0d", + "nonce": "0x04", + "chainId": 3 + }, + "gasLimitSpecified": false, + "estimatedGas": "5209", + "nonceDetails": { + "blockNumber": "0x16b067", + "baseCount": 2, + "baseCountHex": "0x2", + "pendingCount": 2 + } + }, + { + "id": 6616756286038874, + "time": 1502734917414, + "status": "signed", + "metamaskNetworkId": "3", + "txParams": { + "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", + "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", + "value": "0x0", + "gasPrice": "0x4a817c800", + "gas": "0x7b0d", + "nonce": "0x04", + "chainId": 3 + }, + "gasLimitSpecified": false, + "estimatedGas": "5209", + "nonceDetails": { + "blockNumber": "0x16b067", + "baseCount": 2, + "baseCountHex": "0x2", + "pendingCount": 2 + }, + "rawTx": "0xf864048504a817c800827b0d943ae39e89dc7e736cce53091057a45bf44b1a566c808029a04be1c01535745fa7ec7aeb6e0b64d009981713808ca443b181fad802ce941352a03887e90375d9225b8dfd0d42324eed8eb4982fd14ea7b4069290237b29d1dcd3" + }, + { + "id": 6616756286038874, + "time": 1502734917414, + "status": "signed", + "metamaskNetworkId": "3", + "txParams": { + "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", + "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", + "value": "0x0", + "gasPrice": "0x4a817c800", + "gas": "0x7b0d", + "nonce": "0x04", + "chainId": 3 + }, + "gasLimitSpecified": false, + "estimatedGas": "5209", + "nonceDetails": { + "blockNumber": "0x16b067", + "baseCount": 2, + "baseCountHex": "0x2", + "pendingCount": 2 + }, + "rawTx": "0xf864048504a817c800827b0d943ae39e89dc7e736cce53091057a45bf44b1a566c808029a04be1c01535745fa7ec7aeb6e0b64d009981713808ca443b181fad802ce941352a03887e90375d9225b8dfd0d42324eed8eb4982fd14ea7b4069290237b29d1dcd3" + }, + { + "id": 6616756286038874, + "time": 1502734917414, + "status": "signed", + "metamaskNetworkId": "3", + "txParams": { + "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", + "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", + "value": "0x0", + "gasPrice": "0x4a817c800", + "gas": "0x7b0d", + "nonce": "0x04", + "chainId": 3 + }, + "gasLimitSpecified": false, + "estimatedGas": "5209", + "nonceDetails": { + "blockNumber": "0x16b067", + "baseCount": 2, + "baseCountHex": "0x2", + "pendingCount": 2 + }, + "rawTx": "0xf864048504a817c800827b0d943ae39e89dc7e736cce53091057a45bf44b1a566c808029a04be1c01535745fa7ec7aeb6e0b64d009981713808ca443b181fad802ce941352a03887e90375d9225b8dfd0d42324eed8eb4982fd14ea7b4069290237b29d1dcd3", + "hash": "0x9258ed7e451402612f572cbef52b63cd63cc2c59f443a207b7b4f8d317958635" + }, + { + "id": 6616756286038874, + "time": 1502734917414, + "status": "signed", + "metamaskNetworkId": "3", + "txParams": { + "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", + "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", + "value": "0x0", + "gasPrice": "0x4a817c800", + "gas": "0x7b0d", + "nonce": "0x04", + "chainId": 3 + }, + "gasLimitSpecified": false, + "estimatedGas": "5209", + "nonceDetails": { + "blockNumber": "0x16b067", + "baseCount": 2, + "baseCountHex": "0x2", + "pendingCount": 2 + }, + "rawTx": "0xf864048504a817c800827b0d943ae39e89dc7e736cce53091057a45bf44b1a566c808029a04be1c01535745fa7ec7aeb6e0b64d009981713808ca443b181fad802ce941352a03887e90375d9225b8dfd0d42324eed8eb4982fd14ea7b4069290237b29d1dcd3", + "hash": "0x9258ed7e451402612f572cbef52b63cd63cc2c59f443a207b7b4f8d317958635" + }, + { + "id": 6616756286038874, + "time": 1502734917414, + "status": "submitted", + "metamaskNetworkId": "3", + "txParams": { + "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", + "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", + "value": "0x0", + "gasPrice": "0x4a817c800", + "gas": "0x7b0d", + "nonce": "0x04", + "chainId": 3 + }, + "gasLimitSpecified": false, + "estimatedGas": "5209", + "nonceDetails": { + "blockNumber": "0x16b067", + "baseCount": 2, + "baseCountHex": "0x2", + "pendingCount": 2 + }, + "rawTx": "0xf864048504a817c800827b0d943ae39e89dc7e736cce53091057a45bf44b1a566c808029a04be1c01535745fa7ec7aeb6e0b64d009981713808ca443b181fad802ce941352a03887e90375d9225b8dfd0d42324eed8eb4982fd14ea7b4069290237b29d1dcd3", + "hash": "0x9258ed7e451402612f572cbef52b63cd63cc2c59f443a207b7b4f8d317958635" + }, + { + "id": 6616756286038874, + "time": 1502734917414, + "status": "submitted", + "metamaskNetworkId": "3", + "txParams": { + "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", + "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", + "value": "0x0", + "gasPrice": "0x4a817c800", + "gas": "0x7b0d", + "nonce": "0x04", + "chainId": 3 + }, + "gasLimitSpecified": false, + "estimatedGas": "5209", + "nonceDetails": { + "blockNumber": "0x16b067", + "baseCount": 2, + "baseCountHex": "0x2", + "pendingCount": 2 + }, + "rawTx": "0xf864048504a817c800827b0d943ae39e89dc7e736cce53091057a45bf44b1a566c808029a04be1c01535745fa7ec7aeb6e0b64d009981713808ca443b181fad802ce941352a03887e90375d9225b8dfd0d42324eed8eb4982fd14ea7b4069290237b29d1dcd3", + "hash": "0x9258ed7e451402612f572cbef52b63cd63cc2c59f443a207b7b4f8d317958635" + }, + { + "id": 6616756286038874, + "time": 1502734917414, + "status": "confirmed", + "metamaskNetworkId": "3", + "txParams": { + "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", + "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", + "value": "0x0", + "gasPrice": "0x4a817c800", + "gas": "0x7b0d", + "nonce": "0x04", + "chainId": 3 + }, + "gasLimitSpecified": false, + "estimatedGas": "5209", + "nonceDetails": { + "blockNumber": "0x16b067", + "baseCount": 2, + "baseCountHex": "0x2", + "pendingCount": 2 + }, + "rawTx": "0xf864048504a817c800827b0d943ae39e89dc7e736cce53091057a45bf44b1a566c808029a04be1c01535745fa7ec7aeb6e0b64d009981713808ca443b181fad802ce941352a03887e90375d9225b8dfd0d42324eed8eb4982fd14ea7b4069290237b29d1dcd3", + "hash": "0x9258ed7e451402612f572cbef52b63cd63cc2c59f443a207b7b4f8d317958635", + "retryCount": 4 + }, + { + "id": 6616756286038874, + "time": 1502734917414, + "status": "confirmed", + "metamaskNetworkId": "3", + "txParams": { + "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", + "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", + "value": "0x0", + "gasPrice": "0x4a817c800", + "gas": "0x7b0d", + "nonce": "0x04", + "chainId": 3 + }, + "gasLimitSpecified": false, + "estimatedGas": "5209", + "nonceDetails": { + "blockNumber": "0x16b067", + "baseCount": 2, + "baseCountHex": "0x2", + "pendingCount": 2 + }, + "rawTx": "0xf864048504a817c800827b0d943ae39e89dc7e736cce53091057a45bf44b1a566c808029a04be1c01535745fa7ec7aeb6e0b64d009981713808ca443b181fad802ce941352a03887e90375d9225b8dfd0d42324eed8eb4982fd14ea7b4069290237b29d1dcd3", + "hash": "0x9258ed7e451402612f572cbef52b63cd63cc2c59f443a207b7b4f8d317958635", + "retryCount": 4 + } + ], + "gasLimitSpecified": false, + "estimatedGas": "5209", + "nonceDetails": { + "blockNumber": "0x16b067", + "baseCount": 2, + "baseCountHex": "0x2", + "pendingCount": 2 + }, + "rawTx": "0xf864048504a817c800827b0d943ae39e89dc7e736cce53091057a45bf44b1a566c808029a04be1c01535745fa7ec7aeb6e0b64d009981713808ca443b181fad802ce941352a03887e90375d9225b8dfd0d42324eed8eb4982fd14ea7b4069290237b29d1dcd3", + "hash": "0x9258ed7e451402612f572cbef52b63cd63cc2c59f443a207b7b4f8d317958635", + "retryCount": 4 + }, + { + "id": 6616756286038875, + "time": 1502734922745, + "status": "confirmed", + "metamaskNetworkId": "3", + "txParams": { + "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", + "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", + "value": "0x0", + "gasPrice": "0x4a817c800", + "gas": "0x7b0d", + "nonce": "0x05", + "chainId": 3 + }, + "history": [ + { + "id": 6616756286038875, + "time": 1502734922745, + "status": "unapproved", + "metamaskNetworkId": "3", + "txParams": { + "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", + "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", + "value": "0x0", + "gasPrice": "4a817c800", + "gas": "0x7b0d" + }, + "gasLimitSpecified": false, + "estimatedGas": "5209" + }, + { + "id": 6616756286038875, + "time": 1502734922745, + "status": "unapproved", + "metamaskNetworkId": "3", + "txParams": { + "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", + "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", + "value": "0x0", + "gasPrice": "4a817c800", + "gas": "0x7b0d" + }, + "gasLimitSpecified": false, + "estimatedGas": "5209" + }, + { + "id": 6616756286038875, + "time": 1502734922745, + "status": "approved", + "metamaskNetworkId": "3", + "txParams": { + "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", + "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", + "value": "0x0", + "gasPrice": "4a817c800", + "gas": "0x7b0d" + }, + "gasLimitSpecified": false, + "estimatedGas": "5209" + }, + { + "id": 6616756286038875, + "time": 1502734922745, + "status": "approved", + "metamaskNetworkId": "3", + "txParams": { + "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", + "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", + "value": "0x0", + "gasPrice": "4a817c800", + "gas": "0x7b0d" + }, + "gasLimitSpecified": false, + "estimatedGas": "5209" + }, + { + "id": 6616756286038875, + "time": 1502734922745, + "status": "approved", + "metamaskNetworkId": "3", + "txParams": { + "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", + "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", + "value": "0x0", + "gasPrice": "4a817c800", + "gas": "0x7b0d", + "nonce": 5 + }, + "gasLimitSpecified": false, + "estimatedGas": "5209", + "nonceDetails": { + "blockNumber": "0x16b068", + "baseCount": 3, + "baseCountHex": "0x3", + "pendingCount": 2 + } + }, + { + "id": 6616756286038875, + "time": 1502734922745, + "status": "approved", + "metamaskNetworkId": "3", + "txParams": { + "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", + "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", + "value": "0x0", + "gasPrice": "4a817c800", + "gas": "0x7b0d", + "nonce": 5 + }, + "gasLimitSpecified": false, + "estimatedGas": "5209", + "nonceDetails": { + "blockNumber": "0x16b068", + "baseCount": 3, + "baseCountHex": "0x3", + "pendingCount": 2 + } + }, + { + "id": 6616756286038875, + "time": 1502734922745, + "status": "signed", + "metamaskNetworkId": "3", + "txParams": { + "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", + "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", + "value": "0x0", + "gasPrice": "0x4a817c800", + "gas": "0x7b0d", + "nonce": "0x05", + "chainId": 3 + }, + "gasLimitSpecified": false, + "estimatedGas": "5209", + "nonceDetails": { + "blockNumber": "0x16b068", + "baseCount": 3, + "baseCountHex": "0x3", + "pendingCount": 2 + } + }, + { + "id": 6616756286038875, + "time": 1502734922745, + "status": "signed", + "metamaskNetworkId": "3", + "txParams": { + "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", + "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", + "value": "0x0", + "gasPrice": "0x4a817c800", + "gas": "0x7b0d", + "nonce": "0x05", + "chainId": 3 + }, + "gasLimitSpecified": false, + "estimatedGas": "5209", + "nonceDetails": { + "blockNumber": "0x16b068", + "baseCount": 3, + "baseCountHex": "0x3", + "pendingCount": 2 + } + }, + { + "id": 6616756286038875, + "time": 1502734922745, + "status": "signed", + "metamaskNetworkId": "3", + "txParams": { + "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", + "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", + "value": "0x0", + "gasPrice": "0x4a817c800", + "gas": "0x7b0d", + "nonce": "0x05", + "chainId": 3 + }, + "gasLimitSpecified": false, + "estimatedGas": "5209", + "nonceDetails": { + "blockNumber": "0x16b068", + "baseCount": 3, + "baseCountHex": "0x3", + "pendingCount": 2 + }, + "rawTx": "0xf864058504a817c800827b0d943ae39e89dc7e736cce53091057a45bf44b1a566c808029a0dac4756e84c008714b3b8b43807157ed63737450780bc57590e930c8a360750ca00b43ac8ec5235f57ccca7e68ce8fbf77f43d6ffa5fbff296cba66cef47889cf5" + }, + { + "id": 6616756286038875, + "time": 1502734922745, + "status": "signed", + "metamaskNetworkId": "3", + "txParams": { + "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", + "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", + "value": "0x0", + "gasPrice": "0x4a817c800", + "gas": "0x7b0d", + "nonce": "0x05", + "chainId": 3 + }, + "gasLimitSpecified": false, + "estimatedGas": "5209", + "nonceDetails": { + "blockNumber": "0x16b068", + "baseCount": 3, + "baseCountHex": "0x3", + "pendingCount": 2 + }, + "rawTx": "0xf864058504a817c800827b0d943ae39e89dc7e736cce53091057a45bf44b1a566c808029a0dac4756e84c008714b3b8b43807157ed63737450780bc57590e930c8a360750ca00b43ac8ec5235f57ccca7e68ce8fbf77f43d6ffa5fbff296cba66cef47889cf5" + }, + { + "id": 6616756286038875, + "time": 1502734922745, + "status": "signed", + "metamaskNetworkId": "3", + "txParams": { + "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", + "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", + "value": "0x0", + "gasPrice": "0x4a817c800", + "gas": "0x7b0d", + "nonce": "0x05", + "chainId": 3 + }, + "gasLimitSpecified": false, + "estimatedGas": "5209", + "nonceDetails": { + "blockNumber": "0x16b068", + "baseCount": 3, + "baseCountHex": "0x3", + "pendingCount": 2 + }, + "rawTx": "0xf864058504a817c800827b0d943ae39e89dc7e736cce53091057a45bf44b1a566c808029a0dac4756e84c008714b3b8b43807157ed63737450780bc57590e930c8a360750ca00b43ac8ec5235f57ccca7e68ce8fbf77f43d6ffa5fbff296cba66cef47889cf5", + "hash": "0x67cdff49c1f8ed506c759fc8fd7ffe93d59fcb3bfd926b964cad47e2e504dc9e" + }, + { + "id": 6616756286038875, + "time": 1502734922745, + "status": "signed", + "metamaskNetworkId": "3", + "txParams": { + "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", + "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", + "value": "0x0", + "gasPrice": "0x4a817c800", + "gas": "0x7b0d", + "nonce": "0x05", + "chainId": 3 + }, + "gasLimitSpecified": false, + "estimatedGas": "5209", + "nonceDetails": { + "blockNumber": "0x16b068", + "baseCount": 3, + "baseCountHex": "0x3", + "pendingCount": 2 + }, + "rawTx": "0xf864058504a817c800827b0d943ae39e89dc7e736cce53091057a45bf44b1a566c808029a0dac4756e84c008714b3b8b43807157ed63737450780bc57590e930c8a360750ca00b43ac8ec5235f57ccca7e68ce8fbf77f43d6ffa5fbff296cba66cef47889cf5", + "hash": "0x67cdff49c1f8ed506c759fc8fd7ffe93d59fcb3bfd926b964cad47e2e504dc9e" + }, + { + "id": 6616756286038875, + "time": 1502734922745, + "status": "submitted", + "metamaskNetworkId": "3", + "txParams": { + "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", + "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", + "value": "0x0", + "gasPrice": "0x4a817c800", + "gas": "0x7b0d", + "nonce": "0x05", + "chainId": 3 + }, + "gasLimitSpecified": false, + "estimatedGas": "5209", + "nonceDetails": { + "blockNumber": "0x16b068", + "baseCount": 3, + "baseCountHex": "0x3", + "pendingCount": 2 + }, + "rawTx": "0xf864058504a817c800827b0d943ae39e89dc7e736cce53091057a45bf44b1a566c808029a0dac4756e84c008714b3b8b43807157ed63737450780bc57590e930c8a360750ca00b43ac8ec5235f57ccca7e68ce8fbf77f43d6ffa5fbff296cba66cef47889cf5", + "hash": "0x67cdff49c1f8ed506c759fc8fd7ffe93d59fcb3bfd926b964cad47e2e504dc9e" + }, + { + "id": 6616756286038875, + "time": 1502734922745, + "status": "submitted", + "metamaskNetworkId": "3", + "txParams": { + "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", + "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", + "value": "0x0", + "gasPrice": "0x4a817c800", + "gas": "0x7b0d", + "nonce": "0x05", + "chainId": 3 + }, + "gasLimitSpecified": false, + "estimatedGas": "5209", + "nonceDetails": { + "blockNumber": "0x16b068", + "baseCount": 3, + "baseCountHex": "0x3", + "pendingCount": 2 + }, + "rawTx": "0xf864058504a817c800827b0d943ae39e89dc7e736cce53091057a45bf44b1a566c808029a0dac4756e84c008714b3b8b43807157ed63737450780bc57590e930c8a360750ca00b43ac8ec5235f57ccca7e68ce8fbf77f43d6ffa5fbff296cba66cef47889cf5", + "hash": "0x67cdff49c1f8ed506c759fc8fd7ffe93d59fcb3bfd926b964cad47e2e504dc9e" + }, + { + "id": 6616756286038875, + "time": 1502734922745, + "status": "confirmed", + "metamaskNetworkId": "3", + "txParams": { + "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", + "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", + "value": "0x0", + "gasPrice": "0x4a817c800", + "gas": "0x7b0d", + "nonce": "0x05", + "chainId": 3 + }, + "gasLimitSpecified": false, + "estimatedGas": "5209", + "nonceDetails": { + "blockNumber": "0x16b068", + "baseCount": 3, + "baseCountHex": "0x3", + "pendingCount": 2 + }, + "rawTx": "0xf864058504a817c800827b0d943ae39e89dc7e736cce53091057a45bf44b1a566c808029a0dac4756e84c008714b3b8b43807157ed63737450780bc57590e930c8a360750ca00b43ac8ec5235f57ccca7e68ce8fbf77f43d6ffa5fbff296cba66cef47889cf5", + "hash": "0x67cdff49c1f8ed506c759fc8fd7ffe93d59fcb3bfd926b964cad47e2e504dc9e", + "retryCount": 3 + }, + { + "id": 6616756286038875, + "time": 1502734922745, + "status": "confirmed", + "metamaskNetworkId": "3", + "txParams": { + "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", + "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", + "value": "0x0", + "gasPrice": "0x4a817c800", + "gas": "0x7b0d", + "nonce": "0x05", + "chainId": 3 + }, + "gasLimitSpecified": false, + "estimatedGas": "5209", + "nonceDetails": { + "blockNumber": "0x16b068", + "baseCount": 3, + "baseCountHex": "0x3", + "pendingCount": 2 + }, + "rawTx": "0xf864058504a817c800827b0d943ae39e89dc7e736cce53091057a45bf44b1a566c808029a0dac4756e84c008714b3b8b43807157ed63737450780bc57590e930c8a360750ca00b43ac8ec5235f57ccca7e68ce8fbf77f43d6ffa5fbff296cba66cef47889cf5", + "hash": "0x67cdff49c1f8ed506c759fc8fd7ffe93d59fcb3bfd926b964cad47e2e504dc9e", + "retryCount": 3 + } + ], + "gasLimitSpecified": false, + "estimatedGas": "5209", + "nonceDetails": { + "blockNumber": "0x16b068", + "baseCount": 3, + "baseCountHex": "0x3", + "pendingCount": 2 + }, + "rawTx": "0xf864058504a817c800827b0d943ae39e89dc7e736cce53091057a45bf44b1a566c808029a0dac4756e84c008714b3b8b43807157ed63737450780bc57590e930c8a360750ca00b43ac8ec5235f57ccca7e68ce8fbf77f43d6ffa5fbff296cba66cef47889cf5", + "hash": "0x67cdff49c1f8ed506c759fc8fd7ffe93d59fcb3bfd926b964cad47e2e504dc9e", + "retryCount": 3 + }, + { + "id": 6616756286038876, + "time": 1502734928623, + "status": "confirmed", + "metamaskNetworkId": "3", + "txParams": { + "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", + "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", + "value": "0x0", + "gasPrice": "0x4a817c800", + "gas": "0x7b0d", + "nonce": "0x06", + "chainId": 3 + }, + "history": [ + { + "id": 6616756286038876, + "time": 1502734928623, + "status": "unapproved", + "metamaskNetworkId": "3", + "txParams": { + "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", + "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", + "value": "0x0", + "gasPrice": "4a817c800", + "gas": "0x7b0d" + }, + "gasLimitSpecified": false, + "estimatedGas": "5209" + }, + { + "id": 6616756286038876, + "time": 1502734928623, + "status": "unapproved", + "metamaskNetworkId": "3", + "txParams": { + "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", + "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", + "value": "0x0", + "gasPrice": "4a817c800", + "gas": "0x7b0d" + }, + "gasLimitSpecified": false, + "estimatedGas": "5209" + }, + { + "id": 6616756286038876, + "time": 1502734928623, + "status": "approved", + "metamaskNetworkId": "3", + "txParams": { + "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", + "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", + "value": "0x0", + "gasPrice": "4a817c800", + "gas": "0x7b0d" + }, + "gasLimitSpecified": false, + "estimatedGas": "5209" + }, + { + "id": 6616756286038876, + "time": 1502734928623, + "status": "approved", + "metamaskNetworkId": "3", + "txParams": { + "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", + "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", + "value": "0x0", + "gasPrice": "4a817c800", + "gas": "0x7b0d" + }, + "gasLimitSpecified": false, + "estimatedGas": "5209" + }, + { + "id": 6616756286038876, + "time": 1502734928623, + "status": "approved", + "metamaskNetworkId": "3", + "txParams": { + "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", + "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", + "value": "0x0", + "gasPrice": "4a817c800", + "gas": "0x7b0d", + "nonce": 6 + }, + "gasLimitSpecified": false, + "estimatedGas": "5209", + "nonceDetails": { + "blockNumber": "0x16b068", + "baseCount": 3, + "baseCountHex": "0x3", + "pendingCount": 3 + } + }, + { + "id": 6616756286038876, + "time": 1502734928623, + "status": "approved", + "metamaskNetworkId": "3", + "txParams": { + "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", + "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", + "value": "0x0", + "gasPrice": "4a817c800", + "gas": "0x7b0d", + "nonce": 6 + }, + "gasLimitSpecified": false, + "estimatedGas": "5209", + "nonceDetails": { + "blockNumber": "0x16b068", + "baseCount": 3, + "baseCountHex": "0x3", + "pendingCount": 3 + } + }, + { + "id": 6616756286038876, + "time": 1502734928623, + "status": "signed", + "metamaskNetworkId": "3", + "txParams": { + "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", + "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", + "value": "0x0", + "gasPrice": "0x4a817c800", + "gas": "0x7b0d", + "nonce": "0x06", + "chainId": 3 + }, + "gasLimitSpecified": false, + "estimatedGas": "5209", + "nonceDetails": { + "blockNumber": "0x16b068", + "baseCount": 3, + "baseCountHex": "0x3", + "pendingCount": 3 + } + }, + { + "id": 6616756286038876, + "time": 1502734928623, + "status": "signed", + "metamaskNetworkId": "3", + "txParams": { + "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", + "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", + "value": "0x0", + "gasPrice": "0x4a817c800", + "gas": "0x7b0d", + "nonce": "0x06", + "chainId": 3 + }, + "gasLimitSpecified": false, + "estimatedGas": "5209", + "nonceDetails": { + "blockNumber": "0x16b068", + "baseCount": 3, + "baseCountHex": "0x3", + "pendingCount": 3 + } + }, + { + "id": 6616756286038876, + "time": 1502734928623, + "status": "signed", + "metamaskNetworkId": "3", + "txParams": { + "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", + "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", + "value": "0x0", + "gasPrice": "0x4a817c800", + "gas": "0x7b0d", + "nonce": "0x06", + "chainId": 3 + }, + "gasLimitSpecified": false, + "estimatedGas": "5209", + "nonceDetails": { + "blockNumber": "0x16b068", + "baseCount": 3, + "baseCountHex": "0x3", + "pendingCount": 3 + }, + "rawTx": "0xf864068504a817c800827b0d943ae39e89dc7e736cce53091057a45bf44b1a566c80802aa0d983a744f58179522b4bb75f6320dbcf0a699506f2470139282a2ad02e92554fa030d818c35e997ba5d004df65a0c4ebcb49d098ec7dc190d7287a72f9c96f89e7" + }, + { + "id": 6616756286038876, + "time": 1502734928623, + "status": "signed", + "metamaskNetworkId": "3", + "txParams": { + "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", + "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", + "value": "0x0", + "gasPrice": "0x4a817c800", + "gas": "0x7b0d", + "nonce": "0x06", + "chainId": 3 + }, + "gasLimitSpecified": false, + "estimatedGas": "5209", + "nonceDetails": { + "blockNumber": "0x16b068", + "baseCount": 3, + "baseCountHex": "0x3", + "pendingCount": 3 + }, + "rawTx": "0xf864068504a817c800827b0d943ae39e89dc7e736cce53091057a45bf44b1a566c80802aa0d983a744f58179522b4bb75f6320dbcf0a699506f2470139282a2ad02e92554fa030d818c35e997ba5d004df65a0c4ebcb49d098ec7dc190d7287a72f9c96f89e7" + }, + { + "id": 6616756286038876, + "time": 1502734928623, + "status": "signed", + "metamaskNetworkId": "3", + "txParams": { + "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", + "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", + "value": "0x0", + "gasPrice": "0x4a817c800", + "gas": "0x7b0d", + "nonce": "0x06", + "chainId": 3 + }, + "gasLimitSpecified": false, + "estimatedGas": "5209", + "nonceDetails": { + "blockNumber": "0x16b068", + "baseCount": 3, + "baseCountHex": "0x3", + "pendingCount": 3 + }, + "rawTx": "0xf864068504a817c800827b0d943ae39e89dc7e736cce53091057a45bf44b1a566c80802aa0d983a744f58179522b4bb75f6320dbcf0a699506f2470139282a2ad02e92554fa030d818c35e997ba5d004df65a0c4ebcb49d098ec7dc190d7287a72f9c96f89e7", + "hash": "0x2c12c403aeb62a92bd5eabd3edcc38ab9e63bb0c93f706520bd2042894737ad1" + }, + { + "id": 6616756286038876, + "time": 1502734928623, + "status": "signed", + "metamaskNetworkId": "3", + "txParams": { + "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", + "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", + "value": "0x0", + "gasPrice": "0x4a817c800", + "gas": "0x7b0d", + "nonce": "0x06", + "chainId": 3 + }, + "gasLimitSpecified": false, + "estimatedGas": "5209", + "nonceDetails": { + "blockNumber": "0x16b068", + "baseCount": 3, + "baseCountHex": "0x3", + "pendingCount": 3 + }, + "rawTx": "0xf864068504a817c800827b0d943ae39e89dc7e736cce53091057a45bf44b1a566c80802aa0d983a744f58179522b4bb75f6320dbcf0a699506f2470139282a2ad02e92554fa030d818c35e997ba5d004df65a0c4ebcb49d098ec7dc190d7287a72f9c96f89e7", + "hash": "0x2c12c403aeb62a92bd5eabd3edcc38ab9e63bb0c93f706520bd2042894737ad1" + }, + { + "id": 6616756286038876, + "time": 1502734928623, + "status": "submitted", + "metamaskNetworkId": "3", + "txParams": { + "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", + "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", + "value": "0x0", + "gasPrice": "0x4a817c800", + "gas": "0x7b0d", + "nonce": "0x06", + "chainId": 3 + }, + "gasLimitSpecified": false, + "estimatedGas": "5209", + "nonceDetails": { + "blockNumber": "0x16b068", + "baseCount": 3, + "baseCountHex": "0x3", + "pendingCount": 3 + }, + "rawTx": "0xf864068504a817c800827b0d943ae39e89dc7e736cce53091057a45bf44b1a566c80802aa0d983a744f58179522b4bb75f6320dbcf0a699506f2470139282a2ad02e92554fa030d818c35e997ba5d004df65a0c4ebcb49d098ec7dc190d7287a72f9c96f89e7", + "hash": "0x2c12c403aeb62a92bd5eabd3edcc38ab9e63bb0c93f706520bd2042894737ad1" + }, + { + "id": 6616756286038876, + "time": 1502734928623, + "status": "submitted", + "metamaskNetworkId": "3", + "txParams": { + "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", + "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", + "value": "0x0", + "gasPrice": "0x4a817c800", + "gas": "0x7b0d", + "nonce": "0x06", + "chainId": 3 + }, + "gasLimitSpecified": false, + "estimatedGas": "5209", + "nonceDetails": { + "blockNumber": "0x16b068", + "baseCount": 3, + "baseCountHex": "0x3", + "pendingCount": 3 + }, + "rawTx": "0xf864068504a817c800827b0d943ae39e89dc7e736cce53091057a45bf44b1a566c80802aa0d983a744f58179522b4bb75f6320dbcf0a699506f2470139282a2ad02e92554fa030d818c35e997ba5d004df65a0c4ebcb49d098ec7dc190d7287a72f9c96f89e7", + "hash": "0x2c12c403aeb62a92bd5eabd3edcc38ab9e63bb0c93f706520bd2042894737ad1" + }, + { + "id": 6616756286038876, + "time": 1502734928623, + "status": "confirmed", + "metamaskNetworkId": "3", + "txParams": { + "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", + "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", + "value": "0x0", + "gasPrice": "0x4a817c800", + "gas": "0x7b0d", + "nonce": "0x06", + "chainId": 3 + }, + "gasLimitSpecified": false, + "estimatedGas": "5209", + "nonceDetails": { + "blockNumber": "0x16b068", + "baseCount": 3, + "baseCountHex": "0x3", + "pendingCount": 3 + }, + "rawTx": "0xf864068504a817c800827b0d943ae39e89dc7e736cce53091057a45bf44b1a566c80802aa0d983a744f58179522b4bb75f6320dbcf0a699506f2470139282a2ad02e92554fa030d818c35e997ba5d004df65a0c4ebcb49d098ec7dc190d7287a72f9c96f89e7", + "hash": "0x2c12c403aeb62a92bd5eabd3edcc38ab9e63bb0c93f706520bd2042894737ad1", + "retryCount": 5 + }, + { + "id": 6616756286038876, + "time": 1502734928623, + "status": "confirmed", + "metamaskNetworkId": "3", + "txParams": { + "from": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", + "to": "0x3ae39e89dc7e736cce53091057a45bf44b1a566c", + "value": "0x0", + "gasPrice": "0x4a817c800", + "gas": "0x7b0d", + "nonce": "0x06", + "chainId": 3 + }, + "gasLimitSpecified": false, + "estimatedGas": "5209", + "nonceDetails": { + "blockNumber": "0x16b068", + "baseCount": 3, + "baseCountHex": "0x3", + "pendingCount": 3 + }, + "rawTx": "0xf864068504a817c800827b0d943ae39e89dc7e736cce53091057a45bf44b1a566c80802aa0d983a744f58179522b4bb75f6320dbcf0a699506f2470139282a2ad02e92554fa030d818c35e997ba5d004df65a0c4ebcb49d098ec7dc190d7287a72f9c96f89e7", + "hash": "0x2c12c403aeb62a92bd5eabd3edcc38ab9e63bb0c93f706520bd2042894737ad1", + "retryCount": 5 + } + ], + "gasLimitSpecified": false, + "estimatedGas": "5209", + "nonceDetails": { + "blockNumber": "0x16b068", + "baseCount": 3, + "baseCountHex": "0x3", + "pendingCount": 3 + }, + "rawTx": "0xf864068504a817c800827b0d943ae39e89dc7e736cce53091057a45bf44b1a566c80802aa0d983a744f58179522b4bb75f6320dbcf0a699506f2470139282a2ad02e92554fa030d818c35e997ba5d004df65a0c4ebcb49d098ec7dc190d7287a72f9c96f89e7", + "hash": "0x2c12c403aeb62a92bd5eabd3edcc38ab9e63bb0c93f706520bd2042894737ad1", + "retryCount": 5 + } + ] + } + } +} \ No newline at end of file diff --git a/test/integration/helpers.js b/test/integration/helpers.js deleted file mode 100644 index 10cd74e64..000000000 --- a/test/integration/helpers.js +++ /dev/null @@ -1,7 +0,0 @@ -function wait(time) { - return new Promise(function (resolve, reject) { - setTimeout(function () { - resolve() - }, time * 3 || 1500) - }) -} diff --git a/test/integration/index.js b/test/integration/index.js index e089fc39b..144303dbb 100644 --- a/test/integration/index.js +++ b/test/integration/index.js @@ -1,5 +1,6 @@ const fs = require('fs') const path = require('path') +const pump = require('pump') const browserify = require('browserify') const tests = fs.readdirSync(path.join(__dirname, 'lib')) const bundlePath = path.join(__dirname, 'bundle.js') @@ -9,11 +10,17 @@ const b = browserify() const writeStream = fs.createWriteStream(bundlePath) tests.forEach(function (fileName) { - b.add(path.join(__dirname, 'lib', fileName)) + const filePath = path.join(__dirname, 'lib', fileName) + console.log(`bundling test "${filePath}"`) + b.add(filePath) }) -b.bundle() -.pipe(writeStream) -.on('error', (err) => { - throw err -}) +pump( + b.bundle(), + writeStream, + (err) => { + if (err) throw err + console.log(`Integration test build completed: "${bundlePath}"`) + process.exit(0) + } +) \ No newline at end of file diff --git a/test/integration/lib/first-time.js b/test/integration/lib/first-time.js index 0e4b802da..38a94e551 100644 --- a/test/integration/lib/first-time.js +++ b/test/integration/lib/first-time.js @@ -2,125 +2,137 @@ const PASSWORD = 'password123' QUnit.module('first time usage') -QUnit.test('render init screen', function (assert) { - var done = assert.async() - let app - - wait().then(function() { - app = $('iframe').contents().find('#app-content .mock-app-root') - - const recurseNotices = function () { - let button = app.find('button') - if (button.html() === 'Accept') { - let termsPage = app.find('.markdown')[0] - termsPage.scrollTop = termsPage.scrollHeight - return wait().then(() => { - button.click() - return wait() - }).then(() => { - return recurseNotices() - }) - } else { - return wait() - } - } - return recurseNotices() - }).then(function() { - // Scroll through terms - var title = app.find('h1').text() - assert.equal(title, 'MetaMask', 'title screen') - - // enter password - var pwBox = app.find('#password-box')[0] - var confBox = app.find('#password-box-confirm')[0] - pwBox.value = PASSWORD - confBox.value = PASSWORD - - return wait() - }).then(function() { - - // create vault - var createButton = app.find('button.primary')[0] - createButton.click() - - return wait(1500) - }).then(function() { - - var created = app.find('h3')[0] - assert.equal(created.textContent, 'Vault Created', 'Vault created screen') - - // Agree button - var button = app.find('button')[0] - assert.ok(button, 'button present') - button.click() - - return wait(1000) - }).then(function() { - - var detail = app.find('.account-detail-section')[0] - assert.ok(detail, 'Account detail section loaded.') - - var sandwich = app.find('.sandwich-expando')[0] - sandwich.click() - - return wait() - }).then(function() { - - var sandwich = app.find('.menu-droppo')[0] - var children = sandwich.children - var lock = children[children.length - 2] - assert.ok(lock, 'Lock menu item found') - lock.click() - - return wait(1000) - }).then(function() { - - var pwBox = app.find('#password-box')[0] - pwBox.value = PASSWORD - - var createButton = app.find('button.primary')[0] - createButton.click() - - return wait(1000) - }).then(function() { - - var detail = app.find('.account-detail-section')[0] - assert.ok(detail, 'Account detail section loaded again.') - - return wait() - }).then(function (){ - - var qrButton = app.find('.fa.fa-ellipsis-h')[0] // open account settings dropdown - qrButton.click() - - return wait(1000) - }).then(function (){ - - var qrButton = app.find('.dropdown-menu-item')[1] // qr code item - qrButton.click() - - return wait(1000) - }).then(function (){ - - var qrHeader = app.find('.qr-header')[0] - var qrContainer = app.find('#qr-container')[0] - assert.equal(qrHeader.textContent, 'Account 1', 'Should show account label.') - assert.ok(qrContainer, 'QR Container found') - - return wait() - }).then(function (){ - - var networkMenu = app.find('.network-indicator')[0] - networkMenu.click() - - return wait() - }).then(function (){ - - var networkMenu = app.find('.network-indicator')[0] - var children = networkMenu.children - children.length[3] - assert.ok(children, 'All network options present') - +QUnit.test('render init screen', (assert) => { + const done = assert.async() + runFirstTimeUsageTest(assert).then(done).catch((err) => { + assert.notOk(err, `Error was thrown: ${err.stack}`) done() }) }) + +// QUnit.testDone(({ module, name, total, passed, failed, skipped, todo, runtime }) => { +// if (failed > 0) { +// const app = $('iframe').contents()[0].documentElement +// console.warn('Test failures - dumping DOM:') +// console.log(app.innerHTML) +// } +// }) + +async function runFirstTimeUsageTest(assert, done) { + + await timeout() + + const app = $('#app-content .mock-app-root') + + // recurse notices + while (true) { + const button = app.find('button') + if (button.html() === 'Accept') { + // still notices to accept + const termsPage = app.find('.markdown')[0] + termsPage.scrollTop = termsPage.scrollHeight + await timeout() + button.click() + await timeout() + } else { + // exit loop + break + } + } + + await timeout() + + // Scroll through terms + const title = app.find('h1').text() + assert.equal(title, 'MetaMask', 'title screen') + + // enter password + const pwBox = app.find('#password-box')[0] + const confBox = app.find('#password-box-confirm')[0] + pwBox.value = PASSWORD + confBox.value = PASSWORD + + await timeout() + + // create vault + const createButton = app.find('button.primary')[0] + createButton.click() + + await timeout(1500) + + const created = app.find('h3')[0] + assert.equal(created.textContent, 'Vault Created', 'Vault created screen') + + // Agree button + const button = app.find('button')[0] + assert.ok(button, 'button present') + button.click() + + await timeout(1000) + + const detail = app.find('.account-detail-section')[0] + assert.ok(detail, 'Account detail section loaded.') + + const sandwich = app.find('.sandwich-expando')[0] + sandwich.click() + + await timeout() + + const menu = app.find('.menu-droppo')[0] + const children = menu.children + const lock = children[children.length - 2] + assert.ok(lock, 'Lock menu item found') + lock.click() + + await timeout(1000) + + const pwBox2 = app.find('#password-box')[0] + pwBox2.value = PASSWORD + + const createButton2 = app.find('button.primary')[0] + createButton2.click() + + await timeout(1000) + + const detail2 = app.find('.account-detail-section')[0] + assert.ok(detail2, 'Account detail section loaded again.') + + await timeout() + + // open account settings dropdown + const qrButton = app.find('.fa.fa-ellipsis-h')[0] + qrButton.click() + + await timeout(1000) + + // qr code item + const qrButton2 = app.find('.dropdown-menu-item')[1] + qrButton2.click() + + await timeout(1000) + + const qrHeader = app.find('.qr-header')[0] + const qrContainer = app.find('#qr-container')[0] + assert.equal(qrHeader.textContent, 'Account 1', 'Should show account label.') + assert.ok(qrContainer, 'QR Container found') + + await timeout() + + const networkMenu = app.find('.network-indicator')[0] + networkMenu.click() + + await timeout() + + const networkMenu2 = app.find('.network-indicator')[0] + const children2 = networkMenu2.children + children2.length[3] + assert.ok(children2, 'All network options present') +} + +function timeout(time) { + return new Promise(function (resolve, reject) { + setTimeout(function () { + resolve() + }, time * 3 || 1500) + }) +} \ No newline at end of file diff --git a/test/lib/mock-store.js b/test/lib/mock-store.js index 0d50e2d9c..8af8f6d23 100644 --- a/test/lib/mock-store.js +++ b/test/lib/mock-store.js @@ -1,7 +1,7 @@ const createStore = require('redux').createStore const applyMiddleware = require('redux').applyMiddleware -const thunkMiddleware = require('redux-thunk') -const createLogger = require('redux-logger') +const thunkMiddleware = require('redux-thunk').default +const createLogger = require('redux-logger').createLogger const rootReducer = function () {} module.exports = configureStore diff --git a/test/lib/mock-tx-gen.js b/test/lib/mock-tx-gen.js new file mode 100644 index 000000000..7aea09c59 --- /dev/null +++ b/test/lib/mock-tx-gen.js @@ -0,0 +1,40 @@ +const extend = require('xtend') +const BN = require('ethereumjs-util').BN +const template = { + 'status': 'submitted', + 'txParams': { + 'from': '0x7d3517b0d011698406d6e0aed8453f0be2697926', + 'gas': '0x30d40', + 'value': '0x0', + 'nonce': '0x3', + }, +} + +class TxGenerator { + + constructor () { + this.txs = [] + } + + generate (tx = {}, opts = {}) { + let { count, fromNonce } = opts + let nonce = fromNonce || this.txs.length + let txs = [] + for (let i = 0; i < count; i++) { + txs.push(extend(template, { + txParams: { + nonce: hexify(nonce++), + } + }, tx)) + } + this.txs = this.txs.concat(txs) + return txs + } + +} + +function hexify (number) { + return '0x' + (new BN(number)).toString(16) +} + +module.exports = TxGenerator diff --git a/test/unit/actions/tx_test.js b/test/unit/actions/tx_test.js index 67c72e9a5..ea6dfda6a 100644 --- a/test/unit/actions/tx_test.js +++ b/test/unit/actions/tx_test.js @@ -53,7 +53,7 @@ describe('tx confirmation screen', function () { result = reducers(initialState, action) done() }) - + }) it('should transition to the account detail view', function () { @@ -65,91 +65,6 @@ describe('tx confirmation screen', function () { assert.equal(count, 0) }) }) - - describe('sendTx', function () { - var result - - describe('when there is an error', function () { - before(function (done) { - actions._setBackgroundConnection({ - approveTransaction (txId, cb) { cb({message: 'An error!'}) }, - }) - - actions.sendTx({id: firstTxId})(function (action) { - result = reducers(initialState, action) - done() - }) - }) - - it('should stay on the page', function () { - assert.equal(result.appState.currentView.name, 'confTx') - }) - - it('should set errorMessage on the currentView', function () { - assert(result.appState.currentView.errorMessage) - }) - }) - - describe('when there is success', function () { - it('should complete tx and go home', function () { - actions._setBackgroundConnection({ - approveTransaction (txId, cb) { cb() }, - }) - - var dispatchExpect = sinon.mock() - dispatchExpect.twice() - - actions.sendTx({id: firstTxId})(dispatchExpect) - }) - }) - }) - - describe('when there are two pending txs', function () { - var firstTxId = 1457634084250832 - var result, initialState - before(function (done) { - initialState = { - appState: { - currentView: { - name: 'confTx', - }, - }, - metamask: { - unapprovedTxs: { - '1457634084250832': { - id: firstTxId, - status: 'unconfirmed', - time: 1457634084250, - }, - '1457634084250833': { - id: 1457634084250833, - status: 'unconfirmed', - time: 1457634084255, - }, - }, - }, - } - freeze(initialState) - - // Mocking a background connection: - actions._setBackgroundConnection({ - approveTransaction (firstTxId, cb) { cb() }, - }) - - actions.sendTx({id: firstTxId})(function (action) { - result = reducers(initialState, action) - }) - done() - }) - - it('should stay on the confTx view', function () { - assert.equal(result.appState.currentView.name, 'confTx') - }) - - it('should transition to the first tx', function () { - assert.equal(result.appState.currentView.context, 0) - }) - }) }) }) diff --git a/test/unit/nonce-tracker-test.js b/test/unit/nonce-tracker-test.js index 36025a360..8970cf84d 100644 --- a/test/unit/nonce-tracker-test.js +++ b/test/unit/nonce-tracker-test.js @@ -1,41 +1,203 @@ const assert = require('assert') const NonceTracker = require('../../app/scripts/lib/nonce-tracker') +const MockTxGen = require('../lib/mock-tx-gen') +let providerResultStub = {} describe('Nonce Tracker', function () { - let nonceTracker, provider, getPendingTransactions, pendingTxs - - - beforeEach(function () { - pendingTxs = [{ - 'status': 'submitted', - 'txParams': { - 'from': '0x7d3517b0d011698406d6e0aed8453f0be2697926', - 'gas': '0x30d40', - 'value': '0x0', - 'nonce': '0x0', - }, - }] - - - getPendingTransactions = () => pendingTxs - provider = { - sendAsync: (_, cb) => { cb(undefined, {result: '0x0'}) }, - _blockTracker: { - getCurrentBlock: () => '0x11b568', - }, - } - nonceTracker = new NonceTracker({ - provider, - getPendingTransactions, - }) - }) + let nonceTracker, provider + let getPendingTransactions, pendingTxs + let getConfirmedTransactions, confirmedTxs describe('#getNonceLock', function () { - it('should work', async function () { - this.timeout(15000) - const nonceLock = await nonceTracker.getNonceLock('0x7d3517b0d011698406d6e0aed8453f0be2697926') - assert.equal(nonceLock.nextNonce, '1', 'nonce should be 1') - await nonceLock.releaseLock() + + describe('with 3 confirmed and 1 pending', function () { + beforeEach(function () { + const txGen = new MockTxGen() + confirmedTxs = txGen.generate({ status: 'confirmed' }, { count: 3 }) + pendingTxs = txGen.generate({ status: 'submitted' }, { count: 1 }) + nonceTracker = generateNonceTrackerWith(pendingTxs, confirmedTxs, '0x1') + }) + + it('should return 4', async function () { + this.timeout(15000) + const nonceLock = await nonceTracker.getNonceLock('0x7d3517b0d011698406d6e0aed8453f0be2697926') + assert.equal(nonceLock.nextNonce, '4', `nonce should be 4 got ${nonceLock.nextNonce}`) + await nonceLock.releaseLock() + }) + + it('should use localNonce if network returns a nonce lower then a confirmed tx in state', async function () { + this.timeout(15000) + const nonceLock = await nonceTracker.getNonceLock('0x7d3517b0d011698406d6e0aed8453f0be2697926') + assert.equal(nonceLock.nextNonce, '4', 'nonce should be 4') + await nonceLock.releaseLock() + }) + }) + + describe('with no previous txs', function () { + beforeEach(function () { + nonceTracker = generateNonceTrackerWith([], []) + }) + + it('should return 0', async function () { + this.timeout(15000) + const nonceLock = await nonceTracker.getNonceLock('0x7d3517b0d011698406d6e0aed8453f0be2697926') + assert.equal(nonceLock.nextNonce, '0', `nonce should be 0 returned ${nonceLock.nextNonce}`) + await nonceLock.releaseLock() + }) + }) + + describe('with multiple previous txs with same nonce', function () { + beforeEach(function () { + const txGen = new MockTxGen() + confirmedTxs = txGen.generate({ status: 'confirmed' }, { count: 1 }) + pendingTxs = txGen.generate({ + status: 'submitted', + txParams: { nonce: '0x01' }, + }, { count: 5 }) + + nonceTracker = generateNonceTrackerWith(pendingTxs, confirmedTxs, '0x0') + }) + + it('should return nonce after those', async function () { + this.timeout(15000) + const nonceLock = await nonceTracker.getNonceLock('0x7d3517b0d011698406d6e0aed8453f0be2697926') + assert.equal(nonceLock.nextNonce, '2', `nonce should be 2 got ${nonceLock.nextNonce}`) + await nonceLock.releaseLock() + }) + }) + + describe('when local confirmed count is higher than network nonce', function () { + beforeEach(function () { + const txGen = new MockTxGen() + confirmedTxs = txGen.generate({ status: 'confirmed' }, { count: 3 }) + nonceTracker = generateNonceTrackerWith([], confirmedTxs, '0x1') + }) + + it('should return nonce after those', async function () { + this.timeout(15000) + const nonceLock = await nonceTracker.getNonceLock('0x7d3517b0d011698406d6e0aed8453f0be2697926') + assert.equal(nonceLock.nextNonce, '3', `nonce should be 3 got ${nonceLock.nextNonce}`) + await nonceLock.releaseLock() + }) + }) + + describe('when local pending count is higher than other metrics', function () { + beforeEach(function () { + const txGen = new MockTxGen() + pendingTxs = txGen.generate({ status: 'submitted' }, { count: 2 }) + nonceTracker = generateNonceTrackerWith(pendingTxs, []) + }) + + it('should return nonce after those', async function () { + this.timeout(15000) + const nonceLock = await nonceTracker.getNonceLock('0x7d3517b0d011698406d6e0aed8453f0be2697926') + assert.equal(nonceLock.nextNonce, '2', `nonce should be 2 got ${nonceLock.nextNonce}`) + await nonceLock.releaseLock() + }) + }) + + describe('when provider nonce is higher than other metrics', function () { + beforeEach(function () { + const txGen = new MockTxGen() + pendingTxs = txGen.generate({ status: 'submitted' }, { count: 2 }) + nonceTracker = generateNonceTrackerWith(pendingTxs, [], '0x05') + }) + + it('should return nonce after those', async function () { + this.timeout(15000) + const nonceLock = await nonceTracker.getNonceLock('0x7d3517b0d011698406d6e0aed8453f0be2697926') + assert.equal(nonceLock.nextNonce, '5', `nonce should be 5 got ${nonceLock.nextNonce}`) + await nonceLock.releaseLock() + }) + }) + + describe('when there are some pending nonces below the remote one and some over.', function () { + beforeEach(function () { + const txGen = new MockTxGen() + pendingTxs = txGen.generate({ status: 'submitted' }, { count: 5 }) + nonceTracker = generateNonceTrackerWith(pendingTxs, [], '0x03') + }) + + it('should return nonce after those', async function () { + this.timeout(15000) + const nonceLock = await nonceTracker.getNonceLock('0x7d3517b0d011698406d6e0aed8453f0be2697926') + assert.equal(nonceLock.nextNonce, '5', `nonce should be 5 got ${nonceLock.nextNonce}`) + await nonceLock.releaseLock() + }) + }) + + describe('when there are pending nonces non sequentially over the network nonce.', function () { + beforeEach(function () { + const txGen = new MockTxGen() + txGen.generate({ status: 'submitted' }, { count: 5 }) + // 5 over that number + pendingTxs = txGen.generate({ status: 'submitted' }, { count: 5 }) + nonceTracker = generateNonceTrackerWith(pendingTxs, [], '0x00') + }) + + it('should return nonce after network nonce', async function () { + this.timeout(15000) + const nonceLock = await nonceTracker.getNonceLock('0x7d3517b0d011698406d6e0aed8453f0be2697926') + assert.equal(nonceLock.nextNonce, '0', `nonce should be 0 got ${nonceLock.nextNonce}`) + await nonceLock.releaseLock() + }) + }) + + describe('When all three return different values', function () { + beforeEach(function () { + const txGen = new MockTxGen() + const confirmedTxs = txGen.generate({ status: 'confirmed' }, { count: 10 }) + const pendingTxs = txGen.generate({ + status: 'submitted', + nonce: 100, + }, { count: 1 }) + // 0x32 is 50 in hex: + nonceTracker = generateNonceTrackerWith(pendingTxs, confirmedTxs, '0x32') + }) + + it('should return nonce after network nonce', async function () { + this.timeout(15000) + const nonceLock = await nonceTracker.getNonceLock('0x7d3517b0d011698406d6e0aed8453f0be2697926') + assert.equal(nonceLock.nextNonce, '50', `nonce should be 50 got ${nonceLock.nextNonce}`) + await nonceLock.releaseLock() + }) + }) + + describe('Faq issue 67', function () { + beforeEach(function () { + const txGen = new MockTxGen() + const confirmedTxs = txGen.generate({ status: 'confirmed' }, { count: 64 }) + const pendingTxs = txGen.generate({ + status: 'submitted', + }, { count: 10 }) + // 0x40 is 64 in hex: + nonceTracker = generateNonceTrackerWith(pendingTxs, [], '0x40') + }) + + it('should return nonce after network nonce', async function () { + this.timeout(15000) + const nonceLock = await nonceTracker.getNonceLock('0x7d3517b0d011698406d6e0aed8453f0be2697926') + assert.equal(nonceLock.nextNonce, '74', `nonce should be 74 got ${nonceLock.nextNonce}`) + await nonceLock.releaseLock() + }) }) }) }) + +function generateNonceTrackerWith (pending, confirmed, providerStub = '0x0') { + const getPendingTransactions = () => pending + const getConfirmedTransactions = () => confirmed + providerResultStub.result = providerStub + const provider = { + sendAsync: (_, cb) => { cb(undefined, providerResultStub) }, + _blockTracker: { + getCurrentBlock: () => '0x11b568', + }, + } + return new NonceTracker({ + provider, + getPendingTransactions, + getConfirmedTransactions, + }) +} + diff --git a/test/unit/tx-controller-test.js b/test/unit/tx-controller-test.js index 57d7deccd..7bb193242 100644 --- a/test/unit/tx-controller-test.js +++ b/test/unit/tx-controller-test.js @@ -6,12 +6,15 @@ const clone = require('clone') const sinon = require('sinon') const TransactionController = require('../../app/scripts/controllers/transactions') const TxProvideUtils = require('../../app/scripts/lib/tx-utils') +const txStateHistoryHelper = require('../../app/scripts/lib/tx-state-history-helper') + const noop = () => true const currentNetworkId = 42 const otherNetworkId = 36 const privKey = new Buffer('8718b9618a37d1fc78c436511fc6df3c8258d3250635bba617f33003270ec03e', 'hex') const { createStubedProvider } = require('../stub/provider') + describe('Transaction Controller', function () { let txController, engine, provider, providerResultStub @@ -47,7 +50,7 @@ describe('Transaction Controller', function () { metamaskNetworkId: currentNetworkId, txParams, } - txController._saveTxList([txMeta]) + txController.addTx(txMeta) stub = sinon.stub(txController, 'addUnapprovedTransaction').returns(Promise.resolve(txMeta)) }) @@ -279,12 +282,15 @@ describe('Transaction Controller', function () { it('replaces the tx with the same id', function () { txController.addTx({ id: '1', status: 'unapproved', metamaskNetworkId: currentNetworkId, txParams: {} }, noop) txController.addTx({ id: '2', status: 'confirmed', metamaskNetworkId: currentNetworkId, txParams: {} }, noop) - txController.updateTx({ id: '1', status: 'blah', hash: 'foo', metamaskNetworkId: currentNetworkId, txParams: {} }) - var result = txController.getTx('1') - assert.equal(result.hash, 'foo') + const tx1 = txController.getTx('1') + tx1.status = 'blah' + tx1.hash = 'foo' + txController.updateTx(tx1) + const savedResult = txController.getTx('1') + assert.equal(savedResult.hash, 'foo') }) - it('updates gas price', function () { + it('updates gas price and adds history items', function () { const originalGasPrice = '0x01' const desiredGasPrice = '0x02' @@ -297,13 +303,22 @@ describe('Transaction Controller', function () { }, } - const updatedMeta = clone(txMeta) - txController.addTx(txMeta) - updatedMeta.txParams.gasPrice = desiredGasPrice - txController.updateTx(updatedMeta) - var result = txController.getTx('1') + const updatedTx = txController.getTx('1') + // verify tx was initialized correctly + assert.equal(updatedTx.history.length, 1, 'one history item (initial)') + assert.equal(Array.isArray(updatedTx.history[0]), false, 'first history item is initial state') + assert.deepEqual(updatedTx.history[0], txStateHistoryHelper.snapshotFromTxMeta(updatedTx), 'first history item is initial state') + // modify value and updateTx + updatedTx.txParams.gasPrice = desiredGasPrice + txController.updateTx(updatedTx) + // check updated value + const result = txController.getTx('1') assert.equal(result.txParams.gasPrice, desiredGasPrice, 'gas price updated') + // validate history was updated + assert.equal(result.history.length, 2, 'two history items (initial + diff)') + const expectedEntry = { op: 'replace', path: '/txParams/gasPrice', value: desiredGasPrice } + assert.deepEqual(result.history[1], [expectedEntry], 'two history items (initial + diff)') }) }) diff --git a/test/unit/tx-state-history-helper-test.js b/test/unit/tx-state-history-helper-test.js new file mode 100644 index 000000000..90cb10713 --- /dev/null +++ b/test/unit/tx-state-history-helper-test.js @@ -0,0 +1,26 @@ +const assert = require('assert') +const clone = require('clone') +const txStateHistoryHelper = require('../../app/scripts/lib/tx-state-history-helper') + +describe('deepCloneFromTxMeta', function () { + it('should clone deep', function () { + const input = { + foo: { + bar: { + bam: 'baz' + } + } + } + const output = txStateHistoryHelper.snapshotFromTxMeta(input) + assert('foo' in output, 'has a foo key') + assert('bar' in output.foo, 'has a bar key') + assert('bam' in output.foo.bar, 'has a bar key') + assert.equal(output.foo.bar.bam, 'baz', 'has a baz value') + }) + + it('should remove the history key', function () { + const input = { foo: 'bar', history: 'remembered' } + const output = txStateHistoryHelper.snapshotFromTxMeta(input) + assert(typeof output.history, 'undefined', 'should remove history') + }) +}) diff --git a/test/unit/tx-state-history-helper.js b/test/unit/tx-state-history-helper.js new file mode 100644 index 000000000..5bb6c9bee --- /dev/null +++ b/test/unit/tx-state-history-helper.js @@ -0,0 +1,23 @@ +const assert = require('assert') +const txStateHistoryHelper = require('../../app/scripts/lib/tx-state-history-helper') +const testVault = require('../data/v17-long-history.json') + + +describe('tx-state-history-helper', function () { + it('migrates history to diffs and can recover original values', function () { + testVault.data.TransactionController.transactions.forEach((tx, index) => { + const newHistory = txStateHistoryHelper.migrateFromSnapshotsToDiffs(tx.history) + newHistory.forEach((newEntry, index) => { + if (index === 0) { + assert.equal(Array.isArray(newEntry), false, 'initial history item IS NOT a json patch obj') + } else { + assert.equal(Array.isArray(newEntry), true, 'non-initial history entry IS a json patch obj') + } + const oldEntry = tx.history[index] + const historySubset = newHistory.slice(0, index + 1) + const reconstructedValue = txStateHistoryHelper.replayHistory(historySubset) + assert.deepEqual(oldEntry, reconstructedValue, 'was able to reconstruct old entry from diffs') + }) + }) + }) +}) diff --git a/testem.yml b/testem.yml deleted file mode 100644 index 2cf40f7f4..000000000 --- a/testem.yml +++ /dev/null @@ -1,10 +0,0 @@ -launch_in_dev: - - Chrome - - Firefox -launch_in_ci: - - Chrome - - Firefox -framework: - - qunit -before_tests: "npm run buildCiUnits" -test_page: "test/integration/index.html" diff --git a/ui/app/actions.js b/ui/app/actions.js index 47da70277..678c68a6a 100644 --- a/ui/app/actions.js +++ b/ui/app/actions.js @@ -126,6 +126,7 @@ var actions = { txError: txError, nextTx: nextTx, previousTx: previousTx, + cancelAllTx: cancelAllTx, viewPendingTx: viewPendingTx, VIEW_PENDING_TX: 'VIEW_PENDING_TX', // app messages @@ -420,6 +421,7 @@ function signPersonalMsg (msgData) { function signTx (txData) { return (dispatch) => { + dispatch(actions.showLoadingIndication()) global.ethQuery.sendTransaction(txData, (err, data) => { dispatch(actions.hideLoadingIndication()) if (err) return dispatch(actions.displayWarning(err.message)) @@ -464,6 +466,7 @@ function updateAndApproveTx (txData) { dispatch(actions.hideLoadingIndication()) if (err) { dispatch(actions.txError(err)) + dispatch(actions.goHome()) return log.error(err.message) } dispatch(actions.completedTx(txData.id)) @@ -506,6 +509,16 @@ function cancelTx (txData) { } } +function cancelAllTx (txsData) { + return (dispatch) => { + txsData.forEach((txData, i) => { + background.cancelTransaction(txData.id, () => { + dispatch(actions.completedTx(txData.id)) + i === txsData.length - 1 ? dispatch(actions.goHome()) : null + }) + }) + } +} // // initialize screen // diff --git a/ui/app/add-token.js b/ui/app/add-token.js index 5c6dea4a0..4374ee586 100644 --- a/ui/app/add-token.js +++ b/ui/app/add-token.js @@ -3,6 +3,8 @@ const Component = require('react').Component const h = require('react-hyperscript') const connect = require('react-redux').connect const actions = require('./actions') +const Tooltip = require('./components/tooltip.js') + const ethUtil = require('ethereumjs-util') const abi = require('human-standard-token-abi') @@ -15,6 +17,7 @@ module.exports = connect(mapStateToProps)(AddTokenScreen) function mapStateToProps (state) { return { + identities: state.metamask.identities, } } @@ -64,15 +67,25 @@ AddTokenScreen.prototype.render = function () { }, [ h('div', [ - h('span', { - style: { fontWeight: 'bold', paddingRight: '10px'}, - }, 'Token Address'), + h(Tooltip, { + position: 'top', + title: 'The contract of the actual token contract. Click for more info.', + }, [ + h('a', { + style: { fontWeight: 'bold', paddingRight: '10px'}, + href: 'https://consensyssupport.happyfox.com/staff/kb/article/24-what-is-a-token-contract-address', + target: '_blank', + }, [ + h('span', 'Token Contract Address '), + h('i.fa.fa-question-circle'), + ]), + ]), ]), h('section.flex-row.flex-center', [ h('input#token-address', { name: 'address', - placeholder: 'Token Address', + placeholder: 'Token Contract Address', onChange: this.tokenAddressDidChange.bind(this), style: { width: 'inherit', @@ -171,7 +184,9 @@ AddTokenScreen.prototype.tokenAddressDidChange = function (event) { AddTokenScreen.prototype.validateInputs = function () { let msg = '' const state = this.state + const identitiesList = Object.keys(this.props.identities) const { address, symbol, decimals } = state + const standardAddress = ethUtil.addHexPrefix(address).toLowerCase() const validAddress = ethUtil.isValidAddress(address) if (!validAddress) { @@ -189,7 +204,12 @@ AddTokenScreen.prototype.validateInputs = function () { msg += 'Symbol must be between 0 and 10 characters.' } - const isValid = validAddress && validDecimals + const ownAddress = identitiesList.includes(standardAddress) + if (ownAddress) { + msg = 'Personal address detected. Input the token contract address.' + } + + const isValid = validAddress && validDecimals && !ownAddress if (!isValid) { this.setState({ @@ -215,4 +235,3 @@ AddTokenScreen.prototype.attemptToAutoFillTokenParams = async function (address) this.setState({ symbol: symbol[0], decimals: decimals[0].toString() }) } } - diff --git a/ui/app/app.js b/ui/app/app.js index 1ca59e406..14e6a26e2 100644 --- a/ui/app/app.js +++ b/ui/app/app.js @@ -46,6 +46,7 @@ function mapStateToProps (state) { identities, accounts, address, + keyrings, } = state.metamask const selected = address || Object.keys(accounts)[0] @@ -75,6 +76,7 @@ function mapStateToProps (state) { // state needed to get account dropdown temporarily rendering from app bar identities, selected, + keyrings, } } diff --git a/ui/app/components/account-export.js b/ui/app/components/account-export.js index 330f73805..32b103c86 100644 --- a/ui/app/components/account-export.js +++ b/ui/app/components/account-export.js @@ -1,6 +1,7 @@ const Component = require('react').Component const h = require('react-hyperscript') const inherits = require('util').inherits +const exportAsFile = require('../util').exportAsFile const copyToClipboard = require('copy-to-clipboard') const actions = require('../actions') const ethUtil = require('ethereumjs-util') @@ -20,20 +21,21 @@ function mapStateToProps (state) { } ExportAccountView.prototype.render = function () { - var state = this.props - var accountDetail = state.accountDetail + const state = this.props + const accountDetail = state.accountDetail + const nickname = state.identities[state.address].name if (!accountDetail) return h('div') - var accountExport = accountDetail.accountExport + const accountExport = accountDetail.accountExport - var notExporting = accountExport === 'none' - var exportRequested = accountExport === 'requested' - var accountExported = accountExport === 'completed' + const notExporting = accountExport === 'none' + const exportRequested = accountExport === 'requested' + const accountExported = accountExport === 'completed' if (notExporting) return h('div') if (exportRequested) { - var warning = `Export private keys at your own risk.` + const warning = `Export private keys at your own risk.` return ( h('div', { style: { @@ -89,6 +91,8 @@ ExportAccountView.prototype.render = function () { } if (accountExported) { + const plainKey = ethUtil.stripHexPrefix(accountDetail.privateKey) + return h('div.privateKey', { style: { margin: '0 20px', @@ -105,10 +109,16 @@ ExportAccountView.prototype.render = function () { onClick: function (event) { copyToClipboard(ethUtil.stripHexPrefix(accountDetail.privateKey)) }, - }, ethUtil.stripHexPrefix(accountDetail.privateKey)), + }, plainKey), h('button', { onClick: () => this.props.dispatch(actions.backToAccountDetail(this.props.address)), }, 'Done'), + h('button', { + style: { + marginLeft: '10px', + }, + onClick: () => exportAsFile(`MetaMask ${nickname} Private Key`, plainKey), + }, 'Save as File'), ]) } } @@ -117,6 +127,6 @@ ExportAccountView.prototype.onExportKeyPress = function (event) { if (event.key !== 'Enter') return event.preventDefault() - var input = document.getElementById('exportAccount').value + const input = document.getElementById('exportAccount').value this.props.dispatch(actions.exportAccount(input, this.props.address)) } diff --git a/ui/app/components/dropdowns/components/account-dropdowns.js b/ui/app/components/dropdowns/components/account-dropdowns.js index bb112dcca..fe80af8b3 100644 --- a/ui/app/components/dropdowns/components/account-dropdowns.js +++ b/ui/app/components/dropdowns/components/account-dropdowns.js @@ -25,7 +25,7 @@ class AccountDropdowns extends Component { } renderAccounts () { - const { identities, accounts, selected, menuItemStyles, actions } = this.props + const { identities, accounts, selected, menuItemStyles, actions, keyrings } = this.props return Object.keys(identities).map((key, index) => { const identity = identities[key] @@ -33,6 +33,12 @@ class AccountDropdowns extends Component { const balanceValue = accounts[key].balance const formattedBalance = balanceValue ? formatBalance(balanceValue, 6) : '...' + const simpleAddress = identity.address.substring(2).toLowerCase() + + const keyring = keyrings.find((kr) => { + return kr.accounts.includes(simpleAddress) || + kr.accounts.includes(identity.address) + }) return h( DropdownMenuItem, @@ -88,6 +94,7 @@ class AccountDropdowns extends Component { marginLeft: '10px', }, }, [ + this.indicateIfLoose(keyring), h('span.account-dropdown-name', { style: { fontSize: '18px', @@ -97,6 +104,7 @@ class AccountDropdowns extends Component { textOverflow: 'ellipsis', }, }, identity.name || ''), + h('span', { style: { marginLeft: '20px', fontSize: '24px' } }, isSelected ? h('.check', '✓') : null), h('span.account-dropdown-balance', { style: { @@ -125,11 +133,35 @@ class AccountDropdowns extends Component { ]), ]), +// ======= +// }, +// ), +// this.indicateIfLoose(keyring), +// h('span', { +// style: { +// marginLeft: '20px', +// fontSize: '24px', +// maxWidth: '145px', +// whiteSpace: 'nowrap', +// overflow: 'hidden', +// textOverflow: 'ellipsis', +// }, +// }, identity.name || ''), +// h('span', { style: { marginLeft: '20px', fontSize: '24px' } }, isSelected ? h('.check', '✓') : null), +// >>>>>>> master:ui/app/components/account-dropdowns.js ] ) }) } + indicateIfLoose (keyring) { + try { // Sometimes keyrings aren't loaded yet: + const type = keyring.type + const isLoose = type !== 'HD Key Tree' + return isLoose ? h('.keyring-label', 'LOOSE') : null + } catch (e) { return } + } + renderAccountSelector () { const { actions, useCssTransition, innerStyle } = this.props const { accountSelectorActive, menuItemStyles } = this.state @@ -389,7 +421,8 @@ AccountDropdowns.defaultProps = { AccountDropdowns.propTypes = { identities: PropTypes.objectOf(PropTypes.object), - selected: PropTypes.string, // TODO: refactor to be more explicit: selectedAddress + selected: PropTypes.string, + keyrings: PropTypes.array, } const mapDispatchToProps = (dispatch) => { diff --git a/ui/app/components/network.js b/ui/app/components/network.js index 9133c78e3..8424a479a 100644 --- a/ui/app/components/network.js +++ b/ui/app/components/network.js @@ -23,7 +23,7 @@ Network.prototype.render = function () { let iconName, hoverText if (networkNumber === 'loading') { - return h('span', { + return h('span.pointer', { style: { display: 'flex', alignItems: 'center', @@ -38,7 +38,7 @@ Network.prototype.render = function () { }, src: 'images/loading.svg', }), - h('i.fa.fa-sort-desc'), + h('i.fa.fa-caret-down'), ]) } else if (providerName === 'mainnet') { hoverText = 'Main Ethereum Network' @@ -77,7 +77,8 @@ Network.prototype.render = function () { style: { color: '#039396', }}, - 'Ethereum Main Net'), + 'Main Network'), + h('i.fa.fa-caret-down.fa-lg'), ]) case 'ropsten-test-network': return h('.network-indicator', [ @@ -90,6 +91,7 @@ Network.prototype.render = function () { color: '#ff6666', }}, 'Ropsten Test Net'), + h('i.fa.fa-caret-down.fa-lg'), ]) case 'kovan-test-network': return h('.network-indicator', [ @@ -102,6 +104,7 @@ Network.prototype.render = function () { color: '#690496', }}, 'Kovan Test Net'), + h('i.fa.fa-caret-down.fa-lg'), ]) case 'rinkeby-test-network': return h('.network-indicator', [ @@ -114,6 +117,7 @@ Network.prototype.render = function () { color: '#e7a218', }}, 'Rinkeby Test Net'), + h('i.fa.fa-caret-down.fa-lg'), ]) default: return h('.network-indicator', [ @@ -129,6 +133,7 @@ Network.prototype.render = function () { color: '#AEAEAE', }}, 'Private Network'), + h('i.fa.fa-caret-down.fa-lg'), ]) } })(), diff --git a/ui/app/components/pending-msg-details.js b/ui/app/components/pending-msg-details.js index 16308d121..718a22de0 100644 --- a/ui/app/components/pending-msg-details.js +++ b/ui/app/components/pending-msg-details.js @@ -38,7 +38,7 @@ PendingMsgDetails.prototype.render = function () { // message data h('.tx-data.flex-column.flex-justify-center.flex-grow.select-none', [ - h('.flex-row.flex-space-between', [ + h('.flex-column.flex-space-between', [ h('label.font-small', 'MESSAGE'), h('span.font-small', msgParams.data), ]), diff --git a/ui/app/components/pending-msg.js b/ui/app/components/pending-msg.js index b2cac164a..834719c53 100644 --- a/ui/app/components/pending-msg.js +++ b/ui/app/components/pending-msg.js @@ -18,6 +18,9 @@ PendingMsg.prototype.render = function () { h('div', { key: msgData.id, + style: { + maxWidth: '350px', + }, }, [ // header @@ -32,10 +35,21 @@ PendingMsg.prototype.render = function () { style: { margin: '10px', }, - }, `Signing this message can have + }, [ + `Signing this message can have dangerous side effects. Only sign messages from sites you fully trust with your entire account. - This will be fixed in a future version.`), + This dangerous method will be removed in a future version. `, + h('a', { + href: 'https://medium.com/metamask/the-new-secure-way-to-sign-data-in-your-browser-6af9dd2a1527', + style: { color: 'rgb(247, 134, 28)' }, + onClick: (event) => { + event.preventDefault() + const url = 'https://medium.com/metamask/the-new-secure-way-to-sign-data-in-your-browser-6af9dd2a1527' + global.platform.openWindow({ url }) + }, + }, 'Read more here.'), + ]), // message details h(PendingTxDetails, state), diff --git a/ui/app/components/pending-tx.js b/ui/app/components/pending-tx.js index c1b079a25..a679107c9 100644 --- a/ui/app/components/pending-tx.js +++ b/ui/app/components/pending-tx.js @@ -240,6 +240,15 @@ PendingTx.prototype.render = function () { totalInETH, } = this.getData() + // This is from the latest master + // It handles some of the errors that we are not currently handling + // Leaving as comments fo reference + + // const balanceBn = hexToBn(balance) + // const insufficientBalance = balanceBn.lt(maxCost) + // const buyDisabled = insufficientBalance || !this.state.valid || !isValidAddress || this.state.submitting + // const showRejectAll = props.unconfTxListLength > 1 + this.inputs = [] return ( @@ -332,9 +341,88 @@ PendingTx.prototype.render = function () { h('div.confirm-screen-row-info', `$${totalInUSD} USD`), h('div.confirm-screen-row-detail', `${totalInETH} ETH`), ]), - ]), + ]), ]), +// These are latest errors handling from master +// Leaving as comments as reference when we start implementing error handling +// h('style', ` +// .conf-buttons button { +// margin-left: 10px; +// text-transform: uppercase; +// } +// `), + +// txMeta.simulationFails ? +// h('.error', { +// style: { +// marginLeft: 50, +// fontSize: '0.9em', +// }, +// }, 'Transaction Error. Exception thrown in contract code.') +// : null, + +// !isValidAddress ? +// h('.error', { +// style: { +// marginLeft: 50, +// fontSize: '0.9em', +// }, +// }, 'Recipient address is invalid. Sending this transaction will result in a loss of ETH.') +// : null, + +// insufficientBalance ? +// h('span.error', { +// style: { +// marginLeft: 50, +// fontSize: '0.9em', +// }, +// }, 'Insufficient balance for transaction') +// : null, + +// // send + cancel +// h('.flex-row.flex-space-around.conf-buttons', { +// style: { +// display: 'flex', +// justifyContent: 'flex-end', +// margin: '14px 25px', +// }, +// }, [ +// h('button', { +// onClick: (event) => { +// this.resetGasFields() +// event.preventDefault() +// }, +// }, 'Reset'), + +// // Accept Button or Buy Button +// insufficientBalance ? h('button.btn-green', { onClick: props.buyEth }, 'Buy Ether') : +// h('input.confirm.btn-green', { +// type: 'submit', +// value: 'SUBMIT', +// style: { marginLeft: '10px' }, +// disabled: buyDisabled, +// }), + +// h('button.cancel.btn-red', { +// onClick: props.cancelTransaction, +// }, 'Reject'), +// ]), +// showRejectAll ? h('.flex-row.flex-space-around.conf-buttons', { +// style: { +// display: 'flex', +// justifyContent: 'flex-end', +// margin: '14px 25px', +// }, +// }, [ +// h('button.cancel.btn-red', { +// onClick: props.cancelAllTransactions, +// }, 'Reject All'), +// ]) : null, +// ]), +// ]) +// ) +// } ]), h('form#pending-tx-form.flex-column.flex-center', { diff --git a/ui/app/components/token-list.js b/ui/app/components/token-list.js index 2d1dd0ea7..0efa89c63 100644 --- a/ui/app/components/token-list.js +++ b/ui/app/components/token-list.js @@ -48,10 +48,28 @@ TokenList.prototype.render = function () { if (error) { log.error(error) - return this.message('There was a problem loading your token balances.') + return h('.hotFix', { + style: { + padding: '80px', + }, + }, [ + 'We had trouble loading your token balances. You can view them ', + h('span.hotFix', { + style: { + color: 'rgba(247, 134, 28, 1)', + cursor: 'pointer', + }, + onClick: () => { + global.platform.openWindow({ + url: `https://ethplorer.io/address/${userAddress}`, + }) + }, + }, 'here'), + ]) } return h('div', tokens.map((tokenData) => h(TokenCell, tokenData))) + } TokenList.prototype.message = function (body) { @@ -84,7 +102,7 @@ TokenList.prototype.createFreshTokenTracker = function () { this.tracker = new TokenTracker({ userAddress, provider: global.ethereumProvider, - tokens: uniqueMergeTokens(defaultTokens, this.props.tokens), + tokens: this.props.tokens, pollingInterval: 8000, }) @@ -149,4 +167,3 @@ function uniqueMergeTokens (tokensA, tokensB = []) { }) return result } - diff --git a/ui/app/components/transaction-list-item.js b/ui/app/components/transaction-list-item.js index eca2a7100..880a288af 100644 --- a/ui/app/components/transaction-list-item.js +++ b/ui/app/components/transaction-list-item.js @@ -60,16 +60,7 @@ TransactionListItem.prototype.render = function () { }, [ h('.identicon-wrapper.flex-column.flex-center.select-none', [ - h('.pop-hover', { - onClick: (event) => { - event.stopPropagation() - if (!isTx || isPending) return - var url = `https://metamask.github.io/eth-tx-viz/?tx=${transaction.hash}` - global.platform.openWindow({ url }) - }, - }, [ - h(TransactionIcon, { txParams, transaction, isTx, isMsg }), - ]), + h(TransactionIcon, { txParams, transaction, isTx, isMsg }), ]), h(Tooltip, { diff --git a/ui/app/conf-tx.js b/ui/app/conf-tx.js index 7cc319509..7062eee6b 100644 --- a/ui/app/conf-tx.js +++ b/ui/app/conf-tx.js @@ -76,6 +76,7 @@ ConfirmTxScreen.prototype.render = function () { cancelMessage: this.cancelMessage.bind(this, txData), cancelPersonalMessage: this.cancelPersonalMessage.bind(this, txData), }) + } function currentTxView (opts) { @@ -116,6 +117,12 @@ ConfirmTxScreen.prototype.cancelTransaction = function (txData, event) { this.props.dispatch(actions.cancelTx(txData)) } +ConfirmTxScreen.prototype.cancelAllTransactions = function (unconfTxList, event) { + this.stopPropagation(event) + event.preventDefault() + this.props.dispatch(actions.cancelAllTx(unconfTxList)) +} + ConfirmTxScreen.prototype.signMessage = function (msgData, event) { log.info('conf-tx.js: signing message') var params = msgData.msgParams diff --git a/ui/app/config.js b/ui/app/config.js index 62785c49b..d64088ccb 100644 --- a/ui/app/config.js +++ b/ui/app/config.js @@ -5,7 +5,8 @@ const connect = require('react-redux').connect const actions = require('./actions') const currencies = require('./conversion.json').rows const validUrl = require('valid-url') -const copyToClipboard = require('copy-to-clipboard') +const exportAsFile = require('./util').exportAsFile + module.exports = connect(mapStateToProps)(ConfigScreen) @@ -110,9 +111,9 @@ ConfigScreen.prototype.render = function () { alignSelf: 'center', }, onClick (event) { - copyToClipboard(window.logState()) + exportAsFile('MetaMask State Logs', window.logState()) }, - }, 'Copy State Logs'), + }, 'Download State Logs'), ]), h('hr.horizontal-line'), diff --git a/ui/app/css/itcss/tools/utilities.scss b/ui/app/css/itcss/tools/utilities.scss index b9c99219b..9f1caa732 100644 --- a/ui/app/css/itcss/tools/utilities.scss +++ b/ui/app/css/itcss/tools/utilities.scss @@ -238,7 +238,7 @@ hr.horizontal-line { border-radius: 10px; height: 20px; min-width: 20px; - position: relative; + position: absolute; display: flex; align-items: center; justify-content: center; diff --git a/ui/app/info.js b/ui/app/info.js index 899841c83..4c7d4cb4c 100644 --- a/ui/app/info.js +++ b/ui/app/info.js @@ -103,7 +103,7 @@ InfoScreen.prototype.render = function () { [ h('div.fa.fa-support', [ h('a.info', { - href: 'http://metamask.consensyssupport.happyfox.com', + href: 'https://support.metamask.io', target: '_blank', }, 'Visit our Support Center'), ]), diff --git a/ui/app/keychains/hd/create-vault-complete.js b/ui/app/keychains/hd/create-vault-complete.js index c32751fff..745990351 100644 --- a/ui/app/keychains/hd/create-vault-complete.js +++ b/ui/app/keychains/hd/create-vault-complete.js @@ -3,6 +3,7 @@ const Component = require('react').Component const connect = require('react-redux').connect const h = require('react-hyperscript') const actions = require('../../actions') +const exportAsFile = require('../../util').exportAsFile module.exports = connect(mapStateToProps)(CreateVaultCompleteScreen) @@ -65,8 +66,17 @@ CreateVaultCompleteScreen.prototype.render = function () { style: { margin: '24px', fontSize: '0.9em', + marginBottom: '10px', }, }, 'I\'ve copied it somewhere safe'), + + h('button.primary', { + onClick: () => exportAsFile(`MetaMask Seed Words`, seed), + style: { + margin: '10px', + fontSize: '0.9em', + }, + }, 'Save Seed Words As File'), ]) ) } diff --git a/ui/app/reducers.js b/ui/app/reducers.js index 36045772f..6a2f44534 100644 --- a/ui/app/reducers.js +++ b/ui/app/reducers.js @@ -42,7 +42,10 @@ function rootReducer (state, action) { } window.logState = function () { - var stateString = JSON.stringify(window.METAMASK_CACHED_LOG_STATE, removeSeedWords, 2) + let state = window.METAMASK_CACHED_LOG_STATE + const version = global.platform.getVersion() + state.version = version + let stateString = JSON.stringify(state, removeSeedWords, 2) return stateString } diff --git a/ui/app/unlock.js b/ui/app/unlock.js index 1918e2e6a..ec97b03bf 100644 --- a/ui/app/unlock.js +++ b/ui/app/unlock.js @@ -80,7 +80,7 @@ UnlockScreen.prototype.render = function () { color: 'rgb(247, 134, 28)', textDecoration: 'underline', }, - }, 'I forgot my password.'), + }, 'Restore from seed phrase'), ]), ]) ) diff --git a/ui/app/util.js b/ui/app/util.js index 6596ebafb..be26e15a5 100644 --- a/ui/app/util.js +++ b/ui/app/util.js @@ -53,6 +53,7 @@ module.exports = { getTxFeeBn, shortenBalance, getContractAtAddress, + exportAsFile: exportAsFile, } function valuesFor (obj) { @@ -250,3 +251,18 @@ function getTxFeeBn (gas, gasPrice = MIN_GAS_PRICE_BN.toString(16), blockGasLimi function getContractAtAddress (tokenAddress) { return global.eth.contract(abi).at(tokenAddress) } + +function exportAsFile (filename, data) { + // source: https://stackoverflow.com/a/33542499 by Ludovic Feltz + const blob = new Blob([data], {type: 'text/csv'}) + if (window.navigator.msSaveOrOpenBlob) { + window.navigator.msSaveBlob(blob, filename) + } else { + const elem = window.document.createElement('a') + elem.href = window.URL.createObjectURL(blob) + elem.download = filename + document.body.appendChild(elem) + elem.click() + document.body.removeChild(elem) + } +} diff --git a/ui/lib/account-link.js b/ui/lib/account-link.js index d061d0ad1..037d990fa 100644 --- a/ui/lib/account-link.js +++ b/ui/lib/account-link.js @@ -3,19 +3,19 @@ module.exports = function (address, network) { let link switch (net) { case 1: // main net - link = `http://etherscan.io/address/${address}` + link = `https://etherscan.io/address/${address}` break case 2: // morden test net - link = `http://morden.etherscan.io/address/${address}` + link = `https://morden.etherscan.io/address/${address}` break case 3: // ropsten test net - link = `http://ropsten.etherscan.io/address/${address}` + link = `https://ropsten.etherscan.io/address/${address}` break case 4: // rinkeby test net - link = `http://rinkeby.etherscan.io/address/${address}` + link = `https://rinkeby.etherscan.io/address/${address}` break case 42: // kovan test net - link = `http://kovan.etherscan.io/address/${address}` + link = `https://kovan.etherscan.io/address/${address}` break default: link = ''