@@ -723,6 +723,183 @@ describe('SmartTransactionsController', () => {
723723 } ) ;
724724 } ) ;
725725
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+
726903 it ( 'submits a batch of signed transactions' , async ( ) => {
727904 await withController ( async ( { controller } ) => {
728905 const signedTransaction1 = createSignedTransaction ( ) ;
@@ -2090,6 +2267,126 @@ describe('SmartTransactionsController', () => {
20902267 ) ;
20912268 } ) ;
20922269 } ) ;
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+ } ) ;
20932390} ) ;
20942391
20952392type WithControllerCallback < ReturnValue > = ( {
0 commit comments