1
0
mirror of https://github.com/kremalicious/metamask-extension.git synced 2024-11-25 11:28:51 +01:00
metamask-extension/ui/selectors/transactions.js

365 lines
12 KiB
JavaScript
Raw Normal View History

import { createSelector } from 'reselect';
import {
PRIORITY_STATUS_HASH,
PENDING_STATUS_HASH,
} from '../helpers/constants/transactions';
import { hexToDecimal } from '../helpers/utils/conversions.util';
import txHelper from '../helpers/utils/tx-helper';
import {
TRANSACTION_STATUSES,
TRANSACTION_TYPES,
} from '../../shared/constants/transaction';
import { transactionMatchesNetwork } from '../../shared/modules/transaction.utils';
import { getCurrentChainId, deprecatedGetCurrentNetworkId } from './selectors';
import { getSelectedAddress } from '.';
2020-02-15 21:34:12 +01:00
export const incomingTxListSelector = (state) => {
const { showIncomingTransactions } = state.metamask.featureFlags;
if (!showIncomingTransactions) {
return [];
}
const {
network,
provider: { chainId },
} = state.metamask;
const selectedAddress = getSelectedAddress(state);
2020-11-03 00:41:28 +01:00
return Object.values(state.metamask.incomingTransactions).filter(
(tx) =>
tx.txParams.to === selectedAddress &&
transactionMatchesNetwork(tx, chainId, network),
);
};
export const unapprovedMsgsSelector = (state) => state.metamask.unapprovedMsgs;
2020-11-03 00:41:28 +01:00
export const currentNetworkTxListSelector = (state) =>
state.metamask.currentNetworkTxList;
2020-11-03 00:41:28 +01:00
export const unapprovedPersonalMsgsSelector = (state) =>
state.metamask.unapprovedPersonalMsgs;
2020-11-03 00:41:28 +01:00
export const unapprovedDecryptMsgsSelector = (state) =>
state.metamask.unapprovedDecryptMsgs;
2020-11-03 00:41:28 +01:00
export const unapprovedEncryptionPublicKeyMsgsSelector = (state) =>
state.metamask.unapprovedEncryptionPublicKeyMsgs;
2020-11-03 00:41:28 +01:00
export const unapprovedTypedMessagesSelector = (state) =>
state.metamask.unapprovedTypedMessages;
2018-12-09 21:48:06 +01:00
export const selectedAddressTxListSelector = createSelector(
getSelectedAddress,
currentNetworkTxListSelector,
(selectedAddress, transactions = []) => {
2020-11-03 00:41:28 +01:00
return transactions.filter(
({ txParams }) => txParams.from === selectedAddress,
);
},
);
2018-12-09 21:48:06 +01:00
export const unapprovedMessagesSelector = createSelector(
unapprovedMsgsSelector,
unapprovedPersonalMsgsSelector,
unapprovedDecryptMsgsSelector,
unapprovedEncryptionPublicKeyMsgsSelector,
2018-12-09 21:48:06 +01:00
unapprovedTypedMessagesSelector,
deprecatedGetCurrentNetworkId,
getCurrentChainId,
2018-12-09 21:48:06 +01:00
(
unapprovedMsgs = {},
unapprovedPersonalMsgs = {},
unapprovedDecryptMsgs = {},
unapprovedEncryptionPublicKeyMsgs = {},
2018-12-09 21:48:06 +01:00
unapprovedTypedMessages = {},
network,
chainId,
2020-11-03 00:41:28 +01:00
) =>
txHelper(
{},
unapprovedMsgs,
unapprovedPersonalMsgs,
unapprovedDecryptMsgs,
unapprovedEncryptionPublicKeyMsgs,
unapprovedTypedMessages,
network,
chainId,
2020-11-03 00:41:28 +01:00
) || [],
);
export const transactionSubSelector = createSelector(
2018-12-09 21:48:06 +01:00
unapprovedMessagesSelector,
incomingTxListSelector,
(unapprovedMessages = [], incomingTxList = []) => {
return unapprovedMessages.concat(incomingTxList);
},
);
export const transactionsSelector = createSelector(
transactionSubSelector,
selectedAddressTxListSelector,
(subSelectorTxList = [], selectedAddressTxList = []) => {
const txsToRender = selectedAddressTxList.concat(subSelectorTxList);
return txsToRender.sort((a, b) => b.time - a.time);
},
);
2018-12-09 21:48:06 +01:00
/**
* @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;
2018-12-09 21:48:06 +01:00
for (let i = 0; i < nonces.length; i++) {
const nonce = nonces[i];
2018-12-09 21:48:06 +01:00
if (Number(hexToDecimal(nonce)) > Number(hexToDecimal(nonceToInsert))) {
insertIndex = i;
break;
2018-12-09 21:48:06 +01:00
}
}
nonces.splice(insertIndex, 0, nonceToInsert);
};
2018-12-09 21:48:06 +01:00
/**
* @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;
2018-12-09 21:48:06 +01:00
let insertIndex = transactions.length;
2018-12-09 21:48:06 +01:00
for (let i = 0; i < transactions.length; i++) {
const tx = transactions[i];
2018-12-09 21:48:06 +01:00
if (tx.time > time) {
insertIndex = i;
break;
2018-12-09 21:48:06 +01:00
}
}
transactions.splice(insertIndex, 0, transaction);
};
2018-12-09 21:48:06 +01:00
/**
* 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.
*/
const insertTransactionGroupByTime = (transactionGroups, transactionGroup) => {
2020-11-03 00:41:28 +01:00
const {
primaryTransaction: { time: groupToInsertTime } = {},
} = transactionGroup;
2018-12-09 21:48:06 +01:00
let insertIndex = transactionGroups.length;
2018-12-09 21:48:06 +01:00
for (let i = 0; i < transactionGroups.length; i++) {
const txGroup = transactionGroups[i];
const { primaryTransaction: { time } = {} } = txGroup;
2018-12-09 21:48:06 +01:00
if (time > groupToInsertTime) {
insertIndex = i;
break;
2018-12-09 21:48:06 +01:00
}
}
transactionGroups.splice(insertIndex, 0, transactionGroup);
};
2018-12-09 21:48:06 +01:00
/**
* @name mergeNonNonceTransactionGroups
* @private
* @description Inserts (mutates) transactionGroups that are not to be ordered by nonce into an array
* of nonce-ordered transactionGroups by time.
* @param {transactionGroup[]} orderedTransactionGroups - Array of transactionGroups ordered by
* nonce.
* @param {transactionGroup[]} nonNonceTransactionGroups - Array of transactionGroups not intended to be ordered by nonce,
* but intended to be ordered by timestamp
*/
2020-11-03 00:41:28 +01:00
const mergeNonNonceTransactionGroups = (
orderedTransactionGroups,
nonNonceTransactionGroups,
) => {
nonNonceTransactionGroups.forEach((transactionGroup) => {
insertTransactionGroupByTime(orderedTransactionGroups, transactionGroup);
});
};
2018-12-09 21:48:06 +01:00
/**
* @name nonceSortedTransactionsSelector
* @description Returns an array of transactionGroups sorted by nonce in ascending order.
* @returns {transactionGroup[]}
*/
export const nonceSortedTransactionsSelector = createSelector(
transactionsSelector,
2018-12-09 21:48:06 +01:00
(transactions = []) => {
const unapprovedTransactionGroups = [];
const incomingTransactionGroups = [];
const orderedNonces = [];
const nonceToTransactionsMap = {};
2018-12-09 21:48:06 +01:00
2020-02-15 21:34:12 +01:00
transactions.forEach((transaction) => {
2020-11-03 00:41:28 +01:00
const {
txParams: { nonce } = {},
status,
type,
time: txTime,
} = transaction;
2018-12-09 21:48:06 +01:00
if (typeof nonce === 'undefined' || type === TRANSACTION_TYPES.INCOMING) {
2018-12-09 21:48:06 +01:00
const transactionGroup = {
transactions: [transaction],
initialTransaction: transaction,
primaryTransaction: transaction,
hasRetried: false,
hasCancelled: false,
};
2018-12-09 21:48:06 +01:00
if (type === TRANSACTION_TYPES.INCOMING) {
incomingTransactionGroups.push(transactionGroup);
} else {
2020-11-03 00:41:28 +01:00
insertTransactionGroupByTime(
unapprovedTransactionGroups,
transactionGroup,
);
}
2018-12-09 21:48:06 +01:00
} else if (nonce in nonceToTransactionsMap) {
const nonceProps = nonceToTransactionsMap[nonce];
insertTransactionByTime(nonceProps.transactions, transaction);
2018-12-09 21:48:06 +01:00
2020-11-03 00:41:28 +01:00
const {
primaryTransaction: { time: primaryTxTime = 0 } = {},
} = nonceProps;
2020-11-03 00:41:28 +01:00
const previousPrimaryIsNetworkFailure =
nonceProps.primaryTransaction.status ===
TRANSACTION_STATUSES.FAILED &&
nonceProps.primaryTransaction?.txReceipt?.status !== '0x0';
2020-11-03 00:41:28 +01:00
const currentTransactionIsOnChainFailure =
transaction?.txReceipt?.status === '0x0';
2020-11-03 00:41:28 +01:00
if (
status === TRANSACTION_STATUSES.CONFIRMED ||
2020-11-03 00:41:28 +01:00
currentTransactionIsOnChainFailure ||
previousPrimaryIsNetworkFailure ||
(txTime > primaryTxTime && status in PRIORITY_STATUS_HASH)
) {
nonceProps.primaryTransaction = transaction;
2018-12-09 21:48:06 +01:00
}
2020-11-03 00:41:28 +01:00
const {
initialTransaction: { time: initialTxTime = 0 } = {},
} = nonceProps;
2018-12-09 21:48:06 +01:00
// 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;
2018-12-09 21:48:06 +01:00
}
2021-09-15 23:42:06 +02:00
if (
type === TRANSACTION_TYPES.RETRY &&
status in PRIORITY_STATUS_HASH
) {
nonceProps.hasRetried = true;
2018-12-09 21:48:06 +01:00
}
2021-09-15 23:42:06 +02:00
if (
type === TRANSACTION_TYPES.CANCEL &&
status in PRIORITY_STATUS_HASH
) {
nonceProps.hasCancelled = true;
2018-12-09 21:48:06 +01:00
}
} else {
nonceToTransactionsMap[nonce] = {
nonce,
transactions: [transaction],
initialTransaction: transaction,
primaryTransaction: transaction,
2021-09-15 23:42:06 +02:00
hasRetried:
transaction.type === TRANSACTION_TYPES.RETRY &&
transaction.status in PRIORITY_STATUS_HASH,
hasCancelled:
transaction.type === TRANSACTION_TYPES.CANCEL &&
transaction.status in PRIORITY_STATUS_HASH,
};
2018-12-09 21:48:06 +01:00
insertOrderedNonce(orderedNonces, nonce);
2018-12-09 21:48:06 +01:00
}
});
2018-12-09 21:48:06 +01:00
2020-11-03 00:41:28 +01:00
const orderedTransactionGroups = orderedNonces.map(
(nonce) => nonceToTransactionsMap[nonce],
);
2020-11-03 00:41:28 +01:00
mergeNonNonceTransactionGroups(
orderedTransactionGroups,
incomingTransactionGroups,
);
return unapprovedTransactionGroups.concat(orderedTransactionGroups);
},
);
2018-12-09 21:48:06 +01:00
/**
* @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,
2020-11-03 00:41:28 +01:00
(transactions = []) =>
transactions.filter(
({ primaryTransaction }) =>
primaryTransaction.status in PENDING_STATUS_HASH,
),
);
2018-12-09 21:48:06 +01:00
/**
* @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,
2020-11-03 00:41:28 +01:00
(transactions = []) =>
transactions
2020-11-03 00:41:28 +01:00
.filter(
({ primaryTransaction }) =>
!(primaryTransaction.status in PENDING_STATUS_HASH),
)
.reverse(),
);
2018-08-07 07:39:54 +02:00
2018-12-09 21:48:06 +01:00
export const submittedPendingTransactionsSelector = createSelector(
transactionsSelector,
2020-11-03 00:41:28 +01:00
(transactions = []) =>
transactions.filter(
(transaction) => transaction.status === TRANSACTION_STATUSES.SUBMITTED,
2020-11-03 00:41:28 +01:00
),
);