diff --git a/go.mod b/go.mod
index 1f46ca38697f5d8e67582669b50625e9f32826d1..be90c570fff81bcb1ebeb21d9efa3d0f787d95c7 100644
--- a/go.mod
+++ b/go.mod
@@ -7,7 +7,7 @@ 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.6.2-0.20230511215110-b43e18a47875
+	gitlab.com/elixxir/client/v4 v4.6.2-0.20230512234533-17b97e5a36cc
 	gitlab.com/elixxir/crypto v0.0.7-0.20230413162806-a99ec4bfea32
 	gitlab.com/elixxir/primitives v0.0.3-0.20230214180039-9a25e2d3969c
 	gitlab.com/xx_network/crypto v0.0.5-0.20230214003943-8a09396e95dd
diff --git a/go.sum b/go.sum
index e071dbf39c79e147644414620be8475fd8af96d7..c30479ce508ca63955094f661f6a6548bc14130a 100644
--- a/go.sum
+++ b/go.sum
@@ -515,6 +515,8 @@ gitlab.com/elixxir/client/v4 v4.6.2-0.20230425190953-cd51598e9245 h1:pBwoSYD+BFI
 gitlab.com/elixxir/client/v4 v4.6.2-0.20230425190953-cd51598e9245/go.mod h1:G+lN+LvQPGcm5BQnrhnqT1xiRIAzH3OffAM+5oI9SUg=
 gitlab.com/elixxir/client/v4 v4.6.2-0.20230511215110-b43e18a47875 h1:u9DlU8xAk0rTvguhWK+6D/MfLHdR+jlDTfGJjiszJDE=
 gitlab.com/elixxir/client/v4 v4.6.2-0.20230511215110-b43e18a47875/go.mod h1:dLKU2zSWrZLk/fomAtt1DFGgpTHQAfPdxdXNp3EtRZU=
+gitlab.com/elixxir/client/v4 v4.6.2-0.20230512234533-17b97e5a36cc h1:f5lwmwejXTerTUjro9d5Ws1mKGD6ChXHMaBMhB3OhgA=
+gitlab.com/elixxir/client/v4 v4.6.2-0.20230512234533-17b97e5a36cc/go.mod h1:dLKU2zSWrZLk/fomAtt1DFGgpTHQAfPdxdXNp3EtRZU=
 gitlab.com/elixxir/client/v4 v4.6.3 h1:oUsm5cn2Vnfqz+xwGYKrqFkPNN3sDAyp00EPGhUIA5E=
 gitlab.com/elixxir/client/v4 v4.6.3/go.mod h1:G+lN+LvQPGcm5BQnrhnqT1xiRIAzH3OffAM+5oI9SUg=
 gitlab.com/elixxir/comms v0.0.4-0.20230310205528-f06faa0d2f0b h1:8AVK93UEs/aufoqtFgyMVt9gf0oJ8F4pA60ZvEVvG+s=
diff --git a/indexedDb/impl/channels/callbacks.go b/indexedDb/impl/channels/callbacks.go
index 205a5e0df78b3cb6125042a963e5de8026fd4e08..6fc2235b5515e13e92d4bbf66135bc3647a2b740 100644
--- a/indexedDb/impl/channels/callbacks.go
+++ b/indexedDb/impl/channels/callbacks.go
@@ -10,8 +10,9 @@
 package main
 
 import (
-	"crypto/ed25519"
 	"encoding/json"
+	"time"
+
 	"github.com/pkg/errors"
 	jww "github.com/spf13/jwalterweatherman"
 	"gitlab.com/elixxir/client/v4/channels"
@@ -24,7 +25,6 @@ import (
 	"gitlab.com/elixxir/xxdk-wasm/worker"
 	"gitlab.com/xx_network/crypto/csprng"
 	"gitlab.com/xx_network/primitives/id"
-	"time"
 )
 
 var zeroUUID = []byte{0, 0, 0, 0, 0, 0, 0, 0}
@@ -71,8 +71,7 @@ func (m *manager) newWASMEventModelCB(data []byte) ([]byte, error) {
 			"failed to JSON unmarshal Cipher from main thread: %+v", err)
 	}
 
-	m.model, err = NewWASMEventModel(msg.DatabaseName, encryption,
-		m.messageReceivedCallback, m.deletedMessageCallback, m.mutedUserCallback)
+	m.model, err = NewWASMEventModel(msg.DatabaseName, encryption, m)
 	if err != nil {
 		return []byte(err.Error()), nil
 	}
@@ -80,12 +79,8 @@ func (m *manager) newWASMEventModelCB(data []byte) ([]byte, error) {
 	return []byte{}, nil
 }
 
