diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 7ab520cd02563b548eabe925202ef87324725e2c..385f857c9c947089bf9018aabca620a02a9727c1 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -38,7 +38,6 @@ wasm-test:
     - tags
   script:
     - export PATH=/root/go/bin:$PATH
-    - echo > utils/utils_js.s
     - go mod vendor
     - unset SSH_PRIVATE_KEY
     - unset $(env | grep '=' | awk -F= '{print $1}' | grep -v PATH | grep -v GO | grep -v HOME)
diff --git a/Makefile b/Makefile
index f8c143a6a24f68f6b70d2bc64cc6028b56f4c9af..7712c6a1784f114afb04be79d17b0ef647872e0b 100644
--- a/Makefile
+++ b/Makefile
@@ -11,6 +11,7 @@ build:
 	GOOS=js GOARCH=wasm go build ./...
 
 update_release:
+	GOFLAGS="" go get gitlab.com/elixxir/wasm-utils@release
 	GOFLAGS="" go get gitlab.com/xx_network/primitives@release
 	GOFLAGS="" go get gitlab.com/elixxir/primitives@release
 	GOFLAGS="" go get gitlab.com/xx_network/crypto@release
@@ -18,11 +19,12 @@ update_release:
 	GOFLAGS="" go get -d gitlab.com/elixxir/client/v4@release
 
 update_master:
-	GOFLAGS="" go get -d gitlab.com/elixxir/client@master
-	GOFLAGS="" go get gitlab.com/elixxir/crypto@master
+	GOFLAGS="" go get gitlab.com/elixxir/wasm-utils@master
+	GOFLAGS="" go get gitlab.com/xx_network/primitives@master
 	GOFLAGS="" go get gitlab.com/elixxir/primitives@master
 	GOFLAGS="" go get gitlab.com/xx_network/crypto@master
-	GOFLAGS="" go get gitlab.com/xx_network/primitives@master
+	GOFLAGS="" go get gitlab.com/elixxir/crypto@master
+	GOFLAGS="" go get -d gitlab.com/elixxir/client/v4@master
 
 binary:
 	GOOS=js GOARCH=wasm go build -ldflags '-w -s' -trimpath -o xxdk.wasm main.go
@@ -34,11 +36,16 @@ worker_binaries:
 
 binaries: binary worker_binaries
 
+wasmException = "vendor/gitlab.com/elixxir/wasm-utils/exception"
+
 wasm_tests:
-	cp utils/utils_js.s utils/utils_js.s.bak
-	> utils/utils_js.s
+	cp $(wasmException)/throw_js.s $(wasmException)/throw_js.s.bak
+	cp $(wasmException)/throws.go $(wasmException)/throws.go.bak
+	> $(wasmException)/throw_js.s
+	cp $(wasmException)/throws.dev $(wasmException)/throws.go
 	-GOOS=js GOARCH=wasm go test -v ./...
-	mv utils/utils_js.s.bak utils/utils_js.s
+	mv $(wasmException)/throw_js.s.bak $(wasmException)/throw_js.s
+	mv $(wasmException)/throws.go.bak $(wasmException)/throws.go
 
 go_tests:
 	go test ./... -v
