diff --git a/go.mod b/go.mod
index 9c32ac67638309de873941c5581050b082cce044..8b68a9a7cf9eb1382ed9c028e6ab22776996ffcf 100644
--- a/go.mod
+++ b/go.mod
@@ -15,7 +15,7 @@ require (
 	github.com/golang/protobuf v1.3.2 // indirect
 	github.com/google/open-location-code/go v0.0.0-20190903173953-119bc96a3a51
 	github.com/gorilla/websocket v1.4.1
-	github.com/iotaledger/hive.go v0.0.0-20200106003337-d78637b86bf2
+	github.com/iotaledger/hive.go v0.0.0-20200107105553-d35f44074aa2
 	github.com/iotaledger/iota.go v1.0.0-beta.9
 	github.com/kr/text v0.1.0
 	github.com/labstack/echo v3.3.10+incompatible
diff --git a/go.sum b/go.sum
index 88cdee3fd97107a92c0efec2feb589b3c9eb8b83..867562cddd4cdf3925b5dc7a1ee90f9c242231fe 100644
--- a/go.sum
+++ b/go.sum
@@ -105,6 +105,8 @@ github.com/iotaledger/hive.go v0.0.0-20200105155739-b36b4405762f h1:HqEFrjhbUB4u
 github.com/iotaledger/hive.go v0.0.0-20200105155739-b36b4405762f/go.mod h1:vrZrvGaWT1o5kz3Jj2B/PcUtqsFzZnLWrO3zEsGSuwk=
 github.com/iotaledger/hive.go v0.0.0-20200106003337-d78637b86bf2 h1:HBQKEP6PnaEhG7j3HNjZTkpALvek8QYPeoJXyDzBDpo=
 github.com/iotaledger/hive.go v0.0.0-20200106003337-d78637b86bf2/go.mod h1:vrZrvGaWT1o5kz3Jj2B/PcUtqsFzZnLWrO3zEsGSuwk=
+github.com/iotaledger/hive.go v0.0.0-20200107105553-d35f44074aa2 h1:o4jOpCuNXSRhbiSJHPR2SuN/qGkKt/LpX+DIQ0rUMSY=
+github.com/iotaledger/hive.go v0.0.0-20200107105553-d35f44074aa2/go.mod h1:vrZrvGaWT1o5kz3Jj2B/PcUtqsFzZnLWrO3zEsGSuwk=
 github.com/iotaledger/iota.go v1.0.0-beta.9 h1:c654s9pkdhMBkABUvWg+6k91MEBbdtmZXP1xDfQpajg=
 github.com/iotaledger/iota.go v1.0.0-beta.9/go.mod h1:F6WBmYd98mVjAmmPVYhnxg8NNIWCjjH8VWT9qvv3Rc8=
 github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
diff --git a/packages/binary/tangle/approvers/approvers.go b/packages/binary/tangle/approvers/approvers.go
index adf7af3a099ea1dd50714f2719fe24bf5fc46c49..90014e143ddb071753f2cf9aadb42ffe33166b56 100644
--- a/packages/binary/tangle/approvers/approvers.go
+++ b/packages/binary/tangle/approvers/approvers.go
@@ -1,8 +1,11 @@
 package approvers
 
 import (
+	"encoding/binary"
 	"sync"
 
+	"github.com/iotaledger/goshimmer/packages/binary/types"
+
 	"github.com/iotaledger/goshimmer/packages/binary/transaction"
 	"github.com/iotaledger/hive.go/objectstorage"
 )
@@ -11,14 +14,14 @@ type Approvers struct {
 	objectstorage.StorableObjectFlags
 
 	transactionId  transaction.Id
-	approvers      map[transaction.Id]empty
+	approvers      map[transaction.Id]types.Empty
 	approversMutex sync.RWMutex
 }
 
 func New(transactionId transaction.Id) *Approvers {
 	return &Approvers{
 		transactionId: transactionId,
-		approvers:     make(map[transaction.Id]empty),
+		approvers:     make(map[transaction.Id]types.Empty),
 	}
 }
 
@@ -39,11 +42,11 @@ func (approvers *Approvers) GetTransactionId() transaction.Id {
 	return approvers.transactionId
 }
 
-func (approvers *Approvers) Get() (result map[transaction.Id]empty) {
+func (approvers *Approvers) Get() (result map[transaction.Id]types.Empty) {
 	approvers.approversMutex.RLock()
-	result = make(map[transaction.Id]empty, len(approvers.approvers))
+	result = make(map[transaction.Id]types.Empty, len(approvers.approvers))
 	for approverId := range approvers.approvers {
-		result[approverId] = void
+		result[approverId] = types.Void
 	}
 	approvers.approversMutex.RUnlock()
 
@@ -57,7 +60,7 @@ func (approvers *Approvers) Add(transactionId transaction.Id) (modified bool) {
 
 		approvers.approversMutex.Lock()
 		if _, exists := approvers.approvers[transactionId]; !exists {
-			approvers.approvers[transactionId] = void
+			approvers.approvers[transactionId] = types.Void
 
 			modified = true
 
@@ -111,11 +114,50 @@ func (approvers *Approvers) Update(other objectstorage.StorableObject) {
 }
 
 func (approvers *Approvers) MarshalBinary() (result []byte, err error) {
+	approvers.approversMutex.RLock()
+
+	approversCount := len(approvers.approvers)
+	result = make([]byte, 4+approversCount*transaction.IdLength)
+	offset := 0
+
+	binary.LittleEndian.PutUint32(result[offset:], uint32(approversCount))
+	offset += 4
+
+	for approverId := range approvers.approvers {
+		marshaledBytes, marshalErr := approverId.MarshalBinary()
+		if marshalErr != nil {
+			err = marshalErr
+
+			approvers.approversMutex.RUnlock()
+
+			return
+		}
+
+		copy(result[offset:], marshaledBytes)
+		offset += len(marshaledBytes)
+	}
+
+	approvers.approversMutex.RUnlock()
+
 	return
 }
 
 func (approvers *Approvers) UnmarshalBinary(data []byte) (err error) {
-	approvers.approvers = make(map[transaction.Id]empty)
+	approvers.approvers = make(map[transaction.Id]types.Empty)
+	offset := 0
+
+	approversCount := int(binary.LittleEndian.Uint32(data[offset:]))
+	offset += 4
+
+	for i := 0; i < approversCount; i++ {
+		var approverId transaction.Id
+		if err = approverId.UnmarshalBinary(data[offset:]); err != nil {
+			return
+		}
+		offset += transaction.IdLength
+
+		approvers.approvers[approverId] = types.Void
+	}
 
 	return
 }
diff --git a/packages/binary/tangle/approvers/types.go b/packages/binary/tangle/approvers/types.go
deleted file mode 100644
index 99c78a42ede49d7a627262262dd0fc16a16154f6..0000000000000000000000000000000000000000
--- a/packages/binary/tangle/approvers/types.go
+++ /dev/null
@@ -1,5 +0,0 @@
-package approvers
-
-type empty struct{}
-
-var void empty
diff --git a/packages/binary/tangle/events.go b/packages/binary/tangle/events.go
index 91c7013b0fee1136b8073708cfe1fea27399e4f4..23df13656ff31bbaf9d7561bd6d10c912558c4cd 100644
--- a/packages/binary/tangle/events.go
+++ b/packages/binary/tangle/events.go
@@ -10,7 +10,7 @@ type Events struct {
 	TransactionAttached        *events.Event
 	TransactionSolid           *events.Event
 	TransactionMissing         *events.Event
-	MissingTransactionAttached *events.Event
+	MissingTransactionReceived *events.Event
 	TransactionRemoved         *events.Event
 }
 
@@ -19,7 +19,7 @@ func newEvents() *Events {
 		TransactionAttached:        events.NewEvent(cachedTransactionEvent),
 		TransactionSolid:           events.NewEvent(cachedTransactionEvent),
 		TransactionMissing:         events.NewEvent(transactionIdEvent),
-		MissingTransactionAttached: events.NewEvent(transactionIdEvent),
+		MissingTransactionReceived: events.NewEvent(transactionIdEvent),
 		TransactionRemoved:         events.NewEvent(transactionIdEvent),
 	}
 }
diff --git a/packages/binary/tangle/tangle.go b/packages/binary/tangle/tangle.go
index ecf60caa9fa673b055a05590685532b920115400..7827f7e1764a290f7899513c056d6671f8cdc910 100644
--- a/packages/binary/tangle/tangle.go
+++ b/packages/binary/tangle/tangle.go
@@ -19,6 +19,8 @@ const (
 )
 
 type Tangle struct {
+	storageId []byte
+
 	transactionStorage         *objectstorage.ObjectStorage
 	transactionMetadataStorage *objectstorage.ObjectStorage
 	approversStorage           *objectstorage.ObjectStorage
@@ -34,6 +36,7 @@ type Tangle struct {
 // Constructor for the tangle.
 func New(storageId []byte) (result *Tangle) {
 	result = &Tangle{
+		storageId:                  storageId,
 		transactionStorage:         objectstorage.New(append(storageId, storageprefix.TangleTransaction...), transaction.FromStorage),
 		transactionMetadataStorage: objectstorage.New(append(storageId, storageprefix.TangleTransactionMetadata...), transactionmetadata.FromStorage),
 		approversStorage:           objectstorage.New(append(storageId, storageprefix.TangleApprovers...), approvers.FromStorage),
@@ -47,16 +50,29 @@ func New(storageId []byte) (result *Tangle) {
 	return
 }
 
+// Returns the storage id of this tangle (can be used to create ontologies that follow the storage of the main tangle).
+func (tangle *Tangle) GetStorageId() []byte {
+	return tangle.storageId
+}
+
 // Attaches a new transaction to the tangle.
 func (tangle *Tangle) AttachTransaction(transaction *transaction.Transaction) {
 	tangle.storeTransactionsWorkerPool.Submit(func() { tangle.storeTransactionWorker(transaction) })
 }
 
-// Triggers the solidification of a given transaction.
-func (tangle *Tangle) SolidifyTransaction(transactionId transaction.Id) {
-	tangle.solidifierWorkerPool.Submit(func() {
-		tangle.solidifyTransactionWorker(tangle.GetTransaction(transactionId), tangle.GetTransactionMetadata(transactionId))
-	})
+// Retrieves a transaction from the tangle.
+func (tangle *Tangle) GetTransaction(transactionId transaction.Id) *transaction.CachedTransaction {
+	return &transaction.CachedTransaction{CachedObject: tangle.transactionStorage.Load(transactionId[:])}
+}
+
+// Retrieves the metadata of a transaction from the tangle.
+func (tangle *Tangle) GetTransactionMetadata(transactionId transaction.Id) *transactionmetadata.CachedTransactionMetadata {
+	return &transactionmetadata.CachedTransactionMetadata{CachedObject: tangle.transactionMetadataStorage.Load(transactionId[:])}
+}
+
+// Retrieves the approvers of a transaction from the tangle.
+func (tangle *Tangle) GetApprovers(transactionId transaction.Id) *approvers.CachedApprovers {
+	return &approvers.CachedApprovers{CachedObject: tangle.approversStorage.Load(transactionId[:])}
 }
 
 // Deletes a transaction from the tangle (i.e. for local snapshots)
@@ -84,21 +100,6 @@ func (tangle *Tangle) DeleteTransaction(transactionId transaction.Id) {
 	tangle.Events.TransactionRemoved.Trigger(transactionId)
 }
 
-// Retrieves a transaction from the tangle.
-func (tangle *Tangle) GetTransaction(transactionId transaction.Id) *transaction.CachedTransaction {
-	return &transaction.CachedTransaction{CachedObject: tangle.transactionStorage.Load(transactionId[:])}
-}
-
-// Retrieves the metadata of a transaction from the tangle.
-func (tangle *Tangle) GetTransactionMetadata(transactionId transaction.Id) *transactionmetadata.CachedTransactionMetadata {
-	return &transactionmetadata.CachedTransactionMetadata{CachedObject: tangle.transactionMetadataStorage.Load(transactionId[:])}
-}
-
-// Retrieves the approvers of a transaction from the tangle.
-func (tangle *Tangle) GetApprovers(transactionId transaction.Id) *approvers.CachedApprovers {
-	return &approvers.CachedApprovers{CachedObject: tangle.approversStorage.Load(transactionId[:])}
-}
-
 // Marks the tangle as stopped, so it will not accept any new transactions (waits for all backgroundTasks to finish.
 func (tangle *Tangle) Shutdown() *Tangle {
 	tangle.storeTransactionsWorkerPool.ShutdownGracefully()
@@ -161,7 +162,7 @@ func (tangle *Tangle) storeTransactionWorker(tx *transaction.Transaction) {
 	addTransactionToApprovers(transactionId, tx.GetBranchTransactionId())
 
 	if tangle.missingTransactionsStorage.DeleteIfPresent(transactionId[:]) {
-		tangle.Events.MissingTransactionAttached.Trigger(transactionId)
+		tangle.Events.MissingTransactionReceived.Trigger(transactionId)
 	}
 
 	tangle.Events.TransactionAttached.Trigger(cachedTransaction, cachedTransactionMetadata)
diff --git a/packages/binary/transaction/id.go b/packages/binary/transaction/id.go
index c940bf23ebd2e58e1175731b401b5a0749b1cdca..1f39029e5305d6f0c2e6f83fd39dae2d16f95312 100644
--- a/packages/binary/transaction/id.go
+++ b/packages/binary/transaction/id.go
@@ -2,6 +2,19 @@ package transaction
 
 type Id [IdLength]byte
 
+func (id *Id) MarshalBinary() (result []byte, err error) {
+	result = make([]byte, IdLength)
+	copy(result, id[:])
+
+	return
+}
+
+func (id *Id) UnmarshalBinary(data []byte) (err error) {
+	copy(id[:], data)
+
+	return
+}
+
 var EmptyId = Id{}
 
 const IdLength = 64
diff --git a/packages/binary/transaction/payload/valuetransfer/valuetransfer.go b/packages/binary/transaction/payload/valuetransfer/valuetransfer.go
index 6f369dc8883edb5259d6078de9192f5801c822a0..411b2f156dccc6841a581b88e22e172c4b400229 100644
--- a/packages/binary/transaction/payload/valuetransfer/valuetransfer.go
+++ b/packages/binary/transaction/payload/valuetransfer/valuetransfer.go
@@ -43,18 +43,26 @@ func (valueTransfer *ValueTransfer) GetType() payload.Type {
 	return Type
 }
 
-func (valueTransfer *ValueTransfer) AddInput(transferHash transfer.Hash, address address.Address) *ValueTransfer {
+func (valueTransfer *ValueTransfer) AddInput(transferId transfer.Id, address address.Address) *ValueTransfer {
 	if valueTransfer.isFinalized() {
 		panic("you can not add inputs after you have started finalizing (sign / marshal) the ValueTransfer")
 	}
 
 	valueTransfer.inputsMutex.Lock()
-	valueTransfer.inputs = append(valueTransfer.inputs, transfer.NewOutputReference(transferHash, address))
+	valueTransfer.inputs = append(valueTransfer.inputs, transfer.NewOutputReference(transferId, address))
 	valueTransfer.inputsMutex.Unlock()
 
 	return valueTransfer
 }
 
+func (valueTransfer *ValueTransfer) GetInputs() (result []*transfer.OutputReference) {
+	valueTransfer.inputsMutex.RLock()
+	result = valueTransfer.inputs
+	valueTransfer.inputsMutex.RUnlock()
+
+	return
+}
+
 func (valueTransfer *ValueTransfer) AddOutput(address address.Address, balance *coloredcoins.ColoredBalance) *ValueTransfer {
 	if valueTransfer.isFinalized() {
 		panic("you can not add outputs after you have started finalizing (sign / marshal) the ValueTransfer")
@@ -180,7 +188,7 @@ func (valueTransfer *ValueTransfer) marshalPayloadBytes() (result []byte) {
 
 func (valueTransfer *ValueTransfer) marshalInputs() (result []byte) {
 	inputCount := len(valueTransfer.inputs)
-	marshaledTransferOutputReferenceLength := transfer.HashLength + address.Length
+	marshaledTransferOutputReferenceLength := transfer.IdLength + address.Length
 
 	result = make([]byte, 4+inputCount*marshaledTransferOutputReferenceLength)
 	offset := 0
@@ -281,7 +289,7 @@ func (valueTransfer *ValueTransfer) unmarshalInputs(bytes []byte, offset *int) (
 	*offset += 4
 
 	valueTransfer.inputs = make([]*transfer.OutputReference, inputCount)
-	marshaledTransferOutputReferenceLength := transfer.HashLength + address.Length
+	marshaledTransferOutputReferenceLength := transfer.IdLength + address.Length
 	for i := 0; i < inputCount; i++ {
 		var transferOutputReference transfer.OutputReference
 		if err = transferOutputReference.UnmarshalBinary(bytes[*offset:]); err != nil {
diff --git a/packages/binary/transaction/test/transaction_test.go b/packages/binary/transaction/test/transaction_test.go
index 15a4be905482fb370a11b1f75f134e0cd4095f41..d94c14b09388d51d11345a435c1a3ad85c82826d 100644
--- a/packages/binary/transaction/test/transaction_test.go
+++ b/packages/binary/transaction/test/transaction_test.go
@@ -6,9 +6,8 @@ import (
 	"sync"
 	"testing"
 
-	"github.com/iotaledger/goshimmer/packages/binary/async"
-
 	"github.com/iotaledger/goshimmer/packages/binary/signature/ed25119"
+	"github.com/iotaledger/hive.go/async"
 
 	"github.com/iotaledger/goshimmer/packages/ledgerstate/coloredcoins"
 
@@ -66,7 +65,7 @@ func BenchmarkVerifyValueTransactions(b *testing.B) {
 	transactions := make([][]byte, b.N)
 	for i := 0; i < b.N; i++ {
 		tx := transaction.New(transaction.EmptyId, transaction.EmptyId, identity.Generate(), valuetransfer.New().
-			AddInput(transfer.NewHash("test"), address.FromPublicKey(keyPairOfSourceAddress.PublicKey)).
+			AddInput(transfer.NewId([]byte("test")), address.FromPublicKey(keyPairOfSourceAddress.PublicKey)).
 			AddOutput(address.FromPublicKey(keyPairOfTargetAddress.PublicKey), coloredcoins.NewColoredBalance(coloredcoins.NewColor("IOTA"), 12)).
 			Sign(keyPairOfSourceAddress))
 
@@ -134,7 +133,7 @@ func TestNew(t *testing.T) {
 
 	newValueTransaction1 := transaction.New(transaction.EmptyId, transaction.EmptyId, identity.Generate(),
 		valuetransfer.New().
-			AddInput(transfer.NewHash("test"), address.FromPublicKey(keyPairOfSourceAddress.PublicKey)).
+			AddInput(transfer.NewId("test"), address.FromPublicKey(keyPairOfSourceAddress.PublicKey)).
 			AddOutput(address.FromPublicKey(keyPairOfTargetAddress.PublicKey), coloredcoins.NewColoredBalance(coloredcoins.NewColor("IOTA"), 12)).
 			Sign(keyPairOfSourceAddress),
 	)
diff --git a/packages/binary/valuetangle/events.go b/packages/binary/valuetangle/events.go
new file mode 100644
index 0000000000000000000000000000000000000000..2c0352c1a74afe58d8688220e644e4886a70c8a4
--- /dev/null
+++ b/packages/binary/valuetangle/events.go
@@ -0,0 +1,13 @@
+package valuetangle
+
+import (
+	"github.com/iotaledger/hive.go/events"
+)
+
+type Events struct {
+	TransferAttached        *events.Event
+	TransferSolid           *events.Event
+	TransferMissing         *events.Event
+	MissingTransferReceived *events.Event
+	TransferRemoved         *events.Event
+}
diff --git a/packages/binary/valuetangle/model/cached_missing_transfer.go b/packages/binary/valuetangle/model/cached_missing_transfer.go
new file mode 100644
index 0000000000000000000000000000000000000000..6e885c2bf0fccfcf7432644c1b8b796ca4599318
--- /dev/null
+++ b/packages/binary/valuetangle/model/cached_missing_transfer.go
@@ -0,0 +1,21 @@
+package model
+
+import (
+	"github.com/iotaledger/hive.go/objectstorage"
+)
+
+type CachedMissingTransfer struct {
+	*objectstorage.CachedObject
+}
+
+func (cachedObject *CachedMissingTransfer) Unwrap() *MissingTransfer {
+	if untypedObject := cachedObject.Get(); untypedObject == nil {
+		return nil
+	} else {
+		if typedObject := untypedObject.(*MissingTransfer); typedObject == nil || typedObject.IsDeleted() {
+			return nil
+		} else {
+			return typedObject
+		}
+	}
+}
diff --git a/packages/binary/valuetangle/model/cached_valuetransfer.go b/packages/binary/valuetangle/model/cached_valuetransfer.go
new file mode 100644
index 0000000000000000000000000000000000000000..0d8fdeba193911223f6f0a5905fbe57d6e7e6390
--- /dev/null
+++ b/packages/binary/valuetangle/model/cached_valuetransfer.go
@@ -0,0 +1,25 @@
+package model
+
+import (
+	"github.com/iotaledger/goshimmer/packages/binary/transaction"
+	"github.com/iotaledger/goshimmer/packages/binary/transaction/payload/valuetransfer"
+	"github.com/iotaledger/goshimmer/packages/ledgerstate/transfer"
+)
+
+type CachedValueTransfer struct {
+	*transaction.CachedTransaction
+}
+
+func (cachedValueTransfer *CachedValueTransfer) Unwrap() (*valuetransfer.ValueTransfer, transfer.Id) {
+	if untypedTransaction := cachedValueTransfer.Get(); untypedTransaction == nil {
+		return nil, transfer.EmptyId
+	} else {
+		if typeCastedTransaction := untypedTransaction.(*transaction.Transaction); typeCastedTransaction == nil || typeCastedTransaction.IsDeleted() || typeCastedTransaction.GetPayload().GetType() != valuetransfer.Type {
+			return nil, transfer.EmptyId
+		} else {
+			transactionId := typeCastedTransaction.GetId()
+
+			return typeCastedTransaction.GetPayload().(*valuetransfer.ValueTransfer), transfer.NewId(transactionId[:])
+		}
+	}
+}
diff --git a/packages/binary/valuetangle/model/consumers.go b/packages/binary/valuetangle/model/consumers.go
new file mode 100644
index 0000000000000000000000000000000000000000..3f8edf84f405da27fd263f424dedfa71c3a93d33
--- /dev/null
+++ b/packages/binary/valuetangle/model/consumers.go
@@ -0,0 +1,162 @@
+package model
+
+import (
+	"encoding/binary"
+	"sync"
+
+	"github.com/iotaledger/goshimmer/packages/binary/types"
+	"github.com/iotaledger/goshimmer/packages/ledgerstate/transfer"
+	"github.com/iotaledger/hive.go/objectstorage"
+)
+
+type Consumers struct {
+	objectstorage.StorableObjectFlags
+
+	transferId     transfer.Id
+	consumers      map[transfer.Id]types.Empty
+	consumersMutex sync.RWMutex
+}
+
+func NewConsumers(transferId transfer.Id) *Consumers {
+	return &Consumers{
+		transferId: transferId,
+		consumers:  make(map[transfer.Id]types.Empty),
+	}
+}
+
+// Get's called when we restore the approvers from storage. The bytes and the content will be unmarshaled by an external
+// caller (the objectStorage factory).
+func FromStorage(id []byte) (result objectstorage.StorableObject) {
+	var transferId transfer.Id
+	copy(transferId[:], id)
+
+	result = &Consumers{
+		transferId: transferId,
+	}
+
+	return
+}
+
+func (consumers *Consumers) GetTransferId() transfer.Id {
+	return consumers.transferId
+}
+
+func (consumers *Consumers) Get() (result map[transfer.Id]types.Empty) {
+	consumers.consumersMutex.RLock()
+	result = make(map[transfer.Id]types.Empty, len(consumers.consumers))
+	for approverId := range consumers.consumers {
+		result[approverId] = types.Void
+	}
+	consumers.consumersMutex.RUnlock()
+
+	return
+}
+
+func (consumers *Consumers) Add(transferId transfer.Id) (modified bool) {
+	consumers.consumersMutex.RLock()
+	if _, exists := consumers.consumers[transferId]; !exists {
+		consumers.consumersMutex.RUnlock()
+
+		consumers.consumersMutex.Lock()
+		if _, exists := consumers.consumers[transferId]; !exists {
+			consumers.consumers[transferId] = types.Void
+
+			modified = true
+
+			consumers.SetModified()
+		}
+		consumers.consumersMutex.Unlock()
+	} else {
+		consumers.consumersMutex.RUnlock()
+	}
+
+	return
+}
+
+func (consumers *Consumers) Remove(transferId transfer.Id) (modified bool) {
+	consumers.consumersMutex.RLock()
+	if _, exists := consumers.consumers[transferId]; exists {
+		consumers.consumersMutex.RUnlock()
+
+		consumers.consumersMutex.Lock()
+		if _, exists := consumers.consumers[transferId]; exists {
+			delete(consumers.consumers, transferId)
+
+			modified = true
+
+			consumers.SetModified()
+		}
+		consumers.consumersMutex.Unlock()
+	} else {
+		consumers.consumersMutex.RUnlock()
+	}
+
+	return
+}
+
+func (consumers *Consumers) Size() (result int) {
+	consumers.consumersMutex.RLock()
+	result = len(consumers.consumers)
+	consumers.consumersMutex.RUnlock()
+
+	return
+}
+
+func (consumers *Consumers) GetStorageKey() []byte {
+	transferId := consumers.GetTransferId()
+
+	return transferId[:]
+}
+
+func (consumers *Consumers) Update(other objectstorage.StorableObject) {
+	panic("approvers should never be overwritten and only stored once to optimize IO")
+}
+
+func (consumers *Consumers) MarshalBinary() (result []byte, err error) {
+	consumers.consumersMutex.RLock()
+
+	approversCount := len(consumers.consumers)
+	result = make([]byte, 4+approversCount*transfer.IdLength)
+	offset := 0
+
+	binary.LittleEndian.PutUint32(result[offset:], uint32(approversCount))
+	offset += 4
+
+	for approverId := range consumers.consumers {
+		marshaledBytes, marshalErr := approverId.MarshalBinary()
+		if marshalErr != nil {
+			err = marshalErr
+
+			consumers.consumersMutex.RUnlock()
+
+			return
+		}
+
+		copy(result[offset:], marshaledBytes)
+		offset += len(marshaledBytes)
+	}
+
+	consumers.consumersMutex.RUnlock()
+
+	return
+}
+
+func (consumers *Consumers) UnmarshalBinary(data []byte) (err error) {
+	consumers.consumers = make(map[transfer.Id]types.Empty)
+	offset := 0
+
+	approversCount := int(binary.LittleEndian.Uint32(data[offset:]))
+	offset += 4
+
+	for i := 0; i < approversCount; i++ {
+		var approverId transfer.Id
+		if err = approverId.UnmarshalBinary(data[offset:]); err != nil {
+			return
+		}
+		offset += transfer.IdLength
+
+		consumers.consumers[approverId] = types.Void
+	}
+
+	return
+}
diff --git a/packages/binary/valuetangle/model/missingtransfer.go b/packages/binary/valuetangle/model/missingtransfer.go
new file mode 100644
index 0000000000000000000000000000000000000000..f9772bbf3e598d09e7837a22173db41cb85eac9d
--- /dev/null
+++ b/packages/binary/valuetangle/model/missingtransfer.go
@@ -0,0 +1,54 @@
+package model
+
+import (
+	"time"
+
+	"github.com/iotaledger/goshimmer/packages/ledgerstate/transfer"
+
+	"github.com/iotaledger/hive.go/objectstorage"
+)
+
+type MissingTransfer struct {
+	objectstorage.StorableObjectFlags
+
+	transferId   transfer.Id
+	missingSince time.Time
+}
+
+func New(transferId transfer.Id) *MissingTransfer {
+	return &MissingTransfer{
+		transferId:   transferId,
+		missingSince: time.Now(),
+	}
+}
+
+func MissingTransferFromStorage(key []byte) objectstorage.StorableObject {
+	result := &MissingTransfer{}
+	copy(result.transferId[:], key)
+
+	return result
+}
+
+func (missingTransfer *MissingTransfer) GetTransferId() transfer.Id {
+	return missingTransfer.transferId
+}
+
+func (missingTransfer *MissingTransfer) GetMissingSince() time.Time {
+	return missingTransfer.missingSince
+}
+
+func (missingTransfer *MissingTransfer) GetStorageKey() []byte {
+	return missingTransfer.transferId[:]
+}
+
+func (missingTransfer *MissingTransfer) Update(other objectstorage.StorableObject) {
+	panic("missing transfer should never be overwritten and only stored once to optimize IO")
+}
+
+func (missingTransfer *MissingTransfer) MarshalBinary() (result []byte, err error) {
+	return missingTransfer.missingSince.MarshalBinary()
+}
+
+func (missingTransfer *MissingTransfer) UnmarshalBinary(data []byte) (err error) {
+	return missingTransfer.missingSince.UnmarshalBinary(data)
+}
diff --git a/packages/binary/valuetangle/valuetangle.go b/packages/binary/valuetangle/valuetangle.go
new file mode 100644
index 0000000000000000000000000000000000000000..e5b107d0025ea7514f60866f864ca461c02fd6a1
--- /dev/null
+++ b/packages/binary/valuetangle/valuetangle.go
@@ -0,0 +1,246 @@
+package valuetangle
+
+import (
+	"container/list"
+
+	"github.com/iotaledger/goshimmer/packages/binary/address"
+	"github.com/iotaledger/goshimmer/packages/binary/tangle"
+	"github.com/iotaledger/goshimmer/packages/binary/tangle/approvers"
+	"github.com/iotaledger/goshimmer/packages/binary/tangle/missingtransaction"
+	"github.com/iotaledger/goshimmer/packages/binary/transaction"
+	"github.com/iotaledger/goshimmer/packages/binary/transaction/payload/valuetransfer"
+	"github.com/iotaledger/goshimmer/packages/binary/transactionmetadata"
+	"github.com/iotaledger/goshimmer/packages/binary/types"
+	"github.com/iotaledger/goshimmer/packages/binary/valuetangle/model"
+	"github.com/iotaledger/goshimmer/packages/ledgerstate/transfer"
+	"github.com/iotaledger/hive.go/async"
+	"github.com/iotaledger/hive.go/events"
+	"github.com/iotaledger/hive.go/objectstorage"
+)
+
+// The value tangle defines an "ontology" on top of the tangle that "sees"" the value transfers as a hidden tangle in
+// the tangle.
+type ValueTangle struct {
+	tangle *tangle.Tangle
+
+	transferMetadataStorage *objectstorage.ObjectStorage
+	consumersStorage        *objectstorage.ObjectStorage
+	missingTransferStorage  *objectstorage.ObjectStorage
+
+	storeTransactionsWorkerPool async.WorkerPool
+	solidifierWorkerPool        async.WorkerPool
+	// cleanupWorkerPool           async.WorkerPool
+
+	Events Events
+}
+
+func New(tangle *tangle.Tangle) (valueTangle *ValueTangle) {
+	valueTangle = &ValueTangle{
+		tangle: tangle,
+	}
+
+	tangle.Events.TransactionSolid.Attach(events.NewClosure(valueTangle.attachTransaction))
+	tangle.Events.TransactionRemoved.Attach(events.NewClosure(valueTangle.deleteTransfer))
+
+	return
+}
+
+func (valueTangle *ValueTangle) attachTransaction(cachedTransaction *transaction.CachedTransaction, cachedTransactionMetadata *transactionmetadata.CachedTransactionMetadata) {
+	valueTangle.storeTransactionsWorkerPool.Submit(func() {
+		valueTangle.storeTransactionWorker(cachedTransaction, cachedTransactionMetadata)
+	})
+}
+
+// Retrieves a transaction from the tangle.
+func (valueTangle *ValueTangle) GetTransfer(transactionId transaction.Id) *model.CachedValueTransfer {
+	cachedTransaction := valueTangle.tangle.GetTransaction(transactionId)
+
+	// return an empty result if the transaction is no value transaction
+	if tx := cachedTransaction.Unwrap(); tx != nil && tx.GetPayload().GetType() != valuetransfer.Type {
+		cachedTransaction.Release()
+
+		return &model.CachedValueTransfer{CachedTransaction: &transaction.CachedTransaction{CachedObject: objectstorage.NewEmptyCachedObject(transactionId[:])}}
+	}
+
+	return &model.CachedValueTransfer{CachedTransaction: cachedTransaction}
+}
+
+// Retrieves the metadata of a transaction from the tangle.
+func (valueTangle *ValueTangle) GetTransferMetadata(transactionId transaction.Id) *transactionmetadata.CachedTransactionMetadata {
+	return &transactionmetadata.CachedTransactionMetadata{CachedObject: valueTangle.transferMetadataStorage.Load(transactionId[:])}
+}
+
+func (valueTangle *ValueTangle) deleteTransfer(transactionId transaction.Id) {
+
+}
+
+// Marks the tangle as stopped, so it will not accept any new transactions (waits for all backgroundTasks to finish.
+func (valueTangle *ValueTangle) Shutdown() *ValueTangle {
+	valueTangle.storeTransactionsWorkerPool.ShutdownGracefully()
+
+	return valueTangle
+}
+
+func (valueTangle *ValueTangle) storeTransactionWorker(cachedTx *transaction.CachedTransaction, cachedTxMetadata *transactionmetadata.CachedTransactionMetadata) {
+	addTransferToConsumers := func(transferId transfer.Id, consumedTransferHash transfer.Id) {
+		cachedConsumers := valueTangle.consumersStorage.ComputeIfAbsent(consumedTransferHash[:], func([]byte) objectstorage.StorableObject {
+			result := model.NewConsumers(consumedTransferHash)
+
+			result.SetModified()
+
+			return result
+		})
+
+		if _tmp := cachedConsumers.Get(); _tmp != nil {
+			if consumersObject := _tmp.(*model.Consumers); consumersObject != nil {
+				consumersObject.Add(transferId)
+
+				// if the approvers got "cleaned up" while being in cache, we make sure the object gets persisted again
+				consumersObject.Persist()
+			}
+		}
+
+		cachedConsumers.Release()
+	}
+
+	cachedTxMetadata.Release()
+
+	cachedValueTransfer := &model.CachedValueTransfer{CachedTransaction: cachedTx}
+
+	valueTransfer, transferId := cachedValueTransfer.Unwrap()
+	if valueTransfer == nil {
+		cachedValueTransfer.Release()
+
+		return
+	}
+
+	for transferId := range valueTangle.getInputsMap(valueTransfer) {
+		addTransferToConsumers(transferId, transferId)
+	}
+
+	if valueTangle.missingTransferStorage.DeleteIfPresent(transferId[:]) {
+		valueTangle.Events.MissingTransferReceived.Trigger(transferId)
+	}
+
+	valueTangle.Events.TransferAttached.Trigger(cachedValueTransfer)
+
+	valueTangle.solidifierWorkerPool.Submit(func() {
+		valueTangle.solidifyTransferWorker(cachedValueTransfer, transferId)
+	})
+}
+
+// Worker that solidifies the transactions (recursively from past to present).
+func (valueTangle *ValueTangle) solidifyTransferWorker(cachedValueTransfer *model.CachedValueTransfer, transferId transfer.Id) {
+	isTransferMarkedAsSolid := func(transactionId transaction.Id) bool {
+		if transactionId == transaction.EmptyId {
+			return true
+		}
+
+		transferMetadataCached := valueTangle.GetTransferMetadata(transactionId)
+		if transactionMetadata := transferMetadataCached.Unwrap(); transactionMetadata == nil {
+			transferMetadataCached.Release()
+
+			// if transaction is missing and was not reported as missing, yet
+			if cachedMissingTransfer, missingTransactionStored := valueTangle.missingTransferStorage.StoreIfAbsent(transactionId[:], missingtransaction.New(transactionId)); missingTransactionStored {
+				cachedMissingTransfer.Consume(func(object objectstorage.StorableObject) {
+					valueTangle.monitorMissingTransactionWorker(object.(*missingtransaction.MissingTransaction).GetTransactionId())
+				})
+			}
+
+			return false
+		} else if !transactionMetadata.IsSolid() {
+			transferMetadataCached.Release()
+
+			return false
+		}
+		transferMetadataCached.Release()
+
+		return true
+	}
+
+	isTransactionSolid := func(transaction *transaction.Transaction, transactionMetadata *transactionmetadata.TransactionMetadata) bool {
+		if transaction == nil || transaction.IsDeleted() {
+			return false
+		}
+
+		if transactionMetadata == nil || transactionMetadata.IsDeleted() {
+			return false
+		}
+
+		if transactionMetadata.IsSolid() {
+			return true
+		}
+
+		// 1. check tangle solidity
+		isTrunkSolid := isTransferMarkedAsSolid(transaction.GetTrunkTransactionId())
+		isBranchSolid := isTransferMarkedAsSolid(transaction.GetBranchTransactionId())
+		if isTrunkSolid && isBranchSolid {
+			// 2. check payload solidity
+			return true
+		}
+
+		return false
+	}
+
+	popElementsFromStack := func(stack *list.List) (*transaction.CachedTransaction, *transactionmetadata.CachedTransactionMetadata) {
+		currentSolidificationEntry := stack.Front()
+		currentCachedTransaction := currentSolidificationEntry.Value.([2]interface{})[0]
+		currentCachedTransactionMetadata := currentSolidificationEntry.Value.([2]interface{})[1]
+		stack.Remove(currentSolidificationEntry)
+
+		return currentCachedTransaction.(*transaction.CachedTransaction), currentCachedTransactionMetadata.(*transactionmetadata.CachedTransactionMetadata)
+	}
+
+	// initialize the stack
+	solidificationStack := list.New()
+	solidificationStack.PushBack([2]interface{}{cachedValueTransfer, transferId})
+
+	// process transactions that are supposed to be checked for solidity recursively
+	for solidificationStack.Len() > 0 {
+		currentCachedTransaction, currentCachedTransactionMetadata := popElementsFromStack(solidificationStack)
+
+		currentTransaction := currentCachedTransaction.Unwrap()
+		currentTransactionMetadata := currentCachedTransactionMetadata.Unwrap()
+		if currentTransaction == nil || currentTransactionMetadata == nil {
+			currentCachedTransaction.Release()
+			currentCachedTransactionMetadata.Release()
+
+			continue
+		}
+
+		// if current transaction is solid and was not marked as solid before: mark as solid and propagate
+		if isTransactionSolid(currentTransaction, currentTransactionMetadata) && currentTransactionMetadata.SetSolid(true) {
+			valueTangle.Events.TransferSolid.Trigger(currentCachedTransaction, currentCachedTransactionMetadata)
+
+			valueTangle.GetConsumers(currentTransaction.GetId()).Consume(func(object objectstorage.StorableObject) {
+				for approverTransactionId := range object.(*approvers.Approvers).Get() {
+					solidificationStack.PushBack([2]interface{}{
+						valueTangle.GetTransfer(approverTransactionId),
+						valueTangle.GetTransferMetadata(approverTransactionId),
+					})
+				}
+			})
+		}
+
+		// release cached results
+		currentCachedTransaction.Release()
+		currentCachedTransactionMetadata.Release()
+	}
+}
+
+func (valueTangle *ValueTangle) getInputsMap(valueTransfer *valuetransfer.ValueTransfer) (result map[transfer.Id]map[address.Address]types.Empty) {
+	result = make(map[transfer.Id]map[address.Address]types.Empty)
+
+	for _, transferOutputReference := range valueTransfer.GetInputs() {
+		addressMap, addressMapExists := result[transferOutputReference.GetTransferHash()]
+		if !addressMapExists {
+			addressMap = make(map[address.Address]types.Empty)
+
+			result[transferOutputReference.GetTransferHash()] = addressMap
+		}
+
+		addressMap[transferOutputReference.GetAddress()] = types.Void
+	}
+
+	return
+}
diff --git a/packages/ledgerstate/conflict/id.go b/packages/ledgerstate/conflict/id.go
index 8ceabbcef8fbb6537f78070ebb88c39c26f20ac4..377b3e45d5d0f648f3c82f1892c5a7d2c9d57063 100644
--- a/packages/ledgerstate/conflict/id.go
+++ b/packages/ledgerstate/conflict/id.go
@@ -16,19 +16,19 @@ type Id [IdLength]byte
 func NewId(conflictBytes ...interface{}) (result Id) {
 	switch len(conflictBytes) {
 	case 2:
-		transferHash, ok := conflictBytes[0].(transfer.Hash)
+		transferHash, ok := conflictBytes[0].(transfer.Id)
 		if !ok {
 			panic("expected first parameter of NewId to be a TransferHash")
 		}
 
-		addressHash, ok := conflictBytes[0].(transfer.Hash)
+		addressHash, ok := conflictBytes[0].(transfer.Id)
 		if !ok {
 			panic("expected second parameter of NewId to be a AddressHash")
 		}
 
-		fullConflictSetIdentifier := make([]byte, transfer.HashLength+address.Length)
+		fullConflictSetIdentifier := make([]byte, transfer.IdLength+address.Length)
 		copy(fullConflictSetIdentifier, transferHash[:])
-		copy(fullConflictSetIdentifier[transfer.HashLength:], addressHash[:])
+		copy(fullConflictSetIdentifier[transfer.IdLength:], addressHash[:])
 
 		result = blake2b.Sum256(fullConflictSetIdentifier)
 	case 1:
diff --git a/packages/ledgerstate/ledgerstate.go b/packages/ledgerstate/ledgerstate.go
index 48d48147ddb2cc3b58bf664442d9c7d793b2d807..b017becee8589bbe2b353398b323f1117524890c 100644
--- a/packages/ledgerstate/ledgerstate.go
+++ b/packages/ledgerstate/ledgerstate.go
@@ -50,7 +50,7 @@ func NewLedgerState(storageId []byte) *LedgerState {
 	return result
 }
 
-func (ledgerState *LedgerState) AddTransferOutput(transferHash transfer.Hash, addressHash address.Address, balances ...*coloredcoins.ColoredBalance) *LedgerState {
+func (ledgerState *LedgerState) AddTransferOutput(transferHash transfer.Id, addressHash address.Address, balances ...*coloredcoins.ColoredBalance) *LedgerState {
 	ledgerState.GetReality(reality.MAIN_ID).Consume(func(object objectstorage.StorableObject) {
 		mainReality := object.(*Reality)
 
@@ -153,7 +153,7 @@ func (ledgerState *LedgerState) BookTransfer(transfer *transfer.Transfer) (err e
 	ledgerState.getTargetReality(inputs).Consume(func(object objectstorage.StorableObject) {
 		targetReality := object.(*Reality)
 
-		if err = targetReality.bookTransfer(transfer.GetHash(), inputs, transfer.GetOutputs()); err != nil {
+		if err = targetReality.bookTransfer(transfer.GetId(), inputs, transfer.GetOutputs()); err != nil {
 			return
 		}
 
@@ -388,7 +388,7 @@ func (ledgerState *LedgerState) Prune() *LedgerState {
 func (ledgerState *LedgerState) generateFilterPrefixes(filters []interface{}) ([][]byte, bool) {
 	filteredRealities := make([]reality.Id, 0)
 	filteredAddresses := make([]address.Address, 0)
-	filteredTransfers := make([]transfer.Hash, 0)
+	filteredTransfers := make([]transfer.Id, 0)
 	filterSpent := false
 	filterUnspent := false
 
@@ -398,7 +398,7 @@ func (ledgerState *LedgerState) generateFilterPrefixes(filters []interface{}) ([
 			filteredRealities = append(filteredRealities, typeCastedValue)
 		case address.Address:
 			filteredAddresses = append(filteredAddresses, typeCastedValue)
-		case transfer.Hash:
+		case transfer.Id:
 			filteredTransfers = append(filteredTransfers, typeCastedValue)
 		case transfer.SpentIndicator:
 			switch typeCastedValue {
diff --git a/packages/ledgerstate/ledgerstate_test.go b/packages/ledgerstate/ledgerstate_test.go
index bcac9f2fe5ae44a49594fe11e191a23640705eae..62c5c975404d649087f1dcbfeb69a2d065fa25e2 100644
--- a/packages/ledgerstate/ledgerstate_test.go
+++ b/packages/ledgerstate/ledgerstate_test.go
@@ -17,12 +17,12 @@ import (
 var (
 	iota_          = coloredcoins.NewColor("IOTA")
 	eth            = coloredcoins.NewColor("ETH")
-	transferHash1  = transfer.NewHash("TRANSFER1")
-	transferHash2  = transfer.NewHash("TRANSFER2")
-	transferHash3  = transfer.NewHash("TRANSFER3")
-	transferHash4  = transfer.NewHash("TRANSFER4")
-	transferHash5  = transfer.NewHash("TRANSFER5")
-	transferHash6  = transfer.NewHash("TRANSFER6")
+	transferHash1  = transfer.NewId([]byte("TRANSFER1"))
+	transferHash2  = transfer.NewId([]byte("TRANSFER2"))
+	transferHash3  = transfer.NewId([]byte("TRANSFER3"))
+	transferHash4  = transfer.NewId([]byte("TRANSFER4"))
+	transferHash5  = transfer.NewId([]byte("TRANSFER5"))
+	transferHash6  = transfer.NewId([]byte("TRANSFER6"))
 	addressHash1   = address.New([]byte("ADDRESS1"))
 	addressHash2   = address.New([]byte("ADDRESS2"))
 	addressHash3   = address.New([]byte("ADDRESS3"))
@@ -42,7 +42,7 @@ func Benchmark(b *testing.B) {
 	lastTransferHash := transferHash1
 
 	for i := 0; i < b.N; i++ {
-		newTransferHash := transfer.NewHash(strconv.Itoa(i))
+		newTransferHash := transfer.NewId([]byte(strconv.Itoa(i)))
 
 		if err := ledgerState.BookTransfer(transfer.NewTransfer(newTransferHash).AddInput(
 			transfer.NewOutputReference(lastTransferHash, addressHash1),
@@ -108,10 +108,10 @@ func Test(t *testing.T) {
 
 var transferHashCounter = 0
 
-func generateRandomTransferHash() transfer.Hash {
+func generateRandomTransferHash() transfer.Id {
 	transferHashCounter++
 
-	return transfer.NewHash("TRANSFER" + strconv.Itoa(transferHashCounter))
+	return transfer.NewId([]byte("TRANSFER" + strconv.Itoa(transferHashCounter)))
 }
 
 var addressHashCounter = 0
diff --git a/packages/ledgerstate/outputs.png b/packages/ledgerstate/outputs.png
index fbc282cfbfb8ee744230710f46f058f8ff961ccd..c1b424939d22f696b0484d0840cb43566bcee6af 100644
Binary files a/packages/ledgerstate/outputs.png and b/packages/ledgerstate/outputs.png differ
diff --git a/packages/ledgerstate/outputs1.png b/packages/ledgerstate/outputs1.png
index 6da15b92f4a01206aa38ff8b10439123feb22e41..abb2351f713fe9acff448e2fef9411e4e45dc300 100644
Binary files a/packages/ledgerstate/outputs1.png and b/packages/ledgerstate/outputs1.png differ
diff --git a/packages/ledgerstate/outputs2.png b/packages/ledgerstate/outputs2.png
index 4628b918f78a8d522080837b68b29fb28159a0d8..e05b9dc06842dff660319bcf21edd8f4d9da0707 100644
Binary files a/packages/ledgerstate/outputs2.png and b/packages/ledgerstate/outputs2.png differ
diff --git a/packages/ledgerstate/realities.png b/packages/ledgerstate/realities.png
index 9415fdb9f6fee591eada13e24e86fb98a923a16b..049e17a2f46861b937f81d3400acf808ca4ac833 100644
Binary files a/packages/ledgerstate/realities.png and b/packages/ledgerstate/realities.png differ
diff --git a/packages/ledgerstate/reality.go b/packages/ledgerstate/reality.go
index 09bb808a7df694f9c15af51fb5752fe77573fa89..efbc72c629496297eb427f213fbaef82387ae767 100644
--- a/packages/ledgerstate/reality.go
+++ b/packages/ledgerstate/reality.go
@@ -424,7 +424,7 @@ func (mreality *Reality) CreateReality(id reality.Id) *objectstorage.CachedObjec
 
 // Books a transfer into this reality (wrapper for the private bookTransfer function).
 func (mreality *Reality) BookTransfer(transfer *transfer.Transfer) (err error) {
-	err = mreality.bookTransfer(transfer.GetHash(), mreality.ledgerState.getTransferInputs(transfer), transfer.GetOutputs())
+	err = mreality.bookTransfer(transfer.GetId(), mreality.ledgerState.getTransferInputs(transfer), transfer.GetOutputs())
 
 	return
 }
@@ -450,7 +450,7 @@ func (mreality *Reality) String() (result string) {
 }
 
 // Books a transfer into this reality (contains the dispatcher for the actual tasks).
-func (mreality *Reality) bookTransfer(transferHash transfer.Hash, inputs objectstorage.CachedObjects, outputs map[address.Address][]*coloredcoins.ColoredBalance) (err error) {
+func (mreality *Reality) bookTransfer(transferHash transfer.Id, inputs objectstorage.CachedObjects, outputs map[address.Address][]*coloredcoins.ColoredBalance) (err error) {
 	if err = mreality.verifyTransfer(inputs, outputs); err != nil {
 		return
 	}
@@ -511,7 +511,7 @@ func (mreality *Reality) verifyTransfer(inputs []*objectstorage.CachedObject, ou
 
 // Internal utility function that marks the consumed inputs as spent and returns the corresponding conflicts if the
 // inputs have been consumed before.
-func (mreality *Reality) consumeInputs(inputs objectstorage.CachedObjects, transferHash transfer.Hash, outputs map[address.Address][]*coloredcoins.ColoredBalance) (conflicts objectstorage.CachedObjects, err error) {
+func (mreality *Reality) consumeInputs(inputs objectstorage.CachedObjects, transferHash transfer.Id, outputs map[address.Address][]*coloredcoins.ColoredBalance) (conflicts objectstorage.CachedObjects, err error) {
 	conflicts = make(objectstorage.CachedObjects, 0)
 
 	for _, input := range inputs {
@@ -539,7 +539,7 @@ func (mreality *Reality) consumeInputs(inputs objectstorage.CachedObjects, trans
 //
 // If the inputs have been used before and we consequently have a non-empty list of conflicts, we first create a new
 // reality for the inputs and then book the transfer outputs into the correct reality.
-func (mreality *Reality) createTransferOutputs(transferHash transfer.Hash, outputs map[address.Address][]*coloredcoins.ColoredBalance, conflicts objectstorage.CachedObjects) (err error) {
+func (mreality *Reality) createTransferOutputs(transferHash transfer.Id, outputs map[address.Address][]*coloredcoins.ColoredBalance, conflicts objectstorage.CachedObjects) (err error) {
 	if len(conflicts) >= 1 {
 		targetRealityId := transferHash.ToRealityId()
 
@@ -588,7 +588,7 @@ func (mreality *Reality) collectParentConflictRealities(parentConflictRealities
 
 // Utility function that processes a conflicting input by retrieving the corresponding conflict.
 // If there is a non-empty list of consumers to elevate, we elevate them.
-func (mreality *Reality) processConflictingInput(input *transfer.Output, consumersToElevate map[transfer.Hash][]address.Address) (cachedConflict *objectstorage.CachedObject, err error) {
+func (mreality *Reality) processConflictingInput(input *transfer.Output, consumersToElevate map[transfer.Id][]address.Address) (cachedConflict *objectstorage.CachedObject, err error) {
 	conflictId := conflict.NewId(input.GetTransferHash(), input.GetAddressHash())
 
 	if len(consumersToElevate) >= 1 {
@@ -603,7 +603,7 @@ func (mreality *Reality) processConflictingInput(input *transfer.Output, consume
 }
 
 // Creates a Reality for the consumers of the conflicting inputs and registers it as part of the corresponding Conflict.
-func (mreality *Reality) createRealityForPreviouslyUnconflictingConsumers(consumersOfConflictingInput map[transfer.Hash][]address.Address, conflict *conflict.Conflict) (err error) {
+func (mreality *Reality) createRealityForPreviouslyUnconflictingConsumers(consumersOfConflictingInput map[transfer.Id][]address.Address, conflict *conflict.Conflict) (err error) {
 	for transferHash, addressHashes := range consumersOfConflictingInput {
 		elevatedRealityId := transferHash.ToRealityId()
 
diff --git a/packages/ledgerstate/transfer/constants.go b/packages/ledgerstate/transfer/constants.go
index 03b144466dda030547b996bf1188c3259bdb86b6..c3a690bd4eaf5c859933112a0d5893acf5224f2f 100644
--- a/packages/ledgerstate/transfer/constants.go
+++ b/packages/ledgerstate/transfer/constants.go
@@ -16,7 +16,7 @@ const (
 	marshalTransferOutputBookingSpentStart        = marshalTransferOutputBookingAddressHashEnd
 	marshalTransferOutputBookingSpentEnd          = marshalTransferOutputBookingSpentStart + 1
 	marshalTransferOutputBookingTransferHashStart = marshalTransferOutputBookingSpentEnd
-	marshalTransferOutputBookingTransferHashEnd   = marshalTransferOutputBookingTransferHashStart + HashLength
+	marshalTransferOutputBookingTransferHashEnd   = marshalTransferOutputBookingTransferHashStart + IdLength
 	marshalTransferOutputBookingTotalLength       = marshalTransferOutputBookingTransferHashEnd
 )
 
diff --git a/packages/ledgerstate/transfer/hash.go b/packages/ledgerstate/transfer/hash.go
deleted file mode 100644
index ed95f24839d491af50f291e8528132db62659cf3..0000000000000000000000000000000000000000
--- a/packages/ledgerstate/transfer/hash.go
+++ /dev/null
@@ -1,38 +0,0 @@
-package transfer
-
-import (
-	"unicode/utf8"
-
-	"github.com/iotaledger/goshimmer/packages/ledgerstate/reality"
-	"github.com/iotaledger/goshimmer/packages/stringify"
-)
-
-type Hash [HashLength]byte
-
-func NewHash(transferHash string) (result Hash) {
-	copy(result[:], transferHash)
-
-	return
-}
-
-func (transferHash Hash) ToRealityId() (realityId reality.Id) {
-	copy(realityId[:], transferHash[:])
-
-	return
-}
-
-func (transferHash *Hash) UnmarshalBinary(data []byte) error {
-	copy(transferHash[:], data[:HashLength])
-
-	return nil
-}
-
-func (transferHash Hash) String() string {
-	if utf8.Valid(transferHash[:]) {
-		return string(transferHash[:])
-	} else {
-		return stringify.SliceOfBytes(transferHash[:])
-	}
-}
-
-const HashLength = 32
diff --git a/packages/ledgerstate/transfer/id.go b/packages/ledgerstate/transfer/id.go
new file mode 100644
index 0000000000000000000000000000000000000000..8431ea04dc7358f49bb1e49727f30c9321789461
--- /dev/null
+++ b/packages/ledgerstate/transfer/id.go
@@ -0,0 +1,47 @@
+package transfer
+
+import (
+	"unicode/utf8"
+
+	"github.com/iotaledger/goshimmer/packages/ledgerstate/reality"
+	"github.com/iotaledger/goshimmer/packages/stringify"
+)
+
+type Id [IdLength]byte
+
+func NewId(id []byte) (result Id) {
+	copy(result[:], id)
+
+	return
+}
+
+func (transferId Id) ToRealityId() (realityId reality.Id) {
+	copy(realityId[:], transferId[:])
+
+	return
+}
+
+func (transferId *Id) MarshalBinary() (result []byte, err error) {
+	result = make([]byte, IdLength)
+	copy(result, transferId[:])
+
+	return
+}
+
+func (transferId *Id) UnmarshalBinary(data []byte) error {
+	copy(transferId[:], data[:IdLength])
+
+	return nil
+}
+
+func (transferId Id) String() string {
+	if utf8.Valid(transferId[:]) {
+		return string(transferId[:])
+	} else {
+		return stringify.SliceOfBytes(transferId[:])
+	}
+}
+
+var EmptyId = Id{}
+
+const IdLength = 32
diff --git a/packages/ledgerstate/transfer/output.go b/packages/ledgerstate/transfer/output.go
index 9afeefb67a0fc12618199b078a5cb32dfbc11f2a..8c7f5b06b981bfa13a94d7e9429dddd4b80ffab4 100644
--- a/packages/ledgerstate/transfer/output.go
+++ b/packages/ledgerstate/transfer/output.go
@@ -15,11 +15,11 @@ import (
 type Output struct {
 	objectstorage.StorableObjectFlags
 
-	transferHash Hash
+	transferHash Id
 	addressHash  address.Address
 	balances     []*coloredcoins.ColoredBalance
 	realityId    reality.Id
-	consumers    map[Hash][]address.Address
+	consumers    map[Id][]address.Address
 
 	storageKey     []byte
 	OutputBookings *objectstorage.ObjectStorage
@@ -29,20 +29,20 @@ type Output struct {
 	bookingMutex   sync.Mutex
 }
 
-func NewTransferOutput(outputBookings *objectstorage.ObjectStorage, realityId reality.Id, transferHash Hash, addressHash address.Address, balances ...*coloredcoins.ColoredBalance) *Output {
+func NewTransferOutput(outputBookings *objectstorage.ObjectStorage, realityId reality.Id, transferHash Id, addressHash address.Address, balances ...*coloredcoins.ColoredBalance) *Output {
 	return &Output{
 		transferHash: transferHash,
 		addressHash:  addressHash,
 		balances:     balances,
 		realityId:    realityId,
-		consumers:    make(map[Hash][]address.Address),
+		consumers:    make(map[Id][]address.Address),
 
 		storageKey:     append(transferHash[:], addressHash[:]...),
 		OutputBookings: outputBookings,
 	}
 }
 
-func (transferOutput *Output) GetTransferHash() (transferHash Hash) {
+func (transferOutput *Output) GetTransferHash() (transferHash Id) {
 	transferHash = transferOutput.transferHash
 
 	return
@@ -81,8 +81,8 @@ func (transferOutput *Output) GetBalances() []*coloredcoins.ColoredBalance {
 	return transferOutput.balances
 }
 
-func (transferOutput *Output) GetConsumers() (consumers map[Hash][]address.Address) {
-	consumers = make(map[Hash][]address.Address)
+func (transferOutput *Output) GetConsumers() (consumers map[Id][]address.Address) {
+	consumers = make(map[Id][]address.Address)
 
 	transferOutput.consumersMutex.RLock()
 	for transferHash, addresses := range transferOutput.consumers {
@@ -94,7 +94,7 @@ func (transferOutput *Output) GetConsumers() (consumers map[Hash][]address.Addre
 	return
 }
 
-func (transferOutput *Output) AddConsumer(consumer Hash, outputs map[address.Address][]*coloredcoins.ColoredBalance) (consumersToElevate map[Hash][]address.Address, err error) {
+func (transferOutput *Output) AddConsumer(consumer Id, outputs map[address.Address][]*coloredcoins.ColoredBalance) (consumersToElevate map[Id][]address.Address, err error) {
 	transferOutput.consumersMutex.RLock()
 	if _, exist := transferOutput.consumers[consumer]; exist {
 		transferOutput.consumersMutex.RUnlock()
@@ -107,13 +107,13 @@ func (transferOutput *Output) AddConsumer(consumer Hash, outputs map[address.Add
 			consumersToElevate = nil
 			err = transferOutput.markAsSpent()
 		case 1:
-			consumersToElevate = make(map[Hash][]address.Address, 1)
+			consumersToElevate = make(map[Id][]address.Address, 1)
 			for transferHash, addresses := range transferOutput.consumers {
 				consumersToElevate[transferHash] = addresses
 			}
 			err = nil
 		default:
-			consumersToElevate = make(map[Hash][]address.Address)
+			consumersToElevate = make(map[Id][]address.Address)
 			err = nil
 		}
 		consumers := make([]address.Address, len(outputs))
@@ -174,7 +174,7 @@ func (transferOutput *Output) MarshalBinary() ([]byte, error) {
 	balanceCount := len(transferOutput.balances)
 	consumerCount := len(transferOutput.consumers)
 
-	serializedLength := reality.IdLength + 4 + balanceCount*coloredcoins.BalanceLength + 4 + consumerCount*HashLength
+	serializedLength := reality.IdLength + 4 + balanceCount*coloredcoins.BalanceLength + 4 + consumerCount*IdLength
 	for _, addresses := range transferOutput.consumers {
 		serializedLength += 4
 		for range addresses {
@@ -198,8 +198,8 @@ func (transferOutput *Output) MarshalBinary() ([]byte, error) {
 	binary.LittleEndian.PutUint32(result[offset:], uint32(consumerCount))
 	offset += 4
 	for transferHash, addresses := range transferOutput.consumers {
-		copy(result[offset:], transferHash[:HashLength])
-		offset += HashLength
+		copy(result[offset:], transferHash[:IdLength])
+		offset += IdLength
 
 		binary.LittleEndian.PutUint32(result[offset:], uint32(len(addresses)))
 		offset += 4
@@ -217,11 +217,11 @@ func (transferOutput *Output) MarshalBinary() ([]byte, error) {
 }
 
 func (transferOutput *Output) UnmarshalBinary(serializedObject []byte) error {
-	if err := transferOutput.transferHash.UnmarshalBinary(transferOutput.storageKey[:HashLength]); err != nil {
+	if err := transferOutput.transferHash.UnmarshalBinary(transferOutput.storageKey[:IdLength]); err != nil {
 		return err
 	}
 
-	if err := transferOutput.addressHash.UnmarshalBinary(transferOutput.storageKey[HashLength:]); err != nil {
+	if err := transferOutput.addressHash.UnmarshalBinary(transferOutput.storageKey[IdLength:]); err != nil {
 		return err
 	}
 
@@ -268,19 +268,19 @@ func (transferOutput *Output) unmarshalBalances(serializedBalances []byte) ([]*c
 	return balances, nil
 }
 
-func (transferOutput *Output) unmarshalConsumers(serializedConsumers []byte) (map[Hash][]address.Address, error) {
+func (transferOutput *Output) unmarshalConsumers(serializedConsumers []byte) (map[Id][]address.Address, error) {
 	offset := 0
 
 	consumerCount := int(binary.LittleEndian.Uint32(serializedConsumers[offset:]))
 	offset += 4
 
-	consumers := make(map[Hash][]address.Address, consumerCount)
+	consumers := make(map[Id][]address.Address, consumerCount)
 	for i := 0; i < consumerCount; i++ {
-		transferHash := Hash{}
+		transferHash := Id{}
 		if err := transferHash.UnmarshalBinary(serializedConsumers[offset:]); err != nil {
 			return nil, err
 		}
-		offset += HashLength
+		offset += IdLength
 
 		addressHashCount := int(binary.LittleEndian.Uint32(serializedConsumers[offset:]))
 		offset += 4
diff --git a/packages/ledgerstate/transfer/output_booking.go b/packages/ledgerstate/transfer/output_booking.go
index e18a77c1d807bde5afd0e86869345731090b0c5a..e268015151c3eb1437bd584fb729aa86262981d8 100644
--- a/packages/ledgerstate/transfer/output_booking.go
+++ b/packages/ledgerstate/transfer/output_booking.go
@@ -8,8 +8,8 @@ import (
 
 // region private utility methods //////////////////////////////////////////////////////////////////////////////////////
 
-func GenerateOutputBookingStorageKey(realityId reality.Id, addressHash address.Address, spent bool, transferHash Hash) (storageKey []byte) {
-	storageKey = make([]byte, reality.IdLength+address.Length+1+HashLength)
+func GenerateOutputBookingStorageKey(realityId reality.Id, addressHash address.Address, spent bool, transferHash Id) (storageKey []byte) {
+	storageKey = make([]byte, reality.IdLength+address.Length+1+IdLength)
 
 	copy(storageKey[marshalTransferOutputBookingRealityIdStart:marshalTransferOutputBookingRealityIdEnd], realityId[:reality.IdLength])
 	copy(storageKey[marshalTransferOutputBookingAddressHashStart:marshalTransferOutputBookingAddressHashEnd], addressHash[:address.Length])
@@ -18,7 +18,7 @@ func GenerateOutputBookingStorageKey(realityId reality.Id, addressHash address.A
 	} else {
 		storageKey[marshalTransferOutputBookingSpentStart] = byte(UNSPENT)
 	}
-	copy(storageKey[marshalTransferOutputBookingTransferHashStart:marshalTransferOutputBookingTransferHashEnd], transferHash[:HashLength])
+	copy(storageKey[marshalTransferOutputBookingTransferHashStart:marshalTransferOutputBookingTransferHashEnd], transferHash[:IdLength])
 
 	return
 }
diff --git a/packages/ledgerstate/transfer/output_reference.go b/packages/ledgerstate/transfer/output_reference.go
index b061af8fb30545da6772b0be535943be65d9a33c..d990f7bedcbbd5e6ef3da11eb8e412a519c2a404 100644
--- a/packages/ledgerstate/transfer/output_reference.go
+++ b/packages/ledgerstate/transfer/output_reference.go
@@ -7,11 +7,11 @@ import (
 
 type OutputReference struct {
 	storageKey   []byte
-	transferHash Hash
+	transferHash Id
 	addressHash  address.Address
 }
 
-func NewOutputReference(transferHash Hash, addressHash address.Address) *OutputReference {
+func NewOutputReference(transferHash Id, addressHash address.Address) *OutputReference {
 	return &OutputReference{
 		storageKey:   append(transferHash[:], addressHash[:]...),
 		transferHash: transferHash,
@@ -19,16 +19,20 @@ func NewOutputReference(transferHash Hash, addressHash address.Address) *OutputR
 	}
 }
 
+func (reference *OutputReference) GetTransferHash() Id {
+	return reference.transferHash
+}
+
 func (reference *OutputReference) GetAddress() address.Address {
 	return reference.addressHash
 }
 
 func (reference *OutputReference) MarshalBinary() (result []byte, err error) {
-	result = make([]byte, HashLength+address.Length)
+	result = make([]byte, IdLength+address.Length)
 	offset := 0
 
 	copy(result[offset:], reference.transferHash[:])
-	offset += HashLength
+	offset += IdLength
 
 	copy(result[offset:], reference.addressHash[:])
 
@@ -39,7 +43,7 @@ func (reference *OutputReference) UnmarshalBinary(bytes []byte) (err error) {
 	offset := 0
 
 	copy(reference.transferHash[:], bytes[offset:])
-	offset += HashLength
+	offset += IdLength
 
 	copy(reference.addressHash[:], bytes[offset:])
 
diff --git a/packages/ledgerstate/transfer/output_test.go b/packages/ledgerstate/transfer/output_test.go
index 5bf1746964ca8579ed29a09b199b1b9a6de78d73..3166683ca56e960590e95693fd5c000deba1cda2 100644
--- a/packages/ledgerstate/transfer/output_test.go
+++ b/packages/ledgerstate/transfer/output_test.go
@@ -13,10 +13,10 @@ import (
 )
 
 func TestTransferOutput_MarshalUnmarshal(t *testing.T) {
-	transferOutput := NewTransferOutput(nil, reality.NewId("REALITY"), NewHash("RECEIVE"), address.New([]byte("ADDRESS1")), coloredcoins.NewColoredBalance(coloredcoins.NewColor("IOTA"), 44), coloredcoins.NewColoredBalance(coloredcoins.NewColor("BTC"), 88))
-	transferOutput.consumers = make(map[Hash][]address.Address)
+	transferOutput := NewTransferOutput(nil, reality.NewId("REALITY"), NewId([]byte("RECEIVE")), address.New([]byte("ADDRESS1")), coloredcoins.NewColoredBalance(coloredcoins.NewColor("IOTA"), 44), coloredcoins.NewColoredBalance(coloredcoins.NewColor("BTC"), 88))
+	transferOutput.consumers = make(map[Id][]address.Address)
 
-	spendTransferHash := NewHash("SPEND")
+	spendTransferHash := NewId([]byte("SPEND"))
 	transferOutput.consumers[spendTransferHash] = make([]address.Address, 2)
 	transferOutput.consumers[spendTransferHash][0] = address.New([]byte("ADDRESS2"))
 	transferOutput.consumers[spendTransferHash][1] = address.New([]byte("ADDRESS3"))
diff --git a/packages/ledgerstate/transfer/transfer.go b/packages/ledgerstate/transfer/transfer.go
index ee91c1ebf67db1a3fa27172d1eb44f370ea617b7..70c27381af06daed26523fc53f4e25e120f24e0f 100644
--- a/packages/ledgerstate/transfer/transfer.go
+++ b/packages/ledgerstate/transfer/transfer.go
@@ -6,21 +6,21 @@ import (
 )
 
 type Transfer struct {
-	hash    Hash
+	id      Id
 	inputs  []*OutputReference
 	outputs map[address.Address][]*coloredcoins.ColoredBalance
 }
 
-func NewTransfer(transferHash Hash) *Transfer {
+func NewTransfer(id Id) *Transfer {
 	return &Transfer{
-		hash:    transferHash,
+		id:      id,
 		inputs:  make([]*OutputReference, 0),
 		outputs: make(map[address.Address][]*coloredcoins.ColoredBalance),
 	}
 }
 
-func (transfer *Transfer) GetHash() Hash {
-	return transfer.hash
+func (transfer *Transfer) GetId() Id {
+	return transfer.id
 }
 
 func (transfer *Transfer) GetInputs() []*OutputReference {
diff --git a/packages/ledgerstate/transfer/transfer_output_booking.go b/packages/ledgerstate/transfer/transfer_output_booking.go
index 663f1aa99f0d27b2f6d2f60665d75188b1faf458..01238c133c0f6843d097bb362d61305417f17b9c 100644
--- a/packages/ledgerstate/transfer/transfer_output_booking.go
+++ b/packages/ledgerstate/transfer/transfer_output_booking.go
@@ -16,12 +16,12 @@ type OutputBooking struct {
 	realityId    reality.Id
 	addressHash  address.Address
 	spent        bool
-	transferHash Hash
+	transferHash Id
 
 	storageKey []byte
 }
 
-func NewTransferOutputBooking(realityId reality.Id, addressHash address.Address, spent bool, transferHash Hash) (result *OutputBooking) {
+func NewTransferOutputBooking(realityId reality.Id, addressHash address.Address, spent bool, transferHash Id) (result *OutputBooking) {
 	result = &OutputBooking{
 		realityId:    realityId,
 		addressHash:  addressHash,
@@ -46,7 +46,7 @@ func (booking *OutputBooking) IsSpent() bool {
 	return booking.spent
 }
 
-func (booking *OutputBooking) GetTransferHash() Hash {
+func (booking *OutputBooking) GetTransferHash() Id {
 	return booking.transferHash
 }
 
diff --git a/packages/ledgerstate/visualizer.go b/packages/ledgerstate/visualizer.go
index b4a086971bd1fd4498846da1d2140693e17496d8..3820a60ae0d4549354692b0e1b09ab84e660ea7f 100644
--- a/packages/ledgerstate/visualizer.go
+++ b/packages/ledgerstate/visualizer.go
@@ -11,7 +11,7 @@ import (
 	"github.com/iotaledger/hive.go/objectstorage"
 )
 
-type transferOutputId [transfer.HashLength + address.Length]byte
+type transferOutputId [transfer.IdLength + address.Length]byte
 
 type Visualizer struct {
 	ledgerState         *LedgerState
@@ -79,7 +79,7 @@ func (visualizer *Visualizer) generateTransferOutputId(transferOutput *transfer.
 	addressHash := transferOutput.GetAddressHash()
 
 	copy(result[:], transferHash[:])
-	copy(result[transfer.HashLength:], addressHash[:])
+	copy(result[transfer.IdLength:], addressHash[:])
 
 	return
 }