diff --git a/app/scripts/controllers/metametrics.js b/app/scripts/controllers/metametrics.js index 5ce36871262d..bfe7f79d1ac4 100644 --- a/app/scripts/controllers/metametrics.js +++ b/app/scripts/controllers/metametrics.js @@ -16,6 +16,7 @@ import { ENVIRONMENT_TYPE_BACKGROUND } from '../../../shared/constants/app'; import { METAMETRICS_ANONYMOUS_ID, METAMETRICS_BACKGROUND_PAGE_OBJECT, + MetaMetricsEventName, MetaMetricsUserTrait, } from '../../../shared/constants/metametrics'; import { SECOND } from '../../../shared/constants/time'; @@ -40,6 +41,12 @@ export const overrideAnonymousEventNames = { AnonymousTransactionMetaMetricsEvent.rejected, [TransactionMetaMetricsEvent.submitted]: AnonymousTransactionMetaMetricsEvent.submitted, + [MetaMetricsEventName.SignatureRequested]: + MetaMetricsEventName.SignatureRequestedAnon, + [MetaMetricsEventName.SignatureApproved]: + MetaMetricsEventName.SignatureApprovedAnon, + [MetaMetricsEventName.SignatureRejected]: + MetaMetricsEventName.SignatureRejectedAnon, }; const defaultCaptureException = (err) => { diff --git a/app/scripts/controllers/metametrics.test.js b/app/scripts/controllers/metametrics.test.js index de75170b8e58..2113efd1715b 100644 --- a/app/scripts/controllers/metametrics.test.js +++ b/app/scripts/controllers/metametrics.test.js @@ -580,6 +580,38 @@ describe('MetaMetricsController', function () { }); }); + describe('Change Signature XXX anonymous event names', function () { + it.each([ + ['Signature Requested', 'Signature Requested Anon'], + ['Signature Rejected', 'Signature Rejected Anon'], + ['Signature Approved', 'Signature Approved Anon'], + ])( + 'should change "%s" anonymous event names to "%s"', + (eventType, anonEventType) => { + const metaMetricsController = getMetaMetricsController(); + const spy = jest.spyOn(segment, 'track'); + metaMetricsController.submitEvent({ + event: eventType, + category: 'Unit Test', + properties: { ...DEFAULT_EVENT_PROPERTIES }, + sensitiveProperties: { foo: 'bar' }, + }); + + expect(spy).toHaveBeenCalledTimes(2); + + expect(spy.mock.calls[0][0]).toMatchObject({ + event: anonEventType, + properties: { foo: 'bar', ...DEFAULT_EVENT_PROPERTIES }, + }); + + expect(spy.mock.calls[1][0]).toMatchObject({ + event: eventType, + properties: { ...DEFAULT_EVENT_PROPERTIES }, + }); + }, + ); + }); + describe('Change Transaction XXX anonymous event namnes', function () { it('should change "Transaction Added" anonymous event names to "Transaction Added Anon"', function () { const metaMetricsController = getMetaMetricsController(); diff --git a/app/scripts/lib/createRPCMethodTrackingMiddleware.js b/app/scripts/lib/createRPCMethodTrackingMiddleware.js index 092b95943c2d..6f4c06ee521c 100644 --- a/app/scripts/lib/createRPCMethodTrackingMiddleware.js +++ b/app/scripts/lib/createRPCMethodTrackingMiddleware.js @@ -136,9 +136,11 @@ let globalRateLimitCount = 0; function createSignatureFragment(metaMetricsController, req, fragmentPayload) { metaMetricsController.createEventFragment({ category: MetaMetricsEventCategory.InpageProvider, + initialEvent: MetaMetricsEventName.SignatureRequested, successEvent: MetaMetricsEventName.SignatureApproved, failureEvent: MetaMetricsEventName.SignatureRejected, + uniqueIdentifier: generateSignatureUniqueId(req.id), persist: true, referrer: { @@ -164,12 +166,7 @@ function finalizeSignatureFragment( ) { const signatureUniqueId = generateSignatureUniqueId(req.id); - if (fragmentPayload) { - metaMetricsController.updateEventFragment( - signatureUniqueId, - fragmentPayload, - ); - } + metaMetricsController.updateEventFragment(signatureUniqueId, fragmentPayload); metaMetricsController.finalizeEventFragment( signatureUniqueId, @@ -222,8 +219,7 @@ export default function createRPCMethodTrackingMiddleware({ ) { const { origin, method, params } = req; - const rateLimitType = - RATE_LIMIT_MAP[method] ?? RATE_LIMIT_TYPES.RANDOM_SAMPLE; + const rateLimitType = RATE_LIMIT_MAP[method]; let isRateLimited; switch (rateLimitType) { @@ -258,6 +254,7 @@ export default function createRPCMethodTrackingMiddleware({ const eventType = EVENT_NAME_MAP[method]; const eventProperties = {}; + let sensitiveEventProperties; // Boolean variable that reduces code duplication and increases legibility const shouldTrackEvent = @@ -270,8 +267,6 @@ export default function createRPCMethodTrackingMiddleware({ // Don't track if the user isn't participating in metametrics userParticipatingInMetaMetrics === true; - let signatureUniqueId; - if (shouldTrackEvent) { // We track an initial "requested" event as soon as the dapp calls the // provider method. For the events not special cased this is the only @@ -346,14 +341,25 @@ export default function createRPCMethodTrackingMiddleware({ ]; } } else if (method === MESSAGE_TYPE.ETH_SIGN_TYPED_DATA_V4) { - const { primaryType } = parseTypedDataMessage(data); - eventProperties.eip712_primary_type = primaryType; - if (PRIMARY_TYPES_PERMIT.includes(primaryType)) { + const parsedMessageData = parseTypedDataMessage(data); + sensitiveEventProperties = {}; + + eventProperties.eip712_primary_type = parsedMessageData.primaryType; + sensitiveEventProperties.eip712_verifyingContract = + parsedMessageData.domain.verifyingContract; + sensitiveEventProperties.eip712_domain_version = + parsedMessageData.domain.version; + sensitiveEventProperties.eip712_domain_name = + parsedMessageData.domain.name; + + if (PRIMARY_TYPES_PERMIT.includes(parsedMessageData.primaryType)) { eventProperties.ui_customizations = [ ...(eventProperties.ui_customizations || []), MetaMetricsEventUiCustomization.Permit, ]; - } else if (PRIMARY_TYPES_ORDER.includes(primaryType)) { + } else if ( + PRIMARY_TYPES_ORDER.includes(parsedMessageData.primaryType) + ) { eventProperties.ui_customizations = [ ...(eventProperties.ui_customizations || []), MetaMetricsEventUiCustomization.Order, @@ -373,9 +379,12 @@ export default function createRPCMethodTrackingMiddleware({ } if (event === MetaMetricsEventName.SignatureRequested) { - createSignatureFragment(metaMetricsController, req, { + const fragmentPayload = { properties: eventProperties, - }); + sensitiveProperties: sensitiveEventProperties, + }; + + createSignatureFragment(metaMetricsController, req, fragmentPayload); } else { metaMetricsController.trackEvent({ event, @@ -436,12 +445,16 @@ export default function createRPCMethodTrackingMiddleware({ location, }; - if (signatureUniqueId) { + if ( + event === MetaMetricsEventName.SignatureRejected || + event === MetaMetricsEventName.SignatureApproved + ) { const finalizeOptions = { abandoned: event === eventType.REJECTED, }; const fragmentPayload = { properties, + sensitiveProperties: sensitiveEventProperties, }; finalizeSignatureFragment( @@ -460,7 +473,6 @@ export default function createRPCMethodTrackingMiddleware({ properties, }); } - return callback(); }); }; diff --git a/app/scripts/lib/createRPCMethodTrackingMiddleware.test.js b/app/scripts/lib/createRPCMethodTrackingMiddleware.test.js index 569aff50458a..f0b66430ee84 100644 --- a/app/scripts/lib/createRPCMethodTrackingMiddleware.test.js +++ b/app/scripts/lib/createRPCMethodTrackingMiddleware.test.js @@ -13,7 +13,10 @@ import { BlockaidReason, BlockaidResultType, } from '../../../shared/constants/security-provider'; -import { permitSignatureMsg } from '../../../test/data/confirmations/typed_sign'; +import { + permitSignatureMsg, + orderSignatureMsg, +} from '../../../test/data/confirmations/typed_sign'; import { createSegmentMock } from './segment'; import createRPCMethodTrackingMiddleware from './createRPCMethodTrackingMiddleware'; @@ -200,6 +203,45 @@ describe('createRPCMethodTrackingMiddleware', () => { result_type: BlockaidResultType.Malicious, reason: BlockaidReason.maliciousDomain, securityAlertId: 1, + description: 'some_description', + }, + }; + + const res = { + error: null, + }; + const { next } = getNext(); + const handler = createHandler(); + await handler(req, res, next); + + expect(trackEventSpy).toHaveBeenCalledTimes(1); + expect(trackEventSpy.mock.calls[0][0]).toMatchObject({ + category: MetaMetricsEventCategory.InpageProvider, + event: MetaMetricsEventName.SignatureRequested, + properties: { + signature_type: MESSAGE_TYPE.PERSONAL_SIGN, + security_alert_response: BlockaidResultType.Malicious, + security_alert_reason: BlockaidReason.maliciousDomain, + security_alert_description: 'some_description', + }, + referrer: { url: 'some.dapp' }, + }); + }); + + it(`should track a ${MetaMetricsEventName.SignatureRequested} event for personal sign`, async () => { + const req = { + id: MOCK_ID, + method: MESSAGE_TYPE.PERSONAL_SIGN, + origin: 'some.dapp', + params: [ + { data: 'some-data' }, + '0xb60e8dd61c5d32be8058bb8eb970870f07233155', + ], + securityAlertResponse: { + result_type: BlockaidResultType.Malicious, + reason: BlockaidReason.maliciousDomain, + securityAlertId: 1, + description: 'some_description', }, }; @@ -218,6 +260,7 @@ describe('createRPCMethodTrackingMiddleware', () => { signature_type: MESSAGE_TYPE.PERSONAL_SIGN, security_alert_response: BlockaidResultType.Malicious, security_alert_reason: BlockaidReason.maliciousDomain, + security_alert_description: 'some_description', }, referrer: { url: 'some.dapp' }, }); @@ -262,11 +305,12 @@ describe('createRPCMethodTrackingMiddleware', () => { }); }); - it(`should track a ${MetaMetricsEventName.SignatureApproved} event if the user approves`, async () => { + it(`should track a ${MetaMetricsEventName.SignatureApproved} if the user approves`, async () => { const req = { id: MOCK_ID, method: MESSAGE_TYPE.ETH_SIGN_TYPED_DATA_V4, origin: 'some.dapp', + params: [undefined, permitSignatureMsg.msgParams.data], }; const res = { @@ -283,11 +327,52 @@ describe('createRPCMethodTrackingMiddleware', () => { properties: { signature_type: MESSAGE_TYPE.ETH_SIGN_TYPED_DATA_V4, }, + sensitiveProperties: { + eip712_verifyingContract: + '0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC', + eip712_domain_version: '1', + eip712_domain_name: 'MyToken', + }, + referrer: { url: 'some.dapp' }, + }); + }); + + it(`should track a ${MetaMetricsEventName.Rejected} with sensitive properties for internal errors`, async () => { + const req = { + id: MOCK_ID, + method: MESSAGE_TYPE.ETH_SIGN_TYPED_DATA_V4, + origin: 'some.dapp', + params: [undefined, permitSignatureMsg.msgParams.data], + }; + + const res = { + error: { + code: errorCodes.rpc.internal, + message: 'Request rejected by user or snap.', + }, + }; + const { next, executeMiddlewareStack } = getNext(); + const handler = createHandler(); + await handler(req, res, next); + await executeMiddlewareStack(); + expect(trackEventSpy).toHaveBeenCalledTimes(2); + expect(trackEventSpy.mock.calls[1][0]).toMatchObject({ + category: MetaMetricsEventCategory.InpageProvider, + event: MetaMetricsEventName.SignatureRejected, + properties: { + signature_type: MESSAGE_TYPE.ETH_SIGN_TYPED_DATA_V4, + }, + sensitiveProperties: { + eip712_verifyingContract: + '0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC', + eip712_domain_version: '1', + eip712_domain_name: 'MyToken', + }, referrer: { url: 'some.dapp' }, }); }); - it(`should track a ${MetaMetricsEventName.SignatureRejected} event if the user approves`, async () => { + it(`should track a ${MetaMetricsEventName.SignatureRejected} event if the user rejects`, async () => { const req = { id: MOCK_ID, method: MESSAGE_TYPE.PERSONAL_SIGN, @@ -570,6 +655,37 @@ describe('createRPCMethodTrackingMiddleware', () => { }); }); + it('should track when message is not SIWE if detected', async () => { + const req = { + id: MOCK_ID, + method: MESSAGE_TYPE.PERSONAL_SIGN, + origin: 'some.dapp', + }; + const res = { + error: null, + }; + const { next, executeMiddlewareStack } = getNext(); + const handler = createHandler(); + + detectSIWE.mockImplementation(() => { + return { isSIWEMessage: false }; + }); + + await handler(req, res, next); + await executeMiddlewareStack(); + + expect(trackEventSpy).toHaveBeenCalledTimes(2); + + expect(trackEventSpy.mock.calls[1][0]).toMatchObject({ + category: MetaMetricsEventCategory.InpageProvider, + event: MetaMetricsEventName.SignatureApproved, + properties: { + signature_type: MESSAGE_TYPE.PERSONAL_SIGN, + }, + referrer: { url: 'some.dapp' }, + }); + }); + it('should track typed-sign permit message if detected', async () => { const req = { id: MOCK_ID, @@ -600,12 +716,102 @@ describe('createRPCMethodTrackingMiddleware', () => { }); }); + it('should track typed-sign message if detected', async () => { + const req = { + id: MOCK_ID, + method: MESSAGE_TYPE.ETH_SIGN_TYPED_DATA_V3, + origin: 'some.dapp', + }; + const res = { + error: null, + }; + const { next, executeMiddlewareStack } = getNext(); + const handler = createHandler(); + + await handler(req, res, next); + await executeMiddlewareStack(); + + expect(trackEventSpy).toHaveBeenCalledTimes(2); + + expect(trackEventSpy.mock.calls[1][0]).toMatchObject({ + category: MetaMetricsEventCategory.InpageProvider, + event: MetaMetricsEventName.SignatureApproved, + properties: { + signature_type: MESSAGE_TYPE.ETH_SIGN_TYPED_DATA_V3, + }, + referrer: { url: 'some.dapp' }, + }); + }); + + it(`should track typed-sign order message if detected`, async () => { + const req = { + id: MOCK_ID, + method: MESSAGE_TYPE.ETH_SIGN_TYPED_DATA_V4, + origin: 'some.dapp', + params: [undefined, orderSignatureMsg.msgParams.data], + }; + + const res = { + error: null, + }; + const { next, executeMiddlewareStack } = getNext(); + const handler = createHandler(); + await handler(req, res, next); + await executeMiddlewareStack(); + expect(trackEventSpy).toHaveBeenCalledTimes(2); + expect(trackEventSpy.mock.calls[1][0]).toMatchObject({ + category: MetaMetricsEventCategory.InpageProvider, + event: MetaMetricsEventName.SignatureApproved, + properties: { + signature_type: MESSAGE_TYPE.ETH_SIGN_TYPED_DATA_V4, + ui_customizations: [MetaMetricsEventUiCustomization.Order], + }, + referrer: { url: 'some.dapp' }, + }); + }); + + it(`should not track permit message if primary type is unknown`, async () => { + const params = JSON.parse(orderSignatureMsg.msgParams.data); + params.primaryType = 'Unknown'; + + const req = { + id: MOCK_ID, + method: MESSAGE_TYPE.ETH_SIGN_TYPED_DATA_V4, + origin: 'some.dapp', + params: [undefined, JSON.stringify(params)], + }; + + const res = { + error: null, + }; + const { next, executeMiddlewareStack } = getNext(); + const handler = createHandler(); + await handler(req, res, next); + await executeMiddlewareStack(); + + expect(trackEventSpy).toHaveBeenCalledTimes(2); + expect(trackEventSpy.mock.calls[1][0]).toMatchObject({ + category: MetaMetricsEventCategory.InpageProvider, + event: MetaMetricsEventName.SignatureApproved, + properties: { + signature_type: MESSAGE_TYPE.ETH_SIGN_TYPED_DATA_V4, + eip712_primary_type: 'Unknown', + }, + referrer: { url: 'some.dapp' }, + }); + + expect(trackEventSpy.mock.calls[1][0].properties).not.toHaveProperty( + 'ui_customizations', + ); + }); + describe('when request is flagged as safe by security provider', () => { it(`should immediately track a ${MetaMetricsEventName.SignatureRequested} event`, async () => { const req = { id: MOCK_ID, method: MESSAGE_TYPE.ETH_SIGN_TYPED_DATA_V4, origin: 'some.dapp', + params: [undefined, permitSignatureMsg.msgParams.data], }; const res = { error: null, @@ -622,6 +828,12 @@ describe('createRPCMethodTrackingMiddleware', () => { properties: { signature_type: MESSAGE_TYPE.ETH_SIGN_TYPED_DATA_V4, }, + sensitiveProperties: { + eip712_verifyingContract: + '0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC', + eip712_domain_version: '1', + eip712_domain_name: 'MyToken', + }, referrer: { url: 'some.dapp' }, }); }); diff --git a/shared/constants/metametrics.ts b/shared/constants/metametrics.ts index 243b2409f1af..b69cc2c03cdc 100644 --- a/shared/constants/metametrics.ts +++ b/shared/constants/metametrics.ts @@ -599,6 +599,9 @@ export enum MetaMetricsEventName { SignatureFailed = 'Signature Failed', SignatureRejected = 'Signature Rejected', SignatureRequested = 'Signature Requested', + SignatureApprovedAnon = 'Signature Approved Anon', + SignatureRejectedAnon = 'Signature Rejected Anon', + SignatureRequestedAnon = 'Signature Requested Anon', SimulationFails = 'Simulation Fails', SimulationIncompleteAssetDisplayed = 'Incomplete Asset Displayed', SrpRevealStarted = 'Reveal SRP Initiated', diff --git a/sonar-project.properties b/sonar-project.properties index 1475b64f4acb..31d4a98b41e7 100644 --- a/sonar-project.properties +++ b/sonar-project.properties @@ -3,7 +3,7 @@ sonar.organization=consensys # Source sonar.sources=app,development,offscreen,shared,test,types,ui -sonar.exclusions=**/*.test.**,**/*.spec.**,app/images +sonar.exclusions=**/*.test.**,**/*.spec.**,app/images,test/** # Tests sonar.tests=app,development,offscreen,shared,test,types,ui diff --git a/test/e2e/tests/confirmations/helpers.ts b/test/e2e/tests/confirmations/helpers.ts index 6cfc559cf818..7fe5ef9da87b 100644 --- a/test/e2e/tests/confirmations/helpers.ts +++ b/test/e2e/tests/confirmations/helpers.ts @@ -43,62 +43,50 @@ export function withRedesignConfirmationFixtures( ); } -export async function mockSignatureApproved(mockServer: Mockttp) { +async function createMockSegmentEvent(mockServer: Mockttp, eventName: string) { + return await mockServer + .forPost('https://api.segment.io/v1/batch') + .withJsonBodyIncluding({ + batch: [{ type: 'track', event: eventName }], + }) + .thenCallback(() => ({ + statusCode: 200, + })); +} + +export async function mockSignatureApproved( + mockServer: Mockttp, + withAnonEvents = false, +) { + const anonEvents = withAnonEvents + ? [ + await createMockSegmentEvent(mockServer, 'Signature Requested Anon'), + await createMockSegmentEvent(mockServer, 'Signature Approved Anon'), + ] + : []; + return [ - await mockServer - .forPost('https://api.segment.io/v1/batch') - .withJsonBodyIncluding({ - batch: [{ type: 'track', event: 'Signature Requested' }], - }) - .thenCallback(() => { - return { - statusCode: 200, - }; - }), - await mockServer - .forPost('https://api.segment.io/v1/batch') - .withJsonBodyIncluding({ - batch: [{ type: 'track', event: 'Signature Approved' }], - }) - .thenCallback(() => { - return { - statusCode: 200, - }; - }), - await mockServer - .forPost('https://api.segment.io/v1/batch') - .withJsonBodyIncluding({ - batch: [{ type: 'track', event: 'Account Details Opened' }], - }) - .thenCallback(() => { - return { - statusCode: 200, - }; - }), + await createMockSegmentEvent(mockServer, 'Signature Requested'), + await createMockSegmentEvent(mockServer, 'Account Details Opened'), + ...anonEvents, + await createMockSegmentEvent(mockServer, 'Signature Approved'), ]; } -export async function mockSignatureRejected(mockServer: Mockttp) { +export async function mockSignatureRejected( + mockServer: Mockttp, + withAnonEvents = false, +) { + const anonEvents = withAnonEvents + ? [ + await createMockSegmentEvent(mockServer, 'Signature Requested Anon'), + await createMockSegmentEvent(mockServer, 'Signature Rejected Anon'), + ] + : []; + return [ - await mockServer - .forPost('https://api.segment.io/v1/batch') - .withJsonBodyIncluding({ - batch: [{ type: 'track', event: 'Signature Requested' }], - }) - .thenCallback(() => { - return { - statusCode: 200, - }; - }), - await mockServer - .forPost('https://api.segment.io/v1/batch') - .withJsonBodyIncluding({ - batch: [{ type: 'track', event: 'Signature Rejected' }], - }) - .thenCallback(() => { - return { - statusCode: 200, - }; - }), + await createMockSegmentEvent(mockServer, 'Signature Requested'), + await createMockSegmentEvent(mockServer, 'Signature Rejected'), + ...anonEvents, ]; } diff --git a/test/e2e/tests/confirmations/signatures/sign-typed-data-v4.spec.ts b/test/e2e/tests/confirmations/signatures/sign-typed-data-v4.spec.ts index bcd6e3e17695..98311553d463 100644 --- a/test/e2e/tests/confirmations/signatures/sign-typed-data-v4.spec.ts +++ b/test/e2e/tests/confirmations/signatures/sign-typed-data-v4.spec.ts @@ -45,26 +45,30 @@ describe('Confirmation Signature - Sign Typed Data V4 @no-mmi', function (this: await copyAddressAndPasteWalletAddress(driver); await assertPastedAddress(driver); + + await assertInfoValues(driver); + await scrollAndConfirmAndAssertConfirm(driver); + await driver.delay(1000); + await assertAccountDetailsMetrics( driver, mockedEndpoints as MockedEndpoint[], 'eth_signTypedData_v4', ); - await assertInfoValues(driver); - await scrollAndConfirmAndAssertConfirm(driver); - await driver.delay(1000); - await assertSignatureConfirmedMetrics({ driver, mockedEndpoints: mockedEndpoints as MockedEndpoint[], signatureType: 'eth_signTypedData_v4', primaryType: 'Mail', + withAnonEvents: true, }); await assertVerifiedResults(driver, publicAddress); }, - mockSignatureApproved, + async (mockServer) => { + return await mockSignatureApproved(mockServer, true); + }, ); }); @@ -102,7 +106,9 @@ describe('Confirmation Signature - Sign Typed Data V4 @no-mmi', function (this: }); assert.ok(rejectionResult); }, - mockSignatureRejected, + async (mockServer) => { + return await mockSignatureRejected(mockServer, true); + }, ); }); }); diff --git a/test/e2e/tests/confirmations/signatures/signature-helpers.ts b/test/e2e/tests/confirmations/signatures/signature-helpers.ts index 6bb8f4ee0b75..029499f230ec 100644 --- a/test/e2e/tests/confirmations/signatures/signature-helpers.ts +++ b/test/e2e/tests/confirmations/signatures/signature-helpers.ts @@ -28,6 +28,7 @@ type AssertSignatureMetricsOptions = { uiCustomizations?: string[]; location?: string; expectedProps?: Record; + withAnonEvents?: boolean; }; type SignatureEventProperty = { @@ -44,6 +45,12 @@ type SignatureEventProperty = { location?: string; }; +const signatureAnonProperties = { + eip712_verifyingContract: '0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC', + eip712_domain_version: '1', + eip712_domain_name: 'Ether Mail', +}; + /** * Generates expected signature metric properties * @@ -79,16 +86,20 @@ function assertSignatureRequestedMetrics( // eslint-disable-next-line @typescript-eslint/no-explicit-any events: any[], signatureEventProperty: SignatureEventProperty, + withAnonEvents = false, ) { - assert.equal(events[0].event, 'Signature Requested'); - assert.deepStrictEqual( - events[0].properties, - { + assertEventPropertiesMatch(events, 'Signature Requested', { + ...signatureEventProperty, + security_alert_reason: 'NotApplicable', + }); + + if (withAnonEvents) { + assertEventPropertiesMatch(events, 'Signature Requested Anon', { ...signatureEventProperty, security_alert_reason: 'NotApplicable', - }, - 'Signature request event details do not match', - ); + ...signatureAnonProperties, + }); + } } export async function assertSignatureConfirmedMetrics({ @@ -97,6 +108,7 @@ export async function assertSignatureConfirmedMetrics({ signatureType, primaryType = '', uiCustomizations = ['redesigned_confirmation'], + withAnonEvents = false, }: AssertSignatureMetricsOptions) { const events = await getEventPayloads(driver, mockedEndpoints); const signatureEventProperty = getSignatureEventProperty( @@ -105,13 +117,24 @@ export async function assertSignatureConfirmedMetrics({ uiCustomizations, ); - assertSignatureRequestedMetrics(events, signatureEventProperty); - assert.equal(events[1].event, 'Signature Approved'); - assert.deepStrictEqual( - events[1].properties, + assertSignatureRequestedMetrics( + events, + signatureEventProperty, + withAnonEvents, + ); + + assertEventPropertiesMatch( + events, + 'Signature Approved', signatureEventProperty, - 'Signature Accepted event properties do not match', ); + + if (withAnonEvents) { + assertEventPropertiesMatch(events, 'Signature Approved Anon', { + ...signatureEventProperty, + ...signatureAnonProperties, + }); + } } export async function assertSignatureRejectedMetrics({ @@ -122,6 +145,7 @@ export async function assertSignatureRejectedMetrics({ uiCustomizations = ['redesigned_confirmation'], location, expectedProps = {}, + withAnonEvents = false, }: AssertSignatureMetricsOptions) { const events = await getEventPayloads(driver, mockedEndpoints); const signatureEventProperty = getSignatureEventProperty( @@ -130,17 +154,24 @@ export async function assertSignatureRejectedMetrics({ uiCustomizations, ); - assertSignatureRequestedMetrics(events, signatureEventProperty); - assert.equal(events[1].event, 'Signature Rejected'); - assert.deepStrictEqual( - events[1].properties, - { - ...signatureEventProperty, - location, - ...expectedProps, - }, - 'Signature Rejected event properties do not match', + assertSignatureRequestedMetrics( + events, + signatureEventProperty, + withAnonEvents, ); + + assertEventPropertiesMatch(events, 'Signature Rejected', { + ...signatureEventProperty, + location, + ...expectedProps, + }); + + if (withAnonEvents) { + assertEventPropertiesMatch(events, 'Signature Rejected Anon', { + ...signatureEventProperty, + ...signatureAnonProperties, + }); + } } export async function assertAccountDetailsMetrics( @@ -150,19 +181,29 @@ export async function assertAccountDetailsMetrics( ) { const events = await getEventPayloads(driver, mockedEndpoints); - assert.equal(events[1].event, 'Account Details Opened'); + assertEventPropertiesMatch(events, 'Account Details Opened', { + action: 'Confirm Screen', + location: 'signature_confirmation', + signature_type: type, + category: 'Confirmations', + locale: 'en', + chain_id: '0x539', + environment_type: 'notification', + }); +} + +function assertEventPropertiesMatch( + // eslint-disable-next-line @typescript-eslint/no-explicit-any + events: any[], + eventName: string, + expectedProperties: object, +) { + const event = events.find((e) => e.event === eventName); + assert(event, `${eventName} event not found`); assert.deepStrictEqual( - events[1].properties, - { - action: 'Confirm Screen', - location: 'signature_confirmation', - signature_type: type, - category: 'Confirmations', - locale: 'en', - chain_id: '0x539', - environment_type: 'notification', - }, - 'Account Details Metrics do not match', + event.properties, + expectedProperties, + `${eventName} event properties do not match`, ); }