diff --git a/packages/binary/tangle/snapshot.go b/packages/binary/tangle/snapshot.go
new file mode 100644
index 0000000000000000000000000000000000000000..1c1d560921bfa6ebf3a55f4d0f74242afa305ec4
--- /dev/null
+++ b/packages/binary/tangle/snapshot.go
@@ -0,0 +1,11 @@
+package tangle
+
+import (
+	"github.com/iotaledger/goshimmer/packages/binary/address"
+	"github.com/iotaledger/goshimmer/packages/binary/transaction"
+	"github.com/iotaledger/goshimmer/packages/ledgerstate/coloredcoins"
+)
+
+type Snapshot struct {
+	SolidEntryPoints map[transaction.Id]map[address.Address]*coloredcoins.ColoredBalance
+}
diff --git a/packages/binary/tangle/tangle.go b/packages/binary/tangle/tangle.go
index 7827f7e1764a290f7899513c056d6671f8cdc910..0e8d062bf4e39827ae00cae600ee10657ef68176 100644
--- a/packages/binary/tangle/tangle.go
+++ b/packages/binary/tangle/tangle.go
@@ -4,9 +4,12 @@ import (
 	"container/list"
 	"time"
 
+	"github.com/iotaledger/goshimmer/packages/binary/transaction/payload/valuetransfer"
+
 	"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/data"
 	"github.com/iotaledger/goshimmer/packages/binary/transactionmetadata"
 	"github.com/iotaledger/goshimmer/packages/storageprefix"
 	"github.com/iotaledger/hive.go/async"
@@ -50,6 +53,31 @@ func New(storageId []byte) (result *Tangle) {
 	return
 }
 
+func (tangle *Tangle) LoadSnapshot(snapshot *Snapshot) {
+	fakeTransactionId := func(tx *transaction.Transaction, id transaction.Id) *transaction.Transaction {
+		fakedTransaction := transaction.FromStorage(id[:])
+		if err := fakedTransaction.UnmarshalBinary(tx.GetBytes()); err != nil {
+			panic(err)
+		}
+
+		return fakedTransaction.(*transaction.Transaction)
+	}
+
+	for transactionId, addresses := range snapshot.SolidEntryPoints {
+		if addresses == nil {
+			tangle.AttachTransaction(fakeTransactionId(transaction.New(transaction.EmptyId, transaction.EmptyId, nil, data.New(nil)), transactionId))
+		} else {
+			valueTransfer := valuetransfer.New()
+
+			for address, coloredBalance := range addresses {
+				valueTransfer.AddOutput(address, coloredBalance)
+			}
+
+			tangle.AttachTransaction(fakeTransactionId(transaction.New(transaction.EmptyId, transaction.EmptyId, nil, valueTransfer), transactionId))
+		}
+	}
+}
+
 // 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
@@ -157,7 +185,7 @@ func (tangle *Tangle) storeTransactionWorker(tx *transaction.Transaction) {
 
 	transactionId := tx.GetId()
 
-	cachedTransactionMetadata := &transactionmetadata.CachedTransactionMetadata{CachedObject: tangle.transactionMetadataStorage.Store(transactionmetadata.New(tx.GetId()))}
+	cachedTransactionMetadata := &transactionmetadata.CachedTransactionMetadata{CachedObject: tangle.transactionMetadataStorage.Store(transactionmetadata.New(transactionId))}
 	addTransactionToApprovers(transactionId, tx.GetTrunkTransactionId())
 	addTransactionToApprovers(transactionId, tx.GetBranchTransactionId())
 
diff --git a/packages/binary/transaction/id.go b/packages/binary/transaction/id.go
index 1f39029e5305d6f0c2e6f83fd39dae2d16f95312..1c0c5165f19ffa69249cdb8d99c1cd9022eaffba 100644
--- a/packages/binary/transaction/id.go
+++ b/packages/binary/transaction/id.go
@@ -1,7 +1,17 @@
 package transaction
 
+import (
+	"github.com/mr-tron/base58"
+)
+
 type Id [IdLength]byte
 
+func NewId(id []byte) (result Id) {
+	copy(result[:], id)
+
+	return
+}
+
 func (id *Id) MarshalBinary() (result []byte, err error) {
 	result = make([]byte, IdLength)
 	copy(result, id[:])
@@ -15,6 +25,10 @@ func (id *Id) UnmarshalBinary(data []byte) (err error) {
 	return
 }
 
+func (id *Id) String() string {
+	return base58.Encode(id[:])
+}
+
 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 411b2f156dccc6841a581b88e22e172c4b400229..d9fa48ceca6a0e761e25dfdef8a77ac84d04e0bc 100644
--- a/packages/binary/transaction/payload/valuetransfer/valuetransfer.go
+++ b/packages/binary/transaction/payload/valuetransfer/valuetransfer.go
@@ -79,6 +79,14 @@ func (valueTransfer *ValueTransfer) AddOutput(address address.Address, balance *
 	return valueTransfer
 }
 
+func (valueTransfer *ValueTransfer) GetOutputs() (result map[address.Address][]*coloredcoins.ColoredBalance) {
+	valueTransfer.outputsMutex.RLock()
+	result = valueTransfer.outputs
+	valueTransfer.outputsMutex.RUnlock()
+
+	return
+}
+
 func (valueTransfer *ValueTransfer) Sign(keyPair ed25119.KeyPair) *ValueTransfer {
 	payloadBytes := valueTransfer.marshalPayloadBytes()
 
diff --git a/packages/binary/transaction/transaction.go b/packages/binary/transaction/transaction.go
index 4ec441045a3151ce806ef021668da5239df8b03a..5b36af5881a2aa9e3109d498fe15b49175c61829 100644
--- a/packages/binary/transaction/transaction.go
+++ b/packages/binary/transaction/transaction.go
@@ -191,7 +191,9 @@ func (transaction *Transaction) MarshalBinary() (result []byte, err error) {
 			copy(result[offset:], transaction.branchTransactionId[:])
 			offset += IdLength
 
-			copy(result[offset:], transaction.issuer.PublicKey)
+			if transaction.issuer != nil {
+				copy(result[offset:], transaction.issuer.PublicKey)
+			}
 			offset += identity.PublicKeySize
 
 			binary.LittleEndian.PutUint32(result[offset:], transaction.payload.GetType())
@@ -202,10 +204,12 @@ func (transaction *Transaction) MarshalBinary() (result []byte, err error) {
 				offset += serializedPayloadLength
 			}
 
-			transaction.signatureMutex.Lock()
-			copy(transaction.signature[:], transaction.issuer.Sign(result[:offset]))
-			transaction.signatureMutex.Unlock()
-			copy(result[offset:], transaction.signature[:])
+			if transaction.issuer != nil {
+				transaction.signatureMutex.Lock()
+				copy(transaction.signature[:], transaction.issuer.Sign(result[:offset]))
+				transaction.signatureMutex.Unlock()
+				copy(result[offset:], transaction.signature[:])
+			}
 			// offset += identity.SignatureSize
 
 			transaction.bytes = result
diff --git a/packages/binary/valuetangle/events.go b/packages/binary/valuetangle/events.go
index 2c0352c1a74afe58d8688220e644e4886a70c8a4..5d5758f8e22da26a39c4ac10d21559c108483ce1 100644
--- a/packages/binary/valuetangle/events.go
+++ b/packages/binary/valuetangle/events.go
@@ -1,6 +1,8 @@
 package valuetangle
 
 import (
+	"github.com/iotaledger/goshimmer/packages/binary/valuetangle/model"
+	"github.com/iotaledger/goshimmer/packages/ledgerstate/transfer"
 	"github.com/iotaledger/hive.go/events"
 )
 
@@ -11,3 +13,29 @@ type Events struct {
 	MissingTransferReceived *events.Event
 	TransferRemoved         *events.Event
 }
+
+func newEvents() *Events {
+	return &Events{
+		TransferAttached:        events.NewEvent(cachedTransferEvent),
+		TransferSolid:           events.NewEvent(cachedTransferEvent),
+		TransferMissing:         events.NewEvent(transferIdEvent),
+		MissingTransferReceived: events.NewEvent(transferIdEvent),
+		TransferRemoved:         events.NewEvent(transferIdEvent),
+	}
+}
+
+func transferIdEvent(handler interface{}, params ...interface{}) {
+	missingTransactionId := params[0].(transfer.Id)
+
+	handler.(func(transfer.Id))(missingTransactionId)
+}
+
+func cachedTransferEvent(handler interface{}, params ...interface{}) {
+	cachedTransfer := params[0].(*model.CachedValueTransfer)
+	cachedTransferMetadata := params[1].(*model.CachedTransferMetadata)
+
+	cachedTransfer.RegisterConsumer()
+	cachedTransferMetadata.RegisterConsumer()
+
+	handler.(func(*model.CachedValueTransfer, *model.CachedTransferMetadata))(cachedTransfer, cachedTransferMetadata)
+}
diff --git a/packages/binary/valuetangle/model/cached_consumers.go b/packages/binary/valuetangle/model/cached_consumers.go
new file mode 100644
index 0000000000000000000000000000000000000000..c570571a73ccde07771dbd9235330b3def6b9a02
--- /dev/null
+++ b/packages/binary/valuetangle/model/cached_consumers.go
@@ -0,0 +1,21 @@
+package model
+
+import (
+	"github.com/iotaledger/hive.go/objectstorage"
+)
+
+type CachedConsumers struct {
+	*objectstorage.CachedObject
+}
+
+func (cachedObject *CachedConsumers) Unwrap() *Consumers {
+	if untypedObject := cachedObject.Get(); untypedObject == nil {
+		return nil
+	} else {
+		if typedObject := untypedObject.(*Consumers); typedObject == nil || typedObject.IsDeleted() {
+			return nil
+		} else {
+			return typedObject
+		}
+	}
+}
diff --git a/packages/binary/valuetangle/model/cached_transfermetadata.go b/packages/binary/valuetangle/model/cached_transfermetadata.go
new file mode 100644
index 0000000000000000000000000000000000000000..bce48511c388337e3cb14d8580a1e7bc2c023d42
--- /dev/null
+++ b/packages/binary/valuetangle/model/cached_transfermetadata.go
@@ -0,0 +1,21 @@
+package model
+
+import (
+	"github.com/iotaledger/hive.go/objectstorage"
+)
+
+type CachedTransferMetadata struct {
+	*objectstorage.CachedObject
+}
+
+func (cachedObject *CachedTransferMetadata) Unwrap() *TransferMetadata {
+	if untypedObject := cachedObject.Get(); untypedObject == nil {
+		return nil
+	} else {
+		if typedObject := untypedObject.(*TransferMetadata); 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
index 0d8fdeba193911223f6f0a5905fbe57d6e7e6390..7a6ade84636baf9e234857aedf7a5acf62ee3228 100644
--- a/packages/binary/valuetangle/model/cached_valuetransfer.go
+++ b/packages/binary/valuetangle/model/cached_valuetransfer.go
@@ -3,23 +3,20 @@ 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) {
+func (cachedValueTransfer *CachedValueTransfer) Unwrap() *ValueTransfer {
 	if untypedTransaction := cachedValueTransfer.Get(); untypedTransaction == nil {
-		return nil, transfer.EmptyId
+		return nil
 	} else {
 		if typeCastedTransaction := untypedTransaction.(*transaction.Transaction); typeCastedTransaction == nil || typeCastedTransaction.IsDeleted() || typeCastedTransaction.GetPayload().GetType() != valuetransfer.Type {
-			return nil, transfer.EmptyId
+			return nil
 		} else {
-			transactionId := typeCastedTransaction.GetId()
-
-			return typeCastedTransaction.GetPayload().(*valuetransfer.ValueTransfer), transfer.NewId(transactionId[:])
+			return NewValueTransfer(typeCastedTransaction)
 		}
 	}
 }
diff --git a/packages/binary/valuetangle/model/consumers.go b/packages/binary/valuetangle/model/consumers.go
index 3f8edf84f405da27fd263f424dedfa71c3a93d33..4bd073599d38c9f25aec68a176eaaed08b0e8f1e 100644
--- a/packages/binary/valuetangle/model/consumers.go
+++ b/packages/binary/valuetangle/model/consumers.go
@@ -26,15 +26,11 @@ func NewConsumers(transferId transfer.Id) *Consumers {
 
 // 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)
+func ConsumersFromStorage(id []byte) objectstorage.StorableObject {
+	result := &Consumers{}
+	copy(result.transferId[:], id)
 
-	result = &Consumers{
-		transferId: transferId,
-	}
-
-	return
+	return result
 }
 
 func (consumers *Consumers) GetTransferId() transfer.Id {
diff --git a/packages/binary/valuetangle/model/missingtransfer.go b/packages/binary/valuetangle/model/missingtransfer.go
index f9772bbf3e598d09e7837a22173db41cb85eac9d..2f58ee8f1810e7d1131d7c342262df8c6684d836 100644
--- a/packages/binary/valuetangle/model/missingtransfer.go
+++ b/packages/binary/valuetangle/model/missingtransfer.go
@@ -15,7 +15,7 @@ type MissingTransfer struct {
 	missingSince time.Time
 }
 
-func New(transferId transfer.Id) *MissingTransfer {
+func NewMissingTransfer(transferId transfer.Id) *MissingTransfer {
 	return &MissingTransfer{
 		transferId:   transferId,
 		missingSince: time.Now(),
diff --git a/packages/binary/valuetangle/model/transfermetadata.go b/packages/binary/valuetangle/model/transfermetadata.go
new file mode 100644
index 0000000000000000000000000000000000000000..1cce70cb5c00d001840ef226d08b83293e255ff9
--- /dev/null
+++ b/packages/binary/valuetangle/model/transfermetadata.go
@@ -0,0 +1,81 @@
+package model
+
+import (
+	"sync"
+	"time"
+
+	"github.com/iotaledger/goshimmer/packages/ledgerstate/transfer"
+
+	"github.com/iotaledger/hive.go/objectstorage"
+)
+
+type TransferMetadata struct {
+	objectstorage.StorableObjectFlags
+
+	transferId         transfer.Id
+	receivedTime       time.Time
+	solid              bool
+	solidificationTime time.Time
+
+	solidMutex sync.RWMutex
+}
+
+func NewTransferMetadata(transferId transfer.Id) *TransferMetadata {
+	return &TransferMetadata{
+		transferId:   transferId,
+		receivedTime: time.Now(),
+	}
+}
+
+func TransferMetadataFromStorage(id []byte) objectstorage.StorableObject {
+	result := &TransferMetadata{}
+	copy(result.transferId[:], id)
+
+	return result
+}
+
+func (transferMetadata *TransferMetadata) IsSolid() (result bool) {
+	transferMetadata.solidMutex.RLock()
+	result = transferMetadata.solid
+	transferMetadata.solidMutex.RUnlock()
+
+	return
+}
+
+func (transferMetadata *TransferMetadata) SetSolid(solid bool) (modified bool) {
+	transferMetadata.solidMutex.RLock()
+	if transferMetadata.solid != solid {
+		transferMetadata.solidMutex.RUnlock()
+
+		transferMetadata.solidMutex.Lock()
+		if transferMetadata.solid != solid {
+			transferMetadata.solid = solid
+
+			transferMetadata.SetModified()
+
+			modified = true
+		}
+		transferMetadata.solidMutex.Unlock()
+
+	} else {
+		transferMetadata.solidMutex.RUnlock()
+	}
+
+	return
+}
+
+func (transferMetadata *TransferMetadata) GetStorageKey() []byte {
+	return transferMetadata.transferId[:]
+}
+
+func (transferMetadata *TransferMetadata) Update(other objectstorage.StorableObject) {
+	panic("TransferMetadata should never be overwritten")
+}
+
+func (transferMetadata *TransferMetadata) MarshalBinary() ([]byte, error) {
+	return nil, nil
+}
+
+func (transferMetadata *TransferMetadata) UnmarshalBinary([]byte) error {
+	return nil
+}
diff --git a/packages/binary/valuetangle/model/transferoutput/transferoutput.go b/packages/binary/valuetangle/model/transferoutput/transferoutput.go
new file mode 100644
index 0000000000000000000000000000000000000000..ae825b77c3578698565211693a1adf6d7dff55f6
--- /dev/null
+++ b/packages/binary/valuetangle/model/transferoutput/transferoutput.go
@@ -0,0 +1,270 @@
+package transferoutput
+
+import (
+	"encoding/binary"
+	"sync"
+
+	"github.com/iotaledger/goshimmer/packages/binary/address"
+	"github.com/iotaledger/goshimmer/packages/binary/transaction"
+	"github.com/iotaledger/goshimmer/packages/binary/types"
+	"github.com/iotaledger/goshimmer/packages/ledgerstate/coloredcoins"
+	"github.com/iotaledger/goshimmer/packages/ledgerstate/reality"
+	"github.com/iotaledger/goshimmer/packages/stringify"
+	"github.com/iotaledger/hive.go/objectstorage"
+)
+
+type TransferOutput struct {
+	objectstorage.StorableObjectFlags
+
+	transactionId transaction.Id
+	address       address.Address
+	realityId     reality.Id
+	balances      []*coloredcoins.ColoredBalance
+	consumers     map[transaction.Id]types.Empty
+
+	realityIdMutex sync.RWMutex
+	consumersMutex sync.RWMutex
+}
+
+func NewTransferOutput(transactionId transaction.Id, address address.Address, balances ...*coloredcoins.ColoredBalance) *TransferOutput {
+	return &TransferOutput{
+		transactionId: transactionId,
+		address:       address,
+		balances:      balances,
+		realityId:     reality.EmptyId,
+		consumers:     make(map[transaction.Id]types.Empty),
+	}
+}
+
+func FromStorage(key []byte) objectstorage.StorableObject {
+	result := &TransferOutput{}
+	offset := 0
+
+	if err := result.transactionId.UnmarshalBinary(key[offset:]); err != nil {
+		panic(err)
+	}
+	offset += transaction.IdLength
+
+	if err := result.address.UnmarshalBinary(key[offset:]); err != nil {
+		panic(err)
+	}
+
+	return result
+}
+
+func (transferOutput *TransferOutput) GetTransactionId() (transactionId transaction.Id) {
+	transactionId = transferOutput.transactionId
+
+	return
+}
+
+func (transferOutput *TransferOutput) GetAddress() (address address.Address) {
+	return transferOutput.address
+}
+
+func (transferOutput *TransferOutput) GetRealityId() (realityId reality.Id) {
+	transferOutput.realityIdMutex.RLock()
+	realityId = transferOutput.realityId
+	transferOutput.realityIdMutex.RUnlock()
+
+	return
+}
+
+func (transferOutput *TransferOutput) SetRealityId(realityId reality.Id) (modified bool) {
+	transferOutput.realityIdMutex.RLock()
+	if transferOutput.realityId != realityId {
+		transferOutput.realityIdMutex.RUnlock()
+
+		transferOutput.realityIdMutex.Lock()
+		if transferOutput.realityId != realityId {
+			transferOutput.realityId = realityId
+
+			transferOutput.SetModified()
+
+			modified = true
+		}
+		transferOutput.realityIdMutex.Unlock()
+	} else {
+		transferOutput.realityIdMutex.RUnlock()
+	}
+
+	return
+}
+
+func (transferOutput *TransferOutput) GetBalances() []*coloredcoins.ColoredBalance {
+	return transferOutput.balances
+}
+
+func (transferOutput *TransferOutput) GetConsumers() (consumers map[transaction.Id]types.Empty) {
+	consumers = make(map[transaction.Id]types.Empty)
+
+	transferOutput.consumersMutex.RLock()
+	for transferHash := range transferOutput.consumers {
+		consumers[transferHash] = types.Void
+	}
+	transferOutput.consumersMutex.RUnlock()
+
+	return
+}
+
+func (transferOutput *TransferOutput) IsSpent() (result bool) {
+	transferOutput.consumersMutex.RLock()
+	result = len(transferOutput.consumers) >= 1
+	transferOutput.consumersMutex.RUnlock()
+
+	return
+}
+
+func (transferOutput *TransferOutput) AddConsumer(consumer transaction.Id) (previousConsumers map[transaction.Id]types.Empty) {
+	transferOutput.consumersMutex.RLock()
+	if _, exist := transferOutput.consumers[consumer]; !exist {
+		transferOutput.consumersMutex.RUnlock()
+
+		transferOutput.consumersMutex.Lock()
+		if _, exist := transferOutput.consumers[consumer]; !exist {
+			previousConsumers = make(map[transaction.Id]types.Empty)
+			for transactionId := range transferOutput.consumers {
+				previousConsumers[transactionId] = types.Void
+			}
+
+			transferOutput.consumers[consumer] = types.Void
+
+			transferOutput.SetModified()
+		}
+		transferOutput.consumersMutex.Unlock()
+	} else {
+		transferOutput.consumersMutex.RUnlock()
+	}
+
+	return
+}
+
+func (transferOutput *TransferOutput) String() string {
+	return stringify.Struct("TransferOutput",
+		stringify.StructField("transactionId", transferOutput.GetTransactionId().String()),
+		stringify.StructField("address", transferOutput.GetAddress().String()),
+		stringify.StructField("realityId", transferOutput.GetRealityId().String()),
+		stringify.StructField("balances", transferOutput.GetBalances()),
+		stringify.StructField("spent", len(transferOutput.GetConsumers()) >= 1),
+	)
+}
+
+func (transferOutput *TransferOutput) GetStorageKey() []byte {
+	return append(transferOutput.transactionId[:], transferOutput.address[:]...)
+}
+
+func (transferOutput *TransferOutput) Update(other objectstorage.StorableObject) {
+	panic("TransferOutput should never be overwritten / updated")
+}
+
+func (transferOutput *TransferOutput) MarshalBinary() ([]byte, error) {
+	transferOutput.realityIdMutex.RLock()
+	transferOutput.consumersMutex.RLock()
+
+	balanceCount := len(transferOutput.balances)
+	consumerCount := len(transferOutput.consumers)
+
+	result := make([]byte, reality.IdLength+4+balanceCount*coloredcoins.BalanceLength+4+consumerCount*transaction.IdLength)
+	offset := 0
+
+	copy(result[0:], transferOutput.realityId[:])
+	offset += reality.IdLength
+
+	binary.LittleEndian.PutUint32(result[offset:], uint32(balanceCount))
+	offset += 4
+
+	for i := 0; i < balanceCount; i++ {
+		if marshaledColoredBalance, err := transferOutput.balances[i].MarshalBinary(); err != nil {
+			return nil, err
+		} else {
+			copy(result[offset:], marshaledColoredBalance)
+			offset += coloredcoins.BalanceLength
+		}
+	}
+
+	binary.LittleEndian.PutUint32(result[offset:], uint32(consumerCount))
+	offset += 4
+
+	for transactionId := range transferOutput.consumers {
+		copy(result[offset:], transactionId[:])
+		offset += transaction.IdLength
+	}
+
+	transferOutput.consumersMutex.RUnlock()
+	transferOutput.realityIdMutex.RUnlock()
+
+	return result, nil
+}
+
+func (transferOutput *TransferOutput) UnmarshalBinary(serializedObject []byte) error {
+	offset := 0
+
+	if err := transferOutput.realityId.UnmarshalBinary(serializedObject[offset:]); err != nil {
+		return err
+	}
+	offset += reality.IdLength
+
+	if balances, err := transferOutput.unmarshalBalances(serializedObject, &offset); err != nil {
+		return err
+	} else {
+		transferOutput.balances = balances
+	}
+
+	if consumers, err := transferOutput.unmarshalConsumers(serializedObject, &offset); err != nil {
+		return err
+	} else {
+		transferOutput.consumers = consumers
+	}
+
+	return nil
+}
+
+func (transferOutput *TransferOutput) unmarshalBalances(serializedBalances []byte, offset *int) ([]*coloredcoins.ColoredBalance, error) {
+	balanceCount := int(binary.LittleEndian.Uint32(serializedBalances))
+	*offset += 4
+
+	balances := make([]*coloredcoins.ColoredBalance, balanceCount)
+	for i := 0; i < balanceCount; i++ {
+		coloredBalance := coloredcoins.ColoredBalance{}
+		if err := coloredBalance.UnmarshalBinary(serializedBalances[4+i*coloredcoins.BalanceLength:]); err != nil {
+			return nil, err
+		}
+		*offset += coloredcoins.BalanceLength
+
+		balances[i] = &coloredBalance
+	}
+
+	return balances, nil
+}
+
+func (transferOutput *TransferOutput) unmarshalConsumers(serializedConsumers []byte, offset *int) (map[transaction.Id]types.Empty, error) {
+	consumerCount := int(binary.LittleEndian.Uint32(serializedConsumers[*offset:]))
+	*offset += 4
+
+	consumers := make(map[transaction.Id]types.Empty, consumerCount)
+	for i := 0; i < consumerCount; i++ {
+		var transactionId transaction.Id
+		if err := transactionId.UnmarshalBinary(serializedConsumers[*offset:]); err != nil {
+			return nil, err
+		}
+		*offset += transaction.IdLength
+	}
+
+	return consumers, nil
+}
+
+type CachedTransferOutput struct {
+	*objectstorage.CachedObject
+}
+
+func (cachedObject *CachedTransferOutput) Unwrap() *TransferOutput {
+	if untypedObject := cachedObject.Get(); untypedObject == nil {
+		return nil
+	} else {
+		if typedObject := untypedObject.(*TransferOutput); typedObject == nil || typedObject.IsDeleted() {
+			return nil
+		} else {
+			return typedObject
+		}
+	}
+}
diff --git a/packages/binary/valuetangle/model/valuetransfer.go b/packages/binary/valuetangle/model/valuetransfer.go
new file mode 100644
index 0000000000000000000000000000000000000000..fa614d48361b3a6b4d5299bd41d86bc0aad34d97
--- /dev/null
+++ b/packages/binary/valuetangle/model/valuetransfer.go
@@ -0,0 +1,64 @@
+package model
+
+import (
+	"sync"
+
+	"github.com/iotaledger/goshimmer/packages/binary/address"
+	"github.com/iotaledger/goshimmer/packages/binary/transaction"
+	"github.com/iotaledger/goshimmer/packages/binary/transaction/payload/valuetransfer"
+	"github.com/iotaledger/goshimmer/packages/binary/types"
+	"github.com/iotaledger/goshimmer/packages/ledgerstate/transfer"
+)
+
+type ValueTransfer struct {
+	*transaction.Transaction
+	*valuetransfer.ValueTransfer
+
+	inputs      map[transfer.Id]map[address.Address]types.Empty
+	inputsMutex sync.RWMutex
+}
+
+func NewValueTransfer(transaction *transaction.Transaction) *ValueTransfer {
+	return &ValueTransfer{
+		Transaction:   transaction,
+		ValueTransfer: transaction.GetPayload().(*valuetransfer.ValueTransfer),
+	}
+}
+
+func (valueTransfer *ValueTransfer) GetId() transfer.Id {
+	transactionId := valueTransfer.Transaction.GetId()
+
+	return transfer.NewId(transactionId[:])
+}
+
+func (valueTransfer *ValueTransfer) GetInputs() (result map[transfer.Id]map[address.Address]types.Empty) {
+	valueTransfer.inputsMutex.RLock()
+	if valueTransfer.inputs == nil {
+		valueTransfer.inputsMutex.RUnlock()
+
+		valueTransfer.inputsMutex.Lock()
+		if valueTransfer.inputs == nil {
+			result = make(map[transfer.Id]map[address.Address]types.Empty)
+
+			for _, transferOutputReference := range valueTransfer.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
+			}
+
+			valueTransfer.inputs = result
+		} else {
+			result = valueTransfer.inputs
+		}
+		valueTransfer.inputsMutex.Unlock()
+	} else {
+		result = valueTransfer.inputs
+	}
+
+	return
+}
diff --git a/packages/binary/valuetangle/valuetangle.go b/packages/binary/valuetangle/valuetangle.go
index e5b107d0025ea7514f60866f864ca461c02fd6a1..e8e16a9a9d3b8da0a52e32c5f448fca81999e14f 100644
--- a/packages/binary/valuetangle/valuetangle.go
+++ b/packages/binary/valuetangle/valuetangle.go
@@ -2,15 +2,19 @@ package valuetangle
 
 import (
 	"container/list"
+	"fmt"
+	"time"
 
 	"github.com/iotaledger/goshimmer/packages/binary/address"
+
+	"github.com/iotaledger/goshimmer/packages/binary/types"
+
+	"github.com/iotaledger/goshimmer/packages/storageprefix"
+
 	"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"
@@ -18,7 +22,12 @@ import (
 	"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
+const (
+	MaxMissingTimeBeforeCleanup = 30 * time.Second
+	MissingCheckInterval        = 5 * time.Second
+)
+
+// 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
@@ -29,7 +38,7 @@ type ValueTangle struct {
 
 	storeTransactionsWorkerPool async.WorkerPool
 	solidifierWorkerPool        async.WorkerPool
-	// cleanupWorkerPool           async.WorkerPool
+	cleanupWorkerPool           async.WorkerPool
 
 	Events Events
 }
@@ -37,8 +46,16 @@ type ValueTangle struct {
 func New(tangle *tangle.Tangle) (valueTangle *ValueTangle) {
 	valueTangle = &ValueTangle{
 		tangle: tangle,
+
+		transferMetadataStorage: objectstorage.New(append(tangle.GetStorageId(), storageprefix.ValueTangleTransferMetadata...), model.TransferMetadataFromStorage),
+		consumersStorage:        objectstorage.New(append(tangle.GetStorageId(), storageprefix.ValueTangleConsumers...), model.ConsumersFromStorage),
+		missingTransferStorage:  objectstorage.New(append(tangle.GetStorageId(), storageprefix.TangleMissingTransaction...), model.MissingTransferFromStorage),
+
+		Events: *newEvents(),
 	}
 
+	valueTangle.solidifierWorkerPool.Tune(1024)
+
 	tangle.Events.TransactionSolid.Attach(events.NewClosure(valueTangle.attachTransaction))
 	tangle.Events.TransactionRemoved.Attach(events.NewClosure(valueTangle.deleteTransfer))
 
@@ -51,36 +68,60 @@ func (valueTangle *ValueTangle) attachTransaction(cachedTransaction *transaction
 	})
 }
 
-// Retrieves a transaction from the tangle.
-func (valueTangle *ValueTangle) GetTransfer(transactionId transaction.Id) *model.CachedValueTransfer {
-	cachedTransaction := valueTangle.tangle.GetTransaction(transactionId)
+// Retrieves a transfer from the tangle.
+func (valueTangle *ValueTangle) GetTransfer(transferId transfer.Id) *model.CachedValueTransfer {
+	cachedTransaction := valueTangle.tangle.GetTransaction(transaction.NewId(transferId[:]))
 
-	// return an empty result if the transaction is no value transaction
+	// return an empty result if the transfer is no value transfer
 	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: &transaction.CachedTransaction{CachedObject: objectstorage.NewEmptyCachedObject(transferId[:])}}
 	}
 
 	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[:])}
+// Retrieves the metadata of a transfer from the tangle.
+func (valueTangle *ValueTangle) GetTransferMetadata(transferId transfer.Id) *model.CachedTransferMetadata {
+	return &model.CachedTransferMetadata{CachedObject: valueTangle.transferMetadataStorage.Load(transferId[:])}
+}
+
+// Retrieves the approvers of a transfer from the tangle.
+func (valueTangle *ValueTangle) GetConsumers(transferId transfer.Id) *model.CachedConsumers {
+	return &model.CachedConsumers{CachedObject: valueTangle.consumersStorage.Load(transferId[:])}
 }
 
-func (valueTangle *ValueTangle) deleteTransfer(transactionId transaction.Id) {
+func (valueTangle *ValueTangle) deleteTransfer(transferIf transfer.Id) {
 
 }
 
-// Marks the tangle as stopped, so it will not accept any new transactions (waits for all backgroundTasks to finish.
+// Marks the tangle as stopped, so it will not accept any new transfers (waits for all backgroundTasks to finish.
 func (valueTangle *ValueTangle) Shutdown() *ValueTangle {
+	valueTangle.tangle.Shutdown()
+
 	valueTangle.storeTransactionsWorkerPool.ShutdownGracefully()
+	valueTangle.solidifierWorkerPool.ShutdownGracefully()
+	valueTangle.cleanupWorkerPool.ShutdownGracefully()
 
 	return valueTangle
 }
 
+// Resets the database and deletes all objects (good for testing or "node resets").
+func (valueTangle *ValueTangle) Prune() error {
+	for _, storage := range []*objectstorage.ObjectStorage{
+		valueTangle.transferMetadataStorage,
+		valueTangle.consumersStorage,
+		valueTangle.missingTransferStorage,
+	} {
+		if err := storage.Prune(); err != nil {
+			return err
+		}
+	}
+
+	return nil
+}
+
 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 {
@@ -107,113 +148,129 @@ func (valueTangle *ValueTangle) storeTransactionWorker(cachedTx *transaction.Cac
 
 	cachedValueTransfer := &model.CachedValueTransfer{CachedTransaction: cachedTx}
 
-	valueTransfer, transferId := cachedValueTransfer.Unwrap()
+	valueTransfer := cachedValueTransfer.Unwrap()
 	if valueTransfer == nil {
 		cachedValueTransfer.Release()
 
 		return
 	}
 
-	for transferId := range valueTangle.getInputsMap(valueTransfer) {
-		addTransferToConsumers(transferId, transferId)
+	transferId := valueTransfer.GetId()
+
+	cachedTransferMetadata := &model.CachedTransferMetadata{CachedObject: valueTangle.transferMetadataStorage.Store(model.NewTransferMetadata(transferId))}
+	for referencedTransferId := range valueTransfer.GetInputs() {
+		addTransferToConsumers(transferId, referencedTransferId)
 	}
 
 	if valueTangle.missingTransferStorage.DeleteIfPresent(transferId[:]) {
 		valueTangle.Events.MissingTransferReceived.Trigger(transferId)
 	}
 
-	valueTangle.Events.TransferAttached.Trigger(cachedValueTransfer)
+	valueTangle.Events.TransferAttached.Trigger(cachedValueTransfer, cachedTransferMetadata)
 
 	valueTangle.solidifierWorkerPool.Submit(func() {
-		valueTangle.solidifyTransferWorker(cachedValueTransfer, transferId)
+		valueTangle.solidifyTransferWorker(cachedValueTransfer, cachedTransferMetadata)
 	})
 }
 
-// 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 {
+// Worker that solidifies the transfers (recursively from past to present).
+func (valueTangle *ValueTangle) solidifyTransferWorker(cachedValueTransfer *model.CachedValueTransfer, cachedTransferMetadata *model.CachedTransferMetadata) {
+	areInputsSolid := func(transferId transfer.Id, addresses map[address.Address]types.Empty) bool {
+		transferMetadataCached := valueTangle.GetTransferMetadata(transferId)
+		if transferMetadata := transferMetadataCached.Unwrap(); transferMetadata == 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 {
+			// if transfer is missing and was not reported as missing, yet
+			if cachedMissingTransfer, missingTransactionStored := valueTangle.missingTransferStorage.StoreIfAbsent(transferId[:], model.NewMissingTransfer(transferId)); missingTransactionStored {
 				cachedMissingTransfer.Consume(func(object objectstorage.StorableObject) {
-					valueTangle.monitorMissingTransactionWorker(object.(*missingtransaction.MissingTransaction).GetTransactionId())
+					valueTangle.monitorMissingTransactionWorker(object.(*model.MissingTransfer).GetTransferId())
 				})
 			}
 
 			return false
-		} else if !transactionMetadata.IsSolid() {
+		} else if !transferMetadata.IsSolid() {
 			transferMetadataCached.Release()
 
 			return false
 		}
 		transferMetadataCached.Release()
 
+		cachedTransfer := valueTangle.GetTransfer(transferId)
+		if valueTransfer := cachedTransfer.Unwrap(); valueTransfer == nil {
+			cachedTransfer.Release()
+
+			return false
+		} else {
+			outputs := valueTransfer.GetOutputs()
+			for address := range outputs {
+				if _, addressExists := outputs[address]; !addressExists {
+					cachedTransfer.Release()
+
+					fmt.Println("INVALID TX DETECTED")
+
+					return false
+				}
+			}
+		}
+		cachedTransfer.Release()
+
 		return true
 	}
 
-	isTransactionSolid := func(transaction *transaction.Transaction, transactionMetadata *transactionmetadata.TransactionMetadata) bool {
-		if transaction == nil || transaction.IsDeleted() {
+	isTransferSolid := func(transfer *model.ValueTransfer, transferMetadata *model.TransferMetadata) bool {
+		if transfer == nil || transfer.IsDeleted() {
 			return false
 		}
 
-		if transactionMetadata == nil || transactionMetadata.IsDeleted() {
+		if transferMetadata == nil || transferMetadata.IsDeleted() {
 			return false
 		}
 
-		if transactionMetadata.IsSolid() {
+		if transferMetadata.IsSolid() {
 			return true
 		}
 
 		// 1. check tangle solidity
-		isTrunkSolid := isTransferMarkedAsSolid(transaction.GetTrunkTransactionId())
-		isBranchSolid := isTransferMarkedAsSolid(transaction.GetBranchTransactionId())
-		if isTrunkSolid && isBranchSolid {
-			// 2. check payload solidity
-			return true
+		inputsSolid := true
+		for inputTransferId, addresses := range transfer.GetInputs() {
+			inputsSolid = inputsSolid && areInputsSolid(inputTransferId, addresses)
 		}
 
-		return false
+		return inputsSolid
 	}
 
-	popElementsFromStack := func(stack *list.List) (*transaction.CachedTransaction, *transactionmetadata.CachedTransactionMetadata) {
+	popElementsFromStack := func(stack *list.List) (*model.CachedValueTransfer, *model.CachedTransferMetadata) {
 		currentSolidificationEntry := stack.Front()
-		currentCachedTransaction := currentSolidificationEntry.Value.([2]interface{})[0]
-		currentCachedTransactionMetadata := currentSolidificationEntry.Value.([2]interface{})[1]
+		currentCachedTransfer := currentSolidificationEntry.Value.([2]interface{})[0]
+		currentCachedTransferMetadata := currentSolidificationEntry.Value.([2]interface{})[1]
 		stack.Remove(currentSolidificationEntry)
 
-		return currentCachedTransaction.(*transaction.CachedTransaction), currentCachedTransactionMetadata.(*transactionmetadata.CachedTransactionMetadata)
+		return currentCachedTransfer.(*model.CachedValueTransfer), currentCachedTransferMetadata.(*model.CachedTransferMetadata)
 	}
 
 	// initialize the stack
 	solidificationStack := list.New()
-	solidificationStack.PushBack([2]interface{}{cachedValueTransfer, transferId})
+	solidificationStack.PushBack([2]interface{}{cachedValueTransfer, cachedTransferMetadata})
 
-	// process transactions that are supposed to be checked for solidity recursively
+	// process transfers that are supposed to be checked for solidity recursively
 	for solidificationStack.Len() > 0 {
-		currentCachedTransaction, currentCachedTransactionMetadata := popElementsFromStack(solidificationStack)
+		currentCachedTransfer, currentCachedTransferMetadata := popElementsFromStack(solidificationStack)
 
-		currentTransaction := currentCachedTransaction.Unwrap()
-		currentTransactionMetadata := currentCachedTransactionMetadata.Unwrap()
-		if currentTransaction == nil || currentTransactionMetadata == nil {
-			currentCachedTransaction.Release()
-			currentCachedTransactionMetadata.Release()
+		currentTransfer := currentCachedTransfer.Unwrap()
+		currentTransferMetadata := currentCachedTransferMetadata.Unwrap()
+		if currentTransfer == nil || currentTransferMetadata == nil {
+			currentCachedTransfer.Release()
+			currentCachedTransferMetadata.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)
+		// if current transfer is solid and was not marked as solid before: mark as solid and propagate
+		if isTransferSolid(currentTransfer, currentTransferMetadata) && currentTransferMetadata.SetSolid(true) {
+			valueTangle.Events.TransferSolid.Trigger(currentCachedTransfer, currentCachedTransferMetadata)
 
-			valueTangle.GetConsumers(currentTransaction.GetId()).Consume(func(object objectstorage.StorableObject) {
-				for approverTransactionId := range object.(*approvers.Approvers).Get() {
+			valueTangle.GetConsumers(currentTransfer.GetId()).Consume(func(object objectstorage.StorableObject) {
+				for approverTransactionId := range object.(*model.Consumers).Get() {
 					solidificationStack.PushBack([2]interface{}{
 						valueTangle.GetTransfer(approverTransactionId),
 						valueTangle.GetTransferMetadata(approverTransactionId),
@@ -223,24 +280,50 @@ func (valueTangle *ValueTangle) solidifyTransferWorker(cachedValueTransfer *mode
 		}
 
 		// release cached results
-		currentCachedTransaction.Release()
-		currentCachedTransactionMetadata.Release()
+		currentCachedTransfer.Release()
+		currentCachedTransferMetadata.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)
+// Worker that Monitors the missing transfers (by scheduling regular checks).
+func (valueTangle *ValueTangle) monitorMissingTransactionWorker(transferId transfer.Id) {
+	var scheduleNextMissingCheck func(transferId transfer.Id)
+	scheduleNextMissingCheck = func(transferId transfer.Id) {
+		time.AfterFunc(MissingCheckInterval, func() {
+			valueTangle.missingTransferStorage.Load(transferId[:]).Consume(func(object objectstorage.StorableObject) {
+				missingTransfer := object.(*model.MissingTransfer)
 
-	for _, transferOutputReference := range valueTransfer.GetInputs() {
-		addressMap, addressMapExists := result[transferOutputReference.GetTransferHash()]
-		if !addressMapExists {
-			addressMap = make(map[address.Address]types.Empty)
+				if time.Since(missingTransfer.GetMissingSince()) >= MaxMissingTimeBeforeCleanup {
+					valueTangle.cleanupWorkerPool.Submit(func() { valueTangle.cleanupWorker(missingTransfer.GetTransferId()) })
+				} else {
+					valueTangle.Events.TransferMissing.Trigger(transferId)
 
-			result[transferOutputReference.GetTransferHash()] = addressMap
-		}
-
-		addressMap[transferOutputReference.GetAddress()] = types.Void
+					scheduleNextMissingCheck(transferId)
+				}
+			})
+		})
 	}
+	valueTangle.Events.TransferMissing.Trigger(transferId)
 
-	return
+	scheduleNextMissingCheck(transferId)
+}
+
+// Worker that recursively cleans up the approvers of a unsolidifiable missing transfer.
+func (valueTangle *ValueTangle) cleanupWorker(transferId transfer.Id) {
+	cleanupStack := list.New()
+	cleanupStack.PushBack(transferId)
+
+	for cleanupStack.Len() >= 1 {
+		currentStackEntry := cleanupStack.Front()
+		currentTransferId := currentStackEntry.Value.(transfer.Id)
+		cleanupStack.Remove(currentStackEntry)
+
+		valueTangle.GetConsumers(currentTransferId).Consume(func(object objectstorage.StorableObject) {
+			for approverTransactionId := range object.(*model.Consumers).Get() {
+				valueTangle.deleteTransfer(currentTransferId)
+
+				cleanupStack.PushBack(approverTransactionId)
+			}
+		})
+	}
 }
diff --git a/packages/binary/valuetangle/valuetangle_test.go b/packages/binary/valuetangle/valuetangle_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..d4e954bbc2e457fe9e44d37aaa9b056bbb0eef73
--- /dev/null
+++ b/packages/binary/valuetangle/valuetangle_test.go
@@ -0,0 +1,101 @@
+package valuetangle
+
+import (
+	"fmt"
+	"testing"
+	"time"
+
+	"github.com/mr-tron/base58"
+
+	"github.com/iotaledger/goshimmer/packages/binary/address"
+	"github.com/iotaledger/goshimmer/packages/binary/transactionmetadata"
+	"github.com/iotaledger/goshimmer/packages/ledgerstate/coloredcoins"
+	"github.com/iotaledger/goshimmer/packages/ledgerstate/transfer"
+
+	"github.com/iotaledger/goshimmer/packages/binary/signature/ed25119"
+
+	"github.com/iotaledger/goshimmer/packages/binary/transaction/payload/valuetransfer"
+
+	"github.com/iotaledger/goshimmer/packages/binary/identity"
+	"github.com/iotaledger/goshimmer/packages/binary/transaction"
+
+	"github.com/iotaledger/goshimmer/packages/binary/tangle"
+	"github.com/iotaledger/goshimmer/packages/binary/valuetangle/model"
+	"github.com/iotaledger/hive.go/events"
+)
+
+var localSnapshot *tangle.Snapshot
+var keyPairOfAddress1 = ed25119.GenerateKeyPair()
+var keyPairOfAddress2 = ed25119.GenerateKeyPair()
+
+func getLocalSnapshot() *tangle.Snapshot {
+	if localSnapshot == nil {
+		localSnapshot = &tangle.Snapshot{
+			SolidEntryPoints: map[transaction.Id]map[address.Address]*coloredcoins.ColoredBalance{
+				transaction.NewId([]byte("tx0")): nil,
+				transaction.NewId([]byte("tx1")): {
+					address.FromPublicKey(keyPairOfAddress1.PublicKey): coloredcoins.NewColoredBalance(coloredcoins.NewColor("IOTA"), 1337),
+					address.FromPublicKey(keyPairOfAddress2.PublicKey): coloredcoins.NewColoredBalance(coloredcoins.NewColor("IOTA"), 1337),
+				},
+				transaction.NewId([]byte("tx2")): {
+					address.New([]byte("address3")): coloredcoins.NewColoredBalance(coloredcoins.NewColor("IOTA"), 1337),
+					address.New([]byte("address4")): coloredcoins.NewColoredBalance(coloredcoins.NewColor("IOTA"), 1337),
+				},
+			},
+		}
+	}
+
+	return localSnapshot
+}
+
+func TestValueTangle(t *testing.T) {
+	transactionTangle := tangle.New([]byte("testtangle"))
+	transactionTangle.Prune()
+
+	valueTangle := New(transactionTangle)
+	valueTangle.Prune()
+
+	transactionTangle.Events.TransactionAttached.Attach(events.NewClosure(func(cachedTransaction *transaction.CachedTransaction, cachedTransactionMetadata *transactionmetadata.CachedTransactionMetadata) {
+		transactionId := cachedTransaction.Unwrap().GetId()
+
+		if cachedTransaction.Unwrap().GetPayload().GetType() != valuetransfer.Type {
+			fmt.Println("[TRANSACTION TANGLE] Data transaction attached: ", base58.Encode(transactionId[:]))
+		} else {
+			fmt.Println("[TRANSACTION TANGLE] Value Transaction attached:", base58.Encode(transactionId[:]))
+		}
+	}))
+
+	transactionTangle.Events.TransactionSolid.Attach(events.NewClosure(func(cachedTransaction *transaction.CachedTransaction, cachedTransactionMetadata *transactionmetadata.CachedTransactionMetadata) {
+		transactionId := cachedTransaction.Unwrap().GetId()
+
+		if cachedTransaction.Unwrap().GetPayload().GetType() != valuetransfer.Type {
+			fmt.Println("[TRANSACTION TANGLE] Data transaction solid:    ", base58.Encode(transactionId[:]))
+		} else {
+			fmt.Println("[TRANSACTION TANGLE] Value Transaction solid:   ", base58.Encode(transactionId[:]))
+		}
+	}))
+
+	valueTangle.Events.TransferAttached.Attach(events.NewClosure(func(cachedValueTransfer *model.CachedValueTransfer, cachedTransferMetadata *model.CachedTransferMetadata) {
+		transactionId := cachedValueTransfer.Unwrap().GetId()
+
+		fmt.Println("[VALUE TANGLE]       Value Transaction attached:", base58.Encode(transactionId[:]))
+	}))
+	valueTangle.Events.TransferSolid.Attach(events.NewClosure(func(cachedValueTransfer *model.CachedValueTransfer, cachedTransferMetadata *model.CachedTransferMetadata) {
+		transactionId := cachedValueTransfer.Unwrap().GetId()
+
+		fmt.Println("[VALUE TANGLE]       Value Transaction solid:   ", base58.Encode(transactionId[:]))
+	}))
+
+	transactionTangle.LoadSnapshot(getLocalSnapshot())
+
+	myIdentity := identity.Generate()
+
+	transactionTangle.AttachTransaction(transaction.New(transaction.EmptyId, transaction.EmptyId, myIdentity, valuetransfer.New().
+		AddInput(transfer.NewId([]byte("tx1")), address.FromPublicKey(keyPairOfAddress1.PublicKey)).
+		AddOutput(address.FromPublicKey(keyPairOfAddress2.PublicKey), coloredcoins.NewColoredBalance(coloredcoins.NewColor("IOTA"), 12)).
+		Sign(keyPairOfAddress1)))
+
+	time.Sleep(1 * time.Second)
+
+	valueTangle.Shutdown()
+}
diff --git a/packages/ledgerstate/transfer/id.go b/packages/ledgerstate/transfer/id.go
index 8431ea04dc7358f49bb1e49727f30c9321789461..124439db880248516d0a0eefc0e74f18a53a90e1 100644
--- a/packages/ledgerstate/transfer/id.go
+++ b/packages/ledgerstate/transfer/id.go
@@ -44,4 +44,4 @@ func (transferId Id) String() string {
 
 var EmptyId = Id{}
 
-const IdLength = 32
+const IdLength = 64
diff --git a/packages/storageprefix/storageprefix.go b/packages/storageprefix/storageprefix.go
index 45bdcbfeab14e5b0577c4a04fa508233690223bb..c491a24e5d6adfeae617211e290b148a1f5738b3 100644
--- a/packages/storageprefix/storageprefix.go
+++ b/packages/storageprefix/storageprefix.go
@@ -6,6 +6,10 @@ var (
 	TangleApprovers           = []byte{1}
 	TangleMissingTransaction  = []byte{7}
 
+	ValueTangleTransferMetadata = []byte{8}
+	ValueTangleConsumers        = []byte{9}
+	ValueTangleMissingTransfers = []byte{10}
+
 	LedgerStateTransferOutput        = []byte{2}
 	LedgerStateTransferOutputBooking = []byte{3}
 	LedgerStateReality               = []byte{4}