From ce20bb239c673c7757456f6e224c59124b3c9e74 Mon Sep 17 00:00:00 2001 From: Wiktor Starczewski Date: Tue, 27 Jan 2026 14:34:40 +0100 Subject: [PATCH 01/18] Add tutorial runner and Playwright harness --- .gitignore | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 82293f3..b09721e 100644 --- a/.gitignore +++ b/.gitignore @@ -10,4 +10,4 @@ web-client/.next/ web-client/playwright-report/ web-client/test-results/ **/.DS_Store -.DS_Store \ No newline at end of file +.DS_Store From 166c5328e6439d6ab2c7e3301deb9dd460dc996b Mon Sep 17 00:00:00 2001 From: Wiktor Starczewski Date: Tue, 27 Jan 2026 21:55:48 +0100 Subject: [PATCH 02/18] Migrate tutorials to Miden SDK 0.13.0-next.4 --- Migrations.Client.md | 904 ++++++++++++++++++++++++++ rust-client/Cargo.toml | 4 + web-client/lib/mintDevnetToAddress.ts | 70 ++ 3 files changed, 978 insertions(+) create mode 100644 Migrations.Client.md create mode 100644 web-client/lib/mintDevnetToAddress.ts diff --git a/Migrations.Client.md b/Migrations.Client.md new file mode 100644 index 0000000..19b48c6 --- /dev/null +++ b/Migrations.Client.md @@ -0,0 +1,904 @@ +# Miden Client Migration Guide + +This document provides migration guidance for breaking changes introduced in new versions of the Miden Client. +Use this guide to migrate from 0.12.x to 0.13.0; it covers 0.13.0 breaking changes only. + +## Version 0.13.0 + +0.13.0 aligns the SDK with protocol 0.13, refactors note and transaction APIs, and tightens web bindings and storage schemas. Expect updates in input note handling, storage slot naming, auth/key APIs, and web client data shapes. + +## Table of Contents + +- [Input notes API unified](#input-notes-api-unified) +- [Account components and storage slots now use named slots](#account-components-and-storage-slots-now-use-named-slots) +- [Authentication and key management updates](#authentication-and-key-management-updates) +- [NodeRpcClient account proof API changed](#noderpcclient-account-proof-api-changed) +- [FetchedNote and RPC note shapes refactored](#fetchednote-and-rpc-note-shapes-refactored) +- [Protocol 0.13 note metadata tags and attachments](#protocol-013-note-metadata-tags-and-attachments) +- [NoteScreener relevance replaced by NoteConsumptionStatus](#notescreener-relevance-replaced-by-noteconsumptionstatus) +- [WebClient IndexedDB naming for multiple instances](#webclient-indexeddb-naming-for-multiple-instances) +- [Block numbers are numeric in web APIs and IndexedDB](#block-numbers-are-numeric-in-web-apis-and-indexeddb) +- [NetworkId custom networks and toBech32Custom removal](#networkid-custom-networks-and-tobech32custom-removal) +- [Client RNG must be Send and Sync](#client-rng-must-be-send-and-sync) +- [CLI swap payback_note_type removed](#cli-swap-payback_note_type-removed) + +### Input notes API unified + +**PR:** #1624 + +#### Summary + +Input notes are no longer split into authenticated and unauthenticated lists. Builders now accept full `Note` objects and the client determines authentication internally, and the WebClient consume request now accepts `Note[]` instead of note ID strings. + +#### Affected Code + +**Rust:** + +```rust +// Before (0.12.x) +use miden_client::auth::TransactionAuthenticator; +use miden_client::note::NoteId; +use miden_client::transaction::TransactionRequestBuilder; +use miden_client::{Client, ClientError}; + +async fn build_request( + client: &Client, + note_id: NoteId, +) -> Result { + let tx_request = TransactionRequestBuilder::new() + .authenticated_input_notes(vec![(note_id, None)]) + .build()?; + Ok(tx_request) +} +``` + +```rust +// After (0.13.0) +use miden_client::auth::TransactionAuthenticator; +use miden_client::note::{Note, NoteId}; +use miden_client::transaction::TransactionRequestBuilder; +use miden_client::{Client, ClientError}; + +async fn build_request( + client: &Client, + note_id: NoteId, +) -> Result { + let record = client.get_input_note(note_id).await?.expect("note not found"); + let note: Note = record.try_into().expect("failed to convert note record"); + + let tx_request = TransactionRequestBuilder::new() + .input_notes(vec![(note, None)]) + .build()?; + Ok(tx_request) +} +``` + +**TypeScript:** + +```typescript +// Before (0.12.x) +import { + NoteIdAndArgs, + NoteIdAndArgsArray, + TransactionRequestBuilder, + WebClient, +} from "@miden-sdk/miden-sdk"; + +const client = await WebClient.createClient(); +const consumeRequest = client.newConsumeTransactionRequest([noteId]); + +const noteIdAndArgs = new NoteIdAndArgs(noteId, null); +const txRequest = new TransactionRequestBuilder() + .withAuthenticatedInputNotes(new NoteIdAndArgsArray([noteIdAndArgs])) + .build(); +``` + +```typescript +// After (0.13.0) +import { + NoteAndArgs, + NoteAndArgsArray, + TransactionRequestBuilder, + WebClient, +} from "@miden-sdk/miden-sdk"; + +const client = await WebClient.createClient(); +const record = await client.getInputNote(noteId); +if (!record) { + throw new Error(`Note with ID ${noteId} not found`); +} +const note = record.toNote(); + +const consumeRequest = client.newConsumeTransactionRequest([note]); + +const noteAndArgs = new NoteAndArgs(note, null); +const txRequest = new TransactionRequestBuilder() + .withInputNotes(new NoteAndArgsArray([noteAndArgs])) + .build(); +``` + +#### Migration Steps + +1. Replace `authenticated_input_notes` and `unauthenticated_input_notes` with `input_notes`. +2. Convert `NoteId` values into `Note` objects (Rust: `InputNoteRecord` -> `Note` via `try_into`; Web: `InputNoteRecord.toNote()`). +3. Update `newConsumeTransactionRequest` to pass `Note[]`, and replace `NoteIdAndArgs` with `NoteAndArgs`. + +#### Common Errors + +| Error Message | Cause | Solution | +| --------------------------------------------- | ----------------------------------------- | ------------------------------------------- | +| `method not found: authenticated_input_notes` | Deprecated builder methods removed | Use `input_notes` with `Note` values | +| `expected Note, found NoteId` | Passing IDs where full notes are required | Fetch the note record and convert to `Note` | +| `newConsumeTransactionRequest expects Note[]` | API now requires notes, not strings | Call `getInputNote(...).toNote()` first | + +### Account components and storage slots now use named slots + +**PRs:** #1626, #1627 + +#### Summary + +Storage slots are now identified by name instead of index, and the web binding for `AccountComponent.compile` now requires a compiled `AccountComponentCode`. The filesystem keystore is also no longer generic over RNG. + +#### Affected Code + +**Rust:** + +```rust +// Before (0.12.x) +use miden_client::account::{StorageMap, StorageSlot}; + +fn storage_slots(storage_map: StorageMap) -> Vec { + vec![StorageSlot::Map(storage_map)] +} +``` + +```rust +// After (0.13.0) +use miden_client::account::{StorageMap, StorageSlot, StorageSlotName}; + +fn storage_slots(storage_map: StorageMap) -> Vec { + let slot_name = StorageSlotName::new("miden::example::map") + .expect("slot name must be valid"); + vec![StorageSlot::with_map(slot_name, storage_map)] +} +``` + +```rust +// Before (0.12.x) +use miden_client::builder::ClientBuilder; +use miden_client::keystore::FilesystemKeyStore; + +let builder = ClientBuilder::>::new(); +``` + +```rust +// After (0.13.0) +use miden_client::builder::ClientBuilder; +use miden_client::keystore::FilesystemKeyStore; + +let builder = ClientBuilder::::new(); +``` + +**TypeScript:** + +```typescript +// Before (0.12.x) +import { + AccountComponent, + StorageMap, + StorageSlot, + WebClient, +} from "@miden-sdk/miden-sdk"; + +const client = await WebClient.createClient(); +const builder = client.createCodeBuilder(); +const storageMap = new StorageMap(); +const slot = StorageSlot.map(storageMap); + +const component = AccountComponent.compile(accountCode, builder, [slot]); +const value = account.storage().getMapItem(1, key); +``` + +```typescript +// After (0.13.0) +import { + AccountComponent, + StorageMap, + StorageSlot, + WebClient, +} from "@miden-sdk/miden-sdk"; + +const client = await WebClient.createClient(); +const builder = client.createCodeBuilder(); +const storageMap = new StorageMap(); +const slotName = "miden::example::map"; +const slot = StorageSlot.map(slotName, storageMap); + +const componentCode = builder.compileAccountComponentCode(accountCode); +const component = AccountComponent.compile(componentCode, [slot]); + +const value = account.storage().getMapItem(slotName, key); +const slotNames = account.storage().getSlotNames(); +``` + +#### Migration Steps + +1. Replace index-based slots with named slots (`StorageSlotName` in Rust, string names in Web). +2. Update account component compilation to use `CodeBuilder.compileAccountComponentCode` and pass the resulting `AccountComponentCode`. +3. If you used `FilesystemKeyStore<_>` generics, drop the RNG parameter. +4. Update any storage accessors (`getItem`, `getMapItem`, `getMapEntries`) to pass slot names. + +#### Common Errors + +| Error Message | Cause | Solution | +| ------------------------------------------------ | ----------------------------------------- | --------------------------------------------- | +| `expected StorageSlotName` | Creating slots without names | Use `StorageSlotName::new("namespace::slot")` | +| `AccountComponent.compile takes 2 arguments` | Old binding passed `CodeBuilder` directly | Compile to `AccountComponentCode` first | +| `type annotations needed for FilesystemKeyStore` | Removed RNG generic | Use `FilesystemKeyStore` without type params | + +### Authentication and key management updates + +**PRs:** #1546, #1578, #1592, #1608 + +#### Summary + +WebClient auth APIs now take the `AuthScheme` enum instead of numeric IDs, `SecretKey` has been removed in favor of `AuthSecretKey`, and `addAccountSecretKeyToWebStore` now requires an account ID. In Rust, `build_wallet_id` no longer accepts a raw scheme ID and instead infers the scheme from `PublicKey`. Scheme-specific public key methods on `AuthSecretKey` were removed; use `getPublicKeyAsWord` instead. + +#### Affected Code + +**Rust:** + +```rust +// Before (0.12.x) +use miden_client::account::{build_wallet_id, AccountStorageMode}; +use miden_client::auth::{PublicKey, RPO_FALCON_SCHEME_ID}; + +fn build_id( + seed: [u8; 32], + public_key: &PublicKey, +) -> Result { + build_wallet_id( + seed, + public_key, + AccountStorageMode::Public, + true, + RPO_FALCON_SCHEME_ID, + ) +} +``` + +```rust +// After (0.13.0) +use miden_client::account::{build_wallet_id, AccountStorageMode}; +use miden_client::auth::PublicKey; + +fn build_id( + seed: [u8; 32], + public_key: &PublicKey, +) -> Result { + build_wallet_id(seed, public_key, AccountStorageMode::Public, true) +} +``` + +**TypeScript:** + +```typescript +// Before (0.12.x) +import { + AccountComponent, + AccountStorageMode, + SecretKey, + WebClient, +} from "@miden-sdk/miden-sdk"; + +const client = await WebClient.createClient(); +const wallet = await client.newWallet( + AccountStorageMode.public(), + true, + 0, + seed, +); + +const secretKey = SecretKey.rpoFalconWithRNG(seed); +const commitment = secretKey.getRpoFalcon512PublicKeyAsWord(); +const authComponent = AccountComponent.createAuthComponentFromCommitment( + commitment, + 0, +); + +await client.addAccountSecretKeyToWebStore(secretKey); +``` + +```typescript +// After (0.13.0) +import { + AccountComponent, + AccountStorageMode, + AuthScheme, + AuthSecretKey, + WebClient, +} from "@miden-sdk/miden-sdk"; + +const client = await WebClient.createClient(); +const wallet = await client.newWallet( + AccountStorageMode.public(), + true, + AuthScheme.AuthRpoFalcon512, + seed, +); + +const secretKey = AuthSecretKey.rpoFalconWithRNG(seed); +const commitment = secretKey.getPublicKeyAsWord(); +const authComponent = AccountComponent.createAuthComponentFromCommitment( + commitment, + AuthScheme.AuthRpoFalcon512, +); +const fromSecret = AccountComponent.createAuthComponentFromSecretKey(secretKey); + +await client.addAccountSecretKeyToWebStore(wallet.id(), secretKey); +const commitments = await client.getPublicKeyCommitmentsOfAccount(wallet.id()); +``` + +#### Migration Steps + +1. In Rust, drop the `auth_scheme_id` argument from `build_wallet_id`; the scheme is inferred from `PublicKey`. +2. In the WebClient, replace numeric auth scheme IDs with `AuthScheme` enum values. +3. Replace `SecretKey` with `AuthSecretKey` and update calls to `createAuthComponentFromSecretKey`. +4. Replace `getRpoFalcon512PublicKeyAsWord` and `getEcdsaK256KeccakPublicKeyAsWord` with `getPublicKeyAsWord`. +5. Pass an account ID to `addAccountSecretKeyToWebStore` and use `getPublicKeyCommitmentsOfAccount` when you need associated commitments. + +#### Common Errors + +| Error Message | Cause | Solution | +| --------------------------------------------------------- | ----------------------------------- | --------------------------------------------------------------------- | +| `this function takes 4 arguments but 5 were supplied` | `build_wallet_id` signature changed | Remove the `auth_scheme_id` argument | +| `SecretKey is not defined` | Model removed | Use `AuthSecretKey` | +| `Argument of type number is not assignable to AuthScheme` | Numeric scheme IDs removed | Use `AuthScheme.AuthRpoFalcon512` or `AuthScheme.AuthEcdsaK256Keccak` | +| `createAuthComponent is not a function` | Method removed | Use `createAuthComponentFromSecretKey` | + +### NodeRpcClient account proof API changed + +**PR:** #1616 + +#### Summary + +The batch `get_account_proofs` API is replaced with a single-account call that requires `AccountStateAt`, and the known code parameter is now optional per account. + +#### Affected Code + +**Rust:** + +```rust +// Before (0.12.x) +use std::collections::{BTreeMap, BTreeSet}; + +use miden_client::account::{AccountCode, AccountId}; +use miden_client::block::BlockNumber; +use miden_client::rpc::domain::account::AccountProof; +use miden_client::rpc::NodeRpcClient; +use miden_client::transaction::ForeignAccount; + +async fn fetch_proofs( + rpc: &dyn NodeRpcClient, + accounts: BTreeSet, + known_codes: BTreeMap, +) -> Result<(BlockNumber, Vec), miden_client::rpc::RpcError> { + rpc.get_account_proofs(&accounts, known_codes).await +} +``` + +```rust +// After (0.13.0) +use miden_client::account::AccountCode; +use miden_client::block::BlockNumber; +use miden_client::rpc::domain::account::AccountProof; +use miden_client::rpc::{AccountStateAt, NodeRpcClient}; +use miden_client::transaction::ForeignAccount; + +async fn fetch_proof( + rpc: &dyn NodeRpcClient, + account: ForeignAccount, + known_code: Option, +) -> Result<(BlockNumber, AccountProof), miden_client::rpc::RpcError> { + rpc.get_account(account, AccountStateAt::ChainTip, known_code).await +} +``` + +#### Migration Steps + +1. Replace `get_account_proofs` with `get_account` and call it per `ForeignAccount`. +2. Pass the desired state via `AccountStateAt::ChainTip` or `AccountStateAt::Block`. +3. Update implementations of `NodeRpcClient` to match the new signature and return type. + +#### Common Errors + +| Error Message | Cause | Solution | +| ------------------------------------------------ | ----------------------------- | ---------------------------------------------------------- | +| `method not found: get_account_proofs` | Old trait method removed | Use `get_account` and loop | +| `missing argument: account_state` | New `AccountStateAt` required | Pass `AccountStateAt::ChainTip` or `AccountStateAt::Block` | +| `expected AccountProof, found Vec` | Return type changed | Handle single proof per call | + +### FetchedNote and RPC note shapes refactored + +**PRs:** #1536, #1606 + +#### Summary + +Fetched notes now carry a `NoteHeader` for private notes and always expose the inclusion proof in the WebClient. Web `FetchedNote` exposes `header`, `note`, and `inclusionProof`, with `asInputNote()` for public notes. + +#### Affected Code + +**Rust:** + +```rust +// Before (0.12.x) +use miden_client::rpc::domain::note::FetchedNote; + +fn handle_note(note: FetchedNote) { + match note { + FetchedNote::Private(note_id, metadata, proof) => { + let _ = (note_id, metadata, proof); + } + FetchedNote::Public(note, proof) => { + let _ = (note, proof); + } + } +} +``` + +```rust +// After (0.13.0) +use miden_client::rpc::domain::note::FetchedNote; + +fn handle_note(note: FetchedNote) { + match note { + FetchedNote::Private(header, proof) => { + let note_id = header.id(); + let metadata = header.metadata(); + let _ = (note_id, metadata, proof); + } + FetchedNote::Public(note, proof) => { + let _ = (note, proof); + } + } +} +``` + +**TypeScript:** + +```typescript +// Before (0.12.x) +const fetched = (await rpcClient.getNotesById([noteId]))[0]; +if (fetched.inputNote) { + const scriptRoot = fetched.inputNote.note().script().root(); +} +``` + +```typescript +// After (0.13.0) +const fetched = (await rpcClient.getNotesById([noteId]))[0]; +const proof = fetched.inclusionProof; +const note = fetched.note; +if (note) { + const scriptRoot = note.script().root(); +} +const inputNote = fetched.asInputNote(); +``` + +#### Migration Steps + +1. Update pattern matches for `FetchedNote::Private` to use `NoteHeader`. +2. In the WebClient, replace `inputNote` access with `note` plus `inclusionProof`, or call `asInputNote()`. +3. Use `header` for shared access to `noteId` and `metadata`. + +#### Common Errors + +| Error Message | Cause | Solution | +| ------------------------------------------------------------------------ | ------------------------------------ | ------------------------------------------------ | +| `pattern has 3 fields, but the corresponding tuple variant has 2 fields` | `FetchedNote::Private` shape changed | Use `FetchedNote::Private(header, proof)` | +| `Property 'inputNote' does not exist on type 'FetchedNote'` | Web shape updated | Use `note`, `inclusionProof`, or `asInputNote()` | + +### Protocol 0.13 note metadata tags and attachments + +**PR:** #1685 + +#### Summary + +Note metadata and tagging APIs were simplified. Account-target tags now use `withAccountTarget`/`with_account_target`, `NoteExecutionMode` was removed, `NoteMetadata` no longer accepts execution hints in the constructor, and `NoteAttachment` now uses `NoteAttachmentScheme` with new accessors. + +#### Affected Code + +**Rust:** + +```rust +// Before (0.12.x) +use miden_client::note::{ + NoteExecutionHint, + NoteMetadata, + NoteTag, + NoteType, +}; +use miden_client::Felt; + +let tag = NoteTag::from_account_id(target_account_id); +let metadata = NoteMetadata::new( + sender_account_id, + NoteType::Private, + tag, + NoteExecutionHint::none(), + Felt::default(), +) +.expect("valid metadata"); +``` + +```rust +// After (0.13.0) +use miden_client::note::{ + NoteAttachment, + NoteAttachmentScheme, + NoteMetadata, + NoteTag, + NoteType, +}; + +let tag = NoteTag::with_account_target(target_account_id); +let metadata = NoteMetadata::new(sender_account_id, NoteType::Private, tag); + +let scheme = NoteAttachmentScheme::new(42); +let attachment = NoteAttachment::new_word(scheme, word); +let metadata_with_attachment = metadata.with_attachment(attachment); +``` + +**TypeScript:** + +```typescript +// Before (0.12.x) +import { + NoteAttachment, + NoteExecutionHint, + NoteExecutionMode, + NoteMetadata, + NoteTag, + NoteType, +} from "@miden-sdk/miden-sdk"; + +const tag = NoteTag.fromAccountId( + targetAccountId, + NoteExecutionMode.newLocal(), +); +const metadata = new NoteMetadata( + senderAccountId, + NoteType.Private, + tag, + NoteExecutionHint.none(), +); +const attachment = NoteAttachment.newWord(42, word); +``` + +```typescript +// After (0.13.0) +import { + NoteAttachment, + NoteAttachmentScheme, + NoteExecutionHint, + NoteMetadata, + NoteTag, + NoteType, +} from "@miden-sdk/miden-sdk"; + +const tag = NoteTag.withAccountTarget(targetAccountId); +const metadata = new NoteMetadata(senderAccountId, NoteType.Private, tag); + +const scheme = new NoteAttachmentScheme(42); +const attachment = NoteAttachment.newWord(scheme, word); +const metadataWithAttachment = metadata.withAttachment(attachment); + +// Optional: target a network account via attachment. +const networkAttachment = NoteAttachment.newNetworkAccountTarget( + targetAccountId, + NoteExecutionHint.none(), +); +``` + +#### Migration Steps + +1. Replace `NoteTag.fromAccountId` with `NoteTag.withAccountTarget`/`withCustomAccountTarget` (Rust: `NoteTag::with_account_target`/`NoteTag::with_custom_account_target`). +2. Drop `NoteExecutionMode` usages; attach execution context via `NoteAttachment` if needed. +3. Update `NoteMetadata` construction to `new NoteMetadata(sender, type, tag)` and add attachments with `withAttachment` (Rust: `NoteMetadata::new(...)` plus `with_attachment`). +4. Wrap attachment scheme values in `NoteAttachmentScheme` and use the new accessors to read payloads (Web: `asWord()`/`asArray()`, Rust: match on `NoteAttachment::content()`). + +#### Common Errors + +| Error Message | Cause | Solution | +| ------------------------------------------------------------------- | -------------------- | ------------------------------------------- | +| `NoteExecutionMode is not defined` | Class removed | Remove it and use attachments if needed | +| `NoteTag.fromAccountId is not a function` | API renamed | Use `NoteTag.withAccountTarget` | +| `Argument of type number is not assignable to NoteAttachmentScheme` | Scheme wrapper added | Construct `new NoteAttachmentScheme(value)` | + +### NoteScreener relevance replaced by NoteConsumptionStatus + +**PR:** #1630 + +#### Summary + +`NoteRelevance` was removed; `NoteScreener` now reports `NoteConsumptionStatus` values, and the WebClient exposes consumption status objects rather than a single `consumableAfterBlock` field. + +#### Affected Code + +**Rust:** + +```rust +// Before (0.12.x) +use miden_client::note::{NoteRelevance, NoteScreener}; + +let relevances = note_screener.check_relevance(¬e).await?; +for (_, relevance) in relevances { + if relevance == NoteRelevance::Now { + // ... + } +} +``` + +```rust +// After (0.13.0) +use miden_client::note::{NoteConsumptionStatus, NoteScreener}; + +let relevances = note_screener.check_relevance(¬e).await?; +for (_, status) in relevances { + match status { + NoteConsumptionStatus::Consumable + | NoteConsumptionStatus::ConsumableWithAuthorization => { + // ... + } + NoteConsumptionStatus::ConsumableAfter(_) => { + // ... + } + _ => {} + } +} +``` + +**TypeScript:** + +```typescript +// Before (0.12.x) +const records = await client.getConsumableNotes(accountId); +const after = records[0].noteConsumability()[0].consumableAfterBlock(); +``` + +```typescript +// After (0.13.0) +const records = await client.getConsumableNotes(accountId); +const status = records[0].noteConsumability()[0].consumptionStatus(); +const after = status.consumableAfterBlock(); +``` + +#### Migration Steps + +1. Replace `NoteRelevance` with `NoteConsumptionStatus` in Rust logic and pattern matching. +2. Update WebClient consumption checks to call `consumptionStatus()` and then `consumableAfterBlock()`. +3. Remove any reliance on `NoteRelevance::Now` / `After` variants. + +#### Common Errors + +| Error Message | Cause | Solution | +| ---------------------------------------- | ------------------------------------- | ------------------------------------------------- | +| `use of undeclared type NoteRelevance` | Type removed | Use `NoteConsumptionStatus` | +| `consumableAfterBlock is not a function` | API moved under `consumptionStatus()` | Call `consumptionStatus().consumableAfterBlock()` | + +### WebClient IndexedDB naming for multiple instances + +**PR:** #1645 + +#### Summary + +The WebClient store is now named, so multiple clients can coexist in the same browser. `WebClient.createClient` and `createClientWithExternalKeystore` accept an optional store name before callback arguments. + +#### Affected Code + +**TypeScript:** + +```typescript +// Before (0.12.x) +const client = await WebClient.createClient(rpcUrl, noteTransportUrl, seed); + +const clientWithKeystore = await WebClient.createClientWithExternalKeystore( + rpcUrl, + noteTransportUrl, + seed, + getKeyCb, + insertKeyCb, + signCb, +); +``` + +```typescript +// After (0.13.0) +const client = await WebClient.createClient( + rpcUrl, + noteTransportUrl, + seed, + "app-db", +); + +const clientWithKeystore = await WebClient.createClientWithExternalKeystore( + rpcUrl, + noteTransportUrl, + seed, + "app-db", + getKeyCb, + insertKeyCb, + signCb, +); +``` + +#### Migration Steps + +1. Add a store name to `createClient` when you need multiple instances in one origin. +2. Shift external keystore callback arguments one position to the right and pass `storeName` first. + +#### Common Errors + +| Error Message | Cause | Solution | +| --------------------------------- | --------------------------------------------------------- | -------------------------------------- | +| `Expected 7 arguments, but got 6` | Missing `storeName` in `createClientWithExternalKeystore` | Insert the store name before callbacks | + +### Block numbers are numeric in web APIs and IndexedDB + +**PRs:** #1528, #1684 + +#### Summary + +WebClient transaction interfaces and IndexedDB storage now use numeric block numbers instead of strings. + +#### Affected Code + +**TypeScript:** + +```typescript +// Before (0.12.x) +const summary = await client.syncState(); +const blockNum = parseInt(summary.blockNum(), 10); +``` + +```typescript +// After (0.13.0) +const summary = await client.syncState(); +const blockNum = summary.blockNum(); +``` + +#### Migration Steps + +1. Remove `parseInt` or `Number(...)` wrappers around `blockNum()` results. +2. If you integrate with `idxdb-store` helpers directly, pass numbers instead of strings. + +#### Common Errors + +| Error Message | Cause | Solution | +| --------------------------------------------------------------------------- | ------------------------- | ----------------------------- | +| `Argument of type 'string' is not assignable to parameter of type 'number'` | Block numbers now numeric | Pass `number` values directly | + +### NetworkId custom networks and toBech32Custom removal + +**PR:** #1612 + +#### Summary + +`NetworkId` is now a class with static constructors and supports custom prefixes. `toBech32Custom` was removed; use `NetworkId.custom(...)` with `toBech32` instead. + +#### Affected Code + +**TypeScript:** + +```typescript +// Before (0.12.x) +const bech32 = accountId.toBech32Custom("cstm", AccountInterface.BasicWallet); +const network = NetworkId.Testnet; +``` + +```typescript +// After (0.13.0) +const network = NetworkId.custom("cstm"); +const bech32 = accountId.toBech32(network, AccountInterface.BasicWallet); +const testnet = NetworkId.testnet(); +``` + +#### Migration Steps + +1. Replace enum-style `NetworkId.Mainnet/Testnet/Devnet` with `NetworkId.mainnet()/testnet()/devnet()`. +2. Replace `toBech32Custom(prefix, ...)` with `toBech32(NetworkId.custom(prefix), ...)`. + +#### Common Errors + +| Error Message | Cause | Solution | +| -------------------------------------------------------------- | ---------------------- | ---------------------------------------- | +| `Property 'Mainnet' does not exist on type 'typeof NetworkId'` | Enum replaced by class | Use `NetworkId.mainnet()` and friends | +| `toBech32Custom is not a function` | Method removed | Use `NetworkId.custom(...)` + `toBech32` | + +### Client RNG must be Send and Sync + +**PR:** #1677 + +#### Summary + +The client RNG must now be `Send + Sync` via the `ClientFeltRng` marker and `ClientRngBox` alias so `Client` can be `Send + Sync`. + +#### Affected Code + +**Rust:** + +```rust +// Before (0.12.x) +use miden_client::builder::ClientBuilder; +use miden_client::crypto::{FeltRng, RpoRandomCoin}; + +let rng: Box = Box::new(RpoRandomCoin::new([0u8; 32])); +let builder = ClientBuilder::new().rng(rng); +``` + +```rust +// After (0.13.0) +use miden_client::builder::ClientBuilder; +use miden_client::crypto::RpoRandomCoin; +use miden_client::ClientRngBox; + +let rng: ClientRngBox = Box::new(RpoRandomCoin::new([0u8; 32])); +let builder = ClientBuilder::new().rng(rng); +``` + +#### Migration Steps + +1. Ensure your RNG implements `Send + Sync`. +2. Wrap the RNG in `ClientRngBox` and pass it to `ClientBuilder::rng`. + +#### Common Errors + +| Error Message | Cause | Solution | +| --------------------------------------------------- | ------------------------ | ----------------------------------------- | +| `the trait bound ...: Send + Sync is not satisfied` | RNG type not thread-safe | Use a `Send + Sync` RNG or wrap it safely | + +### CLI swap payback_note_type removed + +**PR:** #1700 + +#### Summary + +The CLI swap command no longer accepts a `payback_note_type` argument; the payback note type is now fixed. + +#### Affected Code + +**CLI:** + +```bash +# Before (0.12.x) +miden-client swap \ + --offered-asset 10::0x... \ + --requested-asset 5::0x... \ + --note-type public \ + --payback-note-type public +``` + +```bash +# After (0.13.0) +miden-client swap \ + --offered-asset 10::0x... \ + --requested-asset 5::0x... \ + --note-type public +``` + +#### Migration Steps + +1. Remove `--payback-note-type` from swap command invocations or scripts. + +#### Common Errors + +| Error Message | Cause | Solution | +| ------------------------------------------- | ------------ | ------------------------------ | +| `unexpected argument '--payback-note-type'` | Flag removed | Drop the flag from the command | + +## Need Help? + +If you hit issues migrating, reach out here: + +- [Discord Community](https://discord.gg/miden) +- [GitHub Issues](https://github.com/0xMiden/miden-client/issues) diff --git a/rust-client/Cargo.toml b/rust-client/Cargo.toml index 2eb6749..a65b1dc 100644 --- a/rust-client/Cargo.toml +++ b/rust-client/Cargo.toml @@ -12,3 +12,7 @@ serde = { version = "1", features = ["derive"] } serde_json = { version = "1.0", features = ["raw_value"] } tokio = { version = "1.46", features = ["rt-multi-thread", "net", "macros", "fs"] } rand_chacha = "0.9.0" + +[patch.crates-io] +miden-client = { path = "../../miden-client/crates/rust-client" } +miden-client-sqlite-store = { path = "../../miden-client/crates/sqlite-store" } diff --git a/web-client/lib/mintDevnetToAddress.ts b/web-client/lib/mintDevnetToAddress.ts new file mode 100644 index 0000000..060ee1c --- /dev/null +++ b/web-client/lib/mintDevnetToAddress.ts @@ -0,0 +1,70 @@ +/** + * Mint 100 MIDEN tokens on devnet to a fixed recipient using a local prover. + */ +export async function mintDevnetToAddress(): Promise { + if (typeof window === 'undefined') { + console.warn('Run in browser'); + return; + } + + const { + WebClient, + AccountStorageMode, + AuthScheme, + Address, + NoteType, + TransactionProver, + } = await import('@miden-sdk/miden-sdk'); + + const client = await WebClient.createClient('https://rpc.devnet.miden.io'); + const prover = TransactionProver.newLocalProver(); + + console.log('Latest block:', (await client.syncState()).blockNum()); + + // ── Create a faucet ──────────────────────────────────────────────────────── + console.log('Creating faucet...'); + const faucet = await client.newFaucet( + AccountStorageMode.public(), + false, + 'MID', + 8, + BigInt(1_000_000), + AuthScheme.AuthRpoFalcon512, + ); + console.log('Faucet ID:', faucet.id().toString()); + await client.syncState(); + + // ── Mint to recipient ─────────────────────────────────────────────────────── + const recipientAddress = + 'mdev1arey468fhgnhvyzdfk3suqavhccmp6cu_qruqqypuyph'; + const recipientAccountId = Address.fromBech32(recipientAddress).accountId(); + console.log('Recipient account ID:', recipientAccountId.toString()); + + console.log('Minting 100 MIDEN tokens...'); + const txResult = await client.executeTransaction( + faucet.id(), + client.newMintTransactionRequest( + recipientAccountId, + faucet.id(), + NoteType.Public, + BigInt(100), + ), + ); + const proven = await client.proveTransaction(txResult, prover); + const submissionHeight = await client.submitProvenTransaction( + proven, + txResult, + ); + const txExecutionResult = await client.applyTransaction( + txResult, + submissionHeight, + ); + + console.log('Waiting for settlement...'); + await new Promise((resolve) => setTimeout(resolve, 7_000)); + await client.syncState(); + + const txId = txExecutionResult.executedTransaction().id().toHex().toString(); + console.log('Mint tx id:', txId); + console.log('Mint complete.'); +} From 39f132ef89205161788e1122213f002bbf321e5b Mon Sep 17 00:00:00 2001 From: Wiktor Starczewski Date: Thu, 29 Jan 2026 15:06:21 +0100 Subject: [PATCH 03/18] fix(ci): use crates.io miden client --- rust-client/Cargo.toml | 4 ---- 1 file changed, 4 deletions(-) diff --git a/rust-client/Cargo.toml b/rust-client/Cargo.toml index a65b1dc..2eb6749 100644 --- a/rust-client/Cargo.toml +++ b/rust-client/Cargo.toml @@ -12,7 +12,3 @@ serde = { version = "1", features = ["derive"] } serde_json = { version = "1.0", features = ["raw_value"] } tokio = { version = "1.46", features = ["rt-multi-thread", "net", "macros", "fs"] } rand_chacha = "0.9.0" - -[patch.crates-io] -miden-client = { path = "../../miden-client/crates/rust-client" } -miden-client-sqlite-store = { path = "../../miden-client/crates/sqlite-store" } From ce607e0344d44556c4c5d7cb8b444eab7c3045a6 Mon Sep 17 00:00:00 2001 From: Wiktor Starczewski Date: Fri, 6 Feb 2026 14:36:45 +0400 Subject: [PATCH 04/18] docs: add Building a React Wallet tutorial --- docs/src/web-client/react_wallet_tutorial.md | 885 +++++++++++++++++++ 1 file changed, 885 insertions(+) create mode 100644 docs/src/web-client/react_wallet_tutorial.md diff --git a/docs/src/web-client/react_wallet_tutorial.md b/docs/src/web-client/react_wallet_tutorial.md new file mode 100644 index 0000000..4f4c2b2 --- /dev/null +++ b/docs/src/web-client/react_wallet_tutorial.md @@ -0,0 +1,885 @@ +--- +title: 'Building a React Wallet' +sidebar_position: 8 +--- + +# Building a React Wallet + +_Using the Miden React SDK to build a complete wallet UI with account management, token transfers, and note claiming_ + +## Overview + +In this tutorial we will build a complete wallet application using the `@miden-sdk/react` package. The Miden React SDK provides a set of hooks and utilities that make it easy to integrate Miden functionality into React applications. + +By the end of this tutorial, you will have a working wallet that can: + +- Create new accounts +- Display account balances +- List and claim unclaimed notes +- Send tokens to other accounts + +## What we'll cover + +- Setting up a React project with the Miden React SDK +- Using the `MidenProvider` to configure the client +- Managing accounts with `useAccounts`, `useAccount`, and `useCreateWallet` +- Displaying and claiming notes with `useNotes` and `useConsume` +- Sending tokens with `useSend` +- Formatting utilities for assets and notes +- External signer integration patterns + +## Prerequisites + +- Node `v20` or greater +- Familiarity with React and TypeScript +- `yarn` + +--- + +## Step 1: Project Setup and MidenProvider + +First, create a new Vite + React project and install the Miden React SDK. + +1. Create a new Vite project with React and TypeScript: + + ```bash + yarn create vite miden-wallet --template react-ts + cd miden-wallet + ``` + +2. Install the Miden React SDK: + + ```bash + yarn add @miden-sdk/react + ``` + +3. Configure the `MidenProvider` in your `main.tsx` file. The provider initializes the Miden client and makes it available to all child components: + +```tsx +// main.tsx +import React from "react"; +import ReactDOM from "react-dom/client"; +import { MidenProvider } from "@miden-sdk/react"; +import App from "./App"; + +ReactDOM.createRoot(document.getElementById("root")!).render( + + + + + +); +``` + +The `MidenProvider` accepts a `config` object with the following options: + +- `rpcUrl`: The RPC endpoint to connect to (`"devnet"`, `"testnet"`, or a custom URL) +- `prover`: The prover to use (`"devnet"` for delegated proving, or `"local"` for local proving) + +--- + +## Step 2: App Shell with useMiden + +The `useMiden()` hook provides access to the client's initialization state. Use it to show loading and error states while the client initializes. + +```tsx +// App.tsx +import { useMiden } from "@miden-sdk/react"; + +export default function App() { + const { isReady, error } = useMiden(); + + if (error) return
Error: {error.message}
; + if (!isReady) return
Initializing...
; + + return
Wallet ready!
; +} +``` + +The `useMiden()` hook returns: + +- `isReady`: `true` when the client has finished initializing +- `error`: An error object if initialization failed + +--- + +## Step 3: Listing Accounts with useAccounts + +The `useAccounts()` hook provides access to all accounts stored in the client. Use it to check if the user has any existing wallets. + +```tsx +import { useMiden, useAccounts } from "@miden-sdk/react"; + +export default function App() { + const { isReady, error } = useMiden(); + const { wallets, isLoading } = useAccounts(); + + if (error) return
Error: {error.message}
; + if (!isReady || isLoading) return
Loading...
; + + const accountId = wallets[0]?.id().toString(); + + if (!accountId) { + return
No wallet found. Create one!
; + } + + return
Account: {accountId}
; +} +``` + +The `useAccounts()` hook returns: + +- `wallets`: Array of wallet accounts +- `faucets`: Array of faucet accounts +- `isLoading`: `true` while accounts are being fetched + +--- + +## Step 4: Creating a Wallet with useCreateWallet + +The `useCreateWallet()` hook provides a function to create new wallet accounts. + +```tsx +import { useMiden, useAccounts, useCreateWallet } from "@miden-sdk/react"; + +export default function App() { + const { isReady, error } = useMiden(); + const { wallets, isLoading } = useAccounts(); + const { createWallet, isCreating } = useCreateWallet(); + + if (error) return
Error: {error.message}
; + if (!isReady || isLoading) return
Loading...
; + + const accountId = wallets[0]?.id().toString(); + + if (!accountId) { + return ( +
+

Wallet

+ +
+ ); + } + + return ; +} + +function Wallet({ accountId }: { accountId: string }) { + return
Wallet: {accountId}
; +} +``` + +The `useCreateWallet()` hook returns: + +- `createWallet(options?)`: Function to create a new wallet +- `isCreating`: `true` while a wallet is being created + +--- + +## Step 5: Displaying Account Details with useAccount + +The `useAccount(accountId)` hook provides detailed information about a specific account, including its assets and balances. + +```tsx +import { useAccount, formatAssetAmount } from "@miden-sdk/react"; + +function Wallet({ accountId }: { accountId: string }) { + const { account, assets } = useAccount(accountId); + + return ( +
+

Wallet

+ +
+

Address

+
{account?.bech32id?.() ?? "Loading..."}
+
+ +
+

Balances

+ {assets.length === 0 ? ( +
No assets
+ ) : ( +
    + {assets.map((asset) => ( +
  • + {asset.symbol ?? asset.assetId} + {formatAssetAmount(asset.amount, asset.decimals)} +
  • + ))} +
+ )} +
+
+ ); +} +``` + +The `useAccount(accountId)` hook returns: + +- `account`: The account object with methods like `bech32id()` +- `assets`: Array of asset objects with `assetId`, `symbol`, `amount`, and `decimals` +- `isLoading`: `true` while account data is being fetched + +The `formatAssetAmount(amount, decimals)` utility formats a raw amount with the correct decimal places. + +--- + +## Step 6: Listing Unclaimed Notes with useNotes + +The `useNotes({ accountId })` hook provides access to notes that can be consumed by the account. + +```tsx +import { useNotes, formatNoteSummary } from "@miden-sdk/react"; + +function UnclaimedNotes({ accountId }: { accountId: string }) { + const { consumableNoteSummaries } = useNotes({ accountId }); + + return ( +
+

Unclaimed Notes

+ {consumableNoteSummaries.length === 0 ? ( +
No unclaimed notes
+ ) : ( +
    + {consumableNoteSummaries.map((summary) => ( +
  • {formatNoteSummary(summary)}
  • + ))} +
+ )} +
+ ); +} +``` + +The `useNotes({ accountId })` hook returns: + +- `consumableNoteSummaries`: Array of note summaries that can be consumed +- `isLoading`: `true` while notes are being fetched + +The `formatNoteSummary(summary)` utility formats a note summary for display. + +--- + +## Step 7: Claiming Notes with useConsume + +The `useConsume()` hook provides a function to consume (claim) notes and add their assets to the account. + +```tsx +import { useConsume, formatNoteSummary } from "@miden-sdk/react"; + +function UnclaimedNotes({ accountId, consumableNoteSummaries }: { + accountId: string; + consumableNoteSummaries: Array<{ id: string }>; +}) { + const { consume, isLoading: isConsuming } = useConsume(); + + const claimNote = (id: string) => () => { + consume({ accountId, noteIds: [id] }); + }; + + return ( +
+

Unclaimed Notes

+ {consumableNoteSummaries.length === 0 ? ( +
No unclaimed notes
+ ) : ( +
    + {consumableNoteSummaries.map((summary) => ( +
  • + {formatNoteSummary(summary)} + +
  • + ))} +
+ )} +
+ ); +} +``` + +The `useConsume()` hook returns: + +- `consume({ accountId, noteIds })`: Function to consume one or more notes +- `isLoading`: `true` while notes are being consumed + +--- + +## Step 8: Sending Tokens with useSend + +The `useSend()` hook provides a function to send tokens to other accounts. + +```tsx +import { useState, type ChangeEvent } from "react"; +import { useSend, parseAssetAmount } from "@miden-sdk/react"; + +function SendForm({ accountId, assets }: { + accountId: string; + assets: Array<{ assetId: string; symbol?: string; decimals?: number }>; +}) { + const { send, isLoading: isSending } = useSend(); + const [to, setTo] = useState(""); + const [assetId, setAssetId] = useState(assets[0]?.assetId ?? ""); + const [amount, setAmount] = useState(""); + const [noteType, setNoteType] = useState<"private" | "public">("private"); + + const selectedAsset = assets.find((asset) => asset.assetId === assetId); + const selectedDecimals = selectedAsset?.decimals; + const hasAssets = assets.length > 0; + const canSend = Boolean(hasAssets && to && assetId && amount); + + const handleSend = async () => { + try { + if (!assetId) return; + const amt = parseAssetAmount(amount, selectedDecimals); + await send({ from: accountId, to, assetId, amount: amt, noteType }); + setAmount(""); + } catch (error) { + console.error(error); + } + }; + + const onAssetChange = (e: ChangeEvent) => setAssetId(e.target.value); + const onNoteTypeChange = (e: ChangeEvent) => setNoteType(e.target.value as "private" | "public"); + const onToChange = (e: ChangeEvent) => setTo(e.target.value); + const onAmountChange = (e: ChangeEvent) => setAmount(e.target.value); + + return ( +
+

Send

+ + + + + +
+ ); +} +``` + +The `useSend()` hook returns: + +- `send({ from, to, assetId, amount, noteType })`: Function to send tokens +- `isLoading`: `true` while the transaction is being processed + +Parameters: + +- `from`: The sender's account ID +- `to`: The recipient's address (bech32 format) +- `assetId`: The asset/faucet ID to send +- `amount`: The amount to send (as a BigInt) +- `noteType`: Either `"private"` or `"public"` + +The `parseAssetAmount(amount, decimals)` utility converts a string amount to a BigInt with the correct decimal places. + +--- + +## Summary: Complete Code + +Here is the complete wallet application combining all the features we've covered. + +**main.tsx** + +```tsx +import React from "react"; +import ReactDOM from "react-dom/client"; +import { MidenProvider } from "@miden-sdk/react"; +import App from "./App"; + +ReactDOM.createRoot(document.getElementById("root")!).render( + + + + + +); +``` + +**App.tsx** + +```tsx +import { useEffect, useState, type ChangeEvent, type ReactNode } from "react"; +import { formatAssetAmount, formatNoteSummary, parseAssetAmount } from "@miden-sdk/react"; +import { useMiden, useAccounts, useAccount, useNotes, useCreateWallet, useConsume, useSend } from "@miden-sdk/react"; + +const Panel = ({ title, children }: { title: string; children: ReactNode }) => ( +
+
{title}
+ {children} +
+); + +export default function App() { + const { isReady, error } = useMiden(); + const { wallets, isLoading } = useAccounts(); + const { createWallet, isCreating } = useCreateWallet(); + const handleCreate = () => createWallet(); + const createLabel = isCreating ? "Creating..." : "Create wallet"; + + if (error) return
Error: {error.message}
; + if (!isReady || isLoading) return
{!isReady ? "Initializing..." : "Loading..."}
; + + const accountId = wallets[0]?.id().toString(); + if (!accountId) + return ( +
+

Wallet

+ +
+ ); + + return ; +} + +function Wallet({ accountId }: { accountId: string }) { + const { account, assets } = useAccount(accountId); + const { consumableNoteSummaries } = useNotes({ accountId }); + const { consume, isLoading: isConsuming } = useConsume(); + const { send, isLoading: isSending } = useSend(); + const [to, setTo] = useState(""); + const [assetId, setAssetId] = useState(""); + const [amount, setAmount] = useState(""); + const [noteType, setNoteType] = useState<"private" | "public">("private"); + const defaultAssetId = assets[0]?.assetId; + const selectedAsset = assets.find((asset) => asset.assetId === assetId); + const selectedDecimals = selectedAsset?.decimals; + const hasAssets = assets.length > 0; + + useEffect(() => { + if (!assetId && defaultAssetId) setAssetId(defaultAssetId); + }, [assetId, defaultAssetId]); + + const handleSend = async () => { + try { + if (!assetId) return; + const amt = parseAssetAmount(amount, selectedDecimals); + await send({ from: accountId, to, assetId, amount: amt, noteType }); + setAmount(""); + } catch (error) { + console.error(error); + } + }; + + const claimNote = (id: string) => () => consume({ accountId, noteIds: [id] }); + const onAssetChange = (event: ChangeEvent) => setAssetId(event.target.value); + const onNoteTypeChange = (event: ChangeEvent) => setNoteType(event.target.value as "private" | "public"); + const onToChange = (event: ChangeEvent) => setTo(event.target.value); + const onAmountChange = (event: ChangeEvent) => setAmount(event.target.value); + const canSend = Boolean(hasAssets && to && assetId && amount); + const sendLabel = isSending ? "Sending..." : "Send"; + + return ( +
+

Wallet

+ +
{account?.bech32id?.() ?? "Loading..."}
+
+ + {assets.length === 0 ? ( +
None
+ ) : ( +
+ {assets.map((asset) => ( +
+ {asset.symbol ?? asset.assetId} + {formatAssetAmount(asset.amount, asset.decimals)} +
+ ))} +
+ )} +
+ + {consumableNoteSummaries.length === 0 ? ( +
None
+ ) : ( +
+ {consumableNoteSummaries.map((summary) => { + const id = summary.id; + const label = formatNoteSummary(summary); + return ( +
+ {label} + +
+ ); + })} +
+ )} +
+ +
+ + + + + +
+
+
+ ); +} +``` + +--- + +## Running the Example + +To run a full working example, navigate to the `packages/react-sdk/examples/wallet` directory in the [miden-client](https://github.com/0xMiden/miden-client/) repository: + +```bash +git clone https://github.com/0xMiden/miden-client.git +cd miden-client/packages/react-sdk/examples/wallet +yarn install +yarn dev +``` + +### Resetting the MidenClientDB + +The Miden client stores account and note data in the browser's IndexedDB. To clear this data, paste the following into your browser console: + +```javascript +(async () => { + const dbs = await indexedDB.databases(); + for (const db of dbs) { + await indexedDB.deleteDatabase(db.name); + console.log(`Deleted database: ${db.name}`); + } + console.log('All databases deleted.'); +})(); +``` + +--- + +## External Signer Integration + +By default, the Miden React SDK manages keys internally using the browser's IndexedDB. However, for production applications you may want to integrate with external signers that provide enhanced security, key management, or authentication features. + +### The useSigner Hook + +The `useSigner()` hook from `@miden-sdk/react` provides a unified interface for interacting with any signer provider. When you wrap your app with a signer provider (Para, Turnkey, MidenFi, etc.), the hook returns the signer context with connection state and methods. + +```tsx +import { useSigner } from "@miden-sdk/react"; + +function ConnectButton() { + const signer = useSigner(); + + // Returns null if no signer provider is present (local keystore mode) + if (!signer) return null; + + const { isConnected, connect, disconnect, name } = signer; + + return isConnected ? ( + + ) : ( + + ); +} +``` + +The `useSigner()` hook returns: + +- `isConnected`: Whether the signer is connected and ready +- `connect()`: Triggers the authentication flow +- `disconnect()`: Disconnects from the signer +- `name`: Display name of the signer (e.g., "Para", "Turnkey", "MidenFi") + +This unified interface means your wallet UI code works the same regardless of which signer provider is used. + +--- + +### Para: EVM Wallet Integration + +[Para](https://para.space/) provides a modal-based authentication flow that allows users to sign in with their EVM wallets (MetaMask, WalletConnect, etc.). + +**Installation:** + +```bash +yarn add use-miden-para-react +``` + +**Usage:** + +```tsx +import { ParaSignerProvider } from "use-miden-para-react"; +import { MidenProvider, useSigner } from "@miden-sdk/react"; + +function App() { + return ( + + + + + + ); +} + +function Wallet() { + const signer = useSigner(); + + return ( +
+ {signer?.isConnected ? ( + + ) : ( + + )} +
+ ); +} +``` + +**ParaSignerProvider Props:** + +| Prop | Type | Description | +|------|------|-------------| +| `apiKey` | `string` | Your Para API key | +| `environment` | `"PRODUCTION" \| "DEVELOPMENT" \| "SANDBOX"` | Para environment | +| `showSigningModal` | `boolean` | Whether to show signing confirmation modal | +| `customSignConfirmStep` | `ReactNode` | Custom signing confirmation UI | + +--- + +### Turnkey: App-Controlled Authentication + +[Turnkey](https://turnkey.com/) provides programmatic key management, giving your application full control over the authentication flow. + +**Installation:** + +```bash +yarn add use-miden-turnkey-react @turnkey/sdk-browser +``` + +**Usage:** + +```tsx +import { TurnkeySignerProvider, useTurnkeySigner } from "use-miden-turnkey-react"; +import { MidenProvider, useSigner } from "@miden-sdk/react"; + +const turnkeyConfig = { + apiBaseUrl: "https://api.turnkey.com", + organizationId: "your-org-id", + // Additional Turnkey SDK configuration (stamper, etc.) +}; + +function App() { + return ( + + + + + + ); +} + +function Wallet() { + const signer = useSigner(); + // Use useTurnkeySigner() only when you need Turnkey-specific features + const { setAccount } = useTurnkeySigner(); + + const handleLogin = async () => { + // Your custom authentication logic (passkey, email, etc.) + const walletAccount = await authenticateUser(); + // Register the account with Turnkey signer + setAccount(walletAccount); + }; + + return ( +
+ {signer?.isConnected ? ( + + ) : ( + + )} +
+ ); +} +``` + +**TurnkeySignerProvider Props:** + +| Prop | Type | Description | +|------|------|-------------| +| `config` | `TurnkeySDKClientConfig` | Turnkey SDK client configuration (apiBaseUrl, organizationId, stamper, etc.) | + +The `useTurnkeySigner()` hook provides Turnkey-specific extras like `setAccount()` and access to the `client` instance. + +--- + +### MidenFi: Wallet Adapter + +[MidenFi](https://miden.fi/) provides a wallet adapter pattern similar to Solana's wallet-adapter, enabling integration with the MidenFi ecosystem. + +**Installation:** + +```bash +yarn add @miden-wallet-adapter/react +``` + +**Usage:** + +```tsx +import { MidenFiSignerProvider } from "@miden-wallet-adapter/react"; +import { MidenProvider, useSigner } from "@miden-sdk/react"; + +function App() { + return ( + + + + + + ); +} + +function Wallet() { + const signer = useSigner(); + + return ( +
+ {signer?.isConnected ? ( + + ) : ( + + )} +
+ ); +} +``` + +**MidenFiSignerProvider Props:** + +| Prop | Type | Description | +|------|------|-------------| +| `network` | `"Testnet" \| "Mainnet"` | Target network | +| `privateDataPermission` | `boolean` | Whether to request private data access | +| `allowedPrivateData` | `string[]` | List of allowed private data types | + +--- + +### Building a Custom Signer Provider + +If you need to integrate with a different signing service, you can build your own signer provider by implementing the `SignerContextValue` interface and providing it via `SignerContext.Provider`. + +```tsx +import { useState, useCallback, type ReactNode } from "react"; +import { SignerContext, type SignerContextValue } from "@miden-sdk/react"; +import { AccountStorageMode } from "@miden-sdk/miden-sdk"; + +interface CustomSignerProviderProps { + children: ReactNode; + // Your provider-specific config +} + +export function CustomSignerProvider({ children }: CustomSignerProviderProps) { + const [isConnected, setIsConnected] = useState(false); + const [signerContext, setSignerContext] = useState(null); + + const connect = useCallback(async () => { + // 1. Initialize your signing service and get credentials + const { publicKeyCommitment, signMessage } = await initializeYourSigningService(); + + // 2. Build the signer context + const context: SignerContextValue = { + signCb: async (pubKey, signingInputs) => { + // Sign the message using your service + return signMessage(signingInputs); + }, + accountConfig: { + publicKeyCommitment, + accountType: "RegularAccountImmutableCode", + storageMode: AccountStorageMode.public(), + }, + storeName: "custom_signer", + name: "CustomSigner", + isConnected: true, + connect, + disconnect, + }; + + setSignerContext(context); + setIsConnected(true); + }, []); + + const disconnect = useCallback(async () => { + setSignerContext(null); + setIsConnected(false); + }, []); + + return ( + + {children} + + ); +} +``` + +The `SignerContextValue` interface requires: + +| Field | Type | Description | +|-------|------|-------------| +| `signCb` | `(pubKey, signingInputs) => Promise` | Signs transaction inputs and returns the signature | +| `accountConfig` | `SignerAccountConfig` | Public key commitment, account type, and storage mode | +| `storeName` | `string` | Unique suffix for IndexedDB isolation (e.g., "custom_walletId") | +| `name` | `string` | Display name for UI (e.g., "CustomSigner") | +| `isConnected` | `boolean` | Whether the signer is connected and ready | +| `connect` | `() => Promise` | Triggers the authentication flow | +| `disconnect` | `() => Promise` | Disconnects from the signer | + +--- + +## Continue Learning + +Now that you've built a React wallet, explore these related topics: + +- [Creating Multiple Notes in a Single Transaction](./creating_multiple_notes_tutorial.md) - Learn about batch operations +- [Miden React SDK Reference](https://github.com/0xMiden/miden-client/tree/main/packages/react-sdk) - Full API documentation +- [Miden Documentation](https://docs.miden.io/) - Core Miden concepts From 7eb819747f753e9c8e259c1822eb6378b28a430d Mon Sep 17 00:00:00 2001 From: Wiktor Starczewski Date: Fri, 6 Feb 2026 14:38:56 +0400 Subject: [PATCH 05/18] docs: add React SDK examples to existing tutorials --- docs/src/components/CodeSdkTabs.module.css | 82 ++++++ docs/src/components/CodeSdkTabs.tsx | 113 ++++++++ docs/src/components/index.ts | 1 + docs/src/web-client/create_deploy_tutorial.md | 180 ++++++++++--- .../creating_multiple_notes_tutorial.md | 245 +++++++++++++++--- .../mint_consume_create_tutorial.md | 198 +++++++++++--- .../web-client/unauthenticated_note_how_to.md | 135 +++++++++- web-client/lib/react/createMintConsume.tsx | 84 ++++++ .../react/multiSendWithDelegatedProver.tsx | 78 ++++++ .../lib/react/unauthenticatedNoteTransfer.tsx | 90 +++++++ web-client/package.json | 1 + 11 files changed, 1082 insertions(+), 125 deletions(-) create mode 100644 docs/src/components/CodeSdkTabs.module.css create mode 100644 docs/src/components/CodeSdkTabs.tsx create mode 100644 docs/src/components/index.ts create mode 100644 web-client/lib/react/createMintConsume.tsx create mode 100644 web-client/lib/react/multiSendWithDelegatedProver.tsx create mode 100644 web-client/lib/react/unauthenticatedNoteTransfer.tsx diff --git a/docs/src/components/CodeSdkTabs.module.css b/docs/src/components/CodeSdkTabs.module.css new file mode 100644 index 0000000..391d6d3 --- /dev/null +++ b/docs/src/components/CodeSdkTabs.module.css @@ -0,0 +1,82 @@ +/* CodeSdkTabs Component Styles */ + +.codeContainer { + margin: 1rem 0; + border: 1px solid var(--ifm-color-emphasis-300); + border-radius: var(--ifm-global-radius); + overflow: hidden; + background: var(--ifm-background-color); +} + +.tabContainer { + background: var(--ifm-color-emphasis-100); + border-bottom: 1px solid var(--ifm-color-emphasis-300); +} + +.tabButtons { + display: flex; + gap: 0; +} + +.tabButton { + display: flex; + align-items: center; + gap: 0.5rem; + padding: 0.75rem 1rem; + border: none; + background: transparent; + color: var(--ifm-color-content-secondary); + cursor: pointer; + font-size: 0.875rem; + font-weight: 500; + transition: all 0.2s ease; + border-bottom: 2px solid transparent; +} + +.tabButton:hover { + color: var(--ifm-color-primary); + background: var(--ifm-color-emphasis-200); +} + +.tabButton.active { + color: var(--ifm-color-primary); + background: var(--ifm-color-emphasis-200); + border-bottom: 2px solid var(--ifm-color-primary); +} + +.codeSection { + position: relative; + margin: 0; +} + +.codeSection pre { + margin: 0; + border-radius: 0; + border: none; +} + +/* Remove any extra bottom spacing from the theme code block */ +.codeSection :global(.theme-code-block) { + margin-bottom: 0 !important; +} + +.outputSection { + border-top: 1px solid var(--ifm-color-emphasis-300); + background: var(--ifm-color-emphasis-50); +} + +.outputHeader { + padding: 0.5rem 1rem; + font-size: 0.875rem; + font-weight: 600; + color: var(--ifm-color-content-secondary); + background: var(--ifm-color-emphasis-100); + border-bottom: 1px solid var(--ifm-color-emphasis-200); +} + +.outputSection pre { + margin: 0; + border-radius: 0; + border: none; + background: var(--ifm-color-emphasis-50) !important; +} diff --git a/docs/src/components/CodeSdkTabs.tsx b/docs/src/components/CodeSdkTabs.tsx new file mode 100644 index 0000000..f2c4a37 --- /dev/null +++ b/docs/src/components/CodeSdkTabs.tsx @@ -0,0 +1,113 @@ +import React, { useState } from "react"; +import CodeBlock from "@theme/CodeBlock"; +import styles from "./CodeSdkTabs.module.css"; + +interface CodeExample { + react?: { + code: string; + output?: string; + }; + typescript?: { + code: string; + output?: string; + }; +} + +interface CodeSdkTabsProps { + example: CodeExample; + reactFilename?: string; + tsFilename?: string; +} + +// Preserves indentation by replacing leading dots with spaces +// This works around MDX/webpack stripping leading whitespace from template literals +function preserveIndent(code: string): string { + return code.replace(/^(\.+)/gm, (match) => ' '.repeat(match.length)); +} + +export default function CodeSdkTabs({ + example, + reactFilename = "index.tsx", + tsFilename = "index.ts", +}: CodeSdkTabsProps): JSX.Element { + const [activeTab, setActiveTab] = useState<"react" | "typescript">( + example.react ? "react" : "typescript" + ); + + const hasReact = !!example.react; + const hasTypeScript = !!example.typescript; + + // Infer syntax language from filename extension (.tsx → tsx, .ts → ts) + const langFor = (filename: string, fallback: string) => + filename.endsWith(".tsx") ? "tsx" : filename.endsWith(".ts") ? "ts" : fallback; + + // Don't show tabs if there's only one language + if (!hasReact || !hasTypeScript) { + const singleLang = hasReact ? "react" : "typescript"; + const singleExample = example[singleLang]; + const filename = singleLang === "react" ? reactFilename : tsFilename; + + return ( +
+
+ + {preserveIndent(singleExample!.code)} + +
+ {singleExample!.output && ( +
+
Output
+ {singleExample.output} +
+ )} +
+ ); + } + + const currentExample = example[activeTab]; + const activeFilename = activeTab === "react" ? reactFilename : tsFilename; + + return ( +
+
+
+ + +
+
+ +
+ + {preserveIndent(currentExample!.code)} + +
+ + {currentExample!.output && ( +
+
Output
+ {currentExample.output} +
+ )} +
+ ); +} diff --git a/docs/src/components/index.ts b/docs/src/components/index.ts new file mode 100644 index 0000000..33f91fd --- /dev/null +++ b/docs/src/components/index.ts @@ -0,0 +1 @@ +export { default as CodeSdkTabs } from './CodeSdkTabs'; diff --git a/docs/src/web-client/create_deploy_tutorial.md b/docs/src/web-client/create_deploy_tutorial.md index c350229..c879f49 100644 --- a/docs/src/web-client/create_deploy_tutorial.md +++ b/docs/src/web-client/create_deploy_tutorial.md @@ -3,6 +3,8 @@ title: 'Creating Accounts and Deploying Faucets' sidebar_position: 2 --- +import { CodeSdkTabs } from '@site/src/components'; + _Using the Miden WebClient in TypeScript to create accounts and deploy faucets_ ## Overview @@ -28,7 +30,7 @@ Before we dive into code, a quick refresher: - **Public accounts**: The account's data and code are stored on-chain and are openly visible, including its assets. - **Private accounts**: The account's state and logic are off-chain, only known to its owner. -- **Public notes**: The note's state is visible to anyone - perfect for scenarios where transparency is desired. +- **Public notes**: The note's state is visible to anyone - perfect for scenarios where transparency is desired. - **Private notes**: The note's state is stored off-chain, you will need to share the note data with the relevant parties (via email or Telegram) for them to be able to consume the note. > **Important**: In Miden, "accounts" and "smart contracts" can be used interchangeably due to native account abstraction. Every account is programmable and can contain custom logic. @@ -51,10 +53,12 @@ It is useful to think of notes on Miden as "cryptographic cashier's checks" that cd miden-web-app ``` -3. Install the Miden WebClient SDK: - ```bash - yarn add @miden-sdk/miden-sdk@0.13.0 - ``` +3. Install the Miden SDK: + + **NOTE!**: Be sure to add the `--webpack` command to your `package.json` when running the `dev script`. The dev script should look like this: @@ -71,17 +75,48 @@ It is useful to think of notes on Miden as "cryptographic cashier's checks" that The WebClient is your gateway to interact with the Miden blockchain. It handles state synchronization, transaction creation, and proof generation. Let's set it up. -### Create `lib/createMintConsume.ts` +### Create the library file -First, we'll create a separate file for our blockchain logic. In the project root, create a folder `lib/` and inside it `createMintConsume.ts`: +First, we'll create a separate file for our blockchain logic. In the project root, create a folder `lib/` and inside it the library file: ```bash mkdir -p lib -touch lib/createMintConsume.ts ``` -```ts -// lib/createMintConsume.ts +{/* prettier-ignore */} + { +..// We'll add our logic here +..console.log('Ready to go!'); +.}; + +.return ( +..
+... +..
+.); +} + +export default function CreateMintConsume() { +.return ( +.. +... +.. +.); +}` }, + typescript: { code: `// lib/createMintConsume.ts export async function createMintConsume(): Promise { if (typeof window === 'undefined') { console.warn('webClient() can only run in the browser'); @@ -89,7 +124,7 @@ export async function createMintConsume(): Promise { } // dynamic import → only in the browser, so WASM is loaded client‑side - const { WebClient, AccountStorageMode, AccountId, NoteType } = + const { WebClient } = await import('@miden-sdk/miden-sdk'); // Connect to Miden testnet RPC endpoint @@ -103,18 +138,33 @@ export async function createMintConsume(): Promise { // At this point, your client is connected and synchronized // Ready to create accounts and deploy contracts! -} -``` +}` }, +}} reactFilename="lib/react/createMintConsume.tsx" tsFilename="lib/createMintConsume.ts" /> > Since we will be handling proof generation in the browser, it will be slower than proof generation handled by the Rust client. Check out the [tutorial on delegated proving](./creating_multiple_notes_tutorial.md#what-is-delegated-proving) to speed up proof generation in the browser. ## Step 3: Create the User Interface -Now let's create a simple UI that will trigger our blockchain interactions. We'll replace the default Next.js page with a button that calls our `createMintConsume()` function. +Now let's create a simple UI that will trigger our blockchain interactions. We'll replace the default Next.js page with a button that calls our function. + +Edit `app/page.tsx`: -Edit `app/page.tsx` to call `createMintConsume()` on a button click: +If you're using the **React SDK**, the page simply renders your self-contained component: ```tsx +// app/page.tsx +'use client'; +import CreateMintConsume from '../lib/react/createMintConsume'; + +export default function Home() { + return ; +} +``` + +If you're using the **TypeScript SDK**, the page manages state and calls the library function directly: + +```tsx +// app/page.tsx 'use client'; import { useState } from 'react'; import { createMintConsume } from '../lib/createMintConsume'; @@ -154,11 +204,17 @@ export default function Home() { Now we'll create Alice's account. Let's create a **public** account so we can easily track her transactions. -Back in `lib/createMintConsume.ts`, extend the `createMintConsume()` function: - - -```ts -// lib/createMintConsume.ts +Back in the library file, extend the function: + +{/* prettier-ignore */} + { +.// 1. Create Alice's wallet (public, mutable) +.console.log('Creating account for Alice…'); +.const alice = await createWallet({ storageMode: 'public' }); +.console.log('Alice ID:', alice.id().toString()); +};` }, + typescript: { code: `// lib/createMintConsume.ts export async function createMintConsume(): Promise { if (typeof window === 'undefined') { console.warn('webClient() can only run in the browser'); @@ -184,9 +240,8 @@ export async function createMintConsume(): Promise { AuthScheme.AuthRpoFalcon512 // Auth Scheme: RPO Falcon 512 ); console.log('Alice ID:', alice.id().toString()); -} -``` - +}` }, +}} reactFilename="lib/react/createMintConsume.tsx" tsFilename="lib/createMintConsume.ts" /> ## Step 5: Deploy a Fungible Faucet @@ -194,9 +249,20 @@ A faucet in Miden is a special type of account that can mint new tokens. Think o Add this code after creating Alice's account: - -```ts -// 3. Deploy a fungible faucet +{/* prettier-ignore */} + +console.log('Setup complete.');` }, +}} reactFilename="lib/react/createMintConsume.tsx" tsFilename="lib/createMintConsume.ts" /> ### Understanding Faucet Parameters: @@ -232,10 +297,55 @@ In this tutorial, we've successfully: 3. Created a wallet account for Alice 4. Deployed a fungible faucet that can mint custom tokens -Your final `lib/createMintConsume.ts` should look like: +Your final library file should look like: + +{/* prettier-ignore */} + { +..// 1. Create Alice's wallet (public, mutable) +..console.log('Creating account for Alice…'); +..const alice = await createWallet({ storageMode: 'public' }); +..console.log('Alice ID:', alice.id().toString()); + +..// 2. Deploy a fungible faucet +..console.log('Creating faucet…'); +..const faucet = await createFaucet({ +...tokenSymbol: 'MID', +...decimals: 8, +...maxSupply: BigInt(1_000_000), +...storageMode: 'public', +..}); +..console.log('Faucet ID:', faucet.id().toString()); + +..console.log('Setup complete.'); +.}; + +.return ( +..
+... +..
+.); +} -```ts -// lib/createMintConsume.ts +export default function CreateMintConsume() { +.return ( +.. +... +.. +.); +}` }, + typescript: { code: `// lib/createMintConsume.ts export async function createMintConsume(): Promise { if (typeof window === 'undefined') { console.warn('webClient() can only run in the browser'); @@ -243,7 +353,7 @@ export async function createMintConsume(): Promise { } // dynamic import → only in the browser, so WASM is loaded client‑side - const { WebClient, AccountStorageMode, AuthScheme, NoteType, Address } = + const { WebClient, AccountStorageMode, AuthScheme } = await import('@miden-sdk/miden-sdk'); const nodeEndpoint = 'https://rpc.testnet.miden.io'; @@ -275,8 +385,8 @@ export async function createMintConsume(): Promise { console.log('Faucet ID:', faucet.id().toString()); console.log('Setup complete.'); -} -``` +}` }, +}} reactFilename="lib/react/createMintConsume.tsx" tsFilename="lib/createMintConsume.ts" /> ### Running the example diff --git a/docs/src/web-client/creating_multiple_notes_tutorial.md b/docs/src/web-client/creating_multiple_notes_tutorial.md index b673c31..ec2eccb 100644 --- a/docs/src/web-client/creating_multiple_notes_tutorial.md +++ b/docs/src/web-client/creating_multiple_notes_tutorial.md @@ -3,22 +3,24 @@ title: 'Creating Multiple Notes in a Single Transaction' sidebar_position: 4 --- +import { CodeSdkTabs } from '@site/src/components'; + _Using the Miden WebClient in TypeScript to create several P2ID notes in a single transaction_ ## Overview -In the previous sections we learned how to create accounts, deploy faucets, and mint tokens. In this tutorial we will: +In the previous sections we learned how to create accounts, deploy faucets, and mint tokens. In this tutorial we will: - **Mint** test tokens from a faucet to Alice -- **Consume** the minted notes so the assets appear in Alice’s wallet +- **Consume** the minted notes so the assets appear in Alice's wallet - **Create three P2ID notes in a _single_ transaction** using a custom note‑script and delegated proving The entire flow is wrapped in a helper called `multiSendWithDelegatedProver()` that you can call from any browser page. -## What we’ll cover +## What we'll cover 1. Setting‑up the WebClient and initializing a local prover -2. Building three P2ID notes worth 100 `MID` each +2. Building three P2ID notes worth 100 `MID` each 3. Submitting the transaction _using delegated proving_ ## Prerequisites @@ -56,10 +58,12 @@ can switch to delegated proving later by swapping in `TransactionProver.newRemot cd miden-web-app ``` -3. Install the Miden WebClient SDK: - ```bash - yarn add @miden-sdk/miden-sdk@0.13.0 - ``` +3. Install the Miden SDK: + + **NOTE!**: Be sure to add the `--webpack` command to your `package.json` when running the `dev script`. The dev script should look like this: @@ -76,7 +80,22 @@ can switch to delegated proving later by swapping in `TransactionProver.newRemot Add the following code to the `app/page.tsx` file: +If you're using the **React SDK**, the page simply renders your self-contained component: + +```tsx +// app/page.tsx +'use client'; +import MultiSendWithDelegatedProver from '../lib/react/multiSendWithDelegatedProver'; + +export default function Home() { + return ; +} +``` + +If you're using the **TypeScript SDK**, the page manages state and calls the library function directly: + ```tsx +// app/page.tsx 'use client'; import { useState } from 'react'; import { multiSendWithDelegatedProver } from '../lib/multiSendWithDelegatedProver'; @@ -112,17 +131,50 @@ export default function Home() { } ``` -## Step 3 — Initalize the WebClient +## Step 3 — Initalize the WebClient -Create the file `lib/multiSendWithDelegatedProver.ts` and add the following code. This snippet implements the function `multiSendWithDelegatedProver`, and initializes the WebClient along with a local prover. +Create the library file and add the following code. This snippet initializes the WebClient along with a local prover. ``` mkdir -p lib -touch lib/multiSendWithDelegatedProver.ts ``` -```ts -export async function multiSendWithDelegatedProver(): Promise { + { +..// We'll add our logic here +.}; + +.return ( +..
+... +..
+.); +} + +export default function MultiSendWithDelegatedProver() { +.return ( +.. +... +.. +.); +}` }, + typescript: { code: `export async function multiSendWithDelegatedProver(): Promise { // Ensure this runs only in a browser context if (typeof window === 'undefined') return console.warn('Run in browser'); @@ -146,15 +198,47 @@ export async function multiSendWithDelegatedProver(): Promise { const prover = TransactionProver.newLocalProver(); console.log('Latest block:', (await client.syncState()).blockNum()); -} -``` +}` }, +}} reactFilename="lib/react/multiSendWithDelegatedProver.tsx" tsFilename="lib/multiSendWithDelegatedProver.ts" /> + +## Step 4 — Create an account, deploy a faucet, mint and consume tokens + +Add the code snippet below to the function. This code creates a wallet and faucet, mints tokens from the faucet for the wallet, and then consumes the minted tokens. -## Step 4 — Create an account, deploy a faucet, mint and consume tokens  +{/* prettier-ignore */} + n.inputNoteRecord().id().toString()); +await consume({ accountId: aliceId, noteIds });` }, + typescript: { code: `// ── Creating new account ────────────────────────────────────────────────────── console.log('Creating account for Alice…'); const alice = await client.newWallet( AccountStorageMode.public(), @@ -214,15 +298,29 @@ const noteList = (await client.getConsumableNotes(alice.id())).map((rec) => txResult, ); await client.applyTransaction(txResult, submissionHeight); -} -``` - -## Step 5 — Build and Create P2ID notes - -Add the following code to the `multiSendWithDelegatedProver` function. This code defines three recipient addresses, builds three P2ID notes with 100 `MID` each, and then creates all three notes in the same transaction. +}` }, +}} reactFilename="lib/react/multiSendWithDelegatedProver.tsx" tsFilename="lib/multiSendWithDelegatedProver.ts" /> + +## Step 5 — Build and Create P2ID notes + +Add the following code to the function. This code defines three recipient addresses, builds three P2ID notes with 100 `MID` each, and then creates all three notes in the same transaction. + +{/* prettier-ignore */} + ## Summary -Your `lib/multiSendWithDelegatedProver.ts` file sould now look like this: +Your library file should now look like this: + +{/* prettier-ignore */} + { +..// 1. Create Alice's wallet +..console.log('Creating account for Alice…'); +..const alice = await createWallet({ storageMode: 'public' }); +..const aliceId = alice.id().toString(); +..console.log('Alice account ID:', aliceId); + +..// 2. Deploy a fungible faucet +..const faucet = await createFaucet({ +...tokenSymbol: 'MID', +...decimals: 8, +...maxSupply: BigInt(1_000_000), +...storageMode: 'public', +..}); +..const faucetId = faucet.id().toString(); +..console.log('Faucet ID:', faucetId); + +..// 3. Mint 10,000 MID to Alice +..const mintResult = await mint({ +...faucetId, +...targetAccountId: aliceId, +...amount: BigInt(10_000), +...noteType: 'public', +..}); + +..console.log('Waiting for settlement…'); +..await waitForCommit(mintResult.transactionId); + +..// 4. Consume the freshly minted notes +..const notes = await waitForConsumableNotes({ accountId: aliceId }); +..const noteIds = notes.map((n) => n.inputNoteRecord().id().toString()); +..await consume({ accountId: aliceId, noteIds }); + +..// 5. Send 100 MID to three recipients in a single transaction +..await sendMany({ +...from: aliceId, +...assetId: faucetId, +...recipients: [ +....{ to: 'mtst1aqezqc90x7dkzypr9m5fmlpp85w6cl04', amount: BigInt(100) }, +....{ to: 'mtst1apjg2ul76wrkxyr5qlcnczaskypa4ljn', amount: BigInt(100) }, +....{ to: 'mtst1arpee6y9cm8t7ypn33pc8fzj6gkzz7kd', amount: BigInt(100) }, +...], +...noteType: 'public', +..}); + +..console.log('All notes created ✅'); +.}; + +.return ( +..
+... +..
+.); +} -```ts -/** +export default function MultiSendWithDelegatedProver() { +.return ( +.. +... +.. +.); +}` }, + typescript: { code: `/** * Demonstrates multi-send functionality using a local prover on the Miden Network * Creates multiple P2ID (Pay to ID) notes for different recipients * @@ -384,8 +561,8 @@ export async function multiSendWithDelegatedProver(): Promise { ); console.log('All notes created ✅'); -} -``` +}` }, +}} reactFilename="lib/react/multiSendWithDelegatedProver.tsx" tsFilename="lib/multiSendWithDelegatedProver.ts" /> ### Running the example diff --git a/docs/src/web-client/mint_consume_create_tutorial.md b/docs/src/web-client/mint_consume_create_tutorial.md index 7b8c12b..18d8a6f 100644 --- a/docs/src/web-client/mint_consume_create_tutorial.md +++ b/docs/src/web-client/mint_consume_create_tutorial.md @@ -3,6 +3,8 @@ title: 'Mint, Consume, and Create Notes' sidebar_position: 3 --- +import { CodeSdkTabs } from '@site/src/components'; + _Using the Miden WebClient in TypeScript to mint, consume, and transfer assets_ ## Overview @@ -34,12 +36,23 @@ Before we start coding, it's important to understand **notes**: Let's mint some tokens for Alice. When we mint from a faucet, it creates a note containing the specified amount of tokens targeted to Alice's account. -Add this to the end of your `createMintConsume` function in `lib/createMintConsume.ts`: - - - -```ts -// 4. Mint tokens from the faucet to Alice +Add this to the end of your `createMintConsume` function: + +{/* prettier-ignore */} + setTimeout(resolve, 10000)); -await client.syncState(); -``` - - +await client.syncState();` }, +}} reactFilename="lib/react/createMintConsume.tsx" tsFilename="lib/createMintConsume.ts" /> ### What's happening here? @@ -72,31 +83,42 @@ await client.syncState(); After minting, Alice has a note waiting for her but the tokens aren't in her account yet. To identify notes that are ready to consume, the Miden WebClient provides the `getConsumableNotes` function: -```ts -// 5. Find notes available for consumption -const consumableNotes = await client.getConsumableNotes(alice.id()); -console.log(`Found ${consumableNotes.length} note(s) to consume`); - -const noteIds = consumableNotes.map((note) => - note.inputNoteRecord().id().toString(), -); -console.log('Consumable note IDs:', noteIds); -``` +{/* prettier-ignore */} + n.inputNoteRecord().id().toString()); +console.log('Consumable notes:', noteIds);` }, + typescript: { code: `// 5. Find notes available for consumption +const mintedNotes = await client.getConsumableNotes(alice.id()); +console.log(\`Found \${mintedNotes.length} note(s) to consume\`); + +const mintedNoteList = mintedNotes.map((n) => n.inputNoteRecord().toNote()); +console.log( + 'Minted notes:', + mintedNoteList.map((note) => note.id().toString()), +);` }, +}} reactFilename="lib/react/createMintConsume.tsx" tsFilename="lib/createMintConsume.ts" /> ## Step 3: Consume notes in a single transaction Now let's consume the notes to add the tokens to Alice's account balance: -```ts -// 6. Consume the notes to add tokens to Alice's balance +{/* prettier-ignore */} + ## Step 4: Sending tokens to other accounts @@ -106,18 +128,25 @@ _The standard asset transfer note on Miden is the P2ID note (Pay-to-Id). There i Now that Alice has tokens in her account, she can send some to Bob: - -```ts -// Add this import at the top of the file -import { NoteType } from "@miden-sdk/miden-sdk"; -// ... - -// 7. Send tokens from Alice to Bob +{/* prettier-ignore */} + +console.log('Tokens sent successfully!');` }, +}} reactFilename="lib/react/createMintConsume.tsx" tsFilename="lib/createMintConsume.ts" /> ### Understanding P2ID notes @@ -143,10 +170,95 @@ The transaction creates a **P2ID (Pay-to-ID)** note: ## Summary -Here's the complete `lib/createMintConsume.ts` file: +Here's the complete library file: + +{/* prettier-ignore */} + { +..// 1. Create Alice's wallet (public, mutable) +..console.log('Creating account for Alice…'); +..const alice = await createWallet({ storageMode: 'public' }); +..const aliceId = alice.id().toString(); +..console.log('Alice ID:', aliceId); + +..// 2. Deploy a fungible faucet +..console.log('Creating faucet…'); +..const faucet = await createFaucet({ +...tokenSymbol: 'MID', +...decimals: 8, +...maxSupply: BigInt(1_000_000), +...storageMode: 'public', +..}); +..const faucetId = faucet.id().toString(); +..console.log('Faucet ID:', faucetId); + +..// 3. Mint 1000 tokens to Alice +..console.log('Minting tokens to Alice...'); +..const mintResult = await mint({ +...faucetId, +...targetAccountId: aliceId, +...amount: BigInt(1000), +...noteType: 'public', +..}); +..console.log('Mint tx:', mintResult.transactionId); + +..// 4. Wait for the mint transaction to be committed +..await waitForCommit(mintResult.transactionId); + +..// 5. Wait for consumable notes to appear +..const notes = await waitForConsumableNotes({ accountId: aliceId }); +..const noteIds = notes.map((n) => n.inputNoteRecord().id().toString()); +..console.log('Consumable notes:', noteIds); + +..// 6. Consume minted notes +..console.log('Consuming minted notes...'); +..await consume({ accountId: aliceId, noteIds }); +..console.log('Notes consumed.'); + +..// 7. Send 100 tokens to Bob +..const bobAddress = 'mtst1apve54rq8ux0jqqqqrkh5y0r0y8cwza6_qruqqypuyph'; +..console.log("Sending tokens to Bob's account..."); +..await send({ +...from: aliceId, +...to: bobAddress, +...assetId: faucetId, +...amount: BigInt(100), +...noteType: 'public', +..}); +..console.log('Tokens sent successfully!'); +.}; + +.return ( +..
+... +..
+.); +} -```ts -// lib/createMintConsume.ts +export default function CreateMintConsume() { +.return ( +.. +... +.. +.); +}` }, + typescript: { code: `// lib/createMintConsume.ts export async function createMintConsume(): Promise { if (typeof window === 'undefined') { console.warn('webClient() can only run in the browser'); @@ -239,10 +351,10 @@ export async function createMintConsume(): Promise { await client.submitNewTransaction(alice.id(), sendTxRequest); console.log('Tokens sent successfully!'); -} -``` +}` }, +}} reactFilename="lib/react/createMintConsume.tsx" tsFilename="lib/createMintConsume.ts" /> -Let's run the `lib/createMintConsume.ts` function again. Reload the page and click "Start WebClient". +Let's run the function again. Reload the page and click "Start WebClient". The output will look like this: diff --git a/docs/src/web-client/unauthenticated_note_how_to.md b/docs/src/web-client/unauthenticated_note_how_to.md index 8e661ef..7338150 100644 --- a/docs/src/web-client/unauthenticated_note_how_to.md +++ b/docs/src/web-client/unauthenticated_note_how_to.md @@ -3,6 +3,8 @@ title: 'How to Use Unauthenticated Notes' sidebar_position: 6 --- +import { CodeSdkTabs } from '@site/src/components'; + _Using unauthenticated notes for optimistic note consumption with the Miden WebClient_ ## Overview @@ -74,10 +76,12 @@ This tutorial assumes you have a basic understanding of Miden assembly. To quick cd miden-web-app ``` -3. Install the Miden WebClient SDK: - ```bash - yarn add @miden-sdk/miden-sdk@0.13.0 - ``` +3. Install the Miden SDK: + + **NOTE!**: Be sure to add the `--webpack` command to your `package.json` when running the `dev script`. The dev script should look like this: @@ -94,7 +98,22 @@ This tutorial assumes you have a basic understanding of Miden assembly. To quick Add the following code to the `app/page.tsx` file. This code defines the main page of our web application: +If you're using the **React SDK**, the page simply renders your self-contained component: + +```tsx +// app/page.tsx +'use client'; +import UnauthenticatedNoteTransfer from '../lib/react/unauthenticatedNoteTransfer'; + +export default function Home() { + return ; +} +``` + +If you're using the **TypeScript SDK**, the page manages state and calls the library function directly: + ```tsx +// app/page.tsx 'use client'; import { useState } from 'react'; import { unauthenticatedNoteTransfer } from '../lib/unauthenticatedNoteTransfer'; @@ -132,17 +151,107 @@ export default function Home() { ## Step 3: Create the Unauthenticated Note Transfer Implementation -Create the file `lib/unauthenticatedNoteTransfer.ts` and add the following code: +Create the library file and add the following code: ```bash mkdir -p lib -touch lib/unauthenticatedNoteTransfer.ts ``` -Copy and paste the following code into the `lib/unauthenticatedNoteTransfer.ts` file: +Copy and paste the following code into the library file: + +{/* prettier-ignore */} + { +..// 1. Create Alice and 5 wallets for the transfer chain +..console.log('Creating accounts…'); +..const alice = await createWallet({ storageMode: 'public' }); +..const aliceId = alice.id().toString(); +..console.log('Alice account ID:', aliceId); + +..const walletIds: string[] = []; +..for (let i = 0; i < 5; i++) { +...const wallet = await createWallet({ storageMode: 'public' }); +...walletIds.push(wallet.id().toString()); +...console.log(\`Wallet \${i}:\`, walletIds[i]); +..} + +..// 2. Deploy a fungible faucet +..const faucet = await createFaucet({ +...tokenSymbol: 'MID', +...decimals: 8, +...maxSupply: BigInt(1_000_000), +...storageMode: 'public', +..}); +..const faucetId = faucet.id().toString(); +..console.log('Faucet ID:', faucetId); + +..// 3. Mint 10,000 MID to Alice +..const mintResult = await mint({ +...faucetId, +...targetAccountId: aliceId, +...amount: BigInt(10_000), +...noteType: 'public', +..}); + +..console.log('Waiting for settlement…'); +..await waitForCommit(mintResult.transactionId); + +..// 4. Consume the freshly minted notes +..const notes = await waitForConsumableNotes({ accountId: aliceId }); +..const noteIds = notes.map((n) => n.inputNoteRecord().id().toString()); +..await consume({ accountId: aliceId, noteIds }); + +..// 5. Create the unauthenticated note transfer chain: +..// Alice → Wallet 0 → Wallet 1 → Wallet 2 → Wallet 3 → Wallet 4 +..console.log('Starting unauthenticated transfer chain…'); +..const results = await transferChain({ +...from: aliceId, +...recipients: walletIds, +...assetId: faucetId, +...amount: BigInt(50), +...noteType: 'public', +..}); + +..results.forEach((r, i) => { +...console.log( +....\`Transfer \${i + 1}: https://testnet.midenscan.com/tx/\${r.consumeTransactionId}\`, +...); +..}); + +..console.log('Asset transfer chain completed ✅'); +.}; + +.return ( +..
+... +..
+.); +} -```ts -/** +export default function UnauthenticatedNoteTransfer() { +.return ( +.. +... +.. +.); +}` }, + typescript: { code: `/** * Demonstrates unauthenticated note transfer chain using a local prover on the Miden Network * Creates a chain of P2ID (Pay to ID) notes: Alice → wallet 1 → wallet 2 → wallet 3 → wallet 4 * @@ -252,7 +361,7 @@ export async function unauthenticatedNoteTransfer(): Promise { // ── Create unauthenticated note transfer chain ───────────────────────────────────────────── // Alice → wallet 1 → wallet 2 → wallet 3 → wallet 4 for (let i = 0; i < wallets.length; i++) { - console.log(`\nUnauthenticated tx ${i + 1}`); + console.log(\`\\nUnauthenticated tx \${i + 1}\`); // Determine sender and receiver for this iteration const sender = i === 0 ? alice : wallets[i - 1]; @@ -318,14 +427,14 @@ export async function unauthenticatedNoteTransfer(): Promise { .toString(); console.log( - `Consumed Note Tx on MidenScan: https://testnet.midenscan.com/tx/${txId}`, + \`Consumed Note Tx on MidenScan: https://testnet.midenscan.com/tx/\${txId}\`, ); } } console.log('Asset transfer chain completed ✅'); -} -``` +}` }, +}} reactFilename="lib/react/unauthenticatedNoteTransfer.tsx" tsFilename="lib/unauthenticatedNoteTransfer.ts" /> ## Key Concepts: Unauthenticated Notes diff --git a/web-client/lib/react/createMintConsume.tsx b/web-client/lib/react/createMintConsume.tsx new file mode 100644 index 0000000..e99beac --- /dev/null +++ b/web-client/lib/react/createMintConsume.tsx @@ -0,0 +1,84 @@ +'use client'; + +import { MidenProvider, useMiden, useCreateWallet, useCreateFaucet, useMint, useConsume, useSend, useWaitForCommit, useWaitForNotes } from '@miden-sdk/react'; + +function CreateMintConsumeInner() { + const { isReady } = useMiden(); + const { createWallet } = useCreateWallet(); + const { createFaucet } = useCreateFaucet(); + const { mint } = useMint(); + const { consume } = useConsume(); + const { send } = useSend(); + const { waitForCommit } = useWaitForCommit(); + const { waitForConsumableNotes } = useWaitForNotes(); + + const run = async () => { + // 1. Create Alice's wallet (public, mutable) + console.log('Creating account for Alice…'); + const alice = await createWallet({ storageMode: 'public' }); + const aliceId = alice.id().toString(); + console.log('Alice ID:', aliceId); + + // 2. Deploy a fungible faucet + console.log('Creating faucet…'); + const faucet = await createFaucet({ + tokenSymbol: 'MID', + decimals: 8, + maxSupply: BigInt(1_000_000), + storageMode: 'public', + }); + const faucetId = faucet.id().toString(); + console.log('Faucet ID:', faucetId); + + // 3. Mint 1000 tokens to Alice + console.log('Minting tokens to Alice...'); + const mintResult = await mint({ + faucetId, + targetAccountId: aliceId, + amount: BigInt(1000), + noteType: 'public', + }); + console.log('Mint tx:', mintResult.transactionId); + + // 4. Wait for the mint transaction to be committed + await waitForCommit(mintResult.transactionId); + + // 5. Wait for consumable notes to appear + const notes = await waitForConsumableNotes({ accountId: aliceId }); + const noteIds = notes.map((n) => n.inputNoteRecord().id().toString()); + console.log('Consumable notes:', noteIds); + + // 6. Consume minted notes + console.log('Consuming minted notes...'); + await consume({ accountId: aliceId, noteIds }); + console.log('Notes consumed.'); + + // 7. Send 100 tokens to Bob + const bobAddress = 'mtst1apve54rq8ux0jqqqqrkh5y0r0y8cwza6_qruqqypuyph'; + console.log("Sending tokens to Bob's account..."); + await send({ + from: aliceId, + to: bobAddress, + assetId: faucetId, + amount: BigInt(100), + noteType: 'public', + }); + console.log('Tokens sent successfully!'); + }; + + return ( +
+ +
+ ); +} + +export default function CreateMintConsume() { + return ( + + + + ); +} diff --git a/web-client/lib/react/multiSendWithDelegatedProver.tsx b/web-client/lib/react/multiSendWithDelegatedProver.tsx new file mode 100644 index 0000000..f6bdb66 --- /dev/null +++ b/web-client/lib/react/multiSendWithDelegatedProver.tsx @@ -0,0 +1,78 @@ +'use client'; + +import { MidenProvider, useMiden, useCreateWallet, useCreateFaucet, useMint, useConsume, useMultiSend, useWaitForCommit, useWaitForNotes } from '@miden-sdk/react'; + +function MultiSendInner() { + const { isReady } = useMiden(); + const { createWallet } = useCreateWallet(); + const { createFaucet } = useCreateFaucet(); + const { mint } = useMint(); + const { consume } = useConsume(); + const { sendMany } = useMultiSend(); + const { waitForCommit } = useWaitForCommit(); + const { waitForConsumableNotes } = useWaitForNotes(); + + const run = async () => { + // 1. Create Alice's wallet + console.log('Creating account for Alice…'); + const alice = await createWallet({ storageMode: 'public' }); + const aliceId = alice.id().toString(); + console.log('Alice account ID:', aliceId); + + // 2. Deploy a fungible faucet + const faucet = await createFaucet({ + tokenSymbol: 'MID', + decimals: 8, + maxSupply: BigInt(1_000_000), + storageMode: 'public', + }); + const faucetId = faucet.id().toString(); + console.log('Faucet ID:', faucetId); + + // 3. Mint 10,000 MID to Alice + const mintResult = await mint({ + faucetId, + targetAccountId: aliceId, + amount: BigInt(10_000), + noteType: 'public', + }); + + console.log('Waiting for settlement…'); + await waitForCommit(mintResult.transactionId); + + // 4. Consume the freshly minted notes + const notes = await waitForConsumableNotes({ accountId: aliceId }); + const noteIds = notes.map((n) => n.inputNoteRecord().id().toString()); + await consume({ accountId: aliceId, noteIds }); + + // 5. Send 100 MID to three recipients in a single transaction + await sendMany({ + from: aliceId, + assetId: faucetId, + recipients: [ + { to: 'mtst1aqezqc90x7dkzypr9m5fmlpp85w6cl04', amount: BigInt(100) }, + { to: 'mtst1apjg2ul76wrkxyr5qlcnczaskypa4ljn', amount: BigInt(100) }, + { to: 'mtst1arpee6y9cm8t7ypn33pc8fzj6gkzz7kd', amount: BigInt(100) }, + ], + noteType: 'public', + }); + + console.log('All notes created ✅'); + }; + + return ( +
+ +
+ ); +} + +export default function MultiSendWithDelegatedProver() { + return ( + + + + ); +} diff --git a/web-client/lib/react/unauthenticatedNoteTransfer.tsx b/web-client/lib/react/unauthenticatedNoteTransfer.tsx new file mode 100644 index 0000000..4896e62 --- /dev/null +++ b/web-client/lib/react/unauthenticatedNoteTransfer.tsx @@ -0,0 +1,90 @@ +'use client'; + +import { MidenProvider, useMiden, useCreateWallet, useCreateFaucet, useMint, useConsume, useInternalTransfer, useWaitForCommit, useWaitForNotes } from '@miden-sdk/react'; + +function UnauthenticatedNoteTransferInner() { + const { isReady } = useMiden(); + const { createWallet } = useCreateWallet(); + const { createFaucet } = useCreateFaucet(); + const { mint } = useMint(); + const { consume } = useConsume(); + const { transferChain } = useInternalTransfer(); + const { waitForCommit } = useWaitForCommit(); + const { waitForConsumableNotes } = useWaitForNotes(); + + const run = async () => { + // 1. Create Alice and 5 wallets for the transfer chain + console.log('Creating accounts…'); + const alice = await createWallet({ storageMode: 'public' }); + const aliceId = alice.id().toString(); + console.log('Alice account ID:', aliceId); + + const walletIds: string[] = []; + for (let i = 0; i < 5; i++) { + const wallet = await createWallet({ storageMode: 'public' }); + walletIds.push(wallet.id().toString()); + console.log(`Wallet ${i}:`, walletIds[i]); + } + + // 2. Deploy a fungible faucet + const faucet = await createFaucet({ + tokenSymbol: 'MID', + decimals: 8, + maxSupply: BigInt(1_000_000), + storageMode: 'public', + }); + const faucetId = faucet.id().toString(); + console.log('Faucet ID:', faucetId); + + // 3. Mint 10,000 MID to Alice + const mintResult = await mint({ + faucetId, + targetAccountId: aliceId, + amount: BigInt(10_000), + noteType: 'public', + }); + + console.log('Waiting for settlement…'); + await waitForCommit(mintResult.transactionId); + + // 4. Consume the freshly minted notes + const notes = await waitForConsumableNotes({ accountId: aliceId }); + const noteIds = notes.map((n) => n.inputNoteRecord().id().toString()); + await consume({ accountId: aliceId, noteIds }); + + // 5. Create the unauthenticated note transfer chain: + // Alice → Wallet 0 → Wallet 1 → Wallet 2 → Wallet 3 → Wallet 4 + console.log('Starting unauthenticated transfer chain…'); + const results = await transferChain({ + from: aliceId, + recipients: walletIds, + assetId: faucetId, + amount: BigInt(50), + noteType: 'public', + }); + + results.forEach((r, i) => { + console.log( + `Transfer ${i + 1}: https://testnet.midenscan.com/tx/${r.consumeTransactionId}`, + ); + }); + + console.log('Asset transfer chain completed ✅'); + }; + + return ( +
+ +
+ ); +} + +export default function UnauthenticatedNoteTransfer() { + return ( + + + + ); +} diff --git a/web-client/package.json b/web-client/package.json index 63fe0c1..325b44e 100644 --- a/web-client/package.json +++ b/web-client/package.json @@ -10,6 +10,7 @@ }, "dependencies": { "@miden-sdk/miden-sdk": "0.13.0", + "@miden-sdk/react": "0.13.0", "next": "15.3.2", "react": "^19.0.0", "react-dom": "^19.0.0" From a86f9ef7bc39f756e92cbcab86ee9c8fdd056da9 Mon Sep 17 00:00:00 2001 From: Wiktor Starczewski Date: Fri, 6 Feb 2026 14:42:31 +0400 Subject: [PATCH 06/18] docs: specify filenames in unauthenticated note tutorial --- docs/src/web-client/unauthenticated_note_how_to.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/src/web-client/unauthenticated_note_how_to.md b/docs/src/web-client/unauthenticated_note_how_to.md index 7338150..d3edd12 100644 --- a/docs/src/web-client/unauthenticated_note_how_to.md +++ b/docs/src/web-client/unauthenticated_note_how_to.md @@ -157,7 +157,7 @@ Create the library file and add the following code: mkdir -p lib ``` -Copy and paste the following code into the library file: +Copy and paste the following code into `lib/react/unauthenticatedNoteTransfer.tsx` (React) or `lib/unauthenticatedNoteTransfer.ts` (TypeScript): {/* prettier-ignore */} Date: Fri, 6 Feb 2026 14:44:26 +0400 Subject: [PATCH 07/18] docs: specify filenames in tutorials instead of generic 'library file' --- docs/src/web-client/create_deploy_tutorial.md | 6 +++--- docs/src/web-client/creating_multiple_notes_tutorial.md | 2 +- docs/src/web-client/mint_consume_create_tutorial.md | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/src/web-client/create_deploy_tutorial.md b/docs/src/web-client/create_deploy_tutorial.md index c879f49..4965165 100644 --- a/docs/src/web-client/create_deploy_tutorial.md +++ b/docs/src/web-client/create_deploy_tutorial.md @@ -77,7 +77,7 @@ The WebClient is your gateway to interact with the Miden blockchain. It handles ### Create the library file -First, we'll create a separate file for our blockchain logic. In the project root, create a folder `lib/` and inside it the library file: +First, we'll create a separate file for our blockchain logic. In the project root, create a folder `lib/` and inside it `lib/react/createMintConsume.tsx` (React) or `lib/createMintConsume.ts` (TypeScript): ```bash mkdir -p lib @@ -204,7 +204,7 @@ export default function Home() { Now we'll create Alice's account. Let's create a **public** account so we can easily track her transactions. -Back in the library file, extend the function: +Back in your library file, extend the function: {/* prettier-ignore */} Date: Fri, 6 Feb 2026 14:46:41 +0400 Subject: [PATCH 08/18] style: format markdown with prettier --- docs/src/web-client/create_deploy_tutorial.md | 186 ++++---- .../creating_multiple_notes_tutorial.md | 409 +++++++++--------- .../mint_consume_create_tutorial.md | 246 +++++------ docs/src/web-client/react_wallet_tutorial.md | 263 ++++++----- .../web-client/unauthenticated_note_how_to.md | 224 +++++----- 5 files changed, 697 insertions(+), 631 deletions(-) diff --git a/docs/src/web-client/create_deploy_tutorial.md b/docs/src/web-client/create_deploy_tutorial.md index 4965165..00a4670 100644 --- a/docs/src/web-client/create_deploy_tutorial.md +++ b/docs/src/web-client/create_deploy_tutorial.md @@ -83,9 +83,9 @@ First, we'll create a separate file for our blockchain logic. In the project roo mkdir -p lib ``` -{/* prettier-ignore */} +{/_ prettier-ignore _/} .. .); -}` }, - typescript: { code: `// lib/createMintConsume.ts +}`}, + typescript: { code:`// lib/createMintConsume.ts export async function createMintConsume(): Promise { - if (typeof window === 'undefined') { - console.warn('webClient() can only run in the browser'); - return; - } +if (typeof window === 'undefined') { +console.warn('webClient() can only run in the browser'); +return; +} - // dynamic import → only in the browser, so WASM is loaded client‑side - const { WebClient } = - await import('@miden-sdk/miden-sdk'); +// dynamic import → only in the browser, so WASM is loaded client‑side +const { WebClient } = +await import('@miden-sdk/miden-sdk'); - // Connect to Miden testnet RPC endpoint - const nodeEndpoint = 'https://rpc.testnet.miden.io'; - const client = await WebClient.createClient(nodeEndpoint); +// Connect to Miden testnet RPC endpoint +const nodeEndpoint = 'https://rpc.testnet.miden.io'; +const client = await WebClient.createClient(nodeEndpoint); - // 1. Sync with the latest blockchain state - // This fetches the latest block header and state commitments - const state = await client.syncState(); - console.log('Latest block number:', state.blockNum()); +// 1. Sync with the latest blockchain state +// This fetches the latest block header and state commitments +const state = await client.syncState(); +console.log('Latest block number:', state.blockNum()); - // At this point, your client is connected and synchronized - // Ready to create accounts and deploy contracts! +// At this point, your client is connected and synchronized +// Ready to create accounts and deploy contracts! }` }, }} reactFilename="lib/react/createMintConsume.tsx" tsFilename="lib/createMintConsume.ts" /> @@ -206,40 +206,40 @@ Now we'll create Alice's account. Let's create a **public** account so we can ea Back in your library file, extend the function: -{/* prettier-ignore */} +{/_ prettier-ignore _/} { +react: { code: `const run = async () => { .// 1. Create Alice's wallet (public, mutable) .console.log('Creating account for Alice…'); .const alice = await createWallet({ storageMode: 'public' }); .console.log('Alice ID:', alice.id().toString()); };` }, - typescript: { code: `// lib/createMintConsume.ts +typescript: { code: `// lib/createMintConsume.ts export async function createMintConsume(): Promise { - if (typeof window === 'undefined') { - console.warn('webClient() can only run in the browser'); - return; - } +if (typeof window === 'undefined') { +console.warn('webClient() can only run in the browser'); +return; +} - const { WebClient, AccountStorageMode, AuthScheme } = await import( - "@miden-sdk/miden-sdk" - ); +const { WebClient, AccountStorageMode, AuthScheme } = await import( +"@miden-sdk/miden-sdk" +); - const nodeEndpoint = 'https://rpc.testnet.miden.io'; - const client = await WebClient.createClient(nodeEndpoint); +const nodeEndpoint = 'https://rpc.testnet.miden.io'; +const client = await WebClient.createClient(nodeEndpoint); - // 1. Sync with the latest blockchain state - const state = await client.syncState(); - console.log('Latest block number:', state.blockNum()); +// 1. Sync with the latest blockchain state +const state = await client.syncState(); +console.log('Latest block number:', state.blockNum()); - // 2. Create Alice's account - console.log('Creating account for Alice…'); - const alice = await client.newWallet( - AccountStorageMode.public(), // Public: account state is visible on-chain - true, // Mutable: account code can be upgraded later - AuthScheme.AuthRpoFalcon512 // Auth Scheme: RPO Falcon 512 - ); - console.log('Alice ID:', alice.id().toString()); +// 2. Create Alice's account +console.log('Creating account for Alice…'); +const alice = await client.newWallet( +AccountStorageMode.public(), // Public: account state is visible on-chain +true, // Mutable: account code can be upgraded later +AuthScheme.AuthRpoFalcon512 // Auth Scheme: RPO Falcon 512 +); +console.log('Alice ID:', alice.id().toString()); }` }, }} reactFilename="lib/react/createMintConsume.tsx" tsFilename="lib/createMintConsume.ts" /> @@ -249,29 +249,29 @@ A faucet in Miden is a special type of account that can mint new tokens. Think o Add this code after creating Alice's account: -{/* prettier-ignore */} +{/_ prettier-ignore _/} .. .); -}` }, - typescript: { code: `// lib/createMintConsume.ts +}`}, + typescript: { code:`// lib/createMintConsume.ts export async function createMintConsume(): Promise { - if (typeof window === 'undefined') { - console.warn('webClient() can only run in the browser'); - return; - } +if (typeof window === 'undefined') { +console.warn('webClient() can only run in the browser'); +return; +} - // dynamic import → only in the browser, so WASM is loaded client‑side - const { WebClient, AccountStorageMode, AuthScheme } = - await import('@miden-sdk/miden-sdk'); +// dynamic import → only in the browser, so WASM is loaded client‑side +const { WebClient, AccountStorageMode, AuthScheme } = +await import('@miden-sdk/miden-sdk'); - const nodeEndpoint = 'https://rpc.testnet.miden.io'; - const client = await WebClient.createClient(nodeEndpoint); +const nodeEndpoint = 'https://rpc.testnet.miden.io'; +const client = await WebClient.createClient(nodeEndpoint); - // 1. Sync with the latest blockchain state - const state = await client.syncState(); - console.log('Latest block number:', state.blockNum()); +// 1. Sync with the latest blockchain state +const state = await client.syncState(); +console.log('Latest block number:', state.blockNum()); - // 2. Create Alice's account - console.log('Creating account for Alice…'); - const alice = await client.newWallet( - AccountStorageMode.public(), - true, - AuthScheme.AuthRpoFalcon512, - ); - console.log('Alice ID:', alice.id().toString()); - - // 3. Deploy a fungible faucet - console.log('Creating faucet…'); - const faucet = await client.newFaucet( - AccountStorageMode.public(), - false, - 'MID', - 8, - BigInt(1_000_000), - AuthScheme.AuthRpoFalcon512, - ); - console.log('Faucet ID:', faucet.id().toString()); +// 2. Create Alice's account +console.log('Creating account for Alice…'); +const alice = await client.newWallet( +AccountStorageMode.public(), +true, +AuthScheme.AuthRpoFalcon512, +); +console.log('Alice ID:', alice.id().toString()); + +// 3. Deploy a fungible faucet +console.log('Creating faucet…'); +const faucet = await client.newFaucet( +AccountStorageMode.public(), +false, +'MID', +8, +BigInt(1_000_000), +AuthScheme.AuthRpoFalcon512, +); +console.log('Faucet ID:', faucet.id().toString()); - console.log('Setup complete.'); +console.log('Setup complete.'); }` }, }} reactFilename="lib/react/createMintConsume.tsx" tsFilename="lib/createMintConsume.ts" /> diff --git a/docs/src/web-client/creating_multiple_notes_tutorial.md b/docs/src/web-client/creating_multiple_notes_tutorial.md index 80132e7..055f369 100644 --- a/docs/src/web-client/creating_multiple_notes_tutorial.md +++ b/docs/src/web-client/creating_multiple_notes_tutorial.md @@ -140,7 +140,7 @@ mkdir -p lib ``` .. .); -}` }, - typescript: { code: `export async function multiSendWithDelegatedProver(): Promise { - // Ensure this runs only in a browser context - if (typeof window === 'undefined') return console.warn('Run in browser'); - - const { - WebClient, - AccountStorageMode, - AuthScheme, - Address, - NoteType, - TransactionProver, - Note, - NoteAssets, - OutputNoteArray, - NoteAttachment, - FungibleAsset, - TransactionRequestBuilder, - OutputNote, - } = await import('@miden-sdk/miden-sdk'); - - const client = await WebClient.createClient('https://rpc.testnet.miden.io'); - const prover = TransactionProver.newLocalProver(); - - console.log('Latest block:', (await client.syncState()).blockNum()); +}`}, + typescript: { code:`export async function multiSendWithDelegatedProver(): Promise { +// Ensure this runs only in a browser context +if (typeof window === 'undefined') return console.warn('Run in browser'); + +const { +WebClient, +AccountStorageMode, +AuthScheme, +Address, +NoteType, +TransactionProver, +Note, +NoteAssets, +OutputNoteArray, +NoteAttachment, +FungibleAsset, +TransactionRequestBuilder, +OutputNote, +} = await import('@miden-sdk/miden-sdk'); + +const client = await WebClient.createClient('https://rpc.testnet.miden.io'); +const prover = TransactionProver.newLocalProver(); + +console.log('Latest block:', (await client.syncState()).blockNum()); }` }, }} reactFilename="lib/react/multiSendWithDelegatedProver.tsx" tsFilename="lib/multiSendWithDelegatedProver.ts" /> @@ -205,9 +205,9 @@ export default function MultiSendWithDelegatedProver() { Add the code snippet below to the function. This code creates a wallet and faucet, mints tokens from the faucet for the wallet, and then consumes the minted tokens. -{/* prettier-ignore */} +{/_ prettier-ignore _/} n.inputNoteRecord().id().toString()); -await consume({ accountId: aliceId, noteIds });` }, - typescript: { code: `// ── Creating new account ────────────────────────────────────────────────────── +await consume({ accountId: aliceId, noteIds });`}, + typescript: { code:`// ── Creating new account ────────────────────────────────────────────────────── console.log('Creating account for Alice…'); const alice = await client.newWallet( - AccountStorageMode.public(), - true, - AuthScheme.AuthRpoFalcon512, +AccountStorageMode.public(), +true, +AuthScheme.AuthRpoFalcon512, ); console.log('Alice accout ID:', alice.id().toString()); // ── Creating new faucet ────────────────────────────────────────────────────── const faucet = await client.newFaucet( - AccountStorageMode.public(), - false, - 'MID', - 8, - BigInt(1_000_000), - AuthScheme.AuthRpoFalcon512, +AccountStorageMode.public(), +false, +'MID', +8, +BigInt(1_000_000), +AuthScheme.AuthRpoFalcon512, ); console.log('Faucet ID:', faucet.id().toString()); // ── mint 10 000 MID to Alice ────────────────────────────────────────────────────── { - const txResult = await client.executeTransaction( - faucet.id(), - client.newMintTransactionRequest( - alice.id(), - faucet.id(), - NoteType.Public, - BigInt(10_000), - ), - ); - const proven = await client.proveTransaction(txResult, prover); - const submissionHeight = await client.submitProvenTransaction( - proven, - txResult, - ); - await client.applyTransaction(txResult, submissionHeight); +const txResult = await client.executeTransaction( +faucet.id(), +client.newMintTransactionRequest( +alice.id(), +faucet.id(), +NoteType.Public, +BigInt(10_000), +), +); +const proven = await client.proveTransaction(txResult, prover); +const submissionHeight = await client.submitProvenTransaction( +proven, +txResult, +); +await client.applyTransaction(txResult, submissionHeight); - console.log('waiting for settlement'); - await new Promise((r) => setTimeout(r, 7_000)); - await client.syncState(); +console.log('waiting for settlement'); +await new Promise((r) => setTimeout(r, 7_000)); +await client.syncState(); } // ── consume the freshly minted notes ────────────────────────────────────────────── const noteList = (await client.getConsumableNotes(alice.id())).map((rec) => - rec.inputNoteRecord().toNote(), +rec.inputNoteRecord().toNote(), ); { - const txResult = await client.executeTransaction( - alice.id(), - client.newConsumeTransactionRequest(noteList), - ); - const proven = await client.proveTransaction(txResult, prover); - await client.syncState(); - const submissionHeight = await client.submitProvenTransaction( - proven, - txResult, - ); - await client.applyTransaction(txResult, submissionHeight); +const txResult = await client.executeTransaction( +alice.id(), +client.newConsumeTransactionRequest(noteList), +); +const proven = await client.proveTransaction(txResult, prover); +await client.syncState(); +const submissionHeight = await client.submitProvenTransaction( +proven, +txResult, +); +await client.applyTransaction(txResult, submissionHeight); }` }, }} reactFilename="lib/react/multiSendWithDelegatedProver.tsx" tsFilename="lib/multiSendWithDelegatedProver.ts" /> @@ -305,9 +305,9 @@ const noteList = (await client.getConsumableNotes(alice.id())).map((rec) => Add the following code to the function. This code defines three recipient addresses, builds three P2ID notes with 100 `MID` each, and then creates all three notes in the same transaction. -{/* prettier-ignore */} +{/_ prettier-ignore _/} { - const receiverAccountId = Address.fromBech32(addr).accountId(); - const note = Note.createP2IDNote( - alice.id(), - receiverAccountId, - assets, - NoteType.Public, - new NoteAttachment(), - ); +const receiverAccountId = Address.fromBech32(addr).accountId(); +const note = Note.createP2IDNote( +alice.id(), +receiverAccountId, +assets, +NoteType.Public, +new NoteAttachment(), +); - return OutputNote.full(note); +return OutputNote.full(note); }); // ── create all P2ID notes ─────────────────────────────────────────────────────────────── await client.submitNewTransaction( - alice.id(), - new TransactionRequestBuilder() - .withOwnOutputNotes(new OutputNoteArray(p2idNotes)) - .build(), +alice.id(), +new TransactionRequestBuilder() +.withOwnOutputNotes(new OutputNoteArray(p2idNotes)) +.build(), ); console.log('All notes created ✅');` }, @@ -357,9 +357,9 @@ console.log('All notes created ✅');` }, Your library file should now look like this: -{/* prettier-ignore */} +{/_ prettier-ignore _/} .. .); -}` }, - typescript: { code: `/** - * Demonstrates multi-send functionality using a local prover on the Miden Network - * Creates multiple P2ID (Pay to ID) notes for different recipients - * - * @throws {Error} If the function cannot be executed in a browser environment - */ -export async function multiSendWithDelegatedProver(): Promise { +}`}, + typescript: { code:`/\*\* + +- Demonstrates multi-send functionality using a local prover on the Miden Network +- Creates multiple P2ID (Pay to ID) notes for different recipients +- +- @throws {Error} If the function cannot be executed in a browser environment + \*/ + export async function multiSendWithDelegatedProver(): Promise { // Ensure this runs only in a browser context if (typeof window === 'undefined') return console.warn('Run in browser'); - const { - WebClient, - AccountStorageMode, - AuthScheme, - Address, - NoteType, - TransactionProver, - Note, - NoteAssets, - OutputNoteArray, - FungibleAsset, - NoteAttachment, - TransactionRequestBuilder, - OutputNote, - } = await import('@miden-sdk/miden-sdk'); - - const client = await WebClient.createClient('https://rpc.testnet.miden.io'); - const prover = TransactionProver.newLocalProver(); - - console.log('Latest block:', (await client.syncState()).blockNum()); - - // ── Creating new account ────────────────────────────────────────────────────── - console.log('Creating account for Alice…'); - const alice = await client.newWallet( - AccountStorageMode.public(), - true, - AuthScheme.AuthRpoFalcon512, - ); - console.log('Alice accout ID:', alice.id().toString()); - - // ── Creating new faucet ────────────────────────────────────────────────────── - const faucet = await client.newFaucet( - AccountStorageMode.public(), - false, - 'MID', - 8, - BigInt(1_000_000), - AuthScheme.AuthRpoFalcon512, - ); - console.log('Faucet ID:', faucet.id().toString()); - - // ── mint 10 000 MID to Alice ────────────────────────────────────────────────────── - { - const txResult = await client.executeTransaction( - faucet.id(), - client.newMintTransactionRequest( - alice.id(), - faucet.id(), - NoteType.Public, - BigInt(10_000), - ), - ); - const proven = await client.proveTransaction(txResult, prover); - const submissionHeight = await client.submitProvenTransaction( - proven, - txResult, - ); - await client.applyTransaction(txResult, submissionHeight); +const { +WebClient, +AccountStorageMode, +AuthScheme, +Address, +NoteType, +TransactionProver, +Note, +NoteAssets, +OutputNoteArray, +FungibleAsset, +NoteAttachment, +TransactionRequestBuilder, +OutputNote, +} = await import('@miden-sdk/miden-sdk'); + +const client = await WebClient.createClient('https://rpc.testnet.miden.io'); +const prover = TransactionProver.newLocalProver(); + +console.log('Latest block:', (await client.syncState()).blockNum()); + +// ── Creating new account ────────────────────────────────────────────────────── +console.log('Creating account for Alice…'); +const alice = await client.newWallet( +AccountStorageMode.public(), +true, +AuthScheme.AuthRpoFalcon512, +); +console.log('Alice accout ID:', alice.id().toString()); + +// ── Creating new faucet ────────────────────────────────────────────────────── +const faucet = await client.newFaucet( +AccountStorageMode.public(), +false, +'MID', +8, +BigInt(1_000_000), +AuthScheme.AuthRpoFalcon512, +); +console.log('Faucet ID:', faucet.id().toString()); + +// ── mint 10 000 MID to Alice ────────────────────────────────────────────────────── +{ +const txResult = await client.executeTransaction( +faucet.id(), +client.newMintTransactionRequest( +alice.id(), +faucet.id(), +NoteType.Public, +BigInt(10_000), +), +); +const proven = await client.proveTransaction(txResult, prover); +const submissionHeight = await client.submitProvenTransaction( +proven, +txResult, +); +await client.applyTransaction(txResult, submissionHeight); console.log('waiting for settlement'); await new Promise((r) => setTimeout(r, 7_000)); await client.syncState(); - } - // ── consume the freshly minted notes ────────────────────────────────────────────── - const noteList = (await client.getConsumableNotes(alice.id())).map((rec) => - rec.inputNoteRecord().toNote(), - ); +} - { - const txResult = await client.executeTransaction( - alice.id(), - client.newConsumeTransactionRequest(noteList), - ); - const proven = await client.proveTransaction(txResult, prover); - await client.syncState(); - const submissionHeight = await client.submitProvenTransaction( - proven, - txResult, - ); - await client.applyTransaction(txResult, submissionHeight); - } +// ── consume the freshly minted notes ────────────────────────────────────────────── +const noteList = (await client.getConsumableNotes(alice.id())).map((rec) => +rec.inputNoteRecord().toNote(), +); - // ── build 3 P2ID notes (100 MID each) ───────────────────────────────────────────── - const recipientAddresses = [ - 'mtst1aqezqc90x7dkzypr9m5fmlpp85w6cl04', - 'mtst1apjg2ul76wrkxyr5qlcnczaskypa4ljn', - 'mtst1arpee6y9cm8t7ypn33pc8fzj6gkzz7kd', - ]; - - const assets = new NoteAssets([new FungibleAsset(faucet.id(), BigInt(100))]); - - const p2idNotes = recipientAddresses.map((addr) => { - const receiverAccountId = Address.fromBech32(addr).accountId(); - const note = Note.createP2IDNote( - alice.id(), - receiverAccountId, - assets, - NoteType.Public, - new NoteAttachment(), - ); +{ +const txResult = await client.executeTransaction( +alice.id(), +client.newConsumeTransactionRequest(noteList), +); +const proven = await client.proveTransaction(txResult, prover); +await client.syncState(); +const submissionHeight = await client.submitProvenTransaction( +proven, +txResult, +); +await client.applyTransaction(txResult, submissionHeight); +} + +// ── build 3 P2ID notes (100 MID each) ───────────────────────────────────────────── +const recipientAddresses = [ +'mtst1aqezqc90x7dkzypr9m5fmlpp85w6cl04', +'mtst1apjg2ul76wrkxyr5qlcnczaskypa4ljn', +'mtst1arpee6y9cm8t7ypn33pc8fzj6gkzz7kd', +]; + +const assets = new NoteAssets([new FungibleAsset(faucet.id(), BigInt(100))]); + +const p2idNotes = recipientAddresses.map((addr) => { +const receiverAccountId = Address.fromBech32(addr).accountId(); +const note = Note.createP2IDNote( +alice.id(), +receiverAccountId, +assets, +NoteType.Public, +new NoteAttachment(), +); return OutputNote.full(note); - }); - - // ── create all P2ID notes ─────────────────────────────────────────────────────────────── - await client.submitNewTransaction( - alice.id(), - new TransactionRequestBuilder() - .withOwnOutputNotes(new OutputNoteArray(p2idNotes)) - .build(), - ); - console.log('All notes created ✅'); +}); + +// ── create all P2ID notes ─────────────────────────────────────────────────────────────── +await client.submitNewTransaction( +alice.id(), +new TransactionRequestBuilder() +.withOwnOutputNotes(new OutputNoteArray(p2idNotes)) +.build(), +); + +console.log('All notes created ✅'); }` }, }} reactFilename="lib/react/multiSendWithDelegatedProver.tsx" tsFilename="lib/multiSendWithDelegatedProver.ts" /> diff --git a/docs/src/web-client/mint_consume_create_tutorial.md b/docs/src/web-client/mint_consume_create_tutorial.md index d3d6189..8de0d0e 100644 --- a/docs/src/web-client/mint_consume_create_tutorial.md +++ b/docs/src/web-client/mint_consume_create_tutorial.md @@ -38,29 +38,29 @@ Let's mint some tokens for Alice. When we mint from a faucet, it creates a note Add this to the end of your `createMintConsume` function: -{/* prettier-ignore */} +{/_ prettier-ignore _/} n.inputNoteRecord().id().toString()); console.log('Consumable notes:', noteIds);` }, - typescript: { code: `// 5. Find notes available for consumption +typescript: { code: `// 5. Find notes available for consumption const mintedNotes = await client.getConsumableNotes(alice.id()); console.log(\`Found \${mintedNotes.length} note(s) to consume\`); const mintedNoteList = mintedNotes.map((n) => n.inputNoteRecord().toNote()); console.log( - 'Minted notes:', - mintedNoteList.map((note) => note.id().toString()), +'Minted notes:', +mintedNoteList.map((note) => note.id().toString()), );` }, }} reactFilename="lib/react/createMintConsume.tsx" tsFilename="lib/createMintConsume.ts" /> @@ -104,13 +104,13 @@ console.log( Now let's consume the notes to add the tokens to Alice's account balance: -{/* prettier-ignore */} +{/_ prettier-ignore _/} .. .); -}` }, - typescript: { code: `// lib/createMintConsume.ts +}`}, + typescript: { code:`// lib/createMintConsume.ts export async function createMintConsume(): Promise { - if (typeof window === 'undefined') { - console.warn('webClient() can only run in the browser'); - return; - } +if (typeof window === 'undefined') { +console.warn('webClient() can only run in the browser'); +return; +} + +// dynamic import → only in the browser, so WASM is loaded client‑side +const { WebClient, AccountStorageMode, AuthScheme, NoteType, Address } = +await import('@miden-sdk/miden-sdk'); + +const nodeEndpoint = 'https://rpc.testnet.miden.io'; +const client = await WebClient.createClient(nodeEndpoint); + +// 1. Sync with the latest blockchain state +const state = await client.syncState(); +console.log('Latest block number:', state.blockNum()); + +// 2. Create Alice's account +console.log('Creating account for Alice…'); +const aliceSeed = new Uint8Array(32); +crypto.getRandomValues(aliceSeed); +const alice = await client.newWallet( +AccountStorageMode.public(), +true, +AuthScheme.AuthRpoFalcon512, +aliceSeed, +); +console.log('Alice ID:', alice.id().toString()); + +// 3. Deploy a fungible faucet +console.log('Creating faucet…'); +const faucet = await client.newFaucet( +AccountStorageMode.public(), +false, +'MID', +8, +BigInt(1_000_000), +AuthScheme.AuthRpoFalcon512, +); +console.log('Faucet ID:', faucet.id().toString()); + +await client.syncState(); + +// 4. Mint tokens to Alice +await client.syncState(); + +console.log('Minting tokens to Alice...'); +const mintTxRequest = client.newMintTransactionRequest( +alice.id(), +faucet.id(), +NoteType.Public, +BigInt(1000), +); + +await client.submitNewTransaction(faucet.id(), mintTxRequest); + +console.log('Waiting 10 seconds for transaction confirmation...'); +await new Promise((resolve) => setTimeout(resolve, 10000)); +await client.syncState(); - // dynamic import → only in the browser, so WASM is loaded client‑side - const { WebClient, AccountStorageMode, AuthScheme, NoteType, Address } = - await import('@miden-sdk/miden-sdk'); - - const nodeEndpoint = 'https://rpc.testnet.miden.io'; - const client = await WebClient.createClient(nodeEndpoint); - - // 1. Sync with the latest blockchain state - const state = await client.syncState(); - console.log('Latest block number:', state.blockNum()); - - // 2. Create Alice's account - console.log('Creating account for Alice…'); - const aliceSeed = new Uint8Array(32); - crypto.getRandomValues(aliceSeed); - const alice = await client.newWallet( - AccountStorageMode.public(), - true, - AuthScheme.AuthRpoFalcon512, - aliceSeed, - ); - console.log('Alice ID:', alice.id().toString()); - - // 3. Deploy a fungible faucet - console.log('Creating faucet…'); - const faucet = await client.newFaucet( - AccountStorageMode.public(), - false, - 'MID', - 8, - BigInt(1_000_000), - AuthScheme.AuthRpoFalcon512, - ); - console.log('Faucet ID:', faucet.id().toString()); - - await client.syncState(); - - // 4. Mint tokens to Alice - await client.syncState(); - - console.log('Minting tokens to Alice...'); - const mintTxRequest = client.newMintTransactionRequest( - alice.id(), - faucet.id(), - NoteType.Public, - BigInt(1000), - ); - - await client.submitNewTransaction(faucet.id(), mintTxRequest); - - console.log('Waiting 10 seconds for transaction confirmation...'); - await new Promise((resolve) => setTimeout(resolve, 10000)); - await client.syncState(); - - // 5. Fetch minted notes - const mintedNotes = await client.getConsumableNotes(alice.id()); - const mintedNoteList = mintedNotes.map((n) => n.inputNoteRecord().toNote()); - console.log( - 'Minted notes:', - mintedNoteList.map((note) => note.id().toString()), - ); - - // 6. Consume minted notes - console.log('Consuming minted notes...'); - const consumeTxRequest = client.newConsumeTransactionRequest(mintedNoteList); - - await client.submitNewTransaction(alice.id(), consumeTxRequest); - - await client.syncState(); - console.log('Notes consumed.'); - - // 7. Send tokens to Bob - const bobAccountId = Address.fromBech32( - 'mtst1apve54rq8ux0jqqqqrkh5y0r0y8cwza6_qruqqypuyph', - ).accountId(); - console.log("Sending tokens to Bob's account..."); - const sendTxRequest = client.newSendTransactionRequest( - alice.id(), - bobAccountId, - faucet.id(), - NoteType.Public, - BigInt(100), - ); - - await client.submitNewTransaction(alice.id(), sendTxRequest); - console.log('Tokens sent successfully!'); +// 5. Fetch minted notes +const mintedNotes = await client.getConsumableNotes(alice.id()); +const mintedNoteList = mintedNotes.map((n) => n.inputNoteRecord().toNote()); +console.log( +'Minted notes:', +mintedNoteList.map((note) => note.id().toString()), +); + +// 6. Consume minted notes +console.log('Consuming minted notes...'); +const consumeTxRequest = client.newConsumeTransactionRequest(mintedNoteList); + +await client.submitNewTransaction(alice.id(), consumeTxRequest); + +await client.syncState(); +console.log('Notes consumed.'); + +// 7. Send tokens to Bob +const bobAccountId = Address.fromBech32( +'mtst1apve54rq8ux0jqqqqrkh5y0r0y8cwza6_qruqqypuyph', +).accountId(); +console.log("Sending tokens to Bob's account..."); +const sendTxRequest = client.newSendTransactionRequest( +alice.id(), +bobAccountId, +faucet.id(), +NoteType.Public, +BigInt(100), +); + +await client.submitNewTransaction(alice.id(), sendTxRequest); +console.log('Tokens sent successfully!'); }` }, }} reactFilename="lib/react/createMintConsume.tsx" tsFilename="lib/createMintConsume.ts" /> diff --git a/docs/src/web-client/react_wallet_tutorial.md b/docs/src/web-client/react_wallet_tutorial.md index 4f4c2b2..39f5910 100644 --- a/docs/src/web-client/react_wallet_tutorial.md +++ b/docs/src/web-client/react_wallet_tutorial.md @@ -57,22 +57,22 @@ First, create a new Vite + React project and install the Miden React SDK. ```tsx // main.tsx -import React from "react"; -import ReactDOM from "react-dom/client"; -import { MidenProvider } from "@miden-sdk/react"; -import App from "./App"; +import React from 'react'; +import ReactDOM from 'react-dom/client'; +import { MidenProvider } from '@miden-sdk/react'; +import App from './App'; -ReactDOM.createRoot(document.getElementById("root")!).render( +ReactDOM.createRoot(document.getElementById('root')!).render( - + , ); ``` @@ -89,7 +89,7 @@ The `useMiden()` hook provides access to the client's initialization state. Use ```tsx // App.tsx -import { useMiden } from "@miden-sdk/react"; +import { useMiden } from '@miden-sdk/react'; export default function App() { const { isReady, error } = useMiden(); @@ -113,7 +113,7 @@ The `useMiden()` hook returns: The `useAccounts()` hook provides access to all accounts stored in the client. Use it to check if the user has any existing wallets. ```tsx -import { useMiden, useAccounts } from "@miden-sdk/react"; +import { useMiden, useAccounts } from '@miden-sdk/react'; export default function App() { const { isReady, error } = useMiden(); @@ -145,7 +145,7 @@ The `useAccounts()` hook returns: The `useCreateWallet()` hook provides a function to create new wallet accounts. ```tsx -import { useMiden, useAccounts, useCreateWallet } from "@miden-sdk/react"; +import { useMiden, useAccounts, useCreateWallet } from '@miden-sdk/react'; export default function App() { const { isReady, error } = useMiden(); @@ -162,7 +162,7 @@ export default function App() {

