Skip to main content
Version: 0.16

Part 0: Project Setup

In this section, you'll create a new Miden project and set up the workspace structure for our banking application. By the end, you'll have a working project that compiles successfully.

What You'll Build in This Part​

By the end of this section, you will have:

  • Created a new Miden project using miden new
  • Understood the workspace structure
  • Renamed and configured the project for our bank
  • Successfully compiled a minimal account component

Prerequisites​

Before starting, ensure you have completed the Get Started installation guide and have:

  • Rust toolchain installed and configured
  • midenup toolchain installed with Miden CLI tools (miden command available)

Verify your installation:

>_ Terminal
miden --version
Expected output
The Miden toolchain porcelain:

Environment:
- cargo version: cargo 1.98.1 (797e8a9bc 2026-08-05).

Midenup:
- midenup + miden version: 1.0.0.
- active toolchain version: 0.16.0.
- ...

Step 1: Create the Project​

Create a new Miden project using the CLI:

>_ Terminal
miden new miden-bank
cd miden-bank

This creates a workspace with the following structure:

miden-bank/
├── contracts/ # Smart contract code
│ ├── counter-account/ # Example account contract (we'll replace this)
│ └── increment-note/ # Example note script (we'll replace this)
├── integration/ # Tests and deployment scripts
│ ├── src/
│ │ ├── bin/ # Executable scripts for on-chain interactions
│ │ ├── lib.rs
│ │ └── helpers.rs # Helper functions for tests
│ └── tests/ # Test files
├── Cargo.toml # Workspace root
├── miden-toolchain.toml # Miden toolchain specification
└── rust-toolchain.toml # Rust toolchain specification

The project follows Miden's design philosophy:

  • contracts/: Your smart contract code (account components, note scripts, transaction scripts)
  • integration/: All on-chain interactions, deployment scripts, and tests

Step 2: Set Up the Bank Account Contract​

We'll replace the example counter-account with our bank-account. First, rename the directory:

>_ Terminal
mv contracts/counter-account contracts/bank-account

A contract is configured by three files: a minimal Cargo manifest, a Miden project manifest, and a Cargo build config.

First, update the Cargo.toml inside contracts/bank-account/. Keep the generated build.rs and its build dependency; they prepare the package cache for Cargo and IDE builds:

contracts/bank-account/Cargo.toml
[package]
name = "bank-account"
version = "0.1.0"
edition = "2021"

[lib]
crate-type = ["cdylib"]

[dependencies]
miden = "0.14"

[build-dependencies]
miden-sdk-build-script-support = "0.14"

Next, create contracts/bank-account/miden-project.toml. This is the Miden-specific project manifest that tells the compiler what kind of artifact to build and which package namespace to export:

contracts/bank-account/miden-project.toml
[package]
name = "bank-account"
version = "0.1.0"

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

[dependencies]
miden-core = "*"
miden-protocol = "*"

Finally, create contracts/bank-account/.cargo/config.toml so the contract always builds for the WebAssembly target with the miden cfg enabled (this also makes editor/LSP workflows resolve the right code):

contracts/bank-account/.cargo/config.toml
[build]
target = "wasm32-wasip2"

[target.wasm32-wasip2]
# Force-enable `cfg(miden)` for Miden-VM-targeted builds (including editor/LSP workflows).
rustflags = ["--cfg", "miden"]

Key Configuration Options​

FieldFileDescription
crate-type = ["cdylib"]Cargo.tomlRequired for WebAssembly compilation
kind = "account-component"miden-project.tomlTells the compiler this is an account component
namespace = "miden:bank-account/bank@..."miden-project.tomlThe package namespace used for cross-component calls
target = "wasm32-wasip2".cargo/config.tomlCompile target for the Miden VM

Step 3: Create a Minimal Bank Component​

Replace the contents of contracts/bank-account/src/lib.rs with a minimal bank structure:

contracts/bank-account/src/lib.rs
// Do not link against libstd (i.e. anything defined in `std::`)
#![no_std]
#![feature(alloc_error_handler)]

#[macro_use]
extern crate alloc;

use miden::*;

/// Storage layout for the bank account component.
///
/// We'll build this up throughout the tutorial. The `#[component_storage]`
/// attribute marks the struct that defines the component's named storage slots.
#[component_storage]
struct BankStorage {
/// Tracks whether the bank has been initialized (deposits enabled).
/// Word layout: [is_initialized (0 or 1), 0, 0, 0]
#[storage(description = "initialized")]
initialized: StorageValue<Word>,

/// Maps (depositor AccountId, faucet ID) -> balance (as Felt).
/// We'll use this to track user balances in Part 1.
#[storage(description = "balances")]
balances: StorageMap<Word, Felt>,
}

/// API of the bank account component.
///
/// The `#[component]` trait declares the methods the compiler exports as the
/// component's public WIT interface.
#[component]
trait Bank {
/// Initialize the bank account, enabling deposits.
#[account_procedure]
fn initialize(&mut self);

/// Get the bank-tracked balance for a depositor and specific asset type.
#[account_procedure]
fn get_depositor_balance(&self, depositor: AccountId, asset: Asset) -> Felt;
}