-// messageReceivedCallback sends calls to the channels.MessageReceivedCallback
-// in the main thread.
-//
-// storeEncryptionStatus adhere to the channels.MessageReceivedCallback type.
-func (m *manager) messageReceivedCallback(
-	uuid uint64, channelID *id.ID, update bool) {
+// MessageReceived implements [bindings.ChannelUICallbacks.MessageReceived].
+func (m *manager) MessageReceived(uuid int64, channelID []byte, update bool) {
 	// Package parameters for sending
 	msg := &wChannels.MessageReceivedCallbackMessage{
 		UUID:      uuid,
@@ -102,20 +97,13 @@ func (m *manager) messageReceivedCallback(
 	m.wtm.SendMessage(wChannels.MessageReceivedCallbackTag, data)
 }
 
-// deletedMessageCallback sends calls to the channels.DeletedMessageCallback in
-// the main thread.
-//
-// storeEncryptionStatus adhere to the channels.MessageReceivedCallback type.
-func (m *manager) deletedMessageCallback(messageID message.ID) {
-	m.wtm.SendMessage(wChannels.DeletedMessageCallbackTag, messageID.Marshal())
+// MessageDeleted implements [bindings.ChannelUICallbacks.MessageDeleted].
+func (m *manager) MessageDeleted(messageID []byte) {
+	m.wtm.SendMessage(wChannels.DeletedMessageCallbackTag, messageID)
 }
 
-// mutedUserCallback sends calls to the channels.MutedUserCallback in the main
-// thread.
-//
-// storeEncryptionStatus adhere to the channels.MessageReceivedCallback type.
-func (m *manager) mutedUserCallback(
-	channelID *id.ID, pubKey ed25519.PublicKey, unmute bool) {
+// UserMuted implements [bindings.ChannelUICallbacks.UserMuted].
+func (m *manager) UserMuted(channelID, pubKey []byte, unmute bool) {
 	// Package parameters for sending
 	msg := &wChannels.MuteUserMessage{
 		ChannelID: channelID,
@@ -132,6 +120,12 @@ func (m *manager) mutedUserCallback(
 	m.wtm.SendMessage(wChannels.MutedUserCallbackTag, data)
 }
 
+// NicknameUpdate implements [bindings.ChannelUICallbacks.NicknameUpdate]
+func (m *manager) NicknameUpdate(channelIdBytes []byte, nickname string,
+	exists bool) {
+	jww.FATAL.Panicf("unimplemented")
+}
+
 // joinChannelCB is the callback for wasmModel.JoinChannel. Always returns nil;
 // meaning, no response is supplied (or expected).
 func (m *manager) joinChannelCB(data []byte) ([]byte, error) {
@@ -368,7 +362,12 @@ func (m *manager) muteUserCB(data []byte) ([]byte, error) {
 			"failed to JSON unmarshal %T from main thread: %+v", msg, err)
 	}
 
-	m.model.MuteUser(msg.ChannelID, msg.PubKey, msg.Unmute)
+	channelID := id.ID{}
+	err = channelID.UnmarshalJSON(msg.ChannelID)
+	if err != nil {
+		return nil, err
+	}
+	m.model.MuteUser(&channelID, msg.PubKey, msg.Unmute)
 
 	return nil, nil
 }
diff --git a/indexedDb/impl/channels/implementation.go b/indexedDb/impl/channels/implementation.go
index 0976d263b00011b7ec4a6a5d6bfce38c2780beb0..19dba81e1671cc2dbecbe4761df476473f83e0ad 100644
--- a/indexedDb/impl/channels/implementation.go
+++ b/indexedDb/impl/channels/implementation.go
@@ -21,13 +21,13 @@ import (
 	"github.com/pkg/errors"
 	jww "github.com/spf13/jwalterweatherman"
 
+	"gitlab.com/elixxir/client/v4/bindings"
 	"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"
-	wChannels "gitlab.com/elixxir/xxdk-wasm/indexedDb/worker/channels"
 	"gitlab.com/elixxir/xxdk-wasm/utils"
 	"gitlab.com/xx_network/primitives/id"
 )
@@ -38,11 +38,9 @@ import (
 // NOTE: This model is NOT thread safe - it is the responsibility of the
 // caller to ensure that its methods are called sequentially.
 type wasmModel struct {
-	db                *idb.Database
-	cipher            cryptoChannel.Cipher
-	receivedMessageCB wChannels.MessageReceivedCallback
-	deletedMessageCB  wChannels.DeletedMessageCallback
-	mutedUserCB       wChannels.MutedUserCallback
+	db     *idb.Database
+	cipher cryptoChannel.Cipher
+	cbs    bindings.ChannelUICallbacks
 }
 
 // JoinChannel is called whenever a channel is joined locally.
@@ -161,8 +159,10 @@ func (w *wasmModel) ReceiveMessage(channelID *id.ID, messageID message.ID,
 		}
 	}
 
+	channelIDBytes := channelID.Marshal()
+
 	msgToInsert := buildMessage(
-		channelID.Marshal(), messageID.Bytes(), nil, nickname,
+		channelIDBytes, messageID.Bytes(), nil, nickname,
 		textBytes, pubKey, dmToken, codeset, timestamp, lease, round.ID, mType,
 		false, hidden, status)
 
@@ -172,7 +172,7 @@ func (w *wasmModel) ReceiveMessage(channelID *id.ID, messageID message.ID,
 		return 0
 	}
 
-	go w.receivedMessageCB(uuid, channelID, false)
+	go w.cbs.MessageReceived(int64(uuid), channelIDBytes, false)
 	return uuid
 }
 
@@ -199,7 +199,9 @@ func (w *wasmModel) ReceiveReply(channelID *id.ID, messageID,
 		}
 	}
 
-	msgToInsert := buildMessage(channelID.Marshal(), messageID.Bytes(),
+	channelIDBytes := channelID.Marshal()
+
+	msgToInsert := buildMessage(channelIDBytes, messageID.Bytes(),
 		replyTo.Bytes(), nickname, textBytes, pubKey, dmToken, codeset,
 		timestamp, lease, round.ID, mType, hidden, false, status)
 
@@ -208,7 +210,7 @@ func (w *wasmModel) ReceiveReply(channelID *id.ID, messageID,
 		jww.ERROR.Printf("Failed to receive reply: %+v", err)
 		return 0
 	}
-	go w.receivedMessageCB(uuid, channelID, false)
+	go w.cbs.MessageReceived(int64(uuid), channelIDBytes, false)
 	return uuid
 }
 
@@ -235,8 +237,9 @@ func (w *wasmModel) ReceiveReaction(channelID *id.ID, messageID,
 		}
 	}
 
+	channelIDBytes := channelID.Marshal()
 	msgToInsert := buildMessage(
-		channelID.Marshal(), messageID.Bytes(), reactionTo.Bytes(), nickname,
+		channelIDBytes, messageID.Bytes(), reactionTo.Bytes(), nickname,
 		textBytes, pubKey, dmToken, codeset, timestamp, lease, round.ID, mType,
 		false, hidden, status)
 
@@ -245,7 +248,7 @@ func (w *wasmModel) ReceiveReaction(channelID *id.ID, messageID,
 		jww.ERROR.Printf("Failed to receive reaction: %+v", err)
 		return 0
 	}
-	go w.receivedMessageCB(uuid, channelID, false)
+	go w.cbs.MessageReceived(int64(uuid), channelIDBytes, false)
 	return uuid
 }
 
@@ -392,9 +395,7 @@ func (w *wasmModel) updateMessage(currentMsg *Message, messageID *message.ID,
 	if err != nil {
 		return 0, err
 	}
-	channelID := &id.ID{}
-	copy(channelID[:], currentMsg.ChannelID)
-	go w.receivedMessageCB(uuid, channelID, true)
+	go w.cbs.MessageReceived(int64(uuid), currentMsg.ChannelID, true)
 
 	return uuid, nil
 }
@@ -492,7 +493,7 @@ func (w *wasmModel) DeleteMessage(messageID message.ID) error {
 		return err
 	}
 
-	go w.deletedMessageCB(messageID)
+	go w.cbs.MessageDeleted(messageID.Bytes())
 
 	return nil
 }
@@ -500,7 +501,7 @@ func (w *wasmModel) DeleteMessage(messageID message.ID) error {
 // 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)
+	go w.cbs.UserMuted(channelID.Marshal(), pubKey, unmute)
 }
 
 // valueToMessage is a helper for converting js.Value to Message.
diff --git a/indexedDb/impl/channels/implementation_test.go b/indexedDb/impl/channels/implementation_test.go
index 4b22b8300f5549a5dc2f3902f7ed95277777555f..d189d82413b9e6954dba28be9a1361b75e30d65e 100644
--- a/indexedDb/impl/channels/implementation_test.go
+++ b/indexedDb/impl/channels/implementation_test.go
@@ -11,17 +11,17 @@ package main
 
 import (
 	"bytes"
-	"crypto/ed25519"
 	"encoding/json"
 	"errors"
 	"fmt"
-	cft "gitlab.com/elixxir/client/v4/channelsFileTransfer"
-	"gitlab.com/elixxir/crypto/fileTransfer"
 	"os"
 	"strconv"
 	"testing"
 	"time"
 
+	cft "gitlab.com/elixxir/client/v4/channelsFileTransfer"
+	"gitlab.com/elixxir/crypto/fileTransfer"
+
 	"github.com/hack-pad/go-indexeddb/idb"
 	jww "github.com/spf13/jwalterweatherman"
 
@@ -42,15 +42,19 @@ func TestMain(m *testing.M) {
 	os.Exit(m.Run())
 }
 
-func dummyReceivedMessageCB(uint64, *id.ID, bool)      {}
-func dummyDeletedMessageCB(message.ID)                 {}
-func dummyMutedUserCB(*id.ID, ed25519.PublicKey, bool) {}
+type dummyCbs struct{}
+
+func (c *dummyCbs) MessageReceived(uuid int64, channelID []byte, update bool) {}
+func (c *dummyCbs) UserMuted(channelID []byte, pubKey []byte, unmute bool)    {}
+func (c *dummyCbs) MessageDeleted(messageId []byte)                           {}
+func (c *dummyCbs) NicknameUpdate(channelIdBytes []byte, nickname string,
+	exists bool) {
+}
 
 // Happy path test for receiving, updating, getting, and deleting a File.
 func TestWasmModel_ReceiveFile(t *testing.T) {
 	testString := "TestWasmModel_ReceiveFile"
-	m, err := newWASMModel(testString, nil,
-		dummyReceivedMessageCB, dummyDeletedMessageCB, dummyMutedUserCB)
+	m, err := newWASMModel(testString, nil, &dummyCbs{})
 	if err != nil {
 		t.Fatal(err)
 	}
@@ -137,7 +141,7 @@ func TestWasmModel_GetMessage(t *testing.T) {
 			testMsgId := message.DeriveChannelMessageID(&id.ID{1}, 0, []byte(testString))
 
 			eventModel, err := newWASMModel(testString, c,
-				dummyReceivedMessageCB, dummyDeletedMessageCB, dummyMutedUserCB)
+				&dummyCbs{})
 			if err != nil {
 				t.Fatal(err)
 			}
@@ -167,8 +171,7 @@ 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, dummyReceivedMessageCB,
-		dummyDeletedMessageCB, dummyMutedUserCB)
+	eventModel, err := newWASMModel(testString, nil, &dummyCbs{})
 	if err != nil {
 		t.Fatal(err)
 	}
@@ -225,7 +228,7 @@ func Test_wasmModel_UpdateSentStatus(t *testing.T) {
 			testMsgId := message.DeriveChannelMessageID(
 				&id.ID{1}, 0, []byte(testString))
 			eventModel, err2 := newWASMModel(testString, c,
-				dummyReceivedMessageCB, dummyDeletedMessageCB, dummyMutedUserCB)
+				&dummyCbs{})
 			if err2 != nil {
 				t.Fatal(err)
 			}
@@ -292,8 +295,7 @@ 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, dummyReceivedMessageCB,
-				dummyDeletedMessageCB, dummyMutedUserCB)
+			eventModel, err2 := newWASMModel("test", c, &dummyCbs{})
 			if err2 != nil {
 				t.Fatal(err2)
 			}
@@ -347,7 +349,7 @@ func Test_wasmModel_UUIDTest(t *testing.T) {
 			storage.GetLocalStorage().Clear()
 			testString := "testHello" + cs
 			eventModel, err2 := newWASMModel(testString, c,
-				dummyReceivedMessageCB, dummyDeletedMessageCB, dummyMutedUserCB)
+				&dummyCbs{})
 			if err2 != nil {
 				t.Fatal(err2)
 			}
@@ -394,7 +396,7 @@ func Test_wasmModel_DuplicateReceives(t *testing.T) {
 		t.Run(testString, func(t *testing.T) {
 			storage.GetLocalStorage().Clear()
 			eventModel, err := newWASMModel(testString, c,
-				dummyReceivedMessageCB, dummyDeletedMessageCB, dummyMutedUserCB)
+				&dummyCbs{})
 			if err != nil {
 				t.Fatal(err)
 			}
@@ -443,7 +445,7 @@ func Test_wasmModel_deleteMsgByChannel(t *testing.T) {
 			totalMessages := 10
 			expectedMessages := 5
 			eventModel, err := newWASMModel(testString, c,
-				dummyReceivedMessageCB, dummyDeletedMessageCB, dummyMutedUserCB)
+				&dummyCbs{})
 			if err != nil {
 				t.Fatal(err)
 			}
@@ -514,7 +516,7 @@ func TestWasmModel_receiveHelper_UniqueIndex(t *testing.T) {
 			storage.GetLocalStorage().Clear()
 			testString := fmt.Sprintf("test_receiveHelper_UniqueIndex_%d", i)
 			eventModel, err := newWASMModel(testString, c,
-				dummyReceivedMessageCB, dummyDeletedMessageCB, dummyMutedUserCB)
+				&dummyCbs{})
 			if err != nil {
 				t.Fatal(err)
 			}
diff --git a/indexedDb/impl/channels/init.go b/indexedDb/impl/channels/init.go
index ecc16da447b0db684ca55031a26e6a3d19206320..282b4582fe49a98d9d62ec9912adb43ea6d549e5 100644
--- a/indexedDb/impl/channels/init.go
+++ b/indexedDb/impl/channels/init.go
@@ -15,10 +15,10 @@ import (
 	"github.com/hack-pad/go-indexeddb/idb"
 	jww "github.com/spf13/jwalterweatherman"
 
+	"gitlab.com/elixxir/client/v4/bindings"
 	"gitlab.com/elixxir/client/v4/channels"
 	cryptoChannel "gitlab.com/elixxir/crypto/channel"
 	"gitlab.com/elixxir/xxdk-wasm/indexedDb/impl"
-	wChannels "gitlab.com/elixxir/xxdk-wasm/indexedDb/worker/channels"
 )
 
 // currentVersion is the current version of the IndexedDb runtime. Used for
@@ -29,18 +29,13 @@ const currentVersion uint = 2
 // The name should be a base64 encoding of the users public key. Returns the
 // EventModel based on IndexedDb and the database name as reported by IndexedDb.
 func NewWASMEventModel(databaseName string, encryption cryptoChannel.Cipher,
-	messageReceivedCB wChannels.MessageReceivedCallback,
-	deletedMessageCB wChannels.DeletedMessageCallback,
-	mutedUserCB wChannels.MutedUserCallback) (channels.EventModel, error) {
-	return newWASMModel(databaseName, encryption, messageReceivedCB,
-		deletedMessageCB, mutedUserCB)
+	channelsCbs bindings.ChannelUICallbacks) (channels.EventModel, error) {
+	return newWASMModel(databaseName, encryption, channelsCbs)
 }
 
 // newWASMModel creates the given [idb.Database] and returns a wasmModel.
 func newWASMModel(databaseName string, encryption cryptoChannel.Cipher,
-	messageReceivedCB wChannels.MessageReceivedCallback,
-	deletedMessageCB wChannels.DeletedMessageCallback,
-	mutedUserCB wChannels.MutedUserCallback) (*wasmModel, error) {
+	channelsCbs bindings.ChannelUICallbacks) (*wasmModel, error) {
 	// Attempt to open database object
 	ctx, cancel := impl.NewContext()
 	defer cancel()
@@ -86,11 +81,9 @@ func newWASMModel(databaseName string, encryption cryptoChannel.Cipher,
 	}
 
 	wrapper := &wasmModel{
-		db:                db,
-		cipher:            encryption,
-		receivedMessageCB: messageReceivedCB,
-		deletedMessageCB:  deletedMessageCB,
-		mutedUserCB:       mutedUserCB,
+		db:     db,
+		cipher: encryption,
+		cbs:    channelsCbs,
 	}
 	return wrapper, nil
 }
diff --git a/indexedDb/worker/channels/implementation.go b/indexedDb/worker/channels/implementation.go
index 9639bc185095e74cbf4b3e63256fead39bbcef82..2e11702432dd5d4b1dae668715ccb6a7bdc50950 100644
--- a/indexedDb/worker/channels/implementation.go
+++ b/indexedDb/worker/channels/implementation.go
@@ -452,16 +452,16 @@ func (w *wasmModel) DeleteMessage(messageID message.ID) error {
 // MuteUserMessage is JSON marshalled and sent to the worker for
 // [wasmModel.MuteUser].
 type MuteUserMessage struct {
-	ChannelID *id.ID            `json:"channelID"`
-	PubKey    ed25519.PublicKey `json:"pubKey"`
-	Unmute    bool              `json:"unmute"`
+	ChannelID []byte `json:"channelID"`
+	PubKey    []byte `json:"pubKey"`
+	Unmute    bool   `json:"unmute"`
 }
 
 // MuteUser is called whenever a user is muted or unmuted.
 func (w *wasmModel) MuteUser(
 	channelID *id.ID, pubKey ed25519.PublicKey, unmute bool) {
 	msg := MuteUserMessage{
-		ChannelID: channelID,
+		ChannelID: channelID.Marshal(),
 		PubKey:    pubKey,
 		Unmute:    unmute,
 	}
diff --git a/indexedDb/worker/channels/init.go b/indexedDb/worker/channels/init.go
index 2ee630caf43177460a19f14d2b953d3a03b9c687..34dd3f0d2548042a3778267e9b5e46172c384dd6 100644
--- a/indexedDb/worker/channels/init.go
+++ b/indexedDb/worker/channels/init.go
@@ -10,19 +10,19 @@
 package channels
 
 import (
-	"crypto/ed25519"
 	"encoding/json"
-	"github.com/pkg/errors"
 	"time"
 
+	"github.com/pkg/errors"
+
 	jww "github.com/spf13/jwalterweatherman"
+	"gitlab.com/elixxir/client/v4/bindings"
 	"gitlab.com/elixxir/client/v4/channels"
 
 	cryptoChannel "gitlab.com/elixxir/crypto/channel"
 	"gitlab.com/elixxir/crypto/message"
 	"gitlab.com/elixxir/xxdk-wasm/storage"
 	"gitlab.com/elixxir/xxdk-wasm/worker"
-	"gitlab.com/xx_network/primitives/id"
 )
 
 // databaseSuffix is the suffix to be appended to the name of the database.
@@ -31,26 +31,24 @@ const databaseSuffix = "_speakeasy"
 // MessageReceivedCallback is called any time a message is received or updated.
 //
 // update is true if the row is old and was edited.
-type MessageReceivedCallback func(uuid uint64, channelID *id.ID, update bool)
+type MessageReceivedCallback func(uuid int64, channelID []byte, update bool)
 
 // DeletedMessageCallback is called any time a message is deleted.
-type DeletedMessageCallback func(messageID message.ID)
+type DeletedMessageCallback func(messageID []byte)
 
 // 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)
+type MutedUserCallback func(channelID, pubKey []byte, unmute bool)
 
 // NewWASMEventModelBuilder returns an EventModelBuilder which allows
 // the channel manager to define the path but the callback is the same
 // across the board.
 func NewWASMEventModelBuilder(wasmJsPath string,
-	encryption cryptoChannel.Cipher, messageReceivedCB MessageReceivedCallback,
-	deletedMessageCB DeletedMessageCallback,
-	mutedUserCB MutedUserCallback) channels.EventModelBuilder {
+	encryption cryptoChannel.Cipher,
+	channelCbs bindings.ChannelUICallbacks) channels.EventModelBuilder {
 	fn := func(path string) (channels.EventModel, error) {
 		return NewWASMEventModel(path, wasmJsPath, encryption,
-			messageReceivedCB, deletedMessageCB, mutedUserCB)
+			channelCbs)
 	}
 	return fn
 }
@@ -65,8 +63,7 @@ type NewWASMEventModelMessage struct {
 // NewWASMEventModel returns a [channels.EventModel] backed by a wasmModel.
 // The name should be a base64 encoding of the users public key.
 func NewWASMEventModel(path, wasmJsPath string, encryption cryptoChannel.Cipher,
-	messageReceivedCB MessageReceivedCallback,
-	deletedMessageCB DeletedMessageCallback, mutedUserCB MutedUserCallback) (
+	channelCbs bindings.ChannelUICallbacks) (
 	channels.EventModel, error) {
 	databaseName := path + databaseSuffix
 
@@ -77,15 +74,15 @@ func NewWASMEventModel(path, wasmJsPath string, encryption cryptoChannel.Cipher,
 
 	// Register handler to manage messages for the MessageReceivedCallback
 	wm.RegisterCallback(MessageReceivedCallbackTag,
-		messageReceivedCallbackHandler(messageReceivedCB))
+		messageReceivedCallbackHandler(channelCbs.MessageReceived))
 
 	// Register handler to manage messages for the DeletedMessageCallback
 	wm.RegisterCallback(DeletedMessageCallbackTag,
-		deletedMessageCallbackHandler(deletedMessageCB))
+		deletedMessageCallbackHandler(channelCbs.MessageDeleted))
 
 	// Register handler to manage messages for the MutedUserCallback
 	wm.RegisterCallback(MutedUserCallbackTag,
-		mutedUserCallbackHandler(mutedUserCB))
+		mutedUserCallbackHandler(channelCbs.UserMuted))
 
 	// Store the database name
 	err = storage.StoreIndexedDb(databaseName)
@@ -135,8 +132,8 @@ func NewWASMEventModel(path, wasmJsPath string, encryption cryptoChannel.Cipher,
 // MessageReceivedCallbackMessage is JSON marshalled and received from the
 // worker for the [MessageReceivedCallback] callback.
 type MessageReceivedCallbackMessage struct {
-	UUID      uint64 `json:"uuid"`
-	ChannelID *id.ID `json:"channelID"`
+	UUID      int64  `json:"uuid"`
+	ChannelID []byte `json:"channelID"`
 	Update    bool   `json:"update"`
 }
 
@@ -166,7 +163,7 @@ func deletedMessageCallbackHandler(cb DeletedMessageCallback) func(data []byte)
 				"Failed to JSON unmarshal message ID from worker: %+v", err)
 		}
 
-		cb(messageID)
+		cb(messageID.Bytes())
 	}
 }
 
diff --git a/wasm/channels.go b/wasm/channels.go
index 307fe5cee304ff580ba1726b8036ce81268cc991..92231d692a1cd7ab4c685665fa6738333adbc731 100644
--- a/wasm/channels.go
+++ b/wasm/channels.go
@@ -10,17 +10,15 @@
 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"
 	"syscall/js"
 
+	"gitlab.com/elixxir/client/v4/channels"
+	channelsDb "gitlab.com/elixxir/xxdk-wasm/indexedDb/worker/channels"
+
 	"gitlab.com/elixxir/client/v4/bindings"
 	"gitlab.com/elixxir/xxdk-wasm/utils"
 )
