diff --git a/go.mod b/go.mod
index d87eb1fcf27a77311d89d041a171f392b436fc6a..39de981529fe2fb13dfab43bc9ecff8d9e56c07c 100644
--- a/go.mod
+++ b/go.mod
@@ -7,11 +7,11 @@ 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/v4 v4.3.9-0.20221208215024-325fafebf519
-	gitlab.com/elixxir/crypto v0.0.7-0.20221208214832-13e2a751db1a
+	gitlab.com/elixxir/client/v4 v4.3.9-0.20221210003613-b73478d56e0d
+	gitlab.com/elixxir/crypto v0.0.7-0.20221210003748-5187f4b98788
 	gitlab.com/elixxir/primitives v0.0.3-0.20221114231218-cc461261a6af
 	gitlab.com/xx_network/crypto v0.0.5-0.20221121220724-8eefdbb0eb46
-	gitlab.com/xx_network/primitives v0.0.4-0.20221110180011-fd6ea3058225
+	gitlab.com/xx_network/primitives v0.0.4-0.20221209210320-376735467d58
 	golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa
 )
 
diff --git a/go.sum b/go.sum
index 3f001c22436716a340a99a493d19c407914cde78..afdff6c2a0129b4c7613853930e4f79ac7722697 100644
--- a/go.sum
+++ b/go.sum
@@ -378,12 +378,12 @@ 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/v4 v4.3.9-0.20221208215024-325fafebf519 h1:nxGzvotSXP/7ekAWj0VpHcieXzxV/wxJ6DD4KG8aid8=
-gitlab.com/elixxir/client/v4 v4.3.9-0.20221208215024-325fafebf519/go.mod h1:ID5txokZGTr7l+xTAoEtQMSSdvNZUBntJAWTR81upds=
+gitlab.com/elixxir/client/v4 v4.3.9-0.20221210003613-b73478d56e0d h1:Ydy9DnxHrrCfHY2UI6//88wT9L2kKtXUuA6di7ER3Ew=
+gitlab.com/elixxir/client/v4 v4.3.9-0.20221210003613-b73478d56e0d/go.mod h1:76yQ2oAQgAIFsb71+sZXeb361RBEfOU7jjY8gtyXTgU=
 gitlab.com/elixxir/comms v0.0.4-0.20221110181420-84bca6216fe4 h1:bLRjVCyMVde4n2hTVgoyyIAWrKI4CevpChchkPeb6A0=
 gitlab.com/elixxir/comms v0.0.4-0.20221110181420-84bca6216fe4/go.mod h1:XhI2/CMng+xcH3mAs+1aPz29PSNu1079XMJ8V+xxihw=
-gitlab.com/elixxir/crypto v0.0.7-0.20221208214832-13e2a751db1a h1:d514iJOaPmH2qjqUyI1N93UyEPTWvZ40LJiRPvQ89jw=
-gitlab.com/elixxir/crypto v0.0.7-0.20221208214832-13e2a751db1a/go.mod h1:fb6UMdmr0hVnzOU67hOZzTeS+wcQZ4pUtTO82039wGg=
+gitlab.com/elixxir/crypto v0.0.7-0.20221210003748-5187f4b98788 h1:K0kDn2k54rwwXx8Cay/0OysfE1AI052E0dfAxOlkamQ=
+gitlab.com/elixxir/crypto v0.0.7-0.20221210003748-5187f4b98788/go.mod h1:fb6UMdmr0hVnzOU67hOZzTeS+wcQZ4pUtTO82039wGg=
 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.3-0.20221114231218-cc461261a6af h1:xcPqknK1ehNb9xwcutTdoR0YgD7DC/ySh9z49tIpSxQ=
@@ -392,8 +392,8 @@ gitlab.com/xx_network/comms v0.0.4-0.20221207203143-462f82d6ec01 h1:0jkud7OWqneH
 gitlab.com/xx_network/comms v0.0.4-0.20221207203143-462f82d6ec01/go.mod h1:+RfHgk75ywMvmucOpPS7rSUlsnbPyBuLsr13tsthUTE=
 gitlab.com/xx_network/crypto v0.0.5-0.20221121220724-8eefdbb0eb46 h1:6AHgUpWdJ72RVTTdJSvfThZiYTQNUnrPaTCl/EkRLpg=
 gitlab.com/xx_network/crypto v0.0.5-0.20221121220724-8eefdbb0eb46/go.mod h1:acWUBKCpae/XVaQF7J9RnLAlBT13i5r7gnON+mrIxBk=
-gitlab.com/xx_network/primitives v0.0.4-0.20221110180011-fd6ea3058225 h1:TAn87e6Zt9KwcSnWKyIul5eu8T0RHY9FDubCGs3G0dw=
-gitlab.com/xx_network/primitives v0.0.4-0.20221110180011-fd6ea3058225/go.mod h1:rP/2IsqIFHapuIB4mstXKItvwoJRQ9Wlms/NGeutHsk=
+gitlab.com/xx_network/primitives v0.0.4-0.20221209210320-376735467d58 h1:HpeUIf1gIIelLH3LHxEf3/GalecbbtZnOnIegJHALoc=
+gitlab.com/xx_network/primitives v0.0.4-0.20221209210320-376735467d58/go.mod h1:wUxbEBGOBJZ/RkAiVAltlC1uIlIrU0dE113Nq7HiOhw=
 gitlab.com/xx_network/ring v0.0.3-0.20220902183151-a7d3b15bc981 h1:1s0vX9BbkiD0IVXwr3LOaTBcq1wBrWcUWMBK0s8r0Z0=
 gitlab.com/xx_network/ring v0.0.3-0.20220902183151-a7d3b15bc981/go.mod h1:aLzpP2TiZTQut/PVHR40EJAomzugDdHXetbieRClXIM=
 gitlab.com/yawning/bsaes.git v0.0.0-20190805113838-0a714cd429ec h1:FpfFs4EhNehiVfzQttTuxanPIT43FtkkCFypIod8LHo=
diff --git a/indexedDb/implementation.go b/indexedDb/channels/implementation.go
similarity index 67%
rename from indexedDb/implementation.go
rename to indexedDb/channels/implementation.go
index d819fde24d9d9404e3f23fb20d7376594eb07278..36c5980921de39d694c1ab0b0df3ecb702bb439c 100644
--- a/indexedDb/implementation.go
+++ b/indexedDb/channels/implementation.go
@@ -7,13 +7,13 @@
 
 //go:build js && wasm
 
-package indexedDb
+package channels
 
 import (
-	"context"
 	"crypto/ed25519"
 	"encoding/base64"
 	"encoding/json"
+	"gitlab.com/elixxir/xxdk-wasm/indexedDb"
 	"sync"
 	"syscall/js"
 	"time"
@@ -30,10 +30,6 @@ import (
 	"gitlab.com/xx_network/primitives/id"
 )
 
-// dbTimeout is the global timeout for operations with the storage
-// [context.Context].
-const dbTimeout = time.Second
-
 // wasmModel implements [channels.EventModel] interface, which uses the channels
 // system passed an object that adheres to in order to get events on the
 // channel.
@@ -44,11 +40,6 @@ type wasmModel struct {
 	updateMux         sync.Mutex
 }
 
-// newContext builds a context for database operations.
-func newContext() (context.Context, context.CancelFunc) {
-	return context.WithTimeout(context.Background(), dbTimeout)
-}
-
 // JoinChannel is called whenever a channel is joined locally.
 func (w *wasmModel) JoinChannel(channel *cryptoBroadcast.Channel) {
 	parentErr := errors.New("failed to JoinChannel")
@@ -74,38 +65,11 @@ func (w *wasmModel) JoinChannel(channel *cryptoBroadcast.Channel) {
 		return
 	}
 
-	// Prepare the Transaction
-	txn, err := w.db.Transaction(idb.TransactionReadWrite, channelsStoreName)
+	_, err = indexedDb.Put(w.db, channelsStoreName, channelObj)
 	if err != nil {
 		jww.ERROR.Printf("%+v", errors.WithMessagef(parentErr,
-			"Unable to create Transaction: %+v", err))
-		return
-	}
-	store, err := txn.ObjectStore(channelsStoreName)
-	if err != nil {
-		jww.ERROR.Printf("%+v", errors.WithMessagef(parentErr,
-			"Unable to get ObjectStore: %+v", err))
-		return
+			"Unable to put Channel: %+v", err))
 	}
