diff --git a/.cargo/config b/.cargo/config
deleted file mode 100644
index bbe1fc95..00000000
--- a/.cargo/config
+++ /dev/null
@@ -1,8 +0,0 @@
-[alias]
-# Temporarily removed the backtraces feature from the unit-test run due to compilation errors in
-# the cosmwasm-std package:
-# cosmwasm-std = { git = "https://github.com/scrtlabs/cosmwasm", branch = "secret" }
-# unit-test = "test --lib --features backtraces"
-unit-test = "test --lib"
-integration-test = "test --test integration"
-schema = "run --example schema"
diff --git a/.cargo/config b/.cargo/config
new file mode 120000
index 00000000..ab8b69cb
--- /dev/null
+++ b/.cargo/config
@@ -0,0 +1 @@
+config.toml
\ No newline at end of file
diff --git a/.cargo/config.toml b/.cargo/config.toml
new file mode 100644
index 00000000..0bf94f78
--- /dev/null
+++ b/.cargo/config.toml
@@ -0,0 +1,12 @@
+[alias]
+# Temporarily removed the backtraces feature from the unit-test run due to compilation errors in
+# the cosmwasm-std package:
+# cosmwasm-std = { git = "https://github.com/scrtlabs/cosmwasm", branch = "secret" }
+# unit-test = "test --lib --features backtraces"
+unit-test = "test --lib"
+integration-test = "test --test integration"
+schema = "run --example schema"
+
+[features]
+gas_tracking = []
+gas_evaporation = []
diff --git a/.gitignore b/.gitignore
index ea00b0b0..83987d19 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,5 +1,6 @@
 # Build results
 /target
+/tests/dwb/dist/
 contract.wasm
 contract.wasm.gz
 
@@ -19,3 +20,10 @@ contract.wasm.gz
 # IDEs
 *.iml
 .idea
+
+# Packages
+node_modules/
+
+# Private
+.env
+scrap/
\ No newline at end of file
diff --git a/.vscode/launch.json b/.vscode/launch.json
new file mode 100644
index 00000000..0827ff3b
--- /dev/null
+++ b/.vscode/launch.json
@@ -0,0 +1,36 @@
+{
+	// Use IntelliSense to learn about possible attributes.
+	// Hover to view descriptions of existing attributes.
+	// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
+	"version": "0.2.0",
+	"configurations": [
+		{
+			"type": "bun",
+			"internalConsoleOptions": "neverOpen",
+			"request": "launch",
+			"name": "Debug File",
+			"program": "${file}",
+			"cwd": "${workspaceFolder}",
+			"stopOnEntry": false,
+			"watchMode": false
+		},
+		{
+			"type": "bun",
+			"internalConsoleOptions": "neverOpen",
+			"request": "launch",
+			"name": "Run File",
+			"program": "${file}",
+			"cwd": "${workspaceFolder}",
+			"noDebug": true,
+			"watchMode": false
+		},
+		{
+			"type": "bun",
+			"internalConsoleOptions": "neverOpen",
+			"request": "attach",
+			"name": "Attach Bun",
+			"url": "ws://localhost:6499/",
+			"stopOnEntry": false
+		}
+	]
+}
\ No newline at end of file
diff --git a/.vscode/settings.json b/.vscode/settings.json
new file mode 100644
index 00000000..7dae7aa1
--- /dev/null
+++ b/.vscode/settings.json
@@ -0,0 +1,33 @@
+{
+	"eslint.enable": true,
+	"editor.fontSize": 11,
+	"scm.inputFontSize": 11,
+	"debug.console.fontSize": 10,
+	"markdown.preview.fontSize": 11,
+	"terminal.integrated.fontSize": 10,
+	"files.exclude": {
+		"dist": true,
+		"submodules": true,
+		"**/.git": true,
+		"**/.DS_Store": true,
+		"**/Thumbs.db": true,
+		"**/node_modules": true
+	},
+	"editor.insertSpaces": false,
+	"editor.tabSize": 4,
+	"typescript.tsdk": "node_modules/typescript/lib",
+	"typescript.preferences.quoteStyle": "single",
+	"javascript.format.insertSpaceAfterOpeningAndBeforeClosingEmptyBraces": false,
+	"typescript.format.insertSpaceAfterOpeningAndBeforeClosingEmptyBraces": false,
+	"typescript.format.insertSpaceAfterOpeningAndBeforeClosingJsxExpressionBraces": false,
+	"eslint.workingDirectories": [
+		"./tests/dwb/src",
+	],
+	"eslint.validate": [
+		"javascript",
+		"typescript",
+	],
+	"editor.codeActionsOnSave": {
+		"source.fixAll": "explicit"
+	}
+}
\ No newline at end of file
diff --git a/Cargo.lock b/Cargo.lock
index 8e3ff6b2..d66b8a9f 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -2,11 +2,21 @@
 # It is not intended for manual editing.
 version = 3
 
+[[package]]
+name = "aead"
+version = "0.5.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0"
+dependencies = [
+ "crypto-common",
+ "generic-array",
+]
+
 [[package]]
 name = "ahash"
-version = "0.7.6"
+version = "0.7.8"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47"
+checksum = "891477e0c6a8957309ee5c45a6368af3ae14bb510732d2684ffa19af310920f9"
 dependencies = [
  "getrandom",
  "once_cell",
@@ -27,9 +37,9 @@ checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8"
 
 [[package]]
 name = "base64"
-version = "0.21.0"
+version = "0.21.7"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a4a4ddaa51a5bc52a6948f74c06d20aaaddb71924eab79b8c97a8c556e942d6a"
+checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567"
 
 [[package]]
 name = "base64ct"
@@ -73,9 +83,15 @@ dependencies = [
 
 [[package]]
 name = "byteorder"
-version = "1.4.3"
+version = "1.5.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610"
+checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
+
+[[package]]
+name = "cc"
+version = "1.0.99"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "96c51067fd44124faa7f870b4b1c969379ad32b2ba805aa959430ceaa384f695"
 
 [[package]]
 name = "cfg-if"
@@ -84,36 +100,66 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
 
 [[package]]
-name = "const-oid"
-version = "0.9.2"
+name = "chacha20"
+version = "0.9.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "520fbf3c07483f94e3e3ca9d0cfd913d7718ef2483d2cfd91c0d9e91474ab913"
+checksum = "c3613f74bd2eac03dad61bd53dbe620703d4371614fe0bc3b9f04dd36fe4e818"
+dependencies = [
+ "cfg-if",
+ "cipher",
+ "cpufeatures",
+]
 
 [[package]]
-name = "cosmwasm-crypto"
-version = "1.1.9"
-source = "git+https://github.com/scrtlabs/cosmwasm/?tag=v1.1.9-secret#e40a15f04dae80680dbe22aef760e5eaab6b0a19"
+name = "chacha20poly1305"
+version = "0.10.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "10cd79432192d1c0f4e1a0fef9527696cc039165d729fb41b3f4f4f354c2dc35"
 dependencies = [
- "digest 0.10.6",
- "ed25519-zebra",
- "k256",
- "rand_core 0.6.4",
- "thiserror",
+ "aead",
+ "chacha20",
+ "cipher",
+ "poly1305",
+ "zeroize",
+]
+
+[[package]]
+name = "cipher"
+version = "0.4.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad"
+dependencies = [
+ "crypto-common",
+ "inout",
+ "zeroize",
 ]
 
+[[package]]
+name = "const-oid"
+version = "0.9.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8"
+
+[[package]]
+name = "constant_time_eq"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f7144d30dcf0fafbce74250a3963025d8d52177934239851c917d29f1df280c2"
+
 [[package]]
 name = "cosmwasm-derive"
-version = "1.1.9"
-source = "git+https://github.com/scrtlabs/cosmwasm/?tag=v1.1.9-secret#e40a15f04dae80680dbe22aef760e5eaab6b0a19"
+version = "1.5.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "242e98e7a231c122e08f300d9db3262d1007b51758a8732cd6210b3e9faa4f3a"
 dependencies = [
  "syn 1.0.109",
 ]
 
 [[package]]
 name = "cosmwasm-schema"
-version = "1.2.2"
+version = "1.5.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b1b99f612ccf162940ae2eef9f370ee37cf2ddcf4a9a8f5ee15ec6b46a5ecd2e"
+checksum = "7879036156092ad1c22fe0d7316efc5a5eceec2bc3906462a2560215f2a2f929"
 dependencies = [
  "cosmwasm-schema-derive",
  "schemars",
@@ -124,47 +170,20 @@ dependencies = [
 
 [[package]]
 name = "cosmwasm-schema-derive"
-version = "1.2.2"
+version = "1.5.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a92ceea61033cb69c336abf673da017ddf251fc4e26e0cdd387eaf8bedb14e49"
+checksum = "0bb57855fbfc83327f8445ae0d413b1a05ac0d68c396ab4d122b2abd7bb82cb6"
 dependencies = [
  "proc-macro2",
  "quote",
  "syn 1.0.109",
 ]
 
-[[package]]
-name = "cosmwasm-std"
-version = "1.1.9"
-source = "git+https://github.com/scrtlabs/cosmwasm/?tag=v1.1.9-secret#e40a15f04dae80680dbe22aef760e5eaab6b0a19"
-dependencies = [
- "base64 0.13.1",
- "cosmwasm-crypto",
- "cosmwasm-derive",
- "derivative",
- "forward_ref",
- "hex",
- "schemars",
- "serde",
- "serde-json-wasm",
- "thiserror",
- "uint",
-]
-
-[[package]]
-name = "cosmwasm-storage"
-version = "1.1.9"
-source = "git+https://github.com/scrtlabs/cosmwasm/?tag=v1.1.9-secret#e40a15f04dae80680dbe22aef760e5eaab6b0a19"
-dependencies = [
- "cosmwasm-std",
- "serde",
-]
-
 [[package]]
 name = "cpufeatures"
-version = "0.2.5"
+version = "0.2.12"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "28d997bd5e24a5928dd43e46dc529867e207907fe0b239c3477d924f7f2ca320"
+checksum = "53fe5e26ff1b7aef8bca9c6080520cfb8d9333c7568e1829cef191a9723e5504"
 dependencies = [
  "libc",
 ]
@@ -194,6 +213,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3"
 dependencies = [
  "generic-array",
+ "rand_core 0.6.4",
  "typenum",
 ]
 
@@ -242,9 +262,9 @@ dependencies = [
 
 [[package]]
 name = "digest"
-version = "0.10.6"
+version = "0.10.7"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8168378f4e5023e7218c89c891c0fd8ecdb5e5e4f18cb78f38cf245dd021e76f"
+checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292"
 dependencies = [
  "block-buffer 0.10.4",
  "crypto-common",
@@ -253,9 +273,9 @@ dependencies = [
 
 [[package]]
 name = "dyn-clone"
-version = "1.0.11"
+version = "1.0.17"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "68b0cf012f1230e43cd00ebb729c6bb58707ecfa8ad08b52ef3a4ccd2697fc30"
+checksum = "0d6ef0072f8a535281e4876be788938b528e9a1d43900b82c2569af7da799125"
 
 [[package]]
 name = "ecdsa"
@@ -293,7 +313,7 @@ dependencies = [
  "base16ct",
  "crypto-bigint",
  "der",
- "digest 0.10.6",
+ "digest 0.10.7",
  "ff",
  "generic-array",
  "group",
@@ -314,6 +334,15 @@ dependencies = [
  "subtle",
 ]
 
+[[package]]
+name = "fixed-hash"
+version = "0.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "835c052cb0c08c1acf6ffd71c022172e18723949c8282f2b9f27efbc51e64534"
+dependencies = [
+ "static_assertions",
+]
+
 [[package]]
 name = "forward_ref"
 version = "1.0.0"
@@ -322,9 +351,9 @@ checksum = "c8cbd1169bd7b4a0a20d92b9af7a7e0422888bd38a6f5ec29c1fd8c1558a272e"
 
 [[package]]
 name = "generic-array"
-version = "0.14.6"
+version = "0.14.7"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bff49e947297f3312447abdca79f45f4738097cc82b06e72054d2223f601f1b9"
+checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a"
 dependencies = [
  "typenum",
  "version_check",
@@ -332,9 +361,9 @@ dependencies = [
 
 [[package]]
 name = "getrandom"
-version = "0.2.8"
+version = "0.2.15"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c05aeb6a22b8f62540c194aac980f2115af067bfe15a0734d7277a768d396b31"
+checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7"
 dependencies = [
  "cfg-if",
  "libc",
@@ -367,20 +396,38 @@ version = "0.4.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
 
+[[package]]
+name = "hkdf"
+version = "0.12.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7b5f8eb2ad728638ea2c7d47a21db23b7b58a72ed6a38256b8a1849f15fbbdf7"
+dependencies = [
+ "hmac",
+]
+
 [[package]]
 name = "hmac"
 version = "0.12.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e"
 dependencies = [
- "digest 0.10.6",
+ "digest 0.10.7",
+]
+
+[[package]]
+name = "inout"
+version = "0.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a0c10553d664a4d0bcff9f4215d0aac67a639cc68ef660840afe309b807bc9f5"
+dependencies = [
+ "generic-array",
 ]
 
 [[package]]
 name = "itoa"
-version = "1.0.6"
+version = "1.0.11"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "453ad9f582a441959e5f0d088b02ce04cfe8d51a8eaf077f12ac6d3e94164ca6"
+checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b"
 
 [[package]]
 name = "k256"
@@ -391,26 +438,56 @@ dependencies = [
  "cfg-if",
  "ecdsa",
  "elliptic-curve",
- "sha2 0.10.6",
+ "sha2 0.10.8",
 ]
 
 [[package]]
 name = "libc"
-version = "0.2.140"
+version = "0.2.155"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "99227334921fae1a979cf0bfdfcc6b3e5ce376ef57e16fb6fb3ea2ed6095f80c"
+checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c"
+
+[[package]]
+name = "minicbor"
+version = "0.18.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2a20020e8e2d1881d8736f64011bb5ff99f1db9947ce3089706945c8915695cb"
+dependencies = [
+ "minicbor-derive",
+]
+
+[[package]]
+name = "minicbor-derive"
+version = "0.12.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8608fb1c805b5b6b3d5ab7bd95c40c396df622b64d77b2d621a5eae1eed050ee"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 1.0.109",
+]
+
+[[package]]
+name = "minicbor-ser"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c0834b86a9c56311671913d56f640d7f0b6da803df61121661cc890f0edc0eb1"
+dependencies = [
+ "minicbor",
+ "serde",
+]
 
 [[package]]
 name = "once_cell"
-version = "1.17.1"
+version = "1.19.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b7e5500299e16ebb147ae15a00a942af264cf3688f47923b8fc2cd5858f23ad3"
+checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92"
 
 [[package]]
 name = "opaque-debug"
-version = "0.3.0"
+version = "0.3.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5"
+checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381"
 
 [[package]]
 name = "pkcs8"
@@ -422,26 +499,47 @@ dependencies = [
  "spki",
 ]
 
+[[package]]
+name = "poly1305"
+version = "0.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8159bd90725d2df49889a078b54f4f79e87f1f8a8444194cdca81d38f5393abf"
+dependencies = [
+ "cpufeatures",
+ "opaque-debug",
+ "universal-hash",
+]
+
 [[package]]
 name = "ppv-lite86"
 version = "0.2.17"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de"
 
+[[package]]
+name = "primitive-types"
+version = "0.12.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0b34d9fd68ae0b74a41b21c03c2f62847aa0ffea044eee893b4c140b37e244e2"
+dependencies = [
+ "fixed-hash",
+ "uint",
+]
+
 [[package]]
 name = "proc-macro2"
-version = "1.0.52"
+version = "1.0.85"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1d0e1ae9e836cc3beddd63db0df682593d7e2d3d891ae8c9083d2113e1744224"
+checksum = "22244ce15aa966053a896d1accb3a6e68469b97c7f33f284b99f0d576879fc23"
 dependencies = [
  "unicode-ident",
 ]
 
 [[package]]
 name = "quote"
-version = "1.0.26"
+version = "1.0.36"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4424af4bf778aae2051a77b60283332f386554255d722233d09fbfc7e30da2fc"
+checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7"
 dependencies = [
  "proc-macro2",
 ]
@@ -482,13 +580,13 @@ dependencies = [
 
 [[package]]
 name = "remain"
-version = "0.2.7"
+version = "0.2.14"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3f4b7d9b4676922ecbbad6d317e0f847762c4b28b935a2db3b44bd4f36c1aa7f"
+checksum = "46aef80f842736de545ada6ec65b81ee91504efd6853f4b96de7414c42ae7443"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 1.0.109",
+ "syn 2.0.66",
 ]
 
 [[package]]
@@ -508,20 +606,20 @@ version = "0.1.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "bd124222d17ad93a644ed9d011a40f4fb64aa54275c08cc216524a9ea82fb09f"
 dependencies = [
- "digest 0.10.6",
+ "digest 0.10.7",
 ]
 
 [[package]]
 name = "ryu"
-version = "1.0.13"
+version = "1.0.18"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f91339c0467de62360649f8d3e185ca8de4224ff281f66000de5eb2a77a79041"
+checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f"
 
 [[package]]
 name = "schemars"
-version = "0.8.12"
+version = "0.8.21"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "02c613288622e5f0c3fdc5dbd4db1c5fbe752746b1d1a56a0630b78fd00de44f"
+checksum = "09c024468a378b7e36765cd36702b7a90cc3cba11654f6685c8f233408e89e92"
 dependencies = [
  "dyn-clone",
  "schemars_derive",
@@ -531,14 +629,14 @@ dependencies = [
 
 [[package]]
 name = "schemars_derive"
-version = "0.8.12"
+version = "0.8.21"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "109da1e6b197438deb6db99952990c7f959572794b80ff93707d55a232545e7c"
+checksum = "b1eee588578aff73f856ab961cd2f79e36bc45d7ded33a7562adba4667aecc0e"
 dependencies = [
  "proc-macro2",
  "quote",
  "serde_derive_internals",
- "syn 1.0.109",
+ "syn 2.0.66",
 ]
 
 [[package]]
@@ -555,16 +653,75 @@ dependencies = [
  "zeroize",
 ]
 
+[[package]]
+name = "secp256k1"
+version = "0.27.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "25996b82292a7a57ed3508f052cfff8640d38d32018784acd714758b43da9c8f"
+dependencies = [
+ "secp256k1-sys",
+]
+
+[[package]]
+name = "secp256k1-sys"
+version = "0.8.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "70a129b9e9efbfb223753b9163c4ab3b13cff7fd9c7f010fbac25ab4099fa07e"
+dependencies = [
+ "cc",
+]
+
+[[package]]
+name = "secret-cosmwasm-crypto"
+version = "1.1.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8535d61c88d0a6c222df2cebb69859d8e9ba419a299a1bc84c904b0d9c00c7b2"
+dependencies = [
+ "digest 0.10.7",
+ "ed25519-zebra",
+ "k256",
+ "rand_core 0.6.4",
+ "thiserror",
+]
+
+[[package]]
+name = "secret-cosmwasm-std"
+version = "1.1.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "50e4393b01aa6587007161a6bb193859deaa8165ab06c8a35f253d329ff99e4d"
+dependencies = [
+ "base64 0.13.1",
+ "cosmwasm-derive",
+ "derivative",
+ "forward_ref",
+ "hex",
+ "schemars",
+ "secret-cosmwasm-crypto",
+ "serde",
+ "serde-json-wasm",
+ "thiserror",
+ "uint",
+]
+
+[[package]]
+name = "secret-cosmwasm-storage"
+version = "1.1.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bb43da2cb72a53b16ea1555bca794fb828b48ab24ebeb45f8e26f1881c45a783"
+dependencies = [
+ "secret-cosmwasm-std",
+ "serde",
+]
+
 [[package]]
 name = "secret-toolkit"
-version = "0.8.0"
-source = "git+https://github.com/scrtlabs/secret-toolkit?rev=9b74bdac71c2fedcc12246f18cdfdd94b8991282#9b74bdac71c2fedcc12246f18cdfdd94b8991282"
+version = "0.10.0"
+source = "git+https://github.com/SolarRepublic/secret-toolkit.git?rev=8aed92d589dc119f69d20f8538d5a6eea8003d95#8aed92d589dc119f69d20f8538d5a6eea8003d95"
 dependencies = [
  "secret-toolkit-crypto",
+ "secret-toolkit-notification",
  "secret-toolkit-permit",
  "secret-toolkit-serialization",
- "secret-toolkit-snip20",
- "secret-toolkit-snip721",
  "secret-toolkit-storage",
  "secret-toolkit-utils",
  "secret-toolkit-viewing-key",
@@ -572,93 +729,91 @@ dependencies = [
 
 [[package]]
 name = "secret-toolkit-crypto"
-version = "0.8.0"
-source = "git+https://github.com/scrtlabs/secret-toolkit?rev=9b74bdac71c2fedcc12246f18cdfdd94b8991282#9b74bdac71c2fedcc12246f18cdfdd94b8991282"
+version = "0.10.0"
+source = "git+https://github.com/SolarRepublic/secret-toolkit.git?rev=8aed92d589dc119f69d20f8538d5a6eea8003d95#8aed92d589dc119f69d20f8538d5a6eea8003d95"
 dependencies = [
- "cosmwasm-std",
+ "hkdf",
  "rand_chacha",
  "rand_core 0.6.4",
- "sha2 0.10.6",
+ "secp256k1",
+ "secret-cosmwasm-std",
+ "sha2 0.10.8",
 ]
 
 [[package]]
-name = "secret-toolkit-permit"
-version = "0.8.0"
-source = "git+https://github.com/scrtlabs/secret-toolkit?rev=9b74bdac71c2fedcc12246f18cdfdd94b8991282#9b74bdac71c2fedcc12246f18cdfdd94b8991282"
+name = "secret-toolkit-notification"
+version = "0.10.0"
+source = "git+https://github.com/SolarRepublic/secret-toolkit.git?rev=8aed92d589dc119f69d20f8538d5a6eea8003d95#8aed92d589dc119f69d20f8538d5a6eea8003d95"
 dependencies = [
- "bech32",
- "cosmwasm-std",
- "remain",
+ "chacha20poly1305",
+ "generic-array",
+ "hkdf",
+ "minicbor-ser",
+ "primitive-types",
  "ripemd",
  "schemars",
+ "secret-cosmwasm-std",
  "secret-toolkit-crypto",
  "serde",
+ "sha2 0.10.8",
 ]
 
 [[package]]
-name = "secret-toolkit-serialization"
-version = "0.8.0"
-source = "git+https://github.com/scrtlabs/secret-toolkit?rev=9b74bdac71c2fedcc12246f18cdfdd94b8991282#9b74bdac71c2fedcc12246f18cdfdd94b8991282"
-dependencies = [
- "bincode2",
- "cosmwasm-std",
- "schemars",
- "serde",
-]
-
-[[package]]
-name = "secret-toolkit-snip20"
-version = "0.8.0"
-source = "git+https://github.com/scrtlabs/secret-toolkit?rev=9b74bdac71c2fedcc12246f18cdfdd94b8991282#9b74bdac71c2fedcc12246f18cdfdd94b8991282"
+name = "secret-toolkit-permit"
+version = "0.10.0"
+source = "git+https://github.com/SolarRepublic/secret-toolkit.git?rev=8aed92d589dc119f69d20f8538d5a6eea8003d95#8aed92d589dc119f69d20f8538d5a6eea8003d95"
 dependencies = [
- "cosmwasm-std",
+ "bech32",
+ "remain",
+ "ripemd",
  "schemars",
- "secret-toolkit-utils",
+ "secret-cosmwasm-std",
+ "secret-toolkit-crypto",
  "serde",
 ]
 
 [[package]]
-name = "secret-toolkit-snip721"
-version = "0.8.0"
-source = "git+https://github.com/scrtlabs/secret-toolkit?rev=9b74bdac71c2fedcc12246f18cdfdd94b8991282#9b74bdac71c2fedcc12246f18cdfdd94b8991282"
+name = "secret-toolkit-serialization"
+version = "0.10.0"
+source = "git+https://github.com/SolarRepublic/secret-toolkit.git?rev=8aed92d589dc119f69d20f8538d5a6eea8003d95#8aed92d589dc119f69d20f8538d5a6eea8003d95"
 dependencies = [
- "cosmwasm-std",
+ "bincode2",
  "schemars",
- "secret-toolkit-utils",
+ "secret-cosmwasm-std",
  "serde",
 ]
 
 [[package]]
 name = "secret-toolkit-storage"
-version = "0.8.0"
-source = "git+https://github.com/scrtlabs/secret-toolkit?rev=9b74bdac71c2fedcc12246f18cdfdd94b8991282#9b74bdac71c2fedcc12246f18cdfdd94b8991282"
+version = "0.10.0"
+source = "git+https://github.com/SolarRepublic/secret-toolkit.git?rev=8aed92d589dc119f69d20f8538d5a6eea8003d95#8aed92d589dc119f69d20f8538d5a6eea8003d95"
 dependencies = [
- "cosmwasm-std",
- "cosmwasm-storage",
+ "secret-cosmwasm-std",
+ "secret-cosmwasm-storage",
  "secret-toolkit-serialization",
  "serde",
 ]
 
 [[package]]
 name = "secret-toolkit-utils"
-version = "0.8.0"
-source = "git+https://github.com/scrtlabs/secret-toolkit?rev=9b74bdac71c2fedcc12246f18cdfdd94b8991282#9b74bdac71c2fedcc12246f18cdfdd94b8991282"
+version = "0.10.0"
+source = "git+https://github.com/SolarRepublic/secret-toolkit.git?rev=8aed92d589dc119f69d20f8538d5a6eea8003d95#8aed92d589dc119f69d20f8538d5a6eea8003d95"
 dependencies = [
- "cosmwasm-std",
- "cosmwasm-storage",
  "schemars",
+ "secret-cosmwasm-std",
+ "secret-cosmwasm-storage",
  "serde",
 ]
 
 [[package]]
 name = "secret-toolkit-viewing-key"
-version = "0.8.0"
-source = "git+https://github.com/scrtlabs/secret-toolkit?rev=9b74bdac71c2fedcc12246f18cdfdd94b8991282#9b74bdac71c2fedcc12246f18cdfdd94b8991282"
+version = "0.10.0"
+source = "git+https://github.com/SolarRepublic/secret-toolkit.git?rev=8aed92d589dc119f69d20f8538d5a6eea8003d95#8aed92d589dc119f69d20f8538d5a6eea8003d95"
 dependencies = [
- "base64 0.21.0",
- "cosmwasm-std",
- "cosmwasm-storage",
+ "base64 0.21.7",
  "schemars",
+ "secret-cosmwasm-std",
+ "secret-cosmwasm-storage",
  "secret-toolkit-crypto",
  "secret-toolkit-utils",
  "serde",
@@ -667,13 +822,22 @@ dependencies = [
 
 [[package]]
 name = "serde"
-version = "1.0.158"
+version = "1.0.203"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "771d4d9c4163ee138805e12c710dd365e4f44be8be0503cb1bb9eb989425d9c9"
+checksum = "7253ab4de971e72fb7be983802300c30b5a7f0c2e56fab8abfc6a214307c0094"
 dependencies = [
  "serde_derive",
 ]
 
+[[package]]
+name = "serde-big-array"
+version = "0.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "11fc7cc2c76d73e0f27ee52abbd64eec84d46f370c88371120433196934e4b7f"
+dependencies = [
+ "serde",
+]
+
 [[package]]
 name = "serde-json-wasm"
 version = "0.4.1"
@@ -685,31 +849,31 @@ dependencies = [
 
 [[package]]
 name = "serde_derive"
-version = "1.0.158"
+version = "1.0.203"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e801c1712f48475582b7696ac71e0ca34ebb30e09338425384269d9717c62cad"
+checksum = "500cbc0ebeb6f46627f50f3f5811ccf6bf00643be300b4c3eabc0ef55dc5b5ba"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.4",
+ "syn 2.0.66",
 ]
 
 [[package]]
 name = "serde_derive_internals"
-version = "0.26.0"
+version = "0.29.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "85bf8229e7920a9f636479437026331ce11aa132b4dde37d121944a44d6e5f3c"
+checksum = "18d26a20a969b9e3fdf2fc2d9f21eda6c40e2de84c9408bb5d3b05d499aae711"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 1.0.109",
+ "syn 2.0.66",
 ]
 
 [[package]]
 name = "serde_json"
-version = "1.0.94"
+version = "1.0.117"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1c533a59c9d8a93a09c6ab31f0fd5e5f4dd1b8fc9434804029839884765d04ea"
+checksum = "455182ea6142b14f93f4bc5320a2b31c1f266b66a4a5c858b013302a5d8cbfc3"
 dependencies = [
  "itoa",
  "ryu",
@@ -731,13 +895,13 @@ dependencies = [
 
 [[package]]
 name = "sha2"
-version = "0.10.6"
+version = "0.10.8"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "82e6b795fe2e3b1e845bafcb27aa35405c4d47cdfc92af5fc8d3002f76cebdc0"
+checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8"
 dependencies = [
  "cfg-if",
  "cpufeatures",
- "digest 0.10.6",
+ "digest 0.10.7",
 ]
 
 [[package]]
@@ -746,23 +910,28 @@ version = "1.6.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "74233d3b3b2f6d4b006dc19dee745e73e2a6bfb6f93607cd3b02bd5b00797d7c"
 dependencies = [
- "digest 0.10.6",
+ "digest 0.10.7",
  "rand_core 0.6.4",
 ]
 
 [[package]]
 name = "snip20-reference-impl"
-version = "1.0.0"
+version = "2.0.0"
 dependencies = [
- "base64 0.21.0",
+ "base64 0.21.7",
+ "constant_time_eq",
  "cosmwasm-schema",
- "cosmwasm-std",
- "cosmwasm-storage",
+ "minicbor-ser",
+ "primitive-types",
  "rand",
  "schemars",
+ "secret-cosmwasm-std",
+ "secret-cosmwasm-storage",
  "secret-toolkit",
  "secret-toolkit-crypto",
  "serde",
+ "serde-big-array",
+ "static_assertions",
 ]
 
 [[package]]
@@ -783,9 +952,9 @@ checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
 
 [[package]]
 name = "subtle"
-version = "2.4.1"
+version = "2.5.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601"
+checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc"
 
 [[package]]
 name = "syn"
@@ -800,9 +969,9 @@ dependencies = [
 
 [[package]]
 name = "syn"
-version = "2.0.4"
+version = "2.0.66"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2c622ae390c9302e214c31013517c2061ecb2699935882c60a9b37f82f8625ae"
+checksum = "c42f3f41a2de00b01c0aaad383c5a45241efc8b2d1eda5661812fda5f3cdcff5"
 dependencies = [
  "proc-macro2",
  "quote",
@@ -811,29 +980,29 @@ dependencies = [
 
 [[package]]
 name = "thiserror"
-version = "1.0.39"
+version = "1.0.61"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a5ab016db510546d856297882807df8da66a16fb8c4101cb8b30054b0d5b2d9c"
+checksum = "c546c80d6be4bc6a00c0f01730c08df82eaa7a7a61f11d656526506112cc1709"
 dependencies = [
  "thiserror-impl",
 ]
 
 [[package]]
 name = "thiserror-impl"
-version = "1.0.39"
+version = "1.0.61"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5420d42e90af0c38c3290abcca25b9b3bdf379fc9f55c528f53a269d9c9a267e"
+checksum = "46c3384250002a6d5af4d114f2845d37b57521033f30d5c3f46c4d70e1197533"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 1.0.109",
+ "syn 2.0.66",
 ]
 
 [[package]]
 name = "typenum"
-version = "1.16.0"
+version = "1.17.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba"
+checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825"
 
 [[package]]
 name = "uint"
@@ -849,9 +1018,19 @@ dependencies = [
 
 [[package]]
 name = "unicode-ident"
-version = "1.0.8"
+version = "1.0.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
+
+[[package]]
+name = "universal-hash"
+version = "0.5.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e5464a87b239f13a63a501f2701565754bae92d243d4bb7eb12f6d57d2269bf4"
+checksum = "fc1de2c688dc15305988b563c3854064043356019f97a4b46276fe734c4f07ea"
+dependencies = [
+ "crypto-common",
+ "subtle",
+]
 
 [[package]]
 name = "version_check"
@@ -867,6 +1046,6 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
 
 [[package]]
 name = "zeroize"
-version = "1.5.7"
+version = "1.8.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c394b5bd0c6f669e7275d9c20aa90ae064cb22e75a1cad54e1b34088034b149f"
+checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde"
diff --git a/Cargo.toml b/Cargo.toml
index e6435002..b63d7f2b 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -1,7 +1,7 @@
 [package]
 name = "snip20-reference-impl"
-version = "1.0.0"
-authors = ["Itzik <itzik@keytango.io>"]
+version = "2.0.0"
+authors = ["@reuvenpo","@toml01","@assafmo","@liorbond","Itzik <itzik@keytango.io>","@darwinzer0","@supdoggie"]
 edition = "2021"
 exclude = [
   # Those files are rust-optimizer artifacts. You might want to commit them for convenience but they should not be part of the source code publication.
@@ -29,24 +29,27 @@ overflow-checks = true
 # for more explicit tests, cargo test --features=backtraces
 #default = ["debug-print"]
 backtraces = ["cosmwasm-std/backtraces"]
+gas_tracking = []
+gas_evaporation = []
 
 # debug-print = ["cosmwasm-std/debug-print"]
 [dependencies]
-cosmwasm-std = { git = "https://github.com/scrtlabs/cosmwasm/", default-features = false, tag = "v1.1.9-secret" }
-cosmwasm-storage = { git = "https://github.com/scrtlabs/cosmwasm/", tag = "v1.1.9-secret" }
+cosmwasm-std = { package = "secret-cosmwasm-std", version = "1.1.11"  }
+cosmwasm-storage = { package = "secret-cosmwasm-storage", version = "1.1.11"  }
 rand = { version = "0.8.5", default-features = false }
-secret-toolkit = { git = "https://github.com/scrtlabs/secret-toolkit", features = [
-  "permit",
-  "viewing-key",
-], rev = "9b74bdac71c2fedcc12246f18cdfdd94b8991282" }
-secret-toolkit-crypto = { git = "https://github.com/scrtlabs/secret-toolkit", features = [
-  "rand",
-  "hash",
-], rev = "9b74bdac71c2fedcc12246f18cdfdd94b8991282" }
+# secret-toolkit = { version = "0.10.0", default-features = false, features = ["permit", "storage", "viewing-key"] }
+secret-toolkit = { git = "https://github.com/SolarRepublic/secret-toolkit.git", default-features = false, features = ["permit", "storage", "viewing-key", "notification"], rev = "8aed92d589dc119f69d20f8538d5a6eea8003d95" }
+# secret-toolkit-crypto = { version = "0.10.0", default-features = false, features = ["hash"] }
+secret-toolkit-crypto = { git = "https://github.com/SolarRepublic/secret-toolkit.git", default-features = false, features = ["hash"], rev = "8aed92d589dc119f69d20f8538d5a6eea8003d95" }
+static_assertions = "1.1.0"
 
 schemars = "0.8.12"
 serde = { version = "1.0.158", default-features = false, features = ["derive"] }
+serde-big-array = "0.5.1"
 base64 = "0.21.0"
+constant_time_eq = "0.3.0"
+primitive-types = { version = "0.12.2", default-features = false }
+minicbor-ser = "0.2.0"
 
 [dev-dependencies]
 cosmwasm-schema = { version = "1.1.8" }
diff --git a/Makefile b/Makefile
index eee79095..9c425118 100644
--- a/Makefile
+++ b/Makefile
@@ -51,19 +51,26 @@ _compile:
 	cargo build --target wasm32-unknown-unknown --locked
 	cp ./target/wasm32-unknown-unknown/debug/*.wasm ./contract.wasm
 
+.PHONY: compile-integration _compile-integration
+compile-integration: _compile-integration contract.wasm.gz
+_compile-integration:
+	DWB_CAPACITY=64 BTBE_CAPACITY=64 RUSTFLAGS='-C link-arg=-s' cargo build --features "gas_tracking" --release --target wasm32-unknown-unknown
+	@# The following line is not necessary, may work only on linux (extra size optimization)
+	wasm-opt -Oz ./target/wasm32-unknown-unknown/release/*.wasm --all-features -o ./contract.wasm
+
 .PHONY: compile-optimized _compile-optimized
 compile-optimized: _compile-optimized contract.wasm.gz
 _compile-optimized:
 	RUSTFLAGS='-C link-arg=-s' cargo build --release --target wasm32-unknown-unknown
 	@# The following line is not necessary, may work only on linux (extra size optimization)
-	wasm-opt -Oz ./target/wasm32-unknown-unknown/release/*.wasm -o ./contract.wasm
+	wasm-opt -Oz ./target/wasm32-unknown-unknown/release/*.wasm --all-features -o ./contract.wasm
 
 .PHONY: compile-optimized-reproducible
 compile-optimized-reproducible:
 	docker run --rm -v "$$(pwd)":/contract \
 		--mount type=volume,source="$$(basename "$$(pwd)")_cache",target=/code/target \
 		--mount type=volume,source=registry_cache,target=/usr/local/cargo/registry \
-		enigmampc/secret-contract-optimizer:1.0.7
+		enigmampc/secret-contract-optimizer:1.0.10
 
 contract.wasm.gz: contract.wasm
 	cat ./contract.wasm | gzip -9 > ./contract.wasm.gz
@@ -76,7 +83,7 @@ start-server: # CTRL+C to stop
 	docker run -it --rm \
 		-p 9091:9091 -p 26657:26657 -p 26656:26656 -p 1317:1317 -p 5000:5000 \
 		-v $$(pwd):/root/code \
-		--name secretdev ghcr.io/scrtlabs/localsecret:v1.6.0-alpha.4
+		--name secretdev docker pull ghcr.io/scrtlabs/localsecret:v1.13.1
 
 .PHONY: schema
 schema:
diff --git a/build.rs b/build.rs
new file mode 100644
index 00000000..60de26dd
--- /dev/null
+++ b/build.rs
@@ -0,0 +1,23 @@
+use std::env;
+use std::fs::File;
+use std::io::Write;
+use std::path::Path;
+
+fn main() {
+    // config parameters
+    let dwb_capacity = env::var("DWB_CAPACITY").unwrap_or_else(|_| "64".to_string());
+    let btbe_capacity = env::var("BTBE_CAPACITY").unwrap_or_else(|_| "64".to_string());
+
+    // path to destination config.rs file
+    let out_dir = env::var("OUT_DIR").expect("Missing OUT_DIR");
+    let dest_path = Path::new(&out_dir).join("config.rs");
+
+    // write constants
+    let mut file = File::create(&dest_path).expect("Failed to write to config.rs");
+    write!(file, "pub const DWB_CAPACITY: u16 = {};\n", dwb_capacity).unwrap();
+    write!(file, "pub const BTBE_CAPACITY: u16 = {};\n", btbe_capacity).unwrap();
+
+    // monitor
+    println!("cargo:rerun-if-env-changed=DWB_CAPACITY");
+    println!("cargo:rerun-if-env-changed=BTBE_CAPACITY");
+}
diff --git a/src/batch.rs b/src/batch.rs
index dbe47fb1..47b4bb09 100644
--- a/src/batch.rs
+++ b/src/batch.rs
@@ -3,11 +3,7 @@
 use schemars::JsonSchema;
 use serde::{Deserialize, Serialize};
 
-use cosmwasm_std::{Addr, Binary, Uint128};
-
-pub trait HasDecoy {
-    fn decoys(&self) -> &Option<Vec<Addr>>;
-}
+use cosmwasm_std::{Binary, Uint128};
 
 #[derive(Serialize, Deserialize, JsonSchema, Clone, Debug)]
 #[serde(rename_all = "snake_case")]
@@ -15,7 +11,6 @@ pub struct TransferAction {
     pub recipient: String,
     pub amount: Uint128,
     pub memo: Option<String>,
-    pub decoys: Option<Vec<Addr>>,
 }
 
 #[derive(Serialize, Deserialize, JsonSchema, Clone, Debug)]
@@ -26,7 +21,6 @@ pub struct SendAction {
     pub amount: Uint128,
     pub msg: Option<Binary>,
     pub memo: Option<String>,
-    pub decoys: Option<Vec<Addr>>,
 }
 
 #[derive(Serialize, Deserialize, JsonSchema, Clone, Debug)]
@@ -36,7 +30,6 @@ pub struct TransferFromAction {
     pub recipient: String,
     pub amount: Uint128,
     pub memo: Option<String>,
-    pub decoys: Option<Vec<Addr>>,
 }
 
 #[derive(Serialize, Deserialize, JsonSchema, Clone, Debug)]
@@ -48,7 +41,6 @@ pub struct SendFromAction {
     pub amount: Uint128,
     pub msg: Option<Binary>,
     pub memo: Option<String>,
-    pub decoys: Option<Vec<Addr>>,
 }
 
 #[derive(Serialize, Deserialize, JsonSchema, Clone, Debug)]
@@ -57,7 +49,6 @@ pub struct MintAction {
     pub recipient: String,
     pub amount: Uint128,
     pub memo: Option<String>,
-    pub decoys: Option<Vec<Addr>>,
 }
 
 #[derive(Serialize, Deserialize, JsonSchema, Clone, Debug)]
@@ -66,22 +57,4 @@ pub struct BurnFromAction {
     pub owner: String,
     pub amount: Uint128,
     pub memo: Option<String>,
-    pub decoys: Option<Vec<Addr>>,
 }
-
-macro_rules! impl_decoyable {
-    ($struct:ty) => {
-        impl HasDecoy for $struct {
-            fn decoys(&self) -> &Option<Vec<Addr>> {
-                &self.decoys
-            }
-        }
-    };
-}
-
-impl_decoyable!(BurnFromAction);
-impl_decoyable!(MintAction);
-impl_decoyable!(SendFromAction);
-impl_decoyable!(TransferFromAction);
-impl_decoyable!(TransferAction);
-impl_decoyable!(SendAction);
diff --git a/src/btbe.rs b/src/btbe.rs
new file mode 100644
index 00000000..c213b44c
--- /dev/null
+++ b/src/btbe.rs
@@ -0,0 +1,915 @@
+//! BTBE stands for bitwise-trie of bucketed entries
+
+include!(concat!(env!("OUT_DIR"), "/config.rs"));
+
+use constant_time_eq::constant_time_eq;
+use cosmwasm_std::{CanonicalAddr, StdError, StdResult, Storage};
+use primitive_types::U256;
+use secret_toolkit::{
+    serialization::{Bincode2, Serde},
+    storage::Item,
+};
+use secret_toolkit_crypto::hkdf_sha_256;
+use serde::{Deserialize, Serialize};
+use serde_big_array::BigArray;
+
+use crate::state::{safe_add_u64, INTERNAL_SECRET};
+use crate::dwb::{amount_u64, DelayedWriteBufferEntry, TxBundle};
+#[cfg(feature = "gas_tracking")]
+use crate::gas_tracker::GasTracker;
+
+pub const KEY_BTBE_ENTRY_HISTORY: &[u8] = b"btbe-entry-hist";
+pub const KEY_BTBE_BUCKETS_COUNT: &[u8] = b"btbe-buckets-cnt";
+pub const KEY_BTBE_BUCKETS: &[u8] = b"btbe-buckets";
+pub const KEY_BTBE_TRIE_NODES: &[u8] = b"btbe-trie-nodes";
+pub const KEY_BTBE_TRIE_NODES_COUNT: &[u8] = b"btbe-trie-nodes-cnt";
+
+const BUCKETING_SALT_BYTES: &[u8; 14] = b"bucketing-salt";
+
+const U32_BYTES: usize = 4;
+const U128_BYTES: usize = 16;
+
+#[cfg(test)]
+const BTBE_BUCKET_ADDRESS_BYTES: usize = 54;
+#[cfg(not(test))]
+const BTBE_BUCKET_ADDRESS_BYTES: usize = 20;
+const BTBE_BUCKET_BALANCE_BYTES: usize = 8; // Max 16 (u128)
+const BTBE_BUCKET_HISTORY_BYTES: usize = 4; // Max 4  (u32)
+
+const_assert!(BTBE_BUCKET_BALANCE_BYTES <= U128_BYTES);
+const_assert!(BTBE_BUCKET_HISTORY_BYTES <= U32_BYTES);
+
+const BTBE_BUCKET_ENTRY_BYTES: usize =
+    BTBE_BUCKET_ADDRESS_BYTES + BTBE_BUCKET_BALANCE_BYTES + BTBE_BUCKET_HISTORY_BYTES;
+
+/// canonical address bytes corresponding to the 33-byte null public key, in hexadecimal
+#[cfg(test)]
+const IMPOSSIBLE_ADDR: [u8; BTBE_BUCKET_ADDRESS_BYTES] = [
+    0x29, 0xCF, 0xC6, 0x37, 0x62, 0x55, 0xA7, 0x84, 0x51, 0xEE, 0xB4, 0xB1, 0x29, 0xED, 0x8E, 0xAC,
+    0xFF, 0xA2, 0xFE, 0xEF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+];
+#[cfg(not(test))]
+const IMPOSSIBLE_ADDR: [u8; BTBE_BUCKET_ADDRESS_BYTES] = [
+    0x29, 0xCF, 0xC6, 0x37, 0x62, 0x55, 0xA7, 0x84, 0x51, 0xEE, 0xB4, 0xB1, 0x29, 0xED, 0x8E, 0xAC,
+    0xFF, 0xA2, 0xFE, 0xEF,
+];
+
+/// A `StoredEntry` consists of the address, balance, and tx bundle history length in a byte array representation.
+/// The methods of the struct implementation also handle pushing and getting the tx bundle history in a simplified
+/// append store.
+#[derive(Serialize, Deserialize, Clone, Copy, Debug, PartialEq)]
+#[cfg_attr(test, derive(Eq))]
+pub struct StoredEntry(#[serde(with = "BigArray")] [u8; BTBE_BUCKET_ENTRY_BYTES]);
+
+impl StoredEntry {
+    fn new(address: &CanonicalAddr) -> StdResult<Self> {
+        let address = address.as_slice();
+
+        if address.len() != BTBE_BUCKET_ADDRESS_BYTES {
+            return Err(StdError::generic_err("bucket: invalid address length"));
+        }
+
+        let mut result = [0u8; BTBE_BUCKET_ENTRY_BYTES];
+        result[..BTBE_BUCKET_ADDRESS_BYTES].copy_from_slice(address);
+        Ok(Self { 0: result })
+    }
+
+    fn from(
+        storage: &mut dyn Storage,
+        dwb_entry: &DelayedWriteBufferEntry,
+        amount_spent: Option<u128>,
+    ) -> StdResult<Self> {
+        let mut entry = StoredEntry::new(&dwb_entry.recipient()?)?;
+
+        let amount_spent = amount_u64(amount_spent)?;
+
+        // error should never happen because already checked in `settle_sender_or_owner_account`
+        let balance = if let Some(new_balance) = dwb_entry.amount()?.checked_sub(amount_spent) {
+            new_balance
+        } else {
+            return Err(StdError::generic_err(format!(
+                "insufficient funds while creating StoredEntry; balance:{}, amount_spent:{}",
+                dwb_entry.amount()?,
+                amount_spent,
+            )));
+        };
+
+        entry.set_balance(balance)?;
+        entry.push_tx_bundle(
+            storage,
+            &TxBundle {
+                head_node: dwb_entry.head_node()?,
+                list_len: dwb_entry.list_len()?,
+                offset: 0,
+            },
+        )?;
+
+        Ok(entry)
+    }
+
+    fn address_slice(&self) -> &[u8] {
+        &self.0[..BTBE_BUCKET_ADDRESS_BYTES]
+    }
+
+    fn address(&self) -> StdResult<CanonicalAddr> {
+        let result = CanonicalAddr::try_from(self.address_slice())
+            .or(Err(StdError::generic_err("Get bucket address error")))?;
+        Ok(result)
+    }
+
+    pub fn balance(&self) -> StdResult<u64> {
+        let start = BTBE_BUCKET_ADDRESS_BYTES;
+        let end = start + BTBE_BUCKET_BALANCE_BYTES;
+        let amount_slice = &self.0[start..end];
+        let result = amount_slice
+            .try_into()
+            .or(Err(StdError::generic_err("Get bucket balance error")))?;
+        Ok(u64::from_be_bytes(result))
+    }
+
+    fn set_balance(&mut self, val: u64) -> StdResult<()> {
+        let start = BTBE_BUCKET_ADDRESS_BYTES;
+        let end = start + BTBE_BUCKET_BALANCE_BYTES;
+        self.0[start..end].copy_from_slice(&val.to_be_bytes());
+        Ok(())
+    }
+
+    pub fn history_len(&self) -> StdResult<u32> {
+        let start = BTBE_BUCKET_ADDRESS_BYTES + BTBE_BUCKET_BALANCE_BYTES;
+        let end = start + BTBE_BUCKET_HISTORY_BYTES;
+        let history_len_slice = &self.0[start..end];
+        let mut result = [0u8; U32_BYTES];
+        result[U32_BYTES - BTBE_BUCKET_HISTORY_BYTES..].copy_from_slice(history_len_slice);
+        Ok(u32::from_be_bytes(result))
+    }
+
+    fn set_history_len(&mut self, val: u32) -> StdResult<()> {
+        let start = BTBE_BUCKET_ADDRESS_BYTES + BTBE_BUCKET_BALANCE_BYTES;
+        let end = start + BTBE_BUCKET_HISTORY_BYTES;
+        let val_bytes = &val.to_be_bytes()[U32_BYTES - BTBE_BUCKET_HISTORY_BYTES..];
+        if val_bytes.len() != BTBE_BUCKET_HISTORY_BYTES {
+            return Err(StdError::generic_err("Set bucket history len error"));
+        }
+        self.0[start..end].copy_from_slice(val_bytes);
+        Ok(())
+    }
+
+    pub fn merge_dwb_entry(
+        &mut self,
+        storage: &mut dyn Storage,
+        dwb_entry: &DelayedWriteBufferEntry,
+        amount_spent: Option<u128>,
+    ) -> StdResult<()> {
+        let history_len = self.history_len()?;
+        if history_len == 0 {
+            return Err(StdError::generic_err(
+                "use `from` to create new entry from dwb_entry",
+            ));
+        }
+
+        let mut balance = self.balance()?;
+        safe_add_u64(&mut balance, dwb_entry.amount()?);
+
+        let amount_spent = amount_u64(amount_spent)?;
+
+        // error should never happen because already checked in `settle_sender_or_owner_account`
+        let balance = if let Some(new_balance) = balance.checked_sub(amount_spent) {
+            new_balance
+        } else {
+            return Err(StdError::generic_err(format!(
+                "insufficient funds while merging entry; balance:{}, amount_spent:{}",
+                balance, amount_spent
+            )));
+        };
+
+        self.set_balance(balance)?;
+
+        // peek at the last tx bundle added
+        let last_tx_bundle = self.get_tx_bundle_at(storage, history_len - 1)?;
+        let tx_bundle = TxBundle {
+            head_node: dwb_entry.head_node()?,
+            list_len: dwb_entry.list_len()?,
+            offset: last_tx_bundle.offset + u32::from(last_tx_bundle.list_len),
+        };
+        self.push_tx_bundle(storage, &tx_bundle)?;
+
+        Ok(())
+    }
+
+    // simplified appendstore impl for tx history
+
+    /// gets the element at pos if within bounds
+    pub fn get_tx_bundle_at(&self, storage: &dyn Storage, pos: u32) -> StdResult<TxBundle> {
+        let len = self.history_len()?;
+        if pos >= len {
+            return Err(StdError::generic_err("access out of bounds"));
+        }
+        self.get_tx_bundle_at_unchecked(storage, pos)
+    }
+
+    /// tries to get the element at pos
+    fn get_tx_bundle_at_unchecked(&self, storage: &dyn Storage, pos: u32) -> StdResult<TxBundle> {
+        let bundle_data = storage.get(
+            &[
+                KEY_BTBE_ENTRY_HISTORY,
+                self.address_slice(),
+                pos.to_be_bytes().as_slice(),
+            ]
+            .concat(),
+        );
+        let bundle_data = bundle_data.ok_or_else(|| {
+            return StdError::generic_err("tx bundle not found");
+        })?;
+        Bincode2::deserialize(&bundle_data)
+    }
+
+    /// Sets data at a given index
+    fn set_tx_bundle_at_unchecked(
+        &self,
+        storage: &mut dyn Storage,
+        pos: u32,
+        bundle: &TxBundle,
+    ) -> StdResult<()> {
+        let bundle_data = Bincode2::serialize(bundle)?;
+        storage.set(
+            &[
+                KEY_BTBE_ENTRY_HISTORY,
+                self.address_slice(),
+                pos.to_be_bytes().as_slice(),
+            ]
+            .concat(),
+            &bundle_data,
+        );
+        Ok(())
+    }
+
+    /// Pushes a tx bundle
+    fn push_tx_bundle(&mut self, storage: &mut dyn Storage, bundle: &TxBundle) -> StdResult<()> {
+        let len = self.history_len()?;
+        self.set_tx_bundle_at_unchecked(storage, len, bundle)?;
+        self.set_history_len(len.saturating_add(1))?;
+        Ok(())
+    }
+}
+
+#[derive(Serialize, Deserialize, Clone, Copy, Debug, PartialEq)]
+pub struct BtbeBucket {
+    pub capacity: u16,
+    #[serde(with = "BigArray")]
+    pub entries: [StoredEntry; BTBE_CAPACITY as usize],
+}
+
+//static BTBE_ENTRY_HISTORY: Item<u64> = Item::new(KEY_BTBE_ENTRY_HISTORY);
+static BTBE_BUCKETS_COUNT: Item<u64> = Item::new(KEY_BTBE_BUCKETS_COUNT);
+static BTBE_BUCKETS: Item<BtbeBucket> = Item::new(KEY_BTBE_BUCKETS);
+
+// create type alias to refer to position of a bucket entry, which is its index in the array plus 1
+type BucketEntryPosition = usize;
+
+impl BtbeBucket {
+    pub fn new() -> StdResult<Self> {
+        Ok(Self {
+            capacity: BTBE_CAPACITY,
+            entries: [StoredEntry::new(&CanonicalAddr::from(&IMPOSSIBLE_ADDR))?;
+                BTBE_CAPACITY as usize],
+        })
+    }
+
+    /// Attempts to add an entry to the bucket; returns false if bucket is at capacity, or true on success
+    pub fn add_entry(&mut self, entry: &StoredEntry) -> bool {
+        // buffer is at capacity
+        if self.capacity == 0 {
+            return false;
+        }
+
+        // has capacity for a new entry; save entry to bucket
+        self.entries[(BTBE_CAPACITY - self.capacity) as usize] = entry.clone();
+
+        // update capacity
+        self.capacity -= 1;
+
+        // done
+        true
+    }
+
+    /// Searches the bucket for an entry containing the given address
+    pub fn constant_time_find_address(
+        &self,
+        address: &CanonicalAddr,
+    ) -> Option<(usize, StoredEntry)> {
+        let address = address.as_slice();
+
+        // contant-time only applies to this part, so that the index of the entry cannot be distinguished
+        let mut matched_index_p1: BucketEntryPosition = 0;
+        for (idx, entry) in self.entries.iter().enumerate() {
+            let equals = constant_time_eq(address, entry.address_slice()) as usize;
+            matched_index_p1 |= (idx + 1) * equals;
+        }
+
+        match matched_index_p1 {
+            0 => None,
+            idx => Some((idx - 1, self.entries[idx - 1])),
+        }
+    }
+}
+
+#[derive(Serialize, Deserialize, Clone, Copy, Debug)]
+#[cfg_attr(test, derive(PartialEq))]
+pub struct BitwiseTrieNode {
+    pub left: u64,
+    pub right: u64,
+    pub bucket: u64,
+}
+
+pub static BTBE_TRIE_NODES: Item<BitwiseTrieNode> = Item::new(KEY_BTBE_TRIE_NODES);
+pub static BTBE_TRIE_NODES_COUNT: Item<u64> = Item::new(KEY_BTBE_TRIE_NODES_COUNT);
+
+impl BitwiseTrieNode {
+    // creates a new leaf node
+    pub fn new_leaf(storage: &mut dyn Storage, bucket: BtbeBucket) -> StdResult<Self> {
+        let buckets_count = BTBE_BUCKETS_COUNT.load(storage).unwrap_or_default() + 1;
+
+        // ID for new bucket
+        let bucket_id = buckets_count;
+
+        // save updated count
+        BTBE_BUCKETS_COUNT.save(storage, &buckets_count)?;
+
+        // save bucket to storage
+        BTBE_BUCKETS
+            .add_suffix(&bucket_id.to_be_bytes())
+            .save(storage, &bucket)?;
+
+        // create new node
+        Ok(Self {
+            left: 0,
+            right: 0,
+            bucket: bucket_id,
+        })
+    }
+
+    // loads the node's bucket from storage
+    pub fn bucket(self, storage: &dyn Storage) -> StdResult<BtbeBucket> {
+        if self.bucket == 0 {
+            return Err(StdError::generic_err(
+                "btbe: attempted to load bucket of branch node",
+            ));
+        }
+
+        // load bucket from storage
+        BTBE_BUCKETS
+            .add_suffix(&self.bucket.to_be_bytes())
+            .load(storage)
+    }
+
+    // stores the bucket associated with this node
+    fn set_and_save_bucket(self, storage: &mut dyn Storage, bucket: BtbeBucket) -> StdResult<()> {
+        if self.bucket == 0 {
+            return Err(StdError::generic_err(
+                "btbe: attempted to store a bucket to a branch node",
+            ));
+        }
+
+        BTBE_BUCKETS
+            .add_suffix(&self.bucket.to_be_bytes())
+            .save(storage, &bucket)
+    }
+}
+
+/// Determines whether a given entry belongs in the left node (true) or right node (false)
+fn entry_belongs_in_left_node(secret: &[u8], entry: StoredEntry, bit_pos: u8) -> StdResult<bool> {
+    // create key bytes
+    let key_bytes = hkdf_sha_256(
+        &Some(BUCKETING_SALT_BYTES.to_vec()),
+        secret,
+        entry.address_slice(),
+        32,
+    )?;
+
+    // convert to u258
+    let key_u256 = U256::from_big_endian(&key_bytes);
+
+    // extract the bit value at the target bit position
+    return Ok(U256::from(0) == (key_u256 >> (255 - bit_pos)) & U256::from(1));
+}
+
+/// Locates a btbe node given an address; returns tuple of (node, node_id, bit position)
+pub fn locate_btbe_node(
+    storage: &dyn Storage,
+    address: &CanonicalAddr,
+) -> StdResult<(BitwiseTrieNode, u64, u8)> {
+    // load internal contract secret
+    let secret = INTERNAL_SECRET.load(storage)?;
+    let secret = secret.as_slice();
+
+    // create key bytes
+    let hash = hkdf_sha_256(
+        &Some(BUCKETING_SALT_BYTES.to_vec()),
+        secret,
+        address.as_slice(),
+        32,
+    )?;
+
+    // start at root of trie
+    let mut node_id: u64 = 1;
+    let mut node = BTBE_TRIE_NODES
+        .add_suffix(&node_id.to_be_bytes())
+        .load(storage)?;
+    let mut bit_pos: u8 = 0;
+
+    // while the node has children
+    while node.bucket == 0 {
+        // calculate bit value at current bit position
+        let bit_value = (hash[(bit_pos / 8) as usize] >> (7 - (bit_pos % 8))) & 1;
+
+        // increment bit position
+        bit_pos += 1;
+
+        // choose left or right child depending on bit value
+        node_id = if bit_value == 0 {
+            node.left
+        } else {
+            node.right
+        };
+
+        // load child node
+        node = BTBE_TRIE_NODES
+            .add_suffix(&node_id.to_be_bytes())
+            .load(storage)?;
+    }
+
+    Ok((node, node_id, bit_pos))
+}
+
+/// Does a binary search on the append store to find the bundle where the `start_idx` tx can be found.
+/// For a paginated search `start_idx` = `page` * `page_size`.
+/// Returns the bundle index, the bundle, and the index in the bundle list to start at
+pub fn find_start_bundle(
+    storage: &dyn Storage,
+    account: &CanonicalAddr,
+    start_idx: u32,
+) -> StdResult<Option<(u32, TxBundle, u32)>> {
+    let (node, _, _) = locate_btbe_node(storage, account)?;
+    let bucket = node.bucket(storage)?;
+    if let Some((_, entry)) = bucket.constant_time_find_address(account) {
+        let mut left = 0u32;
+        let mut right = entry.history_len()?;
+
+        while left <= right {
+            let mid = (left + right) / 2;
+            let mid_bundle = entry.get_tx_bundle_at(storage, mid)?;
+            if start_idx >= mid_bundle.offset
+                && start_idx < mid_bundle.offset + (mid_bundle.list_len as u32)
+            {
+                // we have the correct bundle
+                // which index in list to start at?
+                let start_at = (mid_bundle.list_len as u32) - (start_idx - mid_bundle.offset) - 1;
+                return Ok(Some((mid, mid_bundle, start_at)));
+            } else if start_idx < mid_bundle.offset {
+                right = mid - 1;
+            } else {
+                left = mid + 1;
+            }
+        }
+    }
+
+    Ok(None)
+}
+
+/// gets the StoredEntry for a given account
+pub fn stored_entry(
+    storage: &dyn Storage,
+    account: &CanonicalAddr,
+) -> StdResult<Option<StoredEntry>> {
+    let (node, _, _) = locate_btbe_node(storage, account)?;
+    let bucket = node.bucket(storage)?;
+    Ok(bucket.constant_time_find_address(account).map(|b| b.1))
+}
+
+/// returns the current stored balance for an entry
+pub fn stored_balance(storage: &dyn Storage, address: &CanonicalAddr) -> StdResult<u128> {
+    if let Some(entry) = stored_entry(storage, address)? {
+        Ok(entry.balance()? as u128)
+    } else {
+        Ok(0_u128)
+    }
+}
+
+/// Returns the total number of settled transactions for an account by peeking at last bundle
+pub fn stored_tx_count(storage: &dyn Storage, entry: &Option<StoredEntry>) -> StdResult<u32> {
+    if let Some(entry) = entry {
+        // peek at last entry
+        let len = entry.history_len()?;
+        if len > 0 {
+            let bundle = entry.get_tx_bundle_at(storage, len - 1)?;
+            return Ok(bundle.offset + bundle.list_len as u32);
+        }
+    }
+    Ok(0)
+}
+
+// merges a dwb entry into the current node's bucket
+// `amount_spent` is any required subtraction due to being sender of tx
+pub fn merge_dwb_entry(
+    storage: &mut dyn Storage,
+    dwb_entry: &DelayedWriteBufferEntry,
+    amount_spent: Option<u128>,
+    #[cfg(feature = "gas_tracking")] tracker: &mut GasTracker,
+) -> StdResult<()> {
+    #[cfg(feature = "gas_tracking")]
+    let mut group1 = tracker.group("#merge_dwb_entry.1");
+
+    // locate the node that the given entry belongs in
+    let (mut node, mut node_id, mut bit_pos) = locate_btbe_node(storage, &dwb_entry.recipient()?)?;
+
+    // load that node's current bucket
+    let mut bucket = node.bucket(storage)?;
+
+    // bucket ID for logging purposes
+    let mut bucket_id = node.bucket;
+
+    // search for an existing entry
+    if let Some((idx, mut found_entry)) = bucket.constant_time_find_address(&dwb_entry.recipient()?)
+    {
+        // found existing entry
+        // merge amount and history from dwb entry
+        found_entry.merge_dwb_entry(storage, &dwb_entry, amount_spent)?;
+        bucket.entries[idx] = found_entry;
+
+        #[cfg(feature = "gas_tracking")]
+        group1.logf(format!(
+            "merged {} into node #{}, bucket #{} at position {} ",
+            dwb_entry.recipient()?,
+            node_id,
+            bucket_id,
+            idx
+        ));
+
+        // save updated bucket to storage
+        node.set_and_save_bucket(storage, bucket)?;
+    } else {
+        // need to insert new entry
+        // create new stored balance entry
+        let btbe_entry = StoredEntry::from(storage, &dwb_entry, amount_spent)?;
+
+        // load contract's internal secret
+        let secret = INTERNAL_SECRET.load(storage)?;
+        let secret = secret.as_slice();
+
+        loop {
+            // looping as many times as needed until the bucket has capacity for a new entry
+            // try to add to the current bucket
+            if bucket.add_entry(&btbe_entry) {
+                #[cfg(feature = "gas_tracking")]
+                group1.logf(format!(
+                    "inserted into node #{}, bucket #{} (bitpos: {}) at position {}",
+                    node_id,
+                    bucket_id,
+                    bit_pos,
+                    BTBE_CAPACITY - bucket.capacity - 1
+                ));
+
+                // bucket has capacity and it added the new entry
+                // save bucket to storage
+                node.set_and_save_bucket(storage, bucket)?;
+                // break out of the loop
+                break;
+            } else {
+                // bucket is full; split on next bit position
+                // create new left and right buckets
+                let mut left_bucket = BtbeBucket::new()?;
+                let mut right_bucket = BtbeBucket::new()?;
+
+                // each entry
+                for entry in bucket.entries {
+                    // left_bucket.add_entry(&entry);
+                    // route entry
+                    if entry_belongs_in_left_node(secret, entry, bit_pos)? {
+                        left_bucket.add_entry(&entry);
+                    } else {
+                        right_bucket.add_entry(&entry);
+                    }
+                }
+
+                // save left node's bucket to storage, recycling this node's bucket ID
+                let left_bucket_id = node.bucket;
+                BTBE_BUCKETS
+                    .add_suffix(&left_bucket_id.to_be_bytes())
+                    .save(storage, &left_bucket)?;
+
+                // global count of buckets
+                let mut buckets_count = BTBE_BUCKETS_COUNT.load(storage).unwrap_or_default();
+
+                // bucket ID for right node
+                buckets_count += 1;
+                let right_bucket_id = buckets_count;
+                BTBE_BUCKETS
+                    .add_suffix(&right_bucket_id.to_be_bytes())
+                    .save(storage, &right_bucket)?;
+
+                // save updated count
+                BTBE_BUCKETS_COUNT.save(storage, &buckets_count)?;
+
+                // global count of trie nodes
+                let mut nodes_count = BTBE_TRIE_NODES_COUNT.load(storage).unwrap_or_default();
+
+                // ID for left node
+                nodes_count += 1;
+                let left_id = nodes_count;
+
+                // ID for right node
+                nodes_count += 1;
+                let right_id = nodes_count;
+
+                // save updated count
+                BTBE_TRIE_NODES_COUNT.save(storage, &nodes_count)?;
+
+                // create left and right nodes
+                let left = BitwiseTrieNode {
+                    left: 0,
+                    right: 0,
+                    bucket: left_bucket_id,
+                };
+                let right = BitwiseTrieNode {
+                    left: 0,
+                    right: 0,
+                    bucket: right_bucket_id,
+                };
+
+                // save left and right node to storage
+                BTBE_TRIE_NODES
+                    .add_suffix(&left_id.to_be_bytes())
+                    .save(storage, &left)?;
+                BTBE_TRIE_NODES
+                    .add_suffix(&right_id.to_be_bytes())
+                    .save(storage, &right)?;
+
+                // convert this into a branch node
+                node.left = left_id;
+                node.right = right_id;
+                node.bucket = 0;
+
+                // save node
+                BTBE_TRIE_NODES
+                    .add_suffix(&node_id.to_be_bytes())
+                    .save(storage, &node)?;
+
+                #[cfg(feature = "gas_tracking")]
+                group1.logf(format!(
+                    "split node #{}, bucket #{} at bitpos {}, ",
+                    node_id, bucket_id, bit_pos
+                ));
+
+                // route entry
+                if entry_belongs_in_left_node(secret, btbe_entry, bit_pos)? {
+                    node = left;
+                    node_id = left_id;
+                    bucket = left_bucket;
+                    bucket_id = left_bucket_id;
+                } else {
+                    node = right;
+                    node_id = right_id;
+                    bucket = right_bucket;
+                    bucket_id = right_bucket_id;
+                }
+
+                // increment bit position for next iteration of the loop
+                bit_pos += 1;
+            }
+        }
+    }
+
+    Ok(())
+}
+
+/// initializes the btbe
+pub fn initialize_btbe(storage: &mut dyn Storage) -> StdResult<()> {
+    let bucket = BtbeBucket::new()?;
+    let node = BitwiseTrieNode::new_leaf(storage, bucket)?;
+
+    // save count
+    BTBE_TRIE_NODES_COUNT.save(storage, &1)?;
+
+    // save root node to storage
+    BTBE_TRIE_NODES
+        .add_suffix(&1_u64.to_be_bytes())
+        .save(storage, &node)?;
+
+    Ok(())
+}
+
+#[cfg(test)]
+mod tests {
+    use std::any::Any;
+
+    use crate::contract::instantiate;
+    use crate::msg::{InitialBalance, InstantiateMsg, QueryAnswer};
+    use cosmwasm_std::{
+        from_binary, testing::*, Addr, Api, Binary, OwnedDeps, QueryResponse, Response, Uint128,
+    };
+
+    use super::*;
+
+    fn init_helper(
+        initial_balances: Vec<InitialBalance>,
+    ) -> (
+        StdResult<Response>,
+        OwnedDeps<MockStorage, MockApi, MockQuerier>,
+    ) {
+        let mut deps = mock_dependencies_with_balance(&[]);
+        let env = mock_env();
+        let info = mock_info("instantiator", &[]);
+
+        let init_msg = InstantiateMsg {
+            name: "sec-sec".to_string(),
+            admin: Some("admin".to_string()),
+            symbol: "SECSEC".to_string(),
+            decimals: 8,
+            initial_balances: Some(initial_balances),
+            prng_seed: Binary::from("lolz fun yay".as_bytes()),
+            config: None,
+            supported_denoms: None,
+        };
+
+        (instantiate(deps.as_mut(), env, info, init_msg), deps)
+    }
+
+    fn extract_error_msg<T: Any>(error: StdResult<T>) -> String {
+        match error {
+            Ok(response) => {
+                let bin_err = (&response as &dyn Any)
+                    .downcast_ref::<QueryResponse>()
+                    .expect("An error was expected, but no error could be extracted");
+                match from_binary(bin_err).unwrap() {
+                    QueryAnswer::ViewingKeyError { msg } => msg,
+                    _ => panic!("Unexpected query answer"),
+                }
+            }
+            Err(err) => match err {
+                StdError::GenericErr { msg, .. } => msg,
+                _ => panic!("Unexpected result from init"),
+            },
+        }
+    }
+
+    #[test]
+    fn test_stored_entry() {
+        let (init_result, mut deps) = init_helper(vec![InitialBalance {
+            address: "bob".to_string(),
+            amount: Uint128::new(5000),
+        }]);
+        assert!(
+            init_result.is_ok(),
+            "Init failed: {}",
+            init_result.err().unwrap()
+        );
+        let _env = mock_env();
+        let _info = mock_info("bob", &[]);
+
+        let canonical = deps
+            .api
+            .addr_canonicalize(Addr::unchecked("bob".to_string()).as_str())
+            .unwrap();
+        let entry = StoredEntry::new(&canonical).unwrap();
+        assert_eq!(entry.address().unwrap(), canonical);
+        assert_eq!(entry.balance().unwrap(), 0_u64);
+
+        let dwb_entry = DelayedWriteBufferEntry::new(&canonical).unwrap();
+
+        // expect error if trying to spend too much
+        let entry = StoredEntry::from(&mut deps.storage, &dwb_entry, Some(1));
+        let error = extract_error_msg(entry);
+        assert!(error.contains("insufficient funds"));
+
+        let entry = StoredEntry::from(&mut deps.storage, &dwb_entry, None).unwrap();
+        assert_eq!(entry.address().unwrap(), canonical);
+        assert_eq!(entry.balance().unwrap(), 0_u64);
+    }
+
+    #[test]
+    fn test_btbe() {
+        let (init_result, mut deps) = init_helper(vec![InitialBalance {
+            address: "bob".to_string(),
+            amount: Uint128::new(5000),
+        }]);
+        assert!(
+            init_result.is_ok(),
+            "Init failed: {}",
+            init_result.err().unwrap()
+        );
+        let _env = mock_env();
+        let _info = mock_info("bob", &[]);
+
+        let _ = initialize_btbe(&mut deps.storage).unwrap();
+
+        let btbe_node_count = BTBE_TRIE_NODES_COUNT.load(&deps.storage).unwrap();
+        assert_eq!(btbe_node_count, 1);
+
+        for i in 1..=128 {
+            let canonical = deps
+                .api
+                .addr_canonicalize(Addr::unchecked(format!("{i}zzzzzz")).as_str())
+                .unwrap();
+            let entry = StoredEntry::new(&canonical).unwrap();
+            assert_eq!(entry.address().unwrap(), canonical);
+            assert_eq!(entry.balance().unwrap(), 0_u64);
+
+            let dwb_entry = DelayedWriteBufferEntry::new(&canonical).unwrap();
+
+            let _result = merge_dwb_entry(&mut deps.storage, &dwb_entry, None);
+
+            let btbe_node_count = BTBE_TRIE_NODES_COUNT.load(&deps.storage).unwrap();
+            assert_eq!(btbe_node_count, 1);
+
+            let (node, node_id, bit_pos) = locate_btbe_node(&deps.storage, &canonical).unwrap();
+            assert_eq!(
+                node,
+                BitwiseTrieNode {
+                    left: 0,
+                    right: 0,
+                    bucket: 2,
+                }
+            );
+            assert_eq!(node_id, 1);
+            assert_eq!(bit_pos, 0);
+        }
+
+        // btbe trie should split nodes when get to 129th entry
+        let canonical = deps
+            .api
+            .addr_canonicalize(Addr::unchecked(format!("bob")).as_str())
+            .unwrap();
+        let entry = StoredEntry::new(&canonical).unwrap();
+        assert_eq!(entry.address().unwrap(), canonical);
+        assert_eq!(entry.balance().unwrap(), 0_u64);
+
+        let dwb_entry = DelayedWriteBufferEntry::new(&canonical).unwrap();
+
+        let _result = merge_dwb_entry(&mut deps.storage, &dwb_entry, None);
+
+        let btbe_node_count = BTBE_TRIE_NODES_COUNT.load(&deps.storage).unwrap();
+        assert_eq!(btbe_node_count, 3);
+        let (node, node_id, bit_pos) = locate_btbe_node(&deps.storage, &canonical).unwrap();
+        assert_eq!(
+            node,
+            BitwiseTrieNode {
+                left: 0,
+                right: 0,
+                bucket: 3,
+            }
+        );
+        assert_eq!(node_id, 3);
+        assert_eq!(bit_pos, 1);
+
+        // have other addresses been moved to new nodes
+        let first = deps
+            .api
+            .addr_canonicalize(Addr::unchecked(format!("1zzzzzz")).as_str())
+            .unwrap();
+        let (node, node_id, bit_pos) = locate_btbe_node(&deps.storage, &first).unwrap();
+        assert_eq!(
+            node,
+            BitwiseTrieNode {
+                left: 0,
+                right: 0,
+                bucket: 2,
+            }
+        );
+        assert_eq!(node_id, 2);
+        assert_eq!(bit_pos, 1);
+
+        let second = deps
+            .api
+            .addr_canonicalize(Addr::unchecked(format!("2zzzzzz")).as_str())
+            .unwrap();
+        let (node, node_id, bit_pos) = locate_btbe_node(&deps.storage, &second).unwrap();
+        assert_eq!(
+            node,
+            BitwiseTrieNode {
+                left: 0,
+                right: 0,
+                bucket: 3,
+            }
+        );
+        assert_eq!(node_id, 3);
+        assert_eq!(bit_pos, 1);
+
+        let canonical_entry = stored_entry(&deps.storage, &canonical).unwrap().unwrap();
+        assert_eq!(canonical_entry.balance().unwrap(), 0);
+        let first_entry = stored_entry(&deps.storage, &first).unwrap().unwrap();
+        assert_eq!(first_entry.balance().unwrap(), 0);
+        let second_entry = stored_entry(&deps.storage, &second).unwrap().unwrap();
+        assert_eq!(second_entry.balance().unwrap(), 0);
+        let not_entry = stored_entry(
+            &deps.storage,
+            &deps
+                .api
+                .addr_canonicalize(Addr::unchecked("alice".to_string()).as_str())
+                .unwrap(),
+        )
+        .unwrap();
+        assert_eq!(not_entry, None);
+    }
+}
diff --git a/src/contract.rs b/src/contract.rs
index f5f89789..29773f15 100644
--- a/src/contract.rs
+++ b/src/contract.rs
@@ -1,32 +1,48 @@
 /// This contract implements SNIP-20 standard:
 /// https://github.com/SecretFoundation/SNIPs/blob/master/SNIP-20.md
 use cosmwasm_std::{
-    entry_point, to_binary, Addr, BankMsg, Binary, Coin, CosmosMsg, Deps, DepsMut, Env,
-    MessageInfo, Response, StdError, StdResult, Storage, Uint128,
+    entry_point, to_binary, Addr, BankMsg, Binary, BlockInfo, CanonicalAddr, Coin, CosmosMsg,
+    Deps, DepsMut, Env, MessageInfo, Response, StdError, StdResult, Storage, Uint128, Uint64,
 };
-use rand::RngCore;
+#[cfg(feature = "gas_evaporation")]
+use cosmwasm_std::Api;
+use secret_toolkit::notification::{get_seed, notification_id, BloomParameters, ChannelInfoData, Descriptor, FlatDescriptor, Notification, NotificationData, StructDescriptor,};
 use secret_toolkit::permit::{Permit, RevokedPermits, TokenPermissions};
 use secret_toolkit::utils::{pad_handle_result, pad_query_result};
 use secret_toolkit::viewing_key::{ViewingKey, ViewingKeyStore};
-use secret_toolkit_crypto::{sha_256, Prng, SHA256_HASH_SIZE};
+use secret_toolkit_crypto::{hkdf_sha_256, sha_256, ContractPrng};
 
 use crate::batch;
+
+#[cfg(feature = "gas_tracking")]
+use crate::dwb::log_dwb;
+use crate::dwb::{DelayedWriteBuffer, DWB, TX_NODES};
+
+use crate::btbe::{
+    find_start_bundle, initialize_btbe, stored_balance, stored_entry, stored_tx_count,
+};
+#[cfg(feature = "gas_tracking")]
+use crate::gas_tracker::{GasTracker, LoggingExt};
+#[cfg(feature = "gas_evaporation")]
+use crate::msg::Evaporator;
 use crate::msg::{
-    AllowanceGivenResult, AllowanceReceivedResult, ContractStatusLevel, Decoyable, ExecuteAnswer,
-    ExecuteMsg, InstantiateMsg, QueryAnswer, QueryMsg, QueryWithPermit, ResponseStatus::Success,
+    AllowanceGivenResult, AllowanceReceivedResult, ContractStatusLevel, ExecuteAnswer, ExecuteMsg,
+    InstantiateMsg, QueryAnswer, QueryMsg, QueryWithPermit, ResponseStatus::Success,
 };
+use crate::notifications::{multi_received_data, multi_spent_data, AllowanceNotificationData, ReceivedNotificationData, SpentNotificationData, MULTI_RECEIVED_CHANNEL_BLOOM_K, MULTI_RECEIVED_CHANNEL_BLOOM_N, MULTI_RECEIVED_CHANNEL_ID, MULTI_RECEIVED_CHANNEL_PACKET_SIZE, MULTI_SPENT_CHANNEL_BLOOM_K, MULTI_SPENT_CHANNEL_BLOOM_N, MULTI_SPENT_CHANNEL_ID, MULTI_SPENT_CHANNEL_PACKET_SIZE};
 use crate::receiver::Snip20ReceiveMsg;
 use crate::state::{
-    safe_add, AllowancesStore, BalancesStore, Config, MintersStore, PrngStore, ReceiverHashStore,
-    CONFIG, CONTRACT_STATUS, TOTAL_SUPPLY,
+    safe_add, AllowancesStore, Config, MintersStore, ReceiverHashStore, CHANNELS, CONFIG, CONTRACT_STATUS, INTERNAL_SECRET, TOTAL_SUPPLY
 };
+use crate::strings::TRANSFER_HISTORY_UNSUPPORTED_MSG;
 use crate::transaction_history::{
-    store_burn, store_deposit, store_mint, store_redeem, store_transfer, StoredExtendedTx,
-    StoredLegacyTransfer,
+    store_burn_action, store_deposit_action, store_mint_action, store_redeem_action,
+    store_transfer_action, Tx,
 };
 
 /// We make sure that responses from `handle` are padded to a multiple of this size.
 pub const RESPONSE_BLOCK_SIZE: usize = 256;
+pub const NOTIFICATION_BLOCK_SIZE: usize = 36;
 pub const PREFIX_REVOKED_PERMITS: &str = "revoked_permits";
 
 #[entry_point]
@@ -55,49 +71,78 @@ pub fn instantiate(
 
     let admin = match msg.admin {
         Some(admin_addr) => deps.api.addr_validate(admin_addr.as_str())?,
-        None => info.sender,
+        None => info.sender.clone(),
     };
 
     let mut total_supply: u128 = 0;
 
-    let prng_seed_hashed = sha_256(&msg.prng_seed.0);
-    PrngStore::save(deps.storage, prng_seed_hashed)?;
-
-    {
-        let initial_balances = msg.initial_balances.unwrap_or_default();
-        for balance in initial_balances {
-            let amount = balance.amount.u128();
-            let balance_address = deps.api.addr_validate(balance.address.as_str())?;
-            // Here amount is also the amount to be added because the account has no prior balance
-            BalancesStore::update_balance(
-                deps.storage,
-                &balance_address,
-                amount,
-                true,
-                "",
-                &None,
-                &None,
-            )?;
+    // initialize the bitwise-trie of bucketed entries
+    initialize_btbe(deps.storage)?;
+
+    // initialize the delay write buffer
+    DWB.save(deps.storage, &DelayedWriteBuffer::new()?)?;
+
+    let initial_balances = msg.initial_balances.unwrap_or_default();
+    let raw_admin = deps.api.addr_canonicalize(admin.as_str())?;
+    let rng_seed = env.block.random.as_ref().unwrap();
+
+    // use entropy and env.random to create an internal secret for the contract
+    let entropy = msg.prng_seed.0.as_slice();
+    let entropy_len = 16 + info.sender.to_string().len() + entropy.len();
+    let mut rng_entropy = Vec::with_capacity(entropy_len);
+    rng_entropy.extend_from_slice(&env.block.height.to_be_bytes());
+    rng_entropy.extend_from_slice(&env.block.time.seconds().to_be_bytes());
+    rng_entropy.extend_from_slice(info.sender.as_bytes());
+    rng_entropy.extend_from_slice(entropy);
+
+    // Create INTERNAL_SECRET
+    let salt = Some(sha_256(&rng_entropy).to_vec());
+    let internal_secret = hkdf_sha_256(
+        &salt,
+        rng_seed.0.as_slice(),
+        "contract_internal_secret".as_bytes(),
+        32,
+    )?;
+    INTERNAL_SECRET.save(deps.storage, &internal_secret)?;
+
+    // Hard-coded channels
+    let channels: Vec<String> = vec![
+        ReceivedNotificationData::CHANNEL_ID.to_string(),
+        SpentNotificationData::CHANNEL_ID.to_string(),
+        AllowanceNotificationData::CHANNEL_ID.to_string(),
+        MULTI_RECEIVED_CHANNEL_ID.to_string(),
+        MULTI_SPENT_CHANNEL_ID.to_string(),
+    ];
+
+    for channel in channels {
+        CHANNELS.insert(deps.storage, &channel)?;
+    }
 
-            if let Some(new_total_supply) = total_supply.checked_add(amount) {
-                total_supply = new_total_supply;
-            } else {
-                return Err(StdError::generic_err(
-                    "The sum of all initial balances exceeds the maximum possible total supply",
-                ));
-            }
+    let mut rng = ContractPrng::new(rng_seed.as_slice(), &sha_256(&msg.prng_seed.0));
+    for balance in initial_balances {
+        let amount = balance.amount.u128();
+        let balance_address = deps.api.addr_canonicalize(balance.address.as_str())?;
+        #[cfg(feature = "gas_tracking")]
+        let mut tracker = GasTracker::new(deps.api);
+        perform_mint(
+            deps.storage,
+            &mut rng,
+            &raw_admin,
+            &balance_address,
+            amount,
+            msg.symbol.clone(),
+            Some("Initial Balance".to_string()),
+            &env.block,
+            #[cfg(feature = "gas_tracking")]
+            &mut tracker,
+        )?;
 
-            store_mint(
-                deps.storage,
-                admin.clone(),
-                balance_address,
-                balance.amount,
-                msg.symbol.clone(),
-                Some("Initial Balance".to_string()),
-                &env.block,
-                &None,
-                &None,
-            )?;
+        if let Some(new_total_supply) = total_supply.checked_add(amount) {
+            total_supply = new_total_supply;
+        } else {
+            return Err(StdError::generic_err(
+                "The sum of all initial balances exceeds the maximum possible total supply",
+            ));
         }
     }
 
@@ -132,57 +177,35 @@ pub fn instantiate(
     };
     MintersStore::save(deps.storage, minters)?;
 
-    ViewingKey::set_seed(deps.storage, &prng_seed_hashed);
+    let vk_seed = hkdf_sha_256(
+        &salt,
+        rng_seed.0.as_slice(),
+        "contract_viewing_key".as_bytes(),
+        32,
+    )?;
+    ViewingKey::set_seed(deps.storage, &vk_seed);
 
     Ok(Response::default())
 }
 
-fn get_address_position(
-    store: &mut dyn Storage,
-    decoys_size: usize,
-    entropy: &[u8; SHA256_HASH_SIZE],
-) -> StdResult<usize> {
-    let mut rng = Prng::new(&PrngStore::load(store)?, entropy);
-
-    let mut new_contract_entropy = [0u8; 20];
-    rng.rng.fill_bytes(&mut new_contract_entropy);
-
-    let new_prng_seed = sha_256(&new_contract_entropy);
-    PrngStore::save(store, new_prng_seed)?;
-
-    // decoys_size is also an accepted output which means: set the account balance after you've set decoys' balanace
-    Ok(rng.rng.next_u64() as usize % (decoys_size + 1))
-}
-
 #[entry_point]
 pub fn execute(deps: DepsMut, env: Env, info: MessageInfo, msg: ExecuteMsg) -> StdResult<Response> {
-    let contract_status = CONTRACT_STATUS.load(deps.storage)?;
-
-    let mut account_random_pos: Option<usize> = None;
-
-    let entropy = match msg.clone().get_entropy() {
-        None => [0u8; SHA256_HASH_SIZE],
-        Some(e) => sha_256(&e.0),
-    };
+    let mut rng = ContractPrng::from_env(&env);
 
-    let decoys_size = msg.get_minimal_decoys_size();
-    if decoys_size != 0 {
-        account_random_pos = Some(get_address_position(deps.storage, decoys_size, &entropy)?);
-    }
+    let contract_status = CONTRACT_STATUS.load(deps.storage)?;
 
+    #[cfg(feature = "gas_evaporation")]
+    let api = deps.api;
     match contract_status {
         ContractStatusLevel::StopAll | ContractStatusLevel::StopAllButRedeems => {
             let response = match msg {
                 ExecuteMsg::SetContractStatus { level, .. } => {
                     set_contract_status(deps, info, level)
                 }
-                ExecuteMsg::Redeem {
-                    amount,
-                    denom,
-                    decoys,
-                    ..
-                } if contract_status == ContractStatusLevel::StopAllButRedeems => {
-                    try_redeem(deps, env, info, amount, denom, decoys, account_random_pos)
+                ExecuteMsg::Redeem { amount, denom, .. }
+                    if contract_status == ContractStatusLevel::StopAllButRedeems =>
+                {
+                    try_redeem(deps, env, info, amount, denom)
                 }
                 _ => Err(StdError::generic_err(
                     "This contract is stopped and this action is not allowed",
@@ -195,69 +218,43 @@ pub fn execute(deps: DepsMut, env: Env, info: MessageInfo, msg: ExecuteMsg) -> S
 
     let response = match msg.clone() {
         // Native
-        ExecuteMsg::Deposit { decoys, .. } => {
-            try_deposit(deps, env, info, decoys, account_random_pos)
-        }
-        ExecuteMsg::Redeem {
-            amount,
-            denom,
-            decoys,
-            ..
-        } => try_redeem(deps, env, info, amount, denom, decoys, account_random_pos),
+        ExecuteMsg::Deposit { .. } => try_deposit(deps, env, info, &mut rng),
+        ExecuteMsg::Redeem { amount, denom, .. } => try_redeem(deps, env, info, amount, denom),
 
         // Base
         ExecuteMsg::Transfer {
             recipient,
             amount,
             memo,
-            decoys,
             ..
-        } => try_transfer(
-            deps,
-            env,
-            info,
-            recipient,
-            amount,
-            memo,
-            decoys,
-            account_random_pos,
-        ),
+        } => try_transfer(deps, env, info, &mut rng, recipient, amount, memo),
         ExecuteMsg::Send {
             recipient,
             recipient_code_hash,
             amount,
             msg,
             memo,
-            decoys,
             ..
         } => try_send(
             deps,
             env,
             info,
+            &mut rng,
             recipient,
             recipient_code_hash,
             amount,
             memo,
             msg,
-            decoys,
-            account_random_pos,
         ),
         ExecuteMsg::BatchTransfer { actions, .. } => {
-            try_batch_transfer(deps, env, info, actions, account_random_pos)
-        }
-        ExecuteMsg::BatchSend { actions, .. } => {
-            try_batch_send(deps, env, info, actions, account_random_pos)
+            try_batch_transfer(deps, env, info, &mut rng, actions)
         }
-        ExecuteMsg::Burn {
-            amount,
-            memo,
-            decoys,
-            ..
-        } => try_burn(deps, env, info, amount, memo, decoys, account_random_pos),
+        ExecuteMsg::BatchSend { actions, .. } => try_batch_send(deps, env, info, &mut rng, actions),
+        ExecuteMsg::Burn { amount, memo, .. } => try_burn(deps, env, info, amount, memo),
         ExecuteMsg::RegisterReceive { code_hash, .. } => {
             try_register_receive(deps, info, code_hash)
         }
-        ExecuteMsg::CreateViewingKey { entropy, .. } => try_create_key(deps, env, info, entropy),
+        ExecuteMsg::CreateViewingKey { entropy, .. } => try_create_key(deps, env, info, entropy, &mut rng),
         ExecuteMsg::SetViewingKey { key, .. } => try_set_key(deps, info, key),
 
         // Allowance
@@ -278,19 +275,8 @@ pub fn execute(deps: DepsMut, env: Env, info: MessageInfo, msg: ExecuteMsg) -> S
             recipient,
             amount,
             memo,
-            decoys,
             ..
-        } => try_transfer_from(
-            deps,
-            &env,
-            info,
-            owner,
-            recipient,
-            amount,
-            memo,
-            decoys,
-            account_random_pos,
-        ),
+        } => try_transfer_from(deps, &env, info, &mut rng, owner, recipient, amount, memo),
         ExecuteMsg::SendFrom {
             owner,
             recipient,
@@ -298,67 +284,41 @@ pub fn execute(deps: DepsMut, env: Env, info: MessageInfo, msg: ExecuteMsg) -> S
             amount,
             msg,
             memo,
-            decoys,
             ..
         } => try_send_from(
             deps,
             env,
             &info,
+            &mut rng,
             owner,
             recipient,
             recipient_code_hash,
             amount,
             memo,
             msg,
-            decoys,
-            account_random_pos,
         ),
         ExecuteMsg::BatchTransferFrom { actions, .. } => {
-            try_batch_transfer_from(deps, &env, info, actions, account_random_pos)
+            try_batch_transfer_from(deps, &env, info, &mut rng, actions)
         }
         ExecuteMsg::BatchSendFrom { actions, .. } => {
-            try_batch_send_from(deps, env, &info, actions, account_random_pos)
+            try_batch_send_from(deps, env, &info, &mut rng, actions)
         }
         ExecuteMsg::BurnFrom {
             owner,
             amount,
             memo,
-            decoys,
             ..
-        } => try_burn_from(
-            deps,
-            &env,
-            info,
-            owner,
-            amount,
-            memo,
-            decoys,
-            account_random_pos,
-        ),
-        ExecuteMsg::BatchBurnFrom { actions, .. } => {
-            try_batch_burn_from(deps, &env, info, actions, account_random_pos)
-        }
+        } => try_burn_from(deps, &env, info, owner, amount, memo),
+        ExecuteMsg::BatchBurnFrom { actions, .. } => try_batch_burn_from(deps, &env, info, actions),
 
         // Mint
         ExecuteMsg::Mint {
             recipient,
             amount,
             memo,
-            decoys,
             ..
-        } => try_mint(
-            deps,
-            env,
-            info,
-            recipient,
-            amount,
-            memo,
-            decoys,
-            account_random_pos,
-        ),
-        ExecuteMsg::BatchMint { actions, .. } => {
-            try_batch_mint(deps, env, info, actions, account_random_pos)
-        }
+        } => try_mint(deps, env, info, &mut rng, recipient, amount, memo),
+        ExecuteMsg::BatchMint { actions, .. } => try_batch_mint(deps, env, info, &mut rng, actions),
 
         // Other
         ExecuteMsg::ChangeAdmin { address, .. } => change_admin(deps, info, address),
@@ -373,11 +333,16 @@ pub fn execute(deps: DepsMut, env: Env, info: MessageInfo, msg: ExecuteMsg) -> S
         }
     };
 
-    pad_handle_result(response, RESPONSE_BLOCK_SIZE)
+    let padded_result = pad_handle_result(response, RESPONSE_BLOCK_SIZE);
+
+    #[cfg(feature = "gas_evaporation")]
+    let evaporated = msg.evaporate_to_target(api)?;
+
+    padded_result
 }
 
 #[entry_point]
-pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult<Binary> {
+pub fn query(deps: Deps, env: Env, msg: QueryMsg) -> StdResult<Binary> {
     pad_query_result(
         match msg {
             QueryMsg::TokenInfo {} => query_token_info(deps.storage),
@@ -385,14 +350,19 @@ pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult<Binary> {
             QueryMsg::ContractStatus {} => query_contract_status(deps.storage),
             QueryMsg::ExchangeRate {} => query_exchange_rate(deps.storage),
             QueryMsg::Minters { .. } => query_minters(deps),
-            QueryMsg::WithPermit { permit, query } => permit_queries(deps, permit, query),
-            _ => viewing_keys_queries(deps, msg),
+            QueryMsg::ListChannels {} => query_list_channels(deps),
+            QueryMsg::WithPermit { permit, query } => permit_queries(deps, env, permit, query),
+
+            #[cfg(feature = "gas_tracking")]
+            QueryMsg::Dwb {} => log_dwb(deps.storage),
+
+            _ => viewing_keys_queries(deps, env, msg),
         },
         RESPONSE_BLOCK_SIZE,
     )
 }
 
-fn permit_queries(deps: Deps, permit: Permit, query: QueryWithPermit) -> Result<Binary, StdError> {
+fn permit_queries(deps: Deps, env: Env, permit: Permit, query: QueryWithPermit) -> Result<Binary, StdError> {
     // Validate permit content
     let token_address = CONFIG.load(deps.storage)?.contract_address;
 
@@ -416,31 +386,10 @@ fn permit_queries(deps: Deps, permit: Permit, query: QueryWithPermit) -> Result<
 
             query_balance(deps, account)
         }
-        QueryWithPermit::TransferHistory {
-            page,
-            page_size,
-            should_filter_decoys,
-        } => {
-            if !permit.check_permission(&TokenPermissions::History) {
-                return Err(StdError::generic_err(format!(
-                    "No permission to query history, got permissions {:?}",
-                    permit.params.permissions
-                )));
-            }
-
-            query_transfers(
-                deps,
-                account,
-                page.unwrap_or(0),
-                page_size,
-                should_filter_decoys,
-            )
+        QueryWithPermit::TransferHistory { .. } => {
+            return Err(StdError::generic_err(TRANSFER_HISTORY_UNSUPPORTED_MSG));
         }
-        QueryWithPermit::TransactionHistory {
-            page,
-            page_size,
-            should_filter_decoys,
-        } => {
+        QueryWithPermit::TransactionHistory { page, page_size } => {
             if !permit.check_permission(&TokenPermissions::History) {
                 return Err(StdError::generic_err(format!(
                     "No permission to query history, got permissions {:?}",
@@ -448,13 +397,7 @@ fn permit_queries(deps: Deps, permit: Permit, query: QueryWithPermit) -> Result<
                 )));
             }
 
-            query_transactions(
-                deps,
-                account,
-                page.unwrap_or(0),
-                page_size,
-                should_filter_decoys,
-            )
+            query_transactions(deps, account, page.unwrap_or(0), page_size)
         }
         QueryWithPermit::Allowance { owner, spender } => {
             if !permit.check_permission(&TokenPermissions::Allowance) {
@@ -517,10 +460,17 @@ fn permit_queries(deps: Deps, permit: Permit, query: QueryWithPermit) -> Result<
             }
             query_allowances_received(deps, account, page.unwrap_or(0), page_size)
         }
+        QueryWithPermit::ChannelInfo { channels, txhash } => query_channel_info(
+            deps,
+            env,
+            channels,
+            txhash,
+            deps.api.addr_canonicalize(account.as_str())?,
+        )
     }
 }
 
-pub fn viewing_keys_queries(deps: Deps, msg: QueryMsg) -> StdResult<Binary> {
+pub fn viewing_keys_queries(deps: Deps, env: Env,  msg: QueryMsg) -> StdResult<Binary> {
     let (addresses, key) = msg.get_validation_params(deps.api)?;
 
     for address in addresses {
@@ -529,32 +479,15 @@ pub fn viewing_keys_queries(deps: Deps, msg: QueryMsg) -> StdResult<Binary> {
             return match msg {
                 // Base
                 QueryMsg::Balance { address, .. } => query_balance(deps, address),
-                QueryMsg::TransferHistory {
-                    address,
-                    page,
-                    page_size,
-                    should_filter_decoys,
-                    ..
-                } => query_transfers(
-                    deps,
-                    address,
-                    page.unwrap_or(0),
-                    page_size,
-                    should_filter_decoys,
-                ),
+                QueryMsg::TransferHistory { .. } => {
+                    return Err(StdError::generic_err(TRANSFER_HISTORY_UNSUPPORTED_MSG));
+                }
                 QueryMsg::TransactionHistory {
                     address,
                     page,
                     page_size,
-                    should_filter_decoys,
                     ..
-                } => query_transactions(
-                    deps,
-                    address,
-                    page.unwrap_or(0),
-                    page_size,
-                    should_filter_decoys,
-                ),
+                } => query_transactions(deps, address, page.unwrap_or(0), page_size),
                 QueryMsg::Allowance { owner, spender, .. } => query_allowance(deps, owner, spender),
                 QueryMsg::AllowancesGiven {
                     owner,
@@ -568,6 +501,17 @@ pub fn viewing_keys_queries(deps: Deps, msg: QueryMsg) -> StdResult<Binary> {
                     page_size,
                     ..
                 } => query_allowances_received(deps, spender, page.unwrap_or(0), page_size),
+                QueryMsg::ChannelInfo {
+                    channels,
+                    txhash,
+                    viewer,
+                } => query_channel_info(
+                    deps,
+                    env,
+                    channels,
+                    txhash,
+                    deps.api.addr_canonicalize(viewer.address.as_str())?,
+                ),
                 _ => panic!("This query type does not require authentication"),
             };
         }
@@ -578,6 +522,8 @@ pub fn viewing_keys_queries(deps: Deps, msg: QueryMsg) -> StdResult<Binary> {
     })
 }
 
+// query functions
+
 fn query_exchange_rate(storage: &dyn Storage) -> StdResult<Binary> {
     let constants = CONFIG.load(storage)?;
 
@@ -639,65 +585,173 @@ fn query_contract_status(storage: &dyn Storage) -> StdResult<Binary> {
     })
 }
 
-pub fn query_transfers(
-    deps: Deps,
-    account: String,
-    page: u32,
-    page_size: u32,
-    should_filter_decoys: bool,
-) -> StdResult<Binary> {
-    // Notice that if query_transfers() was called by a viewking-key call, the address of 'account'
-    // has already been validated.
-    // The address of 'account' should not be validated if query_transfers() was called by a permit
-    // call, for compatibility with non-Secret addresses.
-    let account = Addr::unchecked(account);
-
-    let (txs, total) = StoredLegacyTransfer::get_transfers(
-        deps.storage,
-        account,
-        page,
-        page_size,
-        should_filter_decoys,
-    )?;
-
-    let result = QueryAnswer::TransferHistory {
-        txs,
-        total: Some(total),
-    };
-    to_binary(&result)
-}
-
 pub fn query_transactions(
     deps: Deps,
     account: String,
     page: u32,
     page_size: u32,
-    should_filter_decoys: bool,
 ) -> StdResult<Binary> {
-    // Notice that if query_transactions() was called by a viewking-key call, the address of
+    if page_size == 0 {
+        return Err(StdError::generic_err("invalid page size"));
+    }
+
+    // Notice that if query_transactions() was called by a viewing-key call, the address of
     // 'account' has already been validated.
     // The address of 'account' should not be validated if query_transactions() was called by a
     // permit call, for compatibility with non-Secret addresses.
     let account = Addr::unchecked(account);
+    let account_raw = deps.api.addr_canonicalize(account.as_str())?;
+
+    let start = page * page_size;
+    let mut end = start + page_size; // one more than end index
+
+    // first check if there are any transactions in dwb
+    let dwb = DWB.load(deps.storage)?;
+    let dwb_index = dwb.recipient_match(&account_raw);
+    let mut txs_in_dwb = vec![];
+    let txs_in_dwb_count = dwb.entries[dwb_index].list_len()?;
+    if dwb_index > 0 && txs_in_dwb_count > 0 && start < txs_in_dwb_count as u32 {
+        // skip if start is after buffer entries
+        let head_node_index = dwb.entries[dwb_index].head_node()?;
+        if head_node_index > 0 {
+            let head_node = TX_NODES
+                .add_suffix(&head_node_index.to_be_bytes())
+                .load(deps.storage)?;
+            txs_in_dwb = head_node.to_vec(deps.storage, deps.api)?;
+        }
+    }
+
+    //let account_slice = account_raw.as_slice();
+    let account_stored_entry = stored_entry(deps.storage, &account_raw)?;
+    let settled_tx_count = stored_tx_count(deps.storage, &account_stored_entry)?;
+    let total = txs_in_dwb_count as u32 + settled_tx_count as u32;
+    if end > total {
+        end = total;
+    }
 
-    let (txs, total) =
-        StoredExtendedTx::get_txs(deps.storage, account, page, page_size, should_filter_decoys)?;
+    let mut txs: Vec<Tx> = vec![];
+
+    let txs_in_dwb_count = txs_in_dwb_count as u32;
+    if start < txs_in_dwb_count && end < txs_in_dwb_count {
+        // option 1, start and end are both in dwb
+        //println!("OPTION 1");
+        txs = txs_in_dwb[start as usize..end as usize].to_vec(); // reverse chronological
+    } else if start < txs_in_dwb_count && end >= txs_in_dwb_count {
+        // option 2, start is in dwb and end is in settled txs
+        // in this case, we do not need to search for txs, just begin at last bundle and move backwards
+        //println!("OPTION 2");
+        txs = txs_in_dwb[start as usize..].to_vec(); // reverse chronological
+        let mut txs_left = (end - start).saturating_sub(txs.len() as u32);
+        if let Some(entry) = account_stored_entry {
+            let tx_bundles_idx_len = entry.history_len()?;
+            if tx_bundles_idx_len > 0 {
+                let mut bundle_idx = tx_bundles_idx_len - 1;
+                loop {
+                    let tx_bundle = entry.get_tx_bundle_at(deps.storage, bundle_idx.clone())?;
+                    let head_node = TX_NODES
+                        .add_suffix(&tx_bundle.head_node.to_be_bytes())
+                        .load(deps.storage)?;
+                    let list_len = tx_bundle.list_len as u32;
+                    if txs_left <= list_len {
+                        txs.extend_from_slice(
+                            &head_node.to_vec(deps.storage, deps.api)?[0..txs_left as usize],
+                        );
+                        break;
+                    }
+                    txs.extend(head_node.to_vec(deps.storage, deps.api)?);
+                    txs_left = txs_left.saturating_sub(list_len);
+                    if bundle_idx > 0 {
+                        bundle_idx -= 1;
+                    } else {
+                        break;
+                    }
+                }
+            }
+        }
+    } else if start >= txs_in_dwb_count {
+        // option 3, start is not in dwb
+        // in this case, search for where the beginning bundle is using binary search
+
+        // bundle tx offsets are chronological, but we need reverse chronological
+        // so get the settled start index as if order is reversed
+        //println!("OPTION 3");
+        let settled_start = settled_tx_count
+            .saturating_sub(start - txs_in_dwb_count)
+            .saturating_sub(1);
+
+        if let Some((bundle_idx, tx_bundle, start_at)) =
+            find_start_bundle(deps.storage, &account_raw, settled_start)?
+        {
+            let mut txs_left = end - start;
+
+            let head_node = TX_NODES
+                .add_suffix(&tx_bundle.head_node.to_be_bytes())
+                .load(deps.storage)?;
+            let list_len = tx_bundle.list_len as u32;
+            if start_at + txs_left <= list_len {
+                // this first bundle has all the txs we need
+                txs = head_node.to_vec(deps.storage, deps.api)?
+                    [start_at as usize..(start_at + txs_left) as usize]
+                    .to_vec();
+            } else {
+                // get the rest of the txs in this bundle and then go back through history
+                txs = head_node.to_vec(deps.storage, deps.api)?[start_at as usize..].to_vec();
+                txs_left = txs_left.saturating_sub(list_len - start_at);
+
+                if bundle_idx > 0 && txs_left > 0 {
+                    // get the next earlier bundle
+                    let mut bundle_idx = bundle_idx - 1;
+                    if let Some(entry) = account_stored_entry {
+                        loop {
+                            let tx_bundle =
+                                entry.get_tx_bundle_at(deps.storage, bundle_idx.clone())?;
+                            let head_node = TX_NODES
+                                .add_suffix(&tx_bundle.head_node.to_be_bytes())
+                                .load(deps.storage)?;
+                            let list_len = tx_bundle.list_len as u32;
+                            if txs_left <= list_len {
+                                txs.extend_from_slice(
+                                    &head_node.to_vec(deps.storage, deps.api)?
+                                        [0..txs_left as usize],
+                                );
+                                break;
+                            }
+                            txs.extend(head_node.to_vec(deps.storage, deps.api)?);
+                            txs_left = txs_left.saturating_sub(list_len);
+                            if bundle_idx > 0 {
+                                bundle_idx -= 1;
+                            } else {
+                                break;
+                            }
+                        }
+                    }
+                }
+            }
+        }
+    }
 
     let result = QueryAnswer::TransactionHistory {
         txs,
-        total: Some(total),
+        total: Some(total as u64),
     };
     to_binary(&result)
 }
 
 pub fn query_balance(deps: Deps, account: String) -> StdResult<Binary> {
-    // Notice that if query_balance() was called by a viewking-key call, the address of 'account'
+    // Notice that if query_balance() was called by a viewing key call, the address of 'account'
     // has already been validated.
     // The address of 'account' should not be validated if query_balance() was called by a permit
     // call, for compatibility with non-Secret addresses.
     let account = Addr::unchecked(account);
+    let account = deps.api.addr_canonicalize(account.as_str())?;
 
-    let amount = Uint128::new(BalancesStore::load(deps.storage, &account));
+    let mut amount = stored_balance(deps.storage, &account)?;
+    let dwb = DWB.load(deps.storage)?;
+    let dwb_index = dwb.recipient_match(&account);
+    if dwb_index > 0 {
+        amount = amount.saturating_add(dwb.entries[dwb_index].amount()? as u128);
+    }
+    let amount = Uint128::new(amount);
     let response = QueryAnswer::Balance { amount };
     to_binary(&response)
 }
@@ -709,6 +763,203 @@ fn query_minters(deps: Deps) -> StdResult<Binary> {
     to_binary(&response)
 }
 
+// *****************
+// SNIP-52 query functions
+// *****************
+
+///
+/// ListChannels query
+///
+///   Public query to list all notification channels.
+///
+fn query_list_channels(deps: Deps) -> StdResult<Binary> {
+    let channels: Vec<String> = CHANNELS
+        .iter(deps.storage)?
+        .map(|channel| channel.unwrap())
+        .collect();
+    to_binary(&QueryAnswer::ListChannels { channels })
+}
+
+///
+/// ChannelInfo query
+///
+///   Authenticated query allows clients to obtain the seed,
+///   and Notification ID of an event for a specific tx_hash, for a specific channel.
+///
+fn query_channel_info(
+    deps: Deps,
+    env: Env,
+    channels: Vec<String>,
+    txhash: Option<String>,
+    sender_raw: CanonicalAddr,
+) -> StdResult<Binary> {
+    let secret = INTERNAL_SECRET.load(deps.storage)?;
+    let secret = secret.as_slice();
+    let seed = get_seed(&sender_raw, secret)?;
+    let mut channels_data = vec![];
+    for channel in channels {
+        let answer_id;
+        if let Some(tx_hash) = &txhash {
+            answer_id = Some(notification_id(&seed, &channel, tx_hash)?);
+        } else {
+            answer_id = None;
+        }
+        match channel.as_str() {
+            ReceivedNotificationData::CHANNEL_ID => {
+                let channel_info_data = ChannelInfoData {
+                    mode: "txhash".to_string(),
+                    channel,
+                    answer_id,
+                    parameters: None,
+                    data: None,
+                    next_id: None,
+                    counter: None,
+                    cddl: Some(ReceivedNotificationData::CDDL_SCHEMA.to_string()),
+                };
+                channels_data.push(channel_info_data);
+            }
+            SpentNotificationData::CHANNEL_ID => {
+                let channel_info_data = ChannelInfoData {
+                    mode: "txhash".to_string(),
+                    channel,
+                    answer_id,
+                    parameters: None,
+                    data: None,
+                    next_id: None,
+                    counter: None,
+                    cddl: Some(SpentNotificationData::CDDL_SCHEMA.to_string()),
+                };
+                channels_data.push(channel_info_data);
+            }
+            AllowanceNotificationData::CHANNEL_ID => {
+                let channel_info_data = ChannelInfoData {
+                    mode: "txhash".to_string(),
+                    channel,
+                    answer_id,
+                    parameters: None,
+                    data: None,
+                    next_id: None,
+                    counter: None,
+                    cddl: Some(AllowanceNotificationData::CDDL_SCHEMA.to_string()),
+                };
+                channels_data.push(channel_info_data);
+            }
+            MULTI_RECEIVED_CHANNEL_ID => {
+                let channel_info_data = ChannelInfoData {
+                    mode: "bloom".to_string(),
+                    channel,
+                    answer_id,
+                    parameters: Some(BloomParameters {
+                        m: 512,
+                        k: MULTI_RECEIVED_CHANNEL_BLOOM_K,
+                        h: "sha256".to_string(),
+                    }),
+                    data: Some(Descriptor {
+                        r#type: format!("packet[{}]", MULTI_RECEIVED_CHANNEL_BLOOM_N),
+                        version: "1".to_string(),
+                        packet_size: MULTI_RECEIVED_CHANNEL_PACKET_SIZE,
+                        data: StructDescriptor {
+                            r#type: "struct".to_string(),
+                            label: "transfer".to_string(),
+                            members: vec![
+                                FlatDescriptor {
+                                    r#type: "uint128".to_string(),
+                                    label: "amount".to_string(),
+                                    description: Some(
+                                        "The transfer amount in base denomination".to_string(),
+                                    ),
+                                },
+                                FlatDescriptor {
+                                    r#type: "bytes8".to_string(),
+                                    label: "spender".to_string(),
+                                    description: Some(
+                                        "The last 8 bytes of the sender's canonical address"
+                                            .to_string(),
+                                    ),
+                                },
+                            ],
+                        },
+                    }),
+                    counter: None,
+                    next_id: None,
+                    cddl: None,
+                };
+                channels_data.push(channel_info_data);
+            }
+            MULTI_SPENT_CHANNEL_ID => {
+                let channel_info_data = ChannelInfoData {
+                    mode: "bloom".to_string(),
+                    channel,
+                    answer_id,
+                    parameters: Some(BloomParameters {
+                        m: 512,
+                        k: MULTI_SPENT_CHANNEL_BLOOM_K,
+                        h: "sha256".to_string(),
+                    }),
+                    data: Some(Descriptor {
+                        r#type: format!("packet[{}]", MULTI_SPENT_CHANNEL_BLOOM_N),
+                        version: "1".to_string(),
+                        packet_size: MULTI_SPENT_CHANNEL_PACKET_SIZE,
+                        data: StructDescriptor {
+                            r#type: "struct".to_string(),
+                            label: "transfer".to_string(),
+                            members: vec![
+                                FlatDescriptor {
+                                    r#type: "uint128".to_string(),
+                                    label: "amount".to_string(),
+                                    description: Some(
+                                        "The transfer amount in base denomination".to_string(),
+                                    ),
+                                },
+                                FlatDescriptor {
+                                    r#type: "uint128".to_string(),
+                                    label: "balance".to_string(),
+                                    description: Some(
+                                        "Spender's new balance after the transfer".to_string(),
+                                    ),
+                                },
+                                FlatDescriptor {
+                                    r#type: "bytes8".to_string(),
+                                    label: "recipient".to_string(),
+                                    description: Some(
+                                        "The last 8 bytes of the recipient's canonical address"
+                                            .to_string(),
+                                    ),
+                                },
+                            ],
+                        },
+                    }),
+                    counter: None,
+                    next_id: None,
+                    cddl: None,
+                };
+                channels_data.push(channel_info_data);
+            }
+            _ => {
+                return Err(StdError::generic_err(format!(
+                    "`{}` channel is undefined",
+                    channel
+                )));
+            }
+        }
+    }
+
+    //Ok(Binary(vec![]))
+    //let schema = CHANNEL_SCHEMATA.get(deps.storage, &channel);
+
+    to_binary(&QueryAnswer::ChannelInfo {
+        as_of_block: Uint64::from(env.block.height),
+        channels: channels_data,
+        seed,
+    })
+}
+
+// *****************
+// End SNIP-52 query functions
+// *****************
+
+// execute functions
+
 fn change_admin(deps: DepsMut, info: MessageInfo, address: String) -> StdResult<Response> {
     let address = deps.api.addr_validate(address.as_str())?;
 
@@ -780,37 +1031,30 @@ fn remove_supported_denoms(
 #[allow(clippy::too_many_arguments)]
 fn try_mint_impl(
     deps: &mut DepsMut,
+    rng: &mut ContractPrng,
     minter: Addr,
     recipient: Addr,
     amount: Uint128,
     denom: String,
     memo: Option<String>,
     block: &cosmwasm_std::BlockInfo,
-    decoys: Option<Vec<Addr>>,
-    account_random_pos: Option<usize>,
+    #[cfg(feature = "gas_tracking")] tracker: &mut GasTracker,
 ) -> StdResult<()> {
     let raw_amount = amount.u128();
+    let raw_recipient = deps.api.addr_canonicalize(recipient.as_str())?;
+    let raw_minter = deps.api.addr_canonicalize(minter.as_str())?;
 
-    BalancesStore::update_balance(
+    perform_mint(
         deps.storage,
-        &recipient,
+        rng,
+        &raw_minter,
+        &raw_recipient,
         raw_amount,
-        true,
-        "",
-        &decoys,
-        &account_random_pos,
-    )?;
-
-    store_mint(
-        deps.storage,
-        minter,
-        recipient,
-        amount,
         denom,
         memo,
         block,
-        &decoys,
-        &account_random_pos,
+        #[cfg(feature = "gas_tracking")]
+        tracker,
     )?;
 
     Ok(())
@@ -821,12 +1065,14 @@ fn try_mint(
     mut deps: DepsMut,
     env: Env,
     info: MessageInfo,
+    rng: &mut ContractPrng,
     recipient: String,
     amount: Uint128,
     memo: Option<String>,
-    decoys: Option<Vec<Addr>>,
-    account_random_pos: Option<usize>,
 ) -> StdResult<Response> {
+    let secret = INTERNAL_SECRET.load(deps.storage)?;
+    let secret = secret.as_slice();
+
     let recipient = deps.api.addr_validate(recipient.as_str())?;
 
     let constants = CONFIG.load(deps.storage)?;
@@ -848,29 +1094,56 @@ fn try_mint(
     let minted_amount = safe_add(&mut total_supply, amount.u128());
     TOTAL_SUPPLY.save(deps.storage, &total_supply)?;
 
+    #[cfg(feature = "gas_tracking")]
+    let mut tracker: GasTracker = GasTracker::new(deps.api);
+
     // Note that even when minted_amount is equal to 0 we still want to perform the operations for logic consistency
     try_mint_impl(
         &mut deps,
+        rng,
         info.sender,
-        recipient,
+        recipient.clone(),
         Uint128::new(minted_amount),
         constants.symbol,
         memo,
         &env.block,
-        decoys,
-        account_random_pos,
+        #[cfg(feature = "gas_tracking")]
+        &mut tracker,
     )?;
 
-    Ok(Response::new().set_data(to_binary(&ExecuteAnswer::Mint { status: Success })?))
+    let received_notification = Notification::new(
+        recipient,
+        ReceivedNotificationData {
+            amount: minted_amount,
+            sender: None,
+        },
+    )
+    .to_txhash_notification(deps.api, &env, secret, Some(NOTIFICATION_BLOCK_SIZE))?;
+
+    let resp = Response::new()
+        .set_data(to_binary(&ExecuteAnswer::Mint { status: Success })?)
+        .add_attribute_plaintext(
+            received_notification.id_plaintext(),
+            received_notification.data_plaintext(),
+        );
+
+    #[cfg(feature = "gas_tracking")]
+    return Ok(resp.add_gas_tracker(tracker));
+
+    #[cfg(not(feature = "gas_tracking"))]
+    Ok(resp)
 }
 
 fn try_batch_mint(
     mut deps: DepsMut,
     env: Env,
     info: MessageInfo,
+    rng: &mut ContractPrng,
     actions: Vec<batch::MintAction>,
-    account_random_pos: Option<usize>,
 ) -> StdResult<Response> {
+    let secret = INTERNAL_SECRET.load(deps.storage)?;
+    let secret = secret.as_slice();
+
     let constants = CONFIG.load(deps.storage)?;
 
     if !constants.mint_is_enabled {
@@ -888,27 +1161,59 @@ fn try_batch_mint(
 
     let mut total_supply = TOTAL_SUPPLY.load(deps.storage)?;
 
+    let mut notifications = vec![];
     // Quick loop to check that the total of amounts is valid
     for action in actions {
         let actual_amount = safe_add(&mut total_supply, action.amount.u128());
 
         let recipient = deps.api.addr_validate(action.recipient.as_str())?;
+
+        #[cfg(feature = "gas_tracking")]
+        let mut tracker: GasTracker = GasTracker::new(deps.api);
+
         try_mint_impl(
             &mut deps,
+            rng,
             info.sender.clone(),
-            recipient,
+            recipient.clone(),
             Uint128::new(actual_amount),
             constants.symbol.clone(),
             action.memo,
             &env.block,
-            action.decoys,
-            account_random_pos,
+            #[cfg(feature = "gas_tracking")]
+            &mut tracker,
         )?;
+        notifications.push(Notification::new (
+            recipient,
+            ReceivedNotificationData {
+                amount: actual_amount,
+                sender: None,
+            },
+        ));
     }
 
+    let tx_hash = env
+        .transaction
+        .clone()
+        .ok_or(StdError::generic_err("no tx hash found"))?
+        .hash;
+    let received_data = multi_received_data(
+        deps.api,
+        notifications,
+        &tx_hash,
+        env.block.random.unwrap(),
+        secret,
+    )?;
+
     TOTAL_SUPPLY.save(deps.storage, &total_supply)?;
 
-    Ok(Response::new().set_data(to_binary(&ExecuteAnswer::BatchMint { status: Success })?))
+    Ok(Response::new()
+        .set_data(to_binary(&ExecuteAnswer::BatchMint { status: Success })?)
+        .add_attribute_plaintext(
+            format!("snip52:#{}", MULTI_RECEIVED_CHANNEL_ID),
+            Binary::from(received_data).to_base64(),
+        )
+    )
 }
 
 pub fn try_set_key(deps: DepsMut, info: MessageInfo, key: String) -> StdResult<Response> {
@@ -924,14 +1229,17 @@ pub fn try_create_key(
     deps: DepsMut,
     env: Env,
     info: MessageInfo,
-    entropy: String,
+    entropy: Option<String>,
+    rng: &mut ContractPrng,
 ) -> StdResult<Response> {
+    let entropy = [entropy.unwrap_or_default().as_bytes(), &rng.rand_bytes()].concat();
+
     let key = ViewingKey::create(
         deps.storage,
         &info,
         &env,
         info.sender.as_str(),
-        entropy.as_ref(),
+        &entropy,
     );
 
     Ok(Response::new().set_data(to_binary(&ExecuteAnswer::CreateViewingKey { key })?))
@@ -1041,8 +1349,7 @@ fn try_deposit(
     deps: DepsMut,
     env: Env,
     info: MessageInfo,
-    decoys: Option<Vec<Addr>>,
-    account_random_pos: Option<usize>,
+    rng: &mut ContractPrng,
 ) -> StdResult<Response> {
     let constants = CONFIG.load(deps.storage)?;
 
@@ -1075,29 +1382,29 @@ fn try_deposit(
     raw_amount = safe_add(&mut total_supply, raw_amount);
     TOTAL_SUPPLY.save(deps.storage, &total_supply)?;
 
-    let sender_address = &info.sender;
+    let sender_address = deps.api.addr_canonicalize(info.sender.as_str())?;
 
-    BalancesStore::update_balance(
-        deps.storage,
-        sender_address,
-        raw_amount,
-        true,
-        "",
-        &decoys,
-        &account_random_pos,
-    )?;
+    #[cfg(feature = "gas_tracking")]
+    let mut tracker: GasTracker = GasTracker::new(deps.api);
 
-    store_deposit(
+    perform_deposit(
         deps.storage,
-        sender_address,
-        Uint128::new(raw_amount),
+        rng,
+        &sender_address,
+        raw_amount,
         "uscrt".to_string(),
         &env.block,
-        &decoys,
-        &account_random_pos,
+        #[cfg(feature = "gas_tracking")]
+        &mut tracker,
     )?;
 
-    Ok(Response::new().set_data(to_binary(&ExecuteAnswer::Deposit { status: Success })?))
+    let resp = Response::new().set_data(to_binary(&ExecuteAnswer::Deposit { status: Success })?);
+
+    #[cfg(feature = "gas_tracking")]
+    return Ok(resp.add_gas_tracker(tracker));
+
+    #[cfg(not(feature = "gas_tracking"))]
+    Ok(resp)
 }
 
 fn try_redeem(
@@ -1106,8 +1413,6 @@ fn try_redeem(
     info: MessageInfo,
     amount: Uint128,
     denom: Option<String>,
-    decoys: Option<Vec<Addr>>,
-    account_random_pos: Option<usize>,
 ) -> StdResult<Response> {
     let constants = CONFIG.load(deps.storage)?;
     if !constants.redeem_is_enabled {
@@ -1133,19 +1438,30 @@ fn try_redeem(
         ));
     };
 
-    let sender_address = &info.sender;
+    let sender_address = deps.api.addr_canonicalize(info.sender.as_str())?;
     let amount_raw = amount.u128();
 
-    BalancesStore::update_balance(
+    let tx_id = store_redeem_action(deps.storage, amount.u128(), constants.symbol, &env.block)?;
+
+    // load delayed write buffer
+    let mut dwb = DWB.load(deps.storage)?;
+
+    #[cfg(feature = "gas_tracking")]
+    let mut tracker = GasTracker::new(deps.api);
+
+    // settle the signer's account in buffer
+    dwb.settle_sender_or_owner_account(
         deps.storage,
-        sender_address,
+        &sender_address,
+        tx_id,
         amount_raw,
-        false,
         "redeem",
-        &decoys,
-        &account_random_pos,
+        #[cfg(feature = "gas_tracking")]
+        &mut tracker,
     )?;
 
+    DWB.save(deps.storage, &dwb)?;
+
     let total_supply = TOTAL_SUPPLY.load(deps.storage)?;
     if let Some(total_supply) = total_supply.checked_sub(amount_raw) {
         TOTAL_SUPPLY.save(deps.storage, &total_supply)?;
@@ -1170,16 +1486,6 @@ fn try_redeem(
         amount,
     }];
 
-    store_redeem(
-        deps.storage,
-        sender_address,
-        amount,
-        constants.symbol,
-        &env.block,
-        &decoys,
-        &account_random_pos,
-    )?;
-
     let message = CosmosMsg::Bank(BankMsg::Send {
         to_address: info.sender.clone().into_string(),
         amount: withdrawal_coins,
@@ -1192,38 +1498,50 @@ fn try_redeem(
 #[allow(clippy::too_many_arguments)]
 fn try_transfer_impl(
     deps: &mut DepsMut,
+    rng: &mut ContractPrng,
     sender: &Addr,
     recipient: &Addr,
     amount: Uint128,
+    denom: String,
     memo: Option<String>,
     block: &cosmwasm_std::BlockInfo,
-    decoys: Option<Vec<Addr>>,
-    account_random_pos: Option<usize>,
-) -> StdResult<()> {
-    perform_transfer(
-        deps.storage,
-        sender,
-        recipient,
-        amount.u128(),
-        &decoys,
-        &account_random_pos,
-    )?;
+    #[cfg(feature = "gas_tracking")] tracker: &mut GasTracker,
+) -> StdResult<(Notification<ReceivedNotificationData>, Notification<SpentNotificationData>)> {
+    let raw_sender = deps.api.addr_canonicalize(sender.as_str())?;
+    let raw_recipient = deps.api.addr_canonicalize(recipient.as_str())?;
 
-    let symbol = CONFIG.load(deps.storage)?.symbol;
-    store_transfer(
+    let sender_balance = perform_transfer(
         deps.storage,
-        sender,
-        sender,
-        recipient,
-        amount,
-        symbol,
+        rng,
+        &raw_sender,
+        &raw_recipient,
+        &raw_sender,
+        amount.u128(),
+        denom,
         memo,
         block,
-        &decoys,
-        &account_random_pos,
+        #[cfg(feature = "gas_tracking")]
+        tracker,
     )?;
+    let received_notification = Notification::new(
+        recipient.clone(),
+        ReceivedNotificationData {
+            amount: amount.u128(),
+            sender: Some(sender.clone()),
+        }
+    );
 
-    Ok(())
+    let spent_notification = Notification::new (
+        sender.clone(),
+        SpentNotificationData {
+            amount: amount.u128(),
+            actions: 1,
+            recipient: Some(recipient.clone()),
+            balance: sender_balance,
+        }
+    );
+
+    Ok((received_notification, spent_notification))
 }
 
 #[allow(clippy::too_many_arguments)]
@@ -1231,54 +1549,160 @@ fn try_transfer(
     mut deps: DepsMut,
     env: Env,
     info: MessageInfo,
+    rng: &mut ContractPrng,
     recipient: String,
     amount: Uint128,
     memo: Option<String>,
-    decoys: Option<Vec<Addr>>,
-    account_random_pos: Option<usize>,
 ) -> StdResult<Response> {
-    let recipient = deps.api.addr_validate(recipient.as_str())?;
+    let secret = INTERNAL_SECRET.load(deps.storage)?;
+    let secret = secret.as_slice();
+
+    let recipient: Addr = deps.api.addr_validate(recipient.as_str())?;
+
+    let symbol = CONFIG.load(deps.storage)?.symbol;
+
+    #[cfg(feature = "gas_tracking")]
+    let mut tracker: GasTracker = GasTracker::new(deps.api);
 
-    try_transfer_impl(
+    let (received_notification, spent_notification) = try_transfer_impl(
         &mut deps,
+        rng,
         &info.sender,
         &recipient,
         amount,
+        symbol,
         memo,
         &env.block,
-        decoys,
-        account_random_pos,
+        #[cfg(feature = "gas_tracking")]
+        &mut tracker,
     )?;
 
-    Ok(Response::new().set_data(to_binary(&ExecuteAnswer::Transfer { status: Success })?))
-}
+    #[cfg(feature = "gas_tracking")]
+    let mut group1 = tracker.group("try_transfer.rest");
 
-fn try_batch_transfer(
-    mut deps: DepsMut,
-    env: Env,
-    info: MessageInfo,
-    actions: Vec<batch::TransferAction>,
-    account_random_pos: Option<usize>,
-) -> StdResult<Response> {
+    let received_notification = received_notification.to_txhash_notification(
+        deps.api,
+        &env,
+        secret,
+        Some(NOTIFICATION_BLOCK_SIZE),
+    )?;
+
+    let spent_notification = spent_notification.to_txhash_notification(
+        deps.api, 
+        &env, 
+        secret, 
+        Some(NOTIFICATION_BLOCK_SIZE)
+    )?;
+
+    let resp = Response::new()
+        .set_data(to_binary(&ExecuteAnswer::Transfer { status: Success })?)
+        .add_attribute_plaintext(
+            received_notification.id_plaintext(),
+            received_notification.data_plaintext(),
+        )
+        .add_attribute_plaintext(
+            spent_notification.id_plaintext(),
+            spent_notification.data_plaintext(),
+        );
+
+    #[cfg(feature = "gas_tracking")]
+    group1.log("rest");
+
+    #[cfg(feature = "gas_tracking")]
+    return Ok(resp.add_gas_tracker(tracker));
+
+    #[cfg(not(feature = "gas_tracking"))]
+    Ok(resp)
+}
+
+fn try_batch_transfer(
+    mut deps: DepsMut,
+    env: Env,
+    info: MessageInfo,
+    rng: &mut ContractPrng,
+    actions: Vec<batch::TransferAction>,
+) -> StdResult<Response> {
+    let num_actions = actions.len();
+    if num_actions == 0 {
+        return Ok(Response::new()
+            .set_data(to_binary(&ExecuteAnswer::BatchTransfer { status: Success })?)
+        );
+    }
+
+    let secret = INTERNAL_SECRET.load(deps.storage)?;
+    let secret = secret.as_slice();
+
+    let symbol = CONFIG.load(deps.storage)?.symbol;
+
+    #[cfg(feature = "gas_tracking")]
+    let mut tracker: GasTracker = GasTracker::new(deps.api);
+
+    let mut notifications = vec![];
     for action in actions {
         let recipient = deps.api.addr_validate(action.recipient.as_str())?;
-        try_transfer_impl(
+        let (received_notification, spent_notification) = try_transfer_impl(
             &mut deps,
+            rng,
             &info.sender,
             &recipient,
             action.amount,
+            symbol.clone(),
             action.memo,
             &env.block,
-            action.decoys,
-            account_random_pos,
+            #[cfg(feature = "gas_tracking")]
+            &mut tracker,
         )?;
+        notifications.push((received_notification, spent_notification));
     }
 
-    Ok(
-        Response::new().set_data(to_binary(&ExecuteAnswer::BatchTransfer {
-            status: Success,
-        })?),
+    let tx_hash = env
+        .transaction
+        .clone()
+        .ok_or(StdError::generic_err("no tx hash found"))?
+        .hash;
+    let (received_notifications, spent_notifications): (
+        Vec<Notification<ReceivedNotificationData>>,
+        Vec<Notification<SpentNotificationData>>,
+    ) = notifications.into_iter().unzip();
+    let received_data = multi_received_data(
+        deps.api,
+        received_notifications,
+        &tx_hash,
+        env.block.random.clone().unwrap(),
+        secret,
+    )?;
+
+    let total_amount_spent = spent_notifications
+        .iter()
+        .fold(0u128, |acc, notification| acc.saturating_add(notification.data.amount));
+
+    let spent_notification = Notification::new (
+        info.sender,
+        SpentNotificationData {
+            amount: total_amount_spent,
+            actions: num_actions as u32,
+            recipient: spent_notifications[0].data.recipient.clone(),
+            balance: spent_notifications.last().unwrap().data.balance,
+        }
     )
+    .to_txhash_notification(deps.api, &env, secret, Some(NOTIFICATION_BLOCK_SIZE))?;
+
+    let resp = Response::new()
+        .set_data(to_binary(&ExecuteAnswer::BatchTransfer { status: Success })?)
+        .add_attribute_plaintext(
+            format!("snip52:#{}", MULTI_RECEIVED_CHANNEL_ID),
+            Binary::from(received_data).to_base64(),
+        )
+        .add_attribute_plaintext(
+            spent_notification.id_plaintext(),
+            spent_notification.data_plaintext(),
+        );
+
+    #[cfg(feature = "gas_tracking")]
+    return Ok(resp.add_gas_tracker(tracker));
+
+    #[cfg(not(feature = "gas_tracking"))]
+    Ok(resp)
 }
 
 #[allow(clippy::too_many_arguments)]
@@ -1314,26 +1738,29 @@ fn try_add_receiver_api_callback(
 #[allow(clippy::too_many_arguments)]
 fn try_send_impl(
     deps: &mut DepsMut,
+    rng: &mut ContractPrng,
     messages: &mut Vec<CosmosMsg>,
     sender: Addr,
     recipient: Addr,
     recipient_code_hash: Option<String>,
     amount: Uint128,
+    denom: String,
     memo: Option<String>,
     msg: Option<Binary>,
     block: &cosmwasm_std::BlockInfo,
-    decoys: Option<Vec<Addr>>,
-    account_random_pos: Option<usize>,
-) -> StdResult<()> {
-    try_transfer_impl(
+    #[cfg(feature = "gas_tracking")] tracker: &mut GasTracker,
+) -> StdResult<(Notification<ReceivedNotificationData>, Notification<SpentNotificationData>)> {
+    let (received_notification, spent_notification) = try_transfer_impl(
         deps,
+        rng,
         &sender,
         &recipient,
         amount,
+        denom,
         memo.clone(),
         block,
-        decoys,
-        account_random_pos,
+        #[cfg(feature = "gas_tracking")]
+        tracker,
     )?;
 
     try_add_receiver_api_callback(
@@ -1348,7 +1775,7 @@ fn try_send_impl(
         memo,
     )?;
 
-    Ok(())
+    Ok((received_notification, spent_notification))
 }
 
 #[allow(clippy::too_many_arguments)]
@@ -1356,64 +1783,160 @@ fn try_send(
     mut deps: DepsMut,
     env: Env,
     info: MessageInfo,
+    rng: &mut ContractPrng,
     recipient: String,
     recipient_code_hash: Option<String>,
     amount: Uint128,
     memo: Option<String>,
     msg: Option<Binary>,
-    decoys: Option<Vec<Addr>>,
-    account_random_pos: Option<usize>,
 ) -> StdResult<Response> {
+    let secret = INTERNAL_SECRET.load(deps.storage)?;
+    let secret = secret.as_slice();
+
     let recipient = deps.api.addr_validate(recipient.as_str())?;
 
     let mut messages = vec![];
-    try_send_impl(
+    let symbol = CONFIG.load(deps.storage)?.symbol;
+
+    #[cfg(feature = "gas_tracking")]
+    let mut tracker: GasTracker = GasTracker::new(deps.api);
+
+    let (received_notification, spent_notification) = try_send_impl(
         &mut deps,
+        rng,
         &mut messages,
         info.sender,
         recipient,
         recipient_code_hash,
         amount,
+        symbol,
         memo,
         msg,
         &env.block,
-        decoys,
-        account_random_pos,
+        #[cfg(feature = "gas_tracking")]
+        &mut tracker,
     )?;
 
-    Ok(Response::new()
+    let received_notification = received_notification.to_txhash_notification(
+        deps.api,
+        &env,
+        secret,
+        Some(NOTIFICATION_BLOCK_SIZE)
+    )?;
+    let spent_notification =
+        spent_notification.to_txhash_notification(deps.api, &env, secret, Some(NOTIFICATION_BLOCK_SIZE))?;
+
+    let resp = Response::new()
         .add_messages(messages)
-        .set_data(to_binary(&ExecuteAnswer::Send { status: Success })?))
+        .set_data(to_binary(&ExecuteAnswer::Send { status: Success })?)
+        .add_attribute_plaintext(
+            received_notification.id_plaintext(),
+            received_notification.data_plaintext(),
+        )
+        .add_attribute_plaintext(
+            spent_notification.id_plaintext(),
+            spent_notification.data_plaintext(),
+        );
+
+    #[cfg(feature = "gas_tracking")]
+    return Ok(resp.add_gas_tracker(tracker));
+
+    #[cfg(not(feature = "gas_tracking"))]
+    Ok(resp)
 }
 
 fn try_batch_send(
     mut deps: DepsMut,
     env: Env,
     info: MessageInfo,
+    rng: &mut ContractPrng,
     actions: Vec<batch::SendAction>,
-    account_random_pos: Option<usize>,
 ) -> StdResult<Response> {
+    let num_actions = actions.len();
+    if num_actions == 0 {
+        return Ok(Response::new()
+            .set_data(to_binary(&ExecuteAnswer::BatchSend { status: Success })?)
+        );
+    }
+
+    let secret = INTERNAL_SECRET.load(deps.storage)?;
+    let secret = secret.as_slice();
+
     let mut messages = vec![];
+
+    let mut notifications = vec![];
+    let num_actions: usize = actions.len();
+
+    let symbol = CONFIG.load(deps.storage)?.symbol;
+
+    #[cfg(feature = "gas_tracking")]
+    let mut tracker: GasTracker = GasTracker::new(deps.api);
+
     for action in actions {
         let recipient = deps.api.addr_validate(action.recipient.as_str())?;
-        try_send_impl(
+        let (received_notification, spent_notification) = try_send_impl(
             &mut deps,
+            rng,
             &mut messages,
             info.sender.clone(),
             recipient,
             action.recipient_code_hash,
             action.amount,
+            symbol.clone(),
             action.memo,
             action.msg,
             &env.block,
-            action.decoys,
-            account_random_pos,
+            #[cfg(feature = "gas_tracking")]
+            &mut tracker,
         )?;
+        notifications.push((received_notification, spent_notification));
     }
 
+    let tx_hash = env
+        .transaction
+        .clone()
+        .ok_or(StdError::generic_err("no tx hash found"))?
+        .hash;
+
+    let (received_notifications, spent_notifications): (
+        Vec<Notification<ReceivedNotificationData>>,
+        Vec<Notification<SpentNotificationData>>,
+    ) = notifications.into_iter().unzip();
+    let received_data = multi_received_data(
+        deps.api,
+        received_notifications,
+        &tx_hash,
+        env.block.random.clone().unwrap(),
+        secret,
+    )?;
+
+    let total_amount_spent = spent_notifications
+        .iter()
+        .fold(0u128, |acc, notification| acc + notification.data.amount);
+
+    let spent_notification = Notification::new (
+        info.sender,
+        SpentNotificationData {
+            amount: total_amount_spent,
+            actions: num_actions as u32,
+            recipient: spent_notifications[0].data.recipient.clone(),
+            balance: spent_notifications.last().unwrap().data.balance,
+        }
+    )
+    .to_txhash_notification(deps.api, &env, secret, Some(NOTIFICATION_BLOCK_SIZE))?;
+
     Ok(Response::new()
         .add_messages(messages)
-        .set_data(to_binary(&ExecuteAnswer::BatchSend { status: Success })?))
+        .set_data(to_binary(&ExecuteAnswer::BatchSend { status: Success })?)
+        .add_attribute_plaintext(
+            format!("snip52:#{}", MULTI_RECEIVED_CHANNEL_ID),
+            Binary::from(received_data).to_base64(),
+        )
+        .add_attribute_plaintext(
+            spent_notification.id_plaintext(),
+            spent_notification.data_plaintext(),
+        )
+    )
 }
 
 fn try_register_receive(
@@ -1461,43 +1984,58 @@ fn use_allowance(
 #[allow(clippy::too_many_arguments)]
 fn try_transfer_from_impl(
     deps: &mut DepsMut,
+    rng: &mut ContractPrng,
     env: &Env,
     spender: &Addr,
     owner: &Addr,
     recipient: &Addr,
     amount: Uint128,
+    denom: String,
     memo: Option<String>,
-    decoys: Option<Vec<Addr>>,
-    account_random_pos: Option<usize>,
-) -> StdResult<()> {
+) -> StdResult<(Notification<ReceivedNotificationData>, Notification<SpentNotificationData>)> {
     let raw_amount = amount.u128();
+    let raw_spender = deps.api.addr_canonicalize(spender.as_str())?;
+    let raw_owner = deps.api.addr_canonicalize(owner.as_str())?;
+    let raw_recipient = deps.api.addr_canonicalize(recipient.as_str())?;
 
     use_allowance(deps.storage, env, owner, spender, raw_amount)?;
 
-    perform_transfer(
-        deps.storage,
-        owner,
-        recipient,
-        raw_amount,
-        &decoys,
-        &account_random_pos,
-    )?;
+    #[cfg(feature = "gas_tracking")]
+    let mut tracker: GasTracker = GasTracker::new(deps.api);
 
-    let symbol = CONFIG.load(deps.storage)?.symbol;
-    store_transfer(
+    let owner_balance = perform_transfer(
         deps.storage,
-        owner,
-        spender,
-        recipient,
-        amount,
-        symbol,
+        rng,
+        &raw_owner,
+        &raw_recipient,
+        &raw_spender,
+        raw_amount,
+        denom,
         memo,
         &env.block,
-        &decoys,
-        &account_random_pos,
+        #[cfg(feature = "gas_tracking")]
+        &mut tracker,
     )?;
 
-    Ok(())
+    let received_notification = Notification::new(
+        recipient.clone(),
+        ReceivedNotificationData {
+            amount: amount.u128(),
+            sender: Some(owner.clone()),
+        }
+    );
+
+    let spent_notification = Notification::new (
+        owner.clone(),
+        SpentNotificationData {
+            amount: amount.u128(),
+            actions: 1,
+            recipient: Some(recipient.clone()),
+            balance: owner_balance,
+        }
+    );
+
+    Ok((received_notification, spent_notification))
 }
 
 #[allow(clippy::too_many_arguments)]
@@ -1505,57 +2043,123 @@ fn try_transfer_from(
     mut deps: DepsMut,
     env: &Env,
     info: MessageInfo,
+    rng: &mut ContractPrng,
     owner: String,
     recipient: String,
     amount: Uint128,
     memo: Option<String>,
-    decoys: Option<Vec<Addr>>,
-    account_random_pos: Option<usize>,
 ) -> StdResult<Response> {
+    let secret = INTERNAL_SECRET.load(deps.storage)?;
+    let secret = secret.as_slice();
+
     let owner = deps.api.addr_validate(owner.as_str())?;
     let recipient = deps.api.addr_validate(recipient.as_str())?;
-    try_transfer_from_impl(
+    let symbol = CONFIG.load(deps.storage)?.symbol;
+    let (received_notification, spent_notification) = try_transfer_from_impl(
         &mut deps,
+        rng,
         env,
         &info.sender,
         &owner,
         &recipient,
         amount,
+        symbol,
         memo,
-        decoys,
-        account_random_pos,
+    )?;
+    let received_notification = received_notification.to_txhash_notification(
+        deps.api,
+        &env,
+        secret,
+        Some(NOTIFICATION_BLOCK_SIZE),
+    )?;
+
+    let spent_notification = spent_notification.to_txhash_notification(
+        deps.api, 
+        &env, 
+        secret, 
+        Some(NOTIFICATION_BLOCK_SIZE)
     )?;
 
-    Ok(Response::new().set_data(to_binary(&ExecuteAnswer::TransferFrom { status: Success })?))
+    Ok(
+        Response::new()
+            .set_data(to_binary(&ExecuteAnswer::TransferFrom { status: Success })?)
+            .add_attribute_plaintext(
+                received_notification.id_plaintext(),
+                received_notification.data_plaintext(),
+            )
+            .add_attribute_plaintext(
+                spent_notification.id_plaintext(),
+                spent_notification.data_plaintext(),
+            )
+    )
 }
 
 fn try_batch_transfer_from(
     mut deps: DepsMut,
     env: &Env,
     info: MessageInfo,
+    rng: &mut ContractPrng,
     actions: Vec<batch::TransferFromAction>,
-    account_random_pos: Option<usize>,
 ) -> StdResult<Response> {
+    let secret = INTERNAL_SECRET.load(deps.storage)?;
+    let secret = secret.as_slice();
+
+    let mut notifications = vec![];
+
+    let symbol = CONFIG.load(deps.storage)?.symbol;
     for action in actions {
         let owner = deps.api.addr_validate(action.owner.as_str())?;
         let recipient = deps.api.addr_validate(action.recipient.as_str())?;
-        try_transfer_from_impl(
+        let (received_notification, spent_notification) = try_transfer_from_impl(
             &mut deps,
+            rng,
             env,
             &info.sender,
             &owner,
             &recipient,
             action.amount,
+            symbol.clone(),
             action.memo,
-            action.decoys,
-            account_random_pos,
         )?;
+        notifications.push((received_notification, spent_notification));
     }
 
+    let tx_hash = env
+        .transaction
+        .clone()
+        .ok_or(StdError::generic_err("no tx hash found"))?
+        .hash;
+
+    let (received_notifications, spent_notifications): (
+        Vec<Notification<ReceivedNotificationData>>,
+        Vec<Notification<SpentNotificationData>>,
+    ) = notifications.into_iter().unzip();
+    let received_data = multi_received_data(
+        deps.api,
+        received_notifications,
+        &tx_hash,
+        env.block.random.clone().unwrap(),
+        secret,
+    )?;
+    let spent_data = multi_spent_data(
+        deps.api,
+        spent_notifications,
+        &tx_hash,
+        env.block.random.clone().unwrap(),
+        secret,
+    )?;
+
     Ok(
-        Response::new().set_data(to_binary(&ExecuteAnswer::BatchTransferFrom {
-            status: Success,
-        })?),
+        Response::new()
+            .set_data(to_binary(&ExecuteAnswer::BatchTransferFrom {status: Success})?)
+            .add_attribute_plaintext(
+                format!("snip52:#{}", MULTI_RECEIVED_CHANNEL_ID),
+                Binary::from(received_data).to_base64(),
+            )
+            .add_attribute_plaintext(
+                format!("snip52:#{}", MULTI_SPENT_CHANNEL_ID),
+                Binary::from(spent_data).to_base64(),
+            )
     )
 }
 
@@ -1564,6 +2168,7 @@ fn try_send_from_impl(
     deps: &mut DepsMut,
     env: Env,
     info: &MessageInfo,
+    rng: &mut ContractPrng,
     messages: &mut Vec<CosmosMsg>,
     owner: Addr,
     recipient: Addr,
@@ -1571,20 +2176,19 @@ fn try_send_from_impl(
     amount: Uint128,
     memo: Option<String>,
     msg: Option<Binary>,
-    decoys: Option<Vec<Addr>>,
-    account_random_pos: Option<usize>,
-) -> StdResult<()> {
+) -> StdResult<(Notification<ReceivedNotificationData>, Notification<SpentNotificationData>)> {
     let spender = info.sender.clone();
-    try_transfer_from_impl(
+    let symbol = CONFIG.load(deps.storage)?.symbol;
+    let (received_notification, spent_notification) = try_transfer_from_impl(
         deps,
+        rng,
         &env,
         &spender,
         &owner,
         &recipient,
         amount,
+        symbol,
         memo.clone(),
-        decoys,
-        account_random_pos,
     )?;
 
     try_add_receiver_api_callback(
@@ -1599,7 +2203,7 @@ fn try_send_from_impl(
         memo,
     )?;
 
-    Ok(())
+    Ok((received_notification, spent_notification))
 }
 
 #[allow(clippy::too_many_arguments)]
@@ -1607,22 +2211,25 @@ fn try_send_from(
     mut deps: DepsMut,
     env: Env,
     info: &MessageInfo,
+    rng: &mut ContractPrng,
     owner: String,
     recipient: String,
     recipient_code_hash: Option<String>,
     amount: Uint128,
     memo: Option<String>,
     msg: Option<Binary>,
-    decoys: Option<Vec<Addr>>,
-    account_random_pos: Option<usize>,
 ) -> StdResult<Response> {
+    let secret = INTERNAL_SECRET.load(deps.storage)?;
+    let secret = secret.as_slice();
+
     let owner = deps.api.addr_validate(owner.as_str())?;
     let recipient = deps.api.addr_validate(recipient.as_str())?;
     let mut messages = vec![];
-    try_send_from_impl(
+    let (received_notification, spent_notification) = try_send_from_impl(
         &mut deps,
-        env,
+        env.clone(),
         info,
+        rng,
         &mut messages,
         owner,
         recipient,
@@ -1630,31 +2237,52 @@ fn try_send_from(
         amount,
         memo,
         msg,
-        decoys,
-        account_random_pos,
     )?;
 
+    let received_notification = received_notification.to_txhash_notification(
+        deps.api,
+        &env,
+        secret,
+        Some(NOTIFICATION_BLOCK_SIZE),
+    )?;
+    let spent_notification =
+        spent_notification.to_txhash_notification(deps.api, &env, secret, Some(NOTIFICATION_BLOCK_SIZE))?;
+
     Ok(Response::new()
         .add_messages(messages)
-        .set_data(to_binary(&ExecuteAnswer::SendFrom { status: Success })?))
+        .set_data(to_binary(&ExecuteAnswer::SendFrom { status: Success })?)
+        .add_attribute_plaintext(
+            received_notification.id_plaintext(),
+            received_notification.data_plaintext(),
+        )
+        .add_attribute_plaintext(
+            spent_notification.id_plaintext(),
+            spent_notification.data_plaintext(),
+        )
+    )
 }
 
 fn try_batch_send_from(
     mut deps: DepsMut,
     env: Env,
     info: &MessageInfo,
+    rng: &mut ContractPrng,
     actions: Vec<batch::SendFromAction>,
-    account_random_pos: Option<usize>,
 ) -> StdResult<Response> {
+    let secret = INTERNAL_SECRET.load(deps.storage)?;
+    let secret = secret.as_slice();
+
     let mut messages = vec![];
+    let mut notifications = vec![];
 
     for action in actions {
         let owner = deps.api.addr_validate(action.owner.as_str())?;
         let recipient = deps.api.addr_validate(action.recipient.as_str())?;
-        try_send_from_impl(
+        let (received_notification, spent_notification) = try_send_from_impl(
             &mut deps,
             env.clone(),
             info,
+            rng,
             &mut messages,
             owner,
             recipient,
@@ -1662,16 +2290,49 @@ fn try_batch_send_from(
             action.amount,
             action.memo,
             action.msg,
-            action.decoys,
-            account_random_pos,
         )?;
+        notifications.push((received_notification, spent_notification));
     }
 
+    let tx_hash = env
+        .transaction
+        .clone()
+        .ok_or(StdError::generic_err("no tx hash found"))?
+        .hash;
+
+    let (received_notifications, spent_notifications): (
+        Vec<Notification<ReceivedNotificationData>>,
+        Vec<Notification<SpentNotificationData>>,
+    ) = notifications.into_iter().unzip();
+    let received_data = multi_received_data(
+        deps.api,
+        received_notifications,
+        &tx_hash,
+        env.block.random.clone().unwrap(),
+        secret,
+    )?;
+    let spent_data = multi_spent_data(
+        deps.api,
+        spent_notifications,
+        &tx_hash,
+        env.block.random.clone().unwrap(),
+        secret,
+    )?;
+
     Ok(Response::new()
         .add_messages(messages)
         .set_data(to_binary(&ExecuteAnswer::BatchSendFrom {
             status: Success,
-        })?))
+        })?)
+        .add_attribute_plaintext(
+            format!("snip52:#{}", MULTI_RECEIVED_CHANNEL_ID),
+            Binary::from(received_data).to_base64(),
+        )
+        .add_attribute_plaintext(
+            format!("snip52:#{}", MULTI_SPENT_CHANNEL_ID),
+            Binary::from(spent_data).to_base64(),
+        )
+    )
 }
 
 #[allow(clippy::too_many_arguments)]
@@ -1682,10 +2343,12 @@ fn try_burn_from(
     owner: String,
     amount: Uint128,
     memo: Option<String>,
-    decoys: Option<Vec<Addr>>,
-    account_random_pos: Option<usize>,
 ) -> StdResult<Response> {
+    let secret = INTERNAL_SECRET.load(deps.storage)?;
+    let secret = secret.as_slice();
+    
     let owner = deps.api.addr_validate(owner.as_str())?;
+    let raw_owner = deps.api.addr_canonicalize(owner.as_str())?;
     let constants = CONFIG.load(deps.storage)?;
     if !constants.burn_is_enabled {
         return Err(StdError::generic_err(
@@ -1695,16 +2358,48 @@ fn try_burn_from(
 
     let raw_amount = amount.u128();
     use_allowance(deps.storage, env, &owner, &info.sender, raw_amount)?;
+    let raw_burner = deps.api.addr_canonicalize(info.sender.as_str())?;
 
-    BalancesStore::update_balance(
+    let tx_id = store_burn_action(
         deps.storage,
-        &owner,
+        raw_owner.clone(),
+        raw_burner.clone(),
+        raw_amount,
+        constants.symbol,
+        memo,
+        &env.block,
+    )?;
+
+    // load delayed write buffer
+    let mut dwb = DWB.load(deps.storage)?;
+
+    #[cfg(feature = "gas_tracking")]
+    let mut tracker = GasTracker::new(deps.api);
+
+    // settle the owner's account in buffer
+    let owner_balance = dwb.settle_sender_or_owner_account(
+        deps.storage,
+        &raw_owner,
+        tx_id,
         raw_amount,
-        false,
         "burn",
-        &decoys,
-        &account_random_pos,
+        #[cfg(feature = "gas_tracking")]
+        &mut tracker,
     )?;
+    if raw_burner != raw_owner {
+        // also settle sender's account
+        dwb.settle_sender_or_owner_account(
+            deps.storage,
+            &raw_burner,
+            tx_id,
+            0,
+            "burn",
+            #[cfg(feature = "gas_tracking")]
+            &mut tracker,
+        )?;
+    }
+
+    DWB.save(deps.storage, &dwb)?;
 
     // remove from supply
     let mut total_supply = TOTAL_SUPPLY.load(deps.storage)?;
@@ -1718,19 +2413,25 @@ fn try_burn_from(
     }
     TOTAL_SUPPLY.save(deps.storage, &total_supply)?;
 
-    store_burn(
-        deps.storage,
+    let spent_notification = Notification::new (
         owner,
-        info.sender,
-        amount,
-        constants.symbol,
-        memo,
-        &env.block,
-        &decoys,
-        &account_random_pos,
-    )?;
+        SpentNotificationData {
+            amount: raw_amount,
+            actions: 1,
+            recipient: None,
+            balance: owner_balance,
+        }
+    )
+    .to_txhash_notification(deps.api, &env, secret, Some(NOTIFICATION_BLOCK_SIZE))?;
 
-    Ok(Response::new().set_data(to_binary(&ExecuteAnswer::BurnFrom { status: Success })?))
+    Ok(
+        Response::new()
+            .set_data(to_binary(&ExecuteAnswer::BurnFrom { status: Success })?)
+            .add_attribute_plaintext(
+                spent_notification.id_plaintext(),
+                spent_notification.data_plaintext(),
+            )
+    )
 }
 
 fn try_batch_burn_from(
@@ -1738,8 +2439,10 @@ fn try_batch_burn_from(
     env: &Env,
     info: MessageInfo,
     actions: Vec<batch::BurnFromAction>,
-    account_random_pos: Option<usize>,
 ) -> StdResult<Response> {
+    let secret = INTERNAL_SECRET.load(deps.storage)?;
+    let secret = secret.as_slice();
+
     let constants = CONFIG.load(deps.storage)?;
     if !constants.burn_is_enabled {
         return Err(StdError::generic_err(
@@ -1747,23 +2450,55 @@ fn try_batch_burn_from(
         ));
     }
 
-    let spender = info.sender;
+    let raw_spender = deps.api.addr_canonicalize(info.sender.as_str())?;
     let mut total_supply = TOTAL_SUPPLY.load(deps.storage)?;
+    let mut spent_notifications = vec![];
 
     for action in actions {
         let owner = deps.api.addr_validate(action.owner.as_str())?;
+        let raw_owner = deps.api.addr_canonicalize(owner.as_str())?;
         let amount = action.amount.u128();
-        use_allowance(deps.storage, env, &owner, &spender, amount)?;
+        use_allowance(deps.storage, env, &owner, &info.sender, amount)?;
 
-        BalancesStore::update_balance(
+        let tx_id = store_burn_action(
             deps.storage,
-            &owner,
+            raw_owner.clone(),
+            raw_spender.clone(),
+            amount,
+            constants.symbol.clone(),
+            action.memo.clone(),
+            &env.block,
+        )?;
+
+        // load delayed write buffer
+        let mut dwb = DWB.load(deps.storage)?;
+
+        #[cfg(feature = "gas_tracking")]
+        let mut tracker = GasTracker::new(deps.api);
+
+        // settle the owner's account in buffer
+        let owner_balance = dwb.settle_sender_or_owner_account(
+            deps.storage,
+            &raw_owner,
+            tx_id,
             amount,
-            false,
             "burn",
-            &action.decoys,
-            &account_random_pos,
+            #[cfg(feature = "gas_tracking")]
+            &mut tracker,
         )?;
+        if raw_spender != raw_owner {
+            dwb.settle_sender_or_owner_account(
+                deps.storage,
+                &raw_spender,
+                tx_id,
+                0,
+                "burn",
+                #[cfg(feature = "gas_tracking")]
+                &mut tracker,
+            )?;
+        }
+
+        DWB.save(deps.storage, &dwb)?;
 
         // remove from supply
         if let Some(new_total_supply) = total_supply.checked_sub(amount) {
@@ -1774,25 +2509,39 @@ fn try_batch_burn_from(
             )));
         }
 
-        store_burn(
-            deps.storage,
-            owner,
-            spender.clone(),
-            action.amount,
-            constants.symbol.clone(),
-            action.memo,
-            &env.block,
-            &action.decoys,
-            &account_random_pos,
-        )?;
+        spent_notifications.push(Notification::new (
+            info.sender.clone(),
+            SpentNotificationData {
+                amount,
+                actions: 1,
+                recipient: None,
+                balance: owner_balance,
+            }
+        ));
     }
 
     TOTAL_SUPPLY.save(deps.storage, &total_supply)?;
 
+    let tx_hash = env
+        .transaction
+        .clone()
+        .ok_or(StdError::generic_err("no tx hash found"))?
+        .hash;
+    let spent_data = multi_spent_data(
+        deps.api,
+        spent_notifications,
+        &tx_hash,
+        env.block.random.clone().unwrap(),
+        secret,
+    )?;
+
     Ok(
-        Response::new().set_data(to_binary(&ExecuteAnswer::BatchBurnFrom {
-            status: Success,
-        })?),
+        Response::new()
+            .set_data(to_binary(&ExecuteAnswer::BatchBurnFrom {status: Success,})?)
+            .add_attribute_plaintext(
+                format!("snip52:#{}", MULTI_SPENT_CHANNEL_ID),
+                Binary::from(spent_data).to_base64(),
+            )
     )
 }
 
@@ -1804,6 +2553,9 @@ fn try_increase_allowance(
     amount: Uint128,
     expiration: Option<u64>,
 ) -> StdResult<Response> {
+    let secret = INTERNAL_SECRET.load(deps.storage)?;
+    let secret = secret.as_slice();
+
     let spender = deps.api.addr_validate(spender.as_str())?;
     let mut allowance = AllowancesStore::load(deps.storage, &info.sender, &spender);
 
@@ -1823,12 +2575,27 @@ fn try_increase_allowance(
     let new_amount = allowance.amount;
     AllowancesStore::save(deps.storage, &info.sender, &spender, &allowance)?;
 
+    let notification = Notification::new (
+        spender.clone(),
+        AllowanceNotificationData {
+            amount: new_amount,
+            allower: info.sender.clone(),
+            expiration,
+        }
+    )
+    .to_txhash_notification(deps.api, &env, secret, Some(NOTIFICATION_BLOCK_SIZE))?;
+
     Ok(
-        Response::new().set_data(to_binary(&ExecuteAnswer::IncreaseAllowance {
-            owner: info.sender,
-            spender,
-            allowance: Uint128::from(new_amount),
-        })?),
+        Response::new()
+            .set_data(to_binary(&ExecuteAnswer::IncreaseAllowance {
+                owner: info.sender,
+                spender,
+                allowance: Uint128::from(new_amount),
+            })?)
+            .add_attribute_plaintext(
+                notification.id_plaintext(),
+                notification.data_plaintext()
+            )
     )
 }
 
@@ -1840,6 +2607,9 @@ fn try_decrease_allowance(
     amount: Uint128,
     expiration: Option<u64>,
 ) -> StdResult<Response> {
+    let secret = INTERNAL_SECRET.load(deps.storage)?;
+    let secret = secret.as_slice();
+
     let spender = deps.api.addr_validate(spender.as_str())?;
     let mut allowance = AllowancesStore::load(deps.storage, &info.sender, &spender);
 
@@ -1859,12 +2629,27 @@ fn try_decrease_allowance(
     let new_amount = allowance.amount;
     AllowancesStore::save(deps.storage, &info.sender, &spender, &allowance)?;
 
+    let notification = Notification::new (
+        spender.clone(),
+        AllowanceNotificationData {
+            amount: new_amount,
+            allower: info.sender.clone(),
+            expiration,
+        }
+    )
+    .to_txhash_notification(deps.api, &env, secret, Some(NOTIFICATION_BLOCK_SIZE))?;
+
     Ok(
-        Response::new().set_data(to_binary(&ExecuteAnswer::DecreaseAllowance {
-            owner: info.sender,
-            spender,
-            allowance: Uint128::from(new_amount),
-        })?),
+        Response::new()
+            .set_data(to_binary(&ExecuteAnswer::DecreaseAllowance {
+                owner: info.sender,
+                spender,
+                allowance: Uint128::from(new_amount),
+            })?)
+            .add_attribute_plaintext(
+                notification.id_plaintext(),
+                notification.data_plaintext()
+            )
     )
 }
 
@@ -1952,9 +2737,10 @@ fn try_burn(
     info: MessageInfo,
     amount: Uint128,
     memo: Option<String>,
-    decoys: Option<Vec<Addr>>,
-    account_random_pos: Option<usize>,
 ) -> StdResult<Response> {
+    let secret = INTERNAL_SECRET.load(deps.storage)?;
+    let secret = secret.as_slice();
+
     let constants = CONFIG.load(deps.storage)?;
     if !constants.burn_is_enabled {
         return Err(StdError::generic_err(
@@ -1963,17 +2749,37 @@ fn try_burn(
     }
 
     let raw_amount = amount.u128();
+    let raw_burn_address = deps.api.addr_canonicalize(info.sender.as_str())?;
 
-    BalancesStore::update_balance(
+    let tx_id = store_burn_action(
         deps.storage,
-        &info.sender,
+        raw_burn_address.clone(),
+        raw_burn_address.clone(),
+        raw_amount,
+        constants.symbol,
+        memo,
+        &env.block,
+    )?;
+
+    // load delayed write buffer
+    let mut dwb = DWB.load(deps.storage)?;
+
+    #[cfg(feature = "gas_tracking")]
+    let mut tracker = GasTracker::new(deps.api);
+
+    // settle the signer's account in buffer
+    let owner_balance = dwb.settle_sender_or_owner_account(
+        deps.storage,
+        &raw_burn_address,
+        tx_id,
         raw_amount,
-        false,
         "burn",
-        &decoys,
-        &account_random_pos,
+        #[cfg(feature = "gas_tracking")]
+        &mut tracker,
     )?;
 
+    DWB.save(deps.storage, &dwb)?;
+
     let mut total_supply = TOTAL_SUPPLY.load(deps.storage)?;
     if let Some(new_total_supply) = total_supply.checked_sub(raw_amount) {
         total_supply = new_total_supply;
@@ -1984,50 +2790,186 @@ fn try_burn(
     }
     TOTAL_SUPPLY.save(deps.storage, &total_supply)?;
 
-    store_burn(
-        deps.storage,
-        info.sender.clone(),
+    let spent_notification = Notification::new (
         info.sender,
-        amount,
-        constants.symbol,
-        memo,
-        &env.block,
-        &decoys,
-        &account_random_pos,
-    )?;
+        SpentNotificationData {
+            amount: raw_amount,
+            actions: 1,
+            recipient: None,
+            balance: owner_balance,
+        }
+    )
+    .to_txhash_notification(deps.api, &env, secret, Some(NOTIFICATION_BLOCK_SIZE))?;
 
-    Ok(Response::new().set_data(to_binary(&ExecuteAnswer::Burn { status: Success })?))
+    Ok(
+        Response::new()
+            .set_data(to_binary(&ExecuteAnswer::Burn { status: Success })?)
+            .add_attribute_plaintext(
+                spent_notification.id_plaintext(),
+                spent_notification.data_plaintext(),
+            )
+    )
 }
 
 fn perform_transfer(
     store: &mut dyn Storage,
-    from: &Addr,
-    to: &Addr,
+    rng: &mut ContractPrng,
+    from: &CanonicalAddr,
+    to: &CanonicalAddr,
+    sender: &CanonicalAddr,
     amount: u128,
-    decoys: &Option<Vec<Addr>>,
-    account_random_pos: &Option<usize>,
-) -> StdResult<()> {
-    BalancesStore::update_balance(store, from, amount, false, "transfer", &None, &None)?;
-    BalancesStore::update_balance(
+    denom: String,
+    memo: Option<String>,
+    block: &BlockInfo,
+    #[cfg(feature = "gas_tracking")] tracker: &mut GasTracker,
+) -> StdResult<u128> {
+    #[cfg(feature = "gas_tracking")]
+    let mut group1 = tracker.group("perform_transfer.1");
+
+    // first store the tx information in the global append list of txs and get the new tx id
+    let tx_id = store_transfer_action(store, from, sender, to, amount, denom, memo, block)?;
+
+    #[cfg(feature = "gas_tracking")]
+    group1.log("@store_transfer_action");
+
+    // load delayed write buffer
+    let mut dwb = DWB.load(store)?;
+
+    #[cfg(feature = "gas_tracking")]
+    group1.log("DWB.load");
+
+    let transfer_str = "transfer";
+
+    // settle the owner's account
+    let owner_balance = dwb.settle_sender_or_owner_account(
+        store,
+        from,
+        tx_id,
+        amount,
+        transfer_str,
+        #[cfg(feature = "gas_tracking")]
+        tracker,
+    )?;
+
+    // if this is a *_from action, settle the sender's account, too
+    if sender != from {
+        dwb.settle_sender_or_owner_account(
+            store,
+            sender,
+            tx_id,
+            0,
+            transfer_str,
+            #[cfg(feature = "gas_tracking")]
+            tracker,
+        )?;
+    }
+
+    // add the tx info for the recipient to the buffer
+    dwb.add_recipient(
         store,
+        rng,
         to,
+        tx_id,
         amount,
-        true,
-        "transfer",
-        decoys,
-        account_random_pos,
+        #[cfg(feature = "gas_tracking")]
+        tracker,
     )?;
 
-    Ok(())
-}
+    #[cfg(feature = "gas_tracking")]
+    let mut group2 = tracker.group("perform_transfer.2");
 
-fn revoke_permit(deps: DepsMut, info: MessageInfo, permit_name: String) -> StdResult<Response> {
-    RevokedPermits::revoke_permit(
-        deps.storage,
-        PREFIX_REVOKED_PERMITS,
-        info.sender.as_str(),
-        &permit_name,
-    );
+    DWB.save(store, &dwb)?;
+
+    #[cfg(feature = "gas_tracking")]
+    group2.log("DWB.save");
+
+    Ok(owner_balance)
+}
+
+fn perform_mint(
+    store: &mut dyn Storage,
+    rng: &mut ContractPrng,
+    minter: &CanonicalAddr,
+    to: &CanonicalAddr,
+    amount: u128,
+    denom: String,
+    memo: Option<String>,
+    block: &BlockInfo,
+    #[cfg(feature = "gas_tracking")] tracker: &mut GasTracker,
+) -> StdResult<()> {
+    // first store the tx information in the global append list of txs and get the new tx id
+    let tx_id = store_mint_action(store, minter, to, amount, denom, memo, block)?;
+
+    // load delayed write buffer
+    let mut dwb = DWB.load(store)?;
+
+    // if minter is not recipient, settle them
+    if minter != to {
+        dwb.settle_sender_or_owner_account(
+            store,
+            minter,
+            tx_id,
+            0,
+            "mint",
+            #[cfg(feature = "gas_tracking")]
+            tracker,
+        )?;
+    }
+
+    // add the tx info for the recipient to the buffer
+    dwb.add_recipient(
+        store,
+        rng,
+        to,
+        tx_id,
+        amount,
+        #[cfg(feature = "gas_tracking")]
+        tracker,
+    )?;
+
+    DWB.save(store, &dwb)?;
+
+    Ok(())
+}
+
+fn perform_deposit(
+    store: &mut dyn Storage,
+    rng: &mut ContractPrng,
+    to: &CanonicalAddr,
+    amount: u128,
+    denom: String,
+    block: &BlockInfo,
+    #[cfg(feature = "gas_tracking")] tracker: &mut GasTracker,
+) -> StdResult<()> {
+    // first store the tx information in the global append list of txs and get the new tx id
+    let tx_id = store_deposit_action(store, amount, denom, block)?;
+
+    // load delayed write buffer
+    let mut dwb = DWB.load(store)?;
+
+    // add the tx info for the recipient to the buffer
+    dwb.add_recipient(
+        store,
+        rng,
+        to,
+        tx_id,
+        amount,
+        #[cfg(feature = "gas_tracking")]
+        tracker,
+    )?;
+
+    DWB.save(store, &dwb)?;
+
+    Ok(())
+}
+
+fn revoke_permit(deps: DepsMut, info: MessageInfo, permit_name: String) -> StdResult<Response> {
+    RevokedPermits::revoke_permit(
+        deps.storage,
+        PREFIX_REVOKED_PERMITS,
+        info.sender.as_str(),
+        &permit_name,
+    );
 
     Ok(Response::new().set_data(to_binary(&ExecuteAnswer::RevokePermit { status: Success })?))
 }
@@ -2067,15 +3009,16 @@ fn is_valid_symbol(symbol: &str) -> bool {
 mod tests {
     use std::any::Any;
 
-    use cosmwasm_std::testing::*;
     use cosmwasm_std::{
-        from_binary, BlockInfo, ContractInfo, MessageInfo, OwnedDeps, QueryResponse, ReplyOn,
-        SubMsg, Timestamp, TransactionInfo, WasmMsg,
+        from_binary, testing::*, Api, BlockInfo, ContractInfo, MessageInfo, OwnedDeps,
+        QueryResponse, ReplyOn, SubMsg, Timestamp, TransactionInfo, WasmMsg,
     };
     use secret_toolkit::permit::{PermitParams, PermitSignature, PubKey};
 
-    use crate::msg::ResponseStatus;
-    use crate::msg::{InitConfig, InitialBalance};
+    use crate::dwb::TX_NODES_COUNT;
+    use crate::msg::{InitConfig, InitialBalance, ResponseStatus};
+    use crate::state::TX_COUNT;
+    use crate::transaction_history::TxAction;
 
     use super::*;
 
@@ -2301,38 +3244,24 @@ mod tests {
     }
 
     #[test]
-    fn test_total_supply_overflow() {
+    fn test_total_supply_overflow_dwb() {
+        // with this implementation of dwbs the max amount a user can get transferred or minted is u64::MAX
+        // for 18 digit coins, u128 amounts might be stored in the dwb (see `fn add_amount` in dwb.rs)
         let (init_result, _deps) = init_helper(vec![InitialBalance {
             address: "lebron".to_string(),
-            amount: Uint128::new(u128::max_value()),
+            amount: Uint128::new(u64::max_value().into()),
         }]);
         assert!(
             init_result.is_ok(),
             "Init failed: {}",
             init_result.err().unwrap()
         );
-
-        let (init_result, _deps) = init_helper(vec![
-            InitialBalance {
-                address: "lebron".to_string(),
-                amount: Uint128::new(u128::max_value()),
-            },
-            InitialBalance {
-                address: "giannis".to_string(),
-                amount: Uint128::new(1),
-            },
-        ]);
-        let error = extract_error_msg(init_result);
-        assert_eq!(
-            error,
-            "The sum of all initial balances exceeds the maximum possible total supply"
-        );
     }
 
     // Handle tests
 
     #[test]
-    fn test_execute_transfer() {
+    fn test_execute_transfer_dwb() {
         let (init_result, mut deps) = init_helper(vec![InitialBalance {
             address: "bob".to_string(),
             amount: Uint128::new(5000),
@@ -2343,413 +3272,756 @@ mod tests {
             init_result.err().unwrap()
         );
 
+        let tx_nodes_count = TX_NODES_COUNT.load(&deps.storage).unwrap_or_default();
+        // should be 2 because we minted 5000 to bob at initialization
+        assert_eq!(2, tx_nodes_count);
+        let tx_count = TX_COUNT.load(&deps.storage).unwrap_or_default();
+        assert_eq!(1, tx_count); // due to mint
+
         let handle_msg = ExecuteMsg::Transfer {
             recipient: "alice".to_string(),
             amount: Uint128::new(1000),
             memo: None,
-            decoys: None,
-            entropy: None,
+            #[cfg(feature = "gas_evaporation")]
+            gas_target: None,
             padding: None,
         };
         let info = mock_info("bob", &[]);
-
-        let handle_result = execute(deps.as_mut(), mock_env(), info, handle_msg);
+        let mut env = mock_env();
+        env.block.random = Some(Binary::from(&[0u8; 32]));
+        let handle_result = execute(deps.as_mut(), env, info, handle_msg);
 
         let result = handle_result.unwrap();
         assert!(ensure_success(result));
-        let bob_addr = Addr::unchecked("bob".to_string());
-        let alice_addr = Addr::unchecked("alice".to_string());
-
-        assert_eq!(5000 - 1000, BalancesStore::load(&deps.storage, &bob_addr));
-        assert_eq!(1000, BalancesStore::load(&deps.storage, &alice_addr));
+        let bob_addr = deps
+            .api
+            .addr_canonicalize(Addr::unchecked("bob").as_str())
+            .unwrap();
+        let alice_addr = deps
+            .api
+            .addr_canonicalize(Addr::unchecked("alice").as_str())
+            .unwrap();
 
+        assert_eq!(
+            5000 - 1000,
+            stored_balance(&deps.storage, &bob_addr).unwrap()
+        );
+        // alice has not been settled yet
+        assert_ne!(1000, stored_balance(&deps.storage, &alice_addr).unwrap());
+
+        let dwb = DWB.load(&deps.storage).unwrap();
+        println!("DWB: {dwb:?}");
+        // assert we have decremented empty_space_counter
+        assert_eq!(62, dwb.empty_space_counter);
+        // assert first entry has correct information for alice
+        let alice_entry = dwb.entries[2];
+        assert_eq!(1, alice_entry.list_len().unwrap());
+        assert_eq!(1000, alice_entry.amount().unwrap());
+        // the id of the head_node
+        assert_eq!(4, alice_entry.head_node().unwrap());
+        let tx_count = TX_COUNT.load(&deps.storage).unwrap_or_default();
+        assert_eq!(2, tx_count);
+
+        // now send 100 to charlie from bob
         let handle_msg = ExecuteMsg::Transfer {
-            recipient: "alice".to_string(),
-            amount: Uint128::new(10000),
+            recipient: "charlie".to_string(),
+            amount: Uint128::new(100),
             memo: None,
-            decoys: None,
-            entropy: None,
+            #[cfg(feature = "gas_evaporation")]
+            gas_target: None,
             padding: None,
         };
         let info = mock_info("bob", &[]);
 
-        let handle_result = execute(deps.as_mut(), mock_env(), info, handle_msg);
-
-        let error = extract_error_msg(handle_result);
-        assert!(error.contains("insufficient funds"));
-    }
-
-    #[test]
-    fn test_decoys_balance_stays_on_transfer() {
-        let (init_result, mut deps) = init_helper(vec![
-            InitialBalance {
-                address: "bob".to_string(),
-                amount: Uint128::new(5000),
-            },
-            InitialBalance {
-                address: "lior".to_string(),
-                amount: Uint128::new(7000),
-            },
-        ]);
-
-        assert!(
-            init_result.is_ok(),
-            "Init failed: {}",
-            init_result.err().unwrap()
-        );
-
-        let bob_addr = Addr::unchecked("bob".to_string());
-        let alice_addr = Addr::unchecked("alice".to_string());
-        let lior_addr = Addr::unchecked("lior".to_string());
-        let jhon_addr = Addr::unchecked("jhon".to_string());
+        let mut env = mock_env();
+        env.block.random = Some(Binary::from(&[1u8; 32]));
+        let handle_result = execute(deps.as_mut(), env, info, handle_msg);
 
-        let bob_balance = BalancesStore::load(&deps.storage, &bob_addr);
-        let alice_balance = BalancesStore::load(&deps.storage, &alice_addr);
-        let lior_balance = BalancesStore::load(&deps.storage, &lior_addr);
-        let jhon_balance = BalancesStore::load(&deps.storage, &jhon_addr);
+        let result = handle_result.unwrap();
+        assert!(ensure_success(result));
+        let charlie_addr = deps
+            .api
+            .addr_canonicalize(Addr::unchecked("charlie").as_str())
+            .unwrap();
 
+        assert_eq!(
+            5000 - 1000 - 100,
+            stored_balance(&deps.storage, &bob_addr).unwrap()
+        );
+        // alice has not been settled yet
+        assert_ne!(1000, stored_balance(&deps.storage, &alice_addr).unwrap());
+        // charlie has not been settled yet
+        assert_ne!(100, stored_balance(&deps.storage, &charlie_addr).unwrap());
+
+        let dwb = DWB.load(&deps.storage).unwrap();
+        //println!("DWB: {dwb:?}");
+        // assert we have decremented empty_space_counter
+        assert_eq!(61, dwb.empty_space_counter);
+        // assert entry has correct information for charlie
+        let charlie_entry = dwb.entries[3];
+        assert_eq!(1, charlie_entry.list_len().unwrap());
+        assert_eq!(100, charlie_entry.amount().unwrap());
+        // the id of the head_node
+        assert_eq!(6, charlie_entry.head_node().unwrap());
+        let tx_count = TX_COUNT.load(&deps.storage).unwrap_or_default();
+        assert_eq!(3, tx_count);
+
+        // send another 500 to alice from bob
         let handle_msg = ExecuteMsg::Transfer {
             recipient: "alice".to_string(),
-            amount: Uint128::new(1000),
+            amount: Uint128::new(500),
             memo: None,
-            decoys: Some(vec![lior_addr.clone(), jhon_addr.clone()]),
-            entropy: Some(Binary::from_base64("VEVTVFRFU1RURVNUQ0hFQ0tDSEVDSw==").unwrap()),
+            #[cfg(feature = "gas_evaporation")]
+            gas_target: None,
             padding: None,
         };
-
         let info = mock_info("bob", &[]);
-
-        let handle_result = execute(deps.as_mut(), mock_env(), info, handle_msg);
+        let mut env = mock_env();
+        env.block.random = Some(Binary::from(&[2u8; 32]));
+        let handle_result = execute(deps.as_mut(), env, info, handle_msg);
 
         let result = handle_result.unwrap();
         assert!(ensure_success(result));
 
         assert_eq!(
-            bob_balance - 1000,
-            BalancesStore::load(&deps.storage, &bob_addr)
-        );
-        assert_eq!(
-            alice_balance + 1000,
-            BalancesStore::load(&deps.storage, &alice_addr)
-        );
-        assert_eq!(lior_balance, BalancesStore::load(&deps.storage, &lior_addr));
-        assert_eq!(jhon_balance, BalancesStore::load(&deps.storage, &jhon_addr));
-    }
+            5000 - 1000 - 100 - 500,
+            stored_balance(&deps.storage, &bob_addr).unwrap()
+        );
+        // make sure alice has not been settled yet
+        assert_ne!(1500, stored_balance(&deps.storage, &alice_addr).unwrap());
+
+        let dwb = DWB.load(&deps.storage).unwrap();
+        //println!("DWB: {dwb:?}");
+        // assert we have not decremented empty_space_counter
+        assert_eq!(61, dwb.empty_space_counter);
+        // assert entry has correct information for alice
+        let alice_entry = dwb.entries[2];
+        assert_eq!(2, alice_entry.list_len().unwrap());
+        assert_eq!(1500, alice_entry.amount().unwrap());
+        // the id of the head_node
+        assert_eq!(8, alice_entry.head_node().unwrap());
+        let tx_count = TX_COUNT.load(&deps.storage).unwrap_or_default();
+        assert_eq!(4, tx_count);
+
+        // convert head_node to vec
+        let alice_nodes = TX_NODES
+            .add_suffix(&alice_entry.head_node().unwrap().to_be_bytes())
+            .load(&deps.storage)
+            .unwrap()
+            .to_vec(&deps.storage, &deps.api)
+            .unwrap();
 
-    #[test]
-    fn test_handle_send() {
-        let (init_result, mut deps) = init_helper(vec![InitialBalance {
-            address: "bob".to_string(),
-            amount: Uint128::new(5000),
-        }]);
-        assert!(
-            init_result.is_ok(),
-            "Init failed: {}",
-            init_result.err().unwrap()
-        );
+        let expected_alice_nodes: Vec<Tx> = vec![
+            Tx {
+                id: 4,
+                action: TxAction::Transfer {
+                    from: Addr::unchecked("bob"),
+                    sender: Addr::unchecked("bob"),
+                    recipient: Addr::unchecked("alice"),
+                },
+                coins: Coin {
+                    amount: Uint128::from(500_u128),
+                    denom: "SECSEC".to_string(),
+                },
+                memo: None,
+                block_time: 1571797419,
+                block_height: 12345,
+            },
+            Tx {
+                id: 2,
+                action: TxAction::Transfer {
+                    from: Addr::unchecked("bob"),
+                    sender: Addr::unchecked("bob"),
+                    recipient: Addr::unchecked("alice"),
+                },
+                coins: Coin {
+                    amount: Uint128::from(1000_u128),
+                    denom: "SECSEC".to_string(),
+                },
+                memo: None,
+                block_time: 1571797419,
+                block_height: 12345,
+            },
+        ];
+        assert_eq!(alice_nodes, expected_alice_nodes);
 
-        let handle_msg = ExecuteMsg::RegisterReceive {
-            code_hash: "this_is_a_hash_of_a_code".to_string(),
+        // now send 200 to ernie from bob
+        let handle_msg = ExecuteMsg::Transfer {
+            recipient: "ernie".to_string(),
+            amount: Uint128::new(200),
+            memo: None,
+            #[cfg(feature = "gas_evaporation")]
+            gas_target: None,
             padding: None,
         };
-        let info = mock_info("contract", &[]);
+        let info = mock_info("bob", &[]);
 
-        let handle_result = execute(deps.as_mut(), mock_env(), info, handle_msg);
+        let mut env = mock_env();
+        env.block.random = Some(Binary::from(&[3u8; 32]));
+        let handle_result = execute(deps.as_mut(), env, info, handle_msg);
 
         let result = handle_result.unwrap();
         assert!(ensure_success(result));
+        let ernie_addr = deps
+            .api
+            .addr_canonicalize(Addr::unchecked("ernie").as_str())
+            .unwrap();
 
-        let handle_msg = ExecuteMsg::Send {
-            recipient: "contract".to_string(),
-            recipient_code_hash: None,
-            amount: Uint128::new(100),
-            memo: Some("my memo".to_string()),
+        assert_eq!(
+            5000 - 1000 - 100 - 500 - 200,
+            stored_balance(&deps.storage, &bob_addr).unwrap()
+        );
+        // alice has not been settled yet
+        assert_ne!(1500, stored_balance(&deps.storage, &alice_addr).unwrap());
+        // charlie has not been settled yet
+        assert_ne!(100, stored_balance(&deps.storage, &charlie_addr).unwrap());
+        // ernie has not been settled yet
+        assert_ne!(200, stored_balance(&deps.storage, &ernie_addr).unwrap());
+
+        let dwb = DWB.load(&deps.storage).unwrap();
+        //println!("DWB: {dwb:?}");
+
+        // assert we have decremented empty_space_counter
+        assert_eq!(60, dwb.empty_space_counter);
+        // assert entry has correct information for ernie
+        let ernie_entry = dwb.entries[4];
+        assert_eq!(1, ernie_entry.list_len().unwrap());
+        assert_eq!(200, ernie_entry.amount().unwrap());
+        // the id of the head_node
+        assert_eq!(10, ernie_entry.head_node().unwrap());
+        let tx_count = TX_COUNT.load(&deps.storage).unwrap_or_default();
+        assert_eq!(5, tx_count);
+
+        // now alice sends 50 to dora
+        // this should settle alice and create entry for dora
+        let handle_msg = ExecuteMsg::Transfer {
+            recipient: "dora".to_string(),
+            amount: Uint128::new(50),
+            memo: None,
+            #[cfg(feature = "gas_evaporation")]
+            gas_target: None,
             padding: None,
-            msg: Some(to_binary("hey hey you you").unwrap()),
-            decoys: None,
-            entropy: None,
         };
-        let info = mock_info("bob", &[]);
-
-        let handle_result = execute(deps.as_mut(), mock_env(), info, handle_msg);
+        let info = mock_info("alice", &[]);
+        let mut env = mock_env();
+        env.block.random = Some(Binary::from(&[4u8; 32]));
+        let handle_result = execute(deps.as_mut(), env, info, handle_msg);
 
         let result = handle_result.unwrap();
-        assert!(ensure_success(result.clone()));
-        let id = 0;
-        assert!(result.messages.contains(&SubMsg {
-            id,
-            msg: CosmosMsg::Wasm(WasmMsg::Execute {
-                contract_addr: "contract".to_string(),
-                code_hash: "this_is_a_hash_of_a_code".to_string(),
-                msg: Snip20ReceiveMsg::new(
-                    Addr::unchecked("bob".to_string()),
-                    Addr::unchecked("bob".to_string()),
-                    Uint128::new(100),
-                    Some("my memo".to_string()),
-                    Some(to_binary("hey hey you you").unwrap())
-                )
-                .into_binary()
-                .unwrap(),
-                funds: vec![],
-            })
-            .into(),
-            reply_on: match id {
-                0 => ReplyOn::Never,
-                _ => ReplyOn::Always,
-            },
-            gas_limit: None,
-        }));
-    }
+        assert!(ensure_success(result));
+        let dora_addr = deps
+            .api
+            .addr_canonicalize(Addr::unchecked("dora").as_str())
+            .unwrap();
 
-    #[test]
-    fn test_handle_register_receive() {
-        let (init_result, mut deps) = init_helper(vec![InitialBalance {
-            address: "bob".to_string(),
-            amount: Uint128::new(5000),
-        }]);
-        assert!(
-            init_result.is_ok(),
-            "Init failed: {}",
-            init_result.err().unwrap()
+        // alice has been settled
+        assert_eq!(
+            1500 - 50,
+            stored_balance(&deps.storage, &alice_addr).unwrap()
+        );
+        // dora has not been settled
+        assert_ne!(50, stored_balance(&deps.storage, &dora_addr).unwrap());
+
+        let dwb = DWB.load(&deps.storage).unwrap();
+        //println!("DWB: {dwb:?}");
+
+        // assert we have decremented empty_space_counter
+        assert_eq!(59, dwb.empty_space_counter);
+        // assert entry has correct information for ernie
+        let dora_entry = dwb.entries[5];
+        assert_eq!(1, dora_entry.list_len().unwrap());
+        assert_eq!(50, dora_entry.amount().unwrap());
+        // the id of the head_node
+        assert_eq!(12, dora_entry.head_node().unwrap());
+        let tx_count = TX_COUNT.load(&deps.storage).unwrap_or_default();
+        assert_eq!(6, tx_count);
+
+        // now we will send to 60 more addresses to fill up the buffer
+        for i in 1..=59 {
+            let recipient = format!("receipient{i}");
+            // now send 1 to recipient from bob
+            let handle_msg = ExecuteMsg::Transfer {
+                recipient,
+                amount: Uint128::new(1),
+                memo: None,
+                #[cfg(feature = "gas_evaporation")]
+                gas_target: None,
+                padding: None,
+            };
+            let info = mock_info("bob", &[]);
+            let mut env = mock_env();
+            env.block.random = Some(Binary::from(&[255 - i; 32]));
+            let handle_result = execute(deps.as_mut(), env, info, handle_msg);
+
+            let result = handle_result.unwrap();
+            assert!(ensure_success(result));
+        }
+        assert_eq!(
+            5000 - 1000 - 100 - 500 - 200 - 59,
+            stored_balance(&deps.storage, &bob_addr).unwrap()
         );
 
-        let handle_msg = ExecuteMsg::RegisterReceive {
-            code_hash: "this_is_a_hash_of_a_code".to_string(),
+        let dwb = DWB.load(&deps.storage).unwrap();
+        //println!("DWB: {dwb:?}");
+
+        // assert we have filled the buffer
+        assert_eq!(0, dwb.empty_space_counter);
+
+        let recipient = format!("receipient_over");
+        // now send 1 to recipient from bob
+        let handle_msg = ExecuteMsg::Transfer {
+            recipient,
+            amount: Uint128::new(1),
+            memo: None,
+            #[cfg(feature = "gas_evaporation")]
+            gas_target: None,
             padding: None,
         };
-        let info = mock_info("contract", &[]);
-
-        let handle_result = execute(deps.as_mut(), mock_env(), info, handle_msg);
+        let info = mock_info("bob", &[]);
+        let mut env = mock_env();
+        env.block.random = Some(Binary::from(&[50; 32]));
+        let handle_result = execute(deps.as_mut(), env, info, handle_msg);
 
         let result = handle_result.unwrap();
         assert!(ensure_success(result));
 
-        let hash =
-            ReceiverHashStore::may_load(&deps.storage, &Addr::unchecked("contract".to_string()))
-                .unwrap()
-                .unwrap();
-        assert_eq!(hash, "this_is_a_hash_of_a_code".to_string());
-    }
-
-    #[test]
-    fn test_handle_create_viewing_key() {
-        let (init_result, mut deps) = init_helper(vec![InitialBalance {
-            address: "bob".to_string(),
-            amount: Uint128::new(5000),
-        }]);
-        assert!(
-            init_result.is_ok(),
-            "Init failed: {}",
-            init_result.err().unwrap()
+        assert_eq!(
+            5000 - 1000 - 100 - 500 - 200 - 59 - 1,
+            stored_balance(&deps.storage, &bob_addr).unwrap()
         );
 
-        let handle_msg = ExecuteMsg::CreateViewingKey {
-            entropy: "".to_string(),
+        //let dwb = DWB.load(&deps.storage).unwrap();
+        //println!("DWB: {dwb:?}");
+
+        let recipient = format!("receipient_over_2");
+        // now send 1 to recipient from bob
+        let handle_msg = ExecuteMsg::Transfer {
+            recipient,
+            amount: Uint128::new(1),
+            memo: None,
+            #[cfg(feature = "gas_evaporation")]
+            gas_target: None,
             padding: None,
         };
         let info = mock_info("bob", &[]);
+        let mut env = mock_env();
+        env.block.random = Some(Binary::from(&[12; 32]));
+        let handle_result = execute(deps.as_mut(), env, info, handle_msg);
 
-        let handle_result = execute(deps.as_mut(), mock_env(), info, handle_msg);
+        let result = handle_result.unwrap();
+        assert!(ensure_success(result));
 
-        assert!(
-            handle_result.is_ok(),
-            "handle() failed: {}",
-            handle_result.err().unwrap()
+        assert_eq!(
+            5000 - 1000 - 100 - 500 - 200 - 59 - 1 - 1,
+            stored_balance(&deps.storage, &bob_addr).unwrap()
         );
-        let answer: ExecuteAnswer = from_binary(&handle_result.unwrap().data.unwrap()).unwrap();
 
-        let key = match answer {
-            ExecuteAnswer::CreateViewingKey { key } => key,
-            _ => panic!("NOPE"),
-        };
-        // let bob_canonical = deps.as_mut().api.addr_canonicalize("bob").unwrap();
+        //let dwb = DWB.load(&deps.storage).unwrap();
+        //println!("DWB: {dwb:?}");
 
-        let result = ViewingKey::check(&deps.storage, "bob", key.as_str());
-        assert!(result.is_ok());
+        // now we send 50 transactions to alice from bob
+        for i in 1..=50 {
+            // send 1 to alice from bob
+            let handle_msg = ExecuteMsg::Transfer {
+                recipient: "alice".to_string(),
+                amount: Uint128::new(i.into()),
+                memo: None,
+                #[cfg(feature = "gas_evaporation")]
+                gas_target: None,
+                padding: None,
+            };
 
-        // let saved_vk = read_viewing_key(&deps.storage, &bob_canonical).unwrap();
-        // assert!(key.check_viewing_key(saved_vk.as_slice()));
-    }
+            let info = mock_info("bob", &[]);
+            let mut env = mock_env();
+            env.block.random = Some(Binary::from(&[125 - i; 32]));
+            let handle_result = execute(deps.as_mut(), env, info, handle_msg);
 
-    #[test]
-    fn test_handle_set_viewing_key() {
-        let (init_result, mut deps) = init_helper(vec![InitialBalance {
-            address: "bob".to_string(),
-            amount: Uint128::new(5000),
-        }]);
-        assert!(
-            init_result.is_ok(),
-            "Init failed: {}",
-            init_result.err().unwrap()
-        );
+            let result = handle_result.unwrap();
+            assert!(ensure_success(result));
 
-        // Set VK
-        let handle_msg = ExecuteMsg::SetViewingKey {
-            key: "hi lol".to_string(),
+            // alice should not settle
+            assert_eq!(
+                1500 - 50,
+                stored_balance(&deps.storage, &alice_addr).unwrap()
+            );
+        }
+
+        // alice sends 1 to dora to settle
+        // this should settle alice and create entry for dora
+        let handle_msg = ExecuteMsg::Transfer {
+            recipient: "dora".to_string(),
+            amount: Uint128::new(1),
+            memo: None,
+            #[cfg(feature = "gas_evaporation")]
+            gas_target: None,
             padding: None,
         };
-        let info = mock_info("bob", &[]);
+        let info = mock_info("alice", &[]);
+        let mut env = mock_env();
+        env.block.random = Some(Binary::from(&[61; 32]));
+        let handle_result = execute(deps.as_mut(), env, info, handle_msg);
 
-        let handle_result = execute(deps.as_mut(), mock_env(), info, handle_msg);
+        let result = handle_result.unwrap();
+        assert!(ensure_success(result));
 
-        let unwrapped_result: ExecuteAnswer =
-            from_binary(&handle_result.unwrap().data.unwrap()).unwrap();
-        assert_eq!(
-            to_binary(&unwrapped_result).unwrap(),
-            to_binary(&ExecuteAnswer::SetViewingKey {
-                status: ResponseStatus::Success
-            })
-            .unwrap(),
-        );
+        assert_eq!(2724, stored_balance(&deps.storage, &alice_addr).unwrap());
 
-        // Set valid VK
-        let actual_vk = "x".to_string().repeat(VIEWING_KEY_SIZE);
-        let handle_msg = ExecuteMsg::SetViewingKey {
-            key: actual_vk.clone(),
-            padding: None,
-        };
-        let info = mock_info("bob", &[]);
+        // now we send 50 more transactions to alice from bob
+        for i in 1..=50 {
+            // send 1 to alice from bob
+            let handle_msg = ExecuteMsg::Transfer {
+                recipient: "alice".to_string(),
+                amount: Uint128::new(i.into()),
+                memo: None,
+                #[cfg(feature = "gas_evaporation")]
+                gas_target: None,
+                padding: None,
+            };
 
-        let handle_result = execute(deps.as_mut(), mock_env(), info, handle_msg);
+            let info = mock_info("bob", &[]);
+            let mut env = mock_env();
+            env.block.random = Some(Binary::from(&[200 - i; 32]));
+            let handle_result = execute(deps.as_mut(), env, info, handle_msg);
 
-        let unwrapped_result: ExecuteAnswer =
-            from_binary(&handle_result.unwrap().data.unwrap()).unwrap();
-        assert_eq!(
-            to_binary(&unwrapped_result).unwrap(),
-            to_binary(&ExecuteAnswer::SetViewingKey { status: Success }).unwrap(),
-        );
+            let result = handle_result.unwrap();
+            assert!(ensure_success(result));
 
-        let result = ViewingKey::check(&deps.storage, "bob", actual_vk.as_str());
-        assert!(result.is_ok());
-    }
+            // alice should not settle
+            assert_eq!(2724, stored_balance(&deps.storage, &alice_addr).unwrap());
+        }
 
-    fn revoke_permit(
-        permit_name: &str,
-        user_address: &str,
-        deps: &mut OwnedDeps<cosmwasm_std::MemoryStorage, MockApi, MockQuerier>,
-    ) -> Result<Response, StdError> {
-        let handle_msg = ExecuteMsg::RevokePermit {
-            permit_name: permit_name.to_string(),
+        let handle_msg = ExecuteMsg::SetViewingKey {
+            key: "key".to_string(),
+            #[cfg(feature = "gas_evaporation")]
+            gas_target: None,
             padding: None,
         };
-        let info = mock_info(user_address, &[]);
+        let info = mock_info("alice", &[]);
+
         let handle_result = execute(deps.as_mut(), mock_env(), info, handle_msg);
-        handle_result
-    }
+        let result = handle_result.unwrap();
+        assert!(ensure_success(result));
 
-    fn get_balance_with_permit_qry_msg(
-        permit_name: &str,
-        chain_id: &str,
-        pub_key_value: &str,
-        signature: &str,
-    ) -> QueryMsg {
-        let permit = gen_permit_obj(
-            permit_name,
-            chain_id,
-            pub_key_value,
-            signature,
-            TokenPermissions::Balance,
-        );
+        // check that alice's balance when queried is correct (includes both settled and dwb amounts)
+        // settled = 2724
+        // dwb = 1275
+        // total should be = 3999
+        let query_msg = QueryMsg::Balance {
+            address: "alice".to_string(),
+            key: "key".to_string(),
+        };
+        let query_result = query(deps.as_ref(), mock_env(), query_msg);
+        let balance = match from_binary(&query_result.unwrap()).unwrap() {
+            QueryAnswer::Balance { amount } => amount,
+            _ => panic!("Unexpected"),
+        };
+        assert_eq!(balance, Uint128::new(3999));
 
-        QueryMsg::WithPermit {
-            permit,
-            query: QueryWithPermit::Balance {},
-        }
-    }
+        // now we use alice to check query transaction history pagination works
 
-    fn gen_permit_obj(
-        permit_name: &str,
-        chain_id: &str,
-        pub_key_value: &str,
-        signature: &str,
-        permit_type: TokenPermissions,
-    ) -> Permit {
-        let permit: Permit = Permit {
-            params: PermitParams {
-                allowed_tokens: vec![MOCK_CONTRACT_ADDR.to_string()],
-                permit_name: permit_name.to_string(),
-                chain_id: chain_id.to_string(),
-                permissions: vec![permit_type],
+        //
+        // check last 3 transactions for alice (all in dwb)
+        //
+        let query_msg = QueryMsg::TransactionHistory {
+            address: "alice".to_string(),
+            key: "key".to_string(),
+            page: None,
+            page_size: 3,
+        };
+        let query_result = query(deps.as_ref(), mock_env(), query_msg);
+        let transfers = match from_binary(&query_result.unwrap()).unwrap() {
+            QueryAnswer::TransactionHistory { txs, .. } => txs,
+            other => panic!("Unexpected: {:?}", other),
+        };
+        //println!("transfers: {transfers:?}");
+        let expected_transfers = vec![
+            Tx {
+                id: 168,
+                action: TxAction::Transfer {
+                    from: Addr::unchecked("bob"),
+                    sender: Addr::unchecked("bob"),
+                    recipient: Addr::unchecked("alice"),
+                },
+                coins: Coin {
+                    denom: "SECSEC".to_string(),
+                    amount: Uint128::from(50u128),
+                },
+                memo: None,
+                block_time: 1571797419,
+                block_height: 12345,
             },
-            signature: PermitSignature {
-                pub_key: PubKey {
-                    r#type: "tendermint/PubKeySecp256k1".to_string(),
-                    value: Binary::from_base64(pub_key_value).unwrap(),
+            Tx {
+                id: 167,
+                action: TxAction::Transfer {
+                    from: Addr::unchecked("bob"),
+                    sender: Addr::unchecked("bob"),
+                    recipient: Addr::unchecked("alice"),
                 },
-                signature: Binary::from_base64(signature).unwrap(),
+                coins: Coin {
+                    denom: "SECSEC".to_string(),
+                    amount: Uint128::from(49u128),
+                },
+                memo: None,
+                block_time: 1571797419,
+                block_height: 12345,
             },
-        };
-        permit
-    }
-
-    fn get_allowances_given_permit(
-        permit_name: &str,
-        chain_id: &str,
-        pub_key_value: &str,
-        signature: &str,
-        spender: String,
-    ) -> QueryMsg {
-        let permit = gen_permit_obj(
-            permit_name,
-            chain_id,
-            pub_key_value,
-            signature,
-            TokenPermissions::Owner,
-        );
-
-        QueryMsg::WithPermit {
-            permit,
-            query: QueryWithPermit::AllowancesReceived {
-                spender,
-                page: None,
-                page_size: 0,
+            Tx {
+                id: 166,
+                action: TxAction::Transfer {
+                    from: Addr::unchecked("bob"),
+                    sender: Addr::unchecked("bob"),
+                    recipient: Addr::unchecked("alice"),
+                },
+                coins: Coin {
+                    denom: "SECSEC".to_string(),
+                    amount: Uint128::from(48u128),
+                },
+                memo: None,
+                block_time: 1571797419,
+                block_height: 12345,
             },
-        }
-    }
+        ];
+        assert_eq!(transfers, expected_transfers);
 
-    #[test]
-    fn test_permit_query_allowances_given_should_fail() {
-        let user_address = "secret18mdrja40gfuftt5yx6tgj0fn5lurplezyp894y";
-        let permit_name = "default";
-        let chain_id = "secretdev-1";
-        let pub_key = "AkZqxdKMtPq2w0kGDGwWGejTAed0H7azPMHtrCX0XYZG";
-        let signature = "ZXyFMlAy6guMG9Gj05rFvcMi5/JGfClRtJpVTHiDtQY3GtSfBHncY70kmYiTXkKIxSxdnh/kS8oXa+GSX5su6Q==";
+        //
+        // check 6 transactions for alice that span over end of the 50 in dwb and settled
+        // page: 8, page size: 6
+        // start is index 48
+        //
+        let query_msg = QueryMsg::TransactionHistory {
+            address: "alice".to_string(),
+            key: "key".to_string(),
+            page: Some(8),
+            page_size: 6,
+        };
+        let query_result = query(deps.as_ref(), mock_env(), query_msg);
+        let transfers = match from_binary(&query_result.unwrap()).unwrap() {
+            QueryAnswer::TransactionHistory { txs, .. } => txs,
+            other => panic!("Unexpected: {:?}", other),
+        };
+        //println!("transfers: {transfers:?}");
+        let expected_transfers = vec![
+            Tx {
+                id: 120,
+                action: TxAction::Transfer {
+                    from: Addr::unchecked("bob"),
+                    sender: Addr::unchecked("bob"),
+                    recipient: Addr::unchecked("alice"),
+                },
+                coins: Coin {
+                    denom: "SECSEC".to_string(),
+                    amount: Uint128::from(2u128),
+                },
+                memo: None,
+                block_time: 1571797419,
+                block_height: 12345,
+            },
+            Tx {
+                id: 119,
+                action: TxAction::Transfer {
+                    from: Addr::unchecked("bob"),
+                    sender: Addr::unchecked("bob"),
+                    recipient: Addr::unchecked("alice"),
+                },
+                coins: Coin {
+                    denom: "SECSEC".to_string(),
+                    amount: Uint128::from(1u128),
+                },
+                memo: None,
+                block_time: 1571797419,
+                block_height: 12345,
+            },
+            Tx {
+                id: 118,
+                action: TxAction::Transfer {
+                    from: Addr::unchecked("alice"),
+                    sender: Addr::unchecked("alice"),
+                    recipient: Addr::unchecked("dora"),
+                },
+                coins: Coin {
+                    denom: "SECSEC".to_string(),
+                    amount: Uint128::from(1u128),
+                },
+                memo: None,
+                block_time: 1571797419,
+                block_height: 12345,
+            },
+            Tx {
+                id: 117,
+                action: TxAction::Transfer {
+                    from: Addr::unchecked("bob"),
+                    sender: Addr::unchecked("bob"),
+                    recipient: Addr::unchecked("alice"),
+                },
+                coins: Coin {
+                    denom: "SECSEC".to_string(),
+                    amount: Uint128::from(50u128),
+                },
+                memo: None,
+                block_time: 1571797419,
+                block_height: 12345,
+            },
+            Tx {
+                id: 116,
+                action: TxAction::Transfer {
+                    from: Addr::unchecked("bob"),
+                    sender: Addr::unchecked("bob"),
+                    recipient: Addr::unchecked("alice"),
+                },
+                coins: Coin {
+                    denom: "SECSEC".to_string(),
+                    amount: Uint128::from(49u128),
+                },
+                memo: None,
+                block_time: 1571797419,
+                block_height: 12345,
+            },
+            Tx {
+                id: 115,
+                action: TxAction::Transfer {
+                    from: Addr::unchecked("bob"),
+                    sender: Addr::unchecked("bob"),
+                    recipient: Addr::unchecked("alice"),
+                },
+                coins: Coin {
+                    denom: "SECSEC".to_string(),
+                    amount: Uint128::from(48u128),
+                },
+                memo: None,
+                block_time: 1571797419,
+                block_height: 12345,
+            },
+        ];
+        assert_eq!(transfers, expected_transfers);
 
-        // Init the contract
-        let (init_result, deps) = init_helper(vec![InitialBalance {
-            address: user_address.to_string(),
-            amount: Uint128::new(50000000),
-        }]);
-        assert!(
-            init_result.is_ok(),
-            "Init failed: {}",
-            init_result.err().unwrap()
-        );
+        //
+        // check transactions for alice, starting in settled across different bundles with `end` past the last transaction
+        // there are 104 transactions total for alice
+        // page: 3, page size: 99
+        // start is index 99 (100th tx)
+        //
+        let query_msg = QueryMsg::TransactionHistory {
+            address: "alice".to_string(),
+            key: "key".to_string(),
+            page: Some(3),
+            page_size: 33,
+            //page: None,
+            //page_size: 500,
+        };
+        let query_result = query(deps.as_ref(), mock_env(), query_msg);
+        let transfers = match from_binary(&query_result.unwrap()).unwrap() {
+            QueryAnswer::TransactionHistory { txs, .. } => txs,
+            other => panic!("Unexpected: {:?}", other),
+        };
+        //println!("transfers: {transfers:?}");
+        let expected_transfers = vec![
+            Tx {
+                id: 69,
+                action: TxAction::Transfer {
+                    from: Addr::unchecked("bob"),
+                    sender: Addr::unchecked("bob"),
+                    recipient: Addr::unchecked("alice"),
+                },
+                coins: Coin {
+                    denom: "SECSEC".to_string(),
+                    amount: Uint128::from(2u128),
+                },
+                memo: None,
+                block_time: 1571797419,
+                block_height: 12345,
+            },
+            Tx {
+                id: 68,
+                action: TxAction::Transfer {
+                    from: Addr::unchecked("bob"),
+                    sender: Addr::unchecked("bob"),
+                    recipient: Addr::unchecked("alice"),
+                },
+                coins: Coin {
+                    denom: "SECSEC".to_string(),
+                    amount: Uint128::from(1u128),
+                },
+                memo: None,
+                block_time: 1571797419,
+                block_height: 12345,
+            },
+            Tx {
+                id: 6,
+                action: TxAction::Transfer {
+                    from: Addr::unchecked("alice"),
+                    sender: Addr::unchecked("alice"),
+                    recipient: Addr::unchecked("dora"),
+                },
+                coins: Coin {
+                    denom: "SECSEC".to_string(),
+                    amount: Uint128::from(50u128),
+                },
+                memo: None,
+                block_time: 1571797419,
+                block_height: 12345,
+            },
+            Tx {
+                id: 4,
+                action: TxAction::Transfer {
+                    from: Addr::unchecked("bob"),
+                    sender: Addr::unchecked("bob"),
+                    recipient: Addr::unchecked("alice"),
+                },
+                coins: Coin {
+                    denom: "SECSEC".to_string(),
+                    amount: Uint128::from(500u128),
+                },
+                memo: None,
+                block_time: 1571797419,
+                block_height: 12345,
+            },
+            Tx {
+                id: 2,
+                action: TxAction::Transfer {
+                    from: Addr::unchecked("bob"),
+                    sender: Addr::unchecked("bob"),
+                    recipient: Addr::unchecked("alice"),
+                },
+                coins: Coin {
+                    denom: "SECSEC".to_string(),
+                    amount: Uint128::from(1000u128),
+                },
+                memo: None,
+                block_time: 1571797419,
+                block_height: 12345,
+            },
+        ];
+        //let transfers_len = transfers.len();
+        //println!("transfers.len(): {transfers_len}");
+        assert_eq!(transfers, expected_transfers);
 
-        let msg = get_allowances_given_permit(
-            permit_name,
-            chain_id,
-            pub_key,
-            signature,
-            "secret1kmgdagt5efcz2kku0ak9ezfgntg29g2vr88q0e".to_string(),
-        );
-        let query_result = query(deps.as_ref(), mock_env(), msg);
+        //
+        //
+        //
+        //
 
-        assert_eq!(query_result.is_err(), true);
+        // now try invalid transfer
+        let handle_msg = ExecuteMsg::Transfer {
+            recipient: "alice".to_string(),
+            amount: Uint128::new(10000),
+            memo: None,
+            #[cfg(feature = "gas_evaporation")]
+            gas_target: None,
+            padding: None,
+        };
+        let info = mock_info("bob", &[]);
+
+        let handle_result = execute(deps.as_mut(), mock_env(), info, handle_msg);
+
+        let error = extract_error_msg(handle_result);
+        assert!(error.contains("insufficient funds"));
     }
 
     #[test]
-    fn test_permit_query_allowances_given() {
-        let user_address = "secret18mdrja40gfuftt5yx6tgj0fn5lurplezyp894y";
-        let permit_name = "default";
-        let chain_id = "secretdev-1";
-        let pub_key = "AkZqxdKMtPq2w0kGDGwWGejTAed0H7azPMHtrCX0XYZG";
-        let signature = "ZXyFMlAy6guMG9Gj05rFvcMi5/JGfClRtJpVTHiDtQY3GtSfBHncY70kmYiTXkKIxSxdnh/kS8oXa+GSX5su6Q==";
-
-        // Init the contract
-        let (init_result, deps) = init_helper(vec![InitialBalance {
-            address: user_address.to_string(),
-            amount: Uint128::new(50000000),
+    fn test_handle_send() {
+        let (init_result, mut deps) = init_helper(vec![InitialBalance {
+            address: "bob".to_string(),
+            amount: Uint128::new(5000),
         }]);
         assert!(
             init_result.is_ok(),
@@ -2757,33 +4029,66 @@ mod tests {
             init_result.err().unwrap()
         );
 
-        let msg = get_allowances_given_permit(
-            permit_name,
-            chain_id,
-            pub_key,
-            signature,
-            "secret18mdrja40gfuftt5yx6tgj0fn5lurplezyp894y".to_string(),
-        );
-        let query_result = query(deps.as_ref(), mock_env(), msg);
+        let handle_msg = ExecuteMsg::RegisterReceive {
+            code_hash: "this_is_a_hash_of_a_code".to_string(),
+            #[cfg(feature = "gas_evaporation")]
+            gas_target: None,
+            padding: None,
+        };
+        let info = mock_info("contract", &[]);
 
-        assert_eq!(query_result.is_ok(), true);
-    }
+        let handle_result = execute(deps.as_mut(), mock_env(), info, handle_msg);
 
-    #[test]
-    fn test_permit_revoke() {
-        let user_address = "secret1kmgdagt5efcz2kku0ak9ezfgntg29g2vr88q0e";
-        let permit_name = "to_be_revoked";
-        let chain_id = "blabla";
+        let result = handle_result.unwrap();
+        assert!(ensure_success(result));
 
-        // Note that 'signature'was generated with the specific values of the above:
-        // user_address, permit_name, chain_id, pub_key_value
-        let pub_key_value = "Ahlb7vwjo4aTY6dqfgpPmPYF7XhTAIReVwncQwlq8Sct";
-        let signature = "VS13F7iv1qxKABxrCAvZQPy2IruLQsIyfTewy/PIhNtybtq417lr3FxsWjV/i9YTqCUxg7weoZwHmYs0YgYX4w==";
+        let handle_msg = ExecuteMsg::Send {
+            recipient: "contract".to_string(),
+            recipient_code_hash: None,
+            amount: Uint128::new(100),
+            memo: Some("my memo".to_string()),
+            padding: None,
+            #[cfg(feature = "gas_evaporation")]
+            gas_target: None,
+            msg: Some(to_binary("hey hey you you").unwrap()),
+        };
+        let info = mock_info("bob", &[]);
 
-        // Init the contract
+        let handle_result = execute(deps.as_mut(), mock_env(), info, handle_msg);
+
+        let result = handle_result.unwrap();
+        assert!(ensure_success(result.clone()));
+        let id = 0;
+        assert!(result.messages.contains(&SubMsg {
+            id,
+            msg: CosmosMsg::Wasm(WasmMsg::Execute {
+                contract_addr: "contract".to_string(),
+                code_hash: "this_is_a_hash_of_a_code".to_string(),
+                msg: Snip20ReceiveMsg::new(
+                    Addr::unchecked("bob".to_string()),
+                    Addr::unchecked("bob".to_string()),
+                    Uint128::new(100),
+                    Some("my memo".to_string()),
+                    Some(to_binary("hey hey you you").unwrap())
+                )
+                .into_binary()
+                .unwrap(),
+                funds: vec![],
+            })
+            .into(),
+            reply_on: match id {
+                0 => ReplyOn::Never,
+                _ => ReplyOn::Always,
+            },
+            gas_limit: None,
+        }));
+    }
+
+    #[test]
+    fn test_handle_register_receive() {
         let (init_result, mut deps) = init_helper(vec![InitialBalance {
-            address: user_address.to_string(),
-            amount: Uint128::new(50000000),
+            address: "bob".to_string(),
+            amount: Uint128::new(5000),
         }]);
         assert!(
             init_result.is_ok(),
@@ -2791,36 +4096,28 @@ mod tests {
             init_result.err().unwrap()
         );
 
-        // Query the account's balance
-        let balance_with_permit_msg =
-            get_balance_with_permit_qry_msg(permit_name, chain_id, pub_key_value, signature);
-        let query_result = query(deps.as_ref(), mock_env(), balance_with_permit_msg);
-        let balance = match from_binary(&query_result.unwrap()).unwrap() {
-            QueryAnswer::Balance { amount } => amount,
-            _ => panic!("Unexpected result from query"),
+        let handle_msg = ExecuteMsg::RegisterReceive {
+            code_hash: "this_is_a_hash_of_a_code".to_string(),
+            #[cfg(feature = "gas_evaporation")]
+            gas_target: None,
+            padding: None,
         };
-        assert_eq!(balance.u128(), 50000000);
+        let info = mock_info("contract", &[]);
 
-        // Revoke the Balance permit
-        let handle_result = revoke_permit(permit_name, user_address, &mut deps);
-        let status = match from_binary(&handle_result.unwrap().data.unwrap()).unwrap() {
-            ExecuteAnswer::RevokePermit { status } => status,
-            _ => panic!("NOPE"),
-        };
-        assert_eq!(status, ResponseStatus::Success);
+        let handle_result = execute(deps.as_mut(), mock_env(), info, handle_msg);
 
-        // Try to query the balance with permit and fail because the permit is now revoked
-        let balance_with_permit_msg =
-            get_balance_with_permit_qry_msg(permit_name, chain_id, pub_key_value, signature);
-        let query_result = query(deps.as_ref(), mock_env(), balance_with_permit_msg);
-        let error = extract_error_msg(query_result);
-        assert!(
-            error.contains(format!("Permit \"{}\" was revoked by account", permit_name).as_str())
-        );
+        let result = handle_result.unwrap();
+        assert!(ensure_success(result));
+
+        let hash =
+            ReceiverHashStore::may_load(&deps.storage, &Addr::unchecked("contract".to_string()))
+                .unwrap()
+                .unwrap();
+        assert_eq!(hash, "this_is_a_hash_of_a_code".to_string());
     }
 
     #[test]
-    fn test_execute_transfer_from() {
+    fn test_handle_create_viewing_key() {
         let (init_result, mut deps) = init_helper(vec![InitialBalance {
             address: "bob".to_string(),
             amount: Uint128::new(5000),
@@ -2831,30 +4128,12 @@ mod tests {
             init_result.err().unwrap()
         );
 
-        // Transfer before allowance
-        let handle_msg = ExecuteMsg::TransferFrom {
-            owner: "bob".to_string(),
-            recipient: "alice".to_string(),
-            amount: Uint128::new(2500),
-            memo: None,
-            decoys: None,
+        let handle_msg = ExecuteMsg::CreateViewingKey {
             entropy: None,
+            #[cfg(feature = "gas_evaporation")]
+            gas_target: None,
             padding: None,
         };
-        let info = mock_info("alice", &[]);
-
-        let handle_result = execute(deps.as_mut(), mock_env(), info, handle_msg);
-
-        let error = extract_error_msg(handle_result);
-        assert!(error.contains("insufficient allowance"));
-
-        // Transfer more than allowance
-        let handle_msg = ExecuteMsg::IncreaseAllowance {
-            spender: "alice".to_string(),
-            amount: Uint128::new(2000),
-            padding: None,
-            expiration: Some(1_571_797_420),
-        };
         let info = mock_info("bob", &[]);
 
         let handle_result = execute(deps.as_mut(), mock_env(), info, handle_msg);
@@ -2864,107 +4143,23 @@ mod tests {
             "handle() failed: {}",
             handle_result.err().unwrap()
         );
-        let handle_msg = ExecuteMsg::TransferFrom {
-            owner: "bob".to_string(),
-            recipient: "alice".to_string(),
-            amount: Uint128::new(2500),
-            memo: None,
-            decoys: None,
-            entropy: None,
-            padding: None,
+        let answer: ExecuteAnswer = from_binary(&handle_result.unwrap().data.unwrap()).unwrap();
+
+        let key = match answer {
+            ExecuteAnswer::CreateViewingKey { key } => key,
+            _ => panic!("NOPE"),
         };
-        let info = mock_info("alice", &[]);
+        // let bob_canonical = deps.as_mut().api.addr_canonicalize("bob").unwrap();
 
-        let handle_result = execute(deps.as_mut(), mock_env(), info, handle_msg);
+        let result = ViewingKey::check(&deps.storage, "bob", key.as_str());
+        assert!(result.is_ok());
 
-        let error = extract_error_msg(handle_result);
-        assert!(error.contains("insufficient allowance"));
-
-        // Transfer after allowance expired
-        let handle_msg = ExecuteMsg::TransferFrom {
-            owner: "bob".to_string(),
-            recipient: "alice".to_string(),
-            amount: Uint128::new(2000),
-            memo: None,
-            decoys: None,
-            entropy: None,
-            padding: None,
-        };
-
-        let info = MessageInfo {
-            sender: Addr::unchecked("bob".to_string()),
-            funds: vec![],
-        };
-
-        let handle_result = execute(
-            deps.as_mut(),
-            Env {
-                block: BlockInfo {
-                    height: 12_345,
-                    time: Timestamp::from_seconds(1_571_797_420),
-                    chain_id: "cosmos-testnet-14002".to_string(),
-                },
-                transaction: Some(TransactionInfo { index: 3 }),
-                contract: ContractInfo {
-                    address: Addr::unchecked(MOCK_CONTRACT_ADDR.to_string()),
-                    code_hash: "".to_string(),
-                },
-            },
-            info,
-            handle_msg,
-        );
-        let error = extract_error_msg(handle_result);
-        assert!(error.contains("insufficient allowance"));
-
-        // Sanity check
-        let handle_msg = ExecuteMsg::TransferFrom {
-            owner: "bob".to_string(),
-            recipient: "alice".to_string(),
-            amount: Uint128::new(2000),
-            memo: None,
-            decoys: None,
-            entropy: None,
-            padding: None,
-        };
-        let info = mock_info("alice", &[]);
-
-        let handle_result = execute(deps.as_mut(), mock_env(), info, handle_msg);
-
-        assert!(
-            handle_result.is_ok(),
-            "handle() failed: {}",
-            handle_result.err().unwrap()
-        );
-        let bob_canonical = Addr::unchecked("bob".to_string());
-        let alice_canonical = Addr::unchecked("alice".to_string());
-
-        let bob_balance = BalancesStore::load(&deps.storage, &bob_canonical);
-        let alice_balance = BalancesStore::load(&deps.storage, &alice_canonical);
-        assert_eq!(bob_balance, 5000 - 2000);
-        assert_eq!(alice_balance, 2000);
-        let total_supply = TOTAL_SUPPLY.load(&deps.storage).unwrap();
-        assert_eq!(total_supply, 5000);
-
-        // Second send more than allowance
-        let handle_msg = ExecuteMsg::TransferFrom {
-            owner: "bob".to_string(),
-            recipient: "alice".to_string(),
-            amount: Uint128::new(1),
-            memo: None,
-            decoys: None,
-            entropy: None,
-            padding: None,
-        };
-        let info = mock_info("alice", &[]);
-
-        let handle_result = execute(deps.as_mut(), mock_env(), info, handle_msg);
-
-        let error = extract_error_msg(handle_result);
-        assert!(error.contains("insufficient allowance"));
-    }
+        // let saved_vk = read_viewing_key(&deps.storage, &bob_canonical).unwrap();
+        // assert!(key.check_viewing_key(saved_vk.as_slice()));
+    }
 
     #[test]
-    fn test_handle_send_from() {
+    fn test_handle_set_viewing_key() {
         let (init_result, mut deps) = init_helper(vec![InitialBalance {
             address: "bob".to_string(),
             amount: Uint128::new(5000),
@@ -2975,191 +4170,268 @@ mod tests {
             init_result.err().unwrap()
         );
 
-        // Send before allowance
-        let handle_msg = ExecuteMsg::SendFrom {
-            owner: "bob".to_string(),
-            recipient: "alice".to_string(),
-            recipient_code_hash: None,
-            amount: Uint128::new(2500),
-            memo: None,
-            msg: None,
-            decoys: None,
-            entropy: None,
+        // Set VK
+        let handle_msg = ExecuteMsg::SetViewingKey {
+            key: "hi lol".to_string(),
+            #[cfg(feature = "gas_evaporation")]
+            gas_target: None,
             padding: None,
         };
-        let info = mock_info("alice", &[]);
+        let info = mock_info("bob", &[]);
 
         let handle_result = execute(deps.as_mut(), mock_env(), info, handle_msg);
 
-        let error = extract_error_msg(handle_result);
-        assert!(error.contains("insufficient allowance"));
+        let unwrapped_result: ExecuteAnswer =
+            from_binary(&handle_result.unwrap().data.unwrap()).unwrap();
+        assert_eq!(
+            to_binary(&unwrapped_result).unwrap(),
+            to_binary(&ExecuteAnswer::SetViewingKey {
+                status: ResponseStatus::Success
+            })
+            .unwrap(),
+        );
 
-        // Send more than allowance
-        let handle_msg = ExecuteMsg::IncreaseAllowance {
-            spender: "alice".to_string(),
-            amount: Uint128::new(2000),
+        // Set valid VK
+        let actual_vk = "x".to_string().repeat(VIEWING_KEY_SIZE);
+        let handle_msg = ExecuteMsg::SetViewingKey {
+            key: actual_vk.clone(),
+            #[cfg(feature = "gas_evaporation")]
+            gas_target: None,
             padding: None,
-            expiration: None,
         };
         let info = mock_info("bob", &[]);
 
         let handle_result = execute(deps.as_mut(), mock_env(), info, handle_msg);
 
-        assert!(
-            handle_result.is_ok(),
-            "handle() failed: {}",
-            handle_result.err().unwrap()
+        let unwrapped_result: ExecuteAnswer =
+            from_binary(&handle_result.unwrap().data.unwrap()).unwrap();
+        assert_eq!(
+            to_binary(&unwrapped_result).unwrap(),
+            to_binary(&ExecuteAnswer::SetViewingKey { status: Success }).unwrap(),
         );
-        let handle_msg = ExecuteMsg::SendFrom {
-            owner: "bob".to_string(),
-            recipient: "alice".to_string(),
-            recipient_code_hash: None,
-            amount: Uint128::new(2500),
-            memo: None,
-            msg: None,
-            decoys: None,
-            entropy: None,
-            padding: None,
-        };
-        let info = mock_info("alice", &[]);
-
-        let handle_result = execute(deps.as_mut(), mock_env(), info, handle_msg);
 
-        let error = extract_error_msg(handle_result);
-        assert!(error.contains("insufficient allowance"));
+        let result = ViewingKey::check(&deps.storage, "bob", actual_vk.as_str());
+        assert!(result.is_ok());
+    }
 
-        // Sanity check
-        let handle_msg = ExecuteMsg::RegisterReceive {
-            code_hash: "lolz".to_string(),
+    fn revoke_permit(
+        permit_name: &str,
+        user_address: &str,
+        deps: &mut OwnedDeps<cosmwasm_std::MemoryStorage, MockApi, MockQuerier>,
+    ) -> Result<Response, StdError> {
+        let handle_msg = ExecuteMsg::RevokePermit {
+            permit_name: permit_name.to_string(),
+            #[cfg(feature = "gas_evaporation")]
+            gas_target: None,
             padding: None,
         };
-        let info = mock_info("contract", &[]);
-
+        let info = mock_info(user_address, &[]);
         let handle_result = execute(deps.as_mut(), mock_env(), info, handle_msg);
+        handle_result
+    }
 
-        assert!(
-            handle_result.is_ok(),
-            "handle() failed: {}",
-            handle_result.err().unwrap()
-        );
-        let send_msg = Binary::from(r#"{ "some_msg": { "some_key": "some_val" } }"#.as_bytes());
-        let snip20_msg = Snip20ReceiveMsg::new(
-            Addr::unchecked("alice".to_string()),
-            Addr::unchecked("bob".to_string()),
-            Uint128::new(2000),
-            Some("my memo".to_string()),
-            Some(send_msg.clone()),
+    fn get_balance_with_permit_qry_msg(
+        permit_name: &str,
+        chain_id: &str,
+        pub_key_value: &str,
+        signature: &str,
+    ) -> QueryMsg {
+        let permit = gen_permit_obj(
+            permit_name,
+            chain_id,
+            pub_key_value,
+            signature,
+            TokenPermissions::Balance,
         );
-        let handle_msg = ExecuteMsg::SendFrom {
-            owner: "bob".to_string(),
-            recipient: "contract".to_string(),
-            recipient_code_hash: None,
-            amount: Uint128::new(2000),
-            memo: Some("my memo".to_string()),
-            msg: Some(send_msg),
-            decoys: None,
-            entropy: None,
-            padding: None,
-        };
-        let info = mock_info("alice", &[]);
-
-        let handle_result = execute(deps.as_mut(), mock_env(), info, handle_msg);
 
-        assert!(
-            handle_result.is_ok(),
-            "handle() failed: {}",
-            handle_result.err().unwrap()
-        );
-        assert!(handle_result.unwrap().messages.contains(
-            &into_cosmos_submsg(
-                snip20_msg,
-                "lolz".to_string(),
-                Addr::unchecked("contract".to_string()),
-                0
-            )
-            .unwrap()
-        ));
-        let bob_canonical = Addr::unchecked("bob".to_string());
-        let contract_canonical = Addr::unchecked("contract".to_string());
-        let bob_balance = BalancesStore::load(&deps.storage, &bob_canonical);
-        let contract_balance = BalancesStore::load(&deps.storage, &contract_canonical);
-        assert_eq!(bob_balance, 5000 - 2000);
-        assert_eq!(contract_balance, 2000);
-        let total_supply = TOTAL_SUPPLY.load(&deps.storage).unwrap();
-        assert_eq!(total_supply, 5000);
+        QueryMsg::WithPermit {
+            permit,
+            query: QueryWithPermit::Balance {},
+        }
+    }
 
-        // Second send more than allowance
-        let handle_msg = ExecuteMsg::SendFrom {
-            owner: "bob".to_string(),
-            recipient: "alice".to_string(),
-            recipient_code_hash: None,
-            amount: Uint128::new(1),
-            memo: None,
-            msg: None,
-            decoys: None,
-            entropy: None,
-            padding: None,
+    fn gen_permit_obj(
+        permit_name: &str,
+        chain_id: &str,
+        pub_key_value: &str,
+        signature: &str,
+        permit_type: TokenPermissions,
+    ) -> Permit {
+        let permit: Permit = Permit {
+            params: PermitParams {
+                allowed_tokens: vec![MOCK_CONTRACT_ADDR.to_string()],
+                permit_name: permit_name.to_string(),
+                chain_id: chain_id.to_string(),
+                permissions: vec![permit_type],
+            },
+            signature: PermitSignature {
+                pub_key: PubKey {
+                    r#type: "tendermint/PubKeySecp256k1".to_string(),
+                    value: Binary::from_base64(pub_key_value).unwrap(),
+                },
+                signature: Binary::from_base64(signature).unwrap(),
+            },
         };
-        let info = mock_info("alice", &[]);
-
-        let handle_result = execute(deps.as_mut(), mock_env(), info, handle_msg);
-
-        let error = extract_error_msg(handle_result);
-        assert!(error.contains("insufficient allowance"));
+        permit
     }
 
-    #[test]
-    fn test_handle_burn_from() {
-        let (init_result, mut deps) = init_helper_with_config(
-            vec![InitialBalance {
-                address: "bob".to_string(),
-                amount: Uint128::new(10000),
-            }],
-            false,
-            false,
-            false,
-            true,
-            0,
-            vec![],
+    fn get_allowances_given_permit(
+        permit_name: &str,
+        chain_id: &str,
+        pub_key_value: &str,
+        signature: &str,
+        spender: String,
+    ) -> QueryMsg {
+        let permit = gen_permit_obj(
+            permit_name,
+            chain_id,
+            pub_key_value,
+            signature,
+            TokenPermissions::Owner,
         );
+
+        QueryMsg::WithPermit {
+            permit,
+            query: QueryWithPermit::AllowancesReceived {
+                spender,
+                page: None,
+                page_size: 0,
+            },
+        }
+    }
+
+    #[test]
+    fn test_permit_query_allowances_given_should_fail() {
+        let user_address = "secret18mdrja40gfuftt5yx6tgj0fn5lurplezyp894y";
+        let permit_name = "default";
+        let chain_id = "secretdev-1";
+        let pub_key = "AkZqxdKMtPq2w0kGDGwWGejTAed0H7azPMHtrCX0XYZG";
+        let signature = "ZXyFMlAy6guMG9Gj05rFvcMi5/JGfClRtJpVTHiDtQY3GtSfBHncY70kmYiTXkKIxSxdnh/kS8oXa+GSX5su6Q==";
+
+        // Init the contract
+        let (init_result, deps) = init_helper(vec![InitialBalance {
+            address: user_address.to_string(),
+            amount: Uint128::new(50000000),
+        }]);
         assert!(
             init_result.is_ok(),
             "Init failed: {}",
             init_result.err().unwrap()
         );
 
-        let (init_result_for_failure, mut deps_for_failure) = init_helper(vec![InitialBalance {
-            address: "bob".to_string(),
-            amount: Uint128::new(10000),
+        let msg = get_allowances_given_permit(
+            permit_name,
+            chain_id,
+            pub_key,
+            signature,
+            "secret1kmgdagt5efcz2kku0ak9ezfgntg29g2vr88q0e".to_string(),
+        );
+        let query_result = query(deps.as_ref(), mock_env(), msg);
+
+        assert_eq!(query_result.is_err(), true);
+    }
+
+    #[test]
+    fn test_permit_query_allowances_given() {
+        let user_address = "secret18mdrja40gfuftt5yx6tgj0fn5lurplezyp894y";
+        let permit_name = "default";
+        let chain_id = "secretdev-1";
+        let pub_key = "AkZqxdKMtPq2w0kGDGwWGejTAed0H7azPMHtrCX0XYZG";
+        let signature = "ZXyFMlAy6guMG9Gj05rFvcMi5/JGfClRtJpVTHiDtQY3GtSfBHncY70kmYiTXkKIxSxdnh/kS8oXa+GSX5su6Q==";
+
+        // Init the contract
+        let (init_result, deps) = init_helper(vec![InitialBalance {
+            address: user_address.to_string(),
+            amount: Uint128::new(50000000),
         }]);
         assert!(
-            init_result_for_failure.is_ok(),
+            init_result.is_ok(),
             "Init failed: {}",
-            init_result_for_failure.err().unwrap()
+            init_result.err().unwrap()
         );
-        // test when burn disabled
-        let handle_msg = ExecuteMsg::BurnFrom {
-            owner: "bob".to_string(),
-            amount: Uint128::new(2500),
-            memo: None,
-            decoys: None,
-            entropy: None,
-            padding: None,
+
+        let msg = get_allowances_given_permit(
+            permit_name,
+            chain_id,
+            pub_key,
+            signature,
+            "secret18mdrja40gfuftt5yx6tgj0fn5lurplezyp894y".to_string(),
+        );
+        let query_result = query(deps.as_ref(), mock_env(), msg);
+
+        assert_eq!(query_result.is_ok(), true);
+    }
+
+    #[test]
+    fn test_permit_revoke() {
+        let user_address = "secret1kmgdagt5efcz2kku0ak9ezfgntg29g2vr88q0e";
+        let permit_name = "to_be_revoked";
+        let chain_id = "blabla";
+
+        // Note that 'signature'was generated with the specific values of the above:
+        // user_address, permit_name, chain_id, pub_key_value
+        let pub_key_value = "Ahlb7vwjo4aTY6dqfgpPmPYF7XhTAIReVwncQwlq8Sct";
+        let signature = "VS13F7iv1qxKABxrCAvZQPy2IruLQsIyfTewy/PIhNtybtq417lr3FxsWjV/i9YTqCUxg7weoZwHmYs0YgYX4w==";
+
+        // Init the contract
+        let (init_result, mut deps) = init_helper(vec![InitialBalance {
+            address: user_address.to_string(),
+            amount: Uint128::new(50000000),
+        }]);
+        assert!(
+            init_result.is_ok(),
+            "Init failed: {}",
+            init_result.err().unwrap()
+        );
+
+        // Query the account's balance
+        let balance_with_permit_msg =
+            get_balance_with_permit_qry_msg(permit_name, chain_id, pub_key_value, signature);
+        let query_result = query(deps.as_ref(), mock_env(), balance_with_permit_msg);
+        let balance = match from_binary(&query_result.unwrap()).unwrap() {
+            QueryAnswer::Balance { amount } => amount,
+            _ => panic!("Unexpected result from query"),
         };
-        let info = mock_info("alice", &[]);
+        assert_eq!(balance.u128(), 50000000);
 
-        let handle_result = execute(deps_for_failure.as_mut(), mock_env(), info, handle_msg);
+        // Revoke the Balance permit
+        let handle_result = revoke_permit(permit_name, user_address, &mut deps);
+        let status = match from_binary(&handle_result.unwrap().data.unwrap()).unwrap() {
+            ExecuteAnswer::RevokePermit { status } => status,
+            _ => panic!("NOPE"),
+        };
+        assert_eq!(status, ResponseStatus::Success);
 
-        let error = extract_error_msg(handle_result);
-        assert!(error.contains("Burn functionality is not enabled for this token."));
+        // Try to query the balance with permit and fail because the permit is now revoked
+        let balance_with_permit_msg =
+            get_balance_with_permit_qry_msg(permit_name, chain_id, pub_key_value, signature);
+        let query_result = query(deps.as_ref(), mock_env(), balance_with_permit_msg);
+        let error = extract_error_msg(query_result);
+        assert!(
+            error.contains(format!("Permit \"{}\" was revoked by account", permit_name).as_str())
+        );
+    }
 
-        // Burn before allowance
-        let handle_msg = ExecuteMsg::BurnFrom {
+    #[test]
+    fn test_execute_transfer_from() {
+        let (init_result, mut deps) = init_helper(vec![InitialBalance {
+            address: "bob".to_string(),
+            amount: Uint128::new(5000),
+        }]);
+        assert!(
+            init_result.is_ok(),
+            "Init failed: {}",
+            init_result.err().unwrap()
+        );
+
+        // Transfer before allowance
+        let handle_msg = ExecuteMsg::TransferFrom {
             owner: "bob".to_string(),
+            recipient: "alice".to_string(),
             amount: Uint128::new(2500),
             memo: None,
-            decoys: None,
-            entropy: None,
+            #[cfg(feature = "gas_evaporation")]
+            gas_target: None,
             padding: None,
         };
         let info = mock_info("alice", &[]);
@@ -3169,12 +4441,14 @@ mod tests {
         let error = extract_error_msg(handle_result);
         assert!(error.contains("insufficient allowance"));
 
-        // Burn more than allowance
+        // Transfer more than allowance
         let handle_msg = ExecuteMsg::IncreaseAllowance {
             spender: "alice".to_string(),
             amount: Uint128::new(2000),
             padding: None,
-            expiration: None,
+            #[cfg(feature = "gas_evaporation")]
+            gas_target: None,
+            expiration: Some(1_571_797_420),
         };
         let info = mock_info("bob", &[]);
 
@@ -3185,12 +4459,13 @@ mod tests {
             "handle() failed: {}",
             handle_result.err().unwrap()
         );
-        let handle_msg = ExecuteMsg::BurnFrom {
+        let handle_msg = ExecuteMsg::TransferFrom {
             owner: "bob".to_string(),
+            recipient: "alice".to_string(),
             amount: Uint128::new(2500),
             memo: None,
-            decoys: None,
-            entropy: None,
+            #[cfg(feature = "gas_evaporation")]
+            gas_target: None,
             padding: None,
         };
         let info = mock_info("alice", &[]);
@@ -3200,13 +4475,57 @@ mod tests {
         let error = extract_error_msg(handle_result);
         assert!(error.contains("insufficient allowance"));
 
+        // Transfer after allowance expired
+        let handle_msg = ExecuteMsg::TransferFrom {
+            owner: "bob".to_string(),
+            recipient: "alice".to_string(),
+            amount: Uint128::new(2000),
+            memo: None,
+            #[cfg(feature = "gas_evaporation")]
+            gas_target: None,
+            padding: None,
+        };
+
+        let info = MessageInfo {
+            sender: Addr::unchecked("bob".to_string()),
+            funds: vec![],
+        };
+
+        let handle_result = execute(
+            deps.as_mut(),
+            Env {
+                block: BlockInfo {
+                    height: 12_345,
+                    time: Timestamp::from_seconds(1_571_797_420),
+                    chain_id: "cosmos-testnet-14002".to_string(),
+                    random: Some(Binary::from(&[
+                        0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20,
+                        21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31,
+                    ])),
+                },
+                transaction: Some(TransactionInfo {
+                    index: 3,
+                    hash: "1010".to_string(),
+                }),
+                contract: ContractInfo {
+                    address: Addr::unchecked(MOCK_CONTRACT_ADDR.to_string()),
+                    code_hash: "".to_string(),
+                },
+            },
+            info,
+            handle_msg,
+        );
+        let error = extract_error_msg(handle_result);
+        assert!(error.contains("insufficient allowance"));
+
         // Sanity check
-        let handle_msg = ExecuteMsg::BurnFrom {
+        let handle_msg = ExecuteMsg::TransferFrom {
             owner: "bob".to_string(),
+            recipient: "alice".to_string(),
             amount: Uint128::new(2000),
             memo: None,
-            decoys: None,
-            entropy: None,
+            #[cfg(feature = "gas_evaporation")]
+            gas_target: None,
             padding: None,
         };
         let info = mock_info("alice", &[]);
@@ -3218,19 +4537,30 @@ mod tests {
             "handle() failed: {}",
             handle_result.err().unwrap()
         );
-        let bob_canonical = Addr::unchecked("bob".to_string());
-        let bob_balance = BalancesStore::load(&deps.storage, &bob_canonical);
-        assert_eq!(bob_balance, 10000 - 2000);
+        let bob_canonical = deps
+            .api
+            .addr_canonicalize(Addr::unchecked("bob".to_string()).as_str())
+            .unwrap();
+        let alice_canonical = deps
+            .api
+            .addr_canonicalize(Addr::unchecked("alice".to_string()).as_str())
+            .unwrap();
+
+        let bob_balance = stored_balance(&deps.storage, &bob_canonical).unwrap();
+        let alice_balance = stored_balance(&deps.storage, &alice_canonical).unwrap();
+        assert_eq!(bob_balance, 5000 - 2000);
+        assert_ne!(alice_balance, 2000);
         let total_supply = TOTAL_SUPPLY.load(&deps.storage).unwrap();
-        assert_eq!(total_supply, 10000 - 2000);
+        assert_eq!(total_supply, 5000);
 
-        // Second burn more than allowance
-        let handle_msg = ExecuteMsg::BurnFrom {
+        // Second send more than allowance
+        let handle_msg = ExecuteMsg::TransferFrom {
             owner: "bob".to_string(),
+            recipient: "alice".to_string(),
             amount: Uint128::new(1),
             memo: None,
-            decoys: None,
-            entropy: None,
+            #[cfg(feature = "gas_evaporation")]
+            gas_target: None,
             padding: None,
         };
         let info = mock_info("alice", &[]);
@@ -3242,127 +4572,80 @@ mod tests {
     }
 
     #[test]
-    fn test_handle_batch_burn_from() {
-        let (init_result, mut deps) = init_helper_with_config(
-            vec![
-                InitialBalance {
-                    address: "bob".to_string(),
-                    amount: Uint128::new(10000),
-                },
-                InitialBalance {
-                    address: "jerry".to_string(),
-                    amount: Uint128::new(10000),
-                },
-                InitialBalance {
-                    address: "mike".to_string(),
-                    amount: Uint128::new(10000),
-                },
-            ],
-            false,
-            false,
-            false,
-            true,
-            0,
-            vec![],
-        );
+    fn test_handle_send_from() {
+        let (init_result, mut deps) = init_helper(vec![InitialBalance {
+            address: "bob".to_string(),
+            amount: Uint128::new(5000),
+        }]);
         assert!(
             init_result.is_ok(),
             "Init failed: {}",
             init_result.err().unwrap()
         );
 
-        let (init_result_for_failure, mut deps_for_failure) = init_helper(vec![InitialBalance {
-            address: "bob".to_string(),
-            amount: Uint128::new(10000),
-        }]);
-        assert!(
-            init_result_for_failure.is_ok(),
-            "Init failed: {}",
-            init_result_for_failure.err().unwrap()
-        );
-        // test when burn disabled
-        let actions: Vec<_> = ["bob", "jerry", "mike"]
-            .iter()
-            .map(|name| batch::BurnFromAction {
-                owner: name.to_string(),
-                amount: Uint128::new(2500),
-                memo: None,
-                decoys: None,
-            })
-            .collect();
-        let handle_msg = ExecuteMsg::BatchBurnFrom {
-            actions,
-            entropy: None,
+        // Send before allowance
+        let handle_msg = ExecuteMsg::SendFrom {
+            owner: "bob".to_string(),
+            recipient: "alice".to_string(),
+            recipient_code_hash: None,
+            amount: Uint128::new(2500),
+            memo: None,
+            msg: None,
+            #[cfg(feature = "gas_evaporation")]
+            gas_target: None,
             padding: None,
         };
         let info = mock_info("alice", &[]);
-        let handle_result = execute(
-            deps_for_failure.as_mut(),
-            mock_env(),
-            info,
-            handle_msg.clone(),
-        );
-        let error = extract_error_msg(handle_result);
-        assert!(error.contains("Burn functionality is not enabled for this token."));
-
-        // Burn before allowance
-        let info = mock_info("alice", &[]);
 
         let handle_result = execute(deps.as_mut(), mock_env(), info, handle_msg);
 
         let error = extract_error_msg(handle_result);
         assert!(error.contains("insufficient allowance"));
 
-        // Burn more than allowance
-        let allowance_size = 2000;
-        for name in &["bob", "jerry", "mike"] {
-            let handle_msg = ExecuteMsg::IncreaseAllowance {
-                spender: "alice".to_string(),
-                amount: Uint128::new(allowance_size),
-                padding: None,
-                expiration: None,
-            };
-            let info = mock_info(*name, &[]);
-            let handle_result = execute(deps.as_mut(), mock_env(), info, handle_msg);
+        // Send more than allowance
+        let handle_msg = ExecuteMsg::IncreaseAllowance {
+            spender: "alice".to_string(),
+            amount: Uint128::new(2000),
+            #[cfg(feature = "gas_evaporation")]
+            gas_target: None,
+            padding: None,
+            expiration: None,
+        };
+        let info = mock_info("bob", &[]);
 
-            assert!(
-                handle_result.is_ok(),
-                "handle() failed: {}",
-                handle_result.err().unwrap()
-            );
-            let handle_msg = ExecuteMsg::BurnFrom {
-                owner: "name".to_string(),
-                amount: Uint128::new(2500),
-                memo: None,
-                decoys: None,
-                entropy: None,
-                padding: None,
-            };
-            let info = mock_info("alice", &[]);
+        let handle_result = execute(deps.as_mut(), mock_env(), info, handle_msg);
 
-            let handle_result = execute(deps.as_mut(), mock_env(), info, handle_msg);
+        assert!(
+            handle_result.is_ok(),
+            "handle() failed: {}",
+            handle_result.err().unwrap()
+        );
+        let handle_msg = ExecuteMsg::SendFrom {
+            owner: "bob".to_string(),
+            recipient: "alice".to_string(),
+            recipient_code_hash: None,
+            amount: Uint128::new(2500),
+            memo: None,
+            msg: None,
+            #[cfg(feature = "gas_evaporation")]
+            gas_target: None,
+            padding: None,
+        };
+        let info = mock_info("alice", &[]);
 
-            let error = extract_error_msg(handle_result);
-            assert!(error.contains("insufficient allowance"));
-        }
+        let handle_result = execute(deps.as_mut(), mock_env(), info, handle_msg);
 
-        // Burn some of the allowance
-        let actions: Vec<_> = [("bob", 200_u128), ("jerry", 300), ("mike", 400)]
-            .iter()
-            .map(|(name, amount)| batch::BurnFromAction {
-                owner: name.to_string(),
-                amount: Uint128::new(*amount),
-                memo: None,
-                decoys: None,
-            })
-            .collect();
+        let error = extract_error_msg(handle_result);
+        assert!(error.contains("insufficient allowance"));
 
-        let handle_msg = ExecuteMsg::BatchBurnFrom {
-            actions,
-            entropy: None,
+        // Sanity check
+        let handle_msg = ExecuteMsg::RegisterReceive {
+            code_hash: "lolz".to_string(),
+            #[cfg(feature = "gas_evaporation")]
+            gas_target: None,
             padding: None,
         };
-        let info = mock_info("alice", &[]);
+        let info = mock_info("contract", &[]);
 
         let handle_result = execute(deps.as_mut(), mock_env(), info, handle_msg);
 
@@ -3371,28 +4654,23 @@ mod tests {
             "handle() failed: {}",
             handle_result.err().unwrap()
         );
-        for (name, amount) in &[("bob", 200_u128), ("jerry", 300), ("mike", 400)] {
-            let name_canon = Addr::unchecked(name.to_string());
-            let balance = BalancesStore::load(&deps.storage, &name_canon);
-            assert_eq!(balance, 10000 - amount);
-        }
-        let total_supply = TOTAL_SUPPLY.load(&deps.storage).unwrap();
-        assert_eq!(total_supply, 10000 * 3 - (200 + 300 + 400));
-
-        // Burn the rest of the allowance
-        let actions: Vec<_> = [("bob", 200_u128), ("jerry", 300), ("mike", 400)]
-            .iter()
-            .map(|(name, amount)| batch::BurnFromAction {
-                owner: name.to_string(),
-                amount: Uint128::new(allowance_size - *amount),
-                memo: None,
-                decoys: None,
-            })
-            .collect();
-
-        let handle_msg = ExecuteMsg::BatchBurnFrom {
-            actions,
-            entropy: None,
+        let send_msg = Binary::from(r#"{ "some_msg": { "some_key": "some_val" } }"#.as_bytes());
+        let snip20_msg = Snip20ReceiveMsg::new(
+            Addr::unchecked("alice".to_string()),
+            Addr::unchecked("bob".to_string()),
+            Uint128::new(2000),
+            Some("my memo".to_string()),
+            Some(send_msg.clone()),
+        );
+        let handle_msg = ExecuteMsg::SendFrom {
+            owner: "bob".to_string(),
+            recipient: "contract".to_string(),
+            recipient_code_hash: None,
+            amount: Uint128::new(2000),
+            memo: Some("my memo".to_string()),
+            msg: Some(send_msg),
+            #[cfg(feature = "gas_evaporation")]
+            gas_target: None,
             padding: None,
         };
         let info = mock_info("alice", &[]);
@@ -3404,27 +4682,42 @@ mod tests {
             "handle() failed: {}",
             handle_result.err().unwrap()
         );
-        for name in &["bob", "jerry", "mike"] {
-            let name_canon = Addr::unchecked(name.to_string());
-            let balance = BalancesStore::load(&deps.storage, &name_canon);
-            assert_eq!(balance, 10000 - allowance_size);
-        }
+        assert!(handle_result.unwrap().messages.contains(
+            &into_cosmos_submsg(
+                snip20_msg,
+                "lolz".to_string(),
+                Addr::unchecked("contract".to_string()),
+                0
+            )
+            .unwrap()
+        ));
+
+        let bob_canonical = deps
+            .api
+            .addr_canonicalize(Addr::unchecked("bob".to_string()).as_str())
+            .unwrap();
+        let contract_canonical = deps
+            .api
+            .addr_canonicalize(Addr::unchecked("contract".to_string()).as_str())
+            .unwrap();
+
+        let bob_balance = stored_balance(&deps.storage, &bob_canonical).unwrap();
+        let contract_balance = stored_balance(&deps.storage, &contract_canonical).unwrap();
+        assert_eq!(bob_balance, 5000 - 2000);
+        assert_ne!(contract_balance, 2000);
         let total_supply = TOTAL_SUPPLY.load(&deps.storage).unwrap();
-        assert_eq!(total_supply, 3 * (10000 - allowance_size));
+        assert_eq!(total_supply, 5000);
 
-        // Second burn more than allowance
-        let actions: Vec<_> = ["bob", "jerry", "mike"]
-            .iter()
-            .map(|name| batch::BurnFromAction {
-                owner: name.to_string(),
-                amount: Uint128::new(1),
-                memo: None,
-                decoys: None,
-            })
-            .collect();
-        let handle_msg = ExecuteMsg::BatchBurnFrom {
-            actions,
-            entropy: None,
+        // Second send more than allowance
+        let handle_msg = ExecuteMsg::SendFrom {
+            owner: "bob".to_string(),
+            recipient: "alice".to_string(),
+            recipient_code_hash: None,
+            amount: Uint128::new(1),
+            memo: None,
+            msg: None,
+            #[cfg(feature = "gas_evaporation")]
+            gas_target: None,
             padding: None,
         };
         let info = mock_info("alice", &[]);
@@ -3436,21 +4729,73 @@ mod tests {
     }
 
     #[test]
-    fn test_handle_decrease_allowance() {
-        let (init_result, mut deps) = init_helper(vec![InitialBalance {
-            address: "bob".to_string(),
-            amount: Uint128::new(5000),
-        }]);
+    fn test_handle_burn_from() {
+        let (init_result, mut deps) = init_helper_with_config(
+            vec![InitialBalance {
+                address: "bob".to_string(),
+                amount: Uint128::new(10000),
+            }],
+            false,
+            false,
+            false,
+            true,
+            0,
+            vec![],
+        );
         assert!(
             init_result.is_ok(),
             "Init failed: {}",
             init_result.err().unwrap()
         );
 
-        let handle_msg = ExecuteMsg::DecreaseAllowance {
+        let (init_result_for_failure, mut deps_for_failure) = init_helper(vec![InitialBalance {
+            address: "bob".to_string(),
+            amount: Uint128::new(10000),
+        }]);
+        assert!(
+            init_result_for_failure.is_ok(),
+            "Init failed: {}",
+            init_result_for_failure.err().unwrap()
+        );
+        // test when burn disabled
+        let handle_msg = ExecuteMsg::BurnFrom {
+            owner: "bob".to_string(),
+            amount: Uint128::new(2500),
+            memo: None,
+            #[cfg(feature = "gas_evaporation")]
+            gas_target: None,
+            padding: None,
+        };
+        let info = mock_info("alice", &[]);
+
+        let handle_result = execute(deps_for_failure.as_mut(), mock_env(), info, handle_msg);
+
+        let error = extract_error_msg(handle_result);
+        assert!(error.contains("Burn functionality is not enabled for this token."));
+
+        // Burn before allowance
+        let handle_msg = ExecuteMsg::BurnFrom {
+            owner: "bob".to_string(),
+            amount: Uint128::new(2500),
+            memo: None,
+            #[cfg(feature = "gas_evaporation")]
+            gas_target: None,
+            padding: None,
+        };
+        let info = mock_info("alice", &[]);
+
+        let handle_result = execute(deps.as_mut(), mock_env(), info, handle_msg);
+
+        let error = extract_error_msg(handle_result);
+        assert!(error.contains("insufficient allowance"));
+
+        // Burn more than allowance
+        let handle_msg = ExecuteMsg::IncreaseAllowance {
             spender: "alice".to_string(),
             amount: Uint128::new(2000),
             padding: None,
+            #[cfg(feature = "gas_evaporation")]
+            gas_target: None,
             expiration: None,
         };
         let info = mock_info("bob", &[]);
@@ -3462,26 +4807,31 @@ mod tests {
             "handle() failed: {}",
             handle_result.err().unwrap()
         );
+        let handle_msg = ExecuteMsg::BurnFrom {
+            owner: "bob".to_string(),
+            amount: Uint128::new(2500),
+            memo: None,
+            #[cfg(feature = "gas_evaporation")]
+            gas_target: None,
+            padding: None,
+        };
+        let info = mock_info("alice", &[]);
 
-        let bob_canonical = Addr::unchecked("bob".to_string());
-        let alice_canonical = Addr::unchecked("alice".to_string());
+        let handle_result = execute(deps.as_mut(), mock_env(), info, handle_msg);
 
-        let allowance = AllowancesStore::load(&deps.storage, &bob_canonical, &alice_canonical);
-        assert_eq!(
-            allowance,
-            crate::state::Allowance {
-                amount: 0,
-                expiration: None
-            }
-        );
+        let error = extract_error_msg(handle_result);
+        assert!(error.contains("insufficient allowance"));
 
-        let handle_msg = ExecuteMsg::IncreaseAllowance {
-            spender: "alice".to_string(),
+        // Sanity check
+        let handle_msg = ExecuteMsg::BurnFrom {
+            owner: "bob".to_string(),
             amount: Uint128::new(2000),
+            memo: None,
+            #[cfg(feature = "gas_evaporation")]
+            gas_target: None,
             padding: None,
-            expiration: None,
         };
-        let info = mock_info("bob", &[]);
+        let info = mock_info("alice", &[]);
 
         let handle_result = execute(deps.as_mut(), mock_env(), info, handle_msg);
 
@@ -3490,14 +4840,157 @@ mod tests {
             "handle() failed: {}",
             handle_result.err().unwrap()
         );
+        let bob_canonical = deps
+            .api
+            .addr_canonicalize(Addr::unchecked("bob".to_string()).as_str())
+            .unwrap();
 
-        let handle_msg = ExecuteMsg::DecreaseAllowance {
-            spender: "alice".to_string(),
-            amount: Uint128::new(50),
+        let bob_balance = stored_balance(&deps.storage, &bob_canonical).unwrap();
+        assert_eq!(bob_balance, 10000 - 2000);
+        let total_supply = TOTAL_SUPPLY.load(&deps.storage).unwrap();
+        assert_eq!(total_supply, 10000 - 2000);
+
+        // Second burn more than allowance
+        let handle_msg = ExecuteMsg::BurnFrom {
+            owner: "bob".to_string(),
+            amount: Uint128::new(1),
+            memo: None,
+            #[cfg(feature = "gas_evaporation")]
+            gas_target: None,
             padding: None,
-            expiration: None,
         };
-        let info = mock_info("bob", &[]);
+        let info = mock_info("alice", &[]);
+
+        let handle_result = execute(deps.as_mut(), mock_env(), info, handle_msg);
+
+        let error = extract_error_msg(handle_result);
+        assert!(error.contains("insufficient allowance"));
+    }
+
+    #[test]
+    fn test_handle_batch_burn_from() {
+        let (init_result, mut deps) = init_helper_with_config(
+            vec![
+                InitialBalance {
+                    address: "bob".to_string(),
+                    amount: Uint128::new(10000),
+                },
+                InitialBalance {
+                    address: "jerry".to_string(),
+                    amount: Uint128::new(10000),
+                },
+                InitialBalance {
+                    address: "mike".to_string(),
+                    amount: Uint128::new(10000),
+                },
+            ],
+            false,
+            false,
+            false,
+            true,
+            0,
+            vec![],
+        );
+        assert!(
+            init_result.is_ok(),
+            "Init failed: {}",
+            init_result.err().unwrap()
+        );
+
+        let (init_result_for_failure, mut deps_for_failure) = init_helper(vec![InitialBalance {
+            address: "bob".to_string(),
+            amount: Uint128::new(10000),
+        }]);
+        assert!(
+            init_result_for_failure.is_ok(),
+            "Init failed: {}",
+            init_result_for_failure.err().unwrap()
+        );
+        // test when burn disabled
+        let actions: Vec<_> = ["bob", "jerry", "mike"]
+            .iter()
+            .map(|name| batch::BurnFromAction {
+                owner: name.to_string(),
+                amount: Uint128::new(2500),
+                memo: None,
+            })
+            .collect();
+        let handle_msg = ExecuteMsg::BatchBurnFrom {
+            actions,
+            #[cfg(feature = "gas_evaporation")]
+            gas_target: None,
+            padding: None,
+        };
+        let info = mock_info("alice", &[]);
+        let handle_result = execute(
+            deps_for_failure.as_mut(),
+            mock_env(),
+            info,
+            handle_msg.clone(),
+        );
+        let error = extract_error_msg(handle_result);
+        assert!(error.contains("Burn functionality is not enabled for this token."));
+
+        // Burn before allowance
+        let info = mock_info("alice", &[]);
+
+        let handle_result = execute(deps.as_mut(), mock_env(), info, handle_msg);
+
+        let error = extract_error_msg(handle_result);
+        assert!(error.contains("insufficient allowance"));
+
+        // Burn more than allowance
+        let allowance_size = 2000;
+        for name in &["bob", "jerry", "mike"] {
+            let handle_msg = ExecuteMsg::IncreaseAllowance {
+                spender: "alice".to_string(),
+                amount: Uint128::new(allowance_size),
+                padding: None,
+                #[cfg(feature = "gas_evaporation")]
+                gas_target: None,
+                expiration: None,
+            };
+            let info = mock_info(*name, &[]);
+            let handle_result = execute(deps.as_mut(), mock_env(), info, handle_msg);
+
+            assert!(
+                handle_result.is_ok(),
+                "handle() failed: {}",
+                handle_result.err().unwrap()
+            );
+            let handle_msg = ExecuteMsg::BurnFrom {
+                owner: "name".to_string(),
+                amount: Uint128::new(2500),
+                memo: None,
+                #[cfg(feature = "gas_evaporation")]
+                gas_target: None,
+                padding: None,
+            };
+            let info = mock_info("alice", &[]);
+
+            let handle_result = execute(deps.as_mut(), mock_env(), info, handle_msg);
+
+            let error = extract_error_msg(handle_result);
+            assert!(error.contains("insufficient allowance"));
+        }
+
+        // Burn some of the allowance
+        let actions: Vec<_> = [("bob", 200_u128), ("jerry", 300), ("mike", 400)]
+            .iter()
+            .map(|(name, amount)| batch::BurnFromAction {
+                owner: name.to_string(),
+                amount: Uint128::new(*amount),
+                memo: None,
+            })
+            .collect();
+
+        let handle_msg = ExecuteMsg::BatchBurnFrom {
+            actions,
+            #[cfg(feature = "gas_evaporation")]
+            gas_target: None,
+            padding: None,
+        };
+        let info = mock_info("alice", &[]);
 
         let handle_result = execute(deps.as_mut(), mock_env(), info, handle_msg);
 
@@ -3506,19 +4999,78 @@ mod tests {
             "handle() failed: {}",
             handle_result.err().unwrap()
         );
+        for (name, amount) in &[("bob", 200_u128), ("jerry", 300), ("mike", 400)] {
+            let name_canon = deps
+                .api
+                .addr_canonicalize(Addr::unchecked(name.to_string()).as_str())
+                .unwrap();
+            let balance = stored_balance(&deps.storage, &name_canon).unwrap();
+            assert_eq!(balance, 10000 - amount);
+        }
+        let total_supply = TOTAL_SUPPLY.load(&deps.storage).unwrap();
+        assert_eq!(total_supply, 10000 * 3 - (200 + 300 + 400));
 
-        let allowance = AllowancesStore::load(&deps.storage, &bob_canonical, &alice_canonical);
-        assert_eq!(
-            allowance,
-            crate::state::Allowance {
-                amount: 1950,
-                expiration: None
-            }
+        // Burn the rest of the allowance
+        let actions: Vec<_> = [("bob", 200_u128), ("jerry", 300), ("mike", 400)]
+            .iter()
+            .map(|(name, amount)| batch::BurnFromAction {
+                owner: name.to_string(),
+                amount: Uint128::new(allowance_size - *amount),
+                memo: None,
+            })
+            .collect();
+
+        let handle_msg = ExecuteMsg::BatchBurnFrom {
+            actions,
+            #[cfg(feature = "gas_evaporation")]
+            gas_target: None,
+            padding: None,
+        };
+        let info = mock_info("alice", &[]);
+
+        let handle_result = execute(deps.as_mut(), mock_env(), info, handle_msg);
+
+        assert!(
+            handle_result.is_ok(),
+            "handle() failed: {}",
+            handle_result.err().unwrap()
         );
+        for name in &["bob", "jerry", "mike"] {
+            let name_canon = deps
+                .api
+                .addr_canonicalize(Addr::unchecked(name.to_string()).as_str())
+                .unwrap();
+            let balance = stored_balance(&deps.storage, &name_canon).unwrap();
+            assert_eq!(balance, 10000 - allowance_size);
+        }
+        let total_supply = TOTAL_SUPPLY.load(&deps.storage).unwrap();
+        assert_eq!(total_supply, 3 * (10000 - allowance_size));
+
+        // Second burn more than allowance
+        let actions: Vec<_> = ["bob", "jerry", "mike"]
+            .iter()
+            .map(|name| batch::BurnFromAction {
+                owner: name.to_string(),
+                amount: Uint128::new(1),
+                memo: None,
+            })
+            .collect();
+        let handle_msg = ExecuteMsg::BatchBurnFrom {
+            actions,
+            #[cfg(feature = "gas_evaporation")]
+            gas_target: None,
+            padding: None,
+        };
+        let info = mock_info("alice", &[]);
+
+        let handle_result = execute(deps.as_mut(), mock_env(), info, handle_msg);
+
+        let error = extract_error_msg(handle_result);
+        assert!(error.contains("insufficient allowance"));
     }
 
     #[test]
-    fn test_handle_increase_allowance() {
+    fn test_handle_decrease_allowance() {
         let (init_result, mut deps) = init_helper(vec![InitialBalance {
             address: "bob".to_string(),
             amount: Uint128::new(5000),
@@ -3529,10 +5081,12 @@ mod tests {
             init_result.err().unwrap()
         );
 
-        let handle_msg = ExecuteMsg::IncreaseAllowance {
+        let handle_msg = ExecuteMsg::DecreaseAllowance {
             spender: "alice".to_string(),
             amount: Uint128::new(2000),
             padding: None,
+            #[cfg(feature = "gas_evaporation")]
+            gas_target: None,
             expiration: None,
         };
         let info = mock_info("bob", &[]);
@@ -3552,7 +5106,7 @@ mod tests {
         assert_eq!(
             allowance,
             crate::state::Allowance {
-                amount: 2000,
+                amount: 0,
                 expiration: None
             }
         );
@@ -3561,6 +5115,8 @@ mod tests {
             spender: "alice".to_string(),
             amount: Uint128::new(2000),
             padding: None,
+            #[cfg(feature = "gas_evaporation")]
+            gas_target: None,
             expiration: None,
         };
         let info = mock_info("bob", &[]);
@@ -3573,33 +5129,15 @@ mod tests {
             handle_result.err().unwrap()
         );
 
-        let allowance = AllowancesStore::load(&deps.storage, &bob_canonical, &alice_canonical);
-        assert_eq!(
-            allowance,
-            crate::state::Allowance {
-                amount: 4000,
-                expiration: None
-            }
-        );
-    }
-
-    #[test]
-    fn test_handle_change_admin() {
-        let (init_result, mut deps) = init_helper(vec![InitialBalance {
-            address: "bob".to_string(),
-            amount: Uint128::new(5000),
-        }]);
-        assert!(
-            init_result.is_ok(),
-            "Init failed: {}",
-            init_result.err().unwrap()
-        );
-
-        let handle_msg = ExecuteMsg::ChangeAdmin {
-            address: "bob".to_string(),
+        let handle_msg = ExecuteMsg::DecreaseAllowance {
+            spender: "alice".to_string(),
+            amount: Uint128::new(50),
             padding: None,
+            #[cfg(feature = "gas_evaporation")]
+            gas_target: None,
+            expiration: None,
         };
-        let info = mock_info("admin", &[]);
+        let info = mock_info("bob", &[]);
 
         let handle_result = execute(deps.as_mut(), mock_env(), info, handle_msg);
 
@@ -3609,14 +5147,20 @@ mod tests {
             handle_result.err().unwrap()
         );
 
-        let admin = CONFIG.load(&deps.storage).unwrap().admin;
-        assert_eq!(admin, Addr::unchecked("bob".to_string()));
+        let allowance = AllowancesStore::load(&deps.storage, &bob_canonical, &alice_canonical);
+        assert_eq!(
+            allowance,
+            crate::state::Allowance {
+                amount: 1950,
+                expiration: None
+            }
+        );
     }
 
     #[test]
-    fn test_handle_set_contract_status() {
+    fn test_handle_increase_allowance() {
         let (init_result, mut deps) = init_helper(vec![InitialBalance {
-            address: "admin".to_string(),
+            address: "bob".to_string(),
             amount: Uint128::new(5000),
         }]);
         assert!(
@@ -3625,11 +5169,15 @@ mod tests {
             init_result.err().unwrap()
         );
 
-        let handle_msg = ExecuteMsg::SetContractStatus {
-            level: ContractStatusLevel::StopAll,
+        let handle_msg = ExecuteMsg::IncreaseAllowance {
+            spender: "alice".to_string(),
+            amount: Uint128::new(2000),
             padding: None,
+            #[cfg(feature = "gas_evaporation")]
+            gas_target: None,
+            expiration: None,
         };
-        let info = mock_info("admin", &[]);
+        let info = mock_info("bob", &[]);
 
         let handle_result = execute(deps.as_mut(), mock_env(), info, handle_msg);
 
@@ -3639,7 +5187,107 @@ mod tests {
             handle_result.err().unwrap()
         );
 
-        let contract_status = CONTRACT_STATUS.load(&deps.storage).unwrap();
+        let bob_canonical = Addr::unchecked("bob".to_string());
+        let alice_canonical = Addr::unchecked("alice".to_string());
+
+        let allowance = AllowancesStore::load(&deps.storage, &bob_canonical, &alice_canonical);
+        assert_eq!(
+            allowance,
+            crate::state::Allowance {
+                amount: 2000,
+                expiration: None
+            }
+        );
+
+        let handle_msg = ExecuteMsg::IncreaseAllowance {
+            spender: "alice".to_string(),
+            amount: Uint128::new(2000),
+            padding: None,
+            #[cfg(feature = "gas_evaporation")]
+            gas_target: None,
+            expiration: None,
+        };
+        let info = mock_info("bob", &[]);
+
+        let handle_result = execute(deps.as_mut(), mock_env(), info, handle_msg);
+
+        assert!(
+            handle_result.is_ok(),
+            "handle() failed: {}",
+            handle_result.err().unwrap()
+        );
+
+        let allowance = AllowancesStore::load(&deps.storage, &bob_canonical, &alice_canonical);
+        assert_eq!(
+            allowance,
+            crate::state::Allowance {
+                amount: 4000,
+                expiration: None
+            }
+        );
+    }
+
+    #[test]
+    fn test_handle_change_admin() {
+        let (init_result, mut deps) = init_helper(vec![InitialBalance {
+            address: "bob".to_string(),
+            amount: Uint128::new(5000),
+        }]);
+        assert!(
+            init_result.is_ok(),
+            "Init failed: {}",
+            init_result.err().unwrap()
+        );
+
+        let handle_msg = ExecuteMsg::ChangeAdmin {
+            address: "bob".to_string(),
+            #[cfg(feature = "gas_evaporation")]
+            gas_target: None,
+            padding: None,
+        };
+        let info = mock_info("admin", &[]);
+
+        let handle_result = execute(deps.as_mut(), mock_env(), info, handle_msg);
+
+        assert!(
+            handle_result.is_ok(),
+            "handle() failed: {}",
+            handle_result.err().unwrap()
+        );
+
+        let admin = CONFIG.load(&deps.storage).unwrap().admin;
+        assert_eq!(admin, Addr::unchecked("bob".to_string()));
+    }
+
+    #[test]
+    fn test_handle_set_contract_status() {
+        let (init_result, mut deps) = init_helper(vec![InitialBalance {
+            address: "admin".to_string(),
+            amount: Uint128::new(5000),
+        }]);
+        assert!(
+            init_result.is_ok(),
+            "Init failed: {}",
+            init_result.err().unwrap()
+        );
+
+        let handle_msg = ExecuteMsg::SetContractStatus {
+            level: ContractStatusLevel::StopAll,
+            #[cfg(feature = "gas_evaporation")]
+            gas_target: None,
+            padding: None,
+        };
+        let info = mock_info("admin", &[]);
+
+        let handle_result = execute(deps.as_mut(), mock_env(), info, handle_msg);
+
+        assert!(
+            handle_result.is_ok(),
+            "handle() failed: {}",
+            handle_result.err().unwrap()
+        );
+
+        let contract_status = CONTRACT_STATUS.load(&deps.storage).unwrap();
         assert!(matches!(
             contract_status,
             ContractStatusLevel::StopAll { .. }
@@ -3697,8 +5345,8 @@ mod tests {
         let handle_msg = ExecuteMsg::Redeem {
             amount: Uint128::new(1000),
             denom: None,
-            decoys: None,
-            entropy: None,
+            #[cfg(feature = "gas_evaporation")]
+            gas_target: None,
             padding: None,
         };
         let info = mock_info("butler", &[]);
@@ -3712,8 +5360,8 @@ mod tests {
         let handle_msg = ExecuteMsg::Redeem {
             amount: Uint128::new(1000),
             denom: None,
-            decoys: None,
-            entropy: None,
+            #[cfg(feature = "gas_evaporation")]
+            gas_target: None,
             padding: None,
         };
         let info = mock_info("butler", &[]);
@@ -3730,9 +5378,9 @@ mod tests {
         let handle_msg = ExecuteMsg::Redeem {
             amount: Uint128::new(1000),
             denom: None,
-            decoys: None,
-            entropy: None,
             padding: None,
+            #[cfg(feature = "gas_evaporation")]
+            gas_target: None,
         };
         let info = mock_info("butler", &[]);
 
@@ -3748,8 +5396,8 @@ mod tests {
         let handle_msg = ExecuteMsg::Redeem {
             amount: Uint128::new(1000),
             denom: Option::from("uscrt".to_string()),
-            decoys: None,
-            entropy: None,
+            #[cfg(feature = "gas_evaporation")]
+            gas_target: None,
             padding: None,
         };
         let info = mock_info("butler", &[]);
@@ -3762,8 +5410,11 @@ mod tests {
             handle_result.err().unwrap()
         );
 
-        let canonical = Addr::unchecked("butler".to_string());
-        assert_eq!(BalancesStore::load(&deps.storage, &canonical), 3000)
+        let canonical = deps
+            .api
+            .addr_canonicalize(Addr::unchecked("butler".to_string()).as_str())
+            .unwrap();
+        assert_eq!(stored_balance(&deps.storage, &canonical).unwrap(), 3000)
     }
 
     #[test]
@@ -3797,8 +5448,8 @@ mod tests {
         );
         // test when deposit disabled
         let handle_msg = ExecuteMsg::Deposit {
-            decoys: None,
-            entropy: None,
+            #[cfg(feature = "gas_evaporation")]
+            gas_target: None,
             padding: None,
         };
         let info = mock_info(
@@ -3814,8 +5465,8 @@ mod tests {
         assert!(error.contains("Tried to deposit an unsupported coin uscrt"));
 
         let handle_msg = ExecuteMsg::Deposit {
-            decoys: None,
-            entropy: None,
+            #[cfg(feature = "gas_evaporation")]
+            gas_target: None,
             padding: None,
         };
 
@@ -3834,8 +5485,38 @@ mod tests {
             handle_result.err().unwrap()
         );
 
-        let canonical = Addr::unchecked("lebron".to_string());
-        assert_eq!(BalancesStore::load(&deps.storage, &canonical), 6000)
+        let canonical = deps
+            .api
+            .addr_canonicalize(Addr::unchecked("lebron".to_string()).as_str())
+            .unwrap();
+
+        // stored balance not updated, still in dwb
+        assert_ne!(stored_balance(&deps.storage, &canonical).unwrap(), 6000);
+
+        let create_vk_msg = ExecuteMsg::CreateViewingKey {
+            entropy: Some("34".to_string()),
+            #[cfg(feature = "gas_evaporation")]
+            gas_target: None,
+            padding: None,
+        };
+        let info = mock_info("lebron", &[]);
+        let handle_response = execute(deps.as_mut(), mock_env(), info, create_vk_msg).unwrap();
+        let vk = match from_binary(&handle_response.data.unwrap()).unwrap() {
+            ExecuteAnswer::CreateViewingKey { key } => key,
+            _ => panic!("Unexpected result from handle"),
+        };
+
+        let query_balance_msg = QueryMsg::Balance {
+            address: "lebron".to_string(),
+            key: vk,
+        };
+
+        let query_response = query(deps.as_ref(), mock_env(), query_balance_msg).unwrap();
+        let balance = match from_binary(&query_response).unwrap() {
+            QueryAnswer::Balance { amount } => amount,
+            _ => panic!("Unexpected result from query"),
+        };
+        assert_eq!(balance, Uint128::new(6000));
     }
 
     #[test]
@@ -3871,8 +5552,8 @@ mod tests {
         let handle_msg = ExecuteMsg::Burn {
             amount: Uint128::new(100),
             memo: None,
-            decoys: None,
-            entropy: None,
+            #[cfg(feature = "gas_evaporation")]
+            gas_target: None,
             padding: None,
         };
         let info = mock_info("lebron", &[]);
@@ -3887,8 +5568,8 @@ mod tests {
         let handle_msg = ExecuteMsg::Burn {
             amount: Uint128::new(burn_amount),
             memo: None,
-            decoys: None,
-            entropy: None,
+            #[cfg(feature = "gas_evaporation")]
+            gas_target: None,
             padding: None,
         };
         let info = mock_info("lebron", &[]);
@@ -3939,8 +5620,8 @@ mod tests {
             recipient: "lebron".to_string(),
             amount: Uint128::new(mint_amount),
             memo: None,
-            decoys: None,
-            entropy: None,
+            #[cfg(feature = "gas_evaporation")]
+            gas_target: None,
             padding: None,
         };
         let info = mock_info("admin", &[]);
@@ -3956,8 +5637,8 @@ mod tests {
             recipient: "lebron".to_string(),
             amount: Uint128::new(mint_amount),
             memo: None,
-            decoys: None,
-            entropy: None,
+            #[cfg(feature = "gas_evaporation")]
+            gas_target: None,
             padding: None,
         };
         let info = mock_info("admin", &[]);
@@ -3997,6 +5678,8 @@ mod tests {
 
         let pause_msg = ExecuteMsg::SetContractStatus {
             level: ContractStatusLevel::StopAllButRedeems,
+            #[cfg(feature = "gas_evaporation")]
+            gas_target: None,
             padding: None,
         };
         let info = mock_info("not_admin", &[]);
@@ -4008,6 +5691,8 @@ mod tests {
 
         let mint_msg = ExecuteMsg::AddMinters {
             minters: vec!["not_admin".to_string()],
+            #[cfg(feature = "gas_evaporation")]
+            gas_target: None,
             padding: None,
         };
         let info = mock_info("not_admin", &[]);
@@ -4019,6 +5704,8 @@ mod tests {
 
         let mint_msg = ExecuteMsg::RemoveMinters {
             minters: vec!["admin".to_string()],
+            #[cfg(feature = "gas_evaporation")]
+            gas_target: None,
             padding: None,
         };
         let info = mock_info("not_admin", &[]);
@@ -4030,6 +5717,8 @@ mod tests {
 
         let mint_msg = ExecuteMsg::SetMinters {
             minters: vec!["not_admin".to_string()],
+            #[cfg(feature = "gas_evaporation")]
+            gas_target: None,
             padding: None,
         };
         let info = mock_info("not_admin", &[]);
@@ -4041,6 +5730,8 @@ mod tests {
 
         let change_admin_msg = ExecuteMsg::ChangeAdmin {
             address: "not_admin".to_string(),
+            #[cfg(feature = "gas_evaporation")]
+            gas_target: None,
             padding: None,
         };
         let info = mock_info("not_admin", &[]);
@@ -4073,6 +5764,8 @@ mod tests {
 
         let pause_msg = ExecuteMsg::SetContractStatus {
             level: ContractStatusLevel::StopAllButRedeems,
+            #[cfg(feature = "gas_evaporation")]
+            gas_target: None,
             padding: None,
         };
 
@@ -4090,8 +5783,8 @@ mod tests {
             recipient: "account".to_string(),
             amount: Uint128::new(123),
             memo: None,
-            decoys: None,
-            entropy: None,
+            #[cfg(feature = "gas_evaporation")]
+            gas_target: None,
             padding: None,
         };
         let info = mock_info("admin", &[]);
@@ -4107,8 +5800,8 @@ mod tests {
         let withdraw_msg = ExecuteMsg::Redeem {
             amount: Uint128::new(5000),
             denom: Option::from("uscrt".to_string()),
-            decoys: None,
-            entropy: None,
+            #[cfg(feature = "gas_evaporation")]
+            gas_target: None,
             padding: None,
         };
         let info = mock_info("lebron", &[]);
@@ -4136,6 +5829,8 @@ mod tests {
 
         let pause_msg = ExecuteMsg::SetContractStatus {
             level: ContractStatusLevel::StopAll,
+            #[cfg(feature = "gas_evaporation")]
+            gas_target: None,
             padding: None,
         };
 
@@ -4153,8 +5848,8 @@ mod tests {
             recipient: "account".to_string(),
             amount: Uint128::new(123),
             memo: None,
-            decoys: None,
-            entropy: None,
+            #[cfg(feature = "gas_evaporation")]
+            gas_target: None,
             padding: None,
         };
         let info = mock_info("admin", &[]);
@@ -4170,8 +5865,8 @@ mod tests {
         let withdraw_msg = ExecuteMsg::Redeem {
             amount: Uint128::new(5000),
             denom: Option::from("uscrt".to_string()),
-            decoys: None,
-            entropy: None,
+            #[cfg(feature = "gas_evaporation")]
+            gas_target: None,
             padding: None,
         };
         let info = mock_info("lebron", &[]);
@@ -4216,6 +5911,8 @@ mod tests {
         // try when mint disabled
         let handle_msg = ExecuteMsg::SetMinters {
             minters: vec!["bob".to_string()],
+            #[cfg(feature = "gas_evaporation")]
+            gas_target: None,
             padding: None,
         };
         let info = mock_info("admin", &[]);
@@ -4227,6 +5924,8 @@ mod tests {
 
         let handle_msg = ExecuteMsg::SetMinters {
             minters: vec!["bob".to_string()],
+            #[cfg(feature = "gas_evaporation")]
+            gas_target: None,
             padding: None,
         };
         let info = mock_info("bob", &[]);
@@ -4238,6 +5937,8 @@ mod tests {
 
         let handle_msg = ExecuteMsg::SetMinters {
             minters: vec!["bob".to_string()],
+            #[cfg(feature = "gas_evaporation")]
+            gas_target: None,
             padding: None,
         };
         let info = mock_info("admin", &[]);
@@ -4250,8 +5951,8 @@ mod tests {
             recipient: "bob".to_string(),
             amount: Uint128::new(100),
             memo: None,
-            decoys: None,
-            entropy: None,
+            #[cfg(feature = "gas_evaporation")]
+            gas_target: None,
             padding: None,
         };
         let info = mock_info("bob", &[]);
@@ -4264,8 +5965,8 @@ mod tests {
             recipient: "bob".to_string(),
             amount: Uint128::new(100),
             memo: None,
-            decoys: None,
-            entropy: None,
+            #[cfg(feature = "gas_evaporation")]
+            gas_target: None,
             padding: None,
         };
         let info = mock_info("admin", &[]);
@@ -4307,6 +6008,8 @@ mod tests {
         // try when mint disabled
         let handle_msg = ExecuteMsg::AddMinters {
             minters: vec!["bob".to_string()],
+            #[cfg(feature = "gas_evaporation")]
+            gas_target: None,
             padding: None,
         };
         let info = mock_info("admin", &[]);
@@ -4318,6 +6021,8 @@ mod tests {
 
         let handle_msg = ExecuteMsg::AddMinters {
             minters: vec!["bob".to_string()],
+            #[cfg(feature = "gas_evaporation")]
+            gas_target: None,
             padding: None,
         };
         let info = mock_info("bob", &[]);
@@ -4329,6 +6034,8 @@ mod tests {
 
         let handle_msg = ExecuteMsg::AddMinters {
             minters: vec!["bob".to_string()],
+            #[cfg(feature = "gas_evaporation")]
+            gas_target: None,
             padding: None,
         };
         let info = mock_info("admin", &[]);
@@ -4341,8 +6048,8 @@ mod tests {
             recipient: "bob".to_string(),
             amount: Uint128::new(100),
             memo: None,
-            decoys: None,
-            entropy: None,
+            #[cfg(feature = "gas_evaporation")]
+            gas_target: None,
             padding: None,
         };
         let info = mock_info("bob", &[]);
@@ -4355,8 +6062,8 @@ mod tests {
             recipient: "bob".to_string(),
             amount: Uint128::new(100),
             memo: None,
-            decoys: None,
-            entropy: None,
+            #[cfg(feature = "gas_evaporation")]
+            gas_target: None,
             padding: None,
         };
         let info = mock_info("admin", &[]);
@@ -4397,6 +6104,8 @@ mod tests {
         // try when mint disabled
         let handle_msg = ExecuteMsg::RemoveMinters {
             minters: vec!["bob".to_string()],
+            #[cfg(feature = "gas_evaporation")]
+            gas_target: None,
             padding: None,
         };
         let info = mock_info("admin", &[]);
@@ -4408,6 +6117,8 @@ mod tests {
 
         let handle_msg = ExecuteMsg::RemoveMinters {
             minters: vec!["admin".to_string()],
+            #[cfg(feature = "gas_evaporation")]
+            gas_target: None,
             padding: None,
         };
         let info = mock_info("bob", &[]);
@@ -4419,6 +6130,8 @@ mod tests {
 
         let handle_msg = ExecuteMsg::RemoveMinters {
             minters: vec!["admin".to_string()],
+            #[cfg(feature = "gas_evaporation")]
+            gas_target: None,
             padding: None,
         };
         let info = mock_info("admin", &[]);
@@ -4431,8 +6144,8 @@ mod tests {
             recipient: "bob".to_string(),
             amount: Uint128::new(100),
             memo: None,
-            decoys: None,
-            entropy: None,
+            #[cfg(feature = "gas_evaporation")]
+            gas_target: None,
             padding: None,
         };
         let info = mock_info("bob", &[]);
@@ -4446,8 +6159,8 @@ mod tests {
             recipient: "bob".to_string(),
             amount: Uint128::new(100),
             memo: None,
-            decoys: None,
-            entropy: None,
+            #[cfg(feature = "gas_evaporation")]
+            gas_target: None,
             padding: None,
         };
         let info = mock_info("admin", &[]);
@@ -4460,6 +6173,8 @@ mod tests {
         // Removing another extra time to ensure nothing funky happens
         let handle_msg = ExecuteMsg::RemoveMinters {
             minters: vec!["admin".to_string()],
+            #[cfg(feature = "gas_evaporation")]
+            gas_target: None,
             padding: None,
         };
         let info = mock_info("admin", &[]);
@@ -4472,8 +6187,8 @@ mod tests {
             recipient: "bob".to_string(),
             amount: Uint128::new(100),
             memo: None,
-            decoys: None,
-            entropy: None,
+            #[cfg(feature = "gas_evaporation")]
+            gas_target: None,
             padding: None,
         };
         let info = mock_info("bob", &[]);
@@ -4487,8 +6202,8 @@ mod tests {
             recipient: "bob".to_string(),
             amount: Uint128::new(100),
             memo: None,
-            decoys: None,
-            entropy: None,
+            #[cfg(feature = "gas_evaporation")]
+            gas_target: None,
             padding: None,
         };
         let info = mock_info("admin", &[]);
@@ -4525,7 +6240,9 @@ mod tests {
         );
 
         let create_vk_msg = ExecuteMsg::CreateViewingKey {
-            entropy: "34".to_string(),
+            entropy: Some("34".to_string()),
+            #[cfg(feature = "gas_evaporation")]
+            gas_target: None,
             padding: None,
         };
         let info = mock_info("giannis", &[]);
@@ -4696,1256 +6413,645 @@ mod tests {
         let init_name = "sec-sec".to_string();
         let init_admin = Addr::unchecked("admin".to_string());
         let init_symbol = "SECSEC".to_string();
-        let init_decimals = 8;
-
-        let init_supply = Uint128::new(5000);
-
-        let mut deps = mock_dependencies_with_balance(&[]);
-        let info = mock_info("instantiator", &[]);
-        let env = mock_env();
-        let init_config: InitConfig = from_binary(&Binary::from(
-            format!(
-                "{{\"public_total_supply\":{},
-            \"enable_deposit\":{},
-            \"enable_redeem\":{},
-            \"enable_mint\":{},
-            \"enable_burn\":{}}}",
-                true, true, false, false, false
-            )
-            .as_bytes(),
-        ))
-        .unwrap();
-        let init_msg = InstantiateMsg {
-            name: init_name.clone(),
-            admin: Some(init_admin.into_string()),
-            symbol: init_symbol.clone(),
-            decimals: init_decimals.clone(),
-            initial_balances: Some(vec![InitialBalance {
-                address: "giannis".to_string(),
-                amount: init_supply,
-            }]),
-            prng_seed: Binary::from("lolz fun yay".as_bytes()),
-            config: Some(init_config),
-            supported_denoms: Some(vec!["uscrt".to_string()]),
-        };
-        let init_result = instantiate(deps.as_mut(), env, info, init_msg);
-        assert!(
-            init_result.is_ok(),
-            "Init failed: {}",
-            init_result.err().unwrap()
-        );
-
-        let query_msg = QueryMsg::ExchangeRate {};
-        let query_result = query(deps.as_ref(), mock_env(), query_msg);
-        assert!(
-            query_result.is_ok(),
-            "Init failed: {}",
-            query_result.err().unwrap()
-        );
-        let query_answer: QueryAnswer = from_binary(&query_result.unwrap()).unwrap();
-        match query_answer {
-            QueryAnswer::ExchangeRate { rate, denom } => {
-                assert_eq!(rate, Uint128::new(100));
-                assert_eq!(denom, "SCRT");
-            }
-            _ => panic!("unexpected"),
-        }
-
-        // test same number of decimals as SCRT
-        let init_name = "sec-sec".to_string();
-        let init_admin = Addr::unchecked("admin".to_string());
-        let init_symbol = "SECSEC".to_string();
-        let init_decimals = 6;
-
-        let init_supply = Uint128::new(5000);
-
-        let mut deps = mock_dependencies_with_balance(&[]);
-        let info = mock_info("instantiator", &[]);
-        let env = mock_env();
-        let init_config: InitConfig = from_binary(&Binary::from(
-            format!(
-                "{{\"public_total_supply\":{},
-            \"enable_deposit\":{},
-            \"enable_redeem\":{},
-            \"enable_mint\":{},
-            \"enable_burn\":{}}}",
-                true, true, false, false, false
-            )
-            .as_bytes(),
-        ))
-        .unwrap();
-        let init_msg = InstantiateMsg {
-            name: init_name.clone(),
-            admin: Some(init_admin.into_string()),
-            symbol: init_symbol.clone(),
-            decimals: init_decimals.clone(),
-            initial_balances: Some(vec![InitialBalance {
-                address: "giannis".to_string(),
-                amount: init_supply,
-            }]),
-            prng_seed: Binary::from("lolz fun yay".as_bytes()),
-            config: Some(init_config),
-            supported_denoms: Some(vec!["uscrt".to_string()]),
-        };
-        let init_result = instantiate(deps.as_mut(), env, info, init_msg);
-        assert!(
-            init_result.is_ok(),
-            "Init failed: {}",
-            init_result.err().unwrap()
-        );
-
-        let query_msg = QueryMsg::ExchangeRate {};
-        let query_result = query(deps.as_ref(), mock_env(), query_msg);
-        assert!(
-            query_result.is_ok(),
-            "Init failed: {}",
-            query_result.err().unwrap()
-        );
-        let query_answer: QueryAnswer = from_binary(&query_result.unwrap()).unwrap();
-        match query_answer {
-            QueryAnswer::ExchangeRate { rate, denom } => {
-                assert_eq!(rate, Uint128::new(1));
-                assert_eq!(denom, "SCRT");
-            }
-            _ => panic!("unexpected"),
-        }
-
-        // test less decimal places than SCRT
-        let init_name = "sec-sec".to_string();
-        let init_admin = Addr::unchecked("admin".to_string());
-        let init_symbol = "SECSEC".to_string();
-        let init_decimals = 3;
-
-        let init_supply = Uint128::new(5000);
-
-        let mut deps = mock_dependencies_with_balance(&[]);
-        let info = mock_info("instantiator", &[]);
-        let env = mock_env();
-        let init_config: InitConfig = from_binary(&Binary::from(
-            format!(
-                "{{\"public_total_supply\":{},
-            \"enable_deposit\":{},
-            \"enable_redeem\":{},
-            \"enable_mint\":{},
-            \"enable_burn\":{}}}",
-                true, true, false, false, false
-            )
-            .as_bytes(),
-        ))
-        .unwrap();
-        let init_msg = InstantiateMsg {
-            name: init_name.clone(),
-            admin: Some(init_admin.into_string()),
-            symbol: init_symbol.clone(),
-            decimals: init_decimals.clone(),
-            initial_balances: Some(vec![InitialBalance {
-                address: "giannis".to_string(),
-                amount: init_supply,
-            }]),
-            prng_seed: Binary::from("lolz fun yay".as_bytes()),
-            config: Some(init_config),
-            supported_denoms: Some(vec!["uscrt".to_string()]),
-        };
-        let init_result = instantiate(deps.as_mut(), env, info, init_msg);
-        assert!(
-            init_result.is_ok(),
-            "Init failed: {}",
-            init_result.err().unwrap()
-        );
-
-        let query_msg = QueryMsg::ExchangeRate {};
-        let query_result = query(deps.as_ref(), mock_env(), query_msg);
-        assert!(
-            query_result.is_ok(),
-            "Init failed: {}",
-            query_result.err().unwrap()
-        );
-        let query_answer: QueryAnswer = from_binary(&query_result.unwrap()).unwrap();
-        match query_answer {
-            QueryAnswer::ExchangeRate { rate, denom } => {
-                assert_eq!(rate, Uint128::new(1000));
-                assert_eq!(denom, "SECSEC");
-            }
-            _ => panic!("unexpected"),
-        }
-
-        // test depost/redeem not enabled
-        let init_name = "sec-sec".to_string();
-        let init_admin = Addr::unchecked("admin".to_string());
-        let init_symbol = "SECSEC".to_string();
-        let init_decimals = 3;
-
-        let init_supply = Uint128::new(5000);
-
-        let mut deps = mock_dependencies_with_balance(&[]);
-        let info = mock_info("instantiator", &[]);
-        let env = mock_env();
-        let init_msg = InstantiateMsg {
-            name: init_name.clone(),
-            admin: Some(init_admin.into_string()),
-            symbol: init_symbol.clone(),
-            decimals: init_decimals.clone(),
-            initial_balances: Some(vec![InitialBalance {
-                address: "giannis".to_string(),
-                amount: init_supply,
-            }]),
-            prng_seed: Binary::from("lolz fun yay".as_bytes()),
-            config: None,
-            supported_denoms: None,
-        };
-        let init_result = instantiate(deps.as_mut(), env, info, init_msg);
-        assert!(
-            init_result.is_ok(),
-            "Init failed: {}",
-            init_result.err().unwrap()
-        );
-
-        let query_msg = QueryMsg::ExchangeRate {};
-        let query_result = query(deps.as_ref(), mock_env(), query_msg);
-        assert!(
-            query_result.is_ok(),
-            "Init failed: {}",
-            query_result.err().unwrap()
-        );
-        let query_answer: QueryAnswer = from_binary(&query_result.unwrap()).unwrap();
-        match query_answer {
-            QueryAnswer::ExchangeRate { rate, denom } => {
-                assert_eq!(rate, Uint128::new(0));
-                assert_eq!(denom, String::new());
-            }
-            _ => panic!("unexpected"),
-        }
-    }
-
-    #[test]
-    fn test_query_allowance() {
-        let (init_result, mut deps) = init_helper(vec![InitialBalance {
-            address: "giannis".to_string(),
-            amount: Uint128::new(5000),
-        }]);
-        assert!(
-            init_result.is_ok(),
-            "Init failed: {}",
-            init_result.err().unwrap()
-        );
-
-        let handle_msg = ExecuteMsg::IncreaseAllowance {
-            spender: "lebron".to_string(),
-            amount: Uint128::new(2000),
-            padding: None,
-            expiration: None,
-        };
-        let info = mock_info("giannis", &[]);
-
-        let handle_result = execute(deps.as_mut(), mock_env(), info, handle_msg);
-
-        assert!(
-            handle_result.is_ok(),
-            "handle() failed: {}",
-            handle_result.err().unwrap()
-        );
-
-        let vk1 = "key1".to_string();
-        let vk2 = "key2".to_string();
-
-        let query_msg = QueryMsg::Allowance {
-            owner: "giannis".to_string(),
-            spender: "lebron".to_string(),
-            key: vk1.clone(),
-        };
-        let query_result = query(deps.as_ref(), mock_env(), query_msg);
-        assert!(
-            query_result.is_ok(),
-            "Query failed: {}",
-            query_result.err().unwrap()
-        );
-        let error = extract_error_msg(query_result);
-        assert!(error.contains("Wrong viewing key"));
-
-        let handle_msg = ExecuteMsg::SetViewingKey {
-            key: vk1.clone(),
-            padding: None,
-        };
-        let info = mock_info("lebron", &[]);
-
-        let handle_result = execute(deps.as_mut(), mock_env(), info, handle_msg);
-
-        let unwrapped_result: ExecuteAnswer =
-            from_binary(&handle_result.unwrap().data.unwrap()).unwrap();
-        assert_eq!(
-            to_binary(&unwrapped_result).unwrap(),
-            to_binary(&ExecuteAnswer::SetViewingKey {
-                status: ResponseStatus::Success
-            })
-            .unwrap(),
-        );
-
-        let handle_msg = ExecuteMsg::SetViewingKey {
-            key: vk2.clone(),
-            padding: None,
-        };
-        let info = mock_info("giannis", &[]);
-
-        let handle_result = execute(deps.as_mut(), mock_env(), info, handle_msg);
-
-        let unwrapped_result: ExecuteAnswer =
-            from_binary(&handle_result.unwrap().data.unwrap()).unwrap();
-        assert_eq!(
-            to_binary(&unwrapped_result).unwrap(),
-            to_binary(&ExecuteAnswer::SetViewingKey {
-                status: ResponseStatus::Success
-            })
-            .unwrap(),
-        );
-
-        let query_msg = QueryMsg::Allowance {
-            owner: "giannis".to_string(),
-            spender: "lebron".to_string(),
-            key: vk1.clone(),
-        };
-        let query_result = query(deps.as_ref(), mock_env(), query_msg);
-        let allowance = match from_binary(&query_result.unwrap()).unwrap() {
-            QueryAnswer::Allowance { allowance, .. } => allowance,
-            _ => panic!("Unexpected"),
-        };
-        assert_eq!(allowance, Uint128::new(2000));
-
-        let query_msg = QueryMsg::Allowance {
-            owner: "giannis".to_string(),
-            spender: "lebron".to_string(),
-            key: vk2.clone(),
-        };
-        let query_result = query(deps.as_ref(), mock_env(), query_msg);
-        let allowance = match from_binary(&query_result.unwrap()).unwrap() {
-            QueryAnswer::Allowance { allowance, .. } => allowance,
-            _ => panic!("Unexpected"),
-        };
-        assert_eq!(allowance, Uint128::new(2000));
-
-        let query_msg = QueryMsg::Allowance {
-            owner: "lebron".to_string(),
-            spender: "giannis".to_string(),
-            key: vk2.clone(),
-        };
-        let query_result = query(deps.as_ref(), mock_env(), query_msg);
-        let allowance = match from_binary(&query_result.unwrap()).unwrap() {
-            QueryAnswer::Allowance { allowance, .. } => allowance,
-            _ => panic!("Unexpected"),
-        };
-        assert_eq!(allowance, Uint128::new(0));
-    }
-
-    #[test]
-    fn test_query_all_allowances() {
-        let num_owners = 3;
-        let num_spenders = 20;
-        let vk = "key".to_string();
-
-        let initial_balances: Vec<InitialBalance> = (0..num_owners)
-            .into_iter()
-            .map(|i| InitialBalance {
-                address: format!("owner{}", i),
-                amount: Uint128::new(5000),
-            })
-            .collect();
-        let (init_result, mut deps) = init_helper(initial_balances);
-        assert!(
-            init_result.is_ok(),
-            "Init failed: {}",
-            init_result.err().unwrap()
-        );
-        for i in 0..num_owners {
-            let handle_msg = ExecuteMsg::SetViewingKey {
-                key: vk.clone(),
-                padding: None,
-            };
-            let info = mock_info(format!("owner{}", i).as_str(), &[]);
-
-            let handle_result = execute(deps.as_mut(), mock_env(), info, handle_msg);
-
-            let unwrapped_result: ExecuteAnswer =
-                from_binary(&handle_result.unwrap().data.unwrap()).unwrap();
-            assert_eq!(
-                to_binary(&unwrapped_result).unwrap(),
-                to_binary(&ExecuteAnswer::SetViewingKey {
-                    status: ResponseStatus::Success
-                })
-                .unwrap(),
-            );
-        }
-
-        for i in 0..num_owners {
-            for j in 0..num_spenders {
-                let handle_msg = ExecuteMsg::IncreaseAllowance {
-                    spender: format!("spender{}", j),
-                    amount: Uint128::new(50),
-                    padding: None,
-                    expiration: None,
-                };
-                let info = mock_info(format!("owner{}", i).as_str(), &[]);
-
-                let handle_result = execute(deps.as_mut(), mock_env(), info, handle_msg);
-                assert!(
-                    handle_result.is_ok(),
-                    "handle() failed: {}",
-                    handle_result.err().unwrap()
-                );
-
-                let handle_msg = ExecuteMsg::SetViewingKey {
-                    key: vk.clone(),
-                    padding: None,
-                };
-                let info = mock_info(format!("spender{}", j).as_str(), &[]);
-
-                let handle_result = execute(deps.as_mut(), mock_env(), info, handle_msg);
-
-                let unwrapped_result: ExecuteAnswer =
-                    from_binary(&handle_result.unwrap().data.unwrap()).unwrap();
-                assert_eq!(
-                    to_binary(&unwrapped_result).unwrap(),
-                    to_binary(&ExecuteAnswer::SetViewingKey {
-                        status: ResponseStatus::Success
-                    })
-                    .unwrap(),
-                );
-            }
-        }
-
-        let query_msg = QueryMsg::AllowancesGiven {
-            owner: "owner0".to_string(),
-            key: vk.clone(),
-            page: None,
-            page_size: 5,
-        };
-        let query_result = query(deps.as_ref(), mock_env(), query_msg);
-        match from_binary(&query_result.unwrap()).unwrap() {
-            QueryAnswer::AllowancesGiven {
-                owner,
-                allowances,
-                count,
-            } => {
-                assert_eq!(owner, "owner0".to_string());
-                assert_eq!(allowances.len(), 5);
-                assert_eq!(allowances[0].spender, "spender0");
-                assert_eq!(allowances[0].allowance, Uint128::from(50_u128));
-                assert_eq!(allowances[0].expiration, None);
-                assert_eq!(count, num_spenders);
-            }
-            _ => panic!("Unexpected"),
-        };
-
-        let query_msg = QueryMsg::AllowancesGiven {
-            owner: "owner1".to_string(),
-            key: vk.clone(),
-            page: Some(1),
-            page_size: 5,
-        };
-        let query_result = query(deps.as_ref(), mock_env(), query_msg);
-        match from_binary(&query_result.unwrap()).unwrap() {
-            QueryAnswer::AllowancesGiven {
-                owner,
-                allowances,
-                count,
-            } => {
-                assert_eq!(owner, "owner1".to_string());
-                assert_eq!(allowances.len(), 5);
-                assert_eq!(allowances[0].spender, "spender5");
-                assert_eq!(allowances[0].allowance, Uint128::from(50_u128));
-                assert_eq!(allowances[0].expiration, None);
-                assert_eq!(count, num_spenders);
-            }
-            _ => panic!("Unexpected"),
-        };
-
-        let query_msg = QueryMsg::AllowancesGiven {
-            owner: "owner1".to_string(),
-            key: vk.clone(),
-            page: Some(0),
-            page_size: 23,
-        };
-        let query_result = query(deps.as_ref(), mock_env(), query_msg);
-        match from_binary(&query_result.unwrap()).unwrap() {
-            QueryAnswer::AllowancesGiven {
-                owner,
-                allowances,
-                count,
-            } => {
-                assert_eq!(owner, "owner1".to_string());
-                assert_eq!(allowances.len(), 20);
-                assert_eq!(count, num_spenders);
-            }
-            _ => panic!("Unexpected"),
-        };
-
-        let query_msg = QueryMsg::AllowancesGiven {
-            owner: "owner1".to_string(),
-            key: vk.clone(),
-            page: Some(2),
-            page_size: 8,
-        };
-        let query_result = query(deps.as_ref(), mock_env(), query_msg);
-        match from_binary(&query_result.unwrap()).unwrap() {
-            QueryAnswer::AllowancesGiven {
-                owner,
-                allowances,
-                count,
-            } => {
-                assert_eq!(owner, "owner1".to_string());
-                assert_eq!(allowances.len(), 4);
-                assert_eq!(count, num_spenders);
-            }
-            _ => panic!("Unexpected"),
-        };
-
-        let query_msg = QueryMsg::AllowancesGiven {
-            owner: "owner2".to_string(),
-            key: vk.clone(),
-            page: Some(5),
-            page_size: 5,
-        };
-        let query_result = query(deps.as_ref(), mock_env(), query_msg);
-        match from_binary(&query_result.unwrap()).unwrap() {
-            QueryAnswer::AllowancesGiven {
-                owner,
-                allowances,
-                count,
-            } => {
-                assert_eq!(owner, "owner2".to_string());
-                assert_eq!(allowances.len(), 0);
-                assert_eq!(count, num_spenders);
-            }
-            _ => panic!("Unexpected"),
-        };
-
-        let query_msg = QueryMsg::AllowancesReceived {
-            spender: "spender0".to_string(),
-            key: vk.clone(),
-            page: None,
-            page_size: 10,
-        };
-        let query_result = query(deps.as_ref(), mock_env(), query_msg);
-        match from_binary(&query_result.unwrap()).unwrap() {
-            QueryAnswer::AllowancesReceived {
-                spender,
-                allowances,
-                count,
-            } => {
-                assert_eq!(spender, "spender0".to_string());
-                assert_eq!(allowances.len(), 3);
-                assert_eq!(allowances[0].owner, "owner0");
-                assert_eq!(allowances[0].allowance, Uint128::from(50_u128));
-                assert_eq!(allowances[0].expiration, None);
-                assert_eq!(count, num_owners);
-            }
-            _ => panic!("Unexpected"),
-        };
-
-        let query_msg = QueryMsg::AllowancesReceived {
-            spender: "spender1".to_string(),
-            key: vk.clone(),
-            page: Some(1),
-            page_size: 1,
-        };
-        let query_result = query(deps.as_ref(), mock_env(), query_msg);
-        match from_binary(&query_result.unwrap()).unwrap() {
-            QueryAnswer::AllowancesReceived {
-                spender,
-                allowances,
-                count,
-            } => {
-                assert_eq!(spender, "spender1".to_string());
-                assert_eq!(allowances.len(), 1);
-                assert_eq!(allowances[0].owner, "owner1");
-                assert_eq!(allowances[0].allowance, Uint128::from(50_u128));
-                assert_eq!(allowances[0].expiration, None);
-                assert_eq!(count, num_owners);
-            }
-            _ => panic!("Unexpected"),
-        };
-    }
+        let init_decimals = 8;
 
-    #[test]
-    fn test_query_balance() {
-        let (init_result, mut deps) = init_helper(vec![InitialBalance {
-            address: "bob".to_string(),
-            amount: Uint128::new(5000),
-        }]);
+        let init_supply = Uint128::new(5000);
+
+        let mut deps = mock_dependencies_with_balance(&[]);
+        let info = mock_info("instantiator", &[]);
+        let env = mock_env();
+        let init_config: InitConfig = from_binary(&Binary::from(
+            format!(
+                "{{\"public_total_supply\":{},
+                \"enable_deposit\":{},
+                \"enable_redeem\":{},
+                \"enable_mint\":{},
+                \"enable_burn\":{}}}",
+                true, true, false, false, false
+            )
+            .as_bytes(),
+        ))
+        .unwrap();
+        let init_msg = InstantiateMsg {
+            name: init_name.clone(),
+            admin: Some(init_admin.into_string()),
+            symbol: init_symbol.clone(),
+            decimals: init_decimals.clone(),
+            initial_balances: Some(vec![InitialBalance {
+                address: "giannis".to_string(),
+                amount: init_supply,
+            }]),
+            prng_seed: Binary::from("lolz fun yay".as_bytes()),
+            config: Some(init_config),
+            supported_denoms: Some(vec!["uscrt".to_string()]),
+        };
+        let init_result = instantiate(deps.as_mut(), env, info, init_msg);
         assert!(
             init_result.is_ok(),
             "Init failed: {}",
             init_result.err().unwrap()
         );
 
-        let handle_msg = ExecuteMsg::SetViewingKey {
-            key: "key".to_string(),
-            padding: None,
-        };
-        let info = mock_info("bob", &[]);
-
-        let handle_result = execute(deps.as_mut(), mock_env(), info, handle_msg);
-
-        let unwrapped_result: ExecuteAnswer =
-            from_binary(&handle_result.unwrap().data.unwrap()).unwrap();
-        assert_eq!(
-            to_binary(&unwrapped_result).unwrap(),
-            to_binary(&ExecuteAnswer::SetViewingKey {
-                status: ResponseStatus::Success
-            })
-            .unwrap(),
+        let query_msg = QueryMsg::ExchangeRate {};
+        let query_result = query(deps.as_ref(), mock_env(), query_msg);
+        assert!(
+            query_result.is_ok(),
+            "Init failed: {}",
+            query_result.err().unwrap()
         );
+        let query_answer: QueryAnswer = from_binary(&query_result.unwrap()).unwrap();
+        match query_answer {
+            QueryAnswer::ExchangeRate { rate, denom } => {
+                assert_eq!(rate, Uint128::new(100));
+                assert_eq!(denom, "SCRT");
+            }
+            _ => panic!("unexpected"),
+        }
 
-        let query_msg = QueryMsg::Balance {
-            address: "bob".to_string(),
-            key: "wrong_key".to_string(),
-        };
-        let query_result = query(deps.as_ref(), mock_env(), query_msg);
-        let error = extract_error_msg(query_result);
-        assert!(error.contains("Wrong viewing key"));
+        // test same number of decimals as SCRT
+        let init_name = "sec-sec".to_string();
+        let init_admin = Addr::unchecked("admin".to_string());
+        let init_symbol = "SECSEC".to_string();
+        let init_decimals = 6;
 
-        let query_msg = QueryMsg::Balance {
-            address: "bob".to_string(),
-            key: "key".to_string(),
-        };
-        let query_result = query(deps.as_ref(), mock_env(), query_msg);
-        let balance = match from_binary(&query_result.unwrap()).unwrap() {
-            QueryAnswer::Balance { amount } => amount,
-            _ => panic!("Unexpected"),
-        };
-        assert_eq!(balance, Uint128::new(5000));
-    }
+        let init_supply = Uint128::new(5000);
 
-    #[test]
-    fn test_query_transfer_history() {
-        let (init_result, mut deps) = init_helper(vec![InitialBalance {
-            address: "bob".to_string(),
-            amount: Uint128::new(5000),
-        }]);
+        let mut deps = mock_dependencies_with_balance(&[]);
+        let info = mock_info("instantiator", &[]);
+        let env = mock_env();
+        let init_config: InitConfig = from_binary(&Binary::from(
+            format!(
+                "{{\"public_total_supply\":{},
+            \"enable_deposit\":{},
+            \"enable_redeem\":{},
+            \"enable_mint\":{},
+            \"enable_burn\":{}}}",
+                true, true, false, false, false
+            )
+            .as_bytes(),
+        ))
+        .unwrap();
+        let init_msg = InstantiateMsg {
+            name: init_name.clone(),
+            admin: Some(init_admin.into_string()),
+            symbol: init_symbol.clone(),
+            decimals: init_decimals.clone(),
+            initial_balances: Some(vec![InitialBalance {
+                address: "giannis".to_string(),
+                amount: init_supply,
+            }]),
+            prng_seed: Binary::from("lolz fun yay".as_bytes()),
+            config: Some(init_config),
+            supported_denoms: Some(vec!["uscrt".to_string()]),
+        };
+        let init_result = instantiate(deps.as_mut(), env, info, init_msg);
         assert!(
             init_result.is_ok(),
             "Init failed: {}",
             init_result.err().unwrap()
         );
 
-        let handle_msg = ExecuteMsg::SetViewingKey {
-            key: "key".to_string(),
-            padding: None,
-        };
-        let info = mock_info("bob", &[]);
-
-        let handle_result = execute(deps.as_mut(), mock_env(), info, handle_msg);
-
-        assert!(ensure_success(handle_result.unwrap()));
-
-        let handle_msg = ExecuteMsg::Transfer {
-            recipient: "alice".to_string(),
-            amount: Uint128::new(1000),
-            memo: None,
-            decoys: None,
-            entropy: None,
-            padding: None,
-        };
-        let info = mock_info("bob", &[]);
-
-        let handle_result = execute(deps.as_mut(), mock_env(), info, handle_msg);
+        let query_msg = QueryMsg::ExchangeRate {};
+        let query_result = query(deps.as_ref(), mock_env(), query_msg);
+        assert!(
+            query_result.is_ok(),
+            "Init failed: {}",
+            query_result.err().unwrap()
+        );
+        let query_answer: QueryAnswer = from_binary(&query_result.unwrap()).unwrap();
+        match query_answer {
+            QueryAnswer::ExchangeRate { rate, denom } => {
+                assert_eq!(rate, Uint128::new(1));
+                assert_eq!(denom, "SCRT");
+            }
+            _ => panic!("unexpected"),
+        }
 
-        let result = handle_result.unwrap();
-        assert!(ensure_success(result));
-        let handle_msg = ExecuteMsg::Transfer {
-            recipient: "banana".to_string(),
-            amount: Uint128::new(500),
-            memo: None,
-            decoys: None,
-            entropy: None,
-            padding: None,
-        };
-        let info = mock_info("bob", &[]);
+        // test less decimal places than SCRT
+        let init_name = "sec-sec".to_string();
+        let init_admin = Addr::unchecked("admin".to_string());
+        let init_symbol = "SECSEC".to_string();
+        let init_decimals = 3;
 
-        let handle_result = execute(deps.as_mut(), mock_env(), info, handle_msg);
+        let init_supply = Uint128::new(5000);
 
-        let result = handle_result.unwrap();
-        assert!(ensure_success(result));
-        let handle_msg = ExecuteMsg::Transfer {
-            recipient: "mango".to_string(),
-            amount: Uint128::new(2500),
-            memo: None,
-            decoys: None,
-            entropy: None,
-            padding: None,
+        let mut deps = mock_dependencies_with_balance(&[]);
+        let info = mock_info("instantiator", &[]);
+        let env = mock_env();
+        let init_config: InitConfig = from_binary(&Binary::from(
+            format!(
+                "{{\"public_total_supply\":{},
+            \"enable_deposit\":{},
+            \"enable_redeem\":{},
+            \"enable_mint\":{},
+            \"enable_burn\":{}}}",
+                true, true, false, false, false
+            )
+            .as_bytes(),
+        ))
+        .unwrap();
+        let init_msg = InstantiateMsg {
+            name: init_name.clone(),
+            admin: Some(init_admin.into_string()),
+            symbol: init_symbol.clone(),
+            decimals: init_decimals.clone(),
+            initial_balances: Some(vec![InitialBalance {
+                address: "giannis".to_string(),
+                amount: init_supply,
+            }]),
+            prng_seed: Binary::from("lolz fun yay".as_bytes()),
+            config: Some(init_config),
+            supported_denoms: Some(vec!["uscrt".to_string()]),
         };
-        let info = mock_info("bob", &[]);
-
-        let handle_result = execute(deps.as_mut(), mock_env(), info, handle_msg);
-
-        let result = handle_result.unwrap();
-        assert!(ensure_success(result));
+        let init_result = instantiate(deps.as_mut(), env, info, init_msg);
+        assert!(
+            init_result.is_ok(),
+            "Init failed: {}",
+            init_result.err().unwrap()
+        );
 
-        let query_msg = QueryMsg::TransferHistory {
-            address: "bob".to_string(),
-            key: "key".to_string(),
-            page: None,
-            page_size: 0,
-            should_filter_decoys: false,
-        };
+        let query_msg = QueryMsg::ExchangeRate {};
         let query_result = query(deps.as_ref(), mock_env(), query_msg);
-        // let a: QueryAnswer = from_binary(&query_result.unwrap()).unwrap();
-        // println!("{:?}", a);
-        let transfers = match from_binary(&query_result.unwrap()).unwrap() {
-            QueryAnswer::TransferHistory { txs, .. } => txs,
-            _ => panic!("Unexpected"),
-        };
-        assert!(transfers.is_empty());
+        assert!(
+            query_result.is_ok(),
+            "Init failed: {}",
+            query_result.err().unwrap()
+        );
+        let query_answer: QueryAnswer = from_binary(&query_result.unwrap()).unwrap();
+        match query_answer {
+            QueryAnswer::ExchangeRate { rate, denom } => {
+                assert_eq!(rate, Uint128::new(1000));
+                assert_eq!(denom, "SECSEC");
+            }
+            _ => panic!("unexpected"),
+        }
 
-        let query_msg = QueryMsg::TransferHistory {
-            address: "bob".to_string(),
-            key: "key".to_string(),
-            page: None,
-            page_size: 10,
-            should_filter_decoys: false,
-        };
-        let query_result = query(deps.as_ref(), mock_env(), query_msg);
-        let transfers = match from_binary(&query_result.unwrap()).unwrap() {
-            QueryAnswer::TransferHistory { txs, .. } => txs,
-            _ => panic!("Unexpected"),
-        };
-        assert_eq!(transfers.len(), 3);
+        // test depost/redeem not enabled
+        let init_name = "sec-sec".to_string();
+        let init_admin = Addr::unchecked("admin".to_string());
+        let init_symbol = "SECSEC".to_string();
+        let init_decimals = 3;
 
-        let query_msg = QueryMsg::TransferHistory {
-            address: "bob".to_string(),
-            key: "key".to_string(),
-            page: None,
-            page_size: 2,
-            should_filter_decoys: false,
-        };
-        let query_result = query(deps.as_ref(), mock_env(), query_msg);
-        let transfers = match from_binary(&query_result.unwrap()).unwrap() {
-            QueryAnswer::TransferHistory { txs, .. } => txs,
-            _ => panic!("Unexpected"),
-        };
-        assert_eq!(transfers.len(), 2);
+        let init_supply = Uint128::new(5000);
 
-        let query_msg = QueryMsg::TransferHistory {
-            address: "bob".to_string(),
-            key: "key".to_string(),
-            page: Some(1),
-            page_size: 2,
-            should_filter_decoys: false,
+        let mut deps = mock_dependencies_with_balance(&[]);
+        let info = mock_info("instantiator", &[]);
+        let env = mock_env();
+        let init_msg = InstantiateMsg {
+            name: init_name.clone(),
+            admin: Some(init_admin.into_string()),
+            symbol: init_symbol.clone(),
+            decimals: init_decimals.clone(),
+            initial_balances: Some(vec![InitialBalance {
+                address: "giannis".to_string(),
+                amount: init_supply,
+            }]),
+            prng_seed: Binary::from("lolz fun yay".as_bytes()),
+            config: None,
+            supported_denoms: None,
         };
+        let init_result = instantiate(deps.as_mut(), env, info, init_msg);
+        assert!(
+            init_result.is_ok(),
+            "Init failed: {}",
+            init_result.err().unwrap()
+        );
+
+        let query_msg = QueryMsg::ExchangeRate {};
         let query_result = query(deps.as_ref(), mock_env(), query_msg);
-        let transfers = match from_binary(&query_result.unwrap()).unwrap() {
-            QueryAnswer::TransferHistory { txs, .. } => txs,
-            _ => panic!("Unexpected"),
-        };
-        assert_eq!(transfers.len(), 1);
+        assert!(
+            query_result.is_ok(),
+            "Init failed: {}",
+            query_result.err().unwrap()
+        );
+        let query_answer: QueryAnswer = from_binary(&query_result.unwrap()).unwrap();
+        match query_answer {
+            QueryAnswer::ExchangeRate { rate, denom } => {
+                assert_eq!(rate, Uint128::new(0));
+                assert_eq!(denom, String::new());
+            }
+            _ => panic!("unexpected"),
+        }
     }
 
     #[test]
-    fn test_query_transfer_history_with_decoys() {
-        let (init_result, mut deps) = init_helper(vec![
-            InitialBalance {
-                address: "bob".to_string(),
-                amount: Uint128::new(5000),
-            },
-            InitialBalance {
-                address: "jhon".to_string(),
-                amount: Uint128::new(7000),
-            },
-        ]);
+    fn test_query_allowance() {
+        let (init_result, mut deps) = init_helper(vec![InitialBalance {
+            address: "giannis".to_string(),
+            amount: Uint128::new(5000),
+        }]);
         assert!(
             init_result.is_ok(),
             "Init failed: {}",
             init_result.err().unwrap()
         );
 
-        let handle_msg = ExecuteMsg::SetViewingKey {
-            key: "key".to_string(),
+        let handle_msg = ExecuteMsg::IncreaseAllowance {
+            spender: "lebron".to_string(),
+            amount: Uint128::new(2000),
             padding: None,
+            #[cfg(feature = "gas_evaporation")]
+            gas_target: None,
+            expiration: None,
         };
-        let info = mock_info("bob", &[]);
+        let info = mock_info("giannis", &[]);
 
         let handle_result = execute(deps.as_mut(), mock_env(), info, handle_msg);
-        assert!(ensure_success(handle_result.unwrap()));
 
-        let handle_msg = ExecuteMsg::SetViewingKey {
-            key: "alice_key".to_string(),
-            padding: None,
-        };
-        let info = mock_info("alice", &[]);
+        assert!(
+            handle_result.is_ok(),
+            "handle() failed: {}",
+            handle_result.err().unwrap()
+        );
 
-        let handle_result = execute(deps.as_mut(), mock_env(), info, handle_msg);
-        assert!(ensure_success(handle_result.unwrap()));
+        let vk1 = "key1".to_string();
+        let vk2 = "key2".to_string();
 
-        let handle_msg = ExecuteMsg::SetViewingKey {
-            key: "lior_key".to_string(),
-            padding: None,
+        let query_msg = QueryMsg::Allowance {
+            owner: "giannis".to_string(),
+            spender: "lebron".to_string(),
+            key: vk1.clone(),
         };
-        let info = mock_info("lior", &[]);
-
-        let handle_result = execute(deps.as_mut(), mock_env(), info, handle_msg);
-        assert!(ensure_success(handle_result.unwrap()));
+        let query_result = query(deps.as_ref(), mock_env(), query_msg);
+        assert!(
+            query_result.is_ok(),
+            "Query failed: {}",
+            query_result.err().unwrap()
+        );
+        let error = extract_error_msg(query_result);
+        assert!(error.contains("Wrong viewing key"));
 
         let handle_msg = ExecuteMsg::SetViewingKey {
-            key: "banana_key".to_string(),
+            key: vk1.clone(),
+            #[cfg(feature = "gas_evaporation")]
+            gas_target: None,
             padding: None,
         };
-        let info = mock_info("banana", &[]);
+        let info = mock_info("lebron", &[]);
 
         let handle_result = execute(deps.as_mut(), mock_env(), info, handle_msg);
 
-        assert!(ensure_success(handle_result.unwrap()));
-
-        let lior_addr = Addr::unchecked("lior".to_string());
-        let jhon_addr = Addr::unchecked("jhon".to_string());
-        let alice_addr = Addr::unchecked("alice".to_string());
-
-        let handle_msg = ExecuteMsg::Transfer {
-            recipient: "alice".to_string(),
-            amount: Uint128::new(1000),
-            memo: None,
-            decoys: Some(vec![
-                lior_addr.clone(),
-                jhon_addr.clone(),
-                alice_addr.clone(),
-            ]),
-
-            entropy: Some(Binary::from_base64("VEVTVFRFU1RURVNUQ0hFQ0tDSEVDSw==").unwrap()),
-            padding: None,
-        };
-        let info = mock_info("bob", &[]);
-
-        let handle_result = execute(deps.as_mut(), mock_env(), info, handle_msg);
+        let unwrapped_result: ExecuteAnswer =
+            from_binary(&handle_result.unwrap().data.unwrap()).unwrap();
+        assert_eq!(
+            to_binary(&unwrapped_result).unwrap(),
+            to_binary(&ExecuteAnswer::SetViewingKey {
+                status: ResponseStatus::Success
+            })
+            .unwrap(),
+        );
 
-        let result = handle_result.unwrap();
-        assert!(ensure_success(result));
-        let handle_msg = ExecuteMsg::Transfer {
-            recipient: "banana".to_string(),
-            amount: Uint128::new(500),
-            memo: None,
-            decoys: None,
-            entropy: None,
+        let handle_msg = ExecuteMsg::SetViewingKey {
+            key: vk2.clone(),
+            #[cfg(feature = "gas_evaporation")]
+            gas_target: None,
             padding: None,
         };
-        let info = mock_info("bob", &[]);
+        let info = mock_info("giannis", &[]);
 
         let handle_result = execute(deps.as_mut(), mock_env(), info, handle_msg);
 
-        let result = handle_result.unwrap();
-        assert!(ensure_success(result));
+        let unwrapped_result: ExecuteAnswer =
+            from_binary(&handle_result.unwrap().data.unwrap()).unwrap();
+        assert_eq!(
+            to_binary(&unwrapped_result).unwrap(),
+            to_binary(&ExecuteAnswer::SetViewingKey {
+                status: ResponseStatus::Success
+            })
+            .unwrap(),
+        );
 
-        let query_msg = QueryMsg::TransferHistory {
-            address: "bob".to_string(),
-            key: "key".to_string(),
-            page: None,
-            page_size: 10,
-            should_filter_decoys: true,
+        let query_msg = QueryMsg::Allowance {
+            owner: "giannis".to_string(),
+            spender: "lebron".to_string(),
+            key: vk1.clone(),
         };
         let query_result = query(deps.as_ref(), mock_env(), query_msg);
-        let transfers = match from_binary(&query_result.unwrap()).unwrap() {
-            QueryAnswer::TransferHistory { txs, .. } => txs,
+        let allowance = match from_binary(&query_result.unwrap()).unwrap() {
+            QueryAnswer::Allowance { allowance, .. } => allowance,
             _ => panic!("Unexpected"),
         };
-        assert_eq!(transfers.len(), 2);
+        assert_eq!(allowance, Uint128::new(2000));
 
-        let query_msg = QueryMsg::TransferHistory {
-            address: "alice".to_string(),
-            key: "alice_key".to_string(),
-            page: None,
-            page_size: 10,
-            should_filter_decoys: false,
+        let query_msg = QueryMsg::Allowance {
+            owner: "giannis".to_string(),
+            spender: "lebron".to_string(),
+            key: vk2.clone(),
         };
         let query_result = query(deps.as_ref(), mock_env(), query_msg);
-        let transfers = match from_binary(&query_result.unwrap()).unwrap() {
-            QueryAnswer::TransferHistory { txs, .. } => txs,
+        let allowance = match from_binary(&query_result.unwrap()).unwrap() {
+            QueryAnswer::Allowance { allowance, .. } => allowance,
             _ => panic!("Unexpected"),
         };
-        assert_eq!(transfers.len(), 2);
+        assert_eq!(allowance, Uint128::new(2000));
 
-        let query_msg = QueryMsg::TransferHistory {
-            address: "alice".to_string(),
-            key: "alice_key".to_string(),
-            page: None,
-            page_size: 10,
-            should_filter_decoys: true,
+        let query_msg = QueryMsg::Allowance {
+            owner: "lebron".to_string(),
+            spender: "giannis".to_string(),
+            key: vk2.clone(),
         };
         let query_result = query(deps.as_ref(), mock_env(), query_msg);
-        let transfers = match from_binary(&query_result.unwrap()).unwrap() {
-            QueryAnswer::TransferHistory { txs, .. } => txs,
+        let allowance = match from_binary(&query_result.unwrap()).unwrap() {
+            QueryAnswer::Allowance { allowance, .. } => allowance,
             _ => panic!("Unexpected"),
         };
-        assert_eq!(transfers.len(), 1);
+        assert_eq!(allowance, Uint128::new(0));
+    }
+
+    #[test]
+    fn test_query_all_allowances() {
+        let num_owners = 3;
+        let num_spenders = 20;
+        let vk = "key".to_string();
+
+        let initial_balances: Vec<InitialBalance> = (0..num_owners)
+            .into_iter()
+            .map(|i| InitialBalance {
+                address: format!("owner{}", i),
+                amount: Uint128::new(5000),
+            })
+            .collect();
+        let (init_result, mut deps) = init_helper(initial_balances);
+        assert!(
+            init_result.is_ok(),
+            "Init failed: {}",
+            init_result.err().unwrap()
+        );
+        for i in 0..num_owners {
+            let handle_msg = ExecuteMsg::SetViewingKey {
+                key: vk.clone(),
+                #[cfg(feature = "gas_evaporation")]
+                gas_target: None,
+                padding: None,
+            };
+            let info = mock_info(format!("owner{}", i).as_str(), &[]);
+
+            let handle_result = execute(deps.as_mut(), mock_env(), info, handle_msg);
+
+            let unwrapped_result: ExecuteAnswer =
+                from_binary(&handle_result.unwrap().data.unwrap()).unwrap();
+            assert_eq!(
+                to_binary(&unwrapped_result).unwrap(),
+                to_binary(&ExecuteAnswer::SetViewingKey {
+                    status: ResponseStatus::Success
+                })
+                .unwrap(),
+            );
+        }
+
+        for i in 0..num_owners {
+            for j in 0..num_spenders {
+                let handle_msg = ExecuteMsg::IncreaseAllowance {
+                    spender: format!("spender{}", j),
+                    amount: Uint128::new(50),
+                    padding: None,
+                    #[cfg(feature = "gas_evaporation")]
+                    gas_target: None,
+                    expiration: None,
+                };
+                let info = mock_info(format!("owner{}", i).as_str(), &[]);
+
+                let handle_result = execute(deps.as_mut(), mock_env(), info, handle_msg);
+                assert!(
+                    handle_result.is_ok(),
+                    "handle() failed: {}",
+                    handle_result.err().unwrap()
+                );
+
+                let handle_msg = ExecuteMsg::SetViewingKey {
+                    key: vk.clone(),
+                    #[cfg(feature = "gas_evaporation")]
+                    gas_target: None,
+                    padding: None,
+                };
+                let info = mock_info(format!("spender{}", j).as_str(), &[]);
+
+                let handle_result = execute(deps.as_mut(), mock_env(), info, handle_msg);
+
+                let unwrapped_result: ExecuteAnswer =
+                    from_binary(&handle_result.unwrap().data.unwrap()).unwrap();
+                assert_eq!(
+                    to_binary(&unwrapped_result).unwrap(),
+                    to_binary(&ExecuteAnswer::SetViewingKey {
+                        status: ResponseStatus::Success
+                    })
+                    .unwrap(),
+                );
+            }
+        }
 
-        let query_msg = QueryMsg::TransferHistory {
-            address: "banana".to_string(),
-            key: "banana_key".to_string(),
+        let query_msg = QueryMsg::AllowancesGiven {
+            owner: "owner0".to_string(),
+            key: vk.clone(),
             page: None,
-            page_size: 10,
-            should_filter_decoys: true,
+            page_size: 5,
         };
         let query_result = query(deps.as_ref(), mock_env(), query_msg);
-        let transfers = match from_binary(&query_result.unwrap()).unwrap() {
-            QueryAnswer::TransferHistory { txs, .. } => txs,
+        match from_binary(&query_result.unwrap()).unwrap() {
+            QueryAnswer::AllowancesGiven {
+                owner,
+                allowances,
+                count,
+            } => {
+                assert_eq!(owner, "owner0".to_string());
+                assert_eq!(allowances.len(), 5);
+                assert_eq!(allowances[0].spender, "spender0");
+                assert_eq!(allowances[0].allowance, Uint128::from(50_u128));
+                assert_eq!(allowances[0].expiration, None);
+                assert_eq!(count, num_spenders);
+            }
             _ => panic!("Unexpected"),
         };
-        assert_eq!(transfers.len(), 1);
 
-        let query_msg = QueryMsg::TransferHistory {
-            address: "lior".to_string(),
-            key: "lior_key".to_string(),
-            page: None,
-            page_size: 10,
-            should_filter_decoys: true,
+        let query_msg = QueryMsg::AllowancesGiven {
+            owner: "owner1".to_string(),
+            key: vk.clone(),
+            page: Some(1),
+            page_size: 5,
         };
         let query_result = query(deps.as_ref(), mock_env(), query_msg);
-        let transfers = match from_binary(&query_result.unwrap()).unwrap() {
-            QueryAnswer::TransferHistory { txs, .. } => txs,
+        match from_binary(&query_result.unwrap()).unwrap() {
+            QueryAnswer::AllowancesGiven {
+                owner,
+                allowances,
+                count,
+            } => {
+                assert_eq!(owner, "owner1".to_string());
+                assert_eq!(allowances.len(), 5);
+                assert_eq!(allowances[0].spender, "spender5");
+                assert_eq!(allowances[0].allowance, Uint128::from(50_u128));
+                assert_eq!(allowances[0].expiration, None);
+                assert_eq!(count, num_spenders);
+            }
             _ => panic!("Unexpected"),
         };
-        assert_eq!(transfers.len(), 0);
 
-        let query_msg = QueryMsg::Balance {
-            address: "bob".to_string(),
-            key: "key".to_string(),
+        let query_msg = QueryMsg::AllowancesGiven {
+            owner: "owner1".to_string(),
+            key: vk.clone(),
+            page: Some(0),
+            page_size: 23,
         };
         let query_result = query(deps.as_ref(), mock_env(), query_msg);
-        let balance = match from_binary(&query_result.unwrap()).unwrap() {
-            QueryAnswer::Balance { amount } => amount,
+        match from_binary(&query_result.unwrap()).unwrap() {
+            QueryAnswer::AllowancesGiven {
+                owner,
+                allowances,
+                count,
+            } => {
+                assert_eq!(owner, "owner1".to_string());
+                assert_eq!(allowances.len(), 20);
+                assert_eq!(count, num_spenders);
+            }
             _ => panic!("Unexpected"),
         };
-        assert_eq!(balance, Uint128::new(3500));
 
-        let query_msg = QueryMsg::Balance {
-            address: "alice".to_string(),
-            key: "alice_key".to_string(),
+        let query_msg = QueryMsg::AllowancesGiven {
+            owner: "owner1".to_string(),
+            key: vk.clone(),
+            page: Some(2),
+            page_size: 8,
         };
         let query_result = query(deps.as_ref(), mock_env(), query_msg);
-        let balance = match from_binary(&query_result.unwrap()).unwrap() {
-            QueryAnswer::Balance { amount } => amount,
+        match from_binary(&query_result.unwrap()).unwrap() {
+            QueryAnswer::AllowancesGiven {
+                owner,
+                allowances,
+                count,
+            } => {
+                assert_eq!(owner, "owner1".to_string());
+                assert_eq!(allowances.len(), 4);
+                assert_eq!(count, num_spenders);
+            }
             _ => panic!("Unexpected"),
         };
-        assert_eq!(balance, Uint128::new(1000));
 
-        let query_msg = QueryMsg::Balance {
-            address: "banana".to_string(),
-            key: "banana_key".to_string(),
+        let query_msg = QueryMsg::AllowancesGiven {
+            owner: "owner2".to_string(),
+            key: vk.clone(),
+            page: Some(5),
+            page_size: 5,
         };
         let query_result = query(deps.as_ref(), mock_env(), query_msg);
-        let balance = match from_binary(&query_result.unwrap()).unwrap() {
-            QueryAnswer::Balance { amount } => amount,
+        match from_binary(&query_result.unwrap()).unwrap() {
+            QueryAnswer::AllowancesGiven {
+                owner,
+                allowances,
+                count,
+            } => {
+                assert_eq!(owner, "owner2".to_string());
+                assert_eq!(allowances.len(), 0);
+                assert_eq!(count, num_spenders);
+            }
             _ => panic!("Unexpected"),
         };
-        assert_eq!(balance, Uint128::new(500));
 
-        let query_msg = QueryMsg::Balance {
-            address: "lior".to_string(),
-            key: "lior_key".to_string(),
+        let query_msg = QueryMsg::AllowancesReceived {
+            spender: "spender0".to_string(),
+            key: vk.clone(),
+            page: None,
+            page_size: 10,
         };
         let query_result = query(deps.as_ref(), mock_env(), query_msg);
-        let balance = match from_binary(&query_result.unwrap()).unwrap() {
-            QueryAnswer::Balance { amount } => amount,
+        match from_binary(&query_result.unwrap()).unwrap() {
+            QueryAnswer::AllowancesReceived {
+                spender,
+                allowances,
+                count,
+            } => {
+                assert_eq!(spender, "spender0".to_string());
+                assert_eq!(allowances.len(), 3);
+                assert_eq!(allowances[0].owner, "owner0");
+                assert_eq!(allowances[0].allowance, Uint128::from(50_u128));
+                assert_eq!(allowances[0].expiration, None);
+                assert_eq!(count, num_owners);
+            }
             _ => panic!("Unexpected"),
         };
-        assert_eq!(balance, Uint128::new(0));
-    }
-
-    #[test]
-    fn test_query_transaction_history() {
-        let (init_result, mut deps) = init_helper_with_config(
-            vec![InitialBalance {
-                address: "bob".to_string(),
-                amount: Uint128::new(10000),
-            }],
-            true,
-            true,
-            true,
-            true,
-            1000,
-            vec!["uscrt".to_string()],
-        );
-        assert!(
-            init_result.is_ok(),
-            "Init failed: {}",
-            init_result.err().unwrap()
-        );
-
-        let handle_msg = ExecuteMsg::SetViewingKey {
-            key: "key".to_string(),
-            padding: None,
-        };
-        let info = mock_info("bob", &[]);
-
-        let handle_result = execute(deps.as_mut(), mock_env(), info, handle_msg);
-
-        assert!(ensure_success(handle_result.unwrap()));
-
-        let handle_msg = ExecuteMsg::Burn {
-            amount: Uint128::new(1),
-            memo: Some("my burn message".to_string()),
-            decoys: None,
-            entropy: None,
-            padding: None,
-        };
-        let info = mock_info("bob", &[]);
-
-        let handle_result = execute(deps.as_mut(), mock_env(), info, handle_msg);
-
-        assert!(
-            handle_result.is_ok(),
-            "Pause handle failed: {}",
-            handle_result.err().unwrap()
-        );
-
-        let handle_msg = ExecuteMsg::Redeem {
-            amount: Uint128::new(1000),
-            denom: Option::from("uscrt".to_string()),
-            decoys: None,
-            entropy: None,
-            padding: None,
-        };
-        let info = mock_info("bob", &[]);
-
-        let handle_result = execute(deps.as_mut(), mock_env(), info, handle_msg);
-
-        assert!(
-            handle_result.is_ok(),
-            "handle() failed: {}",
-            handle_result.err().unwrap()
-        );
-
-        let handle_msg = ExecuteMsg::Mint {
-            recipient: "bob".to_string(),
-            amount: Uint128::new(100),
-            memo: Some("my mint message".to_string()),
-            decoys: None,
-            entropy: None,
-            padding: None,
-        };
-        let info = mock_info("admin", &[]);
-
-        let handle_result = execute(deps.as_mut(), mock_env(), info, handle_msg);
-
-        assert!(ensure_success(handle_result.unwrap()));
-
-        let handle_msg = ExecuteMsg::Deposit {
-            decoys: None,
-            entropy: None,
-            padding: None,
-        };
-        let info = mock_info(
-            "bob",
-            &[Coin {
-                denom: "uscrt".to_string(),
-                amount: Uint128::new(1000),
-            }],
-        );
-
-        let handle_result = execute(deps.as_mut(), mock_env(), info, handle_msg);
-        assert!(
-            handle_result.is_ok(),
-            "handle() failed: {}",
-            handle_result.err().unwrap()
-        );
-
-        let handle_msg = ExecuteMsg::Transfer {
-            recipient: "alice".to_string(),
-            amount: Uint128::new(1000),
-            memo: Some("my transfer message #1".to_string()),
-            decoys: None,
-            entropy: None,
-            padding: None,
-        };
-        let info = mock_info("bob", &[]);
-
-        let handle_result = execute(deps.as_mut(), mock_env(), info, handle_msg);
 
-        let result = handle_result.unwrap();
-        assert!(ensure_success(result));
-
-        let handle_msg = ExecuteMsg::Transfer {
-            recipient: "banana".to_string(),
-            amount: Uint128::new(500),
-            memo: Some("my transfer message #2".to_string()),
-            decoys: None,
-            entropy: None,
-            padding: None,
+        let query_msg = QueryMsg::AllowancesReceived {
+            spender: "spender1".to_string(),
+            key: vk.clone(),
+            page: Some(1),
+            page_size: 1,
         };
-        let info = mock_info("bob", &[]);
-
-        let handle_result = execute(deps.as_mut(), mock_env(), info, handle_msg);
+        let query_result = query(deps.as_ref(), mock_env(), query_msg);
+        match from_binary(&query_result.unwrap()).unwrap() {
+            QueryAnswer::AllowancesReceived {
+                spender,
+                allowances,
+                count,
+            } => {
+                assert_eq!(spender, "spender1".to_string());
+                assert_eq!(allowances.len(), 1);
+                assert_eq!(allowances[0].owner, "owner1");
+                assert_eq!(allowances[0].allowance, Uint128::from(50_u128));
+                assert_eq!(allowances[0].expiration, None);
+                assert_eq!(count, num_owners);
+            }
+            _ => panic!("Unexpected"),
+        };
+    }
 
-        let result = handle_result.unwrap();
-        assert!(ensure_success(result));
+    #[test]
+    fn test_query_balance() {
+        let (init_result, mut deps) = init_helper(vec![InitialBalance {
+            address: "bob".to_string(),
+            amount: Uint128::new(5000),
+        }]);
+        assert!(
+            init_result.is_ok(),
+            "Init failed: {}",
+            init_result.err().unwrap()
+        );
 
-        let handle_msg = ExecuteMsg::Transfer {
-            recipient: "mango".to_string(),
-            amount: Uint128::new(2500),
-            memo: Some("my transfer message #3".to_string()),
-            decoys: None,
-            entropy: None,
+        let handle_msg = ExecuteMsg::SetViewingKey {
+            key: "key".to_string(),
+            #[cfg(feature = "gas_evaporation")]
+            gas_target: None,
             padding: None,
         };
         let info = mock_info("bob", &[]);
 
         let handle_result = execute(deps.as_mut(), mock_env(), info, handle_msg);
 
-        let result = handle_result.unwrap();
-        assert!(ensure_success(result));
+        let unwrapped_result: ExecuteAnswer =
+            from_binary(&handle_result.unwrap().data.unwrap()).unwrap();
+        assert_eq!(
+            to_binary(&unwrapped_result).unwrap(),
+            to_binary(&ExecuteAnswer::SetViewingKey {
+                status: ResponseStatus::Success
+            })
+            .unwrap(),
+        );
 
-        let query_msg = QueryMsg::TransferHistory {
+        let query_msg = QueryMsg::Balance {
             address: "bob".to_string(),
-            key: "key".to_string(),
-            page: None,
-            page_size: 10,
-            should_filter_decoys: false,
+            key: "wrong_key".to_string(),
         };
         let query_result = query(deps.as_ref(), mock_env(), query_msg);
-        let transfers = match from_binary(&query_result.unwrap()).unwrap() {
-            QueryAnswer::TransferHistory { txs, .. } => txs,
-            _ => panic!("Unexpected"),
-        };
-        assert_eq!(transfers.len(), 3);
+        let error = extract_error_msg(query_result);
+        assert!(error.contains("Wrong viewing key"));
 
-        let query_msg = QueryMsg::TransactionHistory {
+        let query_msg = QueryMsg::Balance {
             address: "bob".to_string(),
             key: "key".to_string(),
-            page: None,
-            page_size: 10,
-            should_filter_decoys: false,
         };
         let query_result = query(deps.as_ref(), mock_env(), query_msg);
-        let transfers = match from_binary(&query_result.unwrap()).unwrap() {
-            QueryAnswer::TransactionHistory { txs, .. } => txs,
-            other => panic!("Unexpected: {:?}", other),
+        let balance = match from_binary(&query_result.unwrap()).unwrap() {
+            QueryAnswer::Balance { amount } => amount,
+            _ => panic!("Unexpected"),
         };
-
-        use crate::transaction_history::{ExtendedTx, TxAction};
-        let expected_transfers = [
-            ExtendedTx {
-                id: 8,
-                action: TxAction::Transfer {
-                    from: Addr::unchecked("bob".to_string()),
-                    sender: Addr::unchecked("bob".to_string()),
-                    recipient: Addr::unchecked("mango".to_string()),
-                },
-                coins: Coin {
-                    denom: "SECSEC".to_string(),
-                    amount: Uint128::new(2500),
-                },
-                memo: Some("my transfer message #3".to_string()),
-                block_time: 1571797419,
-                block_height: 12345,
-            },
-            ExtendedTx {
-                id: 7,
-                action: TxAction::Transfer {
-                    from: Addr::unchecked("bob".to_string()),
-                    sender: Addr::unchecked("bob".to_string()),
-                    recipient: Addr::unchecked("banana".to_string()),
-                },
-                coins: Coin {
-                    denom: "SECSEC".to_string(),
-                    amount: Uint128::new(500),
-                },
-                memo: Some("my transfer message #2".to_string()),
-                block_time: 1571797419,
-                block_height: 12345,
-            },
-            ExtendedTx {
-                id: 6,
-                action: TxAction::Transfer {
-                    from: Addr::unchecked("bob".to_string()),
-                    sender: Addr::unchecked("bob".to_string()),
-                    recipient: Addr::unchecked("alice".to_string()),
-                },
-                coins: Coin {
-                    denom: "SECSEC".to_string(),
-                    amount: Uint128::new(1000),
-                },
-                memo: Some("my transfer message #1".to_string()),
-                block_time: 1571797419,
-                block_height: 12345,
-            },
-            ExtendedTx {
-                id: 5,
-                action: TxAction::Deposit {},
-                coins: Coin {
-                    denom: "uscrt".to_string(),
-                    amount: Uint128::new(1000),
-                },
-                memo: None,
-                block_time: 1571797419,
-                block_height: 12345,
-            },
-            ExtendedTx {
-                id: 4,
-                action: TxAction::Mint {
-                    minter: Addr::unchecked("admin".to_string()),
-                    recipient: Addr::unchecked("bob".to_string()),
-                },
-                coins: Coin {
-                    denom: "SECSEC".to_string(),
-                    amount: Uint128::new(100),
-                },
-                memo: Some("my mint message".to_string()),
-                block_time: 1571797419,
-                block_height: 12345,
-            },
-            ExtendedTx {
-                id: 3,
-                action: TxAction::Redeem {},
-                coins: Coin {
-                    denom: "SECSEC".to_string(),
-                    amount: Uint128::new(1000),
-                },
-                memo: None,
-                block_time: 1571797419,
-                block_height: 12345,
-            },
-            ExtendedTx {
-                id: 2,
-                action: TxAction::Burn {
-                    burner: Addr::unchecked("bob".to_string()),
-                    owner: Addr::unchecked("bob".to_string()),
-                },
-                coins: Coin {
-                    denom: "SECSEC".to_string(),
-                    amount: Uint128::new(1),
-                },
-                memo: Some("my burn message".to_string()),
-                block_time: 1571797419,
-                block_height: 12345,
-            },
-            ExtendedTx {
-                id: 1,
-                action: TxAction::Mint {
-                    minter: Addr::unchecked("admin".to_string()),
-                    recipient: Addr::unchecked("bob".to_string()),
-                },
-                coins: Coin {
-                    denom: "SECSEC".to_string(),
-                    amount: Uint128::new(10000),
-                },
-
-                memo: Some("Initial Balance".to_string()),
-                block_time: 1571797419,
-                block_height: 12345,
-            },
-        ];
-
-        assert_eq!(transfers, expected_transfers);
+        assert_eq!(balance, Uint128::new(5000));
     }
 
     #[test]
-    fn test_query_transaction_history_with_decoys() {
+    fn test_query_transaction_history() {
         let (init_result, mut deps) = init_helper_with_config(
-            vec![
-                InitialBalance {
-                    address: "bob".to_string(),
-                    amount: Uint128::new(5000),
-                },
-                InitialBalance {
-                    address: "jhon".to_string(),
-                    amount: Uint128::new(7000),
-                },
-            ],
+            vec![InitialBalance {
+                address: "bob".to_string(),
+                amount: Uint128::new(10000),
+            }],
             true,
             true,
             true,
@@ -5953,7 +7059,6 @@ mod tests {
             1000,
             vec!["uscrt".to_string()],
         );
-
         assert!(
             init_result.is_ok(),
             "Init failed: {}",
@@ -5962,54 +7067,21 @@ mod tests {
 
         let handle_msg = ExecuteMsg::SetViewingKey {
             key: "key".to_string(),
+            #[cfg(feature = "gas_evaporation")]
+            gas_target: None,
             padding: None,
         };
         let info = mock_info("bob", &[]);
 
-        let handle_result = execute(deps.as_mut(), mock_env(), info, handle_msg);
-        assert!(ensure_success(handle_result.unwrap()));
-
-        let handle_msg = ExecuteMsg::SetViewingKey {
-            key: "alice_key".to_string(),
-            padding: None,
-        };
-        let info = mock_info("alice", &[]);
-
-        let handle_result = execute(deps.as_mut(), mock_env(), info, handle_msg);
-        assert!(ensure_success(handle_result.unwrap()));
-
-        let handle_msg = ExecuteMsg::SetViewingKey {
-            key: "lior_key".to_string(),
-            padding: None,
-        };
-        let info = mock_info("lior", &[]);
-
-        let handle_result = execute(deps.as_mut(), mock_env(), info, handle_msg);
-        assert!(ensure_success(handle_result.unwrap()));
-
-        let handle_msg = ExecuteMsg::SetViewingKey {
-            key: "jhon_key".to_string(),
-            padding: None,
-        };
-        let info = mock_info("jhon", &[]);
-
         let handle_result = execute(deps.as_mut(), mock_env(), info, handle_msg);
 
         assert!(ensure_success(handle_result.unwrap()));
 
-        let lior_addr = Addr::unchecked("lior".to_string());
-        let jhon_addr = Addr::unchecked("jhon".to_string());
-        let alice_addr = Addr::unchecked("alice".to_string());
-
         let handle_msg = ExecuteMsg::Burn {
             amount: Uint128::new(1),
             memo: Some("my burn message".to_string()),
-            decoys: Some(vec![
-                lior_addr.clone(),
-                jhon_addr.clone(),
-                alice_addr.clone(),
-            ]),
-            entropy: Some(Binary::from_base64("VEVTVFRFU1RURVNUQ0hFQ0tDSEVDSw==").unwrap()),
+            #[cfg(feature = "gas_evaporation")]
+            gas_target: None,
             padding: None,
         };
         let info = mock_info("bob", &[]);
@@ -6025,12 +7097,8 @@ mod tests {
         let handle_msg = ExecuteMsg::Redeem {
             amount: Uint128::new(1000),
             denom: Option::from("uscrt".to_string()),
-            decoys: Some(vec![
-                lior_addr.clone(),
-                jhon_addr.clone(),
-                alice_addr.clone(),
-            ]),
-            entropy: Some(Binary::from_base64("VEVTVFRFU1RURVNUQ0hFQ0tDSEVDSw==").unwrap()),
+            #[cfg(feature = "gas_evaporation")]
+            gas_target: None,
             padding: None,
         };
         let info = mock_info("bob", &[]);
@@ -6047,12 +7115,8 @@ mod tests {
             recipient: "bob".to_string(),
             amount: Uint128::new(100),
             memo: Some("my mint message".to_string()),
-            decoys: Some(vec![
-                lior_addr.clone(),
-                jhon_addr.clone(),
-                alice_addr.clone(),
-            ]),
-            entropy: Some(Binary::from_base64("VEVTVFRFU1RURVNUQ0hFQ0tDSEVDSw==").unwrap()),
+            #[cfg(feature = "gas_evaporation")]
+            gas_target: None,
             padding: None,
         };
         let info = mock_info("admin", &[]);
@@ -6062,12 +7126,8 @@ mod tests {
         assert!(ensure_success(handle_result.unwrap()));
 
         let handle_msg = ExecuteMsg::Deposit {
-            decoys: Some(vec![
-                lior_addr.clone(),
-                jhon_addr.clone(),
-                alice_addr.clone(),
-            ]),
-            entropy: Some(Binary::from_base64("VEVTVFRFU1RURVNUQ0hFQ0tDSEVDSw==").unwrap()),
+            #[cfg(feature = "gas_evaporation")]
+            gas_target: None,
             padding: None,
         };
         let info = mock_info(
@@ -6089,12 +7149,8 @@ mod tests {
             recipient: "alice".to_string(),
             amount: Uint128::new(1000),
             memo: Some("my transfer message #1".to_string()),
-            decoys: Some(vec![
-                lior_addr.clone(),
-                jhon_addr.clone(),
-                alice_addr.clone(),
-            ]),
-            entropy: Some(Binary::from_base64("VEVTVFRFU1RURVNUQ0hFQ0tDSEVDSw==").unwrap()),
+            #[cfg(feature = "gas_evaporation")]
+            gas_target: None,
             padding: None,
         };
         let info = mock_info("bob", &[]);
@@ -6108,12 +7164,8 @@ mod tests {
             recipient: "banana".to_string(),
             amount: Uint128::new(500),
             memo: Some("my transfer message #2".to_string()),
-            decoys: Some(vec![
-                lior_addr.clone(),
-                jhon_addr.clone(),
-                alice_addr.clone(),
-            ]),
-            entropy: Some(Binary::from_base64("VEVTVFRFU1RURVNUQ0hFQ0tDSEVDSw==").unwrap()),
+            #[cfg(feature = "gas_evaporation")]
+            gas_target: None,
             padding: None,
         };
         let info = mock_info("bob", &[]);
@@ -6127,12 +7179,8 @@ mod tests {
             recipient: "mango".to_string(),
             amount: Uint128::new(2500),
             memo: Some("my transfer message #3".to_string()),
-            decoys: Some(vec![
-                lior_addr.clone(),
-                jhon_addr.clone(),
-                alice_addr.clone(),
-            ]),
-            entropy: Some(Binary::from_base64("VEVTVFRFU1RURVNUQ0hFQ0tDSEVDSw==").unwrap()),
+            #[cfg(feature = "gas_evaporation")]
+            gas_target: None,
             padding: None,
         };
         let info = mock_info("bob", &[]);
@@ -6142,83 +7190,22 @@ mod tests {
         let result = handle_result.unwrap();
         assert!(ensure_success(result));
 
-        let query_msg = QueryMsg::TransactionHistory {
-            address: "lior".to_string(),
-            key: "lior_key".to_string(),
-            page: None,
-            page_size: 10,
-            should_filter_decoys: true,
-        };
-        let query_result = query(deps.as_ref(), mock_env(), query_msg);
-        let transactions = match from_binary(&query_result.unwrap()).unwrap() {
-            QueryAnswer::TransactionHistory { txs, .. } => txs,
-            other => panic!("Unexpected: {:?}", other),
-        };
-
-        assert!(transactions.is_empty());
-
-        let query_msg = QueryMsg::TransactionHistory {
-            address: "alice".to_string(),
-            key: "alice_key".to_string(),
-            page: None,
-            page_size: 10,
-            should_filter_decoys: false,
-        };
-        let query_result = query(deps.as_ref(), mock_env(), query_msg);
-        let transactions = match from_binary(&query_result.unwrap()).unwrap() {
-            QueryAnswer::TransactionHistory { txs, .. } => txs,
-            other => panic!("Unexpected: {:?}", other),
-        };
-
-        assert_eq!(transactions.len(), 7); // Transfer from bob
-
-        let query_msg = QueryMsg::TransactionHistory {
-            address: "alice".to_string(),
-            key: "alice_key".to_string(),
-            page: None,
-            page_size: 10,
-            should_filter_decoys: true,
-        };
-        let query_result = query(deps.as_ref(), mock_env(), query_msg);
-        let transactions = match from_binary(&query_result.unwrap()).unwrap() {
-            QueryAnswer::TransactionHistory { txs, .. } => txs,
-            other => panic!("Unexpected: {:?}", other),
-        };
-
-        assert_eq!(transactions.len(), 1); // Transfer from bob
-
-        let query_msg = QueryMsg::TransactionHistory {
-            address: "jhon".to_string(),
-            key: "jhon_key".to_string(),
-            page: None,
-            page_size: 10,
-            should_filter_decoys: true,
-        };
-        let query_result = query(deps.as_ref(), mock_env(), query_msg);
-        let transactions = match from_binary(&query_result.unwrap()).unwrap() {
-            QueryAnswer::TransactionHistory { txs, .. } => txs,
-            other => panic!("Unexpected: {:?}", other),
-        };
-
-        assert_eq!(transactions.len(), 1); // Mint on init
-
         let query_msg = QueryMsg::TransactionHistory {
             address: "bob".to_string(),
             key: "key".to_string(),
             page: None,
             page_size: 10,
-            should_filter_decoys: true,
         };
         let query_result = query(deps.as_ref(), mock_env(), query_msg);
-        let transactions = match from_binary(&query_result.unwrap()).unwrap() {
+        let transfers = match from_binary(&query_result.unwrap()).unwrap() {
             QueryAnswer::TransactionHistory { txs, .. } => txs,
             other => panic!("Unexpected: {:?}", other),
         };
 
-        use crate::transaction_history::{ExtendedTx, TxAction};
-        let expected_transactions = [
-            ExtendedTx {
-                id: 9,
+        use crate::transaction_history::TxAction;
+        let expected_transfers = [
+            Tx {
+                id: 8,
                 action: TxAction::Transfer {
                     from: Addr::unchecked("bob".to_string()),
                     sender: Addr::unchecked("bob".to_string()),
@@ -6232,8 +7219,8 @@ mod tests {
                 block_time: 1571797419,
                 block_height: 12345,
             },
-            ExtendedTx {
-                id: 8,
+            Tx {
+                id: 7,
                 action: TxAction::Transfer {
                     from: Addr::unchecked("bob".to_string()),
                     sender: Addr::unchecked("bob".to_string()),
@@ -6247,8 +7234,8 @@ mod tests {
                 block_time: 1571797419,
                 block_height: 12345,
             },
-            ExtendedTx {
-                id: 7,
+            Tx {
+                id: 6,
                 action: TxAction::Transfer {
                     from: Addr::unchecked("bob".to_string()),
                     sender: Addr::unchecked("bob".to_string()),
@@ -6262,8 +7249,8 @@ mod tests {
                 block_time: 1571797419,
                 block_height: 12345,
             },
-            ExtendedTx {
-                id: 6,
+            Tx {
+                id: 5,
                 action: TxAction::Deposit {},
                 coins: Coin {
                     denom: "uscrt".to_string(),
@@ -6273,8 +7260,8 @@ mod tests {
                 block_time: 1571797419,
                 block_height: 12345,
             },
-            ExtendedTx {
-                id: 5,
+            Tx {
+                id: 4,
                 action: TxAction::Mint {
                     minter: Addr::unchecked("admin".to_string()),
                     recipient: Addr::unchecked("bob".to_string()),
@@ -6287,8 +7274,8 @@ mod tests {
                 block_time: 1571797419,
                 block_height: 12345,
             },
-            ExtendedTx {
-                id: 4,
+            Tx {
+                id: 3,
                 action: TxAction::Redeem {},
                 coins: Coin {
                     denom: "SECSEC".to_string(),
@@ -6298,8 +7285,8 @@ mod tests {
                 block_time: 1571797419,
                 block_height: 12345,
             },
-            ExtendedTx {
-                id: 3,
+            Tx {
+                id: 2,
                 action: TxAction::Burn {
                     burner: Addr::unchecked("bob".to_string()),
                     owner: Addr::unchecked("bob".to_string()),
@@ -6312,7 +7299,7 @@ mod tests {
                 block_time: 1571797419,
                 block_height: 12345,
             },
-            ExtendedTx {
+            Tx {
                 id: 1,
                 action: TxAction::Mint {
                     minter: Addr::unchecked("admin".to_string()),
@@ -6320,7 +7307,7 @@ mod tests {
                 },
                 coins: Coin {
                     denom: "SECSEC".to_string(),
-                    amount: Uint128::new(5000),
+                    amount: Uint128::new(10000),
                 },
 
                 memo: Some("Initial Balance".to_string()),
@@ -6329,6 +7316,6 @@ mod tests {
             },
         ];
 
-        assert_eq!(transactions, expected_transactions);
+        assert_eq!(transfers, expected_transfers);
     }
 }
diff --git a/src/dwb.rs b/src/dwb.rs
new file mode 100644
index 00000000..8b4c7adf
--- /dev/null
+++ b/src/dwb.rs
@@ -0,0 +1,623 @@
+use constant_time_eq::constant_time_eq;
+use cosmwasm_std::{Api, CanonicalAddr, StdError, StdResult, Storage};
+use rand::RngCore;
+use secret_toolkit::storage::Item;
+use secret_toolkit_crypto::ContractPrng;
+use serde::{Deserialize, Serialize};
+use serde_big_array::BigArray;
+
+use crate::btbe::{merge_dwb_entry, stored_balance};
+use crate::state::{safe_add, safe_add_u64};
+use crate::transaction_history::{Tx, TRANSACTIONS};
+#[cfg(feature = "gas_tracking")]
+use crate::gas_tracker::GasTracker;
+#[cfg(feature = "gas_tracking")]
+use cosmwasm_std::{Binary, to_binary};
+#[cfg(feature = "gas_tracking")]
+use crate::msg::QueryAnswer;
+
+include!(concat!(env!("OUT_DIR"), "/config.rs"));
+
+pub const KEY_DWB: &[u8] = b"dwb";
+pub const KEY_TX_NODES_COUNT: &[u8] = b"dwb-node-cnt";
+pub const KEY_TX_NODES: &[u8] = b"dwb-tx-nodes";
+
+pub static DWB: Item<DelayedWriteBuffer> = Item::new(KEY_DWB);
+// use with add_suffix tx id (u64)
+// does not need to be an AppendStore because we never need to iterate over global list of txs
+pub static TX_NODES: Item<TxNode> = Item::new(KEY_TX_NODES);
+pub static TX_NODES_COUNT: Item<u64> = Item::new(KEY_TX_NODES_COUNT);
+
+fn store_new_tx_node(store: &mut dyn Storage, tx_node: TxNode) -> StdResult<u64> {
+    // tx nodes ids serialized start at 1
+    let tx_nodes_serial_id = TX_NODES_COUNT.load(store).unwrap_or_default() + 1;
+    TX_NODES
+        .add_suffix(&tx_nodes_serial_id.to_be_bytes())
+        .save(store, &tx_node)?;
+    TX_NODES_COUNT.save(store, &(tx_nodes_serial_id))?;
+    Ok(tx_nodes_serial_id)
+}
+
+// n entries + 1 "dummy" entry prepended (idx: 0 in DelayedWriteBufferEntry array)
+// minimum allowable size: 3
+pub const DWB_LEN: u16 = DWB_CAPACITY + 1;
+
+// maximum number of tx events allowed in an entry's linked list
+pub const DWB_MAX_TX_EVENTS: u16 = u16::MAX;
+
+#[derive(Serialize, Deserialize, Debug)]
+pub struct DelayedWriteBuffer {
+    pub empty_space_counter: u16,
+    #[serde(with = "BigArray")]
+    pub entries: [DelayedWriteBufferEntry; DWB_LEN as usize],
+}
+
+pub fn random_in_range(rng: &mut ContractPrng, a: u32, b: u32) -> StdResult<u32> {
+    if b <= a {
+        return Err(StdError::generic_err("invalid range"));
+    }
+    let range_size = (b - a) as u64;
+    // need to make sure random is below threshold to prevent modulo bias
+    let threshold = u64::MAX - range_size;
+    loop {
+        // this loop will almost always run only once since range_size << u64::MAX
+        let random_u64 = rng.next_u64();
+        if random_u64 < threshold {
+            return Ok((random_u64 % range_size) as u32 + a);
+        }
+    }
+}
+
+impl DelayedWriteBuffer {
+    pub fn new() -> StdResult<Self> {
+        Ok(Self {
+            empty_space_counter: DWB_LEN - 1,
+            // first entry is a dummy entry for constant-time writing
+            entries: [DelayedWriteBufferEntry::new(&CanonicalAddr::from(&ZERO_ADDR))?;
+                DWB_LEN as usize],
+        })
+    }
+
+    /// settles a participant's account who may or may not have an entry in the buffer
+    /// gets balance including any amount in the buffer, and then subtracts amount spent in this tx
+    pub fn settle_sender_or_owner_account(
+        &mut self,
+        store: &mut dyn Storage,
+        address: &CanonicalAddr,
+        tx_id: u64,
+        amount_spent: u128,
+        op_name: &str,
+        #[cfg(feature = "gas_tracking")] tracker: &mut GasTracker,
+    ) -> StdResult<u128> {
+        #[cfg(feature = "gas_tracking")]
+        let mut group1 = tracker.group("settle_sender_or_owner_account.1");
+
+        // release the address from the buffer
+        let (balance, mut dwb_entry) = self.release_dwb_recipient(store, address)?;
+
+        #[cfg(feature = "gas_tracking")]
+        group1.log("release_dwb_recipient");
+
+        let checked_balance = balance.checked_sub(amount_spent);
+        if checked_balance.is_none() {
+            return Err(StdError::generic_err(format!(
+                "insufficient funds to {op_name}: balance={balance}, required={amount_spent}",
+            )));
+        };
+
+        dwb_entry.add_tx_node(store, tx_id)?;
+
+        #[cfg(feature = "gas_tracking")]
+        group1.log("add_tx_node");
+
+        let mut entry = dwb_entry.clone();
+        entry.set_recipient(address)?;
+
+        #[cfg(feature = "gas_tracking")]
+        group1.logf(format!(
+            "@entry=address:{}, amount:{}",
+            entry.recipient()?,
+            entry.amount()?
+        ));
+
+        merge_dwb_entry(
+            store,
+            &entry,
+            Some(amount_spent),
+            #[cfg(feature = "gas_tracking")]
+            tracker,
+        )?;
+
+        Ok(checked_balance.unwrap())
+    }
+
+    /// "releases" a given recipient from the buffer, removing their entry if one exists
+    /// returns the new balance and the buffer entry
+    fn release_dwb_recipient(
+        &mut self,
+        store: &mut dyn Storage,
+        address: &CanonicalAddr,
+    ) -> StdResult<(u128, DelayedWriteBufferEntry)> {
+        // get the address' stored balance
+        let mut balance = stored_balance(store, address)?;
+
+        // locate the position of the entry in the buffer
+        let matched_entry_idx = self.recipient_match(address);
+
+        // get the current entry at the matched index (0 if dummy)
+        let entry = self.entries[matched_entry_idx];
+
+        // create a new entry to replace the released one, giving it the same address to avoid introducing random addresses
+        let replacement_entry = DelayedWriteBufferEntry::new(&entry.recipient()?)?;
+
+        // add entry amount to the stored balance for the address (will be 0 if dummy)
+        safe_add(&mut balance, entry.amount()? as u128);
+
+        // overwrite the entry idx with replacement
+        self.entries[matched_entry_idx] = replacement_entry;
+
+        Ok((balance, entry))
+    }
+
+    // returns matched index for a given address
+    pub fn recipient_match(&self, address: &CanonicalAddr) -> usize {
+        let mut matched_index: usize = 0;
+        let address = address.as_slice();
+        for (idx, entry) in self.entries.iter().enumerate().skip(1) {
+            let equals = constant_time_eq(address, entry.recipient_slice()) as usize;
+            // an address can only occur once in the buffer
+            matched_index |= idx * equals;
+        }
+        matched_index
+    }
+
+    pub fn add_recipient<'a>(
+        &mut self,
+        store: &mut dyn Storage,
+        rng: &mut ContractPrng,
+        recipient: &CanonicalAddr,
+        tx_id: u64,
+        amount: u128,
+        #[cfg(feature = "gas_tracking")] tracker: &mut GasTracker<'a>,
+    ) -> StdResult<()> {
+        #[cfg(feature = "gas_tracking")]
+        let mut group1 = tracker.group("add_recipient.1");
+
+        // check if `recipient` is already a recipient in the delayed write buffer
+        let recipient_index = self.recipient_match(recipient);
+        #[cfg(feature = "gas_tracking")]
+        group1.log("recipient_match");
+
+        // the new entry will either derive from a prior entry for the recipient or the dummy entry
+        let mut new_entry = self.entries[recipient_index].clone();
+
+        new_entry.set_recipient(recipient)?;
+        #[cfg(feature = "gas_tracking")]
+        group1.log("set_recipient");
+
+        new_entry.add_tx_node(store, tx_id)?;
+        #[cfg(feature = "gas_tracking")]
+        group1.log("add_tx_node");
+
+        new_entry.add_amount(amount)?;
+        #[cfg(feature = "gas_tracking")]
+        group1.log("add_amount");
+
+        // whether or not recipient is in the buffer (non-zero index)
+        // casting to i32 will never overflow, so long as dwb length is limited to a u16 value
+        let if_recipient_in_buffer = constant_time_is_not_zero(recipient_index as i32);
+        #[cfg(feature = "gas_tracking")]
+        group1.logf(format!(
+            "@if_recipient_in_buffer: {}",
+            if_recipient_in_buffer
+        ));
+
+        // whether or not the buffer is fully saturated yet
+        let if_undersaturated = constant_time_is_not_zero(self.empty_space_counter as i32);
+        #[cfg(feature = "gas_tracking")]
+        group1.logf(format!("@if_undersaturated: {}", if_undersaturated));
+
+        // find the next empty entry in the buffer
+        let next_empty_index = (DWB_LEN - self.empty_space_counter) as usize;
+        #[cfg(feature = "gas_tracking")]
+        group1.logf(format!("@next_empty_index: {}", next_empty_index));
+
+        // which entry to settle (not yet considering if recipient's entry has capacity in history list)
+        //   if recipient is in buffer or buffer is undersaturated then settle the dummy entry
+        //   otherwise, settle a random entry
+        let presumptive_settle_index = constant_time_if_else(
+            if_recipient_in_buffer,
+            0,
+            constant_time_if_else(
+                if_undersaturated,
+                0,
+                random_in_range(rng, 1, DWB_LEN as u32)? as usize,
+            ),
+        );
+        #[cfg(feature = "gas_tracking")]
+        group1.logf(format!(
+            "@presumptive_settle_index: {}",
+            presumptive_settle_index
+        ));
+
+        // check if we have any open slots in the linked list
+        let if_list_can_grow = constant_time_is_not_zero(
+            (DWB_MAX_TX_EVENTS - self.entries[recipient_index].list_len()?) as i32,
+        );
+        #[cfg(feature = "gas_tracking")]
+        group1.logf(format!("@if_list_can_grow: {}", if_list_can_grow));
+
+        // if we would overflow the list by updating the existing entry, then just settle that recipient
+        let actual_settle_index =
+            constant_time_if_else(if_list_can_grow, presumptive_settle_index, recipient_index);
+        #[cfg(feature = "gas_tracking")]
+        group1.logf(format!("@actual_settle_index: {}", actual_settle_index));
+
+        // where to write the new/replacement entry
+        //   if recipient is in buffer then update it
+        //   otherwise, if buffer is undersaturated then put new entry at next open slot
+        //   otherwise, the buffer is saturated so replace the entry that is getting settled
+        let write_index = constant_time_if_else(
+            if_recipient_in_buffer,
+            recipient_index,
+            constant_time_if_else(if_undersaturated, next_empty_index, actual_settle_index),
+        );
+        #[cfg(feature = "gas_tracking")]
+        group1.logf(format!("@write_index: {}", write_index));
+
+        // settle the entry
+        let dwb_entry = self.entries[actual_settle_index];
+        merge_dwb_entry(
+            store,
+            &dwb_entry,
+            None,
+            #[cfg(feature = "gas_tracking")]
+            tracker,
+        )?;
+
+        #[cfg(feature = "gas_tracking")]
+        let mut group2 = tracker.group("add_recipient.2");
+
+        #[cfg(feature = "gas_tracking")]
+        group2.log("merge_dwb_entry");
+
+        // write the new entry, which either overwrites the existing one for the same recipient,
+        // replaces a randomly settled one, or inserts into an "empty" slot in the buffer
+        self.entries[write_index] = new_entry;
+
+        // decrement empty space counter if it is undersaturated and the recipient was not already in the buffer
+        self.empty_space_counter -= constant_time_if_else(
+            if_undersaturated,
+            constant_time_if_else(if_recipient_in_buffer, 0, 1),
+            0,
+        ) as u16;
+        #[cfg(feature = "gas_tracking")]
+        group2.logf(format!(
+            "@empty_space_counter: {}",
+            self.empty_space_counter
+        ));
+
+        Ok(())
+    }
+}
+
+const U16_BYTES: usize = 2;
+const U64_BYTES: usize = 8;
+const U128_BYTES: usize = 16;
+
+#[cfg(test)]
+const DWB_RECIPIENT_BYTES: usize = 54; // because mock_api creates rando canonical addr that is 54 bytes long
+#[cfg(not(test))]
+const DWB_RECIPIENT_BYTES: usize = 20;
+const DWB_AMOUNT_BYTES: usize = 8; // Max 16 (u128)
+const DWB_HEAD_NODE_BYTES: usize = 5; // Max 8  (u64)
+const DWB_LIST_LEN_BYTES: usize = 2; // u16
+
+const_assert!(DWB_AMOUNT_BYTES <= U128_BYTES);
+const_assert!(DWB_HEAD_NODE_BYTES <= U64_BYTES);
+const_assert!(DWB_LIST_LEN_BYTES <= U16_BYTES);
+
+const DWB_ENTRY_BYTES: usize =
+    DWB_RECIPIENT_BYTES + DWB_AMOUNT_BYTES + DWB_HEAD_NODE_BYTES + DWB_LIST_LEN_BYTES;
+
+pub const ZERO_ADDR: [u8; DWB_RECIPIENT_BYTES] = [0u8; DWB_RECIPIENT_BYTES];
+
+/// A delayed write buffer entry consists of the following bytes in this order:
+///
+/// // recipient canonical address
+/// recipient - 20 bytes
+/// // for sscrt w/ 6 decimals u64 is good for > 18 trillion tokens, far exceeding supply
+/// // change to 16 bytes (u128) or other size for tokens with more decimals/higher supply
+/// amount    - 8 bytes (u64)
+/// // global id for head of linked list of transaction nodes
+/// // 40 bits allows for over 1 trillion transactions
+/// head_node - 5 bytes
+/// // length of list (limited to 65535)
+/// list_len  - 2 byte
+///
+/// total: 35 bytes
+#[derive(Serialize, Deserialize, Clone, Copy, Debug)]
+#[cfg_attr(test, derive(Eq, PartialEq))]
+pub struct DelayedWriteBufferEntry(#[serde(with = "BigArray")] [u8; DWB_ENTRY_BYTES]);
+
+impl DelayedWriteBufferEntry {
+    pub fn new(recipient: &CanonicalAddr) -> StdResult<Self> {
+        let recipient = recipient.as_slice();
+        if recipient.len() != DWB_RECIPIENT_BYTES {
+            return Err(StdError::generic_err("dwb: invalid recipient length"));
+        }
+        let mut result = [0u8; DWB_ENTRY_BYTES];
+        result[..DWB_RECIPIENT_BYTES].copy_from_slice(recipient);
+        Ok(Self { 0: result })
+    }
+
+    pub fn recipient_slice(&self) -> &[u8] {
+        &self.0[..DWB_RECIPIENT_BYTES]
+    }
+
+    pub fn recipient(&self) -> StdResult<CanonicalAddr> {
+        let result = CanonicalAddr::try_from(self.recipient_slice())
+            .or(Err(StdError::generic_err("Get dwb recipient error")))?;
+        Ok(result)
+    }
+
+    fn set_recipient(&mut self, val: &CanonicalAddr) -> StdResult<()> {
+        let val_slice = val.as_slice();
+        if val_slice.len() != DWB_RECIPIENT_BYTES {
+            return Err(StdError::generic_err("Set dwb recipient error"));
+        }
+        self.0[..DWB_RECIPIENT_BYTES].copy_from_slice(val_slice);
+        Ok(())
+    }
+
+    pub fn amount(&self) -> StdResult<u64> {
+        let start = DWB_RECIPIENT_BYTES;
+        let end = start + DWB_AMOUNT_BYTES;
+        let amount_slice = &self.0[start..end];
+        let result = amount_slice
+            .try_into()
+            .or(Err(StdError::generic_err("Get dwb amount error")))?;
+        Ok(u64::from_be_bytes(result))
+    }
+
+    fn set_amount(&mut self, val: u64) -> StdResult<()> {
+        let start = DWB_RECIPIENT_BYTES;
+        let end = start + DWB_AMOUNT_BYTES;
+        self.0[start..end].copy_from_slice(&val.to_be_bytes());
+        Ok(())
+    }
+
+    pub fn head_node(&self) -> StdResult<u64> {
+        let start = DWB_RECIPIENT_BYTES + DWB_AMOUNT_BYTES;
+        let end = start + DWB_HEAD_NODE_BYTES;
+        let head_node_slice = &self.0[start..end];
+        let mut result = [0u8; U64_BYTES];
+        result[U64_BYTES - DWB_HEAD_NODE_BYTES..].copy_from_slice(head_node_slice);
+        Ok(u64::from_be_bytes(result))
+    }
+
+    fn set_head_node(&mut self, val: u64) -> StdResult<()> {
+        let start = DWB_RECIPIENT_BYTES + DWB_AMOUNT_BYTES;
+        let end = start + DWB_HEAD_NODE_BYTES;
+        let val_bytes = &val.to_be_bytes()[U64_BYTES - DWB_HEAD_NODE_BYTES..];
+        if val_bytes.len() != DWB_HEAD_NODE_BYTES {
+            return Err(StdError::generic_err("Set dwb head node error"));
+        }
+        self.0[start..end].copy_from_slice(val_bytes);
+        Ok(())
+    }
+
+    pub fn list_len(&self) -> StdResult<u16> {
+        let start = DWB_RECIPIENT_BYTES + DWB_AMOUNT_BYTES + DWB_HEAD_NODE_BYTES;
+        let end = start + DWB_LIST_LEN_BYTES;
+        let list_len_slice = &self.0[start..end];
+        let result = list_len_slice
+            .try_into()
+            .or(Err(StdError::generic_err("Get dwb list len error")))?;
+        Ok(u16::from_be_bytes(result))
+    }
+
+    fn set_list_len(&mut self, val: u16) -> StdResult<()> {
+        let start = DWB_RECIPIENT_BYTES + DWB_AMOUNT_BYTES + DWB_HEAD_NODE_BYTES;
+        let end = start + DWB_LIST_LEN_BYTES;
+        self.0[start..end].copy_from_slice(&val.to_be_bytes());
+        Ok(())
+    }
+
+    /// adds a tx node to the linked list
+    /// returns: the new head node
+    fn add_tx_node(&mut self, store: &mut dyn Storage, tx_id: u64) -> StdResult<u64> {
+        let tx_node = TxNode {
+            tx_id,
+            next: self.head_node()?,
+        };
+
+        // store the new node on chain
+        let new_node = store_new_tx_node(store, tx_node)?;
+        // set the head node to the new node id
+        self.set_head_node(new_node)?;
+        // increment the node list length
+        self.set_list_len(self.list_len()? + 1)?;
+
+        Ok(new_node)
+    }
+
+    // adds some amount to the total amount for all txs in the entry linked list
+    // returns: the new amount
+    fn add_amount(&mut self, add_tx_amount: u128) -> StdResult<u64> {
+        // change this to safe_add if your coin needs to store amount in buffer as u128 (e.g. 18 decimals)
+        let mut amount = self.amount()?;
+        let add_tx_amount_u64 = amount_u64(Some(add_tx_amount))?;
+        safe_add_u64(&mut amount, add_tx_amount_u64);
+        self.set_amount(amount)?;
+
+        Ok(amount)
+    }
+}
+
+pub fn amount_u64(amount_spent: Option<u128>) -> StdResult<u64> {
+    let amount_spent = amount_spent.unwrap_or_default();
+    let amount_spent_u64 = amount_spent
+        .try_into()
+        .or_else(|_| return Err(StdError::generic_err("se: spent overflow")))?;
+    Ok(amount_spent_u64)
+}
+
+#[derive(Serialize, Deserialize, Clone, Copy, Debug)]
+pub struct TxNode {
+    /// transaction id in the TRANSACTIONS list
+    pub tx_id: u64,
+    /// TX_NODES idx - pointer to the next node in the linked list
+    /// 0 if next is null
+    pub next: u64,
+}
+
+impl TxNode {
+    // converts this and following elements in list to a vec of Tx
+    pub fn to_vec(&self, store: &dyn Storage, api: &dyn Api) -> StdResult<Vec<Tx>> {
+        let mut result = vec![];
+        let mut cur_node = Some(self.to_owned());
+        while cur_node.is_some() {
+            let node = cur_node.unwrap();
+            let stored_tx = TRANSACTIONS
+                .add_suffix(&node.tx_id.to_be_bytes())
+                .load(store)?;
+            let tx = stored_tx.into_humanized(api, node.tx_id)?;
+            result.push(tx);
+            if node.next > 0 {
+                let next_node = TX_NODES.add_suffix(&node.next.to_be_bytes()).load(store)?;
+                cur_node = Some(next_node);
+            } else {
+                cur_node = None;
+            }
+        }
+
+        Ok(result)
+    }
+}
+
+/// A tx bundle is 1 or more tx nodes added to an account's history.
+/// The bundle points to a linked list of transaction nodes, which each reference
+/// a transaction record by its global id.
+/// used with add_suffix(canonical addr of account)
+#[derive(Serialize, Deserialize, Clone, Debug)]
+pub struct TxBundle {
+    /// TX_NODES idx - pointer to the head tx node in the linked list
+    pub head_node: u64,
+    /// length of the tx node linked list for this element
+    pub list_len: u16,
+    /// offset of the first tx of this bundle in the history of txs for the account (for pagination)
+    pub offset: u32,
+}
+
+#[inline]
+fn constant_time_is_not_zero(value: i32) -> u32 {
+    (((value | -value) >> 31) & 1) as u32
+}
+
+#[inline]
+fn constant_time_if_else(condition: u32, then: usize, els: usize) -> usize {
+    (then * condition as usize) | (els * (1 - condition as usize))
+}
+
+#[cfg(feature = "gas_tracking")]
+pub fn log_dwb(storage: &dyn Storage) -> StdResult<Binary> {
+    let dwb = DWB.load(storage)?;
+    to_binary(&QueryAnswer::Dwb {
+        dwb: format!("{:?}", dwb),
+    })
+}
+
+#[cfg(test)]
+mod tests {
+    use crate::contract::instantiate;
+    use crate::msg::{InitialBalance, InstantiateMsg};
+    use crate::transaction_history::{append_new_stored_tx, StoredTxAction};
+    use cosmwasm_std::{testing::*, Binary, OwnedDeps, Response, Uint128};
+
+    use super::*;
+
+    fn init_helper(
+        initial_balances: Vec<InitialBalance>,
+    ) -> (
+        StdResult<Response>,
+        OwnedDeps<MockStorage, MockApi, MockQuerier>,
+    ) {
+        let mut deps = mock_dependencies_with_balance(&[]);
+        let env = mock_env();
+        let info = mock_info("instantiator", &[]);
+
+        let init_msg = InstantiateMsg {
+            name: "sec-sec".to_string(),
+            admin: Some("admin".to_string()),
+            symbol: "SECSEC".to_string(),
+            decimals: 8,
+            initial_balances: Some(initial_balances),
+            prng_seed: Binary::from("lolz fun yay".as_bytes()),
+            config: None,
+            supported_denoms: None,
+        };
+
+        (instantiate(deps.as_mut(), env, info, init_msg), deps)
+    }
+
+    #[test]
+    fn test_dwb_entry() {
+        let (init_result, mut deps) = init_helper(vec![InitialBalance {
+            address: "bob".to_string(),
+            amount: Uint128::new(5000),
+        }]);
+        assert!(
+            init_result.is_ok(),
+            "Init failed: {}",
+            init_result.err().unwrap()
+        );
+        let env = mock_env();
+        let _info = mock_info("bob", &[]);
+
+        let recipient = CanonicalAddr::from(ZERO_ADDR);
+        let mut dwb_entry = DelayedWriteBufferEntry::new(&recipient).unwrap();
+        assert_eq!(dwb_entry, DelayedWriteBufferEntry([0u8; DWB_ENTRY_BYTES]));
+
+        assert_eq!(
+            dwb_entry.recipient().unwrap(),
+            CanonicalAddr::from(ZERO_ADDR)
+        );
+        assert_eq!(dwb_entry.amount().unwrap(), 0u64);
+        assert_eq!(dwb_entry.head_node().unwrap(), 0u64);
+        assert_eq!(dwb_entry.list_len().unwrap(), 0u16);
+
+        let canonical_addr = CanonicalAddr::from(&[1u8; DWB_RECIPIENT_BYTES]);
+        dwb_entry.set_recipient(&canonical_addr).unwrap();
+        dwb_entry.set_amount(1).unwrap();
+        dwb_entry.set_head_node(1).unwrap();
+        dwb_entry.set_list_len(1).unwrap();
+
+        assert_eq!(
+            dwb_entry.recipient().unwrap(),
+            CanonicalAddr::from(&[1u8; DWB_RECIPIENT_BYTES])
+        );
+        assert_eq!(dwb_entry.amount().unwrap(), 1u64);
+        assert_eq!(dwb_entry.head_node().unwrap(), 1u64);
+        assert_eq!(dwb_entry.list_len().unwrap(), 1u16);
+
+        // first store the tx information in the global append list of txs and get the new tx id
+        let storage = deps.as_mut().storage;
+        let from = CanonicalAddr::from(&[2u8; 20]);
+        let sender = CanonicalAddr::from(&[2u8; 20]);
+        let to = CanonicalAddr::from(&[1u8; 20]);
+        let action = StoredTxAction::transfer(from.clone(), sender.clone(), to.clone());
+        let tx_id = append_new_stored_tx(
+            storage,
+            &action,
+            1000u128,
+            "uscrt".to_string(),
+            Some("memo".to_string()),
+            &env.block,
+        )
+        .unwrap();
+
+        let result = dwb_entry.add_tx_node(storage, tx_id).unwrap();
+        assert_eq!(dwb_entry.head_node().unwrap(), result);
+    }
+}
diff --git a/src/gas_tracker.rs b/src/gas_tracker.rs
new file mode 100644
index 00000000..8783e630
--- /dev/null
+++ b/src/gas_tracker.rs
@@ -0,0 +1,90 @@
+use cosmwasm_std::{Api, Response};
+
+pub struct GasTracker<'a> {
+    logs: Vec<(String, String)>,
+    api: &'a dyn Api,
+}
+
+impl<'a> GasTracker<'a> {
+    pub fn new(api: &'a dyn Api) -> Self {
+        Self {
+            logs: Vec::new(),
+            api,
+        }
+    }
+
+    pub fn group<'b>(&'b mut self, name: &str) -> GasGroup<'a, 'b> {
+        let mut group = GasGroup::new(self, name.to_string());
+        group.mark();
+        group
+    }
+
+    // pub fn from<'b>(&'b mut self, other: GasGroup<'b, 'b>) -> GasGroup<'a, 'b> {
+    //     let mut group = GasGroup::new(self, other.name);
+    //     group.index = other.index;
+    //     group
+    // }
+
+    // pub fn from<'b>(&'b mut self, name: &str, index: usize) -> GasGroup<'a, 'b> {
+    //     let mut group = GasGroup::new(self, name.to_string());
+    //     group.index = index;
+    //     group
+    // }
+
+    pub fn add_to_response(self, resp: Response) -> Response {
+        let mut new_resp = resp.clone();
+        for log in self.logs.into_iter() {
+            new_resp = new_resp.add_attribute_plaintext(log.0, log.1);
+        }
+        new_resp
+    }
+}
+
+pub trait LoggingExt {
+    fn add_gas_tracker(&self, tracker: GasTracker) -> Response;
+}
+
+impl LoggingExt for Response {
+    fn add_gas_tracker(&self, tracker: GasTracker) -> Response {
+        tracker.add_to_response(self.to_owned())
+    }
+}
+
+pub struct GasGroup<'a, 'b> {
+    pub tracker: &'b mut GasTracker<'a>,
+    pub name: String,
+    pub index: usize,
+}
+
+impl<'a, 'b> GasGroup<'a, 'b> {
+    fn new(tracker: &'b mut GasTracker<'a>, name: String) -> Self {
+        Self {
+            tracker,
+            name,
+            index: 0,
+        }
+    }
+
+    pub fn mark(&mut self) {
+        self.log("");
+    }
+
+    pub fn log(&mut self, comment: &str) {
+        let gas = self.tracker.api.check_gas();
+        let log_entry = (
+            format!("gas.{}", self.name,),
+            format!(
+                "{}:{}:{}",
+                self.index,
+                gas.unwrap_or(0u64).to_string(),
+                comment
+            ),
+        );
+        self.tracker.logs.push(log_entry);
+        self.index += 1;
+    }
+
+    pub fn logf(&mut self, comment: String) {
+        self.log(comment.as_str())
+    }
+}
diff --git a/src/lib.rs b/src/lib.rs
index 9bafd896..b9928515 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -1,6 +1,14 @@
+#[macro_use]
+extern crate static_assertions as sa;
+
 mod batch;
+mod btbe;
 pub mod contract;
+mod dwb;
+mod gas_tracker;
 pub mod msg;
 pub mod receiver;
 pub mod state;
+mod strings;
 mod transaction_history;
+mod notifications;
\ No newline at end of file
diff --git a/src/msg.rs b/src/msg.rs
index cc583dd7..09d0be73 100644
--- a/src/msg.rs
+++ b/src/msg.rs
@@ -3,11 +3,11 @@
 use schemars::JsonSchema;
 use serde::{Deserialize, Serialize};
 
-use crate::batch;
-use crate::batch::HasDecoy;
-use crate::transaction_history::{ExtendedTx, Tx};
-use cosmwasm_std::{Addr, Api, Binary, StdError, StdResult, Uint128};
-use secret_toolkit::permit::Permit;
+use crate::{batch, transaction_history::Tx};
+use cosmwasm_std::{Addr, Api, Binary, StdError, StdResult, Uint128, Uint64,};
+#[cfg(feature = "gas_evaporation")]
+use cosmwasm_std::Uint64;
+use secret_toolkit::{notification::ChannelInfoData, permit::Permit};
 
 #[cfg_attr(test, derive(Eq, PartialEq))]
 #[derive(Serialize, Deserialize, Clone, JsonSchema)]
@@ -55,7 +55,7 @@ pub struct InitConfig {
     /// Indicates whether burn functionality should be enabled
     /// default: False
     enable_burn: Option<bool>,
-    /// Indicated whether an admin can modify supported denoms
+    /// Indicates whether an admin can modify supported denoms
     /// default: False
     can_modify_denoms: Option<bool>,
 }
@@ -93,13 +93,13 @@ pub enum ExecuteMsg {
     Redeem {
         amount: Uint128,
         denom: Option<String>,
-        decoys: Option<Vec<Addr>>,
-        entropy: Option<Binary>,
+        #[cfg(feature = "gas_evaporation")]
+        gas_target: Option<Uint64>,
         padding: Option<String>,
     },
     Deposit {
-        decoys: Option<Vec<Addr>>,
-        entropy: Option<Binary>,
+        #[cfg(feature = "gas_evaporation")]
+        gas_target: Option<Uint64>,
         padding: Option<String>,
     },
 
@@ -108,8 +108,8 @@ pub enum ExecuteMsg {
         recipient: String,
         amount: Uint128,
         memo: Option<String>,
-        decoys: Option<Vec<Addr>>,
-        entropy: Option<Binary>,
+        #[cfg(feature = "gas_evaporation")]
+        gas_target: Option<Uint64>,
         padding: Option<String>,
     },
     Send {
@@ -118,37 +118,45 @@ pub enum ExecuteMsg {
         amount: Uint128,
         msg: Option<Binary>,
         memo: Option<String>,
-        decoys: Option<Vec<Addr>>,
-        entropy: Option<Binary>,
+        #[cfg(feature = "gas_evaporation")]
+        gas_target: Option<Uint64>,
         padding: Option<String>,
     },
     BatchTransfer {
         actions: Vec<batch::TransferAction>,
-        entropy: Option<Binary>,
+        #[cfg(feature = "gas_evaporation")]
+        gas_target: Option<Uint64>,
         padding: Option<String>,
     },
     BatchSend {
         actions: Vec<batch::SendAction>,
-        entropy: Option<Binary>,
+        #[cfg(feature = "gas_evaporation")]
+        gas_target: Option<Uint64>,
         padding: Option<String>,
     },
     Burn {
         amount: Uint128,
         memo: Option<String>,
-        decoys: Option<Vec<Addr>>,
-        entropy: Option<Binary>,
+        #[cfg(feature = "gas_evaporation")]
+        gas_target: Option<Uint64>,
         padding: Option<String>,
     },
     RegisterReceive {
         code_hash: String,
+        #[cfg(feature = "gas_evaporation")]
+        gas_target: Option<Uint64>,
         padding: Option<String>,
     },
     CreateViewingKey {
-        entropy: String,
+        entropy: Option<String>,
+        #[cfg(feature = "gas_evaporation")]
+        gas_target: Option<Uint64>,
         padding: Option<String>,
     },
     SetViewingKey {
         key: String,
+        #[cfg(feature = "gas_evaporation")]
+        gas_target: Option<Uint64>,
         padding: Option<String>,
     },
 
@@ -157,12 +165,16 @@ pub enum ExecuteMsg {
         spender: String,
         amount: Uint128,
         expiration: Option<u64>,
+        #[cfg(feature = "gas_evaporation")]
+        gas_target: Option<Uint64>,
         padding: Option<String>,
     },
     DecreaseAllowance {
         spender: String,
         amount: Uint128,
         expiration: Option<u64>,
+        #[cfg(feature = "gas_evaporation")]
+        gas_target: Option<Uint64>,
         padding: Option<String>,
     },
     TransferFrom {
@@ -170,8 +182,8 @@ pub enum ExecuteMsg {
         recipient: String,
         amount: Uint128,
         memo: Option<String>,
-        decoys: Option<Vec<Addr>>,
-        entropy: Option<Binary>,
+        #[cfg(feature = "gas_evaporation")]
+        gas_target: Option<Uint64>,
         padding: Option<String>,
     },
     SendFrom {
@@ -181,31 +193,34 @@ pub enum ExecuteMsg {
         amount: Uint128,
         msg: Option<Binary>,
         memo: Option<String>,
-        decoys: Option<Vec<Addr>>,
-        entropy: Option<Binary>,
+        #[cfg(feature = "gas_evaporation")]
+        gas_target: Option<Uint64>,
         padding: Option<String>,
     },
     BatchTransferFrom {
         actions: Vec<batch::TransferFromAction>,
-        entropy: Option<Binary>,
+        #[cfg(feature = "gas_evaporation")]
+        gas_target: Option<Uint64>,
         padding: Option<String>,
     },
     BatchSendFrom {
         actions: Vec<batch::SendFromAction>,
-        entropy: Option<Binary>,
+        #[cfg(feature = "gas_evaporation")]
+        gas_target: Option<Uint64>,
         padding: Option<String>,
     },
     BurnFrom {
         owner: String,
         amount: Uint128,
         memo: Option<String>,
-        decoys: Option<Vec<Addr>>,
-        entropy: Option<Binary>,
+        #[cfg(feature = "gas_evaporation")]
+        gas_target: Option<Uint64>,
         padding: Option<String>,
     },
     BatchBurnFrom {
         actions: Vec<batch::BurnFromAction>,
-        entropy: Option<Binary>,
+        #[cfg(feature = "gas_evaporation")]
+        gas_target: Option<Uint64>,
         padding: Option<String>,
     },
 
@@ -214,121 +229,70 @@ pub enum ExecuteMsg {
         recipient: String,
         amount: Uint128,
         memo: Option<String>,
-        decoys: Option<Vec<Addr>>,
-        entropy: Option<Binary>,
+        #[cfg(feature = "gas_evaporation")]
+        gas_target: Option<Uint64>,
         padding: Option<String>,
     },
     BatchMint {
         actions: Vec<batch::MintAction>,
-        entropy: Option<Binary>,
+        #[cfg(feature = "gas_evaporation")]
+        gas_target: Option<Uint64>,
         padding: Option<String>,
     },
     AddMinters {
         minters: Vec<String>,
+        #[cfg(feature = "gas_evaporation")]
+        gas_target: Option<Uint64>,
         padding: Option<String>,
     },
     RemoveMinters {
         minters: Vec<String>,
+        #[cfg(feature = "gas_evaporation")]
+        gas_target: Option<Uint64>,
         padding: Option<String>,
     },
     SetMinters {
         minters: Vec<String>,
+        #[cfg(feature = "gas_evaporation")]
+        gas_target: Option<Uint64>,
         padding: Option<String>,
     },
 
     // Admin
     ChangeAdmin {
         address: String,
+        #[cfg(feature = "gas_evaporation")]
+        gas_target: Option<Uint64>,
         padding: Option<String>,
     },
     SetContractStatus {
         level: ContractStatusLevel,
+        #[cfg(feature = "gas_evaporation")]
+        gas_target: Option<Uint64>,
         padding: Option<String>,
     },
     /// Add deposit/redeem support for these coin denoms
-    AddSupportedDenoms { denoms: Vec<String> },
+    AddSupportedDenoms {
+        denoms: Vec<String>,
+        #[cfg(feature = "gas_evaporation")]
+        gas_target: Option<Uint64>,
+    },
     /// Remove deposit/redeem support for these coin denoms
-    RemoveSupportedDenoms { denoms: Vec<String> },
+    RemoveSupportedDenoms {
+        denoms: Vec<String>,
+        #[cfg(feature = "gas_evaporation")]
+        gas_target: Option<Uint64>,
+    },
 
     // Permit
     RevokePermit {
         permit_name: String,
+        #[cfg(feature = "gas_evaporation")]
+        gas_target: Option<Uint64>,
         padding: Option<String>,
     },
 }
 
-pub trait Decoyable {
-    fn get_minimal_decoys_size(&self) -> usize;
-    fn get_entropy(self) -> Option<Binary>;
-}
-
-impl Decoyable for ExecuteMsg {
-    fn get_minimal_decoys_size(&self) -> usize {
-        match self {
-            ExecuteMsg::Deposit { decoys, .. }
-            | ExecuteMsg::Redeem { decoys, .. }
-            | ExecuteMsg::Transfer { decoys, .. }
-            | ExecuteMsg::Send { decoys, .. }
-            | ExecuteMsg::Burn { decoys, .. }
-            | ExecuteMsg::Mint { decoys, .. }
-            | ExecuteMsg::TransferFrom { decoys, .. }
-            | ExecuteMsg::SendFrom { decoys, .. }
-            | ExecuteMsg::BurnFrom { decoys, .. } => {
-                if let Some(user_decoys) = decoys {
-                    return user_decoys.len();
-                }
-
-                0
-            }
-            ExecuteMsg::BatchSendFrom { actions, .. } => get_min_decoys_count(actions),
-            ExecuteMsg::BatchTransferFrom { actions, .. } => get_min_decoys_count(actions),
-            ExecuteMsg::BatchTransfer { actions, .. } => get_min_decoys_count(actions),
-            ExecuteMsg::BatchSend { actions, .. } => get_min_decoys_count(actions),
-            ExecuteMsg::BatchBurnFrom { actions, .. } => get_min_decoys_count(actions),
-            ExecuteMsg::BatchMint { actions, .. } => get_min_decoys_count(actions),
-            _ => 0,
-        }
-    }
-
-    fn get_entropy(self) -> Option<Binary> {
-        match self {
-            ExecuteMsg::Deposit { entropy, .. }
-            | ExecuteMsg::Redeem { entropy, .. }
-            | ExecuteMsg::Transfer { entropy, .. }
-            | ExecuteMsg::Send { entropy, .. }
-            | ExecuteMsg::Burn { entropy, .. }
-            | ExecuteMsg::Mint { entropy, .. }
-            | ExecuteMsg::TransferFrom { entropy, .. }
-            | ExecuteMsg::SendFrom { entropy, .. }
-            | ExecuteMsg::BurnFrom { entropy, .. }
-            | ExecuteMsg::BatchTransferFrom { entropy, .. }
-            | ExecuteMsg::BatchSendFrom { entropy, .. }
-            | ExecuteMsg::BatchTransfer { entropy, .. }
-            | ExecuteMsg::BatchSend { entropy, .. }
-            | ExecuteMsg::BatchBurnFrom { entropy, .. }
-            | ExecuteMsg::BatchMint { entropy, .. } => entropy,
-            _ => None,
-        }
-    }
-}
-
-fn get_min_decoys_count<T: HasDecoy>(actions: &[T]) -> usize {
-    let mut min_decoys_count = usize::MAX;
-    for action in actions {
-        if let Some(user_decoys) = &action.decoys() {
-            if user_decoys.len() < min_decoys_count {
-                min_decoys_count = user_decoys.len();
-            }
-        }
-    }
-
-    if min_decoys_count == usize::MAX {
-        0
-    } else {
-        min_decoys_count
-    }
-}
-
 #[derive(Serialize, Deserialize, JsonSchema, Debug)]
 #[serde(rename_all = "snake_case")]
 pub enum ExecuteAnswer {
@@ -433,6 +397,58 @@ pub enum ExecuteAnswer {
     },
 }
 
+#[cfg(feature = "gas_evaporation")]
+pub trait Evaporator {
+    fn evaporate_to_target(&self, api: &dyn Api) -> StdResult<u64>;
+}
+
+#[cfg(feature = "gas_evaporation")]
+impl Evaporator for ExecuteMsg {
+    fn evaporate_to_target(&self, api: &dyn Api) -> StdResult<u64> {
+        match self {
+            ExecuteMsg::Redeem { gas_target, .. }
+            | ExecuteMsg::Deposit { gas_target, .. }
+            | ExecuteMsg::Transfer { gas_target, .. }
+            | ExecuteMsg::Send { gas_target, .. }
+            | ExecuteMsg::BatchTransfer { gas_target, .. }
+            | ExecuteMsg::BatchSend { gas_target, .. }
+            | ExecuteMsg::Burn { gas_target, .. }
+            | ExecuteMsg::RegisterReceive { gas_target, .. }
+            | ExecuteMsg::CreateViewingKey { gas_target, .. }
+            | ExecuteMsg::SetViewingKey { gas_target, .. }
+            | ExecuteMsg::IncreaseAllowance { gas_target, .. }
+            | ExecuteMsg::DecreaseAllowance { gas_target, .. }
+            | ExecuteMsg::TransferFrom { gas_target, .. }
+            | ExecuteMsg::SendFrom { gas_target, .. }
+            | ExecuteMsg::BatchTransferFrom { gas_target, .. }
+            | ExecuteMsg::BatchSendFrom { gas_target, .. }
+            | ExecuteMsg::BurnFrom { gas_target, .. }
+            | ExecuteMsg::BatchBurnFrom { gas_target, .. }
+            | ExecuteMsg::Mint { gas_target, .. }
+            | ExecuteMsg::BatchMint { gas_target, .. }
+            | ExecuteMsg::AddMinters { gas_target, .. }
+            | ExecuteMsg::RemoveMinters { gas_target, .. }
+            | ExecuteMsg::SetMinters { gas_target, .. }
+            | ExecuteMsg::ChangeAdmin { gas_target, .. }
+            | ExecuteMsg::SetContractStatus { gas_target, .. }
+            | ExecuteMsg::AddSupportedDenoms { gas_target, .. }
+            | ExecuteMsg::RemoveSupportedDenoms { gas_target, .. }
+            | ExecuteMsg::RevokePermit { gas_target, .. } => match gas_target {
+                Some(gas_target) => {
+                    let gas_used = api.check_gas()?;
+                    if gas_used < gas_target.u64() {
+                        let evaporate_amount = gas_target.u64() - gas_used;
+                        // api.gas_evaporate(evaporate_amount as u32)?;
+                        return Ok(evaporate_amount)
+                    }
+                    Ok(0)
+                }
+                None => Ok(0),
+            },
+        }
+    }
+}
+
 #[derive(Serialize, Deserialize, Clone, Debug, JsonSchema)]
 #[cfg_attr(test, derive(Eq, PartialEq))]
 #[serde(rename_all = "snake_case")]
@@ -467,20 +483,43 @@ pub enum QueryMsg {
         key: String,
         page: Option<u32>,
         page_size: u32,
-        should_filter_decoys: bool,
     },
     TransactionHistory {
         address: String,
         key: String,
         page: Option<u32>,
         page_size: u32,
-        should_filter_decoys: bool,
     },
     Minters {},
+
+    // SNIP-52 Private Push Notifications
+    /// Public query to list all notification channels
+    ListChannels {},
+    /// Authenticated query allows clients to obtain the seed
+    /// and schema for a specific channel.
+    ChannelInfo {
+        channels: Vec<String>,
+        txhash: Option<String>,
+        viewer: ViewerInfo,
+    },
+
     WithPermit {
         permit: Permit,
         query: QueryWithPermit,
     },
+
+    // for debug purposes only
+    #[cfg(feature = "gas_tracking")]
+    Dwb {},
+}
+
+/// the address and viewing key making an authenticated query request
+#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
+pub struct ViewerInfo {
+    /// querying address
+    pub address: String,
+    /// authentication key string
+    pub viewing_key: String,
 }
 
 impl QueryMsg {
@@ -517,6 +556,10 @@ impl QueryMsg {
                 let spender = api.addr_validate(spender.as_str())?;
                 Ok((vec![spender], key.clone()))
             }
+            Self::ChannelInfo { viewer, .. } => {
+                let address = api.addr_validate(viewer.address.as_str())?;
+                Ok((vec![address], viewer.viewing_key.clone()))
+            }
             _ => panic!("This query type does not require authentication"),
         }
     }
@@ -544,12 +587,15 @@ pub enum QueryWithPermit {
     TransferHistory {
         page: Option<u32>,
         page_size: u32,
-        should_filter_decoys: bool,
     },
     TransactionHistory {
         page: Option<u32>,
         page_size: u32,
-        should_filter_decoys: bool,
+    },
+    // SNIP-52 Private Push Notifications
+    ChannelInfo {
+        channels: Vec<String>,
+        txhash: Option<String>,
     },
 }
 
@@ -596,12 +642,8 @@ pub enum QueryAnswer {
     Balance {
         amount: Uint128,
     },
-    TransferHistory {
-        txs: Vec<Tx>,
-        total: Option<u64>,
-    },
     TransactionHistory {
-        txs: Vec<ExtendedTx>,
+        txs: Vec<Tx>,
         total: Option<u64>,
     },
     ViewingKeyError {
@@ -610,6 +652,23 @@ pub enum QueryAnswer {
     Minters {
         minters: Vec<Addr>,
     },
+    
+    // SNIP-52 Private Push Notifications
+    ListChannels {
+        channels: Vec<String>,
+    },
+    ChannelInfo {
+        /// scopes validity of this response
+        as_of_block: Uint64,
+        /// shared secret in base64
+        seed: Binary,
+        channels: Vec<ChannelInfoData>,
+    },
+
+    #[cfg(feature = "gas_tracking")]
+    Dwb {
+        dwb: String,
+    },
 }
 
 #[derive(Serialize, Deserialize, JsonSchema, Clone, Debug)]
diff --git a/src/notifications.rs b/src/notifications.rs
new file mode 100644
index 00000000..38e76f74
--- /dev/null
+++ b/src/notifications.rs
@@ -0,0 +1,402 @@
+use std::collections::HashMap;
+
+use cosmwasm_std::{Addr, Api, Binary, CanonicalAddr, StdError, StdResult};
+use primitive_types::{U256, U512};
+use secret_toolkit::notification::{get_seed, notification_id, xor_bytes, Notification, NotificationData};
+use minicbor_ser as cbor;
+use secret_toolkit_crypto::{hkdf_sha_512, sha_256};
+use serde::{Deserialize, Serialize};
+
+const ZERO_ADDR: [u8; 20] = [0u8; 20];
+
+//  recvd = [
+//      amount: biguint,   ; transfer amount in base denomination
+//      sender: bstr,      ; byte sequence of sender's canonical address
+//      balance: biguint   ; recipient's new balance after the transfer
+//  ]
+
+#[derive(Serialize, Debug, Deserialize, Clone)]
+#[cfg_attr(test, derive(Eq, PartialEq))]
+pub struct ReceivedNotificationData {
+    pub amount: u128,
+    pub sender: Option<Addr>,
+}
+
+impl NotificationData for ReceivedNotificationData {
+	const CHANNEL_ID: &'static str = "recvd";
+	const CDDL_SCHEMA: &'static str = "recvd=[amount:biguint,sender:bstr]";
+
+    fn to_cbor(&self, api: &dyn Api) -> StdResult<Vec<u8>> {
+        let received_data;
+        if let Some(sender) = &self.sender {
+            let sender_raw = api.addr_canonicalize(sender.as_str())?;
+            received_data = cbor::to_vec(&(self.amount.to_be_bytes(), sender_raw.as_slice()))
+                .map_err(|e| StdError::generic_err(format!("{:?}", e)))?;
+        } else {
+            received_data = cbor::to_vec(&(self.amount.to_be_bytes(), ZERO_ADDR))
+                .map_err(|e| StdError::generic_err(format!("{:?}", e)))?;
+        }
+        Ok(received_data)
+    }
+}
+
+// spent = [
+//     amount: biguint,   ; transfer amount in base denomination
+//     actions: uint      ; number of actions the execution performed
+//     recipient: bstr,   ; byte sequence of first recipient's canonical address
+//     balance: biguint   ; sender's new balance aactions: uint      ; number of actions the execution performedfter the transfer
+// ]
+
+#[derive(Serialize, Debug, Deserialize, Clone)]
+#[cfg_attr(test, derive(Eq, PartialEq))]
+pub struct SpentNotificationData {
+    pub amount: u128,
+    pub actions: u32,
+    pub recipient: Option<Addr>,
+    pub balance: u128,
+}
+
+impl NotificationData for SpentNotificationData {
+    const CHANNEL_ID: &'static str = "spent";
+	const CDDL_SCHEMA: &'static str = "spent=[amount:biguint,actions:uint,recipient:bstr,balance:biguint]";
+    fn to_cbor(&self, api: &dyn Api) -> StdResult<Vec<u8>> {
+        let spent_data;
+        if let Some(recipient) = &self.recipient {
+            let recipient_raw = api.addr_canonicalize(recipient.as_str())?;
+            spent_data = cbor::to_vec(&(
+                self.amount.to_be_bytes(),
+                self.actions.to_be_bytes(),
+                recipient_raw.as_slice(),
+                self.balance.to_be_bytes(),
+            ))
+            .map_err(|e| StdError::generic_err(format!("{:?}", e)))?;
+        } else {
+            spent_data = cbor::to_vec(&(
+                self.amount.to_be_bytes(),
+                self.actions.to_be_bytes(),
+                ZERO_ADDR,
+                self.balance.to_be_bytes(),
+            ))
+            .map_err(|e| StdError::generic_err(format!("{:?}", e)))?;
+        }
+        Ok(spent_data)
+    }
+}
+
+//allowance = [
+//    amount: biguint,   ; allowance amount in base denomination
+//    allower: bstr,     ; byte sequence of allower's canonical address
+//    expiration: uint,  ; epoch seconds of allowance expiration
+//]
+
+#[derive(Serialize, Debug, Deserialize, Clone)]
+#[cfg_attr(test, derive(Eq, PartialEq))]
+pub struct AllowanceNotificationData {
+    pub amount: u128,
+    pub allower: Addr,
+    pub expiration: Option<u64>,
+}
+
+impl NotificationData for AllowanceNotificationData {
+    const CHANNEL_ID: &'static str = "allowance";
+    const CDDL_SCHEMA: &'static str = "allowance=[amount:biguint,allower:bstr,expiration:uint]";
+    fn to_cbor(&self, api: &dyn Api) -> StdResult<Vec<u8>> {
+        let allower_raw = api.addr_canonicalize(self.allower.as_str())?;
+
+        // use CBOR to encode data
+        let updated_allowance_data = cbor::to_vec(&(
+            self.amount.to_be_bytes(),
+            allower_raw.as_slice(),
+            self.expiration.unwrap_or(0u64), // expiration == 0 means no expiration
+        ))
+        .map_err(|e| StdError::generic_err(format!("{:?}", e)))?;
+        Ok(updated_allowance_data)
+    }
+}
+
+// multi recipient push notifications
+
+// id for the `multirecvd` channel
+pub const MULTI_RECEIVED_CHANNEL_ID: &str = "multirecvd";
+pub const MULTI_RECEIVED_CHANNEL_BLOOM_K: u32 = 15;
+pub const MULTI_RECEIVED_CHANNEL_BLOOM_N: u32 = 16;
+pub const MULTI_RECEIVED_CHANNEL_PACKET_SIZE: u32 = 24;
+
+// id for the `multispent` channel
+pub const MULTI_SPENT_CHANNEL_ID: &str = "multispent";
+pub const MULTI_SPENT_CHANNEL_BLOOM_K: u32 = 5;
+pub const MULTI_SPENT_CHANNEL_BLOOM_N: u32 = 4;
+pub const MULTI_SPENT_CHANNEL_PACKET_SIZE: u32 = 40;
+
+pub fn multi_received_data(
+    api: &dyn Api,
+    notifications: Vec<Notification<ReceivedNotificationData>>,
+    tx_hash: &String,
+    env_random: Binary,
+    secret: &[u8],
+) -> StdResult<Vec<u8>> {
+    let mut received_bloom_filter: U512 = U512::from(0);
+    let mut received_packets: Vec<(Addr, Vec<u8>)> = vec![];
+
+    // keep track of how often addresses might show up in packet data.
+    // we need to remove any address that might show up more than once.
+    let mut recipient_counts: HashMap<Addr, u16> = HashMap::new();
+
+    for notification in &notifications {
+        recipient_counts.insert(
+            notification.notification_for.clone(),
+            recipient_counts
+                .get(&notification.notification_for)
+                .unwrap_or(&0u16)
+                + 1,
+        );
+
+        // we can short circuit this if recipient count > 1, since we will throw out this packet
+        // anyway, and address has already been added to bloom filter
+        if *recipient_counts
+            .get(&notification.notification_for)
+            .unwrap()
+            > 1
+        {
+            continue;
+        }
+
+        // contribute to received bloom filter
+        let recipient_addr_raw = api.addr_canonicalize(notification.notification_for.as_str())?;
+        let seed = get_seed(&recipient_addr_raw, secret)?;
+        let id = notification_id(&seed, &MULTI_RECEIVED_CHANNEL_ID.to_string(), &tx_hash)?;
+        let mut hash_bytes = U256::from_big_endian(&sha_256(id.0.as_slice()));
+        for _ in 0..MULTI_RECEIVED_CHANNEL_BLOOM_K {
+            let bit_index = (hash_bytes & U256::from(0x01ff)).as_usize();
+            received_bloom_filter = received_bloom_filter | (U512::from(1) << bit_index);
+            hash_bytes = hash_bytes >> 9;
+        }
+
+        // make the received packet
+        let mut received_packet_plaintext: Vec<u8> = vec![];
+        // amount bytes (u128 == 16 bytes)
+        received_packet_plaintext.extend_from_slice(&notification.data.amount.to_be_bytes());
+        // sender account last 8 bytes
+        let sender_bytes: &[u8];
+        let sender_raw;
+        if let Some(sender) = &notification.data.sender {
+            sender_raw = api.addr_canonicalize(sender.as_str())?;
+            sender_bytes = &sender_raw.as_slice()[sender_raw.0.len() - 8..];
+        } else {
+            sender_bytes = &ZERO_ADDR[ZERO_ADDR.len() - 8..];
+        }
+        // 24 bytes total
+        received_packet_plaintext.extend_from_slice(sender_bytes);
+
+        let received_packet_id = &id.0.as_slice()[0..8];
+        let received_packet_ikm = &id.0.as_slice()[8..32];
+        let received_packet_ciphertext =
+            xor_bytes(received_packet_plaintext.as_slice(), received_packet_ikm);
+        let received_packet_bytes: Vec<u8> =
+            [received_packet_id.to_vec(), received_packet_ciphertext].concat();
+
+        received_packets.push((notification.notification_for.clone(), received_packet_bytes));
+    }
+
+    // filter out any notifications for recipients showing up more than once
+    let mut received_packets: Vec<Vec<u8>> = received_packets
+        .into_iter()
+        .filter(|(addr, _)| *recipient_counts.get(addr).unwrap_or(&0u16) <= 1)
+        .map(|(_, packet)| packet)
+        .collect();
+    if received_packets.len() > MULTI_RECEIVED_CHANNEL_BLOOM_N as usize {
+        // still too many packets
+        received_packets = received_packets[0..MULTI_RECEIVED_CHANNEL_BLOOM_N as usize].to_vec();
+    }
+
+    // now add extra packets, if needed, to hide number of packets
+    let padding_size =
+        MULTI_RECEIVED_CHANNEL_BLOOM_N.saturating_sub(received_packets.len() as u32) as usize;
+    if padding_size > 0 {
+        let padding_addresses = hkdf_sha_512(
+            &Some(vec![0u8; 64]),
+            &env_random,
+            format!("{}:decoys", MULTI_RECEIVED_CHANNEL_ID).as_bytes(),
+            padding_size * 20, // 20 bytes per random addr
+        )?;
+
+        // handle each padding package
+        for i in 0..padding_size {
+            let padding_address = &padding_addresses[i * 20..(i + 1) * 20];
+
+            // contribute padding packet to bloom filter
+            let seed = get_seed(&CanonicalAddr::from(padding_address), secret)?;
+            let id = notification_id(&seed, &MULTI_RECEIVED_CHANNEL_ID.to_string(), &tx_hash)?;
+            let mut hash_bytes = U256::from_big_endian(&sha_256(id.0.as_slice()));
+            for _ in 0..MULTI_RECEIVED_CHANNEL_BLOOM_K {
+                let bit_index = (hash_bytes & U256::from(0x01ff)).as_usize();
+                received_bloom_filter = received_bloom_filter | (U512::from(1) << bit_index);
+                hash_bytes = hash_bytes >> 9;
+            }
+
+            // padding packet plaintext
+            let padding_packet_plaintext = [0u8; MULTI_RECEIVED_CHANNEL_PACKET_SIZE as usize];
+            let padding_packet_id = &id.0.as_slice()[0..8];
+            let padding_packet_ikm = &id.0.as_slice()[8..32];
+            let padding_packet_ciphertext =
+                xor_bytes(padding_packet_plaintext.as_slice(), padding_packet_ikm);
+            let padding_packet_bytes: Vec<u8> =
+                [padding_packet_id.to_vec(), padding_packet_ciphertext].concat();
+            received_packets.push(padding_packet_bytes);
+        }
+    }
+
+    let mut received_bloom_filter_bytes: Vec<u8> = vec![];
+    for biguint in received_bloom_filter.0 {
+        received_bloom_filter_bytes.extend_from_slice(&biguint.to_be_bytes());
+    }
+    for packet in received_packets {
+        received_bloom_filter_bytes.extend(packet.iter());
+    }
+
+    Ok(received_bloom_filter_bytes)
+}
+
+pub fn multi_spent_data(
+    api: &dyn Api,
+    notifications: Vec<Notification<SpentNotificationData>>,
+    tx_hash: &String,
+    env_random: Binary,
+    secret: &[u8],
+) -> StdResult<Vec<u8>> {
+    let mut spent_bloom_filter: U512 = U512::from(0);
+    let mut spent_packets: Vec<(Addr, Vec<u8>)> = vec![];
+
+    // keep track of how often addresses might show up in packet data.
+    // we need to remove any address that might show up more than once.
+    let mut spent_counts: HashMap<Addr, u16> = HashMap::new();
+
+    for notification in &notifications {
+        spent_counts.insert(
+            notification.notification_for.clone(),
+            spent_counts
+                .get(&notification.notification_for)
+                .unwrap_or(&0u16)
+                + 1,
+        );
+
+        // we can short circuit this if recipient count > 1, since we will throw out this packet
+        // anyway, and address has already been added to bloom filter
+        if *spent_counts.get(&notification.notification_for).unwrap() > 1 {
+            continue;
+        }
+
+        let spender_addr_raw = api.addr_canonicalize(notification.notification_for.as_str())?;
+        let seed = get_seed(&spender_addr_raw, secret)?;
+        let id = notification_id(&seed, &MULTI_SPENT_CHANNEL_ID.to_string(), &tx_hash)?;
+        let mut hash_bytes = U256::from_big_endian(&sha_256(id.0.as_slice()));
+        for _ in 0..MULTI_SPENT_CHANNEL_BLOOM_K {
+            let bit_index = (hash_bytes & U256::from(0x01ff)).as_usize();
+            spent_bloom_filter = spent_bloom_filter | (U512::from(1) << bit_index);
+            hash_bytes = hash_bytes >> 9;
+        }
+
+        // make the spent packet
+        let mut spent_packet_plaintext: Vec<u8> = vec![];
+        // amount bytes (u128 == 16 bytes)
+        spent_packet_plaintext.extend_from_slice(&notification.data.amount.to_be_bytes());
+        // balance bytes (u128 == 16 bytes)
+        spent_packet_plaintext.extend_from_slice(&notification.data.balance.to_be_bytes());
+        // recipient account last 8 bytes
+        let recipient_bytes: &[u8];
+        let recipient_raw;
+        if let Some(recipient) = &notification.data.recipient {
+            recipient_raw = api.addr_canonicalize(recipient.as_str())?;
+            recipient_bytes = &recipient_raw.as_slice()[recipient_raw.0.len() - 8..];
+        } else {
+            recipient_bytes = &ZERO_ADDR[ZERO_ADDR.len() - 8..];
+        }
+        // 40 bytes total
+        spent_packet_plaintext.extend_from_slice(recipient_bytes);
+
+        let spent_packet_size = spent_packet_plaintext.len();
+        let spent_packet_id = &id.0.as_slice()[0..8];
+        let spent_packet_ikm = &id.0.as_slice()[8..32];
+        let spent_packet_key = hkdf_sha_512(
+            &Some(vec![0u8; 64]),
+            spent_packet_ikm,
+            "".as_bytes(),
+            spent_packet_size,
+        )?;
+        let spent_packet_ciphertext = xor_bytes(
+            spent_packet_plaintext.as_slice(),
+            spent_packet_key.as_slice(),
+        );
+        let spent_packet_bytes: Vec<u8> =
+            [spent_packet_id.to_vec(), spent_packet_ciphertext].concat();
+
+        spent_packets.push((notification.notification_for.clone(), spent_packet_bytes));
+    }
+
+    // filter out any notifications for senders showing up more than once
+    let mut spent_packets: Vec<Vec<u8>> = spent_packets
+        .into_iter()
+        .filter(|(addr, _)| *spent_counts.get(addr).unwrap_or(&0u16) <= 1)
+        .map(|(_, packet)| packet)
+        .collect();
+    if spent_packets.len() > MULTI_SPENT_CHANNEL_BLOOM_N as usize {
+        // still too many packets
+        spent_packets = spent_packets[0..MULTI_SPENT_CHANNEL_BLOOM_N as usize].to_vec();
+    }
+
+    // now add extra packets, if needed, to hide number of packets
+    let padding_size =
+        MULTI_SPENT_CHANNEL_BLOOM_N.saturating_sub(spent_packets.len() as u32) as usize;
+    if padding_size > 0 {
+        let padding_addresses = hkdf_sha_512(
+            &Some(vec![0u8; 64]),
+            &env_random,
+            format!("{}:decoys", MULTI_SPENT_CHANNEL_ID).as_bytes(),
+            padding_size * 20, // 20 bytes per random addr
+        )?;
+
+        // handle each padding package
+        for i in 0..padding_size {
+            let padding_address = &padding_addresses[i * 20..(i + 1) * 20];
+
+            // contribute padding packet to bloom filter
+            let seed = get_seed(&CanonicalAddr::from(padding_address), secret)?;
+            let id = notification_id(&seed, &MULTI_SPENT_CHANNEL_ID.to_string(), &tx_hash)?;
+            let mut hash_bytes = U256::from_big_endian(&sha_256(id.0.as_slice()));
+            for _ in 0..MULTI_SPENT_CHANNEL_BLOOM_K {
+                let bit_index = (hash_bytes & U256::from(0x01ff)).as_usize();
+                spent_bloom_filter = spent_bloom_filter | (U512::from(1) << bit_index);
+                hash_bytes = hash_bytes >> 9;
+            }
+
+            // padding packet plaintext
+            let padding_packet_plaintext = [0u8; MULTI_SPENT_CHANNEL_PACKET_SIZE as usize];
+            let padding_plaintext_size = MULTI_SPENT_CHANNEL_PACKET_SIZE as usize;
+            let padding_packet_id = &id.0.as_slice()[0..8];
+            let padding_packet_ikm = &id.0.as_slice()[8..32];
+            let padding_packet_key = hkdf_sha_512(
+                &Some(vec![0u8; 64]),
+                padding_packet_ikm,
+                "".as_bytes(),
+                padding_plaintext_size,
+            )?;
+            let padding_packet_ciphertext = xor_bytes(
+                padding_packet_plaintext.as_slice(),
+                padding_packet_key.as_slice(),
+            );
+            let padding_packet_bytes: Vec<u8> =
+                [padding_packet_id.to_vec(), padding_packet_ciphertext].concat();
+            spent_packets.push(padding_packet_bytes);
+        }
+    }
+
+    let mut spent_bloom_filter_bytes: Vec<u8> = vec![];
+    for biguint in spent_bloom_filter.0 {
+        spent_bloom_filter_bytes.extend_from_slice(&biguint.to_be_bytes());
+    }
+    for packet in spent_packets {
+        spent_bloom_filter_bytes.extend(packet.iter());
+    }
+
+    Ok(spent_bloom_filter_bytes)
+}
\ No newline at end of file
diff --git a/src/state.rs b/src/state.rs
index ece815a1..3c44b4fb 100644
--- a/src/state.rs
+++ b/src/state.rs
@@ -4,18 +4,15 @@ use serde::{Deserialize, Serialize};
 use cosmwasm_std::{Addr, StdError, StdResult, Storage};
 use secret_toolkit::serialization::Json;
 use secret_toolkit::storage::{Item, Keymap, Keyset};
-use secret_toolkit_crypto::SHA256_HASH_SIZE;
 
 use crate::msg::ContractStatusLevel;
 
 pub const KEY_CONFIG: &[u8] = b"config";
 pub const KEY_TOTAL_SUPPLY: &[u8] = b"total_supply";
 pub const KEY_CONTRACT_STATUS: &[u8] = b"contract_status";
-pub const KEY_PRNG: &[u8] = b"prng";
 pub const KEY_MINTERS: &[u8] = b"minters";
 pub const KEY_TX_COUNT: &[u8] = b"tx-count";
 
-pub const PREFIX_CONFIG: &[u8] = b"config";
 pub const PREFIX_BALANCES: &[u8] = b"balances";
 pub const PREFIX_ALLOWANCES: &[u8] = b"allowances";
 pub const PREFIX_ALLOWED: &[u8] = b"allowed";
@@ -55,23 +52,10 @@ pub static TOTAL_SUPPLY: Item<u128> = Item::new(KEY_TOTAL_SUPPLY);
 
 pub static CONTRACT_STATUS: Item<ContractStatusLevel, Json> = Item::new(KEY_CONTRACT_STATUS);
 
-pub static PRNG: Item<[u8; SHA256_HASH_SIZE]> = Item::new(KEY_PRNG);
-
 pub static MINTERS: Item<Vec<Addr>> = Item::new(KEY_MINTERS);
 
 pub static TX_COUNT: Item<u64> = Item::new(KEY_TX_COUNT);
 
-pub struct PrngStore {}
-impl PrngStore {
-    pub fn load(store: &dyn Storage) -> StdResult<[u8; SHA256_HASH_SIZE]> {
-        PRNG.load(store).map_err(|_err| StdError::generic_err(""))
-    }
-
-    pub fn save(store: &mut dyn Storage, prng_seed: [u8; SHA256_HASH_SIZE]) -> StdResult<()> {
-        PRNG.save(store, &prng_seed)
-    }
-}
-
 pub struct MintersStore {}
 impl MintersStore {
     pub fn load(store: &dyn Storage) -> StdResult<Vec<Addr>> {
@@ -109,7 +93,7 @@ impl MintersStore {
 
 // To avoid balance guessing attacks based on balance overflow we need to perform safe addition and don't expose overflows to the caller.
 // Assuming that max of u128 is probably an unreachable balance, we want the addition to be bounded the max of u128
-// Currently the logic here is very straight forward yet the existence of the function is mendatory for future changes if needed.
+// Currently the logic here is very straight forward yet the existence of the function is mandatory for future changes if needed.
 pub fn safe_add(balance: &mut u128, amount: u128) -> u128 {
     // Note that new_amount can be equal to base after this operation.
     // Currently we do nothing maybe on other implementations we will have something to add here
@@ -120,95 +104,17 @@ pub fn safe_add(balance: &mut u128, amount: u128) -> u128 {
     *balance - prev_balance
 }
 
-pub static BALANCES: Item<u128> = Item::new(PREFIX_BALANCES);
-pub struct BalancesStore {}
-impl BalancesStore {
-    fn save(store: &mut dyn Storage, account: &Addr, amount: u128) -> StdResult<()> {
-        let balances = BALANCES.add_suffix(account.as_str().as_bytes());
-        balances.save(store, &amount)
-    }
-
-    pub fn load(store: &dyn Storage, account: &Addr) -> u128 {
-        let balances = BALANCES.add_suffix(account.as_str().as_bytes());
-        balances.load(store).unwrap_or_default()
-    }
+// To avoid balance guessing attacks based on balance overflow we need to perform safe addition and don't expose overflows to the caller.
+// Assuming that max of u64 is probably an unreachable balance, we want the addition to be bounded the max of u64
+// Currently the logic here is very straight forward yet the existence of the function is mandatory for future changes if needed.
+pub fn safe_add_u64(balance: &mut u64, amount: u64) -> u64 {
+    // Note that new_amount can be equal to base after this operation.
+    // Currently we do nothing maybe on other implementations we will have something to add here
+    let prev_balance: u64 = *balance;
+    *balance = balance.saturating_add(amount);
 
-    pub fn update_balance(
-        store: &mut dyn Storage,
-        account: &Addr,
-        amount_to_be_updated: u128,
-        should_add: bool,
-        operation_name: &str,
-        decoys: &Option<Vec<Addr>>,
-        account_random_pos: &Option<usize>,
-    ) -> StdResult<()> {
-        match decoys {
-            None => {
-                let mut balance = Self::load(store, account);
-                balance = match should_add {
-                    true => {
-                        safe_add(&mut balance, amount_to_be_updated);
-                        balance
-                    }
-                    false => {
-                        if let Some(balance) = balance.checked_sub(amount_to_be_updated) {
-                            balance
-                        } else {
-                            return Err(StdError::generic_err(format!(
-                                "insufficient funds to {operation_name}: balance={balance}, required={amount_to_be_updated}",
-                            )));
-                        }
-                    }
-                };
-
-                Self::save(store, account, balance)
-            }
-            Some(decoys_vec) => {
-                // It should always be set when decoys_vec is set
-                let account_pos = account_random_pos.unwrap();
-
-                let mut accounts_to_be_written: Vec<&Addr> = vec![];
-
-                let (first_part, second_part) = decoys_vec.split_at(account_pos);
-                accounts_to_be_written.extend(first_part);
-                accounts_to_be_written.push(account);
-                accounts_to_be_written.extend(second_part);
-
-                // In a case where the account is also a decoy somehow
-                let mut was_account_updated = false;
-
-                for acc in accounts_to_be_written.iter() {
-                    // Always load account balance to obfuscate the real account
-                    // Please note that decoys are not always present in the DB. In this case it is ok beacuse load will return 0.
-                    let mut acc_balance = Self::load(store, acc);
-                    let mut new_balance = acc_balance;
-
-                    if *acc == account && !was_account_updated {
-                        was_account_updated = true;
-                        new_balance = match should_add {
-                            true => {
-                                safe_add(&mut acc_balance, amount_to_be_updated);
-                                acc_balance
-                            }
-                            false => {
-                                if let Some(balance) = acc_balance.checked_sub(amount_to_be_updated)
-                                {
-                                    balance
-                                } else {
-                                    return Err(StdError::generic_err(format!(
-                                        "insufficient funds to {operation_name}: balance={acc_balance}, required={amount_to_be_updated}",
-                                    )));
-                                }
-                            }
-                        };
-                    }
-                    Self::save(store, acc, new_balance)?;
-                }
-
-                Ok(())
-            }
-        }
-    }
+    // Won't underflow as the minimal value possible is 0
+    *balance - prev_balance
 }
 
 // Allowances
@@ -315,3 +221,9 @@ impl ReceiverHashStore {
         receiver_hash.save(store, &code_hash)
     }
 }
+
+pub static INTERNAL_SECRET: Item<Vec<u8>> = Item::new(b"internal-secret");
+
+// SNIP-52 channels
+pub static CHANNELS: Keyset<String> = Keyset::new(b"channel-ids");
+
diff --git a/src/strings.rs b/src/strings.rs
new file mode 100644
index 00000000..71a574be
--- /dev/null
+++ b/src/strings.rs
@@ -0,0 +1,2 @@
+pub const TRANSFER_HISTORY_UNSUPPORTED_MSG: &str =
+    "`transfer_history` query is UNSUPPORTED. Use `transaction_history` instead.";
diff --git a/src/transaction_history.rs b/src/transaction_history.rs
index 0b3c3270..8f7c72cd 100644
--- a/src/transaction_history.rs
+++ b/src/transaction_history.rs
@@ -1,33 +1,15 @@
 use schemars::JsonSchema;
 use serde::{Deserialize, Serialize};
 
-use cosmwasm_std::{Addr, Coin, StdError, StdResult, Storage, Uint128};
+use cosmwasm_std::{
+    Addr, Api, BlockInfo, CanonicalAddr, Coin, StdError, StdResult, Storage, Uint128,
+};
 
-use secret_toolkit::storage::AppendStore;
+use secret_toolkit::storage::Item;
 
 use crate::state::TX_COUNT;
 
 const PREFIX_TXS: &[u8] = b"transactions";
-const PREFIX_TRANSFERS: &[u8] = b"transfers";
-
-// Note that id is a globally incrementing counter.
-// Since it's 64 bits long, even at 50 tx/s it would take
-// over 11 billion years for it to rollback. I'm pretty sure
-// we'll have bigger issues by then.
-#[derive(Serialize, Deserialize, JsonSchema, Clone, Debug)]
-pub struct Tx {
-    pub id: u64,
-    pub from: Addr,
-    pub sender: Addr,
-    pub receiver: Addr,
-    pub coins: Coin,
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub memo: Option<String>,
-    // The block time and block height are optional so that the JSON schema
-    // reflects that some SNIP-20 contracts may not include this info.
-    pub block_time: Option<u64>,
-    pub block_height: Option<u64>,
-}
 
 #[derive(Serialize, Deserialize, JsonSchema, Clone, Debug, Eq, PartialEq)]
 #[serde(rename_all = "snake_case")]
@@ -47,23 +29,19 @@ pub enum TxAction {
     },
     Deposit {},
     Redeem {},
-    Decoy {
-        address: Addr,
-    },
 }
 
 // Note that id is a globally incrementing counter.
-// Since it's 64 bits long, even at 50 tx/s it would take
-// over 11 billion years for it to rollback. I'm pretty sure
-// we'll have bigger issues by then.
 #[derive(Serialize, Deserialize, JsonSchema, Clone, Debug, PartialEq)]
 #[serde(rename_all = "snake_case")]
-pub struct ExtendedTx {
+pub struct Tx {
     pub id: u64,
     pub action: TxAction,
     pub coins: Coin,
     #[serde(skip_serializing_if = "Option::is_none")]
     pub memo: Option<String>,
+    // The block time and block height are optional so that the JSON schema
+    // reflects that some SNIP-20 contracts may not include this info.
     pub block_time: u64,
     pub block_height: u64,
 }
@@ -94,81 +72,6 @@ impl From<StoredCoin> for Coin {
     }
 }
 
-/// This type is the stored version of the legacy transfers
-#[derive(Serialize, Deserialize, Clone, Debug)]
-#[serde(rename_all = "snake_case")]
-pub struct StoredLegacyTransfer {
-    id: u64,
-    from: Addr,
-    sender: Addr,
-    receiver: Addr,
-    coins: StoredCoin,
-    memo: Option<String>,
-    block_time: u64,
-    block_height: u64,
-}
-static TRANSFERS: AppendStore<StoredLegacyTransfer> = AppendStore::new(PREFIX_TRANSFERS);
-
-impl StoredLegacyTransfer {
-    pub fn into_humanized(self) -> StdResult<Tx> {
-        let tx = Tx {
-            id: self.id,
-            from: self.from,
-            sender: self.sender,
-            receiver: self.receiver,
-            coins: self.coins.into(),
-            memo: self.memo,
-            block_time: Some(self.block_time),
-            block_height: Some(self.block_height),
-        };
-        Ok(tx)
-    }
-
-    fn append_transfer(
-        store: &mut dyn Storage,
-        tx: &StoredLegacyTransfer,
-        for_address: &Addr,
-    ) -> StdResult<()> {
-        let current_addr_store = TRANSFERS.add_suffix(for_address.as_bytes());
-        current_addr_store.push(store, tx)
-    }
-
-    pub fn get_transfers(
-        storage: &dyn Storage,
-        for_address: Addr,
-        page: u32,
-        page_size: u32,
-        should_filter_decoys: bool,
-    ) -> StdResult<(Vec<Tx>, u64)> {
-        let current_addr_store = TRANSFERS.add_suffix(for_address.as_bytes());
-        let len = current_addr_store.get_len(storage)? as u64;
-        // Take `page_size` txs starting from the latest tx, potentially skipping `page * page_size`
-        // txs from the start.
-        let transfer_iter = current_addr_store
-            .iter(storage)?
-            .rev()
-            .skip((page * page_size) as _)
-            .take(page_size as _);
-
-        // The `and_then` here flattens the `StdResult<StdResult<ExtendedTx>>` to an `StdResult<ExtendedTx>`
-        let transfers: StdResult<Vec<Tx>> = if should_filter_decoys {
-            transfer_iter
-                .filter(|transfer| match transfer {
-                    Err(_) => true,
-                    Ok(t) => t.block_height != 0,
-                })
-                .map(|tx| tx.map(|tx| tx.into_humanized()).and_then(|x| x))
-                .collect()
-        } else {
-            transfer_iter
-                .map(|tx| tx.map(|tx| tx.into_humanized()).and_then(|x| x))
-                .collect()
-        };
-
-        transfers.map(|txs| (txs, len))
-    }
-}
-
 #[derive(Clone, Copy, Debug)]
 #[repr(u8)]
 enum TxCode {
@@ -177,7 +80,6 @@ enum TxCode {
     Burn = 2,
     Deposit = 3,
     Redeem = 4,
-    Decoy = 255,
 }
 
 impl TxCode {
@@ -193,9 +95,9 @@ impl TxCode {
             2 => Ok(Burn),
             3 => Ok(Deposit),
             4 => Ok(Redeem),
-            255 => Ok(Decoy),
             other => Err(StdError::generic_err(format!(
-                "Unexpected Tx code in transaction history: {other} Storage is corrupted.",
+                "Unexpected Tx code in transaction history: {} Storage is corrupted.",
+                other
             ))),
         }
     }
@@ -203,15 +105,15 @@ impl TxCode {
 
 #[derive(Serialize, Deserialize, Clone, Debug)]
 #[serde(rename_all = "snake_case")]
-struct StoredTxAction {
+pub struct StoredTxAction {
     tx_type: u8,
-    address1: Option<Addr>,
-    address2: Option<Addr>,
-    address3: Option<Addr>,
+    address1: Option<CanonicalAddr>,
+    address2: Option<CanonicalAddr>,
+    address3: Option<CanonicalAddr>,
 }
 
 impl StoredTxAction {
-    fn transfer(from: Addr, sender: Addr, recipient: Addr) -> Self {
+    pub fn transfer(from: CanonicalAddr, sender: CanonicalAddr, recipient: CanonicalAddr) -> Self {
         Self {
             tx_type: TxCode::Transfer.to_u8(),
             address1: Some(from),
@@ -219,7 +121,7 @@ impl StoredTxAction {
             address3: Some(recipient),
         }
     }
-    fn mint(minter: Addr, recipient: Addr) -> Self {
+    pub fn mint(minter: CanonicalAddr, recipient: CanonicalAddr) -> Self {
         Self {
             tx_type: TxCode::Mint.to_u8(),
             address1: Some(minter),
@@ -227,7 +129,7 @@ impl StoredTxAction {
             address3: None,
         }
     }
-    fn burn(owner: Addr, burner: Addr) -> Self {
+    pub fn burn(owner: CanonicalAddr, burner: CanonicalAddr) -> Self {
         Self {
             tx_type: TxCode::Burn.to_u8(),
             address1: Some(burner),
@@ -235,7 +137,7 @@ impl StoredTxAction {
             address3: None,
         }
     }
-    fn deposit() -> Self {
+    pub fn deposit() -> Self {
         Self {
             tx_type: TxCode::Deposit.to_u8(),
             address1: None,
@@ -243,7 +145,7 @@ impl StoredTxAction {
             address3: None,
         }
     }
-    fn redeem() -> Self {
+    pub fn redeem() -> Self {
         Self {
             tx_type: TxCode::Redeem.to_u8(),
             address1: None,
@@ -251,16 +153,8 @@ impl StoredTxAction {
             address3: None,
         }
     }
-    fn decoy(recipient: &Addr) -> Self {
-        Self {
-            tx_type: TxCode::Decoy.to_u8(),
-            address1: Some(recipient.clone()),
-            address2: None,
-            address3: None,
-        }
-    }
 
-    fn into_tx_action(self) -> StdResult<TxAction> {
+    pub fn into_tx_action(self, api: &dyn Api) -> StdResult<TxAction> {
         let transfer_addr_err = || {
             StdError::generic_err(
                 "Missing address in stored Transfer transaction. Storage is corrupt",
@@ -272,9 +166,6 @@ impl StoredTxAction {
         let burn_addr_err = || {
             StdError::generic_err("Missing address in stored Burn transaction. Storage is corrupt")
         };
-        let decoy_addr_err = || {
-            StdError::generic_err("Missing address in stored decoy transaction. Storage is corrupt")
-        };
 
         // In all of these, we ignore fields that we don't expect to find populated
         let action = match TxCode::from_u8(self.tx_type)? {
@@ -283,39 +174,42 @@ impl StoredTxAction {
                 let sender = self.address2.ok_or_else(transfer_addr_err)?;
                 let recipient = self.address3.ok_or_else(transfer_addr_err)?;
                 TxAction::Transfer {
-                    from,
-                    sender,
-                    recipient,
+                    from: api.addr_humanize(&from)?,
+                    sender: api.addr_humanize(&sender)?,
+                    recipient: api.addr_humanize(&recipient)?,
                 }
             }
             TxCode::Mint => {
                 let minter = self.address1.ok_or_else(mint_addr_err)?;
                 let recipient = self.address2.ok_or_else(mint_addr_err)?;
-                TxAction::Mint { minter, recipient }
+                TxAction::Mint {
+                    minter: api.addr_humanize(&minter)?,
+                    recipient: api.addr_humanize(&recipient)?,
+                }
             }
             TxCode::Burn => {
                 let burner = self.address1.ok_or_else(burn_addr_err)?;
                 let owner = self.address2.ok_or_else(burn_addr_err)?;
-                TxAction::Burn { burner, owner }
+                TxAction::Burn {
+                    burner: api.addr_humanize(&burner)?,
+                    owner: api.addr_humanize(&owner)?,
+                }
             }
             TxCode::Deposit => TxAction::Deposit {},
             TxCode::Redeem => TxAction::Redeem {},
-            TxCode::Decoy => {
-                let address = self.address1.ok_or_else(decoy_addr_err)?;
-                TxAction::Decoy { address }
-            }
         };
 
         Ok(action)
     }
 }
 
-static TRANSACTIONS: AppendStore<StoredExtendedTx> = AppendStore::new(PREFIX_TXS);
+// use with add_suffix tx id (u64 to_be_bytes)
+// does not need to be an AppendStore because we never need to iterate over global list of txs
+pub static TRANSACTIONS: Item<StoredTx> = Item::new(PREFIX_TXS);
 
 #[derive(Serialize, Deserialize, Clone, Debug)]
 #[serde(rename_all = "snake_case")]
-pub struct StoredExtendedTx {
-    id: u64,
+pub struct StoredTx {
     action: StoredTxAction,
     coins: StoredCoin,
     memo: Option<String>,
@@ -323,309 +217,105 @@ pub struct StoredExtendedTx {
     block_height: u64,
 }
 
-impl StoredExtendedTx {
-    fn new(
-        id: u64,
-        action: StoredTxAction,
-        coins: Coin,
-        memo: Option<String>,
-        block: &cosmwasm_std::BlockInfo,
-    ) -> Self {
-        Self {
+impl StoredTx {
+    pub fn into_humanized(self, api: &dyn Api, id: u64) -> StdResult<Tx> {
+        Ok(Tx {
             id,
-            action,
-            coins: coins.into(),
-            memo,
-            block_time: block.time.seconds(),
-            block_height: block.height,
-        }
-    }
-
-    fn into_humanized(self) -> StdResult<ExtendedTx> {
-        Ok(ExtendedTx {
-            id: self.id,
-            action: self.action.into_tx_action()?,
+            action: self.action.into_tx_action(api)?,
             coins: self.coins.into(),
             memo: self.memo,
             block_time: self.block_time,
             block_height: self.block_height,
         })
     }
-
-    fn from_stored_legacy_transfer(transfer: StoredLegacyTransfer) -> Self {
-        let action = StoredTxAction::transfer(transfer.from, transfer.sender, transfer.receiver);
-        Self {
-            id: transfer.id,
-            action,
-            coins: transfer.coins,
-            memo: transfer.memo,
-            block_time: transfer.block_time,
-            block_height: transfer.block_height,
-        }
-    }
-
-    fn append_tx(
-        store: &mut dyn Storage,
-        tx: &StoredExtendedTx,
-        for_address: &Addr,
-    ) -> StdResult<()> {
-        let current_addr_store = TRANSACTIONS.add_suffix(for_address.as_bytes());
-        current_addr_store.push(store, tx)
-    }
-
-    pub fn get_txs(
-        storage: &dyn Storage,
-        for_address: Addr,
-        page: u32,
-        page_size: u32,
-        should_filter_decoys: bool,
-    ) -> StdResult<(Vec<ExtendedTx>, u64)> {
-        let current_addr_store = TRANSACTIONS.add_suffix(for_address.as_bytes());
-        let len = current_addr_store.get_len(storage)? as u64;
-
-        // Take `page_size` txs starting from the latest tx, potentially skipping `page * page_size`
-        // txs from the start.
-        let tx_iter = current_addr_store
-            .iter(storage)?
-            .rev()
-            .skip((page * page_size) as _)
-            .take(page_size as _);
-
-        // The `and_then` here flattens the `StdResult<StdResult<ExtendedTx>>` to an `StdResult<ExtendedTx>`
-        let txs: StdResult<Vec<ExtendedTx>> = if should_filter_decoys {
-            tx_iter
-                .filter(|tx| match tx {
-                    Err(_) => true,
-                    Ok(t) => t.action.tx_type != TxCode::Decoy.to_u8(),
-                })
-                .map(|tx| tx.map(|tx| tx.into_humanized()).and_then(|x| x))
-                .collect()
-        } else {
-            tx_iter
-                .map(|tx| tx.map(|tx| tx.into_humanized()).and_then(|x| x))
-                .collect()
-        };
-
-        txs.map(|txs| (txs, len))
-    }
 }
 
 // Storage functions:
 
-fn increment_tx_count(store: &mut dyn Storage) -> StdResult<u64> {
-    let id = TX_COUNT.load(store).unwrap_or_default() + 1;
-    TX_COUNT.save(store, &id)?;
-    Ok(id)
-}
-
-fn store_tx_with_decoys(
-    store: &mut dyn Storage,
-    tx: &StoredExtendedTx,
-    for_address: &Addr,
-    block: &cosmwasm_std::BlockInfo,
-    decoys: &Option<Vec<Addr>>,
-    account_random_pos: &Option<usize>,
-) -> StdResult<()> {
-    let mut index_changer: Option<usize> = None;
-    match decoys {
-        None => StoredExtendedTx::append_tx(store, tx, for_address)?,
-        Some(user_decoys) => {
-            // It should always be set when decoys_vec is set
-            let account_pos = account_random_pos.unwrap();
-
-            for i in 0..user_decoys.len() + 1 {
-                if i == account_pos {
-                    StoredExtendedTx::append_tx(store, tx, for_address)?;
-                    index_changer = Some(1);
-                    continue;
-                }
-
-                let index = i - index_changer.unwrap_or_default();
-                let decoy_action = StoredTxAction::decoy(&user_decoys[index]);
-                let decoy_tx = StoredExtendedTx::new(
-                    tx.id,
-                    decoy_action,
-                    tx.coins.clone().into(),
-                    tx.memo.clone(),
-                    block,
-                );
-                StoredExtendedTx::append_tx(store, &decoy_tx, &user_decoys[index])?;
-            }
-        }
-    }
-
-    Ok(())
-}
-
-fn store_transfer_tx_with_decoys(
+pub fn append_new_stored_tx(
     store: &mut dyn Storage,
-    transfer: StoredLegacyTransfer,
-    receiver: &Addr,
-    decoys: &Option<Vec<Addr>>,
-    account_random_pos: &Option<usize>,
-) -> StdResult<()> {
-    let mut index_changer: Option<usize> = None;
-    match decoys {
-        None => StoredLegacyTransfer::append_transfer(store, &transfer, receiver)?,
-        Some(user_decoys) => {
-            // It should always be set when decoys_vec is set
-            let account_pos = account_random_pos.unwrap();
-
-            for i in 0..user_decoys.len() + 1 {
-                if i == account_pos {
-                    StoredLegacyTransfer::append_transfer(store, &transfer, receiver)?;
-                    index_changer = Some(1);
-                    continue;
-                }
-
-                let index = i - index_changer.unwrap_or_default();
-                let decoy_transfer = StoredLegacyTransfer {
-                    id: transfer.id,
-                    from: transfer.from.clone(),
-                    sender: transfer.sender.clone(),
-                    receiver: user_decoys[index].clone(),
-                    coins: transfer.coins.clone(),
-                    memo: transfer.memo.clone(),
-                    block_time: transfer.block_time,
-                    block_height: 0, // To identify the decoy
-                };
-                StoredLegacyTransfer::append_transfer(store, &decoy_transfer, &user_decoys[index])?;
-            }
-        }
-    }
-
-    Ok(())
-}
-
-#[allow(clippy::too_many_arguments)] // We just need them
-pub fn store_transfer(
-    store: &mut dyn Storage,
-    owner: &Addr,
-    sender: &Addr,
-    receiver: &Addr,
-    amount: Uint128,
+    action: &StoredTxAction,
+    amount: u128,
     denom: String,
     memo: Option<String>,
-    block: &cosmwasm_std::BlockInfo,
-    decoys: &Option<Vec<Addr>>,
-    account_random_pos: &Option<usize>,
-) -> StdResult<()> {
-    let id = increment_tx_count(store)?;
-    let coins = Coin { denom, amount };
-    let transfer = StoredLegacyTransfer {
-        id,
-        from: owner.clone(),
-        sender: sender.clone(),
-        receiver: receiver.clone(),
-        coins: coins.into(),
+    block: &BlockInfo,
+) -> StdResult<u64> {
+    // tx ids are serialized starting at 1
+    let serial_id = TX_COUNT.load(store).unwrap_or_default() + 1;
+    let coins = StoredCoin { denom, amount };
+    let stored_tx = StoredTx {
+        action: action.clone(),
+        coins,
         memo,
         block_time: block.time.seconds(),
         block_height: block.height,
     };
-    let tx = StoredExtendedTx::from_stored_legacy_transfer(transfer.clone());
 
-    // Write to the owners history if it's different from the other two addresses
-    if owner != sender && owner != receiver {
-        // cosmwasm_std::debug_print("saving transaction history for owner");
-        StoredExtendedTx::append_tx(store, &tx, owner)?;
-        StoredLegacyTransfer::append_transfer(store, &transfer, owner)?;
-    }
-    // Write to the sender's history if it's different from the receiver
-    if sender != receiver {
-        // cosmwasm_std::debug_print("saving transaction history for sender");
-        StoredExtendedTx::append_tx(store, &tx, sender)?;
-        StoredLegacyTransfer::append_transfer(store, &transfer, sender)?;
-    }
-
-    // Always write to the recipient's history
-    // cosmwasm_std::debug_print("saving transaction history for receiver");
-    store_tx_with_decoys(store, &tx, receiver, block, decoys, account_random_pos)?;
-    store_transfer_tx_with_decoys(store, transfer, receiver, decoys, account_random_pos)?;
-
-    Ok(())
+    TRANSACTIONS
+        .add_suffix(&serial_id.to_be_bytes())
+        .save(store, &stored_tx)?;
+    TX_COUNT.save(store, &(serial_id))?;
+    Ok(serial_id)
 }
 
 #[allow(clippy::too_many_arguments)] // We just need them
-pub fn store_mint(
+pub fn store_transfer_action(
     store: &mut dyn Storage,
-    minter: Addr,
-    recipient: Addr,
-    amount: Uint128,
+    owner: &CanonicalAddr,
+    sender: &CanonicalAddr,
+    receiver: &CanonicalAddr,
+    amount: u128,
+    denom: String,
+    memo: Option<String>,
+    block: &BlockInfo,
+) -> StdResult<u64> {
+    let action = StoredTxAction::transfer(owner.clone(), sender.clone(), receiver.clone());
+    append_new_stored_tx(store, &action, amount, denom, memo, block)
+}
+
+pub fn store_mint_action(
+    store: &mut dyn Storage,
+    minter: &CanonicalAddr,
+    recipient: &CanonicalAddr,
+    amount: u128,
     denom: String,
     memo: Option<String>,
     block: &cosmwasm_std::BlockInfo,
-    decoys: &Option<Vec<Addr>>,
-    account_random_pos: &Option<usize>,
-) -> StdResult<()> {
-    let id = increment_tx_count(store)?;
-    let coins = Coin { denom, amount };
+) -> StdResult<u64> {
     let action = StoredTxAction::mint(minter.clone(), recipient.clone());
-    let tx = StoredExtendedTx::new(id, action, coins, memo, block);
-
-    if minter != recipient {
-        store_tx_with_decoys(store, &tx, &recipient, block, decoys, account_random_pos)?;
-    }
-
-    StoredExtendedTx::append_tx(store, &tx, &minter)?;
-
-    Ok(())
+    append_new_stored_tx(store, &action, amount, denom, memo, block)
 }
 
 #[allow(clippy::too_many_arguments)]
-pub fn store_burn(
+pub fn store_burn_action(
     store: &mut dyn Storage,
-    owner: Addr,
-    burner: Addr,
-    amount: Uint128,
+    owner: CanonicalAddr,
+    burner: CanonicalAddr,
+    amount: u128,
     denom: String,
     memo: Option<String>,
     block: &cosmwasm_std::BlockInfo,
-    decoys: &Option<Vec<Addr>>,
-    account_random_pos: &Option<usize>,
-) -> StdResult<()> {
-    let id = increment_tx_count(store)?;
-    let coins = Coin { denom, amount };
-    let action = StoredTxAction::burn(owner.clone(), burner.clone());
-    let tx = StoredExtendedTx::new(id, action, coins, memo, block);
-
-    if burner != owner {
-        store_tx_with_decoys(store, &tx, &owner, block, decoys, account_random_pos)?;
-    }
-
-    StoredExtendedTx::append_tx(store, &tx, &burner)?;
-    Ok(())
+) -> StdResult<u64> {
+    let action = StoredTxAction::burn(owner, burner);
+    append_new_stored_tx(store, &action, amount, denom, memo, block)
 }
 
-pub fn store_deposit(
+pub fn store_deposit_action(
     store: &mut dyn Storage,
-    recipient: &Addr,
-    amount: Uint128,
+    amount: u128,
     denom: String,
     block: &cosmwasm_std::BlockInfo,
-    decoys: &Option<Vec<Addr>>,
-    account_random_pos: &Option<usize>,
-) -> StdResult<()> {
-    let id = increment_tx_count(store)?;
-    let coins = Coin { denom, amount };
+) -> StdResult<u64> {
     let action = StoredTxAction::deposit();
-    let tx = StoredExtendedTx::new(id, action, coins, None, block);
-
-    store_tx_with_decoys(store, &tx, recipient, block, decoys, account_random_pos)
+    append_new_stored_tx(store, &action, amount, denom, None, block)
 }
 
-pub fn store_redeem(
+pub fn store_redeem_action(
     store: &mut dyn Storage,
-    redeemer: &Addr,
-    amount: Uint128,
+    amount: u128,
     denom: String,
     block: &cosmwasm_std::BlockInfo,
-    decoys: &Option<Vec<Addr>>,
-    account_random_pos: &Option<usize>,
-) -> StdResult<()> {
-    let id = increment_tx_count(store)?;
-    let coins = Coin { denom, amount };
+) -> StdResult<u64> {
     let action = StoredTxAction::redeem();
-    let tx = StoredExtendedTx::new(id, action, coins, None, block);
-
-    store_tx_with_decoys(store, &tx, redeemer, block, decoys, account_random_pos)
+    append_new_stored_tx(store, &action, amount, denom, None, block)
 }
diff --git a/tests/dwb/.env.example b/tests/dwb/.env.example
new file mode 100644
index 00000000..0a6b74d9
--- /dev/null
+++ b/tests/dwb/.env.example
@@ -0,0 +1,4 @@
+SECRET_LCD=http://localhost:1317
+SECRET_RPC=http://localhost:26657
+SECRET_CHAIN=secretdev-1
+ENABLE_EVAPORATION_TESTS=1
diff --git a/tests/dwb/.eslintrc.cjs b/tests/dwb/.eslintrc.cjs
new file mode 100644
index 00000000..b045f013
--- /dev/null
+++ b/tests/dwb/.eslintrc.cjs
@@ -0,0 +1,14 @@
+
+module.exports = {
+	extends: '@blake.regalia/eslint-config-elite',
+	parserOptions: {
+		ecmaVersion: 2022,
+		sourceType: 'module',
+		tsconfigRootDir: __dirname,
+		project: 'tsconfig.json',
+	},
+	rules: {
+		'no-console': 'off',
+		'@typescript-eslint/naming-convention': 'off',
+	},
+};
diff --git a/tests/dwb/README.md b/tests/dwb/README.md
new file mode 100644
index 00000000..42d1eb57
--- /dev/null
+++ b/tests/dwb/README.md
@@ -0,0 +1,29 @@
+# DWB Integration Test Suite
+
+## Requirements
+The test suite is run using [bun](https://bun.sh/).
+
+## Setup
+From this directory:
+```sh
+bun install
+cp .env.example .env
+```
+
+Edit the `.env` file (or leave as is) to configure the network to either your localsecret or pulsar-3.
+
+## Run
+```sh
+bun run test  ## compiles the contract for integration tests and runs the main test suite
+```
+
+
+## Debugging
+
+In case there is a silent failure, it may help to run the suite using node.js instead of bun. You can compile it and run it and debug it interactively with the following commands:
+```sh
+bun run build && node --env-file=.env --inspect-brk dist/main.js
+```
+
+The console output should look something like this:
+![Integration test preview](https://github.com/user-attachments/assets/be2fedda-550c-45e6-aee4-5af45a84d5b8)
diff --git a/tests/dwb/bun.lockb b/tests/dwb/bun.lockb
new file mode 100755
index 00000000..48417527
Binary files /dev/null and b/tests/dwb/bun.lockb differ
diff --git a/tests/dwb/package.json b/tests/dwb/package.json
new file mode 100644
index 00000000..2dc0e942
--- /dev/null
+++ b/tests/dwb/package.json
@@ -0,0 +1,29 @@
+{
+  "private": "true",
+  "type": "module",
+  "scripts": {
+    "build": "tsc && tsc-esm-fix --tsconfig tsconfig.tsc-esm-fix.json --target=dist",
+    "make": "pushd ../../ && make compile-integration && popd",
+    "test": "bun run make && NODE_ENV=development bun run src/main.ts"
+  },
+  "devDependencies": {
+    "@blake.regalia/belt": "^0.38.1",
+    "@blake.regalia/eslint-config-elite": "^0.4.4",
+    "@blake.regalia/tsconfig": "^0.2.0",
+    "@solar-republic/types": "^0.2.12",
+    "@types/chai": "^4.3.17",
+    "@types/node": "^22.1.0",
+    "chai": "^5.1.1",
+    "chai-bites": "^0.2.0",
+    "eslint": " 8",
+    "tsc-esm-fix": "^3.0.1",
+    "typescript": "^5.5.4"
+  },
+  "dependencies": {
+    "@solar-republic/contractor": "^0.8.17",
+    "@solar-republic/cosmos-grpc": "^0.17.1",
+    "@solar-republic/crypto": "^0.2.14",
+    "@solar-republic/neutrino": "^1.5.3",
+    "bignumber.js": "^9.1.2"
+  }
+}
diff --git a/tests/dwb/src/constants.ts b/tests/dwb/src/constants.ts
new file mode 100644
index 00000000..120f6046
--- /dev/null
+++ b/tests/dwb/src/constants.ts
@@ -0,0 +1,28 @@
+import type {TrustedContextUrl} from '@solar-republic/types';
+
+import {base64_to_bytes} from '@blake.regalia/belt';
+import {Wallet} from '@solar-republic/neutrino';
+
+export const P_SECRET_LCD = (process.env['SECRET_LCD'] || 'http://localhost:1317') as TrustedContextUrl;
+export const P_SECRET_RPC = (process.env['SECRET_RPC'] || 'http://localhost:26656') as TrustedContextUrl;
+export const SI_SECRET_CHAIN = (process.env['SECRET_CHAIN'] || 'secretdev-1') as TrustedContextUrl;
+export const B_TEST_EVAPORATION = !!parseInt(process.env['ENABLE_EVAPORATION_TESTS'] || '0');
+
+export const X_GAS_PRICE = 0.1;
+
+// import pre-configured wallets
+export const [k_wallet_a, k_wallet_b, k_wallet_c, k_wallet_d] = await Promise.all([
+	'8Ke2frmnGdVPipv7+xh9jClrl5EaBb9cowSUgj5GvrY=',
+	'buqil+tLeeW7VLuugvOdTmkP3+tUwlCoScPZxeteBPE=',
+	'UFrCdmofR9iChp6Eg7kE5O3wT+jsOXwJPWwB6kSeuhE=',
+	'MM/1ZSbT5RF1BnaY6ui/i7yEN0mukGzvXUv+jOyjD0E=',
+].map(sb64_sk => Wallet(base64_to_bytes(sb64_sk), SI_SECRET_CHAIN, P_SECRET_LCD, P_SECRET_RPC, 'secret')));
+
+export const H_ADDRS = {
+	[k_wallet_a.addr]: 'Alice',
+	[k_wallet_b.addr]: 'Bob',
+	[k_wallet_c.addr]: 'Carol',
+	[k_wallet_d.addr]: 'David',
+};
+
+export const N_DECIMALS = 6;
diff --git a/tests/dwb/src/contract.ts b/tests/dwb/src/contract.ts
new file mode 100644
index 00000000..a9aa3e74
--- /dev/null
+++ b/tests/dwb/src/contract.ts
@@ -0,0 +1,109 @@
+import type {JsonObject} from '@blake.regalia/belt';
+import type {EncodedGoogleProtobufAny} from '@solar-republic/cosmos-grpc/google/protobuf/any';
+import type {TxResultTuple, Wallet, WeakSecretAccAddr} from '@solar-republic/neutrino';
+import type {CwHexLower, WeakUintStr} from '@solar-republic/types';
+
+import {promisify} from 'node:util';
+import {gunzip} from 'node:zlib';
+
+import {base64_to_bytes, bytes_to_hex, bytes_to_text, cast, sha256} from '@blake.regalia/belt';
+import {queryCosmosBankBalance} from '@solar-republic/cosmos-grpc/cosmos/bank/v1beta1/query';
+import {encodeGoogleProtobufAny} from '@solar-republic/cosmos-grpc/google/protobuf/any';
+import {SI_MESSAGE_TYPE_SECRET_COMPUTE_MSG_STORE_CODE, SI_MESSAGE_TYPE_SECRET_COMPUTE_MSG_INSTANTIATE_CONTRACT, encodeSecretComputeMsgStoreCode, encodeSecretComputeMsgInstantiateContract} from '@solar-republic/cosmos-grpc/secret/compute/v1beta1/msg';
+import {querySecretComputeCodeHashByCodeId, querySecretComputeCodes} from '@solar-republic/cosmos-grpc/secret/compute/v1beta1/query';
+import {destructSecretRegistrationKey} from '@solar-republic/cosmos-grpc/secret/registration/v1beta1/msg';
+import {querySecretRegistrationTxKey} from '@solar-republic/cosmos-grpc/secret/registration/v1beta1/query';
+import {SecretWasm, TendermintEventFilter, TendermintWs, broadcast_result, create_and_sign_tx_direct, exec_fees} from '@solar-republic/neutrino';
+
+import {X_GAS_PRICE, P_SECRET_LCD, P_SECRET_RPC} from './constants';
+
+const k_tef = await TendermintEventFilter(P_SECRET_RPC);
+
+export async function exec(k_wallet: Wallet, atu8_msg: EncodedGoogleProtobufAny, xg_gas_limit: bigint): Promise<TxResultTuple> {
+	const [atu8_raw, atu8_signdoc, si_txn] = await create_and_sign_tx_direct(
+		k_wallet,
+		[atu8_msg],
+		exec_fees(xg_gas_limit, X_GAS_PRICE, 'uscrt'),
+		xg_gas_limit
+	);
+
+	return await broadcast_result(k_wallet, atu8_raw, si_txn, k_tef);
+}
+
+export async function upload_code(k_wallet: Wallet, atu8_wasm: Uint8Array): Promise<WeakUintStr> {
+	let atu8_bytecode = atu8_wasm;
+
+	// gzip-encoded; decompress
+	if(0x1f === atu8_wasm[0] && 0x8b === atu8_wasm[1]) {
+		atu8_bytecode = await promisify(gunzip)(atu8_wasm);
+	}
+
+	// hash
+	const atu8_hash = await sha256(atu8_bytecode);
+	const sb16_hash = cast<CwHexLower>(bytes_to_hex(atu8_hash));
+
+	// fetch all uploaded codes
+	const [,, g_codes] = await querySecretComputeCodes(P_SECRET_LCD);
+
+	// already uploaded
+	const g_existing = g_codes?.code_infos?.find(g => g.code_hash! === sb16_hash);
+	if(g_existing) {
+		console.info(`Found code ID ${g_existing.code_id} already uploaded to network`);
+
+		return g_existing.code_id as WeakUintStr;
+	}
+
+	// upload
+	const [xc_code, sx_res, g_meta, atu8_data, h_events] = await exec(k_wallet, encodeGoogleProtobufAny(
+		SI_MESSAGE_TYPE_SECRET_COMPUTE_MSG_STORE_CODE,
+		encodeSecretComputeMsgStoreCode(
+			k_wallet.addr,
+			atu8_bytecode
+		)
+	), 30_000000n);
+
+	if(xc_code) throw Error(sx_res);
+
+	return h_events!['message.code_id'][0] as WeakUintStr;
+}
+
+export async function instantiate_contract(k_wallet: Wallet, sg_code_id: WeakUintStr, h_init_msg: JsonObject): Promise<WeakSecretAccAddr> {
+	const [,, g_reg] = await querySecretRegistrationTxKey(P_SECRET_LCD);
+	const [atu8_cons_pk] = destructSecretRegistrationKey(g_reg!);
+	const k_wasm = SecretWasm(atu8_cons_pk!);
+	const [,, g_hash] = await querySecretComputeCodeHashByCodeId(P_SECRET_LCD, sg_code_id);
+
+	// @ts-expect-error imported types versioning
+	const atu8_body = await k_wasm.encodeMsg(g_hash!.code_hash, h_init_msg);
+
+	const [xc_code, sx_res, g_meta, atu8_data, h_events] = await exec(k_wallet, encodeGoogleProtobufAny(
+		SI_MESSAGE_TYPE_SECRET_COMPUTE_MSG_INSTANTIATE_CONTRACT,
+		encodeSecretComputeMsgInstantiateContract(
+			k_wallet.addr,
+			null,
+			sg_code_id,
+			h_init_msg['name'] as string,
+			atu8_body
+		)
+	), 10_000_000n);
+
+	if(xc_code) {
+		const s_error = g_meta?.log ?? sx_res;
+
+		// encrypted error message
+		const m_response = /(\d+):(?: \w+:)*? encrypted: (.+?): (.+?) contract/.exec(s_error);
+		if(m_response) {
+			// destructure match
+			const [, s_index, sb64_encrypted, si_action] = m_response;
+
+			// decrypt ciphertext
+			const atu8_plaintext = await k_wasm.decrypt(base64_to_bytes(sb64_encrypted), atu8_body.slice(0, 32));
+
+			throw Error(bytes_to_text(atu8_plaintext));
+		}
+
+		throw Error(sx_res);
+	}
+
+	return h_events!['message.contract_address'][0] as WeakSecretAccAddr;
+}
diff --git a/tests/dwb/src/dwb-entry.ts b/tests/dwb/src/dwb-entry.ts
new file mode 100644
index 00000000..32372535
--- /dev/null
+++ b/tests/dwb/src/dwb-entry.ts
@@ -0,0 +1,76 @@
+import type {Nilable} from '@blake.regalia/belt';
+import type {CwSecretAccAddr} from '@solar-republic/neutrino';
+
+import {bytes_to_biguint_be, bytes_to_hex} from '@blake.regalia/belt';
+import {bech32_encode} from '@solar-republic/crypto';
+import {BigNumber} from 'bignumber.js';
+
+import {H_ADDRS} from './constants';
+import {SX_ANSI_BLUE, SX_ANSI_DIM_ON, SX_ANSI_GREEN, SX_ANSI_RESET, SX_ANSI_YELLOW} from './helper';
+
+const NB_ADDR = 20;
+const NB_AMOUNT = 8;
+const NB_HEAD = 5;
+const NB_LEN = 2;
+
+const NB_ENTRY = NB_ADDR+NB_AMOUNT+NB_HEAD+NB_LEN;
+
+export class DwbEntry {
+	constructor(protected _atu8_raw: Uint8Array) {
+		if(this._atu8_raw.byteLength !== NB_ENTRY) {
+			throw Error(`DWB entry was not exactly ${NB_ENTRY} bytes in length`);
+		}
+	}
+
+	get raw(): Uint8Array {
+		return this._atu8_raw;
+	}
+
+	get isNil(): boolean {
+		return /^0+$/.test(bytes_to_hex(this._atu8_raw));
+	}
+
+	get address(): CwSecretAccAddr {
+		return bech32_encode('secret', this._atu8_raw.subarray(0, NB_ADDR));
+	}
+
+	get amount(): bigint {
+		return bytes_to_biguint_be(this._atu8_raw.subarray(NB_ADDR, NB_ADDR+NB_AMOUNT));
+	}
+
+	get head(): bigint {
+		return bytes_to_biguint_be(this._atu8_raw.subarray(NB_ADDR+NB_AMOUNT, NB_ADDR+NB_AMOUNT+NB_HEAD));
+	}
+
+	get listlen(): bigint {
+		return bytes_to_biguint_be(this._atu8_raw.subarray(NB_ADDR+NB_AMOUNT+NB_HEAD, NB_ADDR+NB_AMOUNT+NB_HEAD+NB_LEN));
+	}
+
+	toString(k_prev?: Nilable<DwbEntry>): string {
+		let s_alias = H_ADDRS[this.address] || '';
+		s_alias += s_alias? ` (${this.address.slice(0, 12)+'...'+this.address.slice(-5)})`: this.address;
+		s_alias = s_alias.padEnd(45, ' ');
+
+		let s_amount = BigNumber(this.amount+'').shiftedBy(-6).toFixed(6).padStart(12, ' ');
+
+		if(k_prev) {
+			if(this.address !== k_prev.address) {
+				const sx_color = this.amount? SX_ANSI_GREEN: SX_ANSI_YELLOW;
+
+				s_alias = `${sx_color}${s_alias}${SX_ANSI_RESET}`;
+				s_amount = `${sx_color}${s_amount}${SX_ANSI_RESET}`;
+			}
+			else if(this.amount !== k_prev.amount) {
+				s_alias = `${SX_ANSI_BLUE}${s_alias}${SX_ANSI_RESET}`;
+				s_amount = `${SX_ANSI_BLUE}${s_amount}${SX_ANSI_RESET}`;
+			}
+		}
+
+		return [
+			s_alias,
+			s_amount,
+			(this.head+'').padStart(4, ' '),
+			(this.listlen+'').padStart(4, ' '),
+		].map(s => this.amount? s: `${SX_ANSI_DIM_ON}${s}${SX_ANSI_RESET}`).join(' │ ');
+	}
+}
diff --git a/tests/dwb/src/dwb.ts b/tests/dwb/src/dwb.ts
new file mode 100644
index 00000000..bd1e112c
--- /dev/null
+++ b/tests/dwb/src/dwb.ts
@@ -0,0 +1,248 @@
+import type {SecretApp, WeakSecretAccAddr} from '@solar-republic/neutrino';
+
+import {bytes, parse_json} from '@blake.regalia/belt';
+import * as chai from 'chai';
+const {expect} = chai;
+
+
+import {DwbEntry} from './dwb-entry';
+import {SX_ANSI_DIM_ON, SX_ANSI_RESET, fail} from './helper';
+
+export type DwbRequirements = {
+	showDelta?: boolean;
+	shouldNotContainEntriesFor?: WeakSecretAccAddr[];
+};
+
+const R_ENTRY = /\s*DelayedWriteBufferEntry\(([^]*?)\)\s*,?/y;
+
+export function parse_dwb_dump(sx_dump: string) {
+	const [, sx_contents] = /DelayedWriteBuffer\s*\{\s*([^]*?)\s*\}\s*$/.exec(sx_dump)!;
+	const [, sg_empty, sx_entries] = /^\s*empty_space_counter:\s*(\d+),\s*entries:\s*\[([^]*)\]\s*$/.exec(sx_contents)!;
+
+	const a_entries: Uint8Array[] = [];
+	for(;;) {
+		const m_entry = R_ENTRY.exec(sx_entries)!;
+		if(!m_entry) break;
+
+		a_entries.push(bytes(parse_json<number[]>(m_entry[1])));
+	}
+
+	return {
+		empty_space_counter: parse_json(sg_empty),
+		entries: a_entries,
+	};
+}
+
+export class DwbValidator {
+	protected _a_entries_prev: DwbEntry[] = [];
+	protected _a_entries: DwbEntry[] = [];
+	protected _n_empty = 0;
+
+	constructor(protected _k_app: SecretApp) {}
+
+	get entries(): DwbEntry[] {
+		return this._a_entries.slice();
+	}
+
+	get previous(): DwbEntry[] {
+		return this._a_entries_prev.slice();
+	}
+
+	get empty(): number {
+		return this._n_empty;
+	}
+
+	async sync() {
+		// cache previous state
+		this._a_entries_prev = this._a_entries.slice();
+
+		// dump dwb contents
+		const [g_dwb_res] = await this._k_app.query('dwb', {});
+
+		// parse
+		const {
+			empty_space_counter: sg_empty,
+			entries: a_entries,
+		} = parse_dwb_dump((g_dwb_res as {dwb: string}).dwb);
+
+		// update cached entries
+		this._a_entries.length = 0;
+		this._a_entries.push(...a_entries.map(atu8 => new DwbEntry(atu8)));
+
+		// save empty spaces counter
+		this._n_empty = parseFloat(sg_empty as string);
+
+		return this._a_entries;
+	}
+
+	async check(gc_check?: DwbRequirements) {
+		const a_prev = this._a_entries_prev;
+		const a_entries = this._a_entries;
+
+		// should exclude entry for given addresses
+		const a_exclude = gc_check?.shouldNotContainEntriesFor;
+		if(a_exclude?.length) {
+			for(const sa_exclude of a_exclude) {
+				const i_found = a_entries.findIndex(k => sa_exclude === k.address);
+
+				if(i_found > -1) {
+					fail(`Expected buffer to NOT contain an entry for ${sa_exclude} but found it at position ${i_found}`);
+				}
+			}
+		}
+
+		// count empty spaces
+		let c_empty_actual = 0;
+		for(let i_space=a_entries.length-1; i_space>0; i_space--) {
+			if(!a_entries[i_space].amount) {
+				c_empty_actual += 1;
+			}
+			else {
+				break;
+			}
+		}
+
+		// find changes
+		for(let i_space=0; i_space<a_entries.length; i_space++) {
+			const k_prev = a_prev[i_space];
+			const k_curr = a_entries[i_space];
+
+			// same address
+			if(k_prev.address === k_curr.address) {
+				// amount changed
+				if(k_prev.amount && k_curr.amount && k_prev.amount !== k_curr.amount) {
+					// expect it to only ever increase
+					if(k_curr.amount < k_prev.amount) {
+						fail(`Found a negative change in entry amount`);
+					}
+
+					// expect len to have increased by exactly 1
+					if(k_curr.listlen !== k_prev.listlen + 1n) {
+						fail(`List length change was not exactly 1`);
+					}
+				}
+			}
+		}
+
+		// assert empty spaces counter
+		if(c_empty_actual < this._n_empty) {
+			fail(`Contract reports ${this._n_empty} empty spaces but observed ${c_empty_actual}`);
+		}
+	}
+
+	toString(b_show_delta?: boolean): string {
+		const a_prev = this._a_entries_prev;
+
+		const a_lines: string[] = [
+			`┏━━━━━┯━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┯━━━━━━━━━━━━━━┯━━━━━━┯━━━━━━┓`,
+			`┃ idx │ address                                       │ amount       │ head │ len  ┃`,
+			`┠─────┴───────────────────────────────────────────────┴──────────────┴──────┴──────┨`,
+		];
+		const empty_row = (c: number) => `┃   ${SX_ANSI_DIM_ON}`+`...(empty x ${c})`.padEnd(78, ' ')+`${SX_ANSI_RESET}`+' ┃';
+		let i_index = 0;
+		let c_empty = 0;
+
+		for(const k_entry of this._a_entries) {
+			if(k_entry.isNil) {
+				c_empty += 1;
+			}
+			else {
+				if(c_empty) {
+					a_lines.push(empty_row(c_empty));
+					c_empty = 0;
+				}
+
+				a_lines.push(`┃ ${(i_index+'').padStart(3, ' ')} │ ${k_entry.toString(b_show_delta? a_prev[i_index]: null)} ┃`);
+			}
+
+			i_index += 1;
+		}
+
+		if(c_empty) a_lines.push(empty_row(c_empty));
+
+		return [
+			a_lines.join('\n'),
+			`┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛`,
+		].join('\n');
+	}
+
+	print(b_show_delta?: boolean): void {
+		console.log(this.toString(b_show_delta));
+	}
+}
+
+
+// const g_dwb = parse_dwb_dump(`
+// 	DelayedWriteBuffer {
+// empty_space_counter: 61,
+//  entries: [
+//  	DelayedWriteBufferEntry([30, 64, 27, 13, 80, 9, 191, 112, 225, 11, 76, 117, 251, 233, 171, 52, 62, 116, 221, 165, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]), 
+//  	DelayedWriteBufferEntry([252, 120, 243, 61, 153, 55, 155, 238, 217, 219, 75, 240, 232, 43, 128, 39, 177, 94, 70, 241, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]), 
+//  	DelayedWriteBufferEntry([78, 34, 145, 19, 199, 90, 194, 255, 187, 156, 147, 189, 154, 40, 119, 128, 77, 51, 242, 84, 0, 0, 0, 0, 0, 152, 150, 128, 0, 0, 0, 0, 3, 0, 1]), 
+//  	DelayedWriteBufferEntry([236, 133, 74, 220, 71, 232, 157, 194, 70, 160, 113, 10, 155, 74, 105, 192, 216, 151, 180, 80, 0, 0, 0, 0, 0, 30, 132, 128, 0, 0, 0, 0, 7, 0, 2]), 
+//  	DelayedWriteBufferEntry([171, 152, 150, 130, 223, 89, 19, 108, 106, 73, 34, 29, 160, 38, 68, 217, 164, 90, 53, 87, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]), 
+//  	DelayedWriteBufferEntry([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, 0, 0, 0]),
+//  	DelayedWriteBufferEntry([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, 0, 0, 0]),
+//  	DelayedWriteBufferEntry([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, 0, 0, 0]),
+//  	DelayedWriteBufferEntry([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, 0, 0, 0]),
+//  	DelayedWriteBufferEntry([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, 0, 0, 0]),
+//  	DelayedWriteBufferEntry([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, 0, 0, 0]),
+//  	DelayedWriteBufferEntry([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, 0, 0, 0]),
+//  	DelayedWriteBufferEntry([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, 0, 0, 0]),
+//  	DelayedWriteBufferEntry([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, 0, 0, 0]),
+//  	DelayedWriteBufferEntry([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, 0, 0, 0]),
+//  	DelayedWriteBufferEntry([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, 0, 0, 0]),
+//  	DelayedWriteBufferEntry([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, 0, 0, 0]),
+//  	DelayedWriteBufferEntry([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, 0, 0, 0]),
+//  	DelayedWriteBufferEntry([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, 0, 0, 0]),
+//  	DelayedWriteBufferEntry([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, 0, 0, 0]),
+//  	DelayedWriteBufferEntry([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, 0, 0, 0]),
+//  	DelayedWriteBufferEntry([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, 0, 0, 0]),
+//  	DelayedWriteBufferEntry([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, 0, 0, 0]),
+//  	DelayedWriteBufferEntry([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, 0, 0, 0]),
+//  	DelayedWriteBufferEntry([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, 0, 0, 0]),
+//  	DelayedWriteBufferEntry([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, 0, 0, 0]),
+//  	DelayedWriteBufferEntry([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, 0, 0, 0]),
+//  	DelayedWriteBufferEntry([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, 0, 0, 0]),
+//  	DelayedWriteBufferEntry([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, 0, 0, 0]),
+//  	DelayedWriteBufferEntry([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, 0, 0, 0]),
+//  	DelayedWriteBufferEntry([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, 0, 0, 0]),
+//  	DelayedWriteBufferEntry([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, 0, 0, 0]),
+//  	DelayedWriteBufferEntry([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, 0, 0, 0]),
+//  	DelayedWriteBufferEntry([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, 0, 0, 0]),
+//  	DelayedWriteBufferEntry([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, 0, 0, 0]),
+//  	DelayedWriteBufferEntry([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, 0, 0, 0]),
+//  	DelayedWriteBufferEntry([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, 0, 0, 0]),
+//  	DelayedWriteBufferEntry([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, 0, 0, 0]),
+//  	DelayedWriteBufferEntry([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, 0, 0, 0]),
+//  	DelayedWriteBufferEntry([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, 0, 0, 0]),
+//  	DelayedWriteBufferEntry([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, 0, 0, 0]),
+//  	DelayedWriteBufferEntry([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, 0, 0, 0]),
+//  	DelayedWriteBufferEntry([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, 0, 0, 0]),
+//  	DelayedWriteBufferEntry([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, 0, 0, 0]),
+//  	DelayedWriteBufferEntry([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, 0, 0, 0]),
+//  	DelayedWriteBufferEntry([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, 0, 0, 0]),
+//  	DelayedWriteBufferEntry([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, 0, 0, 0]),
+//  	DelayedWriteBufferEntry([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, 0, 0, 0]),
+//  	DelayedWriteBufferEntry([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, 0, 0, 0]),
+//  	DelayedWriteBufferEntry([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, 0, 0, 0]),
+//  	DelayedWriteBufferEntry([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, 0, 0, 0]),
+//  	DelayedWriteBufferEntry([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, 0, 0, 0]),
+//  	DelayedWriteBufferEntry([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, 0, 0, 0]),
+//  	DelayedWriteBufferEntry([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, 0, 0, 0]),
+//  	DelayedWriteBufferEntry([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, 0, 0, 0]),
+//  	DelayedWriteBufferEntry([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, 0, 0, 0]),
+//  	DelayedWriteBufferEntry([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, 0, 0, 0]),
+//  	DelayedWriteBufferEntry([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, 0, 0, 0]),
+//  	DelayedWriteBufferEntry([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, 0, 0, 0]),
+//  	DelayedWriteBufferEntry([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, 0, 0, 0]),
+//  	DelayedWriteBufferEntry([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, 0, 0, 0]),
+//  	DelayedWriteBufferEntry([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, 0, 0, 0]),
+//  	DelayedWriteBufferEntry([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, 0, 0, 0]),
+//  	DelayedWriteBufferEntry([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, 0, 0, 0]),
+//  	DelayedWriteBufferEntry([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, 0, 0, 0])
+//  ]
+// }
+// `);
+
+// console.log(g_dwb.entries.length);
diff --git a/tests/dwb/src/gas-checker.ts b/tests/dwb/src/gas-checker.ts
new file mode 100644
index 00000000..12b4b898
--- /dev/null
+++ b/tests/dwb/src/gas-checker.ts
@@ -0,0 +1,69 @@
+import type {GroupedGasLogs} from './snip';
+
+import {entries, bigint_abs} from '@blake.regalia/belt';
+
+import {SX_ANSI_GREEN, SX_ANSI_RED, SX_ANSI_MAGENTA, SX_ANSI_RESET, SX_ANSI_YELLOW, SX_ANSI_CYAN} from './helper';
+
+const delta_color = (xg_delta: bigint, nl_pad=0) => (bigint_abs(xg_delta) >= 1n
+	? bigint_abs(xg_delta) > 2n
+		? SX_ANSI_RED
+		: SX_ANSI_YELLOW
+	: '')+((xg_delta > 0? '+': '')+xg_delta).padStart(nl_pad, ' ')+SX_ANSI_RESET;
+
+export class GasChecker {
+	constructor(protected _h_baseline: GroupedGasLogs, protected _xg_used: bigint) {}
+
+	compare(h_local: GroupedGasLogs, xg_used: bigint): void {
+		const {_h_baseline, _xg_used} = this;
+
+		console.log(`  ⚖️  Gas usage relative to baseline:  ${xg_used === _xg_used
+			? `${SX_ANSI_GREEN}0`
+			: delta_color(xg_used - _xg_used)
+		}${SX_ANSI_RESET}`);
+
+		// each group
+		for(const [si_group, a_logs_local] of entries(h_local)) {
+			// find group in baseline
+			const a_logs_baseline = _h_baseline[si_group];
+
+			// offset
+			const xg_previous = a_logs_local[0]?.gas;
+
+			// each log
+			for(let i_log=1; i_log<a_logs_local.length; i_log++) {
+				// ref log
+				const {
+					index: i_local,
+					gap: xg_gap_local,
+					comment: s_comment_local,
+				} = a_logs_local[i_log];
+
+				const g_log_baseline = a_logs_baseline.find(g => g.index === i_local);
+
+				const xg_gap_baseline = g_log_baseline?.gap || 0n;
+
+				// calculate delta
+				const xg_delta = xg_gap_local - xg_gap_baseline;
+
+				// comment only
+				if('#' === si_group[0]) {
+					if(s_comment_local.trim()) {
+						console.log([
+							' '.repeat(8)+si_group.slice(0, 20).padEnd(20, ' '),
+							' '.repeat(3),
+							SX_ANSI_CYAN+s_comment_local+SX_ANSI_RESET,
+						].join(' │ '));
+					}
+				}
+				// non-zero delta
+				else if(xg_delta || '@' === s_comment_local[0]) {
+					console.log([
+						' '.repeat(8)+si_group.slice(0, 20).padEnd(20, ' '),
+						delta_color(xg_delta, 3),
+						('@' === s_comment_local[0]? SX_ANSI_MAGENTA: '')+s_comment_local+SX_ANSI_RESET,
+					].join(' │ '));
+				}
+			}
+		}
+	}
+}
diff --git a/tests/dwb/src/helper.ts b/tests/dwb/src/helper.ts
new file mode 100644
index 00000000..60836095
--- /dev/null
+++ b/tests/dwb/src/helper.ts
@@ -0,0 +1,89 @@
+import type {Promisable} from '@blake.regalia/belt';
+
+import {is_string, map_entries} from '@blake.regalia/belt';
+
+/* eslint-disable @typescript-eslint/naming-convention */
+export const SX_ANSI_RESET = '\x1b[0m';
+export const SX_ANSI_DIM_ON = '\x1b[2m';
+export const SX_ANSI_UNDERLINE = '\x1b[4m';
+export const SX_ANSI_DIM_OFF = '\x1b[22m';
+export const SX_ANSI_RED = '\x1b[31m';
+export const SX_ANSI_GREEN = '\x1b[32m';
+export const SX_ANSI_YELLOW = '\x1b[33m';
+export const SX_ANSI_BLUE = '\x1b[34m';
+export const SX_ANSI_MAGENTA = '\x1b[35m';
+export const SX_ANSI_CYAN = '\x1b[36m';
+export const SX_ANSI_WHITE = '\x1b[37m';
+export const SX_ANSI_GRAY_BG = '\x1b[100m';
+/* eslint-enable */
+
+// polyfill crypto global for node.js env
+globalThis.crypto ||= (await import('crypto')).webcrypto;
+
+export function pass(s_test: string): void {
+	// eslint-disable-next-line no-console
+	console.log(`${SX_ANSI_GREEN}✓${SX_ANSI_RESET} ${s_test}`);
+}
+
+function error(s_test: string, ...a_args: Array<string | object>) {
+	const a_rest = a_args.map(z => is_string(z)? z: map_entries(z, ([si, w]) => `\n\t${si}: ${w}`).join('\n'));
+	console.error(`${s_test}: ${a_rest.join('; ')}`);
+}
+
+export function fail(s_test: string, ...a_args: Array<string | object>): void {
+	error(`❌ ${s_test}`, ...a_args);
+	throw Error(`Exitting on error`);
+}
+
+export function caught(s_test: string, ...a_args: Array<string | object>): void {
+	error(`💀 ${s_test}`, ...a_args);
+}
+
+interface GroupCallback {
+	it(s_test: string, f_test: () => Promisable<void>): Promise<void>;
+}
+
+export async function describe(s_group: string, f_group: (g_call: GroupCallback) => Promisable<void>): Promise<void> {
+	const a_results: Array<{
+		type: 'pass';
+		name: string;
+	} | {
+		type: 'fail';
+		name: string;
+		message: string;
+	}> = [];
+
+	await f_group({
+		async it(s_test: string, f_test: () => Promisable<void>) {
+			try {
+				await f_test();
+
+				a_results.push({
+					type: 'pass',
+					name: s_test,
+				});
+			}
+			catch(e_run) {
+				a_results.push({
+					type: 'fail',
+					name: s_test,
+					message: (e_run as Error).stack || '',
+				});
+			}
+		},
+	});
+
+	console.log('');
+	console.log(`# ${s_group}\n${'='.repeat(2+s_group.length)}`);
+
+	for(const g_result of a_results) {
+		if('pass' === g_result.type) {
+			pass(g_result.name);
+		}
+		else {
+			fail(g_result.name, g_result.message);
+		}
+	}
+
+	console.log('');
+}
diff --git a/tests/dwb/src/main.ts b/tests/dwb/src/main.ts
new file mode 100644
index 00000000..e3e8b446
--- /dev/null
+++ b/tests/dwb/src/main.ts
@@ -0,0 +1,258 @@
+import type {Dict, JsonObject} from '@blake.regalia/belt';
+
+import type {SecretContractInterface, FungibleTransferCall, SecretAccAddr, Snip24} from '@solar-republic/contractor';
+
+import type {CwUint128, WeakUint128Str} from '@solar-republic/types';
+
+import {readFileSync} from 'node:fs';
+
+import {bytes, bytes_to_base64, entries, sha256, text_to_bytes, bigint_greater, bigint_abs} from '@blake.regalia/belt';
+import {encodeCosmosBankMsgSend, SI_MESSAGE_TYPE_COSMOS_BANK_MSG_SEND} from '@solar-republic/cosmos-grpc/cosmos/bank/v1beta1/tx';
+import {encodeGoogleProtobufAny} from '@solar-republic/cosmos-grpc/google/protobuf/any';
+import {SecretApp, SecretContract, Wallet, broadcast_result, create_and_sign_tx_direct, random_32, type TxMeta, type WeakSecretAccAddr} from '@solar-republic/neutrino';
+import {BigNumber} from 'bignumber.js';
+
+import {B_TEST_EVAPORATION, N_DECIMALS, P_SECRET_LCD, P_SECRET_RPC, SI_SECRET_CHAIN, X_GAS_PRICE, k_wallet_a, k_wallet_b, k_wallet_c, k_wallet_d} from './constants';
+import {upload_code, instantiate_contract} from './contract';
+import {DwbValidator} from './dwb';
+import {GasChecker} from './gas-checker';
+import {transfer, type TransferResult} from './snip';
+
+const S_CONTRACT_LABEL = 'snip2x-test_'+bytes_to_base64(crypto.getRandomValues(bytes(6)));
+
+const atu8_wasm = readFileSync('../../contract.wasm');
+
+console.log(k_wallet_a.addr);
+
+console.debug(`Uploading code...`);
+const sg_code_id = await upload_code(k_wallet_a, atu8_wasm);
+
+console.debug(`Instantiating contract...`);
+
+const sa_snip = await instantiate_contract(k_wallet_a, sg_code_id, {
+	name: S_CONTRACT_LABEL,
+	symbol: 'TKN',
+	decimals: 6,
+	admin: k_wallet_a.addr,
+	initial_balances: entries({
+		[k_wallet_a.addr]: 10_000_000000n,
+	}).map(([sa_account, xg_balance]) => ({
+		address: sa_account,
+		amount: `${xg_balance}`,
+	})),
+	prng_seed: bytes_to_base64(random_32()),
+	config: {
+		public_total_supply: true,
+		enable_deposit: true,
+		enable_redeem: true,
+		enable_mint: true,
+		enable_burn: true,
+	},
+});
+
+console.debug(`Running tests against ${sa_snip}...`);
+
+// @ts-expect-error deep instantiation
+const k_contract = await SecretContract<SecretContractInterface<{
+	extends: Snip24;
+	executions: {
+		transfer: [FungibleTransferCall & {
+			gas_target?: WeakUint128Str;
+		}];
+	};
+}>>(P_SECRET_LCD, sa_snip);
+
+const k_app_a = SecretApp(k_wallet_a, k_contract, X_GAS_PRICE);
+const k_app_b = SecretApp(k_wallet_b, k_contract, X_GAS_PRICE);
+const k_app_c = SecretApp(k_wallet_c, k_contract, X_GAS_PRICE);
+const k_app_d = SecretApp(k_wallet_d, k_contract, X_GAS_PRICE);
+
+const H_APPS = {
+	a: k_app_a,
+	b: k_app_b,
+	c: k_app_c,
+	d: k_app_d,
+};
+
+// #ts-expect-error validator!
+const k_dwbv = new DwbValidator(k_app_a);
+
+async function transfer_chain(sx_chain: string) {
+	const a_lines = sx_chain.split(/\s*\n+\s*/g).filter(s => s && /^\s*(\d+)/.test(s));
+
+	let k_checker: GasChecker | null = null;
+
+	for(const sx_line of a_lines) {
+		const [, sx_amount, si_from, si_to] = /^\s*([\d.]+)(?:\s*TKN)?\s+(\w+)(?:\s+to|\s*[-=]*>+)?\s+(\w+)\s*/.exec(sx_line)!;
+
+		const xg_amount = BigInt(BigNumber(sx_amount).shiftedBy(N_DECIMALS).toFixed(0));
+
+		console.log(sx_amount, si_from, si_to);
+
+		// @ts-expect-error secret app
+		const g_result = await transfer(k_dwbv, xg_amount, H_APPS[si_from[0].toLowerCase()] as SecretApp, H_APPS[si_to[0].toLowerCase()] as SecretApp, k_checker);
+
+		if(!k_checker) {
+			k_checker = new GasChecker(g_result.tracking, g_result.gasUsed);
+		}
+	}
+}
+
+// evaporation
+if(B_TEST_EVAPORATION) {
+	const xg_post_evaporate_buffer = 50_000n;
+	const xg_gas_wanted = 150_000n;
+	const xg_gas_target = xg_gas_wanted - xg_post_evaporate_buffer;
+
+	const [g_exec, xc_code, sx_res, g_meta, h_events, si_txn] = await k_app_a.exec('transfer', {
+		amount: `${500000n}` as CwUint128,
+		recipient: k_wallet_b.addr,
+		gas_target: `${xg_gas_target}`,
+	}, xg_gas_wanted);
+
+	console.log({g_meta});
+
+	if(xc_code) {
+		throw Error(`Failed evaporation test: ${sx_res}`);
+	}
+
+	const xg_gas_used = BigInt(g_meta?.gas_used || '0');
+	if(xg_gas_used < xg_gas_target) {
+		throw Error(`Expected gas used to be greater than ${xg_gas_target} but only used ${xg_gas_used}`);
+	}
+	else if(bigint_abs(xg_gas_wanted, xg_gas_used) > xg_post_evaporate_buffer) {
+		throw Error(`Expected gas used to be ${xg_gas_wanted} but found ${xg_gas_used}`);
+	}
+}
+
+{
+	console.log('# Initialized');
+	await k_dwbv.sync();
+	k_dwbv.print();
+	console.log('\n');
+
+	// basic transfers between principals
+	await transfer_chain(`
+		1 TKN Alice => Bob
+		2 TKN Alice => Carol
+		5 TKN Alice => David
+		1 TKN Bob => Carol 		-- Bob's entire balance; settles Bob for 1st time
+		1 TKN Carol => David 	-- should accumulate; settles Carol for 1st time
+		1 TKN David => Alice 	-- re-adds Alice to buffer; settles David for 1st time
+	`);
+
+	// extended transfers between principals
+	await transfer_chain(`
+		1 TKN David => Bob
+		1 TKN David => Bob 		-- exact same transfer repeated
+		1 TKN Alice => Bob
+		1 TKN Bob => Carol
+		1 TKN Alice => Carol
+		1 TKN Carol => Bob 		-- yet again
+	`);
+
+	// gas checker ref
+	let k_checker: GasChecker | null = null;
+
+	// grant action from previous simultion
+	let f_grant: undefined | (() => Promise<[w_result: JsonObject | undefined, xc_code: number, s_response: string, g_meta: TxMeta | undefined, h_events: Dict<string[]> | undefined, si_txn: string | undefined]>);
+
+	// number of simulations to perform
+	const N_SIMULATIONS = 300;
+
+	// record maximum gas used for direct transfers
+	let xg_max_gas_used_transfer = 0n;
+
+	// simulate many transfers
+	for(let i_sim=0; i_sim<N_SIMULATIONS; i_sim++) {
+		const si_receiver = i_sim+'';
+
+		const k_wallet = await Wallet(await sha256(text_to_bytes(si_receiver)), SI_SECRET_CHAIN, P_SECRET_LCD, P_SECRET_RPC, 'secret');
+
+		const k_app_sim = SecretApp(k_wallet, k_contract, X_GAS_PRICE);
+
+		// label
+		console.log(`Alice --> ${si_receiver}`);
+
+		// transfer some gas to sim account
+		const [atu8_raw,, si_txn] = await create_and_sign_tx_direct(k_wallet_b, [
+			encodeGoogleProtobufAny(
+				SI_MESSAGE_TYPE_COSMOS_BANK_MSG_SEND,
+				encodeCosmosBankMsgSend(k_wallet_b.addr, k_wallet.addr, [[`${1_000000n}`, 'uscrt']])
+			),
+		], [[`${5000n}`, 'uscrt']], 50_000n);
+
+		// submit all in parallel
+		const [
+			// @ts-expect-error totally stupid
+			g_result_transfer,
+			[xc_send_gas, s_err_send_gas],
+			a_res_increase,
+		] = await Promise.all([
+			// #ts-expect-error secret app
+			transfer(k_dwbv, i_sim % 2? 1_000000n: 2_000000n, k_app_a, k_app_sim, k_checker),
+			broadcast_result(k_wallet, atu8_raw, si_txn),
+			f_grant?.(),
+		]);
+
+		// send gas error
+		if(xc_send_gas) {
+			throw Error(`Failed to transfer gas: ${s_err_send_gas}`);
+		}
+
+		// increase allowance error
+		if(f_grant && a_res_increase?.[1]) {
+			throw Error(`Failed to increase allowance: ${a_res_increase[2]}`);
+		}
+
+		// approve Alice as spender for future txs
+		f_grant = () => k_app_sim.exec('increase_allowance', {
+			spender: k_wallet_a.addr,
+			amount: `${1_000000n}` as CwUint128,
+		}, 60_000n);
+
+		if(!k_checker) {
+			k_checker = new GasChecker((g_result_transfer as TransferResult).tracking, (g_result_transfer as TransferResult).gasUsed);
+		}
+
+		xg_max_gas_used_transfer = bigint_greater(xg_max_gas_used_transfer, g_result_transfer.gasUsed);
+	}
+
+	// reset checker
+	k_checker = null;
+
+	// record maximum gas used for transfer froms
+	let xg_max_gas_used_transfer_from = 0n;
+
+	// perform transfer_from
+	for(let i_sim=N_SIMULATIONS-2; i_sim>0; i_sim--) {
+		const si_owner = i_sim+'';
+		const si_recipient = (i_sim - 1)+'';
+
+		const k_wallet_owner = await Wallet(await sha256(text_to_bytes(si_owner)), SI_SECRET_CHAIN, P_SECRET_LCD, P_SECRET_RPC, 'secret');
+		const k_wallet_recipient = await Wallet(await sha256(text_to_bytes(si_recipient)), SI_SECRET_CHAIN, P_SECRET_LCD, P_SECRET_RPC, 'secret');
+
+		const k_app_owner = SecretApp(k_wallet_owner, k_contract, X_GAS_PRICE);
+		const k_app_recipient = SecretApp(k_wallet_recipient, k_contract, X_GAS_PRICE);
+
+		console.log(`${si_owner} --> ${si_recipient}`);
+
+		// #ts-expect-error secret app
+		const g_result = await transfer(k_dwbv, 1_000000n, k_app_owner, k_app_recipient, k_checker, k_app_a);
+
+		if(!k_checker) {
+			k_checker = new GasChecker(g_result.tracking, g_result.gasUsed);
+		}
+
+		xg_max_gas_used_transfer_from = bigint_greater(xg_max_gas_used_transfer_from, g_result.gasUsed);
+	}
+
+	// report
+	console.log({
+		xg_max_gas_used_transfer,
+		xg_max_gas_used_transfer_from,
+	});
+
+	// done
+	process.exit(0);
+}
diff --git a/tests/dwb/src/snip.ts b/tests/dwb/src/snip.ts
new file mode 100644
index 00000000..4c3b3a62
--- /dev/null
+++ b/tests/dwb/src/snip.ts
@@ -0,0 +1,205 @@
+import type {DwbValidator} from './dwb';
+import type {GasChecker} from './gas-checker';
+import type {Dict, Nilable} from '@blake.regalia/belt';
+import type {SecretContractInterface} from '@solar-republic/contractor';
+import type {SecretApp, WeakSecretAccAddr} from '@solar-republic/neutrino';
+import type {CwUint128, SecretQueryPermit, WeakUintStr} from '@solar-republic/types';
+
+import {entries, is_bigint, stringify_json} from '@blake.regalia/belt';
+import {queryCosmosBankBalance} from '@solar-republic/cosmos-grpc/cosmos/bank/v1beta1/query';
+import {sign_secret_query_permit} from '@solar-republic/neutrino';
+import BigNumber from 'bignumber.js';
+
+import {H_ADDRS, N_DECIMALS, P_SECRET_LCD} from './constants';
+import {fail} from './helper';
+
+
+export type GasLog = {
+	index: number;
+	gas: bigint;
+	gap: bigint;
+	comment: string;
+};
+
+export type GroupedGasLogs = Dict<GasLog[]>;
+
+export type TransferResult = {
+	tracking: GroupedGasLogs;
+	gasUsed: bigint;
+};
+
+type TokenBalance = SecretContractInterface<{
+	queries: {
+		balance: [{}, {
+			amount: CwUint128;
+		}];
+
+		with_permit: {
+			variants: [
+				{
+					msg: {
+						query: {
+							balance: {};
+						};
+						permit: SecretQueryPermit;
+					};
+					response: {
+						balance: {
+							amount: CwUint128;
+						};
+					};
+				},
+			];
+		};
+	};
+}>;
+
+export async function scrt_balance(sa_owner: WeakSecretAccAddr): Promise<bigint> {
+	const [,, g_res] = await queryCosmosBankBalance(P_SECRET_LCD, sa_owner, 'uscrt');
+	return BigInt(g_res?.balance?.amount || '0');
+}
+
+export async function snip_balance(k_app: SecretApp<TokenBalance>) {
+	const g_permit = await sign_secret_query_permit(k_app.wallet, 'snip-balance', [k_app.contract.addr], ['balance']);
+	return await k_app.query('balance', {}, g_permit as unknown as null);
+}
+
+export async function transfer(
+	k_dwbv: DwbValidator,
+	xg_amount: bigint,
+	k_app_owner: SecretApp,
+	k_app_recipient: SecretApp,
+	k_checker?: Nilable<GasChecker>,
+	k_app_sender?: SecretApp
+): Promise<TransferResult> {
+	const sa_owner = k_app_owner.wallet.addr;
+	const sa_recipient = k_app_recipient.wallet.addr;
+
+	// scrt balance of owner before transfer
+	// @ts-expect-error canonical addr
+	const xg_scrt_balance_owner_before = await scrt_balance(sa_owner);
+
+	// query balance of owner and recipient
+	const [
+		[g_balance_owner_before],
+		[g_balance_recipient_before],
+	] = await Promise.all([
+		// @ts-expect-error secret app
+		snip_balance(k_app_owner),
+		// @ts-expect-error secret app
+		snip_balance(k_app_recipient),
+	]);
+
+	// execute transfer
+	const [g_exec, xc_code, sx_res, g_meta, h_events, si_txn] = k_app_sender
+		? await k_app_sender.exec('transfer_from', {
+			owner: k_app_owner.wallet.addr,
+			amount: `${xg_amount}` as CwUint128,
+			recipient: sa_recipient,
+		}, 250000n)
+		: await k_app_owner.exec('transfer', {
+			amount: `${xg_amount}` as CwUint128,
+			recipient: sa_recipient,
+		}, 250000n);
+
+	// section header
+	console.log(`# Transfer ${BigNumber(xg_amount+'').shiftedBy(-N_DECIMALS).toFixed()} TKN ${H_ADDRS[sa_owner] || sa_owner}${k_app_sender? ` (via ${H_ADDRS[k_app_sender.wallet.addr] || k_app_sender.wallet.addr})`: ''} => ${H_ADDRS[sa_recipient] || sa_recipient}      |  ⏹  ${k_dwbv.empty} spaces  |  ⛽️ ${g_meta?.gas_used || '0'} gas used`);
+
+	// query balance of owner and recipient again
+	const [
+		[g_balance_owner_after],
+		[g_balance_recipient_after],
+	] = await Promise.all([
+		// @ts-expect-error secret app
+		snip_balance(k_app_owner),
+		// @ts-expect-error secret app
+		snip_balance(k_app_recipient),
+	]);
+
+	if(xc_code) {
+		console.warn('Diagnostics', {
+			scrt_balance_before: xg_scrt_balance_owner_before,
+			// @ts-expect-error canonical addr
+			scrt_balance_after: await scrt_balance(sa_owner),
+			snip_balance_before: g_balance_owner_before?.amount,
+			snip_balance_after: g_balance_owner_after?.amount,
+			meta: stringify_json(g_meta),
+			events: h_events,
+			exec: g_exec,
+		});
+
+		throw Error(`Failed to execute transfer from ${k_app_owner.wallet.addr} [${xc_code}]: ${sx_res}`);
+	}
+
+	// sync the buffer
+	await k_dwbv.sync();
+
+	const h_tracking: GroupedGasLogs = {};
+	for(const [si_key, a_values] of entries(h_events!)) {
+		const m_key = /^wasm\.gas\.(.+)$/.exec(si_key);
+		if(m_key) {
+			const [, si_group] = m_key;
+
+			const a_logs: GasLog[] = [];
+			let xg_previous = 0n;
+
+			for(const sx_value of a_values) {
+				const [, sg_index, sg_gas, s_comment] = /^(\d+):(\d+):([^]*)$/.exec(sx_value)!;
+
+				const xg_gas = BigInt(sg_gas);
+
+				a_logs.push({
+					index: parseInt(sg_index),
+					gas: xg_gas,
+					gap: xg_gas - xg_previous,
+					comment: s_comment,
+				});
+
+				xg_previous = xg_gas;
+			}
+
+			h_tracking[si_group] = a_logs.sort((g_a, g_b) => g_a.index - g_b.index);
+		}
+	}
+
+	if(k_checker) {
+		k_checker.compare(h_tracking, BigInt(g_meta!.gas_used));
+	}
+	else if(null === k_checker) {
+		console.log(`  ⚖️  Setting baseline gas used to ${g_meta!.gas_used}`);
+	}
+
+	// prit its state
+	k_dwbv.print(true);
+
+
+	// balance queries failed
+	if(!g_balance_owner_before || !g_balance_recipient_before || !g_balance_owner_after || !g_balance_recipient_after) {
+		throw fail(`Failed to fetch balances`);
+	}
+
+	// expect exact amount difference for owner
+	const xg_owner_loss = BigInt(g_balance_owner_before.amount as string) - BigInt(g_balance_owner_after.amount);
+	if(xg_owner_loss !== xg_amount) {
+		fail(`Owner's balance changed by ${-xg_owner_loss}, but the amount sent was ${xg_amount}`);
+	}
+
+	// expect exact amount difference for recipient
+	const xg_recipient_gain = BigInt(g_balance_recipient_after.amount) - BigInt(g_balance_recipient_before.amount);
+	if(xg_recipient_gain !== xg_amount) {
+		fail(`Recipient's balance changed by ${xg_recipient_gain}, but the amount sent was ${xg_amount}`);
+	}
+
+	// make assertions
+	await k_dwbv.check({
+		// shouldNotContainEntriesFor: [k_app_owner.wallet.addr],
+	});
+
+	// close
+	console.log('\n');
+
+	return {
+		tracking: h_tracking,
+		gasUsed: BigInt(g_meta!.gas_used),
+	};
+}
diff --git a/tests/dwb/tsconfig.json b/tests/dwb/tsconfig.json
new file mode 100644
index 00000000..79f379b2
--- /dev/null
+++ b/tests/dwb/tsconfig.json
@@ -0,0 +1,10 @@
+{
+	"extends": [
+		"@blake.regalia/tsconfig/tsconfig.node.json"
+	],
+
+	"compilerOptions": {
+		"moduleResolution": "Bundler",
+		"outDir": "dist",
+	},
+}
\ No newline at end of file
diff --git a/tests/dwb/tsconfig.tsc-esm-fix.json b/tests/dwb/tsconfig.tsc-esm-fix.json
new file mode 100644
index 00000000..64db3f6f
--- /dev/null
+++ b/tests/dwb/tsconfig.tsc-esm-fix.json
@@ -0,0 +1,7 @@
+{
+	"extends": "node_modules/@blake.regalia/tsconfig/tsconfig.node.json",
+	"compilerOptions": {
+		"moduleResolution": "Bundler",
+		"outDir": "dist",
+	},
+}
\ No newline at end of file
diff --git a/tests/integration.sh b/tests/integration.sh
index dba743ee..37846613 100755
--- a/tests/integration.sh
+++ b/tests/integration.sh
@@ -18,7 +18,7 @@ declare -A FROM=(
 # In particular, it's not possible to dynamically expand aliases, but `tx_of` dynamically executes whatever
 # we specify in its arguments.
 function secretcli() {
-    docker exec secretdev /usr/bin/secretd "$@"
+    docker exec localsecret /usr/bin/secretd "$@"
 }
 
 # Just like `echo`, but prints to stderr
@@ -587,7 +587,7 @@ function test_permit() {
     local expected_error="Error: query result: Generic error: Permit doesn't apply to token \"$contract_addr\", allowed tokens: [\"$wrong_contract\"]"
     for key in "${KEY[@]}"; do
         log "permit querying balance for \"$key\" with wrong permit for that contract"
-        permit=$(docker exec secretdev bash -c "/usr/bin/secretd tx sign-doc <(echo '"$permit"') --from '$key'")
+        permit=$(docker exec localsecret bash -c "/usr/bin/secretd tx sign-doc <(echo '"$permit"') --from '$key'")
         permit_query='{"with_permit":{"query":{"balance":{}},"permit":{"params":{"permit_name":"test","chain_id":"blabla","allowed_tokens":["'"$wrong_contract"'"],"permissions":["balance"]},"signature":'"$permit"'}}}'
         result="$(compute_query "$contract_addr" "$permit_query" 2>&1 | sed 's/\\//g' || true)"
         assert_eq "$result" "$expected_error"
@@ -603,7 +603,7 @@ function test_permit() {
         tx_hash="$(compute_execute "$contract_addr" '{"revoke_permit":{"permit_name":"to_be_revoked"}}' ${FROM[$key]} --gas 250000)"
         wait_for_compute_tx "$tx_hash" "waiting for revoke_permit from \"$key\" to process"
 
-        permit=$(docker exec secretdev bash -c "/usr/bin/secretd tx sign-doc <(echo '"$permit"') --from '$key'")
+        permit=$(docker exec localsecret bash -c "/usr/bin/secretd tx sign-doc <(echo '"$permit"') --from '$key'")
         permit_query='{"with_permit":{"query":{"balance":{}},"permit":{"params":{"permit_name":"to_be_revoked","chain_id":"blabla","allowed_tokens":["'"$contract_addr"'"],"permissions":["balance"]},"signature":'"$permit"'}}}'
         expected_error="Error: query result: Generic error: Permit \"to_be_revoked\" was revoked by account \"${ADDRESS[$key]}"
         result="$(compute_query "$contract_addr" "$permit_query" 2>&1 | sed 's/\\//g' || true)"
@@ -617,7 +617,7 @@ function test_permit() {
     local expected_error
     for key in "${KEY[@]}"; do
         log "permit querying balance for \"$key\" with params not matching permit"
-        permit=$(docker exec secretdev bash -c "/usr/bin/secretd tx sign-doc <(echo '"$permit"') --from '$key'")
+        permit=$(docker exec localsecret bash -c "/usr/bin/secretd tx sign-doc <(echo '"$permit"') --from '$key'")
         permit_query='{"with_permit":{"query":{"balance":{}},"permit":{"params":{"permit_name":"test","chain_id":"not_blabla","allowed_tokens":["'"$contract_addr"'"],"permissions":["balance"]},"signature":'"$permit"'}}}'
         expected_error="Error: query result: Generic error: Failed to verify signatures for the given permit"
         result="$(compute_query "$contract_addr" "$permit_query" 2>&1 | sed 's/\\//g' || true)"
@@ -632,7 +632,7 @@ function test_permit() {
     local expected_error
     for key in "${KEY[@]}"; do
         log "permit querying balance for \"$key\" without the right permission"
-        permit=$(docker exec secretdev bash -c "/usr/bin/secretd tx sign-doc <(echo '"$permit_conf"') --from '$key'")
+        permit=$(docker exec localsecret bash -c "/usr/bin/secretd tx sign-doc <(echo '"$permit_conf"') --from '$key'")
         permit_query='{"with_permit":{"query":{"balance":{}},"permit":{"params":{"permit_name":"test","chain_id":"blabla","allowed_tokens":["'"$contract_addr"'"],"permissions":["history"]},"signature":'"$permit"'}}}'
         expected_error="Error: query result: Generic error: No permission to query balance, got permissions [History]"
         result="$(compute_query "$contract_addr" "$permit_query" 2>&1 | sed 's/\\//g' || true)"
@@ -647,7 +647,7 @@ function test_permit() {
     local expected_error
     for key in "${KEY[@]}"; do
         log "permit querying history for \"$key\" without the right permission"
-        permit=$(docker exec secretdev bash -c "/usr/bin/secretd tx sign-doc <(echo '"$permit_conf"') --from '$key'")
+        permit=$(docker exec localsecret bash -c "/usr/bin/secretd tx sign-doc <(echo '"$permit_conf"') --from '$key'")
 
         permit_query='{"with_permit":{"query":{"transfer_history":{"page_size":10, "should_filter_decoys":false}},"permit":{"params":{"permit_name":"test","chain_id":"blabla","allowed_tokens":["'"$contract_addr"'"],"permissions":["balance"]},"signature":'"$permit"'}}}'
         expected_error="Error: query result: Generic error: No permission to query history, got permissions [Balance]"
@@ -668,7 +668,7 @@ function test_permit() {
     local expected_error
     for key in "${KEY[@]}"; do
         log "permit querying allowance for \"$key\" without the right permission"
-        permit=$(docker exec secretdev bash -c "/usr/bin/secretd tx sign-doc <(echo '"$permit_conf"') --from '$key'")
+        permit=$(docker exec localsecret bash -c "/usr/bin/secretd tx sign-doc <(echo '"$permit_conf"') --from '$key'")
         permit_query='{"with_permit":{"query":{"allowance":{"owner":"'"${ADDRESS[$key]}"'","spender":"'"${ADDRESS[$key]}"'"}},"permit":{"params":{"permit_name":"test","chain_id":"blabla","allowed_tokens":["'"$contract_addr"'"],"permissions":["history"]},"signature":'"$permit"'}}}'
         expected_error="Error: query result: Generic error: No permission to query allowance, got permissions [History]"
         result="$(compute_query "$contract_addr" "$permit_query" 2>&1 | sed 's/\\//g' || true)"
@@ -681,7 +681,7 @@ function test_permit() {
     local permit_query
     local expected_error
     log "permit querying allowance without signer being the owner or spender"
-    permit=$(docker exec secretdev bash -c "/usr/bin/secretd tx sign-doc <(echo '"$wrong_permit"') --from a")
+    permit=$(docker exec localsecret bash -c "/usr/bin/secretd tx sign-doc <(echo '"$wrong_permit"') --from a")
     permit_query='{"with_permit":{"query":{"allowance":{"owner":"'"$wrong_contract"'","spender":"'"$wrong_contract"'"}},"permit":{"params":{"permit_name":"test","chain_id":"blabla","allowed_tokens":["'"$contract_addr"'"],"permissions":["allowance"]},"signature":'"$permit"'}}}'
     expected_error="Error: query result: Generic error: Cannot query allowance. Requires permit for either owner \"$wrong_contract\" or spender \"$wrong_contract\", got permit for \"${ADDRESS[a]}"
     result="$(compute_query "$contract_addr" "$permit_query" 2>&1 | sed 's/\\//g' || true)"
@@ -695,7 +695,7 @@ function test_permit() {
     local expected_output
     for key in "${KEY[@]}"; do
         log "permit querying balance for \"$key\""
-        permit=$(docker exec secretdev bash -c "/usr/bin/secretd tx sign-doc <(echo '"$good_permit"') --from '$key'")
+        permit=$(docker exec localsecret bash -c "/usr/bin/secretd tx sign-doc <(echo '"$good_permit"') --from '$key'")
         permit_query='{"with_permit":{"query":{"balance":{}},"permit":{"params":{"permit_name":"test","chain_id":"blabla","allowed_tokens":["'"$contract_addr"'"],"permissions":["balance"]},"signature":'"$permit"'}}}'
         expected_output="{\"balance\":{\"amount\":\"0\"}}"
         result="$(compute_query "$contract_addr" "$permit_query" 2>&1 | sed 's/\\//g' || true)"
@@ -710,7 +710,7 @@ function test_permit() {
     local expected_output
     for key in "${KEY[@]}"; do
         log "permit querying history for \"$key\""
-        permit=$(docker exec secretdev bash -c "/usr/bin/secretd tx sign-doc <(echo '"$good_permit"') --from '$key'")
+        permit=$(docker exec localsecret bash -c "/usr/bin/secretd tx sign-doc <(echo '"$good_permit"') --from '$key'")
 
         permit_query='{"with_permit":{"query":{"transfer_history":{"page_size":10, "should_filter_decoys":false}},"permit":{"params":{"permit_name":"test","chain_id":"blabla","allowed_tokens":["'"$contract_addr"'"],"permissions":["history"]},"signature":'"$permit"'}}}'
         expected_output="{\"transfer_history\":{\"txs\":[],\"total\":0}}"
@@ -731,7 +731,7 @@ function test_permit() {
     local expected_output
     for key in "${KEY[@]}"; do
         log "permit querying history for \"$key\""
-        permit=$(docker exec secretdev bash -c "/usr/bin/secretd tx sign-doc <(echo '"$good_permit"') --from '$key'")
+        permit=$(docker exec localsecret bash -c "/usr/bin/secretd tx sign-doc <(echo '"$good_permit"') --from '$key'")
 
         permit_query='{"with_permit":{"query":{"allowance":{"owner":"'"${ADDRESS[$key]}"'","spender":"'"${ADDRESS[$key]}"'"}},"permit":{"params":{"permit_name":"test","chain_id":"blabla","allowed_tokens":["'"$contract_addr"'"],"permissions":["allowance"]},"signature":'"$permit"'}}}'
         expected_output="{\"allowance\":{\"spender\":\"${ADDRESS[$key]}\",\"owner\":\"${ADDRESS[$key]}\",\"allowance\":\"0\",\"expiration\":null}}"