@@ -274,7 +272,7 @@ func NewChannelsManager(_ js.Value, args []js.Value) any {
 	em := newEventModelBuilder(args[3])
 
 	cm, err := bindings.NewChannelsManager(
-		cmixId, privateIdentity, extensionBuilderIDsJSON, em)
+		cmixId, privateIdentity, extensionBuilderIDsJSON, em, nil)
 	if err != nil {
 		utils.Throw(utils.TypeError, err)
 		return nil
@@ -299,13 +297,17 @@ func NewChannelsManager(_ js.Value, args []js.Value) any {
 //   - args[2] - A function that initialises and returns a Javascript object
 //     that matches the [bindings.EventModel] interface. The function must match
 //     the Build function in [bindings.EventModelBuilder].
+//   - args[3] - A callback object which implements the
+//     [bindings.ChannelUICallbacks] javascript functions.
 //
 // Returns:
 //   - Javascript representation of the [ChannelsManager] object.
 //   - Throws a TypeError if loading the manager fails.
 func LoadChannelsManager(_ js.Value, args []js.Value) any {
 	em := newEventModelBuilder(args[2])
-	cm, err := bindings.LoadChannelsManager(args[0].Int(), args[1].String(), em)
+	cUI := newChannelUI(args[3])
+	cm, err := bindings.LoadChannelsManager(args[0].Int(), args[1].String(),
+		em, cUI)
 	if err != nil {
 		utils.Throw(utils.TypeError, err)
 		return nil
@@ -335,23 +337,9 @@ func LoadChannelsManager(_ js.Value, args []js.Value) any {
 //     IDs. The ID can be retrieved from an object with an extension builder
 //     (e.g., [ChannelsFileTransfer.GetExtensionBuilderID]). Leave empty if not
 //     using extension builders. Example: `[2,11,5]` (Uint8Array).
-//   - args[4] - 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 [channels.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[5] - 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[6] - 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[7] - ID of [ChannelDbCipher] object in tracker (int). Create this
+//   - args[4] - A callback object which implements the
+//     [bindings.ChannelUICallbacks] javascript functions.
+//   - args[5] - ID of [ChannelDbCipher] object in tracker (int). Create this
 //     object with [NewChannelsDatabaseCipher] and get its id with
 //     [ChannelDbCipher.GetID].
 //
@@ -364,10 +352,8 @@ func NewChannelsManagerWithIndexedDb(_ js.Value, args []js.Value) any {
 	wasmJsPath := args[1].String()
 	privateIdentity := utils.CopyBytesToGo(args[2])
 	extensionBuilderIDsJSON := utils.CopyBytesToGo(args[3])
-	messageReceivedCB := args[4]
-	deletedMessageCB := args[5]
-	mutedUserCB := args[6]
-	cipherID := args[7].Int()
+	channelCbs := newChannelUI(args[4])
+	cipherID := args[5].Int()
 
 	cipher, err := bindings.GetChannelDbCipherTrackerFromID(cipherID)
 	if err != nil {
@@ -375,8 +361,7 @@ func NewChannelsManagerWithIndexedDb(_ js.Value, args []js.Value) any {
 	}
 
 	return newChannelsManagerWithIndexedDb(cmixID, wasmJsPath, privateIdentity,
-		extensionBuilderIDsJSON, messageReceivedCB, deletedMessageCB,
-		mutedUserCB, cipher)
+		extensionBuilderIDsJSON, channelCbs, cipher)
 }
 
 // NewChannelsManagerWithIndexedDbUnsafe creates a new [ChannelsManager] from a
@@ -401,22 +386,8 @@ func NewChannelsManagerWithIndexedDb(_ js.Value, args []js.Value) any {
 //     IDs. The ID can be retrieved from an object with an extension builder
 //     (e.g., [ChannelsFileTransfer.GetExtensionBuilderID]). Leave empty if not
 //     using extension builders. Example: `[2,11,5]` (Uint8Array).
-//   - args[4] - 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[5] - 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[6] - 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[4] - A callback object which implements the
+//     [bindings.ChannelUICallbacks] javascript functions.
 //
 // Returns a promise:
 //   - Resolves to a Javascript representation of the [ChannelsManager] object.
@@ -428,38 +399,23 @@ func NewChannelsManagerWithIndexedDbUnsafe(_ js.Value, args []js.Value) any {
 	wasmJsPath := args[1].String()
 	privateIdentity := utils.CopyBytesToGo(args[2])
 	extensionBuilderIDsJSON := utils.CopyBytesToGo(args[3])
-	messageReceivedCB := args[4]
-	deletedMessageCB := args[5]
-	mutedUserCB := args[6]
+	channelsCbs := newChannelUI(args[4])
 
 	return newChannelsManagerWithIndexedDb(cmixID, wasmJsPath, privateIdentity,
-		extensionBuilderIDsJSON, messageReceivedCB, deletedMessageCB,
-		mutedUserCB, nil)
+		extensionBuilderIDsJSON, channelsCbs, nil)
 }
 
 func newChannelsManagerWithIndexedDb(cmixID int, wasmJsPath string,
-	privateIdentity, extensionBuilderIDsJSON []byte, 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)
-	}
+	privateIdentity, extensionBuilderIDsJSON []byte,
+	channelsCbs bindings.ChannelUICallbacks,
+	cipher *bindings.ChannelDbCipher) any {
 
 	model := channelsDb.NewWASMEventModelBuilder(
-		wasmJsPath, cipher, messageReceived, deletedMessage, mutedUser)
+		wasmJsPath, cipher, channelsCbs)
 
 	promiseFn := func(resolve, reject func(args ...any) js.Value) {
 		cm, err := bindings.NewChannelsManagerGoEventModel(
-			cmixID, privateIdentity, extensionBuilderIDsJSON, model)
+			cmixID, privateIdentity, extensionBuilderIDsJSON, model, channelsCbs)
 		if err != nil {
 			reject(utils.JsTrace(err))
 		} else {
@@ -484,23 +440,9 @@ 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] - 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
+//   - args[3] - A callback object which implements the
+//     [bindings.ChannelUICallbacks] javascript functions.
+//   - args[4] - ID of [ChannelDbCipher] object in tracker (int). Create this
 //     object with [NewChannelsDatabaseCipher] and get its id with
 //     [ChannelDbCipher.GetID].
 //
@@ -512,10 +454,8 @@ func LoadChannelsManagerWithIndexedDb(_ js.Value, args []js.Value) any {
 	cmixID := args[0].Int()
 	wasmJsPath := args[1].String()
 	storageTag := args[2].String()
-	messageReceivedCB := args[3]
-	deletedMessageCB := args[4]
-	mutedUserCB := args[5]
-	cipherID := args[6].Int()
+	channelsCbs := newChannelUI(args[3])
+	cipherID := args[4].Int()
 
 	cipher, err := bindings.GetChannelDbCipherTrackerFromID(cipherID)
 	if err != nil {
@@ -523,7 +463,7 @@ func LoadChannelsManagerWithIndexedDb(_ js.Value, args []js.Value) any {
 	}
 
 	return loadChannelsManagerWithIndexedDb(cmixID, wasmJsPath, storageTag,
-		messageReceivedCB, deletedMessageCB, mutedUserCB, cipher)
+		channelsCbs, cipher)
 }
 
 // LoadChannelsManagerWithIndexedDbUnsafe loads an existing [ChannelsManager]
@@ -542,22 +482,8 @@ 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] - 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[3] - A callback object which implements the
+//     [bindings.ChannelUICallbacks] javascript functions.
 //
 // Returns a promise:
 //   - Resolves to a Javascript representation of the [ChannelsManager] object.
@@ -566,37 +492,22 @@ func LoadChannelsManagerWithIndexedDbUnsafe(_ js.Value, args []js.Value) any {
 	cmixID := args[0].Int()
 	wasmJsPath := args[1].String()
 	storageTag := args[2].String()
-	messageReceivedCB := args[3]
-	deletedMessageCB := args[3]
-	mutedUserCB := args[4]
+	cUI := newChannelUI(args[3])
 
 	return loadChannelsManagerWithIndexedDb(cmixID, wasmJsPath, storageTag,
-		messageReceivedCB, deletedMessageCB, mutedUserCB, nil)
+		cUI, nil)
 }
 
 func loadChannelsManagerWithIndexedDb(cmixID int, wasmJsPath, storageTag string,
-	messageReceivedCB, deletedMessageCB, mutedUserCB js.Value,
+	channelsCbs bindings.ChannelUICallbacks,
 	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, messageReceived, deletedMessage, mutedUser)
+		wasmJsPath, cipher, channelsCbs)
 
 	promiseFn := func(resolve, reject func(args ...any) js.Value) {
 		cm, err := bindings.LoadChannelsManagerGoEventModel(
-			cmixID, storageTag, model, nil)
+			cmixID, storageTag, model, nil, channelsCbs)
 		if err != nil {
 			reject(utils.JsTrace(err))
 		} else {
@@ -2274,3 +2185,45 @@ func (c *ChannelDbCipher) UnmarshalJSON(_ js.Value, args []js.Value) any {
 	}
 	return nil
 }
