Skip to content

v0.13 migration guide

Bobbin Threadbare edited this page Jan 27, 2026 · 3 revisions

This guide covers all breaking changes you need to migrate an application to Miden 0.13.0. 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 or web client
  • interacting with storage, auth, or RPCs

this document is for you.

At a Glance

Big themes in 0.13:

  • Storage is now name-based, not index-based
  • Notes use attachments, not overloaded tags. Network-account notes use a standardized attachment
  • Input notes are handled as full notes, not IDs
  • Authentication APIs are unified and explicit

If you only skim one section, skim Account, Notes, and Transactions.

Imports and Dependencies

Crate and Dependency Renames

Summary

The core types moved to miden-protocol and standards/scripts to miden-standards. Miden VM dependencies are now 0.20 and miden-crypto is 0.19, and error types are no longer re-exported at the crate root.

Affected Code

Cargo.toml:

- miden-objects = "0.12"
- miden-lib = "0.12"
- miden-assembly = "0.19"
- miden-core = "0.19"
- miden-crypto = "0.18.2"
+ miden-protocol = "0.13"
+ miden-standards = "0.13"
+ miden-assembly = "0.20"
+ miden-core = "0.20"
+ miden-crypto = "0.19"

Rust:

// Before (0.12.4)
use miden_objects::account::AccountId;
use miden_objects::AccountError;
use miden_lib::note::create_p2id_note;

// After (0.13.0)
use miden_protocol::account::AccountId;
use miden_protocol::errors::AccountError;
use miden_standards::note::create_p2id_note;

Migration Steps

  1. Replace miden-objects with miden-protocol and miden-lib with miden-standards in Cargo.toml and imports.
  2. Bump direct Miden VM dependencies to 0.20 and miden-crypto to 0.19 if you depend on them directly.
  3. Update error imports to miden_protocol::errors::*.
  4. Run cargo check to catch any remaining import path mismatches.

Common Errors

Error Message Cause Solution
unresolved import miden_objects Crate renamed Replace with miden_protocol or miden_standards and update Cargo.toml.
unresolved import miden_protocol::AccountError Errors are no longer re-exported Import from miden_protocol::errors::AccountError.
failed to select a version for miden-assembly Direct dependency still pinned to 0.19 Update miden-assembly (and other Miden VM crates) to 0.20.

Core library rename (StdLibrary → CoreLibrary)

Summary

The MASM standard library was renamed to miden::core, and the Rust wrapper moved from StdLibrary (miden-stdlib) to CoreLibrary (miden-core-lib).

Affected Code

Rust:

- use miden_stdlib::StdLibrary;
+ use miden_core_lib::CoreLibrary;

MASM:

- use std::crypto::hashes::rpo
+ use miden::core::crypto::hashes::rpo256

Migration Steps

  1. Replace StdLibrary with CoreLibrary and update dependencies to miden-core-lib.
  2. Replace MASM imports from std:: to miden::core::.

Common Errors

Error Message Cause Solution
error[E0432]: unresolved import miden_stdlib::StdLibrary Crate and type renamed. Use miden_core_lib::CoreLibrary.
unknown module std::... MASM namespace renamed. Use miden::core::....

Account Changes

Named Storage Slots

Summary

Storage slots are now identified by StorageSlotName instead of numeric indices. Also AccountStorage and AccountStorageDelta APIs are name-based.

Affected Code

Rust Client:

// Before (0.12.x)
use miden_client::account::{StorageMap, StorageSlot};

fn storage_slots(storage_map: StorageMap) -> Vec<StorageSlot> {
    vec![StorageSlot::Map(storage_map)]
}
// After (0.13.0)
use miden_client::account::{StorageMap, StorageSlot, StorageSlotName};

fn storage_slots(storage_map: StorageMap) -> Vec<StorageSlot> {
    let slot_name = StorageSlotName::new("miden::example::map")
        .expect("slot name must be valid");
    vec![StorageSlot::with_map(slot_name, storage_map)]
}

TypeScript Client:

// Before (0.12.x)
import {
  AccountComponent,
  StorageMap,
  StorageSlot,
  WebClient,
} from "@miden-sdk/miden-sdk";

const client = await WebClient.createClient();
const builder = client.createCodeBuilder();
const storageMap = new StorageMap();
const slot = StorageSlot.map(storageMap);

const component = AccountComponent.compile(accountCode, builder, [slot]);
const value = account.storage().getMapItem(1, key);
// After (0.13.0)
import {
  AccountComponent,
  StorageMap,
  StorageSlot,
  WebClient,
} from "@miden-sdk/miden-sdk";

const client = await WebClient.createClient();
const builder = client.createCodeBuilder();
const storageMap = new StorageMap();
const slotName = "miden::example::map";
const slot = StorageSlot.map(slotName, storageMap);

const componentCode = builder.compileAccountComponentCode(accountCode);
const component = AccountComponent.compile(componentCode, [slot]);

const value = account.storage().getMapItem(slotName, key);
const slotNames = account.storage().getSlotNames();

Rust (storage access):

// Before (0.12.4)
use miden_objects::account::{AccountStorage, StorageMap, StorageSlot};
use miden_objects::Word;

let mut storage = AccountStorage::new(vec![
    StorageSlot::Value(Word::from([1, 2, 3, 4u32])),
    StorageSlot::Map(StorageMap::new()),
])?;
let value = storage.get_item(0)?;
storage.set_map_item(1, key, value)?;

// After (0.13.0)
use miden_protocol::account::{AccountStorage, StorageMap, StorageSlot, StorageSlotName};
use miden_protocol::Word;

let value_slot = StorageSlotName::new("demo::value")?;
let map_slot = StorageSlotName::new("demo::map")?;

let mut storage = AccountStorage::new(vec![
    StorageSlot::with_value(value_slot.clone(), Word::from([1, 2, 3, 4u32])),
    StorageSlot::with_map(map_slot.clone(), StorageMap::new()),
])?;
let value = storage.get_item(&value_slot)?;
storage.set_map_item(&map_slot, key, value)?;

Rust (storage deltas):

// Before (0.12.4)
use miden_objects::account::AccountStorageDelta;

