Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
154 changes: 90 additions & 64 deletions docs/src/web-client/counter_contract_tutorial.md
Original file line number Diff line number Diff line change
Expand Up @@ -96,84 +96,118 @@ 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
```

Copy and paste the following code into the `lib/incrementCounterContract.ts` file:

```ts
// lib/incrementCounterContract.ts
import counterContractCode from './masm/counter_contract.masm';

export async function incrementCounterContract(): Promise<void> {
if (typeof window === 'undefined') {
console.warn('webClient() can only run in the browser');
return;
}

// dynamic import → only in the browser, so WASM is loaded client‑side
const {
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';

Expand All @@ -197,8 +231,6 @@ export async function incrementCounterContract(): Promise<void> {
components: [counterAccountComponent],
});

await client.sync();

// Building the transaction script which will call the counter contract
const txScriptCode = `
use external_contract::counter_contract
Expand All @@ -223,20 +255,14 @@ export async function incrementCounterContract(): Promise<void> {
script,
});

// Sync state
await client.sync();

// Logging the count of counter contract
const counter = await client.accounts.get(counterContractAccount.id());

// Here we get the first Word from storage of the counter contract
// 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);
}
Expand Down
12 changes: 4 additions & 8 deletions docs/src/web-client/creating_multiple_notes_tutorial.md
Original file line number Diff line number Diff line change
Expand Up @@ -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({
Expand Down Expand Up @@ -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" />

Expand Down Expand Up @@ -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({
Expand Down Expand Up @@ -451,13 +448,12 @@ export async function multiSendWithDelegatedProver(): Promise<void> {

.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) ─────────────────────────────────────────────
Expand Down
Loading