diff --git a/dapps/valuetransfers/dapp.go b/dapps/valuetransfers/dapp.go
index 8cd496d2c6e2acdfa1899c85fceed4a112bcbd57..1fd95f1ee284a702cec70d6aa0064e0a93bb608c 100644
--- a/dapps/valuetransfers/dapp.go
+++ b/dapps/valuetransfers/dapp.go
@@ -9,11 +9,9 @@ import (
 	"github.com/iotaledger/hive.go/node"
 
 	"github.com/iotaledger/goshimmer/dapps/valuetransfers/packages/branchmanager"
-	"github.com/iotaledger/goshimmer/dapps/valuetransfers/packages/ledgerstate"
 	valuepayload "github.com/iotaledger/goshimmer/dapps/valuetransfers/packages/payload"
 	"github.com/iotaledger/goshimmer/dapps/valuetransfers/packages/tangle"
 	"github.com/iotaledger/goshimmer/dapps/valuetransfers/packages/transaction"
-	"github.com/iotaledger/goshimmer/dapps/valuetransfers/packages/utxodag"
 	"github.com/iotaledger/goshimmer/packages/binary/messagelayer/message"
 	messageTangle "github.com/iotaledger/goshimmer/packages/binary/messagelayer/tangle"
 	"github.com/iotaledger/goshimmer/packages/database"
@@ -37,11 +35,8 @@ var (
 	// Tangle represents the value tangle that is used to express votes on value transactions.
 	Tangle *tangle.Tangle
 
-	// UTXODAG represents the flow of funds that is derived from the value tangle.
-	UTXODAG *utxodag.UTXODAG
-
 	// LedgerState represents the ledger state, that keeps track of the liked branches and offers an API to access funds.
-	LedgerState *ledgerstate.LedgerState
+	LedgerState *tangle.LedgerState
 
 	// log holds a reference to the logger used by this app.
 	log *logger.Logger
@@ -54,14 +49,13 @@ func configure(_ *node.Plugin) {
 
 	// create instances
 	Tangle = tangle.New(database.GetBadgerInstance())
-	UTXODAG = utxodag.New(database.GetBadgerInstance(), Tangle)
 
 	// subscribe to message-layer
 	messagelayer.Tangle.Events.MessageSolid.Attach(events.NewClosure(onReceiveMessageFromMessageLayer))
 
 	// setup behavior of package instances
-	UTXODAG.Events.TransactionBooked.Attach(events.NewClosure(onTransactionBooked))
-	UTXODAG.Events.Fork.Attach(events.NewClosure(onForkOfFirstConsumer))
+	Tangle.Events.TransactionBooked.Attach(events.NewClosure(onTransactionBooked))
+	Tangle.Events.Fork.Attach(events.NewClosure(onForkOfFirstConsumer))
 
 	configureFPC()
 	// TODO: DECIDE WHAT WE SHOULD DO IF FPC FAILS -> cry
@@ -76,12 +70,12 @@ func configure(_ *node.Plugin) {
 
 		switch opinion {
 		case vote.Like:
-			if _, err := UTXODAG.BranchManager().SetBranchPreferred(branchID, true); err != nil {
+			if _, err := Tangle.BranchManager().SetBranchPreferred(branchID, true); err != nil {
 				panic(err)
 			}
 			// TODO: merge branch mutations into the parent branch
 		case vote.Dislike:
-			if _, err := UTXODAG.BranchManager().SetBranchPreferred(branchID, false); err != nil {
+			if _, err := Tangle.BranchManager().SetBranchPreferred(branchID, false); err != nil {
 				panic(err)
 			}
 			// TODO: merge branch mutations into the parent branch / cleanup
@@ -93,7 +87,6 @@ func run(*node.Plugin) {
 	_ = daemon.BackgroundWorker("Tangle", func(shutdownSignal <-chan struct{}) {
 		<-shutdownSignal
 		Tangle.Shutdown()
-		UTXODAG.Shutdown()
 	}, shutdown.PriorityTangle)
 
 	runFPC()
@@ -127,7 +120,7 @@ func onReceiveMessageFromMessageLayer(cachedMessage *message.CachedMessage, cach
 	Tangle.AttachPayload(valuePayload)
 }
 
-func onTransactionBooked(cachedTransaction *transaction.CachedTransaction, cachedTransactionMetadata *utxodag.CachedTransactionMetadata, cachedBranch *branchmanager.CachedBranch, conflictingInputs []transaction.OutputID, decisionPending bool) {
+func onTransactionBooked(cachedTransaction *transaction.CachedTransaction, cachedTransactionMetadata *tangle.CachedTransactionMetadata, cachedBranch *branchmanager.CachedBranch, conflictingInputs []transaction.OutputID, decisionPending bool) {
 	defer cachedTransaction.Release()
 	defer cachedTransactionMetadata.Release()
 	defer cachedBranch.Release()
@@ -174,7 +167,7 @@ func onTransactionBooked(cachedTransaction *transaction.CachedTransaction, cache
 }
 
 // TODO: clarify what we do here
-func onForkOfFirstConsumer(cachedTransaction *transaction.CachedTransaction, cachedTransactionMetadata *utxodag.CachedTransactionMetadata, cachedBranch *branchmanager.CachedBranch, conflictingInputs []transaction.OutputID) {
+func onForkOfFirstConsumer(cachedTransaction *transaction.CachedTransaction, cachedTransactionMetadata *tangle.CachedTransactionMetadata, cachedBranch *branchmanager.CachedBranch, conflictingInputs []transaction.OutputID) {
 	defer cachedTransaction.Release()
 	defer cachedTransactionMetadata.Release()
 	defer cachedBranch.Release()
@@ -197,7 +190,7 @@ func onForkOfFirstConsumer(cachedTransaction *transaction.CachedTransaction, cac
 		return
 	}
 
-	if _, err := UTXODAG.BranchManager().SetBranchPreferred(branch.ID(), true); err != nil {
+	if _, err := Tangle.BranchManager().SetBranchPreferred(branch.ID(), true); err != nil {
 		log.Error(err)
 	}
 
diff --git a/dapps/valuetransfers/fpc.go b/dapps/valuetransfers/fpc.go
index 8e3c4f9f62c7431199b3c0f4c7a7307a605da6ed..d8daf02a96c3f813297a843f7343043a5ec9c261 100644
--- a/dapps/valuetransfers/fpc.go
+++ b/dapps/valuetransfers/fpc.go
@@ -108,7 +108,7 @@ func runFPC() {
 				return vote.Unknown
 			}
 
-			cachedBranch := UTXODAG.BranchManager().Branch(branchID)
+			cachedBranch := Tangle.BranchManager().Branch(branchID)
 			defer cachedBranch.Release()
 
 			branch := cachedBranch.Unwrap()
diff --git a/dapps/valuetransfers/packages/branchmanager/branchmanager.go b/dapps/valuetransfers/packages/branchmanager/branchmanager.go
index 53c9665ae46e02b28c881220493b1bac3ac3accd..ecade28a4d82b467ecd8ab225788bfd4b1862602 100644
--- a/dapps/valuetransfers/packages/branchmanager/branchmanager.go
+++ b/dapps/valuetransfers/packages/branchmanager/branchmanager.go
@@ -184,9 +184,9 @@ func (branchManager *BranchManager) ElevateConflictBranch(branchToElevate Branch
 // BranchesConflicting returns true if the given Branches are part of the same Conflicts and can therefore not be
 // merged.
 func (branchManager *BranchManager) BranchesConflicting(branchIds ...BranchID) (branchesConflicting bool, err error) {
-	// iterate through parameters and collect conflicting branches
-	conflictingBranches := make(map[BranchID]types.Empty)
-	processedBranches := make(map[BranchID]types.Empty)
+	// iterate through branches and collect conflicting branches
+	traversedBranches := make(map[BranchID]types.Empty)
+	blacklistedBranches := make(map[BranchID]types.Empty)
 	for _, branchID := range branchIds {
 		// add the current branch to the stack of branches to check
 		ancestorStack := list.New()
@@ -196,40 +196,44 @@ func (branchManager *BranchManager) BranchesConflicting(branchIds ...BranchID) (
 		for ancestorStack.Len() >= 1 {
 			// retrieve branch from stack
 			firstElement := ancestorStack.Front()
-			ancestorBranchID := firstElement.Value.(BranchID)
+			currentBranchID := firstElement.Value.(BranchID)
 			ancestorStack.Remove(firstElement)
 
 			// abort if we have seen this branch already
-			if _, processedAlready := processedBranches[ancestorBranchID]; processedAlready {
+			if _, traversedAlready := traversedBranches[currentBranchID]; traversedAlready {
 				continue
 			}
-			processedBranches[ancestorBranchID] = types.Void
+
+			// abort if this branch was blacklisted by another branch already
+			if _, branchesConflicting = blacklistedBranches[currentBranchID]; branchesConflicting {
+				return
+			}
 
 			// unpack the branch and abort if we failed to load it
-			cachedBranch := branchManager.Branch(branchID)
-			branch := cachedBranch.Unwrap()
-			if branch == nil {
-				err = fmt.Errorf("failed to load branch '%s'", ancestorBranchID)
+			currentCachedBranch := branchManager.Branch(currentBranchID)
+			currentBranch := currentCachedBranch.Unwrap()
+			if currentBranch == nil {
+				err = fmt.Errorf("failed to load branch '%s'", currentBranchID)
 
-				cachedBranch.Release()
+				currentCachedBranch.Release()
 
 				return
 			}
 
 			// add the parents of the current branch to the list of branches to check
-			for _, parentBranchID := range branch.ParentBranches() {
+			for _, parentBranchID := range currentBranch.ParentBranches() {
 				ancestorStack.PushBack(parentBranchID)
 			}
 
 			// abort the following checks if the branch is aggregated (aggregated branches have no own conflicts)
-			if branch.IsAggregated() {
-				cachedBranch.Release()
+			if currentBranch.IsAggregated() {
+				currentCachedBranch.Release()
 
 				continue
 			}
 
 			// iterate through the conflicts and take note of its member branches
-			for conflictID := range branch.Conflicts() {
+			for conflictID := range currentBranch.Conflicts() {
 				for _, cachedConflictMember := range branchManager.ConflictMembers(conflictID) {
 					// unwrap the current ConflictMember
 					conflictMember := cachedConflictMember.Unwrap()
@@ -239,22 +243,28 @@ func (branchManager *BranchManager) BranchesConflicting(branchIds ...BranchID) (
 						continue
 					}
 
+					if conflictMember.BranchID() == currentBranchID {
+						continue
+					}
+
 					// abort if this branch was found as a conflict of another branch already
-					if _, branchesConflicting = conflictingBranches[conflictMember.BranchID()]; branchesConflicting {
+					if _, branchesConflicting = traversedBranches[conflictMember.BranchID()]; branchesConflicting {
 						cachedConflictMember.Release()
-						cachedBranch.Release()
+						currentCachedBranch.Release()
 
 						return
 					}
 
 					// store the current conflict in the list of seen conflicting branches
-					conflictingBranches[conflictMember.BranchID()] = types.Void
+					blacklistedBranches[conflictMember.BranchID()] = types.Void
 
 					cachedConflictMember.Release()
 				}
 			}
 
-			cachedBranch.Release()
+			currentCachedBranch.Release()
+
+			traversedBranches[currentBranchID] = types.Void
 		}
 	}
 
diff --git a/dapps/valuetransfers/packages/branchmanager/branchmanager_test.go b/dapps/valuetransfers/packages/branchmanager/branchmanager_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..f868df810ae246bf8613857709ea4474eeb53656
--- /dev/null
+++ b/dapps/valuetransfers/packages/branchmanager/branchmanager_test.go
@@ -0,0 +1,23 @@
+package branchmanager
+
+import (
+	"fmt"
+	"testing"
+
+	"github.com/iotaledger/goshimmer/dapps/valuetransfers/packages/transaction"
+	"github.com/iotaledger/goshimmer/packages/binary/testutil"
+)
+
+func TestSomething(t *testing.T) {
+	branchManager := New(testutil.DB(t))
+
+	cachedBranch1, _ := branchManager.Fork(BranchID{2}, []BranchID{MasterBranchID}, []ConflictID{transaction.OutputID{4}})
+	defer cachedBranch1.Release()
+	_ = cachedBranch1.Unwrap()
+
+	cachedBranch2, _ := branchManager.Fork(BranchID{3}, []BranchID{MasterBranchID}, []ConflictID{transaction.OutputID{4}})
+	defer cachedBranch2.Release()
+	branch2 := cachedBranch2.Unwrap()
+
+	fmt.Println(branchManager.BranchesConflicting(MasterBranchID, branch2.ID()))
+}
diff --git a/dapps/valuetransfers/packages/ledgerstate/ledgerstate.go b/dapps/valuetransfers/packages/ledgerstate/ledgerstate.go
deleted file mode 100644
index 5cb09096b181e83d1f30aee9b242c0db714fd75f..0000000000000000000000000000000000000000
--- a/dapps/valuetransfers/packages/ledgerstate/ledgerstate.go
+++ /dev/null
@@ -1,18 +0,0 @@
-package ledgerstate
-
-import (
-	"github.com/iotaledger/goshimmer/dapps/valuetransfers/packages/utxodag"
-)
-
-// LedgerState represents a struct, that allows us to read the balances from the UTXODAG by filtering the existing
-// unspent Outputs depending on the liked branches.
-type LedgerState struct {
-	utxoDAG *utxodag.UTXODAG
-}
-
-// New is the constructor of the LedgerState. It creates a new instance with the given UTXODAG.
-func New(utxoDAG *utxodag.UTXODAG) *LedgerState {
-	return &LedgerState{
-		utxoDAG: utxoDAG,
-	}
-}
diff --git a/dapps/valuetransfers/packages/utxodag/attachment.go b/dapps/valuetransfers/packages/tangle/attachment.go
similarity index 99%
rename from dapps/valuetransfers/packages/utxodag/attachment.go
rename to dapps/valuetransfers/packages/tangle/attachment.go
index 73603af593eb4a5df1e74d4c89cae852256e8564..051deee1dae0f6472cda506dce40797d70696e02 100644
--- a/dapps/valuetransfers/packages/utxodag/attachment.go
+++ b/dapps/valuetransfers/packages/tangle/attachment.go
@@ -1,4 +1,4 @@
-package utxodag
+package tangle
 
 import (
 	"github.com/iotaledger/hive.go/marshalutil"
diff --git a/dapps/valuetransfers/packages/utxodag/consumer.go b/dapps/valuetransfers/packages/tangle/consumer.go
similarity index 99%
rename from dapps/valuetransfers/packages/utxodag/consumer.go
rename to dapps/valuetransfers/packages/tangle/consumer.go
index 212f6523cdebb2aa0b9f41a73429c754eaad0591..201a89a784f438db96ce1ecb74c893614de694b2 100644
--- a/dapps/valuetransfers/packages/utxodag/consumer.go
+++ b/dapps/valuetransfers/packages/tangle/consumer.go
@@ -1,4 +1,4 @@
-package utxodag
+package tangle
 
 import (
 	"github.com/iotaledger/hive.go/marshalutil"
diff --git a/dapps/valuetransfers/packages/tangle/events.go b/dapps/valuetransfers/packages/tangle/events.go
index a807d72805277840c7e428026d2434c985a67b8f..5eea10a5e6ef33d2c17732f19ed5e03afe812eb8 100644
--- a/dapps/valuetransfers/packages/tangle/events.go
+++ b/dapps/valuetransfers/packages/tangle/events.go
@@ -3,7 +3,9 @@ package tangle
 import (
 	"github.com/iotaledger/hive.go/events"
 
+	"github.com/iotaledger/goshimmer/dapps/valuetransfers/packages/branchmanager"
 	"github.com/iotaledger/goshimmer/dapps/valuetransfers/packages/payload"
+	"github.com/iotaledger/goshimmer/dapps/valuetransfers/packages/transaction"
 )
 
 // Events is a container for the different kind of events of the Tangle.
@@ -14,6 +16,18 @@ type Events struct {
 	MissingPayloadReceived *events.Event
 	PayloadMissing         *events.Event
 	PayloadUnsolidifiable  *events.Event
+
+	// TransactionReceived gets triggered whenever a transaction was received for the first time (not solid yet).
+	TransactionReceived *events.Event
+
+	// TransactionBooked gets triggered whenever a transactions becomes solid and gets booked into a particular branch.
+	TransactionBooked *events.Event
+
+	// Fork gets triggered when a previously un-conflicting transaction get's some of its inputs double spend, so that a
+	// new Branch is created.
+	Fork *events.Event
+
+	Error *events.Event
 }
 
 func newEvents() *Events {
@@ -23,6 +37,10 @@ func newEvents() *Events {
 		MissingPayloadReceived: events.NewEvent(cachedPayloadEvent),
 		PayloadMissing:         events.NewEvent(payloadIDEvent),
 		PayloadUnsolidifiable:  events.NewEvent(payloadIDEvent),
+		TransactionReceived:    events.NewEvent(cachedTransactionEvent),
+		TransactionBooked:      events.NewEvent(transactionBookedEvent),
+		Fork:                   events.NewEvent(forkEvent),
+		Error:                  events.NewEvent(events.ErrorCaller),
 	}
 }
 
@@ -36,3 +54,30 @@ func cachedPayloadEvent(handler interface{}, params ...interface{}) {
 		params[1].(*CachedPayloadMetadata).Retain(),
 	)
 }
+
+func transactionBookedEvent(handler interface{}, params ...interface{}) {
+	handler.(func(*transaction.CachedTransaction, *CachedTransactionMetadata, *branchmanager.CachedBranch, []transaction.OutputID, bool))(
+		params[0].(*transaction.CachedTransaction).Retain(),
+		params[1].(*CachedTransactionMetadata).Retain(),
+		params[2].(*branchmanager.CachedBranch).Retain(),
+		params[3].([]transaction.OutputID),
+		params[4].(bool),
+	)
+}
+
+func forkEvent(handler interface{}, params ...interface{}) {
+	handler.(func(*transaction.CachedTransaction, *CachedTransactionMetadata, *branchmanager.CachedBranch, []transaction.OutputID))(
+		params[0].(*transaction.CachedTransaction).Retain(),
+		params[1].(*CachedTransactionMetadata).Retain(),
+		params[2].(*branchmanager.CachedBranch).Retain(),
+		params[3].([]transaction.OutputID),
+	)
+}
+
+func cachedTransactionEvent(handler interface{}, params ...interface{}) {
+	handler.(func(*transaction.CachedTransaction, *CachedTransactionMetadata, *CachedAttachment))(
+		params[0].(*transaction.CachedTransaction).Retain(),
+		params[1].(*CachedTransactionMetadata).Retain(),
+		params[2].(*CachedAttachment).Retain(),
+	)
+}
diff --git a/dapps/valuetransfers/packages/tangle/ledgerstate.go b/dapps/valuetransfers/packages/tangle/ledgerstate.go
new file mode 100644
index 0000000000000000000000000000000000000000..5c0d8c31a7b7bd0c93dcb01aaa29c35719806c22
--- /dev/null
+++ b/dapps/valuetransfers/packages/tangle/ledgerstate.go
@@ -0,0 +1,34 @@
+package tangle
+
+import (
+	"github.com/iotaledger/goshimmer/dapps/valuetransfers/packages/address"
+	"github.com/iotaledger/goshimmer/dapps/valuetransfers/packages/balance"
+)
+
+// LedgerState represents a struct, that allows us to read the balances from the UTXODAG by filtering the existing
+// unspent Outputs depending on the liked branches.
+type LedgerState struct {
+	tangle *Tangle
+}
+
+// NewLedgerState is the constructor of the LedgerState. It creates a new instance with the given UTXODAG.
+func NewLedgerState(tangle *Tangle) *LedgerState {
+	return &LedgerState{
+		tangle: tangle,
+	}
+}
+
+// Balances returns a map containing the balances of the different colors that are unspent on a certain address.
+func (ledgerState *LedgerState) Balances(address address.Address) (coloredBalances map[balance.Color]int64) {
+	coloredBalances = make(map[balance.Color]int64)
+
+	ledgerState.tangle.OutputsOnAddress(address).Consume(func(output *Output) {
+		if output.ConsumerCount() == 0 {
+			for _, coloredBalance := range output.Balances() {
+				coloredBalances[coloredBalance.Color()] += coloredBalance.Value()
+			}
+		}
+	})
+
+	return
+}
diff --git a/dapps/valuetransfers/packages/utxodag/missingoutput.go b/dapps/valuetransfers/packages/tangle/missingoutput.go
similarity index 99%
rename from dapps/valuetransfers/packages/utxodag/missingoutput.go
rename to dapps/valuetransfers/packages/tangle/missingoutput.go
index 77b466241f1dde497af3b3e803042d986e946a64..d04a3e8f600fc75f84f4a1868b563c1e91c5fe04 100644
--- a/dapps/valuetransfers/packages/utxodag/missingoutput.go
+++ b/dapps/valuetransfers/packages/tangle/missingoutput.go
@@ -1,4 +1,4 @@
-package utxodag
+package tangle
 
 import (
 	"time"
diff --git a/dapps/valuetransfers/packages/tangle/objectstorage.go b/dapps/valuetransfers/packages/tangle/objectstorage.go
index 4052ae56ac8e1035718e893096eca3cc3b53c4ff..a549f1a0da9e434e28b8cb7fcf21adffb1c5b999 100644
--- a/dapps/valuetransfers/packages/tangle/objectstorage.go
+++ b/dapps/valuetransfers/packages/tangle/objectstorage.go
@@ -1,9 +1,12 @@
 package tangle
 
 import (
+	"time"
+
 	"github.com/iotaledger/hive.go/objectstorage"
 
 	"github.com/iotaledger/goshimmer/dapps/valuetransfers/packages/payload"
+	"github.com/iotaledger/goshimmer/dapps/valuetransfers/packages/transaction"
 )
 
 const (
@@ -15,6 +18,18 @@ const (
 	osPayloadMetadata
 	osMissingPayload
 	osApprover
+	osTransaction
+	osTransactionMetadata
+	osAttachment
+	osOutput
+	osConsumer
+)
+
+var (
+	osLeakDetectionOption = objectstorage.LeakDetectionEnabled(true, objectstorage.LeakDetectionOptions{
+		MaxConsumersPerObject: 10,
+		MaxConsumerHoldTime:   10 * time.Second,
+	})
 )
 
 func osPayloadFactory(key []byte) (objectstorage.StorableObject, int, error) {
@@ -32,3 +47,23 @@ func osMissingPayloadFactory(key []byte) (objectstorage.StorableObject, int, err
 func osPayloadApproverFactory(key []byte) (objectstorage.StorableObject, int, error) {
 	return PayloadApproverFromStorageKey(key)
 }
+
+func osTransactionFactory(key []byte) (objectstorage.StorableObject, int, error) {
+	return transaction.FromStorageKey(key)
+}
+
+func osTransactionMetadataFactory(key []byte) (objectstorage.StorableObject, int, error) {
+	return TransactionMetadataFromStorageKey(key)
+}
+
+func osAttachmentFactory(key []byte) (objectstorage.StorableObject, int, error) {
+	return AttachmentFromStorageKey(key)
+}
+
+func osOutputFactory(key []byte) (objectstorage.StorableObject, int, error) {
+	return OutputFromStorageKey(key)
+}
+
+func osConsumerFactory(key []byte) (objectstorage.StorableObject, int, error) {
+	return ConsumerFromStorageKey(key)
+}
diff --git a/dapps/valuetransfers/packages/utxodag/output.go b/dapps/valuetransfers/packages/tangle/output.go
similarity index 93%
rename from dapps/valuetransfers/packages/utxodag/output.go
rename to dapps/valuetransfers/packages/tangle/output.go
index 6831c80da31a1faa0b42de5d6251ac741e4320d9..54fe21883bfe1492b398a33193728d5f3600fdc6 100644
--- a/dapps/valuetransfers/packages/utxodag/output.go
+++ b/dapps/valuetransfers/packages/tangle/output.go
@@ -1,4 +1,4 @@
-package utxodag
+package tangle
 
 import (
 	"sync"
@@ -212,12 +212,21 @@ func (output *Output) RegisterConsumer(consumer transaction.ID) (consumerCount i
 		output.firstConsumer = consumer
 	}
 	output.consumerCount++
+	output.SetModified()
 
 	firstConsumerID = output.firstConsumer
 
 	return
 }
 
+// ConsumerCount returns the number of transactions that have spent this Output.
+func (output *Output) ConsumerCount() int {
+	output.consumerMutex.RLock()
+	defer output.consumerMutex.RUnlock()
+
+	return output.consumerCount
+}
+
 // Balances returns the colored balances (color + balance) that this output contains.
 func (output *Output) Balances() []*balance.Balance {
 	return output.balances
@@ -247,10 +256,12 @@ func (output *Output) ObjectStorageValue() []byte {
 	balanceCount := len(output.balances)
 
 	// initialize helper
-	marshalUtil := marshalutil.New(branchmanager.BranchIDLength + marshalutil.BOOL_SIZE + marshalutil.TIME_SIZE + marshalutil.UINT32_SIZE + balanceCount*balance.Length)
+	marshalUtil := marshalutil.New(branchmanager.BranchIDLength + marshalutil.BOOL_SIZE + marshalutil.TIME_SIZE + transaction.IDLength + marshalutil.UINT32_SIZE + marshalutil.UINT32_SIZE + balanceCount*balance.Length)
 	marshalUtil.WriteBytes(output.branchID.Bytes())
 	marshalUtil.WriteBool(output.solid)
 	marshalUtil.WriteTime(output.solidificationTime)
+	marshalUtil.WriteBytes(output.firstConsumer.Bytes())
+	marshalUtil.WriteUint32(uint32(output.consumerCount))
 	marshalUtil.WriteUint32(uint32(balanceCount))
 	for _, balanceToMarshal := range output.balances {
 		marshalUtil.WriteBytes(balanceToMarshal.Bytes())
@@ -272,8 +283,16 @@ func (output *Output) UnmarshalObjectStorageValue(data []byte) (consumedBytes in
 	if output.solidificationTime, err = marshalUtil.ReadTime(); err != nil {
 		return
 	}
-	var balanceCount uint32
-	if balanceCount, err = marshalUtil.ReadUint32(); err != nil {
+	if output.firstConsumer, err = transaction.ParseID(marshalUtil); err != nil {
+		return
+	}
+	consumerCount, err := marshalUtil.ReadUint32()
+	if err != nil {
+		return
+	}
+	output.consumerCount = int(consumerCount)
+	balanceCount, err := marshalUtil.ReadUint32()
+	if err != nil {
 		return
 	}
 	output.balances = make([]*balance.Balance, balanceCount)
diff --git a/dapps/valuetransfers/packages/tangle/payloadmetadata.go b/dapps/valuetransfers/packages/tangle/payloadmetadata.go
index 3fd3d20784628b354bfeb5da23c413e1514dab0f..86d06fd53e6bd3899affced4fe31f54acd18c50b 100644
--- a/dapps/valuetransfers/packages/tangle/payloadmetadata.go
+++ b/dapps/valuetransfers/packages/tangle/payloadmetadata.go
@@ -8,6 +8,7 @@ import (
 	"github.com/iotaledger/hive.go/objectstorage"
 	"github.com/iotaledger/hive.go/stringify"
 
+	"github.com/iotaledger/goshimmer/dapps/valuetransfers/packages/branchmanager"
 	"github.com/iotaledger/goshimmer/dapps/valuetransfers/packages/payload"
 )
 
@@ -19,9 +20,11 @@ type PayloadMetadata struct {
 	payloadID          payload.ID
 	solid              bool
 	solidificationTime time.Time
+	branchID           branchmanager.BranchID
 
 	solidMutex              sync.RWMutex
 	solidificationTimeMutex sync.RWMutex
+	branchIDMutex           sync.RWMutex
 }
 
 // NewPayloadMetadata creates an empty container for the metadata of a value transfer payload.
@@ -136,9 +139,41 @@ func (payloadMetadata *PayloadMetadata) SoldificationTime() time.Time {
 	return payloadMetadata.solidificationTime
 }
 
+// BranchID returns the identifier of the Branch that this Payload was booked into.
+func (payloadMetadata *PayloadMetadata) BranchID() branchmanager.BranchID {
+	payloadMetadata.branchIDMutex.RLock()
+	defer payloadMetadata.branchIDMutex.RUnlock()
+
+	return payloadMetadata.branchID
+}
+
+// SetBranchID is the setter for the BranchID that the corresponding Payload is booked into.
+func (payloadMetadata *PayloadMetadata) SetBranchID(branchID branchmanager.BranchID) (modified bool) {
+	payloadMetadata.branchIDMutex.RLock()
+	if branchID == payloadMetadata.branchID {
+		payloadMetadata.branchIDMutex.RUnlock()
+
+		return
+	}
+
+	payloadMetadata.branchIDMutex.RUnlock()
+	payloadMetadata.branchIDMutex.Lock()
+	defer payloadMetadata.branchIDMutex.Unlock()
+
+	if branchID == payloadMetadata.branchID {
+		return
+	}
+
+	payloadMetadata.branchID = branchID
+	payloadMetadata.SetModified()
+	modified = true
+
+	return
+}
+
 // Bytes marshals the metadata into a sequence of bytes.
 func (payloadMetadata *PayloadMetadata) Bytes() []byte {
-	return marshalutil.New(payload.IDLength + marshalutil.TIME_SIZE + marshalutil.BOOL_SIZE).
+	return marshalutil.New(payload.IDLength + marshalutil.TIME_SIZE + marshalutil.BOOL_SIZE + branchmanager.BranchIDLength).
 		WriteBytes(payloadMetadata.ObjectStorageKey()).
 		WriteBytes(payloadMetadata.ObjectStorageValue()).
 		Bytes()
@@ -170,6 +205,7 @@ func (payloadMetadata *PayloadMetadata) ObjectStorageValue() []byte {
 	return marshalutil.New(marshalutil.TIME_SIZE + marshalutil.BOOL_SIZE).
 		WriteTime(payloadMetadata.solidificationTime).
 		WriteBool(payloadMetadata.solid).
+		WriteBytes(payloadMetadata.branchID.Bytes()).
 		Bytes()
 }
 
@@ -182,6 +218,9 @@ func (payloadMetadata *PayloadMetadata) UnmarshalObjectStorageValue(data []byte)
 	if payloadMetadata.solid, err = marshalUtil.ReadBool(); err != nil {
 		return
 	}
+	if payloadMetadata.branchID, err = branchmanager.ParseBranchID(marshalUtil); err != nil {
+		return
+	}
 	consumedBytes = marshalUtil.ReadOffset()
 
 	return
diff --git a/dapps/valuetransfers/packages/tangle/tangle.go b/dapps/valuetransfers/packages/tangle/tangle.go
index 6332933bff2a6a1bd71749fe53972edc0f4484cf..227944875678d75b4dc8281cdea343c2004cae14 100644
--- a/dapps/valuetransfers/packages/tangle/tangle.go
+++ b/dapps/valuetransfers/packages/tangle/tangle.go
@@ -2,29 +2,43 @@ package tangle
 
 import (
 	"container/list"
+	"errors"
+	"fmt"
+	"math"
 	"time"
 
 	"github.com/dgraph-io/badger/v2"
 	"github.com/iotaledger/hive.go/async"
 	"github.com/iotaledger/hive.go/objectstorage"
+	"github.com/iotaledger/hive.go/types"
 
+	"github.com/iotaledger/goshimmer/dapps/valuetransfers/packages/address"
+	"github.com/iotaledger/goshimmer/dapps/valuetransfers/packages/balance"
+	"github.com/iotaledger/goshimmer/dapps/valuetransfers/packages/branchmanager"
 	"github.com/iotaledger/goshimmer/dapps/valuetransfers/packages/payload"
+	"github.com/iotaledger/goshimmer/dapps/valuetransfers/packages/transaction"
 	"github.com/iotaledger/goshimmer/packages/binary/storageprefix"
 )
 
 // Tangle represents the value tangle that consists out of value payloads.
 // It is an independent ontology, that lives inside the tangle.
 type Tangle struct {
-	payloadStorage         *objectstorage.ObjectStorage
-	payloadMetadataStorage *objectstorage.ObjectStorage
-	approverStorage        *objectstorage.ObjectStorage
-	missingPayloadStorage  *objectstorage.ObjectStorage
+	branchManager *branchmanager.BranchManager
+
+	payloadStorage             *objectstorage.ObjectStorage
+	payloadMetadataStorage     *objectstorage.ObjectStorage
+	approverStorage            *objectstorage.ObjectStorage
+	missingPayloadStorage      *objectstorage.ObjectStorage
+	transactionStorage         *objectstorage.ObjectStorage
+	transactionMetadataStorage *objectstorage.ObjectStorage
+	attachmentStorage          *objectstorage.ObjectStorage
+	outputStorage              *objectstorage.ObjectStorage
+	consumerStorage            *objectstorage.ObjectStorage
 
 	Events Events
 
-	storePayloadWorkerPool async.WorkerPool
-	solidifierWorkerPool   async.WorkerPool
-	cleanupWorkerPool      async.WorkerPool
+	workerPool        async.WorkerPool
+	cleanupWorkerPool async.WorkerPool
 }
 
 // New is the constructor of a Tangle and creates a new Tangle object from the given details.
@@ -32,10 +46,17 @@ func New(badgerInstance *badger.DB) (result *Tangle) {
 	osFactory := objectstorage.NewFactory(badgerInstance, storageprefix.ValueTransfers)
 
 	result = &Tangle{
-		payloadStorage:         osFactory.New(osPayload, osPayloadFactory, objectstorage.CacheTime(time.Second)),
-		payloadMetadataStorage: osFactory.New(osPayloadMetadata, osPayloadMetadataFactory, objectstorage.CacheTime(time.Second)),
-		missingPayloadStorage:  osFactory.New(osMissingPayload, osMissingPayloadFactory, objectstorage.CacheTime(time.Second)),
-		approverStorage:        osFactory.New(osApprover, osPayloadApproverFactory, objectstorage.CacheTime(time.Second), objectstorage.PartitionKey(payload.IDLength, payload.IDLength), objectstorage.KeysOnly(true)),
+		branchManager: branchmanager.New(badgerInstance),
+
+		payloadStorage:             osFactory.New(osPayload, osPayloadFactory, objectstorage.CacheTime(time.Second)),
+		payloadMetadataStorage:     osFactory.New(osPayloadMetadata, osPayloadMetadataFactory, objectstorage.CacheTime(time.Second)),
+		missingPayloadStorage:      osFactory.New(osMissingPayload, osMissingPayloadFactory, objectstorage.CacheTime(time.Second)),
+		approverStorage:            osFactory.New(osApprover, osPayloadApproverFactory, objectstorage.CacheTime(time.Second), objectstorage.PartitionKey(payload.IDLength, payload.IDLength), objectstorage.KeysOnly(true)),
+		transactionStorage:         osFactory.New(osTransaction, osTransactionFactory, objectstorage.CacheTime(time.Second), osLeakDetectionOption),
+		transactionMetadataStorage: osFactory.New(osTransactionMetadata, osTransactionMetadataFactory, objectstorage.CacheTime(time.Second), osLeakDetectionOption),
+		attachmentStorage:          osFactory.New(osAttachment, osAttachmentFactory, objectstorage.CacheTime(time.Second), osLeakDetectionOption),
+		outputStorage:              osFactory.New(osOutput, osOutputFactory, OutputKeyPartitions, objectstorage.CacheTime(time.Second), osLeakDetectionOption),
+		consumerStorage:            osFactory.New(osConsumer, osConsumerFactory, ConsumerPartitionKeys, objectstorage.CacheTime(time.Second), osLeakDetectionOption),
 
 		Events: *newEvents(),
 	}
@@ -43,13 +64,57 @@ func New(badgerInstance *badger.DB) (result *Tangle) {
 	return
 }
 
+// BranchManager is the getter for the manager that takes care of creating and updating branches.
+func (tangle *Tangle) BranchManager() *branchmanager.BranchManager {
+	return tangle.branchManager
+}
+
+// Transaction loads the given transaction from the objectstorage.
+func (tangle *Tangle) Transaction(transactionID transaction.ID) *transaction.CachedTransaction {
+	return &transaction.CachedTransaction{CachedObject: tangle.transactionStorage.Load(transactionID.Bytes())}
+}
+
+// TransactionMetadata retrieves the metadata of a value payload from the object storage.
+func (tangle *Tangle) TransactionMetadata(transactionID transaction.ID) *CachedTransactionMetadata {
+	return &CachedTransactionMetadata{CachedObject: tangle.transactionMetadataStorage.Load(transactionID.Bytes())}
+}
+
+// TransactionOutput loads the given output from the objectstorage.
+func (tangle *Tangle) TransactionOutput(outputID transaction.OutputID) *CachedOutput {
+	return &CachedOutput{CachedObject: tangle.outputStorage.Load(outputID.Bytes())}
+}
+
+// Consumers retrieves the approvers of a payload from the object storage.
+func (tangle *Tangle) Consumers(outputID transaction.OutputID) CachedConsumers {
+	consumers := make(CachedConsumers, 0)
+	tangle.consumerStorage.ForEach(func(key []byte, cachedObject objectstorage.CachedObject) bool {
+		consumers = append(consumers, &CachedConsumer{CachedObject: cachedObject})
+
+		return true
+	}, outputID.Bytes())
+
+	return consumers
+}
+
+// Attachments retrieves the attachment of a payload from the object storage.
+func (tangle *Tangle) Attachments(transactionID transaction.ID) CachedAttachments {
+	attachments := make(CachedAttachments, 0)
+	tangle.attachmentStorage.ForEach(func(key []byte, cachedObject objectstorage.CachedObject) bool {
+		attachments = append(attachments, &CachedAttachment{CachedObject: cachedObject})
+
+		return true
+	}, transactionID.Bytes())
+
+	return attachments
+}
+
 // AttachPayload adds a new payload to the value tangle.
 func (tangle *Tangle) AttachPayload(payload *payload.Payload) {
-	tangle.storePayloadWorkerPool.Submit(func() { tangle.storePayloadWorker(payload) })
+	tangle.workerPool.Submit(func() { tangle.storePayloadWorker(payload) })
 }
 
-// GetPayload retrieves a payload from the object storage.
-func (tangle *Tangle) GetPayload(payloadID payload.ID) *payload.CachedPayload {
+// Payload retrieves a payload from the object storage.
+func (tangle *Tangle) Payload(payloadID payload.ID) *payload.CachedPayload {
 	return &payload.CachedPayload{CachedObject: tangle.payloadStorage.Load(payloadID.Bytes())}
 }
 
@@ -58,8 +123,8 @@ func (tangle *Tangle) PayloadMetadata(payloadID payload.ID) *CachedPayloadMetada
 	return &CachedPayloadMetadata{CachedObject: tangle.payloadMetadataStorage.Load(payloadID.Bytes())}
 }
 
-// GetApprovers retrieves the approvers of a payload from the object storage.
-func (tangle *Tangle) GetApprovers(payloadID payload.ID) CachedApprovers {
+// Approvers retrieves the approvers of a payload from the object storage.
+func (tangle *Tangle) Approvers(payloadID payload.ID) CachedApprovers {
 	approvers := make(CachedApprovers, 0)
 	tangle.approverStorage.ForEach(func(key []byte, cachedObject objectstorage.CachedObject) bool {
 		approvers = append(approvers, &CachedPayloadApprover{CachedObject: cachedObject})
@@ -72,42 +137,69 @@ func (tangle *Tangle) GetApprovers(payloadID payload.ID) CachedApprovers {
 
 // Shutdown stops the worker pools and shuts down the object storage instances.
 func (tangle *Tangle) Shutdown() *Tangle {
-	tangle.storePayloadWorkerPool.ShutdownGracefully()
-	tangle.solidifierWorkerPool.ShutdownGracefully()
+	tangle.workerPool.ShutdownGracefully()
 	tangle.cleanupWorkerPool.ShutdownGracefully()
 
-	tangle.payloadStorage.Shutdown()
-	tangle.payloadMetadataStorage.Shutdown()
-	tangle.approverStorage.Shutdown()
-	tangle.missingPayloadStorage.Shutdown()
+	for _, storage := range []*objectstorage.ObjectStorage{
+		tangle.payloadStorage,
+		tangle.payloadMetadataStorage,
+		tangle.missingPayloadStorage,
+		tangle.approverStorage,
+		tangle.transactionStorage,
+		tangle.transactionMetadataStorage,
+		tangle.attachmentStorage,
+		tangle.outputStorage,
+		tangle.consumerStorage,
+	} {
+		storage.Shutdown()
+	}
 
 	return tangle
 }
 
 // Prune resets the database and deletes all objects (for testing or "node resets").
-func (tangle *Tangle) Prune() error {
+func (tangle *Tangle) Prune() (err error) {
+	if err = tangle.branchManager.Prune(); err != nil {
+		return
+	}
+
 	for _, storage := range []*objectstorage.ObjectStorage{
 		tangle.payloadStorage,
 		tangle.payloadMetadataStorage,
-		tangle.approverStorage,
 		tangle.missingPayloadStorage,
+		tangle.approverStorage,
+		tangle.transactionStorage,
+		tangle.transactionMetadataStorage,
+		tangle.attachmentStorage,
+		tangle.outputStorage,
+		tangle.consumerStorage,
 	} {
-		if err := storage.Prune(); err != nil {
-			return err
+		if err = storage.Prune(); err != nil {
+			return
 		}
 	}
 
-	return nil
+	return
 }
 
 // storePayloadWorker is the worker function that stores the payload and calls the corresponding storage events.
 func (tangle *Tangle) storePayloadWorker(payloadToStore *payload.Payload) {
-	// store the payload and transaction models
+	// store the payload models or abort if we have seen the payload already
 	cachedPayload, cachedPayloadMetadata, payloadStored := tangle.storePayload(payloadToStore)
 	if !payloadStored {
-		// abort if we have seen the payload already
 		return
 	}
+	defer cachedPayload.Release()
+	defer cachedPayloadMetadata.Release()
+
+	// store transaction models or abort if we have seen this attachment already  (nil == was not stored)
+	cachedTransaction, cachedTransactionMetadata, cachedAttachment, transactionIsNew := tangle.storeTransactionModels(payloadToStore)
+	defer cachedTransaction.Release()
+	defer cachedTransactionMetadata.Release()
+	if cachedAttachment == nil {
+		return
+	}
+	defer cachedAttachment.Release()
 
 	// store the references between the different entities (we do this after the actual entities were stored, so that
 	// all the metadata models exist in the database as soon as the entities are reachable by walks).
@@ -118,11 +210,12 @@ func (tangle *Tangle) storePayloadWorker(payloadToStore *payload.Payload) {
 		tangle.Events.MissingPayloadReceived.Trigger(cachedPayload, cachedPayloadMetadata)
 	}
 	tangle.Events.PayloadAttached.Trigger(cachedPayload, cachedPayloadMetadata)
+	if transactionIsNew {
+		tangle.Events.TransactionReceived.Trigger(cachedTransaction, cachedTransactionMetadata, cachedAttachment)
+	}
 
 	// check solidity
-	tangle.solidifierWorkerPool.Submit(func() {
-		tangle.solidifyPayloadWorker(cachedPayload, cachedPayloadMetadata)
-	})
+	tangle.solidifyPayload(cachedPayload.Retain(), cachedPayloadMetadata.Retain(), cachedTransaction.Retain(), cachedTransactionMetadata.Retain())
 }
 
 func (tangle *Tangle) storePayload(payloadToStore *payload.Payload) (cachedPayload *payload.CachedPayload, cachedMetadata *CachedPayloadMetadata, payloadStored bool) {
@@ -138,6 +231,40 @@ func (tangle *Tangle) storePayload(payloadToStore *payload.Payload) (cachedPaylo
 	return
 }
 
+func (tangle *Tangle) storeTransactionModels(solidPayload *payload.Payload) (cachedTransaction *transaction.CachedTransaction, cachedTransactionMetadata *CachedTransactionMetadata, cachedAttachment *CachedAttachment, transactionIsNew bool) {
+	cachedTransaction = &transaction.CachedTransaction{CachedObject: tangle.transactionStorage.ComputeIfAbsent(solidPayload.Transaction().ID().Bytes(), func(key []byte) objectstorage.StorableObject {
+		transactionIsNew = true
+
+		result := solidPayload.Transaction()
+		result.Persist()
+		result.SetModified()
+
+		return result
+	})}
+
+	if transactionIsNew {
+		cachedTransactionMetadata = &CachedTransactionMetadata{CachedObject: tangle.transactionMetadataStorage.Store(NewTransactionMetadata(solidPayload.Transaction().ID()))}
+
+		// store references to the consumed outputs
+		solidPayload.Transaction().Inputs().ForEach(func(outputId transaction.OutputID) bool {
+			tangle.consumerStorage.Store(NewConsumer(outputId, solidPayload.Transaction().ID())).Release()
+
+			return true
+		})
+	} else {
+		cachedTransactionMetadata = &CachedTransactionMetadata{CachedObject: tangle.transactionMetadataStorage.Load(solidPayload.Transaction().ID().Bytes())}
+	}
+
+	// store a reference from the transaction to the payload that attached it or abort, if we have processed this attachment already
+	attachment, stored := tangle.attachmentStorage.StoreIfAbsent(NewAttachment(solidPayload.Transaction().ID(), solidPayload.ID()))
+	if !stored {
+		return
+	}
+	cachedAttachment = &CachedAttachment{CachedObject: attachment}
+
+	return
+}
+
 func (tangle *Tangle) storePayloadReferences(payload *payload.Payload) {
 	// store trunk approver
 	trunkID := payload.TrunkID()
@@ -149,90 +276,310 @@ func (tangle *Tangle) storePayloadReferences(payload *payload.Payload) {
 	}
 }
 
-func (tangle *Tangle) popElementsFromSolidificationStack(stack *list.List) (*payload.CachedPayload, *CachedPayloadMetadata) {
+func (tangle *Tangle) popElementsFromSolidificationStack(stack *list.List) (*payload.CachedPayload, *CachedPayloadMetadata, *transaction.CachedTransaction, *CachedTransactionMetadata) {
 	currentSolidificationEntry := stack.Front()
-	currentCachedPayload := currentSolidificationEntry.Value.([2]interface{})[0]
-	currentCachedMetadata := currentSolidificationEntry.Value.([2]interface{})[1]
+	currentCachedPayload := currentSolidificationEntry.Value.([4]interface{})[0].(*payload.CachedPayload)
+	currentCachedMetadata := currentSolidificationEntry.Value.([4]interface{})[1].(*CachedPayloadMetadata)
+	currentCachedTransaction := currentSolidificationEntry.Value.([4]interface{})[2].(*transaction.CachedTransaction)
+	currentCachedTransactionMetadata := currentSolidificationEntry.Value.([4]interface{})[3].(*CachedTransactionMetadata)
 	stack.Remove(currentSolidificationEntry)
 
-	return currentCachedPayload.(*payload.CachedPayload), currentCachedMetadata.(*CachedPayloadMetadata)
+	return currentCachedPayload, currentCachedMetadata, currentCachedTransaction, currentCachedTransactionMetadata
 }
 
-// solidifyPayloadWorker is the worker function that solidifies the payloads (recursively from past to present).
-func (tangle *Tangle) solidifyPayloadWorker(cachedPayload *payload.CachedPayload, cachedMetadata *CachedPayloadMetadata) {
+// solidifyPayload is the worker function that solidifies the payloads (recursively from past to present).
+func (tangle *Tangle) solidifyPayload(cachedPayload *payload.CachedPayload, cachedMetadata *CachedPayloadMetadata, cachedTransaction *transaction.CachedTransaction, cachedTransactionMetadata *CachedTransactionMetadata) {
 	// initialize the stack
 	solidificationStack := list.New()
-	solidificationStack.PushBack([2]interface{}{cachedPayload, cachedMetadata})
+	solidificationStack.PushBack([4]interface{}{cachedPayload, cachedMetadata, cachedTransaction, cachedTransactionMetadata})
 
 	// process payloads that are supposed to be checked for solidity recursively
 	for solidificationStack.Len() > 0 {
-		// execute logic inside a func, so we can use defer to release the objects
-		func() {
-			// retrieve cached objects
-			currentCachedPayload, currentCachedMetadata := tangle.popElementsFromSolidificationStack(solidificationStack)
-			defer currentCachedPayload.Release()
-			defer currentCachedMetadata.Release()
-
-			// unwrap cached objects
-			currentPayload := currentCachedPayload.Unwrap()
-			currentPayloadMetadata := currentCachedMetadata.Unwrap()
-
-			// abort if any of the retrieved models is nil or payload is not solid or it was set as solid already
-			if currentPayload == nil || currentPayloadMetadata == nil || !tangle.isPayloadSolid(currentPayload, currentPayloadMetadata) || !currentPayloadMetadata.SetSolid(true) {
-				return
+		// retrieve cached objects
+		currentCachedPayload, currentCachedMetadata, currentCachedTransaction, currentCachedTransactionMetadata := tangle.popElementsFromSolidificationStack(solidificationStack)
+
+		// unwrap cached objects
+		currentPayload := currentCachedPayload.Unwrap()
+		currentPayloadMetadata := currentCachedMetadata.Unwrap()
+		currentTransaction := currentCachedTransaction.Unwrap()
+		currentTransactionMetadata := currentCachedTransactionMetadata.Unwrap()
+
+		// abort if any of the retrieved models are nil
+		if currentPayload == nil || currentPayloadMetadata == nil || currentTransaction == nil || currentTransactionMetadata == nil {
+			currentCachedPayload.Release()
+			currentCachedMetadata.Release()
+			currentCachedTransaction.Release()
+			currentCachedTransactionMetadata.Release()
+
+			return
+		}
+
+		// abort if the transaction is not solid or invalid
+		transactionSolid, consumedBranches, err := tangle.checkTransactionSolidity(currentTransaction, currentTransactionMetadata)
+		if err != nil || !transactionSolid {
+			if err != nil {
+				// TODO: TRIGGER INVALID TX + REMOVE TXS + PAYLOADS THAT APPROVE IT
+				fmt.Println(err, currentTransaction)
 			}
 
-			// ... trigger solid event ...
-			tangle.Events.PayloadSolid.Trigger(currentCachedPayload, currentCachedMetadata)
+			currentCachedPayload.Release()
+			currentCachedMetadata.Release()
+			currentCachedTransaction.Release()
+			currentCachedTransactionMetadata.Release()
+
+			return
+		}
+
+		// abort if the payload is not solid or invalid
+		payloadSolid, err := tangle.checkPayloadSolidity(currentPayload, currentPayloadMetadata, consumedBranches)
+		if err != nil || !payloadSolid {
+			if err != nil {
+				// TODO: TRIGGER INVALID TX + REMOVE TXS + PAYLOADS THAT APPROVE IT
+				fmt.Println(err, currentTransaction)
+			}
+
+			currentCachedPayload.Release()
+			currentCachedMetadata.Release()
+			currentCachedTransaction.Release()
+			currentCachedTransactionMetadata.Release()
+
+			return
+		}
+
+		// book the solid entities
+		transactionBooked, payloadBooked, bookingErr := tangle.book(currentCachedPayload.Retain(), currentCachedMetadata.Retain(), currentCachedTransaction.Retain(), currentCachedTransactionMetadata.Retain())
+		if bookingErr != nil {
+			tangle.Events.Error.Trigger(bookingErr)
+
+			currentCachedPayload.Release()
+			currentCachedMetadata.Release()
+			currentCachedTransaction.Release()
+			currentCachedTransactionMetadata.Release()
 
+			return
+		}
+
+		if transactionBooked {
+			tangle.ForEachConsumers(currentTransaction, func(cachedTransaction *transaction.CachedTransaction, transactionMetadata *CachedTransactionMetadata, cachedAttachment *CachedAttachment) {
+				solidificationStack.PushBack([3]interface{}{cachedTransaction, transactionMetadata, cachedAttachment})
+			})
+		}
+
+		if payloadBooked {
 			// ... and schedule check of approvers
-			tangle.ForeachApprovers(currentPayload.ID(), func(payload *payload.CachedPayload, payloadMetadata *CachedPayloadMetadata) {
-				solidificationStack.PushBack([2]interface{}{payload, payloadMetadata})
+			tangle.ForeachApprovers(currentPayload.ID(), func(payload *payload.CachedPayload, payloadMetadata *CachedPayloadMetadata, transaction *transaction.CachedTransaction, transactionMetadata *CachedTransactionMetadata) {
+				solidificationStack.PushBack([4]interface{}{payload, payloadMetadata, transaction, transactionMetadata})
 			})
-		}()
+		}
+
+		currentCachedPayload.Release()
+		currentCachedMetadata.Release()
+		currentCachedTransaction.Release()
+		currentCachedTransactionMetadata.Release()
 	}
 }
 
-// ForeachApprovers iterates through the approvers of a payload and calls the passed in consumer function.
-func (tangle *Tangle) ForeachApprovers(payloadID payload.ID, consume func(payload *payload.CachedPayload, payloadMetadata *CachedPayloadMetadata)) {
-	tangle.GetApprovers(payloadID).Consume(func(approver *PayloadApprover) {
-		approvingPayloadID := approver.ApprovingPayloadID()
-		approvingCachedPayload := tangle.GetPayload(approvingPayloadID)
+func (tangle *Tangle) book(cachedPayload *payload.CachedPayload, cachedPayloadMetadata *CachedPayloadMetadata, cachedTransaction *transaction.CachedTransaction, cachedTransactionMetadata *CachedTransactionMetadata) (transactionBooked bool, payloadBooked bool, err error) {
+	defer cachedPayload.Release()
+	defer cachedPayloadMetadata.Release()
+	defer cachedTransaction.Release()
+	defer cachedTransactionMetadata.Release()
 
-		approvingCachedPayload.Consume(func(payload *payload.Payload) {
-			consume(approvingCachedPayload, tangle.PayloadMetadata(approvingPayloadID))
-		})
-	})
+	if transactionBooked, err = tangle.bookTransaction(cachedTransaction.Retain(), cachedTransactionMetadata.Retain()); err != nil {
+		return
+	}
+
+	if payloadBooked, err = tangle.bookPayload(cachedPayload.Retain(), cachedPayloadMetadata.Retain(), cachedTransactionMetadata.Retain()); err != nil {
+		return
+	}
+
+	return
 }
 
-// isPayloadSolid returns true if the given payload is solid. A payload is considered to be solid solid, if it is either
-// already marked as solid or if its referenced payloads are marked as solid.
-func (tangle *Tangle) isPayloadSolid(payload *payload.Payload, metadata *PayloadMetadata) bool {
-	if payload == nil || payload.IsDeleted() {
-		return false
+func (tangle *Tangle) bookTransaction(cachedTransaction *transaction.CachedTransaction, cachedTransactionMetadata *CachedTransactionMetadata) (transactionBooked bool, err error) {
+	defer cachedTransaction.Release()
+	defer cachedTransactionMetadata.Release()
+
+	transactionToBook := cachedTransaction.Unwrap()
+	if transactionToBook == nil {
+		// TODO: explicit error var
+		err = errors.New("failed to unwrap transaction")
+
+		return
 	}
 
-	if metadata == nil || metadata.IsDeleted() {
-		return false
+	transactionMetadata := cachedTransactionMetadata.Unwrap()
+	if transactionMetadata == nil {
+		// TODO: explicit error var
+		err = errors.New("failed to unwrap transaction metadata")
+
+		return
+	}
+
+	// abort if this transaction was booked by another process already
+	if !transactionMetadata.SetSolid(true) {
+		return
+	}
+
+	consumedBranches := make(branchmanager.BranchIds)
+	conflictingInputs := make([]transaction.OutputID, 0)
+	conflictingInputsOfFirstConsumers := make(map[transaction.ID][]transaction.OutputID)
+
+	if !transactionToBook.Inputs().ForEach(func(outputID transaction.OutputID) bool {
+		cachedOutput := tangle.TransactionOutput(outputID)
+		defer cachedOutput.Release()
+
+		// abort if the output could not be found
+		output := cachedOutput.Unwrap()
+		if output == nil {
+			err = fmt.Errorf("could not load output '%s'", outputID)
+
+			return false
+		}
+
+		consumedBranches[output.BranchID()] = types.Void
+
+		// register the current consumer and check if the input has been consumed before
+		consumerCount, firstConsumerID := output.RegisterConsumer(transactionToBook.ID())
+		switch consumerCount {
+		// continue if we are the first consumer and there is no double spend
+		case 0:
+			return true
+
+		// if the input has been consumed before but not been forked, yet
+		case 1:
+			// keep track of the conflicting inputs so we can fork them
+			if _, conflictingInputsExist := conflictingInputsOfFirstConsumers[firstConsumerID]; !conflictingInputsExist {
+				conflictingInputsOfFirstConsumers[firstConsumerID] = make([]transaction.OutputID, 0)
+			}
+			conflictingInputsOfFirstConsumers[firstConsumerID] = append(conflictingInputsOfFirstConsumers[firstConsumerID], outputID)
+		}
+
+		return true
+	}) {
+		return
 	}
 
-	if metadata.IsSolid() {
+	cachedTargetBranch, err := tangle.branchManager.AggregateBranches(consumedBranches.ToList()...)
+	if err != nil {
+		return
+	}
+	defer cachedTargetBranch.Release()
+
+	targetBranch := cachedTargetBranch.Unwrap()
+	if targetBranch == nil {
+		err = errors.New("failed to unwrap target branch")
+
+		return
+	}
+	targetBranch.Persist()
+
+	if len(conflictingInputs) >= 1 {
+		cachedTargetBranch, _ = tangle.branchManager.Fork(branchmanager.NewBranchID(transactionToBook.ID()), []branchmanager.BranchID{targetBranch.ID()}, conflictingInputs)
+		defer cachedTargetBranch.Release()
+
+		targetBranch = cachedTargetBranch.Unwrap()
+		if targetBranch == nil {
+			err = errors.New("failed to inherit branches")
+
+			return
+		}
+	}
+
+	// book transaction into target branch
+	transactionMetadata.SetBranchID(targetBranch.ID())
+
+	// book outputs into the target branch
+	transactionToBook.Outputs().ForEach(func(address address.Address, balances []*balance.Balance) bool {
+		newOutput := NewOutput(address, transactionToBook.ID(), targetBranch.ID(), balances)
+		newOutput.SetSolid(true)
+		tangle.outputStorage.Store(newOutput).Release()
+
 		return true
+	})
+
+	// fork the conflicting transactions into their own branch
+	decisionPending := false
+	for consumerID, conflictingInputs := range conflictingInputsOfFirstConsumers {
+		_, decisionFinalized, forkedErr := tangle.Fork(consumerID, conflictingInputs)
+		if forkedErr != nil {
+			err = forkedErr
+
+			return
+		}
+
+		decisionPending = decisionPending || !decisionFinalized
+	}
+
+	// trigger events
+	tangle.Events.TransactionBooked.Trigger(cachedTransaction, cachedTransactionMetadata, cachedTargetBranch, conflictingInputs, decisionPending)
+
+	transactionBooked = true
+
+	return
+}
+
+func (tangle *Tangle) bookPayload(cachedPayload *payload.CachedPayload, cachedPayloadMetadata *CachedPayloadMetadata, cachedTransactionMetadata *CachedTransactionMetadata) (payloadBooked bool, err error) {
+	defer cachedPayload.Release()
+	defer cachedPayloadMetadata.Release()
+	defer cachedTransactionMetadata.Release()
+
+	valueObject := cachedPayload.Unwrap()
+	valueObjectMetadata := cachedPayloadMetadata.Unwrap()
+	transactionMetadata := cachedTransactionMetadata.Unwrap()
+
+	if valueObject == nil || valueObjectMetadata == nil || transactionMetadata == nil {
+		return
+	}
+
+	branchBranchID := tangle.payloadBranchID(valueObject.BranchID())
+	trunkBranchID := tangle.payloadBranchID(valueObject.TrunkID())
+	transactionBranchID := transactionMetadata.BranchID()
+
+	if branchBranchID == branchmanager.UndefinedBranchID || trunkBranchID == branchmanager.UndefinedBranchID || transactionBranchID == branchmanager.UndefinedBranchID {
+		return
+	}
+
+	cachedAggregatedBranch, err := tangle.BranchManager().AggregateBranches([]branchmanager.BranchID{branchBranchID, trunkBranchID, transactionBranchID}...)
+	if err != nil {
+		return
 	}
+	defer cachedAggregatedBranch.Release()
+
+	aggregatedBranch := cachedAggregatedBranch.Unwrap()
+	if aggregatedBranch == nil {
+		return
+	}
+
+	payloadBooked = valueObjectMetadata.SetBranchID(aggregatedBranch.ID())
+
+	return
+}
 
-	return tangle.isPayloadMarkedAsSolid(payload.TrunkID()) && tangle.isPayloadMarkedAsSolid(payload.BranchID())
+// ForeachApprovers iterates through the approvers of a payload and calls the passed in consumer function.
+func (tangle *Tangle) ForeachApprovers(payloadID payload.ID, consume func(payload *payload.CachedPayload, payloadMetadata *CachedPayloadMetadata, transaction *transaction.CachedTransaction, transactionMetadata *CachedTransactionMetadata)) {
+	tangle.Approvers(payloadID).Consume(func(approver *PayloadApprover) {
+		approvingPayloadID := approver.ApprovingPayloadID()
+		approvingCachedPayload := tangle.Payload(approvingPayloadID)
+
+		approvingCachedPayload.Consume(func(payload *payload.Payload) {
+			consume(approvingCachedPayload, tangle.PayloadMetadata(approvingPayloadID), tangle.Transaction(payload.Transaction().ID()), tangle.TransactionMetadata(payload.Transaction().ID()))
+		})
+	})
 }
 
-// isPayloadMarkedAsSolid returns true if the payload was marked as solid already (by setting the corresponding flags
-// in its metadata.
-func (tangle *Tangle) isPayloadMarkedAsSolid(payloadID payload.ID) bool {
+// payloadBranchID returns the BranchID that the referenced Payload was booked into.
+func (tangle *Tangle) payloadBranchID(payloadID payload.ID) branchmanager.BranchID {
 	if payloadID == payload.GenesisID {
-		return true
+		return branchmanager.MasterBranchID
 	}
 
-	transactionMetadataCached := tangle.PayloadMetadata(payloadID)
-	if transactionMetadata := transactionMetadataCached.Unwrap(); transactionMetadata == nil {
-		transactionMetadataCached.Release()
+	cachedPayloadMetadata := tangle.PayloadMetadata(payloadID)
+	defer cachedPayloadMetadata.Release()
+
+	payloadMetadata := cachedPayloadMetadata.Unwrap()
+	if payloadMetadata == nil {
+		cachedPayloadMetadata.Release()
 
 		// if transaction is missing and was not reported as missing, yet
 		if cachedMissingPayload, missingPayloadStored := tangle.missingPayloadStorage.StoreIfAbsent(NewMissingPayload(payloadID)); missingPayloadStored {
@@ -241,13 +588,497 @@ func (tangle *Tangle) isPayloadMarkedAsSolid(payloadID payload.ID) bool {
 			})
 		}
 
-		return false
-	} else if !transactionMetadata.IsSolid() {
-		transactionMetadataCached.Release()
+		return branchmanager.UndefinedBranchID
+	}
+
+	// the BranchID is only set if the payload was also marked as solid
+	return payloadMetadata.BranchID()
+}
+
+// checkPayloadSolidity returns true if the given payload is solid. A payload is considered to be solid solid, if it is either
+// already marked as solid or if its referenced payloads are marked as solid.
+func (tangle *Tangle) checkPayloadSolidity(payload *payload.Payload, payloadMetadata *PayloadMetadata, transactionBranches []branchmanager.BranchID) (solid bool, err error) {
+	if payload == nil || payload.IsDeleted() || payloadMetadata == nil || payloadMetadata.IsDeleted() {
+		return
+	}
+
+	if solid = payloadMetadata.IsSolid(); solid {
+		return
+	}
+
+	combinedBranches := transactionBranches
+
+	trunkBranchID := tangle.payloadBranchID(payload.TrunkID())
+	if trunkBranchID == branchmanager.UndefinedBranchID {
+		return
+	}
+	combinedBranches = append(combinedBranches, trunkBranchID)
+
+	branchBranchID := tangle.payloadBranchID(payload.BranchID())
+	if branchBranchID == branchmanager.UndefinedBranchID {
+		return
+	}
+	combinedBranches = append(combinedBranches, branchBranchID)
+
+	branchesConflicting, err := tangle.branchManager.BranchesConflicting(combinedBranches...)
+	if err != nil {
+		return
+	}
+	if branchesConflicting {
+		err = fmt.Errorf("the payload '%s' combines conflicting versions of the ledger state", payload.ID())
+
+		return
+	}
+
+	solid = true
 
+	return
+}
+
+func (tangle *Tangle) checkTransactionSolidity(tx *transaction.Transaction, metadata *TransactionMetadata) (solid bool, consumedBranches []branchmanager.BranchID, err error) {
+	// abort if any of the models are nil or has been deleted
+	if tx == nil || tx.IsDeleted() || metadata == nil || metadata.IsDeleted() {
+		return
+	}
+
+	// abort if we have previously determined the solidity status of the transaction already
+	if solid = metadata.Solid(); solid {
+		consumedBranches = []branchmanager.BranchID{metadata.BranchID()}
+
+		return
+	}
+
+	// determine the consumed inputs and balances of the transaction
+	inputsSolid, cachedInputs, consumedBalances, consumedBranchesMap, err := tangle.retrieveConsumedInputDetails(tx)
+	if err != nil || !inputsSolid {
+		return
+	}
+	defer cachedInputs.Release()
+
+	// abort if the outputs are not matching the inputs
+	if !tangle.checkTransactionOutputs(consumedBalances, tx.Outputs()) {
+		err = fmt.Errorf("the outputs do not match the inputs in transaction with id '%s'", tx.ID())
+
+		return
+	}
+
+	// abort if the branches are conflicting or we faced an error when checking the validity
+	consumedBranches = consumedBranchesMap.ToList()
+	branchesConflicting, err := tangle.branchManager.BranchesConflicting(consumedBranches...)
+	if err != nil {
+		return
+	}
+	if branchesConflicting {
+		err = fmt.Errorf("the transaction '%s' spends conflicting inputs", tx.ID())
+
+		return
+	}
+
+	// set the result to be solid and valid
+	solid = true
+
+	return
+}
+
+// LoadSnapshot creates a set of outputs in the value tangle, that are forming the genesis for future transactions.
+func (tangle *Tangle) LoadSnapshot(snapshot map[transaction.ID]map[address.Address][]*balance.Balance) {
+	for transactionID, addressBalances := range snapshot {
+		for outputAddress, balances := range addressBalances {
+			input := NewOutput(outputAddress, transactionID, branchmanager.MasterBranchID, balances)
+			input.SetSolid(true)
+
+			// store output and abort if the snapshot has already been loaded earlier (output exists in the database)
+			cachedOutput, stored := tangle.outputStorage.StoreIfAbsent(input)
+			if !stored {
+				return
+			}
+
+			cachedOutput.Release()
+		}
+	}
+}
+
+// OutputsOnAddress retrieves all the Outputs that are associated with an address.
+func (tangle *Tangle) OutputsOnAddress(address address.Address) (result CachedOutputs) {
+	result = make(CachedOutputs)
+	tangle.outputStorage.ForEach(func(key []byte, cachedObject objectstorage.CachedObject) bool {
+		outputID, _, err := transaction.OutputIDFromBytes(key)
+		if err != nil {
+			panic(err)
+		}
+
+		result[outputID] = &CachedOutput{CachedObject: cachedObject}
+
+		return true
+	}, address.Bytes())
+
+	return
+}
+
+func (tangle *Tangle) getCachedOutputsFromTransactionInputs(tx *transaction.Transaction) (result CachedOutputs) {
+	result = make(CachedOutputs)
+	tx.Inputs().ForEach(func(inputId transaction.OutputID) bool {
+		result[inputId] = tangle.TransactionOutput(inputId)
+
+		return true
+	})
+
+	return
+}
+
+func (tangle *Tangle) retrieveConsumedInputDetails(tx *transaction.Transaction) (inputsSolid bool, cachedInputs CachedOutputs, consumedBalances map[balance.Color]int64, consumedBranches branchmanager.BranchIds, err error) {
+	cachedInputs = tangle.getCachedOutputsFromTransactionInputs(tx)
+	consumedBalances = make(map[balance.Color]int64)
+	consumedBranches = make(branchmanager.BranchIds)
+	for _, cachedInput := range cachedInputs {
+		input := cachedInput.Unwrap()
+		if input == nil || !input.Solid() {
+			cachedInputs.Release()
+
+			return
+		}
+
+		consumedBranches[input.BranchID()] = types.Void
+
+		// calculate the input balances
+		for _, inputBalance := range input.Balances() {
+			var newBalance int64
+			if currentBalance, balanceExists := consumedBalances[inputBalance.Color()]; balanceExists {
+				// check overflows in the numbers
+				if inputBalance.Value() > math.MaxInt64-currentBalance {
+					// TODO: make it an explicit error var
+					err = fmt.Errorf("buffer overflow in balances of inputs")
+
+					cachedInputs.Release()
+
+					return
+				}
+
+				newBalance = currentBalance + inputBalance.Value()
+			} else {
+				newBalance = inputBalance.Value()
+			}
+			consumedBalances[inputBalance.Color()] = newBalance
+		}
+	}
+	inputsSolid = true
+
+	return
+}
+
+// checkTransactionOutputs is a utility function that returns true, if the outputs are consuming all of the given inputs
+// (the sum of all the balance changes is 0). It also accounts for the ability to "recolor" coins during the creating of
+// outputs. If this function returns false, then the outputs that are defined in the transaction are invalid and the
+// transaction should be removed from the ledger state.
+func (tangle *Tangle) checkTransactionOutputs(inputBalances map[balance.Color]int64, outputs *transaction.Outputs) bool {
+	// create a variable to keep track of outputs that create a new color
+	var newlyColoredCoins int64
+	var uncoloredCoins int64
+
+	// iterate through outputs and check them one by one
+	aborted := !outputs.ForEach(func(address address.Address, balances []*balance.Balance) bool {
+		for _, outputBalance := range balances {
+			// abort if the output creates a negative or empty output
+			if outputBalance.Value() <= 0 {
+				return false
+			}
+
+			// sidestep logic if we have a newly colored output (we check the supply later)
+			if outputBalance.Color() == balance.ColorNew {
+				// catch overflows
+				if newlyColoredCoins > math.MaxInt64-outputBalance.Value() {
+					return false
+				}
+
+				newlyColoredCoins += outputBalance.Value()
+
+				continue
+			}
+
+			// sidestep logic if we have a newly colored output (we check the supply later)
+			if outputBalance.Color() == balance.ColorIOTA {
+				// catch overflows
+				if uncoloredCoins > math.MaxInt64-outputBalance.Value() {
+					return false
+				}
+
+				uncoloredCoins += outputBalance.Value()
+
+				continue
+			}
+
+			// check if the used color does not exist in our supply
+			availableBalance, spentColorExists := inputBalances[outputBalance.Color()]
+			if !spentColorExists {
+				return false
+			}
+
+			// abort if we spend more coins of the given color than we have
+			if availableBalance < outputBalance.Value() {
+				return false
+			}
+
+			// subtract the spent coins from the supply of this color
+			inputBalances[outputBalance.Color()] -= outputBalance.Value()
+
+			// cleanup empty map entries (we have exhausted our funds)
+			if inputBalances[outputBalance.Color()] == 0 {
+				delete(inputBalances, outputBalance.Color())
+			}
+		}
+
+		return true
+	})
+
+	// abort if the previous checks failed
+	if aborted {
 		return false
 	}
-	transactionMetadataCached.Release()
 
-	return true
+	// determine the unspent inputs
+	var unspentCoins int64
+	for _, unspentBalance := range inputBalances {
+		// catch overflows
+		if unspentCoins > math.MaxInt64-unspentBalance {
+			return false
+		}
+
+		unspentCoins += unspentBalance
+	}
+
+	// the outputs are valid if they spend all consumed funds
+	return unspentCoins == newlyColoredCoins+uncoloredCoins
+}
+
+// Fork creates a new branch from an existing transaction.
+func (tangle *Tangle) Fork(transactionID transaction.ID, conflictingInputs []transaction.OutputID) (forked bool, finalized bool, err error) {
+	cachedTransaction := tangle.Transaction(transactionID)
+	cachedTransactionMetadata := tangle.TransactionMetadata(transactionID)
+	defer cachedTransaction.Release()
+	defer cachedTransactionMetadata.Release()
+
+	tx := cachedTransaction.Unwrap()
+	if tx == nil {
+		err = fmt.Errorf("failed to load transaction '%s'", transactionID)
+
+		return
+	}
+	txMetadata := cachedTransactionMetadata.Unwrap()
+	if txMetadata == nil {
+		err = fmt.Errorf("failed to load metadata of transaction '%s'", transactionID)
+
+		return
+	}
+
+	// abort if this transaction was finalized already
+	if txMetadata.Finalized() {
+		finalized = true
+
+		return
+	}
+
+	// update / create new branch
+	cachedTargetBranch, newBranchCreated := tangle.branchManager.Fork(branchmanager.NewBranchID(tx.ID()), []branchmanager.BranchID{txMetadata.BranchID()}, conflictingInputs)
+	defer cachedTargetBranch.Release()
+
+	// abort if the branch existed already
+	if !newBranchCreated {
+		return
+	}
+
+	// unpack branch
+	targetBranch := cachedTargetBranch.Unwrap()
+	if targetBranch == nil {
+		err = fmt.Errorf("failed to unpack branch for transaction '%s'", transactionID)
+
+		return
+	}
+
+	// move transactions to new branch
+	if err = tangle.moveTransactionToBranch(cachedTransaction.Retain(), cachedTransactionMetadata.Retain(), cachedTargetBranch.Retain()); err != nil {
+		return
+	}
+
+	// trigger events + set result
+	tangle.Events.Fork.Trigger(cachedTransaction, cachedTransactionMetadata, targetBranch, conflictingInputs)
+	forked = true
+
+	return
+}
+
+// TODO: write comment what it does
+func (tangle *Tangle) moveTransactionToBranch(cachedTransaction *transaction.CachedTransaction, cachedTransactionMetadata *CachedTransactionMetadata, cachedTargetBranch *branchmanager.CachedBranch) (err error) {
+	// push transaction that shall be moved to the stack
+	transactionStack := list.New()
+	branchStack := list.New()
+	branchStack.PushBack([3]interface{}{cachedTransactionMetadata.Unwrap().BranchID(), cachedTargetBranch, transactionStack})
+	transactionStack.PushBack([2]interface{}{cachedTransaction, cachedTransactionMetadata})
+
+	// iterate through all transactions (grouped by their branch)
+	for branchStack.Len() >= 1 {
+		if err = func() error {
+			// retrieve branch details from stack
+			currentSolidificationEntry := branchStack.Front()
+			currentSourceBranch := currentSolidificationEntry.Value.([3]interface{})[0].(branchmanager.BranchID)
+			currentCachedTargetBranch := currentSolidificationEntry.Value.([3]interface{})[1].(*branchmanager.CachedBranch)
+			transactionStack := currentSolidificationEntry.Value.([3]interface{})[2].(*list.List)
+			branchStack.Remove(currentSolidificationEntry)
+			defer currentCachedTargetBranch.Release()
+
+			// unpack target branch
+			targetBranch := currentCachedTargetBranch.Unwrap()
+			if targetBranch == nil {
+				return errors.New("failed to unpack branch")
+			}
+
+			// iterate through transactions
+			for transactionStack.Len() >= 1 {
+				if err = func() error {
+					// retrieve transaction details from stack
+					currentSolidificationEntry := transactionStack.Front()
+					currentCachedTransaction := currentSolidificationEntry.Value.([2]interface{})[0].(*transaction.CachedTransaction)
+					currentCachedTransactionMetadata := currentSolidificationEntry.Value.([2]interface{})[1].(*CachedTransactionMetadata)
+					transactionStack.Remove(currentSolidificationEntry)
+					defer currentCachedTransaction.Release()
+					defer currentCachedTransactionMetadata.Release()
+
+					// unwrap transaction
+					currentTransaction := currentCachedTransaction.Unwrap()
+					if currentTransaction == nil {
+						return errors.New("failed to unwrap transaction")
+					}
+
+					// unwrap transaction metadata
+					currentTransactionMetadata := currentCachedTransactionMetadata.Unwrap()
+					if currentTransactionMetadata == nil {
+						return errors.New("failed to unwrap transaction metadata")
+					}
+
+					// if we arrived at a nested branch
+					if currentTransactionMetadata.BranchID() != currentSourceBranch {
+						// abort if we the branch is a conflict branch or an error occurred while trying to elevate
+						isConflictBranch, _, elevateErr := tangle.branchManager.ElevateConflictBranch(currentTransactionMetadata.BranchID(), targetBranch.ID())
+						if elevateErr != nil || isConflictBranch {
+							return elevateErr
+						}
+
+						// determine the new branch of the transaction
+						newCachedTargetBranch, branchErr := tangle.calculateBranchOfTransaction(currentTransaction)
+						if branchErr != nil {
+							return branchErr
+						}
+						defer newCachedTargetBranch.Release()
+
+						// unwrap the branch
+						newTargetBranch := newCachedTargetBranch.Unwrap()
+						if newTargetBranch == nil {
+							return errors.New("failed to unwrap branch")
+						}
+						newTargetBranch.Persist()
+
+						// add the new branch (with the current transaction as a starting point to the branch stack)
+						newTransactionStack := list.New()
+						newTransactionStack.PushBack([2]interface{}{currentCachedTransaction.Retain(), currentCachedTransactionMetadata.Retain()})
+						branchStack.PushBack([3]interface{}{currentTransactionMetadata.BranchID(), newCachedTargetBranch.Retain(), newTransactionStack})
+
+						return nil
+					}
+
+					// abort if we did not modify the branch of the transaction
+					if !currentTransactionMetadata.SetBranchID(targetBranch.ID()) {
+						return nil
+					}
+
+					// iterate through the outputs of the moved transaction
+					currentTransaction.Outputs().ForEach(func(address address.Address, balances []*balance.Balance) bool {
+						// create reference to the output
+						outputID := transaction.NewOutputID(address, currentTransaction.ID())
+
+						// load output from database
+						cachedOutput := tangle.TransactionOutput(outputID)
+						defer cachedOutput.Release()
+
+						// unwrap output
+						output := cachedOutput.Unwrap()
+						if output == nil {
+							err = fmt.Errorf("failed to load output '%s'", outputID)
+
+							return false
+						}
+
+						// abort if the output was moved already
+						if !output.SetBranchID(targetBranch.ID()) {
+							return true
+						}
+
+						// schedule consumers for further checks
+						consumingTransactions := make(map[transaction.ID]types.Empty)
+						tangle.Consumers(transaction.NewOutputID(address, currentTransaction.ID())).Consume(func(consumer *Consumer) {
+							consumingTransactions[consumer.TransactionID()] = types.Void
+						})
+						for transactionID := range consumingTransactions {
+							transactionStack.PushBack([2]interface{}{tangle.Transaction(transactionID), tangle.TransactionMetadata(transactionID)})
+						}
+
+						return true
+					})
+
+					return nil
+				}(); err != nil {
+					return err
+				}
+			}
+
+			return nil
+		}(); err != nil {
+			return
+		}
+	}
+
+	return
+}
+
+func (tangle *Tangle) calculateBranchOfTransaction(currentTransaction *transaction.Transaction) (branch *branchmanager.CachedBranch, err error) {
+	consumedBranches := make(branchmanager.BranchIds)
+	if !currentTransaction.Inputs().ForEach(func(outputId transaction.OutputID) bool {
+		cachedTransactionOutput := tangle.TransactionOutput(outputId)
+		defer cachedTransactionOutput.Release()
+
+		transactionOutput := cachedTransactionOutput.Unwrap()
+		if transactionOutput == nil {
+			err = fmt.Errorf("failed to load output '%s'", outputId)
+
+			return false
+		}
+
+		consumedBranches[transactionOutput.BranchID()] = types.Void
+
+		return true
+	}) {
+		return
+	}
+
+	branch, err = tangle.branchManager.AggregateBranches(consumedBranches.ToList()...)
+
+	return
+}
+
+// ForEachConsumers iterates through the transactions that are consuming outputs of the given transactions
+func (tangle *Tangle) ForEachConsumers(currentTransaction *transaction.Transaction, consume func(cachedTransaction *transaction.CachedTransaction, transactionMetadata *CachedTransactionMetadata, cachedAttachment *CachedAttachment)) {
+	seenTransactions := make(map[transaction.ID]types.Empty)
+	currentTransaction.Outputs().ForEach(func(address address.Address, balances []*balance.Balance) bool {
+		tangle.Consumers(transaction.NewOutputID(address, currentTransaction.ID())).Consume(func(consumer *Consumer) {
+			if _, transactionSeen := seenTransactions[consumer.TransactionID()]; !transactionSeen {
+				seenTransactions[consumer.TransactionID()] = types.Void
+
+				cachedTransaction := tangle.Transaction(consumer.TransactionID())
+				cachedTransactionMetadata := tangle.TransactionMetadata(consumer.TransactionID())
+				for _, cachedAttachment := range tangle.Attachments(consumer.TransactionID()) {
+					consume(cachedTransaction, cachedTransactionMetadata, cachedAttachment)
+				}
+			}
+		})
+
+		return true
+	})
 }
diff --git a/dapps/valuetransfers/packages/utxodag/utxodag_test.go b/dapps/valuetransfers/packages/tangle/tangle_test.go
similarity index 50%
rename from dapps/valuetransfers/packages/utxodag/utxodag_test.go
rename to dapps/valuetransfers/packages/tangle/tangle_test.go
index 4db41241e32c185c4ea0fe3ba7af431c2ea7b95c..f335593ca963d286f60897d215c4a1c45edcee18 100644
--- a/dapps/valuetransfers/packages/utxodag/utxodag_test.go
+++ b/dapps/valuetransfers/packages/tangle/tangle_test.go
@@ -1,23 +1,16 @@
-package utxodag
+package tangle
 
 import (
-	"io/ioutil"
-	"os"
 	"testing"
 	"time"
 
-	"github.com/iotaledger/hive.go/crypto/ed25519"
 	"github.com/stretchr/testify/assert"
-	"github.com/stretchr/testify/require"
 
 	"github.com/iotaledger/goshimmer/dapps/valuetransfers/packages/address"
 	"github.com/iotaledger/goshimmer/dapps/valuetransfers/packages/balance"
 	"github.com/iotaledger/goshimmer/dapps/valuetransfers/packages/branchmanager"
 	"github.com/iotaledger/goshimmer/dapps/valuetransfers/packages/payload"
-	"github.com/iotaledger/goshimmer/dapps/valuetransfers/packages/tangle"
 	"github.com/iotaledger/goshimmer/dapps/valuetransfers/packages/transaction"
-	"github.com/iotaledger/goshimmer/packages/database"
-	"github.com/iotaledger/goshimmer/plugins/config"
 )
 
 func TestNewOutput(t *testing.T) {
@@ -71,72 +64,3 @@ func TestAttachment(t *testing.T) {
 	assert.Equal(t, transactionID, clonedAttachment.TransactionID())
 	assert.Equal(t, payloadID, clonedAttachment.PayloadID())
 }
-
-func TestTangle_AttachPayload(t *testing.T) {
-	dir, err := ioutil.TempDir("", t.Name())
-	require.NoError(t, err)
-	defer os.Remove(dir)
-
-	config.Node.Set(database.CFG_DIRECTORY, dir)
-
-	valueTangle := tangle.New(database.GetBadgerInstance())
-	if err := valueTangle.Prune(); err != nil {
-		t.Error(err)
-
-		return
-	}
-
-	utxoDAG := New(database.GetBadgerInstance(), valueTangle)
-
-	addressKeyPair1 := ed25519.GenerateKeyPair()
-	addressKeyPair2 := ed25519.GenerateKeyPair()
-
-	transferID1, _ := transaction.IDFromBase58("8opHzTAnfzRpPEx21XtnrVTX28YQuCpAjcn1PczScKh")
-	transferID2, _ := transaction.IDFromBase58("4uQeVj5tqViQh7yWWGStvkEG1Zmhx6uasJtWCJziofM")
-
-	input1 := NewOutput(address.FromED25519PubKey(addressKeyPair1.PublicKey), transferID1, branchmanager.MasterBranchID, []*balance.Balance{
-		balance.New(balance.ColorIOTA, 337),
-	})
-	input1.SetSolid(true)
-	input2 := NewOutput(address.FromED25519PubKey(addressKeyPair2.PublicKey), transferID2, branchmanager.MasterBranchID, []*balance.Balance{
-		balance.New(balance.ColorIOTA, 1000),
-	})
-	input2.SetSolid(true)
-
-	utxoDAG.outputStorage.Store(input1).Release()
-	utxoDAG.outputStorage.Store(input2).Release()
-
-	outputAddress1 := address.Random()
-	outputAddress2 := address.Random()
-
-	// attach first spend
-	valueTangle.AttachPayload(payload.New(payload.GenesisID, payload.GenesisID, transaction.New(
-		transaction.NewInputs(
-			input1.ID(),
-			input2.ID(),
-		),
-
-		transaction.NewOutputs(map[address.Address][]*balance.Balance{
-			outputAddress1: {
-				balance.New(balance.ColorNew, 1337),
-			},
-		}),
-	)))
-
-	// attach double spend
-	valueTangle.AttachPayload(payload.New(payload.GenesisID, payload.GenesisID, transaction.New(
-		transaction.NewInputs(
-			input1.ID(),
-			input2.ID(),
-		),
-
-		transaction.NewOutputs(map[address.Address][]*balance.Balance{
-			outputAddress2: {
-				balance.New(balance.ColorNew, 1337),
-			},
-		}),
-	)))
-
-	valueTangle.Shutdown()
-	utxoDAG.Shutdown()
-}
diff --git a/dapps/valuetransfers/packages/utxodag/transactionmetadata.go b/dapps/valuetransfers/packages/tangle/transactionmetadata.go
similarity index 99%
rename from dapps/valuetransfers/packages/utxodag/transactionmetadata.go
rename to dapps/valuetransfers/packages/tangle/transactionmetadata.go
index ba9a23b1e91e669ada378854de2077e7af51cc13..7bda5190b319563a46b823e6552461f98e2e6a11 100644
--- a/dapps/valuetransfers/packages/utxodag/transactionmetadata.go
+++ b/dapps/valuetransfers/packages/tangle/transactionmetadata.go
@@ -1,4 +1,4 @@
-package utxodag
+package tangle
 
 import (
 	"sync"
diff --git a/dapps/valuetransfers/packages/test/valuetransfers_test.go b/dapps/valuetransfers/packages/test/valuetransfers_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..d321c931d23b6b07a3934674626e76f0ec11eccd
--- /dev/null
+++ b/dapps/valuetransfers/packages/test/valuetransfers_test.go
@@ -0,0 +1,97 @@
+package test
+
+import (
+	"testing"
+	"time"
+
+	"github.com/iotaledger/hive.go/crypto/ed25519"
+	"github.com/stretchr/testify/assert"
+
+	"github.com/iotaledger/goshimmer/dapps/valuetransfers/packages/address"
+	"github.com/iotaledger/goshimmer/dapps/valuetransfers/packages/balance"
+	"github.com/iotaledger/goshimmer/dapps/valuetransfers/packages/payload"
+	"github.com/iotaledger/goshimmer/dapps/valuetransfers/packages/tangle"
+	"github.com/iotaledger/goshimmer/dapps/valuetransfers/packages/transaction"
+	"github.com/iotaledger/goshimmer/packages/binary/testutil"
+)
+
+func TestTangle_ValueTransfer(t *testing.T) {
+	// initialize tangle + ledgerstate
+	valueTangle := tangle.New(testutil.DB(t))
+	if err := valueTangle.Prune(); err != nil {
+		t.Error(err)
+
+		return
+	}
+	ledgerState := tangle.NewLedgerState(valueTangle)
+
+	//
+	addressKeyPair1 := ed25519.GenerateKeyPair()
+	addressKeyPair2 := ed25519.GenerateKeyPair()
+	address1 := address.FromED25519PubKey(addressKeyPair1.PublicKey)
+	address2 := address.FromED25519PubKey(addressKeyPair2.PublicKey)
+
+	// check if ledger empty first
+	assert.Equal(t, map[balance.Color]int64{}, ledgerState.Balances(address1))
+	assert.Equal(t, map[balance.Color]int64{}, ledgerState.Balances(address2))
+
+	// load snapshot
+	valueTangle.LoadSnapshot(map[transaction.ID]map[address.Address][]*balance.Balance{
+		transaction.GenesisID: {
+			address1: []*balance.Balance{
+				balance.New(balance.ColorIOTA, 337),
+			},
+
+			address2: []*balance.Balance{
+				balance.New(balance.ColorIOTA, 1000),
+			},
+		},
+	})
+
+	// check if balance exists after loading snapshot
+	assert.Equal(t, map[balance.Color]int64{balance.ColorIOTA: 337}, ledgerState.Balances(address1))
+	assert.Equal(t, map[balance.Color]int64{balance.ColorIOTA: 1000}, ledgerState.Balances(address2))
+
+	// attach first spend
+	outputAddress1 := address.Random()
+	valueTangle.AttachPayload(payload.New(payload.GenesisID, payload.GenesisID, transaction.New(
+		transaction.NewInputs(
+			transaction.NewOutputID(address1, transaction.GenesisID),
+			transaction.NewOutputID(address2, transaction.GenesisID),
+		),
+
+		transaction.NewOutputs(map[address.Address][]*balance.Balance{
+			outputAddress1: {
+				balance.New(balance.ColorIOTA, 1337),
+			},
+		}),
+	)))
+
+	// wait for async task to run (TODO: REPLACE TIME BASED APPROACH WITH A WG)
+	time.Sleep(500 * time.Millisecond)
+
+	// check if old addresses are empty
+	assert.Equal(t, map[balance.Color]int64{}, ledgerState.Balances(address1))
+	assert.Equal(t, map[balance.Color]int64{}, ledgerState.Balances(address2))
+
+	// check if new addresses are filled
+	assert.Equal(t, map[balance.Color]int64{balance.ColorIOTA: 1337}, ledgerState.Balances(outputAddress1))
+
+	// attach double spend
+	outputAddress2 := address.Random()
+	valueTangle.AttachPayload(payload.New(payload.GenesisID, payload.GenesisID, transaction.New(
+		transaction.NewInputs(
+			transaction.NewOutputID(address1, transaction.GenesisID),
+			transaction.NewOutputID(address2, transaction.GenesisID),
+		),
+
+		transaction.NewOutputs(map[address.Address][]*balance.Balance{
+			outputAddress2: {
+				balance.New(balance.ColorNew, 1337),
+			},
+		}),
+	)))
+
+	// shutdown tangle
+	valueTangle.Shutdown()
+}
diff --git a/dapps/valuetransfers/packages/transaction/id.go b/dapps/valuetransfers/packages/transaction/id.go
index aa333343d30d345c96e153c920ebc3949f7a92c0..f1acc5e7ef17d5d5702d6472b3dcae0877e2aac8 100644
--- a/dapps/valuetransfers/packages/transaction/id.go
+++ b/dapps/valuetransfers/packages/transaction/id.go
@@ -82,5 +82,7 @@ func (id ID) String() string {
 	return base58.Encode(id[:])
 }
 
+var GenesisID ID
+
 // IDLength contains the amount of bytes that a marshaled version of the ID contains.
 const IDLength = 32
diff --git a/dapps/valuetransfers/packages/utxodag/events.go b/dapps/valuetransfers/packages/utxodag/events.go
deleted file mode 100644
index 3ffdc48b0d59c981a054162b8a4d158f7e0de182..0000000000000000000000000000000000000000
--- a/dapps/valuetransfers/packages/utxodag/events.go
+++ /dev/null
@@ -1,59 +0,0 @@
-package utxodag
-
-import (
-	"github.com/iotaledger/goshimmer/dapps/valuetransfers/packages/branchmanager"
-	"github.com/iotaledger/goshimmer/dapps/valuetransfers/packages/transaction"
-
-	"github.com/iotaledger/hive.go/events"
-)
-
-// Events is a container for the different kind of events of the UTXODAG.
-type Events struct {
-	// TransactionReceived gets triggered whenever a transaction was received for the first time (not solid yet).
-	TransactionReceived *events.Event
-
-	// TransactionBooked gets triggered whenever a transactions becomes solid and gets booked into a particular branch.
-	TransactionBooked *events.Event
-
-	// Fork gets triggered when a previously un-conflicting transaction get's some of its inputs double spend, so that a
-	// new Branch is created.
-	Fork *events.Event
-
-	Error *events.Event
-}
-
-func newEvents() *Events {
-	return &Events{
-		TransactionReceived: events.NewEvent(cachedTransactionEvent),
-		TransactionBooked:   events.NewEvent(transactionBookedEvent),
-		Fork:                events.NewEvent(forkEvent),
-		Error:               events.NewEvent(events.ErrorCaller),
-	}
-}
-
-func transactionBookedEvent(handler interface{}, params ...interface{}) {
-	handler.(func(*transaction.CachedTransaction, *CachedTransactionMetadata, *branchmanager.CachedBranch, []transaction.OutputID, bool))(
-		params[0].(*transaction.CachedTransaction).Retain(),
-		params[1].(*CachedTransactionMetadata).Retain(),
-		params[2].(*branchmanager.CachedBranch).Retain(),
-		params[3].([]transaction.OutputID),
-		params[4].(bool),
-	)
-}
-
-func forkEvent(handler interface{}, params ...interface{}) {
-	handler.(func(*transaction.CachedTransaction, *CachedTransactionMetadata, *branchmanager.CachedBranch, []transaction.OutputID))(
-		params[0].(*transaction.CachedTransaction).Retain(),
-		params[1].(*CachedTransactionMetadata).Retain(),
-		params[2].(*branchmanager.CachedBranch).Retain(),
-		params[3].([]transaction.OutputID),
-	)
-}
-
-func cachedTransactionEvent(handler interface{}, params ...interface{}) {
-	handler.(func(*transaction.CachedTransaction, *CachedTransactionMetadata, *CachedAttachment))(
-		params[0].(*transaction.CachedTransaction).Retain(),
-		params[1].(*CachedTransactionMetadata).Retain(),
-		params[2].(*CachedAttachment).Retain(),
-	)
-}
diff --git a/dapps/valuetransfers/packages/utxodag/objectstorage.go b/dapps/valuetransfers/packages/utxodag/objectstorage.go
deleted file mode 100644
index 3cee1878bdd43fc6c858012bab9cf94bd9c54a64..0000000000000000000000000000000000000000
--- a/dapps/valuetransfers/packages/utxodag/objectstorage.go
+++ /dev/null
@@ -1,48 +0,0 @@
-package utxodag
-
-import (
-	"time"
-
-	"github.com/iotaledger/hive.go/objectstorage"
-
-	"github.com/iotaledger/goshimmer/dapps/valuetransfers/packages/transaction"
-)
-
-const (
-	// the following values are a list of prefixes defined as an enum
-	_ byte = iota
-
-	// prefixes used for the objectstorage
-	osTransaction
-	osTransactionMetadata
-	osAttachment
-	osOutput
-	osConsumer
-)
-
-var (
-	osLeakDetectionOption = objectstorage.LeakDetectionEnabled(true, objectstorage.LeakDetectionOptions{
-		MaxConsumersPerObject: 10,
-		MaxConsumerHoldTime:   10 * time.Second,
-	})
-)
-
-func osTransactionFactory(key []byte) (objectstorage.StorableObject, int, error) {
-	return transaction.FromStorageKey(key)
-}
-
-func osTransactionMetadataFactory(key []byte) (objectstorage.StorableObject, int, error) {
-	return TransactionMetadataFromStorageKey(key)
-}
-
-func osAttachmentFactory(key []byte) (objectstorage.StorableObject, int, error) {
-	return AttachmentFromStorageKey(key)
-}
-
-func osOutputFactory(key []byte) (objectstorage.StorableObject, int, error) {
-	return OutputFromStorageKey(key)
-}
-
-func osConsumerFactory(key []byte) (objectstorage.StorableObject, int, error) {
-	return ConsumerFromStorageKey(key)
-}
diff --git a/dapps/valuetransfers/packages/utxodag/utxodag.go b/dapps/valuetransfers/packages/utxodag/utxodag.go
deleted file mode 100644
index 399e3305eabfcfdae118c6b568106f7b9294f6ab..0000000000000000000000000000000000000000
--- a/dapps/valuetransfers/packages/utxodag/utxodag.go
+++ /dev/null
@@ -1,794 +0,0 @@
-package utxodag
-
-import (
-	"container/list"
-	"errors"
-	"fmt"
-	"math"
-	"time"
-
-	"github.com/dgraph-io/badger/v2"
-	"github.com/iotaledger/hive.go/async"
-	"github.com/iotaledger/hive.go/events"
-	"github.com/iotaledger/hive.go/objectstorage"
-	"github.com/iotaledger/hive.go/types"
-
-	"github.com/iotaledger/goshimmer/dapps/valuetransfers/packages/address"
-	"github.com/iotaledger/goshimmer/dapps/valuetransfers/packages/balance"
-	"github.com/iotaledger/goshimmer/dapps/valuetransfers/packages/branchmanager"
-	"github.com/iotaledger/goshimmer/dapps/valuetransfers/packages/payload"
-	"github.com/iotaledger/goshimmer/dapps/valuetransfers/packages/tangle"
-	"github.com/iotaledger/goshimmer/dapps/valuetransfers/packages/transaction"
-	"github.com/iotaledger/goshimmer/packages/binary/storageprefix"
-)
-
-// UTXODAG represents the DAG of funds that are flowing from the genesis, to the addresses that have balance now, that
-// is embedded as another layer in the message tangle.
-type UTXODAG struct {
-	tangle        *tangle.Tangle
-	branchManager *branchmanager.BranchManager
-
-	transactionStorage         *objectstorage.ObjectStorage
-	transactionMetadataStorage *objectstorage.ObjectStorage
-	attachmentStorage          *objectstorage.ObjectStorage
-	outputStorage              *objectstorage.ObjectStorage
-	consumerStorage            *objectstorage.ObjectStorage
-
-	Events *Events
-
-	workerPool async.WorkerPool
-}
-
-// New is the constructor of the UTXODAG and creates a new DAG on top a tangle.
-func New(badgerInstance *badger.DB, tangle *tangle.Tangle) (result *UTXODAG) {
-	osFactory := objectstorage.NewFactory(badgerInstance, storageprefix.ValueTransfers)
-
-	result = &UTXODAG{
-		tangle:        tangle,
-		branchManager: branchmanager.New(badgerInstance),
-
-		transactionStorage:         osFactory.New(osTransaction, osTransactionFactory, objectstorage.CacheTime(time.Second), osLeakDetectionOption),
-		transactionMetadataStorage: osFactory.New(osTransactionMetadata, osTransactionMetadataFactory, objectstorage.CacheTime(time.Second), osLeakDetectionOption),
-		attachmentStorage:          osFactory.New(osAttachment, osAttachmentFactory, objectstorage.CacheTime(time.Second), osLeakDetectionOption),
-		outputStorage:              osFactory.New(osOutput, osOutputFactory, OutputKeyPartitions, objectstorage.CacheTime(time.Second), osLeakDetectionOption),
-		consumerStorage:            osFactory.New(osConsumer, osConsumerFactory, ConsumerPartitionKeys, objectstorage.CacheTime(time.Second), osLeakDetectionOption),
-
-		Events: newEvents(),
-	}
-
-	tangle.Events.PayloadSolid.Attach(events.NewClosure(result.ProcessSolidPayload))
-
-	return
-}
-
-// BranchManager is the getter for the manager that takes care of creating and updating branches.
-func (utxoDAG *UTXODAG) BranchManager() *branchmanager.BranchManager {
-	return utxoDAG.branchManager
-}
-
-// ProcessSolidPayload is the main method of this struct. It is used to add new solid Payloads to the DAG.
-func (utxoDAG *UTXODAG) ProcessSolidPayload(cachedPayload *payload.CachedPayload, cachedMetadata *tangle.CachedPayloadMetadata) {
-	utxoDAG.workerPool.Submit(func() { utxoDAG.storeTransactionWorker(cachedPayload, cachedMetadata) })
-}
-
-// Transaction loads the given transaction from the objectstorage.
-func (utxoDAG *UTXODAG) Transaction(transactionID transaction.ID) *transaction.CachedTransaction {
-	return &transaction.CachedTransaction{CachedObject: utxoDAG.transactionStorage.Load(transactionID.Bytes())}
-}
-
-// TransactionMetadata retrieves the metadata of a value payload from the object storage.
-func (utxoDAG *UTXODAG) TransactionMetadata(transactionID transaction.ID) *CachedTransactionMetadata {
-	return &CachedTransactionMetadata{CachedObject: utxoDAG.transactionMetadataStorage.Load(transactionID.Bytes())}
-}
-
-// TransactionOutput loads the given output from the objectstorage.
-func (utxoDAG *UTXODAG) TransactionOutput(outputID transaction.OutputID) *CachedOutput {
-	return &CachedOutput{CachedObject: utxoDAG.outputStorage.Load(outputID.Bytes())}
-}
-
-// GetConsumers retrieves the approvers of a payload from the object storage.
-func (utxoDAG *UTXODAG) GetConsumers(outputID transaction.OutputID) CachedConsumers {
-	consumers := make(CachedConsumers, 0)
-	utxoDAG.consumerStorage.ForEach(func(key []byte, cachedObject objectstorage.CachedObject) bool {
-		consumers = append(consumers, &CachedConsumer{CachedObject: cachedObject})
-
-		return true
-	}, outputID.Bytes())
-
-	return consumers
-}
-
-// GetAttachments retrieves the att of a payload from the object storage.
-func (utxoDAG *UTXODAG) GetAttachments(transactionID transaction.ID) CachedAttachments {
-	attachments := make(CachedAttachments, 0)
-	utxoDAG.attachmentStorage.ForEach(func(key []byte, cachedObject objectstorage.CachedObject) bool {
-		attachments = append(attachments, &CachedAttachment{CachedObject: cachedObject})
-
-		return true
-	}, transactionID.Bytes())
-
-	return attachments
-}
-
-// Shutdown stops the worker pools and shuts down the object storage instances.
-func (utxoDAG *UTXODAG) Shutdown() *UTXODAG {
-	utxoDAG.workerPool.ShutdownGracefully()
-
-	utxoDAG.transactionStorage.Shutdown()
-	utxoDAG.transactionMetadataStorage.Shutdown()
-	utxoDAG.outputStorage.Shutdown()
-	utxoDAG.consumerStorage.Shutdown()
-
-	return utxoDAG
-}
-
-// Prune resets the database and deletes all objects (for testing or "node resets").
-func (utxoDAG *UTXODAG) Prune() (err error) {
-	if err = utxoDAG.branchManager.Prune(); err != nil {
-		return
-	}
-
-	for _, storage := range []*objectstorage.ObjectStorage{
-		utxoDAG.transactionStorage,
-		utxoDAG.transactionMetadataStorage,
-		utxoDAG.outputStorage,
-		utxoDAG.consumerStorage,
-	} {
-		if err = storage.Prune(); err != nil {
-			return
-		}
-	}
-
-	return
-}
-
-func (utxoDAG *UTXODAG) storeTransactionWorker(cachedPayload *payload.CachedPayload, cachedPayloadMetadata *tangle.CachedPayloadMetadata) {
-	defer cachedPayload.Release()
-	defer cachedPayloadMetadata.Release()
-
-	// abort if the parameters are empty
-	solidPayload := cachedPayload.Unwrap()
-	if solidPayload == nil || cachedPayloadMetadata.Unwrap() == nil {
-		return
-	}
-
-	// store objects in database
-	cachedTransaction, cachedTransactionMetadata, cachedAttachment, transactionIsNew := utxoDAG.storeTransactionModels(solidPayload)
-
-	// abort if the attachment was previously processed already (nil == was not stored)
-	if cachedAttachment == nil {
-		cachedTransaction.Release()
-		cachedTransactionMetadata.Release()
-
-		return
-	}
-
-	// trigger events for a new transaction
-	if transactionIsNew {
-		utxoDAG.Events.TransactionReceived.Trigger(cachedTransaction, cachedTransactionMetadata, cachedAttachment)
-	}
-
-	// check solidity of transaction and its corresponding attachment
-	utxoDAG.solidifyTransactionWorker(cachedTransaction, cachedTransactionMetadata, cachedAttachment)
-}
-
-func (utxoDAG *UTXODAG) storeTransactionModels(solidPayload *payload.Payload) (cachedTransaction *transaction.CachedTransaction, cachedTransactionMetadata *CachedTransactionMetadata, cachedAttachment *CachedAttachment, transactionIsNew bool) {
-	cachedTransaction = &transaction.CachedTransaction{CachedObject: utxoDAG.transactionStorage.ComputeIfAbsent(solidPayload.Transaction().ID().Bytes(), func(key []byte) objectstorage.StorableObject {
-		transactionIsNew = true
-
-		result := solidPayload.Transaction()
-		result.Persist()
-		result.SetModified()
-
-		return result
-	})}
-
-	if transactionIsNew {
-		cachedTransactionMetadata = &CachedTransactionMetadata{CachedObject: utxoDAG.transactionMetadataStorage.Store(NewTransactionMetadata(solidPayload.Transaction().ID()))}
-
-		// store references to the consumed outputs
-		solidPayload.Transaction().Inputs().ForEach(func(outputId transaction.OutputID) bool {
-			utxoDAG.consumerStorage.Store(NewConsumer(outputId, solidPayload.Transaction().ID())).Release()
-
-			return true
-		})
-	} else {
-		cachedTransactionMetadata = &CachedTransactionMetadata{CachedObject: utxoDAG.transactionMetadataStorage.Load(solidPayload.Transaction().ID().Bytes())}
-	}
-
-	// store a reference from the transaction to the payload that attached it or abort, if we have processed this attachment already
-	attachment, stored := utxoDAG.attachmentStorage.StoreIfAbsent(NewAttachment(solidPayload.Transaction().ID(), solidPayload.ID()))
-	if !stored {
-		return
-	}
-	cachedAttachment = &CachedAttachment{CachedObject: attachment}
-
-	return
-}
-
-func (utxoDAG *UTXODAG) solidifyTransactionWorker(cachedTransaction *transaction.CachedTransaction, cachedTransactionMetadata *CachedTransactionMetadata, attachment *CachedAttachment) {
-	// initialize the stack
-	solidificationStack := list.New()
-	solidificationStack.PushBack([3]interface{}{cachedTransaction, cachedTransactionMetadata, attachment})
-
-	// process payloads that are supposed to be checked for solidity recursively
-	for solidificationStack.Len() > 0 {
-		// execute logic inside a func, so we can use defer to release the objects
-		func() {
-			// retrieve cached objects
-			currentCachedTransaction, currentCachedTransactionMetadata, currentCachedAttachment := utxoDAG.popElementsFromSolidificationStack(solidificationStack)
-			defer currentCachedTransaction.Release()
-			defer currentCachedTransactionMetadata.Release()
-			defer currentCachedAttachment.Release()
-
-			// unwrap cached objects
-			currentTransaction := currentCachedTransaction.Unwrap()
-			currentTransactionMetadata := currentCachedTransactionMetadata.Unwrap()
-			currentAttachment := currentCachedAttachment.Unwrap()
-
-			// abort if any of the retrieved models is nil or payload is not solid or it was set as solid already
-			if currentTransaction == nil || currentTransactionMetadata == nil || currentAttachment == nil {
-				return
-			}
-
-			// abort if the transaction is not solid or invalid
-			if transactionSolid, err := utxoDAG.isTransactionSolid(currentTransaction, currentTransactionMetadata); !transactionSolid || err != nil {
-				if err != nil {
-					// TODO: TRIGGER INVALID TX + REMOVE TXS THAT APPROVE IT
-					fmt.Println(err, currentTransaction)
-				}
-
-				return
-			}
-
-			transactionBecameNewlySolid := currentTransactionMetadata.SetSolid(true)
-			if !transactionBecameNewlySolid {
-				// TODO: book attachment / create reference from the value message to the corresponding branch
-
-				return
-			}
-
-			// ... and schedule check of approvers
-			utxoDAG.ForEachConsumers(currentTransaction, func(cachedTransaction *transaction.CachedTransaction, transactionMetadata *CachedTransactionMetadata, cachedAttachment *CachedAttachment) {
-				solidificationStack.PushBack([3]interface{}{cachedTransaction, transactionMetadata, cachedAttachment})
-			})
-
-			// book transaction
-			if err := utxoDAG.bookTransaction(cachedTransaction.Retain(), cachedTransactionMetadata.Retain()); err != nil {
-				utxoDAG.Events.Error.Trigger(err)
-			}
-		}()
-	}
-}
-
-func (utxoDAG *UTXODAG) popElementsFromSolidificationStack(stack *list.List) (*transaction.CachedTransaction, *CachedTransactionMetadata, *CachedAttachment) {
-	currentSolidificationEntry := stack.Front()
-	cachedTransaction := currentSolidificationEntry.Value.([3]interface{})[0].(*transaction.CachedTransaction)
-	cachedTransactionMetadata := currentSolidificationEntry.Value.([3]interface{})[1].(*CachedTransactionMetadata)
-	cachedAttachment := currentSolidificationEntry.Value.([3]interface{})[2].(*CachedAttachment)
-	stack.Remove(currentSolidificationEntry)
-
-	return cachedTransaction, cachedTransactionMetadata, cachedAttachment
-}
-
-func (utxoDAG *UTXODAG) isTransactionSolid(tx *transaction.Transaction, metadata *TransactionMetadata) (bool, error) {
-	// abort if any of the models are nil or has been deleted
-	if tx == nil || tx.IsDeleted() || metadata == nil || metadata.IsDeleted() {
-		return false, nil
-	}
-
-	// abort if we have previously determined the solidity status of the transaction already
-	if metadata.Solid() {
-		return true, nil
-	}
-
-	// get outputs that were referenced in the transaction inputs
-	cachedInputs := utxoDAG.getCachedOutputsFromTransactionInputs(tx)
-	defer cachedInputs.Release()
-
-	// check the solidity of the inputs and retrieve the consumed balances
-	inputsSolid, consumedBalances, err := utxoDAG.checkTransactionInputs(cachedInputs)
-
-	// abort if an error occurred or the inputs are not solid, yet
-	if !inputsSolid || err != nil {
-		return false, err
-	}
-
-	if !utxoDAG.checkTransactionOutputs(consumedBalances, tx.Outputs()) {
-		return false, fmt.Errorf("the outputs do not match the inputs in transaction with id '%s'", tx.ID())
-	}
-
-	return true, nil
-}
-
-func (utxoDAG *UTXODAG) getCachedOutputsFromTransactionInputs(tx *transaction.Transaction) (result CachedOutputs) {
-	result = make(CachedOutputs)
-	tx.Inputs().ForEach(func(inputId transaction.OutputID) bool {
-		result[inputId] = utxoDAG.TransactionOutput(inputId)
-
-		return true
-	})
-
-	return
-}
-
-func (utxoDAG *UTXODAG) checkTransactionInputs(cachedInputs CachedOutputs) (inputsSolid bool, consumedBalances map[balance.Color]int64, err error) {
-	inputsSolid = true
-	consumedBalances = make(map[balance.Color]int64)
-	consumedBranches := make([]branchmanager.BranchID, 0)
-
-	for _, cachedInput := range cachedInputs {
-		if !cachedInput.Exists() {
-			inputsSolid = false
-
-			continue
-		}
-
-		// should never be nil as we check Exists() before
-		input := cachedInput.Unwrap()
-
-		// update solid status
-		inputsSolid = inputsSolid && input.Solid()
-
-		consumedBranches = append(consumedBranches, input.BranchID())
-
-		// calculate the input balances
-		for _, inputBalance := range input.Balances() {
-			var newBalance int64
-			if currentBalance, balanceExists := consumedBalances[inputBalance.Color()]; balanceExists {
-				// check overflows in the numbers
-				if inputBalance.Value() > math.MaxInt64-currentBalance {
-					// TODO: make it an explicit error var
-					err = fmt.Errorf("buffer overflow in balances of inputs")
-
-					return
-				}
-
-				newBalance = currentBalance + inputBalance.Value()
-			} else {
-				newBalance = inputBalance.Value()
-			}
-			consumedBalances[inputBalance.Color()] = newBalance
-		}
-	}
-
-	branchesConflicting, err := utxoDAG.BranchManager().BranchesConflicting(consumedBranches...)
-	if branchesConflicting {
-		// TODO: make it an explicit error var
-		err = fmt.Errorf("the transaction combines conflicting branches")
-	}
-
-	return
-}
-
-// checkTransactionOutputs is a utility function that returns true, if the outputs are consuming all of the given inputs
-// (the sum of all the balance changes is 0). It also accounts for the ability to "recolor" coins during the creating of
-// outputs. If this function returns false, then the outputs that are defined in the transaction are invalid and the
-// transaction should be removed from the ledger state.
-func (utxoDAG *UTXODAG) checkTransactionOutputs(inputBalances map[balance.Color]int64, outputs *transaction.Outputs) bool {
-	// create a variable to keep track of outputs that create a new color
-	var newlyColoredCoins int64
-	var uncoloredCoins int64
-
-	// iterate through outputs and check them one by one
-	aborted := !outputs.ForEach(func(address address.Address, balances []*balance.Balance) bool {
-		for _, outputBalance := range balances {
-			// abort if the output creates a negative or empty output
-			if outputBalance.Value() <= 0 {
-				return false
-			}
-
-			// sidestep logic if we have a newly colored output (we check the supply later)
-			if outputBalance.Color() == balance.ColorNew {
-				// catch overflows
-				if newlyColoredCoins > math.MaxInt64-outputBalance.Value() {
-					return false
-				}
-
-				newlyColoredCoins += outputBalance.Value()
-
-				continue
-			}
-
-			// sidestep logic if we have a newly colored output (we check the supply later)
-			if outputBalance.Color() == balance.ColorIOTA {
-				// catch overflows
-				if uncoloredCoins > math.MaxInt64-outputBalance.Value() {
-					return false
-				}
-
-				uncoloredCoins += outputBalance.Value()
-
-				continue
-			}
-
-			// check if the used color does not exist in our supply
-			availableBalance, spentColorExists := inputBalances[outputBalance.Color()]
-			if !spentColorExists {
-				return false
-			}
-
-			// abort if we spend more coins of the given color than we have
-			if availableBalance < outputBalance.Value() {
-				return false
-			}
-
-			// subtract the spent coins from the supply of this color
-			inputBalances[outputBalance.Color()] -= outputBalance.Value()
-
-			// cleanup empty map entries (we have exhausted our funds)
-			if inputBalances[outputBalance.Color()] == 0 {
-				delete(inputBalances, outputBalance.Color())
-			}
-		}
-
-		return true
-	})
-
-	// abort if the previous checks failed
-	if aborted {
-		return false
-	}
-
-	// determine the unspent inputs
-	var unspentCoins int64
-	for _, unspentBalance := range inputBalances {
-		// catch overflows
-		if unspentCoins > math.MaxInt64-unspentBalance {
-			return false
-		}
-
-		unspentCoins += unspentBalance
-	}
-
-	// the outputs are valid if they spend all consumed funds
-	return unspentCoins == newlyColoredCoins+uncoloredCoins
-}
-
-// ForEachConsumers iterates through the transactions that are consuming outputs of the given transactions
-func (utxoDAG *UTXODAG) ForEachConsumers(currentTransaction *transaction.Transaction, consume func(cachedTransaction *transaction.CachedTransaction, transactionMetadata *CachedTransactionMetadata, cachedAttachment *CachedAttachment)) {
-	seenTransactions := make(map[transaction.ID]types.Empty)
-	currentTransaction.Outputs().ForEach(func(address address.Address, balances []*balance.Balance) bool {
-		utxoDAG.GetConsumers(transaction.NewOutputID(address, currentTransaction.ID())).Consume(func(consumer *Consumer) {
-			if _, transactionSeen := seenTransactions[consumer.TransactionID()]; !transactionSeen {
-				seenTransactions[consumer.TransactionID()] = types.Void
-
-				cachedTransaction := utxoDAG.Transaction(consumer.TransactionID())
-				cachedTransactionMetadata := utxoDAG.TransactionMetadata(consumer.TransactionID())
-				for _, cachedAttachment := range utxoDAG.GetAttachments(consumer.TransactionID()) {
-					consume(cachedTransaction, cachedTransactionMetadata, cachedAttachment)
-				}
-			}
-		})
-
-		return true
-	})
-}
-
-func (utxoDAG *UTXODAG) bookTransaction(cachedTransaction *transaction.CachedTransaction, cachedTransactionMetadata *CachedTransactionMetadata) (err error) {
-	defer cachedTransaction.Release()
-	defer cachedTransactionMetadata.Release()
-
-	transactionToBook := cachedTransaction.Unwrap()
-	if transactionToBook == nil {
-		// TODO: explicit error var
-		err = errors.New("failed to unwrap transaction")
-
-		return
-	}
-
-	transactionMetadata := cachedTransactionMetadata.Unwrap()
-	if transactionMetadata == nil {
-		// TODO: explicit error var
-		err = errors.New("failed to unwrap transaction metadata")
-
-		return
-	}
-
-	consumedBranches := make(branchmanager.BranchIds)
-	conflictingInputs := make([]transaction.OutputID, 0)
-	conflictingInputsOfFirstConsumers := make(map[transaction.ID][]transaction.OutputID)
-
-	if !transactionToBook.Inputs().ForEach(func(outputID transaction.OutputID) bool {
-		cachedOutput := utxoDAG.TransactionOutput(outputID)
-		defer cachedOutput.Release()
-
-		// abort if the output could not be found
-		output := cachedOutput.Unwrap()
-		if output == nil {
-			err = fmt.Errorf("could not load output '%s'", outputID)
-
-			return false
-		}
-
-		consumedBranches[output.BranchID()] = types.Void
-
-		// register the current consumer and check if the input has been consumed before
-		consumerCount, firstConsumerID := output.RegisterConsumer(transactionToBook.ID())
-		switch consumerCount {
-		// continue if we are the first consumer and there is no double spend
-		case 0:
-			return true
-
-		// if the input has been consumed before but not been forked, yet
-		case 1:
-			// keep track of the conflicting inputs so we can fork them
-			if _, conflictingInputsExist := conflictingInputsOfFirstConsumers[firstConsumerID]; !conflictingInputsExist {
-				conflictingInputsOfFirstConsumers[firstConsumerID] = make([]transaction.OutputID, 0)
-			}
-			conflictingInputsOfFirstConsumers[firstConsumerID] = append(conflictingInputsOfFirstConsumers[firstConsumerID], outputID)
-		}
-
-		// keep track of the conflicting inputs
-		conflictingInputs = append(conflictingInputs, outputID)
-
-		return true
-	}) {
-		return
-	}
-
-	// TODO: handle error
-	cachedTargetBranch, _ := utxoDAG.branchManager.AggregateBranches(consumedBranches.ToList()...)
-	defer cachedTargetBranch.Release()
-
-	targetBranch := cachedTargetBranch.Unwrap()
-	if targetBranch == nil {
-		return errors.New("failed to unwrap target branch")
-	}
-	targetBranch.Persist()
-
-	if len(conflictingInputs) >= 1 {
-		cachedTargetBranch, _ = utxoDAG.branchManager.Fork(branchmanager.NewBranchID(transactionToBook.ID()), []branchmanager.BranchID{targetBranch.ID()}, conflictingInputs)
-		defer cachedTargetBranch.Release()
-
-		targetBranch = cachedTargetBranch.Unwrap()
-		if targetBranch == nil {
-			return errors.New("failed to inherit branches")
-		}
-	}
-
-	// book transaction into target branch
-	transactionMetadata.SetBranchID(targetBranch.ID())
-
-	// book outputs into the target branch
-	transactionToBook.Outputs().ForEach(func(address address.Address, balances []*balance.Balance) bool {
-		newOutput := NewOutput(address, transactionToBook.ID(), targetBranch.ID(), balances)
-		newOutput.SetSolid(true)
-		utxoDAG.outputStorage.Store(newOutput).Release()
-
-		return true
-	})
-
-	// fork the conflicting transactions into their own branch
-	decisionPending := false
-	for consumerID, conflictingInputs := range conflictingInputsOfFirstConsumers {
-		_, decisionFinalized, forkedErr := utxoDAG.Fork(consumerID, conflictingInputs)
-		if forkedErr != nil {
-			err = forkedErr
-
-			return
-		}
-
-		decisionPending = decisionPending || !decisionFinalized
-	}
-
-	// trigger events
-	utxoDAG.Events.TransactionBooked.Trigger(cachedTransaction, cachedTransactionMetadata, cachedTargetBranch, conflictingInputs, decisionPending)
-
-	// TODO: BOOK ATTACHMENT
-
-	return
-}
-
-func (utxoDAG *UTXODAG) calculateBranchOfTransaction(currentTransaction *transaction.Transaction) (branch *branchmanager.CachedBranch, err error) {
-	consumedBranches := make(branchmanager.BranchIds)
-	if !currentTransaction.Inputs().ForEach(func(outputId transaction.OutputID) bool {
-		cachedTransactionOutput := utxoDAG.TransactionOutput(outputId)
-		defer cachedTransactionOutput.Release()
-
-		transactionOutput := cachedTransactionOutput.Unwrap()
-		if transactionOutput == nil {
-			err = fmt.Errorf("failed to load output '%s'", outputId)
-
-			return false
-		}
-
-		consumedBranches[transactionOutput.BranchID()] = types.Void
-
-		return true
-	}) {
-		return
-	}
-
-	branch, err = utxoDAG.branchManager.AggregateBranches(consumedBranches.ToList()...)
-
-	return
-}
-
-// TODO: write comment what it does
-func (utxoDAG *UTXODAG) moveTransactionToBranch(cachedTransaction *transaction.CachedTransaction, cachedTransactionMetadata *CachedTransactionMetadata, cachedTargetBranch *branchmanager.CachedBranch) (err error) {
-	// push transaction that shall be moved to the stack
-	transactionStack := list.New()
-	branchStack := list.New()
-	branchStack.PushBack([3]interface{}{cachedTransactionMetadata.Unwrap().BranchID(), cachedTargetBranch, transactionStack})
-	transactionStack.PushBack([2]interface{}{cachedTransaction, cachedTransactionMetadata})
-
-	// iterate through all transactions (grouped by their branch)
-	for branchStack.Len() >= 1 {
-		if err = func() error {
-			// retrieve branch details from stack
-			currentSolidificationEntry := branchStack.Front()
-			currentSourceBranch := currentSolidificationEntry.Value.([3]interface{})[0].(branchmanager.BranchID)
-			currentCachedTargetBranch := currentSolidificationEntry.Value.([3]interface{})[1].(*branchmanager.CachedBranch)
-			transactionStack := currentSolidificationEntry.Value.([3]interface{})[2].(*list.List)
-			branchStack.Remove(currentSolidificationEntry)
-			defer currentCachedTargetBranch.Release()
-
-			// unpack target branch
-			targetBranch := currentCachedTargetBranch.Unwrap()
-			if targetBranch == nil {
-				return errors.New("failed to unpack branch")
-			}
-
-			// iterate through transactions
-			for transactionStack.Len() >= 1 {
-				if err = func() error {
-					// retrieve transaction details from stack
-					currentSolidificationEntry := transactionStack.Front()
-					currentCachedTransaction := currentSolidificationEntry.Value.([2]interface{})[0].(*transaction.CachedTransaction)
-					currentCachedTransactionMetadata := currentSolidificationEntry.Value.([2]interface{})[1].(*CachedTransactionMetadata)
-					transactionStack.Remove(currentSolidificationEntry)
-					defer currentCachedTransaction.Release()
-					defer currentCachedTransactionMetadata.Release()
-
-					// unwrap transaction
-					currentTransaction := currentCachedTransaction.Unwrap()
-					if currentTransaction == nil {
-						return errors.New("failed to unwrap transaction")
-					}
-
-					// unwrap transaction metadata
-					currentTransactionMetadata := currentCachedTransactionMetadata.Unwrap()
-					if currentTransactionMetadata == nil {
-						return errors.New("failed to unwrap transaction metadata")
-					}
-
-					// if we arrived at a nested branch
-					if currentTransactionMetadata.BranchID() != currentSourceBranch {
-						// abort if we the branch is a conflict branch or an error occurred while trying to elevate
-						isConflictBranch, _, elevateErr := utxoDAG.branchManager.ElevateConflictBranch(currentTransactionMetadata.BranchID(), targetBranch.ID())
-						if elevateErr != nil || isConflictBranch {
-							return elevateErr
-						}
-
-						// determine the new branch of the transaction
-						newCachedTargetBranch, branchErr := utxoDAG.calculateBranchOfTransaction(currentTransaction)
-						if branchErr != nil {
-							return branchErr
-						}
-						defer newCachedTargetBranch.Release()
-
-						// unwrap the branch
-						newTargetBranch := newCachedTargetBranch.Unwrap()
-						if newTargetBranch == nil {
-							return errors.New("failed to unwrap branch")
-						}
-						newTargetBranch.Persist()
-
-						// add the new branch (with the current transaction as a starting point to the branch stack)
-						newTransactionStack := list.New()
-						newTransactionStack.PushBack([2]interface{}{currentCachedTransaction.Retain(), currentCachedTransactionMetadata.Retain()})
-						branchStack.PushBack([3]interface{}{currentTransactionMetadata.BranchID(), newCachedTargetBranch.Retain(), newTransactionStack})
-
-						return nil
-					}
-
-					// abort if we did not modify the branch of the transaction
-					if !currentTransactionMetadata.SetBranchID(targetBranch.ID()) {
-						return nil
-					}
-
-					// iterate through the outputs of the moved transaction
-					currentTransaction.Outputs().ForEach(func(address address.Address, balances []*balance.Balance) bool {
-						// create reference to the output
-						outputID := transaction.NewOutputID(address, currentTransaction.ID())
-
-						// load output from database
-						cachedOutput := utxoDAG.TransactionOutput(outputID)
-						defer cachedOutput.Release()
-
-						// unwrap output
-						output := cachedOutput.Unwrap()
-						if output == nil {
-							err = fmt.Errorf("failed to load output '%s'", outputID)
-
-							return false
-						}
-
-						// abort if the output was moved already
-						if !output.SetBranchID(targetBranch.ID()) {
-							return true
-						}
-
-						// schedule consumers for further checks
-						consumingTransactions := make(map[transaction.ID]types.Empty)
-						utxoDAG.GetConsumers(transaction.NewOutputID(address, currentTransaction.ID())).Consume(func(consumer *Consumer) {
-							consumingTransactions[consumer.TransactionID()] = types.Void
-						})
-						for transactionID := range consumingTransactions {
-							transactionStack.PushBack([2]interface{}{utxoDAG.Transaction(transactionID), utxoDAG.TransactionMetadata(transactionID)})
-						}
-
-						return true
-					})
-
-					return nil
-				}(); err != nil {
-					return err
-				}
-			}
-
-			return nil
-		}(); err != nil {
-			return
-		}
-	}
-
-	return
-}
-
-// Fork creates a new branch from an existing transaction.
-func (utxoDAG *UTXODAG) Fork(transactionID transaction.ID, conflictingInputs []transaction.OutputID) (forked bool, finalized bool, err error) {
-	cachedTransaction := utxoDAG.Transaction(transactionID)
-	cachedTransactionMetadata := utxoDAG.TransactionMetadata(transactionID)
-	defer cachedTransaction.Release()
-	defer cachedTransactionMetadata.Release()
-
-	tx := cachedTransaction.Unwrap()
-	if tx == nil {
-		err = fmt.Errorf("failed to load transaction '%s'", transactionID)
-
-		return
-	}
-	txMetadata := cachedTransactionMetadata.Unwrap()
-	if txMetadata == nil {
-		err = fmt.Errorf("failed to load metadata of transaction '%s'", transactionID)
-
-		return
-	}
-
-	// abort if this transaction was finalized already
-	if txMetadata.Finalized() {
-		finalized = true
-
-		return
-	}
-
-	// update / create new branch
-	cachedTargetBranch, newBranchCreated := utxoDAG.branchManager.Fork(branchmanager.NewBranchID(tx.ID()), []branchmanager.BranchID{txMetadata.BranchID()}, conflictingInputs)
-	defer cachedTargetBranch.Release()
-
-	// abort if the branch existed already
-	if !newBranchCreated {
-		return
-	}
-
-	// unpack branch
-	targetBranch := cachedTargetBranch.Unwrap()
-	if targetBranch == nil {
-		err = fmt.Errorf("failed to unpack branch for transaction '%s'", transactionID)
-
-		return
-	}
-
-	// move transactions to new branch
-	if err = utxoDAG.moveTransactionToBranch(cachedTransaction.Retain(), cachedTransactionMetadata.Retain(), cachedTargetBranch.Retain()); err != nil {
-		return
-	}
-
-	// trigger events + set result
-	utxoDAG.Events.Fork.Trigger(cachedTransaction, cachedTransactionMetadata, targetBranch, conflictingInputs)
-	forked = true
-
-	return
-}