let mut delta = AccountStorageDelta::new();
delta.set_item(0, new_value);
delta.set_map_item(1, key, value);

// After (0.13.0)
use miden_protocol::account::{AccountStorageDelta, StorageSlotName};

let mut delta = AccountStorageDelta::new();
let value_slot = StorageSlotName::new("demo::value")?;
let map_slot = StorageSlotName::new("demo::map")?;
delta.set_item(value_slot.clone(), new_value)?;
delta.set_map_item(map_slot.clone(), key, value)?;

Migration Steps

  1. Define storage slots with StorageSlotName::new and build StorageSlot values with with_value or with_map.
  2. Replace index-based slots with named slots (StorageSlotName in Rust, string names in Web).
  3. Update AccountStorageDelta usage to pass StorageSlotName.
  4. Update any storage accessors (getItem, getMapItem, getMapEntries) to pass slot names.

Common Errors

Error Message Cause Solution
expected &StorageSlotName, found u8 APIs now require slot names Replace numeric indices with StorageSlotName values.
StorageSlotNameNotFound Slot name does not exist in storage Ensure you build storage with the same StorageSlotName used at access time.
expected StorageSlotName Creating slots without names Use StorageSlotName::new("namespace::slot")

MASM set-map-item change

Summary

native_account::set_map_item now takes slot IDs and returns only the old value.

Affected code

MASM (kernel call):

# Before (0.12.4)
# Inputs: [index, KEY, VALUE]
exec.native_account::set_map_item
# => [OLD_MAP_ROOT, OLD_MAP_VALUE]

# After (0.13.0)
# Inputs: [slot_id_prefix, slot_id_suffix, KEY, VALUE]
exec.native_account::set_map_item
# => [OLD_VALUE]

Migration Steps

  1. Handle Result from set_item and set_map_item.
  2. If you call native_account::set_map_item, pass slot ID prefix/suffix and remove any logic that expects the old map root on the stack.

Common Errors

| stack underflow in MASM around set_map_item | Code still expects OLD_MAP_ROOT | Update stack handling to only consume OLD_VALUE. |

(Rust) No generic filesystem keystore

Summary

The filesystem keystore is also no longer generic over RNG.

// Before (0.12.x)
use miden_client::builder::ClientBuilder;
use miden_client::keystore::FilesystemKeyStore;

let builder = ClientBuilder::<FilesystemKeyStore<_>>::new();
// After (0.13.0)
use miden_client::builder::ClientBuilder;
use miden_client::keystore::FilesystemKeyStore;

let builder = ClientBuilder::<FilesystemKeyStore>::new();

Migration Steps

  1. Update account component compilation to use CodeBuilder.compileAccountComponentCode and pass the resulting AccountComponentCode.
  2. If you used FilesystemKeyStore<_> generics, drop the RNG parameter.

Common Errors

Error Message Cause Solution
type annotations needed for FilesystemKeyStore Removed RNG generic Use FilesystemKeyStore without type params

(WebClient) Account component compilation requires AccountComponentCode

Summary

The web binding for AccountComponent.compile now requires a compiled AccountComponentCode.

TypeScript:

// Before (0.12.x)
import {
  AccountComponent,
  StorageMap,
  StorageSlot,
  WebClient,
} from "@miden-sdk/miden-sdk";

const client = await WebClient.createClient();
const builder = client.createCodeBuilder();
const storageMap = new StorageMap();
const slot = StorageSlot.map(storageMap);

const component = AccountComponent.compile(accountCode, builder, [slot]);
const value = account.storage().getMapItem(1, key);
// After (0.13.0)
import {
  AccountComponent,
  StorageMap,
  StorageSlot,
  WebClient,
} from "@miden-sdk/miden-sdk";

const client = await WebClient.createClient();
const builder = client.createCodeBuilder();
const storageMap = new StorageMap();
const slotName = "miden::example::map";
const slot = StorageSlot.map(slotName, storageMap);

const componentCode = builder.compileAccountComponentCode(accountCode);
const component = AccountComponent.compile(componentCode, [slot]);

const value = account.storage().getMapItem(slotName, key);
const slotNames = account.storage().getSlotNames();

Migration Steps

  1. Update account component compilation to use CodeBuilder.compileAccountComponentCode and pass the resulting AccountComponentCode.

Common Errors

| AccountComponent.compile takes 2 arguments | Old binding passed CodeBuilder directly | Compile to AccountComponentCode first |

Account Component Templates Replaced by Storage Schemas

Summary

AccountComponentTemplate is removed in favor of metadata-driven component creation via StorageSchema. InitStorageData now supports native types and map entries keyed by StorageSlotName.

Affected Code

Rust (template removal):

// Before (0.12.4)
use miden_objects::account::{AccountComponent, AccountComponentTemplate, InitStorageData};
use miden_objects::vm::Package;

let template = AccountComponentTemplate::try_from(package.clone())?;
let component = AccountComponent::from_template(&template, &init)?;

// After (0.13.0)
use miden_protocol::account::{AccountComponent, component::InitStorageData};
use miden_protocol::vm::Package;

let init = InitStorageData::default();
let component = AccountComponent::from_package(&package, &init)?;

Rust (storage schema types):

use miden_protocol::account::StorageSlotName;
use miden_protocol::account::component::{
    FeltSchema,
    SchemaTypeId,
    StorageSchema,
    StorageSlotSchema,
    ValueSlotSchema,
    WordSchema,
};

let slot_name = StorageSlotName::new("demo::value")?;
let schema = StorageSchema::new([(
    slot_name,
    StorageSlotSchema::Value(ValueSlotSchema::new(
        None,
        WordSchema::new_value([
            FeltSchema::new_void(),
            FeltSchema::new_void(),
            FeltSchema::new_void(),
            FeltSchema::new_typed(SchemaTypeId::native_felt(), "amount"),
        ]),
    )),
)])?;

Rust (storage schema and init data):

// Before (0.12.4)
use miden_objects::account::{InitStorageData, StorageValueName};

let init = InitStorageData::new(
    [(StorageValueName::new("demo::value")?, "300".to_string())],
    [],
);

