diff --git a/download_cmix_binaries.sh b/download_cmix_binaries.sh
index d38cf4e509a72844c8babcc22947abc91b891ae2..e9a4d39352e748d32d878fc65734b9a7588ba06b 100755
--- a/download_cmix_binaries.sh
+++ b/download_cmix_binaries.sh
@@ -86,13 +86,14 @@ for BRANCH in $(echo "forcedbranch" $FBRANCH $FBRANCH2 $DEFAULTBRANCH); do
         GPULIB_URL=${GPULIB_URL:="${REPOS_API}server/$BRANCH_URL/libpowmosm75.so?job=build"}
         GPULIB2_URL=${GPULIB2_URL:="${REPOS_API}server/$BRANCH_URL/libpow.fatbin?job=build"}
         CLIENT_REG_URL=${CLIENT_REG_URL:="${REPOS_API}client-registrar/$BRANCH_URL/registration$BIN"}
+        XXDK_WASM_URL=${XXDK_WASM_URL:="${REPOS_API}xxdk-wasm/$BRANCH_URL/xxdk.wasm?job=build"}
     else
         UDB_URL=${UDB_URL:="${REPOS_API}/$BRANCH/udb$BIN"}
         SERVER_URL=${SERVER_URL:="${REPOS_API}/$BRANCH/server$BIN"}
         GW_URL=${GW_URL:="${REPOS_API}/$BRANCH/gateway$BIN"}
         PERMISSIONING_URL=${PERMISSIONING_URL:="${REPOS_API}/$BRANCH/registration.stateless$BIN"}
         CLIENT_URL=${CLIENT_URL:="${REPOS_API}/$BRANCH/client$BIN"}
-        CLIENT_REG_URL=${CLIENT_REG_URL:="${REPOS_API}client-registrar/$BRANCH_URL/registration$BIN"}
+        XXDK_WASM_URL=${XXDK_WASM_URL:="${REPOS_API}/$BRANCH/xxdk.wasm?job=build"}
     fi
 
     set -x
@@ -122,11 +123,16 @@ for BRANCH in $(echo "forcedbranch" $FBRANCH $FBRANCH2 $DEFAULTBRANCH); do
         curl -s -f -L -H "PRIVATE-TOKEN: $GITLAB_ACCESS_TOKEN" -o "$download_path/client" ${CLIENT_URL}
     fi
 
-        # Silently download the client registrar binary to the provisioning directory
+    # Silently download the client registrar binary to the provisioning directory
     if [ ! -f $download_path/client-registrar ] && [[ "$CLIENT_REG_URL" != *"forcedbranch"* ]]; then
         curl -s -f -L -H "PRIVATE-TOKEN: $GITLAB_ACCESS_TOKEN" -o "$download_path/client-registrar" ${CLIENT_REG_URL}
     fi
 
+    # Silently download the client registrar binary to the provisioning directory
+    if [ ! -f $download_path/xxdk.wasm ] && [[ "$XXDK_WASM_URL" != *"forcedbranch"* ]]; then
+        curl -s -f -L -H "PRIVATE-TOKEN: $GITLAB_ACCESS_TOKEN" -o "$download_path/xxdk.wasm" ${XXDK_WASM_URL}
+    fi
+
 if [[ $2 == "d" ]]; then
     # Silently download the Server binary to the provisioning directory
     if [ ! -f $download_path/server-cuda ] && [[ "$SERVER_GPU_URL" != *"forcedbranch"* ]]; then
@@ -161,6 +167,7 @@ fi
     unset GPULIB_URL
     unset GPULIB2_URL
     unset CLIENT_REG_URL
+    unset XXDK_WASM_URL
 done
 
 # Make binaries executable
