diff --git a/app/scripts/controllers/transactions/index.js b/app/scripts/controllers/transactions/index.js index ca6c3923e..ca83941fc 100644 --- a/app/scripts/controllers/transactions/index.js +++ b/app/scripts/controllers/transactions/index.js @@ -8,22 +8,37 @@ const TxGasUtil = require('./tx-gas-utils') const PendingTransactionTracker = require('./pending-tx-tracker') const NonceTracker = require('./nonce-tracker') const txUtils = require('./lib/util') -/* + +module.exports = TransactionController + +/** Transaction Controller is an aggregate of sub-controllers and trackers composing them in a way to be exposed to the metamask controller - - txStateManager +
- txStateManager responsible for the state of a transaction and storing the transaction - - pendingTxTracker +
- pendingTxTracker watching blocks for transactions to be include and emitting confirmed events - - txGasUtil +
- txGasUtil gas calculations and safety buffering - - nonceTracker +
- nonceTracker calculating nonces + + +@param {object} opts - + - initState, initial transaction list default is an empty array
+ - networkStore, an observable store for network number
+ - blockTracker,
+ - provider,
+ - signTransaction, function the signs an ethereumjs-tx
+ - getGasPrice, optional gas price calculator
+ - txHistoryLimit, number *optional* for limiting how many transactions are in state
+ - preferencesStore, +@class */ -module.exports = class TransactionController extends EventEmitter { +class TransactionController extends EventEmitter { constructor (opts) { super() this.networkStore = opts.networkStore || new ObservableStore({}) @@ -42,7 +57,7 @@ module.exports = class TransactionController extends EventEmitter { txHistoryLimit: opts.txHistoryLimit, getNetwork: this.getNetwork.bind(this), }) - + this._mapMethods() this._onBootCleanUp() this.store = this.txStateManager.store @@ -68,31 +83,7 @@ module.exports = class TransactionController extends EventEmitter { this.networkStore.subscribe(() => this._updateMemstore()) this.preferencesStore.subscribe(() => this._updateMemstore()) } - - getState () { - return this.memStore.getState() - } - - getNetwork () { - return this.networkStore.getState() - } - - getSelectedAddress () { - return this.preferencesStore.getState().selectedAddress - } - - getUnapprovedTxCount () { - return Object.keys(this.txStateManager.getUnapprovedTxList()).length - } - - getPendingTxCount (account) { - return this.txStateManager.getPendingTransactions(account).length - } - - getFilteredTxList (opts) { - return this.txStateManager.getFilteredTxList(opts) - } - + /** @returns {number} the chainId*/ getChainId () { const networkState = this.networkStore.getState() const getChainId = parseInt(networkState) @@ -103,16 +94,27 @@ module.exports = class TransactionController extends EventEmitter { } } -// Adds a tx to the txlist +/** Adds a tx to the txlist */ addTx (txMeta) { this.txStateManager.addTx(txMeta) this.emit(`${txMeta.id}:unapproved`, txMeta) } +/** + wipes the transactions for a given account + @param address {string} - hex string of the from address for txs being removed +*/ wipeTransactions (address) { this.txStateManager.wipeTransactions(address) } +/** +add a new unapproved transaction to the pipeline +@returns {promise} +@param txParams {object} - txParams for the transaction +@param opts {object} - with the key origin to put the origin on the txMeta + +*/ async newUnapprovedTransaction (txParams, opts = {}) { log.debug(`MetaMaskController newUnapprovedTransaction ${JSON.stringify(txParams)}`) const initialTxMeta = await this.addUnapprovedTransaction(txParams) @@ -135,6 +137,13 @@ module.exports = class TransactionController extends EventEmitter { }) } + /** + validates and generates a txMeta with defaults and puts it in txStateManager + store + + @returns {txMeta} + */ + async addUnapprovedTransaction (txParams) { // validate const normalizedTxParams = txUtils.normalizeTxParams(txParams) @@ -145,7 +154,7 @@ module.exports = class TransactionController extends EventEmitter { this.emit('newUnapprovedTx', txMeta) // add default tx params try { - txMeta = await this.addTxDefaults(txMeta) + txMeta = await this.addTxGasDefaults(txMeta) } catch (error) { console.log(error) this.txStateManager.setTxStatusFailed(txMeta.id, error) @@ -157,8 +166,12 @@ module.exports = class TransactionController extends EventEmitter { return txMeta } - - async addTxDefaults (txMeta) { +/** + adds the tx gas defaults: gas && gasPrice + @param txMeta {object} - the txMeta object + @returns {promise} resolves with txMeta +*/ + async addTxGasDefaults (txMeta) { const txParams = txMeta.txParams // ensure value txMeta.gasPriceSpecified = Boolean(txParams.gasPrice) @@ -167,11 +180,18 @@ module.exports = class TransactionController extends EventEmitter { gasPrice = this.getGasPrice ? this.getGasPrice() : await this.query.gasPrice() } txParams.gasPrice = ethUtil.addHexPrefix(gasPrice.toString(16)) - txParams.value = txParams.value || '0x0' // set gasLimit return await this.txGasUtil.analyzeGasUsage(txMeta) } + /** + creates a new txMeta with the same txParams as the original + to allow the user to resign the transaction with a higher gas values + @param originalTxId {number} - the id of the txMeta that + you want to attempt to retry + @return {txMeta} + */ + async retryTransaction (originalTxId) { const originalTxMeta = this.txStateManager.getTx(originalTxId) const lastGasPrice = originalTxMeta.txParams.gasPrice @@ -185,15 +205,31 @@ module.exports = class TransactionController extends EventEmitter { return txMeta } + /** + updates the txMeta in the txStateManager + @param txMeta {object} - the updated txMeta + */ async updateTransaction (txMeta) { this.txStateManager.updateTx(txMeta, 'confTx: user updated transaction') } + /** + updates and approves the transaction + @param txMeta {object} + */ async updateAndApproveTransaction (txMeta) { this.txStateManager.updateTx(txMeta, 'confTx: user approved transaction') await this.approveTransaction(txMeta.id) } + /** + sets the tx status to approved + auto fills the nonce + signs the transaction + publishes the transaction + if any of these steps fails the tx status will be set to failed + @param txId {number} - the tx's Id + */ async approveTransaction (txId) { let nonceLock try { @@ -225,7 +261,11 @@ module.exports = class TransactionController extends EventEmitter { throw err } } - + /** + adds the chain id and signs the transaction and set the status to signed + @param txId {number} - the tx's Id + @returns - rawTx {string} + */ async signTransaction (txId) { const txMeta = this.txStateManager.getTx(txId) // add network/chain id @@ -241,6 +281,11 @@ module.exports = class TransactionController extends EventEmitter { return rawTx } + /** + publishes the raw tx and sets the txMeta to submitted + @param txId {number} - the tx's Id + @param rawTx {string} - the hex string of the serialized signed transaction + */ async publishTransaction (txId, rawTx) { const txMeta = this.txStateManager.getTx(txId) txMeta.rawTx = rawTx @@ -250,11 +295,19 @@ module.exports = class TransactionController extends EventEmitter { this.txStateManager.setTxStatusSubmitted(txId) } + /** + convenience method for the ui thats sets the transaction to rejected + @param txId {number} - the tx's Id + */ async cancelTransaction (txId) { this.txStateManager.setTxStatusRejected(txId) } - // receives a txHash records the tx as signed + /** + sets the txHas on the txMeta + @param txId {number} - the tx's Id + @param txHash {string} - the hash for the txMeta + */ setTxHash (txId, txHash) { // Add the tx hash to the persisted meta-tx object const txMeta = this.txStateManager.getTx(txId) @@ -265,13 +318,28 @@ module.exports = class TransactionController extends EventEmitter { // // PRIVATE METHODS // + /** maps methods for convenience*/ + _mapMethods () { + /** Returns the state in transaction controller */ + this.getState = () => this.memStore.getState() + /** Returns the network number stored in networkStore */ + this.getNetwork = () => this.networkStore.getState() + /** Returns the user selected address */ + this.getSelectedAddress = () => this.preferencesStore.getState().selectedAddress + /** Returns an array of transactions whos status is unapproved */ + this.getUnapprovedTxCount = () => Object.keys(this.txStateManager.getUnapprovedTxList()).length + /** Returns a number that represents how many transactions have the status submitted*/ + this.getPendingTxCount = (account) => this.txStateManager.getPendingTransactions(account).length + /** see txStateManager */ + this.getFilteredTxList = (opts) => this.txStateManager.getFilteredTxList(opts) + } _onBootCleanUp () { this.txStateManager.getFilteredTxList({ status: 'unapproved', loadingDefaults: true, }).forEach((tx) => { - this.addTxDefaults(tx) + this.addTxGasDefaults(tx) .then((txMeta) => { txMeta.loadingDefaults = false this.txStateManager.updateTx(txMeta, 'transactions: gas estimation for tx on boot') @@ -339,4 +407,4 @@ module.exports = class TransactionController extends EventEmitter { }) this.memStore.updateState({ unapprovedTxs, selectedAddressTxList }) } -} +} \ No newline at end of file diff --git a/test/unit/tx-controller-test.js b/test/unit/tx-controller-test.js index 08f16d83b..20d6f8573 100644 --- a/test/unit/tx-controller-test.js +++ b/test/unit/tx-controller-test.js @@ -188,7 +188,7 @@ describe('Transaction Controller', function () { }) - describe('#addTxDefaults', function () { + describe('#addTxGasDefaults', function () { it('should add the tx defaults if their are none', function (done) { const txMeta = { 'txParams': { @@ -199,7 +199,7 @@ describe('Transaction Controller', function () { providerResultStub.eth_gasPrice = '4a817c800' providerResultStub.eth_getBlockByNumber = { gasLimit: '47b784' } providerResultStub.eth_estimateGas = '5209' - txController.addTxDefaults(txMeta) + txController.addTxGasDefaults(txMeta) .then((txMetaWithDefaults) => { assert(txMetaWithDefaults.txParams.value, '0x0', 'should have added 0x0 as the value') assert(txMetaWithDefaults.txParams.gasPrice, 'should have added the gas price')