@@ -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 ) . toEqual ( {
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 ( ) ;
0 commit comments