一键导入
rust-sdk-patterns
Complete guide to writing Miden smart contracts with the Rust SDK. Covers the three-part
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
菜单
Complete guide to writing Miden smart contracts with the Rust SDK. Covers the three-part
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
基于 SOC 职业分类
Use when writing kernel, account, or note MASM code that reads from or writes to the advice provider (advice stack / advice map) — validate advice data.
Use when writing or reviewing MASM hot paths — prefer the cheaper equivalent instruction: `neq.0` over `push.0 gt` for non-zero checks, `cdrop` over an `if/else` selecting between two values, `dup.N` over `loc_load` for a value still on the stack, `eqw` over hand-rolled element-wise word comparison, `u32gt`/`u32lt` over generic `gt`/`lt` on known-u32 operands.
Use when writing a GENERIC MASM storage utility (one that operates over a caller-chosen slot or map) inside a reusable account component — receive the slot id as a parameter so the utility works against any slot, not one hard-coded one.
Use when constructing a `Felt` from a numeric value in Rust — avoid silently producing a non-canonical Felt that no longer equals the original input.
Critical pitfalls and safety rules for Miden frontend development. Covers WASM initialization, concurrent access crashes, COOP/COEP headers, BigInt handling, Bech32 network mismatches, IndexedDB state loss, auto-sync side effects, Vite configuration, and React rendering race conditions. Use when reviewing, debugging, or writing Miden frontend code.
Guide for advanced Miden frontend development using source repo exploration. Covers AI development practices (Plan Mode, verification-driven development, context engineering, sub-agents) and maps the Miden web-sdk source repository for discovering advanced patterns. Use when building complex applications beyond basic hook usage, implementing custom signers, working with WasmWebClient directly, or troubleshooting SDK internals.
| name | rust-sdk-patterns |
| description | Complete guide to writing Miden smart contracts with the Rust SDK. Covers the three-part |
Defines reusable logic and storage for accounts. Accounts are composed of one or more components.
An account component is written as three parts — the storage struct is annotated #[component_storage], and #[component] applies to the API trait and the impl block:
#[component_storage] on the storage struct — declares typed #[storage(...)] fields and derives slot names.#[component] on a trait — the component's exported API (this is the source of the generated WIT interface).#[component] on the impl Trait for Storage block — the behavior, wired to the guest bindings.#![no_std]
#![feature(alloc_error_handler)]
#[macro_use]
extern crate alloc;
use miden::*;
#[component_storage]
struct BankStorage {
#[storage(description = "initialized")]
initialized: StorageValue<Word>,
#[storage(description = "balances")]
balances: StorageMap<Word, Felt>,
}
#[component]
trait Bank {
fn initialize(&mut self);
fn deposit(&mut self, depositor: AccountId, deposit_asset: Asset);
}
#[component]
impl Bank for BankStorage {
fn initialize(&mut self) { /* read/write self.initialized, etc. */ }
fn deposit(&mut self, depositor: AccountId, deposit_asset: Asset) { /* ... */ }
}
Only the trait's methods are exported to WIT. Inherent (impl BankStorage) methods stay private to the contract — use them for helpers like key derivation.
See miden-bank bank-account for a complete working example demonstrating the three-part pattern, typed StorageValue<Word> / StorageMap<Word, Felt>, get()/set(), felt arithmetic, and private inherent helpers.
Project metadata for accounts: See contracts/bank-account/miden-project.toml in miden-bank for the [lib] kind = "account-component", the namespace, and the supported-types. The Cargo.toml only needs crate-type = ["cdylib"] and the miden dependency.
#[note] / #[note_script])Executes when a note is consumed by an account. Can call component methods on the consuming account.
A note is two parts: a #[note] struct (the note inputs type) and a #[note] impl block containing exactly one #[note_script] entrypoint. The entrypoint takes self by value, exactly one Word argument, and optionally a single reference to an #[account(...)] wrapper (&MyAccount or &mut MyAccount). The consuming account is declared separately with #[account(...)].
#![no_std]
#![feature(alloc_error_handler)]
use miden::*;
// The native (active) account this note runs against: exposes the
// bank-account `Bank` component's methods on the wrapper.
#[account(bank_account::Bank)]
pub struct Wallet;
#[note]
struct DepositNote;
#[note]
impl DepositNote {
#[note_script]
fn run(self, _arg: Word, account: &mut Wallet) {
let depositor = active_note::get_sender();
for asset in active_note::get_assets() {
account.deposit(depositor, asset);
}
}
}
See miden-bank deposit-note for a working example demonstrating #[note], #[note_script], the #[account(...)] wrapper, and a cross-component call.
Project metadata for notes: See contracts/deposit-note/miden-project.toml in miden-bank for [lib] kind = "note", the namespace, the path dependency on the called component, and the cross-component [package.metadata.miden.dependencies] WIT entry.
#[tx_script])One-off logic executed in the context of an account. Used for initialization, admin operations, etc.
#[tx_script] annotates a free fn run. Its signature is fn run(arg: Word) or fn run(arg: Word, account: &mut MyAccount) where MyAccount is an #[account(...)] wrapper. There is no crate::bindings::Account — you declare the account wrapper yourself and the macro instantiates it as the active account.
#![no_std]
#![feature(alloc_error_handler)]
use miden::*;
// The account this tx-script runs against: the bank-account `Bank` component.
#[account(bank_account::Bank)]
pub struct Wallet;
#[tx_script]
fn run(_arg: Word, account: &mut Wallet) {
account.initialize();
}
See miden-bank init-tx-script for the working example.
Project metadata for tx scripts: Like a note, but [lib] kind = "tx-script" and namespace = "miden:base/transaction-script@1.0.0". See contracts/init-tx-script/miden-project.toml in miden-bank.
Storage slot names are part of the on-chain storage ABI and are derived as:
<package_name_snake>::<interface_segment_snake>::<field_name>
The middle segment is the interface segment of the [lib].namespace in miden-project.toml (the part between the last / and @), snake-cased — not the snake-cased struct name. This deliberately decouples slot names from private Rust renames.
Example: package bank-account + namespace = "miden:bank-account/bank@0.1.0" + field balances derives slot bank_account::bank::balances (see miden-bank deposit_test.rs, bank_storage_slots()). Note the version suffix (@0.1.0) is ignored so the slot name stays stable. Slots are derived from the slot name; there is no slot(...) attribute. See the rust-sdk-pitfalls skill (P5) for more on slot naming.
| Type | Usage | Read | Write |
|---|---|---|---|
StorageValue<T> | Single typed slot (flags, counters, IDs) | .get() -> T | .set(T) -> T |
StorageMap<K, V> | Typed key-value mapping (balances, records) | .get(K) -> V | .set(K, V) -> V |
| Module | Key Functions | Purpose |
|---|---|---|
native_account:: | add_asset(Asset) -> Word, remove_asset(Asset) -> Word, incr_nonce() -> Felt, get_id() -> AccountId | Modify current account vault/nonce |
active_account:: | get_id() -> AccountId, get_balance(Word) -> Felt | Query current account (get_balance takes the asset key word, not an AccountId) |
active_note:: | get_storage() -> Vec<Felt>, get_assets() -> Vec<Asset>, get_sender() -> AccountId | Query note being consumed |
note:: | build_recipient(Word, Word, Vec<Felt>) -> Recipient | Build note recipients from serial number, script root, and note storage |
output_note:: | create(Tag, NoteType, Recipient) -> NoteIdx, add_asset(Asset, NoteIdx) | Create output notes |
faucet:: | create_fungible_asset(Felt) -> Asset, mint(Asset), burn(Asset) | Asset minting |
tx:: | get_block_number() -> Felt, get_block_timestamp() -> Felt | Transaction context |
| Intrinsics | assert(Felt), assertz(Felt), assert_eq(Felt, Felt) | Validation (assert fails unless the felt equals 1; assertz fails unless it equals 0) |
Asset is a two-word value (key + value):
Constructor: Asset::new(key, value) builds an Asset from its vault key word and value word (the arguments are impl Into<Word>, so e.g. Asset::new(key_word, value_word) or from [Felt; 4]).
See miden-bank bank-account for complete asset handling patterns including deposit, withdrawal, and balance tracking.
pub struct Asset {
pub key: Word,
pub value: Word,
}
For fungible assets, the amount lives in asset.value[0]. The asset class / vault identity lives in asset.key.
// Access fungible amount
let amount = asset.value[0];
// Keep the asset key if you need to persist or compare the asset class
let asset_key = asset.key;
// Add asset to account vault (only from component methods, not note scripts — see pitfall P11)
native_account::add_asset(asset);
// Remove asset from account vault (Asset is Copy, no clone needed)
native_account::remove_asset(asset);
To send assets to another account, create a P2ID (Pay-to-ID) output note. See miden-bank bank-account create_p2id_note() for a complete working implementation (builds the recipient with note::build_recipient, creates the note with output_note::create, then native_account::remove_asset + output_note::add_asset).
To call another component's methods from a note or tx script, declare the dependency in your miden-project.toml in two places:
[dependencies] — a normal path (or registry) dependency on the component crate.[package.metadata.miden.dependencies] — the generated WIT for the component, e.g. bank-account = { wit = "../bank-account/target/generated-wit/" }. The WIT is produced by building the dependency component first.See contracts/deposit-note/miden-project.toml in miden-bank for a working example showing both sections.
Then expose the dependency's methods on the consuming account by declaring an #[account(package::Interface)] wrapper (e.g. #[account(bank_account::Bank)] pub struct Wallet;) and calling methods on the injected account parameter. The package name is the dependency's Rust-style name (- replaced with _) and Interface is its exported WIT interface in UpperCamelCase. See contracts/deposit-note/src/lib.rs in miden-bank.
// Felt from integer
let f = felt!(42); // preferred for literals in contract code
let f = Felt::new(42).unwrap(); // fallible: Felt::new returns Result<Felt, _> in v0.15
let f = Felt::new_unchecked(42); // infallible, non-reducing form
let f = Felt::from_u32(42); // infallible (u32 always fits)
let f = Felt::from_canonical_checked(42).unwrap(); // returns Option<Felt>
// Word from Felts
let w = Word::from([f0, f1, f2, f3]);
let w = Word::new([f0, f1, f2, f3]);
let w = Word::from([0_u32, 0, 0, 1]);
let w = Word::try_from([0_u64, 0, 0, 1]).unwrap();
// Inspect a Word
let limbs: [Felt; 4] = w.into_elements();
let bytes: [u8; 32] = w.as_bytes();
let hex = w.to_hex();
// Felt to u64 (for comparisons and arithmetic safety)
let n: u64 = f.as_canonical_u64();
Every contract file must start with #![no_std] and #![feature(alloc_error_handler)]. See any contract under contracts/ in miden-bank for the pattern.
If you need heap allocation (Vec, String, etc.):
extern crate alloc;
use alloc::vec::Vec;
The bank-account contract uses #[macro_use] extern crate alloc; so the vec! macro is available (it builds note-recipient inputs with vec![...]).
Note scripts cannot call native_account::add_asset() directly (see pitfall P11). The canonical pattern is for an account component to expose a public (trait) method that wraps native_account::add_asset(), and the note script calls that method through the #[account(...)] wrapper.
See miden-bank bank-account deposit() for the component side: the deposit() trait method validates the deposit, updates storage, and calls native_account::add_asset().
See miden-bank deposit-note for the note side: the note declares #[account(bank_account::Bank)] pub struct Wallet; and, inside #[note_script] fn run(self, _arg: Word, account: &mut Wallet), calls account.deposit(depositor, asset) on that wrapper. It is not a free bank_account::deposit() call.
#![no_std] and #![feature(alloc_error_handler)] at top of every contract#[component_storage] struct + #[component] trait + #[component] impl (never #[component] on a struct)crate-type = ["cdylib"] in Cargo.toml[lib] kind in miden-project.toml (account-component / note / tx-script) with the matching namespaceStorageValue<T> / StorageMap<K, V> with get() / set(); slot names derive from <package>::<namespace-interface>::<field>#[account(package::Interface)] wrapper and call methods on the injected accountmiden-project.toml under both [dependencies] (path) and [package.metadata.miden.dependencies] (wit).as_canonical_u64() (see rust-sdk-pitfalls skill)