docs(migration): add the v0.16 migration guide - #357
Conversation
Replaces the 0.15 guide in the current docs version with a 0.16 guide covering the protocol crates (0.15.3 -> 0.16.0), the VM (0.23 -> 0.29.1), miden-client, the Web SDK, and the Rust contract SDK / compiler. The 0.15 guide remains available under versioned_docs/version-0.15. Every API, signature, flag and error string was verified against source at the release tags rather than taken from the changelogs, which turned out to be unreliable: several entries describe APIs that do not exist in the shipped code, and several real breaking changes are missing from them entirely. Notable corrections carried into the guide: - AccountDelta -> AccountPatch applies to account updates only; TransactionSummary::account_delta() deliberately stays relative. - TransactionRequestBuilder::fee_conversion_info takes an undocumented second `salt: Word` argument; code written from the changelog alone will not compile. - Opening a pre-0.16 store surfaces a migration error, not the SchemaHashMismatch the changelog names. - `miden-client call` now counts arguments in field elements rather than parameters, and `init` dropped auth/acl-auth.masp. Neither is in the changelog. - Claims that AuthNetworkAccount::with_allowed_tx_scripts was removed and that RoleBasedAccessControl gained a builder() are contradicted by source, so they are omitted. Co-authored-by: Cursor <cursoragent@cursor.com>
Follow-up verification against the pinned trees turned up four fixes: - Client::submit_proven_transaction keeps its 0.15 signature exactly. Only the NodeRpcClient trait methods take SealedTransactionInputs, so this is source-breaking solely for trait implementers, not for the applications the guide addresses. The real requirement it imposes on everyone is syncing before submitting, which now fails with ChainValidationError rather than silently. - Account::apply_patch is the direct replacement for apply_delta and is the smaller edit; the guide previously showed only Account::try_from. - ClientBuilder::rpc does not wrap a custom client in VerifyingRpcClient while the built-in gRPC constructors now do, so passing your own RPC client silently loses response verification. - Client::get_consumable_notes keeps its signature; the single-account path is a performance change, not an API change. Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: François Garillot <4142+huitseeker@users.noreply.github.com>
Co-authored-by: François Garillot <4142+huitseeker@users.noreply.github.com>
Co-authored-by: François Garillot <4142+huitseeker@users.noreply.github.com>
| .with_components([ | ||
| AuthSingleSig::new(Approver::new(pub_key, auth_scheme)).into(), | ||
| BasicWallet.into(), | ||
| ]) |
There was a problem hiding this comment.
nit: if you add individual components, use with_component, it reads better. If you have a type that implements IntoIterator<Item = AccountComponent> like AuthNetworkAccount use with_components.
| :::note `AccountType` did not change | ||
| `AccountType` has been `Private` / `Public` since 0.15, and there is no separate `AccountStorageMode`. If you are coming from an older release, that collapse is covered in the [0.15 guide](https://docs.miden.xyz/0.15/builder/migration/account-changes). | ||
| ::: |
There was a problem hiding this comment.
nit: not sure if this is relevant for the 0.15 -> 0.16 migration?
| In 0.15 each standard note was a unit struct — `pub struct P2idNote;` — with a `create` associated function that took every parameter positionally and returned a finished `Note`. In 0.16 each is a real struct holding its fields, built through a `bon` builder and converted to a `Note` with `Into`. | ||
|
|
||
| ### Migration Steps | ||
| The practical benefits are that optional parameters are now actually optional rather than positional, and that the intermediate typed value is inspectable before you convert it. The practical cost is that every call site changes. |
There was a problem hiding this comment.
and that the intermediate typed value is inspectable before you convert
I think this undersells the value of a proper type to represent each standard note. E.g. you can write an API now that takes a P2IdNote rather than Note, which gives you type safety. Worth mentioning here to give downstream users the chance that their agent recognizes the opportunity for a better API rather than just a make-it-work migration.
| 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. | ||
| The asset limit change is the one to watch, because it is a runtime error rather than a compile error, and it only triggers for notes carrying more than 16 assets. |
There was a problem hiding this comment.
nit: Makes it sound like this used to be a compile error, but it was always runtime. Not sure this sentence carries real value.
PhilippGackstatter
left a comment
There was a problem hiding this comment.
Looks good - I reviewed chapters 2 to 7.
Miden Testnet 0.16.0
This guide covers all breaking changes you need to migrate an application to Miden 0.16.0. Like the 0.15 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:
midenSDKthis document is for you. It folds together the breaking changes from the protocol crates (
0.15.3→0.16.0), the VM crates (miden-vm,0.23→0.29.1),miden-client(0.15→0.16.0), the Web SDK (@miden-sdk/*0.15→0.16.0), and themidenRust contract SDK / compiler (0.13→0.14).Quick Upgrade
Try upgrading first — most projects can start with a dependency update:
Cargo.toml
package.json (Web SDK)
{ "@miden-sdk/miden-sdk": "0.16.0-rc.2", "@miden-sdk/react": "0.16.0-rc.2" }Then run:
cargo update && cargo buildIf you encounter errors, continue reading for detailed migration steps.
At a Glance
Big themes in 0.16:
FeeConversionInfofrom the transaction's auth args and emits aTX_FEEnote. On a fee-charging chain, requests signed byAuthSingleSig/AuthMultisigmust callTransactionRequestBuilder::fee_conversion_info(info, salt)..masmfile is only included if its parent declares it withmod/pub mod— an undeclared file is silently dropped.usesplit into module imports and braced item imports, aliases moved from->toas, and imports resolve globally.AccountDelta→AccountPatchfor account updates (ExecutedTransaction,AccountUpdateDetails, client results).TransactionSummary::account_delta()deliberately stays relative.AccountBuilder::with_auth_componentis gone; auth components pass throughwith_component(s)and are found by their@auth_scriptattribute. Keys are wrapped in a newApprover/ApproverSet.AuthMethodandAuthSingleSigAclare removed.AssetVaultKey→AssetId, and the oldAssetId→AssetClass. BecauseAssetIdsurvives with a new meaning, careless renaming compiles and is wrong.Libraryis gone;Packageis the only artifactLibrary/KernelLibrarywere deleted,link_*_librarycollapsed intolink_package,*_from_dirbecame*_from_root, and.maslno longer exists. MAST0.0.4/ package6.0.0are not backward compatible.XNote::create(..)→XNote::builder()…build()?+.into().MAX_ASSETS_PER_NOTEdropped 64 → 16. Mint and burn scripts were unified across faucet kinds, changing their roots.debug.*andtraceare gone from the language, replaced bymiden::core::debugprocedures — which, unlike the decorators, print unconditionally. The client and CLI debug-mode toggles were removed with them.If you only skim a few sections, skim Transaction Changes, Account Changes, MASM Changes, and Client Changes.
Compatibility
@miden-sdk/*)midencontract SDKmidenccompilerTable of Contents
AeadPoseidon2key derivation restored to canonical decodingAccountBuilder::with_auth_componentremovedApproverandApproverSetreplace raw key argumentsAccountCode::from_partsis now fallibleAccountIdno longer converts into[Felt; 2]AccountDeltatoAccountPatchMAX_ASSETS_PER_NOTEdropped from 64 to 16MINTandBURNare unified across faucet kindsAssetVaultKey→AssetId, andAssetId→AssetClassTransactionSummarybinds the reference block, expiration, and user paramsAccountPatchAssetAmountsendPrivateOutputNotesendrenamed totransferaccount --with-codereplaced byaccount --inspectcallcounts arguments in field elementstoken_symbol_map.toml:idrenamed toaddressinitwrites a different package setmod/pub modasaliases, and global resolutiondebug.*andtracedecorators removedmiden::precompilesnamespace, and removalsdo .. while .. endloops addedLibrary→Packagethroughout the assemblermiden::core+miden::precompiles0.0.4, package format6.0.0, and.maslremovedExecutionProofreworked;Verifierreplaces the freeverify_*functionsAdviceInputs.stackreplaced by theAdviceStacktypeModuleInfo→ModuleDescriptor,Kernel→KernelDescriptormiden-project.toml:pathis mandatory on every targetmiden-vm bundlereworkedProjectAssembler::assemble_with_sourcesremoved#[account_procedure]#[account(..)]generates one trait per interfaceFinal Checklist · Need Help?
Imports & Dependencies
Every layer of the stack moves:
miden-protocol,miden-standards,miden-tx,miden-testing) go0.15.3→0.16.0.miden-assembly,miden-core,miden-core-lib,miden-processor,miden-prover,miden-mast-package) go0.23→0.29.1. This is a much larger jump than previous releases and carries breaking MASM language changes — see VM & Assembler Changes.miden-cryptogoes0.25→0.29.1. It is no longer an independent crate line: it was imported into the Miden VM workspace and now shares the VM version number.miden-clientandmiden-client-sqlite-storego0.15→0.16.0.0.15→0.16.0.Two crates changed identity:
miden-tx-batch-proveris nowmiden-tx-batch, and a newmiden-protocol-build-utilscrate provides MASM assembly helpers. On the VM side the core package was split, adding amiden-precompilespackage alongsidemiden-core.Quick Fix
Cargo.toml
package.json (Web SDK)
{ "@miden-sdk/miden-sdk": "0.16.0-rc.2", "@miden-sdk/react": "0.16.0-rc.2" }Then run:
cargo update && cargo buildIf you encounter errors, continue reading for detailed migration steps.
Version Bumps
miden-clientmiden-client-sqlite-storemiden-protocolmiden-standardsmiden-txmiden-testingmiden-tx-batch-provermiden-tx-batch0.16.0miden-protocol-build-utilsmiden-assemblymiden-coremiden-core-libmiden-processormiden-provermiden-verifiermiden-mast-packagemiden-precompilesmiden-crypto@miden-sdk/miden-sdk@miden-sdk/react@miden-sdk/vite-pluginAffected Code
Cargo.toml:
package.json (Web SDK):
MSRV (Minimum Supported Rust Version)
The MSRV rose across the board. Update your
rust-toolchain.tomlto Rust 1.96:rust-toolchain.toml
miden-clientMigration Steps
0.16.0-rc.Nstrings for the protocol and client crates, and runcargo update.miden-tx-batch-proverdependency tomiden-tx-batchif you used it.1.96.@miden-sdk/miden-sdkand@miden-sdk/reacttogether — mixing 0.15 and 0.16 packages will not link against the shared WASM ABI. Drop anymiden-idxdb-storedependency..masppackage from source under the new toolchain, and delete cachedMastForestblobs. The.maslformat is gone entirely.Common Errors
failed to select a version for miden-protocol"0.16"requirement will not match a0.16.0-rc.Npre-release"0.16.0-rc.6".failed to select a version for miden-tx-batch-provermiden-tx-batchinstead.MastForest deserialization failed: unexpected version0.0.46.0.0.masp;.maslis no longer supported at all.Migration error: Attempt to migrate a database with a migration number that is too highrustcversion error during buildrust-toolchain.toml.Hashing & Crypto Changes
Unlike most of this release, nothing here breaks your build. These are value changes, so the symptom is a proof that fails to verify, an account whose storage no longer matches, or an advice-map lookup that misses — all at runtime, all without a compiler error pointing at the cause.
The rule of thumb: if you stored a hash, recompute it. If you only ever compute hashes on the fly from current inputs, you are unaffected.
Quick Fix
Then re-derive anything downstream: account storage slots, note storage, and advice-map keys built from those commitments.
If you encounter errors, continue reading for detailed migration steps.
ECDSA k256 public-key commitment format changed
The ECDSA-k256/Keccak public-key commitment now hashes the native affine coordinate limbs (
qx || qyas little-endianu32limbs) instead of the compressed SEC1 public-key bytes. Compressed SEC1 serialization of the key itself is unchanged — only the commitment value changed (#3342, crypto#1075).Affected Code
Migration Steps
PublicKey::to_commitment().ecdsa_k256_keccak::verifyis still[PK_COMM, MSG_WORD, ...]; only the value ofPK_COMMmoved. The advice layout did change, though — see MASM Changes.MMR peak commitments now bind the leaf count
MMR peak commitments are computed over
[num_leaves, 0, 0, 0] || padded_peaksinstead ofpadded_peaksalone, on both the Rust and MASM sides. All MMR peak commitments change (#3388).Affected Code
The
miden::core::collections::mmrpackandunpackprocedures were updated to the same preimage. In 0.15,packhashed the range starting atmmr_ptr + 4, skipping the leaf-count word; in 0.16 it hashes frommmr_ptr, so the leaf count is absorbed first. The MASM stack contracts ([mmr_ptr, ...] -> [HASH, ...]) are unchanged.Migration Steps
mmr::packandmmr::unpackkeep their signatures.Domain-separated empty-input hashing changed
hash_elements_in_domain(&[], d)for a nonzero domaindused to collide with other inputs. The fix marks the empty-input case in the third capacity element and applies a permutation, so the result is now a distinct, nonzero digest (#3447, refining #3366).Related and also digest-changing:
hash_bytes(&[])no longer returnsWord::default(). The empty-bytes input now absorbs a padding marker and permutes, producing a nonzero digest consistent with the10*sponge padding rule (#3366).Affected Code
Migration Steps
hash_elements_in_domainover an empty element list with a nonzero domain — typically "empty collection" sentinel values.hash_bytes(&[]). A stored zero word is no longer the right answer.merge_in_domainand non-emptyhash_elements_in_domaininputs are unaffected.AeadPoseidon2key derivation restored to canonical decodingAeadPoseidon2::key_from_byteswas restored to canonical-Feltdecoding (#3366). Keys persisted under the brief SHA-256 KDF contract must be re-derived.Migration Steps
key_from_bytesduring the 0.16 pre-release window, re-derive them.Common Errors
These changes do not produce compile errors. Expect runtime symptoms instead:
PK_COMMuses the old preimagePublicKey::to_commitment().Account Changes
Three independent shifts land on accounts in this release.
Auth stops being special. In 0.15 the builder had a dedicated auth slot; in 0.16 the auth component is just a component, and the builder finds it by looking for the
@auth_scriptattribute in its MASM. This is what makes the fee change possible — the auth procedure is now also where fees get paid, so it needed to compose with everything else.Keys are wrapped in an
Approver.AuthSingleSig::newtook a public-key commitment and a scheme; it now takes a singleApprovercarrying both. Multi-signature components take anApproverSetwith a threshold. This is a mechanical rewrite, but it touches every account you construct.Component names were normalised, which changes commitments. Every standard component's
NAMEdropped itscomponents::segment. Since the name feeds component metadata, and metadata feeds the storage schema commitment, this silently changes account commitments even when nothing else about your account changed.Quick Fix
If you encounter errors, continue reading for detailed migration steps.
AccountBuilder::with_auth_componentremovedAccountBuildernow takes all components uniformly throughwith_componentandwith_components, and identifies the auth component by its@auth_scriptMASM attribute.Affected Code
let account = AccountBuilder::new(init_seed) .account_type(AccountType::Public) - .with_auth_component(auth_component) - .with_component(BasicWallet) + .with_components([auth_component.into(), BasicWallet.into()]) .build()?;AccountBuilderalso gainedwith_asset_callbacks(AssetCallbackFlag). Whether a faucet's assets trigger callbacks is now encoded in the account ID rather than in separate storage, so this is set at construction time.Migration Steps
with_auth_componentand pass the auth component throughwith_componentorwith_components.@auth_scriptattribute — that is how the builder recognises it.with_asset_callbacks.Common Errors
no method named with_auth_componentwith_component/with_components.@auth_script.ApproverandApproverSetreplace raw key argumentsApproverbundles a public-key commitment with its signature scheme;ApproverSetbundles a list of approvers with a threshold. Both are new in 0.16. TheAuthMethodenum was removed, andAuthSingleSigAcl/AuthSingleSigAclConfigwere removed outright.Affected Code
The convenience constructors are unchanged and remain the shortest path when you have a concrete key:
New accessors:
AuthSingleSig::approver(),ApproverSet::approvers(), andApproverSet::threshold().Migration Steps
AuthSingleSig::new(pub_key, scheme)arguments inApprover::new(pub_key, scheme).ApproverSet::new(approvers, threshold)?— note it is fallible.AuthMethod; the scheme now travels inside theApprover.AuthSingleSigAcl, there is no drop-in replacement. Rebuild the access-control policy using the components undermiden::standards::access(for exampleRoleBasedAccessControlorAuthority).Common Errors
this function takes 1 argument but 2 were suppliedonAuthSingleSig::newApprover::new.cannot find type AuthMethodApprover/AuthScheme.cannot find type AuthSingleSigAclComponent names changed, and account commitments with them
Every standard component's
NAMEconstant was normalised by dropping thecomponents::segment. Component metadata feeds the storage schema commitment, so the commitment of an account built from the same seed and the same components differs between 0.15 and 0.16.Affected Code
A few names already lacked the segment in 0.15 —
miden::standards::auth::network_accountandmiden::standards::access::ownable2stepare unchanged. Note that the fungible faucet also lost its_faucetsuffix, so it is not a pure prefix change.The
miden::standards::account::metadatamodule was also renamed tomiden::standards::account::inspection, both in MASM and in Rust.Migration Steps
account::metadatatoaccount::inspection.Component MASM must annotate exported procedures
Account component MASM must annotate every exported procedure with
@account_procedure. Un-annotated procedures are not exported. This attribute is new in 0.16 — the protocol's own MASM went from zero uses to 99.@auth_scriptand@note_scriptalready existed in 0.15 and are unchanged.Affected Code
Migration Steps
@account_procedureto every procedure your component intends to export.AccountCodeprocedure list — a missing annotation shows up as a procedure that silently is not callable, not as a compile error.AccountCode::from_partsis now fallibleAccountCode::from_partsvalidated its procedure count withassert!in 0.15 and now returns aResultinstead of panicking.Affected Code
Migration Steps
Add
?or explicit error handling at every call site.AccountIdno longer converts into[Felt; 2]The
impl From<AccountId> for [Felt; 2]was removed. Use theprefix()andsuffix()accessors instead. Conversions to[u8; 15]andu128are unchanged.Affected Code
Migration Steps
Replace the
into()conversion with the two accessors. Noteprefix()returns anAccountIdPrefix, not a bareFelt.Account updates move from
AccountDeltatoAccountPatchAccount updates moved from the relative
AccountDeltato the absoluteAccountPatch.ExecutedTransactionandAccountUpdateDetailsnow carry a patch, andAccount::apply_deltawas replaced by applying a patch.Affected Code
apply_patchis the direct replacement forapply_deltaand keeps the same in-place shape, so it is the smaller edit for existing code. The same rename applies further down:AssetVault::apply_deltabecameapply_patch, taking anAccountVaultPatch.Migration Steps
account_delta()withaccount_patch()onExecutedTransactionand on client transaction results.Account::apply_delta(&delta)withAccount::apply_patch(&patch), or construct a new account withAccount::try_from(&patch).TransactionSummary::account_delta()call sites alone — that one is intentionally still a delta.Network accounts require a fee policy
AuthNetworkAccount::newnow takes aFeePolicyManageralongside the allowed-note set. The manager carries the fee faucet and the active fee policy, and expands into the policy's components when the auth component is installed — so you do not install policy components separately.Affected Code
AuthNetworkAccountno longer converts into a singleAccountComponent— it expands into several, so install it throughwith_components.Migration Steps
FeePolicyManagerwith the fee faucet and an active fee policy, registering any alternatives withallowed_fee_policyfor runtime switching.AuthNetworkAccount::new, which is fallible.with_components, notwith_component, since it expands to several components.Common Errors
no method named with_auth_componentwith_component(s).this function takes 1 argument but 2 were suppliedAuthSingleSig::newtakes anApproverApprover::new.cannot find type AuthMethod/AuthSingleSigAclexpected Result, found AccountCodefrom_partsis fallible?.the trait From<AccountId> is not implemented for [Felt; 2]prefix()/suffix().@account_procedureNote Changes
In 0.15 each standard note was a unit struct —
pub struct P2idNote;— with acreateassociated function that took every parameter positionally and returned a finishedNote. In 0.16 each is a real struct holding its fields, built through abonbuilder and converted to aNotewithInto.The practical benefits are that optional parameters are now actually optional rather than positional, and that the intermediate typed value is inspectable before you convert it. The practical cost is that every call site changes.
The asset limit change is the one to watch, because it is a runtime error rather than a compile error, and it only triggers for notes carrying more than 16 assets.
Quick Fix
If you encounter errors, continue reading for detailed migration steps.
Standard notes are built with typed builders
Each standard note struct now exposes
builder()and converts intoNoteviaFrom/Into.Affected Code
Note two naming details that are easy to get wrong: the setter is
serial_number, notserial_num, andgenerate_serial_number(&mut rng)is the direct replacement for the oldrngparameter.P2ideNotetakes its optional parameters as optional setters rather than positionally:SwapNotefollows the same pattern. In 0.15SwapNote::createreturned a(Note, NoteDetails)tuple carrying the payback details; in 0.16 you build theSwapNoteand read its parts from the typed value.The same builder treatment applies to
MintNote,BurnNote,PswapNote, andTxFeeNote, along with the configuration notes (AllowlistConfigNote,OwnerConfigNote,FaucetMetadataConfigNote,NetworkAccountConfigNote,FaucetPolicyConfigNote,MinBurnAmountConfigNote).Migration Steps
XNote::create(..)call with the correspondingXNote::builder()chain, ending in.build()?and.into().rngargument with.generate_serial_number(&mut rng)..assets(..)for a collection or.asset(..)repeatedly for individual assets.P2ideNote, set only the optional parameters you actually need.reclaimerstill defaults to the sender, so existing "sender can reclaim" behaviour is preserved without changes.Common Errors
no function or associated item named createXNote::builder().no method named serial_numserial_numberorgenerate_serial_number.expected Note, found P2idNote.into().a P2ID note must contain at least one assetMAX_ASSETS_PER_NOTEdropped from 64 to 16The protocol limit on assets carried by a single note fell from 64 to 16.
Affected Code
This is enforced by
NoteAssets::new, so it surfaces as aNoteErrorat build time rather than a compile error.Migration Steps
MINTandBURNare unified across faucet kindsOne
mint.masmand oneburn.masmscript now serve both fungible and non-fungible faucets, with the variant carried in the note storage. The script roots changed, so any hard-coded or cached root is now wrong.Migration Steps
Other note changes
PswapNote(partial swap) gained a minimum-fill parameter, and its fields were renamed.NoteFilewas reworked and moved tomiden-standards, with variants keyed onNoteId,ExpectedNote, andCommitted. This mostly affects client code — see Client Changes.NoteTagmoved undermiden::standards::note::note_tagin MASM. In the released0.16.0-rcline it is still reachable atmiden::standards::note_tag.Common Errors
no function or associated item named createXNote::builder().NoteErrorabout exceeding asset limitsexpected Note, found MintNote.into().Assets, Vault & Faucet Changes
The rename reflects a conceptual correction. The per-asset vault key is the thing that actually identifies an asset, so it took the name
AssetId; the faucet-level identifier it used to share a name with describes a class of assets, so it becameAssetClass.This is the most dangerous rename in the release precisely because it is not a removal.
AssetIdstill exists after the upgrade, so code referring to it keeps compiling while silently meaning something different. Rename in the right order and let the compiler find the rest.Quick Fix
If you encounter errors, continue reading for detailed migration steps.
AssetVaultKey→AssetId, andAssetId→AssetClassThe vault key type was renamed to
AssetId, the previousAssetIdbecameAssetClass, andAsset::vault_key()becameAsset::id().AssetIdHashis the corresponding hash type.Affected Code
Note that
AssetId::new_fungibleno longer takes a callback flag. Whether a faucet's assets trigger callbacks is encoded in the account ID itself, set at construction time viaAccountBuilder::with_asset_callbacks.Assetitself is unchanged in shape — still an enum withFungibleandNonFungiblevariants — andFungibleAsset::new(faucet_id, amount)keeps its signature.Migration Steps
AssetId→AssetClassfirst, throughout your codebase.AssetVaultKey→AssetId.asset.vault_key()withasset.id().new_fungiblecalls; set it on the account instead withwith_asset_callbacks.Common Errors
cannot find type AssetVaultKeyAssetId.no method named vault_keyid().this function takes 1 argument but 2 were suppliedonnew_fungiblewith_asset_callbackson the account.AssetIdused to workAssetIdnow means the vault keyAssetClass.Faucet factories split by authentication scheme
create_fungible_faucettook anAuthMethodand anAccessControlargument and dispatched internally. SinceAuthMethodwas removed (see Account Changes), the factory split into one function per authentication scheme, each taking a concrete auth component.Affected Code
Note that the parameter order changed as well as the parameter list —
account_typemoved to the end.The full set of factories:
create_singlesig_user_fungible_faucetcreate_multisig_user_fungible_faucetcreate_guarded_user_fungible_faucetcreate_network_fungible_faucetcreate_user_non_fungible_faucetcreate_network_non_fungible_faucetNon-fungible faucet factories are new in this release; 0.15 shipped only the fungible factory.
Migration Steps
AuthMethod.access_controlargument. The factories installAuthority::AuthControlledand the pausable components for you.account_typeis now last.Common Errors
cannot find function create_fungible_faucetcannot find type AuthMethodaccount_typemoved to the end.Common Errors
cannot find type AssetVaultKeyAssetIdAssetIdtoAssetClass.AssetIdkept its name with a new meaningAssetIdreference.cannot find function create_fungible_faucetTransaction Changes
The fee change is the largest behavioural change in the release, and it is easy to under-estimate because it does not necessarily break your build. If you target a chain that charges no fee, nothing changes. If you target a chain that does, transactions that used to succeed now fail unless the request declares fee conversion info.
The reason for the move is visible in the standard auth component's own documentation: paying the fee before the transaction summary is created means the fee note and the vault withdrawal funding it are covered by the signature. Under the old model the kernel deducted the fee outside anything the user signed.
The sealing change is a hard compatibility boundary rather than an API change — it mostly costs you a coordinated upgrade rather than a code edit.
Quick Fix
If you encounter errors, continue reading for detailed migration steps.
Transaction fees are paid by the auth procedure
In 0.15 the kernel computed and burned the fee from the native account's vault automatically. In 0.16 the auth procedure reads a
FeeConversionInfoblob out of the auth args, computes the fee, and emits aTX_FEEnote to the fee faucet.The standard auth components already do this for you —
AuthSingleSig's MASM callsfee::load_conversion_infoandfee::pay_feebefore authenticating. What you must supply is the auth args.Affected Code
The exact signatures:
FeeConversionInfois reachable atmiden_client::account::component::FeeConversionInfo— it is not exported frommiden_client::auth, where you would naturally look first. Adding a directmiden-standardsdependency also works.When it is required, and when it is rejected
Only auth components that actually read the auth args honour the conversion info. The client validates this before execution rather than silently paying in the native asset:
The check passes only for
AuthSingleSigandAuthMultisig. Declaring fee conversion info for any other auth component —NoAuth, for example — is rejected. If the request does not declare fee conversion info at all, the check is skipped, so existing code that never calls the builder method is unaffected by this validation.Because
fee_conversion_infoconsumes the auth arg, it conflicts with a manually setauth_arg— whichever is applied last wins.Migration Steps
verification_base_fee, callfee_conversion_info(info, salt)on every request signed by anAuthSingleSigorAuthMultisigaccount. UseFeeConversionInfo::one_to_one(fee_faucet_id)for the native fee asset.fee_conversion_infofor accounts using any other auth component.auth_argmanually, pick one or the other.executed_tx.fee()with inspection of theTX_FEEoutput note.ExecutedTransaction::compute_fee()still exists but only under thetestingfeature — do not use it in production.miden::standards::fee::load_conversion_infofollowed bymiden::standards::fee::pay_fee, or the transaction will fail fee validation.Common Errors
this function takes 2 arguments but 1 was suppliedsaltparameterWordsalt.FeeConversionInfoUnsupportedAuthSingleSig/AuthMultisig.cannot find FeeConversionInfo in miden_client::authmiden_client::account::component::FeeConversionInfo.fee::load_conversion_infothenfee::pay_fee.Transaction inputs are sealed before submission
Transaction inputs are encrypted ("sealed") before being submitted. The RPC layer gained a
get_transaction_encryption_keymethod plus amiden_client::rpc::encryptionmodule.This is a hard compatibility boundary. A 0.16 node rejects plaintext submissions and an older node rejects sealed ones, so the client and node must be upgraded together.
The requirement this does impose on every application is a sync before submitting: sealing resolves an encryption key against the chain state, so a client that has not synced the genesis and chain-tip headers fails with
ClientError::ChainValidationError.Migration Steps
ChainValidationError.NodeRpcClientyourself, updatesubmit_proven_transactionandsubmit_proven_batchto takeSealedTransactionInputs, and addget_transaction_encryption_key.TransactionSummarybinds the reference block, expiration, and user paramsTransactionSummary::newreplaced its singlesaltparameter with a block commitment, an expiration delta, and a structured user-parameters value.Affected Code
TransactionSummaryUserParamscarries seven field elements. On the TypeScript side the corresponding accessor renamed fromTransactionSummary.salt()toTransactionSummary.userParams().The reference block commitment is included because it determines the fee parameters, and therefore the fee amount deducted; the expiration delta is included so the signature covers it.
Migration Steps
saltargument with the block commitment, expiration delta, and user params.summary.salt()withsummary.userParams().Other transaction changes
ExecutedTransaction::account_delta()becameaccount_patch(), matching the account update model. See Account Changes.ExecutedTransaction::compute_fee()is gated behind thetestingfeature. Production fee figures come from theTX_FEEnote.Common Errors
this function takes 6 arguments but 4 were suppliedTransactionSummary::newchangedno method named salton a summaryuserParams().no method named feeon an executed transactionTX_FEEnoteno method named account_deltaon an executed transactionaccount_patch().Client Changes
The client changes fall into four groups. The store break is the one that costs users data, and it is unavoidable. The fee and sealing changes are covered in Transaction Changes — they surface here as a new builder method and a node-version requirement. The rename churn (
account_delta→account_patch,send→transfer, and friends) is mechanical. And a handful of silent behavioural changes — thecallargument counting, the transaction summary display,notes.sendPrivaterequiring a scan height — will not fail your build but will change what your application does.Quick Fix
If you encounter errors, continue reading for detailed migration steps.
(Store) Every pre-0.16 SQLite store must be recreated
The store schema changed substantially: account SMT forest tables were added, account IDs and all digest columns were retyped from hex
TEXTtoBLOB, ascript_rootindex was added, and themigrationstable was dropped in favour of a schema fingerprint.Affected Code
A store written by miden-client 0.15.5 fails to open with:
Beyond the account ID retyping, the schema diff also shows the
latest_account_assetsandhistorical_account_assetscolumnvault_keyrenamed toasset_id(following the protocol rename), a new unique index ontags(tag, source), and all digest columns retyped toBLOB—account_commitment,note_id,nullifier,script_root,recipient_digest, and storage keys and values.Migration Steps
Store, note thatinsert_block_headernow takes anodesargument,insert_partial_blockchain_nodeswas removed, and the newNoteFilter::ScriptRootsvariant makes existing exhaustive matches fail to compile.(Rust) Account updates use
AccountPatchTransactionResult::account_delta()becameaccount_patch(), andAccount::apply_deltawas replaced by construction from a patch.TransactionSummary::account_delta()is deliberately unchanged. This is covered in full under Account Changes.One import detail specific to the client: in 0.15
AccountStorageDeltalived inmiden_client::asset; the 0.16 replacementAccountStoragePatchlives inmiden_client::account. The module moved as well as the name.StorageMapDeltaandStorageSlotDeltawere dropped frommiden_client::assetalongside it, whileAccountVaultDeltaremains there.(Rust) Fee conversion info on the transaction request
TransactionRequestBuilder::fee_conversion_info(info, salt)is new and required on fee-charging chains forAuthSingleSigandAuthMultisigaccounts. See Transaction Changes for the full flow, including the mandatorysaltargument that the changelog omits.(Rust) Fungible amounts use
AssetAmountThe client surface switched from raw
u64toAssetAmountfor fungible amounts.AccountReader::get_balancereturnsAssetAmount, and the token conversion helpers (tokens_to_base_units,base_units_to_tokens) andbuild_pswap_consumefollow.Migration Steps
AssetAmount, or unwrap with the provided accessor where you need au64.TokenParseError::InvalidAmountwhere you parse user-supplied amounts.(Rust) Auth and faucet re-exports changed
AuthMethodandAuthSingleSigAclwere removed, and the single fungible faucet factory split into auth-specific factories — see Assets, Vault & Faucet Changes. Note that the client re-exports only two of the six upstream factory functions; for the rest, depend onmiden-standardsdirectly.The account policy components were also renamed, a change absent from the changelog and found by diffing the re-export lists:
(Rust) Note screening methods renamed
NoteScreener::can_consumebecameget_consumability, andcan_consume_batchbecameget_batch_consumability. A newget_batch_consumability_for_accountwas added.Client::get_consumable_noteskeeps its signature — passing a single account is now screened more efficiently, but nothing about the call changes.(Rust) Debug mode removed
DebugMode,ClientBuilder::in_debug_mode,Client::in_debug_mode, and theMIDEN_DEBUGenvironment variable were all removed. The VM replaced the flag-gateddebug.*decorators withmiden::core::debugprocedures that print unconditionally, so there is nothing left to gate. See MASM Changes.(Rust) Other library changes
StateSyncUpdateis immutable — construct withfrom_parts, read through accessors, and destructure withinto_parts.PartialBlockchainUpdates::insertlost its nodes argument, andextend_authentication_nodeswas added.miden_client::assembly::Librarywas removed. Usemiden_client::vm::Package. Note thatPackageis not new — it was already re-exported in 0.15; only theLibraryremoval is a 0.16 change.Client::fetch_all_private_noteswas removed, replaced by note transport syncing.TransactionRecordgained a private field, so struct literal construction no longer compiles.send_notesreads its payload from the advice provider and requires a payload-commitment script argument. Ascript_argpassed alongside aSendNotestemplate is ignored.AccountSmtForestis generic over its backend, and the root-staging API was removed.VerifyingRpcClient. The built-in gRPC constructors now wrap the transport in it automatically, butClientBuilder::rpcdoes not — passing your ownNodeRpcClientcompiles and runs while silently losing response verification. Wrap it yourself withVerifyingRpcClient::new(..).(Web) Package and API changes
Bump
@miden-sdk/miden-sdkand@miden-sdk/reacttogether — mixing 0.15 and 0.16 packages will not link against the shared WASM ABI.ClientOptions.debugModeremoved;createClient*drops the trailingdebugModeargumentaccountDelta()→accountPatch();AccountStorageDeltaremovedTransactionSummary.accountDelta()is unchanged.TransactionSummary.salt()→userParams()transactions.preview(..)returns only a summary while authorization is pendingnotes.sendPrivaterequiresscanAfterBlockNum; newnotes.sendPrivateOutputnotes.fetchPrivate({ mode: "all" })removedAccountComponent.createNetworkAuth→createNetworkAuthComponentsFungibleAsset.withCallbacks(flag)removedNetworkAccountTargetare priced via a foreign procedure invocation into the targetAdditive:
notes.list({ scriptRoots }),NoteScript.networkAccountConfig(),NoteScript.feeSponsorship(), andcompile.component({ namespace }).If you author MASM through the Web SDK, the language changes apply to you as well —
@account_procedureannotations,moddeclarations, and the new import syntax. See MASM Changes.(React) Send hooks relay through
sendPrivateOutputNoteuseSend,useTransaction, anduseMultiSendnow relay private note output viasendPrivateOutputNote, following thenotes.sendPrivatechange above. If you wrapped these hooks, re-check the relay path.(CLI)
sendrenamed totransferThe
sendsubcommand is nowtransfer. Nothing else changed — every flag, short form, and default is identical.sendis not kept as an alias, so existing scripts fail with an unknown-subcommand error.Affected Code
Migration Steps
Replace
miden-client sendwithmiden-client transferin scripts, aliases, and CI jobs. Change nothing else.(CLI)
account --with-codereplaced byaccount --inspect--with-code, which dumped the account code as one pretty-printed blob, is gone.account --inspect <ID>[:<PROCEDURE>]lists the procedures an account exposes, split into resolved procedures (name, signature, originating package) and unresolved ones listed by MAST root.Affected Code
Migration Steps
account --show <ID> --with-codewithaccount --inspect <ID> --verbose.--inspectis mutually exclusive with--list,--show, and--default.--packageand--verboseboth require--inspect.<unresolved>entries for procedures whose package the CLI cannot find; pass--packageto resolve them.(CLI)
callcounts arguments in field elementscallvalidates argument count against the procedure's signature. In 0.15 it compared against the number of parameters; in 0.16 it compares against the total stack width in field elements. A procedure taking oneWordnow needs four--argsvalues.This change is not in the changelog.
Affected Code
Migration Steps
callwhose procedure takes or returns anything wider than one field element.Raw Signature:line the command prints — it is now the authoritative stack layout.A mismatched count fails with a clear error rather than executing with a mis-shaped stack, so this one fails loudly.
(CLI)
token_symbol_map.toml:idrenamed toaddressThe per-symbol entry key changed from
idtoaddress. The value format is unchanged — it was already a bech32 address — so this is a pure key rename. A file still usingidfails to parse rather than falling back.Affected Code
Migration Steps
id =toaddress =on every entry. Leave the values alone..midendirectory alongsidemiden-client.toml. If you have both a local and a global.midendirectory, update both.(CLI)
initwrites a different package setinitnow writes nine bundled.maspcomponent packages instead of seven.The removal is the CLI-side consequence of dropping
AuthSingleSigAcl, and it is the one most likely to break an existing setup. The changelog mentions only the additions.Two error-reporting changes also landed: running
initwhere a config already exists now names the configured network and points atclear-config, and an unparseable--remote-prover-endpointis a hard error instead of being silently discarded.(CLI) Other changes
--debugandMIDEN_DEBUGremoved. Passing--debugis now a usage error; settingMIDEN_DEBUGis silently ignored.Nonce incremented by: NbecomingNew account nonce: N. This follows from theAccountPatchmove but changes what users read before approving a transaction.swapgained--payback-note-type <private|public>, defaulting toprivate(0.15 hardcoded private). Note thatpswapalready had this flag in 0.15 with the same default. The tag the command tells you to track also changed, from a swap-specific tag to an account-target tag derived from the sender's account ID.consume-notesgained--start-debug-adapter <ADDR>and--record <FILE>;execgained--record.exec --start-debug-adapteralready existed in 0.15. Both require a build with thedapfeature, which is not enabled by default.Common Errors
Migration error: Attempt to migrate a database with a migration number that is too higherror: unrecognized subcommand 'send'transfer.error: unexpected argument '--with-code'--inspect.Procedure '<name>' expects 4 value(s), got 1missing field 'address'parsing the token mapidtoaddress.error: unexpected argument '--debug'no method named account_deltaon a transaction resultaccount_patch().initauth/acl-auth.maspwas removedAuthSingleSigAcl.MASM Changes
The largest change is structural rather than syntactic. In 0.15 the assembler discovered modules by walking directories; in 0.16 it follows an explicit tree of
moddeclarations rooted at your project's root module. This is why the assembler's directory-based entry points disappeared (see VM & Assembler Changes) and whymiden-project.tomlnow requires an explicitpathto that root.The failure mode is worth internalising: forgetting a
moddeclaration is not an error at the declaration site. The module simply is not part of the artifact, and you discover it later as an undefined-symbol error at the call site — or, worse, not at all if nothing calls it.Everything else on this page is mechanical: import rewrites, decorator replacements, and renamed protocol procedures.
Quick Fix
If you encounter errors, continue reading for detailed migration steps.
Every module must be declared with
mod/pub modA submodule's source is resolved as either
<dir>/<name>.masmor<dir>/<name>/mod.masm, relative to the declaring module's directory. The assembler includes only modules reachable through these declarations (#3220).Affected Code
In 0.15 the protocol's kernel root module was a comment; the directory tree was walked implicitly. In 0.16 it enumerates its children, and every intermediate directory gained its own
mod.masm— the protocol repo went from 9mod.masmfiles to 35:Declarations may be interleaved with
usestatements and appear anywhere among the top-level forms. Two other top-level forms landed alongsidemod: an optionalnamespace <path>declaration that names the module explicitly, andextern package "<name>@<version>".Migration Steps
.masmfiles, add amod.masm(or a sibling<dir>.masm) that declares each child withpub mod <name>. Use plainmod <name>for modules that should not be reachable from outside the parent..masmfile is reachable from the root through a chain of declarations.Common Errors
invalid submodule declaration '<name>': could not find module sources at '<dir>/<name>.masm' or '<dir>/<name>/mod.masm'mod <name>with no matching fileinvalid submodule declaration '<name>': submodules must not have the same name as their parentmod fooinsidefoo/mod.masmconflicting submodule paths detected: '<name>' can be parsed from either '<a>' and '<b>', but not both<name>.masmand<name>/mod.masmexistinvalid submodule declaration '<name>': module source '<uri>' is already reachable through another submodule declarationundefined item '<path>'on a call that used to workmoddeclaration.Import syntax: item imports,
asaliases, and global resolutionThe
useform was split into two explicitly distinguished shapes, and import resolution became strictly global (#3220):use some::moduleoruse some::module as alias. Brings a module into scope under a local name. May not bepub.use {item} from some::moduleoruse {a, b as c} from some::module. Brings individual procedures, constants, or types into scope. May bepub, which is how you re-export.Four consequences follow:
pub use <path>for re-exporting a module is gone.pub useis valid only in the braced item form, so you can no longer re-export a module, only named items.->toas.self::prefix.Source-level digest imports (
use 0x<digest>->name) were removed. Direct digest invocation targets (exec.0x…) still work.Affected Code
The alias change, from the protocol's own P2IDE note script:
Re-exporting a procedure from another package:
Re-exporting from your own submodule, from the core library's
stark/mod.masm:Note both halves of that change:
verifieris now a declared submodule, and the re-export path isself::verifierrather than the bare alias. In 0.15 the seconduseresolvedverifierthrough the first — that chaining is exactly what was removed.Plain module imports are unchanged and remain the common case:
Migration Steps
pub use a::b::cre-export aspub use {c} from a::b.use path->aliaswithuse path as alias.usewhose path begins with an alias introduced by an earlierusein the same file to use the full global path.self::. You cannotusea submodule you declared yourself — it is already in scope viamod, so reference it by name.use 0x<digest>->namesource-level digest imports.Common Errors
`pub use` is only supported for braced item importspub use some::modulepub use {item} from some::module.import aliases use `as`; `->` is no longer supporteduse foo->baruse foo as bar.import target '<path>' cannot be resolved through import '<alias>'cannot import submodule '<path>' declared in the same moduleuseof your ownmod-declared childuse.item import target '<path>' resolved to a moduleuse {x} from …wherexis a moduledigest imports are not supporteduse 0x1234->entryexec.0x…directly.debug.*andtracedecorators removedThe
debug.*decorator family and thetracedecorator were removed from the language, along with the CLI--traceflag and the decorator wire slots in the MAST format. Print-style debugging now goes through the newmiden::core::debugmodule, whose procedures are ordinaryemitevents handled host-side (#3169, #3201, #3208).Affected Code
debug.stackexec.debug::print_stackdebug.stack.<n>exec.debug::print_stack(prints the whole stack; there is no top-nform)debug.memexec.debug::print_mem_alldebug.mem.<n>push.<n> exec.debug::print_mem_addrdebug.mem.<n>.<m>push.<m> push.<n> exec.debug::print_mem— takes[start, end], end-exclusivedebug.local,debug.local.<n>,debug.local.<n>.<m>locaddr.<n> exec.debug::print_mem_addrdebug.adv_stack.<n>push.<n> push.0 exec.debug::print_adv_stack, orexec.debug::print_adv_stack_alltrace.<n>The full export list of
miden::core::debugisprint_stack,print_mem,print_mem_addr,print_mem_all,print_adv_stack,print_adv_stack_all,print_adv_map_all, andprint_adv_map_item.On the Rust side,
DebugOptions,Instruction::Debug(..), andInstruction::Trace(..)no longer exist.Migration Steps
debug.andtrace.and replace per the table. Rememberprint_memtakes[start, end]withendexclusive, and both operands are consumed.use miden::core::debugto any module that now calls these.--tracefrom anymiden-vminvocation.CoreLibrary::handlers()includes the stack and memory debug handlers by default; the advice handlers are opt-in, so extend the handler set withmiden_core_lib::handlers::debug::advice_debug_handlersto enableprint_adv_stack*andprint_adv_map*.Core library: the
miden::precompilesnamespace, and removalsThe core MASM package was split into
miden::coreand a newmiden::precompilesnamespace (#3459, #3222). Some procedures that used to live undermiden::core::cryptoare now internal precompile support undermiden::precompiles.miden::core::crypto::hashes::keccak256still exists and still exportshash_bytes,hash, andmerge— it now delegates tomiden::precompiles::hashes::keccak256. Application code should keep calling themiden::core::…facade; reach formiden::precompiles::*only if you are writing your own precompile wrapper.Several modules and procedures were removed outright. EdDSA and SHA-512 are documented as temporarily removed pending precompiles-prover support.
miden::core::crypto::dsa::eddsa_ed25519(whole module)miden::core::crypto::hashes::sha512(whole module)miden::core::crypto::dsa::ecdsa_k256_keccak::verify_prehashverify, or the newverify_bytes.miden::core::sys::log_precompile_requestmiden::core::sys::build_proof_request_key— a different operation, not a rename.miden::core::pcs::fri::frie2f4::preprocessAdditions in the same area:
ecdsa_k256_keccak::verify_bytes, for verifying a signature over a variable-length Keccak256 message held in VM memory (#3563), andmiden::core::math::u256reaching parity with theu64andu128modules (#3167).Migration Steps
ecdsa_k256_keccak::verify_prehashwithverify(word-sized message) orverify_bytes(variable-length message in memory) — and see the ABI change below, which you need either way.sys::log_precompile_request, rewrite it against the deferred-DAG helpers inmiden::precompiles.ECDSA advice and signature ABI changed
The advice-stack layout consumed by
miden::core::crypto::dsa::ecdsa_k256_keccak::verifychanged fromPK[9] | SIG[17]— a 33-byte compressed public key and a 65-byte recoverable signature, byte-packed — toQX[8] | QY[8] | SIG_R[8] | SIG_S[8], native little-endianu32limbs with no recovery byte (#3222). The public-key commitment preimage changed too; see Hashing & Crypto Changes.Affected Code
Two behavioural notes carried in the 0.16 source docs:
verifyaccepts high-ssignatures, because it proves that some witness satisfies the ECDSA equation and(r, s)and(r, n-s)are equivalent witnesses; and it pushes no result word — it traps on failure.Migration Steps
QX[8],QY[8],SIG_R[8],SIG_S[8]as little-endianu32limbs.scheck;verifywill not reject high-s.verify_bytes.do .. while .. endloops addedA tail-controlled loop form was added (#3232). This is additive —
while.trueis unchanged and still performs an entry check.Use it wherever you previously wrote
push.1 while.true … endto force a first iteration.Protocol procedure moves and renames
The protocol MASM surface was reorganised: asset helpers moved from the protocol library into
miden::standards::assets, note creation moved behindmiden::standards::note::note_creator, and severalactive_accountprocedures moved tonative_account.Affected Code
miden::protocol::asset::*(build/validate helpers)miden::standards::assets::*miden::protocol::faucet::create_fungible_asset/create_non_fungible_assetmiden::standards::assetsbuildersmiden::protocol::output_note::create(callable from note scripts)miden::standards::note::note_creator::create_notemiden::protocol::active_account::get_initial_*miden::protocol::native_account::get_initial_*miden::protocol::active_account::has_non_fungible_assethas_assetmiden::protocol::active_note::get_assetsget_initial_assets, plus explicit removal proceduresbasic_wallet::add_assets_to_accountbasic_wallet::move_note_assets_to_accountmiden::standards::account::metadatamiden::standards::account::inspectionMigration Steps
usepath in your MASM per the table above.output_note::createin note scripts withnote_creator::create_note.add_assets_to_accounttomove_note_assets_to_account.Input-note assets are now stateful
In 0.15 a note script read the note's asset list and the kernel reconciled it at the end of execution. In 0.16 the note's initial assets are read with
active_note::get_initial_assets, and assets must be explicitly removed as they are consumed. Partially-consumed notes are representable, so the kernel no longer drains the note for you.Affected Code
The
active_noteasset surface in 0.16, verified fromasm/protocol/src/active_note.masm:active_note::get_storageand the newactive_note::write_storage_to_memoryare the storage-side counterparts.Migration Steps
active_note::get_assetswithactive_note::get_initial_assets.remove_assetfor individual assets, orremove_all_assetsif you consume the note fully.Common Errors
undefined item '<path>'for a procedure that exists on diskmodimport aliases use `as`; `->` is no longer supportedas.`pub use` is only supported for braced item importsundefined instruction debug.stackexec.debug::print_stack.undefined item 'add_assets_to_account'move_note_assets_to_account.remove_asset/remove_all_assets.VM & Assembler Changes
The assembler was rebuilt around a single artifact type. In 0.15 there were three —
Program,Library, andKernelLibrary— serialized as.maslfor libraries. In 0.16 everything is aPackageserialized as.masp, linking goes through onelink_package(package, linkage)method, and the directory-walking*_from_direntry points became*_from_rootentry points that take the root module file. This follows directly from the new explicit module tree: the assembler no longer discovers modules by walking directories, so a directory is no longer a meaningful input.Three wire formats changed at the same time and none are backward compatible, which means every artifact must be rebuilt from source rather than migrated.
Verification was also reshaped: the free
verify(program_info, stack_inputs, stack_outputs, proof)function becameverify(proof, claim)over a singleExecutionClaim, and the caller-managed precompile registry disappeared entirely — deferred proofs are now rehydrated and bound automatically.For the MASM language changes that ship with this VM version — the new
moddeclarations, the rewrittenusesyntax, and the removal of thedebug.*decorators — see MASM Changes. For the changed commitment preimages, see Hashing & Crypto Changes.Quick Fix
miden-project.toml
[lib] namespace = "my::app" + path = "mod.masm"If you encounter errors, continue reading for detailed migration steps.
Library→Packagethroughout the assemblerLibraryandKernelLibrarywere deleted. Every entry point that produced or consumed aLibrarynow produces or consumes aPackage, thelink_*_libraryfamily collapsed intolink_package(package, linkage), andassemble_programreturns aBox<Package>rather than aProgram. Every assemble entry point now takes a package name (#3216, #3220).Affected Code
The complete mapping:
Assembler::with_kernel(sm, kernel_lib: KernelLibrary) -> SelfAssembler::with_kernel(sm, kernel: Arc<Package>) -> Result<Self, Report>link_library(lib, linkage)/link_dynamic_library(lib)/link_static_library(lib)link_package(package: Arc<Package>, linkage: Linkage)with_dynamic_library(lib)/with_static_library(lib)with_package(package: Arc<Package>, linkage: Linkage)compile_and_statically_link_from_dir(dir, namespace)compile_and_statically_link_from_root(root, namespace: Option<&Path>)assemble_library(modules) -> Arc<Library>assemble_library(name, root, support) -> Box<Package>assemble_library_from_dir(dir, namespace) -> Arc<Library>assemble_library_from_root(root, namespace: Option<&Path>) -> Box<Package>assemble_kernel(module) -> KernelLibraryassemble_kernel(name, root, support) -> Box<Package>assemble_kernel_from_dir(sys_path, lib_dir) -> KernelLibraryassemble_kernel_from_root(name, sys_module_path) -> Box<Package>assemble_program(source) -> Programassemble_program(name, source) -> Box<Package>kernel() -> &Kernelkernel() -> &KernelDescriptorwith_profile(&miden_project::Profile)(new)Also removed from the
miden_assemblyre-export surface:Library,KernelLibrary,Parse,ParseOptions,LinkLibraryKind, and thelibrarymodule. Added:Linkage, themodulemodule, and the project-assembly types (ProjectSourceProvider,MasmSourceProvider,ResolvedPackage,AssemblyInterrupted).Migration Steps
Library/KernelLibrarybinding withPackage—Arc<Package>for linking,Box<Package>from the assemble methods.link_dynamic_library(x)/link_static_library(x)/link_library(x, l)intolink_package(x, Linkage::Dynamic)orLinkage::Static.*_from_dircalls to*_from_rootand pass the root module file instead of the directory. Theirnamespaceparameter is nowOption<&Path>rather than a requiredimpl AsRef<Path>.assemble_program,assemble_library, andassemble_kernel. Any string works; the CLI uses the literal"program".assemble_program, call.unwrap_program()(panics on a non-executable package) or.try_into_program()to get theProgramthe processor expects.?toAssembler::with_kernel— it is now fallible.Common Errors
cannot find type Library in miden_assemblyPackage.no method named link_dynamic_librarylink_package(pkg, Linkage::Dynamic).expected Program, found Box<Package>assemble_programreturn type changed.unwrap_program()or.try_into_program().this function takes 2 arguments but 1 was suppliedCore package split into
miden::core+miden::precompilesThe single core MASM package was split into
miden-core(namespacemiden::core) andmiden-precompiles(namespacemiden::precompiles), freeing the baremidennamespace for sibling packages such asmiden-protocol(#3459, #3222). Both must be linked —miden-corehas a runtime dependency onmiden-precompiles.Affected Code
CoreLibrary::SERIALIZEDnow holds themiden-core.maspbytes and a newCoreLibrary::PRECOMPILES_SERIALIZEDholdsmiden-precompiles.masp; in 0.15SERIALIZEDwascore.masl.CoreLibrary::library()andCoreLibrary::verifier_registry()are gone, andCoreLibrary::recursive_verifier_root()is new.Migration Steps
CoreLibrary::default().packages(), or linkpackage()andprecompiles_package()explicitly.CoreLibrary::default().library()with.package().CoreLibrary::verifier_registry(). The deferred-precompile registry now lives in themiden-precompilescrate asmiden_precompiles::registry()and is applied by the verifier automatically.MAST wire format
0.0.4, package format6.0.0, and.maslremovedThree artifact-format changes land together, none backward compatible:
[0,0,3]→[0,0,4], removing inline metadata slots. Assembly-op and debug-variable metadata now live in a separate indexedDebugInfosection (#3201, #3208, #3221). The stripped serialization mode was removed (#3268)..masp) format bumped[4,0,0]→[6,0,0], from consolidating debug sections intoPackageDebugInfo(#3398) and binding dense forest and package digests to stored roots and dependencies (#3334)..masllibrary format no longer exists.Library::LIBRARY_EXTENSIONis gone along with the type;.maspis the only artifact format.Affected Code
Package deserialization is now tiered by trust level. In 0.15
Packageimplemented only the plainDeserializable::read_from:Migration Steps
.masppackage from source under 0.16, and re-serialize every cachedMastForestblob. Invalidate on-disk and database-persisted copies..maslartifacts and any code that reads them.read_from_bytesfor anything from a registry, the network, or a user;read_from_bytes_trustedfor your own build cache when you want debug info retained.ExecutionProofreworked;Verifierreplaces the freeverify_*functionsExecutionProofwas restructured from{ proof, hash_fn, pc_requests }into two envelopes,StarkProofandDeferredProof, and proof serialization changed (#3222). The legacy proof-bound precompile request model was replaced by the deferred-DAG framework inmiden_core::deferred.On the verification side,
verify(program_info, stack_inputs, stack_outputs, proof)andverify_with_precompiles(..)were replaced by aVerifiertype and a freeverify(proof, claim)taking a singleExecutionClaimthat bundles what used to be three arguments (#3422, #3447).Affected Code
ExecutionProofnow exposesmiden_proof() -> &StarkProofanddeferred_proof() -> &DeferredProof, with constructorsExecutionProof::new(miden, deferred)andfrom_parts(bytes, hash_fn, deferred). The 0.15 public fields, the three-argumentnew,stark_proof(),deferred_state(), andinto_parts()are gone.verify_with_precompilesandverify_with_max_deferred_elementsare both removed. Precompile verification is no longer wired up by the caller: the deferred wire is rehydrated under the built-inmiden_precompiles::registry()and bound to the STARK public inputs automatically.proveandprove_synckeep their 0.15 signatures. New in this line:prove_partial,prove_partial_sync, andprove_partial_from_trace_sync.Migration Steps
ExecutionClaim— usuallyExecutionClaim::from_program_info(info, inputs, outputs)— and pass(proof, claim)toverify.PrecompileVerifierRegistryplumbing and calls toverify_with_precompiles/verify_with_max_deferred_elements. UseVerifier::with_max_deferred_elements(n)if you need a non-default budget.ExecutionProofwithmiden_proof()/deferred_proof().verify_partial, do not drop the returnedUnsettled. It is#[must_use]and represents a deferred obligation you must settle or re-expose.AdviceInputs.stackreplaced by theAdviceStacktypeAdviceInputs's publicstack: Vec<Felt>field was replaced by a privateAdviceStack, and thewith_stack/with_stack_values/extend_stackhelpers were removed in favour ofwith_advice_stack(AdviceStack)and theadvice_stack()accessor (#3423).Affected Code
AdviceStackdistinguishes append (bottom) from prepend/push (top) and names the MASM instruction each targets:append_element,append_elements,append_word,append_dword,append_for_adv_push,append_for_adv_pipe,prepend_elements,prepend_word,prepend_stack,push_element, plusconsume_element/consume_word/consume_dwordandinto_elements.AdviceInputs::mapandAdviceInputs::storeremain public fields.Migration Steps
with_stack(iter)withwith_advice_stack(AdviceStack::…), building the stack with the append/prepend methods.with_stack_values(u64s)?withAdviceStack::try_from_values(u64s)?.advice_inputs.stackwithadvice_inputs.advice_stack(), or destructure withinto_parts().append_*adds below (consumed later),prepend_*andpush_elementadd on top (consumed first).ModuleInfo→ModuleDescriptor,Kernel→KernelDescriptorThe module and kernel metadata types were renamed and relocated (#3356).
Affected Code
The module path moved as well: the
librarymodule is gone frommiden_assembly's re-exports, andModuleDescriptorlives undermodule.Migration Steps
Kernel→KernelDescriptorandModuleInfo→ModuleDescriptorat every import and binding.miden_assembly::librarytomiden_assembly::module.miden-project.toml:pathis mandatory on every targetThe
pathkey on[lib]and[[bin]]targets changed from optional to required. It may point at files with extensions other than.masm— a Rust project's source root, for example — which is why the implicit default was dropped (#3216). In the Rust AST,LibTarget::pathandBinTarget::pathmoved fromOption<Span<Uri>>toSpan<Uri>.A project with neither a
[lib]nor any[[bin]]still gets an implicit library target defaulting tomod.masm; that inference is unchanged.Affected Code
miden-project.toml
Migration Steps
pathto every[lib]and[[bin]]in everymiden-project.toml.LibTarget/BinTargetin Rust, drop theSome(..)wrapper aroundpath.miden-vm bundlereworkedmiden-vm bundlenow takes the path to a root.masmmodule instead of a directory,--kernelis a boolean flag instead of taking a path, and the output is a.masppackage instead of a.masllibrary. With--kernelset, the kernel's support modules are derived from the explicitmoddeclarations in the root module (#3216, #3220).Affected Code
--namespaceis now optional in the non-kernel case: if omitted, the assembler expects anamespacedeclaration in the root module, where 0.15 fell back to the directory name. For--kernelthe namespace defaults to$kernel. A new-r/--releaseflag disables debug symbols.Migration Steps
--kernel <path>to a bare--kernelwith the kernel's root module as the positional argument.out.masltoout.masp.mod/pub mod— that is now how support modules are discovered.--namespaceor add anamespacedeclaration to the root module.ProjectAssembler::assemble_with_sourcesremovedProjectAssembler::assemble_with_sources(target, profile, sources)was removed — projects must be assembled from the filesystem (#3216). In its place, project assembly is extensible through theProjectSourceProvidertrait, which lets non-MASM source languages participate (#3375, #3383).Affected Code
ProjectAssembler::assemble(target_selector, profile_name)keeps its 0.15 signature.Migration Steps
assemble_with_sources; write your sources to disk and useassemble, or implement aProjectSourceProvider.assemble_interruptibleand match on theControlFlow.Smaller Rust API removals
These are lower-impact, but each will break a build if you touch it.
MastForest::compactremoved — deduplicate through builders or explicitMastForest::mergeMastForestserialization mode removedDenseMastForestBuilder; non-canonical dense payloads rejectedMastForestBuildersimplified around builder-local refs and immutable finalized forestsprettier::pretty_print_csv,MastNodeId::from_usize_safe,DecoratorId::from_u32_bounded,OpBatch::end_indicesremovedProcessortrait methods moved into their sub-interfacesExecutionOptions::with_overlapped_trace_buildadded, on by defaultmiden-vm run/miden-vm provenow fail when the inferred.inputsfile is missing instead of proceedingResumeContextexposes its debug info outsidemiden-processorand can be built from aPackagebincodetowincode; verifier-side STARK proof deserialization bounded to 64 MiBAeadPoseidon2::key_from_bytesrestored to canonical-Feltdecoding; keys persisted under the brief SHA-256 KDF contract must be re-derivedFelt::from_{u8,u16,u32}are nowconst;Felt::MAXaddedThe
miden-crypto0.26 and 0.27 breaking changes are almost entirely inLargeSmt/LargeSmtForeststorage backends and prover internals, which application code does not call. The one exception worth knowing: the RustCrypto and dalek stack (k256,sha2,sha3,curve25519-dalek,ed25519-dalek,x25519-dalek,hkdf,der) was upgraded (crypto#1045) andrandmoved to 0.10 (crypto#995). Expect version-unification pressure if you depend on those crates directly.Common Errors
unexpected version [0,0,3]reading aMastForest0.0.4unexpected version [4,0,0]reading a package6.0.0.masp..maslfile.masp.cannot find function verify_with_precompilesverify(proof, claim).no field stack on type AdviceInputsadvice_stack()orinto_parts().missing field pathparsingmiden-project.tomlpathis mandatory[lib]and[[bin]].Rust Contract SDK & Compiler
Quick Fix
If you encounter errors, continue reading for detailed migration steps.
Versions
The contract toolchain versions independently of the rest of the stack, and in this release it is genuinely behind.
midenc/ compiler workspacemidencontract SDK crate (andmiden-base-sys,miden-stdlib-sys,miden-sdk-alloc)0.16.0-alpha.4Two consequences worth planning around:
0.16.0-alpha.4and VM0.25, contract code compiled with it sees an earlier snapshot of the 0.16 protocol surface than your client does. The MAST and package wire formats are compatible across VM 0.25 and 0.29.1, so artifacts still load; the skew is in the protocol API surface, not serialization.Component methods must be marked
#[account_procedure]A
#[component]trait's methods are no longer implicitly part of the account interface. Every method that must be callable from a note script, a transaction script, a foreign procedure invocation, or a sibling component now needs#[account_procedure]on the trait declaration, not on theimpl.#[auth_script]and#[account_procedure]cannot be combined in one component. An authentication component keeps using#[auth_script]alone; mixing them is a compile error. Like#[auth_script],#[account_procedure]is recognised by the enclosing#[component]macro and needs no import.Affected Code
The
implblock is unchanged — the attribute is not repeated there.Migration Steps
#[component] trait, add#[account_procedure]above every method called from a note, a transaction script, FPI, or a sibling component.#[auth_script]and must not gain#[account_procedure].#[account(..)]generates one trait per interface#[account(..)]used to generate the referenced component's methods as inherent methods on the wrapper struct. It now generates one trait per referenced interface, named after the interface, and implements it for the wrapper. This lets two components exporting the same method name coexist on one account.Most single-component call sites are unchanged, but two situations break.
Affected Code
The wrapper struct may no longer share its name with a generated trait:
Cross-module call sites need the generated trait in scope. A
#[note]or#[tx_script]entrypoint in the same module sees it automatically; a call site in a different module needs to import the trait, which is named after the interface:Migration Steps
Other changes
Felt, so values that used to be interchangeable now need explicit conversion.AssetAmountis a validated fungible-amount type, matching the protocol and client surfaces.miden-project.tomlrequires an explicitpathon[lib]and every[[bin]]. See VM & Assembler Changes.#[note]reservesget_entrypoint_root, so a note struct cannot define a method with that name, and note structs now implementToFeltRepr.cargo miden newfetches templates from a release bundle rather than embedding them.Additive in this line: typed transaction-script arguments, note constructors, and
println!-style formatting.Common Errors
#[account_procedure]#[auth_script]only.#[account(..)]now generates traitsno method named ..at a cross-module call sitemissing field pathinmiden-project.tomlpathto every target.Final Checklist
Complete these steps to verify your migration:
0.16.0-rc.Nstrings, and renamemiden-tx-batch-provertomiden-tx-batch@miden-sdk/miden-sdkand@miden-sdk/reacttogether; drop anymiden-idxdb-storedependency.maspfrom source and delete cachedMastForestblobs;.maslno longer existsmod/pub moddeclarations so every.masmfile is reachable from your project rootpub use a::b::caspub use {c} from a::b, anduse x->yasuse x as ydebug.*/tracedecorators withmiden::core::debugprocedures, and strip them from production codewith_auth_componentand wrap keys inApprover/ApproverSetAssetId→AssetClassfirst, thenAssetVaultKey→AssetIdaccount_delta()withaccount_patch()— but leaveTransactionSummary::account_delta()aloneXNote::create(..)calls as builders, and cap notes at 16 assetsLibrary/KernelLibrarywithPackage, andlink_*_librarywithlink_packagepathto every[lib]and[[bin]]inmiden-project.tomlExecutionClaimand callverify(proof, claim); discard proofs serialized under 0.15sendtotransfer,--with-codeto--inspect, andidtoaddressintoken_symbol_map.tomlcallinvocation — arguments are now counted in field elements#[account_procedure]and import the traits generated by#[account(..)]cargo build— no errorscargo test— all tests passNeed Help?
rust-sdk,web-sdk,protocol,miden-vm, orcompiler.CHANGELOG.mdfiles carry the full list of changes, including non-breaking features and fixes omitted from this guide.