1
0
mirror of https://github.com/kremalicious/metamask-extension.git synced 2024-11-22 09:57:02 +01:00

meta - transactions - docs yo!

This commit is contained in:
frankiebee 2018-04-19 11:29:26 -07:00
parent 943eea043c
commit eeb9390de8
9 changed files with 351 additions and 73 deletions

View File

@ -0,0 +1,92 @@
# Transaction Controller
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
## flow digram of processing a transaction
![transaction-flow](../../../../docs/transaction-flow.png)
## txMeta's && txParams
A txMeta is the "meta" object it has all the random bits of info we need about a transaction on it. txParams are sacred every thing on txParams gets signed so it must
be a valid key and be hex prefixed except for the network number. Extra stuff must go on the txMeta!
Here is a txMeta too look at:
```js
txMeta = {
"id": 2828415030114568, // unique id for this txMeta used for look ups
"time": 1524094064821, // time of creation
"status": "confirmed",
"metamaskNetworkId": "1524091532133", //the network id for the transaction
"loadingDefaults": false, // used to tell the ui when we are done calculatyig gass defaults
"txParams": { // the txParams object
"from": "0x8acce2391c0d510a6c5e5d8f819a678f79b7e675",
"to": "0x8acce2391c0d510a6c5e5d8f819a678f79b7e675",
"value": "0x0",
"gasPrice": "0x3b9aca00",
"gas": "0x7b0c",
"nonce": "0x0"
},
"history": [{ //debug
"id": 2828415030114568,
"time": 1524094064821,
"status": "unapproved",
"metamaskNetworkId": "1524091532133",
"loadingDefaults": true,
"txParams": {
"from": "0x8acce2391c0d510a6c5e5d8f819a678f79b7e675",
"to": "0x8acce2391c0d510a6c5e5d8f819a678f79b7e675",
"value": "0x0"
}
},
[
{
"op": "add",
"path": "/txParams/gasPrice",
"value": "0x3b9aca00"
},
...], // I've removed most of history for this
"gasPriceSpecified": false, //weather or not the user/dapp has specified gasPrice
"gasLimitSpecified": false, //weather or not the user/dapp has specified gas
"estimatedGas": "5208",
"origin": "MetaMask", //debug
"nonceDetails": {
"params": {
"highestLocallyConfirmed": 0,
"highestSuggested": 0,
"nextNetworkNonce": 0
},
"local": {
"name": "local",
"nonce": 0,
"details": {
"startPoint": 0,
"highest": 0
}
},
"network": {
"name": "network",
"nonce": 0,
"details": {
"baseCount": 0
}
}
},
"rawTx": "0xf86980843b9aca00827b0c948acce2391c0d510a6c5e5d8f819a678f79b7e67580808602c5b5de66eea05c01a320b96ac730cb210ca56d2cb71fa360e1fc2c21fa5cf333687d18eb323fa02ed05987a6e5fd0f2459fcff80710b76b83b296454ad9a37594a0ccb4643ea90", // used for rebroadcast
"hash": "0xa45ba834b97c15e6ff4ed09badd04ecd5ce884b455eb60192cdc73bcc583972a",
"submittedTime": 1524094077902 // time of the attempt to submit the raw tx to the network, used in the ui to show the retry button
}
```

View File

