diff --git a/packages/snap/src/core/handlers/onCronjob/cronjobs/refreshConfirmationEstimation.tsx b/packages/snap/src/core/handlers/onCronjob/cronjobs/refreshConfirmationEstimation.tsx index b52f10022..dc394a37d 100644 --- a/packages/snap/src/core/handlers/onCronjob/cronjobs/refreshConfirmationEstimation.tsx +++ b/packages/snap/src/core/handlers/onCronjob/cronjobs/refreshConfirmationEstimation.tsx @@ -72,6 +72,7 @@ export const refreshConfirmationEstimation: OnCronjobHandler = async () => { transaction: interfaceContext.transaction, scope: interfaceContext.scope, origin: interfaceContext.origin, + account: interfaceContext.account, }); const updatedInterfaceContextFinal = diff --git a/packages/snap/src/core/services/analytics/AnalyticsService.test.ts b/packages/snap/src/core/services/analytics/AnalyticsService.test.ts index 44368788a..4d25430b2 100644 --- a/packages/snap/src/core/services/analytics/AnalyticsService.test.ts +++ b/packages/snap/src/core/services/analytics/AnalyticsService.test.ts @@ -5,6 +5,7 @@ import type { Transaction } from '@metamask/keyring-api'; import { Network } from '../../constants/solana'; import { MOCK_SOLANA_KEYRING_ACCOUNT_0 } from '../../test/mocks/solana-keyring-accounts'; import logger from '../../utils/logger'; +import { ScanStatus, SecurityAlertResponse } from '../transaction-scan/types'; import { AnalyticsService } from './AnalyticsService'; const mockSnapRequest = jest.fn(); @@ -293,6 +294,121 @@ describe('AnalyticsService', () => { }); }); + describe('trackEventSecurityAlertDetected', () => { + it('tracks security alert detected event', async () => { + const securityAlertResponse = SecurityAlertResponse.Warning; + const securityAlertReason = 'transfer_farming'; + const securityAlertDescription = + "Substantial transfer of the account's assets to untrusted entities"; + + await analyticsService.trackEventSecurityAlertDetected( + mockAccount, + mockBase64Transaction, + mockOrigin, + mockScope, + securityAlertResponse, + securityAlertReason, + securityAlertDescription, + ); + + expect(loggerSpy).toHaveBeenCalledWith( + '[📣 AnalyticsService] Tracking event security alert detected', + ); + + expect(mockSnapRequest).toHaveBeenCalledWith({ + method: 'snap_trackEvent', + params: { + event: { + event: 'Security Alert Detected', + properties: { + message: 'Snap security alert detected', + origin: mockOrigin, + account_id: mockAccount.id, + account_address: mockAccount.address, + account_type: mockAccount.type, + chain_id: mockScope, + security_alert_response: securityAlertResponse, + security_alert_reason: securityAlertReason, + security_alert_description: securityAlertDescription, + }, + }, + }, + }); + }); + }); + + describe('trackEventSecurityScanCompleted', () => { + it('tracks security scan completed event with alerts detected', async () => { + const scanStatus = ScanStatus.SUCCESS; + const hasSecurityAlerts = true; + + await analyticsService.trackEventSecurityScanCompleted( + mockAccount, + mockBase64Transaction, + mockOrigin, + mockScope, + scanStatus, + hasSecurityAlerts, + ); + + expect(loggerSpy).toHaveBeenCalledWith( + '[📣 AnalyticsService] Tracking event security scan completed', + ); + + expect(mockSnapRequest).toHaveBeenCalledWith({ + method: 'snap_trackEvent', + params: { + event: { + event: 'Security Scan Completed', + properties: { + message: 'Snap security scan completed', + origin: mockOrigin, + account_id: mockAccount.id, + account_address: mockAccount.address, + account_type: mockAccount.type, + chain_id: mockScope, + scan_status: scanStatus, + has_security_alerts: hasSecurityAlerts, + }, + }, + }, + }); + }); + + it('tracks security scan completed event without alerts', async () => { + const scanStatus = ScanStatus.SUCCESS; + const hasSecurityAlerts = false; + + await analyticsService.trackEventSecurityScanCompleted( + mockAccount, + mockBase64Transaction, + mockOrigin, + mockScope, + scanStatus, + hasSecurityAlerts, + ); + + expect(mockSnapRequest).toHaveBeenCalledWith({ + method: 'snap_trackEvent', + params: { + event: { + event: 'Security Scan Completed', + properties: { + message: 'Snap security scan completed', + origin: mockOrigin, + account_id: mockAccount.id, + account_address: mockAccount.address, + account_type: mockAccount.type, + chain_id: mockScope, + scan_status: scanStatus, + has_security_alerts: hasSecurityAlerts, + }, + }, + }, + }); + }); + }); + describe('error handling', () => { it('handles snap.request errors gracefully', async () => { const error = new Error('Snap request failed'); diff --git a/packages/snap/src/core/services/analytics/AnalyticsService.ts b/packages/snap/src/core/services/analytics/AnalyticsService.ts index 053038d07..1f1a69281 100644 --- a/packages/snap/src/core/services/analytics/AnalyticsService.ts +++ b/packages/snap/src/core/services/analytics/AnalyticsService.ts @@ -3,9 +3,13 @@ import type { Transaction } from '@metamask/keyring-api'; import { assert } from '@metamask/superstruct'; import type { SolanaKeyringAccount } from '../../../entities'; -import type { TransactionMetadata } from '../../constants/solana'; +import type { Network, TransactionMetadata } from '../../constants/solana'; import logger from '../../utils/logger'; import { Base64Struct } from '../../validation/structs'; +import type { + ScanStatus, + SecurityAlertResponse, +} from '../transaction-scan/types'; /** * Service for tracking events related to transactions. @@ -160,4 +164,74 @@ export class AnalyticsService { }, }); } + + async trackEventSecurityAlertDetected( + account: SolanaKeyringAccount, + base64EncodedTransaction: string, + origin: string, + scope: Network, + securityAlertResponse: SecurityAlertResponse, + securityAlertReason: string | null, + securityAlertDescription: string, + ): Promise { + this.#logger.log( + `[📣 AnalyticsService] Tracking event security alert detected`, + ); + + assert(base64EncodedTransaction, Base64Struct); + + await snap.request({ + method: 'snap_trackEvent', + params: { + event: { + event: 'Security Alert Detected', + properties: { + message: 'Snap security alert detected', + origin, + account_id: account.id, + account_address: account.address, + account_type: account.type, + chain_id: scope, + security_alert_response: securityAlertResponse, + security_alert_reason: securityAlertReason, + security_alert_description: securityAlertDescription, + }, + }, + }, + }); + } + + async trackEventSecurityScanCompleted( + account: SolanaKeyringAccount, + base64EncodedTransaction: string, + origin: string, + scope: Network, + scanStatus: ScanStatus, + hasSecurityAlerts: boolean, + ): Promise { + this.#logger.log( + `[📣 AnalyticsService] Tracking event security scan completed`, + ); + + assert(base64EncodedTransaction, Base64Struct); + + await snap.request({ + method: 'snap_trackEvent', + params: { + event: { + event: 'Security Scan Completed', + properties: { + message: 'Snap security scan completed', + origin, + account_id: account.id, + account_address: account.address, + account_type: account.type, + chain_id: scope, + scan_status: scanStatus, + has_security_alerts: hasSecurityAlerts, + }, + }, + }, + }); + } } diff --git a/packages/snap/src/core/services/transaction-scan/TransactionScan.test.ts b/packages/snap/src/core/services/transaction-scan/TransactionScan.test.ts index eb994f930..94e326716 100644 --- a/packages/snap/src/core/services/transaction-scan/TransactionScan.test.ts +++ b/packages/snap/src/core/services/transaction-scan/TransactionScan.test.ts @@ -1,37 +1,48 @@ +/* eslint-disable @typescript-eslint/naming-convention */ import type { SecurityAlertsApiClient } from '../../clients/security-alerts-api/SecurityAlertsApiClient'; import type { SecurityAlertSimulationValidationResponse } from '../../clients/security-alerts-api/types'; import { Network } from '../../constants/solana'; +import { MOCK_SOLANA_KEYRING_ACCOUNT_0 } from '../../test/mocks/solana-keyring-accounts'; import type { ILogger } from '../../utils/logger'; +import type { AnalyticsService } from '../analytics/AnalyticsService'; import type { TokenMetadataService } from '../token-metadata/TokenMetadata'; import { TransactionScanService } from './TransactionScan'; +import { ScanStatus, SecurityAlertResponse } from './types'; describe('TransactionScan', () => { - describe('scanTransaction', () => { - let transactionScanService: TransactionScanService; - let mockSecurityAlertsApiClient: SecurityAlertsApiClient; - let mockLogger: ILogger; - let mockTokenMetadataService: TokenMetadataService; - - beforeEach(() => { - mockTokenMetadataService = { - generateImageComponent: jest.fn().mockResolvedValue(null), - } as unknown as TokenMetadataService; - - mockSecurityAlertsApiClient = { - scanTransactions: jest.fn().mockResolvedValue({}), - } as unknown as SecurityAlertsApiClient; - - mockLogger = { - error: jest.fn(), - } as unknown as ILogger; - - transactionScanService = new TransactionScanService( - mockSecurityAlertsApiClient, - mockTokenMetadataService, - mockLogger, - ); - }); + let transactionScanService: TransactionScanService; + let mockSecurityAlertsApiClient: SecurityAlertsApiClient; + let mockLogger: ILogger; + let mockTokenMetadataService: TokenMetadataService; + let mockAnalyticsService: AnalyticsService; + + beforeEach(() => { + mockTokenMetadataService = { + generateImageComponent: jest.fn().mockResolvedValue(null), + } as unknown as TokenMetadataService; + + mockSecurityAlertsApiClient = { + scanTransactions: jest.fn().mockResolvedValue({}), + } as unknown as SecurityAlertsApiClient; + + mockLogger = { + error: jest.fn(), + } as unknown as ILogger; + mockAnalyticsService = { + trackEventSecurityScanCompleted: jest.fn().mockResolvedValue(undefined), + trackEventSecurityAlertDetected: jest.fn().mockResolvedValue(undefined), + } as unknown as AnalyticsService; + + transactionScanService = new TransactionScanService( + mockSecurityAlertsApiClient, + mockTokenMetadataService, + mockAnalyticsService, + mockLogger, + ); + }); + + describe('scanTransaction', () => { it('scans a transaction', async () => { jest .spyOn(mockSecurityAlertsApiClient, 'scanTransactions') @@ -67,5 +78,247 @@ describe('TransactionScan', () => { expect(result).toBeNull(); }); + + it('tracks security scan completion when account is provided', async () => { + const mockAccount = MOCK_SOLANA_KEYRING_ACCOUNT_0; + + jest + .spyOn(mockSecurityAlertsApiClient, 'scanTransactions') + .mockResolvedValue({ + status: ScanStatus.SUCCESS, + encoding: 'base58', + error: null, + error_details: null, + request_id: 'test-request-id', + result: { + validation: { + result_type: SecurityAlertResponse.Benign, + reason: '', + features: [], + }, + simulation: { + account_summary: { + account_assets_diff: [], + account_delegations: [], + account_ownerships_diff: [], + total_usd_diff: { in: 0, out: 0, total: 0 }, + }, + }, + }, + } as unknown as SecurityAlertSimulationValidationResponse); + + await transactionScanService.scanTransaction({ + method: 'method', + accountAddress: 'accountAddress', + transaction: 'transaction', + scope: Network.Mainnet, + origin: 'https://metamask.io', + account: mockAccount, + }); + + // hasSecurityAlerts = false for Benign response + expect( + mockAnalyticsService.trackEventSecurityScanCompleted, + ).toHaveBeenCalledWith( + mockAccount, + 'transaction', + 'https://metamask.io', + Network.Mainnet, + ScanStatus.SUCCESS, + false, + ); + }); + + it('tracks security alert when malicious transaction is detected', async () => { + const mockAccount = MOCK_SOLANA_KEYRING_ACCOUNT_0; + + jest + .spyOn(mockSecurityAlertsApiClient, 'scanTransactions') + .mockResolvedValue({ + status: ScanStatus.SUCCESS, + encoding: 'base58', + error: null, + error_details: null, + request_id: 'test-request-id', + result: { + validation: { + result_type: SecurityAlertResponse.Warning, + reason: 'transfer_farming', + features: [], + }, + simulation: { + account_summary: { + account_assets_diff: [], + account_delegations: [], + account_ownerships_diff: [], + total_usd_diff: { in: 0, out: 0, total: 0 }, + }, + }, + }, + } as unknown as SecurityAlertSimulationValidationResponse); + + await transactionScanService.scanTransaction({ + method: 'method', + accountAddress: 'accountAddress', + transaction: 'transaction', + scope: Network.Mainnet, + origin: 'https://metamask.io', + account: mockAccount, + }); + + // hasSecurityAlerts = true for Warning response + expect( + mockAnalyticsService.trackEventSecurityScanCompleted, + ).toHaveBeenCalledWith( + mockAccount, + 'transaction', + 'https://metamask.io', + Network.Mainnet, + ScanStatus.SUCCESS, + true, + ); + + expect( + mockAnalyticsService.trackEventSecurityAlertDetected, + ).toHaveBeenCalledWith( + mockAccount, + 'transaction', + 'https://metamask.io', + Network.Mainnet, + SecurityAlertResponse.Warning, + 'transfer_farming', + "Substantial transfer of the account's assets to untrusted entities", + ); + }); + + it('tracks error when scan fails and account is provided', async () => { + const mockAccount = MOCK_SOLANA_KEYRING_ACCOUNT_0; + + jest + .spyOn(mockSecurityAlertsApiClient, 'scanTransactions') + .mockRejectedValue(new Error('Scan failed')); + + await transactionScanService.scanTransaction({ + method: 'method', + accountAddress: 'accountAddress', + transaction: 'transaction', + scope: Network.Mainnet, + origin: 'https://metamask.io', + account: mockAccount, + }); + + expect( + mockAnalyticsService.trackEventSecurityScanCompleted, + ).toHaveBeenCalledWith( + mockAccount, + 'transaction', + 'https://metamask.io', + Network.Mainnet, + ScanStatus.ERROR, + false, + ); + }); + }); + + describe('getSecurityAlertDescription', () => { + it('returns correct description for known reasons', async () => { + const mockAccount = MOCK_SOLANA_KEYRING_ACCOUNT_0; + + jest + .spyOn(mockSecurityAlertsApiClient, 'scanTransactions') + .mockResolvedValue({ + status: ScanStatus.SUCCESS, + encoding: 'base58', + error: null, + error_details: null, + request_id: 'test-request-id', + result: { + validation: { + result_type: SecurityAlertResponse.Warning, + reason: 'transfer_farming', + features: [], + }, + simulation: { + account_summary: { + account_assets_diff: [], + account_delegations: [], + account_ownerships_diff: [], + total_usd_diff: { in: 0, out: 0, total: 0 }, + }, + }, + }, + } as unknown as SecurityAlertSimulationValidationResponse); + + await transactionScanService.scanTransaction({ + method: 'method', + accountAddress: 'accountAddress', + transaction: 'transaction', + scope: Network.Mainnet, + origin: 'https://metamask.io', + account: mockAccount, + }); + + expect( + mockAnalyticsService.trackEventSecurityAlertDetected, + ).toHaveBeenCalledWith( + mockAccount, + 'transaction', + 'https://metamask.io', + Network.Mainnet, + SecurityAlertResponse.Warning, + 'transfer_farming', + "Substantial transfer of the account's assets to untrusted entities", + ); + }); + + it('returns fallback description for unknown reasons', async () => { + const mockAccount = MOCK_SOLANA_KEYRING_ACCOUNT_0; + + jest + .spyOn(mockSecurityAlertsApiClient, 'scanTransactions') + .mockResolvedValue({ + status: ScanStatus.SUCCESS, + encoding: 'base58', + error: null, + error_details: null, + request_id: 'test-request-id', + result: { + validation: { + result_type: SecurityAlertResponse.Warning, + reason: 'unknown_reason', + features: [], + }, + simulation: { + account_summary: { + account_assets_diff: [], + account_delegations: [], + account_ownerships_diff: [], + total_usd_diff: { in: 0, out: 0, total: 0 }, + }, + }, + }, + } as unknown as SecurityAlertSimulationValidationResponse); + + await transactionScanService.scanTransaction({ + method: 'method', + accountAddress: 'accountAddress', + transaction: 'transaction', + scope: Network.Mainnet, + origin: 'https://metamask.io', + account: mockAccount, + }); + + expect( + mockAnalyticsService.trackEventSecurityAlertDetected, + ).toHaveBeenCalledWith( + mockAccount, + 'transaction', + 'https://metamask.io', + Network.Mainnet, + SecurityAlertResponse.Warning, + 'unknown_reason', + 'Security alert: unknown_reason', + ); + }); }); }); diff --git a/packages/snap/src/core/services/transaction-scan/TransactionScan.ts b/packages/snap/src/core/services/transaction-scan/TransactionScan.ts index f126b9119..ed000a792 100644 --- a/packages/snap/src/core/services/transaction-scan/TransactionScan.ts +++ b/packages/snap/src/core/services/transaction-scan/TransactionScan.ts @@ -1,9 +1,13 @@ +/* eslint-disable @typescript-eslint/naming-convention */ +import type { SolanaKeyringAccount } from '../../../entities'; import type { SecurityAlertsApiClient } from '../../clients/security-alerts-api/SecurityAlertsApiClient'; import type { SecurityAlertSimulationValidationResponse } from '../../clients/security-alerts-api/types'; import type { Network } from '../../constants/solana'; import type { ILogger } from '../../utils/logger'; +import type { AnalyticsService } from '../analytics/AnalyticsService'; import type { TokenMetadataService } from '../token-metadata/TokenMetadata'; -import type { TransactionScanResult } from './types'; +import type { TransactionScanResult, TransactionScanValidation } from './types'; +import { ScanStatus, SecurityAlertResponse } from './types'; const ICON_SIZE = 16; @@ -14,13 +18,17 @@ export class TransactionScanService { readonly #tokenMetadataService: TokenMetadataService; + readonly #analyticsService: AnalyticsService; + constructor( securityAlertsApiClient: SecurityAlertsApiClient, tokenMetadataService: TokenMetadataService, + analyticsService: AnalyticsService, logger: ILogger, ) { this.#securityAlertsApiClient = securityAlertsApiClient; this.#tokenMetadataService = tokenMetadataService; + this.#analyticsService = analyticsService; this.#logger = logger; } @@ -33,6 +41,7 @@ export class TransactionScanService { * @param params.scope - The scope of the transaction. * @param params.origin - The origin of the transaction. * @param params.options - The options for the scan. + * @param params.account - The account for analytics tracking. * @returns The result of the scan. */ async scanTransaction({ @@ -42,6 +51,7 @@ export class TransactionScanService { scope, origin, options = ['simulation', 'validation'], + account, }: { method: string; accountAddress: string; @@ -49,6 +59,7 @@ export class TransactionScanService { scope: Network; origin: string; options?: string[]; + account?: SolanaKeyringAccount; }): Promise { try { const result = await this.#securityAlertsApiClient.scanTransactions({ @@ -62,6 +73,75 @@ export class TransactionScanService { const scan = this.#mapScan(result); + if (!scan?.status) { + this.#logger.warn( + 'Invalid scan result received from security alerts API', + ); + + if (account) { + await this.#analyticsService.trackEventSecurityScanCompleted( + account, + transaction, + origin, + scope, + ScanStatus.ERROR, + false, + ); + } + + return null; + } + + // The security scan is completed + if (account) { + const isValidScanStatus = Object.values(ScanStatus).includes( + scan.status as ScanStatus, + ); + const scanStatus = isValidScanStatus + ? (scan.status as ScanStatus) + : ScanStatus.ERROR; + + const hasSecurityAlert = Boolean( + scan.validation?.type && + scan.validation.type !== SecurityAlertResponse.Benign, + ); + + const analyticsPromises = [ + this.#analyticsService.trackEventSecurityScanCompleted( + account, + transaction, + origin, + scope, + scanStatus, + hasSecurityAlert, + ), + ]; + + if (hasSecurityAlert) { + const isValidSecurityAlertType = Object.values( + SecurityAlertResponse, + ).includes(scan.validation.type as SecurityAlertResponse); + const securityAlertType = isValidSecurityAlertType + ? (scan.validation.type as SecurityAlertResponse) + : SecurityAlertResponse.Warning; + + analyticsPromises.push( + this.#analyticsService.trackEventSecurityAlertDetected( + account, + transaction, + origin, + scope, + securityAlertType, + scan.validation.reason ?? 'unknown', + this.#getSecurityAlertDescription(scan.validation), + ), + ); + } + + // Run all analytics calls in parallel + await Promise.all(analyticsPromises); + } + if (!scan?.estimatedChanges?.assets) { return null; } @@ -94,15 +174,70 @@ export class TransactionScanService { return updatedScan; } catch (error) { this.#logger.error(error); + + if (account) { + await this.#analyticsService.trackEventSecurityScanCompleted( + account, + transaction, + origin, + scope, + ScanStatus.ERROR, + false, + ); + } + return null; } } + #getSecurityAlertDescription(validation: TransactionScanValidation): string { + if (!validation?.reason) { + return 'Security alert: Unknown reason'; + } + + // Reference: https://docs.blockaid.io/reference/response-reference-solana + const reasonDescriptions: Record = { + unfair_trade: + "Unfair trade of assets, without adequate compensation to the owner's account", + transfer_farming: + "Substantial transfer of the account's assets to untrusted entities", + writable_accounts_farming: + 'Transaction exposes unused writable account, can be utilized in BIT-FLIP attacks patterns', + native_ownership_change: + 'The account transferred ownership of its native SOL to untrusted entities', + spl_token_ownership_change: + 'The account transferred ownership of its SPL tokens to untrusted entities', + exposure_farming: + 'The account delegates ownership, thereby exposing its assets to untrusted spenders', + known_attacker: + "A known attacker's account is involved in the transaction", + invalid_signature: + 'One of the transactions provided contains non valid signatures, that can lead misleading simulation results', + honeypot: + 'The account invests funds in a token that is part of an orchestrated honeypot scheme', + other: + 'The transaction was marked as malicious for other reason, further details would be described in features field', + }; + + return ( + reasonDescriptions[validation.reason] ?? + `Security alert: ${validation.reason}` + ); + } + #mapScan( result: SecurityAlertSimulationValidationResponse, - ): TransactionScanResult { + ): TransactionScanResult | null { + // Validate that we have a basic result structure + if (!result) { + return null; + } + return { - status: result?.status, + status: + result.status === 'SUCCESS' || result.status === 'ERROR' + ? result.status + : 'ERROR', estimatedChanges: { assets: result.result?.simulation?.account_summary?.account_assets_diff?.map( @@ -119,8 +254,8 @@ export class TransactionScanService { ) ?? [], }, validation: { - type: result.result?.validation?.result_type, - reason: result.result?.validation?.reason, + type: result.result?.validation?.result_type ?? null, + reason: result.result?.validation?.reason ?? null, }, error: result?.error_details ? { diff --git a/packages/snap/src/core/services/transaction-scan/types.ts b/packages/snap/src/core/services/transaction-scan/types.ts index 96aa46115..76d3146a5 100644 --- a/packages/snap/src/core/services/transaction-scan/types.ts +++ b/packages/snap/src/core/services/transaction-scan/types.ts @@ -17,8 +17,12 @@ export type TransactionScanEstimatedChanges = { }; export type TransactionScanValidation = { - type: SecurityAlertSimulationValidationResponse['result']['validation']['result_type']; - reason: SecurityAlertSimulationValidationResponse['result']['validation']['reason']; + type: + | SecurityAlertSimulationValidationResponse['result']['validation']['result_type'] + | null; + reason: + | SecurityAlertSimulationValidationResponse['result']['validation']['reason'] + | null; }; export type TransactionScanError = { @@ -32,3 +36,14 @@ export type TransactionScanResult = { validation: TransactionScanValidation; error: TransactionScanError | null; }; + +export enum SecurityAlertResponse { + Benign = 'Benign', + Warning = 'Warning', + Malicious = 'Malicious', +} + +export enum ScanStatus { + SUCCESS = 'SUCCESS', + ERROR = 'ERROR', +} diff --git a/packages/snap/src/features/confirmation/components/TransactionAlert/TransactionAlert.tsx b/packages/snap/src/features/confirmation/components/TransactionAlert/TransactionAlert.tsx index 6c72d836c..1d39ccb41 100644 --- a/packages/snap/src/features/confirmation/components/TransactionAlert/TransactionAlert.tsx +++ b/packages/snap/src/features/confirmation/components/TransactionAlert/TransactionAlert.tsx @@ -24,7 +24,10 @@ type TransactionAlertProps = { }; const VALIDATION_TYPE_TO_SEVERITY: Partial< - Record + Record< + NonNullable, + BannerProps['severity'] + > > = { Malicious: 'danger', Warning: 'warning', @@ -84,7 +87,9 @@ export const TransactionAlert: SnapComponent = ({ return {null}; } - const severity = VALIDATION_TYPE_TO_SEVERITY[validation?.type]; + const severity = validation?.type + ? VALIDATION_TYPE_TO_SEVERITY[validation.type] + : undefined; /** * Displays a banner if the validation there is a validation. diff --git a/packages/snap/src/snapContext.ts b/packages/snap/src/snapContext.ts index 1ef098023..0554e2b44 100644 --- a/packages/snap/src/snapContext.ts +++ b/packages/snap/src/snapContext.ts @@ -119,6 +119,7 @@ const walletService = new WalletService(connection, transactionHelper, logger); const transactionScanService = new TransactionScanService( new SecurityAlertsApiClient(configProvider), tokenMetadataService, + analyticsService, logger, );