Wallet

); @@ -188,7 +188,7 @@ The `useCreateWallet()` hook returns: The `useAccount(accountId)` hook provides detailed information about a specific account, including its assets and balances. ```tsx -import { useAccount, formatAssetAmount } from "@miden-sdk/react"; +import { useAccount, formatAssetAmount } from '@miden-sdk/react'; function Wallet({ accountId }: { accountId: string }) { const { account, assets } = useAccount(accountId); @@ -199,7 +199,7 @@ function Wallet({ accountId }: { accountId: string }) {

Address

-
{account?.bech32id?.() ?? "Loading..."}
+
{account?.bech32id?.() ?? 'Loading...'}
@@ -237,7 +237,7 @@ The `formatAssetAmount(amount, decimals)` utility formats a raw amount with the The `useNotes({ accountId })` hook provides access to notes that can be consumed by the account. ```tsx -import { useNotes, formatNoteSummary } from "@miden-sdk/react"; +import { useNotes, formatNoteSummary } from '@miden-sdk/react'; function UnclaimedNotes({ accountId }: { accountId: string }) { const { consumableNoteSummaries } = useNotes({ accountId }); @@ -273,9 +273,12 @@ The `formatNoteSummary(summary)` utility formats a note summary for display. The `useConsume()` hook provides a function to consume (claim) notes and add their assets to the account. ```tsx -import { useConsume, formatNoteSummary } from "@miden-sdk/react"; +import { useConsume, formatNoteSummary } from '@miden-sdk/react'; -function UnclaimedNotes({ accountId, consumableNoteSummaries }: { +function UnclaimedNotes({ + accountId, + consumableNoteSummaries, +}: { accountId: string; consumableNoteSummaries: Array<{ id: string }>; }) { @@ -319,18 +322,21 @@ The `useConsume()` hook returns: The `useSend()` hook provides a function to send tokens to other accounts. ```tsx -import { useState, type ChangeEvent } from "react"; -import { useSend, parseAssetAmount } from "@miden-sdk/react"; +import { useState, type ChangeEvent } from 'react'; +import { useSend, parseAssetAmount } from '@miden-sdk/react'; -function SendForm({ accountId, assets }: { +function SendForm({ + accountId, + assets, +}: { accountId: string; assets: Array<{ assetId: string; symbol?: string; decimals?: number }>; }) { const { send, isLoading: isSending } = useSend(); - const [to, setTo] = useState(""); - const [assetId, setAssetId] = useState(assets[0]?.assetId ?? ""); - const [amount, setAmount] = useState(""); - const [noteType, setNoteType] = useState<"private" | "public">("private"); + const [to, setTo] = useState(''); + const [assetId, setAssetId] = useState(assets[0]?.assetId ?? ''); + const [amount, setAmount] = useState(''); + const [noteType, setNoteType] = useState<'private' | 'public'>('private'); const selectedAsset = assets.find((asset) => asset.assetId === assetId); const selectedDecimals = selectedAsset?.decimals; @@ -342,16 +348,20 @@ function SendForm({ accountId, assets }: { if (!assetId) return; const amt = parseAssetAmount(amount, selectedDecimals); await send({ from: accountId, to, assetId, amount: amt, noteType }); - setAmount(""); + setAmount(''); } catch (error) { console.error(error); } }; - const onAssetChange = (e: ChangeEvent) => setAssetId(e.target.value); - const onNoteTypeChange = (e: ChangeEvent) => setNoteType(e.target.value as "private" | "public"); - const onToChange = (e: ChangeEvent) => setTo(e.target.value); - const onAmountChange = (e: ChangeEvent) => setAmount(e.target.value); + const onAssetChange = (e: ChangeEvent) => + setAssetId(e.target.value); + const onNoteTypeChange = (e: ChangeEvent) => + setNoteType(e.target.value as 'private' | 'public'); + const onToChange = (e: ChangeEvent) => + setTo(e.target.value); + const onAmountChange = (e: ChangeEvent) => + setAmount(e.target.value); return (
@@ -371,10 +381,20 @@ function SendForm({ accountId, assets }: { )} - - + +
); @@ -405,31 +425,43 @@ Here is the complete wallet application combining all the features we've covered **main.tsx** ```tsx -import React from "react"; -import ReactDOM from "react-dom/client"; -import { MidenProvider } from "@miden-sdk/react"; -import App from "./App"; +import React from 'react'; +import ReactDOM from 'react-dom/client'; +import { MidenProvider } from '@miden-sdk/react'; +import App from './App'; -ReactDOM.createRoot(document.getElementById("root")!).render( +ReactDOM.createRoot(document.getElementById('root')!).render( - + , ); ``` **App.tsx** ```tsx -import { useEffect, useState, type ChangeEvent, type ReactNode } from "react"; -import { formatAssetAmount, formatNoteSummary, parseAssetAmount } from "@miden-sdk/react"; -import { useMiden, useAccounts, useAccount, useNotes, useCreateWallet, useConsume, useSend } from "@miden-sdk/react"; +import { useEffect, useState, type ChangeEvent, type ReactNode } from 'react'; +import { + formatAssetAmount, + formatNoteSummary, + parseAssetAmount, +} from '@miden-sdk/react'; +import { + useMiden, + useAccounts, + useAccount, + useNotes, + useCreateWallet, + useConsume, + useSend, +} from '@miden-sdk/react'; const Panel = ({ title, children }: { title: string; children: ReactNode }) => (
@@ -443,10 +475,15 @@ export default function App() { const { wallets, isLoading } = useAccounts(); const { createWallet, isCreating } = useCreateWallet(); const handleCreate = () => createWallet(); - const createLabel = isCreating ? "Creating..." : "Create wallet"; + const createLabel = isCreating ? 'Creating...' : 'Create wallet'; if (error) return
Error: {error.message}
; - if (!isReady || isLoading) return
{!isReady ? "Initializing..." : "Loading..."}
; + if (!isReady || isLoading) + return ( +
+ {!isReady ? 'Initializing...' : 'Loading...'} +
+ ); const accountId = wallets[0]?.id().toString(); if (!accountId) @@ -467,10 +504,10 @@ function Wallet({ accountId }: { accountId: string }) { const { consumableNoteSummaries } = useNotes({ accountId }); const { consume, isLoading: isConsuming } = useConsume(); const { send, isLoading: isSending } = useSend(); - const [to, setTo] = useState(""); - const [assetId, setAssetId] = useState(""); - const [amount, setAmount] = useState(""); - const [noteType, setNoteType] = useState<"private" | "public">("private"); + const [to, setTo] = useState(''); + const [assetId, setAssetId] = useState(''); + const [amount, setAmount] = useState(''); + const [noteType, setNoteType] = useState<'private' | 'public'>('private'); const defaultAssetId = assets[0]?.assetId; const selectedAsset = assets.find((asset) => asset.assetId === assetId); const selectedDecimals = selectedAsset?.decimals; @@ -485,25 +522,29 @@ function Wallet({ accountId }: { accountId: string }) { if (!assetId) return; const amt = parseAssetAmount(amount, selectedDecimals); await send({ from: accountId, to, assetId, amount: amt, noteType }); - setAmount(""); + setAmount(''); } catch (error) { console.error(error); } }; const claimNote = (id: string) => () => consume({ accountId, noteIds: [id] }); - const onAssetChange = (event: ChangeEvent) => setAssetId(event.target.value); - const onNoteTypeChange = (event: ChangeEvent) => setNoteType(event.target.value as "private" | "public"); - const onToChange = (event: ChangeEvent) => setTo(event.target.value); - const onAmountChange = (event: ChangeEvent) => setAmount(event.target.value); + const onAssetChange = (event: ChangeEvent) => + setAssetId(event.target.value); + const onNoteTypeChange = (event: ChangeEvent) => + setNoteType(event.target.value as 'private' | 'public'); + const onToChange = (event: ChangeEvent) => + setTo(event.target.value); + const onAmountChange = (event: ChangeEvent) => + setAmount(event.target.value); const canSend = Boolean(hasAssets && to && assetId && amount); - const sendLabel = isSending ? "Sending..." : "Send"; + const sendLabel = isSending ? 'Sending...' : 'Send'; return (

Wallet

-
{account?.bech32id?.() ?? "Loading..."}
+
{account?.bech32id?.() ?? 'Loading...'}
{assets.length === 0 ? ( @@ -545,7 +586,11 @@ function Wallet({ accountId }: { accountId: string }) { - {hasAssets ? ( assets.map((asset) => ( )} - - + + @@ -607,7 +662,7 @@ By default, the Miden React SDK manages keys internally using the browser's Inde The `useSigner()` hook from `@miden-sdk/react` provides a unified interface for interacting with any signer provider. When you wrap your app with a signer provider (Para, Turnkey, MidenFi, etc.), the hook returns the signer context with connection state and methods. ```tsx -import { useSigner } from "@miden-sdk/react"; +import { useSigner } from '@miden-sdk/react'; function ConnectButton() { const signer = useSigner(); @@ -649,13 +704,13 @@ yarn add use-miden-para-react **Usage:** ```tsx -import { ParaSignerProvider } from "use-miden-para-react"; -import { MidenProvider, useSigner } from "@miden-sdk/react"; +import { ParaSignerProvider } from 'use-miden-para-react'; +import { MidenProvider, useSigner } from '@miden-sdk/react'; function App() { return ( - + @@ -679,12 +734,12 @@ function Wallet() { **ParaSignerProvider Props:** -| Prop | Type | Description | -|------|------|-------------| -| `apiKey` | `string` | Your Para API key | -| `environment` | `"PRODUCTION" \| "DEVELOPMENT" \| "SANDBOX"` | Para environment | -| `showSigningModal` | `boolean` | Whether to show signing confirmation modal | -| `customSignConfirmStep` | `ReactNode` | Custom signing confirmation UI | +| Prop | Type | Description | +| ----------------------- | -------------------------------------------- | ------------------------------------------ | +| `apiKey` | `string` | Your Para API key | +| `environment` | `"PRODUCTION" \| "DEVELOPMENT" \| "SANDBOX"` | Para environment | +| `showSigningModal` | `boolean` | Whether to show signing confirmation modal | +| `customSignConfirmStep` | `ReactNode` | Custom signing confirmation UI | --- @@ -701,19 +756,22 @@ yarn add use-miden-turnkey-react @turnkey/sdk-browser **Usage:** ```tsx -import { TurnkeySignerProvider, useTurnkeySigner } from "use-miden-turnkey-react"; -import { MidenProvider, useSigner } from "@miden-sdk/react"; +import { + TurnkeySignerProvider, + useTurnkeySigner, +} from 'use-miden-turnkey-react'; +import { MidenProvider, useSigner } from '@miden-sdk/react'; const turnkeyConfig = { - apiBaseUrl: "https://api.turnkey.com", - organizationId: "your-org-id", + apiBaseUrl: 'https://api.turnkey.com', + organizationId: 'your-org-id', // Additional Turnkey SDK configuration (stamper, etc.) }; function App() { return ( - + @@ -746,8 +804,8 @@ function Wallet() { **TurnkeySignerProvider Props:** -| Prop | Type | Description | -|------|------|-------------| +| Prop | Type | Description | +| -------- | ------------------------ | ---------------------------------------------------------------------------- | | `config` | `TurnkeySDKClientConfig` | Turnkey SDK client configuration (apiBaseUrl, organizationId, stamper, etc.) | The `useTurnkeySigner()` hook provides Turnkey-specific extras like `setAccount()` and access to the `client` instance. @@ -767,13 +825,13 @@ yarn add @miden-wallet-adapter/react **Usage:** ```tsx -import { MidenFiSignerProvider } from "@miden-wallet-adapter/react"; -import { MidenProvider, useSigner } from "@miden-sdk/react"; +import { MidenFiSignerProvider } from '@miden-wallet-adapter/react'; +import { MidenProvider, useSigner } from '@miden-sdk/react'; function App() { return ( - + @@ -797,11 +855,11 @@ function Wallet() { **MidenFiSignerProvider Props:** -| Prop | Type | Description | -|------|------|-------------| -| `network` | `"Testnet" \| "Mainnet"` | Target network | -| `privateDataPermission` | `boolean` | Whether to request private data access | -| `allowedPrivateData` | `string[]` | List of allowed private data types | +| Prop | Type | Description | +| ----------------------- | ------------------------ | -------------------------------------- | +| `network` | `"Testnet" \| "Mainnet"` | Target network | +| `privateDataPermission` | `boolean` | Whether to request private data access | +| `allowedPrivateData` | `string[]` | List of allowed private data types | --- @@ -810,9 +868,9 @@ function Wallet() { If you need to integrate with a different signing service, you can build your own signer provider by implementing the `SignerContextValue` interface and providing it via `SignerContext.Provider`. ```tsx -import { useState, useCallback, type ReactNode } from "react"; -import { SignerContext, type SignerContextValue } from "@miden-sdk/react"; -import { AccountStorageMode } from "@miden-sdk/miden-sdk"; +import { useState, useCallback, type ReactNode } from 'react'; +import { SignerContext, type SignerContextValue } from '@miden-sdk/react'; +import { AccountStorageMode } from '@miden-sdk/miden-sdk'; interface CustomSignerProviderProps { children: ReactNode; @@ -821,11 +879,14 @@ interface CustomSignerProviderProps { export function CustomSignerProvider({ children }: CustomSignerProviderProps) { const [isConnected, setIsConnected] = useState(false); - const [signerContext, setSignerContext] = useState(null); + const [signerContext, setSignerContext] = useState( + null, + ); const connect = useCallback(async () => { // 1. Initialize your signing service and get credentials - const { publicKeyCommitment, signMessage } = await initializeYourSigningService(); + const { publicKeyCommitment, signMessage } = + await initializeYourSigningService(); // 2. Build the signer context const context: SignerContextValue = { @@ -835,11 +896,11 @@ export function CustomSignerProvider({ children }: CustomSignerProviderProps) { }, accountConfig: { publicKeyCommitment, - accountType: "RegularAccountImmutableCode", + accountType: 'RegularAccountImmutableCode', storageMode: AccountStorageMode.public(), }, - storeName: "custom_signer", - name: "CustomSigner", + storeName: 'custom_signer', + name: 'CustomSigner', isConnected: true, connect, disconnect, @@ -864,15 +925,15 @@ export function CustomSignerProvider({ children }: CustomSignerProviderProps) { The `SignerContextValue` interface requires: -| Field | Type | Description | -|-------|------|-------------| -| `signCb` | `(pubKey, signingInputs) => Promise` | Signs transaction inputs and returns the signature | -| `accountConfig` | `SignerAccountConfig` | Public key commitment, account type, and storage mode | -| `storeName` | `string` | Unique suffix for IndexedDB isolation (e.g., "custom_walletId") | -| `name` | `string` | Display name for UI (e.g., "CustomSigner") | -| `isConnected` | `boolean` | Whether the signer is connected and ready | -| `connect` | `() => Promise` | Triggers the authentication flow | -| `disconnect` | `() => Promise` | Disconnects from the signer | +| Field | Type | Description | +| --------------- | ------------------------------------------------ | --------------------------------------------------------------- | +| `signCb` | `(pubKey, signingInputs) => Promise` | Signs transaction inputs and returns the signature | +| `accountConfig` | `SignerAccountConfig` | Public key commitment, account type, and storage mode | +| `storeName` | `string` | Unique suffix for IndexedDB isolation (e.g., "custom_walletId") | +| `name` | `string` | Display name for UI (e.g., "CustomSigner") | +| `isConnected` | `boolean` | Whether the signer is connected and ready | +| `connect` | `() => Promise` | Triggers the authentication flow | +| `disconnect` | `() => Promise` | Disconnects from the signer | --- diff --git a/docs/src/web-client/unauthenticated_note_how_to.md b/docs/src/web-client/unauthenticated_note_how_to.md index d3edd12..0cb765f 100644 --- a/docs/src/web-client/unauthenticated_note_how_to.md +++ b/docs/src/web-client/unauthenticated_note_how_to.md @@ -159,9 +159,9 @@ mkdir -p lib Copy and paste the following code into `lib/react/unauthenticatedNoteTransfer.tsx` (React) or `lib/unauthenticatedNoteTransfer.ts` (TypeScript): -{/* prettier-ignore */} +{/_ prettier-ignore _/} .. .); -}` }, - typescript: { code: `/** - * Demonstrates unauthenticated note transfer chain using a local prover on the Miden Network - * Creates a chain of P2ID (Pay to ID) notes: Alice → wallet 1 → wallet 2 → wallet 3 → wallet 4 - * - * @throws {Error} If the function cannot be executed in a browser environment - */ -export async function unauthenticatedNoteTransfer(): Promise { +}`}, + typescript: { code:`/\*\* + +- Demonstrates unauthenticated note transfer chain using a local prover on the Miden Network +- Creates a chain of P2ID (Pay to ID) notes: Alice → wallet 1 → wallet 2 → wallet 3 → wallet 4 +- +- @throws {Error} If the function cannot be executed in a browser environment + \*/ + export async function unauthenticatedNoteTransfer(): Promise { // Ensure this runs only in a browser context if (typeof window === 'undefined') return console.warn('Run in browser'); - const { - WebClient, - AccountStorageMode, - AuthScheme, - NoteType, - TransactionProver, - Note, - NoteAssets, - OutputNoteArray, - FungibleAsset, - NoteAndArgsArray, - NoteAndArgs, - NoteAttachment, - TransactionRequestBuilder, - OutputNote, - } = await import('@miden-sdk/miden-sdk'); - - const client = await WebClient.createClient('https://rpc.testnet.miden.io'); - const prover = TransactionProver.newLocalProver(); - - console.log('Latest block:', (await client.syncState()).blockNum()); - - // ── Creating new account ────────────────────────────────────────────────────── - console.log('Creating accounts'); - - console.log('Creating account for Alice…'); - const alice = await client.newWallet( - AccountStorageMode.public(), - true, - AuthScheme.AuthRpoFalcon512, - ); - console.log('Alice accout ID:', alice.id().toString()); - - const wallets = []; - for (let i = 0; i < 5; i++) { - const wallet = await client.newWallet( - AccountStorageMode.public(), - true, - AuthScheme.AuthRpoFalcon512, - ); - wallets.push(wallet); - console.log('wallet ', i.toString(), wallet.id().toString()); - } - - // ── Creating new faucet ────────────────────────────────────────────────────── - const faucet = await client.newFaucet( - AccountStorageMode.public(), - false, - 'MID', - 8, - BigInt(1_000_000), - AuthScheme.AuthRpoFalcon512, - ); - console.log('Faucet ID:', faucet.id().toString()); - - // ── mint 10 000 MID to Alice ────────────────────────────────────────────────────── - { - const txResult = await client.executeTransaction( - faucet.id(), - client.newMintTransactionRequest( - alice.id(), - faucet.id(), - NoteType.Public, - BigInt(10_000), - ), - ); - const proven = await client.proveTransaction(txResult, prover); - const submissionHeight = await client.submitProvenTransaction( - proven, - txResult, - ); - await client.applyTransaction(txResult, submissionHeight); - } - - console.log('Waiting for settlement'); - await new Promise((r) => setTimeout(r, 7_000)); - await client.syncState(); +const { +WebClient, +AccountStorageMode, +AuthScheme, +NoteType, +TransactionProver, +Note, +NoteAssets, +OutputNoteArray, +FungibleAsset, +NoteAndArgsArray, +NoteAndArgs, +NoteAttachment, +TransactionRequestBuilder, +OutputNote, +} = await import('@miden-sdk/miden-sdk'); + +const client = await WebClient.createClient('https://rpc.testnet.miden.io'); +const prover = TransactionProver.newLocalProver(); + +console.log('Latest block:', (await client.syncState()).blockNum()); + +// ── Creating new account ────────────────────────────────────────────────────── +console.log('Creating accounts'); + +console.log('Creating account for Alice…'); +const alice = await client.newWallet( +AccountStorageMode.public(), +true, +AuthScheme.AuthRpoFalcon512, +); +console.log('Alice accout ID:', alice.id().toString()); + +const wallets = []; +for (let i = 0; i < 5; i++) { +const wallet = await client.newWallet( +AccountStorageMode.public(), +true, +AuthScheme.AuthRpoFalcon512, +); +wallets.push(wallet); +console.log('wallet ', i.toString(), wallet.id().toString()); +} - // ── Consume the freshly minted note ────────────────────────────────────────────── - const noteList = (await client.getConsumableNotes(alice.id())).map((rec) => - rec.inputNoteRecord().toNote(), - ); +// ── Creating new faucet ────────────────────────────────────────────────────── +const faucet = await client.newFaucet( +AccountStorageMode.public(), +false, +'MID', +8, +BigInt(1_000_000), +AuthScheme.AuthRpoFalcon512, +); +console.log('Faucet ID:', faucet.id().toString()); + +// ── mint 10 000 MID to Alice ────────────────────────────────────────────────────── +{ +const txResult = await client.executeTransaction( +faucet.id(), +client.newMintTransactionRequest( +alice.id(), +faucet.id(), +NoteType.Public, +BigInt(10_000), +), +); +const proven = await client.proveTransaction(txResult, prover); +const submissionHeight = await client.submitProvenTransaction( +proven, +txResult, +); +await client.applyTransaction(txResult, submissionHeight); +} - { - const txResult = await client.executeTransaction( - alice.id(), - client.newConsumeTransactionRequest(noteList), - ); - const proven = await client.proveTransaction(txResult, prover); - const submissionHeight = await client.submitProvenTransaction( - proven, - txResult, - ); - await client.applyTransaction(txResult, submissionHeight); - await client.syncState(); - } +console.log('Waiting for settlement'); +await new Promise((r) => setTimeout(r, 7_000)); +await client.syncState(); + +// ── Consume the freshly minted note ────────────────────────────────────────────── +const noteList = (await client.getConsumableNotes(alice.id())).map((rec) => +rec.inputNoteRecord().toNote(), +); + +{ +const txResult = await client.executeTransaction( +alice.id(), +client.newConsumeTransactionRequest(noteList), +); +const proven = await client.proveTransaction(txResult, prover); +const submissionHeight = await client.submitProvenTransaction( +proven, +txResult, +); +await client.applyTransaction(txResult, submissionHeight); +await client.syncState(); +} - // ── Create unauthenticated note transfer chain ───────────────────────────────────────────── - // Alice → wallet 1 → wallet 2 → wallet 3 → wallet 4 - for (let i = 0; i < wallets.length; i++) { - console.log(\`\\nUnauthenticated tx \${i + 1}\`); +// ── Create unauthenticated note transfer chain ───────────────────────────────────────────── +// Alice → wallet 1 → wallet 2 → wallet 3 → wallet 4 +for (let i = 0; i < wallets.length; i++) { +console.log(\`\\nUnauthenticated tx \${i + 1}\`); // Determine sender and receiver for this iteration const sender = i === 0 ? alice : wallets[i - 1]; @@ -430,9 +431,10 @@ export async function unauthenticatedNoteTransfer(): Promise { \`Consumed Note Tx on MidenScan: https://testnet.midenscan.com/tx/\${txId}\`, ); } - } - console.log('Asset transfer chain completed ✅'); +} + +console.log('Asset transfer chain completed ✅'); }` }, }} reactFilename="lib/react/unauthenticatedNoteTransfer.tsx" tsFilename="lib/unauthenticatedNoteTransfer.ts" /> From c459143d95ddfef09210f302d5e906bff66c95fc Mon Sep 17 00:00:00 2001 From: Wiktor Starczewski Date: Fri, 6 Feb 2026 14:52:38 +0400 Subject: [PATCH 09/18] fix: restore prettier-ignore comments mangled by prettier --- docs/src/web-client/create_deploy_tutorial.md | 8 ++++---- .../src/web-client/creating_multiple_notes_tutorial.md | 6 +++--- docs/src/web-client/mint_consume_create_tutorial.md | 10 +++++----- docs/src/web-client/unauthenticated_note_how_to.md | 2 +- 4 files changed, 13 insertions(+), 13 deletions(-) diff --git a/docs/src/web-client/create_deploy_tutorial.md b/docs/src/web-client/create_deploy_tutorial.md index 00a4670..5eec79f 100644 --- a/docs/src/web-client/create_deploy_tutorial.md +++ b/docs/src/web-client/create_deploy_tutorial.md @@ -83,7 +83,7 @@ First, we'll create a separate file for our blockchain logic. In the project roo mkdir -p lib ``` -{/_ prettier-ignore _/} +{/* prettier-ignore */} { .// 1. Create Alice's wallet (public, mutable) @@ -249,7 +249,7 @@ A faucet in Miden is a special type of account that can mint new tokens. Think o Add this code after creating Alice's account: -{/_ prettier-ignore _/} +{/* prettier-ignore */} note.id().toString()), Now let's consume the notes to add the tokens to Alice's account balance: -{/_ prettier-ignore _/} +{/* prettier-ignore */} Date: Fri, 6 Feb 2026 17:19:01 +0400 Subject: [PATCH 10/18] fix: remove prettier-ignore comments that get mangled --- docs/src/web-client/create_deploy_tutorial.md | 4 ---- docs/src/web-client/creating_multiple_notes_tutorial.md | 3 --- docs/src/web-client/mint_consume_create_tutorial.md | 5 ----- docs/src/web-client/unauthenticated_note_how_to.md | 1 - 4 files changed, 13 deletions(-) diff --git a/docs/src/web-client/create_deploy_tutorial.md b/docs/src/web-client/create_deploy_tutorial.md index 5eec79f..3e0a6d6 100644 --- a/docs/src/web-client/create_deploy_tutorial.md +++ b/docs/src/web-client/create_deploy_tutorial.md @@ -83,7 +83,6 @@ First, we'll create a separate file for our blockchain logic. In the project roo mkdir -p lib ``` -{/* prettier-ignore */} { .// 1. Create Alice's wallet (public, mutable) @@ -249,7 +247,6 @@ A faucet in Miden is a special type of account that can mint new tokens. Think o Add this code after creating Alice's account: -{/* prettier-ignore */} note.id().toString()), Now let's consume the notes to add the tokens to Alice's account balance: -{/* prettier-ignore */} Date: Mon, 9 Feb 2026 22:13:22 +0100 Subject: [PATCH 11/18] docs: update signer package names and Turnkey connect flow --- docs/src/web-client/react_wallet_tutorial.md | 47 +++++++------------- 1 file changed, 17 insertions(+), 30 deletions(-) diff --git a/docs/src/web-client/react_wallet_tutorial.md b/docs/src/web-client/react_wallet_tutorial.md index 39f5910..a7a17f9 100644 --- a/docs/src/web-client/react_wallet_tutorial.md +++ b/docs/src/web-client/react_wallet_tutorial.md @@ -698,13 +698,13 @@ This unified interface means your wallet UI code works the same regardless of wh **Installation:** ```bash -yarn add use-miden-para-react +yarn add @miden-sdk/use-miden-para-react ``` **Usage:** ```tsx -import { ParaSignerProvider } from 'use-miden-para-react'; +import { ParaSignerProvider } from '@miden-sdk/use-miden-para-react'; import { MidenProvider, useSigner } from '@miden-sdk/react'; function App() { @@ -750,27 +750,21 @@ function Wallet() { **Installation:** ```bash -yarn add use-miden-turnkey-react @turnkey/sdk-browser +yarn add @miden-sdk/miden-turnkey-react @turnkey/sdk-browser ``` **Usage:** ```tsx -import { - TurnkeySignerProvider, - useTurnkeySigner, -} from 'use-miden-turnkey-react'; +import { TurnkeySignerProvider } from '@miden-sdk/miden-turnkey-react'; import { MidenProvider, useSigner } from '@miden-sdk/react'; -const turnkeyConfig = { - apiBaseUrl: 'https://api.turnkey.com', - organizationId: 'your-org-id', - // Additional Turnkey SDK configuration (stamper, etc.) -}; - function App() { return ( - + @@ -780,35 +774,28 @@ function App() { function Wallet() { const signer = useSigner(); - // Use useTurnkeySigner() only when you need Turnkey-specific features - const { setAccount } = useTurnkeySigner(); - - const handleLogin = async () => { - // Your custom authentication logic (passkey, email, etc.) - const walletAccount = await authenticateUser(); - // Register the account with Turnkey signer - setAccount(walletAccount); - }; return (
{signer?.isConnected ? ( ) : ( - + )}
); } ``` +Calling `connect()` handles the full Turnkey authentication flow: passkey login, wallet discovery, and account selection. No manual setup is needed. + **TurnkeySignerProvider Props:** -| Prop | Type | Description | -| -------- | ------------------------ | ---------------------------------------------------------------------------- | -| `config` | `TurnkeySDKClientConfig` | Turnkey SDK client configuration (apiBaseUrl, organizationId, stamper, etc.) | +| Prop | Type | Description | +| -------- | ------------------------ | ---------------------------------------------------------------------------------- | +| `config` | `TurnkeySDKClientConfig` | Turnkey SDK client configuration (apiBaseUrl, defaultOrganizationId) | -The `useTurnkeySigner()` hook provides Turnkey-specific extras like `setAccount()` and access to the `client` instance. +The `useTurnkeySigner()` hook is available for advanced use cases where you need direct access to the Turnkey `client`, the selected `account`, or the `setAccount()` method to manually control account selection. --- @@ -819,13 +806,13 @@ The `useTurnkeySigner()` hook provides Turnkey-specific extras like `setAccount( **Installation:** ```bash -yarn add @miden-wallet-adapter/react +yarn add @miden-sdk/miden-wallet-adapter-react ``` **Usage:** ```tsx -import { MidenFiSignerProvider } from '@miden-wallet-adapter/react'; +import { MidenFiSignerProvider } from '@miden-sdk/miden-wallet-adapter-react'; import { MidenProvider, useSigner } from '@miden-sdk/react'; function App() { From 59975a39de1b780b05ba52ce3fe4afe113c7daf0 Mon Sep 17 00:00:00 2001 From: Wiktor Starczewski Date: Mon, 9 Feb 2026 22:21:15 +0100 Subject: [PATCH 12/18] style: format markdown with prettier --- docs/src/web-client/react_wallet_tutorial.md | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/docs/src/web-client/react_wallet_tutorial.md b/docs/src/web-client/react_wallet_tutorial.md index a7a17f9..731ec5f 100644 --- a/docs/src/web-client/react_wallet_tutorial.md +++ b/docs/src/web-client/react_wallet_tutorial.md @@ -761,10 +761,12 @@ import { MidenProvider, useSigner } from '@miden-sdk/react'; function App() { return ( - + @@ -791,8 +793,8 @@ Calling `connect()` handles the full Turnkey authentication flow: passkey login, **TurnkeySignerProvider Props:** -| Prop | Type | Description | -| -------- | ------------------------ | ---------------------------------------------------------------------------------- | +| Prop | Type | Description | +| -------- | ------------------------ | -------------------------------------------------------------------- | | `config` | `TurnkeySDKClientConfig` | Turnkey SDK client configuration (apiBaseUrl, defaultOrganizationId) | The `useTurnkeySigner()` hook is available for advanced use cases where you need direct access to the Turnkey `client`, the selected `account`, or the `setAccount()` method to manually control account selection. From 068395af6c02b6f2ae0b77f2b62c0bb24f4ce5aa Mon Sep 17 00:00:00 2001 From: Wiktor Starczewski Date: Mon, 9 Feb 2026 23:37:38 +0100 Subject: [PATCH 13/18] docs: update TurnkeySignerProvider usage to use defaults --- docs/src/web-client/react_wallet_tutorial.md | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/docs/src/web-client/react_wallet_tutorial.md b/docs/src/web-client/react_wallet_tutorial.md index 731ec5f..cf8693d 100644 --- a/docs/src/web-client/react_wallet_tutorial.md +++ b/docs/src/web-client/react_wallet_tutorial.md @@ -761,12 +761,7 @@ import { MidenProvider, useSigner } from '@miden-sdk/react'; function App() { return ( - + @@ -795,7 +790,7 @@ Calling `connect()` handles the full Turnkey authentication flow: passkey login, | Prop | Type | Description | | -------- | ------------------------ | -------------------------------------------------------------------- | -| `config` | `TurnkeySDKClientConfig` | Turnkey SDK client configuration (apiBaseUrl, defaultOrganizationId) | +| `config` | `Partial` | Optional. Defaults to `apiBaseUrl: "https://api.turnkey.com"` and `defaultOrganizationId` from `VITE_TURNKEY_ORG_ID` env var. | The `useTurnkeySigner()` hook is available for advanced use cases where you need direct access to the Turnkey `client`, the selected `account`, or the `setAccount()` method to manually control account selection. From 97c8fcbf4e1cfe9fd11c2f872c1749bc0c740c43 Mon Sep 17 00:00:00 2001 From: Wiktor Starczewski Date: Tue, 10 Feb 2026 00:42:27 +0100 Subject: [PATCH 14/18] style: fix markdown formatting and add CLAUDE.md --- CLAUDE.md | 19 +++++++++++++++++++ docs/src/web-client/react_wallet_tutorial.md | 4 ++-- 2 files changed, 21 insertions(+), 2 deletions(-) create mode 100644 CLAUDE.md diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..547434f --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,19 @@ +# Miden Tutorials + +## Markdown Formatting + +This repo has a CI check that enforces markdown formatting via Prettier. + +After making any changes to `.md` files, run: + +```sh +npx prettier --check "**/*.md" +``` + +If it fails, fix with: + +```sh +npx prettier --write "**/*.md" +``` + +The Prettier config is in `.prettierrc`. Files listed in `.prettierignore` are excluded from formatting. diff --git a/docs/src/web-client/react_wallet_tutorial.md b/docs/src/web-client/react_wallet_tutorial.md index cf8693d..8810075 100644 --- a/docs/src/web-client/react_wallet_tutorial.md +++ b/docs/src/web-client/react_wallet_tutorial.md @@ -788,8 +788,8 @@ Calling `connect()` handles the full Turnkey authentication flow: passkey login, **TurnkeySignerProvider Props:** -| Prop | Type | Description | -| -------- | ------------------------ | -------------------------------------------------------------------- | +| Prop | Type | Description | +| -------- | ---------------------------------- | ----------------------------------------------------------------------------------------------------------------------------- | | `config` | `Partial` | Optional. Defaults to `apiBaseUrl: "https://api.turnkey.com"` and `defaultOrganizationId` from `VITE_TURNKEY_ORG_ID` env var. | The `useTurnkeySigner()` hook is available for advanced use cases where you need direct access to the Turnkey `client`, the selected `account`, or the `setAccount()` method to manually control account selection. From 15359a043a1ff1382ab3441657cdbdd5a06f82da Mon Sep 17 00:00:00 2001 From: Wiktor Starczewski Date: Mon, 16 Feb 2026 17:39:15 +0100 Subject: [PATCH 15/18] switch tutorials from devnet to testnet --- docs/src/web-client/create_deploy_tutorial.md | 4 ++-- .../web-client/creating_multiple_notes_tutorial.md | 4 ++-- docs/src/web-client/mint_consume_create_tutorial.md | 2 +- docs/src/web-client/react_wallet_tutorial.md | 12 ++++++------ docs/src/web-client/unauthenticated_note_how_to.md | 2 +- rust-client/src/bin/counter_contract_deploy.rs | 2 +- rust-client/src/bin/counter_contract_fpi.rs | 2 +- rust-client/src/bin/counter_contract_increment.rs | 2 +- rust-client/src/bin/create_mint_consume_send.rs | 2 +- rust-client/src/bin/delegated_prover.rs | 2 +- rust-client/src/bin/hash_preimage_note.rs | 2 +- rust-client/src/bin/mapping_example.rs | 2 +- .../src/bin/network_notes_counter_contract.rs | 2 +- rust-client/src/bin/note_creation_in_masm.rs | 2 +- rust-client/src/bin/oracle_data_query.rs | 2 +- rust-client/src/bin/unauthenticated_note_transfer.rs | 2 +- web-client/lib/createMintConsume.ts | 2 +- web-client/lib/foreignProcedureInvocation.ts | 2 +- web-client/lib/incrementCounterContract.ts | 2 +- ...intDevnetToAddress.ts => mintTestnetToAddress.ts} | 8 ++++---- web-client/lib/multiSendWithDelegatedProver.ts | 2 +- web-client/lib/react/createMintConsume.tsx | 2 +- .../lib/react/multiSendWithDelegatedProver.tsx | 2 +- web-client/lib/react/unauthenticatedNoteTransfer.tsx | 2 +- web-client/lib/unauthenticatedNoteTransfer.ts | 2 +- 25 files changed, 35 insertions(+), 35 deletions(-) rename web-client/lib/{mintDevnetToAddress.ts => mintTestnetToAddress.ts} (88%) diff --git a/docs/src/web-client/create_deploy_tutorial.md b/docs/src/web-client/create_deploy_tutorial.md index 3e0a6d6..d9f95aa 100644 --- a/docs/src/web-client/create_deploy_tutorial.md +++ b/docs/src/web-client/create_deploy_tutorial.md @@ -110,7 +110,7 @@ function CreateMintConsumeInner() { export default function CreateMintConsume() { .return ( -.. +.. ... .. .); @@ -336,7 +336,7 @@ function CreateMintConsumeInner() { export default function CreateMintConsume() { .return ( -.. +.. ... .. .); diff --git a/docs/src/web-client/creating_multiple_notes_tutorial.md b/docs/src/web-client/creating_multiple_notes_tutorial.md index 14a9fb6..76d8c59 100644 --- a/docs/src/web-client/creating_multiple_notes_tutorial.md +++ b/docs/src/web-client/creating_multiple_notes_tutorial.md @@ -169,7 +169,7 @@ function MultiSendInner() { export default function MultiSendWithDelegatedProver() { .return ( -.. +.. ... .. .); @@ -429,7 +429,7 @@ function MultiSendInner() { export default function MultiSendWithDelegatedProver() { .return ( -.. +.. ... .. .); diff --git a/docs/src/web-client/mint_consume_create_tutorial.md b/docs/src/web-client/mint_consume_create_tutorial.md index 8c2f43a..04163f8 100644 --- a/docs/src/web-client/mint_consume_create_tutorial.md +++ b/docs/src/web-client/mint_consume_create_tutorial.md @@ -248,7 +248,7 @@ function CreateMintConsumeInner() { export default function CreateMintConsume() { .return ( -.. +.. ... .. .); diff --git a/docs/src/web-client/react_wallet_tutorial.md b/docs/src/web-client/react_wallet_tutorial.md index 8810075..24bfc0b 100644 --- a/docs/src/web-client/react_wallet_tutorial.md +++ b/docs/src/web-client/react_wallet_tutorial.md @@ -66,8 +66,8 @@ ReactDOM.createRoot(document.getElementById('root')!).render( @@ -78,8 +78,8 @@ ReactDOM.createRoot(document.getElementById('root')!).render( The `MidenProvider` accepts a `config` object with the following options: -- `rpcUrl`: The RPC endpoint to connect to (`"devnet"`, `"testnet"`, or a custom URL) -- `prover`: The prover to use (`"devnet"` for delegated proving, or `"local"` for local proving) +- `rpcUrl`: The RPC endpoint to connect to (`"testnet"`, `"devnet"`, or a custom URL) +- `prover`: The prover to use (`"testnet"` for delegated proving, or `"local"` for local proving) --- @@ -434,8 +434,8 @@ ReactDOM.createRoot(document.getElementById('root')!).render( diff --git a/docs/src/web-client/unauthenticated_note_how_to.md b/docs/src/web-client/unauthenticated_note_how_to.md index baf216f..ff5a4e1 100644 --- a/docs/src/web-client/unauthenticated_note_how_to.md +++ b/docs/src/web-client/unauthenticated_note_how_to.md @@ -245,7 +245,7 @@ function UnauthenticatedNoteTransferInner() { export default function UnauthenticatedNoteTransfer() { .return ( -.. +.. ... .. .); diff --git a/rust-client/src/bin/counter_contract_deploy.rs b/rust-client/src/bin/counter_contract_deploy.rs index 73d6556..012ee03 100644 --- a/rust-client/src/bin/counter_contract_deploy.rs +++ b/rust-client/src/bin/counter_contract_deploy.rs @@ -39,7 +39,7 @@ fn create_library( #[tokio::main] async fn main() -> Result<(), ClientError> { // Initialize client - let endpoint = Endpoint::devnet(); + let endpoint = Endpoint::testnet(); let timeout_ms = 10_000; let rpc_client = Arc::new(GrpcClient::new(&endpoint, timeout_ms)); diff --git a/rust-client/src/bin/counter_contract_fpi.rs b/rust-client/src/bin/counter_contract_fpi.rs index 8fa6967..34e26a5 100644 --- a/rust-client/src/bin/counter_contract_fpi.rs +++ b/rust-client/src/bin/counter_contract_fpi.rs @@ -39,7 +39,7 @@ fn create_library( #[tokio::main] async fn main() -> Result<(), ClientError> { // Initialize client - let endpoint = Endpoint::devnet(); + let endpoint = Endpoint::testnet(); let timeout_ms = 10_000; let rpc_client = Arc::new(GrpcClient::new(&endpoint, timeout_ms)); diff --git a/rust-client/src/bin/counter_contract_increment.rs b/rust-client/src/bin/counter_contract_increment.rs index 732fdf6..5fbd799 100644 --- a/rust-client/src/bin/counter_contract_increment.rs +++ b/rust-client/src/bin/counter_contract_increment.rs @@ -32,7 +32,7 @@ fn create_library( #[tokio::main] async fn main() -> Result<(), ClientError> { // Initialize client - let endpoint = Endpoint::devnet(); + let endpoint = Endpoint::testnet(); let timeout_ms = 10_000; let rpc_client = Arc::new(GrpcClient::new(&endpoint, timeout_ms)); diff --git a/rust-client/src/bin/create_mint_consume_send.rs b/rust-client/src/bin/create_mint_consume_send.rs index 5995673..270d0d4 100644 --- a/rust-client/src/bin/create_mint_consume_send.rs +++ b/rust-client/src/bin/create_mint_consume_send.rs @@ -23,7 +23,7 @@ use miden_protocol::account::AccountIdVersion; #[tokio::main] async fn main() -> Result<(), ClientError> { // Initialize client - let endpoint = Endpoint::devnet(); + let endpoint = Endpoint::testnet(); let timeout_ms = 10_000; let rpc_client = Arc::new(GrpcClient::new(&endpoint, timeout_ms)); diff --git a/rust-client/src/bin/delegated_prover.rs b/rust-client/src/bin/delegated_prover.rs index 8366636..43163f9 100644 --- a/rust-client/src/bin/delegated_prover.rs +++ b/rust-client/src/bin/delegated_prover.rs @@ -18,7 +18,7 @@ use miden_client_sqlite_store::ClientBuilderSqliteExt; #[tokio::main] async fn main() -> Result<(), ClientError> { // Initialize client - let endpoint = Endpoint::devnet(); + let endpoint = Endpoint::testnet(); let timeout_ms = 10_000; let rpc_client = Arc::new(GrpcClient::new(&endpoint, timeout_ms)); diff --git a/rust-client/src/bin/hash_preimage_note.rs b/rust-client/src/bin/hash_preimage_note.rs index 6039426..3d52b2b 100644 --- a/rust-client/src/bin/hash_preimage_note.rs +++ b/rust-client/src/bin/hash_preimage_note.rs @@ -107,7 +107,7 @@ async fn wait_for_tx( #[tokio::main] async fn main() -> Result<(), ClientError> { // Initialize client - let endpoint = Endpoint::devnet(); + let endpoint = Endpoint::testnet(); let timeout_ms = 10_000; let rpc_client = Arc::new(GrpcClient::new(&endpoint, timeout_ms)); diff --git a/rust-client/src/bin/mapping_example.rs b/rust-client/src/bin/mapping_example.rs index 6523ba2..d0ebc98 100644 --- a/rust-client/src/bin/mapping_example.rs +++ b/rust-client/src/bin/mapping_example.rs @@ -38,7 +38,7 @@ fn create_library( #[tokio::main] async fn main() -> Result<(), ClientError> { // Initialize client - let endpoint = Endpoint::devnet(); + let endpoint = Endpoint::testnet(); let timeout_ms = 10_000; let rpc_client = Arc::new(GrpcClient::new(&endpoint, timeout_ms)); diff --git a/rust-client/src/bin/network_notes_counter_contract.rs b/rust-client/src/bin/network_notes_counter_contract.rs index c18ebc2..669765c 100644 --- a/rust-client/src/bin/network_notes_counter_contract.rs +++ b/rust-client/src/bin/network_notes_counter_contract.rs @@ -80,7 +80,7 @@ fn create_library( #[tokio::main] async fn main() -> Result<(), Box> { // Initialize client - let endpoint = Endpoint::devnet(); + let endpoint = Endpoint::testnet(); let timeout_ms = 10_000; let rpc_client = Arc::new(GrpcClient::new(&endpoint, timeout_ms)); diff --git a/rust-client/src/bin/note_creation_in_masm.rs b/rust-client/src/bin/note_creation_in_masm.rs index 6816061..0b705d6 100644 --- a/rust-client/src/bin/note_creation_in_masm.rs +++ b/rust-client/src/bin/note_creation_in_masm.rs @@ -97,7 +97,7 @@ async fn wait_for_notes( #[tokio::main] async fn main() -> Result<(), ClientError> { // Initialize client - let endpoint = Endpoint::devnet(); + let endpoint = Endpoint::testnet(); let timeout_ms = 10_000; let rpc_client = Arc::new(GrpcClient::new(&endpoint, timeout_ms)); diff --git a/rust-client/src/bin/oracle_data_query.rs b/rust-client/src/bin/oracle_data_query.rs index e45598f..7b3688d 100644 --- a/rust-client/src/bin/oracle_data_query.rs +++ b/rust-client/src/bin/oracle_data_query.rs @@ -138,7 +138,7 @@ async fn main() -> Result<(), ClientError> { // ------------------------------------------------------------------------- // Initialize Client // ------------------------------------------------------------------------- - let endpoint = Endpoint::devnet(); + let endpoint = Endpoint::testnet(); let timeout_ms = 10_000; let rpc_client = Arc::new(GrpcClient::new(&endpoint, timeout_ms)); diff --git a/rust-client/src/bin/unauthenticated_note_transfer.rs b/rust-client/src/bin/unauthenticated_note_transfer.rs index dc2d0d4..5e64609 100644 --- a/rust-client/src/bin/unauthenticated_note_transfer.rs +++ b/rust-client/src/bin/unauthenticated_note_transfer.rs @@ -56,7 +56,7 @@ async fn wait_for_tx( #[tokio::main] async fn main() -> Result<(), ClientError> { // Initialize client - let endpoint = Endpoint::devnet(); + let endpoint = Endpoint::testnet(); let timeout_ms = 10_000; let rpc_client = Arc::new(GrpcClient::new(&endpoint, timeout_ms)); diff --git a/web-client/lib/createMintConsume.ts b/web-client/lib/createMintConsume.ts index 75c9123..a7612fd 100644 --- a/web-client/lib/createMintConsume.ts +++ b/web-client/lib/createMintConsume.ts @@ -14,7 +14,7 @@ export async function createMintConsume(): Promise { Address, } = await import('@miden-sdk/miden-sdk'); - const nodeEndpoint = 'https://rpc.devnet.miden.io'; + const nodeEndpoint = 'https://rpc.testnet.miden.io'; const client = await WebClient.createClient(nodeEndpoint); // 1. Sync with the latest blockchain state diff --git a/web-client/lib/foreignProcedureInvocation.ts b/web-client/lib/foreignProcedureInvocation.ts index 91b0efe..79c2eda 100644 --- a/web-client/lib/foreignProcedureInvocation.ts +++ b/web-client/lib/foreignProcedureInvocation.ts @@ -21,7 +21,7 @@ export async function foreignProcedureInvocation(): Promise { AccountStorageMode, } = await import('@miden-sdk/miden-sdk'); - const nodeEndpoint = 'https://rpc.devnet.miden.io'; + const nodeEndpoint = 'https://rpc.testnet.miden.io'; const client = await WebClient.createClient(nodeEndpoint); console.log('Current block number: ', (await client.syncState()).blockNum()); diff --git a/web-client/lib/incrementCounterContract.ts b/web-client/lib/incrementCounterContract.ts index 4cfb648..1ed7b7e 100644 --- a/web-client/lib/incrementCounterContract.ts +++ b/web-client/lib/incrementCounterContract.ts @@ -18,7 +18,7 @@ export async function incrementCounterContract(): Promise { WebClient, } = await import('@miden-sdk/miden-sdk'); - const nodeEndpoint = 'https://rpc.devnet.miden.io'; + const nodeEndpoint = 'https://rpc.testnet.miden.io'; const client = await WebClient.createClient(nodeEndpoint); console.log('Current block number: ', (await client.syncState()).blockNum()); diff --git a/web-client/lib/mintDevnetToAddress.ts b/web-client/lib/mintTestnetToAddress.ts similarity index 88% rename from web-client/lib/mintDevnetToAddress.ts rename to web-client/lib/mintTestnetToAddress.ts index 060ee1c..6ed1823 100644 --- a/web-client/lib/mintDevnetToAddress.ts +++ b/web-client/lib/mintTestnetToAddress.ts @@ -1,7 +1,7 @@ /** - * Mint 100 MIDEN tokens on devnet to a fixed recipient using a local prover. + * Mint 100 MIDEN tokens on testnet to a fixed recipient using a local prover. */ -export async function mintDevnetToAddress(): Promise { +export async function mintTestnetToAddress(): Promise { if (typeof window === 'undefined') { console.warn('Run in browser'); return; @@ -16,7 +16,7 @@ export async function mintDevnetToAddress(): Promise { TransactionProver, } = await import('@miden-sdk/miden-sdk'); - const client = await WebClient.createClient('https://rpc.devnet.miden.io'); + const client = await WebClient.createClient('https://rpc.testnet.miden.io'); const prover = TransactionProver.newLocalProver(); console.log('Latest block:', (await client.syncState()).blockNum()); @@ -36,7 +36,7 @@ export async function mintDevnetToAddress(): Promise { // ── Mint to recipient ─────────────────────────────────────────────────────── const recipientAddress = - 'mdev1arey468fhgnhvyzdfk3suqavhccmp6cu_qruqqypuyph'; + 'mtst1apve54rq8ux0jqqqqrkh5y0r0y8cwza6_qruqqypuyph'; const recipientAccountId = Address.fromBech32(recipientAddress).accountId(); console.log('Recipient account ID:', recipientAccountId.toString()); diff --git a/web-client/lib/multiSendWithDelegatedProver.ts b/web-client/lib/multiSendWithDelegatedProver.ts index 1f28a2d..ef7c019 100644 --- a/web-client/lib/multiSendWithDelegatedProver.ts +++ b/web-client/lib/multiSendWithDelegatedProver.ts @@ -24,7 +24,7 @@ export async function multiSendWithDelegatedProver(): Promise { OutputNote, } = await import('@miden-sdk/miden-sdk'); - const client = await WebClient.createClient('https://rpc.devnet.miden.io'); + const client = await WebClient.createClient('https://rpc.testnet.miden.io'); const prover = TransactionProver.newLocalProver(); console.log('Latest block:', (await client.syncState()).blockNum()); diff --git a/web-client/lib/react/createMintConsume.tsx b/web-client/lib/react/createMintConsume.tsx index e99beac..aec8a39 100644 --- a/web-client/lib/react/createMintConsume.tsx +++ b/web-client/lib/react/createMintConsume.tsx @@ -77,7 +77,7 @@ function CreateMintConsumeInner() { export default function CreateMintConsume() { return ( - + ); diff --git a/web-client/lib/react/multiSendWithDelegatedProver.tsx b/web-client/lib/react/multiSendWithDelegatedProver.tsx index f6bdb66..98172e2 100644 --- a/web-client/lib/react/multiSendWithDelegatedProver.tsx +++ b/web-client/lib/react/multiSendWithDelegatedProver.tsx @@ -71,7 +71,7 @@ function MultiSendInner() { export default function MultiSendWithDelegatedProver() { return ( - + ); diff --git a/web-client/lib/react/unauthenticatedNoteTransfer.tsx b/web-client/lib/react/unauthenticatedNoteTransfer.tsx index 4896e62..a0c5f51 100644 --- a/web-client/lib/react/unauthenticatedNoteTransfer.tsx +++ b/web-client/lib/react/unauthenticatedNoteTransfer.tsx @@ -83,7 +83,7 @@ function UnauthenticatedNoteTransferInner() { export default function UnauthenticatedNoteTransfer() { return ( - + ); diff --git a/web-client/lib/unauthenticatedNoteTransfer.ts b/web-client/lib/unauthenticatedNoteTransfer.ts index 4f71ae1..d63d915 100644 --- a/web-client/lib/unauthenticatedNoteTransfer.ts +++ b/web-client/lib/unauthenticatedNoteTransfer.ts @@ -25,7 +25,7 @@ export async function unauthenticatedNoteTransfer(): Promise { OutputNote, } = await import('@miden-sdk/miden-sdk'); - const client = await WebClient.createClient('https://rpc.devnet.miden.io'); + const client = await WebClient.createClient('https://rpc.testnet.miden.io'); const prover = TransactionProver.newLocalProver(); console.log('Latest block:', (await client.syncState()).blockNum()); From 6e85ae677c39568b203b42650c8008b280864b38 Mon Sep 17 00:00:00 2001 From: Wiktor Starczewski Date: Wed, 18 Feb 2026 18:36:35 +0100 Subject: [PATCH 16/18] fix: address PR #159 review feedback - Bump @miden-sdk/react to 0.13.2 (useSigner/SignerContext support) - Fix multiSendWithDelegatedProver to use delegated prover (testnet) - Add dot-based indentation to all TypeScript snippets in CodeSdkTabs - Document dot-indentation convention in CodeSdkTabs.tsx and CONTRIBUTING.md - Add doc-only comments to React example components in lib/react/ - Remove CLAUDE.md and Migrations.Client.md from tracking - Fix "accout" typo in console.log messages --- CLAUDE.md | 19 - Migrations.Client.md | 904 ------------------ contributing.md | 60 +- docs/src/components/CodeSdkTabs.tsx | 29 +- docs/src/web-client/create_deploy_tutorial.md | 156 +-- .../creating_multiple_notes_tutorial.md | 405 ++++---- .../mint_consume_create_tutorial.md | 190 ++-- .../web-client/unauthenticated_note_how_to.md | 353 ++++--- .../lib/multiSendWithDelegatedProver.ts | 2 +- web-client/lib/react/createMintConsume.tsx | 4 + .../react/multiSendWithDelegatedProver.tsx | 6 +- .../lib/react/unauthenticatedNoteTransfer.tsx | 4 + web-client/lib/unauthenticatedNoteTransfer.ts | 2 +- web-client/package.json | 2 +- 14 files changed, 648 insertions(+), 1488 deletions(-) delete mode 100644 CLAUDE.md delete mode 100644 Migrations.Client.md diff --git a/CLAUDE.md b/CLAUDE.md deleted file mode 100644 index 547434f..0000000 --- a/CLAUDE.md +++ /dev/null @@ -1,19 +0,0 @@ -# Miden Tutorials - -## Markdown Formatting - -This repo has a CI check that enforces markdown formatting via Prettier. - -After making any changes to `.md` files, run: - -```sh -npx prettier --check "**/*.md" -``` - -If it fails, fix with: - -```sh -npx prettier --write "**/*.md" -``` - -The Prettier config is in `.prettierrc`. Files listed in `.prettierignore` are excluded from formatting. diff --git a/Migrations.Client.md b/Migrations.Client.md deleted file mode 100644 index 19b48c6..0000000 --- a/Migrations.Client.md +++ /dev/null @@ -1,904 +0,0 @@ -# Miden Client Migration Guide - -This document provides migration guidance for breaking changes introduced in new versions of the Miden Client. -Use this guide to migrate from 0.12.x to 0.13.0; it covers 0.13.0 breaking changes only. - -## Version 0.13.0 - -0.13.0 aligns the SDK with protocol 0.13, refactors note and transaction APIs, and tightens web bindings and storage schemas. Expect updates in input note handling, storage slot naming, auth/key APIs, and web client data shapes. - -## Table of Contents - -- [Input notes API unified](#input-notes-api-unified) -- [Account components and storage slots now use named slots](#account-components-and-storage-slots-now-use-named-slots) -- [Authentication and key management updates](#authentication-and-key-management-updates) -- [NodeRpcClient account proof API changed](#noderpcclient-account-proof-api-changed) -- [FetchedNote and RPC note shapes refactored](#fetchednote-and-rpc-note-shapes-refactored) -- [Protocol 0.13 note metadata tags and attachments](#protocol-013-note-metadata-tags-and-attachments) -- [NoteScreener relevance replaced by NoteConsumptionStatus](#notescreener-relevance-replaced-by-noteconsumptionstatus) -- [WebClient IndexedDB naming for multiple instances](#webclient-indexeddb-naming-for-multiple-instances) -- [Block numbers are numeric in web APIs and IndexedDB](#block-numbers-are-numeric-in-web-apis-and-indexeddb) -- [NetworkId custom networks and toBech32Custom removal](#networkid-custom-networks-and-tobech32custom-removal) -- [Client RNG must be Send and Sync](#client-rng-must-be-send-and-sync) -- [CLI swap payback_note_type removed](#cli-swap-payback_note_type-removed) - -### Input notes API unified - -**PR:** #1624 - -#### Summary - -Input notes are no longer split into authenticated and unauthenticated lists. Builders now accept full `Note` objects and the client determines authentication internally, and the WebClient consume request now accepts `Note[]` instead of note ID strings. - -#### Affected Code - -**Rust:** - -```rust -// Before (0.12.x) -use miden_client::auth::TransactionAuthenticator; -use miden_client::note::NoteId; -use miden_client::transaction::TransactionRequestBuilder; -use miden_client::{Client, ClientError}; - -async fn build_request( - client: &Client, - note_id: NoteId, -) -> Result { - let tx_request = TransactionRequestBuilder::new() - .authenticated_input_notes(vec![(note_id, None)]) - .build()?; - Ok(tx_request) -} -``` - -```rust -// After (0.13.0) -use miden_client::auth::TransactionAuthenticator; -use miden_client::note::{Note, NoteId}; -use miden_client::transaction::TransactionRequestBuilder; -use miden_client::{Client, ClientError}; - -async fn build_request( - client: &Client, - note_id: NoteId, -) -> Result { - let record = client.get_input_note(note_id).await?.expect("note not found"); - let note: Note = record.try_into().expect("failed to convert note record"); - - let tx_request = TransactionRequestBuilder::new() - .input_notes(vec![(note, None)]) - .build()?; - Ok(tx_request) -} -``` - -**TypeScript:** - -```typescript -// Before (0.12.x) -import { - NoteIdAndArgs, - NoteIdAndArgsArray, - TransactionRequestBuilder, - WebClient, -} from "@miden-sdk/miden-sdk"; - -const client = await WebClient.createClient(); -const consumeRequest = client.newConsumeTransactionRequest([noteId]); - -const noteIdAndArgs = new NoteIdAndArgs(noteId, null); -const txRequest = new TransactionRequestBuilder() - .withAuthenticatedInputNotes(new NoteIdAndArgsArray([noteIdAndArgs])) - .build(); -``` - -```typescript -// After (0.13.0) -import { - NoteAndArgs, - NoteAndArgsArray, - TransactionRequestBuilder, - WebClient, -} from "@miden-sdk/miden-sdk"; - -const client = await WebClient.createClient(); -const record = await client.getInputNote(noteId); -if (!record) { - throw new Error(`Note with ID ${noteId} not found`); -} -const note = record.toNote(); - -const consumeRequest = client.newConsumeTransactionRequest([note]); - -const noteAndArgs = new NoteAndArgs(note, null); -const txRequest = new TransactionRequestBuilder() - .withInputNotes(new NoteAndArgsArray([noteAndArgs])) - .build(); -``` - -#### Migration Steps - -1. Replace `authenticated_input_notes` and `unauthenticated_input_notes` with `input_notes`. -2. Convert `NoteId` values into `Note` objects (Rust: `InputNoteRecord` -> `Note` via `try_into`; Web: `InputNoteRecord.toNote()`). -3. Update `newConsumeTransactionRequest` to pass `Note[]`, and replace `NoteIdAndArgs` with `NoteAndArgs`. - -#### Common Errors - -| Error Message | Cause | Solution | -| --------------------------------------------- | ----------------------------------------- | ------------------------------------------- | -| `method not found: authenticated_input_notes` | Deprecated builder methods removed | Use `input_notes` with `Note` values | -| `expected Note, found NoteId` | Passing IDs where full notes are required | Fetch the note record and convert to `Note` | -| `newConsumeTransactionRequest expects Note[]` | API now requires notes, not strings | Call `getInputNote(...).toNote()` first | - -### Account components and storage slots now use named slots - -**PRs:** #1626, #1627 - -#### Summary - -Storage slots are now identified by name instead of index, and the web binding for `AccountComponent.compile` now requires a compiled `AccountComponentCode`. The filesystem keystore is also no longer generic over RNG. - -#### Affected Code - -**Rust:** - -```rust -// Before (0.12.x) -use miden_client::account::{StorageMap, StorageSlot}; - -fn storage_slots(storage_map: StorageMap) -> Vec { - vec![StorageSlot::Map(storage_map)] -} -``` - -```rust -// After (0.13.0) -use miden_client::account::{StorageMap, StorageSlot, StorageSlotName}; - -fn storage_slots(storage_map: StorageMap) -> Vec { - let slot_name = StorageSlotName::new("miden::example::map") - .expect("slot name must be valid"); - vec![StorageSlot::with_map(slot_name, storage_map)] -} -``` - -```rust -// Before (0.12.x) -use miden_client::builder::ClientBuilder; -use miden_client::keystore::FilesystemKeyStore; - -let builder = ClientBuilder::>::new(); -``` - -```rust -// After (0.13.0) -use miden_client::builder::ClientBuilder; -use miden_client::keystore::FilesystemKeyStore; - -let builder = ClientBuilder::::new(); -``` - -**TypeScript:** - -```typescript -// Before (0.12.x) -import { - AccountComponent, - StorageMap, - StorageSlot, - WebClient, -} from "@miden-sdk/miden-sdk"; - -const client = await WebClient.createClient(); -const builder = client.createCodeBuilder(); -const storageMap = new StorageMap(); -const slot = StorageSlot.map(storageMap); - -const component = AccountComponent.compile(accountCode, builder, [slot]); -const value = account.storage().getMapItem(1, key); -``` - -```typescript -// After (0.13.0) -import { - AccountComponent, - StorageMap, - StorageSlot, - WebClient, -} from "@miden-sdk/miden-sdk"; - -const client = await WebClient.createClient(); -const builder = client.createCodeBuilder(); -const storageMap = new StorageMap(); -const slotName = "miden::example::map"; -const slot = StorageSlot.map(slotName, storageMap); - -const componentCode = builder.compileAccountComponentCode(accountCode); -const component = AccountComponent.compile(componentCode, [slot]); - -const value = account.storage().getMapItem(slotName, key); -const slotNames = account.storage().getSlotNames(); -``` - -#### Migration Steps - -1. Replace index-based slots with named slots (`StorageSlotName` in Rust, string names in Web). -2. Update account component compilation to use `CodeBuilder.compileAccountComponentCode` and pass the resulting `AccountComponentCode`. -3. If you used `FilesystemKeyStore<_>` generics, drop the RNG parameter. -4. Update any storage accessors (`getItem`, `getMapItem`, `getMapEntries`) to pass slot names. - -#### Common Errors - -| Error Message | Cause | Solution | -| ------------------------------------------------ | ----------------------------------------- | --------------------------------------------- | -| `expected StorageSlotName` | Creating slots without names | Use `StorageSlotName::new("namespace::slot")` | -| `AccountComponent.compile takes 2 arguments` | Old binding passed `CodeBuilder` directly | Compile to `AccountComponentCode` first | -| `type annotations needed for FilesystemKeyStore` | Removed RNG generic | Use `FilesystemKeyStore` without type params | - -### Authentication and key management updates - -**PRs:** #1546, #1578, #1592, #1608 - -#### Summary - -WebClient auth APIs now take the `AuthScheme` enum instead of numeric IDs, `SecretKey` has been removed in favor of `AuthSecretKey`, and `addAccountSecretKeyToWebStore` now requires an account ID. In Rust, `build_wallet_id` no longer accepts a raw scheme ID and instead infers the scheme from `PublicKey`. Scheme-specific public key methods on `AuthSecretKey` were removed; use `getPublicKeyAsWord` instead. - -#### Affected Code - -**Rust:** - -```rust -// Before (0.12.x) -use miden_client::account::{build_wallet_id, AccountStorageMode}; -use miden_client::auth::{PublicKey, RPO_FALCON_SCHEME_ID}; - -fn build_id( - seed: [u8; 32], - public_key: &PublicKey, -) -> Result { - build_wallet_id( - seed, - public_key, - AccountStorageMode::Public, - true, - RPO_FALCON_SCHEME_ID, - ) -} -``` - -```rust -// After (0.13.0) -use miden_client::account::{build_wallet_id, AccountStorageMode}; -use miden_client::auth::PublicKey; - -fn build_id( - seed: [u8; 32], - public_key: &PublicKey, -) -> Result { - build_wallet_id(seed, public_key, AccountStorageMode::Public, true) -} -``` - -**TypeScript:** - -```typescript -// Before (0.12.x) -import { - AccountComponent, - AccountStorageMode, - SecretKey, - WebClient, -} from "@miden-sdk/miden-sdk"; - -const client = await WebClient.createClient(); -const wallet = await client.newWallet( - AccountStorageMode.public(), - true, - 0, - seed, -); - -const secretKey = SecretKey.rpoFalconWithRNG(seed); -const commitment = secretKey.getRpoFalcon512PublicKeyAsWord(); -const authComponent = AccountComponent.createAuthComponentFromCommitment( - commitment, - 0, -); - -await client.addAccountSecretKeyToWebStore(secretKey); -``` - -```typescript -// After (0.13.0) -import { - AccountComponent, - AccountStorageMode, - AuthScheme, - AuthSecretKey, - WebClient, -} from "@miden-sdk/miden-sdk"; - -const client = await WebClient.createClient(); -const wallet = await client.newWallet( - AccountStorageMode.public(), - true, - AuthScheme.AuthRpoFalcon512, - seed, -); - -const secretKey = AuthSecretKey.rpoFalconWithRNG(seed); -const commitment = secretKey.getPublicKeyAsWord(); -const authComponent = AccountComponent.createAuthComponentFromCommitment( - commitment, - AuthScheme.AuthRpoFalcon512, -); -const fromSecret = AccountComponent.createAuthComponentFromSecretKey(secretKey); - -await client.addAccountSecretKeyToWebStore(wallet.id(), secretKey); -const commitments = await client.getPublicKeyCommitmentsOfAccount(wallet.id()); -``` - -#### Migration Steps - -1. In Rust, drop the `auth_scheme_id` argument from `build_wallet_id`; the scheme is inferred from `PublicKey`. -2. In the WebClient, replace numeric auth scheme IDs with `AuthScheme` enum values. -3. Replace `SecretKey` with `AuthSecretKey` and update calls to `createAuthComponentFromSecretKey`. -4. Replace `getRpoFalcon512PublicKeyAsWord` and `getEcdsaK256KeccakPublicKeyAsWord` with `getPublicKeyAsWord`. -5. Pass an account ID to `addAccountSecretKeyToWebStore` and use `getPublicKeyCommitmentsOfAccount` when you need associated commitments. - -#### Common Errors - -| Error Message | Cause | Solution | -| --------------------------------------------------------- | ----------------------------------- | --------------------------------------------------------------------- | -| `this function takes 4 arguments but 5 were supplied` | `build_wallet_id` signature changed | Remove the `auth_scheme_id` argument | -| `SecretKey is not defined` | Model removed | Use `AuthSecretKey` | -| `Argument of type number is not assignable to AuthScheme` | Numeric scheme IDs removed | Use `AuthScheme.AuthRpoFalcon512` or `AuthScheme.AuthEcdsaK256Keccak` | -| `createAuthComponent is not a function` | Method removed | Use `createAuthComponentFromSecretKey` | - -### NodeRpcClient account proof API changed - -**PR:** #1616 - -#### Summary - -The batch `get_account_proofs` API is replaced with a single-account call that requires `AccountStateAt`, and the known code parameter is now optional per account. - -#### Affected Code - -**Rust:** - -```rust -// Before (0.12.x) -use std::collections::{BTreeMap, BTreeSet}; - -use miden_client::account::{AccountCode, AccountId}; -use miden_client::block::BlockNumber; -use miden_client::rpc::domain::account::AccountProof; -use miden_client::rpc::NodeRpcClient; -use miden_client::transaction::ForeignAccount; - -async fn fetch_proofs( - rpc: &dyn NodeRpcClient, - accounts: BTreeSet, - known_codes: BTreeMap, -) -> Result<(BlockNumber, Vec), miden_client::rpc::RpcError> { - rpc.get_account_proofs(&accounts, known_codes).await -} -``` - -```rust -// After (0.13.0) -use miden_client::account::AccountCode; -use miden_client::block::BlockNumber; -use miden_client::rpc::domain::account::AccountProof; -use miden_client::rpc::{AccountStateAt, NodeRpcClient}; -use miden_client::transaction::ForeignAccount; - -async fn fetch_proof( - rpc: &dyn NodeRpcClient, - account: ForeignAccount, - known_code: Option, -) -> Result<(BlockNumber, AccountProof), miden_client::rpc::RpcError> { - rpc.get_account(account, AccountStateAt::ChainTip, known_code).await -} -``` - -#### Migration Steps - -1. Replace `get_account_proofs` with `get_account` and call it per `ForeignAccount`. -2. Pass the desired state via `AccountStateAt::ChainTip` or `AccountStateAt::Block`. -3. Update implementations of `NodeRpcClient` to match the new signature and return type. - -#### Common Errors - -| Error Message | Cause | Solution | -| ------------------------------------------------ | ----------------------------- | ---------------------------------------------------------- | -| `method not found: get_account_proofs` | Old trait method removed | Use `get_account` and loop | -| `missing argument: account_state` | New `AccountStateAt` required | Pass `AccountStateAt::ChainTip` or `AccountStateAt::Block` | -| `expected AccountProof, found Vec` | Return type changed | Handle single proof per call | - -### FetchedNote and RPC note shapes refactored - -**PRs:** #1536, #1606 - -#### Summary - -Fetched notes now carry a `NoteHeader` for private notes and always expose the inclusion proof in the WebClient. Web `FetchedNote` exposes `header`, `note`, and `inclusionProof`, with `asInputNote()` for public notes. - -#### Affected Code - -**Rust:** - -```rust -// Before (0.12.x) -use miden_client::rpc::domain::note::FetchedNote; - -fn handle_note(note: FetchedNote) { - match note { - FetchedNote::Private(note_id, metadata, proof) => { - let _ = (note_id, metadata, proof); - } - FetchedNote::Public(note, proof) => { - let _ = (note, proof); - } - } -} -``` - -```rust -// After (0.13.0) -use miden_client::rpc::domain::note::FetchedNote; - -fn handle_note(note: FetchedNote) { - match note { - FetchedNote::Private(header, proof) => { - let note_id = header.id(); - let metadata = header.metadata(); - let _ = (note_id, metadata, proof); - } - FetchedNote::Public(note, proof) => { - let _ = (note, proof); - } - } -} -``` - -**TypeScript:** - -```typescript -// Before (0.12.x) -const fetched = (await rpcClient.getNotesById([noteId]))[0]; -if (fetched.inputNote) { - const scriptRoot = fetched.inputNote.note().script().root(); -} -``` - -```typescript -// After (0.13.0) -const fetched = (await rpcClient.getNotesById([noteId]))[0]; -const proof = fetched.inclusionProof; -const note = fetched.note; -if (note) { - const scriptRoot = note.script().root(); -} -const inputNote = fetched.asInputNote(); -``` - -#### Migration Steps - -1. Update pattern matches for `FetchedNote::Private` to use `NoteHeader`. -2. In the WebClient, replace `inputNote` access with `note` plus `inclusionProof`, or call `asInputNote()`. -3. Use `header` for shared access to `noteId` and `metadata`. - -#### Common Errors - -| Error Message | Cause | Solution | -| ------------------------------------------------------------------------ | ------------------------------------ | ------------------------------------------------ | -| `pattern has 3 fields, but the corresponding tuple variant has 2 fields` | `FetchedNote::Private` shape changed | Use `FetchedNote::Private(header, proof)` | -| `Property 'inputNote' does not exist on type 'FetchedNote'` | Web shape updated | Use `note`, `inclusionProof`, or `asInputNote()` | - -### Protocol 0.13 note metadata tags and attachments - -**PR:** #1685 - -#### Summary - -Note metadata and tagging APIs were simplified. Account-target tags now use `withAccountTarget`/`with_account_target`, `NoteExecutionMode` was removed, `NoteMetadata` no longer accepts execution hints in the constructor, and `NoteAttachment` now uses `NoteAttachmentScheme` with new accessors. - -#### Affected Code - -**Rust:** - -```rust -// Before (0.12.x) -use miden_client::note::{ - NoteExecutionHint, - NoteMetadata, - NoteTag, - NoteType, -}; -use miden_client::Felt; - -let tag = NoteTag::from_account_id(target_account_id); -let metadata = NoteMetadata::new( - sender_account_id, - NoteType::Private, - tag, - NoteExecutionHint::none(), - Felt::default(), -) -.expect("valid metadata"); -``` - -```rust -// After (0.13.0) -use miden_client::note::{ - NoteAttachment, - NoteAttachmentScheme, - NoteMetadata, - NoteTag, - NoteType, -}; - -let tag = NoteTag::with_account_target(target_account_id); -let metadata = NoteMetadata::new(sender_account_id, NoteType::Private, tag); - -let scheme = NoteAttachmentScheme::new(42); -let attachment = NoteAttachment::new_word(scheme, word); -let metadata_with_attachment = metadata.with_attachment(attachment); -``` - -**TypeScript:** - -```typescript -// Before (0.12.x) -import { - NoteAttachment, - NoteExecutionHint, - NoteExecutionMode, - NoteMetadata, - NoteTag, - NoteType, -} from "@miden-sdk/miden-sdk"; - -const tag = NoteTag.fromAccountId( - targetAccountId, - NoteExecutionMode.newLocal(), -); -const metadata = new NoteMetadata( - senderAccountId, - NoteType.Private, - tag, - NoteExecutionHint.none(), -); -const attachment = NoteAttachment.newWord(42, word); -``` - -```typescript -// After (0.13.0) -import { - NoteAttachment, - NoteAttachmentScheme, - NoteExecutionHint, - NoteMetadata, - NoteTag, - NoteType, -} from "@miden-sdk/miden-sdk"; - -const tag = NoteTag.withAccountTarget(targetAccountId); -const metadata = new NoteMetadata(senderAccountId, NoteType.Private, tag); - -const scheme = new NoteAttachmentScheme(42); -const attachment = NoteAttachment.newWord(scheme, word); -const metadataWithAttachment = metadata.withAttachment(attachment); - -// Optional: target a network account via attachment. -const networkAttachment = NoteAttachment.newNetworkAccountTarget( - targetAccountId, - NoteExecutionHint.none(), -); -``` - -#### Migration Steps - -1. Replace `NoteTag.fromAccountId` with `NoteTag.withAccountTarget`/`withCustomAccountTarget` (Rust: `NoteTag::with_account_target`/`NoteTag::with_custom_account_target`). -2. Drop `NoteExecutionMode` usages; attach execution context via `NoteAttachment` if needed. -3. Update `NoteMetadata` construction to `new NoteMetadata(sender, type, tag)` and add attachments with `withAttachment` (Rust: `NoteMetadata::new(...)` plus `with_attachment`). -4. Wrap attachment scheme values in `NoteAttachmentScheme` and use the new accessors to read payloads (Web: `asWord()`/`asArray()`, Rust: match on `NoteAttachment::content()`). - -#### Common Errors - -| Error Message | Cause | Solution | -| ------------------------------------------------------------------- | -------------------- | ------------------------------------------- | -| `NoteExecutionMode is not defined` | Class removed | Remove it and use attachments if needed | -| `NoteTag.fromAccountId is not a function` | API renamed | Use `NoteTag.withAccountTarget` | -| `Argument of type number is not assignable to NoteAttachmentScheme` | Scheme wrapper added | Construct `new NoteAttachmentScheme(value)` | - -### NoteScreener relevance replaced by NoteConsumptionStatus - -**PR:** #1630 - -#### Summary - -`NoteRelevance` was removed; `NoteScreener` now reports `NoteConsumptionStatus` values, and the WebClient exposes consumption status objects rather than a single `consumableAfterBlock` field. - -#### Affected Code - -**Rust:** - -```rust -// Before (0.12.x) -use miden_client::note::{NoteRelevance, NoteScreener}; - -let relevances = note_screener.check_relevance(¬e).await?; -for (_, relevance) in relevances { - if relevance == NoteRelevance::Now { - // ... - } -} -``` - -```rust -// After (0.13.0) -use miden_client::note::{NoteConsumptionStatus, NoteScreener}; - -let relevances = note_screener.check_relevance(¬e).await?; -for (_, status) in relevances { - match status { - NoteConsumptionStatus::Consumable - | NoteConsumptionStatus::ConsumableWithAuthorization => { - // ... - } - NoteConsumptionStatus::ConsumableAfter(_) => { - // ... - } - _ => {} - } -} -``` - -**TypeScript:** - -```typescript -// Before (0.12.x) -const records = await client.getConsumableNotes(accountId); -const after = records[0].noteConsumability()[0].consumableAfterBlock(); -``` - -```typescript -// After (0.13.0) -const records = await client.getConsumableNotes(accountId); -const status = records[0].noteConsumability()[0].consumptionStatus(); -const after = status.consumableAfterBlock(); -``` - -#### Migration Steps - -1. Replace `NoteRelevance` with `NoteConsumptionStatus` in Rust logic and pattern matching. -2. Update WebClient consumption checks to call `consumptionStatus()` and then `consumableAfterBlock()`. -3. Remove any reliance on `NoteRelevance::Now` / `After` variants. - -#### Common Errors - -| Error Message | Cause | Solution | -| ---------------------------------------- | ------------------------------------- | ------------------------------------------------- | -| `use of undeclared type NoteRelevance` | Type removed | Use `NoteConsumptionStatus` | -| `consumableAfterBlock is not a function` | API moved under `consumptionStatus()` | Call `consumptionStatus().consumableAfterBlock()` | - -### WebClient IndexedDB naming for multiple instances - -**PR:** #1645 - -#### Summary - -The WebClient store is now named, so multiple clients can coexist in the same browser. `WebClient.createClient` and `createClientWithExternalKeystore` accept an optional store name before callback arguments. - -#### Affected Code - -**TypeScript:** - -```typescript -// Before (0.12.x) -const client = await WebClient.createClient(rpcUrl, noteTransportUrl, seed); - -const clientWithKeystore = await WebClient.createClientWithExternalKeystore( - rpcUrl, - noteTransportUrl, - seed, - getKeyCb, - insertKeyCb, - signCb, -); -``` - -```typescript -// After (0.13.0) -const client = await WebClient.createClient( - rpcUrl, - noteTransportUrl, - seed, - "app-db", -); - -const clientWithKeystore = await WebClient.createClientWithExternalKeystore( - rpcUrl, - noteTransportUrl, - seed, - "app-db", - getKeyCb, - insertKeyCb, - signCb, -); -``` - -#### Migration Steps - -1. Add a store name to `createClient` when you need multiple instances in one origin. -2. Shift external keystore callback arguments one position to the right and pass `storeName` first. - -#### Common Errors - -| Error Message | Cause | Solution | -| --------------------------------- | --------------------------------------------------------- | -------------------------------------- | -| `Expected 7 arguments, but got 6` | Missing `storeName` in `createClientWithExternalKeystore` | Insert the store name before callbacks | - -### Block numbers are numeric in web APIs and IndexedDB - -**PRs:** #1528, #1684 - -#### Summary - -WebClient transaction interfaces and IndexedDB storage now use numeric block numbers instead of strings. - -#### Affected Code - -**TypeScript:** - -```typescript -// Before (0.12.x) -const summary = await client.syncState(); -const blockNum = parseInt(summary.blockNum(), 10); -``` - -```typescript -// After (0.13.0) -const summary = await client.syncState(); -const blockNum = summary.blockNum(); -``` - -#### Migration Steps - -1. Remove `parseInt` or `Number(...)` wrappers around `blockNum()` results. -2. If you integrate with `idxdb-store` helpers directly, pass numbers instead of strings. - -#### Common Errors - -| Error Message | Cause | Solution | -| --------------------------------------------------------------------------- | ------------------------- | ----------------------------- | -| `Argument of type 'string' is not assignable to parameter of type 'number'` | Block numbers now numeric | Pass `number` values directly | - -### NetworkId custom networks and toBech32Custom removal - -**PR:** #1612 - -#### Summary - -`NetworkId` is now a class with static constructors and supports custom prefixes. `toBech32Custom` was removed; use `NetworkId.custom(...)` with `toBech32` instead. - -#### Affected Code - -**TypeScript:** - -```typescript -// Before (0.12.x) -const bech32 = accountId.toBech32Custom("cstm", AccountInterface.BasicWallet); -const network = NetworkId.Testnet; -``` - -```typescript -// After (0.13.0) -const network = NetworkId.custom("cstm"); -const bech32 = accountId.toBech32(network, AccountInterface.BasicWallet); -const testnet = NetworkId.testnet(); -``` - -#### Migration Steps - -1. Replace enum-style `NetworkId.Mainnet/Testnet/Devnet` with `NetworkId.mainnet()/testnet()/devnet()`. -2. Replace `toBech32Custom(prefix, ...)` with `toBech32(NetworkId.custom(prefix), ...)`. - -#### Common Errors - -| Error Message | Cause | Solution | -| -------------------------------------------------------------- | ---------------------- | ---------------------------------------- | -| `Property 'Mainnet' does not exist on type 'typeof NetworkId'` | Enum replaced by class | Use `NetworkId.mainnet()` and friends | -| `toBech32Custom is not a function` | Method removed | Use `NetworkId.custom(...)` + `toBech32` | - -### Client RNG must be Send and Sync - -**PR:** #1677 - -#### Summary - -The client RNG must now be `Send + Sync` via the `ClientFeltRng` marker and `ClientRngBox` alias so `Client` can be `Send + Sync`. - -#### Affected Code - -**Rust:** - -```rust -// Before (0.12.x) -use miden_client::builder::ClientBuilder; -use miden_client::crypto::{FeltRng, RpoRandomCoin}; - -let rng: Box = Box::new(RpoRandomCoin::new([0u8; 32])); -let builder = ClientBuilder::new().rng(rng); -``` - -```rust -// After (0.13.0) -use miden_client::builder::ClientBuilder; -use miden_client::crypto::RpoRandomCoin; -use miden_client::ClientRngBox; - -let rng: ClientRngBox = Box::new(RpoRandomCoin::new([0u8; 32])); -let builder = ClientBuilder::new().rng(rng); -``` - -#### Migration Steps - -1. Ensure your RNG implements `Send + Sync`. -2. Wrap the RNG in `ClientRngBox` and pass it to `ClientBuilder::rng`. - -#### Common Errors - -| Error Message | Cause | Solution | -| --------------------------------------------------- | ------------------------ | ----------------------------------------- | -| `the trait bound ...: Send + Sync is not satisfied` | RNG type not thread-safe | Use a `Send + Sync` RNG or wrap it safely | - -### CLI swap payback_note_type removed - -**PR:** #1700 - -#### Summary - -The CLI swap command no longer accepts a `payback_note_type` argument; the payback note type is now fixed. - -#### Affected Code - -**CLI:** - -```bash -# Before (0.12.x) -miden-client swap \ - --offered-asset 10::0x... \ - --requested-asset 5::0x... \ - --note-type public \ - --payback-note-type public -``` - -```bash -# After (0.13.0) -miden-client swap \ - --offered-asset 10::0x... \ - --requested-asset 5::0x... \ - --note-type public -``` - -#### Migration Steps - -1. Remove `--payback-note-type` from swap command invocations or scripts. - -#### Common Errors - -| Error Message | Cause | Solution | -| ------------------------------------------- | ------------ | ------------------------------ | -| `unexpected argument '--payback-note-type'` | Flag removed | Drop the flag from the command | - -## Need Help? - -If you hit issues migrating, reach out here: - -- [Discord Community](https://discord.gg/miden) -- [GitHub Issues](https://github.com/0xMiden/miden-client/issues) diff --git a/contributing.md b/contributing.md index 9a29924..c023280 100644 --- a/contributing.md +++ b/contributing.md @@ -30,6 +30,64 @@ prettier --write "**/*.md" Make sure to run this command before submitting pull requests. -Thank you for contributing! +## CodeSdkTabs: Dot-Indentation Convention + +The `CodeSdkTabs` component (in `docs/src/components/CodeSdkTabs.tsx`) lets tutorials show React and TypeScript code side-by-side. Code is passed as template literals inside MDX props: + +```mdx + +``` + +**Problem:** MDX/webpack strips leading whitespace from template literals, so indented code renders flush-left. + +**Solution:** Use leading dots (`.`) to represent indentation. Each dot equals one indent level (2 spaces). The `preserveIndent()` function in `CodeSdkTabs.tsx` converts dots to spaces at render time. + +### Example + +Source in `.md` file: + +``` +typescript: { code: `export function foo() { +.const x = 1; +.if (x) { +..console.log(x); +.} +}` } +``` + +Renders as: + +```ts +export function foo() { + const x = 1; + if (x) { + console.log(x); + } +} +``` + +### Rules + +| Context | Dots | Spaces | +| ------------------------------------------- | ---- | ------ | +| Top-level (`export`, `import`, closing `}`) | 0 | 0 | +| Inside function body | 1 | 2 | +| Inside nested block (`if`, `for`, etc.) | 2 | 4 | +| Function call arguments (continuation) | +1 | +2 | +| Deeper nesting | 3+ | 6+ | + +### Tips + +- Standalone code blocks (` ```ts...``` `) outside `CodeSdkTabs` use normal space indentation -- the dot convention only applies inside `CodeSdkTabs` template literals. +- The React and TypeScript code snippets both follow this convention. If you add or edit snippets, apply the same pattern. --- + +Thank you for contributing! diff --git a/docs/src/components/CodeSdkTabs.tsx b/docs/src/components/CodeSdkTabs.tsx index f2c4a37..48b36ba 100644 --- a/docs/src/components/CodeSdkTabs.tsx +++ b/docs/src/components/CodeSdkTabs.tsx @@ -19,8 +19,33 @@ interface CodeSdkTabsProps { tsFilename?: string; } -// Preserves indentation by replacing leading dots with spaces -// This works around MDX/webpack stripping leading whitespace from template literals +// Dot-indentation convention for CodeSdkTabs +// ───────────────────────────────────────────── +// MDX/webpack strips leading whitespace from template literals inside JSX props. +// To preserve indentation in code snippets, use leading dots in the markdown +// source. Each dot represents one indent level (2 spaces). +// +// Example in a .md file: +// typescript: { code: `export function foo() { +// .const x = 1; +// .if (x) { +// ..console.log(x); +// .} +// }` } +// +// Renders as: +// export function foo() { +// const x = 1; +// if (x) { +// console.log(x); +// } +// } +// +// Rules: +// 0 dots → top-level declarations (export, import, closing braces) +// 1 dot → first level inside a function/block body +// 2 dots → second level (nested blocks, function call arguments) +// 3+ dots → deeper nesting function preserveIndent(code: string): string { return code.replace(/^(\.+)/gm, (match) => ' '.repeat(match.length)); } diff --git a/docs/src/web-client/create_deploy_tutorial.md b/docs/src/web-client/create_deploy_tutorial.md index d9f95aa..80666c2 100644 --- a/docs/src/web-client/create_deploy_tutorial.md +++ b/docs/src/web-client/create_deploy_tutorial.md @@ -117,26 +117,26 @@ export default function CreateMintConsume() { }`}, typescript: { code:`// lib/createMintConsume.ts export async function createMintConsume(): Promise { -if (typeof window === 'undefined') { -console.warn('webClient() can only run in the browser'); -return; -} - -// dynamic import → only in the browser, so WASM is loaded client‑side -const { WebClient } = -await import('@miden-sdk/miden-sdk'); - -// Connect to Miden testnet RPC endpoint -const nodeEndpoint = 'https://rpc.testnet.miden.io'; -const client = await WebClient.createClient(nodeEndpoint); - -// 1. Sync with the latest blockchain state -// This fetches the latest block header and state commitments -const state = await client.syncState(); -console.log('Latest block number:', state.blockNum()); - -// At this point, your client is connected and synchronized -// Ready to create accounts and deploy contracts! +.if (typeof window === 'undefined') { +..console.warn('webClient() can only run in the browser'); +..return; +.} + +.// dynamic import → only in the browser, so WASM is loaded client‑side +.const { WebClient } = +..await import('@miden-sdk/miden-sdk'); + +.// Connect to Miden testnet RPC endpoint +.const nodeEndpoint = 'https://rpc.testnet.miden.io'; +.const client = await WebClient.createClient(nodeEndpoint); + +.// 1. Sync with the latest blockchain state +.// This fetches the latest block header and state commitments +.const state = await client.syncState(); +.console.log('Latest block number:', state.blockNum()); + +.// At this point, your client is connected and synchronized +.// Ready to create accounts and deploy contracts! }` }, }} reactFilename="lib/react/createMintConsume.tsx" tsFilename="lib/createMintConsume.ts" /> @@ -214,30 +214,30 @@ react: { code: `const run = async () => { };` }, typescript: { code: `// lib/createMintConsume.ts export async function createMintConsume(): Promise { -if (typeof window === 'undefined') { -console.warn('webClient() can only run in the browser'); -return; -} +.if (typeof window === 'undefined') { +..console.warn('webClient() can only run in the browser'); +..return; +.} -const { WebClient, AccountStorageMode, AuthScheme } = await import( -"@miden-sdk/miden-sdk" -); +.const { WebClient, AccountStorageMode, AuthScheme } = await import( +.."@miden-sdk/miden-sdk" +.); -const nodeEndpoint = 'https://rpc.testnet.miden.io'; -const client = await WebClient.createClient(nodeEndpoint); +.const nodeEndpoint = 'https://rpc.testnet.miden.io'; +.const client = await WebClient.createClient(nodeEndpoint); -// 1. Sync with the latest blockchain state -const state = await client.syncState(); -console.log('Latest block number:', state.blockNum()); +.// 1. Sync with the latest blockchain state +.const state = await client.syncState(); +.console.log('Latest block number:', state.blockNum()); -// 2. Create Alice's account -console.log('Creating account for Alice…'); -const alice = await client.newWallet( -AccountStorageMode.public(), // Public: account state is visible on-chain -true, // Mutable: account code can be upgraded later -AuthScheme.AuthRpoFalcon512 // Auth Scheme: RPO Falcon 512 -); -console.log('Alice ID:', alice.id().toString()); +.// 2. Create Alice's account +.console.log('Creating account for Alice…'); +.const alice = await client.newWallet( +..AccountStorageMode.public(), // Public: account state is visible on-chain +..true, // Mutable: account code can be upgraded later +..AuthScheme.AuthRpoFalcon512 // Auth Scheme: RPO Falcon 512 +.); +.console.log('Alice ID:', alice.id().toString()); }` }, }} reactFilename="lib/react/createMintConsume.tsx" tsFilename="lib/createMintConsume.ts" /> @@ -263,12 +263,12 @@ console.log('Setup complete.');`}, // A faucet is an account that can mint new tokens console.log('Creating faucet…'); const faucetAccount = await client.newFaucet( -AccountStorageMode.public(), // Public: faucet operations are transparent -false, // Immutable: faucet rules cannot be changed -"MID", // Token symbol (like ETH, BTC, etc.) -8, // Decimals (8 means 1 MID = 100,000,000 base units) -BigInt(1_000_000), // Max supply: total tokens that can ever be minted -AuthScheme.AuthRpoFalcon512 // Auth Scheme: RPO Falcon 512 +.AccountStorageMode.public(), // Public: faucet operations are transparent +.false, // Immutable: faucet rules cannot be changed +."MID", // Token symbol (like ETH, BTC, etc.) +.8, // Decimals (8 means 1 MID = 100,000,000 base units) +.BigInt(1_000_000), // Max supply: total tokens that can ever be minted +.AuthScheme.AuthRpoFalcon512 // Auth Scheme: RPO Falcon 512 ); console.log('Faucet account ID:', faucetAccount.id().toString()); @@ -343,44 +343,44 @@ export default function CreateMintConsume() { }`}, typescript: { code:`// lib/createMintConsume.ts export async function createMintConsume(): Promise { -if (typeof window === 'undefined') { -console.warn('webClient() can only run in the browser'); -return; -} +.if (typeof window === 'undefined') { +..console.warn('webClient() can only run in the browser'); +..return; +.} -// dynamic import → only in the browser, so WASM is loaded client‑side -const { WebClient, AccountStorageMode, AuthScheme } = -await import('@miden-sdk/miden-sdk'); +.// dynamic import → only in the browser, so WASM is loaded client‑side +.const { WebClient, AccountStorageMode, AuthScheme } = +..await import('@miden-sdk/miden-sdk'); -const nodeEndpoint = 'https://rpc.testnet.miden.io'; -const client = await WebClient.createClient(nodeEndpoint); +.const nodeEndpoint = 'https://rpc.testnet.miden.io'; +.const client = await WebClient.createClient(nodeEndpoint); -// 1. Sync with the latest blockchain state -const state = await client.syncState(); -console.log('Latest block number:', state.blockNum()); +.// 1. Sync with the latest blockchain state +.const state = await client.syncState(); +.console.log('Latest block number:', state.blockNum()); -// 2. Create Alice's account -console.log('Creating account for Alice…'); -const alice = await client.newWallet( -AccountStorageMode.public(), -true, -AuthScheme.AuthRpoFalcon512, -); -console.log('Alice ID:', alice.id().toString()); +.// 2. Create Alice's account +.console.log('Creating account for Alice…'); +.const alice = await client.newWallet( +..AccountStorageMode.public(), +..true, +..AuthScheme.AuthRpoFalcon512, +.); +.console.log('Alice ID:', alice.id().toString()); -// 3. Deploy a fungible faucet -console.log('Creating faucet…'); -const faucet = await client.newFaucet( -AccountStorageMode.public(), -false, -'MID', -8, -BigInt(1_000_000), -AuthScheme.AuthRpoFalcon512, -); -console.log('Faucet ID:', faucet.id().toString()); +.// 3. Deploy a fungible faucet +.console.log('Creating faucet…'); +.const faucet = await client.newFaucet( +..AccountStorageMode.public(), +..false, +..'MID', +..8, +..BigInt(1_000_000), +..AuthScheme.AuthRpoFalcon512, +.); +.console.log('Faucet ID:', faucet.id().toString()); -console.log('Setup complete.'); +.console.log('Setup complete.'); }` }, }} reactFilename="lib/react/createMintConsume.tsx" tsFilename="lib/createMintConsume.ts" /> diff --git a/docs/src/web-client/creating_multiple_notes_tutorial.md b/docs/src/web-client/creating_multiple_notes_tutorial.md index 76d8c59..dae5307 100644 --- a/docs/src/web-client/creating_multiple_notes_tutorial.md +++ b/docs/src/web-client/creating_multiple_notes_tutorial.md @@ -169,35 +169,35 @@ function MultiSendInner() { export default function MultiSendWithDelegatedProver() { .return ( -.. +.. ... .. .); }`}, typescript: { code:`export async function multiSendWithDelegatedProver(): Promise { -// Ensure this runs only in a browser context -if (typeof window === 'undefined') return console.warn('Run in browser'); - -const { -WebClient, -AccountStorageMode, -AuthScheme, -Address, -NoteType, -TransactionProver, -Note, -NoteAssets, -OutputNoteArray, -NoteAttachment, -FungibleAsset, -TransactionRequestBuilder, -OutputNote, -} = await import('@miden-sdk/miden-sdk'); - -const client = await WebClient.createClient('https://rpc.testnet.miden.io'); -const prover = TransactionProver.newLocalProver(); - -console.log('Latest block:', (await client.syncState()).blockNum()); +.// Ensure this runs only in a browser context +.if (typeof window === 'undefined') return console.warn('Run in browser'); + +.const { +..WebClient, +..AccountStorageMode, +..AuthScheme, +..Address, +..NoteType, +..TransactionProver, +..Note, +..NoteAssets, +..OutputNoteArray, +..NoteAttachment, +..FungibleAsset, +..TransactionRequestBuilder, +..OutputNote, +.} = await import('@miden-sdk/miden-sdk'); + +.const client = await WebClient.createClient('https://rpc.testnet.miden.io'); +.const prover = TransactionProver.newLocalProver(); + +.console.log('Latest block:', (await client.syncState()).blockNum()); }` }, }} reactFilename="lib/react/multiSendWithDelegatedProver.tsx" tsFilename="lib/multiSendWithDelegatedProver.ts" /> @@ -240,63 +240,63 @@ await consume({ accountId: aliceId, noteIds });`}, typescript: { code:`// ── Creating new account ────────────────────────────────────────────────────── console.log('Creating account for Alice…'); const alice = await client.newWallet( -AccountStorageMode.public(), -true, -AuthScheme.AuthRpoFalcon512, +.AccountStorageMode.public(), +.true, +.AuthScheme.AuthRpoFalcon512, ); -console.log('Alice accout ID:', alice.id().toString()); +console.log('Alice account ID:', alice.id().toString()); // ── Creating new faucet ────────────────────────────────────────────────────── const faucet = await client.newFaucet( -AccountStorageMode.public(), -false, -'MID', -8, -BigInt(1_000_000), -AuthScheme.AuthRpoFalcon512, +.AccountStorageMode.public(), +.false, +.'MID', +.8, +.BigInt(1_000_000), +.AuthScheme.AuthRpoFalcon512, ); console.log('Faucet ID:', faucet.id().toString()); // ── mint 10 000 MID to Alice ────────────────────────────────────────────────────── { -const txResult = await client.executeTransaction( -faucet.id(), -client.newMintTransactionRequest( -alice.id(), -faucet.id(), -NoteType.Public, -BigInt(10_000), -), -); -const proven = await client.proveTransaction(txResult, prover); -const submissionHeight = await client.submitProvenTransaction( -proven, -txResult, -); -await client.applyTransaction(txResult, submissionHeight); +.const txResult = await client.executeTransaction( +..faucet.id(), +..client.newMintTransactionRequest( +...alice.id(), +...faucet.id(), +...NoteType.Public, +...BigInt(10_000), +..), +.); +.const proven = await client.proveTransaction(txResult, prover); +.const submissionHeight = await client.submitProvenTransaction( +..proven, +..txResult, +.); +.await client.applyTransaction(txResult, submissionHeight); -console.log('waiting for settlement'); -await new Promise((r) => setTimeout(r, 7_000)); -await client.syncState(); +.console.log('waiting for settlement'); +.await new Promise((r) => setTimeout(r, 7_000)); +.await client.syncState(); } // ── consume the freshly minted notes ────────────────────────────────────────────── const noteList = (await client.getConsumableNotes(alice.id())).map((rec) => -rec.inputNoteRecord().toNote(), +.rec.inputNoteRecord().toNote(), ); { -const txResult = await client.executeTransaction( -alice.id(), -client.newConsumeTransactionRequest(noteList), -); -const proven = await client.proveTransaction(txResult, prover); -await client.syncState(); -const submissionHeight = await client.submitProvenTransaction( -proven, -txResult, -); -await client.applyTransaction(txResult, submissionHeight); +.const txResult = await client.executeTransaction( +..alice.id(), +..client.newConsumeTransactionRequest(noteList), +.); +.const proven = await client.proveTransaction(txResult, prover); +.await client.syncState(); +.const submissionHeight = await client.submitProvenTransaction( +..proven, +..txResult, +.); +.await client.applyTransaction(txResult, submissionHeight); }` }, }} reactFilename="lib/react/multiSendWithDelegatedProver.tsx" tsFilename="lib/multiSendWithDelegatedProver.ts" /> @@ -320,32 +320,32 @@ await sendMany({ console.log('All notes created ✅');`}, typescript: { code:`// ── build 3 P2ID notes (100 MID each) ───────────────────────────────────────────── const recipientAddresses = [ -'mtst1aqezqc90x7dkzypr9m5fmlpp85w6cl04', -'mtst1apjg2ul76wrkxyr5qlcnczaskypa4ljn', -'mtst1arpee6y9cm8t7ypn33pc8fzj6gkzz7kd', +.'mtst1aqezqc90x7dkzypr9m5fmlpp85w6cl04', +.'mtst1apjg2ul76wrkxyr5qlcnczaskypa4ljn', +.'mtst1arpee6y9cm8t7ypn33pc8fzj6gkzz7kd', ]; const assets = new NoteAssets([new FungibleAsset(faucet.id(), BigInt(100))]); const p2idNotes = recipientAddresses.map((addr) => { -const receiverAccountId = Address.fromBech32(addr).accountId(); -const note = Note.createP2IDNote( -alice.id(), -receiverAccountId, -assets, -NoteType.Public, -new NoteAttachment(), -); +.const receiverAccountId = Address.fromBech32(addr).accountId(); +.const note = Note.createP2IDNote( +..alice.id(), +..receiverAccountId, +..assets, +..NoteType.Public, +..new NoteAttachment(), +.); -return OutputNote.full(note); +.return OutputNote.full(note); }); // ── create all P2ID notes ─────────────────────────────────────────────────────────────── await client.submitNewTransaction( -alice.id(), -new TransactionRequestBuilder() -.withOwnOutputNotes(new OutputNoteArray(p2idNotes)) -.build(), +.alice.id(), +.new TransactionRequestBuilder() +..withOwnOutputNotes(new OutputNoteArray(p2idNotes)) +..build(), ); console.log('All notes created ✅');` }, @@ -429,138 +429,131 @@ function MultiSendInner() { export default function MultiSendWithDelegatedProver() { .return ( -.. +.. ... .. .); }`}, - typescript: { code:`/\*\* - -- Demonstrates multi-send functionality using a local prover on the Miden Network -- Creates multiple P2ID (Pay to ID) notes for different recipients -- -- @throws {Error} If the function cannot be executed in a browser environment - \*/ - export async function multiSendWithDelegatedProver(): Promise { - // Ensure this runs only in a browser context - if (typeof window === 'undefined') return console.warn('Run in browser'); - -const { -WebClient, -AccountStorageMode, -AuthScheme, -Address, -NoteType, -TransactionProver, -Note, -NoteAssets, -OutputNoteArray, -FungibleAsset, -NoteAttachment, -TransactionRequestBuilder, -OutputNote, -} = await import('@miden-sdk/miden-sdk'); - -const client = await WebClient.createClient('https://rpc.testnet.miden.io'); -const prover = TransactionProver.newLocalProver(); - -console.log('Latest block:', (await client.syncState()).blockNum()); - -// ── Creating new account ────────────────────────────────────────────────────── -console.log('Creating account for Alice…'); -const alice = await client.newWallet( -AccountStorageMode.public(), -true, -AuthScheme.AuthRpoFalcon512, -); -console.log('Alice accout ID:', alice.id().toString()); - -// ── Creating new faucet ────────────────────────────────────────────────────── -const faucet = await client.newFaucet( -AccountStorageMode.public(), -false, -'MID', -8, -BigInt(1_000_000), -AuthScheme.AuthRpoFalcon512, -); -console.log('Faucet ID:', faucet.id().toString()); - -// ── mint 10 000 MID to Alice ────────────────────────────────────────────────────── -{ -const txResult = await client.executeTransaction( -faucet.id(), -client.newMintTransactionRequest( -alice.id(), -faucet.id(), -NoteType.Public, -BigInt(10_000), -), -); -const proven = await client.proveTransaction(txResult, prover); -const submissionHeight = await client.submitProvenTransaction( -proven, -txResult, -); -await client.applyTransaction(txResult, submissionHeight); - - console.log('waiting for settlement'); - await new Promise((r) => setTimeout(r, 7_000)); - await client.syncState(); - -} - -// ── consume the freshly minted notes ────────────────────────────────────────────── -const noteList = (await client.getConsumableNotes(alice.id())).map((rec) => -rec.inputNoteRecord().toNote(), -); - -{ -const txResult = await client.executeTransaction( -alice.id(), -client.newConsumeTransactionRequest(noteList), -); -const proven = await client.proveTransaction(txResult, prover); -await client.syncState(); -const submissionHeight = await client.submitProvenTransaction( -proven, -txResult, -); -await client.applyTransaction(txResult, submissionHeight); -} - -// ── build 3 P2ID notes (100 MID each) ───────────────────────────────────────────── -const recipientAddresses = [ -'mtst1aqezqc90x7dkzypr9m5fmlpp85w6cl04', -'mtst1apjg2ul76wrkxyr5qlcnczaskypa4ljn', -'mtst1arpee6y9cm8t7ypn33pc8fzj6gkzz7kd', -]; - -const assets = new NoteAssets([new FungibleAsset(faucet.id(), BigInt(100))]); - -const p2idNotes = recipientAddresses.map((addr) => { -const receiverAccountId = Address.fromBech32(addr).accountId(); -const note = Note.createP2IDNote( -alice.id(), -receiverAccountId, -assets, -NoteType.Public, -new NoteAttachment(), -); - - return OutputNote.full(note); - -}); + typescript: { code:`/\*\* \* Demonstrates multi-send functionality using a local prover on the Miden Network \* Creates multiple P2ID (Pay to ID) notes for different recipients \* \* @throws {Error} If the function cannot be executed in a browser environment +\*/ +export async function multiSendWithDelegatedProver(): Promise { +.// Ensure this runs only in a browser context +.if (typeof window === 'undefined') return console.warn('Run in browser'); + +.const { +..WebClient, +..AccountStorageMode, +..AuthScheme, +..Address, +..NoteType, +..TransactionProver, +..Note, +..NoteAssets, +..OutputNoteArray, +..FungibleAsset, +..NoteAttachment, +..TransactionRequestBuilder, +..OutputNote, +.} = await import('@miden-sdk/miden-sdk'); + +.const client = await WebClient.createClient('https://rpc.testnet.miden.io'); +.const prover = TransactionProver.newLocalProver(); + +.console.log('Latest block:', (await client.syncState()).blockNum()); + +.// ── Creating new account ────────────────────────────────────────────────────── +.console.log('Creating account for Alice…'); +.const alice = await client.newWallet( +..AccountStorageMode.public(), +..true, +..AuthScheme.AuthRpoFalcon512, +.); +.console.log('Alice account ID:', alice.id().toString()); + +.// ── Creating new faucet ────────────────────────────────────────────────────── +.const faucet = await client.newFaucet( +..AccountStorageMode.public(), +..false, +..'MID', +..8, +..BigInt(1_000_000), +..AuthScheme.AuthRpoFalcon512, +.); +.console.log('Faucet ID:', faucet.id().toString()); + +.// ── mint 10 000 MID to Alice ────────────────────────────────────────────────────── +.{ +..const txResult = await client.executeTransaction( +...faucet.id(), +...client.newMintTransactionRequest( +....alice.id(), +....faucet.id(), +....NoteType.Public, +....BigInt(10_000), +...), +..); +..const proven = await client.proveTransaction(txResult, prover); +..const submissionHeight = await client.submitProvenTransaction( +...proven, +...txResult, +..); +..await client.applyTransaction(txResult, submissionHeight); + +..console.log('waiting for settlement'); +..await new Promise((r) => setTimeout(r, 7_000)); +..await client.syncState(); +.} + +.// ── consume the freshly minted notes ────────────────────────────────────────────── +.const noteList = (await client.getConsumableNotes(alice.id())).map((rec) => +..rec.inputNoteRecord().toNote(), +.); -// ── create all P2ID notes ─────────────────────────────────────────────────────────────── -await client.submitNewTransaction( -alice.id(), -new TransactionRequestBuilder() -.withOwnOutputNotes(new OutputNoteArray(p2idNotes)) -.build(), -); +.{ +..const txResult = await client.executeTransaction( +...alice.id(), +...client.newConsumeTransactionRequest(noteList), +..); +..const proven = await client.proveTransaction(txResult, prover); +..await client.syncState(); +..const submissionHeight = await client.submitProvenTransaction( +...proven, +...txResult, +..); +..await client.applyTransaction(txResult, submissionHeight); +.} + +.// ── build 3 P2ID notes (100 MID each) ───────────────────────────────────────────── +.const recipientAddresses = [ +..'mtst1aqezqc90x7dkzypr9m5fmlpp85w6cl04', +..'mtst1apjg2ul76wrkxyr5qlcnczaskypa4ljn', +..'mtst1arpee6y9cm8t7ypn33pc8fzj6gkzz7kd', +.]; + +.const assets = new NoteAssets([new FungibleAsset(faucet.id(), BigInt(100))]); + +.const p2idNotes = recipientAddresses.map((addr) => { +..const receiverAccountId = Address.fromBech32(addr).accountId(); +..const note = Note.createP2IDNote( +...alice.id(), +...receiverAccountId, +...assets, +...NoteType.Public, +...new NoteAttachment(), +..); + +..return OutputNote.full(note); +.}); + +.// ── create all P2ID notes ─────────────────────────────────────────────────────────────── +.await client.submitNewTransaction( +..alice.id(), +..new TransactionRequestBuilder() +...withOwnOutputNotes(new OutputNoteArray(p2idNotes)) +...build(), +.); -console.log('All notes created ✅'); +.console.log('All notes created ✅'); }` }, }} reactFilename="lib/react/multiSendWithDelegatedProver.tsx" tsFilename="lib/multiSendWithDelegatedProver.ts" /> diff --git a/docs/src/web-client/mint_consume_create_tutorial.md b/docs/src/web-client/mint_consume_create_tutorial.md index 04163f8..81c03c7 100644 --- a/docs/src/web-client/mint_consume_create_tutorial.md +++ b/docs/src/web-client/mint_consume_create_tutorial.md @@ -56,10 +56,10 @@ await client.syncState(); console.log("Minting tokens to Alice..."); const mintTxRequest = client.newMintTransactionRequest( -alice.id(), // Target account (who receives the tokens) -faucet.id(), // Faucet account (who mints the tokens) -NoteType.Public, // Note visibility (public = onchain) -BigInt(1000), // Amount to mint (in base units) +.alice.id(), // Target account (who receives the tokens) +.faucet.id(), // Faucet account (who mints the tokens) +.NoteType.Public, // Note visibility (public = onchain) +.BigInt(1000), // Amount to mint (in base units) ); await client.submitNewTransaction(faucet.id(), mintTxRequest); @@ -93,8 +93,8 @@ console.log(\`Found \${mintedNotes.length} note(s) to consume\`); const mintedNoteList = mintedNotes.map((n) => n.inputNoteRecord().toNote()); console.log( -'Minted notes:', -mintedNoteList.map((note) => note.id().toString()), +.'Minted notes:', +.mintedNoteList.map((note) => note.id().toString()), );` }, }} reactFilename="lib/react/createMintConsume.tsx" tsFilename="lib/createMintConsume.ts" /> @@ -139,16 +139,16 @@ await send({ console.log('Tokens sent successfully!');` }, typescript: { code: `// 7. Send tokens from Alice to Bob const bobAccountId = Address.fromBech32( -'mtst1apve54rq8ux0jqqqqrkh5y0r0y8cwza6_qruqqypuyph', +.'mtst1apve54rq8ux0jqqqqrkh5y0r0y8cwza6_qruqqypuyph', ).accountId(); console.log("Sending tokens to Bob's account..."); const sendTxRequest = client.newSendTransactionRequest( -alice.id(), // Sender account ID -bobAccountId, // Recipient account ID -faucet.id(), // Asset ID (faucet that created the tokens) -NoteType.Public, // Note visibility -BigInt(100), // Amount to send +.alice.id(), // Sender account ID +.bobAccountId, // Recipient account ID +.faucet.id(), // Asset ID (faucet that created the tokens) +.NoteType.Public, // Note visibility +.BigInt(100), // Amount to send ); await client.submitNewTransaction(alice.id(), sendTxRequest); @@ -255,97 +255,97 @@ export default function CreateMintConsume() { }`}, typescript: { code:`// lib/createMintConsume.ts export async function createMintConsume(): Promise { -if (typeof window === 'undefined') { -console.warn('webClient() can only run in the browser'); -return; -} - -// dynamic import → only in the browser, so WASM is loaded client‑side -const { WebClient, AccountStorageMode, AuthScheme, NoteType, Address } = -await import('@miden-sdk/miden-sdk'); - -const nodeEndpoint = 'https://rpc.testnet.miden.io'; -const client = await WebClient.createClient(nodeEndpoint); - -// 1. Sync with the latest blockchain state -const state = await client.syncState(); -console.log('Latest block number:', state.blockNum()); - -// 2. Create Alice's account -console.log('Creating account for Alice…'); -const aliceSeed = new Uint8Array(32); -crypto.getRandomValues(aliceSeed); -const alice = await client.newWallet( -AccountStorageMode.public(), -true, -AuthScheme.AuthRpoFalcon512, -aliceSeed, -); -console.log('Alice ID:', alice.id().toString()); - -// 3. Deploy a fungible faucet -console.log('Creating faucet…'); -const faucet = await client.newFaucet( -AccountStorageMode.public(), -false, -'MID', -8, -BigInt(1_000_000), -AuthScheme.AuthRpoFalcon512, -); -console.log('Faucet ID:', faucet.id().toString()); - -await client.syncState(); - -// 4. Mint tokens to Alice -await client.syncState(); - -console.log('Minting tokens to Alice...'); -const mintTxRequest = client.newMintTransactionRequest( -alice.id(), -faucet.id(), -NoteType.Public, -BigInt(1000), -); +.if (typeof window === 'undefined') { +..console.warn('webClient() can only run in the browser'); +..return; +.} + +.// dynamic import → only in the browser, so WASM is loaded client‑side +.const { WebClient, AccountStorageMode, AuthScheme, NoteType, Address } = +..await import('@miden-sdk/miden-sdk'); + +.const nodeEndpoint = 'https://rpc.testnet.miden.io'; +.const client = await WebClient.createClient(nodeEndpoint); + +.// 1. Sync with the latest blockchain state +.const state = await client.syncState(); +.console.log('Latest block number:', state.blockNum()); + +.// 2. Create Alice's account +.console.log('Creating account for Alice…'); +.const aliceSeed = new Uint8Array(32); +.crypto.getRandomValues(aliceSeed); +.const alice = await client.newWallet( +..AccountStorageMode.public(), +..true, +..AuthScheme.AuthRpoFalcon512, +..aliceSeed, +.); +.console.log('Alice ID:', alice.id().toString()); + +.// 3. Deploy a fungible faucet +.console.log('Creating faucet…'); +.const faucet = await client.newFaucet( +..AccountStorageMode.public(), +..false, +..'MID', +..8, +..BigInt(1_000_000), +..AuthScheme.AuthRpoFalcon512, +.); +.console.log('Faucet ID:', faucet.id().toString()); -await client.submitNewTransaction(faucet.id(), mintTxRequest); +.await client.syncState(); -console.log('Waiting 10 seconds for transaction confirmation...'); -await new Promise((resolve) => setTimeout(resolve, 10000)); -await client.syncState(); +.// 4. Mint tokens to Alice +.await client.syncState(); -// 5. Fetch minted notes -const mintedNotes = await client.getConsumableNotes(alice.id()); -const mintedNoteList = mintedNotes.map((n) => n.inputNoteRecord().toNote()); -console.log( -'Minted notes:', -mintedNoteList.map((note) => note.id().toString()), -); +.console.log('Minting tokens to Alice...'); +.const mintTxRequest = client.newMintTransactionRequest( +..alice.id(), +..faucet.id(), +..NoteType.Public, +..BigInt(1000), +.); -// 6. Consume minted notes -console.log('Consuming minted notes...'); -const consumeTxRequest = client.newConsumeTransactionRequest(mintedNoteList); +.await client.submitNewTransaction(faucet.id(), mintTxRequest); -await client.submitNewTransaction(alice.id(), consumeTxRequest); +.console.log('Waiting 10 seconds for transaction confirmation...'); +.await new Promise((resolve) => setTimeout(resolve, 10000)); +.await client.syncState(); -await client.syncState(); -console.log('Notes consumed.'); +.// 5. Fetch minted notes +.const mintedNotes = await client.getConsumableNotes(alice.id()); +.const mintedNoteList = mintedNotes.map((n) => n.inputNoteRecord().toNote()); +.console.log( +..'Minted notes:', +..mintedNoteList.map((note) => note.id().toString()), +.); -// 7. Send tokens to Bob -const bobAccountId = Address.fromBech32( -'mtst1apve54rq8ux0jqqqqrkh5y0r0y8cwza6_qruqqypuyph', -).accountId(); -console.log("Sending tokens to Bob's account..."); -const sendTxRequest = client.newSendTransactionRequest( -alice.id(), -bobAccountId, -faucet.id(), -NoteType.Public, -BigInt(100), -); +.// 6. Consume minted notes +.console.log('Consuming minted notes...'); +.const consumeTxRequest = client.newConsumeTransactionRequest(mintedNoteList); + +.await client.submitNewTransaction(alice.id(), consumeTxRequest); + +.await client.syncState(); +.console.log('Notes consumed.'); + +.// 7. Send tokens to Bob +.const bobAccountId = Address.fromBech32( +..'mtst1apve54rq8ux0jqqqqrkh5y0r0y8cwza6_qruqqypuyph', +.).accountId(); +.console.log("Sending tokens to Bob's account..."); +.const sendTxRequest = client.newSendTransactionRequest( +..alice.id(), +..bobAccountId, +..faucet.id(), +..NoteType.Public, +..BigInt(100), +.); -await client.submitNewTransaction(alice.id(), sendTxRequest); -console.log('Tokens sent successfully!'); +.await client.submitNewTransaction(alice.id(), sendTxRequest); +.console.log('Tokens sent successfully!'); }` }, }} reactFilename="lib/react/createMintConsume.tsx" tsFilename="lib/createMintConsume.ts" /> diff --git a/docs/src/web-client/unauthenticated_note_how_to.md b/docs/src/web-client/unauthenticated_note_how_to.md index ff5a4e1..b8c9f79 100644 --- a/docs/src/web-client/unauthenticated_note_how_to.md +++ b/docs/src/web-client/unauthenticated_note_how_to.md @@ -250,190 +250,185 @@ export default function UnauthenticatedNoteTransfer() { .. .); }`}, - typescript: { code:`/\*\* - -- Demonstrates unauthenticated note transfer chain using a local prover on the Miden Network -- Creates a chain of P2ID (Pay to ID) notes: Alice → wallet 1 → wallet 2 → wallet 3 → wallet 4 -- -- @throws {Error} If the function cannot be executed in a browser environment - \*/ - export async function unauthenticatedNoteTransfer(): Promise { - // Ensure this runs only in a browser context - if (typeof window === 'undefined') return console.warn('Run in browser'); - -const { -WebClient, -AccountStorageMode, -AuthScheme, -NoteType, -TransactionProver, -Note, -NoteAssets, -OutputNoteArray, -FungibleAsset, -NoteAndArgsArray, -NoteAndArgs, -NoteAttachment, -TransactionRequestBuilder, -OutputNote, -} = await import('@miden-sdk/miden-sdk'); - -const client = await WebClient.createClient('https://rpc.testnet.miden.io'); -const prover = TransactionProver.newLocalProver(); - -console.log('Latest block:', (await client.syncState()).blockNum()); - -// ── Creating new account ────────────────────────────────────────────────────── -console.log('Creating accounts'); - -console.log('Creating account for Alice…'); -const alice = await client.newWallet( -AccountStorageMode.public(), -true, -AuthScheme.AuthRpoFalcon512, -); -console.log('Alice accout ID:', alice.id().toString()); - -const wallets = []; -for (let i = 0; i < 5; i++) { -const wallet = await client.newWallet( -AccountStorageMode.public(), -true, -AuthScheme.AuthRpoFalcon512, -); -wallets.push(wallet); -console.log('wallet ', i.toString(), wallet.id().toString()); -} + typescript: { code:`/\*\* \* Demonstrates unauthenticated note transfer chain using a local prover on the Miden Network \* Creates a chain of P2ID (Pay to ID) notes: Alice → wallet 1 → wallet 2 → wallet 3 → wallet 4 \* \* @throws {Error} If the function cannot be executed in a browser environment +\*/ +export async function unauthenticatedNoteTransfer(): Promise { +.// Ensure this runs only in a browser context +.if (typeof window === 'undefined') return console.warn('Run in browser'); + +.const { +..WebClient, +..AccountStorageMode, +..AuthScheme, +..NoteType, +..TransactionProver, +..Note, +..NoteAssets, +..OutputNoteArray, +..FungibleAsset, +..NoteAndArgsArray, +..NoteAndArgs, +..NoteAttachment, +..TransactionRequestBuilder, +..OutputNote, +.} = await import('@miden-sdk/miden-sdk'); + +.const client = await WebClient.createClient('https://rpc.testnet.miden.io'); +.const prover = TransactionProver.newLocalProver(); + +.console.log('Latest block:', (await client.syncState()).blockNum()); + +.// ── Creating new account ────────────────────────────────────────────────────── +.console.log('Creating accounts'); + +.console.log('Creating account for Alice…'); +.const alice = await client.newWallet( +..AccountStorageMode.public(), +..true, +..AuthScheme.AuthRpoFalcon512, +.); +.console.log('Alice account ID:', alice.id().toString()); + +.const wallets = []; +.for (let i = 0; i < 5; i++) { +..const wallet = await client.newWallet( +...AccountStorageMode.public(), +...true, +...AuthScheme.AuthRpoFalcon512, +..); +..wallets.push(wallet); +..console.log('wallet ', i.toString(), wallet.id().toString()); +.} + +.// ── Creating new faucet ────────────────────────────────────────────────────── +.const faucet = await client.newFaucet( +..AccountStorageMode.public(), +..false, +..'MID', +..8, +..BigInt(1_000_000), +..AuthScheme.AuthRpoFalcon512, +.); +.console.log('Faucet ID:', faucet.id().toString()); + +.// ── mint 10 000 MID to Alice ────────────────────────────────────────────────────── +.{ +..const txResult = await client.executeTransaction( +...faucet.id(), +...client.newMintTransactionRequest( +....alice.id(), +....faucet.id(), +....NoteType.Public, +....BigInt(10_000), +...), +..); +..const proven = await client.proveTransaction(txResult, prover); +..const submissionHeight = await client.submitProvenTransaction( +...proven, +...txResult, +..); +..await client.applyTransaction(txResult, submissionHeight); +.} + +.console.log('Waiting for settlement'); +.await new Promise((r) => setTimeout(r, 7_000)); +.await client.syncState(); + +.// ── Consume the freshly minted note ────────────────────────────────────────────── +.const noteList = (await client.getConsumableNotes(alice.id())).map((rec) => +..rec.inputNoteRecord().toNote(), +.); -// ── Creating new faucet ────────────────────────────────────────────────────── -const faucet = await client.newFaucet( -AccountStorageMode.public(), -false, -'MID', -8, -BigInt(1_000_000), -AuthScheme.AuthRpoFalcon512, -); -console.log('Faucet ID:', faucet.id().toString()); - -// ── mint 10 000 MID to Alice ────────────────────────────────────────────────────── -{ -const txResult = await client.executeTransaction( -faucet.id(), -client.newMintTransactionRequest( -alice.id(), -faucet.id(), -NoteType.Public, -BigInt(10_000), -), -); -const proven = await client.proveTransaction(txResult, prover); -const submissionHeight = await client.submitProvenTransaction( -proven, -txResult, -); -await client.applyTransaction(txResult, submissionHeight); -} +.{ +..const txResult = await client.executeTransaction( +...alice.id(), +...client.newConsumeTransactionRequest(noteList), +..); +..const proven = await client.proveTransaction(txResult, prover); +..const submissionHeight = await client.submitProvenTransaction( +...proven, +...txResult, +..); +..await client.applyTransaction(txResult, submissionHeight); +..await client.syncState(); +.} + +.// ── Create unauthenticated note transfer chain ───────────────────────────────────────────── +.// Alice → wallet 1 → wallet 2 → wallet 3 → wallet 4 +.for (let i = 0; i < wallets.length; i++) { +..console.log(\`\\nUnauthenticated tx \${i + 1}\`); + +..// Determine sender and receiver for this iteration +..const sender = i === 0 ? alice : wallets[i - 1]; +..const receiver = wallets[i]; + +..console.log('Sender:', sender.id().toString()); +..console.log('Receiver:', receiver.id().toString()); + +..const assets = new NoteAssets([new FungibleAsset(faucet.id(), BigInt(50))]); +..const p2idNote = Note.createP2IDNote( +...sender.id(), +...receiver.id(), +...assets, +...NoteType.Public, +...new NoteAttachment(), +..); + +..const outputP2ID = OutputNote.full(p2idNote); + +..console.log('Creating P2ID note...'); +..{ +...const txResult = await client.executeTransaction( +....sender.id(), +....new TransactionRequestBuilder() +.....withOwnOutputNotes(new OutputNoteArray([outputP2ID])) +.....build(), +...); +...const proven = await client.proveTransaction(txResult, prover); +...const submissionHeight = await client.submitProvenTransaction( +....proven, +....txResult, +...); +...await client.applyTransaction(txResult, submissionHeight); +..} -console.log('Waiting for settlement'); -await new Promise((r) => setTimeout(r, 7_000)); -await client.syncState(); - -// ── Consume the freshly minted note ────────────────────────────────────────────── -const noteList = (await client.getConsumableNotes(alice.id())).map((rec) => -rec.inputNoteRecord().toNote(), -); - -{ -const txResult = await client.executeTransaction( -alice.id(), -client.newConsumeTransactionRequest(noteList), -); -const proven = await client.proveTransaction(txResult, prover); -const submissionHeight = await client.submitProvenTransaction( -proven, -txResult, -); -await client.applyTransaction(txResult, submissionHeight); -await client.syncState(); -} +..console.log('Consuming P2ID note...'); -// ── Create unauthenticated note transfer chain ───────────────────────────────────────────── -// Alice → wallet 1 → wallet 2 → wallet 3 → wallet 4 -for (let i = 0; i < wallets.length; i++) { -console.log(\`\\nUnauthenticated tx \${i + 1}\`); - - // Determine sender and receiver for this iteration - const sender = i === 0 ? alice : wallets[i - 1]; - const receiver = wallets[i]; - - console.log('Sender:', sender.id().toString()); - console.log('Receiver:', receiver.id().toString()); - - const assets = new NoteAssets([new FungibleAsset(faucet.id(), BigInt(50))]); - const p2idNote = Note.createP2IDNote( - sender.id(), - receiver.id(), - assets, - NoteType.Public, - new NoteAttachment(), - ); - - const outputP2ID = OutputNote.full(p2idNote); - - console.log('Creating P2ID note...'); - { - const txResult = await client.executeTransaction( - sender.id(), - new TransactionRequestBuilder() - .withOwnOutputNotes(new OutputNoteArray([outputP2ID])) - .build(), - ); - const proven = await client.proveTransaction(txResult, prover); - const submissionHeight = await client.submitProvenTransaction( - proven, - txResult, - ); - await client.applyTransaction(txResult, submissionHeight); - } - - console.log('Consuming P2ID note...'); - - const noteIdAndArgs = new NoteAndArgs(p2idNote, null); - - const consumeRequest = new TransactionRequestBuilder() - .withInputNotes(new NoteAndArgsArray([noteIdAndArgs])) - .build(); - - { - const txResult = await client.executeTransaction( - receiver.id(), - consumeRequest, - ); - const proven = await client.proveTransaction(txResult, prover); - const submissionHeight = await client.submitProvenTransaction( - proven, - txResult, - ); - const txExecutionResult = await client.applyTransaction( - txResult, - submissionHeight, - ); - - const txId = txExecutionResult - .executedTransaction() - .id() - .toHex() - .toString(); - - console.log( - \`Consumed Note Tx on MidenScan: https://testnet.midenscan.com/tx/\${txId}\`, - ); - } +..const noteIdAndArgs = new NoteAndArgs(p2idNote, null); -} +..const consumeRequest = new TransactionRequestBuilder() +...withInputNotes(new NoteAndArgsArray([noteIdAndArgs])) +...build(); + +..{ +...const txResult = await client.executeTransaction( +....receiver.id(), +....consumeRequest, +...); +...const proven = await client.proveTransaction(txResult, prover); +...const submissionHeight = await client.submitProvenTransaction( +....proven, +....txResult, +...); +...const txExecutionResult = await client.applyTransaction( +....txResult, +....submissionHeight, +...); + +...const txId = txExecutionResult +....executedTransaction() +....id() +....toHex() +....toString(); + +...console.log( +....\`Consumed Note Tx on MidenScan: https://testnet.midenscan.com/tx/\${txId}\`, +...); +..} + +.} -console.log('Asset transfer chain completed ✅'); +.console.log('Asset transfer chain completed ✅'); }` }, }} reactFilename="lib/react/unauthenticatedNoteTransfer.tsx" tsFilename="lib/unauthenticatedNoteTransfer.ts" /> diff --git a/web-client/lib/multiSendWithDelegatedProver.ts b/web-client/lib/multiSendWithDelegatedProver.ts index ef7c019..1fc3673 100644 --- a/web-client/lib/multiSendWithDelegatedProver.ts +++ b/web-client/lib/multiSendWithDelegatedProver.ts @@ -36,7 +36,7 @@ export async function multiSendWithDelegatedProver(): Promise { true, AuthScheme.AuthRpoFalcon512, ); - console.log('Alice accout ID:', alice.id().toString()); + console.log('Alice account ID:', alice.id().toString()); // ── Creating new faucet ────────────────────────────────────────────────────── const faucet = await client.newFaucet( diff --git a/web-client/lib/react/createMintConsume.tsx b/web-client/lib/react/createMintConsume.tsx index aec8a39..2977512 100644 --- a/web-client/lib/react/createMintConsume.tsx +++ b/web-client/lib/react/createMintConsume.tsx @@ -1,3 +1,7 @@ +// Documentation-only example for the "Mint, Consume, and Create Notes" tutorial. +// This component is embedded in docs via CodeSdkTabs and is not wired into the +// test harness (app/page.tsx). The TypeScript equivalent in lib/createMintConsume.ts +// is used for Playwright tests instead. 'use client'; import { MidenProvider, useMiden, useCreateWallet, useCreateFaucet, useMint, useConsume, useSend, useWaitForCommit, useWaitForNotes } from '@miden-sdk/react'; diff --git a/web-client/lib/react/multiSendWithDelegatedProver.tsx b/web-client/lib/react/multiSendWithDelegatedProver.tsx index 98172e2..4226acb 100644 --- a/web-client/lib/react/multiSendWithDelegatedProver.tsx +++ b/web-client/lib/react/multiSendWithDelegatedProver.tsx @@ -1,3 +1,7 @@ +// Documentation-only example for the "Creating Multiple Notes" tutorial. +// This component is embedded in docs via CodeSdkTabs and is not wired into the +// test harness (app/page.tsx). The TypeScript equivalent in +// lib/multiSendWithDelegatedProver.ts is used for Playwright tests instead. 'use client'; import { MidenProvider, useMiden, useCreateWallet, useCreateFaucet, useMint, useConsume, useMultiSend, useWaitForCommit, useWaitForNotes } from '@miden-sdk/react'; @@ -71,7 +75,7 @@ function MultiSendInner() { export default function MultiSendWithDelegatedProver() { return ( - + ); diff --git a/web-client/lib/react/unauthenticatedNoteTransfer.tsx b/web-client/lib/react/unauthenticatedNoteTransfer.tsx index a0c5f51..e3c28db 100644 --- a/web-client/lib/react/unauthenticatedNoteTransfer.tsx +++ b/web-client/lib/react/unauthenticatedNoteTransfer.tsx @@ -1,3 +1,7 @@ +// Documentation-only example for the "Unauthenticated Note Transfer" tutorial. +// This component is embedded in docs via CodeSdkTabs and is not wired into the +// test harness (app/page.tsx). The TypeScript equivalent in +// lib/unauthenticatedNoteTransfer.ts is used for Playwright tests instead. 'use client'; import { MidenProvider, useMiden, useCreateWallet, useCreateFaucet, useMint, useConsume, useInternalTransfer, useWaitForCommit, useWaitForNotes } from '@miden-sdk/react'; diff --git a/web-client/lib/unauthenticatedNoteTransfer.ts b/web-client/lib/unauthenticatedNoteTransfer.ts index d63d915..731c1fa 100644 --- a/web-client/lib/unauthenticatedNoteTransfer.ts +++ b/web-client/lib/unauthenticatedNoteTransfer.ts @@ -39,7 +39,7 @@ export async function unauthenticatedNoteTransfer(): Promise { true, AuthScheme.AuthRpoFalcon512, ); - console.log('Alice accout ID:', alice.id().toString()); + console.log('Alice account ID:', alice.id().toString()); const wallets = []; for (let i = 0; i < 5; i++) { diff --git a/web-client/package.json b/web-client/package.json index 325b44e..2a78ff2 100644 --- a/web-client/package.json +++ b/web-client/package.json @@ -10,7 +10,7 @@ }, "dependencies": { "@miden-sdk/miden-sdk": "0.13.0", - "@miden-sdk/react": "0.13.0", + "@miden-sdk/react": "0.13.2", "next": "15.3.2", "react": "^19.0.0", "react-dom": "^19.0.0" From 8d198a0dafb20d96470ae6d372b8e9aa411bb0ec Mon Sep 17 00:00:00 2001 From: Wiktor Starczewski Date: Wed, 18 Feb 2026 18:38:40 +0100 Subject: [PATCH 17/18] rename contributing.md to CONTRIBUTING.md --- contributing.md => CONTRIBUTING.md | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename contributing.md => CONTRIBUTING.md (100%) diff --git a/contributing.md b/CONTRIBUTING.md similarity index 100% rename from contributing.md rename to CONTRIBUTING.md From 671c9b4ee09616ce9f221e118b269f1c8f49e47d Mon Sep 17 00:00:00 2001 From: Wiktor Starczewski Date: Thu, 19 Feb 2026 14:53:14 +0100 Subject: [PATCH 18/18] fix: address final PR #159 review feedback - Fix preserveIndent() eating method-chain dots by using variable pattern for TransactionRequestBuilder - Fix collapsed JSDoc comments in summary code blocks - Switch multiSend TypeScript to delegated proving (submitNewTransaction) - Document leading-dot limitation in CONTRIBUTING.md --- CONTRIBUTING.md | 17 +++ .../creating_multiple_notes_tutorial.md | 134 ++++++------------ .../web-client/unauthenticated_note_how_to.md | 21 +-- .../lib/multiSendWithDelegatedProver.ts | 60 +++----- web-client/lib/unauthenticatedNoteTransfer.ts | 11 +- 5 files changed, 100 insertions(+), 143 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index c023280..8c199ec 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -83,6 +83,23 @@ export function foo() { | Function call arguments (continuation) | +1 | +2 | | Deeper nesting | 3+ | 6+ | +### Limitation: Leading-Dot Method Chains + +Because `preserveIndent()` converts **all** leading dots to spaces, TypeScript method-chaining syntax (`.withFoo()`, `.build()`) breaks when it appears at the start of a line. The leading `.` is consumed as indentation. + +**Workaround:** Assign the builder to a variable so the `.` appears mid-line: + +``` +// BAD – the leading dots get eaten: +..new TransactionRequestBuilder() +...withOwnOutputNotes(notes) +...build() + +// GOOD – dots are mid-line, preserveIndent ignores them: +..const builder = new TransactionRequestBuilder(); +..const request = builder.withOwnOutputNotes(notes).build(); +``` + ### Tips - Standalone code blocks (` ```ts...``` `) outside `CodeSdkTabs` use normal space indentation -- the dot convention only applies inside `CodeSdkTabs` template literals. diff --git a/docs/src/web-client/creating_multiple_notes_tutorial.md b/docs/src/web-client/creating_multiple_notes_tutorial.md index dae5307..08ed894 100644 --- a/docs/src/web-client/creating_multiple_notes_tutorial.md +++ b/docs/src/web-client/creating_multiple_notes_tutorial.md @@ -19,7 +19,7 @@ The entire flow is wrapped in a helper called `multiSendWithDelegatedProver()` t ## What we'll cover -1. Setting‑up the WebClient and initializing a local prover +1. Setting‑up the WebClient 2. Building three P2ID notes worth 100 `MID` each 3. Submitting the transaction _using delegated proving_ @@ -39,8 +39,8 @@ _How does it work?_ When a user choses to use delegated proving, they send off a Anyone can run their own delegated prover server. If you are building a product on Miden, it may make sense to run your own delegated prover server for your users. To run your own delegated proving server, follow the instructions here: https://crates.io/crates/miden-proving-service -To keep this tutorial runnable without external services, the code below uses a local prover. You -can switch to delegated proving later by swapping in `TransactionProver.newRemoteProver(...)`. +The code below uses `submitNewTransaction`, which handles proving via the network's delegated +proving service. This means your browser never has to generate the full ZK proof locally. ## Step 1: Initialize your Next.js project @@ -131,9 +131,9 @@ export default function Home() { } ``` -## Step 3 — Initalize the WebClient +## Step 3 — Initialize the WebClient -Create `lib/react/multiSendWithDelegatedProver.tsx` (React) or `lib/multiSendWithDelegatedProver.ts` (TypeScript) and add the following code. This snippet initializes the WebClient along with a local prover. +Create `lib/react/multiSendWithDelegatedProver.tsx` (React) or `lib/multiSendWithDelegatedProver.ts` (TypeScript) and add the following code. This snippet initializes the WebClient. ``` mkdir -p lib @@ -184,7 +184,6 @@ export default function MultiSendWithDelegatedProver() { ..AuthScheme, ..Address, ..NoteType, -..TransactionProver, ..Note, ..NoteAssets, ..OutputNoteArray, @@ -195,7 +194,6 @@ export default function MultiSendWithDelegatedProver() { .} = await import('@miden-sdk/miden-sdk'); .const client = await WebClient.createClient('https://rpc.testnet.miden.io'); -.const prover = TransactionProver.newLocalProver(); .console.log('Latest block:', (await client.syncState()).blockNum()); }` }, @@ -258,46 +256,29 @@ const faucet = await client.newFaucet( console.log('Faucet ID:', faucet.id().toString()); // ── mint 10 000 MID to Alice ────────────────────────────────────────────────────── -{ -.const txResult = await client.executeTransaction( +await client.submitNewTransaction( +.faucet.id(), +.client.newMintTransactionRequest( +..alice.id(), ..faucet.id(), -..client.newMintTransactionRequest( -...alice.id(), -...faucet.id(), -...NoteType.Public, -...BigInt(10_000), -..), -.); -.const proven = await client.proveTransaction(txResult, prover); -.const submissionHeight = await client.submitProvenTransaction( -..proven, -..txResult, -.); -.await client.applyTransaction(txResult, submissionHeight); +..NoteType.Public, +..BigInt(10_000), +.), +); -.console.log('waiting for settlement'); -.await new Promise((r) => setTimeout(r, 7_000)); -.await client.syncState(); -} +console.log('waiting for settlement'); +await new Promise((r) => setTimeout(r, 7_000)); +await client.syncState(); // ── consume the freshly minted notes ────────────────────────────────────────────── const noteList = (await client.getConsumableNotes(alice.id())).map((rec) => .rec.inputNoteRecord().toNote(), ); -{ -.const txResult = await client.executeTransaction( -..alice.id(), -..client.newConsumeTransactionRequest(noteList), -.); -.const proven = await client.proveTransaction(txResult, prover); -.await client.syncState(); -.const submissionHeight = await client.submitProvenTransaction( -..proven, -..txResult, -.); -.await client.applyTransaction(txResult, submissionHeight); -}` }, +await client.submitNewTransaction( +.alice.id(), +.client.newConsumeTransactionRequest(noteList), +);` }, }} reactFilename="lib/react/multiSendWithDelegatedProver.tsx" tsFilename="lib/multiSendWithDelegatedProver.ts" /> ## Step 5 — Build and Create P2ID notes @@ -341,12 +322,9 @@ const p2idNotes = recipientAddresses.map((addr) => { }); // ── create all P2ID notes ─────────────────────────────────────────────────────────────── -await client.submitNewTransaction( -.alice.id(), -.new TransactionRequestBuilder() -..withOwnOutputNotes(new OutputNoteArray(p2idNotes)) -..build(), -); +const builder = new TransactionRequestBuilder(); +const txRequest = builder.withOwnOutputNotes(new OutputNoteArray(p2idNotes)).build(); +await client.submitNewTransaction(alice.id(), txRequest); console.log('All notes created ✅');` }, }} reactFilename="lib/react/multiSendWithDelegatedProver.tsx" tsFilename="lib/multiSendWithDelegatedProver.ts" /> @@ -434,8 +412,12 @@ export default function MultiSendWithDelegatedProver() { .. .); }`}, - typescript: { code:`/\*\* \* Demonstrates multi-send functionality using a local prover on the Miden Network \* Creates multiple P2ID (Pay to ID) notes for different recipients \* \* @throws {Error} If the function cannot be executed in a browser environment -\*/ + typescript: { code:`/\*\* +.\* Demonstrates multi-send functionality with delegated proving on the Miden Network +.\* Creates multiple P2ID (Pay to ID) notes for different recipients +.\* +.\* @throws {Error} If the function cannot be executed in a browser environment +.\*/ export async function multiSendWithDelegatedProver(): Promise { .// Ensure this runs only in a browser context .if (typeof window === 'undefined') return console.warn('Run in browser'); @@ -446,7 +428,6 @@ export async function multiSendWithDelegatedProver(): Promise { ..AuthScheme, ..Address, ..NoteType, -..TransactionProver, ..Note, ..NoteAssets, ..OutputNoteArray, @@ -457,7 +438,6 @@ export async function multiSendWithDelegatedProver(): Promise { .} = await import('@miden-sdk/miden-sdk'); .const client = await WebClient.createClient('https://rpc.testnet.miden.io'); -.const prover = TransactionProver.newLocalProver(); .console.log('Latest block:', (await client.syncState()).blockNum()); @@ -482,46 +462,29 @@ export async function multiSendWithDelegatedProver(): Promise { .console.log('Faucet ID:', faucet.id().toString()); .// ── mint 10 000 MID to Alice ────────────────────────────────────────────────────── -.{ -..const txResult = await client.executeTransaction( +.await client.submitNewTransaction( +..faucet.id(), +..client.newMintTransactionRequest( +...alice.id(), ...faucet.id(), -...client.newMintTransactionRequest( -....alice.id(), -....faucet.id(), -....NoteType.Public, -....BigInt(10_000), -...), -..); -..const proven = await client.proveTransaction(txResult, prover); -..const submissionHeight = await client.submitProvenTransaction( -...proven, -...txResult, -..); -..await client.applyTransaction(txResult, submissionHeight); +...NoteType.Public, +...BigInt(10_000), +..), +.); -..console.log('waiting for settlement'); -..await new Promise((r) => setTimeout(r, 7_000)); -..await client.syncState(); -.} +.console.log('waiting for settlement'); +.await new Promise((r) => setTimeout(r, 7_000)); +.await client.syncState(); .// ── consume the freshly minted notes ────────────────────────────────────────────── .const noteList = (await client.getConsumableNotes(alice.id())).map((rec) => ..rec.inputNoteRecord().toNote(), .); -.{ -..const txResult = await client.executeTransaction( -...alice.id(), -...client.newConsumeTransactionRequest(noteList), -..); -..const proven = await client.proveTransaction(txResult, prover); -..await client.syncState(); -..const submissionHeight = await client.submitProvenTransaction( -...proven, -...txResult, -..); -..await client.applyTransaction(txResult, submissionHeight); -.} +.await client.submitNewTransaction( +..alice.id(), +..client.newConsumeTransactionRequest(noteList), +.); .// ── build 3 P2ID notes (100 MID each) ───────────────────────────────────────────── .const recipientAddresses = [ @@ -546,12 +509,9 @@ export async function multiSendWithDelegatedProver(): Promise { .}); .// ── create all P2ID notes ─────────────────────────────────────────────────────────────── -.await client.submitNewTransaction( -..alice.id(), -..new TransactionRequestBuilder() -...withOwnOutputNotes(new OutputNoteArray(p2idNotes)) -...build(), -.); +.const builder = new TransactionRequestBuilder(); +.const txRequest = builder.withOwnOutputNotes(new OutputNoteArray(p2idNotes)).build(); +.await client.submitNewTransaction(alice.id(), txRequest); .console.log('All notes created ✅'); }` }, diff --git a/docs/src/web-client/unauthenticated_note_how_to.md b/docs/src/web-client/unauthenticated_note_how_to.md index b8c9f79..c574355 100644 --- a/docs/src/web-client/unauthenticated_note_how_to.md +++ b/docs/src/web-client/unauthenticated_note_how_to.md @@ -250,8 +250,12 @@ export default function UnauthenticatedNoteTransfer() { .. .); }`}, - typescript: { code:`/\*\* \* Demonstrates unauthenticated note transfer chain using a local prover on the Miden Network \* Creates a chain of P2ID (Pay to ID) notes: Alice → wallet 1 → wallet 2 → wallet 3 → wallet 4 \* \* @throws {Error} If the function cannot be executed in a browser environment -\*/ + typescript: { code:`/\*\* +.\* Demonstrates unauthenticated note transfer chain using a local prover on the Miden Network +.\* Creates a chain of P2ID (Pay to ID) notes: Alice → wallet 1 → wallet 2 → wallet 3 → wallet 4 +.\* +.\* @throws {Error} If the function cannot be executed in a browser environment +.\*/ export async function unauthenticatedNoteTransfer(): Promise { .// Ensure this runs only in a browser context .if (typeof window === 'undefined') return console.warn('Run in browser'); @@ -378,11 +382,11 @@ export async function unauthenticatedNoteTransfer(): Promise { ..console.log('Creating P2ID note...'); ..{ +...const builder = new TransactionRequestBuilder(); +...const request = builder.withOwnOutputNotes(new OutputNoteArray([outputP2ID])).build(); ...const txResult = await client.executeTransaction( ....sender.id(), -....new TransactionRequestBuilder() -.....withOwnOutputNotes(new OutputNoteArray([outputP2ID])) -.....build(), +....request, ...); ...const proven = await client.proveTransaction(txResult, prover); ...const submissionHeight = await client.submitProvenTransaction( @@ -396,9 +400,8 @@ export async function unauthenticatedNoteTransfer(): Promise { ..const noteIdAndArgs = new NoteAndArgs(p2idNote, null); -..const consumeRequest = new TransactionRequestBuilder() -...withInputNotes(new NoteAndArgsArray([noteIdAndArgs])) -...build(); +..const consumeBuilder = new TransactionRequestBuilder(); +..const consumeRequest = consumeBuilder.withInputNotes(new NoteAndArgsArray([noteIdAndArgs])).build(); ..{ ...const txResult = await client.executeTransaction( @@ -523,7 +526,7 @@ Wallet 5 balance: 50 MID Unauthenticated notes on Miden offer a powerful mechanism for achieving faster asset settlements by allowing notes to be both created and consumed within the same block. In this guide, we walked through: -- **Setting up the Miden WebClient** with delegated proving for optimal performance +- **Setting up the Miden WebClient** with a local prover - **Creating P2ID Notes** for targeted asset transfers between specific accounts - **Building Transaction Chains** using unauthenticated input notes for sub-blocktime settlement - **Performance Observations** demonstrating how unauthenticated notes enable faster-than-blocktime transfers diff --git a/web-client/lib/multiSendWithDelegatedProver.ts b/web-client/lib/multiSendWithDelegatedProver.ts index 1fc3673..08a2bad 100644 --- a/web-client/lib/multiSendWithDelegatedProver.ts +++ b/web-client/lib/multiSendWithDelegatedProver.ts @@ -1,5 +1,5 @@ /** - * Demonstrates multi-send functionality using a local prover on the Miden Network + * Demonstrates multi-send functionality with delegated proving on the Miden Network * Creates multiple P2ID (Pay to ID) notes for different recipients * * @throws {Error} If the function cannot be executed in a browser environment @@ -14,7 +14,6 @@ export async function multiSendWithDelegatedProver(): Promise { AuthScheme, Address, NoteType, - TransactionProver, Note, NoteAssets, OutputNoteArray, @@ -25,7 +24,6 @@ export async function multiSendWithDelegatedProver(): Promise { } = await import('@miden-sdk/miden-sdk'); const client = await WebClient.createClient('https://rpc.testnet.miden.io'); - const prover = TransactionProver.newLocalProver(); console.log('Latest block:', (await client.syncState()).blockNum()); @@ -50,46 +48,29 @@ export async function multiSendWithDelegatedProver(): Promise { console.log('Faucet ID:', faucet.id().toString()); // ── mint 10 000 MID to Alice ────────────────────────────────────────────────────── - { - const txResult = await client.executeTransaction( + await client.submitNewTransaction( + faucet.id(), + client.newMintTransactionRequest( + alice.id(), faucet.id(), - client.newMintTransactionRequest( - alice.id(), - faucet.id(), - NoteType.Public, - BigInt(10_000), - ), - ); - const proven = await client.proveTransaction(txResult, prover); - const submissionHeight = await client.submitProvenTransaction( - proven, - txResult, - ); - await client.applyTransaction(txResult, submissionHeight); + NoteType.Public, + BigInt(10_000), + ), + ); - console.log('waiting for settlement'); - await new Promise((r) => setTimeout(r, 7_000)); - await client.syncState(); - } + console.log('waiting for settlement'); + await new Promise((r) => setTimeout(r, 7_000)); + await client.syncState(); // ── consume the freshly minted notes ────────────────────────────────────────────── const noteList = (await client.getConsumableNotes(alice.id())).map((rec) => rec.inputNoteRecord().toNote(), ); - { - const txResult = await client.executeTransaction( - alice.id(), - client.newConsumeTransactionRequest(noteList), - ); - const proven = await client.proveTransaction(txResult, prover); - await client.syncState(); - const submissionHeight = await client.submitProvenTransaction( - proven, - txResult, - ); - await client.applyTransaction(txResult, submissionHeight); - } + await client.submitNewTransaction( + alice.id(), + client.newConsumeTransactionRequest(noteList), + ); // ── build 3 P2ID notes (100 MID each) ───────────────────────────────────────────── const recipientAddresses = [ @@ -114,12 +95,9 @@ export async function multiSendWithDelegatedProver(): Promise { }); // ── create all P2ID notes ─────────────────────────────────────────────────────────────── - await client.submitNewTransaction( - alice.id(), - new TransactionRequestBuilder() - .withOwnOutputNotes(new OutputNoteArray(p2idNotes)) - .build(), - ); + const builder = new TransactionRequestBuilder(); + const txRequest = builder.withOwnOutputNotes(new OutputNoteArray(p2idNotes)).build(); + await client.submitNewTransaction(alice.id(), txRequest); console.log('All notes created ✅'); } diff --git a/web-client/lib/unauthenticatedNoteTransfer.ts b/web-client/lib/unauthenticatedNoteTransfer.ts index 731c1fa..fb7fdc9 100644 --- a/web-client/lib/unauthenticatedNoteTransfer.ts +++ b/web-client/lib/unauthenticatedNoteTransfer.ts @@ -130,11 +130,11 @@ export async function unauthenticatedNoteTransfer(): Promise { console.log('Creating P2ID note...'); { + const builder = new TransactionRequestBuilder(); + const request = builder.withOwnOutputNotes(new OutputNoteArray([outputP2ID])).build(); const txResult = await client.executeTransaction( sender.id(), - new TransactionRequestBuilder() - .withOwnOutputNotes(new OutputNoteArray([outputP2ID])) - .build(), + request, ); const proven = await client.proveTransaction(txResult, prover); const submissionHeight = await client.submitProvenTransaction( @@ -148,9 +148,8 @@ export async function unauthenticatedNoteTransfer(): Promise { const noteIdAndArgs = new NoteAndArgs(p2idNote, null); - const consumeRequest = new TransactionRequestBuilder() - .withInputNotes(new NoteAndArgsArray([noteIdAndArgs])) - .build(); + const consumeBuilder = new TransactionRequestBuilder(); + const consumeRequest = consumeBuilder.withInputNotes(new NoteAndArgsArray([noteIdAndArgs])).build(); { const txResult = await client.executeTransaction(