diff --git a/README.md b/README.md
index d1f59cd4392fce9f8b2b541862e69fa684400039..a898480b75ee28ead215556ad146c7e9a3cdf842 100644
--- a/README.md
+++ b/README.md
@@ -77,7 +77,7 @@ global.Go = class {
             go: {
                 // ...
                 // func Throw(exception string, message string)
-                'gitlab.com/elixxir/xxdk-wasm/utils.throw': (sp) => {
+                'gitlab.com/elixxir/wasm-utils/utils.throw': (sp) => {
                     const exception = loadString(sp + 8)
                     const message = loadString(sp + 24)
                     throw globalThis[exception](message)
diff --git a/indexedDb/impl/channels/fileTransferImpl.go b/indexedDb/impl/channels/fileTransferImpl.go
index d75dc78b6dddd5825d7c1b157dddadd75d62698d..4e30a7f91933d7cae8bc76a0efd06000be5dc3ef 100644
--- a/indexedDb/impl/channels/fileTransferImpl.go
+++ b/indexedDb/impl/channels/fileTransferImpl.go
@@ -15,8 +15,8 @@ import (
 	"gitlab.com/elixxir/client/v4/channels"
 	cft "gitlab.com/elixxir/client/v4/channelsFileTransfer"
 	"gitlab.com/elixxir/crypto/fileTransfer"
+	"gitlab.com/elixxir/wasm-utils/utils"
 	"gitlab.com/elixxir/xxdk-wasm/indexedDb/impl"
-	"gitlab.com/elixxir/xxdk-wasm/utils"
 	"strings"
 	"time"
 )
diff --git a/indexedDb/impl/channels/implementation.go b/indexedDb/impl/channels/implementation.go
index 19dba81e1671cc2dbecbe4761df476473f83e0ad..b7aa1f711719189f12e9719b896d470ee24e2127 100644
--- a/indexedDb/impl/channels/implementation.go
+++ b/indexedDb/impl/channels/implementation.go
@@ -27,8 +27,9 @@ import (
 	cryptoBroadcast "gitlab.com/elixxir/crypto/broadcast"
 	cryptoChannel "gitlab.com/elixxir/crypto/channel"
 	"gitlab.com/elixxir/crypto/message"
+	"gitlab.com/elixxir/wasm-utils/utils"
 	"gitlab.com/elixxir/xxdk-wasm/indexedDb/impl"
-	"gitlab.com/elixxir/xxdk-wasm/utils"
+	wChannels "gitlab.com/elixxir/xxdk-wasm/indexedDb/worker/channels"
 	"gitlab.com/xx_network/primitives/id"
 )
 
diff --git a/indexedDb/impl/channels/implementation_test.go b/indexedDb/impl/channels/implementation_test.go
index d189d82413b9e6954dba28be9a1361b75e30d65e..d13f725d02d33ddbd12cedea8940211fe958890f 100644
--- a/indexedDb/impl/channels/implementation_test.go
+++ b/indexedDb/impl/channels/implementation_test.go
@@ -30,8 +30,8 @@ import (
 	cryptoBroadcast "gitlab.com/elixxir/crypto/broadcast"
 	cryptoChannel "gitlab.com/elixxir/crypto/channel"
 	"gitlab.com/elixxir/crypto/message"
+	"gitlab.com/elixxir/wasm-utils/storage"
 	"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"
diff --git a/indexedDb/impl/channels/init.go b/indexedDb/impl/channels/init.go
index 282b4582fe49a98d9d62ec9912adb43ea6d549e5..4802456c65d757515e617fc8ca40325d487ccaa5 100644
--- a/indexedDb/impl/channels/init.go
+++ b/indexedDb/impl/channels/init.go
@@ -23,7 +23,7 @@ import (
 
 // currentVersion is the current version of the IndexedDb runtime. Used for
 // migration purposes.
-const currentVersion uint = 2
+const currentVersion uint = 1
 
 // NewWASMEventModel returns a [channels.EventModel] backed by a wasmModel.
 // The name should be a base64 encoding of the users public key. Returns the
@@ -57,14 +57,6 @@ func newWASMModel(databaseName string, encryption cryptoChannel.Cipher,
 				oldVersion = 1
 			}
 
-			if oldVersion == 1 && newVersion >= 2 {
-				err := v2Upgrade(db)
-				if err != nil {
-					return err
-				}
-				oldVersion = 2
-			}
-
 			// if oldVersion == 1 && newVersion >= 2 { v2Upgrade(), oldVersion = 2 }
 			return nil
 		})
@@ -143,15 +135,8 @@ func v1Upgrade(db *idb.Database) error {
 		return err
 	}
 
-	return nil
-}
-
-// v1Upgrade performs the v1 -> v2 database upgrade.
-//
-// This can never be changed without permanently breaking backwards
-// compatibility.
-func v2Upgrade(db *idb.Database) error {
-	_, err := db.CreateObjectStore(fileStoreName, idb.ObjectStoreOptions{
+	// Build File ObjectStore
+	_, err = db.CreateObjectStore(fileStoreName, idb.ObjectStoreOptions{
 		KeyPath:       js.ValueOf(pkeyName),
 		AutoIncrement: false,
 	})
diff --git a/indexedDb/impl/dm/implementation.go b/indexedDb/impl/dm/implementation.go
index 1c8e18050d8c36c32ede354f9afaa9a6af1993db..a92691878ebc4b6c21cd1d38297aca23e4109920 100644
--- a/indexedDb/impl/dm/implementation.go
+++ b/indexedDb/impl/dm/implementation.go
@@ -13,6 +13,7 @@ import (
 	"bytes"
 	"crypto/ed25519"
 	"encoding/json"
+	"gitlab.com/xx_network/primitives/netTime"
 	"strings"
 	"syscall/js"
 	"time"
@@ -25,8 +26,8 @@ import (
 	"gitlab.com/elixxir/client/v4/dm"
 	cryptoChannel "gitlab.com/elixxir/crypto/channel"
 	"gitlab.com/elixxir/crypto/message"
+	"gitlab.com/elixxir/wasm-utils/utils"
 	"gitlab.com/elixxir/xxdk-wasm/indexedDb/impl"
-	"gitlab.com/elixxir/xxdk-wasm/utils"
 	"gitlab.com/xx_network/primitives/id"
 )
 
@@ -42,16 +43,16 @@ type wasmModel struct {
 // upsertConversation is used for joining or updating a Conversation.
 func (w *wasmModel) upsertConversation(nickname string,
 	pubKey ed25519.PublicKey, partnerToken uint32, codeset uint8,
-	blocked bool) error {
+	blockedTimestamp *time.Time) error {
 	parentErr := errors.New("[DM indexedDB] failed to upsertConversation")
 
 	// Build object
 	newConvo := Conversation{
-		Pubkey:         pubKey,
-		Nickname:       nickname,
-		Token:          partnerToken,
-		CodesetVersion: codeset,
-		Blocked:        blocked,
+		Pubkey:           pubKey,
+		Nickname:         nickname,
+		Token:            partnerToken,
+		CodesetVersion:   codeset,
+		BlockedTimestamp: blockedTimestamp,
 	}
 
 	// Convert to jsObject
@@ -231,11 +232,11 @@ func (w *wasmModel) receiveWrapper(messageID message.ID, parentID *message.ID, n
 				"[DM indexedDB] Joining conversation with %s", nickname)
 
 			convoToUpdate = &Conversation{
-				Pubkey:         partnerKey,
-				Nickname:       nickname,
-				Token:          partnerToken,
-				CodesetVersion: codeset,
-				Blocked:        false,
+				Pubkey:           partnerKey,
+				Nickname:         nickname,
+				Token:            partnerToken,
+				CodesetVersion:   codeset,
+				BlockedTimestamp: nil,
 			}
 		}
 	} else {
@@ -268,7 +269,7 @@ func (w *wasmModel) receiveWrapper(messageID message.ID, parentID *message.ID, n
 	conversationUpdated := convoToUpdate != nil
 	if conversationUpdated {
 		err = w.upsertConversation(convoToUpdate.Nickname, convoToUpdate.Pubkey,
-			convoToUpdate.Token, convoToUpdate.CodesetVersion, convoToUpdate.Blocked)
+			convoToUpdate.Token, convoToUpdate.CodesetVersion, convoToUpdate.BlockedTimestamp)
 		if err != nil {
 			return 0, err
 		}
@@ -349,14 +350,20 @@ func (w *wasmModel) UnblockSender(senderPubKey ed25519.PublicKey) {
 
 // setBlocked is a helper for blocking/unblocking a given Conversation.
 func (w *wasmModel) setBlocked(senderPubKey ed25519.PublicKey, isBlocked bool) error {
-	// Get current Conversation and set blocked
+	// Get current Conversation and set blocked accordingly
 	resultConvo, err := w.getConversation(senderPubKey)
 	if err != nil {
 		return err
 	}
 
+	var timeBlocked *time.Time
+	if isBlocked {
+		blockUser := netTime.Now()
+		timeBlocked = &blockUser
+	}
+
 	return w.upsertConversation(resultConvo.Nickname, resultConvo.Pubkey,
-		resultConvo.Token, resultConvo.CodesetVersion, isBlocked)
+		resultConvo.Token, resultConvo.CodesetVersion, timeBlocked)
 }
 
 // GetConversation returns the conversation held by the model (receiver).
@@ -369,11 +376,11 @@ func (w *wasmModel) GetConversation(senderPubKey ed25519.PublicKey) *dm.ModelCon
 	}
 
 	return &dm.ModelConversation{
-		Pubkey:         resultConvo.Pubkey,
-		Nickname:       resultConvo.Nickname,
-		Token:          resultConvo.Token,
-		CodesetVersion: resultConvo.CodesetVersion,
-		Blocked:        resultConvo.Blocked,
+		Pubkey:           resultConvo.Pubkey,
+		Nickname:         resultConvo.Nickname,
+		Token:            resultConvo.Token,
+		CodesetVersion:   resultConvo.CodesetVersion,
+		BlockedTimestamp: resultConvo.BlockedTimestamp,
 	}
 }
 
@@ -411,11 +418,11 @@ func (w *wasmModel) GetConversations() []dm.ModelConversation {
 			return nil
 		}
 		conversations[i] = dm.ModelConversation{
-			Pubkey:         resultConvo.Pubkey,
-			Nickname:       resultConvo.Nickname,
-			Token:          resultConvo.Token,
-			CodesetVersion: resultConvo.CodesetVersion,
-			Blocked:        resultConvo.Blocked,
+			Pubkey:           resultConvo.Pubkey,
+			Nickname:         resultConvo.Nickname,
+			Token:            resultConvo.Token,
+			CodesetVersion:   resultConvo.CodesetVersion,
+			BlockedTimestamp: resultConvo.BlockedTimestamp,
 		}
 	}
 	return conversations
diff --git a/indexedDb/impl/dm/implementation_test.go b/indexedDb/impl/dm/implementation_test.go
index 8e05e6af5fff85af79ea0f15be9dc1e2f5a77c12..c8f42c3871bcfc841683e93b57e2ed4105ab000b 100644
--- a/indexedDb/impl/dm/implementation_test.go
+++ b/indexedDb/impl/dm/implementation_test.go
@@ -17,8 +17,8 @@ import (
 	"gitlab.com/elixxir/client/v4/cmix/rounds"
 	"gitlab.com/elixxir/client/v4/dm"
 	"gitlab.com/elixxir/crypto/message"
+	"gitlab.com/elixxir/wasm-utils/utils"
 	"gitlab.com/elixxir/xxdk-wasm/indexedDb/impl"
-	"gitlab.com/elixxir/xxdk-wasm/utils"
 	"gitlab.com/xx_network/primitives/id"
 	"os"
 	"syscall/js"
@@ -102,7 +102,7 @@ func TestImpl_GetConversations(t *testing.T) {
 		testBytes := []byte(fmt.Sprintf("%d", i))
 		testPubKey := ed25519.PublicKey(testBytes)
 		err = m.upsertConversation("test", testPubKey,
-			uint32(i), uint8(i), false)
+			uint32(i), uint8(i), nil)
 		if err != nil {
 			t.Fatal(err.Error())
 		}
@@ -133,28 +133,28 @@ func TestWasmModel_BlockSender(t *testing.T) {
 
 	// Insert a test convo
 	testPubKey := ed25519.PublicKey{}
-	err = m.upsertConversation("test", testPubKey, 0, 0, false)
+	err = m.upsertConversation("test", testPubKey, 0, 0, nil)
 	if err != nil {
 		t.Fatal(err.Error())
 	}
 
 	// Default to unblocked
 	result := m.GetConversation(testPubKey)
-	if result.Blocked {
+	if result.BlockedTimestamp != nil {
 		t.Fatal("Expected blocked to be false")
 	}
 
 	// Now toggle blocked
 	m.BlockSender(testPubKey)
 	result = m.GetConversation(testPubKey)
-	if !result.Blocked {
+	if result.BlockedTimestamp == nil {
 		t.Fatal("Expected blocked to be true")
 	}
 
 	// Now toggle blocked again
 	m.UnblockSender(testPubKey)
 	result = m.GetConversation(testPubKey)
-	if result.Blocked {
+	if result.BlockedTimestamp != nil {
 		t.Fatal("Expected blocked to be false")
 	}
 }
diff --git a/indexedDb/impl/dm/model.go b/indexedDb/impl/dm/model.go
index dd4fee16205c9736936830bbb598252dc0774e05..774d011fe4987078808febd74a1657839e06dc8a 100644
--- a/indexedDb/impl/dm/model.go
+++ b/indexedDb/impl/dm/model.go
@@ -55,9 +55,9 @@ type Message struct {
 // 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"`
+	Pubkey           []byte     `json:"pub_key"` // Matches convoPkeyName
+	Nickname         string     `json:"nickname"`
+	Token            uint32     `json:"token"`
+	CodesetVersion   uint8      `json:"codeset_version"`
+	BlockedTimestamp *time.Time `json:"blocked_timestamp"`
 }
diff --git a/indexedDb/impl/utils.go b/indexedDb/impl/utils.go
index b657240b7e7018a031365ffc8514c3fe54141471..7dbf631c9deee97f3ef79b731832aab142820fbc 100644
--- a/indexedDb/impl/utils.go
+++ b/indexedDb/impl/utils.go
@@ -18,7 +18,7 @@ import (
 	"github.com/hack-pad/go-indexeddb/idb"
 	"github.com/pkg/errors"
 	jww "github.com/spf13/jwalterweatherman"
-	"gitlab.com/elixxir/xxdk-wasm/utils"
+	"gitlab.com/elixxir/wasm-utils/utils"
 	"syscall/js"
 	"time"
 )
diff --git a/logging/logger.go b/logging/logger.go
index 3064a00d551a464a486c8f8a23925cf74cea7998..8155210daf6b0b81667334d8675f63bd6b2aaca5 100644
--- a/logging/logger.go
+++ b/logging/logger.go
@@ -15,7 +15,7 @@ import (
 
 	jww "github.com/spf13/jwalterweatherman"
 
-	"gitlab.com/elixxir/xxdk-wasm/utils"
+	"gitlab.com/elixxir/wasm-utils/utils"
 	"gitlab.com/elixxir/xxdk-wasm/worker"
 )
 
diff --git a/main.go b/main.go
index 81e946e4f25daa2ba483bdd66a43095b48da0bed..07b25c75fde2d824c66f78fb5543dfc7d4d5d9b2 100644
--- a/main.go
+++ b/main.go
@@ -18,9 +18,9 @@ import (
 
 	jww "github.com/spf13/jwalterweatherman"
 
+	"gitlab.com/elixxir/wasm-utils/utils"
 	"gitlab.com/elixxir/xxdk-wasm/logging"
 	"gitlab.com/elixxir/xxdk-wasm/storage"
-	"gitlab.com/elixxir/xxdk-wasm/utils"
 	"gitlab.com/elixxir/xxdk-wasm/wasm"
 )
 
diff --git a/storage/indexedDbEncryptionTrack.go b/storage/indexedDbEncryptionTrack.go
index d568a63787490ea0979a189191272da70d957e05..bb3f049b8adeef1e2f69b535e79285338f5107bc 100644
--- a/storage/indexedDbEncryptionTrack.go
+++ b/storage/indexedDbEncryptionTrack.go
@@ -12,6 +12,8 @@ package storage
 import (
 	"github.com/pkg/errors"
 	"os"
+
+	"gitlab.com/elixxir/wasm-utils/storage"
 )
 
 // Key to store if the database is encrypted or not
@@ -22,12 +24,15 @@ const databaseEncryptionToggleKey = "xxdkWasmDatabaseEncryptionToggle/"
 func StoreIndexedDbEncryptionStatus(
 	databaseName string, encryptionStatus bool) (
 	loadedEncryptionStatus bool, err error) {
-	data, err := GetLocalStorage().GetItem(
-		databaseEncryptionToggleKey + databaseName)
+	ls := storage.GetLocalStorage()
+	data, err := ls.Get(databaseEncryptionToggleKey + databaseName)
 	if err != nil {
 		if errors.Is(err, os.ErrNotExist) {
-			GetLocalStorage().SetItem(
-				databaseEncryptionToggleKey+databaseName, []byte{1})
+			keyName := databaseEncryptionToggleKey + databaseName
+			if err = ls.Set(keyName, []byte{1}); err != nil {
+				return false,
+					errors.Wrapf(err, "localStorage: failed to set %q", keyName)
+			}
 			return encryptionStatus, nil
 		} else {
 			return false, err
diff --git a/storage/indexedDbList.go b/storage/indexedDbList.go
index a736984f0ef742d96e7a63238ea6ebfbdc25d9f9..8917fcdbf3c413d306bd6cc0687d2af80b441992 100644
--- a/storage/indexedDbList.go
+++ b/storage/indexedDbList.go
@@ -11,8 +11,11 @@ package storage
 
 import (
 	"encoding/json"
-	"github.com/pkg/errors"
 	"os"
+
+	"github.com/pkg/errors"
+
+	"gitlab.com/elixxir/wasm-utils/storage"
 )
 
 const indexedDbListKey = "xxDkWasmIndexedDbList"
@@ -20,7 +23,7 @@ const indexedDbListKey = "xxDkWasmIndexedDbList"
 // GetIndexedDbList returns the list of stored indexedDb databases.
 func GetIndexedDbList() (map[string]struct{}, error) {
 	list := make(map[string]struct{})
-	listBytes, err := GetLocalStorage().GetItem(indexedDbListKey)
+	listBytes, err := storage.GetLocalStorage().Get(indexedDbListKey)
 	if err != nil && !errors.Is(err, os.ErrNotExist) {
 		return nil, err
 	} else if err == nil {
@@ -47,7 +50,11 @@ func StoreIndexedDb(databaseName string) error {
 		return err
 	}
 
-	GetLocalStorage().SetItem(indexedDbListKey, listBytes)
+	err = storage.GetLocalStorage().Set(indexedDbListKey, listBytes)
+	if err != nil {
+		return errors.Wrapf(err,
+			"localStorage: failed to set %q", indexedDbListKey)
+	}
 
 	return nil
 }
diff --git a/storage/localStorage.go b/storage/localStorage.go
deleted file mode 100644
index d54039c99c427640b3298c7aeb87dfb819d046be..0000000000000000000000000000000000000000
--- a/storage/localStorage.go
+++ /dev/null
@@ -1,209 +0,0 @@
-////////////////////////////////////////////////////////////////////////////////
-// 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 storage
-
-import (
-	"encoding/base64"
-	"encoding/json"
-	jww "github.com/spf13/jwalterweatherman"
-	"gitlab.com/elixxir/xxdk-wasm/utils"
-	"os"
-	"strings"
-	"syscall/js"
-)
-
-// localStorageWasmPrefix is prefixed to every keyName saved to local storage by
-// LocalStorage. It allows the identifications and deletion of keys only created
-// by this WASM binary while ignoring keys made by other scripts on the same
-// page.
-const localStorageWasmPrefix = "xxdkWasmStorage/"
-
-// LocalStorage contains the js.Value representation of localStorage.
-type LocalStorage struct {
-	// The Javascript value containing the localStorage object
-	v js.Value
-
-	// The prefix appended to each key name. This is so that all keys created by
-	// this structure can be deleted without affecting other keys in local
-	// storage.
-	prefix string
-}
-
-// jsStorage is the global that stores Javascript as window.localStorage.
-//
-//   - Specification:
-//     https://html.spec.whatwg.org/multipage/webstorage.html#dom-localstorage-dev
-//   - Documentation:
-//     https://developer.mozilla.org/en-US/docs/Web/API/Window/localStorage
-var jsStorage = newLocalStorage(localStorageWasmPrefix)
-
-// newLocalStorage creates a new LocalStorage object with the specified prefix.
-func newLocalStorage(prefix string) *LocalStorage {
-	return &LocalStorage{
-		v:      js.Global().Get("localStorage"),
-		prefix: prefix,
-	}
-}
-
-// GetLocalStorage returns Javascript's local storage.
-func GetLocalStorage() *LocalStorage {
-	return jsStorage
-}
-
-// GetItem returns a key's value from the local storage given its name. Returns
-// os.ErrNotExist if the key does not exist. Underneath, it calls
-// localStorage.GetItem().
-//
-//   - Specification:
-//     https://html.spec.whatwg.org/multipage/webstorage.html#dom-storage-getitem-dev
-//   - Documentation:
-//     https://developer.mozilla.org/en-US/docs/Web/API/Storage/getItem
-func (ls *LocalStorage) GetItem(keyName string) ([]byte, error) {
-	keyValue := ls.getItem(ls.prefix + keyName)
-	if keyValue.IsNull() {
-		return nil, os.ErrNotExist
-	}
-
-	decodedKeyValue, err := base64.StdEncoding.DecodeString(keyValue.String())
-	if err != nil {
-		return nil, err
-	}
-
-	return decodedKeyValue, nil
-}
-
-// SetItem adds a key's value to local storage given its name. Underneath, it
-// calls localStorage.SetItem().
-//
-//   - Specification:
-//     https://html.spec.whatwg.org/multipage/webstorage.html#dom-storage-setitem-dev
-//   - Documentation:
-//     https://developer.mozilla.org/en-US/docs/Web/API/Storage/setItem
-func (ls *LocalStorage) SetItem(keyName string, keyValue []byte) {
-	encodedKeyValue := base64.StdEncoding.EncodeToString(keyValue)
-	ls.setItem(ls.prefix+keyName, encodedKeyValue)
-}
-
-// RemoveItem removes a key's value from local storage given its name. If there
-// is no item with the given key, this function does nothing. Underneath, it
-// calls localStorage.RemoveItem().
-//
-//   - Specification:
-//     https://html.spec.whatwg.org/multipage/webstorage.html#dom-storage-removeitem-dev
-//   - Documentation:
-//     https://developer.mozilla.org/en-US/docs/Web/API/Storage/removeItem
-func (ls *LocalStorage) RemoveItem(keyName string) {
-	ls.removeItem(ls.prefix + keyName)
-}
-
-// Clear clears all the keys in storage. Underneath, it calls
-// localStorage.clear().
-//
-//   - Specification:
-//     https://html.spec.whatwg.org/multipage/webstorage.html#dom-storage-clear-dev
-//   - Documentation:
-//     https://developer.mozilla.org/en-US/docs/Web/API/Storage/clear
-func (ls *LocalStorage) Clear() {
-	ls.clear()
-}
-
-// ClearPrefix clears all keys with the given prefix.  Returns the number of
-// keys cleared.
-func (ls *LocalStorage) ClearPrefix(prefix string) int {
-	// Get a copy of all key names at once
-	keys := ls.keys()
-
-	// Loop through each key
-	var n int
-	for i := 0; i < keys.Length(); i++ {
-		if v := keys.Index(i); !v.IsNull() {
-			keyName := strings.TrimPrefix(v.String(), ls.prefix)
-			if strings.HasPrefix(keyName, prefix) {
-				ls.removeItem(v.String())
-				n++
-			}
-		}
-	}
-
-	return n
-}
-
-// ClearWASM clears all the keys in storage created by WASM. Returns the number
-// of keys cleared.
-func (ls *LocalStorage) ClearWASM() int {
-	// Get a copy of all key names at once
-	keys := ls.keys()
-
-	// Loop through each key
-	var n int
-	for i := 0; i < keys.Length(); i++ {
-		if v := keys.Index(i); !v.IsNull() {
-			keyName := v.String()
-			if strings.HasPrefix(keyName, ls.prefix) {
-				ls.RemoveItem(strings.TrimPrefix(keyName, ls.prefix))
-				n++
-			}
-		}
-	}
-
-	return n
-}
-
-// Key returns the name of the nth key in localStorage. Return os.ErrNotExist if
-// the key does not exist. The order of keys is not defined. If there is no item
-// with the given key, this function does nothing. Underneath, it calls
-// localStorage.key().
-//
-//   - Specification:
-//     https://html.spec.whatwg.org/multipage/webstorage.html#dom-storage-key-dev
-//   - Documentation:
-//     https://developer.mozilla.org/en-US/docs/Web/API/Storage/key
-func (ls *LocalStorage) Key(n int) (string, error) {
-	keyName := ls.key(n)
-	if keyName.IsNull() {
-		return "", os.ErrNotExist
-	}
-
-	return strings.TrimPrefix(keyName.String(), ls.prefix), nil
-}
-
-// Keys returns a list of all key names in local storage.
-func (ls *LocalStorage) Keys() []string {
-	keyNamesJson := utils.JSON.Call("stringify", ls.keys())
-
-	var keyNames []string
-	err := json.Unmarshal([]byte(keyNamesJson.String()), &keyNames)
-	if err != nil {
-		jww.FATAL.Panicf(
-			"Failed to JSON unmarshal localStorage key name list: %+v", err)
-	}
-
-	return keyNames
-}
-
-// Length returns the number of keys in localStorage. Underneath, it accesses
-// the property localStorage.length.
-//
-//   - Specification:
-//     https://html.spec.whatwg.org/multipage/webstorage.html#dom-storage-key-dev
-//   - Documentation:
-//     https://developer.mozilla.org/en-US/docs/Web/API/Storage/length
-func (ls *LocalStorage) Length() int {
-	return ls.length().Int()
-}
-
-// Wrappers for Javascript Storage methods and properties.
-func (ls *LocalStorage) getItem(keyName string) js.Value  { return ls.v.Call("getItem", keyName) }
-func (ls *LocalStorage) setItem(keyName, keyValue string) { ls.v.Call("setItem", keyName, keyValue) }
-func (ls *LocalStorage) removeItem(keyName string)        { ls.v.Call("removeItem", keyName) }
-func (ls *LocalStorage) clear()                           { ls.v.Call("clear") }
-func (ls *LocalStorage) key(n int) js.Value               { return ls.v.Call("key", n) }
-func (ls *LocalStorage) length() js.Value                 { return ls.v.Get("length") }
-func (ls *LocalStorage) keys() js.Value                   { return utils.Object.Call("keys", ls.v) }
diff --git a/storage/localStorage_test.go b/storage/localStorage_test.go
deleted file mode 100644
index 20e424108af9b7ed64c9c4ba00e348b121beefc4..0000000000000000000000000000000000000000
--- a/storage/localStorage_test.go
+++ /dev/null
@@ -1,271 +0,0 @@
-////////////////////////////////////////////////////////////////////////////////
-// 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 storage
-
-import (
-	"bytes"
-	"github.com/pkg/errors"
-	"math/rand"
-	"os"
-	"strconv"
-	"testing"
-)
-
-// Tests that a value set with LocalStorage.SetItem and retrieved with
-// LocalStorage.GetItem matches the original.
-func TestLocalStorage_GetItem_SetItem(t *testing.T) {
-	values := map[string][]byte{
-		"key1": []byte("key value"),
-		"key2": {0, 1, 2, 3, 4, 5, 6, 7, 8, 9},
-		"key3": {0, 49, 0, 0, 0, 38, 249, 93, 242, 189, 222, 32, 138, 248, 121,
-			151, 42, 108, 82, 199, 163, 61, 4, 200, 140, 231, 225, 20, 35, 243,
-			253, 161, 61, 2, 227, 208, 173, 183, 33, 66, 236, 107, 105, 119, 26,
-			42, 44, 60, 109, 172, 38, 47, 220, 17, 129, 4, 234, 241, 141, 81,
-			84, 185, 32, 120, 115, 151, 128, 196, 143, 117, 222, 78, 44, 115,
-			109, 20, 249, 46, 158, 139, 231, 157, 54, 219, 141, 252},
-	}
-
-	for keyName, keyValue := range values {
-		jsStorage.SetItem(keyName, keyValue)
-
-		loadedValue, err := jsStorage.GetItem(keyName)
-		if err != nil {
-			t.Errorf("Failed to load %q: %+v", keyName, err)
-		}
-
-		if !bytes.Equal(keyValue, loadedValue) {
-			t.Errorf("Loaded value does not match original for %q"+
-				"\nexpected: %q\nreceived: %q", keyName, keyValue, loadedValue)
-		}
-	}
-}
-
-// Tests that LocalStorage.GetItem returns the error os.ErrNotExist when the key
-// does not exist in storage.
-func TestLocalStorage_GetItem_NotExistError(t *testing.T) {
-	_, err := jsStorage.GetItem("someKey")
-	if err == nil || !errors.Is(err, os.ErrNotExist) {
-		t.Errorf("Incorrect error for non existant key."+
-			"\nexpected: %v\nreceived: %v", os.ErrNotExist, err)
-	}
-}
-
-// Tests that LocalStorage.RemoveItem deletes a key from store and that it
-// cannot be retrieved.
-func TestLocalStorage_RemoveItem(t *testing.T) {
-	keyName := "key"
-	jsStorage.SetItem(keyName, []byte("value"))
-	jsStorage.RemoveItem(keyName)
-
-	_, err := jsStorage.GetItem(keyName)
-	if err == nil || !errors.Is(err, os.ErrNotExist) {
-		t.Errorf("Failed to remove %q: %+v", keyName, err)
-	}
-}
-
-// Tests that LocalStorage.Clear deletes all keys from storage.
-func TestLocalStorage_Clear(t *testing.T) {
-	for i := 0; i < 10; i++ {
-		jsStorage.SetItem(strconv.Itoa(i), []byte(strconv.Itoa(i)))
-	}
-
-	jsStorage.Clear()
-
-	l := jsStorage.Length()
-
-	if l > 0 {
-		t.Errorf("Clear did not delete all keys. Found %d keys.", l)
-	}
-}
-
-// Tests that LocalStorage.ClearPrefix deletes only the keys with the given
-// prefix.
-func TestLocalStorage_ClearPrefix(t *testing.T) {
-	s := newLocalStorage("")
-	s.clear()
-	prng := rand.New(rand.NewSource(11))
-	const numKeys = 10
-	var yesPrefix, noPrefix []string
-	prefix := "keyNamePrefix/"
-
-	for i := 0; i < numKeys; i++ {
-		keyName := "keyNum" + strconv.Itoa(i)
-		if prng.Intn(2) == 0 {
-			keyName = prefix + keyName
-			yesPrefix = append(yesPrefix, keyName)
-		} else {
-			noPrefix = append(noPrefix, keyName)
-		}
-
-		s.SetItem(keyName, []byte(strconv.Itoa(i)))
-	}
-
-	n := s.ClearPrefix(prefix)
-	if n != numKeys/2 {
-		t.Errorf("Incorrect number of keys.\nexpected: %d\nreceived: %d",
-			numKeys/2, n)
-	}
-
-	for _, keyName := range noPrefix {
-		if _, err := s.GetItem(keyName); err != nil {
-			t.Errorf("Could not get keyName %q: %+v", keyName, err)
-		}
-	}
-	for _, keyName := range yesPrefix {
-		keyValue, err := s.GetItem(keyName)
-		if err == nil || !errors.Is(err, os.ErrNotExist) {
-			t.Errorf("Found keyName %q: %q", keyName, keyValue)
-		}
-	}
-}
-
-// Tests that LocalStorage.ClearWASM deletes all the WASM keys from storage and
-// does not remove any others
-func TestLocalStorage_ClearWASM(t *testing.T) {
-	jsStorage.clear()
-	prng := rand.New(rand.NewSource(11))
-	const numKeys = 10
-	var yesPrefix, noPrefix []string
-
-	for i := 0; i < numKeys; i++ {
-		keyName := "keyNum" + strconv.Itoa(i)
-		if prng.Intn(2) == 0 {
-			yesPrefix = append(yesPrefix, keyName)
-			jsStorage.SetItem(keyName, []byte(strconv.Itoa(i)))
-		} else {
-			noPrefix = append(noPrefix, keyName)
-			jsStorage.setItem(keyName, strconv.Itoa(i))
-		}
-	}
-
-	n := jsStorage.ClearWASM()
-	if n != numKeys/2 {
-		t.Errorf("Incorrect number of keys.\nexpected: %d\nreceived: %d",
-			numKeys/2, n)
-	}
-
-	for _, keyName := range noPrefix {
-		if v := jsStorage.getItem(keyName); v.IsNull() {
-			t.Errorf("Could not get keyName %q.", keyName)
-		}
-	}
-	for _, keyName := range yesPrefix {
-		keyValue, err := jsStorage.GetItem(keyName)
-		if err == nil || !errors.Is(err, os.ErrNotExist) {
-			t.Errorf("Found keyName %q: %q", keyName, keyValue)
-		}
-	}
-}
-
-// Tests that LocalStorage.Key return all added keys when looping through all
-// indexes.
-func TestLocalStorage_Key(t *testing.T) {
-	jsStorage.clear()
-	values := map[string][]byte{
-		"key1": []byte("key value"),
-		"key2": {0, 1, 2, 3, 4, 5, 6, 7, 8, 9},
-		"key3": {0, 49, 0, 0, 0, 38, 249, 93},
-	}
-
-	for keyName, keyValue := range values {
-		jsStorage.SetItem(keyName, keyValue)
-	}
-
-	numKeys := len(values)
-	for i := 0; i < numKeys; i++ {
-		keyName, err := jsStorage.Key(i)
-		if err != nil {
-			t.Errorf("No key found for index %d: %+v", i, err)
-		}
-
-		if _, exists := values[keyName]; !exists {
-			t.Errorf("No key with name %q added to storage.", keyName)
-		}
-		delete(values, keyName)
-	}
-
-	if len(values) != 0 {
-		t.Errorf("%d keys not read from storage: %q", len(values), values)
-	}
-}
-
-// Tests that LocalStorage.Key returns the error os.ErrNotExist when the index
-// is greater than or equal to the number of keys.
-func TestLocalStorage_Key_NotExistError(t *testing.T) {
-	jsStorage.clear()
-	jsStorage.SetItem("key", []byte("value"))
-
-	_, err := jsStorage.Key(1)
-	if err == nil || !errors.Is(err, os.ErrNotExist) {
-		t.Errorf("Incorrect error for non existant key index."+
-			"\nexpected: %v\nreceived: %v", os.ErrNotExist, err)
-	}
-
-	_, err = jsStorage.Key(2)
-	if err == nil || !errors.Is(err, os.ErrNotExist) {
-		t.Errorf("Incorrect error for non existant key index."+
-			"\nexpected: %v\nreceived: %v", os.ErrNotExist, err)
-	}
-}
-
-// Tests that LocalStorage.Length returns the correct Length when adding and
-// removing various keys.
-func TestLocalStorage_Length(t *testing.T) {
-	jsStorage.clear()
-	values := map[string][]byte{
-		"key1": []byte("key value"),
-		"key2": {0, 1, 2, 3, 4, 5, 6, 7, 8, 9},
-		"key3": {0, 49, 0, 0, 0, 38, 249, 93},
-	}
-
-	i := 0
-	for keyName, keyValue := range values {
-		jsStorage.SetItem(keyName, keyValue)
-		i++
-
-		if jsStorage.Length() != i {
-			t.Errorf("Incorrect length.\nexpected: %d\nreceived: %d",
-				i, jsStorage.Length())
-		}
-	}
-
-	i = len(values)
-	for keyName := range values {
-		jsStorage.RemoveItem(keyName)
-		i--
-
-		if jsStorage.Length() != i {
-			t.Errorf("Incorrect length.\nexpected: %d\nreceived: %d",
-				i, jsStorage.Length())
-		}
-	}
-}
-
-// Tests that LocalStorage.Keys return a list that contains all the added keys.
-func TestLocalStorage_Keys(t *testing.T) {
-	s := newLocalStorage("")
-	s.clear()
-	values := map[string][]byte{
-		"key1": []byte("key value"),
-		"key2": {0, 1, 2, 3, 4, 5, 6, 7, 8, 9},
-		"key3": {0, 49, 0, 0, 0, 38, 249, 93},
-	}
-
-	for keyName, keyValue := range values {
-		s.SetItem(keyName, keyValue)
-	}
-
-	keys := s.Keys()
-	for i, keyName := range keys {
-		if _, exists := values[keyName]; !exists {
-			t.Errorf("Key %q does not exist (%d).", keyName, i)
-		}
-	}
-}
diff --git a/storage/password.go b/storage/password.go
index 36175e1002dbe3343a32fa41a9af239a9e13a7aa..7be49b310b54b7aa2764678f35d0b91550d2c491 100644
--- a/storage/password.go
+++ b/storage/password.go
@@ -12,16 +12,21 @@ package storage
 import (
 	"crypto/cipher"
 	"encoding/json"
-	"github.com/pkg/errors"
-	jww "github.com/spf13/jwalterweatherman"
-	"gitlab.com/elixxir/xxdk-wasm/utils"
-	"gitlab.com/xx_network/crypto/csprng"
-	"golang.org/x/crypto/argon2"
-	"golang.org/x/crypto/blake2b"
-	"golang.org/x/crypto/chacha20poly1305"
 	"io"
 	"os"
 	"syscall/js"
+
+	"golang.org/x/crypto/argon2"
+	"golang.org/x/crypto/blake2b"
+	"golang.org/x/crypto/chacha20poly1305"
+
+	"github.com/pkg/errors"
+	jww "github.com/spf13/jwalterweatherman"
+
+	"gitlab.com/elixxir/wasm-utils/exception"
+	"gitlab.com/elixxir/wasm-utils/storage"
+	"gitlab.com/elixxir/wasm-utils/utils"
+	"gitlab.com/xx_network/crypto/csprng"
 )
 
 // Data lengths.
@@ -91,7 +96,7 @@ const (
 func GetOrInitPassword(_ js.Value, args []js.Value) any {
 	internalPassword, err := getOrInit(args[0].String())
 	if err != nil {
-		utils.Throw(utils.TypeError, err)
+		exception.ThrowTrace(err)
 		return nil
 	}
 
@@ -109,7 +114,7 @@ func GetOrInitPassword(_ js.Value, args []js.Value) any {
 func ChangeExternalPassword(_ js.Value, args []js.Value) any {
 	err := changeExternalPassword(args[0].String(), args[1].String())
 	if err != nil {
-		utils.Throw(utils.TypeError, err)
+		exception.ThrowTrace(err)
 		return nil
 	}
 
@@ -130,7 +135,7 @@ func VerifyPassword(_ js.Value, args []js.Value) any {
 // getOrInit is the private function for GetOrInitPassword that is used for
 // testing.
 func getOrInit(externalPassword string) ([]byte, error) {
-	localStorage := GetLocalStorage()
+	localStorage := storage.GetLocalStorage()
 	internalPassword, err := getInternalPassword(externalPassword, localStorage)
 	if err != nil {
 		if errors.Is(err, os.ErrNotExist) {
@@ -148,7 +153,7 @@ func getOrInit(externalPassword string) ([]byte, error) {
 // changeExternalPassword is the private function for ChangeExternalPassword
 // that is used for testing.
 func changeExternalPassword(oldExternalPassword, newExternalPassword string) error {
-	localStorage := GetLocalStorage()
+	localStorage := storage.GetLocalStorage()
 	internalPassword, err := getInternalPassword(
 		oldExternalPassword, localStorage)
 	if err != nil {
@@ -159,13 +164,17 @@ func changeExternalPassword(oldExternalPassword, newExternalPassword string) err
 	if err != nil {
 		return err
 	}
-	localStorage.SetItem(saltKey, salt)
+	if err = localStorage.Set(saltKey, salt); err != nil {
+		return errors.Wrapf(err, "localStorage: failed to set %q", saltKey)
+	}
 
 	key := deriveKey(newExternalPassword, salt, defaultParams())
 
 	encryptedInternalPassword := encryptPassword(
 		internalPassword, key, csprng.NewSystemRNG())
-	localStorage.SetItem(passwordKey, encryptedInternalPassword)
+	if err = localStorage.Set(passwordKey, encryptedInternalPassword); err != nil {
+		return errors.Wrapf(err, "localStorage: failed to set %q", passwordKey)
+	}
 
 	return nil
 }
@@ -173,14 +182,14 @@ func changeExternalPassword(oldExternalPassword, newExternalPassword string) err
 // verifyPassword is the private function for VerifyPassword that is used for
 // testing.
 func verifyPassword(externalPassword string) bool {
-	_, err := getInternalPassword(externalPassword, GetLocalStorage())
+	_, err := getInternalPassword(externalPassword, storage.GetLocalStorage())
 	return err == nil
 }
 
 // initInternalPassword generates a new internal password, stores an encrypted
 // version in local storage, and returns it.
 func initInternalPassword(externalPassword string,
-	localStorage *LocalStorage, csprng io.Reader,
+	localStorage *storage.LocalStorage, csprng io.Reader,
 	params argonParams) ([]byte, error) {
 	internalPassword := make([]byte, internalPasswordLen)
 
@@ -198,19 +207,28 @@ func initInternalPassword(externalPassword string,
 	if err != nil {
 		return nil, err
 	}
-	localStorage.SetItem(saltKey, salt)
+	if err = localStorage.Set(saltKey, salt); err != nil {
+		return nil,
+			errors.Wrapf(err, "localStorage: failed to set %q", saltKey)
+	}
 
 	// Store argon2 parameters
 	paramsData, err := json.Marshal(params)
 	if err != nil {
 		return nil, err
 	}
-	localStorage.SetItem(argonParamsKey, paramsData)
+	if err = localStorage.Set(argonParamsKey, paramsData); err != nil {
+		return nil,
+			errors.Wrapf(err, "localStorage: failed to set %q", argonParamsKey)
+	}
 
 	key := deriveKey(externalPassword, salt, params)
 
 	encryptedInternalPassword := encryptPassword(internalPassword, key, csprng)
-	localStorage.SetItem(passwordKey, encryptedInternalPassword)
+	if err = localStorage.Set(passwordKey, encryptedInternalPassword); err != nil {
+		return nil,
+			errors.Wrapf(err, "localStorage: failed to set %q", passwordKey)
+	}
 
 	return internalPassword, nil
 }
@@ -218,18 +236,18 @@ func initInternalPassword(externalPassword string,
 // getInternalPassword retrieves the internal password from local storage,
 // decrypts it, and returns it.
 func getInternalPassword(
-	externalPassword string, localStorage *LocalStorage) ([]byte, error) {
-	encryptedInternalPassword, err := localStorage.GetItem(passwordKey)
+	externalPassword string, localStorage *storage.LocalStorage) ([]byte, error) {
+	encryptedInternalPassword, err := localStorage.Get(passwordKey)
 	if err != nil {
 		return nil, errors.WithMessage(err, getPasswordStorageErr)
 	}
 
-	salt, err := localStorage.GetItem(saltKey)
+	salt, err := localStorage.Get(saltKey)
 	if err != nil {
 		return nil, errors.WithMessage(err, getSaltStorageErr)
 	}
 
-	paramsData, err := localStorage.GetItem(argonParamsKey)
+	paramsData, err := localStorage.Get(argonParamsKey)
 	if err != nil {
 		return nil, errors.WithMessage(err, getParamsStorageErr)
 	}
diff --git a/storage/password_test.go b/storage/password_test.go
index 24f1ed035197a1856c9f1750d2072609d9feb9c1..0e4c4c64105e93247cf24d1665360fdbd834a687 100644
--- a/storage/password_test.go
+++ b/storage/password_test.go
@@ -14,9 +14,11 @@ import (
 	"crypto/rand"
 	"encoding/base64"
 	"fmt"
-	"gitlab.com/xx_network/crypto/csprng"
 	"strings"
 	"testing"
+
+	"gitlab.com/elixxir/wasm-utils/storage"
+	"gitlab.com/xx_network/crypto/csprng"
 )
 
 // Tests that running getOrInit twice returns the same internal password both
@@ -77,7 +79,7 @@ func Test_changeExternalPassword(t *testing.T) {
 // Tests that verifyPassword returns true for a valid password and false for an
 // invalid password
 func Test_verifyPassword(t *testing.T) {
-	GetLocalStorage().Clear()
+	storage.GetLocalStorage().Clear()
 	externalPassword := "myPassword"
 
 	if _, err := getOrInit(externalPassword); err != nil {
@@ -97,7 +99,7 @@ func Test_verifyPassword(t *testing.T) {
 // the encrypted one saved to local storage.
 func Test_initInternalPassword(t *testing.T) {
 	externalPassword := "myPassword"
-	ls := GetLocalStorage()
+	ls := storage.GetLocalStorage()
 	rng := csprng.NewSystemRNG()
 
 	internalPassword, err := initInternalPassword(
@@ -107,14 +109,14 @@ func Test_initInternalPassword(t *testing.T) {
 	}
 
 	// Attempt to retrieve encrypted internal password from storage
-	encryptedInternalPassword, err := ls.GetItem(passwordKey)
+	encryptedInternalPassword, err := ls.Get(passwordKey)
 	if err != nil {
 		t.Errorf(
 			"Failed to load encrypted internal password from storage: %+v", err)
 	}
 
 	// Attempt to retrieve salt from storage
-	salt, err := ls.GetItem(saltKey)
+	salt, err := ls.Get(saltKey)
 	if err != nil {
 		t.Errorf("Failed to load salt from storage: %+v", err)
 	}
@@ -138,7 +140,7 @@ func Test_initInternalPassword(t *testing.T) {
 // error when read.
 func Test_initInternalPassword_CsprngReadError(t *testing.T) {
 	externalPassword := "myPassword"
-	ls := GetLocalStorage()
+	ls := storage.GetLocalStorage()
 	b := bytes.NewBuffer([]byte{})
 
 	expectedErr := strings.Split(readInternalPasswordErr, "%")[0]
@@ -154,7 +156,7 @@ func Test_initInternalPassword_CsprngReadError(t *testing.T) {
 // return enough bytes.
 func Test_initInternalPassword_CsprngReadNumBytesError(t *testing.T) {
 	externalPassword := "myPassword"
-	ls := GetLocalStorage()
+	ls := storage.GetLocalStorage()
 	b := bytes.NewBuffer(make([]byte, internalPasswordLen/2))
 
 	expectedErr := fmt.Sprintf(
@@ -171,7 +173,7 @@ func Test_initInternalPassword_CsprngReadNumBytesError(t *testing.T) {
 // to local storage by initInternalPassword.
 func Test_getInternalPassword(t *testing.T) {
 	externalPassword := "myPassword"
-	ls := GetLocalStorage()
+	ls := storage.GetLocalStorage()
 	rng := csprng.NewSystemRNG()
 
 	internalPassword, err := initInternalPassword(
@@ -196,7 +198,7 @@ func Test_getInternalPassword(t *testing.T) {
 // loaded from local storage.
 func Test_getInternalPassword_LocalStorageGetPasswordError(t *testing.T) {
 	externalPassword := "myPassword"
-	ls := GetLocalStorage()
+	ls := storage.GetLocalStorage()
 	ls.Clear()
 
 	expectedErr := strings.Split(getPasswordStorageErr, "%")[0]
@@ -212,9 +214,11 @@ func Test_getInternalPassword_LocalStorageGetPasswordError(t *testing.T) {
 // loaded from local storage.
 func Test_getInternalPassword_LocalStorageGetError(t *testing.T) {
 	externalPassword := "myPassword"
-	ls := GetLocalStorage()
+	ls := storage.GetLocalStorage()
 	ls.Clear()
-	ls.SetItem(passwordKey, []byte("password"))
+	if err := ls.Set(passwordKey, []byte("password")); err != nil {
+		t.Fatalf("Failed to set %q: %+v", passwordKey, err)
+	}
 
 	expectedErr := strings.Split(getSaltStorageErr, "%")[0]
 
@@ -229,11 +233,17 @@ func Test_getInternalPassword_LocalStorageGetError(t *testing.T) {
 // decrypted.
 func Test_getInternalPassword_DecryptPasswordError(t *testing.T) {
 	externalPassword := "myPassword"
-	ls := GetLocalStorage()
+	ls := storage.GetLocalStorage()
 	ls.Clear()
-	ls.SetItem(saltKey, []byte("salt"))
-	ls.SetItem(passwordKey, []byte("password"))
-	ls.SetItem(argonParamsKey, []byte(`{"Time": 1, "Memory": 65536, "Threads": 4}`))
+	if err := ls.Set(saltKey, []byte("salt")); err != nil {
+		t.Errorf("failed to set %q: %+v", saltKey, err)
+	}
+	if err := ls.Set(passwordKey, []byte("password")); err != nil {
+		t.Errorf("failed to set %q: %+v", passwordKey, err)
+	}
+	if err := ls.Set(argonParamsKey, []byte(`{"Time": 1, "Memory": 65536, "Threads": 4}`)); err != nil {
+		t.Errorf("failed to set %q: %+v", argonParamsKey, err)
+	}
 
 	expectedErr := strings.Split(decryptPasswordErr, "%")[0]
 
diff --git a/storage/purge.go b/storage/purge.go
index b80481b470863c32dee6cad894858cb081a08abe..9cd7dd4bf69f2ce7cbb131c88b757058db758356 100644
--- a/storage/purge.go
+++ b/storage/purge.go
@@ -10,13 +10,15 @@
 package storage
 
 import (
+	"sync/atomic"
+	"syscall/js"
+
 	"github.com/hack-pad/go-indexeddb/idb"
-	"github.com/pkg/errors"
 	jww "github.com/spf13/jwalterweatherman"
+
 	"gitlab.com/elixxir/client/v4/storage/utility"
-	"gitlab.com/elixxir/xxdk-wasm/utils"
-	"sync/atomic"
-	"syscall/js"
+	"gitlab.com/elixxir/wasm-utils/exception"
+	"gitlab.com/elixxir/wasm-utils/storage"
 )
 
 // numClientsRunning is an atomic that tracks the current number of Cmix
@@ -57,22 +59,21 @@ func Purge(_ js.Value, args []js.Value) any {
 
 	// Check the password
 	if !verifyPassword(userPassword) {
-		utils.Throw(utils.TypeError, errors.New("invalid password"))
+		exception.Throwf("invalid password")
 		return nil
 	}
 
 	// Verify all Cmix followers are stopped
 	if n := atomic.LoadUint64(&numClientsRunning); n != 0 {
-		utils.Throw(utils.TypeError, errors.Errorf(
-			"%d cMix followers running; all need to be stopped", n))
+		exception.Throwf("%d cMix followers running; all need to be stopped", n)
 		return nil
 	}
 
 	// Get all indexedDb database names
 	databaseList, err := GetIndexedDbList()
 	if err != nil {
-		utils.Throw(utils.TypeError, errors.Errorf(
-			"failed to get list of indexedDb database names: %+v", err))
+		exception.Throwf(
+			"failed to get list of indexedDb database names: %+v", err)
 		return nil
 	}
 	jww.DEBUG.Printf("[PURGE] Found %d databases to delete: %s",
@@ -82,26 +83,34 @@ func Purge(_ js.Value, args []js.Value) any {
 	for dbName := range databaseList {
 		_, err = idb.Global().DeleteDatabase(dbName)
 		if err != nil {
-			utils.Throw(utils.TypeError, errors.Errorf(
-				"failed to delete indexedDb database %q: %+v", dbName, err))
+			exception.Throwf(
+				"failed to delete indexedDb database %q: %+v", dbName, err)
 			return nil
 		}
 	}
 
 	// Get local storage
-	ls := GetLocalStorage()
+	ls := storage.GetLocalStorage()
 
 	// Clear all local storage saved by this WASM project
-	n := ls.ClearWASM()
+	n := ls.Clear()
 	jww.DEBUG.Printf("[PURGE] Cleared %d WASM keys in local storage", n)
 
 	// Clear all EKV from local storage
-	n = ls.ClearPrefix(storageDirectory)
+	keys := ls.LocalStorageUNSAFE().KeysPrefix(storageDirectory)
+	n = len(keys)
+	for _, keyName := range keys {
+		ls.LocalStorageUNSAFE().RemoveItem(keyName)
+	}
 	jww.DEBUG.Printf("[PURGE] Cleared %d keys with the prefix %q (for EKV)",
 		n, storageDirectory)
 
 	// Clear all NDFs saved to local storage
-	n = ls.ClearPrefix(utility.NdfStorageKeyNamePrefix)
+	keys = ls.LocalStorageUNSAFE().KeysPrefix(utility.NdfStorageKeyNamePrefix)
+	n = len(keys)
+	for _, keyName := range keys {
+		ls.LocalStorageUNSAFE().RemoveItem(keyName)
+	}
 	jww.DEBUG.Printf("[PURGE] Cleared %d keys with the prefix %q (for NDF)",
 		n, utility.NdfStorageKeyNamePrefix)
 
diff --git a/storage/version.go b/storage/version.go
index b0f192ab24f8ad80d1fb9a9a5c96177f67545440..5fa7e439af29a96c66b22c92b86db42c384c121f 100644
--- a/storage/version.go
+++ b/storage/version.go
@@ -17,6 +17,7 @@ import (
 	jww "github.com/spf13/jwalterweatherman"
 
 	"gitlab.com/elixxir/client/v4/bindings"
+	"gitlab.com/elixxir/wasm-utils/storage"
 )
 
 // SEMVER is the current semantic version of xxDK WASM.
@@ -35,11 +36,11 @@ const (
 // On first load, only the xxDK WASM and xxDK client versions are stored.
 func CheckAndStoreVersions() error {
 	return checkAndStoreVersions(
-		SEMVER, bindings.GetVersion(), GetLocalStorage())
+		SEMVER, bindings.GetVersion(), storage.GetLocalStorage())
 }
 
 func checkAndStoreVersions(
-	currentWasmVer, currentClientVer string, ls *LocalStorage) error {
+	currentWasmVer, currentClientVer string, ls *storage.LocalStorage) error {
 	// Get the stored client version, if it exists
 	storedClientVer, err :=
 		initOrLoadStoredSemver(clientVerKey, currentClientVer, ls)
@@ -76,8 +77,12 @@ func checkAndStoreVersions(
 	// Upgrade path code goes here
 
 	// Save current versions
-	ls.SetItem(clientVerKey, []byte(currentClientVer))
-	ls.SetItem(semverKey, []byte(currentWasmVer))
+	if err = ls.Set(clientVerKey, []byte(currentClientVer)); err != nil {
+		return errors.Wrapf(err, "localStorage: failed to set %q", clientVerKey)
+	}
+	if err = ls.Set(semverKey, []byte(currentWasmVer)); err != nil {
+		return errors.Wrapf(err, "localStorage: failed to set %q", semverKey)
+	}
 
 	return nil
 }
@@ -86,13 +91,16 @@ func checkAndStoreVersions(
 // local storage. If no version is stored, then the current version is stored
 // and returned.
 func initOrLoadStoredSemver(
-	key, currentVersion string, ls *LocalStorage) (string, error) {
-	storedVersion, err := ls.GetItem(key)
+	key, currentVersion string, ls *storage.LocalStorage) (string, error) {
+	storedVersion, err := ls.Get(key)
 	if err != nil {
 		if errors.Is(err, os.ErrNotExist) {
 			// Save the current version if this is the first run
 			jww.INFO.Printf("Initialising %s to v%s", key, currentVersion)
-			ls.SetItem(key, []byte(currentVersion))
+			if err = ls.Set(key, []byte(currentVersion)); err != nil {
+				return "",
+					errors.Wrapf(err, "localStorage: failed to set %q", key)
+			}
 			return currentVersion, nil
 		} else {
 			// If the item exists, but cannot be loaded, return an error
diff --git a/storage/version_test.go b/storage/version_test.go
index a8ead72e52a53d9840936a7deb6865d9aa30f201..27043680fea08beb01c6584557335a308d4c457a 100644
--- a/storage/version_test.go
+++ b/storage/version_test.go
@@ -11,12 +11,14 @@ package storage
 
 import (
 	"testing"
+
+	"gitlab.com/elixxir/wasm-utils/storage"
 )
 
 // Tests that checkAndStoreVersions correct initialises the client and WASM
 // versions on first run and upgrades them correctly on subsequent runs.
 func Test_checkAndStoreVersions(t *testing.T) {
-	ls := GetLocalStorage()
+	ls := storage.GetLocalStorage()
 	ls.Clear()
 	oldWasmVer := "0.1"
 	newWasmVer := "1.0"
@@ -28,7 +30,7 @@ func Test_checkAndStoreVersions(t *testing.T) {
 	}
 
 	// Check client version
-	storedClientVer, err := ls.GetItem(clientVerKey)
+	storedClientVer, err := ls.Get(clientVerKey)
 	if err != nil {
 		t.Errorf("Failed to get client version from storage: %+v", err)
 	}
@@ -38,7 +40,7 @@ func Test_checkAndStoreVersions(t *testing.T) {
 	}
 
 	// Check WASM version
-	storedWasmVer, err := ls.GetItem(semverKey)
+	storedWasmVer, err := ls.Get(semverKey)
 	if err != nil {
 		t.Errorf("Failed to get WASM version from storage: %+v", err)
 	}
@@ -53,7 +55,7 @@ func Test_checkAndStoreVersions(t *testing.T) {
 	}
 
 	// Check client version
-	storedClientVer, err = ls.GetItem(clientVerKey)
+	storedClientVer, err = ls.Get(clientVerKey)
 	if err != nil {
 		t.Errorf("Failed to get client version from storage: %+v", err)
 	}
@@ -63,7 +65,7 @@ func Test_checkAndStoreVersions(t *testing.T) {
 	}
 
 	// Check WASM version
-	storedWasmVer, err = ls.GetItem(semverKey)
+	storedWasmVer, err = ls.Get(semverKey)
 	if err != nil {
 		t.Errorf("Failed to get WASM version from storage: %+v", err)
 	}
@@ -76,7 +78,7 @@ func Test_checkAndStoreVersions(t *testing.T) {
 // Tests that initOrLoadStoredSemver initialises the correct version on first
 // run and returns the same version on subsequent runs.
 func Test_initOrLoadStoredSemver(t *testing.T) {
-	ls := GetLocalStorage()
+	ls := storage.GetLocalStorage()
 	key := "testKey"
 	oldVersion := "0.1"
 
diff --git a/test/wasm_exec.js b/test/wasm_exec.js
index c613dfc656f2799b6b3c922f59003cd3347454a7..07649aee63e71d945a3b558a249faa7240be4416 100644
--- a/test/wasm_exec.js
+++ b/test/wasm_exec.js
@@ -503,7 +503,7 @@
 					},
 
 					// func throw(exception string, message string)
-					'gitlab.com/elixxir/xxdk-wasm/utils.throw': (sp) => {
+					'gitlab.com/elixxir/wasm-utils/utils.throw': (sp) => {
 						const exception = loadString(sp + 8)
 						const message = loadString(sp + 24)
 						throw globalThis[exception](message)
diff --git a/utils/array.go b/utils/array.go
deleted file mode 100644
index 3597d0ba9cca229e1141c164d9e1204cf56c117a..0000000000000000000000000000000000000000
--- a/utils/array.go
+++ /dev/null
@@ -1,71 +0,0 @@
-////////////////////////////////////////////////////////////////////////////////
-// 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 utils
-
-import (
-	"bytes"
-	"encoding/base64"
-	"syscall/js"
-)
-
-// Uint8ArrayToBase64 encodes an uint8 array to a base 64 string.
-//
-// Parameters:
-//   - args[0] - Javascript 8-bit unsigned integer array (Uint8Array).
-//
-// Returns:
-//   - Base 64 encoded string (string).
-func Uint8ArrayToBase64(_ js.Value, args []js.Value) any {
-	return base64.StdEncoding.EncodeToString(CopyBytesToGo(args[0]))
-}
-
-// Base64ToUint8Array decodes a base 64 encoded string to a Uint8Array.
-//
-// Parameters:
-//   - args[0] - Base 64 encoded string (string).
-//
-// Returns:
-//   - Javascript 8-bit unsigned integer array (Uint8Array).
-//   - Throws TypeError if decoding the string fails.
-func Base64ToUint8Array(_ js.Value, args []js.Value) any {
-	b, err := base64ToUint8Array(args[0])
-	if err != nil {
-		Throw(TypeError, err)
-	}
-
-	return b
-}
-
-// base64ToUint8Array is a helper function that returns an error instead of
-// throwing it.
-func base64ToUint8Array(base64String js.Value) (js.Value, error) {
-	b, err := base64.StdEncoding.DecodeString(base64String.String())
-	if err != nil {
-		return js.Value{}, err
-	}
-
-	return CopyBytesToJS(b), nil
-}
-
-// Uint8ArrayEquals returns true if the two Uint8Array are equal and false
-// otherwise.
-//
-// Parameters:
-//   - args[0] - Array A (Uint8Array).
-//   - args[1] - Array B (Uint8Array).
-//
-// Returns:
-//   - If the two arrays are equal (boolean).
-func Uint8ArrayEquals(_ js.Value, args []js.Value) any {
-	a := CopyBytesToGo(args[0])
-	b := CopyBytesToGo(args[1])
-
-	return bytes.Equal(a, b)
-}
diff --git a/utils/array_test.go b/utils/array_test.go
deleted file mode 100644
index 0353d1cce9155cf47e4c8e21132809b6d7d1915c..0000000000000000000000000000000000000000
--- a/utils/array_test.go
+++ /dev/null
@@ -1,114 +0,0 @@
-////////////////////////////////////////////////////////////////////////////////
-// 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 utils
-
-import (
-	"encoding/base64"
-	"fmt"
-	"strings"
-	"syscall/js"
-	"testing"
-)
-
-var testBytes = [][]byte{
-	nil,
-	{},
-	{0},
-	{0, 1, 2, 3},
-	{214, 108, 207, 78, 229, 11, 42, 219, 42, 87, 205, 104, 252, 73, 223,
-		229, 145, 209, 79, 111, 34, 96, 238, 127, 11, 105, 114, 62, 239,
-		130, 145, 82, 3},
-}
-
-// Tests that a series of Uint8Array Javascript objects are correctly converted
-// to base 64 strings with Uint8ArrayToBase64.
-func TestUint8ArrayToBase64(t *testing.T) {
-	for i, val := range testBytes {
-		// Create Uint8Array and set each element individually
-		jsBytes := Uint8Array.New(len(val))
-		for j, v := range val {
-			jsBytes.SetIndex(j, v)
-		}
-
-		jsB64 := Uint8ArrayToBase64(js.Value{}, []js.Value{jsBytes})
-
-		expected := base64.StdEncoding.EncodeToString(val)
-
-		if expected != jsB64 {
-			t.Errorf("Did not receive expected base64 encoded string (%d)."+
-				"\nexpected: %s\nreceived: %s", i, expected, jsB64)
-		}
-	}
-}
-
-// Tests that Base64ToUint8Array correctly decodes a series of base 64 encoded
-// strings into Uint8Array.
-func TestBase64ToUint8Array(t *testing.T) {
-	for i, val := range testBytes {
-		b64 := base64.StdEncoding.EncodeToString(val)
-		jsArr, err := base64ToUint8Array(js.ValueOf(b64))
-		if err != nil {
-			t.Errorf("Failed to convert js.Value to base 64: %+v", err)
-		}
-
-		// Generate the expected string to match the output of toString() on a
-		// Uint8Array
-		expected := strings.ReplaceAll(fmt.Sprintf("%d", val), " ", ",")[1:]
-		expected = expected[:len(expected)-1]
-
-		// Get the string value of the Uint8Array
-		jsString := jsArr.Call("toString").String()
-
-		if expected != jsString {
-			t.Errorf("Failed to recevie expected string representation of "+
-				"the Uint8Array (%d).\nexpected: %s\nreceived: %s",
-				i, expected, jsString)
-		}
-	}
-}
-
-// Tests that a base 64 encoded string decoded to Uint8Array via
-// Base64ToUint8Array and back to a base 64 encoded string via
-// Uint8ArrayToBase64 matches the original.
-func TestBase64ToUint8ArrayUint8ArrayToBase64(t *testing.T) {
-	for i, val := range testBytes {
-		b64 := base64.StdEncoding.EncodeToString(val)
-		jsArr, err := base64ToUint8Array(js.ValueOf(b64))
-		if err != nil {
-			t.Errorf("Failed to convert js.Value to base 64: %+v", err)
-		}
-
-		jsB64 := Uint8ArrayToBase64(js.Value{}, []js.Value{jsArr})
-
-		if b64 != jsB64 {
-			t.Errorf("JSON from Uint8Array does not match original (%d)."+
-				"\nexpected: %s\nreceived: %s", i, b64, jsB64)
-		}
-	}
-}
-
-func TestUint8ArrayEquals(t *testing.T) {
-	for i, val := range testBytes {
-		// Create Uint8Array and set each element individually
-		jsBytesA := Uint8Array.New(len(val))
-		for j, v := range val {
-			jsBytesA.SetIndex(j, v)
-		}
-
-		jsBytesB := CopyBytesToJS(val)
-
-		if !Uint8ArrayEquals(js.Value{}, []js.Value{jsBytesA, jsBytesB}).(bool) {
-			t.Errorf("Two equal byte slices were found to be different (%d)."+
-				"\nexpected: %s\nreceived: %s", i,
-				jsBytesA.Call("toString").String(),
-				jsBytesB.Call("toString").String())
-		}
-	}
-}
diff --git a/utils/convert.go b/utils/convert.go
deleted file mode 100644
index b1f2cd10172bca7e88ff6c77931c754ab1b7f1d8..0000000000000000000000000000000000000000
--- a/utils/convert.go
+++ /dev/null
@@ -1,62 +0,0 @@
-////////////////////////////////////////////////////////////////////////////////
-// 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 utils
-
-import (
-	"encoding/json"
-	"syscall/js"
-)
-
-// CopyBytesToGo copies the [Uint8Array] stored in the [js.Value] to []byte.
-// This is a wrapper for [js.CopyBytesToGo] to make it more convenient.
-func CopyBytesToGo(src js.Value) []byte {
-	b := make([]byte, src.Length())
-	js.CopyBytesToGo(b, src)
-	return b
-}
-
-// CopyBytesToJS copies the []byte to a [Uint8Array] stored in a [js.Value].
-// This is a wrapper for [js.CopyBytesToJS] to make it more convenient.
-func CopyBytesToJS(src []byte) js.Value {
-	dst := Uint8Array.New(len(src))
-	js.CopyBytesToJS(dst, src)
-	return dst
-}
-
-// JsToJson converts the Javascript value to JSON.
-func JsToJson(value js.Value) string {
-	if value.IsUndefined() {
-		return "null"
-	}
-
-	return JSON.Call("stringify", value).String()
-}
-
-// JsonToJS converts a JSON bytes input to a [js.Value] of the object subtype.
-func JsonToJS(inputJson []byte) (js.Value, error) {
-	var jsObj map[string]any
-	err := json.Unmarshal(inputJson, &jsObj)
-	if err != nil {
-		return js.ValueOf(nil), err
-	}
-
-	return js.ValueOf(jsObj), nil
-}
-
-// JsErrorToJson converts the Javascript error to JSON. This should be used for
-// all Javascript error objects instead of JsonToJS.
-func JsErrorToJson(value js.Value) string {
-	if value.IsUndefined() {
-		return "null"
-	}
-
-	properties := Object.Call("getOwnPropertyNames", value)
-	return JSON.Call("stringify", value, properties).String()
-}
diff --git a/utils/convert_test.go b/utils/convert_test.go
deleted file mode 100644
index 508c27f783f1365f28b4b7b78b7389fc4d3ff1a6..0000000000000000000000000000000000000000
--- a/utils/convert_test.go
+++ /dev/null
@@ -1,305 +0,0 @@
-////////////////////////////////////////////////////////////////////////////////
-// 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 utils
-
-import (
-	"encoding/base64"
-	"encoding/json"
-	"sort"
-	"syscall/js"
-	"testing"
-)
-
-import (
-	"bytes"
-	"fmt"
-	"strings"
-)
-
-// Tests that CopyBytesToGo returns a byte slice that matches the Uint8Array.
-func TestCopyBytesToGo(t *testing.T) {
-	for i, val := range testBytes {
-		// Create Uint8Array and set each element individually
-		jsBytes := Uint8Array.New(len(val))
-		for j, v := range val {
-			jsBytes.SetIndex(j, v)
-		}
-
-		goBytes := CopyBytesToGo(jsBytes)
-
-		if !bytes.Equal(val, goBytes) {
-			t.Errorf("Failed to recevie expected bytes from Uint8Array (%d)."+
-				"\nexpected: %d\nreceived: %d",
-				i, val, goBytes)
-		}
-	}
-}
-
-// Tests that CopyBytesToJS returns a Javascript Uint8Array with values matching
-// the original byte slice.
-func TestCopyBytesToJS(t *testing.T) {
-	for i, val := range testBytes {
-		jsBytes := CopyBytesToJS(val)
-
-		// Generate the expected string to match the output of toString() on a
-		// Uint8Array
-		expected := strings.ReplaceAll(fmt.Sprintf("%d", val), " ", ",")[1:]
-		expected = expected[:len(expected)-1]
-
-		// Get the string value of the Uint8Array
-		jsString := jsBytes.Call("toString").String()
-
-		if expected != jsString {
-			t.Errorf("Failed to recevie expected string representation of "+
-				"the Uint8Array (%d).\nexpected: %s\nreceived: %s",
-				i, expected, jsString)
-		}
-	}
-}
-
-// Tests that a byte slice converted to Javascript via CopyBytesToJS and
-// converted back to Go via CopyBytesToGo matches the original.
-func TestCopyBytesToJSCopyBytesToGo(t *testing.T) {
-	for i, val := range testBytes {
-		jsBytes := CopyBytesToJS(val)
-		goBytes := CopyBytesToGo(jsBytes)
-
-		if !bytes.Equal(val, goBytes) {
-			t.Errorf("Failed to recevie expected bytes from Uint8Array (%d)."+
-				"\nexpected: %d\nreceived: %d",
-				i, val, goBytes)
-		}
-	}
-
-}
-
-// Tests that JsToJson can convert a Javascript object to JSON that matches the
-// output of json.Marshal on the Go version of the same object.
-func TestJsToJson(t *testing.T) {
-	testObj := map[string]any{
-		"nil":    nil,
-		"bool":   true,
-		"int":    1,
-		"float":  1.5,
-		"string": "I am string",
-		"array":  []any{1, 2, 3},
-		"object": map[string]any{"int": 5},
-	}
-
-	expected, err := json.Marshal(testObj)
-	if err != nil {
-		t.Errorf("Failed to JSON marshal test object: %+v", err)
-	}
-
-	jsJson := JsToJson(js.ValueOf(testObj))
-
-	// Javascript does not return the JSON object fields sorted so the letters
-	// of each Javascript string are sorted and compared
-	er := []rune(string(expected))
-	sort.SliceStable(er, func(i, j int) bool { return er[i] < er[j] })
-	jj := []rune(jsJson)
-	sort.SliceStable(jj, func(i, j int) bool { return jj[i] < jj[j] })
-
-	if string(er) != string(jj) {
-		t.Errorf("Recieved incorrect JSON from Javascript object."+
-			"\nexpected: %s\nreceived: %s", expected, jsJson)
-	}
-}
-
-// Tests that JsToJson return a null object when the Javascript object is
-// undefined.
-func TestJsToJson_Undefined(t *testing.T) {
-	expected, err := json.Marshal(nil)
-	if err != nil {
-		t.Errorf("Failed to JSON marshal test object: %+v", err)
-	}
-
-	jsJson := JsToJson(js.Undefined())
-
-	if string(expected) != jsJson {
-		t.Errorf("Recieved incorrect JSON from Javascript object."+
-			"\nexpected: %s\nreceived: %s", expected, jsJson)
-	}
-}
-
-// Tests that JsonToJS can convert a JSON object with multiple types to a
-// Javascript object and that all values match.
-func TestJsonToJS(t *testing.T) {
-	testObj := map[string]any{
-		"nil":    nil,
-		"bool":   true,
-		"int":    1,
-		"float":  1.5,
-		"string": "I am string",
-		"bytes":  []byte{1, 2, 3},
-		"array":  []any{1, 2, 3},
-		"object": map[string]any{"int": 5},
-	}
-	jsonData, err := json.Marshal(testObj)
-	if err != nil {
-		t.Errorf("Failed to JSON marshal test object: %+v", err)
-	}
-
-	jsObj, err := JsonToJS(jsonData)
-	if err != nil {
-		t.Errorf("Failed to convert JSON to Javascript object: %+v", err)
-	}
-
-	for key, val := range testObj {
-		jsVal := jsObj.Get(key)
-		switch key {
-		case "nil":
-			if !jsVal.IsNull() {
-				t.Errorf("Key %s is not null.", key)
-			}
-		case "bool":
-			if jsVal.Bool() != val {
-				t.Errorf("Incorrect value for key %s."+
-					"\nexpected: %t\nreceived: %t", key, val, jsVal.Bool())
-			}
-		case "int":
-			if jsVal.Int() != val {
-				t.Errorf("Incorrect value for key %s."+
-					"\nexpected: %d\nreceived: %d", key, val, jsVal.Int())
-			}
-		case "float":
-			if jsVal.Float() != val {
-				t.Errorf("Incorrect value for key %s."+
-					"\nexpected: %f\nreceived: %f", key, val, jsVal.Float())
-			}
-		case "string":
-			if jsVal.String() != val {
-				t.Errorf("Incorrect value for key %s."+
-					"\nexpected: %s\nreceived: %s", key, val, jsVal.String())
-			}
-		case "bytes":
-			if jsVal.String() != base64.StdEncoding.EncodeToString(val.([]byte)) {
-				t.Errorf("Incorrect value for key %s."+
-					"\nexpected: %s\nreceived: %s", key,
-					base64.StdEncoding.EncodeToString(val.([]byte)),
-					jsVal.String())
-			}
-		case "array":
-			for i, v := range val.([]any) {
-				if jsVal.Index(i).Int() != v {
-					t.Errorf("Incorrect value for key %s index %d."+
-						"\nexpected: %d\nreceived: %d",
-						key, i, v, jsVal.Index(i).Int())
-				}
-			}
-		case "object":
-			if jsVal.Get("int").Int() != val.(map[string]any)["int"] {
-				t.Errorf("Incorrect value for key %s."+
-					"\nexpected: %d\nreceived: %d", key,
-					val.(map[string]any)["int"], jsVal.Get("int").Int())
-			}
-		}
-	}
-}
-
-// Tests that JSON can be converted to a Javascript object via JsonToJS and back
-// to JSON using JsToJson and matches the original.
-func TestJsonToJSJsToJson(t *testing.T) {
-	testObj := map[string]any{
-		"nil":    nil,
-		"bool":   true,
-		"int":    1,
-		"float":  1.5,
-		"string": "I am string",
-		"bytes":  []byte{1, 2, 3},
-		"array":  []any{1, 2, 3},
-		"object": map[string]any{"int": 5},
-	}
-	jsonData, err := json.Marshal(testObj)
-	if err != nil {
-		t.Errorf("Failed to JSON marshal test object: %+v", err)
-	}
-
-	jsObj, err := JsonToJS(jsonData)
-	if err != nil {
-		t.Errorf("Failed to convert the Javascript object to JSON: %+v", err)
-	}
-
-	jsJson := JsToJson(jsObj)
-
-	// Javascript does not return the JSON object fields sorted so the letters
-	// of each Javascript string are sorted and compared
-	er := []rune(string(jsonData))
-	sort.SliceStable(er, func(i, j int) bool { return er[i] < er[j] })
-	jj := []rune(jsJson)
-	sort.SliceStable(jj, func(i, j int) bool { return jj[i] < jj[j] })
-
-	if string(er) != string(jj) {
-		t.Errorf("JSON from Javascript does not match original."+
-			"\nexpected: %s\nreceived: %s", jsonData, jsJson)
-	}
-}
-
-// Tests that JsErrorToJson can convert a Javascript object to JSON that matches
-// the output of json.Marshal on the Go version of the same object.
-func TestJsErrorToJson(t *testing.T) {
-	testObj := map[string]any{
-		"nil":    nil,
-		"bool":   true,
-		"int":    1,
-		"float":  1.5,
-		"string": "I am string",
-		"array":  []any{1, 2, 3},
-		"object": map[string]any{"int": 5},
-	}
-
-	expected, err := json.Marshal(testObj)
-	if err != nil {
-		t.Errorf("Failed to JSON marshal test object: %+v", err)
-	}
-
-	jsJson := JsErrorToJson(js.ValueOf(testObj))
-
-	// Javascript does not return the JSON object fields sorted so the letters
-	// of each Javascript string are sorted and compared
-	er := []rune(string(expected))
-	sort.SliceStable(er, func(i, j int) bool { return er[i] < er[j] })
-	jj := []rune(jsJson)
-	sort.SliceStable(jj, func(i, j int) bool { return jj[i] < jj[j] })
-
-	if string(er) != string(jj) {
-		t.Errorf("Recieved incorrect JSON from Javascript object."+
-			"\nexpected: %s\nreceived: %s", expected, jsJson)
-	}
-}
-
-// Tests that JsErrorToJson return a null object when the Javascript object is
-// undefined.
-func TestJsErrorToJson_Undefined(t *testing.T) {
-	expected, err := json.Marshal(nil)
-	if err != nil {
-		t.Errorf("Failed to JSON marshal test object: %+v", err)
-	}
-
-	jsJson := JsErrorToJson(js.Undefined())
-
-	if string(expected) != jsJson {
-		t.Errorf("Recieved incorrect JSON from Javascript object."+
-			"\nexpected: %s\nreceived: %s", expected, jsJson)
-	}
-}
-
-// Tests that JsErrorToJson returns a JSON object containing the original error
-// string.
-func TestJsErrorToJson_ErrorObject(t *testing.T) {
-	expected := "An error"
-	jsErr := Error.New(expected)
-	jsJson := JsErrorToJson(jsErr)
-
-	if !strings.Contains(jsJson, expected) {
-		t.Errorf("Recieved incorrect JSON from Javascript error."+
-			"\nexpected: %s\nreceived: %s", expected, jsJson)
-	}
-}
diff --git a/utils/errors.go b/utils/errors.go
deleted file mode 100644
index 2e1cbacbce3b7c70a67f52e1217a76ef63f887b5..0000000000000000000000000000000000000000
--- a/utils/errors.go
+++ /dev/null
@@ -1,71 +0,0 @@
-////////////////////////////////////////////////////////////////////////////////
-// 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 utils
-
-import (
-	"fmt"
-	"syscall/js"
-)
-
-// JsError converts the error to a Javascript Error.
-func JsError(err error) js.Value {
-	return Error.New(err.Error())
-}
-
-// JsTrace converts the error to a Javascript Error that includes the error's
-// stack trace.
-func JsTrace(err error) js.Value {
-	return Error.New(fmt.Sprintf("%+v", err))
-}
-
-// Throw function stub to throws Javascript exceptions. The exception must be
-// one of the defined Exception below. Any other error types will result in an
-// error.
-func Throw(exception Exception, err error) {
-	throw(exception, fmt.Sprintf("%+v", err))
-}
-
-func throw(exception Exception, message string)
-
-// Exception are the possible Javascript error types that can be thrown.
-type Exception string
-
-const (
-	// EvalError occurs when error has occurred in the eval() function.
-	//
-	// Deprecated: This exception is not thrown by JavaScript anymore, however
-	// the EvalError object remains for compatibility.
-	EvalError Exception = "EvalError"
-
-	// RangeError occurs when a numeric variable or parameter is outside its
-	// valid range.
-	RangeError Exception = "RangeError"
-
-	// ReferenceError occurs when a variable that does not exist (or hasn't yet
-	// been initialized) in the current scope is referenced.
-	ReferenceError Exception = "ReferenceError"
-
-	// SyntaxError occurs when trying to interpret syntactically invalid code.
-	SyntaxError Exception = "SyntaxError"
-
-	// TypeError occurs when an operation could not be performed, typically (but
-	// not exclusively) when a value is not of the expected type.
-	//
-	// A TypeError may be thrown when:
-	//  - an operand or argument passed to a function is incompatible with the
-	//    type expected by that operator or function; or
-	//  - when attempting to modify a value that cannot be changed; or
-	//  - when attempting to use a value in an inappropriate way.
-	TypeError Exception = "TypeError"
-
-	// URIError occurs when a global URI handling function was used in a wrong
-	// way.
-	URIError Exception = "URIError"
-)
diff --git a/utils/errors_test.go b/utils/errors_test.go
deleted file mode 100644
index f9965e34878f0cd507e2839482e1f38201a99055..0000000000000000000000000000000000000000
--- a/utils/errors_test.go
+++ /dev/null
@@ -1,42 +0,0 @@
-////////////////////////////////////////////////////////////////////////////////
-// 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 utils
-
-import (
-	"fmt"
-	"github.com/pkg/errors"
-	"testing"
-)
-
-// Tests that TestJsError returns a Javascript Error object with the expected
-// message.
-func TestJsError(t *testing.T) {
-	err := errors.New("test error")
-	expectedErr := err.Error()
-	jsError := JsError(err).Get("message").String()
-
-	if jsError != expectedErr {
-		t.Errorf("Failed to get expected error message."+
-			"\nexpected: %s\nreceived: %s", expectedErr, jsError)
-	}
-}
-
-// Tests that TestJsTrace returns a Javascript Error object with the expected
-// message and stack trace.
-func TestJsTrace(t *testing.T) {
-	err := errors.New("test error")
-	expectedErr := fmt.Sprintf("%+v", err)
-	jsError := JsTrace(err).Get("message").String()
-
-	if jsError != expectedErr {
-		t.Errorf("Failed to get expected error message."+
-			"\nexpected: %s\nreceived: %s", expectedErr, jsError)
-	}
-}
diff --git a/utils/utils.go b/utils/utils.go
deleted file mode 100644
index 28ea6102a7e8239813cb88fb3028ab7378d182dc..0000000000000000000000000000000000000000
--- a/utils/utils.go
+++ /dev/null
@@ -1,126 +0,0 @@
-////////////////////////////////////////////////////////////////////////////////
-// 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 utils
-
-import (
-	"github.com/pkg/errors"
-	jww "github.com/spf13/jwalterweatherman"
-	"syscall/js"
-)
-
-var (
-	// Error is the Javascript Error type. It used to create new Javascript
-	// errors.
-	Error = js.Global().Get("Error")
-
-	// JSON is the Javascript JSON type. It is used to perform JSON operations
-	// on the Javascript layer.
-	JSON = js.Global().Get("JSON")
-
-	// Object is the Javascript Object type. It is used to perform Object
-	// operations on the Javascript layer.
-	Object = js.Global().Get("Object")
-
-	// Promise is the Javascript Promise type. It is used to generate new
-	// promises.
-	Promise = js.Global().Get("Promise")
-
-	// Uint8Array is the Javascript Uint8Array type. It is used to create new
-	// Uint8Array.
-	Uint8Array = js.Global().Get("Uint8Array")
-)
-
-// WrapCB wraps a Javascript function in an object so that it can be called
-// later with only the arguments and without specifying the function name.
-//
-// Panics if m is not a function.
-func WrapCB(parent js.Value, m string) func(args ...any) js.Value {
-	if parent.Get(m).Type() != js.TypeFunction {
-		// Create the error separate from the print so stack trace is printed
-		err := errors.Errorf("Function %q is not of type %s", m, js.TypeFunction)
-		jww.FATAL.Panicf("%+v", err)
-	}
-
-	return func(args ...any) js.Value { return parent.Call(m, args...) }
-}
-
-// PromiseFn converts the Javascript Promise construct into Go.
-//
-// Call resolve with the return of the function on success. Call reject with an
-// error on failure.
-type PromiseFn func(resolve, reject func(args ...any) js.Value)
-
-// CreatePromise creates a Javascript promise to return the value of a blocking
-// Go function to Javascript.
-func CreatePromise(f PromiseFn) any {
-	// Create handler for promise (this will be a Javascript function)
-	var handler js.Func
-	handler = js.FuncOf(func(this js.Value, args []js.Value) any {
-		// Spawn a new go routine to perform the blocking function
-		go func(resolve, reject js.Value) {
-			f(resolve.Invoke, reject.Invoke)
-			go func() { handler.Release() }()
-		}(args[0], args[1])
-
-		return nil
-	})
-
-	// Create and return the Promise object
-	return Promise.New(handler)
-}
-
-// Await waits on a Javascript value. It blocks until the awaitable successfully
-// resolves to the result or rejects to err.
-//
-// If there is a result, err will be nil and vice versa.
-func Await(awaitable js.Value) (result []js.Value, err []js.Value) {
-	then := make(chan []js.Value)
-	defer close(then)
-	thenFunc := js.FuncOf(func(this js.Value, args []js.Value) any {
-		then <- args
-		return nil
-	})
-	defer thenFunc.Release()
-
-	catch := make(chan []js.Value)
-	defer close(catch)
-	catchFunc := js.FuncOf(func(this js.Value, args []js.Value) any {
-		catch <- args
-		return nil
-	})
-	defer catchFunc.Release()
-
-	awaitable.Call("then", thenFunc).Call("catch", catchFunc)
-
-	select {
-	case result = <-then:
-		return result, nil
-	case err = <-catch:
-		return nil, err
-	}
-}
-
-// RunAndCatch runs the specified function and catches any errors thrown by
-// Javascript. The errors should be of type Error.
-func RunAndCatch(fn func() js.Value) (js.Value, error) {
-	var err error
-	defer func() {
-		if r := recover(); r != nil {
-			switch x := r.(type) {
-			case js.Value:
-				err = js.Error{Value: x}
-			default:
-				panic(r)
-			}
-		}
-	}()
-
-	return fn(), err
-}
diff --git a/utils/utils_js.s b/utils/utils_js.s
deleted file mode 100644
index 45c1668a272247a134e6c85508bf5c09f0d7b3f0..0000000000000000000000000000000000000000
--- a/utils/utils_js.s
+++ /dev/null
@@ -1,6 +0,0 @@
-#include "textflag.h"
-
-// Throw enables throwing of Javascript exceptions.
-TEXT ·throw(SB), NOSPLIT, $0
-  CallImport
-  RET
diff --git a/wasm/authenticatedConnection.go b/wasm/authenticatedConnection.go
index 3308a9be32e8321c7d80c7fe5558c794407050b0..61087341cb62c840f4fc765930b16f193536f6d6 100644
--- a/wasm/authenticatedConnection.go
+++ b/wasm/authenticatedConnection.go
@@ -11,7 +11,8 @@ package wasm
 
 import (
 	"gitlab.com/elixxir/client/v4/bindings"
-	"gitlab.com/elixxir/xxdk-wasm/utils"
+	"gitlab.com/elixxir/wasm-utils/exception"
+	"gitlab.com/elixxir/wasm-utils/utils"
 	"syscall/js"
 )
 
@@ -73,7 +74,7 @@ func (ac *AuthenticatedConnection) SendE2E(_ js.Value, args []js.Value) any {
 	promiseFn := func(resolve, reject func(args ...any) js.Value) {
 		sendReport, err := ac.api.SendE2E(mt, payload)
 		if err != nil {
-			reject(utils.JsTrace(err))
+			reject(exception.NewTrace(err))
 		} else {
 			resolve(utils.CopyBytesToJS(sendReport))
 		}
@@ -114,7 +115,7 @@ func (ac *AuthenticatedConnection) RegisterListener(
 	err := ac.api.RegisterListener(args[0].Int(),
 		&listener{utils.WrapCB(args[1], "Hear"), utils.WrapCB(args[1], "Name")})
 	if err != nil {
-		utils.Throw(utils.TypeError, err)
+		exception.ThrowTrace(err)
 		return nil
 	}
 
@@ -143,7 +144,7 @@ func (c *Cmix) ConnectWithAuthentication(_ js.Value, args []js.Value) any {
 		ac, err := c.api.ConnectWithAuthentication(
 			e2eID, recipientContact, e2eParamsJSON)
 		if err != nil {
-			reject(utils.JsTrace(err))
+			reject(exception.NewTrace(err))
 		} else {
 			resolve(newAuthenticatedConnectionJS(ac))
 		}
diff --git a/wasm/backup.go b/wasm/backup.go
index 9f5e7f6e7b302675ad8a385745dd645473961405..d0f3a885bea45ccef74e8dca1184f644fac99ede 100644
--- a/wasm/backup.go
+++ b/wasm/backup.go
@@ -11,7 +11,8 @@ package wasm
 
 import (
 	"gitlab.com/elixxir/client/v4/bindings"
-	"gitlab.com/elixxir/xxdk-wasm/utils"
+	"gitlab.com/elixxir/wasm-utils/exception"
+	"gitlab.com/elixxir/wasm-utils/utils"
 	"syscall/js"
 )
 
@@ -80,7 +81,7 @@ func NewCmixFromBackup(_ js.Value, args []js.Value) any {
 	report, err := bindings.NewCmixFromBackup(ndfJSON, storageDir,
 		backupPassphrase, sessionPassword, backupFileContents)
 	if err != nil {
-		utils.Throw(utils.TypeError, err)
+		exception.ThrowTrace(err)
 		return nil
 	}
 
@@ -110,7 +111,7 @@ func InitializeBackup(_ js.Value, args []js.Value) any {
 	api, err := bindings.InitializeBackup(
 		args[0].Int(), args[1].Int(), args[2].String(), cb)
 	if err != nil {
-		utils.Throw(utils.TypeError, err)
+		exception.ThrowTrace(err)
 		return nil
 	}
 
@@ -138,7 +139,7 @@ func ResumeBackup(_ js.Value, args []js.Value) any {
 	cb := &updateBackupFunc{utils.WrapCB(args[2], "UpdateBackup")}
 	api, err := bindings.ResumeBackup(args[0].Int(), args[1].Int(), cb)
 	if err != nil {
-		utils.Throw(utils.TypeError, err)
+		exception.ThrowTrace(err)
 		return nil
 	}
 
@@ -153,7 +154,7 @@ func ResumeBackup(_ js.Value, args []js.Value) any {
 func (b *Backup) StopBackup(js.Value, []js.Value) any {
 	err := b.api.StopBackup()
 	if err != nil {
-		utils.Throw(utils.TypeError, err)
+		exception.ThrowTrace(err)
 		return nil
 	}
 
diff --git a/wasm/channels.go b/wasm/channels.go
index 57b54f0b3eb767d5943d0e71d03946a0cf9a5b08..71e27547313f25459d94147e615bb270e6c4e693 100644
--- a/wasm/channels.go
+++ b/wasm/channels.go
@@ -21,7 +21,12 @@ import (
 	channelsDb "gitlab.com/elixxir/xxdk-wasm/indexedDb/worker/channels"
 
 	"gitlab.com/elixxir/client/v4/bindings"
-	"gitlab.com/elixxir/xxdk-wasm/utils"
+	"gitlab.com/elixxir/client/v4/channels"
+	"gitlab.com/elixxir/crypto/message"
+	"gitlab.com/elixxir/wasm-utils/exception"
+	"gitlab.com/elixxir/wasm-utils/utils"
+	channelsDb "gitlab.com/elixxir/xxdk-wasm/indexedDb/worker/channels"
+	"gitlab.com/xx_network/primitives/id"
 )
 
 ////////////////////////////////////////////////////////////////////////////////
@@ -108,7 +113,7 @@ func (cm *ChannelsManager) GetID(js.Value, []js.Value) any {
 func GenerateChannelIdentity(_ js.Value, args []js.Value) any {
 	pi, err := bindings.GenerateChannelIdentity(args[0].Int())
 	if err != nil {
-		utils.Throw(utils.TypeError, err)
+		exception.ThrowTrace(err)
 		return nil
 	}
 
@@ -143,7 +148,7 @@ func ConstructIdentity(_ js.Value, args []js.Value) any {
 	identity, err := bindings.ConstructIdentity(
 		utils.CopyBytesToGo(args[0]), args[1].Int())
 	if err != nil {
-		utils.Throw(utils.TypeError, err)
+		exception.ThrowTrace(err)
 		return nil
 	}
 
@@ -166,7 +171,7 @@ func constructIdentity(_ js.Value, args []js.Value) any {
 	identity, err := bindings.ConstructIdentity(
 		utils.CopyBytesToGo(args[0]), args[1].Int())
 	if err != nil {
-		utils.Throw(utils.TypeError, err)
+		exception.ThrowTrace(err)
 		return nil
 	}
 
@@ -190,7 +195,7 @@ func ImportPrivateIdentity(_ js.Value, args []js.Value) any {
 
 	pi, err := bindings.ImportPrivateIdentity(password, data)
 	if err != nil {
-		utils.Throw(utils.TypeError, err)
+		exception.ThrowTrace(err)
 		return nil
 	}
 
@@ -211,7 +216,7 @@ func GetPublicChannelIdentity(_ js.Value, args []js.Value) any {
 	marshaledPublic := utils.CopyBytesToGo(args[0])
 	pi, err := bindings.GetPublicChannelIdentity(marshaledPublic)
 	if err != nil {
-		utils.Throw(utils.TypeError, err)
+		exception.ThrowTrace(err)
 		return nil
 	}
 
@@ -235,7 +240,7 @@ func GetPublicChannelIdentityFromPrivate(_ js.Value, args []js.Value) any {
 	identity, err :=
 		bindings.GetPublicChannelIdentityFromPrivate(marshaledPrivate)
 	if err != nil {
-		utils.Throw(utils.TypeError, err)
+		exception.ThrowTrace(err)
 		return nil
 	}
 
@@ -275,7 +280,7 @@ func NewChannelsManager(_ js.Value, args []js.Value) any {
 	cm, err := bindings.NewChannelsManager(
 		cmixId, privateIdentity, extensionBuilderIDsJSON, em, nil)
 	if err != nil {
-		utils.Throw(utils.TypeError, err)
+		exception.ThrowTrace(err)
 		return nil
 	}
 
@@ -310,7 +315,7 @@ func LoadChannelsManager(_ js.Value, args []js.Value) any {
 	cm, err := bindings.LoadChannelsManager(args[0].Int(), args[1].String(),
 		em, cUI)
 	if err != nil {
-		utils.Throw(utils.TypeError, err)
+		exception.ThrowTrace(err)
 		return nil
 	}
 
@@ -358,7 +363,7 @@ func NewChannelsManagerWithIndexedDb(_ js.Value, args []js.Value) any {
 
 	cipher, err := bindings.GetChannelDbCipherTrackerFromID(cipherID)
 	if err != nil {
-		utils.Throw(utils.TypeError, err)
+		exception.ThrowTrace(err)
 	}
 
 	return newChannelsManagerWithIndexedDb(cmixID, wasmJsPath, privateIdentity,
@@ -418,7 +423,7 @@ func newChannelsManagerWithIndexedDb(cmixID int, wasmJsPath string,
 		cm, err := bindings.NewChannelsManagerGoEventModel(
 			cmixID, privateIdentity, extensionBuilderIDsJSON, model, channelsCbs)
 		if err != nil {
-			reject(utils.JsTrace(err))
+			reject(exception.NewTrace(err))
 		} else {
 			resolve(newChannelsManagerJS(cm))
 		}
@@ -460,7 +465,7 @@ func LoadChannelsManagerWithIndexedDb(_ js.Value, args []js.Value) any {
 
 	cipher, err := bindings.GetChannelDbCipherTrackerFromID(cipherID)
 	if err != nil {
-		utils.Throw(utils.TypeError, err)
+		exception.ThrowTrace(err)
 	}
 
 	return loadChannelsManagerWithIndexedDb(cmixID, wasmJsPath, storageTag,
@@ -510,7 +515,7 @@ func loadChannelsManagerWithIndexedDb(cmixID int, wasmJsPath, storageTag string,
 		cm, err := bindings.LoadChannelsManagerGoEventModel(
 			cmixID, storageTag, model, nil, channelsCbs)
 		if err != nil {
-			reject(utils.JsTrace(err))
+			reject(exception.NewTrace(err))
 		} else {
 			resolve(newChannelsManagerJS(cm))
 		}
@@ -536,7 +541,7 @@ func loadChannelsManagerWithIndexedDb(cmixID int, wasmJsPath, storageTag string,
 func DecodePublicURL(_ js.Value, args []js.Value) any {
 	c, err := bindings.DecodePublicURL(args[0].String())
 	if err != nil {
-		utils.Throw(utils.TypeError, err)
+		exception.ThrowTrace(err)
 		return nil
 	}
 
@@ -558,7 +563,7 @@ func DecodePublicURL(_ js.Value, args []js.Value) any {
 func DecodePrivateURL(_ js.Value, args []js.Value) any {
 	c, err := bindings.DecodePrivateURL(args[0].String(), args[1].String())
 	if err != nil {
-		utils.Throw(utils.TypeError, err)
+		exception.ThrowTrace(err)
 		return nil
 	}
 
@@ -589,7 +594,7 @@ func DecodePrivateURL(_ js.Value, args []js.Value) any {
 func GetChannelJSON(_ js.Value, args []js.Value) any {
 	c, err := bindings.GetChannelJSON(args[0].String())
 	if err != nil {
-		utils.Throw(utils.TypeError, err)
+		exception.ThrowTrace(err)
 		return nil
 	}
 
@@ -612,7 +617,7 @@ func GetChannelJSON(_ js.Value, args []js.Value) any {
 func GetChannelInfo(_ js.Value, args []js.Value) any {
 	ci, err := bindings.GetChannelInfo(args[0].String())
 	if err != nil {
-		utils.Throw(utils.TypeError, err)
+		exception.ThrowTrace(err)
 		return nil
 	}
 
@@ -660,7 +665,7 @@ func (cm *ChannelsManager) GenerateChannel(_ js.Value, args []js.Value) any {
 		prettyPrint, err :=
 			cm.api.GenerateChannel(name, description, privacyLevel)
 		if err != nil {
-			reject(utils.JsTrace(err))
+			reject(exception.NewTrace(err))
 		} else {
 			resolve(prettyPrint)
 		}
@@ -690,7 +695,7 @@ func (cm *ChannelsManager) JoinChannel(_ js.Value, args []js.Value) any {
 	promiseFn := func(resolve, reject func(args ...any) js.Value) {
 		ci, err := cm.api.JoinChannel(channelPretty)
 		if err != nil {
-			reject(utils.JsTrace(err))
+			reject(exception.NewTrace(err))
 		} else {
 			resolve(utils.CopyBytesToJS(ci))
 		}
@@ -714,7 +719,7 @@ func (cm *ChannelsManager) LeaveChannel(_ js.Value, args []js.Value) any {
 	promiseFn := func(resolve, reject func(args ...any) js.Value) {
 		err := cm.api.LeaveChannel(marshalledChanId)
 		if err != nil {
-			reject(utils.JsTrace(err))
+			reject(exception.NewTrace(err))
 		} else {
 			resolve()
 		}
@@ -739,7 +744,7 @@ func (cm *ChannelsManager) ReplayChannel(_ js.Value, args []js.Value) any {
 
 	err := cm.api.ReplayChannel(marshalledChanId)
 	if err != nil {
-		utils.Throw(utils.TypeError, err)
+		exception.ThrowTrace(err)
 		return nil
 	}
 
@@ -761,7 +766,7 @@ func (cm *ChannelsManager) ReplayChannel(_ js.Value, args []js.Value) any {
 func (cm *ChannelsManager) GetChannels(js.Value, []js.Value) any {
 	channelList, err := cm.api.GetChannels()
 	if err != nil {
-		utils.Throw(utils.TypeError, err)
+		exception.ThrowTrace(err)
 		return nil
 	}
 
@@ -780,7 +785,7 @@ func (cm *ChannelsManager) EnableDirectMessages(_ js.Value, args []js.Value) any
 	marshalledChanId := utils.CopyBytesToGo(args[0])
 	err := cm.api.EnableDirectMessages(marshalledChanId)
 	if err != nil {
-		utils.Throw(utils.TypeError, err)
+		exception.ThrowTrace(err)
 		return nil
 	}
 	return nil
@@ -798,7 +803,7 @@ func (cm *ChannelsManager) DisableDirectMessages(_ js.Value, args []js.Value) an
 	marshalledChanId := utils.CopyBytesToGo(args[0])
 	err := cm.api.DisableDirectMessages(marshalledChanId)
 	if err != nil {
-		utils.Throw(utils.TypeError, err)
+		exception.ThrowTrace(err)
 		return nil
 	}
 	return nil
@@ -816,7 +821,7 @@ func (cm *ChannelsManager) AreDMsEnabled(_ js.Value, args []js.Value) any {
 	marshalledChanId := utils.CopyBytesToGo(args[0])
 	enabled, err := cm.api.AreDMsEnabled(marshalledChanId)
 	if err != nil {
-		utils.Throw(utils.TypeError, err)
+		exception.ThrowTrace(err)
 		return false
 	}
 	return enabled
@@ -868,7 +873,7 @@ func (cm *ChannelsManager) GetShareURL(_ js.Value, args []js.Value) any {
 
 	su, err := cm.api.GetShareURL(cmixID, host, maxUses, marshalledChanId)
 	if err != nil {
-		utils.Throw(utils.TypeError, err)
+		exception.ThrowTrace(err)
 		return nil
 	}
 
@@ -894,7 +899,7 @@ func (cm *ChannelsManager) GetShareURL(_ js.Value, args []js.Value) any {
 func GetShareUrlType(_ js.Value, args []js.Value) any {
 	level, err := bindings.GetShareUrlType(args[0].String())
 	if err != nil {
-		utils.Throw(utils.TypeError, err)
+		exception.ThrowTrace(err)
 		return nil
 	}
 
@@ -954,7 +959,7 @@ func (cm *ChannelsManager) SendGeneric(_ js.Value, args []js.Value) any {
 		sendReport, err := cm.api.SendGeneric(marshalledChanId, messageType,
 			msg, leaseTimeMS, tracked, cmixParamsJSON)
 		if err != nil {
-			reject(utils.JsTrace(err))
+			reject(exception.NewTrace(err))
 		} else {
 			resolve(utils.CopyBytesToJS(sendReport))
 		}
@@ -996,7 +1001,7 @@ func (cm *ChannelsManager) SendMessage(_ js.Value, args []js.Value) any {
 		sendReport, err := cm.api.SendMessage(
 			marshalledChanId, msg, leaseTimeMS, cmixParamsJSON)
 		if err != nil {
-			reject(utils.JsTrace(err))
+			reject(exception.NewTrace(err))
 		} else {
 			resolve(utils.CopyBytesToJS(sendReport))
 		}
@@ -1046,7 +1051,7 @@ func (cm *ChannelsManager) SendReply(_ js.Value, args []js.Value) any {
 		sendReport, err := cm.api.SendReply(marshalledChanId, msg,
 			messageToReactTo, leaseTimeMS, cmixParamsJSON)
 		if err != nil {
-			reject(utils.JsTrace(err))
+			reject(exception.NewTrace(err))
 		} else {
 			resolve(utils.CopyBytesToJS(sendReport))
 		}
@@ -1094,7 +1099,7 @@ func (cm *ChannelsManager) SendReaction(_ js.Value, args []js.Value) any {
 		sendReport, err := cm.api.SendReaction(marshalledChanId, reaction,
 			messageToReactTo, leaseTimeMS, cmixParamsJSON)
 		if err != nil {
-			reject(utils.JsTrace(err))
+			reject(exception.NewTrace(err))
 		} else {
 			resolve(utils.CopyBytesToJS(sendReport))
 		}
@@ -1151,7 +1156,7 @@ func (cm *ChannelsManager) SendAdminGeneric(_ js.Value, args []js.Value) any {
 		sendReport, err := cm.api.SendAdminGeneric(marshalledChanId,
 			messageType, msg, leaseTimeMS, tracked, cmixParamsJSON)
 		if err != nil {
-			reject(utils.JsTrace(err))
+			reject(exception.NewTrace(err))
 		} else {
 			resolve(utils.CopyBytesToJS(sendReport))
 		}
@@ -1190,7 +1195,7 @@ func (cm *ChannelsManager) DeleteMessage(_ js.Value, args []js.Value) any {
 		sendReport, err := cm.api.DeleteMessage(
 			channelIdBytes, targetMessageIdBytes, cmixParamsJSON)
 		if err != nil {
-			reject(utils.JsTrace(err))
+			reject(exception.NewTrace(err))
 		} else {
 			resolve(utils.CopyBytesToJS(sendReport))
 		}
@@ -1232,7 +1237,7 @@ func (cm *ChannelsManager) PinMessage(_ js.Value, args []js.Value) any {
 		sendReport, err := cm.api.PinMessage(channelIdBytes,
 			targetMessageIdBytes, undoAction, validUntilMS, cmixParamsJSON)
 		if err != nil {
-			reject(utils.JsTrace(err))
+			reject(exception.NewTrace(err))
 		} else {
 			resolve(utils.CopyBytesToJS(sendReport))
 		}
@@ -1273,7 +1278,7 @@ func (cm *ChannelsManager) MuteUser(_ js.Value, args []js.Value) any {
 		sendReport, err := cm.api.MuteUser(channelIdBytes, mutedUserPubKeyBytes,
 			undoAction, validUntilMS, cmixParamsJSON)
 		if err != nil {
-			reject(utils.JsTrace(err))
+			reject(exception.NewTrace(err))
 		} else {
 			resolve(utils.CopyBytesToJS(sendReport))
 		}
@@ -1295,7 +1300,7 @@ func (cm *ChannelsManager) MuteUser(_ js.Value, args []js.Value) any {
 func (cm *ChannelsManager) GetIdentity(js.Value, []js.Value) any {
 	i, err := cm.api.GetIdentity()
 	if err != nil {
-		utils.Throw(utils.TypeError, err)
+		exception.ThrowTrace(err)
 		return nil
 	}
 
@@ -1314,7 +1319,7 @@ func (cm *ChannelsManager) GetIdentity(js.Value, []js.Value) any {
 func (cm *ChannelsManager) ExportPrivateIdentity(_ js.Value, args []js.Value) any {
 	i, err := cm.api.ExportPrivateIdentity(args[0].String())
 	if err != nil {
-		utils.Throw(utils.TypeError, err)
+		exception.ThrowTrace(err)
 		return nil
 	}
 
@@ -1343,7 +1348,7 @@ func (cm *ChannelsManager) GetStorageTag(js.Value, []js.Value) any {
 func (cm *ChannelsManager) SetNickname(_ js.Value, args []js.Value) any {
 	err := cm.api.SetNickname(args[0].String(), utils.CopyBytesToGo(args[1]))
 	if err != nil {
-		utils.Throw(utils.TypeError, err)
+		exception.ThrowTrace(err)
 		return nil
 	}
 
@@ -1361,7 +1366,7 @@ func (cm *ChannelsManager) SetNickname(_ js.Value, args []js.Value) any {
 func (cm *ChannelsManager) DeleteNickname(_ js.Value, args []js.Value) any {
 	err := cm.api.DeleteNickname(utils.CopyBytesToGo(args[0]))
 	if err != nil {
-		utils.Throw(utils.TypeError, err)
+		exception.ThrowTrace(err)
 		return nil
 	}
 
@@ -1380,7 +1385,7 @@ func (cm *ChannelsManager) DeleteNickname(_ js.Value, args []js.Value) any {
 func (cm *ChannelsManager) GetNickname(_ js.Value, args []js.Value) any {
 	nickname, err := cm.api.GetNickname(utils.CopyBytesToGo(args[0]))
 	if err != nil {
-		utils.Throw(utils.TypeError, err)
+		exception.ThrowTrace(err)
 		return nil
 	}
 
@@ -1402,7 +1407,7 @@ func (cm *ChannelsManager) GetNickname(_ js.Value, args []js.Value) any {
 func IsNicknameValid(_ js.Value, args []js.Value) any {
 	err := bindings.IsNicknameValid(args[0].String())
 	if err != nil {
-		return utils.JsError(err)
+		return exception.NewError(err)
 	}
 
 	return nil
@@ -1422,7 +1427,7 @@ func (cm *ChannelsManager) Muted(_ js.Value, args []js.Value) any {
 
 	muted, err := cm.api.Muted(channelIDBytes)
 	if err != nil {
-		utils.Throw(utils.TypeError, err)
+		exception.ThrowTrace(err)
 		return nil
 	}
 
@@ -1448,7 +1453,7 @@ func (cm *ChannelsManager) GetMutedUsers(_ js.Value, args []js.Value) any {
 	channelIDBytes := utils.CopyBytesToGo(args[0])
 	mutedUsers, err := cm.api.GetMutedUsers(channelIDBytes)
 	if err != nil {
-		utils.Throw(utils.TypeError, err)
+		exception.ThrowTrace(err)
 		return nil
 	}
 
@@ -1553,7 +1558,7 @@ func GetNotificationReportsForMe(_ js.Value, args []js.Value) any {
 func (cm *ChannelsManager) IsChannelAdmin(_ js.Value, args []js.Value) any {
 	isAdmin, err := cm.api.IsChannelAdmin(utils.CopyBytesToGo(args[0]))
 	if err != nil {
-		utils.Throw(utils.TypeError, err)
+		exception.ThrowTrace(err)
 		return nil
 	}
 
@@ -1590,7 +1595,7 @@ func (cm *ChannelsManager) ExportChannelAdminKey(_ js.Value, args []js.Value) an
 	pk, err := cm.api.ExportChannelAdminKey(
 		utils.CopyBytesToGo(args[0]), args[1].String())
 	if err != nil {
-		utils.Throw(utils.TypeError, err)
+		exception.ThrowTrace(err)
 		return nil
 	}
 	return utils.CopyBytesToJS(pk)
@@ -1624,7 +1629,7 @@ func (cm *ChannelsManager) VerifyChannelAdminKey(_ js.Value, args []js.Value) an
 	valid, err := cm.api.VerifyChannelAdminKey(
 		channelID, encryptionPassword, encryptedPrivKey)
 	if err != nil {
-		utils.Throw(utils.TypeError, err)
+		exception.ThrowTrace(err)
 		return nil
 	}
 
@@ -1657,7 +1662,7 @@ func (cm *ChannelsManager) ImportChannelAdminKey(_ js.Value, args []js.Value) an
 	err := cm.api.ImportChannelAdminKey(
 		channelID, encryptionPassword, encryptedPrivKey)
 	if err != nil {
-		utils.Throw(utils.TypeError, err)
+		exception.ThrowTrace(err)
 		return nil
 	}
 
@@ -1678,7 +1683,7 @@ func (cm *ChannelsManager) ImportChannelAdminKey(_ js.Value, args []js.Value) an
 func (cm *ChannelsManager) DeleteChannelAdminKey(_ js.Value, args []js.Value) any {
 	err := cm.api.DeleteChannelAdminKey(utils.CopyBytesToGo(args[0]))
 	if err != nil {
-		utils.Throw(utils.TypeError, err)
+		exception.ThrowTrace(err)
 		return nil
 	}
 
@@ -1708,7 +1713,7 @@ type channelMessageReceptionCallback struct {
 func (cmrCB *channelMessageReceptionCallback) Callback(
 	receivedChannelMessageReport []byte, err error) int {
 	uuid := cmrCB.callback(
-		utils.CopyBytesToJS(receivedChannelMessageReport), utils.JsTrace(err))
+		utils.CopyBytesToJS(receivedChannelMessageReport), exception.NewTrace(err))
 
 	return uuid.Int()
 }
@@ -1749,7 +1754,7 @@ func (cm *ChannelsManager) RegisterReceiveHandler(_ js.Value, args []js.Value) a
 	err := cm.api.RegisterReceiveHandler(
 		messageType, listenerCb, name, userSpace, adminSpace, mutedSpace)
 	if err != nil {
-		utils.Throw(utils.TypeError, err)
+		exception.ThrowTrace(err)
 		return nil
 	}
 
@@ -1781,7 +1786,7 @@ func GetNoMessageErr(js.Value, []js.Value) any {
 // Returns
 //   - True if the error contains channels.NoMessageErr (boolean).
 func CheckNoMessageErr(_ js.Value, args []js.Value) any {
-	return bindings.CheckNoMessageErr(utils.JsErrorToJson(args[0]))
+	return bindings.CheckNoMessageErr(js.Error{Value: args[0]}.Error())
 }
 
 // eventModelBuilder adheres to the [bindings.EventModelBuilder] interface.
@@ -2176,7 +2181,7 @@ func NewChannelsDatabaseCipher(_ js.Value, args []js.Value) any {
 	cipher, err := bindings.NewChannelsDatabaseCipher(
 		cmixId, password, plaintTextBlockSize)
 	if err != nil {
-		utils.Throw(utils.TypeError, err)
+		exception.ThrowTrace(err)
 		return nil
 	}
 
@@ -2206,7 +2211,7 @@ func (c *ChannelDbCipher) GetID(js.Value, []js.Value) any {
 func (c *ChannelDbCipher) Encrypt(_ js.Value, args []js.Value) any {
 	ciphertext, err := c.api.Encrypt(utils.CopyBytesToGo(args[0]))
 	if err != nil {
-		utils.Throw(utils.TypeError, err)
+		exception.ThrowTrace(err)
 		return nil
 	}
 
@@ -2227,7 +2232,7 @@ func (c *ChannelDbCipher) Encrypt(_ js.Value, args []js.Value) any {
 func (c *ChannelDbCipher) Decrypt(_ js.Value, args []js.Value) any {
 	plaintext, err := c.api.Decrypt(utils.CopyBytesToGo(args[0]))
 	if err != nil {
-		utils.Throw(utils.TypeError, err)
+		exception.ThrowTrace(err)
 		return nil
 	}
 
@@ -2242,7 +2247,7 @@ func (c *ChannelDbCipher) Decrypt(_ js.Value, args []js.Value) any {
 func (c *ChannelDbCipher) MarshalJSON(js.Value, []js.Value) any {
 	data, err := c.api.MarshalJSON()
 	if err != nil {
-		utils.Throw(utils.TypeError, err)
+		exception.ThrowTrace(err)
 		return nil
 	}
 
@@ -2263,7 +2268,7 @@ func (c *ChannelDbCipher) MarshalJSON(js.Value, []js.Value) any {
 func (c *ChannelDbCipher) UnmarshalJSON(_ js.Value, args []js.Value) any {
 	err := c.api.UnmarshalJSON(utils.CopyBytesToGo(args[0]))
 	if err != nil {
-		utils.Throw(utils.TypeError, err)
+		exception.ThrowTrace(err)
 		return nil
 	}
 	return nil
diff --git a/wasm/channelsFileTransfer.go b/wasm/channelsFileTransfer.go
index 5155e4ad4668bef0f3ad52f52efec748e7dbb0ff..2a76200423e1ac2aefb94a90857e8c70dc7777c1 100644
--- a/wasm/channelsFileTransfer.go
+++ b/wasm/channelsFileTransfer.go
@@ -11,7 +11,8 @@ package wasm
 
 import (
 	"gitlab.com/elixxir/client/v4/bindings"
-	"gitlab.com/elixxir/xxdk-wasm/utils"
+	"gitlab.com/elixxir/wasm-utils/exception"
+	"gitlab.com/elixxir/wasm-utils/utils"
 	"syscall/js"
 )
 
@@ -67,7 +68,7 @@ func InitChannelsFileTransfer(_ js.Value, args []js.Value) any {
 	promiseFn := func(resolve, reject func(args ...any) js.Value) {
 		cft, err := bindings.InitChannelsFileTransfer(e2eID, paramsJson)
 		if err != nil {
-			reject(utils.JsTrace(err))
+			reject(exception.NewTrace(err))
 		} else {
 			resolve(newChannelsFileTransferJS(cft))
 		}
@@ -164,7 +165,7 @@ func (cft *ChannelsFileTransfer) Upload(_ js.Value, args []js.Value) any {
 	promiseFn := func(resolve, reject func(args ...any) js.Value) {
 		fileID, err := cft.api.Upload(fileData, retry, progressCB, period)
 		if err != nil {
-			reject(utils.JsTrace(err))
+			reject(exception.NewTrace(err))
 		} else {
 			resolve(utils.CopyBytesToJS(fileID))
 		}
@@ -211,7 +212,7 @@ func (cft *ChannelsFileTransfer) Send(_ js.Value, args []js.Value) any {
 		fileID, err := cft.api.Send(channelIdBytes, fileLinkJSON, fileName,
 			fileType, preview, validUntilMS, cmixParamsJSON)
 		if err != nil {
-			reject(utils.JsTrace(err))
+			reject(exception.NewTrace(err))
 		} else {
 			resolve(utils.CopyBytesToJS(fileID))
 		}
@@ -264,7 +265,7 @@ func (cft *ChannelsFileTransfer) RegisterSentProgressCallback(
 		err := cft.api.RegisterSentProgressCallback(
 			fileIDBytes, progressCB, periodMS)
 		if err != nil {
-			reject(utils.JsTrace(err))
+			reject(exception.NewTrace(err))
 		} else {
 			resolve()
 		}
@@ -305,7 +306,7 @@ func (cft *ChannelsFileTransfer) RetryUpload(_ js.Value, args []js.Value) any {
 	promiseFn := func(resolve, reject func(args ...any) js.Value) {
 		err := cft.api.RetryUpload(fileIDBytes, progressCB, periodMS)
 		if err != nil {
-			reject(utils.JsTrace(err))
+			reject(exception.NewTrace(err))
 		} else {
 			resolve()
 		}
@@ -334,7 +335,7 @@ func (cft *ChannelsFileTransfer) CloseSend(_ js.Value, args []js.Value) any {
 	promiseFn := func(resolve, reject func(args ...any) js.Value) {
 		err := cft.api.CloseSend(fileIDBytes)
 		if err != nil {
-			reject(utils.JsTrace(err))
+			reject(exception.NewTrace(err))
 		} else {
 			resolve()
 		}
@@ -386,7 +387,7 @@ func (cft *ChannelsFileTransfer) Download(_ js.Value, args []js.Value) any {
 	promiseFn := func(resolve, reject func(args ...any) js.Value) {
 		fileID, err := cft.api.Download(fileInfoJSON, progressCB, periodMS)
 		if err != nil {
-			reject(utils.JsTrace(err))
+			reject(exception.NewTrace(err))
 		} else {
 			resolve(utils.CopyBytesToJS(fileID))
 		}
@@ -438,7 +439,7 @@ func (cft *ChannelsFileTransfer) RegisterReceivedProgressCallback(
 		err := cft.api.RegisterReceivedProgressCallback(
 			fileIDBytes, progressCB, periodMS)
 		if err != nil {
-			reject(utils.JsTrace(err))
+			reject(exception.NewTrace(err))
 		} else {
 			resolve()
 		}
@@ -490,7 +491,7 @@ type ftSentCallback struct {
 func (fsc *ftSentCallback) Callback(
 	payload []byte, t *bindings.ChFilePartTracker, err error) {
 	fsc.callback(utils.CopyBytesToJS(payload), newChFilePartTrackerJS(t),
-		utils.JsTrace(err))
+		exception.NewTrace(err))
 }
 
 // ftReceivedCallback wraps Javascript callbacks to adhere to the
@@ -518,7 +519,7 @@ type ftReceivedCallback struct {
 func (frc *ftReceivedCallback) Callback(
 	payload []byte, t *bindings.ChFilePartTracker, err error) {
 	frc.callback(utils.CopyBytesToJS(payload), newChFilePartTrackerJS(t),
-		utils.JsTrace(err))
+		exception.NewTrace(err))
 }
 
 ////////////////////////////////////////////////////////////////////////////////
diff --git a/wasm/channels_test.go b/wasm/channels_test.go
index 028e6cbf305e46bc87f398abca1a93918e1f3669..1b4d9eb555177951ea66e9fb81ed7420418f2b22 100644
--- a/wasm/channels_test.go
+++ b/wasm/channels_test.go
@@ -12,7 +12,7 @@ package wasm
 import (
 	"gitlab.com/elixxir/client/v4/bindings"
 	"gitlab.com/elixxir/crypto/channel"
-	"gitlab.com/elixxir/xxdk-wasm/utils"
+	"gitlab.com/elixxir/wasm-utils/utils"
 	"gitlab.com/xx_network/crypto/csprng"
 	"reflect"
 	"syscall/js"
diff --git a/wasm/cmix.go b/wasm/cmix.go
index c24531a0b128810638b5c15e4e3ce0c618db7817..3b735f4eb477652b80ed9b54b93c5334cfe33c6d 100644
--- a/wasm/cmix.go
+++ b/wasm/cmix.go
@@ -13,7 +13,9 @@ import (
 	"syscall/js"
 
 	"gitlab.com/elixxir/client/v4/bindings"
-	"gitlab.com/elixxir/xxdk-wasm/utils"
+	"gitlab.com/elixxir/wasm-utils/exception"
+	"gitlab.com/elixxir/wasm-utils/utils"
+	"syscall/js"
 )
 
 // Cmix wraps the [bindings.Cmix] object so its methods can be wrapped to be
@@ -100,7 +102,7 @@ func NewCmix(_ js.Value, args []js.Value) any {
 	promiseFn := func(resolve, reject func(args ...any) js.Value) {
 		err := bindings.NewCmix(ndfJSON, storageDir, password, registrationCode)
 		if err != nil {
-			reject(utils.JsTrace(err))
+			reject(exception.NewTrace(err))
 		} else {
 			resolve()
 		}
@@ -135,7 +137,7 @@ func LoadCmix(_ js.Value, args []js.Value) any {
 	promiseFn := func(resolve, reject func(args ...any) js.Value) {
 		net, err := bindings.LoadCmix(storageDir, password, cmixParamsJSON)
 		if err != nil {
-			reject(utils.JsTrace(err))
+			reject(exception.NewTrace(err))
 		} else {
 			resolve(newCmixJS(net))
 		}
@@ -219,7 +221,7 @@ func (c *Cmix) EKVGet(_ js.Value, args []js.Value) any {
 	promiseFn := func(resolve, reject func(args ...any) js.Value) {
 		val, err := c.api.EKVGet(key)
 		if err != nil {
-			reject(utils.JsTrace(err))
+			reject(exception.NewTrace(err))
 		} else {
 			resolve(utils.CopyBytesToJS(val))
 		}
@@ -244,7 +246,7 @@ func (c *Cmix) EKVSet(_ js.Value, args []js.Value) any {
 	promiseFn := func(resolve, reject func(args ...any) js.Value) {
 		err := c.api.EKVSet(key, val)
 		if err != nil {
-			reject(utils.JsTrace(err))
+			reject(exception.NewTrace(err))
 		} else {
 			resolve(nil)
 		}
diff --git a/wasm/collective.go b/wasm/collective.go
index 7afe791218cab6268f39221bb526b3cef0b17014..5b35b3d4b597f0bf79543e1f38cb741c49ee8d24 100644
--- a/wasm/collective.go
+++ b/wasm/collective.go
@@ -13,7 +13,7 @@ import (
 	"syscall/js"
 
 	"gitlab.com/elixxir/client/v4/bindings"
-	"gitlab.com/elixxir/xxdk-wasm/utils"
+	"gitlab.com/elixxir/wasm-utils/utils"
 )
 
 ////////////////////////////////////////////////////////////////////////////////
diff --git a/wasm/connect.go b/wasm/connect.go
index bfb95a6d882f808277a856dfbda9937c3bb3d687..279b98512e9b6ad91b62f0b6428a4d510b8c1f78 100644
--- a/wasm/connect.go
+++ b/wasm/connect.go
@@ -11,7 +11,8 @@ package wasm
 
 import (
 	"gitlab.com/elixxir/client/v4/bindings"
-	"gitlab.com/elixxir/xxdk-wasm/utils"
+	"gitlab.com/elixxir/wasm-utils/exception"
+	"gitlab.com/elixxir/wasm-utils/utils"
 	"syscall/js"
 )
 
@@ -68,7 +69,7 @@ func (c *Cmix) Connect(_ js.Value, args []js.Value) any {
 	promiseFn := func(resolve, reject func(args ...any) js.Value) {
 		api, err := c.api.Connect(e2eID, recipientContact, e2eParamsJSON)
 		if err != nil {
-			reject(utils.JsTrace(err))
+			reject(exception.NewTrace(err))
 		} else {
 			resolve(newConnectJS(api))
 		}
@@ -95,7 +96,7 @@ func (c *Connection) SendE2E(_ js.Value, args []js.Value) any {
 	promiseFn := func(resolve, reject func(args ...any) js.Value) {
 		sendReport, err := c.api.SendE2E(e2eID, payload)
 		if err != nil {
-			reject(utils.JsTrace(err))
+			reject(exception.NewTrace(err))
 		} else {
 			resolve(utils.CopyBytesToJS(sendReport))
 		}
@@ -111,7 +112,7 @@ func (c *Connection) SendE2E(_ js.Value, args []js.Value) any {
 func (c *Connection) Close(js.Value, []js.Value) any {
 	err := c.api.Close()
 	if err != nil {
-		utils.Throw(utils.TypeError, err)
+		exception.ThrowTrace(err)
 		return nil
 	}
 
@@ -158,7 +159,7 @@ func (c *Connection) RegisterListener(_ js.Value, args []js.Value) any {
 	err := c.api.RegisterListener(args[0].Int(),
 		&listener{utils.WrapCB(args[1], "Hear"), utils.WrapCB(args[1], "Name")})
 	if err != nil {
-		utils.Throw(utils.TypeError, err)
+		exception.ThrowTrace(err)
 		return nil
 	}
 
diff --git a/wasm/delivery.go b/wasm/delivery.go
index 327f15223426bf3ac19c4d001806b8e69abdfb9e..43c56a69d20c2da68228d2c701505c8938c86654 100644
--- a/wasm/delivery.go
+++ b/wasm/delivery.go
@@ -11,7 +11,8 @@ package wasm
 
 import (
 	"gitlab.com/elixxir/client/v4/bindings"
-	"gitlab.com/elixxir/xxdk-wasm/utils"
+	"gitlab.com/elixxir/wasm-utils/exception"
+	"gitlab.com/elixxir/wasm-utils/utils"
 	"syscall/js"
 )
 
@@ -87,7 +88,7 @@ func (c *Cmix) WaitForRoundResult(_ js.Value, args []js.Value) any {
 
 	err := c.api.WaitForRoundResult(roundList, mdc, args[2].Int())
 	if err != nil {
-		utils.Throw(utils.TypeError, err)
+		exception.ThrowTrace(err)
 		return nil
 	}
 
diff --git a/wasm/dm.go b/wasm/dm.go
index 50aebb08a1ac9bdbce7ba5d7e53c2f0591a24577..5503799fc7bd48be7ddc465202b6a9b4079c30fb 100644
--- a/wasm/dm.go
+++ b/wasm/dm.go
@@ -20,8 +20,9 @@ import (
 	"gitlab.com/elixxir/client/v4/bindings"
 	"gitlab.com/elixxir/client/v4/dm"
 	"gitlab.com/elixxir/crypto/codename"
+	"gitlab.com/elixxir/wasm-utils/exception"
+	"gitlab.com/elixxir/wasm-utils/utils"
 	indexDB "gitlab.com/elixxir/xxdk-wasm/indexedDb/worker/dm"
-	"gitlab.com/elixxir/xxdk-wasm/utils"
 )
 
 ////////////////////////////////////////////////////////////////////////////////
@@ -92,7 +93,7 @@ func NewDMClient(_ js.Value, args []js.Value) any {
 
 	cm, err := bindings.NewDMClient(args[0].Int(), privateIdentity, em)
 	if err != nil {
-		utils.Throw(utils.TypeError, err)
+		exception.ThrowTrace(err)
 		return nil
 	}
 
@@ -138,7 +139,7 @@ func NewDMClientWithIndexedDb(_ js.Value, args []js.Value) any {
 
 	cipher, err := bindings.GetDMDbCipherTrackerFromID(cipherID)
 	if err != nil {
-		utils.Throw(utils.TypeError, err)
+		exception.ThrowTrace(err)
 	}
 
 	return newDMClientWithIndexedDb(
@@ -196,19 +197,19 @@ func newDMClientWithIndexedDb(cmixID int, wasmJsPath string,
 
 		pi, err := codename.UnmarshalPrivateIdentity(privateIdentity)
 		if err != nil {
-			reject(utils.JsTrace(err))
+			reject(exception.NewTrace(err))
 		}
 		dmPath := base64.RawStdEncoding.EncodeToString(pi.PubKey[:])
 		model, err := indexDB.NewWASMEventModel(
 			dmPath, wasmJsPath, cipher, messageReceivedCB)
 		if err != nil {
-			reject(utils.JsTrace(err))
+			reject(exception.NewTrace(err))
 		}
 
 		cm, err := bindings.NewDMClientWithGoEventModel(
 			cmixID, privateIdentity, model)
 		if err != nil {
-			reject(utils.JsTrace(err))
+			reject(exception.NewTrace(err))
 		} else {
 			resolve(newDMClientJS(cm))
 		}
@@ -259,7 +260,7 @@ func (dmc *DMClient) GetIdentity(js.Value, []js.Value) any {
 func (dmc *DMClient) ExportPrivateIdentity(_ js.Value, args []js.Value) any {
 	i, err := dmc.api.ExportPrivateIdentity(args[0].String())
 	if err != nil {
-		utils.Throw(utils.TypeError, err)
+		exception.ThrowTrace(err)
 		return nil
 	}
 
@@ -275,7 +276,7 @@ func (dmc *DMClient) ExportPrivateIdentity(_ js.Value, args []js.Value) any {
 func (dmc *DMClient) GetNickname(_ js.Value, _ []js.Value) any {
 	nickname, err := dmc.api.GetNickname()
 	if err != nil {
-		utils.Throw(utils.TypeError, err)
+		exception.ThrowTrace(err)
 		return nil
 	}
 
@@ -359,7 +360,7 @@ func (dmc *DMClient) SendText(_ js.Value, args []js.Value) any {
 		sendReport, err := dmc.api.SendText(partnerPubKeyBytes, partnerToken,
 			message, leaseTimeMS, cmixParamsJSON)
 		if err != nil {
-			reject(utils.JsTrace(err))
+			reject(exception.NewTrace(err))
 		} else {
 			resolve(utils.CopyBytesToJS(sendReport))
 		}
@@ -417,7 +418,7 @@ func (dmc *DMClient) SendReply(_ js.Value, args []js.Value) any {
 		sendReport, err := dmc.api.SendReply(partnerPubKeyBytes, partnerToken,
 			replyMessage, replyToBytes, leaseTimeMS, cmixParamsJSON)
 		if err != nil {
-			reject(utils.JsTrace(err))
+			reject(exception.NewTrace(err))
 		} else {
 			resolve(utils.CopyBytesToJS(sendReport))
 		}
@@ -464,7 +465,7 @@ func (dmc *DMClient) SendReaction(_ js.Value, args []js.Value) any {
 		sendReport, err := dmc.api.SendReaction(partnerPubKeyBytes,
 			partnerToken, reaction, reactToBytes, cmixParamsJSON)
 		if err != nil {
-			reject(utils.JsTrace(err))
+			reject(exception.NewTrace(err))
 		} else {
 			resolve(utils.CopyBytesToJS(sendReport))
 		}
@@ -515,7 +516,7 @@ func (dmc *DMClient) Send(_ js.Value, args []js.Value) any {
 		sendReport, err := dmc.api.Send(partnerPubKeyBytes, partnerToken,
 			messageType, plaintext, leaseTimeMS, cmixParamsJSON)
 		if err != nil {
-			reject(utils.JsTrace(err))
+			reject(exception.NewTrace(err))
 		} else {
 			resolve(utils.CopyBytesToJS(sendReport))
 		}
@@ -578,7 +579,7 @@ func (dmc *DMClient) GetShareURL(_ js.Value, args []js.Value) any {
 	host := args[0].String()
 	urlReport, err := dmc.api.GetShareURL(host)
 	if err != nil {
-		utils.Throw(utils.TypeError, err)
+		exception.ThrowTrace(err)
 		return nil
 	}
 
@@ -598,7 +599,7 @@ func DecodeDMShareURL(_ js.Value, args []js.Value) any {
 	url := args[0].String()
 	report, err := bindings.DecodeDMShareURL(url)
 	if err != nil {
-		utils.Throw(utils.TypeError, err)
+		exception.ThrowTrace(err)
 		return nil
 	}
 
@@ -629,7 +630,7 @@ func (cmrCB *dmReceptionCallback) Callback(
 	receivedChannelMessageReport []byte, err error) int {
 	uuid := cmrCB.callback(
 		utils.CopyBytesToJS(receivedChannelMessageReport),
-		utils.JsTrace(err))
+		exception.NewTrace(err))
 
 	return uuid.Int()
 }
@@ -970,7 +971,7 @@ func NewDMsDatabaseCipher(_ js.Value, args []js.Value) any {
 	cipher, err := bindings.NewDMsDatabaseCipher(
 		cmixId, password, plaintTextBlockSize)
 	if err != nil {
-		utils.Throw(utils.TypeError, err)
+		exception.ThrowTrace(err)
 		return nil
 	}
 
@@ -1000,7 +1001,7 @@ func (c *DMDbCipher) GetID(js.Value, []js.Value) any {
 func (c *DMDbCipher) Encrypt(_ js.Value, args []js.Value) any {
 	ciphertext, err := c.api.Encrypt(utils.CopyBytesToGo(args[0]))
 	if err != nil {
-		utils.Throw(utils.TypeError, err)
+		exception.ThrowTrace(err)
 		return nil
 	}
 
@@ -1021,7 +1022,7 @@ func (c *DMDbCipher) Encrypt(_ js.Value, args []js.Value) any {
 func (c *DMDbCipher) Decrypt(_ js.Value, args []js.Value) any {
 	plaintext, err := c.api.Decrypt(utils.CopyBytesToGo(args[0]))
 	if err != nil {
-		utils.Throw(utils.TypeError, err)
+		exception.ThrowTrace(err)
 		return nil
 	}
 
@@ -1036,7 +1037,7 @@ func (c *DMDbCipher) Decrypt(_ js.Value, args []js.Value) any {
 func (c *DMDbCipher) MarshalJSON(js.Value, []js.Value) any {
 	data, err := c.api.MarshalJSON()
 	if err != nil {
-		utils.Throw(utils.TypeError, err)
+		exception.ThrowTrace(err)
 		return nil
 	}
 
@@ -1058,7 +1059,7 @@ func (c *DMDbCipher) MarshalJSON(js.Value, []js.Value) any {
 func (c *DMDbCipher) UnmarshalJSON(_ js.Value, args []js.Value) any {
 	err := c.api.UnmarshalJSON(utils.CopyBytesToGo(args[0]))
 	if err != nil {
-		utils.Throw(utils.TypeError, err)
+		exception.ThrowTrace(err)
 		return nil
 	}
 	return nil
diff --git a/wasm/dummy.go b/wasm/dummy.go
index 82b9128ce52cb3521056da6a77358bb44a4a5e5c..b0fa6a82bad4f8e28290663eff285d002f10cafb 100644
--- a/wasm/dummy.go
+++ b/wasm/dummy.go
@@ -11,7 +11,7 @@ package wasm
 
 import (
 	"gitlab.com/elixxir/client/v4/bindings"
-	"gitlab.com/elixxir/xxdk-wasm/utils"
+	"gitlab.com/elixxir/wasm-utils/exception"
 	"syscall/js"
 )
 
@@ -58,7 +58,7 @@ func NewDummyTrafficManager(_ js.Value, args []js.Value) any {
 	dt, err := bindings.NewDummyTrafficManager(
 		args[0].Int(), args[1].Int(), args[2].Int(), args[3].Int())
 	if err != nil {
-		utils.Throw(utils.TypeError, err)
+		exception.ThrowTrace(err)
 		return nil
 	}
 
@@ -80,7 +80,7 @@ func NewDummyTrafficManager(_ js.Value, args []js.Value) any {
 func (dt *DummyTraffic) Pause(js.Value, []js.Value) any {
 	err := dt.api.Pause()
 	if err != nil {
-		utils.Throw(utils.TypeError, err)
+		exception.ThrowTrace(err)
 		return nil
 	}
 
@@ -103,7 +103,7 @@ func (dt *DummyTraffic) Pause(js.Value, []js.Value) any {
 func (dt *DummyTraffic) Start(js.Value, []js.Value) any {
 	err := dt.api.Start()
 	if err != nil {
-		utils.Throw(utils.TypeError, err)
+		exception.ThrowTrace(err)
 		return nil
 	}
 
diff --git a/wasm/e2e.go b/wasm/e2e.go
index 8d8f4ce15fa0c47f71c3bbbeccdf9484a4ccde39..5f3debbca3bb52f2b5e594942eaed3c96bcabd5a 100644
--- a/wasm/e2e.go
+++ b/wasm/e2e.go
@@ -11,7 +11,8 @@ package wasm
 
 import (
 	"gitlab.com/elixxir/client/v4/bindings"
-	"gitlab.com/elixxir/xxdk-wasm/utils"
+	"gitlab.com/elixxir/wasm-utils/exception"
+	"gitlab.com/elixxir/wasm-utils/utils"
 	"syscall/js"
 )
 
@@ -99,7 +100,7 @@ func Login(_ js.Value, args []js.Value) any {
 	newE2E, err := bindings.Login(
 		args[0].Int(), callbacks, identity, e2eParamsJSON)
 	if err != nil {
-		utils.Throw(utils.TypeError, err)
+		exception.ThrowTrace(err)
 		return nil
 	}
 
@@ -129,7 +130,7 @@ func LoginEphemeral(_ js.Value, args []js.Value) any {
 	newE2E, err := bindings.LoginEphemeral(
 		args[0].Int(), callbacks, identity, e2eParamsJSON)
 	if err != nil {
-		utils.Throw(utils.TypeError, err)
+		exception.ThrowTrace(err)
 		return nil
 	}
 
@@ -171,7 +172,7 @@ func (e *E2e) GetUdCertFromNdf(js.Value, []js.Value) any {
 func (e *E2e) GetUdContactFromNdf(js.Value, []js.Value) any {
 	b, err := e.api.GetUdContactFromNdf()
 	if err != nil {
-		utils.Throw(utils.TypeError, err)
+		exception.ThrowTrace(err)
 		return nil
 	}
 
diff --git a/wasm/e2eAuth.go b/wasm/e2eAuth.go
index eb69ef75cd17d26b22715d0eba08bf6b5f0fc02a..351ed123db2b69c315ceb61bcd5885146149e3dd 100644
--- a/wasm/e2eAuth.go
+++ b/wasm/e2eAuth.go
@@ -10,7 +10,8 @@
 package wasm
 
 import (
-	"gitlab.com/elixxir/xxdk-wasm/utils"
+	"gitlab.com/elixxir/wasm-utils/exception"
+	"gitlab.com/elixxir/wasm-utils/utils"
 	"syscall/js"
 )
 
@@ -46,7 +47,7 @@ func (e *E2e) Request(_ js.Value, args []js.Value) any {
 	promiseFn := func(resolve, reject func(args ...any) js.Value) {
 		rid, err := e.api.Request(partnerContact, factsListJson)
 		if err != nil {
-			reject(utils.JsTrace(err))
+			reject(exception.NewTrace(err))
 		} else {
 			resolve(rid)
 		}
@@ -83,7 +84,7 @@ func (e *E2e) Confirm(_ js.Value, args []js.Value) any {
 	promiseFn := func(resolve, reject func(args ...any) js.Value) {
 		rid, err := e.api.Confirm(partnerContact)
 		if err != nil {
-			reject(utils.JsTrace(err))
+			reject(exception.NewTrace(err))
 		} else {
 			resolve(rid)
 		}
@@ -118,7 +119,7 @@ func (e *E2e) Reset(_ js.Value, args []js.Value) any {
 	promiseFn := func(resolve, reject func(args ...any) js.Value) {
 		rid, err := e.api.Reset(partnerContact)
 		if err != nil {
-			reject(utils.JsTrace(err))
+			reject(exception.NewTrace(err))
 		} else {
 			resolve(rid)
 		}
@@ -147,7 +148,7 @@ func (e *E2e) ReplayConfirm(_ js.Value, args []js.Value) any {
 	promiseFn := func(resolve, reject func(args ...any) js.Value) {
 		rid, err := e.api.ReplayConfirm(partnerContact)
 		if err != nil {
-			reject(utils.JsTrace(err))
+			reject(exception.NewTrace(err))
 		} else {
 			resolve(rid)
 		}
@@ -174,7 +175,7 @@ func (e *E2e) DeleteRequest(_ js.Value, args []js.Value) any {
 	partnerContact := utils.CopyBytesToGo(args[0])
 	err := e.api.DeleteRequest(partnerContact)
 	if err != nil {
-		utils.Throw(utils.TypeError, err)
+		exception.ThrowTrace(err)
 		return nil
 	}
 
@@ -188,7 +189,7 @@ func (e *E2e) DeleteRequest(_ js.Value, args []js.Value) any {
 func (e *E2e) DeleteAllRequests(js.Value, []js.Value) any {
 	err := e.api.DeleteAllRequests()
 	if err != nil {
-		utils.Throw(utils.TypeError, err)
+		exception.ThrowTrace(err)
 		return nil
 	}
 
@@ -202,7 +203,7 @@ func (e *E2e) DeleteAllRequests(js.Value, []js.Value) any {
 func (e *E2e) DeleteSentRequests(js.Value, []js.Value) any {
 	err := e.api.DeleteSentRequests()
 	if err != nil {
-		utils.Throw(utils.TypeError, err)
+		exception.ThrowTrace(err)
 		return nil
 	}
 
@@ -216,7 +217,7 @@ func (e *E2e) DeleteSentRequests(js.Value, []js.Value) any {
 func (e *E2e) DeleteReceiveRequests(js.Value, []js.Value) any {
 	err := e.api.DeleteReceiveRequests()
 	if err != nil {
-		utils.Throw(utils.TypeError, err)
+		exception.ThrowTrace(err)
 		return nil
 	}
 
@@ -235,7 +236,7 @@ func (e *E2e) GetReceivedRequest(_ js.Value, args []js.Value) any {
 	partnerContact := utils.CopyBytesToGo(args[0])
 	c, err := e.api.GetReceivedRequest(partnerContact)
 	if err != nil {
-		utils.Throw(utils.TypeError, err)
+		exception.ThrowTrace(err)
 		return nil
 	}
 
@@ -260,7 +261,7 @@ func (e *E2e) VerifyOwnership(_ js.Value, args []js.Value) any {
 	isValid, err := e.api.VerifyOwnership(
 		receivedContact, verifiedContact, args[2].Int())
 	if err != nil {
-		utils.Throw(utils.TypeError, err)
+		exception.ThrowTrace(err)
 		return nil
 	}
 
@@ -282,7 +283,7 @@ func (e *E2e) AddPartnerCallback(_ js.Value, args []js.Value) any {
 	callbacks := newAuthCallbacks(args[1])
 	err := e.api.AddPartnerCallback(partnerID, callbacks)
 	if err != nil {
-		utils.Throw(utils.TypeError, err)
+		exception.ThrowTrace(err)
 		return nil
 	}
 
@@ -301,7 +302,7 @@ func (e *E2e) DeletePartnerCallback(_ js.Value, args []js.Value) any {
 	partnerID := utils.CopyBytesToGo(args[0])
 	err := e.api.DeletePartnerCallback(partnerID)
 	if err != nil {
-		utils.Throw(utils.TypeError, err)
+		exception.ThrowTrace(err)
 		return nil
 	}
 
diff --git a/wasm/e2eHandler.go b/wasm/e2eHandler.go
index bff44e6aa48567e5c6afae5dca439b6fa93a6fa1..7f3b5966e886b7e77752da24a03f64ac369b01be 100644
--- a/wasm/e2eHandler.go
+++ b/wasm/e2eHandler.go
@@ -10,7 +10,8 @@
 package wasm
 
 import (
-	"gitlab.com/elixxir/xxdk-wasm/utils"
+	"gitlab.com/elixxir/wasm-utils/exception"
+	"gitlab.com/elixxir/wasm-utils/utils"
 	"syscall/js"
 )
 
@@ -32,7 +33,7 @@ func (e *E2e) GetReceptionID(js.Value, []js.Value) any {
 func (e *E2e) DeleteContact(_ js.Value, args []js.Value) any {
 	err := e.api.DeleteContact(utils.CopyBytesToGo(args[0]))
 	if err != nil {
-		utils.Throw(utils.TypeError, err)
+		exception.ThrowTrace(err)
 		return nil
 	}
 	return nil
@@ -47,7 +48,7 @@ func (e *E2e) DeleteContact(_ js.Value, args []js.Value) any {
 func (e *E2e) GetAllPartnerIDs(js.Value, []js.Value) any {
 	partnerIDs, err := e.api.GetAllPartnerIDs()
 	if err != nil {
-		utils.Throw(utils.TypeError, err)
+		exception.ThrowTrace(err)
 		return nil
 	}
 	return utils.CopyBytesToJS(partnerIDs)
@@ -100,7 +101,7 @@ func (e *E2e) FirstPartitionSize(js.Value, []js.Value) any {
 func (e *E2e) GetHistoricalDHPrivkey(js.Value, []js.Value) any {
 	privKey, err := e.api.GetHistoricalDHPrivkey()
 	if err != nil {
-		utils.Throw(utils.TypeError, err)
+		exception.ThrowTrace(err)
 		return nil
 	}
 	return utils.CopyBytesToJS(privKey)
@@ -115,7 +116,7 @@ func (e *E2e) GetHistoricalDHPrivkey(js.Value, []js.Value) any {
 func (e *E2e) GetHistoricalDHPubkey(js.Value, []js.Value) any {
 	pubKey, err := e.api.GetHistoricalDHPubkey()
 	if err != nil {
-		utils.Throw(utils.TypeError, err)
+		exception.ThrowTrace(err)
 		return nil
 	}
 	return utils.CopyBytesToJS(pubKey)
@@ -133,7 +134,7 @@ func (e *E2e) GetHistoricalDHPubkey(js.Value, []js.Value) any {
 func (e *E2e) HasAuthenticatedChannel(_ js.Value, args []js.Value) any {
 	exists, err := e.api.HasAuthenticatedChannel(utils.CopyBytesToGo(args[0]))
 	if err != nil {
-		utils.Throw(utils.TypeError, err)
+		exception.ThrowTrace(err)
 		return nil
 	}
 	return exists
@@ -149,7 +150,7 @@ func (e *E2e) HasAuthenticatedChannel(_ js.Value, args []js.Value) any {
 func (e *E2e) RemoveService(_ js.Value, args []js.Value) any {
 	err := e.api.RemoveService(args[0].String())
 	if err != nil {
-		utils.Throw(utils.TypeError, err)
+		exception.ThrowTrace(err)
 		return nil
 	}
 
@@ -178,7 +179,7 @@ func (e *E2e) SendE2E(_ js.Value, args []js.Value) any {
 	promiseFn := func(resolve, reject func(args ...any) js.Value) {
 		sendReport, err := e.api.SendE2E(mt, recipientId, payload, e2eParams)
 		if err != nil {
-			reject(utils.JsTrace(err))
+			reject(exception.NewTrace(err))
 		} else {
 			resolve(utils.CopyBytesToJS(sendReport))
 		}
@@ -236,7 +237,7 @@ func (e *E2e) AddService(_ js.Value, args []js.Value) any {
 
 	err := e.api.AddService(args[0].String(), p)
 	if err != nil {
-		utils.Throw(utils.TypeError, err)
+		exception.ThrowTrace(err)
 		return nil
 	}
 
@@ -261,7 +262,7 @@ func (e *E2e) RegisterListener(_ js.Value, args []js.Value) any {
 
 	err := e.api.RegisterListener(recipientId, args[1].Int(), l)
 	if err != nil {
-		utils.Throw(utils.TypeError, err)
+		exception.ThrowTrace(err)
 		return nil
 	}
 
diff --git a/wasm/emoji.go b/wasm/emoji.go
index fbde2b5717a4c745fc7f7339d96f157703138cad..fd64418cdc7c14ac823c3115178d835efc88faf2 100644
--- a/wasm/emoji.go
+++ b/wasm/emoji.go
@@ -13,7 +13,8 @@ import (
 	"syscall/js"
 
 	"gitlab.com/elixxir/client/v4/bindings"
-	"gitlab.com/elixxir/xxdk-wasm/utils"
+	"gitlab.com/elixxir/wasm-utils/exception"
+	"gitlab.com/elixxir/wasm-utils/utils"
 )
 
 // SupportedEmojis returns a list of emojis that are supported by the backend.
@@ -56,7 +57,7 @@ import (
 func SupportedEmojis(js.Value, []js.Value) any {
 	data, err := bindings.SupportedEmojis()
 	if err != nil {
-		utils.Throw(utils.TypeError, err)
+		exception.ThrowTrace(err)
 		return nil
 	}
 
@@ -101,7 +102,7 @@ func SupportedEmojis(js.Value, []js.Value) any {
 func SupportedEmojisMap(js.Value, []js.Value) any {
 	data, err := bindings.SupportedEmojisMap()
 	if err != nil {
-		utils.Throw(utils.TypeError, err)
+		exception.ThrowTrace(err)
 		return nil
 	}
 
@@ -121,7 +122,7 @@ func SupportedEmojisMap(js.Value, []js.Value) any {
 func ValidateReaction(_ js.Value, args []js.Value) any {
 	err := bindings.ValidateReaction(args[0].String())
 	if err != nil {
-		return utils.JsError(err)
+		return exception.NewError(err)
 	}
 
 	return nil
diff --git a/wasm/errors.go b/wasm/errors.go
index 9299c60e0d9c02e81a1ea54755c26d7be62b87bf..baf9e8b095afed9b291bdfc9c793eaf3d70bea7f 100644
--- a/wasm/errors.go
+++ b/wasm/errors.go
@@ -11,7 +11,7 @@ package wasm
 
 import (
 	"gitlab.com/elixxir/client/v4/bindings"
-	"gitlab.com/elixxir/xxdk-wasm/utils"
+	"gitlab.com/elixxir/wasm-utils/exception"
 	"syscall/js"
 )
 
@@ -53,7 +53,7 @@ func CreateUserFriendlyErrorMessage(_ js.Value, args []js.Value) any {
 func UpdateCommonErrors(_ js.Value, args []js.Value) any {
 	err := bindings.UpdateCommonErrors(args[0].String())
 	if err != nil {
-		utils.Throw(utils.TypeError, err)
+		exception.ThrowTrace(err)
 		return nil
 	}
 
diff --git a/wasm/fileTransfer.go b/wasm/fileTransfer.go
index 0083a98a78b2544f90b96623b88157b9dd70acb2..e91a8837cec438f4644e48407fdf2924a81eb1bc 100644
--- a/wasm/fileTransfer.go
+++ b/wasm/fileTransfer.go
@@ -11,7 +11,8 @@ package wasm
 
 import (
 	"gitlab.com/elixxir/client/v4/bindings"
-	"gitlab.com/elixxir/xxdk-wasm/utils"
+	"gitlab.com/elixxir/wasm-utils/exception"
+	"gitlab.com/elixxir/wasm-utils/utils"
 	"syscall/js"
 )
 
@@ -84,7 +85,7 @@ type fileTransferSentProgressCallback struct {
 func (spc *fileTransferSentProgressCallback) Callback(
 	payload []byte, t *bindings.FilePartTracker, err error) {
 	spc.callback(utils.CopyBytesToJS(payload), newFilePartTrackerJS(t),
-		utils.JsTrace(err))
+		exception.NewTrace(err))
 }
 
 // fileTransferReceiveProgressCallback wraps Javascript callbacks to adhere to
@@ -105,7 +106,7 @@ type fileTransferReceiveProgressCallback struct {
 func (rpc *fileTransferReceiveProgressCallback) Callback(
 	payload []byte, t *bindings.FilePartTracker, err error) {
 	rpc.callback(utils.CopyBytesToJS(payload), newFilePartTrackerJS(t),
-		utils.JsTrace(err))
+		exception.NewTrace(err))
 }
 
 ////////////////////////////////////////////////////////////////////////////////
@@ -133,7 +134,7 @@ func InitFileTransfer(_ js.Value, args []js.Value) any {
 	api, err := bindings.InitFileTransfer(
 		args[0].Int(), rfc, e2eFileTransferParamsJson, fileTransferParamsJson)
 	if err != nil {
-		utils.Throw(utils.TypeError, err)
+		exception.ThrowTrace(err)
 		return nil
 	}
 
@@ -163,7 +164,7 @@ func (f *FileTransfer) Send(_ js.Value, args []js.Value) any {
 	promiseFn := func(resolve, reject func(args ...any) js.Value) {
 		ftID, err := f.api.Send(payload, recipientID, retry, spc, args[4].Int())
 		if err != nil {
-			reject(utils.JsTrace(err))
+			reject(exception.NewTrace(err))
 		} else {
 			resolve(utils.CopyBytesToJS(ftID))
 		}
@@ -190,7 +191,7 @@ func (f *FileTransfer) Send(_ js.Value, args []js.Value) any {
 func (f *FileTransfer) Receive(_ js.Value, args []js.Value) any {
 	file, err := f.api.Receive(utils.CopyBytesToGo(args[0]))
 	if err != nil {
-		utils.Throw(utils.TypeError, err)
+		exception.ThrowTrace(err)
 		return nil
 	}
 
@@ -212,7 +213,7 @@ func (f *FileTransfer) Receive(_ js.Value, args []js.Value) any {
 func (f *FileTransfer) CloseSend(_ js.Value, args []js.Value) any {
 	err := f.api.CloseSend(utils.CopyBytesToGo(args[0]))
 	if err != nil {
-		utils.Throw(utils.TypeError, err)
+		exception.ThrowTrace(err)
 		return nil
 	}
 
@@ -245,7 +246,7 @@ func (f *FileTransfer) RegisterSentProgressCallback(
 
 	err := f.api.RegisterSentProgressCallback(tidBytes, spc, args[2].Int())
 	if err != nil {
-		utils.Throw(utils.TypeError, err)
+		exception.ThrowTrace(err)
 		return nil
 	}
 
@@ -274,7 +275,7 @@ func (f *FileTransfer) RegisterReceivedProgressCallback(
 	err := f.api.RegisterReceivedProgressCallback(
 		tidBytes, rpc, args[2].Int())
 	if err != nil {
-		utils.Throw(utils.TypeError, err)
+		exception.ThrowTrace(err)
 		return nil
 	}
 
diff --git a/wasm/follow.go b/wasm/follow.go
index dbff9722411ddac19999b28b137739ec31279a45..b215bf5471ca7d5028a7d84d6128c61394e472e4 100644
--- a/wasm/follow.go
+++ b/wasm/follow.go
@@ -10,8 +10,9 @@
 package wasm
 
 import (
+	"gitlab.com/elixxir/wasm-utils/exception"
+	"gitlab.com/elixxir/wasm-utils/utils"
 	"gitlab.com/elixxir/xxdk-wasm/storage"
-	"gitlab.com/elixxir/xxdk-wasm/utils"
 	"syscall/js"
 )
 
@@ -57,7 +58,7 @@ import (
 func (c *Cmix) StartNetworkFollower(_ js.Value, args []js.Value) any {
 	err := c.api.StartNetworkFollower(args[0].Int())
 	if err != nil {
-		utils.Throw(utils.TypeError, err)
+		exception.ThrowTrace(err)
 		return nil
 	}
 
@@ -76,7 +77,7 @@ func (c *Cmix) StartNetworkFollower(_ js.Value, args []js.Value) any {
 func (c *Cmix) StopNetworkFollower(js.Value, []js.Value) any {
 	err := c.api.StopNetworkFollower()
 	if err != nil {
-		utils.Throw(utils.TypeError, err)
+		exception.ThrowTrace(err)
 		return nil
 	}
 
@@ -165,7 +166,7 @@ func (c *Cmix) NetworkFollowerStatus(js.Value, []js.Value) any {
 func (c *Cmix) GetNodeRegistrationStatus(js.Value, []js.Value) any {
 	b, err := c.api.GetNodeRegistrationStatus()
 	if err != nil {
-		utils.Throw(utils.TypeError, err)
+		exception.ThrowTrace(err)
 		return nil
 	}
 
@@ -186,7 +187,7 @@ func (c *Cmix) GetNodeRegistrationStatus(js.Value, []js.Value) any {
 func (c *Cmix) IsReady(_ js.Value, args []js.Value) any {
 	isReadyInfo, err := c.api.IsReady(args[0].Float())
 	if err != nil {
-		utils.Throw(utils.TypeError, err)
+		exception.ThrowTrace(err)
 		return nil
 	}
 
@@ -205,7 +206,7 @@ func (c *Cmix) IsReady(_ js.Value, args []js.Value) any {
 func (c *Cmix) PauseNodeRegistrations(_ js.Value, args []js.Value) any {
 	err := c.api.PauseNodeRegistrations(args[0].Int())
 	if err != nil {
-		utils.Throw(utils.TypeError, err)
+		exception.ThrowTrace(err)
 		return nil
 	}
 
@@ -225,7 +226,7 @@ func (c *Cmix) PauseNodeRegistrations(_ js.Value, args []js.Value) any {
 func (c *Cmix) ChangeNumberOfNodeRegistrations(_ js.Value, args []js.Value) any {
 	err := c.api.ChangeNumberOfNodeRegistrations(args[0].Int(), args[1].Int())
 	if err != nil {
-		utils.Throw(utils.TypeError, err)
+		exception.ThrowTrace(err)
 		return nil
 	}
 
@@ -271,7 +272,7 @@ func (c *Cmix) IsHealthy(js.Value, []js.Value) any {
 func (c *Cmix) GetRunningProcesses(js.Value, []js.Value) any {
 	list, err := c.api.GetRunningProcesses()
 	if err != nil {
-		utils.Throw(utils.TypeError, err)
+		exception.ThrowTrace(err)
 		return nil
 	}
 
@@ -377,7 +378,7 @@ type trackServicesCallback struct {
 //	  },
 //	]
 func (tsc *trackServicesCallback) Callback(marshalData []byte, err error) {
-	tsc.callback(utils.CopyBytesToJS(marshalData), utils.JsTrace(err))
+	tsc.callback(utils.CopyBytesToJS(marshalData), exception.NewTrace(err))
 }
 
 // TrackServicesWithIdentity will return via a callback the list of services the
@@ -396,7 +397,7 @@ func (c *Cmix) TrackServicesWithIdentity(_ js.Value, args []js.Value) any {
 	err := c.api.TrackServicesWithIdentity(args[0].Int(),
 		&trackServicesCallback{utils.WrapCB(args[0], "Callback")})
 	if err != nil {
-		utils.Throw(utils.TypeError, err)
+		exception.ThrowTrace(err)
 		return nil
 	}
 
diff --git a/wasm/group.go b/wasm/group.go
index f3e7710a9144ba86fd33322b05c44092dd5c89bc..459a07db0cd79815da19f5d86254150f88ad1748 100644
--- a/wasm/group.go
+++ b/wasm/group.go
@@ -11,7 +11,8 @@ package wasm
 
 import (
 	"gitlab.com/elixxir/client/v4/bindings"
-	"gitlab.com/elixxir/xxdk-wasm/utils"
+	"gitlab.com/elixxir/wasm-utils/exception"
+	"gitlab.com/elixxir/wasm-utils/utils"
 	"syscall/js"
 )
 
@@ -62,7 +63,7 @@ func NewGroupChat(_ js.Value, args []js.Value) any {
 
 	api, err := bindings.NewGroupChat(args[0].Int(), requestFunc, p)
 	if err != nil {
-		utils.Throw(utils.TypeError, err)
+		exception.ThrowTrace(err)
 		return nil
 	}
 
@@ -93,7 +94,7 @@ func (g *GroupChat) MakeGroup(_ js.Value, args []js.Value) any {
 	promiseFn := func(resolve, reject func(args ...any) js.Value) {
 		sendReport, err := g.api.MakeGroup(membershipBytes, message, name)
 		if err != nil {
-			reject(utils.JsTrace(err))
+			reject(exception.NewTrace(err))
 		} else {
 			resolve(utils.CopyBytesToJS(sendReport))
 		}
@@ -117,7 +118,7 @@ func (g *GroupChat) ResendRequest(_ js.Value, args []js.Value) any {
 	promiseFn := func(resolve, reject func(args ...any) js.Value) {
 		sendReport, err := g.api.ResendRequest(groupId)
 		if err != nil {
-			reject(utils.JsTrace(err))
+			reject(exception.NewTrace(err))
 		} else {
 			resolve(utils.CopyBytesToJS(sendReport))
 		}
@@ -139,7 +140,7 @@ func (g *GroupChat) ResendRequest(_ js.Value, args []js.Value) any {
 func (g *GroupChat) JoinGroup(_ js.Value, args []js.Value) any {
 	err := g.api.JoinGroup(utils.CopyBytesToGo(args[0]))
 	if err != nil {
-		utils.Throw(utils.TypeError, err)
+		exception.ThrowTrace(err)
 		return nil
 	}
 
@@ -157,7 +158,7 @@ func (g *GroupChat) JoinGroup(_ js.Value, args []js.Value) any {
 func (g *GroupChat) LeaveGroup(_ js.Value, args []js.Value) any {
 	err := g.api.LeaveGroup(utils.CopyBytesToGo(args[0]))
 	if err != nil {
-		utils.Throw(utils.TypeError, err)
+		exception.ThrowTrace(err)
 		return nil
 	}
 
@@ -187,7 +188,7 @@ func (g *GroupChat) Send(_ js.Value, args []js.Value) any {
 	promiseFn := func(resolve, reject func(args ...any) js.Value) {
 		sendReport, err := g.api.Send(groupId, message, tag)
 		if err != nil {
-			reject(utils.JsTrace(err))
+			reject(exception.NewTrace(err))
 		} else {
 			resolve(utils.CopyBytesToJS(sendReport))
 		}
@@ -204,7 +205,7 @@ func (g *GroupChat) Send(_ js.Value, args []js.Value) any {
 func (g *GroupChat) GetGroups(js.Value, []js.Value) any {
 	groups, err := g.api.GetGroups()
 	if err != nil {
-		utils.Throw(utils.TypeError, err)
+		exception.ThrowTrace(err)
 		return nil
 	}
 
@@ -224,7 +225,7 @@ func (g *GroupChat) GetGroups(js.Value, []js.Value) any {
 func (g *GroupChat) GetGroup(_ js.Value, args []js.Value) any {
 	grp, err := g.api.GetGroup(utils.CopyBytesToGo(args[0]))
 	if err != nil {
-		utils.Throw(utils.TypeError, err)
+		exception.ThrowTrace(err)
 		return nil
 	}
 
@@ -318,7 +319,7 @@ func (g *Group) GetCreatedMS(js.Value, []js.Value) any {
 func (g *Group) GetMembership(js.Value, []js.Value) any {
 	membership, err := g.api.GetMembership()
 	if err != nil {
-		utils.Throw(utils.TypeError, err)
+		exception.ThrowTrace(err)
 		return nil
 	}
 
@@ -345,7 +346,7 @@ func (g *Group) Serialize(js.Value, []js.Value) any {
 func DeserializeGroup(_ js.Value, args []js.Value) any {
 	grp, err := bindings.DeserializeGroup(utils.CopyBytesToGo(args[0]))
 	if err != nil {
-		utils.Throw(utils.TypeError, err)
+		exception.ThrowTrace(err)
 		return nil
 	}
 
@@ -393,7 +394,7 @@ func (gcp *groupChatProcessor) Process(decryptedMessage, msg,
 	receptionId []byte, ephemeralId, roundId int64, roundURL string, err error) {
 	gcp.process(utils.CopyBytesToJS(decryptedMessage),
 		utils.CopyBytesToJS(msg), utils.CopyBytesToJS(receptionId), ephemeralId,
-		roundId, roundURL, utils.JsTrace(err))
+		roundId, roundURL, exception.NewTrace(err))
 }
 
 // String returns a name identifying this processor. Used for debugging.
diff --git a/wasm/identity.go b/wasm/identity.go
index 4141b1f1b9dc0b6a2cf0f374b428118b19899363..013d2059b31378b2ce2e580723ada3d59fa35697 100644
--- a/wasm/identity.go
+++ b/wasm/identity.go
@@ -12,7 +12,8 @@ package wasm
 import (
 	"gitlab.com/elixxir/client/v4/bindings"
 	"gitlab.com/elixxir/client/v4/xxdk"
-	"gitlab.com/elixxir/xxdk-wasm/utils"
+	"gitlab.com/elixxir/wasm-utils/exception"
+	"gitlab.com/elixxir/wasm-utils/utils"
 	"syscall/js"
 )
 
@@ -38,7 +39,7 @@ func StoreReceptionIdentity(_ js.Value, args []js.Value) any {
 		args[0].String(), identity, args[2].Int())
 
 	if err != nil {
-		utils.Throw(utils.TypeError, err)
+		exception.ThrowTrace(err)
 		return nil
 	}
 
@@ -58,7 +59,7 @@ func StoreReceptionIdentity(_ js.Value, args []js.Value) any {
 func LoadReceptionIdentity(_ js.Value, args []js.Value) any {
 	ri, err := bindings.LoadReceptionIdentity(args[0].String(), args[1].Int())
 	if err != nil {
-		utils.Throw(utils.TypeError, err)
+		exception.ThrowTrace(err)
 		return nil
 	}
 
@@ -75,7 +76,7 @@ func (c *Cmix) MakeReceptionIdentity(js.Value, []js.Value) any {
 	promiseFn := func(resolve, reject func(args ...any) js.Value) {
 		ri, err := c.api.MakeReceptionIdentity()
 		if err != nil {
-			reject(utils.JsTrace(err))
+			reject(exception.NewTrace(err))
 		} else {
 			resolve(utils.CopyBytesToJS(ri))
 		}
@@ -94,7 +95,7 @@ func (c *Cmix) MakeLegacyReceptionIdentity(js.Value, []js.Value) any {
 	promiseFn := func(resolve, reject func(args ...any) js.Value) {
 		ri, err := c.api.MakeLegacyReceptionIdentity()
 		if err != nil {
-			reject(utils.JsTrace(err))
+			reject(exception.NewTrace(err))
 		} else {
 			resolve(utils.CopyBytesToJS(ri))
 		}
@@ -132,7 +133,7 @@ func GetContactFromReceptionIdentity(_ js.Value, args []js.Value) any {
 	identityJSON := utils.CopyBytesToGo(args[0])
 	identity, err := xxdk.UnmarshalReceptionIdentity(identityJSON)
 	if err != nil {
-		utils.Throw(utils.TypeError, err)
+		exception.ThrowTrace(err)
 		return nil
 	}
 
@@ -150,7 +151,7 @@ func GetContactFromReceptionIdentity(_ js.Value, args []js.Value) any {
 func GetIDFromContact(_ js.Value, args []js.Value) any {
 	cID, err := bindings.GetIDFromContact(utils.CopyBytesToGo(args[0]))
 	if err != nil {
-		utils.Throw(utils.TypeError, err)
+		exception.ThrowTrace(err)
 		return nil
 	}
 
@@ -169,7 +170,7 @@ func GetIDFromContact(_ js.Value, args []js.Value) any {
 func GetPubkeyFromContact(_ js.Value, args []js.Value) any {
 	key, err := bindings.GetPubkeyFromContact([]byte(args[0].String()))
 	if err != nil {
-		utils.Throw(utils.TypeError, err)
+		exception.ThrowTrace(err)
 		return nil
 	}
 
@@ -195,7 +196,7 @@ func SetFactsOnContact(_ js.Value, args []js.Value) any {
 	factListJSON := utils.CopyBytesToGo(args[1])
 	c, err := bindings.SetFactsOnContact(marshaledContact, factListJSON)
 	if err != nil {
-		utils.Throw(utils.TypeError, err)
+		exception.ThrowTrace(err)
 		return nil
 	}
 
@@ -213,7 +214,7 @@ func SetFactsOnContact(_ js.Value, args []js.Value) any {
 func GetFactsFromContact(_ js.Value, args []js.Value) any {
 	fl, err := bindings.GetFactsFromContact(utils.CopyBytesToGo(args[0]))
 	if err != nil {
-		utils.Throw(utils.TypeError, err)
+		exception.ThrowTrace(err)
 		return nil
 	}
 
diff --git a/wasm/ndf.go b/wasm/ndf.go
index 69441fea2c03e5f369a0e9a76a5936b7e43bf67f..292fe3ecfb42d57a2a7964c03265bdfd7c39d14c 100644
--- a/wasm/ndf.go
+++ b/wasm/ndf.go
@@ -11,7 +11,8 @@ package wasm
 
 import (
 	"gitlab.com/elixxir/client/v4/bindings"
-	"gitlab.com/elixxir/xxdk-wasm/utils"
+	"gitlab.com/elixxir/wasm-utils/exception"
+	"gitlab.com/elixxir/wasm-utils/utils"
 	"syscall/js"
 )
 
@@ -34,7 +35,7 @@ func DownloadAndVerifySignedNdfWithUrl(_ js.Value, args []js.Value) any {
 	promiseFn := func(resolve, reject func(args ...any) js.Value) {
 		ndf, err := bindings.DownloadAndVerifySignedNdfWithUrl(url, cert)
 		if err != nil {
-			reject(utils.JsTrace(err))
+			reject(exception.NewTrace(err))
 		} else {
 			resolve(utils.CopyBytesToJS(ndf))
 		}
diff --git a/wasm/params.go b/wasm/params.go
index 01f307c6c029f1643e674fe7b77db44d4830b2fe..5b8dac2f78656de84be3050705cc4c72415a5ee7 100644
--- a/wasm/params.go
+++ b/wasm/params.go
@@ -11,7 +11,7 @@ package wasm
 
 import (
 	"gitlab.com/elixxir/client/v4/bindings"
-	"gitlab.com/elixxir/xxdk-wasm/utils"
+	"gitlab.com/elixxir/wasm-utils/utils"
 	"syscall/js"
 )
 
diff --git a/wasm/restlike.go b/wasm/restlike.go
index 0cf802a32bb0c638859c77d1a32fc609cd2994b8..d56ff586279f292042d2faa7df39d38714273272 100644
--- a/wasm/restlike.go
+++ b/wasm/restlike.go
@@ -11,7 +11,8 @@ package wasm
 
 import (
 	"gitlab.com/elixxir/client/v4/bindings"
-	"gitlab.com/elixxir/xxdk-wasm/utils"
+	"gitlab.com/elixxir/wasm-utils/exception"
+	"gitlab.com/elixxir/wasm-utils/utils"
 	"syscall/js"
 )
 
@@ -39,7 +40,7 @@ func RestlikeRequest(_ js.Value, args []js.Value) any {
 		msg, err := bindings.RestlikeRequest(
 			cmixId, connectionID, request, e2eParamsJSON)
 		if err != nil {
-			reject(utils.JsTrace(err))
+			reject(exception.NewTrace(err))
 		} else {
 			resolve(utils.CopyBytesToJS(msg))
 		}
@@ -72,7 +73,7 @@ func RestlikeRequestAuth(_ js.Value, args []js.Value) any {
 		msg, err := bindings.RestlikeRequestAuth(
 			cmixId, authConnectionID, request, e2eParamsJSON)
 		if err != nil {
-			reject(utils.JsTrace(err))
+			reject(exception.NewTrace(err))
 		} else {
 			resolve(utils.CopyBytesToJS(msg))
 		}
diff --git a/wasm/restlikeSingle.go b/wasm/restlikeSingle.go
index 391e40c01a2711aa630468ae59d06e15b5312bab..7a2e8f8c2828b3c3b91b56e0ba5cce3ed45ef548 100644
--- a/wasm/restlikeSingle.go
+++ b/wasm/restlikeSingle.go
@@ -11,7 +11,8 @@ package wasm
 
 import (
 	"gitlab.com/elixxir/client/v4/bindings"
-	"gitlab.com/elixxir/xxdk-wasm/utils"
+	"gitlab.com/elixxir/wasm-utils/exception"
+	"gitlab.com/elixxir/wasm-utils/utils"
 	"syscall/js"
 )
 
@@ -27,7 +28,7 @@ type restlikeCallback struct {
 //   - payload - JSON of [restlike.Message] (Uint8Array).
 //   - err - Returns an error on failure (Error).
 func (rlc *restlikeCallback) Callback(payload []byte, err error) {
-	rlc.callback(utils.CopyBytesToJS(payload), utils.JsTrace(err))
+	rlc.callback(utils.CopyBytesToJS(payload), exception.NewTrace(err))
 }
 
 // RequestRestLike sends a restlike request to a given contact.
@@ -54,7 +55,7 @@ func RequestRestLike(_ js.Value, args []js.Value) any {
 		msg, err := bindings.RequestRestLike(
 			e2eID, recipient, request, paramsJSON)
 		if err != nil {
-			reject(utils.JsTrace(err))
+			reject(exception.NewTrace(err))
 		} else {
 			resolve(utils.CopyBytesToJS(msg))
 		}
@@ -91,7 +92,7 @@ func AsyncRequestRestLike(_ js.Value, args []js.Value) any {
 		err := bindings.AsyncRequestRestLike(
 			e2eID, recipient, request, paramsJSON, cb)
 		if err != nil {
-			utils.Throw(utils.TypeError, err)
+			exception.ThrowTrace(err)
 		}
 	}()
 
diff --git a/wasm/secrets.go b/wasm/secrets.go
index d310fcbcd4cd7e2593ec6d360274ed5cf9eea48c..4cba18e0495a251eb7a803bafd968c8f16757e50 100644
--- a/wasm/secrets.go
+++ b/wasm/secrets.go
@@ -11,7 +11,7 @@ package wasm
 
 import (
 	"gitlab.com/elixxir/client/v4/bindings"
-	"gitlab.com/elixxir/xxdk-wasm/utils"
+	"gitlab.com/elixxir/wasm-utils/utils"
 	"syscall/js"
 )
 
diff --git a/wasm/single.go b/wasm/single.go
index 8198bb80fc58de0b21a045c47b39194efd149cfa..5d6aa92eef53055bf8316f5dfaedea1b81746f97 100644
--- a/wasm/single.go
+++ b/wasm/single.go
@@ -11,7 +11,8 @@ package wasm
 
 import (
 	"gitlab.com/elixxir/client/v4/bindings"
-	"gitlab.com/elixxir/xxdk-wasm/utils"
+	"gitlab.com/elixxir/wasm-utils/exception"
+	"gitlab.com/elixxir/wasm-utils/utils"
 	"syscall/js"
 )
 
@@ -49,7 +50,7 @@ func TransmitSingleUse(_ js.Value, args []js.Value) any {
 		sendReport, err := bindings.TransmitSingleUse(
 			e2eID, recipient, tag, payload, paramsJSON, responseCB)
 		if err != nil {
-			reject(utils.JsTrace(err))
+			reject(exception.NewTrace(err))
 		} else {
 			resolve(utils.CopyBytesToJS(sendReport))
 		}
@@ -76,7 +77,7 @@ func Listen(_ js.Value, args []js.Value) any {
 	cb := &singleUseCallback{utils.WrapCB(args[2], "Callback")}
 	api, err := bindings.Listen(args[0].Int(), args[1].String(), cb)
 	if err != nil {
-		utils.Throw(utils.TypeError, err)
+		exception.ThrowTrace(err)
 		return nil
 	}
 
@@ -128,7 +129,7 @@ type singleUseCallback struct {
 //     (Uint8Array).
 //   - err - Returns an error on failure (Error).
 func (suc *singleUseCallback) Callback(callbackReport []byte, err error) {
-	suc.callback(utils.CopyBytesToJS(callbackReport), utils.JsTrace(err))
+	suc.callback(utils.CopyBytesToJS(callbackReport), exception.NewTrace(err))
 }
 
 // singleUseResponse wraps Javascript callbacks to adhere to the
@@ -145,5 +146,5 @@ type singleUseResponse struct {
 //     (Uint8Array).
 //   - err - Returns an error on failure (Error).
 func (sur *singleUseResponse) Callback(responseReport []byte, err error) {
-	sur.callback(utils.CopyBytesToJS(responseReport), utils.JsTrace(err))
+	sur.callback(utils.CopyBytesToJS(responseReport), exception.NewTrace(err))
 }
diff --git a/wasm/timeNow.go b/wasm/timeNow.go
index 627a16b4e344089d782edf435fdcd3225c6c230b..ca3ebe6037161df753d80f7ed08e01c4e3d5b335 100644
--- a/wasm/timeNow.go
+++ b/wasm/timeNow.go
@@ -11,7 +11,7 @@ package wasm
 
 import (
 	"gitlab.com/elixxir/client/v4/bindings"
-	"gitlab.com/elixxir/xxdk-wasm/utils"
+	"gitlab.com/elixxir/wasm-utils/utils"
 	"syscall/js"
 )
 
diff --git a/wasm/ud.go b/wasm/ud.go
index 74bc2b11b628a4139040168194af9b6d21009a72..8b69f196b6362c3116551a9f78a0191d549fa571 100644
--- a/wasm/ud.go
+++ b/wasm/ud.go
@@ -11,7 +11,8 @@ package wasm
 
 import (
 	"gitlab.com/elixxir/client/v4/bindings"
-	"gitlab.com/elixxir/xxdk-wasm/utils"
+	"gitlab.com/elixxir/wasm-utils/exception"
+	"gitlab.com/elixxir/wasm-utils/utils"
 	"syscall/js"
 )
 
@@ -112,7 +113,7 @@ func NewOrLoadUd(_ js.Value, args []js.Value) any {
 	api, err := bindings.NewOrLoadUd(e2eID, follower, username,
 		registrationValidationSignature, cert, contactFile, address)
 	if err != nil {
-		utils.Throw(utils.TypeError, err)
+		exception.ThrowTrace(err)
 		return nil
 	}
 
@@ -157,7 +158,7 @@ func NewUdManagerFromBackup(_ js.Value, args []js.Value) any {
 		e2eID, follower, cert,
 		contactFile, address)
 	if err != nil {
-		utils.Throw(utils.TypeError, err)
+		exception.ThrowTrace(err)
 		return nil
 	}
 
@@ -182,7 +183,7 @@ func (ud *UserDiscovery) GetFacts(js.Value, []js.Value) any {
 func (ud *UserDiscovery) GetContact(js.Value, []js.Value) any {
 	c, err := ud.api.GetContact()
 	if err != nil {
-		utils.Throw(utils.TypeError, err)
+		exception.ThrowTrace(err)
 		return nil
 	}
 
@@ -203,7 +204,7 @@ func (ud *UserDiscovery) GetContact(js.Value, []js.Value) any {
 func (ud *UserDiscovery) ConfirmFact(_ js.Value, args []js.Value) any {
 	err := ud.api.ConfirmFact(args[0].String(), args[1].String())
 	if err != nil {
-		utils.Throw(utils.TypeError, err)
+		exception.ThrowTrace(err)
 		return nil
 	}
 
@@ -228,7 +229,7 @@ func (ud *UserDiscovery) ConfirmFact(_ js.Value, args []js.Value) any {
 func (ud *UserDiscovery) SendRegisterFact(_ js.Value, args []js.Value) any {
 	confirmationID, err := ud.api.SendRegisterFact(utils.CopyBytesToGo(args[0]))
 	if err != nil {
-		utils.Throw(utils.TypeError, err)
+		exception.ThrowTrace(err)
 		return nil
 	}
 
@@ -247,7 +248,7 @@ func (ud *UserDiscovery) SendRegisterFact(_ js.Value, args []js.Value) any {
 func (ud *UserDiscovery) PermanentDeleteAccount(_ js.Value, args []js.Value) any {
 	err := ud.api.PermanentDeleteAccount(utils.CopyBytesToGo(args[0]))
 	if err != nil {
-		utils.Throw(utils.TypeError, err)
+		exception.ThrowTrace(err)
 		return nil
 	}
 
@@ -265,7 +266,7 @@ func (ud *UserDiscovery) PermanentDeleteAccount(_ js.Value, args []js.Value) any
 func (ud *UserDiscovery) RemoveFact(_ js.Value, args []js.Value) any {
 	err := ud.api.RemoveFact(utils.CopyBytesToGo(args[0]))
 	if err != nil {
-		utils.Throw(utils.TypeError, err)
+		exception.ThrowTrace(err)
 		return nil
 	}
 
@@ -290,7 +291,7 @@ type udLookupCallback struct {
 //     the lookup, or nil if an error occurs (Uint8Array).
 //   - err - Returns an error on failure (Error).
 func (ulc *udLookupCallback) Callback(contactBytes []byte, err error) {
-	ulc.callback(utils.CopyBytesToJS(contactBytes), utils.JsTrace(err))
+	ulc.callback(utils.CopyBytesToJS(contactBytes), exception.NewTrace(err))
 }
 
 // LookupUD returns the public key of the passed ID as known by the user
@@ -322,7 +323,7 @@ func LookupUD(_ js.Value, args []js.Value) any {
 		sendReport, err := bindings.LookupUD(
 			e2eID, udContact, cb, lookupId, singleRequestParamsJSON)
 		if err != nil {
-			reject(utils.JsTrace(err))
+			reject(exception.NewTrace(err))
 		} else {
 			resolve(utils.CopyBytesToJS(sendReport))
 		}
@@ -357,7 +358,7 @@ type udSearchCallback struct {
 //	  "<xxc(2)d7RJTu61Vy1lDThDMn8rYIiKSe1uXA/RCvvcIhq5Yg4DEgB7Ugdw/BAr6RsCABkWAFV1c2VybmFtZTI7N3XWrxIUpR29atpFMkcR6A==xxc>"
 //	}
 func (usc *udSearchCallback) Callback(contactListJSON []byte, err error) {
-	usc.callback(utils.CopyBytesToJS(contactListJSON), utils.JsTrace(err))
+	usc.callback(utils.CopyBytesToJS(contactListJSON), exception.NewTrace(err))
 }
 
 // SearchUD searches user discovery for the passed Facts. The searchCallback
@@ -389,7 +390,7 @@ func SearchUD(_ js.Value, args []js.Value) any {
 		sendReport, err := bindings.SearchUD(
 			e2eID, udContact, cb, factListJSON, singleRequestParamsJSON)
 		if err != nil {
-			reject(utils.JsTrace(err))
+			reject(exception.NewTrace(err))
 		} else {
 			resolve(utils.CopyBytesToJS(sendReport))
 		}
diff --git a/wasm/version.go b/wasm/version.go
index ee1a921cc666ef3a5e2491b5f43371ee098e2f3f..ca70bcbb150125aa427a271374632b4cef6118d8 100644
--- a/wasm/version.go
+++ b/wasm/version.go
@@ -14,8 +14,9 @@ import (
 	"syscall/js"
 
 	"gitlab.com/elixxir/client/v4/bindings"
+	"gitlab.com/elixxir/wasm-utils/exception"
+	"gitlab.com/elixxir/wasm-utils/utils"
 	"gitlab.com/elixxir/xxdk-wasm/storage"
-	"gitlab.com/elixxir/xxdk-wasm/utils"
 )
 
 // GetVersion returns the current xxDK WASM semantic version.
@@ -80,7 +81,7 @@ func GetWasmSemanticVersion(js.Value, []js.Value) any {
 
 	data, err := json.Marshal(vi)
 	if err != nil {
-		utils.Throw(utils.TypeError, err)
+		exception.ThrowTrace(err)
 	}
 
 	return utils.CopyBytesToJS(data)
@@ -104,7 +105,7 @@ func GetXXDKSemanticVersion(js.Value, []js.Value) any {
 
 	data, err := json.Marshal(vi)
 	if err != nil {
-		utils.Throw(utils.TypeError, err)
+		exception.ThrowTrace(err)
 	}
 
 	return utils.CopyBytesToJS(data)
diff --git a/wasm_exec.js b/wasm_exec.js
index c6ae00c713c4bb089eb2d76d61d3e88aa0835839..5365a841cd2ff018149c25666e4b525a42bc1344 100644
--- a/wasm_exec.js
+++ b/wasm_exec.js
@@ -453,7 +453,7 @@
 					},
 
 					// func Throw(exception string, message string)
-					'gitlab.com/elixxir/xxdk-wasm/utils.throw': (sp) => {
+					'gitlab.com/elixxir/wasm-utils/exception.throw': (sp) => {
 						const exception = loadString(sp + 8)
 						const message = loadString(sp + 24)
 						throw globalThis[exception](message)
diff --git a/worker/README.md b/worker/README.md
index e5027c3fe7d366d5096bb9b09406ea6ab6edaa2f..18d21f12fb5115a92941451c2f9aa0667ca3fa9a 100644
--- a/worker/README.md
+++ b/worker/README.md
@@ -18,7 +18,7 @@ package main
 
 import (
 	"fmt"
-	"gitlab.com/elixxir/xxdk-wasm/utils/worker"
+	"gitlab.com/elixxir/wasm-utils/utils/worker"
 )
 
 func main() {
@@ -51,4 +51,4 @@ wm, err := worker.NewManager("workerWasm.js", "exampleWebWorker")
 if err != nil {
 	return nil, err
 }
-```
\ No newline at end of file
+```
diff --git a/worker/manager.go b/worker/manager.go
index 3a5831db7c07a54927429b8d818dd66db7b9fc7a..2809a40922609923f300593a3120082afc46f5a5 100644
--- a/worker/manager.go
+++ b/worker/manager.go
@@ -18,7 +18,7 @@ import (
 	"github.com/pkg/errors"
 	jww "github.com/spf13/jwalterweatherman"
 
-	"gitlab.com/elixxir/xxdk-wasm/utils"
+	"gitlab.com/elixxir/wasm-utils/utils"
 )
 
 // initID is the ID for the first item in the callback list. If the list only
diff --git a/worker/manager_test.go b/worker/manager_test.go
index 49a395a9c45a33f25892cc1efc86e263ccf06063..b071c7e437d149eb49aec41da593f858c4c01354 100644
--- a/worker/manager_test.go
+++ b/worker/manager_test.go
@@ -21,7 +21,7 @@ func TestManager_processReceivedMessage(t *testing.T) {
 	m := &Manager{callbacks: make(map[Tag]map[uint64]ReceptionCallback)}
 
 	msg := Message{Tag: readyTag, ID: 5}
-	cbChan := make(chan struct{})
+	cbChan := make(chan struct{}, 1)
 	cb := func([]byte) { cbChan <- struct{}{} }
 	m.callbacks[msg.Tag] = map[uint64]ReceptionCallback{msg.ID: cb}
 
@@ -31,16 +31,16 @@ func TestManager_processReceivedMessage(t *testing.T) {
 	}
 
 	go func() {
-		select {
-		case <-cbChan:
-		case <-time.After(10 * time.Millisecond):
-			t.Error("Timed out waiting for callback to be called.")
+		err = m.processReceivedMessage(data)
+		if err != nil {
+			t.Errorf("Failed to receive message: %+v", err)
 		}
 	}()
 
-	err = m.processReceivedMessage(data)
-	if err != nil {
-		t.Errorf("Failed to receive message: %+v", err)
+	select {
+	case <-cbChan:
+	case <-time.After(10 * time.Millisecond):
+		t.Error("Timed out waiting for callback to be called.")
 	}
 }
 
@@ -97,7 +97,7 @@ func TestManager_RegisterCallback(t *testing.T) {
 	m := &Manager{callbacks: make(map[Tag]map[uint64]ReceptionCallback)}
 
 	msg := Message{Tag: readyTag, ID: initID}
-	cbChan := make(chan struct{})
+	cbChan := make(chan struct{}, 1)
 	cb := func([]byte) { cbChan <- struct{}{} }
 	m.RegisterCallback(msg.Tag, cb)
 
@@ -107,16 +107,16 @@ func TestManager_RegisterCallback(t *testing.T) {
 	}
 
 	go func() {
-		select {
-		case <-cbChan:
-		case <-time.After(10 * time.Millisecond):
-			t.Error("Timed out waiting for callback to be called.")
+		err = m.processReceivedMessage(data)
+		if err != nil {
+			t.Errorf("Failed to receive message: %+v", err)
 		}
 	}()
 
-	err = m.processReceivedMessage(data)
-	if err != nil {
-		t.Errorf("Failed to receive message: %+v", err)
+	select {
+	case <-cbChan:
+	case <-time.After(10 * time.Millisecond):
+		t.Error("Timed out waiting for callback to be called.")
 	}
 }
 
@@ -129,7 +129,7 @@ func TestManager_registerReplyCallback(t *testing.T) {
 	}
 
 	msg := Message{Tag: readyTag, ID: 5}
-	cbChan := make(chan struct{})
+	cbChan := make(chan struct{}, 1)
 	cb := func([]byte) { cbChan <- struct{}{} }
 	m.registerReplyCallback(msg.Tag, cb)
 	m.callbacks[msg.Tag] = map[uint64]ReceptionCallback{msg.ID: cb}
@@ -140,16 +140,16 @@ func TestManager_registerReplyCallback(t *testing.T) {
 	}
 
 	go func() {
-		select {
-		case <-cbChan:
-		case <-time.After(10 * time.Millisecond):
-			t.Error("Timed out waiting for callback to be called.")
+		err = m.processReceivedMessage(data)
+		if err != nil {
+			t.Errorf("Failed to receive message: %+v", err)
 		}
 	}()
 
-	err = m.processReceivedMessage(data)
-	if err != nil {
-		t.Errorf("Failed to receive message: %+v", err)
+	select {
+	case <-cbChan:
+	case <-time.After(10 * time.Millisecond):
+		t.Error("Timed out waiting for callback to be called.")
 	}
 }
 
diff --git a/worker/thread.go b/worker/thread.go
index 203b00302a97c22f4e44501e2c721b52312c14ab..393ff7661db3f0c773932fdff932a15a3bfe92b9 100644
--- a/worker/thread.go
+++ b/worker/thread.go
@@ -17,7 +17,7 @@ import (
 	"github.com/pkg/errors"
 	jww "github.com/spf13/jwalterweatherman"
 
-	"gitlab.com/elixxir/xxdk-wasm/utils"
+	"gitlab.com/elixxir/wasm-utils/utils"
 )
 
 // ThreadReceptionCallback is called with a message received from the main
@@ -244,8 +244,8 @@ func (tm *ThreadManager) addEventListeners() {
 	// Doc: https://developer.mozilla.org/en-US/docs/Web/API/Worker/error_event
 	errorEvent := js.FuncOf(func(_ js.Value, args []js.Value) any {
 		event := args[0]
-		jww.ERROR.Printf("[WW] [%s] Worker received error event: %s",
-			tm.name, utils.JsErrorToJson(event))
+		jww.ERROR.Printf("[WW] [%s] Worker received error event: %+v",
+			tm.name, js.Error{Value: event})
 		return nil
 	})
 
@@ -254,8 +254,8 @@ func (tm *ThreadManager) addEventListeners() {
 	// Doc: https://developer.mozilla.org/en-US/docs/Web/API/Worker/messageerror_event
 	messageerrorEvent := js.FuncOf(func(_ js.Value, args []js.Value) any {
 		event := args[0]
-		jww.ERROR.Printf("[WW] [%s] Worker received message error event: %s",
-			tm.name, utils.JsErrorToJson(event))
+		jww.ERROR.Printf("[WW] [%s] Worker received message error event: %+v",
+			tm.name, js.Error{Value: event})
 		return nil
 	})
 
diff --git a/worker/thread_test.go b/worker/thread_test.go
index ada6de8fc00916699b45eaee1483830a277b9fc9..d738580aad5d66ad4eab81ee934db52b24313825 100644
--- a/worker/thread_test.go
+++ b/worker/thread_test.go
@@ -20,7 +20,7 @@ func TestThreadManager_processReceivedMessage(t *testing.T) {
 	tm := &ThreadManager{callbacks: make(map[Tag]ThreadReceptionCallback)}
 
 	msg := Message{Tag: readyTag, ID: 5}
-	cbChan := make(chan struct{})
+	cbChan := make(chan struct{}, 1)
 	cb := func([]byte) ([]byte, error) { cbChan <- struct{}{}; return nil, nil }
 	tm.callbacks[msg.Tag] = cb
 
@@ -30,16 +30,16 @@ func TestThreadManager_processReceivedMessage(t *testing.T) {
 	}
 
 	go func() {
-		select {
-		case <-cbChan:
-		case <-time.After(10 * time.Millisecond):
-			t.Error("Timed out waiting for callback to be called.")
+		err = tm.processReceivedMessage(data)
+		if err != nil {
+			t.Errorf("Failed to receive message: %+v", err)
 		}
 	}()
 
-	err = tm.processReceivedMessage(data)
-	if err != nil {
-		t.Errorf("Failed to receive message: %+v", err)
+	select {
+	case <-cbChan:
+	case <-time.After(10 * time.Millisecond):
+		t.Error("Timed out waiting for callback to be called.")
 	}
 }
 
@@ -49,7 +49,7 @@ func TestThreadManager_RegisterCallback(t *testing.T) {
 	tm := &ThreadManager{callbacks: make(map[Tag]ThreadReceptionCallback)}
 
 	msg := Message{Tag: readyTag, ID: 5}
-	cbChan := make(chan struct{})
+	cbChan := make(chan struct{}, 1)
 	cb := func([]byte) ([]byte, error) { cbChan <- struct{}{}; return nil, nil }
 	tm.RegisterCallback(msg.Tag, cb)
 
@@ -59,15 +59,15 @@ func TestThreadManager_RegisterCallback(t *testing.T) {
 	}
 
 	go func() {
-		select {
-		case <-cbChan:
-		case <-time.After(10 * time.Millisecond):
-			t.Error("Timed out waiting for callback to be called.")
+		err = tm.processReceivedMessage(data)
+		if err != nil {
+			t.Errorf("Failed to receive message: %+v", err)
 		}
 	}()
 
-	err = tm.processReceivedMessage(data)
-	if err != nil {
-		t.Errorf("Failed to receive message: %+v", err)
+	select {
+	case <-cbChan:
+	case <-time.After(10 * time.Millisecond):
+		t.Error("Timed out waiting for callback to be called.")
 	}
 }