// After (0.13.0)
use miden_protocol::account::StorageSlotName;
use miden_protocol::account::component::{InitStorageData, StorageValueName};
use miden_protocol::Felt;

let mut init = InitStorageData::default();
let slot_name = StorageSlotName::new("demo::value")?;
let value_name = StorageValueName::from_slot_name(&slot_name);
init.set_value(value_name, Felt::new(300))?;

Migration Steps

  1. Replace AccountComponentTemplate usage with AccountComponent::from_package or AccountComponent::from_library.
  2. Define storage layouts with StorageSchema and related schema types (StorageSlotSchema, ValueSlotSchema, WordSchema).
  3. Update InitStorageData to use native types (e.g., Felt, Word) and map entries keyed by StorageSlotName.
  4. Handle new validation errors from InitStorageData::new and InitStorageData::set_value.

Common Errors

Error Message Cause Solution
no method named from_template AccountComponentTemplate removed Use AccountComponent::from_package or from_library.
expected BTreeMap<StorageSlotName, ...> Map init entries now keyed by slot name Use StorageSlotName as the map key.
ConflictingEntries from InitStorageData Mixing slot-level values and field values Pick either a full slot value or field-level values for a slot.

Account Procedure Roots and Index Maps

Summary

AccountProcedureInfo is replaced by AccountProcedureRoot with no storage offset/size. AccountProcedureIndexMap::new is infallible, and duplicate procedure roots across components are now de-duplicated instead of erroring. ACL components rename tracked_procedure_roots_slot to trigger_procedure_roots_slot.

Affected Code

Rust (procedure roots):

// Before (0.12.4)
use miden_objects::account::AccountProcedureInfo;

let info = AccountProcedureInfo::new(mast_root, 0, 3)?;
let root = *info.mast_root();

// After (0.13.0)
use miden_protocol::account::AccountProcedureRoot;

let root = AccountProcedureRoot::from_raw(mast_root);
let root_word: miden_protocol::Word = root.into();

Rust (procedure index map):

// Before (0.12.4)
let index_map = AccountProcedureIndexMap::new([account.code()])?;
let proc_idx = index_map.get_proc_index(process)?;

// After (0.13.0)
let index_map = AccountProcedureIndexMap::new([account.code()]);
let proc_idx = index_map.get_proc_index(code_commitment, procedure_root)?;

Rust (ACL slot rename):

// Before (0.13.0 pre-change)
AuthEcdsaK256KeccakAcl::tracked_procedure_roots_slot();

// After (0.13.0)
AuthEcdsaK256KeccakAcl::trigger_procedure_roots_slot();

Migration Steps

  1. Replace AccountProcedureInfo with AccountProcedureRoot and remove storage offset/size logic.
  2. Update AccountProcedureIndexMap::new call sites to remove error handling and pass the new inputs to get_proc_index.
  3. If you relied on duplicate procedure roots to raise AccountComponentDuplicateProcedureRoot, add your own validation before composing components.
  4. Update ACL helper method name to trigger_procedure_roots_slot.

Common Errors

Error Message Cause Solution
use of undeclared type AccountProcedureInfo Type removed Use AccountProcedureRoot and update code accordingly.
this function takes 2 arguments but 1 argument was supplied get_proc_index signature changed Pass code_commitment and procedure_root.
no method named tracked_procedure_roots_slot Method renamed Use trigger_procedure_roots_slot.

(WebClient) Authentication and key management updates

Summary

WebClient auth APIs now take the AuthScheme enum instead of numeric IDs, SecretKey has been removed in favor of AuthSecretKey, and addAccountSecretKeyToWebStore now requires an account ID. Scheme-specific public key methods on AuthSecretKey were removed; use getPublicKeyAsWord instead.

Affected Code

TypeScript:

// Before (0.12.x)
import {
  AccountComponent,
  AccountStorageMode,
  SecretKey,
  WebClient,
} from "@miden-sdk/miden-sdk";

const client = await WebClient.createClient();
const wallet = await client.newWallet(AccountStorageMode.public(), true, 0, seed);

const secretKey = SecretKey.rpoFalconWithRNG(seed);
const commitment = secretKey.getRpoFalcon512PublicKeyAsWord();
const authComponent = AccountComponent.createAuthComponentFromCommitment(commitment, 0);

await client.addAccountSecretKeyToWebStore(secretKey);
// After (0.13.0)
import {
  AccountComponent,
  AccountStorageMode,
  AuthScheme,
  AuthSecretKey,
  WebClient,
} from "@miden-sdk/miden-sdk";

const client = await WebClient.createClient();
const wallet = await client.newWallet(
  AccountStorageMode.public(),
  true,
  AuthScheme.AuthRpoFalcon512,
  seed
);

const secretKey = AuthSecretKey.rpoFalconWithRNG(seed);
const commitment = secretKey.getPublicKeyAsWord();
const authComponent = AccountComponent.createAuthComponentFromCommitment(
  commitment,
  AuthScheme.AuthRpoFalcon512
);
const fromSecret = AccountComponent.createAuthComponentFromSecretKey(secretKey);

await client.addAccountSecretKeyToWebStore(wallet.id(), secretKey);
const commitments = await client.getPublicKeyCommitmentsOfAccount(wallet.id());

Migration Steps

  1. Replace numeric auth scheme IDs with AuthScheme enum values.
  2. Replace SecretKey with AuthSecretKey and update calls to createAuthComponentFromSecretKey.
  3. Replace getRpoFalcon512PublicKeyAsWord and getEcdsaK256KeccakPublicKeyAsWord with getPublicKeyAsWord.
  4. Pass an account ID to addAccountSecretKeyToWebStore and use getPublicKeyCommitmentsOfAccount when you need associated commitments.

Common Errors

Error Message Cause Solution
SecretKey is not defined Model removed Use AuthSecretKey
Argument of type number is not assignable to AuthScheme Numeric scheme IDs removed Use AuthScheme.AuthRpoFalcon512 or AuthScheme.AuthEcdsaK256Keccak
createAuthComponent is not a function Method removed Use createAuthComponentFromSecretKey

DSA signature encoding helpers

Summary