+
+// newChannelUI maps the methods on the Javascript object to the
+// channelUI callbacks implementation struct.
+func newChannelUI(cbImpl js.Value) *channelUI {
+	return &channelUI{
+		messageReceived: utils.WrapCB(cbImpl, "MessageReceived"),
+		userMuted:       utils.WrapCB(cbImpl, "UserMuted"),
+		messageDeleted:  utils.WrapCB(cbImpl, "MessageDeleted"),
+		nicknameUpdate:  utils.WrapCB(cbImpl, "NicknameUpdate"),
+	}
+}
+
+// eventModel wraps Javascript callbacks to adhere to the
+// [bindings.ChannelUICallbacks] interface.
+type channelUI struct {
+	messageReceived func(args ...any) js.Value
+	userMuted       func(args ...any) js.Value
+	messageDeleted  func(args ...any) js.Value
+	nicknameUpdate  func(args ...any) js.Value
+}
+
+// MessageReceived implements [bindings.ChannelUICallbacks.MessageReceived].
+func (c *channelUI) MessageReceived(uuid int64, channelID []byte, update bool) {
+	c.messageReceived(uuid, utils.CopyBytesToJS(channelID), update)
+}
+
+// UserMuted implements [bindings.ChannelUICallbacks.UserMuted].
+func (c *channelUI) UserMuted(channelID []byte, pubKey []byte, unmute bool) {
+	c.userMuted(utils.CopyBytesToJS(channelID), utils.CopyBytesToJS(pubKey),
+		unmute)
+}
+
+// MessageDeleted implements [bindings.ChannelUICallbacks.MessageDeleted].
+func (c *channelUI) MessageDeleted(messageId []byte) {
+	c.messageDeleted(utils.CopyBytesToJS(messageId))
+}
+
+// NicknameUpdate implements [bindings.ChannelUICallbacks.NicknameUpdate]
+func (c *channelUI) NicknameUpdate(channelIdBytes []byte, nickname string,
+	exists bool) {
+	c.nicknameUpdate(utils.CopyBytesToJS(channelIdBytes), nickname, exists)
+}