diff --git a/ui/pages/confirmations/components/confirm/blockaid-loading-indicator/__snapshots__/blockaid-loading-indicator.test.tsx.snap b/ui/pages/confirmations/components/confirm/blockaid-loading-indicator/__snapshots__/blockaid-loading-indicator.test.tsx.snap new file mode 100644 index 000000000000..b60f849b51f2 --- /dev/null +++ b/ui/pages/confirmations/components/confirm/blockaid-loading-indicator/__snapshots__/blockaid-loading-indicator.test.tsx.snap @@ -0,0 +1,49 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`BlockaidLoadingIndicator returns spinner when there blockaid validation is in progress for signature 1`] = ` +<div> + <div + class="mm-box mm-box--margin-top-4 mm-box--margin-inline-auto" + > + <svg + class="preloader__icon" + fill="none" + height="18" + viewBox="0 0 16 16" + width="18" + xmlns="http://www.w3.org/2000/svg" + > + <path + clip-rule="evenodd" + d="M8 13.7143C4.84409 13.7143 2.28571 11.1559 2.28571 8C2.28571 4.84409 4.84409 2.28571 8 2.28571C11.1559 2.28571 13.7143 4.84409 13.7143 8C13.7143 11.1559 11.1559 13.7143 8 13.7143ZM8 16C3.58172 16 0 12.4183 0 8C0 3.58172 3.58172 0 8 0C12.4183 0 16 3.58172 16 8C16 12.4183 12.4183 16 8 16Z" + fill="var(--color-primary-muted)" + fill-rule="evenodd" + /> + <mask + height="16" + id="mask0" + mask-type="alpha" + maskUnits="userSpaceOnUse" + width="16" + x="0" + y="0" + > + <path + clip-rule="evenodd" + d="M8 13.7143C4.84409 13.7143 2.28571 11.1559 2.28571 8C2.28571 4.84409 4.84409 2.28571 8 2.28571C11.1559 2.28571 13.7143 4.84409 13.7143 8C13.7143 11.1559 11.1559 13.7143 8 13.7143ZM8 16C3.58172 16 0 12.4183 0 8C0 3.58172 3.58172 0 8 0C12.4183 0 16 3.58172 16 8C16 12.4183 12.4183 16 8 16Z" + fill="var(--color-primary-default)" + fill-rule="evenodd" + /> + </mask> + <g + mask="url(#mask0)" + > + <path + d="M6.85718 17.9999V11.4285V8.28564H-4.85711V17.9999H6.85718Z" + fill="var(--color-primary-default)" + /> + </g> + </svg> + </div> +</div> +`; diff --git a/ui/pages/confirmations/components/confirm/blockaid-loading-indicator/blockaid-loading-indicator.test.tsx b/ui/pages/confirmations/components/confirm/blockaid-loading-indicator/blockaid-loading-indicator.test.tsx new file mode 100644 index 000000000000..5be896e98827 --- /dev/null +++ b/ui/pages/confirmations/components/confirm/blockaid-loading-indicator/blockaid-loading-indicator.test.tsx @@ -0,0 +1,79 @@ +import React from 'react'; +import configureStore from 'redux-mock-store'; +import { + TransactionStatus, + TransactionType, +} from '@metamask/transaction-controller'; + +import mockState from '../../../../../../test/data/mock-state.json'; +import { BlockaidResultType } from '../../../../../../shared/constants/security-provider'; +import { renderWithProvider } from '../../../../../../test/lib/render-helpers'; +import { SecurityAlertResponse } from '../../../types/confirm'; + +import BlockaidLoadingIndicator from './blockaid-loading-indicator'; + +const mockSecurityAlertResponse: SecurityAlertResponse = { + securityAlertId: 'test-id-mock', + reason: 'test-reason', + result_type: BlockaidResultType.Loading, +}; + +const render = ( + securityAlertResponse: SecurityAlertResponse = mockSecurityAlertResponse, +) => { + const currentConfirmationMock = { + id: '1', + status: TransactionStatus.unapproved, + time: new Date().getTime(), + type: TransactionType.personalSign, + securityAlertResponse, + chainId: '0x1', + }; + + const mockExpectedState = { + ...mockState, + metamask: { + ...mockState.metamask, + unapprovedPersonalMsgs: { + '1': { ...currentConfirmationMock, msgParams: {} }, + }, + pendingApprovals: { + '1': { + ...currentConfirmationMock, + origin: 'origin', + requestData: {}, + requestState: null, + expectsResult: false, + }, + }, + preferences: { redesignedConfirmationsEnabled: true }, + signatureSecurityAlertResponses: { + 'test-id-mock': securityAlertResponse, + }, + }, + confirm: { currentConfirmation: currentConfirmationMock }, + }; + + const defaultStore = configureStore()(mockExpectedState); + return renderWithProvider(<BlockaidLoadingIndicator />, defaultStore); +}; + +describe('BlockaidLoadingIndicator', () => { + it('returns spinner when there blockaid validation is in progress for signature', () => { + const { container } = render(); + expect(container).toMatchSnapshot(); + }); + + it('returns null if blockaid validation is not in progress', () => { + const { container } = render({ + reason: 'test-reason', + result_type: BlockaidResultType.Benign, + }); + expect(container).toBeEmptyDOMElement(); + }); + + it('returns null if there is not blockaid validation response', () => { + const { container } = render({} as SecurityAlertResponse); + expect(container).toBeEmptyDOMElement(); + }); +}); diff --git a/ui/pages/confirmations/components/confirm/blockaid-loading-indicator/blockaid-loading-indicator.tsx b/ui/pages/confirmations/components/confirm/blockaid-loading-indicator/blockaid-loading-indicator.tsx new file mode 100644 index 000000000000..49f9c8b199cd --- /dev/null +++ b/ui/pages/confirmations/components/confirm/blockaid-loading-indicator/blockaid-loading-indicator.tsx @@ -0,0 +1,28 @@ +import React from 'react'; +import { useSelector } from 'react-redux'; + +import Preloader from '../../../../../components/ui/icon/preloader'; +import { BlockaidResultType } from '../../../../../../shared/constants/security-provider'; +import { Box } from '../../../../../components/component-library'; + +import { currentSignatureRequestSecurityResponseSelector } from '../../../selectors'; + +const BlockaidLoadingIndicator = () => { + const signatureSecurityAlertResponse = useSelector( + currentSignatureRequestSecurityResponseSelector, + ); + + if ( + signatureSecurityAlertResponse?.result_type !== BlockaidResultType.Loading + ) { + return null; + } + + return ( + <Box marginInline={'auto'} marginTop={4}> + <Preloader size={18} /> + </Box> + ); +}; + +export default BlockaidLoadingIndicator; diff --git a/ui/pages/confirmations/components/confirm/blockaid-loading-indicator/index.ts b/ui/pages/confirmations/components/confirm/blockaid-loading-indicator/index.ts new file mode 100644 index 000000000000..331dd7ee53b6 --- /dev/null +++ b/ui/pages/confirmations/components/confirm/blockaid-loading-indicator/index.ts @@ -0,0 +1 @@ +export { default as BlockaidLoadingIndicator } from './blockaid-loading-indicator'; diff --git a/ui/pages/confirmations/confirm/confirm.tsx b/ui/pages/confirmations/confirm/confirm.tsx index 96d6357fe88a..89a2fe5256be 100644 --- a/ui/pages/confirmations/confirm/confirm.tsx +++ b/ui/pages/confirmations/confirm/confirm.tsx @@ -5,6 +5,7 @@ import { Page } from '../../../components/multichain/pages/page'; import { MMISignatureMismatchBanner } from '../../../components/app/mmi-signature-mismatch-banner'; ///: END:ONLY_INCLUDE_IF +import { BlockaidLoadingIndicator } from '../components/confirm/blockaid-loading-indicator'; import ScrollToBottom from '../components/confirm/scroll-to-bottom'; import setCurrentConfirmation from '../hooks/setCurrentConfirmation'; import syncConfirmPath from '../hooks/syncConfirmPath'; @@ -46,6 +47,7 @@ const Confirm = () => { ///: END:ONLY_INCLUDE_IF } <ScrollToBottom showAdvancedDetails={showAdvancedDetails}> + <BlockaidLoadingIndicator /> <LedgerInfo /> <Title /> <Info showAdvancedDetails={showAdvancedDetails} /> diff --git a/ui/pages/confirmations/hooks/alerts/useBlockaidAlerts.ts b/ui/pages/confirmations/hooks/alerts/useBlockaidAlerts.ts index ad93e1a87d5f..ae87e60d64f6 100644 --- a/ui/pages/confirmations/hooks/alerts/useBlockaidAlerts.ts +++ b/ui/pages/confirmations/hooks/alerts/useBlockaidAlerts.ts @@ -13,7 +13,10 @@ import { REDESIGN_TRANSACTION_TYPES, SIGNATURE_TRANSACTION_TYPES, } from '../../utils'; -import { currentConfirmationSelector } from '../../selectors'; +import { + currentConfirmationSelector, + currentSignatureRequestSecurityResponseSelector, +} from '../../selectors'; import { normalizeProviderAlert } from './utils'; const SUPPORTED_TRANSACTION_TYPES = [ @@ -47,8 +50,7 @@ const useBlockaidAlerts = (): Alert[] => { const transactionType = currentConfirmation?.type as TransactionType; const signatureSecurityAlertResponse = useSelector( - (state: SecurityAlertResponsesState) => - state.metamask.signatureSecurityAlertResponses?.[securityAlertId], + currentSignatureRequestSecurityResponseSelector, ); const transactionSecurityAlertResponse = useSelector( diff --git a/ui/pages/confirmations/selectors/confirm.test.ts b/ui/pages/confirmations/selectors/confirm.test.ts index e95da11e2cba..0aa56cb3f82e 100644 --- a/ui/pages/confirmations/selectors/confirm.test.ts +++ b/ui/pages/confirmations/selectors/confirm.test.ts @@ -1,12 +1,24 @@ import { ApprovalType } from '@metamask/controller-utils'; import { TransactionType } from '@metamask/transaction-controller'; -import { ConfirmMetamaskState } from '../types/confirm'; + +import { + BlockaidReason, + BlockaidResultType, +} from '../../../../shared/constants/security-provider'; +import { ConfirmMetamaskState, SecurityAlertResponse } from '../types/confirm'; import { currentConfirmationSelector, + currentSignatureRequestSecurityResponseSelector, latestPendingConfirmationSelector, pendingConfirmationsSelector, } from './confirm'; +const SECURITY_ALERT_RESPONSE_MOCK: SecurityAlertResponse = { + securityAlertId: '1', + result_type: BlockaidResultType.Malicious, + reason: BlockaidReason.permitFarming, +}; + describe('confirm selectors', () => { const mockedState: ConfirmMetamaskState = { confirm: { @@ -75,4 +87,28 @@ describe('confirm selectors', () => { expect(result).toStrictEqual(mockedState.confirm.currentConfirmation); }); }); + + describe('currentSignatureRequestSecurityResponseSelector', () => { + it('should return SecurityAlertResponse for current signature', () => { + const sigMockState: ConfirmMetamaskState = { + confirm: { + currentConfirmation: { + id: '1', + type: TransactionType.personalSign, + securityAlertResponse: SECURITY_ALERT_RESPONSE_MOCK, + }, + }, + metamask: { + pendingApprovals: {}, + approvalFlows: [], + signatureSecurityAlertResponses: { 1: SECURITY_ALERT_RESPONSE_MOCK }, + }, + }; + + const result = + currentSignatureRequestSecurityResponseSelector(sigMockState); + + expect(result).toStrictEqual(SECURITY_ALERT_RESPONSE_MOCK); + }); + }); }); diff --git a/ui/pages/confirmations/selectors/confirm.ts b/ui/pages/confirmations/selectors/confirm.ts index 8cc7dd19a95f..e4af9333e8d5 100644 --- a/ui/pages/confirmations/selectors/confirm.ts +++ b/ui/pages/confirmations/selectors/confirm.ts @@ -2,8 +2,13 @@ import { ApprovalType } from '@metamask/controller-utils'; import { createSelector } from 'reselect'; import { getPendingApprovals } from '../../../selectors/approvals'; -import { ConfirmMetamaskState } from '../types/confirm'; +import { + ConfirmMetamaskState, + Confirmation, + SecurityAlertResponse, +} from '../types/confirm'; import { createDeepEqualSelector } from '../../../selectors/util'; +import { isSignatureTransactionType } from '../utils'; const ConfirmationApprovalTypes = [ ApprovalType.EthSign, @@ -43,3 +48,23 @@ export const confirmSelector = (state: ConfirmMetamaskState) => state.confirm; export const currentConfirmationSelector = (state: ConfirmMetamaskState) => state.confirm.currentConfirmation; + +export const currentSignatureRequestSecurityResponseSelector = ( + state: ConfirmMetamaskState, +) => { + const currentConfirmation: Confirmation | undefined = + currentConfirmationSelector(state); + + if ( + !currentConfirmation || + !isSignatureTransactionType(currentConfirmation) + ) { + return undefined; + } + + const securityAlertId = ( + currentConfirmation?.securityAlertResponse as SecurityAlertResponse + )?.securityAlertId as string; + + return state.metamask.signatureSecurityAlertResponses?.[securityAlertId]; +}; diff --git a/ui/pages/confirmations/types/confirm.ts b/ui/pages/confirmations/types/confirm.ts index ab472318fcac..2050aae32c05 100644 --- a/ui/pages/confirmations/types/confirm.ts +++ b/ui/pages/confirmations/types/confirm.ts @@ -58,5 +58,6 @@ export type ConfirmMetamaskState = { metamask: { pendingApprovals: ApprovalControllerState['pendingApprovals']; approvalFlows: ApprovalControllerState['approvalFlows']; + signatureSecurityAlertResponses?: Record<string, SecurityAlertResponse>; }; };