aboutsummaryrefslogtreecommitdiffstats
path: root/app/scripts
diff options
context:
space:
mode:
Diffstat (limited to 'app/scripts')
-rw-r--r--app/scripts/background.js2
-rw-r--r--app/scripts/controllers/balance.js15
-rw-r--r--app/scripts/controllers/preferences.js2
-rw-r--r--app/scripts/controllers/transactions.js367
-rw-r--r--app/scripts/keyring-controller.js596
-rw-r--r--app/scripts/lib/account-tracker.js6
-rw-r--r--app/scripts/lib/pending-tx-tracker.js28
-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-manager.js245
9 files changed, 362 insertions, 921 deletions
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/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/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 4cd307b07..4f5c94675 100644
--- a/app/scripts/controllers/transactions.js
+++ b/app/scripts/controllers/transactions.js
@@ -1,74 +1,85 @@
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,
+ retryLimit: 3500, // Retry 3500 blocks, or about 1 day.
getBalance: (address) => {
- const account = this.accountTracker.getState().accounts[address]
+ const account = this.accountTracker.store.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,
- })
- },
+ 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', this.txStateManager.updateTx.bind(this.txStateManager))
+ this.pendingTxTracker.on('tx:failed', this.txStateManager.setTxStatusFailed.bind(this.txStateManager))
+ this.pendingTxTracker.on('tx:confirmed', this.txStateManager.setTxStatusConfirmed.bind(this.txStateManager))
+ this.pendingTxTracker.on('tx:retry', (txMeta) => {
+ if (!('retryCount' in txMeta)) txMeta.retryCount = 0
+ txMeta.retryCount++
+ this.txStateManager.updateTx(txMeta)
+ })
this.blockTracker.on('block', this.pendingTxTracker.checkForTxInBlock.bind(this.pendingTxTracker))
// this is a little messy but until ethstore has been either
@@ -80,7 +91,7 @@ module.exports = class TransactionController extends EventEmitter {
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 +108,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
- }
-
- getPendingTxCount () {
- return this.getTxsByMetaData('status', 'signed').length
+ return Object.keys(this.txStateManager.getUnapprovedTxList()).length
}
- // Returns the tx list
- getTxList () {
- const network = this.getNetwork()
- const fullTxList = this.getFullTxList()
- return this.getTxsByMetaData('metamaskNetworkId', network, fullTxList)
+ getPendingTxCount (account) {
+ return this.txStateManager.getPendingTransactions(account).length
}
- // 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 +142,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 +157,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 +176,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.value = txParams.value || '0x0'
- if (!txParams.gasPrice) {
- const gasPrice = await this.query.gasPrice()
- txParams.gasPrice = gasPrice
- }
+ txParams.gasPrice = ethUtil.addHexPrefix(gasPrice.toString(16))
// set gasLimit
- return await this.txProviderUtil.analyzeGasUsage(txMeta)
+ return await this.txGasUtil.analyzeGasUsage(txMeta)
}
async updateAndApproveTransaction (txMeta) {
- this.updateTx(txMeta)
+ this.txStateManager.updateTx(txMeta)
await this.approveTransaction(txMeta.id)
}
@@ -250,24 +192,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)
// 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 +218,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)
+ 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)
- }
-
- /*
- 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
+ this.txStateManager.updateTx(txMeta)
}
- // 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/pending-tx-tracker.js b/app/scripts/lib/pending-tx-tracker.js
index 44e9d50fa..b97cec9ce 100644
--- a/app/scripts/lib/pending-tx-tracker.js
+++ b/app/scripts/lib/pending-tx-tracker.js
@@ -1,7 +1,6 @@
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
@@ -25,11 +24,10 @@ module.exports = class PendingTransactionTracker extends EventEmitter {
super()
this.query = new EthQuery(config.provider)
this.nonceTracker = config.nonceTracker
-
+ this.retryLimit = config.retryLimit || Infinity
this.getBalance = config.getBalance
this.getPendingTransactions = config.getPendingTransactions
this.publishTransaction = config.publishTransaction
- this.giveUpOnTransaction = config.giveUpOnTransaction
}
// checks if a signed tx is in a block and
@@ -44,18 +42,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,7 +94,7 @@ 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)
}))
}
@@ -104,16 +102,16 @@ module.exports = class PendingTransactionTracker extends EventEmitter {
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 (txMeta.retryCount > this.retryLimit) {
+ const err = new Error(`Gave up submitting after ${this.retryLimit} blocks un-mined.`)
+ return this.emit('tx:failed', txMeta.id, err)
}
// if the value of the transaction is greater then the balance, fail.
if (!sufficientBalance(txMeta.txParams, balance)) {
const insufficientFundsError = new Error('Insufficient balance during rebroadcast.')
- this.emit('txFailed', txMeta.id, insufficientFundsError)
+ this.emit('tx:failed', txMeta.id, insufficientFundsError)
log.error(insufficientFundsError)
return
}
@@ -125,7 +123,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 +135,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 +144,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-manager.js b/app/scripts/lib/tx-state-manager.js
new file mode 100644
index 000000000..abb9d7910
--- /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) {
+ 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)
+ 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)
+ }
+
+/*
+ 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)
+ 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