diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 57bb307982992558537e7837628bdafb52387d50..00a9f0f079f2cd1328420b9c01ed1e95370f88b2 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -20,7 +20,6 @@ stages:
   - test
   - build
   - tag
-  - version_check
   - doc-update
   - version_check
 
diff --git a/go.mod b/go.mod
index 331f7bd62019b6fedb5bfd2b49e1f42ee6302b48..60cdd5d061c3d438431dc54a505ddc5b91b90ee5 100644
--- a/go.mod
+++ b/go.mod
@@ -7,8 +7,8 @@ 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.12-0.20230111204451-42f3876835cc
-	gitlab.com/elixxir/crypto v0.0.7-0.20230109232445-64f3e6192c3a
+	gitlab.com/elixxir/client/v4 v4.3.12-0.20230113154924-09ab9d5f0387
+	gitlab.com/elixxir/crypto v0.0.7-0.20230113153754-4be32f0a0a89
 	gitlab.com/elixxir/primitives v0.0.3-0.20230109222259-f62b2a90b62c
 	gitlab.com/xx_network/crypto v0.0.5-0.20230109222209-557b66d73c33
 	gitlab.com/xx_network/primitives v0.0.4-0.20221219230308-4b5550a9247d
@@ -18,35 +18,25 @@ require (
 require (
 	filippo.io/edwards25519 v1.0.0 // indirect
 	git.xx.network/elixxir/grpc-web-go-client v0.0.0-20221221204132-2ed1fec765f1 // indirect
-	github.com/agnivade/wasmbrowsertest v0.6.0 // indirect
 	github.com/andres-erbsen/clock v0.0.0-20160526145045-9e14626cd129 // indirect
 	github.com/badoux/checkmail v1.2.1 // indirect
 	github.com/cenkalti/backoff/v4 v4.1.3 // indirect
-	github.com/chromedp/cdproto v0.0.0-20221108233440-fad8339618ab // indirect
-	github.com/chromedp/chromedp v0.8.6 // indirect
-	github.com/chromedp/sysutil v1.0.0 // indirect
 	github.com/cloudflare/circl v1.2.0 // indirect
 	github.com/desertbit/timer v0.0.0-20180107155436-c41aec40b27f // indirect
 	github.com/elliotchance/orderedmap v1.4.0 // indirect
 	github.com/forPelevin/gomoji v1.1.8 // indirect
 	github.com/fsnotify/fsnotify v1.5.4 // indirect
-	github.com/go-interpreter/wagon v0.6.0 // indirect
-	github.com/gobwas/httphead v0.1.0 // indirect
-	github.com/gobwas/pool v0.2.1 // indirect
 	github.com/gobwas/ws v1.1.0 // indirect
 	github.com/golang-collections/collections v0.0.0-20130729185459-604e922904d3 // indirect
 	github.com/golang/protobuf v1.5.2 // indirect
-	github.com/google/pprof v0.0.0-20221103000818-d260c55eee4c // indirect
 	github.com/gorilla/websocket v1.5.0 // indirect
 	github.com/hashicorp/hcl v1.0.0 // indirect
 	github.com/improbable-eng/grpc-web v0.15.0 // indirect
 	github.com/inconshreveable/mousetrap v1.0.0 // indirect
-	github.com/josharian/intern v1.0.0 // indirect
 	github.com/json-iterator/go v1.1.12 // indirect
 	github.com/klauspost/compress v1.11.7 // indirect
 	github.com/klauspost/cpuid/v2 v2.1.0 // indirect
 	github.com/magiconair/properties v1.8.6 // indirect
-	github.com/mailru/easyjson v0.7.7 // indirect
 	github.com/mattn/go-isatty v0.0.14 // indirect
 	github.com/mitchellh/go-homedir v1.1.0 // indirect
 	github.com/mitchellh/mapstructure v1.5.0 // indirect
diff --git a/go.sum b/go.sum
index 8a21a42deacb65702eb8af57b5a245bd5f093826..0254623295bcaa94abf36d6b831f44b06007c039 100644
--- a/go.sum
+++ b/go.sum
@@ -548,6 +548,18 @@ gitlab.com/elixxir/client/v4 v4.3.12-0.20230111011720-685725612575 h1:ArQqykxDAq
 gitlab.com/elixxir/client/v4 v4.3.12-0.20230111011720-685725612575/go.mod h1:03CuoZNfVcZAbD0IrX/FxOMMwaKUWCMeMWE6781ZDIg=
 gitlab.com/elixxir/client/v4 v4.3.12-0.20230111204451-42f3876835cc h1:c+h+mq8ouaCpUbgjAdvCtlk3zZAvxW4GhyePzHbpocc=
 gitlab.com/elixxir/client/v4 v4.3.12-0.20230111204451-42f3876835cc/go.mod h1:03CuoZNfVcZAbD0IrX/FxOMMwaKUWCMeMWE6781ZDIg=
+gitlab.com/elixxir/client/v4 v4.3.12-0.20230112174912-de174933d587 h1:kdG+nQlSaPOmSEe/YvVK3fria8OZj/bHpZEO4aBXr8M=
+gitlab.com/elixxir/client/v4 v4.3.12-0.20230112174912-de174933d587/go.mod h1:hx/wYHaAvX9hse9vUWIzE/4IqWFFMbCDMPuGQAaAGDQ=
+gitlab.com/elixxir/client/v4 v4.3.12-0.20230112175132-70ac938c67eb h1:B86ZcS4AdozJlY/GNuzivHjOSPYS3rebqiAwH59yXqI=
+gitlab.com/elixxir/client/v4 v4.3.12-0.20230112175132-70ac938c67eb/go.mod h1:hx/wYHaAvX9hse9vUWIzE/4IqWFFMbCDMPuGQAaAGDQ=
+gitlab.com/elixxir/client/v4 v4.3.12-0.20230112205052-fc2a77781cff h1:uWWdqP2ZpwabVdrNHxz5l7u607vk7ATD5QVzy5kgkvU=
+gitlab.com/elixxir/client/v4 v4.3.12-0.20230112205052-fc2a77781cff/go.mod h1:nWdt+YuNQ5T5LhshVpAuVnYqaVASCFMIyFgG2h8V+is=
+gitlab.com/elixxir/client/v4 v4.3.12-0.20230112234243-43cd8e0f61df h1:pq6pT5m4TivgDZNyZdqpK0G/iNeQnBCK4U9kh/arjFE=
+gitlab.com/elixxir/client/v4 v4.3.12-0.20230112234243-43cd8e0f61df/go.mod h1:ohWuyRXcETnCnx8X1+WNQhh70wqLd9GQYQVD0YaKW+4=
+gitlab.com/elixxir/client/v4 v4.3.12-0.20230113004731-2099bf23f6b7 h1:uafHvqWkuAe/0/TLe/mi14uCE1utOxk3/vBwV32QzCs=
+gitlab.com/elixxir/client/v4 v4.3.12-0.20230113004731-2099bf23f6b7/go.mod h1:ohWuyRXcETnCnx8X1+WNQhh70wqLd9GQYQVD0YaKW+4=
+gitlab.com/elixxir/client/v4 v4.3.12-0.20230113154924-09ab9d5f0387 h1:EEJ2MOgGzGcu/Pr6xPBFBLPZDPowShPCsyD0sTRmC2Q=
+gitlab.com/elixxir/client/v4 v4.3.12-0.20230113154924-09ab9d5f0387/go.mod h1:akit+kacZtAbLxE6DkOJDAy8LfYOw2gy385IQJyDWCY=
 gitlab.com/elixxir/comms v0.0.4-0.20230109184457-e10f20295430 h1:OydFdoBbLz5iFzCiYEb+m8Q2pZjdVVCne4m+MyFAzUc=
 gitlab.com/elixxir/comms v0.0.4-0.20230109184457-e10f20295430/go.mod h1:aFnxDpIxEEFHdAa2dEeydzo00u/IAcfrqPSEnmeffbY=
 gitlab.com/elixxir/comms v0.0.4-0.20230109233320-a0c90d3324a0 h1:jMmI+j4P5e+nmf82xKs679M6EzeuUhQJjOwXhXi6Cl0=
@@ -556,6 +568,14 @@ gitlab.com/elixxir/crypto v0.0.7-0.20230109182503-bd51c95bdcb3 h1:5au07K9R4K4RMR
 gitlab.com/elixxir/crypto v0.0.7-0.20230109182503-bd51c95bdcb3/go.mod h1:7whUm4bnEdEoiVfMnu3TbHgvlrz0Ywp/Tekqg2Wl7vw=
 gitlab.com/elixxir/crypto v0.0.7-0.20230109232445-64f3e6192c3a h1:XjE1WZk9DDyw2Rb7p14PWdlpMmsQJFkKVRcMpgMakUQ=
 gitlab.com/elixxir/crypto v0.0.7-0.20230109232445-64f3e6192c3a/go.mod h1:5RnVcYvjX6Q0+2Rg/PcWgqJSrhOHgzw14YLbd0AshjE=
+gitlab.com/elixxir/crypto v0.0.7-0.20230112173817-b922d4a512ed h1:P5wgqwsvnEMJVnerv7dZ/zP0FcjrqJ9zZRaC9N4XEEo=
+gitlab.com/elixxir/crypto v0.0.7-0.20230112173817-b922d4a512ed/go.mod h1:T+uRZRqDdf9C8+VLGNY3mrCoWuoctfzxFlO+XNorIxM=
+gitlab.com/elixxir/crypto v0.0.7-0.20230112203618-74f75155c930 h1:28Meq1azAIm96QlPnGKr+hVH/PPF91u9tnhyNivXf/4=
+gitlab.com/elixxir/crypto v0.0.7-0.20230112203618-74f75155c930/go.mod h1:T+uRZRqDdf9C8+VLGNY3mrCoWuoctfzxFlO+XNorIxM=
+gitlab.com/elixxir/crypto v0.0.7-0.20230112220617-8ac1fa200509 h1:SXHXgaI0dLies9w+nJ26RbMb8Wz+4TzH5gGiJA4scwc=
+gitlab.com/elixxir/crypto v0.0.7-0.20230112220617-8ac1fa200509/go.mod h1:T+uRZRqDdf9C8+VLGNY3mrCoWuoctfzxFlO+XNorIxM=
+gitlab.com/elixxir/crypto v0.0.7-0.20230113153754-4be32f0a0a89 h1:esw3acCVQDJiRGjtYM5oJ+3mPbGY/eJHcyXn7h3Khzg=
+gitlab.com/elixxir/crypto v0.0.7-0.20230113153754-4be32f0a0a89/go.mod h1:T+uRZRqDdf9C8+VLGNY3mrCoWuoctfzxFlO+XNorIxM=
 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.20221214192222-988b44a6958a h1:F17FfEjS+/uDI/TTYQD21S5JvNZ9+p9bieau2nyLCzo=
diff --git a/indexedDb/impl/channels/implementation.go b/indexedDb/impl/channels/implementation.go
index 40fbb264e4fc5f3ae507feec0b728bc30276fd19..28b679546521e254ed9bb46b8de61cf45ebfadef 100644
--- a/indexedDb/impl/channels/implementation.go
+++ b/indexedDb/impl/channels/implementation.go
@@ -13,7 +13,6 @@ import (
 	"crypto/ed25519"
 	"encoding/base64"
 	"encoding/json"
-	"gitlab.com/elixxir/xxdk-wasm/indexedDb/impl"
 	"strings"
 	"sync"
 	"syscall/js"
@@ -28,6 +27,7 @@ import (
 	cryptoBroadcast "gitlab.com/elixxir/crypto/broadcast"
 	cryptoChannel "gitlab.com/elixxir/crypto/channel"
 	"gitlab.com/elixxir/crypto/message"
+	"gitlab.com/elixxir/xxdk-wasm/indexedDb/impl"
 	"gitlab.com/elixxir/xxdk-wasm/utils"
 	"gitlab.com/xx_network/primitives/id"
 )
@@ -39,6 +39,8 @@ type wasmModel struct {
 	db                *idb.Database
 	cipher            cryptoChannel.Cipher
 	receivedMessageCB MessageReceivedCallback
+	deletedMessageCB  DeletedMessageCallback
+	mutedUserCB       MutedUserCallback
 	updateMux         sync.Mutex
 }
 
@@ -79,7 +81,7 @@ func (w *wasmModel) LeaveChannel(channelID *id.ID) {
 	parentErr := errors.New("failed to LeaveChannel")
 
 	// Delete the channel from storage
-	err := impl.Delete(w.db, channelsStoreName,
+	err := indexedDb.Delete(w.db, channelsStoreName,
 		js.ValueOf(channelID.String()))
 	if err != nil {
 		jww.ERROR.Printf("%+v", errors.WithMessagef(parentErr,
@@ -488,8 +490,22 @@ func (w *wasmModel) GetMessage(
 // DeleteMessage removes a message with the given messageID from storage.
 func (w *wasmModel) DeleteMessage(messageID message.ID) error {
 	msgId := js.ValueOf(base64.StdEncoding.EncodeToString(messageID.Bytes()))
-	return impl.DeleteIndex(w.db, messageStoreName,
-		messageStoreMessageIndex, pkeyName, msgId)
+
+	err := impl.DeleteIndex(
+		w.db, messageStoreName, messageStoreMessageIndex, pkeyName, msgId)
+	if err != nil {
+		return err
+	}
+
+	go w.deletedMessageCB(messageID)
+
+	return nil
+}
+
+// MuteUser is called whenever a user is muted or unmuted.
+func (w *wasmModel) MuteUser(
+	channelID *id.ID, pubKey ed25519.PublicKey, unmute bool) {
+	go w.mutedUserCB(channelID, pubKey, unmute)
 }
 
 // msgIDLookup gets the UUID of the Message with the given messageID.
@@ -510,5 +526,4 @@ func (w *wasmModel) msgIDLookup(messageID message.ID) (*Message, error) {
 		return nil, err
 	}
 	return resultMsg, nil
-
 }
diff --git a/indexedDb/impl/channels/implementation_test.go b/indexedDb/impl/channels/implementation_test.go
index 8642dc927d085d125ca669a9c9b67a9c144fcece..0e9b58de94f9b37b899cc428eca2aa735698c800 100644
--- a/indexedDb/impl/channels/implementation_test.go
+++ b/indexedDb/impl/channels/implementation_test.go
@@ -10,25 +10,27 @@
 package main
 
 import (
+	"crypto/ed25519"
 	"encoding/json"
 	"fmt"
-	"github.com/hack-pad/go-indexeddb/idb"
-	"gitlab.com/elixxir/crypto/message"
-	"gitlab.com/elixxir/xxdk-wasm/indexedDb/impl"
-	"gitlab.com/elixxir/xxdk-wasm/storage"
-	"gitlab.com/xx_network/crypto/csprng"
-	"gitlab.com/xx_network/primitives/netTime"
 	"os"
 	"strconv"
 	"testing"
 	"time"
 
+	"github.com/hack-pad/go-indexeddb/idb"
 	jww "github.com/spf13/jwalterweatherman"
+
 	"gitlab.com/elixxir/client/v4/channels"
 	"gitlab.com/elixxir/client/v4/cmix/rounds"
 	cryptoBroadcast "gitlab.com/elixxir/crypto/broadcast"
 	cryptoChannel "gitlab.com/elixxir/crypto/channel"
+	"gitlab.com/elixxir/crypto/message"
+	"gitlab.com/elixxir/xxdk-wasm/indexedDb/impl"
+	"gitlab.com/elixxir/xxdk-wasm/storage"
+	"gitlab.com/xx_network/crypto/csprng"
 	"gitlab.com/xx_network/primitives/id"
+	"gitlab.com/xx_network/primitives/netTime"
 )
 
 func TestMain(m *testing.M) {
@@ -36,14 +38,10 @@ func TestMain(m *testing.M) {
 	os.Exit(m.Run())
 }
 
-func dummyCallback(uint64, *id.ID, bool) {}
-
-// dummyStoreDatabaseName always returns nil error and adheres to the
-// storeDatabaseNameFn type.
+func dummyReceivedMessageCB(uint64, *id.ID, bool)      {}
+func dummyDeletedMessageCB(message.ID)                 {}
+func dummyMutedUserCB(*id.ID, ed25519.PublicKey, bool) {}
 func dummyStoreDatabaseName(string) error { return nil }
-
-// dummyStoreEncryptionStatus returns the same encryption status passed into it
-// and adheres to the storeEncryptionStatusFn type.
 func dummyStoreEncryptionStatus(_ string, encryptionStatus bool) (bool, error) {
 	return encryptionStatus, nil
 }
@@ -66,7 +64,8 @@ func TestWasmModel_msgIDLookup(t *testing.T) {
 			testString := "TestWasmModel_msgIDLookup" + cs
 			testMsgId := message.DeriveChannelMessageID(&id.ID{1}, 0, []byte(testString))
 
-			eventModel, err2 := newWASMModel(testString, c, dummyCallback,
+			eventModel, err2 := newWASMModel(testString, c,
+				dummyReceivedMessageCB, dummyDeletedMessageCB, dummyMutedUserCB,
 				dummyStoreDatabaseName, dummyStoreEncryptionStatus)
 			if err2 != nil {
 				t.Fatal(err2)
@@ -96,8 +95,9 @@ func TestWasmModel_DeleteMessage(t *testing.T) {
 	storage.GetLocalStorage().Clear()
 	testString := "TestWasmModel_DeleteMessage"
 	testMsgId := message.DeriveChannelMessageID(&id.ID{1}, 0, []byte(testString))
-	eventModel, err := newWASMModel(testString, nil, dummyCallback,
-		dummyStoreDatabaseName, dummyStoreEncryptionStatus)
+	eventModel, err := newWASMModel(testString, nil, dummyReceivedMessageCB,
+		dummyDeletedMessageCB, dummyMutedUserCB, dummyStoreDatabaseName,
+		dummyStoreEncryptionStatus)
 	if err != nil {
 		t.Fatal(err)
 	}
@@ -153,7 +153,8 @@ func Test_wasmModel_UpdateSentStatus(t *testing.T) {
 			testString := "Test_wasmModel_UpdateSentStatus" + cs
 			testMsgId := message.DeriveChannelMessageID(
 				&id.ID{1}, 0, []byte(testString))
-			eventModel, err2 := newWASMModel(testString, c, dummyCallback,
+			eventModel, err2 := newWASMModel(testString, c,
+				dummyReceivedMessageCB, dummyDeletedMessageCB, dummyMutedUserCB,
 				dummyStoreDatabaseName, dummyStoreEncryptionStatus)
 			if err2 != nil {
 				t.Fatal(err)
@@ -161,8 +162,8 @@ 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}, 0, 0, netTime.Now(),
-				time.Second, 0, 0, false, false, channels.Sent)
+				testString, []byte(testString), []byte{8, 6, 7, 5}, 0, 0,
+				netTime.Now(), time.Second, 0, 0, false, false, channels.Sent)
 			uuid, err2 := eventModel.receiveHelper(testMsg, false)
 			if err2 != nil {
 				t.Fatal(err2)
@@ -221,8 +222,9 @@ func Test_wasmModel_JoinChannel_LeaveChannel(t *testing.T) {
 		}
 		t.Run("Test_wasmModel_JoinChannel_LeaveChannel"+cs, func(t *testing.T) {
 			storage.GetLocalStorage().Clear()
-			eventModel, err2 := newWASMModel("test", c, dummyCallback,
-				dummyStoreDatabaseName, dummyStoreEncryptionStatus)
+			eventModel, err2 := newWASMModel("test", c, dummyReceivedMessageCB,
+				dummyDeletedMessageCB, dummyMutedUserCB, dummyStoreDatabaseName,
+				dummyStoreEncryptionStatus)
 			if err2 != nil {
 				t.Fatal(err2)
 			}
@@ -275,7 +277,8 @@ func Test_wasmModel_UUIDTest(t *testing.T) {
 		t.Run("Test_wasmModel_UUIDTest"+cs, func(t *testing.T) {
 			storage.GetLocalStorage().Clear()
 			testString := "testHello" + cs
-			eventModel, err2 := newWASMModel(testString, c, dummyCallback,
+			eventModel, err2 := newWASMModel(testString, c,
+				dummyReceivedMessageCB, dummyDeletedMessageCB, dummyMutedUserCB,
 				dummyStoreDatabaseName, dummyStoreEncryptionStatus)
 			if err2 != nil {
 				t.Fatal(err2)
@@ -322,7 +325,8 @@ func Test_wasmModel_DuplicateReceives(t *testing.T) {
 		t.Run("Test_wasmModel_DuplicateReceives"+cs, func(t *testing.T) {
 			storage.GetLocalStorage().Clear()
 			testString := "testHello"
-			eventModel, err2 := newWASMModel(testString, c, dummyCallback,
+			eventModel, err2 := newWASMModel(testString, c,
+				dummyReceivedMessageCB, dummyDeletedMessageCB, dummyMutedUserCB,
 				dummyStoreDatabaseName, dummyStoreEncryptionStatus)
 			if err2 != nil {
 				t.Fatal(err2)
@@ -372,7 +376,8 @@ func Test_wasmModel_deleteMsgByChannel(t *testing.T) {
 			testString := "test_deleteMsgByChannel"
 			totalMessages := 10
 			expectedMessages := 5
-			eventModel, err2 := newWASMModel(testString, c, dummyCallback,
+			eventModel, err2 := newWASMModel(testString, c,
+				dummyReceivedMessageCB, dummyDeletedMessageCB, dummyMutedUserCB,
 				dummyStoreDatabaseName, dummyStoreEncryptionStatus)
 			if err2 != nil {
 				t.Fatal(err2)
@@ -443,7 +448,8 @@ func TestWasmModel_receiveHelper_UniqueIndex(t *testing.T) {
 		t.Run("TestWasmModel_receiveHelper_UniqueIndex"+cs, func(t *testing.T) {
 			storage.GetLocalStorage().Clear()
 			testString := fmt.Sprintf("test_receiveHelper_UniqueIndex_%d", i)
-			eventModel, err2 := newWASMModel(testString, c, dummyCallback,
+			eventModel, err2 := newWASMModel(testString, c,
+				dummyReceivedMessageCB, dummyDeletedMessageCB, dummyMutedUserCB,
 				dummyStoreDatabaseName, dummyStoreEncryptionStatus)
 			if err2 != nil {
 				t.Fatal(err2)
diff --git a/indexedDb/impl/channels/init.go b/indexedDb/impl/channels/init.go
index 70768a14752e50a984741946452fd7702c0d8561..dd0334c00198261013d4c302f29ead76c1fab5da 100644
--- a/indexedDb/impl/channels/init.go
+++ b/indexedDb/impl/channels/init.go
@@ -10,11 +10,13 @@
 package main
 
 import (
+	"crypto/ed25519"
 	"github.com/hack-pad/go-indexeddb/idb"
 	"github.com/pkg/errors"
 	jww "github.com/spf13/jwalterweatherman"
 	"gitlab.com/elixxir/client/v4/channels"
 	cryptoChannel "gitlab.com/elixxir/crypto/channel"
+	"gitlab.com/elixxir/crypto/message"
 	"gitlab.com/elixxir/xxdk-wasm/indexedDb/impl"
 	"gitlab.com/xx_network/primitives/id"
 	"syscall/js"
@@ -35,6 +37,14 @@ const (
 // update is true if the row is old and was edited.
 type MessageReceivedCallback func(uuid uint64, channelID *id.ID, update bool)
 
+// DeletedMessageCallback is called any time a message is deleted.
+type DeletedMessageCallback func(messageID message.ID)
+
+// MutedUserCallback is called any time a user is muted or unmuted. unmute is
+// true if the user has been unmuted and false if they have been muted.
+type MutedUserCallback func(
+	channelID *id.ID, pubKey ed25519.PublicKey, unmute bool)
+
 // storeDatabaseNameFn matches storage.StoreIndexedDb so that the data can be
 // sent between the worker and main thread.
 type storeDatabaseNameFn func(databaseName string) error
@@ -47,16 +57,20 @@ type storeEncryptionStatusFn func(
 // 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, storeDatabaseName storeDatabaseNameFn,
+	messageReceivedCB MessageReceivedCallback,
+	deletedMessageCB DeletedMessageCallback,
+	mutedUserCB MutedUserCallback, storeDatabaseName storeDatabaseNameFn,
 	storeEncryptionStatus storeEncryptionStatusFn) (channels.EventModel, error) {
 	databaseName := path + databaseSuffix
-	return newWASMModel(
-		databaseName, encryption, cb, storeDatabaseName, storeEncryptionStatus)
+	return newWASMModel(databaseName, encryption, messageReceivedCB,
+		deletedMessageCB, mutedUserCB, storeDatabaseName, storeEncryptionStatus)
 }
 
 // newWASMModel creates the given [idb.Database] and returns a wasmModel.
 func newWASMModel(databaseName string, encryption cryptoChannel.Cipher,
-	cb MessageReceivedCallback, storeDatabaseName storeDatabaseNameFn,
+	messageReceivedCB MessageReceivedCallback,
+	deletedMessageCB DeletedMessageCallback, mutedUserCB MutedUserCallback,
+	storeDatabaseName storeDatabaseNameFn,
 	storeEncryptionStatus storeEncryptionStatusFn) (*wasmModel, error) {
 	// Attempt to open database object
 	ctx, cancel := impl.NewContext()
@@ -115,7 +129,13 @@ func newWASMModel(databaseName string, encryption cryptoChannel.Cipher,
 		jww.WARN.Printf("IndexedDb encryption disabled!")
 	}
 
-	wrapper := &wasmModel{db: db, receivedMessageCB: cb, cipher: encryption}
+	wrapper := &wasmModel{
+		db:                db,
+		cipher:            encryption,
+		receivedMessageCB: messageReceivedCB,
+		deletedMessageCB:  deletedMessageCB,
+		mutedUserCB:       mutedUserCB,
+	}
 
 	return wrapper, nil
 }
diff --git a/indexedDb/impl/dm/implementation.go b/indexedDb/impl/dm/implementation.go
index 293754b06f0035b7b63feca8f2a40ec405f38557..8280d7b5138c00eab29b150ecc1fd749b6d60b13 100644
--- a/indexedDb/impl/dm/implementation.go
+++ b/indexedDb/impl/dm/implementation.go
@@ -12,7 +12,6 @@ package main
 import (
 	"crypto/ed25519"
 	"encoding/json"
-	"gitlab.com/elixxir/xxdk-wasm/indexedDb/impl"
 	"strings"
 	"sync"
 	"syscall/js"
@@ -28,6 +27,7 @@ import (
 	"github.com/hack-pad/go-indexeddb/idb"
 	cryptoChannel "gitlab.com/elixxir/crypto/channel"
 	"gitlab.com/elixxir/crypto/message"
+	"gitlab.com/elixxir/xxdk-wasm/indexedDb/impl"
 )
 
 // wasmModel implements dm.EventModel interface, which uses the channels system
@@ -100,8 +100,7 @@ func (w *wasmModel) Receive(messageID message.ID, nickname string, text []byte,
 	parentErr := errors.New("failed to Receive")
 
 	// If there is no extant Conversation, create one.
-	_, err := impl.Get(
-		w.db, conversationStoreName, utils.CopyBytesToJS(pubKey))
+	_, err := impl.Get(w.db, conversationStoreName, utils.CopyBytesToJS(pubKey))
 	if err != nil {
 		if strings.Contains(err.Error(), impl.ErrDoesNotExist) {
 			err = w.joinConversation(nickname, pubKey, dmToken, codeset)
@@ -143,8 +142,7 @@ func (w *wasmModel) ReceiveText(messageID message.ID, nickname, text string,
 	parentErr := errors.New("failed to ReceiveText")
 
 	// If there is no extant Conversation, create one.
-	_, err := impl.Get(
-		w.db, conversationStoreName, utils.CopyBytesToJS(pubKey))
+	_, err := impl.Get(w.db, conversationStoreName, utils.CopyBytesToJS(pubKey))
 	if err != nil {
 		if strings.Contains(err.Error(), impl.ErrDoesNotExist) {
 			err = w.joinConversation(nickname, pubKey, dmToken, codeset)
@@ -188,8 +186,7 @@ func (w *wasmModel) ReceiveReply(messageID, reactionTo message.ID, nickname,
 	parentErr := errors.New("failed to ReceiveReply")
 
 	// If there is no extant Conversation, create one.
-	_, err := impl.Get(
-		w.db, conversationStoreName, utils.CopyBytesToJS(pubKey))
+	_, err := impl.Get(w.db, conversationStoreName, utils.CopyBytesToJS(pubKey))
 	if err != nil {
 		if strings.Contains(err.Error(), impl.ErrDoesNotExist) {
 			err = w.joinConversation(nickname, pubKey, dmToken, codeset)
@@ -233,8 +230,7 @@ func (w *wasmModel) ReceiveReaction(messageID, _ message.ID, nickname,
 	parentErr := errors.New("failed to ReceiveText")
 
 	// If there is no extant Conversation, create one.
-	_, err := impl.Get(
-		w.db, conversationStoreName, utils.CopyBytesToJS(pubKey))
+	_, err := impl.Get(w.db, conversationStoreName, utils.CopyBytesToJS(pubKey))
 	if err != nil {
 		if strings.Contains(err.Error(), impl.ErrDoesNotExist) {
 			err = w.joinConversation(nickname, pubKey, dmToken, codeset)
diff --git a/main.go b/main.go
index de379dbdbf5eb3132e8cc546df5a6b601665e5e6..80fea11e65a139a0d939c1c05a6ada764bd92ab1 100644
--- a/main.go
+++ b/main.go
@@ -196,6 +196,8 @@ func main() {
 	js.Global().Set("GetClientVersion", js.FuncOf(wasm.GetClientVersion))
 	js.Global().Set("GetClientGitVersion", js.FuncOf(wasm.GetClientGitVersion))
 	js.Global().Set("GetClientDependencies", js.FuncOf(wasm.GetClientDependencies))
+	js.Global().Set("GetWasmSemanticVersion", js.FuncOf(wasm.GetWasmSemanticVersion))
+	js.Global().Set("GetXXDKSemanticVersion", js.FuncOf(wasm.GetXXDKSemanticVersion))
 
 	<-make(chan bool)
 	os.Exit(0)
diff --git a/storage/version.go b/storage/version.go
index d513092819786e1a13a1d36fc52464f5c402df94..7d39cb94d3bf0204f846fd620b68c87b59f12ce9 100644
--- a/storage/version.go
+++ b/storage/version.go
@@ -11,9 +11,11 @@ package storage
 
 import (
 	"os"
+	"sync"
 
 	"github.com/pkg/errors"
 	jww "github.com/spf13/jwalterweatherman"
+
 	"gitlab.com/elixxir/client/v4/bindings"
 )
 
@@ -38,17 +40,23 @@ func CheckAndStoreVersions() error {
 
 func checkAndStoreVersions(
 	currentWasmVer, currentClientVer string, ls *LocalStorage) error {
-	// Get the stored client and WASM versions, if they exists
-	storedClientVer, err := initOrLoadStoredSemver(
-		clientVerKey, currentClientVer, ls)
+	// Get the stored client version, if it exists
+	storedClientVer, err :=
+		initOrLoadStoredSemver(clientVerKey, currentClientVer, ls)
 	if err != nil {
 		return err
 	}
+
+	// Get the stored WASM versions, if it exists
 	storedWasmVer, err := initOrLoadStoredSemver(semverKey, currentWasmVer, ls)
 	if err != nil {
 		return err
 	}
 
+	// Store old versions to memory
+	setOldClientSemVersion(storedClientVer)
+	setOldWasmSemVersion(storedWasmVer)
+
 	// Check if client needs an update
 	if storedClientVer != currentClientVer {
 		jww.INFO.Printf("xxDK client out of date; upgrading version: v%s → v%s",
@@ -96,3 +104,43 @@ func initOrLoadStoredSemver(
 	// Return the stored version
 	return string(storedVersion), nil
 }
+
+// oldVersions contains the old versions of xxdk WASM and xxdk client that were
+// stored in storage before being overwritten on update.
+var oldVersions struct {
+	wasm   string
+	client string
+	sync.Mutex
+}
+
+// GetOldWasmSemVersion returns the old version of xxdk WASM before being
+// updated.
+func GetOldWasmSemVersion() string {
+	oldVersions.Lock()
+	defer oldVersions.Unlock()
+	return oldVersions.wasm
+}
+
+// GetOldClientSemVersion returns the old version of xxdk client before being
+// updated.
+func GetOldClientSemVersion() string {
+	oldVersions.Lock()
+	defer oldVersions.Unlock()
+	return oldVersions.client
+}
+
+// setOldWasmSemVersion sets the old version of xxdk WASM. This should be called
+// before it is updated.
+func setOldWasmSemVersion(v string) {
+	oldVersions.Lock()
+	defer oldVersions.Unlock()
+	oldVersions.wasm = v
+}
+
+// setOldClientSemVersion sets the old version of xxdk client. This should be
+// called before it is updated.
+func setOldClientSemVersion(v string) {
+	oldVersions.Lock()
+	defer oldVersions.Unlock()
+	oldVersions.client = v
+}
diff --git a/wasm/channels.go b/wasm/channels.go
index 30442a269ac8d252256179926c3b0dd6404a8163..43ae72bdf4ad9b01441c20c800ca9bafbdcf7620 100644
--- a/wasm/channels.go
+++ b/wasm/channels.go
@@ -10,10 +10,12 @@
 package wasm
 
 import (
+	"crypto/ed25519"
 	"encoding/base64"
 	"encoding/json"
 	"errors"
 	"gitlab.com/elixxir/client/v4/channels"
+	"gitlab.com/elixxir/crypto/message"
 	channelsDb "gitlab.com/elixxir/xxdk-wasm/indexedDb/worker/channels"
 	"gitlab.com/xx_network/primitives/id"
 	"sync"
@@ -320,14 +322,23 @@ func LoadChannelsManager(_ js.Value, args []js.Value) any {
 //   - args[1] - Path to Javascript file that starts the worker (string).
 //   - args[2] - Bytes of a private identity ([channel.PrivateIdentity]) that is
 //     generated by [GenerateChannelIdentity] (Uint8Array).
-//   - args[3] - 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.
-//   - args[4] - ID of [ChannelDbCipher] object in tracker (int). Create this
+//   - args[3] - The received message callback, which is called everytime a
+//     message is added or changed in the database. It is a 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.
+//   - args[4] - The deleted message callback, which is called everytime a
+//     message is deleted from the database. It is a function that takes in the
+//     same parameters as [indexedDb.DeletedMessageCallback]. On the Javascript
+//     side, the message ID is returned as a Uint8Array.
+//   - args[5] - The muted user callback, which is called everytime a user is
+//     muted or unmuted. It is a function that takes in the same parameters as
+//     [indexedDb.MutedUserCallback]. On the Javascript side, the channel ID and
+//     user public key are returned as Uint8Array.
+//   - args[6] - ID of [ChannelDbCipher] object in tracker (int). Create this
 //     object with [NewChannelsDatabaseCipher] and get its id with
 //     [ChannelDbCipher.GetID].
 //
@@ -340,15 +351,17 @@ func NewChannelsManagerWithIndexedDb(_ js.Value, args []js.Value) any {
 	wasmJsPath := args[1].String()
 	privateIdentity := utils.CopyBytesToGo(args[2])
 	messageReceivedCB := args[3]
-	cipherID := args[4].Int()
+	deletedMessageCB := args[4]
+	mutedUserCB := args[5]
+	cipherID := args[6].Int()
 
 	cipher, err := bindings.GetChannelDbCipherTrackerFromID(cipherID)
 	if err != nil {
 		utils.Throw(utils.TypeError, err)
 	}
 
-	return newChannelsManagerWithIndexedDb(
-		cmixID, wasmJsPath, privateIdentity, messageReceivedCB, cipher)
+	return newChannelsManagerWithIndexedDb(cmixID, wasmJsPath, privateIdentity,
+		messageReceivedCB, deletedMessageCB, mutedUserCB, cipher)
 }
 
 // NewChannelsManagerWithIndexedDbUnsafe creates a new [ChannelsManager] from a
@@ -369,35 +382,58 @@ func NewChannelsManagerWithIndexedDb(_ js.Value, args []js.Value) any {
 //   - args[1] - Path to Javascript file that starts the worker (string).
 //   - args[2] - Bytes of a private identity ([channel.PrivateIdentity]) that is
 //     generated by [GenerateChannelIdentity] (Uint8Array).
-//   - args[3] - 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
+//   - args[3] - The received message callback, which is called everytime a
+//     message is added or changed in the database. It is a 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.
+//   - args[4] - The deleted message callback, which is called everytime a
+//     message is deleted from the database. It is a function that takes in the
+//     same parameters as [indexedDb.DeletedMessageCallback]. On the Javascript
+//     side, the message ID is returned as a Uint8Array.
+//   - args[5] - The muted user callback, which is called everytime a user is
+//     muted or unmuted. It is a function that takes in the same parameters as
+//     [indexedDb.MutedUserCallback]. On the Javascript side, the channel ID and
+//     user public key are returned as Uint8Array.
 //
 // Returns a promise:
 //   - Resolves to a Javascript representation of the [ChannelsManager] object.
 //   - Rejected with an error if loading indexedDb or the manager fails.
+// FIXME: package names in comments for indexedDb
 func NewChannelsManagerWithIndexedDbUnsafe(_ js.Value, args []js.Value) any {
 	cmixID := args[0].Int()
 	wasmJsPath := args[1].String()
 	privateIdentity := utils.CopyBytesToGo(args[2])
 	messageReceivedCB := args[3]
+	deletedMessageCB := args[4]
+	mutedUserCB := args[5]
 
-	return newChannelsManagerWithIndexedDb(
-		cmixID, wasmJsPath, privateIdentity, messageReceivedCB, nil)
+	return newChannelsManagerWithIndexedDb(cmixID, wasmJsPath, privateIdentity,
+		messageReceivedCB, deletedMessageCB, mutedUserCB, nil)
 }
 
 func newChannelsManagerWithIndexedDb(cmixID int, wasmJsPath string,
-	privateIdentity []byte, cb js.Value, cipher *bindings.ChannelDbCipher) any {
+	privateIdentity []byte, messageReceivedCB, deletedMessageCB, mutedUserCB js.Value,
+	cipher *bindings.ChannelDbCipher) any {
 
-	messageReceivedCB := func(uuid uint64, channelID *id.ID, update bool) {
-		cb.Invoke(uuid, utils.CopyBytesToJS(channelID.Marshal()), update)
+	messageReceived := func(uuid uint64, channelID *id.ID, update bool) {
+		messageReceivedCB.Invoke(uuid, utils.CopyBytesToJS(channelID.Marshal()), update)
 	}
 
-	model := channelsDb.NewWASMEventModelBuilder(wasmJsPath, cipher, messageReceivedCB)
+	deletedMessage := func(messageID message.ID) {
+		deletedMessageCB.Invoke(utils.CopyBytesToJS(messageID.Marshal()))
+	}
+
+	mutedUser := func(channelID *id.ID, pubKey ed25519.PublicKey, unmute bool) {
+		mutedUserCB.Invoke(utils.CopyBytesToJS(channelID.Marshal()),
+			utils.CopyBytesToJS(pubKey), unmute)
+	}
+
+	model := channelsDb.NewWASMEventModelBuilder(
+		wasmJsPath, cipher, messageReceived, deletedMessage, mutedUser)
 
 	promiseFn := func(resolve, reject func(args ...any) js.Value) {
 		cm, err := bindings.NewChannelsManagerGoEventModel(
@@ -426,14 +462,23 @@ func newChannelsManagerWithIndexedDb(cmixID int, wasmJsPath string,
 //   - args[1] - Path to Javascript file that starts the worker (string).
 //   - args[2] - The storage tag associated with the previously created channel
 //     manager and retrieved with [ChannelsManager.GetStorageTag] (string).
-//   - args[3] - 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.
-//   - args[4] - ID of [ChannelDbCipher] object in tracker (int). Create this
+//   - args[3] - The received message callback, which is called everytime a
+//     message is added or changed in the database. It is a 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.
+//   - args[4] - The deleted message callback, which is called everytime a
+//     message is deleted from the database. It is a function that takes in the
+//     same parameters as [indexedDb.DeletedMessageCallback]. On the Javascript
+//     side, the message ID is returned as a Uint8Array.
+//   - args[5] - The muted user callback, which is called everytime a user is
+//     muted or unmuted. It is a function that takes in the same parameters as
+//     [indexedDb.MutedUserCallback]. On the Javascript side, the channel ID and
+//     user public key are returned as Uint8Array.
+//   - args[6] - ID of [ChannelDbCipher] object in tracker (int). Create this
 //     object with [NewChannelsDatabaseCipher] and get its id with
 //     [ChannelDbCipher.GetID].
 //
@@ -446,15 +491,17 @@ func LoadChannelsManagerWithIndexedDb(_ js.Value, args []js.Value) any {
 	wasmJsPath := args[1].String()
 	storageTag := args[2].String()
 	messageReceivedCB := args[3]
-	cipherID := args[4].Int()
+	deletedMessageCB := args[4]
+	mutedUserCB := args[5]
+	cipherID := args[6].Int()
 
 	cipher, err := bindings.GetChannelDbCipherTrackerFromID(cipherID)
 	if err != nil {
 		utils.Throw(utils.TypeError, err)
 	}
 
-	return loadChannelsManagerWithIndexedDb(
-		cmixID, wasmJsPath, storageTag, messageReceivedCB, cipher)
+	return loadChannelsManagerWithIndexedDb(cmixID, wasmJsPath, storageTag,
+		messageReceivedCB, deletedMessageCB, mutedUserCB, cipher)
 }
 
 // LoadChannelsManagerWithIndexedDbUnsafe loads an existing [ChannelsManager]
@@ -473,13 +520,22 @@ func LoadChannelsManagerWithIndexedDb(_ js.Value, args []js.Value) any {
 //   - args[1] - Path to Javascript file that starts the worker (string).
 //   - args[2] - The storage tag associated with the previously created channel
 //     manager and retrieved with [ChannelsManager.GetStorageTag] (string).
-//   - args[3] - 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
+//   - args[3] - The received message callback, which is called everytime a
+//     message is added or changed in the database. It is a 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.
+//   - args[4] - The deleted message callback, which is called everytime a
+//     message is deleted from the database. It is a function that takes in the
+//     same parameters as [indexedDb.DeletedMessageCallback]. On the Javascript
+//     side, the message ID is returned as a Uint8Array.
+//   - args[5] - The muted user callback, which is called everytime a user is
+//     muted or unmuted. It is a function that takes in the same parameters as
+//     [indexedDb.MutedUserCallback]. On the Javascript side, the channel ID and
+//     user public key are returned as Uint8Array.
 //
 // Returns a promise:
 //   - Resolves to a Javascript representation of the [ChannelsManager] object.
@@ -489,19 +545,32 @@ func LoadChannelsManagerWithIndexedDbUnsafe(_ js.Value, args []js.Value) any {
 	wasmJsPath := args[1].String()
 	storageTag := args[2].String()
 	messageReceivedCB := args[3]
+	deletedMessageCB := args[3]
+	mutedUserCB := args[4]
 
-	return loadChannelsManagerWithIndexedDb(
-		cmixID, wasmJsPath, storageTag, messageReceivedCB, nil)
+	return loadChannelsManagerWithIndexedDb(cmixID, wasmJsPath, storageTag,
+		messageReceivedCB, deletedMessageCB, mutedUserCB, nil)
 }
 
 func loadChannelsManagerWithIndexedDb(cmixID int, wasmJsPath, storageTag string,
-	cb js.Value, cipher *bindings.ChannelDbCipher) any {
-	messageReceivedCB := func(uuid uint64, channelID *id.ID, updated bool) {
-		cb.Invoke(uuid, utils.CopyBytesToJS(channelID.Marshal()), updated)
+	messageReceivedCB, deletedMessageCB, mutedUserCB js.Value,
+	cipher *bindings.ChannelDbCipher) any {
+
+	messageReceived := func(uuid uint64, channelID *id.ID, update bool) {
+		messageReceivedCB.Invoke(uuid, utils.CopyBytesToJS(channelID.Marshal()), update)
+	}
+
+	deletedMessage := func(messageID message.ID) {
+		deletedMessageCB.Invoke(utils.CopyBytesToJS(messageID.Marshal()))
+	}
+
+	mutedUser := func(channelID *id.ID, pubKey ed25519.PublicKey, unmute bool) {
+		mutedUserCB.Invoke(utils.CopyBytesToJS(channelID.Marshal()),
+			utils.CopyBytesToJS(pubKey), unmute)
 	}
 
 	model := channelsDb.NewWASMEventModelBuilder(
-		wasmJsPath, cipher, messageReceivedCB)
+		wasmJsPath, cipher, messageReceived, deletedMessage, mutedUser)
 
 	promiseFn := func(resolve, reject func(args ...any) js.Value) {
 		cm, err := bindings.LoadChannelsManagerGoEventModel(
@@ -879,14 +948,14 @@ func ValidForever(js.Value, []js.Value) any {
 func (cm *ChannelsManager) SendGeneric(_ js.Value, args []js.Value) any {
 	marshalledChanId := utils.CopyBytesToGo(args[0])
 	messageType := args[1].Int()
-	message := utils.CopyBytesToGo(args[2])
+	msg := utils.CopyBytesToGo(args[2])
 	leaseTimeMS := int64(args[3].Int())
 	tracked := args[4].Bool()
 	cmixParamsJSON := utils.CopyBytesToGo(args[5])
 
 	promiseFn := func(resolve, reject func(args ...any) js.Value) {
 		sendReport, err := cm.api.SendGeneric(marshalledChanId, messageType,
-			message, leaseTimeMS, tracked, cmixParamsJSON)
+			msg, leaseTimeMS, tracked, cmixParamsJSON)
 		if err != nil {
 			reject(utils.JsTrace(err))
 		} else {
@@ -922,13 +991,13 @@ func (cm *ChannelsManager) SendGeneric(_ js.Value, args []js.Value) any {
 //   - Rejected with an error if sending fails.
 func (cm *ChannelsManager) SendMessage(_ js.Value, args []js.Value) any {
 	marshalledChanId := utils.CopyBytesToGo(args[0])
-	message := args[1].String()
+	msg := args[1].String()
 	leaseTimeMS := int64(args[2].Int())
 	cmixParamsJSON := utils.CopyBytesToGo(args[3])
 
 	promiseFn := func(resolve, reject func(args ...any) js.Value) {
 		sendReport, err := cm.api.SendMessage(
-			marshalledChanId, message, leaseTimeMS, cmixParamsJSON)
+			marshalledChanId, msg, leaseTimeMS, cmixParamsJSON)
 		if err != nil {
 			reject(utils.JsTrace(err))
 		} else {
@@ -971,13 +1040,13 @@ func (cm *ChannelsManager) SendMessage(_ js.Value, args []js.Value) any {
 //   - Rejected with an error if sending fails.
 func (cm *ChannelsManager) SendReply(_ js.Value, args []js.Value) any {
 	marshalledChanId := utils.CopyBytesToGo(args[0])
-	message := args[1].String()
+	msg := args[1].String()
 	messageToReactTo := utils.CopyBytesToGo(args[2])
 	leaseTimeMS := int64(args[3].Int())
 	cmixParamsJSON := utils.CopyBytesToGo(args[4])
 
 	promiseFn := func(resolve, reject func(args ...any) js.Value) {
-		sendReport, err := cm.api.SendReply(marshalledChanId, message,
+		sendReport, err := cm.api.SendReply(marshalledChanId, msg,
 			messageToReactTo, leaseTimeMS, cmixParamsJSON)
 		if err != nil {
 			reject(utils.JsTrace(err))
@@ -1068,14 +1137,14 @@ func (cm *ChannelsManager) SendReaction(_ js.Value, args []js.Value) any {
 func (cm *ChannelsManager) SendAdminGeneric(_ js.Value, args []js.Value) any {
 	marshalledChanId := utils.CopyBytesToGo(args[0])
 	messageType := args[1].Int()
-	message := utils.CopyBytesToGo(args[2])
+	msg := utils.CopyBytesToGo(args[2])
 	leaseTimeMS := int64(args[3].Int())
 	tracked := args[4].Bool()
 	cmixParamsJSON := utils.CopyBytesToGo(args[5])
 
 	promiseFn := func(resolve, reject func(args ...any) js.Value) {
 		sendReport, err := cm.api.SendAdminGeneric(marshalledChanId,
-			messageType, message, leaseTimeMS, tracked, cmixParamsJSON)
+			messageType, msg, leaseTimeMS, tracked, cmixParamsJSON)
 		if err != nil {
 			reject(utils.JsTrace(err))
 		} else {
@@ -1624,6 +1693,7 @@ func (emb *eventModelBuilder) Build(path string) bindings.EventModel {
 		updateFromMessageID: utils.WrapCB(emJs, "UpdateFromMessageID"),
 		getMessage:          utils.WrapCB(emJs, "GetMessage"),
 		deleteMessage:       utils.WrapCB(emJs, "DeleteMessage"),
+		muteUser:            utils.WrapCB(emJs, "MuteUser"),
 	}
 }
 
@@ -1639,6 +1709,7 @@ type eventModel struct {
 	updateFromMessageID func(args ...any) js.Value
 	getMessage          func(args ...any) js.Value
 	deleteMessage       func(args ...any) js.Value
+	muteUser            func(args ...any) js.Value
 }
 
 // JoinChannel is called whenever a channel is joined locally.
@@ -1858,6 +1929,17 @@ func (em *eventModel) DeleteMessage(messageID []byte) error {
 	return nil
 }
 
+// MuteUser mutes the given user or unmutes them.
+//
+// Parameters:
+//   - channelID - The bytes of the [id.ID] of the channel the user is being
+//     muted in.
+//   - pubKey - The [ed25519.PublicKey] of the user that is muted or unmuted.
+func (em *eventModel) MuteUser(channelID, pubkey []byte, unmute bool) {
+	em.muteUser(
+		utils.CopyBytesToJS(channelID), utils.CopyBytesToJS(pubkey), unmute)
+}
+
 // MessageAndError contains a message returned by eventModel.GetMessage or any
 // possible error that occurs during lookup. Only one field should be present at
 // a time; if an error occurs, ModelMessage should be empty.
diff --git a/wasm/version.go b/wasm/version.go
index 528641b61a5161d308e04a7b6991bde21f801e7c..ee1a921cc666ef3a5e2491b5f43371ee098e2f3f 100644
--- a/wasm/version.go
+++ b/wasm/version.go
@@ -10,9 +10,12 @@
 package wasm
 
 import (
+	"encoding/json"
+	"syscall/js"
+
 	"gitlab.com/elixxir/client/v4/bindings"
 	"gitlab.com/elixxir/xxdk-wasm/storage"
-	"syscall/js"
+	"gitlab.com/elixxir/xxdk-wasm/utils"
 )
 
 // GetVersion returns the current xxDK WASM semantic version.
@@ -49,3 +52,60 @@ func GetClientGitVersion(js.Value, []js.Value) any {
 func GetClientDependencies(js.Value, []js.Value) any {
 	return bindings.GetDependencies()
 }
+
+// VersionInfo contains information about the current and old version of the
+// API.
+type VersionInfo struct {
+	Current string `json:"current"`
+	Updated bool   `json:"updated"`
+	Old     string `json:"old"`
+}
+
+// GetWasmSemanticVersion returns the current version of the WASM client, it's
+// old version before being updated, and if it has been updated.
+//
+// Returns:
+//   - JSON of [VersionInfo] (Uint8Array).
+//   - Throws a TypeError if getting the version failed.
+func GetWasmSemanticVersion(js.Value, []js.Value) any {
+	vi := VersionInfo{
+		Current: storage.SEMVER,
+		Updated: false,
+		Old:     storage.GetOldWasmSemVersion(),
+	}
+
+	if vi.Current != vi.Old {
+		vi.Updated = true
+	}
+
+	data, err := json.Marshal(vi)
+	if err != nil {
+		utils.Throw(utils.TypeError, err)
+	}
+
+	return utils.CopyBytesToJS(data)
+}
+
+// GetXXDKSemanticVersion returns the current version of the xxdk client, it's
+// old version before being updated, and if it has been updated.
+//
+// Returns:
+//   - JSON of [VersionInfo] (Uint8Array).
+//   - Throws a TypeError if getting the version failed.
+func GetXXDKSemanticVersion(js.Value, []js.Value) any {
+	vi := VersionInfo{
+		Current: bindings.GetVersion(),
+		Updated: false,
+		Old:     storage.GetOldClientSemVersion(),
+	}
+	if vi.Current != vi.Old {
+		vi.Updated = true
+	}
+
+	data, err := json.Marshal(vi)
+	if err != nil {
+		utils.Throw(utils.TypeError, err)
+	}
+
+	return utils.CopyBytesToJS(data)
+}