diff options
Diffstat (limited to 'ui/app/components/transaction-activity-log/transaction-activity-log.util.js')
-rw-r--r-- | ui/app/components/transaction-activity-log/transaction-activity-log.util.js | 199 |
1 files changed, 165 insertions, 34 deletions
diff --git a/ui/app/components/transaction-activity-log/transaction-activity-log.util.js b/ui/app/components/transaction-activity-log/transaction-activity-log.util.js index 16597ae1a..6206a4678 100644 --- a/ui/app/components/transaction-activity-log/transaction-activity-log.util.js +++ b/ui/app/components/transaction-activity-log/transaction-activity-log.util.js @@ -1,28 +1,39 @@ +import { getHexGasTotal } from '../../helpers/confirm-transaction/util' + // path constants const STATUS_PATH = '/status' const GAS_PRICE_PATH = '/txParams/gasPrice' - -// status constants -const UNAPPROVED_STATUS = 'unapproved' -const SUBMITTED_STATUS = 'submitted' -const CONFIRMED_STATUS = 'confirmed' -const DROPPED_STATUS = 'dropped' +const GAS_LIMIT_PATH = '/txParams/gas' // op constants const REPLACE_OP = 'replace' -// event constants -const TRANSACTION_CREATED_EVENT = 'transactionCreated' -const TRANSACTION_UPDATED_GAS_EVENT = 'transactionUpdatedGas' -const TRANSACTION_SUBMITTED_EVENT = 'transactionSubmitted' -const TRANSACTION_CONFIRMED_EVENT = 'transactionConfirmed' -const TRANSACTION_DROPPED_EVENT = 'transactionDropped' -const TRANSACTION_UPDATED_EVENT = 'transactionUpdated' -const TRANSACTION_ERRORED_EVENT = 'transactionErrored' +import { + // event constants + TRANSACTION_CREATED_EVENT, + TRANSACTION_SUBMITTED_EVENT, + TRANSACTION_RESUBMITTED_EVENT, + TRANSACTION_CONFIRMED_EVENT, + TRANSACTION_DROPPED_EVENT, + TRANSACTION_UPDATED_EVENT, + TRANSACTION_ERRORED_EVENT, + TRANSACTION_CANCEL_ATTEMPTED_EVENT, + TRANSACTION_CANCEL_SUCCESS_EVENT, + // status constants + SUBMITTED_STATUS, + CONFIRMED_STATUS, + DROPPED_STATUS, +} from './transaction-activity-log.constants' + +import { + TRANSACTION_TYPE_CANCEL, + TRANSACTION_TYPE_RETRY, +} from '../../../../app/scripts/controllers/transactions/enums' const eventPathsHash = { [STATUS_PATH]: true, [GAS_PRICE_PATH]: true, + [GAS_LIMIT_PATH]: true, } const statusHash = { @@ -31,22 +42,39 @@ const statusHash = { [DROPPED_STATUS]: TRANSACTION_DROPPED_EVENT, } -function eventCreator (eventKey, timestamp, value) { - return { - eventKey, - timestamp, - value, - } -} - -export function getActivities (transaction) { - const { history = [], txReceipt: { status } = {} } = transaction - - const historyActivities = history.reduce((acc, base) => { +/** + * @name getActivities + * @param {Object} transaction - txMeta object + * @param {boolean} isFirstTransaction - True if the transaction is the first created transaction + * in the list of transactions with the same nonce. If so, we use this transaction to create the + * transactionCreated activity. + * @returns {Array} + */ +export function getActivities (transaction, isFirstTransaction = false) { + const { id, hash, history = [], txReceipt: { status } = {}, type } = transaction + + let cachedGasLimit = '0x0' + let cachedGasPrice = '0x0' + + const historyActivities = history.reduce((acc, base, index) => { // First history item should be transaction creation - if (!Array.isArray(base) && base.status === UNAPPROVED_STATUS && base.txParams) { - const { time, txParams: { value } = {} } = base - return acc.concat(eventCreator(TRANSACTION_CREATED_EVENT, time, value)) + if (index === 0 && !Array.isArray(base) && base.txParams) { + const { time: timestamp, txParams: { value, gas = '0x0', gasPrice = '0x0' } = {} } = base + // The cached gas limit and gas price are used to display the gas fee in the activity log. We + // need to cache these values because the status update history events don't provide us with + // the latest gas limit and gas price. + cachedGasLimit = gas + cachedGasPrice = gasPrice + + if (isFirstTransaction) { + return acc.concat({ + id, + hash, + eventKey: TRANSACTION_CREATED_EVENT, + timestamp, + value, + }) + } // An entry in the history may be an array of more sub-entries. } else if (Array.isArray(base)) { const events = [] @@ -60,20 +88,69 @@ export function getActivities (transaction) { if (path in eventPathsHash && op === REPLACE_OP) { switch (path) { case STATUS_PATH: { + const gasFee = getHexGasTotal({ gasLimit: cachedGasLimit, gasPrice: cachedGasPrice }) + if (value in statusHash) { - events.push(eventCreator(statusHash[value], timestamp)) + let eventKey = statusHash[value] + + // If the status is 'submitted', we need to determine whether the event is a + // transaction retry or a cancellation attempt. + if (value === SUBMITTED_STATUS) { + if (type === TRANSACTION_TYPE_RETRY) { + eventKey = TRANSACTION_RESUBMITTED_EVENT + } else if (type === TRANSACTION_TYPE_CANCEL) { + eventKey = TRANSACTION_CANCEL_ATTEMPTED_EVENT + } + } else if (value === CONFIRMED_STATUS) { + if (type === TRANSACTION_TYPE_CANCEL) { + eventKey = TRANSACTION_CANCEL_SUCCESS_EVENT + } + } + + events.push({ + id, + hash, + eventKey, + timestamp, + value: gasFee, + }) } break } - case GAS_PRICE_PATH: { - events.push(eventCreator(TRANSACTION_UPDATED_GAS_EVENT, timestamp, value)) + // If the gas price or gas limit has been changed, we update the gasFee of the + // previously submitted event. These events happen when the gas limit and gas price is + // changed at the confirm screen. + case GAS_PRICE_PATH: + case GAS_LIMIT_PATH: { + const lastEvent = events[events.length - 1] || {} + const { lastEventKey } = lastEvent + + if (path === GAS_LIMIT_PATH) { + cachedGasLimit = value + } else if (path === GAS_PRICE_PATH) { + cachedGasPrice = value + } + + if (lastEventKey === TRANSACTION_SUBMITTED_EVENT || + lastEventKey === TRANSACTION_RESUBMITTED_EVENT) { + lastEvent.value = getHexGasTotal({ + gasLimit: cachedGasLimit, + gasPrice: cachedGasPrice, + }) + } + break } default: { - events.push(eventCreator(TRANSACTION_UPDATED_EVENT, timestamp)) + events.push({ + id, + hash, + eventKey: TRANSACTION_UPDATED_EVENT, + timestamp, + }) } } } @@ -88,6 +165,60 @@ export function getActivities (transaction) { // If txReceipt.status is '0x0', that means that an on-chain error occured for the transaction, // so we add an error entry to the Activity Log. return status === '0x0' - ? historyActivities.concat(eventCreator(TRANSACTION_ERRORED_EVENT)) + ? historyActivities.concat({ id, hash, eventKey: TRANSACTION_ERRORED_EVENT }) : historyActivities } + +/** + * @description Removes "Transaction dropped" activities from a list of sorted activities if one of + * the transactions has been confirmed. Typically, if multiple transactions have the same nonce, + * once one transaction is confirmed, the rest are dropped. In this case, we don't want to show + * multiple "Transaction dropped" activities, and instead want to show a single "Transaction + * confirmed". + * @param {Array} activities - List of sorted activities generated from the getActivities function. + * @returns {Array} + */ +function filterSortedActivities (activities) { + const filteredActivities = [] + const hasConfirmedActivity = Boolean(activities.find(({ eventKey }) => ( + eventKey === TRANSACTION_CONFIRMED_EVENT || eventKey === TRANSACTION_CANCEL_SUCCESS_EVENT + ))) + let addedDroppedActivity = false + + activities.forEach(activity => { + if (activity.eventKey === TRANSACTION_DROPPED_EVENT) { + if (!hasConfirmedActivity && !addedDroppedActivity) { + filteredActivities.push(activity) + addedDroppedActivity = true + } + } else { + filteredActivities.push(activity) + } + }) + + return filteredActivities +} + +/** + * Combines the histories of an array of transactions into a single array. + * @param {Array} transactions - Array of txMeta transaction objects. + * @returns {Array} + */ +export function combineTransactionHistories (transactions = []) { + if (!transactions.length) { + return [] + } + + const activities = [] + + transactions.forEach((transaction, index) => { + // The first transaction should be the transaction with the earliest submittedTime. We show the + // 'created' and 'submitted' activities here. All subsequent transactions will use 'resubmitted' + // instead. + const transactionActivities = getActivities(transaction, index === 0) + activities.push(...transactionActivities) + }) + + const sortedActivities = activities.sort((a, b) => a.timestamp - b.timestamp) + return filterSortedActivities(sortedActivities) +} |