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:
parent
26f6ae4c7c
commit
0c2af508ef
@ -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;
|
||||||
}
|
}
|
||||||
|
@ -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));
|
||||||
|
}
|
||||||
|
},
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -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');
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
@ -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),
|
||||||
|
@ -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', () => ({
|
||||||
|
@ -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(() => {
|
||||||
|
@ -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));
|
|
||||||
}
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -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' },
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
@ -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,
|
||||||
|
@ -103,6 +103,7 @@ const baseStore = {
|
|||||||
addressBook: {
|
addressBook: {
|
||||||
[CHAIN_IDS.GOERLI]: [],
|
[CHAIN_IDS.GOERLI]: [],
|
||||||
},
|
},
|
||||||
|
currentNetworkTxList: [],
|
||||||
cachedBalances: {
|
cachedBalances: {
|
||||||
[CHAIN_IDS.GOERLI]: {},
|
[CHAIN_IDS.GOERLI]: {},
|
||||||
},
|
},
|
||||||
|
Loading…
Reference in New Issue
Block a user