diff --git a/Makefile b/Makefile
index 117f48a3a54be1a5a4ed6dd976d193e03adfbcdc..c899add28240bd6c3c47544c55a47d4e9c47c8aa 100644
--- a/Makefile
+++ b/Makefile
@@ -14,11 +14,13 @@ build:
 update_release:
 	GOFLAGS="" go get -d gitlab.com/elixxir/client@release
 	GOFLAGS="" go get gitlab.com/elixxir/crypto@release
+	GOFLAGS="" go get gitlab.com/elixxir/primitives@release
 	GOFLAGS="" go get gitlab.com/xx_network/primitives@release
 
 update_master:
 	GOFLAGS="" go get -d gitlab.com/elixxir/client@master
 	GOFLAGS="" go get gitlab.com/elixxir/crypto@master
+	GOFLAGS="" go get gitlab.com/elixxir/primitives@master
 	GOFLAGS="" go get gitlab.com/xx_network/primitives@master
 
 binary:
diff --git a/go.mod b/go.mod
index f47b1d74b356269f59a8bb673978aa1d88591b08..12ed3aa7ac1e7cb2037738036340f579940cbf5d 100644
--- a/go.mod
+++ b/go.mod
@@ -7,8 +7,9 @@ require (
 	github.com/hack-pad/go-indexeddb v0.2.0
 	github.com/pkg/errors v0.9.1
 	github.com/spf13/jwalterweatherman v1.1.0
-	gitlab.com/elixxir/client v1.5.1-0.20220920212200-25ceacdcbd31
-	gitlab.com/elixxir/crypto v0.0.7-0.20220920002307-5541473e9aa7
+	gitlab.com/elixxir/client v1.5.1-0.20221004163122-5a4635dce0fa
+	gitlab.com/elixxir/crypto v0.0.7-0.20221003185354-b091598d2322
+	gitlab.com/elixxir/primitives v0.0.3-0.20220901220638-1acc75fabdc6
 	gitlab.com/xx_network/primitives v0.0.4-0.20220809193445-9fc0a5209548
 )
 
