diff --git a/docs/builder/get-started/accounts.md b/docs/builder/get-started/accounts.md
index eb12da6a..c9dad704 100644
--- a/docs/builder/get-started/accounts.md
+++ b/docs/builder/get-started/accounts.md
@@ -281,13 +281,13 @@ use miden_client::{
},
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;
diff --git a/docs/builder/get-started/notes.md b/docs/builder/get-started/notes.md
index d61501ad..dd2f34b5 100644
--- a/docs/builder/get-started/notes.md
+++ b/docs/builder/get-started/notes.md
@@ -85,6 +85,7 @@ use miden_client::{
},
AccountBuilder, AccountType,
},
+ asset::{AssetAmount, AssetCallbackFlag, 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;
@@ -193,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)
@@ -255,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());
}
```
@@ -302,6 +305,7 @@ use miden_client::{
},
Account, AccountBuilder, AccountType,
},
+ asset::{AssetAmount, AssetCallbackFlag, AssetVaultKey, FungibleAsset, TokenSymbol},
auth::AuthSecretKey,
builder::ClientBuilder,
keystore::{FilesystemKeyStore, Keystore},
@@ -310,9 +314,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;
@@ -413,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)
@@ -481,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: {:?}",
@@ -533,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.
@@ -545,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));
}
```
@@ -564,7 +575,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))
```
@@ -597,8 +608,9 @@ 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,
builder::ClientBuilder,
keystore::{FilesystemKeyStore, Keystore},
@@ -607,9 +619,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;
@@ -710,7 +719,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)
@@ -778,9 +790,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: {:?}",
@@ -794,9 +808,28 @@ async fn main() -> anyhow::Result<()> {
// SENDING TOKENS TO BOB
//------------------------------------------------------------
- let bob_account_id = AccountId::from_hex("0x103f8a1ad4b983104aec0412ab0b0d")?;
+ // 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)?;
+ let fungible_asset_to_send = FungibleAsset::new(faucet_account.id(), send_amount)?
+ .with_callbacks(AssetCallbackFlag::Enabled);
let p2id_note = P2idNote::create(
alice_account.id(),
@@ -862,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 });
@@ -873,24 +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));
+ // 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.
- const bobAccountId = "0x103f8a1ad4b983104aec0412ab0b0d";
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());
}
```
@@ -903,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 198c9893..d15baec8 100644
--- a/docs/builder/get-started/read-storage.md
+++ b/docs/builder/get-started/read-storage.md
@@ -35,12 +35,12 @@ Let's interact with a counter contract deployed on the Miden testnet. This contr
### 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 +76,9 @@ async fn main() -> anyhow::Result<()> {
// READ PUBLIC STATE OF THE COUNTER ACCOUNT
//------------------------------------------------------------
- let counter_account_id = AccountId::from_hex("0x224a96d294e10d006aef3d4f1b0876")?;
+ // 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?;
@@ -86,14 +88,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(
- "miden::component::miden_counter_account::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,20 +107,22 @@ export async function demo() {
// Initialize client to connect with the Miden Testnet.
const client = await MidenClient.createTestnet();
- const counterAccountId = "0x224a96d294e10d006aef3d4f1b0876";
+ // 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);
// 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);
// 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]));
}
```
@@ -129,7 +130,7 @@ export async function demo() {
Expected output
```text
-Count: 1
+Count: Word([1, 0, 0, 0])
```
@@ -140,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},
+ 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_protocol::asset::{AssetCallbackFlag, AssetVaultKey};
+use miden_standards::AuthMethod;
+use rand::RngCore;
use std::sync::Arc;
+use tokio::time::Duration;
#[tokio::main]
async fn main() -> anyhow::Result<()> {
@@ -177,23 +191,135 @@ async fn main() -> anyhow::Result<()> {
client.sync_state().await?;
//------------------------------------------------------------
- // READ TOKEN BALANCE OF AN ACCOUNT
+ // CREATING A FAUCET, MINTING AND CONSUMING TOKENS
//------------------------------------------------------------
- let alice_account_id = AccountId::from_hex("0x5b2840a923dedc102ea67e0c1eba3c")?;
- let faucet_account_id = AccountId::from_hex("0x29dd1dc628d2842032e751ed1b5da7")?;
+ // 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?;
- client.import_account_by_id(alice_account_id).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;
+ }
+
+ 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()?;
+ // 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,
+ faucet_account.id(),
+ AssetCallbackFlag::Enabled,
);
let balance = alice_account.vault().get_balance(balance_key)?;
@@ -210,14 +336,46 @@ export async function demo() {
// Initialize client to connect with the Miden Testnet.
const client = await MidenClient.createTestnet();
- const aliceId = "0x5b2840a923dedc102ea67e0c1eba3c";
- const faucetId = "0x29dd1dc628d2842032e751ed1b5da7";
-
- // 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));
}
```
@@ -226,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/setup/installation.md b/docs/builder/get-started/setup/installation.md
index 6616afed..a71584d4 100644
--- a/docs/builder/get-started/setup/installation.md
+++ b/docs/builder/get-started/setup/installation.md
@@ -88,8 +88,10 @@ The Miden toolchain installer makes it easy to manage Miden components:
cargo install midenup
```
+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
-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 9d5d2248..9c764570 100644
--- a/docs/builder/get-started/your-first-smart-contract/create.md
+++ b/docs/builder/get-started/your-first-smart-contract/create.md
@@ -54,11 +54,16 @@ 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]
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,13 +75,15 @@ 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 = "*"
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/" }
```
@@ -113,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.
@@ -121,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)]);
@@ -138,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)]);
@@ -193,12 +200,14 @@ 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;
}
```
-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.
@@ -235,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;
@@ -246,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);
@@ -265,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
@@ -281,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/).
@@ -293,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 5ad5f72e..68c6c977 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)
@@ -83,12 +83,12 @@ 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"
```
@@ -160,20 +160,14 @@ 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);
-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();
-let storage_slots = vec![StorageSlot::with_map(
- counter_storage_slot.clone(),
- StorageMap::with_entries([(count_storage_map_key, initial_count)]).unwrap(),
-)];
+// Configure initial storage for the counter account.
+let counter_storage_slot = counter_storage_slot()?;
+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()
};
@@ -193,10 +187,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 `miden::component::::`. 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 (`miden::component::miden_counter_account::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]`), mapped to the initial 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.
@@ -205,14 +201,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()
@@ -221,11 +215,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 660e123f..2fa8c5fa 100644
--- a/docs/builder/get-started/your-first-smart-contract/test.md
+++ b/docs/builder/get-started/your-first-smart-contract/test.md
@@ -47,7 +47,6 @@ 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
@@ -61,19 +60,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, create_testing_account_from_package, create_testing_note_from_package,
- AccountCreationConfig, NoteCreationConfig,
-};
+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, StorageSlotName},
+ 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<()> {
@@ -95,68 +95,63 @@ 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]);
- 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();
- let storage_slots = vec![StorageSlot::with_map(
- counter_storage_slot.clone(),
- StorageMap::with_entries([(StorageMapKey::new(count_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(),
+ // Create the counter account with its initial storage through the component schema.
+ let counter_storage_slot = counter_storage_slot()?;
+ 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, 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
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,56 +199,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 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();
-let storage_slots = vec![StorageSlot::with_map(
- counter_storage_slot.clone(),
- StorageMap::with_entries([(StorageMapKey::new(count_storage_key), initial_count)]).unwrap(),
-)];
-let counter_cfg = AccountCreationConfig {
- storage_slots,
- ..Default::default()
-};
-
-// 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(),
+// Create the counter account with its initial storage through the component schema.
+let counter_storage_slot = counter_storage_slot()?;
+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")?;
```
**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?;
@@ -267,32 +266,28 @@ 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, 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
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
The test verifies the complete flow: the increment note successfully increments the counter from 0 to 1, proving our smart contract works as expected.