1
0
mirror of https://github.com/kremalicious/metamask-extension.git synced 2024-12-23 01:39:44 +01:00

Fix recent recipient order (#16346)

This commit is contained in:
amerkadicE 2023-02-09 18:45:52 +01:00 committed by GitHub
parent 26f6ae4c7c
commit 0c2af508ef
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 118 additions and 28 deletions

View File

@ -93,6 +93,7 @@ export default class ConfirmTransactionBase extends Component {
sendTransaction: PropTypes.func, sendTransaction: PropTypes.func,
showTransactionConfirmedModal: PropTypes.func, showTransactionConfirmedModal: PropTypes.func,
showRejectTransactionsConfirmationModal: PropTypes.func, showRejectTransactionsConfirmationModal: PropTypes.func,
toAccounts: PropTypes.object,
toAddress: PropTypes.string, toAddress: PropTypes.string,
tokenData: PropTypes.object, tokenData: PropTypes.object,
tokenProps: PropTypes.object, tokenProps: PropTypes.object,
@ -103,6 +104,7 @@ export default class ConfirmTransactionBase extends Component {
txData: PropTypes.object, txData: PropTypes.object,
unapprovedTxCount: PropTypes.number, unapprovedTxCount: PropTypes.number,
customGas: PropTypes.object, customGas: PropTypes.object,
addToAddressBookIfNew: PropTypes.func,
// Component props // Component props
actionKey: PropTypes.string, actionKey: PropTypes.string,
contentComponent: PropTypes.node, contentComponent: PropTypes.node,
@ -803,10 +805,16 @@ export default class ConfirmTransactionBase extends Component {
maxPriorityFeePerGas, maxPriorityFeePerGas,
baseFeePerGas, baseFeePerGas,
methodData, methodData,
addToAddressBookIfNew,
toAccounts,
toAddress,
} = this.props; } = this.props;
const { submitting } = this.state; const { submitting } = this.state;
const { name } = methodData; const { name } = methodData;
if (txData.type === TransactionType.simpleSend) {
addToAddressBookIfNew(toAddress, toAccounts);
}
if (submitting) { if (submitting) {
return; return;
} }

View File

@ -13,6 +13,7 @@ import {
getNextNonce, getNextNonce,
tryReverseResolveAddress, tryReverseResolveAddress,
setDefaultHomeActiveTabName, setDefaultHomeActiveTabName,
addToAddressBook,
} from '../../store/actions'; } from '../../store/actions';
import { isBalanceSufficient } from '../send/send.utils'; import { isBalanceSufficient } from '../send/send.utils';
import { shortenAddress, valuesFor } from '../../helpers/utils/util'; import { shortenAddress, valuesFor } from '../../helpers/utils/util';
@ -44,7 +45,9 @@ import {
updateGasFees, updateGasFees,
getIsGasEstimatesLoading, getIsGasEstimatesLoading,
getNativeCurrency, getNativeCurrency,
getSendToAccounts,
} from '../../ducks/metamask/metamask'; } from '../../ducks/metamask/metamask';
import { addHexPrefix } from '../../../app/scripts/lib/util';
import { import {
parseStandardTokenTransactionData, parseStandardTokenTransactionData,
@ -74,6 +77,14 @@ const customNonceMerge = (txData) =>
} }
: txData; : txData;
function addressIsNew(toAccounts, newAddress) {
const newAddressNormalized = newAddress.toLowerCase();
const foundMatching = toAccounts.some(
({ address }) => address.toLowerCase() === newAddressNormalized,
);
return !foundMatching;
}
const mapStateToProps = (state, ownProps) => { const mapStateToProps = (state, ownProps) => {
const { const {
toAddress: propsToAddress, toAddress: propsToAddress,
@ -122,6 +133,8 @@ const mapStateToProps = (state, ownProps) => {
toAddress = propsToAddress || tokenToAddress || txParamsToAddress; toAddress = propsToAddress || tokenToAddress || txParamsToAddress;
} }
const toAccounts = getSendToAccounts(state);
const tokenList = getTokenList(state); const tokenList = getTokenList(state);
const toName = const toName =
@ -196,6 +209,7 @@ const mapStateToProps = (state, ownProps) => {
balance, balance,
fromAddress, fromAddress,
fromName, fromName,
toAccounts,
toAddress, toAddress,
toEns, toEns,
toName, toName,
@ -277,6 +291,13 @@ export const mapDispatchToProps = (dispatch) => {
updateTransactionGasFees: (gasFees) => { updateTransactionGasFees: (gasFees) => {
dispatch(updateGasFees({ ...gasFees, expectHexWei: true })); dispatch(updateGasFees({ ...gasFees, expectHexWei: true }));
}, },
showBuyModal: () => dispatch(showModal({ name: 'DEPOSIT_ETHER' })),
addToAddressBookIfNew: (newAddress, toAccounts, nickname = '') => {
const hexPrefixedAddress = addHexPrefix(newAddress);
if (addressIsNew(toAccounts, hexPrefixedAddress)) {
dispatch(addToAddressBook(hexPrefixedAddress, nickname));
}
},
}; };
}; };

View File

@ -58,4 +58,71 @@ describe('Add Recipient Component', () => {
expect(container).toMatchSnapshot(); expect(container).toMatchSnapshot();
}); });
}); });
describe('Recent recipient order', () => {
const recentRecipientState = {
...mockState,
metamask: {
...mockState.metamask,
addressBook: {
'0x5': {
'0x0000000000000000000000000000000000000001': {
address: '0x0000000000000000000000000000000000000001',
chainId: '0x5',
isEns: false,
memo: '',
name: '',
},
'0x0000000000000000000000000000000000000002': {
address: '0x0000000000000000000000000000000000000002',
chainId: '0x5',
isEns: false,
memo: '',
name: '',
},
'0x0000000000000000000000000000000000000003': {
address: '0x0000000000000000000000000000000000000003',
chainId: '0x5',
isEns: false,
memo: '',
name: '',
},
},
},
currentNetworkTxList: [
{
time: 1674425700001,
txParams: {
to: '0x0000000000000000000000000000000000000001',
},
},
{
time: 1674425700002,
txParams: {
to: '0x0000000000000000000000000000000000000002',
},
},
{
time: 1674425700003,
txParams: {
to: '0x0000000000000000000000000000000000000003',
},
},
],
},
};
const mockStore = configureMockStore()(recentRecipientState);
it('should render latest used recipient first', () => {
const { getAllByTestId } = renderWithProvider(
<AddRecipient />,
mockStore,
);
const recipientList = getAllByTestId('recipient');
expect(recipientList[0]).toHaveTextContent('0x0000...0003');
expect(recipientList[1]).toHaveTextContent('0x0000...0002');
});
});
}); });

