Migration Guide 0.15
- 主要语言
- TypeScript
- 星标
- 10
- 派生
- 50
- 平均合并
- 22 天 23 小时
- 30 天内合并 PR
- 1
描述
# Miden Testnet 0.15.0
This guide covers all breaking changes you need to migrate an application to Miden 0.15.0. Like the 0.14 guide, it is intentionally user‑facing: you do not need to know or care which internal crate (VM, protocol, client) a change came from. If you are:
- building accounts, notes, or transactions
- running a client, web client or React SDK
- writing or compiling MASM
- interacting with storage, auth, or RPCs
this document is for you. It folds together the breaking changes from `miden-base` (protocol `0.14` → `0.15.3`), `miden-vm` (`0.22` → `0.23`), `miden-client` (`0.14` → `0.15`), and the Web SDK (`@miden-sdk/*` `0.14` → `0.15`). Because `miden-client` and the Web SDK still ship from unified‑in‑progress `main`/`next` branches, this guide unions the breaking surface from both.
## At a Glance
Big themes in 0.15:
- **Account IDs no longer encode faucet/regular, mutability, or network mode.** The old `AccountType` enum is gone; `AccountStorageMode` is **renamed `AccountType`** (`{ Private, Public }`). Faucet/network‑ness now comes from an account's *components*; the ID version is renamed `0` → `1`.
- **Notes get a real identity split.** The old `NoteId` (recipient + assets) becomes **`NoteDetailsCommitment`**; the new `NoteId` also commits to metadata, and nullifiers now fold in metadata + the attachments commitment — none roundtrip with 0.14.
- **Notes carry multiple attachments, off the metadata.** `NoteMetadata` → `PartialNoteMetadata`, `NoteMetadataHeader` → `NoteMetadata`; attachments live on the note/record as a `NoteAttachments` collection (≤ 4). `NoteType` is now 1‑bit, default `Private`.
- **Fungible faucets are unified.** `BasicFungibleFaucet` + `NetworkFungibleFaucet` → one **`FungibleFaucet`** (`bon` builder) + `FungibleTokenMetadata` + a `TokenPolicyManager` for mint/burn policies. Amounts are a validated **`AssetAmount`** newtype.
- **Typed roots everywhere.** `NoteScript::root()` → `NoteScriptRoot`, `TransactionScript::root()` → `TransactionScriptRoot`, `procedure_digest!` → `procedure_root!`, plus `AccountComponentName`.
- **VM 0.23 / crypto 0.25 are digest‑changing.** SMT leaf hashing gains a Poseidon2 domain separator, the MAST wire format bumped `0.0.2` → `0.0.3` (old `.masl`/`.masp` won't load), execution is **sync‑first** (`BaseHost`/`SyncHost`; `execute` → `ExecutionOutput`), and `adv_push.N` was removed.
- **The client RPC surface is rebuilt around `GetAccount`.** `get_account_proof`/`get_account_details` reshaped, `check_nullifiers` removed (use `sync_nullifiers`), most sync methods now require an explicit `block_to`.
- **The Web SDK moved to the 0.15 protocol surface.** `"network"` storage mode is gone, the WASM `AccountType` narrowed to `{ Private, Public }`, attachments are word‑vector‑shaped, `Felt`/`Word` throw on overflow, several methods return `undefined`/`string` instead of throwing/objects, the raw client's `proveTransactionWithProver` is renamed `proveTransaction`, and `storeIdentifier()` went async.
If you only skim a few sections, skim **Account Changes**, **Note Changes**, **Asset, Vault and Faucet Changes**, **Hashing & Stack Conventions**, and **Client Changes**.
## Table of Contents
- [Imports and Dependencies](#imports-and-dependencies)
- [Hashing & Stack Conventions](#hashing--stack-conventions)
- [Account Changes](#account-changes)
- [Note Changes](#note-changes)
- [Asset, Vault and Faucet Changes](#asset-vault-and-faucet-changes)
- [Transaction Changes](#transaction-changes)
- [Client Changes](#client-changes) — Rust / Web / React / CLI sub-sections
- [General MASM Changes](#general-masm-changes)
- [VM and Assembler Changes](#vm-and-assembler-changes)
- [Project File Format](#project-file-format)
- [Need Help?](#need-help)
## Imports and Dependencies
### Summary
Every Miden crate moves up a minor: the protocol crates (`miden-protocol`, `miden-standards`, `miden-tx`, `miden-testing`) go `0.14` → `0.15.3`, the VM crates (`miden-assembly`, `miden-core`, `miden-core-lib`, `miden-processor`, `miden-prover`) go `0.22` → `0.23`, and `miden-crypto` goes `0.23` → `0.25`. `miden-client` and `miden-client-sqlite-store` go `0.14` → `0.15`; the Web SDK packages go `0.14` → `0.15`. The client's MSRV is Rust **1.93** (the base crates build on 1.90+).
> The prover crate is **`miden-prover`** in this line — it is *not* `miden-prove`. Keep depending on `miden-prover`.
Because the native hash and the MAST/serialization formats changed upstream, **0.14 artifacts (accounts, notes, proofs, serialized stores, `.masl`/`.masp` packages) do not round‑trip.** Re‑assemble from source and re‑sync into a fresh store.
### Affected Code
**Cargo.toml:**
```diff
- 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):**
```diff
- "@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):**
```diff
- 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](#adv_pushn-immediate-form-removed-adv_pushw-added)) and cross‑module‑referenced MASM constants/procedures must be marked `pub`.
#### Affected Code
```diff
- 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::SecretKey` → `SigningKey` and `eddsa_25519_sha512::SecretKey` → `KeyExchangeKey`.
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
```rust
// 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`.
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 `AuthMultisigGuardian` → `AuthGuardedMultisig` (the `guardian` auth namespace is retained).
- `OwnerControlledBlocklist` was renamed `BlocklistOwnerControlled`.
- New standard components were added: `Pausable`, `Authority`, allowlist/blocklist transfer policies, and `FungibleTokenMetadata`.
## Note Changes
### `NoteId` → `NoteDetailsCommitment`; new `NoteId` commits to metadata
#### Summary
The 0.14 `NoteId` (a commitment over recipient + assets only) is renamed to **`NoteDetailsCommitment`**. A brand‑new **`NoteId`** is introduced that hashes the details commitment together with the note metadata commitment, so the public note ID now changes when the note's metadata (sender, type, tag, attachments) changes.
```text
NoteDetailsCommitment = hash(NOTE_RECIPIENT_DIGEST || NOTE_ASSETS_COMMITMENT) // == old NoteId
NoteId = hash(NOTE_DETAILS_COMMITMENT || NOTE_METADATA_COMMITMENT) // new
```
#### Affected Code
```rust
// 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.
### `NoteMetadata` → `PartialNoteMetadata`; multiple attachments per note
#### Summary
The metadata types were renamed and reshuffled to support **multiple attachments per note** (up to `NoteAttachments::MAX_COUNT` = 4):
| 0.14 | 0.15 |
| --- | --- |
| `NoteMetadata` (sender/type/tag + single attachment) | `PartialNoteMetadata` (sender/type/tag only) |
| `NoteMetadataHeader` (the on‑stack metadata word) | `NoteMetadata` (metadata word + attachment headers + attachments commitment) |
| `NoteAttachment` (single, `with_attachment`) | `NoteAttachments` (collection, `with_attachments`) |
`Note::new(assets, metadata, recipient)` now takes a **`PartialNoteMetadata`**; a new `Note::with_attachments(assets, partial_metadata, recipient, attachments)` carries the attachments. The single `NoteMetadata::with_attachment` / `.attachment()` API is gone.
#### Affected Code
```rust
// 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_attachment` → **`add_attachment`** (and `set_word_attachment` → `add_word_attachment`, `set_array_attachment` → `add_attachment_from_memory`). They *append* instead of overwriting, and the stack signature dropped the `attachment_kind` field.
- `note::extract_attachment_info_from_metadata` → **`metadata_into_attachment_schemes`**, returning the four attachment scheme markers.
- All `get_metadata` procedures (`active_note`, `input_note`, `output_note`) **no longer return attachments** — they return just the single `METADATA` word.
#### Affected Code
```masm
# 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
```rust
// 0.15 — new API:
let nf = Nullifier::new(
script_root, storage_commitment, asset_commitment, serial_num,
metadata.to_metadata_word(), // new
metadata.attachments_commitment(), // new
);
let nf2 = Nullifier::from_details_and_metadata(¬e_details, &metadata);
```
#### Migration Steps
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` (was `Result`) **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
```rust
// 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` and `TryFrom` (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`](#rust-fungiblefaucet-builder--tokenpolicymanager-construction).
#### Affected Code
```rust
// 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_id` → `fee_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::new` → `new_unchecked`; `NoteConsumptionInfo` fields private
#### Summary
Batch/transaction housekeeping changes that affect anyone constructing these types by hand:
- `ProvenBatch::new` was renamed **`ProvenBatch::new_unchecked`** to signal it skips validation.
- `NoteConsumptionInfo` (and related types) gained cycle counts and had their fields made private; use the accessor methods `successful()` / `failed()`.
#### Migration Steps
1. Rename `ProvenBatch::new` → `ProvenBatch::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, 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
```rust
// 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 = 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` 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` 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` 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
```rust
// 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` (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`
#### Summary
`Client::import_notes`, `Client::sync_note_transport`, and the `SyncSummary::new_private_notes` field now yield `Vec` instead of `Vec`. 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
```rust
// 0.15 — new API:
use miden_client::store::NoteFilter;
use miden_protocol::note::NoteDetailsCommitment;
let imported: Vec = client.import_notes(¬e_files).await?;
let private: Vec = 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
```rust
// 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` and `sync_transactions` returns `Vec`.
#### 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`
#### 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 `Option` — `None` means "no script for this root", which previously surfaced as an error.
### (Rust) `miden_client::note` re‑exports realigned
#### Summary
The protocol split attachment data off `NoteMetadata`, and the client's `note` re‑exports follow:
- **Removed:** `NoteAttachmentKind`, `NoteMetadataHeader`.
- **Added:** `NoteAttachmentHeader`, `NoteAttachments`, `PartialNoteMetadata`.
- `NoteScript::root()` now returns `NoteScriptRoot` (re‑exported from `miden_client::note`) instead of `Word`.
#### Migration Steps
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
```typescript
// 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
```typescript
// 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
```typescript
// 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
```typescript
// 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`: `committedNoteIds` → `committedNoteTagSources`
#### Summary
On `miden-idxdb-store`'s `JsStateSyncUpdate`, the `committedNoteIds` field was renamed to `committedNoteTagSources` and now carries details‑commitment hex rather than note‑id hex. Anyone driving the IndexedDB store's sync apply path directly (or stubbing `JsStateSyncUpdate` in tests) must rename the field and feed details‑commitment hex.
#### Migration Steps
1. Rename `committedNoteIds` → `committedNoteTagSources` 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.proveTransactionWithProver` → `proveTransaction`
#### Summary
On the raw WASM client (`WasmWebClient` — the low-level surface behind `MidenClient`), `proveTransactionWithProver(txResult, prover)` was **renamed `proveTransaction(txResult, prover?)`**, with the prover now an optional second parameter (omitting it uses the local prover). The high-level `MidenClient` resource API is unaffected, but anything driving the raw client directly — worker shims, offscreen/prover documents, `_withInnerWebClient` callbacks — must rename the call.
#### Affected Code
```typescript
// 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`** 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`-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](#nullifier-now-includes-metadata-and-attachments-commitment)), a **partial (metadata-less) input note record has no computable nullifier** — `InputNoteRecord.nullifier()` now returns `string | undefined`, pairing with `id()`'s `NoteId | undefined`. A record missing either is a partial note that sync has not yet completed.
#### Migration Steps
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
```diff
# 0.15 — new API:
+ miden-client new-wallet --account-type public
```
#### Migration Steps
1. Replace `--account-type ` 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`.
```diff
# 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 ` ` (a pre‑encoded address) instead of ` [TAG_LEN]`. A new `address encode [TAG_LEN]` subcommand produces the bech32 string from the individual fields.
#### Migration Steps
1. Build the bech32 address first with `address encode [TAG_LEN]`.
2. Pass that string to `address add `.
## 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
```rust
// 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>) { /* ... */ }
}
impl SyncHost for MyHost {
fn get_mast_forest(&self, d: &Word) -> Option> { /* ... */ }
fn on_event(&mut self, p: &ProcessorState<'_>) -> Result, 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`.
```diff
- 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 -> ` 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 `MastForest`s 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.2` → `0.0.3`
#### Summary
The MAST forest serialization format was refactored around fixed‑layout **full**, **stripped**, and **hashless** sections, with stable node IDs and stricter validation of untrusted forests. The wire‑format version constant bumped from `[0, 0, 2]` to `[0, 0, 3]`. Serialized `.masl` / `.masp` / `MastForest` blobs produced under `0.22` will not deserialize under `0.23`. Deserialization of serialized libraries and kernel libraries is now treated as **untrusted** by default, rejecting spoofed/inconsistent node digests rather than trusting the bytes.
#### Affected Code
```rust
// 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](https://discord.gg/0xMiden) — the `#dev-support` channel.
- **GitHub issues:** file against the relevant repo — [`miden-client`](https://github.com/0xMiden/miden-client/issues), [`web-sdk`](https://github.com/0xMiden/web-sdk/issues), [`miden-base`](https://github.com/0xMiden/miden-base/issues), or [`miden-vm`](https://github.com/0xMiden/miden-vm/issues).
- **Changelogs:** the per‑repo `CHANGELOG.md` files carry the full list of changes, including non‑breaking features and fixes omitted from this guide.
贡献指南
评估
这个 Issue 还没有评估数据。