Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 43 additions & 17 deletions .claude/skills/rust-sdk-patterns/SKILL.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
name: rust-sdk-patterns
description: Complete guide to writing Miden smart contracts with the Rust SDK. Covers #[component], #[note], #[tx_script] macros, 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 code.
description: Complete guide to writing Miden smart contracts with the Rust SDK. Covers #[component], #[note], #[tx_script] macros, storage patterns, native functions, asset handling, cross-component calls, and P2ID note creation. Use when writing, editing, or reviewing Miden Rust contract code.
---

# Miden Rust SDK Patterns
Expand Down Expand Up @@ -45,6 +45,8 @@ fn run(_arg: Word, account: &mut Account) {
| `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` |

**Storage keys** are always `Word` (4 Felts). Use `Word::from_u64_unchecked(a, b, c, d)` or `Word::from([f0, f1, f2, f3])`.

## Native Function Modules

| Module | Key Functions | Purpose |
Expand All @@ -62,10 +64,6 @@ fn run(_arg: Word, account: &mut Account) {

`Asset` is now a two-word value:

**Constructor**: `Asset::new(word)` creates an Asset from a Word.

See [miden-bank bank-account](https://github.com/0xMiden/tutorials/blob/main/examples/miden-bank/contracts/bank-account/src/lib.rs) for complete asset handling patterns including deposit, withdrawal, and balance tracking.

```rust
pub struct Asset {
pub key: Word,
Expand All @@ -82,7 +80,7 @@ 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)
// Add asset to account vault
native_account::add_asset(asset);

// Remove asset from account vault
Expand All @@ -91,7 +89,43 @@ native_account::remove_asset(asset.clone());

## P2ID Output Note Creation

To send assets to another account, create a P2ID (Pay-to-ID) output note. See [miden-bank bank-account](https://github.com/0xMiden/tutorials/blob/main/examples/miden-bank/contracts/bank-account/src/lib.rs) `create_p2id_note()` for a complete working implementation.
To send assets to another account, create a P2ID (Pay-to-ID) output note:

```rust
fn create_p2id_note(&mut self, serial_num: Word, asset: &Asset,
recipient_id: AccountId, tag: Felt, note_type: Felt) {
let tag = Tag::from(tag);
let note_type = NoteType::from(note_type);
let script_root = Self::p2id_note_root(); // Hardcoded P2ID script digest

// P2ID note storage: [recipient_suffix, recipient_prefix]
let recipient = note::build_recipient(
serial_num,
script_root,
vec![recipient_id.suffix, recipient_id.prefix],
);

let note_idx = output_note::create(tag, note_type, recipient);
let _remaining_value = native_account::remove_asset(asset);
output_note::add_asset(asset, note_idx);
}
```

`Recipient::compute(...)` was removed in `miden` 0.11. Use `note::build_recipient(...)` instead.

## Note Storage And Attached Assets

Notes receive explicit storage data as `Vec<Felt>`, accessed with `active_note::get_storage()`. Attached assets are separate and should be read with `active_note::get_assets()`.

```rust
let storage = active_note::get_storage();
let serial_num = Word::from([storage[0], storage[1], storage[2], storage[3]]);
let tag = Tag::from(storage[4]);
let note_type = NoteType::from(storage[5]);

let assets = active_note::get_assets();
let first_asset = assets[0];
```

## Cross-Component Dependencies

Expand All @@ -103,8 +137,8 @@ Then import the bindings in your Rust code. See [increment-note/src/lib.rs](../.

```rust
// Felt from integer
let f = felt!(42); // preferred for literals in contract code
let f = Felt::new(42); // construct a Felt from a u64
let f = felt!(42);
let f = Felt::new(42);
let f = Felt::from_u32(42);
let f = Felt::from_canonical_checked(42).unwrap();

Expand Down Expand Up @@ -133,14 +167,6 @@ extern crate alloc;
use alloc::vec::Vec;
```

## Asset Receiving via Component Methods

Note scripts cannot call `native_account::add_asset()` directly (see pitfall P11). The canonical pattern is for an account component to expose a public method that wraps `native_account::add_asset()`, and note scripts call that method via cross-component bindings.

See [miden-bank bank-account deposit()](https://github.com/0xMiden/tutorials/blob/main/examples/miden-bank/contracts/bank-account/src/lib.rs) for the component side: the `deposit()` method validates the deposit, updates storage, and calls `native_account::add_asset()`.

See [miden-bank deposit-note](https://github.com/0xMiden/tutorials/blob/main/examples/miden-bank/contracts/deposit-note/src/lib.rs) for the note side: the note script calls `bank_account::deposit()` via generated bindings.

## Validation Checklist

- [ ] `#![no_std]` and `#![feature(alloc_error_handler)]` at top of every contract
Expand Down
49 changes: 17 additions & 32 deletions .claude/skills/rust-sdk-pitfalls/SKILL.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
name: rust-sdk-pitfalls
description: Critical pitfalls and safety rules for Miden Rust SDK development. Covers felt arithmetic security, comparison operators, argument limits, storage naming, no-std setup, asset layout, P2ID roots, NoteType construction, note-to-component call boundaries, and note input immutability. Use when reviewing, debugging, or writing Miden contract code.
description: Critical pitfalls and safety rules for Miden Rust SDK development. Covers felt arithmetic security, comparison operators, argument limits, storage naming, no-std setup, asset layout, and P2ID roots. Use when reviewing, debugging, or writing Miden contract code.
---

# Miden SDK Pitfalls
Expand All @@ -26,8 +26,6 @@ let new_balance = current_balance - withdraw_amount;

**Rule**: ALWAYS check `.as_canonical_u64()` values before any Felt subtraction.

**Max Felt value**: The maximum valid Felt is `p - 1 = 18446744069414584320`, not `u64::MAX` (`18446744073709551615`). Using `u64::MAX` as a sentinel or boundary value causes silent wraparound.

## P2: Felt Comparison Operators Are Misleading for Quantity Logic

**Severity**: High — silently produces incorrect results
Expand All @@ -40,6 +38,7 @@ if balance > threshold { ... }

// CORRECT for business logic — compare as integers
if balance.as_canonical_u64() > threshold.as_canonical_u64() { ... }
- [ ] `Recipient::compute(...)` replaced with `note::build_recipient(...)`
```

**Rule**: For quantity/business logic, ALWAYS convert to `.as_canonical_u64()` before using comparison operators.
Expand Down Expand Up @@ -152,6 +151,8 @@ let recipient = note::build_recipient(
);
```

`active_note::get_inputs()` also became `active_note::get_storage()`.

## P9: P2ID Note Root Hardcoding

**Severity**: Low-Medium — breaks after miden-standards updates
Expand All @@ -178,32 +179,16 @@ fn p2id_note_root() -> Digest {

**Mitigation**: Use `P2idNote::script_root()` from miden-standards if available, or verify the hardcoded root matches the current version after dependency updates.

**NoteType for P2ID**: P2ID output notes created in contract code should use the private note type value via `NoteType::from(felt!(2))` (see P10). Using the public note type triggers an opaque "missing details in advice provider" error at execution time. See [miden-bank withdraw](https://github.com/0xMiden/tutorials/blob/main/examples/miden-bank/contracts/bank-account/src/lib.rs) for the working pattern.

## P10: NoteType Variants Unavailable in Compiler SDK

**Severity**: Medium -- causes compilation errors

Named enum variants (`NoteType::Private`, `NoteType::Public`, `NoteType::Encrypted`) don't exist in contract code. Construct via `NoteType::from()`:

| NoteType | Value |
|----------|-------|
| Public | `NoteType::from(felt!(1))` |
| Private | `NoteType::from(felt!(2))` |
| Encrypted | `NoteType::from(felt!(3))` |

See [miden-bank bank-account](https://github.com/0xMiden/tutorials/blob/main/examples/miden-bank/contracts/bank-account/src/lib.rs) for `NoteType::from(note_type)` usage.

## P11: Note Scripts Cannot Call Native Account Functions

**Severity**: High -- causes runtime failures

Note scripts cannot call `native_account::add_asset()` or other `native_account::` functions directly. The kernel's `authenticate_account_origin` check rejects these calls from a note context. Instead, note scripts must call an account component method, which then calls `native_account::add_asset()` internally.

See [miden-bank deposit-note](https://github.com/0xMiden/tutorials/blob/main/examples/miden-bank/contracts/deposit-note/src/lib.rs) for the correct pattern: the note script calls `bank_account::deposit()`, which internally calls `native_account::add_asset()`.

## P12: Note Inputs Are Immutable After Creation

**Severity**: Low -- causes incorrect architecture

Note inputs (`active_note::get_storage()`) are baked at note creation time and cannot be modified after creation. Design note input layouts carefully before deployment.
## Quick Reference

| Pitfall | One-Line Rule |
|---------|--------------|
| P1 Felt arithmetic | Always `.as_canonical_u64()` before subtraction |
| P2 Felt comparison | Always `.as_canonical_u64()` for `<` `>` `<=` `>=` in business logic |
| P3 Arg limit | Max 4 Words per function — pass by reference |
| P4 Storage API | Use `StorageValue<T>` / `StorageMap<K, V>` with `WordValue` / `WordKey` |
| P5 Storage names | `package_or_name::component_struct::field` |
| P6 No-std | `#![no_std]` + `#![feature(alloc_error_handler)]` |
| P7 Asset ABI | Use `asset.key` + `asset.value`, not `asset.inner` |
| P8 Recipient builder | Use `note::build_recipient(...)` |
| P9 P2ID root | Verify digest after dependency updates |
8 changes: 4 additions & 4 deletions .claude/skills/rust-sdk-source-guide/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ This is the single highest-leverage practice for AI-assisted Miden development.
- `Recipient::compute(...)` -> `note::build_recipient(...)`
- `Value` -> `StorageValue<T>`
- `StorageMap` -> `StorageMap<K, V>`
- `active_note::get_inputs()` -> `active_note::get_storage()`
3. Search the source repos for a working example of the pattern that failed
4. Adapt the working pattern to your use case
5. Rebuild
Expand Down Expand Up @@ -84,8 +85,8 @@ git clone --depth 1 --branch main https://github.com/0xMiden/compiler.git ../com
# Required: contains client API for deployment and chain interaction
git clone --depth 1 --branch main https://github.com/0xMiden/miden-client.git ../miden-client

# Recommended: complete working banking app with advanced patterns in the `examples/miden-bank` folder of the tutorials repo
git clone --branch main https://github.com/0xMiden/tutorials.git ../tutorials
# Recommended: complete working banking app with advanced patterns
git clone --branch main https://github.com/keinberger/miden-bank.git ../miden-bank
```

**Note**: These commands clone the stable `main` branch. Only use `--branch next` if the user explicitly requests the experimental/upcoming version of the compiler or source repos.
Expand Down Expand Up @@ -162,8 +163,7 @@ Accounts can include standard components (BasicWallet, authentication) alongside
Create output notes (like P2ID) from within contract code. Requires building a recipient with `note::build_recipient(serial_num, script_root, storage)` and then using `output_note::create(...)`. The `miden-bank/` withdraw pattern demonstrates this end-to-end.

### Note Storage Protocol
Notes receive storage data as `Vec<Felt>` which is deserialized into the note object. In the `#[note_script]` method the `self` is deserialized from the note storage (`active_note::get_storage()`).
Attached assets are separate and should be read with `active_note::get_assets()`.
Pass structured data to notes via `Vec<Felt>` storage. Define your storage layout, document the field ordering, and parse it with `active_note::get_storage()` in the `#[note_script]` function. Attached assets are separate and should be read with `active_note::get_assets()`.

### Atomic Swaps
The standard SwapNote in `miden-base/` creates a payback P2ID note automatically when consumed. Explore the SwapNote builder to understand tag construction, storage layout, and the payback mechanism.
Expand Down
Loading