View File

@ -3,6 +3,7 @@ import {
getAddressBook, getAddressBook,
getAddressBookEntry, getAddressBookEntry,
getMetaMaskAccountsOrdered, getMetaMaskAccountsOrdered,
currentNetworkTxListSelector,
} from '../../../../selectors'; } from '../../../../selectors';
import { import {
@ -35,6 +36,22 @@ function mapStateToProps(state) {
const addressBook = getAddressBook(state); const addressBook = getAddressBook(state);
const txList = [...currentNetworkTxListSelector(state)].reverse();
const nonContacts = addressBook
.filter(({ name }) => !name)
.map((nonContact) => {
const nonContactTx = txList.find(
(transaction) =>
transaction.txParams.to === nonContact.address.toLowerCase(),
);
return { ...nonContact, timestamp: nonContactTx?.time };
});
nonContacts.sort((a, b) => {
return b.timestamp - a.timestamp;
});
const ownedAccounts = getMetaMaskAccountsOrdered(state); const ownedAccounts = getMetaMaskAccountsOrdered(state);
return { return {
@ -44,7 +61,7 @@ function mapStateToProps(state) {
domainResolution, domainResolution,
domainError: getDomainError(state), domainError: getDomainError(state),
domainWarning: getDomainWarning(state), domainWarning: getDomainWarning(state),
nonContacts: addressBook.filter(({ name }) => !name), nonContacts,
ownedAccounts, ownedAccounts,
isUsingMyAccountsForRecipientSearch: isUsingMyAccountsForRecipientSearch:
getIsUsingMyAccountForRecipientSearch(state), getIsUsingMyAccountForRecipientSearch(state),

View File

@ -16,6 +16,7 @@ jest.mock('../../../../selectors', () => ({
{ name: `account1:mockState` }, { name: `account1:mockState` },
{ name: `account2:mockState` }, { name: `account2:mockState` },
], ],
currentNetworkTxListSelector: (s) => `currentNetworkTxListSelector:${s}`,
})); }));
jest.mock('../../../../ducks/domains', () => ({ jest.mock('../../../../ducks/domains', () => ({

View File

@ -11,13 +11,10 @@ import { SEND_STAGES } from '../../../ducks/send';
export default class SendFooter extends Component { export default class SendFooter extends Component {
static propTypes = { static propTypes = {
addToAddressBookIfNew: PropTypes.func,
resetSendState: PropTypes.func, resetSendState: PropTypes.func,
disabled: PropTypes.bool.isRequired, disabled: PropTypes.bool.isRequired,
history: PropTypes.object, history: PropTypes.object,
sign: PropTypes.func, sign: PropTypes.func,
to: PropTypes.string,
toAccounts: PropTypes.array,
sendStage: PropTypes.string, sendStage: PropTypes.string,
sendErrors: PropTypes.object, sendErrors: PropTypes.object,
mostRecentOverviewPage: PropTypes.string.isRequired, mostRecentOverviewPage: PropTypes.string.isRequired,
@ -52,11 +49,9 @@ export default class SendFooter extends Component {
async onSubmit(event) { async onSubmit(event) {
event.preventDefault(); event.preventDefault();
const { addToAddressBookIfNew, sign, to, toAccounts, history } = this.props; const { sign, history } = this.props;
const { trackEvent } = this.context; const { trackEvent } = this.context;
// TODO: add nickname functionality
await addToAddressBookIfNew(to, toAccounts);
const promise = sign(); const promise = sign();
Promise.resolve(promise).then(() => { Promise.resolve(promise).then(() => {

View File

@ -1,5 +1,5 @@
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { addToAddressBook, cancelTx } from '../../../store/actions'; import { cancelTx } from '../../../store/actions';
import { import {
resetSendState, resetSendState,
getSendStage, getSendStage,
@ -10,20 +10,11 @@ import {
getDraftTransactionID, getDraftTransactionID,
} from '../../../ducks/send'; } from '../../../ducks/send';
import { getMostRecentOverviewPage } from '../../../ducks/history/history'; import { getMostRecentOverviewPage } from '../../../ducks/history/history';
import { addHexPrefix } from '../../../../app/scripts/lib/util';
import { getSendToAccounts } from '../../../ducks/metamask/metamask'; import { getSendToAccounts } from '../../../ducks/metamask/metamask';
import SendFooter from './send-footer.component'; import SendFooter from './send-footer.component';
export default connect(mapStateToProps, mapDispatchToProps)(SendFooter); export default connect(mapStateToProps, mapDispatchToProps)(SendFooter);
function addressIsNew(toAccounts, newAddress) {
const newAddressNormalized = newAddress.toLowerCase();
const foundMatching = toAccounts.some(
({ address }) => address.toLowerCase() === newAddressNormalized,
);
return !foundMatching;
}
function mapStateToProps(state) { function mapStateToProps(state) {
return { return {
disabled: isSendFormInvalid(state), disabled: isSendFormInvalid(state),
@ -41,12 +32,5 @@ function mapDispatchToProps(dispatch) {
resetSendState: () => dispatch(resetSendState()), resetSendState: () => dispatch(resetSendState()),
cancelTx: (t) => dispatch(cancelTx(t)), cancelTx: (t) => dispatch(cancelTx(t)),
sign: () => dispatch(signTransaction()), sign: () => dispatch(signTransaction()),
addToAddressBookIfNew: (newAddress, toAccounts, nickname = '') => {
const hexPrefixedAddress = addHexPrefix(newAddress);
if (addressIsNew(toAccounts, hexPrefixedAddress)) {
// TODO: nickname, i.e. addToAddressBook(recipient, nickname)
dispatch(addToAddressBook(hexPrefixedAddress, nickname));
}
},
}; };
} }

View File

@ -13,7 +13,6 @@ export default {
mostRecentOverviewPage: { control: 'text' }, mostRecentOverviewPage: { control: 'text' },
sendErrors: { control: 'object' }, sendErrors: { control: 'object' },
history: { action: 'history' }, history: { action: 'history' },
addToAddressBookIfNew: { action: 'addToAddressBookIfNew' },
resetSendState: { action: 'resetSendState' }, resetSendState: { action: 'resetSendState' },
}, },
}; };

View File

@ -13,7 +13,6 @@ import SendFooter from '.';
const mockResetSendState = jest.fn(); const mockResetSendState = jest.fn();
const mockSendTransaction = jest.fn(); const mockSendTransaction = jest.fn();
const mockAddtoAddressBook = jest.fn();
const mockCancelTx = jest.fn(); const mockCancelTx = jest.fn();
jest.mock('../../../ducks/send/index.js', () => ({ jest.mock('../../../ducks/send/index.js', () => ({
@ -23,7 +22,6 @@ jest.mock('../../../ducks/send/index.js', () => ({
})); }));
jest.mock('../../../store/actions.ts', () => ({ jest.mock('../../../store/actions.ts', () => ({
addToAddressBook: () => mockAddtoAddressBook,
cancelTx: () => mockCancelTx, cancelTx: () => mockCancelTx,
})); }));
@ -108,7 +106,6 @@ describe('SendFooter Component', () => {
fireEvent.click(nextText); fireEvent.click(nextText);
await waitFor(() => { await waitFor(() => {
expect(mockAddtoAddressBook).toHaveBeenCalled();
expect(mockSendTransaction).toHaveBeenCalled(); expect(mockSendTransaction).toHaveBeenCalled();
expect(props.history.push).toHaveBeenCalledWith( expect(props.history.push).toHaveBeenCalledWith(
CONFIRM_TRANSACTION_ROUTE, CONFIRM_TRANSACTION_ROUTE,

View File

@ -103,6 +103,7 @@ const baseStore = {
addressBook: { addressBook: {
[CHAIN_IDS.GOERLI]: [], [CHAIN_IDS.GOERLI]: [],
}, },
currentNetworkTxList: [],
cachedBalances: { cachedBalances: {
[CHAIN_IDS.GOERLI]: {}, [CHAIN_IDS.GOERLI]: {},
}, },