aboutsummaryrefslogtreecommitdiffstats
path: root/ui/app/selectors
diff options
context:
space:
mode:
authorAlexander Tseung <alextsg@users.noreply.github.com>2018-12-10 04:48:06 +0800
committerGitHub <noreply@github.com>2018-12-10 04:48:06 +0800
commitd8ab9cc002c10757b7382a174dafff7a0247e307 (patch)
treed0a46ac3ca2334ddec2ee240214d67a8122e81b7 /ui/app/selectors
parent575fb607c3b8deea831aa28293303991b3f6be29 (diff)
downloadtangerine-wallet-browser-d8ab9cc002c10757b7382a174dafff7a0247e307.tar
tangerine-wallet-browser-d8ab9cc002c10757b7382a174dafff7a0247e307.tar.gz
tangerine-wallet-browser-d8ab9cc002c10757b7382a174dafff7a0247e307.tar.bz2
tangerine-wallet-browser-d8ab9cc002c10757b7382a174dafff7a0247e307.tar.lz
tangerine-wallet-browser-d8ab9cc002c10757b7382a174dafff7a0247e307.tar.xz
tangerine-wallet-browser-d8ab9cc002c10757b7382a174dafff7a0247e307.tar.zst
tangerine-wallet-browser-d8ab9cc002c10757b7382a174dafff7a0247e307.zip
Group transactions by nonce (#5886)
Diffstat (limited to 'ui/app/selectors')
-rw-r--r--ui/app/selectors/transactions.js232
1 files changed, 220 insertions, 12 deletions
diff --git a/ui/app/selectors/transactions.js b/ui/app/selectors/transactions.js
index 479002794..301e8d11f 100644
--- a/ui/app/selectors/transactions.js
+++ b/ui/app/selectors/transactions.js
@@ -1,16 +1,44 @@
import { createSelector } from 'reselect'
-import { valuesFor } from '../util'
import {
UNAPPROVED_STATUS,
APPROVED_STATUS,
SUBMITTED_STATUS,
+ CONFIRMED_STATUS,
} from '../constants/transactions'
+import {
+ TRANSACTION_TYPE_CANCEL,
+ TRANSACTION_TYPE_RETRY,
+} from '../../../app/scripts/controllers/transactions/enums'
+import { hexToDecimal } from '../helpers/conversions.util'
import { selectedTokenAddressSelector } from './tokens'
+import txHelper from '../../lib/tx-helper'
export const shapeShiftTxListSelector = state => state.metamask.shapeShiftTxList
export const unapprovedMsgsSelector = state => state.metamask.unapprovedMsgs
export const selectedAddressTxListSelector = state => state.metamask.selectedAddressTxList
+export const unapprovedPersonalMsgsSelector = state => state.metamask.unapprovedPersonalMsgs
+export const unapprovedTypedMessagesSelector = state => state.metamask.unapprovedTypedMessages
+export const networkSelector = state => state.metamask.network
+
+export const unapprovedMessagesSelector = createSelector(
+ unapprovedMsgsSelector,
+ unapprovedPersonalMsgsSelector,
+ unapprovedTypedMessagesSelector,
+ networkSelector,
+ (
+ unapprovedMsgs = {},
+ unapprovedPersonalMsgs = {},
+ unapprovedTypedMessages = {},
+ network
+ ) => txHelper(
+ {},
+ unapprovedMsgs,
+ unapprovedPersonalMsgs,
+ unapprovedTypedMessages,
+ network
+ ) || []
+)
const pendingStatusHash = {
[UNAPPROVED_STATUS]: true,
@@ -18,14 +46,18 @@ const pendingStatusHash = {
[SUBMITTED_STATUS]: true,
}
+const priorityStatusHash = {
+ ...pendingStatusHash,
+ [CONFIRMED_STATUS]: true,
+}
+
export const transactionsSelector = createSelector(
selectedTokenAddressSelector,
- unapprovedMsgsSelector,
+ unapprovedMessagesSelector,
shapeShiftTxListSelector,
selectedAddressTxListSelector,
- (selectedTokenAddress, unapprovedMsgs = {}, shapeShiftTxList = [], transactions = []) => {
- const unapprovedMsgsList = valuesFor(unapprovedMsgs)
- const txsToRender = transactions.concat(unapprovedMsgsList, shapeShiftTxList)
+ (selectedTokenAddress, unapprovedMessages = [], shapeShiftTxList = [], transactions = []) => {
+ const txsToRender = transactions.concat(unapprovedMessages, shapeShiftTxList)
return selectedTokenAddress
? txsToRender
@@ -36,23 +68,199 @@ export const transactionsSelector = createSelector(
}
)
-export const pendingTransactionsSelector = createSelector(
+/**
+ * @name insertOrderedNonce
+ * @private
+ * @description Inserts (mutates) a nonce into an array of ordered nonces, sorted in ascending
+ * order.
+ * @param {string[]} nonces - Array of nonce strings in hex
+ * @param {string} nonceToInsert - Nonce string in hex to be inserted into the array of nonces.
+ * @returns {string[]}
+ */
+const insertOrderedNonce = (nonces, nonceToInsert) => {
+ let insertIndex = nonces.length
+
+ for (let i = 0; i < nonces.length; i++) {
+ const nonce = nonces[i]
+
+ if (Number(hexToDecimal(nonce)) < Number(hexToDecimal(nonceToInsert))) {
+ insertIndex = i
+ break
+ }
+ }
+
+ nonces.splice(insertIndex, 0, nonceToInsert)
+}
+
+/**
+ * @name insertTransactionByTime
+ * @private
+ * @description Inserts (mutates) a transaction object into an array of ordered transactions, sorted
+ * in ascending order by time.
+ * @param {Object[]} transactions - Array of transaction objects.
+ * @param {Object} transaction - Transaction object to be inserted into the array of transactions.
+ * @returns {Object[]}
+ */
+const insertTransactionByTime = (transactions, transaction) => {
+ const { time } = transaction
+
+ let insertIndex = transactions.length
+
+ for (let i = 0; i < transactions.length; i++) {
+ const tx = transactions[i]
+
+ if (tx.time > time) {
+ insertIndex = i
+ break
+ }
+ }
+
+ transactions.splice(insertIndex, 0, transaction)
+}
+
+/**
+ * Contains transactions and properties associated with those transactions of the same nonce.
+ * @typedef {Object} transactionGroup
+ * @property {string} nonce - The nonce that the transactions within this transactionGroup share.
+ * @property {Object[]} transactions - An array of transaction (txMeta) objects.
+ * @property {Object} initialTransaction - The transaction (txMeta) with the lowest "time".
+ * @property {Object} primaryTransaction - Either the latest transaction or the confirmed
+ * transaction.
+ * @property {boolean} hasRetried - True if a transaction in the group was a retry transaction.
+ * @property {boolean} hasCancelled - True if a transaction in the group was a cancel transaction.
+ */
+
+/**
+ * @name insertTransactionGroupByTime
+ * @private
+ * @description Inserts (mutates) a transactionGroup object into an array of ordered
+ * transactionGroups, sorted in ascending order by nonce.
+ * @param {transactionGroup[]} transactionGroups - Array of transactionGroup objects.
+ * @param {transactionGroup} transactionGroup - transactionGroup object to be inserted into the
+ * array of transactionGroups.
+ * @returns {transactionGroup[]}
+ */
+const insertTransactionGroupByTime = (transactionGroups, transactionGroup) => {
+ const { primaryTransaction: { time } = {} } = transactionGroup
+
+ let insertIndex = transactionGroups.length
+
+ for (let i = 0; i < transactionGroups.length; i++) {
+ const txGroup = transactionGroups[i]
+
+ if (txGroup.time > time) {
+ insertIndex = i
+ break
+ }
+ }
+
+ transactionGroups.splice(insertIndex, 0, transactionGroup)
+}
+
+/**
+ * @name nonceSortedTransactionsSelector
+ * @description Returns an array of transactionGroups sorted by nonce in ascending order.
+ * @returns {transactionGroup[]}
+ */
+export const nonceSortedTransactionsSelector = createSelector(
transactionsSelector,
+ (transactions = []) => {
+ const unapprovedTransactionGroups = []
+ const orderedNonces = []
+ const nonceToTransactionsMap = {}
+
+ transactions.forEach(transaction => {
+ const { txParams: { nonce } = {}, status, type, time: txTime } = transaction
+
+ if (typeof nonce === 'undefined') {
+ const transactionGroup = {
+ transactions: [transaction],
+ initialTransaction: transaction,
+ primaryTransaction: transaction,
+ hasRetried: false,
+ hasCancelled: false,
+ }
+
+ insertTransactionGroupByTime(unapprovedTransactionGroups, transactionGroup)
+ } else if (nonce in nonceToTransactionsMap) {
+ const nonceProps = nonceToTransactionsMap[nonce]
+ insertTransactionByTime(nonceProps.transactions, transaction)
+
+ if (status in priorityStatusHash) {
+ const { primaryTransaction: { time: primaryTxTime = 0 } = {} } = nonceProps
+
+ if (status === CONFIRMED_STATUS || txTime > primaryTxTime) {
+ nonceProps.primaryTransaction = transaction
+ }
+ }
+
+ const { initialTransaction: { time: initialTxTime = 0 } = {} } = nonceProps
+
+ // Used to display the transaction action, since we don't want to overwrite the action if
+ // it was replaced with a cancel attempt transaction.
+ if (txTime < initialTxTime) {
+ nonceProps.initialTransaction = transaction
+ }
+
+ if (type === TRANSACTION_TYPE_RETRY) {
+ nonceProps.hasRetried = true
+ }
+
+ if (type === TRANSACTION_TYPE_CANCEL) {
+ nonceProps.hasCancelled = true
+ }
+ } else {
+ nonceToTransactionsMap[nonce] = {
+ nonce,
+ transactions: [transaction],
+ initialTransaction: transaction,
+ primaryTransaction: transaction,
+ hasRetried: transaction.type === TRANSACTION_TYPE_RETRY,
+ hasCancelled: transaction.type === TRANSACTION_TYPE_CANCEL,
+ }
+
+ insertOrderedNonce(orderedNonces, nonce)
+ }
+ })
+
+ const orderedTransactionGroups = orderedNonces.map(nonce => nonceToTransactionsMap[nonce])
+ return unapprovedTransactionGroups.concat(orderedTransactionGroups)
+ }
+)
+
+/**
+ * @name nonceSortedPendingTransactionsSelector
+ * @description Returns an array of transactionGroups where transactions are still pending sorted by
+ * nonce in descending order.
+ * @returns {transactionGroup[]}
+ */
+export const nonceSortedPendingTransactionsSelector = createSelector(
+ nonceSortedTransactionsSelector,
(transactions = []) => (
- transactions.filter(transaction => transaction.status in pendingStatusHash).reverse()
+ transactions
+ .filter(({ primaryTransaction }) => primaryTransaction.status in pendingStatusHash)
+ .reverse()
)
)
-export const submittedPendingTransactionsSelector = createSelector(
- transactionsSelector,
+/**
+ * @name nonceSortedCompletedTransactionsSelector
+ * @description Returns an array of transactionGroups where transactions are confirmed sorted by
+ * nonce in descending order.
+ * @returns {transactionGroup[]}
+ */
+export const nonceSortedCompletedTransactionsSelector = createSelector(
+ nonceSortedTransactionsSelector,
(transactions = []) => (
- transactions.filter(transaction => transaction.status === SUBMITTED_STATUS)
+ transactions.filter(({ primaryTransaction }) => {
+ return !(primaryTransaction.status in pendingStatusHash)
+ })
)
)
-export const completedTransactionsSelector = createSelector(
+export const submittedPendingTransactionsSelector = createSelector(
transactionsSelector,
(transactions = []) => (
- transactions.filter(transaction => !(transaction.status in pendingStatusHash))
+ transactions.filter(transaction => transaction.status === SUBMITTED_STATUS)
)
)