From 67d3a370c769baa59171b65505c5743bba2ec682 Mon Sep 17 00:00:00 2001 From: Melnik George Date: Thu, 30 Jul 2026 15:22:30 +0400 Subject: [PATCH 1/8] =?UTF-8?q?fix(get-started):=20repair=20programmatic?= =?UTF-8?q?=20path=20=E2=80=94=20SDK=20examples,=20build=20command,=20mani?= =?UTF-8?q?fest=20(#343)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/builder/get-started/accounts.md | 13 +++++---- docs/builder/get-started/notes.md | 28 ++++++------------- docs/builder/get-started/read-storage.md | 10 +++---- .../your-first-smart-contract/create.md | 12 +++++--- .../your-first-smart-contract/deploy.md | 10 +++---- .../your-first-smart-contract/test.md | 4 +-- 6 files changed, 36 insertions(+), 41 deletions(-) diff --git a/docs/builder/get-started/accounts.md b/docs/builder/get-started/accounts.md index eb12da6a..f215492c 100644 --- a/docs/builder/get-started/accounts.md +++ b/docs/builder/get-started/accounts.md @@ -127,6 +127,10 @@ Copy the Rust code example into the file, then run: cargo run --bin demo --release ``` +:::note +The first `cargo run` pulls the pinned nightly toolchain and compiles ~400 crates, so it can take a few minutes. Later runs are incremental and much faster. +::: + ### TypeScript Environment If you already created `miden-app` during [installation](./setup/installation#typescript-project), you can reuse it. Otherwise, scaffold a new Vite vanilla-ts project: @@ -276,18 +280,17 @@ use miden_client::{ account::{ component::{ AccessControl, AuthScheme, BurnPolicyConfig, FungibleFaucet, MintPolicyConfig, - PolicyRegistration, TokenName, TokenPolicyManager, TransferPolicy, - create_fungible_faucet, + PolicyRegistration, TokenName, TokenPolicyManager, create_fungible_faucet, }, AccountType, }, + asset::{AssetAmount, TokenSymbol}, auth::AuthSecretKey, builder::ClientBuilder, keystore::{FilesystemKeyStore, Keystore}, rpc::{Endpoint, GrpcClient}, }; use miden_client_sqlite_store::ClientBuilderSqliteExt; -use miden_protocol::asset::{AssetAmount, TokenSymbol}; use miden_standards::AuthMethod; use rand::RngCore; use std::sync::Arc; @@ -340,9 +343,7 @@ async fn main() -> anyhow::Result<()> { .build()?; let policies = TokenPolicyManager::new() .with_mint_policy(MintPolicyConfig::AllowAll, PolicyRegistration::Active)? - .with_burn_policy(BurnPolicyConfig::AllowAll, PolicyRegistration::Active)? - .with_send_policy(TransferPolicy::AllowAll, PolicyRegistration::Active)? - .with_receive_policy(TransferPolicy::AllowAll, PolicyRegistration::Active)?; + .with_burn_policy(BurnPolicyConfig::AllowAll, PolicyRegistration::Active)?; let faucet_account = create_fungible_faucet( init_seed, faucet, diff --git a/docs/builder/get-started/notes.md b/docs/builder/get-started/notes.md index d61501ad..bc236b44 100644 --- a/docs/builder/get-started/notes.md +++ b/docs/builder/get-started/notes.md @@ -81,10 +81,11 @@ use miden_client::{ component::{ AccessControl, AuthScheme, AuthSingleSig, BasicWallet, BurnPolicyConfig, FungibleFaucet, MintPolicyConfig, PolicyRegistration, TokenName, TokenPolicyManager, - TransferPolicy, create_fungible_faucet, + create_fungible_faucet, }, AccountBuilder, AccountType, }, + asset::{AssetAmount, FungibleAsset, TokenSymbol}, auth::AuthSecretKey, builder::ClientBuilder, keystore::{FilesystemKeyStore, Keystore}, @@ -93,7 +94,6 @@ use miden_client::{ transaction::TransactionRequestBuilder, }; use miden_client_sqlite_store::ClientBuilderSqliteExt; -use miden_protocol::asset::{AssetAmount, FungibleAsset, TokenSymbol}; use miden_standards::AuthMethod; use rand::RngCore; use std::sync::Arc; @@ -162,9 +162,7 @@ async fn main() -> anyhow::Result<()> { .build()?; let policies = TokenPolicyManager::new() .with_mint_policy(MintPolicyConfig::AllowAll, PolicyRegistration::Active)? - .with_burn_policy(BurnPolicyConfig::AllowAll, PolicyRegistration::Active)? - .with_send_policy(TransferPolicy::AllowAll, PolicyRegistration::Active)? - .with_receive_policy(TransferPolicy::AllowAll, PolicyRegistration::Active)?; + .with_burn_policy(BurnPolicyConfig::AllowAll, PolicyRegistration::Active)?; let alice_account = account_builder.build()?; let faucet_account = create_fungible_faucet( @@ -298,10 +296,11 @@ use miden_client::{ component::{ AccessControl, AuthScheme, AuthSingleSig, BasicWallet, BurnPolicyConfig, FungibleFaucet, MintPolicyConfig, PolicyRegistration, TokenName, TokenPolicyManager, - TransferPolicy, create_fungible_faucet, + create_fungible_faucet, }, Account, AccountBuilder, AccountType, }, + asset::{AssetAmount, AssetCallbackFlag, AssetVaultKey, FungibleAsset, TokenSymbol}, auth::AuthSecretKey, builder::ClientBuilder, keystore::{FilesystemKeyStore, Keystore}, @@ -310,9 +309,6 @@ use miden_client::{ transaction::TransactionRequestBuilder, }; use miden_client_sqlite_store::ClientBuilderSqliteExt; -use miden_protocol::asset::{ - AssetAmount, AssetCallbackFlag, AssetVaultKey, FungibleAsset, TokenSymbol, -}; use miden_standards::AuthMethod; use rand::RngCore; use std::sync::Arc; @@ -382,9 +378,7 @@ async fn main() -> anyhow::Result<()> { .build()?; let policies = TokenPolicyManager::new() .with_mint_policy(MintPolicyConfig::AllowAll, PolicyRegistration::Active)? - .with_burn_policy(BurnPolicyConfig::AllowAll, PolicyRegistration::Active)? - .with_send_policy(TransferPolicy::AllowAll, PolicyRegistration::Active)? - .with_receive_policy(TransferPolicy::AllowAll, PolicyRegistration::Active)?; + .with_burn_policy(BurnPolicyConfig::AllowAll, PolicyRegistration::Active)?; let alice_account = account_builder.build()?; let faucet_account = create_fungible_faucet( @@ -595,10 +589,11 @@ use miden_client::{ component::{ AccessControl, AuthScheme, AuthSingleSig, BasicWallet, BurnPolicyConfig, FungibleFaucet, MintPolicyConfig, PolicyRegistration, TokenName, TokenPolicyManager, - TransferPolicy, create_fungible_faucet, + create_fungible_faucet, }, Account, AccountBuilder, AccountId, AccountType, }, + asset::{AssetAmount, AssetCallbackFlag, AssetVaultKey, FungibleAsset, TokenSymbol}, auth::AuthSecretKey, builder::ClientBuilder, keystore::{FilesystemKeyStore, Keystore}, @@ -607,9 +602,6 @@ use miden_client::{ transaction::TransactionRequestBuilder, }; use miden_client_sqlite_store::ClientBuilderSqliteExt; -use miden_protocol::asset::{ - AssetAmount, AssetCallbackFlag, AssetVaultKey, FungibleAsset, TokenSymbol, -}; use miden_standards::AuthMethod; use rand::RngCore; use std::sync::Arc; @@ -679,9 +671,7 @@ async fn main() -> anyhow::Result<()> { .build()?; let policies = TokenPolicyManager::new() .with_mint_policy(MintPolicyConfig::AllowAll, PolicyRegistration::Active)? - .with_burn_policy(BurnPolicyConfig::AllowAll, PolicyRegistration::Active)? - .with_send_policy(TransferPolicy::AllowAll, PolicyRegistration::Active)? - .with_receive_policy(TransferPolicy::AllowAll, PolicyRegistration::Active)?; + .with_burn_policy(BurnPolicyConfig::AllowAll, PolicyRegistration::Active)?; let alice_account = account_builder.build()?; let faucet_account = create_fungible_faucet( diff --git a/docs/builder/get-started/read-storage.md b/docs/builder/get-started/read-storage.md index 198c9893..2f81f5ad 100644 --- a/docs/builder/get-started/read-storage.md +++ b/docs/builder/get-started/read-storage.md @@ -76,7 +76,7 @@ async fn main() -> anyhow::Result<()> { // READ PUBLIC STATE OF THE COUNTER ACCOUNT //------------------------------------------------------------ - let counter_account_id = AccountId::from_hex("0x224a96d294e10d006aef3d4f1b0876")?; + let counter_account_id = AccountId::from_hex("0x81cd2cf2dc5031f167a0eadc053ba2")?; client.import_account_by_id(counter_account_id).await?; @@ -88,7 +88,7 @@ async fn main() -> anyhow::Result<()> { // Read the count from the counter account's named storage map slot let slot_name = StorageSlotName::new( - "miden::component::miden_counter_account::count_map" + "counter_account::counter_contract::count_map" )?; let count_key = Word::from([0u32, 0, 0, 1]); let count = counter_account @@ -108,14 +108,14 @@ export async function demo() { // Initialize client to connect with the Miden Testnet. const client = await MidenClient.createTestnet(); - const counterAccountId = "0x224a96d294e10d006aef3d4f1b0876"; + const counterAccountId = "0x81cd2cf2dc5031f167a0eadc053ba2"; // Fetch the counter account (imports it into the local store if needed). const counter = await client.accounts.getOrImport(counterAccountId); // Get the count from the counter account by querying its storage map // using the named storage slot and counter key. - const slotName = "miden::component::miden_counter_account::count_map"; + const slotName = "counter_account::counter_contract::count_map"; const counterKey = new Word(BigUint64Array.from([0n, 0n, 0n, 1n])); const count = counter.storage().getMapItem(slotName, counterKey); @@ -141,12 +141,12 @@ You can also query the assets (tokens) held by an account: ```rust title="integration/src/bin/token-balance.rs" use miden_client::{ account::{Account, AccountId}, + asset::{AssetCallbackFlag, AssetVaultKey}, builder::ClientBuilder, keystore::FilesystemKeyStore, rpc::{Endpoint, GrpcClient}, }; use miden_client_sqlite_store::ClientBuilderSqliteExt; -use miden_protocol::asset::{AssetCallbackFlag, AssetVaultKey}; use std::sync::Arc; #[tokio::main] diff --git a/docs/builder/get-started/your-first-smart-contract/create.md b/docs/builder/get-started/your-first-smart-contract/create.md index 9d5d2248..4ba92e81 100644 --- a/docs/builder/get-started/your-first-smart-contract/create.md +++ b/docs/builder/get-started/your-first-smart-contract/create.md @@ -59,6 +59,9 @@ namespace = "miden:counter-account/counter-contract@0.1.0" [dependencies] miden-core = "*" miden-protocol = "*" + +[package.metadata.miden] +supported-types = ["RegularAccountImmutableCode"] ``` The increment note depends on the counter account's generated WIT so it can call the counter interface: @@ -70,7 +73,8 @@ version = "0.1.0" [lib] kind = "note" -namespace = "miden:increment-note/increment-note@0.1.0" +# Notes export a package-derived interface (`miden-`), matching the `#[note]` macro. +namespace = "miden:increment-note/miden-increment-note@0.1.0" [dependencies] miden-core = "*" @@ -88,11 +92,11 @@ You can build individual contracts by navigating to their directory and running ```bash title=">_ Terminal" # Build the counter account contract cd contracts/counter-account -miden build +cargo miden build # Build the increment note contract cd ../increment-note -miden build +cargo miden build ``` This compiles the Rust contract code into a Miden package (`.masp` file), making it ready for deployment and interaction. @@ -198,7 +202,7 @@ trait CounterContract { } ``` -The `#[component_storage]` attribute marks the storage struct for this Miden [Account component](/reference/protocol/account), while the `#[component]` trait defines the component's public interface. The `count_map` field is a `StorageMap` stored in a named storage slot of the account. In the v0.15-aligned SDK, storage slots are identified by name rather than explicit index numbers — the slot name is derived automatically from the component's package name and field name (e.g., `miden::component::miden_counter_account::count_map`). +The `#[component_storage]` attribute marks the storage struct for this Miden [Account component](/reference/protocol/account), while the `#[component]` trait defines the component's public interface. The `count_map` field is a `StorageMap` stored in a named storage slot of the account. In the v0.15-aligned SDK, storage slots are identified by name rather than explicit index numbers — the slot name is derived automatically from the component's manifest namespace and field name (e.g., `counter_account::counter_contract::count_map`). **Important**: Storage slots in Miden hold `Word` values, which are composed of four field elements (`Felt`). Each `Felt` is a 64-bit unsigned integer (u64). The `StorageMap` provides a key-value interface within a single storage slot, allowing you to store multiple key-value pairs within the four-element word structure. diff --git a/docs/builder/get-started/your-first-smart-contract/deploy.md b/docs/builder/get-started/your-first-smart-contract/deploy.md index 5ad5f72e..c9d2edb3 100644 --- a/docs/builder/get-started/your-first-smart-contract/deploy.md +++ b/docs/builder/get-started/your-first-smart-contract/deploy.md @@ -111,7 +111,7 @@ This process shows how Miden contracts are deployed through state changes rather ## How the Scripts Work -The integration scripts work by connecting to the Miden client and then building contracts from the Miden package files. These package files are generated when you run `miden build` inside each contract directory, but the scripts handle this compilation step automatically - you don't need to manually build the contracts before running the scripts. +The integration scripts work by connecting to the Miden client and then building contracts from the Miden package files. These package files are generated when you run `cargo miden build` inside each contract directory, but the scripts handle this compilation step automatically - you don't need to manually build the contracts before running the scripts. Next, we look into how the scripts convert your Rust contract code into deployable Miden contracts. @@ -151,7 +151,7 @@ The `build_project_in_dir()` function: - Takes the path to your contract's Rust source code - Compiles the Rust code into a Miden package (`.masp` file) - Generates a package containing the compiled contract bytecode and metadata -- This is equivalent to manually running `miden build` in each contract directory +- This is equivalent to manually running `cargo miden build` in each contract directory These packages contain all the information needed to deploy and interact with your contracts on the Miden network. @@ -167,7 +167,7 @@ let initial_count = Word::default(); // Use the slot name generated for the component's manifest namespace and field name. let counter_storage_slot = - StorageSlotName::new("miden::component::miden_counter_account::count_map").unwrap(); + StorageSlotName::new("counter_account::counter_contract::count_map").unwrap(); let storage_slots = vec![StorageSlot::with_map( counter_storage_slot.clone(), StorageMap::with_entries([(count_storage_map_key, initial_count)]).unwrap(), @@ -193,9 +193,9 @@ The `create_account_from_package()` function: - Combines it with the provided configuration (storage, settings, etc.) - Creates a deployable Miden account that can be used in transactions -**Important**: Accounts that use storage must have their storage slots specified when instantiating the account. In the v0.15-aligned SDK, storage slots are identified by name rather than index. The slot name follows the pattern `miden::component::::`. We define the storage configuration with: +**Important**: Accounts that use storage must have their storage slots specified when instantiating the account. In the v0.15-aligned SDK, storage slots are identified by name rather than index. The slot name follows the pattern `::::`, derived from the component's manifest namespace. We define the storage configuration with: -- A named `StorageMap` slot (`miden::component::miden_counter_account::count_map`) +- A named `StorageMap` slot (`counter_account::counter_contract::count_map`) - The counter key `[0, 0, 0, 1]`, wrapped as a `StorageMapKey`, with initial value `[0, 0, 0, 0]` (representing count = 0) This pre-initialization ensures the account's storage is properly configured before deployment. diff --git a/docs/builder/get-started/your-first-smart-contract/test.md b/docs/builder/get-started/your-first-smart-contract/test.md index 660e123f..442b7c37 100644 --- a/docs/builder/get-started/your-first-smart-contract/test.md +++ b/docs/builder/get-started/your-first-smart-contract/test.md @@ -101,7 +101,7 @@ async fn counter_test() -> anyhow::Result<()> { // Use the slot name generated for the component's manifest namespace and field name. let counter_storage_slot = - StorageSlotName::new("miden::component::miden_counter_account::count_map").unwrap(); + StorageSlotName::new("counter_account::counter_contract::count_map").unwrap(); let storage_slots = vec![StorageSlot::with_map( counter_storage_slot.clone(), StorageMap::with_entries([(StorageMapKey::new(count_storage_key), initial_count)]).unwrap(), @@ -209,7 +209,7 @@ let count_storage_key = Word::from([0u32, 0, 0, 1]); let initial_count = Word::default(); let counter_storage_slot = - StorageSlotName::new("miden::component::miden_counter_account::count_map").unwrap(); + StorageSlotName::new("counter_account::counter_contract::count_map").unwrap(); let storage_slots = vec![StorageSlot::with_map( counter_storage_slot.clone(), StorageMap::with_entries([(StorageMapKey::new(count_storage_key), initial_count)]).unwrap(), From b29c2115426a8b324ca66ece4273599233e941fe Mon Sep 17 00:00:00 2001 From: Melnik George Date: Wed, 5 Aug 2026 17:13:58 +0400 Subject: [PATCH 2/8] fix(get-started): restore transfer policies, resync snippets with miden new, repair install --- docs/builder/get-started/accounts.md | 7 ++- docs/builder/get-started/notes.md | 57 +++++++++++++------ docs/builder/get-started/read-storage.md | 41 +++++++------ .../builder/get-started/setup/installation.md | 8 ++- .../your-first-smart-contract/create.md | 28 +++++---- .../your-first-smart-contract/deploy.md | 17 +++--- .../your-first-smart-contract/test.md | 28 +++++---- 7 files changed, 114 insertions(+), 72 deletions(-) diff --git a/docs/builder/get-started/accounts.md b/docs/builder/get-started/accounts.md index f215492c..31beefbe 100644 --- a/docs/builder/get-started/accounts.md +++ b/docs/builder/get-started/accounts.md @@ -280,7 +280,8 @@ use miden_client::{ account::{ component::{ AccessControl, AuthScheme, BurnPolicyConfig, FungibleFaucet, MintPolicyConfig, - PolicyRegistration, TokenName, TokenPolicyManager, create_fungible_faucet, + PolicyRegistration, TokenName, TokenPolicyManager, TransferPolicy, + create_fungible_faucet, }, AccountType, }, @@ -343,7 +344,9 @@ async fn main() -> anyhow::Result<()> { .build()?; let policies = TokenPolicyManager::new() .with_mint_policy(MintPolicyConfig::AllowAll, PolicyRegistration::Active)? - .with_burn_policy(BurnPolicyConfig::AllowAll, PolicyRegistration::Active)?; + .with_burn_policy(BurnPolicyConfig::AllowAll, PolicyRegistration::Active)? + .with_send_policy(TransferPolicy::AllowAll, PolicyRegistration::Active)? + .with_receive_policy(TransferPolicy::AllowAll, PolicyRegistration::Active)?; let faucet_account = create_fungible_faucet( init_seed, faucet, diff --git a/docs/builder/get-started/notes.md b/docs/builder/get-started/notes.md index bc236b44..9ff9df26 100644 --- a/docs/builder/get-started/notes.md +++ b/docs/builder/get-started/notes.md @@ -81,11 +81,11 @@ use miden_client::{ component::{ AccessControl, AuthScheme, AuthSingleSig, BasicWallet, BurnPolicyConfig, FungibleFaucet, MintPolicyConfig, PolicyRegistration, TokenName, TokenPolicyManager, - create_fungible_faucet, + TransferPolicy, create_fungible_faucet, }, AccountBuilder, AccountType, }, - asset::{AssetAmount, FungibleAsset, TokenSymbol}, + asset::{AssetAmount, AssetCallbackFlag, FungibleAsset, TokenSymbol}, auth::AuthSecretKey, builder::ClientBuilder, keystore::{FilesystemKeyStore, Keystore}, @@ -162,7 +162,9 @@ async fn main() -> anyhow::Result<()> { .build()?; let policies = TokenPolicyManager::new() .with_mint_policy(MintPolicyConfig::AllowAll, PolicyRegistration::Active)? - .with_burn_policy(BurnPolicyConfig::AllowAll, PolicyRegistration::Active)?; + .with_burn_policy(BurnPolicyConfig::AllowAll, PolicyRegistration::Active)? + .with_send_policy(TransferPolicy::AllowAll, PolicyRegistration::Active)? + .with_receive_policy(TransferPolicy::AllowAll, PolicyRegistration::Active)?; let alice_account = account_builder.build()?; let faucet_account = create_fungible_faucet( @@ -191,7 +193,10 @@ async fn main() -> anyhow::Result<()> { keystore.add_key(&faucet_key_pair, faucet_account.id()).await?; let amount: u64 = 1000; - let fungible_asset = FungibleAsset::new(faucet_account.id(), amount)?; + // Enable asset callbacks so the faucet's send/receive transfer policies run + // when this asset moves between accounts. + let fungible_asset = FungibleAsset::new(faucet_account.id(), amount)? + .with_callbacks(AssetCallbackFlag::Enabled); // Build transaction request to mint fungible asset to Alice's account // NOTE: This transaction will create a P2ID note (a Miden note containing the minted asset) @@ -296,7 +301,7 @@ use miden_client::{ component::{ AccessControl, AuthScheme, AuthSingleSig, BasicWallet, BurnPolicyConfig, FungibleFaucet, MintPolicyConfig, PolicyRegistration, TokenName, TokenPolicyManager, - create_fungible_faucet, + TransferPolicy, create_fungible_faucet, }, Account, AccountBuilder, AccountType, }, @@ -378,7 +383,9 @@ async fn main() -> anyhow::Result<()> { .build()?; let policies = TokenPolicyManager::new() .with_mint_policy(MintPolicyConfig::AllowAll, PolicyRegistration::Active)? - .with_burn_policy(BurnPolicyConfig::AllowAll, PolicyRegistration::Active)?; + .with_burn_policy(BurnPolicyConfig::AllowAll, PolicyRegistration::Active)? + .with_send_policy(TransferPolicy::AllowAll, PolicyRegistration::Active)? + .with_receive_policy(TransferPolicy::AllowAll, PolicyRegistration::Active)?; let alice_account = account_builder.build()?; let faucet_account = create_fungible_faucet( @@ -407,7 +414,10 @@ async fn main() -> anyhow::Result<()> { keystore.add_key(&faucet_key_pair, faucet_account.id()).await?; let amount: u64 = 1000; - let fungible_asset = FungibleAsset::new(faucet_account.id(), amount)?; + // Enable asset callbacks so the faucet's send/receive transfer policies run + // when this asset moves between accounts. + let fungible_asset = FungibleAsset::new(faucet_account.id(), amount)? + .with_callbacks(AssetCallbackFlag::Enabled); // Build transaction request to mint fungible asset to Alice's account // NOTE: This transaction will create a P2ID note (a Miden note containing the minted asset) @@ -475,9 +485,11 @@ async fn main() -> anyhow::Result<()> { .ok_or_else(|| anyhow::anyhow!("Account not found"))? .try_into()?; let vault = alice_account.vault(); + // The callback flag is part of the vault key, so it must match the flag the + // asset was minted with — otherwise the lookup misses and the balance reads 0. let balance_key = AssetVaultKey::new_fungible( faucet_account.id(), - AssetCallbackFlag::Disabled, + AssetCallbackFlag::Enabled, ); println!( "Alice's TEST token balance: {:?}", @@ -558,7 +570,7 @@ Minting 1000 tokens to Alice... Mint transaction submitted successfully, ID: "0x7a2dbde87ea2f4d41b396d6d3f6bdb9a8d7e2a51555fa57064a1657ad70fca06" Waiting for note to be consumable... Consume transaction submitted successfully, ID: "0xa75872c498ee71cd6725aef9411d2559094cec1e1e89670dbf99c60bb8843481" -Alice's TEST token balance: Ok(1000) +Alice's TEST token balance: Ok(AssetAmount(1000)) ``` @@ -589,7 +601,7 @@ use miden_client::{ component::{ AccessControl, AuthScheme, AuthSingleSig, BasicWallet, BurnPolicyConfig, FungibleFaucet, MintPolicyConfig, PolicyRegistration, TokenName, TokenPolicyManager, - create_fungible_faucet, + TransferPolicy, create_fungible_faucet, }, Account, AccountBuilder, AccountId, AccountType, }, @@ -671,7 +683,9 @@ async fn main() -> anyhow::Result<()> { .build()?; let policies = TokenPolicyManager::new() .with_mint_policy(MintPolicyConfig::AllowAll, PolicyRegistration::Active)? - .with_burn_policy(BurnPolicyConfig::AllowAll, PolicyRegistration::Active)?; + .with_burn_policy(BurnPolicyConfig::AllowAll, PolicyRegistration::Active)? + .with_send_policy(TransferPolicy::AllowAll, PolicyRegistration::Active)? + .with_receive_policy(TransferPolicy::AllowAll, PolicyRegistration::Active)?; let alice_account = account_builder.build()?; let faucet_account = create_fungible_faucet( @@ -700,7 +714,10 @@ async fn main() -> anyhow::Result<()> { keystore.add_key(&faucet_key_pair, faucet_account.id()).await?; let amount: u64 = 1000; - let fungible_asset = FungibleAsset::new(faucet_account.id(), amount)?; + // Enable asset callbacks so the faucet's send/receive transfer policies run + // when this asset moves between accounts. + let fungible_asset = FungibleAsset::new(faucet_account.id(), amount)? + .with_callbacks(AssetCallbackFlag::Enabled); // Build transaction request to mint fungible asset to Alice's account // NOTE: This transaction will create a P2ID note (a Miden note containing the minted asset) @@ -768,9 +785,11 @@ async fn main() -> anyhow::Result<()> { .ok_or_else(|| anyhow::anyhow!("Account not found"))? .try_into()?; let vault = alice_account.vault(); + // The callback flag is part of the vault key, so it must match the flag the + // asset was minted with — otherwise the lookup misses and the balance reads 0. let balance_key = AssetVaultKey::new_fungible( faucet_account.id(), - AssetCallbackFlag::Disabled, + AssetCallbackFlag::Enabled, ); println!( "Alice's TEST token balance: {:?}", @@ -784,9 +803,12 @@ async fn main() -> anyhow::Result<()> { // SENDING TOKENS TO BOB //------------------------------------------------------------ - let bob_account_id = AccountId::from_hex("0x103f8a1ad4b983104aec0412ab0b0d")?; + // Replace this with the ID of the account you want to send to — for example an + // Alice ID printed by an earlier run of this guide. + let bob_account_id = AccountId::from_hex("0x...")?; let send_amount = 100; - let fungible_asset_to_send = FungibleAsset::new(faucet_account.id(), send_amount)?; + let fungible_asset_to_send = FungibleAsset::new(faucet_account.id(), send_amount)? + .with_callbacks(AssetCallbackFlag::Enabled); let p2id_note = P2idNote::create( alice_account.id(), @@ -869,8 +891,9 @@ export async function demo() { const balance = await client.accounts.getBalance(alice, faucet); console.log("Alice's TEST token balance:", Number(balance)); - // Send 100 tokens from Alice to Bob. - const bobAccountId = "0x103f8a1ad4b983104aec0412ab0b0d"; + // Send 100 tokens from Alice to Bob. Replace this with the ID of the account you + // want to send to — for example an Alice ID printed by an earlier run of this guide. + const bobAccountId = "0x..."; console.log("Sending 100 tokens to Bob..."); const { txId } = await client.transactions.send({ account: alice, diff --git a/docs/builder/get-started/read-storage.md b/docs/builder/get-started/read-storage.md index 2f81f5ad..2f5bb6d2 100644 --- a/docs/builder/get-started/read-storage.md +++ b/docs/builder/get-started/read-storage.md @@ -32,15 +32,19 @@ To run the code examples in this guide, you'll need to set up a development envi Let's interact with a counter contract deployed on the Miden testnet. This contract maintains a simple counter value in a named storage map slot. +:::note +This example needs the ID of a deployed counter account. [Your First Smart Contract](./your-first-smart-contract/deploy) walks through deploying one — come back here with that account's ID, or use any public counter account whose ID you already know. The deploy script logs the counter account ID in its structured (`Debug`) form; print `account.id().to_hex()` to get the `0x…` string these examples expect. +::: + ### Reading the Count of a Counter contract ```rust title="integration/src/bin/read-count.rs" +use integration::helpers::{counter_storage_slot, COUNTER_STORAGE_KEY}; use miden_client::{ - account::{Account, AccountId, StorageSlotName}, + account::{Account, AccountId}, builder::ClientBuilder, keystore::FilesystemKeyStore, rpc::{Endpoint, GrpcClient}, - Word, }; use miden_client_sqlite_store::ClientBuilderSqliteExt; use std::sync::Arc; @@ -76,7 +80,8 @@ async fn main() -> anyhow::Result<()> { // READ PUBLIC STATE OF THE COUNTER ACCOUNT //------------------------------------------------------------ - let counter_account_id = AccountId::from_hex("0x81cd2cf2dc5031f167a0eadc053ba2")?; + // Replace this with the ID of the counter account you deployed (see the note above). + let counter_account_id = AccountId::from_hex("0x...")?; client.import_account_by_id(counter_account_id).await?; @@ -86,14 +91,11 @@ async fn main() -> anyhow::Result<()> { .ok_or_else(|| anyhow::anyhow!("Account not found"))? .try_into()?; - // Read the count from the counter account's named storage map slot - let slot_name = StorageSlotName::new( - "counter_account::counter_contract::count_map" - )?; - let count_key = Word::from([0u32, 0, 0, 1]); + // Read the count from the counter account's named storage map slot. Both the slot + // name and the map key come from the project's `integration/src/helpers.rs`. let count = counter_account .storage() - .get_map_item(&slot_name, count_key)?; + .get_map_item(&counter_storage_slot()?, COUNTER_STORAGE_KEY)?; println!("Count: {:?}", count); @@ -108,7 +110,8 @@ export async function demo() { // Initialize client to connect with the Miden Testnet. const client = await MidenClient.createTestnet(); - const counterAccountId = "0x81cd2cf2dc5031f167a0eadc053ba2"; + // Replace this with the ID of the counter account you deployed (see the note above). + const counterAccountId = "0x..."; // Fetch the counter account (imports it into the local store if needed). const counter = await client.accounts.getOrImport(counterAccountId); @@ -129,9 +132,11 @@ export async function demo() { Expected output ```text -Count: 1 +Count: Word([1, 0, 0, 0]) ``` +The Rust example prints the raw `Word`; map values use a `[value, 0, 0, 0]` layout, so the count is the first element. The TypeScript example unpacks that word and logs the number directly. + ## Reading Account Token Balances @@ -180,8 +185,9 @@ async fn main() -> anyhow::Result<()> { // READ TOKEN BALANCE OF AN ACCOUNT //------------------------------------------------------------ - let alice_account_id = AccountId::from_hex("0x5b2840a923dedc102ea67e0c1eba3c")?; - let faucet_account_id = AccountId::from_hex("0x29dd1dc628d2842032e751ed1b5da7")?; + // Replace these with the IDs printed by the mint example in the previous section. + let alice_account_id = AccountId::from_hex("0x...")?; + let faucet_account_id = AccountId::from_hex("0x...")?; client.import_account_by_id(alice_account_id).await?; @@ -191,9 +197,11 @@ async fn main() -> anyhow::Result<()> { .ok_or_else(|| anyhow::anyhow!("Account not found"))? .try_into()?; + // The callback flag is part of the vault key, so it must match the flag the asset + // was minted with — otherwise the lookup misses and the balance reads 0. let balance_key = AssetVaultKey::new_fungible( faucet_account_id, - AssetCallbackFlag::Disabled, + AssetCallbackFlag::Enabled, ); let balance = alice_account.vault().get_balance(balance_key)?; @@ -210,8 +218,9 @@ export async function demo() { // Initialize client to connect with the Miden Testnet. const client = await MidenClient.createTestnet(); - const aliceId = "0x5b2840a923dedc102ea67e0c1eba3c"; - const faucetId = "0x29dd1dc628d2842032e751ed1b5da7"; + // Replace these with the IDs printed by the mint example in the previous section. + const aliceId = "0x..."; + const faucetId = "0x..."; // Fetch Alice's account (imports it into the local store if needed) // and query her balance for the faucet's token. diff --git a/docs/builder/get-started/setup/installation.md b/docs/builder/get-started/setup/installation.md index 6616afed..3d98c7b0 100644 --- a/docs/builder/get-started/setup/installation.md +++ b/docs/builder/get-started/setup/installation.md @@ -85,11 +85,15 @@ v22.x.x # or higher The Miden toolchain installer makes it easy to manage Miden components: ```bash title=">_ Terminal" -cargo install midenup +cargo install midenup --version 1.0.0-alpha.1 ``` +:::caution +Pass `--version` explicitly. A bare `cargo install midenup` resolves to the latest **stable** release (0.3.2), which cannot read the current channel manifest and fails with `channel manifest v2.0.0 requires a newer version of midenup`. +::: + :::info -Until published to crates.io, install using: `cargo install --git https://github.com/0xMiden/midenup.git` +To install from source instead, name the package explicitly — the repository contains more than one binary: `cargo install --git https://github.com/0xMiden/midenup.git midenup` ::: **Initialize midenup** diff --git a/docs/builder/get-started/your-first-smart-contract/create.md b/docs/builder/get-started/your-first-smart-contract/create.md index 4ba92e81..82ac2131 100644 --- a/docs/builder/get-started/your-first-smart-contract/create.md +++ b/docs/builder/get-started/your-first-smart-contract/create.md @@ -54,6 +54,8 @@ version = "0.1.0" [lib] kind = "account-component" +# Full `miden:/@` id. The interface segment is the +# kebab-cased component trait name (`CounterContract` -> `counter-contract`). namespace = "miden:counter-account/counter-contract@0.1.0" [dependencies] @@ -81,6 +83,7 @@ miden-core = "*" miden-protocol = "*" counter-account = { path = "../counter-account" } +# WIT for the account component this note calls, produced by building counter-account. [package.metadata.miden.dependencies] counter-account = { wit = "../counter-account/target/generated-wit/" } ``` @@ -117,7 +120,7 @@ Let's examine the counter account contract that comes with the project template. use miden::{component, component_storage, felt, Felt, StorageMap, Word}; -/// Storage for the counter example. +/// Storage layout for the counter example. #[component_storage] struct CounterContractStorage { /// Storage map holding the counter value. @@ -125,16 +128,17 @@ struct CounterContractStorage { count_map: StorageMap, } -/// Public interface for the counter component. +/// API of the counter contract account component. #[component] trait CounterContract { + /// Returns the current counter value stored in the contract's storage map. fn get_count(&self) -> Felt; + /// Increments the counter value stored in the contract's storage map by one. fn increment_count(&mut self) -> Felt; } #[component] impl CounterContract for CounterContractStorage { - /// Returns the current counter value stored in the contract's storage map. fn get_count(&self) -> Felt { // Define a fixed key for the counter value within the map let key = Word::new([felt!(0), felt!(0), felt!(0), felt!(1)]); @@ -142,7 +146,6 @@ impl CounterContract for CounterContractStorage { self.count_map.get(key) } - /// Increments the counter value stored in the contract's storage map by one. fn increment_count(&mut self) -> Felt { // Define the same fixed key let key = Word::new([felt!(0), felt!(0), felt!(0), felt!(1)]); @@ -197,7 +200,9 @@ struct CounterContractStorage { #[component] trait CounterContract { + /// Returns the current counter value stored in the contract's storage map. fn get_count(&self) -> Felt; + /// Increments the counter value stored in the contract's storage map by one. fn increment_count(&mut self) -> Felt; } ``` @@ -239,10 +244,11 @@ Now let's examine the increment note script at `contracts/increment-note/src/lib // extern crate alloc; // use alloc::vec::Vec; -use miden::{account, note, Felt, Word}; +use miden::*; +/// Native account of the note: exposes the `counter-contract` component methods gathered from the `counter-contract` package. #[account(counter_account::CounterContract)] -pub struct CounterAccount; +pub struct Wallet; #[note] struct IncrementNote; @@ -250,7 +256,7 @@ struct IncrementNote; #[note] impl IncrementNote { #[note_script] - fn run(self, _arg: Word, account: &mut CounterAccount) { + fn run(self, _arg: Word, account: &mut Wallet) { let initial_value = account.get_count(); account.increment_count(); let expected_value = initial_value + Felt::from_u32(1); @@ -269,10 +275,10 @@ Similar to the account contract, the note script uses `#![no_std]` with the same #### Miden Imports ```rust -use miden::{account, note, Felt, Word}; +use miden::*; ``` -These imports bring in the note macro, explicit account binding macro, and the basic field/word types used by the note. +The note script glob-imports the `miden` prelude: the `#[note]` and `#[account]` macros, the basic field/word types (`Felt`, `Word`), and free functions such as `assert_eq`. Listing the imports individually is easy to get wrong — `assert_eq` here is a function from the prelude, not Rust's `assert_eq!` macro, so omitting it fails to compile. #### Note Script Structure @@ -285,11 +291,11 @@ struct IncrementNote; #[note] impl IncrementNote { #[note_script] - fn run(self, _arg: Word, account: &mut CounterAccount) { ... } + fn run(self, _arg: Word, account: &mut Wallet) { ... } } ``` -The struct definition (`IncrementNote`) provides a named type for the note script. Unlike account contracts, note scripts don't store persistent data — the struct serves as the entry point container. The `CounterAccount` type is declared with `#[account(counter_account::CounterContract)]`, which binds the note to the counter account interface generated from `miden-project.toml`. +The struct definition (`IncrementNote`) provides a named type for the note script. Unlike account contracts, note scripts don't store persistent data — the struct serves as the entry point container. The `Wallet` type is declared with `#[account(counter_account::CounterContract)]`, which binds the note to the counter account interface generated from `miden-project.toml`. Learn more about [note scripts in the Miden documentation](/reference/protocol/note/). diff --git a/docs/builder/get-started/your-first-smart-contract/deploy.md b/docs/builder/get-started/your-first-smart-contract/deploy.md index c9d2edb3..204a3152 100644 --- a/docs/builder/get-started/your-first-smart-contract/deploy.md +++ b/docs/builder/get-started/your-first-smart-contract/deploy.md @@ -39,7 +39,7 @@ The integration folder serves two essential functions in Miden development: ### 1. Contract Interaction Scripts (Binary Executables) -Think of the scripts in `src/bin/` as Miden's equivalent to [**Foundry scripts**](https://getfoundry.sh/guides/scripting-with-solidity). These are executable Rust binaries that handle all your contract interactions: +Think of the scripts in `src/bin/` as Miden's equivalent to [**Foundry scripts**](https://www.getfoundry.sh/forge/scripting). These are executable Rust binaries that handle all your contract interactions: - **Contract Deployment**: Scripts that create and deploy accounts to the network - **Function/Procedure Calls**: Scripts that interact with deployed contracts through notes or [transaction scripts](/reference/protocol/transaction#transaction-lifecycle) @@ -160,14 +160,13 @@ These packages contain all the information needed to deploy and interact with yo Once we have the compiled packages, we convert them into deployable accounts and notes: ```rust -// Configure initial storage for the counter account -let count_storage_key = Word::from([0u32, 0, 0, 1]); -let count_storage_map_key = StorageMapKey::new(count_storage_key); +// Configure initial storage for the counter account. `COUNTER_STORAGE_KEY` and +// `counter_storage_slot()` both come from `integration/src/helpers.rs`, so the key +// and slot name stay in one place instead of being repeated as literals. +let count_storage_map_key = StorageMapKey::new(COUNTER_STORAGE_KEY); let initial_count = Word::default(); -// Use the slot name generated for the component's manifest namespace and field name. -let counter_storage_slot = - StorageSlotName::new("counter_account::counter_contract::count_map").unwrap(); +let counter_storage_slot = counter_storage_slot()?; let storage_slots = vec![StorageSlot::with_map( counter_storage_slot.clone(), StorageMap::with_entries([(count_storage_map_key, initial_count)]).unwrap(), @@ -195,8 +194,8 @@ The `create_account_from_package()` function: **Important**: Accounts that use storage must have their storage slots specified when instantiating the account. In the v0.15-aligned SDK, storage slots are identified by name rather than index. The slot name follows the pattern `::::`, derived from the component's manifest namespace. We define the storage configuration with: -- A named `StorageMap` slot (`counter_account::counter_contract::count_map`) -- The counter key `[0, 0, 0, 1]`, wrapped as a `StorageMapKey`, with initial value `[0, 0, 0, 0]` (representing count = 0) +- A named `StorageMap` slot, returned by the `counter_storage_slot()` helper (`counter_account::counter_contract::count_map`) +- The counter key `COUNTER_STORAGE_KEY` (`[0, 0, 0, 1]`), wrapped as a `StorageMapKey`, with initial value `[0, 0, 0, 0]` (representing count = 0) This pre-initialization ensures the account's storage is properly configured before deployment. diff --git a/docs/builder/get-started/your-first-smart-contract/test.md b/docs/builder/get-started/your-first-smart-contract/test.md index 442b7c37..ac1e29c8 100644 --- a/docs/builder/get-started/your-first-smart-contract/test.md +++ b/docs/builder/get-started/your-first-smart-contract/test.md @@ -62,12 +62,13 @@ Your project includes a comprehensive test file at `integration/tests/counter_te ```rust title="integration/tests/counter_test.rs" use integration::helpers::{ - build_project_in_dir, create_testing_account_from_package, create_testing_note_from_package, - AccountCreationConfig, NoteCreationConfig, + build_project_in_dir, counter_storage_slot, create_testing_account_from_package, + create_testing_note_from_package, AccountCreationConfig, NoteCreationConfig, + COUNTER_STORAGE_KEY, }; use miden_client::{ - account::{StorageMap, StorageMapKey, StorageSlot, StorageSlotName}, + account::{StorageMap, StorageMapKey, StorageSlot}, auth::AuthSchemeId, transaction::RawOutputNote, Word, @@ -95,16 +96,15 @@ async fn counter_test() -> anyhow::Result<()> { true, )?); - // Create the counter account with initial storage and no-auth auth component - let count_storage_key = Word::from([0u32, 0, 0, 1]); + // Create the counter account with initial storage and no-auth auth component. + // The slot name and map key come from `integration/src/helpers.rs`. let initial_count = Word::default(); - // Use the slot name generated for the component's manifest namespace and field name. - let counter_storage_slot = - StorageSlotName::new("counter_account::counter_contract::count_map").unwrap(); + let counter_storage_slot = counter_storage_slot()?; let storage_slots = vec![StorageSlot::with_map( counter_storage_slot.clone(), - StorageMap::with_entries([(StorageMapKey::new(count_storage_key), initial_count)]).unwrap(), + StorageMap::with_entries([(StorageMapKey::new(COUNTER_STORAGE_KEY), initial_count)]) + .unwrap(), )]; let counter_cfg = AccountCreationConfig { storage_slots, @@ -146,7 +146,7 @@ async fn counter_test() -> anyhow::Result<()> { // Get the count from the updated counter account let count = counter_account .storage() - .get_map_item(&counter_storage_slot, count_storage_key) + .get_map_item(&counter_storage_slot, COUNTER_STORAGE_KEY) .expect("Failed to get counter value from storage slot"); // Assert that the count value is equal to 1 after executing the transaction @@ -205,14 +205,12 @@ let note_package = Arc::new(build_project_in_dir( ```rust // Create the counter account with initial storage and no-auth auth component -let count_storage_key = Word::from([0u32, 0, 0, 1]); let initial_count = Word::default(); -let counter_storage_slot = - StorageSlotName::new("counter_account::counter_contract::count_map").unwrap(); +let counter_storage_slot = counter_storage_slot()?; let storage_slots = vec![StorageSlot::with_map( counter_storage_slot.clone(), - StorageMap::with_entries([(StorageMapKey::new(count_storage_key), initial_count)]).unwrap(), + StorageMap::with_entries([(StorageMapKey::new(COUNTER_STORAGE_KEY), initial_count)]).unwrap(), )]; let counter_cfg = AccountCreationConfig { storage_slots, @@ -277,7 +275,7 @@ mock_chain.prove_next_block()?; // Get the count from the updated counter account let count = counter_account .storage() - .get_map_item(&counter_storage_slot, count_storage_key) + .get_map_item(&counter_storage_slot, COUNTER_STORAGE_KEY) .expect("Failed to get counter value from storage slot"); // Assert that the count value is equal to 1 after executing the transaction From de916bf86ea068e8b2da27dc569a2e31f7c8135a Mon Sep 17 00:00:00 2001 From: Melnik George Date: Wed, 5 Aug 2026 18:36:15 +0400 Subject: [PATCH 3/8] fix(get-started): resync examples with miden new, restore transfer policies --- .../your-first-smart-contract/deploy.md | 63 +++---- .../your-first-smart-contract/test.md | 159 +++++++++--------- 2 files changed, 113 insertions(+), 109 deletions(-) diff --git a/docs/builder/get-started/your-first-smart-contract/deploy.md b/docs/builder/get-started/your-first-smart-contract/deploy.md index 204a3152..2966d4fb 100644 --- a/docs/builder/get-started/your-first-smart-contract/deploy.md +++ b/docs/builder/get-started/your-first-smart-contract/deploy.md @@ -83,14 +83,18 @@ cargo run --bin increment_count --release Expected Output ```text -Account ID: V0(AccountIdV0 { prefix: 14134910893364381952, suffix: 3644349760121494784 }) -Sender account ID: "0xd85b347218c5a80052dbd47b2f36ad" -Counter note hash: "0xf0e821396a896eb9983e682bc056021d57ddcaa43082f34597bf9e026421e566" -Note publish transaction ID: "0xc6f080855724402cadf26650ffe993fe97a127a8f6c9c82ec621960e936e6d732 -Consume transaction ID: "0x2d1d8510e546ce0fbc22fa7d1a82322259d73cd1d7e0ca86622d0be70fab0548" -Account delta: AccountDelta { account_id: V0(AccountIdV0 { prefix: 7255964780328958976, suffix: 2724050564200846336 }), storage: AccountStorageDelta { values: {}, maps: {0: StorageMapDelta({LexicographicWord(Word([0, 0, 0, 1])): Word([0, 0, 0, 1])})} }, vault: AccountVaultDelta { fungible: FungibleAssetDelta({}), non_fungible: NonFungibleAssetDelta({}) }, nonce_delta: 1 } +Latest block: 1238402 +Account ID: V1(AccountIdV1 { suffix: 6091438912547090176, prefix: 14314767458661568337 }) +Sender account ID: "0x7d9c7007a5773c116bc58f9f28762b" +Counter note hash: "0xa3642c5eb08d8298a2fb8ed7f268f13c4139771c77540768a7347c882f47ab4f" +Note publish transaction ID: "0xfc190c8edbf972115cd5f00b22c8eac06c9515d0403b84ff697efa7093d6b8a7" +Consume transaction ID: "0xa1aa7971e146710df74a674b7e99d1968bec8d2db1c4da6077a7e22843c92045" ``` +:::note +The counter account ID is logged in its structured form. To get the `0x…` string that `AccountId::from_hex()` expects — for example to read the counter back in [Read Storage Values](../read-storage) — print `account.id().to_hex()`. +::: + Congratulations, you have successfully deployed the Counter Contract to the Miden Testnet, and incremented its count by one! You can verify your transaction on [MidenScan](https://testnet.midenscan.com) by searching for your transaction ID. @@ -160,19 +164,16 @@ These packages contain all the information needed to deploy and interact with yo Once we have the compiled packages, we convert them into deployable accounts and notes: ```rust -// Configure initial storage for the counter account. `COUNTER_STORAGE_KEY` and -// `counter_storage_slot()` both come from `integration/src/helpers.rs`, so the key -// and slot name stay in one place instead of being repeated as literals. -let count_storage_map_key = StorageMapKey::new(COUNTER_STORAGE_KEY); -let initial_count = Word::default(); - +// Create the counter account with initial component storage. `COUNTER_STORAGE_KEY` +// and `counter_storage_slot()` both come from `integration/src/helpers.rs`, so the +// key and slot name stay in one place instead of being repeated as literals. let counter_storage_slot = counter_storage_slot()?; -let storage_slots = vec![StorageSlot::with_map( - counter_storage_slot.clone(), - StorageMap::with_entries([(count_storage_map_key, initial_count)]).unwrap(), -)]; +let mut init_storage_data = InitStorageData::default(); +init_storage_data + .insert_map_entry(counter_storage_slot, COUNTER_STORAGE_KEY, 0_u64) + .context("Failed to seed counter storage")?; let counter_cfg = AccountCreationConfig { - storage_slots, + init_storage_data, ..Default::default() }; @@ -192,10 +193,12 @@ The `create_account_from_package()` function: - Combines it with the provided configuration (storage, settings, etc.) - Creates a deployable Miden account that can be used in transactions -**Important**: Accounts that use storage must have their storage slots specified when instantiating the account. In the v0.15-aligned SDK, storage slots are identified by name rather than index. The slot name follows the pattern `::::`, derived from the component's manifest namespace. We define the storage configuration with: +**Important**: Accounts that use storage must have that storage seeded when instantiating the account. In the v0.15-aligned SDK, storage slots are identified by name rather than index. The slot name follows the pattern `::::`, derived from the component's manifest namespace. We seed the storage with: - A named `StorageMap` slot, returned by the `counter_storage_slot()` helper (`counter_account::counter_contract::count_map`) -- The counter key `COUNTER_STORAGE_KEY` (`[0, 0, 0, 1]`), wrapped as a `StorageMapKey`, with initial value `[0, 0, 0, 0]` (representing count = 0) +- The counter key `COUNTER_STORAGE_KEY` (`[0, 0, 0, 1]`), mapped to the initial value `0` (representing count = 0) + +`InitStorageData` carries these seed values into `AccountComponent::from_package()`, which the helper calls for you. This pre-initialization ensures the account's storage is properly configured before deployment. @@ -204,14 +207,12 @@ This pre-initialization ensures the account's storage is properly configured bef Similarly, we convert the note package into an executable note: ```rust -// Convert the increment note package into an executable note -let counter_note = create_note_from_package( - &mut client, - note_package.clone(), - sender_account.id(), - NoteCreationConfig::default() -) -.context("Failed to create counter note from package")?; +// Build the increment note directly from the compiled package. +let counter_note = NoteBuilder::new(sender_account.id(), client.rng()) + .package((*note_package).clone()) + .tag(0) + .build() + .context("Failed to create counter note from package")?; // Publish the note to the network let note_publish_request = TransactionRequestBuilder::new() @@ -220,11 +221,11 @@ let note_publish_request = TransactionRequestBuilder::new() .context("Failed to build note publish transaction request")?; ``` -The `create_note_from_package()` function: +`NoteBuilder` (from `miden_standards::testing::note`): -- Takes the compiled note script package -- Combines it with the sender account ID and configuration -- Creates an executable note containing the increment script logic +- Takes the sender account ID and the client's RNG +- Accepts the compiled note script package via `.package()` +- Produces an executable note containing the increment script logic - The note can then be published to the network and consumed by the target (counter) account This demonstrates the complete workflow: Rust source code → compiled packages → deployable accounts/notes → network transactions. diff --git a/docs/builder/get-started/your-first-smart-contract/test.md b/docs/builder/get-started/your-first-smart-contract/test.md index ac1e29c8..e1f7de4a 100644 --- a/docs/builder/get-started/your-first-smart-contract/test.md +++ b/docs/builder/get-started/your-first-smart-contract/test.md @@ -47,12 +47,13 @@ You should see output confirming the test passes: ```text title="Expected Output" running 1 test -Test passed! test counter_test ... ok test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out ``` +The test compiles both contracts before it runs, so the first invocation takes noticeably longer than later ones. + ## Understanding the Mockchain Test Your project includes a comprehensive test file at `integration/tests/counter_test.rs` that demonstrates how to test the counter contract using the Mockchain. Let's walk through this test to understand the testing patterns: @@ -61,20 +62,20 @@ Your project includes a comprehensive test file at `integration/tests/counter_te Test File ```rust title="integration/tests/counter_test.rs" -use integration::helpers::{ - build_project_in_dir, counter_storage_slot, create_testing_account_from_package, - create_testing_note_from_package, AccountCreationConfig, NoteCreationConfig, - COUNTER_STORAGE_KEY, -}; +use std::{path::Path, sync::Arc}; +use anyhow::Context; +use integration::helpers::{build_project_in_dir, counter_storage_slot, COUNTER_STORAGE_KEY}; use miden_client::{ - account::{StorageMap, StorageMapKey, StorageSlot}, + account::{component::InitStorageData, AccountBuilder, AccountComponent, AccountType}, auth::AuthSchemeId, + crypto::RandomCoin, + note::NoteScript, transaction::RawOutputNote, Word, }; -use miden_testing::{Auth, MockChain}; -use std::{path::Path, sync::Arc}; +use miden_standards::testing::note::NoteBuilder; +use miden_testing::{AccountState, Auth, MockChain}; #[tokio::test] async fn counter_test() -> anyhow::Result<()> { @@ -96,67 +97,64 @@ async fn counter_test() -> anyhow::Result<()> { true, )?); - // Create the counter account with initial storage and no-auth auth component. - // The slot name and map key come from `integration/src/helpers.rs`. - let initial_count = Word::default(); - + // Create the counter account with its initial storage through the component schema. let counter_storage_slot = counter_storage_slot()?; - let storage_slots = vec![StorageSlot::with_map( - counter_storage_slot.clone(), - StorageMap::with_entries([(StorageMapKey::new(COUNTER_STORAGE_KEY), initial_count)]) - .unwrap(), - )]; - let counter_cfg = AccountCreationConfig { - storage_slots, - ..Default::default() - }; - - // create testing counter account - let mut counter_account = - create_testing_account_from_package(contract_package.clone(), counter_cfg).await?; - - // create testing increment note - let counter_note = create_testing_note_from_package( - note_package.clone(), - sender.id(), - NoteCreationConfig::default(), + let mut init_storage_data = InitStorageData::default(); + init_storage_data.insert_map_entry(counter_storage_slot.clone(), COUNTER_STORAGE_KEY, 0_u64)?; + + let counter_component = AccountComponent::from_package(&contract_package, &init_storage_data) + .context("failed to build account component from counter package")?; + let counter_account = builder.add_account_from_builder( + Auth::BasicAuth { + auth_scheme: AuthSchemeId::Falcon512Poseidon2, + }, + AccountBuilder::new([3_u8; 32]) + .account_type(AccountType::Public) + .with_component(counter_component), + AccountState::Exists, )?; + let mut note_rng = RandomCoin::new(Word::from( + NoteScript::from_package(note_package.as_ref()) + .context("failed to build note script from package")? + .root(), + )); + let counter_note = NoteBuilder::new(sender.id(), &mut note_rng) + .package((*note_package).clone()) + .build() + .context("failed to build counter note from package")?; + // add counter account and note to mockchain - builder.add_account(counter_account.clone())?; builder.add_output_note(RawOutputNote::Full(counter_note.clone())); // Build the mock chain let mut mock_chain = builder.build()?; + // Build the transaction context let tx_context = mock_chain - .build_tx_context(counter_account.id(), &[counter_note.id()], &[])? + .build_tx_context(counter_account.clone(), &[counter_note.id()], &[])? .build()?; // Execute the transaction let executed_transaction = tx_context.execute().await?; - // Apply the account delta to the counter account - counter_account.apply_delta(executed_transaction.account_delta())?; - // Add the executed transaction to the mockchain mock_chain.add_pending_executed_transaction(&executed_transaction)?; mock_chain.prove_next_block()?; // Get the count from the updated counter account - let count = counter_account + let count = mock_chain + .committed_account(counter_account.id())? .storage() .get_map_item(&counter_storage_slot, COUNTER_STORAGE_KEY) .expect("Failed to get counter value from storage slot"); - // Assert that the count value is equal to 1 after executing the transaction + // Map values are returned as scalar words in `[value, 0, 0, 0]` layout. assert_eq!( - count, - Word::from([0u32, 0, 0, 1]), + count[0].as_canonical_u64(), + 1, "Count value is not equal to 1" ); - - println!("Test passed!"); Ok(()) } ``` @@ -204,54 +202,60 @@ let note_package = Arc::new(build_project_in_dir( ### 3. Creating the Test Account and Note ```rust -// Create the counter account with initial storage and no-auth auth component -let initial_count = Word::default(); - +// Create the counter account with its initial storage through the component schema. let counter_storage_slot = counter_storage_slot()?; -let storage_slots = vec![StorageSlot::with_map( - counter_storage_slot.clone(), - StorageMap::with_entries([(StorageMapKey::new(COUNTER_STORAGE_KEY), initial_count)]).unwrap(), -)]; -let counter_cfg = AccountCreationConfig { - storage_slots, - ..Default::default() -}; +let mut init_storage_data = InitStorageData::default(); +init_storage_data.insert_map_entry(counter_storage_slot.clone(), COUNTER_STORAGE_KEY, 0_u64)?; -// Create testing entities -let mut counter_account = create_testing_account_from_package(contract_package.clone(), counter_cfg).await?; -let counter_note = create_testing_note_from_package( - note_package.clone(), - sender.id(), - NoteCreationConfig::default(), +let counter_component = AccountComponent::from_package(&contract_package, &init_storage_data) + .context("failed to build account component from counter package")?; +let counter_account = builder.add_account_from_builder( + Auth::BasicAuth { + auth_scheme: AuthSchemeId::Falcon512Poseidon2, + }, + AccountBuilder::new([3_u8; 32]) + .account_type(AccountType::Public) + .with_component(counter_component), + AccountState::Exists, )?; + +let mut note_rng = RandomCoin::new(Word::from( + NoteScript::from_package(note_package.as_ref()) + .context("failed to build note script from package")? + .root(), +)); +let counter_note = NoteBuilder::new(sender.id(), &mut note_rng) + .package((*note_package).clone()) + .build() + .context("failed to build counter note from package")?; ``` **What's happening:** -- We configure the **counter account's initial storage** with count = 0 at storage key `[0, 0, 0, 1]` -- We create the **testing counter account** from the compiled package using `create_testing_account_from_package()` -- We create the **testing increment note** using `create_testing_note_from_package()` -- These helper functions create test-specific versions optimized for the Mockchain environment +- We seed the **counter account's initial storage** through `InitStorageData`, mapping `COUNTER_STORAGE_KEY` to `0` inside the named slot returned by `counter_storage_slot()` +- `AccountComponent::from_package()` turns the compiled package plus that storage seed into an account component +- `builder.add_account_from_builder()` registers the account with the mockchain in the `AccountState::Exists` state, so it behaves like an already-deployed account +- The note is built with `NoteBuilder`, seeded from a `RandomCoin` derived from the note script's MAST root — this keeps note generation deterministic across test runs -### 4. Adding Components to the Mockchain +### 4. Adding the Note to the Mockchain ```rust -builder.add_account(counter_account.clone())?; builder.add_output_note(RawOutputNote::Full(counter_note.clone())); let mut mock_chain = builder.build()?; ``` **What's happening:** -- We **add the counter account** to the mockchain builder - We **add the increment note** as a full output note to the mockchain - We **build the mockchain** - now we have a complete testing environment ready to use +The counter account does not need a separate `add_account()` call: `add_account_from_builder()` already registered it in the previous step. + ### 5. Creating and Executing the Transaction ```rust let tx_context = mock_chain - .build_tx_context(counter_account.id(), &[counter_note.id()], &[])? + .build_tx_context(counter_account.clone(), &[counter_note.id()], &[])? .build()?; let executed_transaction = tx_context.execute().await?; @@ -265,34 +269,33 @@ let executed_transaction = tx_context.execute().await?; ### 6. Verifying the Results ```rust -// Apply the account delta to the counter account -counter_account.apply_delta(executed_transaction.account_delta())?; - // Add the executed transaction to the mockchain mock_chain.add_pending_executed_transaction(&executed_transaction)?; mock_chain.prove_next_block()?; // Get the count from the updated counter account -let count = counter_account +let count = mock_chain + .committed_account(counter_account.id())? .storage() .get_map_item(&counter_storage_slot, COUNTER_STORAGE_KEY) .expect("Failed to get counter value from storage slot"); -// Assert that the count value is equal to 1 after executing the transaction +// Map values are returned as scalar words in `[value, 0, 0, 0]` layout. assert_eq!( - count, - Word::from([0u32, 0, 0, 1]), + count[0].as_canonical_u64(), + 1, "Count value is not equal to 1" ); ``` **What's happening:** -- We **apply the account delta** from the executed transaction to the counter account to update its state -- We **add the executed transaction** to the mockchain -- We **read the counter value** from storage using the same key we initialized +- We **add the executed transaction** to the mockchain and prove the next block, which commits the new account state +- We **read the counter value** back from `mock_chain.committed_account()` — the committed state already reflects the transaction, so there is no need to apply the account delta by hand - We **assert that the count equals 1** - verifying the increment operation worked correctly +Note the value layout: map values come back as words shaped `[value, 0, 0, 0]`, so the count lives in the first element, read here with `count[0].as_canonical_u64()`. + The test verifies the complete flow: the increment note successfully increments the counter from 0 to 1, proving our smart contract works as expected. ## Next Steps From 0e8904151b232a1055cfbd42be16cae7051e4d48 Mon Sep 17 00:00:00 2001 From: Melnik George Date: Thu, 6 Aug 2026 16:22:39 +0400 Subject: [PATCH 4/8] docs(get-started): trim review-flagged notes, fix Word value wording --- docs/builder/get-started/read-storage.md | 2 +- docs/builder/get-started/setup/installation.md | 4 +--- .../get-started/your-first-smart-contract/deploy.md | 12 +++--------- 3 files changed, 5 insertions(+), 13 deletions(-) diff --git a/docs/builder/get-started/read-storage.md b/docs/builder/get-started/read-storage.md index 2f5bb6d2..db97cf0e 100644 --- a/docs/builder/get-started/read-storage.md +++ b/docs/builder/get-started/read-storage.md @@ -135,7 +135,7 @@ export async function demo() { Count: Word([1, 0, 0, 0]) ``` -The Rust example prints the raw `Word`; map values use a `[value, 0, 0, 0]` layout, so the count is the first element. The TypeScript example unpacks that word and logs the number directly. +The Rust example prints the raw `Word` — four field elements, which is what every storage map value is. How those elements are used is up to the contract: this counter keeps its count in a single element and leaves the rest at zero, while another contract could use all four. The TypeScript example reads the count element out of the word and logs it as a number. diff --git a/docs/builder/get-started/setup/installation.md b/docs/builder/get-started/setup/installation.md index 3d98c7b0..ea3f7ee7 100644 --- a/docs/builder/get-started/setup/installation.md +++ b/docs/builder/get-started/setup/installation.md @@ -88,9 +88,7 @@ The Miden toolchain installer makes it easy to manage Miden components: cargo install midenup --version 1.0.0-alpha.1 ``` -:::caution -Pass `--version` explicitly. A bare `cargo install midenup` resolves to the latest **stable** release (0.3.2), which cannot read the current channel manifest and fails with `channel manifest v2.0.0 requires a newer version of midenup`. -::: +`1.0.0-alpha.1` is a pre-release, so `cargo install` only picks it up when the version is given explicitly. :::info To install from source instead, name the package explicitly — the repository contains more than one binary: `cargo install --git https://github.com/0xMiden/midenup.git midenup` diff --git a/docs/builder/get-started/your-first-smart-contract/deploy.md b/docs/builder/get-started/your-first-smart-contract/deploy.md index 2966d4fb..d1115f31 100644 --- a/docs/builder/get-started/your-first-smart-contract/deploy.md +++ b/docs/builder/get-started/your-first-smart-contract/deploy.md @@ -91,10 +91,6 @@ Note publish transaction ID: "0xfc190c8edbf972115cd5f00b22c8eac06c9515d0403b84ff Consume transaction ID: "0xa1aa7971e146710df74a674b7e99d1968bec8d2db1c4da6077a7e22843c92045" ``` -:::note -The counter account ID is logged in its structured form. To get the `0x…` string that `AccountId::from_hex()` expects — for example to read the counter back in [Read Storage Values](../read-storage) — print `account.id().to_hex()`. -::: - Congratulations, you have successfully deployed the Counter Contract to the Miden Testnet, and incremented its count by one! You can verify your transaction on [MidenScan](https://testnet.midenscan.com) by searching for your transaction ID. @@ -164,9 +160,7 @@ These packages contain all the information needed to deploy and interact with yo Once we have the compiled packages, we convert them into deployable accounts and notes: ```rust -// Create the counter account with initial component storage. `COUNTER_STORAGE_KEY` -// and `counter_storage_slot()` both come from `integration/src/helpers.rs`, so the -// key and slot name stay in one place instead of being repeated as literals. +// Configure initial storage for the counter account. let counter_storage_slot = counter_storage_slot()?; let mut init_storage_data = InitStorageData::default(); init_storage_data @@ -196,9 +190,9 @@ The `create_account_from_package()` function: **Important**: Accounts that use storage must have that storage seeded when instantiating the account. In the v0.15-aligned SDK, storage slots are identified by name rather than index. The slot name follows the pattern `::::`, derived from the component's manifest namespace. We seed the storage with: - A named `StorageMap` slot, returned by the `counter_storage_slot()` helper (`counter_account::counter_contract::count_map`) -- The counter key `COUNTER_STORAGE_KEY` (`[0, 0, 0, 1]`), mapped to the initial value `0` (representing count = 0) +- The counter key `COUNTER_STORAGE_KEY` (`[0, 0, 0, 1]`), mapped to the initial count `0` -`InitStorageData` carries these seed values into `AccountComponent::from_package()`, which the helper calls for you. +Map values are `Word`s, so `insert_map_entry` widens the `0_u64` count into the word `[0, 0, 0, 0]` before storing it. `InitStorageData` carries these seed values into `AccountComponent::from_package()`, which the helper calls for you. This pre-initialization ensures the account's storage is properly configured before deployment. From 7cb980d0e7ac1dd82cdd714b191f7e4a00d43dcf Mon Sep 17 00:00:00 2001 From: Melnik George Date: Fri, 7 Aug 2026 14:34:57 +0400 Subject: [PATCH 5/8] docs(get-started): simplify install command, drop redundant notes --- docs/builder/get-started/accounts.md | 4 ---- docs/builder/get-started/read-storage.md | 2 -- docs/builder/get-started/setup/installation.md | 4 ++-- docs/builder/get-started/your-first-smart-contract/deploy.md | 2 +- docs/builder/get-started/your-first-smart-contract/test.md | 2 -- 5 files changed, 3 insertions(+), 11 deletions(-) diff --git a/docs/builder/get-started/accounts.md b/docs/builder/get-started/accounts.md index 31beefbe..c9dad704 100644 --- a/docs/builder/get-started/accounts.md +++ b/docs/builder/get-started/accounts.md @@ -127,10 +127,6 @@ Copy the Rust code example into the file, then run: cargo run --bin demo --release ``` -:::note -The first `cargo run` pulls the pinned nightly toolchain and compiles ~400 crates, so it can take a few minutes. Later runs are incremental and much faster. -::: - ### TypeScript Environment If you already created `miden-app` during [installation](./setup/installation#typescript-project), you can reuse it. Otherwise, scaffold a new Vite vanilla-ts project: diff --git a/docs/builder/get-started/read-storage.md b/docs/builder/get-started/read-storage.md index db97cf0e..7ea51158 100644 --- a/docs/builder/get-started/read-storage.md +++ b/docs/builder/get-started/read-storage.md @@ -135,8 +135,6 @@ export async function demo() { Count: Word([1, 0, 0, 0]) ``` -The Rust example prints the raw `Word` — four field elements, which is what every storage map value is. How those elements are used is up to the contract: this counter keeps its count in a single element and leaves the rest at zero, while another contract could use all four. The TypeScript example reads the count element out of the word and logs it as a number. - ## Reading Account Token Balances diff --git a/docs/builder/get-started/setup/installation.md b/docs/builder/get-started/setup/installation.md index ea3f7ee7..a71584d4 100644 --- a/docs/builder/get-started/setup/installation.md +++ b/docs/builder/get-started/setup/installation.md @@ -85,10 +85,10 @@ v22.x.x # or higher The Miden toolchain installer makes it easy to manage Miden components: ```bash title=">_ Terminal" -cargo install midenup --version 1.0.0-alpha.1 +cargo install midenup ``` -`1.0.0-alpha.1` is a pre-release, so `cargo install` only picks it up when the version is given explicitly. +To install a specific release, pass `--version` — this guide is written against `1.0.0-alpha.1`, which as a pre-release is only installed when named explicitly: `cargo install midenup --version 1.0.0-alpha.1`. :::info To install from source instead, name the package explicitly — the repository contains more than one binary: `cargo install --git https://github.com/0xMiden/midenup.git midenup` diff --git a/docs/builder/get-started/your-first-smart-contract/deploy.md b/docs/builder/get-started/your-first-smart-contract/deploy.md index d1115f31..e859219e 100644 --- a/docs/builder/get-started/your-first-smart-contract/deploy.md +++ b/docs/builder/get-started/your-first-smart-contract/deploy.md @@ -192,7 +192,7 @@ The `create_account_from_package()` function: - A named `StorageMap` slot, returned by the `counter_storage_slot()` helper (`counter_account::counter_contract::count_map`) - The counter key `COUNTER_STORAGE_KEY` (`[0, 0, 0, 1]`), mapped to the initial count `0` -Map values are `Word`s, so `insert_map_entry` widens the `0_u64` count into the word `[0, 0, 0, 0]` before storing it. `InitStorageData` carries these seed values into `AccountComponent::from_package()`, which the helper calls for you. +`InitStorageData` carries these seed values into `AccountComponent::from_package()`, which the helper calls for you. This pre-initialization ensures the account's storage is properly configured before deployment. diff --git a/docs/builder/get-started/your-first-smart-contract/test.md b/docs/builder/get-started/your-first-smart-contract/test.md index e1f7de4a..7844f278 100644 --- a/docs/builder/get-started/your-first-smart-contract/test.md +++ b/docs/builder/get-started/your-first-smart-contract/test.md @@ -52,8 +52,6 @@ test counter_test ... ok test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out ``` -The test compiles both contracts before it runs, so the first invocation takes noticeably longer than later ones. - ## Understanding the Mockchain Test Your project includes a comprehensive test file at `integration/tests/counter_test.rs` that demonstrates how to test the counter contract using the Mockchain. Let's walk through this test to understand the testing patterns: From ad6963d0be222056b1abec5b36fd1ca2f12c8e82 Mon Sep 17 00:00:00 2001 From: Melnik George Date: Mon, 10 Aug 2026 14:12:48 +0400 Subject: [PATCH 6/8] docs(read-storage): fold the account ID note into the code comments --- docs/builder/get-started/read-storage.md | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/docs/builder/get-started/read-storage.md b/docs/builder/get-started/read-storage.md index 7ea51158..f115fe0f 100644 --- a/docs/builder/get-started/read-storage.md +++ b/docs/builder/get-started/read-storage.md @@ -32,10 +32,6 @@ To run the code examples in this guide, you'll need to set up a development envi Let's interact with a counter contract deployed on the Miden testnet. This contract maintains a simple counter value in a named storage map slot. -:::note -This example needs the ID of a deployed counter account. [Your First Smart Contract](./your-first-smart-contract/deploy) walks through deploying one — come back here with that account's ID, or use any public counter account whose ID you already know. The deploy script logs the counter account ID in its structured (`Debug`) form; print `account.id().to_hex()` to get the `0x…` string these examples expect. -::: - ### Reading the Count of a Counter contract ```rust title="integration/src/bin/read-count.rs" @@ -80,7 +76,10 @@ async fn main() -> anyhow::Result<()> { // READ PUBLIC STATE OF THE COUNTER ACCOUNT //------------------------------------------------------------ - // Replace this with the ID of the counter account you deployed (see the note above). + // Paste the ID of your deployed counter account here; a wrong or stale ID fails + // with "Account not found". Deploy your own with + // `cargo run --bin increment_count --release` and print `account.id().to_hex()` + // to get the `0x…` string. let counter_account_id = AccountId::from_hex("0x...")?; client.import_account_by_id(counter_account_id).await?; @@ -110,7 +109,10 @@ export async function demo() { // Initialize client to connect with the Miden Testnet. const client = await MidenClient.createTestnet(); - // Replace this with the ID of the counter account you deployed (see the note above). + // Paste the ID of your deployed counter account here; a wrong or stale ID fails + // with "Account not found". Deploy your own with + // `cargo run --bin increment_count --release` and print `account.id().to_hex()` + // to get the `0x…` string. const counterAccountId = "0x..."; // Fetch the counter account (imports it into the local store if needed). From 44ae93cb9af35f2814eba7dca26812f8fc817b59 Mon Sep 17 00:00:00 2001 From: Melnik George Date: Mon, 10 Aug 2026 14:19:23 +0400 Subject: [PATCH 7/8] docs(test): drop the remaining Word layout explanation --- docs/builder/get-started/your-first-smart-contract/test.md | 4 ---- 1 file changed, 4 deletions(-) diff --git a/docs/builder/get-started/your-first-smart-contract/test.md b/docs/builder/get-started/your-first-smart-contract/test.md index 7844f278..2fa8c5fa 100644 --- a/docs/builder/get-started/your-first-smart-contract/test.md +++ b/docs/builder/get-started/your-first-smart-contract/test.md @@ -147,7 +147,6 @@ async fn counter_test() -> anyhow::Result<()> { .get_map_item(&counter_storage_slot, COUNTER_STORAGE_KEY) .expect("Failed to get counter value from storage slot"); - // Map values are returned as scalar words in `[value, 0, 0, 0]` layout. assert_eq!( count[0].as_canonical_u64(), 1, @@ -278,7 +277,6 @@ let count = mock_chain .get_map_item(&counter_storage_slot, COUNTER_STORAGE_KEY) .expect("Failed to get counter value from storage slot"); -// Map values are returned as scalar words in `[value, 0, 0, 0]` layout. assert_eq!( count[0].as_canonical_u64(), 1, @@ -292,8 +290,6 @@ assert_eq!( - We **read the counter value** back from `mock_chain.committed_account()` — the committed state already reflects the transaction, so there is no need to apply the account delta by hand - We **assert that the count equals 1** - verifying the increment operation worked correctly -Note the value layout: map values come back as words shaped `[value, 0, 0, 0]`, so the count lives in the first element, read here with `count[0].as_canonical_u64()`. - The test verifies the complete flow: the increment note successfully increments the counter from 0 to 1, proving our smart contract works as expected. ## Next Steps From 7d65f2f2dbd13398d9c11d98fb8b760e10a3c70f Mon Sep 17 00:00:00 2001 From: Melnik George Date: Thu, 13 Aug 2026 16:36:02 +0400 Subject: [PATCH 8/8] fix(get-started): address review feedback on Rust/TS examples --- docs/builder/get-started/notes.md | 68 ++++-- docs/builder/get-started/read-storage.md | 216 +++++++++++++++--- .../your-first-smart-contract/create.md | 6 +- .../your-first-smart-contract/deploy.md | 4 +- 4 files changed, 239 insertions(+), 55 deletions(-) diff --git a/docs/builder/get-started/notes.md b/docs/builder/get-started/notes.md index 9ff9df26..dd2f34b5 100644 --- a/docs/builder/get-started/notes.md +++ b/docs/builder/get-started/notes.md @@ -258,7 +258,7 @@ export async function demo() { amount: 1000n, type: "public", // note visibility }); - console.log("Mint transaction submitted successfully, ID:", txId.toString()); + console.log("Mint transaction submitted successfully, ID:", txId.toHex()); } ``` @@ -539,7 +539,7 @@ export async function demo() { }); console.log( "Mint transaction submitted successfully, ID:", - mintResult.txId.toString(), + mintResult.txId.toHex(), ); // List notes available to Alice and consume them — tokens move into her vault. @@ -551,11 +551,16 @@ export async function demo() { }); console.log( "Consume transaction submitted successfully, ID:", - consumeResult.txId.toString(), + consumeResult.txId.toHex(), ); - // Read Alice's TEST token balance directly via the accounts resource. - const balance = await client.accounts.getBalance(alice, faucet); + // Fetch Alice again so her vault reflects the consumed note. + const updatedAlice = await client.accounts.get(alice); + if (!updatedAlice) { + throw new Error("Alice's account was not found"); + } + + const balance = updatedAlice.vault().getBalance(faucet.id()); console.log("Alice's TEST token balance:", Number(balance)); } ``` @@ -603,7 +608,7 @@ use miden_client::{ FungibleFaucet, MintPolicyConfig, PolicyRegistration, TokenName, TokenPolicyManager, TransferPolicy, create_fungible_faucet, }, - Account, AccountBuilder, AccountId, AccountType, + Account, AccountBuilder, AccountType, }, asset::{AssetAmount, AssetCallbackFlag, AssetVaultKey, FungibleAsset, TokenSymbol}, auth::AuthSecretKey, @@ -803,9 +808,25 @@ async fn main() -> anyhow::Result<()> { // SENDING TOKENS TO BOB //------------------------------------------------------------ - // Replace this with the ID of the account you want to send to — for example an - // Alice ID printed by an earlier run of this guide. - let bob_account_id = AccountId::from_hex("0x...")?; + // Create Bob's account so this example is self-contained. + let mut bob_seed = [0u8; 32]; + client.rng().fill_bytes(&mut bob_seed); + let bob_key_pair = AuthSecretKey::new_falcon512_poseidon2(); + let bob_account = AccountBuilder::new(bob_seed) + .account_type(AccountType::Public) + .with_auth_component(AuthSingleSig::new( + bob_key_pair.public_key().to_commitment(), + AuthScheme::Falcon512Poseidon2, + )) + .with_component(BasicWallet) + .build()?; + + client.add_account(&bob_account, false).await?; + keystore.add_key(&bob_key_pair, bob_account.id()).await?; + + println!("Bob's account ID: {:?}", bob_account.id().to_hex()); + + let bob_account_id = bob_account.id(); let send_amount = 100; let fungible_asset_to_send = FungibleAsset::new(faucet_account.id(), send_amount)? .with_callbacks(AssetCallbackFlag::Enabled); @@ -874,7 +895,7 @@ export async function demo() { }); console.log( "Mint transaction submitted successfully, ID:", - mintResult.txId.toString(), + mintResult.txId.toHex(), ); const notes = await client.notes.listAvailable({ account: alice }); @@ -885,25 +906,35 @@ export async function demo() { }); console.log( "Consume transaction submitted successfully, ID:", - consumeResult.txId.toString(), + consumeResult.txId.toHex(), ); - const balance = await client.accounts.getBalance(alice, faucet); + // Fetch Alice again so her vault reflects the consumed note. + const updatedAlice = await client.accounts.get(alice); + if (!updatedAlice) { + throw new Error("Alice's account was not found"); + } + + const balance = updatedAlice.vault().getBalance(faucet.id()); console.log("Alice's TEST token balance:", Number(balance)); - // Send 100 tokens from Alice to Bob. Replace this with the ID of the account you - // want to send to — for example an Alice ID printed by an earlier run of this guide. - const bobAccountId = "0x..."; + // Create Bob's account so this example is self-contained. + const bob = await client.accounts.create({ + storage: "public", + }); + console.log("Bob's account ID:", bob.id().toString()); + + // Send 100 tokens from Alice to Bob. console.log("Sending 100 tokens to Bob..."); const { txId } = await client.transactions.send({ account: alice, - to: bobAccountId, + to: bob, token: faucet, amount: 100n, type: "public", waitForConfirmation: true, }); - console.log("Send transaction submitted successfully, ID:", txId.toString()); + console.log("Send transaction submitted successfully, ID:", txId.toHex()); } ``` @@ -916,7 +947,8 @@ Faucet account ID: 0xe48c43d6ad6496201bcfa585a5a4b6 Minting 1000 tokens to Alice... Mint transaction submitted successfully, ID: 0x948a0eef754068b3126dd3261b6b54214fa5608fb13c5e5953faf59bad79c75f Consume transaction submitted successfully, ID: 0xc69ab84b784120abe858bb536aebda90bd2067695f11d5da93ab0b704f39ad78 -Alice's TEST token balance: 100 +Alice's TEST token balance: 1000 +Bob's account ID: 0x103f8a1ad4b983104aec0412ab0b0d Send 100 tokens to Bob note transaction ID: "0x51ac27474ade3a54adadd50db6c2b9a2ede254c5f9137f93d7a970f0bc7d66d5" ``` diff --git a/docs/builder/get-started/read-storage.md b/docs/builder/get-started/read-storage.md index f115fe0f..d15baec8 100644 --- a/docs/builder/get-started/read-storage.md +++ b/docs/builder/get-started/read-storage.md @@ -76,11 +76,9 @@ async fn main() -> anyhow::Result<()> { // READ PUBLIC STATE OF THE COUNTER ACCOUNT //------------------------------------------------------------ - // Paste the ID of your deployed counter account here; a wrong or stale ID fails - // with "Account not found". Deploy your own with - // `cargo run --bin increment_count --release` and print `account.id().to_hex()` - // to get the `0x…` string. - let counter_account_id = AccountId::from_hex("0x...")?; + // A counter contract deployed on the Miden testnet. It is a public fixture and + // may need updating after a new release. + let counter_account_id = AccountId::from_hex("0x6a1b2d59a9ebd3f1534cfb2fcf4d7e")?; client.import_account_by_id(counter_account_id).await?; @@ -109,11 +107,9 @@ export async function demo() { // Initialize client to connect with the Miden Testnet. const client = await MidenClient.createTestnet(); - // Paste the ID of your deployed counter account here; a wrong or stale ID fails - // with "Account not found". Deploy your own with - // `cargo run --bin increment_count --release` and print `account.id().to_hex()` - // to get the `0x…` string. - const counterAccountId = "0x..."; + // A counter contract deployed on the Miden testnet. It is a public fixture and + // may need updating after a new release. + const counterAccountId = "0x6a1b2d59a9ebd3f1534cfb2fcf4d7e"; // Fetch the counter account (imports it into the local store if needed). const counter = await client.accounts.getOrImport(counterAccountId); @@ -125,8 +121,8 @@ export async function demo() { const count = counter.storage().getMapItem(slotName, counterKey); // The count value is a WORD (array of 4 u64 values). - // The 4th value is the counter number. - console.log("Count:", Number(count?.toU64s()[3])); + // The counter number is the first element. + console.log("Count:", Number(count?.toU64s()[0])); } ``` @@ -145,14 +141,27 @@ You can also query the assets (tokens) held by an account: ```rust title="integration/src/bin/token-balance.rs" use miden_client::{ - account::{Account, AccountId}, - asset::{AssetCallbackFlag, AssetVaultKey}, + account::{ + component::{ + AccessControl, AuthScheme, AuthSingleSig, BasicWallet, BurnPolicyConfig, + FungibleFaucet, MintPolicyConfig, PolicyRegistration, TokenName, TokenPolicyManager, + TransferPolicy, create_fungible_faucet, + }, + Account, AccountBuilder, AccountType, + }, + asset::{AssetAmount, AssetCallbackFlag, AssetVaultKey, FungibleAsset, TokenSymbol}, + auth::AuthSecretKey, builder::ClientBuilder, - keystore::FilesystemKeyStore, + keystore::{FilesystemKeyStore, Keystore}, + note::NoteType, rpc::{Endpoint, GrpcClient}, + transaction::TransactionRequestBuilder, }; use miden_client_sqlite_store::ClientBuilderSqliteExt; +use miden_standards::AuthMethod; +use rand::RngCore; use std::sync::Arc; +use tokio::time::Duration; #[tokio::main] async fn main() -> anyhow::Result<()> { @@ -182,17 +191,126 @@ async fn main() -> anyhow::Result<()> { client.sync_state().await?; //------------------------------------------------------------ - // READ TOKEN BALANCE OF AN ACCOUNT + // CREATING A FAUCET, MINTING AND CONSUMING TOKENS //------------------------------------------------------------ - // Replace these with the IDs printed by the mint example in the previous section. - let alice_account_id = AccountId::from_hex("0x...")?; - let faucet_account_id = AccountId::from_hex("0x...")?; + // Account seeds + let mut alice_seed = [0u8; 32]; + client.rng().fill_bytes(&mut alice_seed); + let mut faucet_seed = [0u8; 32]; + client.rng().fill_bytes(&mut faucet_seed); + + // Faucet parameters + let symbol = TokenSymbol::new("TEST")?; + let decimals = 8; + let max_supply = AssetAmount::from(1_000_000u32); + + // Generate key pair + let alice_key_pair = AuthSecretKey::new_falcon512_poseidon2(); + let faucet_key_pair = AuthSecretKey::new_falcon512_poseidon2(); + + // Build the account + let account_builder = AccountBuilder::new(alice_seed) + .account_type(AccountType::Public) + .with_auth_component(AuthSingleSig::new( + alice_key_pair.public_key().to_commitment(), + AuthScheme::Falcon512Poseidon2, + )) + .with_component(BasicWallet); + + // Build the faucet + let faucet = FungibleFaucet::builder() + .name(TokenName::new("Test Token")?) + .symbol(symbol) + .decimals(decimals) + .max_supply(max_supply) + .build()?; + let policies = TokenPolicyManager::new() + .with_mint_policy(MintPolicyConfig::AllowAll, PolicyRegistration::Active)? + .with_burn_policy(BurnPolicyConfig::AllowAll, PolicyRegistration::Active)? + .with_send_policy(TransferPolicy::AllowAll, PolicyRegistration::Active)? + .with_receive_policy(TransferPolicy::AllowAll, PolicyRegistration::Active)?; + + let alice_account = account_builder.build()?; + let faucet_account = create_fungible_faucet( + faucet_seed, + faucet, + AccountType::Public, + AuthMethod::SingleSig { + approver: ( + faucet_key_pair.public_key().to_commitment(), + AuthScheme::Falcon512Poseidon2, + ), + }, + AccessControl::AuthControlled, + policies, + )?; + + println!("Alice's account ID: {:?}", alice_account.id().to_hex()); + println!("Faucet account ID: {:?}", faucet_account.id().to_hex()); + + // Add accounts to client + client.add_account(&alice_account, false).await?; + client.add_account(&faucet_account, false).await?; + + // Add keys to keystore + keystore.add_key(&alice_key_pair, alice_account.id()).await?; + keystore.add_key(&faucet_key_pair, faucet_account.id()).await?; + + let amount: u64 = 1000; + // Enable asset callbacks so the faucet's send/receive transfer policies run + // when this asset moves between accounts. + let fungible_asset = FungibleAsset::new(faucet_account.id(), amount)? + .with_callbacks(AssetCallbackFlag::Enabled); + + // Mint the asset to Alice — this creates a P2ID note she can consume. + let transaction_request = TransactionRequestBuilder::new().build_mint_fungible_asset( + fungible_asset, + alice_account.id(), + NoteType::Public, + client.rng(), + )?; + client + .submit_new_transaction(faucet_account.id(), transaction_request) + .await?; + client.sync_state().await?; + + // Public notes must be committed to a block before they can be consumed. + // Poll until the network includes our mint note in a block. + loop { + client.sync_state().await?; + + let consumable_notes = client + .get_consumable_notes(Some(alice_account.id())) + .await?; + + if consumable_notes.is_empty() { + println!("Waiting for P2ID note to be comitted..."); + tokio::time::sleep(Duration::from_secs(2)).await; + continue; + } - client.import_account_by_id(alice_account_id).await?; + let notes: Vec = consumable_notes + .into_iter() + .map(|(record, _)| record.try_into().expect("Failed to convert to Note")) + .collect(); + let consume_tx_request = TransactionRequestBuilder::new().build_consume_notes(notes)?; + client + .submit_new_transaction(alice_account.id(), consume_tx_request) + .await?; + client.sync_state().await?; + + break; + } + + //------------------------------------------------------------ + // READ TOKEN BALANCE OF AN ACCOUNT + //------------------------------------------------------------ + + // Fetch the account again so the vault reflects the consumed note. let alice_account: Account = client - .get_account(alice_account_id) + .get_account(alice_account.id()) .await? .ok_or_else(|| anyhow::anyhow!("Account not found"))? .try_into()?; @@ -200,7 +318,7 @@ async fn main() -> anyhow::Result<()> { // The callback flag is part of the vault key, so it must match the flag the asset // was minted with — otherwise the lookup misses and the balance reads 0. let balance_key = AssetVaultKey::new_fungible( - faucet_account_id, + faucet_account.id(), AssetCallbackFlag::Enabled, ); let balance = alice_account.vault().get_balance(balance_key)?; @@ -218,15 +336,46 @@ export async function demo() { // Initialize client to connect with the Miden Testnet. const client = await MidenClient.createTestnet(); - // Replace these with the IDs printed by the mint example in the previous section. - const aliceId = "0x..."; - const faucetId = "0x..."; - - // Fetch Alice's account (imports it into the local store if needed) - // and query her balance for the faucet's token. - await client.accounts.getOrImport(aliceId); - const balance = await client.accounts.getBalance(aliceId, faucetId); - + // Create Alice's account and a faucet. + const alice = await client.accounts.create({ + storage: "public", + }); + console.log("Alice's account ID:", alice.id().toString()); + + const decimals = 8; + const maxSupply = 10_000_000n * 10n ** BigInt(decimals); + const faucet = await client.accounts.create({ + type: 0, // Fungible faucet + symbol: "TEST", + decimals, + maxSupply, + storage: "public", + }); + console.log("Faucet account ID:", faucet.id().toString()); + + // Mint 1000 tokens to Alice and consume the resulting P2ID note. + await client.transactions.mint({ + account: faucet, + to: alice, + amount: 1000n, + type: "public", + waitForConfirmation: true, + }); + + const notes = await client.notes.listAvailable({ account: alice }); + await client.transactions.consume({ + account: alice, + notes: [notes[0]], + waitForConfirmation: true, + }); + + // Fetch Alice again so the vault reflects the consumed note. + const updatedAlice = await client.accounts.get(alice); + if (!updatedAlice) { + throw new Error("Alice's account was not found"); + } + + const balance = updatedAlice.vault().getBalance(faucet.id()); console.log("Alice's TEST token balance:", Number(balance)); } ``` @@ -235,7 +384,10 @@ export async function demo() { Expected output ```text -Alice's TEST token balance: 900 +Alice's account ID: "0x5b2840a923dedc102ea67e0c1eba3c" +Faucet account ID: "0x29dd1dc628d2842032e751ed1b5da7" +Waiting for P2ID note to be comitted... +Alice's TEST token balance: AssetAmount(1000) ``` diff --git a/docs/builder/get-started/your-first-smart-contract/create.md b/docs/builder/get-started/your-first-smart-contract/create.md index 82ac2131..9c764570 100644 --- a/docs/builder/get-started/your-first-smart-contract/create.md +++ b/docs/builder/get-started/your-first-smart-contract/create.md @@ -95,11 +95,11 @@ You can build individual contracts by navigating to their directory and running ```bash title=">_ Terminal" # Build the counter account contract cd contracts/counter-account -cargo miden build +miden build # Build the increment note contract cd ../increment-note -cargo miden build +miden build ``` This compiles the Rust contract code into a Miden package (`.masp` file), making it ready for deployment and interaction. @@ -303,7 +303,7 @@ Learn more about [note scripts in the Miden documentation](/reference/protocol/n ```rust #[note_script] -fn run(self, _arg: Word, account: &mut CounterAccount) { +fn run(self, _arg: Word, account: &mut Wallet) { let initial_value = account.get_count(); account.increment_count(); let expected_value = initial_value + Felt::from_u32(1); diff --git a/docs/builder/get-started/your-first-smart-contract/deploy.md b/docs/builder/get-started/your-first-smart-contract/deploy.md index e859219e..68c6c977 100644 --- a/docs/builder/get-started/your-first-smart-contract/deploy.md +++ b/docs/builder/get-started/your-first-smart-contract/deploy.md @@ -111,7 +111,7 @@ This process shows how Miden contracts are deployed through state changes rather ## How the Scripts Work -The integration scripts work by connecting to the Miden client and then building contracts from the Miden package files. These package files are generated when you run `cargo miden build` inside each contract directory, but the scripts handle this compilation step automatically - you don't need to manually build the contracts before running the scripts. +The integration scripts work by connecting to the Miden client and then building contracts from the Miden package files. These package files are generated when you run `miden build` inside each contract directory, but the scripts handle this compilation step automatically - you don't need to manually build the contracts before running the scripts. Next, we look into how the scripts convert your Rust contract code into deployable Miden contracts. @@ -151,7 +151,7 @@ The `build_project_in_dir()` function: - Takes the path to your contract's Rust source code - Compiles the Rust code into a Miden package (`.masp` file) - Generates a package containing the compiled contract bytecode and metadata -- This is equivalent to manually running `cargo miden build` in each contract directory +- This is equivalent to manually running `miden build` in each contract directory These packages contain all the information needed to deploy and interact with your contracts on the Miden network.