@@ -38,10 +39,9 @@ require (
 	github.com/tyler-smith/go-bip39 v1.1.0 // indirect
 	github.com/zeebo/blake3 v0.2.3 // indirect
 	gitlab.com/elixxir/bloomfilter v0.0.0-20211222005329-7d931ceead6f // indirect
-	gitlab.com/elixxir/comms v0.0.4-0.20220913220502-eed192f654bd // indirect
+	gitlab.com/elixxir/comms v0.0.4-0.20220916185715-f1e9a5eda939 // indirect
 	gitlab.com/elixxir/ekv v0.2.1 // indirect
-	gitlab.com/elixxir/primitives v0.0.3-0.20220901220638-1acc75fabdc6 // indirect
-	gitlab.com/xx_network/comms v0.0.4-0.20220913215811-c4bf83b27de3 // indirect
+	gitlab.com/xx_network/comms v0.0.4-0.20220916185248-8a984b8594de // indirect
 	gitlab.com/xx_network/crypto v0.0.5-0.20220913213008-98764f5b3287 // indirect
 	gitlab.com/xx_network/ring v0.0.3-0.20220222211904-da613960ad93 // indirect
 	go.uber.org/atomic v1.10.0 // indirect
diff --git a/go.sum b/go.sum
index 3f19ed257d84bf386fc99877142b9cfd1ba5a2ce..34119701844d9e3c94cd258b9a9c4b1aaea848b5 100644
--- a/go.sum
+++ b/go.sum
@@ -614,15 +614,15 @@ github.com/zeebo/pcg v1.0.1 h1:lyqfGeWiv4ahac6ttHs+I5hwtH/+1mrhlCtVNQM2kHo=
 github.com/zeebo/pcg v1.0.1/go.mod h1:09F0S9iiKrwn9rlI5yjLkmrug154/YRW6KnnXVDM/l4=
 gitlab.com/elixxir/bloomfilter v0.0.0-20211222005329-7d931ceead6f h1:yXGvNBqzZwAhDYlSnxPRbgor6JWoOt1Z7s3z1O9JR40=
 gitlab.com/elixxir/bloomfilter v0.0.0-20211222005329-7d931ceead6f/go.mod h1:H6jztdm0k+wEV2QGK/KYA+MY9nj9Zzatux/qIvDDv3k=
-gitlab.com/elixxir/client v1.5.1-0.20220920212200-25ceacdcbd31 h1:+Di3jF1Vdmr+gQGYwJOSKqbpRylTzLVP81fzkdkpegw=
-gitlab.com/elixxir/client v1.5.1-0.20220920212200-25ceacdcbd31/go.mod h1:pX1uLFS8v6pNVzJEcfbMUrYPTWLPl8p71ghqW2Xm0Ns=
-gitlab.com/elixxir/comms v0.0.4-0.20220913220502-eed192f654bd h1:2nHE7EoptSTBFjCxMeAveKT6urbguCwgg8Jx7XYEVe4=
-gitlab.com/elixxir/comms v0.0.4-0.20220913220502-eed192f654bd/go.mod h1:AO6XkMhaHJW8eXlgL5m3UUcJqsSP8F5Wm1GX+wyq/rw=
+gitlab.com/elixxir/client v1.5.1-0.20221004163122-5a4635dce0fa h1:sjZ+73Jesh/wU036YbZ5UAGjLIeKCVscf7sQDHMC4DM=
+gitlab.com/elixxir/client v1.5.1-0.20221004163122-5a4635dce0fa/go.mod h1:wuTIcLuMnvIGSo8i/Gg/SbYF57bE+CbKPpA1Xbk2AKk=
+gitlab.com/elixxir/comms v0.0.4-0.20220916185715-f1e9a5eda939 h1:+VRx2ULHKs040bBhDAOKNCZnbcXxUk3jD9JoKQzQpLk=
+gitlab.com/elixxir/comms v0.0.4-0.20220916185715-f1e9a5eda939/go.mod h1:AO6XkMhaHJW8eXlgL5m3UUcJqsSP8F5Wm1GX+wyq/rw=
 gitlab.com/elixxir/crypto v0.0.0-20200804182833-984246dea2c4/go.mod h1:ucm9SFKJo+K0N2GwRRpaNr+tKXMIOVWzmyUD0SbOu2c=
 gitlab.com/elixxir/crypto v0.0.3/go.mod h1:ZNgBOblhYToR4m8tj4cMvJ9UsJAUKq+p0gCp07WQmhA=
 gitlab.com/elixxir/crypto v0.0.7-0.20220913220142-ab0771bad0af/go.mod h1:QF8SzsrYh9Elip9EUYUDAhPjqO9DGrrrQxYHvn+VXok=
-gitlab.com/elixxir/crypto v0.0.7-0.20220920002307-5541473e9aa7 h1:9IsBtL8zcUG86XcfNUVIKcnlL5tyKlyQt1cJ5nogr1U=
-gitlab.com/elixxir/crypto v0.0.7-0.20220920002307-5541473e9aa7/go.mod h1:QF8SzsrYh9Elip9EUYUDAhPjqO9DGrrrQxYHvn+VXok=
+gitlab.com/elixxir/crypto v0.0.7-0.20221003185354-b091598d2322 h1:8unQE70BDNRXTWUbjOO9d4kWyh19LySlTZo0Jqx0gPE=
+gitlab.com/elixxir/crypto v0.0.7-0.20221003185354-b091598d2322/go.mod h1:QF8SzsrYh9Elip9EUYUDAhPjqO9DGrrrQxYHvn+VXok=
 gitlab.com/elixxir/ekv v0.2.1 h1:dtwbt6KmAXG2Tik5d60iDz2fLhoFBgWwST03p7T+9Is=
 gitlab.com/elixxir/ekv v0.2.1/go.mod h1:USLD7xeDnuZEavygdrgzNEwZXeLQJK/w1a+htpN+JEU=
 gitlab.com/elixxir/primitives v0.0.0-20200731184040-494269b53b4d/go.mod h1:OQgUZq7SjnE0b+8+iIAT2eqQF+2IFHn73tOo+aV11mg=
@@ -634,8 +634,9 @@ gitlab.com/elixxir/primitives v0.0.3-0.20220810173935-592f34a88326/go.mod h1:9Bb
 gitlab.com/elixxir/primitives v0.0.3-0.20220901220638-1acc75fabdc6 h1:/cxxZBP5jTPDpC3zgOx9vV1ojmJyG8pYtkl3IbcewNQ=
 gitlab.com/elixxir/primitives v0.0.3-0.20220901220638-1acc75fabdc6/go.mod h1:9Bb2+u+CDSwsEU5Droo6saDAXuBDvLRjexpBhPAYxhA=
 gitlab.com/xx_network/comms v0.0.0-20200805174823-841427dd5023/go.mod h1:owEcxTRl7gsoM8c3RQ5KAm5GstxrJp5tn+6JfQ4z5Hw=
-gitlab.com/xx_network/comms v0.0.4-0.20220913215811-c4bf83b27de3 h1:7mReTvEUVoI5Qpltcmbodc/j6rdPPHDIvenY4ZmWP7o=
 gitlab.com/xx_network/comms v0.0.4-0.20220913215811-c4bf83b27de3/go.mod h1:E2QKOKyPKLRjLUwMxgZpTKueEsHDEqshfqOHJ54ttxU=
+gitlab.com/xx_network/comms v0.0.4-0.20220916185248-8a984b8594de h1:44VKuVgT6X1l+MX8/oNmYORA+pa4nkOWV8hYxi4SCzc=
+gitlab.com/xx_network/comms v0.0.4-0.20220916185248-8a984b8594de/go.mod h1:E2QKOKyPKLRjLUwMxgZpTKueEsHDEqshfqOHJ54ttxU=
 gitlab.com/xx_network/crypto v0.0.3/go.mod h1:DF2HYvvCw9wkBybXcXAgQMzX+MiGbFPjwt3t17VRqRE=
 gitlab.com/xx_network/crypto v0.0.4/go.mod h1:+lcQEy+Th4eswFgQDwT0EXKp4AXrlubxalwQFH5O0Mk=
 gitlab.com/xx_network/crypto v0.0.5-0.20220913213008-98764f5b3287 h1:Jd71F8f/8rieWybMqkxpKKZVVyGkeCNZWZcviGGnQ9A=
diff --git a/indexedDb/implementation.go b/indexedDb/implementation.go
index e88499d22e452013be2c39de73f057a74ec163fd..1d8d804074140da708672192358375f6a3ebe4dd 100644
--- a/indexedDb/implementation.go
+++ b/indexedDb/implementation.go
@@ -13,12 +13,14 @@ import (
 	"context"
 	"encoding/base64"
 	"encoding/json"
+	"sync"
+	"syscall/js"
+	"time"
+
 	"github.com/hack-pad/go-indexeddb/idb"
 	"github.com/pkg/errors"
 	jww "github.com/spf13/jwalterweatherman"
 	"gitlab.com/elixxir/xxdk-wasm/utils"
-	"syscall/js"
-	"time"
 
 	"gitlab.com/elixxir/client/channels"
 	"gitlab.com/elixxir/client/cmix/rounds"
@@ -35,7 +37,9 @@ const dbTimeout = time.Second
 // system passed an object that adheres to in order to get events on the
 // channel.
 type wasmModel struct {
-	db *idb.Database
+	db                *idb.Database
+	receivedMessageCB MessageReceivedCallback
+	updateMux         sync.Mutex
 }
 
 // newContext builds a context for database operations.
@@ -49,7 +53,7 @@ func (w *wasmModel) JoinChannel(channel *cryptoBroadcast.Channel) {
 
 	// Build object
 	newChannel := Channel{
-		Id:          channel.ReceptionID.Marshal(),
+		ID:          channel.ReceptionID.Marshal(),
 		Name:        channel.Name,
 		Description: channel.Description,
 	}
@@ -83,7 +87,7 @@ func (w *wasmModel) JoinChannel(channel *cryptoBroadcast.Channel) {
 	}
 
 	// Perform the operation
-	_, err = store.Add(channelObj)
+	_, err = store.Put(channelObj)
 	if err != nil {
 		jww.ERROR.Printf("%+v", errors.WithMessagef(parentErr,
 			"Unable to Add Channel: %+v", err))
@@ -145,16 +149,32 @@ func (w *wasmModel) LeaveChannel(channelID *id.ID) {
 // It may be called multiple times on the same message; it is incumbent on the
 // user of the API to filter such called by message ID.
 func (w *wasmModel) ReceiveMessage(channelID *id.ID,
-	messageID cryptoChannel.MessageID, senderUsername string, text string,
-	timestamp time.Time, lease time.Duration, _ rounds.Round,
-	status channels.SentStatus) {
-	parentErr := errors.New("failed to ReceiveMessage")
+	messageID cryptoChannel.MessageID, nickname, text string,
+	identity cryptoChannel.Identity, timestamp time.Time, lease time.Duration,
+	round rounds.Round, mType channels.MessageType,
+	status channels.SentStatus) uint64 {
+
+	msgToInsert := buildMessage(channelID.Marshal(), messageID.Bytes(), nil,
+		nickname, text, identity, timestamp, lease, round.ID, mType, status)
+
+	// Attempt a lookup on the MessageID if it is non-zero to find an existing
+	// entry for it. This occurs any time a sender receives their own message
+	// from the mixnet.
+	if !messageID.Equals(cryptoChannel.MessageID{}) {
+		uuid, err := w.msgIDLookup(messageID)
+		if err != nil {
+			// message is already in the database, no insert necessary
+			return uuid
+		}
+	}
 
-	err := w.receiveHelper(buildMessage(channelID.Marshal(), messageID.Bytes(),
-		nil, senderUsername, text, timestamp, lease, status))
+	uuid, err := w.receiveHelper(msgToInsert)
 	if err != nil {
-		jww.ERROR.Printf("%+v", errors.Wrap(parentErr, err.Error()))
+		jww.ERROR.Printf("Failed to receiver message: %+v", err)
 	}
+
+	go w.receivedMessageCB(uuid, channelID, false)
+	return uuid
 }
 
 // ReceiveReply is called whenever a message is received that is a reply on a
@@ -165,15 +185,32 @@ func (w *wasmModel) ReceiveMessage(channelID *id.ID,
 // the initial message. As a result, it may be important to buffer replies.
 func (w *wasmModel) ReceiveReply(channelID *id.ID,
 	messageID cryptoChannel.MessageID, replyTo cryptoChannel.MessageID,
-	senderUsername string, text string, timestamp time.Time,
-	lease time.Duration, _ rounds.Round, status channels.SentStatus) {
-	parentErr := errors.New("failed to ReceiveReply")
+	nickname, text string, identity cryptoChannel.Identity, timestamp time.Time,
+	lease time.Duration, round rounds.Round, mType channels.MessageType,
+	status channels.SentStatus) uint64 {
+
+	msgToInsert := buildMessage(channelID.Marshal(), messageID.Bytes(),
+		replyTo.Bytes(), nickname, text, identity, timestamp, lease, round.ID,
+		mType, status)
+
+	// Attempt a lookup on the MessageID if it is non-zero to find an existing
+	// entry for it. This occurs any time a sender receives their own message
+	// from the mixnet.
+	if !messageID.Equals(cryptoChannel.MessageID{}) {
+		uuid, err := w.msgIDLookup(messageID)
+		if err != nil {
+			// message is already in the database, no insert necessary
+			return uuid
+		}
+	}
+
+	uuid, err := w.receiveHelper(msgToInsert)
 
-	err := w.receiveHelper(buildMessage(channelID.Marshal(), messageID.Bytes(),
-		replyTo.Bytes(), senderUsername, text, timestamp, lease, status))
 	if err != nil {
-		jww.ERROR.Printf("%+v", errors.Wrap(parentErr, err.Error()))
+		jww.ERROR.Printf("Failed to receive reply: %+v", err)
 	}
+	go w.receivedMessageCB(uuid, channelID, false)
+	return uuid
 }
 
 // ReceiveReaction is called whenever a reaction to a message is received on a
@@ -184,26 +221,50 @@ func (w *wasmModel) ReceiveReply(channelID *id.ID,
 // the initial message. As a result, it may be important to buffer reactions.
 func (w *wasmModel) ReceiveReaction(channelID *id.ID,
 	messageID cryptoChannel.MessageID, reactionTo cryptoChannel.MessageID,
-	senderUsername string, reaction string, timestamp time.Time,
-	lease time.Duration, _ rounds.Round, status channels.SentStatus) {
-	parentErr := errors.New("failed to ReceiveReaction")
+	nickname, reaction string, identity cryptoChannel.Identity,
+	timestamp time.Time, lease time.Duration, round rounds.Round,
+	mType channels.MessageType, status channels.SentStatus) uint64 {
+
+	msgToInsert := buildMessage(channelID.Marshal(), messageID.Bytes(),
+		reactionTo.Bytes(), nickname, reaction, identity, timestamp, lease,
+		round.ID, mType, status)
+
+	// Attempt a lookup on the MessageID if it is non-zero to find
+	// an existing entry for it. This occurs any time a sender
+	// receives their own message from the mixnet.
+	if !messageID.Equals(cryptoChannel.MessageID{}) {
+		uuid, err := w.msgIDLookup(messageID)
+		if err != nil {
+			// message is already in the database, no insert necessary
+			return uuid
+		}
+	}
 
-	err := w.receiveHelper(buildMessage(channelID.Marshal(), messageID.Bytes(),
-		reactionTo.Bytes(), senderUsername, reaction, timestamp, lease, status))
+	uuid, err := w.receiveHelper(msgToInsert)
 	if err != nil {
-		jww.ERROR.Printf("%+v", errors.Wrap(parentErr, err.Error()))
+		jww.ERROR.Printf("Failed to receive reaction: %+v", err)
 	}
+	go w.receivedMessageCB(uuid, channelID, false)
+	return uuid
 }
 
 // UpdateSentStatus is called whenever the [channels.SentStatus] of a message
-// has changed.
+// has changed. At this point the message ID goes from empty/unknown to
+// populated.
+//
 // TODO: Potential race condition due to separate get/update operations.
-func (w *wasmModel) UpdateSentStatus(messageID cryptoChannel.MessageID,
-	status channels.SentStatus) {
+func (w *wasmModel) UpdateSentStatus(uuid uint64, messageID cryptoChannel.MessageID,
+	timestamp time.Time, round rounds.Round, status channels.SentStatus) {
 	parentErr := errors.New("failed to UpdateSentStatus")
 
+	// FIXME: this is a bit of race condition without the mux.
+	//        This should be done via the transactions (i.e., make a
+	//        special version of receiveHelper)
+	w.updateMux.Lock()
+	defer w.updateMux.Unlock()
+
 	// Convert messageID to the key generated by json.Marshal
-	key := js.ValueOf(base64.StdEncoding.EncodeToString(messageID[:]))
+	key := js.ValueOf(uuid)
 
 	// Use the key to get the existing Message
 	currentMsg, err := w.get(messageStoreName, key)
@@ -218,59 +279,95 @@ func (w *wasmModel) UpdateSentStatus(messageID cryptoChannel.MessageID,
 		return
 	}
 	newMessage.Status = uint8(status)
+	if !messageID.Equals(cryptoChannel.MessageID{}) {
+		newMessage.MessageID = messageID.Bytes()
+	}
+
+	if round.ID == 0 {
+		newMessage.Round = uint64(round.ID)
+	}
+
+	if !timestamp.Equal(time.Time{}) {
+		newMessage.Timestamp = timestamp
+	}
 
 	// Store the updated Message
-	err = w.receiveHelper(newMessage)
+	_, err = w.receiveHelper(newMessage)
 	if err != nil {
 		jww.ERROR.Printf("%+v", errors.Wrap(parentErr, err.Error()))
 	}
+	channelID := &id.ID{}
+	copy(channelID[:], newMessage.ChannelID)
+	go w.receivedMessageCB(uuid, channelID, true)
 }
 
 // buildMessage is a private helper that converts typical [channels.EventModel]
 // inputs into a basic Message structure for insertion into storage.
-func buildMessage(channelID, messageID, parentId []byte, senderUsername,
-	text string, timestamp time.Time, lease time.Duration,
+// NOTE: ID is not set inside this function because we want to use the
+//       autoincrement key by default. If you are trying to overwrite
+//       an existing message, then you need to set it manually
+//       yourself.
+func buildMessage(channelID, messageID, parentID []byte, nickname, text string,
+	identity cryptoChannel.Identity, timestamp time.Time, lease time.Duration,
+	round id.Round, mType channels.MessageType,
 	status channels.SentStatus) *Message {
 	return &Message{
-		Id:              messageID,
-		SenderUsername:  senderUsername,
-		ChannelId:       channelID,
-		ParentMessageId: parentId,
+		MessageID:       messageID,
+		Nickname:        nickname,
+		ChannelID:       channelID,
+		ParentMessageID: parentID,
 		Timestamp:       timestamp,
 		Lease:           lease,
 		Status:          uint8(status),
 		Hidden:          false,
 		Pinned:          false,
 		Text:            text,
+		Type:            uint16(mType),
+		Round:           uint64(round),
+		// User Identity Info
+		Pubkey:         identity.PubKey,
+		Codename:       identity.Codename,
+		Color:          identity.Color,
+		Extension:      identity.Extension,
+		CodesetVersion: identity.CodesetVersion,
 	}
 }
 
 // receiveHelper is a private helper for receiving any sort of message.
-func (w *wasmModel) receiveHelper(newMessage *Message) error {
+func (w *wasmModel) receiveHelper(newMessage *Message) (uint64,
+	error) {
 	// Convert to jsObject
 	newMessageJson, err := json.Marshal(newMessage)
 	if err != nil {
-		return errors.Errorf("Unable to marshal Message: %+v", err)
+		return 0, errors.Errorf("Unable to marshal Message: %+v", err)
 	}
 	messageObj, err := utils.JsonToJS(newMessageJson)
 	if err != nil {
-		return errors.Errorf("Unable to marshal Message: %+v", err)
+		return 0, errors.Errorf("Unable to marshal Message: %+v", err)
+	}
+
+	// NOTE: This is weird, but correct. When the "ID" field is 0, we
+	// unset it from the JSValue so that it is auto-populated and
+	// incremented.
+	if newMessage.ID == 0 {
+		messageObj.JSValue().Delete("id")
 	}
 
 	// Prepare the Transaction
 	txn, err := w.db.Transaction(idb.TransactionReadWrite, messageStoreName)
 	if err != nil {
-		return errors.Errorf("Unable to create Transaction: %+v", err)
+		return 0, errors.Errorf("Unable to create Transaction: %+v",
+			err)
 	}
 	store, err := txn.ObjectStore(messageStoreName)
 	if err != nil {
-		return errors.Errorf("Unable to get ObjectStore: %+v", err)
+		return 0, errors.Errorf("Unable to get ObjectStore: %+v", err)
 	}
 
 	// Perform the upsert (put) operation
-	_, err = store.Put(messageObj)
+	addReq, err := store.Put(messageObj)
 	if err != nil {
-		return errors.Errorf("Unable to upsert Message: %+v", err)
+		return 0, errors.Errorf("Unable to upsert Message: %+v", err)
 	}
 
 	// Wait for the operation to return
@@ -278,11 +375,15 @@ func (w *wasmModel) receiveHelper(newMessage *Message) error {
 	err = txn.Await(ctx)
 	cancel()
 	if err != nil {
-		return errors.Errorf("Upserting Message failed: %+v", err)
+		return 0, errors.Errorf("Upserting Message failed: %+v", err)
 	}
+	res, _ := addReq.Result()
+	uuid := uint64(res.Int())
 	jww.DEBUG.Printf(
-		"Successfully stored message from %s", newMessage.SenderUsername)
-	return nil
+		"Successfully stored message from %s, id %d",
+		newMessage.Codename, uuid)
+
+	return uuid, nil
 }
 
 // get is a generic private helper for getting values from the given
@@ -324,6 +425,56 @@ func (w *wasmModel) get(objectStoreName string, key js.Value) (string, error) {
 	return resultStr, nil
 }
 
+func (w *wasmModel) msgIDLookup(messageID cryptoChannel.MessageID) (uint64,
+	error) {
+	parentErr := errors.Errorf("failed to get %s/%s", messageStoreName,
+		messageID)
+
+	// Prepare the Transaction
+	txn, err := w.db.Transaction(idb.TransactionReadOnly, messageStoreName)
+	if err != nil {
+		return 0, errors.WithMessagef(parentErr,
+			"Unable to create Transaction: %+v", err)
+	}
+	store, err := txn.ObjectStore(messageStoreName)
+	if err != nil {
+		return 0, errors.WithMessagef(parentErr,
+			"Unable to get ObjectStore: %+v", err)
+	}
+	idx, err := store.Index(messageStoreMessageIndex)
+	if err != nil {
+		return 0, errors.WithMessagef(parentErr,
+			"Unable to get index: %+v", err)
+	}
+
+	msgIDStr := base64.StdEncoding.EncodeToString(messageID.Bytes())
+
+	keyReq, err := idx.Get(js.ValueOf(msgIDStr))
+	if err != nil {
+		return 0, errors.WithMessagef(parentErr,
+			"Unable to get keyReq: %+v", err)
+	}
+	// Wait for the operation to return
+	ctx, cancel := newContext()
+	keyObj, err := keyReq.Await(ctx)
+	cancel()
+	if err != nil {
+		return 0, errors.WithMessagef(parentErr,
+			"Unable to get from ObjectStore: %+v", err)
+	}
+
+	// Process result into string
+	resultStr := utils.JsToJson(keyObj)
+	jww.DEBUG.Printf("Index lookup of %s/%s/%s: %s", messageStoreName,
+		messageStoreMessageIndex, msgIDStr, resultStr)
+
+	uuid := uint64(0)
+	if !keyObj.IsUndefined() {
+		uuid = uint64(keyObj.Get("id").Int())
+	}
+	return uuid, nil
+}
+
 // dump returns the given [idb.ObjectStore] contents to string slice for
 // debugging purposes.
 func (w *wasmModel) dump(objectStoreName string) ([]string, error) {
@@ -349,16 +500,17 @@ func (w *wasmModel) dump(objectStoreName string) ([]string, error) {
 	jww.DEBUG.Printf("%s values:", objectStoreName)
 	results := make([]string, 0)
 	ctx, cancel := newContext()
-	err = cursorRequest.Iter(ctx, func(cursor *idb.CursorWithValue) error {
-		value, err := cursor.Value()
-		if err != nil {
-			return err
-		}
-		valueStr := utils.JsToJson(value)
-		results = append(results, valueStr)
-		jww.DEBUG.Printf("- %v", valueStr)
-		return nil
-	})
+	err = cursorRequest.Iter(ctx,
+		func(cursor *idb.CursorWithValue) error {
+			value, err := cursor.Value()
+			if err != nil {
+				return err
+			}
+			valueStr := utils.JsToJson(value)
+			results = append(results, valueStr)
+			jww.DEBUG.Printf("- %v", valueStr)
+			return nil
+		})
 	cancel()
 	if err != nil {
 		return nil, errors.WithMessagef(parentErr,
diff --git a/indexedDb/implementation_test.go b/indexedDb/implementation_test.go
index a4d5d0bd72b7cdb441dfbad87cfdf7d95ce4215d..f47117f7171ce9851b05a5017745c3b4651370c8 100644
--- a/indexedDb/implementation_test.go
+++ b/indexedDb/implementation_test.go
@@ -11,14 +11,17 @@ package indexedDb
 
 import (
 	"encoding/json"
+	"fmt"
+	"os"
+	"testing"
+	"time"
+
 	jww "github.com/spf13/jwalterweatherman"
 	"gitlab.com/elixxir/client/channels"
+	"gitlab.com/elixxir/client/cmix/rounds"
 	cryptoBroadcast "gitlab.com/elixxir/crypto/broadcast"
 	"gitlab.com/elixxir/crypto/channel"
 	"gitlab.com/xx_network/primitives/id"
-	"os"
-	"testing"
-	"time"
 )
 
 func TestMain(m *testing.M) {
@@ -26,19 +29,24 @@ func TestMain(m *testing.M) {
 	os.Exit(m.Run())
 }
 
+func dummyCallback(uint64, *id.ID, bool) {}
+
 // Test wasmModel.UpdateSentStatus happy path and ensure fields don't change.
 func TestWasmModel_UpdateSentStatus(t *testing.T) {
 	testString := "test"
-	testMsgId := channel.MakeMessageID([]byte(testString))
-	eventModel, err := newWasmModel(testString)
+	testMsgId := channel.MakeMessageID([]byte(testString), &id.ID{1})
+	eventModel, err := newWASMModel(testString, dummyCallback)
 	if err != nil {
 		t.Fatalf("%+v", err)
 	}
 
+	cid := channel.Identity{}
+
 	// Store a test message
-	testMsg := buildMessage([]byte(testString), testMsgId.Bytes(),
-		nil, testString, testString, time.Now(), time.Second, channels.Sent)
-	err = eventModel.receiveHelper(testMsg)
+	testMsg := buildMessage([]byte(testString), testMsgId.Bytes(), nil,
+		testString, testString, cid, time.Now(), time.Second, 0, 0,
+		channels.Sent)
+	uuid, err := eventModel.receiveHelper(testMsg)
 	if err != nil {
 		t.Fatalf("%+v", err)
 	}
@@ -54,7 +62,8 @@ func TestWasmModel_UpdateSentStatus(t *testing.T) {
 
 	// Update the sentStatus
 	expectedStatus := channels.Failed
-	eventModel.UpdateSentStatus(testMsgId, expectedStatus)
+	eventModel.UpdateSentStatus(uuid, testMsgId, time.Now(),
+		rounds.Round{ID: 8675309}, expectedStatus)
 
 	// Check the resulting status
 	results, err = eventModel.dump(messageStoreName)
@@ -74,14 +83,14 @@ func TestWasmModel_UpdateSentStatus(t *testing.T) {
 	}
 
 	// Make sure other fields didn't change
-	if resultMsg.SenderUsername != testString {
-		t.Fatalf("Unexpected SenderUsername: %v", resultMsg.SenderUsername)
+	if resultMsg.Nickname != testString {
+		t.Fatalf("Unexpected Nickname: %v", resultMsg.Nickname)
 	}
 }
 
 // Smoke test wasmModel.JoinChannel/wasmModel.LeaveChannel happy paths.
 func TestWasmModel_JoinChannel_LeaveChannel(t *testing.T) {
-	eventModel, err := newWasmModel("test")
+	eventModel, err := newWASMModel("test", dummyCallback)
 	if err != nil {
 		t.Fatalf("%+v", err)
 	}
@@ -116,3 +125,45 @@ func TestWasmModel_JoinChannel_LeaveChannel(t *testing.T) {
 		t.Fatalf("Expected 1 channels to exist")
 	}
 }
+
+// Test wasmModel.UpdateSentStatus happy path and ensure fields don't change.
+func TestWasmModel_UUIDTest(t *testing.T) {
+	testString := "testHello"
+	eventModel, err := newWASMModel(testString, dummyCallback)
+	if err != nil {
+		t.Fatalf("%+v", err)
+	}
+
+	cid := channel.Identity{
+		Codename:       "codename123",
+		PubKey:         []byte{8, 6, 7, 5},
+		Color:          "#FFFFFF",
+		Extension:      "gif",
+		CodesetVersion: 0,
+	}
+
+	uuids := make([]uint64, 10)
+
+	for i := 0; i < 10; i++ {
+		// Store a test message
+		channelID := id.NewIdFromBytes([]byte(testString), t)
+		msgID := channel.MessageID{}
+		copy(msgID[:], testString+fmt.Sprintf("%d", i))
+		rnd := rounds.Round{ID: id.Round(42)}
+		uuid := eventModel.ReceiveMessage(channelID, msgID,
+			"test", testString+fmt.Sprintf("%d", i), cid, time.Now(),
+			time.Hour, rnd, 0, channels.Sent)
+		uuids[i] = uuid
+	}
+
+	_, _ = eventModel.dump(messageStoreName)
+
+	for i := 0; i < 10; i++ {
+		for j := i + 1; j < 10; j++ {
+			if uuids[i] == uuids[j] {
+				t.Fatalf("uuid failed: %d[%d] == %d[%d]",
+					uuids[i], i, uuids[j], j)
+			}
+		}
+	}
+}
diff --git a/indexedDb/init.go b/indexedDb/init.go
index 515505c7bad04b0f81fbe2e621f66b4801280014..9f107ba0cb695e42b41f8d133f261a0703c58ce9 100644
--- a/indexedDb/init.go
+++ b/indexedDb/init.go
@@ -10,31 +10,52 @@
 package indexedDb
 
 import (
+	"syscall/js"
+
 	"github.com/hack-pad/go-indexeddb/idb"
 	"github.com/pkg/errors"
 	jww "github.com/spf13/jwalterweatherman"
-	"syscall/js"
 
 	"gitlab.com/elixxir/client/channels"
+	"gitlab.com/xx_network/primitives/id"
 )
 
 const (
-	// databaseSuffix is the suffix to be appended to the name of the database.
-	databaseSuffix = "_messenger"
+	// databaseSuffix is the suffix to be appended to the name of
+	// the database.
+	databaseSuffix = "_speakeasy"
 
-	// currentVersion is the current version of the IndexDb runtime. Used for
-	// migration purposes.
+	// currentVersion is the current version of the IndexDb
+	// runtime. Used for migration purposes.
 	currentVersion uint = 1
 )
 
-// NewWasmEventModel returns a [channels.EventModel] backed by a wasmModel.
-func NewWasmEventModel(username string) (channels.EventModel, error) {
-	databaseName := username + databaseSuffix
-	return newWasmModel(databaseName)
+// MessageReceivedCallback is called any time a message is received or updated
+// update is true if the row is old and was edited
+type MessageReceivedCallback func(uuid uint64, channelID *id.ID, update bool)
+
+// NewWASMEventModelBuilder returns an EventModelBuilder which allows
+// the channel manager to define the path but the callback is the same
+// across the board.
+func NewWASMEventModelBuilder(
+	cb MessageReceivedCallback) channels.EventModelBuilder {
+	fn := func(path string) (channels.EventModel, error) {
+		return NewWASMEventModel(path, cb)
+	}
+	return fn
 }
 
-// newWasmModel creates the given [idb.Database] and returns a wasmModel.
-func newWasmModel(databaseName string) (*wasmModel, error) {
+// NewWASMEventModel returns a [channels.EventModel] backed by a wasmModel.
+// The name should be a base64 encoding of the users public key.
+func NewWASMEventModel(path string, cb MessageReceivedCallback) (
+	channels.EventModel, error) {
+	databaseName := path + databaseSuffix
+	return newWASMModel(databaseName, cb)
+}
+
+// newWASMModel creates the given [idb.Database] and returns a wasmModel.
+func newWASMModel(databaseName string, cb MessageReceivedCallback) (
+	*wasmModel, error) {
 	// Attempt to open database object
 	ctx, cancel := newContext()
 	defer cancel()
@@ -59,7 +80,7 @@ func newWasmModel(databaseName string) (*wasmModel, error) {
 	// Wait for database open to finish
 	db, err := openRequest.Await(ctx)
 
-	return &wasmModel{db: db}, err
+	return &wasmModel{db: db, receivedMessageCB: cb}, err
 }
 
 // v1Upgrade performs the v0 -> v1 database upgrade.
@@ -69,7 +90,7 @@ func newWasmModel(databaseName string) (*wasmModel, error) {
 func v1Upgrade(db *idb.Database) error {
 	storeOpts := idb.ObjectStoreOptions{
 		KeyPath:       js.ValueOf(pkeyName),
-		AutoIncrement: false,
+		AutoIncrement: true,
 	}
 	indexOpts := idb.IndexOptions{
 		Unique:     false,
@@ -81,6 +102,11 @@ func v1Upgrade(db *idb.Database) error {
 	if err != nil {
 		return err
 	}
+	_, err = messageStore.CreateIndex(messageStoreMessageIndex,
+		js.ValueOf(messageStoreMessage), indexOpts)
+	if err != nil {
+		return err
+	}
 	_, err = messageStore.CreateIndex(messageStoreChannelIndex,
 		js.ValueOf(messageStoreChannel), indexOpts)
 	if err != nil {
diff --git a/indexedDb/model.go b/indexedDb/model.go
index c46bad409ca43a8fb8276c249c3dcb3876dd1457..7204468bfc14b71ce5b7af6caf9a22329592735c 100644
--- a/indexedDb/model.go
+++ b/indexedDb/model.go
@@ -22,12 +22,15 @@ const (
 	channelsStoreName = "channels"
 
 	// Message index names.
+	messageStoreMessageIndex   = "message_id_index"
 	messageStoreChannelIndex   = "channel_id_index"
 	messageStoreParentIndex    = "parent_message_id_index"
 	messageStoreTimestampIndex = "timestamp_index"
 	messageStorePinnedIndex    = "pinned_index"
+	messageStorePubkeyIndex    = "pubkey_index"
 
 	// Message keyPath names (must match json struct tags).
+	messageStoreMessage   = "message_id"
 	messageStoreChannel   = "channel_id"
 	messageStoreParent    = "parent_message_id"
 	messageStoreTimestamp = "timestamp"
@@ -39,24 +42,41 @@ const (
 // A Message belongs to one Channel.
 //
 // A Message may belong to one Message (Parent).
+//
+// A Message belongs to one User (cryptographic identity).
+// The user's nickname can change each message, but the rest does not. We
+// still duplicate all of it for each entry to simplify code for now.
 type Message struct {
-	Id              []byte        `json:"id"` // Matches pkeyName
-	SenderUsername  string        `json:"sender_username"`
-	ChannelId       []byte        `json:"channel_id"`        // Index
-	ParentMessageId []byte        `json:"parent_message_id"` // Index
+	ID              uint64        `json:"id"` // Matches pkeyName
+	Nickname        string        `json:"nickname"`
+	MessageID       []byte        `json:"message_id"`        // Index
+	ChannelID       []byte        `json:"channel_id"`        // Index
+	ParentMessageID []byte        `json:"parent_message_id"` // Index
 	Timestamp       time.Time     `json:"timestamp"`         // Index
 	Lease           time.Duration `json:"lease"`
 	Status          uint8         `json:"status"`
 	Hidden          bool          `json:"hidden"`
 	Pinned          bool          `json:"pinned"` // Index
 	Text            string        `json:"text"`
+	Type            uint16        `json:"type"`
+	Round           uint64        `json:"round"`
+
+	// User cryptographic Identity struct -- could be pulled out
+	Pubkey []byte `json:"pubkey"` // Index
+	// Honorific      string `json:"honorific"`
+	// Adjective      string `json:"adjective"`
+	// Noun           string `json:"noun"`
+	Codename       string `json:"codename"`
+	Color          string `json:"color"`
+	Extension      string `json:"extension"`
+	CodesetVersion uint8  `json:"codeset_version"`
 }
 
 // Channel defines the IndexedDb representation of a single Channel.
 //
 // A Channel has many Message.
 type Channel struct {
-	Id          []byte `json:"id"` // Matches pkeyName
+	ID          []byte `json:"id"` // Matches pkeyName
 	Name        string `json:"name"`
 	Description string `json:"description"`
 }
diff --git a/main.go b/main.go
index 53347e1fcec7d7e8552e7c824472ee50176d391d..a7042228bd1a49304f7bccaae7c4c973755a359c 100644
--- a/main.go
+++ b/main.go
@@ -42,16 +42,21 @@ func main() {
 	js.Global().Set("ResumeBackup", js.FuncOf(wasm.ResumeBackup))
 
 	// wasm/channels.go
+	js.Global().Set("GenerateChannelIdentity",
+		js.FuncOf(wasm.GenerateChannelIdentity))
+	js.Global().Set("GetPublicChannelIdentity",
+		js.FuncOf(wasm.GetPublicChannelIdentity))
+	js.Global().Set("GetPublicChannelIdentityFromPrivate",
+		js.FuncOf(wasm.GetPublicChannelIdentityFromPrivate))
 	js.Global().Set("NewChannelsManager", js.FuncOf(wasm.NewChannelsManager))
+	js.Global().Set("LoadChannelsManager", js.FuncOf(wasm.LoadChannelsManager))
 	js.Global().Set("NewChannelsManagerWithIndexedDb",
 		js.FuncOf(wasm.NewChannelsManagerWithIndexedDb))
-	js.Global().Set("NewChannelsManagerWithIndexedDbDummyNameService",
-		js.FuncOf(wasm.NewChannelsManagerWithIndexedDbDummyNameService))
-	js.Global().Set("NewChannelsManagerDummyNameService",
-		js.FuncOf(wasm.NewChannelsManagerDummyNameService))
-	js.Global().Set("GenerateChannel",
-		js.FuncOf(wasm.GenerateChannel))
+	js.Global().Set("LoadChannelsManagerWithIndexedDb",
+		js.FuncOf(wasm.LoadChannelsManagerWithIndexedDb))
+	js.Global().Set("GenerateChannel", js.FuncOf(wasm.GenerateChannel))
 	js.Global().Set("GetChannelInfo", js.FuncOf(wasm.GetChannelInfo))
+	js.Global().Set("IsNicknameValid", js.FuncOf(wasm.IsNicknameValid))
 
 	// wasm/cmix.go
 	js.Global().Set("NewCmix", js.FuncOf(wasm.NewCmix))
diff --git a/wasm/authenticatedConnection.go b/wasm/authenticatedConnection.go
index 8b2d03f263b1d60277ee5709d7437c6d5c4b5583..c18f48d062b918b682a77188b951e3fa16d77d08 100644
--- a/wasm/authenticatedConnection.go
+++ b/wasm/authenticatedConnection.go
@@ -68,10 +68,11 @@ func (ac *AuthenticatedConnection) GetId(js.Value, []js.Value) interface{} {
 //    [Cmix.WaitForRoundResult] to see if the send succeeded (Uint8Array).
 //  - Rejected with an error if sending fails.
 func (ac *AuthenticatedConnection) SendE2E(_ js.Value, args []js.Value) interface{} {
-	payload := utils.CopyBytesToGo(args[2])
+	mt := args[0].Int()
+	payload := utils.CopyBytesToGo(args[1])
 
 	promiseFn := func(resolve, reject func(args ...interface{}) js.Value) {
-		sendReport, err := ac.api.SendE2E(args[0].Int(), payload)
+		sendReport, err := ac.api.SendE2E(mt, payload)
 		if err != nil {
 			reject(utils.JsTrace(err))
 		} else {
@@ -135,12 +136,13 @@ func (ac *AuthenticatedConnection) RegisterListener(
 //  - Resolves to a Javascript representation of the [Connection] object.
 //  - Rejected with an error if loading the parameters or connecting fails.
 func (c *Cmix) ConnectWithAuthentication(_ js.Value, args []js.Value) interface{} {
+	e2eID := args[0].Int()
 	recipientContact := utils.CopyBytesToGo(args[1])
 	e2eParamsJSON := utils.CopyBytesToGo(args[2])
 
 	promiseFn := func(resolve, reject func(args ...interface{}) js.Value) {
 		ac, err := c.api.ConnectWithAuthentication(
-			args[0].Int(), recipientContact, e2eParamsJSON)
+			e2eID, recipientContact, e2eParamsJSON)
 		if err != nil {
 			reject(utils.JsTrace(err))
 		} else {
diff --git a/wasm/channels.go b/wasm/channels.go
index 9c685776864215898e7721f440926e6c1d02ba27..b8e58b14c22c2195fe2875edd26aa835ad498791 100644
--- a/wasm/channels.go
+++ b/wasm/channels.go
@@ -10,10 +10,12 @@
 package wasm
 
 import (
+	"gitlab.com/xx_network/primitives/id"
+	"syscall/js"
+
 	"gitlab.com/elixxir/client/bindings"
 	"gitlab.com/elixxir/xxdk-wasm/indexedDb"
 	"gitlab.com/elixxir/xxdk-wasm/utils"
-	"syscall/js"
 )
 
 ////////////////////////////////////////////////////////////////////////////////
@@ -44,6 +46,11 @@ func newChannelsManagerJS(api *bindings.ChannelsManager) map[string]interface{}
 		"SendMessage":      js.FuncOf(cm.SendMessage),
 		"SendReply":        js.FuncOf(cm.SendReply),
 		"SendReaction":     js.FuncOf(cm.SendReaction),
+		"GetIdentity":      js.FuncOf(cm.GetIdentity),
+		"GetStorageTag":    js.FuncOf(cm.GetStorageTag),
+		"SetNickname":      js.FuncOf(cm.SetNickname),
+		"DeleteNickname":   js.FuncOf(cm.DeleteNickname),
+		"GetNickname":      js.FuncOf(cm.GetNickname),
 
 		// Channel Receiving Logic and Callback Registration
 		"RegisterReceiveHandler": js.FuncOf(cm.RegisterReceiveHandler),
@@ -61,19 +68,114 @@ func (ch *ChannelsManager) GetID(js.Value, []js.Value) interface{} {
 	return ch.api.GetID()
 }
 
-// NewChannelsManager constructs a [ChannelsManager].
+// GenerateChannelIdentity creates a new private channel identity
+// ([channel.PrivateIdentity]). The public component can be retrieved as JSON
+// via [GetPublicChannelIdentityFromPrivate].
+//
+// Parameters:
+//  - args[0] - ID of [Cmix] object in tracker (int).
+//
+// Returns:
+//  - JSON of [channel.PrivateIdentity] (Uint8Array).
+//  - Throws a TypeError if generating the identity fails.
+func GenerateChannelIdentity(_ js.Value, args []js.Value) interface{} {
+	pi, err := bindings.GenerateChannelIdentity(args[0].Int())
+	if err != nil {
+		utils.Throw(utils.TypeError, err)
+	}
+
+	return utils.CopyBytesToJS(pi)
+}
+
+// GetPublicChannelIdentity constructs a public identity ([channel.Identity])
+// from a bytes version and returns it JSON marshaled.
+//
+// Parameters:
+//  - args[0] - Bytes of the public identity ([channel.Identity]) (Uint8Array).
+//
+// Returns:
+//  - JSON of the constructed [channel.Identity] (Uint8Array).
+//  - Throws a TypeError if unmarshalling the bytes or marshalling the identity
+//    fails.
+func GetPublicChannelIdentity(_ js.Value, args []js.Value) interface{} {
+	marshaledPublic := utils.CopyBytesToGo(args[0])
+	pi, err := bindings.GetPublicChannelIdentity(marshaledPublic)
+	if err != nil {
+		utils.Throw(utils.TypeError, err)
+	}
+
+	return utils.CopyBytesToJS(pi)
+}
+
+// GetPublicChannelIdentityFromPrivate returns the public identity
+// ([channel.Identity]) contained in the given private identity
+// ([channel.PrivateIdentity]).
+//
+// Parameters:
+//  - args[0] - Bytes of the private identity
+//    (channel.PrivateIdentity]) (Uint8Array).
+//
+// Returns:
+//  - JSON of the public identity ([channel.Identity]) (Uint8Array).
+//  - Throws a TypeError if unmarshalling the bytes or marshalling the identity
+//    fails.
+func GetPublicChannelIdentityFromPrivate(_ js.Value, args []js.Value) interface{} {
+	marshaledPrivate := utils.CopyBytesToGo(args[0])
+	identity, err := bindings.GetPublicChannelIdentityFromPrivate(
+		marshaledPrivate)
+	if err != nil {
+		utils.Throw(utils.TypeError, err)
+	}
+
+	return utils.CopyBytesToJS(identity)
+}
+
+// eventModelBuilder adheres to the [bindings.EventModelBuilder] interface.
+type eventModelBuilder struct {
+	build func(args ...interface{}) js.Value
+}
+
+// Build initializes and returns the event model.  It wraps a Javascript object
+// that has all the methods in [bindings.EventModel] to make it adhere to the Go
+// interface [bindings.EventModel].
+func (emb *eventModelBuilder) Build(path string) bindings.EventModel {
+	emJs := emb.build(path)
+	return &eventModel{
+		joinChannel:      utils.WrapCB(emJs, "JoinChannel"),
+		leaveChannel:     utils.WrapCB(emJs, "LeaveChannel"),
+		receiveMessage:   utils.WrapCB(emJs, "ReceiveMessage"),
+		receiveReply:     utils.WrapCB(emJs, "ReceiveReply"),
+		receiveReaction:  utils.WrapCB(emJs, "ReceiveReaction"),
+		updateSentStatus: utils.WrapCB(emJs, "UpdateSentStatus"),
+	}
+}
+
+// NewChannelsManager creates a new [ChannelsManager] from a new private
+// identity ([channel.PrivateIdentity]).
+//
+// This is for creating a manager for an identity for the first time. For
+// generating a new one channel identity, use [GenerateChannelIdentity]. To
+// reload this channel manager, use [LoadChannelsManager], passing in the
+// storage tag retrieved by [ChannelsManager.GetStorageTag].
 //
 // Parameters:
-//  - args[0] - ID of [E2e] object in tracker (int). This can be retrieved using
-//    [E2e.GetID].
-//  - args[1] - ID of [UserDiscovery] object in tracker (int). This can be
-//    retrieved using [UserDiscovery.GetID].
+//  - args[0] - ID of [Cmix] object in tracker (int). This can be retrieved
+//    using [Cmix.GetID].
+//  - args[1] - Bytes of a private identity ([channel.PrivateIdentity]) that is
+//    generated by [GenerateChannelIdentity] (Uint8Array).
+//  - args[2] - A function that initialises and returns a Javascript object that
+//    matches the [bindings.EventModel] interface. The function must match the
+//    Build function in [bindings.EventModelBuilder].
 //
 // Returns:
-//  - Javascript representation of the [bindings.ChannelsManager] object.
-//  - Throws a TypeError if logging in fails.
+//  - Javascript representation of the [ChannelsManager] object.
+//  - Throws a TypeError if creating the manager fails.
 func NewChannelsManager(_ js.Value, args []js.Value) interface{} {
-	cm, err := bindings.NewChannelsManager(args[0].Int(), args[1].Int())
+	privateIdentity := utils.CopyBytesToGo(args[1])
+
+	em := &eventModelBuilder{args[2].Invoke}
+
+	cm, err := bindings.NewChannelsManager(args[0].Int(), privateIdentity, em)
 	if err != nil {
 		utils.Throw(utils.TypeError, err)
 		return nil
@@ -82,63 +184,76 @@ func NewChannelsManager(_ js.Value, args []js.Value) interface{} {
 	return newChannelsManagerJS(cm)
 }
 
-// NewChannelsManagerWithIndexedDb constructs a [ChannelsManager] using an
-// indexedDb backend.
+// LoadChannelsManager loads an existing [ChannelsManager].
+//
+// This is for loading a manager for an identity that has already been created.
+// The channel manager should have previously been created with
+// [NewChannelsManager] and the storage is retrievable with
+// [ChannelsManager.GetStorageTag].
 //
 // Parameters:
-//  - args[0] - ID of [E2e] object in tracker (int). This can be retrieved using
-//    [E2e.GetID].
-//  - args[1] - ID of [UserDiscovery] object in tracker (int). This can be
-//    retrieved using [UserDiscovery.GetID].
-//  - args[2] - username (string).
+//  - args[0] - ID of [Cmix] object in tracker (int). This can be retrieved
+//    using [Cmix.GetID].
+//  - args[1] - The storage tag associated with the previously created channel
+//    manager and retrieved with [ChannelsManager.GetStorageTag] (string).
+//  - args[2] - A function that initialises and returns a Javascript object that
+//    matches the [bindings.EventModel] interface. The function must match the
+//    Build function in [bindings.EventModelBuilder].
 //
-// Returns a promise:
-//  - Resolves to a Javascript representation of the [bindings.ChannelsManager]
-//    object.
-//  - Rejected with an error if loading indexedDb or the manager fails.
-func NewChannelsManagerWithIndexedDb(_ js.Value, args []js.Value) interface{} {
-	promiseFn := func(resolve, reject func(args ...interface{}) js.Value) {
-		em, err := indexedDb.NewWasmEventModel(args[2].String())
-		if err != nil {
-			reject(utils.JsTrace(err))
-		}
-
-		cm, err := bindings.NewChannelsManagerGoEventModel(
-			args[0].Int(), args[1].Int(), em)
-		if err != nil {
-			reject(utils.JsTrace(err))
-		} else {
-			resolve(newChannelsManagerJS(cm))
-		}
+// Returns:
+//  - Javascript representation of the [ChannelsManager] object.
+//  - Throws a TypeError if loading the manager fails.
+func LoadChannelsManager(_ js.Value, args []js.Value) interface{} {
+	em := &eventModelBuilder{args[2].Invoke}
+	cm, err := bindings.LoadChannelsManager(args[0].Int(), args[1].String(), em)
+	if err != nil {
+		utils.Throw(utils.TypeError, err)
+		return nil
 	}
 
-	return utils.CreatePromise(promiseFn)
+	return newChannelsManagerJS(cm)
 }
 
-// NewChannelsManagerWithIndexedDbDummyNameService constructs a
-// [ChannelsManager] using an indexedDb backend and a dummy name server instead
-// of UD.
+// NewChannelsManagerWithIndexedDb creates a new [ChannelsManager] from a new
+// private identity ([channel.PrivateIdentity]) and using indexedDb as a backend
+// to manage the event model.
+//
+// This is for creating a manager for an identity for the first time. For
+// generating a new one channel identity, use [GenerateChannelIdentity]. To
+// reload this channel manager, use [LoadChannelsManagerWithIndexedDb], passing
+// in the storage tag retrieved by [ChannelsManager.GetStorageTag].
 //
 // This function initialises an indexedDb database.
 //
 // Parameters:
 //  - args[0] - ID of [Cmix] object in tracker (int). This can be retrieved
 //    using [Cmix.GetID].
-//  - args[1] - Username (string).
+//  - args[1] - Bytes of a private identity ([channel.PrivateIdentity]) that is
+//    generated by [GenerateChannelIdentity] (Uint8Array).
+//  - args[2] - Function that takes in the same parameters as
+//    [indexedDb.MessageReceivedCallback]. On the Javascript side, the uuid is
+//    returned as an int and the channelID as a Uint8Array. The row in the
+//    database that was updated can be found using the UUID. The channel ID is
+//    provided so that the recipient can filter if they want to the processes
+//    the update now or not. An "update" bool is present which tells you if
+//	  the row is new or if it is an edited old row
 //
 // Returns a promise:
-//  - Resolves to a Javascript representation of the [bindings.ChannelsManager]
-//    object.
+//  - Resolves to a Javascript representation of the [ChannelsManager] object.
 //  - Rejected with an error if loading indexedDb or the manager fails.
-func NewChannelsManagerWithIndexedDbDummyNameService(_ js.Value, args []js.Value) interface{} {
-	promiseFn := func(resolve, reject func(args ...interface{}) js.Value) {
-		em, err := indexedDb.NewWasmEventModel(args[1].String())
-		if err != nil {
-			reject(utils.JsTrace(err))
-		}
+func NewChannelsManagerWithIndexedDb(_ js.Value, args []js.Value) interface{} {
+	cmixID := args[0].Int()
+	privateIdentity := utils.CopyBytesToGo(args[1])
+
+	fn := func(uuid uint64, channelID *id.ID, update bool) {
+		args[2].Invoke(uuid, utils.CopyBytesToJS(channelID.Marshal()), update)
+	}
 
-		cm, err := bindings.NewChannelsManagerGoEventModelDummyNameService(
-			args[0].Int(), args[1].String(), em)
+	model := indexedDb.NewWASMEventModelBuilder(fn)
+
+	promiseFn := func(resolve, reject func(args ...interface{}) js.Value) {
+		cm, err := bindings.NewChannelsManagerGoEventModel(
+			cmixID, privateIdentity, model)
 		if err != nil {
 			reject(utils.JsTrace(err))
 		} else {
@@ -149,55 +264,66 @@ func NewChannelsManagerWithIndexedDbDummyNameService(_ js.Value, args []js.Value
 	return utils.CreatePromise(promiseFn)
 }
 
-// NewChannelsManagerDummyNameService constructs a [ChannelsManager]
-// using a Javascript event model backend and a dummy name server instead of UD.
+// LoadChannelsManagerWithIndexedDb loads an existing [ChannelsManager] using
+// an existing indexedDb database as a backend to manage the event model.
+//
+// This is for loading a manager for an identity that has already been created.
+// The channel manager should have previously been created with
+// [NewChannelsManagerWithIndexedDb] and the storage is retrievable with
+// [ChannelsManager.GetStorageTag].
 //
 // Parameters:
-//  - args[0] -  ID of [Cmix] object in tracker (int). This can be retrieved
+//  - args[0] - ID of [Cmix] object in tracker (int). This can be retrieved
 //    using [Cmix.GetID].
-//  - args[1] - Username (string).
-//  - args[2] - Javascript object that matches the [bindings.EventModel]
-//    interface.
+//  - args[1] - The storage tag associated with the previously created channel
+//    manager and retrieved with [ChannelsManager.GetStorageTag] (string).
+//  - args[2] - Function that takes in the same parameters as
+//    [indexedDb.MessageReceivedCallback]. On the Javascript side, the uuid is
+//    returned as an int and the channelID as a Uint8Array. The row in the
+//    database that was updated can be found using the UUID. The channel ID is
+//    provided so that the recipient can filter if they want to the processes
+//    the update now or not. An "update" bool is present which tells you if
+//	  the row is new or if it is an edited old row
 //
-// Returns:
-//  - Javascript representation of the [bindings.ChannelsManager] object.
-//  - Throws a TypeError if initialising indexedDb or created the new channel
-//    manager fails.
-func NewChannelsManagerDummyNameService(_ js.Value, args []js.Value) interface{} {
-	em := &eventModel{
-		joinChannel:      utils.WrapCB(args[2], "JoinChannel"),
-		leaveChannel:     utils.WrapCB(args[2], "LeaveChannel"),
-		receiveMessage:   utils.WrapCB(args[2], "ReceiveMessage"),
-		receiveReply:     utils.WrapCB(args[2], "ReceiveReply"),
-		receiveReaction:  utils.WrapCB(args[2], "ReceiveReaction"),
-		updateSentStatus: utils.WrapCB(args[2], "UpdateSentStatus"),
+// Returns a promise:
+//  - Resolves to a Javascript representation of the [ChannelsManager] object.
+//  - Rejected with an error if loading indexedDb or the manager fails.
+func LoadChannelsManagerWithIndexedDb(_ js.Value, args []js.Value) interface{} {
+	cmixID := args[0].Int()
+	storageTag := args[1].String()
+
+	fn := func(uuid uint64, channelID *id.ID, updated bool) {
+		args[2].Invoke(uuid, utils.CopyBytesToJS(channelID.Marshal()), updated)
 	}
 
-	cm, err := bindings.NewChannelsManagerGoEventModelDummyNameService(
-		args[0].Int(), args[1].String(), bindings.NewEventModel(em))
-	if err != nil {
-		utils.Throw(utils.TypeError, err)
-		return nil
+	model := indexedDb.NewWASMEventModelBuilder(fn)
+
+	promiseFn := func(resolve, reject func(args ...interface{}) js.Value) {
+		cm, err := bindings.LoadChannelsManagerGoEventModel(
+			cmixID, storageTag, model)
+		if err != nil {
+			reject(utils.JsTrace(err))
+		} else {
+			resolve(newChannelsManagerJS(cm))
+		}
 	}
 
-	return newChannelsManagerJS(cm)
+	return utils.CreatePromise(promiseFn)
 }
 
-// GenerateChannel is used to create a channel. This makes a new channel of
-// which you are the admin. It is only for making new channels, not joining
-// existing ones.
+// GenerateChannel is used to create a channel a new channel of which you are
+// the admin. It is only for making new channels, not joining existing ones.
 //
 // It returns a pretty print of the channel and the private key.
 //
-// The name cannot be longer that ____ characters.
-//
-// The description cannot be longer than ___ and can only use ______ characters.
+// The name cannot be longer that __ characters. The description cannot be
+// longer than __ and can only use ______ characters.
 //
 // Parameters:
 //  - args[0] - ID of [Cmix] object in tracker (int).
 //  - args[1] - The name of the new channel. The name cannot be longer than __
-//    characters and must contain only __ characters. It cannot be changed once
-//    a channel is created (string).
+//    characters and must contain only _____ characters. It cannot be changed
+//    once a channel is created. (string).
 //  - args[2] - The description of a channel. The description cannot be longer
 //    than __ characters and must contain only __ characters. It cannot be
 //    changed once a channel is created (string).
@@ -455,15 +581,15 @@ func (ch *ChannelsManager) SendMessage(_ js.Value, args []js.Value) interface{}
 	return utils.CreatePromise(promiseFn)
 }
 
-// SendReply is used to send a formatted message over a channel.
-// Due to the underlying encoding using compression, it isn't possible to define
-// the largest payload that can be sent, but it will always be possible to send
-// a payload of 766 bytes at minimum.
+// SendReply is used to send a formatted message over a channel. Due to the
+// underlying encoding using compression, it isn't possible to define the
+// largest payload that can be sent, but it will always be possible to send a
+// payload of 766 bytes at minimum.
 //
-// If the message ID the reply is sent to does not exist, then the other side
-// will post the message as a normal message and not a reply.
-// The message will auto delete validUntil after the round it is sent in,
-// lasting forever if ValidForever is used.
+// If the message ID the reply is sent to is nonexistent, the other side will
+// post the message as a normal message and not a reply. The message will auto
+// delete validUntil after the round it is sent in, lasting forever if
+// [channels.ValidForever] is used.
 //
 // Parameters:
 //  - args[0] - Marshalled bytes of the channel [id.ID] (Uint8Array).
@@ -544,6 +670,106 @@ func (ch *ChannelsManager) SendReaction(_ js.Value, args []js.Value) interface{}
 	return utils.CreatePromise(promiseFn)
 }
 
+// GetIdentity returns the marshaled public identity ([channel.Identity]) that
+// the channel is using.
+//
+// Returns:
+//  - JSON of the [channel.Identity] (Uint8Array).
+//  - Throws TypeError if marshalling the identity fails.
+func (ch *ChannelsManager) GetIdentity(js.Value, []js.Value) interface{} {
+	i, err := ch.api.GetIdentity()
+	if err != nil {
+		utils.Throw(utils.TypeError, err)
+		return nil
+	}
+
+	return utils.CopyBytesToJS(i)
+}
+
+// GetStorageTag returns the storage tag needed to reload the manager.
+//
+// Returns:
+//  - Storage tag (string).
+func (ch *ChannelsManager) GetStorageTag(js.Value, []js.Value) interface{} {
+	return ch.api.GetStorageTag()
+}
+
+// SetNickname sets the nickname for a given channel. The nickname must be valid
+// according to [IsNicknameValid].
+//
+// Parameters:
+//  - args[0] - The nickname to set (string).
+//  - args[1] - Marshalled bytes if the channel's [id.ID] (Uint8Array).
+//
+// Returns:
+//  - Throws TypeError if unmarshalling the ID fails or the nickname is invalid.
+func (ch *ChannelsManager) SetNickname(_ js.Value, args []js.Value) interface{} {
+	err := ch.api.SetNickname(args[0].String(), utils.CopyBytesToGo(args[1]))
+	if err != nil {
+		utils.Throw(utils.TypeError, err)
+		return nil
+	}
+
+	return nil
+}
+
+// DeleteNickname deletes the nickname for a given channel.
+//
+// Parameters:
+//  - args[0] - Marshalled bytes if the channel's [id.ID] (Uint8Array).
+//
+// Returns:
+//  - Throws TypeError if deleting the nickname fails.
+func (ch *ChannelsManager) DeleteNickname(_ js.Value, args []js.Value) interface{} {
+	err := ch.api.DeleteNickname(utils.CopyBytesToGo(args[0]))
+	if err != nil {
+		utils.Throw(utils.TypeError, err)
+		return nil
+	}
+
+	return nil
+}
+
+// GetNickname returns the nickname set for a given channel. Returns an error if
+// there is no nickname set.
+//
+// Parameters:
+//  - args[0] - Marshalled bytes if the channel's [id.ID] (Uint8Array).
+//
+// Returns:
+//  - The nickname (string).
+//  - Throws TypeError if the channel has no nickname set.
+func (ch *ChannelsManager) GetNickname(_ js.Value, args []js.Value) interface{} {
+	nickname, err := ch.api.GetNickname(utils.CopyBytesToGo(args[0]))
+	if err != nil {
+		utils.Throw(utils.TypeError, err)
+		return nil
+	}
+
+	return nickname
+}
+
+// IsNicknameValid checks if a nickname is valid.
+//
+// Rules:
+//  1. A nickname must not be longer than 24 characters.
+//  2. A nickname must not be shorter than 1 character.
+//
+// Parameters:
+//  - args[0] - Nickname to check (string).
+//
+// Returns:
+//  - A Javascript Error object if the nickname is invalid with the reason why.
+//  - Null if the nickname is valid.
+func IsNicknameValid(_ js.Value, args []js.Value) interface{} {
+	err := bindings.IsNicknameValid(args[0].String())
+	if err != nil {
+		return utils.JsError(err)
+	}
+
+	return nil
+}
+
 ////////////////////////////////////////////////////////////////////////////////
 // Channel Receiving Logic and Callback Registration                          //
 ////////////////////////////////////////////////////////////////////////////////
@@ -560,10 +786,16 @@ type channelMessageReceptionCallback struct {
 //  - receivedChannelMessageReport - Returns the JSON of
 //   [bindings.ReceivedChannelMessageReport] (Uint8Array).
 //  - err - Returns an error on failure (Error).
+//
+// Returns:
+//  - It must return a unique UUID for the message that it can be referenced by
+//    later (int).
 func (cmrCB *channelMessageReceptionCallback) Callback(
-	receivedChannelMessageReport []byte, err error) {
-	cmrCB.callback(utils.CopyBytesToJS(receivedChannelMessageReport),
-		utils.JsTrace(err))
+	receivedChannelMessageReport []byte, err error) int {
+	uuid := cmrCB.callback(
+		utils.CopyBytesToJS(receivedChannelMessageReport), utils.JsTrace(err))
+
+	return uuid.Int()
 }
 
 // RegisterReceiveHandler is used to register handlers for non-default message
@@ -635,23 +867,33 @@ func (em *eventModel) LeaveChannel(channelID []byte) {
 //  - channelID - Marshalled bytes of the channel [id.ID] (Uint8Array).
 //  - messageID - The bytes of the [channel.MessageID] of the received message
 //    (Uint8Array).
-//  - senderUsername - The username of the sender of the message (string).
+//  - nickname - The nickname of the sender of the message (string).
 //  - text - The content of the message (string).
+//  - identity - JSON of the sender's public ([channel.Identity]) (Uint8Array).
 //  - timestamp - Time the message was received; represented as nanoseconds
 //    since unix epoch (int).
 //  - lease - The number of nanoseconds that the message is valid for (int).
 //  - roundId - The ID of the round that the message was received on (int).
+//  - msgType - The type of message ([channels.MessageType]) to send (int).
 //  - status - The [channels.SentStatus] of the message (int).
 //
 // Statuses will be enumerated as such:
 //  Sent      =  0
 //  Delivered =  1
 //  Failed    =  2
-func (em *eventModel) ReceiveMessage(channelID, messageID []byte,
-	senderUsername, text string, timestamp, lease, roundId, status int64) {
-	em.receiveMessage(utils.CopyBytesToJS(channelID),
-		utils.CopyBytesToJS(messageID),
-		senderUsername, text, timestamp, lease, roundId, status)
+//
+// Returns:
+//  - A non-negative unique UUID for the message that it can be referenced by
+//    later with [eventModel.UpdateSentStatus].
+func (em *eventModel) ReceiveMessage(channelID, messageID []byte, nickname,
+	text string, identity []byte, timestamp, lease, roundId, msgType,
+	status int64) int64 {
+	uuid := em.receiveMessage(utils.CopyBytesToJS(channelID),
+		utils.CopyBytesToJS(messageID), nickname, text,
+		utils.CopyBytesToJS(identity),
+		timestamp, lease, roundId, msgType, status)
+
+	return int64(uuid.Int())
 }
 
 // ReceiveReply is called whenever a message is received that is a reply on a
@@ -669,21 +911,31 @@ func (em *eventModel) ReceiveMessage(channelID, messageID []byte,
 //    (Uint8Array).
 //  - senderUsername - The username of the sender of the message (string).
 //  - text - The content of the message (string).
+//  - identity - JSON of the sender's public ([channel.Identity]) (Uint8Array).
 //  - timestamp - Time the message was received; represented as nanoseconds
 //    since unix epoch (int).
 //  - lease - The number of nanoseconds that the message is valid for (int).
 //  - roundId - The ID of the round that the message was received on (int).
+//  - msgType - The type of message ([channels.MessageType]) to send (int).
 //  - status - The [channels.SentStatus] of the message (int).
 //
 // Statuses will be enumerated as such:
 //  Sent      =  0
 //  Delivered =  1
 //  Failed    =  2
+//
+// Returns:
+//  - A non-negative unique UUID for the message that it can be referenced by
+//    later with [eventModel.UpdateSentStatus].
 func (em *eventModel) ReceiveReply(channelID, messageID, reactionTo []byte,
-	senderUsername, text string, timestamp, lease, roundId, status int64) {
-	em.receiveReply(utils.CopyBytesToJS(channelID),
+	senderUsername, text string, identity []byte, timestamp, lease, roundId,
+	msgType, status int64) int64 {
+	uuid := em.receiveReply(utils.CopyBytesToJS(channelID),
 		utils.CopyBytesToJS(messageID), utils.CopyBytesToJS(reactionTo),
-		senderUsername, text, timestamp, lease, roundId, status)
+		senderUsername, text, utils.CopyBytesToJS(identity),
+		timestamp, lease, roundId, msgType, status)
+
+	return int64(uuid.Int())
 }
 
 // ReceiveReaction is called whenever a reaction to a message is received on a
@@ -701,35 +953,51 @@ func (em *eventModel) ReceiveReply(channelID, messageID, reactionTo []byte,
 //    (Uint8Array).
 //  - senderUsername - The username of the sender of the message (string).
 //  - reaction - The contents of the reaction message (string).
+//  - identity - JSON of the sender's public ([channel.Identity]) (Uint8Array).
 //  - timestamp - Time the message was received; represented as nanoseconds
 //    since unix epoch (int).
 //  - lease - The number of nanoseconds that the message is valid for (int).
 //  - roundId - The ID of the round that the message was received on (int).
+//  - msgType - The type of message ([channels.MessageType]) to send (int).
 //  - status - The [channels.SentStatus] of the message (int).
 //
 // Statuses will be enumerated as such:
 //  Sent      =  0
 //  Delivered =  1
 //  Failed    =  2
+//
+// Returns:
+//  - A non-negative unique UUID for the message that it can be referenced by
+//    later with [eventModel.UpdateSentStatus].
 func (em *eventModel) ReceiveReaction(channelID, messageID, reactionTo []byte,
-	senderUsername, reaction string, timestamp, lease, roundId, status int64) {
-	em.receiveReaction(utils.CopyBytesToJS(channelID),
+	senderUsername, reaction string, identity []byte, timestamp, lease, roundId,
+	msgType, status int64) int64 {
+	uuid := em.receiveReaction(utils.CopyBytesToJS(channelID),
 		utils.CopyBytesToJS(messageID), utils.CopyBytesToJS(reactionTo),
-		senderUsername, reaction, timestamp, lease, roundId, status)
+		senderUsername, reaction, utils.CopyBytesToJS(identity),
+		timestamp, lease, roundId, msgType, status)
+
+	return int64(uuid.Int())
 }
 
 // UpdateSentStatus is called whenever the sent status of a message has
 // changed.
 //
 // Parameters:
+//  - uuid - The unique identifier for the message (int).
 //  - messageID - The bytes of the [channel.MessageID] of the received message
 //    (Uint8Array).
+//  - timestamp - Time the message was received; represented as nanoseconds
+//    since unix epoch (int).
+//  - roundId - The ID of the round that the message was received on (int).
 //  - status - The [channels.SentStatus] of the message (int).
 //
 // Statuses will be enumerated as such:
 //  Sent      =  0
 //  Delivered =  1
 //  Failed    =  2
-func (em *eventModel) UpdateSentStatus(messageID []byte, status int64) {
-	em.updateSentStatus(utils.CopyBytesToJS(messageID), status)
+func (em *eventModel) UpdateSentStatus(
+	uuid int64, messageID []byte, timestamp, roundID, status int64) {
+	em.updateSentStatus(
+		uuid, utils.CopyBytesToJS(messageID), timestamp, roundID, status)
 }
diff --git a/wasm/connect.go b/wasm/connect.go
index e3bc13313df46d53856c2cb0f1468f4369cec013..cd3cce95d47094d7fb6edb093e093ad9129b9cad 100644
--- a/wasm/connect.go
+++ b/wasm/connect.go
@@ -61,11 +61,12 @@ func (c *Connection) GetId(js.Value, []js.Value) interface{} {
 //  - Resolves to a Javascript representation of the [Connection] object.
 //  - Rejected with an error if loading the parameters or connecting fails.
 func (c *Cmix) Connect(_ js.Value, args []js.Value) interface{} {
+	e2eID := args[0].Int()
 	recipientContact := utils.CopyBytesToGo(args[1])
 	e2eParamsJSON := utils.CopyBytesToGo(args[2])
 
 	promiseFn := func(resolve, reject func(args ...interface{}) js.Value) {
-		api, err := c.api.Connect(args[0].Int(), recipientContact, e2eParamsJSON)
+		api, err := c.api.Connect(e2eID, recipientContact, e2eParamsJSON)
 		if err != nil {
 			reject(utils.JsTrace(err))
 		} else {
@@ -92,10 +93,11 @@ func (c *Cmix) Connect(_ js.Value, args []js.Value) interface{} {
 //    into [Cmix.WaitForRoundResult] to see if the send succeeded (Uint8Array).
 //  - Rejected with an error if sending fails.
 func (c *Connection) SendE2E(_ js.Value, args []js.Value) interface{} {
+	e2eID := args[0].Int()
 	payload := utils.CopyBytesToGo(args[1])
 
 	promiseFn := func(resolve, reject func(args ...interface{}) js.Value) {
-		sendReport, err := c.api.SendE2E(args[0].Int(), payload)
+		sendReport, err := c.api.SendE2E(e2eID, payload)
 		if err != nil {
 			reject(utils.JsTrace(err))
 		} else {
diff --git a/wasm/e2eHandler.go b/wasm/e2eHandler.go
index 1929b3539ba721e1b0157a194cecf4964a238c83..556f9699da670662d1a5294c7d7407d02e79304c 100644
--- a/wasm/e2eHandler.go
+++ b/wasm/e2eHandler.go
@@ -170,13 +170,13 @@ func (e *E2e) RemoveService(_ js.Value, args []js.Value) interface{} {
 //    into [Cmix.WaitForRoundResult] to see if the send succeeded (Uint8Array).
 //  - Rejected with an error if sending fails.
 func (e *E2e) SendE2E(_ js.Value, args []js.Value) interface{} {
+	mt := args[0].Int()
 	recipientId := utils.CopyBytesToGo(args[1])
 	payload := utils.CopyBytesToGo(args[2])
 	e2eParams := utils.CopyBytesToGo(args[3])
 
 	promiseFn := func(resolve, reject func(args ...interface{}) js.Value) {
-		sendReport, err := e.api.SendE2E(
-			args[0].Int(), recipientId, payload, e2eParams)
+		sendReport, err := e.api.SendE2E(mt, recipientId, payload, e2eParams)
 		if err != nil {
 			reject(utils.JsTrace(err))
 		} else {
diff --git a/wasm/follow.go b/wasm/follow.go
index f794aa606d8396ced9ce570958c989c01aec75e8..627250636d990de545a9ed0b2b98e1afad752116 100644
--- a/wasm/follow.go
+++ b/wasm/follow.go
@@ -91,8 +91,9 @@ func (c *Cmix) StopNetworkFollower(js.Value, []js.Value) interface{} {
 //  - A promise that resolves if the network is healthy and rejects if the
 //    network is not healthy.
 func (c *Cmix) WaitForNetwork(_ js.Value, args []js.Value) interface{} {
+	timeoutMS := args[0].Int()
 	promiseFn := func(resolve, reject func(args ...interface{}) js.Value) {
-		if c.api.WaitForNetwork(args[0].Int()) {
+		if c.api.WaitForNetwork(timeoutMS) {
 			resolve()
 		} else {
 			reject()
diff --git a/wasm/group.go b/wasm/group.go
index 31c031af2f42160e03f8cd4f9349d9f10b12a430..ebe3b6aa38ad719407a1f3aaaefee687a9488b7d 100644
--- a/wasm/group.go
+++ b/wasm/group.go
@@ -113,8 +113,9 @@ func (g *GroupChat) MakeGroup(_ js.Value, args []js.Value) interface{} {
 //    into [Cmix.WaitForRoundResult] to see if the send succeeded (Uint8Array).
 //  - Rejected with an error if resending the request fails.
 func (g *GroupChat) ResendRequest(_ js.Value, args []js.Value) interface{} {
+	groupId := utils.CopyBytesToGo(args[0])
 	promiseFn := func(resolve, reject func(args ...interface{}) js.Value) {
-		sendReport, err := g.api.ResendRequest(utils.CopyBytesToGo(args[0]))
+		sendReport, err := g.api.ResendRequest(groupId)
 		if err != nil {
 			reject(utils.JsTrace(err))
 		} else {
@@ -181,9 +182,10 @@ func (g *GroupChat) LeaveGroup(_ js.Value, args []js.Value) interface{} {
 func (g *GroupChat) Send(_ js.Value, args []js.Value) interface{} {
 	groupId := utils.CopyBytesToGo(args[0])
 	message := utils.CopyBytesToGo(args[1])
+	tag := args[2].String()
 
 	promiseFn := func(resolve, reject func(args ...interface{}) js.Value) {
-		sendReport, err := g.api.Send(groupId, message, args[2].String())
+		sendReport, err := g.api.Send(groupId, message, tag)
 		if err != nil {
 			reject(utils.JsTrace(err))
 		} else {
diff --git a/wasm/ndf.go b/wasm/ndf.go
index 0d99bc9e58f4680a232d86d29956c51d5be5a85c..8e84254b3c0b017bb37d14fa1e6993a724d3adcc 100644
--- a/wasm/ndf.go
+++ b/wasm/ndf.go
@@ -28,9 +28,11 @@ import (
 //  - Resolves to the JSON of the NDF ([ndf.NetworkDefinition]) (Uint8Array).
 //  - Rejected with an error if downloading fails.
 func DownloadAndVerifySignedNdfWithUrl(_ js.Value, args []js.Value) interface{} {
+	url := args[0].String()
+	cert := args[1].String()
+
 	promiseFn := func(resolve, reject func(args ...interface{}) js.Value) {
-		ndf, err := bindings.DownloadAndVerifySignedNdfWithUrl(
-			args[0].String(), args[1].String())
+		ndf, err := bindings.DownloadAndVerifySignedNdfWithUrl(url, cert)
 		if err != nil {
 			reject(utils.JsTrace(err))
 		} else {
diff --git a/wasm_test.go b/wasm_test.go
index d256a93a4d72b0396f9cbda758fa06ab9605dc1e..c17d1df49b02f5ea433b862ddbda430d50314618 100644
--- a/wasm_test.go
+++ b/wasm_test.go
@@ -39,9 +39,9 @@ func TestPublicFunctions(t *testing.T) {
 
 		// These functions are used internally by the WASM bindings but are not
 		// exposed
-		"NewEventModel":                                  {},
-		"NewChannelsManagerGoEventModel":                 {},
-		"NewChannelsManagerGoEventModelDummyNameService": {},
+		"NewEventModel":                   {},
+		"NewChannelsManagerGoEventModel":  {},
+		"LoadChannelsManagerGoEventModel": {},
 	}
 	wasmFuncs := getPublicFunctions("wasm", t)
 	bindingsFuncs := getPublicFunctions(