mirror of
https://github.com/kremalicious/metamask-extension.git
synced 2024-11-23 02:10:12 +01:00
Track send flow history on txMeta (#14510)
This commit is contained in:
parent
214211f847
commit
f251ca4ff2
@ -647,6 +647,35 @@ export default class TransactionController extends EventEmitter {
|
||||
return this._getTransaction(txId);
|
||||
}
|
||||
|
||||
/**
|
||||
* append new sendFlowHistory to the transaction with id if the transaction
|
||||
* state is unapproved. Returns the updated transaction.
|
||||
*
|
||||
* @param {string} txId - transaction id
|
||||
* @param {Array<{ entry: string, timestamp: number }>} sendFlowHistory -
|
||||
* history to add to the sendFlowHistory property of txMeta.
|
||||
* @returns {TransactionMeta} the txMeta of the updated transaction
|
||||
*/
|
||||
updateTransactionSendFlowHistory(txId, sendFlowHistory) {
|
||||
this._throwErrorIfNotUnapprovedTx(txId, 'updateTransactionSendFlowHistory');
|
||||
const txMeta = this._getTransaction(txId);
|
||||
|
||||
// only update what is defined
|
||||
const note = `Update sendFlowHistory for ${txId}`;
|
||||
|
||||
this.txStateManager.updateTransaction(
|
||||
{
|
||||
...txMeta,
|
||||
sendFlowHistory: [
|
||||
...(txMeta?.sendFlowHistory ?? []),
|
||||
...sendFlowHistory,
|
||||
],
|
||||
},
|
||||
note,
|
||||
);
|
||||
return this._getTransaction(txId);
|
||||
}
|
||||
|
||||
// ====================================================================================================================================================
|
||||
|
||||
/**
|
||||
@ -656,9 +685,15 @@ export default class TransactionController extends EventEmitter {
|
||||
* @param txParams
|
||||
* @param origin
|
||||
* @param transactionType
|
||||
* @param sendFlowHistory
|
||||
* @returns {txMeta}
|
||||
*/
|
||||
async addUnapprovedTransaction(txParams, origin, transactionType) {
|
||||
async addUnapprovedTransaction(
|
||||
txParams,
|
||||
origin,
|
||||
transactionType,
|
||||
sendFlowHistory = [],
|
||||
) {
|
||||
if (
|
||||
transactionType !== undefined &&
|
||||
!VALID_UNAPPROVED_TRANSACTION_TYPES.includes(transactionType)
|
||||
@ -683,6 +718,7 @@ export default class TransactionController extends EventEmitter {
|
||||
let txMeta = this.txStateManager.generateTxMeta({
|
||||
txParams: normalizedTxParams,
|
||||
origin,
|
||||
sendFlowHistory,
|
||||
});
|
||||
|
||||
if (origin === ORIGIN_METAMASK) {
|
||||
|
@ -127,6 +127,7 @@ export default class TransactionStateManager extends EventEmitter {
|
||||
chainId,
|
||||
loadingDefaults: true,
|
||||
dappSuggestedGasFees,
|
||||
sendFlowHistory: [],
|
||||
...opts,
|
||||
};
|
||||
}
|
||||
|
@ -1632,6 +1632,9 @@ export default class MetamaskController extends EventEmitter {
|
||||
updateTransactionGasFees: txController.updateTransactionGasFees.bind(
|
||||
txController,
|
||||
),
|
||||
updateTransactionSendFlowHistory: txController.updateTransactionSendFlowHistory.bind(
|
||||
txController,
|
||||
),
|
||||
|
||||
updateSwapApprovalTransaction: txController.updateSwapApprovalTransaction.bind(
|
||||
txController,
|
||||
|
@ -60,6 +60,7 @@ import {
|
||||
getTokenStandardAndDetails,
|
||||
showModal,
|
||||
addUnapprovedTransactionAndRouteToConfirmationPage,
|
||||
updateTransactionSendFlowHistory,
|
||||
} from '../../store/actions';
|
||||
import { setCustomGasLimit } from '../gas/gas.duck';
|
||||
import {
|
||||
@ -110,6 +111,7 @@ import {
|
||||
import { readAddressAsContract } from '../../../shared/modules/contract-utils';
|
||||
import { INVALID_ASSET_TYPE } from '../../helpers/constants/error-keys';
|
||||
import { isEqualCaseInsensitive } from '../../../shared/modules/string-utils';
|
||||
import { getValueFromWeiHex } from '../../helpers/utils/confirm-tx.util';
|
||||
// typedefs
|
||||
/**
|
||||
* @typedef {import('@reduxjs/toolkit').PayloadAction} PayloadAction
|
||||
@ -684,12 +686,19 @@ export const initialState = {
|
||||
// Layer 1 gas fee total on multi-layer fee networks
|
||||
layer1GasTotal: '0x0',
|
||||
},
|
||||
history: [],
|
||||
};
|
||||
|
||||
const slice = createSlice({
|
||||
name,
|
||||
initialState,
|
||||
reducers: {
|
||||
addHistoryEntry: (state, action) => {
|
||||
state.history.push({
|
||||
entry: action.payload,
|
||||
timestamp: Date.now(),
|
||||
});
|
||||
},
|
||||
/**
|
||||
* update current amount.value in state and run post update validation of
|
||||
* the amount field and the send state. Recomputes the draftTransaction
|
||||
@ -1402,9 +1411,10 @@ const {
|
||||
updateGasLimit,
|
||||
validateRecipientUserInput,
|
||||
updateRecipientSearchMode,
|
||||
addHistoryEntry,
|
||||
} = actions;
|
||||
|
||||
export { useDefaultGas, useCustomGas, updateGasLimit };
|
||||
export { useDefaultGas, useCustomGas, updateGasLimit, addHistoryEntry };
|
||||
|
||||
// Action Creators
|
||||
|
||||
@ -1421,6 +1431,9 @@ export { useDefaultGas, useCustomGas, updateGasLimit };
|
||||
*/
|
||||
export function updateGasPrice(gasPrice) {
|
||||
return (dispatch) => {
|
||||
dispatch(
|
||||
addHistoryEntry(`sendFlow - user set legacy gasPrice to ${gasPrice}`),
|
||||
);
|
||||
dispatch(
|
||||
actions.updateGasFees({
|
||||
gasPrice,
|
||||
@ -1452,8 +1465,36 @@ export function resetSendState() {
|
||||
*/
|
||||
export function updateSendAmount(amount) {
|
||||
return async (dispatch, getState) => {
|
||||
await dispatch(actions.updateSendAmount(amount));
|
||||
const state = getState();
|
||||
let logAmount = amount;
|
||||
if (state[name].asset.type === ASSET_TYPES.TOKEN) {
|
||||
const multiplier = Math.pow(
|
||||
10,
|
||||
Number(state[name].asset.details?.decimals || 0),
|
||||
);
|
||||
const decimalValueString = conversionUtil(addHexPrefix(amount), {
|
||||
fromNumericBase: 'hex',
|
||||
toNumericBase: 'dec',
|
||||
toCurrency: state[name].asset.details?.symbol,
|
||||
conversionRate: multiplier,
|
||||
invertConversionRate: true,
|
||||
});
|
||||
|
||||
logAmount = `${Number(decimalValueString) ? decimalValueString : ''} ${
|
||||
state[name].asset.details?.symbol
|
||||
}`;
|
||||
} else {
|
||||
const ethValue = getValueFromWeiHex({
|
||||
value: amount,
|
||||
toCurrency: ETH,
|
||||
numberOfDecimals: 8,
|
||||
});
|
||||
logAmount = `${ethValue} ${ETH}`;
|
||||
}
|
||||
await dispatch(
|
||||
addHistoryEntry(`sendFlow - user set amount to ${logAmount}`),
|
||||
);
|
||||
await dispatch(actions.updateSendAmount(amount));
|
||||
if (state.send.amount.mode === AMOUNT_MODES.MAX) {
|
||||
await dispatch(actions.updateAmountMode(AMOUNT_MODES.INPUT));
|
||||
}
|
||||
@ -1482,6 +1523,19 @@ export function updateSendAmount(amount) {
|
||||
*/
|
||||
export function updateSendAsset({ type, details }) {
|
||||
return async (dispatch, getState) => {
|
||||
dispatch(addHistoryEntry(`sendFlow - user set asset type to ${type}`));
|
||||
dispatch(
|
||||
addHistoryEntry(
|
||||
`sendFlow - user set asset symbol to ${details?.symbol ?? 'undefined'}`,
|
||||
),
|
||||
);
|
||||
dispatch(
|
||||
addHistoryEntry(
|
||||
`sendFlow - user set asset address to ${
|
||||
details?.address ?? 'undefined'
|
||||
}`,
|
||||
),
|
||||
);
|
||||
const state = getState();
|
||||
let { balance, error } = state.send.asset;
|
||||
const userAddress = state.send.account.address ?? getSelectedAddress(state);
|
||||
@ -1580,6 +1634,11 @@ export function updateSendAsset({ type, details }) {
|
||||
* it only applicable for use within action creators.
|
||||
*/
|
||||
const debouncedValidateRecipientUserInput = debounce((dispatch, payload) => {
|
||||
dispatch(
|
||||
addHistoryEntry(
|
||||
`sendFlow - user typed ${payload.userInput} into recipient input field`,
|
||||
),
|
||||
);
|
||||
dispatch(validateRecipientUserInput(payload));
|
||||
}, 300);
|
||||
|
||||
@ -1600,6 +1659,7 @@ export function updateRecipientUserInput(userInput) {
|
||||
const useTokenDetection = getUseTokenDetection(state);
|
||||
const tokenAddressList = Object.keys(getTokenList(state));
|
||||
debouncedValidateRecipientUserInput(dispatch, {
|
||||
userInput,
|
||||
chainId,
|
||||
tokens,
|
||||
useTokenDetection,
|
||||
@ -1610,12 +1670,22 @@ export function updateRecipientUserInput(userInput) {
|
||||
|
||||
export function useContactListForRecipientSearch() {
|
||||
return (dispatch) => {
|
||||
dispatch(
|
||||
addHistoryEntry(
|
||||
`sendFlow - user selected back to all on recipient screen`,
|
||||
),
|
||||
);
|
||||
dispatch(updateRecipientSearchMode(RECIPIENT_SEARCH_MODES.CONTACT_LIST));
|
||||
};
|
||||
}
|
||||
|
||||
export function useMyAccountsForRecipientSearch() {
|
||||
return (dispatch) => {
|
||||
dispatch(
|
||||
addHistoryEntry(
|
||||
`sendFlow - user selected transfer to my accounts on recipient screen`,
|
||||
),
|
||||
);
|
||||
dispatch(updateRecipientSearchMode(RECIPIENT_SEARCH_MODES.MY_ACCOUNTS));
|
||||
};
|
||||
}
|
||||
@ -1638,6 +1708,8 @@ export function useMyAccountsForRecipientSearch() {
|
||||
*/
|
||||
export function updateRecipient({ address, nickname }) {
|
||||
return async (dispatch, getState) => {
|
||||
// Do not addHistoryEntry here as this is called from a number of places
|
||||
// each with significance to the user and transaction history.
|
||||
const state = getState();
|
||||
const nicknameFromAddressBookEntryOrAccountName =
|
||||
getAddressBookEntryOrAccountName(state, address) ?? '';
|
||||
@ -1656,6 +1728,7 @@ export function updateRecipient({ address, nickname }) {
|
||||
*/
|
||||
export function resetRecipientInput() {
|
||||
return async (dispatch) => {
|
||||
await dispatch(addHistoryEntry(`sendFlow - user cleared recipient input`));
|
||||
await dispatch(updateRecipientUserInput(''));
|
||||
await dispatch(updateRecipient({ address: '', nickname: '' }));
|
||||
await dispatch(resetEnsResolution());
|
||||
@ -1675,6 +1748,9 @@ export function resetRecipientInput() {
|
||||
*/
|
||||
export function updateSendHexData(hexData) {
|
||||
return async (dispatch, getState) => {
|
||||
await dispatch(
|
||||
addHistoryEntry(`sendFlow - user added custom hexData ${hexData}`),
|
||||
);
|
||||
await dispatch(actions.updateUserInputHexData(hexData));
|
||||
const state = getState();
|
||||
if (state.send.asset.type === ASSET_TYPES.NATIVE) {
|
||||
@ -1695,9 +1771,11 @@ export function toggleSendMaxMode() {
|
||||
if (state.send.amount.mode === AMOUNT_MODES.MAX) {
|
||||
await dispatch(actions.updateAmountMode(AMOUNT_MODES.INPUT));
|
||||
await dispatch(actions.updateSendAmount('0x0'));
|
||||
await dispatch(addHistoryEntry(`sendFlow - user toggled max mode off`));
|
||||
} else {
|
||||
await dispatch(actions.updateAmountMode(AMOUNT_MODES.MAX));
|
||||
await dispatch(actions.updateAmountToMax());
|
||||
await dispatch(addHistoryEntry(`sendFlow - user toggled max mode on`));
|
||||
}
|
||||
await dispatch(computeEstimatedGasLimit());
|
||||
};
|
||||
@ -1746,6 +1824,12 @@ export function signTransaction() {
|
||||
eip1559support ? eip1559OnlyTxParamsToUpdate : txParams,
|
||||
),
|
||||
};
|
||||
await dispatch(
|
||||
addHistoryEntry(
|
||||
`sendFlow - user clicked next and transaction should be updated in controller`,
|
||||
),
|
||||
);
|
||||
await dispatch(updateTransactionSendFlowHistory(id, state[name].history));
|
||||
dispatch(updateEditableParams(id, editingTx.txParams));
|
||||
dispatch(updateTransactionGasFees(id, editingTx.txParams));
|
||||
} else {
|
||||
@ -1757,10 +1841,17 @@ export function signTransaction() {
|
||||
? TRANSACTION_TYPES.TOKEN_METHOD_TRANSFER_FROM
|
||||
: TRANSACTION_TYPES.TOKEN_METHOD_TRANSFER;
|
||||
}
|
||||
await dispatch(
|
||||
addHistoryEntry(
|
||||
`sendFlow - user clicked next and transaction should be added to controller`,
|
||||
),
|
||||
);
|
||||
|
||||
dispatch(
|
||||
addUnapprovedTransactionAndRouteToConfirmationPage(
|
||||
txParams,
|
||||
transactionType,
|
||||
state[name].history,
|
||||
),
|
||||
);
|
||||
}
|
||||
@ -1775,6 +1866,11 @@ export function editTransaction(
|
||||
) {
|
||||
return async (dispatch, getState) => {
|
||||
const state = getState();
|
||||
await dispatch(
|
||||
addHistoryEntry(
|
||||
`sendFlow - user clicked edit on transaction with id ${transactionId}`,
|
||||
),
|
||||
);
|
||||
const unapprovedTransactions = getUnapprovedTxs(state);
|
||||
const transaction = unapprovedTransactions[transactionId];
|
||||
const { txParams } = transaction;
|
||||
|
@ -80,9 +80,10 @@ jest.mock('./send', () => {
|
||||
|
||||
setBackgroundConnection({
|
||||
addPollingTokenToAppState: jest.fn(),
|
||||
addUnapprovedTransaction: jest.fn((_x, _y, _z, cb) => {
|
||||
return cb(null, {});
|
||||
addUnapprovedTransaction: jest.fn((_w, _x, _y, _z, cb) => {
|
||||
cb(null);
|
||||
}),
|
||||
updateTransactionSendFlowHistory: jest.fn((_x, _y, cb) => cb(null)),
|
||||
});
|
||||
|
||||
describe('Send Slice', () => {
|
||||
@ -1247,6 +1248,10 @@ describe('Send Slice', () => {
|
||||
const actionResult = store.getActions();
|
||||
|
||||
const expectedActionResult = [
|
||||
{
|
||||
type: 'send/addHistoryEntry',
|
||||
payload: 'sendFlow - user set legacy gasPrice to 0x0',
|
||||
},
|
||||
{
|
||||
type: 'send/updateGasFees',
|
||||
payload: {
|
||||
@ -1302,22 +1307,28 @@ describe('Send Slice', () => {
|
||||
};
|
||||
const store = mockStore(sendState);
|
||||
|
||||
const newSendAmount = 'aNewSendAmount';
|
||||
const newSendAmount = 'DE0B6B3A7640000';
|
||||
|
||||
await store.dispatch(updateSendAmount(newSendAmount));
|
||||
|
||||
const actionResult = store.getActions();
|
||||
|
||||
const expectedFirstActionResult = {
|
||||
type: 'send/addHistoryEntry',
|
||||
payload: 'sendFlow - user set amount to 1 ETH',
|
||||
};
|
||||
|
||||
const expectedSecondActionResult = {
|
||||
type: 'send/updateSendAmount',
|
||||
payload: 'aNewSendAmount',
|
||||
payload: 'DE0B6B3A7640000',
|
||||
};
|
||||
|
||||
expect(actionResult[0]).toStrictEqual(expectedFirstActionResult);
|
||||
expect(actionResult[1].type).toStrictEqual(
|
||||
expect(actionResult[1]).toStrictEqual(expectedSecondActionResult);
|
||||
expect(actionResult[2].type).toStrictEqual(
|
||||
'send/computeEstimatedGasLimit/pending',
|
||||
);
|
||||
expect(actionResult[2].type).toStrictEqual(
|
||||
expect(actionResult[3].type).toStrictEqual(
|
||||
'send/computeEstimatedGasLimit/rejected',
|
||||
);
|
||||
});
|
||||
@ -1358,15 +1369,21 @@ describe('Send Slice', () => {
|
||||
const actionResult = store.getActions();
|
||||
|
||||
const expectedFirstActionResult = {
|
||||
type: 'send/addHistoryEntry',
|
||||
payload: 'sendFlow - user set amount to 0 ETH',
|
||||
};
|
||||
|
||||
const expectedSecondActionResult = {
|
||||
type: 'send/updateSendAmount',
|
||||
payload: undefined,
|
||||
};
|
||||
|
||||
expect(actionResult[0]).toStrictEqual(expectedFirstActionResult);
|
||||
expect(actionResult[1].type).toStrictEqual(
|
||||
expect(actionResult[1]).toStrictEqual(expectedSecondActionResult);
|
||||
expect(actionResult[2].type).toStrictEqual(
|
||||
'send/computeEstimatedGasLimit/pending',
|
||||
);
|
||||
expect(actionResult[2].type).toStrictEqual(
|
||||
expect(actionResult[3].type).toStrictEqual(
|
||||
'send/computeEstimatedGasLimit/rejected',
|
||||
);
|
||||
});
|
||||
@ -1407,12 +1424,13 @@ describe('Send Slice', () => {
|
||||
|
||||
const actionResult = store.getActions();
|
||||
|
||||
expect(actionResult).toHaveLength(3);
|
||||
expect(actionResult[0].type).toStrictEqual('send/updateSendAmount');
|
||||
expect(actionResult[1].type).toStrictEqual(
|
||||
expect(actionResult).toHaveLength(4);
|
||||
expect(actionResult[0].type).toStrictEqual('send/addHistoryEntry');
|
||||
expect(actionResult[1].type).toStrictEqual('send/updateSendAmount');
|
||||
expect(actionResult[2].type).toStrictEqual(
|
||||
'send/computeEstimatedGasLimit/pending',
|
||||
);
|
||||
expect(actionResult[2].type).toStrictEqual(
|
||||
expect(actionResult[3].type).toStrictEqual(
|
||||
'send/computeEstimatedGasLimit/rejected',
|
||||
);
|
||||
});
|
||||
@ -1466,19 +1484,31 @@ describe('Send Slice', () => {
|
||||
|
||||
const actionResult = store.getActions();
|
||||
|
||||
expect(actionResult).toHaveLength(3);
|
||||
expect(actionResult).toHaveLength(6);
|
||||
expect(actionResult[0]).toMatchObject({
|
||||
type: 'send/addHistoryEntry',
|
||||
payload: 'sendFlow - user set asset type to ',
|
||||
});
|
||||
expect(actionResult[1]).toMatchObject({
|
||||
type: 'send/addHistoryEntry',
|
||||
payload: 'sendFlow - user set asset symbol to ',
|
||||
});
|
||||
expect(actionResult[2]).toMatchObject({
|
||||
type: 'send/addHistoryEntry',
|
||||
payload: 'sendFlow - user set asset address to ',
|
||||
});
|
||||
|
||||
expect(actionResult[0].type).toStrictEqual('send/updateAsset');
|
||||
expect(actionResult[0].payload).toStrictEqual({
|
||||
expect(actionResult[3].type).toStrictEqual('send/updateAsset');
|
||||
expect(actionResult[3].payload).toStrictEqual({
|
||||
...newSendAsset,
|
||||
balance: '',
|
||||
error: null,
|
||||
});
|
||||
|
||||
expect(actionResult[1].type).toStrictEqual(
|
||||
expect(actionResult[4].type).toStrictEqual(
|
||||
'send/computeEstimatedGasLimit/pending',
|
||||
);
|
||||
expect(actionResult[2].type).toStrictEqual(
|
||||
expect(actionResult[5].type).toStrictEqual(
|
||||
'send/computeEstimatedGasLimit/rejected',
|
||||
);
|
||||
});
|
||||
@ -1506,19 +1536,31 @@ describe('Send Slice', () => {
|
||||
|
||||
const actionResult = store.getActions();
|
||||
|
||||
expect(actionResult).toHaveLength(5);
|
||||
expect(actionResult[0].type).toStrictEqual('SHOW_LOADING_INDICATION');
|
||||
expect(actionResult[1].type).toStrictEqual('HIDE_LOADING_INDICATION');
|
||||
expect(actionResult[2].payload).toStrictEqual({
|
||||
expect(actionResult).toHaveLength(8);
|
||||
expect(actionResult[0]).toMatchObject({
|
||||
type: 'send/addHistoryEntry',
|
||||
payload: `sendFlow - user set asset type to ${ASSET_TYPES.TOKEN}`,
|
||||
});
|
||||
expect(actionResult[1]).toMatchObject({
|
||||
type: 'send/addHistoryEntry',
|
||||
payload: 'sendFlow - user set asset symbol to tokenSymbol',
|
||||
});
|
||||
expect(actionResult[2]).toMatchObject({
|
||||
type: 'send/addHistoryEntry',
|
||||
payload: 'sendFlow - user set asset address to tokenAddress',
|
||||
});
|
||||
expect(actionResult[3].type).toStrictEqual('SHOW_LOADING_INDICATION');
|
||||
expect(actionResult[4].type).toStrictEqual('HIDE_LOADING_INDICATION');
|
||||
expect(actionResult[5].payload).toStrictEqual({
|
||||
...newSendAsset,
|
||||
balance: '0x0',
|
||||
error: null,
|
||||
});
|
||||
|
||||
expect(actionResult[3].type).toStrictEqual(
|
||||
expect(actionResult[6].type).toStrictEqual(
|
||||
'send/computeEstimatedGasLimit/pending',
|
||||
);
|
||||
expect(actionResult[4].type).toStrictEqual(
|
||||
expect(actionResult[7].type).toStrictEqual(
|
||||
'send/computeEstimatedGasLimit/rejected',
|
||||
);
|
||||
});
|
||||
@ -1543,10 +1585,22 @@ describe('Send Slice', () => {
|
||||
store.dispatch(updateSendAsset(newSendAsset)),
|
||||
).rejects.toThrow('invalidAssetType');
|
||||
const actionResult = store.getActions();
|
||||
expect(actionResult).toHaveLength(3);
|
||||
expect(actionResult[0].type).toStrictEqual('SHOW_LOADING_INDICATION');
|
||||
expect(actionResult[1].type).toStrictEqual('HIDE_LOADING_INDICATION');
|
||||
expect(actionResult[2]).toStrictEqual({
|
||||
expect(actionResult).toHaveLength(6);
|
||||
expect(actionResult[0]).toMatchObject({
|
||||
type: 'send/addHistoryEntry',
|
||||
payload: `sendFlow - user set asset type to ${ASSET_TYPES.TOKEN}`,
|
||||
});
|
||||
expect(actionResult[1]).toMatchObject({
|
||||
type: 'send/addHistoryEntry',
|
||||
payload: 'sendFlow - user set asset symbol to tokenSymbol',
|
||||
});
|
||||
expect(actionResult[2]).toMatchObject({
|
||||
type: 'send/addHistoryEntry',
|
||||
payload: 'sendFlow - user set asset address to tokenAddress',
|
||||
});
|
||||
expect(actionResult[3].type).toStrictEqual('SHOW_LOADING_INDICATION');
|
||||
expect(actionResult[4].type).toStrictEqual('HIDE_LOADING_INDICATION');
|
||||
expect(actionResult[5]).toStrictEqual({
|
||||
payload: {
|
||||
name: 'CONVERT_TOKEN_TO_NFT',
|
||||
tokenAddress: 'tokenAddress',
|
||||
@ -1600,24 +1654,32 @@ describe('Send Slice', () => {
|
||||
|
||||
await store.dispatch(updateRecipientUserInput(newUserRecipientInput));
|
||||
|
||||
expect(store.getActions()).toHaveLength(1);
|
||||
expect(store.getActions()[0].type).toStrictEqual(
|
||||
const actionResult = store.getActions();
|
||||
|
||||
expect(actionResult).toHaveLength(1);
|
||||
expect(actionResult[0].type).toStrictEqual(
|
||||
'send/updateRecipientUserInput',
|
||||
);
|
||||
expect(store.getActions()[0].payload).toStrictEqual(
|
||||
newUserRecipientInput,
|
||||
);
|
||||
expect(actionResult[0].payload).toStrictEqual(newUserRecipientInput);
|
||||
|
||||
clock.tick(300); // debounce
|
||||
|
||||
expect(store.getActions()).toHaveLength(2);
|
||||
expect(store.getActions()[1].type).toStrictEqual(
|
||||
const actionResultAfterDebounce = store.getActions();
|
||||
expect(actionResultAfterDebounce).toHaveLength(3);
|
||||
|
||||
expect(actionResultAfterDebounce[1]).toMatchObject({
|
||||
type: 'send/addHistoryEntry',
|
||||
payload: `sendFlow - user typed ${newUserRecipientInput} into recipient input field`,
|
||||
});
|
||||
|
||||
expect(actionResultAfterDebounce[2].type).toStrictEqual(
|
||||
'send/validateRecipientUserInput',
|
||||
);
|
||||
expect(store.getActions()[1].payload).toStrictEqual({
|
||||
expect(actionResultAfterDebounce[2].payload).toStrictEqual({
|
||||
chainId: '',
|
||||
tokens: [],
|
||||
useTokenDetection: true,
|
||||
userInput: newUserRecipientInput,
|
||||
tokenAddressList: ['0x514910771af9ca656af840dff83e8264ecf986ca'],
|
||||
});
|
||||
});
|
||||
@ -1630,8 +1692,13 @@ describe('Send Slice', () => {
|
||||
await store.dispatch(useContactListForRecipientSearch());
|
||||
|
||||
const actionResult = store.getActions();
|
||||
expect(actionResult).toHaveLength(2);
|
||||
|
||||
expect(actionResult).toStrictEqual([
|
||||
{
|
||||
type: 'send/addHistoryEntry',
|
||||
payload: 'sendFlow - user selected back to all on recipient screen',
|
||||
},
|
||||
{
|
||||
type: 'send/updateRecipientSearchMode',
|
||||
payload: RECIPIENT_SEARCH_MODES.CONTACT_LIST,
|
||||
@ -1648,7 +1715,14 @@ describe('Send Slice', () => {
|
||||
|
||||
const actionResult = store.getActions();
|
||||
|
||||
expect(actionResult).toHaveLength(2);
|
||||
|
||||
expect(actionResult).toStrictEqual([
|
||||
{
|
||||
type: 'send/addHistoryEntry',
|
||||
payload:
|
||||
'sendFlow - user selected transfer to my accounts on recipient screen',
|
||||
},
|
||||
{
|
||||
type: 'send/updateRecipientSearchMode',
|
||||
payload: RECIPIENT_SEARCH_MODES.MY_ACCOUNTS,
|
||||
@ -1890,20 +1964,24 @@ describe('Send Slice', () => {
|
||||
await store.dispatch(resetRecipientInput());
|
||||
const actionResult = store.getActions();
|
||||
|
||||
expect(actionResult).toHaveLength(6);
|
||||
expect(actionResult[0].type).toStrictEqual(
|
||||
expect(actionResult).toHaveLength(7);
|
||||
expect(actionResult[0]).toMatchObject({
|
||||
type: 'send/addHistoryEntry',
|
||||
payload: 'sendFlow - user cleared recipient input',
|
||||
});
|
||||
expect(actionResult[1].type).toStrictEqual(
|
||||
'send/updateRecipientUserInput',
|
||||
);
|
||||
expect(actionResult[0].payload).toStrictEqual('');
|
||||
expect(actionResult[1].type).toStrictEqual('send/updateRecipient');
|
||||
expect(actionResult[2].type).toStrictEqual(
|
||||
expect(actionResult[1].payload).toStrictEqual('');
|
||||
expect(actionResult[2].type).toStrictEqual('send/updateRecipient');
|
||||
expect(actionResult[3].type).toStrictEqual(
|
||||
'send/computeEstimatedGasLimit/pending',
|
||||
);
|
||||
expect(actionResult[3].type).toStrictEqual(
|
||||
expect(actionResult[4].type).toStrictEqual(
|
||||
'send/computeEstimatedGasLimit/rejected',
|
||||
);
|
||||
expect(actionResult[4].type).toStrictEqual('ENS/resetEnsResolution');
|
||||
expect(actionResult[5].type).toStrictEqual(
|
||||
expect(actionResult[5].type).toStrictEqual('ENS/resetEnsResolution');
|
||||
expect(actionResult[6].type).toStrictEqual(
|
||||
'send/validateRecipientUserInput',
|
||||
);
|
||||
});
|
||||
@ -1927,10 +2005,14 @@ describe('Send Slice', () => {
|
||||
const actionResult = store.getActions();
|
||||
|
||||
const expectActionResult = [
|
||||
{
|
||||
type: 'send/addHistoryEntry',
|
||||
payload: 'sendFlow - user added custom hexData 0x1',
|
||||
},
|
||||
{ type: 'send/updateUserInputHexData', payload: hexData },
|
||||
];
|
||||
|
||||
expect(actionResult).toHaveLength(1);
|
||||
expect(actionResult).toHaveLength(2);
|
||||
expect(actionResult).toStrictEqual(expectActionResult);
|
||||
});
|
||||
});
|
||||
@ -1970,13 +2052,17 @@ describe('Send Slice', () => {
|
||||
|
||||
const actionResult = store.getActions();
|
||||
|
||||
expect(actionResult).toHaveLength(4);
|
||||
expect(actionResult).toHaveLength(5);
|
||||
expect(actionResult[0].type).toStrictEqual('send/updateAmountMode');
|
||||
expect(actionResult[1].type).toStrictEqual('send/updateAmountToMax');
|
||||
expect(actionResult[2].type).toStrictEqual(
|
||||
expect(actionResult[2]).toMatchObject({
|
||||
type: 'send/addHistoryEntry',
|
||||
payload: 'sendFlow - user toggled max mode on',
|
||||
});
|
||||
expect(actionResult[3].type).toStrictEqual(
|
||||
'send/computeEstimatedGasLimit/pending',
|
||||
);
|
||||
expect(actionResult[3].type).toStrictEqual(
|
||||
expect(actionResult[4].type).toStrictEqual(
|
||||
'send/computeEstimatedGasLimit/rejected',
|
||||
);
|
||||
});
|
||||
@ -2014,13 +2100,17 @@ describe('Send Slice', () => {
|
||||
|
||||
const actionResult = store.getActions();
|
||||
|
||||
expect(actionResult).toHaveLength(4);
|
||||
expect(actionResult).toHaveLength(5);
|
||||
expect(actionResult[0].type).toStrictEqual('send/updateAmountMode');
|
||||
expect(actionResult[1].type).toStrictEqual('send/updateSendAmount');
|
||||
expect(actionResult[2].type).toStrictEqual(
|
||||
expect(actionResult[2]).toMatchObject({
|
||||
type: 'send/addHistoryEntry',
|
||||
payload: 'sendFlow - user toggled max mode off',
|
||||
});
|
||||
expect(actionResult[3].type).toStrictEqual(
|
||||
'send/computeEstimatedGasLimit/pending',
|
||||
);
|
||||
expect(actionResult[3].type).toStrictEqual(
|
||||
expect(actionResult[4].type).toStrictEqual(
|
||||
'send/computeEstimatedGasLimit/rejected',
|
||||
);
|
||||
});
|
||||
@ -2045,8 +2135,13 @@ describe('Send Slice', () => {
|
||||
|
||||
const actionResult = store.getActions();
|
||||
|
||||
expect(actionResult).toHaveLength(1);
|
||||
expect(actionResult[0].type).toStrictEqual('SHOW_CONF_TX_PAGE');
|
||||
expect(actionResult).toHaveLength(2);
|
||||
expect(actionResult[0]).toMatchObject({
|
||||
type: 'send/addHistoryEntry',
|
||||
payload:
|
||||
'sendFlow - user clicked next and transaction should be added to controller',
|
||||
});
|
||||
expect(actionResult[1].type).toStrictEqual('SHOW_CONF_TX_PAGE');
|
||||
});
|
||||
|
||||
it('should create actions for updateTransaction rejecting', async () => {
|
||||
@ -2081,11 +2176,16 @@ describe('Send Slice', () => {
|
||||
|
||||
const actionResult = store.getActions();
|
||||
|
||||
expect(actionResult).toHaveLength(2);
|
||||
expect(actionResult[0].type).toStrictEqual(
|
||||
expect(actionResult).toHaveLength(3);
|
||||
expect(actionResult[0]).toMatchObject({
|
||||
type: 'send/addHistoryEntry',
|
||||
payload:
|
||||
'sendFlow - user clicked next and transaction should be updated in controller',
|
||||
});
|
||||
expect(actionResult[1].type).toStrictEqual(
|
||||
'UPDATE_TRANSACTION_EDITABLE_PARAMS',
|
||||
);
|
||||
expect(actionResult[1].type).toStrictEqual(
|
||||
expect(actionResult[2].type).toStrictEqual(
|
||||
'UPDATE_TRANSACTION_GAS_FEES',
|
||||
);
|
||||
});
|
||||
@ -2133,9 +2233,13 @@ describe('Send Slice', () => {
|
||||
await store.dispatch(editTransaction(ASSET_TYPES.NATIVE, 1));
|
||||
const actionResult = store.getActions();
|
||||
|
||||
expect(actionResult).toHaveLength(1);
|
||||
expect(actionResult[0].type).toStrictEqual('send/editTransaction');
|
||||
expect(actionResult[0].payload).toStrictEqual({
|
||||
expect(actionResult).toHaveLength(2);
|
||||
expect(actionResult[0]).toMatchObject({
|
||||
type: 'send/addHistoryEntry',
|
||||
payload: 'sendFlow - user clicked edit on transaction with id 1',
|
||||
});
|
||||
expect(actionResult[1].type).toStrictEqual('send/editTransaction');
|
||||
expect(actionResult[1].payload).toStrictEqual({
|
||||
address: '0xRecipientAddress',
|
||||
amount: '0xde0b6b3a7640000',
|
||||
data: '',
|
||||
@ -2146,7 +2250,7 @@ describe('Send Slice', () => {
|
||||
nickname: '',
|
||||
});
|
||||
|
||||
const action = actionResult[0];
|
||||
const action = actionResult[1];
|
||||
|
||||
const result = sendReducer(initialState, action);
|
||||
|
||||
@ -2254,9 +2358,25 @@ describe('Send Slice', () => {
|
||||
),
|
||||
);
|
||||
const actionResult = store.getActions();
|
||||
expect(actionResult).toHaveLength(5);
|
||||
expect(actionResult[0].type).toStrictEqual('send/updateAsset');
|
||||
expect(actionResult[0].payload).toStrictEqual({
|
||||
expect(actionResult).toHaveLength(9);
|
||||
expect(actionResult[0]).toMatchObject({
|
||||
type: 'send/addHistoryEntry',
|
||||
payload: 'sendFlow - user clicked edit on transaction with id 1',
|
||||
});
|
||||
expect(actionResult[1]).toMatchObject({
|
||||
type: 'send/addHistoryEntry',
|
||||
payload: `sendFlow - user set asset type to ${ASSET_TYPES.COLLECTIBLE}`,
|
||||
});
|
||||
expect(actionResult[2]).toMatchObject({
|
||||
type: 'send/addHistoryEntry',
|
||||
payload: 'sendFlow - user set asset symbol to undefined',
|
||||
});
|
||||
expect(actionResult[3]).toMatchObject({
|
||||
type: 'send/addHistoryEntry',
|
||||
payload: 'sendFlow - user set asset address to 0xTokenAddress',
|
||||
});
|
||||
expect(actionResult[4].type).toStrictEqual('send/updateAsset');
|
||||
expect(actionResult[4].payload).toStrictEqual({
|
||||
balance: '0x1',
|
||||
type: ASSET_TYPES.COLLECTIBLE,
|
||||
error: null,
|
||||
@ -2270,18 +2390,17 @@ describe('Send Slice', () => {
|
||||
tokenId: '26847',
|
||||
},
|
||||
});
|
||||
expect(actionResult[1].type).toStrictEqual(
|
||||
expect(actionResult[5].type).toStrictEqual(
|
||||
'send/computeEstimatedGasLimit/pending',
|
||||
);
|
||||
expect(actionResult[2].type).toStrictEqual(
|
||||
expect(actionResult[6].type).toStrictEqual(
|
||||
'metamask/gas/SET_CUSTOM_GAS_LIMIT',
|
||||
);
|
||||
expect(actionResult[3].type).toStrictEqual(
|
||||
expect(actionResult[7].type).toStrictEqual(
|
||||
'send/computeEstimatedGasLimit/fulfilled',
|
||||
);
|
||||
expect(actionResult[4].type).toStrictEqual('send/editTransaction');
|
||||
|
||||
const action = actionResult[4];
|
||||
expect(actionResult[8].type).toStrictEqual('send/editTransaction');
|
||||
const action = actionResult[8];
|
||||
|
||||
const result = sendReducer(initialState, action);
|
||||
|
||||
@ -2383,11 +2502,27 @@ describe('Send Slice', () => {
|
||||
);
|
||||
const actionResult = store.getActions();
|
||||
|
||||
expect(actionResult).toHaveLength(7);
|
||||
expect(actionResult[0].type).toStrictEqual('SHOW_LOADING_INDICATION');
|
||||
expect(actionResult[1].type).toStrictEqual('HIDE_LOADING_INDICATION');
|
||||
expect(actionResult[2].type).toStrictEqual('send/updateAsset');
|
||||
expect(actionResult[2].payload).toStrictEqual({
|
||||
expect(actionResult).toHaveLength(11);
|
||||
expect(actionResult[0]).toMatchObject({
|
||||
type: 'send/addHistoryEntry',
|
||||
payload: 'sendFlow - user clicked edit on transaction with id 1',
|
||||
});
|
||||
expect(actionResult[1]).toMatchObject({
|
||||
type: 'send/addHistoryEntry',
|
||||
payload: `sendFlow - user set asset type to ${ASSET_TYPES.TOKEN}`,
|
||||
});
|
||||
expect(actionResult[2]).toMatchObject({
|
||||
type: 'send/addHistoryEntry',
|
||||
payload: 'sendFlow - user set asset symbol to SYMB',
|
||||
});
|
||||
expect(actionResult[3]).toMatchObject({
|
||||
type: 'send/addHistoryEntry',
|
||||
payload: 'sendFlow - user set asset address to 0xTokenAddress',
|
||||
});
|
||||
expect(actionResult[4].type).toStrictEqual('SHOW_LOADING_INDICATION');
|
||||
expect(actionResult[5].type).toStrictEqual('HIDE_LOADING_INDICATION');
|
||||
expect(actionResult[6].type).toStrictEqual('send/updateAsset');
|
||||
expect(actionResult[6].payload).toStrictEqual({
|
||||
balance: '0x0',
|
||||
type: ASSET_TYPES.TOKEN,
|
||||
error: null,
|
||||
@ -2398,17 +2533,17 @@ describe('Send Slice', () => {
|
||||
standard: 'ERC20',
|
||||
},
|
||||
});
|
||||
expect(actionResult[3].type).toStrictEqual(
|
||||
expect(actionResult[7].type).toStrictEqual(
|
||||
'send/computeEstimatedGasLimit/pending',
|
||||
);
|
||||
expect(actionResult[4].type).toStrictEqual(
|
||||
expect(actionResult[8].type).toStrictEqual(
|
||||
'metamask/gas/SET_CUSTOM_GAS_LIMIT',
|
||||
);
|
||||
expect(actionResult[5].type).toStrictEqual(
|
||||
expect(actionResult[9].type).toStrictEqual(
|
||||
'send/computeEstimatedGasLimit/fulfilled',
|
||||
);
|
||||
expect(actionResult[6].type).toStrictEqual('send/editTransaction');
|
||||
expect(actionResult[6].payload).toStrictEqual({
|
||||
expect(actionResult[10].type).toStrictEqual('send/editTransaction');
|
||||
expect(actionResult[10].payload).toStrictEqual({
|
||||
address: '0xrecipientaddress', // getting address from tokenData does .toLowerCase
|
||||
amount: '0x3a98',
|
||||
data: '',
|
||||
@ -2419,7 +2554,7 @@ describe('Send Slice', () => {
|
||||
nickname: '',
|
||||
});
|
||||
|
||||
const action = actionResult[6];
|
||||
const action = actionResult[10];
|
||||
|
||||
const result = sendReducer(initialState, action);
|
||||
|
||||
|
@ -22,6 +22,7 @@ export default class AddRecipient extends Component {
|
||||
addressBookEntryName: PropTypes.string,
|
||||
contacts: PropTypes.array,
|
||||
nonContacts: PropTypes.array,
|
||||
addHistoryEntry: PropTypes.func,
|
||||
useMyAccountsForRecipientSearch: PropTypes.func,
|
||||
useContactListForRecipientSearch: PropTypes.func,
|
||||
isUsingMyAccountsForRecipientSearch: PropTypes.bool,
|
||||
@ -64,7 +65,10 @@ export default class AddRecipient extends Component {
|
||||
metricsEvent: PropTypes.func,
|
||||
};
|
||||
|
||||
selectRecipient = (address, nickname = '') => {
|
||||
selectRecipient = (address, nickname = '', type = 'user input') => {
|
||||
this.props.addHistoryEntry(
|
||||
`sendFlow - User clicked recipient from ${type}. address: ${address}, nickname ${nickname}`,
|
||||
);
|
||||
this.props.updateRecipient({ address, nickname });
|
||||
};
|
||||
|
||||
@ -109,11 +113,13 @@ export default class AddRecipient extends Component {
|
||||
content = this.renderExplicitAddress(
|
||||
recipient.address,
|
||||
recipient.nickname,
|
||||
'validated user input',
|
||||
);
|
||||
} else if (ensResolution) {
|
||||
content = this.renderExplicitAddress(
|
||||
ensResolution,
|
||||
addressBookEntryName || userInput,
|
||||
'ENS resolution',
|
||||
);
|
||||
} else if (isUsingMyAccountsForRecipientSearch) {
|
||||
content = this.renderTransfer();
|
||||
@ -127,12 +133,12 @@ export default class AddRecipient extends Component {
|
||||
);
|
||||
}
|
||||
|
||||
renderExplicitAddress(address, name) {
|
||||
renderExplicitAddress(address, name, type) {
|
||||
return (
|
||||
<div
|
||||
key={address}
|
||||
className="send__select-recipient-wrapper__group-item"
|
||||
onClick={() => this.selectRecipient(address, name)}
|
||||
onClick={() => this.selectRecipient(address, name, type)}
|
||||
>
|
||||
<Identicon address={address} diameter={28} />
|
||||
<div className="send__select-recipient-wrapper__group-item__content">
|
||||
@ -179,7 +185,9 @@ export default class AddRecipient extends Component {
|
||||
<RecipientGroup
|
||||
label={t('myAccounts')}
|
||||
items={ownedAccounts}
|
||||
onSelect={this.selectRecipient}
|
||||
onSelect={(address, name) =>
|
||||
this.selectRecipient(address, name, 'my accounts')
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
@ -200,7 +208,9 @@ export default class AddRecipient extends Component {
|
||||
addressBook={addressBook}
|
||||
searchForContacts={this.searchForContacts.bind(this)}
|
||||
searchForRecents={this.searchForRecents.bind(this)}
|
||||
selectRecipient={this.selectRecipient.bind(this)}
|
||||
selectRecipient={(address, name) =>
|
||||
this.selectRecipient(address, name, 'contact list')
|
||||
}
|
||||
>
|
||||
{ownedAccounts && ownedAccounts.length > 1 && !userInput && (
|
||||
<Button
|
||||
|
@ -13,6 +13,7 @@ import {
|
||||
getIsUsingMyAccountForRecipientSearch,
|
||||
getRecipientUserInput,
|
||||
getRecipient,
|
||||
addHistoryEntry,
|
||||
} from '../../../../ducks/send';
|
||||
import {
|
||||
getEnsResolution,
|
||||
@ -55,6 +56,7 @@ function mapStateToProps(state) {
|
||||
|
||||
function mapDispatchToProps(dispatch) {
|
||||
return {
|
||||
addHistoryEntry: (entry) => dispatch(addHistoryEntry(entry)),
|
||||
updateRecipient: ({ address, nickname }) =>
|
||||
dispatch(updateRecipient({ address, nickname })),
|
||||
updateRecipientUserInput: (newInput) =>
|
||||
|
@ -2,6 +2,7 @@ import React, { useEffect, useCallback, useContext } from 'react';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import { useHistory, useLocation } from 'react-router-dom';
|
||||
import {
|
||||
addHistoryEntry,
|
||||
getIsUsingMyAccountForRecipientSearch,
|
||||
getRecipient,
|
||||
getRecipientUserInput,
|
||||
@ -95,13 +96,23 @@ export default function SendTransactionScreen() {
|
||||
userInput={userInput}
|
||||
className="send__to-row"
|
||||
onChange={(address) => dispatch(updateRecipientUserInput(address))}
|
||||
onValidAddressTyped={(address) =>
|
||||
dispatch(updateRecipient({ address, nickname: '' }))
|
||||
}
|
||||
onValidAddressTyped={(address) => {
|
||||
dispatch(
|
||||
addHistoryEntry(`sendFlow - Valid address typed ${address}`),
|
||||
);
|
||||
dispatch(updateRecipient({ address, nickname: '' }));
|
||||
}}
|
||||
internalSearch={isUsingMyAccountsForRecipientSearch}
|
||||
selectedAddress={recipient.address}
|
||||
selectedName={recipient.nickname}
|
||||
onPaste={(text) => updateRecipient({ address: text, nickname: '' })}
|
||||
onPaste={(text) => {
|
||||
dispatch(
|
||||
addHistoryEntry(
|
||||
`sendFlow - User pasted ${text} into address field`,
|
||||
),
|
||||
);
|
||||
return dispatch(updateRecipient({ address: text, nickname: '' }));
|
||||
}}
|
||||
onReset={() => dispatch(resetRecipientInput())}
|
||||
scanQrCode={() => {
|
||||
trackEvent({
|
||||
|
@ -738,6 +738,32 @@ export function updateEditableParams(txId, editableParams) {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Appends new send flow history to a transaction
|
||||
*
|
||||
* @param {string} txId - the id of the transaction to update
|
||||
* @param {Array<{event: string, timestamp: number}>} sendFlowHistory - the new send flow history to append to the
|
||||
* transaction
|
||||
* @returns {import('../../shared/constants/transaction').TransactionMeta}
|
||||
*/
|
||||
export function updateTransactionSendFlowHistory(txId, sendFlowHistory) {
|
||||
return async (dispatch) => {
|
||||
let updatedTransaction;
|
||||
try {
|
||||
updatedTransaction = await promisifiedBackground.updateTransactionSendFlowHistory(
|
||||
txId,
|
||||
sendFlowHistory,
|
||||
);
|
||||
} catch (error) {
|
||||
dispatch(txError(error));
|
||||
log.error(error.message);
|
||||
throw error;
|
||||
}
|
||||
|
||||
return updatedTransaction;
|
||||
};
|
||||
}
|
||||
|
||||
export function updateTransactionGasFees(txId, txGasFees) {
|
||||
return async (dispatch) => {
|
||||
let updatedTransaction;
|
||||
@ -811,11 +837,14 @@ export function updateTransaction(txData, dontShowLoadingIndicator) {
|
||||
* @param {import(
|
||||
* '../../shared/constants/transaction'
|
||||
* ).TransactionTypeString} type - The type of the transaction being added.
|
||||
* @param {Array<{event: string, timestamp: number}>} sendFlowHistory - The
|
||||
* history of the send flow at time of creation.
|
||||
* @returns {import('../../shared/constants/transaction').TransactionMeta}
|
||||
*/
|
||||
export function addUnapprovedTransactionAndRouteToConfirmationPage(
|
||||
txParams,
|
||||
type,
|
||||
sendFlowHistory,
|
||||
) {
|
||||
return async (dispatch) => {
|
||||
try {
|
||||
@ -824,6 +853,7 @@ export function addUnapprovedTransactionAndRouteToConfirmationPage(
|
||||
txParams,
|
||||
ORIGIN_METAMASK,
|
||||
type,
|
||||
sendFlowHistory,
|
||||
);
|
||||
dispatch(showConfTxPage());
|
||||
return txMeta;
|
||||
|
Loading…
Reference in New Issue
Block a user