Skip to content

Migration Guide 0.15 #312

@WiktorStarczewski

Description

@WiktorStarczewski

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.140.15.3), miden-vm (0.220.23), miden-client (0.140.15), and the Web SDK (@miden-sdk/* 0.140.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 01.
  • 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. NoteMetadataPartialNoteMetadata, NoteMetadataHeaderNoteMetadata; 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.20.0.3 (old .masl/.masp won't load), execution is sync‑first (BaseHost/SyncHost; executeExecutionOutput), 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.140.15.3, the VM crates (miden-assembly, miden-core, miden-core-lib, miden-processor, miden-prover) go 0.220.23, and miden-crypto goes 0.230.25. miden-client and miden-client-sqlite-store go 0.140.15; the Web SDK packages go 0.140.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

  1. 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.
  2. Do not rename miden-prover to miden-prove.
  3. Set the client toolchain to at least Rust 1.93.
  4. 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.
  5. 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

  1. Re‑derive every persisted SMT root, leaf digest, and advice‑map key computed from a MASM‑side SMT leaf hash under 0.22.
  2. If you compute SMT leaf digests in Rust via miden-crypto, upgrade to 0.25 so both sides agree.
  3. 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

  1. Replace Felt::new(...) with Felt::new_unchecked(...) where you relied on the non‑reducing constructor.
  2. Rename ecdsa_k256_keccak::SecretKeySigningKey and eddsa_25519_sha512::SecretKeyKeyExchangeKey.
  3. 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

  1. Replace imports of AccountStorageMode with AccountType; the variants are Private / Public.
  2. Delete the old AccountType import (Regular*/*Faucet); that enum is gone.
  3. Replace id.storage_mode() with id.account_type() (or id.is_public() / id.is_private()).
  4. Replace id.is_faucet() / id.is_regular_account() with checks on the account's code/components.
  5. 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

  1. Remove any use of AccountStorageMode::Network; pick Public and add the network‑account components (AuthNetworkAccount::with_allowed_notes(...)).
  2. 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

  1. Rename AuthNetworkAccount::with_allowlist(...) to with_allowed_notes(...).
  2. 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

  1. Rename procedure_digest!procedure_root! and pass Component::code() as the last argument; the static is now a LazyLock<AccountProcedureRoot>.
  2. Update bindings of note_script.root() to NoteScriptRoot (convert with .into() / Word::from(..) where a Word is needed).
  3. 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 AuthMultisigGuardianAuthGuardedMultisig (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

NoteIdNoteDetailsCommitment; 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

  1. Rename any value you treated as a "note id without metadata" to NoteDetailsCommitment.
  2. Replace NoteId::new(recipient, asset_commitment) with NoteDetailsCommitment::new(&recipient, &assets).
  3. To obtain the public NoteId, call note.id(), or NoteId::new(details_commitment, &metadata).
  4. Recompute and re‑persist any stored note IDs — 0.14 ids do not roundtrip, and an id now changes if metadata changes.

NoteMetadataPartialNoteMetadata; 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

  1. Search/replace the type used to construct a note from NoteMetadata to PartialNoteMetadata.
  2. Replace NoteMetadataHeader with NoteMetadata (the word‑shaped metadata is NoteMetadata::to_metadata_word()).
  3. Replace .with_attachment(a) with a NoteAttachments::new(vec![...]) collection and Note::with_attachments(...).
  4. 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_attachmentadd_attachment (and set_word_attachmentadd_word_attachment, set_array_attachmentadd_attachment_from_memory). They append instead of overwriting, and the stack signature dropped the attachment_kind field.
  • note::extract_attachment_info_from_metadatametadata_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

  1. 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.
  2. Replace extract_attachment_info_from_metadata with metadata_into_attachment_schemes.
  3. 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

  1. Drop any hard‑coded 0b01 / 0b10 note‑type bit literals; use the NoteType variants.
  2. 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).
  3. 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(&note_details, &metadata);

