Skip to content

Commit e0553f3

Browse files
authored
Merge branch 'develop' into fix-missing-usd-conversion
2 parents 06f7bf4 + 564ad2f commit e0553f3

File tree

18 files changed

+517
-139
lines changed

18 files changed

+517
-139
lines changed

.circleci/config.yml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -670,6 +670,13 @@ jobs:
670670
- run:
671671
name: test:coverage:jest
672672
command: yarn test:coverage:jest
673+
- run:
674+
name: Validate coverage thresholds
675+
command: |
676+
if ! git diff --exit-code jest.config.js development/jest.config.js; then
677+
echo "Detected changes in coverage thresholds"
678+
exit 1
679+
fi
673680
- persist_to_workspace:
674681
root: .
675682
paths:

app/scripts/controllers/metametrics.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -621,7 +621,9 @@ export default class MetaMetricsController {
621621
*/
622622
_getAllNFTsFlattened = memoize((allCollectibles = {}) => {
623623
return Object.values(allCollectibles)
624-
.flatMap((chainNFTs) => Object.values(chainNFTs))
624+
.reduce((result, chainNFTs) => {
625+
return result.concat(Object.values(chainNFTs));
626+
}, [])
625627
.flat();
626628
});
627629

app/scripts/controllers/transactions/index.js

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -647,6 +647,35 @@ export default class TransactionController extends EventEmitter {
647647
return this._getTransaction(txId);
648648
}
649649

650+
/**
651+
* append new sendFlowHistory to the transaction with id if the transaction
652+
* state is unapproved. Returns the updated transaction.
653+
*
654+
* @param {string} txId - transaction id
655+
* @param {Array<{ entry: string, timestamp: number }>} sendFlowHistory -
656+
* history to add to the sendFlowHistory property of txMeta.
657+
* @returns {TransactionMeta} the txMeta of the updated transaction
658+
*/
659+
updateTransactionSendFlowHistory(txId, sendFlowHistory) {
660+
this._throwErrorIfNotUnapprovedTx(txId, 'updateTransactionSendFlowHistory');
661+
const txMeta = this._getTransaction(txId);
662+
663+
// only update what is defined
664+
const note = `Update sendFlowHistory for ${txId}`;
665+
666+
this.txStateManager.updateTransaction(
667+
{
668+
...txMeta,
669+
sendFlowHistory: [
670+
...(txMeta?.sendFlowHistory ?? []),
671+
...sendFlowHistory,
672+
],
673+
},
674+
note,
675+
);
676+
return this._getTransaction(txId);
677+
}
678+
650679
// ====================================================================================================================================================
651680

652681
/**
@@ -656,9 +685,15 @@ export default class TransactionController extends EventEmitter {
656685
* @param txParams
657686
* @param origin
658687
* @param transactionType
688+
* @param sendFlowHistory
659689
* @returns {txMeta}
660690
*/
661-
async addUnapprovedTransaction(txParams, origin, transactionType) {
691+
async addUnapprovedTransaction(
692+
txParams,
693+
origin,
694+
transactionType,
695+
sendFlowHistory = [],
696+
) {
662697
if (
663698
transactionType !== undefined &&
664699
!VALID_UNAPPROVED_TRANSACTION_TYPES.includes(transactionType)
@@ -683,6 +718,7 @@ export default class TransactionController extends EventEmitter {
683718
let txMeta = this.txStateManager.generateTxMeta({
684719
txParams: normalizedTxParams,
685720
origin,
721+
sendFlowHistory,
686722
});
687723

688724
if (origin === ORIGIN_METAMASK) {

app/scripts/controllers/transactions/tx-state-manager.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,7 @@ export default class TransactionStateManager extends EventEmitter {
127127
chainId,
128128
loadingDefaults: true,
129129
dappSuggestedGasFees,
130+
sendFlowHistory: [],
130131
...opts,
131132
};
132133
}

app/scripts/metamask-controller.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1637,6 +1637,9 @@ export default class MetamaskController extends EventEmitter {
16371637
updateTransactionGasFees: txController.updateTransactionGasFees.bind(
16381638
txController,
16391639
),
1640+
updateTransactionSendFlowHistory: txController.updateTransactionSendFlowHistory.bind(
1641+
txController,
1642+
),
16401643

16411644
updateSwapApprovalTransaction: txController.updateSwapApprovalTransaction.bind(
16421645
txController,

development/jest.config.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ module.exports = {
22
displayName: '/development',
33
collectCoverageFrom: ['<rootDir>/**/*.js'],
44
coverageDirectory: '../jest-coverage/development/',
5-
coverageReporters: ['html', 'text-summary'],
5+
coverageReporters: ['html', 'text-summary', 'json-summary'],
66
coverageThreshold: {
77
'./development/build/transforms/**/*.js': {
88
branches: 100,

jest.config.js

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,13 @@ module.exports = {
66
],
77
coverageDirectory: './jest-coverage/main',
88
coveragePathIgnorePatterns: ['.stories.js', '.snap'],
9-
coverageReporters: ['html', 'text-summary'],
9+
coverageReporters: ['html', 'text-summary', 'json-summary'],
1010
coverageThreshold: {
1111
global: {
12-
branches: 35,
13-
functions: 37,
14-
lines: 43,
15-
statements: 43,
12+
branches: 44,
13+
functions: 42,
14+
lines: 48,
15+
statements: 48,
1616
},
1717
'./app/scripts/controllers/permissions/**/*.js': {
1818
branches: 100,

package.json

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@
3434
"test:e2e:firefox:snaps": "SELENIUM_BROWSER=firefox node test/e2e/run-all.js --snaps",
3535
"test:e2e:single": "node test/e2e/run-e2e-test.js",
3636
"test:coverage:mocha": "nyc --reporter=text --reporter=html yarn test:unit:mocha",
37-
"test:coverage:jest": "yarn test:unit:jest --coverage --maxWorkers=2",
37+
"test:coverage:jest": "yarn test:unit:jest --coverage --maxWorkers=2 && yarn jest-it-up -m 5",
3838
"ganache:start": "./development/run-ganache.sh",
3939
"sentry:publish": "node ./development/sentry-publish.js",
4040
"lint": "yarn lint:prettier && yarn lint:eslint && yarn lint:tsc && yarn lint:styles",
@@ -276,7 +276,7 @@
276276
"@typescript-eslint/parser": "^4.20.0",
277277
"addons-linter": "1.14.0",
278278
"babelify": "^10.0.0",
279-
"bify-module-groups": "^1.0.0",
279+
"bify-module-groups": "^2.0.0",
280280
"brfs": "^2.0.2",
281281
"browser-util-inspect": "^0.2.0",
282282
"browserify": "^16.5.1",
@@ -325,6 +325,7 @@
325325
"improved-yarn-audit": "^3.0.0",
326326
"jest": "^26.6.3",
327327
"jest-canvas-mock": "^2.3.1",
328+
"jest-it-up": "^2.0.2",
328329
"jsdom": "^11.2.0",
329330
"koa": "^2.7.0",
330331
"lavamoat": "^6.1.2",

ui/ducks/send/send.js

Lines changed: 98 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ import {
6060
getTokenStandardAndDetails,
6161
showModal,
6262
addUnapprovedTransactionAndRouteToConfirmationPage,
63+
updateTransactionSendFlowHistory,
6364
} from '../../store/actions';
6465
import { setCustomGasLimit } from '../gas/gas.duck';
6566
import {
@@ -110,6 +111,7 @@ import {
110111
import { readAddressAsContract } from '../../../shared/modules/contract-utils';
111112
import { INVALID_ASSET_TYPE } from '../../helpers/constants/error-keys';
112113
import { isEqualCaseInsensitive } from '../../../shared/modules/string-utils';
114+
import { getValueFromWeiHex } from '../../helpers/utils/confirm-tx.util';
113115
// typedefs
114116
/**
115117
* @typedef {import('@reduxjs/toolkit').PayloadAction} PayloadAction
@@ -684,12 +686,19 @@ export const initialState = {
684686
// Layer 1 gas fee total on multi-layer fee networks
685687
layer1GasTotal: '0x0',
686688
},
689+
history: [],
687690
};
688691

689692
const slice = createSlice({
690693
name,
691694
initialState,
692695
reducers: {
696+
addHistoryEntry: (state, action) => {
697+
state.history.push({
698+
entry: action.payload,
699+
timestamp: Date.now(),
700+
});
701+
},
693702
/**
694703
* update current amount.value in state and run post update validation of
695704
* the amount field and the send state. Recomputes the draftTransaction
@@ -1402,9 +1411,10 @@ const {
14021411
updateGasLimit,
14031412
validateRecipientUserInput,
14041413
updateRecipientSearchMode,
1414+
addHistoryEntry,
14051415
} = actions;
14061416

1407-
export { useDefaultGas, useCustomGas, updateGasLimit };
1417+
export { useDefaultGas, useCustomGas, updateGasLimit, addHistoryEntry };
14081418

14091419
// Action Creators
14101420

@@ -1421,6 +1431,9 @@ export { useDefaultGas, useCustomGas, updateGasLimit };
14211431
*/
14221432
export function updateGasPrice(gasPrice) {
14231433
return (dispatch) => {
1434+
dispatch(
1435+
addHistoryEntry(`sendFlow - user set legacy gasPrice to ${gasPrice}`),
1436+
);
14241437
dispatch(
14251438
actions.updateGasFees({
14261439
gasPrice,
@@ -1452,8 +1465,36 @@ export function resetSendState() {
14521465
*/
14531466
export function updateSendAmount(amount) {
14541467
return async (dispatch, getState) => {
1455-
await dispatch(actions.updateSendAmount(amount));
14561468
const state = getState();
1469+
let logAmount = amount;
1470+
if (state[name].asset.type === ASSET_TYPES.TOKEN) {
1471+
const multiplier = Math.pow(
1472+
10,
1473+
Number(state[name].asset.details?.decimals || 0),
1474+
);
1475+
const decimalValueString = conversionUtil(addHexPrefix(amount), {
1476+
fromNumericBase: 'hex',
1477+
toNumericBase: 'dec',
1478+
toCurrency: state[name].asset.details?.symbol,
1479+
conversionRate: multiplier,
1480+
invertConversionRate: true,
1481+
});
1482+
1483+
logAmount = `${Number(decimalValueString) ? decimalValueString : ''} ${
1484+
state[name].asset.details?.symbol
1485+
}`;
1486+
} else {
1487+
const ethValue = getValueFromWeiHex({
1488+
value: amount,
1489+
toCurrency: ETH,
1490+
numberOfDecimals: 8,
1491+
});
1492+
logAmount = `${ethValue} ${ETH}`;
1493+
}
1494+
await dispatch(
1495+
addHistoryEntry(`sendFlow - user set amount to ${logAmount}`),
1496+
);
1497+
await dispatch(actions.updateSendAmount(amount));
14571498
if (state.send.amount.mode === AMOUNT_MODES.MAX) {
14581499
await dispatch(actions.updateAmountMode(AMOUNT_MODES.INPUT));
14591500
}
@@ -1482,6 +1523,19 @@ export function updateSendAmount(amount) {
14821523
*/
14831524
export function updateSendAsset({ type, details }) {
14841525
return async (dispatch, getState) => {
1526+
dispatch(addHistoryEntry(`sendFlow - user set asset type to ${type}`));
1527+
dispatch(
1528+
addHistoryEntry(
1529+
`sendFlow - user set asset symbol to ${details?.symbol ?? 'undefined'}`,
1530+
),
1531+
);
1532+
dispatch(
1533+
addHistoryEntry(
1534+
`sendFlow - user set asset address to ${
1535+
details?.address ?? 'undefined'
1536+
}`,
1537+
),
1538+
);
14851539
const state = getState();
14861540
let { balance, error } = state.send.asset;
14871541
const userAddress = state.send.account.address ?? getSelectedAddress(state);
@@ -1580,6 +1634,11 @@ export function updateSendAsset({ type, details }) {
15801634
* it only applicable for use within action creators.
15811635
*/
15821636
const debouncedValidateRecipientUserInput = debounce((dispatch, payload) => {
1637+
dispatch(
1638+
addHistoryEntry(
1639+
`sendFlow - user typed ${payload.userInput} into recipient input field`,
1640+
),
1641+
);
15831642
dispatch(validateRecipientUserInput(payload));
15841643
}, 300);
15851644

@@ -1600,6 +1659,7 @@ export function updateRecipientUserInput(userInput) {
16001659
const useTokenDetection = getUseTokenDetection(state);
16011660
const tokenAddressList = Object.keys(getTokenList(state));
16021661
debouncedValidateRecipientUserInput(dispatch, {
1662+
userInput,
16031663
chainId,
16041664
tokens,
16051665
useTokenDetection,
@@ -1610,12 +1670,22 @@ export function updateRecipientUserInput(userInput) {
16101670

16111671
export function useContactListForRecipientSearch() {
16121672
return (dispatch) => {
1673+
dispatch(
1674+
addHistoryEntry(
1675+
`sendFlow - user selected back to all on recipient screen`,
1676+
),
1677+
);
16131678
dispatch(updateRecipientSearchMode(RECIPIENT_SEARCH_MODES.CONTACT_LIST));
16141679
};
16151680
}
16161681

16171682
export function useMyAccountsForRecipientSearch() {
16181683
return (dispatch) => {
1684+
dispatch(
1685+
addHistoryEntry(
1686+
`sendFlow - user selected transfer to my accounts on recipient screen`,
1687+
),
1688+
);
16191689
dispatch(updateRecipientSearchMode(RECIPIENT_SEARCH_MODES.MY_ACCOUNTS));
16201690
};
16211691
}
@@ -1638,6 +1708,8 @@ export function useMyAccountsForRecipientSearch() {
16381708
*/
16391709
export function updateRecipient({ address, nickname }) {
16401710
return async (dispatch, getState) => {
1711+
// Do not addHistoryEntry here as this is called from a number of places
1712+
// each with significance to the user and transaction history.
16411713
const state = getState();
16421714
const nicknameFromAddressBookEntryOrAccountName =
16431715
getAddressBookEntryOrAccountName(state, address) ?? '';
@@ -1656,6 +1728,7 @@ export function updateRecipient({ address, nickname }) {
16561728
*/
16571729
export function resetRecipientInput() {
16581730
return async (dispatch) => {
1731+
await dispatch(addHistoryEntry(`sendFlow - user cleared recipient input`));
16591732
await dispatch(updateRecipientUserInput(''));
16601733
await dispatch(updateRecipient({ address: '', nickname: '' }));
16611734
await dispatch(resetEnsResolution());
@@ -1675,6 +1748,9 @@ export function resetRecipientInput() {
16751748
*/
16761749
export function updateSendHexData(hexData) {
16771750
return async (dispatch, getState) => {
1751+
await dispatch(
1752+
addHistoryEntry(`sendFlow - user added custom hexData ${hexData}`),
1753+
);
16781754
await dispatch(actions.updateUserInputHexData(hexData));
16791755
const state = getState();
16801756
if (state.send.asset.type === ASSET_TYPES.NATIVE) {
@@ -1695,9 +1771,11 @@ export function toggleSendMaxMode() {
16951771
if (state.send.amount.mode === AMOUNT_MODES.MAX) {
16961772
await dispatch(actions.updateAmountMode(AMOUNT_MODES.INPUT));
16971773
await dispatch(actions.updateSendAmount('0x0'));
1774+
await dispatch(addHistoryEntry(`sendFlow - user toggled max mode off`));
16981775
} else {
16991776
await dispatch(actions.updateAmountMode(AMOUNT_MODES.MAX));
17001777
await dispatch(actions.updateAmountToMax());
1778+
await dispatch(addHistoryEntry(`sendFlow - user toggled max mode on`));
17011779
}
17021780
await dispatch(computeEstimatedGasLimit());
17031781
};
@@ -1746,6 +1824,12 @@ export function signTransaction() {
17461824
eip1559support ? eip1559OnlyTxParamsToUpdate : txParams,
17471825
),
17481826
};
1827+
await dispatch(
1828+
addHistoryEntry(
1829+
`sendFlow - user clicked next and transaction should be updated in controller`,
1830+
),
1831+
);
1832+
await dispatch(updateTransactionSendFlowHistory(id, state[name].history));
17491833
dispatch(updateEditableParams(id, editingTx.txParams));
17501834
dispatch(updateTransactionGasFees(id, editingTx.txParams));
17511835
} else {
@@ -1757,10 +1841,17 @@ export function signTransaction() {
17571841
? TRANSACTION_TYPES.TOKEN_METHOD_TRANSFER_FROM
17581842
: TRANSACTION_TYPES.TOKEN_METHOD_TRANSFER;
17591843
}
1844+
await dispatch(
1845+
addHistoryEntry(
1846+
`sendFlow - user clicked next and transaction should be added to controller`,
1847+
),
1848+
);
1849+
17601850
dispatch(
17611851
addUnapprovedTransactionAndRouteToConfirmationPage(
17621852
txParams,
17631853
transactionType,
1854+
state[name].history,
17641855
),
17651856
);
17661857
}
@@ -1775,6 +1866,11 @@ export function editTransaction(
17751866
) {
17761867
return async (dispatch, getState) => {
17771868
const state = getState();
1869+
await dispatch(
1870+
addHistoryEntry(
1871+
`sendFlow - user clicked edit on transaction with id ${transactionId}`,
1872+
),
1873+
);
17781874
const unapprovedTransactions = getUnapprovedTxs(state);
17791875
const transaction = unapprovedTransactions[transactionId];
17801876
const { txParams } = transaction;

0 commit comments

Comments
 (0)