1
0
mirror of https://github.com/kremalicious/metamask-extension.git synced 2024-12-22 09:23:21 +01:00

Signature-Request refactor (#19104)

* refactoring signature-request and adding test coverage

* adding storybook and removing the reduntant files:

* adding new components from

* replacing <SiteOrigin/> with <TagUrl/>

* updating review comments from Jyoti and George

* adding the hook

* refactoring in the changes from #18770 MMI PR

* adding new hook for the MMISign changes

* updating lavamoat

* updating lavamoat

* removing a commeted line

* updating the sign check with accountType conditional

* fixing build issues

* updating the review comments on the hooks

* updating signatureRequestHeader

* lint fix

* fixing test failure

* lint fix

* updating review comments

* adding the renamed hook

* updating the origin url

* fixing test failure

* migrating changes from #19184

* updating snapshot

* fixing e2e failure

* fixing e2e failure

* addressing review comments from Joao

* migrating chnages from #19892

* moving shallowEqual inside of mmi build

* migrating changes from #19881

* fixing build failure

* migrating changes from #19949

* migrating changes from #19468

* updating snapshot

* updating snapshot

* updating QA review comments

* fixing full screen height issue from develop

* migrating changes from #20083

* fixing snapshot
This commit is contained in:
Niranjana Binoy 2023-07-20 13:51:38 -04:00 committed by GitHub
parent 6ce80fe997
commit 0c203d0518
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 781 additions and 1181 deletions

View File

@ -136,7 +136,7 @@ describe('Eth sign', function () {
await driver.waitForSelector({
text: 'Reject 2 requests',
tag: 'a',
tag: 'button',
});
await verifyAndAssertEthSign(driver, DAPP_URL, expectedEthSignMessage);

View File

@ -102,7 +102,7 @@ describe('Personal sign', function () {
await driver.waitForSelector({
text: 'Reject 2 requests',
tag: 'a',
tag: 'button',
});
const personalMessageRow = await driver.findElement(

View File

@ -160,7 +160,7 @@ describe('Sign Typed Data Signature Request', function () {
await driver.waitForSelector({
text: 'Reject 2 requests',
tag: 'a',
tag: 'button',
});
await verifyAndAssertSignTypedData(

View File

@ -14,7 +14,6 @@ import {
} from '../../../helpers/utils/util';
import { stripHexPrefix } from '../../../../shared/modules/hexstring-utils';
import { isSuspiciousResponse } from '../../../../shared/modules/security-provider.utils';
import Button from '../../ui/button';
import SiteOrigin from '../../ui/site-origin';
import Typography from '../../ui/typography/typography';
import { PageContainerFooter } from '../../ui/page-container';
@ -23,6 +22,7 @@ import {
FontWeight,
TextAlign,
TextColor,
Size,
///: BEGIN:ONLY_INCLUDE_IN(build-mmi)
IconColor,
DISPLAY,
@ -31,12 +31,20 @@ import {
BackgroundColor,
///: END:ONLY_INCLUDE_IN
} from '../../../helpers/constants/design-system';
import ConfirmPageContainerNavigation from '../confirm-page-container/confirm-page-container-navigation';
import SecurityProviderBannerMessage from '../security-provider-banner-message/security-provider-banner-message';
import {
ButtonLink,
///: BEGIN:ONLY_INCLUDE_IN(build-mmi)
Icon,
IconName,
Text,
///: END:ONLY_INCLUDE_IN
} from '../../component-library';
///: BEGIN:ONLY_INCLUDE_IN(build-mmi)
import { Icon, IconName, Text } from '../../component-library';
import Box from '../../ui/box/box';
///: END:ONLY_INCLUDE_IN
import ConfirmPageContainerNavigation from '../confirm-page-container/confirm-page-container-navigation';
import SecurityProviderBannerMessage from '../security-provider-banner-message/security-provider-banner-message';
import SignatureRequestHeader from '../signature-request-header';
import SignatureRequestOriginalWarning from './signature-request-original-warning';
@ -366,13 +374,13 @@ export default class SignatureRequestOriginal extends Component {
)}
{this.renderFooter()}
{messagesCount > 1 ? (
<Button
type="link"
<ButtonLink
size={Size.inherit}
className="request-signature__container__reject"
onClick={() => this.handleCancelAll()}
>
{rejectNText}
</Button>
</ButtonLink>
) : null}
</div>
);

View File

@ -50,7 +50,6 @@ export default function SignatureRequestSIWE({ txData }) {
const t = useContext(I18nContext);
const allAccounts = useSelector(accountsWithSendEtherInfoSelector);
const subjectMetadata = useSelector(getSubjectMetadata);
const messagesCount = useSelector(getTotalUnapprovedMessagesCount);
const messagesList = useSelector(unconfirmedMessagesHashSelector);
const mostRecentOverviewPage = useSelector(getMostRecentOverviewPage);

View File

@ -41,7 +41,7 @@ exports[`Signature Request Component render should match snapshot when we are us
of
1
0
</div>
<div
class="confirm-page-container-navigation__longtext"
@ -51,7 +51,7 @@ exports[`Signature Request Component render should match snapshot when we are us
</div>
<div
class="confirm-page-container-navigation__container"
style="visibility: initial;"
style="visibility: hidden;"
>
<button
class="confirm-page-container-navigation__arrow"
@ -92,7 +92,7 @@ exports[`Signature Request Component render should match snapshot when we are us
style="height: 32px; width: 32px; border-radius: 16px;"
>
<div
style="border-radius: 50px; overflow: hidden; padding: 0px; margin: 0px; width: 32px; height: 32px; display: inline-block; background: rgb(245, 228, 0);"
style="border-radius: 50px; overflow: hidden; padding: 0px; margin: 0px; width: 32px; height: 32px; display: inline-block; background: rgb(24, 151, 242);"
>
<svg
height="32"
@ -101,25 +101,25 @@ exports[`Signature Request Component render should match snapshot when we are us
y="0"
>
<rect
fill="#F2BE02"
fill="#FA7900"
height="32"
transform="translate(5.482725134273373 -5.1953603353074) rotate(387.1 16 16)"
transform="translate(-0.4291965340260923 -4.589212785086649) rotate(266.5 16 16)"
width="32"
x="0"
y="0"
/>
<rect
fill="#01778E"
fill="#F2A202"
height="32"
transform="translate(-2.6054559418209475 16.704606255523125) rotate(227.4 16 16)"
transform="translate(2.4067447047270143 13.767950912205709) rotate(242.5 16 16)"
width="32"
x="0"
y="0"
/>
<rect
fill="#C81435"
fill="#C8144A"
height="32"
transform="translate(17.969690840904306 -12.592649051897114) rotate(422.3 16 16)"
transform="translate(-19.808734531871874 -18.328897166120687) rotate(396.2 16 16)"
width="32"
x="0"
y="0"
@ -134,7 +134,7 @@ exports[`Signature Request Component render should match snapshot when we are us
<span
class="icon-with-fallback__fallback"
>
U
L
</span>
</div>
</div>
@ -144,12 +144,12 @@ exports[`Signature Request Component render should match snapshot when we are us
<h6
class="mm-box mm-text mm-text--body-sm mm-box--color-text-alternative"
>
Unknown private network
Localhost 8545
</h6>
<h6
class="mm-box mm-text mm-text--body-sm mm-text--font-weight-bold mm-box--color-text-default"
>
Antonio
John Doe
</h6>
</div>
</div>
@ -165,9 +165,9 @@ exports[`Signature Request Component render should match snapshot when we are us
align="end"
class="mm-box mm-text mm-text--body-sm mm-text--font-weight-bold mm-box--color-text-default"
>
966.987986
0
ABC
ETH
</h6>
</div>
</div>
@ -179,56 +179,48 @@ exports[`Signature Request Component render should match snapshot when we are us
class="signature-request__origin"
>
<div
class="site-origin"
class="box mm-tag-url box--padding-right-4 box--padding-left-2 box--display-flex box--gap-2 box--flex-direction-row box--align-items-center box--background-color-background-default box--rounded-pill box--border-color-border-default box--border-width-1 box--border-style-solid"
>
<div
class="chip chip--with-left-icon chip--border-color-border-muted chip--background-color-undefined"
class="mm-box mm-text mm-avatar-base mm-avatar-base--size-md mm-avatar-favicon mm-text--body-sm mm-text--text-transform-uppercase mm-box--display-flex mm-box--justify-content-center mm-box--align-items-center mm-box--color-text-default mm-box--background-color-background-alternative mm-box--rounded-full mm-box--border-color-transparent box--border-style-solid box--border-width-1"
>
<div
class="chip__left-icon"
>
<div
class=""
>
<span
class="icon-with-fallback__fallback"
>
T
</span>
</div>
</div>
<span
class="box box--margin-top-1 box--margin-bottom-1 box--flex-direction-row typography chip__label typography--h6 typography--weight-normal typography--style-normal typography--color-text-alternative"
>
test
</span>
class="box mm-icon mm-icon--size-md box--display-inline-block box--flex-direction-row box--color-icon-default"
style="mask-image: url('./images/icons/global.svg');"
/>
</div>
<p
class="mm-box mm-text mm-text--body-md mm-text--ellipsis mm-box--color-text-alternative"
>
test
</p>
</div>
</div>
<h3
class="mm-box mm-text signature-request__content__title mm-text--heading-md mm-box--margin-top-4 mm-box--color-text-default"
<h2
class="mm-box mm-text signature-request__content__title mm-text--heading-lg mm-box--margin-top-4 mm-box--color-text-default"
>
Signature request
</h3>
</h2>
<h6
align="center"
class="mm-box mm-text request-signature__content__subtitle mm-text--body-sm mm-box--margin-12 mm-box--margin-top-3 mm-box--color-text-alternative"
class="mm-box mm-text request-signature__content__subtitle mm-text--body-sm mm-text--text-align-center mm-box--margin-top-4 mm-box--margin-right-12 mm-box--margin-left-12 mm-box--color-text-alternative"
>
Only sign this message if you fully understand the content and trust the requesting site.
</h6>
<div>
<a
class="button btn-link signature-request-content__verify-contract-details"
<button
class="mm-box mm-text mm-button-base signature-request-content__verify-contract-details mm-button-link mm-button-link--size-auto mm-text--body-md-medium mm-box--padding-right-0 mm-box--padding-left-0 mm-box--display-inline-flex mm-box--justify-content-center mm-box--align-items-center mm-box--color-primary-default mm-box--background-color-transparent"
data-testid="verify-contract-details"
role="button"
tabindex="0"
>
<h6
class="mm-box mm-text mm-text--body-sm mm-box--color-primary-default"
<span
class="mm-box mm-text mm-text--inherit mm-box--color-primary-default"
>
Verify third-party details
</h6>
</a>
<h6
class="mm-box mm-text mm-text--body-sm mm-box--color-primary-default"
>
Verify third-party details
</h6>
</span>
</button>
</div>
</div>
<div
@ -773,6 +765,12 @@ exports[`Signature Request Component render should match snapshot when we are us
</button>
</footer>
</div>
<button
class="mm-box mm-text mm-button-base signature-request__reject-all-button mm-button-link mm-button-link--size-inherit mm-text--body-md-medium mm-box--padding-right-0 mm-box--padding-left-0 mm-box--display-inline-flex mm-box--justify-content-center mm-box--align-items-center mm-box--color-primary-default mm-box--background-color-transparent"
data-testid="signature-request-reject-all"
>
Reject 2 requests
</button>
</div>
</div>
`;
@ -818,7 +816,7 @@ exports[`Signature Request Component render should match snapshot when we want t
of
1
0
</div>
<div
class="confirm-page-container-navigation__longtext"
@ -828,7 +826,7 @@ exports[`Signature Request Component render should match snapshot when we want t
</div>
<div
class="confirm-page-container-navigation__container"
style="visibility: initial;"
style="visibility: hidden;"
>
<button
class="confirm-page-container-navigation__arrow"
@ -869,7 +867,7 @@ exports[`Signature Request Component render should match snapshot when we want t
style="height: 32px; width: 32px; border-radius: 16px;"
>
<div
style="border-radius: 50px; overflow: hidden; padding: 0px; margin: 0px; width: 32px; height: 32px; display: inline-block; background: rgb(245, 228, 0);"
style="border-radius: 50px; overflow: hidden; padding: 0px; margin: 0px; width: 32px; height: 32px; display: inline-block; background: rgb(24, 151, 242);"
>
<svg
height="32"
@ -878,25 +876,25 @@ exports[`Signature Request Component render should match snapshot when we want t
y="0"
>
<rect
fill="#F2BE02"
fill="#FA7900"
height="32"
transform="translate(5.482725134273373 -5.1953603353074) rotate(387.1 16 16)"
transform="translate(-0.4291965340260923 -4.589212785086649) rotate(266.5 16 16)"
width="32"
x="0"
y="0"
/>
<rect
fill="#01778E"
fill="#F2A202"
height="32"
transform="translate(-2.6054559418209475 16.704606255523125) rotate(227.4 16 16)"
transform="translate(2.4067447047270143 13.767950912205709) rotate(242.5 16 16)"
width="32"
x="0"
y="0"
/>
<rect
fill="#C81435"
fill="#C8144A"
height="32"
transform="translate(17.969690840904306 -12.592649051897114) rotate(422.3 16 16)"
transform="translate(-19.808734531871874 -18.328897166120687) rotate(396.2 16 16)"
width="32"
x="0"
y="0"
@ -911,7 +909,7 @@ exports[`Signature Request Component render should match snapshot when we want t
<span
class="icon-with-fallback__fallback"
>
U
L
</span>
</div>
</div>
@ -921,12 +919,12 @@ exports[`Signature Request Component render should match snapshot when we want t
<h6
class="mm-box mm-text mm-text--body-sm mm-box--color-text-alternative"
>
Unknown private network
Localhost 8545
</h6>
<h6
class="mm-box mm-text mm-text--body-sm mm-text--font-weight-bold mm-box--color-text-default"
>
Antonio
John Doe
</h6>
</div>
</div>
@ -942,9 +940,9 @@ exports[`Signature Request Component render should match snapshot when we want t
align="end"
class="mm-box mm-text mm-text--body-sm mm-text--font-weight-bold mm-box--color-text-default"
>
1515270.174798
0
DEF
ETH
</h6>
</div>
</div>
@ -956,56 +954,48 @@ exports[`Signature Request Component render should match snapshot when we want t
class="signature-request__origin"
>
<div
class="site-origin"
class="box mm-tag-url box--padding-right-4 box--padding-left-2 box--display-flex box--gap-2 box--flex-direction-row box--align-items-center box--background-color-background-default box--rounded-pill box--border-color-border-default box--border-width-1 box--border-style-solid"
>
<div
class="chip chip--with-left-icon chip--border-color-border-muted chip--background-color-undefined"
class="mm-box mm-text mm-avatar-base mm-avatar-base--size-md mm-avatar-favicon mm-text--body-sm mm-text--text-transform-uppercase mm-box--display-flex mm-box--justify-content-center mm-box--align-items-center mm-box--color-text-default mm-box--background-color-background-alternative mm-box--rounded-full mm-box--border-color-transparent box--border-style-solid box--border-width-1"
>
<div
class="chip__left-icon"
>
<div
class=""
>
<span
class="icon-with-fallback__fallback"
>
T
</span>
</div>
</div>
<span
class="box box--margin-top-1 box--margin-bottom-1 box--flex-direction-row typography chip__label typography--h6 typography--weight-normal typography--style-normal typography--color-text-alternative"
>
test
</span>
class="box mm-icon mm-icon--size-md box--display-inline-block box--flex-direction-row box--color-icon-default"
style="mask-image: url('./images/icons/global.svg');"
/>
</div>
<p
class="mm-box mm-text mm-text--body-md mm-text--ellipsis mm-box--color-text-alternative"
>
test
</p>
</div>
</div>
<h3
class="mm-box mm-text signature-request__content__title mm-text--heading-md mm-box--margin-top-4 mm-box--color-text-default"
<h2
class="mm-box mm-text signature-request__content__title mm-text--heading-lg mm-box--margin-top-4 mm-box--color-text-default"
>
Signature request
</h3>
</h2>
<h6
align="center"
class="mm-box mm-text request-signature__content__subtitle mm-text--body-sm mm-box--margin-12 mm-box--margin-top-3 mm-box--color-text-alternative"
class="mm-box mm-text request-signature__content__subtitle mm-text--body-sm mm-text--text-align-center mm-box--margin-top-4 mm-box--margin-right-12 mm-box--margin-left-12 mm-box--color-text-alternative"
>
Only sign this message if you fully understand the content and trust the requesting site.
</h6>
<div>
<a
class="button btn-link signature-request-content__verify-contract-details"
<button
class="mm-box mm-text mm-button-base signature-request-content__verify-contract-details mm-button-link mm-button-link--size-auto mm-text--body-md-medium mm-box--padding-right-0 mm-box--padding-left-0 mm-box--display-inline-flex mm-box--justify-content-center mm-box--align-items-center mm-box--color-primary-default mm-box--background-color-transparent"
data-testid="verify-contract-details"
role="button"
tabindex="0"
>
<h6
class="mm-box mm-text mm-text--body-sm mm-box--color-primary-default"
<span
class="mm-box mm-text mm-text--inherit mm-box--color-primary-default"
>
Verify third-party details
</h6>
</a>
<h6
class="mm-box mm-text mm-text--body-sm mm-box--color-primary-default"
>
Verify third-party details
</h6>
</span>
</button>
</div>
</div>
<div
@ -1550,6 +1540,12 @@ exports[`Signature Request Component render should match snapshot when we want t
</button>
</footer>
</div>
<button
class="mm-box mm-text mm-button-base signature-request__reject-all-button mm-button-link mm-button-link--size-inherit mm-text--body-md-medium mm-box--padding-right-0 mm-box--padding-left-0 mm-box--display-inline-flex mm-box--justify-content-center mm-box--align-items-center mm-box--color-primary-default mm-box--background-color-transparent"
data-testid="signature-request-reject-all"
>
Reject 2 requests
</button>
</div>
</div>
`;

View File

@ -1 +1 @@
export { default } from './signature-request.container';
export { default } from './signature-request';

View File

@ -14,7 +14,7 @@
border-radius: 8px;
@include screen-sm-min {
max-height: 80vh;
max-height: max-content;
min-height: 570px;
flex: 0 0 auto;
margin-left: auto;
@ -22,7 +22,7 @@
}
&__reject-all-button {
margin-top: -15px;
padding-bottom: 20px;
}
&__origin {
@ -58,12 +58,6 @@
font-weight: 500;
}
p {
@include H6;
color: var(--color-text-muted);
}
}
a.signature-request-content__verify-contract-details {

View File

@ -1,427 +0,0 @@
import React, { PureComponent } from 'react';
import { memoize } from 'lodash';
import PropTypes from 'prop-types';
import { ethErrors, serializeError } from 'eth-rpc-errors';
import LedgerInstructionField from '../ledger-instruction-field';
import {
sanitizeMessage,
getURLHostName,
getNetworkNameFromProviderType,
///: BEGIN:ONLY_INCLUDE_IN(build-mmi)
shortenAddress,
///: END:ONLY_INCLUDE_IN
} from '../../../helpers/utils/util';
import {
MetaMetricsEventCategory,
///: BEGIN:ONLY_INCLUDE_IN(build-mmi)
MetaMetricsEventName,
///: END:ONLY_INCLUDE_IN
} from '../../../../shared/constants/metametrics';
import SiteOrigin from '../../ui/site-origin';
import Button from '../../ui/button';
import ContractDetailsModal from '../modals/contract-details-modal/contract-details-modal';
import {
TextAlign,
TextColor,
TextVariant,
///: BEGIN:ONLY_INCLUDE_IN(build-mmi)
IconColor,
Display,
BlockSize,
BackgroundColor,
///: END:ONLY_INCLUDE_IN
} from '../../../helpers/constants/design-system';
import NetworkAccountBalanceHeader from '../network-account-balance-header';
import { Numeric } from '../../../../shared/modules/Numeric';
import { isSuspiciousResponse } from '../../../../shared/modules/security-provider.utils';
import { EtherDenomination } from '../../../../shared/constants/common';
import ConfirmPageContainerNavigation from '../confirm-page-container/confirm-page-container-navigation';
import SecurityProviderBannerMessage from '../security-provider-banner-message/security-provider-banner-message';
import { formatCurrency } from '../../../helpers/utils/confirm-tx.util';
import { getValueFromWeiHex } from '../../../../shared/modules/conversion.utils';
import {
///: BEGIN:ONLY_INCLUDE_IN(build-mmi)
Box,
Icon,
IconName,
///: END:ONLY_INCLUDE_IN
Text,
} from '../../component-library';
import Footer from './signature-request-footer';
import Message from './signature-request-message';
export default class SignatureRequest extends PureComponent {
static propTypes = {
/**
* The display content of transaction data
*/
txData: PropTypes.object.isRequired,
/**
* The display content of sender account
*/
fromAccount: PropTypes.shape({
address: PropTypes.string.isRequired,
balance: PropTypes.string,
name: PropTypes.string,
}).isRequired,
/**
* Check if the wallet is ledget wallet or not
*/
isLedgerWallet: PropTypes.bool,
/**
* 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,
nativeCurrency: PropTypes.string,
currentCurrency: PropTypes.string.isRequired,
conversionRate: PropTypes.number,
providerConfig: PropTypes.object,
subjectMetadata: PropTypes.object,
unapprovedMessagesCount: PropTypes.number,
clearConfirmTransaction: PropTypes.func.isRequired,
history: PropTypes.object,
mostRecentOverviewPage: PropTypes.string,
showRejectTransactionsConfirmationModal: PropTypes.func.isRequired,
cancelAllApprovals: PropTypes.func.isRequired,
resolvePendingApproval: PropTypes.func.isRequired,
rejectPendingApproval: PropTypes.func.isRequired,
completedTx: PropTypes.func.isRequired,
///: BEGIN:ONLY_INCLUDE_IN(build-mmi)
showCustodianDeepLink: PropTypes.func,
isNotification: PropTypes.bool,
mmiOnSignCallback: PropTypes.func,
// Used to show a warning if the signing account is not the selected account
// Largely relevant for contract wallet custodians
selectedAccount: PropTypes.object,
///: END:ONLY_INCLUDE_IN
};
static contextTypes = {
t: PropTypes.func,
trackEvent: PropTypes.func,
};
state = {
hasScrolledMessage: false,
showContractDetails: false,
};
///: BEGIN:ONLY_INCLUDE_IN(build-mmi)
componentDidMount() {
if (this.props.txData.custodyId) {
this.props.showCustodianDeepLink({
custodyId: this.props.txData.custodyId,
fromAddress: this.props.fromAccount.address,
closeNotification: this.props.isNotification,
onDeepLinkFetched: () => undefined,
onDeepLinkShown: () => {
this.context.trackEvent({
category: MetaMetricsEventCategory.MMI,
event: MetaMetricsEventName.SignatureDeeplinkDisplayed,
});
},
});
}
}
///: END:ONLY_INCLUDE_IN
setMessageRootRef(ref) {
this.messageRootRef = ref;
}
formatWallet(wallet) {
return `${wallet.slice(0, 8)}...${wallet.slice(
wallet.length - 8,
wallet.length,
)}`;
}
memoizedParseMessage = memoize((data) => {
const { message, domain = {}, primaryType, types } = JSON.parse(data);
const sanitizedMessage = sanitizeMessage(message, primaryType, types);
return { sanitizedMessage, domain, primaryType };
});
handleCancelAll = () => {
const {
clearConfirmTransaction,
history,
mostRecentOverviewPage,
showRejectTransactionsConfirmationModal,
unapprovedMessagesCount,
cancelAllApprovals,
} = this.props;
showRejectTransactionsConfirmationModal({
unapprovedTxCount: unapprovedMessagesCount,
onSubmit: async () => {
await cancelAllApprovals();
clearConfirmTransaction();
history.push(mostRecentOverviewPage);
},
});
};
render() {
const {
providerConfig,
txData: {
msgParams: { data, origin, version },
type,
id,
},
fromAccount: { address, balance, name },
isLedgerWallet,
hardwareWalletRequiresConnection,
chainId,
rpcPrefs,
txData,
subjectMetadata,
nativeCurrency,
currentCurrency,
conversionRate,
unapprovedMessagesCount,
resolvePendingApproval,
rejectPendingApproval,
completedTx,
} = this.props;
const { t, trackEvent } = this.context;
const {
sanitizedMessage,
domain: { verifyingContract },
primaryType,
} = this.memoizedParseMessage(data);
const rejectNText = t('rejectRequestsN', [unapprovedMessagesCount]);
const networkName = getNetworkNameFromProviderType(providerConfig.type);
const currentNetwork =
networkName === ''
? providerConfig.nickname || t('unknownNetwork')
: t(networkName);
const balanceInBaseAsset = conversionRate
? formatCurrency(
getValueFromWeiHex({
value: balance,
fromCurrency: nativeCurrency,
toCurrency: currentCurrency,
conversionRate,
numberOfDecimals: 6,
toDenomination: EtherDenomination.ETH,
}),
currentCurrency,
)
: new Numeric(balance, 16, EtherDenomination.WEI)
.toDenomination(EtherDenomination.ETH)
.round(6)
.toBase(10)
.toString();
const onSign = async () => {
///: BEGIN:ONLY_INCLUDE_IN(build-mmi)
if (this.props.mmiOnSignCallback) {
await this.props.mmiOnSignCallback(txData);
return;
}
///: END:ONLY_INCLUDE_IN
await resolvePendingApproval(id);
completedTx(id);
trackEvent({
category: MetaMetricsEventCategory.Transactions,
event: 'Confirm',
properties: {
action: 'Sign Request',
legacy_event: true,
type,
version,
},
});
};
const onCancel = async () => {
await rejectPendingApproval(
id,
serializeError(ethErrors.provider.userRejectedRequest()),
);
trackEvent({
category: MetaMetricsEventCategory.Transactions,
event: 'Cancel',
properties: {
action: 'Sign Request',
legacy_event: true,
type,
version,
},
});
};
const messageIsScrollable =
this.messageRootRef?.scrollHeight > this.messageRootRef?.clientHeight;
const targetSubjectMetadata = txData.msgParams.origin
? subjectMetadata?.[txData.msgParams.origin]
: null;
return (
<div className="signature-request">
<ConfirmPageContainerNavigation />
<div
className="request-signature__account"
data-testid="request-signature-account"
>
<NetworkAccountBalanceHeader
networkName={currentNetwork}
accountName={name}
accountBalance={balanceInBaseAsset}
tokenName={
conversionRate ? currentCurrency?.toUpperCase() : nativeCurrency
}
accountAddress={address}
/>
</div>
<div className="signature-request-content">
{isSuspiciousResponse(txData?.securityProviderResponse) && (
<SecurityProviderBannerMessage
securityProviderResponse={txData.securityProviderResponse}
/>
)}
{
///: BEGIN:ONLY_INCLUDE_IN(build-mmi)
this.props.selectedAccount.address === address ? null : (
<Box
className="request-signature__mismatch-info"
display={Display.Flex}
width={BlockSize.Full}
padding={4}
marginBottom={4}
backgroundColor={BackgroundColor.primaryMuted}
>
<Icon
name={IconName.Info}
color={IconColor.infoDefault}
marginRight={2}
/>
<Text
variant={TextVariant.bodyXs}
color={TextColor.textDefault}
>
{this.context.t('mismatchAccount', [
shortenAddress(this.props.selectedAccount.address),
shortenAddress(address),
])}
</Text>
</Box>
)
///: END:ONLY_INCLUDE_IN
}
<div className="signature-request__origin">
<SiteOrigin
siteOrigin={origin}
iconSrc={targetSubjectMetadata?.iconUrl}
iconName={getURLHostName(origin) || origin}
chip
/>
</div>
<Text
className="signature-request__content__title"
variant={TextVariant.headingMd}
as="h3"
marginTop={4}
>
{this.context.t('sigRequest')}
</Text>
<Text
className="request-signature__content__subtitle"
variant={TextVariant.bodySm}
as="h6"
color={TextColor.textAlternative}
align={TextAlign.Center}
margin={12}
marginTop={3}
>
{this.context.t('signatureRequestGuidance')}
</Text>
{verifyingContract ? (
<div>
<Button
type="link"
onClick={() => this.setState({ showContractDetails: true })}
className="signature-request-content__verify-contract-details"
data-testid="verify-contract-details"
>
<Text
variant={TextVariant.bodySm}
as="h6"
color={TextColor.primaryDefault}
>
{this.context.t('verifyContractDetails')}
</Text>
</Button>
</div>
) : null}
</div>
{isLedgerWallet ? (
<div className="confirm-approve-content__ledger-instruction-wrapper">
<LedgerInstructionField showDataInstruction />
</div>
) : null}
<Message
data={sanitizedMessage}
onMessageScrolled={() => this.setState({ hasScrolledMessage: true })}
setMessageRootRef={this.setMessageRootRef.bind(this)}
messageRootRef={this.messageRootRef}
messageIsScrollable={messageIsScrollable}
primaryType={primaryType}
/>
<Footer
cancelAction={onCancel}
signAction={onSign}
disabled={
///: BEGIN:ONLY_INCLUDE_IN(build-mmi)
Boolean(this.props.txData?.custodyId) ||
///: END:ONLY_INCLUDE_IN
hardwareWalletRequiresConnection ||
(messageIsScrollable && !this.state.hasScrolledMessage)
}
/>
{this.state.showContractDetails && (
<ContractDetailsModal
toAddress={verifyingContract}
chainId={chainId}
rpcPrefs={rpcPrefs}
onClose={() => this.setState({ showContractDetails: false })}
isContractRequestingSignature
/>
)}
{unapprovedMessagesCount > 1 ? (
<Button
type="link"
className="signature-request__reject-all-button"
data-testid="signature-request-reject-all"
onClick={(e) => {
e.preventDefault();
this.handleCancelAll();
}}
>
{rejectNText}
</Button>
) : null}
</div>
);
}
}

View File

@ -1,3 +0,0 @@
import { ENVIRONMENT_TYPE_NOTIFICATION } from '../../../../shared/constants/app';
export { ENVIRONMENT_TYPE_NOTIFICATION };

View File

@ -1,298 +0,0 @@
import { connect } from 'react-redux';
import {
accountsWithSendEtherInfoSelector,
doesAddressRequireLedgerHidConnection,
getCurrentChainId,
getRpcPrefsForCurrentProvider,
getSubjectMetadata,
unconfirmedMessagesHashSelector,
getTotalUnapprovedMessagesCount,
getCurrentCurrency,
getPreferences,
conversionRateSelector,
///: BEGIN:ONLY_INCLUDE_IN(build-mmi)
getAccountType,
getSelectedAccount,
unapprovedTypedMessagesSelector,
///: END:ONLY_INCLUDE_IN
} from '../../../selectors';
import {
isAddressLedger,
getNativeCurrency,
getProviderConfig,
} from '../../../ducks/metamask/metamask';
import { getAccountByAddress, valuesFor } from '../../../helpers/utils/util';
///: BEGIN:ONLY_INCLUDE_IN(build-mmi)
// eslint-disable-next-line import/order
import { showCustodianDeepLink } from '@metamask-institutional/extension';
import {
mmiActionsFactory,
setTypedMessageInProgress,
} from '../../../store/institutional/institution-background';
import { getEnvironmentType } from '../../../../app/scripts/lib/util';
import {
showCustodyConfirmLink,
checkForUnapprovedMessages,
} from '../../../store/institutional/institution-actions';
///: END:ONLY_INCLUDE_IN
import {
///: BEGIN:ONLY_INCLUDE_IN(build-mmi)
ENVIRONMENT_TYPE_NOTIFICATION,
///: END:ONLY_INCLUDE_IN
} from '../../../../shared/constants/app';
import {
showModal,
resolvePendingApproval,
rejectPendingApproval,
rejectAllMessages,
completedTx,
///: BEGIN:ONLY_INCLUDE_IN(build-mmi)
goHome,
///: END:ONLY_INCLUDE_IN
} from '../../../store/actions';
import { getMostRecentOverviewPage } from '../../../ducks/history/history';
import { clearConfirmTransaction } from '../../../ducks/confirm-transaction/confirm-transaction.duck';
import SignatureRequest from './signature-request.component';
function mapStateToProps(state, ownProps) {
const { txData } = ownProps;
///: BEGIN:ONLY_INCLUDE_IN(build-mmi)
const envType = getEnvironmentType();
///: END:ONLY_INCLUDE_IN
const {
msgParams: { from },
} = txData;
const providerConfig = getProviderConfig(state);
const hardwareWalletRequiresConnection =
doesAddressRequireLedgerHidConnection(state, from);
const isLedgerWallet = isAddressLedger(state, from);
const chainId = getCurrentChainId(state);
const rpcPrefs = getRpcPrefsForCurrentProvider(state);
const unconfirmedMessagesList = unconfirmedMessagesHashSelector(state);
const unapprovedMessagesCount = getTotalUnapprovedMessagesCount(state);
const { useNativeCurrencyAsPrimaryCurrency } = getPreferences(state);
return {
providerConfig,
isLedgerWallet,
hardwareWalletRequiresConnection,
chainId,
rpcPrefs,
unconfirmedMessagesList,
unapprovedMessagesCount,
mostRecentOverviewPage: getMostRecentOverviewPage(state),
nativeCurrency: getNativeCurrency(state),
currentCurrency: getCurrentCurrency(state),
conversionRate: useNativeCurrencyAsPrimaryCurrency
? null
: conversionRateSelector(state),
subjectMetadata: getSubjectMetadata(state),
// not forwarded to component
allAccounts: accountsWithSendEtherInfoSelector(state),
///: BEGIN:ONLY_INCLUDE_IN(build-mmi)
accountType: getAccountType(state),
isNotification: envType === ENVIRONMENT_TYPE_NOTIFICATION,
selectedAccount: getSelectedAccount(state),
unapprovedTypedMessages: unapprovedTypedMessagesSelector(state),
///: END:ONLY_INCLUDE_IN
};
}
let mapDispatchToProps = null;
mapDispatchToProps = function (dispatch) {
return {
resolvePendingApproval: (id) => dispatch(resolvePendingApproval(id)),
completedTx: (id) => dispatch(completedTx(id)),
rejectPendingApproval: (id, error) =>
dispatch(rejectPendingApproval(id, error)),
clearConfirmTransaction: () => dispatch(clearConfirmTransaction()),
showRejectTransactionsConfirmationModal: ({
onSubmit,
unapprovedTxCount: unapprovedMessagesCount,
}) => {
return dispatch(
showModal({
name: 'REJECT_TRANSACTIONS',
onSubmit,
unapprovedTxCount: unapprovedMessagesCount,
isRequestType: true,
}),
);
},
cancelAllApprovals: (unconfirmedMessagesList) => {
dispatch(rejectAllMessages(unconfirmedMessagesList));
},
};
};
///: BEGIN:ONLY_INCLUDE_IN(build-mmi)
function mmiMapDispatchToProps(dispatch) {
const mmiActions = mmiActionsFactory();
return {
setMsgInProgress: (msgId) => dispatch(setTypedMessageInProgress(msgId)),
showCustodianDeepLink: ({
custodyId,
fromAddress,
closeNotification,
onDeepLinkFetched,
onDeepLinkShown,
}) =>
showCustodianDeepLink({
dispatch,
mmiActions,
txId: undefined,
fromAddress,
custodyId,
isSignature: true,
closeNotification,
onDeepLinkFetched,
onDeepLinkShown,
showCustodyConfirmLink,
}),
showTransactionsFailedModal: ({
errorMessage,
closeNotification,
operationFailed,
}) =>
dispatch(
showModal({
name: 'TRANSACTION_FAILED',
errorMessage,
closeNotification,
operationFailed,
}),
),
setWaitForConfirmDeepLinkDialog: (wait) =>
dispatch(mmiActions.setWaitForConfirmDeepLinkDialog(wait)),
goHome: () => dispatch(goHome()),
resolvePendingApproval: (id) => dispatch(resolvePendingApproval(id)),
completedTx: (id) => dispatch(completedTx(id)),
rejectPendingApproval: (id, error) =>
dispatch(rejectPendingApproval(id, error)),
clearConfirmTransaction: () => dispatch(clearConfirmTransaction()),
showRejectTransactionsConfirmationModal: ({
onSubmit,
unapprovedTxCount: unapprovedMessagesCount,
}) => {
return dispatch(
showModal({
name: 'REJECT_TRANSACTIONS',
onSubmit,
unapprovedTxCount: unapprovedMessagesCount,
isRequestType: true,
}),
);
},
cancelAllApprovals: (unconfirmedMessagesList) => {
dispatch(rejectAllMessages(unconfirmedMessagesList));
},
};
}
mapDispatchToProps = mmiMapDispatchToProps;
///: END:ONLY_INCLUDE_IN
function mergeProps(stateProps, dispatchProps, ownProps) {
const {
allAccounts,
isLedgerWallet,
hardwareWalletRequiresConnection,
chainId,
rpcPrefs,
nativeCurrency,
currentCurrency,
conversionRate,
providerConfig,
subjectMetadata,
unconfirmedMessagesList,
unapprovedMessagesCount,
mostRecentOverviewPage,
///: BEGIN:ONLY_INCLUDE_IN(build-mmi)
accountType,
isNotification,
unapprovedTypedMessages,
///: END:ONLY_INCLUDE_IN
} = stateProps;
const { txData } = ownProps;
const {
cancelAll: dispatchCancelAll,
cancelAllApprovals: dispatchCancelAllApprovals,
} = dispatchProps;
const {
msgParams: { from },
} = txData;
const fromAccount = getAccountByAddress(allAccounts, from);
///: BEGIN:ONLY_INCLUDE_IN(build-mmi)
const mmiOnSignCallback = async (_msgData) => {
if (accountType === 'custody') {
try {
let msgData = _msgData;
let id = _msgData.custodyId;
if (!_msgData.custodyId) {
msgData = checkForUnapprovedMessages(
_msgData,
unapprovedTypedMessages,
);
id = msgData.custodyId;
}
dispatchProps.showCustodianDeepLink({
custodyId: id,
fromAddress: fromAccount.address,
closeNotification: isNotification,
onDeepLinkFetched: () => undefined,
onDeepLinkShown: () => undefined,
});
await dispatchProps.setMsgInProgress(msgData.metamaskId);
await dispatchProps.setWaitForConfirmDeepLinkDialog(true);
await goHome();
} catch (err) {
await dispatchProps.setWaitForConfirmDeepLinkDialog(true);
await dispatchProps.showTransactionsFailedModal({
errorMessage: err.message,
closeNotification: true,
operationFailed: true,
});
}
}
};
///: END:ONLY_INCLUDE_IN
return {
...ownProps,
...dispatchProps,
fromAccount,
txData,
isLedgerWallet,
hardwareWalletRequiresConnection,
chainId,
rpcPrefs,
nativeCurrency,
currentCurrency,
conversionRate,
providerConfig,
subjectMetadata,
unapprovedMessagesCount,
mostRecentOverviewPage,
cancelAll: () => dispatchCancelAll(valuesFor(unconfirmedMessagesList)),
cancelAllApprovals: () =>
dispatchCancelAllApprovals(valuesFor(unconfirmedMessagesList)),
///: BEGIN:ONLY_INCLUDE_IN(build-mmi)
mmiOnSignCallback,
///: END:ONLY_INCLUDE_IN
};
}
export default connect(
mapStateToProps,
mapDispatchToProps,
mergeProps,
)(SignatureRequest);

View File

@ -1,237 +0,0 @@
import React from 'react';
import sinon from 'sinon';
import { fireEvent, screen, act } from '@testing-library/react';
import configureMockStore from 'redux-mock-store';
import { renderWithProvider } from '../../../../test/lib/render-helpers';
import SignatureRequest from './signature-request.container';
const mockStoreWithEth = {
metamask: {
tokenList: {
'0x514910771af9ca656af840dff83e8264ecf986ca': {
address: '0x514910771af9ca656af840dff83e8264ecf986ca',
symbol: 'LINK',
decimals: 18,
name: 'ChainLink Token',
iconUrl: 'https://crypto.com/price/coin-data/icon/LINK/color_icon.png',
aggregators: [
'Aave',
'Bancor',
'CMC',
'Crypto.com',
'CoinGecko',
'1inch',
'Paraswap',
'PMM',
'Zapper',
'Zerion',
'0x',
],
occurrences: 12,
unlisted: false,
},
},
identities: {
'0xb19ac54efa18cc3a14a5b821bfec73d284bf0c5e': {
name: 'Account 2',
address: '0xb19ac54efa18cc3a14a5b821bfec73d284bf0c5e',
},
},
addressBook: {
undefined: {
0: {
address: '0x39a4e4Af7cCB654dB9500F258c64781c8FbD39F0',
name: '',
isEns: false,
},
},
},
providerConfig: {
type: 'rpc',
},
preferences: {
useNativeCurrencyAsPrimaryCurrency: true,
},
accounts: {
'0xd8f6a2ffb0fc5952d16c9768b71cfd35b6399aa5': {
address: '0xd8f6a2ffb0fc5952d16c9768b71cfd35b6399aa5',
balance: '0x03',
},
},
cachedBalances: {},
unapprovedDecryptMsgs: {},
unapprovedEncryptionPublicKeyMsgs: {},
unconfirmedTransactions: {},
selectedAddress: '0xd8f6a2ffb0fc5952d16c9768b71cfd35b6399aa5',
nativeCurrency: 'ETH',
currentCurrency: 'usd',
conversionRate: 231.06,
},
};
const mockStoreWithFiat = {
...mockStoreWithEth,
preferences: {
useNativeCurrencyAsPrimaryCurrency: false,
},
};
describe('Signature Request', () => {
const propsWithEth = {
fromAccount: {
address: '0xd8f6a2ffb0fc5952d16c9768b71cfd35b6399aa5',
balance: '0x346ba7725f412cbfdb',
name: 'John Doe',
},
history: {
push: sinon.spy(),
},
hardwareWalletRequiresConnection: false,
mostRecentOverviewPage: '/',
clearConfirmTransaction: sinon.spy(),
cancelMessage: sinon.spy(),
cancel: sinon.stub().resolves(),
rejectPendingApproval: sinon.stub().resolves(),
showRejectTransactionsConfirmationModal: sinon.stub().resolves(),
cancelAll: sinon.stub().resolves(),
providerConfig: {
type: 'rpc',
},
unapprovedMessagesCount: 2,
sign: sinon.stub().resolves(),
cancelAllApprovals: sinon.stub().resolves(),
resolvePendingApproval: sinon.stub().resolves(),
completedTx: sinon.stub().resolves(),
txData: {
msgParams: {
id: 1,
data: '{"types":{"EIP712Domain":[{"name":"name","type":"string"},{"name":"version","type":"string"},{"name":"chainId","type":"uint256"},{"name":"verifyingContract","type":"address"}],"Person":[{"name":"name","type":"string"},{"name":"wallet","type":"address"}],"Mail":[{"name":"from","type":"Person"},{"name":"to","type":"Person"},{"name":"contents","type":"string"}]},"primaryType":"Mail","domain":{"name":"Ether Mail","version":"1","chainId":"4","verifyingContract":"0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC"},"message":{"from":{"name":"Cow","wallet":"0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826"},"to":{"name":"Bob","wallet":"0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB"},"contents":"Hello, Bob!"}}',
from: '0xd8f6a2ffb0fc5952d16c9768b71cfd35b6399aa5',
origin: 'test.domain',
},
status: 'unapproved',
time: 1,
type: 'eth_sign',
},
nativeCurrency: 'ETH',
currentCurrency: 'usd',
conversionRate: null,
selectedAccount: {
address: '0x123456789abcdef',
},
};
const propsWithFiat = {
...propsWithEth,
conversionRate: 156.72,
};
describe('Render with different currencies', () => {
it('should render balance with ETH when conversionRate is not provided', () => {
const store = configureMockStore()(mockStoreWithEth);
renderWithProvider(
<SignatureRequest.WrappedComponent {...propsWithEth} />,
store,
);
expect(
screen.getByTestId('request-signature-account').textContent,
).toMatchInlineSnapshot(
`"UUnknown private networkJohn DoeBalance966.987986 ETH"`,
);
});
it('should render balance with fiat when conversionRate not provided', () => {
const store = configureMockStore()(mockStoreWithFiat);
renderWithProvider(
<SignatureRequest.WrappedComponent {...propsWithFiat} />,
store,
);
expect(
screen.getByTestId('request-signature-account').textContent,
).toMatchInlineSnapshot(
`"UUnknown private networkJohn DoeBalance$151,546.36 USD"`,
);
});
});
describe('functionality check', () => {
beforeEach(() => {
const store = configureMockStore()(mockStoreWithFiat);
renderWithProvider(
<SignatureRequest.WrappedComponent {...propsWithFiat} />,
store,
);
});
afterEach(() => {
propsWithFiat.clearConfirmTransaction.resetHistory();
});
it('cancel', async () => {
const cancelButton = screen.getByTestId('page-container-footer-cancel');
await act(() => {
fireEvent.click(cancelButton);
});
expect(propsWithFiat.rejectPendingApproval.calledOnce).toStrictEqual(
true,
);
});
it('sign', async () => {
const signButton = screen.getByTestId('page-container-footer-next');
await act(() => {
fireEvent.click(signButton);
});
expect(propsWithFiat.resolvePendingApproval.calledOnce).toStrictEqual(
true,
);
});
it('have user warning', () => {
const warningText = screen.getByText(
'Only sign this message if you fully understand the content and trust the requesting site.',
);
expect(warningText).toBeInTheDocument();
});
});
describe('contract details', () => {
let store;
beforeEach(() => {
store = configureMockStore()(mockStoreWithFiat);
});
it('shows verify contract details link when verifyingContract is set', () => {
renderWithProvider(
<SignatureRequest.WrappedComponent {...propsWithFiat} />,
store,
);
const verifyingContractLink = screen.getByTestId(
'verify-contract-details',
);
expect(verifyingContractLink).toBeInTheDocument();
});
it('should not show verify contract details link when verifyingContract is not set', () => {
const newData = JSON.parse(propsWithFiat.txData.msgParams.data);
delete newData.domain.verifyingContract;
const newProps = {
...propsWithFiat,
txData: {
...propsWithFiat.txData,
msgParams: {
...propsWithFiat.txData.msgParams,
data: JSON.stringify(newData),
},
},
};
renderWithProvider(
<SignatureRequest.WrappedComponent {...newProps} />,
store,
);
expect(screen.queryByTestId('verify-contract-details')).toBeNull();
});
});
});

View File

@ -0,0 +1,380 @@
import React, { useContext, useState, useEffect } from 'react';
import {
useDispatch,
useSelector,
///: BEGIN:ONLY_INCLUDE_IN(build-mmi)
shallowEqual,
///: END:ONLY_INCLUDE_IN
} from 'react-redux';
import PropTypes from 'prop-types';
import { memoize } from 'lodash';
import { ethErrors, serializeError } from 'eth-rpc-errors';
///: BEGIN:ONLY_INCLUDE_IN(build-mmi)
import { showCustodianDeepLink } from '@metamask-institutional/extension';
///: END:ONLY_INCLUDE_IN
import {
resolvePendingApproval,
rejectPendingApproval,
completedTx,
} from '../../../store/actions';
import {
doesAddressRequireLedgerHidConnection,
getSubjectMetadata,
getTotalUnapprovedMessagesCount,
///: BEGIN:ONLY_INCLUDE_IN(build-mmi)
accountsWithSendEtherInfoSelector,
getSelectedAccount,
getAccountType,
///: END:ONLY_INCLUDE_IN
} from '../../../selectors';
import {
getProviderConfig,
isAddressLedger,
} from '../../../ducks/metamask/metamask';
import {
sanitizeMessage,
///: BEGIN:ONLY_INCLUDE_IN(build-mmi)
getAccountByAddress,
shortenAddress,
///: END:ONLY_INCLUDE_IN
} from '../../../helpers/utils/util';
import { useI18nContext } from '../../../hooks/useI18nContext';
import { useRejectTransactionModal } from '../../../hooks/useRejectTransactionModal';
import { ConfirmPageContainerNavigation } from '../confirm-page-container';
import SignatureRequestHeader from '../signature-request-header/signature-request-header';
import SecurityProviderBannerMessage from '../security-provider-banner-message';
import LedgerInstructionField from '../ledger-instruction-field';
import ContractDetailsModal from '../modals/contract-details-modal';
import { MetaMetricsContext } from '../../../contexts/metametrics';
import {
MetaMetricsEventCategory,
///: BEGIN:ONLY_INCLUDE_IN(build-mmi)
MetaMetricsEventName,
///: END:ONLY_INCLUDE_IN
} from '../../../../shared/constants/metametrics';
import { SECURITY_PROVIDER_MESSAGE_SEVERITY } from '../../../../shared/constants/security-provider';
import {
TextAlign,
TextColor,
TextVariant,
Size,
///: BEGIN:ONLY_INCLUDE_IN(build-mmi)
IconColor,
BackgroundColor,
Display,
BlockSize,
///: END:ONLY_INCLUDE_IN
} from '../../../helpers/constants/design-system';
import {
BUTTON_VARIANT,
Button,
ButtonLink,
TagUrl,
Text,
///: BEGIN:ONLY_INCLUDE_IN(build-mmi)
Icon,
IconName,
///: END:ONLY_INCLUDE_IN
} from '../../component-library';
///: BEGIN:ONLY_INCLUDE_IN(build-mmi)
// eslint-disable-next-line import/order
import Box from '../../ui/box/box';
import { ENVIRONMENT_TYPE_NOTIFICATION } from '../../../../shared/constants/app';
import { getEnvironmentType } from '../../../../app/scripts/lib/util';
import { mmiActionsFactory } from '../../../store/institutional/institution-background';
import { showCustodyConfirmLink } from '../../../store/institutional/institution-actions';
import { useMMICustodySignMessage } from '../../../hooks/useMMICustodySignMessage';
///: END:ONLY_INCLUDE_IN
import Message from './signature-request-message';
import Footer from './signature-request-footer';
const SignatureRequest = ({ txData }) => {
const trackEvent = useContext(MetaMetricsContext);
const dispatch = useDispatch();
const t = useI18nContext();
const [hasScrolledMessage, setHasScrolledMessage] = useState(false);
const [showContractDetails, setShowContractDetails] = useState(false);
const [messageRootRef, setMessageRootRef] = useState(null);
const [messageIsScrollable, setMessageIsScrollable] = useState(false);
const {
id,
type,
msgParams: { from, data, origin, version },
} = txData;
// not forwarded to component
const hardwareWalletRequiresConnection = useSelector((state) =>
doesAddressRequireLedgerHidConnection(state, from),
);
const { chainId, rpcPrefs } = useSelector(getProviderConfig);
const unapprovedMessagesCount = useSelector(getTotalUnapprovedMessagesCount);
const subjectMetadata = useSelector(getSubjectMetadata);
const isLedgerWallet = useSelector((state) => isAddressLedger(state, from));
const { handleCancelAll } = useRejectTransactionModal();
///: BEGIN:ONLY_INCLUDE_IN(build-mmi)
// Used to show a warning if the signing account is not the selected account
// Largely relevant for contract wallet custodians
const selectedAccount = useSelector(getSelectedAccount);
const mmiActions = mmiActionsFactory();
const accountType = useSelector(getAccountType);
const isNotification = getEnvironmentType() === ENVIRONMENT_TYPE_NOTIFICATION;
const allAccounts = useSelector(
accountsWithSendEtherInfoSelector,
shallowEqual,
);
const { address } = getAccountByAddress(allAccounts, from) || {};
const { custodySignFn } = useMMICustodySignMessage();
///: END:ONLY_INCLUDE_IN
useEffect(() => {
setMessageIsScrollable(
messageRootRef?.scrollHeight > messageRootRef?.clientHeight,
);
}, [messageRootRef]);
const targetSubjectMetadata = subjectMetadata?.[origin] || null;
const parseMessage = memoize((dataToParse) => {
const {
message,
domain = {},
primaryType,
types,
} = JSON.parse(dataToParse);
const sanitizedMessage = sanitizeMessage(message, primaryType, types);
return { sanitizedMessage, domain, primaryType };
});
const onSign = async () => {
///: BEGIN:ONLY_INCLUDE_IN(build-mmi)
if (accountType === 'custody') {
await custodySignFn(txData);
}
///: END:ONLY_INCLUDE_IN
await dispatch(resolvePendingApproval(id));
completedTx(id);
trackEvent({
category: MetaMetricsEventCategory.Transactions,
event: 'Confirm',
properties: {
action: 'Sign Request',
legacy_event: true,
type,
version,
},
});
};
const onCancel = async () => {
await dispatch(
rejectPendingApproval(
id,
serializeError(ethErrors.provider.userRejectedRequest()),
),
);
trackEvent({
category: MetaMetricsEventCategory.Transactions,
event: 'Cancel',
properties: {
action: 'Sign Request',
legacy_event: true,
type,
version,
},
});
};
const {
sanitizedMessage,
domain: { verifyingContract },
primaryType,
} = parseMessage(data);
///: BEGIN:ONLY_INCLUDE_IN(build-mmi)
useEffect(() => {
if (txData.custodyId) {
showCustodianDeepLink({
dispatch,
mmiActions,
txId: undefined,
custodyId: txData.custodyId,
fromAddress: address,
isSignature: true,
closeNotification: isNotification,
onDeepLinkFetched: () => undefined,
onDeepLinkShown: () => {
trackEvent({
category: MetaMetricsEventCategory.MMI,
event: MetaMetricsEventName.SignatureDeeplinkDisplayed,
});
},
showCustodyConfirmLink,
});
}
}, [
dispatch,
mmiActions,
txData.custodyId,
address,
isNotification,
trackEvent,
]);
///: END:ONLY_INCLUDE_IN
return (
<div className="signature-request">
<ConfirmPageContainerNavigation />
<div
className="request-signature__account"
data-testid="request-signature-account"
>
<SignatureRequestHeader txData={txData} />
</div>
<div className="signature-request-content">
{(txData?.securityProviderResponse?.flagAsDangerous !== undefined &&
txData?.securityProviderResponse?.flagAsDangerous !==
SECURITY_PROVIDER_MESSAGE_SEVERITY.NOT_MALICIOUS) ||
(txData?.securityProviderResponse &&
Object.keys(txData.securityProviderResponse).length === 0) ? (
<SecurityProviderBannerMessage
securityProviderResponse={txData.securityProviderResponse}
/>
) : null}
{
///: BEGIN:ONLY_INCLUDE_IN(build-mmi)
selectedAccount.address === address ? null : (
<Box
className="request-signature__mismatch-info"
display={Display.Flex}
width={BlockSize.Full}
padding={4}
marginBottom={4}
backgroundColor={BackgroundColor.primaryMuted}
>
<Icon
name={IconName.Info}
color={IconColor.infoDefault}
marginRight={2}
/>
<Text
variant={TextVariant.bodyXs}
color={TextColor.textDefault}
as="h6"
>
{t('mismatchAccount', [
shortenAddress(selectedAccount.address),
shortenAddress(address),
])}
</Text>
</Box>
)
///: END:ONLY_INCLUDE_IN
}
<div className="signature-request__origin">
<TagUrl
label={origin}
labelProps={{
color: TextColor.textAlternative,
}}
src={targetSubjectMetadata?.iconUrl}
/>
</div>
<Text
className="signature-request__content__title"
variant={TextVariant.headingLg}
marginTop={4}
>
{t('sigRequest')}
</Text>
<Text
className="request-signature__content__subtitle"
variant={TextVariant.bodySm}
color={TextColor.textAlternative}
textAlign={TextAlign.Center}
marginLeft={12}
marginRight={12}
marginTop={4}
as="h6"
>
{t('signatureRequestGuidance')}
</Text>
{verifyingContract ? (
<div>
<Button
variant={BUTTON_VARIANT.LINK}
onClick={() => setShowContractDetails(true)}
className="signature-request-content__verify-contract-details"
data-testid="verify-contract-details"
>
<Text
variant={TextVariant.bodySm}
color={TextColor.primaryDefault}
as="h6"
>
{t('verifyContractDetails')}
</Text>
</Button>
</div>
) : null}
</div>
{isLedgerWallet ? (
<div className="confirm-approve-content__ledger-instruction-wrapper">
<LedgerInstructionField showDataInstruction />
</div>
) : null}
<Message
data={sanitizedMessage}
onMessageScrolled={() => setHasScrolledMessage(true)}
setMessageRootRef={setMessageRootRef}
messageRootRef={messageRootRef}
messageIsScrollable={messageIsScrollable}
primaryType={primaryType}
/>
<Footer
cancelAction={onCancel}
signAction={onSign}
disabled={
///: BEGIN:ONLY_INCLUDE_IN(build-mmi)
Boolean(txData?.custodyId) ||
///: END:ONLY_INCLUDE_IN
hardwareWalletRequiresConnection ||
(messageIsScrollable && !hasScrolledMessage)
}
/>
{showContractDetails && (
<ContractDetailsModal
toAddress={verifyingContract}
chainId={chainId}
rpcPrefs={rpcPrefs}
onClose={() => setShowContractDetails(false)}
isContractRequestingSignature
/>
)}
{unapprovedMessagesCount > 1 ? (
<ButtonLink
size={Size.inherit}
className="signature-request__reject-all-button"
data-testid="signature-request-reject-all"
onClick={handleCancelAll}
>
{t('rejectRequestsN', [unapprovedMessagesCount])}
</ButtonLink>
) : null}
</div>
);
};
SignatureRequest.propTypes = {
txData: PropTypes.object,
};
export default SignatureRequest;

View File

@ -1,14 +1,22 @@
import React from 'react';
import { Provider } from 'react-redux';
import configureStore from '../../../store/store';
import testData from '../../../../.storybook/test-data';
import README from './README.mdx';
import SignatureRequest from './signature-request.component';
import SignatureRequest from './signature-request';
const [MOCK_PRIMARY_IDENTITY, MOCK_SECONDARY_IDENTITY] = Object.values(
testData.metamask.identities,
);
const store = configureStore({
...testData,
metamask: {
...testData.metamask,
selectedAddress: '0xb19ac54efa18cc3a14a5b821bfec73d284bf0c5e',
},
});
export default {
title: 'Components/App/SignatureRequest',
decorators: [(story) => <Provider store={store}>{story()}</Provider>],
component: SignatureRequest,
parameters: {
docs: {
@ -17,22 +25,6 @@ export default {
},
argTypes: {
txData: { control: 'object' },
fromAccount: {
table: {
address: { control: 'text' },
balance: { control: 'text' },
name: { control: 'text' },
},
},
hardwareWalletRequiresConnection: { control: 'boolean' },
isLedgerWallet: { control: 'boolean' },
clearConfirmTransaction: { action: 'Clean Confirm' },
cancel: { action: 'Cancel' },
sign: { action: 'Sign' },
showRejectTransactionsConfirmationModal: {
action: 'showRejectTransactionsConfirmationModal',
},
cancelAll: { action: 'cancelAll' },
},
};
@ -45,6 +37,7 @@ DefaultStory.storyName = 'Default';
DefaultStory.args = {
txData: {
msgParams: {
from: '0xb19ac54efa18cc3a14a5b821bfec73d284bf0c5e',
data: JSON.stringify({
domain: {
name: 'happydapp.website',
@ -79,11 +72,6 @@ DefaultStory.args = {
origin: 'https://happydapp.website/',
},
},
fromAccount: MOCK_PRIMARY_IDENTITY,
providerConfig: { name: 'Goerli ETH' },
selectedAccount: MOCK_PRIMARY_IDENTITY,
hardwareWalletRequiresConnection: false,
currentCurrency: 'usd',
};
export const AccountMismatchStory = (args) => {
@ -94,5 +82,10 @@ AccountMismatchStory.storyName = 'AccountMismatch';
AccountMismatchStory.args = {
...DefaultStory.args,
selectedAccount: MOCK_SECONDARY_IDENTITY,
txData: {
msgParams: {
...DefaultStory.args.txData.msgParams,
from: '0x64a845a5b02460acf8a3d84503b0d68d028b4bb4',
},
},
};

View File

@ -1,33 +1,98 @@
import React from 'react';
import { useSelector } from 'react-redux';
import { fireEvent } from '@testing-library/react';
import configureMockStore from 'redux-mock-store';
import mockState from '../../../../test/data/mock-state.json';
import { renderWithProvider } from '../../../../test/lib/render-helpers';
import { SECURITY_PROVIDER_MESSAGE_SEVERITY } from '../../../../shared/constants/security-provider';
import SignatureRequest from './signature-request.component';
import {
getNativeCurrency,
getProviderConfig,
} from '../../../ducks/metamask/metamask';
import {
accountsWithSendEtherInfoSelector,
conversionRateSelector,
getCurrentCurrency,
getMemoizedAddressBook,
getMemoizedMetaMaskIdentities,
getPreferences,
getSelectedAccount,
getTotalUnapprovedMessagesCount,
unconfirmedTransactionsHashSelector,
} from '../../../selectors';
import SignatureRequest from './signature-request';
const baseProps = {
hardwareWalletRequiresConnection: false,
clearConfirmTransaction: () => undefined,
cancel: () => undefined,
cancelAll: () => undefined,
mostRecentOverviewPage: '/',
showRejectTransactionsConfirmationModal: () => undefined,
sign: () => undefined,
history: { push: '/' },
providerConfig: { type: 'rpc' },
nativeCurrency: 'ABC',
currentCurrency: 'def',
fromAccount: {
address: '0x123456789abcdef',
balance: '0x346ba7725f412cbfdb',
name: 'Antonio',
},
selectedAccount: {
address: '0x123456789abcdef',
clearConfirmTransaction: () => jest.fn(),
cancel: () => jest.fn(),
cancelAll: () => jest.fn(),
showRejectTransactionsConfirmationModal: () => jest.fn(),
sign: () => jest.fn(),
};
const mockStore = {
metamask: {
providerConfig: {
chainId: '0x539',
nickname: 'Localhost 8545',
rpcPrefs: {},
rpcUrl: 'http://localhost:8545',
ticker: 'ETH',
type: 'rpc',
},
preferences: {
useNativeCurrencyAsPrimaryCurrency: true,
},
accounts: {
'0xd8f6a2ffb0fc5952d16c9768b71cfd35b6399aa5': {
address: '0xd8f6a2ffb0fc5952d16c9768b71cfd35b6399aa5',
balance: '0x03',
name: 'John Doe',
},
},
selectedAddress: '0xd8f6a2ffb0fc5952d16c9768b71cfd35b6399aa5',
nativeCurrency: 'ETH',
currentCurrency: 'usd',
conversionRate: null,
unapprovedTypedMessagesCount: 2,
},
};
jest.mock('react-redux', () => {
const actual = jest.requireActual('react-redux');
return {
...actual,
useSelector: jest.fn(),
useDispatch: () => jest.fn(),
};
});
const generateUseSelectorRouter = (opts) => (selector) => {
switch (selector) {
case getProviderConfig:
return opts.metamask.providerConfig;
case getCurrentCurrency:
return opts.metamask.currentCurrency;
case getNativeCurrency:
return opts.metamask.nativeCurrency;
case getTotalUnapprovedMessagesCount:
return opts.metamask.unapprovedTypedMessagesCount;
case getPreferences:
return opts.metamask.preferences;
case conversionRateSelector:
return opts.metamask.conversionRate;
case getSelectedAccount:
return opts.metamask.accounts[opts.metamask.selectedAddress];
case getMemoizedAddressBook:
return [];
case accountsWithSendEtherInfoSelector:
return Object.values(opts.metamask.accounts);
case unconfirmedTransactionsHashSelector:
case getMemoizedMetaMaskIdentities:
return {};
default:
return undefined;
}
};
describe('Signature Request Component', () => {
const store = configureMockStore()(mockState);
@ -35,6 +100,7 @@ describe('Signature Request Component', () => {
let messageData;
beforeEach(() => {
useSelector.mockImplementation(generateUseSelectorRouter(mockStore));
messageData = {
domain: {
chainId: 97,
@ -84,7 +150,17 @@ describe('Signature Request Component', () => {
});
it('should match snapshot when we want to switch to fiat', () => {
useSelector.mockImplementation(
generateUseSelectorRouter({
...mockStore,
metamask: {
...mockStore.metamask,
conversionRate: 231.06,
},
}),
);
const msgParams = {
from: '0xd8f6a2ffb0fc5952d16c9768b71cfd35b6399aa5',
data: JSON.stringify(messageData),
version: 'V4',
origin: 'test',
@ -95,7 +171,6 @@ describe('Signature Request Component', () => {
txData={{
msgParams,
}}
conversionRate={1567}
/>,
store,
);
@ -105,6 +180,7 @@ describe('Signature Request Component', () => {
it('should match snapshot when we are using eth', () => {
const msgParams = {
from: '0xd8f6a2ffb0fc5952d16c9768b71cfd35b6399aa5',
data: JSON.stringify(messageData),
version: 'V4',
origin: 'test',
@ -115,7 +191,6 @@ describe('Signature Request Component', () => {
txData={{
msgParams,
}}
conversionRate={null}
/>,
store,
);
@ -125,6 +200,7 @@ describe('Signature Request Component', () => {
it('should render navigation', () => {
const msgParams = {
from: '0xd8f6a2ffb0fc5952d16c9768b71cfd35b6399aa5',
data: JSON.stringify(messageData),
version: 'V4',
origin: 'test',
@ -135,7 +211,6 @@ describe('Signature Request Component', () => {
txData={{
msgParams,
}}
conversionRate={null}
/>,
store,
);
@ -149,6 +224,7 @@ describe('Signature Request Component', () => {
do_not_display: 'two',
};
const msgParams = {
from: '0xd8f6a2ffb0fc5952d16c9768b71cfd35b6399aa5',
data: JSON.stringify(messageData),
version: 'V4',
origin: 'test',
@ -159,7 +235,6 @@ describe('Signature Request Component', () => {
txData={{
msgParams,
}}
conversionRate={null}
/>,
store,
);
@ -171,7 +246,17 @@ describe('Signature Request Component', () => {
});
it('should not render a reject multiple requests link if there is not multiple requests', () => {
useSelector.mockImplementation(
generateUseSelectorRouter({
...mockStore,
metamask: {
...mockStore.metamask,
unapprovedTypedMessagesCount: 0,
},
}),
);
const msgParams = {
from: '0xd8f6a2ffb0fc5952d16c9768b71cfd35b6399aa5',
data: JSON.stringify(messageData),
version: 'V4',
origin: 'test',
@ -182,7 +267,6 @@ describe('Signature Request Component', () => {
txData={{
msgParams,
}}
conversionRate={null}
/>,
store,
);
@ -194,6 +278,7 @@ describe('Signature Request Component', () => {
it('should render a reject multiple requests link if there is multiple requests (greater than 1)', () => {
const msgParams = {
from: '0xd8f6a2ffb0fc5952d16c9768b71cfd35b6399aa5',
data: JSON.stringify(messageData),
version: 'V4',
origin: 'test',
@ -204,8 +289,6 @@ describe('Signature Request Component', () => {
txData={{
msgParams,
}}
conversionRate={null}
unapprovedMessagesCount={2}
/>,
store,
);
@ -217,6 +300,7 @@ describe('Signature Request Component', () => {
it('should call reject all button when button is clicked', () => {
const msgParams = {
from: '0xd8f6a2ffb0fc5952d16c9768b71cfd35b6399aa5',
data: JSON.stringify(messageData),
version: 'V4',
origin: 'test',
@ -227,8 +311,6 @@ describe('Signature Request Component', () => {
txData={{
msgParams,
}}
conversionRate={null}
unapprovedMessagesCount={2}
/>,
store,
);
@ -242,6 +324,7 @@ describe('Signature Request Component', () => {
it('should render text of reject all button', () => {
const msgParams = {
from: '0xd8f6a2ffb0fc5952d16c9768b71cfd35b6399aa5',
data: JSON.stringify(messageData),
version: 'V4',
origin: 'test',
@ -252,8 +335,6 @@ describe('Signature Request Component', () => {
txData={{
msgParams,
}}
conversionRate={null}
unapprovedMessagesCount={2}
/>,
store,
);
@ -263,6 +344,7 @@ describe('Signature Request Component', () => {
it('should render SecurityProviderBannerMessage component properly', () => {
const msgParams = {
from: '0xd8f6a2ffb0fc5952d16c9768b71cfd35b6399aa5',
data: JSON.stringify(messageData),
version: 'V4',
origin: 'test',
@ -271,7 +353,6 @@ describe('Signature Request Component', () => {
const { queryByText } = renderWithProvider(
<SignatureRequest
{...baseProps}
conversionRate={null}
txData={{
msgParams,
securityProviderResponse: {
@ -280,7 +361,6 @@ describe('Signature Request Component', () => {
reason_header: 'Some reason header...',
},
}}
unapprovedMessagesCount={2}
/>,
store,
);
@ -296,6 +376,7 @@ describe('Signature Request Component', () => {
it('should not render SecurityProviderBannerMessage component when flagAsDangerous is not malicious', () => {
const msgParams = {
from: '0xd8f6a2ffb0fc5952d16c9768b71cfd35b6399aa5',
data: JSON.stringify(messageData),
version: 'V4',
origin: 'test',
@ -304,14 +385,12 @@ describe('Signature Request Component', () => {
const { queryByText } = renderWithProvider(
<SignatureRequest
{...baseProps}
conversionRate={null}
txData={{
msgParams,
securityProviderResponse: {
flagAsDangerous: SECURITY_PROVIDER_MESSAGE_SEVERITY.NOT_MALICIOUS,
},
}}
unapprovedMessagesCount={2}
/>,
store,
);
@ -327,27 +406,39 @@ describe('Signature Request Component', () => {
it('should render a warning when the selected account is not the one being used to sign', () => {
const msgParams = {
from: '0xd8f6a2ffb0fc5952d16c9768b71cfd35b6399aa5',
data: JSON.stringify(messageData),
version: 'V4',
origin: 'test',
};
useSelector.mockImplementation(
generateUseSelectorRouter({
...mockStore,
metamask: {
...mockStore.metamask,
selectedAddress: '0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc',
accounts: {
...mockStore.metamask.accounts,
'0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc': {
address: '0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc',
balance: '0x0',
name: 'Account 1',
},
},
},
}),
);
const { container } = renderWithProvider(
<SignatureRequest
{...baseProps}
selectedAccount={{
address: '0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc',
balance: '0x0',
name: 'Account 1',
}}
conversionRate={null}
txData={{
msgParams,
securityProviderResponse: {
flagAsDangerous: SECURITY_PROVIDER_MESSAGE_SEVERITY.NOT_MALICIOUS,
},
}}
unapprovedMessagesCount={2}
/>,
store,
);

View File

@ -0,0 +1,77 @@
import { shallowEqual, useDispatch, useSelector } from 'react-redux';
import { showCustodianDeepLink } from '@metamask-institutional/extension';
import {
showCustodyConfirmLink,
checkForUnapprovedMessages,
} from '../store/institutional/institution-actions';
import {
mmiActionsFactory,
setTypedMessageInProgress,
} from '../store/institutional/institution-background';
import {
accountsWithSendEtherInfoSelector,
getAccountType,
unapprovedTypedMessagesSelector,
} from '../selectors';
import { getAccountByAddress } from '../helpers/utils/util';
import { getEnvironmentType } from '../../app/scripts/lib/util';
import { goHome, showModal } from '../store/actions';
import { ENVIRONMENT_TYPE_NOTIFICATION } from '../../shared/constants/app';
export function useMMICustodySignMessage() {
const dispatch = useDispatch();
const mmiActions = mmiActionsFactory();
const envType = getEnvironmentType();
const accountType = useSelector(getAccountType);
const isNotification = envType === ENVIRONMENT_TYPE_NOTIFICATION;
const allAccounts = useSelector(
accountsWithSendEtherInfoSelector,
shallowEqual,
);
const unapprovedTypedMessages = useSelector(unapprovedTypedMessagesSelector);
const custodySignFn = async (_msgData) => {
if (accountType === 'custody') {
const { address: fromAddress } =
getAccountByAddress(allAccounts, _msgData.msgParams.from) || {};
try {
let msgData = _msgData;
let id = _msgData.custodyId;
if (!_msgData.custodyId) {
msgData = checkForUnapprovedMessages(
_msgData,
unapprovedTypedMessages,
);
id = msgData.custodyId;
}
showCustodianDeepLink({
dispatch,
mmiActions,
txId: undefined,
custodyId: id,
fromAddress,
isSignature: true,
closeNotification: isNotification,
onDeepLinkFetched: () => undefined,
onDeepLinkShown: () => undefined,
showCustodyConfirmLink,
});
await dispatch(setTypedMessageInProgress(msgData.metamaskId));
await dispatch(mmiActions.setWaitForConfirmDeepLinkDialog(true));
await dispatch(goHome());
} catch (err) {
await dispatch(mmiActions.setWaitForConfirmDeepLinkDialog(true));
await dispatch(
showModal({
name: 'TRANSACTION_FAILED',
errorMessage: err.message,
closeNotification: true,
operationFailed: true,
}),
);
}
}
};
return { custodySignFn };
}

View File

@ -0,0 +1,35 @@
import { useDispatch, useSelector } from 'react-redux';
import { useHistory } from 'react-router-dom';
import { valuesFor } from '../helpers/utils/util';
import { showModal, rejectAllMessages } from '../store/actions';
import { clearConfirmTransaction } from '../ducks/confirm-transaction/confirm-transaction.duck';
import { getMostRecentOverviewPage } from '../ducks/history/history';
import {
getTotalUnapprovedMessagesCount,
unconfirmedMessagesHashSelector,
} from '../selectors';
export function useRejectTransactionModal() {
const dispatch = useDispatch();
const history = useHistory();
const mostRecentOverviewPage = useSelector(getMostRecentOverviewPage);
const unapprovedMessagesCount = useSelector(getTotalUnapprovedMessagesCount);
const unconfirmedMessagesList = useSelector(unconfirmedMessagesHashSelector);
const handleCancelAll = () => {
dispatch(
showModal({
name: 'REJECT_TRANSACTIONS',
onSubmit: async () => {
await dispatch(rejectAllMessages(valuesFor(unconfirmedMessagesList)));
dispatch(clearConfirmTransaction());
history.push(mostRecentOverviewPage);
},
unapprovedTxCount: unapprovedMessagesCount,
isRequestType: true,
}),
);
};
return { handleCancelAll };
}

View File

@ -1,6 +1,6 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Signature Request Component render should match snapshot 1`] = `
exports[`Confirm Signature Request Component render should match snapshot 1`] = `
<div>
<div
class="signature-request"
@ -178,56 +178,48 @@ exports[`Signature Request Component render should match snapshot 1`] = `
class="signature-request__origin"
>
<div
class="site-origin"
class="box mm-tag-url box--padding-right-4 box--padding-left-2 box--display-flex box--gap-2 box--flex-direction-row box--align-items-center box--background-color-background-default box--rounded-pill box--border-color-border-default box--border-width-1 box--border-style-solid"
>
<div
class="chip chip--with-left-icon chip--border-color-border-muted chip--background-color-undefined"
class="mm-box mm-text mm-avatar-base mm-avatar-base--size-md mm-avatar-favicon mm-text--body-sm mm-text--text-transform-uppercase mm-box--display-flex mm-box--justify-content-center mm-box--align-items-center mm-box--color-text-default mm-box--background-color-background-alternative mm-box--rounded-full mm-box--border-color-transparent box--border-style-solid box--border-width-1"
>
<div
class="chip__left-icon"
>
<div
class=""
>
<span
class="icon-with-fallback__fallback"
>
M
</span>
</div>
</div>
<span
class="box box--margin-top-1 box--margin-bottom-1 box--flex-direction-row typography chip__label typography--h6 typography--weight-normal typography--style-normal typography--color-text-alternative"
>
https://metamask.github.io
</span>
class="box mm-icon mm-icon--size-md box--display-inline-block box--flex-direction-row box--color-icon-default"
style="mask-image: url('./images/icons/global.svg');"
/>
</div>
<p
class="mm-box mm-text mm-text--body-md mm-text--ellipsis mm-box--color-text-alternative"
>
https://metamask.github.io
</p>
</div>
</div>
<h3
class="mm-box mm-text signature-request__content__title mm-text--heading-md mm-box--margin-top-4 mm-box--color-text-default"
<h2
class="mm-box mm-text signature-request__content__title mm-text--heading-lg mm-box--margin-top-4 mm-box--color-text-default"
>
Signature request
</h3>
</h2>
<h6
align="center"
class="mm-box mm-text request-signature__content__subtitle mm-text--body-sm mm-box--margin-12 mm-box--margin-top-3 mm-box--color-text-alternative"
class="mm-box mm-text request-signature__content__subtitle mm-text--body-sm mm-text--text-align-center mm-box--margin-top-4 mm-box--margin-right-12 mm-box--margin-left-12 mm-box--color-text-alternative"
>
Only sign this message if you fully understand the content and trust the requesting site.
</h6>
<div>
<a
class="button btn-link signature-request-content__verify-contract-details"
<button
class="mm-box mm-text mm-button-base signature-request-content__verify-contract-details mm-button-link mm-button-link--size-auto mm-text--body-md-medium mm-box--padding-right-0 mm-box--padding-left-0 mm-box--display-inline-flex mm-box--justify-content-center mm-box--align-items-center mm-box--color-primary-default mm-box--background-color-transparent"
data-testid="verify-contract-details"
role="button"
tabindex="0"
>
<h6
class="mm-box mm-text mm-text--body-sm mm-box--color-primary-default"
<span
class="mm-box mm-text mm-text--inherit mm-box--color-primary-default"
>
Verify third-party details
</h6>
</a>
<h6
class="mm-box mm-text mm-text--body-sm mm-box--color-primary-default"
>
Verify third-party details
</h6>
</span>
</button>
</div>
</div>
<div

View File

@ -57,7 +57,7 @@ const mockState = {
send: { draftTransactions: {} },
};
describe('Signature Request Component', () => {
describe('Confirm Signature Request Component', () => {
const store = configureMockStore()(mockState);
describe('render', () => {