Migration Steps

  1. Thread the metadata word and attachments commitment into every Nullifier::new call.
  2. Replace Nullifier::from(&details) / (&details).into() with Nullifier::from_details_and_metadata(&details, &metadata).
  3. 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

  1. Cap note asset lists at 64; split larger payloads across multiple notes.
  2. 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

  1. Stop reading/writing the removed payback_attachment / attachment storage fields on swap/mint notes.
  2. 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

  1. Wrap u64 amounts you pass into faucet/asset constructors in AssetAmount (AssetAmount::from(n) for small ints, AssetAmount::try_from(n) for u64).
  2. Unwrap AssetAmount back to u64 with .as_u64() / u64::from(_) where a raw integer is needed.
  3. Replace vault.get_balance(faucet_id) with vault.get_balance(AssetVaultKey::new_fungible(faucet_id, callback_flag)).
  4. 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

  1. Replace BasicFungibleFaucet / NetworkFungibleFaucet imports with FungibleFaucet.
  2. Switch construction to FungibleFaucet::builder() with the required setters name, symbol, decimals, max_supply.
  3. 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

  1. Where you constructed a raw vault key word, use AssetVaultKey::new_fungible / AssetVaultKey::new.
  2. 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

  1. Rename native_asset_idfee_faucet_id at the constructor, field, and getter.
  2. 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

  1. Update the binding type of tx_script.root() to TransactionScriptRoot.
  2. Insert .into() / Word::from(root) where a Word is required.

