用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
直接命令不会经过审查 Prompt;运行前请先检查来源。
npx skills add https://github.com/0xMiden/agent-tools --skill rust-sdk-patterns命令会保持在同一行。复制前请横向滚动并检查完整内容。
想先保存到本地?可下载 SkillsMP 当前能够提供的文件。
基于 SOC 职业分类
正在显示 SKILL.md
| 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
: [Felt; ] = w.();
: [; ] = w.();
= w.();
: = f.();
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)