@ -25,14 +25,15 @@ const txUtils = require('./lib/util')
@param {object} opts - @param {object} opts -
- initState, initial transaction list default is an empty array<br> @property {object} opts.initState initial transaction list default is an empty array
- networkStore, an observable store for network number<br> @property {Object} opts.networkStore an observable store for network number
- blockTracker,<br> @property {Object} opts.blockTracker
- provider,<br> @property {Object} opts.provider
- signTransaction, function the signs an ethereumjs-tx<br> @property {Object} opts.signTransaction function the signs an ethereumjs-tx
- getGasPrice, optional gas price calculator<br> @property {function} opts.getGasPrice optional gas price calculator
- txHistoryLimit, number *optional* for limiting how many transactions are in state <br> @property {function} opts.signTransaction ethTx signer that returns a rawTx
- preferencesStore, @property {number} opts.txHistoryLimit number *optional* for limiting how many transactions are in state
@property {Object} opts.preferencesStore
@class @class
*/ */
@ -50,12 +51,12 @@ class TransactionController extends EventEmitter {
this.query = new EthQuery(this.provider) this.query = new EthQuery(this.provider)
this.txGasUtil = new TxGasUtil(this.provider) this.txGasUtil = new TxGasUtil(this.provider)
this._mapMethods()
this.txStateManager = new TransactionStateManager({ this.txStateManager = new TransactionStateManager({
initState: opts.initState, initState: opts.initState,
txHistoryLimit: opts.txHistoryLimit, txHistoryLimit: opts.txHistoryLimit,
getNetwork: this.getNetwork.bind(this), getNetwork: this.getNetwork.bind(this),
}) })
this._mapMethods()
this._onBootCleanUp() this._onBootCleanUp()
this.store = this.txStateManager.store this.store = this.txStateManager.store
@ -92,7 +93,10 @@ class TransactionController extends EventEmitter {
} }
} }
/** Adds a tx to the txlist */ /**
Adds a tx to the txlist
@emits ${txMeta.id}:unapproved
*/
addTx (txMeta) { addTx (txMeta) {
this.txStateManager.addTx(txMeta) this.txStateManager.addTx(txMeta)
this.emit(`${txMeta.id}:unapproved`, txMeta) this.emit(`${txMeta.id}:unapproved`, txMeta)
@ -172,6 +176,7 @@ add a new unapproved transaction to the pipeline
async addTxGasDefaults (txMeta) { async addTxGasDefaults (txMeta) {
const txParams = txMeta.txParams const txParams = txMeta.txParams
// ensure value // ensure value
txParams.value = txParams.value ? ethUtil.addHexPrefix(value) : '0x0',
txMeta.gasPriceSpecified = Boolean(txParams.gasPrice) txMeta.gasPriceSpecified = Boolean(txParams.gasPrice)
let gasPrice = txParams.gasPrice let gasPrice = txParams.gasPrice
if (!gasPrice) { if (!gasPrice) {
@ -412,4 +417,4 @@ add a new unapproved transaction to the pipeline
} }
} }
module.exports = TransactionController module.exports = TransactionController

View File

@ -1,6 +1,6 @@
const jsonDiffer = require('fast-json-patch') const jsonDiffer = require('fast-json-patch')
const clone = require('clone') const clone = require('clone')
/** @module*/
module.exports = { module.exports = {
generateHistoryEntry, generateHistoryEntry,
replayHistory, replayHistory,
@ -8,7 +8,11 @@ module.exports = {
migrateFromSnapshotsToDiffs, migrateFromSnapshotsToDiffs,
} }
/**
converts non-initial history entries into diffs
@param longHistory {array}
@returns {array}
*/
function migrateFromSnapshotsToDiffs (longHistory) { function migrateFromSnapshotsToDiffs (longHistory) {
return ( return (
longHistory longHistory
@ -20,6 +24,17 @@ function migrateFromSnapshotsToDiffs (longHistory) {
) )
} }
/**
generates an array of history objects sense the previous state.
The object has the keys opp(the operation preformed),
path(the key and if a nested object then each key will be seperated with a `/`)
value
with the first entry having the note
@param previousState {object} - the previous state of the object
@param newState {object} - the update object
@param note {string} - a optional note for the state change
@reurns {array}
*/
function generateHistoryEntry (previousState, newState, note) { function generateHistoryEntry (previousState, newState, note) {
const entry = jsonDiffer.compare(previousState, newState) const entry = jsonDiffer.compare(previousState, newState)
// Add a note to the first op, since it breaks if we append it to the entry // Add a note to the first op, since it breaks if we append it to the entry
@ -27,11 +42,19 @@ function generateHistoryEntry (previousState, newState, note) {
return entry return entry
} }
/**
Recovers previous txMeta state obj
@return {object}
*/
function replayHistory (_shortHistory) { function replayHistory (_shortHistory) {
const shortHistory = clone(_shortHistory) const shortHistory = clone(_shortHistory)
return shortHistory.reduce((val, entry) => jsonDiffer.applyPatch(val, entry).newDocument) return shortHistory.reduce((val, entry) => jsonDiffer.applyPatch(val, entry).newDocument)
} }
/**
@param txMeta {object}
@returns {object} a clone object of the txMeta with out history
*/
function snapshotFromTxMeta (txMeta) { function snapshotFromTxMeta (txMeta) {
// create txMeta snapshot for history // create txMeta snapshot for history
const snapshot = clone(txMeta) const snapshot = clone(txMeta)

View File

@ -3,11 +3,15 @@ const {
isValidAddress, isValidAddress,
} = require('ethereumjs-util') } = require('ethereumjs-util')
/**
@module
*/
module.exports = { module.exports = {
normalizeTxParams, normalizeTxParams,
validateTxParams, validateTxParams,
validateFrom, validateFrom,
validateRecipient, validateRecipient,
getFinalStates,
} }
@ -16,22 +20,30 @@ const normalizers = {
from: from => addHexPrefix(from).toLowerCase(), from: from => addHexPrefix(from).toLowerCase(),
to: to => addHexPrefix(to).toLowerCase(), to: to => addHexPrefix(to).toLowerCase(),
nonce: nonce => addHexPrefix(nonce), nonce: nonce => addHexPrefix(nonce),
value: value => value ? addHexPrefix(value) : '0x0', value: value => addHexPrefix(value),
data: data => addHexPrefix(data), data: data => addHexPrefix(data),
gas: gas => addHexPrefix(gas), gas: gas => addHexPrefix(gas),
gasPrice: gasPrice => addHexPrefix(gasPrice), gasPrice: gasPrice => addHexPrefix(gasPrice),
} }
/** /**
normalizes txParams
@param txParams {object}
@returns {object} normalized txParams
*/ */
function normalizeTxParams (txParams) { function normalizeTxParams (txParams) {
// apply only keys in the normalizers // apply only keys in the normalizers
const normalizedTxParams = {} const normalizedTxParams = {}
for (let key in normalizers) { for (const key in normalizers) {
if (txParams[key]) normalizedTxParams[key] = normalizers[key](txParams[key]) if (txParams[key]) normalizedTxParams[key] = normalizers[key](txParams[key])
} }
return normalizedTxParams return normalizedTxParams
} }
/**
validates txParams
@param txParams {object}
*/
function validateTxParams (txParams) { function validateTxParams (txParams) {
validateFrom(txParams) validateFrom(txParams)
validateRecipient(txParams) validateRecipient(txParams)
@ -47,11 +59,19 @@ function validateTxParams (txParams) {
} }
} }
/**
validates the from field in txParams
@param txParams {object}
*/
function validateFrom (txParams) { function validateFrom (txParams) {
if (!(typeof txParams.from === 'string')) throw new Error(`Invalid from address ${txParams.from} not a string`) if (!(typeof txParams.from === 'string')) throw new Error(`Invalid from address ${txParams.from} not a string`)
if (!isValidAddress(txParams.from)) throw new Error('Invalid from address') if (!isValidAddress(txParams.from)) throw new Error('Invalid from address')
} }
/**
validates the to field in txParams
@param txParams {object}
*/
function validateRecipient (txParams) { function validateRecipient (txParams) {
if (txParams.to === '0x' || txParams.to === null) { if (txParams.to === '0x' || txParams.to === null) {
if (txParams.data) { if (txParams.data) {
@ -64,3 +84,16 @@ function validateRecipient (txParams) {
} }
return txParams return txParams
} }
/**
@returns an {array} of states that can be considered final
*/
function getFinalStates () {
return [
'rejected', // the user has responded no!
'confirmed', // the tx has been included in a block.
'failed', // the tx failed for some reason, included on tx data.
'dropped', // the tx nonce was already used
]
}

View File

@ -1,7 +1,15 @@
const EthQuery = require('ethjs-query') const EthQuery = require('ethjs-query')
const assert = require('assert') const assert = require('assert')
const Mutex = require('await-semaphore').Mutex const Mutex = require('await-semaphore').Mutex
/**
@param opts {object} -
@property {Object} opts.provider a ethereum provider
@property {function} opts.getPendingTransactions a function that returns an array of txMeta
whos status is `submitted`
@property {function} opts.getConfirmedTransactions a function that returns an array of txMeta
whos status is `confirmed`
@class
*/
class NonceTracker { class NonceTracker {
constructor ({ provider, getPendingTransactions, getConfirmedTransactions }) { constructor ({ provider, getPendingTransactions, getConfirmedTransactions }) {
@ -12,6 +20,9 @@ class NonceTracker {
this.lockMap = {} this.lockMap = {}
} }
/**
@returns {object} with the key releaseLock (the gloabl mutex)
*/
async getGlobalLock () { async getGlobalLock () {
const globalMutex = this._lookupMutex('global') const globalMutex = this._lookupMutex('global')
// await global mutex free // await global mutex free
@ -19,8 +30,19 @@ class NonceTracker {
return { releaseLock } return { releaseLock }
} }
// releaseLock must be called /**
// releaseLock must be called after adding signed tx to pending transactions (or discarding) this will return an object with the `nextNonce` `nonceDetails` which is an
object with:
highestLocallyConfirmed (nonce),
highestSuggested (either the network nonce or the highestLocallyConfirmed nonce),
nextNetworkNonce (the nonce suggested by the network),
and the releaseLock
<br>note: releaseLock must be called after adding signed tx to pending transactions
(or discarding)<br>
@param address {string} the hex string for the address whos nonce we are calculating
@returns {object}
*/
async getNonceLock (address) { async getNonceLock (address) {
// await global mutex free // await global mutex free
await this._globalMutexFree() await this._globalMutexFree()

View File

@ -8,10 +8,10 @@ const EthQuery = require('ethjs-query')
As well as continues broadcast while in the pending state As well as continues broadcast while in the pending state
<br> <br>
@param config {object} - non optional configuration object consists of: @param config {object} - non optional configuration object consists of:
<br>provider @property {Object} config.provider
<br>nonceTracker: see nonce tracker @property {Object} config.nonceTracker see nonce tracker
<br>getPendingTransactions: a function for getting an array of transactions, @property {function} config.getPendingTransactions a function for getting an array of transactions,
<br>publishTransaction: a async function for publishing raw transactions, @property {function} config.publishTransaction a async function for publishing raw transactions,
@class @class
@ -220,4 +220,4 @@ class PendingTransactionTracker extends EventEmitter {
} }
} }
module.exports = PendingTransactionTracker module.exports = PendingTransactionTracker

View File

@ -11,6 +11,7 @@ const SIMPLE_GAS_COST = '0x5208' // Hex for 21000, cost of a simple send.
tx-utils are utility methods for Transaction manager tx-utils are utility methods for Transaction manager
its passed ethquery its passed ethquery
and used to do things like calculate gas of a tx. and used to do things like calculate gas of a tx.
@param provider {object}
*/ */
module.exports = class TxGasUtil { module.exports = class TxGasUtil {

View File

@ -1,22 +1,33 @@
const extend = require('xtend') const extend = require('xtend')
const EventEmitter = require('events') const EventEmitter = require('events')
const ObservableStore = require('obs-store') const ObservableStore = require('obs-store')
const createId = require('../../lib/random-id')
const ethUtil = require('ethereumjs-util') const ethUtil = require('ethereumjs-util')
const txStateHistoryHelper = require('./lib/tx-state-history-helper') const txStateHistoryHelper = require('./lib/tx-state-history-helper')
const createId = require('../../lib/random-id')
// STATUS METHODS const { getFinalStates } = require('./lib/util')
// statuses: /**
// - `'unapproved'` the user has not responded TransactionStateManager is responsible for the state of a transaction and
// - `'rejected'` the user has responded no! storing the transaction
// - `'approved'` the user has approved the tx it also has some convenience methods for finding subsets of transactions
// - `'signed'` the tx is signed *
// - `'submitted'` the tx is sent to a server *STATUS METHODS
// - `'confirmed'` the tx has been included in a block. <br>statuses:
// - `'failed'` the tx failed for some reason, included on tx data. <br> - `'unapproved'` the user has not responded
// - `'dropped'` the tx nonce was already used <br> - `'rejected'` the user has responded no!
<br> - `'approved'` the user has approved the tx
module.exports = class TransactionStateManager extends EventEmitter { <br> - `'signed'` the tx is signed
<br> - `'submitted'` the tx is sent to a server
<br> - `'confirmed'` the tx has been included in a block.
<br> - `'failed'` the tx failed for some reason, included on tx data.
<br> - `'dropped'` the tx nonce was already used
@param opts {object} -
@property {object} opts.initState with the key transaction {array}
@property {number} opts.txHistoryLimit limit for how many finished
transactions can hang around in state
@property {function} opts.getNetwork return network number
@class
*/
class TransactionStateManager extends EventEmitter {
constructor ({ initState, txHistoryLimit, getNetwork }) { constructor ({ initState, txHistoryLimit, getNetwork }) {
super() super()
@ -28,6 +39,10 @@ module.exports = class TransactionStateManager extends EventEmitter {
this.getNetwork = getNetwork this.getNetwork = getNetwork
} }
/**
@param opts {object} - the object to use when overwriting defaults
@returns {txMeta} the default txMeta object
*/
generateTxMeta (opts) { generateTxMeta (opts) {
return extend({ return extend({
id: createId(), id: createId(),
@ -38,17 +53,25 @@ module.exports = class TransactionStateManager extends EventEmitter {
}, opts) }, opts)
} }
/**
@returns {array} of txMetas that have been filtered for only the current network
*/
getTxList () { getTxList () {
const network = this.getNetwork() const network = this.getNetwork()
const fullTxList = this.getFullTxList() const fullTxList = this.getFullTxList()
return fullTxList.filter((txMeta) => txMeta.metamaskNetworkId === network) return fullTxList.filter((txMeta) => txMeta.metamaskNetworkId === network)
} }
/**
@returns {array} of all the txMetas in store
*/
getFullTxList () { getFullTxList () {
return this.store.getState().transactions return this.store.getState().transactions
} }
// Returns the tx list /**
@returns {array} the tx list whos status is unapproved
*/
getUnapprovedTxList () { getUnapprovedTxList () {
const txList = this.getTxsByMetaData('status', 'unapproved') const txList = this.getTxsByMetaData('status', 'unapproved')
return txList.reduce((result, tx) => { return txList.reduce((result, tx) => {
@ -57,18 +80,35 @@ module.exports = class TransactionStateManager extends EventEmitter {
}, {}) }, {})
} }
/**
@param address {string} - hex prefixed address to sort the txMetas for [optional]
@returns {array} the tx list whos status is submitted
*/
getPendingTransactions (address) { getPendingTransactions (address) {
const opts = { status: 'submitted' } const opts = { status: 'submitted' }
if (address) opts.from = address if (address) opts.from = address
return this.getFilteredTxList(opts) return this.getFilteredTxList(opts)
} }
/**
@param address {string} - hex prefixed address to sort the txMetas for [optional]
@returns {array} the tx list whos status is confirmed
*/
getConfirmedTransactions (address) { getConfirmedTransactions (address) {
const opts = { status: 'confirmed' } const opts = { status: 'confirmed' }
if (address) opts.from = address if (address) opts.from = address
return this.getFilteredTxList(opts) return this.getFilteredTxList(opts)
} }
/**
Adds the txMeta to the list of transactions in the store.
if the list is over txHistoryLimit it will remove a transaction that
is in its final state
it will allso add the key `history` to the txMeta with the snap shot of the original
object
@param txMeta {object}
@returns {object} the txMeta
*/
addTx (txMeta) { addTx (txMeta) {
this.once(`${txMeta.id}:signed`, function (txId) { this.once(`${txMeta.id}:signed`, function (txId) {
this.removeAllListeners(`${txMeta.id}:rejected`) this.removeAllListeners(`${txMeta.id}:rejected`)
@ -93,7 +133,7 @@ module.exports = class TransactionStateManager extends EventEmitter {
// not tx's that are pending or unapproved // not tx's that are pending or unapproved
if (txCount > txHistoryLimit - 1) { if (txCount > txHistoryLimit - 1) {
const index = transactions.findIndex((metaTx) => { const index = transactions.findIndex((metaTx) => {
return this.getFinalStates().includes(metaTx.status) return getFinalStates().includes(metaTx.status)
}) })
if (index !== -1) { if (index !== -1) {
transactions.splice(index, 1) transactions.splice(index, 1)
@ -103,12 +143,21 @@ module.exports = class TransactionStateManager extends EventEmitter {
this._saveTxList(transactions) this._saveTxList(transactions)
return txMeta return txMeta
} }
// gets tx by Id and returns it /**
@param txId {number}
@returns {object} the txMeta who matches the given id if none found
for the network returns undefined
*/
getTx (txId) { getTx (txId) {
const txMeta = this.getTxsByMetaData('id', txId)[0] const txMeta = this.getTxsByMetaData('id', txId)[0]
return txMeta return txMeta
} }
/**
updates the txMeta in the list and adds a history entry
@param txMeta {object} - the txMeta to update
@param note {string} - a not about the update for history
*/
updateTx (txMeta, note) { updateTx (txMeta, note) {
// validate txParams // validate txParams
if (txMeta.txParams) { if (txMeta.txParams) {
@ -136,15 +185,22 @@ module.exports = class TransactionStateManager extends EventEmitter {
} }
// merges txParams obj onto txData.txParams /**
// use extend to ensure that all fields are filled merges txParams obj onto txMeta.txParams
use extend to ensure that all fields are filled
@param txId {number} - the id of the txMeta
@param txParams {object} - the updated txParams
*/
updateTxParams (txId, txParams) { updateTxParams (txId, txParams) {
const txMeta = this.getTx(txId) const txMeta = this.getTx(txId)
txMeta.txParams = extend(txMeta.txParams, txParams) txMeta.txParams = extend(txMeta.txParams, txParams)
this.updateTx(txMeta, `txStateManager#updateTxParams`) this.updateTx(txMeta, `txStateManager#updateTxParams`)
} }
// validates txParams members by type /**
validates txParams members by type
@param txParams {object} - txParams to validate
*/
validateTxParams (txParams) { validateTxParams (txParams) {
Object.keys(txParams).forEach((key) => { Object.keys(txParams).forEach((key) => {
const value = txParams[key] const value = txParams[key]
@ -161,17 +217,18 @@ module.exports = class TransactionStateManager extends EventEmitter {
}) })
} }
/* /**
Takes an object of fields to search for eg: @param opts {object} - an object of fields to search for eg:<br>
let thingsToLookFor = { let <code>thingsToLookFor = {<br>
to: '0x0..', to: '0x0..',<br>
from: '0x0..', from: '0x0..',<br>
status: 'signed', status: 'signed',<br>
err: undefined, err: undefined,<br>
} }<br></code>
and returns a list of tx with all @returns a {array} of txMeta with all
options matching options matching
*/
/*
****************HINT**************** ****************HINT****************
| `err: undefined` is like looking | | `err: undefined` is like looking |
| for a tx with no err | | for a tx with no err |
@ -192,7 +249,14 @@ module.exports = class TransactionStateManager extends EventEmitter {
}) })
return filteredTxList return filteredTxList
} }
/**
@param key {string} - the key to check
@param value - the value your looking for
@param txList {array} - [optional] the list to search. default is the txList
from txStateManager#getTxList
@returns {array} a list of txMetas who matches the search params
*/
getTxsByMetaData (key, value, txList = this.getTxList()) { getTxsByMetaData (key, value, txList = this.getTxList()) {
return txList.filter((txMeta) => { return txList.filter((txMeta) => {
if (txMeta.txParams[key]) { if (txMeta.txParams[key]) {
@ -205,33 +269,51 @@ module.exports = class TransactionStateManager extends EventEmitter {
// get::set status // get::set status
// should return the status of the tx. /**
@param txId {number} - the txMeta Id
@return {string} the status of the tx.
*/
getTxStatus (txId) { getTxStatus (txId) {
const txMeta = this.getTx(txId) const txMeta = this.getTx(txId)
return txMeta.status return txMeta.status
} }
// should update the status of the tx to 'rejected'. /**
should update the status of the tx to 'rejected'.
@param txId {number} - the txMeta Id
*/
setTxStatusRejected (txId) { setTxStatusRejected (txId) {
this._setTxStatus(txId, 'rejected') this._setTxStatus(txId, 'rejected')
} }
// should update the status of the tx to 'unapproved'. /**
should update the status of the tx to 'unapproved'.
@param txId {number} - the txMeta Id
*/
setTxStatusUnapproved (txId) { setTxStatusUnapproved (txId) {
this._setTxStatus(txId, 'unapproved') this._setTxStatus(txId, 'unapproved')
} }
// should update the status of the tx to 'approved'. /**
should update the status of the tx to 'approved'.
@param txId {number} - the txMeta Id
*/
setTxStatusApproved (txId) { setTxStatusApproved (txId) {
this._setTxStatus(txId, 'approved') this._setTxStatus(txId, 'approved')
} }
// should update the status of the tx to 'signed'. /**
should update the status of the tx to 'signed'.
@param txId {number} - the txMeta Id
*/
setTxStatusSigned (txId) { setTxStatusSigned (txId) {
this._setTxStatus(txId, 'signed') this._setTxStatus(txId, 'signed')
} }
// should update the status of the tx to 'submitted'. /**
// and add a time stamp for when it was called should update the status of the tx to 'submitted'.
and add a time stamp for when it was called
@param txId {number} - the txMeta Id
*/
setTxStatusSubmitted (txId) { setTxStatusSubmitted (txId) {
const txMeta = this.getTx(txId) const txMeta = this.getTx(txId)
txMeta.submittedTime = (new Date()).getTime() txMeta.submittedTime = (new Date()).getTime()
@ -239,17 +321,29 @@ module.exports = class TransactionStateManager extends EventEmitter {
this._setTxStatus(txId, 'submitted') this._setTxStatus(txId, 'submitted')
} }
// should update the status of the tx to 'confirmed'. /**
should update the status of the tx to 'confirmed'.
@param txId {number} - the txMeta Id
*/
setTxStatusConfirmed (txId) { setTxStatusConfirmed (txId) {
this._setTxStatus(txId, 'confirmed') this._setTxStatus(txId, 'confirmed')
} }
// should update the status dropped /**
should update the status of the tx to 'dropped'.
@param txId {number} - the txMeta Id
*/
setTxStatusDropped (txId) { setTxStatusDropped (txId) {
this._setTxStatus(txId, 'dropped') this._setTxStatus(txId, 'dropped')
} }
/**
should update the status of the tx to 'failed'.
and put the error on the txMeta
@param txId {number} - the txMeta Id
@param err {erroObject} - error object
*/
setTxStatusFailed (txId, err) { setTxStatusFailed (txId, err) {
const txMeta = this.getTx(txId) const txMeta = this.getTx(txId)
txMeta.err = { txMeta.err = {
@ -260,16 +354,11 @@ module.exports = class TransactionStateManager extends EventEmitter {
this._setTxStatus(txId, 'failed') this._setTxStatus(txId, 'failed')
} }
// returns an array of states that can be considered final /**
getFinalStates () { Removes transaction from the given address for the current network
return [ from the txList
'rejected', // the user has responded no! @param address {string} - hex string of the from address on the txParams to remove
'confirmed', // the tx has been included in a block. */
'failed', // the tx failed for some reason, included on tx data.
'dropped', // the tx nonce was already used
]
}
wipeTransactions (address) { wipeTransactions (address) {
// network only tx // network only tx
const txs = this.getFullTxList() const txs = this.getFullTxList()
@ -295,6 +384,14 @@ module.exports = class TransactionStateManager extends EventEmitter {
// - `'confirmed'` the tx has been included in a block. // - `'confirmed'` the tx has been included in a block.
// - `'failed'` the tx failed for some reason, included on tx data. // - `'failed'` the tx failed for some reason, included on tx data.
// - `'dropped'` the tx nonce was already used // - `'dropped'` the tx nonce was already used
/**
@param txId {number} - the txMeta Id
@param status {string} - the status to set on the txMeta
@emits tx:status-update - passes txId and status
@emits ${txMeta.id}:finished - if it is a finished state. Passes the txMeta
@emits update:badge
*/
_setTxStatus (txId, status) { _setTxStatus (txId, status) {
const txMeta = this.getTx(txId) const txMeta = this.getTx(txId)
txMeta.status = status txMeta.status = status
@ -307,9 +404,14 @@ module.exports = class TransactionStateManager extends EventEmitter {
this.emit('update:badge') this.emit('update:badge')
} }
// Saves the new/updated txList. /**
Saves the new/updated txList.
@param transactions {array} - the list of transactions to save
*/
// Function is intended only for internal use // Function is intended only for internal use
_saveTxList (transactions) { _saveTxList (transactions) {
this.store.updateState({ transactions }) this.store.updateState({ transactions })
} }
} }
module.exports = TransactionStateManager

BIN
docs/transaction-flow.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 138 KiB