@@ -723,6 +723,183 @@ describe('SmartTransactionsController', () => {
723
723
} ) ;
724
724
} ) ;
725
725
726
+ it ( 'should acquire nonce for Swap transactions only' , async ( ) => {
727
+ // Create a mock for getNonceLock
728
+ const mockGetNonceLock = jest . fn ( ) . mockResolvedValue ( {
729
+ nextNonce : 'nextNonce' ,
730
+ nonceDetails : { test : 'details' } ,
731
+ releaseLock : jest . fn ( ) ,
732
+ } ) ;
733
+
734
+ await withController (
735
+ {
736
+ options : {
737
+ getNonceLock : mockGetNonceLock ,
738
+ } ,
739
+ } ,
740
+ async ( { controller } ) => {
741
+ const signedTransaction = createSignedTransaction ( ) ;
742
+ const submitTransactionsApiResponse =
743
+ createSubmitTransactionsApiResponse ( ) ;
744
+
745
+ // First API mock for the case without nonce
746
+ nock ( API_BASE_URL )
747
+ . post (
748
+ `/networks/${ ethereumChainIdDec } /submitTransactions?stxControllerVersion=${ packageJson . version } ` ,
749
+ )
750
+ . reply ( 200 , submitTransactionsApiResponse ) ;
751
+
752
+ // Second API mock for the case with nonce
753
+ nock ( API_BASE_URL )
754
+ . post (
755
+ `/networks/${ ethereumChainIdDec } /submitTransactions?stxControllerVersion=${ packageJson . version } ` ,
756
+ )
757
+ . reply ( 200 , submitTransactionsApiResponse ) ;
758
+
759
+ // Case 1: Swap transaction without nonce (should call getNonceLock)
760
+ const txParamsWithoutNonce = {
761
+ ...createTxParams ( ) ,
762
+ nonce : undefined , // Explicitly undefined nonce
763
+ } ;
764
+
765
+ await controller . submitSignedTransactions ( {
766
+ signedTransactions : [ signedTransaction ] ,
767
+ txParams : txParamsWithoutNonce ,
768
+ // No transactionMeta means type defaults to 'swap'
769
+ } ) ;
770
+
771
+ // Verify getNonceLock was called for the Swap
772
+ expect ( mockGetNonceLock ) . toHaveBeenCalledTimes ( 1 ) ;
773
+ expect ( mockGetNonceLock ) . toHaveBeenCalledWith (
774
+ txParamsWithoutNonce . from ,
775
+ NetworkType . mainnet ,
776
+ ) ;
777
+
778
+ // Reset the mock
779
+ mockGetNonceLock . mockClear ( ) ;
780
+
781
+ // Case 2: Transaction with nonce already set (should NOT call getNonceLock)
782
+ const txParamsWithNonce = createTxParams ( ) ; // This has nonce: '0'
783
+
784
+ await controller . submitSignedTransactions ( {
785
+ signedTransactions : [ signedTransaction ] ,
786
+ txParams : txParamsWithNonce ,
787
+ } ) ;
788
+
789
+ // Verify getNonceLock was NOT called for transaction with nonce
790
+ expect ( mockGetNonceLock ) . not . toHaveBeenCalled ( ) ;
791
+ } ,
792
+ ) ;
793
+ } ) ;
794
+
795
+ it ( 'should properly set nonce on txParams and mark transaction as swap type' , async ( ) => {
796
+ // Mock with a specific nextNonce value we can verify
797
+ const mockGetNonceLock = jest . fn ( ) . mockResolvedValue ( {
798
+ nextNonce : 42 ,
799
+ nonceDetails : { test : 'nonce details' } ,
800
+ releaseLock : jest . fn ( ) ,
801
+ } ) ;
802
+
803
+ await withController (
804
+ {
805
+ options : {
806
+ getNonceLock : mockGetNonceLock ,
807
+ } ,
808
+ } ,
809
+ async ( { controller } ) => {
810
+ const signedTransaction = createSignedTransaction ( ) ;
811
+ const submitTransactionsApiResponse =
812
+ createSubmitTransactionsApiResponse ( ) ;
813
+ nock ( API_BASE_URL )
814
+ . post (
815
+ `/networks/${ ethereumChainIdDec } /submitTransactions?stxControllerVersion=${ packageJson . version } ` ,
816
+ )
817
+ . reply ( 200 , submitTransactionsApiResponse ) ;
818
+
819
+ // Create txParams without nonce
820
+ const txParamsWithoutNonce = {
821
+ ...createTxParams ( ) ,
822
+ nonce : undefined ,
823
+ from : addressFrom ,
824
+ } ;
825
+
826
+ await controller . submitSignedTransactions ( {
827
+ signedTransactions : [ signedTransaction ] ,
828
+ txParams : txParamsWithoutNonce ,
829
+ // No transactionMeta provided, should default to 'swap' type
830
+ } ) ;
831
+
832
+ // Get the created smart transaction
833
+ const createdSmartTransaction =
834
+ controller . state . smartTransactionsState . smartTransactions [
835
+ ChainId . mainnet
836
+ ] [ 0 ] ;
837
+
838
+ // Verify nonce was set correctly on the txParams in the created transaction
839
+ expect ( createdSmartTransaction . txParams . nonce ) . toBe ( '0x42' ) ; // 42 as a hex string
840
+
841
+ // Verify transaction type is set to 'swap' by default
842
+ expect ( createdSmartTransaction . type ) . toBe ( 'swap' ) ;
843
+
844
+ // Verify nonceDetails were passed correctly
845
+ expect ( createdSmartTransaction . nonceDetails ) . toStrictEqual ( {
846
+ test : 'nonce details' ,
847
+ } ) ;
848
+ } ,
849
+ ) ;
850
+ } ) ;
851
+
852
+ it ( 'should handle errors when acquiring nonce lock' , async ( ) => {
853
+ // Mock getNonceLock to reject with an error
854
+ const mockError = new Error ( 'Failed to acquire nonce' ) ;
855
+ const mockGetNonceLock = jest . fn ( ) . mockRejectedValue ( mockError ) ;
856
+
857
+ // Spy on console.error to verify it's called
858
+ const consoleErrorSpy = jest . spyOn ( console , 'error' ) . mockImplementation ( ) ;
859
+
860
+ await withController (
861
+ {
862
+ options : {
863
+ getNonceLock : mockGetNonceLock ,
864
+ } ,
865
+ } ,
866
+ async ( { controller } ) => {
867
+ const signedTransaction = createSignedTransaction ( ) ;
868
+ const submitTransactionsApiResponse =
869
+ createSubmitTransactionsApiResponse ( ) ;
870
+ nock ( API_BASE_URL )
871
+ . post (
872
+ `/networks/${ ethereumChainIdDec } /submitTransactions?stxControllerVersion=${ packageJson . version } ` ,
873
+ )
874
+ . reply ( 200 , submitTransactionsApiResponse ) ;
875
+
876
+ // Create txParams without nonce
877
+ const txParamsWithoutNonce = {
878
+ ...createTxParams ( ) ,
879
+ nonce : undefined ,
880
+ from : addressFrom ,
881
+ } ;
882
+
883
+ // Attempt to submit a transaction that will fail when acquiring nonce
884
+ await expect (
885
+ controller . submitSignedTransactions ( {
886
+ signedTransactions : [ signedTransaction ] ,
887
+ txParams : txParamsWithoutNonce ,
888
+ } ) ,
889
+ ) . rejects . toThrow ( 'Failed to acquire nonce' ) ;
890
+
891
+ // Verify error was logged
892
+ expect ( consoleErrorSpy ) . toHaveBeenCalledWith (
893
+ 'Failed to acquire nonce lock:' ,
894
+ mockError ,
895
+ ) ;
896
+
897
+ // Cleanup spy
898
+ consoleErrorSpy . mockRestore ( ) ;
899
+ } ,
900
+ ) ;
901
+ } ) ;
902
+
726
903
it ( 'submits a batch of signed transactions' , async ( ) => {
727
904
await withController ( async ( { controller } ) => {
728
905
const signedTransaction1 = createSignedTransaction ( ) ;
@@ -2090,6 +2267,126 @@ describe('SmartTransactionsController', () => {
2090
2267
) ;
2091
2268
} ) ;
2092
2269
} ) ;
2270
+
2271
+ describe ( 'createOrUpdateSmartTransaction' , ( ) => {
2272
+ beforeEach ( ( ) => {
2273
+ jest
2274
+ . spyOn ( SmartTransactionsController . prototype , 'checkPoll' )
2275
+ . mockImplementation ( ( ) => ( { } ) ) ;
2276
+ } ) ;
2277
+
2278
+ it ( 'adds metaMetricsProps to new smart transactions' , async ( ) => {
2279
+ const { smartTransactionsState } =
2280
+ getDefaultSmartTransactionsControllerState ( ) ;
2281
+ const newSmartTransaction = {
2282
+ uuid : 'new-uuid-test' ,
2283
+ status : SmartTransactionStatuses . PENDING ,
2284
+ txParams : {
2285
+ from : addressFrom ,
2286
+ } ,
2287
+ } ;
2288
+
2289
+ await withController (
2290
+ {
2291
+ options : {
2292
+ state : {
2293
+ smartTransactionsState : {
2294
+ ...smartTransactionsState ,
2295
+ smartTransactions : {
2296
+ [ ChainId . mainnet ] : [ ] ,
2297
+ } ,
2298
+ } ,
2299
+ } ,
2300
+ getMetaMetricsProps : jest . fn ( ) . mockResolvedValue ( {
2301
+ accountHardwareType : 'Test Hardware' ,
2302
+ accountType : 'test-account' ,
2303
+ deviceModel : 'test-model' ,
2304
+ } ) ,
2305
+ } ,
2306
+ } ,
2307
+ async ( { controller } ) => {
2308
+ controller . updateSmartTransaction (
2309
+ newSmartTransaction as SmartTransaction ,
2310
+ ) ;
2311
+
2312
+ // Allow async operations to complete
2313
+ await flushPromises ( ) ;
2314
+
2315
+ // Verify MetaMetricsProps were added
2316
+ const updatedTransaction =
2317
+ controller . state . smartTransactionsState . smartTransactions [
2318
+ ChainId . mainnet
2319
+ ] [ 0 ] ;
2320
+ expect ( updatedTransaction . accountHardwareType ) . toBe ( 'Test Hardware' ) ;
2321
+ expect ( updatedTransaction . accountType ) . toBe ( 'test-account' ) ;
2322
+ expect ( updatedTransaction . deviceModel ) . toBe ( 'test-model' ) ;
2323
+ } ,
2324
+ ) ;
2325
+ } ) ;
2326
+
2327
+ it ( 'continues without metaMetricsProps if adding them fails' , async ( ) => {
2328
+ const { smartTransactionsState } =
2329
+ getDefaultSmartTransactionsControllerState ( ) ;
2330
+ const newSmartTransaction = {
2331
+ uuid : 'new-uuid-test' ,
2332
+ status : SmartTransactionStatuses . PENDING ,
2333
+ txParams : {
2334
+ from : addressFrom ,
2335
+ } ,
2336
+ } ;
2337
+
2338
+ // Mock console.error to verify it's called
2339
+ const consoleErrorSpy = jest . spyOn ( console , 'error' ) . mockImplementation ( ) ;
2340
+
2341
+ await withController (
2342
+ {
2343
+ options : {
2344
+ state : {
2345
+ smartTransactionsState : {
2346
+ ...smartTransactionsState ,
2347
+ smartTransactions : {
2348
+ [ ChainId . mainnet ] : [ ] ,
2349
+ } ,
2350
+ } ,
2351
+ } ,
2352
+ // Mock getting MetaMetricsProps to fail
2353
+ getMetaMetricsProps : jest
2354
+ . fn ( )
2355
+ . mockRejectedValue ( new Error ( 'Test metrics error' ) ) ,
2356
+ } ,
2357
+ } ,
2358
+ async ( { controller } ) => {
2359
+ controller . updateSmartTransaction (
2360
+ newSmartTransaction as SmartTransaction ,
2361
+ ) ;
2362
+
2363
+ // Allow async operations to complete
2364
+ await flushPromises ( ) ;
2365
+
2366
+ // Verify transaction was still added even without metrics props
2367
+ const updatedTransaction =
2368
+ controller . state . smartTransactionsState . smartTransactions [
2369
+ ChainId . mainnet
2370
+ ] [ 0 ] ;
2371
+ expect ( updatedTransaction . uuid ) . toBe ( 'new-uuid-test' ) ;
2372
+
2373
+ // These should be undefined since getting metrics props failed
2374
+ expect ( updatedTransaction . accountHardwareType ) . toBeUndefined ( ) ;
2375
+ expect ( updatedTransaction . accountType ) . toBeUndefined ( ) ;
2376
+ expect ( updatedTransaction . deviceModel ) . toBeUndefined ( ) ;
2377
+
2378
+ // Verify the error was logged
2379
+ expect ( consoleErrorSpy ) . toHaveBeenCalledWith (
2380
+ 'Failed to add metrics props to smart transaction:' ,
2381
+ expect . any ( Error ) ,
2382
+ ) ;
2383
+
2384
+ // Clean up the spy
2385
+ consoleErrorSpy . mockRestore ( ) ;
2386
+ } ,
2387
+ ) ;
2388
+ } ) ;
2389
+ } ) ;
2093
2390
} ) ;
2094
2391
2095
2392
type WithControllerCallback < ReturnValue > = ( {
0 commit comments