aboutsummaryrefslogtreecommitdiffstats
path: root/ui/app/components/app/transaction-activity-log/transaction-activity-log.util.js
diff options
context:
space:
mode:
Diffstat (limited to 'ui/app/components/app/transaction-activity-log/transaction-activity-log.util.js')
-rw-r--r--ui/app/components/app/transaction-activity-log/transaction-activity-log.util.js233
1 files changed, 233 insertions, 0 deletions
diff --git a/ui/app/components/app/transaction-activity-log/transaction-activity-log.util.js b/ui/app/components/app/transaction-activity-log/transaction-activity-log.util.js
new file mode 100644
index 000000000..b74513879
--- /dev/null
+++ b/ui/app/components/app/transaction-activity-log/transaction-activity-log.util.js
@@ -0,0 +1,233 @@
+import { getHexGasTotal } from '../../../helpers/utils/confirm-tx.util'
+
+// path constants
+const STATUS_PATH = '/status'
+const GAS_PRICE_PATH = '/txParams/gasPrice'
+const GAS_LIMIT_PATH = '/txParams/gas'
+
+// op constants
+const REPLACE_OP = 'replace'
+
+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 = {
+ [SUBMITTED_STATUS]: TRANSACTION_SUBMITTED_EVENT,
+ [CONFIRMED_STATUS]: TRANSACTION_CONFIRMED_EVENT,
+ [DROPPED_STATUS]: TRANSACTION_DROPPED_EVENT,
+}
+
+/**
+ * @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 = [],
+ txParams: { gas: paramsGasLimit, gasPrice: paramsGasPrice},
+ xReceipt: { status } = {},
+ type,
+ } = transaction
+
+ let cachedGasLimit = '0x0'
+ let cachedGasPrice = '0x0'
+
+ const historyActivities = history.reduce((acc, base, index) => {
+ // First history item should be transaction creation
+ 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 = []
+
+ base.forEach(entry => {
+ const { op, path, value, timestamp: entryTimestamp } = entry
+ // Not all sub-entries in a history entry have a timestamp. If the sub-entry does not have a
+ // timestamp, the first sub-entry in a history entry should.
+ const timestamp = entryTimestamp || base[0] && base[0].timestamp
+
+ if (path in eventPathsHash && op === REPLACE_OP) {
+ switch (path) {
+ case STATUS_PATH: {
+ const gasFee = cachedGasLimit === '0x0' && cachedGasPrice === '0x0'
+ ? getHexGasTotal({ gasLimit: paramsGasLimit, gasPrice: paramsGasPrice })
+ : getHexGasTotal({ gasLimit: cachedGasLimit, gasPrice: cachedGasPrice })
+
+ if (value in statusHash) {
+ 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
+ }
+
+ // 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({
+ id,
+ hash,
+ eventKey: TRANSACTION_UPDATED_EVENT,
+ timestamp,
+ })
+ }
+ }
+ }
+ })
+
+ return acc.concat(events)
+ }
+
+ return acc
+ }, [])
+
+ // 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({ 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)
+}