Skip to main content
Version: 0.13

Components

Components are the building blocks of Miden accounts. Each component defines a storage layout, exposes public methods through a WIT interface, and can be composed with other components on the same account — for example, a wallet component + an auth component + custom logic. This modularity lets you reuse a wallet component across many accounts and test or upgrade components independently.

The #[component] macro

Apply #[component] to both a struct definition and its impl block:

use miden::{component, felt, Felt, StorageMap, StorageMapAccess, Word};

#[component]
struct CounterContract {
#[storage(description = "counter contract storage map")]
count_map: StorageMap,
}

#[component]
impl CounterContract {
pub fn get_count(&self) -> Felt {
let key = Word::from_u64_unchecked(0, 0, 0, 1);
self.count_map.get(&key)
}

pub fn increment_count(&mut self) -> Felt {
let key = Word::from_u64_unchecked(0, 0, 0, 1);
let current_value: Felt = self.count_map.get(&key);
let new_value = current_value + felt!(1);
self.count_map.set(key, new_value);
new_value
}
}

The macro generates:

  1. WIT interface definition describing the component's public API
  2. Storage metadata mapping slot names to slot IDs (derived from the component package + field name)
  3. Export bindings for the Miden runtime

Struct definition

The struct defines the component's storage layout:

#[component]
struct MyContract {
#[storage(description = "owner account identifier")]
owner: Value,

#[storage(description = "user balances")]
balances: StorageMap,
}

Storage fields

Each field must be either Value (single-slot) or StorageMap (map-slot), annotated with #[storage]:

#[storage(description = "human-readable description")]
field_name: Value,

#[storage(description = "human-readable description")]
field_name: StorageMap,

The description is optional and becomes part of the generated metadata. Slot IDs are derived from the component package name (from [package.metadata.component]) and the field name, so renaming a field changes the slot ID. Ordering does not matter, and slot(N) is not supported.

Impl block — methods

Read methods (&self)

Methods that take &self are read-only — they can query storage and account state but cannot modify anything:

pub fn get_balance(&self, depositor: AccountId) -> Felt {
let key = Word::from([depositor.prefix, depositor.suffix, felt!(0), felt!(0)]);
self.balances.get(&key)
}

Write methods (&mut self)

Methods that take &mut self can modify state — write to storage, add/remove assets, create notes:

pub fn deposit(&mut self, asset: Asset) {
self.add_asset(asset);
}

Read methods (&self) produce proofs that don't include state transitions. Write methods (&mut self) produce proofs that do. The distinction is enforced by the compiler and determines which kernel operations are available.

Private methods

Methods without pub are private — they can be called from other methods in the same component but are not exported in the WIT interface:

fn require_initialized(&self) {
let state: Word = self.initialized.read();
assert!(state[0] == felt!(1));
}

pub fn do_something(&mut self) {
self.require_initialized();
// ...
}

Supported parameter and return types

Public methods can use these types in their signatures:

TypeAs parameterAs return
FeltYesYes
WordYesYes
AssetYesYes
AccountIdYesYes
NoteIdxYesYes
Custom types with #[export_type]YesYes

Auto-generated methods

The #[component] macro automatically provides methods on self through the NativeAccount and ActiveAccount traits.

Always available (via NativeAccount trait)

// Add an asset to the account vault
self.add_asset(asset: Asset) -> Asset

// Remove an asset from the account vault
self.remove_asset(asset: Asset) -> Asset

// Increment the account nonce (replay protection)
self.incr_nonce() -> Felt

// Compute commitment of account state changes (read-only)
self.compute_delta_commitment() -> Word

// Check if a procedure was called during this transaction (read-only)
self.was_procedure_called(proc_root: Word) -> bool

Always available (via ActiveAccount trait on &self)

// Get the account ID
self.get_id() -> AccountId

// Get the account nonce
self.get_nonce() -> Felt

// Get fungible asset balance for a faucet
self.get_balance(faucet_id: AccountId) -> Felt

// Check non-fungible asset ownership
self.has_non_fungible_asset(asset: Asset) -> bool

// Get storage and vault commitments
self.get_vault_root() -> Word
self.compute_commitment() -> Word
self.compute_storage_commitment() -> Word
// ... and more (see API Reference)

For the full list of auto-generated methods, see the Cheatsheet. To export your own types for use in public method signatures, see Custom Types. For the complete account and vault query API, see Account Operations.

Full API docs on docs.rs: miden (top-level — #[component] macro)