ProvenBatch::newnew_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

  1. Rename ProvenBatch::newProvenBatch::new_unchecked.
  2. 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

  1. Replace get_account_proof(...) calls with get_account(account_id, GetAccountRequest::new()....); move each positional arg onto the corresponding builder method.
  2. Replace match FetchedAccount { Public | Private } on get_account_details with Option<Account> handling; route private‑account commitment lookups through get_account.
  3. 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

  1. Replace sync_chain_mmr(_, None) with sync_chain_mmr(_, SyncTarget::CommittedChainTip); map any explicit Some(n) to SyncTarget::BlockNumber(n).
  2. Replace check_nullifiers with sync_nullifiers and adapt to Vec<NullifierUpdate> (drop the SmtProof path).
  3. 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.
  4. Add include_proof to get_block_by_number calls (false unless you need the block proof).
  5. 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(&note_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

  1. Change the bound type of import_notes / sync_note_transport results and the SyncSummary::new_private_notes field from NoteId to NoteDetailsCommitment.
  2. 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

  1. Replace BasicFungibleFaucet::new(symbol, decimals, max_supply) with FungibleFaucet::builder()....build(), wrapping max_supply in AssetAmount::new(..).
  2. Replace the standalone mint‑policy component with a TokenPolicyManager configured via with_mint_policy / with_burn_policy, installed with .with_components(policy_manager).
  3. 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

  1. Thread a NoteAttachments (or NoteAttachments::default()) into every InputNoteRecord::new call as the second argument.
  2. 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

  1. Drop the .blocks / .transactions field access — the methods return the collections directly.
  2. 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

  1. Handle the OptionNone 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

  1. Replace NoteAttachmentKind / NoteMetadataHeader imports with NoteAttachmentHeader / NoteAttachments / PartialNoteMetadata as needed.
  2. 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

  1. 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

  1. Drop the is_mutable argument.
  2. 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

  1. 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

  1. Replace every AccountStorageMode.network() with .public() or .private().
  2. Replace StorageMode.Network / the "network" string with "public" or "private".
  3. 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

  1. Move accountId.isFaucet() / .isRegularAccount() onto the materialised Account. You need the full Account, not just its AccountId.
  2. Remove accountId.isNetwork(), account.isNetwork(), and account.isUpdatable() — they have no replacement.
  3. 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

  1. Replace storageMode: "network" with "public" or "private" in every createWallet / createFaucet call.
  2. 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

  1. Replace NoteAttachment.newWord(scheme, word) with NoteAttachment.fromWord(scheme, word) and newArray(scheme, felts) with fromWords(scheme, words).
  2. Replace att.asWord() / att.asArray() / att.attachmentKind() reads with att.toWords() and decode the Word[] yourself.
  3. Replace noteMetadata.attachment() with inputNoteRecord.attachments().
  4. 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

  1. Guard every record.id() use against undefined (partial notes have no ID).
  2. Replace noteMetadata.attachment() reads with record.attachments().
  3. 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

  1. Treat the return value as a hex string; drop any .toString() / NoteId method calls on it.
  2. If you need a NoteId, wrap the result with NoteId.fromHex(hex).
  3. 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

  1. Reduce or validate values to the field modulus before constructing Felt / Word, or catch the thrown error.
  2. 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

  1. Always pass an explicit blockTo to syncNotes.
  2. 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

  1. Replace the try/catch "not found" path with an === undefined check.
  2. 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

  1. Replace fetched.header.id() with fetched.noteId and fetched.header.metadata() with fetched.metadata.
  2. Remove any code that reconstructs a NoteHeader from a FetchedNote.
  3. 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

  1. No call‑site change for creating faucets — the JS signature is the same.
  2. Re‑create faucets under 0.15 if you need BasicFungibleFaucetComponent.fromAccount(...) to introspect them.
  3. Keep passing non_fungible = false; true still throws "Non‑fungible faucets are not supported yet".

(Web) idxdb-store: committedNoteIdscommittedNoteTagSources

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

  1. Rename committedNoteIdscommittedNoteTagSources everywhere you construct or read JsStateSyncUpdate.
  2. 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

  1. Point getAccountProof callers at a 0.15 node (the underlying RPC was renamed/reshaped).
  2. 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.proveTransactionWithProverproveTransaction

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

  1. Rename proveTransactionWithProver(txResult, prover) to proveTransaction(txResult, prover).
  2. 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

  1. 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 nullifierInputNoteRecord.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

  1. 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

  1. Replace --account-type <regular/faucet variant> with --account-type private|public.
  2. 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

  1. Rename the section to [fungible-faucet-metadata] and switch to typed scalars (decimals/max_supply are integers, not quoted strings).
  2. 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

  1. Build the bech32 address first with address encode <ACCOUNT_ID> <INTERFACE> [TAG_LEN].
  2. 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

  1. Search/replace the four procedure names per the table.
  2. 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

  1. Remove the drop / movup+drop that cleared the echoed dest_ptr after get_assets / get_storage.
  2. After get_storage, the NOTE_STORAGE_COMMITMENT word is no longer on the stack — delete the dropw that consumed it.
  3. 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

  1. Replace every adv_push.N with N adv_push instructions (or repeat.N adv_push end).
  2. Where you previously used adv_push.4 to fetch a word, consider adv_pushw.
  3. 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

  1. 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

  1. Split your Host impl into a BaseHost impl (label/source resolution) plus a SyncHost impl with plain (non‑async) get_mast_forest / on_event.
  2. If you call the sync entry points, pass a SyncHost.
  3. Replace destructuring of an ExecutionTrace return with ExecutionOutput accessors; build a trace explicitly only when needed.
  4. 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

  1. Stop calling ProvingOptions::with_execution_options(...); pass ExecutionOptions as its own argument.
  2. Ensure the host you pass to prove_sync implements SyncHost.
  3. 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

  1. If you seed very large advice maps up front, split the data or stream it in during execution.
  2. 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

  1. If you wrapped assembly in panic‑catching logic for malformed inputs, replace it with normal Result/Report error handling.
  2. 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

  1. 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.20.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

  1. Re‑assemble every .masl / .masp package and re‑serialize any cached MastForest blobs from source under 0.23.
  2. If you persisted MAST forests or packages to disk or a database, invalidate and regenerate them.
  3. 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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions