aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--CHANGELOG.md27
-rw-r--r--app/manifest.json5
-rw-r--r--app/scripts/background.js2
-rw-r--r--app/scripts/config.js2
-rw-r--r--app/scripts/contentscript.js11
-rw-r--r--app/scripts/controllers/balance.js15
-rw-r--r--app/scripts/controllers/currency.js6
-rw-r--r--app/scripts/controllers/network.js110
-rw-r--r--app/scripts/controllers/preferences.js2
-rw-r--r--app/scripts/controllers/transactions.js380
-rw-r--r--app/scripts/keyring-controller.js596
-rw-r--r--app/scripts/lib/account-tracker.js6
-rw-r--r--app/scripts/lib/events-proxy.js31
-rw-r--r--app/scripts/lib/pending-tx-tracker.js41
-rw-r--r--app/scripts/lib/tx-gas-utils.js (renamed from app/scripts/lib/tx-utils.js)22
-rw-r--r--app/scripts/lib/tx-state-history-helper.js10
-rw-r--r--app/scripts/lib/tx-state-manager.js245
-rw-r--r--app/scripts/metamask-controller.js50
-rw-r--r--app/scripts/migrations/_multi-keyring.js2
-rw-r--r--app/scripts/platforms/extension.js1
-rw-r--r--development/index.html84
-rw-r--r--mascara/example/app.js67
-rw-r--r--mascara/example/app/index.html6
-rw-r--r--mascara/src/background.js3
-rw-r--r--mascara/src/lib/setup-iframe.js19
-rw-r--r--mascara/src/lib/setup-provider.js22
-rw-r--r--mascara/src/mascara.js48
-rw-r--r--mock-dev.js1
-rw-r--r--package.json15
-rw-r--r--test/unit/components/pending-tx-test.js3
-rw-r--r--test/unit/currency-controller-test.js20
-rw-r--r--test/unit/keyring-controller-test.js164
-rw-r--r--test/unit/metamask-controller-test.js1
-rw-r--r--test/unit/network-contoller-test.js7
-rw-r--r--test/unit/pending-tx-test.js49
-rw-r--r--test/unit/tx-controller-test.js411
-rw-r--r--test/unit/tx-state-history-helper.js23
-rw-r--r--test/unit/tx-state-manager-test.js241
-rw-r--r--test/unit/tx-utils-test.js6
-rw-r--r--ui-dev.js51
-rw-r--r--ui/app/actions.js33
-rw-r--r--ui/app/app.js2
-rw-r--r--ui/app/components/fiat-value.js3
-rw-r--r--ui/app/config.js8
-rw-r--r--ui/app/conversion.json207
-rw-r--r--ui/app/infura-conversion.json653
46 files changed, 1786 insertions, 1925 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index c18104f92..069602915 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -3,7 +3,30 @@
## Current Master
- Remove Slack link from info page, since it is a big phishing target.
+
+## 3.10.8 2017-9-28
+
+- Fixed usage of new currency fetching API.
+
+## 3.10.7 2017-9-28
+
+- Fixed bug where sometimes the current account was not correctly set and exposed to web apps.
+- Added AUD, HKD, SGD, IDR, PHP to currency conversion list
+
+## 3.10.6 2017-9-27
+
+- Fix bug where newly created accounts were not selected.
+- Fix bug where selected account was not persisted between lockings.
+
+## 3.10.5 2017-9-27
+
+- Fix block gas limit estimation.
+
+## 3.10.4 2017-9-27
+
- Fix bug that could mis-render token balances when very small. (Not actually included in 3.9.9)
+- Fix memory leak warning.
+- Fix bug where new event filters would not include historical events.
## 3.10.3 2017-9-21
@@ -23,7 +46,8 @@ rollback to 3.10.0 due to bug
- Fixed a long standing memory leak associated with filters installed by dapps
- Fix link to support center.
- Fixed tooltip icon locations to avoid overflow.
-- Warn users when a dapp proposes a high gas limit (90% of blockGasLimit or higher)
+- Warn users when a dapp proposes a high gas limit (90% of blockGasLimit or higher
+- Sort currencies by currency name (thanks to strelok1: https://github.com/strelok1).
## 3.10.0 2017-9-11
@@ -33,6 +57,7 @@ rollback to 3.10.0 due to bug
- 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.
diff --git a/app/manifest.json b/app/manifest.json
index fd07f15a9..0fc43c7d4 100644
--- a/app/manifest.json
+++ b/app/manifest.json
@@ -1,7 +1,7 @@
{
"name": "MetaMask",
"short_name": "Metamask",
- "version": "3.10.3",
+ "version": "3.10.8",
"manifest_version": 2,
"author": "https://metamask.io",
"description": "Ethereum Browser Extension",
@@ -57,7 +57,8 @@
"permissions": [
"storage",
"clipboardWrite",
- "http://localhost:8545/"
+ "http://localhost:8545/",
+ "https://*.infura.io/"
],
"web_accessible_resources": [
"scripts/inpage.js"
diff --git a/app/scripts/background.js b/app/scripts/background.js
index 1b96d68b5..195881e15 100644
--- a/app/scripts/background.js
+++ b/app/scripts/background.js
@@ -114,7 +114,7 @@ function setupController (initState) {
//
updateBadge()
- controller.txController.on('updateBadge', updateBadge)
+ controller.txController.on('update:badge', updateBadge)
controller.messageManager.on('updateBadge', updateBadge)
controller.personalMessageManager.on('updateBadge', updateBadge)
diff --git a/app/scripts/config.js b/app/scripts/config.js
index c5f260583..1d4ff7c0d 100644
--- a/app/scripts/config.js
+++ b/app/scripts/config.js
@@ -2,11 +2,13 @@ const MAINET_RPC_URL = 'https://mainnet.infura.io/metamask'
const ROPSTEN_RPC_URL = 'https://ropsten.infura.io/metamask'
const KOVAN_RPC_URL = 'https://kovan.infura.io/metamask'
const RINKEBY_RPC_URL = 'https://rinkeby.infura.io/metamask'
+const LOCALHOST_RPC_URL = 'http://localhost:8545'
global.METAMASK_DEBUG = 'GULP_METAMASK_DEBUG'
module.exports = {
network: {
+ localhost: LOCALHOST_RPC_URL,
mainnet: MAINET_RPC_URL,
ropsten: ROPSTEN_RPC_URL,
kovan: KOVAN_RPC_URL,
diff --git a/app/scripts/contentscript.js b/app/scripts/contentscript.js
index 90a0f1f22..b4708189e 100644
--- a/app/scripts/contentscript.js
+++ b/app/scripts/contentscript.js
@@ -42,16 +42,21 @@ function setupStreams () {
name: 'contentscript',
target: 'inpage',
})
- pageStream.on('error', console.error)
const pluginPort = extension.runtime.connect({ name: 'contentscript' })
const pluginStream = new PortStream(pluginPort)
- pluginStream.on('error', console.error)
// forward communication plugin->inpage
- pageStream.pipe(pluginStream).pipe(pageStream)
+ pump(
+ pageStream,
+ pluginStream,
+ pageStream,
+ (err) => logStreamDisconnectWarning('MetaMask Contentscript Forwarding', err)
+ )
// setup local multistream channels
const mux = new ObjectMultiplex()
+ mux.setMaxListeners(25)
+
pump(
mux,
pageStream,
diff --git a/app/scripts/controllers/balance.js b/app/scripts/controllers/balance.js
index 964dff0df..4fa4c78fe 100644
--- a/app/scripts/controllers/balance.js
+++ b/app/scripts/controllers/balance.js
@@ -33,9 +33,18 @@ class BalanceController {
_registerUpdates () {
const update = this.updateBalance.bind(this)
- this.txController.on('submitted', update)
- this.txController.on('confirmed', update)
- this.txController.on('failed', update)
+
+ this.txController.on('tx:status-update', (txId, status) => {
+ switch (status) {
+ case 'submitted':
+ case 'confirmed':
+ case 'failed':
+ update()
+ return
+ default:
+ return
+ }
+ })
this.accountTracker.store.subscribe(update)
this.blockTracker.on('block', update)
}
diff --git a/app/scripts/controllers/currency.js b/app/scripts/controllers/currency.js
index 1f20dc005..25a7a942e 100644
--- a/app/scripts/controllers/currency.js
+++ b/app/scripts/controllers/currency.js
@@ -8,7 +8,7 @@ class CurrencyController {
constructor (opts = {}) {
const initState = extend({
- currentCurrency: 'USD',
+ currentCurrency: 'usd',
conversionRate: 0,
conversionDate: 'N/A',
}, opts.initState)
@@ -45,10 +45,10 @@ class CurrencyController {
updateConversionRate () {
const currentCurrency = this.getCurrentCurrency()
- return fetch(`https://api.cryptonator.com/api/ticker/eth-${currentCurrency}`)
+ return fetch(`https://api.infura.io/v1/ticker/eth${currentCurrency.toLowerCase()}`)
.then(response => response.json())
.then((parsedResponse) => {
- this.setConversionRate(Number(parsedResponse.ticker.price))
+ this.setConversionRate(Number(parsedResponse.bid))
this.setConversionDate(Number(parsedResponse.timestamp))
}).catch((err) => {
if (err) {
diff --git a/app/scripts/controllers/network.js b/app/scripts/controllers/network.js
index 0a3e5e26b..0f9db4d53 100644
--- a/app/scripts/controllers/network.js
+++ b/app/scripts/controllers/network.js
@@ -1,71 +1,40 @@
+const assert = require('assert')
const EventEmitter = require('events')
-const MetaMaskProvider = require('web3-provider-engine/zero.js')
+const createMetamaskProvider = require('web3-provider-engine/zero.js')
const ObservableStore = require('obs-store')
const ComposedStore = require('obs-store/lib/composed')
const extend = require('xtend')
const EthQuery = require('eth-query')
+const createEventEmitterProxy = require('../lib/events-proxy.js')
const RPC_ADDRESS_LIST = require('../config.js').network
const DEFAULT_RPC = RPC_ADDRESS_LIST['rinkeby']
module.exports = class NetworkController extends EventEmitter {
+
constructor (config) {
super()
- this.networkStore = new ObservableStore('loading')
config.provider.rpcTarget = this.getRpcAddressForType(config.provider.type, config.provider)
+ this.networkStore = new ObservableStore('loading')
this.providerStore = new ObservableStore(config.provider)
this.store = new ComposedStore({ provider: this.providerStore, network: this.networkStore })
- this._providerListeners = {}
+ this._proxy = createEventEmitterProxy()
this.on('networkDidChange', this.lookupNetwork)
- this.providerStore.subscribe((state) => this.switchNetwork({rpcUrl: state.rpcTarget}))
- }
-
- get provider () {
- return this._proxy
- }
-
- set provider (provider) {
- this._provider = provider
}
- initializeProvider (opts, providerContructor = MetaMaskProvider) {
- this.providerInit = opts
- this._provider = providerContructor(opts)
- this._proxy = new Proxy(this._provider, {
- get: (obj, name) => {
- if (name === 'on') return this._on.bind(this)
- return this._provider[name]
- },
- set: (obj, name, value) => {
- this._provider[name] = value
- return value
- },
- })
- this.provider.on('block', this._logBlock.bind(this))
- this.provider.on('error', this.verifyNetwork.bind(this))
- this.ethQuery = new EthQuery(this.provider)
+ initializeProvider (_providerParams) {
+ this._baseProviderParams = _providerParams
+ const rpcUrl = this.getCurrentRpcAddress()
+ this._configureStandardProvider({ rpcUrl })
+ this._proxy.on('block', this._logBlock.bind(this))
+ this._proxy.on('error', this.verifyNetwork.bind(this))
+ this.ethQuery = new EthQuery(this._proxy)
this.lookupNetwork()
- return this.provider
- }
-
- switchNetwork (providerInit) {
- this.setNetworkState('loading')
- const newInit = extend(this.providerInit, providerInit)
- this.providerInit = newInit
-
- this._provider.removeAllListeners()
- this._provider.stop()
- this.provider = MetaMaskProvider(newInit)
- // apply the listners created by other controllers
- Object.keys(this._providerListeners).forEach((key) => {
- this._providerListeners[key].forEach((handler) => this._provider.addListener(key, handler))
- })
- this.emit('networkDidChange')
+ return this._proxy
}
-
verifyNetwork () {
- // Check network when restoring connectivity:
+ // Check network when restoring connectivity:
if (this.isNetworkLoading()) this.lookupNetwork()
}
@@ -94,6 +63,7 @@ module.exports = class NetworkController extends EventEmitter {
type: 'rpc',
rpcTarget: rpcUrl,
})
+ this._switchNetwork({ rpcUrl })
}
getCurrentRpcAddress () {
@@ -102,10 +72,14 @@ module.exports = class NetworkController extends EventEmitter {
return this.getRpcAddressForType(provider.type)
}
- setProviderType (type) {
+ async setProviderType (type) {
+ assert(type !== 'rpc', `NetworkController.setProviderType - cannot connect by type "rpc"`)
+ // skip if type already matches
if (type === this.getProviderConfig().type) return
const rpcTarget = this.getRpcAddressForType(type)
- this.providerStore.updateState({type, rpcTarget})
+ assert(rpcTarget, `NetworkController - unknown rpc address for type "${type}"`)
+ this.providerStore.updateState({ type, rpcTarget })
+ this._switchNetwork({ rpcUrl: rpcTarget })
}
getProviderConfig () {
@@ -117,14 +91,42 @@ module.exports = class NetworkController extends EventEmitter {
return provider && provider.rpcTarget ? provider.rpcTarget : DEFAULT_RPC
}
+ //
+ // Private
+ //
+
+ _switchNetwork (providerParams) {
+ this.setNetworkState('loading')
+ this._configureStandardProvider(providerParams)
+ this.emit('networkDidChange')
+ }
+
+ _configureStandardProvider(_providerParams) {
+ const providerParams = extend(this._baseProviderParams, _providerParams)
+ const provider = createMetamaskProvider(providerParams)
+ this._setProvider(provider)
+ }
+
+ _setProvider (provider) {
+ // collect old block tracker events
+ const oldProvider = this._provider
+ let blockTrackerHandlers
+ if (oldProvider) {
+ // capture old block handlers
+ blockTrackerHandlers = oldProvider._blockTracker.proxyEventHandlers
+ // tear down
+ oldProvider.removeAllListeners()
+ oldProvider.stop()
+ }
+ // override block tracler
+ provider._blockTracker = createEventEmitterProxy(provider._blockTracker, blockTrackerHandlers)
+ // set as new provider
+ this._provider = provider
+ this._proxy.setTarget(provider)
+ }
+
_logBlock (block) {
log.info(`BLOCK CHANGED: #${block.number.toString('hex')} 0x${block.hash.toString('hex')}`)
this.verifyNetwork()
}
-
- _on (event, handler) {
- if (!this._providerListeners[event]) this._providerListeners[event] = []
- this._providerListeners[event].push(handler)
- this._provider.on(event, handler)
- }
}
diff --git a/app/scripts/controllers/preferences.js b/app/scripts/controllers/preferences.js
index e45224593..bc4848421 100644
--- a/app/scripts/controllers/preferences.js
+++ b/app/scripts/controllers/preferences.js
@@ -22,7 +22,7 @@ class PreferencesController {
})
}
- getSelectedAddress (_address) {
+ getSelectedAddress () {
return this.store.getState().selectedAddress
}
diff --git a/app/scripts/controllers/transactions.js b/app/scripts/controllers/transactions.js
index 2aff4e5ff..94e04c429 100644
--- a/app/scripts/controllers/transactions.js
+++ b/app/scripts/controllers/transactions.js
@@ -1,86 +1,90 @@
const EventEmitter = require('events')
-const extend = require('xtend')
const ObservableStore = require('obs-store')
const ethUtil = require('ethereumjs-util')
+const Transaction = require('ethereumjs-tx')
const EthQuery = require('ethjs-query')
-const TxProviderUtil = require('../lib/tx-utils')
+const TransactionStateManger = require('../lib/tx-state-manager')
+const TxGasUtil = require('../lib/tx-gas-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')
+
+/*
+ Transaction Controller is an aggregate of sub-controllers and trackers
+ composing them in a way to be exposed to the metamask controller
+ - txStateManager
+ responsible for the state of a transaction and
+ storing the transaction
+ - pendingTxTracker
+ watching blocks for transactions to be include
+ and emitting confirmed events
+ - txGasUtil
+ gas calculations and safety buffering
+ - nonceTracker
+ calculating nonces
+*/
module.exports = class TransactionController extends EventEmitter {
constructor (opts) {
super()
- this.store = new ObservableStore(extend({
- transactions: [],
- }, opts.initState))
- this.memStore = new ObservableStore({})
this.networkStore = opts.networkStore || new ObservableStore({})
this.preferencesStore = opts.preferencesStore || new ObservableStore({})
- this.txHistoryLimit = opts.txHistoryLimit
this.provider = opts.provider
this.blockTracker = opts.blockTracker
this.signEthTx = opts.signTransaction
- this.accountTracker = opts.accountTracker
+ this.memStore = new ObservableStore({})
+ this.query = new EthQuery(this.provider)
+ this.txGasUtil = new TxGasUtil(this.provider)
+
+ this.txStateManager = new TransactionStateManger({
+ initState: opts.initState,
+ txHistoryLimit: opts.txHistoryLimit,
+ getNetwork: this.getNetwork.bind(this),
+ })
+ this.store = this.txStateManager.store
+ this.txStateManager.on('tx:status-update', this.emit.bind(this, 'tx:status-update'))
this.nonceTracker = new NonceTracker({
provider: this.provider,
- getPendingTransactions: (address) => {
- return this.getFilteredTxList({
- from: address,
- status: 'submitted',
- err: undefined,
- })
- },
+ getPendingTransactions: this.txStateManager.getPendingTransactions.bind(this.txStateManager),
getConfirmedTransactions: (address) => {
- return this.getFilteredTxList({
+ return this.txStateManager.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)
this.pendingTxTracker = new PendingTransactionTracker({
provider: this.provider,
nonceTracker: this.nonceTracker,
- getBalance: (address) => {
- const account = this.accountTracker.getState().accounts[address]
- if (!account) return
- return account.balance
- },
- publishTransaction: this.txProviderUtil.publishTransaction.bind(this.txProviderUtil),
- getPendingTransactions: () => {
- const network = this.getNetwork()
- return this.getFilteredTxList({
- status: 'submitted',
- metamaskNetworkId: network,
- })
- },
+ retryLimit: 3500, // Retry 3500 blocks, or about 1 day.
+ publishTransaction: (rawTx) => this.query.sendRawTransaction(rawTx),
+ getPendingTransactions: this.txStateManager.getPendingTransactions.bind(this.txStateManager),
})
- this.pendingTxTracker.on('txWarning', this.updateTx.bind(this))
- this.pendingTxTracker.on('txFailed', this.setTxStatusFailed.bind(this))
- this.pendingTxTracker.on('txConfirmed', this.setTxStatusConfirmed.bind(this))
+ this.txStateManager.store.subscribe(() => this.emit('update:badge'))
+
+ this.pendingTxTracker.on('tx:warning', (txMeta) => {
+ this.txStateManager.updateTx(txMeta, 'transactions/pending-tx-tracker#event: tx:warning')
+ })
+ this.pendingTxTracker.on('tx:failed', this.txStateManager.setTxStatusFailed.bind(this.txStateManager))
+ this.pendingTxTracker.on('tx:confirmed', this.txStateManager.setTxStatusConfirmed.bind(this.txStateManager))
+ this.pendingTxTracker.on('tx:retry', (txMeta) => {
+ if (!('retryCount' in txMeta)) txMeta.retryCount = 0
+ txMeta.retryCount++
+ this.txStateManager.updateTx(txMeta, 'transactions/pending-tx-tracker#event: tx:retry')
+ })
- this.blockTracker.on('rawBlock', this.pendingTxTracker.checkForTxInBlock.bind(this.pendingTxTracker))
+ this.blockTracker.on('block', this.pendingTxTracker.checkForTxInBlock.bind(this.pendingTxTracker))
// this is a little messy but until ethstore has been either
// removed or redone this is to guard against the race condition
- // where accountTracker hasent been populated by the results yet
- this.blockTracker.once('latest', () => {
- this.blockTracker.on('latest', this.pendingTxTracker.resubmitPendingTxs.bind(this.pendingTxTracker))
- })
+ this.blockTracker.on('latest', this.pendingTxTracker.resubmitPendingTxs.bind(this.pendingTxTracker))
this.blockTracker.on('sync', this.pendingTxTracker.queryPendingTxs.bind(this.pendingTxTracker))
// memstore is computed from a few different stores
this._updateMemstore()
- this.store.subscribe(() => this._updateMemstore())
+ this.txStateManager.store.subscribe(() => this._updateMemstore())
this.networkStore.subscribe(() => this._updateMemstore())
this.preferencesStore.subscribe(() => this._updateMemstore())
}
@@ -97,98 +101,31 @@ module.exports = class TransactionController extends EventEmitter {
return this.preferencesStore.getState().selectedAddress
}
- // Returns the number of txs for the current network.
- getTxCount () {
- return this.getTxList().length
- }
-
- // Returns the full tx list across all networks
- getFullTxList () {
- return this.store.getState().transactions
- }
-
getUnapprovedTxCount () {
- return Object.keys(this.getUnapprovedTxList()).length
+ return Object.keys(this.txStateManager.getUnapprovedTxList()).length
}
- getPendingTxCount () {
- return this.getTxsByMetaData('status', 'signed').length
+ getPendingTxCount (account) {
+ return this.txStateManager.getPendingTransactions(account).length
}
- // Returns the tx list
- getTxList () {
- const network = this.getNetwork()
- const fullTxList = this.getFullTxList()
- return this.getTxsByMetaData('metamaskNetworkId', network, fullTxList)
- }
-
- // gets tx by Id and returns it
- getTx (txId) {
- const txList = this.getTxList()
- const txMeta = txList.find(txData => txData.id === txId)
- return txMeta
- }
- getUnapprovedTxList () {
- const txList = this.getTxList()
- return txList.filter((txMeta) => txMeta.status === 'unapproved')
- .reduce((result, tx) => {
- result[tx.id] = tx
- return result
- }, {})
+ getFilteredTxList (opts) {
+ return this.txStateManager.getFilteredTxList(opts)
}
- updateTx (txMeta) {
- // create txMeta snapshot for history
- 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)
- txList[index] = txMeta
- this._saveTxList(txList)
- this.emit('update')
+ getChainId () {
+ const networkState = this.networkStore.getState()
+ const getChainId = parseInt(networkState)
+ if (Number.isNaN(getChainId)) {
+ return 0
+ } else {
+ return getChainId
+ }
}
// Adds a tx to the txlist
addTx (txMeta) {
- // 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)
- }
- fullTxList.push(txMeta)
- this._saveTxList(fullTxList)
- this.emit('update')
-
- this.once(`${txMeta.id}:signed`, function (txId) {
- this.removeAllListeners(`${txMeta.id}:rejected`)
- })
- this.once(`${txMeta.id}:rejected`, function (txId) {
- this.removeAllListeners(`${txMeta.id}:signed`)
- })
-
- this.emit('updateBadge')
+ this.txStateManager.addTx(txMeta)
this.emit(`${txMeta.id}:unapproved`, txMeta)
}
@@ -198,7 +135,7 @@ module.exports = class TransactionController extends EventEmitter {
this.emit('newUnaprovedTx', txMeta)
// listen for tx completion (success, fail)
return new Promise((resolve, reject) => {
- this.once(`${txMeta.id}:finished`, (completedTx) => {
+ this.txStateManager.once(`${txMeta.id}:finished`, (completedTx) => {
switch (completedTx.status) {
case 'submitted':
return resolve(completedTx.hash)
@@ -213,7 +150,7 @@ module.exports = class TransactionController extends EventEmitter {
async addUnapprovedTransaction (txParams) {
// validate
- await this.txProviderUtil.validateTxParams(txParams)
+ await this.txGasUtil.validateTxParams(txParams)
// construct txMeta
const txMeta = {
id: createId(),
@@ -232,17 +169,15 @@ module.exports = class TransactionController extends EventEmitter {
async addTxDefaults (txMeta) {
const txParams = txMeta.txParams
// ensure value
+ const gasPrice = txParams.gasPrice || await this.query.gasPrice()
+ txParams.gasPrice = ethUtil.addHexPrefix(gasPrice.toString(16))
txParams.value = txParams.value || '0x0'
- if (!txParams.gasPrice) {
- const gasPrice = await this.query.gasPrice()
- txParams.gasPrice = gasPrice
- }
// set gasLimit
- return await this.txProviderUtil.analyzeGasUsage(txMeta)
+ return await this.txGasUtil.analyzeGasUsage(txMeta)
}
async updateAndApproveTransaction (txMeta) {
- this.updateTx(txMeta)
+ this.txStateManager.updateTx(txMeta, 'confTx: user approved transaction')
await this.approveTransaction(txMeta.id)
}
@@ -250,24 +185,24 @@ module.exports = class TransactionController extends EventEmitter {
let nonceLock
try {
// approve
- this.setTxStatusApproved(txId)
+ this.txStateManager.setTxStatusApproved(txId)
// get next nonce
- const txMeta = this.getTx(txId)
+ const txMeta = this.txStateManager.getTx(txId)
const fromAddress = txMeta.txParams.from
// wait for a nonce
nonceLock = await this.nonceTracker.getNonceLock(fromAddress)
// add nonce to txParams
- txMeta.txParams.nonce = nonceLock.nextNonce
+ txMeta.txParams.nonce = ethUtil.addHexPrefix(nonceLock.nextNonce.toString(16))
// add nonce debugging information to txMeta
txMeta.nonceDetails = nonceLock.nonceDetails
- this.updateTx(txMeta)
+ this.txStateManager.updateTx(txMeta, 'transactions#approveTransaction')
// sign transaction
const rawTx = await this.signTransaction(txId)
await this.publishTransaction(txId, rawTx)
// must set transaction to submitted/failed before releasing lock
nonceLock.releaseLock()
} catch (err) {
- this.setTxStatusFailed(txId, err)
+ this.txStateManager.setTxStatusFailed(txId, err)
// must set transaction to submitted/failed before releasing lock
if (nonceLock) nonceLock.releaseLock()
// continue with error chain
@@ -276,181 +211,46 @@ module.exports = class TransactionController extends EventEmitter {
}
async signTransaction (txId) {
- const txMeta = this.getTx(txId)
+ const txMeta = this.txStateManager.getTx(txId)
const txParams = txMeta.txParams
const fromAddress = txParams.from
// add network/chain id
- txParams.chainId = this.getChainId()
- const ethTx = this.txProviderUtil.buildEthTxFromParams(txParams)
+ txParams.chainId = ethUtil.addHexPrefix(this.getChainId().toString(16))
+ const ethTx = new Transaction(txParams)
await this.signEthTx(ethTx, fromAddress)
- this.setTxStatusSigned(txMeta.id)
+ this.txStateManager.setTxStatusSigned(txMeta.id)
const rawTx = ethUtil.bufferToHex(ethTx.serialize())
return rawTx
}
async publishTransaction (txId, rawTx) {
- const txMeta = this.getTx(txId)
+ const txMeta = this.txStateManager.getTx(txId)
txMeta.rawTx = rawTx
- this.updateTx(txMeta)
- const txHash = await this.txProviderUtil.publishTransaction(rawTx)
+ this.txStateManager.updateTx(txMeta, 'transactions#publishTransaction')
+ const txHash = await this.query.sendRawTransaction(rawTx)
this.setTxHash(txId, txHash)
- this.setTxStatusSubmitted(txId)
+ this.txStateManager.setTxStatusSubmitted(txId)
}
async cancelTransaction (txId) {
- this.setTxStatusRejected(txId)
- }
-
-
- getChainId () {
- const networkState = this.networkStore.getState()
- const getChainId = parseInt(networkState)
- if (Number.isNaN(getChainId)) {
- return 0
- } else {
- return getChainId
- }
+ this.txStateManager.setTxStatusRejected(txId)
}
// receives a txHash records the tx as signed
setTxHash (txId, txHash) {
// Add the tx hash to the persisted meta-tx object
- const txMeta = this.getTx(txId)
+ const txMeta = this.txStateManager.getTx(txId)
txMeta.hash = txHash
- this.updateTx(txMeta)
+ this.txStateManager.updateTx(txMeta, 'transactions#setTxHash')
}
- /*
- Takes an object of fields to search for eg:
- let thingsToLookFor = {
- to: '0x0..',
- from: '0x0..',
- status: 'signed',
- err: undefined,
- }
- and returns a list of tx with all
- options matching
-
- ****************HINT****************
- | `err: undefined` is like looking |
- | for a tx with no err |
- | so you can also search txs that |
- | dont have something as well by |
- | setting the value as undefined |
- ************************************
-
- this is for things like filtering a the tx list
- for only tx's from 1 account
- or for filltering for all txs from one account
- and that have been 'confirmed'
- */
- getFilteredTxList (opts) {
- let filteredTxList
- Object.keys(opts).forEach((key) => {
- filteredTxList = this.getTxsByMetaData(key, opts[key], filteredTxList)
- })
- return filteredTxList
- }
-
- getTxsByMetaData (key, value, txList = this.getTxList()) {
- return txList.filter((txMeta) => {
- if (txMeta.txParams[key]) {
- return txMeta.txParams[key] === value
- } else {
- return txMeta[key] === value
- }
- })
- }
-
- // STATUS METHODS
- // get::set status
-
- // should return the status of the tx.
- getTxStatus (txId) {
- const txMeta = this.getTx(txId)
- return txMeta.status
- }
-
- // should update the status of the tx to 'rejected'.
- setTxStatusRejected (txId) {
- this._setTxStatus(txId, 'rejected')
- }
-
- // should update the status of the tx to 'approved'.
- setTxStatusApproved (txId) {
- this._setTxStatus(txId, 'approved')
- }
-
- // should update the status of the tx to 'signed'.
- setTxStatusSigned (txId) {
- this._setTxStatus(txId, 'signed')
- }
-
- // should update the status of the tx to 'submitted'.
- setTxStatusSubmitted (txId) {
- this._setTxStatus(txId, 'submitted')
- }
-
- // should update the status of the tx to 'confirmed'.
- setTxStatusConfirmed (txId) {
- this._setTxStatus(txId, 'confirmed')
- }
-
- setTxStatusFailed (txId, err) {
- const txMeta = this.getTx(txId)
- txMeta.err = {
- message: err.toString(),
- stack: err.stack,
- }
- this.updateTx(txMeta)
- this._setTxStatus(txId, 'failed')
- }
-
- // merges txParams obj onto txData.txParams
- // use extend to ensure that all fields are filled
- updateTxParams (txId, txParams) {
- const txMeta = this.getTx(txId)
- txMeta.txParams = extend(txMeta.txParams, txParams)
- this.updateTx(txMeta)
- }
-
-/* _____________________________________
-| |
-| PRIVATE METHODS |
-|______________________________________*/
-
-
- // Should find the tx in the tx list and
- // update it.
- // should set the status in txData
- // - `'unapproved'` the user has not responded
- // - `'rejected'` the user has responded no!
- // - `'approved'` the user has approved the tx
- // - `'signed'` the tx is signed
- // - `'submitted'` the tx is sent to a server
- // - `'confirmed'` the tx has been included in a block.
- // - `'failed'` the tx failed for some reason, included on tx data.
- _setTxStatus (txId, status) {
- const txMeta = this.getTx(txId)
- txMeta.status = status
- this.emit(`${txMeta.id}:${status}`, txId)
- this.emit(`${status}`, txId)
- if (status === 'submitted' || status === 'rejected') {
- this.emit(`${txMeta.id}:finished`, txMeta)
- }
- this.updateTx(txMeta)
- this.emit('updateBadge')
- }
-
- // Saves the new/updated txList.
- // Function is intended only for internal use
- _saveTxList (transactions) {
- this.store.updateState({ transactions })
- }
+//
+// PRIVATE METHODS
+//
_updateMemstore () {
- const unapprovedTxs = this.getUnapprovedTxList()
- const selectedAddressTxList = this.getFilteredTxList({
+ const unapprovedTxs = this.txStateManager.getUnapprovedTxList()
+ const selectedAddressTxList = this.txStateManager.getFilteredTxList({
from: this.getSelectedAddress(),
metamaskNetworkId: this.getNetwork(),
})
diff --git a/app/scripts/keyring-controller.js b/app/scripts/keyring-controller.js
deleted file mode 100644
index 34e008ec4..000000000
--- a/app/scripts/keyring-controller.js
+++ /dev/null
@@ -1,596 +0,0 @@
-const ethUtil = require('ethereumjs-util')
-const BN = ethUtil.BN
-const bip39 = require('bip39')
-const EventEmitter = require('events').EventEmitter
-const ObservableStore = require('obs-store')
-const filter = require('promise-filter')
-const encryptor = require('browser-passworder')
-const sigUtil = require('eth-sig-util')
-const normalizeAddress = sigUtil.normalize
-// Keyrings:
-const SimpleKeyring = require('eth-simple-keyring')
-const HdKeyring = require('eth-hd-keyring')
-const keyringTypes = [
- SimpleKeyring,
- HdKeyring,
-]
-
-class KeyringController extends EventEmitter {
-
- // PUBLIC METHODS
- //
- // THE FIRST SECTION OF METHODS ARE PUBLIC-FACING,
- // MEANING THEY ARE USED BY CONSUMERS OF THIS CLASS.
- //
- // THEIR SURFACE AREA SHOULD BE CHANGED WITH GREAT CARE.
-
- constructor (opts) {
- super()
- const initState = opts.initState || {}
- this.keyringTypes = keyringTypes
- this.store = new ObservableStore(initState)
- this.memStore = new ObservableStore({
- isUnlocked: false,
- keyringTypes: this.keyringTypes.map(krt => krt.type),
- keyrings: [],
- identities: {},
- })
-
- this.accountTracker = opts.accountTracker
- this.encryptor = opts.encryptor || encryptor
- this.keyrings = []
- this.getNetwork = opts.getNetwork
- }
-
- // Full Update
- // returns Promise( @object state )
- //
- // Emits the `update` event and
- // returns a Promise that resolves to the current state.
- //
- // Frequently used to end asynchronous chains in this class,
- // indicating consumers can often either listen for updates,
- // or accept a state-resolving promise to consume their results.
- //
- // Not all methods end with this, that might be a nice refactor.
- fullUpdate () {
- this.emit('update')
- return Promise.resolve(this.memStore.getState())
- }
-
- // Create New Vault And Keychain
- // @string password - The password to encrypt the vault with
- //
- // returns Promise( @object state )
- //
- // Destroys any old encrypted storage,
- // creates a new encrypted store with the given password,
- // randomly creates a new HD wallet with 1 account,
- // faucets that account on the testnet.
- createNewVaultAndKeychain (password) {
- return this.persistAllKeyrings(password)
- .then(this.createFirstKeyTree.bind(this))
- .then(this.fullUpdate.bind(this))
- }
-
- // CreateNewVaultAndRestore
- // @string password - The password to encrypt the vault with
- // @string seed - The BIP44-compliant seed phrase.
- //
- // returns Promise( @object state )
- //
- // Destroys any old encrypted storage,
- // creates a new encrypted store with the given password,
- // creates a new HD wallet from the given seed with 1 account.
- createNewVaultAndRestore (password, seed) {
- if (typeof password !== 'string') {
- return Promise.reject('Password must be text.')
- }
-
- if (!bip39.validateMnemonic(seed)) {
- return Promise.reject(new Error('Seed phrase is invalid.'))
- }
-
- this.clearKeyrings()
-
- return this.persistAllKeyrings(password)
- .then(() => {
- return this.addNewKeyring('HD Key Tree', {
- mnemonic: seed,
- numberOfAccounts: 1,
- })
- })
- .then((firstKeyring) => {
- return firstKeyring.getAccounts()
- })
- .then((accounts) => {
- const firstAccount = accounts[0]
- if (!firstAccount) throw new Error('KeyringController - First Account not found.')
- const hexAccount = normalizeAddress(firstAccount)
- this.emit('newAccount', hexAccount)
- return this.setupAccounts(accounts)
- })
- .then(this.persistAllKeyrings.bind(this, password))
- .then(this.fullUpdate.bind(this))
- }
-
- // Set Locked
- // returns Promise( @object state )
- //
- // This method deallocates all secrets, and effectively locks metamask.
- setLocked () {
- // set locked
- this.password = null
- this.memStore.updateState({ isUnlocked: false })
- // remove keyrings
- this.keyrings = []
- this._updateMemStoreKeyrings()
- return this.fullUpdate()
- }
-
- // Submit Password
- // @string password
- //
- // returns Promise( @object state )
- //
- // Attempts to decrypt the current vault and load its keyrings
- // into memory.
- //
- // Temporarily also migrates any old-style vaults first, as well.
- // (Pre MetaMask 3.0.0)
- submitPassword (password) {
- return this.unlockKeyrings(password)
- .then((keyrings) => {
- this.keyrings = keyrings
- return this.fullUpdate()
- })
- }
-
- // Add New Keyring
- // @string type
- // @object opts
- //
- // returns Promise( @Keyring keyring )
- //
- // Adds a new Keyring of the given `type` to the vault
- // and the current decrypted Keyrings array.
- //
- // All Keyring classes implement a unique `type` string,
- // and this is used to retrieve them from the keyringTypes array.
- addNewKeyring (type, opts) {
- const Keyring = this.getKeyringClassForType(type)
- const keyring = new Keyring(opts)
- return keyring.deserialize(opts)
- .then(() => {
- return keyring.getAccounts()
- })
- .then((accounts) => {
- return this.checkForDuplicate(type, accounts)
- })
- .then((checkedAccounts) => {
- this.keyrings.push(keyring)
- return this.setupAccounts(checkedAccounts)
- })
- .then(() => this.persistAllKeyrings())
- .then(() => this._updateMemStoreKeyrings())
- .then(() => this.fullUpdate())
- .then(() => {
- return keyring
- })
- }
-
- // For now just checks for simple key pairs
- // but in the future
- // should possibly add HD and other types
- //
- checkForDuplicate (type, newAccount) {
- return this.getAccounts()
- .then((accounts) => {
- switch (type) {
- case 'Simple Key Pair':
- const isNotIncluded = !accounts.find((key) => key === newAccount[0] || key === ethUtil.stripHexPrefix(newAccount[0]))
- return (isNotIncluded) ? Promise.resolve(newAccount) : Promise.reject(new Error('The account you\'re are trying to import is a duplicate'))
- default:
- return Promise.resolve(newAccount)
- }
- })
- }
-
-
- // Add New Account
- // @number keyRingNum
- //
- // returns Promise( @object state )
- //
- // Calls the `addAccounts` method on the Keyring
- // in the kryings array at index `keyringNum`,
- // and then saves those changes.
- addNewAccount (selectedKeyring) {
- return selectedKeyring.addAccounts(1)
- .then(this.setupAccounts.bind(this))
- .then(this.persistAllKeyrings.bind(this))
- .then(this._updateMemStoreKeyrings.bind(this))
- .then(this.fullUpdate.bind(this))
- }
-
- // Save Account Label
- // @string account
- // @string label
- //
- // returns Promise( @string label )
- //
- // Persists a nickname equal to `label` for the specified account.
- saveAccountLabel (account, label) {
- try {
- const hexAddress = normalizeAddress(account)
- // update state on diskStore
- const state = this.store.getState()
- const walletNicknames = state.walletNicknames || {}
- walletNicknames[hexAddress] = label
- this.store.updateState({ walletNicknames })
- // update state on memStore
- const identities = this.memStore.getState().identities
- identities[hexAddress].name = label
- this.memStore.updateState({ identities })
- return Promise.resolve(label)
- } catch (err) {
- return Promise.reject(err)
- }
- }
-
- // Export Account
- // @string address
- //
- // returns Promise( @string privateKey )
- //
- // Requests the private key from the keyring controlling
- // the specified address.
- //
- // Returns a Promise that may resolve with the private key string.
- exportAccount (address) {
- try {
- return this.getKeyringForAccount(address)
- .then((keyring) => {
- return keyring.exportAccount(normalizeAddress(address))
- })
- } catch (e) {
- return Promise.reject(e)
- }
- }
-
-
- // SIGNING METHODS
- //
- // This method signs tx and returns a promise for
- // TX Manager to update the state after signing
-
- signTransaction (ethTx, _fromAddress) {
- const fromAddress = normalizeAddress(_fromAddress)
- return this.getKeyringForAccount(fromAddress)
- .then((keyring) => {
- return keyring.signTransaction(fromAddress, ethTx)
- })
- }
-
- // Sign Message
- // @object msgParams
- //
- // returns Promise(@buffer rawSig)
- //
- // Attempts to sign the provided @object msgParams.
- signMessage (msgParams) {
- const address = normalizeAddress(msgParams.from)
- return this.getKeyringForAccount(address)
- .then((keyring) => {
- return keyring.signMessage(address, msgParams.data)
- })
- }
-
- // Sign Personal Message
- // @object msgParams
- //
- // returns Promise(@buffer rawSig)
- //
- // Attempts to sign the provided @object msgParams.
- // Prefixes the hash before signing as per the new geth behavior.
- signPersonalMessage (msgParams) {
- const address = normalizeAddress(msgParams.from)
- return this.getKeyringForAccount(address)
- .then((keyring) => {
- return keyring.signPersonalMessage(address, msgParams.data)
- })
- }
-
- // PRIVATE METHODS
- //
- // THESE METHODS ARE ONLY USED INTERNALLY TO THE KEYRING-CONTROLLER
- // AND SO MAY BE CHANGED MORE LIBERALLY THAN THE ABOVE METHODS.
-
- // Create First Key Tree
- // returns @Promise
- //
- // Clears the vault,
- // creates a new one,
- // creates a random new HD Keyring with 1 account,
- // makes that account the selected account,
- // faucets that account on testnet,
- // puts the current seed words into the state tree.
- createFirstKeyTree () {
- this.clearKeyrings()
- return this.addNewKeyring('HD Key Tree', { numberOfAccounts: 1 })
- .then((keyring) => {
- return keyring.getAccounts()
- })
- .then((accounts) => {
- const firstAccount = accounts[0]
- if (!firstAccount) throw new Error('KeyringController - No account found on keychain.')
- const hexAccount = normalizeAddress(firstAccount)
- this.emit('newAccount', hexAccount)
- this.emit('newVault', hexAccount)
- return this.setupAccounts(accounts)
- })
- .then(this.persistAllKeyrings.bind(this))
- }
-
- // Setup Accounts
- // @array accounts
- //
- // returns @Promise(@object account)
- //
- // Initializes the provided account array
- // Gives them numerically incremented nicknames,
- // and adds them to the accountTracker for regular balance checking.
- setupAccounts (accounts) {
- return this.getAccounts()
- .then((loadedAccounts) => {
- const arr = accounts || loadedAccounts
- return Promise.all(arr.map((account) => {
- return this.getBalanceAndNickname(account)
- }))
- })
- }
-
- // Get Balance And Nickname
- // @string account
- //
- // returns Promise( @string label )
- //
- // Takes an account address and an iterator representing
- // the current number of named accounts.
- getBalanceAndNickname (account) {
- if (!account) {
- throw new Error('Problem loading account.')
- }
- const address = normalizeAddress(account)
- this.accountTracker.addAccount(address)
- return this.createNickname(address)
- }
-
- // Create Nickname
- // @string address
- //
- // returns Promise( @string label )
- //
- // Takes an address, and assigns it an incremented nickname, persisting it.
- createNickname (address) {
- const hexAddress = normalizeAddress(address)
- const identities = this.memStore.getState().identities
- const currentIdentityCount = Object.keys(identities).length + 1
- const nicknames = this.store.getState().walletNicknames || {}
- const existingNickname = nicknames[hexAddress]
- const name = existingNickname || `Account ${currentIdentityCount}`
- identities[hexAddress] = {
- address: hexAddress,
- name,
- }
- this.memStore.updateState({ identities })
- return this.saveAccountLabel(hexAddress, name)
- }
-
- // Persist All Keyrings
- // @password string
- //
- // returns Promise
- //
- // Iterates the current `keyrings` array,
- // serializes each one into a serialized array,
- // encrypts that array with the provided `password`,
- // and persists that encrypted string to storage.
- persistAllKeyrings (password = this.password) {
- if (typeof password === 'string') {
- this.password = password
- this.memStore.updateState({ isUnlocked: true })
- }
- return Promise.all(this.keyrings.map((keyring) => {
- return Promise.all([keyring.type, keyring.serialize()])
- .then((serializedKeyringArray) => {
- // Label the output values on each serialized Keyring:
- return {
- type: serializedKeyringArray[0],
- data: serializedKeyringArray[1],
- }
- })
- }))
- .then((serializedKeyrings) => {
- return this.encryptor.encrypt(this.password, serializedKeyrings)
- })
- .then((encryptedString) => {
- this.store.updateState({ vault: encryptedString })
- return true
- })
- }
-
- // Unlock Keyrings
- // @string password
- //
- // returns Promise( @array keyrings )
- //
- // Attempts to unlock the persisted encrypted storage,
- // initializing the persisted keyrings to RAM.
- unlockKeyrings (password) {
- const encryptedVault = this.store.getState().vault
- if (!encryptedVault) {
- throw new Error('Cannot unlock without a previous vault.')
- }
-
- return this.encryptor.decrypt(password, encryptedVault)
- .then((vault) => {
- this.password = password
- this.memStore.updateState({ isUnlocked: true })
- vault.forEach(this.restoreKeyring.bind(this))
- return this.keyrings
- })
- }
-
- // Restore Keyring
- // @object serialized
- //
- // returns Promise( @Keyring deserialized )
- //
- // Attempts to initialize a new keyring from the provided
- // serialized payload.
- //
- // On success, returns the resulting @Keyring instance.
- restoreKeyring (serialized) {
- const { type, data } = serialized
-
- const Keyring = this.getKeyringClassForType(type)
- const keyring = new Keyring()
- return keyring.deserialize(data)
- .then(() => {
- return keyring.getAccounts()
- })
- .then((accounts) => {
- return this.setupAccounts(accounts)
- })
- .then(() => {
- this.keyrings.push(keyring)
- this._updateMemStoreKeyrings()
- return keyring
- })
- }
-
- // Get Keyring Class For Type
- // @string type
- //
- // Returns @class Keyring
- //
- // Searches the current `keyringTypes` array
- // for a Keyring class whose unique `type` property
- // matches the provided `type`,
- // returning it if it exists.
- getKeyringClassForType (type) {
- return this.keyringTypes.find(kr => kr.type === type)
- }
-
- getKeyringsByType (type) {
- return this.keyrings.filter((keyring) => keyring.type === type)
- }
-
- // Get Accounts
- // returns Promise( @Array[ @string accounts ] )
- //
- // Returns the public addresses of all current accounts
- // managed by all currently unlocked keyrings.
- getAccounts () {
- const keyrings = this.keyrings || []
- return Promise.all(keyrings.map(kr => kr.getAccounts()))
- .then((keyringArrays) => {
- return keyringArrays.reduce((res, arr) => {
- return res.concat(arr)
- }, [])
- })
- }
-
- // Get Keyring For Account
- // @string address
- //
- // returns Promise(@Keyring keyring)
- //
- // Returns the currently initialized keyring that manages
- // the specified `address` if one exists.
- getKeyringForAccount (address) {
- const hexed = normalizeAddress(address)
- log.debug(`KeyringController - getKeyringForAccount: ${hexed}`)
-
- return Promise.all(this.keyrings.map((keyring) => {
- return Promise.all([
- keyring,
- keyring.getAccounts(),
- ])
- }))
- .then(filter((candidate) => {
- const accounts = candidate[1].map(normalizeAddress)
- return accounts.includes(hexed)
- }))
- .then((winners) => {
- if (winners && winners.length > 0) {
- return winners[0][0]
- } else {
- throw new Error('No keyring found for the requested account.')
- }
- })
- }
-
- // Display For Keyring
- // @Keyring keyring
- //
- // returns Promise( @Object { type:String, accounts:Array } )
- //
- // Is used for adding the current keyrings to the state object.
- displayForKeyring (keyring) {
- return keyring.getAccounts()
- .then((accounts) => {
- return {
- type: keyring.type,
- accounts: accounts,
- }
- })
- }
-
- // Add Gas Buffer
- // @string gas (as hexadecimal value)
- //
- // returns @string bufferedGas (as hexadecimal value)
- //
- // Adds a healthy buffer of gas to an initial gas estimate.
- addGasBuffer (gas) {
- const gasBuffer = new BN('100000', 10)
- const bnGas = new BN(ethUtil.stripHexPrefix(gas), 16)
- const correct = bnGas.add(gasBuffer)
- return ethUtil.addHexPrefix(correct.toString(16))
- }
-
- // Clear Keyrings
- //
- // Deallocates all currently managed keyrings and accounts.
- // Used before initializing a new vault.
- clearKeyrings () {
- let accounts
- try {
- accounts = Object.keys(this.accountTracker.getState())
- } catch (e) {
- accounts = []
- }
- accounts.forEach((address) => {
- this.accountTracker.removeAccount(address)
- })
-
- // clear keyrings from memory
- this.keyrings = []
- this.memStore.updateState({
- keyrings: [],
- identities: {},
- })
- }
-
- _updateMemStoreKeyrings () {
- Promise.all(this.keyrings.map(this.displayForKeyring))
- .then((keyrings) => {
- this.memStore.updateState({ keyrings })
- })
- }
-
-}
-
-module.exports = KeyringController
diff --git a/app/scripts/lib/account-tracker.js b/app/scripts/lib/account-tracker.js
index e2892b1ce..cdc21282d 100644
--- a/app/scripts/lib/account-tracker.js
+++ b/app/scripts/lib/account-tracker.js
@@ -57,10 +57,10 @@ class AccountTracker extends EventEmitter {
//
_updateForBlock (block) {
- const blockNumber = '0x' + block.number.toString('hex')
- this._currentBlockNumber = blockNumber
+ this._currentBlockNumber = block.number
+ const currentBlockGasLimit = block.gasLimit
- this.store.updateState({ currentBlockGasLimit: `0x${block.gasLimit.toString('hex')}` })
+ this.store.updateState({ currentBlockGasLimit })
async.parallel([
this._updateAccounts.bind(this),
diff --git a/app/scripts/lib/events-proxy.js b/app/scripts/lib/events-proxy.js
new file mode 100644
index 000000000..d1199a278
--- /dev/null
+++ b/app/scripts/lib/events-proxy.js
@@ -0,0 +1,31 @@
+module.exports = function createEventEmitterProxy(eventEmitter, listeners) {
+ let target = eventEmitter
+ const eventHandlers = listeners || {}
+ const proxy = new Proxy({}, {
+ get: (obj, name) => {
+ // intercept listeners
+ if (name === 'on') return addListener
+ if (name === 'setTarget') return setTarget
+ if (name === 'proxyEventHandlers') return eventHandlers
+ return target[name]
+ },
+ set: (obj, name, value) => {
+ target[name] = value
+ return true
+ },
+ })
+ function setTarget (eventEmitter) {
+ target = eventEmitter
+ // migrate listeners
+ Object.keys(eventHandlers).forEach((name) => {
+ eventHandlers[name].forEach((handler) => target.on(name, handler))
+ })
+ }
+ function addListener (name, handler) {
+ if (!eventHandlers[name]) eventHandlers[name] = []
+ eventHandlers[name].push(handler)
+ target.on(name, handler)
+ }
+ if (listeners) proxy.setTarget(eventEmitter)
+ return proxy
+} \ No newline at end of file
diff --git a/app/scripts/lib/pending-tx-tracker.js b/app/scripts/lib/pending-tx-tracker.js
index 44e9d50fa..3d358b00e 100644
--- a/app/scripts/lib/pending-tx-tracker.js
+++ b/app/scripts/lib/pending-tx-tracker.js
@@ -1,7 +1,5 @@
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
@@ -13,7 +11,6 @@ const RETRY_LIMIT = 3500 // Retry 3500 blocks, or about 1 day.
requires a: {
provider: //,
nonceTracker: //see nonce tracker,
- getBalnce: //(address) a function for getting balances,
getPendingTransactions: //() a function for getting an array of transactions,
publishTransaction: //(rawTx) a async function for publishing raw transactions,
}
@@ -25,11 +22,9 @@ module.exports = class PendingTransactionTracker extends EventEmitter {
super()
this.query = new EthQuery(config.provider)
this.nonceTracker = config.nonceTracker
-
- this.getBalance = config.getBalance
+ this.retryLimit = config.retryLimit || Infinity
this.getPendingTransactions = config.getPendingTransactions
this.publishTransaction = config.publishTransaction
- this.giveUpOnTransaction = config.giveUpOnTransaction
}
// checks if a signed tx is in a block and
@@ -44,18 +39,18 @@ module.exports = class PendingTransactionTracker extends EventEmitter {
if (!txHash) {
const noTxHashErr = new Error('We had an error while submitting this transaction, please try again.')
noTxHashErr.name = 'NoTxHashError'
- this.emit('txFailed', txId, noTxHashErr)
+ this.emit('tx:failed', txId, noTxHashErr)
return
}
block.transactions.forEach((tx) => {
- if (tx.hash === txHash) this.emit('txConfirmed', txId)
+ if (tx.hash === txHash) this.emit('tx:confirmed', txId)
})
})
}
- queryPendingTxs ({oldBlock, newBlock}) {
+ queryPendingTxs ({ oldBlock, newBlock }) {
// check pending transactions on start
if (!oldBlock) {
this._checkPendingTxs()
@@ -96,26 +91,14 @@ module.exports = class PendingTransactionTracker extends EventEmitter {
// ignore resubmit warnings, return early
if (isKnownTx) return
// encountered real error - transition to error state
- this.emit('txFailed', txMeta.id, err)
+ this.emit('tx:failed', txMeta.id, err)
}))
}
async _resubmitTx (txMeta) {
- const address = txMeta.txParams.from
- const balance = this.getBalance(address)
- 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.')
- this.emit('txFailed', txMeta.id, insufficientFundsError)
- log.error(insufficientFundsError)
- return
+ if (txMeta.retryCount > this.retryLimit) {
+ const err = new Error(`Gave up submitting after ${this.retryLimit} blocks un-mined.`)
+ return this.emit('tx:failed', txMeta.id, err)
}
// Only auto-submit already-signed txs:
@@ -125,7 +108,7 @@ module.exports = class PendingTransactionTracker extends EventEmitter {
const txHash = await this.publishTransaction(rawTx)
// Increment successful tries:
- txMeta.retryCount++
+ this.emit('tx:retry', txMeta)
return txHash
}
@@ -137,7 +120,7 @@ module.exports = class PendingTransactionTracker extends EventEmitter {
if (!txHash) {
const noTxHashErr = new Error('We had an error while submitting this transaction, please try again.')
noTxHashErr.name = 'NoTxHashError'
- this.emit('txFailed', txId, noTxHashErr)
+ this.emit('tx:failed', txId, noTxHashErr)
return
}
// get latest transaction status
@@ -146,14 +129,14 @@ module.exports = class PendingTransactionTracker extends EventEmitter {
txParams = await this.query.getTransactionByHash(txHash)
if (!txParams) return
if (txParams.blockNumber) {
- this.emit('txConfirmed', txId)
+ this.emit('tx:confirmed', txId)
}
} catch (err) {
txMeta.warning = {
error: err,
message: 'There was a problem loading this transaction.',
}
- this.emit('txWarning', txMeta)
+ this.emit('tx:warning', txMeta)
throw err
}
}
diff --git a/app/scripts/lib/tx-utils.js b/app/scripts/lib/tx-gas-utils.js
index 5af078dc4..41f67e230 100644
--- a/app/scripts/lib/tx-utils.js
+++ b/app/scripts/lib/tx-gas-utils.js
@@ -1,6 +1,4 @@
const EthQuery = require('ethjs-query')
-const Transaction = require('ethereumjs-tx')
-const normalize = require('eth-sig-util').normalize
const {
hexToBn,
BnMultiplyByFraction,
@@ -78,26 +76,6 @@ module.exports = class txProvideUtil {
return bnToHex(upperGasLimitBn)
}
- // builds ethTx from txParams object
- buildEthTxFromParams (txParams) {
- // normalize values
- txParams.to = normalize(txParams.to)
- txParams.from = normalize(txParams.from)
- txParams.value = normalize(txParams.value)
- txParams.data = normalize(txParams.data)
- txParams.gas = normalize(txParams.gas || txParams.gasLimit)
- txParams.gasPrice = normalize(txParams.gasPrice)
- txParams.nonce = normalize(txParams.nonce)
- // build ethTx
- log.info(`Prepared tx for signing: ${JSON.stringify(txParams)}`)
- const ethTx = new Transaction(txParams)
- return ethTx
- }
-
- async publishTransaction (rawTx) {
- return await this.query.sendRawTransaction(rawTx)
- }
-
async validateTxParams (txParams) {
if (('value' in txParams) && txParams.value.indexOf('-') === 0) {
throw new Error(`Invalid transaction value of ${txParams.value} not a positive number.`)
diff --git a/app/scripts/lib/tx-state-history-helper.js b/app/scripts/lib/tx-state-history-helper.js
index 304069d57..db6e3bc9f 100644
--- a/app/scripts/lib/tx-state-history-helper.js
+++ b/app/scripts/lib/tx-state-history-helper.js
@@ -20,11 +20,15 @@ function migrateFromSnapshotsToDiffs(longHistory) {
)
}
-function generateHistoryEntry(previousState, newState) {
- return jsonDiffer.compare(previousState, newState)
+function generateHistoryEntry(previousState, newState, note) {
+ const entry = jsonDiffer.compare(previousState, newState)
+ // Add a note to the first op, since it breaks if we append it to the entry
+ if (note && entry[0]) entry[0].note = note
+ return entry
}
-function replayHistory(shortHistory) {
+function replayHistory(_shortHistory) {
+ const shortHistory = clone(_shortHistory)
return shortHistory.reduce((val, entry) => jsonDiffer.applyPatch(val, entry).newDocument)
}
diff --git a/app/scripts/lib/tx-state-manager.js b/app/scripts/lib/tx-state-manager.js
new file mode 100644
index 000000000..cf8117864
--- /dev/null
+++ b/app/scripts/lib/tx-state-manager.js
@@ -0,0 +1,245 @@
+const extend = require('xtend')
+const EventEmitter = require('events')
+const ObservableStore = require('obs-store')
+const ethUtil = require('ethereumjs-util')
+const txStateHistoryHelper = require('./tx-state-history-helper')
+
+module.exports = class TransactionStateManger extends EventEmitter {
+ constructor ({ initState, txHistoryLimit, getNetwork }) {
+ super()
+
+ this.store = new ObservableStore(
+ extend({
+ transactions: [],
+ }, initState))
+ this.txHistoryLimit = txHistoryLimit
+ this.getNetwork = getNetwork
+ }
+
+ // Returns the number of txs for the current network.
+ getTxCount () {
+ return this.getTxList().length
+ }
+
+ getTxList () {
+ const network = this.getNetwork()
+ const fullTxList = this.getFullTxList()
+ return fullTxList.filter((txMeta) => txMeta.metamaskNetworkId === network)
+ }
+
+ getFullTxList () {
+ return this.store.getState().transactions
+ }
+
+ // Returns the tx list
+ getUnapprovedTxList () {
+ const txList = this.getTxsByMetaData('status', 'unapproved')
+ return txList.reduce((result, tx) => {
+ result[tx.id] = tx
+ return result
+ }, {})
+ }
+
+ getPendingTransactions (address) {
+ const opts = { status: 'submitted' }
+ if (address) opts.from = address
+ return this.getFilteredTxList(opts)
+ }
+
+ addTx (txMeta) {
+ this.once(`${txMeta.id}:signed`, function (txId) {
+ this.removeAllListeners(`${txMeta.id}:rejected`)
+ })
+ this.once(`${txMeta.id}:rejected`, function (txId) {
+ this.removeAllListeners(`${txMeta.id}:signed`)
+ })
+ // initialize history
+ txMeta.history = []
+ // capture initial snapshot of txMeta for history
+ const snapshot = txStateHistoryHelper.snapshotFromTxMeta(txMeta)
+ txMeta.history.push(snapshot)
+
+ const transactions = this.getFullTxList()
+ const txCount = this.getTxCount()
+ const txHistoryLimit = this.txHistoryLimit
+
+ // 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
+ if (txCount > txHistoryLimit - 1) {
+ const index = transactions.findIndex((metaTx) => metaTx.status === 'confirmed' || metaTx.status === 'rejected')
+ transactions.splice(index, 1)
+ }
+ transactions.push(txMeta)
+ this._saveTxList(transactions)
+ return txMeta
+ }
+ // gets tx by Id and returns it
+ getTx (txId) {
+ const txMeta = this.getTxsByMetaData('id', txId)[0]
+ return txMeta
+ }
+
+ updateTx (txMeta, note) {
+ if (txMeta.txParams) {
+ Object.keys(txMeta.txParams).forEach((key) => {
+ let value = txMeta.txParams[key]
+ if (typeof value !== 'string') console.error(`${key}: ${value} in txParams is not a string`)
+ if (!ethUtil.isHexPrefixed(value)) console.error('is not hex prefixed, anything on txParams must be hex prefixed')
+ })
+ }
+
+ // create txMeta snapshot for history
+ 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, note)
+ txMeta.history.push(entry)
+
+ // commit txMeta to state
+ const txId = txMeta.id
+ const txList = this.getFullTxList()
+ const index = txList.findIndex(txData => txData.id === txId)
+ txList[index] = txMeta
+ this._saveTxList(txList)
+ }
+
+
+ // merges txParams obj onto txData.txParams
+ // use extend to ensure that all fields are filled
+ updateTxParams (txId, txParams) {
+ const txMeta = this.getTx(txId)
+ txMeta.txParams = extend(txMeta.txParams, txParams)
+ this.updateTx(txMeta, `txStateManager#updateTxParams`)
+ }
+
+/*
+ Takes an object of fields to search for eg:
+ let thingsToLookFor = {
+ to: '0x0..',
+ from: '0x0..',
+ status: 'signed',
+ err: undefined,
+ }
+ and returns a list of tx with all
+ options matching
+
+ ****************HINT****************
+ | `err: undefined` is like looking |
+ | for a tx with no err |
+ | so you can also search txs that |
+ | dont have something as well by |
+ | setting the value as undefined |
+ ************************************
+
+ this is for things like filtering a the tx list
+ for only tx's from 1 account
+ or for filltering for all txs from one account
+ and that have been 'confirmed'
+ */
+ getFilteredTxList (opts, initialList) {
+ let filteredTxList = initialList
+ Object.keys(opts).forEach((key) => {
+ filteredTxList = this.getTxsByMetaData(key, opts[key], filteredTxList)
+ })
+ return filteredTxList
+ }
+
+ getTxsByMetaData (key, value, txList = this.getTxList()) {
+ return txList.filter((txMeta) => {
+ if (txMeta.txParams[key]) {
+ return txMeta.txParams[key] === value
+ } else {
+ return txMeta[key] === value
+ }
+ })
+ }
+
+ // STATUS METHODS
+ // statuses:
+ // - `'unapproved'` the user has not responded
+ // - `'rejected'` the user has responded no!
+ // - `'approved'` the user has approved the tx
+ // - `'signed'` the tx is signed
+ // - `'submitted'` the tx is sent to a server
+ // - `'confirmed'` the tx has been included in a block.
+ // - `'failed'` the tx failed for some reason, included on tx data.
+
+ // get::set status
+
+ // should return the status of the tx.
+ getTxStatus (txId) {
+ const txMeta = this.getTx(txId)
+ return txMeta.status
+ }
+
+ // should update the status of the tx to 'rejected'.
+ setTxStatusRejected (txId) {
+ this._setTxStatus(txId, 'rejected')
+ }
+
+ // should update the status of the tx to 'approved'.
+ setTxStatusApproved (txId) {
+ this._setTxStatus(txId, 'approved')
+ }
+
+ // should update the status of the tx to 'signed'.
+ setTxStatusSigned (txId) {
+ this._setTxStatus(txId, 'signed')
+ }
+
+ // should update the status of the tx to 'submitted'.
+ setTxStatusSubmitted (txId) {
+ this._setTxStatus(txId, 'submitted')
+ }
+
+ // should update the status of the tx to 'confirmed'.
+ setTxStatusConfirmed (txId) {
+ this._setTxStatus(txId, 'confirmed')
+ }
+
+ setTxStatusFailed (txId, err) {
+ const txMeta = this.getTx(txId)
+ txMeta.err = {
+ message: err.toString(),
+ stack: err.stack,
+ }
+ this.updateTx(txMeta)
+ this._setTxStatus(txId, 'failed')
+ }
+
+//
+// PRIVATE METHODS
+//
+
+ // Should find the tx in the tx list and
+ // update it.
+ // should set the status in txData
+ // - `'unapproved'` the user has not responded
+ // - `'rejected'` the user has responded no!
+ // - `'approved'` the user has approved the tx
+ // - `'signed'` the tx is signed
+ // - `'submitted'` the tx is sent to a server
+ // - `'confirmed'` the tx has been included in a block.
+ // - `'failed'` the tx failed for some reason, included on tx data.
+ _setTxStatus (txId, status) {
+ const txMeta = this.getTx(txId)
+ txMeta.status = status
+ this.emit(`${txMeta.id}:${status}`, txId)
+ this.emit(`tx:status-update`, txId, status)
+ if (status === 'submitted' || status === 'rejected') {
+ this.emit(`${txMeta.id}:finished`, txMeta)
+ }
+ this.updateTx(txMeta, `txStateManager: setting status to ${status}`)
+ this.emit('update:badge')
+ }
+
+ // Saves the new/updated txList.
+ // Function is intended only for internal use
+ _saveTxList (transactions) {
+ this.store.updateState({ transactions })
+ }
+} \ No newline at end of file
diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js
index 0f850b7f5..03e021a92 100644
--- a/app/scripts/metamask-controller.js
+++ b/app/scripts/metamask-controller.js
@@ -14,7 +14,7 @@ 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 KeyringController = require('eth-keyring-controller')
const NetworkController = require('./controllers/network')
const PreferencesController = require('./controllers/preferences')
const CurrencyController = require('./controllers/currency')
@@ -82,7 +82,7 @@ module.exports = class MetamaskController extends EventEmitter {
// rpc provider
this.provider = this.initializeProvider()
- this.blockTracker = this.provider
+ this.blockTracker = this.provider._blockTracker
// eth data query tools
this.ethQuery = new EthQuery(this.provider)
@@ -100,6 +100,14 @@ module.exports = class MetamaskController extends EventEmitter {
encryptor: opts.encryptor || undefined,
})
+ // If only one account exists, make sure it is selected.
+ this.keyringController.store.subscribe((state) => {
+ const addresses = Object.keys(state.walletNicknames || {})
+ if (addresses.length === 1) {
+ const address = addresses[0]
+ this.preferencesController.setSelectedAddress(address)
+ }
+ })
this.keyringController.on('newAccount', (address) => {
this.preferencesController.setSelectedAddress(address)
this.accountTracker.addAccount(address)
@@ -124,7 +132,6 @@ module.exports = class MetamaskController extends EventEmitter {
provider: this.provider,
blockTracker: this.blockTracker,
ethQuery: this.ethQuery,
- accountTracker: this.accountTracker,
})
this.txController.on('newUnaprovedTx', opts.showUnapprovedTx.bind(opts))
@@ -209,19 +216,18 @@ module.exports = class MetamaskController extends EventEmitter {
//
initializeProvider () {
- return this.networkController.initializeProvider({
+ const providerOpts = {
static: {
eth_syncing: false,
web3_clientVersion: `MetaMask/v${version}`,
},
- // rpc data source
- rpcUrl: this.networkController.getCurrentRpcAddress(),
originHttpHeaderKey: 'X-Metamask-Origin',
// account mgmt
getAccounts: (cb) => {
const isUnlocked = this.keyringController.memStore.getState().isUnlocked
const result = []
const selectedAddress = this.preferencesController.getSelectedAddress()
+
// only show address if account is unlocked
if (isUnlocked && selectedAddress) {
result.push(selectedAddress)
@@ -232,10 +238,11 @@ module.exports = class MetamaskController extends EventEmitter {
processTransaction: nodeify(async (txParams) => await this.txController.newUnapprovedTransaction(txParams), this),
// old style msg signing
processMessage: this.newUnsignedMessage.bind(this),
-
- // new style msg signing
+ // personal_sign msg signing
processPersonalMessage: this.newUnsignedPersonalMessage.bind(this),
- })
+ }
+ const providerProxy = this.networkController.initializeProvider(providerOpts)
+ return providerProxy
}
initPublicConfigStore () {
@@ -304,13 +311,14 @@ module.exports = class MetamaskController extends EventEmitter {
const txController = this.txController
const noticeController = this.noticeController
const addressBookController = this.addressBookController
+ const networkController = this.networkController
return {
// etc
getState: (cb) => cb(null, this.getState()),
- setProviderType: this.networkController.setProviderType.bind(this.networkController),
setCurrentCurrency: this.setCurrentCurrency.bind(this),
markAccountsFound: this.markAccountsFound.bind(this),
+
// coinbase
buyEth: this.buyEth.bind(this),
// shapeshift
@@ -325,12 +333,14 @@ module.exports = class MetamaskController extends EventEmitter {
// vault management
submitPassword: this.submitPassword.bind(this),
+ // network management
+ setProviderType: nodeify(networkController.setProviderType, networkController),
+ setCustomRpc: nodeify(this.setCustomRpc, this),
+
// PreferencesController
setSelectedAddress: nodeify(preferencesController.setSelectedAddress, preferencesController),
addToken: nodeify(preferencesController.addToken, preferencesController),
setCurrentAccountTab: nodeify(preferencesController.setCurrentAccountTab, preferencesController),
- setDefaultRpc: nodeify(this.setDefaultRpc, this),
- setCustomRpc: nodeify(this.setCustomRpc, this),
// AddressController
setAddressBook: nodeify(addressBookController.setAddressBook, addressBookController),
@@ -681,19 +691,13 @@ module.exports = class MetamaskController extends EventEmitter {
createShapeShiftTx (depositAddress, depositType) {
this.shapeshiftController.createShapeShiftTx(depositAddress, depositType)
}
-// network
- setDefaultRpc () {
- this.networkController.setRpcTarget('http://localhost:8545')
- return Promise.resolve('http://localhost:8545')
- }
+ // network
- setCustomRpc (rpcTarget, rpcList) {
+ async setCustomRpc (rpcTarget, rpcList) {
this.networkController.setRpcTarget(rpcTarget)
-
- return this.preferencesController.updateFrequentRpcList(rpcTarget)
- .then(() => {
- return Promise.resolve(rpcTarget)
- })
+ await this.preferencesController.updateFrequentRpcList(rpcTarget)
+ return rpcTarget
}
+
}
diff --git a/app/scripts/migrations/_multi-keyring.js b/app/scripts/migrations/_multi-keyring.js
index 253aa3d9d..7a4578ea7 100644
--- a/app/scripts/migrations/_multi-keyring.js
+++ b/app/scripts/migrations/_multi-keyring.js
@@ -10,7 +10,7 @@ which we dont have access to at the time of this writing.
const ObservableStore = require('obs-store')
const ConfigManager = require('../../app/scripts/lib/config-manager')
const IdentityStoreMigrator = require('../../app/scripts/lib/idStore-migrator')
-const KeyringController = require('../../app/scripts/lib/keyring-controller')
+const KeyringController = require('eth-keyring-controller')
const password = 'obviously not correct'
diff --git a/app/scripts/platforms/extension.js b/app/scripts/platforms/extension.js
index 00c2aa275..0afe04b74 100644
--- a/app/scripts/platforms/extension.js
+++ b/app/scripts/platforms/extension.js
@@ -5,7 +5,6 @@ class ExtensionPlatform {
//
// Public
//
-
reload () {
extension.runtime.reload()
}
diff --git a/development/index.html b/development/index.html
index a0814cb55..e5a027447 100644
--- a/development/index.html
+++ b/development/index.html
@@ -3,62 +3,58 @@
<head>
<meta charset="utf-8">
<title>MetaMask</title>
-
</head>
<body>
-
- <!-- app content -->
- <div id="app-content" style="height: 100%"></div>
<script src="./bundle.js" type="text/javascript" charset="utf-8"></script>
+
+ <style>
+ html, body, #test-container, .super-dev-container {
+ height: 100%;
+ width: 100%;
+ position: relative;
+ background: white;
+ }
+ #app-content {
+ background: #F7F7F7;
+ }
+ </style>
- </body>
+ <script>
+ liveReloadCode(Date.now(), 300)
+ function liveReloadCode(lastUpdate, updateRate) {
+ setTimeout(iter, updateRate)
-<style>
-html, body, #test-container, .super-dev-container {
- height: 100%;
- width: 100%;
- position: relative;
- background: white;
-}
-#app-content {
- background: #F7F7F7;
-}
-</style>
+ function iter() {
+ var xhr = new XMLHttpRequest()
-<script>
-liveReloadCode(Date.now(), 300)
-function liveReloadCode(lastUpdate, updateRate) {
- setTimeout(iter, updateRate)
+ xhr.open('GET', '/-/live-reload')
+ xhr.onreadystatechange = function() {
+ if(xhr.readyState !== 4) {
+ return
+ }
- function iter() {
- var xhr = new XMLHttpRequest()
+ try {
+ var change = JSON.parse(xhr.responseText).lastUpdate
- xhr.open('GET', '/-/live-reload')
- xhr.onreadystatechange = function() {
- if(xhr.readyState !== 4) {
- return
- }
+ if(lastUpdate < change) {
+ return reload()
+ }
+ } catch(err) {
+ }
- try {
- var change = JSON.parse(xhr.responseText).lastUpdate
+ xhr =
+ xhr.onreadystatechange = null
+ setTimeout(iter, updateRate)
+ }
- if(lastUpdate < change) {
- return reload()
+ xhr.send(null)
}
- } catch(err) {
}
- xhr =
- xhr.onreadystatechange = null
- setTimeout(iter, updateRate)
- }
-
- xhr.send(null)
- }
-}
+ function reload() {
+ window.location.reload()
+ }
+ </script>
-function reload() {
- window.location.reload()
-}
- </script>
+ </body>
</html>
diff --git a/mascara/example/app.js b/mascara/example/app.js
index aae7ccd19..d0cb6ba83 100644
--- a/mascara/example/app.js
+++ b/mascara/example/app.js
@@ -1,57 +1,26 @@
-window.addEventListener('load', web3Detect)
+const EthQuery = require('ethjs-query')
+
+window.addEventListener('load', loadProvider)
window.addEventListener('message', console.warn)
-function web3Detect() {
- if (global.web3) {
- logToDom('web3 detected!')
- startApp()
- } else {
- logToDom('no web3 detected!')
- }
+async function loadProvider() {
+ const ethereumProvider = window.metamask.createDefaultProvider({ host: 'http://localhost:9001' })
+ const ethQuery = new EthQuery(ethereumProvider)
+ const accounts = await ethQuery.accounts()
+ logToDom(accounts.length ? accounts[0] : 'LOCKED or undefined')
+ setupButton(ethQuery)
}
-function startApp(){
- console.log('app started')
-
- var primaryAccount
- console.log('getting main account...')
- web3.eth.getAccounts((err, addresses) => {
- if (err) console.error(err)
- console.log('set address', addresses[0])
- primaryAccount = addresses[0]
- })
-
- document.querySelector('.action-button-1').addEventListener('click', function(){
- console.log('saw click')
- console.log('sending tx')
- primaryAccount
- web3.eth.sendTransaction({
- from: primaryAccount,
- to: primaryAccount,
- value: 0,
- }, function(err, txHash){
- if (err) throw err
- console.log('sendTransaction result:', err || txHash)
- })
- })
- document.querySelector('.action-button-2').addEventListener('click', function(){
- console.log('saw click')
- setTimeout(function(){
- console.log('sending tx')
- web3.eth.sendTransaction({
- from: primaryAccount,
- to: primaryAccount,
- value: 0,
- }, function(err, txHash){
- if (err) throw err
- console.log('sendTransaction result:', err || txHash)
- })
- })
- })
-
-}
function logToDom(message){
- document.body.appendChild(document.createTextNode(message))
+ document.getElementById('account').innerText = message
console.log(message)
}
+
+function setupButton (ethQuery) {
+ const button = document.getElementById('action-button-1')
+ button.addEventListener('click', async () => {
+ const accounts = await ethQuery.accounts()
+ logToDom(accounts.length ? accounts[0] : 'LOCKED or undefined')
+ })
+} \ No newline at end of file
diff --git a/mascara/example/app/index.html b/mascara/example/app/index.html
index 02323e5f9..f3e38877c 100644
--- a/mascara/example/app/index.html
+++ b/mascara/example/app/index.html
@@ -3,13 +3,13 @@
<html lang="en">
<head>
<meta charset="utf-8">
- <title>MetaMask ZeroClient Example</title>
<script src="http://localhost:9001/metamascara.js"></script>
+ <title>MetaMask ZeroClient Example</title>
</head>
<body>
- <button class="action-button-1">SYNC TX</button>
- <button class="action-button-2">ASYNC TX</button>
+ <button id="action-button-1">GET ACCOUNT</button>
+ <div id="account"></div>
<script src="./app.js"></script>
</body>
</html> \ No newline at end of file
diff --git a/mascara/src/background.js b/mascara/src/background.js
index d9dbf593a..5ba865ad8 100644
--- a/mascara/src/background.js
+++ b/mascara/src/background.js
@@ -19,8 +19,7 @@ const migrations = require('../../app/scripts/migrations/')
const firstTimeState = require('../../app/scripts/first-time-state')
const STORAGE_KEY = 'metamask-config'
-// const METAMASK_DEBUG = 'GULP_METAMASK_DEBUG'
-const METAMASK_DEBUG = true
+const METAMASK_DEBUG = process.env.METAMASK_DEBUG
let popupIsOpen = false
let connectedClientCount = 0
diff --git a/mascara/src/lib/setup-iframe.js b/mascara/src/lib/setup-iframe.js
deleted file mode 100644
index dcf404574..000000000
--- a/mascara/src/lib/setup-iframe.js
+++ /dev/null
@@ -1,19 +0,0 @@
-const Iframe = require('iframe')
-const createIframeStream = require('iframe-stream').IframeStream
-
-module.exports = setupIframe
-
-
-function setupIframe(opts) {
- opts = opts || {}
- var frame = Iframe({
- src: opts.zeroClientProvider || 'https://zero.metamask.io/',
- container: opts.container || document.head,
- sandboxAttributes: opts.sandboxAttributes || ['allow-scripts', 'allow-popups'],
- })
- var iframe = frame.iframe
- iframe.style.setProperty('display', 'none')
- var iframeStream = createIframeStream(iframe)
-
- return iframeStream
-}
diff --git a/mascara/src/lib/setup-provider.js b/mascara/src/lib/setup-provider.js
deleted file mode 100644
index 62335b18d..000000000
--- a/mascara/src/lib/setup-provider.js
+++ /dev/null
@@ -1,22 +0,0 @@
-const setupIframe = require('./setup-iframe.js')
-const MetamaskInpageProvider = require('../../../app/scripts/lib/inpage-provider.js')
-
-module.exports = getProvider
-
-
-function getProvider(opts){
- if (global.web3) {
- console.log('MetaMask ZeroClient - using environmental web3 provider')
- return global.web3.currentProvider
- }
- console.log('MetaMask ZeroClient - injecting zero-client iframe!')
- var iframeStream = setupIframe({
- zeroClientProvider: opts.mascaraUrl,
- sandboxAttributes: ['allow-scripts', 'allow-popups', 'allow-same-origin'],
- container: document.body,
- })
-
- var inpageProvider = new MetamaskInpageProvider(iframeStream)
- return inpageProvider
-
-}
diff --git a/mascara/src/mascara.js b/mascara/src/mascara.js
index 1655d1f64..0af6f532f 100644
--- a/mascara/src/mascara.js
+++ b/mascara/src/mascara.js
@@ -1,47 +1 @@
-const Web3 = require('web3')
-const setupProvider = require('./lib/setup-provider.js')
-const setupDappAutoReload = require('../../app/scripts/lib/auto-reload.js')
-const MASCARA_ORIGIN = process.env.MASCARA_ORIGIN || 'http://localhost:9001'
-console.log('MASCARA_ORIGIN:', MASCARA_ORIGIN)
-
-//
-// setup web3
-//
-
-const provider = setupProvider({
- mascaraUrl: MASCARA_ORIGIN + '/proxy/',
-})
-instrumentForUserInteractionTriggers(provider)
-
-const web3 = new Web3(provider)
-setupDappAutoReload(web3, provider.publicConfigStore)
-//
-// ui stuff
-//
-
-let shouldPop = false
-window.addEventListener('click', maybeTriggerPopup)
-
-//
-// util
-//
-
-function maybeTriggerPopup(){
- if (!shouldPop) return
- shouldPop = false
- window.open(MASCARA_ORIGIN, '', 'width=360 height=500')
- console.log('opening window...')
-}
-
-function instrumentForUserInteractionTriggers(provider){
- const _super = provider.sendAsync.bind(provider)
- provider.sendAsync = function(payload, cb){
- if (payload.method === 'eth_sendTransaction') {
- console.log('saw send')
- shouldPop = true
- }
- _super(payload, cb)
- }
-}
-
-
+global.metamask = require('metamascara')
diff --git a/mock-dev.js b/mock-dev.js
index a47f1ed4d..0a3eb12ce 100644
--- a/mock-dev.js
+++ b/mock-dev.js
@@ -62,6 +62,7 @@ const controller = new MetamaskController({
showUnconfirmedMessage: noop,
unlockAccountMessage: noop,
showUnapprovedTx: noop,
+ platform: {},
// initial state
initState: firstTimeState,
})
diff --git a/package.json b/package.json
index 182ae9900..918531f15 100644
--- a/package.json
+++ b/package.json
@@ -9,7 +9,7 @@
"ui": "npm run test:flat:build:states && 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 ./",
"watch": "mocha watch --recursive \"test/unit/**/*.js\"",
- "mascara": "node ./mascara/example/server",
+ "mascara": "METAMASK_DEBUG=true node ./mascara/example/server",
"dist": "npm run dist:clear && npm install && gulp dist",
"dist:clear": "rm -rf node_modules/eth-contract-metadata && rm -rf node_modules/eth-phishing-detect",
"test": "npm run lint && npm run test:coverage && npm run test:integration",
@@ -53,10 +53,8 @@
"async": "^2.5.0",
"await-semaphore": "^0.1.1",
"babel-runtime": "^6.23.0",
- "bip39": "^2.2.0",
"bluebird": "^3.5.0",
"bn.js": "^4.11.7",
- "browser-passworder": "^2.0.3",
"browserify-derequire": "^0.9.4",
"client-sw-ready-event": "^3.3.0",
"clone": "^2.1.1",
@@ -69,9 +67,11 @@
"end-of-stream": "^1.1.0",
"ensnare": "^1.0.0",
"eth-bin-to-ops": "^1.0.1",
+ "eth-block-tracker": "^2.2.0",
"eth-contract-metadata": "^1.1.4",
"eth-hd-keyring": "^1.1.1",
- "eth-json-rpc-filters": "^1.1.0",
+ "eth-json-rpc-filters": "^1.2.1",
+ "eth-keyring-controller": "^2.0.0",
"eth-phishing-detect": "^1.1.4",
"eth-query": "^2.1.2",
"eth-sig-util": "^1.2.2",
@@ -80,9 +80,10 @@
"ethereumjs-tx": "^1.3.0",
"ethereumjs-util": "github:ethereumjs/ethereumjs-util#ac5d0908536b447083ea422b435da27f26615de9",
"ethereumjs-wallet": "^0.6.0",
+ "ethjs-contract": "^0.1.9",
"ethjs-ens": "^2.0.0",
"ethjs-query": "^0.2.9",
- "express": "^4.14.0",
+ "express": "^4.15.5",
"extension-link-enabler": "^1.0.0",
"extensionizer": "^1.0.0",
"fast-json-patch": "^2.0.4",
@@ -90,6 +91,7 @@
"gulp": "github:gulpjs/gulp#4.0",
"gulp-eslint": "^4.0.0",
"hat": "0.0.3",
+ "human-standard-token-abi": "^1.0.2",
"idb-global": "^2.1.0",
"identicon.js": "^2.3.1",
"iframe": "^1.0.0",
@@ -99,6 +101,7 @@
"json-rpc-engine": "^3.2.0",
"json-rpc-middleware-stream": "^1.0.1",
"loglevel": "^1.4.1",
+ "metamascara": "^1.3.1",
"metamask-logo": "^2.1.2",
"mississippi": "^1.2.0",
"mkdirp": "^0.5.1",
@@ -138,7 +141,7 @@
"valid-url": "^1.0.9",
"vreme": "^3.0.2",
"web3": "^0.20.1",
- "web3-provider-engine": "^13.2.9",
+ "web3-provider-engine": "^13.3.1",
"web3-stream-provider": "^3.0.1",
"xtend": "^4.0.1"
},
diff --git a/test/unit/components/pending-tx-test.js b/test/unit/components/pending-tx-test.js
index fdade1042..20feba2a3 100644
--- a/test/unit/components/pending-tx-test.js
+++ b/test/unit/components/pending-tx-test.js
@@ -24,7 +24,8 @@ describe('PendingTx', function () {
'to': '0xc5b8dbac4c1d3f152cdeb400e2313f309c410acb',
'value': '0xde0b6b3a7640000',
gasPrice,
- 'gas': '0x7b0c'},
+ 'gas': '0x7b0c',
+ },
'gasLimitSpecified': false,
'estimatedGas': '0x5208',
}
diff --git a/test/unit/currency-controller-test.js b/test/unit/currency-controller-test.js
index 5eeaf9bcc..63ab60f9e 100644
--- a/test/unit/currency-controller-test.js
+++ b/test/unit/currency-controller-test.js
@@ -15,11 +15,11 @@ describe('currency-controller', function () {
describe('currency conversions', function () {
describe('#setCurrentCurrency', function () {
it('should return USD as default', function () {
- assert.equal(currencyController.getCurrentCurrency(), 'USD')
+ assert.equal(currencyController.getCurrentCurrency(), 'usd')
})
it('should be able to set to other currency', function () {
- assert.equal(currencyController.getCurrentCurrency(), 'USD')
+ assert.equal(currencyController.getCurrentCurrency(), 'usd')
currencyController.setCurrentCurrency('JPY')
var result = currencyController.getCurrentCurrency()
assert.equal(result, 'JPY')
@@ -36,12 +36,12 @@ describe('currency-controller', function () {
describe('#updateConversionRate', function () {
it('should retrieve an update for ETH to USD and set it in memory', function (done) {
this.timeout(15000)
- nock('https://api.cryptonator.com')
- .get('/api/ticker/eth-USD')
- .reply(200, '{"ticker":{"base":"ETH","target":"USD","price":"11.02456145","volume":"44948.91745289","change":"-0.01472534"},"timestamp":1472072136,"success":true,"error":""}')
+ nock('https://api.infura.io')
+ .get('/v1/ticker/ethusd')
+ .reply(200, '{"base": "ETH", "quote": "USD", "bid": 288.45, "ask": 288.46, "volume": 112888.17569277, "exchange": "bitfinex", "total_volume": 272175.00106721005, "num_exchanges": 8, "timestamp": 1506444677}')
assert.equal(currencyController.getConversionRate(), 0)
- currencyController.setCurrentCurrency('USD')
+ currencyController.setCurrentCurrency('usd')
currencyController.updateConversionRate()
.then(function () {
var result = currencyController.getConversionRate()
@@ -57,14 +57,14 @@ describe('currency-controller', function () {
this.timeout(15000)
assert.equal(currencyController.getConversionRate(), 0)
- nock('https://api.cryptonator.com')
- .get('/api/ticker/eth-JPY')
- .reply(200, '{"ticker":{"base":"ETH","target":"JPY","price":"11.02456145","volume":"44948.91745289","change":"-0.01472534"},"timestamp":1472072136,"success":true,"error":""}')
+ nock('https://api.infura.io')
+ .get('/v1/ticker/ethjpy')
+ .reply(200, '{"base": "ETH", "quote": "JPY", "bid": 32300.0, "ask": 32400.0, "volume": 247.4616071, "exchange": "kraken", "total_volume": 247.4616071, "num_exchanges": 1, "timestamp": 1506444676}')
var promise = new Promise(
function (resolve, reject) {
- currencyController.setCurrentCurrency('JPY')
+ currencyController.setCurrentCurrency('jpy')
currencyController.updateConversionRate().then(function () {
resolve()
})
diff --git a/test/unit/keyring-controller-test.js b/test/unit/keyring-controller-test.js
deleted file mode 100644
index 135edf365..000000000
--- a/test/unit/keyring-controller-test.js
+++ /dev/null
@@ -1,164 +0,0 @@
-const assert = require('assert')
-const KeyringController = require('../../app/scripts/keyring-controller')
-const configManagerGen = require('../lib/mock-config-manager')
-const ethUtil = require('ethereumjs-util')
-const BN = ethUtil.BN
-const mockEncryptor = require('../lib/mock-encryptor')
-const sinon = require('sinon')
-
-describe('KeyringController', function () {
- let keyringController
- const password = 'password123'
- const seedWords = 'puzzle seed penalty soldier say clay field arctic metal hen cage runway'
- const addresses = ['eF35cA8EbB9669A35c31b5F6f249A9941a812AC1'.toLowerCase()]
- const accounts = []
- // let originalKeystore
-
- beforeEach(function (done) {
- this.sinon = sinon.sandbox.create()
- window.localStorage = {} // Hacking localStorage support into JSDom
-
- keyringController = new KeyringController({
- configManager: configManagerGen(),
- txManager: {
- getTxList: () => [],
- getUnapprovedTxList: () => [],
- },
- accountTracker: {
- addAccount (acct) { accounts.push(ethUtil.addHexPrefix(acct)) },
- },
- encryptor: mockEncryptor,
- })
-
- keyringController.createNewVaultAndKeychain(password)
- .then(function (newState) {
- newState
- done()
- })
- .catch((err) => {
- done(err)
- })
- })
-
- afterEach(function () {
- // Cleanup mocks
- this.sinon.restore()
- })
-
- describe('#createNewVaultAndKeychain', function () {
- this.timeout(10000)
-
- it('should set a vault on the configManager', function (done) {
- keyringController.store.updateState({ vault: null })
- assert(!keyringController.store.getState().vault, 'no previous vault')
- keyringController.createNewVaultAndKeychain(password)
- .then(() => {
- const vault = keyringController.store.getState().vault
- assert(vault, 'vault created')
- done()
- })
- .catch((reason) => {
- done(reason)
- })
- })
- })
-
- describe('#restoreKeyring', function () {
- it(`should pass a keyring's serialized data back to the correct type.`, function (done) {
- const mockSerialized = {
- type: 'HD Key Tree',
- data: {
- mnemonic: seedWords,
- numberOfAccounts: 1,
- },
- }
- const mock = this.sinon.mock(keyringController)
-
- mock.expects('getBalanceAndNickname')
- .exactly(1)
-
- keyringController.restoreKeyring(mockSerialized)
- .then((keyring) => {
- assert.equal(keyring.wallets.length, 1, 'one wallet restored')
- return keyring.getAccounts()
- })
- .then((accounts) => {
- assert.equal(accounts[0], addresses[0])
- mock.verify()
- done()
- })
- .catch((reason) => {
- done(reason)
- })
- })
- })
-
- describe('#createNickname', function () {
- it('should add the address to the identities hash', function () {
- const fakeAddress = '0x12345678'
- keyringController.createNickname(fakeAddress)
- const identities = keyringController.memStore.getState().identities
- const identity = identities[fakeAddress]
- assert.equal(identity.address, fakeAddress)
- })
- })
-
- describe('#saveAccountLabel', function () {
- it('sets the nickname', function (done) {
- const account = addresses[0]
- var nick = 'Test nickname'
- const identities = keyringController.memStore.getState().identities
- identities[ethUtil.addHexPrefix(account)] = {}
- keyringController.memStore.updateState({ identities })
- keyringController.saveAccountLabel(account, nick)
- .then((label) => {
- try {
- assert.equal(label, nick)
- const persisted = keyringController.store.getState().walletNicknames[account]
- assert.equal(persisted, nick)
- done()
- } catch (err) {
- done()
- }
- })
- .catch((reason) => {
- done(reason)
- })
- })
- })
-
- describe('#getAccounts', function () {
- it('returns the result of getAccounts for each keyring', function (done) {
- keyringController.keyrings = [
- { getAccounts () { return Promise.resolve([1, 2, 3]) } },
- { getAccounts () { return Promise.resolve([4, 5, 6]) } },
- ]
-
- keyringController.getAccounts()
- .then((result) => {
- assert.deepEqual(result, [1, 2, 3, 4, 5, 6])
- done()
- })
- })
- })
-
- describe('#addGasBuffer', function () {
- it('adds 100k gas buffer to estimates', function () {
- const gas = '0x04ee59' // Actual estimated gas example
- const tooBigOutput = '0x80674f9' // Actual bad output
- const bnGas = new BN(ethUtil.stripHexPrefix(gas), 16)
- const correctBuffer = new BN('100000', 10)
- const correct = bnGas.add(correctBuffer)
-
- // const tooBig = new BN(tooBigOutput, 16)
- const result = keyringController.addGasBuffer(gas)
- const bnResult = new BN(ethUtil.stripHexPrefix(result), 16)
-
- assert.equal(result.indexOf('0x'), 0, 'included hex prefix')
- assert(bnResult.gt(bnGas), 'Estimate increased in value.')
- assert.equal(bnResult.sub(bnGas).toString(10), '100000', 'added 100k gas')
- assert.equal(result, '0x' + correct.toString(16), 'Added the right amount')
- assert.notEqual(result, tooBigOutput, 'not that bad estimate')
- })
- })
-})
diff --git a/test/unit/metamask-controller-test.js b/test/unit/metamask-controller-test.js
index 5ee0a6c84..ef6cae758 100644
--- a/test/unit/metamask-controller-test.js
+++ b/test/unit/metamask-controller-test.js
@@ -10,6 +10,7 @@ describe('MetaMaskController', function () {
showUnconfirmedMessage: noop,
unlockAccountMessage: noop,
showUnapprovedTx: noop,
+ platform: {},
// initial state
initState: clone(firstTimeState),
})
diff --git a/test/unit/network-contoller-test.js b/test/unit/network-contoller-test.js
index 87c2ee7a3..0b3b5adeb 100644
--- a/test/unit/network-contoller-test.js
+++ b/test/unit/network-contoller-test.js
@@ -20,9 +20,9 @@ describe('# Network Controller', function () {
describe('#provider', function () {
it('provider should be updatable without reassignment', function () {
networkController.initializeProvider(networkControllerProviderInit, dummyProviderConstructor)
- const provider = networkController.provider
- networkController._provider = {test: true}
- assert.ok(provider.test)
+ const proxy = networkController._proxy
+ proxy.setTarget({ test: true, on: () => {} })
+ assert.ok(proxy.test)
})
})
describe('#getNetworkState', function () {
@@ -71,6 +71,7 @@ function dummyProviderConstructor() {
// provider
sendAsync: noop,
// block tracker
+ _blockTracker: {},
start: noop,
stop: noop,
on: noop,
diff --git a/test/unit/pending-tx-test.js b/test/unit/pending-tx-test.js
index 8c6d287f8..4da0eff5d 100644
--- a/test/unit/pending-tx-test.js
+++ b/test/unit/pending-tx-test.js
@@ -40,14 +40,12 @@ describe('PendingTransactionTracker', function () {
pendingTxTracker = new PendingTransactionTracker({
provider,
- getBalance: () => {},
nonceTracker: {
getGlobalLock: async () => {
return { releaseLock: () => {} }
}
},
getPendingTransactions: () => {return []},
- sufficientBalance: () => {},
publishTransaction: () => {},
})
})
@@ -62,7 +60,7 @@ describe('PendingTransactionTracker', function () {
it('should emit \'txFailed\' if the txMeta does not have a hash', function (done) {
const block = Proxy.revocable({}, {}).revoke()
pendingTxTracker.getPendingTransactions = () => [txMetaNoHash]
- pendingTxTracker.once('txFailed', (txId, err) => {
+ pendingTxTracker.once('tx:failed', (txId, err) => {
assert(txId, txMetaNoHash.id, 'should pass txId')
done()
})
@@ -71,11 +69,11 @@ describe('PendingTransactionTracker', function () {
it('should emit \'txConfirmed\' if the tx is in the block', function (done) {
const block = { transactions: [txMeta]}
pendingTxTracker.getPendingTransactions = () => [txMeta]
- pendingTxTracker.once('txConfirmed', (txId) => {
+ pendingTxTracker.once('tx:confirmed', (txId) => {
assert(txId, txMeta.id, 'should pass txId')
done()
})
- pendingTxTracker.once('txFailed', (_, err) => { done(err) })
+ pendingTxTracker.once('tx:failed', (_, err) => { done(err) })
pendingTxTracker.checkForTxInBlock(block)
})
})
@@ -84,14 +82,14 @@ describe('PendingTransactionTracker', function () {
let newBlock, oldBlock
newBlock = { number: '0x01' }
pendingTxTracker._checkPendingTxs = done
- pendingTxTracker.queryPendingTxs({oldBlock, newBlock})
+ pendingTxTracker.queryPendingTxs({ oldBlock, newBlock })
})
it('should call #_checkPendingTxs if oldBlock and the newBlock have a diff of greater then 1', function (done) {
let newBlock, oldBlock
oldBlock = { number: '0x01' }
newBlock = { number: '0x03' }
pendingTxTracker._checkPendingTxs = done
- pendingTxTracker.queryPendingTxs({oldBlock, newBlock})
+ pendingTxTracker.queryPendingTxs({ oldBlock, newBlock })
})
it('should not call #_checkPendingTxs if oldBlock and the newBlock have a diff of 1 or less', function (done) {
let newBlock, oldBlock
@@ -101,14 +99,14 @@ describe('PendingTransactionTracker', function () {
const err = new Error('should not call #_checkPendingTxs if oldBlock and the newBlock have a diff of 1 or less')
done(err)
}
- pendingTxTracker.queryPendingTxs({oldBlock, newBlock})
+ pendingTxTracker.queryPendingTxs({ oldBlock, newBlock })
done()
})
})
describe('#_checkPendingTx', function () {
it('should emit \'txFailed\' if the txMeta does not have a hash', function (done) {
- pendingTxTracker.once('txFailed', (txId, err) => {
+ pendingTxTracker.once('tx:failed', (txId, err) => {
assert(txId, txMetaNoHash.id, 'should pass txId')
done()
})
@@ -122,11 +120,11 @@ describe('PendingTransactionTracker', function () {
it('should emit \'txConfirmed\'', function (done) {
providerResultStub.eth_getTransactionByHash = {blockNumber: '0x01'}
- pendingTxTracker.once('txConfirmed', (txId) => {
+ pendingTxTracker.once('tx:confirmed', (txId) => {
assert(txId, txMeta.id, 'should pass txId')
done()
})
- pendingTxTracker.once('txFailed', (_, err) => { done(err) })
+ pendingTxTracker.once('tx:failed', (_, err) => { done(err) })
pendingTxTracker._checkPendingTx(txMeta)
})
})
@@ -188,7 +186,7 @@ describe('PendingTransactionTracker', function () {
]
const enoughForAllErrors = txList.concat(txList)
- pendingTxTracker.on('txFailed', (_, err) => done(err))
+ pendingTxTracker.on('tx:failed', (_, err) => done(err))
pendingTxTracker.getPendingTransactions = () => enoughForAllErrors
pendingTxTracker._resubmitTx = async (tx) => {
@@ -202,7 +200,7 @@ describe('PendingTransactionTracker', function () {
pendingTxTracker.resubmitPendingTxs()
})
it('should emit \'txFailed\' if it encountered a real error', function (done) {
- pendingTxTracker.once('txFailed', (id, err) => err.message === 'im some real error' ? txList[id - 1].resolve() : done(err))
+ pendingTxTracker.once('tx:failed', (id, err) => err.message === 'im some real error' ? txList[id - 1].resolve() : done(err))
pendingTxTracker.getPendingTransactions = () => txList
pendingTxTracker._resubmitTx = async (tx) => { throw new TypeError('im some real error') }
@@ -213,30 +211,7 @@ describe('PendingTransactionTracker', function () {
pendingTxTracker.resubmitPendingTxs()
})
})
- describe('#_resubmitTx with a too-low balance', function () {
- it('should return before publishing the transaction because to low of balance', function (done) {
- const lowBalance = '0x0'
- pendingTxTracker.getBalance = (address) => {
- assert.equal(address, txMeta.txParams.from, 'Should pass the address')
- return lowBalance
- }
- pendingTxTracker.publishTransaction = async (rawTx) => {
- done(new Error('tried to publish transaction'))
- }
-
- // Stubbing out current account state:
- // Adding the fake tx:
- pendingTxTracker.once('txFailed', (txId, err) => {
- assert(err, 'Should have a error')
- done()
- })
- pendingTxTracker._resubmitTx(txMeta)
- .catch((err) => {
- assert.ifError(err, 'should not throw an error')
- done(err)
- })
- })
-
+ describe('#_resubmitTx', function () {
it('should publishing the transaction', function (done) {
const enoughBalance = '0x100000'
pendingTxTracker.getBalance = (address) => {
diff --git a/test/unit/tx-controller-test.js b/test/unit/tx-controller-test.js
index 7b875db66..bb51ab01f 100644
--- a/test/unit/tx-controller-test.js
+++ b/test/unit/tx-controller-test.js
@@ -2,21 +2,19 @@ const assert = require('assert')
const ethUtil = require('ethereumjs-util')
const EthTx = require('ethereumjs-tx')
const ObservableStore = require('obs-store')
-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 TxGasUtils = require('../../app/scripts/lib/tx-gas-utils')
+const { createStubedProvider } = require('../stub/provider')
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
+ let txController, provider, providerResultStub
beforeEach(function () {
providerResultStub = {}
@@ -27,34 +25,96 @@ describe('Transaction Controller', function () {
networkStore: new ObservableStore(currentNetworkId),
txHistoryLimit: 10,
blockTracker: { getCurrentBlock: noop, on: noop, once: noop },
- accountTracker: { getState: noop },
signTransaction: (ethTx) => new Promise((resolve) => {
ethTx.sign(privKey)
resolve()
}),
})
txController.nonceTracker.getNonceLock = () => Promise.resolve({ nextNonce: 0, releaseLock: noop })
- txController.txProviderUtils = new TxProvideUtils(txController.provider)
+ txController.txProviderUtils = new TxGasUtils(txController.provider)
+ })
+
+ describe('#getState', function () {
+ it('should return a state object with the right keys and datat types', function () {
+ const exposedState = txController.getState()
+ assert('unapprovedTxs' in exposedState, 'state should have the key unapprovedTxs')
+ assert('selectedAddressTxList' in exposedState, 'state should have the key selectedAddressTxList')
+ assert(typeof exposedState.unapprovedTxs === 'object', 'should be an object')
+ assert(Array.isArray(exposedState.selectedAddressTxList), 'should be an array')
+ })
+ })
+
+ describe('#getUnapprovedTxCount', function () {
+ it('should return the number of unapproved txs', function () {
+ txController.txStateManager._saveTxList([
+ { id: 1, status: 'unapproved', metamaskNetworkId: currentNetworkId, txParams: {} },
+ { id: 2, status: 'unapproved', metamaskNetworkId: currentNetworkId, txParams: {} },
+ { id: 3, status: 'unapproved', metamaskNetworkId: currentNetworkId, txParams: {} },
+ ])
+ const unapprovedTxCount = txController.getUnapprovedTxCount()
+ assert.equal(unapprovedTxCount, 3, 'should be 3')
+ })
})
+ describe('#getPendingTxCount', function () {
+ it('should return the number of pending txs', function () {
+ txController.txStateManager._saveTxList([
+ { id: 1, status: 'submitted', metamaskNetworkId: currentNetworkId, txParams: {} },
+ { id: 2, status: 'submitted', metamaskNetworkId: currentNetworkId, txParams: {} },
+ { id: 3, status: 'submitted', metamaskNetworkId: currentNetworkId, txParams: {} },
+ ])
+ const pendingTxCount = txController.getPendingTxCount()
+ assert.equal(pendingTxCount, 3, 'should be 3')
+ })
+ })
+
+ describe('#getConfirmedTransactions', function () {
+ let address
+ beforeEach(function () {
+ address = '0xc684832530fcbddae4b4230a47e991ddcec2831d'
+ const txParams = {
+ 'from': address,
+ 'to': '0xc684832530fcbddae4b4230a47e991ddcec2831d',
+ }
+ txController.txStateManager._saveTxList([
+ {id: 0, status: 'confirmed', metamaskNetworkId: currentNetworkId, txParams},
+ {id: 1, status: 'confirmed', metamaskNetworkId: currentNetworkId, txParams},
+ {id: 2, status: 'confirmed', metamaskNetworkId: currentNetworkId, txParams},
+ {id: 3, status: 'unapproved', metamaskNetworkId: currentNetworkId, txParams},
+ {id: 4, status: 'rejected', metamaskNetworkId: currentNetworkId, txParams},
+ {id: 5, status: 'approved', metamaskNetworkId: currentNetworkId, txParams},
+ {id: 6, status: 'signed', metamaskNetworkId: currentNetworkId, txParams},
+ {id: 7, status: 'submitted', metamaskNetworkId: currentNetworkId, txParams},
+ {id: 8, status: 'failed', metamaskNetworkId: currentNetworkId, txParams},
+ ])
+ })
+
+ it('should return the number of confirmed txs', function () {
+ assert.equal(txController.nonceTracker.getConfirmedTransactions(address).length, 3)
+ })
+ })
+
+
describe('#newUnapprovedTransaction', function () {
let stub, txMeta, txParams
beforeEach(function () {
txParams = {
- 'from':'0xc684832530fcbddae4b4230a47e991ddcec2831d',
- 'to':'0xc684832530fcbddae4b4230a47e991ddcec2831d',
- },
+ 'from': '0xc684832530fcbddae4b4230a47e991ddcec2831d',
+ 'to': '0xc684832530fcbddae4b4230a47e991ddcec2831d',
+ }
txMeta = {
status: 'unapproved',
id: 1,
metamaskNetworkId: currentNetworkId,
txParams,
+ history: [],
}
- txController.addTx(txMeta)
- stub = sinon.stub(txController, 'addUnapprovedTransaction').returns(Promise.resolve(txMeta))
+ txController.txStateManager._saveTxList([txMeta])
+ stub = sinon.stub(txController, 'addUnapprovedTransaction').returns(Promise.resolve(txController.txStateManager.addTx(txMeta)))
})
afterEach(function () {
+ txController.txStateManager._saveTxList([])
stub.restore()
})
@@ -72,7 +132,7 @@ describe('Transaction Controller', function () {
txController.once('newUnaprovedTx', (txMetaFromEmit) => {
setTimeout(() => {
txController.setTxHash(txMetaFromEmit.id, '0x0')
- txController.setTxStatusSubmitted(txMetaFromEmit.id)
+ txController.txStateManager.setTxStatusSubmitted(txMetaFromEmit.id)
}, 10)
})
@@ -87,7 +147,7 @@ describe('Transaction Controller', function () {
it('should reject when finished and status is rejected', function (done) {
txController.once('newUnaprovedTx', (txMetaFromEmit) => {
setTimeout(() => {
- txController.setTxStatusRejected(txMetaFromEmit.id)
+ txController.txStateManager.setTxStatusRejected(txMetaFromEmit.id)
}, 10)
})
@@ -110,7 +170,7 @@ describe('Transaction Controller', function () {
assert(('txParams' in txMeta), 'should have a txParams')
assert(('history' in txMeta), 'should have a history')
- const memTxMeta = txController.getTx(txMeta.id)
+ const memTxMeta = txController.txStateManager.getTx(txMeta.id)
assert.deepEqual(txMeta, memTxMeta, `txMeta should be stored in txController after adding it\n expected: ${txMeta} \n got: ${memTxMeta}`)
addTxDefaultsStub.restore()
done()
@@ -120,10 +180,10 @@ describe('Transaction Controller', function () {
describe('#addTxDefaults', function () {
it('should add the tx defaults if their are none', function (done) {
- let txMeta = {
+ const txMeta = {
'txParams': {
- 'from':'0xc684832530fcbddae4b4230a47e991ddcec2831d',
- 'to':'0xc684832530fcbddae4b4230a47e991ddcec2831d',
+ 'from': '0xc684832530fcbddae4b4230a47e991ddcec2831d',
+ 'to': '0xc684832530fcbddae4b4230a47e991ddcec2831d',
},
}
providerResultStub.eth_gasPrice = '4a817c800'
@@ -131,7 +191,7 @@ describe('Transaction Controller', function () {
providerResultStub.eth_estimateGas = '5209'
txController.addTxDefaults(txMeta)
.then((txMetaWithDefaults) => {
- assert(txMetaWithDefaults.txParams.value, '0x0','should have added 0x0 as the value')
+ assert(txMetaWithDefaults.txParams.value, '0x0', 'should have added 0x0 as the value')
assert(txMetaWithDefaults.txParams.gasPrice, 'should have added the gas price')
assert(txMetaWithDefaults.txParams.gas, 'should have added the gas field')
done()
@@ -163,214 +223,31 @@ describe('Transaction Controller', function () {
})
})
- describe('#getTxList', function () {
- it('when new should return empty array', function () {
- var result = txController.getTxList()
- assert.ok(Array.isArray(result))
- assert.equal(result.length, 0)
- })
- })
-
describe('#addTx', function () {
- it('adds a tx returned in getTxList', function () {
- var tx = { id: 1, status: 'confirmed', metamaskNetworkId: currentNetworkId, txParams: {} }
- txController.addTx(tx, noop)
- var result = txController.getTxList()
- assert.ok(Array.isArray(result))
- assert.equal(result.length, 1)
- assert.equal(result[0].id, 1)
- })
-
- it('does not override txs from other networks', function () {
- var tx = { id: 1, status: 'confirmed', metamaskNetworkId: currentNetworkId, txParams: {} }
- var tx2 = { id: 2, status: 'confirmed', metamaskNetworkId: otherNetworkId, txParams: {} }
- txController.addTx(tx, noop)
- txController.addTx(tx2, noop)
- var result = txController.getFullTxList()
- var result2 = txController.getTxList()
- assert.equal(result.length, 2, 'txs were deleted')
- assert.equal(result2.length, 1, 'incorrect number of txs on network.')
- })
-
- it('cuts off early txs beyond a limit', function () {
- const limit = txController.txHistoryLimit
- for (let i = 0; i < limit + 1; i++) {
- const tx = { id: i, time: new Date(), status: 'confirmed', metamaskNetworkId: currentNetworkId, txParams: {} }
- txController.addTx(tx, noop)
- }
- var result = txController.getTxList()
- assert.equal(result.length, limit, `limit of ${limit} txs enforced`)
- assert.equal(result[0].id, 1, 'early txs truncted')
- })
-
- it('cuts off early txs beyond a limit whether or not it is confirmed or rejected', function () {
- const limit = txController.txHistoryLimit
- for (let i = 0; i < limit + 1; i++) {
- const tx = { id: i, time: new Date(), status: 'rejected', metamaskNetworkId: currentNetworkId, txParams: {} }
- txController.addTx(tx, noop)
- }
- var result = txController.getTxList()
- assert.equal(result.length, limit, `limit of ${limit} txs enforced`)
- assert.equal(result[0].id, 1, 'early txs truncted')
- })
-
- it('cuts off early txs beyond a limit but does not cut unapproved txs', function () {
- var unconfirmedTx = { id: 0, time: new Date(), status: 'unapproved', metamaskNetworkId: currentNetworkId, txParams: {} }
- txController.addTx(unconfirmedTx, noop)
- const limit = txController.txHistoryLimit
- for (let i = 1; i < limit + 1; i++) {
- const tx = { id: i, time: new Date(), status: 'confirmed', metamaskNetworkId: currentNetworkId, txParams: {} }
- txController.addTx(tx, noop)
- }
- var result = txController.getTxList()
- assert.equal(result.length, limit, `limit of ${limit} txs enforced`)
- assert.equal(result[0].id, 0, 'first tx should still be there')
- assert.equal(result[0].status, 'unapproved', 'first tx should be unapproved')
- assert.equal(result[1].id, 2, 'early txs truncted')
- })
- })
-
- describe('#setTxStatusSigned', function () {
- it('sets the tx status to signed', function () {
- var tx = { id: 1, status: 'unapproved', metamaskNetworkId: currentNetworkId, txParams: {} }
- txController.addTx(tx, noop)
- txController.setTxStatusSigned(1)
- var result = txController.getTxList()
- assert.ok(Array.isArray(result))
- assert.equal(result.length, 1)
- assert.equal(result[0].status, 'signed')
- })
-
- it('should emit a signed event to signal the exciton of callback', (done) => {
- this.timeout(10000)
- var tx = { id: 1, status: 'unapproved', metamaskNetworkId: currentNetworkId, txParams: {} }
- const noop = function () {
- assert(true, 'event listener has been triggered and noop executed')
- done()
- }
- txController.addTx(tx)
- txController.on('1:signed', noop)
- txController.setTxStatusSigned(1)
- })
- })
-
- describe('#setTxStatusRejected', function () {
- it('sets the tx status to rejected', function () {
- var tx = { id: 1, status: 'unapproved', metamaskNetworkId: currentNetworkId, txParams: {} }
- txController.addTx(tx)
- txController.setTxStatusRejected(1)
- var result = txController.getTxList()
- assert.ok(Array.isArray(result))
- assert.equal(result.length, 1)
- assert.equal(result[0].status, 'rejected')
- })
-
- it('should emit a rejected event to signal the exciton of callback', (done) => {
- this.timeout(10000)
- var tx = { id: 1, status: 'unapproved', metamaskNetworkId: currentNetworkId, txParams: {} }
- txController.addTx(tx)
- const noop = function (err, txId) {
- assert(true, 'event listener has been triggered and noop executed')
- done()
- }
- txController.on('1:rejected', noop)
- txController.setTxStatusRejected(1)
- })
- })
-
- describe('#updateTx', 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)
- 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 and adds history items', function () {
- const originalGasPrice = '0x01'
- const desiredGasPrice = '0x02'
-
+ it('should emit updates', function (done) {
const txMeta = {
id: '1',
status: 'unapproved',
metamaskNetworkId: currentNetworkId,
- txParams: {
- gasPrice: originalGasPrice,
- },
+ txParams: {},
}
+ const eventNames = ['update:badge', '1:unapproved']
+ const listeners = []
+ eventNames.forEach((eventName) => {
+ listeners.push(new Promise((resolve) => {
+ txController.once(eventName, (arg) => {
+ resolve(arg)
+ })
+ }))
+ })
+ Promise.all(listeners)
+ .then((returnValues) => {
+ assert.deepEqual(returnValues.pop(), txMeta, 'last event 1:unapproved should return txMeta')
+ done()
+ })
+ .catch(done)
txController.addTx(txMeta)
- 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)')
- })
- })
-
- describe('#getUnapprovedTxList', function () {
- it('returns unapproved txs in a hash', function () {
- txController.addTx({ id: '1', status: 'unapproved', metamaskNetworkId: currentNetworkId, txParams: {} }, noop)
- txController.addTx({ id: '2', status: 'confirmed', metamaskNetworkId: currentNetworkId, txParams: {} }, noop)
- const result = txController.getUnapprovedTxList()
- assert.equal(typeof result, 'object')
- assert.equal(result['1'].status, 'unapproved')
- assert.equal(result['2'], undefined)
- })
- })
-
- describe('#getTx', function () {
- it('returns a tx with the requested id', function () {
- txController.addTx({ id: '1', status: 'unapproved', metamaskNetworkId: currentNetworkId, txParams: {} }, noop)
- txController.addTx({ id: '2', status: 'confirmed', metamaskNetworkId: currentNetworkId, txParams: {} }, noop)
- assert.equal(txController.getTx('1').status, 'unapproved')
- assert.equal(txController.getTx('2').status, 'confirmed')
- })
- })
-
- describe('#getFilteredTxList', function () {
- it('returns a tx with the requested data', function () {
- const txMetas = [
- { id: 0, status: 'unapproved', txParams: { from: '0xaa', to: '0xbb' }, metamaskNetworkId: currentNetworkId },
- { id: 1, status: 'unapproved', txParams: { from: '0xaa', to: '0xbb' }, metamaskNetworkId: currentNetworkId },
- { id: 2, status: 'unapproved', txParams: { from: '0xaa', to: '0xbb' }, metamaskNetworkId: currentNetworkId },
- { id: 3, status: 'unapproved', txParams: { from: '0xbb', to: '0xaa' }, metamaskNetworkId: currentNetworkId },
- { id: 4, status: 'unapproved', txParams: { from: '0xbb', to: '0xaa' }, metamaskNetworkId: currentNetworkId },
- { id: 5, status: 'confirmed', txParams: { from: '0xaa', to: '0xbb' }, metamaskNetworkId: currentNetworkId },
- { id: 6, status: 'confirmed', txParams: { from: '0xaa', to: '0xbb' }, metamaskNetworkId: currentNetworkId },
- { id: 7, status: 'confirmed', txParams: { from: '0xbb', to: '0xaa' }, metamaskNetworkId: currentNetworkId },
- { id: 8, status: 'confirmed', txParams: { from: '0xbb', to: '0xaa' }, metamaskNetworkId: currentNetworkId },
- { id: 9, status: 'confirmed', txParams: { from: '0xbb', to: '0xaa' }, metamaskNetworkId: currentNetworkId },
- ]
- txMetas.forEach((txMeta) => txController.addTx(txMeta, noop))
- let filterParams
-
- filterParams = { status: 'unapproved', from: '0xaa' }
- assert.equal(txController.getFilteredTxList(filterParams).length, 3, `getFilteredTxList - ${JSON.stringify(filterParams)}`)
- filterParams = { status: 'unapproved', to: '0xaa' }
- assert.equal(txController.getFilteredTxList(filterParams).length, 2, `getFilteredTxList - ${JSON.stringify(filterParams)}`)
- filterParams = { status: 'confirmed', from: '0xbb' }
- assert.equal(txController.getFilteredTxList(filterParams).length, 3, `getFilteredTxList - ${JSON.stringify(filterParams)}`)
- filterParams = { status: 'confirmed' }
- assert.equal(txController.getFilteredTxList(filterParams).length, 5, `getFilteredTxList - ${JSON.stringify(filterParams)}`)
- filterParams = { from: '0xaa' }
- assert.equal(txController.getFilteredTxList(filterParams).length, 5, `getFilteredTxList - ${JSON.stringify(filterParams)}`)
- filterParams = { to: '0xaa' }
- assert.equal(txController.getFilteredTxList(filterParams).length, 5, `getFilteredTxList - ${JSON.stringify(filterParams)}`)
})
})
@@ -404,11 +281,11 @@ describe('Transaction Controller', function () {
const pubStub = sinon.stub(txController, 'publishTransaction').callsFake(() => {
txController.setTxHash('1', originalValue)
- txController.setTxStatusSubmitted('1')
+ txController.txStateManager.setTxStatusSubmitted('1')
})
txController.approveTransaction(txMeta.id).then(() => {
- const result = txController.getTx(txMeta.id)
+ const result = txController.txStateManager.getTx(txMeta.id)
const params = result.txParams
assert.equal(params.gas, originalValue, 'gas unmodified')
@@ -431,4 +308,96 @@ describe('Transaction Controller', function () {
}).catch(done)
})
})
+
+ describe('#updateAndApproveTransaction', function () {
+ let txMeta
+ beforeEach(function () {
+ txMeta = {
+ id: 1,
+ status: 'unapproved',
+ txParams: {
+ from: '0xc684832530fcbddae4b4230a47e991ddcec2831d',
+ to: '0x1678a085c290ebd122dc42cba69373b5953b831d',
+ gasPrice: '0x77359400',
+ gas: '0x7b0d',
+ nonce: '0x4b',
+ },
+ metamaskNetworkId: currentNetworkId,
+ }
+ })
+ it('should update and approve transactions', function () {
+ txController.txStateManager.addTx(txMeta)
+ txController.updateAndApproveTransaction(txMeta)
+ const tx = txController.txStateManager.getTx(1)
+ assert.equal(tx.status, 'approved')
+ })
+ })
+
+ describe('#getChainId', function () {
+ it('returns 0 when the chainId is NaN', function () {
+ txController.networkStore = new ObservableStore(NaN)
+ assert.equal(txController.getChainId(), 0)
+ })
+ })
+
+ describe('#cancelTransaction', function () {
+ beforeEach(function () {
+ txController.txStateManager._saveTxList([
+ { id: 0, status: 'unapproved', txParams: {}, metamaskNetworkId: currentNetworkId, history: [{}] },
+ { id: 1, status: 'rejected', txParams: {}, metamaskNetworkId: currentNetworkId, history: [{}] },
+ { id: 2, status: 'approved', txParams: {}, metamaskNetworkId: currentNetworkId, history: [{}] },
+ { id: 3, status: 'signed', txParams: {}, metamaskNetworkId: currentNetworkId, history: [{}] },
+ { id: 4, status: 'submitted', txParams: {}, metamaskNetworkId: currentNetworkId, history: [{}] },
+ { id: 5, status: 'confirmed', txParams: {}, metamaskNetworkId: currentNetworkId, history: [{}] },
+ { id: 6, status: 'failed', txParams: {}, metamaskNetworkId: currentNetworkId, history: [{}] },
+ ])
+ })
+
+ it('should set the transaction to rejected from unapproved', async function () {
+ await txController.cancelTransaction(0)
+ assert.equal(txController.txStateManager.getTx(0).status, 'rejected')
+ })
+
+ })
+
+ describe('#publishTransaction', function () {
+ let hash, txMeta
+ beforeEach(function () {
+ hash = '0x2a5523c6fa98b47b7d9b6c8320179785150b42a16bcff36b398c5062b65657e8'
+ txMeta = {
+ id: 1,
+ status: 'unapproved',
+ txParams: {},
+ metamaskNetworkId: currentNetworkId,
+ }
+ providerResultStub.eth_sendRawTransaction = hash
+ })
+
+ it('should publish a tx, updates the rawTx when provided a one', async function () {
+ txController.txStateManager.addTx(txMeta)
+ await txController.publishTransaction(txMeta.id)
+ const publishedTx = txController.txStateManager.getTx(1)
+ assert.equal(publishedTx.hash, hash)
+ assert.equal(publishedTx.status, 'submitted')
+ })
+ })
+
+
+ describe('#getPendingTransactions', function () {
+ beforeEach(function () {
+ txController.txStateManager._saveTxList([
+ { id: 1, status: 'unapproved', metamaskNetworkId: currentNetworkId, txParams: {} },
+ { id: 2, status: 'rejected', metamaskNetworkId: currentNetworkId, txParams: {} },
+ { id: 3, status: 'approved', metamaskNetworkId: currentNetworkId, txParams: {} },
+ { id: 4, status: 'signed', metamaskNetworkId: currentNetworkId, txParams: {} },
+ { id: 5, status: 'submitted', metamaskNetworkId: currentNetworkId, txParams: {} },
+ { id: 6, status: 'confimed', metamaskNetworkId: currentNetworkId, txParams: {} },
+ { id: 7, status: 'failed', metamaskNetworkId: currentNetworkId, txParams: {} },
+ ])
+ })
+ it('should show only submitted transactions as pending transasction', function () {
+ assert(txController.pendingTxTracker.getPendingTransactions().length, 1)
+ assert(txController.pendingTxTracker.getPendingTransactions()[0].status, 'submitted')
+ })
+ })
})
diff --git a/test/unit/tx-state-history-helper.js b/test/unit/tx-state-history-helper.js
index 5bb6c9bee..79ee26d6e 100644
--- a/test/unit/tx-state-history-helper.js
+++ b/test/unit/tx-state-history-helper.js
@@ -20,4 +20,27 @@ describe('tx-state-history-helper', function () {
})
})
})
+
+ it('replaying history does not mutate the original obj', function () {
+ const initialState = { test: true, message: 'hello', value: 1 }
+ const diff1 = [{
+ "op": "replace",
+ "path": "/message",
+ "value": "haay",
+ }]
+ const diff2 = [{
+ "op": "replace",
+ "path": "/value",
+ "value": 2,
+ }]
+ const history = [initialState, diff1, diff2]
+
+ const beforeStateSnapshot = JSON.stringify(initialState)
+ const latestState = txStateHistoryHelper.replayHistory(history)
+ const afterStateSnapshot = JSON.stringify(initialState)
+
+ assert.notEqual(initialState, latestState, 'initial state is not the same obj as the latest state')
+ assert.equal(beforeStateSnapshot, afterStateSnapshot, 'initial state is not modified during run')
+ })
+
})
diff --git a/test/unit/tx-state-manager-test.js b/test/unit/tx-state-manager-test.js
new file mode 100644
index 000000000..464e50ee4
--- /dev/null
+++ b/test/unit/tx-state-manager-test.js
@@ -0,0 +1,241 @@
+const assert = require('assert')
+const clone = require('clone')
+const ObservableStore = require('obs-store')
+const TxStateManager = require('../../app/scripts/lib/tx-state-manager')
+const txStateHistoryHelper = require('../../app/scripts/lib/tx-state-history-helper')
+const noop = () => true
+
+describe('TransactionStateManger', function () {
+ let txStateManager
+ const currentNetworkId = 42
+ const otherNetworkId = 2
+
+ beforeEach(function () {
+ txStateManager = new TxStateManager({
+ initState: {
+ transactions: [],
+ },
+ txHistoryLimit: 10,
+ getNetwork: () => currentNetworkId
+ })
+ })
+
+ describe('#setTxStatusSigned', function () {
+ it('sets the tx status to signed', function () {
+ let tx = { id: 1, status: 'unapproved', metamaskNetworkId: currentNetworkId, txParams: {} }
+ txStateManager.addTx(tx, noop)
+ txStateManager.setTxStatusSigned(1)
+ let result = txStateManager.getTxList()
+ assert.ok(Array.isArray(result))
+ assert.equal(result.length, 1)
+ assert.equal(result[0].status, 'signed')
+ })
+
+ it('should emit a signed event to signal the exciton of callback', (done) => {
+ let tx = { id: 1, status: 'unapproved', metamaskNetworkId: currentNetworkId, txParams: {} }
+ const noop = function () {
+ assert(true, 'event listener has been triggered and noop executed')
+ done()
+ }
+ txStateManager.addTx(tx)
+ txStateManager.on('1:signed', noop)
+ txStateManager.setTxStatusSigned(1)
+
+ })
+ })
+
+ describe('#setTxStatusRejected', function () {
+ it('sets the tx status to rejected', function () {
+ let tx = { id: 1, status: 'unapproved', metamaskNetworkId: currentNetworkId, txParams: {} }
+ txStateManager.addTx(tx)
+ txStateManager.setTxStatusRejected(1)
+ let result = txStateManager.getTxList()
+ assert.ok(Array.isArray(result))
+ assert.equal(result.length, 1)
+ assert.equal(result[0].status, 'rejected')
+ })
+
+ it('should emit a rejected event to signal the exciton of callback', (done) => {
+ let tx = { id: 1, status: 'unapproved', metamaskNetworkId: currentNetworkId, txParams: {} }
+ txStateManager.addTx(tx)
+ const noop = function (err, txId) {
+ assert(true, 'event listener has been triggered and noop executed')
+ done()
+ }
+ txStateManager.on('1:rejected', noop)
+ txStateManager.setTxStatusRejected(1)
+ })
+ })
+
+ describe('#getFullTxList', function () {
+ it('when new should return empty array', function () {
+ let result = txStateManager.getTxList()
+ assert.ok(Array.isArray(result))
+ assert.equal(result.length, 0)
+ })
+ })
+
+ describe('#getTxList', function () {
+ it('when new should return empty array', function () {
+ let result = txStateManager.getTxList()
+ assert.ok(Array.isArray(result))
+ assert.equal(result.length, 0)
+ })
+ })
+
+ describe('#addTx', function () {
+ it('adds a tx returned in getTxList', function () {
+ let tx = { id: 1, status: 'confirmed', metamaskNetworkId: currentNetworkId, txParams: {} }
+ txStateManager.addTx(tx, noop)
+ let result = txStateManager.getTxList()
+ assert.ok(Array.isArray(result))
+ assert.equal(result.length, 1)
+ assert.equal(result[0].id, 1)
+ })
+
+ it('does not override txs from other networks', function () {
+ let tx = { id: 1, status: 'confirmed', metamaskNetworkId: currentNetworkId, txParams: {} }
+ let tx2 = { id: 2, status: 'confirmed', metamaskNetworkId: otherNetworkId, txParams: {} }
+ txStateManager.addTx(tx, noop)
+ txStateManager.addTx(tx2, noop)
+ let result = txStateManager.getFullTxList()
+ let result2 = txStateManager.getTxList()
+ assert.equal(result.length, 2, 'txs were deleted')
+ assert.equal(result2.length, 1, 'incorrect number of txs on network.')
+ })
+
+ it('cuts off early txs beyond a limit', function () {
+ const limit = txStateManager.txHistoryLimit
+ for (let i = 0; i < limit + 1; i++) {
+ const tx = { id: i, time: new Date(), status: 'confirmed', metamaskNetworkId: currentNetworkId, txParams: {} }
+ txStateManager.addTx(tx, noop)
+ }
+ let result = txStateManager.getTxList()
+ assert.equal(result.length, limit, `limit of ${limit} txs enforced`)
+ assert.equal(result[0].id, 1, 'early txs truncted')
+ })
+
+ it('cuts off early txs beyond a limit whether or not it is confirmed or rejected', function () {
+ const limit = txStateManager.txHistoryLimit
+ for (let i = 0; i < limit + 1; i++) {
+ const tx = { id: i, time: new Date(), status: 'rejected', metamaskNetworkId: currentNetworkId, txParams: {} }
+ txStateManager.addTx(tx, noop)
+ }
+ let result = txStateManager.getTxList()
+ assert.equal(result.length, limit, `limit of ${limit} txs enforced`)
+ assert.equal(result[0].id, 1, 'early txs truncted')
+ })
+
+ it('cuts off early txs beyond a limit but does not cut unapproved txs', function () {
+ let unconfirmedTx = { id: 0, time: new Date(), status: 'unapproved', metamaskNetworkId: currentNetworkId, txParams: {} }
+ txStateManager.addTx(unconfirmedTx, noop)
+ const limit = txStateManager.txHistoryLimit
+ for (let i = 1; i < limit + 1; i++) {
+ const tx = { id: i, time: new Date(), status: 'confirmed', metamaskNetworkId: currentNetworkId, txParams: {} }
+ txStateManager.addTx(tx, noop)
+ }
+ let result = txStateManager.getTxList()
+ assert.equal(result.length, limit, `limit of ${limit} txs enforced`)
+ assert.equal(result[0].id, 0, 'first tx should still be there')
+ assert.equal(result[0].status, 'unapproved', 'first tx should be unapproved')
+ assert.equal(result[1].id, 2, 'early txs truncted')
+ })
+ })
+
+ describe('#updateTx', function () {
+ it('replaces the tx with the same id', function () {
+ txStateManager.addTx({ id: '1', status: 'unapproved', metamaskNetworkId: currentNetworkId, txParams: {} }, noop)
+ txStateManager.addTx({ id: '2', status: 'confirmed', metamaskNetworkId: currentNetworkId, txParams: {} }, noop)
+ const txMeta = txStateManager.getTx('1')
+ txMeta.hash = 'foo'
+ txStateManager.updateTx(txMeta)
+ let result = txStateManager.getTx('1')
+ assert.equal(result.hash, 'foo')
+ })
+
+ it('updates gas price and adds history items', function () {
+ const originalGasPrice = '0x01'
+ const desiredGasPrice = '0x02'
+
+ const txMeta = {
+ id: '1',
+ status: 'unapproved',
+ metamaskNetworkId: currentNetworkId,
+ txParams: {
+ gasPrice: originalGasPrice,
+ },
+ }
+
+ const updatedMeta = clone(txMeta)
+
+ txStateManager.addTx(txMeta)
+ const updatedTx = txStateManager.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
+ txStateManager.updateTx(updatedTx)
+ // check updated value
+ const result = txStateManager.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)')
+ })
+ })
+
+ describe('#getUnapprovedTxList', function () {
+ it('returns unapproved txs in a hash', function () {
+ txStateManager.addTx({ id: '1', status: 'unapproved', metamaskNetworkId: currentNetworkId, txParams: {} }, noop)
+ txStateManager.addTx({ id: '2', status: 'confirmed', metamaskNetworkId: currentNetworkId, txParams: {} }, noop)
+ const result = txStateManager.getUnapprovedTxList()
+ assert.equal(typeof result, 'object')
+ assert.equal(result['1'].status, 'unapproved')
+ assert.equal(result['2'], undefined)
+ })
+ })
+
+ describe('#getTx', function () {
+ it('returns a tx with the requested id', function () {
+ txStateManager.addTx({ id: '1', status: 'unapproved', metamaskNetworkId: currentNetworkId, txParams: {} }, noop)
+ txStateManager.addTx({ id: '2', status: 'confirmed', metamaskNetworkId: currentNetworkId, txParams: {} }, noop)
+ assert.equal(txStateManager.getTx('1').status, 'unapproved')
+ assert.equal(txStateManager.getTx('2').status, 'confirmed')
+ })
+ })
+
+ describe('#getFilteredTxList', function () {
+ it('returns a tx with the requested data', function () {
+ const txMetas = [
+ { id: 0, status: 'unapproved', txParams: { from: '0xaa', to: '0xbb' }, metamaskNetworkId: currentNetworkId },
+ { id: 1, status: 'unapproved', txParams: { from: '0xaa', to: '0xbb' }, metamaskNetworkId: currentNetworkId },
+ { id: 2, status: 'unapproved', txParams: { from: '0xaa', to: '0xbb' }, metamaskNetworkId: currentNetworkId },
+ { id: 3, status: 'unapproved', txParams: { from: '0xbb', to: '0xaa' }, metamaskNetworkId: currentNetworkId },
+ { id: 4, status: 'unapproved', txParams: { from: '0xbb', to: '0xaa' }, metamaskNetworkId: currentNetworkId },
+ { id: 5, status: 'confirmed', txParams: { from: '0xaa', to: '0xbb' }, metamaskNetworkId: currentNetworkId },
+ { id: 6, status: 'confirmed', txParams: { from: '0xaa', to: '0xbb' }, metamaskNetworkId: currentNetworkId },
+ { id: 7, status: 'confirmed', txParams: { from: '0xbb', to: '0xaa' }, metamaskNetworkId: currentNetworkId },
+ { id: 8, status: 'confirmed', txParams: { from: '0xbb', to: '0xaa' }, metamaskNetworkId: currentNetworkId },
+ { id: 9, status: 'confirmed', txParams: { from: '0xbb', to: '0xaa' }, metamaskNetworkId: currentNetworkId },
+ ]
+ txMetas.forEach((txMeta) => txStateManager.addTx(txMeta, noop))
+ let filterParams
+
+ filterParams = { status: 'unapproved', from: '0xaa' }
+ assert.equal(txStateManager.getFilteredTxList(filterParams).length, 3, `getFilteredTxList - ${JSON.stringify(filterParams)}`)
+ filterParams = { status: 'unapproved', to: '0xaa' }
+ assert.equal(txStateManager.getFilteredTxList(filterParams).length, 2, `getFilteredTxList - ${JSON.stringify(filterParams)}`)
+ filterParams = { status: 'confirmed', from: '0xbb' }
+ assert.equal(txStateManager.getFilteredTxList(filterParams).length, 3, `getFilteredTxList - ${JSON.stringify(filterParams)}`)
+ filterParams = { status: 'confirmed' }
+ assert.equal(txStateManager.getFilteredTxList(filterParams).length, 5, `getFilteredTxList - ${JSON.stringify(filterParams)}`)
+ filterParams = { from: '0xaa' }
+ assert.equal(txStateManager.getFilteredTxList(filterParams).length, 5, `getFilteredTxList - ${JSON.stringify(filterParams)}`)
+ filterParams = { to: '0xaa' }
+ assert.equal(txStateManager.getFilteredTxList(filterParams).length, 5, `getFilteredTxList - ${JSON.stringify(filterParams)}`)
+ })
+ })
+}) \ No newline at end of file
diff --git a/test/unit/tx-utils-test.js b/test/unit/tx-utils-test.js
index 43128b977..8ca13412e 100644
--- a/test/unit/tx-utils-test.js
+++ b/test/unit/tx-utils-test.js
@@ -1,8 +1,10 @@
const assert = require('assert')
+const Transaction = require('ethereumjs-tx')
const BN = require('bn.js')
+
const { hexToBn, bnToHex } = require('../../app/scripts/lib/util')
-const TxUtils = require('../../app/scripts/lib/tx-utils')
+const TxUtils = require('../../app/scripts/lib/tx-gas-utils')
describe('txUtils', function () {
@@ -28,7 +30,7 @@ describe('txUtils', function () {
nonce: '0x3',
chainId: 42,
}
- const ethTx = txUtils.buildEthTxFromParams(txParams)
+ const ethTx = new Transaction(txParams)
assert.equal(ethTx.getChainId(), 42, 'chainId is set from tx params')
})
})
diff --git a/ui-dev.js b/ui-dev.js
index de5dfd8ef..620d81667 100644
--- a/ui-dev.js
+++ b/ui-dev.js
@@ -61,30 +61,37 @@ const actions = {
var css = MetaMaskUiCss()
injectCss(css)
-const container = document.querySelector('#test-container')
-
// parse opts
var store = configureStore(states[selectedView])
// start app
-render(
- h('.super-dev-container', [
-
- h(Selector, { actions, selectedKey: selectedView, states, store }),
-
- h('#app-content', {
- style: {
- height: '500px',
- width: '360px',
- boxShadow: 'grey 0px 2px 9px',
- margin: '20px',
- },
- }, [
- h(Root, {
- store: store,
- }),
- ]),
-
- ]
-), container)
+startApp()
+
+function startApp(){
+ const body = document.body
+ const container = document.createElement('div')
+ container.id = 'test-container'
+ body.appendChild(container)
+
+ render(
+ h('.super-dev-container', [
+
+ h(Selector, { actions, selectedKey: selectedView, states, store }),
+
+ h('#app-content', {
+ style: {
+ height: '500px',
+ width: '360px',
+ boxShadow: 'grey 0px 2px 9px',
+ margin: '20px',
+ },
+ }, [
+ h(Root, {
+ store: store,
+ }),
+ ]),
+
+ ]
+ ), container)
+}
diff --git a/ui/app/actions.js b/ui/app/actions.js
index e793e6a21..3ea092e57 100644
--- a/ui/app/actions.js
+++ b/ui/app/actions.js
@@ -119,14 +119,11 @@ var actions = {
SET_RPC_TARGET: 'SET_RPC_TARGET',
SET_DEFAULT_RPC_TARGET: 'SET_DEFAULT_RPC_TARGET',
SET_PROVIDER_TYPE: 'SET_PROVIDER_TYPE',
- USE_ETHERSCAN_PROVIDER: 'USE_ETHERSCAN_PROVIDER',
- useEtherscanProvider: useEtherscanProvider,
showConfigPage,
SHOW_ADD_TOKEN_PAGE: 'SHOW_ADD_TOKEN_PAGE',
showAddTokenPage,
addToken,
setRpcTarget: setRpcTarget,
- setDefaultRpcTarget: setDefaultRpcTarget,
setProviderType: setProviderType,
// loading overlay
SHOW_LOADING: 'SHOW_LOADING_INDICATION',
@@ -706,16 +703,19 @@ function markAccountsFound () {
// config
//
-// default rpc target refers to localhost:8545 in this instance.
-function setDefaultRpcTarget () {
- log.debug(`background.setDefaultRpcTarget`)
+function setProviderType (type) {
return (dispatch) => {
- background.setDefaultRpc((err, result) => {
+ log.debug(`background.setProviderType`)
+ background.setProviderType(type, (err, result) => {
if (err) {
log.error(err)
- return dispatch(self.displayWarning('Had a problem changing networks.'))
+ return dispatch(self.displayWarning('Had a problem changing networks!'))
}
})
+ return {
+ type: actions.SET_PROVIDER_TYPE,
+ value: type,
+ }
}
}
@@ -744,23 +744,6 @@ function addToAddressBook (recipient, nickname) {
}
}
-function setProviderType (type) {
- log.debug(`background.setProviderType`)
- background.setProviderType(type)
- return {
- type: actions.SET_PROVIDER_TYPE,
- value: type,
- }
-}
-
-function useEtherscanProvider () {
- log.debug(`background.useEtherscanProvider`)
- background.useEtherscanProvider()
- return {
- type: actions.USE_ETHERSCAN_PROVIDER,
- }
-}
-
function showLoadingIndication (message) {
return {
type: actions.SHOW_LOADING,
diff --git a/ui/app/app.js b/ui/app/app.js
index ee800ea90..50121b055 100644
--- a/ui/app/app.js
+++ b/ui/app/app.js
@@ -319,7 +319,7 @@ App.prototype.renderNetworkDropdown = function () {
{
key: 'default',
closeMenu: () => this.setState({ isNetworkMenuOpen: !isOpen }),
- onClick: () => props.dispatch(actions.setDefaultRpcTarget()),
+ onClick: () => props.dispatch(actions.setProviderType('localhost')),
style: {
fontSize: '18px',
},
diff --git a/ui/app/components/fiat-value.js b/ui/app/components/fiat-value.js
index 8a64a1cfc..d69f41d11 100644
--- a/ui/app/components/fiat-value.js
+++ b/ui/app/components/fiat-value.js
@@ -13,6 +13,7 @@ function FiatValue () {
FiatValue.prototype.render = function () {
const props = this.props
const { conversionRate, currentCurrency } = props
+ const renderedCurrency = currentCurrency || ''
const value = formatBalance(props.value, 6)
@@ -28,7 +29,7 @@ FiatValue.prototype.render = function () {
fiatTooltipNumber = 'Unknown'
}
- return fiatDisplay(fiatDisplayNumber, currentCurrency)
+ return fiatDisplay(fiatDisplayNumber, renderedCurrency.toUpperCase())
}
function fiatDisplay (fiatDisplayNumber, fiatSuffix) {
diff --git a/ui/app/config.js b/ui/app/config.js
index d64088ccb..0fe232c07 100644
--- a/ui/app/config.js
+++ b/ui/app/config.js
@@ -3,7 +3,9 @@ const Component = require('react').Component
const h = require('react-hyperscript')
const connect = require('react-redux').connect
const actions = require('./actions')
-const currencies = require('./conversion.json').rows
+const infuraCurrencies = require('./infura-conversion.json').objects.sort((a, b) => {
+ return a.quote.name.toLocaleLowerCase().localeCompare(b.quote.name.toLocaleLowerCase())
+ })
const validUrl = require('valid-url')
const exportAsFile = require('./util').exportAsFile
@@ -167,8 +169,8 @@ function currentConversionInformation (metamaskState, state) {
state.dispatch(actions.setCurrentCurrency(newCurrency))
},
defaultValue: currentCurrency,
- }, currencies.map((currency) => {
- return h('option', {key: currency.code, value: currency.code}, `${currency.code} - ${currency.name}`)
+ }, infuraCurrencies.map((currency) => {
+ return h('option', {key: currency.quote.code, value: currency.quote.code}, `${currency.quote.code.toUpperCase()} - ${currency.quote.name}`)
})
),
])
diff --git a/ui/app/conversion.json b/ui/app/conversion.json
deleted file mode 100644
index 155ffc4fc..000000000
--- a/ui/app/conversion.json
+++ /dev/null
@@ -1,207 +0,0 @@
-{
- "rows": [
- {
- "code": "REP",
- "name": "Augur",
- "statuses": [
- "primary"
- ]
- },
- {
- "code": "BCN",
- "name": "Bytecoin",
- "statuses": [
- "primary"
- ]
- },
- {
- "code": "BTC",
- "name": "Bitcoin",
- "statuses": [
- "primary",
- "secondary"
- ]
- },
- {
- "code": "BTS",
- "name": "BitShares",
- "statuses": [
- "primary",
- "secondary"
- ]
- },
- {
- "code": "BLK",
- "name": "Blackcoin",
- "statuses": [
- "primary"
- ]
- },
- {
- "code": "GBP",
- "name": "British Pound Sterling",
- "statuses": [
- "secondary"
- ]
- },
- {
- "code": "CAD",
- "name": "Canadian Dollar",
- "statuses": [
- "secondary"
- ]
- },
- {
- "code": "CNY",
- "name": "Chinese Yuan",
- "statuses": [
- "secondary"
- ]
- },
- {
- "code": "DSH",
- "name": "Dashcoin",
- "statuses": [
- "primary"
- ]
- },
- {
- "code": "DOGE",
- "name": "Dogecoin",
- "statuses": [
- "primary",
- "secondary"
- ]
- },
- {
- "code": "ETC",
- "name": "Ethereum Classic",
- "statuses": [
- "primary"
- ]
- },
- {
- "code": "EUR",
- "name": "Euro",
- "statuses": [
- "primary",
- "secondary"
- ]
- },
- {
- "code": "GNO",
- "name": "GNO",
- "statuses": [
- "primary"
- ]
- },
- {
- "code": "GNT",
- "name": "GNT",
- "statuses": [
- "primary"
- ]
- },
- {
- "code": "JPY",
- "name": "Japanese Yen",
- "statuses": [
- "secondary"
- ]
- },
- {
- "code": "LTC",
- "name": "Litecoin",
- "statuses": [
- "primary",
- "secondary"
- ]
- },
- {
- "code": "MAID",
- "name": "MaidSafeCoin",
- "statuses": [
- "primary"
- ]
- },
- {
- "code": "XEM",
- "name": "NEM",
- "statuses": [
- "primary"
- ]
- },
- {
- "code": "XLM",
- "name": "Stellar",
- "statuses": [
- "primary"
- ]
- },
- {
- "code": "XMR",
- "name": "Monero",
- "statuses": [
- "primary",
- "secondary"
- ]
- },
- {
- "code": "XRP",
- "name": "Ripple",
- "statuses": [
- "primary"
- ]
- },
- {
- "code": "RUR",
- "name": "Ruble",
- "statuses": [
- "secondary"
- ]
- },
- {
- "code": "STEEM",
- "name": "Steem",
- "statuses": [
- "primary"
- ]
- },
- {
- "code": "STRAT",
- "name": "STRAT",
- "statuses": [
- "primary"
- ]
- },
- {
- "code": "UAH",
- "name": "Ukrainian Hryvnia",
- "statuses": [
- "secondary"
- ]
- },
- {
- "code": "USD",
- "name": "US Dollar",
- "statuses": [
- "primary",
- "secondary"
- ]
- },
- {
- "code": "WAVES",
- "name": "WAVES",
- "statuses": [
- "primary"
- ]
- },
- {
- "code": "ZEC",
- "name": "Zcash",
- "statuses": [
- "primary"
- ]
- }
- ]
-}
diff --git a/ui/app/infura-conversion.json b/ui/app/infura-conversion.json
new file mode 100644
index 000000000..9a96fe069
--- /dev/null
+++ b/ui/app/infura-conversion.json
@@ -0,0 +1,653 @@
+{
+ "objects": [
+ {
+ "symbol": "ethaud",
+ "base": {
+ "code": "eth",
+ "name": "Ethereum"
+ },
+ "quote": {
+ "code": "aud",
+ "name": "Australian Dollar"
+ }
+ },
+ {
+ "symbol": "ethhkd",
+ "base": {
+ "code": "eth",
+ "name": "Ethereum"
+ },
+ "quote": {
+ "code": "hkd",
+ "name": "Hong Kong Dollar"
+ }
+ },
+ {
+ "symbol": "ethsgd",
+ "base": {
+ "code": "eth",
+ "name": "Ethereum"
+ },
+ "quote": {
+ "code": "sgd",
+ "name": "Singapore Dollar"
+ }
+ },
+ {
+ "symbol": "ethidr",
+ "base": {
+ "code": "eth",
+ "name": "Ethereum"
+ },
+ "quote": {
+ "code": "idr",
+ "name": "Indonesian Rupiah"
+ }
+ },
+ {
+ "symbol": "ethphp",
+ "base": {
+ "code": "eth",
+ "name": "Ethereum"
+ },
+ "quote": {
+ "code": "php",
+ "name": "Philippine Peso"
+ }
+ },
+ {
+ "symbol": "eth1st",
+ "base": {
+ "code": "eth",
+ "name": "Ethereum"
+ },
+ "quote": {
+ "code": "1st",
+ "name": "FirstBlood"
+ }
+ },
+ {
+ "symbol": "ethadt",
+ "base": {
+ "code": "eth",
+ "name": "Ethereum"
+ },
+ "quote": {
+ "code": "adt",
+ "name": "adToken"
+ }
+ },
+ {
+ "symbol": "ethadx",
+ "base": {
+ "code": "eth",
+ "name": "Ethereum"
+ },
+ "quote": {
+ "code": "adx",
+ "name": "AdEx"
+ }
+ },
+ {
+ "symbol": "ethant",
+ "base": {
+ "code": "eth",
+ "name": "Ethereum"
+ },
+ "quote": {
+ "code": "ant",
+ "name": "Aragon"
+ }
+ },
+ {
+ "symbol": "ethbat",
+ "base": {
+ "code": "eth",
+ "name": "Ethereum"
+ },
+ "quote": {
+ "code": "bat",
+ "name": "Basic Attention Token"
+ }
+ },
+ {
+ "symbol": "ethbnt",
+ "base": {
+ "code": "eth",
+ "name": "Ethereum"
+ },
+ "quote": {
+ "code": "bnt",
+ "name": "Bancor"
+ }
+ },
+ {
+ "symbol": "ethbtc",
+ "base": {
+ "code": "eth",
+ "name": "Ethereum"
+ },
+ "quote": {
+ "code": "btc",
+ "name": "Bitcoin"
+ }
+ },
+ {
+ "symbol": "ethcad",
+ "base": {
+ "code": "eth",
+ "name": "Ethereum"
+ },
+ "quote": {
+ "code": "cad",
+ "name": "Canadian Dollar"
+ }
+ },
+ {
+ "symbol": "ethcfi",
+ "base": {
+ "code": "eth",
+ "name": "Ethereum"
+ },
+ "quote": {
+ "code": "cfi",
+ "name": "Cofound.it"
+ }
+ },
+ {
+ "symbol": "ethcrb",
+ "base": {
+ "code": "eth",
+ "name": "Ethereum"
+ },
+ "quote": {
+ "code": "crb",
+ "name": "CreditBit"
+ }
+ },
+ {
+ "symbol": "ethcvc",
+ "base": {
+ "code": "eth",
+ "name": "Ethereum"
+ },
+ "quote": {
+ "code": "cvc",
+ "name": "Civic"
+ }
+ },
+ {
+ "symbol": "ethdash",
+ "base": {
+ "code": "eth",
+ "name": "Ethereum"
+ },
+ "quote": {
+ "code": "dash",
+ "name": "Dash"
+ }
+ },
+ {
+ "symbol": "ethdgd",
+ "base": {
+ "code": "eth",
+ "name": "Ethereum"
+ },
+ "quote": {
+ "code": "dgd",
+ "name": "DigixDAO"
+ }
+ },
+ {
+ "symbol": "ethetc",
+ "base": {
+ "code": "eth",
+ "name": "Ethereum"
+ },
+ "quote": {
+ "code": "etc",
+ "name": "Ethereum Classic"
+ }
+ },
+ {
+ "symbol": "etheur",
+ "base": {
+ "code": "eth",
+ "name": "Ethereum"
+ },
+ "quote": {
+ "code": "eur",
+ "name": "Euro"
+ }
+ },
+ {
+ "symbol": "ethfun",
+ "base": {
+ "code": "eth",
+ "name": "Ethereum"
+ },
+ "quote": {
+ "code": "fun",
+ "name": "FunFair"
+ }
+ },
+ {
+ "symbol": "ethgbp",
+ "base": {
+ "code": "eth",
+ "name": "Ethereum"
+ },
+ "quote": {
+ "code": "gbp",
+ "name": "Pound Sterling"
+ }
+ },
+ {
+ "symbol": "ethgno",
+ "base": {
+ "code": "eth",
+ "name": "Ethereum"
+ },
+ "quote": {
+ "code": "gno",
+ "name": "Gnosis"
+ }
+ },
+ {
+ "symbol": "ethgnt",
+ "base": {
+ "code": "eth",
+ "name": "Ethereum"
+ },
+ "quote": {
+ "code": "gnt",
+ "name": "Golem"
+ }
+ },
+ {
+ "symbol": "ethgup",
+ "base": {
+ "code": "eth",
+ "name": "Ethereum"
+ },
+ "quote": {
+ "code": "gup",
+ "name": "Matchpool"
+ }
+ },
+ {
+ "symbol": "ethhmq",
+ "base": {
+ "code": "eth",
+ "name": "Ethereum"
+ },
+ "quote": {
+ "code": "hmq",
+ "name": "Humaniq"
+ }
+ },
+ {
+ "symbol": "ethjpy",
+ "base": {
+ "code": "eth",
+ "name": "Ethereum"
+ },
+ "quote": {
+ "code": "jpy",
+ "name": "Japanese Yen"
+ }
+ },
+ {
+ "symbol": "ethlgd",
+ "base": {
+ "code": "eth",
+ "name": "Ethereum"
+ },
+ "quote": {
+ "code": "lgd",
+ "name": "Legends Room"
+ }
+ },
+ {
+ "symbol": "ethlsk",
+ "base": {
+ "code": "eth",
+ "name": "Ethereum"
+ },
+ "quote": {
+ "code": "lsk",
+ "name": "Lisk"
+ }
+ },
+ {
+ "symbol": "ethltc",
+ "base": {
+ "code": "eth",
+ "name": "Ethereum"
+ },
+ "quote": {
+ "code": "ltc",
+ "name": "Litecoin"
+ }
+ },
+ {
+ "symbol": "ethlun",
+ "base": {
+ "code": "eth",
+ "name": "Ethereum"
+ },
+ "quote": {
+ "code": "lun",
+ "name": "Lunyr"
+ }
+ },
+ {
+ "symbol": "ethmco",
+ "base": {
+ "code": "eth",
+ "name": "Ethereum"
+ },
+ "quote": {
+ "code": "mco",
+ "name": "Monaco"
+ }
+ },
+ {
+ "symbol": "ethmtl",
+ "base": {
+ "code": "eth",
+ "name": "Ethereum"
+ },
+ "quote": {
+ "code": "mtl",
+ "name": "Metal"
+ }
+ },
+ {
+ "symbol": "ethmyst",
+ "base": {
+ "code": "eth",
+ "name": "Ethereum"
+ },
+ "quote": {
+ "code": "myst",
+ "name": "Mysterium"
+ }
+ },
+ {
+ "symbol": "ethnmr",
+ "base": {
+ "code": "eth",
+ "name": "Ethereum"
+ },
+ "quote": {
+ "code": "nmr",
+ "name": "Numeraire"
+ }
+ },
+ {
+ "symbol": "ethomg",
+ "base": {
+ "code": "eth",
+ "name": "Ethereum"
+ },
+ "quote": {
+ "code": "omg",
+ "name": "OmiseGO"
+ }
+ },
+ {
+ "symbol": "ethpay",
+ "base": {
+ "code": "eth",
+ "name": "Ethereum"
+ },
+ "quote": {
+ "code": "pay",
+ "name": "TenX"
+ }
+ },
+ {
+ "symbol": "ethptoy",
+ "base": {
+ "code": "eth",
+ "name": "Ethereum"
+ },
+ "quote": {
+ "code": "ptoy",
+ "name": "Patientory"
+ }
+ },
+ {
+ "symbol": "ethqrl",
+ "base": {
+ "code": "eth",
+ "name": "Ethereum"
+ },
+ "quote": {
+ "code": "qrl",
+ "name": "Quantum-Resistant Ledger"
+ }
+ },
+ {
+ "symbol": "ethqtum",
+ "base": {
+ "code": "eth",
+ "name": "Ethereum"
+ },
+ "quote": {
+ "code": "qtum",
+ "name": "Qtum"
+ }
+ },
+ {
+ "symbol": "ethrep",
+ "base": {
+ "code": "eth",
+ "name": "Ethereum"
+ },
+ "quote": {
+ "code": "rep",
+ "name": "Augur"
+ }
+ },
+ {
+ "symbol": "ethrlc",
+ "base": {
+ "code": "eth",
+ "name": "Ethereum"
+ },
+ "quote": {
+ "code": "rlc",
+ "name": "iEx.ec"
+ }
+ },
+ {
+ "symbol": "ethrub",
+ "base": {
+ "code": "eth",
+ "name": "Ethereum"
+ },
+ "quote": {
+ "code": "rub",
+ "name": "Russian Ruble"
+ }
+ },
+ {
+ "symbol": "ethsc",
+ "base": {
+ "code": "eth",
+ "name": "Ethereum"
+ },
+ "quote": {
+ "code": "sc",
+ "name": "Siacoin"
+ }
+ },
+ {
+ "symbol": "ethsngls",
+ "base": {
+ "code": "eth",
+ "name": "Ethereum"
+ },
+ "quote": {
+ "code": "sngls",
+ "name": "SingularDTV"
+ }
+ },
+ {
+ "symbol": "ethsnt",
+ "base": {
+ "code": "eth",
+ "name": "Ethereum"
+ },
+ "quote": {
+ "code": "snt",
+ "name": "Status"
+ }
+ },
+ {
+ "symbol": "ethsteem",
+ "base": {
+ "code": "eth",
+ "name": "Ethereum"
+ },
+ "quote": {
+ "code": "steem",
+ "name": "Steem"
+ }
+ },
+ {
+ "symbol": "ethstorj",
+ "base": {
+ "code": "eth",
+ "name": "Ethereum"
+ },
+ "quote": {
+ "code": "storj",
+ "name": "Storj"
+ }
+ },
+ {
+ "symbol": "ethtime",
+ "base": {
+ "code": "eth",
+ "name": "Ethereum"
+ },
+ "quote": {
+ "code": "time",
+ "name": "ChronoBank"
+ }
+ },
+ {
+ "symbol": "ethtkn",
+ "base": {
+ "code": "eth",
+ "name": "Ethereum"
+ },
+ "quote": {
+ "code": "tkn",
+ "name": "TokenCard"
+ }
+ },
+ {
+ "symbol": "ethtrst",
+ "base": {
+ "code": "eth",
+ "name": "Ethereum"
+ },
+ "quote": {
+ "code": "trst",
+ "name": "WeTrust"
+ }
+ },
+ {
+ "symbol": "ethuah",
+ "base": {
+ "code": "eth",
+ "name": "Ethereum"
+ },
+ "quote": {
+ "code": "uah",
+ "name": "Ukrainian Hryvnia"
+ }
+ },
+ {
+ "symbol": "ethusd",
+ "base": {
+ "code": "eth",
+ "name": "Ethereum"
+ },
+ "quote": {
+ "code": "usd",
+ "name": "United States Dollar"
+ }
+ },
+ {
+ "symbol": "ethwings",
+ "base": {
+ "code": "eth",
+ "name": "Ethereum"
+ },
+ "quote": {
+ "code": "wings",
+ "name": "Wings"
+ }
+ },
+ {
+ "symbol": "ethxem",
+ "base": {
+ "code": "eth",
+ "name": "Ethereum"
+ },
+ "quote": {
+ "code": "xem",
+ "name": "NEM"
+ }
+ },
+ {
+ "symbol": "ethxlm",
+ "base": {
+ "code": "eth",
+ "name": "Ethereum"
+ },
+ "quote": {
+ "code": "xlm",
+ "name": "Stellar Lumen"
+ }
+ },
+ {
+ "symbol": "ethxmr",
+ "base": {
+ "code": "eth",
+ "name": "Ethereum"
+ },
+ "quote": {
+ "code": "xmr",
+ "name": "Monero"
+ }
+ },
+ {
+ "symbol": "ethxrp",
+ "base": {
+ "code": "eth",
+ "name": "Ethereum"
+ },
+ "quote": {
+ "code": "xrp",
+ "name": "Ripple"
+ }
+ },
+ {
+ "symbol": "ethzec",
+ "base": {
+ "code": "eth",
+ "name": "Ethereum"
+ },
+ "quote": {
+ "code": "zec",
+ "name": "Zcash"
+ }
+ }
+ ]
+}