@@ -537,6 +537,281 @@ describe('transactions/signer', () => {
537537 } )
538538 )
539539 } )
540+
541+ describe ( 'chunked batching' , ( ) => {
542+ it ( 'should chunk transactions when they exceed LZ_BATCH_SIZE' , async ( ) => {
543+ const originalBatchSize = process . env . LZ_BATCH_SIZE
544+ process . env . LZ_BATCH_SIZE = '3'
545+
546+ try {
547+ // Create 7 transactions for the same EID to ensure chunking
548+ const eid = 30101
549+ const transactions = Array . from ( { length : 7 } , ( _ , i ) => ( {
550+ point : { eid, address : `0x${ i } ` } ,
551+ data : `0x${ i } ` ,
552+ } ) )
553+
554+ const receipt = { transactionHash : '0x0' }
555+ const wait = jest . fn ( ) . mockResolvedValue ( receipt )
556+ const response : OmniTransactionResponse = {
557+ transactionHash : '0x0' ,
558+ wait,
559+ }
560+
561+ const signAndSendBatch = jest . fn ( ) . mockResolvedValue ( response )
562+ const signAndSend = jest . fn ( ) . mockRejectedValue ( 'Oh god no' )
563+ const sign = jest . fn ( ) . mockRejectedValue ( 'Oh god no' )
564+ const signerFactory : OmniSignerFactory = jest
565+ . fn ( )
566+ . mockResolvedValue ( { signAndSend, signAndSendBatch, sign } )
567+ const signAndSendTransactions = createSignAndSend ( signerFactory )
568+
569+ const [ successful , errors , pending ] = await signAndSendTransactions ( transactions )
570+
571+ // Should have 3 calls to signAndSendBatch: [3, 3, 1]
572+ expect ( signAndSendBatch ) . toHaveBeenCalledTimes ( 3 )
573+ expect ( signAndSendBatch ) . toHaveBeenNthCalledWith ( 1 , transactions . slice ( 0 , 3 ) )
574+ expect ( signAndSendBatch ) . toHaveBeenNthCalledWith ( 2 , transactions . slice ( 3 , 6 ) )
575+ expect ( signAndSendBatch ) . toHaveBeenNthCalledWith ( 3 , transactions . slice ( 6 , 7 ) )
576+
577+ expect ( successful ) . toHaveLength ( 7 )
578+ expect ( errors ) . toEqual ( [ ] )
579+ expect ( pending ) . toEqual ( [ ] )
580+ } finally {
581+ process . env . LZ_BATCH_SIZE = originalBatchSize
582+ }
583+ } )
584+
585+ it ( 'should use default batch size when LZ_BATCH_SIZE is not set' , async ( ) => {
586+ const originalBatchSize = process . env . LZ_BATCH_SIZE
587+ delete process . env . LZ_BATCH_SIZE
588+
589+ try {
590+ // Create 25 transactions for the same EID (more than default batch size of 20)
591+ const eid = 30101
592+ const transactions = Array . from ( { length : 25 } , ( _ , i ) => ( {
593+ point : { eid, address : `0x${ i } ` } ,
594+ data : `0x${ i } ` ,
595+ } ) )
596+
597+ const receipt = { transactionHash : '0x0' }
598+ const wait = jest . fn ( ) . mockResolvedValue ( receipt )
599+ const response : OmniTransactionResponse = {
600+ transactionHash : '0x0' ,
601+ wait,
602+ }
603+
604+ const signAndSendBatch = jest . fn ( ) . mockResolvedValue ( response )
605+ const signAndSend = jest . fn ( ) . mockRejectedValue ( 'Oh god no' )
606+ const sign = jest . fn ( ) . mockRejectedValue ( 'Oh god no' )
607+ const signerFactory : OmniSignerFactory = jest
608+ . fn ( )
609+ . mockResolvedValue ( { signAndSend, signAndSendBatch, sign } )
610+ const signAndSendTransactions = createSignAndSend ( signerFactory )
611+
612+ const [ successful , errors , pending ] = await signAndSendTransactions ( transactions )
613+
614+ // Should have 2 calls to signAndSendBatch: [20, 5] (default batch size is 20)
615+ expect ( signAndSendBatch ) . toHaveBeenCalledTimes ( 2 )
616+ expect ( signAndSendBatch ) . toHaveBeenNthCalledWith ( 1 , transactions . slice ( 0 , 20 ) )
617+ expect ( signAndSendBatch ) . toHaveBeenNthCalledWith ( 2 , transactions . slice ( 20 , 25 ) )
618+
619+ expect ( successful ) . toHaveLength ( 25 )
620+ expect ( errors ) . toEqual ( [ ] )
621+ expect ( pending ) . toEqual ( [ ] )
622+ } finally {
623+ process . env . LZ_BATCH_SIZE = originalBatchSize
624+ }
625+ } )
626+
627+ it ( 'should not chunk when transactions are within batch size limit' , async ( ) => {
628+ const originalBatchSize = process . env . LZ_BATCH_SIZE
629+ process . env . LZ_BATCH_SIZE = '10'
630+
631+ try {
632+ // Create 5 transactions for the same EID (less than batch size)
633+ const eid = 30101
634+ const transactions = Array . from ( { length : 5 } , ( _ , i ) => ( {
635+ point : { eid, address : `0x${ i } ` } ,
636+ data : `0x${ i } ` ,
637+ } ) )
638+
639+ const receipt = { transactionHash : '0x0' }
640+ const wait = jest . fn ( ) . mockResolvedValue ( receipt )
641+ const response : OmniTransactionResponse = {
642+ transactionHash : '0x0' ,
643+ wait,
644+ }
645+
646+ const signAndSendBatch = jest . fn ( ) . mockResolvedValue ( response )
647+ const signAndSend = jest . fn ( ) . mockRejectedValue ( 'Oh god no' )
648+ const sign = jest . fn ( ) . mockRejectedValue ( 'Oh god no' )
649+ const signerFactory : OmniSignerFactory = jest
650+ . fn ( )
651+ . mockResolvedValue ( { signAndSend, signAndSendBatch, sign } )
652+ const signAndSendTransactions = createSignAndSend ( signerFactory )
653+
654+ const [ successful , errors , pending ] = await signAndSendTransactions ( transactions )
655+
656+ // Should have 1 call to signAndSendBatch with all 5 transactions
657+ expect ( signAndSendBatch ) . toHaveBeenCalledTimes ( 1 )
658+ expect ( signAndSendBatch ) . toHaveBeenNthCalledWith ( 1 , transactions )
659+
660+ expect ( successful ) . toHaveLength ( 5 )
661+ expect ( errors ) . toEqual ( [ ] )
662+ expect ( pending ) . toEqual ( [ ] )
663+ } finally {
664+ process . env . LZ_BATCH_SIZE = originalBatchSize
665+ }
666+ } )
667+
668+ it ( 'should stop processing batches when one batch fails' , async ( ) => {
669+ const originalBatchSize = process . env . LZ_BATCH_SIZE
670+ process . env . LZ_BATCH_SIZE = '3'
671+
672+ try {
673+ // Create 9 transactions for the same EID to ensure chunking
674+ const eid = 30101
675+ const transactions = Array . from ( { length : 9 } , ( _ , i ) => ( {
676+ point : { eid, address : `0x${ i } ` } ,
677+ data : `0x${ i } ` ,
678+ } ) )
679+
680+ const receipt = { transactionHash : '0x0' }
681+ const wait = jest . fn ( ) . mockResolvedValue ( receipt )
682+ const successResponse : OmniTransactionResponse = {
683+ transactionHash : '0x0' ,
684+ wait,
685+ }
686+
687+ const error = new Error ( 'Batch failed' )
688+ const signAndSendBatch = jest
689+ . fn ( )
690+ . mockResolvedValueOnce ( successResponse ) // First batch succeeds
691+ . mockRejectedValueOnce ( error ) // Second batch fails
692+ . mockResolvedValue ( successResponse ) // Third batch would succeed but shouldn't be called
693+
694+ const signAndSend = jest . fn ( ) . mockRejectedValue ( 'Oh god no' )
695+ const sign = jest . fn ( ) . mockRejectedValue ( 'Oh god no' )
696+ const signerFactory : OmniSignerFactory = jest
697+ . fn ( )
698+ . mockResolvedValue ( { signAndSend, signAndSendBatch, sign } )
699+ const signAndSendTransactions = createSignAndSend ( signerFactory )
700+
701+ const [ successful , errors , pending ] = await signAndSendTransactions ( transactions )
702+
703+ // Should have 2 calls to signAndSendBatch (first succeeds, second fails, third not called)
704+ expect ( signAndSendBatch ) . toHaveBeenCalledTimes ( 2 )
705+ expect ( signAndSendBatch ) . toHaveBeenNthCalledWith ( 1 , transactions . slice ( 0 , 3 ) )
706+ expect ( signAndSendBatch ) . toHaveBeenNthCalledWith ( 2 , transactions . slice ( 3 , 6 ) )
707+
708+ // First batch should succeed
709+ expect ( successful ) . toHaveLength ( 3 )
710+ expect ( successful ) . toContainAllValues (
711+ transactions . slice ( 0 , 3 ) . map ( ( transaction ) => ( { transaction, receipt } ) )
712+ )
713+
714+ // Second batch should fail
715+ expect ( errors ) . toHaveLength ( 3 )
716+ expect ( errors ) . toContainAllValues (
717+ transactions . slice ( 3 , 6 ) . map ( ( transaction ) => ( { transaction, error } ) )
718+ )
719+
720+ // Third batch should be pending
721+ expect ( pending ) . toContainAllValues ( transactions . slice ( 3 , 9 ) )
722+ } finally {
723+ process . env . LZ_BATCH_SIZE = originalBatchSize
724+ }
725+ } )
726+
727+ it ( 'should handle chunking across multiple EIDs correctly' , async ( ) => {
728+ const originalBatchSize = process . env . LZ_BATCH_SIZE
729+ process . env . LZ_BATCH_SIZE = '2'
730+
731+ try {
732+ // Create transactions for different EIDs
733+ const transactions = [
734+ { point : { eid : 30101 , address : '0x1' } , data : '0x1' } ,
735+ { point : { eid : 30101 , address : '0x2' } , data : '0x2' } ,
736+ { point : { eid : 30101 , address : '0x3' } , data : '0x3' } ,
737+ { point : { eid : 30102 , address : '0x4' } , data : '0x4' } ,
738+ { point : { eid : 30102 , address : '0x5' } , data : '0x5' } ,
739+ { point : { eid : 30102 , address : '0x6' } , data : '0x6' } ,
740+ ]
741+
742+ const receipt = { transactionHash : '0x0' }
743+ const wait = jest . fn ( ) . mockResolvedValue ( receipt )
744+ const response : OmniTransactionResponse = {
745+ transactionHash : '0x0' ,
746+ wait,
747+ }
748+
749+ const signAndSendBatch = jest . fn ( ) . mockResolvedValue ( response )
750+ const signAndSend = jest . fn ( ) . mockRejectedValue ( 'Oh god no' )
751+ const sign = jest . fn ( ) . mockRejectedValue ( 'Oh god no' )
752+ const signerFactory : OmniSignerFactory = jest
753+ . fn ( )
754+ . mockResolvedValue ( { signAndSend, signAndSendBatch, sign } )
755+ const signAndSendTransactions = createSignAndSend ( signerFactory )
756+
757+ const [ successful , errors , pending ] = await signAndSendTransactions ( transactions )
758+
759+ // Should chunk per EID:
760+ // EID 30101: 3 transactions -> 2 batches [2, 1]
761+ // EID 30102: 3 transactions -> 2 batches [2, 1]
762+ // Total: 4 calls to signAndSendBatch
763+ expect ( signAndSendBatch ) . toHaveBeenCalledTimes ( 4 )
764+
765+ expect ( successful ) . toHaveLength ( 6 )
766+ expect ( errors ) . toEqual ( [ ] )
767+ expect ( pending ) . toEqual ( [ ] )
768+ } finally {
769+ process . env . LZ_BATCH_SIZE = originalBatchSize
770+ }
771+ } )
772+
773+ it ( 'should handle edge case of exact multiple of batch size' , async ( ) => {
774+ const originalBatchSize = process . env . LZ_BATCH_SIZE
775+ process . env . LZ_BATCH_SIZE = '3'
776+
777+ try {
778+ // Create exactly 6 transactions (2 * batch size)
779+ const eid = 30101
780+ const transactions = Array . from ( { length : 6 } , ( _ , i ) => ( {
781+ point : { eid, address : `0x${ i } ` } ,
782+ data : `0x${ i } ` ,
783+ } ) )
784+
785+ const receipt = { transactionHash : '0x0' }
786+ const wait = jest . fn ( ) . mockResolvedValue ( receipt )
787+ const response : OmniTransactionResponse = {
788+ transactionHash : '0x0' ,
789+ wait,
790+ }
791+
792+ const signAndSendBatch = jest . fn ( ) . mockResolvedValue ( response )
793+ const signAndSend = jest . fn ( ) . mockRejectedValue ( 'Oh god no' )
794+ const sign = jest . fn ( ) . mockRejectedValue ( 'Oh god no' )
795+ const signerFactory : OmniSignerFactory = jest
796+ . fn ( )
797+ . mockResolvedValue ( { signAndSend, signAndSendBatch, sign } )
798+ const signAndSendTransactions = createSignAndSend ( signerFactory )
799+
800+ const [ successful , errors , pending ] = await signAndSendTransactions ( transactions )
801+
802+ // Should have exactly 2 calls to signAndSendBatch with 3 transactions each
803+ expect ( signAndSendBatch ) . toHaveBeenCalledTimes ( 2 )
804+ expect ( signAndSendBatch ) . toHaveBeenNthCalledWith ( 1 , transactions . slice ( 0 , 3 ) )
805+ expect ( signAndSendBatch ) . toHaveBeenNthCalledWith ( 2 , transactions . slice ( 3 , 6 ) )
806+
807+ expect ( successful ) . toHaveLength ( 6 )
808+ expect ( errors ) . toEqual ( [ ] )
809+ expect ( pending ) . toEqual ( [ ] )
810+ } finally {
811+ process . env . LZ_BATCH_SIZE = originalBatchSize
812+ }
813+ } )
814+ } )
540815 } )
541816 } )
542817 } )
0 commit comments