@ -1,7 +1,8 @@
import EventEmitter from 'safe-event-emitter' ;
import { ObservableStore } from '@metamask/obs-store' ;
import log from 'loglevel' ;
import createId from '../../lib/random-id' ;
import { keyBy , mapValues , omitBy , pickBy , sortBy } from 'lodash' ;
import createId from '../../../../shared/modules/random-id' ;
import { TRANSACTION _STATUSES } from '../../../../shared/constants/transaction' ;
import { METAMASK _CONTROLLER _EVENTS } from '../../metamask-controller' ;
import { transactionMatchesNetwork } from '../../../../shared/modules/transaction.utils' ;
@ -10,11 +11,29 @@ import {
replayHistory ,
snapshotFromTxMeta ,
} from './lib/tx-state-history-helpers' ;
import { getFinalStates , normalizeTxParams } from './lib/util' ;
import { getFinalStates , normalizeAndValidate TxParams } from './lib/util' ;
/ * *
* TransactionStatuses reimported from the shared transaction constants file
* @ typedef { import ( '../../../../shared/constants/transaction' ) . TransactionStatuses } TransactionStatuses
* @ typedef { import (
* '../../../../shared/constants/transaction'
* ) . TransactionStatusString } TransactionStatusString
* /
/ * *
* @ typedef { import ( '../../../../shared/constants/transaction' ) . TxParams } TxParams
* /
/ * *
* @ typedef { import (
* '../../../../shared/constants/transaction'
* ) . TransactionMeta } TransactionMeta
* /
/ * *
* @ typedef { Object } TransactionState
* @ property { Record < string , TransactionMeta > } transactions - TransactionMeta
* keyed by the transaction ' s id .
* /
/ * *
@ -22,7 +41,8 @@ import { getFinalStates, normalizeTxParams } from './lib/util';
* storing the transaction . It also has some convenience methods for finding
* subsets of transactions .
* @ param { Object } opts
* @ param { Object } [ opts . initState = { transactions : [ ] } ] - initial transactions list with the key transaction { Array }
* @ param { TransactionState } [ opts . initState = { transactions : { } } ] - initial
* transactions list keyed by id
* @ param { number } [ opts . txHistoryLimit ] - limit for how many finished
* transactions can hang around in state
* @ param { Function } opts . getNetwork - return network number
@ -32,15 +52,25 @@ export default class TransactionStateManager extends EventEmitter {
constructor ( { initState , txHistoryLimit , getNetwork , getCurrentChainId } ) {
super ( ) ;
this . store = new ObservableStore ( { transactions : [ ] , ... initState } ) ;
this . store = new ObservableStore ( {
transactions : { } ,
... initState ,
} ) ;
this . txHistoryLimit = txHistoryLimit ;
this . getNetwork = getNetwork ;
this . getCurrentChainId = getCurrentChainId ;
}
/ * *
* @ param { Object } opts - the object to use when overwriting defaults
* @ returns { txMeta } the default txMeta object
* Generates a TransactionMeta object consisting of the fields required for
* use throughout the extension . The argument here will override everything
* in the resulting transaction meta .
*
* TODO : Don ' t overwrite everything ?
*
* @ param { Partial < TransactionMeta > } opts - the object to use when
* overwriting default keys of the TransactionMeta
* @ returns { TransactionMeta } the default txMeta object
* /
generateTxMeta ( opts ) {
const netId = this . getNetwork ( ) ;
@ -60,100 +90,70 @@ export default class TransactionStateManager extends EventEmitter {
}
/ * *
* Returns the full tx list for the current network
*
* The list is iterated backwards as new transactions are pushed onto it .
* Get an object containing all unapproved transactions for the current
* network . This is the only transaction fetching method that returns an
* object , so it doesn ' t use getTransactions like everything else .
*
* @ param { number } [ limit ] - a limit for the number of transactions to return
* @ returns { Object [ ] } The { @ code txMeta } s , filtered to the current network
* /
getTxList ( limit ) {
const network = this . getNetwork ( ) ;
const chainId = this . getCurrentChainId ( ) ;
const fullTxList = this . getFullTxList ( ) ;
const nonces = new Set ( ) ;
const txs = [ ] ;
for ( let i = fullTxList . length - 1 ; i > - 1 ; i -- ) {
const txMeta = fullTxList [ i ] ;
if ( transactionMatchesNetwork ( txMeta , chainId , network ) === false ) {
continue ;
}
if ( limit !== undefined ) {
const { nonce } = txMeta . txParams ;
if ( ! nonces . has ( nonce ) ) {
if ( nonces . size < limit ) {
nonces . add ( nonce ) ;
} else {
continue ;
}
}
}
txs . unshift ( txMeta ) ;
}
return txs ;
}
/ * *
* @ returns { Array } of all the txMetas in store
* /
getFullTxList ( ) {
return this . store . getState ( ) . transactions ;
}
/ * *
* @ returns { Array } the tx list with unapproved status
* @ returns { Record < string , TransactionMeta > } Unapproved transactions keyed
* by id
* /
getUnapprovedTxList ( ) {
const txList = this . getTxsByMetaData (
'status' ,
TRANSACTION _STATUSES . UNAPPROVED ,
const chainId = this . getCurrentChainId ( ) ;
const network = this . getNetwork ( ) ;
return pickBy (
this . store . getState ( ) . transactions ,
( transaction ) =>
transaction . status === TRANSACTION _STATUSES . UNAPPROVED &&
transactionMatchesNetwork ( transaction , chainId , network ) ,
) ;
return txList . reduce ( ( result , tx ) => {
result [ tx . id ] = tx ;
return result ;
} , { } ) ;
}
/ * *
* @ param { string } [ address ] - hex prefixed address to sort the txMetas for [ optional ]
* @ returns { Array } the tx list with approved status if no address is provide
* returns all txMetas with approved statuses for the current network
* Get all approved transactions for the current network . If an address is
* provided , the list will be further refined to only those transactions
* originating from the supplied address .
*
* @ param { string } [ address ] - hex prefixed address to find transactions for .
* @ returns { TransactionMeta [ ] } the filtered list of transactions
* /
getApprovedTransactions ( address ) {
const opts = { status : TRANSACTION _STATUSES . APPROVED } ;
const searchCriteria = { status : TRANSACTION _STATUSES . APPROVED } ;
if ( address ) {
opt s. from = address ;
searchCriteria . from = address ;
}
return this . getFilteredTxList ( opts ) ;
return this . getTransactions ( { searchCriteria } ) ;
}
/ * *
* @ param { string } [ address ] - hex prefixed address to sort the txMetas for [ optional ]
* @ returns { Array } the tx list submitted status if no address is provide
* returns all txMetas with submitted statuses for the current network
* Get all pending transactions for the current network . If an address is
* provided , the list will be further refined to only those transactions
* originating from the supplied address .
*
* @ param { string } [ address ] - hex prefixed address to find transactions for .
* @ returns { TransactionMeta [ ] } the filtered list of transactions
* /
getPendingTransactions ( address ) {
const opts = { status : TRANSACTION _STATUSES . SUBMITTED } ;
const searchCriteria = { status : TRANSACTION _STATUSES . SUBMITTED } ;
if ( address ) {
opt s. from = address ;
searchCriteria . from = address ;
}
return this . getFilteredTxList ( opts ) ;
return this . getTransactions ( { searchCriteria } ) ;
}
/ * *
@ param { string } [ address ] - hex prefixed address to sort the txMetas for [ optional ]
@ returns { Array } the tx list whose status is confirmed if no address is provide
returns all txMetas who ' s status is confirmed for the current network
* /
* Get all confirmed transactions for the current network . If an address is
* provided , the list will be further refined to only those transactions
* originating from the supplied address .
*
* @ param { string } [ address ] - hex prefixed address to find transactions for .
* @ returns { TransactionMeta [ ] } the filtered list of transactions
* /
getConfirmedTransactions ( address ) {
const opts = { status : TRANSACTION _STATUSES . CONFIRMED } ;
const searchCriteria = { status : TRANSACTION _STATUSES . CONFIRMED } ;
if ( address ) {
opt s. from = address ;
searchCriteria . from = address ;
}
return this . getFilteredTxList ( opts ) ;
return this . getTransactions ( { searchCriteria } ) ;
}
/ * *
@ -162,13 +162,14 @@ export default class TransactionStateManager extends EventEmitter {
* is in its final state .
* it will also add the key ` history ` to the txMeta with the snap shot of
* the original object
* @ param { Object } txMeta
* @ returns { Object } the txMeta
* @ param { TransactionMeta } txMeta - The TransactionMeta object to add .
* @ returns { TransactionMeta } The same TransactionMeta , but with validated
* txParams and transaction history .
* /
addTx ( txMeta ) {
addTransaction ( txMeta ) {
// normalize and validate txParams if present
if ( txMeta . txParams ) {
txMeta . txParams = this . normalizeAndValidateTxParams ( txMeta . txParams ) ;
txMeta . txParams = normalizeAndValidateTxParams ( txMeta . txParams , false ) ;
}
this . once ( ` ${ txMeta . id } :signed ` , ( ) => {
@ -183,42 +184,43 @@ export default class TransactionStateManager extends EventEmitter {
const snapshot = snapshotFromTxMeta ( txMeta ) ;
txMeta . history . push ( snapshot ) ;
const transactions = this . getFullTxList ( ) ;
const transactions = this . getTransactions ( {
filterToCurrentNetwork : false ,
} ) ;
const txCount = transactions . length ;
const { txHistoryLimit } = this ;
// 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
// checks if the length of the tx history is longer then desired persistence
// limit and then if it is removes the oldest confirmed or rejected tx.
// Pending or unapproved transactions will not be removed by this
// operation.
//
// TODO: we are already limiting what we send to the UI, and in the future
// we will send UI only collected groups of transactions *per page* so at
// some point in the future, this persistence limit can be adjusted. When
// we do that I think we should figure out a better storage solution for
// transaction history entries.
if ( txCount > txHistoryLimit - 1 ) {
const index = transactions . findIndex ( ( metaTx ) => {
return getFinalStates ( ) . includes ( metaTx . status ) ;
} ) ;
if ( index !== - 1 ) {
transactions . splice ( index , 1 ) ;
this . _deleteTransaction ( transactions [ index ] . id ) ;
}
}
const newTxIndex = transactions . findIndex (
( currentTxMeta ) => currentTxMeta . time > txMeta . time ,
) ;
newTxIndex === - 1
? transactions . push ( txMeta )
: transactions . splice ( newTxIndex , 0 , txMeta ) ;
this . _saveTxList ( transactions ) ;
this . _addTransactionsToState ( [ txMeta ] ) ;
return txMeta ;
}
/ * *
* @ param { number } txId
* @ returns { Object } the txMeta who matches the given id if none found
* @ returns { TransactionMeta } the txMeta who matches the given id if none found
* for the network returns undefined
* /
getTx ( txId ) {
const txMeta = this . getTxsByMetaData ( 'id' , txId ) [ 0 ] ;
return txMeta ;
getTransaction ( txId ) {
const { transactions } = this . store . getState ( ) ;
return transactions [ txId ] ;
}
/ * *
@ -226,10 +228,10 @@ export default class TransactionStateManager extends EventEmitter {
* @ param { Object } txMeta - the txMeta to update
* @ param { string } [ note ] - a note about the update for history
* /
updateTx ( txMeta , note ) {
updateTransaction ( txMeta , note ) {
// normalize and validate txParams if present
if ( txMeta . txParams ) {
txMeta . txParams = this . normalizeAndValidateTxParams ( txMeta . txParams ) ;
txMeta . txParams = normalizeAndValidateTxParams ( txMeta . txParams , false ) ;
}
// create txMeta snapshot for history
@ -244,232 +246,277 @@ export default class TransactionStateManager extends EventEmitter {
// 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 . store . updateState ( {
transactions : {
... this . store . getState ( ) . transactions ,
[ txId ] : txMeta ,
} ,
} ) ;
}
/ * *
* merges txParams obj onto txMeta . txParams use extend to ensur e
* that all fields are filled
* @ param { number } txId - the id of the txMeta
* @ param { Object } txParams - the updated txParam s
* SearchCriteria can search in any key in TxParams or the bas e
* TransactionMeta . This type represents any key on either of those two
* types .
* @ typedef { TxParams [ keyof TxParams ] | TransactionMeta [ keyof TransactionMeta ] } SearchableKey s
* /
updateTxParams ( txId , txParams ) {
const txMeta = this . getTx ( txId ) ;
txMeta . txParams = { ... txMeta . txParams , ... txParams } ;
this . updateTx ( txMeta , ` txStateManager#updateTxParams ` ) ;
}
/ * *
* normalize and validate txParams members
* @ param { Object } txParams - txParams
* Predicates can either be strict values , which is shorthand for using
* strict equality , or a method that receives he value of the specified key
* and returns a boolean .
* @ typedef { ( v : unknown ) => boolean | unknown } FilterPredicate
* /
normalizeAndValidateTxParams ( txParams ) {
if ( typeof txParams . data === 'undefined' ) {
delete txParams . data ;
}
// eslint-disable-next-line no-param-reassign
txParams = normalizeTxParams ( txParams , false ) ;
this . validateTxParams ( txParams ) ;
return txParams ;
}
/ * *
* validates txParams members by type
* @ param { Object } txParams - txParams to validate
* Retrieve a list of transactions from state . By default this will return
* the full list of Transactions for the currently selected chain / network .
* Additional options can be provided to change what is included in the final
* list .
*
* @ param opts - options to change filter behavior
* @ param { Record < SearchableKeys , FilterPredicate > } [ opts . searchCriteria ] -
* an object with keys that match keys in TransactionMeta or TxParams , and
* values that are predicates . Predicates can either be strict values ,
* which is shorthand for using strict equality , or a method that receives
* the value of the specified key and returns a boolean . The transaction
* list will be filtered to only those items that the predicate returns
* truthy for . * * HINT * * : ` err: undefined ` is like looking for a tx with no
* err . so you can also search txs that don ' t have something as well by
* setting the value as undefined .
* @ param { TransactionMeta [ ] } [ opts . initialList ] - If provided the filtering
* will occur on the provided list . By default this will be the full list
* from state sorted by time ASC .
* @ param { boolean } [ opts . filterToCurrentNetwork = true ] - Filter transaction
* list to only those that occurred on the current chain or network .
* Defaults to true .
* @ param { number } [ opts . limit ] - limit the number of transactions returned
* to N unique nonces .
* @ returns { TransactionMeta [ ] } The TransactionMeta objects that all provided
* predicates return truthy for .
* /
validateTxParams ( txParams ) {
Object . keys ( txParams ) . forEach ( ( key ) => {
const value = txParams [ key ] ;
// validate types
switch ( key ) {
case 'chainId' :
if ( typeof value !== 'number' && typeof value !== 'string' ) {
throw new Error (
` ${ key } in txParams is not a Number or hex string. got: ( ${ value } ) ` ,
) ;
}
break ;
default :
if ( typeof value !== 'string' ) {
throw new Error (
` ${ key } in txParams is not a string. got: ( ${ value } ) ` ,
) ;
}
break ;
}
} ) ;
}
/ * *
@ param { Object } opts - an object of fields to search for eg : < br >
let < code > thingsToLookFor = { < br >
to : '0x0..' , < br >
from : '0x0..' , < br >
status : 'signed' , \ \ ( status ) => status !== 'rejected' give me all txs who ' s status is not rejected < br >
err : undefined , < br >
} < br > < / c o d e >
optionally the values of the keys can be functions for situations like where
you want all but one status .
@ param { Array } [ initialList = this . getTxList ( ) ]
@ returns { Array } array of txMeta 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 filtering 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 ) ;
getTransactions ( {
searchCriteria = { } ,
initialList ,
filterToCurrentNetwork = true ,
limit ,
} = { } ) {
const chainId = this . getCurrentChainId ( ) ;
const network = this . getNetwork ( ) ;
// searchCriteria is an object that might have values that aren't predicate
// methods. When providing any other value type (string, number, etc), we
// consider this shorthand for "check the value at key for strict equality
// with the provided value". To conform this object to be only methods, we
// mapValues (lodash) such that every value on the object is a method that
// returns a boolean.
const predicateMethods = mapValues ( searchCriteria , ( predicate ) => {
return typeof predicate === 'function'
? predicate
: ( v ) => v === predicate ;
} ) ;
return filteredTxList ;
}
/ * *
* @ param { string } key - the key to check
* @ param { any } value - the value your looking for can also be a function that returns a bool
* @ param { Array } [ txList = this . getTxList ( ) ] - 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 ( ) ) {
const filter = typeof value === 'function' ? value : ( v ) => v === value ;
// If an initial list is provided we need to change it back into an object
// first, so that it matches the shape of our state. This is done by the
// lodash keyBy method. This is the edge case for this method, typically
// initialList will be undefined.
const transactionsToFilter = initialList
? keyBy ( initialList , 'id' )
: this . store . getState ( ) . transactions ;
// Combine sortBy and pickBy to transform our state object into an array of
// matching transactions that are sorted by time.
const filteredTransactions = sortBy (
pickBy ( transactionsToFilter , ( transaction ) => {
// default matchesCriteria to the value of transactionMatchesNetwork
// when filterToCurrentNetwork is true.
if (
filterToCurrentNetwork &&
transactionMatchesNetwork ( transaction , chainId , network ) === false
) {
return false ;
}
// iterate over the predicateMethods keys to check if the transaction
// matches the searchCriteria
for ( const [ key , predicate ] of Object . entries ( predicateMethods ) ) {
// We return false early as soon as we know that one of the specified
// search criteria do not match the transaction. This prevents
// needlessly checking all criteria when we already know the criteria
// are not fully satisfied. We check both txParams and the base
// object as predicate keys can be either.
if ( key in transaction . txParams ) {
if ( predicate ( transaction . txParams [ key ] ) === false ) {
return false ;
}
} else if ( predicate ( transaction [ key ] ) === false ) {
return false ;
}
}
return txList . filter ( ( txMeta ) => {
if ( key in txMeta . txParams ) {
return filter ( txMeta . txParams [ key ] ) ;
return true ;
} ) ,
'time' ,
) ;
if ( limit !== undefined ) {
// We need to have all transactions of a given nonce in order to display
// necessary details in the UI. We use the size of this set to determine
// whether we have reached the limit provided, thus ensuring that all
// transactions of nonces we include will be sent to the UI.
const nonces = new Set ( ) ;
const txs = [ ] ;
// By default, the transaction list we filter from is sorted by time ASC.
// To ensure that filtered results prefers the newest transactions we
// iterate from right to left, inserting transactions into front of a new
// array. The original order is preserved, but we ensure that newest txs
// are preferred.
for ( let i = filteredTransactions . length - 1 ; i > - 1 ; i -- ) {
const txMeta = filteredTransactions [ i ] ;
const { nonce } = txMeta . txParams ;
if ( ! nonces . has ( nonce ) ) {
if ( nonces . size < limit ) {
nonces . add ( nonce ) ;
} else {
continue ;
}
}
// Push transaction into the beginning of our array to ensure the
// original order is preserved.
txs . unshift ( txMeta ) ;
}
return filter ( txMeta [ key ] ) ;
} ) ;
}
// get::set status
/ * *
* @ param { number } txId - the txMeta Id
* @ returns { string } the status of the tx .
* /
getTxStatus ( txId ) {
const txMeta = this . getTx ( txId ) ;
return txMeta . status ;
return txs ;
}
return filteredTransactions ;
}
/ * *
* Update the status of the tx to 'rejected' .
* @ param { number } txId - the txMeta Id
* Update status of the TransactionMeta with provided id to 'rejected' .
* After setting the status , the TransactionMeta is deleted from state .
*
* TODO : Should we show historically rejected transactions somewhere in the
* UI ? Seems like it could be valuable for information purposes . Of course
* only after limit issues are reduced .
*
* @ param { number } txId - the target TransactionMeta ' s Id
* /
setTxStatusRejected ( txId ) {
this . _setTxStatus ( txId , 'rejected' ) ;
this . _removeTx ( txId ) ;
this . _setTransaction Status ( txId , 'rejected' ) ;
this . _deleteTransaction ( txId ) ;
}
/ * *
* Update the status of the tx to 'unapproved' .
* @ param { number } txId - the txMeta Id
* Update status of the TransactionMeta with provided id to 'unapproved'
*
* @ param { number } txId - the target TransactionMeta ' s Id
* /
setTxStatusUnapproved ( txId ) {
this . _setTx Status ( txId , TRANSACTION _STATUSES . UNAPPROVED ) ;
this . _setTransaction Status ( txId , TRANSACTION _STATUSES . UNAPPROVED ) ;
}
/ * *
* Update the status of the tx to 'approved' .
* @ param { number } txId - the txMeta Id
* Update status of the TransactionMeta with provided id to 'approved'
*
* @ param { number } txId - the target TransactionMeta ' s Id
* /
setTxStatusApproved ( txId ) {
this . _setTx Status ( txId , TRANSACTION _STATUSES . APPROVED ) ;
this . _setTransaction Status ( txId , TRANSACTION _STATUSES . APPROVED ) ;
}
/ * *
* Update the status of the tx to 'signed' .
* @ param { number } txId - the txMeta Id
* Update status of the TransactionMeta with provided id to 'signed'
*
* @ param { number } txId - the target TransactionMeta ' s Id
* /
setTxStatusSigned ( txId ) {
this . _setTx Status ( txId , TRANSACTION _STATUSES . SIGNED ) ;
this . _setTransaction Status ( txId , TRANSACTION _STATUSES . SIGNED ) ;
}
/ * *
* Update the status of the tx to 'submitted' and add a time stamp
* for when it was called
* @ param { number } txId - the txMeta Id
* Update status of the TransactionMeta with provided id to 'submitted'
* and sets the 'submittedTime' property with the current Unix epoch time .
*
* @ param { number } txId - the target TransactionMeta ' s Id
* /
setTxStatusSubmitted ( txId ) {
const txMeta = this . getTx ( txId ) ;
const txMeta = this . getTransaction ( txId ) ;
txMeta . submittedTime = new Date ( ) . getTime ( ) ;
this . updateTx ( txMeta , 'txStateManager - add submitted time stamp' ) ;
this . _setTx Status ( txId , TRANSACTION _STATUSES . SUBMITTED ) ;
this . updateTransaction ( txMeta , 'txStateManager - add submitted time stamp' ) ;
this . _setTransaction Status ( txId , TRANSACTION _STATUSES . SUBMITTED ) ;
}
/ * *
* Update the status of the tx to 'confirmed' .
* @ param { number } txId - the txMeta Id
* Update status of the TransactionMeta with provided id to 'confirmed'
*
* @ param { number } txId - the target TransactionMeta ' s Id
* /
setTxStatusConfirmed ( txId ) {
this . _setTx Status ( txId , TRANSACTION _STATUSES . CONFIRMED ) ;
this . _setTransaction Status ( txId , TRANSACTION _STATUSES . CONFIRMED ) ;
}
/ * *
* Update the status of the tx to 'dropped' .
* @ param { number } txId - the txMeta Id
* Update status of the TransactionMeta with provided id to 'dropped'
*
* @ param { number } txId - the target TransactionMeta ' s Id
* /
setTxStatusDropped ( txId ) {
this . _setTx Status ( txId , TRANSACTION _STATUSES . DROPPED ) ;
this . _setTransaction Status ( txId , TRANSACTION _STATUSES . DROPPED ) ;
}
/ * *
* Updates the status of the tx to 'failed' and put the error on the txMeta
* @ param { number } txId - the txMeta Id
* @ param { erroObject } err - error object
* Update status of the TransactionMeta with provided id to 'failed' and put
* the error on the TransactionMeta object .
*
* @ param { number } txId - the target TransactionMeta ' s Id
* @ param { Error } err - error object
* /
setTxStatusFailed ( txId , err ) {
const error = err || new Error ( 'Internal metamask failure' ) ;
const txMeta = this . getTx ( txId ) ;
const txMeta = this . getTransaction ( txId ) ;
txMeta . err = {
message : error . toString ( ) ,
rpc : error . value ,
stack : error . stack ,
} ;
this . updateTx ( txMeta , 'transactions:tx-state-manager#fail - add error' ) ;
this . _setTxStatus ( txId , TRANSACTION _STATUSES . FAILED ) ;
this . updateTransaction (
txMeta ,
'transactions:tx-state-manager#fail - add error' ,
) ;
this . _setTransactionStatus ( txId , TRANSACTION _STATUSES . FAILED ) ;
}
/ * *
* Removes transaction from the given address for the current network
* from the txList
* Removes all transactions for the given address on the current network ,
* preferring chainId for comparison over networkId .
*
* @ param { string } address - hex string of the from address on the txParams
* to remove
* /
wipeTransactions ( address ) {
// network only tx
const txs = this . getFullTxList ( ) ;
const { transactions } = this . store . getState ( ) ;
const network = this . getNetwork ( ) ;
const chainId = this . getCurrentChainId ( ) ;
// Filter out the ones from the current account and network
const otherAccountTxs = txs . filter (
( txMeta ) =>
! (
txMeta . txParams . from === address &&
transactionMatchesNetwork ( txMeta , chainId , network )
) ,
) ;
// Update state
this . _saveTxList ( otherAccountTxs ) ;
this . store . updateState ( {
transactions : omitBy (
transactions ,
( transaction ) =>
transaction . txParams . from === address &&
transactionMatchesNetwork ( transaction , chainId , network ) ,
) ,
} ) ;
}
/ * *
* Filters out the unapproved transactions from state
* /
clearUnapprovedTxs ( ) {
this . store . updateState ( {
transactions : omitBy (
this . store . getState ( ) . transactions ,
( transaction ) => transaction . status === TRANSACTION _STATUSES . UNAPPROVED ,
) ,
} ) ;
}
//
@ -477,14 +524,37 @@ export default class TransactionStateManager extends EventEmitter {
//
/ * *
* @ param { number } txId - the txMeta Id
* @ param { TransactionStatuses [ keyof TransactionStatuses ] } status - 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 'updateBadge'
* Updates a transaction ' s status in state , and then emits events that are
* subscribed to elsewhere . See below for best guesses on where and how these
* events are received .
* @ param { number } txId - the TransactionMeta Id
* @ param { TransactionStatusString } status - the status to set on the
* TransactionMeta
* @ emits txMeta . id : txMeta . status - every time a transaction ' s status changes
* we emit the change passing along the id . This does not appear to be used
* outside of this file , which only listens to this to unsubscribe listeners
* of : rejected and : signed statuses when the inverse status changes . Likely
* safe to drop .
* @ emits tx : status - update - every time a transaction ' s status changes we
* emit this event and pass txId and status . This event is subscribed to in
* the TransactionController and re - broadcast by the TransactionController .
* It is used internally within the TransactionController to try and update
* pending transactions on each new block ( from blockTracker ) . It ' s also
* subscribed to in metamask - controller to display a browser notification on
* confirmed or failed transactions .
* @ emits txMeta . id : finished - When a transaction moves to a finished state
* this event is emitted , which is used in the TransactionController to pass
* along details of the transaction to the dapp that suggested them . This
* pattern is replicated across all of the message managers and can likely
* be supplemented or replaced by the ApprovalController .
* @ emits updateBadge - When the number of transactions changes in state ,
* the badge in the browser extension bar should be updated to reflect the
* number of pending transactions . This particular emit doesn ' t appear to
* bubble up anywhere that is actually used . TransactionController emits
* this * anytime the state changes * , so this is probably superfluous .
* /
_setTxStatus ( txId , status ) {
const txMeta = this . getTx ( txId ) ;
_setTransaction Status ( txId , status ) {
const txMeta = this . getTransaction ( txId ) ;
if ( ! txMeta ) {
return ;
@ -492,7 +562,10 @@ export default class TransactionStateManager extends EventEmitter {
txMeta . status = status ;
try {
this . updateTx ( txMeta , ` txStateManager: setting status to ${ status } ` ) ;
this . updateTransaction (
txMeta ,
` txStateManager: setting status to ${ status } ` ,
) ;
this . emit ( ` ${ txMeta . id } : ${ status } ` , txId ) ;
this . emit ( ` tx:status-update ` , txId , status ) ;
if (
@ -511,26 +584,32 @@ export default class TransactionStateManager extends EventEmitter {
}
/ * *
* Saves the new / u p d a t e d t x L i s t . I n t e n d e d o n l y f o r i n t e r n a l u s e
* @ param { Array } transactions - the list of transactions to save
* Adds one or more transactions into state . This is not intended for
* external use .
*
* @ private
* @ param { TransactionMeta [ ] } transactions - the list of transactions to save
* /
_saveTxList ( transactions ) {
this . store . updateState ( { transactions } ) ;
}
_removeTx ( txId ) {
const transactionList = this . getFullTxList ( ) ;
this . _saveTxList ( transactionList . filter ( ( txMeta ) => txMeta . id !== txId ) ) ;
_addTransactionsToState ( transactions ) {
this . store . updateState ( {
transactions : transactions . reduce ( ( result , newTx ) => {
result [ newTx . id ] = newTx ;
return result ;
} , this . store . getState ( ) . transactions ) ,
} ) ;
}
/ * *
* Filters out the unapproved transactions
* removes one transaction from state . This is not intended for external use .
*
* @ private
* @ param { number } targetTransactionId - the transaction to delete
* /
clearUnapprovedTxs ( ) {
const transactions = this . getFullTxList ( ) ;
const nonUnapprovedTxs = transactions . filter (
( tx ) => tx . status !== TRANSACTION _STATUSES . UNAPPROVED ,
) ;
this . _saveTxList ( nonUnapprovedTxs ) ;
_deleteTransaction ( targetTransactionId ) {
const { transactions } = this . store . getState ( ) ;
delete transactions [ targetTransactionId ] ;
this . store . updateState ( {
transactions ,
} ) ;
}
}