mirror of
https://github.com/kremalicious/metamask-extension.git
synced 2024-12-22 17:33:23 +01:00
Replace the address in SignTypedData_v4 signatures with a 'Verify contract details' link (#16191)
This commit is contained in:
parent
704e837405
commit
c87a4f5968
3
app/_locales/en/messages.json
generated
3
app/_locales/en/messages.json
generated
@ -790,6 +790,9 @@
|
||||
"contractRequestingAccess": {
|
||||
"message": "Contract requesting access"
|
||||
},
|
||||
"contractRequestingSignature": {
|
||||
"message": "Contract requesting signature"
|
||||
},
|
||||
"contractRequestingSpendingCap": {
|
||||
"message": "Contract requesting spending cap"
|
||||
},
|
||||
|
@ -53,20 +53,22 @@ describe('Sign Typed Data V4 Signature Request', function () {
|
||||
const content = await driver.findElements(
|
||||
'.signature-request-content__info',
|
||||
);
|
||||
const verifyContractDetailsButton = await driver.findElement(
|
||||
'.signature-request-content__verify-contract-details',
|
||||
);
|
||||
const origin = content[0];
|
||||
const address = content[1];
|
||||
const message = await driver.findElement(
|
||||
'.signature-request-data__node__value',
|
||||
);
|
||||
assert.equal(await title.getText(), 'Signature request');
|
||||
assert.equal(await name.getText(), 'Ether Mail');
|
||||
assert.equal(await origin.getText(), 'http://127.0.0.1:8080');
|
||||
assert.equal(
|
||||
await address.getText(),
|
||||
`${publicAddress.slice(0, 8)}...${publicAddress.slice(
|
||||
publicAddress.length - 8,
|
||||
)}`,
|
||||
);
|
||||
|
||||
verifyContractDetailsButton.click();
|
||||
await driver.findElement({ text: 'Contract details', tag: 'h5' });
|
||||
await driver.findElement('[data-testid="recipient"]');
|
||||
await driver.clickElement({ text: 'Got it', tag: 'button' });
|
||||
|
||||
assert.equal(await message.getText(), 'Hello, Bob!');
|
||||
// Approve signing typed data
|
||||
await driver.clickElement(
|
||||
@ -137,20 +139,22 @@ describe('Sign Typed Data V3 Signature Request', function () {
|
||||
const content = await driver.findElements(
|
||||
'.signature-request-content__info',
|
||||
);
|
||||
const verifyContractDetailsButton = await driver.findElement(
|
||||
'.signature-request-content__verify-contract-details',
|
||||
);
|
||||
const origin = content[0];
|
||||
const address = content[1];
|
||||
const messages = await driver.findElements(
|
||||
'.signature-request-data__node__value',
|
||||
);
|
||||
assert.equal(await title.getText(), 'Signature request');
|
||||
assert.equal(await name.getText(), 'Ether Mail');
|
||||
assert.equal(await origin.getText(), 'http://127.0.0.1:8080');
|
||||
assert.equal(
|
||||
await address.getText(),
|
||||
`${publicAddress.slice(0, 8)}...${publicAddress.slice(
|
||||
publicAddress.length - 8,
|
||||
)}`,
|
||||
);
|
||||
|
||||
verifyContractDetailsButton.click();
|
||||
await driver.findElement({ text: 'Contract details', tag: 'h5' });
|
||||
await driver.findElement('[data-testid="recipient"]');
|
||||
await driver.clickElement({ text: 'Got it', tag: 'button' });
|
||||
|
||||
assert.equal(await messages[4].getText(), 'Hello, Bob!');
|
||||
|
||||
// Approve signing typed data
|
||||
|
@ -40,6 +40,7 @@ export default function ContractDetailsModal({
|
||||
tokenId,
|
||||
assetName,
|
||||
assetStandard,
|
||||
isContractRequestingSignature,
|
||||
}) {
|
||||
const t = useI18nContext();
|
||||
const [copiedTokenAddress, handleCopyTokenAddress] = useCopyToClipboard();
|
||||
@ -80,117 +81,123 @@ export default function ContractDetailsModal({
|
||||
>
|
||||
{t('contractDescription')}
|
||||
</Typography>
|
||||
<Typography
|
||||
variant={TYPOGRAPHY.H6}
|
||||
display={DISPLAY.FLEX}
|
||||
marginTop={4}
|
||||
marginBottom={2}
|
||||
>
|
||||
{nft ? t('contractNFT') : t('contractToken')}
|
||||
</Typography>
|
||||
<Box
|
||||
display={DISPLAY.FLEX}
|
||||
borderRadius={SIZES.SM}
|
||||
borderStyle={BORDER_STYLE.SOLID}
|
||||
borderColor={COLORS.BORDER_DEFAULT}
|
||||
className="contract-details-modal__content__contract"
|
||||
>
|
||||
{nft ? (
|
||||
<Box margin={4}>
|
||||
<NftCollectionImage
|
||||
assetName={assetName}
|
||||
tokenAddress={tokenAddress}
|
||||
/>
|
||||
</Box>
|
||||
) : (
|
||||
<Identicon
|
||||
className="contract-details-modal__content__contract__identicon"
|
||||
address={tokenAddress}
|
||||
diameter={24}
|
||||
/>
|
||||
)}
|
||||
<Box data-testid="recipient">
|
||||
{!isContractRequestingSignature && (
|
||||
<>
|
||||
<Typography
|
||||
fontWeight={FONT_WEIGHT.BOLD}
|
||||
variant={TYPOGRAPHY.H5}
|
||||
variant={TYPOGRAPHY.H6}
|
||||
display={DISPLAY.FLEX}
|
||||
marginTop={4}
|
||||
marginBottom={2}
|
||||
>
|
||||
{tokenName || ellipsify(tokenAddress)}
|
||||
{nft ? t('contractNFT') : t('contractToken')}
|
||||
</Typography>
|
||||
{tokenName && (
|
||||
<Typography
|
||||
variant={TYPOGRAPHY.H6}
|
||||
display={DISPLAY.FLEX}
|
||||
color={COLORS.TEXT_ALTERNATIVE}
|
||||
marginTop={0}
|
||||
marginBottom={4}
|
||||
>
|
||||
{ellipsify(tokenAddress)}
|
||||
</Typography>
|
||||
)}
|
||||
</Box>
|
||||
<Box
|
||||
justifyContent={JUSTIFY_CONTENT.FLEX_END}
|
||||
className="contract-details-modal__content__contract__buttons"
|
||||
>
|
||||
<Box marginTop={4} marginRight={5}>
|
||||
<Tooltip
|
||||
position="top"
|
||||
title={
|
||||
copiedTokenAddress
|
||||
? t('copiedExclamation')
|
||||
: t('copyToClipboard')
|
||||
}
|
||||
>
|
||||
<Button
|
||||
className="contract-details-modal__content__contract__buttons__copy"
|
||||
type="link"
|
||||
onClick={() => {
|
||||
handleCopyTokenAddress(tokenAddress);
|
||||
}}
|
||||
>
|
||||
<IconCopy color="var(--color-icon-muted)" />
|
||||
</Button>
|
||||
</Tooltip>
|
||||
</Box>
|
||||
<Box marginTop={5} marginRight={5}>
|
||||
<Tooltip position="top" title={t('openInBlockExplorer')}>
|
||||
<Button
|
||||
className="contract-details-modal__content__contract__buttons__block-explorer"
|
||||
type="link"
|
||||
onClick={() => {
|
||||
const blockExplorerTokenLink = getAccountLink(
|
||||
tokenAddress,
|
||||
chainId,
|
||||
{
|
||||
blockExplorerUrl: rpcPrefs?.blockExplorerUrl ?? null,
|
||||
},
|
||||
null,
|
||||
);
|
||||
global.platform.openTab({
|
||||
url: blockExplorerTokenLink,
|
||||
});
|
||||
}}
|
||||
>
|
||||
<IconBlockExplorer
|
||||
size={16}
|
||||
color="var(--color-icon-muted)"
|
||||
<Box
|
||||
display={DISPLAY.FLEX}
|
||||
borderRadius={SIZES.SM}
|
||||
borderStyle={BORDER_STYLE.SOLID}
|
||||
borderColor={COLORS.BORDER_DEFAULT}
|
||||
className="contract-details-modal__content__contract"
|
||||
>
|
||||
{nft ? (
|
||||
<Box margin={4}>
|
||||
<NftCollectionImage
|
||||
assetName={assetName}
|
||||
tokenAddress={tokenAddress}
|
||||
/>
|
||||
</Button>
|
||||
</Tooltip>
|
||||
</Box>
|
||||
) : (
|
||||
<Identicon
|
||||
className="contract-details-modal__content__contract__identicon"
|
||||
address={tokenAddress}
|
||||
diameter={24}
|
||||
/>
|
||||
)}
|
||||
<Box data-testid="recipient">
|
||||
<Typography
|
||||
fontWeight={FONT_WEIGHT.BOLD}
|
||||
variant={TYPOGRAPHY.H5}
|
||||
marginTop={4}
|
||||
>
|
||||
{tokenName || ellipsify(tokenAddress)}
|
||||
</Typography>
|
||||
{tokenName && (
|
||||
<Typography
|
||||
variant={TYPOGRAPHY.H6}
|
||||
display={DISPLAY.FLEX}
|
||||
color={COLORS.TEXT_ALTERNATIVE}
|
||||
marginTop={0}
|
||||
marginBottom={4}
|
||||
>
|
||||
{ellipsify(tokenAddress)}
|
||||
</Typography>
|
||||
)}
|
||||
</Box>
|
||||
<Box
|
||||
justifyContent={JUSTIFY_CONTENT.FLEX_END}
|
||||
className="contract-details-modal__content__contract__buttons"
|
||||
>
|
||||
<Box marginTop={4} marginRight={5}>
|
||||
<Tooltip
|
||||
position="top"
|
||||
title={
|
||||
copiedTokenAddress
|
||||
? t('copiedExclamation')
|
||||
: t('copyToClipboard')
|
||||
}
|
||||
>
|
||||
<Button
|
||||
className="contract-details-modal__content__contract__buttons__copy"
|
||||
type="link"
|
||||
onClick={() => {
|
||||
handleCopyTokenAddress(tokenAddress);
|
||||
}}
|
||||
>
|
||||
<IconCopy color="var(--color-icon-muted)" />
|
||||
</Button>
|
||||
</Tooltip>
|
||||
</Box>
|
||||
<Box marginTop={5} marginRight={5}>
|
||||
<Tooltip position="top" title={t('openInBlockExplorer')}>
|
||||
<Button
|
||||
className="contract-details-modal__content__contract__buttons__block-explorer"
|
||||
type="link"
|
||||
onClick={() => {
|
||||
const blockExplorerTokenLink = getAccountLink(
|
||||
tokenAddress,
|
||||
chainId,
|
||||
{
|
||||
blockExplorerUrl:
|
||||
rpcPrefs?.blockExplorerUrl ?? null,
|
||||
},
|
||||
null,
|
||||
);
|
||||
global.platform.openTab({
|
||||
url: blockExplorerTokenLink,
|
||||
});
|
||||
}}
|
||||
>
|
||||
<IconBlockExplorer
|
||||
size={16}
|
||||
color="var(--color-icon-muted)"
|
||||
/>
|
||||
</Button>
|
||||
</Tooltip>
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
</>
|
||||
)}
|
||||
<Typography
|
||||
variant={TYPOGRAPHY.H6}
|
||||
display={DISPLAY.FLEX}
|
||||
marginTop={4}
|
||||
marginBottom={2}
|
||||
>
|
||||
{nft
|
||||
? t('contractRequestingAccess')
|
||||
: t('contractRequestingSpendingCap')}
|
||||
{nft && t('contractRequestingAccess')}
|
||||
{isContractRequestingSignature && t('contractRequestingSignature')}
|
||||
{!nft &&
|
||||
!isContractRequestingSignature &&
|
||||
t('contractRequestingSpendingCap')}
|
||||
</Typography>
|
||||
<Box
|
||||
display={DISPLAY.FLEX}
|
||||
@ -356,4 +363,8 @@ ContractDetailsModal.propTypes = {
|
||||
* The name of the collection
|
||||
*/
|
||||
assetName: PropTypes.string,
|
||||
/**
|
||||
* Whether contract requesting signature flow has started
|
||||
*/
|
||||
isContractRequestingSignature: PropTypes.bool,
|
||||
};
|
||||
|
@ -85,3 +85,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
a.signature-request-content__verify-contract-details {
|
||||
padding: 0;
|
||||
}
|
||||
|
@ -5,9 +5,13 @@ import LedgerInstructionField from '../ledger-instruction-field';
|
||||
import { sanitizeMessage } from '../../../helpers/utils/util';
|
||||
import { EVENT } from '../../../../shared/constants/metametrics';
|
||||
import SiteOrigin from '../../ui/site-origin';
|
||||
import Header from './signature-request-header';
|
||||
import Footer from './signature-request-footer';
|
||||
import Button from '../../ui/button';
|
||||
import Typography from '../../ui/typography/typography';
|
||||
import { COLORS, TYPOGRAPHY } from '../../../helpers/constants/design-system';
|
||||
import ContractDetailsModal from '../modals/contract-details-modal/contract-details-modal';
|
||||
import Message from './signature-request-message';
|
||||
import Footer from './signature-request-footer';
|
||||
import Header from './signature-request-header';
|
||||
|
||||
export default class SignatureRequest extends PureComponent {
|
||||
static propTypes = {
|
||||
@ -39,6 +43,18 @@ export default class SignatureRequest extends PureComponent {
|
||||
* Whether the hardware wallet requires a connection disables the sign button if true.
|
||||
*/
|
||||
hardwareWalletRequiresConnection: PropTypes.bool.isRequired,
|
||||
/**
|
||||
* Current network chainId
|
||||
*/
|
||||
chainId: PropTypes.string,
|
||||
/**
|
||||
* RPC prefs of the current network
|
||||
*/
|
||||
rpcPrefs: PropTypes.object,
|
||||
/**
|
||||
* Dapp image
|
||||
*/
|
||||
siteImage: PropTypes.string,
|
||||
};
|
||||
|
||||
static contextTypes = {
|
||||
@ -48,6 +64,7 @@ export default class SignatureRequest extends PureComponent {
|
||||
|
||||
state = {
|
||||
hasScrolledMessage: false,
|
||||
showContractDetails: false,
|
||||
};
|
||||
|
||||
setMessageRootRef(ref) {
|
||||
@ -72,6 +89,9 @@ export default class SignatureRequest extends PureComponent {
|
||||
sign,
|
||||
isLedgerWallet,
|
||||
hardwareWalletRequiresConnection,
|
||||
chainId,
|
||||
rpcPrefs,
|
||||
siteImage,
|
||||
} = this.props;
|
||||
const { address: fromAddress } = fromAccount;
|
||||
const { message, domain = {}, primaryType, types } = JSON.parse(data);
|
||||
@ -129,8 +149,19 @@ export default class SignatureRequest extends PureComponent {
|
||||
className="signature-request-content__info"
|
||||
siteOrigin={origin}
|
||||
/>
|
||||
<div className="signature-request-content__info">
|
||||
{this.formatWallet(fromAddress)}
|
||||
<div>
|
||||
<Button
|
||||
type="link"
|
||||
onClick={() => this.setState({ showContractDetails: true })}
|
||||
className="signature-request-content__verify-contract-details"
|
||||
>
|
||||
<Typography
|
||||
variant={TYPOGRAPHY.H7}
|
||||
color={COLORS.PRIMARY_DEFAULT}
|
||||
>
|
||||
{this.context.t('verifyContractDetails')}
|
||||
</Typography>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
{isLedgerWallet ? (
|
||||
@ -153,6 +184,17 @@ export default class SignatureRequest extends PureComponent {
|
||||
(messageIsScrollable && !this.state.hasScrolledMessage)
|
||||
}
|
||||
/>
|
||||
{this.state.showContractDetails && (
|
||||
<ContractDetailsModal
|
||||
toAddress={domain.verifyingContract}
|
||||
chainId={chainId}
|
||||
rpcPrefs={rpcPrefs}
|
||||
origin={origin}
|
||||
siteImage={siteImage}
|
||||
onClose={() => this.setState({ showContractDetails: false })}
|
||||
isContractRequestingSignature
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -2,6 +2,9 @@ import { connect } from 'react-redux';
|
||||
import {
|
||||
accountsWithSendEtherInfoSelector,
|
||||
doesAddressRequireLedgerHidConnection,
|
||||
getCurrentChainId,
|
||||
getRpcPrefsForCurrentProvider,
|
||||
getSubjectMetadata,
|
||||
} from '../../../selectors';
|
||||
import { isAddressLedger } from '../../../ducks/metamask/metamask';
|
||||
import { getAccountByAddress } from '../../../helpers/utils/util';
|
||||
@ -16,18 +19,33 @@ function mapStateToProps(state, ownProps) {
|
||||
const hardwareWalletRequiresConnection =
|
||||
doesAddressRequireLedgerHidConnection(state, from);
|
||||
const isLedgerWallet = isAddressLedger(state, from);
|
||||
const chainId = getCurrentChainId(state);
|
||||
const rpcPrefs = getRpcPrefsForCurrentProvider(state);
|
||||
const subjectMetadata = getSubjectMetadata(state);
|
||||
|
||||
const { iconUrl: siteImage = '' } =
|
||||
subjectMetadata[txData.msgParams.origin] || {};
|
||||
|
||||
return {
|
||||
isLedgerWallet,
|
||||
hardwareWalletRequiresConnection,
|
||||
chainId,
|
||||
rpcPrefs,
|
||||
siteImage,
|
||||
// not forwarded to component
|
||||
allAccounts: accountsWithSendEtherInfoSelector(state),
|
||||
};
|
||||
}
|
||||
|
||||
function mergeProps(stateProps, dispatchProps, ownProps) {
|
||||
const { allAccounts, isLedgerWallet, hardwareWalletRequiresConnection } =
|
||||
stateProps;
|
||||
const {
|
||||
allAccounts,
|
||||
isLedgerWallet,
|
||||
hardwareWalletRequiresConnection,
|
||||
chainId,
|
||||
rpcPrefs,
|
||||
siteImage,
|
||||
} = stateProps;
|
||||
const {
|
||||
signPersonalMessage,
|
||||
signTypedMessage,
|
||||
@ -68,6 +86,9 @@ function mergeProps(stateProps, dispatchProps, ownProps) {
|
||||
sign,
|
||||
isLedgerWallet,
|
||||
hardwareWalletRequiresConnection,
|
||||
chainId,
|
||||
rpcPrefs,
|
||||
siteImage,
|
||||
};
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user