#[component]
impl Bank for BankStorage {
fn initialize(&mut self) {
// Check not already initialized
let current: Word = self.initialized.get();
assert!(
current[0].as_canonical_u64() == 0,
"Bank already initialized"
);

// Set initialized flag to 1
let initialized_word = Word::from([felt!(1), felt!(0), felt!(0), felt!(0)]);
self.initialized.set(initialized_word);
}

fn get_depositor_balance(&self, depositor: AccountId, asset: Asset) -> Felt {
let key = Word::from([
depositor.prefix,
depositor.suffix,
asset.key[3], // faucet id prefix
asset.key[2], // faucet id suffix (folds in the asset metadata byte)
]);
self.balances.get(key)
}
}

This is our starting point with two storage slots:

  • initialized: A StorageValue<Word> slot to track whether the bank is ready
  • balances: A StorageMap<Word, Felt> to track user balances (we'll use this starting in Part 1)

The #[component_storage] struct declares the storage layout, the #[component] trait declares the exported API, and #[component] impl Bank for BankStorage implements it. Any private helper methods you add later live in a separate plain impl BankStorage block — the #[component] macro only exports trait methods.

Mark each method that notes, transaction scripts, or other account components can call with #[account_procedure] on the trait declaration. Unmarked methods are exported but are not part of the account procedure table.

The balance accessor is named get_depositor_balance rather than get_balance so it does not collide with the built-in ActiveAccount::get_balance vault method that the account wrapper generates.

Contracts are excluded from the Cargo workspace and built independently by the Miden toolchain. Each contract carries its own miden guest dependency plus a miden-project.toml. Only the integration crate remains a workspace member.

Because contracts are excluded, your IDE (rust-analyzer) may not provide completions or diagnostics for contract code. This is expected — contracts are built independently using miden build.

Step 4: Build and Verify​

Let's verify everything compiles correctly:

>_ Terminal
cd contracts/bank-account
miden build --release
Expected output
   Compiling bank-account v0.1.0 (/path/to/miden-bank/contracts/bank-account)
Finished `release` profile [optimized] target(s)

The compiled output is stored in target/miden/release/bank-account.masp.

A .masp file is a Miden Assembly Package. It contains the compiled MASM (Miden Assembly) code and metadata needed to deploy and interact with your contract.

The notes and transaction script call the bank account through generated bindings. Their miden-project.toml files declare the bank as an automatic path dependency, so miden build compiles it before the dependent contract and provides its interface and procedure roots to #[account(...)].

Optional: Verify Your Setup​

This is an optional self-check. It loads the package compiled in Step 4 and creates a test account locally.

Create a new test file:

integration/tests/part0_setup_test.rs
use miden_client::account::{
component::{BasicWallet, InitStorageData, StorageValueName},
AccountBuilder, AccountComponent, AccountType, StorageSlotName,
};
use miden_client::{utils::Deserializable, Word};
use miden_mast_package::Package;
use miden_standards::account::auth::NoAuth;
use std::path::Path;

#[test]
fn test_bank_account_loads() -> anyhow::Result<()> {
// Load the bank account package compiled in Step 4.
let package_path = Path::new(env!("CARGO_MANIFEST_DIR"))
.join("../contracts/bank-account/target/miden/release/bank-account.masp");
let bank_package = Package::read_from_bytes(&std::fs::read(package_path)?)?;

// The `initialized` value slot has no schema default, so it must be seeded
// (with a zero Word = uninitialized) or `AccountComponent::from_package`
// errors with `InitValueNotProvided`. The `balances` map slot defaults to empty.
let initialized_slot =
StorageSlotName::new("bank_account::bank::initialized")
.expect("Valid slot name");

let mut init_storage_data = InitStorageData::default();
init_storage_data.insert_value(
StorageValueName::from_slot_name(&initialized_slot),
Word::default(),
)?;
let bank_component = AccountComponent::from_package(&bank_package, &init_storage_data)?;
assert_eq!(bank_component.procedures().count(), 2);

let bank_account = AccountBuilder::new([3u8; 32])
.account_type(AccountType::Public)
.with_component(bank_component)
.with_component(BasicWallet)
.with_component(NoAuth)
.build_existing()?;

// Verify the account was created
println!("Bank account created with ID: {}", bank_account.id().to_hex());
println!("Part 0 setup verified!");

Ok(())
}

Run the test from the project root:

>_ Terminal
cargo test --package integration test_bank_account_loads -- --nocapture
Expected output
   Compiling integration v0.1.0 (/path/to/miden-bank/integration)
Finished `test` profile [unoptimized + debuginfo] target(s)
Running tests/part0_setup_test.rs

running 1 test
Bank account created with ID: 0x...
Part 0 setup verified!
test test_bank_account_loads ... ok

test result: ok. 1 passed; 0 failed; 0 ignored

What We've Built So Far​

At this point, you have:

ComponentStatusDescription
bank-accountMinimalInitialization flag + balance storage
deposit-noteNot startedComing in Part 4
withdraw-request-noteNot startedComing in Part 7
init-tx-scriptNot startedComing in Part 6

Your bank can be created, but doesn't do anything useful yet. In the next parts, we'll add:

  1. Part 1: Deeper dive into storage (Value vs StorageMap)
  2. Part 2: Business rules and constraints
  3. Part 3: Asset handling for deposits
  4. And more...

Key Takeaways​

  1. miden new creates a complete project workspace with contracts and integration folders
  2. Account components are defined with a #[component_storage] struct plus a #[component] trait and impl
  3. Storage slots are declared with #[storage(description = "...")] attributes
  4. miden build compiles Rust to Miden Assembly (.masp package)
  5. Tests verify that your code works before moving on

Next Steps​

Now that your project is set up, let's dive deeper into account components and storage in Part 1: Account Components and Storage.