diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000000000000000000000000000000000000..efa49079f70407b247a5f9037300b3ca6818145b
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,18 @@
+# Binaries for programs and plugins
+*.exe
+*.exe~
+*.dll
+*.so
+*.dylib
+
+# Test binary, build with `go test -c`
+*.test
+
+# Output of the go coverage tool, specifically when used with LiteIDE
+*.out
+
+# Project files
+.idea
+
+# Database directory
+mainnetdb/
\ No newline at end of file
diff --git a/packages/accountability/accountability.go b/packages/accountability/accountability.go
new file mode 100644
index 0000000000000000000000000000000000000000..e581796192e6fd2d5ed4f3462a2f678863cb9886
--- /dev/null
+++ b/packages/accountability/accountability.go
@@ -0,0 +1,49 @@
+package accountability
+
+import (
+    "github.com/dgraph-io/badger"
+    "github.com/iotaledger/goshimmer/packages/settings"
+    "github.com/iotaledger/goshimmer/packages/identity"
+)
+
+var OWN_ID *identity.Identity
+
+func generateNewIdentity() *identity.Identity {
+    newIdentity := identity.GenerateRandomIdentity()
+
+    if err := settings.Set([]byte("ACCOUNTABILITY_PUBLIC_KEY"), newIdentity.PublicKey); err != nil {
+        panic(err)
+    }
+
+    if err := settings.Set([]byte("ACCOUNTABILITY_PRIVATE_KEY"), newIdentity.PrivateKey); err != nil {
+        panic(err)
+    }
+
+    return newIdentity
+}
+
+func getIdentity() *identity.Identity {
+    publicKey, err := settings.Get([]byte("ACCOUNTABILITY_PUBLIC_KEY"))
+    if err != nil {
+        if err == badger.ErrKeyNotFound {
+            return generateNewIdentity()
+        } else {
+            panic(err)
+        }
+    }
+
+    privateKey, err := settings.Get([]byte("ACCOUNTABILITY_PRIVATE_KEY"))
+    if err != nil {
+        if err == badger.ErrKeyNotFound {
+            return generateNewIdentity()
+        } else {
+            panic(err)
+        }
+    }
+
+    return identity.NewIdentity(publicKey, privateKey)
+}
+
+func init() {
+    OWN_ID = getIdentity()
+}
\ No newline at end of file
diff --git a/packages/daemon/daemon.go b/packages/daemon/daemon.go
new file mode 100644
index 0000000000000000000000000000000000000000..46f6822e9961861b0d41c039a4997606ddfd2fcc
--- /dev/null
+++ b/packages/daemon/daemon.go
@@ -0,0 +1,98 @@
+package daemon
+
+import (
+    "sync"
+)
+
+var (
+    running              bool
+    runOnce              sync.Once
+    shutdownOnce         sync.Once
+    wg                   sync.WaitGroup
+    installWG            sync.WaitGroup
+    ShutdownSignal       = make(chan int, 1)
+    backgroundWorkers    = make([]func(), 0)
+    backgroundWorkerChan = make(chan func(), 10)
+)
+
+var Events = daemonEvents{
+    Run: &callbackEvent{
+        callbacks: map[uintptr]Callback{},
+    },
+    Shutdown: &callbackEvent{
+        callbacks: map[uintptr]Callback{},
+    },
+}
+
+func init() {
+    shutdownOnce.Do(func() {})
+
+    go func() {
+        for {
+            backgroundWorker := <- backgroundWorkerChan
+
+            backgroundWorkers = append(backgroundWorkers, backgroundWorker)
+
+            installWG.Done()
+
+            if IsRunning() {
+                runBackgroundWorker(backgroundWorker)
+            }
+        }
+    }()
+}
+
+func runBackgroundWorker(backgroundWorker func()) {
+    wg.Add(1)
+
+    go func() {
+        backgroundWorker()
+
+        wg.Done()
+    }()
+}
+
+func runBackgroundWorkers() {
+    for _, backgroundWorker := range backgroundWorkers {
+        runBackgroundWorker(backgroundWorker)
+    }
+}
+
+func BackgroundWorker(handler func()) {
+    installWG.Add(1)
+
+    backgroundWorkerChan <- handler
+}
+
+func Run() {
+    runOnce.Do(func() {
+        installWG.Wait()
+
+        ShutdownSignal = make(chan int, 1)
+        running = true
+
+        Events.Run.Trigger()
+
+        runBackgroundWorkers()
+
+        shutdownOnce = sync.Once{}
+    })
+
+    wg.Wait()
+}
+
+func Shutdown() {
+    shutdownOnce.Do(func() {
+        close(ShutdownSignal)
+
+        running = false
+
+        Events.Shutdown.Trigger()
+
+        runOnce = sync.Once{}
+    })
+}
+
+func IsRunning() bool {
+    return running
+}
diff --git a/packages/daemon/events.go b/packages/daemon/events.go
new file mode 100644
index 0000000000000000000000000000000000000000..b8cef7a985b6aec434f9c1442f3cd5afe488264b
--- /dev/null
+++ b/packages/daemon/events.go
@@ -0,0 +1,26 @@
+package daemon
+
+import "reflect"
+
+type daemonEvents struct {
+    Run      *callbackEvent
+    Shutdown *callbackEvent
+}
+
+type callbackEvent struct {
+    callbacks map[uintptr]Callback
+}
+
+func (this *callbackEvent) Attach(callback Callback) {
+    this.callbacks[reflect.ValueOf(callback).Pointer()] = callback
+}
+
+func (this *callbackEvent) Detach(callback Callback) {
+    delete(this.callbacks, reflect.ValueOf(callback).Pointer())
+}
+
+func (this *callbackEvent) Trigger() {
+    for _, callback := range this.callbacks {
+        callback()
+    }
+}
diff --git a/packages/daemon/types.go b/packages/daemon/types.go
new file mode 100644
index 0000000000000000000000000000000000000000..e87a1a750e3f97d9bcb11bb536a694a39ea1b016
--- /dev/null
+++ b/packages/daemon/types.go
@@ -0,0 +1,3 @@
+package daemon
+
+type Callback = func()
diff --git a/packages/database/database.go b/packages/database/database.go
new file mode 100644
index 0000000000000000000000000000000000000000..4dfb0803ef83de32fbf58e55d355ef9c8aea0064
--- /dev/null
+++ b/packages/database/database.go
@@ -0,0 +1,114 @@
+package database
+
+import (
+    "github.com/dgraph-io/badger"
+    "os"
+    "path/filepath"
+    "sync"
+)
+
+var databasesByName = make(map[string]*databaseImpl)
+var getLock sync.Mutex
+
+var ErrKeyNotFound = badger.ErrKeyNotFound
+
+type databaseImpl struct {
+    db       *badger.DB
+    name     string
+    openLock sync.Mutex
+}
+
+func Get(name string) (Database, error) {
+    getLock.Lock()
+    defer getLock.Unlock()
+
+    if database, exists := databasesByName[name]; exists {
+        return database, nil
+    }
+
+    database := &databaseImpl{
+        db:   nil,
+        name: name,
+    }
+    if err := database.Open(); err != nil {
+        return nil, err
+    }
+
+    databasesByName[name] = database
+
+    return databasesByName[name], nil
+}
+
+func (this *databaseImpl) Open() error {
+    this.openLock.Lock()
+    defer this.openLock.Unlock()
+
+    if this.db == nil {
+        directory := *DIRECTORY.Value
+
+        if _, err := os.Stat(directory); os.IsNotExist(err) {
+            if err := os.Mkdir(directory, 0700); err != nil {
+                return err
+            }
+        }
+
+        opts := badger.DefaultOptions
+        opts.Dir = directory + string(filepath.Separator) + this.name
+        opts.ValueDir = opts.Dir
+        opts.Logger = &logger{}
+        opts.Truncate = true
+
+        db, err := badger.Open(opts)
+        if err != nil {
+            return err
+        }
+        this.db = db
+    }
+
+    return nil
+}
+
+func (this *databaseImpl) Set(key []byte, value []byte) error {
+    if err := this.db.Update(func(txn *badger.Txn) error { return txn.Set(key, value) }); err != nil {
+        return err
+    }
+
+    return nil
+}
+
+func (this *databaseImpl) Get(key []byte) ([]byte, error) {
+    var result []byte = nil
+    var err error = nil
+
+    err = this.db.View(func(txn *badger.Txn) error {
+        item, err := txn.Get(key)
+        if err != nil {
+            return err
+        }
+
+        return item.Value(func(val []byte) error {
+            result = append([]byte{}, val...)
+
+            return nil
+        })
+    })
+
+    return result, err
+}
+
+func (this *databaseImpl) Close() error {
+    this.openLock.Lock()
+    defer this.openLock.Unlock()
+
+    if this.db != nil {
+        err := this.db.Close()
+
+        this.db = nil
+
+        if err != nil {
+            return err
+        }
+    }
+
+    return nil
+}
diff --git a/packages/database/interfaces.go b/packages/database/interfaces.go
new file mode 100644
index 0000000000000000000000000000000000000000..c8fbb8295a3be54c12754d81638e6c85d88aeef2
--- /dev/null
+++ b/packages/database/interfaces.go
@@ -0,0 +1,8 @@
+package database
+
+type Database interface {
+    Open() error
+    Set(key []byte, value []byte) error
+    Get(key []byte) ([]byte, error)
+    Close() error
+}
diff --git a/packages/database/logger.go b/packages/database/logger.go
new file mode 100644
index 0000000000000000000000000000000000000000..e8a4c6fda2f351a48938b6c9d7d3ba2d7136fe39
--- /dev/null
+++ b/packages/database/logger.go
@@ -0,0 +1,15 @@
+package database
+
+type logger struct {}
+
+func (this *logger) Errorf(string, ...interface{}) {
+    // disable logging
+}
+
+func (this *logger) Infof(string, ...interface{}) {
+    // disable logging
+}
+
+func (this *logger) Warningf(string, ...interface{}) {
+    // disable logging
+}
diff --git a/packages/database/parameters.go b/packages/database/parameters.go
new file mode 100644
index 0000000000000000000000000000000000000000..4e28a7793085065ebb704e03673696dad1d1477d
--- /dev/null
+++ b/packages/database/parameters.go
@@ -0,0 +1,7 @@
+package database
+
+import (
+    "github.com/iotadevelopment/go/modules/parameter"
+)
+
+var DIRECTORY = parameter.AddString("DATABASE/DIRECTORY", "mainnetdb", "path to the database folder")
diff --git a/packages/identity/constants.go b/packages/identity/constants.go
new file mode 100644
index 0000000000000000000000000000000000000000..0d7a5b986b1b57d5a3774256154d08898f7950f3
--- /dev/null
+++ b/packages/identity/constants.go
@@ -0,0 +1,8 @@
+package identity
+
+const (
+    PRIVATE_TYPE = IdentityType(0)
+    PUBLIC_TYPE  = IdentityType(1)
+
+    PUBLIC_KEY_BYTE_LENGTH = 65
+)
diff --git a/packages/identity/identity.go b/packages/identity/identity.go
new file mode 100644
index 0000000000000000000000000000000000000000..05815470eb30d85715a6e821b4235cb3b8b58581
--- /dev/null
+++ b/packages/identity/identity.go
@@ -0,0 +1,86 @@
+package identity
+
+import (
+    "crypto/ecdsa"
+    "crypto/elliptic"
+    "crypto/rand"
+    "crypto/sha256"
+    "fmt"
+    "github.com/ethereum/go-ethereum/crypto/secp256k1"
+    "github.com/iotadevelopment/consensus/crypto"
+)
+
+type Identity struct {
+    Type             IdentityType
+    Identifier       []byte
+    StringIdentifier string
+    PublicKey        []byte
+    PrivateKey       []byte
+}
+
+func NewIdentity(publicKey []byte, optionalPrivateKey ... []byte) *Identity {
+    this := &Identity{
+        Identifier: crypto.Hash20(publicKey),
+        PublicKey:  publicKey,
+    }
+
+    this.StringIdentifier = fmt.Sprintf("%x", this.Identifier)
+
+    if len(optionalPrivateKey) == 1 {
+        this.Type = PRIVATE_TYPE
+        this.PrivateKey = optionalPrivateKey[0]
+    } else {
+        this.Type = PUBLIC_TYPE
+    }
+
+    return this
+}
+
+func (this *Identity) Sign(data []byte) ([]byte, error) {
+    sha256Hasher := sha256.New()
+    sha256Hasher.Write(data)
+
+    sig, err := secp256k1.Sign(sha256Hasher.Sum(nil), this.PrivateKey)
+    if err != nil {
+        return nil, err
+    }
+
+    return sig, nil
+}
+
+func (this *Identity) VerifySignature(data []byte, signature []byte) bool {
+    sha256Hasher := sha256.New()
+    sha256Hasher.Write(data)
+
+    return secp256k1.VerifySignature(this.PublicKey, sha256Hasher.Sum(nil), signature[:64])
+}
+
+func GenerateRandomIdentity() *Identity {
+    // generate key pair
+    keyPair, err := ecdsa.GenerateKey(secp256k1.S256(), rand.Reader)
+    if err != nil {
+        panic(err)
+    }
+
+    // build public key bytes
+    publicKey := elliptic.Marshal(secp256k1.S256(), keyPair.X, keyPair.Y)
+
+    // build private key bytes
+    privkey := make([]byte, 32)
+    blob := keyPair.D.Bytes()
+    copy(privkey[32-len(blob):], blob)
+
+    return NewIdentity(publicKey, privkey)
+}
+
+func FromSignedData(data []byte, signature []byte) (*Identity, error) {
+    sha256Hasher := sha256.New()
+    sha256Hasher.Write(data)
+
+    pubKey, err := secp256k1.RecoverPubkey(sha256Hasher.Sum(nil), signature)
+    if err != nil {
+        return nil, err
+    }
+
+    return NewIdentity(pubKey), nil
+}
diff --git a/packages/identity/types.go b/packages/identity/types.go
new file mode 100644
index 0000000000000000000000000000000000000000..30565ab6786fc441931203041602e4d47b1e539f
--- /dev/null
+++ b/packages/identity/types.go
@@ -0,0 +1,3 @@
+package identity
+
+type IdentityType int
diff --git a/packages/network/interfaces.go b/packages/network/interfaces.go
new file mode 100644
index 0000000000000000000000000000000000000000..7590e112572aab36cbd741a2ed6877a19e18b116
--- /dev/null
+++ b/packages/network/interfaces.go
@@ -0,0 +1,27 @@
+package network
+
+import (
+    "net"
+    "time"
+)
+
+type Connection interface {
+    GetProtocol() string
+    GetConnection() net.Conn
+    Write(data []byte)
+    OnReceiveData(callback DataConsumer) Connection
+    OnDisconnect(callback Callback) Connection
+    OnError(callback ErrorConsumer) Connection
+    TriggerReceiveData(data []byte) Connection
+    TriggerDisconnect() Connection
+    TriggerError(err error) Connection
+    SetTimeout(duration time.Duration) Connection
+    HandleConnection()
+}
+
+type Callback func()
+
+type ErrorConsumer func(err error)
+
+type DataConsumer func(data []byte)
+
diff --git a/packages/network/parameters.go b/packages/network/parameters.go
new file mode 100644
index 0000000000000000000000000000000000000000..65200679dc22d3777e39a8b3cb80613e7cb8f41b
--- /dev/null
+++ b/packages/network/parameters.go
@@ -0,0 +1,5 @@
+package network
+
+const (
+    READ_BUFFER_SIZE = 81920
+)
diff --git a/packages/network/peer.go b/packages/network/peer.go
new file mode 100644
index 0000000000000000000000000000000000000000..7035a804371be58d3ce1da13aa7361cddcd3062d
--- /dev/null
+++ b/packages/network/peer.go
@@ -0,0 +1,118 @@
+package network
+
+import (
+    "io"
+    "net"
+    "time"
+)
+
+type peerImplementation struct {
+    timeout             time.Duration
+    protocol            string
+    conn                net.Conn
+    receiveDataHandlers []DataConsumer
+    disconnectHandlers  []Callback
+    errorHandlers       []ErrorConsumer
+}
+
+func NewPeer(protocol string, conn net.Conn) Connection {
+    this := &peerImplementation{
+        protocol:            protocol,
+        conn:                conn,
+        receiveDataHandlers: make([]DataConsumer, 0),
+        disconnectHandlers:  make([]Callback, 0),
+        errorHandlers:       make([]ErrorConsumer, 0),
+    }
+
+    return this
+}
+
+func (this *peerImplementation) SetTimeout(duration time.Duration) Connection {
+    this.timeout = duration
+
+    //this.conn.SetDeadline(time.Now().Add(this.timeout))
+
+    return this
+}
+
+func (this *peerImplementation) GetProtocol() string {
+    return this.protocol
+}
+
+func (this *peerImplementation) GetConnection() net.Conn {
+    return this.conn
+}
+
+func (this *peerImplementation) Write(data []byte) {
+    //this.conn.SetDeadline(time.Now().Add(this.timeout))
+
+    if _, err := this.conn.Write(data); err != nil {
+        this.TriggerError(err)
+    }
+}
+
+func (this *peerImplementation) OnReceiveData(callback DataConsumer) Connection {
+    this.receiveDataHandlers = append(this.receiveDataHandlers, callback)
+
+    return this
+}
+
+func (this *peerImplementation) OnDisconnect(callback Callback) Connection {
+    this.disconnectHandlers = append(this.disconnectHandlers, callback)
+
+    return this
+}
+
+func (this *peerImplementation) OnError(callback ErrorConsumer) Connection {
+    this.errorHandlers = append(this.errorHandlers, callback)
+
+    return this
+}
+
+func (this *peerImplementation) TriggerReceiveData(data []byte) Connection {
+    for _, receiveDataHandler := range this.receiveDataHandlers {
+        receiveDataHandler(data)
+    }
+
+    return this
+}
+
+func (this *peerImplementation) TriggerDisconnect() Connection {
+    for _, disconnectHandler := range this.disconnectHandlers {
+        disconnectHandler()
+    }
+
+    return this
+}
+
+func (this *peerImplementation) TriggerError(err error) Connection {
+    for _, errorHandler := range this.errorHandlers {
+        errorHandler(err)
+    }
+
+    return this
+}
+
+func (this *peerImplementation) HandleConnection() {
+    defer this.conn.Close()
+    defer this.TriggerDisconnect()
+
+    receiveBuffer := make([]byte, READ_BUFFER_SIZE)
+    for {
+        //this.conn.SetDeadline(time.Now().Add(this.timeout))
+
+        byteCount, err := this.conn.Read(receiveBuffer)
+        if err != nil {
+            if err != io.EOF {
+                this.TriggerError(err)
+            }
+
+            return
+        }
+
+        receivedData := make([]byte, byteCount)
+        copy(receivedData, receiveBuffer)
+
+        this.TriggerReceiveData(receivedData)
+    }
+}
diff --git a/packages/network/tcp/server.go b/packages/network/tcp/server.go
new file mode 100644
index 0000000000000000000000000000000000000000..51ec4bbab23d0409699941acee3061f254e6afc6
--- /dev/null
+++ b/packages/network/tcp/server.go
@@ -0,0 +1,60 @@
+package tcp
+
+import (
+    "github.com/iotaledger/goshimmer/packages/network"
+    "net"
+    "strconv"
+)
+
+type Server struct {
+    Socket net.Listener
+    Events serverEvents
+}
+
+func (this *Server) Shutdown() {
+    if this.Socket != nil {
+        socket := this.Socket
+        this.Socket = nil
+
+        socket.Close()
+    }
+}
+
+func (this *Server) Listen(port int) *Server {
+    socket, err := net.Listen("tcp4", "0.0.0.0:"+strconv.Itoa(port))
+    if err != nil {
+        this.Events.Error.Trigger(err)
+
+        return this
+    } else {
+        this.Socket = socket
+    }
+
+    this.Events.Start.Trigger()
+    defer this.Events.Shutdown.Trigger()
+
+    for this.Socket != nil {
+        if socket, err := this.Socket.Accept(); err != nil {
+            if this.Socket != nil {
+                this.Events.Error.Trigger(err)
+            }
+        } else {
+            peer := network.NewPeer("tcp", socket)
+
+            go this.Events.Connect.Trigger(peer)
+        }
+    }
+
+    return this
+}
+
+func NewServer() *Server {
+    return &Server{
+        Events: serverEvents{
+            Start:    &callbackEvent{make(map[uintptr]Callback)},
+            Shutdown: &callbackEvent{make(map[uintptr]Callback)},
+            Connect:  &peerConsumerEvent{make(map[uintptr]PeerConsumer)},
+            Error:    &errorConsumerEvent{make(map[uintptr]ErrorConsumer)},
+        },
+    }
+}
diff --git a/packages/network/tcp/server_events.go b/packages/network/tcp/server_events.go
new file mode 100644
index 0000000000000000000000000000000000000000..27a3af2666bb2f8358bbf0bd08200c33c5ab7f0a
--- /dev/null
+++ b/packages/network/tcp/server_events.go
@@ -0,0 +1,79 @@
+package tcp
+
+import (
+    "github.com/iotaledger/goshimmer/packages/network"
+    "reflect"
+)
+
+type serverEvents struct {
+    Start    *callbackEvent
+    Shutdown *callbackEvent
+    Connect  *peerConsumerEvent
+    Error    *errorConsumerEvent
+}
+
+// region callbackEvent /////////////////////////////////////////////////////////////////////////////////////////////////
+
+type callbackEvent struct {
+    callbacks map[uintptr]Callback
+}
+
+func (this *callbackEvent) Attach(callback Callback) {
+    this.callbacks[reflect.ValueOf(callback).Pointer()] = callback
+}
+
+func (this *callbackEvent) Detach(callback Callback) {
+    delete(this.callbacks, reflect.ValueOf(callback).Pointer())
+}
+
+func (this *callbackEvent) Trigger() {
+    for _, callback := range this.callbacks {
+        callback()
+    }
+}
+
+// endregion ////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+// region errorConsumerEvent ////////////////////////////////////////////////////////////////////////////////////////////
+
+type errorConsumerEvent struct {
+    callbacks map[uintptr]ErrorConsumer
+}
+
+func (this *errorConsumerEvent) Attach(callback ErrorConsumer) {
+    this.callbacks[reflect.ValueOf(callback).Pointer()] = callback
+}
+
+func (this *errorConsumerEvent) Detach(callback ErrorConsumer) {
+    delete(this.callbacks, reflect.ValueOf(callback).Pointer())
+}
+
+func (this *errorConsumerEvent) Trigger(err error) {
+    for _, callback := range this.callbacks {
+        callback(err)
+    }
+}
+
+// endregion ////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+// region peerConsumerEvent /////////////////////////////////////////////////////////////////////////////////////////////
+
+type peerConsumerEvent struct {
+    callbacks map[uintptr]PeerConsumer
+}
+
+func (this *peerConsumerEvent) Attach(callback PeerConsumer) {
+    this.callbacks[reflect.ValueOf(callback).Pointer()] = callback
+}
+
+func (this *peerConsumerEvent) Detach(callback PeerConsumer) {
+    delete(this.callbacks, reflect.ValueOf(callback).Pointer())
+}
+
+func (this *peerConsumerEvent) Trigger(peer network.Connection) {
+    for _, callback := range this.callbacks {
+        callback(peer)
+    }
+}
+
+// endregion ////////////////////////////////////////////////////////////////////////////////////////////////////////////
diff --git a/packages/network/tcp/types.go b/packages/network/tcp/types.go
new file mode 100644
index 0000000000000000000000000000000000000000..6eb9d5d16eae568e8849a5d4adba75f6016ccb88
--- /dev/null
+++ b/packages/network/tcp/types.go
@@ -0,0 +1,9 @@
+package tcp
+
+import "github.com/iotaledger/goshimmer/packages/network"
+
+type Callback = func()
+
+type ErrorConsumer = func(e error)
+
+type PeerConsumer = func(peer network.Connection)
diff --git a/packages/network/udp/events.go b/packages/network/udp/events.go
new file mode 100644
index 0000000000000000000000000000000000000000..0ce863860fa74063d4df934b8d5f67fe8ca2feb4
--- /dev/null
+++ b/packages/network/udp/events.go
@@ -0,0 +1,72 @@
+package udp
+
+import (
+    "net"
+    "reflect"
+)
+
+//region peerConsumerEvent /////////////////////////////////////////////////////////////////////////////////////////////
+
+type callbackEvent struct {
+    callbacks map[uintptr]Callback
+}
+
+func (this *callbackEvent) Attach(callback Callback) {
+    this.callbacks[reflect.ValueOf(callback).Pointer()] = callback
+}
+
+func (this *callbackEvent) Detach(callback Callback) {
+    delete(this.callbacks, reflect.ValueOf(callback).Pointer())
+}
+
+func (this *callbackEvent) Trigger() {
+    for _, callback := range this.callbacks {
+        callback()
+    }
+}
+
+//endregion ////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+//region dataConsumerEvent /////////////////////////////////////////////////////////////////////////////////////////////
+
+type dataConsumerEvent struct {
+    callbacks map[uintptr]AddressDataConsumer
+}
+
+func (this *dataConsumerEvent) Attach(callback AddressDataConsumer) {
+    this.callbacks[reflect.ValueOf(callback).Pointer()] = callback
+}
+
+func (this *dataConsumerEvent) Detach(callback AddressDataConsumer) {
+    delete(this.callbacks, reflect.ValueOf(callback).Pointer())
+}
+
+func (this *dataConsumerEvent) Trigger(addr *net.UDPAddr, data []byte) {
+    for _, callback := range this.callbacks {
+        callback(addr, data)
+    }
+}
+
+//endregion ////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+//region errorConsumerEvent ////////////////////////////////////////////////////////////////////////////////////////////
+
+type errorConsumerEvent struct {
+    callbacks map[uintptr]ErrorConsumer
+}
+
+func (this *errorConsumerEvent) Attach(callback ErrorConsumer) {
+    this.callbacks[reflect.ValueOf(callback).Pointer()] = callback
+}
+
+func (this *errorConsumerEvent) Detach(callback ErrorConsumer) {
+    delete(this.callbacks, reflect.ValueOf(callback).Pointer())
+}
+
+func (this *errorConsumerEvent) Trigger(err error) {
+    for _, callback := range this.callbacks {
+        callback(err)
+    }
+}
+
+//endregion ////////////////////////////////////////////////////////////////////////////////////////////////////////////
diff --git a/packages/network/udp/server.go b/packages/network/udp/server.go
new file mode 100644
index 0000000000000000000000000000000000000000..08f265ceac5d9d00f192033947458ece3a037670
--- /dev/null
+++ b/packages/network/udp/server.go
@@ -0,0 +1,64 @@
+package udp
+
+import (
+    "net"
+)
+
+type serverEvents struct {
+    Start       *callbackEvent
+    Shutdown    *callbackEvent
+    ReceiveData *dataConsumerEvent
+    Error       *errorConsumerEvent
+}
+
+type Server struct {
+    Socket *net.UDPConn
+    Events serverEvents
+}
+
+func (this *Server) Shutdown() {
+    if this.Socket != nil {
+        socket := this.Socket
+        this.Socket = nil
+
+        socket.Close()
+    }
+}
+
+func (this *Server) Listen(address string, port int) {
+    if socket, err := net.ListenUDP("udp", &net.UDPAddr{
+        Port: port,
+        IP:   net.ParseIP(address),
+    }); err != nil {
+        this.Events.Error.Trigger(err)
+
+        return
+    } else {
+        this.Socket = socket
+    }
+
+    this.Events.Start.Trigger()
+    defer this.Events.Shutdown.Trigger()
+
+    buf := make([]byte, 1500)
+    for this.Socket != nil {
+        if bytesRead, addr, err := this.Socket.ReadFromUDP(buf); err != nil {
+            if this.Socket != nil {
+                this.Events.Error.Trigger(err)
+            }
+        } else {
+            this.Events.ReceiveData.Trigger(addr, buf[:bytesRead])
+        }
+    }
+}
+
+func NewServer() *Server {
+    return &Server{
+        Events: serverEvents{
+            Start:       &callbackEvent{make(map[uintptr]Callback)},
+            Shutdown:    &callbackEvent{make(map[uintptr]Callback)},
+            ReceiveData: &dataConsumerEvent{make(map[uintptr]AddressDataConsumer)},
+            Error:       &errorConsumerEvent{make(map[uintptr]ErrorConsumer)},
+        },
+    }
+}
diff --git a/packages/network/udp/types.go b/packages/network/udp/types.go
new file mode 100644
index 0000000000000000000000000000000000000000..5ee6236e770225de18527d7209f6aa2e8e67ae70
--- /dev/null
+++ b/packages/network/udp/types.go
@@ -0,0 +1,9 @@
+package udp
+
+import "net"
+
+type Callback = func()
+
+type AddressDataConsumer = func(addr *net.UDPAddr, data []byte)
+
+type ErrorConsumer = func(err error)
diff --git a/packages/node/constants.go b/packages/node/constants.go
new file mode 100644
index 0000000000000000000000000000000000000000..4b4ea4fae61c166f5c9a5f21be64b7717b443b31
--- /dev/null
+++ b/packages/node/constants.go
@@ -0,0 +1,8 @@
+package node
+
+const (
+    LOG_LEVEL_FAILURE = 0
+    LOG_LEVEL_WARNING = 1
+    LOG_LEVEL_SUCCESS = 2
+    LOG_LEVEL_INFO    = 3
+)
diff --git a/packages/node/events.go b/packages/node/events.go
new file mode 100644
index 0000000000000000000000000000000000000000..09c7327b31eb1d99bcf7de4b85ea2cece00b25e9
--- /dev/null
+++ b/packages/node/events.go
@@ -0,0 +1,28 @@
+package node
+
+import (
+    "reflect"
+)
+
+type pluginEvents struct {
+    Configure *callbackEvent
+    Run       *callbackEvent
+}
+
+type callbackEvent struct {
+    callbacks map[uintptr]Callback
+}
+
+func (this *callbackEvent) Attach(callback Callback) {
+    this.callbacks[reflect.ValueOf(callback).Pointer()] = callback
+}
+
+func (this *callbackEvent) Detach(callback Callback) {
+    delete(this.callbacks, reflect.ValueOf(callback).Pointer())
+}
+
+func (this *callbackEvent) Trigger(plugin *Plugin) {
+    for _, callback := range this.callbacks {
+        callback(plugin)
+    }
+}
diff --git a/packages/node/logger.go b/packages/node/logger.go
new file mode 100644
index 0000000000000000000000000000000000000000..a2665c1eac539d1274f829302053e5eb0763dbf4
--- /dev/null
+++ b/packages/node/logger.go
@@ -0,0 +1,38 @@
+package node
+
+import "fmt"
+
+type Logger struct {
+    Enabled    bool
+    LogInfo    func(pluginName string, message string)
+    LogSuccess func(pluginName string, message string)
+    LogWarning func(pluginName string, message string)
+    LogFailure func(pluginName string, message string)
+}
+
+func pluginPrefix(pluginName string) string {
+    var pluginPrefix string
+    if pluginName == "Node" {
+        pluginPrefix = ""
+    } else {
+        pluginPrefix = pluginName + ": "
+    }
+
+    return pluginPrefix
+}
+
+var DEFAULT_LOGGER = &Logger{
+    Enabled: true,
+    LogSuccess: func(pluginName string, message string) {
+        fmt.Println("[  OK  ] " + pluginPrefix(pluginName) + message)
+    },
+    LogInfo: func(pluginName string, message string) {
+        fmt.Println("[ INFO ] " + pluginPrefix(pluginName) + message)
+    },
+    LogWarning: func(pluginName string, message string) {
+        fmt.Println("[ WARN ] " + pluginPrefix(pluginName) + message)
+    },
+    LogFailure: func(pluginName string, message string) {
+        fmt.Println("[ FAIL ] " + pluginPrefix(pluginName) + message)
+    },
+}
diff --git a/packages/node/node.go b/packages/node/node.go
new file mode 100644
index 0000000000000000000000000000000000000000..4f74e6524ae333dd4a418d74a700eca752855489
--- /dev/null
+++ b/packages/node/node.go
@@ -0,0 +1,122 @@
+package node
+
+import (
+    "fmt"
+    "github.com/iotaledger/goshimmer/packages/daemon"
+    "sync"
+)
+
+type Node struct {
+    wg            *sync.WaitGroup
+    loggers       []*Logger
+    loadedPlugins []*Plugin
+    logLevel      int
+}
+
+func Load(plugins ...*Plugin) *Node {
+    fmt.Println("  _____ _   _ ________  ______  ___ ___________ ")
+    fmt.Println(" /  ___| | | |_   _|  \\/  ||  \\/  ||  ___| ___ \\")
+    fmt.Println(" \\ `--.| |_| | | | | .  . || .  . || |__ | |_/ /")
+    fmt.Println("  `--. \\  _  | | | | |\\/| || |\\/| ||  __||    / ")
+    fmt.Println(" /\\__/ / | | |_| |_| |  | || |  | || |___| |\\ \\ ")
+    fmt.Println(" \\____/\\_| |_/\\___/\\_|  |_/\\_|  |_/\\____/\\_| \\_| fullnode 1.0")
+    fmt.Println()
+
+    node := &Node{
+        logLevel:      LOG_LEVEL_INFO,
+        loggers:       make([]*Logger, 0),
+        wg:            &sync.WaitGroup{},
+        loadedPlugins: make([]*Plugin, 0),
+    }
+
+    node.AddLogger(DEFAULT_LOGGER)
+    node.Load(plugins...)
+
+    return node
+}
+
+func Run(plugins ...*Plugin) *Node {
+    node := Load(plugins...)
+    node.Run()
+
+    return node
+}
+
+func (node *Node) AddLogger(logger *Logger) {
+    node.loggers = append(node.loggers, logger)
+}
+
+func (node *Node) LogSuccess(pluginName string, message string) {
+    if node.logLevel >= LOG_LEVEL_SUCCESS {
+        for _, logger := range node.loggers {
+            if logger.Enabled {
+                logger.LogSuccess(pluginName, message)
+            }
+        }
+    }
+}
+
+func (node *Node) LogInfo(pluginName string, message string) {
+    if node.logLevel >= LOG_LEVEL_INFO {
+        for _, logger := range node.loggers {
+            if logger.Enabled {
+                logger.LogInfo(pluginName, message)
+            }
+        }
+    }
+}
+
+func (node *Node) LogWarning(pluginName string, message string) {
+    if node.logLevel >= LOG_LEVEL_WARNING {
+        for _, logger := range node.loggers {
+            if logger.Enabled {
+                logger.LogWarning(pluginName, message)
+            }
+        }
+    }
+}
+
+func (node *Node) LogFailure(pluginName string, message string) {
+    if node.logLevel >= LOG_LEVEL_FAILURE {
+        for _, logger := range node.loggers {
+            if logger.Enabled {
+                logger.LogFailure(pluginName, message)
+            }
+        }
+    }
+}
+
+func (node *Node) Load(plugins ...*Plugin) {
+    node.LogInfo("Node", "Loading plugins ...")
+
+    if len(plugins) >= 1 {
+        for _, plugin := range plugins {
+            plugin.wg = node.wg
+            plugin.Node = node
+
+            plugin.Events.Configure.Trigger(plugin)
+
+            node.LogInfo("Node", "Loading Plugin: "+plugin.Name+" ... done")
+        }
+    }
+
+    node.loadedPlugins = append(node.loadedPlugins, plugins...)
+}
+
+func (node *Node) Run() {
+    node.LogInfo("Node", "Executing plugins ...")
+
+    if len(node.loadedPlugins) >= 1 {
+        for _, plugin := range node.loadedPlugins {
+            plugin.Events.Run.Trigger(plugin)
+
+            node.LogSuccess("Node", "Starting Plugin: "+plugin.Name+" ... done")
+        }
+    }
+
+    node.LogSuccess("Node", "Starting background workers ...")
+
+    daemon.Run()
+
+    node.LogSuccess("Node", "Shutdown complete!")
+}
diff --git a/packages/node/plugin.go b/packages/node/plugin.go
new file mode 100644
index 0000000000000000000000000000000000000000..5ebf45df451238185072ce1b817b6d2337478d38
--- /dev/null
+++ b/packages/node/plugin.go
@@ -0,0 +1,51 @@
+package node
+
+import (
+    "sync"
+)
+
+type Plugin struct {
+    Node   *Node
+    Name   string
+    Events pluginEvents
+    wg     *sync.WaitGroup
+}
+
+func NewPlugin(name string, callback Callback, callbacks ...Callback) *Plugin {
+    plugin := &Plugin{
+        Name: name,
+        Events: pluginEvents{
+            Configure: &callbackEvent{make(map[uintptr]Callback)},
+            Run:       &callbackEvent{make(map[uintptr]Callback)},
+        },
+    }
+
+    if len(callbacks) >= 1 {
+        plugin.Events.Configure.Attach(callback)
+        for _, callback = range callbacks[:len(callbacks)-1] {
+            plugin.Events.Configure.Attach(callback)
+        }
+
+        plugin.Events.Run.Attach(callbacks[len(callbacks)-1])
+    } else {
+        plugin.Events.Run.Attach(callback)
+    }
+
+    return plugin
+}
+
+func (plugin *Plugin) LogSuccess(message string) {
+    plugin.Node.LogSuccess(plugin.Name, message)
+}
+
+func (plugin *Plugin) LogInfo(message string) {
+    plugin.Node.LogInfo(plugin.Name, message)
+}
+
+func (plugin *Plugin) LogWarning(message string) {
+    plugin.Node.LogWarning(plugin.Name, message)
+}
+
+func (plugin *Plugin) LogFailure(message string) {
+    plugin.Node.LogFailure(plugin.Name, message)
+}
diff --git a/packages/node/types.go b/packages/node/types.go
new file mode 100644
index 0000000000000000000000000000000000000000..fff6b603572ce25e17f1aa9f7b96a0edc30df98d
--- /dev/null
+++ b/packages/node/types.go
@@ -0,0 +1,3 @@
+package node
+
+type Callback = func(plugin *Plugin)
diff --git a/packages/parameter/api.go b/packages/parameter/api.go
new file mode 100644
index 0000000000000000000000000000000000000000..dc9df16dc445d4c7f1f3308a2b012c3fed5c68f1
--- /dev/null
+++ b/packages/parameter/api.go
@@ -0,0 +1,19 @@
+package parameter
+
+var (
+    // expose int methods
+    AddInt = addInt
+    GetInt = getInt
+    GetInts = getInts
+
+    // expose string methods
+    AddString = addString
+    GetString = getString
+    GetStrings = getStrings
+
+    // expose events
+    Events = moduleEvents{
+        AddInt:    &intParameterEvent{make(map[uintptr]IntParameterConsumer)},
+        AddString: &stringParameterEvent{make(map[uintptr]StringParameterConsumer)},
+    }
+)
diff --git a/packages/parameter/events.go b/packages/parameter/events.go
new file mode 100644
index 0000000000000000000000000000000000000000..7ca92df1de48b4fd4f39ebfab0a572a86e633a43
--- /dev/null
+++ b/packages/parameter/events.go
@@ -0,0 +1,52 @@
+package parameter
+
+import "reflect"
+
+type moduleEvents struct {
+    AddInt    *intParameterEvent
+    AddString *stringParameterEvent
+}
+
+//region intParameterEvent /////////////////////////////////////////////////////////////////////////////////////////////
+
+type intParameterEvent struct {
+    callbacks map[uintptr]IntParameterConsumer
+}
+
+func (this *intParameterEvent) Attach(callback IntParameterConsumer) {
+    this.callbacks[reflect.ValueOf(callback).Pointer()] = callback
+}
+
+func (this *intParameterEvent) Detach(callback IntParameterConsumer) {
+    delete(this.callbacks, reflect.ValueOf(callback).Pointer())
+}
+
+func (this *intParameterEvent) Trigger(param *IntParameter) {
+    for _, callback := range this.callbacks {
+        callback(param)
+    }
+}
+
+//endregion ////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+//region stringParameterEvent //////////////////////////////////////////////////////////////////////////////////////////
+
+type stringParameterEvent struct {
+    callbacks map[uintptr]StringParameterConsumer
+}
+
+func (this *stringParameterEvent) Attach(callback StringParameterConsumer) {
+    this.callbacks[reflect.ValueOf(callback).Pointer()] = callback
+}
+
+func (this *stringParameterEvent) Detach(callback StringParameterConsumer) {
+    delete(this.callbacks, reflect.ValueOf(callback).Pointer())
+}
+
+func (this *stringParameterEvent) Trigger(param *StringParameter) {
+    for _, callback := range this.callbacks {
+        callback(param)
+    }
+}
+
+//endregion ////////////////////////////////////////////////////////////////////////////////////////////////////////////
diff --git a/packages/parameter/parameter.go b/packages/parameter/parameter.go
new file mode 100644
index 0000000000000000000000000000000000000000..07553c5cffabd8e2f823e0caa48297fab1caf63a
--- /dev/null
+++ b/packages/parameter/parameter.go
@@ -0,0 +1,59 @@
+package parameter
+
+var intParameters = make(map[string]*IntParameter)
+
+func addInt(name string, defaultValue int, description string) *IntParameter {
+    if intParameters[name] != nil {
+        panic("duplicate parameter - \"" + name + "\" was defined already")
+    }
+
+    newParameter := &IntParameter{
+        Name:          name,
+        DefaultValue:  defaultValue,
+        Value:         &defaultValue,
+        Description:   description,
+    }
+
+    intParameters[name] = newParameter
+
+    Events.AddInt.Trigger(newParameter)
+
+    return newParameter
+}
+
+func getInt(name string) *IntParameter {
+    return intParameters[name]
+}
+
+func getInts() map[string]*IntParameter {
+    return intParameters
+}
+
+var stringParameters  = make(map[string]*StringParameter)
+
+func addString(name string, defaultValue string, description string) *StringParameter {
+    if intParameters[name] != nil {
+        panic("duplicate parameter - \"" + name + "\" was defined already")
+    }
+
+    newParameter := &StringParameter{
+        Name:         name,
+        DefaultValue: defaultValue,
+        Value:        &defaultValue,
+        Description:  description,
+    }
+
+    stringParameters[name] = newParameter
+
+    Events.AddString.Trigger(newParameter)
+
+    return stringParameters[name]
+}
+
+func getString(name string) *StringParameter {
+    return stringParameters[name]
+}
+
+func getStrings() map[string]*StringParameter {
+    return stringParameters
+}
diff --git a/packages/parameter/types.go b/packages/parameter/types.go
new file mode 100644
index 0000000000000000000000000000000000000000..3c5f3c62d835ea3060a483a1275d89a89f993cd9
--- /dev/null
+++ b/packages/parameter/types.go
@@ -0,0 +1,19 @@
+package parameter
+
+type IntParameter struct {
+    Name         string
+    Value        *int
+    DefaultValue int
+    Description  string
+}
+
+type StringParameter struct {
+    Name         string
+    Value        *string
+    DefaultValue string
+    Description  string
+}
+
+type IntParameterConsumer = func(param *IntParameter)
+
+type StringParameterConsumer = func(param *StringParameter)
diff --git a/packages/settings/settings.go b/packages/settings/settings.go
new file mode 100644
index 0000000000000000000000000000000000000000..d2a0862f46059ad7fb628fcc176a9ecac94de080
--- /dev/null
+++ b/packages/settings/settings.go
@@ -0,0 +1,21 @@
+package settings
+
+import "github.com/iotaledger/goshimmer/packages/database"
+
+var settingsDatabase database.Database
+
+func init() {
+    if db, err := database.Get("settings"); err != nil {
+        panic(err)
+    } else {
+        settingsDatabase = db
+    }
+}
+
+func Get(key []byte) ([]byte, error) {
+    return settingsDatabase.Get(key)
+}
+
+func Set(key []byte, value []byte) error {
+    return settingsDatabase.Set(key, value)
+}
\ No newline at end of file
diff --git a/packages/transaction/constants.go b/packages/transaction/constants.go
new file mode 100644
index 0000000000000000000000000000000000000000..2320114fab0ecd7dc8cbeca2d1c8c9575df6bfd1
--- /dev/null
+++ b/packages/transaction/constants.go
@@ -0,0 +1,48 @@
+package transaction
+
+const (
+    // sizes of the transaction fields
+    SIGNATURE_MESSAGE_FRAGMENT_SIZE         = 6561
+    ADDRESS_SIZE                            = 243
+    VALUE_SIZE                              = 81
+    TIMESTAMP_SIZE                          = 27
+    CURRENT_INDEX_SIZE                      = 27
+    LATEST_INDEX_SIZE                       = 27
+    BUNDLE_HASH_SIZE                        = 243
+    TRUNK_TRANSACTION_HASH_SIZE             = 243
+    BRANCH_TRANSACTION_HASH_SIZE            = 243
+    TAG_SIZE                                = 81
+    NODE_ID_SIZE                            = 243
+    NONCE_SIZE                              = 81
+
+    // offsets of the transaction fields
+    SIGNATURE_MESSAGE_FRAGMENT_OFFSET       = 0
+    ADDRESS_OFFSET                          = SIGNATURE_MESSAGE_FRAGMENT_END
+    VALUE_OFFSET                            = ADDRESS_END
+    TIMESTAMP_OFFSET                        = VALUE_END
+    CURRENT_INDEX_OFFSET                    = TIMESTAMP_END
+    LATEST_INDEX_OFFSET                     = CURRENT_INDEX_END
+    BUNDLE_HASH_OFFSET                      = LATEST_INDEX_END
+    TRUNK_TRANSACTION_HASH_OFFSET           = BUNDLE_HASH_END
+    BRANCH_TRANSACTION_HASH_OFFSET          = TRUNK_TRANSACTION_HASH_END
+    TAG_OFFSET                              = BRANCH_TRANSACTION_HASH_END
+    NODE_ID_OFFSET                          = TAG_END
+    NONCE_OFFSET                            = NODE_ID_END
+
+    // ends of the transaction fields
+    SIGNATURE_MESSAGE_FRAGMENT_END          = SIGNATURE_MESSAGE_FRAGMENT_OFFSET + SIGNATURE_MESSAGE_FRAGMENT_SIZE
+    ADDRESS_END                             = ADDRESS_OFFSET + ADDRESS_SIZE
+    VALUE_END                               = VALUE_OFFSET + VALUE_SIZE
+    TIMESTAMP_END                           = TIMESTAMP_OFFSET + TIMESTAMP_SIZE
+    CURRENT_INDEX_END                       = CURRENT_INDEX_OFFSET + CURRENT_INDEX_SIZE
+    LATEST_INDEX_END                        = LATEST_INDEX_OFFSET + LATEST_INDEX_SIZE
+    BUNDLE_HASH_END                         = BUNDLE_HASH_OFFSET + BUNDLE_HASH_SIZE
+    TRUNK_TRANSACTION_HASH_END              = TRUNK_TRANSACTION_HASH_OFFSET + TRUNK_TRANSACTION_HASH_SIZE
+    BRANCH_TRANSACTION_HASH_END             = BRANCH_TRANSACTION_HASH_OFFSET + BRANCH_TRANSACTION_HASH_SIZE
+    TAG_END                                 = TAG_OFFSET + TAG_SIZE
+    NODE_ID_END                             = NODE_ID_OFFSET + NODE_ID_SIZE
+    NONCE_END                               = NONCE_OFFSET + NONCE_SIZE
+
+    // the full size of a transaction
+    TRANSACTION_SIZE                        = NONCE_END
+)
diff --git a/packages/transaction/transaction.go b/packages/transaction/transaction.go
new file mode 100644
index 0000000000000000000000000000000000000000..84e486723fca89f2fec60eff304629265771a825
--- /dev/null
+++ b/packages/transaction/transaction.go
@@ -0,0 +1,58 @@
+package transaction
+
+import (
+    "github.com/iotadevelopment/go/packages/curl"
+    "github.com/iotadevelopment/go/packages/ternary"
+)
+
+type Transaction struct {
+    SignatureMessageFragment      ternary.Trits
+    Address                       ternary.Trits
+    Value                         int64
+    Timestamp                     uint64
+    CurrentIndex                  uint64
+    LatestIndex                   uint64
+    BundleHash                    ternary.Trits
+    TrunkTransactionHash          ternary.Trits
+    BranchTransactionHash         ternary.Trits
+    Tag                           ternary.Trits
+    NodeId                        ternary.Trits
+    Nonce                         ternary.Trits
+
+    Hash                          ternary.Trits
+    WeightMagnitude               int
+    Bytes                         []byte
+    Trits                         ternary.Trits
+}
+
+func FromTrits(trits ternary.Trits, optionalHash ...ternary.Trits) *Transaction {
+    hash := <- curl.CURLP81.Hash(trits)
+
+    transaction := &Transaction{
+        SignatureMessageFragment:      trits[SIGNATURE_MESSAGE_FRAGMENT_OFFSET:SIGNATURE_MESSAGE_FRAGMENT_END],
+        Address:                       trits[ADDRESS_OFFSET:ADDRESS_END],
+        Value:                         trits[VALUE_OFFSET:VALUE_END].ToInt64(),
+        Timestamp:                     trits[TIMESTAMP_OFFSET:TIMESTAMP_END].ToUint64(),
+        CurrentIndex:                  trits[CURRENT_INDEX_OFFSET:CURRENT_INDEX_END].ToUint64(),
+        LatestIndex:                   trits[LATEST_INDEX_OFFSET:LATEST_INDEX_END].ToUint64(),
+        BundleHash:                    trits[BUNDLE_HASH_OFFSET:BUNDLE_HASH_END],
+        TrunkTransactionHash:          trits[TRUNK_TRANSACTION_HASH_OFFSET:TRUNK_TRANSACTION_HASH_END],
+        BranchTransactionHash:         trits[BRANCH_TRANSACTION_HASH_OFFSET:BRANCH_TRANSACTION_HASH_END],
+        Tag:                           trits[TAG_OFFSET:TAG_END],
+        NodeId:                        trits[NODE_ID_OFFSET:NODE_ID_END],
+        Nonce:                         trits[NONCE_OFFSET:NONCE_END],
+
+        Hash:                          hash,
+        WeightMagnitude:               hash.TrailingZeroes(),
+        Trits:                         trits,
+    }
+
+    return transaction
+}
+
+func FromBytes(bytes []byte) *Transaction {
+    transaction := FromTrits(ternary.BytesToTrits(bytes)[:TRANSACTION_SIZE])
+    transaction.Bytes = bytes
+
+    return transaction
+}
\ No newline at end of file
diff --git a/plugins/autopeering/autopeering.go b/plugins/autopeering/autopeering.go
new file mode 100644
index 0000000000000000000000000000000000000000..d26a4e1f4b2292f5e583aa05197004bc86200594
--- /dev/null
+++ b/plugins/autopeering/autopeering.go
@@ -0,0 +1,25 @@
+package autopeering
+
+import (
+    "github.com/iotaledger/goshimmer/packages/daemon"
+    "github.com/iotaledger/goshimmer/packages/node"
+    "github.com/iotaledger/goshimmer/plugins/autopeering/peermanager"
+    "github.com/iotaledger/goshimmer/plugins/autopeering/server"
+)
+
+func configure(plugin *node.Plugin) {
+    server.Configure(plugin)
+    peermanager.Configure(plugin)
+
+    daemon.Events.Shutdown.Attach(func() {
+        server.Shutdown(plugin)
+        peermanager.Shutdown(plugin)
+    })
+}
+
+func run(plugin *node.Plugin) {
+    server.Run(plugin)
+    peermanager.Run(plugin)
+}
+
+var PLUGIN = node.NewPlugin("Auto Peering", configure, run)
diff --git a/plugins/autopeering/parameters/parameters.go b/plugins/autopeering/parameters/parameters.go
new file mode 100644
index 0000000000000000000000000000000000000000..63014eda145c558ab2740ea7e46bbcac863beb05
--- /dev/null
+++ b/plugins/autopeering/parameters/parameters.go
@@ -0,0 +1,9 @@
+package parameters
+
+import "github.com/iotaledger/goshimmer/packages/parameter"
+
+var (
+    ADDRESS     = parameter.AddString("AUTOPEERING/ADDRESS", "0.0.0.0", "address to bind for incoming peering requests")
+    UDP_PORT    = parameter.AddInt("AUTOPEERING/UDP_PORT", 14626, "udp port for incoming peering requests")
+    ENTRY_NODES = parameter.AddString("AUTOPEERING/ENTRY_NODES", "tcp://82.165.29.179:14626", "list of trusted entry nodes for auto peering")
+)
diff --git a/plugins/autopeering/peermanager/constants.go b/plugins/autopeering/peermanager/constants.go
new file mode 100644
index 0000000000000000000000000000000000000000..80fa7543ab834406b25ad6b1356253ec6b7e113b
--- /dev/null
+++ b/plugins/autopeering/peermanager/constants.go
@@ -0,0 +1,12 @@
+package peermanager
+
+import (
+    "github.com/iotaledger/goshimmer/packages/identity"
+    "time"
+)
+
+const (
+    FIND_NEIGHBOR_INTERVAL = 5 * time.Second
+)
+
+var UNKNOWN_IDENTITY = identity.GenerateRandomIdentity()
diff --git a/plugins/autopeering/peermanager/entry_nodes.go b/plugins/autopeering/peermanager/entry_nodes.go
new file mode 100644
index 0000000000000000000000000000000000000000..799192128b0019887dbafdfb9255258ff15edb13
--- /dev/null
+++ b/plugins/autopeering/peermanager/entry_nodes.go
@@ -0,0 +1,74 @@
+package peermanager
+
+import (
+    "github.com/iotaledger/goshimmer/plugins/autopeering/parameters"
+    "github.com/iotaledger/goshimmer/plugins/autopeering/protocol"
+    "net"
+    "strconv"
+    "strings"
+)
+
+func getEntryNodes() []*protocol.Peer {
+    result := make([]*protocol.Peer, 0)
+
+    for _, entryNodeDefinition := range strings.Fields(*parameters.ENTRY_NODES.Value) {
+        if entryNodeDefinition == "" {
+            continue
+        }
+
+        entryNode := &protocol.Peer{
+            Identity: UNKNOWN_IDENTITY,
+        }
+
+        protocolBits := strings.Split(entryNodeDefinition, "://")
+        if len(protocolBits) != 2 {
+            panic("invalid entry in list of trusted entry nodes: " + entryNodeDefinition)
+        }
+        switch protocolBits[0] {
+        case "tcp":
+            entryNode.PeeringProtocolType = protocol.TCP_PROTOCOL
+        case "udp":
+            entryNode.PeeringProtocolType = protocol.UDP_PROTOCOL
+        }
+
+        addressBits := strings.Split(protocolBits[1], ":")
+        switch len(addressBits) {
+        case 2:
+            host := addressBits[0]
+            port, err := strconv.Atoi(addressBits[1])
+            if err != nil {
+                panic("error while parsing port of entry in list of entry nodes")
+            }
+
+            ip := net.ParseIP(host)
+            if ip == nil {
+                panic("error while parsing ip of entry in list of entry nodes")
+            }
+
+            entryNode.Address = ip
+            entryNode.PeeringPort = uint16(port)
+        case 6:
+            host := strings.Join(addressBits[:5], ":")
+            port, err := strconv.Atoi(addressBits[5])
+            if err != nil {
+                panic("error while parsing port of entry in list of entry nodes")
+            }
+
+            ip := net.ParseIP(host)
+            if ip == nil {
+                panic("error while parsing ip of entry in list of entry nodes")
+            }
+
+            entryNode.Address = ip
+            entryNode.PeeringPort = uint16(port)
+        default:
+            panic("invalid entry in list of trusted entry nodes: " + entryNodeDefinition)
+        }
+
+        result = append(result, entryNode)
+    }
+
+    return result
+}
+
+var ENTRY_NODES = getEntryNodes()
\ No newline at end of file
diff --git a/plugins/autopeering/peermanager/peer_list.go b/plugins/autopeering/peermanager/peer_list.go
new file mode 100644
index 0000000000000000000000000000000000000000..f7a4a73313e6f7f596ef815a35b4de7b45f3f48f
--- /dev/null
+++ b/plugins/autopeering/peermanager/peer_list.go
@@ -0,0 +1,50 @@
+package peermanager
+
+import (
+    "bytes"
+    "github.com/iotaledger/goshimmer/packages/accountability"
+    "github.com/iotaledger/goshimmer/plugins/autopeering/protocol"
+    "time"
+)
+
+type PeerList struct {
+    Peers map[string]*protocol.Peer
+}
+
+func (this *PeerList) Update(peer *protocol.Peer) {
+    if peer.Identity == UNKNOWN_IDENTITY || bytes.Equal(peer.Identity.Identifier, accountability.OWN_ID.Identifier) {
+        return
+    }
+
+    now := time.Now()
+
+    if existingPeer, exists := this.Peers[peer.Identity.StringIdentifier]; exists {
+        existingPeer.Address = peer.Address
+        existingPeer.GossipPort = peer.GossipPort
+        existingPeer.PeeringPort = peer.PeeringPort
+        existingPeer.LastSeen = now
+        existingPeer.LastContact = now
+
+        // trigger update peer
+    } else {
+        peer.FirstSeen = now
+        peer.LastSeen = now
+        peer.LastContact = now
+
+        this.Peers[peer.Identity.StringIdentifier] = peer
+
+        // trigger add peer
+    }
+}
+
+func (this *PeerList) Add(peer *protocol.Peer) {
+    this.Peers[peer.Identity.StringIdentifier] = peer
+}
+
+func (this *PeerList) Contains(key string) bool {
+    if _, exists := this.Peers[key]; exists {
+        return true
+    } else {
+        return false
+    }
+}
diff --git a/plugins/autopeering/peermanager/peer_manager.go b/plugins/autopeering/peermanager/peer_manager.go
new file mode 100644
index 0000000000000000000000000000000000000000..62253cf5cd25d298f9a69102c88859a1925186e7
--- /dev/null
+++ b/plugins/autopeering/peermanager/peer_manager.go
@@ -0,0 +1,201 @@
+package peermanager
+
+import (
+    "github.com/iotaledger/goshimmer/packages/accountability"
+    "github.com/iotaledger/goshimmer/packages/daemon"
+    "github.com/iotaledger/goshimmer/packages/network"
+    "github.com/iotaledger/goshimmer/packages/node"
+    "github.com/iotaledger/goshimmer/plugins/autopeering/parameters"
+    "github.com/iotaledger/goshimmer/plugins/autopeering/protocol"
+    "github.com/iotaledger/goshimmer/plugins/autopeering/salt"
+    "github.com/iotaledger/goshimmer/plugins/autopeering/saltmanager"
+    "github.com/iotaledger/goshimmer/plugins/autopeering/server"
+    "math"
+    "net"
+    "strconv"
+    "time"
+)
+
+var PEERING_REQUEST *protocol.PeeringRequest
+
+var KNOWN_PEERS = &PeerList{make(map[string]*protocol.Peer)}
+
+var CHOSEN_NEIGHBORS = &PeerList{make(map[string]*protocol.Peer)}
+
+var ACCEPTED_NEIGHBORS = &PeerList{make(map[string]*protocol.Peer)}
+
+func configurePeeringRequest() {
+    PEERING_REQUEST = &protocol.PeeringRequest{
+        Issuer: &protocol.Peer{
+            Identity:            accountability.OWN_ID,
+            PeeringProtocolType: protocol.TCP_PROTOCOL,
+            PeeringPort:         uint16(*parameters.UDP_PORT.Value),
+            GossipProtocolType:  protocol.TCP_PROTOCOL,
+            GossipPort:          uint16(*parameters.UDP_PORT.Value),
+            Address:             net.IPv4(0, 0, 0, 0),
+        },
+        Salt: saltmanager.PUBLIC_SALT,
+    }
+    PEERING_REQUEST.Sign()
+
+    saltmanager.Events.UpdatePublicSalt.Attach(func(salt *salt.Salt) {
+        PEERING_REQUEST.Sign()
+    })
+}
+
+func Configure(plugin *node.Plugin) {
+    configurePeeringRequest()
+
+    server.Events.ReceivePeeringRequest.Attach(func(ip net.IP, peeringRequest *protocol.PeeringRequest) {
+        peer := peeringRequest.Issuer
+        peer.Address = ip
+
+        KNOWN_PEERS.Update(peer)
+
+        plugin.LogInfo("received peering request from " + peeringRequest.Issuer.Identity.StringIdentifier)
+    })
+    server.Events.ReceiveTCPPeeringRequest.Attach(func(conn network.Connection, request *protocol.PeeringRequest) {
+        peer := request.Issuer
+        peer.Address = conn.GetConnection().RemoteAddr().(*net.TCPAddr).IP
+
+        KNOWN_PEERS.Update(peer)
+
+        plugin.LogInfo("received peering request from " + request.Issuer.Identity.StringIdentifier)
+
+        sendPeeringResponse(conn.GetConnection())
+    })
+    server.Events.Error.Attach(func(ip net.IP, err error) {
+        plugin.LogFailure("invalid peering request from " + ip.String())
+    })
+}
+
+func Run(plugin *node.Plugin) {
+    daemon.BackgroundWorker(func() {
+        chooseNeighbors(plugin)
+
+        ticker := time.NewTicker(FIND_NEIGHBOR_INTERVAL)
+        for {
+            select {
+            case <- daemon.ShutdownSignal:
+                return
+            case <- ticker.C:
+                chooseNeighbors(plugin)
+            }
+        }
+    })
+}
+
+func Shutdown(plugin *node.Plugin) {}
+
+func generateProposedNodeCandidates() []*protocol.Peer {
+    peers := make([]*protocol.Peer, 0)
+    for _, peer := range KNOWN_PEERS.Peers {
+        peers = append(peers, peer)
+    }
+
+    return peers
+}
+
+func rejectPeeringRequest(conn net.Conn) {
+    conn.Write((&protocol.PeeringResponse{
+        Type:   protocol.PEERING_RESPONSE_REJECT,
+        Issuer: PEERING_REQUEST.Issuer,
+        Peers:  generateProposedNodeCandidates(),
+    }).Sign().Marshal())
+    conn.Close()
+}
+
+func acceptPeeringRequest(conn net.Conn) {
+    conn.Write((&protocol.PeeringResponse{
+        Type:   protocol.PEERING_RESPONSE_ACCEPT,
+        Issuer: PEERING_REQUEST.Issuer,
+        Peers:  generateProposedNodeCandidates(),
+    }).Sign().Marshal())
+    conn.Close()
+}
+
+func sendPeeringResponse(conn net.Conn) {
+    if len(ACCEPTED_NEIGHBORS.Peers) < protocol.NEIGHBOR_COUNT / 2 {
+        acceptPeeringRequest(conn)
+    } else {
+        rejectPeeringRequest(conn)
+    }
+}
+
+func sendPeeringRequest(plugin *node.Plugin, peer *protocol.Peer) {
+    var protocolString string
+    switch peer.PeeringProtocolType {
+    case protocol.TCP_PROTOCOL:
+        protocolString = "tcp"
+    case protocol.UDP_PROTOCOL:
+        protocolString = "udp"
+    default:
+        panic("invalid protocol in known peers")
+    }
+
+    conn, err := net.Dial(protocolString, peer.Address.String() + ":" + strconv.Itoa(int(peer.PeeringPort)))
+    if err != nil {
+        plugin.LogFailure(err.Error())
+    } else {
+        conn := network.NewPeer(protocolString, conn)
+
+        conn.Write(PEERING_REQUEST.Marshal())
+
+        buffer := make([]byte, protocol.PEERING_RESPONSE_MARSHALLED_TOTAL_SIZE)
+        offset := 0
+        conn.OnReceiveData(func(data []byte) {
+            remainingCapacity := int(math.Min(float64(protocol.PEERING_RESPONSE_MARSHALLED_TOTAL_SIZE - offset), float64(len(data))))
+
+            copy(buffer[offset:], data[:remainingCapacity])
+            offset += len(data)
+
+            if offset >= protocol.PEERING_RESPONSE_MARSHALLED_TOTAL_SIZE {
+                peeringResponse, err := protocol.UnmarshalPeeringResponse(buffer)
+                if err != nil {
+                    plugin.LogFailure("invalid peering response from " + conn.GetConnection().RemoteAddr().String())
+                } else {
+                    processPeeringResponse(plugin, peeringResponse)
+                }
+
+                conn.GetConnection().Close()
+            }
+        })
+
+        go conn.HandleConnection()
+    }
+}
+
+func processPeeringResponse(plugin *node.Plugin, response *protocol.PeeringResponse) {
+    KNOWN_PEERS.Update(response.Issuer)
+    for _, peer := range response.Peers {
+        KNOWN_PEERS.Update(peer)
+    }
+
+    switch response.Type {
+    case protocol.PEERING_RESPONSE_ACCEPT:
+        CHOSEN_NEIGHBORS.Update(response.Issuer)
+    case protocol.PEERING_RESPONSE_REJECT:
+    default:
+        plugin.LogInfo("invalid response type in peering response of " + response.Issuer.Address.String() + ":" + strconv.Itoa(int(response.Issuer.PeeringPort)))
+    }
+}
+
+func getChosenNeighborCandidates() []*protocol.Peer {
+    result := make([]*protocol.Peer, 0)
+
+    for _, peer := range KNOWN_PEERS.Peers {
+        result = append(result, peer)
+    }
+
+    for _, peer := range ENTRY_NODES {
+        result = append(result, peer)
+    }
+
+    return result
+}
+
+func chooseNeighbors(plugin *node.Plugin) {
+    for _, peer := range getChosenNeighborCandidates() {
+        sendPeeringRequest(plugin, peer)
+    }
+}
diff --git a/plugins/autopeering/protocol/constants.go b/plugins/autopeering/protocol/constants.go
new file mode 100644
index 0000000000000000000000000000000000000000..5e677f1691436bba0e453fb5fa9e0b3ffa212fe6
--- /dev/null
+++ b/plugins/autopeering/protocol/constants.go
@@ -0,0 +1,85 @@
+package protocol
+
+import (
+    "github.com/iotaledger/goshimmer/packages/identity"
+    "github.com/iotaledger/goshimmer/plugins/autopeering/salt"
+)
+
+const (
+    // PEERING REQUEST PACKET STRUCTURE ////////////////////////////////////////////////////////////////////////////////
+
+    NEIGHBOR_COUNT = 8
+
+    PACKET_HEADER_SIZE = 1
+    ISSUER_SIZE        = PEER_MARSHALLED_TOTAL_SIZE
+    SALT_SIZE          = salt.SALT_MARSHALLED_SIZE
+    SIGNATURE_SIZE     = 65
+
+    PACKET_HEADER_START = 0
+    ISSUER_START        = PACKET_HEADER_END
+    SALT_START          = ISSUER_END
+    SIGNATURE_START     = SALT_END
+
+    PACKET_HEADER_END = PACKET_HEADER_START + PACKET_HEADER_SIZE
+    ISSUER_END        = ISSUER_START + ISSUER_SIZE
+    SALT_END          = SALT_START + SALT_SIZE
+    SIGNATURE_END     = SIGNATURE_START + SIGNATURE_SIZE
+
+    PEERING_REQUEST_MARSHALLED_TOTAL_SIZE = SIGNATURE_END
+
+    PACKET_HEADER = 0xBE
+
+    PEERING_RESPONSE_REJECT = ResponseType(0)
+    PEERING_RESPONSE_ACCEPT = ResponseType(1)
+
+    PEERING_RESPONSE_MARSHALLED_PEERS_AMOUNT = NEIGHBOR_COUNT + NEIGHBOR_COUNT*NEIGHBOR_COUNT
+
+    PEERING_RESPONSE_MARSHALLED_TYPE_SIZE      = 1
+    PEERING_RESPONSE_MARSHALLED_ISSUER_SIZE    = PEER_MARSHALLED_TOTAL_SIZE
+    PEERING_RESPONSE_MARSHALLED_PEER_FLAG_SIZE = 1
+    PEERING_RESPONSE_MARSHALLED_PEER_SIZE      = PEERING_RESPONSE_MARSHALLED_PEER_FLAG_SIZE + PEER_MARSHALLED_TOTAL_SIZE
+    PEERING_RESPONSE_MARSHALLED_PEERS_SIZE     = PEERING_RESPONSE_MARSHALLED_PEERS_AMOUNT * PEERING_RESPONSE_MARSHALLED_PEER_SIZE
+    PEERING_RESPONSE_MARSHALLED_SIGNATURE_SIZE = 65
+    PEERING_RESPONSE_MARSHALLED_TOTAL_SIZE     = PEERING_RESPONSE_MARSHALLED_SIGNATURE_END
+
+    PEERING_RESPONSE_MARSHALLED_TYPE_START      = 0
+    PEERING_RESPONSE_MARSHALLED_ISSUER_START    = PEERING_RESPONSE_MARSHALLED_TYPE_END
+    PEERING_RESPONSE_MARSHALLED_PEERS_START     = PEERING_RESPONSE_MARSHALLED_ISSUER_END
+    PEERING_RESPONSE_MARSHALLED_SIGNATURE_START = PEERING_RESPONSE_MARSHALLED_PEERS_END
+
+    PEERING_RESPONSE_MARSHALLED_TYPE_END      = PEERING_RESPONSE_MARSHALLED_TYPE_START + PEERING_RESPONSE_MARSHALLED_TYPE_SIZE
+    PEERING_RESPONSE_MARSHALLED_PEERS_END     = PEERING_RESPONSE_MARSHALLED_PEERS_START + PEERING_RESPONSE_MARSHALLED_PEERS_SIZE
+    PEERING_RESPONSE_MARSHALLED_ISSUER_END    = PEERING_RESPONSE_MARSHALLED_ISSUER_START + PEERING_RESPONSE_MARSHALLED_ISSUER_SIZE
+    PEERING_RESPONSE_MARSHALLED_SIGNATURE_END = PEERING_RESPONSE_MARSHALLED_SIGNATURE_START + PEERING_RESPONSE_MARSHALLED_SIGNATURE_SIZE
+
+    PEER_MARSHALLED_PUBLIC_KEY_SIZE            = identity.PUBLIC_KEY_BYTE_LENGTH
+    PEER_MARSHALLED_ADDRESS_TYPE_SIZE          = 1 // ipv4/ipv6
+    PEER_MARSHALLED_ADDRESS_SIZE               = 16
+    PEER_MARSHALLED_PEERING_PROTOCOL_TYPE_SIZE = 1 // tcp udp
+    PEER_MARSHALLED_PEERING_PORT_SIZE          = 2
+    PEER_MARSHALLED_GOSSIP_PROTOCOL_TYPE_SIZE  = 1
+    PEER_MARSHALLED_GOSSIP_PORT_SIZE           = 2 // tcp udp
+    PEER_MARSHALLED_TOTAL_SIZE                 = PEER_MARSHALLED_GOSSIP_PORT_END
+
+    PEER_MARSHALLED_PUBLIC_KEY_START            = 0
+    PEER_MARSHALLED_ADDRESS_TYPE_START          = PEER_MARSHALLED_PUBLIC_KEY_END
+    PEER_MARSHALLED_ADDRESS_START               = PEER_MARSHALLED_ADDRESS_TYPE_END
+    PEER_MARSHALLED_PEERING_PROTOCOL_TYPE_START = PEER_MARSHALLED_ADDRESS_END
+    PEER_MARSHALLED_PEERING_PORT_START          = PEER_MARSHALLED_PEERING_PROTOCOL_TYPE_END
+    PEER_MARSHALLED_GOSSIP_PROTOCOL_TYPE_START  = PEER_MARSHALLED_PEERING_PORT_END
+    PEER_MARSHALLED_GOSSIP_PORT_START           = PEER_MARSHALLED_GOSSIP_PROTOCOL_TYPE_END
+
+    PEER_MARSHALLED_PUBLIC_KEY_END            = PEER_MARSHALLED_PUBLIC_KEY_START + PEER_MARSHALLED_PUBLIC_KEY_SIZE
+    PEER_MARSHALLED_ADDRESS_TYPE_END          = PEER_MARSHALLED_ADDRESS_TYPE_START + PEER_MARSHALLED_ADDRESS_TYPE_SIZE
+    PEER_MARSHALLED_ADDRESS_END               = PEER_MARSHALLED_ADDRESS_START + PEER_MARSHALLED_ADDRESS_SIZE
+    PEER_MARSHALLED_PEERING_PROTOCOL_TYPE_END = PEER_MARSHALLED_PEERING_PROTOCOL_TYPE_START + PEER_MARSHALLED_PEERING_PROTOCOL_TYPE_SIZE
+    PEER_MARSHALLED_PEERING_PORT_END          = PEER_MARSHALLED_PEERING_PORT_START + PEER_MARSHALLED_PEERING_PORT_SIZE
+    PEER_MARSHALLED_GOSSIP_PROTOCOL_TYPE_END  = PEER_MARSHALLED_GOSSIP_PROTOCOL_TYPE_START + PEER_MARSHALLED_GOSSIP_PROTOCOL_TYPE_SIZE
+    PEER_MARSHALLED_GOSSIP_PORT_END           = PEER_MARSHALLED_GOSSIP_PORT_START + PEER_MARSHALLED_GOSSIP_PORT_SIZE
+
+    PEER_MARSHALLED_ADDRESS_TYPE_IPV4 = AddressType(0)
+    PEER_MARSHALLED_ADDRESS_TYPE_IPV6 = AddressType(1)
+
+    TCP_PROTOCOL = ProtocolType(0)
+    UDP_PROTOCOL = ProtocolType(1)
+)
diff --git a/plugins/autopeering/protocol/errors.go b/plugins/autopeering/protocol/errors.go
new file mode 100644
index 0000000000000000000000000000000000000000..53f4ab081ff3274a3057eefc7f63cc1fdaad3567
--- /dev/null
+++ b/plugins/autopeering/protocol/errors.go
@@ -0,0 +1,10 @@
+package protocol
+
+import "github.com/pkg/errors"
+
+var (
+    ErrPublicSaltExpired         = errors.New("expired public salt in peering request")
+    ErrPublicSaltInvalidLifetime = errors.New("invalid public salt lifetime")
+    ErrInvalidSignature          = errors.New("invalid signature in peering request")
+    ErrMalformedPeeringRequest   = errors.New("malformed peering request")
+)
diff --git a/plugins/autopeering/protocol/peer.go b/plugins/autopeering/protocol/peer.go
new file mode 100644
index 0000000000000000000000000000000000000000..013de5495610e5884de4835ce4c6c336d09b67fb
--- /dev/null
+++ b/plugins/autopeering/protocol/peer.go
@@ -0,0 +1,72 @@
+package protocol
+
+import (
+    "encoding/binary"
+    "github.com/iotaledger/goshimmer/packages/identity"
+    "github.com/pkg/errors"
+    "net"
+    "time"
+)
+
+type Peer struct {
+    Identity            *identity.Identity
+    Address             net.IP
+    PeeringProtocolType ProtocolType
+    PeeringPort         uint16
+    GossipProtocolType  ProtocolType
+    GossipPort          uint16
+    FirstSeen           time.Time
+    LastSeen            time.Time
+    LastContact         time.Time
+}
+
+func UnmarshalPeer(data []byte) (*Peer, error) {
+    if len(data) < PEER_MARSHALLED_TOTAL_SIZE {
+        return nil, errors.New("size of marshalled peer is too small")
+    }
+
+    peer := &Peer{
+        Identity: identity.NewIdentity(data[PEER_MARSHALLED_PUBLIC_KEY_START:PEER_MARSHALLED_PUBLIC_KEY_END]),
+    }
+
+    switch data[PEER_MARSHALLED_ADDRESS_TYPE_START] {
+    case PEER_MARSHALLED_ADDRESS_TYPE_IPV4:
+        peer.Address = net.IP(data[PEER_MARSHALLED_ADDRESS_START:PEER_MARSHALLED_ADDRESS_END]).To4()
+    case PEER_MARSHALLED_ADDRESS_TYPE_IPV6:
+        peer.Address = net.IP(data[PEER_MARSHALLED_ADDRESS_START:PEER_MARSHALLED_ADDRESS_END]).To16()
+    }
+
+    peer.PeeringProtocolType = ProtocolType(data[PEER_MARSHALLED_PEERING_PROTOCOL_TYPE_START])
+    peer.PeeringPort = binary.BigEndian.Uint16(data[PEER_MARSHALLED_PEERING_PORT_START:PEER_MARSHALLED_PEERING_PORT_END])
+
+    peer.GossipProtocolType = ProtocolType(data[PEER_MARSHALLED_GOSSIP_PROTOCOL_TYPE_START])
+    peer.GossipPort = binary.BigEndian.Uint16(data[PEER_MARSHALLED_GOSSIP_PORT_START:PEER_MARSHALLED_GOSSIP_PORT_END])
+
+    return peer, nil
+}
+
+func (peer *Peer) Marshal() []byte {
+    result := make([]byte, PEER_MARSHALLED_TOTAL_SIZE)
+
+    copy(result[PEER_MARSHALLED_PUBLIC_KEY_START:PEER_MARSHALLED_PUBLIC_KEY_END],
+        peer.Identity.PublicKey[:PEER_MARSHALLED_PUBLIC_KEY_SIZE])
+
+    switch len(peer.Address) {
+    case net.IPv4len:
+        result[PEER_MARSHALLED_ADDRESS_TYPE_START] = PEER_MARSHALLED_ADDRESS_TYPE_IPV4
+    case net.IPv6len:
+        result[PEER_MARSHALLED_ADDRESS_TYPE_START] = PEER_MARSHALLED_ADDRESS_TYPE_IPV6
+    default:
+        panic("invalid address in peer")
+    }
+
+    copy(result[PEER_MARSHALLED_ADDRESS_START:PEER_MARSHALLED_ADDRESS_END], peer.Address.To16())
+
+    result[PEER_MARSHALLED_PEERING_PROTOCOL_TYPE_START] = peer.PeeringProtocolType
+    binary.BigEndian.PutUint16(result[PEER_MARSHALLED_PEERING_PORT_START:PEER_MARSHALLED_PEERING_PORT_END], peer.PeeringPort)
+
+    result[PEER_MARSHALLED_GOSSIP_PROTOCOL_TYPE_START] = peer.GossipProtocolType
+    binary.BigEndian.PutUint16(result[PEER_MARSHALLED_GOSSIP_PORT_START:PEER_MARSHALLED_GOSSIP_PORT_END], peer.GossipPort)
+
+    return result
+}
diff --git a/plugins/autopeering/protocol/peering_request.go b/plugins/autopeering/protocol/peering_request.go
new file mode 100644
index 0000000000000000000000000000000000000000..1ed834db1c9097a8f757357e3c6f429805550c03
--- /dev/null
+++ b/plugins/autopeering/protocol/peering_request.go
@@ -0,0 +1,74 @@
+package protocol
+
+import (
+    "bytes"
+    "github.com/iotaledger/goshimmer/packages/identity"
+    "github.com/iotaledger/goshimmer/plugins/autopeering/salt"
+    "github.com/iotaledger/goshimmer/plugins/autopeering/saltmanager"
+    "time"
+)
+
+type PeeringRequest struct {
+    Issuer    *Peer
+    Salt      *salt.Salt
+    Signature [SIGNATURE_SIZE]byte
+}
+
+func UnmarshalPeeringRequest(data []byte) (*PeeringRequest, error) {
+    if data[0] != PACKET_HEADER || len(data) != PEERING_REQUEST_MARSHALLED_TOTAL_SIZE {
+        return nil, ErrMalformedPeeringRequest
+    }
+
+    peeringRequest := &PeeringRequest{}
+
+    if unmarshalledPeer, err := UnmarshalPeer(data[ISSUER_START:ISSUER_END]); err != nil {
+        return nil, err
+    } else {
+        peeringRequest.Issuer = unmarshalledPeer
+    }
+
+    if unmarshalledSalt, err := salt.Unmarshal(data[SALT_START:SALT_END]); err != nil {
+        return nil, err
+    } else {
+        peeringRequest.Salt = unmarshalledSalt
+    }
+
+    now := time.Now()
+    if peeringRequest.Salt.ExpirationTime.Before(now) {
+        return nil, ErrPublicSaltExpired
+    }
+    if peeringRequest.Salt.ExpirationTime.After(now.Add(saltmanager.PUBLIC_SALT_LIFETIME + 1*time.Minute)) {
+        return nil, ErrPublicSaltInvalidLifetime
+    }
+
+    if issuer, err := identity.FromSignedData(data[:SIGNATURE_START], data[SIGNATURE_START:]); err != nil {
+        return nil, err
+    } else {
+        if !bytes.Equal(issuer.Identifier, peeringRequest.Issuer.Identity.Identifier) {
+            return nil, ErrInvalidSignature
+        }
+    }
+    copy(peeringRequest.Signature[:], data[SIGNATURE_START:SIGNATURE_END])
+
+    return peeringRequest, nil
+}
+
+func (this *PeeringRequest) Sign() {
+    dataToSign := this.Marshal()[:SIGNATURE_START]
+    if signature, err := this.Issuer.Identity.Sign(dataToSign); err != nil {
+        panic(err)
+    } else {
+        copy(this.Signature[:], signature)
+    }
+}
+
+func (this *PeeringRequest) Marshal() []byte {
+    result := make([]byte, PEERING_REQUEST_MARSHALLED_TOTAL_SIZE)
+
+    result[PACKET_HEADER_START] = PACKET_HEADER
+    copy(result[ISSUER_START:ISSUER_END], this.Issuer.Marshal())
+    copy(result[SALT_START:SALT_END], this.Salt.Marshal())
+    copy(result[SIGNATURE_START:SIGNATURE_END], this.Signature[:SIGNATURE_SIZE])
+
+    return result
+}
diff --git a/plugins/autopeering/protocol/peering_response.go b/plugins/autopeering/protocol/peering_response.go
new file mode 100644
index 0000000000000000000000000000000000000000..1cb9dee23d7584b79f4861193f33d4028776380c
--- /dev/null
+++ b/plugins/autopeering/protocol/peering_response.go
@@ -0,0 +1,87 @@
+package protocol
+
+import (
+    "bytes"
+    "github.com/iotaledger/goshimmer/packages/identity"
+    "github.com/pkg/errors"
+)
+
+type PeeringResponse struct {
+    Type      ResponseType
+    Issuer    *Peer
+    Peers     []*Peer
+    Signature [PEERING_RESPONSE_MARSHALLED_SIGNATURE_SIZE]byte
+}
+
+func UnmarshalPeeringResponse(data []byte) (*PeeringResponse, error) {
+    if len(data) < PEERING_RESPONSE_MARSHALLED_TOTAL_SIZE {
+        return nil, errors.New("size of marshalled peering response is too small")
+    }
+
+    peeringResponse := &PeeringResponse{
+        Type:  data[PEERING_RESPONSE_MARSHALLED_TYPE_START],
+        Peers: make([]*Peer, 0),
+    }
+
+    if unmarshalledPeer, err := UnmarshalPeer(data[PEERING_RESPONSE_MARSHALLED_ISSUER_START:PEERING_RESPONSE_MARSHALLED_ISSUER_END]); err != nil {
+        return nil, err
+    } else {
+        peeringResponse.Issuer = unmarshalledPeer
+    }
+
+    for i := 0; i < PEERING_RESPONSE_MARSHALLED_PEERS_AMOUNT; i++ {
+        PEERING_RESPONSE_MARSHALLED_PEER_START := PEERING_RESPONSE_MARSHALLED_PEERS_START + (i * PEERING_RESPONSE_MARSHALLED_PEER_SIZE)
+        PEERING_RESPONSE_MARSHALLED_PEER_END := PEERING_RESPONSE_MARSHALLED_PEER_START + PEERING_RESPONSE_MARSHALLED_PEER_SIZE
+
+        if data[PEERING_RESPONSE_MARSHALLED_PEER_START] == 1 {
+            peer, err := UnmarshalPeer(data[PEERING_RESPONSE_MARSHALLED_PEER_START+1 : PEERING_RESPONSE_MARSHALLED_PEER_END])
+            if err != nil {
+                return nil, err
+            }
+
+            peeringResponse.Peers = append(peeringResponse.Peers, peer)
+        }
+    }
+
+    if issuer, err := identity.FromSignedData(data[:PEERING_RESPONSE_MARSHALLED_SIGNATURE_START], data[PEERING_RESPONSE_MARSHALLED_SIGNATURE_START:]); err != nil {
+        return nil, err
+    } else {
+        if !bytes.Equal(issuer.Identifier, peeringResponse.Issuer.Identity.Identifier) {
+            return nil, ErrInvalidSignature
+        }
+    }
+    copy(peeringResponse.Signature[:], data[PEERING_RESPONSE_MARSHALLED_SIGNATURE_START:PEERING_RESPONSE_MARSHALLED_SIGNATURE_END])
+
+    return peeringResponse, nil
+}
+
+func (this *PeeringResponse) Sign() *PeeringResponse {
+    dataToSign := this.Marshal()[:PEERING_RESPONSE_MARSHALLED_SIGNATURE_START]
+    if signature, err := this.Issuer.Identity.Sign(dataToSign); err != nil {
+        panic(err)
+    } else {
+        copy(this.Signature[:], signature)
+    }
+
+    return this
+}
+
+func (this *PeeringResponse) Marshal() []byte {
+    result := make([]byte, PEERING_RESPONSE_MARSHALLED_TOTAL_SIZE)
+
+    result[PEERING_RESPONSE_MARSHALLED_TYPE_START] = this.Type
+
+    copy(result[PEERING_RESPONSE_MARSHALLED_ISSUER_START:PEERING_RESPONSE_MARSHALLED_ISSUER_END], this.Issuer.Marshal())
+
+    for i, peer := range this.Peers {
+        PEERING_RESPONSE_MARSHALLED_PEER_START := PEERING_RESPONSE_MARSHALLED_PEERS_START + (i * PEERING_RESPONSE_MARSHALLED_PEER_SIZE)
+        PEERING_RESPONSE_MARSHALLED_PEER_END := PEERING_RESPONSE_MARSHALLED_PEER_START + PEERING_RESPONSE_MARSHALLED_PEER_SIZE
+
+        result[PEERING_RESPONSE_MARSHALLED_PEER_START] = 1
+        copy(result[PEERING_RESPONSE_MARSHALLED_PEER_START+1:PEERING_RESPONSE_MARSHALLED_PEER_END], peer.Marshal()[:PEERING_RESPONSE_MARSHALLED_PEER_SIZE-1])
+    }
+
+    copy(result[PEERING_RESPONSE_MARSHALLED_SIGNATURE_START:PEERING_RESPONSE_MARSHALLED_SIGNATURE_END], this.Signature[:PEERING_RESPONSE_MARSHALLED_SIGNATURE_SIZE])
+
+    return result
+}
diff --git a/plugins/autopeering/protocol/types.go b/plugins/autopeering/protocol/types.go
new file mode 100644
index 0000000000000000000000000000000000000000..22a7f44ea0c7d6dd34cda08f0429ae0ef626e3cb
--- /dev/null
+++ b/plugins/autopeering/protocol/types.go
@@ -0,0 +1,7 @@
+package protocol
+
+type ProtocolType = byte
+
+type AddressType = byte
+
+type ResponseType = byte
diff --git a/plugins/autopeering/salt/constants.go b/plugins/autopeering/salt/constants.go
new file mode 100644
index 0000000000000000000000000000000000000000..6c5d016d590f6b142ed1f93331d7d827f8978451
--- /dev/null
+++ b/plugins/autopeering/salt/constants.go
@@ -0,0 +1,14 @@
+package salt
+
+const (
+    SALT_BYTES_SIZE = 20
+    SALT_TIME_SIZE  = 15
+
+    SALT_BYTES_START = 0
+    SALT_TIME_START  = SALT_BYTES_END
+
+    SALT_BYTES_END = SALT_BYTES_START + SALT_BYTES_SIZE
+    SALT_TIME_END  = SALT_TIME_START + SALT_TIME_SIZE
+
+    SALT_MARSHALLED_SIZE = SALT_TIME_END
+)
diff --git a/plugins/autopeering/salt/salt.go b/plugins/autopeering/salt/salt.go
new file mode 100644
index 0000000000000000000000000000000000000000..bc1555de1360bdeff87a101539ad58dc41f6b0d3
--- /dev/null
+++ b/plugins/autopeering/salt/salt.go
@@ -0,0 +1,56 @@
+package salt
+
+import (
+    "crypto/rand"
+    "github.com/pkg/errors"
+    "time"
+)
+
+type Salt struct {
+    Bytes          []byte
+    ExpirationTime time.Time
+}
+
+func New(lifetime time.Duration) *Salt {
+    salt := &Salt{
+        Bytes: make([]byte, SALT_BYTES_SIZE),
+        ExpirationTime: time.Now().Add(lifetime),
+    }
+
+    if _, err := rand.Read(salt.Bytes); err != nil {
+        panic(err)
+    }
+
+    return salt
+}
+
+func Unmarshal(marshalledSalt []byte) (*Salt, error) {
+    if len(marshalledSalt) < SALT_MARSHALLED_SIZE {
+        return nil, errors.New("marshalled salt bytes not long enough")
+    }
+
+    salt := &Salt{
+        Bytes: make([]byte, SALT_BYTES_SIZE),
+    }
+    copy(salt.Bytes, marshalledSalt[SALT_BYTES_START:SALT_BYTES_END])
+
+    if err := salt.ExpirationTime.UnmarshalBinary(marshalledSalt[SALT_TIME_START:SALT_TIME_END]); err != nil {
+        return nil, err
+    }
+
+    return salt, nil
+}
+
+func (this *Salt) Marshal() []byte {
+    result := make([]byte, SALT_BYTES_SIZE+SALT_TIME_SIZE)
+
+    copy(result[SALT_BYTES_START:SALT_BYTES_END], this.Bytes)
+
+    if bytes, err := this.ExpirationTime.MarshalBinary(); err != nil {
+        panic(err)
+    } else {
+        copy(result[SALT_TIME_START:SALT_TIME_END], bytes)
+    }
+
+    return result
+}
\ No newline at end of file
diff --git a/plugins/autopeering/saltmanager/constants.go b/plugins/autopeering/saltmanager/constants.go
new file mode 100644
index 0000000000000000000000000000000000000000..12c19170ebe4b67a79172d0d69fcced9e867355a
--- /dev/null
+++ b/plugins/autopeering/saltmanager/constants.go
@@ -0,0 +1,13 @@
+package saltmanager
+
+import "time"
+
+const (
+    PUBLIC_SALT_LIFETIME  = 50 * time.Second
+    PRIVATE_SALT_LIFETIME = 50 * time.Second
+)
+
+var (
+    PUBLIC_SALT_SETTINGS_KEY = []byte("PUBLIC_SALT")
+    PRIVATE_SALT_SETTINGS_KEY = []byte("PRIVATE_SALT")
+)
diff --git a/plugins/autopeering/saltmanager/events.go b/plugins/autopeering/saltmanager/events.go
new file mode 100644
index 0000000000000000000000000000000000000000..a1bc3a8d3afd497dab5e389eb7537e13db975636
--- /dev/null
+++ b/plugins/autopeering/saltmanager/events.go
@@ -0,0 +1,34 @@
+package saltmanager
+
+import (
+    "github.com/iotaledger/goshimmer/plugins/autopeering/salt"
+    "reflect"
+)
+
+type packageEvents struct {
+    UpdatePublicSalt  *saltEvent
+    UpdatePrivateSalt *saltEvent
+}
+
+type saltEvent struct {
+    callbacks map[uintptr]SaltConsumer
+}
+
+func (this *saltEvent) Attach(callback SaltConsumer) {
+    this.callbacks[reflect.ValueOf(callback).Pointer()] = callback
+}
+
+func (this *saltEvent) Detach(callback SaltConsumer) {
+    delete(this.callbacks, reflect.ValueOf(callback).Pointer())
+}
+
+func (this *saltEvent) Trigger(salt *salt.Salt) {
+    for _, callback := range this.callbacks {
+        callback(salt)
+    }
+}
+
+var Events = packageEvents{
+    UpdatePublicSalt:  &saltEvent{make(map[uintptr]SaltConsumer)},
+    UpdatePrivateSalt: &saltEvent{make(map[uintptr]SaltConsumer)},
+}
diff --git a/plugins/autopeering/saltmanager/saltmanager.go b/plugins/autopeering/saltmanager/saltmanager.go
new file mode 100644
index 0000000000000000000000000000000000000000..c119d3b23c5a06aad6274dab3dff368a17353220
--- /dev/null
+++ b/plugins/autopeering/saltmanager/saltmanager.go
@@ -0,0 +1,81 @@
+package saltmanager
+
+import (
+    "github.com/dgraph-io/badger"
+    "github.com/iotaledger/goshimmer/packages/daemon"
+    "github.com/iotaledger/goshimmer/packages/settings"
+    "github.com/iotaledger/goshimmer/plugins/autopeering/salt"
+    "time"
+)
+
+var (
+    PRIVATE_SALT = createSalt(PRIVATE_SALT_SETTINGS_KEY, PRIVATE_SALT_LIFETIME, Events.UpdatePrivateSalt.Trigger)
+    PUBLIC_SALT  = createSalt(PUBLIC_SALT_SETTINGS_KEY, PUBLIC_SALT_LIFETIME, Events.UpdatePublicSalt.Trigger)
+)
+
+func generateNewSalt(key []byte, lifetime time.Duration) *salt.Salt {
+    newSalt := salt.New(lifetime)
+
+    if err := settings.Set(key, newSalt.Marshal()); err != nil {
+        panic(err)
+    }
+
+    return newSalt
+}
+
+func getSalt(key []byte, lifetime time.Duration) *salt.Salt {
+    saltBytes, err := settings.Get(key)
+    if err != nil {
+        if err == badger.ErrKeyNotFound {
+            return generateNewSalt(key, lifetime)
+        } else {
+            panic(err)
+        }
+    }
+
+    if resultingSalt, err := salt.Unmarshal(saltBytes); err != nil {
+        panic(err)
+    } else {
+        return resultingSalt
+    }
+}
+
+func updatePublicSalt(saltToUpdate *salt.Salt, settingsKey []byte, lifeSpan time.Duration, updateCallback func(salt *salt.Salt)) {
+    newSalt := salt.New(lifeSpan)
+
+    saltToUpdate.Bytes = newSalt.Bytes
+    saltToUpdate.ExpirationTime = newSalt.ExpirationTime
+
+    if err := settings.Set(settingsKey, saltToUpdate.Marshal()); err != nil {
+        panic(err)
+    }
+
+    updateCallback(saltToUpdate)
+
+    scheduleUpdateForSalt(saltToUpdate, settingsKey, lifeSpan, updateCallback)
+}
+
+func scheduleUpdateForSalt(saltToUpdate *salt.Salt, settingsKey []byte, lifeSpan time.Duration, callback SaltConsumer) {
+    now := time.Now()
+
+    if saltToUpdate.ExpirationTime.Before(now) {
+        updatePublicSalt(saltToUpdate, settingsKey, lifeSpan, callback)
+    } else {
+        go func() {
+            select {
+            case <-daemon.ShutdownSignal:
+                return
+            case <-time.After(saltToUpdate.ExpirationTime.Sub(now)):
+                updatePublicSalt(saltToUpdate, settingsKey, lifeSpan, callback)
+            }
+        }()
+    }
+}
+
+func createSalt(settingsKey []byte, lifeSpan time.Duration, updateCallback SaltConsumer) *salt.Salt {
+    newSalt := getSalt(settingsKey, lifeSpan)
+
+    scheduleUpdateForSalt(newSalt, settingsKey, lifeSpan, updateCallback)
+
+    return newSalt
+}
diff --git a/plugins/autopeering/saltmanager/types.go b/plugins/autopeering/saltmanager/types.go
new file mode 100644
index 0000000000000000000000000000000000000000..650741e2e188ce605d0b6290f7f75076866c1ec2
--- /dev/null
+++ b/plugins/autopeering/saltmanager/types.go
@@ -0,0 +1,5 @@
+package saltmanager
+
+import "github.com/iotaledger/goshimmer/plugins/autopeering/salt"
+
+type SaltConsumer = func(salt *salt.Salt)
diff --git a/plugins/autopeering/server/constants.go b/plugins/autopeering/server/constants.go
new file mode 100644
index 0000000000000000000000000000000000000000..c122957ba31c3e7c99c1996ce7ce70cf891e4cab
--- /dev/null
+++ b/plugins/autopeering/server/constants.go
@@ -0,0 +1,7 @@
+package server
+
+import "time"
+
+const (
+    TCP_IDLE_TIMEOUT = 5 * time.Second
+)
diff --git a/plugins/autopeering/server/events.go b/plugins/autopeering/server/events.go
new file mode 100644
index 0000000000000000000000000000000000000000..734a280756b2e5a0416236178198fd883c40e8e5
--- /dev/null
+++ b/plugins/autopeering/server/events.go
@@ -0,0 +1,74 @@
+package server
+
+import (
+    "github.com/iotaledger/goshimmer/packages/network"
+    "github.com/iotaledger/goshimmer/plugins/autopeering/protocol"
+    "net"
+    "reflect"
+)
+
+type pluginEvents struct {
+    ReceivePeeringRequest    *addressPeeringRequestEvent
+    ReceiveTCPPeeringRequest *tcpPeeringRequestEvent
+    Error                    *ipErrorEvent
+}
+
+type tcpPeeringRequestEvent struct {
+    callbacks map[uintptr]ConnectionPeeringRequestConsumer
+}
+
+func (this *tcpPeeringRequestEvent) Attach(callback ConnectionPeeringRequestConsumer) {
+    this.callbacks[reflect.ValueOf(callback).Pointer()] = callback
+}
+
+func (this *tcpPeeringRequestEvent) Detach(callback ConnectionPeeringRequestConsumer) {
+    delete(this.callbacks, reflect.ValueOf(callback).Pointer())
+}
+
+func (this *tcpPeeringRequestEvent) Trigger(conn network.Connection, request *protocol.PeeringRequest) {
+    for _, callback := range this.callbacks {
+        callback(conn, request)
+    }
+}
+
+type addressPeeringRequestEvent struct {
+    callbacks map[uintptr]IPPeeringRequestConsumer
+}
+
+func (this *addressPeeringRequestEvent) Attach(callback IPPeeringRequestConsumer) {
+    this.callbacks[reflect.ValueOf(callback).Pointer()] = callback
+}
+
+func (this *addressPeeringRequestEvent) Detach(callback IPPeeringRequestConsumer) {
+    delete(this.callbacks, reflect.ValueOf(callback).Pointer())
+}
+
+func (this *addressPeeringRequestEvent) Trigger(ip net.IP, request *protocol.PeeringRequest) {
+    for _, callback := range this.callbacks {
+        callback(ip, request)
+    }
+}
+
+type ipErrorEvent struct {
+    callbacks map[uintptr]IPErrorConsumer
+}
+
+func (this *ipErrorEvent) Attach(callback IPErrorConsumer) {
+    this.callbacks[reflect.ValueOf(callback).Pointer()] = callback
+}
+
+func (this *ipErrorEvent) Detach(callback IPErrorConsumer) {
+    delete(this.callbacks, reflect.ValueOf(callback).Pointer())
+}
+
+func (this *ipErrorEvent) Trigger(ip net.IP, err error) {
+    for _, callback := range this.callbacks {
+        callback(ip, err)
+    }
+}
+
+var Events = &pluginEvents{
+    ReceivePeeringRequest:    &addressPeeringRequestEvent{make(map[uintptr]IPPeeringRequestConsumer)},
+    ReceiveTCPPeeringRequest: &tcpPeeringRequestEvent{make(map[uintptr]ConnectionPeeringRequestConsumer)},
+    Error:                    &ipErrorEvent{make(map[uintptr]IPErrorConsumer)},
+}
diff --git a/plugins/autopeering/server/server.go b/plugins/autopeering/server/server.go
new file mode 100644
index 0000000000000000000000000000000000000000..3615391bd0044eabf5c1e459c449c0300b813906
--- /dev/null
+++ b/plugins/autopeering/server/server.go
@@ -0,0 +1,20 @@
+package server
+
+import (
+    "github.com/iotaledger/goshimmer/packages/node"
+)
+
+func Configure(plugin *node.Plugin) {
+    ConfigureUDPServer(plugin)
+    ConfigureTCPServer(plugin)
+}
+
+func Run(plugin *node.Plugin) {
+    RunUDPServer(plugin)
+    RunTCPServer(plugin)
+}
+
+func Shutdown(plugin *node.Plugin) {
+    ShutdownUDPServer(plugin)
+    ShutdownTCPServer(plugin)
+}
diff --git a/plugins/autopeering/server/tcp_server.go b/plugins/autopeering/server/tcp_server.go
new file mode 100644
index 0000000000000000000000000000000000000000..266870b142bc0abc9684a48f7854788c4d59af6d
--- /dev/null
+++ b/plugins/autopeering/server/tcp_server.go
@@ -0,0 +1,73 @@
+package server
+
+import (
+    "github.com/iotaledger/goshimmer/packages/daemon"
+    "github.com/iotaledger/goshimmer/packages/network"
+    "github.com/iotaledger/goshimmer/packages/network/tcp"
+    "github.com/iotaledger/goshimmer/packages/node"
+    "github.com/iotaledger/goshimmer/plugins/autopeering/parameters"
+    "github.com/iotaledger/goshimmer/plugins/autopeering/protocol"
+    "math"
+    "net"
+    "strconv"
+)
+
+var tcpServer = tcp.NewServer()
+
+func ConfigureTCPServer(plugin *node.Plugin) {
+    tcpServer.Events.Connect.Attach(func(peer network.Connection) {
+        receivedData := make([]byte, protocol.PEERING_REQUEST_MARSHALLED_TOTAL_SIZE)
+
+        peer.SetTimeout(TCP_IDLE_TIMEOUT)
+
+        offset := 0
+        peer.OnReceiveData(func(data []byte) {
+            remainingCapacity := int(math.Min(float64(protocol.PEERING_REQUEST_MARSHALLED_TOTAL_SIZE- offset), float64(len(data))))
+
+            copy(receivedData[offset:], data[:remainingCapacity])
+            offset += len(data)
+
+            if offset >= protocol.PEERING_REQUEST_MARSHALLED_TOTAL_SIZE {
+                if peeringRequest, err := protocol.UnmarshalPeeringRequest(receivedData); err != nil {
+                    Events.Error.Trigger(peer.GetConnection().RemoteAddr().(*net.TCPAddr).IP, err)
+                } else {
+                    Events.ReceiveTCPPeeringRequest.Trigger(peer, peeringRequest)
+                }
+            }
+        })
+
+        go peer.HandleConnection()
+    })
+
+    tcpServer.Events.Error.Attach(func(err error) {
+        plugin.LogFailure(err.Error())
+    })
+    tcpServer.Events.Start.Attach(func() {
+        if *parameters.ADDRESS.Value == "0.0.0.0" {
+            plugin.LogSuccess("Starting TCP Server (port " + strconv.Itoa(*parameters.UDP_PORT.Value) + ") ... done")
+        } else {
+            plugin.LogSuccess("Starting TCP Server (" + *parameters.ADDRESS.Value + ":" + strconv.Itoa(*parameters.UDP_PORT.Value) + ") ... done")
+        }
+    })
+    tcpServer.Events.Shutdown.Attach(func() {
+        plugin.LogSuccess("Stopping TCP Server ... done")
+    })
+}
+
+func RunTCPServer(plugin *node.Plugin) {
+    daemon.BackgroundWorker(func() {
+        if *parameters.ADDRESS.Value == "0.0.0.0" {
+            plugin.LogInfo("Starting TCP Server (port " + strconv.Itoa(*parameters.UDP_PORT.Value) + ") ...")
+        } else {
+            plugin.LogInfo("Starting TCP Server (" + *parameters.ADDRESS.Value + ":" + strconv.Itoa(*parameters.UDP_PORT.Value) + ") ...")
+        }
+
+        tcpServer.Listen(*parameters.UDP_PORT.Value)
+    })
+}
+
+func ShutdownTCPServer(plugin *node.Plugin) {
+    plugin.LogInfo("Stopping TCP Server ...")
+
+    tcpServer.Shutdown()
+}
diff --git a/plugins/autopeering/server/types.go b/plugins/autopeering/server/types.go
new file mode 100644
index 0000000000000000000000000000000000000000..b368652ef58d97ed84f9fff29e409ca0e88cfbbf
--- /dev/null
+++ b/plugins/autopeering/server/types.go
@@ -0,0 +1,13 @@
+package server
+
+import (
+    "github.com/iotaledger/goshimmer/packages/network"
+    "github.com/iotaledger/goshimmer/plugins/autopeering/protocol"
+    "net"
+)
+
+type ConnectionPeeringRequestConsumer = func(conn network.Connection, request *protocol.PeeringRequest)
+
+type IPPeeringRequestConsumer = func(ip net.IP, request *protocol.PeeringRequest)
+
+type IPErrorConsumer = func(ip net.IP, err error)
diff --git a/plugins/autopeering/server/udp_server.go b/plugins/autopeering/server/udp_server.go
new file mode 100644
index 0000000000000000000000000000000000000000..89e729b632a4fad87d7a654680cb64ebfd9ae639
--- /dev/null
+++ b/plugins/autopeering/server/udp_server.go
@@ -0,0 +1,62 @@
+package server
+
+import (
+    "github.com/iotaledger/goshimmer/packages/daemon"
+    "github.com/iotaledger/goshimmer/packages/network/udp"
+    "github.com/iotaledger/goshimmer/packages/node"
+    "github.com/iotaledger/goshimmer/plugins/autopeering/parameters"
+    "github.com/iotaledger/goshimmer/plugins/autopeering/protocol"
+    "net"
+    "strconv"
+)
+
+var udpServer = udp.NewServer()
+
+func ConfigureUDPServer(plugin *node.Plugin) {
+    Events.Error.Attach(func(ip net.IP, err error) {
+        plugin.LogFailure(err.Error())
+    })
+
+    udpServer.Events.ReceiveData.Attach(processReceivedData)
+    udpServer.Events.Error.Attach(func(err error) {
+        plugin.LogFailure(err.Error())
+    })
+    udpServer.Events.Start.Attach(func() {
+        if *parameters.ADDRESS.Value == "0.0.0.0" {
+            plugin.LogSuccess("Starting UDP Server (port " + strconv.Itoa(*parameters.UDP_PORT.Value) + ") ... done")
+        } else {
+            plugin.LogSuccess("Starting UDP Server (" + *parameters.ADDRESS.Value + ":" + strconv.Itoa(*parameters.UDP_PORT.Value) + ") ... done")
+        }
+    })
+    udpServer.Events.Shutdown.Attach(func() {
+        plugin.LogSuccess("Stopping UDP Server ... done")
+    })
+}
+
+func RunUDPServer(plugin *node.Plugin) {
+    daemon.BackgroundWorker(func() {
+        if *parameters.ADDRESS.Value == "0.0.0.0" {
+            plugin.LogInfo("Starting UDP Server (port " + strconv.Itoa(*parameters.UDP_PORT.Value) + ") ...")
+        } else {
+            plugin.LogInfo("Starting UDP Server (" + *parameters.ADDRESS.Value + ":" + strconv.Itoa(*parameters.UDP_PORT.Value) + ") ...")
+        }
+
+        udpServer.Listen(*parameters.ADDRESS.Value, *parameters.UDP_PORT.Value)
+    })
+}
+
+func ShutdownUDPServer(plugin *node.Plugin) {
+    plugin.LogInfo("Stopping UDP Server ...")
+
+    udpServer.Shutdown()
+}
+
+func processReceivedData(addr *net.UDPAddr, data []byte) {
+
+    if peeringRequest, err := protocol.UnmarshalPeeringRequest(data); err != nil {
+        Events.Error.Trigger(addr.IP, err)
+    } else {
+        Events.ReceivePeeringRequest.Trigger(addr.IP, peeringRequest)
+    }
+}
+
diff --git a/plugins/cli/cli.go b/plugins/cli/cli.go
new file mode 100644
index 0000000000000000000000000000000000000000..5676ae7269da776d04f1c4c1e64e2b1c15627392
--- /dev/null
+++ b/plugins/cli/cli.go
@@ -0,0 +1,34 @@
+package cli
+
+import (
+    "flag"
+    "fmt"
+    "os"
+    "path/filepath"
+)
+
+func AddIntParameter(p *int, name string, usage string) {
+    flag.IntVar(p, name, *p, usage)
+}
+
+func AddStringParameter(p *string, name string, usage string) {
+    flag.StringVar(p, name, *p, usage)
+}
+
+func printUsage() {
+    _, err := fmt.Fprintf(
+        os.Stderr,
+        "\n" +
+            "SHIMMER 1.0\n\n" +
+            "  A lightweight modular IOTA node.\n\n" +
+            "Usage:\n\n" +
+            "  %s [OPTIONS]\n\n" +
+            "Options:\n\n",
+        filepath.Base(os.Args[0]),
+    )
+    if err != nil {
+        panic(err)
+    }
+
+    flag.PrintDefaults()
+}
diff --git a/plugins/cli/plugin.go b/plugins/cli/plugin.go
new file mode 100644
index 0000000000000000000000000000000000000000..fd3121b753196342d6ec3b4fa0a7d954bc31ff56
--- /dev/null
+++ b/plugins/cli/plugin.go
@@ -0,0 +1,43 @@
+package cli
+
+import (
+    "flag"
+    "github.com/iotaledger/goshimmer/packages/parameter"
+    "github.com/iotaledger/goshimmer/packages/node"
+    "strings"
+)
+
+func onAddIntParameter(param *parameter.IntParameter) {
+    flagName := strings.Replace(strings.Replace(strings.ToLower(param.Name), "/", "-", 1), "_", "-", -1)
+
+    AddIntParameter(param.Value, flagName, param.Description)
+}
+
+func onAddStringParameter(param *parameter.StringParameter) {
+    flagName := strings.Replace(strings.Replace(strings.ToLower(param.Name), "/", "-", 1), "_", "-", -1)
+
+    AddStringParameter(param.Value, flagName, param.Description)
+}
+
+func init() {
+    for _, param := range parameter.GetInts() {
+        onAddIntParameter(param)
+    }
+
+    for _, param := range parameter.GetStrings() {
+        onAddStringParameter(param)
+    }
+
+    parameter.Events.AddInt.Attach(onAddIntParameter)
+    parameter.Events.AddString.Attach(onAddStringParameter)
+
+    flag.Usage = printUsage
+
+    flag.Parse()
+}
+
+func configure(ctx *node.Plugin) {}
+
+func run(plugin *node.Plugin) {}
+
+var PLUGIN = node.NewPlugin("CLI", configure)
diff --git a/plugins/gracefulshutdown/constants.go b/plugins/gracefulshutdown/constants.go
new file mode 100644
index 0000000000000000000000000000000000000000..e63ac569efe5b0df58f2521503eae105c7f874e1
--- /dev/null
+++ b/plugins/gracefulshutdown/constants.go
@@ -0,0 +1,4 @@
+package gracefulshutdown
+
+// The maximum amount of time we wait for background processes to terminate. After that the process is killed.
+const WAIT_TO_KILL_TIME_IN_SECONDS = 10
diff --git a/plugins/gracefulshutdown/plugin.go b/plugins/gracefulshutdown/plugin.go
new file mode 100644
index 0000000000000000000000000000000000000000..cd6bc693a850164978fc42378e417b07dfdea76c
--- /dev/null
+++ b/plugins/gracefulshutdown/plugin.go
@@ -0,0 +1,42 @@
+package gracefulshutdown
+
+import (
+    "github.com/iotaledger/goshimmer/packages/daemon"
+    "github.com/iotaledger/goshimmer/packages/node"
+    "os"
+    "os/signal"
+    "strconv"
+    "syscall"
+    "time"
+)
+
+func run(plugin *node.Plugin) {
+    gracefulStop := make(chan os.Signal)
+    signal.Notify(gracefulStop, syscall.SIGTERM)
+    signal.Notify(gracefulStop, syscall.SIGINT)
+
+    go func() {
+        <- gracefulStop
+
+        plugin.LogWarning("Received shutdown request - waiting (max " + strconv.Itoa(WAIT_TO_KILL_TIME_IN_SECONDS) + " seconds) to finish processing ...")
+
+        go func() {
+            start := time.Now()
+            for x := range time.Tick(1 * time.Second) {
+                secondsSinceStart := x.Sub(start).Seconds()
+
+                if secondsSinceStart <= WAIT_TO_KILL_TIME_IN_SECONDS {
+                    plugin.LogWarning("Received shutdown request - waiting (max " + strconv.Itoa(WAIT_TO_KILL_TIME_IN_SECONDS - int(secondsSinceStart)) + " seconds) to finish processing ...")
+                } else {
+                    plugin.LogFailure("The background processes did not terminate in time! Forcing shutdown ...")
+
+                    os.Exit(1)
+                }
+            }
+        }()
+
+        daemon.Shutdown()
+    }()
+}
+
+var PLUGIN = node.NewPlugin("Graceful Shutdown", run)
diff --git a/plugins/statusscreen/constants.go b/plugins/statusscreen/constants.go
new file mode 100644
index 0000000000000000000000000000000000000000..d34a8a8ac4fe849d8706704a7d15ef96dedf1921
--- /dev/null
+++ b/plugins/statusscreen/constants.go
@@ -0,0 +1,7 @@
+package statusscreen
+
+import "time"
+
+const (
+    REPAINT_INTERVAL = 500 * time.Millisecond
+)
diff --git a/plugins/statusscreen/logger.go b/plugins/statusscreen/logger.go
new file mode 100644
index 0000000000000000000000000000000000000000..5fd4b29a57af173a1c245901e10e06369b693612
--- /dev/null
+++ b/plugins/statusscreen/logger.go
@@ -0,0 +1,44 @@
+package statusscreen
+
+import (
+    "github.com/iotaledger/goshimmer/packages/node"
+    "time"
+)
+
+func storeStatusMessage(pluginName string, message string, logLevel int) {
+    messageLog = append(messageLog, &StatusMessage{
+        Source:   pluginName,
+        LogLevel: logLevel,
+        Message:  message,
+        Time:     time.Now(),
+    })
+
+    if statusMessage, exists := statusMessages[pluginName]; !exists {
+        statusMessages[pluginName] = &StatusMessage{
+            Source:   pluginName,
+            LogLevel: logLevel,
+            Message:  message,
+            Time:     time.Now(),
+        }
+    } else {
+        statusMessage.LogLevel = logLevel
+        statusMessage.Message = message
+        statusMessage.Time = time.Now()
+    }
+}
+
+var DEFAULT_LOGGER = &node.Logger{
+    Enabled: true,
+    LogInfo: func(pluginName string, message string) {
+        storeStatusMessage(pluginName, message, node.LOG_LEVEL_INFO)
+    },
+    LogSuccess: func(pluginName string, message string) {
+        storeStatusMessage(pluginName, message, node.LOG_LEVEL_SUCCESS)
+    },
+    LogWarning: func(pluginName string, message string) {
+        storeStatusMessage(pluginName, message, node.LOG_LEVEL_WARNING)
+    },
+    LogFailure: func(pluginName string, message string) {
+        storeStatusMessage(pluginName, message, node.LOG_LEVEL_FAILURE)
+    },
+}
diff --git a/plugins/statusscreen/status_message.go b/plugins/statusscreen/status_message.go
new file mode 100644
index 0000000000000000000000000000000000000000..5f2a9ecdf058ed962104802be6b90ea8ea6a9b67
--- /dev/null
+++ b/plugins/statusscreen/status_message.go
@@ -0,0 +1,10 @@
+package statusscreen
+
+import "time"
+
+type StatusMessage struct {
+    Source   string
+    LogLevel int
+    Message  string
+    Time     time.Time
+}
diff --git a/plugins/statusscreen/statusscreen.go b/plugins/statusscreen/statusscreen.go
new file mode 100644
index 0000000000000000000000000000000000000000..df4e4f717fba2a1490c6859f919861bd0565bc91
--- /dev/null
+++ b/plugins/statusscreen/statusscreen.go
@@ -0,0 +1,119 @@
+package statusscreen
+
+import (
+    "github.com/gdamore/tcell"
+    "github.com/iotaledger/goshimmer/packages/daemon"
+    "github.com/iotaledger/goshimmer/packages/node"
+    "github.com/rivo/tview"
+    "time"
+)
+
+var statusMessages = make(map[string]*StatusMessage)
+var messageLog = make([]*StatusMessage, 0)
+
+var app *tview.Application
+
+func configure(plugin *node.Plugin) {
+    node.DEFAULT_LOGGER.Enabled = false
+
+    plugin.Node.AddLogger(DEFAULT_LOGGER)
+
+    daemon.Events.Shutdown.Attach(func() {
+        node.DEFAULT_LOGGER.Enabled = true
+
+        if app != nil {
+            app.Stop()
+        }
+    })
+}
+
+func run(plugin *node.Plugin) {
+    newPrimitive := func(text string) *tview.TextView {
+        textView := tview.NewTextView()
+
+        textView.
+            SetTextAlign(tview.AlignLeft).
+            SetText(" " + text)
+
+        return textView
+    }
+
+    app = tview.NewApplication()
+
+    headerBar := NewUIHeaderBar()
+
+    content := tview.NewGrid()
+    content.SetBackgroundColor(tcell.ColorWhite)
+    content.SetColumns(0)
+
+    footer := newPrimitive("")
+    footer.SetBackgroundColor(tcell.ColorDarkMagenta)
+    footer.SetTextColor(tcell.ColorWhite)
+
+    grid := tview.NewGrid().
+        SetRows(10, 0, 1).
+        SetColumns(0).
+        SetBorders(false).
+        AddItem(headerBar.Primitive, 0, 0, 1, 1, 0, 0, false).
+        AddItem(content, 1, 0, 1, 1, 0, 0, false).
+        AddItem(footer, 2, 0, 1, 1, 0, 0, false)
+
+    frame := tview.NewFrame(grid).
+        SetBorders(1, 1, 0, 0, 2, 2)
+    frame.SetBackgroundColor(tcell.ColorDarkGray)
+
+    app.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey {
+        if event.Key() == tcell.KeyCtrlC || event.Key() == tcell.KeyESC{
+            daemon.Shutdown()
+
+            return nil
+        }
+
+        return event
+    })
+
+    app.SetBeforeDrawFunc(func(screen tcell.Screen) bool {
+        headerBar.Update()
+
+        rows := make([]int, 1)
+        rows[0] = 1
+        _, _, _, height := content.GetRect()
+        for i := 0; i < len(messageLog) && i < height - 2; i++ {
+            rows = append(rows, 1)
+        }
+
+        content.Clear()
+        content.SetRows(rows...)
+
+        blankLine := newPrimitive("")
+        blankLine.SetBackgroundColor(tcell.ColorWhite)
+        content.AddItem(blankLine, 0, 0, 1, 1, 0, 0, false)
+
+        for i, message := range messageLog[len(messageLog) - len(rows) - 1 + 2:] {
+            if i < height - 2 {
+                content.AddItem(NewUILogEntry(*message).Primitive, i+1, 0, 1, 1, 0, 0, false)
+            }
+        }
+
+        return false
+    })
+
+    daemon.BackgroundWorker(func() {
+        for {
+            select {
+            case <- daemon.ShutdownSignal:
+                return
+            case <- time.After(1 * time.Second):
+                app.QueueUpdateDraw(func() {})
+            }
+        }
+    })
+
+    daemon.BackgroundWorker(func() {
+        if err := app.SetRoot(frame, true).SetFocus(frame).Run(); err != nil {
+            panic(err)
+        }
+    })
+}
+
+var PLUGIN = node.NewPlugin("Status Screen", configure, run)
diff --git a/plugins/statusscreen/ui_header_bar.go b/plugins/statusscreen/ui_header_bar.go
new file mode 100644
index 0000000000000000000000000000000000000000..0315e24fb8133cda0a72dc4c1947042a4f44b9f0
--- /dev/null
+++ b/plugins/statusscreen/ui_header_bar.go
@@ -0,0 +1,95 @@
+package statusscreen
+
+import (
+    "fmt"
+    "github.com/gdamore/tcell"
+    "github.com/iotaledger/goshimmer/packages/accountability"
+    "github.com/iotaledger/goshimmer/plugins/autopeering/peermanager"
+    "github.com/rivo/tview"
+    "strconv"
+    "time"
+)
+
+var start = time.Now()
+
+type UIHeaderBar struct {
+    Primitive     *tview.Grid
+    LogoContainer *tview.TextView
+    InfoContainer *tview.TextView
+}
+
+func NewUIHeaderBar() *UIHeaderBar {
+    headerBar := &UIHeaderBar{
+        Primitive: tview.NewGrid(),
+        LogoContainer: tview.NewTextView(),
+        InfoContainer: tview.NewTextView(),
+    }
+
+    headerBar.LogoContainer.
+        SetTextAlign(tview.AlignLeft).
+        SetTextColor(tcell.ColorWhite).
+        SetDynamicColors(true).
+        SetBackgroundColor(tcell.ColorDarkMagenta)
+
+    headerBar.InfoContainer.
+        SetTextAlign(tview.AlignRight).
+        SetTextColor(tcell.ColorWhite).
+        SetDynamicColors(true).
+        SetBackgroundColor(tcell.ColorDarkMagenta)
+
+    headerBar.Primitive.
+        SetColumns(17, 0).
+        SetRows(0).
+        SetBorders(false).
+        AddItem(headerBar.LogoContainer, 0, 0, 1, 1, 0, 0, false).
+        AddItem(headerBar.InfoContainer, 0, 1, 1, 1, 0, 0, false)
+
+
+    headerBar.printLogo()
+    headerBar.Update()
+
+    return headerBar
+}
+
+func (headerBar *UIHeaderBar) Update() {
+    duration := time.Now().Sub(start)
+
+    headerBar.InfoContainer.Clear()
+
+    fmt.Fprintln(headerBar.InfoContainer)
+    fmt.Fprintln(headerBar.InfoContainer, "[::d]COO-LESS IOTA PROTOTYPE  -  [::b]Status: [green::b]SYNCED  ")
+    fmt.Fprintln(headerBar.InfoContainer)
+    fmt.Fprintln(headerBar.InfoContainer)
+    fmt.Fprintln(headerBar.InfoContainer)
+    fmt.Fprintln(headerBar.InfoContainer)
+    fmt.Fprintln(headerBar.InfoContainer, "[::b]Node Identifier: [::d]" + accountability.OWN_ID.StringIdentifier + "  ")
+    fmt.Fprintln(headerBar.InfoContainer, "[::b]Known Peers: [::d]" + strconv.Itoa(len(peermanager.KNOWN_PEERS.Peers)) + "  ")
+    fmt.Fprintf(headerBar.InfoContainer, "[::b]Uptime: [::d]");
+
+    if int(duration.Seconds()) / (60 * 60 * 24) > 0 {
+        // d
+        fmt.Fprintf(headerBar.InfoContainer, "%02dd ", int(duration.Hours()) / 24)
+    }
+
+    if int(duration.Seconds()) / (60 * 60) > 0 {
+        fmt.Fprintf(headerBar.InfoContainer, "%02dh ", int(duration.Hours()) % 24)
+    }
+
+    if int(duration.Seconds()) / 60 > 0 {
+        fmt.Fprintf(headerBar.InfoContainer, "%02dm ", int(duration.Minutes()) % 60)
+    }
+
+    fmt.Fprintf(headerBar.InfoContainer, "%02ds  ", int(duration.Seconds()) % 60)
+}
+
+func (headerBar *UIHeaderBar) printLogo() {
+    fmt.Fprintln(headerBar.LogoContainer, "")
+    fmt.Fprintln(headerBar.LogoContainer, "   SHIMMER 1.0.0")
+    fmt.Fprintln(headerBar.LogoContainer, "  ┏━━━━━━┳━━━━━━┓")
+    fmt.Fprintln(headerBar.LogoContainer, "    ━━━┓ ┃ ┏━━━")
+    fmt.Fprintln(headerBar.LogoContainer, "     ┓ ┃ ┃ ┃ ┏")
+    fmt.Fprintln(headerBar.LogoContainer, "     ┃ ┗ ┃ ┛ ┃")
+    fmt.Fprintln(headerBar.LogoContainer, "     ┗ ┏ ┃ ┓ ┛")
+    fmt.Fprintln(headerBar.LogoContainer, "       ┃ ┃ ┃")
+    fmt.Fprintln(headerBar.LogoContainer, "         ┻")
+}
diff --git a/plugins/statusscreen/ui_log.go b/plugins/statusscreen/ui_log.go
new file mode 100644
index 0000000000000000000000000000000000000000..3f0826aee15b914d1949b3b85a49c6d6e5301eca
--- /dev/null
+++ b/plugins/statusscreen/ui_log.go
@@ -0,0 +1,15 @@
+package statusscreen
+
+import "github.com/rivo/tview"
+
+type UILog struct {
+    Primitive *tview.Grid
+}
+
+func NewUILog() *UILog {
+    uiLog := &UILog{
+        Primitive: tview.NewGrid(),
+    }
+
+    return uiLog
+}
diff --git a/plugins/statusscreen/ui_log_entry.go b/plugins/statusscreen/ui_log_entry.go
new file mode 100644
index 0000000000000000000000000000000000000000..0ee917dbefce20730c487e737ecc46c2216ff4d3
--- /dev/null
+++ b/plugins/statusscreen/ui_log_entry.go
@@ -0,0 +1,69 @@
+package statusscreen
+
+import (
+    "fmt"
+    "github.com/gdamore/tcell"
+    "github.com/iotaledger/goshimmer/packages/node"
+    "github.com/rivo/tview"
+)
+
+type UILogEntry struct {
+    Primitive         *tview.Grid
+    TimeContainer     *tview.TextView
+    MessageContainer  *tview.TextView
+    LogLevelContainer *tview.TextView
+}
+
+func NewUILogEntry(message StatusMessage) *UILogEntry {
+    logEntry := &UILogEntry{
+        Primitive:         tview.NewGrid(),
+        TimeContainer:     tview.NewTextView(),
+        MessageContainer:  tview.NewTextView(),
+        LogLevelContainer: tview.NewTextView(),
+    }
+
+    logEntry.TimeContainer.SetBackgroundColor(tcell.ColorWhite)
+    logEntry.TimeContainer.SetTextColor(tcell.ColorBlack)
+    logEntry.TimeContainer.SetDynamicColors(true)
+
+    logEntry.MessageContainer.SetBackgroundColor(tcell.ColorWhite)
+    logEntry.MessageContainer.SetTextColor(tcell.ColorBlack)
+    logEntry.MessageContainer.SetDynamicColors(true)
+
+    logEntry.LogLevelContainer.SetBackgroundColor(tcell.ColorWhite)
+    logEntry.LogLevelContainer.SetTextColor(tcell.ColorBlack)
+    logEntry.LogLevelContainer.SetDynamicColors(true)
+
+    textColor := "black"
+    switch message.LogLevel {
+    case node.LOG_LEVEL_INFO:
+        fmt.Fprintf(logEntry.LogLevelContainer, " [black::d][ [blue::d]INFO [black::d]]")
+    case node.LOG_LEVEL_SUCCESS:
+        fmt.Fprintf(logEntry.LogLevelContainer, " [black::d][  [green::d]OK  [black::d]]")
+    case node.LOG_LEVEL_WARNING:
+        fmt.Fprintf(logEntry.LogLevelContainer, " [black::d][ [yellow::d]WARN [black::d]]")
+
+        textColor = "yellow"
+    case node.LOG_LEVEL_FAILURE:
+        fmt.Fprintf(logEntry.LogLevelContainer, " [black::d][ [red::d]FAIL [black::d]]")
+
+        textColor = "red"
+    }
+
+    fmt.Fprintf(logEntry.TimeContainer, "  [black::b]" + message.Time.Format("01/02/2006 03:04:05 PM"))
+    if message.Source == "Node" {
+        fmt.Fprintf(logEntry.MessageContainer, "[" + textColor + "::d]" + message.Message)
+    } else {
+        fmt.Fprintf(logEntry.MessageContainer, "[" + textColor + "::d]" + message.Source + ": " + message.Message)
+    }
+
+    logEntry.Primitive.
+        SetColumns(25, 0, 11).
+        SetRows(1).
+        SetBorders(false).
+        AddItem(logEntry.TimeContainer, 0, 0, 1, 1, 0, 0, false).
+        AddItem(logEntry.MessageContainer, 0, 1, 1, 1, 0, 0, false).
+        AddItem(logEntry.LogLevelContainer, 0, 2, 1, 1, 0, 0, false)
+
+    return logEntry
+}