1
0
mirror of https://github.com/kremalicious/metamask-extension.git synced 2024-12-23 09:52:26 +01:00

Resolve merge conflict from master

This commit is contained in:
Kevin Serrano 2017-09-05 09:03:44 -07:00
commit e647337a8a
No known key found for this signature in database
GPG Key ID: BF999DEFC7371BA1
32 changed files with 3789 additions and 268 deletions

View File

@ -2,8 +2,44 @@
## Current Master ## Current Master
- Make eth_sign deprecation warning less noisy
- 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.
## 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
- Replace account screen with an account drop-down menu. - Replace account screen with an account drop-down menu.
- Replace confusing buttons with a new account-specific drop-down menu. - Replace account buttons with a new account-specific drop-down menu.
## 3.9.5 2017-8-04 ## 3.9.5 2017-8-04

View File

@ -1,7 +1,7 @@
{ {
"name": "MetaMask", "name": "MetaMask",
"short_name": "Metamask", "short_name": "Metamask",
"version": "3.9.5", "version": "3.9.11",
"manifest_version": 2, "manifest_version": 2,
"author": "https://metamask.io", "author": "https://metamask.io",
"description": "Ethereum Browser Extension", "description": "Ethereum Browser Extension",

View File

@ -1,6 +1,5 @@
const EventEmitter = require('events') const EventEmitter = require('events')
const extend = require('xtend') const extend = require('xtend')
const clone = require('clone')
const ObservableStore = require('obs-store') const ObservableStore = require('obs-store')
const ethUtil = require('ethereumjs-util') const ethUtil = require('ethereumjs-util')
const EthQuery = require('ethjs-query') const EthQuery = require('ethjs-query')
@ -8,6 +7,7 @@ const TxProviderUtil = require('../lib/tx-utils')
const PendingTransactionTracker = require('../lib/pending-tx-tracker') const PendingTransactionTracker = require('../lib/pending-tx-tracker')
const createId = require('../lib/random-id') const createId = require('../lib/random-id')
const NonceTracker = require('../lib/nonce-tracker') const NonceTracker = require('../lib/nonce-tracker')
const txStateHistoryHelper = require('../lib/tx-state-history-helper')
module.exports = class TransactionController extends EventEmitter { module.exports = class TransactionController extends EventEmitter {
constructor (opts) { constructor (opts) {
@ -33,6 +33,17 @@ module.exports = class TransactionController extends EventEmitter {
err: undefined, 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.query = new EthQuery(this.provider)
this.txProviderUtil = new TxProviderUtil(this.provider) this.txProviderUtil = new TxProviderUtil(this.provider)
@ -128,19 +139,17 @@ module.exports = class TransactionController extends EventEmitter {
updateTx (txMeta) { updateTx (txMeta) {
// create txMeta snapshot for history // create txMeta snapshot for history
const txMetaForHistory = clone(txMeta) const currentState = txStateHistoryHelper.snapshotFromTxMeta(txMeta)
// dont include previous history in this snapshot // recover previous tx state obj
delete txMetaForHistory.history const previousState = txStateHistoryHelper.replayHistory(txMeta.history)
// add snapshot to tx history // generate history entry and add to history
if (!txMeta.history) txMeta.history = [] const entry = txStateHistoryHelper.generateHistoryEntry(previousState, currentState)
txMeta.history.push(txMetaForHistory) txMeta.history.push(entry)
// commit txMeta to state
const txId = txMeta.id const txId = txMeta.id
const txList = this.getFullTxList() const txList = this.getFullTxList()
const index = txList.findIndex(txData => txData.id === txId) const index = txList.findIndex(txData => txData.id === txId)
if (!txMeta.history) txMeta.history = []
txMeta.history.push(txMetaForHistory)
txList[index] = txMeta txList[index] = txMeta
this._saveTxList(txList) this._saveTxList(txList)
this.emit('update') this.emit('update')
@ -148,16 +157,22 @@ module.exports = class TransactionController extends EventEmitter {
// Adds a tx to the txlist // Adds a tx to the txlist
addTx (txMeta) { addTx (txMeta) {
const txCount = this.getTxCount() // initialize history
const network = this.getNetwork() txMeta.history = []
const fullTxList = this.getFullTxList() // capture initial snapshot of txMeta for history
const txHistoryLimit = this.txHistoryLimit const snapshot = txStateHistoryHelper.snapshotFromTxMeta(txMeta)
txMeta.history.push(snapshot)
// checks if the length of the tx history is // checks if the length of the tx history is
// longer then desired persistence limit // longer then desired persistence limit
// and then if it is removes only confirmed // and then if it is removes only confirmed
// or rejected tx's. // or rejected tx's.
// not tx's that are pending or unapproved // 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) { if (txCount > txHistoryLimit - 1) {
const index = fullTxList.findIndex((metaTx) => ((metaTx.status === 'confirmed' || metaTx.status === 'rejected') && network === txMeta.metamaskNetworkId)) const index = fullTxList.findIndex((metaTx) => ((metaTx.status === 'confirmed' || metaTx.status === 'rejected') && network === txMeta.metamaskNetworkId))
fullTxList.splice(index, 1) fullTxList.splice(index, 1)
@ -206,7 +221,6 @@ module.exports = class TransactionController extends EventEmitter {
status: 'unapproved', status: 'unapproved',
metamaskNetworkId: this.getNetwork(), metamaskNetworkId: this.getNetwork(),
txParams: txParams, txParams: txParams,
history: [],
} }
// add default tx params // add default tx params
await this.addTxDefaults(txMeta) await this.addTxDefaults(txMeta)

View File

@ -2,30 +2,55 @@ module.exports = setupDappAutoReload
function setupDappAutoReload (web3, observable) { function setupDappAutoReload (web3, observable) {
// export web3 as a global, checking for usage // export web3 as a global, checking for usage
let hasBeenWarned = false
let reloadInProgress = false
let lastTimeUsed
let lastSeenNetwork
global.web3 = new Proxy(web3, { global.web3 = new Proxy(web3, {
get: (_web3, name) => { 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/ethereum/mist/releases/tag/v0.9.0')
hasBeenWarned = true
}
// get the time of use // get the time of use
if (name !== '_used') _web3._used = Date.now() lastTimeUsed = Date.now()
return _web3[name] // return value normally
return _web3[key]
}, },
set: (_web3, name, value) => { set: (_web3, key, value) => {
_web3[name] = value // set value normally
_web3[key] = value
}, },
}) })
var networkVersion
observable.subscribe(function (state) { observable.subscribe(function (state) {
// get the initial network // if reload in progress, no need to check reload logic
const curentNetVersion = state.networkVersion if (reloadInProgress) return
if (!networkVersion) networkVersion = curentNetVersion
if (curentNetVersion !== networkVersion && web3._used) { const currentNetwork = state.networkVersion
const timeSinceUse = Date.now() - web3._used
// 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 web3 was recently used then delay the reloading of the page
timeSinceUse > 500 ? triggerReset() : setTimeout(triggerReset, 500) if (timeSinceUse > 500) {
// prevent reentry into if statement if state updates again before triggerReset()
// reload } else {
networkVersion = curentNetVersion setTimeout(triggerReset, 500)
} }
}) })
} }

View File

@ -80,7 +80,7 @@ MetamaskInpageProvider.prototype.send = function (payload) {
case 'eth_coinbase': case 'eth_coinbase':
// read from localStorage // read from localStorage
selectedAddress = self.publicConfigStore.getState().selectedAddress selectedAddress = self.publicConfigStore.getState().selectedAddress
result = selectedAddress result = selectedAddress || null
break break
case 'eth_uninstallFilter': case 'eth_uninstallFilter':
@ -90,7 +90,7 @@ MetamaskInpageProvider.prototype.send = function (payload) {
case 'net_version': case 'net_version':
const networkVersion = self.publicConfigStore.getState().networkVersion const networkVersion = self.publicConfigStore.getState().networkVersion
result = networkVersion result = networkVersion || null
break break
// throw not-supported Error // throw not-supported Error

View File

@ -1,13 +1,14 @@
const EthQuery = require('eth-query') const EthQuery = require('ethjs-query')
const assert = require('assert') const assert = require('assert')
const Mutex = require('await-semaphore').Mutex const Mutex = require('await-semaphore').Mutex
class NonceTracker { class NonceTracker {
constructor ({ provider, getPendingTransactions }) { constructor ({ provider, getPendingTransactions, getConfirmedTransactions }) {
this.provider = provider this.provider = provider
this.ethQuery = new EthQuery(provider) this.ethQuery = new EthQuery(provider)
this.getPendingTransactions = getPendingTransactions this.getPendingTransactions = getPendingTransactions
this.getConfirmedTransactions = getConfirmedTransactions
this.lockMap = {} this.lockMap = {}
} }
@ -25,21 +26,28 @@ class NonceTracker {
await this._globalMutexFree() await this._globalMutexFree()
// await lock free, then take lock // await lock free, then take lock
const releaseLock = await this._takeMutex(address) const releaseLock = await this._takeMutex(address)
// calculate next nonce // evaluate multiple nextNonce strategies
// we need to make sure our base count const nonceDetails = {}
// and pending count are from the same block const networkNonceResult = await this._getNetworkNextNonce(address)
const currentBlock = await this._getCurrentBlock() const highestLocallyConfirmed = this._getHighestLocallyConfirmed(address)
const pendingTransactions = this.getPendingTransactions(address) const nextNetworkNonce = networkNonceResult.nonce
const pendingCount = pendingTransactions.length const highestLocalNonce = highestLocallyConfirmed
assert(Number.isInteger(pendingCount), `nonce-tracker - pendingCount is not an integer - got: (${typeof pendingCount}) "${pendingCount}"`) const highestSuggested = Math.max(nextNetworkNonce, highestLocalNonce)
const baseCountHex = await this._getTxCount(address, currentBlock)
const baseCount = parseInt(baseCountHex, 16) const pendingTxs = this.getPendingTransactions(address)
assert(Number.isInteger(baseCount), `nonce-tracker - baseCount is not an integer - got: (${typeof baseCount}) "${baseCount}"`) const localNonceResult = this._getHighestContinuousFrom(pendingTxs, highestSuggested) || 0
const nextNonce = baseCount + pendingCount
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}"`) 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 nonce and release cb
return { nextNonce, nonceDetails, releaseLock } 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 () { async _globalMutexFree () {
const globalMutex = this._lookupMutex('global') const globalMutex = this._lookupMutex('global')
const release = await globalMutex.acquire() const release = await globalMutex.acquire()
@ -83,12 +82,68 @@ class NonceTracker {
return mutex 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 // this is a hotfix for the fact that the blockTracker will
// change when the network changes // change when the network changes
_getBlockTracker () { _getBlockTracker () {
return this.provider._blockTracker return this.provider._blockTracker
} }
} }
module.exports = NonceTracker module.exports = NonceTracker

View File

@ -1,6 +1,7 @@
const EventEmitter = require('events') const EventEmitter = require('events')
const EthQuery = require('ethjs-query') const EthQuery = require('ethjs-query')
const sufficientBalance = require('./util').sufficientBalance const sufficientBalance = require('./util').sufficientBalance
const RETRY_LIMIT = 3500 // Retry 3500 blocks, or about 1 day.
/* /*
Utility class for tracking the transactions as they Utility class for tracking the transactions as they
@ -28,6 +29,7 @@ module.exports = class PendingTransactionTracker extends EventEmitter {
this.getBalance = config.getBalance this.getBalance = config.getBalance
this.getPendingTransactions = config.getPendingTransactions this.getPendingTransactions = config.getPendingTransactions
this.publishTransaction = config.publishTransaction this.publishTransaction = config.publishTransaction
this.giveUpOnTransaction = config.giveUpOnTransaction
} }
// checks if a signed tx is in a block and // 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 (balance === undefined) return
if (!('retryCount' in txMeta)) txMeta.retryCount = 0 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 the value of the transaction is greater then the balance, fail.
if (!sufficientBalance(txMeta.txParams, balance)) { if (!sufficientBalance(txMeta.txParams, balance)) {
const insufficientFundsError = new Error('Insufficient balance during rebroadcast.') const insufficientFundsError = new Error('Insufficient balance during rebroadcast.')

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}

View File

@ -28,4 +28,6 @@ module.exports = [
require('./015'), require('./015'),
require('./016'), require('./016'),
require('./017'), require('./017'),
require('./018'),
require('./019'),
] ]

View File

@ -1,5 +1,5 @@
const Iframe = require('iframe') const Iframe = require('iframe')
const IframeStream = require('iframe-stream').IframeStream const createIframeStream = require('iframe-stream').IframeStream
module.exports = setupIframe module.exports = setupIframe
@ -13,7 +13,7 @@ function setupIframe(opts) {
}) })
var iframe = frame.iframe var iframe = frame.iframe
iframe.style.setProperty('display', 'none') iframe.style.setProperty('display', 'none')
var iframeStream = new IframeStream(iframe) var iframeStream = createIframeStream(iframe)
return iframeStream return iframeStream
} }

View File

@ -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 SWcontroller = require('client-sw-ready-event/lib/sw-client.js')
const SwStream = require('sw-stream/lib/sw-stream.js') const SwStream = require('sw-stream/lib/sw-stream.js')
const SetupUntrustedComunication = ('./lib/setup-untrusted-connection.js') const SetupUntrustedComunication = ('./lib/setup-untrusted-connection.js')
@ -11,7 +11,7 @@ const background = new SWcontroller({
intervalDelay, intervalDelay,
}) })
const pageStream = new ParentStream() const pageStream = createParentStream()
background.on('ready', (_) => { background.on('ready', (_) => {
let swStream = SwStream({ let swStream = SwStream({
serviceWorker: background.controller, serviceWorker: background.controller,

View File

@ -73,15 +73,16 @@
"eth-query": "^2.1.2", "eth-query": "^2.1.2",
"eth-sig-util": "^1.2.2", "eth-sig-util": "^1.2.2",
"eth-simple-keyring": "^1.1.1", "eth-simple-keyring": "^1.1.1",
"eth-token-tracker": "^1.1.2", "eth-token-tracker": "^1.1.3",
"ethereumjs-tx": "^1.3.0", "ethereumjs-tx": "^1.3.0",
"ethereumjs-util": "github:ethereumjs/ethereumjs-util#ac5d0908536b447083ea422b435da27f26615de9", "ethereumjs-util": "github:ethereumjs/ethereumjs-util#ac5d0908536b447083ea422b435da27f26615de9",
"ethereumjs-wallet": "^0.6.0", "ethereumjs-wallet": "^0.6.0",
"ethjs-ens": "^2.0.0", "ethjs-ens": "^2.0.0",
"ethjs-query": "^0.2.6", "ethjs-query": "^0.2.9",
"express": "^4.14.0", "express": "^4.14.0",
"extension-link-enabler": "^1.0.0", "extension-link-enabler": "^1.0.0",
"extensionizer": "^1.0.0", "extensionizer": "^1.0.0",
"fast-json-patch": "^2.0.4",
"fast-levenshtein": "^2.0.6", "fast-levenshtein": "^2.0.6",
"gulp": "github:gulpjs/gulp#4.0", "gulp": "github:gulpjs/gulp#4.0",
"gulp-eslint": "^4.0.0", "gulp-eslint": "^4.0.0",
@ -185,7 +186,7 @@
"react-addons-test-utils": "^15.5.1", "react-addons-test-utils": "^15.5.1",
"react-test-renderer": "^15.5.4", "react-test-renderer": "^15.5.4",
"react-testutils-additions": "^15.2.0", "react-testutils-additions": "^15.2.0",
"sinon": "^2.3.8", "sinon": "^3.2.0",
"tape": "^4.5.1", "tape": "^4.5.1",
"testem": "^1.10.3", "testem": "^1.10.3",
"uglifyify": "^4.0.2", "uglifyify": "^4.0.2",

File diff suppressed because it is too large Load Diff

40
test/lib/mock-tx-gen.js Normal file
View File

@ -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

View File

@ -65,91 +65,6 @@ describe('tx confirmation screen', function () {
assert.equal(count, 0) 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)
})
})
}) })
}) })

View File

@ -1,41 +1,184 @@
const assert = require('assert') const assert = require('assert')
const NonceTracker = require('../../app/scripts/lib/nonce-tracker') const NonceTracker = require('../../app/scripts/lib/nonce-tracker')
const MockTxGen = require('../lib/mock-tx-gen')
let providerResultStub = {}
describe('Nonce Tracker', function () { describe('Nonce Tracker', function () {
let nonceTracker, provider, getPendingTransactions, pendingTxs let nonceTracker, provider
let getPendingTransactions, pendingTxs
let getConfirmedTransactions, confirmedTxs
describe('#getNonceLock', function () {
describe('with 3 confirmed and 1 pending', function () {
beforeEach(function () { beforeEach(function () {
pendingTxs = [{ const txGen = new MockTxGen()
'status': 'submitted', confirmedTxs = txGen.generate({ status: 'confirmed' }, { count: 3 })
'txParams': { pendingTxs = txGen.generate({ status: 'submitted' }, { count: 1 })
'from': '0x7d3517b0d011698406d6e0aed8453f0be2697926', nonceTracker = generateNonceTrackerWith(pendingTxs, confirmedTxs, '0x1')
'gas': '0x30d40', })
'value': '0x0',
'nonce': '0x0',
},
}]
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()
})
getPendingTransactions = () => pendingTxs it('should use localNonce if network returns a nonce lower then a confirmed tx in state', async function () {
provider = { this.timeout(15000)
sendAsync: (_, cb) => { cb(undefined, {result: '0x0'}) }, 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()
})
})
})
})
function generateNonceTrackerWith (pending, confirmed, providerStub = '0x0') {
const getPendingTransactions = () => pending
const getConfirmedTransactions = () => confirmed
providerResultStub.result = providerStub
const provider = {
sendAsync: (_, cb) => { cb(undefined, providerResultStub) },
_blockTracker: { _blockTracker: {
getCurrentBlock: () => '0x11b568', getCurrentBlock: () => '0x11b568',
}, },
} }
nonceTracker = new NonceTracker({ return new NonceTracker({
provider, provider,
getPendingTransactions, getPendingTransactions,
getConfirmedTransactions,
}) })
}) }
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()
})
})
})

View File

@ -6,12 +6,15 @@ const clone = require('clone')
const sinon = require('sinon') const sinon = require('sinon')
const TransactionController = require('../../app/scripts/controllers/transactions') const TransactionController = require('../../app/scripts/controllers/transactions')
const TxProvideUtils = require('../../app/scripts/lib/tx-utils') const TxProvideUtils = require('../../app/scripts/lib/tx-utils')
const txStateHistoryHelper = require('../../app/scripts/lib/tx-state-history-helper')
const noop = () => true const noop = () => true
const currentNetworkId = 42 const currentNetworkId = 42
const otherNetworkId = 36 const otherNetworkId = 36
const privKey = new Buffer('8718b9618a37d1fc78c436511fc6df3c8258d3250635bba617f33003270ec03e', 'hex') const privKey = new Buffer('8718b9618a37d1fc78c436511fc6df3c8258d3250635bba617f33003270ec03e', 'hex')
const { createStubedProvider } = require('../stub/provider') const { createStubedProvider } = require('../stub/provider')
describe('Transaction Controller', function () { describe('Transaction Controller', function () {
let txController, engine, provider, providerResultStub let txController, engine, provider, providerResultStub
@ -47,7 +50,7 @@ describe('Transaction Controller', function () {
metamaskNetworkId: currentNetworkId, metamaskNetworkId: currentNetworkId,
txParams, txParams,
} }
txController._saveTxList([txMeta]) txController.addTx(txMeta)
stub = sinon.stub(txController, 'addUnapprovedTransaction').returns(Promise.resolve(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 () { it('replaces the tx with the same id', function () {
txController.addTx({ id: '1', status: 'unapproved', metamaskNetworkId: currentNetworkId, txParams: {} }, noop) txController.addTx({ id: '1', status: 'unapproved', metamaskNetworkId: currentNetworkId, txParams: {} }, noop)
txController.addTx({ id: '2', status: 'confirmed', 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: {} }) const tx1 = txController.getTx('1')
var result = txController.getTx('1') tx1.status = 'blah'
assert.equal(result.hash, 'foo') 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 originalGasPrice = '0x01'
const desiredGasPrice = '0x02' const desiredGasPrice = '0x02'
@ -297,13 +303,22 @@ describe('Transaction Controller', function () {
}, },
} }
const updatedMeta = clone(txMeta)
txController.addTx(txMeta) txController.addTx(txMeta)
updatedMeta.txParams.gasPrice = desiredGasPrice const updatedTx = txController.getTx('1')
txController.updateTx(updatedMeta) // verify tx was initialized correctly
var result = txController.getTx('1') 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') 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)')
}) })
}) })

View File

@ -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')
})
})

View File

@ -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')
})
})
})
})

View File

@ -107,14 +107,23 @@ AccountDetailScreen.prototype.render = function () {
}, },
[ [
h( h(
'h2.font-medium.color-forest', 'div.font-medium.color-forest',
{ {
name: 'edit', name: 'edit',
style: { style: {
}, },
}, },
[ [
h('h2', {
style: {
maxWidth: '180px',
overflow: 'hidden',
textOverflow: 'ellipsis',
padding: '5px 0px',
},
}, [
identity && identity.name, identity && identity.name,
]),
] ]
), ),
h( h(

View File

@ -97,7 +97,6 @@ var actions = {
cancelMsg: cancelMsg, cancelMsg: cancelMsg,
signPersonalMsg, signPersonalMsg,
cancelPersonalMsg, cancelPersonalMsg,
sendTx: sendTx,
signTx: signTx, signTx: signTx,
updateAndApproveTx, updateAndApproveTx,
cancelTx: cancelTx, cancelTx: cancelTx,
@ -397,26 +396,13 @@ function signPersonalMsg (msgData) {
function signTx (txData) { function signTx (txData) {
return (dispatch) => { return (dispatch) => {
dispatch(actions.showLoadingIndication())
global.ethQuery.sendTransaction(txData, (err, data) => { global.ethQuery.sendTransaction(txData, (err, data) => {
dispatch(actions.hideLoadingIndication()) dispatch(actions.hideLoadingIndication())
if (err) return dispatch(actions.displayWarning(err.message)) if (err) dispatch(actions.displayWarning(err.message))
dispatch(actions.hideWarning()) dispatch(this.goHome())
})
dispatch(this.showConfTxPage())
}
}
function sendTx (txData) {
log.info(`actions - sendTx: ${JSON.stringify(txData.txParams)}`)
return (dispatch) => {
log.debug(`actions calling background.approveTransaction`)
background.approveTransaction(txData.id, (err) => {
if (err) {
dispatch(actions.txError(err))
return log.error(err.message)
}
dispatch(actions.completedTx(txData.id))
}) })
dispatch(actions.showConfTxPage())
} }
} }
@ -428,6 +414,7 @@ function updateAndApproveTx (txData) {
dispatch(actions.hideLoadingIndication()) dispatch(actions.hideLoadingIndication())
if (err) { if (err) {
dispatch(actions.txError(err)) dispatch(actions.txError(err))
dispatch(actions.goHome())
return log.error(err.message) return log.error(err.message)
} }
dispatch(actions.completedTx(txData.id)) dispatch(actions.completedTx(txData.id))

View File

@ -187,7 +187,7 @@ App.prototype.renderAppBar = function () {
style: {}, style: {},
enableAccountsSelector: true, enableAccountsSelector: true,
identities: this.props.identities, identities: this.props.identities,
selected: this.props.selected, selected: this.props.currentView.context,
network: this.props.network, network: this.props.network,
keyrings: this.props.keyrings, keyrings: this.props.keyrings,
}, []), }, []),

View File

@ -59,7 +59,16 @@ class AccountDropdowns extends Component {
}, },
), ),
this.indicateIfLoose(keyring.type), this.indicateIfLoose(keyring.type),
h('span', { style: { marginLeft: '20px', fontSize: '24px' } }, identity.name || ''), 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), h('span', { style: { marginLeft: '20px', fontSize: '24px' } }, isSelected ? h('.check', '✓') : null),
] ]
) )

View File

@ -38,7 +38,7 @@ PendingMsgDetails.prototype.render = function () {
// message data // message data
h('.tx-data.flex-column.flex-justify-center.flex-grow.select-none', [ 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('label.font-small', 'MESSAGE'),
h('span.font-small', msgParams.data), h('span.font-small', msgParams.data),
]), ]),

View File

@ -18,6 +18,9 @@ PendingMsg.prototype.render = function () {
h('div', { h('div', {
key: msgData.id, key: msgData.id,
style: {
maxWidth: '350px',
},
}, [ }, [
// header // header
@ -35,7 +38,7 @@ PendingMsg.prototype.render = function () {
}, `Signing this message can have }, `Signing this message can have
dangerous side effects. Only sign messages from dangerous side effects. Only sign messages from
sites you fully trust with your entire account. 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.`),
// message details // message details
h(PendingTxDetails, state), h(PendingTxDetails, state),

View File

@ -3,17 +3,6 @@ const h = require('react-hyperscript')
const inherits = require('util').inherits const inherits = require('util').inherits
const TokenTracker = require('eth-token-tracker') const TokenTracker = require('eth-token-tracker')
const TokenCell = require('./token-cell.js') const TokenCell = require('./token-cell.js')
const normalizeAddress = require('eth-sig-util').normalize
const defaultTokens = []
const contracts = require('eth-contract-metadata')
for (const address in contracts) {
const contract = contracts[address]
if (contract.erc20) {
contract.address = address
defaultTokens.push(contract)
}
}
module.exports = TokenList module.exports = TokenList
@ -38,7 +27,24 @@ TokenList.prototype.render = function () {
if (error) { if (error) {
log.error(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'),
])
} }
const tokenViews = tokens.map((tokenData) => { const tokenViews = tokens.map((tokenData) => {
@ -89,7 +95,7 @@ TokenList.prototype.renderTokenStatusBar = function () {
let msg let msg
if (tokens.length === 1) { if (tokens.length === 1) {
msg = `You own 1 token` msg = `You own 1 token`
} else if (tokens.length === 1) { } else if (tokens.length > 1) {
msg = `You own ${tokens.length} tokens` msg = `You own ${tokens.length} tokens`
} else { } else {
msg = `No tokens found` msg = `No tokens found`
@ -153,7 +159,7 @@ TokenList.prototype.createFreshTokenTracker = function () {
this.tracker = new TokenTracker({ this.tracker = new TokenTracker({
userAddress, userAddress,
provider: global.ethereumProvider, provider: global.ethereumProvider,
tokens: uniqueMergeTokens(defaultTokens, this.props.tokens), tokens: this.props.tokens,
pollingInterval: 8000, pollingInterval: 8000,
}) })
@ -199,16 +205,3 @@ TokenList.prototype.componentWillUnmount = function () {
this.tracker.stop() this.tracker.stop()
} }
function uniqueMergeTokens (tokensA, tokensB) {
const uniqueAddresses = []
const result = []
tokensA.concat(tokensB).forEach((token) => {
const normal = normalizeAddress(token.address)
if (!uniqueAddresses.includes(normal)) {
uniqueAddresses.push(normal)
result.push(token)
}
})
return result
}

View File

@ -60,17 +60,8 @@ TransactionListItem.prototype.render = function () {
}, [ }, [
h('.identicon-wrapper.flex-column.flex-center.select-none', [ 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, { h(Tooltip, {
title: 'Transaction Number', title: 'Transaction Number',

View File

@ -103,7 +103,7 @@ InfoScreen.prototype.render = function () {
[ [
h('div.fa.fa-support', [ h('div.fa.fa-support', [
h('a.info', { h('a.info', {
href: 'http://metamask.consensyssupport.happyfox.com', href: 'https://support.metamask.com',
target: '_blank', target: '_blank',
}, 'Visit our Support Center'), }, 'Visit our Support Center'),
]), ]),

View File

@ -42,7 +42,10 @@ function rootReducer (state, action) {
} }
window.logState = function () { 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 return stateString
} }

View File

@ -3,19 +3,19 @@ module.exports = function (address, network) {
let link let link
switch (net) { switch (net) {
case 1: // main net case 1: // main net
link = `http://etherscan.io/address/${address}` link = `https://etherscan.io/address/${address}`
break break
case 2: // morden test net case 2: // morden test net
link = `http://morden.etherscan.io/address/${address}` link = `https://morden.etherscan.io/address/${address}`
break break
case 3: // ropsten test net case 3: // ropsten test net
link = `http://ropsten.etherscan.io/address/${address}` link = `https://ropsten.etherscan.io/address/${address}`
break break
case 4: // rinkeby test net case 4: // rinkeby test net
link = `http://rinkeby.etherscan.io/address/${address}` link = `https://rinkeby.etherscan.io/address/${address}`
break break
case 42: // kovan test net case 42: // kovan test net
link = `http://kovan.etherscan.io/address/${address}` link = `https://kovan.etherscan.io/address/${address}`
break break
default: default:
link = '' link = ''