Signature encoding helpers were normalized under miden-core-lib::dsa::* with consistent sign/encode_signature naming.

Affected Code

Rust:

// Before (0.19.x)
use miden_stdlib::falcon_sign;
use miden_core::{Felt, Word};

let sig: Vec<Felt> = falcon_sign(&secret_key_felts, msg).expect("valid key");
// After (0.20.x)
use miden_core_lib::dsa::falcon512_rpo;
use miden_core::{Felt, Word};

let sig: Vec<Felt> = falcon512_rpo::sign(&secret_key, msg).expect("valid key");

Migration Steps

  1. Replace miden_stdlib::falcon_sign with miden_core_lib::dsa::falcon512_rpo::sign.
  2. For ECDSA and EdDSA, use miden_core_lib::dsa::ecdsa_k256_keccak::encode_signature and miden_core_lib::dsa::eddsa_ed25519::encode_signature to build advice inputs.

Common Errors

Error Message Cause Solution
error[E0432]: unresolved import miden_stdlib::falcon_sign Helper moved and renamed. Use miden_core_lib::dsa::falcon512_rpo::sign.

Note Changes

Note Metadata Attachments and Tag Semantics

Summary

NoteMetadata no longer stores aux or NoteExecutionHint. Metadata attachments are now represented by NoteAttachment, and NoteTag is a plain u32 without built-in validation. Notes targeting network accounts now use the standardized NetworkAccountTarget attachment instead of NoteTag::NetworkAccountId.

In the WebClient Note metadata and tagging APIs is therefore simplified. NoteTag.fromAccountId is now withAccountTarget, NoteExecutionMode was removed, NoteMetadata no longer accepts execution hints in the constructor, and NoteAttachment now uses NoteAttachmentScheme with asWord/asArray accessors.

Affected Code

Rust:

// Before (0.12.4)
use miden_objects::note::{NoteExecutionHint, NoteMetadata, NoteTag, NoteType};

let tag = NoteTag::from_account_id(target);
let metadata = NoteMetadata::new(sender, note_type, tag, NoteExecutionHint::always(), aux)?;

// After (0.13.0)
use miden_protocol::note::{NoteAttachment, NoteAttachmentScheme, NoteMetadata, NoteTag, NoteType};
use miden_protocol::{Felt, Word};

let tag = NoteTag::with_account_target(target);
let attachment = NoteAttachment::new_word(
    NoteAttachmentScheme::none(),
    Word::from([aux, Felt::ZERO, Felt::ZERO, Felt::ZERO]),
);
let metadata = NoteMetadata::new(sender, note_type, tag).with_attachment(attachment);

Rust (network account target via attachment):

// Before (0.12.4)
use miden_objects::note::{NoteExecutionHint, NoteMetadata, NoteTag, NoteType};

let tag = NoteTag::NetworkAccountId(target);
let metadata = NoteMetadata::new(
    sender,
    NoteType::Public,
    tag,
    NoteExecutionHint::after_block(block_num),
    aux,
)?;

// After (0.13.0)
use miden_protocol::note::{NoteExecutionHint, NoteMetadata, NoteTag, NoteType};
use miden_standards::note::NetworkAccountTarget;

let tag = NoteTag::with_account_target(target);
let attachment = NetworkAccountTarget::new(target, NoteExecutionHint::after_block(block_num))?
    .into();
let metadata = NoteMetadata::new(sender, NoteType::Public, tag).with_attachment(attachment);

Rust client:

// Before (0.12.x)
use miden_client::note::{
    NoteExecutionHint,
    NoteMetadata,
    NoteTag,
    NoteType,
};
use miden_client::Felt;

let tag = NoteTag::from_account_id(target_account_id);
let metadata = NoteMetadata::new(
    sender_account_id,
    NoteType::Private,
    tag,
    NoteExecutionHint::none(),
    Felt::default(),
)
.expect("valid metadata");
// After (0.13.0)
use miden_client::note::{
    NoteAttachment,
    NoteAttachmentScheme,
    NoteMetadata,
    NoteTag,
    NoteType,
};

let tag = NoteTag::with_account_target(target_account_id);
let metadata = NoteMetadata::new(sender_account_id, NoteType::Private, tag);

let scheme = NoteAttachmentScheme::new(42);
let attachment = NoteAttachment::new_word(scheme, word);
let metadata_with_attachment = metadata.with_attachment(attachment);

WebClient:

// Before (0.12.x)
import {
  NoteAttachment,
  NoteExecutionHint,
  NoteExecutionMode,
  NoteMetadata,
  NoteTag,
  NoteType,
} from "@miden-sdk/miden-sdk";

const tag = NoteTag.fromAccountId(targetAccountId, NoteExecutionMode.newLocal());
const metadata = new NoteMetadata(
  senderAccountId,
  NoteType.Private,
  tag,
  NoteExecutionHint.none()
);
const attachment = NoteAttachment.newWord(42, word);
// After (0.13.0)
import {
  NoteAttachment,
  NoteAttachmentScheme,
  NoteExecutionHint,
  NoteMetadata,
  NoteTag,
  NoteType,
} from "@miden-sdk/miden-sdk";

const tag = NoteTag.withAccountTarget(targetAccountId);
const metadata = new NoteMetadata(senderAccountId, NoteType.Private, tag);

const scheme = new NoteAttachmentScheme(42);
const attachment = NoteAttachment.newWord(scheme, word);
const metadataWithAttachment = metadata.withAttachment(attachment);

// Optional: target a network account via attachment.
const networkAttachment = NoteAttachment.newNetworkAccountTarget(
  targetAccountId,
  NoteExecutionHint.none()
);

Migration Steps

  1. Drop aux and NoteExecutionHint parameters from NoteMetadata::new calls.
  2. If you need auxiliary data, encode it into a NoteAttachment (word or array).
  3. Replace NoteTag::from_account_id with NoteTag::with_account_target and use NoteTag::new for custom tags.
  4. If you used NoteTag::NetworkAccountId for network-account notes, replace it with a NetworkAccountTarget attachment from miden_standards::note.
  5. If you relied on tag/type validation, add your own checks before constructing metadata.