diff --git a/wasm/README.md b/wasm/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..ebb0478a036caa237074f94f6beded193d354277
--- /dev/null
+++ b/wasm/README.md
@@ -0,0 +1,56 @@
+# Running WASM Tests
+
+To run WASM tests, servers and gateways are run like normal, but the client is
+served by an HTTP server in the browser.
+
+The `testServer` directory contains a basic HTTP server (`clientServer.go`) and
+various directories each containing a different example. Each example contains
+one or more cMix clients running in javascript. The `assets` directory contains
+files used by all the examples.
+
+To run the server, first ensure the compiled bindings are in the `bin`
+directory. Then run the server
+
+```shell
+$ GOOS=js GOARCH=wasm go build -o examples/xxdk.wasm
+$ cd testServer/
+$ go run clientServer.go
+```
+
+Navigate to http://localhost:9090 to see a list of files in the server and
+navigate to a `.html` file in any of the examples to open a client.
+
+To start the network, run `run.sh` in the console.
+
+
+## `wasm_exec.js`
+
+`wasm_exec.js` is provided by Go and is used to import the WebAssembly module in
+the browser. It can be retrieved from Go using the following command.
+
+```shell
+$ cp "$(go env GOROOT)/misc/wasm/wasm_exec.js" test/assets/
+```
+
+Note that this repository makes edits to `wasm_exec.js` and you must either use
+the one in this repository or add the following lines in the `go` `importObject`
+on `global.Go`.
+
+```javascript
+global.Go = class {
+    constructor() {
+        // ...
+        this.importObject = {
+            go: {
+                // ...
+                // func Throw(exception string, message string)
+                'gitlab.com/elixxir/xxdk-wasm/utils.throw': (sp) => {
+                    const exception = loadString(sp + 8)
+                    const message = loadString(sp + 24)
+                    throw globalThis[exception](message)
+                },
+            }
+        }
+    }
+}
+```
diff --git a/wasm/client-registrar.yaml b/wasm/client-registrar.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..bfc9d3b0cd31cf3ed5d8d7728c23f89f4caeb567
--- /dev/null
+++ b/wasm/client-registrar.yaml
@@ -0,0 +1,36 @@
+# ==================================
+# Client Registrar Configuration
+# ==================================
+
+# Log message level (0 = info, 1 = debug, >1 = trace)
+logLevel: 0
+# Path to log file
+logPath: "results/client-registrar.log"
+
+# Public address, used in NDF it gives to client
+publicAddress: "localhost:11421"
+# The listening port of this server
+port: 11421
+
+# === REQUIRED FOR ENABLING TLS ===
+# Path to the registration server private key file
+keyPath: "../keys/cmix.rip.key"
+# Path to the registration server certificate file
+certPath: "../keys/cmix.rip.crt"
+
+# Maximum number of connections per period
+userRegCapacity: 1000
+# How often the number of connections is reset
+userRegLeakPeriod: "24h"
+
+# Database connection information
+dbUsername: "cmix"
+dbPassword: ""
+dbName: "cmix_server"
+dbAddress: ""
+
+# List of client codes to be added to the database (for testing)
+clientRegCodes:
+  - "AAAA"
+  - "BBBB"
+  - "CCCC"
diff --git a/wasm/gateway-1.yaml b/wasm/gateway-1.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..2a38d5c404db1dda39924d10d2ee081abd7303cc
--- /dev/null
+++ b/wasm/gateway-1.yaml
@@ -0,0 +1,45 @@
+# START YAML ===
+################################################################################
+## Copyright © 2019 Privategrity Corporation                                   #
+##                                                                             #
+## All rights reserved.                                                        #
+################################################################################
+
+# Output log file
+log: "results/gateways/gateway-1.log"
+
+# Log message level
+logLevel: 1
+
+# The public IP address and port of the node associated with this gateway
+cmixAddress: "127.0.0.1:50000"
+
+# Port for Gateway to listen on. Gateway must be the only listener on this port.
+# Required field.
+port: 8440
+
+# The public IPv4 address of the Gateway, as reported to the network, to use
+# instead of dynamically looking up Gateway's own IP address. If a port is not
+# included, then the port flag is used instead.
+overridePublicIP: "localhost"
+
+# The number of seconds a message should remain in the globals before being
+# deleted from the user's message queue
+messageTimeout: "1800s"
+
+# Path to where the IDF is saved. This is used by the wrapper management script.
+idfPath: "results/gateways/gateway-1-idf.json"
+
+# === REQUIRED FOR ENABLING TLS ===
+# Path to the private key file
+keyPath: "../keys/cmix.rip.key"
+# Path to the certificate file
+certPath: "../keys/cmix.rip.crt"
+# Path to the permissioning certificate
+schedulingCertPath: "../keys/cmix.rip.crt"
+# Path to the certificate file
+cmixCertPath: "../keys/cmix.rip.crt"
+
+devMode: true
+
+# === END YAML
diff --git a/wasm/gateway-2.yaml b/wasm/gateway-2.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..574bbef1056c8082c046a8e929bd9c9f9c5faa76
--- /dev/null
+++ b/wasm/gateway-2.yaml
@@ -0,0 +1,45 @@
+# START YAML ===
+################################################################################
+## Copyright © 2019 Privategrity Corporation                                   #
+##                                                                             #
+## All rights reserved.                                                        #
+################################################################################
+
+# Output log file
+log: "results/gateways/gateway-2.log"
+
+# Log message level
+logLevel: 1
+
+# The public IP address and port of the node associated with this gateway
+cmixAddress: "127.0.0.1:50001"
+
+# Port for Gateway to listen on. Gateway must be the only listener on this port.
+# Required field.
+port: 8441
+
+# The public IPv4 address of the Gateway, as reported to the network, to use
+# instead of dynamically looking up Gateway's own IP address. If a port is not
+# included, then the port flag is used instead.
+overridePublicIP: "localhost"
+
+# The number of seconds a message should remain in the globals before being
+# deleted from the user's message queue
+messageTimeout: "1800s"
+
+# Path to where the IDF is saved. This is used by the wrapper management script.
+idfPath: "results/gateways/gateway-2-idf.json"
+
+# === REQUIRED FOR ENABLING TLS ===
+# Path to the private key file
+keyPath: "../keys/cmix.rip.key"
+# Path to the certificate file
+certPath: "../keys/cmix.rip.crt"
+# Path to the permissioning certificate
+schedulingCertPath: "../keys/cmix.rip.crt"
+# Path to the certificate file
+cmixCertPath: "../keys/cmix.rip.crt"
+
+devMode: true
+
+# === END YAML
diff --git a/wasm/gateway-3.yaml b/wasm/gateway-3.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..42c61354d38a0cd730bc60aee811472734d5617c
--- /dev/null
+++ b/wasm/gateway-3.yaml
@@ -0,0 +1,45 @@
+# START YAML ===
+################################################################################
+## Copyright © 2019 Privategrity Corporation                                   #
+##                                                                             #
+## All rights reserved.                                                        #
+################################################################################
+
+# Output log file
+log: "results/gateways/gateway-3.log"
+
+# Log message level
+logLevel: 1
+
+# The public IP address and port of the node associated with this gateway
+cmixAddress: "127.0.0.1:50002"
+
+# Port for Gateway to listen on. Gateway must be the only listener on this port.
+# Required field.
+port: 8442
+
+# The public IPv4 address of the Gateway, as reported to the network, to use
+# instead of dynamically looking up Gateway's own IP address. If a port is not
+# included, then the port flag is used instead.
+overridePublicIP: "localhost"
+
+# The number of seconds a message should remain in the globals before being
+# deleted from the user's message queue
+messageTimeout: "1800s"
+
+# Path to where the IDF is saved. This is used by the wrapper management script.
+idfPath: "results/gateways/gateway-3-idf.json"
+
+# === REQUIRED FOR ENABLING TLS ===
+# Path to the private key file
+keyPath: "../keys/cmix.rip.key"
+# Path to the certificate file
+certPath: "../keys/cmix.rip.crt"
+# Path to the permissioning certificate
+schedulingCertPath: "../keys/cmix.rip.crt"
+# Path to the certificate file
+cmixCertPath: "../keys/cmix.rip.crt"
+
+devMode: true
+
+# === END YAML
diff --git a/wasm/gateway-4.yaml b/wasm/gateway-4.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..cdfc018cd8f50d3c691bdc2b006140057f628ea9
--- /dev/null
+++ b/wasm/gateway-4.yaml
@@ -0,0 +1,45 @@
+# START YAML ===
+################################################################################
+## Copyright © 2019 Privategrity Corporation                                   #
+##                                                                             #
+## All rights reserved.                                                        #
+################################################################################
+
+# Output log file
+log: "results/gateways/gateway-4.log"
+
+# Log message level
+logLevel: 1
+
+# The public IP address and port of the node associated with this gateway
+cmixAddress: "127.0.0.1:50003"
+
+# Port for Gateway to listen on. Gateway must be the only listener on this port.
+# Required field.
+port: 8443
+
+# The public IPv4 address of the Gateway, as reported to the network, to use
+# instead of dynamically looking up Gateway's own IP address. If a port is not
+# included, then the port flag is used instead.
+overridePublicIP: "localhost"
+
+# The number of seconds a message should remain in the globals before being
+# deleted from the user's message queue
+messageTimeout: "1800s"
+
+# Path to where the IDF is saved. This is used by the wrapper management script.
+idfPath: "results/gateways/gateway-4-idf.json"
+
+# === REQUIRED FOR ENABLING TLS ===
+# Path to the private key file
+keyPath: "../keys/cmix.rip.key"
+# Path to the certificate file
+certPath: "../keys/cmix.rip.crt"
+# Path to the permissioning certificate
+schedulingCertPath: "../keys/cmix.rip.crt"
+# Path to the certificate file
+cmixCertPath: "../keys/cmix.rip.crt"
+
+devMode: true
+
+# === END YAML
diff --git a/wasm/gateway-5.yaml b/wasm/gateway-5.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..3bd10a8d15052fa9ef16a1e342b0977363c64847
--- /dev/null
+++ b/wasm/gateway-5.yaml
@@ -0,0 +1,45 @@
+# START YAML ===
+################################################################################
+## Copyright © 2019 Privategrity Corporation                                   #
+##                                                                             #
+## All rights reserved.                                                        #
+################################################################################
+
+# Output log file
+log: "results/gateways/gateway-5.log"
+
+# Log message level
+logLevel: 1
+
+# The public IP address and port of the node associated with this gateway
+cmixAddress: "127.0.0.1:50004"
+
+# Port for Gateway to listen on. Gateway must be the only listener on this port.
+# Required field.
+port: 8444
+
+# The public IPv4 address of the Gateway, as reported to the network, to use
+# instead of dynamically looking up Gateway's own IP address. If a port is not
+# included, then the port flag is used instead.
+overridePublicIP: "localhost"
+
+# The number of seconds a message should remain in the globals before being
+# deleted from the user's message queue
+messageTimeout: "1800s"
+
+# Path to where the IDF is saved. This is used by the wrapper management script.
+idfPath: "results/gateways/gateway-5-idf.json"
+
+# === REQUIRED FOR ENABLING TLS ===
+# Path to the private key file
+keyPath: "../keys/cmix.rip.key"
+# Path to the certificate file
+certPath: "../keys/cmix.rip.crt"
+# Path to the permissioning certificate
+schedulingCertPath: "../keys/cmix.rip.crt"
+# Path to the certificate file
+cmixCertPath: "../keys/cmix.rip.crt"
+
+devMode: true
+
+# === END YAML
diff --git a/wasm/network.sh b/wasm/network.sh
new file mode 100644
index 0000000000000000000000000000000000000000..fa821fba884d86308475a246f3f4154b1226f11e
--- /dev/null
+++ b/wasm/network.sh
@@ -0,0 +1,109 @@
+# This script is used to start a basic 5 node network for running clients on. It is meant to be `source`'d into a script
+# which will run clients on the network, such as `client-session-tests.sh` or the main `run.sh`. 
+# 
+# You **must** source it, because otherwise the `trap finish EXIT` instruction will cause the network to stop when 
+# network.sh returns to your script or shell. Sourcing it will "import" the commands into your script instead, causing 
+# the trap instruction to stop the network when your script/shell exits.
+
+echo "STARTING SERVERS..."
+
+# Copy udbContact into place when running locally.
+cp udbContact.bin results/udbContact.bin
+
+PERMCMD="../bin/permissioning --logLevel $DEBUGLEVEL -c permissioning.yaml "
+$PERMCMD > results/permissioning-console.txt 2>&1 &
+PIDVAL=$!
+echo "$PERMCMD -- $PIDVAL"
+
+
+# Run Client Registrar
+CLIENT_REG_CMD="../bin/client-registrar \
+-l 2 -c client-registrar.yaml"
+$CLIENT_REG_CMD > results/client-registrat-console.txt 2>&1 &
+PIDVAL=$!
+echo "$CLIENT_REG_CMD -- $PIDVAL"
+
+for SERVERID in $(seq 5 -1 1)
+do
+    IDX=$(($SERVERID - 1))
+    SERVERCMD="../bin/server --logLevel $DEBUGLEVEL --config server-$SERVERID.yaml"
+    if [ $SERVERID -eq 5 ] && [ -n "$NSYSENABLED" ]
+    then
+        SERVERCMD="nsys profile --session-new=gputest --trace=cuda -o server-$SERVERID $SERVERCMD"
+    fi
+    $SERVERCMD > $SERVERLOGS/server-$SERVERID-console.txt 2>&1 &
+    PIDVAL=$!
+    echo "$SERVERCMD -- $PIDVAL"
+done
+
+# Start gateways
+for GWID in $(seq 5 -1 1)
+do
+    IDX=$(($GWID - 1))
+    GATEWAYCMD="../bin/gateway --logLevel $DEBUGLEVEL --config gateway-$GWID.yaml"
+    $GATEWAYCMD > $GATEWAYLOGS/gateway-$GWID-console.txt 2>&1 &
+    PIDVAL=$!
+    echo "$GATEWAYCMD -- $PIDVAL"
+done
+
+jobs -p > results/serverpids
+
+finish() {
+    echo "STOPPING SERVERS AND GATEWAYS..."
+    if [ -n "$NSYSENABLED" ]
+    then
+        nsys stop --session=gputest
+    fi
+    # NOTE: jobs -p doesn't work in a signal handler
+    for job in $(cat results/serverpids)
+    do
+        echo "KILLING $job"
+        kill $job || true
+    done
+
+    sleep 5
+
+    for job in $(cat results/serverpids)
+    do
+        echo "KILL -9 $job"
+        kill -9 $job || true
+    done
+    #tail $SERVERLOGS/*
+    #tail $CLIENTCLEAN/*
+    #diff -aruN clients.goldoutput $CLIENTCLEAN
+}
+
+trap finish EXIT
+trap finish INT
+
+# Sleeps can die in a fire on the sun, we wait for the servers to start running
+# rounds
+rm rid.txt || true
+touch rid.txt
+cnt=0
+echo -n "Waiting for a round to run"
+while [ ! -s rid.txt ] && [ $cnt -lt 120 ]; do
+    sleep 1
+    grep -a "RID 1 ReceiveFinishRealtime END" results/servers/server-* > rid.txt || true
+    cnt=$(($cnt + 1))
+    echo -n "."
+done
+
+# Start a user discovery bot server
+echo "STARTING UDB..."
+UDBCMD="../bin/udb --logLevel $DEBUGLEVEL --skipVerification --protoUserPath	udbProto.json --config udb.yaml -l 1"
+$UDBCMD >> $UDBOUT 2>&1 &
+PIDVAL=$!
+echo $PIDVAL >> results/serverpids
+echo "$UDBCMD -- $PIDVAL"
+rm rid.txt || true
+while [ ! -s rid.txt ] && [ $cnt -lt 30 ]; do
+    sleep 1
+    grep -a "Sending Poll message" results/udb-console.txt > rid.txt || true
+    cnt=$(($cnt + 1))
+    echo -n "."
+done
+
+echo "localhost:8440" > results/startgwserver.txt
+
+echo "DONE LETS DO STUFF"
\ No newline at end of file
diff --git a/wasm/permissioning.yaml b/wasm/permissioning.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..e6189cd1987a0c274b279a35888622c9652cd5a6
--- /dev/null
+++ b/wasm/permissioning.yaml
@@ -0,0 +1,90 @@
+# ==================================
+# Permissioning Server Configuration
+# ==================================
+
+# Log message level
+logLevel: 1
+
+# Path to log file
+logPath: "results/permissioning.log"
+
+# The listening pofrt of this server
+port: 18000
+
+# Database connection information
+dbUsername: ""
+dbPassword: ""
+dbName: ""
+dbAddress: "0.0.0.0:6969"
+
+minimumNodes: 5
+
+minGatewayVersion: "3.0.0"
+minServerVersion:  "3.0.0"
+minClientVersion: "4.0.0"
+nodeMetricInterval: 3
+
+# For testing, use the sequence as the country code. Do not use the geobinning database
+disableGeoBinning: true
+
+# For testing, do not exclude node or gateway IPs which are local to the machine
+allowLocalIPs: true
+# Disable pruning of NDF for offline nodes
+# if set to false, network will sleep for five minutes on start
+disableNDFPruning: true
+permissiveIPChecking: true
+
+# How long rounds will be tracked by gateways. Rounds (and messages as an extension)
+# prior to this period are not guaranteed to be delivered to clients.
+# Expects duration in"h". (Defaults to 1 weeks (168 hours)
+messageRetentionLimit: "168h"
+
+# Path to the file containing the round ID
+roundIdPath: "results/roundId.txt"
+
+# Path to the file containing the update ID
+updateIdPath: "results/updateId.txt"
+
+# Public address used in NDF to give to client
+registrationAddress: "localhost:11421"
+
+# Path to whitelisted IPs for client ratelimiting
+whitelistedIpAddressesPath: "whitelist.txt"
+
+# === REQUIRED FOR ENABLING TLS ===
+# Path to the registration server private key file
+keyPath: "../keys/cmix.rip.key"
+# Path to the registration server certificate file
+certPath: "../keys/cmix.rip.crt"
+
+fullNdfOutputPath: "results/permissions-ndfoutput.json"
+
+# === REQUIRED FOR ENABLING TLS ===
+# Path to the UDB certificate file
+udbCertPath: "../keys/cmix.rip.crt"
+# "Location of the user discovery contact file.
+udContactPath: "results/udbContact.bin"
+udbAddress: "127.0.0.1:18001"
+
+# Time interval (in minutes) in which the database is
+# checked for banned nodes
+BanTrackerInterval: "3"
+
+groups:
+  cmix:
+    prime: "FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3DC2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F83655D23DCA3AD961C62F356208552BB9ED529077096966D670C354E4ABC9804F1746C08CA18217C32905E462E36CE3BE39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF6955817183995497CEA956AE515D2261898FA051015728E5A8AAAC42DAD33170D04507A33A85521ABDF1CBA64ECFB850458DBEF0A8AEA71575D060C7DB3970F85A6E1E4C7ABF5AE8CDB0933D71E8C94E04A25619DCEE3D2261AD2EE6BF12FFA06D98A0864D87602733EC86A64521F2B18177B200CBBE117577A615D6C770988C0BAD946E208E24FA074E5AB3143DB5BFCE0FD108E4B82D120A92108011A723C12A787E6D788719A10BDBA5B2699C327186AF4E23C1A946834B6150BDA2583E9CA2AD44CE8DBBBC2DB04DE8EF92E8EFC141FBECAA6287C59474E6BC05D99B2964FA090C3A2233BA186515BE7ED1F612970CEE2D7AFB81BDD762170481CD0069127D5B05AA993B4EA988D8FDDC186FFB7DC90A6C08F4DF435C934063199FFFFFFFFFFFFFFFF"
+    generator: "2"
+  e2e:
+    prime: "E2EE983D031DC1DB6F1A7A67DF0E9A8E5561DB8E8D49413394C049B7A8ACCEDC298708F121951D9CF920EC5D146727AA4AE535B0922C688B55B3DD2AEDF6C01C94764DAB937935AA83BE36E67760713AB44A6337C20E7861575E745D31F8B9E9AD8412118C62A3E2E29DF46B0864D0C951C394A5CBBDC6ADC718DD2A3E041023DBB5AB23EBB4742DE9C1687B5B34FA48C3521632C4A530E8FFB1BC51DADDF453B0B2717C2BC6669ED76B4BDD5C9FF558E88F26E5785302BEDBCA23EAC5ACE92096EE8A60642FB61E8F3D24990B8CB12EE448EEF78E184C7242DD161C7738F32BF29A841698978825B4111B4BC3E1E198455095958333D776D8B2BEEED3A1A1A221A6E37E664A64B83981C46FFDDC1A45E3D5211AAF8BFBC072768C4F50D7D7803D2D4F278DE8014A47323631D7E064DE81C0C6BFA43EF0E6998860F1390B5D3FEACAF1696015CB79C3F9C2D93D961120CD0E5F12CBB687EAB045241F96789C38E89D796138E6319BE62E35D87B1048CA28BE389B575E994DCA755471584A09EC723742DC35873847AEF49F66E43873"
+    generator: "2"
+
+
+# Path to file with config for scheduling algorithm within the user directory
+schedulingConfigPath: "registration.json"
+
+# Path to JSON file with list of Node registration codes (in order of network
+# placement)
+RegCodesFilePath: "regCodes.json"
+
+# Set address space size for ephemeral IDs
+addressSpace: 32
diff --git a/wasm/regCodes.json b/wasm/regCodes.json
new file mode 100644
index 0000000000000000000000000000000000000000..fea6a9d6e7f50348e01fb324a74de2e200506b35
--- /dev/null
+++ b/wasm/regCodes.json
@@ -0,0 +1 @@
+[{"RegCode": "qpol", "Order": "CR"},{"RegCode": "yiiq", "Order": "GB"},{"RegCode": "vydz", "Order": "SK"},{"RegCode": "gwxs", "Order": "HR"},{"RegCode": "nahv", "Order": "IQ"}, {"RegCode": "doko", "Order": "RU"}]
diff --git a/wasm/registration.json b/wasm/registration.json
new file mode 100644
index 0000000000000000000000000000000000000000..0c4a121947b1427f52c71c26eb83aaa5e03b7af7
--- /dev/null
+++ b/wasm/registration.json
@@ -0,0 +1,12 @@
+{
+    "TeamSize": 5,
+    "PrecomputationTimeout": 30000,
+    "RealtimeTimeout": 15000,
+    "DebugTrackRounds": true,
+    "BatchSize": 32,
+    "MinimumDelay": 1000,
+    "RealtimeDelay": 2000,
+    "Threshold":     0.3,
+    "NodeCleanUpInterval": 180000,
+    "ResourceQueueTimeout": 300000
+}
diff --git a/wasm/run.sh b/wasm/run.sh
new file mode 100644
index 0000000000000000000000000000000000000000..790799ec8540405f614c098ca9ad4ea5c9dbaba1
--- /dev/null
+++ b/wasm/run.sh
@@ -0,0 +1,43 @@
+#!/bin/bash
+
+set -e
+
+# --- Define variables to use for the test & local network ---
+
+DEBUGLEVEL=${DEBUGLEVEL-1}
+SERVERLOGS=results/servers
+GATEWAYLOGS=results/gateways
+UDBOUT=results/udb-console.txt
+
+# --- Setup a local network ---
+
+rm -rf results.bak || true
+mv results results.bak || rm -rf results || true
+rm -rf client*.log blob* rick*.bin ben*.bin
+mkdir results
+
+mkdir -p $SERVERLOGS
+mkdir -p $GATEWAYLOGS
+
+# Start the network
+source network.sh
+
+# This remains commented out while using HTTP
+#echo "DOWNLOADING TLS Cert..."
+#CMD="openssl s_client -showcerts -connect $(tr -d '[:space:]' <results/startgwserver.txt)"
+#echo "$CMD"
+#eval "$CMD" </dev/null 2>&1 >"results/startgwcert.bin"
+#CMD="cat results/startgwcert.bin | openssl x509 -outform PEM"
+#echo "$CMD"
+#eval "$CMD" >"results/startgwcert.pem"
+#head "results/startgwcert.pem"
+#
+#echo "DOWNLOADING NDF..."
+#CLIENTCMD="../bin/client getndf -v $DEBUGLEVEL --gwhost $(tr -d '[:space:]' <results/startgwserver.txt) --cert results/startgwcert.pem"
+#eval "$CLIENTCMD" >>results/ndf.json 2>&1 &
+#echo "$CLIENTCMD -- $PIDVAL"
+#wait $PIDVAL
+
+while :; do
+    sleep 10
+done
diff --git a/wasm/server-1.yaml b/wasm/server-1.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..fc8b704b210b9dfa19e88ac61bacaa61e1148e3d
--- /dev/null
+++ b/wasm/server-1.yaml
@@ -0,0 +1,39 @@
+# START YAML ===
+################################################################################
+## Copyright © 2019 Privategrity Corporation                                   #
+##                                                                             #
+## All rights reserved.                                                        #
+################################################################################
+# registration code used for first time registration. Unique. Provided by xx network
+registrationCode: "qpol"
+useGPU: false
+devMode: true
+rawPermAddr: true
+logLevel: 1
+cmix:
+    paths:
+        idf:  "results/servers/nodeID-1.json"
+        cert: "../keys/cmix.rip.crt"
+        key:  "../keys/cmix.rip.key"
+        log:  "results/servers/server-1.log"
+        errOutput: "results/servers/server-1.err"
+        ipListOutput: "results/servers/iplist-1.txt"
+    port: 50000
+    overridePublicIP: "127.0.0.1"
+database:
+    name: "node1"
+    username: "cmix_server"
+    password: ""
+    address: ""
+gateway:
+    paths:
+        cert: "../keys/cmix.rip.crt"
+scheduling:
+  paths:
+    cert: "../keys/cmix.rip.crt"
+  address: "127.0.0.1:18000"
+metrics:
+  # location of stored metrics data. Modification to set to permissioning
+  # server instead of saving will be made at a later date
+  log:  "results/servers/server-1-metrics.log"
+# === END YAML
diff --git a/wasm/server-2.yaml b/wasm/server-2.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..37765bc314816bd9928ff96803653145aeea2671
--- /dev/null
+++ b/wasm/server-2.yaml
@@ -0,0 +1,39 @@
+# START YAML ===
+################################################################################
+## Copyright © 2019 Privategrity Corporation                                   #
+##                                                                             #
+## All rights reserved.                                                        #
+################################################################################
+# registration code used for first time registration. Unique. Provided by xx network
+registrationCode: "yiiq"
+useGPU: false
+devMode: true
+rawPermAddr: true
+logLevel: 1
+cmix:
+    paths:
+        idf:  "results/servers/nodeID-2.json"
+        cert: "../keys/cmix.rip.crt"
+        key:  "../keys/cmix.rip.key"
+        log:  "results/servers/server-2.log"
+        errOutput: "results/servers/server-2.err"
+        ipListOutput: "results/servers/iplist-2.txt"
+    port: 50001
+    overridePublicIP: "127.0.0.1"
+database:
+    name: "node2"
+    username: "cmix_server"
+    password: ""
+    address: ""
+gateway:
+    paths:
+        cert: "../keys/cmix.rip.crt"
+scheduling:
+  paths:
+    cert: "../keys/cmix.rip.crt"
+  address: "127.0.0.1:18000"
+metrics:
+  # location of stored metrics data. Modification to set to permissioning
+  # server instead of saving will be made at a later date
+  log:  "results/servers/server-2-metrics.log"
+# === END YAML
diff --git a/wasm/server-3.yaml b/wasm/server-3.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..f17b4255f1b9519ee5c3e4e787264a4cb1f3a645
--- /dev/null
+++ b/wasm/server-3.yaml
@@ -0,0 +1,39 @@
+# START YAML ===
+################################################################################
+## Copyright © 2019 Privategrity Corporation                                   #
+##                                                                             #
+## All rights reserved.                                                        #
+################################################################################
+# registration code used for first time registration. Unique. Provided by xx network
+registrationCode: "vydz"
+useGPU: false
+devMode: true
+rawPermAddr: true
+logLevel: 1
+cmix:
+    paths:
+        idf:  "results/servers/nodeID-3.json"
+        cert: "../keys/cmix.rip.crt"
+        key:  "../keys/cmix.rip.key"
+        log:  "results/servers/server-3.log"
+        errOutput: "results/servers/server-3.err"
+        ipListOutput: "results/servers/iplist-3.txt"
+    port: 50002
+    overridePublicIP: "127.0.0.1"
+database:
+    name: "node3"
+    username: "cmix_server"
+    password: ""
+    address: ""
+gateway:
+    paths:
+        cert: "../keys/cmix.rip.crt"
+scheduling:
+  paths:
+    cert: "../keys/cmix.rip.crt"
+  address: "127.0.0.1:18000"
+metrics:
+  # location of stored metrics data. Modification to set to permissioning
+  # server instead of saving will be made at a later date
+  log:  "results/servers/server-3-metrics.log"
+# === END YAML
diff --git a/wasm/server-4.yaml b/wasm/server-4.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..b13f6fd5a329fb44e4748a76f4aed4407482523a
--- /dev/null
+++ b/wasm/server-4.yaml
@@ -0,0 +1,39 @@
+# START YAML ===
+################################################################################
+## Copyright © 2019 Privategrity Corporation                                   #
+##                                                                             #
+## All rights reserved.                                                        #
+################################################################################
+# registration code used for first time registration. Unique. Provided by xx network
+registrationCode: "gwxs"
+useGPU: false
+devMode: true
+rawPermAddr: true
+logLevel: 1
+cmix:
+    paths:
+        idf:  "results/servers/nodeID-4.json"
+        cert: "../keys/cmix.rip.crt"
+        key:  "../keys/cmix.rip.key"
+        log:  "results/servers/server-4.log"
+        errOutput: "results/servers/server-4.err"
+        ipListOutput: "results/servers/iplist-4.txt"
+    port: 50003
+    overridePublicIP: "127.0.0.1"
+database:
+    name: "node4"
+    username: "cmix_server"
+    password: ""
+    address: ""
+gateway:
+    paths:
+        cert: "../keys/cmix.rip.crt"
+scheduling:
+  paths:
+    cert: "../keys/cmix.rip.crt"
+  address: "127.0.0.1:18000"
+metrics:
+  # location of stored metrics data. Modification to set to permissioning
+  # server instead of saving will be made at a later date
+  log:  "results/servers/server-4-metrics.log"
+# === END YAML
diff --git a/wasm/server-5.yaml b/wasm/server-5.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..d820326869ac148f9823eb929ed686719970c4c8
--- /dev/null
+++ b/wasm/server-5.yaml
@@ -0,0 +1,39 @@
+# START YAML ===
+################################################################################
+## Copyright © 2019 Privategrity Corporation                                   #
+##                                                                             #
+## All rights reserved.                                                        #
+################################################################################
+# registration code used for first time registration. Unique. Provided by xx network
+registrationCode: "nahv"
+useGPU: false
+devMode: true
+rawPermAddr: true
+logLevel: 1
+cmix:
+    paths:
+        idf:  "results/servers/nodeID-5.json"
+        cert: "../keys/cmix.rip.crt"
+        key:  "../keys/cmix.rip.key"
+        log:  "results/servers/server-5.log"
+        errOutput: "results/servers/server-5.err"
+        ipListOutput: "results/servers/iplist-5.txt"
+    port: 50004
+    overridePublicIP: "127.0.0.1"
+database:
+    name: "node5"
+    username: "cmix_server"
+    password: ""
+    address: ""
+gateway:
+    paths:
+        cert: "../keys/cmix.rip.crt"
+scheduling:
+  paths:
+    cert: "../keys/cmix.rip.crt"
+  address: "127.0.0.1:18000"
+metrics:
+  # location of stored metrics data. Modification to set to permissioning
+  # server instead of saving will be made at a later date
+  log:  "results/servers/server-5-metrics.log"
+# === END YAML
diff --git a/wasm/testServer/assets/styles.css b/wasm/testServer/assets/styles.css
new file mode 100644
index 0000000000000000000000000000000000000000..292d39800aebbc29f525f89c7fe21c1a10c6080b
--- /dev/null
+++ b/wasm/testServer/assets/styles.css
@@ -0,0 +1,138 @@
+/******************************************************************************
+ * Copyright © 2022 xx foundation                                             *
+ *                                                                            *
+ * Use of this source code is governed by a license that can be found in the  *
+ * LICENSE file.                                                              *
+ ******************************************************************************/
+
+/******************************************************************************
+ * CSS Reset (meyerweb reset, v2.0 | 20110126)                                *
+ ******************************************************************************/
+
+html, body, div, span, applet, object, iframe,
+h1, h2, h3, h4, h5, h6, p, blockquote, pre,
+a, abbr, acronym, address, big, cite, code,
+del, dfn, em, img, ins, kbd, q, s, samp,
+small, strike, strong, sub, sup, tt, var,
+b, u, i, center,
+dl, dt, dd, ol, ul, li,
+fieldset, form, label, legend,
+table, caption, tbody, tfoot, thead, tr, th, td,
+article, aside, canvas, details, embed,
+figure, figcaption, footer, header, hgroup,
+menu, nav, output, ruby, section, summary,
+time, mark, audio, video {
+    margin: 0;
+    padding: 0;
+    border: 0;
+    font-size: 100%;
+    font: inherit;
+    vertical-align: baseline;
+}
+
+/* HTML5 display-role reset for older browsers */
+article, aside, details, figcaption, figure,
+footer, header, hgroup, menu, nav, section {
+    display: block;
+}
+
+body {
+    line-height: 1;
+}
+
+ol, ul {
+    list-style: none;
+}
+
+blockquote, q {
+    quotes: none;
+}
+
+blockquote:before, blockquote:after,
+q:before, q:after {
+    content: '';
+    content: none;
+}
+
+table {
+    border-collapse: collapse;
+    border-spacing: 0;
+}
+
+/******************************************************************************
+ * Global Styles                                                              *
+ ******************************************************************************/
+body {
+    font-family: "Roboto", "Franklin Gothic Medium", Tahoma, sans-serif;
+    padding:25px;
+}
+
+form {
+}
+
+form div {
+    margin:1em 0;
+}
+
+samp, var, code {
+    border-radius:.385em;
+    font-family:Consolas, monaco, monospace;
+    white-space:pre;
+    background:#e5e5e5;
+    color:#000;
+    font-size:1em;
+    padding:.125em .25em;
+}
+
+div.toolbar {
+    float:right;
+    display: flex;
+}
+
+div.toolbar input {
+    margin:2px;
+}
+
+/*========================================
+  Headers
+  ========================================*/
+h1, h2, h3, h4, h5, h6 {
+    color:#000;
+    font-weight:700;
+    line-height:1em;
+}
+
+h1 {
+    font-size:3em;
+    margin:0.5em 0;
+}
+
+/*========================================
+  HTML Console
+  ========================================*/
+
+div#logOutput {
+    font-family: "Roboto Mono", monaco, Consolas, "Lucida Console", monospace;
+    display: block;
+    font-size: 1em;
+    line-height: 1.5em;
+    border-radius:9px;
+    background:#f5f5f5;
+    border:1px solid #6ce26c;
+    border-left:3px solid #6ce26c;
+    margin:25px 0;
+    max-height:60vh;
+    padding:15px;
+    white-space:pre-wrap;
+    width:auto;
+    overflow-y: scroll;
+}
+
+div#logOutput p{
+    padding:0.1em 0;
+}
+
+div#logOutput p.error{
+    color: #ff0000;
+    font-weight: bold;
+}
\ No newline at end of file
diff --git a/wasm/testServer/assets/utils.js b/wasm/testServer/assets/utils.js
new file mode 100644
index 0000000000000000000000000000000000000000..b657062c12ea1a6de7502ac7c52f8f1099a7f4b0
--- /dev/null
+++ b/wasm/testServer/assets/utils.js
@@ -0,0 +1,55 @@
+////////////////////////////////////////////////////////////////////////////////
+// Copyright © 2022 xx foundation                                             //
+//                                                                            //
+// Use of this source code is governed by a license that can be found in the  //
+// LICENSE file.                                                              //
+////////////////////////////////////////////////////////////////////////////////
+
+// Function to download data to a file.
+function download(filename, data) {
+    const file = new Blob([data], {type: 'text/plain'});
+    let a = document.createElement("a"),
+        url = URL.createObjectURL(file);
+    a.href = url;
+    a.download = filename;
+    document.body.appendChild(a);
+    a.click();
+    setTimeout(function() {
+        document.body.removeChild(a);
+        window.URL.revokeObjectURL(url);
+    }, 0);
+}
+
+// sleepUntil waits until the condition f is met or until the timeout is
+// reached.
+async function sleepUntil(f, timeoutMs) {
+    return new Promise((resolve, reject) => {
+        const timeWas = new Date();
+        const wait = setInterval(function() {
+            if (f()) {
+                console.log("resolved after", new Date() - timeWas, "ms");
+                clearInterval(wait);
+                resolve();
+            } else if (new Date() - timeWas > timeoutMs) { // Timeout
+                console.log("rejected after", new Date() - timeWas, "ms");
+                clearInterval(wait);
+                reject();
+            }
+        }, 20);
+    });
+}
+
+// newHtmlConsole returns an object that allows for printing log messages or
+// error messages to an element.
+function newHtmlConsole(elem) {
+    return {
+        log: function (message) {
+            console.log(message)
+            elem.innerHTML += "<p>" + message + "</p>"
+        },
+        error: function (message) {
+            console.error(message)
+            elem.innerHTML += "<p class='error'>" + message + "</p>"
+        }
+    };
+}
\ No newline at end of file
diff --git a/wasm/testServer/assets/wasm_exec.js b/wasm/testServer/assets/wasm_exec.js
new file mode 100644
index 0000000000000000000000000000000000000000..bba4a84051b5fb6bf284df03d21556eb56500062
--- /dev/null
+++ b/wasm/testServer/assets/wasm_exec.js
@@ -0,0 +1,650 @@
+/*
+ * Copyright © 2020 xx network SEZC                                           ///
+ *                                                                            ///
+ * Use of this source code is governed by a license that can be found in the  ///
+ * LICENSE file                                                               ///
+ */
+
+// Copyright 2018 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+(() => {
+	// Map multiple JavaScript environments to a single common API,
+	// preferring web standards over Node.js API.
+	//
+	// Environments considered:
+	// - Browsers
+	// - Node.js
+	// - Electron
+	// - Parcel
+	// - Webpack
+
+	if (typeof global !== "undefined") {
+		// global already exists
+	} else if (typeof window !== "undefined") {
+		window.global = window;
+	} else if (typeof self !== "undefined") {
+		self.global = self;
+	} else {
+		throw new Error("cannot export Go (neither global, window nor self is defined)");
+	}
+
+	if (!global.require && typeof require !== "undefined") {
+		global.require = require;
+	}
+
+	if (!global.fs && global.require) {
+		const fs = require("fs");
+		if (typeof fs === "object" && fs !== null && Object.keys(fs).length !== 0) {
+			global.fs = fs;
+		}
+	}
+
+	const enosys = () => {
+		const err = new Error("not implemented");
+		err.code = "ENOSYS";
+		return err;
+	};
+
+	if (!global.fs) {
+		let outputBuf = "";
+		global.fs = {
+			constants: { O_WRONLY: -1, O_RDWR: -1, O_CREAT: -1, O_TRUNC: -1, O_APPEND: -1, O_EXCL: -1 }, // unused
+			writeSync(fd, buf) {
+				outputBuf += decoder.decode(buf);
+				const nl = outputBuf.lastIndexOf("\n");
+				if (nl != -1) {
+					console.log(outputBuf.substr(0, nl));
+					outputBuf = outputBuf.substr(nl + 1);
+				}
+				return buf.length;
+			},
+			write(fd, buf, offset, length, position, callback) {
+				if (offset !== 0 || length !== buf.length || position !== null) {
+					callback(enosys());
+					return;
+				}
+				const n = this.writeSync(fd, buf);
+				callback(null, n);
+			},
+			chmod(path, mode, callback) { callback(enosys()); },
+			chown(path, uid, gid, callback) { callback(enosys()); },
+			close(fd, callback) { callback(enosys()); },
+			fchmod(fd, mode, callback) { callback(enosys()); },
+			fchown(fd, uid, gid, callback) { callback(enosys()); },
+			fstat(fd, callback) { callback(enosys()); },
+			fsync(fd, callback) { callback(null); },
+			ftruncate(fd, length, callback) { callback(enosys()); },
+			lchown(path, uid, gid, callback) { callback(enosys()); },
+			link(path, link, callback) { callback(enosys()); },
+			lstat(path, callback) { callback(enosys()); },
+			mkdir(path, perm, callback) { callback(enosys()); },
+			open(path, flags, mode, callback) { callback(enosys()); },
+			read(fd, buffer, offset, length, position, callback) { callback(enosys()); },
+			readdir(path, callback) { callback(enosys()); },
+			readlink(path, callback) { callback(enosys()); },
+			rename(from, to, callback) { callback(enosys()); },
+			rmdir(path, callback) { callback(enosys()); },
+			stat(path, callback) { callback(enosys()); },
+			symlink(path, link, callback) { callback(enosys()); },
+			truncate(path, length, callback) { callback(enosys()); },
+			unlink(path, callback) { callback(enosys()); },
+			utimes(path, atime, mtime, callback) { callback(enosys()); },
+		};
+	}
+
+	if (!global.process) {
+		global.process = {
+			getuid() { return -1; },
+			getgid() { return -1; },
+			geteuid() { return -1; },
+			getegid() { return -1; },
+			getgroups() { throw enosys(); },
+			pid: -1,
+			ppid: -1,
+			umask() { throw enosys(); },
+			cwd() { throw enosys(); },
+			chdir() { throw enosys(); },
+		}
+	}
+
+	if (!global.crypto && global.require) {
+		const nodeCrypto = require("crypto");
+		global.crypto = {
+			getRandomValues(b) {
+				nodeCrypto.randomFillSync(b);
+			},
+		};
+	}
+	if (!global.crypto) {
+		throw new Error("global.crypto is not available, polyfill required (getRandomValues only)");
+	}
+
+	if (!global.performance) {
+		global.performance = {
+			now() {
+				const [sec, nsec] = process.hrtime();
+				return sec * 1000 + nsec / 1000000;
+			},
+		};
+	}
+
+	if (!global.TextEncoder && global.require) {
+		global.TextEncoder = require("util").TextEncoder;
+	}
+	if (!global.TextEncoder) {
+		throw new Error("global.TextEncoder is not available, polyfill required");
+	}
+
+	if (!global.TextDecoder && global.require) {
+		global.TextDecoder = require("util").TextDecoder;
+	}
+	if (!global.TextDecoder) {
+		throw new Error("global.TextDecoder is not available, polyfill required");
+	}
+
+	// End of polyfills for common API.
+
+	const encoder = new TextEncoder("utf-8");
+	const decoder = new TextDecoder("utf-8");
+
+	global.Go = class {
+		constructor() {
+			this.argv = ["js"];
+			this.env = {};
+			this.exit = (code) => {
+				if (code !== 0) {
+					console.warn("exit code:", code);
+				}
+			};
+			this._exitPromise = new Promise((resolve) => {
+				this._resolveExitPromise = resolve;
+			});
+			this._pendingEvent = null;
+			this._scheduledTimeouts = new Map();
+			this._nextCallbackTimeoutID = 1;
+
+			const setInt64 = (addr, v) => {
+				this.mem.setUint32(addr + 0, v, true);
+				this.mem.setUint32(addr + 4, Math.floor(v / 4294967296), true);
+			}
+
+			const getInt64 = (addr) => {
+				const low = this.mem.getUint32(addr + 0, true);
+				const high = this.mem.getInt32(addr + 4, true);
+				return low + high * 4294967296;
+			}
+
+			const loadValue = (addr) => {
+				const f = this.mem.getFloat64(addr, true);
+				if (f === 0) {
+					return undefined;
+				}
+				if (!isNaN(f)) {
+					return f;
+				}
+
+				const id = this.mem.getUint32(addr, true);
+				return this._values[id];
+			}
+
+			const storeValue = (addr, v) => {
+				const nanHead = 0x7FF80000;
+
+				if (typeof v === "number" && v !== 0) {
+					if (isNaN(v)) {
+						this.mem.setUint32(addr + 4, nanHead, true);
+						this.mem.setUint32(addr, 0, true);
+						return;
+					}
+					this.mem.setFloat64(addr, v, true);
+					return;
+				}
+
+				if (v === undefined) {
+					this.mem.setFloat64(addr, 0, true);
+					return;
+				}
+
+				let id = this._ids.get(v);
+				if (id === undefined) {
+					id = this._idPool.pop();
+					if (id === undefined) {
+						id = this._values.length;
+					}
+					this._values[id] = v;
+					this._goRefCounts[id] = 0;
+					this._ids.set(v, id);
+				}
+				this._goRefCounts[id]++;
+				let typeFlag = 0;
+				switch (typeof v) {
+					case "object":
+						if (v !== null) {
+							typeFlag = 1;
+						}
+						break;
+					case "string":
+						typeFlag = 2;
+						break;
+					case "symbol":
+						typeFlag = 3;
+						break;
+					case "function":
+						typeFlag = 4;
+						break;
+				}
+				this.mem.setUint32(addr + 4, nanHead | typeFlag, true);
+				this.mem.setUint32(addr, id, true);
+			}
+
+			const loadSlice = (addr) => {
+				const array = getInt64(addr + 0);
+				const len = getInt64(addr + 8);
+				return new Uint8Array(this._inst.exports.mem.buffer, array, len);
+			}
+
+			const loadSliceOfValues = (addr) => {
+				const array = getInt64(addr + 0);
+				const len = getInt64(addr + 8);
+				const a = new Array(len);
+				for (let i = 0; i < len; i++) {
+					a[i] = loadValue(array + i * 8);
+				}
+				return a;
+			}
+
+			const loadString = (addr) => {
+				const saddr = getInt64(addr + 0);
+				const len = getInt64(addr + 8);
+				return decoder.decode(new DataView(this._inst.exports.mem.buffer, saddr, len));
+			}
+
+			const timeOrigin = Date.now() - performance.now();
+			this.importObject = {
+				go: {
+					// Go's SP does not change as long as no Go code is running. Some operations (e.g. calls, getters and setters)
+					// may synchronously trigger a Go event handler. This makes Go code get executed in the middle of the imported
+					// function. A goroutine can switch to a new stack if the current stack is too small (see morestack function).
+					// This changes the SP, thus we have to update the SP used by the imported function.
+
+					// func wasmExit(code int32)
+					"runtime.wasmExit": (sp) => {
+						sp >>>= 0;
+						const code = this.mem.getInt32(sp + 8, true);
+						this.exited = true;
+						delete this._inst;
+						delete this._values;
+						delete this._goRefCounts;
+						delete this._ids;
+						delete this._idPool;
+						this.exit(code);
+					},
+
+					// func wasmWrite(fd uintptr, p unsafe.Pointer, n int32)
+					"runtime.wasmWrite": (sp) => {
+						sp >>>= 0;
+						const fd = getInt64(sp + 8);
+						const p = getInt64(sp + 16);
+						const n = this.mem.getInt32(sp + 24, true);
+						fs.writeSync(fd, new Uint8Array(this._inst.exports.mem.buffer, p, n));
+					},
+
+					// func resetMemoryDataView()
+					"runtime.resetMemoryDataView": (sp) => {
+						sp >>>= 0;
+						this.mem = new DataView(this._inst.exports.mem.buffer);
+					},
+
+					// func nanotime1() int64
+					"runtime.nanotime1": (sp) => {
+						sp >>>= 0;
+						setInt64(sp + 8, (timeOrigin + performance.now()) * 1000000);
+					},
+
+					// func walltime() (sec int64, nsec int32)
+					"runtime.walltime": (sp) => {
+						sp >>>= 0;
+						const msec = (new Date).getTime();
+						setInt64(sp + 8, msec / 1000);
+						this.mem.setInt32(sp + 16, (msec % 1000) * 1000000, true);
+					},
+
+					// func scheduleTimeoutEvent(delay int64) int32
+					"runtime.scheduleTimeoutEvent": (sp) => {
+						sp >>>= 0;
+						const id = this._nextCallbackTimeoutID;
+						this._nextCallbackTimeoutID++;
+						this._scheduledTimeouts.set(id, setTimeout(
+							() => {
+								this._resume();
+								while (this._scheduledTimeouts.has(id)) {
+									// for some reason Go failed to register the timeout event, log and try again
+									// (temporary workaround for https://github.com/golang/go/issues/28975)
+									console.warn("scheduleTimeoutEvent: missed timeout event");
+									this._resume();
+								}
+							},
+							getInt64(sp + 8) + 1, // setTimeout has been seen to fire up to 1 millisecond early
+						));
+						this.mem.setInt32(sp + 16, id, true);
+					},
+
+					// func clearTimeoutEvent(id int32)
+					"runtime.clearTimeoutEvent": (sp) => {
+						sp >>>= 0;
+						const id = this.mem.getInt32(sp + 8, true);
+						clearTimeout(this._scheduledTimeouts.get(id));
+						this._scheduledTimeouts.delete(id);
+					},
+
+					// func getRandomData(r []byte)
+					"runtime.getRandomData": (sp) => {
+						sp >>>= 0;
+						crypto.getRandomValues(loadSlice(sp + 8));
+					},
+
+					// func finalizeRef(v ref)
+					"syscall/js.finalizeRef": (sp) => {
+						sp >>>= 0;
+						const id = this.mem.getUint32(sp + 8, true);
+						this._goRefCounts[id]--;
+						if (this._goRefCounts[id] === 0) {
+							const v = this._values[id];
+							this._values[id] = null;
+							this._ids.delete(v);
+							this._idPool.push(id);
+						}
+					},
+
+					// func stringVal(value string) ref
+					"syscall/js.stringVal": (sp) => {
+						sp >>>= 0;
+						storeValue(sp + 24, loadString(sp + 8));
+					},
+
+					// func valueGet(v ref, p string) ref
+					"syscall/js.valueGet": (sp) => {
+						sp >>>= 0;
+						const result = Reflect.get(loadValue(sp + 8), loadString(sp + 16));
+						sp = this._inst.exports.getsp() >>> 0; // see comment above
+						storeValue(sp + 32, result);
+					},
+
+					// func valueSet(v ref, p string, x ref)
+					"syscall/js.valueSet": (sp) => {
+						sp >>>= 0;
+						Reflect.set(loadValue(sp + 8), loadString(sp + 16), loadValue(sp + 32));
+					},
+
+					// func valueDelete(v ref, p string)
+					"syscall/js.valueDelete": (sp) => {
+						sp >>>= 0;
+						Reflect.deleteProperty(loadValue(sp + 8), loadString(sp + 16));
+					},
+
+					// func valueIndex(v ref, i int) ref
+					"syscall/js.valueIndex": (sp) => {
+						sp >>>= 0;
+						storeValue(sp + 24, Reflect.get(loadValue(sp + 8), getInt64(sp + 16)));
+					},
+
+					// valueSetIndex(v ref, i int, x ref)
+					"syscall/js.valueSetIndex": (sp) => {
+						sp >>>= 0;
+						Reflect.set(loadValue(sp + 8), getInt64(sp + 16), loadValue(sp + 24));
+					},
+
+					// func valueCall(v ref, m string, args []ref) (ref, bool)
+					"syscall/js.valueCall": (sp) => {
+						sp >>>= 0;
+						try {
+							const v = loadValue(sp + 8);
+							const m = Reflect.get(v, loadString(sp + 16));
+							const args = loadSliceOfValues(sp + 32);
+							const result = Reflect.apply(m, v, args);
+							sp = this._inst.exports.getsp() >>> 0; // see comment above
+							storeValue(sp + 56, result);
+							this.mem.setUint8(sp + 64, 1);
+						} catch (err) {
+							sp = this._inst.exports.getsp() >>> 0; // see comment above
+							storeValue(sp + 56, err);
+							this.mem.setUint8(sp + 64, 0);
+						}
+					},
+
+					// func valueInvoke(v ref, args []ref) (ref, bool)
+					"syscall/js.valueInvoke": (sp) => {
+						sp >>>= 0;
+						try {
+							const v = loadValue(sp + 8);
+							const args = loadSliceOfValues(sp + 16);
+							const result = Reflect.apply(v, undefined, args);
+							sp = this._inst.exports.getsp() >>> 0; // see comment above
+							storeValue(sp + 40, result);
+							this.mem.setUint8(sp + 48, 1);
+						} catch (err) {
+							sp = this._inst.exports.getsp() >>> 0; // see comment above
+							storeValue(sp + 40, err);
+							this.mem.setUint8(sp + 48, 0);
+						}
+					},
+
+					// func valueNew(v ref, args []ref) (ref, bool)
+					"syscall/js.valueNew": (sp) => {
+						sp >>>= 0;
+						try {
+							const v = loadValue(sp + 8);
+							const args = loadSliceOfValues(sp + 16);
+							const result = Reflect.construct(v, args);
+							sp = this._inst.exports.getsp() >>> 0; // see comment above
+							storeValue(sp + 40, result);
+							this.mem.setUint8(sp + 48, 1);
+						} catch (err) {
+							sp = this._inst.exports.getsp() >>> 0; // see comment above
+							storeValue(sp + 40, err);
+							this.mem.setUint8(sp + 48, 0);
+						}
+					},
+
+					// func valueLength(v ref) int
+					"syscall/js.valueLength": (sp) => {
+						sp >>>= 0;
+						setInt64(sp + 16, parseInt(loadValue(sp + 8).length));
+					},
+
+					// valuePrepareString(v ref) (ref, int)
+					"syscall/js.valuePrepareString": (sp) => {
+						sp >>>= 0;
+						const str = encoder.encode(String(loadValue(sp + 8)));
+						storeValue(sp + 16, str);
+						setInt64(sp + 24, str.length);
+					},
+
+					// valueLoadString(v ref, b []byte)
+					"syscall/js.valueLoadString": (sp) => {
+						sp >>>= 0;
+						const str = loadValue(sp + 8);
+						loadSlice(sp + 16).set(str);
+					},
+
+					// func valueInstanceOf(v ref, t ref) bool
+					"syscall/js.valueInstanceOf": (sp) => {
+						sp >>>= 0;
+						this.mem.setUint8(sp + 24, (loadValue(sp + 8) instanceof loadValue(sp + 16)) ? 1 : 0);
+					},
+
+					// func copyBytesToGo(dst []byte, src ref) (int, bool)
+					"syscall/js.copyBytesToGo": (sp) => {
+						sp >>>= 0;
+						const dst = loadSlice(sp + 8);
+						const src = loadValue(sp + 32);
+						if (!(src instanceof Uint8Array || src instanceof Uint8ClampedArray)) {
+							this.mem.setUint8(sp + 48, 0);
+							return;
+						}
+						const toCopy = src.subarray(0, dst.length);
+						dst.set(toCopy);
+						setInt64(sp + 40, toCopy.length);
+						this.mem.setUint8(sp + 48, 1);
+					},
+
+					// func copyBytesToJS(dst ref, src []byte) (int, bool)
+					"syscall/js.copyBytesToJS": (sp) => {
+						sp >>>= 0;
+						const dst = loadValue(sp + 8);
+						const src = loadSlice(sp + 16);
+						if (!(dst instanceof Uint8Array || dst instanceof Uint8ClampedArray)) {
+							this.mem.setUint8(sp + 48, 0);
+							return;
+						}
+						const toCopy = src.subarray(0, dst.length);
+						dst.set(toCopy);
+						setInt64(sp + 40, toCopy.length);
+						this.mem.setUint8(sp + 48, 1);
+					},
+
+					"debug": (value) => {
+						console.log(value);
+					},
+
+					// func throw(exception string, message string)
+					'gitlab.com/elixxir/xxdk-wasm/utils.throw': (sp) => {
+						const exception = loadString(sp + 8)
+						const message = loadString(sp + 24)
+						throw globalThis[exception](message)
+					},
+				}
+			};
+		}
+
+		async run(instance) {
+			if (!(instance instanceof WebAssembly.Instance)) {
+				throw new Error("Go.run: WebAssembly.Instance expected");
+			}
+			this._inst = instance;
+			this.mem = new DataView(this._inst.exports.mem.buffer);
+			this._values = [ // JS values that Go currently has references to, indexed by reference id
+				NaN,
+				0,
+				null,
+				true,
+				false,
+				global,
+				this,
+			];
+			this._goRefCounts = new Array(this._values.length).fill(Infinity); // number of references that Go has to a JS value, indexed by reference id
+			this._ids = new Map([ // mapping from JS values to reference ids
+				[0, 1],
+				[null, 2],
+				[true, 3],
+				[false, 4],
+				[global, 5],
+				[this, 6],
+			]);
+			this._idPool = [];   // unused ids that have been garbage collected
+			this.exited = false; // whether the Go program has exited
+
+			// Pass command line arguments and environment variables to WebAssembly by writing them to the linear memory.
+			let offset = 4096;
+
+			const strPtr = (str) => {
+				const ptr = offset;
+				const bytes = encoder.encode(str + "\0");
+				new Uint8Array(this.mem.buffer, offset, bytes.length).set(bytes);
+				offset += bytes.length;
+				if (offset % 8 !== 0) {
+					offset += 8 - (offset % 8);
+				}
+				return ptr;
+			};
+
+			const argc = this.argv.length;
+
+			const argvPtrs = [];
+			this.argv.forEach((arg) => {
+				argvPtrs.push(strPtr(arg));
+			});
+			argvPtrs.push(0);
+
+			const keys = Object.keys(this.env).sort();
+			keys.forEach((key) => {
+				argvPtrs.push(strPtr(`${key}=${this.env[key]}`));
+			});
+			argvPtrs.push(0);
+
+			const argv = offset;
+			argvPtrs.forEach((ptr) => {
+				this.mem.setUint32(offset, ptr, true);
+				this.mem.setUint32(offset + 4, 0, true);
+				offset += 8;
+			});
+
+			// The linker guarantees global data starts from at least wasmMinDataAddr.
+			// Keep in sync with cmd/link/internal/ld/data.go:wasmMinDataAddr.
+			const wasmMinDataAddr = 4096 + 8192;
+			if (offset >= wasmMinDataAddr) {
+				throw new Error("total length of command line and environment variables exceeds limit");
+			}
+
+			this._inst.exports.run(argc, argv);
+			if (this.exited) {
+				this._resolveExitPromise();
+			}
+			await this._exitPromise;
+		}
+
+		_resume() {
+			if (this.exited) {
+				throw new Error("Go program has already exited");
+			}
+			this._inst.exports.resume();
+			if (this.exited) {
+				this._resolveExitPromise();
+			}
+		}
+
+		_makeFuncWrapper(id) {
+			const go = this;
+			return function () {
+				const event = { id: id, this: this, args: arguments };
+				go._pendingEvent = event;
+				go._resume();
+				return event.result;
+			};
+		}
+	}
+
+	if (
+		typeof module !== "undefined" &&
+		global.require &&
+		global.require.main === module &&
+		global.process &&
+		global.process.versions &&
+		!global.process.versions.electron
+	) {
+		if (process.argv.length < 3) {
+			console.error("usage: go_js_wasm_exec [wasm binary] [arguments]");
+			process.exit(1);
+		}
+
+		const go = new Go();
+		go.argv = process.argv.slice(2);
+		go.env = Object.assign({ TMPDIR: require("os").tmpdir() }, process.env);
+		go.exit = process.exit;
+		WebAssembly.instantiate(fs.readFileSync(process.argv[2]), go.importObject).then((result) => {
+			process.on("exit", (code) => { // Node.js exits if no event handler is pending
+				if (code === 0 && !go.exited) {
+					// deadlock, make Go print error and stack traces
+					go._pendingEvent = { id: 0 };
+					go._resume();
+				}
+			});
+			return go.run(result.instance);
+		}).catch((err) => {
+			console.error(err);
+			process.exit(1);
+		});
+	}
+})();
diff --git a/wasm/testServer/clientServer.go b/wasm/testServer/clientServer.go
new file mode 100644
index 0000000000000000000000000000000000000000..c637bd458a3f2ea833dc7050e8b0779ffd1bd1ca
--- /dev/null
+++ b/wasm/testServer/clientServer.go
@@ -0,0 +1,25 @@
+////////////////////////////////////////////////////////////////////////////////
+// Copyright © 2022 xx foundation                                             //
+//                                                                            //
+// Use of this source code is governed by a license that can be found in the  //
+// LICENSE file.                                                              //
+////////////////////////////////////////////////////////////////////////////////
+
+package main
+
+import (
+	"fmt"
+	"net/http"
+)
+
+func main() {
+	port := "9090"
+	root := ""
+	fmt.Printf("Starting server on port %s from %s\n", port, root)
+	fmt.Printf("\thttp://localhost:%s\n", port)
+
+	err := http.ListenAndServe(":"+port, http.FileServer(http.Dir(root)))
+	if err != nil {
+		panic(fmt.Sprintf("Failed to start server: %+v", err))
+	}
+}
diff --git a/wasm/testServer/sendE2E/README.md b/wasm/testServer/sendE2E/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..1a5bb7cc2a434dbc54bfb9b12e7ea93c5d6e94c5
--- /dev/null
+++ b/wasm/testServer/sendE2E/README.md
@@ -0,0 +1,27 @@
+# SendE2E Test
+
+This test sets up two clients in the browser and has one client send an E2E
+message to the other.
+
+## Running the Test
+
+1. First, compile the WASM binary and start the local HTTP server. Refer to wasm
+   test [README](../README.md) for details on how to do this. Open the two
+   clients, the sender and receiver.
+
+2. Next, start the network using the `run.sh` script. This will start all the
+   gateways and client registrar using localhost as their public IP addresses
+   and the NDF will be provided by the permissioning server rather than
+   downloaded from a gateway.
+
+3. Once rounds are running, on the receiver webpage, navigate to the results
+   folder in integration and select the `permissions-ndfoutput.json` file. Doing
+   this will start the client. Once the client generates keys and joins the
+   network, it will prompt its contact file for download. Copy the contents of
+   this file into the `recipientContactFile` const in `sender.html`.
+
+4. On the sender webpage (make sure to refresh the page), select the NDF file as
+   described for the recipient above. This will start the sender client. Once it
+   generates its keys and joins the network, it will add the receiver client as
+   a partner, they will exchange requests and confirmations, and finally, the
+   sender will send an E2E message to the recipient.
\ No newline at end of file
diff --git a/wasm/testServer/sendE2E/receiver.html b/wasm/testServer/sendE2E/receiver.html
new file mode 100644
index 0000000000000000000000000000000000000000..3fb9d3400150e2b8db60c03fc69cab5b80f57238
--- /dev/null
+++ b/wasm/testServer/sendE2E/receiver.html
@@ -0,0 +1,80 @@
+<!--////////////////////////////////////////////////////////////////////////////
+// Copyright © 2022 xx foundation                                             //
+//                                                                            //
+// Use of this source code is governed by a license that can be found in the  //
+// LICENSE file.                                                              //
+/////////////////////////////////////////////////////////////////////////////-->
+
+<!DOCTYPE html>
+<html lang="en">
+<head>
+	<meta charset="UTF-8">
+	<title>Receiver</title>
+
+	<link rel="stylesheet" type="text/css" href="../assets/styles.css">
+	<link rel="shortcut icon" type="image/x-icon" href="receiver.ico"/>
+
+
+	<script type="text/javascript" src="sendE2ejs"></script>
+	<script type="text/javascript" src="../assets/utils.js"></script>
+	<script type="text/javascript" src="../assets/wasm_exec.js"></script>
+	<script>
+        const go = new Go();
+        const binPath = '../../../bin/xxdk.wasm'
+        WebAssembly.instantiateStreaming(fetch(binPath), go.importObject).then((result) => {
+            go.run(result.instance);
+
+            // Set log level
+            LogLevel(0);
+
+            // Output log to file that can be downloaded using a button
+            const logFile = LogToFile(0, "receiver.log", 1000000);
+            document.getElementById('logFileDownload').addEventListener(
+                'click', () => download(logFile.Name(), logFile.GetFile()))
+
+            // Get element to print log messages to
+            const logOutput = document.getElementById("logOutput")
+            const htmlConsole = newHtmlConsole(logOutput)
+
+            // Get button that will stop the network follower
+            const stopNetworkFollowerBtn = document.getElementById("stopNetworkFollowerBtn")
+
+            // Client specific parameters
+            const recipientContactFile = '';
+            const myContactFileName = 'receiverContact.xxc';
+            const statePath = 'statePathRecipient';
+            const statePass = 'password';
+
+            document.getElementById('ndfInput').addEventListener('change', e => {
+                let reader = new FileReader();
+                reader.onload = async function (e) {
+                    try {
+                        await SendE2e(htmlConsole, stopNetworkFollowerBtn,
+                            e.target.result, recipientContactFile,
+                            myContactFileName, statePath, statePass);
+                    } catch (e) {
+                        htmlConsole.error(e)
+                    }
+                };
+                reader.readAsText(e.target.files[0]);
+            }, false);
+        });
+	</script>
+</head>
+<body>
+<h1 style="margin-top: 0">SendE2E: Receiver</h1>
+<div class="toolbar">
+	<input type="button" value="Clear localStorage" onclick="localStorage.clear();">
+	<input type="button" value="Download Log File" id="logFileDownload">
+	<input type="button" value="Stop Network Follower" id="stopNetworkFollowerBtn" style="display:none;" >
+</div>
+<p>Selecting an NDF will start the client.</p>
+<form id="startCmix">
+	<div>
+		<label for="ndfInput">Select NDF file to use <code>ndf</code> variable in JS:</label><br/>
+		<input type="file" id="ndfInput" required/>
+	</div>
+</form>
+<div id="logOutput"></div>
+</body>
+</html>
\ No newline at end of file
diff --git a/wasm/testServer/sendE2E/receiver.ico b/wasm/testServer/sendE2E/receiver.ico
new file mode 100644
index 0000000000000000000000000000000000000000..3a7b5826dfdad175084ea63406202334dfe29bf4
Binary files /dev/null and b/wasm/testServer/sendE2E/receiver.ico differ
diff --git a/wasm/testServer/sendE2E/sendE2ejs b/wasm/testServer/sendE2E/sendE2ejs
new file mode 100644
index 0000000000000000000000000000000000000000..ae635574f02b184909f1431314b44a016970e3c9
--- /dev/null
+++ b/wasm/testServer/sendE2E/sendE2ejs
@@ -0,0 +1,228 @@
+////////////////////////////////////////////////////////////////////////////////
+// Copyright © 2022 xx foundation                                             //
+//                                                                            //
+// Use of this source code is governed by a license that can be found in the  //
+// LICENSE file.                                                              //
+////////////////////////////////////////////////////////////////////////////////
+
+async function SendE2e(htmlConsole, stopNetworkFollowerBtn, ndf,
+                       recipientContactFile, myContactFileName, statePath,
+                       statePassString) {
+
+    let enc = new TextEncoder();
+    let dec = new TextDecoder();
+
+    const statePass = enc.encode(statePassString);
+
+    console.log("Starting client with:" +
+        "\n\trecipientContactFile: " + recipientContactFile +
+        "\n\tmyContactFileName: " + myContactFileName +
+        "\n\tstatePath: " + statePath +
+        "\n\tstatePass: " + statePassString)
+    htmlConsole.log("Starting client with path " + statePath)
+
+    // Check if state exists
+    if (localStorage.getItem(statePath) === null) {
+
+        htmlConsole.log("No state found at " + statePath + ". Calling NewCmix.")
+
+        // Initialize the state
+        NewCmix(ndf, statePath, statePass, '');
+    } else {
+        htmlConsole.log("State found at " + statePath)
+    }
+
+
+    ////////////////////////////////////////////////////////////////////////////
+    // Login to your client session                                           //
+    ////////////////////////////////////////////////////////////////////////////
+
+    // Login with the same statePath and statePass used to call NewCmix
+    htmlConsole.log("Starting to load cmix with path " + statePath)
+    let net;
+    try {
+        net = await LoadCmix(statePath, statePass, GetDefaultCMixParams());
+    } catch (e) {
+        htmlConsole.error("Failed to load Cmix: " + e)
+        return
+    }
+    htmlConsole.log("Loaded Cmix.")
+    console.log("Loaded Cmix: " + JSON.stringify(net))
+
+    // Get reception identity (automatically created if one does not exist)
+    const identityStorageKey = "identityStorageKey";
+    let identity;
+    try {
+        htmlConsole.log("Getting reception identity.")
+        identity = LoadReceptionIdentity(identityStorageKey, net.GetID());
+    } catch (e) {
+        htmlConsole.log("No reception identity found. Generating a new one.")
+
+        // If no extant xxdk.ReceptionIdentity, generate and store a new one
+        identity = await net.MakeReceptionIdentity();
+
+        StoreReceptionIdentity(identityStorageKey, identity, net.GetID());
+    }
+
+    // Print contact to console.
+    const myContactFile = dec.decode(GetContactFromReceptionIdentity(identity))
+    htmlConsole.log("My contact file: " + encodeURIComponent(myContactFile))
+
+    // Start contact file download
+    if (myContactFileName !== '') {
+        download(myContactFileName, myContactFile);
+    }
+
+    let confirm = false;
+    let confirmContact;
+    let e2eClient;
+    let authCallbacks = {
+        Confirm: function (contact, receptionId, ephemeralId, roundId) {
+            confirm = true;
+            confirmContact = contact
+
+            htmlConsole.log("Confirm: from " + Uint8ArrayToBase64(receptionId) + " on round " + roundId)
+        },
+        Request: async function (contact, receptionId, ephemeralId, roundId) {
+            htmlConsole.log("Request: from " + Uint8ArrayToBase64(receptionId) + " on round " + roundId)
+
+            htmlConsole.log("Calling confirm on contact " + dec.decode(contact))
+            const rid = await e2eClient.Confirm(contact)
+            htmlConsole.log("Called confirm on round " + rid)
+        }
+    }
+
+    // Create an E2E client
+    // Pass in auth object which controls auth callbacks for this client
+    const params = GetDefaultE2EParams();
+    htmlConsole.log("Logging in E2E")
+    e2eClient = Login(net.GetID(), authCallbacks, identity, new Uint8Array());
+    htmlConsole.log("Logged in E2E")
+
+    ////////////////////////////////////////////////////////////////////////////
+    // Start network threads                                                  //
+    ////////////////////////////////////////////////////////////////////////////
+
+    // Set networkFollowerTimeout to a value of your choice (seconds)
+    net.StartNetworkFollower(5000);
+
+    htmlConsole.log("Started network follower")
+
+    stopNetworkFollowerBtn.style.display = 'block';
+    stopNetworkFollowerBtn.addEventListener('click', async () => {
+        htmlConsole.log("Stopping network follower")
+        try {
+            await net.StopNetworkFollower()
+        } catch (e) {
+            htmlConsole.log("Failed to stop network follower: " + e)
+        }
+    })
+
+    // Wait for network to become healthy
+    htmlConsole.log("Waiting for network to be healthy")
+    await net.WaitForNetwork(25000).then(
+        () => {
+            htmlConsole.log("Network is healthy")
+        },
+        () => {
+            htmlConsole.error("Timed out. Network is not healthy.")
+            throw new Error("Timed out. Network is not healthy.")
+        }
+    )
+
+
+    ////////////////////////////////////////////////////////////////////////////
+    // Register a listener for messages                                       //
+    ////////////////////////////////////////////////////////////////////////////
+
+    let listener = {
+        Hear: function (item) {
+            htmlConsole.log("Listener heard: " + dec.decode(item))
+            const message = JSON.parse(dec.decode(item))
+            htmlConsole.log("Listener message: " + atob(message.Payload))
+        },
+        Name: function () {
+            return "Listener";
+        }
+    }
+
+    // Listen for all types of messages using catalog.NoType
+    // Listen for messages from all users using id.ZeroUser
+    let zerUser = Uint8Array.from([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3]);
+    e2eClient.RegisterListener(zerUser, 0, listener);
+    htmlConsole.log("Registered listener.")
+
+    ////////////////////////////////////////////////////////////////////////////
+    // Connect with the recipient                                             //
+    ////////////////////////////////////////////////////////////////////////////
+
+    // Check that the partner exists
+    if (recipientContactFile !== '') {
+        let exists = false;
+        htmlConsole.log("getting ID from contact")
+        const recipientContactID = GetIDFromContact(enc.encode(recipientContactFile));
+        const recipientContactIDBase64 = Uint8ArrayToBase64(recipientContactID)
+
+        htmlConsole.log("Checking if partner " + recipientContactIDBase64 + " exists")
+
+        const partnerIDS = dec.decode(e2eClient.GetAllPartnerIDs())
+        console.log("partnerIDS: " + partnerIDS)
+
+        let partners = JSON.parse(partnerIDS);
+        for (let i = 0; i < partners.length; i++) {
+            console.log("Checking partner #" + i + ": " + partners[i] + " matches recipient " + recipientContactIDBase64)
+            if (partners[i] === recipientContactIDBase64) {
+                htmlConsole.log("Already partner with " + recipientContactIDBase64)
+                exists = true;
+                break
+            }
+        }
+
+        // If the partner does not exist, send a request
+        if (exists === false) {
+            htmlConsole.log("Partner does not exist, Request being sent to " + recipientContactIDBase64)
+            const factList = enc.encode('[]')
+            const rid = await e2eClient.Request(enc.encode(recipientContactFile), factList)
+            htmlConsole.log("Request sent on round " + rid)
+
+
+            htmlConsole.log("Waiting to receive confirmation.")
+            try {
+                await sleepUntil(() => confirm === true, 35000);
+                htmlConsole.log("Received confirmation: " + confirm)
+            } catch {
+                htmlConsole.error("Timed out. Did not receive confirm: " + confirm)
+                throw new Error("Did not receive confirm: " + confirm)
+            }
+
+            console.log("confirmContact: " + confirmContact)
+            console.log("confirmContact: " + dec.decode(confirmContact))
+            const confirmContactID = GetIDFromContact(confirmContact)
+            if (!Uint8ArrayEquals(recipientContactID, confirmContactID)) {
+                htmlConsole.log("contact ID from confirmation " +
+                    Uint8ArrayToBase64(confirmContactID) +
+                    " does not match recipient ID " + recipientContactIDBase64)
+                throw new Error("contact ID from confirmation " +
+                    Uint8ArrayToBase64(confirmContactID) +
+                    " does not match recipient ID " + recipientContactIDBase64)
+            }
+        } else {
+            htmlConsole.log("Partner exists")
+        }
+
+        ////////////////////////////////////////////////////////////////////////////
+        // Send a message to the recipient                                        //
+        ////////////////////////////////////////////////////////////////////////////
+
+        // Test message
+        const msgBody = "If this message is sent successfully, we'll have established contact with the recipient."
+
+        htmlConsole.log("Sending E2E message: " + msgBody)
+        const e2eSendReport = await e2eClient.SendE2E(2, recipientContactID, enc.encode(msgBody), params)
+
+        htmlConsole.log("Send E2e message. Report: " + dec.decode(e2eSendReport))
+    } else {
+        htmlConsole.log("No recipient specified. Waiting for request")
+    }
+}
\ No newline at end of file
diff --git a/wasm/testServer/sendE2E/sender.html b/wasm/testServer/sendE2E/sender.html
new file mode 100644
index 0000000000000000000000000000000000000000..c88b0e950f83b8c3f5030312a833215cbd599852
--- /dev/null
+++ b/wasm/testServer/sendE2E/sender.html
@@ -0,0 +1,79 @@
+<!--////////////////////////////////////////////////////////////////////////////
+// Copyright © 2022 xx foundation                                             //
+//                                                                            //
+// Use of this source code is governed by a license that can be found in the  //
+// LICENSE file.                                                              //
+/////////////////////////////////////////////////////////////////////////////-->
+
+<!DOCTYPE html>
+<html lang="en">
+<head>
+	<meta charset="UTF-8">
+	<title>Sender</title>
+
+	<link rel="stylesheet" type="text/css" href="../assets/styles.css">
+	<link rel="shortcut icon" type="image/x-icon" href="sender.ico"/>
+
+	<script type="text/javascript" src="sendE2ejs"></script>
+	<script type="text/javascript" src="../assets/utils.js"></script>
+	<script type="text/javascript" src="../assets/wasm_exec.js"></script>
+	<script>
+        const go = new Go();
+        const binPath = '../../../bin/xxdk.wasm'
+        WebAssembly.instantiateStreaming(fetch(binPath), go.importObject).then((result) => {
+            go.run(result.instance);
+
+            // Set log level
+            LogLevel(0);
+
+            // Output log to file that can be downloaded using a button
+            const logFile = LogToFile(0, "sender.log", 1000000);
+            document.getElementById('logFileDownload').addEventListener(
+                'click', () => download(logFile.Name(), logFile.GetFile()))
+
+            // Get element to print log messages to
+            const logOutput = document.getElementById("logOutput")
+            const htmlConsole = newHtmlConsole(logOutput)
+
+            // Get button that will stop the network follower
+            const stopNetworkFollowerBtn = document.getElementById("stopNetworkFollowerBtn")
+
+            // Client specific parameters
+            const recipientContactFile = '<xxc(2)8xlGWi5QO7ypchZrwF0HIZwNv7MhrwojO1Zw6Mjmsx4DrgZ7Ugdw/BAr6UIgauCEAsxLTzY9qk+fDN6Q4lLf5bOCRKRavadoNTRpbG6GWmps0e1aMs6VsGflG4i95G4zWEu3tkGptr65SPC5c5/V1V9aYvf1rLSl8UwtiANaHlDdxy4ZEZbiibg7EnVi3mKZ3h4orsflDiWSwwSsdFbv9SxNmhzaFD4qpR0bi4jRyinyR4Ty6j8tA+ozcVo+Lt+gPeiUQqCt8NORS3Kcoe54yi4Lf9ylKxBumioRNytdyvo93r/SAX44w8j2tFTa+zYLz/Y09hPz391GHa3MDbDliPQgzRyJrAQRVJV9YcGTgi6ESDOFG3IoJbaNioHles7+2y/hqOKh3kCkbRtNozqGsp91PGiatCLx3bpRJq7h+o4Zk0yjZLf6e92JtFN4IBkHVc9RsiQEH8hy6tY9XLfvMOfr8xhWJ1gvIDxsS/Ice/lYkOs3zPCXwQXTBCAmZGZtsGxjMcSElz0d6cIVFBNgaGh3clWiWzz9nb62ofz0FcQ1O5n13L1Ir/41DyJU6GdeE5/+zvfqIKqSlAAAAgA76aDSbZXRB/a463h2byeQMw==xxc>';
+            const myContactFileName = '';
+            const statePath = 'statePathSender';
+            const statePass = 'password';
+
+            document.getElementById('ndfInput').addEventListener('change', e => {
+                let reader = new FileReader();
+                reader.onload = async function (e) {
+                    try {
+                        await SendE2e(htmlConsole, stopNetworkFollowerBtn,
+                            e.target.result, recipientContactFile,
+                            myContactFileName, statePath, statePass);
+                    } catch (e) {
+                        htmlConsole.error(e)
+                    }
+                };
+                reader.readAsText(e.target.files[0]);
+            }, false);
+        });
+	</script>
+</head>
+<body>
+<h1 style="margin-top: 0">SendE2E: Sender</h1>
+<div class="toolbar">
+	<input type="button" value="Clear localStorage" onclick="localStorage.clear();">
+	<input type="button" value="Download Log File" id="logFileDownload">
+	<input type="button" value="Stop Network Follower" id="stopNetworkFollowerBtn" style="display:none;" >
+</div>
+<p>Selecting an NDF will start the client.</p>
+<form id="startCmix">
+	<div>
+		<label for="ndfInput">Select NDF file to use <code>ndf</code> variable in JS:</label><br/>
+		<input type="file" id="ndfInput" required/>
+	</div>
+</form>
+<div id="logOutput"></div>
+</body>
+</html>
\ No newline at end of file
diff --git a/wasm/testServer/sendE2E/sender.ico b/wasm/testServer/sendE2E/sender.ico
new file mode 100644
index 0000000000000000000000000000000000000000..149d6cb2fdb5e20b4a4f80a5797878f73e14d25e
Binary files /dev/null and b/wasm/testServer/sendE2E/sender.ico differ
diff --git a/wasm/udb.yaml b/wasm/udb.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..14b3ed1ed5398db64c1d56f33d99f499c898ca03
--- /dev/null
+++ b/wasm/udb.yaml
@@ -0,0 +1,10 @@
+# UDB Config for E2E Integration test
+log: "./results/udb.log"
+sessionPath: "./results/udbsession"
+sessionPass: "hello"
+certPath: "../keys/cmix.rip.crt"
+port: "18001"
+keyPath: "../keys/cmix.rip.key"
+permCertPath: "../keys/cmix.rip.crt"
+permAddress: "0.0.0.0:18000"
+devMode: true
\ No newline at end of file
diff --git a/wasm/udbContact.bin b/wasm/udbContact.bin
new file mode 100644
index 0000000000000000000000000000000000000000..10b08460a8ec87eaa6474298845d1deaa9482a5c
--- /dev/null
+++ b/wasm/udbContact.bin
@@ -0,0 +1 @@
+<xxc(2)uu49Kr9myQUG0gG8VKwalxmem65pfPu4D2Lk1u8pi/UDrgZ7Ugdw/BAr6QTgt3dDqBSTfbLpgWOpOQvmR1bACzi52YiM90WbtsF4Iy5RZ64mRVbzN+erifSAaH8JAWSoK23KmMJH9F/HqBBwMjWJmpmkpxR0wo4bHDoBnHulqnBf/WxsHBeLk6HRbWqXWljV3LnO0ugSZDxa3cNu8mjfMNnxgoQr3JWY/bgiAHUGvUSHGxfMgmRvbxuFe3TRO9ZNVGhIC291GsvA+6kKnIjC2NuHu+UZ5Gmk86fw/1kbVK6lxFZsWN7dDgDrodUMc0Bnq2fQ+pmwP26x2Wj5tUHUKK+VHqmY+DGHg/RObjpgnr1qctl3/+gpL83Q3TyWwAjH/73tVww/M6lDnFYxAb62k0NYtVKZVFPDqrocFyAlDXaReg1it9VNJSPYiUM4TCimMmtYL6AgcXdMeD5E7UX1vsvmA5Ll9jZNOmoyPWLYt5W9Gvl79Xqgzqlf0t1DxR4ZmFtG/NwH3S1gCARcIlYgCb66j2gifEMH7aAQWyW8YLm2hEZmwLxISnIPjEL3gIOzNHtmGGXN23jUIwAAAgA790tNCG8KTm8tuEBiH/sjKg==xxc>
\ No newline at end of file
diff --git a/wasm/udbProto.json b/wasm/udbProto.json
new file mode 100644
index 0000000000000000000000000000000000000000..940cadfe9b1bb56946fd38a8c677207245add9e3
--- /dev/null
+++ b/wasm/udbProto.json
@@ -0,0 +1 @@
+{"TransmissionID":"MhaUvxgJp0REgqp/FNes3ddICg1vKmIIUr+hABI9PXgD","TransmissionSalt":"aUdWmk0Dtu8YZ9N4fUCl80QAmCZ+eY96BDTAN9//9kI=","TransmissionRSA":{"N":744095517605335188486192402593567285818149781037814392671915097997165799160704009076529206117565015316892087089782583938102479243183663906078668582528984516276493241709164519970005583971481012694667579696383360567823504989650655410246207331522294633002054530951336779742521949130565223443549835766371552961634577790108359687869697030104209247319401651289712207512551543251668177804743453344668589351336794674335174328494860289416890175422687829958938924106165398205285038813958087994742173028066268997679203164659373760933342345626244738488110458781897756397149360694648367300111068967014678500898983090333707836644483738687322761365319344259236535454689996343110842585871572930571278463402471136616034421156328875234527272277980275379473515969296415235308154781609704415193243251254222667078013823356321213274280009612841451973719445431427792198300346805888677562529255025870805189890421627704987545754460307965087998094692006804411392783507966480991167024256590215549839475685306270811326657913050667578064503246626321836700579972796860953792588080819942544062977767967992236600692121009380090485897336412623665757415839315721348591326702012914394412545952829437103713850347584948953528257126120160975904938795178616150713777831589,"E":65537,"D":702540524327712978286466077105790056678969955922255232545891489063744574128612569535199928085456619203859372153966109927796010011603759273638246729040785738035622202988204724082335711421080162694477101925219543187134214539033120295707837878642675511638740378895522619590888057835298904908929813505588790799198357720349344223969868061274671644969751681902643121660346855086187525184614960443841773399647021439245580083376969805274103382587388114414289085660271241356003819929067314812253558149711712876228635033944612512096562618373058664318305855593748378360108198295661336701847393296488506669516858822963804260311691759542739227343536282374650987690037136673732778544975139599300829103845745104309687652878356279709829981494578600603924386279381105751731671579921080691074783353253958778076575957155595416051906390745893233111609000272562409467803170191115741547810223834894875859746256757717726366292758015729401293357270389003259918967902654657293283011346796119435418406947470723708887763547764820828540891209479365431423513260945988353481802346962185241475598840911175187647890159503133204568937310632267175230680232531126383217383814916312952923810037599497876585604599444464808553067231902765607317662766023981051800036686273,"Primes":[26431524235468771028852443590104055376636364921067197450041684258482439988835561020261125258562308094700689754910670346007026107201010688328852518422104352082149765577020457481232265846864075786524112374194109435619505107638299597352847727718122930754740563865787954820820245269180427904294490102595163613363903301612449832540143489589841021282833070290450230168473246516211511298499533872232550844877914066189330777819461606196011609435557144318950621971682270122109937087991576359208132704648705570764167630153594184009208403478937807655656357669009470285691425163502536227383519714393737089527860969984842067786101,28151820189273258942344071129053694480161436139819710188331124791415914831471008775403858034120373831438530464789505032183420165894885328994613924494905439083295166072103442061113078040409562754110291758200572034754286027961921301572668934253552291343681689448082037497152344335274886686268374921532985704635156460979764429070570728443686028271768081803840073189209047377853848238258439123998890243019814395279696459993201039305339332983028931741597790390904270131316198444444886804187393230626448411790408221074812330007942755799986304653723364502190308487509016389841021566288050103973334556368552133193493944408689],"Precomputed":{"Dp":275861918871181765777119358768805009042514512504538856765315959424477607341860685381671569904887601458340659358208317693345833000068530918670905329824669680092015802595205653556965833639852721943062588521732316919659756986505285939077892576089782636316013025988357127995499454722056436616528544641577916467658114626896496413590157420685280964301964082558981299651123802082620103577729849834552463156636605601011676641111307179731631610447855207198410446444461491425045347943699562532590182186851925025599137266354249078570861467256564390137921306217899471678791138011134699170397294423689155274685397614624288178673,"Dq":20251853375093108718674238635432892563921007495734318881533472838489627548476924176040866851955981272517828971313395551327029420652154717499734014133036480629583806088703921134828221879139253331786377393413250059516388743370809492102858684015410780592477790114275535038844384485571048533054744709867618963802570860877854857148803386838061270563205181572605460893517398533291080291147479606024866554731101049481309326072897084076010928068387048779916221764340337818499984617266561381665606317822215493877818423040314663633588189342602717841897580646356474723470712524458623415234392941421286158880506566848352312481377,"Qinv":1782249293930479585148549445278205034617546516712012566064256819261113180298258405511667713490475276726336613818509018461173201977639000556416891523605685233456051292911470649097170785609362912595510275601045219404262259673943809118153699614833210258102652927663612925023313592281316017977621743103307097046836057862305643762370317914029974246165942098634086048350280652529323604758408742898452312792547897909618079071765102987807077305433629474092046709583007609210604882495077254479038254736382806594706188378137791260031991135639188023310238199612626314094372849297487936335153146414944425343861682455207985266708,"CRTValues":null}},"ReceptionID":"uu49Kr9myQUG0gG8VKwalxmem65pfPu4D2Lk1u8pi/UD","ReceptionSalt":"M8xD+2gHGbqwKPwHFxs7+IjutQYuyBUkX2H257o5BdU=","ReceptionRSA":{"N":850387188153991188889446100749739241848829271482462061108409439312531431988795734813327514693008344548532502397064837726907012068926804456615485530991075075479632026273101933309061970039508777559060326644210787796911046233949155489473727669005294322689793791335544727360305644959942063168476363583581030898050353126928764965273874658694962134566452334451854924568242626514389265043292391299760010067072010852660228863317130740474026897759770820934809759670185849936472580423333979237659958984231856174610567212253308506984182380860548206401655999782332308043025450559980137052816211530713615583429240205892728859426128000967414395838939110894033790769681039750313146913688429864118346214901692066118374534016813397369786419474818398950057293996832967235881835337432259690040386208608584839830051784956186429114899224875646403109211367267143599678019107255952565689487312514740269900292167402469336239686704437448306260169629496444993371773823439258565787685710030837201534059640894021404396138451751712905956843779570695671045422782022593718074598748351627333401529057059641321731684199811309173775075814578877100319468485440759413419208114295109676086784903373038008038594143762297734223790838124082684159063469085109435783518725031,"E":65537,"D":564922212042972891506779603709986684962272914422874876092540423872769289935398323184885484645154711027502930510414145293747815500326018670791589385580030144211006599750553715770887147559547944650942359905229184557030627277529401445675232029624235148495438489607025204008203409747486116303247866721674280821649117660056115481226310038842219913218786872835656314004723732098768107056926939591645201310559136617365280437344369150983684163873340894930173985790635539446178597340291613196667556255191759193646646393943456253239701974694076434107586512390304739235381525566166520085882164295171867519962140940902875266686494581933486332733070115573951932064475826442525759304559384157338056633545437785084083189928354125207030609304055082642325819334882909664939190272656190536034921638933681026317152328190142016810293928283213410554345222497221023348512487512997673475708771529517243600219742073183452616019438402558324904073954972062329692828835961585134598049832023174978649417520451705037067815379933732830551892585260348438752787466224668527842810502446714266623795601014094978116799695556231619140778349831856996205593248896859777976571100881350185386707051672977669257878795867903988402998091514995549897864288962757393054768438537,"Primes":[29492971595987200184565588575492524180677323915637743461334512913337954089756992535478030745356298455923396638365524831317118482754099964262507886663300281433334915984551779676428190574578201359535331987821184071989738056990202803783701055959370528270478305175226218851763791555786561228871062383690072445559534386478623474224364245986294525688152961853703225064960942669169364540334937714823971415905428637789133243443480147178878208486722743719938775945712937478778805521783173263515825112840086735855547887576584469859603118910509756820711149404941056507505214523564857945496996487150674719706390219045171074335669,28833553966793039902622472693344948085468484145845354555175845509525852913656457209339298633228826325187527465622133573224700820135296269687184379379824668957542611483930310268532473436266625079565533379890421336412596736616538404624204368463036294562591617517553183068875112160214949467183247851787117317409259574446000362822944034217039330133706829603389396809485151845126091352794981119059018300220209826040784745553271153888708759107151118112035826198322288728622562498893857193711017030398086686353945134010647373894524028231986696810322055915091233789726845203397655178875279735934668248673328323303568542440299],"Precomputed":{"Dp":22035237410890233868459863048338975639819722683815281575052938413439628332162765147926847055043275613688284730725665248110879449899362264829236899315936009893392193130347405158251161198776593825925012295095354961090338647794761436856568384348544759704654930999358405268422632307384972924785564942206454328040993656009976019887941091363945732186709946704404815833892496723925079492767447780739975286934240402358132188919389129905454498658046281432577050584888444445861180895892596225766397251174372446422126467723384631073675430939059008540612500276377295754916960329986927526454681065555835446395523094977597412985733,"Dq":16841607730729779627880254737037774275779080108991259477426970506807599516834294855936468737964805708655851677434354230175954764404521738920387232291067463077265226781891943132572792211121754246391635530802528781571848010706640373056663308127699305062117691053480260736325118535987736173516864180026715457076559142313393867416303731172135824915975669274116088772252187506773681691029370847575861277330815144740242001614038173411351927897550159472187183222786780178092858880596561535853910492143960790906343282877269046075993405262988094570992390564561887627916194430414303984731460217763692273970657921724531238912593,"Qinv":13386450555509069245317666985331292478728039484440837065189457918756926085772889573452664818969207436096145088071795396654392004940595219762405312750934346518716740936238520072004578261328949676162223098531690599793600587771458065485838287464893640290027597304483114889945915722313306140200593797334834667938307085618314432134506785949791967130287157125647210497214516165218726084383497011560605024384546734297139331536623591040849235382041138776467271730696249828686335872619825082175542573979118200539219034800857165800266206532665733326893998872307865475986277696080650713536968589910998008126592314634991854532329,"CRTValues":null}},"Precanned":false,"RegistrationTimestamp":1640900456461843040,"RegCode":"","TransmissionRegValidationSig":"Zln6nmiiuvO2vZGe4jMrHyEWyd3j89GYUjnugb88V+1Q/mdn9BFV/qmiBpnoNyqAn5XOpHmkYOw0DkBX2Tc3w5NQWHiLgcrAMHlZUkinFZ2/jWeLNGuWTFmauu2q+z1C4R/reAjlRiyo2OuKBZxFoE3mbw85mWYoiyyMbEfyNCHK2ScCmdapHP5kSzWeUi2WjaNi+LUBqmWMLX9nWZs24YD6VapNnStcj7w14XTWtUJ/oXtgwI/uMDa3ZvLwCO6JwsRR3VGq98Dbky4eJhXwCnFTW/Jy/ZR5n3xPccU3Eb+/0eHyYwZGORe0Tr7qGq0YI9wFNfjv3rpEmqqScq9sHg==","ReceptionRegValidationSig":"ABBNeRb2gEVIi/LU3c4rwTxauQpJ0PV0Dp78JT3QGXyoThMjDIgpCZHZRr8clexXkDSNZ0GZCKLQtv48e2TJbO15oodzKZq9fS6HPNc/D2l17DBbNCFTy+kM7MKIqHU4dN2h/fhjYgQ0k3WaR04NGSFtX4FSyeDqh8LbQAlrvKn13Z1fCId6kt9Sp3GTWtRMm2RD3hRNYLzrFHqAiH/rDcMy/d4Ar9wTwKlIcFyX3+1ENjAxTcsnjno7+aa5tT8XI0HdlcvrMDkx+m2kbHquqGJM9hCg/XxhrLFqsjY0F0bVp7J2UeHoLw6s6OgEl9Eg5I0k51K+SSMa1Mse1zggkg==","CmixDhPrivateKey":{"Value":23134336198863750741748703660400387868824411194091704843877765878704684400020511760943730932032181255742725205976185967794840498055119688330124475657025083380879051516507316264855378759159543513707252259922784173347685874456358247463151784175729466898154272096842618045994973870223785531415217217965394756900460790339174016946558150507580144972427210564566872862707000226886100833409714665643615263325876515987666291734559374206190550344559412403144311029314745799204676946278572048079685978558133653417488694953610922501137922416052437955367323383147526279158425720871674296239641638893436600760625905793653337291326,"Fingerprint":1019130854791890857},"CmixDhPublicKey":{"Value":40661473203398365346021139834751279657661515042218153062933068745659041507124553367048607412838109996778705972152516147090017024944177990861357148498071195775787956973205443551118903422765769934453067811496751124796541305224646917714432655244199681118218184307551604728113020909007420531039281826284338933975869040846161322930224431922676228280139352116332784218225442881629816602619617561152949947075502732590198688997850331029595028876889786739167152345971604837504050490241011792155573239919262293917635508122225378077992880296300468988471302542680432268331360185693269950450132051419581315000110594207999508607050616011689445128093546507459669376840509120845943383094511932011628510767784841945662325523012877900497552502527594150800481434891933803449151469858078545202819003974303280259033864766743161709598642156724209578764288690522108924880552091308234645729967740909969025514152089886697815454891469560150611996697804062619010044123824507982370347027313396405015494843434897303661589566168416699605512854149233601685403032629550047093556970695648101495563847168540708660964238960062140890332386702253196064038177801032833477139196626595095716721717130200091320630907308046939829403546847081437153253274137106108481349472925,"Fingerprint":1019130854791890857},"E2eDhPrivateKey":{"Value":15868054927490624034769207675284372396111367138702747294809526549406434197505594302868553416930040949078939726209524016245099170354423124146109791439676150153811847753697222698706991672115541614080484720172498069738416608757527621565114235134343850506971256805006649306801742780960331446133932778313421336826492419515327757859355790985926801091202339643161980910811435573134233363961673385626782920679443568800015505367010870530111147205683029126252427303195582360585233280495455148410685790997024364716148825012857917467229638326785383545555885827743352446807694303549475074691875396849487056555719913457474008032697,"Fingerprint":16801541511233098363},"E2eDhPublicKey":{"Value":147139791407699234997192910595075527435460061524772944343748681206265869846642332345707990677406040145177534321538182440717890359993428686109525202118205223891431899168261959123977071363023161983520620405235747590084693661804115691677488049367521195795602971625363864492999496527190606383593289607097185210792999982667279095678826515909506042508081456493456997354040504436530126940434391612132572210469911068602346111602950742796752446044197938946885845119733654743975968520762482827365434031479613917149883640347855512864879556669871625702373555191119636414088476652878563700716501799409607427689929171266147047756204689439519092680494886131474665331718064247744119146288064620933248987203778241513546175445638858254194548267620960510274776658377038971793051901595492445943697519511203993381901627766295075444537422179881027670118336783809271801530698319821686375879565780071214330909899155111647279589474822393047955732167728825532445888096165721311471522851,"Fingerprint":16801541511233098363}}
\ No newline at end of file
diff --git a/wasm/whitelist.txt b/wasm/whitelist.txt
new file mode 100644
index 0000000000000000000000000000000000000000..b13380509def3eae45d04009b5e89b0843b2d88b
--- /dev/null
+++ b/wasm/whitelist.txt
@@ -0,0 +1 @@
+["0.0.0.0", "127.0.0.1", "::1"]
\ No newline at end of file