1
0
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:
Jyoti Puri 2022-03-02 20:56:53 +05:30 committed by GitHub
parent e4181ec581
commit 137b47370e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 276 additions and 8 deletions

View File

@ -3665,6 +3665,9 @@
"message": "Sending collectible (ERC-721) tokens is not currently supported", "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" "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": { "updatedWithDate": {
"message": "Updated $1" "message": "Updated $1"
}, },

View File

@ -42,6 +42,7 @@ describe('Confirm Page Container Container Test', () => {
identities: [], identities: [],
featureFlags: {}, featureFlags: {},
enableEIP1559V2NoticeDismissed: true, enableEIP1559V2NoticeDismissed: true,
tokenList: {},
}, },
}; };

View File

@ -55,6 +55,8 @@ export default class ConfirmPageContainerContent extends Component {
nativeCurrency: PropTypes.string, nativeCurrency: PropTypes.string,
networkName: PropTypes.string, networkName: PropTypes.string,
showBuyModal: PropTypes.func, showBuyModal: PropTypes.func,
toAddress: PropTypes.string,
transactionType: PropTypes.string,
}; };
renderContent() { renderContent() {
@ -128,6 +130,8 @@ export default class ConfirmPageContainerContent extends Component {
nativeCurrency, nativeCurrency,
networkName, networkName,
showBuyModal, showBuyModal,
toAddress,
transactionType,
} = this.props; } = this.props;
const primaryAction = hideUserAcknowledgedGasMissing const primaryAction = hideUserAcknowledgedGasMissing
@ -179,6 +183,8 @@ export default class ConfirmPageContainerContent extends Component {
nonce={nonce} nonce={nonce}
origin={origin} origin={origin}
hideTitle={hideTitle} hideTitle={hideTitle}
toAddress={toAddress}
transactionType={transactionType}
/> />
{this.renderContent()} {this.renderContent()}
{!supportsEIP1559V2 && {!supportsEIP1559V2 &&

View File

@ -1,6 +1,7 @@
import { fireEvent } from '@testing-library/react'; import { fireEvent } from '@testing-library/react';
import React from 'react'; import React from 'react';
import configureMockStore from 'redux-mock-store'; import configureMockStore from 'redux-mock-store';
import { TRANSACTION_TYPES } from '../../../../../shared/constants/transaction';
import { renderWithProvider } from '../../../../../test/lib/render-helpers'; import { renderWithProvider } from '../../../../../test/lib/render-helpers';
import { TRANSACTION_ERROR_KEY } from '../../../../helpers/constants/error-keys'; import { TRANSACTION_ERROR_KEY } from '../../../../helpers/constants/error-keys';
import ConfirmPageContainerContent from './confirm-page-container-content.component'; import ConfirmPageContainerContent from './confirm-page-container-content.component';
@ -10,8 +11,18 @@ describe('Confirm Page Container Content', () => {
metamask: { metamask: {
provider: { provider: {
type: 'test', type: 'test',
chainId: '0x3',
}, },
eip1559V2Enabled: false, 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); fireEvent.click(cancelButton);
expect(props.onCancel).toHaveBeenCalledTimes(1); 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();
});
}); });

View File

@ -1,8 +1,16 @@
/* eslint-disable no-negated-condition */ /* eslint-disable no-negated-condition */
import React from 'react'; import React, { useState } from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import classnames from 'classnames'; 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 Identicon from '../../../../ui/identicon';
import InfoTooltip from '../../../../ui/info-tooltip';
import NicknamePopovers from '../../../modals/nickname-popovers';
const ConfirmPageContainerSummary = (props) => { const ConfirmPageContainerSummary = (props) => {
const { const {
@ -17,8 +25,18 @@ const ConfirmPageContainerSummary = (props) => {
origin, origin,
hideTitle, hideTitle,
image, image,
transactionType,
toAddress,
} = props; } = 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 = () => { const renderImage = () => {
if (image) { if (image) {
return ( return (
@ -47,7 +65,29 @@ const ConfirmPageContainerSummary = (props) => {
<div className="confirm-page-container-summary__origin">{origin}</div> <div className="confirm-page-container-summary__origin">{origin}</div>
)} )}
<div className="confirm-page-container-summary__action-row"> <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 && ( {nonce && (
<div className="confirm-page-container-summary__nonce"> <div className="confirm-page-container-summary__nonce">
{`#${nonce}`} {`#${nonce}`}
@ -69,6 +109,12 @@ const ConfirmPageContainerSummary = (props) => {
</div> </div>
)} )}
</> </>
{showNicknamePopovers && (
<NicknamePopovers
onClose={() => setShowNicknamePopovers(false)}
address={checksummedAddress}
/>
)}
</div> </div>
); );
}; };
@ -85,6 +131,8 @@ ConfirmPageContainerSummary.propTypes = {
nonce: PropTypes.string, nonce: PropTypes.string,
origin: PropTypes.string.isRequired, origin: PropTypes.string.isRequired,
hideTitle: PropTypes.bool, hideTitle: PropTypes.bool,
toAddress: PropTypes.string,
transactionType: PropTypes.string,
}; };
export default ConfirmPageContainerSummary; export default ConfirmPageContainerSummary;

View File

@ -23,12 +23,36 @@
&__action { &__action {
@include H7; @include H7;
text-transform: uppercase;
color: var(--oslo-gray); color: var(--oslo-gray);
padding: 3px 8px; padding: 3px 8px;
border: 1px solid var(--oslo-gray); border: 1px solid var(--oslo-gray);
border-radius: 4px; 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 { &__nonce {

View File

@ -256,6 +256,8 @@ export default class ConfirmPageContainer extends Component {
nativeCurrency={nativeCurrency} nativeCurrency={nativeCurrency}
networkName={networkName} networkName={networkName}
showBuyModal={showBuyModal} showBuyModal={showBuyModal}
toAddress={toAddress}
transactionType={currentTransaction.type}
/> />
)} )}
{shouldDisplayWarning && errorKey === INSUFFICIENT_FUNDS_ERROR_KEY && ( {shouldDisplayWarning && errorKey === INSUFFICIENT_FUNDS_ERROR_KEY && (

View 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;

View 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);
});
});

View File

@ -1045,10 +1045,7 @@ export default class ConfirmTransactionBase extends Component {
}; };
let functionType; let functionType;
if ( if (txData.type === TRANSACTION_TYPES.CONTRACT_INTERACTION) {
txData.type === TRANSACTION_TYPES.DEPLOY_CONTRACT ||
txData.type === TRANSACTION_TYPES.CONTRACT_INTERACTION
) {
functionType = getMethodName(name); functionType = getMethodName(name);
} }