Migration Steps (WebClient)

  1. Replace NoteTag.fromAccountId with NoteTag.withAccountTarget or withCustomAccountTarget.
  2. Drop NoteExecutionMode usages; attach execution context via NoteAttachment if needed.
  3. Update NoteMetadata construction to new NoteMetadata(sender, type, tag) and add attachments with withAttachment.
  4. Wrap attachment scheme values in NoteAttachmentScheme and use asWord() / asArray() to read payloads.

Common Errors

Error Message Cause Solution
this function takes 3 arguments but 5 arguments were supplied NoteMetadata::new signature changed Remove execution_hint and aux, and use with_attachment.
no variant or associated item named from_account_id NoteTag API changed Use NoteTag::with_account_target or NoteTag::new.
type mismatch: expected NoteMetadata, found Result NoteMetadata::new is no longer fallible Remove ? and handle validation manually.

Common Errors (WebClient)

Error Message Cause Solution
NoteExecutionMode is not defined Class removed Remove it and use attachments if needed
NoteTag.fromAccountId is not a function API renamed Use NoteTag.withAccountTarget
Argument of type number is not assignable to NoteAttachmentScheme Scheme wrapper added Construct new NoteAttachmentScheme(value)

MINT Note Inputs and MAX_INPUTS_PER_NOTE

Summary

create_mint_note now takes a MintNoteInputs enum to support private and public output notes, and MAX_INPUTS_PER_NOTE increases to 1024.

Affected Code

Rust:

// Before (0.12.4)
use miden_lib::note::create_mint_note;

let note = create_mint_note(
    faucet_id,
    sender,
    recipient_digest,
    output_note_tag,
    amount,
    aux,
    output_note_aux,
    rng,
)?;

// After (0.13.0)
use miden_protocol::note::NoteAttachment;
use miden_standards::note::{create_mint_note, MintNoteInputs};

let mint_inputs = MintNoteInputs::new_private(recipient_digest, amount, output_note_tag);
let note = create_mint_note(
    faucet_id,
    sender,
    mint_inputs,
    NoteAttachment::default(),
    rng,
)?;

Migration Steps

  1. Replace the old create_mint_note parameter list with a MintNoteInputs value.
  2. Use MintNoteInputs::new_private for private outputs or MintNoteInputs::new_public for public outputs.
  3. Update any hard-coded input length limits to use miden_protocol::MAX_INPUTS_PER_NOTE.

Common Errors

Error Message Cause Solution
this function takes 5 arguments but 8 arguments were supplied create_mint_note signature changed Build a MintNoteInputs value and pass it instead.
NoteError::TooManyInputs Public output note inputs exceed the limit Ensure total inputs do not exceed MAX_INPUTS_PER_NOTE (now 1024).
cannot find type MintNoteInputs Missing miden-standards import Add miden-standards dependency and import from miden_standards::note.

Input notes API unified

Summary

Input notes are no longer split into authenticated and unauthenticated lists. Builders now accept full Note objects and the client determines authentication internally, and the WebClient consume request now accepts Note[] instead of note ID strings.

Affected Code

Rust:

// Before (0.12.x)
use miden_client::auth::TransactionAuthenticator;
use miden_client::note::NoteId;
use miden_client::transaction::TransactionRequestBuilder;
use miden_client::{Client, ClientError};

async fn build_request<AUTH: TransactionAuthenticator + Sync>(
    client: &Client<AUTH>,
    note_id: NoteId,
) -> Result<miden_client::transaction::TransactionRequest, ClientError> {
    let tx_request = TransactionRequestBuilder::new()
        .authenticated_input_notes(vec![(note_id, None)])
        .build()?;
    Ok(tx_request)
}
// After (0.13.0)
use miden_client::auth::TransactionAuthenticator;
use miden_client::note::{Note, NoteId};
use miden_client::transaction::TransactionRequestBuilder;
use miden_client::{Client, ClientError};

async fn build_request<AUTH: TransactionAuthenticator + Sync>(
    client: &Client<AUTH>,
    note_id: NoteId,
) -> Result<miden_client::transaction::TransactionRequest, ClientError> {
    let record = client.get_input_note(note_id).await?.expect("note not found");
    let note: Note = record.try_into().expect("failed to convert note record");

    let tx_request = TransactionRequestBuilder::new()
        .input_notes(vec![(note, None)])
        .build()?;
    Ok(tx_request)
}

TypeScript:

// Before (0.12.x)
import {
  NoteIdAndArgs,
  NoteIdAndArgsArray,
  TransactionRequestBuilder,
  WebClient,
} from "@miden-sdk/miden-sdk";

const client = await WebClient.createClient();
const consumeRequest = client.newConsumeTransactionRequest([noteId]);

const noteIdAndArgs = new NoteIdAndArgs(noteId, null);
const txRequest = new TransactionRequestBuilder()
  .withAuthenticatedInputNotes(new NoteIdAndArgsArray([noteIdAndArgs]))
  .build();
// After (0.13.0)
import {
  NoteAndArgs,
  NoteAndArgsArray,
  TransactionRequestBuilder,
  WebClient,
} from "@miden-sdk/miden-sdk";

const client = await WebClient.createClient();
const record = await client.getInputNote(noteId);
if (!record) {
  throw new Error(`Note with ID ${noteId} not found`);
}
const note = record.toNote();

const consumeRequest = client.newConsumeTransactionRequest([note]);

const noteAndArgs = new NoteAndArgs(note, null);
const txRequest = new TransactionRequestBuilder()
  .withInputNotes(new NoteAndArgsArray([noteAndArgs]))
  .build();

Migration Steps

  1. Replace authenticated_input_notes and unauthenticated_input_notes with input_notes.
  2. Convert NoteId values into Note objects (Rust: InputNoteRecord -> Note via try_into; Web: InputNoteRecord.toNote()).
  3. Update newConsumeTransactionRequest to pass Note[], and replace NoteIdAndArgs with NoteAndArgs.

Common Errors

Error Message Cause Solution
method not found: authenticated_input_notes Deprecated builder methods removed Use input_notes with Note values
expected Note, found NoteId Passing IDs where full notes are required Fetch the note record and convert to Note
newConsumeTransactionRequest expects Note[] API now requires notes, not strings Call getInputNote(...).toNote() first

