diff --git a/README.md b/README.md
index 8e020e6c..c5a02cbf 100644
--- a/README.md
+++ b/README.md
@@ -14,4 +14,4 @@ This repository is organized into several parts:
The documentation (tutorials) in the `docs` folder is built using Docusaurus and is automatically absorbed into the main [miden-docs](https://github.com/0xMiden/miden-docs) repository for the main documentation website. Changes to the `next` branch trigger an automated deployment workflow. The docs folder requires npm packages to be installed before building.
-The documentation folder is also a standalone Rust repository. The purpose of this is to be able to run `cargo doc test`, to test the Rust code inside of the tutorial markdowns.
+The documentation folder is also a Rust crate. Run `cargo test --doc` inside `docs/` to check the Rust examples in the tutorial markdowns.
diff --git a/docs/Cargo.lock b/docs/Cargo.lock
index 1477cc89..defd92ef 100644
--- a/docs/Cargo.lock
+++ b/docs/Cargo.lock
@@ -19,12 +19,12 @@ checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa"
[[package]]
name = "aead"
-version = "0.5.2"
+version = "0.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0"
+checksum = "1973cfbc1a2daf9cf550e74e1f088c28e7f7d8c1e1418fb6c9dc5184b7e84c99"
dependencies = [
- "crypto-common",
- "generic-array",
+ "crypto-common 0.2.2",
+ "inout",
]
[[package]]
@@ -50,7 +50,17 @@ dependencies = [
"paste",
"ruint",
"rustc-hash",
- "sha3",
+ "sha3 0.10.8",
+]
+
+[[package]]
+name = "alloy-rlp"
+version = "0.3.16"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "24671b1f62edcf0f9b62994c7bf72cd621a04a4b99f5020ece1a647b40e2f103"
+dependencies = [
+ "arrayvec",
+ "bytes",
]
[[package]]
@@ -76,11 +86,11 @@ dependencies = [
"alloy-sol-macro-input",
"const-hex",
"heck",
- "indexmap",
+ "indexmap 2.14.0",
"proc-macro-error2",
"proc-macro2",
"quote",
- "sha3",
+ "sha3 0.10.8",
"syn 2.0.115",
"syn-solidity",
]
@@ -179,6 +189,269 @@ dependencies = [
"backtrace",
]
+[[package]]
+name = "ark-ff"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6b3235cc41ee7a12aaaf2c575a2ad7b46713a8a50bda2fc3b003a04845c05dd6"
+dependencies = [
+ "ark-ff-asm 0.3.0",
+ "ark-ff-macros 0.3.0",
+ "ark-serialize 0.3.0",
+ "ark-std 0.3.0",
+ "derivative",
+ "num-bigint 0.4.6",
+ "num-traits",
+ "paste",
+ "rustc_version 0.3.3",
+ "zeroize",
+]
+
+[[package]]
+name = "ark-ff"
+version = "0.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ec847af850f44ad29048935519032c33da8aa03340876d351dfab5660d2966ba"
+dependencies = [
+ "ark-ff-asm 0.4.2",
+ "ark-ff-macros 0.4.2",
+ "ark-serialize 0.4.2",
+ "ark-std 0.4.0",
+ "derivative",
+ "digest 0.10.7",
+ "itertools 0.10.5",
+ "num-bigint 0.4.6",
+ "num-traits",
+ "paste",
+ "rustc_version 0.4.1",
+ "zeroize",
+]
+
+[[package]]
+name = "ark-ff"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a177aba0ed1e0fbb62aa9f6d0502e9b46dad8c2eab04c14258a1212d2557ea70"
+dependencies = [
+ "ark-ff-asm 0.5.0",
+ "ark-ff-macros 0.5.0",
+ "ark-serialize 0.5.0",
+ "ark-std 0.5.0",
+ "arrayvec",
+ "digest 0.10.7",
+ "educe",
+ "itertools 0.13.0",
+ "num-bigint 0.4.6",
+ "num-traits",
+ "paste",
+ "zeroize",
+]
+
+[[package]]
+name = "ark-ff"
+version = "0.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f7a806ac6c8307b929df4645776290a50ee2aac754ad09d8bdf73391309e43af"
+dependencies = [
+ "ark-ff-asm 0.6.0",
+ "ark-ff-macros 0.6.0",
+ "ark-serialize 0.6.0",
+ "ark-std 0.6.0",
+ "digest 0.10.7",
+ "educe",
+ "num-bigint 0.4.6",
+ "num-traits",
+ "zeroize",
+]
+
+[[package]]
+name = "ark-ff-asm"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "db02d390bf6643fb404d3d22d31aee1c4bc4459600aef9113833d17e786c6e44"
+dependencies = [
+ "quote",
+ "syn 1.0.109",
+]
+
+[[package]]
+name = "ark-ff-asm"
+version = "0.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3ed4aa4fe255d0bc6d79373f7e31d2ea147bcf486cba1be5ba7ea85abdb92348"
+dependencies = [
+ "quote",
+ "syn 1.0.109",
+]
+
+[[package]]
+name = "ark-ff-asm"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "62945a2f7e6de02a31fe400aa489f0e0f5b2502e69f95f853adb82a96c7a6b60"
+dependencies = [
+ "quote",
+ "syn 2.0.115",
+]
+
+[[package]]
+name = "ark-ff-asm"
+version = "0.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1479009684adc073dff49a1025d3a7065b317a9ead25aaaca38cdc70058ba8a2"
+dependencies = [
+ "quote",
+ "syn 2.0.115",
+]
+
+[[package]]
+name = "ark-ff-macros"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "db2fd794a08ccb318058009eefdf15bcaaaaf6f8161eb3345f907222bac38b20"
+dependencies = [
+ "num-bigint 0.4.6",
+ "num-traits",
+ "quote",
+ "syn 1.0.109",
+]
+
+[[package]]
+name = "ark-ff-macros"
+version = "0.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7abe79b0e4288889c4574159ab790824d0033b9fdcb2a112a3182fac2e514565"
+dependencies = [
+ "num-bigint 0.4.6",
+ "num-traits",
+ "proc-macro2",
+ "quote",
+ "syn 1.0.109",
+]
+
+[[package]]
+name = "ark-ff-macros"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "09be120733ee33f7693ceaa202ca41accd5653b779563608f1234f78ae07c4b3"
+dependencies = [
+ "num-bigint 0.4.6",
+ "num-traits",
+ "proc-macro2",
+ "quote",
+ "syn 2.0.115",
+]
+
+[[package]]
+name = "ark-ff-macros"
+version = "0.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4a0691ed21ef00ef89c1e9bda832eba493dda3ec2f8d892fb25b705f73f06bb8"
+dependencies = [
+ "num-bigint 0.4.6",
+ "num-traits",
+ "proc-macro2",
+ "quote",
+ "syn 2.0.115",
+]
+
+[[package]]
+name = "ark-serialize"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1d6c2b318ee6e10f8c2853e73a83adc0ccb88995aa978d8a3408d492ab2ee671"
+dependencies = [
+ "ark-std 0.3.0",
+ "digest 0.9.0",
+]
+
+[[package]]
+name = "ark-serialize"
+version = "0.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "adb7b85a02b83d2f22f89bd5cac66c9c89474240cb6207cb1efc16d098e822a5"
+dependencies = [
+ "ark-std 0.4.0",
+ "digest 0.10.7",
+ "num-bigint 0.4.6",
+]
+
+[[package]]
+name = "ark-serialize"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3f4d068aaf107ebcd7dfb52bc748f8030e0fc930ac8e360146ca54c1203088f7"
+dependencies = [
+ "ark-std 0.5.0",
+ "arrayvec",
+ "digest 0.10.7",
+ "num-bigint 0.4.6",
+]
+
+[[package]]
+name = "ark-serialize"
+version = "0.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a74dd304fd536fb95d0a328e72be759209cc496a9da094c5bc56e5fea4f9e86b"
+dependencies = [
+ "ark-serialize-derive",
+ "ark-std 0.6.0",
+ "digest 0.10.7",
+ "num-bigint 0.4.6",
+ "serde_with",
+]
+
+[[package]]
+name = "ark-serialize-derive"
+version = "0.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4f153690697a2b91e5e1251ff98411ee5371500a111a0fd317a70e588eb300f9"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.115",
+]
+
+[[package]]
+name = "ark-std"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1df2c09229cbc5a028b1d70e00fdb2acee28b1055dfb5ca73eea49c5a25c4e7c"
+dependencies = [
+ "num-traits",
+ "rand 0.8.5",
+]
+
+[[package]]
+name = "ark-std"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "94893f1e0c6eeab764ade8dc4c0db24caf4fe7cbbaafc0eba0a9030f447b5185"
+dependencies = [
+ "num-traits",
+ "rand 0.8.5",
+]
+
+[[package]]
+name = "ark-std"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "246a225cc6131e9ee4f24619af0f19d67761fff15d7ccc22e42b80846e69449a"
+dependencies = [
+ "num-traits",
+ "rand 0.8.5",
+]
+
+[[package]]
+name = "ark-std"
+version = "0.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "367c9c827ed431bff6868b7aa926e05b16eb46603cc8b6e768e4a5553fa1d155"
+dependencies = [
+ "num-traits",
+ "rand 0.8.5",
+]
+
[[package]]
name = "arrayref"
version = "0.3.9"
@@ -191,15 +464,6 @@ version = "0.7.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50"
-[[package]]
-name = "ascii-canvas"
-version = "4.0.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ef1e3e699d84ab1b0911a1010c5c106aa34ae89aeac103be5ce0c3859db1e891"
-dependencies = [
- "term",
-]
-
[[package]]
name = "async-trait"
version = "0.1.89"
@@ -217,6 +481,17 @@ version = "1.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0"
+[[package]]
+name = "auto_impl"
+version = "1.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ffdcb70bdbc4d478427380519163274ac86e52916e10f0a8889adf0f96d3fee7"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.115",
+]
+
[[package]]
name = "autocfg"
version = "1.5.0"
@@ -249,9 +524,9 @@ dependencies = [
[[package]]
name = "base16ct"
-version = "0.2.0"
+version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf"
+checksum = "fd307490d624467aa6f74b0eabb77633d1f758a7b25f12bceb0b22e08d9726f6"
[[package]]
name = "base64"
@@ -278,35 +553,23 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3a8241f3ebb85c056b509d4327ad0358fbbba6ffb340bf388f26350aeda225b1"
[[package]]
-name = "bincode"
-version = "1.3.3"
+name = "bitflags"
+version = "2.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad"
-dependencies = [
- "serde",
-]
+checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3"
[[package]]
-name = "bit-set"
-version = "0.8.0"
+name = "bitvec"
+version = "1.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "08807e080ed7f9d5433fa9b275196cfc35414f66a0c79d864dc51a0d825231a3"
+checksum = "ddcec3d12c579d40898fe0a9a358a803c23e9c52ca3c425707f81c9436211837"
dependencies = [
- "bit-vec",
+ "funty",
+ "radium",
+ "tap",
+ "wyz",
]
-[[package]]
-name = "bit-vec"
-version = "0.8.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7"
-
-[[package]]
-name = "bitflags"
-version = "2.10.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3"
-
[[package]]
name = "blake3"
version = "1.8.2"
@@ -329,6 +592,15 @@ dependencies = [
"generic-array",
]
+[[package]]
+name = "block-buffer"
+version = "0.12.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d2f6c7dbe95a6ed67ad9f18e57daf93a2f034c524b99fd2b76d18fdfeb6660aa"
+dependencies = [
+ "hybrid-array",
+]
+
[[package]]
name = "bon"
version = "3.9.3"
@@ -354,6 +626,15 @@ dependencies = [
"syn 2.0.115",
]
+[[package]]
+name = "bs58"
+version = "0.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bf88ba1141d185c399bee5288d850d63b8369520c1eafc32a0430b5b6c287bf4"
+dependencies = [
+ "tinyvec",
+]
+
[[package]]
name = "build-rs"
version = "0.3.3"
@@ -369,6 +650,12 @@ version = "3.19.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43"
+[[package]]
+name = "byte-slice-cast"
+version = "1.2.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7575182f7272186991736b70173b0ea045398f984bf5ebbb3804736ce1330c9d"
+
[[package]]
name = "byteorder"
version = "1.5.0"
@@ -399,28 +686,34 @@ version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
+[[package]]
+name = "cfg_aliases"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f079e83a288787bcd14a6aea84cee5c87a67c5a3e660c30f557a3d24761b3527"
+
[[package]]
name = "chacha20"
-version = "0.9.1"
+version = "0.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c3613f74bd2eac03dad61bd53dbe620703d4371614fe0bc3b9f04dd36fe4e818"
+checksum = "d524456ba66e72eb8b115ff89e01e497f8e6d11d78b70b1aa13c0fbd97540a81"
dependencies = [
"cfg-if",
"cipher",
- "cpufeatures",
+ "cpufeatures 0.3.0",
+ "rand_core 0.10.0",
]
[[package]]
name = "chacha20poly1305"
-version = "0.10.1"
+version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "10cd79432192d1c0f4e1a0fef9527696cc039165d729fb41b3f4f4f354c2dc35"
+checksum = "9b89e1c441e926b9c82a8d023f6e1b7ae0adcfaa7d621814e4d60789bac751cb"
dependencies = [
"aead",
"chacha20",
"cipher",
"poly1305",
- "zeroize",
]
[[package]]
@@ -432,28 +725,35 @@ dependencies = [
"iana-time-zone",
"js-sys",
"num-traits",
+ "serde",
"wasm-bindgen",
"windows-link 0.2.1",
]
[[package]]
name = "cipher"
-version = "0.4.4"
+version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad"
+checksum = "e8cf2a2c93cd704877c0858356ed03480ff301ee950b43f1cbe4573b088bfa6c"
dependencies = [
- "crypto-common",
+ "block-buffer 0.12.1",
+ "crypto-common 0.2.2",
"inout",
- "zeroize",
]
+[[package]]
+name = "cmov"
+version = "0.5.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0c9ea0ac24bc397ab3c98583a3c9ba74fa56b09a4449bbe172b9b1ddb016027a"
+
[[package]]
name = "codegen"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "573800db6c3319bc125ddbf9b9cb001ad1602957f53642ba8d09ff3ddd4da7f1"
dependencies = [
- "indexmap",
+ "indexmap 2.14.0",
]
[[package]]
@@ -469,16 +769,37 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "531185e432bb31db1ecda541e9e7ab21468d4d844ad7505e0546a49b4945d49b"
dependencies = [
"cfg-if",
- "cpufeatures",
+ "cpufeatures 0.2.17",
"proptest",
"serde_core",
]
[[package]]
name = "const-oid"
-version = "0.9.6"
+version = "0.10.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8"
+checksum = "a6ef517f0926dd24a1582492c791b6a4818a4d94e789a334894aa15b0d12f55c"
+
+[[package]]
+name = "const_format"
+version = "0.2.36"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4481a617ad9a412be3b97c5d403fef8ed023103368908b9c50af598ff467cc1e"
+dependencies = [
+ "const_format_proc_macros",
+ "konst",
+]
+
+[[package]]
+name = "const_format_proc_macros"
+version = "0.2.34"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1d57c2eccfb16dbac1f4e61e206105db5820c9d26c3c472bc17c774259ef7744"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "unicode-xid",
+]
[[package]]
name = "constant_time_eq"
@@ -502,6 +823,12 @@ version = "0.8.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b"
+[[package]]
+name = "cpubits"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "15b85f9c39137c3a891689859392b1bd49812121d0d61c9caf00d46ed5ce06ae"
+
[[package]]
name = "cpufeatures"
version = "0.2.17"
@@ -511,6 +838,15 @@ dependencies = [
"libc",
]
+[[package]]
+name = "cpufeatures"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8b2a41393f66f16b0823bb79094d54ac5fbd34ab292ddafb9a0456ac9f87d201"
+dependencies = [
+ "libc",
+]
+
[[package]]
name = "critical-section"
version = "1.2.0"
@@ -550,12 +886,15 @@ checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5"
[[package]]
name = "crypto-bigint"
-version = "0.5.5"
+version = "0.7.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0dc92fb57ca44df6db8059111ab3af99a63d5d0f8375d9972e319a379c6bab76"
+checksum = "1a52aa3fcda4e6302a9f48734f234d35d4721b96f8fe07d073f07ce9df4f0271"
dependencies = [
- "generic-array",
- "rand_core 0.6.4",
+ "cpubits",
+ "ctutils",
+ "hybrid-array",
+ "num-traits",
+ "rand_core 0.10.0",
"subtle",
"zeroize",
]
@@ -567,20 +906,39 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3"
dependencies = [
"generic-array",
- "rand_core 0.6.4",
"typenum",
]
+[[package]]
+name = "crypto-common"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ce6e4c961d6cd6c9a86db418387425e8bdeaf05b3c8bc1411e6dca4c252f1453"
+dependencies = [
+ "hybrid-array",
+ "rand_core 0.10.0",
+]
+
+[[package]]
+name = "ctutils"
+version = "0.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7d5515a3834141de9eafb9717ad39eea8247b5674e6066c404e8c4b365d2a29e"
+dependencies = [
+ "cmov",
+ "subtle",
+]
+
[[package]]
name = "curve25519-dalek"
-version = "4.1.3"
+version = "5.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "97fb8b7c4503de7d6ae7b42ab72a5a59857b4c937ec27a3d4539dba95b5ab2be"
+checksum = "b5eed333089e2e1c1ac8c6c0398e5e2497b4c9926ca6d0365ed1e099afa5bc23"
dependencies = [
"cfg-if",
- "cpufeatures",
+ "cpufeatures 0.3.0",
"curve25519-dalek-derive",
- "digest",
+ "digest 0.11.3",
"fiat-crypto",
"rustc_version 0.4.1",
"subtle",
@@ -664,14 +1022,34 @@ dependencies = [
[[package]]
name = "der"
-version = "0.7.10"
+version = "0.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb"
+checksum = "a69dedd701da44b0536442edf09c81a64b0ab97a7a4a5e3d1971f00027cbc63d"
dependencies = [
"const-oid",
"zeroize",
]
+[[package]]
+name = "deranged"
+version = "0.5.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c"
+dependencies = [
+ "serde_core",
+]
+
+[[package]]
+name = "derivative"
+version = "2.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 1.0.109",
+]
+
[[package]]
name = "derive_more"
version = "2.0.1"
@@ -694,15 +1072,45 @@ dependencies = [
]
[[package]]
-name = "digest"
-version = "0.10.7"
+name = "digest"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066"
+dependencies = [
+ "generic-array",
+]
+
+[[package]]
+name = "digest"
+version = "0.10.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292"
+dependencies = [
+ "block-buffer 0.10.4",
+ "crypto-common 0.1.6",
+]
+
+[[package]]
+name = "digest"
+version = "0.11.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f1dd6dbb5841937940781866fa1281a1ff7bd3bf827091440879f9994983d5c2"
+dependencies = [
+ "block-buffer 0.12.1",
+ "const-oid",
+ "crypto-common 0.2.2",
+ "ctutils",
+]
+
+[[package]]
+name = "displaydoc"
+version = "0.2.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292"
+checksum = "c6232dd377dcc64799954cbd3a9bb882e9cdc1308ccd87b1c098f1fb2eaf82a8"
dependencies = [
- "block-buffer",
- "const-oid",
- "crypto-common",
- "subtle",
+ "proc-macro2",
+ "quote",
+ "syn 3.0.3",
]
[[package]]
@@ -717,25 +1125,32 @@ version = "1.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813"
+[[package]]
+name = "dyn-clone"
+version = "1.0.20"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555"
+
[[package]]
name = "ecdsa"
-version = "0.16.9"
+version = "0.17.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ee27f32b5c5292967d2d4a9d7f1e0b0aed2c15daded5a60300e4abb9d8020bca"
+checksum = "c0681a4fc24c767085329728d8dfba959af91228aa4610cca4f8ce317ba46ae0"
dependencies = [
"der",
- "digest",
+ "digest 0.11.3",
"elliptic-curve",
"rfc6979",
"signature",
"spki",
+ "zeroize",
]
[[package]]
name = "ed25519"
-version = "2.2.3"
+version = "3.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "115531babc129696a58c64a4fef0a8bf9e9698629fb97e9e40767d235cfbcd53"
+checksum = "29fcf32e6c73d1079f83ab4d782de2d81620346a5f38c6237a86a22f8368980a"
dependencies = [
"pkcs8",
"signature",
@@ -743,18 +1158,31 @@ dependencies = [
[[package]]
name = "ed25519-dalek"
-version = "2.2.0"
+version = "3.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "70e796c081cee67dc755e1a36a0a172b897fab85fc3f6bc48307991f64e4eca9"
+checksum = "6ebaa1a2bf1290ab3bfe5a7b771d050ebffab2711c19a81691c683a5144a25de"
dependencies = [
"curve25519-dalek",
"ed25519",
"serde",
- "sha2",
+ "sha2 0.11.0",
+ "signature",
"subtle",
"zeroize",
]
+[[package]]
+name = "educe"
+version = "0.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1d7bc049e1bd8cdeb31b68bbd586a9464ecf9f3944af3958a7a9d0f8b9799417"
+dependencies = [
+ "enum-ordinalize",
+ "proc-macro2",
+ "quote",
+ "syn 2.0.115",
+]
+
[[package]]
name = "either"
version = "1.15.0"
@@ -763,31 +1191,43 @@ checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719"
[[package]]
name = "elliptic-curve"
-version = "0.13.8"
+version = "0.14.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b5e6043086bf7973472e0c7dff2142ea0b680d30e18d9cc40f267efbf222bd47"
+checksum = "9d65aa39b3a5c1c9c1b745c9a019234bb7a21b77abcb4f4d266d706e2d577d65"
dependencies = [
"base16ct",
"crypto-bigint",
- "digest",
+ "crypto-common 0.2.2",
+ "digest 0.11.3",
"ff",
- "generic-array",
"group",
"hkdf",
+ "hybrid-array",
"pkcs8",
- "rand_core 0.6.4",
+ "rand_core 0.10.0",
"sec1",
"subtle",
"zeroize",
]
[[package]]
-name = "ena"
-version = "0.14.3"
+name = "enum-ordinalize"
+version = "4.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3d248bdd43ce613d87415282f69b9bb99d947d290b10962dd6c56233312c2ad5"
+checksum = "89dd01549b09589510cf0647475075d12071456586d70f5c75c98ae2a5537677"
dependencies = [
- "log",
+ "enum-ordinalize-derive",
+]
+
+[[package]]
+name = "enum-ordinalize-derive"
+version = "4.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a65863d15a4ce2888bd2f0f543cc963d3879c3a022c8ee43f6141d479a3ac815"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 3.0.3",
]
[[package]]
@@ -858,21 +1298,43 @@ version = "2.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be"
+[[package]]
+name = "fastrlp"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "139834ddba373bbdd213dffe02c8d110508dcf1726c2be27e8d1f7d7e1856418"
+dependencies = [
+ "arrayvec",
+ "auto_impl",
+ "bytes",
+]
+
+[[package]]
+name = "fastrlp"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ce8dba4714ef14b8274c371879b175aa55b16b30f269663f19d576f380018dc4"
+dependencies = [
+ "arrayvec",
+ "auto_impl",
+ "bytes",
+]
+
[[package]]
name = "ff"
-version = "0.13.1"
+version = "0.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c0b50bfb653653f9ca9095b427bed08ab8d75a137839d9ad64eb11810d5b6393"
+checksum = "a1f686ab92a9fb0eaf188f6c6c87b89490baa6fdb0db4544ba4dc47f7942489f"
dependencies = [
- "rand_core 0.6.4",
+ "rand_core 0.10.0",
"subtle",
]
[[package]]
name = "fiat-crypto"
-version = "0.2.9"
+version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d"
+checksum = "64cd1e32ddd350061ae6edb1b082d7c54915b5c672c389143b9a63403a109f24"
[[package]]
name = "find-msvc-tools"
@@ -886,6 +1348,9 @@ version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "835c052cb0c08c1acf6ffd71c022172e18723949c8282f2b9f27efbc51e64534"
dependencies = [
+ "byteorder",
+ "rand 0.8.5",
+ "rustc-hex",
"static_assertions",
]
@@ -897,13 +1362,10 @@ checksum = "1d674e81391d1e1ab681a28d99df07927c6d4aa5b027d7da16ba32d1d21ecd99"
[[package]]
name = "flume"
-version = "0.11.1"
+version = "0.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "da0e4dd2a88388a1f4ccc7c9ce104604dab68d9f408dc34cd45823d5a9069095"
+checksum = "5e139bc46ca777eb5efaf62df0ab8cc5fd400866427e56c68b22e414e53bd3be"
dependencies = [
- "futures-core",
- "futures-sink",
- "nanorand",
"spin 0.9.8",
]
@@ -919,6 +1381,21 @@ version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2"
+[[package]]
+name = "foldhash"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb"
+
+[[package]]
+name = "form_urlencoded"
+version = "1.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf"
+dependencies = [
+ "percent-encoding",
+]
+
[[package]]
name = "fs-err"
version = "3.2.2"
@@ -928,6 +1405,12 @@ dependencies = [
"autocfg",
]
+[[package]]
+name = "funty"
+version = "2.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c"
+
[[package]]
name = "futures"
version = "0.3.31"
@@ -1039,7 +1522,6 @@ checksum = "4bb6743198531e02858aeaea5398fcc883e71851fcbcb5a2f773e2fb6cb1edf2"
dependencies = [
"typenum",
"version_check",
- "zeroize",
]
[[package]]
@@ -1079,6 +1561,7 @@ dependencies = [
"js-sys",
"libc",
"r-efi 6.0.0",
+ "rand_core 0.10.0",
"wasip2",
"wasip3",
"wasm-bindgen",
@@ -1110,12 +1593,12 @@ dependencies = [
[[package]]
name = "group"
-version = "0.13.0"
+version = "0.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63"
+checksum = "7fd1a1c7a5206c5b7a3f5a0d7ccd3ff85d0c8f5133d62a02680255b0004af5f4"
dependencies = [
"ff",
- "rand_core 0.6.4",
+ "rand_core 0.10.0",
"subtle",
]
@@ -1131,20 +1614,26 @@ dependencies = [
"futures-core",
"futures-sink",
"http",
- "indexmap",
+ "indexmap 2.14.0",
"slab",
"tokio",
"tokio-util",
"tracing",
]
+[[package]]
+name = "hashbrown"
+version = "0.12.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
+
[[package]]
name = "hashbrown"
version = "0.15.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1"
dependencies = [
- "foldhash",
+ "foldhash 0.1.5",
]
[[package]]
@@ -1152,6 +1641,9 @@ name = "hashbrown"
version = "0.17.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4f467dd6dccf739c208452f8014c75c18bb8301b050ad1cfb27153803edb0f51"
+dependencies = [
+ "foldhash 0.2.0",
+]
[[package]]
name = "hashlink"
@@ -1182,20 +1674,20 @@ checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
[[package]]
name = "hkdf"
-version = "0.12.4"
+version = "0.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7b5f8eb2ad728638ea2c7d47a21db23b7b58a72ed6a38256b8a1849f15fbbdf7"
+checksum = "4aaa26c720c68b866f2c96ef5c1264b3e6f473fe5d4ce61cd44bbe913e553018"
dependencies = [
"hmac",
]
[[package]]
name = "hmac"
-version = "0.12.1"
+version = "0.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e"
+checksum = "6303bc9732ae41b04cb554b844a762b4115a61bfaa81e3e83050991eeb56863f"
dependencies = [
- "digest",
+ "digest 0.11.3",
]
[[package]]
@@ -1244,6 +1736,17 @@ version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9"
+[[package]]
+name = "hybrid-array"
+version = "0.4.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "707114b52a152fa7bdb290cd7cd5912d9467273b6d74e21b8d81aca1f8533f6b"
+dependencies = [
+ "subtle",
+ "typenum",
+ "zeroize",
+]
+
[[package]]
name = "hyper"
version = "1.7.0"
@@ -1267,6 +1770,22 @@ dependencies = [
"want",
]
+[[package]]
+name = "hyper-rustls"
+version = "0.27.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "33ca68d021ef39cf6463ab54c1d0f5daf03377b70561305bb89a8f83aab66e0f"
+dependencies = [
+ "http",
+ "hyper",
+ "hyper-util",
+ "rustls",
+ "tokio",
+ "tokio-rustls",
+ "tower-service",
+ "webpki-roots",
+]
+
[[package]]
name = "hyper-timeout"
version = "0.5.2"
@@ -1286,6 +1805,7 @@ version = "0.1.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3c6995591a8f1380fcb4ba966a252a4b29188d51d2b89e3a252f5305be65aea8"
dependencies = [
+ "base64",
"bytes",
"futures-channel",
"futures-core",
@@ -1293,7 +1813,9 @@ dependencies = [
"http",
"http-body",
"hyper",
+ "ipnet",
"libc",
+ "percent-encoding",
"pin-project-lite",
"socket2",
"tokio",
@@ -1325,6 +1847,89 @@ dependencies = [
"cc",
]
+[[package]]
+name = "icu_collections"
+version = "2.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fa68d21081c4a05d5a901a1c62add574c77048b6a1c67be3b50ce0b60d4ca513"
+dependencies = [
+ "displaydoc",
+ "potential_utf",
+ "utf8_iter",
+ "yoke",
+ "zerofrom",
+ "zerovec",
+]
+
+[[package]]
+name = "icu_locale_core"
+version = "2.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d56e28588da92eee5c3201a6eff33fabdd49b62269c8938d4ff050ce4d900deb"
+dependencies = [
+ "displaydoc",
+ "litemap",
+ "tinystr",
+ "writeable",
+ "zerovec",
+]
+
+[[package]]
+name = "icu_normalizer"
+version = "2.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "12f9cf5f235641ed274641dd81c3f28d870e276763d0797aeeab72317b1c646f"
+dependencies = [
+ "icu_collections",
+ "icu_normalizer_data",
+ "icu_properties",
+ "icu_provider",
+ "smallvec",
+ "zerovec",
+]
+
+[[package]]
+name = "icu_normalizer_data"
+version = "2.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1563da1ed3e0b3bf3d74c9b85917ac9c56464d2f57242270c09c9e752f8021a0"
+
+[[package]]
+name = "icu_properties"
+version = "2.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7e7ca276ad3145661a65914e6daf131ca5120cd3dcee8f8f3214b8875184a148"
+dependencies = [
+ "displaydoc",
+ "icu_collections",
+ "icu_locale_core",
+ "icu_properties_data",
+ "icu_provider",
+ "zerotrie",
+ "zerovec",
+]
+
+[[package]]
+name = "icu_properties_data"
+version = "2.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e590f038c1464a96894fd6d10127e90a8be4509f56ff7ecef851b15cee0b7caa"
+
+[[package]]
+name = "icu_provider"
+version = "2.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d27bbb9d3abbefac45d55f647c9de1d44aafcd1186eb91879afef17c396c3e73"
+dependencies = [
+ "displaydoc",
+ "icu_locale_core",
+ "writeable",
+ "yoke",
+ "zerofrom",
+ "zerotrie",
+ "zerovec",
+]
+
[[package]]
name = "id-arena"
version = "2.3.0"
@@ -1337,12 +1942,64 @@ version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39"
+[[package]]
+name = "idna"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de"
+dependencies = [
+ "idna_adapter",
+ "smallvec",
+ "utf8_iter",
+]
+
+[[package]]
+name = "idna_adapter"
+version = "1.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cb68373c0d6620ef8105e855e7745e18b0d00d3bdb07fb532e434244cdb9a714"
+dependencies = [
+ "icu_normalizer",
+ "icu_properties",
+]
+
+[[package]]
+name = "impl-codec"
+version = "0.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ba6a270039626615617f3f36d15fc827041df3b78c439da2cadfa47455a77f2f"
+dependencies = [
+ "parity-scale-codec",
+]
+
+[[package]]
+name = "impl-trait-for-tuples"
+version = "0.2.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a0eb5a3343abf848c0984fe4604b2b105da9539376e24fc0a3b0007411ae4fd9"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.115",
+]
+
[[package]]
name = "indenter"
version = "0.3.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "964de6e86d545b246d84badc0fef527924ace5134f30641c203ef52ba83f58d5"
+[[package]]
+name = "indexmap"
+version = "1.9.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99"
+dependencies = [
+ "autocfg",
+ "hashbrown 0.12.3",
+ "serde",
+]
+
[[package]]
name = "indexmap"
version = "2.14.0"
@@ -1357,13 +2014,19 @@ dependencies = [
[[package]]
name = "inout"
-version = "0.1.4"
+version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "879f10e63c20629ecabbb64a8010319738c66a5cd0c29b02d63d272b03751d01"
+checksum = "4250ce6452e92010fdf7268ccc5d14faa80bb12fc741938534c58f16804e03c7"
dependencies = [
- "generic-array",
+ "hybrid-array",
]
+[[package]]
+name = "ipnet"
+version = "2.12.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6a756c3fac73139e83f14c2d742155dd2b78d3ee56597b419a0579b7bdd6dd78"
+
[[package]]
name = "is_ci"
version = "1.2.0"
@@ -1376,6 +2039,24 @@ version = "1.70.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695"
+[[package]]
+name = "itertools"
+version = "0.10.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473"
+dependencies = [
+ "either",
+]
+
+[[package]]
+name = "itertools"
+version = "0.13.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186"
+dependencies = [
+ "either",
+]
+
[[package]]
name = "itertools"
version = "0.14.0"
@@ -1385,6 +2066,15 @@ dependencies = [
"either",
]
+[[package]]
+name = "itertools"
+version = "0.15.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8b4baf93f58d4425749ca49a51c50ebab072c5df6994d08fed93541c331481dc"
+dependencies = [
+ "either",
+]
+
[[package]]
name = "itoa"
version = "1.0.15"
@@ -1398,10 +2088,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "be1f93b8b1eb69c77f24bbb0afdf66f54b632ee39af40ca21c4365a1d7347e49"
dependencies = [
"jiff-static",
+ "jiff-tzdb-platform",
"log",
"portable-atomic",
"portable-atomic-util",
"serde",
+ "windows-sys 0.52.0",
]
[[package]]
@@ -1415,6 +2107,21 @@ dependencies = [
"syn 2.0.115",
]
+[[package]]
+name = "jiff-tzdb"
+version = "0.1.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "142bd39932ad231f10513df9ab62661fead8719872150b7ad02a2df79f4e141e"
+
+[[package]]
+name = "jiff-tzdb-platform"
+version = "0.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "875a5a69ac2bab1a891711cf5eccbec1ce0341ea805560dcd90b7a2e925132e8"
+dependencies = [
+ "jiff-tzdb",
+]
+
[[package]]
name = "jobserver"
version = "0.1.34"
@@ -1439,16 +2146,16 @@ dependencies = [
[[package]]
name = "k256"
-version = "0.13.4"
+version = "0.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f6e3919bbaa2945715f0bb6d3934a173d1e9a59ac23767fbaaef277265a7411b"
+checksum = "93f50113171a713f4a4231ef82eb26703607139b35dcb56241f0ceab2ae1f7d8"
dependencies = [
- "cfg-if",
+ "cpubits",
"ecdsa",
"elliptic-curve",
- "once_cell",
- "sha2",
- "signature",
+ "primeorder",
+ "sha2 0.11.0",
+ "wnaf",
]
[[package]]
@@ -1457,39 +2164,34 @@ version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ecc2af9a1119c51f12a14607e783cb977bde58bc069ff0c3da1095e635d70654"
dependencies = [
- "cpufeatures",
+ "cpufeatures 0.2.17",
]
[[package]]
-name = "lalrpop"
-version = "0.22.2"
+name = "keccak"
+version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ba4ebbd48ce411c1d10fb35185f5a51a7bfa3d8b24b4e330d30c9e3a34129501"
+checksum = "ffd9697dc4a9a62e2da93389f34400b77a28f0287711263cabb203b3ccb9c0e4"
dependencies = [
- "ascii-canvas",
- "bit-set",
- "ena",
- "itertools",
- "lalrpop-util",
- "petgraph",
- "regex",
- "regex-syntax",
- "sha3",
- "string_cache",
- "term",
- "unicode-xid",
- "walkdir",
+ "cfg-if",
+ "cpufeatures 0.3.0",
]
[[package]]
-name = "lalrpop-util"
-version = "0.22.2"
+name = "konst"
+version = "0.2.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b5baa5e9ff84f1aefd264e6869907646538a52147a755d494517a8007fb48733"
+checksum = "128133ed7824fcd73d6e7b17957c5eb7bacb885649bd8c69708b2331a10bcefb"
dependencies = [
- "rustversion",
+ "konst_macro_rules",
]
+[[package]]
+name = "konst_macro_rules"
+version = "0.2.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a4933f3f57a8e9d9da04db23fb153356ecaf00cbd14aee46279c33dc80925c37"
+
[[package]]
name = "lazy_static"
version = "1.5.0"
@@ -1531,6 +2233,12 @@ version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039"
+[[package]]
+name = "litemap"
+version = "0.8.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "47d9d19d1d6efa0109d2f65ff4c85cddd50bd572e5a00127ab10987290bcefae"
+
[[package]]
name = "lock_api"
version = "0.4.14"
@@ -1593,6 +2301,12 @@ dependencies = [
"tracing-subscriber",
]
+[[package]]
+name = "lru-slab"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154"
+
[[package]]
name = "macro-string"
version = "0.1.4"
@@ -1621,10 +2335,11 @@ checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273"
[[package]]
name = "miden-ace-codegen"
-version = "0.23.4"
+version = "0.29.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5fd45076fe4fef71f0f8b30aa0f018eb39c3086eeb5f3cafc0e12d60cd28339e"
+checksum = "1219d5caa6fcb00a96e1ec0ffc66bc03d86fc3e6367d5d8021e7ed420973d64e"
dependencies = [
+ "miden-constraint-compiler",
"miden-core",
"miden-crypto",
"thiserror",
@@ -1632,37 +2347,38 @@ dependencies = [
[[package]]
name = "miden-agglayer"
-version = "0.15.3"
+version = "0.16.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ead17cc16651de0fea5fc3ea67109ad449c7bb274ca8ff91c5b25e2be4a0c9f8"
+checksum = "3664d9d786b69d3ddef13eda4a2d8eb1138ad381ddb740367167fe721dd9cb76"
dependencies = [
"alloy-sol-types",
"fs-err",
"miden-assembly",
"miden-core",
+ "miden-core-lib",
"miden-crypto",
+ "miden-mast-package",
+ "miden-package-registry",
"miden-protocol",
+ "miden-protocol-build-utils",
"miden-standards",
"miden-utils-sync",
- "primitive-types",
- "regex",
"serde",
"serde_json",
"thiserror",
- "walkdir",
]
[[package]]
name = "miden-air"
-version = "0.23.4"
+version = "0.29.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "69f1a80330b3e3d3f98e08817dc6a5e3d90d11ab5e88aa9c0dad5d3b4202598b"
+checksum = "20a825f5e8b687969ad32107a669b9ae254ba780c18f2e43869407aa010bc5e8"
dependencies = [
"miden-ace-codegen",
"miden-core",
"miden-crypto",
- "miden-lifted-stark",
"miden-utils-indexing",
+ "p3-field",
"proptest",
"thiserror",
"tracing",
@@ -1670,9 +2386,9 @@ dependencies = [
[[package]]
name = "miden-assembly"
-version = "0.23.4"
+version = "0.29.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8582d184360be35eb2111a99245f556f43e1066ed09192fbcd0f218c466862a5"
+checksum = "3f1487a11b83df90e74469fdf61ee0b27831562972c3a4689e0c9bb7b0158ba5"
dependencies = [
"env_logger",
"log",
@@ -1688,15 +2404,13 @@ dependencies = [
[[package]]
name = "miden-assembly-syntax"
-version = "0.23.4"
+version = "0.29.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ffa307bc2cbd1f0cb74ed58981823f400a433900fb8f963762331fbb8389d5dc"
+checksum = "e77f0289745bb1887147ee67acfb47e14443579b7c7d0655660599f62545bc34"
dependencies = [
- "aho-corasick",
"env_logger",
- "lalrpop",
- "lalrpop-util",
"log",
+ "miden-assembly-syntax-cst",
"miden-core",
"miden-debug-types",
"miden-utils-diagnostics",
@@ -1711,11 +2425,23 @@ dependencies = [
"thiserror",
]
+[[package]]
+name = "miden-assembly-syntax-cst"
+version = "0.29.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7f41c6c73726eb40149ca2d174c5dd5ac9fcbcd738d851bc4c7ddb7b7f6d4561"
+dependencies = [
+ "miden-debug-types",
+ "miden-rowan",
+ "miden-utils-diagnostics",
+ "thiserror",
+]
+
[[package]]
name = "miden-block-prover"
-version = "0.15.3"
+version = "0.16.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "292ded918a0ddd056dc34db471f0bef6ac7991467d513ba505a4fcc0b1ecfac4"
+checksum = "286198b93e8ada957dc89fa920fdd442066c02bb0f28efbe0172fce03119545f"
dependencies = [
"miden-protocol",
"thiserror",
@@ -1723,9 +2449,9 @@ dependencies = [
[[package]]
name = "miden-client"
-version = "0.15.2"
+version = "0.16.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6eb783623b8f55d013833c4eef35190f44da3629d0d13a13693285004f8d3fe2"
+checksum = "c9862fde2ef60a6a1f1066834c9920f50973a2012465c451ec64381fea4654fc"
dependencies = [
"anyhow",
"async-trait",
@@ -1735,18 +2461,19 @@ dependencies = [
"gloo-timers",
"hex",
"miden-agglayer",
+ "miden-assembly-syntax",
"miden-node-proto-build",
"miden-note-transport-proto-build",
+ "miden-processor",
"miden-protocol",
- "miden-remote-prover-client",
"miden-standards",
"miden-testing",
"miden-tx",
- "miden-tx-batch-prover",
+ "miden-tx-batch",
"miette",
"prost",
"prost-types",
- "rand 0.9.2",
+ "rand 0.10.2",
"serde",
"serde_json",
"tempfile",
@@ -1763,9 +2490,9 @@ dependencies = [
[[package]]
name = "miden-client-sqlite-store"
-version = "0.15.2"
+version = "0.16.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "efb7f78f6ce83e114c49c601aa563df2863ebebea471023d70c3577177356b39"
+checksum = "b1bdb490a10753418209cf9efcb8d923b3b0a39105b9cba987e9674dd1528b66"
dependencies = [
"anyhow",
"async-trait",
@@ -1778,13 +2505,24 @@ dependencies = [
"rusqlite_migration",
"thiserror",
"tokio",
+ "tracing",
+]
+
+[[package]]
+name = "miden-constraint-compiler"
+version = "0.29.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dec5809dc0c9bc973f90cdb76116bfc3f5463d1e1ef9ce73a671141ba9f6d89a"
+dependencies = [
+ "miden-core",
+ "miden-crypto",
]
[[package]]
name = "miden-core"
-version = "0.23.4"
+version = "0.29.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "80657c32850817f5f67dcf114866495a4b055531778b7c26d0602646ce777eb8"
+checksum = "e8727b184f044ca41e61c9c2c345336d0e570f29c041bb49575520aade3c8f2d"
dependencies = [
"derive_more",
"log",
@@ -1794,36 +2532,47 @@ dependencies = [
"miden-utils-core-derive",
"miden-utils-indexing",
"miden-utils-sync",
- "num-derive",
- "num-traits",
"proptest",
- "proptest-derive",
"serde",
"thiserror",
]
[[package]]
name = "miden-core-lib"
-version = "0.23.4"
+version = "0.29.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "16410655f32f98537afc9ddf57b71cb6d1ca9d980da5f4eea164cafcaf891b2e"
+checksum = "e48b2c62c2c0144717e4219cb42aaf427d58ccd730a18ba97f2e37387a3c3193"
dependencies = [
"env_logger",
"fs-err",
"miden-assembly",
+ "miden-assembly-syntax",
"miden-core",
+ "miden-core-lib-codegen",
"miden-crypto",
+ "miden-mast-package",
"miden-package-registry",
+ "miden-precompiles",
"miden-processor",
"miden-utils-sync",
"thiserror",
]
+[[package]]
+name = "miden-core-lib-codegen"
+version = "0.29.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a0a0787b5fd63be0e6f5ee96bc531c075e96c0a787b3608fbaf60db0d82c47ed"
+dependencies = [
+ "miden-core",
+ "miden-precompiles",
+]
+
[[package]]
name = "miden-crypto"
-version = "0.25.1"
+version = "0.29.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "35198bebd353cddc25ad4aafb5f4ef9e71b283d71c787b8938c575c16974135d"
+checksum = "8202e1536816c254332798e27f5d46f4940766e01322826553b77489b78ce44d"
dependencies = [
"blake3",
"cc",
@@ -1850,14 +2599,12 @@ dependencies = [
"p3-maybe-rayon",
"p3-symmetric",
"p3-util",
- "rand 0.9.2",
- "rand_chacha",
- "rand_core 0.9.3",
- "rand_hc",
+ "rand 0.10.2",
+ "rand_chacha 0.10.0",
"rayon",
"serde",
- "sha2",
- "sha3",
+ "sha2 0.11.0",
+ "sha3 0.12.0",
"subtle",
"thiserror",
"x25519-dalek",
@@ -1865,9 +2612,9 @@ dependencies = [
[[package]]
name = "miden-crypto-derive"
-version = "0.25.1"
+version = "0.29.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9068c6554db0e051f62913575de9949841a46b96ae92d4b7d28e1fed5d8f052b"
+checksum = "dcdc897827882684b76ac4b45cae93885cc252ee69c578e6b8ce0e3954043674"
dependencies = [
"quote",
"syn 2.0.115",
@@ -1875,9 +2622,9 @@ dependencies = [
[[package]]
name = "miden-debug-types"
-version = "0.23.4"
+version = "0.29.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "956708ccb2f643db398b4b3d4f8d0baf199b1bfb5e34c8be1cd1bc811c005e8e"
+checksum = "da1533b6a269126a48dbcb14ce22c3e31be0d0284b516b84f0954261c79f1756"
dependencies = [
"memchr",
"miden-crypto",
@@ -1890,22 +2637,23 @@ dependencies = [
"serde",
"serde_spanned",
"thiserror",
+ "zerocopy",
]
[[package]]
name = "miden-field"
-version = "0.25.1"
+version = "0.29.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "379a39db52cd932a95d4017a18b712ee53ed0f86cfedf8c63ed72d687a18a191"
+checksum = "8d9c9b62982fcb18fa1b6c3bcaefc7956da30859d5e881a7f88f77dcf972db99"
dependencies = [
"miden-serde-utils",
- "num-bigint",
+ "num-bigint 0.5.1",
"p3-challenger",
"p3-field",
"p3-goldilocks",
"p3-util",
"paste",
- "rand 0.10.0",
+ "rand 0.10.2",
"serde",
"subtle",
"thiserror",
@@ -1922,11 +2670,12 @@ dependencies = [
[[package]]
name = "miden-lifted-air"
-version = "0.25.1"
+version = "0.29.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "789e0e469d1731012d8a018057317f31580611535c20d2a47c022213228cb733"
+checksum = "8314dec43170ef8549a7a00ae6dd016975d772c9bdf03c06fb8a69d39fed300f"
dependencies = [
"p3-air",
+ "p3-challenger",
"p3-field",
"p3-matrix",
"p3-util",
@@ -1935,9 +2684,9 @@ dependencies = [
[[package]]
name = "miden-lifted-stark"
-version = "0.25.1"
+version = "0.29.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f62cca91182917b22a47e150028b7c785df620a15b2974a39c64e2b1b7a889d3"
+checksum = "48166c2c8ee308ecbaea6ec8db07981973ff42f99bb39c09b914c30510942a9b"
dependencies = [
"miden-lifted-air",
"miden-stark-transcript",
@@ -1950,7 +2699,7 @@ dependencies = [
"p3-maybe-rayon",
"p3-symmetric",
"p3-util",
- "rand 0.10.0",
+ "rand 0.10.2",
"serde",
"thiserror",
"tracing",
@@ -1958,15 +2707,20 @@ dependencies = [
[[package]]
name = "miden-mast-package"
-version = "0.23.4"
+version = "0.29.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f37c21836b40785ce297d363c57740d4e33edcc411f84185a524eddadd5f53c7"
+checksum = "3e99190edc3ae5a8be14aa77aa38df7a06d28455f8e9c02f4486a653f92e21cc"
dependencies = [
+ "hashbrown 0.17.0",
+ "log",
"miden-assembly-syntax",
"miden-core",
"miden-debug-types",
+ "miden-utils-indexing",
+ "rustc-hash",
"serde",
"thiserror",
+ "zerocopy",
]
[[package]]
@@ -2007,9 +2761,9 @@ dependencies = [
[[package]]
name = "miden-node-proto-build"
-version = "0.15.0"
+version = "0.16.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ef3b301741cedd6d0b532583690bc21dbf856d4e14218c591f3924bc905c660a"
+checksum = "724a79e7663157e73de1fc19fcd6e30c7cee4e4c4189d44477922a3969797acb"
dependencies = [
"build-rs",
"codegen",
@@ -2021,9 +2775,9 @@ dependencies = [
[[package]]
name = "miden-note-transport-proto-build"
-version = "0.4.1"
+version = "0.5.0-rc.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7399c2999453c781601f16d82f328ecc695f9375e2415a05147449990f32f71f"
+checksum = "5a9d736051a42941788c6534caf8cf779b388c0ae72c9d5f0d729d2a9872d833"
dependencies = [
"fs-err",
"miette",
@@ -2033,9 +2787,9 @@ dependencies = [
[[package]]
name = "miden-package-registry"
-version = "0.23.4"
+version = "0.29.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9ece6064beb0582d1c64ba30d0c548c4b7a45f87abae6d69e22fd49c8b343258"
+checksum = "5a4b2c12e20f45e74c24ef88819c1ba36724519ce3b76d367a72e1d958df62b8"
dependencies = [
"miden-assembly-syntax",
"miden-core",
@@ -2047,16 +2801,51 @@ dependencies = [
"thiserror",
]
+[[package]]
+name = "miden-precompiles"
+version = "0.29.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "823d3e418adcbe5a8cea6843f2a167f6f5b521ab827075261974b807ef979ccf"
+dependencies = [
+ "miden-core",
+ "miden-crypto",
+]
+
+[[package]]
+name = "miden-precompiles-prover"
+version = "0.29.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "467e9fb8d805d8c4621af093a31f0a2465eefd68d63208b49f5663eaf030f89f"
+dependencies = [
+ "miden-ace-codegen",
+ "miden-air",
+ "miden-core",
+ "miden-crypto",
+ "miden-lifted-air",
+ "miden-lifted-stark",
+ "miden-precompiles",
+ "miden-serde-utils",
+ "ruint",
+ "serde",
+ "serde-wincode",
+ "thiserror",
+ "tracing",
+ "wincode",
+]
+
[[package]]
name = "miden-processor"
-version = "0.23.4"
+version = "0.29.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ea972ca9e45dbf26aa396367e8508db0f7292adea6f6ddf8d39d0e334285fe2b"
+checksum = "1a350061ef4f412639475c5bd8d68ae665ce9fd34ffc768c6082acd3972f791c"
dependencies = [
- "itertools",
+ "hashbrown 0.17.0",
+ "itertools 0.15.0",
"miden-air",
"miden-core",
"miden-debug-types",
+ "miden-mast-package",
+ "miden-precompiles",
"miden-utils-diagnostics",
"miden-utils-indexing",
"paste",
@@ -2067,9 +2856,9 @@ dependencies = [
[[package]]
name = "miden-project"
-version = "0.23.4"
+version = "0.29.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5320e7e5b562359bd6161ac752dfe43dd4f69bb06e87f94a21a27bb656e5a20d"
+checksum = "dbfb83107a2016867e3650e0b34bb8bc356ab2a039dce1992b808bab73da7385"
dependencies = [
"miden-assembly-syntax",
"miden-core",
@@ -2084,13 +2873,13 @@ dependencies = [
[[package]]
name = "miden-protocol"
-version = "0.15.3"
+version = "0.16.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "66340243e37da5936cb278a8dd11037813f1dc6731c2fc866703b76ed465ebc3"
+checksum = "46b3c270a0fabe3618006176f1b9600e9b5b59a53a8e26ce6afcf65f1b02cb16"
dependencies = [
"bech32",
"fs-err",
- "getrandom 0.3.4",
+ "getrandom 0.4.2",
"miden-assembly",
"miden-assembly-syntax",
"miden-core",
@@ -2098,89 +2887,96 @@ dependencies = [
"miden-crypto",
"miden-crypto-derive",
"miden-mast-package",
+ "miden-package-registry",
"miden-processor",
+ "miden-protocol-build-utils",
"miden-utils-sync",
"miden-verifier",
- "rand 0.9.2",
- "rand_chacha",
+ "rand 0.10.2",
+ "rand_chacha 0.10.0",
"rand_xoshiro",
"regex",
"semver 1.0.27",
"serde",
"thiserror",
"toml 1.1.2+spec-1.1.0",
+]
+
+[[package]]
+name = "miden-protocol-build-utils"
+version = "0.16.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ffb3076db13d4b6d12bf01d96ffc3fea400f415fce26467d81081400dcdc59c9"
+dependencies = [
+ "fs-err",
+ "miden-assembly",
+ "miden-core",
+ "miden-mast-package",
+ "miden-package-registry",
+ "miden-project",
+ "regex",
"walkdir",
]
[[package]]
name = "miden-prover"
-version = "0.23.4"
+version = "0.29.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1a91bcc00840b01126cd54e3b68ce3235def2ed48803f2eeb36a035d6951fbc1"
+checksum = "65268275b38d5add4cb2542b2e2f2047047a96d6f7bac3b43678e78e36d51794"
dependencies = [
- "bincode",
"miden-air",
"miden-core",
"miden-crypto",
+ "miden-precompiles-prover",
"miden-processor",
"serde",
+ "serde-wincode",
"tracing",
]
[[package]]
-name = "miden-remote-prover-client"
-version = "0.15.0"
+name = "miden-rowan"
+version = "0.16.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f2acdb9494689feeec0f60e3b61901b5eb2362b49b993b4cbc3eb75ad83514dd"
+checksum = "c13695bf99aabaa21d6572b807c66bb26251aa3d9b75e828b3c99b97a3b1ce7e"
dependencies = [
- "build-rs",
- "fs-err",
- "getrandom 0.4.2",
- "miden-node-proto-build",
- "miden-protocol",
- "miden-tx",
- "miette",
- "prost",
- "thiserror",
- "tokio",
- "tonic",
- "tonic-prost",
- "tonic-prost-build",
- "tonic-web-wasm-client",
+ "hashbrown 0.17.0",
+ "rustc-hash",
]
[[package]]
name = "miden-serde-utils"
-version = "0.25.1"
+version = "0.29.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d78cd1d4fcad937312e544f7d53423485e453598aa4fb989d2b6374027a8c136"
+checksum = "e1f37aa58c6ec69c19ed0edbdba0322c2112356fbbc131a42b5994462a2b794d"
dependencies = [
"p3-field",
"p3-goldilocks",
+ "wincode",
]
[[package]]
name = "miden-standards"
-version = "0.15.3"
+version = "0.16.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c7c7146b028e637f4079b5bdefeefc54d7d6e47a805451fa0a18859d19efa2ff"
+checksum = "6be6c71d2114157431f3d5860450b189e902a1fdf3351c8a7d91be8906fd35ce"
dependencies = [
"bon",
- "fs-err",
"miden-assembly",
"miden-core-lib",
+ "miden-package-registry",
"miden-protocol",
- "rand 0.9.2",
- "regex",
+ "miden-protocol-build-utils",
+ "primitive-types 0.14.0",
+ "rand 0.10.2",
"thiserror",
- "walkdir",
]
[[package]]
name = "miden-stark-transcript"
-version = "0.25.1"
+version = "0.29.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "05901db2e30d3954243960fe21cea7fbec39f97c27774b56fd5031c28c4881ba"
+checksum = "5b3238f74844f9826a9ee41d39b1d726426fd298a5aba16c34346471d7d184db"
dependencies = [
"p3-challenger",
"p3-field",
@@ -2190,9 +2986,9 @@ dependencies = [
[[package]]
name = "miden-stateful-hasher"
-version = "0.25.1"
+version = "0.29.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "faeb47a90c55c5d45051d23cf691588804dd531995b4582c79108b64e445a905"
+checksum = "88a5545db3e83e041e365c7bb1ba5b8c5953b45a7ad32d698d3b4c30e45727a8"
dependencies = [
"p3-field",
"p3-symmetric",
@@ -2200,12 +2996,12 @@ dependencies = [
[[package]]
name = "miden-testing"
-version = "0.15.3"
+version = "0.16.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d4096fc44a4c88f37405be284e25efdce194d6051e9472ae136ab9249659433c"
+checksum = "dba121f560380c4cf70ba422098feedc512d897f41130163255a7dfd94c9aab3"
dependencies = [
"anyhow",
- "itertools",
+ "itertools 0.15.0",
"miden-block-prover",
"miden-core-lib",
"miden-crypto",
@@ -2213,9 +3009,9 @@ dependencies = [
"miden-protocol",
"miden-standards",
"miden-tx",
- "miden-tx-batch-prover",
- "rand 0.9.2",
- "rand_chacha",
+ "miden-tx-batch",
+ "rand 0.10.2",
+ "rand_chacha 0.10.0",
"thiserror",
]
@@ -2226,39 +3022,44 @@ dependencies = [
"miden-client",
"miden-client-sqlite-store",
"miden-protocol",
- "rand 0.9.2",
+ "rand 0.10.2",
+ "rust-client",
"tokio",
]
[[package]]
name = "miden-tx"
-version = "0.15.3"
+version = "0.16.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "94092b45bc0abc656af25473c9807d1e6cee8e682c58d3b1186f0bd0fb6471fb"
+checksum = "9dcc3e4708af1d15dc13baec1162fd9c1b9663fd4eeac1c2d2f79ef4202d018c"
dependencies = [
+ "bon",
+ "miden-agglayer",
"miden-processor",
"miden-protocol",
"miden-prover",
"miden-standards",
- "miden-verifier",
"thiserror",
]
[[package]]
-name = "miden-tx-batch-prover"
-version = "0.15.3"
+name = "miden-tx-batch"
+version = "0.16.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f60add2b40559352661bc86970541f88ffcf98c528f301295f9dc3bf15481b46"
+checksum = "5db9201b5b56c96dbed1cd5540e7344530a83348fe82e31cabce2b0d963c1dbe"
dependencies = [
+ "miden-processor",
"miden-protocol",
- "miden-tx",
+ "miden-prover",
+ "miden-verifier",
+ "thiserror",
]
[[package]]
name = "miden-utils-core-derive"
-version = "0.23.4"
+version = "0.29.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d0b1ee4662beb049a824e11bb21f95a79746c52874967983c9999f1b19a2f471"
+checksum = "d0e529fda0dc73e1fdb56d0c29ca92780a4f3d766cf5bf2bc688b95b0f8a8623"
dependencies = [
"proc-macro2",
"quote",
@@ -2267,11 +3068,10 @@ dependencies = [
[[package]]
name = "miden-utils-diagnostics"
-version = "0.23.4"
+version = "0.29.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9fdc1cd4eda372e1c4b99b9c3677e9b1f87a4d2e362a9f4b8f904273d395efc9"
+checksum = "197029df3c899525204f4a1f7e5b6e82c69427489b36032ae56fbcf53f9d6b2f"
dependencies = [
- "miden-crypto",
"miden-debug-types",
"miden-miette",
"tracing",
@@ -2279,11 +3079,11 @@ dependencies = [
[[package]]
name = "miden-utils-indexing"
-version = "0.23.4"
+version = "0.29.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c31444125649f4dad9cde647f614309b6be4f918fed276ada4eb99c01e8b9ca7"
+checksum = "5b87e9b3f949c27e56dc390d9c9e679f2886e6ea6adfbc7158de7f377c1b6940"
dependencies = [
- "miden-crypto",
+ "miden-serde-utils",
"proptest",
"serde",
"thiserror",
@@ -2291,9 +3091,9 @@ dependencies = [
[[package]]
name = "miden-utils-sync"
-version = "0.23.4"
+version = "0.29.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "807c8ae625b7652ae7246b225c907c05da72927c31a0fd71c835c4f80931e92e"
+checksum = "e9e911afc22a03dcf439a0d1d20a67631169a6757100230b257c57e546dd5c3a"
dependencies = [
"lock_api",
"loom",
@@ -2303,24 +3103,26 @@ dependencies = [
[[package]]
name = "miden-verifier"
-version = "0.23.4"
+version = "0.29.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ec5556dac919a1c13edeb2bd7181fc6a4c2ce52764a3e518bcdcd9ed48e5b38e"
+checksum = "6aaf6b63aa6300832a302207f3e93932faae299679e75a0c993de6b1c02f5cde"
dependencies = [
- "bincode",
"miden-air",
"miden-core",
"miden-crypto",
+ "miden-precompiles",
+ "miden-precompiles-prover",
+ "miden-serde-utils",
"serde",
+ "serde-wincode",
"thiserror",
- "tracing",
]
[[package]]
name = "midenc-hir-type"
-version = "0.6.1"
+version = "0.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1ff0511aa2201f7098995e38a3c97a319d379c3b2d26fb83677b21b71f61a7b4"
+checksum = "f72909a4bae8dca4bbd34c28dcbcdff595afc47e48c312a683108f7452bd270b"
dependencies = [
"miden-formatting",
"miden-serde-utils",
@@ -2386,21 +3188,6 @@ version = "0.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1d87ecb2933e8aeadb3e3a02b828fed80a7528047e68b4f424523a0981a3a084"
-[[package]]
-name = "nanorand"
-version = "0.7.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6a51313c5820b0b02bd422f4b44776fbf47961755c74ce64afc73bfad10226c3"
-dependencies = [
- "getrandom 0.2.16",
-]
-
-[[package]]
-name = "new_debug_unreachable"
-version = "1.0.6"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086"
-
[[package]]
name = "nu-ansi-term"
version = "0.50.3"
@@ -2416,7 +3203,7 @@ version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "35bd024e8b2ff75562e5f34e7f4905839deb4b22955ef5e73d2fea1b9813cb23"
dependencies = [
- "num-bigint",
+ "num-bigint 0.4.6",
"num-complex",
"num-integer",
"num-iter",
@@ -2434,6 +3221,16 @@ dependencies = [
"num-traits",
]
+[[package]]
+name = "num-bigint"
+version = "0.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "93e7820bc0a80a0238e650327316f929ba18d5be054b647490a3a6a339f3e7c0"
+dependencies = [
+ "num-integer",
+ "num-traits",
+]
+
[[package]]
name = "num-complex"
version = "0.4.6"
@@ -2444,15 +3241,10 @@ dependencies = [
]
[[package]]
-name = "num-derive"
-version = "0.4.2"
+name = "num-conv"
+version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202"
-dependencies = [
- "proc-macro2",
- "quote",
- "syn 2.0.115",
-]
+checksum = "521739c6d2bac4aa25192232afe6841231376b2b26d4d9fae5ecf8ca5772e441"
[[package]]
name = "num-integer"
@@ -2480,7 +3272,7 @@ version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824"
dependencies = [
- "num-bigint",
+ "num-bigint 0.4.6",
"num-integer",
"num-traits",
]
@@ -2530,12 +3322,6 @@ version = "1.70.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe"
-[[package]]
-name = "opaque-debug"
-version = "0.3.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381"
-
[[package]]
name = "openssl-probe"
version = "0.1.6"
@@ -2550,9 +3336,9 @@ checksum = "9c6901729fa79e91a0913333229e9ca5dc725089d1c363b2f4b4760709dc4a52"
[[package]]
name = "p3-air"
-version = "0.5.2"
+version = "0.6.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9f2ec9cbfc642fc5173817287c3f8b789d07743b5f7e812d058b7a03e344f9ab"
+checksum = "ddb1be05c0d6f691afe0c9f468018a9a37cfa904dee78a8081ec96eb3cdd88e8"
dependencies = [
"p3-field",
"p3-matrix",
@@ -2561,9 +3347,9 @@ dependencies = [
[[package]]
name = "p3-blake3"
-version = "0.5.2"
+version = "0.6.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b667f43b19499dd939c9e2553aa95688936a88360d50117dae3c8848d07dbc70"
+checksum = "6f202f5fbcceb6f56f783d98efb5de27e5a171470e3364de97b0923b39c87ab5"
dependencies = [
"blake3",
"p3-symmetric",
@@ -2572,9 +3358,9 @@ dependencies = [
[[package]]
name = "p3-challenger"
-version = "0.5.2"
+version = "0.6.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4a0b490c745a7d2adeeafff06411814c8078c432740162332b3cd71be0158a76"
+checksum = "84d5d5e1ecf2c80b09b48ce870e8abd08b643454101c5dc9d0fd71bfbd78224d"
dependencies = [
"p3-field",
"p3-maybe-rayon",
@@ -2586,42 +3372,42 @@ dependencies = [
[[package]]
name = "p3-dft"
-version = "0.5.2"
+version = "0.6.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "55301e91544440254977108b85c32c09d7ea05f2f0dd61092a2825339906a4a7"
+checksum = "4321a952da2721ecd85ca593ea189798dfb4e439a2cc1378ce1442091880f173"
dependencies = [
- "itertools",
+ "itertools 0.15.0",
"p3-field",
"p3-matrix",
"p3-maybe-rayon",
"p3-util",
- "spin 0.10.0",
+ "spin 0.12.3",
"tracing",
]
[[package]]
name = "p3-field"
-version = "0.5.2"
+version = "0.6.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "85affca7fc983889f260655c4cf74163eebb94605f702e4b6809ead707cba54f"
+checksum = "53db75d38e04fc255826f388eca9d05976733dc9754aa3db411bc9ea1a37c1a0"
dependencies = [
- "itertools",
- "num-bigint",
+ "itertools 0.15.0",
+ "num-bigint 0.5.1",
"p3-maybe-rayon",
"p3-util",
"paste",
- "rand 0.10.0",
+ "rand 0.10.2",
"serde",
"tracing",
]
[[package]]
name = "p3-goldilocks"
-version = "0.5.2"
+version = "0.6.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0ca1081f5c47b940f2d75a11c04f62ea1cc58a5d480dd465fef3861c045c63cd"
+checksum = "d03b3f31080df31be723b876709246f8f1e532e1c5b82efb5281d705c8304c63"
dependencies = [
- "num-bigint",
+ "num-bigint 0.5.1",
"p3-challenger",
"p3-dft",
"p3-field",
@@ -2631,15 +3417,16 @@ dependencies = [
"p3-symmetric",
"p3-util",
"paste",
- "rand 0.10.0",
+ "rand 0.10.2",
"serde",
+ "spin 0.12.3",
]
[[package]]
name = "p3-keccak"
-version = "0.5.2"
+version = "0.6.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ebcf27615ece1995e4fcf4c69740f1cf515d1481367a20b4b3ce7f4f1b8d70f7"
+checksum = "ae50c8c37eb847c660298fb275e53c025c49b2623a8cfabf67f5322258b2b4db"
dependencies = [
"p3-symmetric",
"p3-util",
@@ -2648,49 +3435,49 @@ dependencies = [
[[package]]
name = "p3-matrix"
-version = "0.5.2"
+version = "0.6.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "53428126b009071563d1d07305a9de8be0d21de00b57d2475289ee32ffca6577"
+checksum = "473eb920c446a6f4536e0d3528fbdca2a23c0e24e1d0d7767452e6d385dd335c"
dependencies = [
- "itertools",
+ "itertools 0.15.0",
"p3-field",
"p3-maybe-rayon",
"p3-util",
- "rand 0.10.0",
+ "rand 0.10.2",
"serde",
"tracing",
]
[[package]]
name = "p3-maybe-rayon"
-version = "0.5.2"
+version = "0.6.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "082bf467011c06c768c579ec6eb9accb5e1e62108891634cc770396e917f978a"
+checksum = "e6fddfd435f96394769414cf5590b77058aa506659bf20d6592e9d1989e04440"
dependencies = [
"rayon",
]
[[package]]
name = "p3-mds"
-version = "0.5.2"
+version = "0.6.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "35209e6214102ea6ec6b8cb1b9c15a9b8e597a39f9173597c957f123bced81b3"
+checksum = "551ba0ab2cccd89f85a99450224898aff224e323bbf61f777ba6344f0896ef10"
dependencies = [
"p3-dft",
"p3-field",
"p3-symmetric",
"p3-util",
- "rand 0.10.0",
+ "rand 0.10.2",
]
[[package]]
name = "p3-monty-31"
-version = "0.5.2"
+version = "0.6.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ffa8c99ec50c035020bbf5457c6a729ba6a975719c1a8dd3f16421081e4f650c"
+checksum = "871f635f7340cd0868b17e43e0c98fefdafdaed90469d0725caf6d8372a2a47c"
dependencies = [
- "itertools",
- "num-bigint",
+ "itertools 0.15.0",
+ "num-bigint 0.5.1",
"p3-dft",
"p3-field",
"p3-matrix",
@@ -2701,43 +3488,44 @@ dependencies = [
"p3-symmetric",
"p3-util",
"paste",
- "rand 0.10.0",
+ "rand 0.10.2",
"serde",
- "spin 0.10.0",
+ "spin 0.12.3",
"tracing",
]
[[package]]
name = "p3-poseidon1"
-version = "0.5.2"
+version = "0.6.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6a018b618e3fa0aec8be933b1d8e404edd23f46991f6bf3f5c2f3f95e9413fe9"
+checksum = "8d0d304e9a1f29c0d66534aa84e69528e2118351fdce08dcf5898af4e0fecc32"
dependencies = [
"p3-field",
+ "p3-mds",
"p3-symmetric",
- "rand 0.10.0",
+ "rand 0.10.2",
]
[[package]]
name = "p3-poseidon2"
-version = "0.5.2"
+version = "0.6.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "256a668a9ba916f8767552f13d0ba50d18968bc74a623bfdafa41e2970c944d0"
+checksum = "43eb8a73a26d14becaed1c67c3e8a047e4311d7909b402383c82ca9643ba17c6"
dependencies = [
"p3-field",
"p3-mds",
"p3-symmetric",
"p3-util",
- "rand 0.10.0",
+ "rand 0.10.2",
]
[[package]]
name = "p3-symmetric"
-version = "0.5.2"
+version = "0.6.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6c60a71a1507c13611b0f2b0b6e83669fd5b76f8e3115bcbced5ccfdf3ca7807"
+checksum = "2015ea80cad969b6aabf27a04884286fe1354393b166d968ee0d80a95126b2a4"
dependencies = [
- "itertools",
+ "itertools 0.15.0",
"p3-field",
"p3-util",
"serde",
@@ -2745,13 +3533,40 @@ dependencies = [
[[package]]
name = "p3-util"
-version = "0.5.2"
+version = "0.6.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f8b766b9e9254bf3fa98d76e42cf8a5b30628c182dfd5272d270076ee12f0fc0"
+checksum = "6c5466fc40e6df89d3b291a2eff16b33e68e8571207790370137ec18090aadab"
dependencies = [
"rayon",
"serde",
- "transpose",
+]
+
+[[package]]
+name = "parity-scale-codec"
+version = "3.7.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "799781ae679d79a948e13d4824a40970bfa500058d245760dd857301059810fa"
+dependencies = [
+ "arrayvec",
+ "bitvec",
+ "byte-slice-cast",
+ "const_format",
+ "impl-trait-for-tuples",
+ "parity-scale-codec-derive",
+ "rustversion",
+ "serde",
+]
+
+[[package]]
+name = "parity-scale-codec-derive"
+version = "3.7.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "34b4653168b563151153c9e4c08ebed57fb8262bebfa79711552fa983c623e7a"
+dependencies = [
+ "proc-macro-crate",
+ "proc-macro2",
+ "quote",
+ "syn 2.0.115",
]
[[package]]
@@ -2783,6 +3598,12 @@ version = "1.0.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a"
+[[package]]
+name = "pastey"
+version = "0.2.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2ee67f1008b1ba2321834326597b8e186293b049a023cdef258527550b9935b4"
+
[[package]]
name = "percent-encoding"
version = "2.3.2"
@@ -2790,22 +3611,23 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220"
[[package]]
-name = "petgraph"
-version = "0.7.1"
+name = "pest"
+version = "2.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3672b37090dbd86368a4145bc067582552b29c27377cad4e0a306c97f9bd7772"
+checksum = "5a07a60cc7a4d00c91f95c685609d1d2f79050e6804b70ebedd7650f0b839bcf"
dependencies = [
- "fixedbitset",
- "indexmap",
+ "memchr",
+ "ucd-trie",
]
[[package]]
-name = "phf_shared"
-version = "0.11.3"
+name = "petgraph"
+version = "0.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "67eabc2ef2a60eb7faa00097bd1ffdb5bd28e62bf39990626a582201b7a754e5"
+checksum = "3672b37090dbd86368a4145bc067582552b29c27377cad4e0a306c97f9bd7772"
dependencies = [
- "siphasher",
+ "fixedbitset",
+ "indexmap 2.14.0",
]
[[package]]
@@ -2842,9 +3664,9 @@ checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
[[package]]
name = "pkcs8"
-version = "0.10.2"
+version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7"
+checksum = "451913da69c775a56034ea8d9003d27ee8948e12443eae7c038ba100a4f21cb7"
dependencies = [
"der",
"spki",
@@ -2858,12 +3680,11 @@ checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c"
[[package]]
name = "poly1305"
-version = "0.8.0"
+version = "0.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8159bd90725d2df49889a078b54f4f79e87f1f8a8444194cdca81d38f5393abf"
+checksum = "6e2d0073b297041425c7c3df6eb4792d598a15323fe63346852b092eca02904c"
dependencies = [
- "cpufeatures",
- "opaque-debug",
+ "cpufeatures 0.3.0",
"universal-hash",
]
@@ -2883,28 +3704,74 @@ dependencies = [
]
[[package]]
-name = "ppv-lite86"
-version = "0.2.21"
+name = "potential_utf"
+version = "0.1.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d83eb9bc6d8e5cf568e7a1101d60ee05e81ed50ea106026f3d18deeb046d7661"
+dependencies = [
+ "zerovec",
+]
+
+[[package]]
+name = "powerfmt"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391"
+
+[[package]]
+name = "ppv-lite86"
+version = "0.2.21"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9"
+dependencies = [
+ "zerocopy",
+]
+
+[[package]]
+name = "prettyplease"
+version = "0.2.37"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b"
+dependencies = [
+ "proc-macro2",
+ "syn 2.0.115",
+]
+
+[[package]]
+name = "primefield"
+version = "0.14.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c555a6e4eb7d4e158fcb028c835c3b8642206ddc279b5c6b202ef9a8bdb592f4"
+dependencies = [
+ "crypto-bigint",
+ "crypto-common 0.2.2",
+ "ff",
+ "rand_core 0.10.0",
+ "subtle",
+ "zeroize",
+]
+
+[[package]]
+name = "primeorder"
+version = "0.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9"
+checksum = "5c9f42978c78a00e3d68f69fc03e57a234debae69da4020a4fb588fcdcd07b06"
dependencies = [
- "zerocopy",
+ "elliptic-curve",
+ "primefield",
+ "serdect",
+ "wnaf",
]
[[package]]
-name = "precomputed-hash"
-version = "0.1.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c"
-
-[[package]]
-name = "prettyplease"
-version = "0.2.37"
+name = "primitive-types"
+version = "0.12.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b"
+checksum = "0b34d9fd68ae0b74a41b21c03c2f62847aa0ffea044eee893b4c140b37e244e2"
dependencies = [
- "proc-macro2",
- "syn 2.0.115",
+ "fixed-hash",
+ "impl-codec",
+ "uint 0.9.5",
]
[[package]]
@@ -2914,7 +3781,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "721a1da530b5a2633218dc9f75713394c983c352be88d2d7c9ee85e2c4c21794"
dependencies = [
"fixed-hash",
- "uint",
+ "uint 0.10.0",
]
[[package]]
@@ -2924,10 +3791,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "93980406f12d9f8140ed5abe7155acb10bb1e69ea55c88960b9c2f117445ef96"
dependencies = [
"equivalent",
- "indexmap",
+ "indexmap 2.14.0",
"serde",
]
+[[package]]
+name = "proc-macro-crate"
+version = "3.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e67ba7e9b2b56446f1d419b1d807906278ffa1a658a8a5d8a39dcb1f5a78614f"
+dependencies = [
+ "toml_edit",
+]
+
[[package]]
name = "proc-macro-error-attr2"
version = "2.0.0"
@@ -2952,9 +3828,9 @@ dependencies = [
[[package]]
name = "proc-macro2"
-version = "1.0.101"
+version = "1.0.107"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "89ae43fd86e4158d6db51ad8e2b80f313af9cc74f5c0e03ccb87de09998732de"
+checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9"
dependencies = [
"unicode-ident",
]
@@ -2968,7 +3844,7 @@ dependencies = [
"bitflags",
"num-traits",
"rand 0.9.2",
- "rand_chacha",
+ "rand_chacha 0.9.0",
"rand_xorshift",
"regex-syntax",
"unarray",
@@ -3002,7 +3878,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac6c3320f9abac597dcbc668774ef006702672474aad53c6d596b62e487b40b1"
dependencies = [
"heck",
- "itertools",
+ "itertools 0.14.0",
"log",
"multimap",
"once_cell",
@@ -3024,7 +3900,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "27c6023962132f4b30eb4c172c91ce92d933da334c59c23cddee82358ddafb0b"
dependencies = [
"anyhow",
- "itertools",
+ "itertools 0.14.0",
"proc-macro2",
"quote",
"syn 2.0.115",
@@ -3084,7 +3960,7 @@ version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f5df7e552bc7edd075f5783a87fbfc21d6a546e32c16985679c488c18192d83"
dependencies = [
- "indexmap",
+ "indexmap 2.14.0",
"log",
"priority-queue",
"rustc-hash",
@@ -3112,11 +3988,67 @@ dependencies = [
"pulldown-cmark",
]
+[[package]]
+name = "quinn"
+version = "0.11.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0c1a41e437b6bbd489372cd4971de128e85c855f56c57f283d20ff016cf7c0a8"
+dependencies = [
+ "bytes",
+ "cfg_aliases",
+ "pin-project-lite",
+ "quinn-proto",
+ "quinn-udp",
+ "rustc-hash",
+ "rustls",
+ "socket2",
+ "thiserror",
+ "tokio",
+ "tracing",
+ "web-time",
+]
+
+[[package]]
+name = "quinn-proto"
+version = "0.11.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "04759210543be93709136e28212294a659ef5001836ff4eab4d663e4529bba83"
+dependencies = [
+ "bytes",
+ "getrandom 0.4.2",
+ "lru-slab",
+ "rand 0.10.2",
+ "rand_pcg",
+ "ring",
+ "rustc-hash",
+ "rustls",
+ "rustls-pki-types",
+ "slab",
+ "thiserror",
+ "tinyvec",
+ "tracing",
+ "web-time",
+]
+
+[[package]]
+name = "quinn-udp"
+version = "0.5.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "35a133f956daabe89a61a685c2649f13d82d5aa4bd5d12d1277e1072a21c0694"
+dependencies = [
+ "cfg_aliases",
+ "libc",
+ "once_cell",
+ "socket2",
+ "tracing",
+ "windows-sys 0.61.2",
+]
+
[[package]]
name = "quote"
-version = "1.0.41"
+version = "1.0.47"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ce25767e7b499d1b604768e7cde645d14cc8584231ea6b295e9c9eb22c02e1d1"
+checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001"
dependencies = [
"proc-macro2",
]
@@ -3133,12 +4065,20 @@ version = "6.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf"
+[[package]]
+name = "radium"
+version = "0.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09"
+
[[package]]
name = "rand"
version = "0.8.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
dependencies = [
+ "libc",
+ "rand_chacha 0.3.1",
"rand_core 0.6.4",
]
@@ -3148,19 +4088,31 @@ version = "0.9.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1"
dependencies = [
- "rand_chacha",
+ "rand_chacha 0.9.0",
"rand_core 0.9.3",
]
[[package]]
name = "rand"
-version = "0.10.0"
+version = "0.10.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bc266eb313df6c5c09c1c7b1fbe2510961e5bcd3add930c1e31f7ed9da0feff8"
+checksum = "c7f5fa3a058cd35567ef9bfa5e75732bee0f9e4c55fa90477bef2dfcdbc4be80"
dependencies = [
+ "chacha20",
+ "getrandom 0.4.2",
"rand_core 0.10.0",
]
+[[package]]
+name = "rand_chacha"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
+dependencies = [
+ "ppv-lite86",
+ "rand_core 0.6.4",
+]
+
[[package]]
name = "rand_chacha"
version = "0.9.0"
@@ -3171,6 +4123,16 @@ dependencies = [
"rand_core 0.9.3",
]
+[[package]]
+name = "rand_chacha"
+version = "0.10.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3e6af7f3e25ded52c41df4e0b1af2d047e45896c2f3281792ed68a1c243daedb"
+dependencies = [
+ "ppv-lite86",
+ "rand_core 0.10.0",
+]
+
[[package]]
name = "rand_core"
version = "0.6.4"
@@ -3196,12 +4158,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0c8d0fd677905edcbeedbf2edb6494d676f0e98d54d5cf9bda0b061cb8fb8aba"
[[package]]
-name = "rand_hc"
-version = "0.3.2"
+name = "rand_pcg"
+version = "0.10.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7b363d4f6370f88d62bf586c80405657bde0f0e1b8945d47d2ad59b906cb4f54"
+checksum = "caa0f4137e1c0a72f4c651489402276c8e8e1cf081f3b0ba156d2cbeef09e86a"
dependencies = [
- "rand_core 0.6.4",
+ "rand_core 0.10.0",
]
[[package]]
@@ -3215,18 +4177,18 @@ dependencies = [
[[package]]
name = "rand_xoshiro"
-version = "0.7.0"
+version = "0.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f703f4665700daf5512dcca5f43afa6af89f09db47fb56be587f80636bda2d41"
+checksum = "662effc7698e08ea324d3acccf8d9d7f7bf79b9785e270a174ea36e56900c91d"
dependencies = [
- "rand_core 0.9.3",
+ "rand_core 0.10.0",
]
[[package]]
name = "rayon"
-version = "1.11.0"
+version = "1.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "368f01d005bf8fd9b1206fb6fa653e6c4a81ceb1466406b81792d87c5677a58f"
+checksum = "fb39b166781f92d482534ef4b4b1b2568f42613b53e5b6c160e24cfbfa30926d"
dependencies = [
"either",
"rayon-core",
@@ -3251,6 +4213,26 @@ dependencies = [
"bitflags",
]
+[[package]]
+name = "ref-cast"
+version = "1.0.27"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7e440fb4e4b4147295338efb76001ab9e4efc0e5839df2c47fc5ac2381d365c3"
+dependencies = [
+ "ref-cast-impl",
+]
+
+[[package]]
+name = "ref-cast-impl"
+version = "1.0.27"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "92ecd8964f8453721699a1ed72037b0db49ce2f5a5138486ee89bed6f67cdf3a"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 3.0.3",
+]
+
[[package]]
name = "regex"
version = "1.12.2"
@@ -3280,14 +4262,52 @@ version = "0.8.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58"
+[[package]]
+name = "reqwest"
+version = "0.12.28"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "eddd3ca559203180a307f12d114c268abf583f59b03cb906fd0b3ff8646c1147"
+dependencies = [
+ "base64",
+ "bytes",
+ "futures-core",
+ "http",
+ "http-body",
+ "http-body-util",
+ "hyper",
+ "hyper-rustls",
+ "hyper-util",
+ "js-sys",
+ "log",
+ "percent-encoding",
+ "pin-project-lite",
+ "quinn",
+ "rustls",
+ "rustls-pki-types",
+ "serde",
+ "serde_json",
+ "serde_urlencoded",
+ "sync_wrapper",
+ "tokio",
+ "tokio-rustls",
+ "tower",
+ "tower-http",
+ "tower-service",
+ "url",
+ "wasm-bindgen",
+ "wasm-bindgen-futures",
+ "web-sys",
+ "webpki-roots",
+]
+
[[package]]
name = "rfc6979"
-version = "0.4.0"
+version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f8dd2a808d456c4a54e300a23e9f5a67e122c3024119acbfd73e3bf664491cb2"
+checksum = "b4a459cddafb3fe76b31fd8f1108007566c40301feb64dc7b54656eb7388172b"
dependencies = [
+ "crypto-bigint",
"hmac",
- "subtle",
]
[[package]]
@@ -3304,15 +4324,39 @@ dependencies = [
"windows-sys 0.52.0",
]
+[[package]]
+name = "rlp"
+version = "0.5.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bb919243f34364b6bd2fc10ef797edbfa75f33c252e7998527479c6d6b47e1ec"
+dependencies = [
+ "bytes",
+ "rustc-hex",
+]
+
[[package]]
name = "ruint"
-version = "1.17.2"
+version = "1.20.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c141e807189ad38a07276942c6623032d3753c8859c146104ac2e4d68865945a"
+checksum = "f5e99bff0393163bb25029a6af25d3d8d202ba5b5438a74d1bd8789f5c822970"
dependencies = [
+ "alloy-rlp",
+ "ark-ff 0.3.0",
+ "ark-ff 0.4.2",
+ "ark-ff 0.5.0",
+ "ark-ff 0.6.0",
+ "bytes",
+ "fastrlp 0.3.1",
+ "fastrlp 0.4.0",
+ "num-bigint 0.4.6",
+ "num-integer",
+ "num-traits",
+ "parity-scale-codec",
+ "primitive-types 0.12.2",
"proptest",
"rand 0.8.5",
"rand 0.9.2",
+ "rlp",
"ruint-macro",
"serde_core",
"valuable",
@@ -3349,6 +4393,21 @@ dependencies = [
"rusqlite",
]
+[[package]]
+name = "rust-client"
+version = "0.1.0"
+dependencies = [
+ "hex",
+ "miden-client",
+ "miden-client-sqlite-store",
+ "miden-protocol",
+ "rand 0.10.2",
+ "reqwest",
+ "serde",
+ "sha2 0.10.9",
+ "tokio",
+]
+
[[package]]
name = "rustc-demangle"
version = "0.1.26"
@@ -3361,6 +4420,12 @@ version = "2.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "94300abf3f1ae2e2b8ffb7b58043de3d399c73fa6f4b73826402a5c457614dbe"
+[[package]]
+name = "rustc-hex"
+version = "2.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3e75f6a532d0fd9f7f13144f392b6ad56a32696bfcd9c78f797f16bbb6f072d6"
+
[[package]]
name = "rustc_version"
version = "0.2.3"
@@ -3370,6 +4435,15 @@ dependencies = [
"semver 0.9.0",
]
+[[package]]
+name = "rustc_version"
+version = "0.3.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f0dfe2087c51c460008730de8b57e6a320782fbfb312e1f4d520e6c6fae155ee"
+dependencies = [
+ "semver 0.11.0",
+]
+
[[package]]
name = "rustc_version"
version = "0.4.1"
@@ -3425,6 +4499,7 @@ version = "1.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "229a4a4c221013e7e1f1a043678c5cc39fe5171437c88fb47151a21e6f5b5c79"
dependencies = [
+ "web-time",
"zeroize",
]
@@ -3469,6 +4544,30 @@ dependencies = [
"windows-sys 0.61.2",
]
+[[package]]
+name = "schemars"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4cd191f9397d57d581cddd31014772520aa448f65ef991055d7f61582c65165f"
+dependencies = [
+ "dyn-clone",
+ "ref-cast",
+ "serde",
+ "serde_json",
+]
+
+[[package]]
+name = "schemars"
+version = "1.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "687274d293b6cdc6e73e0fee520bf2049650090d7164f87672d212a3c530cf4a"
+dependencies = [
+ "dyn-clone",
+ "ref-cast",
+ "serde",
+ "serde_json",
+]
+
[[package]]
name = "scoped-tls"
version = "1.0.1"
@@ -3483,14 +4582,14 @@ checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
[[package]]
name = "sec1"
-version = "0.7.3"
+version = "0.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d3e97a565f76233a6003f9f5c54be1d9c5bdfa3eccfb189469f11ec4901c47dc"
+checksum = "d56d437c2f19203ce5f7122e507831de96f3d2d4d3be5af44a0b0a09d8a80e4d"
dependencies = [
"base16ct",
+ "ctutils",
"der",
- "generic-array",
- "pkcs8",
+ "hybrid-array",
"subtle",
"zeroize",
]
@@ -3524,7 +4623,16 @@ version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403"
dependencies = [
- "semver-parser",
+ "semver-parser 0.7.0",
+]
+
+[[package]]
+name = "semver"
+version = "0.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f301af10236f6df4160f7c3f04eec6dbc70ace82d23326abad5edee88801c6b6"
+dependencies = [
+ "semver-parser 0.10.3",
]
[[package]]
@@ -3543,6 +4651,15 @@ version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3"
+[[package]]
+name = "semver-parser"
+version = "0.10.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9900206b54a3527fdc7b8a938bffd94a568bac4f4aa8113b209df75a09c0dec2"
+dependencies = [
+ "pest",
+]
+
[[package]]
name = "serde"
version = "1.0.228"
@@ -3565,6 +4682,17 @@ dependencies = [
"typeid",
]
+[[package]]
+name = "serde-wincode"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9aa9d3a86c66cf10ce79df36f555a5a4c8d72a82515d9ea8ca420e02c925c30f"
+dependencies = [
+ "serde",
+ "thiserror",
+ "wincode",
+]
+
[[package]]
name = "serde_core"
version = "1.0.228"
@@ -3618,25 +4746,89 @@ dependencies = [
"serde_core",
]
+[[package]]
+name = "serde_urlencoded"
+version = "0.7.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd"
+dependencies = [
+ "form_urlencoded",
+ "itoa",
+ "ryu",
+ "serde",
+]
+
+[[package]]
+name = "serde_with"
+version = "3.22.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ee78f1fbe43ac4a0e47aadb3dbd357b69eb0d3793e948624cd03dd2750ab1c0a"
+dependencies = [
+ "base64",
+ "bs58",
+ "chrono",
+ "hex",
+ "indexmap 1.9.3",
+ "indexmap 2.14.0",
+ "jiff",
+ "schemars 0.9.0",
+ "schemars 1.2.2",
+ "serde_core",
+ "serde_json",
+ "time",
+]
+
+[[package]]
+name = "serdect"
+version = "0.4.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "66cf8fedced2fcf12406bcb34223dffb92eaf34908ede12fed414c82b7f00b3e"
+dependencies = [
+ "base16ct",
+ "serde",
+]
+
[[package]]
name = "sha2"
version = "0.10.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283"
+checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283"
+dependencies = [
+ "cfg-if",
+ "cpufeatures 0.2.17",
+ "digest 0.10.7",
+]
+
+[[package]]
+name = "sha2"
+version = "0.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "446ba717509524cb3f22f17ecc096f10f4822d76ab5c0b9822c5f9c284e825f4"
+dependencies = [
+ "cfg-if",
+ "cpufeatures 0.3.0",
+ "digest 0.11.3",
+]
+
+[[package]]
+name = "sha3"
+version = "0.10.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "75872d278a8f37ef87fa0ddbda7802605cb18344497949862c0d4dcb291eba60"
dependencies = [
- "cfg-if",
- "cpufeatures",
- "digest",
+ "digest 0.10.7",
+ "keccak 0.1.5",
]
[[package]]
name = "sha3"
-version = "0.10.8"
+version = "0.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "75872d278a8f37ef87fa0ddbda7802605cb18344497949862c0d4dcb291eba60"
+checksum = "bc9bad02c26382724b2d2692c6f179285e4b54eeecd7968f52a50059c3c11759"
dependencies = [
- "digest",
- "keccak",
+ "digest 0.11.3",
+ "keccak 0.2.1",
+ "sponge-cursor",
]
[[package]]
@@ -3656,20 +4848,14 @@ checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
[[package]]
name = "signature"
-version = "2.2.0"
+version = "3.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de"
+checksum = "28d567dcbaf0049cb8ac2608a76cd95ff9e4412e1899d389ee400918ca7537f5"
dependencies = [
- "digest",
- "rand_core 0.6.4",
+ "digest 0.11.3",
+ "rand_core 0.10.0",
]
-[[package]]
-name = "siphasher"
-version = "1.0.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d"
-
[[package]]
name = "slab"
version = "0.4.11"
@@ -3712,46 +4898,40 @@ dependencies = [
[[package]]
name = "spin"
-version = "0.10.0"
+version = "0.12.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d5fe4ccb98d9c292d56fec89a5e07da7fc4cf0dc11e156b41793132775d3e591"
+checksum = "0134f9043ed38b087ac4f7d4af44c79e2c9e5094421fe3164f435ce585953b10"
dependencies = [
"lock_api",
]
[[package]]
name = "spki"
-version = "0.7.3"
+version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d"
+checksum = "1d9efca8738c78ee9484207732f728b1ef517bbb1833d6fc0879ca898a522f6f"
dependencies = [
"base64ct",
"der",
]
[[package]]
-name = "static_assertions"
-version = "1.1.0"
+name = "sponge-cursor"
+version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
+checksum = "3a0219bd7d979d58245a4f41f695e1ac9f8befdffadd7f61f1bae9e39abc6620"
[[package]]
-name = "strength_reduce"
-version = "0.2.4"
+name = "stable_deref_trait"
+version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fe895eb47f22e2ddd4dabc02bce419d2e643c8e3b585c78158b349195bc24d82"
+checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596"
[[package]]
-name = "string_cache"
-version = "0.8.9"
+name = "static_assertions"
+version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bf776ba3fa74f83bf4b63c3dcbbf82173db2632ed8452cb2d891d33f459de70f"
-dependencies = [
- "new_debug_unreachable",
- "parking_lot",
- "phf_shared",
- "precomputed-hash",
-]
+checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
[[package]]
name = "strip-ansi-escapes"
@@ -3817,6 +4997,17 @@ dependencies = [
"unicode-ident",
]
+[[package]]
+name = "syn"
+version = "3.0.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "53e9bae58849f64dfa4f5d5ae372c8341f7305f82a3868709269343628b659a3"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "unicode-ident",
+]
+
[[package]]
name = "syn-solidity"
version = "1.5.7"
@@ -3834,6 +5025,26 @@ name = "sync_wrapper"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263"
+dependencies = [
+ "futures-core",
+]
+
+[[package]]
+name = "synstructure"
+version = "0.13.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.115",
+]
+
+[[package]]
+name = "tap"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369"
[[package]]
name = "target-triple"
@@ -3854,15 +5065,6 @@ dependencies = [
"windows-sys 0.61.2",
]
-[[package]]
-name = "term"
-version = "1.2.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2111ef44dae28680ae9752bb89409e7310ca33a8c621ebe7b106cf5c928b3ac0"
-dependencies = [
- "windows-sys 0.61.2",
-]
-
[[package]]
name = "termcolor"
version = "1.4.1"
@@ -3895,22 +5097,22 @@ dependencies = [
[[package]]
name = "thiserror"
-version = "2.0.17"
+version = "2.0.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f63587ca0f12b72a0600bcba1d40081f830876000bb46dd2337a3051618f4fc8"
+checksum = "ec86235f5fcc2a73650310756d2ac5b138a5780bbbdfae3eeccec992c435ba4f"
dependencies = [
"thiserror-impl",
]
[[package]]
name = "thiserror-impl"
-version = "2.0.17"
+version = "2.0.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913"
+checksum = "bc04cd3e1236dd4a98afca4569f2deb3f120e5422a4023be2cb683f8486292af"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.115",
+ "syn 3.0.3",
]
[[package]]
@@ -3922,6 +5124,36 @@ dependencies = [
"cfg-if",
]
+[[package]]
+name = "time"
+version = "0.3.55"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cdb87b95ec50ddfa440816d227a17b2ccbdda963a316a727fda0fc4334f7d134"
+dependencies = [
+ "deranged",
+ "num-conv",
+ "powerfmt",
+ "serde_core",
+ "time-core",
+ "time-macros",
+]
+
+[[package]]
+name = "time-core"
+version = "0.1.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9e1c906769ad99c88eaa54e728060edef082f8e358ff32030cb7c7d315e81109"
+
+[[package]]
+name = "time-macros"
+version = "0.2.32"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7e689342a48d2ea927c87ea50cabf8594854bf940e9310208848d680d668ed85"
+dependencies = [
+ "num-conv",
+ "time-core",
+]
+
[[package]]
name = "tiny-keccak"
version = "2.0.2"
@@ -3931,6 +5163,31 @@ dependencies = [
"crunchy",
]
+[[package]]
+name = "tinystr"
+version = "0.8.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b1e27c91459209c2986af3dcf603a5a74a4368754ce37414f59acc971167f643"
+dependencies = [
+ "displaydoc",
+ "zerovec",
+]
+
+[[package]]
+name = "tinyvec"
+version = "1.12.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bb4ebadaa0af04fab11ae01eb5f9fdb5f9c5b875506e210e71c07873528baa7f"
+dependencies = [
+ "tinyvec_macros",
+]
+
+[[package]]
+name = "tinyvec_macros"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
+
[[package]]
name = "tokio"
version = "1.48.0"
@@ -3998,7 +5255,7 @@ version = "0.9.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f0dc8b1fb61449e27716ec0e1bdf0f6b8f3e8f6b05391e8497b8b6d7804ea6d8"
dependencies = [
- "indexmap",
+ "indexmap 2.14.0",
"serde_core",
"serde_spanned",
"toml_datetime 0.7.3",
@@ -4013,7 +5270,7 @@ version = "1.1.2+spec-1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "81f3d15e84cbcd896376e6730314d59fb5a87f31e4b038454184435cd57defee"
dependencies = [
- "indexmap",
+ "indexmap 2.14.0",
"serde_core",
"serde_spanned",
"toml_datetime 1.1.1+spec-1.1.0",
@@ -4040,6 +5297,18 @@ dependencies = [
"serde_core",
]
+[[package]]
+name = "toml_edit"
+version = "0.25.13+spec-1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6975367e4d2ef766d86af01ffad14b622fecc8d4357a998fbc4deb6e9bacaf9b"
+dependencies = [
+ "indexmap 2.14.0",
+ "toml_datetime 1.1.1+spec-1.1.0",
+ "toml_parser",
+ "winnow 1.0.1",
+]
+
[[package]]
name = "toml_parser"
version = "1.1.2+spec-1.1.0"
@@ -4170,7 +5439,7 @@ checksum = "d039ad9159c98b70ecfd540b2573b97f7f52c3e8d9f8ad57a24b916a536975f9"
dependencies = [
"futures-core",
"futures-util",
- "indexmap",
+ "indexmap 2.14.0",
"pin-project-lite",
"slab",
"sync_wrapper",
@@ -4181,6 +5450,24 @@ dependencies = [
"tracing",
]
+[[package]]
+name = "tower-http"
+version = "0.6.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4cfcf7e2740e6fc6d4d688b4ef00650406bb94adf4731e43c096c3a19fe40840"
+dependencies = [
+ "bitflags",
+ "bytes",
+ "futures-util",
+ "http",
+ "http-body",
+ "pin-project-lite",
+ "tower",
+ "tower-layer",
+ "tower-service",
+ "url",
+]
+
[[package]]
name = "tower-layer"
version = "0.3.3"
@@ -4254,16 +5541,6 @@ dependencies = [
"tracing-log",
]
-[[package]]
-name = "transpose"
-version = "0.2.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1ad61aed86bc3faea4300c7aee358b4c6d0c8d6ccc36524c96e4c92ccf26e77e"
-dependencies = [
- "num-integer",
- "strength_reduce",
-]
-
[[package]]
name = "try-lock"
version = "0.2.5"
@@ -4294,9 +5571,27 @@ checksum = "bc7d623258602320d5c55d1bc22793b57daff0ec7efc270ea7d55ce1d5f5471c"
[[package]]
name = "typenum"
-version = "1.19.0"
+version = "1.20.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b6f5e870be6c3b371b77fe0ee0bafb859fa4964b4404c27de1d380043c4dda20"
+
+[[package]]
+name = "ucd-trie"
+version = "0.1.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2896d95c02a80c6d6a5d6e953d479f5ddf2dfdb6a244441010e373ac0fb88971"
+
+[[package]]
+name = "uint"
+version = "0.9.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb"
+checksum = "76f64bba2c53b04fcab63c01a7d7427eadc821e3bc48c34dc9ba29c501164b52"
+dependencies = [
+ "byteorder",
+ "crunchy",
+ "hex",
+ "static_assertions",
+]
[[package]]
name = "uint"
@@ -4354,12 +5649,12 @@ checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853"
[[package]]
name = "universal-hash"
-version = "0.5.1"
+version = "0.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fc1de2c688dc15305988b563c3854064043356019f97a4b46276fe734c4f07ea"
+checksum = "f4987bdc12753382e0bec4a65c50738ffaabc998b9cdd1f952fb5f39b0048a96"
dependencies = [
- "crypto-common",
- "subtle",
+ "crypto-common 0.2.2",
+ "ctutils",
]
[[package]]
@@ -4368,6 +5663,24 @@ version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1"
+[[package]]
+name = "url"
+version = "2.5.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ff67a8a4397373c3ef660812acab3268222035010ab8680ec4215f38ba3d0eed"
+dependencies = [
+ "form_urlencoded",
+ "idna",
+ "percent-encoding",
+ "serde",
+]
+
+[[package]]
+name = "utf8_iter"
+version = "1.0.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be"
+
[[package]]
name = "utf8parse"
version = "0.2.2"
@@ -4537,7 +5850,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909"
dependencies = [
"anyhow",
- "indexmap",
+ "indexmap 2.14.0",
"wasm-encoder",
"wasmparser",
]
@@ -4563,7 +5876,7 @@ checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe"
dependencies = [
"bitflags",
"hashbrown 0.15.5",
- "indexmap",
+ "indexmap 2.14.0",
"semver 1.0.27",
]
@@ -4577,6 +5890,25 @@ dependencies = [
"wasm-bindgen",
]
+[[package]]
+name = "web-time"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb"
+dependencies = [
+ "js-sys",
+ "wasm-bindgen",
+]
+
+[[package]]
+name = "webpki-roots"
+version = "1.0.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7dcd9d09a39985f5344844e66b0c530a33843579125f23e21e9f0f220850f22a"
+dependencies = [
+ "rustls-pki-types",
+]
+
[[package]]
name = "winapi-util"
version = "0.1.11"
@@ -4586,6 +5918,18 @@ dependencies = [
"windows-sys 0.61.2",
]
+[[package]]
+name = "wincode"
+version = "0.6.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bfc6339f1ba427bf7ad7c42403b28e524832ba2ddb5eef1bb2cc3b85db6b7b75"
+dependencies = [
+ "pastey",
+ "proc-macro2",
+ "quote",
+ "thiserror",
+]
+
[[package]]
name = "windows"
version = "0.61.3"
@@ -4901,6 +6245,9 @@ name = "winnow"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09dac053f1cd375980747450bfc7250c264eaae0583872e845c0c7cd578872b5"
+dependencies = [
+ "memchr",
+]
[[package]]
name = "wit-bindgen"
@@ -4936,7 +6283,7 @@ checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21"
dependencies = [
"anyhow",
"heck",
- "indexmap",
+ "indexmap 2.14.0",
"prettyplease",
"syn 2.0.115",
"wasm-metadata",
@@ -4967,7 +6314,7 @@ checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2"
dependencies = [
"anyhow",
"bitflags",
- "indexmap",
+ "indexmap 2.14.0",
"log",
"serde",
"serde_derive",
@@ -4986,7 +6333,7 @@ checksum = "ecc8ac4bc1dc3381b7f59c34f00b67e18f910c2c0f50015669dde7def656a736"
dependencies = [
"anyhow",
"id-arena",
- "indexmap",
+ "indexmap 2.14.0",
"log",
"semver 1.0.27",
"serde",
@@ -4996,14 +6343,63 @@ dependencies = [
"wasmparser",
]
+[[package]]
+name = "wnaf"
+version = "0.14.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ab12e7090f27e2ffd9322651492942d50c2926094af30601e1964337db39daf1"
+dependencies = [
+ "ff",
+ "group",
+ "hybrid-array",
+]
+
+[[package]]
+name = "writeable"
+version = "0.6.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3ad82d2a33cdc9674dc7465672f271e096168fcdbe0f799d9e6db8c5892679dc"
+
+[[package]]
+name = "wyz"
+version = "0.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f118f77b9ed"
+dependencies = [
+ "tap",
+]
+
[[package]]
name = "x25519-dalek"
-version = "2.0.1"
+version = "3.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c7e468321c81fb07fa7f4c636c3972b9100f0346e5b6a9f2bd0603a52f7ed277"
+checksum = "e7e8131a03190127fb2263afc72b322ecadae46b6ff8c6f399ff5d02f5559af6"
dependencies = [
"curve25519-dalek",
- "rand_core 0.6.4",
+ "rand_core 0.10.0",
+]
+
+[[package]]
+name = "yoke"
+version = "0.8.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "709fe23a0424b6a435d82152b1bd3fdfb0833487d5fa90d05d42762a9891fef5"
+dependencies = [
+ "stable_deref_trait",
+ "yoke-derive",
+ "zerofrom",
+]
+
+[[package]]
+name = "yoke-derive"
+version = "0.8.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "de844c262c8848816172cef550288e7dc6c7b7814b4ee56b3e1553f275f1858e"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.115",
+ "synstructure",
]
[[package]]
@@ -5026,8 +6422,76 @@ dependencies = [
"syn 2.0.115",
]
+[[package]]
+name = "zerofrom"
+version = "0.1.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0ec05a11813ea801ff6d75110ad09cd0824ddba17dfe17128ea0d5f68e6c5272"
+dependencies = [
+ "zerofrom-derive",
+]
+
+[[package]]
+name = "zerofrom-derive"
+version = "0.1.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "11532158c46691caf0f2593ea8358fed6bbf68a0315e80aae9bd41fbade684a1"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.115",
+ "synstructure",
+]
+
[[package]]
name = "zeroize"
version = "1.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0"
+dependencies = [
+ "zeroize_derive",
+]
+
+[[package]]
+name = "zeroize_derive"
+version = "1.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3c50655cbb0fe3fc43170059e702f1ce5e19b84cec58dc87b037a09935c2f328"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.115",
+]
+
+[[package]]
+name = "zerotrie"
+version = "0.2.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4ea269c3bd32f0a32c321907a2ae912ba6f4649bb0fc764a15627e99a7095a3f"
+dependencies = [
+ "displaydoc",
+ "yoke",
+ "zerofrom",
+]
+
+[[package]]
+name = "zerovec"
+version = "0.11.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bb0464e17806c1d976d5cba29399c7f08e516e279e2ba493f63123b5fca67dd8"
+dependencies = [
+ "yoke",
+ "zerofrom",
+ "zerovec-derive",
+]
+
+[[package]]
+name = "zerovec-derive"
+version = "0.11.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "34df6fc39dbd26ddc9c10e6a2984476e13acce22e64e4487636ef494369225da"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 3.0.3",
+]
diff --git a/docs/Cargo.toml b/docs/Cargo.toml
index d95f3098..0dab8cf9 100644
--- a/docs/Cargo.toml
+++ b/docs/Cargo.toml
@@ -4,8 +4,9 @@ version = "0.1.0"
edition = "2021"
[dependencies]
-miden-client = { version = "0.15", features = ["testing", "tonic"] }
-miden-client-sqlite-store = { version = "0.15", package = "miden-client-sqlite-store" }
-miden-protocol = { version = "0.15" }
-rand = { version = "0.9" }
+miden-client = { version = "=0.16.0", features = ["testing", "tonic"] }
+miden-client-sqlite-store = { version = "=0.16.0", package = "miden-client-sqlite-store" }
+miden-protocol = { version = "=0.16.0" }
+rust-client = { path = "../rust-client" }
+rand = { version = "0.10" }
tokio = { version = "1.48", features = ["rt-multi-thread", "net", "macros", "fs"] }
diff --git a/docs/src/miden-bank/00-project-setup.md b/docs/src/miden-bank/00-project-setup.md
index 3a713726..58ffe4b6 100644
--- a/docs/src/miden-bank/00-project-setup.md
+++ b/docs/src/miden-bank/00-project-setup.md
@@ -37,11 +37,11 @@ miden --version
The Miden toolchain porcelain:
Environment:
-- cargo version: cargo 1.93.0 (083ac5135 2025-12-15).
+- cargo version: cargo 1.98.1 (797e8a9bc 2026-08-05).
Midenup:
-- midenup + miden version: 0.1.0.
-- active toolchain version: 0.20.3.
+- midenup + miden version: 1.0.0.
+- active toolchain version: 0.16.0.
- ...
```
@@ -70,6 +70,7 @@ miden-bank/
│ │ └── helpers.rs # Helper functions for tests
│ └── tests/ # Test files
├── Cargo.toml # Workspace root
+├── miden-toolchain.toml # Miden toolchain specification
└── rust-toolchain.toml # Rust toolchain specification
```
@@ -88,7 +89,7 @@ mv contracts/counter-account contracts/bank-account
A contract is configured by three files: a minimal Cargo manifest, a Miden project manifest, and a Cargo build config.
-First, update the `Cargo.toml` inside `contracts/bank-account/`. It only needs the `miden` guest dependency and the `cdylib` crate type:
+First, update the `Cargo.toml` inside `contracts/bank-account/`. Keep the generated `build.rs` and its build dependency; they prepare the package cache for Cargo and IDE builds:
```toml title="contracts/bank-account/Cargo.toml"
[package]
@@ -100,7 +101,10 @@ edition = "2021"
crate-type = ["cdylib"]
[dependencies]
-miden = "0.13"
+miden = "0.14"
+
+[build-dependencies]
+miden-sdk-build-script-support = "0.14"
```
Next, create `contracts/bank-account/miden-project.toml`. This is the Miden-specific project manifest that tells the compiler what kind of artifact to build and which package namespace to export:
@@ -112,14 +116,12 @@ version = "0.1.0"
[lib]
kind = "account-component"
+path = "src/lib.rs"
namespace = "miden:bank-account/bank@0.1.0"
[dependencies]
miden-core = "*"
miden-protocol = "*"
-
-[package.metadata.miden]
-supported-types = ["RegularAccountImmutableCode"]
```
Finally, create `contracts/bank-account/.cargo/config.toml` so the contract always builds for the WebAssembly target with the `miden` cfg enabled (this also makes editor/LSP workflows resolve the right code):
@@ -140,17 +142,8 @@ rustflags = ["--cfg", "miden"]
| `crate-type = ["cdylib"]` | `Cargo.toml` | Required for WebAssembly compilation |
| `kind = "account-component"` | `miden-project.toml` | Tells the compiler this is an account component |
| `namespace = "miden:bank-account/bank@..."` | `miden-project.toml` | The package namespace used for cross-component calls |
-| `supported-types` | `miden-project.toml` | Account types this component supports |
| `target = "wasm32-wasip2"` | `.cargo/config.toml` | Compile target for the Miden VM |
-:::info Supported Account Types
-`RegularAccountImmutableCode` means the account code cannot be changed after deployment. This is appropriate for our bank since we want the logic to be fixed.
-:::
-
-:::note Toolchain
-This tutorial targets protocol v0.15. The contracts depend on the published `miden = "0.13"` SDK (the cross-component / sibling-call line of the v0.15 compiler), and the integration harness builds them with the published `cargo-miden = "0.9"` release. The pinned `rust-toolchain.toml` is `nightly-2026-04-30` with the `wasm32-wasip2` target.
-:::
-
## Step 3: Create a Minimal Bank Component
Replace the contents of `contracts/bank-account/src/lib.rs` with a minimal bank structure:
@@ -189,9 +182,11 @@ struct BankStorage {
#[component]
trait Bank {
/// Initialize the bank account, enabling deposits.
+ #[account_procedure]
fn initialize(&mut self);
/// Get the bank-tracked balance for a depositor and specific asset type.
+ #[account_procedure]
fn get_depositor_balance(&self, depositor: AccountId, asset: Asset) -> Felt;
}
@@ -229,10 +224,12 @@ This is our starting point with two storage slots:
:::note Component Structure
The `#[component_storage]` struct declares the storage layout, the `#[component] trait` declares the exported API, and `#[component] impl Bank for BankStorage` implements it. Any private helper methods you add later live in a separate plain `impl BankStorage` block — the `#[component]` macro only exports trait methods.
+
+Mark each method that notes, transaction scripts, or other account components can call with `#[account_procedure]` on the trait declaration. Unmarked methods are exported but are not part of the account procedure table.
:::
:::note get_depositor_balance, not get_balance
-The balance accessor is named `get_depositor_balance` rather than `get_balance` so it does not collide with the built-in `ActiveAccount::get_balance` vault method that the account wrapper generates. It also exercises the WIT binding types (`AccountId`, `Asset`), which the compiler needs in at least one exported method.
+The balance accessor is named `get_depositor_balance` rather than `get_balance` so it does not collide with the built-in `ActiveAccount::get_balance` vault method that the account wrapper generates.
:::
:::info Contracts Are Excluded
@@ -262,44 +259,38 @@ miden build --release
The compiled output is stored in `target/miden/release/bank-account.masp`.
-:::note Cosmetic MAST ERROR lines
-Every contract build prints one or more non-fatal `MAST`-serialization lines starting with `ERROR`. These are cosmetic — the build still succeeds and produces the `.masp` package. You can ignore them.
-:::
-
:::tip What's a .masp File?
A `.masp` file is a Miden Assembly Package. It contains the compiled MASM (Miden Assembly) code and metadata needed to deploy and interact with your contract.
:::
-:::info Build Order Matters
-The bank account is the base contract. The deposit/withdraw notes and the init transaction script call into it, and their build relies on the bank account's already-compiled package (the FPI `#[account(...)]` macro reads the bank's procedure roots from its `.masp` at compile time). So always build `bank-account` first, then the notes and transaction script. The integration test harness handles this ordering for you.
+:::info Contract dependencies
+The notes and transaction script call the bank account through generated bindings. Their `miden-project.toml` files declare the bank as an automatic path dependency, so `miden build` compiles it before the dependent contract and provides its interface and procedure roots to `#[account(...)]`.
:::
## Optional: Verify Your Setup
:::note
-This is an optional self-check. If you create this test file, you can run it to verify your code compiles and loads correctly. The main runnable tests begin in Part 4.
+This is an optional self-check. It loads the package compiled in Step 4 and creates a test account locally.
:::
Create a new test file:
```rust title="integration/tests/part0_setup_test.rs"
-use integration::helpers::{
- build_project_in_dir, create_testing_account_from_package, AccountCreationConfig,
-};
use miden_client::account::{
- component::{InitStorageData, StorageValueName},
- StorageSlotName,
+ component::{BasicWallet, InitStorageData, StorageValueName},
+ AccountBuilder, AccountComponent, AccountType, StorageSlotName,
};
-use miden_client::Word;
-use std::{path::Path, sync::Arc};
-
-#[tokio::test]
-async fn test_bank_account_builds_and_loads() -> anyhow::Result<()> {
- // Build the bank account contract
- let bank_package = Arc::new(build_project_in_dir(
- Path::new("../contracts/bank-account"),
- true,
- )?);
+use miden_client::{utils::Deserializable, Word};
+use miden_mast_package::Package;
+use miden_standards::account::auth::NoAuth;
+use std::path::Path;
+
+#[test]
+fn test_bank_account_loads() -> anyhow::Result<()> {
+ // Load the bank account package compiled in Step 4.
+ let package_path = Path::new(env!("CARGO_MANIFEST_DIR"))
+ .join("../contracts/bank-account/target/miden/release/bank-account.masp");
+ let bank_package = Package::read_from_bytes(&std::fs::read(package_path)?)?;
// The `initialized` value slot has no schema default, so it must be seeded
// (with a zero Word = uninitialized) or `AccountComponent::from_package`
@@ -313,16 +304,18 @@ async fn test_bank_account_builds_and_loads() -> anyhow::Result<()> {
StorageValueName::from_slot_name(&initialized_slot),
Word::default(),
)?;
- let bank_cfg = AccountCreationConfig {
- init_storage_data,
- ..Default::default()
- };
+ let bank_component = AccountComponent::from_package(&bank_package, &init_storage_data)?;
+ assert_eq!(bank_component.procedures().count(), 2);
- let bank_account =
- create_testing_account_from_package(bank_package.clone(), bank_cfg)?;
+ let bank_account = AccountBuilder::new([3u8; 32])
+ .account_type(AccountType::Public)
+ .with_component(bank_component)
+ .with_component(BasicWallet)
+ .with_component(NoAuth)
+ .build_existing()?;
// Verify the account was created
- println!("Bank account created with ID: {:?}", bank_account.id());
+ println!("Bank account created with ID: {}", bank_account.id().to_hex());
println!("Part 0 setup verified!");
Ok(())
@@ -332,7 +325,7 @@ async fn test_bank_account_builds_and_loads() -> anyhow::Result<()> {
Run the test from the project root:
```bash title=">_ Terminal"
-cargo test --package integration test_bank_account_builds_and_loads -- --nocapture
+cargo test --package integration test_bank_account_loads -- --nocapture
```
@@ -346,7 +339,7 @@ cargo test --package integration test_bank_account_builds_and_loads -- --nocaptu
running 1 test
Bank account created with ID: 0x...
Part 0 setup verified!
-test test_bank_account_builds_and_loads ... ok
+test test_bank_account_loads ... ok
test result: ok. 1 passed; 0 failed; 0 ignored
```
diff --git a/docs/src/miden-bank/01-account-components.md b/docs/src/miden-bank/01-account-components.md
index a4000cf8..9198ed6d 100644
--- a/docs/src/miden-bank/01-account-components.md
+++ b/docs/src/miden-bank/01-account-components.md
@@ -6,7 +6,7 @@ description: "Learn how to define account components with the #[component] attri
# Part 1: Account Components and Storage
-In this section, you'll learn the fundamentals of building Miden account components. We'll explore the storage types introduced in Part 0 — `Value` and `StorageMap` — and add component methods.
+In this section, you'll learn the fundamentals of building Miden account components. We'll explore the storage types introduced in Part 0 — `StorageValue` and `StorageMap` — and add component methods.
## What You'll Build in This Part
@@ -82,11 +82,11 @@ struct BankStorage {
}
```
-The `balances` field is a `StorageMap` that tracks each depositor's balance. The compiler derives slot IDs by hashing slot names (not by field declaration order). Slot names follow the pattern `{package_name}::{component_struct}::{field_name}` — here `bank_account::bank::initialized` and `bank_account::bank::balances`.
+The `balances` field is a `StorageMap` that tracks each depositor's balance. The compiler derives slot IDs by hashing slot names (not by field declaration order). Slot names follow the pattern `{package_name}::{component_interface}::{field_name}` — here `bank_account::bank::initialized` and `bank_account::bank::balances`.
## Storage Types Explained
-Miden accounts have storage slots that persist state on-chain. Each slot holds one `Word` (4 Felts = 32 bytes). The Miden Rust compiler provides two abstractions:
+Miden accounts have persistent storage slots. Public account storage is published on-chain; private accounts publish its commitment. A value slot holds one `Word` (4 Felts = 32 bytes), while a map slot holds the root of its key-value map. The Miden Rust compiler provides two abstractions:
### StorageValue Storage
@@ -116,7 +116,7 @@ self.initialized.set(new_value);
```
:::tip Type Annotations
-The `.get()` method requires a type annotation: `let current: Word = self.initialized.get();`
+`StorageValue::get()` returns a `Word`. The annotation in `let current: Word = self.initialized.get();` makes that type explicit but is not required.
:::
### StorageMap
@@ -143,17 +143,16 @@ let key = Word::from([
asset.key[2],
]);
-// Get returns a generic type V where V: From.
-// Here we annotate the result as Felt, which works because Felt implements From.
+// This field is StorageMap, so get returns Felt.
let balance: Felt = self.balances.get(key);
-// Set stores a value at the key (any type that implements Into)
+// Set stores a Felt at this Word key.
let new_balance = Felt::new(balance.as_canonical_u64() + deposit_amount.as_canonical_u64()).unwrap();
self.balances.set(key, new_balance);
```
:::info StorageMap Has a Generic API
-`StorageMap::get()` returns a generic type `V` (constrained by `V: From`), not specifically `Felt`. The type is inferred from the variable annotation. In this tutorial we use `Felt` because we store single balance values, but you could also use `Word` or any custom type that implements the trait.
+`StorageMap::get()` returns the map's declared value type `V`, which must implement `WordValue`. Our `StorageMap` therefore returns `Felt`; the variable annotation does not change the map's value type. A map declared with `Word` values would return `Word`.
:::
### Storage Layout
@@ -165,7 +164,7 @@ Plan your storage layout carefully:
| `initialized` | `StorageValue` | Initialization flag |
| `balances` | `StorageMap` | Depositor balances |
-The `description` attribute generates named slot identifiers (e.g., `bank_account::bank::initialized`) used in tests to reference specific slots. The naming convention is `{package_name}::{component_struct}::{field_name}`. The compiler derives slot IDs by hashing these names, so field declaration order does not affect slot assignment.
+The `description` attribute adds human-readable metadata. The package namespace, component interface, and field name determine slot names such as `bank_account::bank::initialized`, which tests use to identify slots. The naming convention is `{package_name}::{component_interface}::{field_name}`. The compiler derives slot IDs by hashing these names, so field declaration order does not affect slot assignment.
## Step 2: Implement Component Methods
@@ -176,9 +175,11 @@ Now let's add methods to our Bank. The exported API is declared as a `#[componen
#[component]
trait Bank {
/// Initialize the bank account, enabling deposits.
+ #[account_procedure]
fn initialize(&mut self);
/// Get the bank-tracked balance for a depositor and specific asset type.
+ #[account_procedure]
fn get_depositor_balance(&self, depositor: AccountId, asset: Asset) -> Felt;
}
@@ -232,8 +233,8 @@ impl BankStorage {
}
```
-:::info v0.15 fungible-asset key layout
-A fungible asset's vault key Word is `[asset_id_suffix, asset_id_prefix, faucet_suffix | metadata_byte, faucet_prefix]`. So `asset.key[3]` is the faucet id prefix and `asset.key[2]` is the faucet id suffix folded together with a metadata byte (composition + callback flag) in its low 8 bits — `key[2]` is **not** the raw faucet suffix. For the callbacks-disabled fungible assets this bank accepts the metadata byte is constant, so `(key[3], key[2])` is a stable per-faucet identifier. The host-side mirror is `FungibleAsset::to_key_word()` indices `[3]`/`[2]`.
+:::info v0.16 fungible-asset ID layout
+A fungible asset's ID Word is `[asset_class_suffix, asset_class_prefix, faucet_suffix | metadata_byte, faucet_prefix]`. For fungible assets, the asset class is empty. `asset.key[3]` is the faucet ID prefix and `asset.key[2]` is the faucet ID suffix with the composition bits in its low byte, so `key[2]` is **not** the raw faucet suffix. The callback flag is now encoded in the faucet account ID, not in the asset metadata byte. The host-side mirror is `FungibleAsset::to_id_word()` indices `[3]`/`[2]`.
:::
The bank requires initialization before accepting deposits: `require_initialized()` is called at the top of `deposit()` and `withdraw()` (covered in later parts).
@@ -262,12 +263,8 @@ miden build
This compiles the Rust code to Miden Assembly and generates:
-- `target/miden/release/bank-account.masp` - The compiled package
-- `target/generated-wit/` - WIT interface files for other contracts to use
-
-:::note Cosmetic build errors
-The build prints non-fatal `MAST`-serialization `ERROR` lines on every run. These are cosmetic — the build still succeeds and produces the `.masp` package.
-:::
+- `target/miden/dev/bank-account.masp` - The compiled package
+- The package embeds the WIT interface used by dependent contracts
## Optional: Verify Your Code
@@ -287,7 +284,7 @@ Create a new test file:
use integration::helpers::{
build_project_in_dir, create_testing_account_from_package, AccountCreationConfig,
};
-use miden_client::account::{StorageMap, StorageSlot, StorageSlotName};
+use miden_client::account::{component::{InitStorageData, StorageValueName}, StorageSlotName};
use miden_client::{Felt, Word};
use std::{path::Path, sync::Arc};
@@ -304,7 +301,7 @@ async fn test_bank_account_storage() -> anyhow::Result<()> {
)?);
// Create named storage slots matching the contract's storage layout
- // The naming convention is: {package_name}::{component_struct}::{field_name}
+ // The naming convention is: {package_name}::{component_interface}::{field_name}
let initialized_slot =
StorageSlotName::new("bank_account::bank::initialized")
.expect("Valid slot name");
@@ -349,8 +346,11 @@ async fn test_bank_account_storage() -> anyhow::Result<()> {
// =========================================================================
// Check that we can query the balances map (should return 0 for any key)
- let test_key = Word::from([Felt::new(1), Felt::new(2), Felt::new(0), Felt::new(0)]);
- let balance = bank_account.storage().get_map_item(&balances_slot, test_key)?;
+ let test_key = Word::from([Felt::from(1u32), Felt::from(2u32), Felt::from(0u32), Felt::from(0u32)]);
+ let balance = bank_account.storage().get_map_item(
+ &balances_slot,
+ miden_client::account::StorageMapKey::new(test_key),
+ )?;
// Balance for non-existent depositor should be all zeros
assert_eq!(
@@ -436,9 +436,11 @@ struct BankStorage {
#[component]
trait Bank {
/// Initialize the bank account, enabling deposits.
+ #[account_procedure]
fn initialize(&mut self);
/// Get the bank-tracked balance for a depositor and specific asset type.
+ #[account_procedure]
fn get_depositor_balance(&self, depositor: AccountId, asset: Asset) -> Felt;
}
@@ -493,7 +495,7 @@ impl BankStorage {
## Key Takeaways
-1. **`#[component]`** marks structs and impl blocks as Miden account components
+1. **`#[component]`** marks the exported component trait and its implementation; `#[component_storage]` marks the storage struct
2. **`StorageValue`** stores a single Word, read with `.get()`, write with `.set()`
3. **`StorageMap`** stores key-value pairs, access with `.get()` and `.set()`
4. **Storage slots** are identified by name (IDs derived from hashed slot names), each holds 4 Felts (32 bytes)
diff --git a/docs/src/miden-bank/02-constants-constraints.md b/docs/src/miden-bank/02-constants-constraints.md
index 2eb444f2..61db85c5 100644
--- a/docs/src/miden-bank/02-constants-constraints.md
+++ b/docs/src/miden-bank/02-constants-constraints.md
@@ -14,9 +14,9 @@ By the end of this section, you will have:
- Defined constants for business rules (`MAX_DEPOSIT_AMOUNT`, `MAX_BALANCE`)
- Used `assert!()` for transaction validation
-- Learned safe Felt comparison with `.as_canonical_u64()`
+- Compared token amounts with `u64` business limits using `.as_canonical_u64()`
- Added a deposit method skeleton with validation
-- **Verified constraints work** by testing that invalid operations fail
+- **Verified the component builds and loads** with its initial storage; later parts exercise the transaction guards
## Building on Part 1
@@ -93,40 +93,55 @@ When an assertion fails:
This is the primary mechanism for enforcing business rules in Miden contracts.
-## Safe Felt Comparisons
+## Comparing Felt Amounts with Business Limits
-:::warning Pitfall: Felt Comparison Operators
-Never use `<`, `>`, `<=`, or `>=` operators directly on `Felt` values. They produce incorrect results due to field element ordering.
+:::note Comparison and Arithmetic
+The current SDK's `<`, `>`, `<=`, and `>=` operators compare the canonical integer values of `Felt`s. Direct comparisons are supported. Arithmetic on `Felt` is modular, however, so validate amounts before addition or subtraction can wrap around the field modulus.
:::
-**Wrong approach:**
+**Comparing two field elements:**
```rust
-// DON'T DO THIS - produces incorrect results
+// Direct Felt comparison uses canonical integer ordering.
if deposit_amount > felt!(1_000_000) {
- // This comparison is unreliable!
+ // The amount exceeds the limit.
}
```
-**Correct approach:**
+**Comparing with our `u64` constant:**
```rust
-// CORRECT - convert to u64 first
+// Convert the amount to compare it with the u64 business limit.
if deposit_amount.as_canonical_u64() > MAX_DEPOSIT_AMOUNT {
- // This works correctly
+ // The amount exceeds the limit.
}
```
-The `.as_canonical_u64()` method extracts the underlying 64-bit integer from a Felt, allowing standard Rust comparisons.
+Both comparisons have the same result. This tutorial uses `.as_canonical_u64()` to express quantity checks against `u64` limits explicitly. Converting a value after field arithmetic has wrapped does not recover its original quantity.
## Step 1: Add the Constant and Deposit Method
-Update your `contracts/bank-account/src/lib.rs` to add the constant and a deposit method skeleton. The component's public API lives in `#[component] impl Bank for BankStorage`, while private helpers like `require_initialized` and `balance_key` live in a separate plain `impl BankStorage` block (the `#[component]` macro only exports trait methods):
+Update your `contracts/bank-account/src/lib.rs` to add the constants and a deposit method skeleton. Keep the storage struct from Part 1 and replace the `Bank` trait and implementation blocks with the code below. Declare `deposit` in the trait with `#[account_procedure]` before implementing it in `#[component] impl Bank for BankStorage`. Private helpers like `require_initialized` and `balance_key` remain in a separate plain `impl BankStorage` block:
```rust title="contracts/bank-account/src/lib.rs"
const MAX_DEPOSIT_AMOUNT: u64 = 1_000_000;
const MAX_BALANCE: u64 = 9_223_372_034_707_292_160; // 2^63 - 2^31
+#[component]
+trait Bank {
+ /// Initialize the bank account, enabling deposits.
+ #[account_procedure]
+ fn initialize(&mut self);
+
+ /// Get the bank-tracked balance for a depositor and specific asset type.
+ #[account_procedure]
+ fn get_depositor_balance(&self, depositor: AccountId, asset: Asset) -> Felt;
+
+ /// Deposit an asset into the bank for a specific depositor.
+ #[account_procedure]
+ fn deposit(&mut self, depositor: AccountId, deposit_asset: Asset);
+}
+
#[component]
impl Bank for BankStorage {
fn initialize(&mut self) {
@@ -191,8 +206,8 @@ impl BankStorage {
}
```
-:::warning v0.15 asset-key layout
-In v0.15 the fungible-asset vault key Word is `[asset_id_suffix, asset_id_prefix, faucet_suffix | metadata_byte, faucet_prefix]`. So `asset.key[2]` is the faucet suffix combined with a metadata byte (asset composition + a callback flag in the low 8 bits), **not** the raw faucet suffix. For the callbacks-disabled fungible assets this bank accepts the metadata byte is constant, so `(key[3], key[2])` remains a stable per-faucet identifier. The host/test side derives the same key from `FungibleAsset::new(faucet.id(), amt)?.to_key_word()` indices `[3]`/`[2]` (not `faucet.id().prefix()/suffix()`).
+:::warning v0.16 asset-ID layout
+In v0.16 the fungible-asset ID Word is `[asset_class_suffix, asset_class_prefix, faucet_suffix | metadata_byte, faucet_prefix]`. The fungible asset class is empty, and the metadata byte contains the composition bits; the callback flag is part of the faucet account ID. Thus `asset.key[2]` is **not** the raw faucet suffix. The host/test side derives the same ID from `FungibleAsset::new(faucet.id(), amt)?.to_id_word()` indices `[3]`/`[2]` (not `faucet.id().prefix()/suffix()`).
:::
### The require_initialized() Guard
@@ -256,10 +271,6 @@ cd contracts/bank-account
miden build
```
-:::note Cosmetic build output
-The Miden compiler prints non-fatal `MAST`-serialization `ERROR` lines on every build. These are cosmetic — the build still succeeds and emits the `.masp` package.
-:::
-
## Optional: Verify Constraints Work
:::note
@@ -378,6 +389,8 @@ This pattern is **mandatory** for any operation that subtracts from a balance. M
### State Checks
+This optional extension assumes a `paused: StorageValue` slot has been added to the component and initialized to a zero Word. The Bank component in this tutorial does not include that slot.
+
```rust
fn require_not_paused(&self) {
let paused: Word = self.paused.get();
@@ -426,12 +439,15 @@ struct BankStorage {
#[component]
trait Bank {
/// Initialize the bank account, enabling deposits.
+ #[account_procedure]
fn initialize(&mut self);
/// Get the bank-tracked balance for a depositor and specific asset type.
+ #[account_procedure]
fn get_depositor_balance(&self, depositor: AccountId, asset: Asset) -> Felt;
/// Deposit an asset into the bank for a specific depositor.
+ #[account_procedure]
fn deposit(&mut self, depositor: AccountId, deposit_asset: Asset);
}
@@ -501,7 +517,7 @@ impl BankStorage {
1. **Constants** define immutable business rules at compile time
2. **`assert!()`** enforces constraints - failures reject the transaction
-3. **Always use `.as_canonical_u64()`** for Felt comparisons, never direct operators
+3. **Use `.as_canonical_u64()`** to compare amounts with `u64` business limits; validate quantities before field arithmetic can wrap
4. **Helper methods** like `require_initialized()` centralize validation logic
5. **Failed assertions** mean no valid proof can be generated
diff --git a/docs/src/miden-bank/03-asset-management.md b/docs/src/miden-bank/03-asset-management.md
index df3e2c07..f45c156c 100644
--- a/docs/src/miden-bank/03-asset-management.md
+++ b/docs/src/miden-bank/03-asset-management.md
@@ -37,12 +37,12 @@ Part 2: Part 3:
## The Asset Type
Miden splits a fungible `Asset` into a `value` word and a `key` word. The `value`
-holds the amount; the `key` is the vault key word. In protocol v0.15 the fungible
+holds the amount; the `key` is the vault key word. In protocol v0.16 the fungible
vault key word has this layout:
```text
Asset value: [amount, 0, 0, 0]
-Asset key: [asset_id_suffix, asset_id_prefix, faucet_suffix | metadata, faucet_prefix]
+Asset key: [asset_class_suffix, asset_class_prefix, faucet_suffix | metadata, faucet_prefix]
━━━━━━━━━━━━━━━━━━━━━━━ ━━━━━━━━━━━━━
key index 2 key index 3
```
@@ -62,12 +62,11 @@ let faucet_suffix = deposit_asset.key[2]; // Faucet ID suffix (+ metadata b
let faucet_prefix = deposit_asset.key[3]; // Faucet ID prefix
```
-:::note v0.15 vault-key layout
-`asset.key[2]` is **not** the raw faucet suffix — the asset's metadata byte
-(composition + a callback flag) is folded into its low 8 bits. For the
-callbacks-disabled fungible assets this bank accepts that byte is constant, so
-`(asset.key[3], asset.key[2])` is still a stable per-faucet identifier. The
-host-side mirror is `FungibleAsset::to_key_word()` indices `[3]` / `[2]`.
+:::note v0.16 asset-ID layout
+`asset.key[2]` is **not** the raw faucet suffix — the composition metadata is
+folded into its low byte. The callback flag is encoded in the faucet account ID
+in v0.16. Thus `(asset.key[3], asset.key[2])` is a stable per-faucet identifier.
+The host-side mirror is `FungibleAsset::to_id_word()` indices `[3]` / `[2]`.
:::
## Receiving Assets with add_asset()
@@ -98,10 +97,10 @@ fn deposit(&mut self, depositor: AccountId, deposit_asset: Asset) {
// NOTE: Initialization guard — enabled in Part 6 (Transaction Scripts)
// self.require_initialized();
- // Verify this is a fungible asset.
- // For fungible assets, value = [amount, 0, 0, 0]; value[1] is always 0.
+ // Verify the asset composition identifies a fungible asset.
+ // Zero padding in the value word alone cannot distinguish NFTs.
assert!(
- deposit_asset.value[1].as_canonical_u64() == 0,
+ deposit_asset.is_fungible(),
"Only fungible assets are supported"
);
@@ -166,9 +165,9 @@ This design allows:
- **Per-asset tracking**: Different token types are tracked separately
- **Unique keys**: The combination ensures no collisions
-Because `asset.key[2]` carries the v0.15 metadata byte in its low bits (not the raw
-faucet suffix), the host side must derive the _same_ key from
-`FungibleAsset::to_key_word()` rather than from `faucet.id().suffix()` directly — the
+Because `asset.key[2]` carries the v0.16 composition metadata in its low byte (not the
+raw faucet suffix), the host side must derive the _same_ ID from
+`FungibleAsset::to_id_word()` rather than from `faucet.id().suffix()` directly — the
test below shows this.
The remaining internal helpers live in a separate, plain `impl BankStorage` block (not
@@ -203,7 +202,15 @@ let new_balance = current_balance - withdraw_amount;
This is not optional - it's a **security requirement** for any financial operation.
:::
-Add this method to your Bank impl block:
+Add this declaration inside your existing `Bank` trait:
+
+```rust title="contracts/bank-account/src/lib.rs"
+/// Withdraw assets back to the depositor.
+#[account_procedure]
+fn withdraw(&mut self, withdraw_asset: Asset, serial_num: Word, tag: Felt, note_type: Felt);
+```
+
+Then add this method inside your existing `impl Bank for BankStorage` block:
```rust title="contracts/bank-account/src/lib.rs"
fn withdraw(
@@ -222,7 +229,7 @@ fn withdraw(
// Verify this is a fungible asset — see `deposit()` for the rationale.
assert!(
- withdraw_asset.value[1].as_canonical_u64() == 0,
+ withdraw_asset.is_fungible(),
"Only fungible assets are supported"
);
@@ -275,7 +282,7 @@ fn create_p2id_note(
_note_type: Felt,
) {
// Placeholder - implemented in Part 7: Output Notes
- // For now, this will cause a compile error if actually called
+ // Calling this placeholder aborts execution
todo!("P2ID note creation - see Part 7")
}
```
@@ -289,11 +296,6 @@ cd contracts/bank-account
miden build
```
-:::note Cosmetic build output
-The Miden compiler prints non-fatal `MAST`-serialization `ERROR` lines on every
-build. They are cosmetic — the build still succeeds and produces the `.masp`.
-:::
-
## Try It: Verify Deposits Work
First, verify your bank-account contract compiles:
@@ -322,7 +324,7 @@ use integration::helpers::{
use miden_client::{
account::{component::{InitStorageData, StorageValueName}, StorageSlotName},
- auth::AuthSchemeId,
+ auth::AuthScheme,
note::NoteAssets,
transaction::RawOutputNote,
Felt, Word,
@@ -354,7 +356,7 @@ async fn deposit_test() -> anyhow::Result<()> {
// Create a faucet to mint test assets
let faucet = builder.add_existing_basic_faucet(
Auth::BasicAuth {
- auth_scheme: AuthSchemeId::Falcon512Poseidon2,
+ auth_scheme: AuthScheme::Falcon512Poseidon2,
},
"TEST",
1000,
@@ -364,7 +366,7 @@ async fn deposit_test() -> anyhow::Result<()> {
// Create note sender account (the depositor)
let sender = builder.add_existing_wallet_with_assets(
Auth::BasicAuth {
- auth_scheme: AuthSchemeId::Falcon512Poseidon2,
+ auth_scheme: AuthScheme::Falcon512Poseidon2,
},
[FungibleAsset::new(faucet.id(), 100)?.into()],
)?;
@@ -434,14 +436,14 @@ async fn deposit_test() -> anyhow::Result<()> {
let init_tx_script = build_tx_script_from_package(init_tx_script_package.as_ref())?;
let init_tx_context = mock_chain
- .build_tx_context(bank_account.id(), &[], &[])?
+ .build_transaction(bank_account.id())
.tx_script(init_tx_script)
.build()?;
let executed_init = init_tx_context.execute().await?;
- bank_account.apply_delta(&executed_init.account_delta())?;
mock_chain.add_pending_executed_transaction(&executed_init)?;
mock_chain.prove_next_block()?;
+ bank_account = mock_chain.committed_account(bank_account.id())?.clone();
println!("Bank initialized successfully");
@@ -451,27 +453,26 @@ async fn deposit_test() -> anyhow::Result<()> {
// Build the transaction context where bank consumes the deposit note
let tx_context = mock_chain
- .build_tx_context(bank_account.id(), &[deposit_note.id()], &[])?
+ .build_transaction(bank_account.id())
+ .authenticated_input_note(deposit_note.id())
.build()?;
// Execute the transaction
let executed_transaction = tx_context.execute().await?;
- // Apply the account delta to the bank account
- bank_account.apply_delta(&executed_transaction.account_delta())?;
-
// Add the executed transaction to the mockchain and prove
mock_chain.add_pending_executed_transaction(&executed_transaction)?;
mock_chain.prove_next_block()?;
+ bank_account = mock_chain.committed_account(bank_account.id())?.clone();
// Create the key for the depositor (sender) in the storage map.
// Key format: [depositor_prefix, depositor_suffix, asset.key[3], asset.key[2]].
- // In v0.15 the fungible-asset vault key is
- // [asset_id_suffix, asset_id_prefix, faucet_suffix | metadata_byte, faucet_prefix],
- // so `key[2]` is the faucet suffix combined with a metadata byte (composition +
- // callback flag) — not the raw faucet suffix. Derive the read key from the asset's
+ // In v0.16 the fungible-asset vault key is
+ // [asset_class_suffix, asset_class_prefix, faucet_suffix | metadata_byte, faucet_prefix],
+ // so `key[2]` is the faucet suffix combined with composition metadata,
+ // not the raw faucet suffix. Derive the read key from the asset's
// actual key word so it matches the key the contract writes.
- let asset_key_word = FungibleAsset::new(faucet.id(), deposit_amount)?.to_key_word();
+ let asset_key_word = FungibleAsset::new(faucet.id(), deposit_amount)?.to_id_word();
let depositor_key = Word::from([
sender.id().prefix().as_felt(),
sender.id().suffix(),
@@ -480,7 +481,7 @@ async fn deposit_test() -> anyhow::Result<()> {
]);
// Get the depositor's balance from the bank's storage using named slot
- let balance = bank_account.storage().get_map_item(&balances_slot, depositor_key)?;
+ let balance = bank_account.storage().get_map_item(&balances_slot, miden_client::account::StorageMapKey::new(depositor_key))?;
// The contract stores `balance` as a `Felt`; reading the map returns the
// single-Felt value widened into a Word at position [0] ([amount, 0, 0, 0]).
@@ -515,21 +516,13 @@ cargo test --package integration --test deposit_test -- --nocapture
Finished `test` profile [unoptimized + debuginfo] target(s)
Running tests/deposit_test.rs
-running 3 tests
-Bank initialized successfully
+running 1 test
Deposit test passed! Deposited 1000 tokens
test deposit_test ... ok
-test deposit_exceeds_max_should_fail ... ok
-test deposit_without_init_should_fail ... ok
-test result: ok. 3 passed; 0 failed; 0 ignored
+test result: ok. 1 passed; 0 failed; 0 ignored
```
-:::note Cosmetic build output
-Each contract build during the test prints non-fatal `MAST`-serialization `ERROR`
-lines from the Miden compiler. They are cosmetic and do not affect the result.
-:::
-
@@ -591,6 +584,7 @@ struct BankStorage {
#[component]
trait Bank {
/// Initialize the bank account, enabling deposits.
+ #[account_procedure]
fn initialize(&mut self);
/// Get the bank-tracked balance for a depositor and specific asset type.
@@ -598,12 +592,15 @@ trait Bank {
/// Named `get_depositor_balance` (not `get_balance`) to avoid colliding with
/// the built-in `ActiveAccount::get_balance` vault method that the account
/// wrapper generates.
+ #[account_procedure]
fn get_depositor_balance(&self, depositor: AccountId, asset: Asset) -> Felt;
/// Deposit an asset into the bank for a specific depositor.
+ #[account_procedure]
fn deposit(&mut self, depositor: AccountId, deposit_asset: Asset);
/// Withdraw assets back to the depositor.
+ #[account_procedure]
fn withdraw(&mut self, withdraw_asset: Asset, serial_num: Word, tag: Felt, note_type: Felt);
}
@@ -636,7 +633,7 @@ impl Bank for BankStorage {
// self.require_initialized();
assert!(
- deposit_asset.value[1].as_canonical_u64() == 0,
+ deposit_asset.is_fungible(),
"Only fungible assets are supported"
);
@@ -684,7 +681,7 @@ impl Bank for BankStorage {
let depositor = active_note::get_sender();
assert!(
- withdraw_asset.value[1].as_canonical_u64() == 0,
+ withdraw_asset.is_fungible(),
"Only fungible assets are supported"
);
@@ -744,7 +741,7 @@ impl BankStorage {
## Key Takeaways
-1. **Asset layout**: `value[0]` = amount; `key[2]` = faucet_suffix + metadata byte (v0.15); `key[3]` = faucet_prefix. Mirror it host-side with `FungibleAsset::to_key_word()` indices `[3]`/`[2]`
+1. **Asset layout**: `value[0]` = amount; `key[2]` = faucet suffix plus composition metadata; `key[3]` = faucet prefix. Mirror it host-side with `FungibleAsset::to_id_word()` indices `[3]`/`[2]`
2. **`native_account::add_asset()`** adds assets to the vault
3. **`native_account::remove_asset()`** removes assets from the vault (Part 7)
4. **Balance tracking** is application-level logic using `StorageMap`
diff --git a/docs/src/miden-bank/04-note-scripts.md b/docs/src/miden-bank/04-note-scripts.md
index 14a2d63c..c37bdda9 100644
--- a/docs/src/miden-bank/04-note-scripts.md
+++ b/docs/src/miden-bank/04-note-scripts.md
@@ -80,7 +80,7 @@ edition = "2021"
crate-type = ["cdylib"]
[dependencies]
-miden = "0.13"
+miden = "=0.14.0"
```
Create the `miden-project.toml`. This is where the note declares its kind and its dependency on the bank account it calls into:
@@ -92,6 +92,7 @@ version = "0.1.0"
[lib]
kind = "note"
+path = "src/lib.rs"
namespace = "miden:deposit-note/miden-deposit-note@0.1.0"
[dependencies]
@@ -99,9 +100,6 @@ miden-core = "*"
miden-protocol = "*"
bank-account = { path = "../bank-account" }
-# WIT for the account component this note calls, produced by building bank-account.
-[package.metadata.miden.dependencies]
-bank-account = { wit = "../bank-account/target/generated-wit/" }
```
Finally, the `.cargo/config.toml` pins the WebAssembly target and the `miden` cfg:
@@ -117,7 +115,7 @@ rustflags = ["--cfg", "miden"]
Key configuration:
- `kind = "note"` - Marks this as a note script
-- `bank-account = { path = "../bank-account" }` and the `[package.metadata.miden.dependencies]` `wit` entry declare the account component this note calls; the `wit` path points at the WIT files produced when `bank-account` is built
+- `bank-account = { path = "../bank-account" }` declares the component this note calls. Compiler 0.10 builds the dependency and reads its interface from the compiled package
## Step 3: Implement the Deposit Note
@@ -150,7 +148,7 @@ impl DepositNote {
let depositor = active_note::get_sender();
// Get all assets attached to this note
- let assets = active_note::get_assets();
+ let assets = active_note::get_initial_assets();
// Deposit each asset into the bank
for asset in assets {
@@ -161,7 +159,7 @@ impl DepositNote {
```
:::info Cross-Component Calls
-The `#[account(bank_account::Bank)] pub struct Wallet;` declaration and the `account.deposit(...)` call use Miden's cross-component binding system. The `#[account(...)]` macro wraps the consuming account so the note can call the bank's `Bank` methods directly. We'll explain exactly how this works in [Part 5: Cross-Component Calls](./cross-component-calls). For now, just know that building `bank-account` first generates the WIT files that `deposit-note` binds against.
+The `#[account(bank_account::Bank)] pub struct Wallet;` declaration and the `account.deposit(...)` call use Miden's cross-component binding system. The `#[account(...)]` macro wraps the consuming account so the note can call the bank's `Bank` methods directly. We'll explain exactly how this works in [Part 5: Cross-Component Calls](./cross-component-calls). When you build `deposit-note`, Compiler 0.10 builds the `bank-account` dependency declared in `miden-project.toml` and reads its interface from the compiled package.
:::
### The #[note] and #[note_script] Attributes
@@ -189,16 +187,16 @@ Returns the `AccountId` of the account that created/sent the note. In our bank:
- The sender is the depositor
- Their ID is used to credit their balance
-### get_assets() - Attached Assets
+### get_initial_assets() - Attached Assets
```rust
-let assets = active_note::get_assets();
+let assets = active_note::get_initial_assets();
for asset in assets {
// Process each asset
}
```
-Returns an iterator over all assets attached to the note.
+Returns a `Vec` containing all assets initially attached to the note. The `for` loop consumes that vector.
### get_storage() - Note Parameters
@@ -207,22 +205,20 @@ let storage = active_note::get_storage();
let first_item = storage[0];
```
-Returns a slice of `Felt` values passed when the note was created. We'll use storage items in the withdraw request note (Part 7).
+Returns a `Vec` containing the storage items passed when the note was created. The indexing example requires at least one item. We'll use storage items in the withdraw request note (Part 7).
## Step 4: Build the Note Script
-:::info Build Order Matters
-Build account components **first** before building note scripts that depend on them. The note script needs the generated WIT files from the account, and the FPI `#[account(...)]` macro reads the bank account's procedure roots from its compiled `.masp` at compile time.
+:::info Dependencies Build Automatically
+Compiler 0.10 resolves and builds the `bank-account` dependency before compiling the note. The `#[account(...)]` macro uses the bank's compiled interface and procedure roots to bind calls to its methods. You can build the note directly using the dependency declaration in `miden-project.toml`.
:::
-```bash title=">_ Terminal"
-# First, ensure bank-account is built (generates WIT + the .masp the note binds against)
-cd contracts/bank-account
-cargo miden build --release
+From the project root:
-# Now build the deposit note
-cd ../deposit-note
-cargo miden build --release
+```bash title=">_ Terminal"
+cd contracts/deposit-note
+miden build --release
+cd ../..
```
@@ -235,10 +231,6 @@ cargo miden build --release
-:::note Cosmetic MAST-serialization errors
-The Miden compiler prints non-fatal `ERROR` lines about `MAST` serialization on every build. They are cosmetic — the build still succeeds and produces the `.masp` package.
-:::
-
## Execution Flow Diagram
```text
@@ -257,31 +249,22 @@ The Miden compiler prints non-fatal `ERROR` lines about `MAST` serialization on
3. Note script runs
depositor = get_sender() → User's AccountId
- assets = get_assets() → [100 tokens]
+ assets = get_initial_assets() → [100 tokens]
account.deposit(depositor, 100 tokens)
4. Bank's deposit() method executes
- - Validates initialization and amount
+ - Validates asset type and amount
+ - Checks initialization once the guard is enabled in Part 6
- Updates balance: balances[User] += 100
- Adds asset to vault
```
## Try It: Verify Deposits Work
-First, verify your deposit-note builds successfully:
+This test verifies the deposit flow end-to-end — building the contracts, initializing the bank, creating a deposit, and checking the balance.
-```bash title=">_ Terminal"
-# Ensure bank-account is built first
-cd contracts/bank-account && cargo miden build --release
-
-# Then build deposit-note
-cd ../deposit-note && cargo miden build --release
-```
-
-This is the first runnable test in the tutorial. It verifies the deposit flow end-to-end — building the bank and deposit-note contracts, creating a deposit, and checking the balance.
-
-:::note Initialization happens before deposits
-The bank's `require_initialized()` guard is active, so a deposit only succeeds once the bank has been initialized. The shipped `deposit_test.rs` initializes the bank first via the init transaction script (which we build in Part 6). The illustrative excerpt below omits that step to keep the focus on the deposit and note-script mechanics; see the shipped test for the complete init-then-deposit flow.
+:::note Preview of the Part 6 Initialization Flow
+The bank inherited from Part 3 still has `require_initialized()` commented out, so deposits currently work without initialization. In [Part 6](./06-transaction-scripts.md), we'll create the initialization transaction script and enable the guard, making initialization mandatory. The test below previews that flow: it initializes the bank, consumes a deposit note, and checks the depositor's balance. Run it after completing Part 6, or use the complete example projects from the repository.
:::
Create the test file:
@@ -290,76 +273,101 @@ Create the test file:
The snippet below illustrates the deposit happy-path. The shipped repository's `examples/miden-bank/integration/tests/deposit_test.rs` is the source of truth and additionally exercises failure paths (`deposit_exceeds_max_should_fail`, `deposit_without_init_should_fail`).
:::
-```rust title="integration/tests/deposit_test.rs (illustrative — see shipped file for the full version)"
+```rust title="integration/tests/deposit_test.rs"
use integration::helpers::{
- build_project_in_dir, create_testing_account_from_package,
+ build_project_in_dir, build_tx_script_from_package, create_testing_account_from_package,
create_testing_note_from_package, AccountCreationConfig, NoteCreationConfig,
};
-use miden_client::account::{component::{InitStorageData, StorageValueName}, StorageSlotName};
+
+use miden_client::{
+ account::{component::{InitStorageData, StorageValueName}, StorageSlotName},
+ auth::AuthScheme,
+ note::NoteAssets,
+ transaction::RawOutputNote,
+ Felt, Word,
+};
use miden_client::asset::{Asset, FungibleAsset};
-use miden_client::auth::AuthSchemeId;
-use miden_client::note::NoteAssets;
-use miden_client::transaction::RawOutputNote;
-use miden_client::{Felt, Word};
use miden_testing::{Auth, MockChain};
use std::{path::Path, sync::Arc};
+/// Storage slot names for the bank account component.
+///
+/// The `initialized` value slot has no schema default, so `AccountComponent::from_package`
+/// requires it to be seeded via `InitStorageData` (otherwise it errors with
+/// `InitValueNotProvided`). The `balances` map slot defaults to empty and needs no entry.
+fn bank_storage_slots() -> (StorageSlotName, StorageSlotName) {
+ let initialized_slot =
+ StorageSlotName::new("bank_account::bank::initialized")
+ .expect("Valid slot name");
+ let balances_slot =
+ StorageSlotName::new("bank_account::bank::balances")
+ .expect("Valid slot name");
+ (initialized_slot, balances_slot)
+}
+
#[tokio::test]
async fn deposit_test() -> anyhow::Result<()> {
- // =========================================================================
- // SETUP: Build contracts and create mock chain
- // =========================================================================
+ // Test that after executing the deposit note, the depositor's balance is updated
let mut builder = MockChain::builder();
- // Create a faucet for test tokens
- let faucet = builder.add_existing_basic_faucet(Auth::BasicAuth { auth_scheme: AuthSchemeId::Falcon512Poseidon2 }, "TEST", 1000, Some(10))?;
+ // Create a faucet to mint test assets
+ let faucet = builder.add_existing_basic_faucet(
+ Auth::BasicAuth {
+ auth_scheme: AuthScheme::Falcon512Poseidon2,
+ },
+ "TEST",
+ 1000,
+ Some(10),
+ )?;
- // Create sender (depositor) wallet
- let sender = builder.add_existing_wallet_with_assets(Auth::BasicAuth { auth_scheme: AuthSchemeId::Falcon512Poseidon2 }, [FungibleAsset::new(faucet.id(), 100)?.into()])?;
+ // Create note sender account (the depositor)
+ let sender = builder.add_existing_wallet_with_assets(
+ Auth::BasicAuth {
+ auth_scheme: AuthScheme::Falcon512Poseidon2,
+ },
+ [FungibleAsset::new(faucet.id(), 100)?.into()],
+ )?;
- // Build bank-account and deposit-note (the shipped test also builds init-tx-script; omitted here for brevity)
+ // Build contracts
let bank_package = Arc::new(build_project_in_dir(
Path::new("../contracts/bank-account"),
true,
)?);
-
let deposit_note_package = Arc::new(build_project_in_dir(
Path::new("../contracts/deposit-note"),
true,
)?);
+ let init_tx_script_package = Arc::new(build_project_in_dir(
+ Path::new("../contracts/init-tx-script"),
+ true,
+ )?);
- // Create the bank account with storage slots.
- //
- // The shipped deposit_test.rs initializes the bank first (via the init
- // transaction script built in Part 6) because `require_initialized()` is
- // active; this excerpt omits that step and focuses on the deposit flow.
- let initialized_slot =
- StorageSlotName::new("bank_account::bank::initialized")
- .expect("Valid slot name");
- let balances_slot =
- StorageSlotName::new("bank_account::bank::balances")
- .expect("Valid slot name");
-
- let mut init_storage_data = InitStorageData::default();
- init_storage_data.insert_value(
- StorageValueName::from_slot_name(&initialized_slot),
- Word::default(),
- )?;
-
+ // Create the bank account. The `initialized` value slot has no schema default, so it must
+ // be seeded (here with a zero Word = uninitialized) or `from_package` errors with
+ // `InitValueNotProvided`; the `balances` map defaults to empty.
+ let (initialized_slot, balances_slot) = bank_storage_slots();
let bank_cfg = AccountCreationConfig {
- init_storage_data,
+ init_storage_data: {
+ let mut data = InitStorageData::default();
+ data.insert_value(
+ StorageValueName::from_slot_name(&initialized_slot),
+ Word::default(),
+ )?;
+ data
+ },
..Default::default()
};
let mut bank_account =
create_testing_account_from_package(bank_package.clone(), bank_cfg)?;
- builder.add_account(bank_account.clone())?;
- // Create the deposit note
+ // Create a fungible asset to deposit
let deposit_amount: u64 = 1000;
let fungible_asset = FungibleAsset::new(faucet.id(), deposit_amount)?;
let note_assets = NoteAssets::new(vec![Asset::Fungible(fungible_asset)])?;
+ // Create the deposit note with assets attached
+ // The sender becomes the depositor
let deposit_note = create_testing_note_from_package(
deposit_note_package.clone(),
sender.id(),
@@ -369,33 +377,59 @@ async fn deposit_test() -> anyhow::Result<()> {
},
)?;
+ // Add bank account and deposit note to mockchain
+ builder.add_account(bank_account.clone())?;
builder.add_output_note(RawOutputNote::Full(deposit_note.clone()));
+
+ // Build the mock chain
let mut mock_chain = builder.build()?;
- // =========================================================================
- // EXECUTE DEPOSIT (the shipped test initializes the bank first; this excerpt omits that step)
- // =========================================================================
+ // *********************************************************************************
+ // STEP 1: INITIALIZE THE BANK VIA TX SCRIPT
+ // *********************************************************************************
+ // Preview the Part 6 flow, where require_initialized() is enabled.
+ // Initialize via a transaction script that calls bank.initialize().
+
+ let init_tx_script = build_tx_script_from_package(init_tx_script_package.as_ref())?;
+
+ let init_tx_context = mock_chain
+ .build_transaction(bank_account.id())
+ .tx_script(init_tx_script)
+ .build()?;
+
+ let executed_init = init_tx_context.execute().await?;
+ mock_chain.add_pending_executed_transaction(&executed_init)?;
+ mock_chain.prove_next_block()?;
+ bank_account = mock_chain.committed_account(bank_account.id())?.clone();
+
+ println!("Bank initialized successfully");
+
+ // *********************************************************************************
+ // STEP 2: DEPOSIT
+ // *********************************************************************************
+
+ // Build the transaction context where bank consumes the deposit note
let tx_context = mock_chain
- .build_tx_context(bank_account.id(), &[deposit_note.id()], &[])?
+ .build_transaction(bank_account.id())
+ .authenticated_input_note(deposit_note.id())
.build()?;
+ // Execute the transaction
let executed_transaction = tx_context.execute().await?;
- bank_account.apply_delta(&executed_transaction.account_delta())?;
+
+ // Add the executed transaction to the mockchain and prove
mock_chain.add_pending_executed_transaction(&executed_transaction)?;
mock_chain.prove_next_block()?;
+ bank_account = mock_chain.committed_account(bank_account.id())?.clone();
- println!("Deposit transaction executed!");
-
- // =========================================================================
- // VERIFY: Check balance was updated
- // =========================================================================
+ // Create the key for the depositor (sender) in the storage map.
// Key format: [depositor_prefix, depositor_suffix, asset.key[3], asset.key[2]].
- // In v0.15 the fungible-asset vault key is
- // [asset_id_suffix, asset_id_prefix, faucet_suffix | metadata_byte, faucet_prefix],
- // so `key[2]` is the faucet suffix combined with a metadata byte (composition +
- // callback flag) — not the raw faucet suffix. Derive the read key from the asset's
+ // In v0.16 the fungible-asset vault key is
+ // [asset_class_suffix, asset_class_prefix, faucet_suffix | metadata_byte, faucet_prefix],
+ // so `key[2]` is the faucet suffix combined with composition metadata,
+ // not the raw faucet suffix. Derive the read key from the asset's
// actual key word so it matches the key the contract writes.
- let asset_key_word = FungibleAsset::new(faucet.id(), deposit_amount)?.to_key_word();
+ let asset_key_word = FungibleAsset::new(faucet.id(), deposit_amount)?.to_id_word();
let depositor_key = Word::from([
sender.id().prefix().as_felt(),
sender.id().suffix(),
@@ -403,7 +437,8 @@ async fn deposit_test() -> anyhow::Result<()> {
asset_key_word[2],
]);
- let balance = bank_account.storage().get_map_item(&balances_slot, depositor_key)?;
+ // Get the depositor's balance from the bank's storage using named slot
+ let balance = bank_account.storage().get_map_item(&balances_slot, miden_client::account::StorageMapKey::new(depositor_key))?;
// The contract stores `balance` as a `Felt`; reading the map returns the
// single-Felt value widened into a Word at position [0] ([amount, 0, 0, 0]).
@@ -416,10 +451,10 @@ async fn deposit_test() -> anyhow::Result<()> {
assert_eq!(
balance, expected_balance,
- "Balance should equal deposited amount"
+ "Depositor balance should equal the deposited amount"
);
- println!("\nPart 4 deposit test passed!");
+ println!("Deposit test passed! Deposited {} tokens", deposit_amount);
Ok(())
}
```
@@ -438,12 +473,12 @@ cargo test --package integration --test deposit_test -- --nocapture
Finished `test` profile [unoptimized + debuginfo] target(s)
Running tests/deposit_test.rs
-running 3 tests
+running 1 test
+Bank initialized successfully
+Deposit test passed! Deposited 1000 tokens
test deposit_test ... ok
-test deposit_exceeds_max_should_fail ... ok
-test deposit_without_init_should_fail ... ok
-test result: ok. 3 passed; 0 failed; 0 ignored
+test result: ok. 1 passed; 0 failed; 0 ignored
```
@@ -463,10 +498,10 @@ pub struct Wallet;
/// # Note Storage (14 Felts)
/// [0-3]: withdraw asset, encoded as [amount, 0, faucet_suffix(+metadata), faucet_prefix].
/// `storage[2]` carries the faucet suffix with the asset's metadata byte in its
-/// low 8 bits (host side: `FungibleAsset::to_key_word()[2]`), not the raw suffix.
+/// low 8 bits (host side: `FungibleAsset::to_id_word()[2]`), not the raw suffix.
/// [4-7]: serial_num (random/unique per note)
/// [8]: tag (P2ID note tag for routing)
-/// [9]: note_type (1 = Public, 2 = Private)
+/// [9]: note_type (1 = Public, 0 = Private)
/// [10-13]: P2ID script_root (MAST root of the P2ID note script, Poseidon2-hashed)
#[note]
struct WithdrawRequestNote;
@@ -482,7 +517,7 @@ impl WithdrawRequestNote {
"Withdraw request requires exactly 14 storage items"
);
- // Asset: reconstruct the v0.15 fungible-asset key/value from the note storage.
+ // Asset: reconstruct the v0.16 fungible-asset ID/value from the note storage.
// key = [0, 0, storage[2], storage[3]] where storage[2] = faucet suffix + metadata
// byte (low 8 bits) and storage[3] = faucet prefix.
// value = [amount, 0, 0, 0]
@@ -542,7 +577,7 @@ impl DepositNote {
let depositor = active_note::get_sender();
// Get all assets attached to this note
- let assets = active_note::get_assets();
+ let assets = active_note::get_initial_assets();
// Deposit each asset into the bank
for asset in assets {
@@ -559,10 +594,10 @@ impl DepositNote {
1. **`#[note]`** marks the struct and impl block, with **`#[note_script]`** on the entry point method `fn run(self, _arg: Word, account: &mut Wallet)`
2. **`#[account(bank_account::Bank)] pub struct Wallet;`** wraps the consuming account so the note can call the bank's methods via `account.deposit(...)`
3. **`active_note::get_sender()`** returns who created the note
-4. **`active_note::get_assets()`** returns assets attached to the note
+4. **`active_note::get_initial_assets()`** returns the assets attached to the note at creation time
5. **`active_note::get_storage()`** returns parameterized data
6. **Note scripts execute once** when consumed - no persistent state
-7. **Build order matters** - account components first, then note scripts
+7. **Dependencies build automatically** - declare the account component in `miden-project.toml`, then build the note with `miden build`
:::tip View Complete Source
See the complete note script implementations:
diff --git a/docs/src/miden-bank/05-cross-component-calls.md b/docs/src/miden-bank/05-cross-component-calls.md
index d8a6e396..d09850cf 100644
--- a/docs/src/miden-bank/05-cross-component-calls.md
+++ b/docs/src/miden-bank/05-cross-component-calls.md
@@ -14,7 +14,7 @@ By the end of this section, you will have:
- Understood how bindings are generated and imported
- Learned the dependency configuration in `miden-project.toml`
-- Explored the WIT interface files
+- Explored the embedded WIT interface
- **Verified cross-component calls work** via the deposit flow
## Building on Part 4
@@ -22,24 +22,32 @@ By the end of this section, you will have:
In Part 4, you wrote `account.deposit(depositor, asset)` in the deposit note. But how does that call actually work? This part explains the binding system:
```text
-┌────────────────────────────────────────────────────────────┐
-│ How Bindings Work │
-├────────────────────────────────────────────────────────────┤
-│ │
-│ bank-account/ │
-│ └── src/lib.rs miden build │
-│ fn deposit() ─────────────▶ generated-wit/ │
-│ fn withdraw() miden-bank-account.wit
-│ │
-│ ┌───────────────────────────┐ │
-│ ▼ │ │
-│ deposit-note/ │ │
-│ └── src/lib.rs │ │
-│ #[account(bank_account::Bank)] │ │
-│ pub struct Wallet; │ │
-│ account.deposit(...) ────────────▶ calls via binding│
-│ │
-└────────────────────────────────────────────────────────────┘
+┌────────────────────────────────────────────────────────────────────────┐
+│ How Bindings Work │
+├────────────────────────────────────────────────────────────────────────┤
+│ │
+│ bank-account/ │
+│ └── src/lib.rs │
+│ #[component] trait Bank │
+│ ├── deposit(...) │
+│ └── withdraw(...) │
+│ │ │
+│ │ miden build │
+│ ▼ │
+│ ┌────────────────────────────────────────────────┐ │
+│ │ bank-account.masp │ │
+│ │ Compiled code + embedded WIT + procedure roots │ │
+│ └────────────────────────────────────────────────┘ │
+│ │ │
+│ │ Read the embedded interface during the note build │
+│ ▼ │
+│ deposit-note/ │
+│ └── src/lib.rs │
+│ #[account(bank_account::Bank)] │
+│ pub struct Wallet; │
+│ account.deposit(...) ──▶ generated binding ──▶ Bank::deposit │
+│ │
+└────────────────────────────────────────────────────────────────────────┘
```
## The Bindings System
@@ -47,23 +55,26 @@ In Part 4, you wrote `account.deposit(depositor, asset)` in the deposit note. Bu
When you build an account component with `miden build`, it generates:
1. **MASM code** - The compiled contract logic
-2. **WIT files** - WebAssembly Interface Type definitions
+2. **Embedded WIT** - WebAssembly Interface Type definitions stored in the package
-Other contracts (note scripts, transaction scripts) import these WIT files to call the account's methods.
+Other contracts (note scripts, transaction scripts) read the package's embedded interface to call the account's methods.
```text
Build Flow:
-┌──────────────────┐ miden build ┌─────────────────────────────────┐
-│ bank-account/ │ ─────────────────▶│ target/generated-wit/ │
-│ src/lib.rs │ │ miden-bank-account.wit │
-│ │ │ (includes world definition) │
-└──────────────────┘ └─────────────────────────────────┘
- │
- ▼
- ┌─────────────────────────────────┐
- │ deposit-note/ │
- │ imports generated bindings │
- └─────────────────────────────────┘
+
+┌────────────────────┐ ┌───────────────────────────────────┐
+│ bank-account/ │ miden build │ bank-account.masp │
+│ src/lib.rs │ ──────────────▶ │ Code + embedded WIT │
+│ Bank component │ │ Account procedure roots │
+└────────────────────┘ └───────────────────────────────────┘
+ │
+ ▼
+ ┌───────────────────────────────────┐
+ │ deposit-note/ │
+ │ #[account(bank_account::Bank)] │
+ │ Generated Wallet bindings │
+ │ account.deposit(...) │
+ └───────────────────────────────────┘
```
## Declaring the Account Wrapper
@@ -108,7 +119,7 @@ impl DepositNote {
let depositor = active_note::get_sender();
// Get all assets attached to this note
- let assets = active_note::get_assets();
+ let assets = active_note::get_initial_assets();
// Deposit each asset into the bank
for asset in assets {
@@ -126,7 +137,7 @@ The binding automatically handles:
## Configuring Dependencies
-Cross-component calls are configured in the note's `miden-project.toml`, which needs **two** dependency entries:
+Cross-component calls are configured in the note's `miden-project.toml`, which declares the bank as a path dependency:
```toml title="contracts/deposit-note/miden-project.toml"
[dependencies]
@@ -134,9 +145,6 @@ miden-core = "*"
miden-protocol = "*"
bank-account = { path = "../bank-account" }
-# WIT for the account component this note calls, produced by building bank-account.
-[package.metadata.miden.dependencies]
-bank-account = { wit = "../bank-account/target/generated-wit/" }
```
### `[dependencies]` path
@@ -151,22 +159,13 @@ This tells `cargo-miden` where to find the source package. Used during the build
- Verify interface compatibility
- Link the compiled MASM code
-### `[package.metadata.miden.dependencies]` WIT
-
-```toml
-[package.metadata.miden.dependencies]
-bank-account = { wit = "../bank-account/target/generated-wit/" }
-```
-
-This points at the WIT interface files for the account component this note calls. The path is the `generated-wit/` directory created when you built the account component.
+### Embedded Interface
-:::warning Both Entries Required
-If either entry is missing, your build will fail with linking or interface errors.
-:::
+Compiler 0.10 embeds the WIT interface in the compiled bank package. The path dependency provides both the interface and the procedure roots. Remove the legacy `[package.metadata.miden.dependencies]` `wit` override: supplying it alongside an embedded interface causes the compiler to reject the build.
## Build Order
-Components must be built in dependency order:
+From the project root, build the account first to inspect its output, then build the note:
```bash title=">_ Terminal"
# 1. Build the account component first
@@ -176,9 +175,12 @@ miden build
# 2. Then build note scripts that depend on it
cd ../deposit-note
miden build
+
+# 3. Return to the project root
+cd ../..
```
-If you build out of order, you'll see errors about missing WIT files.
+If you build a dependent contract directly, compiler 0.10 also builds its path dependencies.
## What Methods Are Available?
@@ -189,9 +191,13 @@ Only the methods declared on the `#[component] trait Bank` are exported through
#[component]
trait Bank {
// EXPORTED: Available through bindings
+ #[account_procedure]
fn initialize(&mut self);
+ #[account_procedure]
fn get_depositor_balance(&self, depositor: AccountId, asset: Asset) -> Felt;
+ #[account_procedure]
fn deposit(&mut self, depositor: AccountId, deposit_asset: Asset);
+ #[account_procedure]
fn withdraw(&mut self, withdraw_asset: Asset, serial_num: Word, tag: Felt, note_type: Felt);
}
```
@@ -213,17 +219,25 @@ The balance getter is named `get_depositor_balance` to avoid colliding with the
## Understanding the Generated WIT
-The WIT files describe the interface. Here's a simplified example:
+The compiler embeds this WIT in the bank package. Its imported core types come from the SDK:
+
+```wit title="Embedded bank interface"
+package miden:bank-account@0.1.0;
+
+use miden:base/core-types@1.0.0;
-```wit title="target/generated-wit/miden-bank-account.wit"
-interface bank-account {
- use miden:types/types.{account-id, asset, felt, word};
+interface bank {
+ use core-types.{account-id, asset, felt, word};
initialize: func();
get-depositor-balance: func(depositor: account-id, asset: asset) -> felt;
deposit: func(depositor: account-id, deposit-asset: asset);
withdraw: func(withdraw-asset: asset, serial-num: word, tag: felt, note-type: felt);
}
+
+world bank-world {
+ export bank;
+}
```
This WIT is what the `#[account(bank_account::Bank)]` macro reads to generate the `Wallet` wrapper's methods.
@@ -249,23 +263,23 @@ The `Wallet` wrapper gives direct method access through the `account` parameter,
## Try It: Verify Bindings Work
-If you completed Part 4 and built both contracts, the bindings are already working! Let's verify:
+After running the builds above, check the bank package from the project root:
```bash title=">_ Terminal"
-# Check that the WIT files were generated
-ls contracts/bank-account/target/generated-wit/
+# Check the compiled package (its interface is embedded)
+ls contracts/bank-account/target/miden/dev/bank-account.masp
```
Expected output
```text
-miden-bank-account.wit
+contracts/bank-account/target/miden/dev/bank-account.masp
```
-These files enable the deposit note's `#[account(bank_account::Bank)]` wrapper to call `account.deposit()`.
+The embedded interface enables the deposit note's `#[account(bank_account::Bank)]` wrapper to call `account.deposit()`.
## Common Issues
@@ -275,12 +289,12 @@ These files enable the deposit note's `#[account(bank_account::Bank)]` wrapper t
error: cannot find module `bindings`
```
-**Cause**: The account component wasn't built, or the WIT path is wrong.
+**Cause**: The account path dependency is missing or points to the wrong project.
**Solution**:
1. Build the account: `cd contracts/bank-account && miden build`
-2. Verify the WIT path in `miden-project.toml` points to `target/generated-wit/`
+2. Verify the `bank-account` path dependency in `miden-project.toml` points to the account project; remove legacy `wit` overrides
### "Method not found" Error
@@ -300,12 +314,12 @@ error: dependency 'bank-account' not found
**Cause**: One of the dependency entries in `miden-project.toml` is missing or has the wrong path.
-**Solution**: Ensure both `[dependencies]` (`bank-account = { path = "../bank-account" }`) and `[package.metadata.miden.dependencies]` (`bank-account = { wit = "../bank-account/target/generated-wit/" }`) are present with correct paths.
+**Solution**: Add `bank-account = { path = "../bank-account" }` under `[dependencies]` and remove the legacy `wit` override.
## Key Takeaways
-1. **Build accounts first** - They generate WIT files that note scripts need
-2. **Two dependency entries** - Both `[dependencies]` (`path`) and `[package.metadata.miden.dependencies]` (`wit`) in `miden-project.toml` are required
+1. **Declare the path dependency** - The compiler builds the account package and reads its embedded WIT
+2. **Embedded interface** - Declare the path under `[dependencies]`; compiler 0.10 reads the interface from the compiled dependency. Do not add the legacy `wit` override.
3. **Account wrapper pattern** - `#[account(bank_account::Bank)] pub struct Wallet;` exposes the component's methods on the `account` parameter
4. **Only trait methods** - Methods on the private `impl BankStorage` helpers aren't exposed in bindings
5. **Note and tx scripts share the pattern** - Both receive the account wrapper as a parameter (Part 6)
diff --git a/docs/src/miden-bank/06-transaction-scripts.md b/docs/src/miden-bank/06-transaction-scripts.md
index 8f8cb6b8..fea8bdea 100644
--- a/docs/src/miden-bank/06-transaction-scripts.md
+++ b/docs/src/miden-bank/06-transaction-scripts.md
@@ -8,6 +8,8 @@ description: "Learn how to write transaction scripts for account initialization
In this section, you'll learn how to write transaction scripts - code that the account owner explicitly executes. We'll implement an initialization script that enables the bank to accept deposits.
+The companion testnet example uses `AuthSingleSig` with Falcon512Poseidon2 and saves the bank owner's key in its keystore. This authentication component enforces ownership. The `NoAuth` component in our MockChain test helper is only for isolated tests and must not be used to deploy the bank to a live network.
+
## What You'll Build in This Part
By the end of this section, you will have:
@@ -22,30 +24,32 @@ By the end of this section, you will have:
In Parts 4-5, you created note scripts that execute when notes are consumed. Now you'll create a transaction script - code the account owner explicitly runs:
```text
-┌────────────────────────────────────────────────────────────────┐
-│ Script Types Comparison │
-├────────────────────────────────────────────────────────────────┤
-│ │
-│ Note Scripts (Parts 4-5) Transaction Scripts (Part 6)│
-│ ───────────────────────── ────────────────────────────│
-│ • Triggered by note consumption • Explicitly called by owner│
-│ • Import bindings via modules • Receive account parameter │
-│ • Process incoming assets • Setup, admin operations │
-│ │
-│ deposit-note/ init-tx-script/ │
-│ └── calls bank_account::deposit() └── calls account.initialize()
-│ │
-└────────────────────────────────────────────────────────────────┘
+┌────────────────────────────────────────────────────────────────────────┐
+│ Script Types Comparison │
+├────────────────────────────────────────────────────────────────────────┤
+│ │
+│ Note Scripts (Parts 4-5) Transaction Scripts (Part 6) │
+│ ────────────────────────────── ────────────────────────────── │
+│ Triggered by note consumption Attached to a transaction │
+│ Receive account: &mut Wallet Receive account: &mut Wallet │
+│ Read active_note:: context Run setup / owner operations │
+│ Process incoming assets Authorized by account auth │
+│ │
+│ deposit-note/ init-tx-script/ │
+│ └── account.deposit(...) └── account.initialize() │
+│ │
+└────────────────────────────────────────────────────────────────────────┘
```
## Transaction Scripts vs Note Scripts
-| Aspect | Transaction Script | Note Script |
-| ---------- | ---------------------------------- | -------------------------------- |
-| Initiation | Explicitly called by account owner | Triggered when note is consumed |
-| Access | Direct account method access | Must call through bindings |
-| Use case | Setup, owner operations | Receiving messages/assets |
-| Parameter | `account: &mut Wallet` | Note context via `active_note::` |
+| Aspect | Transaction Script | Note Script |
+| ---------- | ---------------------------- | ---------------------------------- |
+| Initiation | Selected for the transaction | Triggered when note is consumed |
+| Access | Account wrapper bindings | Account wrapper bindings |
+| Use case | Setup, owner operations | Receiving messages/assets |
+| Parameter | `account: &mut Wallet` | `account: &mut Wallet` |
+| Context | Active account | Active account and `active_note::` |
**Use transaction scripts for:**
@@ -83,7 +87,7 @@ edition = "2021"
crate-type = ["cdylib"]
[dependencies]
-miden = "0.13"
+miden = "=0.14.0"
```
Create the `miden-project.toml`:
@@ -95,6 +99,7 @@ version = "0.1.0"
[lib]
kind = "tx-script"
+path = "src/lib.rs"
namespace = "miden:base/transaction-script@1.0.0"
[dependencies]
@@ -102,8 +107,6 @@ miden-core = "*"
miden-protocol = "*"
bank-account = { path = "../bank-account" }
-[package.metadata.miden.dependencies]
-bank-account = { wit = "../bank-account/target/generated-wit/" }
```
Create the `.cargo/config.toml`:
@@ -120,7 +123,7 @@ Key configuration:
- `kind = "tx-script"` - Marks this as a transaction script (not `account-component` or `note`)
- `namespace = "miden:base/transaction-script@1.0.0"` - The standard transaction-script namespace
-- The `bank-account` path dependency plus the `[package.metadata.miden.dependencies]` WIT entry let the script call into the account component (same pattern as the note scripts)
+- The `bank-account` path dependency lets the script call the account component through the interface embedded in its compiled package
## Step 3: Implement the Transaction Script
@@ -146,7 +149,7 @@ pub struct Wallet;
/// 1. Transaction is created with this script attached
/// 2. Script executes in the context of the bank account
/// 3. Calls `account.initialize()` to enable deposits
-/// 4. Bank account is now "deployed" and visible on chain
+/// 4. Bank is ready to process deposits after the transaction commits
///
/// # Arguments
/// * `_arg` - Transaction script argument (unused in this script)
@@ -191,14 +194,16 @@ The `Wallet` type is generated by the `#[account(...)]` attribute and provides a
## The Native Account Binding
-Both note scripts and transaction scripts bind the native account with `#[account(bank_account::Bank)]` and call its methods directly on the `&mut Wallet` parameter. The difference is the trigger and the available context:
+Both note scripts and transaction scripts bind the native account with `#[account(bank_account::Bank)]` and call its methods directly on the `&mut Wallet` parameter. The entrypoints below belong in separate note-script and transaction-script contracts; the note method goes inside an `impl` marked `#[note]`. The difference is the trigger and the available context:
```rust
// Note script: triggered by note consumption, has access to note context.
#[note_script]
fn run(self, _arg: Word, account: &mut Wallet) {
let depositor = active_note::get_sender(); // note context
- account.deposit(depositor, asset); // native-account method
+ for asset in active_note::get_initial_assets() {
+ account.deposit(depositor, asset); // native-account method
+ }
}
// Transaction script: explicitly run by the owner, no note context.
@@ -216,16 +221,14 @@ The `Wallet` wrapper provides:
## Step 4: Build the Transaction Script
-Build in dependency order. The transaction script calls into the bank account via the FPI `#[account(...)]` macro, which reads the account's procedure roots from its compiled `.masp` at build time, so the `bank-account` component must be built first:
+Compiler 0.10 resolves and builds the `bank-account` path dependency automatically. The `#[account(...)]` macro reads the interface and procedure roots from the compiled dependency to generate the script's account bindings.
-```bash title=">_ Terminal"
-# First, build the account component (generates WIT files and its .masp)
-cd contracts/bank-account
-cargo miden build --release
+From the project root, build the transaction script directly:
-# Then build the transaction script
-cd ../init-tx-script
-cargo miden build --release
+```bash title=">_ Terminal"
+cd contracts/init-tx-script
+miden build --release
+cd ../..
```
@@ -238,48 +241,44 @@ cargo miden build --release
-:::note Cosmetic build errors
-The Miden compiler prints non-fatal `MAST`-serialization `ERROR` lines on every build. They are cosmetic — the build still succeeds and produces the `.masp` package.
-:::
-
## Account Deployment Pattern
-In Miden, accounts are only visible on-chain after their first state change. Transaction scripts are commonly used for this "deployment":
+A public account becomes visible on-chain when its first transaction commits. The bank's `initialized` flag is separate from deployment: it controls whether the bank's deposit and withdrawal methods can run.
+
+The live example first consumes a funding note to obtain native tokens for fees. That transaction deploys the account while the bank is still uninitialized. The owner then submits the initialization script:
```text
-Execution Flow:
-
-1. Account owner creates transaction with init-tx-script
- ┌───────────────────────────────────────┐
- │ Transaction │
- │ Account: Bank's AccountId │
- │ Script: init-tx-script │
- └───────────────────────────────────────┘
-
-2. Transaction executes
- ┌───────────────────────────────────────┐
- │ run(_arg, account) │
- │ └─ account.initialize() │
- │ └─ Sets initialized flag to 1 │
- └───────────────────────────────────────┘
-
-3. Account state updated
- ┌───────────────────────────────────────┐
- │ Bank Account │
- │ Storage[0] = [1, 0, 0, 0] ← Initialized
- │ Now visible on-chain │
- └───────────────────────────────────────┘
+┌────────────────────────────────────────────────────────────────────────┐
+│ Initialization Flow │
+├────────────────────────────────────────────────────────────────────────┤
+│ │
+│ 1. OWNER SIGNS THE INITIALIZATION TRANSACTION │
+│ ┌────────────────────────────────────────────┐ │
+│ │ Account: Bank's AccountId │ │
+│ │ Script: init-tx-script │ │
+│ │ AuthSingleSig verifies the owner signature │ │
+│ └────────────────────────────────────────────┘ │
+│ │ │
+│ ▼ │
+│ 2. TRANSACTION SCRIPT EXECUTES │
+│ ┌────────────────────────────────────────┐ │
+│ │ run(_arg, account) │ │
+│ │ └── account.initialize() │ │
+│ │ └── Sets the initialized flag to 1 │ │
+│ └────────────────────────────────────────┘ │
+│ │ │
+│ ▼ │
+│ 3. TRANSACTION COMMITS │
+│ ┌──────────────────────────────────────────────────────────┐ │
+│ │ bank_account::bank::initialized = [1, 0, 0, 0] │ │
+│ │ Bank can now process deposits and withdrawals │ │
+│ │ Funding already deployed the account in the live example │ │
+│ └──────────────────────────────────────────────────────────┘ │
+│ │
+└────────────────────────────────────────────────────────────────────────┘
```
-Before initialization:
-
-- Account exists locally but isn't visible on the network
-- Cannot receive notes or interact with other accounts
-
-After initialization:
-
-- Account is "deployed" and visible
-- Can receive deposits and interact normally
+Before initialization, other accounts can already create notes addressed to the bank, but its deposit and withdrawal methods reject them. After initialization, those methods can process the notes. A funding P2ID is handled by `BasicWallet` and does not credit any depositor's ledger balance.
## Using Script Arguments
@@ -288,18 +287,17 @@ The `_arg` parameter can pass data to the script:
```rust title="Example: Parameterized script"
#[tx_script]
fn run(arg: Word, account: &mut Wallet) {
- // Use arg as configuration
- let config_value = arg[0];
- account.configure(config_value);
+ assert!(arg[0].as_canonical_u64() == 42, "Expected initialization argument");
+ account.initialize();
}
```
-When creating the transaction, provide the argument:
+When creating the transaction, pass the argument with `tx_script_args`:
```rust title="Integration code (not contract code)"
-let tx_script_args = Word::from([felt!(42), felt!(0), felt!(0), felt!(0)]);
+let tx_script_args = Word::from([42u32, 0, 0, 0]);
let tx_context = mock_chain
- .build_tx_context(bank_account.id(), &[], &[])?
+ .build_transaction(bank_account.id())
.tx_script(init_tx_script)
.tx_script_args(tx_script_args) // Pass the argument
.build()?;
@@ -317,7 +315,7 @@ use integration::helpers::{
use miden_client::{
account::{component::{InitStorageData, StorageValueName}, StorageSlotName},
- auth::AuthSchemeId,
+ auth::AuthScheme,
Word,
};
use miden_testing::{Auth, MockChain};
@@ -372,7 +370,7 @@ async fn init_test() -> anyhow::Result<()> {
let mut builder = MockChain::builder();
builder.add_existing_basic_faucet(
Auth::BasicAuth {
- auth_scheme: AuthSchemeId::Falcon512Poseidon2,
+ auth_scheme: AuthScheme::Falcon512Poseidon2,
},
"TEST",
10_000_000,
@@ -385,14 +383,14 @@ async fn init_test() -> anyhow::Result<()> {
let init_tx_script = build_tx_script_from_package(init_tx_script_package.as_ref())?;
let init_tx_context = mock_chain
- .build_tx_context(bank_account.id(), &[], &[])?
+ .build_transaction(bank_account.id())
.tx_script(init_tx_script)
.build()?;
let executed_init = init_tx_context.execute().await?;
- bank_account.apply_delta(&executed_init.account_delta())?;
mock_chain.add_pending_executed_transaction(&executed_init)?;
mock_chain.prove_next_block()?;
+ bank_account = mock_chain.committed_account(bank_account.id())?.clone();
// Verify initialized flag flipped to 1
let after = bank_account.storage().get_item(&initialized_slot)?;
@@ -412,7 +410,7 @@ A few things to note in this test:
- The slot name is `bank_account::bank::initialized` (the namespace is `bank_account`, not `miden_bank_account`).
- The `initialized` value slot has **no schema default**, so it must be seeded via `InitStorageData` or `AccountComponent::from_package` errors with `InitValueNotProvided`. Only the `balances` map slot defaults to empty.
-- A `kind = "tx-script"` contract compiles to a `TransactionScript`-kind package, **not** an `Executable`. So `unwrap_program()` / `TransactionScript::from_package` do not apply — the `build_tx_script_from_package` helper locates the entry export and builds the script via `TransactionScript::from_parts`.
+- A `kind = "tx-script"` contract exposes its entry procedure with `#[transaction_script]`. The `build_tx_script_from_package` helper loads it with `TransactionScript::from_package`.
## Enable the Initialization Guard
@@ -435,7 +433,7 @@ With this change, deposits and withdrawals will fail unless the bank has been in
## Try It: Verify Initialization
-Run the companion init test to verify the transaction script correctly flips the initialized flag:
+From the project root, run the companion init test to verify the transaction script correctly flips the initialized flag:
```bash title=">_ Terminal"
cargo test --package integration --test init_test -- --nocapture
@@ -466,13 +464,13 @@ Your actual output may include additional trace lines from the Miden VM or MockC
:::
:::tip Troubleshooting
-**"Cannot find module bindings"**: The bank-account wasn't built. Run `cargo miden build --release` in `contracts/bank-account` first — the FPI `#[account(...)]` macro reads its procedure roots from the compiled `.masp`.
+**Account binding errors**: Check that `#[account(bank_account::Bank)]` matches the dependency name and exported component trait. Declare the `bank-account` path under `[dependencies]` and remove legacy `wit` overrides. Rebuild the transaction script with `miden build --release`; the compiler builds its account dependency automatically.
-**"Dependency not found"**: Check that the `bank-account` path dependency and the `[package.metadata.miden.dependencies]` WIT entry are both present in `miden-project.toml` with correct paths.
+**"Dependency not found"**: Check that the `bank-account` path dependency in `miden-project.toml` points to the account project.
:::
:::note Live network bin
-The MockChain test above is the source of truth for verifying this flow. The live-network bin (`cargo run --bin initialize`) also runs against a testnet node.
+The MockChain test above is the source of truth for verifying this flow. The live-network bin (`cargo run --bin initialize`) also runs against a testnet node. It prints the new bank ID and waits while you request native tokens from the testnet faucet, then consumes the funding note and submits the signed initialization transaction.
:::
## What We've Built So Far
@@ -509,7 +507,7 @@ pub struct Wallet;
/// 1. Transaction is created with this script attached
/// 2. Script executes in the context of the bank account
/// 3. Calls `account.initialize()` to enable deposits
-/// 4. Bank account is now "deployed" and visible on chain
+/// 4. Bank is ready to process deposits after the transaction commits
///
/// # Arguments
/// * `_arg` - Transaction script argument (unused in this script)
@@ -530,7 +528,7 @@ edition = "2021"
crate-type = ["cdylib"]
[dependencies]
-miden = "0.13"
+miden = "=0.14.0"
```
```toml title="contracts/init-tx-script/miden-project.toml"
@@ -540,6 +538,7 @@ version = "0.1.0"
[lib]
kind = "tx-script"
+path = "src/lib.rs"
namespace = "miden:base/transaction-script@1.0.0"
[dependencies]
@@ -547,8 +546,6 @@ miden-core = "*"
miden-protocol = "*"
bank-account = { path = "../bank-account" }
-[package.metadata.miden.dependencies]
-bank-account = { wit = "../bank-account/target/generated-wit/" }
```
```toml title="contracts/init-tx-script/.cargo/config.toml"
@@ -566,9 +563,9 @@ rustflags = ["--cfg", "miden"]
1. **`#[tx_script]`** marks the entry point with signature `fn run(_arg: Word, account: &mut Wallet)`
2. **`#[account(...)]`** binds a `Wallet` wrapper to the native account's component, enabling direct method calls
3. **Direct account access** - Methods called on the `account` parameter, not via module imports
-4. **Owner-initiated** - Only the account owner can execute transaction scripts
+4. **Authorization** - The account's authentication component controls which transactions can update it; a transaction script alone does not enforce ownership
5. **Deployment pattern** - First state change makes account visible on-chain
-6. **TransactionScript-kind package** - Unlike an executable, the compiled tx-script is extracted with `build_tx_script_from_package`
+6. **Loading a transaction script** - `build_tx_script_from_package` uses `TransactionScript::from_package` to load the compiled entry procedure
:::tip View Complete Source
See the complete transaction script implementation in [contracts/init-tx-script/src/lib.rs](https://github.com/0xMiden/miden-tutorials/blob/main/examples/miden-bank/contracts/init-tx-script/src/lib.rs).
diff --git a/docs/src/miden-bank/07-output-notes.md b/docs/src/miden-bank/07-output-notes.md
index e911c68a..ff47ccb6 100644
--- a/docs/src/miden-bank/07-output-notes.md
+++ b/docs/src/miden-bank/07-output-notes.md
@@ -72,7 +72,7 @@ P2ID (Pay-to-ID) is a standard note pattern in Miden that sends assets to a spec
In Part 3, we introduced `withdraw()` and `create_p2id_note()` as skeletons. Now we'll complete them with full implementations.
-Update `contracts/bank-account/src/lib.rs`:
+Replace the existing `withdraw()` method in `impl Bank for BankStorage` in `contracts/bank-account/src/lib.rs`. Keep the other methods:
```rust title="contracts/bank-account/src/lib.rs"
#[component]
@@ -95,7 +95,7 @@ impl Bank for BankStorage {
// Verify this is a fungible asset — see `deposit()` for the rationale.
assert!(
- withdraw_asset.value[1].as_canonical_u64() == 0,
+ withdraw_asset.is_fungible(),
"Only fungible assets are supported"
);
@@ -131,7 +131,7 @@ impl Bank for BankStorage {
}
```
-The withdraw method derives the balance-map key inline by packing `depositor.prefix`, `depositor.suffix`, `withdraw_asset.key[3]`, and `withdraw_asset.key[2]` into a `Word`. In the v0.15 fungible-asset vault-key layout, `asset.key[3]` is the faucet id prefix and `asset.key[2]` is the faucet id suffix with the asset's metadata byte folded into its low 8 bits — so `key[2]` is NOT the raw faucet suffix. `withdraw()` and `deposit()` derive the key the same way so a withdrawal reconstructs the exact key the deposit was recorded under.
+The withdraw method derives the balance-map key inline by packing `depositor.prefix`, `depositor.suffix`, `withdraw_asset.key[3]`, and `withdraw_asset.key[2]` into a `Word`. In the v0.16 fungible-asset ID layout, `asset.key[3]` is the faucet ID prefix and `asset.key[2]` is the faucet ID suffix with the composition metadata folded into its low byte — so `key[2]` is NOT the raw faucet suffix. `withdraw()` and `deposit()` derive the key the same way so a withdrawal reconstructs the exact key the deposit was recorded under.
:::danger Critical Security: Balance Validation
Always validate `current_balance >= withdraw_amount` BEFORE subtraction. Miden uses modular field arithmetic - subtracting a larger value silently wraps to a massive positive number!
@@ -146,11 +146,11 @@ let storage = active_note::get_storage();
let script_root = Word::from([storage[10], storage[11], storage[12], storage[13]]);
```
-This design keeps the bank contract version-agnostic: callers embed the P2ID script root they want to use into the note storage when they create the withdraw-request note. The test obtains the correct value at test time with `P2idNote::script_root()` from the `miden_client` crate. In v0.15 `script_root()` returns a `NoteScriptRoot`, so wrap it in `Word::from(...)` before indexing its felts (see the test below).
+This design keeps the bank contract version-agnostic: callers embed the P2ID script root they want to use into the note storage when they create the withdraw-request note. The test obtains the correct value at test time with `P2idNote::script_root()` from the `miden_client` crate. In v0.16 `script_root()` returns a `NoteScriptRoot`, so wrap it in `Word::from(...)` before indexing its felts (see the test below).
## Step 3: Implement create_p2id_note
-This replaces the `todo!()` placeholder from Part 3. The `#[component]` macro exports only the `Bank` trait methods, so `create_p2id_note` (along with the other private helper `require_initialized`) lives in a plain `impl BankStorage` block, NOT inside `impl Bank for BankStorage`. Add the full implementation:
+This replaces the `todo!()` placeholder from Part 3. The `#[component]` macro exports only the `Bank` trait methods, so `create_p2id_note` (along with the other private helper `require_initialized`) lives in a plain `impl BankStorage` block, NOT inside `impl Bank for BankStorage`. Replace the existing helper with the implementation below, keeping `require_initialized()` in the same block:
```rust title="contracts/bank-account/src/lib.rs"
/// Internal helpers that are not part of the component's exported WIT API.
@@ -173,7 +173,7 @@ impl BankStorage {
script_root: Word,
) {
// Convert the passed tag Felt to a Tag and note_type Felt to a NoteType.
- // note_type: 1 = Public (stored on-chain), 2 = Private (off-chain)
+ // note_type: 1 = Public (stored on-chain), 0 = Private (off-chain)
let tag = Tag::from(tag);
let note_type = NoteType::from(note_type);
@@ -218,7 +218,7 @@ Note the order: `suffix` comes before `prefix`. This is the opposite of how `Acc
| Parameter | Type | Description |
| ----------- | ----------- | -------------------------------- |
| `tag` | `Tag` | Routing information for the note |
-| `note_type` | `NoteType` | Public (1) or Private (2) |
+| `note_type` | `NoteType` | Public (1) or Private (0) |
| `recipient` | `Recipient` | Who can consume the note |
## Step 4: Create the Withdraw Request Note Project
@@ -243,7 +243,7 @@ edition = "2021"
crate-type = ["cdylib"]
[dependencies]
-miden = "0.13"
+miden = "=0.14.0"
```
```toml title="contracts/withdraw-request-note/miden-project.toml"
@@ -253,6 +253,7 @@ version = "0.1.0"
[lib]
kind = "note"
+path = "src/lib.rs"
namespace = "miden:withdraw-request-note/miden-withdraw-request-note@0.1.0"
[dependencies]
@@ -260,9 +261,6 @@ miden-core = "*"
miden-protocol = "*"
bank-account = { path = "../bank-account" }
-# WIT for the account component this note calls, produced by building bank-account.
-[package.metadata.miden.dependencies]
-bank-account = { wit = "../bank-account/target/generated-wit/" }
```
```toml title="contracts/withdraw-request-note/.cargo/config.toml"
@@ -273,7 +271,7 @@ target = "wasm32-wasip2"
rustflags = ["--cfg", "miden"]
```
-The note declares `bank-account` as both a path dependency (so its types are visible) and as a `[package.metadata.miden.dependencies]` WIT dependency pointing at bank-account's generated WIT. That WIT is produced by building bank-account first — see the build order below.
+The note declares `bank-account` as a path dependency. Compiler 0.10 builds the account package and reads its embedded interface; do not add a separate `wit` override.
## Step 5: Implement the Withdraw Request Note Script
@@ -304,13 +302,13 @@ pub struct Wallet;
///
/// # Note Storage (14 Felts)
/// [0-3]: withdraw asset, encoded as [amount, 0, faucet_suffix(+metadata), faucet_prefix].
-/// Reconstructed into the v0.15 vault key [0, 0, storage[2], storage[3]] and value
+/// Reconstructed into the v0.16 asset ID [0, 0, storage[2], storage[3]] and value
/// [amount, 0, 0, 0]. `storage[2]` carries the faucet suffix with the asset's metadata
-/// byte in its low 8 bits (host side: `FungibleAsset::to_key_word()[2]`), not the raw
+/// composition bits in its low byte (host side: `FungibleAsset::to_id_word()[2]`), not the raw
/// suffix — so the bank reconstructs exactly the key the depositor's asset had.
/// [4-7]: serial_num (random/unique per note)
/// [8]: tag (P2ID note tag for routing)
-/// [9]: note_type (1 = Public, 2 = Private)
+/// [9]: note_type (1 = Public, 0 = Private)
/// [10-13]: P2ID script_root (MAST root of the P2ID note script, Poseidon2-hashed).
/// Consumed by the bank account directly from the active note's storage inside
/// `Bank::withdraw`, so it never appears on the call — this keeps that
@@ -329,7 +327,7 @@ impl WithdrawRequestNote {
"Withdraw request requires exactly 14 storage items"
);
- // Asset: reconstruct the v0.15 fungible-asset key/value from the note storage.
+ // Asset: reconstruct the v0.16 fungible-asset ID/value from the note storage.
// key = [0, 0, storage[2], storage[3]] where storage[2] = faucet suffix + metadata
// byte (low 8 bits) and storage[3] = faucet prefix.
// value = [amount, 0, 0, 0]
@@ -344,7 +342,7 @@ impl WithdrawRequestNote {
// Tag: single Felt for P2ID note routing
let tag = storage[8];
- // Note type: 1 = Public, 2 = Private
+ // Note type: 1 = Public, 0 = Private
let note_type = storage[9];
// Note: P2ID script root (storage[10..13]) is read by the bank account directly
@@ -358,7 +356,7 @@ impl WithdrawRequestNote {
}
```
-The `#[account(bank_account::Bank)]` macro generates the `Wallet` wrapper from bank-account's WIT, giving the note script a typed `account.withdraw(...)` call. The macro reads bank-account's procedure roots from its compiled `.masp` at compile time, which is why bank-account must be built first.
+The `#[account(bank_account::Bank)]` macro generates the `Wallet` wrapper from bank-account's WIT, giving the note script a typed `account.withdraw(...)` call. The automatic path dependency in `miden-project.toml` builds bank-account and supplies its compiled interface and procedure roots to the macro.
### Note Storage Layout
@@ -366,44 +364,43 @@ The withdraw-request-note uses 14 Felt storage items:
```text
Note Storage (14 Felts):
-┌───────────────────────────────────────────────────────────────────────────┐
-│ Index │ Value │ Description │
-├───────┼─────────────────┼─────────────────────────────────────────────────┤
-│ 0 │ amount │ Token amount to withdraw │
-│ 1 │ 0 │ Reserved (always 0 for fungible) │
-│ 2 │ faucet_suffix* │ Faucet ID suffix + metadata byte (v0.15 key[2]) │
-│ 3 │ faucet_prefix │ Faucet ID prefix (identifies asset type) │
-│ 4-7 │ serial_num │ Unique ID for the output P2ID note (4 Felts) │
-│ 8 │ tag │ Note routing tag for P2ID note │
-│ 9 │ note_type │ 1 (Public) or 2 (Private) │
-│ 10-13 │ script_root │ P2ID script MAST root (Poseidon2-hashed, 4 Felts)│
-└───────────────────────────────────────────────────────────────────────────┘
+┌───────┬────────────────┬───────────────────────────────────────────────────┐
+│ Index │ Value │ Description │
+├───────┼────────────────┼───────────────────────────────────────────────────┤
+│ 0 │ amount │ Token amount to withdraw │
+├───────┼────────────────┼───────────────────────────────────────────────────┤
+│ 1 │ 0 │ Reserved (always 0 for fungible) │
+├───────┼────────────────┼───────────────────────────────────────────────────┤
+│ 2 │ faucet_suffix* │ Faucet ID suffix + metadata byte (v0.16 key[2]) │
+├───────┼────────────────┼───────────────────────────────────────────────────┤
+│ 3 │ faucet_prefix │ Faucet ID prefix (identifies asset type) │
+├───────┼────────────────┼───────────────────────────────────────────────────┤
+│ 4-7 │ serial_num │ Unique ID for the output P2ID note (4 Felts) │
+├───────┼────────────────┼───────────────────────────────────────────────────┤
+│ 8 │ tag │ Note routing tag for P2ID note │
+├───────┼────────────────┼───────────────────────────────────────────────────┤
+│ 9 │ note_type │ 1 (Public) or 0 (Private) │
+├───────┼────────────────┼───────────────────────────────────────────────────┤
+│ 10-13 │ script_root │ P2ID script MAST root (Poseidon2-hashed, 4 Felts) │
+└───────┴────────────────┴───────────────────────────────────────────────────┘
```
-\*Index 2 is the v0.15 fungible-asset vault key's `key[2]`: the faucet ID suffix with the asset's metadata byte folded into its low 8 bits, not the raw suffix. The host side encodes it from `FungibleAsset::new(faucet.id(), amount)?.to_key_word()[2]`.
+\*Index 2 is the v0.16 fungible-asset ID's `key[2]`: the faucet ID suffix with the composition metadata folded into its low byte, not the raw suffix. The host side encodes it from `FungibleAsset::new(faucet.id(), amount)?.to_id_word()[2]`.
:::note Why the Asset is in Inputs
-Unlike the deposit note which gets assets from `active_note::get_assets()`, the withdraw request note doesn't carry assets. Instead, the asset to withdraw is specified in the note inputs. The bank then withdraws from its own vault based on these inputs.
+Unlike the deposit note which gets its creation-time assets from `active_note::get_initial_assets()`, the withdraw request note doesn't carry assets. Instead, the asset to withdraw is specified in the note inputs. The bank then withdraws from its own vault based on these inputs.
:::
## Step 6: Build All Components
-Build in dependency order — bank-account first so its WIT and compiled `.masp` exist before the note that depends on them:
+Build the withdrawal note from the workspace root. Its automatic path dependency also rebuilds the bank account:
```bash title=">_ Terminal"
-# 1. Build the account component (generates WIT files and the .masp)
-cd contracts/bank-account
-cargo miden build --release
-
-# 2. Build the withdraw request note
-cd ../withdraw-request-note
-cargo miden build --release
+cd contracts/withdraw-request-note
+miden build --release
+cd ../..
```
-:::note Cosmetic build errors
-The Miden compiler prints non-fatal `MAST`-serialization `ERROR` lines on every build. They are cosmetic — the build still succeeds and produces the package. You can ignore them.
-:::
-
## Try It: Verify Withdrawals Work
Let's test the complete withdraw flow. This test:
@@ -411,15 +408,18 @@ Let's test the complete withdraw flow. This test:
1. Creates a bank account and initializes it
2. Creates a deposit note and processes it
3. Creates a withdraw-request note with the 14-Felt storage layout
-4. Processes the withdrawal and verifies a P2ID output note is created
+4. Processes the withdrawal and verifies the expected P2ID output note
+5. Rejects consumption by another account, then credits the depositor when they consume it
-A few v0.15 host-side details to note:
+The test exercises both public and private output notes. MockChain is supplied with the full expected note in either case; on a live network, private note details must reach the recipient separately.
+
+A few host-side details to note:
- The bank account's slot names are `bank_account::bank::initialized` and `bank_account::bank::balances`.
- The `initialized` value slot has no schema default, so it MUST be seeded via `InitStorageData` (with a zero `Word` = uninitialized) or `from_package` fails with `InitValueNotProvided`. Only the `balances` map defaults to empty.
-- The init transaction script is extracted with the `build_tx_script_from_package` helper. A tx-script package is a `TargetType::TransactionScript`, so `unwrap_program()` / `TransactionScript::from_package` would panic.
+- The `build_tx_script_from_package` helper calls `TransactionScript::from_package` to load the procedure marked `#[transaction_script]` from the compiled package.
- Host-side felts use `Felt::new_unchecked`, and the expected output note uses `PartialNoteMetadata` (not `NoteMetadata`).
-- The withdraw asset is encoded from `FungibleAsset::new(faucet.id(), withdraw_amount)?.to_key_word()` indices `[2]`/`[3]` so the bank reconstructs the exact vault key the deposit recorded — NOT from `faucet.id().suffix()/prefix()`.
+- The withdraw asset is encoded from `FungibleAsset::new(faucet.id(), withdraw_amount)?.to_id_word()` indices `[2]`/`[3]` so the bank reconstructs the exact asset ID the deposit recorded — NOT from `faucet.id().suffix()/prefix()`.
```rust title="integration/tests/withdraw_test.rs"
use integration::helpers::{
@@ -427,14 +427,17 @@ use integration::helpers::{
create_testing_note_from_package, AccountCreationConfig, NoteCreationConfig,
};
+use miden_client::asset::{Asset, FungibleAsset};
use miden_client::{
- account::{component::{InitStorageData, StorageValueName}, StorageSlotName},
- auth::AuthSchemeId,
+ account::{
+ component::{InitStorageData, StorageValueName},
+ StorageSlotName,
+ },
+ auth::AuthScheme,
note::{Note, NoteAssets, NoteTag, NoteType, P2idNote, P2idNoteStorage, PartialNoteMetadata},
transaction::RawOutputNote,
Felt, Word,
};
-use miden_client::asset::{Asset, FungibleAsset};
use miden_testing::{Auth, MockChain};
use std::{path::Path, sync::Arc};
@@ -442,21 +445,26 @@ use std::{path::Path, sync::Arc};
/// seeded via `InitStorageData` (no schema default); the `balances` map defaults to empty.
fn bank_storage_slots() -> (StorageSlotName, StorageSlotName) {
let initialized_slot =
- StorageSlotName::new("bank_account::bank::initialized")
- .expect("Valid slot name");
+ StorageSlotName::new("bank_account::bank::initialized").expect("Valid slot name");
let balances_slot =
- StorageSlotName::new("bank_account::bank::balances")
- .expect("Valid slot name");
+ StorageSlotName::new("bank_account::bank::balances").expect("Valid slot name");
(initialized_slot, balances_slot)
}
#[tokio::test]
async fn withdraw_test() -> anyhow::Result<()> {
+ for note_type in [NoteType::Public, NoteType::Private] {
+ withdraw_flow(note_type).await?;
+ }
+ Ok(())
+}
+
+async fn withdraw_flow(note_type: NoteType) -> anyhow::Result<()> {
// *********************************************************************************
// SETUP
// *********************************************************************************
- // Test that after executing the deposit note, the depositor's balance is updated
+ // Verify withdrawal through consumption of the resulting P2ID note.
let mut builder = MockChain::builder();
// Define the deposit amount
@@ -465,7 +473,7 @@ async fn withdraw_test() -> anyhow::Result<()> {
// Create a faucet to mint test assets
let faucet = builder.add_existing_basic_faucet(
Auth::BasicAuth {
- auth_scheme: AuthSchemeId::Falcon512Poseidon2,
+ auth_scheme: AuthScheme::Falcon512Poseidon2,
},
"TEST",
deposit_amount,
@@ -475,7 +483,7 @@ async fn withdraw_test() -> anyhow::Result<()> {
// Create note sender account (the depositor)
let sender = builder.add_existing_wallet_with_assets(
Auth::BasicAuth {
- auth_scheme: AuthSchemeId::Falcon512Poseidon2,
+ auth_scheme: AuthScheme::Falcon512Poseidon2,
},
[FungibleAsset::new(faucet.id(), deposit_amount)?.into()],
)?;
@@ -497,7 +505,7 @@ async fn withdraw_test() -> anyhow::Result<()> {
// Create the bank account. The `initialized` value slot has no schema default, so it must
// be seeded (here with a zero Word = uninitialized) or `from_package` errors with
// `InitValueNotProvided`; the `balances` map defaults to empty.
- let (initialized_slot, _balances_slot) = bank_storage_slots();
+ let (initialized_slot, balances_slot) = bank_storage_slots();
let bank_cfg = AccountCreationConfig {
init_storage_data: {
let mut data = InitStorageData::default();
@@ -510,8 +518,7 @@ async fn withdraw_test() -> anyhow::Result<()> {
..Default::default()
};
- let mut bank_account =
- create_testing_account_from_package(bank_package.clone(), bank_cfg)?;
+ let mut bank_account = create_testing_account_from_package(bank_package.clone(), bank_cfg)?;
// *********************************************************************************
// STEP 1: CRAFT DEPOSIT NOTE
@@ -560,22 +567,22 @@ async fn withdraw_test() -> anyhow::Result<()> {
println!("Serial num (random): {:?}", p2id_output_note_serial_num);
// Note type for the P2ID output note
- let note_type_felt = Felt::new_unchecked(1); // 1 = Public note (stored on-chain)
+ let note_type_felt = Felt::from(note_type); // Public = 1, Private = 0
// Get the P2ID script root (Poseidon2-hashed MAST root). `script_root()` returns
- // a `NoteScriptRoot` in v0.15; convert to a `Word` so its felts can be indexed.
+ // a `NoteScriptRoot` in v0.16; convert to a `Word` so its felts can be indexed.
let p2id_script_root = Word::from(P2idNote::script_root());
// Note storage layout (14 Felts):
- // [0-3]: withdraw asset encoded as [amount, 0, faucet_suffix, faucet_prefix]
+ // [0-3]: withdraw asset encoded as [amount, 0, asset.key[2] (faucet suffix + metadata byte), asset.key[3] (faucet prefix)]
// [4-7]: serial_num (random/unique per note)
// [8]: tag (P2ID note tag for routing)
- // [9]: note_type (1 = Public, 2 = Private)
+ // [9]: note_type (1 = Public, 0 = Private)
// [10-13]: P2ID script_root (MAST root for recipient computation)
- // In v0.15 the fungible-asset vault key encodes the faucet suffix together with a
+ // In v0.16 the fungible-asset vault key encodes the faucet suffix together with a
// metadata byte at index [2] (and the faucet prefix at [3]). Encode the asset from the
// asset's real key word so the bank reconstructs the same key it deposited under.
- let withdraw_asset_key_word = FungibleAsset::new(faucet.id(), withdraw_amount)?.to_key_word();
+ let withdraw_asset_key_word = FungibleAsset::new(faucet.id(), withdraw_amount)?.to_id_word();
let withdraw_request_note_storage = vec![
// WITHDRAW ASSET ENCODING
Felt::new_unchecked(withdraw_amount),
@@ -589,7 +596,7 @@ async fn withdraw_test() -> anyhow::Result<()> {
p2id_output_note_serial_num[3],
// TAG (directly passed, no advice provider needed)
p2id_tag_felt,
- // NOTE TYPE (1 = Public)
+ // NOTE TYPE (1 = Public, 0 = Private)
note_type_felt,
// P2ID SCRIPT ROOT (4 Felts)
p2id_script_root[0],
@@ -625,14 +632,14 @@ async fn withdraw_test() -> anyhow::Result<()> {
let init_tx_script = build_tx_script_from_package(init_tx_script_package.as_ref())?;
let init_tx_context = mock_chain
- .build_tx_context(bank_account.id(), &[], &[])?
+ .build_transaction(bank_account.id())
.tx_script(init_tx_script)
.build()?;
let executed_init = init_tx_context.execute().await?;
- bank_account.apply_delta(&executed_init.account_delta())?;
mock_chain.add_pending_executed_transaction(&executed_init)?;
mock_chain.prove_next_block()?;
+ bank_account = mock_chain.committed_account(bank_account.id())?.clone();
println!("Bank initialized successfully");
@@ -642,18 +649,17 @@ async fn withdraw_test() -> anyhow::Result<()> {
// Build the transaction context where bank consumes the deposit note
let deposit_tx_context = mock_chain
- .build_tx_context(bank_account.id(), &[deposit_note.id()], &[])?
+ .build_transaction(bank_account.id())
+ .authenticated_input_note(deposit_note.id())
.build()?;
// Execute the transaction
let executed_deposit_transaction = deposit_tx_context.execute().await?;
- // Apply the account delta to the bank account
- bank_account.apply_delta(&executed_deposit_transaction.account_delta())?;
-
// Add the executed transaction to the mockchain and prove
mock_chain.add_pending_executed_transaction(&executed_deposit_transaction)?;
mock_chain.prove_next_block()?;
+ bank_account = mock_chain.committed_account(bank_account.id())?.clone();
println!("Bank deposit successful");
@@ -665,8 +671,8 @@ async fn withdraw_test() -> anyhow::Result<()> {
let recipient = P2idNoteStorage::new(sender.id()).into_recipient(p2id_output_note_serial_num);
let p2id_output_note_asset = FungibleAsset::new(faucet.id(), withdraw_amount)?;
let p2id_output_note_assets = NoteAssets::new(vec![p2id_output_note_asset.into()])?;
- let p2id_output_note_metadata = PartialNoteMetadata::new(bank_account.id(), NoteType::Public)
- .with_tag(p2id_tag);
+ let p2id_output_note_metadata =
+ PartialNoteMetadata::new(bank_account.id(), note_type).with_tag(p2id_tag);
println!("Recipient digest: {:?}", recipient.digest().to_hex());
@@ -677,18 +683,72 @@ async fn withdraw_test() -> anyhow::Result<()> {
);
let withdraw_request_tx_context = mock_chain
- .build_tx_context(bank_account.id(), &[withdraw_request_note.id()], &[])?
- .extend_expected_output_notes(vec![RawOutputNote::Full(p2id_output_note)])
+ .build_transaction(bank_account.id())
+ .authenticated_input_note(withdraw_request_note.id())
+ .expected_output_notes(vec![RawOutputNote::Full(p2id_output_note.clone())])
.build()?;
let executed_withdraw_request_transaction = withdraw_request_tx_context.execute().await?;
- bank_account.apply_delta(&executed_withdraw_request_transaction.account_delta())?;
-
mock_chain.add_pending_executed_transaction(&executed_withdraw_request_transaction)?;
mock_chain.prove_next_block()?;
+ bank_account = mock_chain.committed_account(bank_account.id())?.clone();
+
+ let remaining = deposit_amount - withdraw_amount;
+ let asset = FungibleAsset::new(faucet.id(), withdraw_amount)?;
+ let asset_key = asset.to_id_word();
+ let depositor_key = miden_client::account::StorageMapKey::new(Word::from([
+ sender.id().prefix().as_felt(),
+ sender.id().suffix(),
+ asset_key[3],
+ asset_key[2],
+ ]));
+ let balance = bank_account
+ .storage()
+ .get_map_item(&balances_slot, depositor_key)?;
+ assert_eq!(balance[0].as_canonical_u64(), remaining);
+ assert_eq!(
+ u64::from(bank_account.vault().get_balance(asset.id())?),
+ remaining
+ );
+ assert_eq!(
+ executed_withdraw_request_transaction
+ .output_notes()
+ .num_notes(),
+ 1
+ );
+ // MockChain retains only headers for newly committed private notes. Supply their
+ // full details explicitly, as the recipient would receive them out of band.
+ let consume_p2id = |account_id| {
+ let tx = mock_chain.build_transaction(account_id);
+ if note_type == NoteType::Public {
+ tx.authenticated_input_note(p2id_output_note.id())
+ } else {
+ tx.unauthenticated_input_note(p2id_output_note.clone())
+ }
+ };
- println!("Withdraw test passed!");
+ // P2ID must reject a consumer other than the depositor.
+ let error = consume_p2id(bank_account.id())
+ .build()?
+ .execute()
+ .await
+ .expect_err("only the depositor may consume the withdrawal note");
+ assert!(
+ format!("{error:?}").contains("FailedAssertion"),
+ "unexpected failure: {error:?}"
+ );
+
+ let sender_balance_before = u64::from(sender.vault().get_balance(asset.id())?);
+ let executed_receive = consume_p2id(sender.id()).build()?.execute().await?;
+ mock_chain.add_pending_executed_transaction(&executed_receive)?;
+ mock_chain.prove_next_block()?;
+ let sender_after = mock_chain.committed_account(sender.id())?;
+ assert_eq!(
+ u64::from(sender_after.vault().get_balance(asset.id())?),
+ sender_balance_before + withdraw_amount
+ );
+ println!("{note_type:?} withdrawal consumed by depositor! Remaining bank balance: {remaining}");
Ok(())
}
@@ -755,13 +815,13 @@ pub struct Wallet;
///
/// # Note Storage (14 Felts)
/// [0-3]: withdraw asset, encoded as [amount, 0, faucet_suffix(+metadata), faucet_prefix].
-/// Reconstructed into the v0.15 vault key [0, 0, storage[2], storage[3]] and value
+/// Reconstructed into the v0.16 asset ID [0, 0, storage[2], storage[3]] and value
/// [amount, 0, 0, 0]. `storage[2]` carries the faucet suffix with the asset's metadata
-/// byte in its low 8 bits (host side: `FungibleAsset::to_key_word()[2]`), not the raw
+/// composition bits in its low byte (host side: `FungibleAsset::to_id_word()[2]`), not the raw
/// suffix — so the bank reconstructs exactly the key the depositor's asset had.
/// [4-7]: serial_num (random/unique per note)
/// [8]: tag (P2ID note tag for routing)
-/// [9]: note_type (1 = Public, 2 = Private)
+/// [9]: note_type (1 = Public, 0 = Private)
/// [10-13]: P2ID script_root (MAST root of the P2ID note script, Poseidon2-hashed).
/// Consumed by the bank account directly from the active note's storage inside
/// `Bank::withdraw`, so it never appears on the call — this keeps that
@@ -780,7 +840,7 @@ impl WithdrawRequestNote {
"Withdraw request requires exactly 14 storage items"
);
- // Asset: reconstruct the v0.15 fungible-asset key/value from the note storage.
+ // Asset: reconstruct the v0.16 fungible-asset ID/value from the note storage.
// key = [0, 0, storage[2], storage[3]] where storage[2] = faucet suffix + metadata
// byte (low 8 bits) and storage[3] = faucet prefix.
// value = [amount, 0, 0, 0]
@@ -795,7 +855,7 @@ impl WithdrawRequestNote {
// Tag: single Felt for P2ID note routing
let tag = storage[8];
- // Note type: 1 = Public, 2 = Private
+ // Note type: 1 = Public, 0 = Private
let note_type = storage[9];
// Note: P2ID script root (storage[10..13]) is read by the bank account directly
diff --git a/docs/src/miden-bank/08-complete-flows.md b/docs/src/miden-bank/08-complete-flows.md
index fc931882..a28f4978 100644
--- a/docs/src/miden-bank/08-complete-flows.md
+++ b/docs/src/miden-bank/08-complete-flows.md
@@ -22,28 +22,29 @@ By the end of this section, you will have:
You've built all the pieces. Now let's see them work together:
```text
-┌────────────────────────────────────────────────────────────────┐
-│ COMPLETE BANK SYSTEM │
-├────────────────────────────────────────────────────────────────┤
-│ │
-│ Components Built: │
-│ ┌─────────────────────────────────────────────────────────┐ │
-│ │ bank-account │ Storage + deposit() + withdraw() │ │
-│ ├─────────────────┼───────────────────────────────────────┤ │
-│ │ deposit-note │ Note script → bank_account::deposit() │ │
-│ ├─────────────────┼───────────────────────────────────────┤ │
-│ │ withdraw-note │ Note script → bank_account::withdraw() │ │
-│ ├─────────────────┼───────────────────────────────────────┤ │
-│ │ init-tx-script │ Transaction script → initialize() │ │
-│ └─────────────────┴───────────────────────────────────────┘ │
-│ │
-│ Storage Layout: │
-│ ┌─────────────────────────────────────────────────────────┐ │
-│ │ initialized (Value) │ Word: [1, 0, 0, 0] when ready│ │
-│ │ balances (StorageMap) │ Map: user_key → [balance, 0, 0, 0]│ │
-│ └─────────────────────────────────────────────────────────┘ │
-│ │
-└────────────────────────────────────────────────────────────────┘
+┌────────────────────────────────────────────────────────────────────────┐
+│ COMPLETE BANK SYSTEM │
+├────────────────────────────────────────────────────────────────────────┤
+│ │
+│ Components Built: │
+│ ┌───────────────────────┬───────────────────────────────────┐ │
+│ │ bank-account │ Storage + deposit() + withdraw() │ │
+│ ├───────────────────────┼───────────────────────────────────┤ │
+│ │ deposit-note │ Note script → account.deposit() │ │
+│ ├───────────────────────┼───────────────────────────────────┤ │
+│ │ withdraw-request-note │ Note script → account.withdraw() │ │
+│ ├───────────────────────┼───────────────────────────────────┤ │
+│ │ init-tx-script │ Transaction script → initialize() │ │
+│ └───────────────────────┴───────────────────────────────────┘ │
+│ │
+│ Storage Layout: │
+│ ┌───────────────────────┬──────────────────────────────────┐ │
+│ │ initialized (Value) │ Word: [1, 0, 0, 0] when ready │ │
+│ ├───────────────────────┼──────────────────────────────────┤ │
+│ │ balances (StorageMap) │ Balance per user and asset class │ │
+│ └───────────────────────┴──────────────────────────────────┘ │
+│ │
+└────────────────────────────────────────────────────────────────────────┘
```
## The Complete Deposit Flow
@@ -51,48 +52,53 @@ You've built all the pieces. Now let's see them work together:
Let's trace through exactly what happens when a user deposits tokens:
```text
-┌─────────────────────────────────────────────────────────────────────┐
-│ DEPOSIT FLOW │
-├─────────────────────────────────────────────────────────────────────┤
-│ │
-│ 1. USER CREATES DEPOSIT NOTE │
-│ ┌──────────────────────┐ │
-│ │ Deposit Note │ │
-│ │ sender: User │ │
-│ │ assets: [1000 tok] │ │
-│ │ script: deposit-note│ │
-│ │ target: Bank │ │
-│ └──────────────────────┘ │
-│ │ │
-│ ▼ │
-│ 2. BANK CONSUMES NOTE (Transaction begins) │
-│ ┌──────────────────────┐ │
-│ │ Bank Account │ │
-│ │ vault += 1000 tokens│ ◀── Protocol adds assets to vault │
-│ └──────────────────────┘ │
-│ │ │
-│ ▼ │
-│ 3. NOTE SCRIPT EXECUTES │
-│ depositor = active_note::get_sender() → User's AccountId │
-│ assets = active_note::get_assets() → [1000 tokens] │
-│ for asset in assets: │
-│ bank_account::deposit(depositor, asset) ◀── Cross-component│
-│ │ │
-│ ▼ │
-│ 4. DEPOSIT METHOD RUNS (in bank-account context) │
-│ ┌──────────────────────────────────────────┐ │
-│ │ require_initialized() ✓ Passes │ │
-│ │ amount <= MAX_DEPOSIT ✓ 1000 <= 100k │ │
-│ │ native_account::add_asset() ← Confirm │ │
-│ │ balances[User] += 1000 ← Update │ │
-│ └──────────────────────────────────────────┘ │
-│ │ │
-│ ▼ │
-│ 5. TRANSACTION COMPLETES │
-│ Bank storage: balances[User] = 1000 │
-│ Bank vault: +1000 tokens │
-│ │
-└─────────────────────────────────────────────────────────────────────┘
+┌────────────────────────────────────────────────────────────────────────┐
+│ DEPOSIT FLOW │
+├────────────────────────────────────────────────────────────────────────┤
+│ │
+│ 1. USER CREATES DEPOSIT NOTE │
+│ ┌─────────────────────────┐ │
+│ │ Deposit Note │ │
+│ │ sender: User │ │
+│ │ assets: [1000 tokens] │ │
+│ │ script: deposit-note │ │
+│ │ consumed by: Bank │ │
+│ └─────────────────────────┘ │
+│ │ │
+│ ▼ │
+│ 2. BANK CONSUMES THE NOTE │
+│ ┌────────────────────────────────────────────────────┐ │
+│ │ Transaction begins; note script executes │ │
+│ │ Vault is credited when deposit() calls add_asset() │ │
+│ └────────────────────────────────────────────────────┘ │
+│ │ │
+│ ▼ │
+│ 3. NOTE SCRIPT CALLS THE BANK COMPONENT │
+│ ┌────────────────────────────────────────────────────────────┐ │
+│ │ depositor = active_note::get_sender() → User's AccountId │ │
+│ │ assets = active_note::get_initial_assets() → [1000 tokens] │ │
+│ │ for asset in assets: │ │
+│ │ account.deposit(depositor, asset) │ │
+│ └────────────────────────────────────────────────────────────┘ │
+│ │ │
+│ ▼ │
+│ 4. DEPOSIT METHOD VALIDATES AND RECEIVES THE ASSET │
+│ ┌─────────────────────────────────────────────────────┐ │
+│ │ require_initialized() ✓ │ │
+│ │ assert asset.is_fungible() ✓ │ │
+│ │ assert 0 < amount <= MAX_DEPOSIT (1,000,000) ✓ │ │
+│ │ native_account::add_asset(asset) → Credit vault │ │
+│ │ balances[User, asset class] += 1000 → Update ledger │ │
+│ └─────────────────────────────────────────────────────┘ │
+│ │ │
+│ ▼ │
+│ 5. TRANSACTION COMMITS │
+│ ┌──────────────────────────────────┐ │
+│ │ Depositor's ledger balance: 1000 │ │
+│ │ Bank vault: +1000 tokens │ │
+│ └──────────────────────────────────┘ │
+│ │
+└────────────────────────────────────────────────────────────────────────┘
```
## The Complete Withdraw Flow
@@ -100,76 +106,79 @@ Let's trace through exactly what happens when a user deposits tokens:
Now let's trace the withdrawal process:
```text
-┌─────────────────────────────────────────────────────────────────────┐
-│ WITHDRAW FLOW │
-├─────────────────────────────────────────────────────────────────────┤
-│ │
-│ 1. USER CREATES WITHDRAW REQUEST NOTE │
-│ ┌──────────────────────────────┐ │
-│ │ Withdraw Request Note │ │
-│ │ sender: User │ │
-│ │ inputs: [serial, tag, │ │
-│ │ note_type] │ │
-│ │ assets: [withdraw amount] │ │
-│ │ target: Bank │ │
-│ └──────────────────────────────┘ │
-│ │ │
-│ ▼ │
-│ 2. BANK CONSUMES REQUEST (Transaction begins) │
-│ ┌──────────────────────────────┐ │
-│ │ Note script executes: │ │
-│ │ sender = get_sender() │ │
-│ │ storage = get_storage() │ │
-│ │ asset = Asset from inputs │ │
-│ │ bank_account::withdraw(...) │ │
-│ └──────────────────────────────┘ │
-│ │ │
-│ ▼ │
-│ 3. WITHDRAW METHOD RUNS │
-│ ┌──────────────────────────────────────────────────────┐ │
-│ │ require_initialized() ✓ Passes │ │
-│ │ current_balance = get_depositor_balance(User) → 1000 │ │
-│ │ VALIDATE: 1000 >= 400 ✓ Passes │ ◀ CRITICAL
-│ │ balances[User] = 1000 - 400 → 600 │ │
-│ │ create_p2id_note(...) → Output note │ │
-│ └──────────────────────────────────────────────────────┘ │
-│ │ │
-│ ▼ │
-│ 4. P2ID NOTE CREATED (inside create_p2id_note) │
-│ ┌──────────────────────────────────────────────────────┐ │
-│ │ script_root = storage[10..13] → MAST digest │ │
-│ │ recipient = note::build_recipient( │ │
-│ │ serial_num, script_root, │ │
-│ │ [user.suffix, user.prefix] │ │
-│ │ ) │ │
-│ │ note_idx = output_note::create(tag, note_type, │ │
-│ │ recipient) │ │
-│ │ native_account::remove_asset(400 tokens) │ │
-│ │ output_note::add_asset(400 tokens, note_idx) │ │
-│ └──────────────────────────────────────────────────────┘ │
-│ │ │
-│ ▼ │
-│ 5. TRANSACTION COMPLETES │
-│ Bank storage: balances[User] = 600 │
-│ Bank vault: -400 tokens │
-│ Output: P2ID note with 400 tokens → User │
-│ │ │
-│ ▼ │
-│ 6. USER CONSUMES P2ID NOTE (separate transaction) │
-│ User's wallet receives 400 tokens │
-│ │
-└─────────────────────────────────────────────────────────────────────┘
+┌────────────────────────────────────────────────────────────────────────┐
+│ WITHDRAW FLOW │
+├────────────────────────────────────────────────────────────────────────┤
+│ │
+│ 1. USER CREATES WITHDRAWAL REQUEST NOTE │
+│ ┌───────────────────────────────────────────────────────┐ │
+│ │ Withdrawal Request Note │ │
+│ │ sender: User │ │
+│ │ assets: [] │ │
+│ │ storage (14 Felts): │ │
+│ │ asset (4), serial (4), tag, type, script root (4) │ │
+│ │ requested amount: 500 tokens │ │
+│ │ consumed by: Bank │ │
+│ └───────────────────────────────────────────────────────┘ │
+│ │ │
+│ ▼ │
+│ 2. BANK CONSUMES THE REQUEST │
+│ ┌─────────────────────────────────────────────────────┐ │
+│ │ Note script reads active_note::get_storage() │ │
+│ │ Reconstructs the requested asset from storage[0..4] │ │
+│ │ Calls account.withdraw(asset, serial, tag, type) │ │
+│ └─────────────────────────────────────────────────────┘ │
+│ │ │
+│ ▼ │
+│ 3. WITHDRAW METHOD VALIDATES AND UPDATES THE LEDGER │
+│ ┌────────────────────────────────────────────────┐ │
+│ │ require_initialized() ✓ │ │
+│ │ assert asset.is_fungible() ✓ │ │
+│ │ User = active_note::get_sender() │ │
+│ │ current_balance = 1000 │ │
+│ │ assert current_balance >= 500 ✓ │ │
+│ │ balances[User, asset class] = 1000 - 500 = 500 │ │
+│ │ create_p2id_note(...) → Output note │ │
+│ └────────────────────────────────────────────────┘ │
+│ │ │
+│ ▼ │
+│ 4. P2ID NOTE IS CREATED │
+│ ┌──────────────────────────────────────────────────────┐ │
+│ │ script_root = storage[10..14] │ │
+│ │ recipient = note::build_recipient( │ │
+│ │ serial, script_root, [user.suffix, user.prefix]) │ │
+│ │ note_idx = output_note::create(tag, type, recipient) │ │
+│ │ native_account::remove_asset(500 tokens) │ │
+│ │ output_note::add_asset(500 tokens, note_idx) │ │
+│ └──────────────────────────────────────────────────────┘ │
+│ │ │
+│ ▼ │
+│ 5. TRANSACTION COMMITS │
+│ ┌───────────────────────────────────────────────────────────┐ │
+│ │ Depositor's ledger balance: 500 │ │
+│ │ Bank vault: -500 tokens │ │
+│ │ Output: public or private P2ID carrying 500 tokens → User │ │
+│ └───────────────────────────────────────────────────────────┘ │
+│ │ │
+│ ▼ │
+│ 6. USER CONSUMES P2ID IN A SEPARATE TRANSACTION │
+│ ┌──────────────────────────────────────┐ │
+│ │ P2ID recipient check passes for User │ │
+│ │ User's wallet receives 500 tokens │ │
+│ └──────────────────────────────────────┘ │
+│ │
+└────────────────────────────────────────────────────────────────────────┘
```
## Try It: Complete End-to-End Test
-The complete flow is exercised by the three integration tests built up over the previous chapters, which together cover the same `init → deposit → withdraw` story shown in the diagram above:
+The complete flow is exercised by the three integration test files built up over the previous chapters, which together cover the same `init → deposit → withdraw` story shown in the diagram above:
-- `examples/miden-bank/integration/tests/deposit_test.rs` — introduced in Part 4. Covers the deposit happy path (`deposit_test`) plus two failure paths: `deposit_exceeds_max_should_fail` and `deposit_without_init_should_fail`.
+- `examples/miden-bank/integration/tests/deposit_test.rs` — introduced in Part 4. Covers the deposit happy path (`deposit_test`) plus rejection tests for excessive deposits, deposits before initialization, and NFTs with zero padding in their value word.
- `examples/miden-bank/integration/tests/init_test.rs` — introduced in Part 6. Exercises the init transaction script (`init_test`) and verifies the `initialized` flag flips from `0` to `1`.
-- `examples/miden-bank/integration/tests/withdraw_test.rs` — introduced in Part 7. Runs init + deposit + withdraw end-to-end (`withdraw_test`) and asserts the P2ID output note is created with the correct payload.
+- `examples/miden-bank/integration/tests/withdraw_test.rs` — introduced in Part 7. Runs init + deposit + withdraw end-to-end (`withdraw_test`) for both public and private outputs, rejects consumption by another account, and verifies the depositor receives the withdrawn tokens.
-Running all three together from the workspace root is the closest thing to a single end-to-end run:
+Run the complete suite from the workspace root:
```bash title=">_ Terminal"
cargo test --package integration --release -- --nocapture --test-threads=1
@@ -183,12 +192,13 @@ cargo test --package integration --release -- --nocapture --test-threads=1
Finished `release` profile [optimized] target(s)
Running tests/deposit_test.rs
-running 3 tests
+running 4 tests
test deposit_test ... ok
test deposit_exceeds_max_should_fail ... ok
test deposit_without_init_should_fail ... ok
+test deposit_nft_with_zero_padding_should_fail ... ok
-test result: ok. 3 passed; 0 failed; 0 ignored
+test result: ok. 4 passed; 0 failed; 0 ignored
Running tests/init_test.rs
@@ -208,7 +218,7 @@ test result: ok. 1 passed; 0 failed; 0 ignored
:::note Live network bins
-The repository also ships `cargo run --bin initialize` and `cargo run --bin deposit` (under `examples/miden-bank/integration/src/bin/`) for exercising the same flow against a live testnet node. The MockChain integration tests above verify the deposit, init, and withdraw flows end-to-end.
+The repository also ships `cargo run --bin initialize` and `cargo run --bin deposit` (under `examples/miden-bank/integration/src/bin/`) for exercising the same flow against a live testnet node. The deposit bin attaches 1,000 native base units, and both binaries wait for actual transaction commitment before reporting success. Testnet charges fees: each binary prints the new account ID and waits for a public native-token P2ID. Request funding for that ID from the testnet faucet while it waits; the helper consumes the funding note before proceeding. The MockChain tests above verify initialization, deposit, and withdrawal without external funding.
:::
## Summary: All Components
@@ -227,15 +237,15 @@ Here's the complete picture of what you've built:
| `initialized` | `StorageValue` | Initialization flag |
| `balances` | `StorageMap` | Depositor balances |
-| API | Purpose |
-| -------------------------------- | --------------------- |
-| `active_note::get_sender()` | Identify note creator |
-| `active_note::get_assets()` | Get attached assets |
-| `active_note::get_storage()` | Get note parameters |
-| `native_account::add_asset()` | Receive into vault |
-| `native_account::remove_asset()` | Send from vault |
-| `output_note::create()` | Create output note |
-| `output_note::add_asset()` | Attach assets to note |
+| API | Purpose |
+| ----------------------------------- | --------------------------------- |
+| `active_note::get_sender()` | Identify note creator |
+| `active_note::get_initial_assets()` | Get creation-time attached assets |
+| `active_note::get_storage()` | Get note parameters |
+| `native_account::add_asset()` | Receive into vault |
+| `native_account::remove_asset()` | Send from vault |
+| `output_note::create()` | Create output note |
+| `output_note::add_asset()` | Attach assets to note |
## Key Security Patterns
@@ -257,17 +267,19 @@ let new_balance = current_balance - withdraw_amount;
:::
-:::warning Felt Comparison Operators
-Never use `<`, `>` on Felt values directly. Always convert to u64 first:
+:::note Felt Comparison Operators
+Direct `Felt` comparisons use canonical integer ordering in the current SDK. Both forms below are valid; this tutorial uses the explicit `u64` form for quantity checks:
```rust
-// ❌ BROKEN: Produces incorrect results
+// Direct comparison of canonical Felt values.
if current_balance < withdraw_amount { ... }
-// ✅ CORRECT: Use as_canonical_u64()
+// Equivalent comparison with explicit integer conversion.
if current_balance.as_canonical_u64() < withdraw_amount.as_canonical_u64() { ... }
```
+Always perform this check before subtracting. Conversion after modular underflow cannot recover the intended balance.
+
:::
## Congratulations! 🎉
diff --git a/docs/src/miden-bank/index.md b/docs/src/miden-bank/index.md
index d5b5dead..bf80d823 100644
--- a/docs/src/miden-bank/index.md
+++ b/docs/src/miden-bank/index.md
@@ -15,12 +15,12 @@ You'll create a **banking system** consisting of:
- **Bank Account Component**: A smart contract that manages depositor balances and vault operations
- **Deposit Note**: A note script that processes deposits into the bank
- **Withdraw Request Note**: A note script that requests withdrawals from the bank
-- **Initialization Script**: A transaction script to deploy and initialize the bank
+- **Initialization Script**: A transaction script to initialize the bank
-The tutorial includes runnable tests where appropriate — some parts are setup-only or conceptual, with the first runnable test in Part 4.
+The tutorial includes runnable tests where appropriate — some parts are setup-only or conceptual, with setup tests in Parts 0–2 and transaction tests once the required contracts are in place.
-:::note Verification runs on MockChain
-This tutorial targets protocol **v0.15** and the v0.15-aligned Rust compiler. The contracts depend on the published `miden = "0.13"` SDK, and the integration harness builds them with the published `cargo-miden = "0.9"` release. The flow is verified end-to-end by the MockChain integration tests (`tests/{init,deposit,withdraw}_test.rs`), which all pass. The live-network binaries (`cargo run --bin initialize` / `--bin deposit`) also run against testnet.
+:::note Version and fee setup
+The contracts use stable `miden = "=0.14.0"` and compiler 0.10.0; the native integration harness uses protocol/client 0.16. The companion MockChain tests cover initialization, deposit, deposit rejection, and withdrawal. Live testnet transactions also need native tokens for fees. The live binaries print each new account ID and wait for an externally requested public P2ID funding note, then consume it before proceeding.
:::
## Tutorial Structure
@@ -49,23 +49,30 @@ This tutorial is designed for hands-on learning. Each part builds on the previou
## Tutorial Cards
import DocCard from '@theme/DocCard';
+import {useDoc} from '@docusaurus/plugin-content-docs/client';
+
+export const BankDocCard = ({item}) => {
+const {metadata} = useDoc();
+const sectionPath = metadata.permalink.replace(/\/$/, '');
+ return ;
+};
-
-
-
-
-
-
-
-
- Result<(), ClientError> {
// Initialize client
- let endpoint = Endpoint::testnet();
+ let network = TutorialNetwork::from_env()?;
+ let endpoint = network.endpoint();
let timeout_ms = 10_000;
- let rpc_client = Arc::new(GrpcClient::new(&endpoint, timeout_ms));
+ let rpc_client = Arc::new(VerifyingRpcClient::new(GrpcClient::new(
+ &endpoint, timeout_ms,
+ )));
// Initialize keystore
let keystore_path = PathBuf::from("./keystore");
@@ -86,12 +99,12 @@ async fn main() -> Result<(), ClientError> {
.rpc(rpc_client)
.sqlite_store(store_path)
.authenticator(keystore.clone())
- .in_debug_mode(true.into())
.build()
.await?;
let sync_summary = client.sync_state().await.unwrap();
println!("Latest block: {}", sync_summary.block_num);
+ let fee_config = FeeConfig::from_client(&client, network).await?;
Ok(())
}
@@ -103,35 +116,20 @@ _When running the code above, there will be some unused imports, however, we wil
## Step 2: Build the counter contract
-For better code organization, we will separate the Miden assembly code from our Rust code.
-
-Create a directory named `masm` at the **root** of your `miden-counter-contract` directory. This will contain our contract and script masm code.
-
-Initialize the `masm` directory:
-
-```bash
-mkdir -p masm/accounts masm/scripts
-```
-
-This will create:
-
-```text
-masm/
-├── accounts/
-└── scripts/
-```
+The account and transaction-script sources are already in the repository. We examine them below before compiling them from Rust.
### Custom Miden smart contract
-Below is our counter contract. It has a two exported procedures: `get_count` and `increment_count`.
+Below is our counter contract. It has two exported procedures: `get_count` and `increment_count`.
At the beginning of the MASM file, we define our imports. In this case, we import
-`miden::protocol::active_account`, `miden::protocol::native_account`, `miden::core::word`, and
+`miden::protocol::active_account`, `miden::protocol::native_account`, and
`miden::core::sys`.
The `miden::protocol::active_account` and `miden::protocol::native_account` modules contain
-procedures for reading and writing contract state. We use `miden::core::word` to convert the slot
-name into a slot ID for the account storage APIs.
+procedures for reading and writing contract state. The compile-time expression
+`word("miden::tutorials::counter")` derives the named storage slot's ID; `[0..2]`
+selects the two elements used by the account storage APIs.
The import `miden::core::sys` contains a useful procedure for truncating the operand stack at the
end of a procedure.
@@ -152,51 +150,68 @@ end of a procedure.
4. Adds `1` to the count value returned from `active_account::get_item`.
5. Pushes the slot ID prefix and suffix again so we can write the updated count.
6. Calls `native_account::set_item` which saves the incremented count to storage.
-7. Calls `sys::truncate_stack` to clean up the stack.
+7. Drops the old storage word returned by `set_item`, then calls `sys::truncate_stack` to clean up the stack.
-Inside of the `masm/accounts/` directory, create the `counter.masm` file:
+The counter is defined in `masm/accounts/counter.masm`:
```masm
use miden::protocol::active_account
use miden::protocol::native_account
-use miden::core::word
use miden::core::sys
+# CONSTANTS
+# =================================================================================================
+
const COUNTER_SLOT = word("miden::tutorials::counter")
-#! Inputs: []
-#! Outputs: [count]
-pub proc get_count
+# PUBLIC INTERFACE
+# =================================================================================================
+
+#! Returns the current count.
+#!
+#! Inputs: [pad(16)]
+#! Outputs: [count, pad(15)]
+#!
+#! Invocation: call
+@account_procedure
+pub proc get_count() -> felt
push.COUNTER_SLOT[0..2] exec.active_account::get_item
- # => [count]
+ # => [[count, 0, 0, 0], pad(16)]
exec.sys::truncate_stack
- # => [count]
+ # => [count, pad(15)]
end
-#! Inputs: []
-#! Outputs: []
-pub proc increment_count
+#! Increments the current count by one.
+#!
+#! Inputs: [pad(16)]
+#! Outputs: [pad(16)]
+#!
+#! Invocation: call
+@account_procedure
+pub proc increment_count()
push.COUNTER_SLOT[0..2] exec.active_account::get_item
- # => [count]
+ # => [[count, 0, 0, 0], pad(16)]
add.1
- # => [count+1]
+ # => [[count + 1, 0, 0, 0], pad(16)]
push.COUNTER_SLOT[0..2] exec.native_account::set_item
- # => []
+ # => [OLD_VALUE, pad(16)]
+
+ dropw
+ # => [pad(16)]
exec.sys::truncate_stack
- # => []
+ # => [pad(16)]
end
-
```
**Note**: _It's a good habit to add comments below each line of MASM code with the expected stack state. This improves readability and helps with debugging._
### Authentication Component
-**Important**: Starting with Miden Client 0.10.0, all accounts must have an authentication component. For smart contracts that don't require authentication (like our counter contract), we use a `NoAuth` component.
+Accounts require an authentication component. This public counter deliberately uses `NoAuth`, which pays transaction fees from the account's native-asset balance and updates its nonce without verifying a signature.
This `NoAuth` component allows any user to interact with the smart contract without requiring signature verification.
@@ -204,21 +219,35 @@ This `NoAuth` component allows any user to interact with the smart contract with
This is a Miden assembly script that will call the `increment_count` procedure during the transaction.
-The string `{increment_count}` will be replaced with the hash of the `increment_count` procedure in our rust program.
+The Rust code links the counter module as `external_contract::counter_contract`, so the script can call `counter_contract::increment_count` by name.
-Inside of the `masm/scripts/` directory, create the `counter_script.masm` file:
+The transaction script is defined in `masm/scripts/counter_script.masm`:
```masm
use external_contract::counter_contract
-begin
+#! Increments the counter.
+#!
+#! Inputs: [ARGS, pad(12)]
+#! Outputs: [pad(16)]
+#!
+#! Where:
+#! - ARGS contains unused transaction script arguments.
+#!
+#! Invocation: dyncall
+@transaction_script
+pub proc main(args: word)
+ dropw
+ # => [pad(16)]
+
call.counter_contract::increment_count
+ # => [pad(16)]
end
```
## Step 3: Build the counter smart contract
-To build the counter contract copy and paste the following code at the end of your `src/main.rs` file:
+To build the counter contract, insert the following code inside `main`, immediately before its final `Ok(())`:
```rust ignore
// -------------------------------------------------------------------------
@@ -226,21 +255,22 @@ To build the counter contract copy and paste the following code at the end of yo
// -------------------------------------------------------------------------
println!("\n[STEP 1] Creating counter contract.");
-// Load the MASM file for the counter contract. `include_str!` resolves at
-// compile time relative to this source file, so the binary is independent of
-// the working directory it is run from.
-let counter_code = include_str!("../masm/accounts/counter.masm");
+// Read the MASM source from the tutorials repository.
+let counter_code = std::fs::read_to_string("../tutorials/masm/accounts/counter.masm").unwrap();
// Compile the account code into `AccountComponent` with one storage slot.
let counter_slot_name =
StorageSlotName::new("miden::tutorials::counter").expect("valid slot name");
let component_code = client
.code_builder()
- .compile_component_code("external_contract::counter_contract", counter_code)
+ .compile_component_code("external_contract::counter_contract", &counter_code)
.unwrap();
let counter_component = AccountComponent::new(
component_code,
- vec![StorageSlot::with_value(counter_slot_name.clone(), Word::default())],
+ vec![StorageSlot::with_value(
+ counter_slot_name.clone(),
+ Word::default(),
+ )],
AccountComponentMetadata::new("external_contract::counter_contract"),
)
.unwrap();
@@ -253,7 +283,8 @@ client.rng().fill_bytes(&mut seed);
let counter_contract = AccountBuilder::new(seed)
.account_type(AccountType::Public)
.with_component(counter_component.clone())
- .with_auth_component(NoAuth)
+ .with_component(BasicWallet)
+ .with_component(NoAuth)
.build()
.unwrap();
@@ -265,28 +296,31 @@ println!("counter_contract id: {:?}", counter_contract.id());
println!("counter_contract storage: {:?}", counter_contract.storage());
client.add_account(&counter_contract, false).await.unwrap();
+fund_account_for_fees(&mut client, counter_contract.id(), &fee_config).await?;
```
Run the following command to execute `src/main.rs`:
```bash
-cargo run --release
+TUTORIAL_NETWORK=testnet cargo run --release
```
-After the program executes, you should see the counter contract hash and contract id printed to the terminal, for example:
+After the program executes, it prints the initial account commitment, ID, and storage. Abridged output looks like this; generated values vary:
```text
[STEP 1] Creating counter contract.
-counter_contract commitment: RpoDigest([3700134472268167470, 14878091556015233722, 3335592073702485043, 16978997897830363420])
-counter_contract id: ""
-counter_contract storage: AccountStorage { slots: [Value([0, 0, 0, 0]), Value([0, 0, 0, 0])] }
+counter_contract commitment: Word([...])
+counter_contract id: V1(AccountIdV1 { suffix: ..., prefix: ... })
+counter_contract storage: AccountStorage { slots: [StorageSlot { ... content: Value(Word([0, 0, 0, 0])) }] }
```
+The funding helper then consumes a native-asset note and waits for confirmation. That first transaction publishes the account without incrementing its counter.
+
## Step 4: Incrementing the count
-Now that we built the counter contract, lets create a transaction request to increment the count:
+Now that we have built and funded the counter contract, let's create a transaction request to increment the count:
-Paste the following code at the end of your `src/main.rs` file:
+Insert the following code after the previous step, still inside `main` and before `Ok(())`:
```rust ignore
// -------------------------------------------------------------------------
@@ -295,15 +329,16 @@ Paste the following code at the end of your `src/main.rs` file:
println!("\n[STEP 2] Call Counter Contract With Script");
// Load the MASM script referencing the increment procedure
-let script_code = include_str!("../masm/scripts/counter_script.masm");
+let script_code =
+ std::fs::read_to_string("../tutorials/masm/scripts/counter_script.masm").unwrap();
// Compile the script with the counter contract code linked as a module
// on the same `CodeBuilder` chain.
let tx_script = client
.code_builder()
- .with_linked_module("external_contract::counter_contract", counter_code)
+ .with_linked_module("external_contract::counter_contract", &counter_code)
.unwrap()
- .compile_tx_script(script_code)
+ .compile_tx_script(&script_code)
.unwrap();
// Build a transaction request with the custom script
@@ -314,18 +349,19 @@ let tx_increment_request = TransactionRequestBuilder::new()
// Execute and submit the transaction
let tx_id = client
- .submit_new_transaction(counter_contract.id(), tx_increment_request)
+ .submit_tutorial_transaction(counter_contract.id(), tx_increment_request)
.await
.unwrap();
println!(
- "View transaction on MidenScan: https://testnet.midenscan.com/tx/{:?}",
+ "View transaction on MidenScan: {}/tx/{:?}",
+ network.explorer_url(),
tx_id
);
println!(
"Counter contract id: {:?}",
- counter_contract.id().to_bech32(NetworkId::Testnet)
+ counter_contract.id().to_bech32(network.network_id())
);
client.sync_state().await.unwrap();
@@ -340,39 +376,48 @@ println!(
"counter contract storage: {:?}",
account.storage().get_item(&counter_slot_name)
);
+assert_eq!(
+ account.storage().get_item(&counter_slot_name).unwrap()[0].as_canonical_u64(),
+ 1,
+ "the deployed counter must increment from zero to one",
+);
```
-**Note**: _Once our counter contract is deployed, other users can increment the count of the smart contract simply by knowing the account id of the contract and the procedure hash of the `increment_count` procedure._
+Because this counter uses `NoAuth`, another client can import its public state and execute the same increment script. Public visibility alone does not grant that permission; the account's authentication component does.
## Summary
The final `src/main.rs` file should look like this:
```rust no_run
-use rand::RngCore;
+use rand::Rng;
+use rust_client::TutorialClientExt;
use std::{path::PathBuf, sync::Arc};
use miden_client::{
+ ClientError, Word,
account::{
- component::AccountComponentMetadata, AccountBuilder, AccountComponent,
- AccountType, StorageSlot, StorageSlotName,
+ AccountBuilder, AccountComponent, AccountType, StorageSlot, StorageSlotName,
+ component::{AccountComponentMetadata, BasicWallet},
},
- address::NetworkId,
auth::NoAuth,
builder::ClientBuilder,
keystore::FilesystemKeyStore,
- rpc::{Endpoint, GrpcClient},
+ rpc::{GrpcClient, VerifyingRpcClient},
transaction::TransactionRequestBuilder,
- ClientError, Word,
};
use miden_client_sqlite_store::ClientBuilderSqliteExt;
+use rust_client::{FeeConfig, TutorialNetwork, fund_account_for_fees};
#[tokio::main]
async fn main() -> Result<(), ClientError> {
// Initialize client
- let endpoint = Endpoint::testnet();
+ let network = TutorialNetwork::from_env()?;
+ let endpoint = network.endpoint();
let timeout_ms = 10_000;
- let rpc_client = Arc::new(GrpcClient::new(&endpoint, timeout_ms));
+ let rpc_client = Arc::new(VerifyingRpcClient::new(GrpcClient::new(
+ &endpoint, timeout_ms,
+ )));
// Initialize keystore
let keystore_path = PathBuf::from("./keystore");
@@ -384,33 +429,34 @@ async fn main() -> Result<(), ClientError> {
.rpc(rpc_client)
.sqlite_store(store_path)
.authenticator(keystore.clone())
- .in_debug_mode(true.into())
.build()
.await?;
let sync_summary = client.sync_state().await.unwrap();
println!("Latest block: {}", sync_summary.block_num);
+ let fee_config = FeeConfig::from_client(&client, network).await?;
// -------------------------------------------------------------------------
// STEP 1: Create a basic counter contract
// -------------------------------------------------------------------------
println!("\n[STEP 1] Creating counter contract.");
- // Load the MASM file for the counter contract. `include_str!` resolves at
- // compile time relative to this source file, so the binary is independent
- // of the working directory it is run from.
- let counter_code = include_str!("../masm/accounts/counter.masm");
+ // Read the MASM source from the tutorials repository.
+ let counter_code = std::fs::read_to_string("../tutorials/masm/accounts/counter.masm").unwrap();
// Compile the account code into `AccountComponent` with one storage slot.
let counter_slot_name =
StorageSlotName::new("miden::tutorials::counter").expect("valid slot name");
let component_code = client
.code_builder()
- .compile_component_code("external_contract::counter_contract", counter_code)
+ .compile_component_code("external_contract::counter_contract", &counter_code)
.unwrap();
let counter_component = AccountComponent::new(
component_code,
- vec![StorageSlot::with_value(counter_slot_name.clone(), Word::default())],
+ vec![StorageSlot::with_value(
+ counter_slot_name.clone(),
+ Word::default(),
+ )],
AccountComponentMetadata::new("external_contract::counter_contract"),
)
.unwrap();
@@ -423,7 +469,8 @@ async fn main() -> Result<(), ClientError> {
let counter_contract = AccountBuilder::new(seed)
.account_type(AccountType::Public)
.with_component(counter_component.clone())
- .with_auth_component(NoAuth)
+ .with_component(BasicWallet)
+ .with_component(NoAuth)
.build()
.unwrap();
@@ -435,6 +482,7 @@ async fn main() -> Result<(), ClientError> {
println!("counter_contract storage: {:?}", counter_contract.storage());
client.add_account(&counter_contract, false).await.unwrap();
+ fund_account_for_fees(&mut client, counter_contract.id(), &fee_config).await?;
// -------------------------------------------------------------------------
// STEP 2: Call the Counter Contract with a script
@@ -442,15 +490,16 @@ async fn main() -> Result<(), ClientError> {
println!("\n[STEP 2] Call Counter Contract With Script");
// Load the MASM script referencing the increment procedure
- let script_code = include_str!("../masm/scripts/counter_script.masm");
+ let script_code =
+ std::fs::read_to_string("../tutorials/masm/scripts/counter_script.masm").unwrap();
// Compile the script with the counter contract code linked as a module
// on the same `CodeBuilder` chain.
let tx_script = client
.code_builder()
- .with_linked_module("external_contract::counter_contract", counter_code)
+ .with_linked_module("external_contract::counter_contract", &counter_code)
.unwrap()
- .compile_tx_script(script_code)
+ .compile_tx_script(&script_code)
.unwrap();
// Build a transaction request with the custom script
@@ -461,18 +510,19 @@ async fn main() -> Result<(), ClientError> {
// Execute and submit the transaction
let tx_id = client
- .submit_new_transaction(counter_contract.id(), tx_increment_request)
+ .submit_tutorial_transaction(counter_contract.id(), tx_increment_request)
.await
.unwrap();
println!(
- "View transaction on MidenScan: https://testnet.midenscan.com/tx/{:?}",
+ "View transaction on MidenScan: {}/tx/{:?}",
+ network.explorer_url(),
tx_id
);
println!(
"Counter contract id: {:?}",
- counter_contract.id().to_bech32(NetworkId::Testnet)
+ counter_contract.id().to_bech32(network.network_id())
);
client.sync_state().await.unwrap();
@@ -487,62 +537,41 @@ async fn main() -> Result<(), ClientError> {
"counter contract storage: {:?}",
account.storage().get_item(&counter_slot_name)
);
+ assert_eq!(
+ account.storage().get_item(&counter_slot_name).unwrap()[0].as_canonical_u64(),
+ 1,
+ "the deployed counter must increment from zero to one",
+ );
Ok(())
}
```
-The output of our program will look something like this:
+Successful output includes the following lines (abridged; generated values vary):
```text
-Latest block: 374255
+Latest block:
[STEP 1] Creating counter contract.
-one or more warnings were emitted
-counter_contract commitment: Word([3964727668949550262, 4265714847747507878, 5784293172192015964, 16803438753763367241])
-counter_contract id: ""
-counter_contract storage: AccountStorage { slots: [Value(Word([0, 0, 0, 0]))] }
+counter_contract commitment: Word([...])
+counter_contract id: V1(AccountIdV1 { suffix: ..., prefix: ... })
+counter_contract storage: AccountStorage { slots: [StorageSlot { ... content: Value(Word([0, 0, 0, 0])) }] }
[STEP 2] Call Counter Contract With Script
-Stack state before step 2610:
-├── 0: 1
-├── 1: 0
-├── 2: 0
-├── 3: 0
-├── 4: 0
-├── 5: 0
-├── 6: 0
-├── 7: 0
-├── 8: 0
-├── 9: 0
-├── 10: 0
-├── 11: 0
-├── 12: 0
-├── 13: 0
-├── 14: 0
-├── 15: 0
-├── 16: 0
-├── 17: 0
-├── 18: 0
-└── 19: 0
-
-└── (0 more items)
-
-View transaction on MidenScan: https://testnet.midenscan.com/tx/0x9767940bbed7bd3a74c24dc43f1ea8fe90a876dc7925621c217f648c63c4ab7a
-counter contract storage: Ok(Word([0, 0, 0, 1]))
+View transaction on MidenScan: https://testnet.midenscan.com/tx/
+Counter contract id: ""
+counter contract storage: Ok(Word([1, 0, 0, 0]))
```
-The line in the output `Stack state before step 2505` ouputs the stack state when we call "debug.stack" in the `counter.masm` file.
-
-To increment the count of the counter contract all you need is to know the account id of the counter and the procedure hash of the `increment_count` procedure. To increment the count without deploying the counter each time, you can modify the program above to hardcode the account id of the counter and the procedure hash of the `increment_count` prodedure in the masm script.
+To increment the contract again without redeploying it, pass the printed testnet account ID to the [public-account interaction tutorial](./public_account_interaction_tutorial.md). Keeping the ID as an input avoids hard-coding an address that becomes invalid after a testnet reset.
### Running the example
-To run the full example, navigate to the `rust-client` directory in the [miden-tutorials](https://github.com/0xMiden/miden-tutorials/) repository and run this command:
+To run the checked-in example, return to the root of the [tutorials repository](https://github.com/0xMiden/tutorials/) and run:
```bash
cd rust-client
-cargo run --release --bin counter_contract_deploy
+TUTORIAL_NETWORK=testnet cargo run --release --bin counter_contract_deploy
```
### Continue learning
diff --git a/docs/src/rust-client/create_deploy_tutorial.md b/docs/src/rust-client/create_deploy_tutorial.md
index 2876f6a0..c5397462 100644
--- a/docs/src/rust-client/create_deploy_tutorial.md
+++ b/docs/src/rust-client/create_deploy_tutorial.md
@@ -7,6 +7,8 @@ sidebar_position: 2
_Using the Miden client in Rust to create accounts and deploy faucets_
+For toolchain requirements and shared fee helpers, see the [Rust client setup](./index.md#running-the-v016-examples).
+
## Overview
In this tutorial, we will create a Miden account for _Alice_ and deploy a fungible faucet. In the next section, we will mint tokens from the faucet to fund her account and transfer tokens from Alice's account to other Miden accounts.
@@ -20,39 +22,46 @@ In this tutorial, we will create a Miden account for _Alice_ and deploy a fungib
## Prerequisites
-Before you begin, ensure that a Miden node is running locally in a separate terminal window. To get the Miden node running locally, you can follow the instructions on the [Miden Node Setup](../miden_node_setup.md) page.
+The commands in this guide select the public testnet explicitly. If you change the endpoint to a local node, start that node first by following [Miden Node Setup](../miden_node_setup.md).
## Public vs. private accounts & notes
Before diving into coding, let's clarify the concepts of public and private accounts & notes on Miden:
- Public accounts: The account's data and code are stored on-chain and are openly visible, including its assets.
-- Private accounts: The account's state and logic are off-chain, only known to its owner.
+- Private accounts: Only a commitment to the account state is stored on-chain. The owner keeps the full state locally and may share it with others.
- Public notes: The note's state is visible to anyone - perfect for scenarios where transparency is desired.
- Private notes: The note's state is stored off-chain, you will need to share the note data with the relevant parties (via email or Telegram) for them to be able to consume the note.
Note: _The term "account" can be used interchangeably with the term "smart contract" since account abstraction on Miden is handled natively._
-_It is useful to think of notes on Miden as "cryptographic cashier's checks" that allow users to send tokens. If the note is private, the note transfer is only known to the sender and receiver._
+_It is useful to think of notes on Miden as "cryptographic cashier's checks" that allow users to send tokens. Private note details must be shared with the recipient; the chain still records the note commitment and its eventual nullifier._
## Step 1: Initialize your repository
-Create a new Rust repository for your Miden project and navigate to it with the following command:
+Start in the directory containing your `tutorials` clone and create a sibling Cargo project. The dependency path below assumes the clone is named `tutorials`.
```bash
cargo new miden-rust-client
cd miden-rust-client
+rustup override set 1.98.1
+cp ../tutorials/rust-client/Cargo.lock Cargo.lock
```
-Add the following dependencies to your `Cargo.toml` file:
+Keep the generated `[package]` section in `Cargo.toml`, replace its empty `[dependencies]` section with the following, and add the development profile:
```toml
[dependencies]
-miden-client = { version = "0.15", features = ["testing", "tonic"] }
-miden-client-sqlite-store = { version = "0.15", package = "miden-client-sqlite-store" }
-miden-protocol = { version = "0.15" }
-rand = { version = "0.9" }
-tokio = { version = "1.46", features = ["rt-multi-thread", "net", "macros", "fs"] }
+# Clone tutorials next to this Cargo project (see Rust client setup).
+rust-client = { path = "../tutorials/rust-client" }
+miden-client = { version = "=0.16.0", features = ["testing", "tonic"] }
+miden-client-sqlite-store = { version = "=0.16.0", package = "miden-client-sqlite-store" }
+miden-protocol = { version = "=0.16.0" }
+rand = { version = "0.10" }
+tokio = { version = "1.48", features = ["rt-multi-thread", "net", "macros", "fs"] }
+
+[profile.dev]
+opt-level = 2
```
## Step 2: Initialize the client
@@ -67,41 +76,41 @@ Before interacting with the Miden network, we must instantiate the client. In th
Copy and paste the following code into your `src/main.rs` file.
```rust no_run
-use miden_client::auth::{AuthSchemeId, AuthSingleSig};
-use rand::RngCore;
+use rand::Rng;
+use rust_client::TutorialClientExt;
use std::{path::PathBuf, sync::Arc};
use tokio::time::Duration;
use miden_client::{
+ ClientError,
account::{
+ AccountBuilder, AccountId, AccountType,
component::{
- BasicWallet, BurnPolicyConfig, FungibleFaucet, MintPolicyConfig, PolicyRegistration,
- TokenName, TokenPolicyManager,
+ create_singlesig_user_fungible_faucet, BasicWallet, BurnPolicy, FungibleFaucet,
+ MintPolicy, TokenName, TokenPolicyManager,
},
- AccountId,
},
- address::NetworkId,
- auth::AuthSecretKey,
+ asset::{AssetAmount, AssetCallbackFlag, AssetId, FungibleAsset, TokenSymbol},
+ auth::{AuthSecretKey, AuthSingleSig},
builder::ClientBuilder,
- keystore::FilesystemKeyStore,
- note::{NoteAttachments, NoteType, P2idNote},
- rpc::{Endpoint, GrpcClient},
- transaction::TransactionRequestBuilder,
- ClientError,
+ keystore::{FilesystemKeyStore, Keystore},
+ note::{Note, NoteType, P2idNote},
+ rpc::{GrpcClient, VerifyingRpcClient},
+ transaction::{PaymentNoteDescription, TransactionRequestBuilder},
};
use miden_client_sqlite_store::ClientBuilderSqliteExt;
use miden_protocol::account::AccountIdVersion;
-use miden_client::{
- account::{AccountBuilder, AccountType},
- asset::{AssetAmount, FungibleAsset, TokenSymbol},
-};
+use rust_client::{FeeConfig, TutorialNetwork, fund_account_for_fees};
#[tokio::main]
async fn main() -> Result<(), ClientError> {
// Initialize client
- let endpoint = Endpoint::testnet();
+ let network = TutorialNetwork::from_env()?;
+ let endpoint = network.endpoint();
let timeout_ms = 10_000;
- let rpc_client = Arc::new(GrpcClient::new(&endpoint, timeout_ms));
+ let rpc_client = Arc::new(VerifyingRpcClient::new(GrpcClient::new(
+ &endpoint, timeout_ms,
+ )));
// Initialize keystore
let keystore_path = PathBuf::from("./keystore");
@@ -113,12 +122,12 @@ async fn main() -> Result<(), ClientError> {
.rpc(rpc_client)
.sqlite_store(store_path)
.authenticator(keystore.clone())
- .in_debug_mode(true.into())
.build()
.await?;
let sync_summary = client.sync_state().await.unwrap();
println!("Latest block: {}", sync_summary.block_num);
+ let fee_config = FeeConfig::from_client(&client, network).await?;
Ok(())
}
@@ -131,24 +140,24 @@ _When running the code above, there will be some unused imports, however, we wil
Run the following command to execute `src/main.rs`:
```bash
-cargo run --release
+TUTORIAL_NETWORK=testnet cargo run --release
```
After the program executes, you should see the latest block number printed to the terminal, for example:
```text
-Latest block number: 3855
+Latest block:
```
## Step 3: Creating a wallet
Now that we've initialized the client, we can create a wallet for Alice.
-To create a wallet for Alice using the Miden client, we specify whether the account is public or private via `AccountType` (in v0.15 the account type is simply `AccountType::Public` or `AccountType::Private`). A wallet on Miden is simply an account with standardized code.
+To create a wallet for Alice using the Miden client, we select `AccountType::Public` or `AccountType::Private`. A wallet on Miden is simply an account with standardized code.
In the example below we create a public account for Alice.
-Add this snippet to the end of your file in the `main()` function:
+Insert this snippet inside `main()`, immediately before its final `Ok(())`:
```rust ignore
//------------------------------------------------------------
@@ -165,7 +174,7 @@ let key_pair = AuthSecretKey::new_falcon512_poseidon2_with_rng(client.rng());
// Build the account
let alice_account = AccountBuilder::new(init_seed)
.account_type(AccountType::Public)
- .with_auth_component(AuthSingleSig::new(key_pair.public_key().to_commitment(), AuthSchemeId::Falcon512Poseidon2))
+ .with_component(AuthSingleSig::from_public_key(key_pair.public_key()))
.with_component(BasicWallet)
.build()
.unwrap();
@@ -174,20 +183,24 @@ let alice_account = AccountBuilder::new(init_seed)
client.add_account(&alice_account, false).await?;
// Add the key pair to the keystore
-use miden_client::keystore::Keystore;
-keystore.add_key(&key_pair, alice_account.id()).await.unwrap();
+keystore
+ .add_key(&key_pair, alice_account.id())
+ .await
+ .unwrap();
-let alice_account_id_bech32 = alice_account.id().to_bech32(NetworkId::Testnet);
+let alice_account_id_bech32 = alice_account.id().to_bech32(network.network_id());
println!("Alice's account ID: {:?}", alice_account_id_bech32);
+
+fund_account_for_fees(&mut client, alice_account.id(), &fee_config).await?;
```
## Step 4: Deploying a fungible faucet
-To provide Alice with testnet assets, we must first deploy a faucet. A faucet account on Miden mints fungible tokens.
+To provide Alice with the tutorial's `MID` asset, we first deploy a faucet. This is separate from the public testnet faucet that supplies the native asset used to pay transaction fees. A faucet account on Miden mints its own fungible token.
-We'll create a public faucet with a token symbol, decimals, and a max supply. We will use this faucet to mint tokens to Alice's account in the next section.
+We'll create a public faucet with a token symbol, decimals, and a max supply. Amounts in these examples are raw units: with eight decimals, `100` units means `0.000001 MID`. The faucet's maximum supply is `1_000_000` raw units. We will use it to mint tokens to Alice's account in the next section.
-Add this snippet to the end of your file in the `main()` function:
+Insert this snippet inside `main()`, immediately before its final `Ok(())`:
```rust ignore
//------------------------------------------------------------
@@ -208,46 +221,51 @@ let max_supply = AssetAmount::new(1_000_000).unwrap();
let key_pair = AuthSecretKey::new_falcon512_poseidon2_with_rng(client.rng());
// Build the faucet account.
-// In v0.15 the faucet is a `FungibleFaucet` component plus a `TokenPolicyManager`
+// The faucet is a `FungibleFaucet` component plus a `TokenPolicyManager`
// that registers an "allow all" mint (and burn) policy; minting is rejected
// unless an active mint policy is present.
-let faucet_account = AccountBuilder::new(init_seed)
- .account_type(AccountType::Public)
- .with_auth_component(AuthSingleSig::new(key_pair.public_key().to_commitment(), AuthSchemeId::Falcon512Poseidon2))
- .with_component(
- FungibleFaucet::builder()
- .name(TokenName::new("MID").unwrap())
- .symbol(symbol)
- .decimals(decimals)
- .max_supply(max_supply)
- .build()
- .unwrap(),
- )
- .with_components(
- TokenPolicyManager::new()
- .with_mint_policy(MintPolicyConfig::AllowAll, PolicyRegistration::Active)
- .unwrap()
- .with_burn_policy(BurnPolicyConfig::AllowAll, PolicyRegistration::Active)
- .unwrap(),
- )
+let faucet = FungibleFaucet::builder()
+ .name(TokenName::new("MID").unwrap())
+ .symbol(symbol)
+ .decimals(decimals)
+ .max_supply(max_supply)
.build()
.unwrap();
+let policies = TokenPolicyManager::builder()
+ .active_mint_policy(MintPolicy::allow_all())
+ .active_burn_policy(BurnPolicy::allow_all())
+ .build();
+// The SDK factory includes BasicWallet so the faucet can receive the native fee asset.
+let faucet_account = create_singlesig_user_fungible_faucet(
+ init_seed,
+ faucet,
+ AuthSingleSig::from_public_key(key_pair.public_key()),
+ policies,
+ AccountType::Public,
+)
+.unwrap();
// Add the faucet to the client
client.add_account(&faucet_account, false).await?;
// Add the key pair to the keystore
-use miden_client::keystore::Keystore;
-keystore.add_key(&key_pair, faucet_account.id()).await.unwrap();
+keystore
+ .add_key(&key_pair, faucet_account.id())
+ .await
+ .unwrap();
-let faucet_account_id_bech32 = faucet_account.id().to_bech32(NetworkId::Testnet);
+let faucet_account_id_bech32 = faucet_account.id().to_bech32(network.network_id());
println!("Faucet account ID: {:?}", faucet_account_id_bech32);
+fund_account_for_fees(&mut client, faucet_account.id(), &fee_config).await?;
+
// Resync to show newly deployed faucet
client.sync_state().await?;
tokio::time::sleep(Duration::from_secs(2)).await;
```
+`client.add_account` registers each new account locally. `fund_account_for_fees` then consumes a native-asset funding note; this first confirmed transaction deploys the account and gives it a fee balance.
+
_When tokens are minted from this faucet, each token batch is represented as a "note" (UTXO). You can think of a Miden Note as a cryptographic cashier's check that has certain spend conditions attached to it._
## Summary
@@ -255,41 +273,41 @@ _When tokens are minted from this faucet, each token batch is represented as a "
Your updated `main()` function in `src/main.rs` should look like this:
```rust no_run
-use miden_client::auth::{AuthSchemeId, AuthSingleSig};
-use rand::RngCore;
+use rand::Rng;
+use rust_client::TutorialClientExt;
use std::{path::PathBuf, sync::Arc};
use tokio::time::Duration;
use miden_client::{
+ ClientError,
account::{
+ AccountBuilder, AccountId, AccountType,
component::{
- BasicWallet, BurnPolicyConfig, FungibleFaucet, MintPolicyConfig, PolicyRegistration,
- TokenName, TokenPolicyManager,
+ create_singlesig_user_fungible_faucet, BasicWallet, BurnPolicy, FungibleFaucet,
+ MintPolicy, TokenName, TokenPolicyManager,
},
- AccountId,
},
- address::NetworkId,
- auth::AuthSecretKey,
+ asset::{AssetAmount, AssetCallbackFlag, AssetId, FungibleAsset, TokenSymbol},
+ auth::{AuthSecretKey, AuthSingleSig},
builder::ClientBuilder,
keystore::{FilesystemKeyStore, Keystore},
- note::{NoteAttachments, NoteType, P2idNote},
- rpc::{Endpoint, GrpcClient},
- transaction::TransactionRequestBuilder,
- ClientError,
+ note::{Note, NoteType, P2idNote},
+ rpc::{GrpcClient, VerifyingRpcClient},
+ transaction::{PaymentNoteDescription, TransactionRequestBuilder},
};
use miden_client_sqlite_store::ClientBuilderSqliteExt;
use miden_protocol::account::AccountIdVersion;
-use miden_client::{
- account::{AccountBuilder, AccountType},
- asset::{AssetAmount, FungibleAsset, TokenSymbol},
-};
+use rust_client::{FeeConfig, TutorialNetwork, fund_account_for_fees};
#[tokio::main]
async fn main() -> Result<(), ClientError> {
// Initialize client
- let endpoint = Endpoint::testnet();
+ let network = TutorialNetwork::from_env()?;
+ let endpoint = network.endpoint();
let timeout_ms = 10_000;
- let rpc_client = Arc::new(GrpcClient::new(&endpoint, timeout_ms));
+ let rpc_client = Arc::new(VerifyingRpcClient::new(GrpcClient::new(
+ &endpoint, timeout_ms,
+ )));
// Initialize keystore
let keystore_path = PathBuf::from("./keystore");
@@ -301,12 +319,12 @@ async fn main() -> Result<(), ClientError> {
.rpc(rpc_client)
.sqlite_store(store_path)
.authenticator(keystore.clone())
- .in_debug_mode(true.into())
.build()
.await?;
let sync_summary = client.sync_state().await.unwrap();
println!("Latest block: {}", sync_summary.block_num);
+ let fee_config = FeeConfig::from_client(&client, network).await?;
//------------------------------------------------------------
// STEP 1: Create a basic wallet for Alice
@@ -322,7 +340,7 @@ async fn main() -> Result<(), ClientError> {
// Build the account
let alice_account = AccountBuilder::new(init_seed)
.account_type(AccountType::Public)
- .with_auth_component(AuthSingleSig::new(key_pair.public_key().to_commitment(), AuthSchemeId::Falcon512Poseidon2))
+ .with_component(AuthSingleSig::from_public_key(key_pair.public_key()))
.with_component(BasicWallet)
.build()
.unwrap();
@@ -331,11 +349,16 @@ async fn main() -> Result<(), ClientError> {
client.add_account(&alice_account, false).await?;
// Add the key pair to the keystore
- keystore.add_key(&key_pair, alice_account.id()).await.unwrap();
+ keystore
+ .add_key(&key_pair, alice_account.id())
+ .await
+ .unwrap();
- let alice_account_id_bech32 = alice_account.id().to_bech32(NetworkId::Testnet);
+ let alice_account_id_bech32 = alice_account.id().to_bech32(network.network_id());
println!("Alice's account ID: {:?}", alice_account_id_bech32);
+ fund_account_for_fees(&mut client, alice_account.id(), &fee_config).await?;
+
//------------------------------------------------------------
// STEP 2: Deploy a fungible faucet
//------------------------------------------------------------
@@ -354,40 +377,44 @@ async fn main() -> Result<(), ClientError> {
let key_pair = AuthSecretKey::new_falcon512_poseidon2_with_rng(client.rng());
// Build the faucet account.
- // In v0.15 the faucet is a `FungibleFaucet` component plus a `TokenPolicyManager`
+ // The faucet is a `FungibleFaucet` component plus a `TokenPolicyManager`
// that registers an "allow all" mint (and burn) policy; minting is rejected
// unless an active mint policy is present.
- let faucet_account = AccountBuilder::new(init_seed)
- .account_type(AccountType::Public)
- .with_auth_component(AuthSingleSig::new(key_pair.public_key().to_commitment(), AuthSchemeId::Falcon512Poseidon2))
- .with_component(
- FungibleFaucet::builder()
- .name(TokenName::new("MID").unwrap())
- .symbol(symbol)
- .decimals(decimals)
- .max_supply(max_supply)
- .build()
- .unwrap(),
- )
- .with_components(
- TokenPolicyManager::new()
- .with_mint_policy(MintPolicyConfig::AllowAll, PolicyRegistration::Active)
- .unwrap()
- .with_burn_policy(BurnPolicyConfig::AllowAll, PolicyRegistration::Active)
- .unwrap(),
- )
+ let faucet = FungibleFaucet::builder()
+ .name(TokenName::new("MID").unwrap())
+ .symbol(symbol)
+ .decimals(decimals)
+ .max_supply(max_supply)
.build()
.unwrap();
+ let policies = TokenPolicyManager::builder()
+ .active_mint_policy(MintPolicy::allow_all())
+ .active_burn_policy(BurnPolicy::allow_all())
+ .build();
+ // The SDK factory includes BasicWallet so the faucet can receive the native fee asset.
+ let faucet_account = create_singlesig_user_fungible_faucet(
+ init_seed,
+ faucet,
+ AuthSingleSig::from_public_key(key_pair.public_key()),
+ policies,
+ AccountType::Public,
+ )
+ .unwrap();
// Add the faucet to the client
client.add_account(&faucet_account, false).await?;
// Add the key pair to the keystore
- keystore.add_key(&key_pair, faucet_account.id()).await.unwrap();
+ keystore
+ .add_key(&key_pair, faucet_account.id())
+ .await
+ .unwrap();
- let faucet_account_id_bech32 = faucet_account.id().to_bech32(NetworkId::Testnet);
+ let faucet_account_id_bech32 = faucet_account.id().to_bech32(network.network_id());
println!("Faucet account ID: {:?}", faucet_account_id_bech32);
+ fund_account_for_fees(&mut client, faucet_account.id(), &fee_config).await?;
+
// Resync to show newly deployed faucet
client.sync_state().await?;
tokio::time::sleep(Duration::from_secs(2)).await;
@@ -399,19 +426,19 @@ async fn main() -> Result<(), ClientError> {
Let's run the `src/main.rs` program again:
```bash
-cargo run --release
+TUTORIAL_NETWORK=testnet cargo run --release
```
-The output will look like this:
+The following is an abbreviated output; account IDs and block numbers vary, and the funding helper also prints transaction confirmations:
```text
-Latest block: 17771
+Latest block:
[STEP 1] Creating a new account for Alice
-Alice's account ID: "0x3cb3e596d14ad410000017901eaa7b"
+Alice's account ID: ""
[STEP 2] Deploying a new fungible faucet.
-Faucet account ID: "0x6ad1894ac233e4200000088311bb6b"
+Faucet account ID: ""
```
In this section we explained how to instantiate the Miden client, create a wallet account, and deploy a faucet.
@@ -420,11 +447,11 @@ In the next section we will cover how to mint tokens from the faucet, consume no
### Running the example
-To run a full working example navigate to the `rust-client` directory in the [miden-tutorials](https://github.com/0xMiden/miden-tutorials/) repository and run this command:
+From the root of your `tutorials` clone, run the checked-in example:
```bash
cd rust-client
-cargo run --release --bin create_mint_consume_send
+TUTORIAL_NETWORK=testnet cargo run --release --bin create_mint_consume_send
```
### Continue learning
diff --git a/docs/src/rust-client/creating_notes_in_masm_tutorial.md b/docs/src/rust-client/creating_notes_in_masm_tutorial.md
index a168a079..29fad3b7 100644
--- a/docs/src/rust-client/creating_notes_in_masm_tutorial.md
+++ b/docs/src/rust-client/creating_notes_in_masm_tutorial.md
@@ -7,13 +7,15 @@ sidebar_position: 11
_Creating notes inside the MidenVM using Miden assembly_
+For toolchain requirements and shared fee helpers, see the [Rust client setup](./index.md#running-the-v016-examples).
+
## Overview
In this tutorial, we will create a custom note that generates a copy of itself when it is consumed by an account. The purpose of this tutorial is to demonstrate how to create notes inside the MidenVM using Miden assembly (MASM). By the end of this tutorial, you will understand how to write MASM code that creates notes.
## What We'll Cover
-- Computing the note inputs commitment in MASM
+- Computing the note storage commitment and recipient in MASM
- Creating notes in MASM
## Prerequisites
@@ -26,7 +28,7 @@ Being able to create a note in MASM enables you to build various types of applic
Here are some tangible examples of when creating a note in MASM is useful in a DeFi context:
-- Creating snapshots of an account's state at a specific point in time (not possible in an EVM context)
+- Creating notes that record selected values from an account's state
- Representing partially fillable buy/sell orders as notes (SWAPP)
- Handling withdrawals from a smart contract
@@ -36,166 +38,184 @@ Here are some tangible examples of when creating a note in MASM is useful in a D
In the diagram above, note A is consumed by an account, and during the transaction, note A' is created.
-In this tutorial, we will create a note that contains an asset. When consumed, it outputs a copy of itself and allows the consuming account to take half of the asset. Although this type of note would not be used in a real-world context, it demonstrates several key concepts for writing MASM code that can create notes.
+In this tutorial, Alice creates a note containing 100 raw units of a fungible asset. Bob consumes it, keeps 50 units, and creates a successor note with the other 50, the same script and storage, and an incremented serial number. The script does not restrict consumption to a particular account.
+
+This example requires exactly one fungible asset with a positive even amount and performs one split, from 100 to 50. MASM `div` is field division, so this script does not implement rounding for odd integer amounts and should not be used as an arbitrary repeated-halving contract.
## Step 1: Initialize Your Repository
-Create a new Rust repository for your Miden project and navigate to it with the following command:
+Start in the directory containing your `tutorials` clone and create a sibling Cargo project. The dependency path below assumes the clone is named `tutorials`.
```bash
cargo new miden-project
cd miden-project
+rustup override set 1.98.1
+cp ../tutorials/rust-client/Cargo.lock Cargo.lock
```
-Add the following dependencies to your `Cargo.toml` file:
+Keep the generated `[package]` section in `Cargo.toml`, replace its empty `[dependencies]` section with the following, and add the development profile:
```toml
[dependencies]
-miden-client = { version = "0.15", features = ["testing", "tonic"] }
-miden-client-sqlite-store = { version = "0.15", package = "miden-client-sqlite-store" }
-miden-protocol = { version = "0.15" }
-rand = { version = "0.9" }
-tokio = { version = "1.46", features = ["rt-multi-thread", "net", "macros", "fs"] }
+# Clone tutorials next to this Cargo project (see Rust client setup).
+rust-client = { path = "../tutorials/rust-client" }
+miden-client = { version = "=0.16.0", features = ["testing", "tonic"] }
+miden-client-sqlite-store = { version = "=0.16.0", package = "miden-client-sqlite-store" }
+miden-protocol = { version = "=0.16.0" }
+rand = { version = "0.10" }
+tokio = { version = "1.48", features = ["rt-multi-thread", "net", "macros", "fs"] }
+
+[profile.dev]
+opt-level = 2
```
## Step 2: Write the Note Script
-For better code organization, we will separate the Miden assembly code from our Rust code.
-
-Create a directory named `masm` at the **root** of your `miden-project` directory. This directory will contain our contract and MASM script code.
-
-Initialize the `masm` directory:
-
-```bash
-mkdir masm/notes
-```
-
-This will create:
-
-```text
-masm/
-└── notes/
-```
-
-Inside the `masm/notes/` directory, create the file `iterative_output_note.masm`. Note scripts are compiled as libraries; the `@note_script` attribute marks the entrypoint procedure.
+The note script is in `masm/notes/iterative_output_note.masm`. Note scripts are compiled as libraries; the `@note_script` attribute marks the entrypoint procedure.
```masm
use miden::protocol::active_note
use miden::protocol::note
-use miden::protocol::output_note
use miden::core::sys
-use miden::standards::wallets::basic->wallet
-
-# Memory Addresses
-# get_assets writes: ASSET_KEY at ASSET_KEY_PTR, ASSET_VALUE at ASSET_KEY_PTR+4 (ASSET_SIZE=8)
-const ASSET_KEY_PTR=0
-const ASSET_VALUE_PTR=4
-const ASSET_HALF_VALUE_PTR=8 # half-amount ASSET_VALUE stored here
-const ACCOUNT_ID_PREFIX=12 # storage: [prefix, suffix, tag, 0]
-const TAG=14 # = ACCOUNT_ID_PREFIX + 2
-
-#! Inputs: []
-#! Outputs: []
+use miden::standards::wallets::basic as wallet
+use miden::standards::note::note_creator
+
+# CONSTANTS
+# =================================================================================================
+
+# get_initial_assets writes the eight-felt asset as ASSET_ID followed by ASSET_VALUE
+const ASSET_ID_PTR = 0
+const ASSET_VALUE_PTR = 4
+const ASSET_HALF_VALUE_PTR = 8
+const ACCOUNT_ID_PREFIX = 12 # storage: [prefix, suffix, tag, 0]
+const TAG = 14 # ACCOUNT_ID_PREFIX + 2
+
+# PUBLIC INTERFACE
+# =================================================================================================
+
+#! Receives this note's assets and creates a successor with half its fungible amount.
+#!
+#! This example expects exactly one fungible asset with a positive, even amount. Field division
+#! by two is not integer rounding, so an odd amount does not produce a valid half-amount transfer.
+#! Any account exposing the wallet and note-creator procedures may consume the note; the account
+#! ID in storage is copied into the successor's storage and does not restrict consumption.
+#!
+#! Inputs: [ARGS, pad(12)]
+#! Outputs: [pad(16)]
+#!
+#! Where:
+#! - ARGS contains unused note arguments.
+#! - note storage contains a copied account ID and the successor's note tag.
+#!
+#! Panics if:
+#! - the account cannot receive the note's assets or move the computed half amount to the successor.
+#!
+#! Invocation: dyncall
@note_script
-pub proc main
- # Drop word if user accidentally pushes note_args
+pub proc main(args: word)
+ # discard the unused note arguments
dropw
- # => []
+ # => [pad(16)]
- # Get asset contained in note into memory (ASSET_KEY at 0, ASSET_VALUE at 4)
- # get_assets leaves [num_assets] on the stack in v0.15; drop it.
- push.ASSET_KEY_PTR exec.active_note::get_assets drop
- # => []
+ # get asset contained in note into memory (ASSET_ID at 0, ASSET_VALUE at 4)
+ # get_initial_assets leaves [num_assets] on the stack; drop it.
+ push.ASSET_ID_PTR exec.active_note::get_initial_assets drop
+ # => [pad(16)]
- # Load ASSET_VALUE and compute half amount
+ # load ASSET_VALUE and compute half amount
padw push.ASSET_VALUE_PTR mem_loadw_le
- # => [av0, av1, av2, av3] (av0 = amount for fungible asset, av1/av2/av3 = 0)
+ # => [[amount, 0, 0, 0], pad(16)]
- # Halve the amount (av0 is the amount for fungible assets)
+ # halve the even fungible amount
push.2 div
- # => [av0/2, av1, av2, av3]
+ # => [[amount / 2, 0, 0, 0], pad(16)]
- # Store as ASSET_HALF_VALUE
+ # store as ASSET_HALF_VALUE
mem_storew_le.ASSET_HALF_VALUE_PTR dropw
- # => []
+ # => [pad(16)]
- # Receive all assets from note into the account wallet
- exec.wallet::add_assets_to_account
- # => []
+ # receive all assets from note into the account wallet
+ exec.wallet::move_note_assets_to_account
+ # => [pad(16)]
- # Push script hash
+ # push script hash
exec.active_note::get_script_root
- # => [SCRIPT_HASH]
+ # => [SCRIPT_ROOT, pad(16)]
- # Get the current note serial number
+ # get the current note serial number
exec.active_note::get_serial_number
- # => [SERIAL_NUM, SCRIPT_HASH]
+ # => [SERIAL_NUM, SCRIPT_ROOT, pad(16)]
- # Increment the last element of the serial number by 1
+ # increment the last element of the serial number by 1
# (serial_num[3] is at depth 3; matches Rust: serial_num[3] + 1)
swap.3 push.1 add swap.3
- # => [SERIAL_NUM+1, SCRIPT_HASH]
+ # => [NEXT_SERIAL_NUM, SCRIPT_ROOT, pad(16)]
- # Load note storage into memory for recipient construction.
- # get_storage consumes dest_ptr and leaves only [num_storage_items],
- # so re-push the storage_ptr for the recipient call rather than swapping.
+ # load note storage into memory for recipient construction
push.ACCOUNT_ID_PREFIX
exec.active_note::get_storage
- # => [num_storage_items, SERIAL_NUM+1, SCRIPT_HASH]
+ # => [num_storage_items, NEXT_SERIAL_NUM, SCRIPT_ROOT, pad(16)]
push.ACCOUNT_ID_PREFIX
- # => [storage_ptr, num_storage_items, SERIAL_NUM+1, SCRIPT_HASH]
+ # => [storage_ptr, num_storage_items, NEXT_SERIAL_NUM, SCRIPT_ROOT, pad(16)]
- # v0.15 renamed note::build_recipient -> note::compute_and_store_recipient
- # (arg shape [storage_ptr, num_storage_items, SERIAL_NUM, SCRIPT_ROOT]).
+ # argument shape: [storage_ptr, num_storage_items, SERIAL_NUM, SCRIPT_ROOT].
exec.note::compute_and_store_recipient
- # => [RECIPIENT]
+ # => [RECIPIENT, pad(16)]
- # Push note type to stack (public note = 1)
+ # push note type to stack (public note = 1)
push.1
- # => [note_type, RECIPIENT]
+ # => [note_type, RECIPIENT, pad(16)]
- # Load tag from memory
+ # load tag from memory
mem_load.TAG
- # => [tag, note_type, RECIPIENT]
+ # => [tag, note_type, RECIPIENT, pad(16)]
- exec.output_note::create
- # => [note_idx]
+ # note creation from a note script must call the account's note-creator procedure.
+ # pad the stack for the account procedure call convention.
+ push.0 movdn.6 push.0 movdn.6 padw padw swapdw
+ # => [tag, note_type, RECIPIENT, pad(26)]
- # Build [ASSET_KEY, ASSET_HALF_VALUE, note_idx] for move_asset_to_note
- # Inputs: [ASSET_KEY, ASSET_VALUE, note_idx, pad(7)]
+ call.note_creator::create_note
+ # => [note_idx, pad(31)]
- # Push ASSET_HALF_VALUE (note_idx moves to depth 4)
+ movdn.15 dropw dropw dropw drop drop drop
+ # => [note_idx, pad(16)]
+
+ # build [ASSET_ID, ASSET_HALF_VALUE, note_idx] for move_asset_to_note
+ # inputs: [ASSET_ID, ASSET_VALUE, note_idx, pad(7)]
+
+ # push ASSET_HALF_VALUE (note_idx moves to depth 4)
padw push.ASSET_HALF_VALUE_PTR mem_loadw_le
- # => [ASSET_HALF_VALUE, note_idx]
+ # => [ASSET_HALF_VALUE, note_idx, pad(16)]
- # Push ASSET_KEY (ASSET_HALF_VALUE moves to depth 4, note_idx to depth 8)
- padw push.ASSET_KEY_PTR mem_loadw_le
- # => [ASSET_KEY, ASSET_HALF_VALUE, note_idx]
+ # push ASSET_ID (ASSET_HALF_VALUE moves to depth 4, note_idx to depth 8)
+ padw push.ASSET_ID_PTR mem_loadw_le
+ # => [ASSET_ID, ASSET_HALF_VALUE, note_idx, pad(16)]
call.wallet::move_asset_to_note
- # => [pad(16)]
+ # => [pad(25)]
dropw dropw dropw dropw
- # => []
+ # => [pad(16)]
exec.sys::truncate_stack
- # => []
+ # => [pad(16)]
end
```
### How the Assembly Code Works:
1. **Retrieving the asset:**
- The note calls `active_note::get_assets` to write the asset into memory, with `ASSET_KEY` at address 0 and `ASSET_VALUE` at address 4. It halves the amount in `ASSET_VALUE` and stores it at `ASSET_HALF_VALUE_PTR`. Finally, it calls `wallet::add_assets_to_account` to receive all note assets into the consuming account.
+ The note calls `active_note::get_initial_assets` to copy the initial asset into memory, with `ASSET_ID` at address 0 and `ASSET_VALUE` at address 4. It halves the amount in `ASSET_VALUE` and stores it at `ASSET_HALF_VALUE_PTR`. Finally, it calls `wallet::move_note_assets_to_account`, which explicitly removes the assets from the note and receives them into the consuming account.
2. **Getting the script hash and serial number:**
The note script calls `active_note::get_script_root` to fetch the script hash and `active_note::get_serial_number` to fetch the current serial number, then increments element 3 (the last element) by 1 to avoid duplicate recipients.
3. **Building the `RECIPIENT`:**
The script loads the note storage into memory with `active_note::get_storage`, then calls `note::compute_and_store_recipient`. This computes the storage commitment and stores the preimage in the advice map, which is required for public notes.
4. **Creating the note:**
- To create the note, the script pushes the note type and tag onto the stack, then calls the `output_note::create` procedure, which returns the note index.
+ To create the note from a note script, the script pads the stack for the account-call ABI and calls the account's exported `note_creator::create_note` procedure, which enters the account context and returns the note index. The consuming account must expose `NoteCreator`; `BasicWallet` includes it.
5. **Moving assets to the note:**
- After the note is created, the script loads `ASSET_KEY` and `ASSET_HALF_VALUE` from memory onto the stack and calls `wallet::move_asset_to_note` with the note index.
+ After the note is created, the script loads `ASSET_ID` and `ASSET_HALF_VALUE` from memory onto the stack and calls `wallet::move_asset_to_note` with the note index.
6. **Stack cleanup:**
Finally, the script cleans up the stack by calling `sys::truncate_stack`.
@@ -206,21 +226,23 @@ With the Miden assembly note script written, we can move on to writing the Rust
Copy and paste the following code into your `src/main.rs` file.
```rust no_run
-use rand::RngCore;
+use rand::Rng;
+use rust_client::TutorialClientExt;
use std::{path::PathBuf, sync::Arc};
-use tokio::time::{sleep, Duration};
+use tokio::time::{Duration, sleep};
use miden_client::{
+ Client, ClientError, Felt,
account::{
+ Account, AccountBuilder, AccountType,
component::{
- BasicWallet, BurnPolicyConfig, FungibleFaucet, MintPolicyConfig, PolicyRegistration,
- TokenName, TokenPolicyManager,
+ create_singlesig_user_fungible_faucet, BasicWallet, BurnPolicy, FungibleFaucet,
+ MintPolicy, TokenName, TokenPolicyManager,
},
- Account, AccountBuilder, AccountType,
},
address::NetworkId,
- asset::{AssetAmount, FungibleAsset, TokenSymbol},
- auth::{AuthSchemeId, AuthSecretKey, AuthSingleSig},
+ asset::{AssetAmount, AssetId, FungibleAsset, TokenSymbol},
+ auth::{AuthSecretKey, AuthSingleSig},
builder::ClientBuilder,
crypto::FeltRng,
keystore::{FilesystemKeyStore, Keystore},
@@ -228,12 +250,11 @@ use miden_client::{
Note, NoteAssets, NoteDetails, NoteRecipient, NoteStorage, NoteTag, NoteType,
PartialNoteMetadata,
},
- rpc::{Endpoint, GrpcClient},
- store::TransactionFilter,
- transaction::{TransactionId, TransactionRequestBuilder, TransactionStatus},
- Client, ClientError, Felt,
+ rpc::{GrpcClient, VerifyingRpcClient},
+ transaction::{TransactionId, TransactionRequestBuilder},
};
use miden_client_sqlite_store::ClientBuilderSqliteExt;
+use rust_client::{FeeConfig, TutorialNetwork, fund_account_for_fees};
// Helper to create a basic account
async fn create_basic_account(
@@ -247,7 +268,7 @@ async fn create_basic_account(
let account = AccountBuilder::new(init_seed)
.account_type(AccountType::Public)
- .with_auth_component(AuthSingleSig::new(key_pair.public_key().to_commitment(), AuthSchemeId::Falcon512Poseidon2))
+ .with_component(AuthSingleSig::from_public_key(key_pair.public_key()))
.with_component(BasicWallet)
.build()
.unwrap();
@@ -270,27 +291,25 @@ async fn create_basic_faucet(
let decimals = 8;
let max_supply = AssetAmount::new(1_000_000).unwrap();
- let account = AccountBuilder::new(init_seed)
- .account_type(AccountType::Public)
- .with_auth_component(AuthSingleSig::new(key_pair.public_key().to_commitment(), AuthSchemeId::Falcon512Poseidon2))
- .with_component(
- FungibleFaucet::builder()
- .name(TokenName::new("MID").unwrap())
- .symbol(symbol)
- .decimals(decimals)
- .max_supply(max_supply)
- .build()
- .unwrap(),
- )
- .with_components(
- TokenPolicyManager::new()
- .with_mint_policy(MintPolicyConfig::AllowAll, PolicyRegistration::Active)
- .unwrap()
- .with_burn_policy(BurnPolicyConfig::AllowAll, PolicyRegistration::Active)
- .unwrap(),
- )
+ let faucet = FungibleFaucet::builder()
+ .name(TokenName::new("MID").unwrap())
+ .symbol(symbol)
+ .decimals(decimals)
+ .max_supply(max_supply)
.build()
.unwrap();
+ let policies = TokenPolicyManager::builder()
+ .active_mint_policy(MintPolicy::allow_all())
+ .active_burn_policy(BurnPolicy::allow_all())
+ .build();
+ let account = create_singlesig_user_fungible_faucet(
+ init_seed,
+ faucet,
+ AuthSingleSig::from_public_key(key_pair.public_key()),
+ policies,
+ AccountType::Public,
+ )
+ .unwrap();
client.add_account(&account, false).await?;
keystore.add_key(&key_pair, account.id()).await.unwrap();
@@ -303,21 +322,29 @@ async fn wait_for_notes(
client: &mut Client,
account_id: &Account,
expected: usize,
+ network_id: NetworkId,
) -> Result<(), ClientError> {
- loop {
+ for _ in 0..24 {
client.sync_state().await?;
- let notes = client.get_consumable_notes(Some(account_id.id())).await?;
+ let notes = client
+ .get_consumable_tutorial_notes(Some(account_id.id()))
+ .await?;
if notes.len() >= expected {
- break;
+ return Ok(());
}
println!(
"{} consumable notes found for account {}. Waiting...",
notes.len(),
- account_id.id().to_bech32(NetworkId::Testnet)
+ account_id.id().to_bech32(network_id.clone())
);
sleep(Duration::from_secs(3)).await;
}
- Ok(())
+ Err(ClientError::Observer(Box::new(std::io::Error::other(
+ format!(
+ "timed out waiting for {expected} tutorial notes for {}",
+ account_id.id()
+ ),
+ ))))
}
/// Waits for a specific transaction to be committed.
@@ -325,39 +352,18 @@ async fn wait_for_tx(
client: &mut Client,
tx_id: TransactionId,
) -> Result<(), ClientError> {
- loop {
- client.sync_state().await?;
-
- // Check transaction status
- let txs = client
- .get_transactions(TransactionFilter::Ids(vec![tx_id]))
- .await?;
- let tx_committed = if !txs.is_empty() {
- matches!(txs[0].status, TransactionStatus::Committed { .. })
- } else {
- false
- };
-
- if tx_committed {
- println!("✅ transaction {} committed", tx_id.to_hex());
- break;
- }
-
- println!(
- "Transaction {} not yet committed. Waiting...",
- tx_id.to_hex()
- );
- sleep(Duration::from_secs(2)).await;
- }
- Ok(())
+ rust_client::wait_for_transaction(client, tx_id).await
}
#[tokio::main]
async fn main() -> Result<(), ClientError> {
// Initialize client
- let endpoint = Endpoint::testnet();
+ let network = TutorialNetwork::from_env()?;
+ let endpoint = network.endpoint();
let timeout_ms = 10_000;
- let rpc_client = Arc::new(GrpcClient::new(&endpoint, timeout_ms));
+ let rpc_client = Arc::new(VerifyingRpcClient::new(GrpcClient::new(
+ &endpoint, timeout_ms,
+ )));
// Initialize keystore
let keystore_path = PathBuf::from("./keystore");
@@ -369,12 +375,12 @@ async fn main() -> Result<(), ClientError> {
.rpc(rpc_client)
.sqlite_store(store_path)
.authenticator(keystore.clone())
- .in_debug_mode(true.into())
.build()
.await?;
let sync_summary = client.sync_state().await.unwrap();
println!("Latest block: {}", sync_summary.block_num);
+ let fee_config = FeeConfig::from_client(&client, network).await?;
// -------------------------------------------------------------------------
// STEP 1: Create accounts and deploy faucet
@@ -383,20 +389,23 @@ async fn main() -> Result<(), ClientError> {
let alice_account = create_basic_account(&mut client, &keystore).await?;
println!(
"Alice's account ID: {:?}",
- alice_account.id().to_bech32(NetworkId::Testnet)
+ alice_account.id().to_bech32(network.network_id())
);
let bob_account = create_basic_account(&mut client, &keystore).await?;
println!(
"Bob's account ID: {:?}",
- bob_account.id().to_bech32(NetworkId::Testnet)
+ bob_account.id().to_bech32(network.network_id())
);
println!("\nDeploying a new fungible faucet.");
let faucet = create_basic_faucet(&mut client, &keystore).await?;
println!(
"Faucet account ID: {:?}",
- faucet.id().to_bech32(NetworkId::Testnet)
+ faucet.id().to_bech32(network.network_id())
);
+ for account_id in [alice_account.id(), bob_account.id(), faucet.id()] {
+ fund_account_for_fees(&mut client, account_id, &fee_config).await?;
+ }
client.sync_state().await?;
// -------------------------------------------------------------------------
@@ -416,14 +425,16 @@ async fn main() -> Result<(), ClientError> {
)
.unwrap();
- let tx_id = client.submit_new_transaction(faucet.id(), tx_req).await?;
+ let tx_id = client
+ .submit_tutorial_transaction(faucet.id(), tx_req)
+ .await?;
println!("Minted tokens. TX: {:?}", tx_id);
- wait_for_notes(&mut client, &alice_account, 1).await?;
+ wait_for_notes(&mut client, &alice_account, 1, network.network_id()).await?;
// Consume the minted note
let consumable_notes = client
- .get_consumable_notes(Some(alice_account.id()))
+ .get_consumable_tutorial_notes(Some(alice_account.id()))
.await?;
if let Some((note_record, _)) = consumable_notes.first() {
@@ -431,7 +442,7 @@ async fn main() -> Result<(), ClientError> {
let consume_req = TransactionRequestBuilder::new().build_consume_notes(vec![note])?;
let tx_id = client
- .submit_new_transaction(alice_account.id(), consume_req)
+ .submit_tutorial_transaction(alice_account.id(), consume_req)
.await?;
println!("Consumed minted note. TX: {:?}", tx_id);
}
@@ -443,15 +454,15 @@ async fn main() -> Result<(), ClientError> {
// -------------------------------------------------------------------------
println!("\n[STEP 3] Create iterative output note");
- // `include_str!` resolves at compile time relative to this source file,
- // so the binary is independent of the working directory it is run from.
- let code = include_str!("../masm/notes/iterative_output_note.masm");
+ // Read the MASM source from the tutorials repository.
+ let code =
+ std::fs::read_to_string("../tutorials/masm/notes/iterative_output_note.masm").unwrap();
let serial_num = client.rng().draw_word();
// Create note metadata and tag
let tag = NoteTag::new(0);
let metadata = PartialNoteMetadata::new(alice_account.id(), NoteType::Public).with_tag(tag);
- let note_script = client.code_builder().compile_note_script(code).unwrap();
+ let note_script = client.code_builder().compile_note_script(&code).unwrap();
let note_storage = NoteStorage::new(vec![
alice_account.id().prefix().as_felt(),
alice_account.id().suffix(),
@@ -470,10 +481,11 @@ async fn main() -> Result<(), ClientError> {
.unwrap();
let tx_id = client
- .submit_new_transaction(alice_account.id(), note_req)
+ .submit_tutorial_transaction(alice_account.id(), note_req)
.await?;
println!(
- "View transaction on MidenScan: https://testnet.midenscan.com/tx/{:?}",
+ "View transaction on MidenScan: {}/tx/{:?}",
+ network.explorer_url(),
tx_id
);
@@ -505,25 +517,53 @@ async fn main() -> Result<(), ClientError> {
let consume_custom_req = TransactionRequestBuilder::new()
.input_notes([(custom_note, None)])
- .expected_future_notes(vec![(
- NoteDetails::from(output_note.clone()),
- output_note.metadata().tag(),
- )
- .clone()])
+ .expected_future_notes(vec![
+ (
+ NoteDetails::from(output_note.clone()),
+ output_note.metadata().tag(),
+ )
+ .clone(),
+ ])
.expected_output_recipients(vec![output_note.recipient().clone()])
.build()
.unwrap();
let tx_id = client
- .submit_new_transaction(bob_account.id(), consume_custom_req)
+ .submit_tutorial_transaction(bob_account.id(), consume_custom_req)
.await?;
println!(
- "Consumed Note Tx on MidenScan: https://testnet.midenscan.com/tx/{:?}",
+ "Consumed Note Tx on MidenScan: {}/tx/{:?}",
+ network.explorer_url(),
tx_id
);
wait_for_tx(&mut client, tx_id).await?;
+ // The SDK verifies expected recipients; also check the actual successor's assets and metadata.
+ let successor = client
+ .get_output_note(output_note.id())
+ .await?
+ .expect("the transaction must create the expected successor note");
+ assert!(successor.is_committed(), "the successor must be committed");
+ assert_eq!(successor.assets(), output_note.assets());
+ assert_eq!(successor.metadata(), output_note.metadata());
+ println!(
+ "Successor note committed with 50 tokens: {}",
+ successor.id()
+ );
+
+ let bob = client
+ .get_account(bob_account.id())
+ .await?
+ .expect("Bob's account must exist after consuming the note");
+ let balance = bob.vault().get_balance(AssetId::new_fungible(faucet_id))?;
+ assert_eq!(
+ balance.as_u64(),
+ 50,
+ "Bob must retain the other half of the note's tokens",
+ );
+ println!("Bob's retained token balance: {balance}");
+
Ok(())
}
```
@@ -531,44 +571,44 @@ async fn main() -> Result<(), ClientError> {
Run the following command to execute `src/main.rs`:
```bash
-cargo run --release
+TUTORIAL_NETWORK=testnet cargo run --release
```
-The output will look something like this:
+The following is an abbreviated output; IDs vary, and funding and repeated confirmation messages are omitted:
```text
-Latest block: 488715
+Latest block:
[STEP 1] Creating new accounts
-Alice's account ID: "mtst1azvwquwfvh0jyytq0dk9xya9tvhvu935"
-Bob's account ID: "mtst1ap9hwvau7sy9tvtka6smn0ev7cxtgt03"
+Alice's account ID: ""
+Bob's account ID: ""
Deploying a new fungible faucet.
-Faucet account ID: "mtst1apj3jthkj4mweyf7qt254h5m5gdemp9u"
+Faucet account ID: ""
[STEP 2] Mint tokens with P2ID
-Minted tokens. TX: 0xf3c8f183aeefb086ca4a63f2a6f34535ea4217849e8e870033f892503302fb7d
-0 consumable notes found for account mtst1azvwquwfvh0jyytq0dk9xya9tvhvu935. Waiting...
-Consumed minted note. TX: 0x2d31ce827549d8bf35d1c3613610f8388d6c2369dbd1aa34f4f3406f86fdff55
+Minted tokens. TX:
+Consumed minted note. TX:
[STEP 3] Create iterative output note
-View transaction on MidenScan: https://testnet.midenscan.com/tx/0xadabf7a920ee27bf1fabd3b02e8e6f3d80f84ece31a23d73f27b0b56bbc2fdc3
+View transaction on MidenScan: https://testnet.midenscan.com/tx/
[STEP 4] Bob consumes the note and creates a copy
-Consumed Note Tx on MidenScan: https://testnet.midenscan.com/tx/0xa003d298db5e7de263a8b98930b6e75336c3cca7cd4a090c707edb6a7f061ad5
-Transaction 0xa003d298db5e7de263a8b98930b6e75336c3cca7cd4a090c707edb6a7f061ad5 not yet committed. Waiting...
-✅ transaction 0xa003d298db5e7de263a8b98930b6e75336c3cca7cd4a090c707edb6a7f061ad5 committed
+Consumed Note Tx on MidenScan: https://testnet.midenscan.com/tx/
+Transaction committed:
+Successor note committed with 50 tokens:
+Bob's retained token balance: 50
```
---
### Running the example
-To run the full example, navigate to the `rust-client` directory in the [miden-tutorials](https://github.com/0xMiden/miden-tutorials/) repository and run this command:
+From the root of your `tutorials` clone, run the checked-in example:
```bash
cd rust-client
-cargo run --release --bin note_creation_in_masm
+TUTORIAL_NETWORK=testnet cargo run --release --bin note_creation_in_masm
```
### Continue learning
diff --git a/docs/src/rust-client/custom_note_how_to.md b/docs/src/rust-client/custom_note_how_to.md
index e3c28b2e..78f19c9d 100644
--- a/docs/src/rust-client/custom_note_how_to.md
+++ b/docs/src/rust-client/custom_note_how_to.md
@@ -7,6 +7,8 @@ sidebar_position: 7
_Creating notes with custom logic_
+For toolchain requirements and shared fee helpers, see the [Rust client setup](./index.md#running-the-v016-examples).
+
## Overview
In this guide, we will create a custom note on Miden that can only be consumed by someone who knows the preimage of the hash stored in the note. This approach securely embeds assets into the note and restricts spending to those who possess the correct secret number.
@@ -14,9 +16,9 @@ In this guide, we will create a custom note on Miden that can only be consumed b
By following the steps below and using the Miden Assembly code and Rust example, you will learn how to:
- Create a note with custom logic.
-- Leverage Miden’s privacy features to keep certain transaction details private.
+- Store the hash publicly while providing its preimage only to the transaction that consumes the note.
-Unlike Ethereum, where all pending transactions are publicly visible in the mempool, Miden enables you to partially or completely hide transaction details.
+This example uses public accounts and a public note. Its script checks knowledge of the secret, rather than a particular account ID, so any account with the secret and the required wallet procedure can consume it.
## What we'll cover
@@ -37,67 +39,77 @@ First, we create two basic accounts for the two users:
The security of the custom note hinges on a secret number. Here, we will:
- Choose a secret number (for example, an array of four integers).
-- For simplicity, we're only hashing 4 elements. Therefore, we prepend an empty word—consisting of 4 zero integers—as a placeholder. This is required by the RPO hashing algorithm to ensure the input has the correct structure and length for proper processing.
-- Compute the hash of the secret. The resulting hash will serve as the note’s input, meaning that the note can only be consumed if the secret number’s hash preimage is provided during consumption.
+- Hash the four field elements directly with `miden_protocol::Hasher::hash_elements`, which uses Poseidon2 in v0.16. The MASM `hash` instruction computes the matching digest; do not prepend an extra zero word to the Rust input.
+- Compute the hash of the secret. The resulting hash will be stored in the note’s storage, meaning that the note can only be consumed if the secret number’s hash preimage is provided during consumption.
### 3. Creating the custom note
Now, combine the minted asset and the secret hash to build the custom note. The note is created using the following key steps:
-1. **Note Inputs:**
- - The note is set up with the asset and the hash of the secret number as its input.
+1. **Assets and storage:**
+ - The note carries 100 raw units of the tutorial asset and stores the secret's digest in `NoteStorage`. The secret itself is supplied later as the consuming transaction's note arguments.
2. **Miden Assembly Code:**
- - The Miden assembly note script ensures that the note can only be consumed if the provided secret, when hashed, matches the hash stored in the note input.
+ - The Miden assembly note script ensures that the note can only be consumed if the provided secret, when hashed, matches the hash stored in the note storage.
Below is the Miden Assembly code for the note. Note scripts are compiled as libraries; the `@note_script` attribute marks the entrypoint procedure.
```masm
use miden::protocol::active_note
-use miden::standards::wallets::basic->wallet
+use miden::standards::wallets::basic as wallet
# CONSTANTS
# =================================================================================================
-const EXPECTED_DIGEST_PTR=0
+const EXPECTED_DIGEST_PTR = 0
# ERRORS
# =================================================================================================
-const ERROR_DIGEST_MISMATCH="Expected digest does not match computed digest"
+const ERROR_DIGEST_MISMATCH = "Expected digest does not match computed digest"
+
+# PUBLIC INTERFACE
+# =================================================================================================
-#! Inputs (arguments): [HASH_PREIMAGE_SECRET]
-#! Outputs: []
+#! Consumes the note's assets when the secret hashes to its stored digest.
+#!
+#! Inputs: [HASH_PREIMAGE_SECRET, pad(12)]
+#! Outputs: [pad(16)]
#!
-#! Note storage is assumed to be as follows:
-#! => EXPECTED_DIGEST
+#! Where:
+#! - HASH_PREIMAGE_SECRET is the four-felt secret supplied as note arguments.
+#!
+#! Panics if:
+#! - the supplied secret does not match the digest stored in the note.
+#!
+#! Invocation: dyncall
@note_script
-pub proc main
- # => HASH_PREIMAGE_SECRET
- # Hashing the secret number
+pub proc main(hash_preimage_secret: word)
+ # => [HASH_PREIMAGE_SECRET, pad(12)]
+ # hashing the secret number
hash
- # => [DIGEST]
+ # => [DIGEST, pad(12)]
- # Writing the note storage to memory.
+ # writing the note storage to memory.
# get_storage leaves only [num_storage_items], so drop a single element
# here, not two, to keep the computed DIGEST intact.
push.EXPECTED_DIGEST_PTR exec.active_note::get_storage drop
- # Pad stack and load expected digest from memory (LE: mem[addr] ends up on top)
+ # pad stack and load expected digest from memory (LE: mem[addr] ends up on top)
padw push.EXPECTED_DIGEST_PTR mem_loadw_le
- # => [EXPECTED_DIGEST, DIGEST]
+ # => [EXPECTED_DIGEST, DIGEST, pad(12)]
- # Assert that the note input matches the digest
- # Will fail if the two hashes do not match
+ # assert that the note input matches the digest
+ # will fail if the two hashes do not match
assert_eqw.err=ERROR_DIGEST_MISMATCH
- # => []
+ # => [pad(16)]
# ---------------------------------------------------------------------------------------------
- # If the check is successful, we allow for the asset to be consumed
+ # if the check is successful, we allow for the asset to be consumed
# ---------------------------------------------------------------------------------------------
- # Add all assets from the note to the account
- exec.wallet::add_assets_to_account
- # => []
+ # add all assets from the note to the account
+ exec.wallet::move_note_assets_to_account
+ # => [pad(16)]
end
```
@@ -112,45 +124,73 @@ end
4. **Digest Comparison:**
The assembly code loads the expected digest from note storage into memory, then reads it back with `mem_loadw_le` (which places `mem[addr]` on top, matching the hash output order) and compares with the computed hash. If they don't match, the transaction fails with a clear error message.
5. **Asset Transfer:**
- If the hash matches, `wallet::add_assets_to_account` transfers all note assets into the consuming account's vault.
+ If the hash matches, `wallet::move_note_assets_to_account` explicitly removes all assets from the note and transfers them into the consuming account's vault.
-### 5. Consuming the note
+### 4. Consuming the note
With the note created, Bob can now consume it—but only if he provides the correct secret. When Bob initiates the transaction to consume the note, he must supply the same secret number used when Alice created the note. The custom note’s logic will hash the secret and compare it with its stored hash. If they match, Bob’s wallet receives the asset.
---
+## Set up the Rust project
+
+Start in the directory containing your `tutorials` clone and create a sibling Cargo project:
+
+```bash
+cargo new miden-custom-note
+cd miden-custom-note
+rustup override set 1.98.1
+cp ../tutorials/rust-client/Cargo.lock Cargo.lock
+```
+
+Keep the generated `[package]` section in `Cargo.toml`, replace its empty `[dependencies]` section with the following, and add the development profile. The path assumes the repository clone is named `tutorials`.
+
+```toml
+[dependencies]
+# Clone tutorials next to this Cargo project (see Rust client setup).
+rust-client = { path = "../tutorials/rust-client" }
+miden-client = { version = "=0.16.0", features = ["testing", "tonic"] }
+miden-client-sqlite-store = { version = "=0.16.0", package = "miden-client-sqlite-store" }
+miden-protocol = { version = "=0.16.0" }
+rand = { version = "0.10" }
+tokio = { version = "1.48", features = ["rt-multi-thread", "net", "macros", "fs"] }
+
+[profile.dev]
+opt-level = 2
+```
+
+Copy the complete Rust example below into `src/main.rs`. Run it from this new project's directory with `TUTORIAL_NETWORK=testnet cargo run --release`. The client creates `store.sqlite3` and `keystore/` here; keep both out of version control.
+
## Full Rust code example
The following Rust code demonstrates how to implement the steps outlined above using the Miden client library:
```rust no_run
-use rand::RngCore;
+use rand::Rng;
+use rust_client::TutorialClientExt;
use std::{path::PathBuf, sync::Arc};
-use tokio::time::{sleep, Duration};
use miden_client::{
+ Client, ClientError, Felt,
account::{
+ Account, AccountBuilder, AccountType,
component::{
- BasicWallet, BurnPolicyConfig, FungibleFaucet, MintPolicyConfig, PolicyRegistration,
- TokenName, TokenPolicyManager,
+ create_singlesig_user_fungible_faucet, BasicWallet, BurnPolicy, FungibleFaucet,
+ MintPolicy, TokenName, TokenPolicyManager,
},
- Account, AccountBuilder, AccountType,
},
- address::NetworkId,
- asset::{AssetAmount, FungibleAsset, TokenSymbol},
- auth::{AuthSchemeId, AuthSecretKey, AuthSingleSig},
+ asset::{AssetAmount, AssetId, FungibleAsset, TokenSymbol},
+ auth::{AuthSecretKey, AuthSingleSig},
builder::ClientBuilder,
crypto::FeltRng,
keystore::{FilesystemKeyStore, Keystore},
note::{Note, NoteAssets, NoteRecipient, NoteStorage, NoteTag, NoteType, PartialNoteMetadata},
- rpc::{Endpoint, GrpcClient},
- store::TransactionFilter,
- transaction::{TransactionId, TransactionRequestBuilder, TransactionStatus},
- Client, ClientError, Felt,
+ rpc::{GrpcClient, VerifyingRpcClient},
+ transaction::{TransactionId, TransactionRequestBuilder},
};
use miden_client_sqlite_store::ClientBuilderSqliteExt;
use miden_protocol::Hasher;
+use rust_client::{FeeConfig, TutorialNetwork, fund_account_for_fees};
// Helper to create a basic account
async fn create_basic_account(
@@ -164,7 +204,7 @@ async fn create_basic_account(
let account = AccountBuilder::new(init_seed)
.account_type(AccountType::Public)
- .with_auth_component(AuthSingleSig::new(key_pair.public_key().to_commitment(), AuthSchemeId::Falcon512Poseidon2))
+ .with_component(AuthSingleSig::from_public_key(key_pair.public_key()))
.with_component(BasicWallet)
.build()
.unwrap();
@@ -187,27 +227,25 @@ async fn create_basic_faucet(
let decimals = 8;
let max_supply = AssetAmount::new(1_000_000).unwrap();
- let account = AccountBuilder::new(init_seed)
- .account_type(AccountType::Public)
- .with_auth_component(AuthSingleSig::new(key_pair.public_key().to_commitment(), AuthSchemeId::Falcon512Poseidon2))
- .with_component(
- FungibleFaucet::builder()
- .name(TokenName::new("MID").unwrap())
- .symbol(symbol)
- .decimals(decimals)
- .max_supply(max_supply)
- .build()
- .unwrap(),
- )
- .with_components(
- TokenPolicyManager::new()
- .with_mint_policy(MintPolicyConfig::AllowAll, PolicyRegistration::Active)
- .unwrap()
- .with_burn_policy(BurnPolicyConfig::AllowAll, PolicyRegistration::Active)
- .unwrap(),
- )
+ let faucet = FungibleFaucet::builder()
+ .name(TokenName::new("MID").unwrap())
+ .symbol(symbol)
+ .decimals(decimals)
+ .max_supply(max_supply)
.build()
.unwrap();
+ let policies = TokenPolicyManager::builder()
+ .active_mint_policy(MintPolicy::allow_all())
+ .active_burn_policy(BurnPolicy::allow_all())
+ .build();
+ let account = create_singlesig_user_fungible_faucet(
+ init_seed,
+ faucet,
+ AuthSingleSig::from_public_key(key_pair.public_key()),
+ policies,
+ AccountType::Public,
+ )
+ .unwrap();
client.add_account(&account, false).await?;
keystore.add_key(&key_pair, account.id()).await.unwrap();
@@ -220,39 +258,18 @@ async fn wait_for_tx(
client: &mut Client,
tx_id: TransactionId,
) -> Result<(), ClientError> {
- loop {
- client.sync_state().await?;
-
- // Check transaction status
- let txs = client
- .get_transactions(TransactionFilter::Ids(vec![tx_id]))
- .await?;
- let tx_committed = if !txs.is_empty() {
- matches!(txs[0].status, TransactionStatus::Committed { .. })
- } else {
- false
- };
-
- if tx_committed {
- println!("✅ transaction {} committed", tx_id.to_hex());
- break;
- }
-
- println!(
- "Transaction {} not yet committed. Waiting...",
- tx_id.to_hex()
- );
- sleep(Duration::from_secs(2)).await;
- }
- Ok(())
+ rust_client::wait_for_transaction(client, tx_id).await
}
#[tokio::main]
async fn main() -> Result<(), ClientError> {
// Initialize client
- let endpoint = Endpoint::testnet();
+ let network = TutorialNetwork::from_env()?;
+ let endpoint = network.endpoint();
let timeout_ms = 10_000;
- let rpc_client = Arc::new(GrpcClient::new(&endpoint, timeout_ms));
+ let rpc_client = Arc::new(VerifyingRpcClient::new(GrpcClient::new(
+ &endpoint, timeout_ms,
+ )));
// Initialize keystore
let keystore_path = PathBuf::from("./keystore");
@@ -264,12 +281,12 @@ async fn main() -> Result<(), ClientError> {
.rpc(rpc_client)
.sqlite_store(store_path)
.authenticator(keystore.clone())
- .in_debug_mode(true.into())
.build()
.await?;
let sync_summary = client.sync_state().await.unwrap();
println!("Latest block: {}", sync_summary.block_num);
+ let fee_config = FeeConfig::from_client(&client, network).await?;
// -------------------------------------------------------------------------
// STEP 1: Create accounts and deploy faucet
@@ -278,20 +295,23 @@ async fn main() -> Result<(), ClientError> {
let alice_account = create_basic_account(&mut client, &keystore).await?;
println!(
"Alice's account ID: {:?}",
- alice_account.id().to_bech32(NetworkId::Testnet)
+ alice_account.id().to_bech32(network.network_id())
);
let bob_account = create_basic_account(&mut client, &keystore).await?;
println!(
"Bob's account ID: {:?}",
- bob_account.id().to_bech32(NetworkId::Testnet)
+ bob_account.id().to_bech32(network.network_id())
);
println!("\nDeploying a new fungible faucet.");
let faucet = create_basic_faucet(&mut client, &keystore).await?;
println!(
"Faucet account ID: {:?}",
- faucet.id().to_bech32(NetworkId::Testnet)
+ faucet.id().to_bech32(network.network_id())
);
+ for account_id in [alice_account.id(), bob_account.id(), faucet.id()] {
+ fund_account_for_fees(&mut client, account_id, &fee_config).await?;
+ }
client.sync_state().await?;
// -------------------------------------------------------------------------
@@ -311,7 +331,7 @@ async fn main() -> Result<(), ClientError> {
.unwrap();
let tx_id = client
- .submit_new_transaction(faucet.id(), tx_request)
+ .submit_tutorial_transaction(faucet.id(), tx_request)
.await?;
println!("Minted tokens. TX: {:?}", tx_id);
@@ -321,17 +341,15 @@ async fn main() -> Result<(), ClientError> {
// Consume the minted note
let consumable_notes = client
- .get_consumable_notes(Some(alice_account.id()))
+ .get_consumable_tutorial_notes(Some(alice_account.id()))
.await?;
if let Some((note_record, _)) = consumable_notes.first() {
let note: Note = note_record.clone().try_into()?;
- let consume_request = TransactionRequestBuilder::new()
- .build_consume_notes(vec![note])
- .unwrap();
+ let consume_request = TransactionRequestBuilder::new().build_consume_notes(vec![note])?;
let tx_id = client
- .submit_new_transaction(alice_account.id(), consume_request)
+ .submit_tutorial_transaction(alice_account.id(), consume_request)
.await?;
println!("Consumed minted note. TX: {:?}", tx_id);
}
@@ -342,16 +360,20 @@ async fn main() -> Result<(), ClientError> {
// STEP 3: Create custom note
// -------------------------------------------------------------------------
println!("\n[STEP 3] Create custom note");
- let secret_vals = vec![Felt::new_unchecked(1), Felt::new_unchecked(2), Felt::new_unchecked(3), Felt::new_unchecked(4)];
+ let secret_vals = vec![
+ Felt::new_unchecked(1),
+ Felt::new_unchecked(2),
+ Felt::new_unchecked(3),
+ Felt::new_unchecked(4),
+ ];
let digest = Hasher::hash_elements(&secret_vals);
println!("digest: {:?}", digest);
- // `include_str!` resolves at compile time relative to this source file,
- // so the binary is independent of the working directory it is run from.
- let code = include_str!("../masm/notes/hash_preimage_note.masm");
+ // Read the MASM source from the tutorials repository.
+ let code = std::fs::read_to_string("../tutorials/masm/notes/hash_preimage_note.masm").unwrap();
let serial_num = client.rng().draw_word();
- let note_script = client.code_builder().compile_note_script(code).unwrap();
+ let note_script = client.code_builder().compile_note_script(&code).unwrap();
let note_storage = NoteStorage::new(digest.to_vec()).unwrap();
let recipient = NoteRecipient::new(serial_num, note_script, note_storage);
let tag = NoteTag::new(0);
@@ -366,10 +388,11 @@ async fn main() -> Result<(), ClientError> {
.unwrap();
let tx_id = client
- .submit_new_transaction(alice_account.id(), note_request)
+ .submit_tutorial_transaction(alice_account.id(), note_request)
.await?;
println!(
- "View transaction on MidenScan: https://testnet.midenscan.com/tx/{:?}",
+ "View transaction on MidenScan: {}/tx/{:?}",
+ network.explorer_url(),
tx_id
);
@@ -380,30 +403,48 @@ async fn main() -> Result<(), ClientError> {
// -------------------------------------------------------------------------
println!("\n[STEP 4] Bob consumes the Custom Note with Correct Secret");
- let secret = [Felt::new_unchecked(1), Felt::new_unchecked(2), Felt::new_unchecked(3), Felt::new_unchecked(4)];
+ let secret = [
+ Felt::new_unchecked(1),
+ Felt::new_unchecked(2),
+ Felt::new_unchecked(3),
+ Felt::new_unchecked(4),
+ ];
let consume_custom_request = TransactionRequestBuilder::new()
.input_notes([(custom_note, Some(secret.into()))])
.build()
.unwrap();
let tx_id = client
- .submit_new_transaction(bob_account.id(), consume_custom_request)
+ .submit_tutorial_transaction(bob_account.id(), consume_custom_request)
.await?;
println!(
- "Consumed Note Tx on MidenScan: https://testnet.midenscan.com/tx/{:?} \n",
+ "Consumed Note Tx on MidenScan: {}/tx/{:?} \n",
+ network.explorer_url(),
tx_id
);
wait_for_tx(&mut client, tx_id).await?;
+ let bob = client
+ .get_account(bob_account.id())
+ .await?
+ .expect("Bob's account must exist after consuming the note");
+ let balance = bob.vault().get_balance(AssetId::new_fungible(faucet_id))?;
+ assert_eq!(
+ balance.as_u64(),
+ amount,
+ "Bob must receive all assets from the hash-preimage note",
+ );
+ println!("Bob's custom-note token balance: {balance}");
+
Ok(())
}
```
-The output of our program will look something like this:
+The following is an abbreviated output; IDs vary, and funding and repeated confirmation messages are omitted:
```text
-Latest block: 488704
+Latest block:
[STEP 1] Creating new accounts
Alice's account ID: ""
@@ -413,21 +454,20 @@ Deploying a new fungible faucet.
Faucet account ID: ""
[STEP 2] Mint tokens with P2ID
-Minted tokens. TX: 0x970265408eb22068b22ec677f6ad09a2524913ab11b3dbf010e4cae73587e2e3
-Transaction 0x970265408eb22068b22ec677f6ad09a2524913ab11b3dbf010e4cae73587e2e3 not yet committed. Waiting...
-✅ transaction 0x970265408eb22068b22ec677f6ad09a2524913ab11b3dbf010e4cae73587e2e3 committed
-Consumed minted note. TX: 0x47850a0c44d9e147b8866285c24ba05a0d4bed0a99c1f25a13f32e57feecfe1b
+Minted tokens. TX:
+Transaction committed:
+Consumed minted note. TX:
[STEP 3] Create custom note
digest: Word([14206540680072267069, 9571949196318390099, 5950603493574130513, 3457190364553631046])
note hash: "0xf48f362f1817bbc5575e0bb8b77c496dd67e4b85d8ff45d21dff5743de2b174d"
-View transaction on MidenScan: https://testnet.midenscan.com/tx/0x9911ef1b9d2b066e017de187b7c1f8d95012748366358474b11adc91e49971b5
+View transaction on MidenScan: https://testnet.midenscan.com/tx/
[STEP 4] Bob consumes the Custom Note with Correct Secret
-Consumed Note Tx on MidenScan: https://testnet.midenscan.com/tx/0x2a7a192b692984ae649dd1a13d15e4b79178e9e554615ffec7426e25e75193fa
+Consumed Note Tx on MidenScan: https://testnet.midenscan.com/tx/
-Transaction 0x2a7a192b692984ae649dd1a13d15e4b79178e9e554615ffec7426e25e75193fa not yet committed. Waiting...
-✅ transaction 0x2a7a192b692984ae649dd1a13d15e4b79178e9e554615ffec7426e25e75193fa committed
+Transaction committed:
+Bob's custom-note token balance: 100
```
## Conclusion
@@ -439,15 +479,15 @@ You have now seen how to create a custom note on Miden that requires a secret pr
3. Building a note with custom logic in Miden Assembly
4. Consuming the note by providing the correct secret
-By leveraging Miden’s privacy features, you can create customized logic for secure asset transfers that depend on keeping parts of the transaction private.
+The fixed secret `[1, 2, 3, 4]` is for demonstration. A real secret must be unpredictable and shared only with the intended consumer. Anyone who learns it can satisfy this note's spending condition.
### Running the example
-To run the custom note example, navigate to the `rust-client` directory in the [miden-tutorials](https://github.com/0xMiden/miden-tutorials/) repository and run this command:
+From the root of your `tutorials` clone, run the checked-in example:
```bash
cd rust-client
-cargo run --release --bin hash_preimage_note
+TUTORIAL_NETWORK=testnet cargo run --release --bin hash_preimage_note
```
### Continue learning
diff --git a/docs/src/rust-client/delegated_proving_tutorial.md b/docs/src/rust-client/delegated_proving_tutorial.md
index 58d070ae..f7d8c62e 100644
--- a/docs/src/rust-client/delegated_proving_tutorial.md
+++ b/docs/src/rust-client/delegated_proving_tutorial.md
@@ -7,9 +7,11 @@ sidebar_position: 12
_Using delegated proving to minimize transaction proving times on computationally constrained devices_
+For toolchain requirements and shared fee helpers, see the [Rust client setup](./index.md#running-the-v016-examples).
+
## Overview
-In this tutorial we will cover how to use delegated proving with the Miden Rust client to minimize the time it takes to generate a valid transaction proof. In the code below, we will create an account, mint tokens from a faucet, then send the tokens to another account using delegated proving.
+In this tutorial we will cover how to use delegated proving with the Miden Rust client to minimize the time it takes to generate a valid transaction proof. We create and fund an account, execute a minimal transaction locally, prove it with the network's remote prover, and verify that its confirmed nonce increases by one. Even this minimal transaction pays a verification fee on testnet.
## Prerequisites
@@ -26,9 +28,9 @@ Before diving into our code example, let's clarify what "delegated proving" mean
Delegated proving is the process of outsourcing the ZK proof generation of your transaction to a third party. For certain computationally constrained devices such as mobile phones and web browser environments, generating ZK proofs might take too long to ensure an acceptable user experience. Devices that do not have the computational resources to generate Miden proofs in under 1-2 seconds can use delegated proving to provide a more responsive user experience.
-_How does it work?_ When a user choses to use delegated proving, they send off their locally executed transaction to a dedicated server. This dedicated server generates the ZK proof for the executed transaction and sends the proof back to the user. Proving a transaction with delegated proving is trustless, meaning if the delegated prover is malicious, they could not compromise the security of the account that is submitting a transaction to be processed by the delegated prover.
+_How does it work?_ When a user chooses to use delegated proving, they send off their locally executed transaction to a dedicated server. This dedicated server generates the ZK proof for the executed transaction and sends the proof back to the user. The transaction proof is verified under the same rules as a locally generated proof: the delegated prover cannot make an invalid state transition valid. This protects transaction integrity, but does not keep the witness private from the prover.
-The only downside of using delegated proving is that it reduces the privacy of the account that uses delegated proving, because the delegated prover would have knowledge of the inputs to the transaction that is being proven. For example, it would not be advisable to use delegated proving in the case of our "How to Create a Custom Note" tutorial, since the note we create requires knowledge of a hash preimage to redeem the assets in the note. Using delegated proving would reveal the hash preimage to the server running the delegated proving service.
+Delegated proving reveals the transaction witness to the prover and depends on that service being available. The witness can include private account state and note arguments. For example, it would not be advisable to use delegated proving in the case of our "How to Create a Custom Note" tutorial, since the note we create requires knowledge of a hash preimage to redeem the assets in the note. Using delegated proving would reveal the hash preimage to the server running the delegated proving service.
Anyone can run their own delegated prover server. If you are building a product on Miden, it may make sense to run your own delegated prover server for your users. To run your own delegated proving server, follow the instructions here: https://crates.io/crates/miden-remote-prover.
@@ -38,52 +40,61 @@ prover instead, point `RemoteTransactionProver` at its URL.
## Step 1: Initialize your repository
-Create a new Rust repository for your Miden project and navigate to it with the following command:
+Start in the directory containing your `tutorials` clone and create a sibling Cargo project. The dependency path below assumes the clone is named `tutorials`.
```bash
cargo new miden-delegated-proving-app
cd miden-delegated-proving-app
+rustup override set 1.98.1
+cp ../tutorials/rust-client/Cargo.lock Cargo.lock
```
-Add the following dependencies to your `Cargo.toml` file:
+Keep the generated `[package]` section in `Cargo.toml`, replace its empty `[dependencies]` section with the following, and add the development profile:
```toml
[dependencies]
-miden-client = { version = "0.15", features = ["testing", "tonic"] }
-miden-client-sqlite-store = { version = "0.15", package = "miden-client-sqlite-store" }
-miden-protocol = { version = "0.15" }
-rand = { version = "0.9" }
-tokio = { version = "1.46", features = ["rt-multi-thread", "net", "macros", "fs"] }
+# Clone tutorials next to this Cargo project (see Rust client setup).
+rust-client = { path = "../tutorials/rust-client" }
+miden-client = { version = "=0.16.0", features = ["testing", "tonic"] }
+miden-client-sqlite-store = { version = "=0.16.0", package = "miden-client-sqlite-store" }
+miden-protocol = { version = "=0.16.0" }
+rand = { version = "0.10" }
+tokio = { version = "1.48", features = ["rt-multi-thread", "net", "macros", "fs"] }
+
+[profile.dev]
+opt-level = 2
```
## Step 2: Initialize the client and prover and construct transactions
Similarly to previous tutorials, we must instantiate the client.
-We construct a `RemoteTransactionProver` pointed at the public Miden testnet delegated prover for this walkthrough.
+We construct a `RemoteTransactionProver` pointed at the public Miden testnet delegated prover for this walkthrough. Copy this complete example into `src/main.rs`. The client creates `store.sqlite3` and `keystore/` in the project directory; keep both out of version control.
```rust no_run
-use miden_client::auth::AuthSecretKey;
-use miden_client::auth::{AuthSchemeId, AuthSingleSig};
-use rand::RngCore;
+use rand::Rng;
use std::{path::PathBuf, sync::Arc};
use miden_client::{
- account::component::BasicWallet,
+ ClientError, RemoteTransactionProver,
+ account::{AccountBuilder, AccountType, component::BasicWallet},
+ auth::{AuthSecretKey, AuthSingleSig},
builder::ClientBuilder,
keystore::{FilesystemKeyStore, Keystore},
- rpc::{Endpoint, GrpcClient},
+ rpc::{GrpcClient, VerifyingRpcClient},
transaction::{TransactionProver, TransactionRequestBuilder},
- ClientError, RemoteTransactionProver,
};
use miden_client_sqlite_store::ClientBuilderSqliteExt;
-use miden_client::account::{AccountBuilder, AccountType};
+use rust_client::{FeeConfig, TutorialNetwork, fund_account_for_fees};
#[tokio::main]
async fn main() -> Result<(), ClientError> {
// Initialize client
- let endpoint = Endpoint::testnet();
+ let network = TutorialNetwork::from_env()?;
+ let endpoint = network.endpoint();
let timeout_ms = 10_000;
- let rpc_client = Arc::new(GrpcClient::new(&endpoint, timeout_ms));
+ let rpc_client = Arc::new(VerifyingRpcClient::new(GrpcClient::new(
+ &endpoint, timeout_ms,
+ )));
// Initialize keystore
let keystore_path = PathBuf::from("./keystore");
@@ -95,12 +106,12 @@ async fn main() -> Result<(), ClientError> {
.rpc(rpc_client)
.sqlite_store(store_path)
.authenticator(keystore.clone())
- .in_debug_mode(true.into())
.build()
.await?;
let sync_summary = client.sync_state().await.unwrap();
println!("Latest block: {}", sync_summary.block_num);
+ let fee_config = FeeConfig::from_client(&client, network).await?;
// Create Alice's account
let mut init_seed = [0_u8; 32];
@@ -110,29 +121,37 @@ async fn main() -> Result<(), ClientError> {
let alice_account = AccountBuilder::new(init_seed)
.account_type(AccountType::Private)
- .with_auth_component(AuthSingleSig::new(key_pair.public_key().to_commitment(), AuthSchemeId::Falcon512Poseidon2))
+ .with_component(AuthSingleSig::from_public_key(key_pair.public_key()))
.with_component(BasicWallet)
.build()
.unwrap();
client.add_account(&alice_account, false).await?;
- keystore.add_key(&key_pair, alice_account.id()).await.unwrap();
+ keystore
+ .add_key(&key_pair, alice_account.id())
+ .await
+ .unwrap();
+ fund_account_for_fees(&mut client, alice_account.id(), &fee_config).await?;
// -------------------------------------------------------------------------
// Set up the delegated (remote) tx prover
// -------------------------------------------------------------------------
// Delegated proving outsources ZK proof generation to a remote service. This is
- // the public Miden testnet prover; run your own
+ // the public prover for the selected network; run your own
// (https://crates.io/crates/miden-remote-prover) and swap the URL to use it.
- // The constant `miden_client::grpc_support::TESTNET_PROVER_ENDPOINT` holds this
- // same URL.
- let remote_tx_prover = RemoteTransactionProver::new("https://tx-prover.testnet.miden.io");
+ // The upstream constant keeps this URL synchronized with the selected network.
+ let remote_tx_prover = RemoteTransactionProver::new(network.remote_prover_url());
let tx_prover: Arc = Arc::new(remote_tx_prover);
// We use a dummy transaction request to showcase delegated proving.
- // The only effect of this tx should be increasing Alice's nonce.
- println!("Alice nonce initial: {:?}", alice_account.nonce());
- let script_code = "begin push.1 drop end";
+ // In addition to paying the network fee, this transaction increments Alice's nonce.
+ let initial_nonce = client
+ .get_account(alice_account.id())
+ .await?
+ .expect("Alice exists")
+ .nonce();
+ println!("Alice nonce initial: {:?}", initial_nonce);
+ let script_code = "@transaction_script pub proc main push.1 drop end";
let tx_script = client
.code_builder()
.compile_tx_script(script_code)
@@ -145,6 +164,7 @@ async fn main() -> Result<(), ClientError> {
// Step 1: Execute the transaction locally
println!("Executing transaction...");
+ client.sync_state().await?;
let tx_result = client
.execute_transaction(alice_account.id(), transaction_request)
.await?;
@@ -163,6 +183,7 @@ async fn main() -> Result<(), ClientError> {
client
.apply_transaction(&tx_result, submission_height)
.await?;
+ rust_client::wait_for_transaction(&mut client, tx_result.id()).await?;
println!("Transaction submitted successfully using the delegated prover!");
@@ -175,6 +196,7 @@ async fn main() -> Result<(), ClientError> {
.expect("alice account not found");
println!("Alice nonce has increased: {:?}", account.nonce());
+ assert_eq!(account.nonce(), initial_nonce + miden_client::Felt::ONE);
Ok(())
}
@@ -183,28 +205,29 @@ async fn main() -> Result<(), ClientError> {
Now let's run the `src/main.rs` program:
```bash
-cargo run --release
+TUTORIAL_NETWORK=testnet cargo run --release
```
-The output will look like this:
+The following is an abbreviated output. The funding transaction has already increased Alice's nonce from 0 to 1; the delegated transaction then increases it to 2:
```text
-Latest block: 488706
-Alice nonce initial: 0
+Latest block:
+Alice nonce initial: 1
Executing transaction...
Proving transaction with the delegated prover...
Submitting proven transaction...
+Transaction committed:
Transaction submitted successfully using the delegated prover!
-Alice nonce has increased: 1
+Alice nonce has increased: 2
```
### Running the example
-To run a full working example navigate to the `rust-client` directory in the [miden-tutorials](https://github.com/0xMiden/miden-tutorials/) repository and run this command:
+From the root of your `tutorials` clone, run the checked-in example:
```bash
cd rust-client
-cargo run --release --bin delegated_prover
+TUTORIAL_NETWORK=testnet cargo run --release --bin delegated_prover
```
### Continue learning
diff --git a/docs/src/rust-client/foreign_procedure_invocation_tutorial.md b/docs/src/rust-client/foreign_procedure_invocation_tutorial.md
index eb178162..b18bb126 100644
--- a/docs/src/rust-client/foreign_procedure_invocation_tutorial.md
+++ b/docs/src/rust-client/foreign_procedure_invocation_tutorial.md
@@ -7,6 +7,8 @@ sidebar_position: 7
_Using foreign procedure invocation to craft read-only cross-contract calls in the Miden VM_
+For toolchain requirements and shared fee helpers, see the [Rust client setup](./index.md#running-the-v016-examples).
+
## Overview
In previous tutorials we deployed a public counter contract and incremented the count from a different client instance.
@@ -32,43 +34,91 @@ The diagram above depicts the "count copy" smart contract using foreign procedur
## Prerequisites
-This tutorial assumes you have a basic understanding of Miden assembly and completed the previous tutorial on deploying the counter contract. We will be working within the same `miden-counter-contract` repository that we created in the [Interacting with Public Smart Contracts](./public_account_interaction_tutorial.md) tutorial.
+This tutorial assumes you have a basic understanding of Miden assembly and a counter deployed using the [counter contract tutorial](./counter_contract_tutorial.md). Keep its printed `mtst1...` account ID. The reader runs in a separate Cargo project and reads that counter's public state.
## Step 1: Set up your repository
-We will be using the same repository used in the "Interacting with Public Smart Contracts" tutorial. To set up your repository for this tutorial, first follow up until step two [here](./public_account_interaction_tutorial.md).
+From the parent directory of your `tutorials` clone, create a sibling Cargo project:
+
+```bash
+cargo new miden-fpi
+cd miden-fpi
+rustup override set 1.98.1
+cp ../tutorials/rust-client/Cargo.lock Cargo.lock
+```
+
+Add these dependencies and the development profile to your `Cargo.toml`:
+
+```toml
+[dependencies]
+rust-client = { path = "../tutorials/rust-client" }
+miden-client = { version = "=0.16.0", features = ["testing", "tonic"] }
+miden-client-sqlite-store = { version = "=0.16.0", package = "miden-client-sqlite-store" }
+miden-protocol = { version = "=0.16.0" }
+rand = { version = "0.10" }
+tokio = { version = "1.48", features = ["rt-multi-thread", "net", "macros", "fs"] }
+
+[profile.dev]
+opt-level = 2
+```
## Step 2: Set up the "count reader" contract
-Inside of the `masm/accounts/` directory, create the `count_reader.masm` file. This is the smart contract that will read the "count" value from the counter contract.
+The reader contract in `masm/accounts/count_reader.masm` reads the counter’s value through FPI.
`masm/accounts/count_reader.masm`:
```masm
-use miden::protocol::active_account
use miden::protocol::native_account
use miden::protocol::tx
-use miden::core::word
use miden::core::sys
+use {AccountId, AccountProcedureRoot} from miden::protocol::types
+
+# CONSTANTS
+# =================================================================================================
const COUNT_READER_SLOT = word("miden::tutorials::count_reader")
-# => [account_id_suffix, account_id_prefix, PROC_HASH(4), foreign_procedure_inputs(16)]
-pub proc copy_count
+# PUBLIC INTERFACE
+# =================================================================================================
+
+#! Copies the count returned by the foreign counter into this account's storage.
+#!
+#! Inputs: [foreign_account_id_{suffix,prefix}, FOREIGN_PROC_ROOT, pad(10)]
+#! Outputs: [pad(16)]
+#!
+#! Where:
+#! - foreign_account_id_{suffix,prefix} identifies the public counter account.
+#! - FOREIGN_PROC_ROOT is the root of its get_count procedure.
+#!
+#! Invocation: call
+@account_procedure
+@locals(6)
+pub proc copy_count(foreign_account_id: AccountId, foreign_proc_root: AccountProcedureRoot)
+ # save the foreign target while preparing its sixteen zero inputs
+ loc_store.4 loc_store.5 loc_storew_le.0 dropw
+ # => [pad(16)]
+
+ padw padw padw padw
+ # => [foreign_procedure_inputs(16), pad(16)]
+
+ padw loc_loadw_le.0 loc_load.5 loc_load.4
+ # => [foreign_account_id_suffix, foreign_account_id_prefix, FOREIGN_PROC_ROOT, foreign_procedure_inputs(16), pad(16)]
+
exec.tx::execute_foreign_procedure
- # => [count, pad(12)]
+ # => [[count, 0, 0, 0], pad(28)]
push.COUNT_READER_SLOT[0..2]
- # [slot_id_prefix, slot_id_suffix, count, pad(12)]
+ # => [slot_id_suffix, slot_id_prefix, [count, 0, 0, 0], pad(28)]
exec.native_account::set_item
- # => [OLD_VALUE, pad(12)]
+ # => [OLD_VALUE, pad(28)]
- dropw dropw dropw dropw
- # => []
+ dropw
+ # => [pad(28)]
exec.sys::truncate_stack
- # => []
+ # => [pad(16)]
end
```
@@ -84,22 +134,33 @@ This is what the stack state should look like before we call `tx::execute_foreig
`execute_foreign_procedure` always requires exactly 16 `foreign_procedure_inputs` on the stack
below the procedure hash and account ID. Since `get_count` takes no arguments, we pass 16 zero
-words (`padw padw padw padw`) as the inputs. After the call, the procedure returns 16 output
-elements; the count word sits at the top and we clean up the rest with `dropw dropw dropw dropw`.
+felts (`padw padw padw padw`, four words) as the inputs. The reader prepares these
+inputs internally after saving the account ID and procedure root in local memory. The
+caller therefore passes only those six identifying felts. After the foreign call,
+the count is the first of 16 output elements; the reader stores its word, discards
+the previous storage value, and truncates the remaining padding.
After calling the `get_count` procedure in the counter contract, we save the count into the
`miden::tutorials::count_reader` storage slot.
-**Note**: _The bracket symbols used in the count copy contract are not valid MASM syntax. These are simply placeholder elements that we will replace with the actual values before compilation._
-
-Inside the `masm/scripts/` directory, create the `reader_script.masm` file:
+The transaction script is defined in `masm/scripts/reader_script.masm`:
```masm
use external_contract::count_reader_contract
use miden::core::sys
-begin
- padw padw padw padw
+#! Copies a public counter through the reader account.
+#!
+#! Inputs: [ARGS, pad(12)]
+#! Outputs: [pad(16)]
+#!
+#! Where:
+#! - ARGS contains unused transaction script arguments.
+#!
+#! Invocation: dyncall
+@transaction_script
+pub proc main(args: word)
+ dropw
# => [pad(16)]
push.{get_count_proc_hash}
@@ -112,43 +173,49 @@ begin
# => [account_id_suffix, account_id_prefix, GET_COUNT_HASH, pad(16)]
call.count_reader_contract::copy_count
- # => []
+ # => [pad(22)]
exec.sys::truncate_stack
- # => []
+ # => [pad(16)]
end
```
-**Note**: _`push.{get_count_proc_hash}` is not valid MASM, we will format the string with the value get_count_proc_hash before passing this script code to the assembler._
+The braces mark template values, not valid MASM operands. The Rust code replaces the procedure root and both account ID elements before assembling the script.
-### Step 3: Set up your `src/main.rs` file:
+## Step 3: Set up your `src/main.rs` file
```rust no_run
-use rand::RngCore;
+use rand::Rng;
+use rust_client::TutorialClientExt;
use std::{path::PathBuf, sync::Arc, time::Duration};
use tokio::time::sleep;
use miden_client::{
+ ClientError, Word,
account::{
- component::AccountComponentMetadata, AccountBuilder, AccountComponent, AccountId,
- AccountType, StorageSlot, StorageSlotName,
+ AccountBuilder, AccountComponent, AccountId, AccountType, StorageSlot, StorageSlotName,
+ component::{AccountComponentMetadata, BasicWallet},
},
auth::NoAuth,
builder::ClientBuilder,
keystore::FilesystemKeyStore,
- rpc::{domain::account::AccountStorageRequirements, Endpoint, GrpcClient},
+ rpc::{GrpcClient, VerifyingRpcClient, domain::account::AccountStorageRequirements},
transaction::{ForeignAccount, TransactionRequestBuilder},
- ClientError, Word,
};
use miden_client_sqlite_store::ClientBuilderSqliteExt;
+use rust_client::{FeeConfig, TutorialNetwork, fund_account_for_fees};
#[tokio::main]
async fn main() -> Result<(), ClientError> {
// Initialize client
- let endpoint = Endpoint::testnet();
+ let network = TutorialNetwork::from_env()?;
+ let endpoint = network.endpoint();
let timeout_ms = 10_000;
- let rpc_client = Arc::new(GrpcClient::new(&endpoint, timeout_ms));
+ let rpc_client = Arc::new(VerifyingRpcClient::new(GrpcClient::new(
+ &endpoint, timeout_ms,
+ )));
+ // Initialize keystore
let keystore_path = PathBuf::from("./keystore");
let keystore = Arc::new(FilesystemKeyStore::new(keystore_path).unwrap());
@@ -158,27 +225,30 @@ async fn main() -> Result<(), ClientError> {
.rpc(rpc_client)
.sqlite_store(store_path)
.authenticator(keystore.clone())
- .in_debug_mode(true.into())
.build()
.await?;
let sync_summary = client.sync_state().await.unwrap();
println!("Latest block: {}", sync_summary.block_num);
+ let fee_config = FeeConfig::from_client(&client, network).await?;
// -------------------------------------------------------------------------
// STEP 1: Create the Count Reader Contract
// -------------------------------------------------------------------------
println!("\n[STEP 1] Creating count reader contract.");
- // `include_str!` resolves at compile time relative to this source file,
- // so the binary is independent of the working directory it is run from.
- let count_reader_code = include_str!("../masm/accounts/count_reader.masm");
+ // Read the MASM source from the tutorials repository.
+ let count_reader_code =
+ std::fs::read_to_string("../tutorials/masm/accounts/count_reader.masm").unwrap();
let count_reader_slot_name =
StorageSlotName::new("miden::tutorials::count_reader").expect("valid slot name");
let count_reader_component_code = client
.code_builder()
- .compile_component_code("external_contract::count_reader_contract", count_reader_code)
+ .compile_component_code(
+ "external_contract::count_reader_contract",
+ &count_reader_code,
+ )
.unwrap();
let count_reader_component = AccountComponent::new(
count_reader_component_code,
@@ -196,7 +266,8 @@ async fn main() -> Result<(), ClientError> {
let count_reader_contract = AccountBuilder::new(init_seed)
.account_type(AccountType::Public)
.with_component(count_reader_component.clone())
- .with_auth_component(NoAuth)
+ .with_component(BasicWallet)
+ .with_component(NoAuth)
.build()
.unwrap();
@@ -204,12 +275,13 @@ async fn main() -> Result<(), ClientError> {
"count_reader hash: {:?}",
count_reader_contract.to_commitment()
);
- println!("contract id: {:?}", count_reader_contract.id());
+ println!("count_reader id: {:?}", count_reader_contract.id());
client
.add_account(&count_reader_contract, false)
.await
.unwrap();
+ fund_account_for_fees(&mut client, count_reader_contract.id(), &fee_config).await?;
Ok(())
}
@@ -218,22 +290,24 @@ async fn main() -> Result<(), ClientError> {
Run the following command to execute src/main.rs:
```bash
-cargo run --release
+TUTORIAL_NETWORK=testnet cargo run --release
```
-The output of our program will look something like this:
+The output includes the reader's initial commitment and ID (abridged; values vary):
```text
-Latest block: 226976
+Latest block:
[STEP 1] Creating count reader contract.
-count_reader hash: RpoDigest([15888177100833057221, 15548657445961063290, 5580812380698193124, 9604096693288041818])
-contract id: ""
+count_reader hash: Word([...])
+count_reader id: V1(AccountIdV1 { suffix: ..., prefix: ... })
```
## Step 4: Import the pre-deployed counter contract
-The FPI call needs a counter contract already deployed on-chain. We import the counter contract that was deployed in the [counter contract tutorial](./counter_contract_tutorial.md) by its testnet address:
+The FPI call needs a counter contract already deployed on-chain. Copy its `mtst1...` testnet account ID into the `MIDEN_COUNTER_ACCOUNT_ID` environment variable. Using an input avoids baking in an address that becomes invalid after a testnet reset.
+
+Insert this fragment inside `main`, immediately before its final `Ok(())`:
```rust ignore
// -------------------------------------------------------------------------
@@ -241,9 +315,19 @@ The FPI call needs a counter contract already deployed on-chain. We import the c
// -------------------------------------------------------------------------
println!("\n[STEP 2] Building counter contract from public state");
-// Define the Counter Contract account id from counter contract deploy
-let (_, counter_contract_id) =
- AccountId::from_bech32("mtst1apcqs7aj3a2cf5t6pnsfy0p4ns7wl7sp").unwrap();
+// Pass the account ID printed by `counter_contract_deploy` as the first argument, or via
+// `MIDEN_COUNTER_ACCOUNT_ID`.
+let counter_contract_bech32 = std::env::args()
+ .nth(1)
+ .or_else(|| std::env::var("MIDEN_COUNTER_ACCOUNT_ID").ok())
+ .expect("pass the counter account ID from counter_contract_deploy");
+let (account_network, counter_contract_id) =
+ AccountId::from_bech32(&counter_contract_bech32).expect("invalid counter account ID");
+assert_eq!(
+ account_network,
+ network.network_id(),
+ "counter account must match the selected tutorial network"
+);
println!("counter contract id: {:?}", counter_contract_id);
@@ -265,7 +349,7 @@ println!(
## Step 5: Call the counter contract via foreign procedure invocation
-Add this snippet to the end of your file in the `main()` function:
+Insert this fragment after the import step, inside `main` and before `Ok(())`:
```rust ignore
// -------------------------------------------------------------------------
@@ -273,14 +357,17 @@ Add this snippet to the end of your file in the `main()` function:
// -------------------------------------------------------------------------
println!("\n[STEP 3] Call counter contract with FPI from count reader contract");
-// Derive the get_count procedure hash from the locally compiled counter library.
-let counter_contract_code = include_str!("../masm/accounts/counter.masm");
+let counter_contract_code =
+ std::fs::read_to_string("../tutorials/masm/accounts/counter.masm").unwrap();
// Compile the counter as a component (same path as the deploy binary) to get
// the correct procedure root that matches the on-chain MAST.
let counter_component_code = client
.code_builder()
- .compile_component_code("external_contract::counter_contract", counter_contract_code)
+ .compile_component_code(
+ "external_contract::counter_contract",
+ &counter_contract_code,
+ )
.unwrap();
let counter_component = AccountComponent::new(
counter_component_code,
@@ -291,7 +378,6 @@ let counter_component = AccountComponent::new(
let get_count_root = counter_component
.component_code()
- .as_library()
.get_procedure_root_by_path("external_contract::counter_contract::get_count")
.expect("get_count export not found");
let get_count_hash = format!("{}", get_count_root);
@@ -300,7 +386,8 @@ println!("get_count hash: {:?}", get_count_hash);
println!("counter id prefix: {:?}", counter_contract_id.prefix());
println!("counter id suffix: {:?}", counter_contract_id.suffix());
-let script_code = include_str!("../masm/scripts/reader_script.masm")
+let script_code = std::fs::read_to_string("../tutorials/masm/scripts/reader_script.masm")
+ .unwrap()
.replace("{get_count_proc_hash}", &get_count_hash)
.replace(
"{account_id_suffix}",
@@ -315,7 +402,10 @@ let script_code = include_str!("../masm/scripts/reader_script.masm")
// that compiles the script.
let tx_script = client
.code_builder()
- .with_linked_module("external_contract::count_reader_contract", count_reader_code)
+ .with_linked_module(
+ "external_contract::count_reader_contract",
+ &count_reader_code,
+ )
.unwrap()
.compile_tx_script(script_code.as_str())
.unwrap();
@@ -330,12 +420,13 @@ let tx_request = TransactionRequestBuilder::new()
.unwrap();
let tx_id = client
- .submit_new_transaction(count_reader_contract.id(), tx_request)
+ .submit_tutorial_transaction(count_reader_contract.id(), tx_request)
.await
.unwrap();
println!(
- "View transaction on MidenScan: https://testnet.midenscan.com/tx/{:?}",
+ "View transaction on MidenScan: {}/tx/{:?}",
+ network.explorer_url(),
tx_id
);
@@ -365,42 +456,56 @@ println!(
"count reader contract storage: {:?}",
account_2.storage().get_item(&count_reader_slot_name)
);
+assert_eq!(
+ account_2
+ .storage()
+ .get_item(&count_reader_slot_name)
+ .unwrap(),
+ account_1.storage().get_item(&counter_slot_name).unwrap(),
+ "FPI must copy the current counter value",
+);
```
-The key here is the use of the `.foreign_accounts()` method on the `TransactionRequestBuilder`. Using this method, it is possible to create transactions with multiple foreign procedure calls.
+The `.foreign_accounts()` method declares the foreign state that the client must fetch and prove. `AccountStorageRequirements::default()` suffices here because `get_count` reads a value slot. A procedure that reads map entries must also request proofs for the specific map keys it uses. The MASM script performs the actual foreign call.
## Summary
-In this tutorial created a smart contract that calls the `get_count` procedure in the counter contract using foreign procedure invocation, and then saves the returned value to its local storage.
+In this tutorial, we created a smart contract that calls the counter's `get_count` procedure through FPI and saves the returned value in its own storage. The reader account pays for this transaction; the foreign counter is read-only and is not charged or modified.
The final `src/main.rs` file should look like this:
```rust no_run
-use rand::RngCore;
+use rand::Rng;
+use rust_client::TutorialClientExt;
use std::{path::PathBuf, sync::Arc, time::Duration};
use tokio::time::sleep;
use miden_client::{
+ ClientError, Word,
account::{
- component::AccountComponentMetadata, AccountBuilder, AccountComponent, AccountId,
- AccountType, StorageSlot, StorageSlotName,
+ AccountBuilder, AccountComponent, AccountId, AccountType, StorageSlot, StorageSlotName,
+ component::{AccountComponentMetadata, BasicWallet},
},
auth::NoAuth,
builder::ClientBuilder,
keystore::FilesystemKeyStore,
- rpc::{domain::account::AccountStorageRequirements, Endpoint, GrpcClient},
+ rpc::{GrpcClient, VerifyingRpcClient, domain::account::AccountStorageRequirements},
transaction::{ForeignAccount, TransactionRequestBuilder},
- ClientError, Word,
};
use miden_client_sqlite_store::ClientBuilderSqliteExt;
+use rust_client::{FeeConfig, TutorialNetwork, fund_account_for_fees};
#[tokio::main]
async fn main() -> Result<(), ClientError> {
// Initialize client
- let endpoint = Endpoint::testnet();
+ let network = TutorialNetwork::from_env()?;
+ let endpoint = network.endpoint();
let timeout_ms = 10_000;
- let rpc_client = Arc::new(GrpcClient::new(&endpoint, timeout_ms));
+ let rpc_client = Arc::new(VerifyingRpcClient::new(GrpcClient::new(
+ &endpoint, timeout_ms,
+ )));
+ // Initialize keystore
let keystore_path = PathBuf::from("./keystore");
let keystore = Arc::new(FilesystemKeyStore::new(keystore_path).unwrap());
@@ -410,21 +515,21 @@ async fn main() -> Result<(), ClientError> {
.rpc(rpc_client)
.sqlite_store(store_path)
.authenticator(keystore.clone())
- .in_debug_mode(true.into())
.build()
.await?;
let sync_summary = client.sync_state().await.unwrap();
println!("Latest block: {}", sync_summary.block_num);
+ let fee_config = FeeConfig::from_client(&client, network).await?;
// -------------------------------------------------------------------------
// STEP 1: Create the Count Reader Contract
// -------------------------------------------------------------------------
println!("\n[STEP 1] Creating count reader contract.");
- // `include_str!` resolves at compile time relative to this source file,
- // so the binary is independent of the working directory it is run from.
- let count_reader_code = include_str!("../masm/accounts/count_reader.masm");
+ // Read the MASM source from the tutorials repository.
+ let count_reader_code =
+ std::fs::read_to_string("../tutorials/masm/accounts/count_reader.masm").unwrap();
let count_reader_slot_name =
StorageSlotName::new("miden::tutorials::count_reader").expect("valid slot name");
@@ -432,7 +537,7 @@ async fn main() -> Result<(), ClientError> {
.code_builder()
.compile_component_code(
"external_contract::count_reader_contract",
- count_reader_code,
+ &count_reader_code,
)
.unwrap();
let count_reader_component = AccountComponent::new(
@@ -451,7 +556,8 @@ async fn main() -> Result<(), ClientError> {
let count_reader_contract = AccountBuilder::new(init_seed)
.account_type(AccountType::Public)
.with_component(count_reader_component.clone())
- .with_auth_component(NoAuth)
+ .with_component(BasicWallet)
+ .with_component(NoAuth)
.build()
.unwrap();
@@ -465,15 +571,26 @@ async fn main() -> Result<(), ClientError> {
.add_account(&count_reader_contract, false)
.await
.unwrap();
+ fund_account_for_fees(&mut client, count_reader_contract.id(), &fee_config).await?;
// -------------------------------------------------------------------------
// STEP 2: Build & Get State of the Counter Contract
// -------------------------------------------------------------------------
println!("\n[STEP 2] Building counter contract from public state");
- // Define the Counter Contract account id from counter contract deploy
- let (_, counter_contract_id) =
- AccountId::from_bech32("mtst1apcqs7aj3a2cf5t6pnsfy0p4ns7wl7sp").unwrap();
+ // Pass the account ID printed by `counter_contract_deploy` as the first argument, or via
+ // `MIDEN_COUNTER_ACCOUNT_ID`.
+ let counter_contract_bech32 = std::env::args()
+ .nth(1)
+ .or_else(|| std::env::var("MIDEN_COUNTER_ACCOUNT_ID").ok())
+ .expect("pass the counter account ID from counter_contract_deploy");
+ let (account_network, counter_contract_id) =
+ AccountId::from_bech32(&counter_contract_bech32).expect("invalid counter account ID");
+ assert_eq!(
+ account_network,
+ network.network_id(),
+ "counter account must match the selected tutorial network"
+ );
println!("counter contract id: {:?}", counter_contract_id);
@@ -497,13 +614,17 @@ async fn main() -> Result<(), ClientError> {
// -------------------------------------------------------------------------
println!("\n[STEP 3] Call counter contract with FPI from count reader contract");
- let counter_contract_code = include_str!("../masm/accounts/counter.masm");
+ let counter_contract_code =
+ std::fs::read_to_string("../tutorials/masm/accounts/counter.masm").unwrap();
// Compile the counter as a component (same path as the deploy binary) to get
// the correct procedure root that matches the on-chain MAST.
let counter_component_code = client
.code_builder()
- .compile_component_code("external_contract::counter_contract", counter_contract_code)
+ .compile_component_code(
+ "external_contract::counter_contract",
+ &counter_contract_code,
+ )
.unwrap();
let counter_component = AccountComponent::new(
counter_component_code,
@@ -514,7 +635,6 @@ async fn main() -> Result<(), ClientError> {
let get_count_root = counter_component
.component_code()
- .as_library()
.get_procedure_root_by_path("external_contract::counter_contract::get_count")
.expect("get_count export not found");
let get_count_hash = format!("{}", get_count_root);
@@ -523,7 +643,8 @@ async fn main() -> Result<(), ClientError> {
println!("counter id prefix: {:?}", counter_contract_id.prefix());
println!("counter id suffix: {:?}", counter_contract_id.suffix());
- let script_code = include_str!("../masm/scripts/reader_script.masm")
+ let script_code = std::fs::read_to_string("../tutorials/masm/scripts/reader_script.masm")
+ .unwrap()
.replace("{get_count_proc_hash}", &get_count_hash)
.replace(
"{account_id_suffix}",
@@ -538,14 +659,16 @@ async fn main() -> Result<(), ClientError> {
// that compiles the script.
let tx_script = client
.code_builder()
- .with_linked_module("external_contract::count_reader_contract", count_reader_code)
+ .with_linked_module(
+ "external_contract::count_reader_contract",
+ &count_reader_code,
+ )
.unwrap()
.compile_tx_script(script_code.as_str())
.unwrap();
let foreign_account =
- ForeignAccount::public(counter_contract_id, AccountStorageRequirements::default())
- .unwrap();
+ ForeignAccount::public(counter_contract_id, AccountStorageRequirements::default()).unwrap();
let tx_request = TransactionRequestBuilder::new()
.foreign_accounts([foreign_account])
@@ -554,12 +677,13 @@ async fn main() -> Result<(), ClientError> {
.unwrap();
let tx_id = client
- .submit_new_transaction(count_reader_contract.id(), tx_request)
+ .submit_tutorial_transaction(count_reader_contract.id(), tx_request)
.await
.unwrap();
println!(
- "View transaction on MidenScan: https://testnet.midenscan.com/tx/{:?}",
+ "View transaction on MidenScan: {}/tx/{:?}",
+ network.explorer_url(),
tx_id
);
@@ -589,22 +713,32 @@ async fn main() -> Result<(), ClientError> {
"count reader contract storage: {:?}",
account_2.storage().get_item(&count_reader_slot_name)
);
+ assert_eq!(
+ account_2
+ .storage()
+ .get_item(&count_reader_slot_name)
+ .unwrap(),
+ account_1.storage().get_item(&counter_slot_name).unwrap(),
+ "FPI must copy the current counter value",
+ );
Ok(())
}
```
-The output will show the count reader contract being created, the counter contract being imported from testnet, and finally both storage slots reflecting the same count value after the FPI transaction is confirmed.
+Run the standalone project with `TUTORIAL_NETWORK=testnet cargo run --release`. With `MIDEN_COUNTER_ACCOUNT_ID` set, the output shows the reader being created, the counter imported from testnet, and both storage slots containing the same count after the FPI transaction is confirmed. The final assertion assumes the counter is not concurrently incremented while the example runs; use a fresh counter for this check.
### Running the example
-To run the full example, navigate to the `rust-client` directory in the [miden-tutorials](https://github.com/0xMiden/miden-tutorials/) repository and run this command:
+To run the checked-in example, return to the root of the [tutorials repository](https://github.com/0xMiden/tutorials/) and run:
```bash
cd rust-client
-cargo run --release --bin counter_contract_fpi
+TUTORIAL_NETWORK=testnet cargo run --release --bin counter_contract_fpi -- "$MIDEN_COUNTER_ACCOUNT_ID"
```
+If `MIDEN_COUNTER_ACCOUNT_ID` is exported in your shell, you can omit `--` and the final argument.
+
### Continue learning
Next tutorial: [How to Use Unauthenticated Notes](unauthenticated_note_how_to.md)
diff --git a/docs/src/rust-client/index.md b/docs/src/rust-client/index.md
index 4515a95b..5ef55e66 100644
--- a/docs/src/rust-client/index.md
+++ b/docs/src/rust-client/index.md
@@ -15,4 +15,38 @@ The Miden Rust client can be used for a variety of things, including:
This section of the docs is an overview of the different things one can achieve using the Rust client, and how to implement them.
+## Running the v0.16 examples
+
+The examples use Rust 1.98.1 and Miden v0.16. The repository includes the
+required toolchain configuration and dependency lockfiles.
+
+From the repository root, run the Rust tutorials on testnet with Node.js and Yarn:
+
+```bash
+yarn tutorials --rust
+```
+
+For devnet validation, run `TUTORIAL_NETWORK=devnet yarn tutorials --rust` instead.
+
+The runner uses a fresh store for each example and deploys a counter before the
+FPI and public-account interaction examples. The [oracle tutorial](./oracle_tutorial.md)
+requires an external deployment and is excluded from the default run.
+
+Testnet transactions pay fees in the native asset. The
+shared helpers in `rust-client/src/lib.rs` in your v0.16 checkout
+fund each executing account, synchronize before submission, and wait for confirmation.
+They also filter `TX_FEE` notes when selecting tutorial notes. Set
+`MIDEN_FAUCET_URL` if you need to override the network's public faucet API.
+
+To follow a tutorial in a standalone project, clone this repository as `tutorials`
+and create the project alongside it. The tutorial's `Cargo.toml` includes the
+local `rust-client` dependency and development optimization profile. Follow its
+commands to select the Rust toolchain and copy the repository's `Cargo.lock`.
+Run the first build without `--locked` so Cargo can add the new project's package
+entry to the lockfile.
+
+Run standalone programs from their Cargo project directory. They load the shared
+MASM files from `../tutorials/masm/`; the corresponding tutorial shows and explains
+those sources. No additional MASM files need to be copied into the new project.
+
Keep in mind that both the Rust client and the documentation are works-in-progress!
diff --git a/docs/src/rust-client/mappings_in_masm_how_to.md b/docs/src/rust-client/mappings_in_masm_how_to.md
index 5053700c..be7f6127 100644
--- a/docs/src/rust-client/mappings_in_masm_how_to.md
+++ b/docs/src/rust-client/mappings_in_masm_how_to.md
@@ -7,6 +7,8 @@ sidebar_position: 10
_Using mappings in Miden assembly for storing key value pairs_
+For toolchain requirements and shared fee helpers, see the [Rust client setup](./index.md#running-the-v016-examples).
+
## Overview
In this example, we will explore how to use mappings in Miden Assembly. Mappings are essential data structures that store key-value pairs. We will demonstrate how to create an account that contains a mapping and then call a procedure in that account to update the mapping.
@@ -16,8 +18,7 @@ At a high level, this example involves:
- Setting up an account with a mapping stored in one of its storage slots.
- Writing a smart contract in Miden Assembly that includes procedures to read from and write to the mapping.
- Creating a transaction script that calls these procedures.
-- Using Rust code to deploy the account and submit a transaction that updates the mapping.
- After the Miden Assembly snippets, we explain that the transaction script calls a procedure in the account. This procedure then updates the mapping by modifying the mapping stored in the account's storage slot.
+- Using Rust code to deploy the account and submit a transaction that updates the mapping.
## What we'll cover
@@ -42,46 +43,67 @@ At a high level, this example involves:
```masm
use miden::protocol::active_account
use miden::protocol::native_account
-use miden::core::word
use miden::core::sys
+use {StorageMapKey} from miden::protocol::types
+
+# CONSTANTS
+# =================================================================================================
const MAP_SLOT = word("miden::tutorials::mapping::map")
-# Inputs: [KEY, VALUE]
-# Outputs: []
-pub proc write_to_map
- # The storage map is in the mapping slot.
+# PUBLIC INTERFACE
+# =================================================================================================
+
+#! Stores VALUE under KEY in the mapping.
+#!
+#! Inputs: [KEY, VALUE, pad(8)]
+#! Outputs: [pad(16)]
+#!
+#! Invocation: call
+@account_procedure
+pub proc write_to_map(key: StorageMapKey, value: word)
+ # the storage map is in the mapping slot
push.MAP_SLOT[0..2]
- # => [slot_id_prefix, slot_id_suffix, KEY, VALUE]
+ # => [slot_id_suffix, slot_id_prefix, KEY, VALUE, pad(8)]
- # Setting the key value pair in the map
+ # set the key-value pair in the map
exec.native_account::set_map_item
- # => [OLD_VALUE]
+ # => [OLD_VALUE, pad(12)]
dropw
- # => []
+ # => [pad(16)]
end
-# Inputs: [KEY]
-# Outputs: [VALUE]
-pub proc get_value_in_map
- # The storage map is in the mapping slot.
+#! Returns the VALUE stored under KEY in the mapping.
+#!
+#! Inputs: [KEY, pad(12)]
+#! Outputs: [VALUE, pad(12)]
+#!
+#! Invocation: call
+@account_procedure
+pub proc get_value_in_map(key: StorageMapKey) -> word
+ # the storage map is in the mapping slot
push.MAP_SLOT[0..2]
- # => [slot_id_prefix, slot_id_suffix, KEY]
+ # => [slot_id_suffix, slot_id_prefix, KEY, pad(12)]
exec.active_account::get_map_item
- # => [VALUE]
+ # => [VALUE, pad(12)]
end
-# Inputs: []
-# Outputs: [CURRENT_ROOT]
-pub proc get_current_map_root
- # Getting the current root from the mapping slot.
+#! Returns the CURRENT_ROOT of the mapping.
+#!
+#! Inputs: [pad(16)]
+#! Outputs: [CURRENT_ROOT, pad(12)]
+#!
+#! Invocation: call
+@account_procedure
+pub proc get_current_map_root() -> word
+ # get the current root from the mapping slot
push.MAP_SLOT[0..2] exec.active_account::get_item
- # => [CURRENT_ROOT]
+ # => [CURRENT_ROOT, pad(16)]
exec.sys::truncate_stack
- # => [CURRENT_ROOT]
+ # => [CURRENT_ROOT, pad(12)]
end
```
@@ -95,7 +117,7 @@ end
- **get_current_map_root:**
This procedure retrieves the current root of the mapping by calling `get_item` with the mapping slot ID and then truncating the stack to leave only the mapping root.
-**Security Note**: The procedure `write_to_map` calls the account procedure `incr_nonce`. This allows any external account to be able to write to the storage map of the account. Smart contract developers should know that procedures that call the `account::incr_nonce` procedure allow anyone to call the procedure and modify the state of the account.
+The Rust account below uses `NoAuth`, so anyone can import its public state and submit a mapping update without a signature. `NoAuth` handles fee payment and nonce changes; incrementing a nonce does not itself grant authorization. Use an appropriate authentication component when writes should be restricted.
### Transaction script that calls the smart contract
@@ -103,27 +125,42 @@ end
use miden_by_example::mapping_example_contract
use miden::core::sys
-begin
+#! Writes a mapping entry, reads it, and returns the current map root.
+#!
+#! Inputs: [ARGS, pad(12)]
+#! Outputs: [CURRENT_ROOT, pad(12)]
+#!
+#! Where:
+#! - ARGS contains unused transaction script arguments.
+#! - CURRENT_ROOT is the mapping's Merkle root after the write.
+#!
+#! Invocation: dyncall
+@transaction_script
+pub proc main(args: word) -> word
+ dropw
+ # => [pad(16)]
+
push.1.2.3.4
push.0.0.0.0
- # => [KEY, VALUE]
+ # => [KEY, VALUE, pad(16)]
call.mapping_example_contract::write_to_map
- # => []
+ # => [pad(24)]
push.0.0.0.0
- # => [KEY]
+ # => [KEY, pad(24)]
call.mapping_example_contract::get_value_in_map
- # => [VALUE]
+ # => [VALUE, pad(24)]
dropw
- # => []
+ # => [pad(24)]
call.mapping_example_contract::get_current_map_root
- # => [CURRENT_ROOT]
+ # => [CURRENT_ROOT, pad(20)]
exec.sys::truncate_stack
+ # => [CURRENT_ROOT, pad(12)]
end
```
@@ -131,7 +168,7 @@ end
The transaction script does the following:
-- It pushes a key (`[0.0.0.0]`) and a value (`[1.2.3.4]`) onto the stack.
+- It pushes the value with `push.1.2.3.4`, then the key with `push.0.0.0.0`. The last pushed element is on top, so the stored value is `[4, 3, 2, 1]` and the key is `[0, 0, 0, 0]`.
- It calls the `write_to_map` procedure, which is defined in the account’s smart contract. This updates the mapping in the account.
- It then pushes the key again and calls `get_value_in_map` to retrieve the value associated with the key.
- Finally, it calls `get_current_map_root` to get the current state (root) of the mapping.
@@ -142,32 +179,62 @@ The script calls the `write_to_map` procedure in the account which writes the ke
### Rust code that sets everything up
+From the parent directory of your `tutorials` clone, create a sibling Cargo project:
+
+```bash
+cargo new miden-mappings
+cd miden-mappings
+rustup override set 1.98.1
+cp ../tutorials/rust-client/Cargo.lock Cargo.lock
+```
+
+Add these dependencies and the development profile to your `Cargo.toml`:
+
+```toml
+[dependencies]
+rust-client = { path = "../tutorials/rust-client" }
+miden-client = { version = "=0.16.0", features = ["testing", "tonic"] }
+miden-client-sqlite-store = { version = "=0.16.0", package = "miden-client-sqlite-store" }
+miden-protocol = { version = "=0.16.0" }
+rand = { version = "0.10" }
+tokio = { version = "1.48", features = ["rt-multi-thread", "net", "macros", "fs"] }
+
+[profile.dev]
+opt-level = 2
+```
+
Below is the Rust code that deploys the smart contract, creates the transaction script, and submits a transaction to update the mapping in the account:
```rust no_run
-use rand::RngCore;
+use rand::Rng;
+use rust_client::TutorialClientExt;
use std::{path::PathBuf, sync::Arc};
use miden_client::{
+ ClientError,
account::{
- component::AccountComponentMetadata, AccountBuilder, AccountComponent,
- AccountType, StorageMap, StorageSlot, StorageSlotName,
+ AccountBuilder, AccountComponent, AccountType, StorageMap, StorageMapKey, StorageSlot,
+ StorageSlotName,
+ component::{AccountComponentMetadata, BasicWallet},
},
auth::NoAuth,
builder::ClientBuilder,
keystore::FilesystemKeyStore,
- rpc::{Endpoint, GrpcClient},
+ rpc::{GrpcClient, VerifyingRpcClient},
transaction::TransactionRequestBuilder,
- ClientError, Felt, Word,
};
use miden_client_sqlite_store::ClientBuilderSqliteExt;
+use rust_client::{FeeConfig, TutorialNetwork, fund_account_for_fees};
#[tokio::main]
async fn main() -> Result<(), ClientError> {
// Initialize client
- let endpoint = Endpoint::testnet();
+ let network = TutorialNetwork::from_env()?;
+ let endpoint = network.endpoint();
let timeout_ms = 10_000;
- let rpc_client = Arc::new(GrpcClient::new(&endpoint, timeout_ms));
+ let rpc_client = Arc::new(VerifyingRpcClient::new(GrpcClient::new(
+ &endpoint, timeout_ms,
+ )));
// Initialize keystore
let keystore_path = PathBuf::from("./keystore");
@@ -179,29 +246,24 @@ async fn main() -> Result<(), ClientError> {
.rpc(rpc_client)
.sqlite_store(store_path)
.authenticator(keystore.clone())
- .in_debug_mode(true.into())
.build()
.await?;
let sync_summary = client.sync_state().await.unwrap();
println!("Latest block: {}", sync_summary.block_num);
+ let fee_config = FeeConfig::from_client(&client, network).await?;
// -------------------------------------------------------------------------
// STEP 1: Deploy a smart contract with a mapping
// -------------------------------------------------------------------------
println!("\n[STEP 1] Deploy a smart contract with a mapping");
- // Load the MASM file for the counter contract. `include_str!` resolves at
- // compile time relative to this source file.
- let account_code = include_str!("../masm/accounts/mapping_example_contract.masm");
+ // Read the MASM source from the tutorials repository.
+ let account_code =
+ std::fs::read_to_string("../tutorials/masm/accounts/mapping_example_contract.masm")
+ .unwrap();
- // Using an empty storage value in slot 0 since this is usually reserved
- // for the account pub_key and metadata
- let empty_slot_name =
- StorageSlotName::new("miden::tutorials::mapping::value").expect("valid slot name");
- let empty_storage_slot = StorageSlot::with_value(empty_slot_name.clone(), Word::default());
-
- // initialize storage map
+ // Storage slots are named in v0.16; the component only needs its mapping slot.
let storage_map = StorageMap::new();
let map_slot_name =
StorageSlotName::new("miden::tutorials::mapping::map").expect("valid slot name");
@@ -210,16 +272,16 @@ async fn main() -> Result<(), ClientError> {
// Compile the account code into `AccountComponent` with one storage slot
let component_code = client
.code_builder()
- .compile_component_code("miden_by_example::mapping_example_contract", account_code)
+ .compile_component_code("miden_by_example::mapping_example_contract", &account_code)
.unwrap();
let mapping_contract_component = AccountComponent::new(
component_code,
- vec![empty_storage_slot, storage_slot_map],
+ vec![storage_slot_map],
AccountComponentMetadata::new("miden_by_example::mapping_example_contract"),
)
.unwrap();
- // Init seed for the counter contract
+ // Init seed for the mapping contract
let mut init_seed = [0_u8; 32];
client.rng().fill_bytes(&mut init_seed);
@@ -227,7 +289,8 @@ async fn main() -> Result<(), ClientError> {
let mapping_example_contract = AccountBuilder::new(init_seed)
.account_type(AccountType::Public)
.with_component(mapping_contract_component.clone())
- .with_auth_component(NoAuth)
+ .with_component(BasicWallet)
+ .with_component(NoAuth)
.build()
.unwrap();
@@ -235,21 +298,23 @@ async fn main() -> Result<(), ClientError> {
.add_account(&mapping_example_contract, false)
.await
.unwrap();
+ fund_account_for_fees(&mut client, mapping_example_contract.id(), &fee_config).await?;
// -------------------------------------------------------------------------
// STEP 2: Call the Mapping Contract with a Script
// -------------------------------------------------------------------------
println!("\n[STEP 2] Call Mapping Contract With Script");
- let script_code = include_str!("../masm/scripts/mapping_example_script.masm");
+ let script_code =
+ std::fs::read_to_string("../tutorials/masm/scripts/mapping_example_script.masm").unwrap();
// Compile the transaction script with the account code linked as a
// module on the same `CodeBuilder` chain.
let tx_script = client
.code_builder()
- .with_linked_module("miden_by_example::mapping_example_contract", account_code)
+ .with_linked_module("miden_by_example::mapping_example_contract", &account_code)
.unwrap()
- .compile_tx_script(script_code)
+ .compile_tx_script(&script_code)
.unwrap();
// Build a transaction request with the custom script
@@ -260,12 +325,13 @@ async fn main() -> Result<(), ClientError> {
// Execute and submit the transaction
let tx_id = client
- .submit_new_transaction(mapping_example_contract.id(), tx_increment_request)
+ .submit_tutorial_transaction(mapping_example_contract.id(), tx_increment_request)
.await
.unwrap();
println!(
- "View transaction on MidenScan: https://testnet.midenscan.com/tx/{:?}",
+ "View transaction on MidenScan: {}/tx/{:?}",
+ network.explorer_url(),
tx_id
);
@@ -276,19 +342,22 @@ async fn main() -> Result<(), ClientError> {
.await
.unwrap()
.expect("mapping contract not found");
- let key = [
- Felt::new_unchecked(0),
- Felt::new_unchecked(0),
- Felt::new_unchecked(0),
- Felt::new_unchecked(0),
- ]
- .into();
+ let key = StorageMapKey::empty();
println!(
"Mapping state\n Index: {:?}\n Key: {:?}\n Value: {:?}",
map_slot_name,
key,
account.storage().get_map_item(&map_slot_name, key)
);
+ let value = account.storage().get_map_item(&map_slot_name, key).unwrap();
+ assert_eq!(
+ value
+ .iter()
+ .map(|felt| felt.as_canonical_u64())
+ .collect::>(),
+ vec![4, 3, 2, 1],
+ "the mapping must store the value written by the transaction script",
+ );
Ok(())
}
@@ -297,10 +366,10 @@ async fn main() -> Result<(), ClientError> {
### What the Rust code does
- **Client Initialization:**
- The client is initialized with a connection to the Miden Testnet and a SQLite store. This sets up the environment to deploy and interact with accounts.
+ The client connects to Miden testnet and uses a SQLite store to track accounts, notes, and transactions.
- **Deploying the Smart Contract:**
- The account containing the mapping is created by reading the MASM smart contract from a file, compiling it into an `AccountComponent`, and deploying it using an `AccountBuilder`.
+ The account MASM is compiled into an `AccountComponent` with a named map slot. `AccountBuilder` creates the account locally; consuming its native-asset funding note publishes it on-chain.
- **Creating and Executing a Transaction Script:**
A separate MASM script is compiled into a `TransactionScript`. This script calls the smart contract's procedures to write to and then read from the mapping.
@@ -312,11 +381,13 @@ async fn main() -> Result<(), ClientError> {
### Running the example
-To run the full example, navigate to the `rust-client` directory in the [miden-tutorials](https://github.com/0xMiden/miden-tutorials/) repository and run this command:
+For the standalone Cargo project, save the Rust code as `src/main.rs` and run `TUTORIAL_NETWORK=testnet cargo run --release`.
+
+To run the checked-in example, return to the root of the [tutorials repository](https://github.com/0xMiden/tutorials/) and run:
```bash
cd rust-client
-cargo run --release --bin mapping_example
+TUTORIAL_NETWORK=testnet cargo run --release --bin mapping_example
```
This example shows how the script calls the procedure in the account, which then updates the mapping stored within the account. The mapping update is verified by reading the mapping’s key-value pair after the transaction completes.
diff --git a/docs/src/rust-client/mint_consume_create_tutorial.md b/docs/src/rust-client/mint_consume_create_tutorial.md
index b5b9377a..07e2cc2b 100644
--- a/docs/src/rust-client/mint_consume_create_tutorial.md
+++ b/docs/src/rust-client/mint_consume_create_tutorial.md
@@ -7,6 +7,8 @@ sidebar_position: 3
_Using the Miden client in Rust to mint, consume, and create notes_
+For toolchain requirements and shared fee helpers, see the [Rust client setup](./index.md#running-the-v016-examples).
+
## Overview
In the previous section, we initialized our repository and covered how to create an account and deploy a faucet. In this section, we will mint tokens from the faucet for _Alice_, consume the newly created notes, and demonstrate how to send assets to other accounts.
@@ -19,13 +21,13 @@ In the previous section, we initialized our repository and covered how to create
## Step 1: Minting tokens from the faucet
-To mint notes with tokens from the faucet we created, Alice needs to call the faucet with a mint transaction request.
+To mint notes with tokens from the faucet we created, the client submits a mint transaction signed by the faucet's key. The faucet executes the transaction and creates a note for Alice; Alice later signs a separate transaction to consume it.
_In essence, a transaction request is a structured template that outlines the data required to generate a zero-knowledge proof of a state change of an account. It specifies which input notes (if any) will be consumed, includes an optional transaction script to execute, and enumerates the set of notes expected to be created (if any)._
-Below is an example of a transaction request minting tokens from the faucet for Alice. This code snippet will create 5 transaction mint transaction requests.
+Below is an example of a transaction request minting tokens from the faucet for Alice. This code snippet creates and confirms five mint transactions, each producing one note containing 100 raw units of the tutorial asset. Transaction fees are paid in the separate native fee asset.
-Add this snippet to the end of your file in the `main()` function that we created in the previous chapter:
+Continue the same project from [Creating Accounts and Faucets](./create_deploy_tutorial.md), keeping its `Cargo.toml` and seeded `Cargo.lock`. Insert this snippet inside `main()`, after the previous steps and immediately before its final `Ok(())`:
```rust ignore
//------------------------------------------------------------
@@ -36,6 +38,7 @@ println!("\n[STEP 3] Minting 5 notes of 100 tokens each for Alice.");
let amount: u64 = 100;
let fungible_asset = FungibleAsset::new(faucet_account.id(), amount).unwrap();
+let mut minted_note_ids = Vec::new();
for i in 1..=5 {
let transaction_request = TransactionRequestBuilder::new()
.build_mint_fungible_asset(
@@ -46,10 +49,16 @@ for i in 1..=5 {
)
.unwrap();
+ minted_note_ids.extend(
+ transaction_request
+ .expected_output_own_notes()
+ .iter()
+ .map(Note::id),
+ );
println!("tx request built");
let tx_id = client
- .submit_new_transaction(faucet_account.id(), transaction_request)
+ .submit_tutorial_transaction(faucet_account.id(), transaction_request)
.await?;
println!(
"Minted note #{} of {} tokens for Alice. TX: {:?}",
@@ -68,14 +77,16 @@ Once Alice has minted a note from the faucet, she will eventually want to spend
Minting a note from a faucet on Miden means a faucet account creates a new note targeted to the requesting account. The requesting account needs to consume this new note to have the assets appear in their account.
-To identify consumable notes, the Miden client provides the `get_consumable_notes` function. Before calling it, ensure that the client state is synced.
+To identify consumable notes, the Miden client provides `get_consumable_notes`. The `TutorialClientExt::get_consumable_tutorial_notes` wrapper used below filters out `TX_FEE` notes. Call it after syncing the client state.
-_Tip: If you know how many notes to expect after a transaction, use an await or loop condition to check how many notes of the type you expect are available for consumption instead of using a set timeout before calling `get_consumable_notes`. This ensures your application isn't idle for longer than necessary._
+Track the output note IDs from each mint request and wait for those notes to be committed. Do not wait for the total consumable-note count to equal five: `TX_FEE` notes can also be consumable and make that condition impossible. The `wait_for_notes_by_id` helper in Step 3 polls for the specific minted notes with a timeout.
#### Identifying which notes are available:
```rust ignore
-let consumable_notes = client.get_consumable_notes(Some(alice_account.id())).await?;
+let consumable_notes = client
+ .get_consumable_tutorial_notes(Some(alice_account.id()))
+ .await?;
```
## Step 3: Consuming multiple notes in a single transaction:
@@ -84,7 +95,7 @@ Now that we know how to identify notes ready to consume, let's consume the notes
The following code snippet identifies consumable notes and consumes them in a single transaction.
-Add this snippet to the end of your file in the `main()` function:
+Insert this snippet after the preceding steps inside `main()`, immediately before its final `Ok(())`:
```rust ignore
//------------------------------------------------------------
@@ -92,41 +103,17 @@ Add this snippet to the end of your file in the `main()` function:
//------------------------------------------------------------
println!("\n[STEP 4] Alice will now consume all of her notes to consolidate them.");
-// Consume all minted notes in a single transaction
-loop {
- // Resync to get the latest data
- client.sync_state().await?;
-
- let consumable_notes = client
- .get_consumable_notes(Some(alice_account.id()))
- .await?;
- let notes = consumable_notes
- .iter()
- .map(|(note, _)| note.clone().try_into())
- .collect::, _>>()?;
-
- if notes.len() == 5 {
- println!("Found 5 consumable notes for Alice. Consuming them now...");
- let transaction_request = TransactionRequestBuilder::new()
- .build_consume_notes(notes)
- .unwrap();
-
- let tx_id = client
- .submit_new_transaction(alice_account.id(), transaction_request)
- .await?;
- println!(
- "All of Alice's notes consumed successfully. TX: {:?}",
- tx_id
- );
- break;
- } else {
- println!(
- "Currently, Alice has {} consumable notes. Waiting...",
- notes.len()
- );
- tokio::time::sleep(Duration::from_secs(3)).await;
- }
-}
+// TX_FEE notes are also consumable. Select only the five P2ID notes we minted.
+let notes = rust_client::wait_for_notes_by_id(&mut client, &minted_note_ids).await?;
+assert_eq!(notes.len(), 5);
+let transaction_request = TransactionRequestBuilder::new().build_consume_notes(notes)?;
+let tx_id = client
+ .submit_tutorial_transaction(alice_account.id(), transaction_request)
+ .await?;
+println!(
+ "All of Alice's notes consumed successfully. TX: {:?}",
+ tx_id
+);
```
## Step 4: Sending tokens to other accounts
@@ -143,9 +130,9 @@ For the sake of the example, the first four P2ID transfers are handled in a sing
To output multiple notes in a single transaction we need to create a list of our expected output notes. The expected output notes are the notes that we expect to create in our transaction request.
-In the snippet below, we create an empty vector to store five P2ID output notes, loop over five iterations `(using 0..=4)` to create five unique dummy account IDs, build a P2ID note for each one, and push each note onto the vector. Finally, we build a transaction request using `.own_output_notes()`—passing in all five notes—and submit it to the node.
+In the snippet below, we create an empty vector, loop over four iterations using `1..=4`, build a P2ID note for each generated dummy account ID, and push each note onto the vector. We pass all four notes to `.own_output_notes()` and submit one transaction. The following step sends the fifth note.
-Add this snippet to the end of your file in the `main()` function:
+Insert this snippet after the preceding steps inside `main()`, immediately before its final `Ok(())`:
```rust ignore
//------------------------------------------------------------
@@ -168,30 +155,32 @@ for _ in 1..=4 {
init_seed,
AccountIdVersion::Version1,
AccountType::Public,
+ AssetCallbackFlag::Disabled,
);
let send_amount = 50;
let fungible_asset = FungibleAsset::new(faucet_account.id(), send_amount).unwrap();
- let p2id_note = P2idNote::create(
- alice_account.id(),
- target_account_id,
- vec![fungible_asset.into()],
- NoteType::Public,
- NoteAttachments::empty(),
- client.rng(),
- )?;
+ let p2id_note: Note = P2idNote::builder()
+ .sender(alice_account.id())
+ .target(target_account_id)
+ .asset(fungible_asset)
+ .note_type(NoteType::Public)
+ .generate_serial_number(client.rng())
+ .build()?
+ .into();
p2id_notes.push(p2id_note);
}
// Specifying output notes and creating a tx request to create them
+let output_notes = p2id_notes;
let transaction_request = TransactionRequestBuilder::new()
- .own_output_notes(p2id_notes)
+ .own_output_notes(output_notes)
.build()
.unwrap();
let tx_id = client
- .submit_new_transaction(alice_account.id(), transaction_request)
+ .submit_tutorial_transaction(alice_account.id(), transaction_request)
.await?;
println!("Submitted a transaction with 4 P2ID notes. TX: {:?}", tx_id);
@@ -199,9 +188,9 @@ println!("Submitted a transaction with 4 P2ID notes. TX: {:?}", tx_id);
### Basic P2ID transfer
-Now as an example, Alice will send some tokens to an account in a single transaction.
+`build_pay_to_id` creates the P2ID note and transaction request for a single transfer. Alice will use it to send tokens to one more account.
-Add this snippet to the end of your file in the `main()` function:
+Insert this snippet after the preceding steps inside `main()`, immediately before its final `Ok(())`:
```rust ignore
println!("Submitting one more single P2ID transaction...");
@@ -214,70 +203,85 @@ let target_account_id = AccountId::dummy(
init_seed,
AccountIdVersion::Version1,
AccountType::Public,
+ AssetCallbackFlag::Disabled,
);
let send_amount = 50;
let fungible_asset = FungibleAsset::new(faucet_account.id(), send_amount).unwrap();
-let p2id_note = P2idNote::create(
+let payment = PaymentNoteDescription::new(
+ vec![fungible_asset.into()],
alice_account.id(),
target_account_id,
- vec![fungible_asset.into()],
+);
+let transaction_request = TransactionRequestBuilder::new().build_pay_to_id(
+ payment,
NoteType::Public,
- NoteAttachments::empty(),
client.rng(),
)?;
-let transaction_request = TransactionRequestBuilder::new()
- .own_output_notes(vec![p2id_note])
- .build()
- .unwrap();
-
let tx_id = client
- .submit_new_transaction(alice_account.id(), transaction_request)
+ .submit_tutorial_transaction(alice_account.id(), transaction_request)
.await?;
println!("Submitted final P2ID transaction. TX: {:?}", tx_id);
+let alice = client
+ .get_account(alice_account.id())
+ .await?
+ .expect("Alice exists");
+let balance = alice
+ .vault()
+ .get_balance(AssetId::new_fungible(faucet_account.id()))?;
+assert_eq!(balance.as_u64(), 250, "Alice should retain 500 - 250 MID");
+
+println!("\nAll steps completed successfully!");
+println!("Alice created a wallet, a faucet was deployed,");
+println!("5 notes of 100 tokens were minted to Alice, those notes were consumed,");
+println!("and then Alice sent 5 separate 50-token notes to 5 different users.");
```
-Note: _In a production environment do not use `AccountId::dummy()`, this is simply for the sake of the tutorial example._
+Note: _`AccountId::dummy()` generates example IDs without deployable accounts or keys. These notes demonstrate creation and cannot be consumed by real recipients. Use actual recipient IDs when transferring useful assets._
## Summary
Your `src/main.rs` function should now look like this:
```rust no_run
-use rand::RngCore;
+use rand::Rng;
+use rust_client::TutorialClientExt;
use std::{path::PathBuf, sync::Arc};
use tokio::time::Duration;
use miden_client::{
+ ClientError,
account::{
+ AccountBuilder, AccountId, AccountType,
component::{
- BasicWallet, BurnPolicyConfig, FungibleFaucet, MintPolicyConfig, PolicyRegistration,
- TokenName, TokenPolicyManager,
+ create_singlesig_user_fungible_faucet, BasicWallet, BurnPolicy, FungibleFaucet,
+ MintPolicy, TokenName, TokenPolicyManager,
},
- AccountBuilder, AccountId, AccountType,
},
- address::NetworkId,
- asset::{AssetAmount, FungibleAsset, TokenSymbol},
- auth::{AuthSchemeId, AuthSecretKey, AuthSingleSig},
+ asset::{AssetAmount, AssetCallbackFlag, AssetId, FungibleAsset, TokenSymbol},
+ auth::{AuthSecretKey, AuthSingleSig},
builder::ClientBuilder,
keystore::{FilesystemKeyStore, Keystore},
- note::{NoteAttachments, NoteType, P2idNote},
- rpc::{Endpoint, GrpcClient},
- transaction::TransactionRequestBuilder,
- ClientError,
+ note::{Note, NoteType, P2idNote},
+ rpc::{GrpcClient, VerifyingRpcClient},
+ transaction::{PaymentNoteDescription, TransactionRequestBuilder},
};
use miden_client_sqlite_store::ClientBuilderSqliteExt;
use miden_protocol::account::AccountIdVersion;
+use rust_client::{FeeConfig, TutorialNetwork, fund_account_for_fees};
#[tokio::main]
async fn main() -> Result<(), ClientError> {
// Initialize client
- let endpoint = Endpoint::testnet();
+ let network = TutorialNetwork::from_env()?;
+ let endpoint = network.endpoint();
let timeout_ms = 10_000;
- let rpc_client = Arc::new(GrpcClient::new(&endpoint, timeout_ms));
+ let rpc_client = Arc::new(VerifyingRpcClient::new(GrpcClient::new(
+ &endpoint, timeout_ms,
+ )));
// Initialize keystore
let keystore_path = PathBuf::from("./keystore");
@@ -289,12 +293,12 @@ async fn main() -> Result<(), ClientError> {
.rpc(rpc_client)
.sqlite_store(store_path)
.authenticator(keystore.clone())
- .in_debug_mode(true.into())
.build()
.await?;
let sync_summary = client.sync_state().await.unwrap();
println!("Latest block: {}", sync_summary.block_num);
+ let fee_config = FeeConfig::from_client(&client, network).await?;
//------------------------------------------------------------
// STEP 1: Create a basic wallet for Alice
@@ -310,7 +314,7 @@ async fn main() -> Result<(), ClientError> {
// Build the account
let alice_account = AccountBuilder::new(init_seed)
.account_type(AccountType::Public)
- .with_auth_component(AuthSingleSig::new(key_pair.public_key().to_commitment(), AuthSchemeId::Falcon512Poseidon2))
+ .with_component(AuthSingleSig::from_public_key(key_pair.public_key()))
.with_component(BasicWallet)
.build()
.unwrap();
@@ -319,11 +323,16 @@ async fn main() -> Result<(), ClientError> {
client.add_account(&alice_account, false).await?;
// Add the key pair to the keystore
- keystore.add_key(&key_pair, alice_account.id()).await.unwrap();
+ keystore
+ .add_key(&key_pair, alice_account.id())
+ .await
+ .unwrap();
- let alice_account_id_bech32 = alice_account.id().to_bech32(NetworkId::Testnet);
+ let alice_account_id_bech32 = alice_account.id().to_bech32(network.network_id());
println!("Alice's account ID: {:?}", alice_account_id_bech32);
+ fund_account_for_fees(&mut client, alice_account.id(), &fee_config).await?;
+
//------------------------------------------------------------
// STEP 2: Deploy a fungible faucet
//------------------------------------------------------------
@@ -342,40 +351,44 @@ async fn main() -> Result<(), ClientError> {
let key_pair = AuthSecretKey::new_falcon512_poseidon2_with_rng(client.rng());
// Build the faucet account.
- // In v0.15 the faucet is a `FungibleFaucet` component plus a `TokenPolicyManager`
+ // The faucet is a `FungibleFaucet` component plus a `TokenPolicyManager`
// that registers an "allow all" mint (and burn) policy; minting is rejected
// unless an active mint policy is present.
- let faucet_account = AccountBuilder::new(init_seed)
- .account_type(AccountType::Public)
- .with_auth_component(AuthSingleSig::new(key_pair.public_key().to_commitment(), AuthSchemeId::Falcon512Poseidon2))
- .with_component(
- FungibleFaucet::builder()
- .name(TokenName::new("MID").unwrap())
- .symbol(symbol)
- .decimals(decimals)
- .max_supply(max_supply)
- .build()
- .unwrap(),
- )
- .with_components(
- TokenPolicyManager::new()
- .with_mint_policy(MintPolicyConfig::AllowAll, PolicyRegistration::Active)
- .unwrap()
- .with_burn_policy(BurnPolicyConfig::AllowAll, PolicyRegistration::Active)
- .unwrap(),
- )
+ let faucet = FungibleFaucet::builder()
+ .name(TokenName::new("MID").unwrap())
+ .symbol(symbol)
+ .decimals(decimals)
+ .max_supply(max_supply)
.build()
.unwrap();
+ let policies = TokenPolicyManager::builder()
+ .active_mint_policy(MintPolicy::allow_all())
+ .active_burn_policy(BurnPolicy::allow_all())
+ .build();
+ // The SDK factory includes BasicWallet so the faucet can receive the native fee asset.
+ let faucet_account = create_singlesig_user_fungible_faucet(
+ init_seed,
+ faucet,
+ AuthSingleSig::from_public_key(key_pair.public_key()),
+ policies,
+ AccountType::Public,
+ )
+ .unwrap();
// Add the faucet to the client
client.add_account(&faucet_account, false).await?;
// Add the key pair to the keystore
- keystore.add_key(&key_pair, faucet_account.id()).await.unwrap();
+ keystore
+ .add_key(&key_pair, faucet_account.id())
+ .await
+ .unwrap();
- let faucet_account_id_bech32 = faucet_account.id().to_bech32(NetworkId::Testnet);
+ let faucet_account_id_bech32 = faucet_account.id().to_bech32(network.network_id());
println!("Faucet account ID: {:?}", faucet_account_id_bech32);
+ fund_account_for_fees(&mut client, faucet_account.id(), &fee_config).await?;
+
// Resync to show newly deployed faucet
client.sync_state().await?;
tokio::time::sleep(Duration::from_secs(2)).await;
@@ -388,6 +401,7 @@ async fn main() -> Result<(), ClientError> {
let amount: u64 = 100;
let fungible_asset = FungibleAsset::new(faucet_account.id(), amount).unwrap();
+ let mut minted_note_ids = Vec::new();
for i in 1..=5 {
let transaction_request = TransactionRequestBuilder::new()
.build_mint_fungible_asset(
@@ -398,10 +412,16 @@ async fn main() -> Result<(), ClientError> {
)
.unwrap();
+ minted_note_ids.extend(
+ transaction_request
+ .expected_output_own_notes()
+ .iter()
+ .map(Note::id),
+ );
println!("tx request built");
let tx_id = client
- .submit_new_transaction(faucet_account.id(), transaction_request)
+ .submit_tutorial_transaction(faucet_account.id(), transaction_request)
.await?;
println!(
"Minted note #{} of {} tokens for Alice. TX: {:?}",
@@ -418,40 +438,17 @@ async fn main() -> Result<(), ClientError> {
//------------------------------------------------------------
println!("\n[STEP 4] Alice will now consume all of her notes to consolidate them.");
- // Consume all minted notes in a single transaction
- loop {
- // Resync to get the latest data
- client.sync_state().await?;
-
- let consumable_notes = client
- .get_consumable_notes(Some(alice_account.id()))
- .await?;
- let notes = consumable_notes
- .iter()
- .map(|(note, _)| note.clone().try_into())
- .collect::, _>>()?;
-
- if notes.len() == 5 {
- println!("Found 5 consumable notes for Alice. Consuming them now...");
- let transaction_request =
- TransactionRequestBuilder::new().build_consume_notes(notes)?;
-
- let tx_id = client
- .submit_new_transaction(alice_account.id(), transaction_request)
- .await?;
- println!(
- "All of Alice's notes consumed successfully. TX: {:?}",
- tx_id
- );
- break;
- } else {
- println!(
- "Currently, Alice has {} consumable notes. Waiting...",
- notes.len()
- );
- tokio::time::sleep(Duration::from_secs(3)).await;
- }
- }
+ // TX_FEE notes are also consumable. Select only the five P2ID notes we minted.
+ let notes = rust_client::wait_for_notes_by_id(&mut client, &minted_note_ids).await?;
+ assert_eq!(notes.len(), 5);
+ let transaction_request = TransactionRequestBuilder::new().build_consume_notes(notes)?;
+ let tx_id = client
+ .submit_tutorial_transaction(alice_account.id(), transaction_request)
+ .await?;
+ println!(
+ "All of Alice's notes consumed successfully. TX: {:?}",
+ tx_id
+ );
//------------------------------------------------------------
// STEP 5: Alice sends 5 notes of 50 tokens to 5 users
@@ -473,19 +470,20 @@ async fn main() -> Result<(), ClientError> {
init_seed,
AccountIdVersion::Version1,
AccountType::Public,
+ AssetCallbackFlag::Disabled,
);
let send_amount = 50;
let fungible_asset = FungibleAsset::new(faucet_account.id(), send_amount).unwrap();
- let p2id_note = P2idNote::create(
- alice_account.id(),
- target_account_id,
- vec![fungible_asset.into()],
- NoteType::Public,
- NoteAttachments::empty(),
- client.rng(),
- )?;
+ let p2id_note: Note = P2idNote::builder()
+ .sender(alice_account.id())
+ .target(target_account_id)
+ .asset(fungible_asset)
+ .note_type(NoteType::Public)
+ .generate_serial_number(client.rng())
+ .build()?
+ .into();
p2id_notes.push(p2id_note);
}
@@ -497,7 +495,7 @@ async fn main() -> Result<(), ClientError> {
.unwrap();
let tx_id = client
- .submit_new_transaction(alice_account.id(), transaction_request)
+ .submit_tutorial_transaction(alice_account.id(), transaction_request)
.await?;
println!("Submitted a transaction with 4 P2ID notes. TX: {:?}", tx_id);
@@ -512,30 +510,36 @@ async fn main() -> Result<(), ClientError> {
init_seed,
AccountIdVersion::Version1,
AccountType::Public,
+ AssetCallbackFlag::Disabled,
);
let send_amount = 50;
let fungible_asset = FungibleAsset::new(faucet_account.id(), send_amount).unwrap();
- let p2id_note = P2idNote::create(
+ let payment = PaymentNoteDescription::new(
+ vec![fungible_asset.into()],
alice_account.id(),
target_account_id,
- vec![fungible_asset.into()],
+ );
+ let transaction_request = TransactionRequestBuilder::new().build_pay_to_id(
+ payment,
NoteType::Public,
- NoteAttachments::empty(),
client.rng(),
)?;
- let transaction_request = TransactionRequestBuilder::new()
- .own_output_notes(vec![p2id_note])
- .build()
- .unwrap();
-
let tx_id = client
- .submit_new_transaction(alice_account.id(), transaction_request)
+ .submit_tutorial_transaction(alice_account.id(), transaction_request)
.await?;
println!("Submitted final P2ID transaction. TX: {:?}", tx_id);
+ let alice = client
+ .get_account(alice_account.id())
+ .await?
+ .expect("Alice exists");
+ let balance = alice
+ .vault()
+ .get_balance(AssetId::new_fungible(faucet_account.id()))?;
+ assert_eq!(balance.as_u64(), 250, "Alice should retain 500 - 250 MID");
println!("\nAll steps completed successfully!");
println!("Alice created a wallet, a faucet was deployed,");
@@ -549,43 +553,35 @@ async fn main() -> Result<(), ClientError> {
Let's run the `src/main.rs` program again:
```bash
-cargo run --release
+TUTORIAL_NETWORK=testnet cargo run --release
```
-The output will look like this:
+The following is an abbreviated output; IDs vary and the helper also prints funding and transaction confirmations:
```text
-Latest block: 226896
+Latest block:
[STEP 1] Creating a new account for Alice
-Alice's account ID: ""
+Alice's account ID: ""
[STEP 2] Deploying a new fungible faucet.
-Faucet account ID: ""
+Faucet account ID: ""
[STEP 3] Minting 5 notes of 100 tokens each for Alice.
tx request built
-Minted note #1 of 100 tokens for Alice.
-tx request built
-Minted note #2 of 100 tokens for Alice.
-tx request built
-Minted note #3 of 100 tokens for Alice.
-tx request built
-Minted note #4 of 100 tokens for Alice.
-tx request built
-Minted note #5 of 100 tokens for Alice.
+Minted note #1 of 100 tokens for Alice. TX:
+...
+Minted note #5 of 100 tokens for Alice. TX:
All 5 notes minted for Alice successfully!
[STEP 4] Alice will now consume all of her notes to consolidate them.
-Currently, Alice has 2 consumable notes. Waiting...
-Currently, Alice has 4 consumable notes. Waiting...
-Found 5 consumable notes for Alice. Consuming them now...
-All of Alice's notes consumed successfully.
+All of Alice's notes consumed successfully. TX:
[STEP 5] Alice sends 5 notes of 50 tokens each to 5 different users.
Creating multiple P2ID notes for 4 target accounts in one transaction...
-Submitted a transaction with 4 P2ID notes.
+Submitted a transaction with 4 P2ID notes. TX:
Submitting one more single P2ID transaction...
+Submitted final P2ID transaction. TX:
All steps completed successfully!
Alice created a wallet, a faucet was deployed,
@@ -595,11 +591,11 @@ and then Alice sent 5 separate 50-token notes to 5 different users.
### Running the example
-To run a full working example navigate to the `rust-client` directory in the [miden-tutorials](https://github.com/0xMiden/miden-tutorials/) repository and run this command:
+From the root of your `tutorials` clone, run the checked-in example:
```bash
cd rust-client
-cargo run --release --bin create_mint_consume_send
+TUTORIAL_NETWORK=testnet cargo run --release --bin create_mint_consume_send
```
### Continue learning
diff --git a/docs/src/rust-client/network_transactions_tutorial.md b/docs/src/rust-client/network_transactions_tutorial.md
index af4a239e..ef41d2d7 100644
--- a/docs/src/rust-client/network_transactions_tutorial.md
+++ b/docs/src/rust-client/network_transactions_tutorial.md
@@ -7,17 +7,21 @@ sidebar_position: 6
_Using the Miden client in Rust to deploy and interact with smart contracts using network transactions_
+For toolchain requirements and shared fee helpers, see the [Rust client setup](./index.md#running-the-v016-examples).
+
## Overview
In this tutorial, we will explore Network Transactions (NTXs) on Miden - a powerful feature that enables autonomous smart contract execution and public shared state management. Unlike local transactions that require users to execute and prove, network transactions are executed and proven by a network transaction builder.
-We'll build a network counter smart contract using the same MASM code as the regular counter. In v0.15 there is no separate network storage mode. Instead, an account is a _network account_ — one the network transaction builder executes on a user's behalf — if and only if it is public (`AccountType::Public`) **and** carries the `AuthNetworkAccount` auth component with a non-empty note-script allowlist. The allowed note-script roots and transaction-script roots are pinned at account creation. Attaching a `NetworkAccountTarget` to a note is necessary but not sufficient: without the allowlist the node never classifies the account as a network account, and the note is silently orphaned. See the [account changes migration guide](https://docs.miden.xyz/builder/migration/account-changes).
+We'll build a public network counter using the same MASM code as the regular counter. `AuthNetworkAccount` configures the note scripts that the network transaction builder may execute, and a fee policy prices those notes. The increment note also carries a `NetworkAccountTarget` attachment identifying its target. See the [account changes migration guide](https://docs.miden.xyz/builder/migration/account-changes).
+
+Deployment and subsequent updates are different operations. On a fee-enabled network, consuming the initial native-asset funding note publishes the new network account with count **0**. After publication, the public RPC rejects user-submitted transactions that directly update an existing network account. Alice must publish an increment note from her own account; the network transaction builder consumes it and changes the counter to **1**. This restriction is enforced by the [node's submission handler](https://github.com/0xMiden/node/blob/v0.16.0/crates/rpc/src/server/api/submit_proven_tx.rs#L94).
## What we'll cover
- Understanding Network Transactions and when to use them
- Deploying public smart contracts that the network operator can execute
-- Using transaction scripts to initialize network contracts on-chain
+- Publishing a new network account through its initial funding transaction
- Creating network notes for user interactions
- Validating network transaction results
@@ -29,108 +33,134 @@ This tutorial assumes you have completed the [counter contract tutorial](counter
Network transactions are executed and proven by the Miden operator rather than the client. They are useful for:
-- **Public shared state**: Multiple users can interact with the same contract state without race conditions
+- **Public shared state**: Multiple users can publish notes targeting the same contract; the network transaction builder orders their execution
- **Autonomous execution**: Smart contracts can execute when conditions are met without user intervention
- **Resource-constrained devices**: Clients that can't generate ZK proofs efficiently
- **AMM applications**: Using network notes, you can build sophisticated AMMs where trades execute automatically
-The main trade-off is reduced privacy since the operator can see transaction inputs.
+The account state and increment notes in this example are public, so the operator can see the transaction inputs.
## Step 1: Initialize your repository
-Create a new Rust repository for your Miden project and navigate to it:
+From the parent directory of your `tutorials` clone, create a sibling Cargo project:
```bash
cargo new miden-network-transactions
cd miden-network-transactions
+rustup override set 1.98.1
+cp ../tutorials/rust-client/Cargo.lock Cargo.lock
```
Add the following dependencies to your `Cargo.toml` file:
```toml
[dependencies]
-miden-client = { version = "0.15", features = ["testing", "tonic"] }
-miden-client-sqlite-store = { version = "0.15", package = "miden-client-sqlite-store" }
-miden-protocol = { version = "0.15" }
-rand = { version = "0.9" }
-tokio = { version = "1.46", features = ["rt-multi-thread", "net", "macros", "fs"] }
+# Clone tutorials next to this Cargo project (see Rust client setup).
+rust-client = { path = "../tutorials/rust-client" }
+miden-client = { version = "=0.16.0", features = ["testing", "tonic"] }
+miden-client-sqlite-store = { version = "=0.16.0", package = "miden-client-sqlite-store" }
+miden-protocol = { version = "=0.16.0" }
+rand = { version = "0.10" }
+tokio = { version = "1.48", features = ["rt-multi-thread", "net", "macros", "fs"] }
+
+[profile.dev]
+opt-level = 2
```
## Step 2: Set up MASM files
-Create the directory structure:
-
-```bash
-mkdir -p masm/accounts masm/scripts masm/notes
-```
+The example reads the counter and note sources from the repository’s `masm/` directory.
### Counter Contract
We'll use the same counter contract MASM code as the regular counter tutorial. The key difference is in the Rust configuration, not the MASM code.
-Create `masm/accounts/counter.masm`:
+The counter is defined in `masm/accounts/counter.masm`:
```masm
use miden::protocol::active_account
use miden::protocol::native_account
-use miden::core::word
use miden::core::sys
+# CONSTANTS
+# =================================================================================================
+
const COUNTER_SLOT = word("miden::tutorials::counter")
-#! Inputs: []
-#! Outputs: [count]
-pub proc get_count
+# PUBLIC INTERFACE
+# =================================================================================================
+
+#! Returns the current count.
+#!
+#! Inputs: [pad(16)]
+#! Outputs: [count, pad(15)]
+#!
+#! Invocation: call
+@account_procedure
+pub proc get_count() -> felt
push.COUNTER_SLOT[0..2] exec.active_account::get_item
- # => [count]
+ # => [[count, 0, 0, 0], pad(16)]
exec.sys::truncate_stack
- # => [count]
+ # => [count, pad(15)]
end
-#! Inputs: []
-#! Outputs: []
-pub proc increment_count
+#! Increments the current count by one.
+#!
+#! Inputs: [pad(16)]
+#! Outputs: [pad(16)]
+#!
+#! Invocation: call
+@account_procedure
+pub proc increment_count()
push.COUNTER_SLOT[0..2] exec.active_account::get_item
- # => [count]
+ # => [[count, 0, 0, 0], pad(16)]
add.1
- # => [count+1]
+ # => [[count + 1, 0, 0, 0], pad(16)]
push.COUNTER_SLOT[0..2] exec.native_account::set_item
- # => []
+ # => [OLD_VALUE, pad(16)]
+
+ dropw
+ # => [pad(16)]
exec.sys::truncate_stack
- # => []
+ # => [pad(16)]
end
```
-### Transaction Script for Deployment
-
-Create `masm/scripts/counter_script.masm`:
-
-```masm
-use external_contract::counter_contract
+### Initial deployment
-begin
- call.counter_contract::increment_count
-end
-```
-
-This script executes a function call (increment) that creates a necessary state change for our contract to be deployed and stored on the network on-chain. In Miden, public contracts must have their state modified through a transaction to be properly registered and committed to the blockchain - simply creating the account isn't sufficient.
+Creating an account locally does not publish it. The funding helper below creates
+the new account's first committed transaction by consuming its native-asset note.
+No separate increment transaction script is needed on a fee-enabled network. The
+empty deployment request shown later is only for networks with zero fees, where
+funding does not perform that initial transaction.
### Network Note for User Interaction
-Create `masm/notes/network_increment_note.masm`. Note scripts are compiled as libraries; the `@note_script` attribute marks the entrypoint procedure.
+The increment note is defined in `masm/notes/network_increment_note.masm`. Note scripts are compiled as libraries; the `@note_script` attribute marks the entrypoint procedure.
```masm
use external_contract::counter_contract
-#! Inputs: []
-#! Outputs: []
+#! Increments the network counter when this note is consumed.
+#!
+#! Inputs: [ARGS, pad(12)]
+#! Outputs: [pad(16)]
+#!
+#! Where:
+#! - ARGS contains unused note script arguments.
+#!
+#! Invocation: dyncall
@note_script
-pub proc main
+pub proc main(args: word)
+ dropw
+ # => [pad(16)]
+
call.counter_contract::increment_count
+ # => [pad(16)]
end
```
@@ -143,69 +173,52 @@ Before deploying the network account and creating network notes, we need to set
Copy and paste the following code into your `src/main.rs` file:
```rust no_run
+use rust_client::TutorialClientExt;
use std::{collections::BTreeSet, path::PathBuf, sync::Arc};
use miden_client::{
+ Client, ClientError, Felt, Word,
account::{
- component::{AccountComponentMetadata, AuthNetworkAccount, BasicWallet}, AccountBuilder, AccountComponent,
- AccountType, StorageSlot, StorageSlotName,
+ AccountBuilder, AccountComponent, AccountType, StorageSlot, StorageSlotName,
+ component::{
+ AccountComponentMetadata, AuthNetworkAccount, BasicConstantFeePolicy, BasicWallet,
+ FeePolicy, FeePolicyManager,
+ },
},
- address::NetworkId,
- auth::{AuthSchemeId, AuthSecretKey, AuthSingleSig},
+ asset::AssetAmount,
+ auth::{AuthSecretKey, AuthSingleSig},
builder::ClientBuilder,
crypto::FeltRng,
keystore::{FilesystemKeyStore, Keystore},
note::{
NetworkAccountTarget, Note, NoteAssets, NoteAttachments, NoteError, NoteExecutionHint,
- NoteRecipient, NoteStorage, NoteTag, NoteType, PartialNoteMetadata,
+ NoteRecipient, NoteStorage, NoteTag, NoteType, P2idNote, PartialNoteMetadata,
},
- rpc::{Endpoint, GrpcClient},
- store::TransactionFilter,
- transaction::{TransactionId, TransactionRequestBuilder, TransactionStatus},
- Client, ClientError, Felt, Word,
+ rpc::{GrpcClient, VerifyingRpcClient},
+ transaction::{ExpirationTransactionScript, TransactionId, TransactionRequestBuilder},
};
use miden_client_sqlite_store::ClientBuilderSqliteExt;
-use rand::RngCore;
-use tokio::time::{sleep, Duration};
+use rand::Rng;
+use rust_client::{FeeConfig, TutorialNetwork, fund_account_for_fees};
+use tokio::time::{Duration, sleep};
/// Waits for a specific transaction to be committed.
async fn wait_for_tx(
client: &mut Client,
tx_id: TransactionId,
) -> Result<(), ClientError> {
- loop {
- client.sync_state().await?;
-
- // Check transaction status
- let txs = client
- .get_transactions(TransactionFilter::Ids(vec![tx_id]))
- .await?;
- let tx_committed = if !txs.is_empty() {
- matches!(txs[0].status, TransactionStatus::Committed { .. })
- } else {
- false
- };
-
- if tx_committed {
- println!("✅ transaction {} committed", tx_id.to_hex());
- break;
- }
-
- println!(
- "Transaction {} not yet committed. Waiting...",
- tx_id.to_hex()
- );
- sleep(Duration::from_secs(2)).await;
- }
- Ok(())
+ rust_client::wait_for_transaction(client, tx_id).await
}
#[tokio::main]
async fn main() -> Result<(), Box> {
// Initialize client
- let endpoint = Endpoint::testnet();
+ let network = TutorialNetwork::from_env()?;
+ let endpoint = network.endpoint();
let timeout_ms = 10_000;
- let rpc_client = Arc::new(GrpcClient::new(&endpoint, timeout_ms));
+ let rpc_client = Arc::new(VerifyingRpcClient::new(GrpcClient::new(
+ &endpoint, timeout_ms,
+ )));
// Initialize keystore
let keystore_path = PathBuf::from("./keystore");
@@ -217,12 +230,13 @@ async fn main() -> Result<(), Box> {
.rpc(rpc_client)
.sqlite_store(store_path)
.authenticator(keystore.clone())
- .in_debug_mode(true.into())
.build()
.await?;
let sync_summary = client.sync_state().await.unwrap();
println!("Latest block: {}", sync_summary.block_num);
+ let fee_config = FeeConfig::from_client(&client, network).await?;
+ let fee_faucet_id = fee_config.native_fee_faucet_id();
// -------------------------------------------------------------------------
// STEP 1: Create Basic User Account
@@ -238,7 +252,7 @@ async fn main() -> Result<(), Box> {
// Build the account
let alice_account = AccountBuilder::new(init_seed)
.account_type(AccountType::Public)
- .with_auth_component(AuthSingleSig::new(key_pair.public_key().to_commitment(), AuthSchemeId::Falcon512Poseidon2))
+ .with_component(AuthSingleSig::from_public_key(key_pair.public_key()))
.with_component(BasicWallet)
.build()
.unwrap();
@@ -247,11 +261,15 @@ async fn main() -> Result<(), Box> {
client.add_account(&alice_account, false).await?;
// Add the key pair to the keystore
- keystore.add_key(&key_pair, alice_account.id()).await.unwrap();
+ keystore
+ .add_key(&key_pair, alice_account.id())
+ .await
+ .unwrap();
+ fund_account_for_fees(&mut client, alice_account.id(), &fee_config).await?;
println!(
"Alice's account ID: {:?}",
- alice_account.id().to_bech32(NetworkId::Testnet)
+ alice_account.id().to_bech32(network.network_id())
);
Ok(())
@@ -262,9 +280,17 @@ This step initializes the Miden client and creates a basic user account (Alice)
## Step 4: Create the network counter smart contract
-Now we'll create the network smart contract. In v0.15 what makes an account network-executable is its auth component, not a storage mode: the contract is a public account (`AccountType::Public`) built with the `AuthNetworkAccount` component. Its note-script allowlist is what marks the account as a network account, and its transaction-script allowlist authorizes the deploy script in Step 5. Both allowlists are fixed at account creation, so we compile the note script and the deploy transaction script first and pass their MAST roots in.
+Build a public account with `AuthNetworkAccount`, the counter component, and
+`BasicWallet`. Compile the increment note first so its root can be allowlisted.
+Also allow the P2ID funding note. Use `AuthNetworkAccount::custom` for this minimal
+account and explicitly allow `ExpirationTransactionScript::script_root()`, which
+the network builder uses. No configuration or sponsorship note scripts are
+enabled: this account does not implement their required authority components.
+There is no custom increment transaction script to allowlist. The zero per-note
+policy charge does not remove the network's verification fee: the account pays
+that fee from its funded native-asset balance.
-Add this code to your `main()` function:
+Insert this code inside `main`, immediately before its final `Ok(())`:
```rust ignore
// -------------------------------------------------------------------------
@@ -272,44 +298,28 @@ Add this code to your `main()` function:
// -------------------------------------------------------------------------
println!("\n[STEP 2] Creating a network counter smart contract");
-// `include_str!` resolves at compile time relative to this source file,
-// so the binary is independent of the working directory it is run from.
-let counter_code = include_str!("../masm/accounts/counter.masm");
-let script_code = include_str!("../masm/scripts/counter_script.masm");
-let network_note_code = include_str!("../masm/notes/network_increment_note.masm");
+// Read the MASM source from the tutorials repository.
+let counter_code = std::fs::read_to_string("../tutorials/masm/accounts/counter.masm").unwrap();
+let network_note_code =
+ std::fs::read_to_string("../tutorials/masm/notes/network_increment_note.masm").unwrap();
-// In protocol v0.15 an account is a *network account* (one the network
+// An account is a *network account* (one the network
// transaction builder executes on a user's behalf) if and only if it is
// public AND carries the `AuthNetworkAccount` auth component. That component
-// holds two allowlists, both fixed at account creation:
-// * the note-script allowlist: its presence is what marks the account as a
-// network account, and the builder only executes notes whose script root
-// is listed here;
-// * the tx-script allowlist: the network auth procedure rejects any custom
-// tx script whose root is not listed, so the STEP 3 deploy script must be
-// in it.
-// We therefore compile the note script and the deploy tx script now and feed
-// their MAST roots into the allowlists below. Both compiled scripts are reused
-// as-is in STEP 3 (tx script) and STEP 4 (note script) — nothing is compiled
-// twice.
+// holds an allowlist of note scripts the network builder may execute.
+// Compile the increment note first so its root can be included at creation.
let note_script = client
.code_builder()
- .with_linked_module("external_contract::counter_contract", counter_code)?
- .compile_note_script(network_note_code)?;
+ .with_linked_module("external_contract::counter_contract", &counter_code)?
+ .compile_note_script(&network_note_code)?;
let note_script_root = note_script.root();
-let tx_script = client
- .code_builder()
- .with_linked_module("external_contract::counter_contract", counter_code)?
- .compile_tx_script(script_code)?;
-let tx_script_root = tx_script.root();
-
// Compile the counter MASM into an account component
let counter_slot_name =
StorageSlotName::new("miden::tutorials::counter").expect("valid slot name");
let component_code = client
.code_builder()
- .compile_component_code("external_contract::counter_contract", counter_code)?;
+ .compile_component_code("external_contract::counter_contract", &counter_code)?;
let counter_component = AccountComponent::new(
component_code,
vec![StorageSlot::with_value(
@@ -323,68 +333,83 @@ let counter_component = AccountComponent::new(
let mut init_seed = [0_u8; 32];
client.rng().fill_bytes(&mut init_seed);
-// Build the network account: public + `AuthNetworkAccount` with the note-script
-// root allowlisted (this is what makes it a network account) and the deploy
-// tx-script root allowlisted (so the auth procedure accepts the STEP 3 deploy).
-let network_auth = AuthNetworkAccount::with_allowed_notes(BTreeSet::from([note_script_root]))?
- .with_allowed_tx_scripts(BTreeSet::from([tx_script_root]));
+// Build the public network account with the increment and funding notes allowed.
+let fee_policy: FeePolicy = BasicConstantFeePolicy::new()
+ .with_fees(
+ [note_script_root, P2idNote::script_root()].map(|root| (root, AssetAmount::ZERO)),
+ )
+ .into();
+let fee_policy_manager = FeePolicyManager::builder()
+ .fee_faucet_id(fee_faucet_id)
+ .active_fee_policy(fee_policy)
+ .build();
+// Match the protocol/node counter example: only permit the two note scripts
+// this account implements. Config notes need Authority, which it does not have.
+// The canonical expiration script is required by the network builder.
+let network_auth = AuthNetworkAccount::custom(
+ BTreeSet::from([note_script_root, P2idNote::script_root()]),
+ fee_policy_manager,
+)?
+.with_allowed_tx_scripts([ExpirationTransactionScript::script_root()]);
let counter_contract = AccountBuilder::new(init_seed)
.account_type(AccountType::Public)
- .with_auth_component(network_auth)
+ .with_components(network_auth)
.with_component(counter_component)
+ .with_component(BasicWallet)
.build()
.unwrap();
client.add_account(&counter_contract, false).await.unwrap();
+fund_account_for_fees(&mut client, counter_contract.id(), &fee_config).await?;
println!(
"contract id: {:?}",
- counter_contract.id().to_bech32(NetworkId::Testnet)
+ counter_contract.id().to_bech32(network.network_id())
);
```
-This step creates a public smart contract (`AccountType::Public`) whose `AuthNetworkAccount` component allowlists the increment note's script root — marking it as a network account the operator will execute — and the deploy transaction script's root, so the Step 5 deploy is authorized.
+This step creates and funds a public network account. On a fee-enabled network,
+the funding transaction also publishes it. Its counter remains zero.
-## Step 5: Deploy the network account with a transaction script
+## Step 5: Confirm publication of the network account
-We use a transaction script to deploy the network account and ensure it's properly registered on-chain. The script calls the `increment` function, which initializes the counter to 1.
+When fees are active, initial funding has already published the network account. Do not send
+another direct increment transaction from the user client: the node rejects
+user-submitted updates to existing network accounts. Only the zero-fee path needs
+an explicit first deployment transaction here.
-Add this code to your `main()` function:
+Insert this code after account creation, inside `main` and before `Ok(())`:
```rust ignore
// -------------------------------------------------------------------------
-// STEP 3: Deploy Network Account with Transaction Script
+// STEP 3: Publish the network account
// -------------------------------------------------------------------------
println!("\n[STEP 3] Deploy network counter smart contract");
-// Reuse the `tx_script` compiled in STEP 2 (its root is allowlisted on the
-// account, so the network auth procedure accepts this deploy transaction).
-let tx_increment_request = TransactionRequestBuilder::new()
- .custom_script(tx_script)
- .build()
- .unwrap();
-
-let tx_id = client
- .submit_new_transaction(counter_contract.id(), tx_increment_request)
- .await
- .unwrap();
-
-println!(
- "View transaction on MidenScan: https://testnet.midenscan.com/tx/{:?}",
- tx_id
-);
-
-// Wait for the transaction to be committed
-wait_for_tx(&mut client, tx_id).await.unwrap();
+// On a fee-enabled network, consuming the funding note already published this
+// account. RPC permits users to deploy new network accounts, but rejects
+// user-submitted transactions for existing ones. Subsequent increments must
+// be requested by notes and executed by the network transaction builder.
+if !fee_config.fees_are_active() {
+ let deployment = TransactionRequestBuilder::new().build()?;
+ client
+ .submit_tutorial_transaction(counter_contract.id(), deployment)
+ .await?;
+}
+println!("Network counter deployed; initial count is 0");
```
-This step uses a transaction script to deploy the network account and ensure it's properly registered on-chain. The script calls the `increment` function, which initializes the counter to 1.
+The initial committed count is zero. All later counter updates go through network
+notes executed by the network transaction builder.
## Step 6: Create a network note for user interaction
-We create a public note that the network operator can consume to execute the increment function. This increments the counter from 1 to 2.
+Alice publishes a public increment note from her own funded account. The network
+transaction builder then consumes that note on the counter's behalf, changing
+the counter from zero to one. Confirmation of Alice's transaction alone is not
+enough; the example also waits for the counter's updated state.
-Add this code to your `main()` function:
+Replace the final `Ok(())` in `main` with the following fragment. Its polling loop and final `Err(...)` expressions provide the function's result:
```rust ignore
// -------------------------------------------------------------------------
@@ -420,11 +445,12 @@ let note_req = TransactionRequestBuilder::new()
.build()?;
let note_tx_id = client
- .submit_new_transaction(alice_account.id(), note_req)
+ .submit_tutorial_transaction(alice_account.id(), note_req)
.await?;
println!(
- "View transaction on MidenScan: https://testnet.midenscan.com/tx/{:?}",
+ "View transaction on MidenScan: {}/tx/{:?}",
+ network.explorer_url(),
note_tx_id
);
@@ -439,7 +465,7 @@ wait_for_tx(&mut client, note_tx_id).await.unwrap();
sleep(Duration::from_secs(6)).await;
let mut last_val = None;
-for _ in 0..10 {
+for _ in 0..24 {
client.sync_state().await?;
// Checking updated state
@@ -452,7 +478,7 @@ for _ in 0..10 {
.unwrap()
.into();
let val = count[0].as_canonical_u64();
- if val >= 2 {
+ if val == 1 {
println!("🔢 Final counter value: {}", val);
return Ok(());
}
@@ -464,12 +490,12 @@ for _ in 0..10 {
}
// The network note was submitted, but it is executed asynchronously by the
-// network transaction builder. If the counter has not reached 2 within the
+// network transaction builder. If the counter has not reached 1 within the
// polling window, the tutorial's final state is unconfirmed, so fail rather
// than claim success.
if let Some(val) = last_val {
Err(format!(
- "Counter did not reach the expected value 2 within the timeout (last observed {}). \
+ "Counter did not reach the expected value 1 within the timeout (last observed {}). \
The network note was submitted but its execution is still pending on the network \
transaction builder; re-run or check Midenscan.",
val
@@ -481,78 +507,57 @@ if let Some(val) = last_val {
}
```
-This step creates a public note that the network operator can consume to execute the increment function. This increments the counter from 1 to 2.
+## Complete example
-## Summary
-
-Your complete `main()` function should look like this:
+Your complete `src/main.rs` file should look like this:
```rust no_run
+use rust_client::TutorialClientExt;
use std::{collections::BTreeSet, path::PathBuf, sync::Arc};
use miden_client::{
+ Client, ClientError, Felt, Word,
account::{
- component::{AccountComponentMetadata, AuthNetworkAccount, BasicWallet}, AccountBuilder, AccountComponent,
- AccountType, StorageSlot, StorageSlotName,
+ AccountBuilder, AccountComponent, AccountType, StorageSlot, StorageSlotName,
+ component::{
+ AccountComponentMetadata, AuthNetworkAccount, BasicConstantFeePolicy, BasicWallet,
+ FeePolicy, FeePolicyManager,
+ },
},
- address::NetworkId,
- auth::{AuthSchemeId, AuthSecretKey, AuthSingleSig},
+ asset::AssetAmount,
+ auth::{AuthSecretKey, AuthSingleSig},
builder::ClientBuilder,
crypto::FeltRng,
keystore::{FilesystemKeyStore, Keystore},
note::{
NetworkAccountTarget, Note, NoteAssets, NoteAttachments, NoteError, NoteExecutionHint,
- NoteRecipient, NoteStorage, NoteTag, NoteType, PartialNoteMetadata,
- },
- rpc::{Endpoint, GrpcClient},
- store::TransactionFilter,
- transaction::{
- TransactionId, TransactionRequestBuilder, TransactionStatus,
+ NoteRecipient, NoteStorage, NoteTag, NoteType, P2idNote, PartialNoteMetadata,
},
- Client, ClientError, Felt, Word,
+ rpc::{GrpcClient, VerifyingRpcClient},
+ transaction::{ExpirationTransactionScript, TransactionId, TransactionRequestBuilder},
};
use miden_client_sqlite_store::ClientBuilderSqliteExt;
-use rand::RngCore;
-use tokio::time::{sleep, Duration};
+use rand::Rng;
+use rust_client::{FeeConfig, TutorialNetwork, fund_account_for_fees};
+use tokio::time::{Duration, sleep};
/// Waits for a specific transaction to be committed.
async fn wait_for_tx(
client: &mut Client,
tx_id: TransactionId,
) -> Result<(), ClientError> {
- loop {
- client.sync_state().await?;
-
- // Check transaction status
- let txs = client
- .get_transactions(TransactionFilter::Ids(vec![tx_id]))
- .await?;
- let tx_committed = if !txs.is_empty() {
- matches!(txs[0].status, TransactionStatus::Committed { .. })
- } else {
- false
- };
-
- if tx_committed {
- println!("✅ transaction {} committed", tx_id.to_hex());
- break;
- }
-
- println!(
- "Transaction {} not yet committed. Waiting...",
- tx_id.to_hex()
- );
- sleep(Duration::from_secs(2)).await;
- }
- Ok(())
+ rust_client::wait_for_transaction(client, tx_id).await
}
#[tokio::main]
async fn main() -> Result<(), Box> {
// Initialize client
- let endpoint = Endpoint::testnet();
+ let network = TutorialNetwork::from_env()?;
+ let endpoint = network.endpoint();
let timeout_ms = 10_000;
- let rpc_client = Arc::new(GrpcClient::new(&endpoint, timeout_ms));
+ let rpc_client = Arc::new(VerifyingRpcClient::new(GrpcClient::new(
+ &endpoint, timeout_ms,
+ )));
// Initialize keystore
let keystore_path = PathBuf::from("./keystore");
@@ -564,12 +569,13 @@ async fn main() -> Result<(), Box> {
.rpc(rpc_client)
.sqlite_store(store_path)
.authenticator(keystore.clone())
- .in_debug_mode(true.into())
.build()
.await?;
let sync_summary = client.sync_state().await.unwrap();
println!("Latest block: {}", sync_summary.block_num);
+ let fee_config = FeeConfig::from_client(&client, network).await?;
+ let fee_faucet_id = fee_config.native_fee_faucet_id();
// -------------------------------------------------------------------------
// STEP 1: Create Basic User Account
@@ -585,7 +591,7 @@ async fn main() -> Result<(), Box> {
// Build the account
let alice_account = AccountBuilder::new(init_seed)
.account_type(AccountType::Public)
- .with_auth_component(AuthSingleSig::new(key_pair.public_key().to_commitment(), AuthSchemeId::Falcon512Poseidon2))
+ .with_component(AuthSingleSig::from_public_key(key_pair.public_key()))
.with_component(BasicWallet)
.build()
.unwrap();
@@ -594,11 +600,15 @@ async fn main() -> Result<(), Box> {
client.add_account(&alice_account, false).await?;
// Add the key pair to the keystore
- keystore.add_key(&key_pair, alice_account.id()).await.unwrap();
+ keystore
+ .add_key(&key_pair, alice_account.id())
+ .await
+ .unwrap();
+ fund_account_for_fees(&mut client, alice_account.id(), &fee_config).await?;
println!(
"Alice's account ID: {:?}",
- alice_account.id().to_bech32(NetworkId::Testnet)
+ alice_account.id().to_bech32(network.network_id())
);
// -------------------------------------------------------------------------
@@ -606,44 +616,28 @@ async fn main() -> Result<(), Box> {
// -------------------------------------------------------------------------
println!("\n[STEP 2] Creating a network counter smart contract");
- // `include_str!` resolves at compile time relative to this source file,
- // so the binary is independent of the working directory it is run from.
- let counter_code = include_str!("../masm/accounts/counter.masm");
- let script_code = include_str!("../masm/scripts/counter_script.masm");
- let network_note_code = include_str!("../masm/notes/network_increment_note.masm");
+ // Read the MASM source from the tutorials repository.
+ let counter_code = std::fs::read_to_string("../tutorials/masm/accounts/counter.masm").unwrap();
+ let network_note_code =
+ std::fs::read_to_string("../tutorials/masm/notes/network_increment_note.masm").unwrap();
- // In protocol v0.15 an account is a *network account* (one the network
+ // An account is a *network account* (one the network
// transaction builder executes on a user's behalf) if and only if it is
// public AND carries the `AuthNetworkAccount` auth component. That component
- // holds two allowlists, both fixed at account creation:
- // * the note-script allowlist: its presence is what marks the account as a
- // network account, and the builder only executes notes whose script root
- // is listed here;
- // * the tx-script allowlist: the network auth procedure rejects any custom
- // tx script whose root is not listed, so the STEP 3 deploy script must be
- // in it.
- // We therefore compile the note script and the deploy tx script now and feed
- // their MAST roots into the allowlists below. Both compiled scripts are reused
- // as-is in STEP 3 (tx script) and STEP 4 (note script) — nothing is compiled
- // twice.
+ // holds an allowlist of note scripts the network builder may execute.
+ // Compile the increment note first so its root can be included at creation.
let note_script = client
.code_builder()
- .with_linked_module("external_contract::counter_contract", counter_code)?
- .compile_note_script(network_note_code)?;
+ .with_linked_module("external_contract::counter_contract", &counter_code)?
+ .compile_note_script(&network_note_code)?;
let note_script_root = note_script.root();
- let tx_script = client
- .code_builder()
- .with_linked_module("external_contract::counter_contract", counter_code)?
- .compile_tx_script(script_code)?;
- let tx_script_root = tx_script.root();
-
// Compile the counter MASM into an account component
let counter_slot_name =
StorageSlotName::new("miden::tutorials::counter").expect("valid slot name");
let component_code = client
.code_builder()
- .compile_component_code("external_contract::counter_contract", counter_code)?;
+ .compile_component_code("external_contract::counter_contract", &counter_code)?;
let counter_component = AccountComponent::new(
component_code,
vec![StorageSlot::with_value(
@@ -657,49 +651,56 @@ async fn main() -> Result<(), Box> {
let mut init_seed = [0_u8; 32];
client.rng().fill_bytes(&mut init_seed);
- // Build the network account: public + `AuthNetworkAccount` with the note-script
- // root allowlisted (this is what makes it a network account) and the deploy
- // tx-script root allowlisted (so the auth procedure accepts the STEP 3 deploy).
- let network_auth = AuthNetworkAccount::with_allowed_notes(BTreeSet::from([note_script_root]))?
- .with_allowed_tx_scripts(BTreeSet::from([tx_script_root]));
+ // Build the public network account with the increment and funding notes allowed.
+ let fee_policy: FeePolicy = BasicConstantFeePolicy::new()
+ .with_fees(
+ [note_script_root, P2idNote::script_root()].map(|root| (root, AssetAmount::ZERO)),
+ )
+ .into();
+ let fee_policy_manager = FeePolicyManager::builder()
+ .fee_faucet_id(fee_faucet_id)
+ .active_fee_policy(fee_policy)
+ .build();
+ // Match the protocol/node counter example: only permit the two note scripts
+ // this account implements. Config notes need Authority, which it does not have.
+ // The canonical expiration script is required by the network builder.
+ let network_auth = AuthNetworkAccount::custom(
+ BTreeSet::from([note_script_root, P2idNote::script_root()]),
+ fee_policy_manager,
+ )?
+ .with_allowed_tx_scripts([ExpirationTransactionScript::script_root()]);
let counter_contract = AccountBuilder::new(init_seed)
.account_type(AccountType::Public)
- .with_auth_component(network_auth)
+ .with_components(network_auth)
.with_component(counter_component)
+ .with_component(BasicWallet)
.build()
.unwrap();
client.add_account(&counter_contract, false).await.unwrap();
+ fund_account_for_fees(&mut client, counter_contract.id(), &fee_config).await?;
println!(
"contract id: {:?}",
- counter_contract.id().to_bech32(NetworkId::Testnet)
+ counter_contract.id().to_bech32(network.network_id())
);
// -------------------------------------------------------------------------
- // STEP 3: Deploy Network Account with Transaction Script
+ // STEP 3: Publish the network account
// -------------------------------------------------------------------------
println!("\n[STEP 3] Deploy network counter smart contract");
- // Reuse the `tx_script` compiled in STEP 2 (its root is allowlisted on the
- // account, so the network auth procedure accepts this deploy transaction).
- let tx_increment_request = TransactionRequestBuilder::new()
- .custom_script(tx_script)
- .build()
- .unwrap();
-
- let tx_id = client
- .submit_new_transaction(counter_contract.id(), tx_increment_request)
- .await
- .unwrap();
-
- println!(
- "View transaction on MidenScan: https://testnet.midenscan.com/tx/{:?}",
- tx_id
- );
-
- // Wait for the transaction to be committed
- wait_for_tx(&mut client, tx_id).await.unwrap();
+ // On a fee-enabled network, consuming the funding note already published this
+ // account. RPC permits users to deploy new network accounts, but rejects
+ // user-submitted transactions for existing ones. Subsequent increments must
+ // be requested by notes and executed by the network transaction builder.
+ if !fee_config.fees_are_active() {
+ let deployment = TransactionRequestBuilder::new().build()?;
+ client
+ .submit_tutorial_transaction(counter_contract.id(), deployment)
+ .await?;
+ }
+ println!("Network counter deployed; initial count is 0");
// -------------------------------------------------------------------------
// STEP 4: Prepare & Create the Network Note
@@ -734,11 +735,12 @@ async fn main() -> Result<(), Box> {
.build()?;
let note_tx_id = client
- .submit_new_transaction(alice_account.id(), note_req)
+ .submit_tutorial_transaction(alice_account.id(), note_req)
.await?;
println!(
- "View transaction on MidenScan: https://testnet.midenscan.com/tx/{:?}",
+ "View transaction on MidenScan: {}/tx/{:?}",
+ network.explorer_url(),
note_tx_id
);
@@ -753,7 +755,7 @@ async fn main() -> Result<(), Box> {
sleep(Duration::from_secs(6)).await;
let mut last_val = None;
- for _ in 0..10 {
+ for _ in 0..24 {
client.sync_state().await?;
// Checking updated state
@@ -766,7 +768,7 @@ async fn main() -> Result<(), Box> {
.unwrap()
.into();
let val = count[0].as_canonical_u64();
- if val >= 2 {
+ if val == 1 {
println!("🔢 Final counter value: {}", val);
return Ok(());
}
@@ -778,12 +780,12 @@ async fn main() -> Result<(), Box> {
}
// The network note was submitted, but it is executed asynchronously by the
- // network transaction builder. If the counter has not reached 2 within the
+ // network transaction builder. If the counter has not reached 1 within the
// polling window, the tutorial's final state is unconfirmed, so fail rather
// than claim success.
if let Some(val) = last_val {
Err(format!(
- "Counter did not reach the expected value 2 within the timeout (last observed {}). \
+ "Counter did not reach the expected value 1 within the timeout (last observed {}). \
The network note was submitted but its execution is still pending on the network \
transaction builder; re-run or check Midenscan.",
val
@@ -798,38 +800,33 @@ async fn main() -> Result<(), Box> {
## Step 7: Running the Example
-To run the complete network transaction example:
+For the standalone Cargo project, run `TUTORIAL_NETWORK=testnet cargo run --release` from `miden-network-transactions`.
+
+To run the checked-in example from the repository root:
```bash
cd rust-client
-cargo run --release --bin network_notes_counter_contract
+TUTORIAL_NETWORK=testnet cargo run --release --bin network_notes_counter_contract
```
-Expected output:
+Successful output has this shape (abridged; IDs and block numbers vary):
```text
-Latest block: 486537
+Latest block:
[STEP 1] Creating a new account for Alice
-Alice's account ID: "mtst1aqcmenmxlqkw8ugn005s7r09kq0xpqp9"
+Alice's account ID: ""
[STEP 2] Creating a network counter smart contract
-one or more warnings were emitted
-one or more warnings were emitted
-one or more warnings were emitted
-contract id: "mtst1aqtvag789v45tvtqdaknugytf5d5gxxu"
+contract id: ""
[STEP 3] Deploy network counter smart contract
-View transaction on MidenScan: https://testnet.midenscan.com/tx/0x37c202efb825f0c7a55bd4416858fbe2d30064a5b1e557876a0053ed5307607f
-Transaction 0x37c202efb825f0c7a55bd4416858fbe2d30064a5b1e557876a0053ed5307607f not yet committed. Waiting...
-✅ transaction 0x37c202efb825f0c7a55bd4416858fbe2d30064a5b1e557876a0053ed5307607f committed
+Network counter deployed; initial count is 0
[STEP 4] Creating a network note for network counter contract
-View transaction on MidenScan: https://testnet.midenscan.com/tx/0xa5ec0baa9443a0f5aa056bd4c0e4583c5c3f5a5163aa0536b469a0255b489f75
+View transaction on MidenScan: https://testnet.midenscan.com/tx/
network increment note creation tx submitted, waiting for onchain commitment
-Transaction 0xa5ec0baa9443a0f5aa056bd4c0e4583c5c3f5a5163aa0536b469a0255b489f75 not yet committed. Waiting...
-✅ transaction 0xa5ec0baa9443a0f5aa056bd4c0e4583c5c3f5a5163aa0536b469a0255b489f75 committed
-🔢 Final counter value: 2
+🔢 Final counter value: 1
```
## Summary
@@ -837,8 +834,8 @@ Transaction 0xa5ec0baa9443a0f5aa056bd4c0e4583c5c3f5a5163aa0536b469a0255b489f75 n
Network transactions on Miden enable powerful use cases by allowing the operator to execute transactions on behalf of users. The key steps are:
1. **Create user account**: Standard account creation for interaction
-2. **Create network account**: Build a public account (`AccountType::Public`) with the `AuthNetworkAccount` auth component, allowlisting the note-script root (this is what marks it as a network account) and the deploy transaction-script root
-3. **Deploy with transaction script**: Ensures the contract is registered on-chain
+2. **Create network account**: Build a public account with `AuthNetworkAccount`, allowlisting the increment and funding note scripts
+3. **Publish and fund it**: The initial native-asset consumption transaction registers the new account with count zero
4. **Interact with network notes**: Users create public notes that the operator executes
The same MASM code works for both regular and network contracts — the difference is purely in the Rust configuration (the `AuthNetworkAccount` auth component and its allowlists). This makes network transactions a powerful tool for building applications like AMMs where multiple users need to interact with shared state efficiently.
diff --git a/docs/src/rust-client/oracle_tutorial.md b/docs/src/rust-client/oracle_tutorial.md
index 865d99bc..c51119f8 100644
--- a/docs/src/rust-client/oracle_tutorial.md
+++ b/docs/src/rust-client/oracle_tutorial.md
@@ -7,72 +7,90 @@ sidebar_position: 13
_Using the Pragma oracle to get on chain price data_
+For toolchain requirements and shared fee helpers, see the [Rust client setup](./index.md#running-the-v016-examples).
+
## Overview
In this tutorial, we will build a simple “price reader” smart contract that will read Bitcoin price data from the on-chain Pragma oracle.
-We will use a script to call the `read_price` function in our "price reader" smart contract, which, in turn, calls the Pragma oracle via foreign procedure invocation (FPI). This tutorial lays the foundation for how you can integrate on-chain price data into your DeFi applications on Miden.
+We will use a script to call the `get_price` procedure in our reader account, which invokes Pragma through foreign procedure invocation (FPI). This example demonstrates the call plumbing and discards the returned price. An application would need to validate and use the result.
## What we'll cover
- Deploying a smart contract that can read oracle price data
-- Using foreign procedure invocation to get real time on-chain price data
+- Using foreign procedure invocation to query published on-chain price data
## Prerequisites
+:::warning Deployment required
+
+Pragma's [published deployment table](https://github.com/astraly-labs/pragma-miden#deployments)
+lists Miden v0.15 testnet only. Running this example requires a compatible v0.16
+testnet deployment, its account ID, `get_median` procedure root, and pair identifiers.
+
+The reader assumes the named slots `pragma::oracle::next_publisher_index`,
+`pragma::oracle::publishers`, and `pragma::publisher::entries`. Check these slots,
+the publisher ID layout and index range, and the return values against that deployment.
+
+:::
+
This tutorial assumes you have a basic understanding of Miden assembly, have completed the previous tutorials on using the Rust client, and have completed the tutorial on foreign procedure invocation.
To quickly get up to speed with Miden assembly (MASM), please play around with running Miden programs in the [Miden playground](https://0xMiden.github.io/examples/).
## Step 1: Initialize your repository
-Create a new Rust repository for your Miden project and navigate to it with the following command:
+From the parent directory of your `tutorials` clone, create a sibling Cargo project:
```bash
cargo new miden-defi-app
cd miden-defi-app
+rustup override set 1.98.1
+cp ../tutorials/rust-client/Cargo.lock Cargo.lock
```
Add the following dependencies to your `Cargo.toml` file:
```toml
[dependencies]
-miden-client = { version = "0.15", features = ["testing", "tonic"] }
-miden-client-sqlite-store = { version = "0.15", package = "miden-client-sqlite-store" }
-miden-protocol = { version = "0.15" }
-rand = { version = "0.9" }
+# Clone tutorials next to this Cargo project (see Rust client setup).
+rust-client = { path = "../tutorials/rust-client" }
+miden-client = { version = "=0.16.0", features = ["testing", "tonic"] }
+miden-client-sqlite-store = { version = "=0.16.0", package = "miden-client-sqlite-store" }
+miden-protocol = { version = "=0.16.0" }
+rand = { version = "0.10" }
serde = { version = "1", features = ["derive"] }
serde_json = { version = "1.0", features = ["raw_value"] }
-tokio = { version = "1.46", features = ["rt-multi-thread", "net", "macros", "fs"] }
-rand_chacha = "0.9.0"
+tokio = { version = "1.48", features = ["rt-multi-thread", "net", "macros", "fs"] }
+
+[profile.dev]
+opt-level = 2
```
-### Step 1: Set up your `src/main.rs` file
+### Set up your `src/main.rs` file
Copy and paste the following code into your `src/main.rs` file:
```rust no_run
use miden_client::{
+ Client, ClientError, Felt, Word, ZERO,
account::{
- component::AccountComponentMetadata, AccountBuilder, AccountComponent, AccountId,
- AccountType, StorageMapKey, StorageSlot, StorageSlotName,
- },
- assembly::{
- CodeBuilder, DefaultSourceManager, Module, ModuleKind, Path as AssemblyPath,
+ AccountBuilder, AccountComponent, AccountId, AccountType, StorageMapKey, StorageSlot,
+ StorageSlotName,
+ component::{AccountComponentMetadata, BasicWallet},
},
+ assembly::CodeBuilder,
auth::NoAuth,
builder::ClientBuilder,
keystore::FilesystemKeyStore,
- rpc::{
- domain::account::AccountStorageRequirements,
- Endpoint, GrpcClient,
- },
- transaction::{ForeignAccount, TransactionKernel, TransactionRequestBuilder},
- Client, ClientError, Felt, Word, ZERO,
+ rpc::{GrpcClient, VerifyingRpcClient, domain::account::AccountStorageRequirements},
+ transaction::{ForeignAccount, TransactionRequestBuilder},
};
use miden_client_sqlite_store::ClientBuilderSqliteExt;
-use rand::RngCore;
-use std::{fs, path::Path, sync::Arc};
+use rand::Rng;
+use rust_client::TutorialClientExt;
+use rust_client::{FeeConfig, TutorialNetwork, fund_account_for_fees};
+use std::sync::Arc;
/// Import the oracle + its publishers and return the ForeignAccount list
/// Due to Pragma's decentralized oracle architecture, we need to get the
@@ -107,7 +125,7 @@ pub async fn get_oracle_foreign_accounts(
StorageSlotName::new("pragma::oracle::publishers").expect("valid slot name");
let publisher_ids: Vec = (2..next_publisher_index)
.map(|index| {
- let key: Word = [Felt::new_unchecked(index), ZERO, ZERO, ZERO].into();
+ let key = StorageMapKey::new([Felt::new_unchecked(index), ZERO, ZERO, ZERO].into());
let publisher_word = storage
.get_map_item(&publishers_slot, key)
.expect("publisher entry missing from oracle storage");
@@ -118,8 +136,7 @@ pub async fn get_oracle_foreign_accounts(
// Each publisher exposes its price entries in the `entries` map, keyed by
// the faucet ID word of the trading pair.
- let entries_slot =
- StorageSlotName::new("pragma::publisher::entries").expect("valid slot name");
+ let entries_slot = StorageSlotName::new("pragma::publisher::entries").expect("valid slot name");
let mut foreign_accounts = Vec::with_capacity(publisher_ids.len() + 1);
for publisher_id in publisher_ids {
@@ -149,29 +166,17 @@ pub async fn get_oracle_foreign_accounts(
Ok(foreign_accounts)
}
-fn create_library(
- library_path: &str,
- source_code: &str,
-) -> Result, Box> {
- let source_manager = Arc::new(DefaultSourceManager::default());
- let assembler = TransactionKernel::assembler_with_source_manager(source_manager.clone());
- let module = Module::parser(ModuleKind::Library).parse_str(
- AssemblyPath::new(library_path),
- source_code,
- source_manager,
- )?;
- let library = assembler.assemble_library([module])?;
- Ok(library)
-}
-
#[tokio::main]
async fn main() -> Result<(), ClientError> {
// -------------------------------------------------------------------------
// Initialize Client
// -------------------------------------------------------------------------
- let endpoint = Endpoint::testnet();
+ let network = TutorialNetwork::from_env()?;
+ let endpoint = network.endpoint();
let timeout_ms = 10_000;
- let rpc_client = Arc::new(GrpcClient::new(&endpoint, timeout_ms));
+ let rpc_client = Arc::new(VerifyingRpcClient::new(GrpcClient::new(
+ &endpoint, timeout_ms,
+ )));
let keystore_path = std::path::PathBuf::from("./keystore");
let keystore = Arc::new(FilesystemKeyStore::new(keystore_path).unwrap());
@@ -182,30 +187,55 @@ async fn main() -> Result<(), ClientError> {
.rpc(rpc_client)
.sqlite_store(store_path)
.authenticator(keystore.clone())
- .in_debug_mode(true.into())
.build()
.await?;
println!("Latest block: {}", client.sync_state().await?.block_num);
+ let fee_config = FeeConfig::from_client(&client, network).await?;
// -------------------------------------------------------------------------
// Get all foreign accounts for oracle data
// -------------------------------------------------------------------------
- // Defaults to Pragma's current Miden v0.15 testnet oracle; pass a different
- // bech32 id as the first CLI argument to point at another deployment. Pragma's
- // addresses change between testnet iterations, so check their README
- // (https://github.com/astraly-labs/pragma-miden) if this feed stops resolving.
+ // Pass a compatible oracle account ID and its `get_median` procedure root as CLI
+ // arguments (or through the matching environment variables). This tutorial remains skipped
+ // by the runner until Pragma publishes a deployment for the current protocol release.
let oracle_bech32 = std::env::args()
.nth(1)
- .unwrap_or_else(|| "mtst1apadf2szkxqkcyt7x2znuggv9qkhccam".to_string());
- let (_, oracle_account_id) = AccountId::from_bech32(&oracle_bech32).unwrap();
+ .or_else(|| std::env::var("MIDEN_ORACLE_ACCOUNT_ID").ok())
+ .ok_or_else(|| ClientError::Observer(Box::new(std::io::Error::other(
+ "Oracle deployment is required: set MIDEN_ORACLE_ACCOUNT_ID and MIDEN_ORACLE_GET_MEDIAN_ROOT for the selected network. Use a compatible v0.16 deployment on the selected network.",
+ ))))?;
+ let get_median_proc_root = std::env::args()
+ .nth(2)
+ .or_else(|| std::env::var("MIDEN_ORACLE_GET_MEDIAN_ROOT").ok())
+ .ok_or_else(|| {
+ ClientError::Observer(Box::new(std::io::Error::other(
+ "Set MIDEN_ORACLE_GET_MEDIAN_ROOT to the deployed oracle's get_median procedure root",
+ )))
+ })?;
+ let (account_network, oracle_account_id) = AccountId::from_bech32(&oracle_bech32).unwrap();
+ assert_eq!(
+ account_network,
+ network.network_id(),
+ "oracle account must match the selected tutorial network"
+ );
- // BTC/USD is identified by the faucet ID pair `1:0` (prefix 1, suffix 0).
+ // BTC/USD was identified by the faucet ID pair `1:0` in the previous deployment. Override
+ // either value with the optional third and fourth CLI arguments for the selected deployment.
// The faucet ID word is laid out as [0, 0, suffix, prefix].
- let pair_prefix: u64 = 1;
- let pair_suffix: u64 = 0;
- let btc_usd_pair: Word =
- [ZERO, ZERO, Felt::new_unchecked(pair_suffix), Felt::new_unchecked(pair_prefix)].into();
+ let pair_prefix: u64 = std::env::args()
+ .nth(3)
+ .map_or(1, |value| value.parse().expect("pair prefix must be a u64"));
+ let pair_suffix: u64 = std::env::args()
+ .nth(4)
+ .map_or(0, |value| value.parse().expect("pair suffix must be a u64"));
+ let btc_usd_pair: Word = [
+ ZERO,
+ ZERO,
+ Felt::new_unchecked(pair_suffix),
+ Felt::new_unchecked(pair_prefix),
+ ]
+ .into();
let foreign_accounts: Vec =
get_oracle_foreign_accounts(&mut client, oracle_account_id, btc_usd_pair).await?;
@@ -218,8 +248,19 @@ async fn main() -> Result<(), ClientError> {
// -------------------------------------------------------------------------
// Create Oracle Reader contract
// -------------------------------------------------------------------------
- let contract_code =
- fs::read_to_string(Path::new("../masm/accounts/oracle_reader.masm")).unwrap();
+ let contract_code = std::fs::read_to_string("../tutorials/masm/accounts/oracle_reader.masm")
+ .unwrap()
+ .replace("{get_median_proc_root}", &get_median_proc_root)
+ .replace(
+ "{oracle_id_prefix}",
+ &oracle_account_id.prefix().to_string(),
+ )
+ .replace(
+ "{oracle_id_suffix}",
+ &oracle_account_id.suffix().to_string(),
+ )
+ .replace("{pair_prefix}", &pair_prefix.to_string())
+ .replace("{pair_suffix}", &pair_suffix.to_string());
let contract_slot_name =
StorageSlotName::new("miden::tutorials::oracle_reader").expect("valid slot name");
@@ -242,7 +283,8 @@ async fn main() -> Result<(), ClientError> {
let oracle_reader_contract = AccountBuilder::new(seed)
.account_type(AccountType::Public)
.with_component(contract_component.clone())
- .with_auth_component(NoAuth)
+ .with_component(BasicWallet)
+ .with_component(NoAuth)
.build()
.unwrap();
@@ -250,20 +292,17 @@ async fn main() -> Result<(), ClientError> {
.add_account(&oracle_reader_contract, false)
.await
.unwrap();
+ fund_account_for_fees(&mut client, oracle_reader_contract.id(), &fee_config).await?;
// -------------------------------------------------------------------------
// Build the script that calls our `get_price` procedure
// -------------------------------------------------------------------------
- let script_path = Path::new("../masm/scripts/oracle_reader_script.masm");
- let script_code = fs::read_to_string(script_path).unwrap();
-
- let library_path = "external_contract::oracle_reader";
- let account_component_lib =
- create_library(library_path, &contract_code).unwrap();
+ let script_code =
+ std::fs::read_to_string("../tutorials/masm/scripts/oracle_reader_script.masm").unwrap();
let tx_script = client
.code_builder()
- .with_dynamically_linked_library(&account_component_lib)
+ .with_linked_module("external_contract::oracle_reader", &contract_code)
.unwrap()
.compile_tx_script(&script_code)
.unwrap();
@@ -275,12 +314,13 @@ async fn main() -> Result<(), ClientError> {
.unwrap();
let tx_id = client
- .submit_new_transaction(oracle_reader_contract.id(), tx_increment_request)
+ .submit_tutorial_transaction(oracle_reader_contract.id(), tx_increment_request)
.await
.unwrap();
println!(
- "View transaction on MidenScan: https://testnet.midenscan.com/tx/{:?}",
+ "View transaction on MidenScan: {}/tx/{:?}",
+ network.explorer_url(),
tx_id
);
@@ -290,168 +330,137 @@ async fn main() -> Result<(), ClientError> {
}
```
-_Don't run this code just yet, we still need to create our smart contract that queries the oracle_
-
-In the code above, the Pragma oracle account ID is provided as a command-line argument in bech32 form, and the BTC/USD price feed is identified by the faucet ID pair `1:0` (prefix `1`, suffix `0`). The `get_oracle_foreign_accounts` function returns all of the `ForeignAccount`s that you will need to execute the transaction to get the price data from the oracle. Since Pragma's oracle aggregates data from multiple publishers, this function reads the oracle's on-chain publisher registry and collects every publisher account id required to make a successful FPI call.
+The following section explains the two MASM templates loaded by the Rust example.
-:::note
-The oracle account ID, procedure hash, and faucet pair used in this tutorial reference Pragma's testnet deployment. These values are maintained by Pragma and may change if they redeploy their oracle. For the latest values, check the [Pragma Miden repository](https://github.com/astraly-labs/pragma-miden).
-:::
+In the code above, a compatible testnet oracle account ID and `get_median` procedure root are required inputs. The BTC/USD price feed used prefix `1` and suffix `0` in Pragma's earlier deployment; the optional third and fourth arguments let you supply the pair identifiers published for a new deployment. The `get_oracle_foreign_accounts` function returns every `ForeignAccount` needed to execute the transaction. Since Pragma's oracle aggregates data from multiple publishers, the function reads the on-chain publisher registry and requests the storage proofs needed by the nested FPI calls.
## Step 2: Build the price reader smart contract and script
-Just like in previous tutorials, for better code organization we will separate the Miden assembly code from our Rust code.
-
-Create a directory named `masm` at the **root** of your `miden-counter-contract` directory. This will contain our contract and script masm code.
-
-Initialize the `masm` directory:
-
-```bash
-mkdir -p masm/accounts masm/scripts
-```
-
-This will create:
-
-```text
-masm/
-├── accounts/
-└── scripts/
-```
+The reader and transaction script are in the repository’s `masm/` directory.
### Oracle price reader smart contract
Below is our oracle price reader contract. It has a single exported procedure: `get_price`
-The import `miden::tx` contains the `tx::execute_foreign_procedure` which we will use to read the price from the oracle contract.
+The `miden::protocol::tx` module exports `execute_foreign_procedure`, which the reader uses to invoke the oracle.
#### Here's a breakdown of what the `get_price` procedure does:
-1. Pushes the 16 foreign procedure inputs that `tx::execute_foreign_procedure` requires. The first four are the arguments to `get_median` — the BTC/USD faucet ID prefix `1`, suffix `0`, an `amount` of `0`, and a trailing `0` — and the remaining twelve are zero padding.
-2. Pushes `0xaa3a12d4e9de2dad37c50dba93809b9c17226d512e642d3d620c77088a85da71` onto the stack, which is the procedure root of the `get_median` procedure in the oracle.
-3. Pushes the Pragma oracle account ID prefix and suffix.
-4. Calls `tx::execute_foreign_procedure`, which invokes the `get_median` procedure via foreign procedure invocation. `get_median` returns `[is_tracked, median_price, amount]` on the stack.
+1. Pushes the 16 foreign procedure inputs that `tx::execute_foreign_procedure` requires. The first four contain the requested pair prefix and suffix, an `amount` of `0`, and a trailing `0`; the remaining twelve are zero padding.
+2. Pushes the supplied `get_median` procedure root.
+3. Pushes the supplied testnet oracle account ID prefix and suffix.
+4. Calls `tx::execute_foreign_procedure`, which invokes `get_median`. The template expects `[is_tracked, median_price, amount]` at the top of the returned stack; confirm this interface against the compatible deployment.
+5. Drops the sixteen foreign output elements, including the price, to restore the caller's stack.
-Inside of the `masm/accounts/` directory, create the `oracle_reader.masm` file:
+The reader is defined in `masm/accounts/oracle_reader.masm`:
```masm
-# The oracle account ID, procedure hash, and pair ID below reference
-# Pragma's Miden v0.15 testnet deployment (https://github.com/astraly-labs/pragma-miden).
-# Pragma's addresses change between testnet iterations (their README is the
-# source of truth), so if the oracle is redeployed these values must be updated:
-# the oracle account id, the `get_median` procedure root, and (if a different
-# feed) the faucet pair id.
+# the Rust runner replaces these placeholders with values from a compatible
+# pragma deployment before compiling this component.
use miden::protocol::tx
-# Fetches the current price from the `get_median`
-# procedure from the Pragma oracle
-# => []
-pub proc get_price
+# PUBLIC INTERFACE
+# =================================================================================================
+
+#! Queries the configured Pragma oracle's median price through a foreign procedure.
+#!
+#! Inputs: [pad(16)]
+#! Outputs: [pad(16)]
+#!
+#! Panics if:
+#! - the configured oracle procedure or its required foreign state is unavailable.
+#!
+#! Invocation: call
+@account_procedure
+pub proc get_price()
# `execute_foreign_procedure` requires exactly 16 foreign procedure inputs.
# `get_median` only reads the first four, so the rest are zero padding.
padw padw padw
- # => [PAD(12)]
+ # => [pad(28)]
- # BTC/USD pair: faucet id prefix `1`, suffix `0`, amount `0`
- push.0.0.0.1
- # => [pair_prefix, pair_suffix, amount, 0, PAD(12)]
+ # requested pair: faucet ID prefix/suffix, amount `0`.
+ push.0.0.{pair_suffix}.{pair_prefix}
+ # => [pair_prefix, pair_suffix, amount, 0, pad(28)]
- # This is the procedure root of the `get_median` procedure
- push.0xaa3a12d4e9de2dad37c50dba93809b9c17226d512e642d3d620c77088a85da71
- # => [GET_MEDIAN_HASH, FOREIGN_INPUTS(16)]
+ # this is the procedure root of the `get_median` procedure.
+ push.{get_median_proc_root}
+ # => [GET_MEDIAN_HASH, foreign_procedure_inputs(16), pad(16)]
- # The Pragma oracle account id: prefix then suffix, leaving suffix on top
- push.8850886096234572817.9093477099503364096
- # => [oracle_id_suffix, oracle_id_prefix, GET_MEDIAN_HASH, FOREIGN_INPUTS(16)]
+ # the Pragma oracle account id: prefix then suffix, leaving suffix on top.
+ push.{oracle_id_prefix}.{oracle_id_suffix}
+ # => [oracle_id_suffix, oracle_id_prefix, GET_MEDIAN_HASH, foreign_procedure_inputs(16), pad(16)]
exec.tx::execute_foreign_procedure
- # => [is_tracked, median_price, amount, PAD(13)]
-
- debug.stack
- # => [is_tracked, median_price, amount, PAD(13)]
+ # => [is_tracked, median_price, amount, pad(29)]
dropw dropw dropw dropw
+ # => [pad(16)]
end
```
-**Note**: _It's a good habit to add comments above each line of MASM code with the expected stack state. This improves readability and helps with debugging._
+Stack comments below instruction groups show the expected stack state after execution. The braces in this template are replaced by Rust before assembly.
### Create the script which calls the `get_price` procedure
This is a Miden assembly script that will call the `get_price` procedure during the transaction.
-Inside of the `masm/scripts/` directory, create the `oracle_reader_script.masm` file:
+The transaction script is defined in `masm/scripts/oracle_reader_script.masm`:
```masm
use external_contract::oracle_reader
-begin
- exec.oracle_reader::get_price
+#! Queries the configured oracle through the reader account.
+#!
+#! Inputs: [ARGS, pad(12)]
+#! Outputs: [pad(16)]
+#!
+#! Where:
+#! - ARGS contains unused transaction script arguments.
+#!
+#! Invocation: dyncall
+@transaction_script
+pub proc main(args: word)
+ dropw
+ # => [pad(16)]
+
+ call.oracle_reader::get_price
+ # => [pad(16)]
end
```
## Step 3: Run the program
-Run the following command to execute src/main.rs:
+Compile-check the standalone program with `cargo check`. To execute it once a compatible deployment is available, set `MIDEN_ORACLE_ACCOUNT_ID` and `MIDEN_ORACLE_GET_MEDIAN_ROOT` in your shell, then run:
```bash
-cargo run --release
+TUTORIAL_NETWORK=testnet cargo run --release -- \
+ "$MIDEN_ORACLE_ACCOUNT_ID" "$MIDEN_ORACLE_GET_MEDIAN_ROOT"
```
-The output of our program will look something like this:
+The command defaults to pair prefix `1` and suffix `0`. Append the deployment's pair prefix and suffix as the third and fourth arguments when those values differ. Do not assume the old pair identifies BTC/USD on a new deployment.
+
+With a compatible deployment, the output includes:
```text
-Latest block: 489876
-Oracle accountId prefix: V1(AccountIdPrefixV1 { prefix: 8850886096234572817 }) suffix: 9093477099503364096
-Stack state before step 6324:
-├── 0: 1
-├── 1: 6439689500000
-├── 2: 0
-├── 3: 0
-├── 4: 0
-├── 5: 0
-├── 6: 0
-├── 7: 0
-├── 8: 0
-├── 9: 0
-├── 10: 0
-├── 11: 0
-├── 12: 0
-├── 13: 0
-├── 14: 0
-├── 15: 0
-├── 16: 0
-├── 17: 0
-├── 18: 0
-├── 19: 0
-├── 20: 0
-├── 21: 0
-├── 22: 0
-├── 23: 0
-├── 24: 0
-├── 25: 0
-├── 26: 0
-├── 27: 0
-├── 28: 0
-├── 29: 0
-├── 30: 0
-└── 31: 0
-
-View transaction on MidenScan: https://testnet.midenscan.com/tx/0xbf6048faa60c72c43ab5645d937d02d0d768ee4fdad3c64b736789fdf0ef6a44
+Latest block:
+Oracle accountId prefix: suffix:
+View transaction on MidenScan: https://testnet.midenscan.com/tx/
```
-The `get_median` procedure leaves three values on the stack. Index `0` holds `is_tracked` — `1` when Pragma tracks the requested pair. Index `1` holds the median price as a raw fixed-point integer; in the output above it is `6439689500000`. The number of decimal places is defined by Pragma's feed (BTC/USD is published with 8 decimals), so this reading is about `$64,396.89`. Index `2` holds the `amount` value that was passed into the call. Pragma publishes several price feeds on testnet; this tutorial reads the `BTC/USD` feed.
+The template expects `[is_tracked, median_price, amount]` on the stack, then drops those values. Before using the price in an application, check the feed's tracking status, freshness rules, and fixed-point precision, then store the value or use it within the same procedure.
### Running the tutorial
-To run this tutorial end-to-end, navigate to the `rust-client` directory in the [miden-tutorials](https://github.com/0xMiden/miden-tutorials/) repository and run:
+Once the deployment prerequisites are met, return to the root of the [tutorials repository](https://github.com/0xMiden/tutorials/) and run:
```bash
cd rust-client
-cargo run --release --bin oracle_data_query
+TUTORIAL_NETWORK=testnet cargo run --release --bin oracle_data_query -- \
+ "$MIDEN_ORACLE_ACCOUNT_ID" "$MIDEN_ORACLE_GET_MEDIAN_ROOT"
```
-This defaults to Pragma's current testnet oracle. To read from a different deployment, pass its bech32 account ID as an argument: `cargo run --release --bin oracle_data_query -- `.
+If both variables are exported in your shell, you can omit `--` and the explicit arguments. The account must use the `mtst1...` testnet prefix.
### Continue learning
diff --git a/docs/src/rust-client/public_account_interaction_tutorial.md b/docs/src/rust-client/public_account_interaction_tutorial.md
index 6f9ad348..fe7695d1 100644
--- a/docs/src/rust-client/public_account_interaction_tutorial.md
+++ b/docs/src/rust-client/public_account_interaction_tutorial.md
@@ -7,11 +7,13 @@ sidebar_position: 5
_Using the Miden client in Rust to interact with public smart contracts on Miden_
+For toolchain requirements and shared fee helpers, see the [Rust client setup](./index.md#running-the-v016-examples).
+
## Overview
In the previous tutorial, we built a simple counter contract and deployed it to the Miden testnet. However, we only covered how the contract’s deployer could interact with it. Now, let’s explore how anyone can interact with a public smart contract on Miden.
-We’ll retrieve the counter contract’s state from the chain and rebuild it locally so a local transaction can be executed against it. In the near future, Miden will support network transactions, making the process of submitting transactions to public smart contracts much more like traditional blockchains.
+We'll import the counter contract's public state from the chain and execute a local transaction against it. Its `NoAuth` authentication component permits this without a signature; a public account with signature authentication would still require the appropriate authorization. For contracts that should execute autonomously on behalf of users, continue with the network transactions tutorial after this one.
Just like in the previous tutorial, we will use a script to invoke the increment function within the counter contract to update the count. However, this tutorial demonstrates how to call a procedure in a smart contract that was deployed by a different user on Miden.
@@ -22,120 +24,151 @@ Just like in the previous tutorial, we will use a script to invoke the increment
## Prerequisites
-This tutorial assumes you have a basic understanding of Miden assembly and completed the previous tutorial on deploying the counter contract. Although not a requirement, it is recommended to complete the counter contract deployment tutorial before starting this tutorial.
+This tutorial assumes you have a basic understanding of Miden assembly and a counter deployed with the code from the previous tutorial. Keep the `mtst1...` account ID printed by that deployment; the standalone program requires it.
+
+The counter deployment example also funds the contract's native fee balance.
+This example spends from that existing balance when incrementing the counter;
+ensure the imported contract has enough funds. The runner supplies a freshly
+deployed, funded counter automatically.
## Step 1: Initialize your repository
-Create a new Rust repository for your Miden project and navigate to it with the following command:
+From the parent directory of your `tutorials` clone, create a sibling Cargo project:
```bash
-cargo new miden-counter-contract
-cd miden-counter-contract
+cargo new miden-public-account-interaction
+cd miden-public-account-interaction
+rustup override set 1.98.1
+cp ../tutorials/rust-client/Cargo.lock Cargo.lock
```
Add the following dependencies to your `Cargo.toml` file:
```toml
[dependencies]
-miden-client = { version = "0.15", features = ["testing", "tonic"] }
-miden-client-sqlite-store = { version = "0.15", package = "miden-client-sqlite-store" }
-miden-protocol = { version = "0.15" }
-rand = { version = "0.9" }
-tokio = { version = "1.46", features = ["rt-multi-thread", "net", "macros", "fs"] }
+# Clone tutorials next to this Cargo project (see Rust client setup).
+rust-client = { path = "../tutorials/rust-client" }
+miden-client = { version = "=0.16.0", features = ["testing", "tonic"] }
+miden-client-sqlite-store = { version = "=0.16.0", package = "miden-client-sqlite-store" }
+miden-protocol = { version = "=0.16.0" }
+rand = { version = "0.10" }
+tokio = { version = "1.48", features = ["rt-multi-thread", "net", "macros", "fs"] }
+
+[profile.dev]
+opt-level = 2
```
-## Step 2: Build the counter contract
-
-For better code organization, we will separate the Miden assembly code from our Rust code.
+## Step 2: Prepare the counter module and script
-Create a directory named `masm` at the **root** of your `miden-counter-contract` directory. This will contain our contract and script masm code.
-
-Initialize the `masm` directory:
-
-```bash
-mkdir -p masm/accounts masm/scripts
-```
-
-This will create:
-
-```text
-masm/
-├── accounts/
-└── scripts/
-```
-
-Inside of the `masm/accounts/` directory, create the `counter.masm` file:
+The account already exists on-chain. We use the repository’s `masm/accounts/counter.masm` module to link the increment transaction script:
```masm
use miden::protocol::active_account
use miden::protocol::native_account
-use miden::core::word
use miden::core::sys
+# CONSTANTS
+# =================================================================================================
+
const COUNTER_SLOT = word("miden::tutorials::counter")
-#! Inputs: []
-#! Outputs: [count]
-pub proc get_count
+# PUBLIC INTERFACE
+# =================================================================================================
+
+#! Returns the current count.
+#!
+#! Inputs: [pad(16)]
+#! Outputs: [count, pad(15)]
+#!
+#! Invocation: call
+@account_procedure
+pub proc get_count() -> felt
push.COUNTER_SLOT[0..2] exec.active_account::get_item
- # => [count]
+ # => [[count, 0, 0, 0], pad(16)]
exec.sys::truncate_stack
- # => [count]
+ # => [count, pad(15)]
end
-#! Inputs: []
-#! Outputs: []
-pub proc increment_count
+#! Increments the current count by one.
+#!
+#! Inputs: [pad(16)]
+#! Outputs: [pad(16)]
+#!
+#! Invocation: call
+@account_procedure
+pub proc increment_count()
push.COUNTER_SLOT[0..2] exec.active_account::get_item
- # => [count]
+ # => [[count, 0, 0, 0], pad(16)]
add.1
- # => [count+1]
+ # => [[count + 1, 0, 0, 0], pad(16)]
push.COUNTER_SLOT[0..2] exec.native_account::set_item
- # => []
+ # => [OLD_VALUE, pad(16)]
+
+ dropw
+ # => [pad(16)]
exec.sys::truncate_stack
- # => []
+ # => [pad(16)]
end
```
-Inside of the `masm/scripts/` directory, create the `counter_script.masm` file:
+The transaction script is defined in `masm/scripts/counter_script.masm`:
```masm
use external_contract::counter_contract
-begin
+#! Increments the counter.
+#!
+#! Inputs: [ARGS, pad(12)]
+#! Outputs: [pad(16)]
+#!
+#! Where:
+#! - ARGS contains unused transaction script arguments.
+#!
+#! Invocation: dyncall
+@transaction_script
+pub proc main(args: word)
+ dropw
+ # => [pad(16)]
+
call.counter_contract::increment_count
+ # => [pad(16)]
end
```
**Note**: _We explained in the previous counter contract tutorial what exactly happens at each step in the `increment_count` procedure._
-### Step 3: Set up your `src/main.rs` file
+## Step 3: Set up your `src/main.rs` file
Copy and paste the following code into your `src/main.rs` file:
```rust no_run
+use rust_client::TutorialClientExt;
use std::{path::PathBuf, sync::Arc};
use miden_client::{
+ ClientError,
account::{AccountId, StorageSlotName},
builder::ClientBuilder,
keystore::FilesystemKeyStore,
- rpc::{Endpoint, GrpcClient},
+ rpc::{GrpcClient, VerifyingRpcClient},
transaction::TransactionRequestBuilder,
- ClientError,
};
use miden_client_sqlite_store::ClientBuilderSqliteExt;
+use rust_client::TutorialNetwork;
#[tokio::main]
async fn main() -> Result<(), ClientError> {
// Initialize client
- let endpoint = Endpoint::testnet();
+ let network = TutorialNetwork::from_env()?;
+ let endpoint = network.endpoint();
let timeout_ms = 10_000;
- let rpc_client = Arc::new(GrpcClient::new(&endpoint, timeout_ms));
+ let rpc_client = Arc::new(VerifyingRpcClient::new(GrpcClient::new(
+ &endpoint, timeout_ms,
+ )));
// Initialize keystore
let keystore_path = PathBuf::from("./keystore");
@@ -147,7 +180,6 @@ async fn main() -> Result<(), ClientError> {
.rpc(rpc_client)
.sqlite_store(store_path)
.authenticator(keystore.clone())
- .in_debug_mode(true.into())
.build()
.await?;
@@ -160,11 +192,9 @@ async fn main() -> Result<(), ClientError> {
## Step 4: Reading public state from a smart contract
-To read the public storage state of a smart contract on Miden we either instantiate the `TonicRpcClient` by itself, or use the `test_rpc_api()` method on the `Client` instance. In this example, we will be using the `test_rpc_api()` method.
+This tutorial uses `Client::import_account_by_id` to import a public account from testnet and read its storage. First run the [counter contract tutorial](./counter_contract_tutorial.md), then copy the deployed counter's `mtst1...` account ID. Pass that ID to this program instead of hard-coding an address, because testnet is reset periodically.
-We will be reading the public storage state of the counter contract deployed on the testnet at address `mtst1apcqs7aj3a2cf5t6pnsfy0p4ns7wl7sp`.
-
-Add the following code snippet to the end of your `src/main.rs` function:
+Insert the following code inside `main`, immediately before its final `Ok(())`:
```rust ignore
// -------------------------------------------------------------------------
@@ -172,9 +202,19 @@ Add the following code snippet to the end of your `src/main.rs` function:
// -------------------------------------------------------------------------
println!("\n[STEP 1] Reading data from public state");
-// Define the Counter Contract account id from counter contract deploy
-let (_, counter_contract_id) =
- AccountId::from_bech32("mtst1apcqs7aj3a2cf5t6pnsfy0p4ns7wl7sp").unwrap();
+// Pass the account ID printed by `counter_contract_deploy` as the first argument, or via
+// `MIDEN_COUNTER_ACCOUNT_ID`.
+let counter_contract_bech32 = std::env::args()
+ .nth(1)
+ .or_else(|| std::env::var("MIDEN_COUNTER_ACCOUNT_ID").ok())
+ .expect("pass the counter account ID from counter_contract_deploy");
+let (account_network, counter_contract_id) =
+ AccountId::from_bech32(&counter_contract_bech32).expect("invalid counter account ID");
+assert_eq!(
+ account_network,
+ network.network_id(),
+ "counter account must match the selected tutorial network"
+);
client
.import_account_by_id(counter_contract_id)
@@ -190,24 +230,29 @@ println!(
"Account details: {:?}",
counter_contract.storage().slots().first().unwrap()
);
+let counter_slot_name =
+ StorageSlotName::new("miden::tutorials::counter").expect("valid slot name");
+let count_before = counter_contract
+ .storage()
+ .get_item(&counter_slot_name)
+ .unwrap()[0];
```
-Run the following command to execute src/main.rs:
+Set `MIDEN_COUNTER_ACCOUNT_ID` to the deployed `mtst1...` address in your shell, or replace the quoted variable below with that address. Run the following command to execute `src/main.rs`:
```bash
-cargo run --release
+TUTORIAL_NETWORK=testnet cargo run --release -- "$MIDEN_COUNTER_ACCOUNT_ID"
```
-After the program executes, you should see the counter contract count value and nonce printed to the terminal, for example:
+The program prints the imported storage slot. For a freshly deployed counter, the abridged output is:
```text
-count val: [0, 0, 0, 5]
-counter nonce: 5
+Account details: StorageSlot { ... content: Value(Word([1, 0, 0, 0])) }
```
-## Step 5: Importing a public account
+## Step 5: Increment the imported counter
-Add the following code snippet to the end of your `src/main.rs` function:
+Insert the following code after the import step, inside `main` and before `Ok(())`:
```rust ignore
// -------------------------------------------------------------------------
@@ -215,18 +260,18 @@ Add the following code snippet to the end of your `src/main.rs` function:
// -------------------------------------------------------------------------
println!("\n[STEP 2] Call the increment_count procedure in the counter contract");
-// Load the MASM sources at compile time so the binary is independent of
-// the working directory it is run from.
-let script_code = include_str!("../masm/scripts/counter_script.masm");
-let counter_code = include_str!("../masm/accounts/counter.masm");
+// Read the MASM source from the tutorials repository.
+let script_code =
+ std::fs::read_to_string("../tutorials/masm/scripts/counter_script.masm").unwrap();
+let counter_code = std::fs::read_to_string("../tutorials/masm/accounts/counter.masm").unwrap();
// Compile the script with the counter contract code linked as a module
// on the same `CodeBuilder` chain.
let tx_script = client
.code_builder()
- .with_linked_module("external_contract::counter_contract", counter_code)
+ .with_linked_module("external_contract::counter_contract", &counter_code)
.unwrap()
- .compile_tx_script(script_code)
+ .compile_tx_script(&script_code)
.unwrap();
// Build a transaction request with the custom script
@@ -237,12 +282,13 @@ let tx_increment_request = TransactionRequestBuilder::new()
// Execute and submit the transaction
let tx_id = client
- .submit_new_transaction(counter_contract.id(), tx_increment_request)
+ .submit_tutorial_transaction(counter_contract_id, tx_increment_request)
.await
.unwrap();
println!(
- "View transaction on MidenScan: https://testnet.midenscan.com/tx/{:?}",
+ "View transaction on MidenScan: {}/tx/{:?}",
+ network.explorer_url(),
tx_id
);
@@ -250,17 +296,19 @@ client.sync_state().await.unwrap();
// Retrieve updated contract data to see the incremented counter
let account = client
- .get_account(counter_contract.id())
+ .get_account(counter_contract_id)
.await
.unwrap()
.expect("counter contract not found");
-let counter_slot_name =
- miden_client::account::StorageSlotName::new("miden::tutorials::counter")
- .expect("valid slot name");
println!(
"counter contract storage: {:?}",
account.storage().get_item(&counter_slot_name)
);
+assert_eq!(
+ account.storage().get_item(&counter_slot_name).unwrap()[0],
+ count_before + miden_client::ONE,
+ "the imported counter must increment exactly once",
+);
```
## Summary
@@ -268,24 +316,29 @@ println!(
The final `src/main.rs` file should look like this:
```rust no_run
+use rust_client::TutorialClientExt;
use std::{path::PathBuf, sync::Arc};
use miden_client::{
+ ClientError,
account::{AccountId, StorageSlotName},
builder::ClientBuilder,
keystore::FilesystemKeyStore,
- rpc::{Endpoint, GrpcClient},
+ rpc::{GrpcClient, VerifyingRpcClient},
transaction::TransactionRequestBuilder,
- ClientError,
};
use miden_client_sqlite_store::ClientBuilderSqliteExt;
+use rust_client::TutorialNetwork;
#[tokio::main]
async fn main() -> Result<(), ClientError> {
// Initialize client
- let endpoint = Endpoint::testnet();
+ let network = TutorialNetwork::from_env()?;
+ let endpoint = network.endpoint();
let timeout_ms = 10_000;
- let rpc_client = Arc::new(GrpcClient::new(&endpoint, timeout_ms));
+ let rpc_client = Arc::new(VerifyingRpcClient::new(GrpcClient::new(
+ &endpoint, timeout_ms,
+ )));
// Initialize keystore
let keystore_path = PathBuf::from("./keystore");
@@ -297,7 +350,6 @@ async fn main() -> Result<(), ClientError> {
.rpc(rpc_client)
.sqlite_store(store_path)
.authenticator(keystore.clone())
- .in_debug_mode(true.into())
.build()
.await?;
@@ -309,9 +361,19 @@ async fn main() -> Result<(), ClientError> {
// -------------------------------------------------------------------------
println!("\n[STEP 1] Reading data from public state");
- // Define the Counter Contract account id from counter contract deploy
- let (_, counter_contract_id) =
- AccountId::from_bech32("mtst1apcqs7aj3a2cf5t6pnsfy0p4ns7wl7sp").unwrap();
+ // Pass the account ID printed by `counter_contract_deploy` as the first argument, or via
+ // `MIDEN_COUNTER_ACCOUNT_ID`.
+ let counter_contract_bech32 = std::env::args()
+ .nth(1)
+ .or_else(|| std::env::var("MIDEN_COUNTER_ACCOUNT_ID").ok())
+ .expect("pass the counter account ID from counter_contract_deploy");
+ let (account_network, counter_contract_id) =
+ AccountId::from_bech32(&counter_contract_bech32).expect("invalid counter account ID");
+ assert_eq!(
+ account_network,
+ network.network_id(),
+ "counter account must match the selected tutorial network"
+ );
client
.import_account_by_id(counter_contract_id)
@@ -327,24 +389,30 @@ async fn main() -> Result<(), ClientError> {
"Account details: {:?}",
counter_contract.storage().slots().first().unwrap()
);
+ let counter_slot_name =
+ StorageSlotName::new("miden::tutorials::counter").expect("valid slot name");
+ let count_before = counter_contract
+ .storage()
+ .get_item(&counter_slot_name)
+ .unwrap()[0];
// -------------------------------------------------------------------------
// STEP 2: Call the Counter Contract with a script
// -------------------------------------------------------------------------
println!("\n[STEP 2] Call the increment_count procedure in the counter contract");
- // Load the MASM sources at compile time so the binary is independent of
- // the working directory it is run from.
- let script_code = include_str!("../masm/scripts/counter_script.masm");
- let counter_code = include_str!("../masm/accounts/counter.masm");
+ // Read the MASM source from the tutorials repository.
+ let script_code =
+ std::fs::read_to_string("../tutorials/masm/scripts/counter_script.masm").unwrap();
+ let counter_code = std::fs::read_to_string("../tutorials/masm/accounts/counter.masm").unwrap();
// Compile the script with the counter contract code linked as a module
// on the same `CodeBuilder` chain.
let tx_script = client
.code_builder()
- .with_linked_module("external_contract::counter_contract", counter_code)
+ .with_linked_module("external_contract::counter_contract", &counter_code)
.unwrap()
- .compile_tx_script(script_code)
+ .compile_tx_script(&script_code)
.unwrap();
// Build a transaction request with the custom script
@@ -355,12 +423,13 @@ async fn main() -> Result<(), ClientError> {
// Execute and submit the transaction
let tx_id = client
- .submit_new_transaction(counter_contract.id(), tx_increment_request)
+ .submit_tutorial_transaction(counter_contract_id, tx_increment_request)
.await
.unwrap();
println!(
- "View transaction on MidenScan: https://testnet.midenscan.com/tx/{:?}",
+ "View transaction on MidenScan: {}/tx/{:?}",
+ network.explorer_url(),
tx_id
);
@@ -368,17 +437,19 @@ async fn main() -> Result<(), ClientError> {
// Retrieve updated contract data to see the incremented counter
let account = client
- .get_account(counter_contract.id())
+ .get_account(counter_contract_id)
.await
.unwrap()
.expect("counter contract not found");
- let counter_slot_name =
- miden_client::account::StorageSlotName::new("miden::tutorials::counter")
- .expect("valid slot name");
println!(
"counter contract storage: {:?}",
account.storage().get_item(&counter_slot_name)
);
+ assert_eq!(
+ account.storage().get_item(&counter_slot_name).unwrap()[0],
+ count_before + miden_client::ONE,
+ "the imported counter must increment exactly once",
+ );
Ok(())
}
```
@@ -386,63 +457,33 @@ async fn main() -> Result<(), ClientError> {
Run the following command to execute src/main.rs:
```bash
-cargo run --release
+TUTORIAL_NETWORK=testnet cargo run --release -- "$MIDEN_COUNTER_ACCOUNT_ID"
```
The output of our program will look something like this depending on the current count value in the smart contract:
```text
-Client initialized successfully.
-Latest block: 242342
+Latest block:
-[STEP 1] Building counter contract from public state
-count val: [0, 0, 0, 1]
-counter nonce: 1
+[STEP 1] Reading data from public state
+Account details: StorageSlot { ... content: Value(Word([1, 0, 0, 0])) }
[STEP 2] Call the increment_count procedure in the counter contract
-Procedure 1: "0x92495ca54d519eb5e4ba22350f837904d3895e48d74d8079450f19574bb84cb6"
-Procedure 2: "0xecd7eb223a5524af0cc78580d96357b298bb0b3d33fe95aeb175d6dab9de2e54"
-number of procedures: 2
-Final script:
-begin
- # => []
- call.0xecd7eb223a5524af0cc78580d96357b298bb0b3d33fe95aeb175d6dab9de2e54
-end
-Stack state before step 1812:
-├── 0: 2
-├── 1: 0
-├── 2: 0
-├── 3: 0
-├── 4: 0
-├── 5: 0
-├── 6: 0
-├── 7: 0
-├── 8: 0
-├── 9: 0
-├── 10: 0
-├── 11: 0
-├── 12: 0
-├── 13: 0
-├── 14: 0
-├── 15: 0
-├── 16: 0
-├── 17: 0
-├── 18: 0
-└── 19: 0
-
-View transaction on MidenScan: https://testnet.midenscan.com/tx/0x8183aed150f20b9c26d4cb7840bfc92571ea45ece31116170b11cdff2649eb5c
-counter contract storage: Ok(RpoDigest([0, 0, 0, 2]))
+View transaction on MidenScan: https://testnet.midenscan.com/tx/
+counter contract storage: Ok(Word([2, 0, 0, 0]))
```
### Running the example
-To run the full example, navigate to the `rust-client` directory in the [miden-tutorials](https://github.com/0xMiden/miden-tutorials/) repository and run this command:
+To run the checked-in example, return to the root of the [tutorials repository](https://github.com/0xMiden/tutorials/) and run:
```bash
cd rust-client
-cargo run --release --bin counter_contract_increment
+TUTORIAL_NETWORK=testnet cargo run --release --bin counter_contract_increment -- "$MIDEN_COUNTER_ACCOUNT_ID"
```
+If `MIDEN_COUNTER_ACCOUNT_ID` is exported in your shell, you can omit `--` and the final argument.
+
### Continue learning
Next tutorial: [Network Transactions on Miden](network_transactions_tutorial.md)
diff --git a/docs/src/rust-client/unauthenticated_note_how_to.md b/docs/src/rust-client/unauthenticated_note_how_to.md
index de3597b1..53e43f7d 100644
--- a/docs/src/rust-client/unauthenticated_note_how_to.md
+++ b/docs/src/rust-client/unauthenticated_note_how_to.md
@@ -7,23 +7,25 @@ sidebar_position: 9
_Using unauthenticated notes for optimistic note consumption_
+For toolchain requirements and shared fee helpers, see the [Rust client setup](./index.md#running-the-v016-examples).
+
## Overview
-In this guide, we will explore how to leverage unauthenticated notes on Miden to settle transactions faster than the blocktime. Unauthenticated notes are essentially UTXOs that have not yet been fully committed into a block. This feature allows the notes to be created and consumed within the same block.
+In this guide, we supply a complete note to a consuming transaction before waiting for the note's inclusion proof. Such an input is unauthenticated: the node checks the dependency on its creation transaction. This lets a note be created and consumed within the same block, although confirmation still depends on block production.
-We construct a chain of transactions using the unauthenticated notes method on the transaction builder. Unauthenticated notes are also referred to as "unauthenticated notes" or "erasable notes". We also demonstrate how a note can be serialized and deserialized, highlighting the ability to transfer notes between client instances for asset transfers that can be settled between parties faster than the blocktime.
+We construct the transfer chain with `TransactionRequestBuilder::explicit_input_notes`, wrapping each complete `Note` in `InputNote::unauthenticated`. This pins the input mode even if a sync has already fetched its inclusion proof. `build_consume_notes` selects the mode from the store and can authenticate an input when a proof is available. We also serialize and deserialize each note to demonstrate how its details could be sent between clients. The example uses one client for all accounts and waits for each transfer and consumption to confirm before beginning the next hop.
For example, our demo creates a chain of unauthenticated note transactions:
```markdown
-Alice ➡ Bob ➡ Charlie ➡ Dave ➡ Eve ➡ Frank ➡ ...
+Alice ➡ Bob ➡ Charlie ➡ Dave ➡ Eve
```
## What we'll cover
- **Introduction to Unauthenticated Notes:** Understand what unauthenticated notes are and how they differ from standard notes.
- **Serialization Example:** See how to serialize and deserialize a note to demonstrate how notes can be propagated to client instances faster than the blocktime.
-- **Performance Insights:** Observe how unauthenticated notes can reduce transaction times dramatically.
+- **Confirmation and balances:** Check each transaction and verify the final balances after four transfers between five accounts.
## Step-by-step process
@@ -42,7 +44,7 @@ Alice ➡ Bob ➡ Charlie ➡ Dave ➡ Eve ➡ Frank ➡ ...
4. **Minting and Transacting with Unauthenticated Notes:**
- Mint tokens for one of the accounts (Alice) from the deployed faucet.
- Create a note representing the minted tokens.
- - Build and submit a transaction that uses the unauthenticated note via the "unauthenticated" method.
+ - Submit the note-creation transaction without waiting for confirmation, then pass the complete note to `.explicit_input_notes([(InputNote::unauthenticated(note), None)])`. The explicit mode stays unauthenticated even if the note commits before the consuming transaction executes.
- Serialize the note to demonstrate how it could be transferred to another client instance.
- Consume the note in a subsequent transaction, effectively creating a chain of unauthenticated transactions.
@@ -50,73 +52,82 @@ Alice ➡ Bob ➡ Charlie ➡ Dave ➡ Eve ➡ Frank ➡ ...
- Measure the time taken for each transaction iteration.
- Sync the client state and print account balances to verify the transactions.
+## Set up the Rust project
+
+Start in the directory containing your `tutorials` clone and create a sibling Cargo project:
+
+```bash
+cargo new miden-unauthenticated-notes
+cd miden-unauthenticated-notes
+rustup override set 1.98.1
+cp ../tutorials/rust-client/Cargo.lock Cargo.lock
+```
+
+Keep the generated `[package]` section in `Cargo.toml`, replace its empty `[dependencies]` section with the following, and add the development profile. The path assumes the repository clone is named `tutorials`.
+
+```toml
+[dependencies]
+# Clone tutorials next to this Cargo project (see Rust client setup).
+rust-client = { path = "../tutorials/rust-client" }
+miden-client = { version = "=0.16.0", features = ["testing", "tonic"] }
+miden-client-sqlite-store = { version = "=0.16.0", package = "miden-client-sqlite-store" }
+miden-protocol = { version = "=0.16.0" }
+rand = { version = "0.10" }
+tokio = { version = "1.48", features = ["rt-multi-thread", "net", "macros", "fs"] }
+
+[profile.dev]
+opt-level = 2
+```
+
+Copy the complete Rust example below into `src/main.rs`. Run it from this new project's directory with `TUTORIAL_NETWORK=testnet cargo run --release`. The client creates `store.sqlite3` and `keystore/` here; keep both out of version control.
+
## Full Rust code example
```rust no_run
-use rand::RngCore;
+use rand::Rng;
+use rust_client::TutorialClientExt;
use std::{path::PathBuf, sync::Arc};
-use tokio::time::{sleep, Duration, Instant};
+use tokio::time::{Duration, Instant};
use miden_client::{
+ Client, ClientError,
account::{
+ AccountBuilder, AccountType,
component::{
- BasicWallet, BurnPolicyConfig, FungibleFaucet, MintPolicyConfig, PolicyRegistration,
- TokenName, TokenPolicyManager,
+ create_singlesig_user_fungible_faucet, BasicWallet, BurnPolicy, FungibleFaucet,
+ MintPolicy, TokenName, TokenPolicyManager,
},
- AccountBuilder, AccountType,
},
- address::NetworkId,
- asset::{AssetAmount, AssetCallbackFlag, AssetVaultKey, FungibleAsset, TokenSymbol},
- auth::{AuthSchemeId, AuthSecretKey, AuthSingleSig},
+ asset::{AssetAmount, AssetId, FungibleAsset, TokenSymbol},
+ auth::{AuthSecretKey, AuthSingleSig},
builder::ClientBuilder,
keystore::{FilesystemKeyStore, Keystore},
- note::{Note, NoteAttachments, NoteType, P2idNote},
- rpc::{Endpoint, GrpcClient},
- store::TransactionFilter,
- transaction::{TransactionId, TransactionRequestBuilder, TransactionStatus},
+ note::{Note, NoteType, P2idNote},
+ rpc::{GrpcClient, VerifyingRpcClient},
+ transaction::{TransactionId, TransactionRequestBuilder},
utils::{Deserializable, Serializable},
- Client, ClientError,
};
use miden_client_sqlite_store::ClientBuilderSqliteExt;
+use miden_protocol::transaction::InputNote;
+use rust_client::{FeeConfig, TutorialNetwork, fund_account_for_fees};
/// Waits for a specific transaction to be committed.
async fn wait_for_tx(
client: &mut Client,
tx_id: TransactionId,
) -> Result<(), ClientError> {
- loop {
- client.sync_state().await?;
-
- // Check transaction status
- let txs = client
- .get_transactions(TransactionFilter::Ids(vec![tx_id]))
- .await?;
- let tx_committed = if !txs.is_empty() {
- matches!(txs[0].status, TransactionStatus::Committed { .. })
- } else {
- false
- };
-
- if tx_committed {
- println!("✅ transaction {} committed", tx_id.to_hex());
- break;
- }
-
- println!(
- "Transaction {} not yet committed. Waiting...",
- tx_id.to_hex()
- );
- sleep(Duration::from_secs(2)).await;
- }
- Ok(())
+ rust_client::wait_for_transaction(client, tx_id).await
}
#[tokio::main]
async fn main() -> Result<(), ClientError> {
// Initialize client
- let endpoint = Endpoint::testnet();
+ let network = TutorialNetwork::from_env()?;
+ let endpoint = network.endpoint();
let timeout_ms = 10_000;
- let rpc_client = Arc::new(GrpcClient::new(&endpoint, timeout_ms));
+ let rpc_client = Arc::new(VerifyingRpcClient::new(GrpcClient::new(
+ &endpoint, timeout_ms,
+ )));
// Initialize keystore
let keystore_path = PathBuf::from("./keystore");
@@ -128,12 +139,12 @@ async fn main() -> Result<(), ClientError> {
.rpc(rpc_client)
.sqlite_store(store_path)
.authenticator(keystore.clone())
- .in_debug_mode(true.into())
.build()
.await?;
let sync_summary = client.sync_state().await.unwrap();
println!("Latest block: {}", sync_summary.block_num);
+ let fee_config = FeeConfig::from_client(&client, network).await?;
//------------------------------------------------------------
// STEP 1: Deploy a fungible faucet
@@ -153,38 +164,40 @@ async fn main() -> Result<(), ClientError> {
let max_supply = AssetAmount::new(1_000_000).unwrap();
// Build the account
- let faucet_account = AccountBuilder::new(init_seed)
- .account_type(AccountType::Public)
- .with_auth_component(AuthSingleSig::new(key_pair.public_key().to_commitment(), AuthSchemeId::Falcon512Poseidon2))
- .with_component(
- FungibleFaucet::builder()
- .name(TokenName::new("MID").unwrap())
- .symbol(symbol)
- .decimals(decimals)
- .max_supply(max_supply)
- .build()
- .unwrap(),
- )
- .with_components(
- TokenPolicyManager::new()
- .with_mint_policy(MintPolicyConfig::AllowAll, PolicyRegistration::Active)
- .unwrap()
- .with_burn_policy(BurnPolicyConfig::AllowAll, PolicyRegistration::Active)
- .unwrap(),
- )
+ let faucet = FungibleFaucet::builder()
+ .name(TokenName::new("MID").unwrap())
+ .symbol(symbol)
+ .decimals(decimals)
+ .max_supply(max_supply)
.build()
.unwrap();
+ let policies = TokenPolicyManager::builder()
+ .active_mint_policy(MintPolicy::allow_all())
+ .active_burn_policy(BurnPolicy::allow_all())
+ .build();
+ let faucet_account = create_singlesig_user_fungible_faucet(
+ init_seed,
+ faucet,
+ AuthSingleSig::from_public_key(key_pair.public_key()),
+ policies,
+ AccountType::Public,
+ )
+ .unwrap();
// Add the faucet to the client
client.add_account(&faucet_account, false).await?;
println!(
"Faucet account ID: {}",
- faucet_account.id().to_bech32(NetworkId::Testnet)
+ faucet_account.id().to_bech32(network.network_id())
);
// Add the key pair to the keystore
- keystore.add_key(&key_pair, faucet_account.id()).await.unwrap();
+ keystore
+ .add_key(&key_pair, faucet_account.id())
+ .await
+ .unwrap();
+ fund_account_for_fees(&mut client, faucet_account.id(), &fee_config).await?;
// Resync to show newly deployed faucet
tokio::time::sleep(Duration::from_secs(2)).await;
@@ -196,7 +209,7 @@ async fn main() -> Result<(), ClientError> {
println!("\n[STEP 2] Creating new accounts");
let mut accounts = vec![];
- let number_of_accounts = 2;
+ let number_of_accounts = 5;
for i in 0..number_of_accounts {
let mut init_seed = [0_u8; 32];
@@ -206,7 +219,7 @@ async fn main() -> Result<(), ClientError> {
let account = AccountBuilder::new(init_seed)
.account_type(AccountType::Public)
- .with_auth_component(AuthSingleSig::new(key_pair.public_key().to_commitment(), AuthSchemeId::Falcon512Poseidon2))
+ .with_component(AuthSingleSig::from_public_key(key_pair.public_key()))
.with_component(BasicWallet)
.build()
.unwrap();
@@ -215,12 +228,13 @@ async fn main() -> Result<(), ClientError> {
println!(
"account id {:?}: {}",
i,
- account.id().to_bech32(NetworkId::Testnet)
+ account.id().to_bech32(network.network_id())
);
client.add_account(&account, true).await?;
// Add the key pair to the keystore
keystore.add_key(&key_pair, account.id()).await.unwrap();
+ fund_account_for_fees(&mut client, account.id(), &fee_config).await?;
}
// For demo purposes, Alice is the first account.
@@ -243,7 +257,7 @@ async fn main() -> Result<(), ClientError> {
.unwrap();
let tx_id = client
- .submit_new_transaction(faucet_account.id(), transaction_request)
+ .submit_tutorial_transaction(faucet_account.id(), transaction_request)
.await?;
println!("Minted tokens. TX: {:?}", tx_id);
@@ -251,16 +265,17 @@ async fn main() -> Result<(), ClientError> {
wait_for_tx(&mut client, tx_id).await?;
// Get the minted note and consume it
- let consumable_notes = client.get_consumable_notes(Some(alice.id())).await?;
+ let consumable_notes = client
+ .get_consumable_tutorial_notes(Some(alice.id()))
+ .await?;
if let Some((note_record, _)) = consumable_notes.first() {
let note: Note = note_record.clone().try_into()?;
- let transaction_request = TransactionRequestBuilder::new()
- .build_consume_notes(vec![note])
- .unwrap();
+ let transaction_request =
+ TransactionRequestBuilder::new().build_consume_notes(vec![note])?;
let consume_tx_id = client
- .submit_new_transaction(alice.id(), transaction_request)
+ .submit_tutorial_transaction(alice.id(), transaction_request)
.await?;
println!("Consumed minted note. TX: {:?}", consume_tx_id);
@@ -277,10 +292,13 @@ async fn main() -> Result<(), ClientError> {
for i in 0..number_of_accounts - 1 {
let loop_start = Instant::now();
println!("\nunauthenticated tx {:?}", i + 1);
- println!("sender: {}", accounts[i].id().to_bech32(NetworkId::Testnet));
+ println!(
+ "sender: {}",
+ accounts[i].id().to_bech32(network.network_id())
+ );
println!(
"target: {}",
- accounts[i + 1].id().to_bech32(NetworkId::Testnet)
+ accounts[i + 1].id().to_bech32(network.network_id())
);
// Time the creation of the p2id note
@@ -295,26 +313,30 @@ async fn main() -> Result<(), ClientError> {
NoteType::Public
};
- let p2id_note = P2idNote::create(
- accounts[i].id(),
- accounts[i + 1].id(),
- vec![fungible_asset_send_amount.into()],
- note_type,
- NoteAttachments::empty(),
- client.rng(),
- )
- .unwrap();
+ let p2id_note: Note = P2idNote::builder()
+ .sender(accounts[i].id())
+ .target(accounts[i + 1].id())
+ .asset(fungible_asset_send_amount)
+ .note_type(note_type)
+ .generate_serial_number(client.rng())
+ .build()
+ .unwrap()
+ .into();
+
+ let output_note = p2id_note.clone();
// Time transaction request building
let transaction_request = TransactionRequestBuilder::new()
- .own_output_notes(vec![p2id_note.clone()])
+ .own_output_notes(vec![output_note])
.build()
.unwrap();
- let tx_id = client
+ // Do not wait for inclusion: the receiver is given the complete note below.
+ client.sync_state().await?;
+ let send_tx_id = client
.submit_new_transaction(accounts[i].id(), transaction_request)
.await?;
- println!("Created note. TX: {:?}", tx_id);
+ println!("Created note. TX: {:?}", send_tx_id);
// Note serialization/deserialization
// This demonstrates how you could send the serialized note to another client instance
@@ -322,17 +344,19 @@ async fn main() -> Result<(), ClientError> {
let deserialized_p2id_note = Note::read_from_bytes(&serialized).unwrap();
// Time consume note request building
+ // Keep this input unauthenticated even if syncing has already fetched its proof.
let consume_note_request = TransactionRequestBuilder::new()
- .input_notes([(deserialized_p2id_note, None)])
- .build()
- .unwrap();
+ .explicit_input_notes([(InputNote::unauthenticated(deserialized_p2id_note), None)])
+ .build()?;
let tx_id = client
- .submit_new_transaction(accounts[i + 1].id(), consume_note_request)
+ .submit_tutorial_transaction(accounts[i + 1].id(), consume_note_request)
.await?;
+ rust_client::wait_for_transaction(&mut client, send_tx_id).await?;
println!(
- "Consumed Note Tx on MidenScan: https://testnet.midenscan.com/tx/{:?}",
+ "Consumed Note Tx on MidenScan: {}/tx/{:?}",
+ network.explorer_url(),
tx_id
);
println!(
@@ -350,33 +374,42 @@ async fn main() -> Result<(), ClientError> {
// Final resync and display account balances
tokio::time::sleep(Duration::from_secs(3)).await;
client.sync_state().await?;
- for account in accounts.clone() {
- let new_account = client.get_account(account.id()).await.unwrap().expect("account not found");
+ for (index, account) in accounts.iter().enumerate() {
+ let new_account = client.get_account(account.id()).await.unwrap().unwrap();
let balance = new_account
.vault()
- .get_balance(AssetVaultKey::new_fungible(
- faucet_account.id(),
- AssetCallbackFlag::Disabled,
- ))
+ .get_balance(AssetId::new_fungible(faucet_account.id()))
.unwrap();
println!(
"Account: {} balance: {}",
- account.id().to_bech32(NetworkId::Testnet),
+ account.id().to_bech32(network.network_id()),
balance
);
+ let expected = if index == 0 {
+ 80
+ } else if index == accounts.len() - 1 {
+ 20
+ } else {
+ 0
+ };
+ assert_eq!(
+ balance.as_u64(),
+ expected,
+ "unexpected transfer-chain balance"
+ );
}
Ok(())
}
```
-The output of our program will look something like this:
+The following is an abbreviated output. IDs and timings vary; each measured iteration includes confirmation polling, and funding logs are omitted:
```text
-Latest block: 227040
+Latest block:
[STEP 1] Deploying a new fungible faucet.
-Faucet account ID:
+Faucet account ID:
[STEP 2] Creating new accounts
account id 0:
@@ -384,101 +417,71 @@ account id 1:
account id 2:
account id 3:
account id 4:
-account id 5:
-account id 6:
-account id 7:
-account id 8:
-account id 9:
[STEP 3] Mint tokens
Minting tokens for Alice...
+Minted tokens. TX:
+Consumed minted note. TX:
[STEP 4] Create unauthenticated note tx chain
unauthenticated tx 1
sender:
target:
-Consumed Note Tx on MidenScan: https://testnet.midenscan.com/tx/0x31f48117c645c5b4ccff78ef356bad764798d4f207925e492ebbae1b86ef4f55
-Total time for loop iteration 0: 1.952243542s
+Created note. TX:
+Transaction committed:
+Transaction committed:
+Consumed Note Tx on MidenScan: https://testnet.midenscan.com/tx/
+Total time for loop iteration 0:
unauthenticated tx 2
sender:
target:
-Consumed Note Tx on MidenScan: https://testnet.midenscan.com/tx/0x45b4c62c6e8e79a1c7200d1c84dc6304a88debd37b20b069dd739498827354c1
-Total time for loop iteration 1: 2.091625458s
+Created note. TX:
+Transaction committed:
+Transaction committed:
+Consumed Note Tx on MidenScan: https://testnet.midenscan.com/tx/
+Total time for loop iteration 1:
unauthenticated tx 3
sender:
target:
-Consumed Note Tx on MidenScan: https://testnet.midenscan.com/tx/0xb2241e10df8f6f891b910975a3b4f4fd47657c47de164138300d683cfca5dd61
-Total time for loop iteration 2: 1.846021291s
+Created note. TX:
+Transaction committed:
+Transaction committed:
+Consumed Note Tx on MidenScan: https://testnet.midenscan.com/tx/
+Total time for loop iteration 2:
unauthenticated tx 4
sender:
target:
-Consumed Note Tx on MidenScan: https://testnet.midenscan.com/tx/0xd3ea6fa1da6c317f055ac4b069388d93b88d526039e01531879e75598e0f8cff
-Total time for loop iteration 3: 1.877627958s
-
-unauthenticated tx 5
-sender:
-target:
-Consumed Note Tx on MidenScan: https://testnet.midenscan.com/tx/0x6098638ec0ff7331432c037331ee7372977abe20af5c56315985fd314e21548d
-Total time for loop iteration 4: 1.884586875s
-
-unauthenticated tx 6
-sender:
-target:
-Consumed Note Tx on MidenScan: https://testnet.midenscan.com/tx/0x8258292e49e0cfdd96603450c2de6738afecb1e7482ede0fb68ea375e884e1d8
-Total time for loop iteration 5: 1.886505875s
-
-unauthenticated tx 7
-sender:
-target:
-Consumed Note Tx on MidenScan: https://testnet.midenscan.com/tx/0x9e0f84e00a9393bf6e5f224d55ccdf8bd0ef32ee20c3299e2dfccf1771001dfd
-Total time for loop iteration 6: 2.095149458s
-
-unauthenticated tx 8
-sender:
-target:
-Consumed Note Tx on MidenScan: https://testnet.midenscan.com/tx/0xa9db6445dfaa44ccf9dd52bf4cd8d9057946571ccb5299a7a56c59faf2ed2093
-Total time for loop iteration 7: 1.935587291s
-
-unauthenticated tx 9
-sender:
-target:
-Consumed Note Tx on MidenScan: https://testnet.midenscan.com/tx/0xba4bb4ae3c7aaf949cdd3be8c9ea52169f958e7dca8e9d4541fd5ac939393e41
-Total time for loop iteration 8: 1.964682833s
-
-Total execution time for unauthenticated note txs: 17.534611542s
-blocks: [BlockNumber(227047), BlockNumber(227047), BlockNumber(227047), BlockNumber(227047), BlockNumber(227047), BlockNumber(227047), BlockNumber(227047), BlockNumber(227047), BlockNumber(227047)]
+Created note. TX:
+Transaction committed:
+Transaction committed:
+Consumed Note Tx on MidenScan: https://testnet.midenscan.com/tx/
+Total time for loop iteration 3:
+
+Total execution time for unauthenticated note txs:
Account: balance: 80
Account: balance: 0
Account: balance: 0
Account: balance: 0
-Account: balance: 0
-Account: balance: 0
-Account: balance: 0
-Account: balance: 0
-Account: balance: 0
-Account: balance: 20
+Account: balance: 20
```
## Conclusion
-Unauthenticated notes on Miden offer a powerful mechanism for achieving faster asset settlements by allowing notes to be both created and consumed within the same block. In this guide, we walked through:
-
-- **Minting and Transacting with Unauthenticated Notes:** Building, serializing, and consuming notes quickly using the Miden client's "unauthenticated note" method.
-- **Performance Observations:** Measuring and demonstrating how unauthenticated notes enable assets to be sent faster than the blocktime.
+This example builds and serializes complete notes, then consumes the four transfer notes through `explicit_input_notes` with an explicitly unauthenticated mode. The earlier mint consumption uses `build_consume_notes` and may be authenticated. It confirms four transfers across five accounts and checks the tutorial-asset balances `[80, 0, 0, 0, 20]`; each account's native fee balance is separate.
-By following this guide, you should now have a clear understanding of how to build and deploy high-performance transactions using unauthenticated notes on Miden. Unauthenticated notes are the ideal approach for applications like central limit order books (CLOBs) or other DeFi platforms where transaction speed is critical.
+Applications can use this pattern to submit dependent transactions before the notes are committed. The node must still accept the creation transaction for its dependent consumption to settle.
### Running the example
-To run the unauthenticated note transfer example, navigate to the `rust-client` directory in the [miden-tutorials](https://github.com/0xMiden/miden-tutorials/) repository and run this command:
+From the root of your `tutorials` clone, run the checked-in example:
```bash
cd rust-client
-cargo run --release --bin unauthenticated_note_transfer
+TUTORIAL_NETWORK=testnet cargo run --release --bin unauthenticated_note_transfer
```
### Continue learning
diff --git a/docs/src/web-client/bridging_with_epoch_tutorial.md b/docs/src/web-client/bridging_with_epoch_tutorial.md
index 2dd9d20e..7c43886b 100644
--- a/docs/src/web-client/bridging_with_epoch_tutorial.md
+++ b/docs/src/web-client/bridging_with_epoch_tutorial.md
@@ -5,13 +5,13 @@ sidebar_position: 9
# Bridging Miden to and from EVM with Epoch
-_Move assets between Miden and Sepolia testnet through the Epoch protocol intent SDK, without writing a custom bridge_
+_Move assets between Miden testnet and Sepolia testnet through the Epoch protocol intent SDK, without writing a custom bridge_
## Overview
-This is a guided tour of the runnable reference app under [`examples/bridging-app/`](https://github.com/0xMiden/tutorials/tree/main/examples/bridging-app), which bridges fungible tokens between Miden and an EVM chain (Sepolia testnet) in both directions through the [Epoch protocol](https://epochprotocol.xyz/) intent SDK. Clone and run the app, then read the steps below as annotations on the integration points you'd port into your own Miden frontend. Every fenced code block is a verbatim slice of the app; the file and line range above each block points to the source.
+This is a guided tour of the reference app under [`examples/bridging-app/`](https://github.com/0xMiden/tutorials/tree/main/examples/bridging-app), which bridges fungible tokens between Miden testnet and Sepolia through the [Epoch protocol](https://epochprotocol.xyz/) intent SDK. Clone and run the app, then read the steps below as annotations on the integration points you'd port into your own Miden frontend. Every fenced code block is a verbatim slice of the app; the file and line range above each block points to the source.
-> **When to use Epoch vs Agglayer.** This tutorial uses **Epoch** because it is the only Miden bridge with a working TypeScript SDK, EVM-wallet integration, and broad chain coverage today — Epoch's Compact contract is deployed on Ethereum, Polygon, Optimism, Arbitrum, Base (mainnet) and on Sepolia plus six other EVM testnets. If your app is **authored in Rust/MASM, needs Polygon CDK ecosystem compatibility, or settles on a Polygon Agglayer-connected rollup**, the Agglayer protocol surface ships in-tree at [`protocol/crates/miden-agglayer/SPEC.md`](https://github.com/0xMiden/protocol/blob/next/crates/miden-agglayer/SPEC.md); Miden testnet ↔ Sepolia bridging via Agglayer went live on 2026-04-24.
+Live settlement requires an Epoch allocator and asset faucet deployed on the selected Miden network and compatible with v0.16. Devnet is available for explicit checks by setting `VITE_MIDEN_NETWORK`, `VITE_MIDEN_RPC_URL`, and `VITE_MIDEN_PROVER` to `devnet` with a matching allocator and faucet.
Stack: Vite + React 19 + TypeScript, `@miden-sdk/react`, `@epoch-protocol/epoch-intents-sdk`, [RainbowKit](https://www.rainbowkit.com/) + [wagmi](https://wagmi.sh/) + [viem](https://viem.sh/).
@@ -20,18 +20,18 @@ Stack: Vite + React 19 + TypeScript, `@miden-sdk/react`, `@epoch-protocol/epoch-
- Wire the Epoch SDK against a wagmi `walletClient`, including the chain-id override for Miden-source intents.
- Build a Miden → EVM bridge: reverse-quote, sign a P2IDE note via the MidenFi wallet adapter, submit the intent, and poll for settlement.
- Build the reverse EVM → Miden bridge: deposit an ERC-20 into Epoch's Compact contract and receive a P2ID note on Miden.
-- A `EpochIntentSDK` API reference card with all 11 public methods.
+- An `EpochIntentSDK` API reference card for quoting, settlement, and recovery.
- Inline pitfalls — the eleven traps every Epoch integration hits before the first successful round-trip.
## Prerequisites
You need three things to follow along.
-1. The reference app, cloned from this repo. The bridging-specific layer (Epoch SDK wiring, wagmi/RainbowKit + viem, intent forms, status panels) lives in `examples/bridging-app/`; `yarn create miden-app` (≥ 1.0.7) is the Miden + Vite + WASM scaffold it started from. Clone the repo, `cd examples/bridging-app`, then `cp .env.example .env` inside that directory and fill in: `VITE_RAINBOWKIT_PROJECT_ID` (a [WalletConnect Cloud](https://cloud.walletconnect.com/) project id — required), `VITE_ALLOCATOR_URL` (default `https://testnet-dev.epochprotocol.xyz`), `VITE_MIDEN_RPC_URL` (default `testnet`), `VITE_MIDEN_PROVER` (default `testnet`), and the optional `VITE_MIDENSCAN_URL`. See the [setup guide](./setup_guide.md) if this is your first Miden frontend.
+1. The reference app, cloned from this repo. The bridging-specific layer (Epoch SDK wiring, wagmi/RainbowKit + viem, intent forms, status panels) lives in `examples/bridging-app/`; `yarn create miden-app` (≥ 1.0.7) is the Miden + Vite + WASM scaffold it started from. Clone the repo, `cd examples/bridging-app`, then `cp .env.example .env` inside that directory and fill in: `VITE_RAINBOWKIT_PROJECT_ID` (a [WalletConnect Cloud](https://cloud.walletconnect.com/) project id — required), `VITE_ALLOCATOR_URL` (an Epoch allocator compatible with the pinned SDK), `VITE_MIDEN_NETWORK` (default `testnet`), optional `VITE_MIDEN_RPC_URL` and `VITE_MIDEN_PROVER` overrides, `VITE_MIDEN_USDC_FAUCET_ID` from the selected allocator deployment, and the optional `VITE_MIDENSCAN_URL`. The wallet network, RPC, faucet, and allocator must agree. Keep native MIDEN tokens in the wallet to pay both collateral and note-consumption fees. See the [setup guide](./setup_guide.md) if this is your first Miden frontend.
2. Two wallets: an EVM wallet supported by [RainbowKit](https://www.rainbowkit.com/) (MetaMask, Rabby, Coinbase Wallet, …) and the [MidenFi browser extension](https://chromewebstore.google.com/detail/miden-wallet/ablmompanofnodfdkgchkpmphailefpb) for signing P2IDE notes on Miden.
-3. A small Sepolia ETH balance for gas. The community [pk910 PoW faucet](https://sepolia-faucet.pk910.de/) pays 0.05–0.1 ETH per ~10-minute mining session; the [Google Cloud Sepolia faucet](https://cloud.google.com/application/web3/faucet/ethereum/sepolia) is the backup. Either covers the gas for `depositERC20AndRegister` plus a couple of allowance approvals.
+3. A small Sepolia ETH balance for gas. The community [pk910 PoW faucet](https://sepolia-faucet.pk910.de/) and [Google Cloud Sepolia faucet](https://cloud.google.com/application/web3/faucet/ethereum/sepolia) are possible sources; check their current requirements and limits. Keep enough test ETH for token approvals and the Compact deposit.
:::caution Do not set COOP/COEP headers
`@miden-sdk/vite-plugin` defaults to `crossOriginIsolation: true`, which sets `Cross-Origin-Opener-Policy` and `Cross-Origin-Embedder-Policy` headers on the dev server and breaks gRPC-Web to `transport.miden.io`. The reference app passes `{ crossOriginIsolation: false }` to opt out — see the [Vite + WASM setup guide](./setup_guide.md) for the deployment-side counterpart.
@@ -90,7 +90,7 @@ The snippet below sits inside the `useEpochIntent` hook — `walletClient` is `u
```
:::caution Do not follow the package README
-The npm package ships a `# Compact SDK` README that documents a different SDK and a different surface. Treat `EpochIntentSDK`'s exported method names from `dist/index.d.ts` as the source of truth — the [API Reference Card](#api-reference-card) below lists them.
+The npm package ships a `# Compact SDK` README that documents a different SDK and a different surface. Treat `EpochIntentSDK`'s exported method names from `dist/sdk/epoch-intent-sdk.d.ts` as the source of truth — the [API Reference Card](#api-reference-card) below lists them.
:::
The `useWithdrawIntent` hook keeps `walletClient.chain.id` untouched — the EVM → Miden direction uses the real Sepolia chain id (`11155111`).
@@ -99,9 +99,9 @@ The `useWithdrawIntent` hook keeps `walletClient.chain.id` untouched — the EVM
A Miden → EVM bridge runs four stages: `getTaskData` (the allocator computes a quote envelope), `getIntentQuote` (price discovery), `solveIntent` (the user signs a P2IDE note on Miden via the wallet adapter callback), and a 5-second polling loop against `getIntentStatus` until the solver lands the EVM transfer. The reference app's `buildEpochTaskDataParams` produces the envelope and computes `midenReclaimHeight` at the call site so it stays relative to the current Miden chain tip (Pitfalls row 4 has the technical reason).
-**From `examples/bridging-app/src/services/epoch-bridge.ts` (lines 141–173):**
+**From `examples/bridging-app/src/services/epoch-bridge.ts` (lines 133–165):**
-
+
```typescript
// Reclaim height must come from the call site as `currentMidenBlock + N`.
@@ -139,48 +139,51 @@ A Miden → EVM bridge runs four stages: `getTaskData` (the allocator computes a
};
```
-Once the quote returns and the user clicks **Confirm & sign**, the `createMidenP2IDNote` callback fires. The reference app's callback uses `useMidenFiWallet().requestSend` to construct an explicitly `'public'` P2IDE `SendTransaction`, guards the amount under `Number.MAX_SAFE_INTEGER` (the wallet adapter's `SendTransaction` constructor takes a `number`, not a `bigint`), and awaits a 120-second `waitForTransaction(txId, 120_000)` to read the output note id.
+Once the quote returns and the user clicks **Confirm & sign**, `createMidenP2IDENote` receives the allocator, relative recall window, and mandate-binding attachment from Epoch. The app builds a public P2IDE note containing those exact values, prepares a fee-aware custom request, and asks the wallet to sign it with `requestTransaction`. Amounts remain `bigint`. After confirmation it locates the exact collateral note ID, excluding any `TX_FEE` output.
-**From `examples/bridging-app/src/components/crosschain/IntentForm.tsx` (lines 200–243):**
+**From `examples/bridging-app/src/components/crosschain/IntentForm.tsx` (lines 202–248):**
-
+
```typescript
- const createMidenP2IDNote: SolveIntentParams['createMidenP2IDNote'] = async (
+ const createMidenP2IDENote: SolveIntentParams['createMidenP2IDENote'] = async (
faucetIdParam,
amountParam,
allocatorId,
+ recallBlocks,
+ bindingAttachmentFelts,
) => {
setConfirmStatus('Resource lock required — creating P2IDE note on Miden…');
try {
if (!midenAccountId) {
throw new Error('Missing Miden account id');
}
- if (!requestSend) {
- throw new Error('Miden wallet adapter is not connected');
+ if (!requestTransaction || !waitForTransaction || !client) {
+ throw new Error('Connect a Miden wallet that supports custom transactions and confirmation');
}
- const normalizedAmount = BigInt(amountParam);
- if (normalizedAmount > BigInt(Number.MAX_SAFE_INTEGER)) {
- throw new Error('Amount too large for wallet adapter send');
- }
-
- const payload = new SendTransaction(
- midenAccountId,
- allocatorId,
- faucetIdParam,
- 'public',
- Number(normalizedAmount),
+ const { request, expectedNoteId } = await runExclusive(async () => {
+ const head = await client.syncState();
+ const note = createEpochCollateralNote({
+ sender: midenAccountId, allocator: allocatorId, faucet: faucetIdParam,
+ amount: BigInt(amountParam), currentBlock: head.blockNum(),
+ recallBlocks, bindingAttachmentFelts,
+ });
+ const builder = await client.feeAwareTransactionRequestBuilder(AccountId.fromHex(midenAccountId));
+ return {
+ expectedNoteId: note.id().toString(),
+ request: builder.withOwnOutputNotes(new NoteArray([note])).build(),
+ };
+ });
+ const txId = await requestTransaction(
+ Transaction.createCustomTransaction(midenAccountId, allocatorId, request),
);
- const txId = await requestSend(payload);
- // Prefer adapter waitForTransaction to get the output note id.
- if (!waitForTransaction) {
- throw new Error('Miden wallet adapter is missing waitForTransaction');
- }
+ // Wait for the wallet to confirm the collateral before submitting it to Epoch.
const finalized = await waitForTransaction(txId, 120_000);
- const first = finalized.outputNotes?.[0];
- const noteId = first ? first.id().toString() : '';
+ // A fee-paying transaction also creates a TX_FEE note. Match our exact
+ // collateral note instead of assuming outputNotes[0] is the payment.
+ const noteId = finalized.outputNotes?.find(note => note.id().toString() === expectedNoteId)?.id().toString();
if (!noteId) {
throw new Error(`Could not read output note id for tx ${txId}`);
}
@@ -192,19 +195,19 @@ Once the quote returns and the user clicks **Confirm & sign**, the `createMidenP
};
```
-Success is signalled by the 5-second polling loop: `getIntentStatus` returns an `IntentTransactionStatus[]`, which the app reduces into the composite `IntentFlowStatus`. The forward bridge is settled once the destination chain reports a terminal-OK row (`evmCompleted`) and the synthetic Miden row carries a terminal `midenStatus` — the EVM transfer landed and the allocator consumed the P2IDE note. That reducer is destination-chain aware: it filters status rows to the chain the user selected, never reports completion while any destination-chain row is still `pending`, and takes the last destination-chain success — so an intermediate allocator/Compact row is never mistaken for the final settlement. The Pitfalls section below catalogues the gotchas this step inherits (public note type, awaiting `waitForTransaction`, advisory `midenFaucetDecimals`, the `Number.MAX_SAFE_INTEGER` guard).
+Success is signalled by the 5-second polling loop: `getIntentStatus` returns an `IntentTransactionStatus[]`, which the app reduces into the composite `IntentFlowStatus`. The forward bridge is settled once the destination chain reports a terminal-OK row (`evmCompleted`) and the synthetic Miden row carries a terminal `midenStatus` — the EVM transfer landed and the allocator consumed the P2IDE note. That reducer is destination-chain aware: it filters status rows to the chain the user selected, never reports completion while any destination-chain row is still `pending`, and takes the last destination-chain success — so an intermediate allocator/Compact row is never mistaken for the final settlement. The Pitfalls section below catalogues the gotchas this step inherits (public note type, awaiting `waitForTransaction`, advisory `midenFaucetDecimals`, the exact collateral-note ID check).
## Step 3: EVM → Miden bridge
The reverse direction lives in `buildEVMToMidenTaskDataParams` + `useWithdrawIntent`. The task envelope sets `destinationChainId` to the Miden virtual chain id (`999999999`) so the allocator's `getTokenDataFromMidenFaucetId` resolves the output side as Miden-native, and the note type flips to `P2ID` (not `P2IDE`) because the Miden recipient consumes the note directly rather than recalling it. The reverse-quote convention is the same as Step 2: pass `tokenInAmount: '0'` and a Miden-side `minTokenOut` in base units; the backend computes the required EVM input.
:::caution Bridge with headroom before the reverse direction
-The Step 3 reverse quote folds a route fee into the required deposit, so a Step 2 bridge of exactly 1 USDC won't cover a 1-USDC reverse — the quote asks for ~1.01 USDC and MetaMask flags `depositERC20AndRegister` as likely to fail (the `approve` lands first; rejecting the deposit is recoverable). Set Step 2's `min output` to about `2e18` for headroom, or run a second forward bridge before retrying.
+The reverse quote includes route fees, so bridging out one token may not leave enough to request one token back. Compare the quoted input amount with your balance before approval and deposit. Use the selected token's decimals when converting amounts to base units; do not assume an 18-decimal asset.
:::
-**From `examples/bridging-app/src/services/epoch-bridge.ts` (lines 224–245):**
+**From `examples/bridging-app/src/services/epoch-bridge.ts` (lines 216–237):**
-
+
```typescript
const taskDataParams = {
@@ -231,10 +234,10 @@ The Step 3 reverse quote folds a route fee into the required deposit, so a Step
return taskDataParams;
```
-`solveIntent({ ..., collateralType: CollateralType.EVM })` then walks the user's wallet through an ERC-20 `approve` (only on the first deposit of a given token) and `depositERC20AndRegister` / `depositNativeAndRegister` against Epoch's [Compact](https://docs.epochprotocol.xyz/epoch-miden-integration/integration-guide) contract on Sepolia. The intent nonce extracted from the solve result drives the same 5-second status poll as the forward direction.
+`solveIntent({ ..., collateralType: CollateralType.EVM })` then walks the user's wallet through an ERC-20 `approve` (only on the first deposit of a given token) and `depositERC20AndRegister` / `depositNativeAndRegister` against Epoch's [Compact](https://docs.epochprotocol.xyz/integration-guides/sdk-integration-guide) contract on Sepolia. The intent nonce extracted from the solve result drives the same 5-second status poll as the forward direction.
:::caution Forced-withdrawal preflight
-If the user cancelled a prior EVM → Miden intent on the same Compact deposit id, the next intent will revert. Call `sdk.disableForcedWithdrawal({ ... })` first; the SDK error message names the deposit id when this preflight is required.
+If the user cancelled a prior EVM → Miden intent on the same Compact deposit id, the next intent will revert. Call `sdk.disableForcedWithdrawal(depositId)` first; the SDK error message names the deposit id when this preflight is required.
:::
:::caution The Withdraw token is Epoch's test ERC-20, not Circle's USDC
@@ -243,47 +246,47 @@ The "USDC" the Withdraw form lists is Epoch's test token (`0x2BB4FfD7…`), not
## Step 4: The bridged P2ID note is consumed by your wallet
-Step 3's allocator delivers its output as a **P2ID note** addressed to your Miden account, not as a vault credit. In Miden's actor model, a note must be _consumed_ in a transaction before it becomes spendable balance. The Miden Wallet consumes incoming P2ID notes when it detects them, so the bridged funds appear as wallet balance within seconds and are immediately usable as the source for another Miden → EVM bridge. The reference app's `WithdrawConsume` component keeps this step informational: it shows the delivered note ID and links to Midenscan so you can confirm settlement.
+Step 3's allocator delivers its output as a **P2ID note** addressed to your Miden account, not as a vault credit. The note must be consumed in a transaction before it becomes spendable. Wallet auto-consumption depends on the installed wallet's configuration and sufficient native MIDEN for fees: a USDC-only note cannot pay that native fee. The reference app links the delivered note to Midenscan; confirm its consumption and the final wallet balance before treating the bridge as complete.
## API Reference Card
-Most apps only touch four methods (`getTaskData`, `getIntentQuote`, `solveIntent`, `getIntentStatus`); the rest cover recovery and read-only queries. Sources cite `dist/index.d.ts` from `@epoch-protocol/epoch-intents-sdk@1.0.23`.
-
-| Method | Signature (abridged) | Use it to | Source |
-| --------------------------- | ------------------------------------------------------------------------ | ---------------------------------------------------------------------- | -------------------- |
-| `getTaskData` | `(params: GetTaskDataParams) => Promise<{ taskTypeString, intentData }>` | Construct the SIO envelope before quoting | `dist/index.d.ts:11` |
-| `solveIntent` | `(params: SolveIntentParams) => Promise` | Submit the intent + run the optional Miden P2ID note callback | `dist/index.d.ts:12` |
-| `getIntentQuote` | `(params: GetIntentQuoteParams) => Promise` | Reverse-quote (`tokenInAmount: '0'`) or forward quote | `dist/index.d.ts:13` |
-| `retryIntentSolve` | `(id: string) => Promise` | Re-run the solver if a transient failure is observed | `dist/index.d.ts:14` |
-| `initateDepositWithdrawal` | `(id: string) => Promise` | Initiate a forced withdrawal flow (verbatim misspelling; see Pitfalls) | `dist/index.d.ts:16` |
-| `disableForcedWithdrawal` | `(params: DisableForcedWithdrawalParams) => Promise` | Cancel a pending forced withdrawal so a new intent can solve | `dist/index.d.ts:17` |
-| `withdrawToken` | `(params: WithdrawTokenParams) => Promise` | Reclaim an unfulfilled EVM-side deposit | `dist/index.d.ts:18` |
-| `getForcedWithdrawalStatus` | `(id: string) => Promise` | Observe a forced-withdrawal lifecycle | `dist/index.d.ts:19` |
-| `getDepositedBalances` | `(addr: string) => Promise` | List the user's locked balances in the Compact | `dist/index.d.ts:20` |
-| `getIntentStatus` | `(addr: string, nonce: string) => Promise` | Drive the 5s polling loop | `dist/index.d.ts:21` |
-| `getHealthCheck` | `() => Promise` | Probe allocator availability before quoting | `dist/index.d.ts:22` |
+Most apps only touch four methods. The selected signatures below come from `dist/sdk/epoch-intent-sdk.d.ts` in the pinned `@epoch-protocol/epoch-intents-sdk@1.0.38`.
+
+| Method | Signature (abridged) | Purpose |
+| --------------------------- | ------------------------------------------------------------- | ---------------------------- |
+| `getTaskData` | `(params: GetTaskDataParams)` | Build the quote envelope |
+| `getIntentQuote` | `({ sponsorAddress, taskTypeString, intentData, isNative? })` | Obtain a quote |
+| `solveIntent` | `(params: SolveIntentParams)` | Sign and submit the intent |
+| `getIntentStatus` | `(userAddress: string, nonce: string)` | Poll settlement |
+| `retryIntentSolve` | `(request: CompactRequest)` | Retry a recorded allocation |
+| `initateDepositWithdrawal` | `(id: string)` | Initiate forced withdrawal |
+| `disableForcedWithdrawal` | `(id: string)` | Cancel forced withdrawal |
+| `withdrawToken` | `(id: string, recipient: string, amount: string)` | Withdraw a deposit |
+| `getForcedWithdrawalStatus` | `(account: string, id: string)` | Inspect recovery status |
+| `getDepositedBalances` | `(account: string, tokens: CompactTokenBalanceInput[])` | Inspect locked balances |
+| `getHealthCheck` | `()` | Probe allocator availability |
Recovery primitives (`retryIntentSolve`, `disableForcedWithdrawal`, `withdrawToken`, `initateDepositWithdrawal`) are the difference between an intent flow that "mostly works" and one that lets users recover from solver outages or network failures.
## Pitfalls
-Eleven traps every Epoch integration hits before the first successful round-trip. The reference app ships mitigations for each.
+Check these integration details before a live round trip:
-- **Don't follow the npm package README.** It documents an unrelated SDK; the [integration guide](https://docs.epochprotocol.xyz/epoch-miden-integration/integration-guide) and `dist/index.d.ts` are the source of truth.
+- **Don't follow the npm package README.** It documents an unrelated SDK; the [integration guide](https://docs.epochprotocol.xyz/integration-guides/sdk-integration-guide) and `dist/sdk/epoch-intent-sdk.d.ts` are the source of truth.
- **Public notes only.** P2IDE notes for the allocator must be `'public'`; a `'private'` note is invisible to the solver.
-- **Always await `waitForTransaction`.** Reading `outputNotes[0]` early returns an empty array; the 120-second timeout covers proving + testnet submission.
-- **Reclaim height is `currentBlock + N`.** `midenReclaimHeight` is absolute; use `useSyncState().syncHeight + 1000` at the call site, never a literal.
+- **Await confirmation and match the note ID.** A fee-paying transaction also creates a `TX_FEE` output. Do not pass `outputNotes[0]` to Epoch; locate the exact collateral note ID after `waitForTransaction`.
+- **Honor Epoch’s callback window and binding.** `createMidenP2IDENote` supplies `recallBlocks` and `bindingAttachmentFelts`. Build a public P2IDE with `currentBlock + recallBlocks` after a fresh sync and include the attachment verbatim. A plain `SendTransaction` cannot represent this attachment. The quote’s preliminary reclaim-height field is not a substitute for the callback values.
- **`minTokenOut` is base units.** The reverse-quote path passes it straight through — no `parseUnits`. For an 18-decimal token, `"1000000000000000000"` is one whole unit.
- **Override `walletClient.chain.id` for Miden-source intents.** Set `chain.id = 999999999` for Miden → EVM only; leave it as the real EVM chain id for the reverse direction.
- **`midenFaucetDecimals` is advisory.** Fall back to UI-selected decimals when the allocator value would change the displayed amount by an order of magnitude.
-- **`Number.MAX_SAFE_INTEGER` guard.** The wallet adapter's `SendTransaction` constructor takes a `number`; guard the amount before casting.
-- **No COOP/COEP on the dev server.** `midenVitePlugin({ crossOriginIsolation: false })` is mandatory — the default breaks gRPC-Web to `transport.miden.io`.
+- **Keep collateral amounts as `bigint`.** The custom request uses `FungibleAsset` directly, avoiding a lossy `number` conversion.
+- **Check browser cross-origin compatibility.** This single-threaded app uses `midenVitePlugin({ crossOriginIsolation: false })`; verify the chosen RPC and wallet support before enabling cross-origin isolation.
- **Forced-withdrawal preflight.** Call `sdk.disableForcedWithdrawal` before re-running an EVM → Miden intent on a deposit id the user cancelled previously.
- **`initateDepositWithdrawal` (misspelling).** The SDK exports the method with the typo — use it verbatim, do not silently rename.
## Where to go next
- The runnable [`examples/bridging-app/`](https://github.com/0xMiden/tutorials/tree/main/examples/bridging-app) is the canonical reference; every code block above is a paste-verified slice of it.
-- The [Epoch protocol integration guide](https://docs.epochprotocol.xyz/epoch-miden-integration/integration-guide) covers the SDK surface in depth, including the parts this tutorial does not exercise (multi-hop intents, custom resource locks).
+- The [Epoch protocol integration guide](https://docs.epochprotocol.xyz/integration-guides/sdk-integration-guide) covers the SDK surface in depth, including the parts this tutorial does not exercise (multi-hop intents, custom resource locks).
- Upstream Epoch example: [`epochprotocol/miden-integration-example`](https://github.com/epochprotocol/miden-integration-example). The reference app forks this with the adaptations documented in its README.
- The companion [React wallet tutorial](./react_wallet_tutorial.md) walks the `@miden-sdk/react` hook surface end-to-end if you want a deeper foundation before extending the bridging app.
diff --git a/docs/src/web-client/counter_contract_tutorial.md b/docs/src/web-client/counter_contract_tutorial.md
index 024c4f58..53349cf6 100644
--- a/docs/src/web-client/counter_contract_tutorial.md
+++ b/docs/src/web-client/counter_contract_tutorial.md
@@ -5,6 +5,13 @@ sidebar_position: 5
_Using the Miden client to interact with a custom smart contract_
+:::note v0.16 setup
+
+Follow the [network and fee setup](./setup_guide.md#network-and-fee-setup)
+and copy the shared support files imported by the complete example.
+
+:::
+
## Overview
In this tutorial, we will deploy a custom counter smart contract and increment its count using the Miden client. Each run creates a fresh counter account, deploys it to the network, and immediately calls its `increment_count` procedure via a transaction script — so the final count is always `1`.
@@ -42,18 +49,20 @@ This tutorial assumes you have a basic understanding of Miden assembly. To quick
3. Install the Miden SDK:
```bash
- yarn add @miden-sdk/miden-sdk@0.15.2
+ yarn add @miden-sdk/miden-sdk@0.16.0
```
-**NOTE!**: Be sure to add the `--webpack` command to your `package.json` when running the `dev script`. The dev script should look like this:
+The current Next.js template uses Turbopack by default. These examples use the webpack configuration from the setup guide, so update both scripts in `package.json`:
`package.json`
```json
+{
"scripts": {
"dev": "next dev --webpack",
- ...
+ "build": "next build --webpack"
}
+}
```
## Step 2: Edit the `app/page.tsx` file:
@@ -111,35 +120,53 @@ Create the file `lib/masm/counter_contract.masm` with the following Miden Assemb
```masm
use miden::protocol::active_account
use miden::protocol::native_account
-use miden::core::word
use miden::core::sys
+# CONSTANTS
+# =================================================================================================
+
const COUNTER_SLOT = word("miden::tutorials::counter")
-#! Inputs: []
-#! Outputs: [count]
-pub proc get_count
+# PUBLIC INTERFACE
+# =================================================================================================
+
+#! Returns the current count.
+#!
+#! Inputs: [pad(16)]
+#! Outputs: [count, pad(15)]
+#!
+#! Invocation: call
+@account_procedure
+pub proc get_count() -> felt
push.COUNTER_SLOT[0..2] exec.active_account::get_item
- # => [count]
+ # => [[count, 0, 0, 0], pad(16)]
exec.sys::truncate_stack
- # => [count]
+ # => [count, pad(15)]
end
-#! Inputs: []
-#! Outputs: []
-pub proc increment_count
+#! Increments the current count by one.
+#!
+#! Inputs: [pad(16)]
+#! Outputs: [pad(16)]
+#!
+#! Invocation: call
+@account_procedure
+pub proc increment_count()
push.COUNTER_SLOT[0..2] exec.active_account::get_item
- # => [count]
+ # => [[count, 0, 0, 0], pad(16)]
add.1
- # => [count+1]
+ # => [[count + 1, 0, 0, 0], pad(16)]
push.COUNTER_SLOT[0..2] exec.native_account::set_item
- # => []
+ # => [OLD_VALUE, pad(16)]
+
+ dropw
+ # => [pad(16)]
exec.sys::truncate_stack
- # => []
+ # => [pad(16)]
end
```
@@ -159,17 +186,11 @@ Add an `asset/source` webpack rule so `.masm` files are imported as plain text s
Open `next.config.ts` and add the following rule inside the `webpack` callback:
```ts
-webpack: (config, { isServer }) => {
- // ... existing WASM config ...
-
- // Import .masm files as strings
- config.module.rules.push({
- test: /\.masm$/,
- type: "asset/source",
- });
-
- return config;
-},
+// Import .masm files as strings. Keep the existing WASM configuration.
+config.module.rules.push({
+ test: /\.masm$/,
+ type: "asset/source",
+});
```
:::tip Other bundlers
@@ -193,11 +214,14 @@ Copy and paste the following code into the `lib/incrementCounterContract.ts` fil
import counterContractCode from './masm/counter_contract.masm';
import {
AuthSecretKey,
- StorageMode,
StorageSlot,
StorageResult,
- MidenClient,
} from '@miden-sdk/miden-sdk/lazy';
+import {
+ createFundableContractAccount,
+ createTutorialClient,
+ fundAccountForFees,
+} from './feeSupport';
export async function incrementCounterContract(): Promise {
if (typeof window === 'undefined') {
@@ -205,17 +229,11 @@ export async function incrementCounterContract(): Promise {
return;
}
- // Wait for the WASM module to finish initializing before touching any
- // wasm-bindgen type (see setup_guide.md "Entry points: eager vs lazy").
- await MidenClient.ready();
-
- const nodeEndpoint = 'https://rpc.testnet.miden.io';
- const client = await MidenClient.create({ rpcUrl: nodeEndpoint });
+ const client = await createTutorialClient({ proverUrl: 'local' });
console.log('Current block number: ', (await client.sync()).blockNum());
const counterSlotName = 'miden::tutorials::counter';
- // Compile the counter component
const counterAccountComponent = await client.compile.component({
code: counterContractCode,
slots: [StorageSlot.emptyValue(counterSlotName)],
@@ -223,23 +241,37 @@ export async function incrementCounterContract(): Promise {
const walletSeed = new Uint8Array(32);
crypto.getRandomValues(walletSeed);
-
const auth = AuthSecretKey.rpoFalconWithRNG(walletSeed);
- // Create the counter contract account
- const account = await client.accounts.create({
- storage: StorageMode.Public,
- seed: walletSeed,
+ const account = await createFundableContractAccount(
+ client,
+ walletSeed,
auth,
- components: [counterAccountComponent],
- });
+ [counterAccountComponent],
+ );
+
+ await fundAccountForFees(client, account);
- // Building the transaction script which will call the counter contract
const txScriptCode = `
- use external_contract::counter_contract
- begin
+use external_contract::counter_contract
+
+#! Increments the counter.
+#!
+#! Inputs: [ARGS, pad(12)]
+#! Outputs: [pad(16)]
+#!
+#! Where:
+#! - ARGS contains unused transaction script arguments.
+#!
+#! Invocation: dyncall
+@transaction_script
+pub proc main(args: word)
+ dropw
+ # => [pad(16)]
+
call.counter_contract::increment_count
- end
+ # => [pad(16)]
+end
`;
const script = await client.compile.txScript({
@@ -252,34 +284,33 @@ export async function incrementCounterContract(): Promise {
],
});
- // Executing the transaction script against the counter contract — this
- // deploys the counter and runs `increment_count` in a single transaction.
- await client.transactions.execute({
+ await client.sync();
+ const { txId } = await client.transactions.execute({
account,
script,
+ waitForConfirmation: true,
+ timeout: 120_000,
});
+ console.log(`Transaction committed: ${txId.toHex()}`);
console.log('Counter contract ID:', account.id().toString());
- // Logging the count of the counter contract we just incremented
const counter = await client.accounts.get(account);
-
// `getItem()` is typed to return a low-level `Word`, but at runtime the SDK
// wraps the slot in a `StorageResult` whose `toBigInt()` reads the first
// felt — the count. The cast reflects that runtime type.
const count = counter?.storage().getItem(counterSlotName) as unknown as
- | StorageResult
- | undefined;
-
+ StorageResult | undefined;
const counterValue = Number(count!.toBigInt());
-
+ if (counterValue !== 1)
+ throw new Error(`Expected counter 1, got ${counterValue}`);
console.log('Count: ', counterValue);
}
```
To run the code above in our frontend, run the following command:
-```
+```bash
yarn dev
```
@@ -312,52 +343,74 @@ Count: 1
4. Adds `1` to the count value returned from `active_account::get_item`.
5. Pushes the slot ID prefix and suffix again so we can write the updated count.
6. Calls `native_account::set_item` which saves the incremented count to storage.
-7. Calls `sys::truncate_stack` to truncate the stack to size 16.
+7. Drops the previous storage word returned by `set_item`.
+8. Calls `sys::truncate_stack` to leave only the 16 padding elements.
```masm
use miden::protocol::active_account
use miden::protocol::native_account
-use miden::core::word
use miden::core::sys
+# CONSTANTS
+# =================================================================================================
+
const COUNTER_SLOT = word("miden::tutorials::counter")
-#! Inputs: []
-#! Outputs: [count]
-pub proc get_count
+# PUBLIC INTERFACE
+# =================================================================================================
+
+#! Returns the current count.
+#!
+#! Inputs: [pad(16)]
+#! Outputs: [count, pad(15)]
+#!
+#! Invocation: call
+@account_procedure
+pub proc get_count() -> felt
push.COUNTER_SLOT[0..2] exec.active_account::get_item
- # => [count]
+ # => [[count, 0, 0, 0], pad(16)]
exec.sys::truncate_stack
- # => [count]
+ # => [count, pad(15)]
end
-#! Inputs: []
-#! Outputs: []
-pub proc increment_count
+#! Increments the current count by one.
+#!
+#! Inputs: [pad(16)]
+#! Outputs: [pad(16)]
+#!
+#! Invocation: call
+@account_procedure
+pub proc increment_count()
push.COUNTER_SLOT[0..2] exec.active_account::get_item
- # => [count]
+ # => [[count, 0, 0, 0], pad(16)]
add.1
- # => [count+1]
+ # => [[count + 1, 0, 0, 0], pad(16)]
push.COUNTER_SLOT[0..2] exec.native_account::set_item
- # => []
+ # => [OLD_VALUE, pad(16)]
+
+ dropw
+ # => [pad(16)]
exec.sys::truncate_stack
- # => []
+ # => [pad(16)]
end
```
-**Note**: _It's a good habit to add comments below each line of MASM code with the expected stack state. This improves readability and helps with debugging._
+The examples follow the [protocol MASM conventions](https://github.com/0xMiden/protocol/tree/next/.claude/skills): public procedures declare typed signatures and invocation style, and stack comments list the top element first. Calls return 16 stack elements, including `pad(N)` padding; storage values are four-element words.
### Authentication Component
-**Important**: All accounts must have an authentication component. For smart contracts that do not require authentication (like our counter contract), we use a `NoAuth` component.
+The counter uses a Falcon single-signature authentication component. The client
+stores the secret key and signs transactions that increment the counter. Public
+storage lets other accounts read its state through FPI; it does not grant them
+permission to update it.
-This `NoAuth` component allows any user to interact with the smart contract without requiring signature verification.
-
-**Note**: _Adding the `account::incr_nonce` to a state changing procedure allows any user to call the procedure._
+The account also includes `BasicWallet` so it can consume a native-asset funding
+note and pay transaction fees. The `createFundableContractAccount` helper adds
+both components and registers the account and key in the client.
### Compiling the account component
@@ -372,17 +425,19 @@ const counterAccountComponent = await client.compile.component({
### Creating the contract account
-Use `client.accounts.create()` to build and register the contract. Passing `components` makes this a contract account, so no account type is needed — `storage` selects visibility (`StorageMode.Public` here). You must supply a `seed` (for deterministic ID derivation) and a raw `AuthSecretKey` — the client stores the key automatically:
+Use the repository helper to build the account with authentication, the custom
+counter component, and `BasicWallet`, then fund it before executing a script:
```ts
const auth = AuthSecretKey.rpoFalconWithRNG(walletSeed);
-const account = await client.accounts.create({
- storage: StorageMode.Public,
- seed: walletSeed,
+const account = await createFundableContractAccount(
+ client,
+ walletSeed,
auth,
- components: [counterAccountComponent],
-});
+ [counterAccountComponent],
+);
+await fundAccountForFees(client, account);
```
### Compiling and executing the custom script
@@ -401,12 +456,15 @@ const script = await client.compile.txScript({
});
```
-Then execute it with `client.transactions.execute()`:
+Synchronize, execute the script, and wait for commitment:
```ts
+await client.sync();
await client.transactions.execute({
account,
script,
+ waitForConfirmation: true,
+ timeout: 120_000,
});
```
@@ -417,8 +475,22 @@ This is the Miden assembly script that calls the `increment_count` procedure dur
```masm
use external_contract::counter_contract
-begin
+#! Increments the counter.
+#!
+#! Inputs: [ARGS, pad(12)]
+#! Outputs: [pad(16)]
+#!
+#! Where:
+#! - ARGS contains unused transaction script arguments.
+#!
+#! Invocation: dyncall
+@transaction_script
+pub proc main(args: word)
+ dropw
+ # => [pad(16)]
+
call.counter_contract::increment_count
+ # => [pad(16)]
end
```
@@ -429,20 +501,23 @@ To run a full working example navigate to the `web-client` directory in the [mid
```bash
cd web-client
yarn install
-yarn start
+yarn dev
```
### Resetting the `MidenClientDB`
-The Miden webclient stores account and note data in the browser. If you get errors such as "Failed to build MMR", then you should reset the Miden webclient store. When switching between Miden networks such as from localhost to testnet be sure to reset the browser store. To clear the account and node data in the browser, paste this code snippet into the browser console:
+The Miden webclient stores account and note data in IndexedDB. Stop or terminate the tutorial client and close other tabs using its store before resetting it. This deletes local account data and keys, so use it only for disposable tutorial accounts. The following browser-console snippet deletes the default testnet `MidenClientDB_mtst` store after the deletion request completes; change `name` if you configured a different store.
```javascript
(async () => {
- const dbs = await indexedDB.databases();
- for (const db of dbs) {
- await indexedDB.deleteDatabase(db.name);
- console.log(`Deleted database: ${db.name}`);
- }
- console.log('All databases deleted.');
+ const name = 'MidenClientDB_mtst';
+ await new Promise((resolve, reject) => {
+ const request = indexedDB.deleteDatabase(name);
+ request.onsuccess = () => resolve();
+ request.onerror = () => reject(request.error);
+ request.onblocked = () =>
+ reject(new Error('Close clients and tabs using this store, then retry.'));
+ });
+ console.log(`Deleted database: ${name}`);
})();
```
diff --git a/docs/src/web-client/create_deploy_tutorial.md b/docs/src/web-client/create_deploy_tutorial.md
index 8ef72419..15aa44b4 100644
--- a/docs/src/web-client/create_deploy_tutorial.md
+++ b/docs/src/web-client/create_deploy_tutorial.md
@@ -7,6 +7,15 @@ import { CodeSdkTabs } from '@site/src/components';
_Using the Miden client in TypeScript to create accounts and deploy faucets_
+:::note v0.16 setup
+
+Follow the [network and fee setup](./setup_guide.md#network-and-fee-setup)
+and copy the shared support files imported by the complete example.
+For React snippets, initialize `authScheme` with `await tutorialAuthScheme()`
+as shown in the complete example.
+
+:::
+
## Overview
In this tutorial, we'll build a simple Next.js application that demonstrates the fundamentals of interacting with the Miden blockchain using the Miden SDK. We'll walk through creating a Miden account for Alice and deploying a fungible faucet contract that can mint tokens. This sets the foundation for more complex operations like issuing assets and transferring them between accounts.
@@ -29,13 +38,13 @@ In this tutorial, we'll build a simple Next.js application that demonstrates the
Before we dive into code, a quick refresher:
- **Public accounts**: The account's data and code are stored on-chain and are openly visible, including its assets.
-- **Private accounts**: The account's state and logic are off-chain, only known to its owner.
+- **Private accounts**: The account's state and logic are kept off-chain. The owner retains them locally and can share them; the chain records the account commitment.
- **Public notes**: The note's state is visible to anyone - perfect for scenarios where transparency is desired.
- **Private notes**: The note's state is stored off-chain, you will need to share the note data with the relevant parties (via email or Telegram) for them to be able to consume the note.
> **Important**: In Miden, "accounts" and "smart contracts" can be used interchangeably due to native account abstraction. Every account is programmable and can contain custom logic.
-It is useful to think of notes on Miden as "cryptographic cashier's checks" that allow users to send tokens. If the note is private, the note transfer is only known to the sender and receiver.
+It is useful to think of notes on Miden as "cryptographic cashier's checks" that allow users to send tokens. Private note details must be shared with the recipient. The chain still records public note metadata, the note commitment, and its eventual nullifier; privacy also depends on how the details and transaction witness are shared.
## Step 1: Initialize your Next.js project
@@ -56,19 +65,21 @@ It is useful to think of notes on Miden as "cryptographic cashier's checks" that
3. Install the Miden SDK:
-**NOTE!**: Be sure to add the `--webpack` command to your `package.json` when running the `dev script`. The dev script should look like this:
+The current Next.js template uses Turbopack by default. These SDK examples use the webpack configuration from the setup guide, so update both scripts in `package.json`:
`package.json`
```json
+{
"scripts": {
"dev": "next dev --webpack",
- ...
+ "build": "next build --webpack"
}
+}
```
## Step 2: Set up the Miden client
@@ -80,7 +91,7 @@ The Miden client is your gateway to interact with the Miden blockchain. It handl
First, we'll create a separate file for our blockchain logic. In the project root, create a folder `lib/` and inside it `lib/react/createMintConsume.tsx` (React) or `lib/createMintConsume.ts` (TypeScript):
```bash
-mkdir -p lib
+mkdir -p lib/react
```
{
.// wasm-bindgen type (see setup_guide.md "Entry points: eager vs lazy").
.await MidenClient.ready();
-.// Connect to Miden testnet RPC endpoint
-.const client = await MidenClient.create({
-..rpcUrl: 'https://rpc.testnet.miden.io',
+.// Connect to Miden testnet with local proving
+.const client = await MidenClient.createTestnet({
+..proverUrl: 'local',
.});
.// 1. Sync with the latest blockchain state
@@ -215,11 +226,11 @@ Back in your library file, extend the function:
react: { code: `const run = async () => {
.// 1. Create Alice's wallet (public, mutable)
.console.log('Creating account for Alice…');
-.const alice = await createWallet({ storageMode: StorageMode.Public });
+.const alice = await createWallet({ storageMode: StorageMode.Public, authScheme });
.console.log('Alice ID:', alice.id().toString());
};` },
typescript: { code: `// lib/createMintConsume.ts
-import { MidenClient } from '@miden-sdk/miden-sdk/lazy';
+import { MidenClient, StorageMode } from '@miden-sdk/miden-sdk/lazy';
export async function createMintConsume(): Promise {
.if (typeof window === 'undefined') {
@@ -231,8 +242,8 @@ export async function createMintConsume(): Promise {
.// wasm-bindgen type (see setup_guide.md "Entry points: eager vs lazy").
.await MidenClient.ready();
-.const client = await MidenClient.create({
-..rpcUrl: 'https://rpc.testnet.miden.io',
+.const client = await MidenClient.createTestnet({
+..proverUrl: 'local',
.});
.// 1. Sync with the latest blockchain state
@@ -252,32 +263,35 @@ export async function createMintConsume(): Promise {
A faucet in Miden is a special type of account that can mint new tokens. Think of it as your own token factory. Let's deploy one that will create our custom "MID" tokens.
-Add this code after creating Alice's account:
+Add this code after creating Alice’s account. Use `fundAccount` from `useTutorialSupport` in React, or import `fundAccountForFees` from `./feeSupport` in TypeScript, as shown in the complete example. Consuming native fee funding is the first transaction that deploys each account.
@@ -296,7 +310,7 @@ console.log('Setup complete.');` },
In this tutorial, we've successfully:
1. Set up a Next.js application with the Miden SDK
-2. Connected to the Miden testnet
+2. Connected to Miden testnet
3. Created a wallet account for Alice
4. Deployed a fungible faucet that can mint custom tokens
@@ -305,51 +319,81 @@ Your final `lib/react/createMintConsume.tsx` (React) or `lib/createMintConsume.t
{
-..// 1. Create Alice's wallet (public, mutable)
-..console.log('Creating account for Alice…');
-..const alice = await createWallet({ storageMode: StorageMode.Public });
+..console.log('Synchronizing before creating accounts…');
+..await sync();
+..console.log('Creating Alice with useCreateWallet…');
+..const authScheme = await tutorialAuthScheme();
+..// Native fee tokens and the tutorial's MID token are separate assets.
+..const alice = await createWallet({
+...storageMode: StorageMode.Public,
+...authScheme,
+..});
..console.log('Alice ID:', alice.id().toString());
+..await fundAccount(alice);
-..// 2. Deploy a fungible faucet
-..console.log('Creating faucet…');
+..// v0.16 faucets include BasicWallet, so they can receive fee funding.
..const faucet = await createFaucet({
...tokenSymbol: 'MID',
...decimals: 8,
...maxSupply: BigInt(1_000_000),
...storageMode: StorageMode.Public,
+...authScheme,
..});
..console.log('Faucet ID:', faucet.id().toString());
+..await fundAccount(faucet);
..console.log('Setup complete.');
.};
.return (
-..
-...
-..
+..
.);
}
export default function CreateMintConsume() {
.return (
-..
+..
...
..
.);
}`},
- typescript: { code:`// lib/createMintConsume.ts
-import { MidenClient, StorageMode } from '@miden-sdk/miden-sdk/lazy';
+ typescript: { code: `// lib/createMintConsume.ts
+import { StorageMode } from '@miden-sdk/miden-sdk/lazy';
+import {
+.createTutorialClient,
+.fundAccountForFees,
+} from './feeSupport';
export async function createMintConsume(): Promise {
.if (typeof window === 'undefined') {
@@ -357,12 +401,8 @@ export async function createMintConsume(): Promise {
..return;
.}
-.// Wait for the WASM module to finish initializing before touching any
-.// wasm-bindgen type (see setup_guide.md "Entry points: eager vs lazy").
-.await MidenClient.ready();
-
-.const client = await MidenClient.create({
-..rpcUrl: 'https://rpc.testnet.miden.io',
+.const client = await createTutorialClient({
+..proverUrl: 'local',
.});
.// 1. Sync with the latest blockchain state
@@ -376,7 +416,8 @@ export async function createMintConsume(): Promise {
.});
.console.log('Alice ID:', alice.id().toString());
-.// 3. Deploy a fungible faucet
+.// 3. Create our own fungible faucet. SDK v0.16 includes BasicWallet,
+.// allowing both accounts to consume native fee funding before minting MID.
.console.log('Creating faucet…');
.const faucet = await client.accounts.create({
..type: 0, // 0 = FungibleFaucet
@@ -386,6 +427,8 @@ export async function createMintConsume(): Promise {
..storage: StorageMode.Public,
.});
.console.log('Faucet ID:', faucet.id().toString());
+.await fundAccountForFees(client, alice);
+.await fundAccountForFees(client, faucet);
.console.log('Setup complete.');
}` },
@@ -393,6 +436,8 @@ export async function createMintConsume(): Promise {
### Running the example
+From the parent directory of `miden-web-app`:
+
```bash
cd miden-web-app
yarn install
@@ -402,11 +447,11 @@ yarn dev
Open [http://localhost:3000](http://localhost:3000) in your browser, click **Tutorial #1: Create a wallet and deploy a faucet**, and check the browser console (F12 or right-click → Inspect → Console):
```
-Latest block: 2247
+Latest block number:
Creating account for Alice…
-Alice ID: 0xd70b2072c6495d100000869a8bacf2
+Alice ID:
Creating faucet…
-Faucet ID: 0x2d7e506fb88dde200000a1386efec8
+Faucet ID:
Setup complete.
```
diff --git a/docs/src/web-client/creating_multiple_notes_tutorial.md b/docs/src/web-client/creating_multiple_notes_tutorial.md
index bb6ba85e..a4751a36 100644
--- a/docs/src/web-client/creating_multiple_notes_tutorial.md
+++ b/docs/src/web-client/creating_multiple_notes_tutorial.md
@@ -7,13 +7,22 @@ import { CodeSdkTabs } from '@site/src/components';
_Using the Miden client in TypeScript to create several P2ID notes in a single transaction_
+:::note v0.16 setup
+
+Follow the [network and fee setup](./setup_guide.md#network-and-fee-setup)
+and copy the shared support files imported by the complete example.
+For React snippets, initialize `authScheme` with `await tutorialAuthScheme()`
+as shown in the complete example.
+
+:::
+
## Overview
In the previous sections we learned how to create accounts, deploy faucets, and mint tokens. In this tutorial we will:
- **Mint** test tokens from a faucet to Alice
- **Consume** the minted notes so the assets appear in Alice's wallet
-- **Create three P2ID notes in a _single_ transaction** using a custom note‑script and delegated proving
+- **Create three P2ID notes in a _single_ transaction** using standard P2ID notes and delegated proving
The entire flow is wrapped in a helper called `multiSendWithDelegatedProver()` that you can call from any browser page.
@@ -33,9 +42,9 @@ The entire flow is wrapped in a helper called `multiSendWithDelegatedProver()` t
Before diving into our code example, let's clarify what in the world "delegated proving" actually is.
-Delegated proving is the process of outsourcing a part of the ZK proof generation of your transaction to a third party. For certain computationally constrained devices such as mobile phones and web browser environments, generating ZK proofs might take too long to ensure an acceptable user experience. Devices that do not have the computational resources to generate Miden proofs in under 1-2 seconds can use delegated proving to provide a more responsive user experience.
+Delegated proving moves transaction proof generation to a remote service. This can reduce the work required on a mobile device or in a browser. The time to submit a transaction still depends on execution, network latency, prover capacity, and node settlement.
-_How does it work?_ When a user choses to use delegated proving, they send off a portion of the zk proof of their transaction to a dedicated server. This dedicated server generates the remainder of the ZK proof of the transaction and submits it to the network. Submitting a transaction with delegated proving is trustless, meaning if the delegated prover is malicious, the could not compromise the security of the account that is submitting a transaction to be processed by the delegated prover. The downside of using delegated proving is that it reduces the privacy of the account that uses delegated proving, because the delegated prover would have knowledge of the transaction that is being proven. Additionally, transactions that require sensitive data such as the knowledge of a hash preimage or a secret, should not use delegated proving as this data will be shared with the delegated prover for proof generation.
+_How does it work?_ The client sends a transaction witness to the delegated prover, receives the generated proof, and submits the proven transaction to the node. The node verifies the proof. Delegation shares the witness with the prover, including private data needed for execution; use local proving when those inputs must remain on your device.
Anyone can run their own delegated prover server. If you are building a product on Miden, it may make sense to run your own delegated prover server for your users. To run your own delegated proving server, follow the instructions here: https://crates.io/crates/miden-proving-service
@@ -61,19 +70,21 @@ proving service. This means your browser never has to generate the full ZK proof
3. Install the Miden SDK:
-**NOTE!**: Be sure to add the `--webpack` command to your `package.json` when running the `dev script`. The dev script should look like this:
+The current Next.js template uses Turbopack by default. These SDK examples use the webpack configuration from the setup guide, so update both scripts in `package.json`:
`package.json`
```json
+{
"scripts": {
"dev": "next dev --webpack",
- ...
+ "build": "next build --webpack"
}
+}
```
## Step 2: Edit the `app/page.tsx` file:
@@ -137,27 +148,30 @@ export default function Home() {
Create `lib/react/multiSendWithDelegatedProver.tsx` (React) or `lib/multiSendWithDelegatedProver.ts` (TypeScript) and add the following code. This snippet initializes the Miden client.
-```
-mkdir -p lib
+```bash
+mkdir -p lib/react
```
{
+..await sync();
+..const authScheme = await tutorialAuthScheme();
..// We'll add our logic here
.};
@@ -183,8 +197,8 @@ export default function MultiSendWithDelegatedProver() {
.StorageMode,
.createP2IDNote,
.NoteArray,
-.TransactionRequestBuilder,
} from '@miden-sdk/miden-sdk/lazy';
+import { fundAccountForFees, consumeAllFeeAware } from './feeSupport';
export async function multiSendWithDelegatedProver(): Promise {
.// Ensure this runs only in a browser context
@@ -193,9 +207,7 @@ export async function multiSendWithDelegatedProver(): Promise {
.// Wait for WASM to be ready before touching any wasm-bindgen type.
.await MidenClient.ready();
-.const client = await MidenClient.create({
-..rpcUrl: 'https://rpc.testnet.miden.io',
-.});
+.const client = await MidenClient.createTestnet();
.console.log('Latest block:', (await client.sync()).blockNum());
}` },
@@ -203,17 +215,18 @@ export async function multiSendWithDelegatedProver(): Promise {
## Step 4 — Create an account, deploy a faucet, mint and consume tokens
-Add the code snippet below to the function. This code creates a wallet and faucet, mints tokens from the faucet for the wallet, and then consumes the minted tokens.
+Add the code below to the function. The shared helpers imported in Step 3 fund Alice and the faucet with native fee tokens, wait for confirmation and select the tutorial notes before consuming them. The React helpers come from the same `tutorialSupport` file used by the complete example.
## Step 5 — Build and Create P2ID notes
-Add the following code to the function. This code builds three P2ID notes with 100 `MID` each (one per hardcoded recipient address), and then creates all three notes in the same transaction.
+Add the following code to the function. This code creates three testnet recipients, builds a P2ID note with 100 `MID` for each, and then creates all three notes in the same transaction.
+..createWallet({ storageMode: StorageMode.Public, authScheme }),
+.),
+);
+
+const sent = await sendMany({
.from: alice,
.assetId: faucet,
-.recipients: [
-..{ to: 'mtst1arqeemdpnzu4k52wlpd3xekl5uklfjl5', amount: BigInt(100) },
-..{ to: 'mtst1arqk5qt3kms0cut9rdtqdaz8y5xmj245', amount: BigInt(100) },
-..{ to: 'mtst1aq6kyfrh23n9gvt6jkg0z7fyts99hdqr', amount: BigInt(100) },
-.],
+.recipients: recipients.map((account) => ({
+..to: account.id().toString(),
+..amount: BigInt(100),
+.})),
.noteType: NoteVisibility.Public,
});
+await committed(sent.transactionId);
+await assertBalance(alice, faucet, BigInt(9700));
console.log('All notes created ✅');`},
typescript: { code:`// ── build 3 P2ID notes (100 MID each) ─────────────────────────────────────────────
-const recipientAddresses = [
-.'mtst1arqeemdpnzu4k52wlpd3xekl5uklfjl5',
-.'mtst1arqk5qt3kms0cut9rdtqdaz8y5xmj245',
-.'mtst1aq6kyfrh23n9gvt6jkg0z7fyts99hdqr',
-];
+const recipients = await Promise.all(
+.Array.from({ length: 3 }, () =>
+..client.accounts.create({ storage: StorageMode.Public }),
+.),
+);
+const recipientAddresses = recipients.map((account) =>
+.account.id().toString(),
+);
const p2idNotes = recipientAddresses.map((addr) =>
.createP2IDNote({
@@ -305,9 +332,13 @@ const p2idNotes = recipientAddresses.map((addr) =>
);
// ── create all P2ID notes ───────────────────────────────────────────────────────────────
-const builder = new TransactionRequestBuilder();
-const txRequest = builder.withOwnOutputNotes(new NoteArray(p2idNotes)).build();
-await client.transactions.submit(alice, txRequest);
+await client.sync();
+const builder = await client.feeAwareTransactionRequestBuilder(alice);
+const outputs = new NoteArray();
+for (const note of p2idNotes) outputs.push(note);
+const txRequest = builder.withOwnOutputNotes(outputs).build();
+const { txId } = await client.transactions.submit(alice, txRequest);
+await client.transactions.waitFor(txId, { timeout: 120_000 });
console.log('All notes created ✅');` },
}} reactFilename="lib/react/multiSendWithDelegatedProver.tsx" tsFilename="lib/multiSendWithDelegatedProver.ts" />
@@ -319,107 +350,133 @@ Your library file should now look like this:
{
-..// 1. Create Alice's wallet
-..console.log('Creating account for Alice…');
-..const alice = await createWallet({ storageMode: StorageMode.Public });
-..const aliceId = alice.id().toString();
-..console.log('Alice account ID:', aliceId);
-
-..// 2. Deploy a fungible faucet
+..await sync();
+..const authScheme = await tutorialAuthScheme();
+..const alice = await createWallet({
+...storageMode: StorageMode.Public,
+...authScheme,
+..});
+..console.log('Alice ID:', alice.id().toString());
+..await fundAccount(alice);
..const faucet = await createFaucet({
...tokenSymbol: 'MID',
...decimals: 8,
...maxSupply: BigInt(1_000_000),
...storageMode: StorageMode.Public,
+...authScheme,
..});
-..const faucetId = faucet.id().toString();
-..console.log('Faucet ID:', faucetId);
+..console.log('Faucet ID:', faucet.id().toString());
+..await fundAccount(faucet);
-..// 3. Mint 10,000 MID to Alice
-..const mintResult = await mint({
-...faucetId,
-...targetAccountId: aliceId,
+..await sync();
+..const minted = await mint({
+...faucetId: faucet,
+...targetAccountId: alice,
...amount: BigInt(10_000),
...noteType: NoteVisibility.Public,
..});
-
-..console.log('Waiting for settlement…');
-..await waitForCommit(mintResult.transactionId);
-
-..// 4. Consume the freshly minted notes
-..const notes = await waitForConsumableNotes({ accountId: aliceId });
-..await consume({ accountId: aliceId, notes });
-
-..// 5. Send 100 MID to three recipients in a single transaction
-..await sendMany({
+..await committed(minted.transactionId);
+..const notes = await waitForTokenNotes(alice, faucet);
+..const consumed = await consume({ accountId: alice.id().toString(), notes });
+..await committed(consumed.transactionId);
+
+..const recipients = [];
+..for (let index = 0; index < 3; index += 1) {
+...recipients.push(
+....await createWallet({ storageMode: StorageMode.Public, authScheme }),
+...);
+..}
+..const sent = await sendMany({
...from: alice,
...assetId: faucet,
-...recipients: [
-....{ to: 'mtst1arqeemdpnzu4k52wlpd3xekl5uklfjl5', amount: BigInt(100) },
-....{ to: 'mtst1arqk5qt3kms0cut9rdtqdaz8y5xmj245', amount: BigInt(100) },
-....{ to: 'mtst1aq6kyfrh23n9gvt6jkg0z7fyts99hdqr', amount: BigInt(100) },
-...],
+...recipients: recipients.map((account) => ({
+....to: account,
+....amount: BigInt(100),
+...})),
...noteType: NoteVisibility.Public,
..});
-
+..await committed(sent.transactionId);
+..for (const recipient of recipients) {
+...const outputs = await waitForTokenNotes(recipient, faucet);
+...if (
+....outputs.length !== 1 ||
+....outputs[0].details().assets().fungibleAssets()[0]?.amount() !==
+.....BigInt(100)
+...) {
+....throw new Error(\`Expected one 100 MID note for \${recipient.id()}\`);
+...}
+..}
+..await assertBalance(alice, faucet, BigInt(9700));
..console.log('All notes created ✅');
.};
.return (
-..
-...
-..
+..
.);
}
export default function MultiSendWithDelegatedProver() {
.return (
-..
+..
...
..
.);
}`},
- typescript: { code:`import {
-.MidenClient,
+ typescript: { code: `import {
+.NoteArray,
.NoteVisibility,
.StorageMode,
.createP2IDNote,
-.NoteArray,
-.TransactionRequestBuilder,
} from '@miden-sdk/miden-sdk/lazy';
+import {
+.consumeAllFeeAware,
+.createTutorialClient,
+.fundAccountForFees,
+} from './feeSupport';
-/\*\*
-.\* Demonstrates multi-send functionality with delegated proving on the Miden Network
-.\* Creates multiple P2ID (Pay to ID) notes for different recipients
-.\*
-.\* @throws {Error} If the function cannot be executed in a browser environment
-.\*/
export async function multiSendWithDelegatedProver(): Promise {
.// Ensure this runs only in a browser context
.if (typeof window === 'undefined') return console.warn('Run in browser');
-.// Wait for WASM to be ready before touching any wasm-bindgen type.
-.await MidenClient.ready();
-
-.const client = await MidenClient.create({
-..rpcUrl: 'https://rpc.testnet.miden.io',
-.});
+.const client = await createTutorialClient();
.console.log('Latest block:', (await client.sync()).blockNum());
@@ -430,7 +487,7 @@ export async function multiSendWithDelegatedProver(): Promise {
.});
.console.log('Alice account ID:', alice.id().toString());
-.// ── Creating new faucet ──────────────────────────────────────────────────────
+.// ── Creating new faucet ────────────────────────────────────────────────────
.const faucet = await client.accounts.create({
..type: 0, // 0 = FungibleFaucet
..symbol: 'MID',
@@ -439,29 +496,30 @@ export async function multiSendWithDelegatedProver(): Promise {
..storage: StorageMode.Public,
.});
.console.log('Faucet ID:', faucet.id().toString());
+.await fundAccountForFees(client, alice);
+.await fundAccountForFees(client, faucet);
-.// ── mint 10 000 MID to Alice ──────────────────────────────────────────────────────
+.// ── mint 10 000 MID to Alice ───────────────────────────────────────────────
+.await client.sync();
.const { txId: mintTxId } = await client.transactions.mint({
..account: faucet,
..to: alice,
..amount: BigInt(10_000),
..type: NoteVisibility.Public,
.});
-
.console.log('waiting for settlement');
-.await client.transactions.waitFor(mintTxId);
-
-.// ── consume the freshly minted notes ──────────────────────────────────────────────
-.await client.transactions.consumeAll({
-..account: alice,
-.});
+.await client.transactions.waitFor(mintTxId, { timeout: 120_000 });
+.await consumeAllFeeAware(client, alice);
.// ── build 3 P2ID notes (100 MID each) ─────────────────────────────────────────────
-.const recipientAddresses = [
-..'mtst1arqeemdpnzu4k52wlpd3xekl5uklfjl5',
-..'mtst1arqk5qt3kms0cut9rdtqdaz8y5xmj245',
-..'mtst1aq6kyfrh23n9gvt6jkg0z7fyts99hdqr',
-.];
+.const recipients = await Promise.all(
+..Array.from({ length: 3 }, () =>
+...client.accounts.create({ storage: StorageMode.Public }),
+..),
+.);
+.const recipientAddresses = recipients.map((account) =>
+..account.id().toString(),
+.);
.const p2idNotes = recipientAddresses.map((addr) =>
..createP2IDNote({
@@ -473,9 +531,18 @@ export async function multiSendWithDelegatedProver(): Promise {
.);
.// ── create all P2ID notes ───────────────────────────────────────────────────────────────
-.const builder = new TransactionRequestBuilder();
-.const txRequest = builder.withOwnOutputNotes(new NoteArray(p2idNotes)).build();
-.await client.transactions.submit(alice, txRequest);
+.await client.sync();
+.const builder = await client.feeAwareTransactionRequestBuilder(alice);
+.const outputs = new NoteArray();
+.for (const note of p2idNotes) outputs.push(note);
+.const request = builder.withOwnOutputNotes(outputs).build();
+.const { txId } = await client.transactions.submit(alice, request);
+.await client.transactions.waitFor(txId, { timeout: 120_000 });
+.console.log(\`Transaction committed: \${txId.toHex()}\`);
+.const updatedAlice = await client.accounts.get(alice);
+.const balance = updatedAlice?.vault().getBalance(faucet.id());
+.if (balance !== BigInt(9_700))
+..throw new Error(\`Expected Alice to retain 9700 MID, got \${balance}\`);
.console.log('All notes created ✅');
}` },
@@ -488,20 +555,23 @@ To run a full working example navigate to the `web-client` directory in the [mid
```bash
cd web-client
yarn install
-yarn start
+yarn dev
```
### Resetting the `MidenClientDB`
-The Miden webclient stores account and note data in the browser. To clear the account and node data in the browser, paste this code snippet into the browser console:
+The Miden webclient stores account and note data in IndexedDB. Stop or terminate the tutorial client and close other tabs using its store before resetting it. This deletes local account data and keys, so use it only for disposable tutorial accounts. The following browser-console snippet deletes the default testnet `MidenClientDB_mtst` store after the deletion request completes; change `name` if you configured a different store.
```javascript
(async () => {
- const dbs = await indexedDB.databases(); // Get all database names
- for (const db of dbs) {
- await indexedDB.deleteDatabase(db.name);
- console.log(`Deleted database: ${db.name}`);
- }
- console.log('All databases deleted.');
+ const name = 'MidenClientDB_mtst';
+ await new Promise((resolve, reject) => {
+ const request = indexedDB.deleteDatabase(name);
+ request.onsuccess = () => resolve();
+ request.onerror = () => reject(request.error);
+ request.onblocked = () =>
+ reject(new Error('Close clients and tabs using this store, then retry.'));
+ });
+ console.log(`Deleted database: ${name}`);
})();
```
diff --git a/docs/src/web-client/foreign_procedure_invocation_tutorial.md b/docs/src/web-client/foreign_procedure_invocation_tutorial.md
index 641d1b14..e1bf1014 100644
--- a/docs/src/web-client/foreign_procedure_invocation_tutorial.md
+++ b/docs/src/web-client/foreign_procedure_invocation_tutorial.md
@@ -7,6 +7,13 @@ sidebar_position: 7
_Using foreign procedure invocation to craft read-only cross-contract calls with the Miden client_
+:::note v0.16 setup
+
+Follow the [network and fee setup](./setup_guide.md#network-and-fee-setup)
+and copy the shared support files imported by the complete example.
+
+:::
+
## Overview
In the previous tutorial we deployed a fresh counter smart contract and incremented its count with a transaction script.
@@ -57,18 +64,20 @@ This tutorial assumes you have a basic understanding of Miden assembly and compl
3. Install the Miden SDK:
```bash
- yarn add @miden-sdk/miden-sdk@0.15.2
+ yarn add @miden-sdk/miden-sdk@0.16.0
```
-**NOTE!**: Be sure to add the `--webpack` command to your `package.json` when running the `dev script`. The dev script should look like this:
+The current Next.js template uses Turbopack by default. These examples use the webpack configuration from the setup guide, so update both scripts in `package.json`:
`package.json`
```json
+{
"scripts": {
"dev": "next dev --webpack",
- ...
+ "build": "next build --webpack"
}
+}
```
## Step 2: Edit the `app/page.tsx` file
@@ -128,35 +137,53 @@ Create the file `lib/masm/counter_contract.masm`. This is the same counter contr
```masm
use miden::protocol::active_account
use miden::protocol::native_account
-use miden::core::word
use miden::core::sys
+# CONSTANTS
+# =================================================================================================
+
const COUNTER_SLOT = word("miden::tutorials::counter")
-#! Inputs: []
-#! Outputs: [count]
-pub proc get_count
+# PUBLIC INTERFACE
+# =================================================================================================
+
+#! Returns the current count.
+#!
+#! Inputs: [pad(16)]
+#! Outputs: [count, pad(15)]
+#!
+#! Invocation: call
+@account_procedure
+pub proc get_count() -> felt
push.COUNTER_SLOT[0..2] exec.active_account::get_item
- # => [count]
+ # => [[count, 0, 0, 0], pad(16)]
exec.sys::truncate_stack
- # => [count]
+ # => [count, pad(15)]
end
-#! Inputs: []
-#! Outputs: []
-pub proc increment_count
+#! Increments the current count by one.
+#!
+#! Inputs: [pad(16)]
+#! Outputs: [pad(16)]
+#!
+#! Invocation: call
+@account_procedure
+pub proc increment_count()
push.COUNTER_SLOT[0..2] exec.active_account::get_item
- # => [count]
+ # => [[count, 0, 0, 0], pad(16)]
add.1
- # => [count+1]
+ # => [[count + 1, 0, 0, 0], pad(16)]
push.COUNTER_SLOT[0..2] exec.native_account::set_item
- # => []
+ # => [OLD_VALUE, pad(16)]
+
+ dropw
+ # => [pad(16)]
exec.sys::truncate_stack
- # => []
+ # => [pad(16)]
end
```
@@ -165,30 +192,56 @@ end
Create the file `lib/masm/count_reader.masm`. This is the new "count copy" contract that reads the counter value via FPI and stores it locally:
```masm
-use miden::protocol::active_account
use miden::protocol::native_account
use miden::protocol::tx
-use miden::core::word
use miden::core::sys
+use {AccountId, AccountProcedureRoot} from miden::protocol::types
+
+# CONSTANTS
+# =================================================================================================
const COUNT_READER_SLOT = word("miden::tutorials::count_reader")
-# => [account_id_suffix, account_id_prefix, PROC_HASH(4), foreign_procedure_inputs(16)]
-pub proc copy_count
+# PUBLIC INTERFACE
+# =================================================================================================
+
+#! Copies the count returned by the foreign counter into this account's storage.
+#!
+#! Inputs: [foreign_account_id_{suffix,prefix}, FOREIGN_PROC_ROOT, pad(10)]
+#! Outputs: [pad(16)]
+#!
+#! Where:
+#! - foreign_account_id_{suffix,prefix} identifies the public counter account.
+#! - FOREIGN_PROC_ROOT is the root of its get_count procedure.
+#!
+#! Invocation: call
+@account_procedure
+@locals(6)
+pub proc copy_count(foreign_account_id: AccountId, foreign_proc_root: AccountProcedureRoot)
+ # save the foreign target while preparing its sixteen zero inputs
+ loc_store.4 loc_store.5 loc_storew_le.0 dropw
+ # => [pad(16)]
+
+ padw padw padw padw
+ # => [foreign_procedure_inputs(16), pad(16)]
+
+ padw loc_loadw_le.0 loc_load.5 loc_load.4
+ # => [foreign_account_id_suffix, foreign_account_id_prefix, FOREIGN_PROC_ROOT, foreign_procedure_inputs(16), pad(16)]
+
exec.tx::execute_foreign_procedure
- # => [count, pad(12)]
+ # => [[count, 0, 0, 0], pad(28)]
push.COUNT_READER_SLOT[0..2]
- # [slot_id_prefix, slot_id_suffix, count, pad(12)]
+ # => [slot_id_suffix, slot_id_prefix, [count, 0, 0, 0], pad(28)]
exec.native_account::set_item
- # => [OLD_VALUE, pad(12)]
+ # => [OLD_VALUE, pad(28)]
- dropw dropw dropw dropw
- # => []
+ dropw
+ # => [pad(28)]
exec.sys::truncate_stack
- # => []
+ # => [pad(16)]
end
```
@@ -210,17 +263,11 @@ We need to tell our bundler to treat `.masm` files as plain text strings. In Nex
Open `next.config.ts` and add the highlighted rule inside the `webpack` callback:
```ts
-webpack: (config, { isServer }) => {
- // ... existing WASM config ...
-
- // Import .masm files as strings
- config.module.rules.push({
- test: /\.masm$/,
- type: "asset/source",
- });
-
- return config;
-},
+// Import .masm files as strings. Keep the existing WASM configuration.
+config.module.rules.push({
+ test: /\.masm$/,
+ type: "asset/source",
+});
```
:::tip Other bundlers
@@ -245,11 +292,14 @@ import counterContractCode from './masm/counter_contract.masm';
import countReaderCode from './masm/count_reader.masm';
import {
AuthSecretKey,
- StorageMode,
StorageSlot,
StorageResult,
- MidenClient,
} from '@miden-sdk/miden-sdk/lazy';
+import {
+ createFundableContractAccount,
+ createTutorialClient,
+ fundAccountForFees,
+} from './feeSupport';
export async function foreignProcedureInvocation(): Promise {
if (typeof window === 'undefined') {
@@ -257,12 +307,7 @@ export async function foreignProcedureInvocation(): Promise {
return;
}
- // Wait for the WASM module to finish initializing before touching any
- // wasm-bindgen type (see setup_guide.md "Entry points: eager vs lazy").
- await MidenClient.ready();
-
- const nodeEndpoint = 'https://rpc.testnet.miden.io';
- const client = await MidenClient.create({ rpcUrl: nodeEndpoint });
+ const client = await createTutorialClient({ proverUrl: 'local' });
console.log('Current block number: ', (await client.sync()).blockNum());
const counterSlotName = 'miden::tutorials::counter';
@@ -282,21 +327,38 @@ export async function foreignProcedureInvocation(): Promise {
crypto.getRandomValues(counterSeed);
const counterAuth = AuthSecretKey.rpoFalconWithRNG(counterSeed);
- const counterAccount = await client.accounts.create({
- storage: StorageMode.Public,
- seed: counterSeed,
- auth: counterAuth,
- components: [counterComponent],
- });
+ const counterAccount = await createFundableContractAccount(
+ client,
+ counterSeed,
+ counterAuth,
+ [counterComponent],
+ );
+
+ await fundAccountForFees(client, counterAccount);
// Deploy the counter to the node by executing a transaction on it
const deployScript = await client.compile.txScript({
code: `
- use external_contract::counter_contract
- begin
- call.counter_contract::increment_count
- end
- `,
+use external_contract::counter_contract
+
+#! Increments the counter.
+#!
+#! Inputs: [ARGS, pad(12)]
+#! Outputs: [pad(16)]
+#!
+#! Where:
+#! - ARGS contains unused transaction script arguments.
+#!
+#! Invocation: dyncall
+@transaction_script
+pub proc main(args: word)
+ dropw
+ # => [pad(16)]
+
+ call.counter_contract::increment_count
+ # => [pad(16)]
+end
+`,
libraries: [
{
namespace: 'external_contract::counter_contract',
@@ -307,10 +369,12 @@ export async function foreignProcedureInvocation(): Promise {
// Wait for the deploy transaction to be committed to a block
// before using it as a foreign account in FPI
+ await client.sync();
await client.transactions.execute({
account: counterAccount,
script: deployScript,
waitForConfirmation: true,
+ timeout: 120_000,
});
console.log('Counter contract ID:', counterAccount.id().toString());
@@ -328,12 +392,14 @@ export async function foreignProcedureInvocation(): Promise {
crypto.getRandomValues(readerSeed);
const readerAuth = AuthSecretKey.rpoFalconWithRNG(readerSeed);
- const countReaderAccount = await client.accounts.create({
- storage: StorageMode.Public,
- seed: readerSeed,
- auth: readerAuth,
- components: [countReaderComponent],
- });
+ const countReaderAccount = await createFundableContractAccount(
+ client,
+ readerSeed,
+ readerAuth,
+ [countReaderComponent],
+ );
+
+ await fundAccountForFees(client, countReaderAccount);
console.log('Count reader contract ID:', countReaderAccount.id().toString());
@@ -347,11 +413,21 @@ export async function foreignProcedureInvocation(): Promise {
const getCountProcHash = counterComponent.getProcedureHash('get_count');
const fpiScriptCode = `
- use external_contract::count_reader_contract
- use miden::core::sys
+use external_contract::count_reader_contract
+use miden::core::sys
- begin
- padw padw padw padw
+#! Copies a public counter through the reader account.
+#!
+#! Inputs: [ARGS, pad(12)]
+#! Outputs: [pad(16)]
+#!
+#! Where:
+#! - ARGS contains unused transaction script arguments.
+#!
+#! Invocation: dyncall
+@transaction_script
+pub proc main(args: word)
+ dropw
# => [pad(16)]
push.${getCountProcHash}
@@ -364,12 +440,11 @@ export async function foreignProcedureInvocation(): Promise {
# => [account_id_suffix, account_id_prefix, GET_COUNT_HASH, pad(16)]
call.count_reader_contract::copy_count
- # => []
+ # => [pad(16)]
exec.sys::truncate_stack
- # => []
-
- end
+ # => [pad(16)]
+end
`;
const script = await client.compile.txScript({
@@ -382,11 +457,15 @@ export async function foreignProcedureInvocation(): Promise {
],
});
- await client.transactions.execute({
+ await client.sync();
+ const { txId } = await client.transactions.execute({
account: countReaderAccount,
script,
foreignAccounts: [counterAccount],
+ waitForConfirmation: true,
+ timeout: 120_000,
});
+ console.log(`Transaction committed: ${txId.toHex()}`);
const updatedCountReader = await client.accounts.get(countReaderAccount);
// `getItem()` is typed to return a low-level `Word`, but at runtime the SDK
@@ -398,7 +477,11 @@ export async function foreignProcedureInvocation(): Promise {
if (countReaderStorage) {
const countValue = Number(countReaderStorage.toBigInt());
+ if (countValue !== 1)
+ throw new Error(`Expected copied counter 1, got ${countValue}`);
console.log('Count copied via Foreign Procedure Invocation:', countValue);
+ } else {
+ throw new Error('Count reader storage was not available after commitment');
}
console.log('\nForeign Procedure Invocation Transaction completed!');
@@ -435,30 +518,56 @@ Foreign Procedure Invocation Transaction completed!
The count reader smart contract contains a `copy_count` procedure that uses `tx::execute_foreign_procedure` to call the `get_count` procedure in the counter contract.
```masm
-use miden::protocol::active_account
use miden::protocol::native_account
use miden::protocol::tx
-use miden::core::word
use miden::core::sys
+use {AccountId, AccountProcedureRoot} from miden::protocol::types
+
+# CONSTANTS
+# =================================================================================================
const COUNT_READER_SLOT = word("miden::tutorials::count_reader")
-# => [account_id_suffix, account_id_prefix, PROC_HASH(4), foreign_procedure_inputs(16)]
-pub proc copy_count
+# PUBLIC INTERFACE
+# =================================================================================================
+
+#! Copies the count returned by the foreign counter into this account's storage.
+#!
+#! Inputs: [foreign_account_id_{suffix,prefix}, FOREIGN_PROC_ROOT, pad(10)]
+#! Outputs: [pad(16)]
+#!
+#! Where:
+#! - foreign_account_id_{suffix,prefix} identifies the public counter account.
+#! - FOREIGN_PROC_ROOT is the root of its get_count procedure.
+#!
+#! Invocation: call
+@account_procedure
+@locals(6)
+pub proc copy_count(foreign_account_id: AccountId, foreign_proc_root: AccountProcedureRoot)
+ # save the foreign target while preparing its sixteen zero inputs
+ loc_store.4 loc_store.5 loc_storew_le.0 dropw
+ # => [pad(16)]
+
+ padw padw padw padw
+ # => [foreign_procedure_inputs(16), pad(16)]
+
+ padw loc_loadw_le.0 loc_load.5 loc_load.4
+ # => [foreign_account_id_suffix, foreign_account_id_prefix, FOREIGN_PROC_ROOT, foreign_procedure_inputs(16), pad(16)]
+
exec.tx::execute_foreign_procedure
- # => [count, pad(12)]
+ # => [[count, 0, 0, 0], pad(28)]
push.COUNT_READER_SLOT[0..2]
- # [slot_id_prefix, slot_id_suffix, count, pad(12)]
+ # => [slot_id_suffix, slot_id_prefix, [count, 0, 0, 0], pad(28)]
exec.native_account::set_item
- # => [OLD_VALUE, pad(12)]
+ # => [OLD_VALUE, pad(28)]
- dropw dropw dropw dropw
- # => []
+ dropw
+ # => [pad(28)]
exec.sys::truncate_stack
- # => []
+ # => [pad(16)]
end
```
@@ -467,10 +576,10 @@ To call the `get_count` procedure, we push its hash along with the counter contr
The stack state before calling `tx::execute_foreign_procedure` should look like this:
```
-# => [account_id_suffix, account_id_prefix, PROC_HASH(4), foreign_procedure_inputs(16)]
+# => [foreign_account_id_suffix, foreign_account_id_prefix, FOREIGN_PROC_ROOT, foreign_procedure_inputs(16), pad(16)]
```
-`execute_foreign_procedure` always requires exactly 16 `foreign_procedure_inputs` on the stack below the procedure hash and account ID. Since `get_count` takes no arguments, we pass 16 zero words (`padw padw padw padw`) as the inputs.
+`execute_foreign_procedure` always requires exactly 16 `foreign_procedure_inputs` on the stack below the procedure hash and account ID. Since `get_count` takes no arguments, `copy_count` prepares 16 zero field elements (four words: `padw padw padw padw`) as the inputs. The transaction script only passes the account ID and procedure root; the reader saves them in local memory while preparing those inputs.
After calling the `get_count` procedure in the counter contract, we save the count into the
`miden::tutorials::count_reader` storage slot.
@@ -483,8 +592,18 @@ The transaction script that executes the foreign procedure invocation looks like
use external_contract::count_reader_contract
use miden::core::sys
-begin
- padw padw padw padw
+#! Copies a public counter through the reader account.
+#!
+#! Inputs: [ARGS, pad(12)]
+#! Outputs: [pad(16)]
+#!
+#! Where:
+#! - ARGS contains unused transaction script arguments.
+#!
+#! Invocation: dyncall
+@transaction_script
+pub proc main(args: word)
+ dropw
# => [pad(16)]
push.${getCountProcHash}
@@ -497,19 +616,20 @@ begin
# => [account_id_suffix, account_id_prefix, GET_COUNT_HASH, pad(16)]
call.count_reader_contract::copy_count
- # => []
+ # => [pad(16)]
exec.sys::truncate_stack
- # => []
+ # => [pad(16)]
end
```
This script:
-1. Pushes the procedure hash of the `get_count` function
-2. Pushes the counter contract's account ID suffix and prefix
-3. Calls the `copy_count` procedure in our count reader contract
-4. Truncates the stack
+1. Discards the unused transaction script arguments.
+2. Pushes the procedure root of `get_count`.
+3. Pushes the counter account ID prefix, then suffix, leaving the suffix on top.
+4. Calls `copy_count`, which prepares the foreign call and stores the result.
+5. Truncates the stack.
## Key Miden Client Concepts for FPI
@@ -551,6 +671,8 @@ await client.transactions.execute({
account: countReaderAccount,
script,
foreignAccounts: [counterAccount],
+ waitForConfirmation: true,
+ timeout: 120_000,
});
```
@@ -575,21 +697,24 @@ To run a full working example navigate to the `web-client` directory in the [mid
```bash
cd web-client
yarn install
-yarn start
+yarn dev
```
### Resetting the `MidenClientDB`
-The Miden webclient stores account and note data in the browser. If you get errors such as "Failed to build MMR", then you should reset the Miden webclient store. When switching between Miden networks such as from localhost to testnet be sure to reset the browser store. To clear the account and node data in the browser, paste this code snippet into the browser console:
+The Miden webclient stores account and note data in IndexedDB. Stop or terminate the tutorial client and close other tabs using its store before resetting it. This deletes local account data and keys, so use it only for disposable tutorial accounts. The following browser-console snippet deletes the default testnet `MidenClientDB_mtst` store after the deletion request completes; change `name` if you configured a different store.
```javascript
(async () => {
- const dbs = await indexedDB.databases();
- for (const db of dbs) {
- await indexedDB.deleteDatabase(db.name);
- console.log(`Deleted database: ${db.name}`);
- }
- console.log('All databases deleted.');
+ const name = 'MidenClientDB_mtst';
+ await new Promise((resolve, reject) => {
+ const request = indexedDB.deleteDatabase(name);
+ request.onsuccess = () => resolve();
+ request.onerror = () => reject(request.error);
+ request.onblocked = () =>
+ reject(new Error('Close clients and tabs using this store, then retry.'));
+ });
+ console.log(`Deleted database: ${name}`);
})();
```
diff --git a/docs/src/web-client/mint_consume_create_tutorial.md b/docs/src/web-client/mint_consume_create_tutorial.md
index 9aff6a2f..0d048e3b 100644
--- a/docs/src/web-client/mint_consume_create_tutorial.md
+++ b/docs/src/web-client/mint_consume_create_tutorial.md
@@ -7,6 +7,15 @@ import { CodeSdkTabs } from '@site/src/components';
_Using the Miden client in TypeScript to mint, consume, and transfer assets_
+:::note v0.16 setup
+
+Follow the [network and fee setup](./setup_guide.md#network-and-fee-setup)
+and copy the shared support files imported by the complete example.
+For React snippets, initialize `authScheme` with `await tutorialAuthScheme()`
+as shown in the complete example.
+
+:::
+
## Overview
In the previous tutorial, we set up the foundation - creating Alice's wallet and deploying a faucet. Now we'll put these to use by minting and transferring assets.
@@ -36,21 +45,21 @@ Before we start coding, it's important to understand **notes**:
Let's mint some tokens for Alice. When we mint from a faucet, it creates a note containing the specified amount of tokens targeted to Alice's account.
-Add this to the end of your `createMintConsume` function:
+Add the operations below after funding Alice and the faucet in `createMintConsume`. Import `NoteVisibility` and the shared support functions shown in the complete example. For React, also initialize `useMint`, `useConsume` and `useSend`, and obtain `committed`, `waitForTokenNotes` and `assertBalance` from `useTutorialSupport`.
@@ -102,19 +111,25 @@ _The standard asset transfer note on Miden is the P2ID note (Pay-to-Id). There i
Now that Alice has tokens in her account, she can send some to Bob:
{
-..// 1. Create Alice's wallet (public, mutable)
-..console.log('Creating account for Alice…');
-..const alice = await createWallet({ storageMode: StorageMode.Public });
-..const aliceId = alice.id().toString();
-..console.log('Alice ID:', aliceId);
-
-..// 2. Deploy a fungible faucet
-..console.log('Creating faucet…');
+..console.log('Synchronizing before creating accounts…');
+..await sync();
+..console.log('Creating Alice with useCreateWallet…');
+..const authScheme = await tutorialAuthScheme();
+..// Native fee tokens and the tutorial's MID token are separate assets.
+..const alice = await createWallet({
+...storageMode: StorageMode.Public,
+...authScheme,
+..});
+..console.log('Alice ID:', alice.id().toString());
+..await fundAccount(alice);
+
+..// v0.16 faucets include BasicWallet, so they can receive fee funding.
..const faucet = await createFaucet({
...tokenSymbol: 'MID',
...decimals: 8,
...maxSupply: BigInt(1_000_000),
...storageMode: StorageMode.Public,
+...authScheme,
..});
-..const faucetId = faucet.id().toString();
-..console.log('Faucet ID:', faucetId);
-
-..// 3. Mint 1000 tokens to Alice
-..console.log('Minting tokens to Alice...');
-..const mintResult = await mint({
-...faucetId,
-...targetAccountId: aliceId,
+..console.log('Faucet ID:', faucet.id().toString());
+..await fundAccount(faucet);
+
+..await sync();
+..const minted = await mint({
+...faucetId: faucet,
+...targetAccountId: alice,
...amount: BigInt(1000),
...noteType: NoteVisibility.Public,
..});
-..console.log('Mint tx:', mintResult.transactionId);
-
-..// 4. Wait for the mint transaction to be committed
-..await waitForCommit(mintResult.transactionId);
-
-..// 5. Wait for consumable notes to appear, then consume them
-..const notes = await waitForConsumableNotes({ accountId: alice });
-..console.log('Consumable notes:', notes.length);
+..await committed(minted.transactionId);
+..const notes = await waitForTokenNotes(alice, faucet);
+..const consumed = await consume({ accountId: alice.id().toString(), notes });
+..await committed(consumed.transactionId);
+..await assertBalance(alice, faucet, BigInt(1000));
-..console.log('Consuming minted notes...');
-..await consume({ accountId: alice.id().toString(), notes });
-..console.log('Notes consumed.');
-
-..// 7. Send 100 tokens to Bob
-..const bobAddress = 'mtst1arpsz3jlmjxl7u2jjzfsc0wyqyaas6a9';
-..console.log("Sending tokens to Bob's account...");
-..await send({
+..const bob = await createWallet({
+...storageMode: StorageMode.Public,
+...authScheme,
+..});
+..const sent = await send({
...from: alice,
-...to: bobAddress,
+...to: bob,
...assetId: faucet,
...amount: BigInt(100),
...noteType: NoteVisibility.Public,
+...returnNote: true,
..});
+..await committed(sent.txId);
+..if (!sent.note) throw new Error('Send did not return its output note');
+..await waitForNote(sent.note.id().toString());
+..await assertBalance(alice, faucet, BigInt(900));
..console.log('Tokens sent successfully!');
.};
.return (
-..
-...
-..
+..
.);
}
export default function CreateMintConsume() {
.return (
-..
+..
...
..
.);
}`},
- typescript: { code:`// lib/createMintConsume.ts
-import { MidenClient, NoteVisibility, StorageMode } from '@miden-sdk/miden-sdk/lazy';
+ typescript: { code: `// lib/createMintConsume.ts
+import { NoteVisibility, StorageMode } from '@miden-sdk/miden-sdk/lazy';
+import {
+.consumeAllFeeAware,
+.createTutorialClient,
+.fundAccountForFees,
+} from './feeSupport';
export async function createMintConsume(): Promise {
.if (typeof window === 'undefined') {
@@ -233,11 +284,8 @@ export async function createMintConsume(): Promise {
..return;
.}
-.// Wait for WASM to be ready before touching any wasm-bindgen type.
-.await MidenClient.ready();
-
-.const client = await MidenClient.create({
-..rpcUrl: 'https://rpc.testnet.miden.io',
+.const client = await createTutorialClient({
+..proverUrl: 'local',
.});
.// 1. Sync with the latest blockchain state
@@ -251,7 +299,8 @@ export async function createMintConsume(): Promise {
.});
.console.log('Alice ID:', alice.id().toString());
-.// 3. Deploy a fungible faucet
+.// 3. Create our own fungible faucet. SDK v0.16 includes BasicWallet,
+.// allowing both accounts to consume native fee funding before minting MID.
.console.log('Creating faucet…');
.const faucet = await client.accounts.create({
..type: 0, // 0 = FungibleFaucet
@@ -261,38 +310,47 @@ export async function createMintConsume(): Promise {
..storage: StorageMode.Public,
.});
.console.log('Faucet ID:', faucet.id().toString());
+.await fundAccountForFees(client, alice);
+.await fundAccountForFees(client, faucet);
-.// 4. Mint tokens to Alice
-
+.// 4. Mint tokens to Alice.
.console.log('Minting tokens to Alice...');
+.await client.sync();
.const { txId: mintTxId } = await client.transactions.mint({
..account: faucet,
..to: alice,
..amount: BigInt(1000),
..type: NoteVisibility.Public,
.});
-
.console.log('Waiting for transaction confirmation...');
-.await client.transactions.waitFor(mintTxId);
+.await client.transactions.waitFor(mintTxId, { timeout: 120_000 });
-.// 5-6. Consume all available notes for Alice in a single transaction
+.// 5-6. Consume all available notes for Alice.
.console.log('Consuming minted notes...');
-.await client.transactions.consumeAll({
-..account: alice,
-.});
+.await consumeAllFeeAware(client, alice);
.console.log('Notes consumed.');
.// 7. Send tokens to Bob
-.const bobAddress = 'mtst1arpsz3jlmjxl7u2jjzfsc0wyqyaas6a9';
+.const bob = await client.accounts.create({
+..storage: StorageMode.Public,
+.});
.console.log("Sending tokens to Bob's account...");
-.await client.transactions.send({
+.await client.sync();
+.const { txId: sendTxId } = await client.transactions.send({
..account: alice,
-..to: bobAddress,
+..to: bob,
..token: faucet,
..amount: BigInt(100),
..type: NoteVisibility.Public,
+..waitForConfirmation: true,
+..timeout: 120_000,
.});
+.console.log(\`Transaction committed: \${sendTxId.toHex()}\`);
+.const updatedAlice = await client.accounts.get(alice);
+.const balance = updatedAlice?.vault().getBalance(faucet.id());
+.if (balance !== BigInt(900))
+..throw new Error(\`Expected Alice to retain 900 MID, got \${balance}\`);
.console.log('Tokens sent successfully!');
}` },
}} reactFilename="lib/react/createMintConsume.tsx" tsFilename="lib/createMintConsume.ts" />
@@ -318,16 +376,19 @@ Tokens sent successfully!
### Resetting the `MidenClientDB`
-The Miden webclient stores account and note data in the browser. To clear the account and note data in the browser, paste this code snippet into the browser console:
+The Miden webclient stores account and note data in IndexedDB. Stop or terminate the tutorial client and close other tabs using its store before resetting it. This deletes local account data and keys, so use it only for disposable tutorial accounts. The following browser-console snippet deletes the default testnet `MidenClientDB_mtst` store after the deletion request completes; change `name` if you configured a different store.
```javascript
(async () => {
- const dbs = await indexedDB.databases(); // Get all database names
- for (const db of dbs) {
- await indexedDB.deleteDatabase(db.name);
- console.log(`Deleted database: ${db.name}`);
- }
- console.log('All databases deleted.');
+ const name = 'MidenClientDB_mtst';
+ await new Promise((resolve, reject) => {
+ const request = indexedDB.deleteDatabase(name);
+ request.onsuccess = () => resolve();
+ request.onerror = () => reject(request.error);
+ request.onblocked = () =>
+ reject(new Error('Close clients and tabs using this store, then retry.'));
+ });
+ console.log(`Deleted database: ${name}`);
})();
```
diff --git a/docs/src/web-client/react_wallet_tutorial.md b/docs/src/web-client/react_wallet_tutorial.md
index a8ed7a19..f288edfa 100644
--- a/docs/src/web-client/react_wallet_tutorial.md
+++ b/docs/src/web-client/react_wallet_tutorial.md
@@ -34,6 +34,17 @@ By the end of this tutorial, you will have a working wallet that can:
- Familiarity with React and TypeScript
- `yarn`
+:::note v0.16 testnet and fees
+
+A new wallet needs native fee tokens before sending assets. Request and claim a
+funding note from the testnet faucet, as described in the
+[fee setup](./setup_guide.md#network-and-fee-setup). Claim the returned
+note ID and wait for confirmation before sending.
+
+Select testnet in both the client and any external wallet adapter.
+
+:::
+
---
## Step 1: Project Setup and MidenProvider
@@ -50,16 +61,39 @@ First, create a new Vite + React project and install the Miden React SDK.
2. Install the Miden React SDK:
```bash
- yarn add @miden-sdk/react
+ yarn add @miden-sdk/miden-sdk@0.16.0 @miden-sdk/react@0.16.0
+ ```
+
+3. Install the Vite integration and update `vite.config.ts`. The Miden plugin v0.16 supports Vite 5 and 6, so pin Vite 6 and its compatible React plugin even if the generator installed newer versions:
+
+ ```bash
+ yarn add -D vite@^6 @vitejs/plugin-react@^4 @miden-sdk/vite-plugin@0.16.0 vite-plugin-wasm vite-plugin-top-level-await
+ ```
+
+ ```ts
+ import { defineConfig } from 'vite';
+ import react from '@vitejs/plugin-react';
+ import { midenVitePlugin } from '@miden-sdk/vite-plugin';
+ import wasm from 'vite-plugin-wasm';
+ import topLevelAwait from 'vite-plugin-top-level-await';
+
+ export default defineConfig({
+ plugins: [react(), midenVitePlugin({ crossOriginIsolation: false }), wasm(), topLevelAwait()],
+ worker: { format: 'es', plugins: () => [wasm(), topLevelAwait()] },
+ });
```
-3. Configure the `MidenProvider` in your `main.tsx` file. The provider initializes the Miden client and makes it available to all child components:
+ This Vite tutorial uses the main SDK entries throughout. Para and Turnkey import
+ `SignerContext` from `@miden-sdk/react`; mixing it with `/lazy` creates separate
+ contexts in v0.16. Keep providers and hooks on the same entry.
+
+4. Configure the `MidenProvider` in your `main.tsx` file. The provider initializes the Miden client and makes it available to all child components:
```tsx
// main.tsx
import React from 'react';
import ReactDOM from 'react-dom/client';
-import { MidenProvider } from '@miden-sdk/react/lazy';
+import { MidenProvider } from '@miden-sdk/react';
import App from './App';
ReactDOM.createRoot(document.getElementById('root')!).render(
@@ -78,8 +112,8 @@ ReactDOM.createRoot(document.getElementById('root')!).render(
The `MidenProvider` accepts a `config` object with the following options:
-- `rpcUrl`: The RPC endpoint to connect to (`"testnet"` or a custom URL)
-- `prover`: The prover to use (`"testnet"` for delegated proving, or `"local"` for local proving)
+- `rpcUrl`: The RPC endpoint to connect to (`"testnet"`, `"devnet"`, or a custom URL)
+- `prover`: The prover to use (`"testnet"` for testnet delegated proving, or `"local"` for local proving)
---
@@ -89,7 +123,7 @@ The `useMiden()` hook provides access to the client's initialization state. Use
```tsx
// App.tsx
-import { useMiden } from '@miden-sdk/react/lazy';
+import { useMiden } from '@miden-sdk/react';
export default function App() {
const { isReady, error } = useMiden();
@@ -113,7 +147,7 @@ The `useMiden()` hook returns:
The `useAccounts()` hook provides access to all accounts stored in the client. Use it to check if the user has any existing wallets.
```tsx
-import { useMiden, useAccounts } from '@miden-sdk/react/lazy';
+import { useMiden, useAccounts } from '@miden-sdk/react';
export default function App() {
const { isReady, error } = useMiden();
@@ -145,7 +179,8 @@ The `useAccounts()` hook returns:
The `useCreateWallet()` hook provides a function to create new wallet accounts.
```tsx
-import { useMiden, useAccounts, useCreateWallet } from '@miden-sdk/react/lazy';
+import { useMiden, useAccounts, useCreateWallet } from '@miden-sdk/react';
+import { getWasmOrThrow } from '@miden-sdk/miden-sdk';
export default function App() {
const { isReady, error } = useMiden();
@@ -161,7 +196,12 @@ export default function App() {
return (
Wallet
-
@@ -176,6 +216,10 @@ function Wallet({ accountId }: { accountId: string }) {
}
```
+Pass the low-level Falcon enum explicitly with the pinned lazy React SDK. The
+high-level client's authentication enum is not interchangeable with the numeric
+enum expected by this hook.
+
The `useCreateWallet()` hook returns:
- `createWallet(options?)`: Function to create a new wallet
@@ -188,7 +232,7 @@ The `useCreateWallet()` hook returns:
The `useAccount(accountId)` hook provides detailed information about a specific account, including its assets and balances.
```tsx
-import { useAccount, formatAssetAmount } from '@miden-sdk/react/lazy';
+import { useAccount, formatAssetAmount } from '@miden-sdk/react';
function Wallet({ accountId }: { accountId: string }) {
const { account, assets } = useAccount(accountId);
@@ -237,7 +281,7 @@ The `formatAssetAmount(amount, decimals)` utility formats a raw amount with the
The `useNotes({ accountId })` hook provides access to notes that can be consumed by the account.
```tsx
-import { useNotes, formatNoteSummary } from '@miden-sdk/react/lazy';
+import { useNotes, formatNoteSummary } from '@miden-sdk/react';
function UnclaimedNotes({ accountId }: { accountId: string }) {
const { consumableNoteSummaries } = useNotes({ accountId });
@@ -273,14 +317,14 @@ The `formatNoteSummary(summary)` utility formats a note summary for display.
The `useConsume()` hook provides a function to consume (claim) notes and add their assets to the account.
```tsx
-import { useConsume, formatNoteSummary } from '@miden-sdk/react/lazy';
+import { useConsume, formatNoteSummary, type NoteSummary } from '@miden-sdk/react';
function UnclaimedNotes({
accountId,
consumableNoteSummaries,
}: {
accountId: string;
- consumableNoteSummaries: Array<{ id: string }>;
+ consumableNoteSummaries: NoteSummary[];
}) {
const { consume, isLoading: isConsuming } = useConsume();
@@ -319,12 +363,12 @@ The `useConsume()` hook returns:
## Step 8: Sending Tokens with useSend
-The `useSend()` hook provides a function to send tokens to other accounts.
+The `useSend()` hook provides a function to send tokens to other accounts. Select the first asset when balances arrive asynchronously, as shown below, so a newly funded wallet can use the form.
```tsx
-import { useState, type ChangeEvent } from 'react';
-import { useSend, parseAssetAmount } from '@miden-sdk/react/lazy';
-import { NoteVisibility } from '@miden-sdk/miden-sdk/lazy';
+import { useEffect, useState, type ChangeEvent } from 'react';
+import { useSend, parseAssetAmount } from '@miden-sdk/react';
+import { NoteVisibility } from '@miden-sdk/miden-sdk';
function SendForm({
accountId,
@@ -335,7 +379,12 @@ function SendForm({
}) {
const { send, isLoading: isSending } = useSend();
const [to, setTo] = useState('');
- const [assetId, setAssetId] = useState(assets[0]?.assetId ?? '');
+ const [assetId, setAssetId] = useState('');
+ const defaultAssetId = assets[0]?.assetId;
+
+ useEffect(() => {
+ if (!assetId && defaultAssetId) setAssetId(defaultAssetId);
+ }, [assetId, defaultAssetId]);
const [amount, setAmount] = useState('');
const [noteType, setNoteType] = useState(
NoteVisibility.Private,
@@ -430,7 +479,7 @@ Here is the complete wallet application combining all the features we've covered
```tsx
import React from 'react';
import ReactDOM from 'react-dom/client';
-import { MidenProvider } from '@miden-sdk/react/lazy';
+import { MidenProvider } from '@miden-sdk/react';
import App from './App';
ReactDOM.createRoot(document.getElementById('root')!).render(
@@ -455,7 +504,7 @@ import {
formatAssetAmount,
formatNoteSummary,
parseAssetAmount,
-} from '@miden-sdk/react/lazy';
+} from '@miden-sdk/react';
import {
useMiden,
useAccounts,
@@ -464,8 +513,8 @@ import {
useCreateWallet,
useConsume,
useSend,
-} from '@miden-sdk/react/lazy';
-import { NoteVisibility } from '@miden-sdk/miden-sdk/lazy';
+} from '@miden-sdk/react';
+import { NoteVisibility, getWasmOrThrow } from '@miden-sdk/miden-sdk';
const Panel = ({ title, children }: { title: string; children: ReactNode }) => (
;
@@ -633,27 +684,42 @@ function Wallet({ accountId }: { accountId: string }) {
## Running the Example
-To run a full working example, navigate to the `packages/react-sdk/examples/wallet` directory in the [miden-client](https://github.com/0xMiden/miden-client/) repository:
+The complete upstream wallet example lives in
+[`packages/react-sdk/examples/wallet` in the web-sdk v0.16.0 release](https://github.com/0xMiden/web-sdk/tree/v0.16.0/packages/react-sdk/examples/wallet).
+Follow that example's README for its workspace setup and select testnet in its
+provider configuration.
+
+To exercise this repository's three React transaction examples on testnet:
```bash
-git clone https://github.com/0xMiden/miden-client.git
-cd miden-client/packages/react-sdk/examples/wallet
+cd web-client
yarn install
yarn dev
```
+Open `/react-tutorials` and select an example. These examples create and fund their
+own accounts; the wallet UI above starts with an empty wallet that needs funding.
+
### Resetting the MidenClientDB
-The Miden client stores account and note data in the browser's IndexedDB. To clear this data, paste the following into your browser console:
+The Miden client stores account and note data in the browser's IndexedDB.
+Upgrading from v0.15 automatically recreates the Miden store; export private
+notes and any other local data you need before upgrading. To manually reset only
+Miden databases on the current origin, stop the current client, close other tabs using it, and run:
```javascript
(async () => {
const dbs = await indexedDB.databases();
for (const db of dbs) {
- await indexedDB.deleteDatabase(db.name);
- console.log(`Deleted database: ${db.name}`);
+ if (!db.name?.startsWith('MidenClientDB')) continue;
+ await new Promise((resolve, reject) => {
+ const request = indexedDB.deleteDatabase(db.name);
+ request.onsuccess = () => resolve();
+ request.onerror = () => reject(request.error);
+ request.onblocked = () => reject(new Error('Close other Miden tabs and retry'));
+ });
+ console.log(`Deleted Miden database: ${db.name}`);
}
- console.log('All databases deleted.');
})();
```
@@ -668,7 +734,7 @@ By default, the Miden React SDK manages keys internally using the browser's Inde
The `useSigner()` hook from `@miden-sdk/react` provides a unified interface for interacting with any signer provider. When you wrap your app with a signer provider (Para, Turnkey, MidenFi, etc.), the hook returns the signer context with connection state and methods.
```tsx
-import { useSigner } from '@miden-sdk/react/lazy';
+import { useSigner } from '@miden-sdk/react';
function ConnectButton() {
const signer = useSigner();
@@ -697,28 +763,68 @@ This unified interface means your wallet UI code works the same regardless of wh
---
+### External signer sessions in v0.16
+
+The Para and Turnkey examples below import an **existing public testnet account**
+controlled by the selected signer. Pass its ID as `existingAccountId`. This uses
+`importAccountId` to avoid the new-account authentication-enum mismatch in React
+SDK v0.16.0. An API key or organization ID alone does not create that account.
+
+Use this shared wrapper in `SignerSession.tsx`. It mounts `MidenProvider` after
+connection and unmounts it on disconnect, so reconnecting creates a fresh client
+instead of calling v0.16.0's unavailable `setSignCb` method. Replace the original
+provider wrapper in `main.tsx` with the selected signer example; do not nest two
+`MidenProvider` instances.
+
+```tsx
+import type { ReactNode } from 'react';
+import { MidenProvider, useSigner } from '@miden-sdk/react';
+
+export function SignerSession({ children }: { children: ReactNode }) {
+ const signer = useSigner();
+ if (!signer?.isConnected) {
+ return Connect wallet;
+ }
+ return (
+
+ {children}
+
+ );
+}
+```
+
### Para: EVM Wallet Integration
-[Para](https://para.space/) provides a modal-based authentication flow that allows users to sign in with their EVM wallets (MetaMask, WalletConnect, etc.).
+[Para](https://docs.getpara.com/v2/introduction/welcome) provides a modal-based authentication flow that allows users to sign in with their EVM wallets (MetaMask, WalletConnect, etc.).
-**Installation:**
+Use `@miden-sdk/para-react@0.16.0` with the 0.16 SDK packages.
+Provide a Para API key for the selected environment.
+
+Install the adapter and its peer dependencies:
```bash
-yarn add @miden-sdk/use-miden-para-react
+yarn add @miden-sdk/para-react@0.16.0 @miden-sdk/para@0.16.0 @getpara/react-sdk-lite@^2.11.0 @getpara/web-sdk@^2.11.0 @tanstack/react-query@^5
+yarn add -D vite-plugin-node-polyfills@^0.22.0
```
-**Usage:**
+In `vite.config.ts`, import `paraVitePlugin` from `@miden-sdk/para-react/vite`
+and add `paraVitePlugin()` to the existing `plugins` array. It supplies the
+browser polyfills required by Para. Import `@getpara/react-sdk-lite/styles.css`
+once in `src/main.tsx` for the connection modal.
+
+**Integration:**
```tsx
-import { ParaSignerProvider } from '@miden-sdk/use-miden-para-react';
-import { MidenProvider, useSigner } from '@miden-sdk/react/lazy';
+import { ParaSignerProvider } from '@miden-sdk/para-react';
+import { useSigner } from '@miden-sdk/react';
+import { SignerSession } from './SignerSession';
-function App() {
+function App({ existingAccountId }: { existingAccountId: string }) {
return (
-
-
+
+
-
+
);
}
@@ -745,7 +851,7 @@ function Wallet() {
| `apiKey` | `string` | Your Para API key |
| `environment` | `"PRODUCTION" \| "DEVELOPMENT" \| "SANDBOX"` | Para environment |
| `showSigningModal` | `boolean` | Whether to show signing confirmation modal |
-| `customSignConfirmStep` | `ReactNode` | Custom signing confirmation UI |
+| `customSignConfirmStep` | `CustomSignConfirmStep` | Custom signing confirmation UI |
---
@@ -753,24 +859,28 @@ function Wallet() {
[Turnkey](https://turnkey.com/) provides programmatic key management, giving your application full control over the authentication flow.
-**Installation:**
+Use `@miden-sdk/turnkey-react@0.16.0` with the 0.16 SDK packages.
+Create a Turnkey organization and pass its ID in `config`.
+
+Install the adapter and its peer dependencies:
```bash
-yarn add @miden-sdk/miden-turnkey-react @turnkey/sdk-browser
+yarn add @miden-sdk/turnkey-react@0.16.0 @miden-sdk/turnkey@0.16.0 @turnkey/core@^1.8.2 @turnkey/react-wallet-kit@^1.6.2 @turnkey/sdk-browser@^5.13.4
```
-**Usage:**
+**Integration:**
```tsx
-import { TurnkeySignerProvider } from '@miden-sdk/miden-turnkey-react';
-import { MidenProvider, useSigner } from '@miden-sdk/react/lazy';
+import { TurnkeySignerProvider } from '@miden-sdk/turnkey-react';
+import { useSigner } from '@miden-sdk/react';
+import { SignerSession } from './SignerSession';
-function App() {
+function App({ existingAccountId }: { existingAccountId: string }) {
return (
-
-
+
+
-
+
);
}
@@ -790,13 +900,13 @@ function Wallet() {
}
```
-Calling `connect()` handles the full Turnkey authentication flow: passkey login, wallet discovery, and account selection. No manual setup is needed.
+After configuring the organization, `connect()` starts the Turnkey authentication flow: passkey login, wallet discovery, and account selection.
**TurnkeySignerProvider Props:**
-| Prop | Type | Description |
-| -------- | ---------------------------------- | ----------------------------------------------------------------------------------------------------------------------------- |
-| `config` | `Partial` | Optional. Defaults to `apiBaseUrl: "https://api.turnkey.com"` and `defaultOrganizationId` from `VITE_TURNKEY_ORG_ID` env var. |
+| Prop | Type | Description |
+| -------- | -------------------------------------- | ------------------------------------------------------------------------------------------ |
+| `config` | `TurnkeySignerProviderProps["config"]` | Required. Set `defaultOrganizationId`; `apiBaseUrl` defaults to `https://api.turnkey.com`. |
The `useTurnkeySigner()` hook is available for advanced use cases where you need direct access to the Turnkey `client`, the selected `account`, or the `setAccount()` method to manually control account selection.
@@ -804,26 +914,28 @@ The `useTurnkeySigner()` hook is available for advanced use cases where you need
### MidenFi: Wallet Adapter
-[MidenFi](https://miden.fi/) provides a wallet adapter pattern similar to Solana's wallet-adapter, enabling integration with the MidenFi ecosystem.
+[MidenFi](https://github.com/0xMiden/wallet) provides a wallet adapter pattern similar to Solana's wallet-adapter, enabling integration with the MidenFi ecosystem.
**Installation:**
```bash
-yarn add @miden-sdk/miden-wallet-adapter-react
+yarn add @miden-sdk/miden-wallet-adapter-react@0.16.0 @miden-sdk/miden-wallet-adapter-base@0.16.0
```
**Usage:**
```tsx
import { MidenFiSignerProvider } from '@miden-sdk/miden-wallet-adapter-react';
-import { MidenProvider, useSigner } from '@miden-sdk/react/lazy';
+import { WalletAdapterNetwork } from '@miden-sdk/miden-wallet-adapter-base';
+import { useSigner } from '@miden-sdk/react';
+import { SignerSession } from './SignerSession';
function App() {
return (
-
-
+
+
-
+
);
}
@@ -845,11 +957,14 @@ function Wallet() {
**MidenFiSignerProvider Props:**
-| Prop | Type | Description |
-| ----------------------- | ------------------------- | -------------------------------------- |
-| `network` | `"testnet" \| "localnet"` | Target network |
-| `privateDataPermission` | `boolean` | Whether to request private data access |
-| `allowedPrivateData` | `string[]` | List of allowed private data types |
+| Prop | Type | Description |
+| ----------------------- | ----------------------- | ---------------------------------------- |
+| `network` | `WalletAdapterNetwork` | `Testnet`, `Devnet`, or `Localnet` |
+| `privateDataPermission` | `PrivateDataPermission` | Permission level for private data access |
+| `allowedPrivateData` | `AllowedPrivateData` | Private-data categories the app requests |
+
+Import the enums from `@miden-sdk/miden-wallet-adapter-base`. Do not pass raw
+network strings, booleans, or string arrays in place of these enum values.
---
@@ -857,59 +972,93 @@ function Wallet() {
If you need to integrate with a different signing service, you can build your own signer provider by implementing the `SignerContextValue` interface and providing it via `SignerContext.Provider`.
+Pass your service initializer as `initializeSigningService`. It must return the ID
+of an existing public account on testnet, its serialized public key commitment,
+and a callback that signs the SDK's serialized signing inputs. The callback must
+verify the requested public key before signing. Account creation and deployment
+belong to the signing service in this example: `importAccountId` imports that
+account instead of rebuilding it with a new seed. This also avoids the new-account
+auth-enum mismatch in the published React SDK v0.16.0.
+
+Keep a non-null context while disconnected so `useSigner()` can expose `connect`
+and `MidenProvider` waits for the signer. In v0.16, `accountConfig` is only read
+when connected, although its TypeScript type is non-nullable; the assertion below
+reflects that runtime guard. Import WASM classes from the core SDK and await
+`MidenClient.ready()` before constructing them.
+
+The keyed fragment remounts the child `MidenProvider` after each connection.
+This avoids v0.16.0's reconnect path, which calls an unavailable `setSignCb`
+method. The account-specific store name stays the same, preserving local data.
+
```tsx
-import { useState, useCallback, type ReactNode } from 'react';
+import { Fragment, useState, useRef, useCallback, useMemo, type ReactNode } from 'react';
import {
SignerContext,
type SignerContextValue,
- AccountStorageMode,
-} from '@miden-sdk/react/lazy';
+ type SignerAccountConfig,
+ type SignCallback,
+} from '@miden-sdk/react';
+import { AccountStorageMode, MidenClient } from '@miden-sdk/miden-sdk';
+
+interface SigningService {
+ // An existing public account on the network configured in MidenProvider.
+ accountId: string;
+ publicKeyCommitment: Uint8Array;
+ signMessage: SignCallback;
+ disconnect?: () => Promise;
+}
interface CustomSignerProviderProps {
children: ReactNode;
- // Your provider-specific config
+ initializeSigningService: () => Promise;
}
-export function CustomSignerProvider({ children }: CustomSignerProviderProps) {
- const [isConnected, setIsConnected] = useState(false);
- const [signerContext, setSignerContext] = useState(
- null,
- );
+export function CustomSignerProvider({
+ children,
+ initializeSigningService,
+}: CustomSignerProviderProps) {
+ const service = useRef(null);
+ const [accountConfig, setAccountConfig] = useState(null);
+ const [connection, setConnection] = useState(0);
const connect = useCallback(async () => {
- // 1. Initialize your signing service and get credentials
- const { publicKeyCommitment, signMessage } =
- await initializeYourSigningService();
-
- // 2. Build the signer context
- const context: SignerContextValue = {
- signCb: async (pubKey, signingInputs) => {
- // Sign the message using your service
- return signMessage(signingInputs);
- },
- accountConfig: {
- publicKeyCommitment,
- storageMode: AccountStorageMode.public(),
- },
- storeName: 'custom_signer',
- name: 'CustomSigner',
- isConnected: true,
- connect,
- disconnect,
- };
-
- setSignerContext(context);
- setIsConnected(true);
- }, []);
+ await MidenClient.ready();
+ const connected = await initializeSigningService();
+ service.current = connected;
+ setAccountConfig({
+ publicKeyCommitment: connected.publicKeyCommitment,
+ storageMode: AccountStorageMode.public(),
+ importAccountId: connected.accountId,
+ });
+ setConnection((previous) => previous + 1);
+ }, [initializeSigningService]);
const disconnect = useCallback(async () => {
- setSignerContext(null);
- setIsConnected(false);
+ const connected = service.current;
+ service.current = null;
+ setAccountConfig(null);
+ await connected?.disconnect?.();
}, []);
+ const signCb = useCallback(async (pubKey, signingInputs) => {
+ if (!service.current) throw new Error('CustomSigner is not connected');
+ return service.current.signMessage(pubKey, signingInputs);
+ }, []);
+
+ const signerContext = useMemo(() => ({
+ signCb,
+ // v0.16 only reads this field when isConnected is true.
+ accountConfig: accountConfig!,
+ storeName: accountConfig ? `custom_${accountConfig.importAccountId}` : 'custom',
+ name: 'CustomSigner',
+ isConnected: accountConfig !== null,
+ connect,
+ disconnect,
+ }), [accountConfig, signCb, connect, disconnect]);
+
return (
- {children}
+ {children}
);
}
@@ -934,5 +1083,5 @@ The `SignerContextValue` interface requires:
Now that you've built a React wallet, explore these related topics:
- [Creating Multiple Notes in a Single Transaction](./creating_multiple_notes_tutorial.md) - Learn about batch operations
-- [Miden React SDK Reference](https://github.com/0xMiden/miden-client/tree/main/packages/react-sdk) - Full API documentation
-- [Miden Documentation](https://docs.miden.io/) - Core Miden concepts
+- [Miden React SDK Reference](https://github.com/0xMiden/web-sdk/tree/v0.16.0/packages/react-sdk) - Full API documentation
+- [Miden Documentation](https://docs.miden.xyz/) - Core Miden concepts
diff --git a/docs/src/web-client/setup_guide.md b/docs/src/web-client/setup_guide.md
index bf5fc4d3..8823683a 100644
--- a/docs/src/web-client/setup_guide.md
+++ b/docs/src/web-client/setup_guide.md
@@ -9,27 +9,27 @@ This guide covers the configuration required to use the Miden web SDK (`@miden-s
## Prerequisites
-- Node.js 20+ (Node 22+ requires an extra `localStorage` polyfill — see below)
+- Node.js 20.9+ for the current Next.js template; see the `localStorage` compatibility workaround below if needed
- Next.js 14+ with App Router
- yarn or npm
## Install the SDK
```bash
-yarn add @miden-sdk/miden-sdk
+yarn add @miden-sdk/miden-sdk@0.16.0
```
For React hook support:
```bash
-yarn add @miden-sdk/react
+yarn add @miden-sdk/miden-sdk@0.16.0 @miden-sdk/react@0.16.0
```
These tutorials use Next.js, so all code examples import from the SDK's `/lazy` subpath — see [Entry points: eager vs lazy](#entry-points-eager-vs-lazy) below for why that's required.
## Next.js Configuration
-Create or update `next.config.ts` with these required settings:
+Create or update `next.config.ts` with these required settings. With Next.js 16 or newer, use `next dev --webpack` and `next build --webpack` so this webpack callback runs. The repository’s Next.js 15 app uses webpack by default.
```ts
import type { NextConfig } from 'next';
@@ -109,9 +109,7 @@ export async function doSomething() {
if (typeof window === 'undefined') return;
await MidenClient.ready();
// Safe to construct wasm-bindgen types from here.
- const client = await MidenClient.create({
- rpcUrl: 'https://rpc.testnet.miden.io',
- });
+ const client = await MidenClient.createTestnet();
// …
}
```
@@ -120,15 +118,16 @@ In React, the `@miden-sdk/react/lazy` provider manages WASM readiness for you vi
```tsx
import { useMiden, useCreateWallet } from '@miden-sdk/react/lazy';
+import { getWasmOrThrow } from '@miden-sdk/miden-sdk/lazy';
function Component() {
const { isReady } = useMiden();
const { createWallet } = useCreateWallet();
return (
+ onClick={async () =>
createWallet({
- /* … */
+ authScheme: (await getWasmOrThrow()).AuthScheme.AuthRpoFalcon512,
})
}
disabled={!isReady}
@@ -145,15 +144,40 @@ Never construct wasm-bindgen types (`AccountId`, `Note`, `createP2IDNote`, `Tran
:::
+## Network and fee setup
+
+The v0.16 examples use testnet by default. Run them with `yarn tutorials --web`
+from the repository root; add `--web=react:createMintConsume` to select a React example.
+For explicit devnet testing, run `TUTORIAL_NETWORK=devnet yarn tutorials --web`.
+
+New accounts need the native fee asset before executing transactions. The examples
+request a public P2ID note from the faucet and consume it as their first transaction,
+paying that transaction's fee from the input note. User-created faucets include
+`BasicWallet`, so they can receive fee funding too. The SDK supplies native
+fee-conversion data automatically.
+
+Copy `web-client/lib/feeSupport.ts` with the complete TypeScript examples and
+`web-client/lib/react/tutorialSupport.tsx` with React examples. These repository
+helpers fund accounts, synchronize state, and await confirmation. They select
+application notes by ID or token and exclude `TX_FEE` notes (tag `0xFEE`).
+
+The React helper's `tutorialAuthScheme()` returns the low-level Falcon enum
+required by wallet and faucet hooks; it differs from the high-level client enum.
+
+The faucet URL and funding amount default to the selected network's faucet and its advertised
+`base_amount`. Override them with `NEXT_PUBLIC_MIDEN_FAUCET_URL` and
+`NEXT_PUBLIC_MIDEN_FEE_AMOUNT`, or `MIDEN_FAUCET_URL` and `MIDEN_FEE_AMOUNT` in the
+repository runner. `NEXT_PUBLIC_MIDEN_NETWORK` selects the network when running the app directly.
+
## Node.js 22+ `localStorage` polyfill
-If you run `next dev` under Node.js 22 or later, every page request will crash with:
+Some Node.js and Next.js combinations fail in the development overlay with:
```
TypeError: localStorage.getItem is not a function
```
-This is a Node + Next.js interaction, not a Miden SDK issue. Node 22+ defines `globalThis.localStorage` as an object, but its methods (`getItem`, `setItem`, …) are undefined unless Node is launched with `--localstorage-file`. Next.js's dev overlay guards with `typeof localStorage !== 'undefined'`, which passes on Node 22+, and then calls the missing methods.
+The workaround below handles a server-side `localStorage` object that lacks the methods the development overlay expects. Apply it if you encounter this error; it is not a requirement for every Node.js 22+ installation.
Add this polyfill at the top of `next.config.ts`, before the config object:
@@ -180,22 +204,23 @@ Add this polyfill at the top of `next.config.ts`, before the config object:
}
```
-This only affects `next dev` (SSR); static exports via `next build` are unaffected. The polyfill is harmless on Node ≤21 — it installs an in-memory stub that the dev overlay uses just like Node 22+'s (broken) built-in.
+This fallback supplies in-memory storage to server-side tooling. It does not persist application data and does not replace the browser's storage.
## SDK API Patterns
### Transaction return types
-All transaction methods return an object, not a plain transaction ID:
+Transaction calls return an object containing the ID and result. In these fragments, `mintOptions` and `sendOptions` are the parameter objects built with your funded accounts and token, as shown in the mint-and-transfer tutorial:
```ts
// mint and consume return { txId, result }
-const { txId } = await client.transactions.mint({ ... });
+const { txId: mintTxId, result: mintResult } =
+ await client.transactions.mint(mintOptions);
// send returns { txId, note, result }
// note is non-null when returnNote: true
-const { txId, note } = await client.transactions.send({
- ...,
+const { txId: sendTxId, note } = await client.transactions.send({
+ ...sendOptions,
returnNote: true,
});
```
@@ -207,12 +232,12 @@ You can wait for a transaction to be committed in two ways:
```ts
// Option 1: Pass waitForConfirmation in the transaction call
await client.transactions.mint({
- ...,
+ ...mintOptions,
waitForConfirmation: true,
});
// Option 2: Wait separately using waitFor
-const { txId } = await client.transactions.mint({ ... });
+const { txId } = await client.transactions.mint(mintOptions);
await client.transactions.waitFor(txId); // accepts TransactionId object or hex string
```
@@ -221,7 +246,7 @@ await client.transactions.waitFor(txId); // accepts TransactionId object or hex
When displaying transaction IDs in explorer links, call `.toHex()`:
```ts
-const { txId } = await client.transactions.mint({ ... });
+const { txId } = await client.transactions.mint(mintOptions);
console.log(`https://testnet.midenscan.com/tx/${txId.toHex()}`);
```
diff --git a/docs/src/web-client/unauthenticated_note_how_to.md b/docs/src/web-client/unauthenticated_note_how_to.md
index 73b7e929..82009467 100644
--- a/docs/src/web-client/unauthenticated_note_how_to.md
+++ b/docs/src/web-client/unauthenticated_note_how_to.md
@@ -7,15 +7,24 @@ import { CodeSdkTabs } from '@site/src/components';
_Using unauthenticated notes for optimistic note consumption with the Miden client_
+:::note v0.16 setup
+
+Follow the [network and fee setup](./setup_guide.md#network-and-fee-setup)
+and copy the shared support files imported by the complete example.
+For React snippets, initialize `authScheme` with `await tutorialAuthScheme()`
+as shown in the complete example.
+
+:::
+
## Overview
-In this tutorial, we will explore how to leverage unauthenticated notes on Miden to settle transactions faster than the blocktime using the Miden client. Unauthenticated notes are essentially UTXOs that have not yet been fully committed into a block. This feature allows the notes to be created and consumed within the same batch during [batch production](https://0xmiden.github.io/miden-docs/imported/miden-base/src/blockchain.html#batch-production).
+This tutorial passes newly created P2ID notes directly to the next consumer. An unauthenticated input contains the full note without an inclusion proof. The transaction kernel delegates verification of the note's existence to the protocol kernels, allowing the consumer transaction to execute before the producer transaction is confirmed. Final settlement still requires verification by the network.
-When using unauthenticated notes, both the creation and consumption of notes can happen within the same batch, enabling faster-than-blocktime settlement. This is particularly powerful for applications requiring high-frequency transactions or optimistic settlement patterns.
+The Web and React SDKs choose the input mode from the executing client's store: they use an authenticated input when an inclusion proof is available and an unauthenticated input otherwise. Passing a full `Note` supports unauthenticated consumption but does not force that mode; synchronization can make an inclusion proof available.
-We construct a chain of transactions using the unauthenticated notes method on the transaction builder. Unauthenticated notes are also referred to as "erasable notes". We also demonstrate how a note can be created and consumed, highlighting the ability to transfer notes between client instances for asset transfers that can be settled between parties faster than the blocktime.
+The example uses one client to manage Alice and five recipient wallets. At each hop, it submits the consumer transaction before waiting for the sender's confirmation, then waits for both transactions before advancing to the next wallet. It verifies the resulting balances; it does not measure transaction latency or guarantee that both transactions settle in the same batch.
-For example, our demo creates a chain of unauthenticated note transactions:
+The asset follows this chain:
```markdown
Alice ➡ Wallet 1 ➡ Wallet 2 ➡ Wallet 3 ➡ Wallet 4 ➡ Wallet 5
@@ -26,11 +35,11 @@ Alice ➡ Wallet 1 ➡ Wallet 2 ➡ Wallet 3 ➡ Wallet 4 ➡ Wallet 5
- **Introduction to Unauthenticated Notes:** Understand what unauthenticated notes are and how they differ from standard notes.
- **Miden Client Setup:** Configure the Miden client for browser-based transactions.
- **P2ID Note Creation:** Learn how to create Pay-to-ID notes for targeted transfers.
-- **Performance Insights:** Observe how unauthenticated notes can reduce transaction times dramatically.
+- **Confirmation Boundaries:** Distinguish optimistic execution from confirmed settlement.
## Prerequisites
-- Node `v20` or greater
+- Node `v20.9.0` or greater (required by the current Next.js template)
- Familiarity with TypeScript
- `yarn`
@@ -43,7 +52,7 @@ This tutorial assumes you have a basic understanding of Miden assembly. To quick
- Install the Miden SDK.
2. **Client Initialization:**
- - Set up the Miden client to connect with the Miden testnet.
+ - Set up the Miden client to connect with Miden testnet.
3. **Account Creation:**
- Create wallet accounts for Alice and multiple transfer recipients.
@@ -55,8 +64,8 @@ This tutorial assumes you have a basic understanding of Miden assembly. To quick
5. **Unauthenticated Note Transfer Chain:**
- Create P2ID (Pay-to-ID) notes for each transfer in the chain.
- - Use unauthenticated input notes to consume notes faster than blocktime.
- - Measure and observe the performance benefits.
+ - Pass each full output note directly to the next consumer.
+ - Wait for both transactions to be confirmed and verify the balances.
## Step 1: Initialize your Next.js project
@@ -77,19 +86,21 @@ This tutorial assumes you have a basic understanding of Miden assembly. To quick
3. Install the Miden SDK:
-**NOTE!**: Be sure to add the `--webpack` command to your `package.json` when running the `dev script`. The dev script should look like this:
+The current Next.js template uses Turbopack by default. Use the webpack configuration from the setup guide and update both scripts in `package.json`:
`package.json`
```json
+{
"scripts": {
"dev": "next dev --webpack",
- ...
+ "build": "next build --webpack"
}
+}
```
## Step 2: Edit the `app/page.tsx` file
@@ -154,7 +165,7 @@ export default function Home() {
Create the library file and add the following code:
```bash
-mkdir -p lib
+mkdir -p lib/react
```
Copy and paste the following code into `lib/react/unauthenticatedNoteTransfer.tsx` (React) or `lib/unauthenticatedNoteTransfer.ts` (TypeScript):
@@ -162,63 +173,80 @@ Copy and paste the following code into `lib/react/unauthenticatedNoteTransfer.ts
{
-..// 1. Create Alice and 5 wallets for the transfer chain
-..console.log('Creating accounts…');
-..const alice = await createWallet({ storageMode: StorageMode.Public });
-..console.log('Alice account ID:', alice.id().toString());
-
-..const wallets = [];
-..for (let i = 0; i < 5; i++) {
-...const wallet = await createWallet({ storageMode: StorageMode.Public });
+..await sync();
+..const authScheme = await tutorialAuthScheme();
+..const alice = await createWallet({
+...storageMode: StorageMode.Public,
+...authScheme,
+..});
+..console.log('Alice ID:', alice.id().toString());
+..await fundAccount(alice);
+..const wallets: Account[] = [];
+..for (let index = 0; index < 5; index += 1) {
+...const wallet = await createWallet({
+....storageMode: StorageMode.Public,
+....authScheme,
+...});
+...console.log(\`Wallet \${index}:\`, wallet.id().toString());
+...// Every recipient pays fees when consuming and forwarding the note.
+...await fundAccount(wallet);
...wallets.push(wallet);
-...console.log(\`Wallet \${i}:\`, wallet.id().toString());
..}
-
-..// 2. Deploy a fungible faucet
..const faucet = await createFaucet({
...tokenSymbol: 'MID',
...decimals: 8,
...maxSupply: BigInt(1_000_000),
...storageMode: StorageMode.Public,
+...authScheme,
..});
..console.log('Faucet ID:', faucet.id().toString());
-
-..// 3. Mint 10,000 MID to Alice
-..const mintResult = await mint({
+..await fundAccount(faucet);
+..await sync();
+..const minted = await mint({
...faucetId: faucet,
...targetAccountId: alice,
...amount: BigInt(10_000),
...noteType: NoteVisibility.Public,
..});
+..await committed(minted.transactionId);
+..const notes = await waitForTokenNotes(alice, faucet);
+..const consumed = await consume({ accountId: alice.id().toString(), notes });
+..await committed(consumed.transactionId);
-..console.log('Waiting for settlement…');
-..await waitForCommit(mintResult.transactionId);
-
-..// 4. Consume the freshly minted notes
-..const notes = await waitForConsumableNotes({ accountId: alice });
-..await consume({ accountId: alice.id().toString(), notes });
-
-..// 5. Create the unauthenticated note transfer chain:
-..// Alice → Wallet 0 → Wallet 1 → Wallet 2 → Wallet 3 → Wallet 4
-..console.log('Starting unauthenticated transfer chain…');
+..// Pass full Note objects directly, without fetching an inclusion proof.
..let currentSender = alice;
-..for (let i = 0; i < wallets.length; i++) {
-...const wallet = wallets[i];
-...const { note } = await send({
+..for (let index = 0; index < wallets.length; index += 1) {
+...const wallet = wallets[index];
+...const sent = await send({
....from: currentSender,
....to: wallet,
....assetId: faucet,
@@ -226,58 +254,68 @@ function UnauthenticatedNoteTransferInner() {
....noteType: NoteVisibility.Public,
....returnNote: true,
...});
-...const result = await consume({ accountId: wallet.id().toString(), notes: [note] });
+...if (!sent.note) throw new Error('Send did not return its output note');
+...const received = await consume({
+....accountId: wallet.id().toString(),
+....notes: [sent.note],
+...});
+...await committed(sent.txId);
+...await committed(received.transactionId);
+...await assertBalance(wallet, faucet, BigInt(50));
...console.log(
-....\`Transfer \${i + 1}: https://testnet.midenscan.com/tx/\${result.transactionId}\`,
+....\`Transfer \${index + 1}: \${tutorialExplorerUrl()}/tx/\${received.transactionId}\`,
...);
...currentSender = wallet;
..}
-
+..await assertBalance(alice, faucet, BigInt(9950));
+..for (const wallet of wallets.slice(0, -1))
+...await assertBalance(wallet, faucet, BigInt(0));
..console.log('Asset transfer chain completed ✅');
.};
.return (
-..
+..
.);
}
export default function UnauthenticatedNoteTransfer() {
.return (
-..
+..
...
..
.);
}`},
- typescript: { code:`import {
-.MidenClient,
-.NoteVisibility,
-.StorageMode,
-} from '@miden-sdk/miden-sdk/lazy';
-
-/\*\*
-.\* Demonstrates unauthenticated note transfer chain against Miden testnet
-.\* Creates a chain of P2ID (Pay to ID) notes: Alice → wallet 1 → wallet 2 → wallet 3 → wallet 4
-.\*
-.\* @throws {Error} If the function cannot be executed in a browser environment
-.\*/
+ typescript: { code: `import { NoteVisibility, StorageMode } from '@miden-sdk/miden-sdk/lazy';
+import {
+.consumeAllFeeAware,
+.createTutorialClient,
+.fundAccountForFees,
+.tutorialExplorerUrl,
+} from './feeSupport';
+
export async function unauthenticatedNoteTransfer(): Promise {
.// Ensure this runs only in a browser context
.if (typeof window === 'undefined') return console.warn('Run in browser');
-.// Wait for WASM to be ready before touching any wasm-bindgen type.
-.await MidenClient.ready();
-
-.const client = await MidenClient.create({
-..rpcUrl: 'https://rpc.testnet.miden.io',
+.const client = await createTutorialClient({
+..proverUrl: 'local',
.});
.console.log('Latest block:', (await client.sync()).blockNum());
-.// ── Creating accounts ──────────────────────────────────────────────────────
+.// ── Creating new account ──────────────────────────────────────────────────────
+.console.log('Creating accounts');
+
.console.log('Creating account for Alice…');
.const alice = await client.accounts.create({
..storage: StorageMode.Public,
@@ -293,7 +331,6 @@ export async function unauthenticatedNoteTransfer(): Promise {
..console.log('wallet ', i.toString(), wallet.id().toString());
.}
-.// ── Creating new faucet ──────────────────────────────────────────────────────
.const faucet = await client.accounts.create({
..type: 0, // 0 = FungibleFaucet
..symbol: 'MID',
@@ -302,22 +339,23 @@ export async function unauthenticatedNoteTransfer(): Promise {
..storage: StorageMode.Public,
.});
.console.log('Faucet ID:', faucet.id().toString());
+.await fundAccountForFees(client, alice);
+.await fundAccountForFees(client, faucet);
-.// ── Mint 10,000 MID to Alice ──────────────────────────────────────────────────────
+.await client.sync();
.const { txId: mintTxId } = await client.transactions.mint({
..account: faucet,
..to: alice,
..amount: BigInt(10_000),
..type: NoteVisibility.Public,
.});
-
.console.log('Waiting for settlement');
-.await client.transactions.waitFor(mintTxId);
+.await client.transactions.waitFor(mintTxId, { timeout: 120_000 });
+.await consumeAllFeeAware(client, alice);
-.// ── Consume the freshly minted note ──────────────────────────────────────────────
-.await client.transactions.consumeAll({
-..account: alice,
-.});
+.for (const wallet of wallets) {
+..await fundAccountForFees(client, wallet);
+.}
.// ── Create unauthenticated note transfer chain ─────────────────────────────────────────────
.// Alice → wallet 1 → wallet 2 → wallet 3 → wallet 4
@@ -330,25 +368,37 @@ export async function unauthenticatedNoteTransfer(): Promise {
..console.log('Sender:', sender.id().toString());
..console.log('Receiver:', receiver.id().toString());
-..const { note } = await client.transactions.send({
+..await client.sync();
+..const { note, txId: sendTxId } = await client.transactions.send({
...account: sender,
...to: receiver,
...token: faucet,
...amount: BigInt(50),
...type: NoteVisibility.Public,
...returnNote: true,
+...waitForConfirmation: false,
..});
+..// Pass the full note before waiting for the sender's transaction.
+..await client.sync();
..const { txId: consumeTxId } = await client.transactions.consume({
...account: receiver,
...notes: [note],
+...waitForConfirmation: true,
+...timeout: 120_000,
..});
+..await client.transactions.waitFor(sendTxId, { timeout: 120_000 });
+..console.log(\`Transaction committed: \${consumeTxId.toHex()}\`);
..console.log(
-...\`Consumed Note Tx on MidenScan: https://testnet.midenscan.com/tx/\${consumeTxId.toHex()}\`,
+...\`Consumed Note Tx on MidenScan: \${tutorialExplorerUrl()}/tx/\${consumeTxId.toHex()}\`,
..);
.}
+.const lastWallet = await client.accounts.get(wallets[wallets.length - 1]);
+.const balance = lastWallet?.vault().getBalance(faucet.id());
+.if (balance !== BigInt(50))
+..throw new Error(\`Expected last wallet to hold 50 MID, got \${balance}\`);
.console.log('Asset transfer chain completed ✅');
}` },
}} reactFilename="lib/react/unauthenticatedNoteTransfer.tsx" tsFilename="lib/unauthenticatedNoteTransfer.ts" />
@@ -360,7 +410,7 @@ export async function unauthenticatedNoteTransfer(): Promise {
Unauthenticated notes are a powerful feature that allows notes to be:
- **Created and consumed in the same block**
-- **Transferred faster than blocktime**
+- **Passed to a consuming transaction before block confirmation**
- **Used for optimistic transactions**
### Performance Benefits
@@ -368,8 +418,8 @@ Unauthenticated notes are a powerful feature that allows notes to be:
By using unauthenticated notes, we can:
- Skip waiting for block confirmation between note creation and consumption
-- Create transaction chains that execute within a single block
-- Achieve sub-blocktime settlement for certain use cases
+- Submit dependent transaction chains that may be included in a single block
+- Begin dependent execution earlier; final settlement still requires network confirmation
### Use Cases
@@ -378,7 +428,7 @@ Unauthenticated notes are ideal for:
- **High-frequency trading applications**
- **Payment channels**
- **Micropayment systems**
-- **Any scenario requiring fast settlement**
+- **Applications that benefit from optimistic execution before confirmation**
## Running the Example
@@ -423,27 +473,30 @@ Asset transfer chain completed ✅
## Conclusion
-Unauthenticated notes on Miden offer a powerful mechanism for achieving faster asset settlements by allowing notes to be both created and consumed within the same block. In this guide, we walked through:
+Unauthenticated notes let applications submit dependent transactions without first waiting for the producer's confirmation. Creation and consumption may be included in the same block; this does not provide settlement before block production. In this guide, we walked through:
- **Setting up the Miden client** against testnet
- **Creating P2ID Notes** for targeted asset transfers between specific accounts
-- **Building Transaction Chains** using unauthenticated input notes for sub-blocktime settlement
-- **Performance Observations** demonstrating how unauthenticated notes enable faster-than-blocktime transfers
+- **Building Transaction Chains** that submit consumption before waiting for the producer's confirmation
+- **Confirmation and balance checks** for the complete transfer chain
By following this guide, you should now have a clear understanding of how to build and deploy high-performance transactions using unauthenticated notes on Miden with the Miden client. Unauthenticated notes are the ideal approach for applications like central limit order books (CLOBs) or other DeFi platforms where transaction speed is critical.
### Resetting the `MidenClientDB`
-The Miden webclient stores account and note data in the browser. If you get errors such as "Failed to build MMR", then you should reset the Miden webclient store. When switching between Miden networks such as from localhost to testnet be sure to reset the browser store. To clear the account and node data in the browser, paste this code snippet into the browser console:
+Stop or terminate the tutorial client and close other tabs using its store before resetting it. This deletes local account data and keys, so use it only for disposable tutorial accounts. The snippet below waits for deletion of the default testnet store; change `name` if you configured a different store.
```javascript
(async () => {
- const dbs = await indexedDB.databases();
- for (const db of dbs) {
- await indexedDB.deleteDatabase(db.name);
- console.log(`Deleted database: ${db.name}`);
- }
- console.log('All databases deleted.');
+ const name = 'MidenClientDB_mtst';
+ await new Promise((resolve, reject) => {
+ const request = indexedDB.deleteDatabase(name);
+ request.onsuccess = () => resolve();
+ request.onerror = () => reject(request.error);
+ request.onblocked = () =>
+ reject(new Error('Close clients and tabs using this store, then retry.'));
+ });
+ console.log(`Deleted database: ${name}`);
})();
```
@@ -454,7 +507,7 @@ To run a full working example navigate to the `web-client` directory in the [mid
```bash
cd web-client
yarn install
-yarn start
+yarn dev
```
### Continue learning
diff --git a/examples/bridging-app/.env.example b/examples/bridging-app/.env.example
index 65a1139a..72680d80 100644
--- a/examples/bridging-app/.env.example
+++ b/examples/bridging-app/.env.example
@@ -1,15 +1,21 @@
# Miden SDK network configuration
-# Supported values: devnet | testnet | local | https://your-rpc-url
+# Wallet network: devnet | testnet | local
+VITE_MIDEN_NETWORK=testnet
+# RPC: devnet | testnet | local | https://your-rpc-url
VITE_MIDEN_RPC_URL=testnet
# Prover configuration
# Supported values: devnet | testnet | local | https://your-prover-url
VITE_MIDEN_PROVER=testnet
-# Epoch allocator service URL (Sepolia <-> Miden testnet bridging).
+# Epoch allocator compatible with v0.16 on the selected Miden network.
VITE_ALLOCATOR_URL=https://testnet-dev.epochprotocol.xyz
-# Optional: override the Miden testnet explorer used for tx links.
+# Obtain the USDC faucet ID from an allocator deployed on the selected network.
+# When unset, enter the faucet ID manually in the form.
+VITE_MIDEN_USDC_FAUCET_ID=
+
+# Optional: override the selected network's explorer used for tx links.
# Default if unset: https://testnet.midenscan.com
# VITE_MIDENSCAN_URL=https://testnet.midenscan.com
diff --git a/examples/bridging-app/README.md b/examples/bridging-app/README.md
index 3aa95331..bf17ca0f 100644
--- a/examples/bridging-app/README.md
+++ b/examples/bridging-app/README.md
@@ -15,24 +15,30 @@ yarn install
yarn dev
```
-Open [http://localhost:5173](http://localhost:5173). The app exposes two tabs — `Bridge to EVM` (Miden → Sepolia) and `Withdraw to Miden` (Sepolia → Miden) — wired to the Epoch testnet allocator (`testnet-dev.epochprotocol.xyz`).
+Open [http://localhost:5173](http://localhost:5173). The app exposes two tabs — `Bridge to EVM` (Miden → Sepolia) and `Withdraw to Miden` (Sepolia → Miden). It defaults to Miden **testnet**. A live round trip requires an Epoch allocator and asset faucet deployed on that same network, plus both wallets.
## Environment
Copy `.env.example` to `.env` and supply the required values:
-| Variable | Required | Description |
-| ---------------------------- | -------- | --------------------------------------------------------------------------- |
-| `VITE_RAINBOWKIT_PROJECT_ID` | yes | WalletConnect Cloud project id from . |
-| `VITE_ALLOCATOR_URL` | yes | Epoch allocator endpoint (default `https://testnet-dev.epochprotocol.xyz`). |
-| `VITE_MIDEN_RPC_URL` | no | Miden RPC; defaults to `testnet`. |
-| `VITE_MIDEN_PROVER` | no | Miden prover; defaults to `testnet`. |
-| `VITE_MIDENSCAN_URL` | no | Override block-explorer base; defaults to `https://testnet.midenscan.com`. |
+| Variable | Required | Description |
+| ---------------------------- | ------------ | ----------------------------------------------------------------------------------------------------------------- |
+| `VITE_RAINBOWKIT_PROJECT_ID` | yes | WalletConnect Cloud project id from . |
+| `VITE_ALLOCATOR_URL` | yes | An Epoch allocator compatible with v0.16 on the selected Miden network. |
+| `VITE_MIDEN_NETWORK` | no | `testnet` (default), `devnet`, or `local`; selects the wallet network and RPC/prover defaults. |
+| `VITE_MIDEN_RPC_URL` | no | Override Miden RPC; must match the selected wallet network. |
+| `VITE_MIDEN_PROVER` | no | Override the prover; `local` uses local proving. |
+| `VITE_MIDEN_USDC_FAUCET_ID` | yes for USDC | Allocator-approved faucet on the selected network. If unset, enter it manually in the form; no old ID is assumed. |
+| `VITE_MIDENSCAN_URL` | no | Override block-explorer base; defaults to the selected network's Midenscan. |
+
+For explicit devnet checks, set `VITE_MIDEN_NETWORK`, `VITE_MIDEN_RPC_URL`, and
+`VITE_MIDEN_PROVER` to `devnet` and use a matching allocator and faucet.
## Prerequisites
- An EVM wallet supported by [RainbowKit](https://www.rainbowkit.com/) (MetaMask, Rabby, Coinbase Wallet, …).
- The [MidenFi browser extension](https://chromewebstore.google.com/detail/miden-wallet/ablmompanofnodfdkgchkpmphailefpb) to sign the P2IDE note on Miden.
+- Native MIDEN tokens in the Miden wallet to pay transaction fees.
- A small Sepolia ETH balance for gas; grab some from the [pk910 PoW faucet](https://sepolia-faucet.pk910.de/) or the [Google Cloud Sepolia faucet](https://cloud.google.com/application/web3/faucet/ethereum/sepolia).
## Scripts
@@ -41,7 +47,7 @@ Copy `.env.example` to `.env` and supply the required values:
yarn dev # Vite dev server (http://localhost:5173)
yarn build # tsc -b && vite build
yarn preview # Serve the production build locally
-yarn test # Vitest (scaffold-inherited tests)
+yarn test # Vitest
yarn lint # ESLint
```
diff --git a/examples/bridging-app/package.json b/examples/bridging-app/package.json
index 81c11533..122000c2 100644
--- a/examples/bridging-app/package.json
+++ b/examples/bridging-app/package.json
@@ -14,11 +14,11 @@
"test:coverage": "vitest --run --coverage"
},
"dependencies": {
- "@epoch-protocol/epoch-intents-sdk": "^1.0.23",
- "@miden-sdk/miden-sdk": "0.14.4",
- "@miden-sdk/miden-wallet-adapter-base": "0.14.3",
- "@miden-sdk/miden-wallet-adapter-react": "0.14.3",
- "@miden-sdk/react": "0.14.4",
+ "@epoch-protocol/epoch-intents-sdk": "1.0.38",
+ "@miden-sdk/miden-sdk": "0.16.0",
+ "@miden-sdk/miden-wallet-adapter-base": "0.16.0",
+ "@miden-sdk/miden-wallet-adapter-react": "0.16.0",
+ "@miden-sdk/react": "0.16.0",
"@phosphor-icons/react": "^2.1.10",
"@rainbow-me/rainbowkit": "^2.2.10",
"@tanstack/react-query": "^5.90.20",
@@ -34,7 +34,7 @@
},
"devDependencies": {
"@eslint/js": "^9.36.0",
- "@miden-sdk/vite-plugin": "0.14.4",
+ "@miden-sdk/vite-plugin": "0.16.0",
"@testing-library/dom": "^10.4.1",
"@testing-library/jest-dom": "^6.9.1",
"@testing-library/react": "^16.3.2",
@@ -58,4 +58,4 @@
"vite-plugin-wasm": "^3.5.0",
"vitest": "^4.0.18"
}
-}
\ No newline at end of file
+}
diff --git a/examples/bridging-app/src/components/__tests__/IntentForm.test.tsx b/examples/bridging-app/src/components/__tests__/IntentForm.test.tsx
index 2a2ec0cf..1ff4ce6c 100644
--- a/examples/bridging-app/src/components/__tests__/IntentForm.test.tsx
+++ b/examples/bridging-app/src/components/__tests__/IntentForm.test.tsx
@@ -37,12 +37,8 @@ vi.mock('@miden-sdk/miden-wallet-adapter-react', () => ({
}),
}));
vi.mock('@miden-sdk/miden-wallet-adapter-base', () => ({
- // IntentForm constructs a `SendTransaction` only in the Confirm-&-sign flow,
- // which these tests do not exercise. A minimal class stub keeps imports
- // resolvable without dragging in the real WASM-backed SDK.
- SendTransaction: class {
- constructor(public sender: string, public recipient: string, public faucet: string, public note: string, public amount: number) {}
- },
+ // These rendering tests do not submit the custom collateral transaction.
+ Transaction: { createCustomTransaction: vi.fn() },
}));
vi.mock('wagmi', () => ({
useAccount: () => ({ address: undefined, isConnected: false }),
diff --git a/examples/bridging-app/src/components/__tests__/WithdrawConsume.test.tsx b/examples/bridging-app/src/components/__tests__/WithdrawConsume.test.tsx
index c6b91e7c..bd269f47 100644
--- a/examples/bridging-app/src/components/__tests__/WithdrawConsume.test.tsx
+++ b/examples/bridging-app/src/components/__tests__/WithdrawConsume.test.tsx
@@ -30,7 +30,7 @@ describe('WithdrawConsume', () => {
expect(
screen.getByRole('heading', { name: /Note delivered to your Miden wallet/i }),
).toBeInTheDocument();
- expect(screen.getByText(/Miden Wallet auto-consumes incoming notes/i)).toBeInTheDocument();
+ expect(screen.getByText(/sufficient native MIDEN to pay the consumption fee/i)).toBeInTheDocument();
// Truncated note id (head of the hex string) is rendered.
expect(screen.getByText(/0xnote1234/i)).toBeInTheDocument();
// Midenscan link points at the right path.
diff --git a/examples/bridging-app/src/components/crosschain/IntentForm.tsx b/examples/bridging-app/src/components/crosschain/IntentForm.tsx
index 32017f5b..d4813a3e 100644
--- a/examples/bridging-app/src/components/crosschain/IntentForm.tsx
+++ b/examples/bridging-app/src/components/crosschain/IntentForm.tsx
@@ -3,8 +3,9 @@ import { Input } from '@/components/ui/input';
import { Label } from '@/components/ui/label';
import { SelectContent, SelectItem, SelectRoot, SelectTrigger, SelectValue } from '@/components/ui/select';
import { useMidenFiWallet } from '@miden-sdk/miden-wallet-adapter-react';
-import { SendTransaction } from '@miden-sdk/miden-wallet-adapter-base';
-import { useSyncState } from '@miden-sdk/react';
+import { Transaction } from '@miden-sdk/miden-wallet-adapter-base';
+import { AccountId, NoteArray } from '@miden-sdk/miden-sdk';
+import { useMiden, useSyncState } from '@miden-sdk/react';
import { useState } from 'react';
import { toast } from 'sonner';
import type { CrossChainIntentParams } from '../../types/miden';
@@ -16,6 +17,8 @@ import { DEFAULT_SEPOLIA_CHAIN_ID_STR } from '../../constants/chains';
import { useAccount } from 'wagmi';
import { useIntentTransactionStatus } from '../../hooks/useIntentTransactionStatus';
import { selectDestinationSettlement } from '../../lib/intentSettlement';
+import { createEpochCollateralNote } from '../../services/epoch-collateral';
+import { midenscanNoteUrl } from '../../lib/explorers';
const SEPOLIA_TOKENS = [
{ symbol: 'USDC', address: '0x2BB4FfD7E2c6D432b697554Efd77fA13bdbefd69', decimals: 18 },
@@ -41,7 +44,7 @@ interface Props {
}>;
isLoadingMidenAssets: boolean;
onFetchQuote: (params: CrossChainIntentParams) => Promise;
- onConfirmIntent: (createMidenP2IDNote: SolveIntentParams['createMidenP2IDNote']) => Promise;
+ onConfirmIntent: (createMidenP2IDENote: SolveIntentParams['createMidenP2IDENote']) => Promise;
onClearQuote: () => void;
pendingQuote: CrossChainQuote | null;
isFetchingQuote: boolean;
@@ -65,7 +68,8 @@ export function IntentForm({
intentNonce,
intentUserAddress,
}: Props) {
- const { requestSend, waitForTransaction } = useMidenFiWallet();
+ const { requestTransaction, waitForTransaction } = useMidenFiWallet();
+ const { client, runExclusive } = useMiden();
const { syncHeight } = useSyncState();
const [selectedAssetId, setSelectedAssetId] = useState('');
@@ -103,10 +107,8 @@ export function IntentForm({
const evmTransactionHash = evmCompletedStatus?.transactionHash;
const evmTxChainId = evmCompletedStatus?.chainId ?? destinationChainIdNum;
- const midenScanBase =
- (import.meta as any).env?.VITE_MIDENSCAN_URL || 'https://testnet.midenscan.com';
const midenNoteUrl = localMidenNoteId
- ? `${midenScanBase}/note/${localMidenNoteId}`
+ ? midenscanNoteUrl(localMidenNoteId)
: undefined;
const explorerTxUrl = (() => {
@@ -197,41 +199,44 @@ export function IntentForm({
const handleConfirm = () => {
if (!pendingQuote) return;
- const createMidenP2IDNote: SolveIntentParams['createMidenP2IDNote'] = async (
+ const createMidenP2IDENote: SolveIntentParams['createMidenP2IDENote'] = async (
faucetIdParam,
amountParam,
allocatorId,
+ recallBlocks,
+ bindingAttachmentFelts,
) => {
setConfirmStatus('Resource lock required — creating P2IDE note on Miden…');
try {
if (!midenAccountId) {
throw new Error('Missing Miden account id');
}
- if (!requestSend) {
- throw new Error('Miden wallet adapter is not connected');
+ if (!requestTransaction || !waitForTransaction || !client) {
+ throw new Error('Connect a Miden wallet that supports custom transactions and confirmation');
}
- const normalizedAmount = BigInt(amountParam);
- if (normalizedAmount > BigInt(Number.MAX_SAFE_INTEGER)) {
- throw new Error('Amount too large for wallet adapter send');
- }
-
- const payload = new SendTransaction(
- midenAccountId,
- allocatorId,
- faucetIdParam,
- 'public',
- Number(normalizedAmount),
+ const { request, expectedNoteId } = await runExclusive(async () => {
+ const head = await client.syncState();
+ const note = createEpochCollateralNote({
+ sender: midenAccountId, allocator: allocatorId, faucet: faucetIdParam,
+ amount: BigInt(amountParam), currentBlock: head.blockNum(),
+ recallBlocks, bindingAttachmentFelts,
+ });
+ const builder = await client.feeAwareTransactionRequestBuilder(AccountId.fromHex(midenAccountId));
+ return {
+ expectedNoteId: note.id().toString(),
+ request: builder.withOwnOutputNotes(new NoteArray([note])).build(),
+ };
+ });
+ const txId = await requestTransaction(
+ Transaction.createCustomTransaction(midenAccountId, allocatorId, request),
);
- const txId = await requestSend(payload);
- // Prefer adapter waitForTransaction to get the output note id.
- if (!waitForTransaction) {
- throw new Error('Miden wallet adapter is missing waitForTransaction');
- }
+ // Wait for the wallet to confirm the collateral before submitting it to Epoch.
const finalized = await waitForTransaction(txId, 120_000);
- const first = finalized.outputNotes?.[0];
- const noteId = first ? first.id().toString() : '';
+ // A fee-paying transaction also creates a TX_FEE note. Match our exact
+ // collateral note instead of assuming outputNotes[0] is the payment.
+ const noteId = finalized.outputNotes?.find(note => note.id().toString() === expectedNoteId)?.id().toString();
if (!noteId) {
throw new Error(`Could not read output note id for tx ${txId}`);
}
@@ -245,7 +250,7 @@ export function IntentForm({
void toast.promise(
(async () => {
setConfirmStatus('Submitting intent…');
- const result = await onConfirmIntent(createMidenP2IDNote);
+ const result = await onConfirmIntent(createMidenP2IDENote);
if (result && typeof result === 'object' && 'error' in result && (result as { error?: string }).error) {
throw new Error((result as { error: string }).error);
}
diff --git a/examples/bridging-app/src/components/crosschain/WithdrawConsume.tsx b/examples/bridging-app/src/components/crosschain/WithdrawConsume.tsx
index 83574d87..1ca43e51 100644
--- a/examples/bridging-app/src/components/crosschain/WithdrawConsume.tsx
+++ b/examples/bridging-app/src/components/crosschain/WithdrawConsume.tsx
@@ -12,9 +12,9 @@ interface Props {
* Post-withdraw informational panel.
*
* The Epoch allocator delivers bridged funds as a P2ID note addressed to the
- * user's Miden wallet account. The Miden Wallet consumes that note when it
- * detects it, so this panel reports delivery and links to Midenscan instead of
- * initiating a second transaction from the page.
+ * user's Miden wallet account. Its consumption is handled by the wallet and
+ * requires native fees, so this panel reports delivery and links to Midenscan
+ * instead of initiating a second transaction from the page.
*/
export function WithdrawConsume({ noteId }: Props) {
if (!noteId) return null;
@@ -26,10 +26,10 @@ export function WithdrawConsume({ noteId }: Props) {
The allocator delivered the bridged funds as a P2ID note to your Miden
wallet account. In Miden's actor model the note must be{' '}
- consumed before it becomes spendable balance — but the Miden Wallet auto-consumes
- incoming notes on detection, so no action is required here. Open your wallet to confirm
- the new balance; the bridged USDC is ready to use as the source for another
- Miden → EVM bridge or any other Miden transaction.
+ consumed before it becomes spendable balance. Wallet auto-consumption depends
+ on your wallet settings and sufficient native MIDEN to pay the consumption fee.
+ A USDC note does not itself fund that native fee. Open your wallet to confirm
+ consumption and the new balance before spending the bridged funds.
If your wallet does not show the credited balance, the note may still be propagating
- through testnet — refresh the wallet after a few seconds. Some wallet builds let you
+ through the network — refresh the wallet after a few seconds. Some wallet builds let you
disable auto-consume; in that case, consume the note manually from the wallet's Notes tab.
diff --git a/examples/bridging-app/src/components/crosschain/WithdrawForm.tsx b/examples/bridging-app/src/components/crosschain/WithdrawForm.tsx
index d87488e2..b77d5278 100644
--- a/examples/bridging-app/src/components/crosschain/WithdrawForm.tsx
+++ b/examples/bridging-app/src/components/crosschain/WithdrawForm.tsx
@@ -8,6 +8,7 @@ import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
import { Label } from '@/components/ui/label';
import { SelectContent, SelectItem, SelectRoot, SelectTrigger, SelectValue } from '@/components/ui/select';
+import { MIDEN_USDC_FAUCET_ID } from '@/config';
// EVM-side token decimals + the Miden-side faucet account ID for each known
// token. The Epoch test ERC-20s on Sepolia are 18-decimal (verified on-chain);
@@ -27,7 +28,7 @@ const SEPOLIA_TOKENS: ReadonlyArray<{
symbol: 'USDC',
address: '0x2BB4FfD7E2c6D432b697554Efd77fA13bdbefd69',
decimals: 18,
- midenFaucetId: '0x0a7d175ed63ec5200fb2ced86f6aa5',
+ midenFaucetId: MIDEN_USDC_FAUCET_ID,
},
{ symbol: 'USDT', address: '0xc04d2869665Be874881133943523723Be5782720', decimals: 18 },
{ symbol: 'Custom', address: '', decimals: 18 },
diff --git a/examples/bridging-app/src/components/layout/Header.tsx b/examples/bridging-app/src/components/layout/Header.tsx
index d4b09aea..7568a9e1 100644
--- a/examples/bridging-app/src/components/layout/Header.tsx
+++ b/examples/bridging-app/src/components/layout/Header.tsx
@@ -1,3 +1,5 @@
+import { MIDEN_NETWORK } from '@/config';
+
export function Header() {
return (
@@ -9,7 +11,7 @@ export function Header() {
M