-
-	// Perform the operation
-	_, err = store.Put(channelObj)
-	if err != nil {
-		jww.ERROR.Printf("%+v", errors.WithMessagef(parentErr,
-			"Unable to Add Channel: %+v", err))
-		return
-	}
-
-	// Wait for the operation to return
-	ctx, cancel := newContext()
-	err = txn.Await(ctx)
-	cancel()
-	if err != nil {
-		jww.ERROR.Printf("%+v", errors.WithMessagef(parentErr,
-			"Adding Channel failed: %+v", err))
-		return
-	}
-	jww.DEBUG.Printf("Successfully added channel: %s", channel.ReceptionID)
 }
 
 // LeaveChannel is called whenever a channel is left locally.
@@ -135,7 +99,7 @@ func (w *wasmModel) LeaveChannel(channelID *id.ID) {
 	}
 
 	// Wait for the operation to return
-	ctx, cancel := newContext()
+	ctx, cancel := indexedDb.NewContext()
 	err = txn.Await(ctx)
 	cancel()
 	if err != nil {
@@ -183,7 +147,7 @@ func (w *wasmModel) deleteMsgByChannel(channelID *id.ID) error {
 	if err != nil {
 		return errors.WithMessagef(parentErr, "Unable to open Cursor: %+v", err)
 	}
-	ctx, cancel := newContext()
+	ctx, cancel := indexedDb.NewContext()
 	err = cursorRequest.Iter(ctx,
 		func(cursor *idb.CursorWithValue) error {
 			_, err := cursor.Delete()
@@ -203,7 +167,7 @@ func (w *wasmModel) deleteMsgByChannel(channelID *id.ID) error {
 // user of the API to filter such called by message ID.
 func (w *wasmModel) ReceiveMessage(channelID *id.ID,
 	messageID cryptoChannel.MessageID, nickname, text string,
-	pubKey ed25519.PublicKey, dmToken []byte, codeset uint8,
+	pubKey ed25519.PublicKey, dmToken uint32, codeset uint8,
 	timestamp time.Time, lease time.Duration, round rounds.Round,
 	mType channels.MessageType, status channels.SentStatus) uint64 {
 	textBytes := []byte(text)
@@ -239,7 +203,7 @@ 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,
-	nickname, text string, pubKey ed25519.PublicKey, dmToken []byte, codeset uint8,
+	nickname, text string, pubKey ed25519.PublicKey, dmToken uint32, codeset uint8,
 	timestamp time.Time, lease time.Duration, round rounds.Round,
 	mType channels.MessageType, status channels.SentStatus) uint64 {
 	textBytes := []byte(text)
@@ -275,7 +239,7 @@ 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,
-	nickname, reaction string, pubKey ed25519.PublicKey, dmToken []byte, codeset uint8,
+	nickname, reaction string, pubKey ed25519.PublicKey, dmToken uint32, codeset uint8,
 	timestamp time.Time, lease time.Duration, round rounds.Round,
 	mType channels.MessageType, status channels.SentStatus) uint64 {
 	textBytes := []byte(reaction)
@@ -322,7 +286,7 @@ func (w *wasmModel) UpdateSentStatus(uuid uint64,
 	key := js.ValueOf(uuid)
 
 	// Use the key to get the existing Message
-	currentMsg, err := w.get(messageStoreName, key)
+	currentMsg, err := indexedDb.Get(w.db, messageStoreName, key)
 	if err != nil {
 		return
 	}
@@ -363,7 +327,7 @@ func (w *wasmModel) UpdateSentStatus(uuid uint64,
 // 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 string,
-	text []byte, pubKey ed25519.PublicKey, dmToken []byte, codeset uint8, timestamp time.Time,
+	text []byte, pubKey ed25519.PublicKey, dmToken uint32, codeset uint8, timestamp time.Time,
 	lease time.Duration, round id.Round, mType channels.MessageType,
 	status channels.SentStatus) *Message {
 	return &Message{
@@ -405,31 +369,15 @@ func (w *wasmModel) receiveHelper(newMessage *Message, isUpdate bool) (uint64,
 		messageObj.Delete("id")
 	}
 
-	// Prepare the Transaction
-	txn, err := w.db.Transaction(idb.TransactionReadWrite, messageStoreName)
-	if err != nil {
-		return 0, errors.Errorf("Unable to create Transaction: %+v",
-			err)
-	}
-	store, err := txn.ObjectStore(messageStoreName)
-	if err != nil {
-		return 0, errors.Errorf("Unable to get ObjectStore: %+v", err)
-	}
-
-	// Perform the upsert (put) operation
-	addReq, err := store.Put(messageObj)
+	// Store message to database
+	addReq, err := indexedDb.Put(w.db, messageStoreName, messageObj)
 	if err != nil {
-		return 0, errors.Errorf("Unable to upsert Message: %+v", err)
+		return 0, errors.Errorf("Unable to put Message: %+v", err)
 	}
-
-	// Wait for the operation to return
-	ctx, cancel := newContext()
-	err = txn.Await(ctx)
-	cancel()
+	res, err := addReq.Result()
 	if err != nil {
-		return 0, errors.Errorf("Upserting Message failed: %+v", err)
+		return 0, errors.Errorf("Unable to get Message result: %+v", err)
 	}
-	res, err := addReq.Result()
 
 	// NOTE: Sometimes the insert fails to return an error but hits a duplicate
 	//  insert, so this fallthrough returns the UUID entry in that case.
@@ -448,135 +396,19 @@ func (w *wasmModel) receiveHelper(newMessage *Message, isUpdate bool) (uint64,
 	return uuid, nil
 }
 
-// get is a generic private helper for getting values from the given
-// [idb.ObjectStore].
-func (w *wasmModel) get(objectStoreName string, key js.Value) (string, error) {
-	parentErr := errors.Errorf("failed to get %s/%s", objectStoreName, key)
-
-	// Prepare the Transaction
-	txn, err := w.db.Transaction(idb.TransactionReadOnly, objectStoreName)
-	if err != nil {
-		return "", errors.WithMessagef(parentErr,
-			"Unable to create Transaction: %+v", err)
-	}
-	store, err := txn.ObjectStore(objectStoreName)
-	if err != nil {
-		return "", errors.WithMessagef(parentErr,
-			"Unable to get ObjectStore: %+v", err)
-	}
-
-	// Perform the operation
-	getRequest, err := store.Get(key)
-	if err != nil {
-		return "", errors.WithMessagef(parentErr,
-			"Unable to Get from ObjectStore: %+v", err)
-	}
-
-	// Wait for the operation to return
-	ctx, cancel := newContext()
-	resultObj, err := getRequest.Await(ctx)
-	cancel()
-	if err != nil {
-		return "", errors.WithMessagef(parentErr,
-			"Unable to get from ObjectStore: %+v", err)
-	}
-
-	// Process result into string
-	resultStr := utils.JsToJson(resultObj)
-	jww.DEBUG.Printf("Got from %s/%s: %s", objectStoreName, key, resultStr)
-	return resultStr, nil
-}
-
+// msgIDLookup gets the UUID of the Message with the given messageID.
 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()
+	msgIDStr := js.ValueOf(base64.StdEncoding.EncodeToString(messageID.Bytes()))
+	resultObj, err := indexedDb.GetIndex(w.db, messageStoreName,
+		messageStoreMessageIndex, msgIDStr)
 	if err != nil {
-		return 0, errors.WithMessagef(parentErr,
-			"Unable to get from ObjectStore: %+v", err)
+		return 0, 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())
+	if !resultObj.IsUndefined() {
+		uuid = uint64(resultObj.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) {
-	parentErr := errors.Errorf("failed to dump %s", objectStoreName)
-
-	txn, err := w.db.Transaction(idb.TransactionReadOnly, objectStoreName)
-	if err != nil {
-		return nil, errors.WithMessagef(parentErr,
-			"Unable to create Transaction: %+v", err)
-	}
-	store, err := txn.ObjectStore(objectStoreName)
-	if err != nil {
-		return nil, errors.WithMessagef(parentErr,
-			"Unable to get ObjectStore: %+v", err)
-	}
-	cursorRequest, err := store.OpenCursor(idb.CursorNext)
-	if err != nil {
-		return nil, errors.WithMessagef(parentErr,
-			"Unable to open Cursor: %+v", err)
-	}
-
-	// Run the query
-	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
-		})
-	cancel()
-	if err != nil {
-		return nil, errors.WithMessagef(parentErr,
-			"Unable to dump ObjectStore: %+v", err)
-	}
-	return results, nil
-}
diff --git a/indexedDb/implementation_test.go b/indexedDb/channels/implementation_test.go
similarity index 89%
rename from indexedDb/implementation_test.go
rename to indexedDb/channels/implementation_test.go
index 511a022b6c6257f6e8c5877c7be914addedd725d..a14de4f928575189915a04d84a634d160e44d1d8 100644
--- a/indexedDb/implementation_test.go
+++ b/indexedDb/channels/implementation_test.go
@@ -7,12 +7,13 @@
 
 //go:build js && wasm
 
-package indexedDb
+package channels
 
 import (
 	"encoding/json"
 	"fmt"
 	"github.com/hack-pad/go-indexeddb/idb"
+	"gitlab.com/elixxir/xxdk-wasm/indexedDb"
 	"gitlab.com/elixxir/xxdk-wasm/storage"
 	"gitlab.com/xx_network/primitives/netTime"
 	"os"
@@ -46,7 +47,7 @@ func Test_wasmModel_UpdateSentStatus(t *testing.T) {
 
 	// Store a test message
 	testMsg := buildMessage([]byte(testString), testMsgId.Bytes(), nil,
-		testString, []byte(testString), []byte{8, 6, 7, 5}, nil, 0, netTime.Now(),
+		testString, []byte(testString), []byte{8, 6, 7, 5}, 0, 0, netTime.Now(),
 		time.Second, 0, 0, channels.Sent)
 	uuid, err := eventModel.receiveHelper(testMsg, false)
 	if err != nil {
@@ -54,7 +55,7 @@ func Test_wasmModel_UpdateSentStatus(t *testing.T) {
 	}
 
 	// Ensure one message is stored
-	results, err := eventModel.dump(messageStoreName)
+	results, err := indexedDb.Dump(eventModel.db, messageStoreName)
 	if err != nil {
 		t.Fatalf("%+v", err)
 	}
@@ -68,7 +69,7 @@ func Test_wasmModel_UpdateSentStatus(t *testing.T) {
 		rounds.Round{ID: 8675309}, expectedStatus)
 
 	// Check the resulting status
-	results, err = eventModel.dump(messageStoreName)
+	results, err = indexedDb.Dump(eventModel.db, messageStoreName)
 	if err != nil {
 		t.Fatalf("%+v", err)
 	}
@@ -112,7 +113,7 @@ func Test_wasmModel_JoinChannel_LeaveChannel(t *testing.T) {
 	}
 	eventModel.JoinChannel(testChannel)
 	eventModel.JoinChannel(testChannel2)
-	results, err := eventModel.dump(channelsStoreName)
+	results, err := indexedDb.Dump(eventModel.db, channelsStoreName)
 	if err != nil {
 		t.Fatalf("%+v", err)
 	}
@@ -120,7 +121,7 @@ func Test_wasmModel_JoinChannel_LeaveChannel(t *testing.T) {
 		t.Fatalf("Expected 2 channels to exist")
 	}
 	eventModel.LeaveChannel(testChannel.ReceptionID)
-	results, err = eventModel.dump(channelsStoreName)
+	results, err = indexedDb.Dump(eventModel.db, channelsStoreName)
 	if err != nil {
 		t.Fatalf("%+v", err)
 	}
@@ -146,13 +147,11 @@ func Test_wasmModel_UUIDTest(t *testing.T) {
 		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), []byte{8, 6, 7, 5}, nil, 0,
+			testString+fmt.Sprintf("%d", i), []byte{8, 6, 7, 5}, 0, 0,
 			netTime.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] {
@@ -181,13 +180,11 @@ func Test_wasmModel_DuplicateReceives(t *testing.T) {
 		channelID := id.NewIdFromBytes([]byte(testString), t)
 		rnd := rounds.Round{ID: id.Round(42)}
 		uuid := eventModel.ReceiveMessage(channelID, msgID, "test",
-			testString+fmt.Sprintf("%d", i), []byte{8, 6, 7, 5}, nil, 0,
+			testString+fmt.Sprintf("%d", i), []byte{8, 6, 7, 5}, 0, 0,
 			netTime.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] {
@@ -225,12 +222,12 @@ func Test_wasmModel_deleteMsgByChannel(t *testing.T) {
 
 		testMsgId := channel.MakeMessageID([]byte(testStr), &id.ID{1})
 		eventModel.ReceiveMessage(thisChannel, testMsgId, testStr, testStr,
-			[]byte{8, 6, 7, 5}, nil, 0, netTime.Now(), time.Second,
+			[]byte{8, 6, 7, 5}, 0, 0, netTime.Now(), time.Second,
 			rounds.Round{ID: id.Round(0)}, 0, channels.Sent)
 	}
 
 	// Check pre-results
-	result, err := eventModel.dump(messageStoreName)
+	result, err := indexedDb.Dump(eventModel.db, messageStoreName)
 	if err != nil {
 		t.Fatalf("%+v", err)
 	}
@@ -245,7 +242,7 @@ func Test_wasmModel_deleteMsgByChannel(t *testing.T) {
 	}
 
 	// Check final results
-	result, err = eventModel.dump(messageStoreName)
+	result, err = indexedDb.Dump(eventModel.db, messageStoreName)
 	if err != nil {
 		t.Fatalf("%+v", err)
 	}
@@ -286,7 +283,7 @@ func TestWasmModel_receiveHelper_UniqueIndex(t *testing.T) {
 	// First message insert should succeed
 	testMsgId := channel.MakeMessageID([]byte(testString), &id.ID{1})
 	testMsg := buildMessage([]byte(testString), testMsgId.Bytes(), nil,
-		testString, []byte(testString), []byte{8, 6, 7, 5}, nil, 0, netTime.Now(),
+		testString, []byte(testString), []byte{8, 6, 7, 5}, 0, 0, netTime.Now(),
 		time.Second, 0, 0, channels.Sent)
 	_, err = eventModel.receiveHelper(testMsg, false)
 	if err != nil {
@@ -298,7 +295,7 @@ func TestWasmModel_receiveHelper_UniqueIndex(t *testing.T) {
 	if err != nil {
 		t.Fatalf("%+v", err)
 	}
-	results, err := eventModel.dump(messageStoreName)
+	results, err := indexedDb.Dump(eventModel.db, messageStoreName)
 	if err != nil {
 		t.Fatalf("%+v", err)
 	}
@@ -309,7 +306,7 @@ func TestWasmModel_receiveHelper_UniqueIndex(t *testing.T) {
 	// Now insert a message with a different message ID from the first
 	testMsgId2 := channel.MakeMessageID([]byte(testString), &id.ID{2})
 	testMsg = buildMessage([]byte(testString), testMsgId2.Bytes(), nil,
-		testString, []byte(testString), []byte{8, 6, 7, 5}, nil, 0, netTime.Now(),
+		testString, []byte(testString), []byte{8, 6, 7, 5}, 0, 0, netTime.Now(),
 		time.Second, 0, 0, channels.Sent)
 	primaryKey, err := eventModel.receiveHelper(testMsg, false)
 	if err != nil {
@@ -327,7 +324,7 @@ func TestWasmModel_receiveHelper_UniqueIndex(t *testing.T) {
 
 	// The update to duplicate message ID won't fail,
 	// but it just silently shouldn't happen
-	results, err = eventModel.dump(messageStoreName)
+	results, err = indexedDb.Dump(eventModel.db, messageStoreName)
 	if err != nil {
 		t.Fatalf("%+v", err)
 	}
diff --git a/indexedDb/init.go b/indexedDb/channels/init.go
similarity index 97%
rename from indexedDb/init.go
rename to indexedDb/channels/init.go
index f3f4503dec9284896ce6f66d2db44209640a46ad..0b52434ff260ee3df42c8b0f201527f08377205b 100644
--- a/indexedDb/init.go
+++ b/indexedDb/channels/init.go
@@ -7,7 +7,7 @@
 
 //go:build js && wasm
 
-package indexedDb
+package channels
 
 import (
 	"github.com/hack-pad/go-indexeddb/idb"
@@ -15,6 +15,7 @@ import (
 	jww "github.com/spf13/jwalterweatherman"
 	"gitlab.com/elixxir/client/v4/channels"
 	cryptoChannel "gitlab.com/elixxir/crypto/channel"
+	"gitlab.com/elixxir/xxdk-wasm/indexedDb"
 	"gitlab.com/elixxir/xxdk-wasm/storage"
 	"gitlab.com/xx_network/primitives/id"
 	"syscall/js"
@@ -58,7 +59,7 @@ func NewWASMEventModel(path string, encryption cryptoChannel.Cipher,
 func newWASMModel(databaseName string, encryption cryptoChannel.Cipher,
 	cb MessageReceivedCallback) (*wasmModel, error) {
 	// Attempt to open database object
-	ctx, cancel := newContext()
+	ctx, cancel := indexedDb.NewContext()
 	defer cancel()
 	openRequest, err := idb.Global().Open(ctx, databaseName, currentVersion,
 		func(db *idb.Database, oldVersion, newVersion uint) error {
@@ -109,9 +110,6 @@ func newWASMModel(databaseName string, encryption cryptoChannel.Cipher,
 	}
 
 	// Attempt to ensure the database has been properly initialized
-	if err != nil {
-		return nil, err
-	}
 	openRequest, err = idb.Global().Open(ctx, databaseName, currentVersion,
 		func(db *idb.Database, oldVersion, newVersion uint) error {
 			return nil
@@ -209,7 +207,7 @@ func (w *wasmModel) hackTestDb() error {
 	if helper != nil {
 		return helper
 	}
-	result, err := w.get(messageStoreName, js.ValueOf(msgId))
+	result, err := indexedDb.Get(w.db, messageStoreName, js.ValueOf(msgId))
 	if err != nil {
 		return err
 	}
diff --git a/indexedDb/model.go b/indexedDb/channels/model.go
similarity index 95%
rename from indexedDb/model.go
rename to indexedDb/channels/model.go
index 839ed29c3c7b6aafe33ab9a0001d7b919964dd18..078d6bd67f7c43d9e50373b7801f69937cfce2ac 100644
--- a/indexedDb/model.go
+++ b/indexedDb/channels/model.go
@@ -7,7 +7,7 @@
 
 //go:build js && wasm
 
-package indexedDb
+package channels
 
 import (
 	"time"
@@ -60,8 +60,8 @@ type Message struct {
 	Round           uint64        `json:"round"`
 
 	// User cryptographic Identity struct -- could be pulled out
-	Pubkey         []byte `json:"pubkey"`   // Index
-	DmToken        []byte `json:"dm_token"` // Index
+	Pubkey         []byte `json:"pubkey"`
+	DmToken        uint32 `json:"dm_token"`
 	CodesetVersion uint8  `json:"codeset_version"`
 }
 
diff --git a/indexedDb/dm/implementation.go b/indexedDb/dm/implementation.go
new file mode 100644
index 0000000000000000000000000000000000000000..3327959392089e4d30c1a8b09141edc83c136ddc
--- /dev/null
+++ b/indexedDb/dm/implementation.go
@@ -0,0 +1,367 @@
+////////////////////////////////////////////////////////////////////////////////
+// Copyright © 2022 xx foundation                                             //
+//                                                                            //
+// Use of this source code is governed by a license that can be found in the  //
+// LICENSE file.                                                              //
+////////////////////////////////////////////////////////////////////////////////
+
+//go:build js && wasm
+
+package channelEventModel
+
+import (
+	"crypto/ed25519"
+	"encoding/json"
+	"github.com/pkg/errors"
+	jww "github.com/spf13/jwalterweatherman"
+	"gitlab.com/elixxir/client/v4/cmix/rounds"
+	"gitlab.com/elixxir/client/v4/dm"
+	"gitlab.com/elixxir/xxdk-wasm/indexedDb"
+	"gitlab.com/elixxir/xxdk-wasm/utils"
+	"gitlab.com/xx_network/primitives/id"
+	"sync"
+	"syscall/js"
+	"time"
+
+	"github.com/hack-pad/go-indexeddb/idb"
+	cryptoChannel "gitlab.com/elixxir/crypto/channel"
+)
+
+// wasmModel implements [dm.Receiver] interface, which uses the channels
+// system passed an object that adheres to in order to get events on the
+// channel.
+type wasmModel struct {
+	db                *idb.Database
+	cipher            cryptoChannel.Cipher
+	receivedMessageCB MessageReceivedCallback
+	updateMux         sync.Mutex
+}
+
+// joinConversation is used for joining new conversations.
+func (w *wasmModel) joinConversation(nickname string,
+	pubKey ed25519.PublicKey, dmToken uint32, codeset uint8) error {
+	parentErr := errors.New("failed to joinConversation")
+
+	// Build object
+	newConvo := Conversation{
+		Pubkey:         pubKey,
+		Nickname:       nickname,
+		Token:          dmToken,
+		CodesetVersion: codeset,
+		Blocked:        false,
+	}
+
+	// Convert to jsObject
+	newConvoJson, err := json.Marshal(&newConvo)
+	if err != nil {
+		return errors.WithMessagef(parentErr,
+			"Unable to marshal Conversation: %+v", err)
+	}
+	convoObj, err := utils.JsonToJS(newConvoJson)
+	if err != nil {
+		return errors.WithMessagef(parentErr,
+			"Unable to marshal Conversation: %+v", err)
+	}
+
+	_, err = indexedDb.Put(w.db, conversationStoreName, convoObj)
+	if err != nil {
+		return errors.WithMessagef(parentErr,
+			"Unable to put Conversation: %+v", err)
+	}
+	return nil
+}
+
+// buildMessage is a private helper that converts typical [dm.Receiver]
+// inputs into a basic Message structure for insertion into storage.
+//
+// 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(messageID, parentID []byte, text []byte,
+	pubKey ed25519.PublicKey, timestamp time.Time, round id.Round,
+	mType dm.MessageType, status dm.Status) *Message {
+	return &Message{
+		MessageID:          messageID,
+		ConversationPubKey: pubKey,
+		ParentMessageID:    parentID,
+		Timestamp:          timestamp,
+		Status:             uint8(status),
+		Text:               text,
+		Type:               uint16(mType),
+		Round:              uint64(round),
+	}
+}
+
+func (w *wasmModel) Receive(messageID dm.MessageID, nickname string, text []byte,
+	pubKey ed25519.PublicKey, dmToken uint32, codeset uint8, timestamp time.Time,
+	round rounds.Round, mType dm.MessageType, status dm.Status) uint64 {
+	parentErr := errors.New("failed to Receive")
+	var err error
+
+	// If there is no extant Conversation, create one.
+	if result, err := indexedDb.Get(w.db, conversationStoreName,
+		utils.CopyBytesToJS(pubKey)); err != nil {
+		jww.ERROR.Printf("%+v", errors.WithMessagef(parentErr,
+			"Unable to get Conversation: %+v", err))
+		return 0
+	} else if len(result) == 0 {
+		err = w.joinConversation(nickname, pubKey, dmToken, codeset)
+		jww.ERROR.Printf("%+v", err)
+		return 0
+	} else {
+		jww.DEBUG.Printf("Conversation with %s already joined", nickname)
+	}
+
+	// Handle encryption, if it is present
+	if w.cipher != nil {
+		text, err = w.cipher.Encrypt(text)
+		if err != nil {
+			jww.ERROR.Printf("Failed to encrypt Message: %+v", err)
+			return 0
+		}
+	}
+
+	msgToInsert := buildMessage(messageID.Bytes(), nil, text,
+		pubKey, timestamp, round.ID, mType, status)
+	uuid, err := w.receiveHelper(msgToInsert, false)
+	if err != nil {
+		jww.ERROR.Printf("Failed to receive Message: %+v", err)
+	}
+
+	go w.receivedMessageCB(uuid, pubKey, false)
+	return uuid
+}
+
+func (w *wasmModel) ReceiveText(messageID dm.MessageID, nickname, text string,
+	pubKey ed25519.PublicKey, dmToken uint32, codeset uint8, timestamp time.Time,
+	round rounds.Round, status dm.Status) uint64 {
+	parentErr := errors.New("failed to ReceiveText")
+	var err error
+
+	// If there is no extant Conversation, create one.
+	if result, err := indexedDb.Get(w.db, conversationStoreName,
+		utils.CopyBytesToJS(pubKey)); err != nil {
+		jww.ERROR.Printf("%+v", errors.WithMessagef(parentErr,
+			"Unable to get Conversation: %+v", err))
+		return 0
+	} else if len(result) == 0 {
+		err = w.joinConversation(nickname, pubKey, dmToken, codeset)
+		jww.ERROR.Printf("%+v", err)
+		return 0
+	} else {
+		jww.DEBUG.Printf("Conversation with %s already joined", nickname)
+	}
+
+	// Handle encryption, if it is present
+	textBytes := []byte(text)
+	if w.cipher != nil {
+		textBytes, err = w.cipher.Encrypt(textBytes)
+		if err != nil {
+			jww.ERROR.Printf("Failed to encrypt Message: %+v", err)
+			return 0
+		}
+	}
+
+	msgToInsert := buildMessage(messageID.Bytes(), nil, textBytes,
+		pubKey, timestamp, round.ID, dm.TextType, status)
+
+	uuid, err := w.receiveHelper(msgToInsert, false)
+	if err != nil {
+		jww.ERROR.Printf("Failed to receive Message: %+v", err)
+	}
+
+	go w.receivedMessageCB(uuid, pubKey, false)
+	return uuid
+}
+
+func (w *wasmModel) ReceiveReply(messageID dm.MessageID, reactionTo dm.MessageID,
+	nickname, text string, pubKey ed25519.PublicKey, dmToken uint32, codeset uint8,
+	timestamp time.Time, round rounds.Round, status dm.Status) uint64 {
+	parentErr := errors.New("failed to ReceiveReply")
+	var err error
+
+	// If there is no extant Conversation, create one.
+	if result, err := indexedDb.Get(w.db, conversationStoreName,
+		utils.CopyBytesToJS(pubKey)); err != nil {
+		jww.ERROR.Printf("%+v", errors.WithMessagef(parentErr,
+			"Unable to get Conversation: %+v", err))
+		return 0
+	} else if len(result) == 0 {
+		err = w.joinConversation(nickname, pubKey, dmToken, codeset)
+		jww.ERROR.Printf("%+v", err)
+		return 0
+	} else {
+		jww.DEBUG.Printf("Conversation with %s already joined", nickname)
+	}
+
+	// Handle encryption, if it is present
+	textBytes := []byte(text)
+	if w.cipher != nil {
+		textBytes, err = w.cipher.Encrypt(textBytes)
+		if err != nil {
+			jww.ERROR.Printf("Failed to encrypt Message: %+v", err)
+			return 0
+		}
+	}
+
+	msgToInsert := buildMessage(messageID.Bytes(), reactionTo.Marshal(), textBytes,
+		pubKey, timestamp, round.ID, dm.TextType, status)
+
+	uuid, err := w.receiveHelper(msgToInsert, false)
+	if err != nil {
+		jww.ERROR.Printf("Failed to receive Message: %+v", err)
+	}
+
+	go w.receivedMessageCB(uuid, pubKey, false)
+	return uuid
+}
+
+func (w *wasmModel) ReceiveReaction(messageID dm.MessageID, reactionTo dm.MessageID,
+	nickname, reaction string, pubKey ed25519.PublicKey, dmToken uint32,
+	codeset uint8, timestamp time.Time, round rounds.Round, status dm.Status) uint64 {
+	parentErr := errors.New("failed to ReceiveText")
+	var err error
+
+	// If there is no extant Conversation, create one.
+	if result, err := indexedDb.Get(w.db, conversationStoreName,
+		utils.CopyBytesToJS(pubKey)); err != nil {
+		jww.ERROR.Printf("%+v", errors.WithMessagef(parentErr,
+			"Unable to get Conversation: %+v", err))
+		return 0
+	} else if len(result) == 0 {
+		err = w.joinConversation(nickname, pubKey, dmToken, codeset)
+		jww.ERROR.Printf("%+v", err)
+		return 0
+	} else {
+		jww.DEBUG.Printf("Conversation with %s already joined", nickname)
+	}
+
+	// Handle encryption, if it is present
+	textBytes := []byte(reaction)
+	if w.cipher != nil {
+		textBytes, err = w.cipher.Encrypt(textBytes)
+		if err != nil {
+			jww.ERROR.Printf("Failed to encrypt Message: %+v", err)
+			return 0
+		}
+	}
+
+	msgToInsert := buildMessage(messageID.Bytes(), nil, textBytes,
+		pubKey, timestamp, round.ID, dm.ReactionType, status)
+
+	uuid, err := w.receiveHelper(msgToInsert, false)
+	if err != nil {
+		jww.ERROR.Printf("Failed to receive Message: %+v", err)
+	}
+
+	go w.receivedMessageCB(uuid, pubKey, false)
+	return uuid
+}
+
+func (w *wasmModel) UpdateSentStatus(uuid uint64, messageID dm.MessageID,
+	timestamp time.Time, round rounds.Round, status dm.Status) {
+	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(uuid)
+
+	// Use the key to get the existing Message
+	currentMsg, err := indexedDb.Get(w.db, messageStoreName, key)
+	if err != nil {
+		return
+	}
+
+	// Extract the existing Message and update the Status
+	newMessage := &Message{}
+	err = json.Unmarshal([]byte(currentMsg), newMessage)
+	if err != nil {
+		return
+	}
+	newMessage.Status = uint8(status)
+	if !messageID.Equals(dm.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, true)
+	if err != nil {
+		jww.ERROR.Printf("%+v", errors.Wrap(parentErr, err.Error()))
+	}
+	go w.receivedMessageCB(uuid, newMessage.ConversationPubKey, true)
+}
+
+// receiveHelper is a private helper for receiving any sort of message.
+func (w *wasmModel) receiveHelper(newMessage *Message, isUpdate bool) (uint64,
+	error) {
+	// Convert to jsObject
+	newMessageJson, err := json.Marshal(newMessage)
+	if err != nil {
+		return 0, errors.Errorf("Unable to marshal Message: %+v", err)
+	}
+	messageObj, err := utils.JsonToJS(newMessageJson)
+	if err != nil {
+		return 0, errors.Errorf("Unable to marshal Message: %+v", err)
+	}
+
+	// Unset the primaryKey for inserts so that it can be auto-populated and
+	// incremented
+	if !isUpdate {
+		messageObj.Delete("id")
+	}
+
+	// Store message to database
+	addReq, err := indexedDb.Put(w.db, messageStoreName, messageObj)
+	if err != nil {
+		return 0, errors.Errorf("Unable to put Message: %+v", err)
+	}
+	res, err := addReq.Result()
+	if err != nil {
+		return 0, errors.Errorf("Unable to get Message result: %+v", err)
+	}
+
+	// NOTE: Sometimes the insert fails to return an error but hits a duplicate
+	//  insert, so this fallthrough returns the UUID entry in that case.
+	if res.IsUndefined() {
+		msgID := cryptoChannel.MessageID{}
+		copy(msgID[:], newMessage.MessageID)
+		uuid, errLookup := w.msgIDLookup(msgID)
+		if uuid != 0 && errLookup == nil {
+			return uuid, nil
+		}
+		return 0, errors.Errorf("uuid lookup failure: %+v", err)
+	}
+	uuid := uint64(res.Int())
+	jww.DEBUG.Printf("Successfully stored message %d", uuid)
+
+	return uuid, nil
+}
+
+// msgIDLookup gets the UUID of the Message with the given messageID.
+func (w *wasmModel) msgIDLookup(messageID cryptoChannel.MessageID) (uint64,
+	error) {
+	resultObj, err := indexedDb.GetIndex(w.db, messageStoreName,
+		messageStoreMessageIndex, utils.CopyBytesToJS(messageID.Marshal()))
+	if err != nil {
+		return 0, err
+	}
+
+	uuid := uint64(0)
+	if !resultObj.IsUndefined() {
+		uuid = uint64(resultObj.Get("id").Int())
+	}
+	return uuid, nil
+}
diff --git a/indexedDb/dm/init.go b/indexedDb/dm/init.go
new file mode 100644
index 0000000000000000000000000000000000000000..d2c3ba1dbb2d6306a39ffc8f5f0b4e6f0159dc73
--- /dev/null
+++ b/indexedDb/dm/init.go
@@ -0,0 +1,181 @@
+////////////////////////////////////////////////////////////////////////////////
+// Copyright © 2022 xx foundation                                             //
+//                                                                            //
+// Use of this source code is governed by a license that can be found in the  //
+// LICENSE file.                                                              //
+////////////////////////////////////////////////////////////////////////////////
+
+//go:build js && wasm
+
+package channelEventModel
+
+import (
+	"crypto/ed25519"
+	"github.com/hack-pad/go-indexeddb/idb"
+	"github.com/pkg/errors"
+	jww "github.com/spf13/jwalterweatherman"
+	"gitlab.com/elixxir/client/v4/dm"
+	cryptoChannel "gitlab.com/elixxir/crypto/channel"
+	"gitlab.com/elixxir/xxdk-wasm/indexedDb"
+	"gitlab.com/elixxir/xxdk-wasm/storage"
+	"syscall/js"
+)
+
+const (
+	// databaseSuffix is the suffix to be appended to the name of
+	// the database.
+	databaseSuffix = "_speakeasy_dm"
+
+	// currentVersion is the current version of the IndexDb
+	// runtime. Used for migration purposes.
+	currentVersion uint = 1
+)
+
+// 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, pubKey ed25519.PublicKey, update bool)
+
+// 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, encryption cryptoChannel.Cipher,
+	cb MessageReceivedCallback) (dm.Receiver, error) {
+	databaseName := path + databaseSuffix
+	return newWASMModel(databaseName, encryption, cb)
+}
+
+// newWASMModel creates the given [idb.Database] and returns a wasmModel.
+func newWASMModel(databaseName string, encryption cryptoChannel.Cipher,
+	cb MessageReceivedCallback) (*wasmModel, error) {
+	// Attempt to open database object
+	ctx, cancel := indexedDb.NewContext()
+	defer cancel()
+	openRequest, err := idb.Global().Open(ctx, databaseName, currentVersion,
+		func(db *idb.Database, oldVersion, newVersion uint) error {
+			if oldVersion == newVersion {
+				jww.INFO.Printf("IndexDb version is current: v%d",
+					newVersion)
+				return nil
+			}
+
+			jww.INFO.Printf("IndexDb upgrade required: v%d -> v%d",
+				oldVersion, newVersion)
+
+			if oldVersion == 0 && newVersion >= 1 {
+				err := v1Upgrade(db)
+				if err != nil {
+					return err
+				}
+				oldVersion = 1
+			}
+
+			// if oldVersion == 1 && newVersion >= 2 { v2Upgrade(), oldVersion = 2 }
+			return nil
+		})
+	if err != nil {
+		return nil, err
+	}
+
+	// Wait for database open to finish
+	db, err := openRequest.Await(ctx)
+	if err != nil {
+		return nil, err
+	}
+
+	// Save the encryption status to storage
+	encryptionStatus := encryption != nil
+	loadedEncryptionStatus, err := storage.StoreIndexedDbEncryptionStatus(
+		databaseName, encryptionStatus)
+	if err != nil {
+		return nil, err
+	}
+
+	// Verify encryption status does not change
+	if encryptionStatus != loadedEncryptionStatus {
+		return nil, errors.New(
+			"Cannot load database with different encryption status.")
+	} else if !encryptionStatus {
+		jww.WARN.Printf("IndexedDb encryption disabled!")
+	}
+
+	// Attempt to ensure the database has been properly initialized
+	openRequest, err = idb.Global().Open(ctx, databaseName, currentVersion,
+		func(db *idb.Database, oldVersion, newVersion uint) error {
+			return nil
+		})
+	if err != nil {
+		return nil, err
+	}
+	// Wait for database open to finish
+	db, err = openRequest.Await(ctx)
+	if err != nil {
+		return nil, err
+	}
+	wrapper := &wasmModel{db: db, receivedMessageCB: cb, cipher: encryption}
+
+	return wrapper, nil
+}
+
+// v1Upgrade performs the v0 -> v1 database upgrade.
+//
+// This can never be changed without permanently breaking backwards
+// compatibility.
+func v1Upgrade(db *idb.Database) error {
+	indexOpts := idb.IndexOptions{
+		Unique:     false,
+		MultiEntry: false,
+	}
+
+	// Build Message ObjectStore and Indexes
+	messageStoreOpts := idb.ObjectStoreOptions{
+		KeyPath:       js.ValueOf(msgPkeyName),
+		AutoIncrement: true,
+	}
+	messageStore, err := db.CreateObjectStore(messageStoreName, messageStoreOpts)
+	if err != nil {
+		return err
+	}
+	_, err = messageStore.CreateIndex(messageStoreMessageIndex,
+		js.ValueOf(messageStoreMessage),
+		idb.IndexOptions{
+			Unique:     true,
+			MultiEntry: false,
+		})
+	if err != nil {
+		return err
+	}
+	_, err = messageStore.CreateIndex(messageStoreConversationIndex,
+		js.ValueOf(messageStoreConversation), indexOpts)
+	if err != nil {
+		return err
+	}
+	_, err = messageStore.CreateIndex(messageStoreParentIndex,
+		js.ValueOf(messageStoreParent), indexOpts)
+	if err != nil {
+		return err
+	}
+	_, err = messageStore.CreateIndex(messageStoreTimestampIndex,
+		js.ValueOf(messageStoreTimestamp), indexOpts)
+	if err != nil {
+		return err
+	}
+
+	// Build Channel ObjectStore
+	conversationStoreOpts := idb.ObjectStoreOptions{
+		KeyPath:       js.ValueOf(convoPkeyName),
+		AutoIncrement: false,
+	}
+	_, err = db.CreateObjectStore(conversationStoreName, conversationStoreOpts)
+	if err != nil {
+		return err
+	}
+
+	// Get the database name and save it to storage
+	if databaseName, err := db.Name(); err != nil {
+		return err
+	} else if err = storage.StoreIndexedDb(databaseName); err != nil {
+		return err
+	}
+
+	return nil
+}
diff --git a/indexedDb/dm/model.go b/indexedDb/dm/model.go
new file mode 100644
index 0000000000000000000000000000000000000000..bb6f34588aa2976c1689b629c6df31219de6b585
--- /dev/null
+++ b/indexedDb/dm/model.go
@@ -0,0 +1,63 @@
+////////////////////////////////////////////////////////////////////////////////
+// Copyright © 2022 xx foundation                                             //
+//                                                                            //
+// Use of this source code is governed by a license that can be found in the  //
+// LICENSE file.                                                              //
+////////////////////////////////////////////////////////////////////////////////
+
+//go:build js && wasm
+
+package channelEventModel
+
+import (
+	"time"
+)
+
+const (
+	// Text representation of primary key value (keyPath).
+	msgPkeyName   = "id"
+	convoPkeyName = "pub_key"
+
+	// Text representation of the names of the various [idb.ObjectStore].
+	messageStoreName      = "messages"
+	conversationStoreName = "conversations"
+
+	// Message index names.
+	messageStoreMessageIndex      = "message_id_index"
+	messageStoreConversationIndex = "conversation_id_index"
+	messageStoreParentIndex       = "parent_message_id_index"
+	messageStoreTimestampIndex    = "timestamp_index"
+
+	// Message keyPath names (must match json struct tags).
+	messageStoreMessage      = "message_id"
+	messageStoreConversation = "conversation_id"
+	messageStoreParent       = "parent_message_id"
+	messageStoreTimestamp    = "timestamp"
+)
+
+// Message defines the IndexedDb representation of a single Message.
+//
+// A Message belongs to one Conversation.
+// A Message may belong to one Message (Parent).
+type Message struct {
+	ID                 uint64    `json:"id"`                   // Matches msgPkeyName
+	MessageID          []byte    `json:"message_id"`           // Index
+	ConversationPubKey []byte    `json:"conversation_pub_key"` // Index
+	ParentMessageID    []byte    `json:"parent_message_id"`    // Index
+	Timestamp          time.Time `json:"timestamp"`            // Index
+	Status             uint8     `json:"status"`
+	Text               []byte    `json:"text"`
+	Type               uint16    `json:"type"`
+	Round              uint64    `json:"round"`
+}
+
+// Conversation defines the IndexedDb representation of a single
+// message exchange between two recipients.
+// A Conversation has many Message.
+type Conversation struct {
+	Pubkey         []byte `json:"pub_key"` // Matches convoPkeyName
+	Nickname       string `json:"nickname"`
+	Token          uint32 `json:"token"`
+	CodesetVersion uint8  `json:"codeset_version"`
+	Blocked        bool   `json:"blocked"`
+}
diff --git a/indexedDb/utils.go b/indexedDb/utils.go
new file mode 100644
index 0000000000000000000000000000000000000000..18904936a8733f69e0c59c7706e22b5152d60412
--- /dev/null
+++ b/indexedDb/utils.go
@@ -0,0 +1,192 @@
+////////////////////////////////////////////////////////////////////////////////
+// Copyright © 2022 xx foundation                                             //
+//                                                                            //
+// Use of this source code is governed by a license that can be found in the  //
+// LICENSE file.                                                              //
+////////////////////////////////////////////////////////////////////////////////
+
+//go:build js && wasm
+
+// This file contains several generic IndexedDB helper functions that
+// may be useful for any IndexedDB implementations.
+
+package indexedDb
+
+import (
+	"context"
+	"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"
+)
+
+// dbTimeout is the global timeout for operations with the storage
+// [context.Context].
+const dbTimeout = time.Second
+
+// NewContext builds a context for indexedDb operations.
+func NewContext() (context.Context, context.CancelFunc) {
+	return context.WithTimeout(context.Background(), dbTimeout)
+}
+
+// Get is a generic helper for getting values from the given [idb.ObjectStore].
+func Get(db *idb.Database, objectStoreName string, key js.Value) (string, error) {
+	parentErr := errors.Errorf("failed to Get %s/%s", objectStoreName, key)
+
+	// Prepare the Transaction
+	txn, err := db.Transaction(idb.TransactionReadOnly, objectStoreName)
+	if err != nil {
+		return "", errors.WithMessagef(parentErr,
+			"Unable to create Transaction: %+v", err)
+	}
+	store, err := txn.ObjectStore(objectStoreName)
+	if err != nil {
+		return "", errors.WithMessagef(parentErr,
+			"Unable to get ObjectStore: %+v", err)
+	}
+
+	// Perform the operation
+	getRequest, err := store.Get(key)
+	if err != nil {
+		return "", errors.WithMessagef(parentErr,
+			"Unable to Get from ObjectStore: %+v", err)
+	}
+
+	// Wait for the operation to return
+	ctx, cancel := NewContext()
+	resultObj, err := getRequest.Await(ctx)
+	cancel()
+	if err != nil {
+		return "", errors.WithMessagef(parentErr,
+			"Unable to get from ObjectStore: %+v", err)
+	}
+
+	// Process result into string
+	resultStr := utils.JsToJson(resultObj)
+	jww.DEBUG.Printf("Got from %s/%s: %s", objectStoreName, key, resultStr)
+	return resultStr, nil
+}
+
+// GetIndex is a generic helper for getting values from the given
+// [idb.ObjectStore] using the given [idb.Index].
+func GetIndex(db *idb.Database, objectStoreName string,
+	indexName string, key js.Value) (js.Value, error) {
+	parentErr := errors.Errorf("failed to GetIndex %s/%s/%s",
+		objectStoreName, indexName, key)
+
+	// Prepare the Transaction
+	txn, err := db.Transaction(idb.TransactionReadOnly, objectStoreName)
+	if err != nil {
+		return js.Value{}, errors.WithMessagef(parentErr,
+			"Unable to create Transaction: %+v", err)
+	}
+	store, err := txn.ObjectStore(objectStoreName)
+	if err != nil {
+		return js.Value{}, errors.WithMessagef(parentErr,
+			"Unable to get ObjectStore: %+v", err)
+	}
+	idx, err := store.Index(indexName)
+	if err != nil {
+		return js.Value{}, errors.WithMessagef(parentErr,
+			"Unable to get Index: %+v", err)
+	}
+
+	// Perform the operation
+	getRequest, err := idx.Get(key)
+	if err != nil {
+		return js.Value{}, errors.WithMessagef(parentErr,
+			"Unable to Get from ObjectStore: %+v", err)
+	}
+
+	// Wait for the operation to return
+	ctx, cancel := NewContext()
+	resultObj, err := getRequest.Await(ctx)
+	cancel()
+	if err != nil {
+		return js.Value{}, errors.WithMessagef(parentErr,
+			"Unable to get from ObjectStore: %+v", err)
+	}
+
+	// Process result into string
+	resultStr := utils.JsToJson(resultObj)
+	jww.DEBUG.Printf("Got from %s/%s/%s: %s",
+		objectStoreName, indexName, key, resultStr)
+	return resultObj, nil
+}
+
+// Put is a generic helper for putting values into the given [idb.ObjectStore].
+// Equivalent to insert if not exists else update.
+func Put(db *idb.Database, objectStoreName string, value js.Value) (*idb.Request, error) {
+	// Prepare the Transaction
+	txn, err := db.Transaction(idb.TransactionReadWrite, objectStoreName)
+	if err != nil {
+		return nil, errors.Errorf("Unable to create Transaction: %+v", err)
+	}
+	store, err := txn.ObjectStore(objectStoreName)
+	if err != nil {
+		return nil, errors.Errorf("Unable to get ObjectStore: %+v", err)
+	}
+
+	// Perform the operation
+	request, err := store.Put(value)
+	if err != nil {
+		return nil, errors.Errorf("Unable to Put: %+v", err)
+	}
+
+	// Wait for the operation to return
+	ctx, cancel := NewContext()
+	err = txn.Await(ctx)
+	cancel()
+	if err != nil {
+		return nil, errors.Errorf("Putting value failed: %+v", err)
+	}
+	jww.DEBUG.Printf("Successfully put value in %s: %v",
+		objectStoreName, value.String())
+	return request, nil
+}
+
+// Dump returns the given [idb.ObjectStore] contents to string slice for
+// testing and debugging purposes.
+func Dump(db *idb.Database, objectStoreName string) ([]string, error) {
+	parentErr := errors.Errorf("failed to Dump %s", objectStoreName)
+
+	txn, err := db.Transaction(idb.TransactionReadOnly, objectStoreName)
+	if err != nil {
+		return nil, errors.WithMessagef(parentErr,
+			"Unable to create Transaction: %+v", err)
+	}
+	store, err := txn.ObjectStore(objectStoreName)
+	if err != nil {
+		return nil, errors.WithMessagef(parentErr,
+			"Unable to get ObjectStore: %+v", err)
+	}
+	cursorRequest, err := store.OpenCursor(idb.CursorNext)
+	if err != nil {
+		return nil, errors.WithMessagef(parentErr,
+			"Unable to open Cursor: %+v", err)
+	}
+
+	// Run the query
+	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
+		})
+	cancel()
+	if err != nil {
+		return nil, errors.WithMessagef(parentErr,
+			"Unable to dump ObjectStore: %+v", err)
+	}
+	return results, nil
+}
diff --git a/wasm/channels.go b/wasm/channels.go
index c906ae53f50081b8bdc6c26c55a56992c0ba1bd4..66dd9e656a156c275d692a7a7bfb7ca2efadaf7c 100644
--- a/wasm/channels.go
+++ b/wasm/channels.go
@@ -11,12 +11,12 @@ package wasm
 
 import (
 	"encoding/base64"
+	"gitlab.com/elixxir/xxdk-wasm/indexedDb/channels"
 	"gitlab.com/xx_network/primitives/id"
 	"sync"
 	"syscall/js"
 
 	"gitlab.com/elixxir/client/v4/bindings"
-	"gitlab.com/elixxir/xxdk-wasm/indexedDb"
 	"gitlab.com/elixxir/xxdk-wasm/utils"
 )
 
@@ -394,7 +394,7 @@ func newChannelsManagerWithIndexedDb(cmixID int, privateIdentity []byte,
 		cb.Invoke(uuid, utils.CopyBytesToJS(channelID.Marshal()), update)
 	}
 
-	model := indexedDb.NewWASMEventModelBuilder(cipher, messageReceivedCB)
+	model := channels.NewWASMEventModelBuilder(cipher, messageReceivedCB)
 
 	promiseFn := func(resolve, reject func(args ...any) js.Value) {
 		cm, err := bindings.NewChannelsManagerGoEventModel(
@@ -493,7 +493,7 @@ func loadChannelsManagerWithIndexedDb(cmixID int, storageTag string,
 		cb.Invoke(uuid, utils.CopyBytesToJS(channelID.Marshal()), updated)
 	}
 
-	model := indexedDb.NewWASMEventModelBuilder(cipher, messageReceivedCB)
+	model := channels.NewWASMEventModelBuilder(cipher, messageReceivedCB)
 
 	promiseFn := func(resolve, reject func(args ...any) js.Value) {
 		cm, err := bindings.LoadChannelsManagerGoEventModel(
@@ -1284,7 +1284,7 @@ func (em *eventModel) LeaveChannel(channelID []byte) {
 //   - nickname - The nickname of the sender of the message (string).
 //   - text - The content of the message (string).
 //   - pubKey - The sender's Ed25519 public key (Uint8Array).
-//   - dmToken - The dmToken (Uint8Array).
+//   - dmToken - The dmToken (int32).
 //   - codeset - The codeset version (int).
 //   - timestamp - Time the message was received; represented as nanoseconds
 //     since unix epoch (int).
@@ -1303,7 +1303,7 @@ func (em *eventModel) LeaveChannel(channelID []byte) {
 //   - 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, pubKey []byte, dmToken []byte, codeset int, timestamp, lease, roundId, msgType,
+	text string, pubKey []byte, dmToken int32, codeset int, timestamp, lease, roundId, msgType,
 	status int64) int64 {
 	uuid := em.receiveMessage(utils.CopyBytesToJS(channelID),
 		utils.CopyBytesToJS(messageID), nickname, text,
@@ -1329,7 +1329,7 @@ func (em *eventModel) ReceiveMessage(channelID, messageID []byte, nickname,
 //   - senderUsername - The username of the sender of the message (string).
 //   - text - The content of the message (string).
 //   - pubKey - The sender's Ed25519 public key (Uint8Array).
-//   - dmToken - The dmToken (Uint8Array).
+//   - dmToken - The dmToken (int32).
 //   - codeset - The codeset version (int).
 //   - timestamp - Time the message was received; represented as nanoseconds
 //     since unix epoch (int).
@@ -1348,7 +1348,7 @@ func (em *eventModel) ReceiveMessage(channelID, messageID []byte, nickname,
 //   - 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, pubKey []byte, dmToken []byte, codeset int, timestamp, lease,
+	senderUsername, text string, pubKey []byte, dmToken int32, codeset int, timestamp, lease,
 	roundId, msgType, status int64) int64 {
 	uuid := em.receiveReply(utils.CopyBytesToJS(channelID),
 		utils.CopyBytesToJS(messageID), utils.CopyBytesToJS(reactionTo),
@@ -1374,7 +1374,7 @@ func (em *eventModel) ReceiveReply(channelID, messageID, reactionTo []byte,
 //   - senderUsername - The username of the sender of the message (string).
 //   - reaction - The contents of the reaction message (string).
 //   - pubKey - The sender's Ed25519 public key (Uint8Array).
-//   - dmToken - The dmToken (Uint8Array).
+//   - dmToken - The dmToken (int32).
 //   - codeset - The codeset version (int).
 //   - timestamp - Time the message was received; represented as nanoseconds
 //     since unix epoch (int).
@@ -1393,7 +1393,7 @@ func (em *eventModel) ReceiveReply(channelID, messageID, reactionTo []byte,
 //   - 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, pubKey []byte, dmToken []byte, codeset int, timestamp,
+	senderUsername, reaction string, pubKey []byte, dmToken int32, codeset int, timestamp,
 	lease, roundId, msgType, status int64) int64 {
 	uuid := em.receiveReaction(utils.CopyBytesToJS(channelID),
 		utils.CopyBytesToJS(messageID), utils.CopyBytesToJS(reactionTo),