FetchedNote and RPC note shapes refactored

Summary

Fetched notes now carry a NoteHeader for private notes and always expose the inclusion proof in the WebClient. Web FetchedNote exposes header, note, and inclusionProof, with asInputNote() for public notes.

Affected Code

Rust:

// Before (0.12.x)
use miden_client::rpc::domain::note::FetchedNote;

fn handle_note(note: FetchedNote) {
    match note {
        FetchedNote::Private(note_id, metadata, proof) => {
            let _ = (note_id, metadata, proof);
        }
        FetchedNote::Public(note, proof) => {
            let _ = (note, proof);
        }
    }
}
// After (0.13.0)
use miden_client::rpc::domain::note::FetchedNote;

fn handle_note(note: FetchedNote) {
    match note {
        FetchedNote::Private(header, proof) => {
            let note_id = header.id();
            let metadata = header.metadata();
            let _ = (note_id, metadata, proof);
        }
        FetchedNote::Public(note, proof) => {
            let _ = (note, proof);
        }
    }
}

TypeScript:

// Before (0.12.x)
const fetched = (await rpcClient.getNotesById([noteId]))[0];
if (fetched.inputNote) {
  const scriptRoot = fetched.inputNote.note().script().root();
}
// After (0.13.0)
const fetched = (await rpcClient.getNotesById([noteId]))[0];
const proof = fetched.inclusionProof;
const note = fetched.note;
if (note) {
  const scriptRoot = note.script().root();
}
const inputNote = fetched.asInputNote();

Migration Steps

  1. Update pattern matches for FetchedNote::Private to use NoteHeader.
  2. In the WebClient, replace inputNote access with note plus inclusionProof, or call asInputNote().
  3. Use header for shared access to noteId and metadata.

Common Errors

Error Message Cause Solution
pattern has 3 fields, but the corresponding tuple variant has 2 fields FetchedNote::Private shape changed Use FetchedNote::Private(header, proof)
Property 'inputNote' does not exist on type 'FetchedNote' Web shape updated Use note, inclusionProof, or asInputNote()

NoteScreener relevance replaced by NoteConsumptionStatus

Summary

NoteRelevance was removed; NoteScreener now reports NoteConsumptionStatus values, and the WebClient exposes consumption status objects rather than a single consumableAfterBlock field.

Affected Code

Rust:

// Before (0.12.x)
use miden_client::note::{NoteRelevance, NoteScreener};

let relevances = note_screener.check_relevance(&note).await?;
for (_, relevance) in relevances {
    if relevance == NoteRelevance::Now {
        // ...
    }
}
// After (0.13.0)
use miden_client::note::{NoteConsumptionStatus, NoteScreener};

let relevances = note_screener.check_relevance(&note).await?;
for (_, status) in relevances {
    match status {
        NoteConsumptionStatus::Consumable
        | NoteConsumptionStatus::ConsumableWithAuthorization => {
            // ...
        }
        NoteConsumptionStatus::ConsumableAfter(_) => {
            // ...
        }
        _ => {}
    }
}

TypeScript:

// Before (0.12.x)
const records = await client.getConsumableNotes(accountId);
const after = records[0].noteConsumability()[0].consumableAfterBlock();
// After (0.13.0)
const records = await client.getConsumableNotes(accountId);
const status = records[0].noteConsumability()[0].consumptionStatus();
const after = status.consumableAfterBlock();

Migration Steps

  1. Replace NoteRelevance with NoteConsumptionStatus in Rust logic and pattern matching.
  2. Update WebClient consumption checks to call consumptionStatus() and then consumableAfterBlock().
  3. Remove any reliance on NoteRelevance::Now / After variants.

Common Errors

Error Message Cause Solution
use of undeclared type NoteRelevance Type removed Use NoteConsumptionStatus
consumableAfterBlock is not a function API moved under consumptionStatus() Call consumptionStatus().consumableAfterBlock()

Transaction Changes

Transaction Events: IDs vs Data Extraction

Summary

TransactionEvent was renamed to TransactionEventId, and event data extraction is now handled separately inside miden-tx via TransactionEvent::extract.

Affected Code

Rust:

// Before (0.12.4)
use miden_lib::transaction::{EventId, TransactionEvent};

let event = TransactionEvent::try_from(event_id)?;
match event {
    TransactionEvent::AccountStorageAfterSetItem => {
        // read stack/process data manually
    }
    _ => {}
}

// After (0.13.0)
use miden_protocol::transaction::TransactionEventId;
use crate::host::TransactionEvent; // inside miden-tx host implementation

let _event_id = TransactionEventId::try_from(event_id)?;
if let Some(event) = TransactionEvent::extract(base_host, process)? {
    match event {
        TransactionEvent::AccountStorageAfterSetItem { slot_name, new_value } => {
            // use extracted data directly
        }
        _ => {}
    }
}

Migration Steps

  1. Replace TransactionEvent uses with TransactionEventId for ID-level checks.
  2. If you implement a custom host in miden-tx, use TransactionEvent::extract to get data-rich events.
  3. Remove manual stack parsing for events that now surface structured data.

Common Errors

Error Message Cause Solution
unresolved import miden_lib::transaction::TransactionEvent Enum renamed and moved Use miden_protocol::transaction::TransactionEventId.
no function or associated item named extract Using old event API Use TransactionEvent::extract from miden-tx host code.
pattern requires 0 fields but 2 fields were supplied Event variants now carry data Match on the new data-carrying variants.

Client Changes

(Rust) NodeRpcClient account proof API changed

Summary

The batch get_account_proofs API is replaced with a single-account call that requires AccountStateAt, and the known code parameter is now optional per account.

Affected Code

Rust:

// Before (0.12.x)
use std::collections::{BTreeMap, BTreeSet};

use miden_client::account::{AccountCode, AccountId};
use miden_client::block::BlockNumber;
use miden_client::rpc::domain::account::AccountProof;
use miden_client::rpc::NodeRpcClient;
use miden_client::transaction::ForeignAccount;

