diff --git a/packages/binary/valuetransfer/balance/color.go b/packages/binary/valuetransfer/balance/color.go
index b50ccacfd5458773a0167a3adbdf26a279485a6c..5f7ef740c003b927bad08fac65f5b63ad3026d2b 100644
--- a/packages/binary/valuetransfer/balance/color.go
+++ b/packages/binary/valuetransfer/balance/color.go
@@ -34,3 +34,5 @@ func (color Color) String() string {
 }
 
 var COLOR_IOTA Color = [32]byte{}
+
+var COLOR_NEW = [32]byte{255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255}
diff --git a/packages/binary/valuetransfer/ledgerstate/ledgerstate.go b/packages/binary/valuetransfer/ledgerstate/ledgerstate.go
new file mode 100644
index 0000000000000000000000000000000000000000..0bd374e50d1f28e322b5ab3f0e02df5c03264a64
--- /dev/null
+++ b/packages/binary/valuetransfer/ledgerstate/ledgerstate.go
@@ -0,0 +1,4 @@
+package ledgerstate
+
+type LedgerState struct {
+}
diff --git a/packages/binary/valuetransfer/tangle/branch.go b/packages/binary/valuetransfer/tangle/branch.go
new file mode 100644
index 0000000000000000000000000000000000000000..ad0af4da4332553891c6ee8f8d2fc9c26d980c33
--- /dev/null
+++ b/packages/binary/valuetransfer/tangle/branch.go
@@ -0,0 +1,84 @@
+package tangle
+
+import (
+	"github.com/iotaledger/hive.go/objectstorage"
+)
+
+type Branch struct {
+	objectstorage.StorableObjectFlags
+
+	id             BranchId
+	parentBranches []BranchId
+}
+
+func (branch *Branch) Update(other objectstorage.StorableObject) {
+	panic("implement me")
+}
+
+func (branch *Branch) ObjectStorageKey() []byte {
+	panic("implement me")
+}
+
+func (branch *Branch) ObjectStorageValue() []byte {
+	panic("implement me")
+}
+
+func (branch *Branch) UnmarshalObjectStorageValue(valueBytes []byte) (err error, consumedBytes int) {
+	panic("implement me")
+}
+
+func NewBranch(id BranchId, parentBranches []BranchId) *Branch {
+	return nil
+}
+
+func (branch *Branch) Id() BranchId {
+	return branch.id
+}
+
+func (branch *Branch) ParentBranches() []BranchId {
+	return branch.parentBranches
+}
+
+func (branch *Branch) IsAggregated() bool {
+	return len(branch.parentBranches) > 1
+}
+
+type CachedBranch struct {
+	objectstorage.CachedObject
+}
+
+func (cachedBranches *CachedBranch) Unwrap() *Branch {
+	if untypedObject := cachedBranches.Get(); untypedObject == nil {
+		return nil
+	} else {
+		if typedObject := untypedObject.(*Branch); typedObject == nil || typedObject.IsDeleted() {
+			return nil
+		} else {
+			return typedObject
+		}
+	}
+}
+
+func (cachedBranches *CachedBranch) Consume(consumer func(branch *Branch), forceRelease ...bool) (consumed bool) {
+	return cachedBranches.CachedObject.Consume(func(object objectstorage.StorableObject) {
+		consumer(object.(*Branch))
+	}, forceRelease...)
+}
+
+type CachedBranches map[BranchId]*CachedBranch
+
+func (cachedBranches CachedBranches) Consume(consumer func(branch *Branch)) (consumed bool) {
+	for _, cachedBranch := range cachedBranches {
+		consumed = cachedBranch.Consume(func(output *Branch) {
+			consumer(output)
+		}) || consumed
+	}
+
+	return
+}
+
+func (cachedBranches CachedBranches) Release(force ...bool) {
+	for _, cachedBranch := range cachedBranches {
+		cachedBranch.Release(force...)
+	}
+}
diff --git a/packages/binary/valuetransfer/tangle/branchid.go b/packages/binary/valuetransfer/tangle/branchid.go
new file mode 100644
index 0000000000000000000000000000000000000000..ba7f6f93039742e87906f7418fe8e3f23b0e2733
--- /dev/null
+++ b/packages/binary/valuetransfer/tangle/branchid.go
@@ -0,0 +1,84 @@
+package tangle
+
+import (
+	"fmt"
+
+	"github.com/iotaledger/hive.go/marshalutil"
+	"github.com/mr-tron/base58"
+
+	"github.com/iotaledger/goshimmer/packages/binary/valuetransfer/transaction"
+)
+
+type BranchId [BranchIdLength]byte
+
+var (
+	UndefinedBranchId = BranchId{}
+	MasterBranchId    = BranchId{1}
+)
+
+// NewBranchId creates a new BranchId from a transaction Id.
+func NewBranchId(transactionId transaction.Id) (branchId BranchId) {
+	copy(branchId[:], transactionId.Bytes())
+
+	return
+}
+
+func BranchIdFromBytes(bytes []byte) (result BranchId, err error, consumedBytes int) {
+	// parse the bytes
+	marshalUtil := marshalutil.New(bytes)
+	branchIdBytes, idErr := marshalUtil.ReadBytes(BranchIdLength)
+	if idErr != nil {
+		err = idErr
+
+		return
+	}
+	copy(result[:], branchIdBytes)
+	consumedBytes = marshalUtil.ReadOffset()
+
+	return
+}
+
+func BranchIdFromBase58(base58String string) (branchId BranchId, err error) {
+	// decode string
+	bytes, err := base58.Decode(base58String)
+	if err != nil {
+		return
+	}
+
+	// sanitize input
+	if len(bytes) != BranchIdLength {
+		err = fmt.Errorf("base58 encoded string does not match the length of a BranchId")
+
+		return
+	}
+
+	// copy bytes to result
+	copy(branchId[:], bytes)
+
+	return
+}
+
+func ParseBranchId(marshalUtil *marshalutil.MarshalUtil) (result BranchId, err error) {
+	var branchIdBytes []byte
+	if branchIdBytes, err = marshalUtil.ReadBytes(BranchIdLength); err != nil {
+		return
+	}
+
+	copy(result[:], branchIdBytes)
+
+	return
+}
+
+// Bytes marshals the BranchId into a sequence of bytes.
+func (branchId BranchId) Bytes() []byte {
+	return branchId[:]
+}
+
+// String creates a base58 encoded version of the BranchId.
+func (branchId BranchId) String() string {
+	return base58.Encode(branchId[:])
+}
+
+// BranchIdLength encodes the length of a branch identifier - since branches get created by transactions, it has the
+// same length as a transaction Id.
+const BranchIdLength = transaction.IdLength
diff --git a/packages/binary/valuetransfer/tangle/objectstorage.go b/packages/binary/valuetransfer/tangle/objectstorage.go
index e02ee3b34b926503949f082b654e8dc4ae5ba5b5..421fdb41c1fb1c421e4aa326dc2bc913a4e75f9b 100644
--- a/packages/binary/valuetransfer/tangle/objectstorage.go
+++ b/packages/binary/valuetransfer/tangle/objectstorage.go
@@ -4,7 +4,6 @@ import (
 	"github.com/iotaledger/hive.go/objectstorage"
 
 	"github.com/iotaledger/goshimmer/packages/binary/valuetransfer/payload"
-	"github.com/iotaledger/goshimmer/packages/binary/valuetransfer/transaction"
 )
 
 const (
@@ -43,7 +42,7 @@ func osAttachmentFactory(key []byte) (objectstorage.StorableObject, error, int)
 }
 
 func osOutputFactory(key []byte) (objectstorage.StorableObject, error, int) {
-	return transaction.OutputFromStorageKey(key)
+	return OutputFromStorageKey(key)
 }
 
 func osMissingOutputFactory(key []byte) (objectstorage.StorableObject, error, int) {
diff --git a/packages/binary/valuetransfer/transaction/output.go b/packages/binary/valuetransfer/tangle/output.go
similarity index 76%
rename from packages/binary/valuetransfer/transaction/output.go
rename to packages/binary/valuetransfer/tangle/output.go
index 78535f4f246b161c54bd13dad520ca3f9e0bad36..1696031bda99b75fb6184677f00c12f600a06fed 100644
--- a/packages/binary/valuetransfer/transaction/output.go
+++ b/packages/binary/valuetransfer/tangle/output.go
@@ -1,4 +1,4 @@
-package transaction
+package tangle
 
 import (
 	"sync"
@@ -6,33 +6,40 @@ import (
 
 	"github.com/iotaledger/hive.go/marshalutil"
 	"github.com/iotaledger/hive.go/objectstorage"
+	"github.com/iotaledger/hive.go/stringify"
 
 	"github.com/iotaledger/goshimmer/packages/binary/valuetransfer/address"
 	"github.com/iotaledger/goshimmer/packages/binary/valuetransfer/balance"
+	"github.com/iotaledger/goshimmer/packages/binary/valuetransfer/transaction"
 )
 
-var OutputKeyPartitions = objectstorage.PartitionKey([]int{address.Length, IdLength}...)
+var OutputKeyPartitions = objectstorage.PartitionKey([]int{address.Length, transaction.IdLength}...)
 
 // Output represents the output of a Transaction and contains the balances and the identifiers for this output.
 type Output struct {
 	address            address.Address
-	transactionId      Id
+	transactionId      transaction.Id
+	branchId           BranchId
 	solid              bool
 	solidificationTime time.Time
+	firstConsumer      transaction.Id
+	consumerCount      int
 	balances           []*balance.Balance
 
 	solidMutex              sync.RWMutex
 	solidificationTimeMutex sync.RWMutex
+	consumerMutex           sync.RWMutex
 
 	objectstorage.StorableObjectFlags
 	storageKey []byte
 }
 
 // NewOutput creates an Output that contains the balances and identifiers of a Transaction.
-func NewOutput(address address.Address, transactionId Id, balances []*balance.Balance) *Output {
+func NewOutput(address address.Address, transactionId transaction.Id, branchId BranchId, balances []*balance.Balance) *Output {
 	return &Output{
 		address:            address,
 		transactionId:      transactionId,
+		branchId:           branchId,
 		solid:              false,
 		solidificationTime: time.Time{},
 		balances:           balances,
@@ -93,26 +100,35 @@ func OutputFromStorageKey(keyBytes []byte, optionalTargetObject ...*Output) (res
 	if err != nil {
 		return
 	}
-	result.transactionId, err = ParseId(marshalUtil)
+	result.transactionId, err = transaction.ParseId(marshalUtil)
 	if err != nil {
 		return
 	}
-	result.storageKey = marshalutil.New(keyBytes[:OutputIdLength]).Bytes(true)
+	result.storageKey = marshalutil.New(keyBytes[:transaction.OutputIdLength]).Bytes(true)
 	consumedBytes = marshalUtil.ReadOffset()
 
 	return
 }
 
+func (output *Output) Id() transaction.OutputId {
+	return transaction.NewOutputId(output.Address(), output.TransactionId())
+}
+
 // Address returns the address that this output belongs to.
 func (output *Output) Address() address.Address {
 	return output.address
 }
 
 // TransactionId returns the id of the Transaction, that created this output.
-func (output *Output) TransactionId() Id {
+func (output *Output) TransactionId() transaction.Id {
 	return output.transactionId
 }
 
+// BranchId returns the id of the ledger state branch, that this output was booked in.
+func (output *Output) BranchId() BranchId {
+	return output.branchId
+}
+
 // Solid returns true if the output has been marked as solid.
 func (output *Output) Solid() bool {
 	output.solidMutex.RLock()
@@ -155,6 +171,20 @@ func (output *Output) SolidificationTime() time.Time {
 	return output.solidificationTime
 }
 
+func (output *Output) RegisterConsumer(consumer transaction.Id) (consumerCount int, firstConsumerId transaction.Id) {
+	output.consumerMutex.Lock()
+	defer output.consumerMutex.Unlock()
+
+	if consumerCount = output.consumerCount; consumerCount == 0 {
+		output.firstConsumer = consumer
+	}
+	output.consumerCount++
+
+	firstConsumerId = output.firstConsumer
+
+	return
+}
+
 // Balances returns the colored balances (color + balance) that this output contains.
 func (output *Output) Balances() []*balance.Balance {
 	return output.balances
@@ -171,7 +201,7 @@ func (output *Output) Bytes() []byte {
 // ObjectStorageKey returns the key that is used to store the object in the database.
 // It is required to match StorableObject interface.
 func (output *Output) ObjectStorageKey() []byte {
-	return marshalutil.New(OutputIdLength).
+	return marshalutil.New(transaction.OutputIdLength).
 		WriteBytes(output.address.Bytes()).
 		WriteBytes(output.transactionId.Bytes()).
 		Bytes()
@@ -184,7 +214,8 @@ func (output *Output) ObjectStorageValue() []byte {
 	balanceCount := len(output.balances)
 
 	// initialize helper
-	marshalUtil := marshalutil.New(marshalutil.BOOL_SIZE + marshalutil.TIME_SIZE + marshalutil.UINT32_SIZE + balanceCount*balance.Length)
+	marshalUtil := marshalutil.New(BranchIdLength + marshalutil.BOOL_SIZE + marshalutil.TIME_SIZE + marshalutil.UINT32_SIZE + balanceCount*balance.Length)
+	marshalUtil.WriteBytes(output.branchId.Bytes())
 	marshalUtil.WriteBool(output.solid)
 	marshalUtil.WriteTime(output.solidificationTime)
 	marshalUtil.WriteUint32(uint32(balanceCount))
@@ -199,6 +230,9 @@ func (output *Output) ObjectStorageValue() []byte {
 // being stored in its key rather than the content of the database to reduce storage requirements.
 func (output *Output) UnmarshalObjectStorageValue(data []byte) (err error, consumedBytes int) {
 	marshalUtil := marshalutil.New(data)
+	if output.branchId, err = ParseBranchId(marshalUtil); err != nil {
+		return
+	}
 	if output.solid, err = marshalUtil.ReadBool(); err != nil {
 		return
 	}
@@ -227,6 +261,17 @@ func (output *Output) Update(other objectstorage.StorableObject) {
 	panic("this object should never be updated")
 }
 
+func (output *Output) String() string {
+	return stringify.Struct("Output",
+		stringify.StructField("address", output.Address()),
+		stringify.StructField("transactionId", output.TransactionId()),
+		stringify.StructField("branchId", output.BranchId()),
+		stringify.StructField("solid", output.Solid()),
+		stringify.StructField("solidificationTime", output.SolidificationTime()),
+		stringify.StructField("balances", output.Balances()),
+	)
+}
+
 // define contract (ensure that the struct fulfills the given interface)
 var _ objectstorage.StorableObject = &Output{}
 
@@ -254,7 +299,7 @@ func (cachedOutput *CachedOutput) Consume(consumer func(output *Output)) (consum
 	})
 }
 
-type CachedOutputs []*CachedOutput
+type CachedOutputs map[transaction.OutputId]*CachedOutput
 
 func (cachedOutputs CachedOutputs) Consume(consumer func(output *Output)) (consumed bool) {
 	for _, cachedOutput := range cachedOutputs {
@@ -266,4 +311,10 @@ func (cachedOutputs CachedOutputs) Consume(consumer func(output *Output)) (consu
 	return
 }
 
+func (cachedOutputs CachedOutputs) Release(force ...bool) {
+	for _, cachedOutput := range cachedOutputs {
+		cachedOutput.Release(force...)
+	}
+}
+
 // endregion ///////////////////////////////////////////////////////////////////////////////////////////////////////////
diff --git a/packages/binary/valuetransfer/transaction/output_test.go b/packages/binary/valuetransfer/tangle/output_test.go
similarity index 84%
rename from packages/binary/valuetransfer/transaction/output_test.go
rename to packages/binary/valuetransfer/tangle/output_test.go
index d926bc72f4ac9aa42a5e498b25d0cecca6222de9..690668f158acc632f90ea56af2273f6e4dfa5191 100644
--- a/packages/binary/valuetransfer/transaction/output_test.go
+++ b/packages/binary/valuetransfer/tangle/output_test.go
@@ -1,4 +1,4 @@
-package transaction
+package tangle
 
 import (
 	"testing"
@@ -8,13 +8,14 @@ import (
 
 	"github.com/iotaledger/goshimmer/packages/binary/valuetransfer/address"
 	"github.com/iotaledger/goshimmer/packages/binary/valuetransfer/balance"
+	"github.com/iotaledger/goshimmer/packages/binary/valuetransfer/transaction"
 )
 
 func TestNew(t *testing.T) {
 	randomAddress := address.Random()
-	randomTransactionId := RandomId()
+	randomTransactionId := transaction.RandomId()
 
-	output := NewOutput(randomAddress, randomTransactionId, []*balance.Balance{
+	output := NewOutput(randomAddress, randomTransactionId, MasterBranchId, []*balance.Balance{
 		balance.New(balance.COLOR_IOTA, 1337),
 	})
 
diff --git a/packages/binary/valuetransfer/tangle/tangle.go b/packages/binary/valuetransfer/tangle/tangle.go
index f19490fdbc289cf388f1aba4f58b35ad9919cfd3..a1aef9b378268a9383397b412106ddbdd3e02ada 100644
--- a/packages/binary/valuetransfer/tangle/tangle.go
+++ b/packages/binary/valuetransfer/tangle/tangle.go
@@ -2,12 +2,17 @@ package tangle
 
 import (
 	"container/list"
+	"fmt"
+	"math"
+	"sort"
 	"time"
 
 	"github.com/dgraph-io/badger/v2"
 	"github.com/iotaledger/hive.go/async"
+	"github.com/iotaledger/hive.go/marshalutil"
 	"github.com/iotaledger/hive.go/objectstorage"
 	"github.com/iotaledger/hive.go/types"
+	"golang.org/x/crypto/blake2b"
 
 	"github.com/iotaledger/goshimmer/packages/binary/storageprefix"
 	"github.com/iotaledger/goshimmer/packages/binary/valuetransfer/address"
@@ -28,11 +33,13 @@ type Tangle struct {
 	outputStorage        *objectstorage.ObjectStorage
 	consumerStorage      *objectstorage.ObjectStorage
 	missingOutputStorage *objectstorage.ObjectStorage
+	branchStorage        *objectstorage.ObjectStorage
 
 	Events Events
 
 	storePayloadWorkerPool async.WorkerPool
 	solidifierWorkerPool   async.WorkerPool
+	bookerWorkerPool       async.WorkerPool
 	cleanupWorkerPool      async.WorkerPool
 }
 
@@ -48,7 +55,7 @@ func New(badgerInstance *badger.DB) (result *Tangle) {
 
 		// transaction related storage
 		attachmentStorage:    osFactory.New(osAttachment, osAttachmentFactory, objectstorage.CacheTime(time.Second)),
-		outputStorage:        osFactory.New(osOutput, osOutputFactory, transaction.OutputKeyPartitions, objectstorage.CacheTime(time.Second)),
+		outputStorage:        osFactory.New(osOutput, osOutputFactory, OutputKeyPartitions, objectstorage.CacheTime(time.Second)),
 		missingOutputStorage: osFactory.New(osMissingOutput, osMissingOutputFactory, MissingOutputKeyPartitions, objectstorage.CacheTime(time.Second)),
 		consumerStorage:      osFactory.New(osConsumer, osConsumerFactory, ConsumerPartitionKeys, objectstorage.CacheTime(time.Second)),
 
@@ -78,8 +85,8 @@ func (tangle *Tangle) GetTransactionMetadata(transactionId transaction.Id) *Cach
 	return &CachedTransactionMetadata{CachedObject: tangle.missingOutputStorage.Load(transactionId.Bytes())}
 }
 
-func (tangle *Tangle) GetTransactionOutput(outputId transaction.OutputId) *transaction.CachedOutput {
-	return &transaction.CachedOutput{CachedObject: tangle.outputStorage.Load(outputId.Bytes())}
+func (tangle *Tangle) GetTransactionOutput(outputId transaction.OutputId) *CachedOutput {
+	return &CachedOutput{CachedObject: tangle.outputStorage.Load(outputId.Bytes())}
 }
 
 // GetApprovers retrieves the approvers of a payload from the object storage.
@@ -94,7 +101,7 @@ func (tangle *Tangle) GetApprovers(payloadId payload.Id) CachedApprovers {
 	return approvers
 }
 
-// GetApprovers retrieves the approvers of a payload from the object storage.
+// GetConsumers retrieves the approvers of a payload from the object storage.
 func (tangle *Tangle) GetConsumers(outputId transaction.OutputId) CachedConsumers {
 	consumers := make(CachedConsumers, 0)
 	tangle.consumerStorage.ForEach(func(key []byte, cachedObject objectstorage.CachedObject) bool {
@@ -200,12 +207,6 @@ func (tangle *Tangle) storeTransaction(tx *transaction.Transaction) (cachedTrans
 		return result
 	})}
 
-	if transactionStored {
-		tx.Outputs().ForEach(func(address address.Address, balances []*balance.Balance) {
-			tangle.outputStorage.Store(transaction.NewOutput(address, tx.Id(), balances))
-		})
-	}
-
 	return
 }
 
@@ -232,18 +233,18 @@ func (tangle *Tangle) storeTransactionReferences(tx *transaction.Transaction) {
 	})
 }
 
-// solidifyTransactionWorker is the worker function that solidifies the payloads (recursively from past to present).
-func (tangle *Tangle) solidifyTransactionWorker(cachedPayload *payload.CachedPayload, cachedMetadata *CachedPayloadMetadata, cachedTransactionMetadata *CachedTransactionMetadata) {
-	popElementsFromStack := func(stack *list.List) (*payload.CachedPayload, *CachedPayloadMetadata, *CachedTransactionMetadata) {
-		currentSolidificationEntry := stack.Front()
-		currentCachedPayload := currentSolidificationEntry.Value.([3]interface{})[0]
-		currentCachedMetadata := currentSolidificationEntry.Value.([3]interface{})[1]
-		currentCachedTransactionMetadata := currentSolidificationEntry.Value.([3]interface{})[2]
-		stack.Remove(currentSolidificationEntry)
+func (tangle *Tangle) popElementsFromSolidificationStack(stack *list.List) (*payload.CachedPayload, *CachedPayloadMetadata, *CachedTransactionMetadata) {
+	currentSolidificationEntry := stack.Front()
+	currentCachedPayload := currentSolidificationEntry.Value.([3]interface{})[0]
+	currentCachedMetadata := currentSolidificationEntry.Value.([3]interface{})[1]
+	currentCachedTransactionMetadata := currentSolidificationEntry.Value.([3]interface{})[2]
+	stack.Remove(currentSolidificationEntry)
 
-		return currentCachedPayload.(*payload.CachedPayload), currentCachedMetadata.(*CachedPayloadMetadata), currentCachedTransactionMetadata.(*CachedTransactionMetadata)
-	}
+	return currentCachedPayload.(*payload.CachedPayload), currentCachedMetadata.(*CachedPayloadMetadata), currentCachedTransactionMetadata.(*CachedTransactionMetadata)
+}
 
+// solidifyTransactionWorker is the worker function that solidifies the payloads (recursively from past to present).
+func (tangle *Tangle) solidifyTransactionWorker(cachedPayload *payload.CachedPayload, cachedMetadata *CachedPayloadMetadata, cachedTransactionMetadata *CachedTransactionMetadata) {
 	// initialize the stack
 	solidificationStack := list.New()
 	solidificationStack.PushBack([3]interface{}{cachedPayload, cachedMetadata, cachedTransactionMetadata})
@@ -253,7 +254,7 @@ func (tangle *Tangle) solidifyTransactionWorker(cachedPayload *payload.CachedPay
 		// execute logic inside a func, so we can use defer to release the objects
 		func() {
 			// retrieve cached objects
-			currentCachedPayload, currentCachedMetadata, currentCachedTransactionMetadata := popElementsFromStack(solidificationStack)
+			currentCachedPayload, currentCachedMetadata, currentCachedTransactionMetadata := tangle.popElementsFromSolidificationStack(solidificationStack)
 			defer currentCachedPayload.Release()
 			defer currentCachedMetadata.Release()
 			defer currentCachedTransactionMetadata.Release()
@@ -262,118 +263,522 @@ func (tangle *Tangle) solidifyTransactionWorker(cachedPayload *payload.CachedPay
 			currentPayload := currentCachedPayload.Unwrap()
 			currentPayloadMetadata := currentCachedMetadata.Unwrap()
 			currentTransactionMetadata := currentCachedTransactionMetadata.Unwrap()
-			currentTransaction := currentPayload.Transaction()
 
-			// abort if any of the retrieved models is nil
-			if currentPayload == nil || currentPayloadMetadata == nil || currentTransactionMetadata == nil {
+			// abort if any of the retrieved models is nil or payload is not solid
+			if currentPayload == nil || currentPayloadMetadata == nil || currentTransactionMetadata == nil || !tangle.isPayloadSolid(currentPayload, currentPayloadMetadata) {
 				return
 			}
 
-			// abort if the entities are not solid
-			if !tangle.isPayloadSolid(currentPayload, currentPayloadMetadata) || !tangle.isTransactionSolid(currentTransaction, currentTransactionMetadata) {
+			// abort if the transaction is not solid or invalid
+			if transactionSolid, err := tangle.isTransactionSolid(currentPayload.Transaction(), currentTransactionMetadata); !transactionSolid || err != nil {
+				if err != nil {
+					// TODO: TRIGGER INVALID TX + REMOVE TXS THAT APPROVE IT
+					fmt.Println(err)
+				}
+
 				return
 			}
 
 			// abort if the payload was marked as solid already (if a payload is solid already then the tx is also solid)
-			payloadBecameSolid := currentPayloadMetadata.SetSolid(true)
-			if !payloadBecameSolid {
+			if !currentPayloadMetadata.SetSolid(true) {
 				return
 			}
 
-			// set the transaction related entities to be solid
-			transactionBecameSolid := currentTransactionMetadata.SetSolid(true)
-			if transactionBecameSolid {
-				currentTransaction.Outputs().ForEach(func(address address.Address, balances []*balance.Balance) {
-					tangle.GetTransactionOutput(transaction.NewOutputId(address, currentTransaction.Id())).Consume(func(output *transaction.Output) {
-						output.SetSolid(true)
-					})
-				})
-			}
-
 			// ... trigger solid event ...
 			tangle.Events.PayloadSolid.Trigger(currentCachedPayload, currentCachedMetadata)
 
 			// ... and schedule check of approvers
-			tangle.GetApprovers(currentPayload.Id()).Consume(func(approver *PayloadApprover) {
-				approvingPayloadId := approver.GetApprovingPayloadId()
-				approvingCachedPayload := tangle.GetPayload(approvingPayloadId)
-
-				approvingCachedPayload.Consume(func(payload *payload.Payload) {
-					solidificationStack.PushBack([3]interface{}{
-						approvingCachedPayload,
-						tangle.GetPayloadMetadata(approvingPayloadId),
-						tangle.GetTransactionMetadata(payload.Transaction().Id()),
-					})
-				})
+			tangle.ForeachApprovers(currentPayload.Id(), func(payload *payload.CachedPayload, payloadMetadata *CachedPayloadMetadata, cachedTransactionMetadata *CachedTransactionMetadata) {
+				solidificationStack.PushBack([3]interface{}{payload, payloadMetadata, cachedTransactionMetadata})
 			})
 
-			if !transactionBecameSolid {
+			// book the outputs
+			if !currentTransactionMetadata.SetSolid(true) {
 				return
 			}
 
-			tangle.Events.TransactionSolid.Trigger(currentTransaction, currentTransactionMetadata)
+			tangle.Events.TransactionSolid.Trigger(currentPayload.Transaction(), currentTransactionMetadata)
 
-			seenTransactions := make(map[transaction.Id]types.Empty)
-			currentTransaction.Outputs().ForEach(func(address address.Address, balances []*balance.Balance) {
-				tangle.GetTransactionOutput(transaction.NewOutputId(address, currentTransaction.Id())).Consume(func(output *transaction.Output) {
-					// trigger events
-				})
+			tangle.ForEachConsumers(currentPayload.Transaction(), func(payload *payload.CachedPayload, payloadMetadata *CachedPayloadMetadata, cachedTransactionMetadata *CachedTransactionMetadata) {
+				solidificationStack.PushBack([3]interface{}{payload, payloadMetadata, cachedTransactionMetadata})
+			})
 
-				tangle.GetConsumers(transaction.NewOutputId(address, currentTransaction.Id())).Consume(func(consumer *Consumer) {
-					// keep track of the processed transactions (the same transaction can consume multiple outputs)
-					if _, transactionSeen := seenTransactions[consumer.TransactionId()]; transactionSeen {
-						seenTransactions[consumer.TransactionId()] = types.Void
-
-						transactionMetadata := tangle.GetTransactionMetadata(consumer.TransactionId())
-
-						// retrieve all the payloads that attached the transaction
-						tangle.GetAttachments(consumer.TransactionId()).Consume(func(attachment *Attachment) {
-							solidificationStack.PushBack([3]interface{}{
-								tangle.GetPayload(attachment.PayloadId()),
-								tangle.GetPayloadMetadata(attachment.PayloadId()),
-								transactionMetadata,
-							})
-						})
-					}
-				})
+			payloadToBook := cachedPayload.Retain()
+			tangle.bookerWorkerPool.Submit(func() {
+				tangle.bookPayloadTransaction(payloadToBook)
 			})
 		}()
 	}
 }
 
-func (tangle *Tangle) isTransactionSolid(transaction *transaction.Transaction, metadata *TransactionMetadata) bool {
-	if transaction == nil || transaction.IsDeleted() {
-		return false
+func (tangle *Tangle) bookPayloadTransaction(cachedPayload *payload.CachedPayload) {
+	payloadToBook := cachedPayload.Unwrap()
+	defer cachedPayload.Release()
+
+	if payloadToBook == nil {
+		return
 	}
+	transactionToBook := payloadToBook.Transaction()
 
-	if metadata == nil || metadata.IsDeleted() {
-		return false
+	consumedBranches := make(map[BranchId]types.Empty)
+	conflictingConsumersToFork := make(map[transaction.Id]types.Empty)
+	createFork := false
+
+	inputsSuccessfullyProcessed := payloadToBook.Transaction().Inputs().ForEach(func(outputId transaction.OutputId) bool {
+		cachedOutput := tangle.GetTransactionOutput(outputId)
+		defer cachedOutput.Release()
+
+		// abort if the output could not be found
+		output := cachedOutput.Unwrap()
+		if output == nil {
+			return false
+		}
+
+		consumedBranches[output.BranchId()] = types.Void
+
+		// continue if we are the first consumer and there is no double spend
+		consumerCount, firstConsumerId := output.RegisterConsumer(transactionToBook.Id())
+		if consumerCount == 0 {
+			return true
+		}
+
+		// fork into a new branch
+		createFork = true
+
+		// also fork the previous consumer
+		if consumerCount == 1 {
+			conflictingConsumersToFork[firstConsumerId] = types.Void
+		}
+
+		return true
+	})
+
+	if !inputsSuccessfullyProcessed {
+		return
 	}
 
-	if metadata.Solid() {
+	transactionToBook.Outputs().ForEach(func(address address.Address, balances []*balance.Balance) bool {
+		newOutput := NewOutput(address, transactionToBook.Id(), MasterBranchId, balances)
+		newOutput.SetSolid(true)
+		tangle.outputStorage.Store(newOutput)
+
 		return true
+	})
+
+	fmt.Println(consumedBranches)
+	fmt.Println(MasterBranchId)
+	fmt.Println(createFork)
+}
+
+func (tangle *Tangle) InheritBranches(branches ...BranchId) (cachedAggregatedBranch *CachedBranch, err error) {
+	// return the MasterBranch if we have no branches in the parameters
+	if len(branches) == 0 {
+		cachedAggregatedBranch = tangle.GetBranch(MasterBranchId)
+
+		return
 	}
 
-	// iterate through all transfers and check if they are solid
-	return transaction.Inputs().ForEach(tangle.isOutputMarkedAsSolid)
+	if len(branches) == 1 {
+		cachedAggregatedBranch = tangle.GetBranch(branches[0])
+
+		return
+	}
+
+	// filter out duplicates and shared ancestor Branches (abort if we faced an error)
+	deepestCommonAncestors, err := tangle.findDeepestCommonAncestorBranches(branches...)
+	if err != nil {
+		return
+	}
+
+	// if there is only one branch that we found, then we are done
+	if len(deepestCommonAncestors) == 1 {
+		for _, firstBranchInList := range deepestCommonAncestors {
+			cachedAggregatedBranch = firstBranchInList
+		}
+
+		return
+	}
+
+	// if there is more than one parents: aggregate
+	aggregatedBranchId, aggregatedBranchParents, err := tangle.determineAggregatedBranchDetails(deepestCommonAncestors)
+	if err != nil {
+		return
+	}
+
+	newAggregatedBranchCreated := false
+	cachedAggregatedBranch = &CachedBranch{CachedObject: tangle.branchStorage.ComputeIfAbsent(aggregatedBranchId.Bytes(), func(key []byte) (object objectstorage.StorableObject) {
+		aggregatedReality := NewBranch(aggregatedBranchId, aggregatedBranchParents)
+
+		// TODO: FIX
+		/*
+			for _, parentRealityId := range aggregatedBranchParents {
+				tangle.GetBranch(parentRealityId).Consume(func(branch *Branch) {
+					branch.RegisterSubReality(aggregatedRealityId)
+				})
+			}
+		*/
+
+		aggregatedReality.SetModified()
+
+		newAggregatedBranchCreated = true
+
+		return aggregatedReality
+	})}
+
+	if !newAggregatedBranchCreated {
+		fmt.Println("1")
+		// TODO: FIX
+		/*
+			aggregatedBranch := cachedAggregatedBranch.Unwrap()
+
+			for _, realityId := range aggregatedBranchParents {
+				if aggregatedBranch.AddParentReality(realityId) {
+					tangle.GetBranch(realityId).Consume(func(branch *Branch) {
+						branch.RegisterSubReality(aggregatedRealityId)
+					})
+				}
+			}
+		*/
+	}
+
+	return
 }
 
-func (tangle *Tangle) isOutputMarkedAsSolid(transactionOutputId transaction.OutputId) (result bool) {
-	outputExists := tangle.GetTransactionOutput(transactionOutputId).Consume(func(output *transaction.Output) {
-		result = output.Solid()
+func (tangle *Tangle) determineAggregatedBranchDetails(deepestCommonAncestors CachedBranches) (aggregatedBranchId BranchId, aggregatedBranchParents []BranchId, err error) {
+	aggregatedBranchParents = make([]BranchId, len(deepestCommonAncestors))
+
+	i := 0
+	aggregatedBranchConflictParents := make(CachedBranches)
+	for branchId, cachedBranch := range deepestCommonAncestors {
+		// release all following entries if we have encountered an error
+		if err != nil {
+			cachedBranch.Release()
+
+			continue
+		}
+
+		// store BranchId as parent
+		aggregatedBranchParents[i] = branchId
+		i++
+
+		// abort if we could not unwrap the Branch (should never happen)
+		branch := cachedBranch.Unwrap()
+		if branch == nil {
+			cachedBranch.Release()
+
+			err = fmt.Errorf("failed to unwrap brach '%s'", branchId)
+
+			continue
+		}
+
+		if branch.IsAggregated() {
+			aggregatedBranchConflictParents[branchId] = cachedBranch
+
+			continue
+		}
+
+		err = tangle.collectClosestConflictAncestors(branch, aggregatedBranchConflictParents)
+
+		cachedBranch.Release()
+	}
+
+	if err != nil {
+		aggregatedBranchConflictParents.Release()
+		aggregatedBranchConflictParents = nil
+
+		return
+	}
+
+	aggregatedBranchId = tangle.generateAggregatedBranchId(aggregatedBranchConflictParents)
+
+	return
+}
+
+func (tangle *Tangle) generateAggregatedBranchId(aggregatedBranches CachedBranches) BranchId {
+	counter := 0
+	branchIds := make([]BranchId, len(aggregatedBranches))
+	for branchId, cachedBranch := range aggregatedBranches {
+		branchIds[counter] = branchId
+
+		counter++
+
+		cachedBranch.Release()
+	}
+
+	sort.Slice(branchIds, func(i, j int) bool {
+		for k := 0; k < len(branchIds[k]); k++ {
+			if branchIds[i][k] < branchIds[j][k] {
+				return true
+			} else if branchIds[i][k] > branchIds[j][k] {
+				return false
+			}
+		}
+
+		return false
 	})
 
-	if !outputExists {
-		if cachedMissingOutput, missingOutputStored := tangle.missingOutputStorage.StoreIfAbsent(NewMissingOutput(transactionOutputId)); missingOutputStored {
-			cachedMissingOutput.Consume(func(object objectstorage.StorableObject) {
-				tangle.Events.OutputMissing.Trigger(object.(*MissingOutput).Id())
-			})
+	marshalUtil := marshalutil.New(BranchIdLength * len(branchIds))
+	for _, branchId := range branchIds {
+		marshalUtil.WriteBytes(branchId.Bytes())
+	}
+
+	return blake2b.Sum256(marshalUtil.Bytes())
+}
+
+func (tangle *Tangle) collectClosestConflictAncestors(branch *Branch, closestConflictAncestors CachedBranches) (err error) {
+	// initialize stack
+	stack := list.New()
+	for _, parentRealityId := range branch.ParentBranches() {
+		stack.PushBack(parentRealityId)
+	}
+
+	// work through stack
+	processedBranches := make(map[BranchId]types.Empty)
+	for stack.Len() != 0 {
+		// iterate through the parents (in a func so we can used defer)
+		err = func() error {
+			// pop parent branch id from stack
+			firstStackElement := stack.Front()
+			defer stack.Remove(firstStackElement)
+			parentBranchId := stack.Front().Value.(BranchId)
+
+			// abort if the parent has been processed already
+			if _, branchProcessed := processedBranches[parentBranchId]; branchProcessed {
+				return nil
+			}
+			processedBranches[parentBranchId] = types.Void
+
+			// load parent branch from database
+			cachedParentBranch := tangle.GetBranch(parentBranchId)
+
+			// abort if the parent branch could not be found (should never happen)
+			parentBranch := cachedParentBranch.Unwrap()
+			if parentBranch == nil {
+				cachedParentBranch.Release()
+
+				return fmt.Errorf("failed to load branch '%s'", parentBranchId)
+			}
+
+			// if the parent Branch is not aggregated, then we have found the closest conflict ancestor
+			if !parentBranch.IsAggregated() {
+				closestConflictAncestors[parentBranchId] = cachedParentBranch
+
+				return nil
+			}
+
+			// queue parents for additional check (recursion)
+			for _, parentRealityId := range parentBranch.ParentBranches() {
+				stack.PushBack(parentRealityId)
+			}
+
+			// release the branch (we don't need it anymore)
+			cachedParentBranch.Release()
+
+			return nil
+		}()
+
+		if err != nil {
+			return
 		}
 	}
 
 	return
 }
 
+// findDeepestCommonAncestorBranches takes a number of BranchIds and determines the most specialized Branches (furthest
+// away from the MasterBranch) in that list, that contains all of the named BranchIds.
+//
+// Example: If we hand in "A, B" and B has A as its parent, then the result will contain the Branch B, because B is a
+//          child of A.
+func (tangle *Tangle) findDeepestCommonAncestorBranches(branches ...BranchId) (result CachedBranches, err error) {
+	result = make(CachedBranches)
+
+	processedBranches := make(map[BranchId]types.Empty)
+	for _, branchId := range branches {
+		err = func() error {
+			// continue, if we have processed this branch already
+			if _, exists := processedBranches[branchId]; exists {
+				return nil
+			}
+			processedBranches[branchId] = types.Void
+
+			// load branch from objectstorage
+			cachedBranch := tangle.GetBranch(branchId)
+
+			// abort if we could not load the CachedBranch
+			branch := cachedBranch.Unwrap()
+			if branch == nil {
+				cachedBranch.Release()
+
+				return fmt.Errorf("could not load branch '%s'", branchId)
+			}
+
+			// check branches position relative to already aggregated branches
+			for aggregatedBranchId, cachedAggregatedBranch := range result {
+				// abort if we can not load the branch
+				aggregatedBranch := cachedAggregatedBranch.Unwrap()
+				if aggregatedBranch == nil {
+					return fmt.Errorf("could not load branch '%s'", aggregatedBranchId)
+				}
+
+				// if the current branch is an ancestor of an already aggregated branch, then we have found the more
+				// "specialized" branch already and keep it
+				if isAncestor, ancestorErr := tangle.branchIsAncestorOfBranch(branch, aggregatedBranch); isAncestor || ancestorErr != nil {
+					return ancestorErr
+				}
+
+				// check if the aggregated Branch is an ancestor of the current Branch and abort if we face an error
+				isAncestor, ancestorErr := tangle.branchIsAncestorOfBranch(aggregatedBranch, branch)
+				if ancestorErr != nil {
+					return ancestorErr
+				}
+
+				// if the aggregated branch is an ancestor of the current branch, then we have found a more specialized
+				// Branch and replace the old one with this one.
+				if isAncestor {
+					// replace aggregated branch if we have found a more specialized on
+					delete(result, aggregatedBranchId)
+					cachedAggregatedBranch.Release()
+
+					result[branchId] = cachedBranch
+
+					return nil
+				}
+			}
+
+			// store the branch as a new aggregate candidate if it was not found to be in any relation with the already
+			// aggregated ones.
+			result[branchId] = cachedBranch
+
+			return nil
+		}()
+
+		// abort if an error occurred while processing the current branch
+		if err != nil {
+			result.Release()
+			result = nil
+
+			return
+		}
+	}
+
+	return
+}
+
+func (tangle *Tangle) branchIsAncestorOfBranch(ancestor *Branch, descendant *Branch) (isAncestor bool, err error) {
+	if ancestor.Id() == descendant.Id() {
+		return true, nil
+	}
+
+	ancestorBranches, err := tangle.getAncestorBranches(descendant)
+	if err != nil {
+		return
+	}
+
+	ancestorBranches.Consume(func(ancestorOfDescendant *Branch) {
+		if ancestorOfDescendant.Id() == ancestor.Id() {
+			isAncestor = true
+		}
+	})
+
+	return
+}
+
+func (tangle *Tangle) getAncestorBranches(branch *Branch) (ancestorBranches CachedBranches, err error) {
+	// initialize result
+	ancestorBranches = make(CachedBranches)
+
+	// initialize stack
+	stack := list.New()
+	for _, parentRealityId := range branch.ParentBranches() {
+		stack.PushBack(parentRealityId)
+	}
+
+	// work through stack
+	for stack.Len() != 0 {
+		// iterate through the parents (in a func so we can used defer)
+		err = func() error {
+			// pop parent branch id from stack
+			firstStackElement := stack.Front()
+			defer stack.Remove(firstStackElement)
+			parentBranchId := stack.Front().Value.(BranchId)
+
+			// abort if the parent has been processed already
+			if _, branchProcessed := ancestorBranches[parentBranchId]; branchProcessed {
+				return nil
+			}
+
+			// load parent branch from database
+			cachedParentBranch := tangle.GetBranch(parentBranchId)
+
+			// abort if the parent branch could not be founds (should never happen)
+			parentBranch := cachedParentBranch.Unwrap()
+			if parentBranch == nil {
+				cachedParentBranch.Release()
+
+				return fmt.Errorf("failed to unwrap branch '%s'", parentBranchId)
+			}
+
+			// store parent branch in result
+			ancestorBranches[parentBranchId] = cachedParentBranch
+
+			// queue parents for additional check (recursion)
+			for _, parentRealityId := range parentBranch.ParentBranches() {
+				stack.PushBack(parentRealityId)
+			}
+
+			return nil
+		}()
+
+		// abort if an error occurs while trying to process the parents
+		if err != nil {
+			ancestorBranches.Release()
+			ancestorBranches = nil
+
+			return
+		}
+	}
+
+	return
+}
+
+func (tangle *Tangle) GetBranch(branchId BranchId) *CachedBranch {
+	// TODO: IMPLEMENT
+	return nil
+}
+
+func (tangle *Tangle) ForeachApprovers(payloadId payload.Id, consume func(payload *payload.CachedPayload, payloadMetadata *CachedPayloadMetadata, cachedTransactionMetadata *CachedTransactionMetadata)) {
+	tangle.GetApprovers(payloadId).Consume(func(approver *PayloadApprover) {
+		approvingPayloadId := approver.GetApprovingPayloadId()
+		approvingCachedPayload := tangle.GetPayload(approvingPayloadId)
+
+		approvingCachedPayload.Consume(func(payload *payload.Payload) {
+			consume(approvingCachedPayload, tangle.GetPayloadMetadata(approvingPayloadId), tangle.GetTransactionMetadata(payload.Transaction().Id()))
+		})
+	})
+}
+
+func (tangle *Tangle) ForEachConsumers(currentTransaction *transaction.Transaction, consume func(payload *payload.CachedPayload, payloadMetadata *CachedPayloadMetadata, cachedTransactionMetadata *CachedTransactionMetadata)) {
+	seenTransactions := make(map[transaction.Id]types.Empty)
+	currentTransaction.Outputs().ForEach(func(address address.Address, balances []*balance.Balance) bool {
+		tangle.GetConsumers(transaction.NewOutputId(address, currentTransaction.Id())).Consume(func(consumer *Consumer) {
+			// keep track of the processed transactions (the same transaction can consume multiple outputs)
+			if _, transactionSeen := seenTransactions[consumer.TransactionId()]; transactionSeen {
+				seenTransactions[consumer.TransactionId()] = types.Void
+
+				transactionMetadata := tangle.GetTransactionMetadata(consumer.TransactionId())
+
+				// retrieve all the payloads that attached the transaction
+				tangle.GetAttachments(consumer.TransactionId()).Consume(func(attachment *Attachment) {
+					consume(tangle.GetPayload(attachment.PayloadId()), tangle.GetPayloadMetadata(attachment.PayloadId()), transactionMetadata)
+				})
+			}
+		})
+
+		return true
+	})
+}
+
 // 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 {
@@ -420,3 +825,160 @@ func (tangle *Tangle) isPayloadMarkedAsSolid(payloadId payload.Id) bool {
 
 	return true
 }
+
+func (tangle *Tangle) 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 := tangle.getCachedOutputsFromTransactionInputs(tx)
+	defer cachedInputs.Release()
+
+	// check the solidity of the inputs and retrieve the consumed balances
+	inputsSolid, consumedBalances, err := tangle.checkTransactionInputs(cachedInputs)
+
+	// abort if an error occurred or the inputs are not solid, yet
+	if !inputsSolid || err != nil {
+		return false, err
+	}
+
+	if !tangle.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 (tangle *Tangle) getCachedOutputsFromTransactionInputs(tx *transaction.Transaction) (result CachedOutputs) {
+	result = make(CachedOutputs)
+	tx.Inputs().ForEach(func(inputId transaction.OutputId) bool {
+		result[inputId] = tangle.GetTransactionOutput(inputId)
+
+		return true
+	})
+
+	return
+}
+
+func (tangle *Tangle) checkTransactionInputs(cachedInputs CachedOutputs) (inputsSolid bool, consumedBalances map[balance.Color]int64, err error) {
+	inputsSolid = true
+	consumedBalances = make(map[balance.Color]int64)
+
+	for inputId, cachedInput := range cachedInputs {
+		if !cachedInput.Exists() {
+			inputsSolid = false
+
+			if cachedMissingOutput, missingOutputStored := tangle.missingOutputStorage.StoreIfAbsent(NewMissingOutput(inputId)); missingOutputStored {
+				cachedMissingOutput.Consume(func(object objectstorage.StorableObject) {
+					tangle.Events.OutputMissing.Trigger(object.(*MissingOutput).Id())
+				})
+			}
+
+			continue
+		}
+
+		// should never be nil as we check Exists() before
+		input := cachedInput.Unwrap()
+
+		// update solid status
+		inputsSolid = inputsSolid && input.Solid()
+
+		// 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 {
+					err = fmt.Errorf("buffer overflow in balances of inputs")
+
+					return
+				}
+
+				newBalance = currentBalance + inputBalance.Value()
+			} else {
+				newBalance = inputBalance.Value()
+			}
+			consumedBalances[inputBalance.Color()] = newBalance
+		}
+	}
+
+	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
+
+	// 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.COLOR_NEW {
+				// catch overflows
+				if newlyColoredCoins > math.MaxInt64-outputBalance.Value() {
+					return false
+				}
+
+				newlyColoredCoins += 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 transaction
+			inputBalances[outputBalance.Color()] -= outputBalance.Value()
+
+			// cleanup the entry in the supply map if we have exhausted all 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 outputs
+	return unspentCoins == newlyColoredCoins
+}
diff --git a/packages/binary/valuetransfer/tangle/tangle_test.go b/packages/binary/valuetransfer/tangle/tangle_test.go
index 2b21059b6437736a6dca02037aef47004ebf9aa7..9980c5bedbefceef1bb6f2111c1fcd152c6f1b66 100644
--- a/packages/binary/valuetransfer/tangle/tangle_test.go
+++ b/packages/binary/valuetransfer/tangle/tangle_test.go
@@ -1,13 +1,12 @@
 package tangle
 
 import (
-	"fmt"
 	"io/ioutil"
 	"os"
 	"testing"
+	"time"
 
 	"github.com/iotaledger/hive.go/crypto/ed25519"
-	"github.com/iotaledger/hive.go/events"
 	"github.com/stretchr/testify/assert"
 	"github.com/stretchr/testify/require"
 
@@ -52,31 +51,47 @@ func TestTangle_AttachPayload(t *testing.T) {
 		return
 	}
 
-	tangle.Events.PayloadSolid.Attach(events.NewClosure(func(payload *payload.CachedPayload, metadata *CachedPayloadMetadata) {
-		fmt.Println(payload.Unwrap())
-
-		payload.Release()
-		metadata.Release()
-	}))
-
 	addressKeyPair1 := ed25519.GenerateKeyPair()
 	addressKeyPair2 := ed25519.GenerateKeyPair()
 
 	transferId1, _ := transaction.IdFromBase58("8opHzTAnfzRpPEx21XtnrVTX28YQuCpAjcn1PczScKh")
 	transferId2, _ := transaction.IdFromBase58("4uQeVj5tqViQh7yWWGStvkEG1Zmhx6uasJtWCJziofM")
 
-	tangle.AttachPayload(payload.New(payload.GenesisId, payload.GenesisId, transaction.New(
+	input1 := NewOutput(address.FromED25519PubKey(addressKeyPair1.PublicKey), transferId1, MasterBranchId, []*balance.Balance{
+		balance.New(balance.COLOR_IOTA, 337),
+	})
+	input1.SetSolid(true)
+	input2 := NewOutput(address.FromED25519PubKey(addressKeyPair2.PublicKey), transferId2, MasterBranchId, []*balance.Balance{
+		balance.New(balance.COLOR_IOTA, 1000),
+	})
+	input2.SetSolid(true)
+
+	tangle.outputStorage.Store(input1)
+	tangle.outputStorage.Store(input2)
+
+	outputAddress := address.Random()
+
+	tx := transaction.New(
 		transaction.NewInputs(
-			transaction.NewOutputId(address.FromED25519PubKey(addressKeyPair1.PublicKey), transferId1),
-			transaction.NewOutputId(address.FromED25519PubKey(addressKeyPair2.PublicKey), transferId2),
+			input1.Id(),
+			input2.Id(),
 		),
 
 		transaction.NewOutputs(map[address.Address][]*balance.Balance{
-			address.Random(): {
-				balance.New(balance.COLOR_IOTA, 1337),
+			outputAddress: {
+				balance.New(balance.COLOR_NEW, 1337),
 			},
 		}),
-	)))
+	)
+
+	tangle.AttachPayload(payload.New(payload.GenesisId, payload.GenesisId, tx))
+
+	time.Sleep(1 * time.Second)
+
+	outputFound := tangle.GetTransactionOutput(transaction.NewOutputId(outputAddress, tx.Id())).Consume(func(output *Output) {
+		assert.Equal(t, true, output.Solid())
+	})
+	assert.Equal(t, true, outputFound)
 
 	tangle.Shutdown()
 }
diff --git a/packages/binary/valuetransfer/transaction/outputs.go b/packages/binary/valuetransfer/transaction/outputs.go
index daf995bbea3799e65868699dad447f0f79ec9fd0..8e1cea9830f29922031b296a454600eb7b3a676e 100644
--- a/packages/binary/valuetransfer/transaction/outputs.go
+++ b/packages/binary/valuetransfer/transaction/outputs.go
@@ -90,11 +90,9 @@ func (outputs *Outputs) Add(address address.Address, balances []*balance.Balance
 	return outputs
 }
 
-func (outputs *Outputs) ForEach(consumer func(address address.Address, balances []*balance.Balance)) {
-	outputs.OrderedMap.ForEach(func(key, value interface{}) bool {
-		consumer(key.(address.Address), value.([]*balance.Balance))
-
-		return true
+func (outputs *Outputs) ForEach(consumer func(address address.Address, balances []*balance.Balance) bool) bool {
+	return outputs.OrderedMap.ForEach(func(key, value interface{}) bool {
+		return consumer(key.(address.Address), value.([]*balance.Balance))
 	})
 }
 
@@ -108,13 +106,15 @@ func (outputs *Outputs) Bytes() []byte {
 	}
 
 	marshalUtil.WriteUint32(uint32(outputs.Size()))
-	outputs.ForEach(func(address address.Address, balances []*balance.Balance) {
+	outputs.ForEach(func(address address.Address, balances []*balance.Balance) bool {
 		marshalUtil.WriteBytes(address.Bytes())
 		marshalUtil.WriteUint32(uint32(len(balances)))
 
 		for _, balance := range balances {
 			marshalUtil.WriteBytes(balance.Bytes())
 		}
+
+		return true
 	})
 
 	return marshalUtil.Bytes()
@@ -127,7 +127,7 @@ func (outputs *Outputs) String() string {
 
 	result := "[\n"
 	empty := true
-	outputs.ForEach(func(address address.Address, balances []*balance.Balance) {
+	outputs.ForEach(func(address address.Address, balances []*balance.Balance) bool {
 		empty = false
 
 		result += "    " + address.String() + ": [\n"
@@ -144,6 +144,8 @@ func (outputs *Outputs) String() string {
 		}
 
 		result += "    ]\n"
+
+		return true
 	})
 
 	if empty {