mirror of
https://github.com/kremalicious/metamask-extension.git
synced 2024-12-23 09:52:26 +01:00
Confirm transaction page: show contract address details in title (#13683)
This commit is contained in:
parent
e4181ec581
commit
137b47370e
3
app/_locales/en/messages.json
generated
3
app/_locales/en/messages.json
generated
@ -3665,6 +3665,9 @@
|
||||
"message": "Sending collectible (ERC-721) tokens is not currently supported",
|
||||
"description": "This is an error message we show the user if they attempt to send a collectible asset type, for which currently don't support sending"
|
||||
},
|
||||
"unverifiedContractAddressMessage": {
|
||||
"message": "We cannot verify this contract. Make sure you trust this address."
|
||||
},
|
||||
"updatedWithDate": {
|
||||
"message": "Updated $1"
|
||||
},
|
||||
|
@ -42,6 +42,7 @@ describe('Confirm Page Container Container Test', () => {
|
||||
identities: [],
|
||||
featureFlags: {},
|
||||
enableEIP1559V2NoticeDismissed: true,
|
||||
tokenList: {},
|
||||
},
|
||||
};
|
||||
|
||||
|
@ -55,6 +55,8 @@ export default class ConfirmPageContainerContent extends Component {
|
||||
nativeCurrency: PropTypes.string,
|
||||
networkName: PropTypes.string,
|
||||
showBuyModal: PropTypes.func,
|
||||
toAddress: PropTypes.string,
|
||||
transactionType: PropTypes.string,
|
||||
};
|
||||
|
||||
renderContent() {
|
||||
@ -128,6 +130,8 @@ export default class ConfirmPageContainerContent extends Component {
|
||||
nativeCurrency,
|
||||
networkName,
|
||||
showBuyModal,
|
||||
toAddress,
|
||||
transactionType,
|
||||
} = this.props;
|
||||
|
||||
const primaryAction = hideUserAcknowledgedGasMissing
|
||||
@ -179,6 +183,8 @@ export default class ConfirmPageContainerContent extends Component {
|
||||
nonce={nonce}
|
||||
origin={origin}
|
||||
hideTitle={hideTitle}
|
||||
toAddress={toAddress}
|
||||
transactionType={transactionType}
|
||||
/>
|
||||
{this.renderContent()}
|
||||
{!supportsEIP1559V2 &&
|
||||
|
@ -1,6 +1,7 @@
|
||||
import { fireEvent } from '@testing-library/react';
|
||||
import React from 'react';
|
||||
import configureMockStore from 'redux-mock-store';
|
||||
import { TRANSACTION_TYPES } from '../../../../../shared/constants/transaction';
|
||||
import { renderWithProvider } from '../../../../../test/lib/render-helpers';
|
||||
import { TRANSACTION_ERROR_KEY } from '../../../../helpers/constants/error-keys';
|
||||
import ConfirmPageContainerContent from './confirm-page-container-content.component';
|
||||
@ -10,8 +11,18 @@ describe('Confirm Page Container Content', () => {
|
||||
metamask: {
|
||||
provider: {
|
||||
type: 'test',
|
||||
chainId: '0x3',
|
||||
},
|
||||
eip1559V2Enabled: false,
|
||||
addressBook: {
|
||||
'0x3': {
|
||||
'0x06195827297c7A80a443b6894d3BDB8824b43896': {
|
||||
address: '0x06195827297c7A80a443b6894d3BDB8824b43896',
|
||||
name: 'Address Book Account 1',
|
||||
chainId: '0x3',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
@ -125,4 +136,30 @@ describe('Confirm Page Container Content', () => {
|
||||
fireEvent.click(cancelButton);
|
||||
expect(props.onCancel).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('render contract address name from addressBook in title for contract', async () => {
|
||||
props.hasSimulationError = false;
|
||||
props.disabled = false;
|
||||
props.toAddress = '0x06195827297c7A80a443b6894d3BDB8824b43896';
|
||||
props.transactionType = TRANSACTION_TYPES.CONTRACT_INTERACTION;
|
||||
const { queryByText } = renderWithProvider(
|
||||
<ConfirmPageContainerContent {...props} />,
|
||||
store,
|
||||
);
|
||||
|
||||
expect(queryByText('Address Book Account 1')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('render simple title without address name for simple send', async () => {
|
||||
props.hasSimulationError = false;
|
||||
props.disabled = false;
|
||||
props.toAddress = '0x06195827297c7A80a443b6894d3BDB8824b43896';
|
||||
props.transactionType = TRANSACTION_TYPES.SIMPLE_SEND;
|
||||
const { queryByText } = renderWithProvider(
|
||||
<ConfirmPageContainerContent {...props} />,
|
||||
store,
|
||||
);
|
||||
|
||||
expect(queryByText('Address Book Account 1')).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
@ -1,8 +1,16 @@
|
||||
/* eslint-disable no-negated-condition */
|
||||
import React from 'react';
|
||||
import React, { useState } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import classnames from 'classnames';
|
||||
|
||||
import { TRANSACTION_TYPES } from '../../../../../../shared/constants/transaction';
|
||||
import { toChecksumHexAddress } from '../../../../../../shared/modules/hexstring-utils';
|
||||
import { useI18nContext } from '../../../../../hooks/useI18nContext';
|
||||
import useAddressDetails from '../../../../../hooks/useAddressDetails';
|
||||
|
||||
import Identicon from '../../../../ui/identicon';
|
||||
import InfoTooltip from '../../../../ui/info-tooltip';
|
||||
import NicknamePopovers from '../../../modals/nickname-popovers';
|
||||
|
||||
const ConfirmPageContainerSummary = (props) => {
|
||||
const {
|
||||
@ -17,8 +25,18 @@ const ConfirmPageContainerSummary = (props) => {
|
||||
origin,
|
||||
hideTitle,
|
||||
image,
|
||||
transactionType,
|
||||
toAddress,
|
||||
} = props;
|
||||
|
||||
const [showNicknamePopovers, setShowNicknamePopovers] = useState(false);
|
||||
const t = useI18nContext();
|
||||
const { toName, isTrusted } = useAddressDetails(toAddress);
|
||||
|
||||
const isContractTypeTransaction =
|
||||
transactionType === TRANSACTION_TYPES.CONTRACT_INTERACTION;
|
||||
const checksummedAddress = toChecksumHexAddress(toAddress);
|
||||
|
||||
const renderImage = () => {
|
||||
if (image) {
|
||||
return (
|
||||
@ -47,7 +65,29 @@ const ConfirmPageContainerSummary = (props) => {
|
||||
<div className="confirm-page-container-summary__origin">{origin}</div>
|
||||
)}
|
||||
<div className="confirm-page-container-summary__action-row">
|
||||
<div className="confirm-page-container-summary__action">{action}</div>
|
||||
<div className="confirm-page-container-summary__action">
|
||||
{isContractTypeTransaction && toName && (
|
||||
<span className="confirm-page-container-summary__action__contract-address">
|
||||
<button
|
||||
className="confirm-page-container-summary__action__contract-address-btn"
|
||||
onClick={() => setShowNicknamePopovers(true)}
|
||||
role="button"
|
||||
>
|
||||
{toName}
|
||||
</button>
|
||||
:
|
||||
</span>
|
||||
)}
|
||||
<span className="confirm-page-container-summary__action__name">
|
||||
{action}
|
||||
</span>
|
||||
{isContractTypeTransaction && isTrusted === false && (
|
||||
<InfoTooltip
|
||||
position="top"
|
||||
contentText={t('unverifiedContractAddressMessage')}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
{nonce && (
|
||||
<div className="confirm-page-container-summary__nonce">
|
||||
{`#${nonce}`}
|
||||
@ -69,6 +109,12 @@ const ConfirmPageContainerSummary = (props) => {
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
{showNicknamePopovers && (
|
||||
<NicknamePopovers
|
||||
onClose={() => setShowNicknamePopovers(false)}
|
||||
address={checksummedAddress}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@ -85,6 +131,8 @@ ConfirmPageContainerSummary.propTypes = {
|
||||
nonce: PropTypes.string,
|
||||
origin: PropTypes.string.isRequired,
|
||||
hideTitle: PropTypes.bool,
|
||||
toAddress: PropTypes.string,
|
||||
transactionType: PropTypes.string,
|
||||
};
|
||||
|
||||
export default ConfirmPageContainerSummary;
|
||||
|
@ -23,12 +23,36 @@
|
||||
&__action {
|
||||
@include H7;
|
||||
|
||||
text-transform: uppercase;
|
||||
color: var(--oslo-gray);
|
||||
padding: 3px 8px;
|
||||
border: 1px solid var(--oslo-gray);
|
||||
border-radius: 4px;
|
||||
display: inline-block;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
|
||||
&__name {
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.info-tooltip {
|
||||
margin-inline-start: 4px;
|
||||
|
||||
&__tooltip-container {
|
||||
margin-bottom: -3px;
|
||||
}
|
||||
}
|
||||
|
||||
&__contract-address {
|
||||
margin-inline-end: 4px;
|
||||
}
|
||||
|
||||
&__contract-address-btn {
|
||||
background: none;
|
||||
border: none;
|
||||
padding: 0;
|
||||
margin-inline-end: 4px;
|
||||
color: var(--primary-1);
|
||||
}
|
||||
}
|
||||
|
||||
&__nonce {
|
||||
|
@ -256,6 +256,8 @@ export default class ConfirmPageContainer extends Component {
|
||||
nativeCurrency={nativeCurrency}
|
||||
networkName={networkName}
|
||||
showBuyModal={showBuyModal}
|
||||
toAddress={toAddress}
|
||||
transactionType={currentTransaction.type}
|
||||
/>
|
||||
)}
|
||||
{shouldDisplayWarning && errorKey === INSUFFICIENT_FUNDS_ERROR_KEY && (
|
||||
|
48
ui/hooks/useAddressDetails.js
Normal file
48
ui/hooks/useAddressDetails.js
Normal file
@ -0,0 +1,48 @@
|
||||
import { useSelector } from 'react-redux';
|
||||
|
||||
import { toChecksumHexAddress } from '../../shared/modules/hexstring-utils';
|
||||
import {
|
||||
getAddressBook,
|
||||
getMetaMaskIdentities,
|
||||
getTokenList,
|
||||
getUseTokenDetection,
|
||||
} from '../selectors';
|
||||
import { shortenAddress } from '../helpers/utils/util';
|
||||
|
||||
const useAddressDetails = (toAddress) => {
|
||||
const addressBook = useSelector(getAddressBook);
|
||||
const identities = useSelector(getMetaMaskIdentities);
|
||||
const tokenList = useSelector(getTokenList);
|
||||
const useTokenDetection = useSelector(getUseTokenDetection);
|
||||
const checksummedAddress = toChecksumHexAddress(toAddress);
|
||||
|
||||
if (!toAddress) {
|
||||
return {};
|
||||
}
|
||||
const addressBookEntryObject = addressBook.find(
|
||||
(entry) => entry.address === checksummedAddress,
|
||||
);
|
||||
if (addressBookEntryObject?.name) {
|
||||
return { toName: addressBookEntryObject.name, isTrusted: true };
|
||||
}
|
||||
if (identities[toAddress]?.name) {
|
||||
return { toName: identities[toAddress].name, isTrusted: true };
|
||||
}
|
||||
const casedTokenList = useTokenDetection
|
||||
? tokenList
|
||||
: Object.keys(tokenList).reduce((acc, base) => {
|
||||
return {
|
||||
...acc,
|
||||
[base.toLowerCase()]: tokenList[base],
|
||||
};
|
||||
}, {});
|
||||
if (casedTokenList[toAddress]?.name) {
|
||||
return { toName: casedTokenList[toAddress].name, isTrusted: true };
|
||||
}
|
||||
return {
|
||||
toName: shortenAddress(checksummedAddress),
|
||||
isTrusted: false,
|
||||
};
|
||||
};
|
||||
|
||||
export default useAddressDetails;
|
102
ui/hooks/useAddressDetails.test.js
Normal file
102
ui/hooks/useAddressDetails.test.js
Normal file
@ -0,0 +1,102 @@
|
||||
import React from 'react';
|
||||
import { Provider } from 'react-redux';
|
||||
import { renderHook } from '@testing-library/react-hooks';
|
||||
|
||||
import configureStore from '../store/store';
|
||||
import useAddressDetails from './useAddressDetails';
|
||||
|
||||
const renderUseAddressDetails = (toAddress, stateVariables = {}) => {
|
||||
const mockState = {
|
||||
metamask: {
|
||||
provider: {
|
||||
type: 'test',
|
||||
chainId: '0x3',
|
||||
},
|
||||
tokenList: {},
|
||||
...stateVariables,
|
||||
},
|
||||
};
|
||||
|
||||
const wrapper = ({ children }) => (
|
||||
<Provider store={configureStore(mockState)}>{children}</Provider>
|
||||
);
|
||||
|
||||
return renderHook(() => useAddressDetails(toAddress), { wrapper });
|
||||
};
|
||||
|
||||
describe('useAddressDetails', () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
it('should return empty object if no address is passed', () => {
|
||||
const { result } = renderUseAddressDetails();
|
||||
expect(result.current).toStrictEqual({});
|
||||
});
|
||||
|
||||
it('should return name from addressBook if address is present in addressBook', () => {
|
||||
const { result } = renderUseAddressDetails(
|
||||
'0x06195827297c7A80a443b6894d3BDB8824b43896',
|
||||
{
|
||||
addressBook: {
|
||||
'0x3': {
|
||||
'0x06195827297c7A80a443b6894d3BDB8824b43896': {
|
||||
address: '0x06195827297c7A80a443b6894d3BDB8824b43896',
|
||||
name: 'Address Book Account 1',
|
||||
chainId: '0x3',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
);
|
||||
const { toName, isTrusted } = result.current;
|
||||
expect(toName).toBe('Address Book Account 1');
|
||||
expect(isTrusted).toBe(true);
|
||||
});
|
||||
|
||||
it('should return name from identities if address is present in identities', () => {
|
||||
const { result } = renderUseAddressDetails(
|
||||
'0x06195827297c7A80a443b6894d3BDB8824b43896',
|
||||
{
|
||||
identities: {
|
||||
'0x06195827297c7A80a443b6894d3BDB8824b43896': {
|
||||
address: '0x06195827297c7A80a443b6894d3BDB8824b43896',
|
||||
name: 'Account 1',
|
||||
},
|
||||
},
|
||||
},
|
||||
);
|
||||
const { toName, isTrusted } = result.current;
|
||||
expect(toName).toBe('Account 1');
|
||||
expect(isTrusted).toBe(true);
|
||||
});
|
||||
|
||||
it('should return name from tokenlist if address is present in tokens', () => {
|
||||
const { result } = renderUseAddressDetails(
|
||||
'0x06195827297c7A80a443b6894d3BDB8824b43896',
|
||||
{
|
||||
useTokenDetection: true,
|
||||
tokenList: {
|
||||
'0x06195827297c7A80a443b6894d3BDB8824b43896': {
|
||||
address: '0x06195827297c7A80a443b6894d3BDB8824b43896',
|
||||
symbol: 'LINK',
|
||||
decimals: 18,
|
||||
name: 'TOKEN-ABC',
|
||||
},
|
||||
},
|
||||
},
|
||||
);
|
||||
const { toName, isTrusted } = result.current;
|
||||
expect(toName).toBe('TOKEN-ABC');
|
||||
expect(isTrusted).toBe(true);
|
||||
});
|
||||
|
||||
it('should return shortened address if address is not presend in any of above sources', () => {
|
||||
const { result } = renderUseAddressDetails(
|
||||
'0x06195827297c7A80a443b6894d3BDB8824b43896',
|
||||
);
|
||||
const { toName, isTrusted } = result.current;
|
||||
expect(toName).toBe('0x061...3896');
|
||||
expect(isTrusted).toBe(false);
|
||||
});
|
||||
});
|
@ -1045,10 +1045,7 @@ export default class ConfirmTransactionBase extends Component {
|
||||
};
|
||||
|
||||
let functionType;
|
||||
if (
|
||||
txData.type === TRANSACTION_TYPES.DEPLOY_CONTRACT ||
|
||||
txData.type === TRANSACTION_TYPES.CONTRACT_INTERACTION
|
||||
) {
|
||||
if (txData.type === TRANSACTION_TYPES.CONTRACT_INTERACTION) {
|
||||
functionType = getMethodName(name);
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user