async fn fetch_proofs(
    rpc: &dyn NodeRpcClient,
    accounts: BTreeSet<ForeignAccount>,
    known_codes: BTreeMap<AccountId, AccountCode>,
) -> Result<(BlockNumber, Vec<AccountProof>), miden_client::rpc::RpcError> {
    rpc.get_account_proofs(&accounts, known_codes).await
}
// After (0.13.0)
use miden_client::account::AccountCode;
use miden_client::block::BlockNumber;
use miden_client::rpc::domain::account::AccountProof;
use miden_client::rpc::{AccountStateAt, NodeRpcClient};
use miden_client::transaction::ForeignAccount;

async fn fetch_proof(
    rpc: &dyn NodeRpcClient,
    account: ForeignAccount,
    known_code: Option<AccountCode>,
) -> Result<(BlockNumber, AccountProof), miden_client::rpc::RpcError> {
    rpc.get_account(account, AccountStateAt::ChainTip, known_code).await
}

Migration Steps

  1. Replace get_account_proofs with get_account and call it per ForeignAccount.
  2. Pass the desired state via AccountStateAt::ChainTip or AccountStateAt::Block.
  3. Update implementations of NodeRpcClient to match the new signature and return type.

Common Errors

Error Message Cause Solution
method not found: get_account_proofs Old trait method removed Use get_account and loop
missing argument: account_state New AccountStateAt required Pass AccountStateAt::ChainTip or AccountStateAt::Block
expected AccountProof, found Vec<AccountProof> Return type changed Handle single proof per call

(Rust) Client RNG must be Send and Sync

Summary

The client RNG must now be Send + Sync via the ClientFeltRng marker and ClientRngBox alias so Client can be Send + Sync.

Affected Code

Rust:

// Before (0.12.x)
use miden_client::builder::ClientBuilder;
use miden_client::crypto::{FeltRng, RpoRandomCoin};

let rng: Box<dyn FeltRng> = Box::new(RpoRandomCoin::new([0u8; 32]));
let builder = ClientBuilder::new().rng(rng);
// After (0.13.0)
use miden_client::builder::ClientBuilder;
use miden_client::crypto::RpoRandomCoin;
use miden_client::ClientRngBox;

let rng: ClientRngBox = Box::new(RpoRandomCoin::new([0u8; 32]));
let builder = ClientBuilder::new().rng(rng);

Migration Steps

  1. Ensure your RNG implements Send + Sync.
  2. Wrap the RNG in ClientRngBox and pass it to ClientBuilder::rng.

Common Errors

Error Message Cause Solution
the trait bound ...: Send + Sync is not satisfied RNG type not thread-safe Use a Send + Sync RNG or wrap it safely

(Rust) CLI swap payback_note_type removed

Summary

The CLI swap command no longer accepts a payback_note_type argument; the payback note type is now fixed.

Affected Code

CLI:

# Before (0.12.x)
miden-client swap \
  --offered-asset 10::0x... \
  --requested-asset 5::0x... \
  --note-type public \
  --payback-note-type public
# After (0.13.0)
miden-client swap \
  --offered-asset 10::0x... \
  --requested-asset 5::0x... \
  --note-type public

Migration Steps

  1. Remove --payback-note-type from swap command invocations or scripts.

Common Errors

Error Message Cause Solution
unexpected argument '--payback-note-type' Flag removed Drop the flag from the command

(WebClient) IndexedDB naming for multiple instances

Summary

The WebClient store is now named, so multiple clients can coexist in the same browser. WebClient.createClient and createClientWithExternalKeystore accept an optional store name before callback arguments.

Affected Code

TypeScript:

// Before (0.12.x)
const client = await WebClient.createClient(rpcUrl, noteTransportUrl, seed);

const clientWithKeystore = await WebClient.createClientWithExternalKeystore(
  rpcUrl,
  noteTransportUrl,
  seed,
  getKeyCb,
  insertKeyCb,
  signCb
);
// After (0.13.0)
const client = await WebClient.createClient(
  rpcUrl,
  noteTransportUrl,
  seed,
  "app-db"
);

const clientWithKeystore = await WebClient.createClientWithExternalKeystore(
  rpcUrl,
  noteTransportUrl,
  seed,
  "app-db",
  getKeyCb,
  insertKeyCb,
  signCb
);

Migration Steps

  1. Add a store name to createClient when you need multiple instances in one origin.
  2. Shift external keystore callback arguments one position to the right and pass storeName first.

Common Errors

Error Message Cause Solution
Expected 7 arguments, but got 6 Missing storeName in createClientWithExternalKeystore Insert the store name before callbacks

(WebClient) Block numbers are numeric in web APIs and IndexedDB

Summary

WebClient transaction interfaces and IndexedDB storage now use numeric block numbers instead of strings.

Affected Code

TypeScript:

// Before (0.12.x)
const summary = await client.syncState();
const blockNum = parseInt(summary.blockNum(), 10);
// After (0.13.0)
const summary = await client.syncState();
const blockNum = summary.blockNum();

Migration Steps

  1. Remove parseInt or Number(...) wrappers around blockNum() results.
  2. If you integrate with idxdb-store helpers directly, pass numbers instead of strings.

Common Errors

Error Message Cause Solution
Argument of type 'string' is not assignable to parameter of type 'number' Block numbers now numeric Pass number values directly

(WebClient) NetworkId custom networks and toBech32Custom removal

Summary

NetworkId is now a class with static constructors and supports custom prefixes. toBech32Custom was removed; use NetworkId.custom(...) with toBech32 instead.

Affected Code

TypeScript:

// Before (0.12.x)
const bech32 = accountId.toBech32Custom("cstm", AccountInterface.BasicWallet);
const network = NetworkId.Testnet;
// After (0.13.0)
const network = NetworkId.custom("cstm");
const bech32 = accountId.toBech32(network, AccountInterface.BasicWallet);
const testnet = NetworkId.testnet();

Migration Steps

  1. Replace enum-style NetworkId.Mainnet/Testnet/Devnet with NetworkId.mainnet()/testnet()/devnet().
  2. Replace toBech32Custom(prefix, ...) with toBech32(NetworkId.custom(prefix), ...).

Common Errors

