11import { deriveStateFromMetadata , Messenger } from '@metamask/base-controller' ;
2+ import type { TransactionControllerStateChangeEvent } from '@metamask/transaction-controller' ;
23import { strict as assert } from 'assert' ;
34import nock , { cleanAll , isDone , pendingMocks } from 'nock' ;
45import sinon from 'sinon' ;
@@ -10,6 +11,7 @@ import {
1011 PhishingController ,
1112 PHISHING_CONFIG_BASE_URL ,
1213 type PhishingControllerActions ,
14+ type PhishingControllerEvents ,
1315 type PhishingControllerOptions ,
1416 CLIENT_SIDE_DETECION_BASE_URL ,
1517 C2_DOMAIN_BLOCKLIST_ENDPOINT ,
@@ -18,26 +20,37 @@ import {
1820 PHISHING_DETECTION_BULK_SCAN_ENDPOINT ,
1921 type BulkPhishingDetectionScanResponse ,
2022} from './PhishingController' ;
21- import { formatHostnameToUrl } from './tests/utils' ;
23+ import {
24+ createMockStateChangePayload ,
25+ createMockTransaction ,
26+ formatHostnameToUrl ,
27+ TEST_ADDRESSES ,
28+ } from './tests/utils' ;
2229import type { PhishingDetectionScanResult } from './types' ;
2330import { PhishingDetectorResultType , RecommendedAction } from './types' ;
2431import { getHostnameFromUrl } from './utils' ;
2532
2633const controllerName = 'PhishingController' ;
2734
2835/**
29- * Constructs a restricted messenger.
36+ * Constructs a restricted messenger with transaction events enabled .
3037 *
31- * @returns A restricted messenger.
38+ * @returns A restricted messenger that can listen to TransactionController events .
3239 */
33- function getRestrictedMessenger ( ) {
34- const messenger = new Messenger < PhishingControllerActions , never > ( ) ;
35-
36- return messenger . getRestricted ( {
37- name : controllerName ,
38- allowedActions : [ ] ,
39- allowedEvents : [ ] ,
40- } ) ;
40+ function getRestrictedMessengerWithTransactionEvents ( ) {
41+ const messenger = new Messenger <
42+ PhishingControllerActions ,
43+ PhishingControllerEvents | TransactionControllerStateChangeEvent
44+ > ( ) ;
45+
46+ return {
47+ messenger : messenger . getRestricted ( {
48+ name : controllerName ,
49+ allowedActions : [ ] ,
50+ allowedEvents : [ 'TransactionController:stateChange' ] ,
51+ } ) ,
52+ globalMessenger : messenger ,
53+ } ;
4154}
4255
4356/**
@@ -48,7 +61,7 @@ function getRestrictedMessenger() {
4861 */
4962function getPhishingController ( options ?: Partial < PhishingControllerOptions > ) {
5063 return new PhishingController ( {
51- messenger : getRestrictedMessenger ( ) ,
64+ messenger : getRestrictedMessengerWithTransactionEvents ( ) . messenger ,
5265 ...options ,
5366 } ) ;
5467}
@@ -3416,4 +3429,196 @@ describe('URL Scan Cache', () => {
34163429 ` ) ;
34173430 } ) ;
34183431 } ) ;
3432+
3433+ describe ( 'Transaction Controller State Change Integration' , ( ) => {
3434+ let controller : PhishingController ;
3435+ let globalMessenger : Messenger <
3436+ PhishingControllerActions ,
3437+ PhishingControllerEvents | TransactionControllerStateChangeEvent
3438+ > ;
3439+ let bulkScanTokensSpy : jest . SpyInstance ;
3440+
3441+ beforeEach ( ( ) => {
3442+ const messengerSetup = getRestrictedMessengerWithTransactionEvents ( ) ;
3443+ globalMessenger = messengerSetup . globalMessenger ;
3444+
3445+ controller = new PhishingController ( {
3446+ messenger : messengerSetup . messenger ,
3447+ } ) ;
3448+
3449+ bulkScanTokensSpy = jest
3450+ . spyOn ( controller , 'bulkScanTokens' )
3451+ . mockResolvedValue ( { } ) ;
3452+ } ) ;
3453+
3454+ afterEach ( ( ) => {
3455+ bulkScanTokensSpy . mockRestore ( ) ;
3456+ } ) ;
3457+
3458+ it ( 'should trigger bulk token scanning when transaction with token balance changes is added' , async ( ) => {
3459+ const mockTransaction = createMockTransaction ( 'test-tx-1' , [
3460+ TEST_ADDRESSES . USDC ,
3461+ TEST_ADDRESSES . MOCK_TOKEN_1 ,
3462+ ] ) ;
3463+ const stateChangePayload = createMockStateChangePayload ( [
3464+ mockTransaction ,
3465+ ] ) ;
3466+
3467+ globalMessenger . publish (
3468+ 'TransactionController:stateChange' ,
3469+ stateChangePayload ,
3470+ [
3471+ {
3472+ op : 'add' as const ,
3473+ path : [ 'transactions' , 0 ] ,
3474+ value : mockTransaction ,
3475+ } ,
3476+ ] ,
3477+ ) ;
3478+
3479+ await new Promise ( process . nextTick ) ;
3480+
3481+ expect ( bulkScanTokensSpy ) . toHaveBeenCalledWith ( {
3482+ chainId : mockTransaction . chainId . toLowerCase ( ) ,
3483+ tokens : [
3484+ TEST_ADDRESSES . USDC . toLowerCase ( ) ,
3485+ TEST_ADDRESSES . MOCK_TOKEN_1 . toLowerCase ( ) ,
3486+ ] ,
3487+ } ) ;
3488+ } ) ;
3489+
3490+ it ( 'should skip processing when patch operation is remove' , async ( ) => {
3491+ const mockTransaction = createMockTransaction ( 'test-tx-1' , [
3492+ TEST_ADDRESSES . USDC ,
3493+ ] ) ;
3494+
3495+ const stateChangePayload = createMockStateChangePayload ( [ ] ) ;
3496+
3497+ globalMessenger . publish (
3498+ 'TransactionController:stateChange' ,
3499+ stateChangePayload ,
3500+ [
3501+ {
3502+ op : 'remove' as const ,
3503+ path : [ 'transactions' , 0 ] ,
3504+ value : mockTransaction ,
3505+ } ,
3506+ ] ,
3507+ ) ;
3508+
3509+ await new Promise ( process . nextTick ) ;
3510+
3511+ expect ( bulkScanTokensSpy ) . not . toHaveBeenCalled ( ) ;
3512+ } ) ;
3513+
3514+ it ( 'should not trigger bulk token scanning when transaction has no token balance changes' , async ( ) => {
3515+ const mockTransaction = createMockTransaction ( 'test-tx-1' , [ ] ) ;
3516+
3517+ const stateChangePayload = createMockStateChangePayload ( [
3518+ mockTransaction ,
3519+ ] ) ;
3520+
3521+ globalMessenger . publish (
3522+ 'TransactionController:stateChange' ,
3523+ stateChangePayload ,
3524+ [
3525+ {
3526+ op : 'add' as const ,
3527+ path : [ 'transactions' , 0 ] ,
3528+ value : mockTransaction ,
3529+ } ,
3530+ ] ,
3531+ ) ;
3532+
3533+ await new Promise ( process . nextTick ) ;
3534+
3535+ expect ( bulkScanTokensSpy ) . not . toHaveBeenCalled ( ) ;
3536+ } ) ;
3537+
3538+ it ( 'should not trigger bulk token scanning when using default tokenAddresses parameter' , async ( ) => {
3539+ const mockTransaction = createMockTransaction ( 'test-tx-2' ) ;
3540+
3541+ const stateChangePayload = createMockStateChangePayload ( [
3542+ mockTransaction ,
3543+ ] ) ;
3544+
3545+ globalMessenger . publish (
3546+ 'TransactionController:stateChange' ,
3547+ stateChangePayload ,
3548+ [
3549+ {
3550+ op : 'add' as const ,
3551+ path : [ 'transactions' , 0 ] ,
3552+ value : mockTransaction ,
3553+ } ,
3554+ ] ,
3555+ ) ;
3556+
3557+ await new Promise ( process . nextTick ) ;
3558+
3559+ expect ( bulkScanTokensSpy ) . not . toHaveBeenCalled ( ) ;
3560+ } ) ;
3561+
3562+ it ( 'should handle errors in transaction state change processing' , async ( ) => {
3563+ const consoleErrorSpy = jest . spyOn ( console , 'error' ) . mockImplementation ( ) ;
3564+
3565+ const stateChangePayload = createMockStateChangePayload ( [ ] ) ;
3566+
3567+ globalMessenger . publish (
3568+ 'TransactionController:stateChange' ,
3569+ stateChangePayload ,
3570+ [
3571+ {
3572+ op : 'add' as const ,
3573+ path : [ 'transactions' , 0 ] ,
3574+ value : null ,
3575+ } ,
3576+ ] ,
3577+ ) ;
3578+
3579+ await new Promise ( process . nextTick ) ;
3580+
3581+ expect ( consoleErrorSpy ) . toHaveBeenCalledWith (
3582+ 'Error processing transaction state change:' ,
3583+ expect . any ( Error ) ,
3584+ ) ;
3585+
3586+ consoleErrorSpy . mockRestore ( ) ;
3587+ } ) ;
3588+
3589+ it ( 'should handle errors in bulk token scanning' , async ( ) => {
3590+ const consoleErrorSpy = jest . spyOn ( console , 'error' ) . mockImplementation ( ) ;
3591+
3592+ bulkScanTokensSpy . mockRejectedValue ( new Error ( 'Scanning failed' ) ) ;
3593+
3594+ const mockTransaction = createMockTransaction ( 'test-tx-1' , [
3595+ TEST_ADDRESSES . USDC ,
3596+ ] ) ;
3597+
3598+ const stateChangePayload = createMockStateChangePayload ( [
3599+ mockTransaction ,
3600+ ] ) ;
3601+
3602+ globalMessenger . publish (
3603+ 'TransactionController:stateChange' ,
3604+ stateChangePayload ,
3605+ [
3606+ {
3607+ op : 'add' as const ,
3608+ path : [ 'transactions' , 0 ] ,
3609+ value : mockTransaction ,
3610+ } ,
3611+ ] ,
3612+ ) ;
3613+
3614+ await new Promise ( process . nextTick ) ;
3615+
3616+ expect ( consoleErrorSpy ) . toHaveBeenCalledWith (
3617+ 'Error scanning tokens for chain 0x1:' ,
3618+ expect . any ( Error ) ,
3619+ ) ;
3620+
3621+ consoleErrorSpy . mockRestore ( ) ;
3622+ } ) ;
3623+ } ) ;
34193624} ) ;
0 commit comments