diff --git a/docs/src/web-client/counter_contract_tutorial.md b/docs/src/web-client/counter_contract_tutorial.md index 0c1a1c0..6b12c9f 100644 --- a/docs/src/web-client/counter_contract_tutorial.md +++ b/docs/src/web-client/counter_contract_tutorial.md @@ -96,12 +96,91 @@ 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 ``` -mkdir -p lib + +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; +}, +``` + +:::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 +188,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'); @@ -116,64 +197,17 @@ export async function incrementCounterContract(): Promise { } // dynamic import → only in the browser, so WASM is loaded client‑side - const { - AccountType, - Address, - 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()); - // 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 -`; - - // Counter contract account id on testnet - const counterContractId = Address.fromBech32( + // Import the counter contract from testnet by its bech32 address + const counterContractAccount = await client.accounts.getOrImport( 'mtst1arjemrxne8lj5qz4mg9c8mtyxg954483', - ).accountId(); - - // Reading the public state of the counter contract from testnet, - // and importing it into the client - const counterContractAccount = - await client.accounts.getOrImport(counterContractId); + ); const counterSlotName = 'miden::tutorials::counter'; @@ -197,8 +231,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 @@ -223,9 +255,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()); @@ -233,10 +262,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/creating_multiple_notes_tutorial.md b/docs/src/web-client/creating_multiple_notes_tutorial.md index b9e2e77..14e94e7 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({ @@ -261,13 +260,12 @@ 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 }); 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 +361,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({ @@ -451,13 +448,12 @@ 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 }); .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/foreign_procedure_invocation_tutorial.md b/docs/src/web-client/foreign_procedure_invocation_tutorial.md index ed82799..7e05043 100644 --- a/docs/src/web-client/foreign_procedure_invocation_tutorial.md +++ b/docs/src/web-client/foreign_procedure_invocation_tutorial.md @@ -111,12 +111,127 @@ 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 +239,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'); @@ -131,14 +249,8 @@ export async function foreignProcedureInvocation(): Promise { } // dynamic import → only in the browser, so WASM is loaded client‑side - const { - AccountType, - Address, - 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 }); @@ -149,35 +261,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'; @@ -194,7 +277,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, @@ -204,21 +287,15 @@ 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 // ------------------------------------------------------------------------- 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( + // Import the counter contract from testnet by its bech32 address + let counterContractAccount = await client.accounts.getOrImport( 'mtst1arjemrxne8lj5qz4mg9c8mtyxg954483', - ).accountId(); - - // Import the counter contract - const counterContractAccount = - await client.accounts.getOrImport(counterContractId); + ); console.log( 'Account storage slot:', counterContractAccount.storage().getItem(counterSlotName)?.toHex(), @@ -231,42 +308,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, @@ -276,7 +317,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 @@ -323,41 +366,25 @@ export async function foreignProcedureInvocation(): Promise { txId.toHex(), ); - 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) { - 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); } @@ -518,11 +545,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/docs/src/web-client/mint_consume_create_tutorial.md b/docs/src/web-client/mint_consume_create_tutorial.md index 7204035..317d9c0 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) @@ -64,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? @@ -81,15 +78,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,16 +96,15 @@ Now let's consume the notes to add the tokens to Alice's account balance: n.inputNoteRecord()), +.notes: mintedNotes, }); -await client.sync(); console.log('Notes consumed.');` }, }} reactFilename="lib/react/createMintConsume.tsx" tsFilename="lib/createMintConsume.ts" /> @@ -209,12 +204,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 @@ -283,8 +277,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...'); @@ -297,23 +289,21 @@ 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 }); .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(); .console.log('Notes consumed.'); .// 7. Send tokens to Bob 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..83fee2f 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 @@ -225,9 +224,9 @@ function UnauthenticatedNoteTransferInner() { ....assetId: faucet, ....amount: BigInt(50), ....noteType: NoteVisibility.Public, -....authenticated: false, +....returnNote: true, ...}); -...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}\`, ...); @@ -315,15 +314,13 @@ 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 }); .await client.transactions.consume({ ..account: alice, -..notes: noteList.map((n) => n.inputNoteRecord()), +..notes: noteList, .}); -.await client.sync(); .// ── Create unauthenticated note transfer chain ───────────────────────────────────────────── .// Alice → wallet 1 → wallet 2 → wallet 3 → wallet 4 @@ -342,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/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 diff --git a/web-client/lib/createMintConsume.ts b/web-client/lib/createMintConsume.ts index 86b0b95..8474b08 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({ @@ -48,23 +46,21 @@ 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 }); 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(); console.log('Notes consumed.'); // 7. Send tokens to Bob diff --git a/web-client/lib/foreignProcedureInvocation.ts b/web-client/lib/foreignProcedureInvocation.ts index 47ded5b..4b5a535 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'; @@ -118,7 +58,6 @@ export async function foreignProcedureInvocation(): Promise { script: deployScript, waitForConfirmation: true, }); - await client.sync(); console.log('Counter contract ID:', counterAccount.id().toString()); // ------------------------------------------------------------------------- @@ -135,7 +74,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, @@ -143,7 +82,6 @@ export async function foreignProcedureInvocation(): Promise { components: [countReaderComponent], }); - await client.sync(); console.log('Count reader contract ID:', countReaderAccount.id().toString()); // ------------------------------------------------------------------------- @@ -189,27 +127,13 @@ export async function foreignProcedureInvocation(): Promise { foreignAccounts: [counterAccount], }); - 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); 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..0257752 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 @@ -85,14 +50,10 @@ export async function incrementCounterContract(): Promise { script, }); - await client.sync(); - console.log('Counter contract ID:', account.id().toString()); 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); } 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/lib/mintTestnetToAddress.ts b/web-client/lib/mintTestnetToAddress.ts index 90ddaf1..c929afd 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({ @@ -32,25 +31,22 @@ export async function mintTestnetToAddress(): Promise { storage: StorageMode.Public, }); console.log('Faucet ID:', faucet.id().toString()); - await client.sync(); // ── 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, }); 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 a6912ac..3468d6d 100644 --- a/web-client/lib/multiSendWithDelegatedProver.ts +++ b/web-client/lib/multiSendWithDelegatedProver.ts @@ -52,13 +52,12 @@ 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 }); 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..5f3e138 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 @@ -67,10 +66,10 @@ function UnauthenticatedNoteTransferInner() { assetId: faucet, amount: BigInt(50), noteType: NoteVisibility.Public, - authenticated: false, + returnNote: true, }); - 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..7f0fced 100644 --- a/web-client/lib/unauthenticatedNoteTransfer.ts +++ b/web-client/lib/unauthenticatedNoteTransfer.ts @@ -62,15 +62,13 @@ 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 }); await client.transactions.consume({ account: alice, - notes: noteList.map((n) => n.inputNoteRecord()), + notes: noteList, }); - await client.sync(); // ── Create unauthenticated note transfer chain ───────────────────────────────────────────── // Alice → wallet 1 → wallet 2 → wallet 3 → wallet 4 @@ -89,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({ 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; }, };