Error Message Cause Solution
Property 'Mainnet' does not exist on type 'typeof NetworkId' Enum replaced by class Use NetworkId.mainnet() and friends
toBech32Custom is not a function Method removed Use NetworkId.custom(...) + toBech32

General MASM Changes

MASM syntax updates

Summary

MASM syntax was modernized. Several legacy forms are no longer accepted by the parser.

Affected Code

MASM:

- const.A
+ const A

- export.foo
+ pub proc foo

- proc.bar
+ proc bar

- use.miden::*
+ use miden::*

- export.miden::foo::bar
+ pub use miden::foo::bar

Migration Steps

  1. Replace dotted keywords (const.A, export.foo, proc.bar) with spaced forms.
  2. Replace use.miden::* with use miden::*.
  3. For re-exports, use pub use instead of export.<path>.

Common Errors

Error Message Cause Solution
unexpected token '.' Legacy dotted syntax. Use const A, pub proc foo, proc bar, use miden::*.

Falcon512Rpo Renames

Summary

The RpoFalcon512 naming is replaced with Falcon512Rpo across types, modules, procedures, and files.

Affected Code

Rust:

// Before (0.12.4)
use miden_lib::account::auth::AuthRpoFalcon512Acl;
use miden_objects::crypto::dsa::rpo_falcon_512;

// After (0.13.0)
use miden_standards::account::auth::AuthFalcon512RpoAcl;
use miden_protocol::crypto::dsa::falcon512_rpo;

Migration Steps

  1. Rename RpoFalcon512 types and modules to Falcon512Rpo in imports and identifiers.
  2. Update any file or procedure names that embed rpo_falcon_512 to falcon_512_rpo.

Common Errors

Error Message Cause Solution
unresolved import miden_lib::account::auth::AuthRpoFalcon512Acl Type renamed and crate moved Use miden_standards::account::auth::AuthFalcon512RpoAcl.
cannot find module rpo_falcon_512 Module renamed Import from falcon512_rpo.

ECDSA precompile procedure renames

Summary

ECDSA precompile procedures were renamed and moved under the normalized miden::core::crypto::dsa::ecdsa_k256_keccak module.

Affected Code

MASM:

- use std::crypto::dsa::ecdsa::secp256k1
+ use miden::core::crypto::dsa::ecdsa_k256_keccak

- exec.secp256k1::verify
- exec.secp256k1::verify_impl
+ exec.ecdsa_k256_keccak::verify
+ exec.ecdsa_k256_keccak::verify_prehash

Migration Steps

  1. Update module paths from std::crypto::dsa::ecdsa::secp256k1 to miden::core::crypto::dsa::ecdsa_k256_keccak.
  2. Replace verify_impl calls with verify_prehash where used directly.
  3. If you referenced the ECDSA event name in host code, update it to miden::core::crypto::dsa::ecdsa_k256_keccak::verify.

Common Errors

Error Message Cause Solution
unrecognized token "secp256k1::verify" Procedure renamed and moved. Use ecdsa_k256_keccak::verify.
unrecognized token "verify_impl" Internal precompile wrapper renamed. Use verify_prehash.

RPO hash helper renames

Summary

RPO memory hashing helpers were renamed for consistency. These are MASM procedure name changes.

Affected Code

MASM:

- exec.rpo::hash_memory_with_state
- exec.rpo::hash_memory_words
- exec.rpo::hash_memory_double_words
+ exec.rpo256::hash_elements_with_state
+ exec.rpo256::hash_words
+ exec.rpo256::hash_double_words

Migration Steps

  1. Update module path to miden::core::crypto::hashes::rpo256 if you were using rpo.
  2. Replace the procedure names as shown above.

Common Errors

Error Message Cause Solution
unrecognized token "hash_memory_words" Procedure renamed. Use hash_words.

Assembler Changes

LibraryPath removal (Path-only APIs)

Summary

LibraryPath was removed; APIs now accept Path (from miden_assembly or std::path).

Affected Code

Rust:

// Before (0.19.x)
use miden_assembly::{LibraryPath, Path};

let namespace = LibraryPath::from_str("miden::core::foo")?;
assembler.compile_and_statically_link_from_dir(namespace, dir)?;
// After (0.20.x)
use miden_assembly::Path;

assembler.compile_and_statically_link_from_dir(dir, Path::new("miden::core::foo"))?;

Migration Steps

  1. Remove LibraryPath usage and construct Path directly.
  2. If you use both std::path::Path and miden_assembly::Path, qualify imports to avoid ambiguity.

Common Errors

Error Message Cause Solution
error[E0432]: unresolved import miden_assembly::LibraryPath Type removed. Use miden_assembly::Path.

Assembler API updates

Summary

Assembler debug mode is now always enabled and the debug-mode toggles were removed. The argument order of compile_and_statically_link_from_dir changed to (dir, namespace) and namespaces are now Path-based.

Affected Code

Rust:

// Before (0.19.x)
use miden_assembly::{Assembler, LibraryNamespace};

let mut assembler = Assembler::default().with_debug_mode(true);
assembler.compile_and_statically_link_from_dir(LibraryNamespace::Kernel, "./masm")?;
// After (0.20.x)
use miden_assembly::{Assembler, Path};

let mut assembler = Assembler::default();
assembler.compile_and_statically_link_from_dir("./masm", Path::new("miden::core"))?;

Migration Steps

  1. Remove calls to with_debug_mode, set_debug_mode, or in_debug_mode; the assembler always emits debug info now.
  2. Update compile_and_statically_link_from_dir to pass the directory first.
  3. Replace LibraryNamespace usage with Path (e.g., Path::KERNEL, Path::EXEC, or a string).

Common Errors

Error Message Cause Solution
error[E0599]: no method named with_debug_mode found for struct Assembler Debug mode toggles were removed. Drop the call and rely on default behavior.
error[E0308]: mismatched types: expected Path, found LibraryNamespace The API now uses Path. Use Path::new("miden::core") or a string.
error[E0061]: this method takes 2 arguments but 2 arguments were supplied in the wrong order Argument order was flipped. Pass (dir, namespace) instead of (namespace, dir).

Clone this wiki locally