Rust SDK & Compiler Changes
This section covers the miden Rust SDK and compiler (the miden crate and midenc), used to write Miden smart contracts, notes, and transaction scripts in Rust. The relevant step is the SDK's 0.12 → 0.13 release, which is the line aligned with VM 0.23 / protocol 0.15. (The miden SDK carries its own version number, distinct from the protocol and client crate versions used elsewhere in this guide.)
The SDK macros were reworked: #[component] is now a trait + a storage struct, a miden-project.toml manifest is required, accounts must be declared explicitly with #[account(...)], and the tx-kernel bindings were aligned with protocol v0.15. The macro changes touch every account component, every authentication component, and every note/tx-script that references an account. Work through the sections in order: rewrite the component (1), add the project manifest (2), update account references (3), then the bindings (4).
#[component] is now a trait + a storage struct
Summary
#[component] no longer applies to a struct or an inherent impl. An account component is now three pieces:
- a
#[component_storage]struct holding the#[storage(...)]fields, - a
#[component]trait declaring the API (the trait name yields the WIT interface), and - a
#[component] impl Trait for Storageblock providing the behavior.
Method receivers (&self / &mut self) and method bodies are unchanged.
Affected Code
Before (0.12):
use miden::{component, felt, Felt, StorageMap, Word};
#[component]
struct CounterContract {
#[storage(description = "counter contract storage map")]
count_map: StorageMap<Word, Felt>,
}
#[component]
impl CounterContract {
pub fn get_count(&self) -> Felt {
let key = Word::new([felt!(0), felt!(0), felt!(0), felt!(1)]);
self.count_map.get(key)
}
pub fn increment_count(&mut self) -> Felt {
let key = Word::new([felt!(0), felt!(0), felt!(0), felt!(1)]);
let new_value = self.count_map.get(key) + felt!(1);
self.count_map.set(key, new_value);
new_value
}
}
After (0.13):
use miden::{component, component_storage, felt, Felt, StorageMap, Word};
// 1. storage fields move to a `#[component_storage]` struct
#[component_storage]
struct CounterContractStorage {
#[storage(description = "counter contract storage map")]
count_map: StorageMap<Word, Felt>,
}
// 2. the API becomes a `#[component]` trait (its name is the WIT interface)
#[component]
trait CounterContract {
fn get_count(&self) -> Felt;
fn increment_count(&mut self) -> Felt;
}
// 3. the behavior is a `#[component] impl Trait for Storage` block
#[component]
impl CounterContract for CounterContractStorage {
fn get_count(&self) -> Felt {
let key = Word::new([felt!(0), felt!(0), felt!(0), felt!(1)]);
self.count_map.get(key)
}
fn increment_count(&mut self) -> Felt {
let key = Word::new([felt!(0), felt!(0), felt!(0), felt!(1)]);
let new_value = self.count_map.get(key) + felt!(1);
self.count_map.set(key, new_value);
new_value
}
}
Authentication components migrate the same way. #[auth_script] was already required in 0.12; in 0.13 it simply moves onto the trait method declaration (the impl method no longer repeats it):
// before (0.12): inherent impl
#[component]
struct AuthComponent;
#[component]
impl AuthComponent {
#[auth_script]
pub fn auth_procedure(&mut self, _arg: Word) { /* ... */ }
}
// after (0.13): trait + storage, `#[auth_script]` on the trait method
#[component_storage]
struct AuthComponentStorage;
#[component]
trait AuthComponent {
#[auth_script]
fn auth_procedure(&mut self, _arg: Word);
}
#[component]
impl AuthComponent for AuthComponentStorage {
fn auth_procedure(&mut self, _arg: Word) { /* ... */ }
}
Migration Steps
- Move each component's
#[storage(...)]fields into a#[component_storage]struct. - Declare the API as a
#[component] trait(the trait name becomes the WIT interface). - Provide the behavior in a
#[component] impl Trait for Storageblock; drop thepubon the method bodies. - For auth components, move
#[auth_script]from theimplmethod onto the trait method declaration.
miden-project.toml is now a required file
Summary
0.13 introduces a dedicated project manifest, miden-project.toml, placed next to Cargo.toml at the crate root. The Miden-specific configuration that previously lived in Cargo.toml [package.metadata.*] now lives here, and the proc-macros read it to resolve the WIT interface name, the project kind, and any FPI/sibling dependencies. Building a 0.13 project without it fails (for components, with an undefined ::init link error).
Affected Code
Create miden-project.toml like this (account component without dependencies):
[package]
name = "counter-contract" # crate name; kebab-case
version = "0.1.0" # project version; supplies the WIT `@version`
[lib]
kind = "account-component" # project kind: "account-component" | "note" | "tx-script"
# Full WIT id: miden:<package>/<interface>@<version>
# <package> = the kebab-cased [package].name
# <interface> = the kebab-cased `#[component]` trait name (here: `CounterContract`)
# <version> = the [package].version above
namespace = "miden:counter-contract/[email protected]"
[dependencies]
miden-core = "*"
miden-protocol = "*"
# account components only: which account types may host this component
[package.metadata.miden]
supported-types = ["RegularAccountUpdatableCode"]
Walking through the fields:
[package]—nameandversion. The version feeds the@versionsuffix of the WIT id, so bumping it changes the component's interface id.[lib].kind— the project kind:account-component,note, ortx-script.[lib].namespace— the fullmiden:<package>/<interface>@<version>WIT id. The interface segment must equal the kebab-cased#[component]trait name; a mismatch fails to link with an undefined::init. (Fornote/tx-scriptprojects, the interface segment is the project's own name rather than a component trait.)[dependencies]— the Miden crates the project links against (miden-core,miden-protocol), plus any FPI/sibling dependency packages bypath(see the next section).[package.metadata.miden].supported-types— account components only.
Storage slot names derive from the [lib].namespace interface segment (which mirrors the component trait name), and slot names feed StorageSlotId derivation. Renaming the component trait (and updating [lib].namespace to match) re-keys the storage slot ids of an already-deployed component. Keep the trait name stable across upgrades of a live component.
For a project that calls another account/component (FPI or sibling), add the dependency in both [dependencies] (the package) and [package.metadata.miden.dependencies] (its generated WIT):
[dependencies]
miden-core = "*"
miden-protocol = "*"
basic-wallet = { path = "../basic-wallet" }
# the dependency's generated WIT, used to generate the call bindings
[package.metadata.miden.dependencies]
basic-wallet = { wit = "../basic-wallet/target/generated-wit/" }
The [package.metadata.miden.dependencies].<name>.wit entry is what the macros read to generate the typed call bindings, and it is the same entry used by note scripts, by account components doing FPI, and by sibling component calls.
Migration Steps
- Add a
miden-project.tomlnext toCargo.tomlwith[package],[lib].kind, and[lib].namespace. - Set the
[lib].namespaceinterface segment to the kebab-cased#[component]trait name. - Move any
[package.metadata.*]Miden config out ofCargo.tomlinto this file. - For account components, list
supported-types. For FPI/sibling calls, add the dependency in[dependencies]and its generated WIT under[package.metadata.miden.dependencies].
Accounts: declare #[account(...)] explicitly with an interface
Summary
The auto-generated crate::bindings::Account struct is gone. Declare the account explicitly with #[account(...)] and use that type as the note/tx-script entrypoint account parameter. The dependency reference now requires the exported WIT interface (kebab-cased and validated): write #[account(basic_wallet::BasicWallet)], not #[account(basic_wallet)].
Affected Code
Before (0.12):
use miden::{active_note, note, AccountId, Word};
use crate::bindings::Account; // auto-generated
#[note]
struct P2idNote {
target_account_id: AccountId,
}
#[note]
impl P2idNote {
#[note_script]
pub fn script(self, _arg: Word, account: &mut Account) {
for asset in active_note::get_assets() {
account.receive_asset(asset);
}
}
}
After (0.13):
use miden::{account, active_note, note, AccountId, Word};
// declare the native account explicitly; pick the package's WIT interface
#[account(basic_wallet::BasicWallet)]
pub struct Wallet;
#[note]
struct P2idNote {
target_account_id: AccountId,
}
#[note]
impl P2idNote {
#[note_script]
pub fn script(self, _arg: Word, account: &mut Wallet) {
for asset in active_note::get_assets() {
account.receive_asset(asset);
}
}
}
The same #[account(...)] type serves two roles. Passed to a #[note]/#[tx_script] entrypoint it is the transaction's native (active) account. Constructed with new(account_id) it is a foreign account caller, whose method calls are routed through execute_foreign_procedure (FPI):
let counter = CounterContract::new(counter_account_id);
let count = counter.get_count();
FPI is not limited to note/tx scripts — an account component can call another account through FPI too. Declare the #[account(...)] wrapper in the component crate (and the dependency in miden-project.toml) and use it from inside the #[component] impl:
#[account(callee_account::CounterContract)]
struct CalleeAccount;
#[component]
impl CallerAccount for CallerAccountStorage {
fn read_foreign_count(&self, callee_account_id: AccountId) -> Felt {
let callee = CalleeAccount::new(callee_account_id);
callee.get_count(key)
}
}
Migration Steps
- Remove
use crate::bindings::Account;and any reliance on the auto-generatedAccount. - Declare each account explicitly with
#[account(package::Interface)](kebab-cased exported interface, not just the package name). - Use that type as the
&mutaccount parameter of your note/tx-script entrypoints. - For FPI, construct the same type with
new(account_id)and add the callee as a dependency inmiden-project.toml.
Tx-kernel bindings: protocol v0.15
Summary
The SDK bindings were aligned with VM 0.23 / protocol 0.15 (miden-field bumped to ^0.25).
Felt::newis now fallible — it returnsResult<Felt, _>instead ofFelt. ReplaceFelt::new(x)withFelt::new(x).unwrap()(or handle the error). Thefelt!(x)macro is unchanged and remains the preferred constructor for literals.asset::{create_fungible_asset, create_non_fungible_asset}now take a trailingenable_callbacks: boolargument.active_account::{get_balance, get_initial_balance}(and the correspondingActiveAccounttrait methods) now take an asset keyWordinstead of a faucetAccountId.faucet::{mint, burn}no longer return anAsset; thefaucet::{mint_value, burn_value}helpers were removed. Use the returned value-free API to match the tx kernel.output_note::set_attachmentwas removed. The attachment shape is selected by function instead of a runtimeattachment_kindargument.
Affected Code
// before: output_note::set_attachment(note_idx, scheme, kind, attachment);
// after, for a single word:
output_note::add_word_attachment(note_idx, scheme, attachment);
// or `add_attachment` for a commitment, `add_attachment_from_memory` for multiple words.
Scalar Felt values stored in StorageValue<Felt> / StorageMap<_, Felt> are now packed into the low word limb ([v, 0, 0, 0]) instead of the high limb ([0, 0, 0, v]), matching protocol v0.15. This is transparent when you recompile and redeploy, but state written by 0.12 code is read back differently by 0.13 code.
Migration Steps
- Wrap
Felt::new(x)calls in.unwrap()(or handle theResult); keep usingfelt!(x)for literals. - Add the trailing
enable_callbacksargument toasset::create_fungible_asset/create_non_fungible_asset. - Pass an asset key
Wordtoactive_account::get_balance/get_initial_balanceinstead of a faucetAccountId. - Drop the return values of
faucet::mint/burn; remove uses ofmint_value/burn_value. - Replace
output_note::set_attachmentwithadd_word_attachment/add_attachment/add_attachment_from_memory. - Re-deploy contracts that persist scalar
Feltstorage — the low-limb packing means0.12state is not read back identically.
New in 0.13 (no migration required)
These are additive and do not require changes to existing code:
- Sibling component calls —
#[component(package::Interface, ...)]on the component trait lets one component call another component deployed on the same account. println!— aprintln!macro (anddebug::println) for emitting a debug message during execution.