From 6fc73778223b2f3480a9dbb5551bc5a46e3cbe16 Mon Sep 17 00:00:00 2001 From: Wiktor Starczewski Date: Mon, 2 Mar 2026 23:42:51 +0100 Subject: [PATCH 1/9] docs: update tutorials for simplified notes API - listAvailable now returns InputNoteRecord[] directly - consume accepts notes array instead of noteIds - Remove .inputNoteRecord() and .map(n => n.id()) boilerplate --- .../creating_multiple_notes_tutorial.md | 10 ++++------ .../web-client/mint_consume_create_tutorial.md | 18 ++++++++---------- docs/src/web-client/react_wallet_tutorial.md | 6 +++--- .../web-client/unauthenticated_note_how_to.md | 7 +++---- web-client/lib/createMintConsume.ts | 4 ++-- web-client/lib/multiSendWithDelegatedProver.ts | 2 +- web-client/lib/react/createMintConsume.tsx | 5 ++--- .../lib/react/multiSendWithDelegatedProver.tsx | 3 +-- .../lib/react/unauthenticatedNoteTransfer.tsx | 5 ++--- web-client/lib/unauthenticatedNoteTransfer.ts | 2 +- 10 files changed, 27 insertions(+), 35 deletions(-) diff --git a/docs/src/web-client/creating_multiple_notes_tutorial.md b/docs/src/web-client/creating_multiple_notes_tutorial.md index b9e2e77..f869ab4 100644 --- a/docs/src/web-client/creating_multiple_notes_tutorial.md +++ b/docs/src/web-client/creating_multiple_notes_tutorial.md @@ -231,8 +231,7 @@ await waitForCommit(mintResult.transactionId); // 4. Consume the freshly minted notes const notes = await waitForConsumableNotes({ accountId: aliceId }); -const noteIds = notes.map((n) => n.inputNoteRecord().id()); -await consume({ accountId: aliceId, noteIds });`}, +await consume({ accountId: aliceId, notes });`}, typescript: { code:`// ── Creating new account ────────────────────────────────────────────────────── console.log('Creating account for Alice…'); const alice = await client.accounts.create({ @@ -267,7 +266,7 @@ await client.sync(); const noteList = await client.notes.listAvailable({ account: alice }); await client.transactions.consume({ .account: alice, -.notes: noteList.map((n) => n.inputNoteRecord()), +.notes: noteList, });` }, }} reactFilename="lib/react/multiSendWithDelegatedProver.tsx" tsFilename="lib/multiSendWithDelegatedProver.ts" /> @@ -363,8 +362,7 @@ function MultiSendInner() { ..// 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 }); +..await consume({ accountId: aliceId, notes }); ..// 5. Send 100 MID to three recipients in a single transaction ..await sendMany({ @@ -457,7 +455,7 @@ export async function multiSendWithDelegatedProver(): Promise { .const noteList = await client.notes.listAvailable({ account: alice }); .await client.transactions.consume({ ..account: alice, -..notes: noteList.map((n) => n.inputNoteRecord()), +..notes: noteList, .}); .// ── build 3 P2ID notes (100 MID each) ───────────────────────────────────────────── diff --git a/docs/src/web-client/mint_consume_create_tutorial.md b/docs/src/web-client/mint_consume_create_tutorial.md index 7204035..62ede86 100644 --- a/docs/src/web-client/mint_consume_create_tutorial.md +++ b/docs/src/web-client/mint_consume_create_tutorial.md @@ -81,15 +81,14 @@ To identify notes that are ready to consume, the MidenClient provides the `clien n.inputNoteRecord().id()); -console.log('Consumable notes:', noteIds.length);` }, +console.log('Consumable notes:', notes.length);` }, typescript: { code: `// 5. Find notes available for consumption const mintedNotes = await client.notes.listAvailable({ account: alice }); console.log(\`Found \${mintedNotes.length} note(s) to consume\`); console.log( .'Minted notes:', -.mintedNotes.map((n) => n.inputNoteRecord().id().toString()), +.mintedNotes.map((n) => n.id().toString()), );` }, }} reactFilename="lib/react/createMintConsume.tsx" tsFilename="lib/createMintConsume.ts" /> @@ -100,13 +99,13 @@ Now let's consume the notes to add the tokens to Alice's account balance: n.inputNoteRecord()), +.notes: mintedNotes, }); await client.sync(); @@ -209,12 +208,11 @@ function CreateMintConsumeInner() { ..// 5. Wait for consumable notes to appear ..const notes = await waitForConsumableNotes({ accountId: aliceId }); -..const noteIds = notes.map((n) => n.inputNoteRecord().id()); -..console.log('Consumable notes:', noteIds.length); +..console.log('Consumable notes:', notes.length); ..// 6. Consume minted notes ..console.log('Consuming minted notes...'); -..await consume({ accountId: aliceId, noteIds }); +..await consume({ accountId: alice, notes }); ..console.log('Notes consumed.'); ..// 7. Send 100 tokens to Bob @@ -303,14 +301,14 @@ export async function createMintConsume(): Promise { .const mintedNotes = await client.notes.listAvailable({ account: alice }); .console.log( ..'Minted notes:', -..mintedNotes.map((n) => n.inputNoteRecord().id().toString()), +..mintedNotes.map((n) => n.id().toString()), .); .// 6. Consume minted notes .console.log('Consuming minted notes...'); .await client.transactions.consume({ ..account: alice, -..notes: mintedNotes.map((n) => n.inputNoteRecord()), +..notes: mintedNotes, .}); .await client.sync(); diff --git a/docs/src/web-client/react_wallet_tutorial.md b/docs/src/web-client/react_wallet_tutorial.md index 7fc2a08..d368c1d 100644 --- a/docs/src/web-client/react_wallet_tutorial.md +++ b/docs/src/web-client/react_wallet_tutorial.md @@ -285,7 +285,7 @@ function UnclaimedNotes({ const { consume, isLoading: isConsuming } = useConsume(); const claimNote = (id: string) => () => { - consume({ accountId, noteIds: [id] }); + consume({ accountId, notes: [id] }); }; return ( @@ -312,7 +312,7 @@ function UnclaimedNotes({ The `useConsume()` hook returns: -- `consume({ accountId, noteIds })`: Function to consume one or more notes +- `consume({ accountId, notes })`: Function to consume one or more notes - `isLoading`: `true` while notes are being consumed --- @@ -534,7 +534,7 @@ function Wallet({ accountId }: { accountId: string }) { } }; - const claimNote = (id: string) => () => consume({ accountId, noteIds: [id] }); + const claimNote = (id: string) => () => consume({ accountId, notes: [id] }); const onAssetChange = (event: ChangeEvent) => setAssetId(event.target.value); const onNoteTypeChange = (event: ChangeEvent) => diff --git a/docs/src/web-client/unauthenticated_note_how_to.md b/docs/src/web-client/unauthenticated_note_how_to.md index bb0f7bf..3c40ea0 100644 --- a/docs/src/web-client/unauthenticated_note_how_to.md +++ b/docs/src/web-client/unauthenticated_note_how_to.md @@ -210,8 +210,7 @@ function UnauthenticatedNoteTransferInner() { ..// 4. Consume the freshly minted notes ..const notes = await waitForConsumableNotes({ accountId: alice }); -..const noteIds = notes.map((n) => n.inputNoteRecord().id()); -..await consume({ accountId: alice, noteIds }); +..await consume({ accountId: alice, notes }); ..// 5. Create the unauthenticated note transfer chain: ..// Alice → Wallet 0 → Wallet 1 → Wallet 2 → Wallet 3 → Wallet 4 @@ -227,7 +226,7 @@ function UnauthenticatedNoteTransferInner() { ....noteType: NoteVisibility.Public, ....authenticated: false, ...}); -...const result = await consume({ accountId: wallet, noteIds: [note] }); +...const result = await consume({ accountId: wallet, notes: [note] }); ...console.log( ....\`Transfer \${i + 1}: https://testnet.midenscan.com/tx/\${result.transactionId}\`, ...); @@ -321,7 +320,7 @@ export async function unauthenticatedNoteTransfer(): Promise { .const noteList = await client.notes.listAvailable({ account: alice }); .await client.transactions.consume({ ..account: alice, -..notes: noteList.map((n) => n.inputNoteRecord()), +..notes: noteList, .}); .await client.sync(); diff --git a/web-client/lib/createMintConsume.ts b/web-client/lib/createMintConsume.ts index 86b0b95..d058095 100644 --- a/web-client/lib/createMintConsume.ts +++ b/web-client/lib/createMintConsume.ts @@ -54,14 +54,14 @@ export async function createMintConsume(): Promise { const mintedNotes = await client.notes.listAvailable({ account: alice }); console.log( 'Minted notes:', - mintedNotes.map((n) => n.inputNoteRecord().id().toString()), + mintedNotes.map((n) => n.id().toString()), ); // 6. Consume minted notes console.log('Consuming minted notes...'); await client.transactions.consume({ account: alice, - notes: mintedNotes.map((n) => n.inputNoteRecord()), + notes: mintedNotes, }); await client.sync(); diff --git a/web-client/lib/multiSendWithDelegatedProver.ts b/web-client/lib/multiSendWithDelegatedProver.ts index a6912ac..6c9a31b 100644 --- a/web-client/lib/multiSendWithDelegatedProver.ts +++ b/web-client/lib/multiSendWithDelegatedProver.ts @@ -58,7 +58,7 @@ export async function multiSendWithDelegatedProver(): Promise { const noteList = await client.notes.listAvailable({ account: alice }); await client.transactions.consume({ account: alice, - notes: noteList.map((n) => n.inputNoteRecord()), + notes: noteList, }); // ── build 3 P2ID notes (100 MID each) ───────────────────────────────────────────── diff --git a/web-client/lib/react/createMintConsume.tsx b/web-client/lib/react/createMintConsume.tsx index 3c27946..bfa8f7e 100644 --- a/web-client/lib/react/createMintConsume.tsx +++ b/web-client/lib/react/createMintConsume.tsx @@ -48,12 +48,11 @@ function CreateMintConsumeInner() { // 5. Wait for consumable notes to appear const notes = await waitForConsumableNotes({ accountId: alice }); - const noteIds = notes.map((n) => n.inputNoteRecord().id()); - console.log('Consumable notes:', noteIds.length); + console.log('Consumable notes:', notes.length); // 6. Consume minted notes console.log('Consuming minted notes...'); - await consume({ accountId: alice, noteIds }); + await consume({ accountId: alice, notes }); console.log('Notes consumed.'); // 7. Send 100 tokens to Bob diff --git a/web-client/lib/react/multiSendWithDelegatedProver.tsx b/web-client/lib/react/multiSendWithDelegatedProver.tsx index f6c1667..f651f1c 100644 --- a/web-client/lib/react/multiSendWithDelegatedProver.tsx +++ b/web-client/lib/react/multiSendWithDelegatedProver.tsx @@ -45,8 +45,7 @@ function MultiSendInner() { // 4. Consume the freshly minted notes const notes = await waitForConsumableNotes({ accountId: alice }); - const noteIds = notes.map((n) => n.inputNoteRecord().id()); - await consume({ accountId: alice, noteIds }); + await consume({ accountId: alice, notes }); // 5. Send 100 MID to three recipients in a single transaction await sendMany({ diff --git a/web-client/lib/react/unauthenticatedNoteTransfer.tsx b/web-client/lib/react/unauthenticatedNoteTransfer.tsx index ada2b87..b820876 100644 --- a/web-client/lib/react/unauthenticatedNoteTransfer.tsx +++ b/web-client/lib/react/unauthenticatedNoteTransfer.tsx @@ -52,8 +52,7 @@ function UnauthenticatedNoteTransferInner() { // 4. Consume the freshly minted notes const notes = await waitForConsumableNotes({ accountId: alice }); - const noteIds = notes.map((n) => n.inputNoteRecord().id()); - await consume({ accountId: alice, noteIds }); + await consume({ accountId: alice, notes }); // 5. Create the unauthenticated note transfer chain: // Alice → Wallet 0 → Wallet 1 → Wallet 2 → Wallet 3 → Wallet 4 @@ -70,7 +69,7 @@ function UnauthenticatedNoteTransferInner() { authenticated: false, }); - const result = await consume({ accountId: wallet, noteIds: [note!] }); + const result = await consume({ accountId: wallet, notes: [note!] }); console.log( `Transfer ${i + 1}: https://testnet.midenscan.com/tx/${result.transactionId}`, ); diff --git a/web-client/lib/unauthenticatedNoteTransfer.ts b/web-client/lib/unauthenticatedNoteTransfer.ts index fc05b41..aeab5e4 100644 --- a/web-client/lib/unauthenticatedNoteTransfer.ts +++ b/web-client/lib/unauthenticatedNoteTransfer.ts @@ -68,7 +68,7 @@ export async function unauthenticatedNoteTransfer(): Promise { const noteList = await client.notes.listAvailable({ account: alice }); await client.transactions.consume({ account: alice, - notes: noteList.map((n) => n.inputNoteRecord()), + notes: noteList, }); await client.sync(); From 3d26469710f7f549b0066f96c07c527a1d6a57d4 Mon Sep 17 00:00:00 2001 From: Wiktor Starczewski Date: Mon, 2 Mar 2026 23:45:03 +0100 Subject: [PATCH 2/9] docs: simplify getOrImport to accept bech32 strings directly MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Remove Address.fromBech32(...).accountId() ceremony — getOrImport already accepts AccountRef (which includes bech32 strings). --- docs/src/web-client/counter_contract_tutorial.md | 11 ++--------- .../foreign_procedure_invocation_tutorial.md | 10 ++-------- web-client/lib/mintTestnetToAddress.ts | 6 ++---- 3 files changed, 6 insertions(+), 21 deletions(-) diff --git a/docs/src/web-client/counter_contract_tutorial.md b/docs/src/web-client/counter_contract_tutorial.md index 0c1a1c0..7b0ad93 100644 --- a/docs/src/web-client/counter_contract_tutorial.md +++ b/docs/src/web-client/counter_contract_tutorial.md @@ -118,7 +118,6 @@ export async function incrementCounterContract(): Promise { // dynamic import → only in the browser, so WASM is loaded client‑side const { AccountType, - Address, AuthSecretKey, StorageMode, StorageSlot, @@ -165,15 +164,9 @@ export async function incrementCounterContract(): Promise { end `; - // Counter contract account id on testnet - const counterContractId = Address.fromBech32( - 'mtst1arjemrxne8lj5qz4mg9c8mtyxg954483', - ).accountId(); - - // Reading the public state of the counter contract from testnet, - // and importing it into the client + // Import the counter contract from testnet by its bech32 address const counterContractAccount = - await client.accounts.getOrImport(counterContractId); + await client.accounts.getOrImport('mtst1arjemrxne8lj5qz4mg9c8mtyxg954483'); const counterSlotName = 'miden::tutorials::counter'; diff --git a/docs/src/web-client/foreign_procedure_invocation_tutorial.md b/docs/src/web-client/foreign_procedure_invocation_tutorial.md index ed82799..2f498c4 100644 --- a/docs/src/web-client/foreign_procedure_invocation_tutorial.md +++ b/docs/src/web-client/foreign_procedure_invocation_tutorial.md @@ -133,7 +133,6 @@ export async function foreignProcedureInvocation(): Promise { // dynamic import → only in the browser, so WASM is loaded client‑side const { AccountType, - Address, AuthSecretKey, StorageMode, StorageSlot, @@ -211,14 +210,9 @@ export async function foreignProcedureInvocation(): Promise { // ------------------------------------------------------------------------- console.log('\n[STEP 2] Building counter contract from public state'); - // Define the Counter Contract account id from counter contract deploy - const counterContractId = Address.fromBech32( - 'mtst1arjemrxne8lj5qz4mg9c8mtyxg954483', - ).accountId(); - - // Import the counter contract + // Import the counter contract from testnet by its bech32 address const counterContractAccount = - await client.accounts.getOrImport(counterContractId); + await client.accounts.getOrImport('mtst1arjemrxne8lj5qz4mg9c8mtyxg954483'); console.log( 'Account storage slot:', counterContractAccount.storage().getItem(counterSlotName)?.toHex(), diff --git a/web-client/lib/mintTestnetToAddress.ts b/web-client/lib/mintTestnetToAddress.ts index 90ddaf1..28748ad 100644 --- a/web-client/lib/mintTestnetToAddress.ts +++ b/web-client/lib/mintTestnetToAddress.ts @@ -12,7 +12,6 @@ export async function mintTestnetToAddress(): Promise { AccountType, NoteVisibility, StorageMode, - Address, } = await import('@miden-sdk/miden-sdk'); const client = await MidenClient.create({ @@ -37,13 +36,12 @@ export async function mintTestnetToAddress(): Promise { // ── Mint to recipient ─────────────────────────────────────────────────────── const recipientAddress = 'mtst1apve54rq8ux0jqqqqrkh5y0r0y8cwza6_qruqqypuyph'; - const recipientAccountId = Address.fromBech32(recipientAddress).accountId(); - console.log('Recipient account ID:', recipientAccountId.toString()); + console.log('Recipient address:', recipientAddress); console.log('Minting 100 MIDEN tokens...'); const mintTxId = await client.transactions.mint({ account: faucet, - to: recipientAccountId, + to: recipientAddress, amount: BigInt(100), type: NoteVisibility.Public, }); From 79973150faa9fdf7304fc82510c4ebcb7c456fe2 Mon Sep 17 00:00:00 2001 From: Wiktor Starczewski Date: Mon, 2 Mar 2026 23:49:35 +0100 Subject: [PATCH 3/9] docs: replace hex dance with Word.toU64s() for counter values --- docs/src/web-client/counter_contract_tutorial.md | 5 +---- .../foreign_procedure_invocation_tutorial.md | 12 +----------- web-client/lib/foreignProcedureInvocation.ts | 12 +----------- web-client/lib/incrementCounterContract.ts | 4 +--- 4 files changed, 4 insertions(+), 29 deletions(-) diff --git a/docs/src/web-client/counter_contract_tutorial.md b/docs/src/web-client/counter_contract_tutorial.md index 7b0ad93..c6e3052 100644 --- a/docs/src/web-client/counter_contract_tutorial.md +++ b/docs/src/web-client/counter_contract_tutorial.md @@ -226,10 +226,7 @@ export async function incrementCounterContract(): Promise { // A word is comprised of 4 Felts, 2**64 - 2**32 + 1 const count = counter?.storage().getItem(counterSlotName); - // Converting the Word represented as a hex to a single integer value - const counterValue = Number( - BigInt('0x' + count!.toHex().slice(-16).match(/../g)!.reverse().join('')), - ); + const counterValue = Number(count!.toU64s()[3]); console.log('Count: ', counterValue); } diff --git a/docs/src/web-client/foreign_procedure_invocation_tutorial.md b/docs/src/web-client/foreign_procedure_invocation_tutorial.md index 2f498c4..b707a04 100644 --- a/docs/src/web-client/foreign_procedure_invocation_tutorial.md +++ b/docs/src/web-client/foreign_procedure_invocation_tutorial.md @@ -341,17 +341,7 @@ export async function foreignProcedureInvocation(): Promise { ?.storage() .getItem(countReaderSlotName); if (countReaderStorage) { - const countValue = Number( - BigInt( - '0x' + - countReaderStorage - .toHex() - .slice(-16) - .match(/../g)! - .reverse() - .join(''), - ), - ); + const countValue = Number(countReaderStorage.toU64s()[3]); console.log('Count copied via Foreign Procedure Invocation:', countValue); } diff --git a/web-client/lib/foreignProcedureInvocation.ts b/web-client/lib/foreignProcedureInvocation.ts index 47ded5b..bc9232f 100644 --- a/web-client/lib/foreignProcedureInvocation.ts +++ b/web-client/lib/foreignProcedureInvocation.ts @@ -199,17 +199,7 @@ export async function foreignProcedureInvocation(): Promise { .getItem(countReaderSlotName); if (countReaderStorage) { - const countValue = Number( - BigInt( - '0x' + - countReaderStorage - .toHex() - .slice(-16) - .match(/../g)! - .reverse() - .join(''), - ), - ); + const countValue = Number(countReaderStorage.toU64s()[3]); console.log('Count copied via Foreign Procedure Invocation:', countValue); } diff --git a/web-client/lib/incrementCounterContract.ts b/web-client/lib/incrementCounterContract.ts index 8c36252..4747292 100644 --- a/web-client/lib/incrementCounterContract.ts +++ b/web-client/lib/incrementCounterContract.ts @@ -91,8 +91,6 @@ export async function incrementCounterContract(): Promise { const counter = await client.accounts.get(account); const count = counter?.storage().getItem(counterSlotName); - const counterValue = Number( - BigInt('0x' + count!.toHex().slice(-16).match(/../g)!.reverse().join('')), - ); + const counterValue = Number(count!.toU64s()[3]); console.log('Count: ', counterValue); } From 4f0d48d824c2a0f1fdbf88dfae1212b603159c9b Mon Sep 17 00:00:00 2001 From: Wiktor Starczewski Date: Tue, 3 Mar 2026 00:21:42 +0100 Subject: [PATCH 4/9] refactor(web-client): extract MASM code into separate files Move inline MASM template strings from foreignProcedureInvocation.ts into dedicated .masm files (counter_contract.masm, count_reader.masm). Add webpack asset/source rule for .masm imports and a TypeScript module declaration. Restructure FPI tutorial to show file creation steps. --- .../foreign_procedure_invocation_tutorial.md | 222 +++++++++++------- web-client/lib/foreignProcedureInvocation.ts | 74 +----- web-client/lib/masm/count_reader.masm | 25 ++ web-client/lib/masm/counter_contract.masm | 32 +++ web-client/lib/masm/masm.d.ts | 4 + web-client/next.config.ts | 6 + 6 files changed, 210 insertions(+), 153 deletions(-) create mode 100644 web-client/lib/masm/count_reader.masm create mode 100644 web-client/lib/masm/counter_contract.masm create mode 100644 web-client/lib/masm/masm.d.ts diff --git a/docs/src/web-client/foreign_procedure_invocation_tutorial.md b/docs/src/web-client/foreign_procedure_invocation_tutorial.md index b707a04..f14f44d 100644 --- a/docs/src/web-client/foreign_procedure_invocation_tutorial.md +++ b/docs/src/web-client/foreign_procedure_invocation_tutorial.md @@ -111,12 +111,126 @@ export default function Home() { } ``` -## Step 3: Create the Foreign Procedure Invocation Implementation +## Step 3: Write the MASM Contract Files + +The MASM (Miden Assembly) code for our smart contracts lives in separate `.masm` files. Create a `lib/masm/` directory and add the two contract files: + +```bash +mkdir -p lib/masm +``` + +### Counter contract + +Create the file `lib/masm/counter_contract.masm`. This is the counter contract that was deployed in the previous tutorial. We need its source code here so we can compile it locally and obtain the procedure hash for `get_count`: + +```masm +use miden::protocol::active_account +use miden::protocol::native_account +use miden::core::word +use miden::core::sys + +const COUNTER_SLOT = word("miden::tutorials::counter") + +#! Inputs: [] +#! Outputs: [count] +pub proc get_count + push.COUNTER_SLOT[0..2] exec.active_account::get_item + # => [count] + + exec.sys::truncate_stack + # => [count] +end + +#! Inputs: [] +#! Outputs: [] +pub proc increment_count + push.COUNTER_SLOT[0..2] exec.active_account::get_item + # => [count] + + add.1 + # => [count+1] + + push.COUNTER_SLOT[0..2] exec.native_account::set_item + # => [] + + exec.sys::truncate_stack + # => [] +end +``` + +### Count reader contract + +Create the file `lib/masm/count_reader.masm`. This is the new "count copy" contract that reads the counter value via FPI and stores it locally: + +```masm +use miden::protocol::active_account +use miden::protocol::native_account +use miden::protocol::tx +use miden::core::word +use miden::core::sys + +const COUNT_READER_SLOT = word("miden::tutorials::count_reader") + +# => [account_id_prefix, account_id_suffix, get_count_proc_hash] +pub proc copy_count + exec.tx::execute_foreign_procedure + # => [count] + + push.COUNT_READER_SLOT[0..2] + # [slot_id_prefix, slot_id_suffix, count] + + exec.native_account::set_item + # => [OLD_VALUE] + + dropw + # => [] + + exec.sys::truncate_stack + # => [] +end +``` + +### Type declaration + +Create `lib/masm/masm.d.ts` so TypeScript recognizes `.masm` imports: + +```ts +declare module '*.masm' { + const content: string; + export default content; +} +``` + +## Step 4: Configure Your Bundler to Import `.masm` Files + +We need to tell our bundler to treat `.masm` files as plain text strings. In Next.js, add an `asset/source` webpack rule. + +Open `next.config.ts` and add the highlighted rule inside the `webpack` callback: + +```ts +webpack: (config, { isServer }) => { + // ... existing WASM config ... + + // Import .masm files as strings + config.module.rules.push({ + test: /\.masm$/, + type: "asset/source", + }); + + return config; +}, +``` + +:::tip Other bundlers +- **Vite:** use the `?raw` suffix — `import code from './masm/counter_contract.masm?raw'` +- **Other bundlers / no bundler:** use `fetch()` at runtime — `const code = await fetch('/masm/counter_contract.masm').then(r => r.text())` +::: + +## Step 5: Create the Foreign Procedure Invocation Implementation Create the file `lib/foreignProcedureInvocation.ts` and add the following code. ```bash -mkdir -p lib touch lib/foreignProcedureInvocation.ts ``` @@ -124,6 +238,9 @@ Copy and paste the following code into the `lib/foreignProcedureInvocation.ts` f ```ts // lib/foreignProcedureInvocation.ts +import counterContractCode from './masm/counter_contract.masm'; +import countReaderCode from './masm/count_reader.masm'; + export async function foreignProcedureInvocation(): Promise { if (typeof window === 'undefined') { console.warn('foreignProcedureInvocation() can only run in the browser'); @@ -148,35 +265,6 @@ export async function foreignProcedureInvocation(): Promise { // ------------------------------------------------------------------------- console.log('\n[STEP 1] Creating count reader contract.'); - // Count reader contract code in Miden Assembly (exactly from count_reader.masm) - const countReaderCode = ` - use miden::protocol::active_account - use miden::protocol::native_account - use miden::protocol::tx - use miden::core::word - use miden::core::sys - - const COUNT_READER_SLOT = word("miden::tutorials::count_reader") - - # => [account_id_prefix, account_id_suffix, get_count_proc_hash] - pub proc copy_count - exec.tx::execute_foreign_procedure - # => [count] - - push.COUNT_READER_SLOT[0..2] - # [slot_id_prefix, slot_id_suffix, count] - - exec.native_account::set_item - # => [OLD_VALUE] - - dropw - # => [] - - exec.sys::truncate_stack - # => [] - end -`; - const countReaderSlotName = 'miden::tutorials::count_reader'; const counterSlotName = 'miden::tutorials::counter'; @@ -193,7 +281,7 @@ export async function foreignProcedureInvocation(): Promise { // Create the count reader contract account console.log('Creating count reader contract account...'); - const countReaderAccount = await client.accounts.create({ + let countReaderAccount = await client.accounts.create({ type: AccountType.ImmutableContract, storage: StorageMode.Public, seed: walletSeed, @@ -211,7 +299,7 @@ export async function foreignProcedureInvocation(): Promise { console.log('\n[STEP 2] Building counter contract from public state'); // Import the counter contract from testnet by its bech32 address - const counterContractAccount = + let counterContractAccount = await client.accounts.getOrImport('mtst1arjemrxne8lj5qz4mg9c8mtyxg954483'); console.log( 'Account storage slot:', @@ -225,42 +313,6 @@ export async function foreignProcedureInvocation(): Promise { '\n[STEP 3] Call counter contract with FPI from count reader contract', ); - // Counter contract code (exactly from counter.masm) - const counterContractCode = ` - use miden::protocol::active_account - use miden::protocol::native_account - use miden::core::word - use miden::core::sys - - const COUNTER_SLOT = word("miden::tutorials::counter") - - #! Inputs: [] - #! Outputs: [count] - pub proc get_count - push.COUNTER_SLOT[0..2] exec.active_account::get_item - # => [count] - - exec.sys::truncate_stack - # => [count] - end - - #! Inputs: [] - #! Outputs: [] - pub proc increment_count - push.COUNTER_SLOT[0..2] exec.active_account::get_item - # => [count] - - add.1 - # => [count+1] - - push.COUNTER_SLOT[0..2] exec.native_account::set_item - # => [] - - exec.sys::truncate_stack - # => [] - end -`; - // Compile the counter contract component to get the procedure hash const counterContractComponent = await client.compile.component({ code: counterContractCode, @@ -270,7 +322,9 @@ export async function foreignProcedureInvocation(): Promise { const getCountProcHash = counterContractComponent.getProcedureHash('get_count'); - // Build the script that calls the count reader contract + // Build the script that calls the count reader contract. + // This script uses template literals because it interpolates runtime values + // (procedure hash and account ID) that are only known after compilation. const fpiScriptCode = ` use external_contract::count_reader_contract use miden::core::sys @@ -319,25 +373,21 @@ export async function foreignProcedureInvocation(): Promise { await client.sync(); - // Retrieve updated contract data to see the results - const updatedCounterContract = await client.accounts.get( - counterContractAccount.id(), - ); + // Refresh account objects to see the results + counterContractAccount = await client.accounts.get(counterContractAccount); console.log( 'counter contract storage:', - updatedCounterContract?.storage().getItem(counterSlotName)?.toHex(), + counterContractAccount?.storage().getItem(counterSlotName)?.toHex(), ); - const updatedCountReaderContract = await client.accounts.get( - countReaderAccount.id(), - ); + countReaderAccount = await client.accounts.get(countReaderAccount); console.log( 'count reader contract storage:', - updatedCountReaderContract?.storage().getItem(countReaderSlotName)?.toHex(), + countReaderAccount?.storage().getItem(countReaderSlotName)?.toHex(), ); // Log the count value copied via FPI - const countReaderStorage = updatedCountReaderContract + const countReaderStorage = countReaderAccount ?.storage() .getItem(countReaderSlotName); if (countReaderStorage) { @@ -502,11 +552,13 @@ In this tutorial we created a smart contract that calls the `get_count` procedur The key steps were: -1. Creating a count reader contract with a `copy_count` procedure -2. Importing the counter contract from the network -3. Getting the procedure hash for the `get_count` function -4. Building a transaction script that calls our count reader contract -5. Executing the transaction with a foreign account reference +1. Writing the MASM contract files (`counter_contract.masm` and `count_reader.masm`) +2. Configuring the bundler to import `.masm` files as strings +3. Creating a count reader contract with a `copy_count` procedure +4. Importing the counter contract from the network +5. Getting the procedure hash for the `get_count` function +6. Building a transaction script that calls our count reader contract +7. Executing the transaction with a foreign account reference ### Running the example diff --git a/web-client/lib/foreignProcedureInvocation.ts b/web-client/lib/foreignProcedureInvocation.ts index bc9232f..cdc53dd 100644 --- a/web-client/lib/foreignProcedureInvocation.ts +++ b/web-client/lib/foreignProcedureInvocation.ts @@ -1,4 +1,7 @@ // lib/foreignProcedureInvocation.ts +import counterContractCode from './masm/counter_contract.masm'; +import countReaderCode from './masm/count_reader.masm'; + export async function foreignProcedureInvocation(): Promise { if (typeof window === 'undefined') { console.warn('foreignProcedureInvocation() can only run in the browser'); @@ -12,69 +15,6 @@ export async function foreignProcedureInvocation(): Promise { const client = await MidenClient.create({ rpcUrl: nodeEndpoint }); console.log('Current block number: ', (await client.sync()).blockNum()); - const counterContractCode = ` - use miden::protocol::active_account - use miden::protocol::native_account - use miden::core::word - use miden::core::sys - - const COUNTER_SLOT = word("miden::tutorials::counter") - - #! Inputs: [] - #! Outputs: [count] - pub proc get_count - push.COUNTER_SLOT[0..2] exec.active_account::get_item - # => [count] - - exec.sys::truncate_stack - # => [count] - end - - #! Inputs: [] - #! Outputs: [] - pub proc increment_count - push.COUNTER_SLOT[0..2] exec.active_account::get_item - # => [count] - - add.1 - # => [count+1] - - push.COUNTER_SLOT[0..2] exec.native_account::set_item - # => [] - - exec.sys::truncate_stack - # => [] - end -`; - - const countReaderCode = ` - use miden::protocol::active_account - use miden::protocol::native_account - use miden::protocol::tx - use miden::core::word - use miden::core::sys - - const COUNT_READER_SLOT = word("miden::tutorials::count_reader") - - # => [account_id_prefix, account_id_suffix, get_count_proc_hash] - pub proc copy_count - exec.tx::execute_foreign_procedure - # => [count] - - push.COUNT_READER_SLOT[0..2] - # [slot_id_prefix, slot_id_suffix, count] - - exec.native_account::set_item - # => [OLD_VALUE] - - dropw - # => [] - - exec.sys::truncate_stack - # => [] - end -`; - const counterSlotName = 'miden::tutorials::counter'; const countReaderSlotName = 'miden::tutorials::count_reader'; @@ -135,7 +75,7 @@ export async function foreignProcedureInvocation(): Promise { crypto.getRandomValues(readerSeed); const readerAuth = AuthSecretKey.rpoFalconWithRNG(readerSeed); - const countReaderAccount = await client.accounts.create({ + let countReaderAccount = await client.accounts.create({ type: AccountType.ImmutableContract, storage: StorageMode.Public, seed: readerSeed, @@ -191,10 +131,8 @@ export async function foreignProcedureInvocation(): Promise { await client.sync(); - const updatedCountReaderContract = await client.accounts.get( - countReaderAccount, - ); - const countReaderStorage = updatedCountReaderContract + countReaderAccount = await client.accounts.get(countReaderAccount); + const countReaderStorage = countReaderAccount ?.storage() .getItem(countReaderSlotName); diff --git a/web-client/lib/masm/count_reader.masm b/web-client/lib/masm/count_reader.masm new file mode 100644 index 0000000..7f2fe29 --- /dev/null +++ b/web-client/lib/masm/count_reader.masm @@ -0,0 +1,25 @@ +use miden::protocol::active_account +use miden::protocol::native_account +use miden::protocol::tx +use miden::core::word +use miden::core::sys + +const COUNT_READER_SLOT = word("miden::tutorials::count_reader") + +# => [account_id_prefix, account_id_suffix, get_count_proc_hash] +pub proc copy_count + exec.tx::execute_foreign_procedure + # => [count] + + push.COUNT_READER_SLOT[0..2] + # [slot_id_prefix, slot_id_suffix, count] + + exec.native_account::set_item + # => [OLD_VALUE] + + dropw + # => [] + + exec.sys::truncate_stack + # => [] +end diff --git a/web-client/lib/masm/counter_contract.masm b/web-client/lib/masm/counter_contract.masm new file mode 100644 index 0000000..8ac3bbc --- /dev/null +++ b/web-client/lib/masm/counter_contract.masm @@ -0,0 +1,32 @@ +use miden::protocol::active_account +use miden::protocol::native_account +use miden::core::word +use miden::core::sys + +const COUNTER_SLOT = word("miden::tutorials::counter") + +#! Inputs: [] +#! Outputs: [count] +pub proc get_count + push.COUNTER_SLOT[0..2] exec.active_account::get_item + # => [count] + + exec.sys::truncate_stack + # => [count] +end + +#! Inputs: [] +#! Outputs: [] +pub proc increment_count + push.COUNTER_SLOT[0..2] exec.active_account::get_item + # => [count] + + add.1 + # => [count+1] + + push.COUNTER_SLOT[0..2] exec.native_account::set_item + # => [] + + exec.sys::truncate_stack + # => [] +end diff --git a/web-client/lib/masm/masm.d.ts b/web-client/lib/masm/masm.d.ts new file mode 100644 index 0000000..9113bb9 --- /dev/null +++ b/web-client/lib/masm/masm.d.ts @@ -0,0 +1,4 @@ +declare module '*.masm' { + const content: string; + export default content; +} diff --git a/web-client/next.config.ts b/web-client/next.config.ts index 971a9d2..9c075ad 100644 --- a/web-client/next.config.ts +++ b/web-client/next.config.ts @@ -21,6 +21,12 @@ const nextConfig: NextConfig = { type: "asset/resource", }); + // Import .masm files as strings + config.module.rules.push({ + test: /\.masm$/, + type: "asset/source", + }); + return config; }, }; From c59d4dae2a166b086a49f8a51387d4988d94fe8f Mon Sep 17 00:00:00 2001 From: Wiktor Starczewski Date: Tue, 3 Mar 2026 00:32:23 +0100 Subject: [PATCH 5/9] refactor: extract MASM from counter tutorial, remove unnecessary syncs Extract inline MASM from incrementCounterContract.ts into the shared counter_contract.masm file. Restructure counter contract tutorial with dedicated MASM and bundler config steps. Remove unnecessary client.sync() calls across all web tutorials where local state is already up to date (after account creation, after consume with no subsequent chain query, etc.). --- .../web-client/counter_contract_tutorial.md | 124 ++++++++++++------ .../foreign_procedure_invocation_tutorial.md | 2 - .../mint_consume_create_tutorial.md | 6 - .../web-client/unauthenticated_note_how_to.md | 1 - web-client/lib/createMintConsume.ts | 3 - web-client/lib/foreignProcedureInvocation.ts | 1 - web-client/lib/incrementCounterContract.ts | 39 +----- web-client/lib/mintTestnetToAddress.ts | 1 - web-client/lib/unauthenticatedNoteTransfer.ts | 1 - 9 files changed, 85 insertions(+), 93 deletions(-) diff --git a/docs/src/web-client/counter_contract_tutorial.md b/docs/src/web-client/counter_contract_tutorial.md index c6e3052..1c8706d 100644 --- a/docs/src/web-client/counter_contract_tutorial.md +++ b/docs/src/web-client/counter_contract_tutorial.md @@ -96,12 +96,90 @@ export default function Home() { } ``` -## Step 3 — Incrementing the Count of the Counter Contract +## Step 3: Write the MASM Counter Contract -Create the file `lib/incrementCounterContract.ts` and add the following code. +The counter contract code lives in a separate `.masm` file. Create a `lib/masm/` directory and add the contract file: +```bash +mkdir -p lib/masm +``` + +Create the file `lib/masm/counter_contract.masm` with the following Miden Assembly code: + +```masm +use miden::protocol::active_account +use miden::protocol::native_account +use miden::core::word +use miden::core::sys + +const COUNTER_SLOT = word("miden::tutorials::counter") + +#! Inputs: [] +#! Outputs: [count] +pub proc get_count + push.COUNTER_SLOT[0..2] exec.active_account::get_item + # => [count] + + exec.sys::truncate_stack + # => [count] +end + +#! Inputs: [] +#! Outputs: [] +pub proc increment_count + push.COUNTER_SLOT[0..2] exec.active_account::get_item + # => [count] + + add.1 + # => [count+1] + + push.COUNTER_SLOT[0..2] exec.native_account::set_item + # => [] + + exec.sys::truncate_stack + # => [] +end +``` + +Also create `lib/masm/masm.d.ts` so TypeScript recognizes `.masm` imports: + +```ts +declare module '*.masm' { + const content: string; + export default content; +} +``` + +## Step 4: Configure Your Bundler to Import `.masm` Files + +Add an `asset/source` webpack rule so `.masm` files are imported as plain text strings. + +Open `next.config.ts` and add the following rule inside the `webpack` callback: + +```ts +webpack: (config, { isServer }) => { + // ... existing WASM config ... + + // Import .masm files as strings + config.module.rules.push({ + test: /\.masm$/, + type: "asset/source", + }); + + return config; +}, ``` -mkdir -p lib + +:::tip Other bundlers +- **Vite:** use the `?raw` suffix — `import code from './masm/counter_contract.masm?raw'` +- **Other bundlers / no bundler:** use `fetch()` at runtime — `const code = await fetch('/masm/counter_contract.masm').then(r => r.text())` +::: + +## Step 5: Incrementing the Count of the Counter Contract + +Create the file `lib/incrementCounterContract.ts`: + +```bash touch lib/incrementCounterContract.ts ``` @@ -109,6 +187,8 @@ Copy and paste the following code into the `lib/incrementCounterContract.ts` fil ```ts // lib/incrementCounterContract.ts +import counterContractCode from './masm/counter_contract.masm'; + export async function incrementCounterContract(): Promise { if (typeof window === 'undefined') { console.warn('webClient() can only run in the browser'); @@ -128,42 +208,6 @@ export async function incrementCounterContract(): Promise { const client = await MidenClient.create({ rpcUrl: nodeEndpoint }); console.log('Current block number: ', (await client.sync()).blockNum()); - // Counter contract code in Miden Assembly - const counterContractCode = ` - use miden::protocol::active_account - use miden::protocol::native_account - use miden::core::word - use miden::core::sys - - const COUNTER_SLOT = word("miden::tutorials::counter") - - #! Inputs: [] - #! Outputs: [count] - pub proc get_count - push.COUNTER_SLOT[0..2] exec.active_account::get_item - # => [count] - - exec.sys::truncate_stack - # => [count] - end - - #! Inputs: [] - #! Outputs: [] - pub proc increment_count - push.COUNTER_SLOT[0..2] exec.active_account::get_item - # => [count] - - add.1 - # => [count+1] - - push.COUNTER_SLOT[0..2] exec.native_account::set_item - # => [] - - exec.sys::truncate_stack - # => [] - end -`; - // Import the counter contract from testnet by its bech32 address const counterContractAccount = await client.accounts.getOrImport('mtst1arjemrxne8lj5qz4mg9c8mtyxg954483'); @@ -190,8 +234,6 @@ export async function incrementCounterContract(): Promise { components: [counterAccountComponent], }); - await client.sync(); - // Building the transaction script which will call the counter contract const txScriptCode = ` use external_contract::counter_contract diff --git a/docs/src/web-client/foreign_procedure_invocation_tutorial.md b/docs/src/web-client/foreign_procedure_invocation_tutorial.md index f14f44d..89c9237 100644 --- a/docs/src/web-client/foreign_procedure_invocation_tutorial.md +++ b/docs/src/web-client/foreign_procedure_invocation_tutorial.md @@ -291,8 +291,6 @@ export async function foreignProcedureInvocation(): Promise { console.log('Count reader contract ID:', countReaderAccount.id().toString()); - await client.sync(); - // ------------------------------------------------------------------------- // STEP 2: Build & Get State of the Counter Contract // ------------------------------------------------------------------------- diff --git a/docs/src/web-client/mint_consume_create_tutorial.md b/docs/src/web-client/mint_consume_create_tutorial.md index 62ede86..f73986b 100644 --- a/docs/src/web-client/mint_consume_create_tutorial.md +++ b/docs/src/web-client/mint_consume_create_tutorial.md @@ -52,8 +52,6 @@ console.log('Mint tx:', mintResult.transactionId); // Wait for the mint transaction to be committed await waitForCommit(mintResult.transactionId);`}, typescript: { code:`// 4. Mint tokens from the faucet to Alice -await client.sync(); - console.log("Minting tokens to Alice..."); const mintTxId = await client.transactions.mint({ .account: faucet, // Faucet account (who mints the tokens) @@ -108,7 +106,6 @@ await client.transactions.consume({ .notes: mintedNotes, }); -await client.sync(); console.log('Notes consumed.');` }, }} reactFilename="lib/react/createMintConsume.tsx" tsFilename="lib/createMintConsume.ts" /> @@ -281,8 +278,6 @@ export async function createMintConsume(): Promise { .}); .console.log('Faucet ID:', faucet.id().toString()); -.await client.sync(); - .// 4. Mint tokens to Alice .console.log('Minting tokens to Alice...'); @@ -311,7 +306,6 @@ export async function createMintConsume(): Promise { ..notes: mintedNotes, .}); -.await client.sync(); .console.log('Notes consumed.'); .// 7. Send tokens to Bob diff --git a/docs/src/web-client/unauthenticated_note_how_to.md b/docs/src/web-client/unauthenticated_note_how_to.md index 3c40ea0..7d07f90 100644 --- a/docs/src/web-client/unauthenticated_note_how_to.md +++ b/docs/src/web-client/unauthenticated_note_how_to.md @@ -322,7 +322,6 @@ export async function unauthenticatedNoteTransfer(): Promise { ..account: alice, ..notes: noteList, .}); -.await client.sync(); .// ── Create unauthenticated note transfer chain ───────────────────────────────────────────── .// Alice → wallet 1 → wallet 2 → wallet 3 → wallet 4 diff --git a/web-client/lib/createMintConsume.ts b/web-client/lib/createMintConsume.ts index d058095..bd2a042 100644 --- a/web-client/lib/createMintConsume.ts +++ b/web-client/lib/createMintConsume.ts @@ -35,8 +35,6 @@ export async function createMintConsume(): Promise { }); console.log('Faucet ID:', faucet.id().toString()); - await client.sync(); - // 4. Mint tokens to Alice console.log('Minting tokens to Alice...'); const mintTxId = await client.transactions.mint({ @@ -64,7 +62,6 @@ export async function createMintConsume(): Promise { notes: mintedNotes, }); - await client.sync(); console.log('Notes consumed.'); // 7. Send tokens to Bob diff --git a/web-client/lib/foreignProcedureInvocation.ts b/web-client/lib/foreignProcedureInvocation.ts index cdc53dd..d22a882 100644 --- a/web-client/lib/foreignProcedureInvocation.ts +++ b/web-client/lib/foreignProcedureInvocation.ts @@ -83,7 +83,6 @@ export async function foreignProcedureInvocation(): Promise { components: [countReaderComponent], }); - await client.sync(); console.log('Count reader contract ID:', countReaderAccount.id().toString()); // ------------------------------------------------------------------------- diff --git a/web-client/lib/incrementCounterContract.ts b/web-client/lib/incrementCounterContract.ts index 4747292..1eb78ef 100644 --- a/web-client/lib/incrementCounterContract.ts +++ b/web-client/lib/incrementCounterContract.ts @@ -1,4 +1,6 @@ // lib/incrementCounterContract.ts +import counterContractCode from './masm/counter_contract.masm'; + export async function incrementCounterContract(): Promise { if (typeof window === 'undefined') { console.warn('webClient() can only run in the browser'); @@ -12,41 +14,6 @@ export async function incrementCounterContract(): Promise { const client = await MidenClient.create({ rpcUrl: nodeEndpoint }); console.log('Current block number: ', (await client.sync()).blockNum()); - const counterContractCode = ` - use miden::protocol::active_account - use miden::protocol::native_account - use miden::core::word - use miden::core::sys - - const COUNTER_SLOT = word("miden::tutorials::counter") - - #! Inputs: [] - #! Outputs: [count] - pub proc get_count - push.COUNTER_SLOT[0..2] exec.active_account::get_item - # => [count] - - exec.sys::truncate_stack - # => [count] - end - - #! Inputs: [] - #! Outputs: [] - pub proc increment_count - push.COUNTER_SLOT[0..2] exec.active_account::get_item - # => [count] - - add.1 - # => [count+1] - - push.COUNTER_SLOT[0..2] exec.native_account::set_item - # => [] - - exec.sys::truncate_stack - # => [] - end -`; - const counterSlotName = 'miden::tutorials::counter'; const counterAccountComponent = await client.compile.component({ @@ -66,8 +33,6 @@ export async function incrementCounterContract(): Promise { components: [counterAccountComponent], }); - await client.sync(); - const txScriptCode = ` use external_contract::counter_contract begin diff --git a/web-client/lib/mintTestnetToAddress.ts b/web-client/lib/mintTestnetToAddress.ts index 28748ad..da022aa 100644 --- a/web-client/lib/mintTestnetToAddress.ts +++ b/web-client/lib/mintTestnetToAddress.ts @@ -31,7 +31,6 @@ export async function mintTestnetToAddress(): Promise { storage: StorageMode.Public, }); console.log('Faucet ID:', faucet.id().toString()); - await client.sync(); // ── Mint to recipient ─────────────────────────────────────────────────────── const recipientAddress = diff --git a/web-client/lib/unauthenticatedNoteTransfer.ts b/web-client/lib/unauthenticatedNoteTransfer.ts index aeab5e4..ea4fb2c 100644 --- a/web-client/lib/unauthenticatedNoteTransfer.ts +++ b/web-client/lib/unauthenticatedNoteTransfer.ts @@ -70,7 +70,6 @@ export async function unauthenticatedNoteTransfer(): Promise { account: alice, notes: noteList, }); - await client.sync(); // ── Create unauthenticated note transfer chain ───────────────────────────────────────────── // Alice → wallet 1 → wallet 2 → wallet 3 → wallet 4 From 62daf1e599aa33c65a46efc6677d06cfc1311a83 Mon Sep 17 00:00:00 2001 From: Wiktor Starczewski Date: Tue, 3 Mar 2026 00:50:00 +0100 Subject: [PATCH 6/9] refactor: remove all remaining unnecessary client.sync() calls waitFor() already calls syncStateWithTimeout(0) on each polling iteration, so explicit sync after it is redundant. execute() updates local account state directly, so sync before accounts.get() is also unnecessary. --- docs/src/web-client/counter_contract_tutorial.md | 3 --- docs/src/web-client/creating_multiple_notes_tutorial.md | 2 -- docs/src/web-client/foreign_procedure_invocation_tutorial.md | 2 -- docs/src/web-client/mint_consume_create_tutorial.md | 4 +--- docs/src/web-client/unauthenticated_note_how_to.md | 1 - web-client/lib/createMintConsume.ts | 1 - web-client/lib/foreignProcedureInvocation.ts | 3 --- web-client/lib/incrementCounterContract.ts | 2 -- web-client/lib/mintTestnetToAddress.ts | 1 - web-client/lib/multiSendWithDelegatedProver.ts | 1 - web-client/lib/unauthenticatedNoteTransfer.ts | 1 - 11 files changed, 1 insertion(+), 20 deletions(-) diff --git a/docs/src/web-client/counter_contract_tutorial.md b/docs/src/web-client/counter_contract_tutorial.md index 1c8706d..4ce6d18 100644 --- a/docs/src/web-client/counter_contract_tutorial.md +++ b/docs/src/web-client/counter_contract_tutorial.md @@ -258,9 +258,6 @@ export async function incrementCounterContract(): Promise { script, }); - // Sync state - await client.sync(); - // Logging the count of counter contract const counter = await client.accounts.get(counterContractAccount.id()); diff --git a/docs/src/web-client/creating_multiple_notes_tutorial.md b/docs/src/web-client/creating_multiple_notes_tutorial.md index f869ab4..14e94e7 100644 --- a/docs/src/web-client/creating_multiple_notes_tutorial.md +++ b/docs/src/web-client/creating_multiple_notes_tutorial.md @@ -260,7 +260,6 @@ const mintTxId = await client.transactions.mint({ console.log('waiting for settlement'); await client.transactions.waitFor(mintTxId); -await client.sync(); // ── consume the freshly minted notes ────────────────────────────────────────────── const noteList = await client.notes.listAvailable({ account: alice }); @@ -449,7 +448,6 @@ export async function multiSendWithDelegatedProver(): Promise { .console.log('waiting for settlement'); .await client.transactions.waitFor(mintTxId); -.await client.sync(); .// ── consume the freshly minted notes ────────────────────────────────────────────── .const noteList = await client.notes.listAvailable({ account: alice }); diff --git a/docs/src/web-client/foreign_procedure_invocation_tutorial.md b/docs/src/web-client/foreign_procedure_invocation_tutorial.md index 89c9237..9dcf5c6 100644 --- a/docs/src/web-client/foreign_procedure_invocation_tutorial.md +++ b/docs/src/web-client/foreign_procedure_invocation_tutorial.md @@ -369,8 +369,6 @@ export async function foreignProcedureInvocation(): Promise { txId.toHex(), ); - await client.sync(); - // Refresh account objects to see the results counterContractAccount = await client.accounts.get(counterContractAccount); console.log( diff --git a/docs/src/web-client/mint_consume_create_tutorial.md b/docs/src/web-client/mint_consume_create_tutorial.md index f73986b..317d9c0 100644 --- a/docs/src/web-client/mint_consume_create_tutorial.md +++ b/docs/src/web-client/mint_consume_create_tutorial.md @@ -62,8 +62,7 @@ const mintTxId = await client.transactions.mint({ // Wait for the transaction to be processed console.log("Waiting for transaction confirmation..."); -await client.transactions.waitFor(mintTxId); -await client.sync();` }, +await client.transactions.waitFor(mintTxId);` }, }} reactFilename="lib/react/createMintConsume.tsx" tsFilename="lib/createMintConsume.ts" /> ### What's happening here? @@ -290,7 +289,6 @@ export async function createMintConsume(): Promise { .console.log('Waiting for transaction confirmation...'); .await client.transactions.waitFor(mintTxId); -.await client.sync(); .// 5. Fetch minted notes .const mintedNotes = await client.notes.listAvailable({ account: alice }); diff --git a/docs/src/web-client/unauthenticated_note_how_to.md b/docs/src/web-client/unauthenticated_note_how_to.md index 7d07f90..858034b 100644 --- a/docs/src/web-client/unauthenticated_note_how_to.md +++ b/docs/src/web-client/unauthenticated_note_how_to.md @@ -314,7 +314,6 @@ export async function unauthenticatedNoteTransfer(): Promise { .console.log('Waiting for settlement'); .await client.transactions.waitFor(mintTxId); -.await client.sync(); .// ── Consume the freshly minted note ────────────────────────────────────────────── .const noteList = await client.notes.listAvailable({ account: alice }); diff --git a/web-client/lib/createMintConsume.ts b/web-client/lib/createMintConsume.ts index bd2a042..8474b08 100644 --- a/web-client/lib/createMintConsume.ts +++ b/web-client/lib/createMintConsume.ts @@ -46,7 +46,6 @@ export async function createMintConsume(): Promise { console.log('Waiting for transaction confirmation...'); await client.transactions.waitFor(mintTxId); - await client.sync(); // 5. Fetch minted notes const mintedNotes = await client.notes.listAvailable({ account: alice }); diff --git a/web-client/lib/foreignProcedureInvocation.ts b/web-client/lib/foreignProcedureInvocation.ts index d22a882..4b5a535 100644 --- a/web-client/lib/foreignProcedureInvocation.ts +++ b/web-client/lib/foreignProcedureInvocation.ts @@ -58,7 +58,6 @@ export async function foreignProcedureInvocation(): Promise { script: deployScript, waitForConfirmation: true, }); - await client.sync(); console.log('Counter contract ID:', counterAccount.id().toString()); // ------------------------------------------------------------------------- @@ -128,8 +127,6 @@ export async function foreignProcedureInvocation(): Promise { foreignAccounts: [counterAccount], }); - await client.sync(); - countReaderAccount = await client.accounts.get(countReaderAccount); const countReaderStorage = countReaderAccount ?.storage() diff --git a/web-client/lib/incrementCounterContract.ts b/web-client/lib/incrementCounterContract.ts index 1eb78ef..0257752 100644 --- a/web-client/lib/incrementCounterContract.ts +++ b/web-client/lib/incrementCounterContract.ts @@ -50,8 +50,6 @@ export async function incrementCounterContract(): Promise { script, }); - await client.sync(); - console.log('Counter contract ID:', account.id().toString()); const counter = await client.accounts.get(account); diff --git a/web-client/lib/mintTestnetToAddress.ts b/web-client/lib/mintTestnetToAddress.ts index da022aa..c929afd 100644 --- a/web-client/lib/mintTestnetToAddress.ts +++ b/web-client/lib/mintTestnetToAddress.ts @@ -47,7 +47,6 @@ export async function mintTestnetToAddress(): Promise { console.log('Waiting for settlement...'); await client.transactions.waitFor(mintTxId); - await client.sync(); console.log('Mint tx id:', mintTxId.toHex()); console.log('Mint complete.'); diff --git a/web-client/lib/multiSendWithDelegatedProver.ts b/web-client/lib/multiSendWithDelegatedProver.ts index 6c9a31b..3468d6d 100644 --- a/web-client/lib/multiSendWithDelegatedProver.ts +++ b/web-client/lib/multiSendWithDelegatedProver.ts @@ -52,7 +52,6 @@ export async function multiSendWithDelegatedProver(): Promise { console.log('waiting for settlement'); await client.transactions.waitFor(mintTxId); - await client.sync(); // ── consume the freshly minted notes ────────────────────────────────────────────── const noteList = await client.notes.listAvailable({ account: alice }); diff --git a/web-client/lib/unauthenticatedNoteTransfer.ts b/web-client/lib/unauthenticatedNoteTransfer.ts index ea4fb2c..5edcc47 100644 --- a/web-client/lib/unauthenticatedNoteTransfer.ts +++ b/web-client/lib/unauthenticatedNoteTransfer.ts @@ -62,7 +62,6 @@ export async function unauthenticatedNoteTransfer(): Promise { console.log('Waiting for settlement'); await client.transactions.waitFor(mintTxId); - await client.sync(); // ── Consume the freshly minted note ────────────────────────────────────────────── const noteList = await client.notes.listAvailable({ account: alice }); From 4066e720e15310807e26b2cc0bae5d59a334cd15 Mon Sep 17 00:00:00 2001 From: Wiktor Starczewski Date: Tue, 3 Mar 2026 01:43:49 +0100 Subject: [PATCH 7/9] fix(web-client): update send() API from authenticated: false to returnNote: true --- docs/src/web-client/unauthenticated_note_how_to.md | 4 ++-- web-client/lib/react/unauthenticatedNoteTransfer.tsx | 2 +- web-client/lib/unauthenticatedNoteTransfer.ts | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/src/web-client/unauthenticated_note_how_to.md b/docs/src/web-client/unauthenticated_note_how_to.md index 858034b..83fee2f 100644 --- a/docs/src/web-client/unauthenticated_note_how_to.md +++ b/docs/src/web-client/unauthenticated_note_how_to.md @@ -224,7 +224,7 @@ function UnauthenticatedNoteTransferInner() { ....assetId: faucet, ....amount: BigInt(50), ....noteType: NoteVisibility.Public, -....authenticated: false, +....returnNote: true, ...}); ...const result = await consume({ accountId: wallet, notes: [note] }); ...console.log( @@ -339,7 +339,7 @@ export async function unauthenticatedNoteTransfer(): Promise { ...token: faucet, ...amount: BigInt(50), ...type: NoteVisibility.Public, -...authenticated: false, +...returnNote: true, ..}); ..const consumeTxId = await client.transactions.consume({ diff --git a/web-client/lib/react/unauthenticatedNoteTransfer.tsx b/web-client/lib/react/unauthenticatedNoteTransfer.tsx index b820876..5f3e138 100644 --- a/web-client/lib/react/unauthenticatedNoteTransfer.tsx +++ b/web-client/lib/react/unauthenticatedNoteTransfer.tsx @@ -66,7 +66,7 @@ function UnauthenticatedNoteTransferInner() { assetId: faucet, amount: BigInt(50), noteType: NoteVisibility.Public, - authenticated: false, + returnNote: true, }); const result = await consume({ accountId: wallet, notes: [note!] }); diff --git a/web-client/lib/unauthenticatedNoteTransfer.ts b/web-client/lib/unauthenticatedNoteTransfer.ts index 5edcc47..7f0fced 100644 --- a/web-client/lib/unauthenticatedNoteTransfer.ts +++ b/web-client/lib/unauthenticatedNoteTransfer.ts @@ -87,7 +87,7 @@ export async function unauthenticatedNoteTransfer(): Promise { token: faucet, amount: BigInt(50), type: NoteVisibility.Public, - authenticated: false, + returnNote: true, }); const consumeTxId = await client.transactions.consume({ From be16f4c3f84662373e8e8d77b4e873af4c43a465 Mon Sep 17 00:00:00 2001 From: Wiktor Starczewski Date: Tue, 3 Mar 2026 02:45:06 +0100 Subject: [PATCH 8/9] ci: whitelist all web tutorials and fix empty array expansion Enable incrementCounterContract and foreignProcedureInvocation in CI by clearing WEB_SKIPPED. Guard empty-array references with the ${arr[@]+"${arr[@]}"} idiom so set -u does not trigger unbound-variable errors. --- scripts/run_tutorials.sh | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/scripts/run_tutorials.sh b/scripts/run_tutorials.sh index 34c40f5..318776a 100644 --- a/scripts/run_tutorials.sh +++ b/scripts/run_tutorials.sh @@ -14,10 +14,8 @@ WEB_EXAMPLES=( foreignProcedureInvocation ) -WEB_SKIPPED=( - incrementCounterContract - foreignProcedureInvocation -) +WEB_SKIPPED=() + RUST_EXAMPLES=( counter_contract_deploy @@ -126,14 +124,14 @@ while [[ $# -gt 0 ]]; do --list) echo "Web tutorials (default):" for name in "${WEB_EXAMPLES[@]}"; do - if ! contains "$name" "${WEB_SKIPPED[@]}"; then + if ! contains "$name" ${WEB_SKIPPED[@]+"${WEB_SKIPPED[@]}"}; then printf " %s\n" "$name" fi done if [[ ${#WEB_SKIPPED[@]} -gt 0 ]]; then echo "" echo "Web tutorials (skipped by default):" - printf " %s\n" "${WEB_SKIPPED[@]}" + printf " %s\n" ${WEB_SKIPPED[@]+"${WEB_SKIPPED[@]}"} fi echo "" echo "Rust tutorials (default):" @@ -171,7 +169,7 @@ if [[ "$run_web" -eq 1 ]]; then if [[ ${#web_names[@]} -eq 0 ]]; then web_names=() for name in "${WEB_EXAMPLES[@]}"; do - if ! contains "$name" "${WEB_SKIPPED[@]}"; then + if ! contains "$name" ${WEB_SKIPPED[@]+"${WEB_SKIPPED[@]}"}; then web_names+=("$name") fi done @@ -183,7 +181,7 @@ if [[ "$run_web" -eq 1 ]]; then echo "Available web tutorials: ${WEB_EXAMPLES[*]}" >&2 exit 1 fi - if contains "$name" "${WEB_SKIPPED[@]}"; then + if contains "$name" ${WEB_SKIPPED[@]+"${WEB_SKIPPED[@]}"}; then echo "Note: $name is skipped by default but will run because it was explicitly requested." fi done From 59473cbf327c69fbfb10613471cc35b177034e9f Mon Sep 17 00:00:00 2001 From: Wiktor Starczewski Date: Tue, 3 Mar 2026 02:46:29 +0100 Subject: [PATCH 9/9] fix(docs): format markdown with prettier --- .../src/web-client/counter_contract_tutorial.md | 17 +++++++---------- .../foreign_procedure_invocation_tutorial.md | 17 +++++++---------- 2 files changed, 14 insertions(+), 20 deletions(-) diff --git a/docs/src/web-client/counter_contract_tutorial.md b/docs/src/web-client/counter_contract_tutorial.md index 4ce6d18..6b12c9f 100644 --- a/docs/src/web-client/counter_contract_tutorial.md +++ b/docs/src/web-client/counter_contract_tutorial.md @@ -171,9 +171,10 @@ webpack: (config, { isServer }) => { ``` :::tip Other bundlers + - **Vite:** use the `?raw` suffix — `import code from './masm/counter_contract.masm?raw'` - **Other bundlers / no bundler:** use `fetch()` at runtime — `const code = await fetch('/masm/counter_contract.masm').then(r => r.text())` -::: + ::: ## Step 5: Incrementing the Count of the Counter Contract @@ -196,21 +197,17 @@ export async function incrementCounterContract(): Promise { } // dynamic import → only in the browser, so WASM is loaded client‑side - const { - AccountType, - AuthSecretKey, - StorageMode, - StorageSlot, - MidenClient, - } = await import('@miden-sdk/miden-sdk'); + const { AccountType, AuthSecretKey, StorageMode, StorageSlot, MidenClient } = + await import('@miden-sdk/miden-sdk'); const nodeEndpoint = 'https://rpc.testnet.miden.io'; const client = await MidenClient.create({ rpcUrl: nodeEndpoint }); console.log('Current block number: ', (await client.sync()).blockNum()); // Import the counter contract from testnet by its bech32 address - const counterContractAccount = - await client.accounts.getOrImport('mtst1arjemrxne8lj5qz4mg9c8mtyxg954483'); + const counterContractAccount = await client.accounts.getOrImport( + 'mtst1arjemrxne8lj5qz4mg9c8mtyxg954483', + ); const counterSlotName = 'miden::tutorials::counter'; diff --git a/docs/src/web-client/foreign_procedure_invocation_tutorial.md b/docs/src/web-client/foreign_procedure_invocation_tutorial.md index 9dcf5c6..7e05043 100644 --- a/docs/src/web-client/foreign_procedure_invocation_tutorial.md +++ b/docs/src/web-client/foreign_procedure_invocation_tutorial.md @@ -222,9 +222,10 @@ webpack: (config, { isServer }) => { ``` :::tip Other bundlers + - **Vite:** use the `?raw` suffix — `import code from './masm/counter_contract.masm?raw'` - **Other bundlers / no bundler:** use `fetch()` at runtime — `const code = await fetch('/masm/counter_contract.masm').then(r => r.text())` -::: + ::: ## Step 5: Create the Foreign Procedure Invocation Implementation @@ -248,13 +249,8 @@ export async function foreignProcedureInvocation(): Promise { } // dynamic import → only in the browser, so WASM is loaded client‑side - const { - AccountType, - AuthSecretKey, - StorageMode, - StorageSlot, - MidenClient, - } = await import('@miden-sdk/miden-sdk'); + const { AccountType, AuthSecretKey, StorageMode, StorageSlot, MidenClient } = + await import('@miden-sdk/miden-sdk'); const nodeEndpoint = 'https://rpc.testnet.miden.io'; const client = await MidenClient.create({ rpcUrl: nodeEndpoint }); @@ -297,8 +293,9 @@ export async function foreignProcedureInvocation(): Promise { console.log('\n[STEP 2] Building counter contract from public state'); // Import the counter contract from testnet by its bech32 address - let counterContractAccount = - await client.accounts.getOrImport('mtst1arjemrxne8lj5qz4mg9c8mtyxg954483'); + let counterContractAccount = await client.accounts.getOrImport( + 'mtst1arjemrxne8lj5qz4mg9c8mtyxg954483', + ); console.log( 'Account storage slot:', counterContractAccount.storage().getItem(counterSlotName)?.toHex(),