diff --git a/backup/backup_test.go b/backup/backup_test.go
index b77d5f4..addff9a 100644
--- a/backup/backup_test.go
+++ b/backup/backup_test.go
@@ -4,6 +4,7 @@ import (
 	"bufio"
 	"context"
 	"errors"
+	"fmt"
 	"os"
 	"testing"
 	"time"
@@ -83,191 +84,268 @@ func TestBackup_DetermineRightBound(t *testing.T) {
 	})
 }
 
-func TestBackup_ExecuteBackup_FixedRange(t *testing.T) {
-	t.Parallel()
-
-	var (
-		tempFile = createTempFile(t)
+func generateMemo(blockNum, txNum uint64) string {
+	return fmt.Sprintf("example transaction %d:%d", blockNum, txNum)
+}
 
-		fromBlock uint64 = 10
-		toBlock          = fromBlock + 10
+var blockTime = time.Date(1987, 0o6, 24, 6, 32, 11, 0, time.FixedZone("Europe/Madrid", 0))
 
-		exampleTx = std.Tx{
-			Memo: "example transaction",
-		}
+func generateBlocks(t *testing.T, from, to, nTxs uint64) []*client.Block {
+	t.Helper()
 
-		cfg = DefaultConfig()
+	// generateBlocks return only blocks containing transaction
+	if nTxs == 0 {
+		return nil
+	}
 
-		blockTime = time.Date(1987, 0o6, 24, 6, 32, 11, 0, time.FixedZone("Europe/Madrid", 0))
+	blocks := make([]*client.Block, to-from+1)
 
-		mockClient = &mockClient{
-			getLatestBlockNumberFn: func() (uint64, error) {
-				return toBlock, nil
-			},
-			getBlockFn: func(blockNum uint64) (*client.Block, error) {
-				// Sanity check
-				if blockNum < fromBlock && blockNum > toBlock {
-					t.Fatal("invalid block number requested")
-				}
+	for i := range blocks {
+		txs := make([]std.Tx, nTxs)
+		blockNum := from + uint64(i)
 
-				return &client.Block{
-					Height:    blockNum,
-					Timestamp: blockTime.Add(time.Duration(blockNum) * time.Minute).UnixMilli(),
-					Txs:       []std.Tx{exampleTx},
-				}, nil // 1 tx per block
-			},
+		for j := range txs {
+			txs[j].Memo = generateMemo(blockNum, uint64(j))
 		}
-	)
-
-	// Temp file cleanup
-	t.Cleanup(func() {
-		require.NoError(t, tempFile.Close())
-		require.NoError(t, os.Remove(tempFile.Name()))
-	})
 
-	// Set the config
-	cfg.FromBlock = fromBlock
-	cfg.ToBlock = &toBlock
-
-	s := NewService(mockClient, standard.NewWriter(tempFile), WithLogger(noop.New()))
+		blocks[i] = &client.Block{
+			Height:    blockNum,
+			Timestamp: blockTime.Add(time.Duration(blockNum) * time.Minute).UnixMilli(),
+			Txs:       txs,
+		}
+	}
 
-	// Run the backup procedure
-	require.NoError(
-		t,
-		s.ExecuteBackup(
-			context.Background(),
-			cfg,
-		),
-	)
+	return blocks
+}
 
-	// Read the output file
-	fileRaw, err := os.Open(tempFile.Name())
-	require.NoError(t, err)
+type testCase struct {
+	name        string
+	batchSize   uint
+	fromBlock   uint64
+	toBlock     uint64
+	txsPerBlock uint64
+}
 
-	// Set up a line-by-line scanner
-	scanner := bufio.NewScanner(fileRaw)
+var testCases = []testCase{
+	// Batch 0 (should be forced to fetch by 1 by config)
+	{name: "batch 0/10 blocks/3 txs", batchSize: 0, fromBlock: 1, toBlock: 10, txsPerBlock: 3},
+	{name: "batch 0/10 blocks/1 tx", batchSize: 0, fromBlock: 1, toBlock: 10, txsPerBlock: 1},
+	{name: "batch 0/10 blocks/0 tx", batchSize: 0, fromBlock: 1, toBlock: 10, txsPerBlock: 0},
+	// Batch 1 (fetch 1 by 1)
+	{name: "batch 1/10 blocks/3 txs", batchSize: 1, fromBlock: 1, toBlock: 10, txsPerBlock: 3},
+	{name: "batch 1/10 blocks/1 tx", batchSize: 1, fromBlock: 1, toBlock: 10, txsPerBlock: 1},
+	{name: "batch 1/10 blocks/0 tx", batchSize: 1, fromBlock: 1, toBlock: 10, txsPerBlock: 0},
+	// Batch 6 (first fetch 6, then 4)
+	{name: "batch 6/10 blocks/3 txs", batchSize: 6, fromBlock: 1, toBlock: 10, txsPerBlock: 3},
+	{name: "batch 6/10 blocks/1 tx", batchSize: 6, fromBlock: 1, toBlock: 10, txsPerBlock: 1},
+	{name: "batch 6/10 blocks/0 tx", batchSize: 6, fromBlock: 1, toBlock: 10, txsPerBlock: 0},
+	// Batch 10 (fetch all blocks in 1 batch)
+	{name: "batch 10/10 blocks/3 txs", batchSize: 10, fromBlock: 1, toBlock: 10, txsPerBlock: 3},
+	{name: "batch 10/10 blocks/1 tx", batchSize: 10, fromBlock: 1, toBlock: 10, txsPerBlock: 1},
+	{name: "batch 10/10 blocks/0 tx", batchSize: 10, fromBlock: 1, toBlock: 10, txsPerBlock: 0},
+	// Batch 11 (batch size (11) bigger than block count within range (10))
+	{name: "batch 11/10 blocks/3 txs", batchSize: 11, fromBlock: 1, toBlock: 10, txsPerBlock: 3},
+	{name: "batch 11/10 blocks/1 tx", batchSize: 11, fromBlock: 1, toBlock: 10, txsPerBlock: 1},
+	{name: "batch 11/10 blocks/0 tx", batchSize: 11, fromBlock: 1, toBlock: 10, txsPerBlock: 0},
+}
 
-	expectedBlock := fromBlock
+func TestBackup_ExecuteBackup_FixedRange(t *testing.T) {
+	t.Parallel()
 
-	// Iterate over each line in the file
-	for scanner.Scan() {
-		var txData types.TxData
+	//nolint:thelper,gocritic
+	testFunc := func(t *testing.T, tCase testCase) {
+		t.Run(tCase.name, func(t *testing.T) {
+			t.Parallel()
+
+			var (
+				tempFile = createTempFile(t)
+				cfg      = DefaultConfig()
+
+				mockClient = &mockClient{
+					getLatestBlockNumberFn: func() (uint64, error) {
+						return tCase.toBlock, nil
+					},
+					getBlocksFn: func(ctx context.Context, from, to uint64) ([]*client.Block, error) {
+						// Sanity check
+						if from > to {
+							t.Fatal("invalid block number requested")
+						}
+
+						return generateBlocks(t, from, to, tCase.txsPerBlock), nil
+					},
+				}
+			)
+
+			// Temp file cleanup
+			t.Cleanup(func() {
+				require.NoError(t, tempFile.Close())
+				require.NoError(t, os.Remove(tempFile.Name()))
+			})
+
+			// Set the config
+			cfg.FromBlock = tCase.fromBlock
+			cfg.ToBlock = &tCase.toBlock
+
+			s := NewService(mockClient, standard.NewWriter(tempFile), WithLogger(noop.New()), WithBatchSize(tCase.batchSize))
+
+			// Run the backup procedure
+			require.NoError(
+				t,
+				s.ExecuteBackup(
+					context.Background(),
+					cfg,
+				),
+			)
+
+			// Read the output file
+			fileRaw, err := os.Open(tempFile.Name())
+			require.NoError(t, err)
+
+			// Set up a line-by-line scanner
+			scanner := bufio.NewScanner(fileRaw)
+			lineCount := uint64(0)
+
+			// Iterate over each line in the file
+			for ; scanner.Scan(); lineCount++ {
+				var txData types.TxData
+
+				// Unmarshal the JSON data into the Person struct
+				if err := amino.UnmarshalJSON(scanner.Bytes(), &txData); err != nil {
+					t.Fatalf("unable to unmarshal JSON line, %v", err)
+				}
 
-		// Unmarshal the JSON data into the Person struct
-		if err := amino.UnmarshalJSON(scanner.Bytes(), &txData); err != nil {
-			t.Fatalf("unable to unmarshal JSON line, %v", err)
-		}
+				expectedBlock := tCase.fromBlock + lineCount/tCase.txsPerBlock
+				expectedTx := lineCount % tCase.txsPerBlock
+				expectedTxMemo := generateMemo(expectedBlock, expectedTx)
+				assert.Equal(t, expectedBlock, txData.BlockNum)
+				assert.Equal(t, expectedTxMemo, txData.Tx.Memo)
+				assert.Equal(t, blockTime.Add(time.Duration(expectedBlock)*time.Minute).Local(), time.UnixMilli(txData.Timestamp))
+			}
 
-		assert.Equal(t, expectedBlock, txData.BlockNum)
-		assert.Equal(t, exampleTx, txData.Tx)
-		assert.Equal(t, blockTime.Add(time.Duration(expectedBlock)*time.Minute).Local(), time.UnixMilli(txData.Timestamp))
+			// Check for errors during scanning
+			if err := scanner.Err(); err != nil {
+				t.Fatalf("error encountered during scan, %v", err)
+			}
 
-		expectedBlock++
+			// Ensure we found 1 line by expected transaction
+			expectedTxCount := (tCase.toBlock - tCase.fromBlock + 1) * tCase.txsPerBlock
+			assert.Equal(t, expectedTxCount, lineCount)
+		})
 	}
 
-	// Check for errors during scanning
-	if err := scanner.Err(); err != nil {
-		t.Fatalf("error encountered during scan, %v", err)
+	for _, tCase := range testCases {
+		testFunc(t, tCase)
 	}
 }
 
 func TestBackup_ExecuteBackup_Watch(t *testing.T) {
 	t.Parallel()
 
-	// Set up the context that is controlled by the test
-	ctx, cancelFn := context.WithCancel(context.Background())
-	defer cancelFn()
-
-	var (
-		tempFile = createTempFile(t)
-
-		fromBlock uint64 = 10
-		toBlock          = fromBlock + 10
-
-		requestToBlock = toBlock / 2
-
-		exampleTx = std.Tx{
-			Memo: "example transaction",
-		}
-
-		cfg = DefaultConfig()
-
-		blockTime = time.Date(1987, 0o6, 24, 6, 32, 11, 0, time.FixedZone("Europe/Madrid", 0))
-
-		mockClient = &mockClient{
-			getLatestBlockNumberFn: func() (uint64, error) {
-				return toBlock, nil
-			},
-			getBlockFn: func(blockNum uint64) (*client.Block, error) {
-				// Sanity check
-				if blockNum < fromBlock && blockNum > toBlock {
-					t.Fatal("invalid block number requested")
+	//nolint:thelper,gocritic
+	testFunc := func(t *testing.T, tCase testCase) {
+		t.Run(tCase.name, func(t *testing.T) {
+			t.Parallel()
+
+			// Set up the context that is controlled by the test
+			ctx, cancelFn := context.WithCancel(context.Background())
+			defer cancelFn()
+
+			var (
+				tempFile   = createTempFile(t)
+				cfg        = DefaultConfig()
+				watchStart = tCase.toBlock / 2
+				latest     = watchStart
+
+				mockClient = &mockClient{
+					getLatestBlockNumberFn: func() (uint64, error) {
+						if latest == tCase.toBlock { // Simulate last block incrementing while in watch mode
+							cancelFn()
+						} else {
+							latest++
+						}
+
+						return latest, nil
+					},
+					getBlocksFn: func(ctx context.Context, from, to uint64) ([]*client.Block, error) {
+						// Sanity check
+						if from > to {
+							t.Fatal("invalid block number requested")
+						}
+
+						switch {
+						case from > tCase.toBlock:
+							return nil, nil
+						case from > watchStart: // Watch mode, return blocks 1 by 1
+							return generateBlocks(t, from, from, tCase.txsPerBlock), nil
+						case to > latest:
+							return generateBlocks(t, from, latest, tCase.txsPerBlock), nil
+						}
+
+						return generateBlocks(t, from, to, tCase.txsPerBlock), nil
+					},
 				}
-
-				if blockNum == toBlock {
-					// End of the road, close the watch process
-					cancelFn()
+			)
+
+			// Temp file cleanup
+			t.Cleanup(func() {
+				require.NoError(t, tempFile.Close())
+				require.NoError(t, os.Remove(tempFile.Name()))
+			})
+
+			// Set the config
+			cfg.FromBlock = tCase.fromBlock
+			cfg.ToBlock = &tCase.toBlock
+			cfg.Watch = true
+
+			s := NewService(mockClient, standard.NewWriter(tempFile), WithLogger(noop.New()), WithBatchSize(tCase.batchSize))
+			s.watchInterval = 10 * time.Millisecond // make the interval almost instant for the test
+
+			// Run the backup procedure
+			require.NoError(
+				t,
+				s.ExecuteBackup(
+					ctx,
+					cfg,
+				),
+			)
+
+			// Read the output file
+			fileRaw, err := os.Open(tempFile.Name())
+			require.NoError(t, err)
+
+			// Set up a line-by-line scanner
+			scanner := bufio.NewScanner(fileRaw)
+			lineCount := uint64(0)
+
+			// Iterate over each line in the file
+			for ; scanner.Scan(); lineCount++ {
+				var txData types.TxData
+
+				// Unmarshal the JSON data into the Person struct
+				if err := amino.UnmarshalJSON(scanner.Bytes(), &txData); err != nil {
+					t.Fatalf("unable to unmarshal JSON line, %v", err)
 				}
 
-				return &client.Block{
-					Height:    blockNum,
-					Timestamp: blockTime.Add(time.Duration(blockNum) * time.Minute).UnixMilli(),
-					Txs:       []std.Tx{exampleTx},
-				}, nil // 1 tx per block
-			},
-		}
-	)
-
-	// Temp file cleanup
-	t.Cleanup(func() {
-		require.NoError(t, tempFile.Close())
-		require.NoError(t, os.Remove(tempFile.Name()))
-	})
-
-	// Set the config
-	cfg.FromBlock = fromBlock
-	cfg.ToBlock = &requestToBlock
-	cfg.Watch = true
-
-	s := NewService(mockClient, standard.NewWriter(tempFile), WithLogger(noop.New()))
-	s.watchInterval = 10 * time.Millisecond // make the interval almost instant for the test
-
-	// Run the backup procedure
-	require.NoError(
-		t,
-		s.ExecuteBackup(
-			ctx,
-			cfg,
-		),
-	)
-
-	// Read the output file
-	fileRaw, err := os.Open(tempFile.Name())
-	require.NoError(t, err)
-
-	// Set up a line-by-line scanner
-	scanner := bufio.NewScanner(fileRaw)
-
-	expectedBlock := fromBlock
-
-	// Iterate over each line in the file
-	for scanner.Scan() {
-		var txData types.TxData
-
-		// Unmarshal the JSON data into the Person struct
-		if err := amino.UnmarshalJSON(scanner.Bytes(), &txData); err != nil {
-			t.Fatalf("unable to unmarshal JSON line, %v", err)
-		}
+				expectedBlock := tCase.fromBlock + lineCount/tCase.txsPerBlock
+				expectedTx := lineCount % tCase.txsPerBlock
+				expectedTxMemo := generateMemo(expectedBlock, expectedTx)
+				assert.Equal(t, expectedBlock, txData.BlockNum)
+				assert.Equal(t, expectedTxMemo, txData.Tx.Memo)
+				assert.Equal(t, blockTime.Add(time.Duration(expectedBlock)*time.Minute).Local(), time.UnixMilli(txData.Timestamp))
+			}
 
-		assert.Equal(t, expectedBlock, txData.BlockNum)
-		assert.Equal(t, exampleTx, txData.Tx)
-		assert.Equal(t, blockTime.Add(time.Duration(expectedBlock)*time.Minute).Local(), time.UnixMilli(txData.Timestamp))
+			// Check for errors during scanning
+			if err := scanner.Err(); err != nil {
+				t.Fatalf("error encountered during scan, %v", err)
+			}
 
-		expectedBlock++
+			// Ensure we found 1 line by expected transaction
+			expectedTxCount := (tCase.toBlock - tCase.fromBlock + 1) * tCase.txsPerBlock
+			assert.Equal(t, expectedTxCount, lineCount)
+		})
 	}
 
-	// Check for errors during scanning
-	if err := scanner.Err(); err != nil {
-		t.Fatalf("error encountered during scan, %v", err)
+	for _, tCase := range testCases {
+		testFunc(t, tCase)
 	}
 }
diff --git a/backup/mock_test.go b/backup/mock_test.go
index a819843..db95c66 100644
--- a/backup/mock_test.go
+++ b/backup/mock_test.go
@@ -1,17 +1,19 @@
 package backup
 
 import (
+	"context"
+
 	"github.com/gnolang/tx-archive/backup/client"
 )
 
 type (
 	getLatestBlockNumberDelegate func() (uint64, error)
-	getBlockDelegate             func(uint64) (*client.Block, error)
+	getBlocksDelegate            func(context.Context, uint64, uint64) ([]*client.Block, error)
 )
 
 type mockClient struct {
 	getLatestBlockNumberFn getLatestBlockNumberDelegate
-	getBlockFn             getBlockDelegate
+	getBlocksFn            getBlocksDelegate
 }
 
 func (m *mockClient) GetLatestBlockNumber() (uint64, error) {
@@ -22,9 +24,9 @@ func (m *mockClient) GetLatestBlockNumber() (uint64, error) {
 	return 0, nil
 }
 
-func (m *mockClient) GetBlock(blockNum uint64) (*client.Block, error) {
-	if m.getBlockFn != nil {
-		return m.getBlockFn(blockNum)
+func (m *mockClient) GetBlocks(ctx context.Context, from, to uint64) ([]*client.Block, error) {
+	if m.getBlocksFn != nil {
+		return m.getBlocksFn(ctx, from, to)
 	}
 
 	return nil, nil