Skip to main content
Version: 0.14 (unstable)

Cross-Component Calls

Miden components communicate through WIT (WebAssembly Interface Types) bindings. Since accounts can have multiple components (e.g., wallet + auth + custom logic), those components need to call each other's methods. Note scripts also need to call methods on the consuming account's components to transfer assets. WIT provides the interface contract between caller and callee.

How it works

When you build a component with miden build, the compiler generates a WIT interface describing its public methods. Other projects can import this WIT interface to call those methods.

counter-contract (component)
→ generates WIT interface
→ counter-note imports the WIT
→ calls counter_contract::get_count()

The simplest way to make cross-component calls from note scripts is through the #[note] macro with an Account parameter:

use crate::bindings::Account;
use miden::{active_note, Asset};

#[note]
struct P2idNote;

#[note]
impl P2idNote {
#[note_script]
pub fn run(self, _arg: Word, account: &mut Account) {
// Iterate over the note's assets and transfer each to the account
for asset in active_note::get_assets() {
account.receive_asset(asset);
}
}
}

The _arg: Word parameter is the note's first input Word, passed automatically when the note is consumed. It's unused in this example (prefixed with _), but note scripts can use it for recipient-specific data like expected account IDs or amounts.

The Account type is auto-generated from the WIT bindings of the dependent component. Its methods correspond to the component's public methods.

Using generate!() directly

For more control, use miden::generate!() to manually generate bindings:

use miden::*;

miden::generate!();
bindings::export!(MyNote);

This generates a bindings module with:

  • exports::miden::base::note_script::Guest — The trait your note must implement
  • Imported interfaces — Functions from dependent components

Example: cross-ctx-note

cross-ctx-note/src/lib.rs
#![no_std]
#![feature(alloc_error_handler)]

#[global_allocator]
static ALLOC: miden::BumpAlloc = miden::BumpAlloc::new();

use miden::*;

miden::generate!();
bindings::export!(MyNote);

use bindings::{
exports::miden::base::note_script::Guest,
miden::cross_ctx_account::foo::process_felt,
};

struct MyNote;

impl Guest for MyNote {
fn run(_arg: Word) {
let input = Felt::from_u32(11);
let output = process_felt(input);
assert_eq(output, felt!(53));
}
}

Key points:

  • miden::generate!() generates the bindings module from WIT definitions
  • bindings::export!(MyNote) registers MyNote as the implementation
  • process_felt is imported from the cross-ctx-account component
  • The import path follows the WIT package structure: bindings::miden::cross_ctx_account::foo::process_felt

The bindings:: module structure

After generate!(), the bindings module has this structure:

bindings/
├── exports/
│ └── miden/
│ └── base/
│ └── note_script/
│ └── Guest (trait to implement)
└── miden/
└── <package-name>/
└── <interface-name>/
└── <function-name>() (callable functions)

Cargo.toml configuration

Cross-component calls require two dependency declarations:

1. Miden dependencies (for cargo-miden linking)

[package.metadata.miden.dependencies]
"miden:basic-wallet" = { path = "../basic-wallet" }

This tells cargo-miden where to find the component for linking.

2. WIT dependencies (for binding generation)

[package.metadata.component.target.dependencies]
"miden:basic-wallet" = { path = "../basic-wallet/target/generated-wit/" }

This points to the generated WIT files used to create Rust bindings.

Complete example

counter-note/Cargo.toml
[package]
name = "counter-note"
version = "0.1.0"
edition = "2024"

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

[dependencies]
miden = { path = "../../sdk/sdk" }

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

[package.metadata.component]
package = "miden:counter-note"

# Miden dependency — the component we want to call
[package.metadata.miden.dependencies]
"miden:counter-contract" = { path = "../counter-contract" }

# WIT dependency — generated interface for binding generation
[package.metadata.component.target.dependencies]
"miden:counter-account" = { path = "../counter-contract/target/generated-wit/" }

The dependent component must be built first so its WIT files exist. Build counter-contract before building counter-note.

Example: Counter note calling counter contract

counter-note/src/lib.rs
#![no_std]
#![feature(alloc_error_handler)]

use miden::*;

// Import the counter contract's generated bindings
use crate::bindings::miden::counter_contract::counter_contract;

#[note]
struct CounterNote;

#[note]
impl CounterNote {
#[note_script]
pub fn run(self, _arg: Word) {
// Call get_count() on the counter contract component
let initial_value = counter_contract::get_count();

// Call increment_count() — modifies the account's storage
counter_contract::increment_count();

// Verify the count increased
let expected_value = initial_value + Felt::from_u32(1);
let final_value = counter_contract::get_count();
assert_eq(final_value, expected_value);
}
}

When to use which pattern

PatternUse when
#[note] with &mut AccountNote needs to call a single account component's methods
generate!() + Guest traitNote needs to call multiple components or needs full control
Direct module importsCalling specific functions from a known component interface

For the complete list of macros and bindings functions, see the Cheatsheet.

Full API docs on docs.rs: miden (generate!() macro)