diff --git a/app/_locales/en/messages.json b/app/_locales/en/messages.json
index 27bdc23e8..ebe1cbc07 100644
--- a/app/_locales/en/messages.json
+++ b/app/_locales/en/messages.json
@@ -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"
},
diff --git a/ui/components/app/confirm-page-container/confirm-page-container-container.test.js b/ui/components/app/confirm-page-container/confirm-page-container-container.test.js
index 92fa00dc4..da2025e12 100644
--- a/ui/components/app/confirm-page-container/confirm-page-container-container.test.js
+++ b/ui/components/app/confirm-page-container/confirm-page-container-container.test.js
@@ -42,6 +42,7 @@ describe('Confirm Page Container Container Test', () => {
identities: [],
featureFlags: {},
enableEIP1559V2NoticeDismissed: true,
+ tokenList: {},
},
};
diff --git a/ui/components/app/confirm-page-container/confirm-page-container-content/confirm-page-container-content.component.js b/ui/components/app/confirm-page-container/confirm-page-container-content/confirm-page-container-content.component.js
index 54dcb6d62..9723f4ce5 100644
--- a/ui/components/app/confirm-page-container/confirm-page-container-content/confirm-page-container-content.component.js
+++ b/ui/components/app/confirm-page-container/confirm-page-container-content/confirm-page-container-content.component.js
@@ -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 &&
diff --git a/ui/components/app/confirm-page-container/confirm-page-container-content/confirm-page-container-content.component.test.js b/ui/components/app/confirm-page-container/confirm-page-container-content/confirm-page-container-content.component.test.js
index bff595911..89c855a71 100644
--- a/ui/components/app/confirm-page-container/confirm-page-container-content/confirm-page-container-content.component.test.js
+++ b/ui/components/app/confirm-page-container/confirm-page-container-content/confirm-page-container-content.component.test.js
@@ -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(
+ ,
+ 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(
+ ,
+ store,
+ );
+
+ expect(queryByText('Address Book Account 1')).not.toBeInTheDocument();
+ });
});
diff --git a/ui/components/app/confirm-page-container/confirm-page-container-content/confirm-page-container-summary/confirm-page-container-summary.component.js b/ui/components/app/confirm-page-container/confirm-page-container-content/confirm-page-container-summary/confirm-page-container-summary.component.js
index d4878442f..e941568e9 100644
--- a/ui/components/app/confirm-page-container/confirm-page-container-content/confirm-page-container-summary/confirm-page-container-summary.component.js
+++ b/ui/components/app/confirm-page-container/confirm-page-container-content/confirm-page-container-summary/confirm-page-container-summary.component.js
@@ -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) => {
{origin}
)}
-
{action}
+
+ {isContractTypeTransaction && toName && (
+
+
+ :
+
+ )}
+
+ {action}
+
+ {isContractTypeTransaction && isTrusted === false && (
+
+ )}
+
{nonce && (
{`#${nonce}`}
@@ -69,6 +109,12 @@ const ConfirmPageContainerSummary = (props) => {
)}
>
+ {showNicknamePopovers && (
+
setShowNicknamePopovers(false)}
+ address={checksummedAddress}
+ />
+ )}
);
};
@@ -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;
diff --git a/ui/components/app/confirm-page-container/confirm-page-container-content/confirm-page-container-summary/index.scss b/ui/components/app/confirm-page-container/confirm-page-container-content/confirm-page-container-summary/index.scss
index d8f7a307c..0f4941976 100644
--- a/ui/components/app/confirm-page-container/confirm-page-container-content/confirm-page-container-summary/index.scss
+++ b/ui/components/app/confirm-page-container/confirm-page-container-content/confirm-page-container-summary/index.scss
@@ -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 {
diff --git a/ui/components/app/confirm-page-container/confirm-page-container.component.js b/ui/components/app/confirm-page-container/confirm-page-container.component.js
index 25c9ff937..270055889 100644
--- a/ui/components/app/confirm-page-container/confirm-page-container.component.js
+++ b/ui/components/app/confirm-page-container/confirm-page-container.component.js
@@ -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 && (
diff --git a/ui/hooks/useAddressDetails.js b/ui/hooks/useAddressDetails.js
new file mode 100644
index 000000000..9755f3bf1
--- /dev/null
+++ b/ui/hooks/useAddressDetails.js
@@ -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;
diff --git a/ui/hooks/useAddressDetails.test.js b/ui/hooks/useAddressDetails.test.js
new file mode 100644
index 000000000..89fb38d12
--- /dev/null
+++ b/ui/hooks/useAddressDetails.test.js
@@ -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 }) => (
+ {children}
+ );
+
+ 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);
+ });
+});
diff --git a/ui/pages/confirm-transaction-base/confirm-transaction-base.component.js b/ui/pages/confirm-transaction-base/confirm-transaction-base.component.js
index a7202b6ca..7669eb59f 100644
--- a/ui/pages/confirm-transaction-base/confirm-transaction-base.component.js
+++ b/ui/pages/confirm-transaction-base/confirm-transaction-base.component.js
@@ -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);
}