0xmiden/compiler/rust-sdk-patterns
Claude Code and agent skill by 0xmiden/compiler
- community
Checking that the SKILL.md file is still in the repository.
Complete guide to writing Miden smart contracts with the Rust SDK. Covers the three-part #[component_storage]/#[component] account-component pattern, the #[account_procedure] interface marker, #[note]/#[note_script] notes, #[tx_script] scripts, the #[account(...)] wrapper and its generated traits, storage patterns, native functions, asset handling, cross-component calls, P2ID note creation, and asset receiving via component methods. Use when writing, editing, or reviewing Miden Rust contract co…
Install
npx vskill@latest install 0xmiden/compiler/rust-sdk-patternsSKILL.md
Miden Rust SDK Patterns
Three Contract Types
Account Component (three-part pattern)
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.
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.
Reference: examples/basic-wallet/src/lib.rs and examples/counter-contract/src/lib.rs in the compiler repo.
#[account_procedure]: being exported is not the same as being callable
A component's methods are not implicitly part of the account interface. Mark every method that must be reachable from a transaction script, a note script, foreign procedure invocation (FPI), or a sibling component with #[account_procedure].
Three rules that catch people out:
- It goes on the `#[component]` trait declaration, not on the impl block. Putting it on the impl does nothing.
- An unmarked method still compiles and is still exported by the package — it simply is not an account procedure. There is no error at build time; the call site fails later.
- `#[account_procedure]` and `#[auth_script]` cannot be combined in one component. They belong to different component kinds: an ordinary account component versus an authentication component. An authentication component uses
#[auth_script]alone, and its single method is the interface implicitly.
#[account_procedure] needs no import — the enclosing #[component] macro recognises it, exactly as it does #[auth_script].
Any number of methods may be marked.
Project metadata for accounts: [lib] needs kind, namespace, and an explicit path; [dependencies] needs miden-core and miden-protocol:
supported-types also accepts "RegularAccountUpdatableCode" and the faucet kinds ["FungibleFaucet", "NonFungibleFaucet"].
The project-template contract Cargo.toml files currently use edition = "2021", crate-type = ["cdylib"], and miden / miden-sdk-build-script-support 0.15.0-rc.2 dependencies. Copy the local manifests unless intentionally changing the template line:
Contracts build on nightly-2026-09-01 with target wasm32-wasip2. Use the midenup / cargo-miden command documented in this repository's README.md and CLAUDE.md; the local build.rs calls miden_sdk_build_script_support::prepare_package_cache() so source dependencies are available to the SDK macros during Cargo checks and IDE analysis.
Note Script (#[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(...)].
A #[note] struct with fields is auto-decoded from active_note::get_storage(). The decoder is strict: it calls ensure_eof(), so surplus felts in the note's storage fail with FeltReprError::TrailingData. A zero-sized note type skips get_storage() entirely.
Reference: examples/p2id-note/src/lib.rs, examples/p2ide-note/src/lib.rs, examples/counter-note/src/lib.rs.
Project metadata for notes: [lib] kind = "note", plus namespace and path. Conventional namespace shape is miden::<pkg>::<pkg> (snake_case package name).
Transaction Script (#[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.
Reference: examples/basic-wallet-tx-script/src/lib.rs.
Project metadata for tx scripts: like a note, but [lib] kind = "tx-script" and namespace = "miden::<pkg>::<pkg>", plus path = "src/lib.rs". The script's entrypoint is exported at <namespace>::run.
#[account(...)] generates one trait per interface
#[account(pkg::Interface)] does not generate inherent methods on the wrapper struct. It generates one trait per referenced interface, named after the interface and carrying the wrapper's visibility, and implements it for the wrapper. Single-component accounts still call account.method(..) unchanged — as long as the generated trait is in scope.
The consequences worth knowing before you hit them:
- The wrapper struct must not share a name with any generated trait.
#[account(counter_contract::CounterContract)] struct CounterContract;is a hard error. Rename the struct (e.g.Counter). - A call site in a different module than the wrapper must `use` the generated trait. A same-module
#[note]/#[tx_script]entrypoint sees it automatically. - A referenced interface must export at least one method, or
#[account(...)]errors. - Wrappers are module-scope only.
- Clashing method names are disambiguated with UFCS:
<Wallet as BasicWallet>::deposit(account, asset). Generated traits are same-module so they need no import — but disambiguating against anActiveAccountbuilt-in does:use miden::active_account::ActiveAccount;. - Clashing trait names are renamed with `as`:
#[account(counter_contract::CounterContract as RemoteCounter)]. The path still selects the interface. - A component method named `new` is now legal.
Wallet::new(id)resolves to the inherent constructor,wallet.new()to the component method.
Storage Slot Naming
Storage slot names are part of the on-chain storage ABI and are derived as:
The prefix is the three-segment [lib].namespace Miden path from `miden-project.toml` (e.g. miden::counter_contract::counter_contract) — not the struct name. This deliberately decouples slot names from private Rust renames, and there is no slot(...) attribute. The same namespace names every exported procedure: <namespace>::<method>.
Live values from the examples: miden::counter_contract::counter_contract::count_map, miden::auth_component_rpo_falcon512::auth_component::owner_public_key.
See the rust-sdk-pitfalls skill (P5) for more on slot naming.
Storage Types
| 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 |
K: WordKey, and T/V: WordValue. WordValue is implemented for Word, Felt, AssetAmount, Digest, AccountId, Recipient, Tag, NoteIdx, NoteType; WordKey for the same set minus Digest and Recipient.
Native Function Modules
| Module | Key Functions | Purpose | |
|---|---|---|---|
native_account:: | add_asset(Asset) -> Word, remove_asset(Asset) -> Word, incr_nonce() -> Nonce, get_id() -> AccountId, get_initial_asset(AssetId) -> Word, get_initial_commitment() -> Word, compute_commitment() -> Word, has_state_changed() -> bool, was_procedure_called(Word) -> bool, compute_delta_commitment() -> Word | Modify / read the native account | |
active_account:: | get_id() -> AccountId, get_nonce() -> Nonce, get_asset(asset_id: AssetId) -> Word, has_asset(asset_id: AssetId) -> bool, get_vault_root() -> Word, get_num_procedures() -> u32, get_procedure_root(u32) -> Word, has_procedure(Word) -> bool | Query the active account | |
active_note:: | get_storage() -> Vec<Felt>, get_initial_assets() -> Vec<Asset>, get_sender() -> AccountId, get_recipient() -> Recipient, get_metadata() -> NoteMetadata, find_attachment(Felt) -> Option<u32>, write_attachment_to_memory(u32) -> Vec<Word> | Query the 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), the *_attachment family | Create output notes | |
faucet:: | mint(Asset), burn(Asset) | Move assets in and out of existence | |
tx:: | get_reference_block_number() -> BlockNumber, get_block_timestamp() -> u32, get_num_input_notes() -> u32, get_num_output_notes() -> u32, get_expiration_block_delta() -> u16, update_expiration_block_delta(u16), execute_foreign_procedure(..) | Transaction context and FPI | |
| Intrinsics | assert(Felt), assertz(Felt), assert_eq(Felt, Felt) | Validation (assert fails unless the felt equals 1; assertz fails unless it equals 0) |
add_asset, remove_asset and the active_account queries are also trait methods auto-implemented on the #[component_storage] struct, so the idiomatic body is self.add_asset(asset) rather than the free function.
Three context restrictions the compiler will not catch
This is the start of the file. Read the full SKILL.md on GitHub.
Security
- Security scanPassed, 100 of 100Pattern checks on the skill's files.
- Model reviewNot runA model review is scheduled for the next rescan.
Quality evaluation
- Task evaluationNot runNo evaluation yet. Run one in Skill Studio.
Source
- SKILL.md in the repositoryCheckingextra/templates/project/.claude/skills/rust-sdk-patterns/SKILL.md
Popularity
- Repository stars
- 116
- Repository forks
- 83
- 7-day trend score
- 29
Stars and forks are for the whole GitHub repository, not this skill alone. Not refreshed yet.
Works with
Any agent vskill installs to: 56 targets in all. These 39 have a page on this site.
- Claude Code
- Cursor
- GitHub Copilot
- Windsurf
- Codex
- Gemini CLI
- Cline
- Amp
- Roo Code
- Goose
- Continue
- Kilo Code
- Junie
- Trae
- OpenHands
- Qwen Code
- AI Agent
- Tabnine Chat
- Sourcegraph Cody
- Amazon Q
- Firebase Genkit
- Sweep
- Devin
- Phind
- Mentat
- Claude Desktop
- Aider
- GPT Pilot
- Plandex
- bolt.new
- v0
- Replit Agent
- Anse
- TypingMind
- BetterChatGPT
- LibreChat
- Warp AI
- Pieces
- Fig AI
Embed badge
[](https://verified-skill.com/skills/0xmiden/compiler/rust-sdk-patterns)