Skip to main content
Version: 0.14 (unstable)

Note Scripts

Note scripts define the logic that executes when a note is consumed. They determine who can consume a note and what happens to its assets.

The #[note] pattern

A note script consists of a struct (holding note inputs) and an impl block with a #[note_script] method:

use miden::{AccountId, Word, active_note, note};

#[note]
struct MyNote {
target_account_id: AccountId,
}

#[note]
impl MyNote {
#[note_script]
pub fn run(self, _arg: Word, account: &mut Account) {
// Script logic here
}
}

The #[note] macro:

  1. Generates the WIT note-script interface
  2. Deserializes note inputs into struct fields
  3. Exports the run function as the note's entry point

Struct fields as note inputs

Fields on the #[note] struct are populated from the note's input data when the note is consumed:

#[note]
struct MyNote {
target_account_id: AccountId, // Deserialized from note inputs
}

The compiler maps struct fields to note inputs based on their order and type. Supported field types include AccountId, Felt, Word, and other SDK types.

If you don't need inputs, use a unit struct:

#[note]
struct CounterNote;

#[note_script] method requirements

The #[note_script] method has specific signature constraints:

ConstraintDetails
Receiverself (by value only — not &self or &mut self)
Return type()
Required argOne Word argument (the note script argument)
Optional arg&Account or &mut Account (the consuming account)
GenericsNot allowed
AsyncNot allowed

Parameter ordering

The Word and &mut Account parameters can appear in either order:

// Both are valid:
pub fn run(self, _arg: Word, account: &mut Account) { ... }
pub fn run(self, account: &mut Account, _arg: Word) { ... }

With account access

When you include &mut Account (or &Account), the note script can call methods on the consuming account's components:

#[note_script]
pub fn run(self, _arg: Word, account: &mut Account) {
let assets = active_note::get_assets();
for asset in assets {
account.receive_asset(asset); // Cross-component call
}
}

The Account type comes from WIT bindings of the account component — see Cross-Component Calls.

Without account access

Use this pattern for trigger or command notes that carry no assets and only execute logic (e.g., calling a counter contract). If your note transfers assets, include &mut Account.

#[note_script]
pub fn run(self, _arg: Word) {
// For logic-only notes that carry no assets.
// Cannot call account methods — see the counter note example below.
}

Example: Counter note (cross-component calls)

A note that calls methods on the consuming account's component:

All note script crates require #![no_std] and #![feature(alloc_error_handler)] at the crate root. These are omitted from examples for brevity.

counter-note/src/lib.rs
use miden::*;

// generated by cargo-component from the counter component's WIT interface
use crate::bindings::miden::counter_contract::counter_contract;

#[note]
struct CounterNote;

#[note]
impl CounterNote {
#[note_script]
pub fn run(self, _arg: Word) {
let initial_value = counter_contract::get_count();
counter_contract::increment_count();
let expected_value = initial_value + Felt::from_u32(1);
let final_value = counter_contract::get_count();
assert_eq(final_value, expected_value);
}
}

This note doesn't take &mut Account — instead it calls the counter contract's methods directly through generated bindings. See Cross-Component Calls.

Cargo.toml for note scripts

Note scripts require project-kind = "note-script" and must declare dependencies on any account components they interact with:

[package.metadata.miden]
project-kind = "note-script"

# Miden dependencies — account components this note calls
[package.metadata.miden.dependencies]
"miden:basic-wallet" = { path = "../basic-wallet" }

# WIT dependencies — generated interfaces for cross-component calls
[package.metadata.component.target.dependencies]
"miden:basic-wallet" = { path = "../basic-wallet/target/generated-wit/" }