Skip to main content
Version: Next (unstable)

Getting started with Cargo

As part of the Miden compiler toolchain, we provide a Cargo extension, cargo-miden, which provides a template to spin up a new Miden project in Rust, and takes care of orchestrating rustc and midenc to compile the Rust crate to a Miden package.

Installation​

Currently, midenc (and as a result, cargo-miden), requires the nightly Rust toolchain, so make sure you have it installed first:

rustup toolchain install nightly-2026-09-01

NOTE: You can also use the latest nightly, but the specific nightly shown here is known to work.

To install the extension:

cargo +nightly-2026-09-01 install cargo-miden --locked

This will take a minute to compile, but once complete, you can run cargo help miden or just cargo miden to see the set of available commands and options.

To get help for a specific command, use cargo miden help <command> or cargo miden <command> --help. For build, use cargo miden build --help: every argument of cargo miden build is forwarded to midenc, so that is midenc's own help, and it is the one that lists them.

Creating a new project​

Your first step will be to create a new Rust project set up for compiling to Miden:

cargo miden new foo

In this above example, this will create a new directory foo, containing a Cargo project for a crate named foo, generated from our Miden project template.

Templates are released independently of the compiler, so cargo miden new fetches the newest compatible template bundle at run time and falls back to a copy embedded in cargo-miden — you get template fixes without reinstalling, and project creation still works with no network access.

Pass --force-download to require the released bundle and fail rather than silently falling back, which is useful for confirming what a released template actually produces:

cargo miden new foo --force-download

Pass --template-path <dir> to generate from templates on disk instead.

The template we use sets things up so that you can pretty much just build and run. Since the toolchain depends on Rust's native WebAssembly target, it is set up just like a minimal WebAssembly crate, with some additional tweaks for Miden specifically.

Out of the box, you will get a Rust crate that depends on the Miden SDK, and sets the global allocator to a simple bump allocator we provide as part of the SDK, and is well suited for most Miden use cases, avoiding the overhead of more complex allocators.

As there is no panic infrastructure, panic = "abort" is set, and the panic handler is configured to use the native WebAssembly unreachable intrinsic, so the compiler will strip out all of the usual panic formatting code.

Compiling to Miden package​

Now that you've created your project, compiling it to Miden package is as easy as running the following command from the root of the project directory:

cargo miden build --release

This will emit the compiled artifacts to target/miden/release/foo.masp, and print the path of the compiled Miden package on success.

cargo miden build is a thin wrapper: every argument after build is passed to midenc unchanged, so cargo miden build <args> compiles what midenc <args> would in the same directory. Run cargo miden build --help to see the options — that help is midenc's.

The project is located the same way too: the miden-project.toml in the current directory, or the Cargo.toml there when no Miden manifest exists beside it, or the one --manifest-path names. Logging is controlled by MIDENC_TRACE, the same variable midenc reads.

Naming​

Account component, note and transaction script projects are named by the [lib].namespace key of their miden-project.toml. It is a Miden path of exactly three segments, ns::pkg::iface, where each segment is a snake_case identifier: lowercase ASCII letters and digits in words joined by single _, starting with a letter ([a-z][a-z0-9]*(_[a-z0-9]+)*, e.g. counter_contract or wallet2), and not a WIT or Rust 2024 keyword such as list, type, match or gen. The first two segments form the WIT package id, so they must be unique among all crates a consumer links, whatever their versions; for the same reason the last segment must not be core_types, which names the SDK's own WIT interface. The namespace must neither be nor lie under a library that components link against: miden::base (the SDK's own WIT package), miden::protocol, miden::core, intrinsics and std. Projects created with cargo miden new default to miden::<package>::<package> (package name in snake_case):

[package]
name = "counter-contract"
version = "0.1.0"

[lib]
kind = "account-component"
namespace = "miden::counter_contract::counter_contract"
path = "src/lib.rs"

The SDK macros derive every other name from the namespace:

ItemRuleExample
WIT interface idns:pkg/iface@<[package].version>, _ replaced by -miden:counter-contract/[email protected]
Exported procedure<namespace>::<Rust fn name>miden::counter_contract::counter_contract::get_count
Storage slot<namespace>::<field name>miden::counter_contract::counter_contract::count_map
Transaction script entrypoint<namespace>::runmiden::basic_wallet_tx_script::basic_wallet_tx_script::run

Paths never contain a version. Changing the namespace changes procedure paths and storage slot names, so treat it as part of the component's on-chain interface. Exported WIT function names must not start with fpi- or dyncall- (fpi_/dyncall_ in Rust): the compiler reserves both prefixes for the imports the SDK macros generate. A storage field's name is used as written in its slot name and, for a StoredProcedure slot, in its <namespace>::dyncall::<field> import path: a raw identifier loses its r# (r#type gives <namespace>::type), and a field name starting with _ or containing a non-ASCII character is rejected, since a slot name segment cannot start with an underscore and allows only ASCII letters, digits and _.

All exports of a package sit directly under its namespace. Calls into a dependency use the dependency's paths as they are, e.g. a note calling the basic wallet calls miden::basic_wallet::basic_wallet::receive_asset.

If you write WIT by hand instead of generating it with the SDK macros, every function must carry its full Miden path in an @external-id attribute, otherwise compilation fails:

interface counter-contract {
@external-id("miden::counter_contract::counter_contract::get_count")
get-count: func() -> felt;
}

The parent of an imported function's path (the path without the function name) names a dependency component, so the parents of a component's imports must not nest in one another or in the component's own namespace: importing both acme::math::add and acme::math::u64::add is rejected. Foreign procedure invocation (FPI) imports generated by #[account(...)] and the stored-procedure imports generated by #[component_storage] for StoredProcedure slots are exempt: their paths (<namespace>::fpi::<dependency path>::<function> and <namespace>::dyncall::<field>) nest in the component's own namespace by design, but they call a procedure by its root and declare no dependency component. The other paths generated by the SDK macros satisfy the rule. A WIT import names a procedure of another component. In a library package (the core library or a Miden Assembly library dependency), procedures tagged with a protocol role attribute (@account_procedure, @note_script, @auth_script, @tx_script), such as the standard account components of the miden-standards library, stay importable through WIT, and every other procedure of a library package is rejected: bind a plain library procedure natively with an extern "C" function carrying its path in #[link_name]. The procedures of component packages stay importable.

The core Wasm module of a component is named after the crate, so a crate named like the Miden path leaf of one of its exports (a crate swap exporting miden::swap::swap::swap) or named init is rejected: rename the crate (or, for an export clash, the function). An executable without a target namespace is rooted at its crate name, which must not be a reserved library root (miden, intrinsics, std); rename the crate.

When compiling a bare .wasm or .wat component without a manifest or an explicit namespace, the namespace is inferred only when every export has the same parent path, which becomes the namespace; exports under different parents are an error, and a component that exports nothing is an error unless a namespace is given. If a namespace is given (by the manifest or --name) and does not match the exports, compilation fails.

Running a compiled Miden VM program​

Use miden-debug to execute the compiled package. See Debugging programs for installation and the input-file format.

miden-debug target/miden/release/foo.masp --inputs some_inputs.toml

This opens the interactive debugger. For terminal commands, add --repl; for a non-interactive run, use --commands with a debugger command file, as described in the guide. Run miden-debug --help for the available options.

Examples​

Check out the examples for some cargo-miden project examples.