diff --git a/packages/ledgerstate/ledgerstate.go b/packages/ledgerstate/ledgerstate.go
index caf22788e6857bbf8dd24f3f317a8343e3b7de1a..e941bfab368a6ad7220165e4b7077fa01f57a994 100644
--- a/packages/ledgerstate/ledgerstate.go
+++ b/packages/ledgerstate/ledgerstate.go
@@ -1,9 +1,6 @@
 package ledgerstate
 
 import (
-	"io/ioutil"
-	"os"
-	"os/exec"
 	"reflect"
 	"sort"
 	"strconv"
@@ -234,68 +231,6 @@ func (ledgerState *LedgerState) GenerateRealityVisualization(pngFilename string)
 	return graphviz.RenderPNG(graph, pngFilename)
 }
 
-func (ledgerState *LedgerState) GenerateVisualization() error {
-	di := dot.NewGraph(dot.Directed)
-
-	realityGraphs := make(map[RealityId]*dot.Graph)
-
-	getRealityGraph := func(realityId RealityId) *dot.Graph {
-		realityGraph, exists := realityGraphs[realityId]
-		if !exists {
-			realityGraph = di.Subgraph("REALITY: "+strings.Trim(realityId.String(), "\x00"), dot.ClusterOption{})
-			realityGraph.Attr("style", "filled")
-			realityGraph.Attr("color", "lightgrey")
-			realityGraph.Attr("fillcolor", "lightgrey")
-
-			realityGraphs[realityId] = realityGraph
-		}
-
-		return realityGraph
-	}
-
-	var drawTransferOutput func(transferOutput *TransferOutput) dot.Node
-	drawTransferOutput = func(transferOutput *TransferOutput) dot.Node {
-		transferOutputNode := getRealityGraph(transferOutput.GetRealityId()).Node(transferOutput.GetTransferHash().String() + "\n" + transferOutput.GetAddressHash().String())
-		transferOutputNode.Attr("style", "filled")
-		transferOutputNode.Attr("color", "white")
-		transferOutputNode.Attr("fillcolor", "white")
-
-		for transferHash, addresses := range transferOutput.GetConsumers() {
-			for _, addressHash := range addresses {
-				ledgerState.GetTransferOutput(NewTransferOutputReference(transferHash, addressHash)).Consume(func(object objectstorage.StorableObject) {
-					transferOutputNode.Edge(drawTransferOutput(object.(*TransferOutput)))
-				})
-			}
-		}
-
-		return transferOutputNode
-	}
-
-	ledgerState.ForEachTransferOutput(func(object *objectstorage.CachedObject) bool {
-		object.Consume(func(object objectstorage.StorableObject) {
-			drawTransferOutput(object.(*TransferOutput))
-		})
-
-		return true
-	}, MAIN_REALITY_ID)
-
-	d1 := []byte(di.String())
-	if err := ioutil.WriteFile("_tmp.dot", d1, 0755); err != nil {
-		return err
-	}
-
-	_, err := exec.Command("dot", "-Tpng", "_tmp.dot", "-o", "viz.png").Output()
-	if err != nil {
-		return err
-	}
-
-	if err := os.Remove("_tmp.dot"); err != nil {
-		return err
-	}
-
-	return nil
-}
-
 func (ledgerState *LedgerState) AggregateRealities(realityIds ...RealityId) *objectstorage.CachedObject {
 	switch len(realityIds) {
 	case 0:
diff --git a/packages/ledgerstate/ledgerstate_test.go b/packages/ledgerstate/ledgerstate_test.go
index 3ede10579c54fcf560b224a631e50a65c469b4f1..6c36a10338f778cd05f74d2028581108792a8bef 100644
--- a/packages/ledgerstate/ledgerstate_test.go
+++ b/packages/ledgerstate/ledgerstate_test.go
@@ -8,13 +8,11 @@ import (
 
 	"github.com/iotaledger/hive.go/parameter"
 
-	"github.com/iotaledger/goshimmer/packages/utils"
-
 	"github.com/iotaledger/hive.go/objectstorage"
 )
 
 var (
-	iota           = NewColor("IOTA")
+	iota_          = NewColor("IOTA")
 	eth            = NewColor("ETH")
 	transferHash1  = NewTransferHash("TRANSFER1")
 	transferHash2  = NewTransferHash("TRANSFER2")
@@ -63,7 +61,7 @@ func Benchmark(b *testing.B) {
 
 func Test(t *testing.T) {
 	ledgerState := NewLedgerState("testLedger").Prune().AddTransferOutput(
-		transferHash1, addressHash1, NewColoredBalance(eth, 1337), NewColoredBalance(iota, 1338),
+		transferHash1, addressHash1, NewColoredBalance(eth, 1337), NewColoredBalance(iota_, 1338),
 	)
 
 	ledgerState.CreateReality(pendingReality)
@@ -71,11 +69,11 @@ func Test(t *testing.T) {
 	transfer := NewTransfer(transferHash2).AddInput(
 		NewTransferOutputReference(transferHash1, addressHash1),
 	).AddOutput(
-		addressHash3, NewColoredBalance(iota, 338),
+		addressHash3, NewColoredBalance(iota_, 338),
 	).AddOutput(
 		addressHash3, NewColoredBalance(eth, 337),
 	).AddOutput(
-		addressHash4, NewColoredBalance(iota, 1000),
+		addressHash4, NewColoredBalance(iota_, 1000),
 	).AddOutput(
 		addressHash4, NewColoredBalance(eth, 1000),
 	)
@@ -87,11 +85,11 @@ func Test(t *testing.T) {
 	if err := ledgerState.BookTransfer(NewTransfer(transferHash3).AddInput(
 		NewTransferOutputReference(transferHash1, addressHash1),
 	).AddOutput(
-		addressHash3, NewColoredBalance(iota, 338),
+		addressHash3, NewColoredBalance(iota_, 338),
 	).AddOutput(
 		addressHash3, NewColoredBalance(eth, 337),
 	).AddOutput(
-		addressHash4, NewColoredBalance(iota, 1000),
+		addressHash4, NewColoredBalance(iota_, 1000),
 	).AddOutput(
 		addressHash4, NewColoredBalance(eth, 1000),
 	)); err != nil {
@@ -111,12 +109,20 @@ func Test(t *testing.T) {
 	})
 }
 
+var transferHashCounter = 0
+
 func generateRandomTransferHash() TransferHash {
-	return NewTransferHash(utils.RandomString(32))
+	transferHashCounter++
+
+	return NewTransferHash("TRANSFER" + strconv.Itoa(transferHashCounter))
 }
 
+var addressHashCounter = 0
+
 func generateRandomAddressHash() AddressHash {
-	return NewAddressHash(utils.RandomString(32))
+	addressHashCounter++
+
+	return NewAddressHash("ADDRESS" + strconv.Itoa(addressHashCounter))
 }
 
 func initializeLedgerStateWithBalances(numberOfBalances int) (ledgerState *LedgerState, result []*TransferOutputReference) {
@@ -126,7 +132,7 @@ func initializeLedgerStateWithBalances(numberOfBalances int) (ledgerState *Ledge
 		transferHash := generateRandomTransferHash()
 		addressHash := generateRandomAddressHash()
 
-		ledgerState.AddTransferOutput(transferHash, addressHash, NewColoredBalance(iota, 1337))
+		ledgerState.AddTransferOutput(transferHash, addressHash, NewColoredBalance(iota_, 1337))
 
 		result = append(result, NewTransferOutputReference(transferHash, addressHash))
 	}
@@ -147,7 +153,27 @@ func spend(ledgerState *LedgerState, transferOutputReferences ...*TransferOutput
 	addressHash := generateRandomAddressHash()
 
 	transfer := NewTransfer(transferHash).AddOutput(
-		addressHash, NewColoredBalance(iota, uint64(len(transferOutputReferences))*1337),
+		addressHash, NewColoredBalance(iota_, uint64(len(transferOutputReferences))*1337),
+	)
+	for _, transferOutputReference := range transferOutputReferences {
+		transfer.AddInput(transferOutputReference)
+	}
+
+	if err := ledgerState.BookTransfer(transfer); err != nil {
+		panic(err)
+	}
+
+	result = NewTransferOutputReference(transferHash, addressHash)
+
+	return
+}
+
+func spend2(ledgerState *LedgerState, transferOutputReferences ...*TransferOutputReference) (result *TransferOutputReference) {
+	transferHash := generateRandomTransferHash()
+	addressHash := generateRandomAddressHash()
+
+	transfer := NewTransfer(transferHash).AddOutput(
+		addressHash, NewColoredBalance(iota_, uint64(len(transferOutputReferences))*2*1337),
 	)
 	for _, transferOutputReference := range transferOutputReferences {
 		transfer.AddInput(transferOutputReference)
@@ -163,21 +189,29 @@ func spend(ledgerState *LedgerState, transferOutputReferences ...*TransferOutput
 }
 
 func TestElevateAggregatedReality(t *testing.T) {
-	ledgerState, transferOutputs := initializeLedgerStateWithBalances(2)
+	ledgerState, transferOutputs := initializeLedgerStateWithBalances(3)
 
 	// create 2 double spends
 	doubleSpentOutputs1 := doubleSpend(ledgerState, transferOutputs[0])
 	doubleSpentOutputs2 := doubleSpend(ledgerState, transferOutputs[1])
+	normalSpend := spend(ledgerState, transferOutputs[2])
+	_ = doubleSpend(ledgerState, normalSpend)
 
 	// send funds from one of the double spends further
 	spentInput := spend(ledgerState, doubleSpentOutputs1[1])
 
 	// aggregate further sent funds with other reality
-	spend(ledgerState, spentInput, doubleSpentOutputs2[0])
+	outputOfAggregatedReality := spend(ledgerState, spentInput, doubleSpentOutputs2[0])
 
 	// double spend further spend to elevate aggregated reality
 	spend(ledgerState, doubleSpentOutputs1[1])
 
+	// double spend funds of aggregated reality
+	spend(ledgerState, spentInput, doubleSpentOutputs2[0])
+
+	// spend funds of conflict in aggregated reality further
+	spend2(ledgerState, outputOfAggregatedReality)
+
 	time.Sleep(1000 * time.Millisecond)
 
 	objectstorage.WaitForWritesToFlush()
@@ -186,25 +220,21 @@ func TestElevateAggregatedReality(t *testing.T) {
 		t.Error(err)
 	}
 
-	ledgerState.ForEachTransferOutput(func(object *objectstorage.CachedObject) bool {
-		object.Consume(func(object objectstorage.StorableObject) {
-			fmt.Println(object.(*TransferOutput))
-		})
-
-		return true
-	}, MAIN_REALITY_ID)
+	if err := NewVisualizer(ledgerState).RenderTransferOutputs("outputs.png"); err != nil {
+		t.Error(err)
+	}
 }
 
 func TestElevate(t *testing.T) {
 	ledgerState := NewLedgerState("testLedger").Prune().AddTransferOutput(
-		transferHash1, addressHash1, NewColoredBalance(eth, 1337), NewColoredBalance(iota, 1338),
+		transferHash1, addressHash1, NewColoredBalance(eth, 1337), NewColoredBalance(iota_, 1338),
 	)
 
 	// create first legit spend
 	if err := ledgerState.BookTransfer(NewTransfer(transferHash2).AddInput(
 		NewTransferOutputReference(transferHash1, addressHash1),
 	).AddOutput(
-		addressHash2, NewColoredBalance(iota, 1338),
+		addressHash2, NewColoredBalance(iota_, 1338),
 	).AddOutput(
 		addressHash2, NewColoredBalance(eth, 1337),
 	)); err != nil {
@@ -215,7 +245,7 @@ func TestElevate(t *testing.T) {
 	if err := ledgerState.BookTransfer(NewTransfer(transferHash3).AddInput(
 		NewTransferOutputReference(transferHash2, addressHash2),
 	).AddOutput(
-		addressHash4, NewColoredBalance(iota, 1338),
+		addressHash4, NewColoredBalance(iota_, 1338),
 	).AddOutput(
 		addressHash4, NewColoredBalance(eth, 1337),
 	)); err != nil {
@@ -225,7 +255,7 @@ func TestElevate(t *testing.T) {
 	if err := ledgerState.BookTransfer(NewTransfer(transferHash4).AddInput(
 		NewTransferOutputReference(transferHash2, addressHash2),
 	).AddOutput(
-		addressHash4, NewColoredBalance(iota, 1338),
+		addressHash4, NewColoredBalance(iota_, 1338),
 	).AddOutput(
 		addressHash4, NewColoredBalance(eth, 1337),
 	)); err != nil {
@@ -238,7 +268,7 @@ func TestElevate(t *testing.T) {
 	).AddInput(
 		NewTransferOutputReference(transferHash4, addressHash4),
 	).AddOutput(
-		addressHash6, NewColoredBalance(iota, 2676),
+		addressHash6, NewColoredBalance(iota_, 2676),
 	).AddOutput(
 		addressHash6, NewColoredBalance(eth, 2674),
 	)); err != nil {
@@ -249,7 +279,7 @@ func TestElevate(t *testing.T) {
 	if err := ledgerState.BookTransfer(NewTransfer(transferHash5).AddInput(
 		NewTransferOutputReference(transferHash1, addressHash1),
 	).AddOutput(
-		addressHash5, NewColoredBalance(iota, 1338),
+		addressHash5, NewColoredBalance(iota_, 1338),
 	).AddOutput(
 		addressHash5, NewColoredBalance(eth, 1337),
 	)); err != nil {
diff --git a/packages/ledgerstate/outputs.png b/packages/ledgerstate/outputs.png
new file mode 100644
index 0000000000000000000000000000000000000000..3c9f53b9f5faaac17c0e4f188387bc0bdc5b8232
Binary files /dev/null and b/packages/ledgerstate/outputs.png differ
diff --git a/packages/ledgerstate/realities.png b/packages/ledgerstate/realities.png
index ee04a70dc73d14b3b2f6fb0e66d329a0c21c9f19..789f1c48bb59962df9ec5af99bcfb7410b387968 100644
Binary files a/packages/ledgerstate/realities.png and b/packages/ledgerstate/realities.png differ
diff --git a/packages/ledgerstate/reality.go b/packages/ledgerstate/reality.go
index 3645f10697a9699a661c835e263234772b8e6516..5be5e02ac4cc1c0f79c1ab55a1501e0d2ced86c8 100644
--- a/packages/ledgerstate/reality.go
+++ b/packages/ledgerstate/reality.go
@@ -144,7 +144,7 @@ func (reality *Reality) GetAncestorRealities() (result map[RealityId]*objectstor
 }
 
 // [DONE] Registers the conflict set in the Reality.
-func (reality *Reality) AddConflictSet(conflictSetId ConflictId) {
+func (reality *Reality) AddConflict(conflictSetId ConflictId) {
 	reality.conflictIdsMutex.Lock()
 	reality.conflictIds[conflictSetId] = void
 	reality.conflictIdsMutex.Unlock()
@@ -164,44 +164,47 @@ func (reality *Reality) BookTransfer(transfer *Transfer) (err error) {
 	return
 }
 
-func (reality *Reality) bookTransfer(transferHash TransferHash, inputs objectstorage.CachedObjects, outputs map[AddressHash][]*ColoredBalance) error {
-	if err := reality.verifyTransfer(inputs, outputs); err != nil {
-		return err
+func (reality *Reality) bookTransfer(transferHash TransferHash, inputs objectstorage.CachedObjects, outputs map[AddressHash][]*ColoredBalance) (err error) {
+	if err = reality.verifyTransfer(inputs, outputs); err != nil {
+		return
 	}
 
-	conflictSets, err := reality.consumeInputs(inputs, transferHash, outputs)
+	conflicts, err := reality.consumeInputs(inputs, transferHash, outputs)
 	if err != nil {
-		return err
+		return
 	}
 
-	if len(conflictSets) >= 1 {
-		var targetRealityId RealityId
-		copy(targetRealityId[:], transferHash[:])
+	if len(conflicts) >= 1 {
+		targetRealityId := transferHash.ToRealityId()
 
 		reality.CreateReality(targetRealityId).Consume(func(object objectstorage.StorableObject) {
 			targetReality := object.(*Reality)
 
-			for _, cachedConflictSet := range conflictSets {
+			for _, cachedConflictSet := range conflicts {
 				conflictSet := cachedConflictSet.Get().(*Conflict)
 
 				conflictSet.AddReality(targetRealityId)
-				targetReality.AddConflictSet(conflictSet.GetId())
+				targetReality.AddConflict(conflictSet.GetId())
 			}
 
 			for addressHash, coloredBalances := range outputs {
-				targetReality.bookTransferOutput(NewTransferOutput(reality.ledgerState, emptyRealityId, transferHash, addressHash, coloredBalances...))
+				if err = targetReality.bookTransferOutput(NewTransferOutput(reality.ledgerState, emptyRealityId, transferHash, addressHash, coloredBalances...)); err != nil {
+					return
+				}
 			}
 		})
 	} else {
 		for addressHash, coloredBalances := range outputs {
-			reality.bookTransferOutput(NewTransferOutput(reality.ledgerState, emptyRealityId, transferHash, addressHash, coloredBalances...))
+			if err = reality.bookTransferOutput(NewTransferOutput(reality.ledgerState, emptyRealityId, transferHash, addressHash, coloredBalances...)); err != nil {
+				return
+			}
 		}
 	}
 
-	conflictSets.Release()
+	conflicts.Release()
 	inputs.Release()
 
-	return nil
+	return
 }
 
 // Verifies the transfer and checks if it is valid (spends existing funds + the net balance is 0).
@@ -213,12 +216,12 @@ func (reality *Reality) verifyTransfer(inputs []*objectstorage.CachedObject, out
 			return errors.New("missing input in transfer")
 		}
 
-		transferOutput := cachedInput.Get().(*TransferOutput)
-		if !reality.DescendsFrom(transferOutput.GetRealityId()) {
+		input := cachedInput.Get().(*TransferOutput)
+		if !reality.DescendsFrom(input.GetRealityId()) {
 			return errors.New("the referenced funds do not exist in this reality")
 		}
 
-		for _, balance := range transferOutput.GetBalances() {
+		for _, balance := range input.GetBalances() {
 			totalColoredBalances[balance.GetColor()] += balance.GetValue()
 		}
 	}
@@ -243,24 +246,24 @@ func (reality *Reality) verifyTransfer(inputs []*objectstorage.CachedObject, out
 	return nil
 }
 
-// Marks the consumed inputs as spent and returns the corresponding ConflictSets if the inputs have been consumed before.
-func (reality *Reality) consumeInputs(inputs objectstorage.CachedObjects, transferHash TransferHash, outputs map[AddressHash][]*ColoredBalance) (conflictSets objectstorage.CachedObjects, err error) {
-	conflictSets = make(objectstorage.CachedObjects, 0)
+// Marks the consumed inputs as spent and returns the corresponding Conflict if the inputs have been consumed before.
+func (reality *Reality) consumeInputs(inputs objectstorage.CachedObjects, transferHash TransferHash, outputs map[AddressHash][]*ColoredBalance) (conflicts objectstorage.CachedObjects, err error) {
+	conflicts = make(objectstorage.CachedObjects, 0)
 
 	for _, input := range inputs {
-		consumedTransferOutput := input.Get().(*TransferOutput)
+		consumedInput := input.Get().(*TransferOutput)
 
-		if consumersToElevate, consumeErr := consumedTransferOutput.addConsumer(transferHash, outputs); consumeErr != nil {
+		if consumersToElevate, consumeErr := consumedInput.addConsumer(transferHash, outputs); consumeErr != nil {
 			err = consumeErr
 
 			return
 		} else if consumersToElevate != nil {
-			if conflictSet, conflictErr := reality.retrieveConflictSetForConflictingInput(consumedTransferOutput, consumersToElevate); conflictErr != nil {
+			if conflict, conflictErr := reality.retrieveConflictForConflictingInput(consumedInput, consumersToElevate); conflictErr != nil {
 				err = conflictErr
 
 				return
 			} else {
-				conflictSets = append(conflictSets, conflictSet)
+				conflicts = append(conflicts, conflict)
 			}
 		}
 
@@ -270,50 +273,61 @@ func (reality *Reality) consumeInputs(inputs objectstorage.CachedObjects, transf
 	return
 }
 
-func (reality *Reality) retrieveConflictSetForConflictingInput(input *TransferOutput, consumersToElevate map[TransferHash][]AddressHash) (conflictSet *objectstorage.CachedObject, err error) {
+func (reality *Reality) retrieveConflictForConflictingInput(input *TransferOutput, consumersToElevate map[TransferHash][]AddressHash) (conflict *objectstorage.CachedObject, err error) {
 	conflictSetId := NewConflictSetId(input.GetTransferHash(), input.GetAddressHash())
 
 	if len(consumersToElevate) >= 1 {
-		newConflictSet := newConflictSet(conflictSetId)
-		newConflictSet.ledgerState = reality.ledgerState
+		newConflict := newConflictSet(conflictSetId)
+		newConflict.ledgerState = reality.ledgerState
 
-		conflictSet = reality.ledgerState.conflictSets.Store(newConflictSet)
+		conflict = reality.ledgerState.conflictSets.Store(newConflict)
 
-		err = reality.createRealityForConflictingConsumers(consumersToElevate, conflictSet.Get().(*Conflict))
-		if err != nil {
-			return
-		}
+		err = reality.createRealityForConsumerOfConflictingInput(consumersToElevate, conflict.Get().(*Conflict))
 	} else {
-		conflictSet, err = reality.ledgerState.conflictSets.Load(conflictSetId[:])
-		if err != nil {
-			return
+		if conflict, err = reality.ledgerState.conflictSets.Load(conflictSetId[:]); err == nil {
+			conflict.Get().(*Conflict).ledgerState = reality.ledgerState
 		}
-		conflictSet.Get().(*Conflict).ledgerState = reality.ledgerState
 	}
 
 	return
 }
 
-func (reality *Reality) createRealityForConflictingConsumers(conflictingConsumers map[TransferHash][]AddressHash, conflictSet *Conflict) (err error) {
-	for transferHash, addressHashes := range conflictingConsumers {
-		// determine RealityId
-		elevatedRealityId := transferHash.ToRealityId()
-
-		// create new reality for every Transfer
-		reality.CreateReality(elevatedRealityId).Consume(func(object objectstorage.StorableObject) {
-			elevatedReality := object.(*Reality)
-
-			// register Reality <-> Conflict
-			conflictSet.AddReality(elevatedRealityId)
-			elevatedReality.AddConflictSet(conflictSet.GetId())
-
-			// elevate TransferOutputs to the new Reality
-			for _, addressHash := range addressHashes {
-				if err = reality.elevateTransferOutput(NewTransferOutputReference(transferHash, addressHash), elevatedReality); err != nil {
-					return
+// Creates a Reality for the consumers of the conflicting inputs and registers it as part of the corresponding Conflict.
+func (reality *Reality) createRealityForConsumerOfConflictingInput(consumersOfConflictingInput map[TransferHash][]AddressHash, conflict *Conflict) (err error) {
+	for transferHash, addressHashes := range consumersOfConflictingInput {
+		var elevatedRealityId = transferHash.ToRealityId()
+		var realityIsNew bool
+		var cachedElevatedReality *objectstorage.CachedObject
+
+		// Retrieve the Reality for this Transfer or create one if no Reality exists, yet.
+		if cachedElevatedReality, err = reality.ledgerState.realities.ComputeIfAbsent(elevatedRealityId[:], func(key []byte) (object objectstorage.StorableObject, e error) {
+			newReality := newReality(elevatedRealityId, reality.id)
+			newReality.ledgerState = reality.ledgerState
+
+			realityIsNew = true
+
+			return newReality, nil
+		}); err == nil {
+			cachedElevatedReality.Store().Consume(func(object objectstorage.StorableObject) {
+				elevatedReality := object.(*Reality)
+
+				// We register every Conflict with the Reality (independent if it is "new" or not), to reflect its
+				// association to all corresponding Conflicts. (Note: A Reality can be part of multiple Conflicts if the
+				// Transfer that is associated to this Reality consumes multiple inputs.
+				conflict.AddReality(elevatedRealityId)
+				elevatedReality.AddConflict(conflict.GetId())
+
+				// A transaction can consume multiple inputs. We only elevate the consumers of a Reality once (when the
+				// Reality is created the first time).
+				if realityIsNew {
+					for _, addressHash := range addressHashes {
+						if err = reality.elevateTransferOutput(NewTransferOutputReference(transferHash, addressHash), elevatedReality); err != nil {
+							return
+						}
+					}
 				}
-			}
-		})
+			})
+		}
 	}
 
 	return
diff --git a/packages/ledgerstate/visualizer.go b/packages/ledgerstate/visualizer.go
new file mode 100644
index 0000000000000000000000000000000000000000..53c700a7017371550f76ad0922cc011a2928a273
--- /dev/null
+++ b/packages/ledgerstate/visualizer.go
@@ -0,0 +1,182 @@
+package ledgerstate
+
+import (
+	"strings"
+
+	"github.com/iotaledger/goshimmer/packages/graphviz"
+
+	"github.com/emicklei/dot"
+	"github.com/iotaledger/hive.go/objectstorage"
+)
+
+type transferOutputId [transferHashLength + addressHashLength]byte
+
+type Visualizer struct {
+	ledgerState         *LedgerState
+	graph               *dot.Graph
+	realitySubGraphs    map[RealityId]*dot.Graph
+	transferOutputNodes map[transferOutputId]dot.Node
+}
+
+func NewVisualizer(ledgerState *LedgerState) *Visualizer {
+	return &Visualizer{
+		ledgerState: ledgerState,
+	}
+}
+
+func (visualizer *Visualizer) RenderTransferOutputs(pngFileName string) error {
+	visualizer.reset()
+
+	visualizer.graph.Attr("ranksep", "1.0 equally")
+	visualizer.graph.Attr("compound", "true")
+
+	visualizer.ledgerState.ForEachTransferOutput(func(object *objectstorage.CachedObject) bool {
+		object.Consume(func(object objectstorage.StorableObject) {
+			visualizer.drawTransferOutput(object.(*TransferOutput))
+		})
+
+		return true
+	}, MAIN_REALITY_ID)
+
+	return graphviz.RenderPNG(visualizer.graph, pngFileName)
+}
+
+func (visualizer *Visualizer) reset() *Visualizer {
+	visualizer.graph = dot.NewGraph(dot.Directed)
+	visualizer.realitySubGraphs = make(map[RealityId]*dot.Graph)
+	visualizer.transferOutputNodes = make(map[transferOutputId]dot.Node)
+
+	return visualizer
+}
+
+func (visualizer *Visualizer) drawTransferOutput(transferOutput *TransferOutput) dot.Node {
+	transferOutputIdentifier := visualizer.generateTransferOutputId(transferOutput)
+	transferOutputNode, transferOutputDrawn := visualizer.transferOutputNodes[transferOutputIdentifier]
+
+	if !transferOutputDrawn {
+		transferOutputNode = visualizer.getRealitySubGraph(transferOutput.GetRealityId()).Node("OUTPUT: " + strings.Trim(transferOutput.GetTransferHash().String(), "\x00") + " / " + strings.Trim(transferOutput.GetAddressHash().String(), "\x00"))
+
+		visualizer.styleTransferOutputNode(transferOutputNode)
+
+		for transferHash, addresses := range transferOutput.GetConsumers() {
+			for _, addressHash := range addresses {
+				visualizer.ledgerState.GetTransferOutput(NewTransferOutputReference(transferHash, addressHash)).Consume(func(object objectstorage.StorableObject) {
+					transferOutputNode.Edge(visualizer.drawTransferOutput(object.(*TransferOutput)))
+				})
+			}
+		}
+
+		visualizer.transferOutputNodes[transferOutputIdentifier] = transferOutputNode
+	}
+
+	return transferOutputNode
+}
+
+func (visualizer *Visualizer) generateTransferOutputId(transferOutput *TransferOutput) (result transferOutputId) {
+	transferHash := transferOutput.GetTransferHash()
+	addressHash := transferOutput.GetAddressHash()
+
+	copy(result[:], transferHash[:])
+	copy(result[transferHashLength:], addressHash[:])
+
+	return
+}
+
+func (Visualizer *Visualizer) styleTransferOutputNode(transferOutputNode dot.Node) {
+	transferOutputNode.Attr("fontname", "helvetica")
+	transferOutputNode.Attr("fontsize", "11")
+	transferOutputNode.Attr("style", "filled")
+	transferOutputNode.Attr("shape", "box")
+	transferOutputNode.Attr("color", "#6C8EBF")
+	transferOutputNode.Attr("fillcolor", "white")
+}
+
+func (visualizer *Visualizer) getRealitySubGraph(realityId RealityId) *dot.Graph {
+	realityGraph, exists := visualizer.realitySubGraphs[realityId]
+	if !exists {
+		visualizer.ledgerState.GetReality(realityId).Consume(func(object objectstorage.StorableObject) {
+			reality := object.(*Reality)
+
+			parentRealities := reality.GetParentRealityIds()
+			switch true {
+			case len(parentRealities) > 1:
+				realityGraph = visualizer.getRealitySubGraph(MAIN_REALITY_ID).Subgraph(visualizer.generateRealityName(parentRealities.ToList()...), dot.ClusterOption{})
+
+				visualizer.styleRealitySubGraph(realityGraph, realityTypeAggregated)
+
+				//dummyNode := realityGraph.Node(ledgerState.generateRealityName(parentRealities.ToList()...) + "_dummy")
+				//dummyNode.Attr("shape", "point")
+				//dummyNode.Attr("style", "invis")
+				//dummyNode.Attr("peripheries", "0")
+				//dummyNode.Attr("height", "0")
+				//dummyNode.Attr("width", "0")
+			case len(parentRealities) == 1:
+				for parentRealityId := range parentRealities {
+					realityGraph = visualizer.getRealitySubGraph(parentRealityId).Subgraph(visualizer.generateRealityName(realityId), dot.ClusterOption{})
+
+					visualizer.styleRealitySubGraph(realityGraph, realityTypeDefault)
+				}
+			default:
+				realityGraph = visualizer.graph.Subgraph(visualizer.generateRealityName(realityId), dot.ClusterOption{})
+
+				visualizer.styleRealitySubGraph(realityGraph, realityTypeMain)
+			}
+		})
+
+		visualizer.realitySubGraphs[realityId] = realityGraph
+	}
+
+	return realityGraph
+}
+
+func (visualizer *Visualizer) styleRealitySubGraph(realitySubGraph *dot.Graph, realityType realityType) {
+	realitySubGraph.Attr("fontname", "helvetica")
+	realitySubGraph.Attr("fontsize", "11")
+	realitySubGraph.Attr("style", "filled")
+	realitySubGraph.Attr("nodesep", "0")
+
+	switch realityType {
+	case realityTypeAggregated:
+		realitySubGraph.Attr("color", "#9673A6")
+		realitySubGraph.Attr("fillcolor", "#E1D5E7")
+	case realityTypeMain:
+		realitySubGraph.Attr("color", "#D6B656")
+		realitySubGraph.Attr("fillcolor", "#FFF2CC")
+	case realityTypeDefault:
+		realitySubGraph.Attr("color", "#6C8EBF")
+		realitySubGraph.Attr("fillcolor", "#DAE8FC")
+	}
+}
+
+func (visualizer *Visualizer) generateRealityName(realityIds ...RealityId) string {
+	if len(realityIds) > 1 {
+		result := "AGGREGATED REALITY [ "
+
+		realityIdCount := len(realityIds)
+		for id, realityId := range realityIds {
+			if id == realityIdCount-1 {
+				result += strings.Trim(realityId.String(), "\x00")
+			} else {
+				result += strings.Trim(realityId.String(), "\x00") + " + "
+			}
+		}
+
+		result += " ]"
+
+		return result
+	} else {
+		if realityIds[0] == MAIN_REALITY_ID {
+			return strings.Trim(realityIds[0].String(), "\x00")
+		}
+
+		return "REALITY [ " + strings.Trim(realityIds[0].String(), "\x00") + " ]"
+	}
+}
+
+type realityType int
+
+const (
+	realityTypeAggregated = iota
+	realityTypeMain
+	realityTypeDefault
+)