Miden Testnet 0.15.0
This guide covers all breaking changes you need to migrate an application to Miden 0.15.0. Like the 0.14 guide, it is intentionally user‑facing: you do not need to know or care which internal crate (VM, protocol, client) a change came from. If you are:
- building accounts, notes, or transactions
- running a client, web client or React SDK
- writing or compiling MASM
- interacting with storage, auth, or RPCs
this document is for you. It folds together the breaking changes from miden-base (protocol 0.14 → 0.15.3), miden-vm (0.22 → 0.23), miden-client (0.14 → 0.15), and the Web SDK (@miden-sdk/* 0.14 → 0.15). Because miden-client and the Web SDK still ship from unified‑in‑progress main/next branches, this guide unions the breaking surface from both.
At a Glance
Big themes in 0.15:
- Account IDs no longer encode faucet/regular, mutability, or network mode. The old
AccountType enum is gone; AccountStorageMode is renamed AccountType ({ Private, Public }). Faucet/network‑ness now comes from an account's components; the ID version is renamed 0 → 1.
- Notes get a real identity split. The old
NoteId (recipient + assets) becomes NoteDetailsCommitment; the new NoteId also commits to metadata, and nullifiers now fold in metadata + the attachments commitment — none roundtrip with 0.14.
- Notes carry multiple attachments, off the metadata.
NoteMetadata → PartialNoteMetadata, NoteMetadataHeader → NoteMetadata; attachments live on the note/record as a NoteAttachments collection (≤ 4). NoteType is now 1‑bit, default Private.
- Fungible faucets are unified.
BasicFungibleFaucet + NetworkFungibleFaucet → one FungibleFaucet (bon builder) + FungibleTokenMetadata + a TokenPolicyManager for mint/burn policies. Amounts are a validated AssetAmount newtype.
- Typed roots everywhere.
NoteScript::root() → NoteScriptRoot, TransactionScript::root() → TransactionScriptRoot, procedure_digest! → procedure_root!, plus AccountComponentName.
- VM 0.23 / crypto 0.25 are digest‑changing. SMT leaf hashing gains a Poseidon2 domain separator, the MAST wire format bumped
0.0.2 → 0.0.3 (old .masl/.masp won't load), execution is sync‑first (BaseHost/SyncHost; execute → ExecutionOutput), and adv_push.N was removed.
- The client RPC surface is rebuilt around
GetAccount. get_account_proof/get_account_details reshaped, check_nullifiers removed (use sync_nullifiers), most sync methods now require an explicit block_to.
- The Web SDK moved to the 0.15 protocol surface.
"network" storage mode is gone, the WASM AccountType narrowed to { Private, Public }, attachments are word‑vector‑shaped, Felt/Word throw on overflow, several methods return undefined/string instead of throwing/objects, the raw client's proveTransactionWithProver is renamed proveTransaction, and storeIdentifier() went async.
If you only skim a few sections, skim Account Changes, Note Changes, Asset, Vault and Faucet Changes, Hashing & Stack Conventions, and Client Changes.
Table of Contents
Imports and Dependencies
Summary
Every Miden crate moves up a minor: the protocol crates (miden-protocol, miden-standards, miden-tx, miden-testing) go 0.14 → 0.15.3, the VM crates (miden-assembly, miden-core, miden-core-lib, miden-processor, miden-prover) go 0.22 → 0.23, and miden-crypto goes 0.23 → 0.25. miden-client and miden-client-sqlite-store go 0.14 → 0.15; the Web SDK packages go 0.14 → 0.15. The client's MSRV is Rust 1.93 (the base crates build on 1.90+).
The prover crate is miden-prover in this line — it is not miden-prove. Keep depending on miden-prover.
Because the native hash and the MAST/serialization formats changed upstream, 0.14 artifacts (accounts, notes, proofs, serialized stores, .masl/.masp packages) do not round‑trip. Re‑assemble from source and re‑sync into a fresh store.
Affected Code
Cargo.toml:
- miden-client = "0.14"
- miden-client-sqlite-store = "0.14"
- miden-protocol = "0.14"
- miden-standards = "0.14"
- miden-tx = "0.14"
- miden-assembly = "0.22"
- miden-core = "0.22"
- miden-core-lib = "0.22"
- miden-processor = "0.22"
- miden-prover = "0.22"
- miden-crypto = "0.23"
+ miden-client = "0.15"
+ miden-client-sqlite-store = "0.15"
+ miden-protocol = "0.15.3"
+ miden-standards = "0.15.3"
+ miden-tx = "0.15.3"
+ miden-assembly = "0.23"
+ miden-core = "0.23"
+ miden-core-lib = "0.23"
+ miden-processor = "0.23"
+ miden-prover = "0.23"
+ miden-crypto = "0.25"
package.json (Web SDK):
- "@miden-sdk/miden-sdk": "^0.14.0",
- "@miden-sdk/react": "^0.14.0",
- "miden-idxdb-store": "^0.14.0"
+ "@miden-sdk/miden-sdk": "^0.15.0",
+ "@miden-sdk/react": "^0.15.0",
+ "miden-idxdb-store": "^0.15.0"
Migration Steps
- Bump every Miden crate per the table above and run
cargo update to pull the matching miden-crypto 0.25 and VM 0.23 minors.
- Do not rename
miden-prover to miden-prove.
- Set the client toolchain to at least Rust
1.93.
- Bump
@miden-sdk/miden-sdk, @miden-sdk/react, and miden-idxdb-store to ^0.15.0 together — a mix of 0.14/0.15 packages will not link against the shared WASM ABI.
- Point the client at a
0.15 node (the protocol version is negotiated at connect; a mismatch is rejected), and re‑sync into a fresh store.
Common Errors
| Error Message |
Cause |
Solution |
failed to select a version for miden-prove |
Crate not renamed in 0.15 |
Keep depending on miden-prover. |
MastForest deserialization failed: unexpected version |
MAST wire format bumped to 0.0.3 |
Re‑assemble every .masl/.masp from source under 0.23. |
| node version negotiation failure |
0.15 client against a 0.14 node |
Upgrade the node to 0.15. |
Hashing & Stack Conventions
SMT leaf hashing switched to Poseidon2 domain separation
Summary
The core library's Sparse Merkle Tree leaf hashing (miden::core collections::smt) now mixes a leaf‑domain separator into the Poseidon2 capacity word, so MASM‑side leaf digests match SmtLeaf::hash() in miden-crypto. Leaf preimages are hashed with poseidon2::merge_in_domain using LEAF_DOMAIN = 0x13af. This is a digest‑changing change: any SMT leaf digest, SMT root, or advice‑map key derived from MASM‑side leaf hashing under 0.22 will not reproduce under 0.23. It pairs with the miden-crypto 0.25 bump.
Affected Code
MASM (core‑lib collections::smt, simplified):
- exec.poseidon2::merge assert_eqw
+ push.LEAF_DOMAIN exec.poseidon2::merge_in_domain assert_eqw
The per‑leaf cycle cost also changed (the pair_count coefficient went from 3 to 6), so any hard‑coded cycle‑count expectations around smt::get / smt::set need updating.
Migration Steps
- Re‑derive every persisted SMT root, leaf digest, and advice‑map key computed from a MASM‑side SMT leaf hash under
0.22.
- If you compute SMT leaf digests in Rust via
miden-crypto, upgrade to 0.25 so both sides agree.
- Discard cached proofs / transaction artifacts whose witnesses depend on the old leaf hashing.
miden-crypto 0.25 downstream renames
Summary
Bumping to miden-crypto 0.25 (and miden-vm 0.23) surfaces several renames in code that builds against the protocol crates directly:
Felt::new(n) call sites that want the previous (non‑reducing) behaviour are now Felt::new_unchecked(n) (Felt::new now reduces modulo the field).
- The ECDSA secret key type
ecdsa_k256_keccak::SecretKey is renamed SigningKey; the EdDSA/X25519 key eddsa_25519_sha512::SecretKey is KeyExchangeKey. Falcon's falcon512_poseidon2::SecretKey is unchanged.
- The kernel's
EMPTY_SMT_ROOT constant was recomputed for the Plonky3‑aligned Poseidon2 and the domain‑separated SmtLeaf::hash — any hard‑coded SMT‑root literal changes.
- In kernel/standards MASM, the immediate form of
adv_push was dropped (see adv_push.N removed) and cross‑module‑referenced MASM constants/procedures must be marked pub.
Affected Code
- let f = Felt::new(value);
- use miden_protocol::crypto::dsa::ecdsa_k256_keccak::SecretKey;
- use miden_protocol::crypto::dsa::eddsa_25519_sha512::SecretKey as EdSecretKey;
+ let f = Felt::new_unchecked(value);
+ use miden_protocol::crypto::dsa::ecdsa_k256_keccak::SigningKey;
+ use miden_protocol::crypto::dsa::eddsa_25519_sha512::KeyExchangeKey;
Migration Steps
- Replace
Felt::new(...) with Felt::new_unchecked(...) where you relied on the non‑reducing constructor.
- Rename
ecdsa_k256_keccak::SecretKey → SigningKey and eddsa_25519_sha512::SecretKey → KeyExchangeKey.
- Mark any cross‑module‑referenced MASM constants/procedures
pub, and regenerate hard‑coded EMPTY_SMT_ROOT / SMT‑root literals.
Account Changes
AccountType removed; AccountStorageMode renamed to AccountType
Summary
The account ID was simplified so its prefix no longer encodes whether the account is a faucet/regular account or whether its code is mutable. As a result:
- The old
AccountType enum (FungibleFaucet, NonFungibleFaucet, RegularAccountImmutableCode, RegularAccountUpdatableCode) is removed.
AccountStorageMode is renamed to AccountType and trimmed to { Private, Public } (the Network variant is gone — see the next section).
- The
AccountId accessors is_faucet(), is_regular_account(), storage_mode(), is_network(), and the old account_type() semantics no longer exist. AccountId::account_type() now returns the visibility‑style AccountType (Private/Public).
- The account ID version is renamed 0 → 1; encoded version
0 is now invalid.
Faucet‑vs‑regular is now a property of the account's code/components, not its ID.
Affected Code
// 0.15 — new API:
use miden_protocol::account::AccountType; // formerly AccountStorageMode
let kind: AccountType = account_id.account_type(); // Private / Public
let is_pub = account_id.is_public();
// "is this a faucet?" now comes from the account's code/interface, not the id.
AccountId::new(seed, version, ..) keeps the same parameters, but version must be AccountIdVersion::Version1 (Version0 no longer exists).
Migration Steps
- Replace imports of
AccountStorageMode with AccountType; the variants are Private / Public.
- Delete the old
AccountType import (Regular*/*Faucet); that enum is gone.
- Replace
id.storage_mode() with id.account_type() (or id.is_public() / id.is_private()).
- Replace
id.is_faucet() / id.is_regular_account() with checks on the account's code/components.
- Replace
AccountIdVersion::Version0 with Version1; regenerate any persisted account IDs.
AccountStorageMode::Network removed; network accounts via an allowlist
Summary
The Network storage mode was removed (AccountStorageMode itself is now AccountType). An account is now recognised as a network account by the presence of a standardized NetworkAccountNoteAllowlist storage slot, with helpers NetworkAccount (a wrapper for identification) and the AuthNetworkAccount auth component. AccountId::is_network() is gone.
Migration Steps
- Remove any use of
AccountStorageMode::Network; pick Public and add the network‑account components (AuthNetworkAccount::with_allowed_notes(...)).
- Replace
id.is_network() with NetworkAccount::new(account) / the NetworkAccountNoteAllowlist slot check.
AuthNetworkAccount gains a tx‑script allowlist
Summary
(v0.15.2) The AuthNetworkAccount auth component previously banned transaction scripts outright. It now gates them with a root allowlist, so approved tx scripts (e.g. setting the expiration delta) can run. The note‑allowlist constructor with_allowlist was renamed to with_allowed_notes to pair with the new with_allowed_tx_scripts setter.
Migration Steps
- Rename
AuthNetworkAccount::with_allowlist(...) to with_allowed_notes(...).
- To permit specific transaction scripts, chain
.with_allowed_tx_scripts(roots) (an empty set — the default — permits none).
procedure_digest! → procedure_root!; new NoteScriptRoot / AccountComponentName
Summary
Several root/identifier values gained dedicated newtypes:
- The
procedure_digest! macro is renamed procedure_root!. It now returns an AccountProcedureRoot (instead of Word) and takes an AccountComponentCode (Component::code()) instead of a library‑producing closure.
NoteScript::root() returns a new NoteScriptRoot newtype instead of Word (convert with .into()).
- A new
AccountComponentName string wrapper validates component names.
Migration Steps
- Rename
procedure_digest! → procedure_root! and pass Component::code() as the last argument; the static is now a LazyLock<AccountProcedureRoot>.
- Update bindings of
note_script.root() to NoteScriptRoot (convert with .into() / Word::from(..) where a Word is needed).
- Use
AccountComponentName::new(...) where a validated component name is required.
is_compatible_with removed; auth/policy renames
Summary
A handful of standards‑library APIs changed:
StandardNote::is_compatible_with and AccountInterfaceExt::is_compatible_with were removed — perform compatibility checks via the account interface directly.
- The guarded‑multisig API was renamed
AuthMultisigGuardian → AuthGuardedMultisig (the guardian auth namespace is retained).
OwnerControlledBlocklist was renamed BlocklistOwnerControlled.
- New standard components were added:
Pausable, Authority, allowlist/blocklist transfer policies, and FungibleTokenMetadata.
Note Changes
NoteId → NoteDetailsCommitment; new NoteId commits to metadata
Summary
The 0.14 NoteId (a commitment over recipient + assets only) is renamed to NoteDetailsCommitment. A brand‑new NoteId is introduced that hashes the details commitment together with the note metadata commitment, so the public note ID now changes when the note's metadata (sender, type, tag, attachments) changes.
NoteDetailsCommitment = hash(NOTE_RECIPIENT_DIGEST || NOTE_ASSETS_COMMITMENT) // == old NoteId
NoteId = hash(NOTE_DETAILS_COMMITMENT || NOTE_METADATA_COMMITMENT) // new
Affected Code
// 0.15 — new API:
use miden_protocol::note::{NoteDetailsCommitment, NoteId};
let details_commitment = NoteDetailsCommitment::new(&recipient, &assets);
let id = NoteId::new(details_commitment, &metadata); // the real NoteId mixes in metadata
// On a built note: note.id() / note.details_commitment().
NoteDetails::commitment() now returns a NoteDetailsCommitment (not the public note id, which requires metadata).
Migration Steps
- Rename any value you treated as a "note id without metadata" to
NoteDetailsCommitment.
- Replace
NoteId::new(recipient, asset_commitment) with NoteDetailsCommitment::new(&recipient, &assets).
- To obtain the public
NoteId, call note.id(), or NoteId::new(details_commitment, &metadata).
- Recompute and re‑persist any stored note IDs — 0.14 ids do not roundtrip, and an id now changes if metadata changes.
NoteMetadata → PartialNoteMetadata; multiple attachments per note
Summary
The metadata types were renamed and reshuffled to support multiple attachments per note (up to NoteAttachments::MAX_COUNT = 4):
| 0.14 |
0.15 |
NoteMetadata (sender/type/tag + single attachment) |
PartialNoteMetadata (sender/type/tag only) |
NoteMetadataHeader (the on‑stack metadata word) |
NoteMetadata (metadata word + attachment headers + attachments commitment) |
NoteAttachment (single, with_attachment) |
NoteAttachments (collection, with_attachments) |
Note::new(assets, metadata, recipient) now takes a PartialNoteMetadata; a new Note::with_attachments(assets, partial_metadata, recipient, attachments) carries the attachments. The single NoteMetadata::with_attachment / .attachment() API is gone.
Affected Code
// 0.15 — new API:
use miden_protocol::note::{Note, PartialNoteMetadata, NoteType, NoteAttachments};
let partial = PartialNoteMetadata::new(sender, NoteType::Public).with_tag(tag);
let attachments = NoteAttachments::new(vec![attachment_a, attachment_b])?; // 0..=4
let note = Note::with_attachments(assets, partial, recipient, attachments);
// (use Note::new(assets, partial, recipient) for a note with no attachments)
let found = note.attachments().find(scheme);
Migration Steps
- Search/replace the type used to construct a note from
NoteMetadata to PartialNoteMetadata.
- Replace
NoteMetadataHeader with NoteMetadata (the word‑shaped metadata is NoteMetadata::to_metadata_word()).
- Replace
.with_attachment(a) with a NoteAttachments::new(vec![...]) collection and Note::with_attachments(...).
- Replace single
metadata.attachment() reads with note.attachments().get(i) / .find(scheme) / note.has_attachments().
Attachment MASM: set_* → add_*; get_metadata drops attachments
Summary
Because a note can now hold several attachments, the kernel/protocol attachment procedures were rewritten:
output_note::set_attachment → add_attachment (and set_word_attachment → add_word_attachment, set_array_attachment → add_attachment_from_memory). They append instead of overwriting, and the stack signature dropped the attachment_kind field.
note::extract_attachment_info_from_metadata → metadata_into_attachment_schemes, returning the four attachment scheme markers.
- All
get_metadata procedures (active_note, input_note, output_note) no longer return attachments — they return just the single METADATA word.
Affected Code
# 0.15 — new API:
# Operand Stack: [attachment_scheme, ATTACHMENT_COMMITMENT, note_idx]
exec.output_note::add_attachment
exec.active_note::get_metadata # => [METADATA]
exec.note::metadata_into_attachment_schemes
# => [attachment_0_scheme, attachment_1_scheme, attachment_2_scheme, attachment_3_scheme]
Migration Steps
- Rename
set_attachment / set_word_attachment / set_array_attachment to add_attachment / add_word_attachment / add_attachment_from_memory, and drop the attachment_kind operand.
- Replace
extract_attachment_info_from_metadata with metadata_into_attachment_schemes.
- Audit every
get_metadata consumer: it now leaves only [METADATA] — remove the extra cleanup that handled the attachment word.
NoteType encoding 2‑bit → 1‑bit; Private is the default
Summary
NoteType dropped from a 2‑bit encoding to 1 bit. The numeric encodings flipped and NoteType::Private is now the #[default]. Anything that serialized a note type, packed it into a tag, or relied on the old Public = 0b01 / Private = 0b10 values changes.
|
0.14 |
0.15 |
Public |
0b01 |
1 |
Private |
0b10 |
0 (default) |
Migration Steps
- Drop any hard‑coded
0b01 / 0b10 note‑type bit literals; use the NoteType variants.
- Re‑derive note tags / metadata words that packed the old 2‑bit type (
SwapNote::build_tag, for example, now uses the 1‑bit encoding — script‑root bits 14 → 15).
- If you relied on a particular default, note it is now
Private.
Nullifier now includes metadata and attachments commitment
Summary
The note nullifier hash now folds in the note's metadata word and attachments commitment in addition to the serial number, script root, storage commitment, and asset commitment. Nullifier::new gained two parameters and the From<&NoteDetails> conversion was replaced by Nullifier::from_details_and_metadata, because a nullifier can no longer be computed from details alone.
Affected Code
// 0.15 — new API:
let nf = Nullifier::new(
script_root, storage_commitment, asset_commitment, serial_num,
metadata.to_metadata_word(), // new
metadata.attachments_commitment(), // new
);
let nf2 = Nullifier::from_details_and_metadata(¬e_details, &metadata);
Migration Steps
- Thread the metadata word and attachments commitment into every
Nullifier::new call.
- Replace
Nullifier::from(&details) / (&details).into() with Nullifier::from_details_and_metadata(&details, &metadata).
- Recompute and re‑persist nullifiers — 0.14 nullifiers will not match.
MAX_ASSETS_PER_NOTE 255 → 64; NOTE_MEM_SIZE 3072 → 1024
Summary
The per‑note asset cap was reduced from 255 to 64, and the kernel note memory region (NOTE_MEM_SIZE) shrank from 3072 to 1024. Notes carrying more than 64 assets now fail to build, and MASM that hard‑codes note‑memory offsets against the old 3072‑word region must be reworked.
Migration Steps
- Cap note asset lists at 64; split larger payloads across multiple notes.
- Audit any MASM that indexes into the note memory region against the new
NOTE_MEM_SIZE = 1024.
SwapNote/MintNote storage trimmed; PSWAP added
Summary
Unused fields were removed from standard note storage: payback_attachment from SwapNoteStorage and attachment from MintNoteStorage. A new PSWAP (partial swap) note and PswapNote API (with a PswapAttachment scheme and payback_note / remainder_note discovery helpers) supports partial‑fill asset exchange with remainder re‑creation.
Migration Steps
- Stop reading/writing the removed
payback_attachment / attachment storage fields on swap/mint notes.
- Use
PswapNote for partial‑fill swaps; reconstruct private paybacks via PswapNote::payback_note / remainder_note.
Asset, Vault and Faucet Changes
FungibleAsset::amount() / get_balance() return AssetAmount
Summary
A new validated AssetAmount newtype wraps fungible amounts. FungibleAsset::amount() now returns AssetAmount (was u64), and AssetVault::get_balance() returns Result<AssetAmount, AssetError> (was Result<u64, AssetVaultError>) and takes an AssetVaultKey instead of an AccountId. The vault key carries the asset's AssetComposition, so balance lookups are explicit about fungible‑vs‑non‑fungible.
Affected Code
// 0.15 — new API:
use miden_protocol::asset::{AssetAmount, AssetCallbackFlag, AssetVaultKey};
let amt: AssetAmount = fungible_asset.amount();
let raw: u64 = amt.as_u64(); // or: u64::from(amt)
let key = AssetVaultKey::new_fungible(faucet_id, AssetCallbackFlag::Disabled);
let bal: AssetAmount = vault.get_balance(key)?;
AssetAmount implements From<u8/u16/u32> and TryFrom<u64> (validating against the max fungible amount), plus Add/Sub and Display.
Migration Steps
- Wrap
u64 amounts you pass into faucet/asset constructors in AssetAmount (AssetAmount::from(n) for small ints, AssetAmount::try_from(n) for u64).
- Unwrap
AssetAmount back to u64 with .as_u64() / u64::from(_) where a raw integer is needed.
- Replace
vault.get_balance(faucet_id) with vault.get_balance(AssetVaultKey::new_fungible(faucet_id, callback_flag)).
- Update error handling from
AssetVaultError to AssetError on get_balance.
FungibleFaucet replaces BasicFungibleFaucet + NetworkFungibleFaucet
Summary
The separate BasicFungibleFaucet and NetworkFungibleFaucet components were merged into a single FungibleFaucet component, and its old FungibleFaucetBuilder was replaced with a bon‑generated builder (FungibleFaucet::builder()). The constructor accepts a structured TokenName plus optional token‑metadata fields and an AssetAmount max_supply. A companion FungibleTokenMetadata component exposes the metadata via MASM getters. For the end‑to‑end client construction recipe (with TokenPolicyManager), see (Rust) FungibleFaucet builder + TokenPolicyManager.
Affected Code
// 0.15 — new API:
use miden_protocol::asset::{AssetAmount, TokenSymbol};
use miden_standards::account::faucets::{FungibleFaucet, TokenName};
let faucet = FungibleFaucet::builder()
.name(TokenName::new("My Token")?)
.symbol(TokenSymbol::new("MTK")?)
.decimals(8)
.max_supply(AssetAmount::from(1_000_000u32))
.build()?;
Migration Steps
- Replace
BasicFungibleFaucet / NetworkFungibleFaucet imports with FungibleFaucet.
- Switch construction to
FungibleFaucet::builder() with the required setters name, symbol, decimals, max_supply.
- Convert
max_supply from Felt to AssetAmount.
AssetComposition and the AssetVaultKey composition byte
Summary
A new AssetComposition enum (None, Fungible, Custom) discriminates assets, and the asset vault key's metadata byte now encodes the composition (plus the asset‑callback flag). AssetVaultKey::new(asset_id, faucet_id, composition, callback_flag) is the general constructor; AssetVaultKey::new_fungible(faucet_id, callback_flag) is the fungible shortcut. (Custom composition is reserved and currently rejected.)
Migration Steps
- Where you constructed a raw vault key word, use
AssetVaultKey::new_fungible / AssetVaultKey::new.
- Branch on
AssetComposition (via key.composition()) instead of inspecting raw bits.
Transaction Changes
fee_faucet_id replaces native_asset_id on FeeParameters
Summary
FeeParameters::native_asset_id (field, getter, and new parameter) was renamed to fee_faucet_id. Because account IDs no longer encode faucet‑ness, FeeParameters::new no longer validates that the ID is a fungible faucet and is now infallible (returns Self, not Result).
Migration Steps
- Rename
native_asset_id → fee_faucet_id at the constructor, field, and getter.
- Drop the
? / FeeError handling on FeeParameters::new.
TransactionScript::root() returns TransactionScriptRoot
Summary
(v0.15.2) TransactionScript::root() now returns a typed TransactionScriptRoot newtype instead of a raw Word (convert with .into()). A new TransactionScript::from_package(&Package) builds a script from a compiled miden-mast-package package, and the new tx::get_tx_script_root kernel proc returns the executed tx‑script root (empty word if none).
Migration Steps
- Update the binding type of
tx_script.root() to TransactionScriptRoot.
- Insert
.into() / Word::from(root) where a Word is required.
ProvenBatch::new → new_unchecked; NoteConsumptionInfo fields private
Summary
Batch/transaction housekeeping changes that affect anyone constructing these types by hand:
ProvenBatch::new was renamed ProvenBatch::new_unchecked to signal it skips validation.
NoteConsumptionInfo (and related types) gained cycle counts and had their fields made private; use the accessor methods successful() / failed().
Migration Steps
- Rename
ProvenBatch::new → ProvenBatch::new_unchecked.
- Replace direct field access on
NoteConsumptionInfo with the accessor methods.
Client Changes
(Rust) NodeRpcClient GetAccount surface reshaped
Summary
The account‑fetching surface on the NodeRpcClient trait was rebuilt around the node's /GetAccount endpoint:
get_account_proof(account_id, storage_requirements, account_state, known_account_code, known_vault_commitment) was replaced by get_account(account_id, request: GetAccountRequest), where GetAccountRequest bundles the previous positional args behind a builder.
get_account_details no longer returns a FetchedAccount enum — it returns Result<Option<Account>, RpcError> (None for accounts without public state), fetching all of a public account's storage maps and vault in a single round‑trip. It no longer returns anything for private accounts; use get_account for a private account's commitment.
- New default helpers
resolve_oversize_vault / resolve_oversize_storage_maps fill in vault/map entries the node flagged as oversize.
GetAccountRequest, StorageMapFetch, VaultFetch, and AccountStateAt live under miden_client::rpc::domain.
Affected Code
// 0.15 — new API:
use miden_client::rpc::domain::account::{GetAccountRequest, StorageMapFetch, VaultFetch};
let (block, proof) = rpc_api
.get_account(account_id, GetAccountRequest::new()
.with_storage(StorageMapFetch::All)
.with_vault(VaultFetch::Always)
.with_known_code(Some(known_code)))
.await?;
let account: Option<Account> = rpc_api.get_account_details(account_id).await?;
Migration Steps
- Replace
get_account_proof(...) calls with get_account(account_id, GetAccountRequest::new()....); move each positional arg onto the corresponding builder method.
- Replace
match FetchedAccount { Public | Private } on get_account_details with Option<Account> handling; route private‑account commitment lookups through get_account.
- The default
get_account_details now calls resolve_oversize_vault/resolve_oversize_storage_maps for you.
(Rust) NodeRpcClient: SyncTarget, check_nullifiers removed, required block_to
Summary
Several NodeRpcClient methods changed to match the 0.15 RPC definitions:
sync_chain_mmr's block_to: Option<BlockNumber> became upper_bound: SyncTarget. Use SyncTarget::CommittedChainTip for the old None behavior, SyncTarget::ProvenChainTip for the latest proven block, or SyncTarget::BlockNumber(n) for a specific height.
check_nullifiers (and RpcEndpoint::CheckNullifiers, EndpointError::CheckNullifiers, CheckNullifiersError) were removed. Use sync_nullifiers to retrieve nullifier updates.
sync_nullifiers, sync_notes, sync_notes_with_details, sync_storage_maps, and sync_account_vault lost their Option<BlockNumber> upper bound in favor of a required block_to: BlockNumber.
get_block_by_number gained an include_proof: bool parameter.
submit_proven_batch is a new required trait method.
SyncTarget lives at miden_client::rpc::domain::sync::SyncTarget.
Affected Code
// 0.15 — new API:
use miden_client::rpc::domain::sync::SyncTarget;
let mmr = rpc_api.sync_chain_mmr(block_from, SyncTarget::CommittedChainTip).await?;
let updates = rpc_api.sync_nullifiers(&prefixes, block_from, chain_tip).await?; // check_nullifiers is gone
let block = rpc_api.get_block_by_number(block_num, /* include_proof */ false).await?;
Migration Steps
- Replace
sync_chain_mmr(_, None) with sync_chain_mmr(_, SyncTarget::CommittedChainTip); map any explicit Some(n) to SyncTarget::BlockNumber(n).
- Replace
check_nullifiers with sync_nullifiers and adapt to Vec<NullifierUpdate> (drop the SmtProof path).
- Pass an explicit
block_to (e.g. the client's current sync height / chain tip) to sync_nullifiers, sync_notes, sync_storage_maps, sync_account_vault.
- Add
include_proof to get_block_by_number calls (false unless you need the block proof).
- If you implement
NodeRpcClient yourself, add submit_proven_batch.
(Rust) Note‑import APIs return Vec<NoteDetailsCommitment>
Summary
Client::import_notes, Client::sync_note_transport, and the SyncSummary::new_private_notes field now yield Vec<NoteDetailsCommitment> instead of Vec<NoteId>. A metadata‑less import has no NoteId yet, so the client identifies such notes by their metadata‑independent details commitment. Resolve a commitment back to a record with Client::get_input_notes(NoteFilter::DetailsCommitments(vec![..])).
Affected Code
// 0.15 — new API:
use miden_client::store::NoteFilter;
use miden_protocol::note::NoteDetailsCommitment;
let imported: Vec<NoteDetailsCommitment> = client.import_notes(¬e_files).await?;
let private: Vec<NoteDetailsCommitment> = sync_summary.new_private_notes;
// Resolve commitments to the stored records (works even before metadata is known):
let records = client.get_input_notes(NoteFilter::DetailsCommitments(imported)).await?;
Migration Steps
- Change the bound type of
import_notes / sync_note_transport results and the SyncSummary::new_private_notes field from NoteId to NoteDetailsCommitment.
- Where you used a returned
NoteId to look a note up, switch to NoteFilter::DetailsCommitments(..) against get_input_notes.
(Rust) FungibleFaucet builder + TokenPolicyManager construction
Summary
The fungible‑faucet construction story was redesigned end to end. The client previously re‑exported BasicFungibleFaucet plus the MintAuthControlled / MintOwnerControlled / BurnAuthControlled / BurnOwnerControlled policy components. In 0.15:
- The faucet component is
FungibleFaucet, built via FungibleFaucet::builder().
- Mint/burn policy is configured by installing a single
TokenPolicyManager (.with_mint_policy(MintPolicyConfig, PolicyRegistration).with_burn_policy(...)), with standalone MintAllowAll / MintOwnerOnly / BurnAllowAll / BurnOwnerOnly policy components. The old Mint*Controlled / Burn*Controlled types were removed.
These are re‑exported from miden_client::account.
Affected Code
// 0.15 — new API:
use miden_client::account::{
AccountBuilder, AccountType, FungibleFaucet, TokenName, TokenPolicyManager,
MintPolicyConfig, BurnPolicyConfig, PolicyRegistration,
};
use miden_protocol::asset::AssetAmount;
let faucet = FungibleFaucet::builder()
.name(TokenName::new(&symbol.to_string())?).symbol(symbol).decimals(10)
.max_supply(AssetAmount::new(max_supply)?).build()?;
let policy_manager = TokenPolicyManager::new()
.with_mint_policy(MintPolicyConfig::AllowAll, PolicyRegistration::Active)?
.with_burn_policy(BurnPolicyConfig::AllowAll, PolicyRegistration::Active)?;
let account = AccountBuilder::new(init_seed)
.account_type(account_visibility) // AccountType::Public / ::Private
.with_auth_component(auth_component)
.with_component(faucet)
.with_components(policy_manager)
.build_with_schema_commitment()?;
Migration Steps
- Replace
BasicFungibleFaucet::new(symbol, decimals, max_supply) with FungibleFaucet::builder()....build(), wrapping max_supply in AssetAmount::new(..).
- Replace the standalone mint‑policy component with a
TokenPolicyManager configured via with_mint_policy / with_burn_policy, installed with .with_components(policy_manager).
- Drop
Mint*Controlled / Burn*Controlled imports.
(Rust) InputNoteRecord::new takes NoteAttachments; store attachments column
Summary
InputNoteRecord::new gained a second positional parameter, attachments: NoteAttachments, so input notes persist their attachment content. The SQLite store's input_notes table gained an attachments column. NoteAttachments is re‑exported from miden_client::note.
Migration Steps
- Thread a
NoteAttachments (or NoteAttachments::default()) into every InputNoteRecord::new call as the second argument.
- If you use a custom
Store, add an attachments column to your input‑notes table and persist/load it.
(Rust) sync_notes / sync_transactions return updates directly
Summary
sync_notes and sync_transactions now return the fetched updates directly. The wrapper structs NoteSyncInfo and TransactionsInfo were removed: sync_notes returns Vec<NoteSyncBlock> and sync_transactions returns Vec<TransactionRecord>.
Migration Steps
- Drop the
.blocks / .transactions field access — the methods return the collections directly.
- Source the chain tip separately (e.g.
get_block_header_by_number(None, false)) where you previously read it off NoteSyncInfo.
(Rust) get_note_script_by_root returns Option<NoteScript>
Summary
NodeRpcClient::get_note_script_by_root no longer errors when the node has no script registered for the requested root — it returns Ok(None). Implementations must still verify a returned script's root matches the request.
Migration Steps
- Handle the
Option — None means "no script for this root", which previously surfaced as an error.
(Rust) miden_client::note re‑exports realigned
Summary
The protocol split attachment data off NoteMetadata, and the client's note re‑exports follow:
- Removed:
NoteAttachmentKind, NoteMetadataHeader.
- Added:
NoteAttachmentHeader, NoteAttachments, PartialNoteMetadata.
NoteScript::root() now returns NoteScriptRoot (re‑exported from miden_client::note) instead of Word.
Migration Steps
- Replace
NoteAttachmentKind / NoteMetadataHeader imports with NoteAttachmentHeader / NoteAttachments / PartialNoteMetadata as needed.
- Where
note_script.root() was used as a Word, insert a .into() / Word::from(..) conversion.
(Rust) CommittedNoteMetadata removed
Summary
The CommittedNoteMetadata enum (with Full(NoteMetadata) and a header‑only Header { sender, note_type, tag, attachment_kind } variant) was removed. Sync responses now always carry full metadata, so CommittedNote::metadata() returns &NoteMetadata directly — no Option, no header‑only case.
Migration Steps
- Delete the
CommittedNoteMetadata match — read NoteMetadata directly off the committed note.
(Rust) build_wallet_id signature
Summary
build_wallet_id dropped its trailing is_mutable: bool (code mutability isn't encoded in the account ID) and its storage_mode: AccountStorageMode parameter was replaced by account_visibility: AccountType.
Migration Steps
- Drop the
is_mutable argument.
- Replace the
AccountStorageMode argument with the corresponding AccountType visibility.
(Rust) compile_note_script expects a @note_script library
Summary
client.code_builder().compile_note_script(src) now expects a MASM library with a single procedure annotated @note_script instead of a bare begin … end program (the underlying assembly switched from assemble_program to assemble_library).
Migration Steps
- Wrap each note‑script body in
@note_script + pub proc main … end; remove the begin … end framing.
(Web) AccountStorageMode.network() removed
Summary
The 0.15 chain has no separate network‑account flag. AccountStorageMode.network() was removed, and the StorageMode string union dropped "network" (it is now "public" | "private"). Anywhere you constructed a network‑mode account — AccountStorageMode.network(), StorageMode.Network, or accounts.create({ storage: "network" }) — must switch to "public" or "private". AccountStorageMode.tryFromStr("network") now rejects.
Affected Code
// 0.15 — new API:
const mode = AccountStorageMode.public(); // or .private()
await client.accounts.create({ storage: "public" }); // "public" | "private"
Migration Steps
- Replace every
AccountStorageMode.network() with .public() or .private().
- Replace
StorageMode.Network / the "network" string with "public" or "private".
- Audit
accounts.create({ storage }) and any tryFromStr call sites for "network".
(Web) AccountType narrowed; faucet checks move to Account
Summary
The on‑chain AccountType no longer encodes faucet‑vs‑regular or updatable‑vs‑immutable. The WASM‑exported AccountType enum narrowed from { FungibleFaucet, NonFungibleFaucet, RegularAccountImmutableCode, RegularAccountUpdatableCode } to { Private, Public }. As a result:
AccountId.isFaucet(), AccountId.isNetwork(), and AccountId.isRegularAccount() were removed (only isPublic() / isPrivate() remain).
- Faucet / regular detection now lives on
Account: Account.isFaucet() / Account.isRegularAccount().
Account.isNetwork() and Account.isUpdatable() were removed outright.
This is the low‑level WASM AccountType enum. The high‑level resource‑API AccountType constant used by accounts.create({ type: "FungibleFaucet" | ... }) is unchanged.
Affected Code
// 0.15 — new API:
if (account.isFaucet()) {} // moved onto Account
if (account.isRegularAccount()) {}
if (account.id().isPublic()) {} // isPublic()/isPrivate() still on both
// account.isNetwork() / account.isUpdatable() have no replacement — drop them.
Migration Steps
- Move
accountId.isFaucet() / .isRegularAccount() onto the materialised Account. You need the full Account, not just its AccountId.
- Remove
accountId.isNetwork(), account.isNetwork(), and account.isUpdatable() — they have no replacement.
- Replace any
switch on the WASM AccountType enum's faucet/regular variants with the Account predicates; the enum only carries Private / Public now.
(React) useCreateWallet / useCreateFaucet drop "network"
Summary
Matching the StorageMode narrowing, the React useCreateWallet({ storageMode }) and useCreateFaucet({ storageMode }) hooks no longer accept "network". The storageMode option type is now "private" | "public".
Migration Steps
- Replace
storageMode: "network" with "public" or "private" in every createWallet / createFaucet call.
- Update any local
StorageMode‑typed state feeding these hooks.
(Web) NoteAttachment reshaped to word‑vector content
Summary
Attachments are now always word‑vector‑shaped. The NoteAttachmentKind { Word, Array } dispatch and the per‑variant accessors/constructors NoteAttachment.newWord / .newArray / .asWord / .asArray, the attachmentKind getter, and the NoteMetadata.attachment() getter were all removed. Build attachments with NoteAttachment.fromWord(scheme, word) or NoteAttachment.fromWords(scheme, words), and read them back with NoteAttachment.toWords(). The attachment words now live on the note record (InputNoteRecord.attachments()), not on NoteMetadata. NoteAttachmentScheme is now u16‑backed: its constructor throws if the value exceeds the u16 range, and NoteAttachmentScheme.asU32() was removed.
Affected Code
// 0.15 — new API:
const att = NoteAttachment.fromWord(scheme, word); // single word
const attMulti = NoteAttachment.fromWords(scheme, [w0, w1]);
const words = att.toWords(); // Word[] — inverse of fromWord/fromWords
// NoteAttachmentKind, asWord, asArray, attachmentKind, scheme.asU32(),
// and NoteMetadata.attachment() no longer exist.
Migration Steps
- Replace
NoteAttachment.newWord(scheme, word) with NoteAttachment.fromWord(scheme, word) and newArray(scheme, felts) with fromWords(scheme, words).
- Replace
att.asWord() / att.asArray() / att.attachmentKind() reads with att.toWords() and decode the Word[] yourself.
- Replace
noteMetadata.attachment() with inputNoteRecord.attachments().
- Drop
NoteAttachmentScheme.asU32(); the scheme is u16‑backed and its constructor validates the range.
(Web) InputNoteRecord.id() returns NoteId | undefined; IDB re‑keyed
Summary
A partial (metadata‑less) input note has no note ID yet, so InputNoteRecord.id() now returns NoteId | undefined. A new InputNoteRecord.attachments() getter returns the note's attachments (NoteAttachment[]; empty when none). On the storage side, miden-idxdb-store now keys input notes by their details commitment instead of their note ID (matching the SQLite store): the InputNotes table's primary index changed from noteId to detailsCommitment, so a partial note later completed with its note ID updates the same row instead of creating a duplicate.
Affected Code
// 0.15 — new API:
const id = record.id(); // NoteId | undefined
if (id) { const idStr = id.toString(); }
const attachments = record.attachments(); // NoteAttachment[]
Migration Steps
- Guard every
record.id() use against undefined (partial notes have no ID).
- Replace
noteMetadata.attachment() reads with record.attachments().
- If you query IndexedDB directly, switch input‑note lookups from
noteId to detailsCommitment. Re‑sync existing 0.14 stores under 0.15.
(Web) importNoteFile / notes.import resolve to a hex string
Summary
WebClient.importNoteFile(...) (and notes.import(...)) now resolves to a hex string instead of a NoteId object. Upstream Client::import_notes returns details‑commitments rather than note IDs, so the web method returns the note‑id hex for a metadata‑bearing file, or the details‑commitment hex for a details‑only file. Pass it to NoteId.fromHex(...) if you need a NoteId instance.
Migration Steps
- Treat the return value as a hex
string; drop any .toString() / NoteId method calls on it.
- If you need a
NoteId, wrap the result with NoteId.fromHex(hex).
- Be aware the returned hex may be a details‑commitment (not a note‑id) for details‑only files.
(Web) Felt and Word constructors throw on overflow
Summary
new Felt(value) and new Word(values) now throw when an input is at or beyond the field modulus, instead of silently constructing an out‑of‑range value. Each input must be a canonical field element. Wrap construction from untrusted input in try/catch.
Migration Steps
- Reduce or validate values to the field modulus before constructing
Felt / Word, or catch the thrown error.
- Audit any code that fed raw
u64 / bigint values (e.g. from external systems) into these constructors.
(Web) syncNotes blockTo required; chainTip() removed
Summary
RpcClient.syncNotes(blockFrom, blockTo, noteTags)'s blockTo parameter is now required (was optional). The upstream RPC no longer returns the chain tip, so NoteSyncInfo.chainTip() was removed — use client.syncState() to learn the chain tip. NoteSyncInfo.blockTo() still exists.
Migration Steps
- Always pass an explicit
blockTo to syncNotes.
- Replace
NoteSyncInfo.chainTip() reads with client.syncState().
(Web) getNoteScriptByRoot returns NoteScript | undefined
Summary
RpcClient.getNoteScriptByRoot(scriptRoot) now resolves to NoteScript | undefined instead of throwing when the node has no script for the given root.
Migration Steps
- Replace the
try/catch "not found" path with an === undefined check.
- Keep a
try/catch only for genuine transport/RPC failures.
(Web) FetchedNote exposes noteId/metadata; header removed
Summary
FetchedNote (returned by RpcClient.getNotesById(...)) now stores noteId and metadata directly and exposes them as getters. The synthetic header getter was removed — a NoteHeader can no longer be reconstructed from header‑shaped fields alone for private notes. A new attachments getter exposes the note's attachments (populated for both public and private fetched notes). The JS constructor signature is unchanged.
Migration Steps
- Replace
fetched.header.id() with fetched.noteId and fetched.header.metadata() with fetched.metadata.
- Remove any code that reconstructs a
NoteHeader from a FetchedNote.
- Read attachments via
fetched.attachments (works for private notes too).
(Web) newFaucet rebuilt on FungibleFaucet + TokenPolicyManager
Summary
WebClient.newFaucet(...) (and accounts.create({ type: "FungibleFaucet", ... })) now assembles a faucet from the 0.15 FungibleFaucet component plus a TokenPolicyManager that registers AllowAll mint and burn policies (transfer policies are intentionally omitted). non_fungible = true still fails fast. The JS signature newFaucet(storageMode, nonFungible, tokenName, tokenSymbol, decimals, maxSupply, authScheme) is unchanged. Separately, BasicFungibleFaucetComponent.fromAccount(account) now reads the new 0.15 metadata slot, so faucets minted by prior SDK versions can no longer be introspected through it.
Migration Steps
- No call‑site change for creating faucets — the JS signature is the same.
- Re‑create faucets under
0.15 if you need BasicFungibleFaucetComponent.fromAccount(...) to introspect them.
- Keep passing
non_fungible = false; true still throws "Non‑fungible faucets are not supported yet".
(Web) idxdb-store: committedNoteIds → committedNoteTagSources
Summary
On miden-idxdb-store's JsStateSyncUpdate, the committedNoteIds field was renamed to committedNoteTagSources and now carries details‑commitment hex rather than note‑id hex. Anyone driving the IndexedDB store's sync apply path directly (or stubbing JsStateSyncUpdate in tests) must rename the field and feed details‑commitment hex.
Migration Steps
- Rename
committedNoteIds → committedNoteTagSources everywhere you construct or read JsStateSyncUpdate.
- Supply details‑commitment hex (not note‑id hex) for those entries.
(Web) getAccountProof rewired; WASM ClientError gains a code
Summary
Two smaller web changes:
RpcClient.getAccountProof(accountId, storageRequirements?, blockNum?, knownVaultCommitment?) keeps the same JS signature but is now wired onto the 0.15 get_account(GetAccountRequest...) upstream API. No JS call‑site change is required; it needs a 0.15 node.
- WASM client errors now carry a stable, machine‑readable
code property on the thrown JS error — currently ACCOUNT_NOT_FOUND_ON_CHAIN and ACCOUNT_ALREADY_TRACKED. This is additive (message‑string matching still works) but branching on code is the recommended pattern, and the worker shim forwards it.
Migration Steps
- Point
getAccountProof callers at a 0.15 node (the underlying RPC was renamed/reshaped).
- Where you match on
ClientError message text for "account not found on chain" or "already tracked", switch to error.code. Keep a fallback for errors without a code (only those two variants are mapped today).
(Web) WasmWebClient.proveTransactionWithProver → proveTransaction
Summary
On the raw WASM client (WasmWebClient — the low-level surface behind MidenClient), proveTransactionWithProver(txResult, prover) was renamed proveTransaction(txResult, prover?), with the prover now an optional second parameter (omitting it uses the local prover). The high-level MidenClient resource API is unaffected, but anything driving the raw client directly — worker shims, offscreen/prover documents, _withInnerWebClient callbacks — must rename the call.
Affected Code
// 0.15 — new API (raw WasmWebClient surface):
const proven = await wasmWebClient.proveTransaction(txResult, prover);
// prover may be omitted to prove locally:
const proven2 = await wasmWebClient.proveTransaction(txResult);
Migration Steps
- Rename
proveTransactionWithProver(txResult, prover) to proveTransaction(txResult, prover).
- Audit any code that already called a 0.14
proveTransaction() without a prover — the 0.15 signature is compatible (prover optional), no change needed.
(Web) storeIdentifier() is now async
Summary
MidenClient.storeIdentifier() now returns a Promise<string> instead of a plain string (the identifier is read from the client behind the worker/async boundary). Call sites that fed the result into exportStore / importStore or string operations must await it.
Migration Steps
await client.storeIdentifier() everywhere; un-awaited uses surface as Promise<string>-vs-string type errors (or [object Promise] store names at runtime).
(Web) InputNoteRecord.nullifier() returns string | undefined
Summary
Because a 0.15 nullifier folds in the note's metadata (see Nullifier now includes metadata and attachments commitment), a partial (metadata-less) input note record has no computable nullifier — InputNoteRecord.nullifier() now returns string | undefined, pairing with id()'s NoteId | undefined. A record missing either is a partial note that sync has not yet completed.
Migration Steps
- Guard
record.nullifier() against undefined alongside the existing record.id() guard; treat records missing either as not-yet-consumable and skip them from listings.
(CLI) --account-type accepts only private / public
Summary
The -t / --account-type flag on new-account / new-wallet now takes only private or public (account visibility); the legacy values (fungible-faucet, non-fungible-faucet, regular-account-immutable-code, regular-account-updatable-code), the separate --mutable flag, and the standalone --storage-mode toggle were removed. Whether an account is a faucet is derived from its components — installing a FungibleFaucet component yields a fungible faucet (with an implicit TokenPolicyManager).
Affected Code
# 0.15 — new API:
+ miden-client new-wallet --account-type public
Migration Steps
- Replace
--account-type <regular/faucet variant> with --account-type private|public.
- Drop
--mutable and --storage-mode; pick the faucet vs. wallet shape via the -p package/components.
(CLI) new-faucet requires a [fungible-faucet-metadata] block
Summary
The faucet init‑data file passed via -i now uses a typed [fungible-faucet-metadata] block (symbol, decimals, max_supply, optional name) instead of the old stringly‑typed ["miden::standards::fungible_faucets::metadata"] section. Faucet accounts created with the previous layout are no longer recognized by account list / account show.
# 0.15 — new API: — init-data TOML
+ [fungible-faucet-metadata]
+ symbol = "BTC"
+ decimals = 10
+ max_supply = 10000000
Migration Steps
- Rename the section to
[fungible-faucet-metadata] and switch to typed scalars (decimals/max_supply are integers, not quoted strings).
- Re‑create existing faucets — the previous component layout is no longer recognized.
(CLI) address add takes bech32; new address encode
Summary
address add now takes <ACCOUNT_ID> <BECH32_ADDRESS> (a pre‑encoded address) instead of <ACCOUNT_ID> <INTERFACE> [TAG_LEN]. A new address encode <ACCOUNT_ID> <INTERFACE> [TAG_LEN] subcommand produces the bech32 string from the individual fields.
Migration Steps
- Build the bech32 address first with
address encode <ACCOUNT_ID> <INTERFACE> [TAG_LEN].
- Pass that string to
address add <ACCOUNT_ID> <BECH32_ADDRESS>.
General MASM Changes
Kernel/protocol proc renames: build_recipient*, extract_*_from_metadata
Summary
Several core MASM procedures in miden::protocol::note were renamed for consistency. These are used by every custom note script that computes a recipient or reads metadata.
| 0.14 |
0.15 |
note::build_recipient_hash |
note::compute_recipient |
note::build_recipient |
note::compute_and_store_recipient |
note::extract_sender_from_metadata |
note::metadata_into_sender |
note::extract_attachment_info_from_metadata |
note::metadata_into_attachment_schemes |
New convenience helpers were also added: note::metadata_into_note_type and note::metadata_into_tag (use the latter instead of slicing the header manually).
Migration Steps
- Search/replace the four procedure names per the table.
- Where you manually extracted the tag from the metadata header, switch to
metadata_into_tag.
Redundant kernel outputs removed
Summary
Six kernel procedures stopped returning values that were identical to (or trivially recoverable from) their inputs. Custom MASM that consumed the now‑removed outputs must drop the stale cleanup.
| Procedure |
0.14 output |
0.15 output |
active_note::get_assets (also input_note/output_note) |
[num_assets, dest_ptr] |
[num_assets] |
active_note::get_storage |
[NOTE_STORAGE_COMMITMENT, num_storage_items, dest_ptr] |
[num_storage_items] |
faucet::mint |
[NEW_ASSET_VALUE] |
[] |
note::write_assets_to_memory |
echoed inputs |
trimmed |
Migration Steps
- Remove the
drop / movup+drop that cleared the echoed dest_ptr after get_assets / get_storage.
- After
get_storage, the NOTE_STORAGE_COMMITMENT word is no longer on the stack — delete the dropw that consumed it.
- After
faucet::mint, do not expect NEW_ASSET_VALUE.
adv_push.N immediate form removed; adv_pushw added
Summary
The immediate form of adv_push (adv_push.N) was removed. adv_push now always pops exactly one element from the advice stack. To push N elements, emit N consecutive adv_push instructions (or repeat.N adv_push end). A new adv_pushw instruction pushes a full word (4 elements). On the Rust AST side, Instruction::AdvPush(ImmU8) became Instruction::AdvPush plus a new Instruction::AdvPushW.
Migration Steps
- Replace every
adv_push.N with N adv_push instructions (or repeat.N adv_push end).
- Where you previously used
adv_push.4 to fetch a word, consider adv_pushw.
- If you build MASM AST programmatically, replace
Instruction::AdvPush(n) with N Instruction::AdvPush (or Instruction::AdvPushW).
Internal _impl precompile procedures removed
Summary
The internal _impl precompile helper procedures were removed from the core‑lib public surface: ecdsa_k256_keccak::verify_prehash_impl, eddsa_ed25519::verify_prehash_impl, keccak256::hash_bytes_impl, and sha512::hash_bytes_impl. The public wrappers (verify, verify_prehash, hash_bytes, …) are unchanged and remain the supported entry points.
Migration Steps
- If you called any
*_impl precompile helper directly by fully‑qualified path, switch to the corresponding public wrapper.
VM and Assembler Changes
Sync‑first execution: BaseHost/SyncHost; execute returns ExecutionOutput
Summary
Execution and proving became sync‑first with runtime‑free async compatibility. The single Host trait from 0.22 is split into three: BaseHost (shared source/label resolution + event‑name lookup), SyncHost: BaseHost (synchronous get_mast_forest / on_event), and Host: BaseHost (the async variant). A blanket impl makes every SyncHost automatically a Host. The sync entry points (execute_sync, prove_sync, FastProcessor::execute_sync/execute_mut_sync) require SyncHost. Both execute() and execute_sync() now return ExecutionOutput instead of ExecutionTrace — trace building is explicit via execute_trace_inputs*() + trace::build_trace(). The deprecated execute_sync_mut() / execute_for_trace*() aliases and the unbound TraceBuildInputs::new() / from_program() constructors were removed.
Affected Code
// After (0.23): implement BaseHost + SyncHost; you get Host for free via the blanket impl.
// (Before: a single async `impl Host for MyHost` with async get_mast_forest / on_event.)
use miden_processor::{BaseHost, SyncHost, AdviceMutation, host::handlers::EventError, ProcessorState};
impl BaseHost for MyHost {
fn get_label_and_source_file(&self, location: &Location) -> (SourceSpan, Option<Arc<SourceFile>>) { /* ... */ }
}
impl SyncHost for MyHost {
fn get_mast_forest(&self, d: &Word) -> Option<Arc<MastForest>> { /* ... */ }
fn on_event(&mut self, p: &ProcessorState<'_>) -> Result<Vec<AdviceMutation>, EventError> { /* ... */ }
}
// Calling execute now returns ExecutionOutput (exposes `stack`, `advice`, `memory`):
let output: ExecutionOutput = miden_processor::execute_sync(&program, stack_inputs, advice_inputs, &mut host, options)?;
Migration Steps
- Split your
Host impl into a BaseHost impl (label/source resolution) plus a SyncHost impl with plain (non‑async) get_mast_forest / on_event.
- If you call the sync entry points, pass a
SyncHost.
- Replace destructuring of an
ExecutionTrace return with ExecutionOutput accessors; build a trace explicitly only when needed.
- Replace
FastProcessor::execute_sync_mut(...) with execute_mut_sync(...), and execute_for_trace* / TraceBuildInputs::new() with execute_trace_inputs_sync() / execute_trace_inputs().
prove_sync takes execution options separately
Summary
ProvingOptions no longer carries an ExecutionOptions. prove_sync / prove now take execution options and proving options as two separate parameters (and the sync path requires a SyncHost). The with_execution_options(...) / execution_options() accessors on ProvingOptions are gone. prove_from_trace_sync() now takes a TraceProvingInputs.
- let options = ProvingOptions::default().with_execution_options(exec_options);
- let (stack_outputs, proof) = prove_sync(&program, stack_inputs, advice_inputs, &mut host, options)?;
+ use miden_processor::ExecutionOptions;
+ let (stack_outputs, proof) = prove_sync(
+ &program, stack_inputs, advice_inputs, &mut host, // must be a SyncHost
+ ExecutionOptions::default(), ProvingOptions::default())?;
Migration Steps
- Stop calling
ProvingOptions::with_execution_options(...); pass ExecutionOptions as its own argument.
- Ensure the host you pass to
prove_sync implements SyncHost.
- If you drove
prove_from_trace_sync(), build a TraceProvingInputs from post‑execution trace inputs.
Live advice map bounded by total field elements
Summary
The live advice map is now bounded by total field‑element count during execution. Advice‑provider setup returns an error when the initial advice already exceeds the limit, and writes that would push the live map past the limit fail. AdviceMap gained a total_element_count() accessor.
Migration Steps
- If you seed very large advice maps up front, split the data or stream it in during execution.
- Handle the new setup‑time error from advice‑provider construction instead of assuming it always succeeds.
Stricter assembly resolution: structured errors replace panics
Summary
Several previously‑panicking or silently‑partial assembly paths now return structured errors: oversized modules are rejected at resolver construction, non‑procedure invoke targets are rejected, self‑recursive / rootless call graphs return typed cycle errors, and unresolved pub use <digest> -> <name> returns a normal assembly error. The linker also rejects non‑syscall references to exported kernel procedures and rejects empty kernel packages. Code that assembled cleanly under 0.22 continues to assemble; the change is that malformed inputs now surface as recoverable Report errors instead of panics.
Migration Steps
- If you wrapped assembly in panic‑catching logic for malformed inputs, replace it with normal
Result/Report error handling.
- Fix any MASM that referenced an exported kernel procedure via
exec/call instead of syscall — that is now a hard error.
Post‑last‑operation decorators deprecated
Summary
Operation‑indexed decorators placed after the last operation of a basic block are now rejected in both block assembly and serialized MAST forests. Decorators that should run after a block exits must use the after_exit slot instead. This only affects code that builds MastForests programmatically — ordinary MASM source is unaffected.
Migration Steps
- If you attach decorators programmatically, move any decorator targeting the post‑last‑op index to the block's
after_exit decorator list.
Project File Format
MAST wire format 0.0.2 → 0.0.3
Summary
The MAST forest serialization format was refactored around fixed‑layout full, stripped, and hashless sections, with stable node IDs and stricter validation of untrusted forests. The wire‑format version constant bumped from [0, 0, 2] to [0, 0, 3]. Serialized .masl / .masp / MastForest blobs produced under 0.22 will not deserialize under 0.23. Deserialization of serialized libraries and kernel libraries is now treated as untrusted by default, rejecting spoofed/inconsistent node digests rather than trusting the bytes.
Affected Code
// Any blob serialized under 0.22 (VERSION = [0, 0, 2]) fails to read under 0.23:
let forest = MastForest::read_from_bytes(&old_bytes)?; // Err: unexpected version
Migration Steps
- Re‑assemble every
.masl / .masp package and re‑serialize any cached MastForest blobs from source under 0.23.
- If you persisted MAST forests or packages to disk or a database, invalidate and regenerate them.
- If you deserialize forests from an untrusted source, expect stricter validation — malformed or spoofed‑digest forests now return an error instead of loading.
Need Help?
- Discord: Miden Discord — the
#dev-support channel.
- GitHub issues: file against the relevant repo —
miden-client, web-sdk, miden-base, or miden-vm.
- Changelogs: the per‑repo
CHANGELOG.md files carry the full list of changes, including non‑breaking features and fixes omitted from this guide.
Miden Testnet 0.15.0
This guide covers all breaking changes you need to migrate an application to Miden 0.15.0. Like the 0.14 guide, it is intentionally user‑facing: you do not need to know or care which internal crate (VM, protocol, client) a change came from. If you are:
this document is for you. It folds together the breaking changes from
miden-base(protocol0.14→0.15.3),miden-vm(0.22→0.23),miden-client(0.14→0.15), and the Web SDK (@miden-sdk/*0.14→0.15). Becausemiden-clientand the Web SDK still ship from unified‑in‑progressmain/nextbranches, this guide unions the breaking surface from both.At a Glance
Big themes in 0.15:
AccountTypeenum is gone;AccountStorageModeis renamedAccountType({ Private, Public }). Faucet/network‑ness now comes from an account's components; the ID version is renamed0→1.NoteId(recipient + assets) becomesNoteDetailsCommitment; the newNoteIdalso commits to metadata, and nullifiers now fold in metadata + the attachments commitment — none roundtrip with 0.14.NoteMetadata→PartialNoteMetadata,NoteMetadataHeader→NoteMetadata; attachments live on the note/record as aNoteAttachmentscollection (≤ 4).NoteTypeis now 1‑bit, defaultPrivate.BasicFungibleFaucet+NetworkFungibleFaucet→ oneFungibleFaucet(bonbuilder) +FungibleTokenMetadata+ aTokenPolicyManagerfor mint/burn policies. Amounts are a validatedAssetAmountnewtype.NoteScript::root()→NoteScriptRoot,TransactionScript::root()→TransactionScriptRoot,procedure_digest!→procedure_root!, plusAccountComponentName.0.0.2→0.0.3(old.masl/.maspwon't load), execution is sync‑first (BaseHost/SyncHost;execute→ExecutionOutput), andadv_push.Nwas removed.GetAccount.get_account_proof/get_account_detailsreshaped,check_nullifiersremoved (usesync_nullifiers), most sync methods now require an explicitblock_to."network"storage mode is gone, the WASMAccountTypenarrowed to{ Private, Public }, attachments are word‑vector‑shaped,Felt/Wordthrow on overflow, several methods returnundefined/stringinstead of throwing/objects, the raw client'sproveTransactionWithProveris renamedproveTransaction, andstoreIdentifier()went async.If you only skim a few sections, skim Account Changes, Note Changes, Asset, Vault and Faucet Changes, Hashing & Stack Conventions, and Client Changes.
Table of Contents
Imports and Dependencies
Summary
Every Miden crate moves up a minor: the protocol crates (
miden-protocol,miden-standards,miden-tx,miden-testing) go0.14→0.15.3, the VM crates (miden-assembly,miden-core,miden-core-lib,miden-processor,miden-prover) go0.22→0.23, andmiden-cryptogoes0.23→0.25.miden-clientandmiden-client-sqlite-storego0.14→0.15; the Web SDK packages go0.14→0.15. The client's MSRV is Rust 1.93 (the base crates build on 1.90+).Because the native hash and the MAST/serialization formats changed upstream, 0.14 artifacts (accounts, notes, proofs, serialized stores,
.masl/.masppackages) do not round‑trip. Re‑assemble from source and re‑sync into a fresh store.Affected Code
Cargo.toml:
package.json (Web SDK):
Migration Steps
cargo updateto pull the matchingmiden-crypto 0.25and VM0.23minors.miden-provertomiden-prove.1.93.@miden-sdk/miden-sdk,@miden-sdk/react, andmiden-idxdb-storeto^0.15.0together — a mix of 0.14/0.15 packages will not link against the shared WASM ABI.0.15node (the protocol version is negotiated at connect; a mismatch is rejected), and re‑sync into a fresh store.Common Errors
failed to select a version for miden-provemiden-prover.MastForest deserialization failed: unexpected version0.0.3.masl/.maspfrom source under0.23.0.15client against a0.14node0.15.Hashing & Stack Conventions
SMT leaf hashing switched to Poseidon2 domain separation
Summary
The core library's Sparse Merkle Tree leaf hashing (
miden::corecollections::smt) now mixes a leaf‑domain separator into the Poseidon2 capacity word, so MASM‑side leaf digests matchSmtLeaf::hash()inmiden-crypto. Leaf preimages are hashed withposeidon2::merge_in_domainusingLEAF_DOMAIN = 0x13af. This is a digest‑changing change: any SMT leaf digest, SMT root, or advice‑map key derived from MASM‑side leaf hashing under0.22will not reproduce under0.23. It pairs with themiden-crypto 0.25bump.Affected Code
MASM (core‑lib
collections::smt, simplified):The per‑leaf cycle cost also changed (the
pair_countcoefficient went from3to6), so any hard‑coded cycle‑count expectations aroundsmt::get/smt::setneed updating.Migration Steps
0.22.miden-crypto, upgrade to0.25so both sides agree.miden-crypto0.25 downstream renamesSummary
Bumping to
miden-crypto 0.25(andmiden-vm 0.23) surfaces several renames in code that builds against the protocol crates directly:Felt::new(n)call sites that want the previous (non‑reducing) behaviour are nowFelt::new_unchecked(n)(Felt::newnow reduces modulo the field).ecdsa_k256_keccak::SecretKeyis renamedSigningKey; the EdDSA/X25519 keyeddsa_25519_sha512::SecretKeyisKeyExchangeKey. Falcon'sfalcon512_poseidon2::SecretKeyis unchanged.EMPTY_SMT_ROOTconstant was recomputed for the Plonky3‑aligned Poseidon2 and the domain‑separatedSmtLeaf::hash— any hard‑coded SMT‑root literal changes.adv_pushwas dropped (seeadv_push.Nremoved) and cross‑module‑referenced MASM constants/procedures must be markedpub.Affected Code
Migration Steps
Felt::new(...)withFelt::new_unchecked(...)where you relied on the non‑reducing constructor.ecdsa_k256_keccak::SecretKey→SigningKeyandeddsa_25519_sha512::SecretKey→KeyExchangeKey.pub, and regenerate hard‑codedEMPTY_SMT_ROOT/ SMT‑root literals.Account Changes
AccountTyperemoved;AccountStorageModerenamed toAccountTypeSummary
The account ID was simplified so its prefix no longer encodes whether the account is a faucet/regular account or whether its code is mutable. As a result:
AccountTypeenum (FungibleFaucet,NonFungibleFaucet,RegularAccountImmutableCode,RegularAccountUpdatableCode) is removed.AccountStorageModeis renamed toAccountTypeand trimmed to{ Private, Public }(theNetworkvariant is gone — see the next section).AccountIdaccessorsis_faucet(),is_regular_account(),storage_mode(),is_network(), and the oldaccount_type()semantics no longer exist.AccountId::account_type()now returns the visibility‑styleAccountType(Private/Public).0is now invalid.Faucet‑vs‑regular is now a property of the account's code/components, not its ID.
Affected Code
AccountId::new(seed, version, ..)keeps the same parameters, butversionmust beAccountIdVersion::Version1(Version0no longer exists).Migration Steps
AccountStorageModewithAccountType; the variants arePrivate/Public.AccountTypeimport (Regular*/*Faucet); that enum is gone.id.storage_mode()withid.account_type()(orid.is_public()/id.is_private()).id.is_faucet()/id.is_regular_account()with checks on the account's code/components.AccountIdVersion::Version0withVersion1; regenerate any persisted account IDs.AccountStorageMode::Networkremoved; network accounts via an allowlistSummary
The
Networkstorage mode was removed (AccountStorageModeitself is nowAccountType). An account is now recognised as a network account by the presence of a standardizedNetworkAccountNoteAllowliststorage slot, with helpersNetworkAccount(a wrapper for identification) and theAuthNetworkAccountauth component.AccountId::is_network()is gone.Migration Steps
AccountStorageMode::Network; pickPublicand add the network‑account components (AuthNetworkAccount::with_allowed_notes(...)).id.is_network()withNetworkAccount::new(account)/ theNetworkAccountNoteAllowlistslot check.AuthNetworkAccountgains a tx‑script allowlistSummary
(v0.15.2) The
AuthNetworkAccountauth component previously banned transaction scripts outright. It now gates them with a root allowlist, so approved tx scripts (e.g. setting the expiration delta) can run. The note‑allowlist constructorwith_allowlistwas renamed towith_allowed_notesto pair with the newwith_allowed_tx_scriptssetter.Migration Steps
AuthNetworkAccount::with_allowlist(...)towith_allowed_notes(...)..with_allowed_tx_scripts(roots)(an empty set — the default — permits none).procedure_digest!→procedure_root!; newNoteScriptRoot/AccountComponentNameSummary
Several root/identifier values gained dedicated newtypes:
procedure_digest!macro is renamedprocedure_root!. It now returns anAccountProcedureRoot(instead ofWord) and takes anAccountComponentCode(Component::code()) instead of a library‑producing closure.NoteScript::root()returns a newNoteScriptRootnewtype instead ofWord(convert with.into()).AccountComponentNamestring wrapper validates component names.Migration Steps
procedure_digest!→procedure_root!and passComponent::code()as the last argument; the static is now aLazyLock<AccountProcedureRoot>.note_script.root()toNoteScriptRoot(convert with.into()/Word::from(..)where aWordis needed).AccountComponentName::new(...)where a validated component name is required.is_compatible_withremoved; auth/policy renamesSummary
A handful of standards‑library APIs changed:
StandardNote::is_compatible_withandAccountInterfaceExt::is_compatible_withwere removed — perform compatibility checks via the account interface directly.AuthMultisigGuardian→AuthGuardedMultisig(theguardianauth namespace is retained).OwnerControlledBlocklistwas renamedBlocklistOwnerControlled.Pausable,Authority, allowlist/blocklist transfer policies, andFungibleTokenMetadata.Note Changes
NoteId→NoteDetailsCommitment; newNoteIdcommits to metadataSummary
The 0.14
NoteId(a commitment over recipient + assets only) is renamed toNoteDetailsCommitment. A brand‑newNoteIdis introduced that hashes the details commitment together with the note metadata commitment, so the public note ID now changes when the note's metadata (sender, type, tag, attachments) changes.Affected Code
NoteDetails::commitment()now returns aNoteDetailsCommitment(not the public note id, which requires metadata).Migration Steps
NoteDetailsCommitment.NoteId::new(recipient, asset_commitment)withNoteDetailsCommitment::new(&recipient, &assets).NoteId, callnote.id(), orNoteId::new(details_commitment, &metadata).NoteMetadata→PartialNoteMetadata; multiple attachments per noteSummary
The metadata types were renamed and reshuffled to support multiple attachments per note (up to
NoteAttachments::MAX_COUNT= 4):NoteMetadata(sender/type/tag + single attachment)PartialNoteMetadata(sender/type/tag only)NoteMetadataHeader(the on‑stack metadata word)NoteMetadata(metadata word + attachment headers + attachments commitment)NoteAttachment(single,with_attachment)NoteAttachments(collection,with_attachments)Note::new(assets, metadata, recipient)now takes aPartialNoteMetadata; a newNote::with_attachments(assets, partial_metadata, recipient, attachments)carries the attachments. The singleNoteMetadata::with_attachment/.attachment()API is gone.Affected Code
Migration Steps
NoteMetadatatoPartialNoteMetadata.NoteMetadataHeaderwithNoteMetadata(the word‑shaped metadata isNoteMetadata::to_metadata_word())..with_attachment(a)with aNoteAttachments::new(vec![...])collection andNote::with_attachments(...).metadata.attachment()reads withnote.attachments().get(i)/.find(scheme)/note.has_attachments().Attachment MASM:
set_*→add_*;get_metadatadrops attachmentsSummary
Because a note can now hold several attachments, the kernel/protocol attachment procedures were rewritten:
output_note::set_attachment→add_attachment(andset_word_attachment→add_word_attachment,set_array_attachment→add_attachment_from_memory). They append instead of overwriting, and the stack signature dropped theattachment_kindfield.note::extract_attachment_info_from_metadata→metadata_into_attachment_schemes, returning the four attachment scheme markers.get_metadataprocedures (active_note,input_note,output_note) no longer return attachments — they return just the singleMETADATAword.Affected Code
Migration Steps
set_attachment/set_word_attachment/set_array_attachmenttoadd_attachment/add_word_attachment/add_attachment_from_memory, and drop theattachment_kindoperand.extract_attachment_info_from_metadatawithmetadata_into_attachment_schemes.get_metadataconsumer: it now leaves only[METADATA]— remove the extra cleanup that handled the attachment word.NoteTypeencoding 2‑bit → 1‑bit;Privateis the defaultSummary
NoteTypedropped from a 2‑bit encoding to 1 bit. The numeric encodings flipped andNoteType::Privateis now the#[default]. Anything that serialized a note type, packed it into a tag, or relied on the oldPublic = 0b01/Private = 0b10values changes.Public0b011Private0b100(default)Migration Steps
0b01/0b10note‑type bit literals; use theNoteTypevariants.SwapNote::build_tag, for example, now uses the 1‑bit encoding — script‑root bits 14 → 15).Private.Nullifier now includes metadata and attachments commitment
Summary
The note nullifier hash now folds in the note's metadata word and attachments commitment in addition to the serial number, script root, storage commitment, and asset commitment.
Nullifier::newgained two parameters and theFrom<&NoteDetails>conversion was replaced byNullifier::from_details_and_metadata, because a nullifier can no longer be computed from details alone.Affected Code
Migration Steps
Nullifier::newcall.Nullifier::from(&details)/(&details).into()withNullifier::from_details_and_metadata(&details, &metadata).MAX_ASSETS_PER_NOTE255 → 64;NOTE_MEM_SIZE3072 → 1024Summary
The per‑note asset cap was reduced from 255 to 64, and the kernel note memory region (
NOTE_MEM_SIZE) shrank from 3072 to 1024. Notes carrying more than 64 assets now fail to build, and MASM that hard‑codes note‑memory offsets against the old 3072‑word region must be reworked.Migration Steps
NOTE_MEM_SIZE = 1024.SwapNote/MintNotestorage trimmed;PSWAPaddedSummary
Unused fields were removed from standard note storage:
payback_attachmentfromSwapNoteStorageandattachmentfromMintNoteStorage. A newPSWAP(partial swap) note andPswapNoteAPI (with aPswapAttachmentscheme andpayback_note/remainder_notediscovery helpers) supports partial‑fill asset exchange with remainder re‑creation.Migration Steps
payback_attachment/attachmentstorage fields on swap/mint notes.PswapNotefor partial‑fill swaps; reconstruct private paybacks viaPswapNote::payback_note/remainder_note.Asset, Vault and Faucet Changes
FungibleAsset::amount()/get_balance()returnAssetAmountSummary
A new validated
AssetAmountnewtype wraps fungible amounts.FungibleAsset::amount()now returnsAssetAmount(wasu64), andAssetVault::get_balance()returnsResult<AssetAmount, AssetError>(wasResult<u64, AssetVaultError>) and takes anAssetVaultKeyinstead of anAccountId. The vault key carries the asset'sAssetComposition, so balance lookups are explicit about fungible‑vs‑non‑fungible.Affected Code
AssetAmountimplementsFrom<u8/u16/u32>andTryFrom<u64>(validating against the max fungible amount), plusAdd/SubandDisplay.Migration Steps
u64amounts you pass into faucet/asset constructors inAssetAmount(AssetAmount::from(n)for small ints,AssetAmount::try_from(n)foru64).AssetAmountback tou64with.as_u64()/u64::from(_)where a raw integer is needed.vault.get_balance(faucet_id)withvault.get_balance(AssetVaultKey::new_fungible(faucet_id, callback_flag)).AssetVaultErrortoAssetErroronget_balance.FungibleFaucetreplacesBasicFungibleFaucet+NetworkFungibleFaucetSummary
The separate
BasicFungibleFaucetandNetworkFungibleFaucetcomponents were merged into a singleFungibleFaucetcomponent, and its oldFungibleFaucetBuilderwas replaced with abon‑generated builder (FungibleFaucet::builder()). The constructor accepts a structuredTokenNameplus optional token‑metadata fields and anAssetAmountmax_supply. A companionFungibleTokenMetadatacomponent exposes the metadata via MASM getters. For the end‑to‑end client construction recipe (withTokenPolicyManager), see (Rust)FungibleFaucetbuilder +TokenPolicyManager.Affected Code
Migration Steps
BasicFungibleFaucet/NetworkFungibleFaucetimports withFungibleFaucet.FungibleFaucet::builder()with the required settersname,symbol,decimals,max_supply.max_supplyfromFelttoAssetAmount.AssetCompositionand theAssetVaultKeycomposition byteSummary
A new
AssetCompositionenum (None,Fungible,Custom) discriminates assets, and the asset vault key's metadata byte now encodes the composition (plus the asset‑callback flag).AssetVaultKey::new(asset_id, faucet_id, composition, callback_flag)is the general constructor;AssetVaultKey::new_fungible(faucet_id, callback_flag)is the fungible shortcut. (Customcomposition is reserved and currently rejected.)Migration Steps
AssetVaultKey::new_fungible/AssetVaultKey::new.AssetComposition(viakey.composition()) instead of inspecting raw bits.Transaction Changes
fee_faucet_idreplacesnative_asset_idonFeeParametersSummary
FeeParameters::native_asset_id(field, getter, andnewparameter) was renamed tofee_faucet_id. Because account IDs no longer encode faucet‑ness,FeeParameters::newno longer validates that the ID is a fungible faucet and is now infallible (returnsSelf, notResult).Migration Steps
native_asset_id→fee_faucet_idat the constructor, field, and getter.?/FeeErrorhandling onFeeParameters::new.TransactionScript::root()returnsTransactionScriptRootSummary
(v0.15.2)
TransactionScript::root()now returns a typedTransactionScriptRootnewtype instead of a rawWord(convert with.into()). A newTransactionScript::from_package(&Package)builds a script from a compiledmiden-mast-packagepackage, and the newtx::get_tx_script_rootkernel proc returns the executed tx‑script root (empty word if none).Migration Steps
tx_script.root()toTransactionScriptRoot..into()/Word::from(root)where aWordis required.ProvenBatch::new→new_unchecked;NoteConsumptionInfofields privateSummary
Batch/transaction housekeeping changes that affect anyone constructing these types by hand:
ProvenBatch::newwas renamedProvenBatch::new_uncheckedto signal it skips validation.NoteConsumptionInfo(and related types) gained cycle counts and had their fields made private; use the accessor methodssuccessful()/failed().Migration Steps
ProvenBatch::new→ProvenBatch::new_unchecked.NoteConsumptionInfowith the accessor methods.Client Changes
(Rust)
NodeRpcClientGetAccountsurface reshapedSummary
The account‑fetching surface on the
NodeRpcClienttrait was rebuilt around the node's/GetAccountendpoint:get_account_proof(account_id, storage_requirements, account_state, known_account_code, known_vault_commitment)was replaced byget_account(account_id, request: GetAccountRequest), whereGetAccountRequestbundles the previous positional args behind a builder.get_account_detailsno longer returns aFetchedAccountenum — it returnsResult<Option<Account>, RpcError>(Nonefor accounts without public state), fetching all of a public account's storage maps and vault in a single round‑trip. It no longer returns anything for private accounts; useget_accountfor a private account's commitment.resolve_oversize_vault/resolve_oversize_storage_mapsfill in vault/map entries the node flagged as oversize.GetAccountRequest,StorageMapFetch,VaultFetch, andAccountStateAtlive undermiden_client::rpc::domain.Affected Code
Migration Steps
get_account_proof(...)calls withget_account(account_id, GetAccountRequest::new()....); move each positional arg onto the corresponding builder method.match FetchedAccount { Public | Private }onget_account_detailswithOption<Account>handling; route private‑account commitment lookups throughget_account.get_account_detailsnow callsresolve_oversize_vault/resolve_oversize_storage_mapsfor you.(Rust)
NodeRpcClient:SyncTarget,check_nullifiersremoved, requiredblock_toSummary
Several
NodeRpcClientmethods changed to match the0.15RPC definitions:sync_chain_mmr'sblock_to: Option<BlockNumber>becameupper_bound: SyncTarget. UseSyncTarget::CommittedChainTipfor the oldNonebehavior,SyncTarget::ProvenChainTipfor the latest proven block, orSyncTarget::BlockNumber(n)for a specific height.check_nullifiers(andRpcEndpoint::CheckNullifiers,EndpointError::CheckNullifiers,CheckNullifiersError) were removed. Usesync_nullifiersto retrieve nullifier updates.sync_nullifiers,sync_notes,sync_notes_with_details,sync_storage_maps, andsync_account_vaultlost theirOption<BlockNumber>upper bound in favor of a requiredblock_to: BlockNumber.get_block_by_numbergained aninclude_proof: boolparameter.submit_proven_batchis a new required trait method.SyncTargetlives atmiden_client::rpc::domain::sync::SyncTarget.Affected Code
Migration Steps
sync_chain_mmr(_, None)withsync_chain_mmr(_, SyncTarget::CommittedChainTip); map any explicitSome(n)toSyncTarget::BlockNumber(n).check_nullifierswithsync_nullifiersand adapt toVec<NullifierUpdate>(drop theSmtProofpath).block_to(e.g. the client's current sync height / chain tip) tosync_nullifiers,sync_notes,sync_storage_maps,sync_account_vault.include_prooftoget_block_by_numbercalls (falseunless you need the block proof).NodeRpcClientyourself, addsubmit_proven_batch.(Rust) Note‑import APIs return
Vec<NoteDetailsCommitment>Summary
Client::import_notes,Client::sync_note_transport, and theSyncSummary::new_private_notesfield now yieldVec<NoteDetailsCommitment>instead ofVec<NoteId>. A metadata‑less import has noNoteIdyet, so the client identifies such notes by their metadata‑independent details commitment. Resolve a commitment back to a record withClient::get_input_notes(NoteFilter::DetailsCommitments(vec![..])).Affected Code
Migration Steps
import_notes/sync_note_transportresults and theSyncSummary::new_private_notesfield fromNoteIdtoNoteDetailsCommitment.NoteIdto look a note up, switch toNoteFilter::DetailsCommitments(..)againstget_input_notes.(Rust)
FungibleFaucetbuilder +TokenPolicyManagerconstructionSummary
The fungible‑faucet construction story was redesigned end to end. The client previously re‑exported
BasicFungibleFaucetplus theMintAuthControlled/MintOwnerControlled/BurnAuthControlled/BurnOwnerControlledpolicy components. In0.15:FungibleFaucet, built viaFungibleFaucet::builder().TokenPolicyManager(.with_mint_policy(MintPolicyConfig, PolicyRegistration).with_burn_policy(...)), with standaloneMintAllowAll/MintOwnerOnly/BurnAllowAll/BurnOwnerOnlypolicy components. The oldMint*Controlled/Burn*Controlledtypes were removed.These are re‑exported from
miden_client::account.Affected Code
Migration Steps
BasicFungibleFaucet::new(symbol, decimals, max_supply)withFungibleFaucet::builder()....build(), wrappingmax_supplyinAssetAmount::new(..).TokenPolicyManagerconfigured viawith_mint_policy/with_burn_policy, installed with.with_components(policy_manager).Mint*Controlled/Burn*Controlledimports.(Rust)
InputNoteRecord::newtakesNoteAttachments; storeattachmentscolumnSummary
InputNoteRecord::newgained a second positional parameter,attachments: NoteAttachments, so input notes persist their attachment content. The SQLite store'sinput_notestable gained anattachmentscolumn.NoteAttachmentsis re‑exported frommiden_client::note.Migration Steps
NoteAttachments(orNoteAttachments::default()) into everyInputNoteRecord::newcall as the second argument.Store, add anattachmentscolumn to your input‑notes table and persist/load it.(Rust)
sync_notes/sync_transactionsreturn updates directlySummary
sync_notesandsync_transactionsnow return the fetched updates directly. The wrapper structsNoteSyncInfoandTransactionsInfowere removed:sync_notesreturnsVec<NoteSyncBlock>andsync_transactionsreturnsVec<TransactionRecord>.Migration Steps
.blocks/.transactionsfield access — the methods return the collections directly.get_block_header_by_number(None, false)) where you previously read it offNoteSyncInfo.(Rust)
get_note_script_by_rootreturnsOption<NoteScript>Summary
NodeRpcClient::get_note_script_by_rootno longer errors when the node has no script registered for the requested root — it returnsOk(None). Implementations must still verify a returned script's root matches the request.Migration Steps
Option—Nonemeans "no script for this root", which previously surfaced as an error.(Rust)
miden_client::notere‑exports realignedSummary
The protocol split attachment data off
NoteMetadata, and the client'snotere‑exports follow:NoteAttachmentKind,NoteMetadataHeader.NoteAttachmentHeader,NoteAttachments,PartialNoteMetadata.NoteScript::root()now returnsNoteScriptRoot(re‑exported frommiden_client::note) instead ofWord.Migration Steps
NoteAttachmentKind/NoteMetadataHeaderimports withNoteAttachmentHeader/NoteAttachments/PartialNoteMetadataas needed.note_script.root()was used as aWord, insert a.into()/Word::from(..)conversion.(Rust)
CommittedNoteMetadataremovedSummary
The
CommittedNoteMetadataenum (withFull(NoteMetadata)and a header‑onlyHeader { sender, note_type, tag, attachment_kind }variant) was removed. Sync responses now always carry full metadata, soCommittedNote::metadata()returns&NoteMetadatadirectly — noOption, no header‑only case.Migration Steps
CommittedNoteMetadatamatch — readNoteMetadatadirectly off the committed note.(Rust)
build_wallet_idsignatureSummary
build_wallet_iddropped its trailingis_mutable: bool(code mutability isn't encoded in the account ID) and itsstorage_mode: AccountStorageModeparameter was replaced byaccount_visibility: AccountType.Migration Steps
is_mutableargument.AccountStorageModeargument with the correspondingAccountTypevisibility.(Rust)
compile_note_scriptexpects a@note_scriptlibrarySummary
client.code_builder().compile_note_script(src)now expects a MASM library with a single procedure annotated@note_scriptinstead of a barebegin … endprogram (the underlying assembly switched fromassemble_programtoassemble_library).Migration Steps
@note_script+pub proc main … end; remove thebegin … endframing.(Web)
AccountStorageMode.network()removedSummary
The
0.15chain has no separate network‑account flag.AccountStorageMode.network()was removed, and theStorageModestring union dropped"network"(it is now"public" | "private"). Anywhere you constructed a network‑mode account —AccountStorageMode.network(),StorageMode.Network, oraccounts.create({ storage: "network" })— must switch to"public"or"private".AccountStorageMode.tryFromStr("network")now rejects.Affected Code
Migration Steps
AccountStorageMode.network()with.public()or.private().StorageMode.Network/ the"network"string with"public"or"private".accounts.create({ storage })and anytryFromStrcall sites for"network".(Web)
AccountTypenarrowed; faucet checks move toAccountSummary
The on‑chain
AccountTypeno longer encodes faucet‑vs‑regular or updatable‑vs‑immutable. The WASM‑exportedAccountTypeenum narrowed from{ FungibleFaucet, NonFungibleFaucet, RegularAccountImmutableCode, RegularAccountUpdatableCode }to{ Private, Public }. As a result:AccountId.isFaucet(),AccountId.isNetwork(), andAccountId.isRegularAccount()were removed (onlyisPublic()/isPrivate()remain).Account:Account.isFaucet()/Account.isRegularAccount().Account.isNetwork()andAccount.isUpdatable()were removed outright.Affected Code
Migration Steps
accountId.isFaucet()/.isRegularAccount()onto the materialisedAccount. You need the fullAccount, not just itsAccountId.accountId.isNetwork(),account.isNetwork(), andaccount.isUpdatable()— they have no replacement.switchon the WASMAccountTypeenum's faucet/regular variants with theAccountpredicates; the enum only carriesPrivate/Publicnow.(React)
useCreateWallet/useCreateFaucetdrop"network"Summary
Matching the
StorageModenarrowing, the ReactuseCreateWallet({ storageMode })anduseCreateFaucet({ storageMode })hooks no longer accept"network". ThestorageModeoption type is now"private" | "public".Migration Steps
storageMode: "network"with"public"or"private"in everycreateWallet/createFaucetcall.StorageMode‑typed state feeding these hooks.(Web)
NoteAttachmentreshaped to word‑vector contentSummary
Attachments are now always word‑vector‑shaped. The
NoteAttachmentKind { Word, Array }dispatch and the per‑variant accessors/constructorsNoteAttachment.newWord/.newArray/.asWord/.asArray, theattachmentKindgetter, and theNoteMetadata.attachment()getter were all removed. Build attachments withNoteAttachment.fromWord(scheme, word)orNoteAttachment.fromWords(scheme, words), and read them back withNoteAttachment.toWords(). The attachment words now live on the note record (InputNoteRecord.attachments()), not onNoteMetadata.NoteAttachmentSchemeis now u16‑backed: its constructor throws if the value exceeds the u16 range, andNoteAttachmentScheme.asU32()was removed.Affected Code
Migration Steps
NoteAttachment.newWord(scheme, word)withNoteAttachment.fromWord(scheme, word)andnewArray(scheme, felts)withfromWords(scheme, words).att.asWord()/att.asArray()/att.attachmentKind()reads withatt.toWords()and decode theWord[]yourself.noteMetadata.attachment()withinputNoteRecord.attachments().NoteAttachmentScheme.asU32(); the scheme is u16‑backed and its constructor validates the range.(Web)
InputNoteRecord.id()returnsNoteId | undefined; IDB re‑keyedSummary
A partial (metadata‑less) input note has no note ID yet, so
InputNoteRecord.id()now returnsNoteId | undefined. A newInputNoteRecord.attachments()getter returns the note's attachments (NoteAttachment[]; empty when none). On the storage side,miden-idxdb-storenow keys input notes by their details commitment instead of their note ID (matching the SQLite store): theInputNotestable's primary index changed fromnoteIdtodetailsCommitment, so a partial note later completed with its note ID updates the same row instead of creating a duplicate.Affected Code
Migration Steps
record.id()use againstundefined(partial notes have no ID).noteMetadata.attachment()reads withrecord.attachments().noteIdtodetailsCommitment. Re‑sync existing0.14stores under0.15.(Web)
importNoteFile/notes.importresolve to a hexstringSummary
WebClient.importNoteFile(...)(andnotes.import(...)) now resolves to a hexstringinstead of aNoteIdobject. UpstreamClient::import_notesreturns details‑commitments rather than note IDs, so the web method returns the note‑id hex for a metadata‑bearing file, or the details‑commitment hex for a details‑only file. Pass it toNoteId.fromHex(...)if you need aNoteIdinstance.Migration Steps
string; drop any.toString()/NoteIdmethod calls on it.NoteId, wrap the result withNoteId.fromHex(hex).(Web)
FeltandWordconstructors throw on overflowSummary
new Felt(value)andnew Word(values)now throw when an input is at or beyond the field modulus, instead of silently constructing an out‑of‑range value. Each input must be a canonical field element. Wrap construction from untrusted input intry/catch.Migration Steps
Felt/Word, or catch the thrown error.u64/bigintvalues (e.g. from external systems) into these constructors.(Web)
syncNotesblockTorequired;chainTip()removedSummary
RpcClient.syncNotes(blockFrom, blockTo, noteTags)'sblockToparameter is now required (was optional). The upstream RPC no longer returns the chain tip, soNoteSyncInfo.chainTip()was removed — useclient.syncState()to learn the chain tip.NoteSyncInfo.blockTo()still exists.Migration Steps
blockTotosyncNotes.NoteSyncInfo.chainTip()reads withclient.syncState().(Web)
getNoteScriptByRootreturnsNoteScript | undefinedSummary
RpcClient.getNoteScriptByRoot(scriptRoot)now resolves toNoteScript | undefinedinstead of throwing when the node has no script for the given root.Migration Steps
try/catch"not found" path with an=== undefinedcheck.try/catchonly for genuine transport/RPC failures.(Web)
FetchedNoteexposesnoteId/metadata;headerremovedSummary
FetchedNote(returned byRpcClient.getNotesById(...)) now storesnoteIdandmetadatadirectly and exposes them as getters. The syntheticheadergetter was removed — aNoteHeadercan no longer be reconstructed from header‑shaped fields alone for private notes. A newattachmentsgetter exposes the note's attachments (populated for both public and private fetched notes). The JS constructor signature is unchanged.Migration Steps
fetched.header.id()withfetched.noteIdandfetched.header.metadata()withfetched.metadata.NoteHeaderfrom aFetchedNote.fetched.attachments(works for private notes too).(Web)
newFaucetrebuilt onFungibleFaucet+TokenPolicyManagerSummary
WebClient.newFaucet(...)(andaccounts.create({ type: "FungibleFaucet", ... })) now assembles a faucet from the0.15FungibleFaucetcomponent plus aTokenPolicyManagerthat registersAllowAllmint and burn policies (transfer policies are intentionally omitted).non_fungible = truestill fails fast. The JS signaturenewFaucet(storageMode, nonFungible, tokenName, tokenSymbol, decimals, maxSupply, authScheme)is unchanged. Separately,BasicFungibleFaucetComponent.fromAccount(account)now reads the new0.15metadata slot, so faucets minted by prior SDK versions can no longer be introspected through it.Migration Steps
0.15if you needBasicFungibleFaucetComponent.fromAccount(...)to introspect them.non_fungible = false;truestill throws "Non‑fungible faucets are not supported yet".(Web)
idxdb-store:committedNoteIds→committedNoteTagSourcesSummary
On
miden-idxdb-store'sJsStateSyncUpdate, thecommittedNoteIdsfield was renamed tocommittedNoteTagSourcesand now carries details‑commitment hex rather than note‑id hex. Anyone driving the IndexedDB store's sync apply path directly (or stubbingJsStateSyncUpdatein tests) must rename the field and feed details‑commitment hex.Migration Steps
committedNoteIds→committedNoteTagSourceseverywhere you construct or readJsStateSyncUpdate.(Web)
getAccountProofrewired; WASMClientErrorgains acodeSummary
Two smaller web changes:
RpcClient.getAccountProof(accountId, storageRequirements?, blockNum?, knownVaultCommitment?)keeps the same JS signature but is now wired onto the0.15get_account(GetAccountRequest...)upstream API. No JS call‑site change is required; it needs a0.15node.codeproperty on the thrown JS error — currentlyACCOUNT_NOT_FOUND_ON_CHAINandACCOUNT_ALREADY_TRACKED. This is additive (message‑string matching still works) but branching oncodeis the recommended pattern, and the worker shim forwards it.Migration Steps
getAccountProofcallers at a0.15node (the underlying RPC was renamed/reshaped).ClientErrormessage text for "account not found on chain" or "already tracked", switch toerror.code. Keep a fallback for errors without acode(only those two variants are mapped today).(Web)
WasmWebClient.proveTransactionWithProver→proveTransactionSummary
On the raw WASM client (
WasmWebClient— the low-level surface behindMidenClient),proveTransactionWithProver(txResult, prover)was renamedproveTransaction(txResult, prover?), with the prover now an optional second parameter (omitting it uses the local prover). The high-levelMidenClientresource API is unaffected, but anything driving the raw client directly — worker shims, offscreen/prover documents,_withInnerWebClientcallbacks — must rename the call.Affected Code
Migration Steps
proveTransactionWithProver(txResult, prover)toproveTransaction(txResult, prover).proveTransaction()without a prover — the 0.15 signature is compatible (prover optional), no change needed.(Web)
storeIdentifier()is now asyncSummary
MidenClient.storeIdentifier()now returns aPromise<string>instead of a plainstring(the identifier is read from the client behind the worker/async boundary). Call sites that fed the result intoexportStore/importStoreor string operations mustawaitit.Migration Steps
await client.storeIdentifier()everywhere; un-awaited uses surface asPromise<string>-vs-stringtype errors (or[object Promise]store names at runtime).(Web)
InputNoteRecord.nullifier()returnsstring | undefinedSummary
Because a 0.15 nullifier folds in the note's metadata (see Nullifier now includes metadata and attachments commitment), a partial (metadata-less) input note record has no computable nullifier —
InputNoteRecord.nullifier()now returnsstring | undefined, pairing withid()'sNoteId | undefined. A record missing either is a partial note that sync has not yet completed.Migration Steps
record.nullifier()againstundefinedalongside the existingrecord.id()guard; treat records missing either as not-yet-consumable and skip them from listings.(CLI)
--account-typeaccepts onlyprivate/publicSummary
The
-t/--account-typeflag onnew-account/new-walletnow takes onlyprivateorpublic(account visibility); the legacy values (fungible-faucet,non-fungible-faucet,regular-account-immutable-code,regular-account-updatable-code), the separate--mutableflag, and the standalone--storage-modetoggle were removed. Whether an account is a faucet is derived from its components — installing aFungibleFaucetcomponent yields a fungible faucet (with an implicitTokenPolicyManager).Affected Code
Migration Steps
--account-type <regular/faucet variant>with--account-type private|public.--mutableand--storage-mode; pick the faucet vs. wallet shape via the-ppackage/components.(CLI)
new-faucetrequires a[fungible-faucet-metadata]blockSummary
The faucet init‑data file passed via
-inow uses a typed[fungible-faucet-metadata]block (symbol,decimals,max_supply, optionalname) instead of the old stringly‑typed["miden::standards::fungible_faucets::metadata"]section. Faucet accounts created with the previous layout are no longer recognized byaccount list/account show.Migration Steps
[fungible-faucet-metadata]and switch to typed scalars (decimals/max_supplyare integers, not quoted strings).(CLI)
address addtakes bech32; newaddress encodeSummary
address addnow takes<ACCOUNT_ID> <BECH32_ADDRESS>(a pre‑encoded address) instead of<ACCOUNT_ID> <INTERFACE> [TAG_LEN]. A newaddress encode <ACCOUNT_ID> <INTERFACE> [TAG_LEN]subcommand produces the bech32 string from the individual fields.Migration Steps
address encode <ACCOUNT_ID> <INTERFACE> [TAG_LEN].address add <ACCOUNT_ID> <BECH32_ADDRESS>.General MASM Changes
Kernel/protocol proc renames:
build_recipient*,extract_*_from_metadataSummary
Several core MASM procedures in
miden::protocol::notewere renamed for consistency. These are used by every custom note script that computes a recipient or reads metadata.note::build_recipient_hashnote::compute_recipientnote::build_recipientnote::compute_and_store_recipientnote::extract_sender_from_metadatanote::metadata_into_sendernote::extract_attachment_info_from_metadatanote::metadata_into_attachment_schemesNew convenience helpers were also added:
note::metadata_into_note_typeandnote::metadata_into_tag(use the latter instead of slicing the header manually).Migration Steps
metadata_into_tag.Redundant kernel outputs removed
Summary
Six kernel procedures stopped returning values that were identical to (or trivially recoverable from) their inputs. Custom MASM that consumed the now‑removed outputs must drop the stale cleanup.
active_note::get_assets(alsoinput_note/output_note)[num_assets, dest_ptr][num_assets]active_note::get_storage[NOTE_STORAGE_COMMITMENT, num_storage_items, dest_ptr][num_storage_items]faucet::mint[NEW_ASSET_VALUE][]note::write_assets_to_memoryMigration Steps
drop/movup+dropthat cleared the echoeddest_ptrafterget_assets/get_storage.get_storage, theNOTE_STORAGE_COMMITMENTword is no longer on the stack — delete thedropwthat consumed it.faucet::mint, do not expectNEW_ASSET_VALUE.adv_push.Nimmediate form removed;adv_pushwaddedSummary
The immediate form of
adv_push(adv_push.N) was removed.adv_pushnow always pops exactly one element from the advice stack. To push N elements, emit N consecutiveadv_pushinstructions (orrepeat.N adv_push end). A newadv_pushwinstruction pushes a full word (4 elements). On the Rust AST side,Instruction::AdvPush(ImmU8)becameInstruction::AdvPushplus a newInstruction::AdvPushW.Migration Steps
adv_push.Nwith Nadv_pushinstructions (orrepeat.N adv_push end).adv_push.4to fetch a word, consideradv_pushw.Instruction::AdvPush(n)with NInstruction::AdvPush(orInstruction::AdvPushW).Internal
_implprecompile procedures removedSummary
The internal
_implprecompile helper procedures were removed from the core‑lib public surface:ecdsa_k256_keccak::verify_prehash_impl,eddsa_ed25519::verify_prehash_impl,keccak256::hash_bytes_impl, andsha512::hash_bytes_impl. The public wrappers (verify,verify_prehash,hash_bytes, …) are unchanged and remain the supported entry points.Migration Steps
*_implprecompile helper directly by fully‑qualified path, switch to the corresponding public wrapper.VM and Assembler Changes
Sync‑first execution:
BaseHost/SyncHost;executereturnsExecutionOutputSummary
Execution and proving became sync‑first with runtime‑free async compatibility. The single
Hosttrait from0.22is split into three:BaseHost(shared source/label resolution + event‑name lookup),SyncHost: BaseHost(synchronousget_mast_forest/on_event), andHost: BaseHost(the async variant). A blanket impl makes everySyncHostautomatically aHost. The sync entry points (execute_sync,prove_sync,FastProcessor::execute_sync/execute_mut_sync) requireSyncHost. Bothexecute()andexecute_sync()now returnExecutionOutputinstead ofExecutionTrace— trace building is explicit viaexecute_trace_inputs*()+trace::build_trace(). The deprecatedexecute_sync_mut()/execute_for_trace*()aliases and the unboundTraceBuildInputs::new()/from_program()constructors were removed.Affected Code
Migration Steps
Hostimpl into aBaseHostimpl (label/source resolution) plus aSyncHostimpl with plain (non‑async)get_mast_forest/on_event.SyncHost.ExecutionTracereturn withExecutionOutputaccessors; build a trace explicitly only when needed.FastProcessor::execute_sync_mut(...)withexecute_mut_sync(...), andexecute_for_trace*/TraceBuildInputs::new()withexecute_trace_inputs_sync()/execute_trace_inputs().prove_synctakes execution options separatelySummary
ProvingOptionsno longer carries anExecutionOptions.prove_sync/provenow take execution options and proving options as two separate parameters (and the sync path requires aSyncHost). Thewith_execution_options(...)/execution_options()accessors onProvingOptionsare gone.prove_from_trace_sync()now takes aTraceProvingInputs.Migration Steps
ProvingOptions::with_execution_options(...); passExecutionOptionsas its own argument.prove_syncimplementsSyncHost.prove_from_trace_sync(), build aTraceProvingInputsfrom post‑execution trace inputs.Live advice map bounded by total field elements
Summary
The live advice map is now bounded by total field‑element count during execution. Advice‑provider setup returns an error when the initial advice already exceeds the limit, and writes that would push the live map past the limit fail.
AdviceMapgained atotal_element_count()accessor.Migration Steps
Stricter assembly resolution: structured errors replace panics
Summary
Several previously‑panicking or silently‑partial assembly paths now return structured errors: oversized modules are rejected at resolver construction, non‑procedure invoke targets are rejected, self‑recursive / rootless call graphs return typed cycle errors, and unresolved
pub use <digest> -> <name>returns a normal assembly error. The linker also rejects non‑syscallreferences to exported kernel procedures and rejects empty kernel packages. Code that assembled cleanly under0.22continues to assemble; the change is that malformed inputs now surface as recoverableReporterrors instead of panics.Migration Steps
Result/Reporterror handling.exec/callinstead ofsyscall— that is now a hard error.Post‑last‑operation decorators deprecated
Summary
Operation‑indexed decorators placed after the last operation of a basic block are now rejected in both block assembly and serialized MAST forests. Decorators that should run after a block exits must use the
after_exitslot instead. This only affects code that buildsMastForests programmatically — ordinary MASM source is unaffected.Migration Steps
after_exitdecorator list.Project File Format
MAST wire format
0.0.2→0.0.3Summary
The MAST forest serialization format was refactored around fixed‑layout full, stripped, and hashless sections, with stable node IDs and stricter validation of untrusted forests. The wire‑format version constant bumped from
[0, 0, 2]to[0, 0, 3]. Serialized.masl/.masp/MastForestblobs produced under0.22will not deserialize under0.23. Deserialization of serialized libraries and kernel libraries is now treated as untrusted by default, rejecting spoofed/inconsistent node digests rather than trusting the bytes.Affected Code
Migration Steps
.masl/.masppackage and re‑serialize any cachedMastForestblobs from source under0.23.Need Help?
#dev-supportchannel.miden-client,web-sdk,miden-base, ormiden-vm.CHANGELOG.mdfiles carry the full list of changes, including non‑breaking features and fixes omitted from this guide.