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
6 changes: 6 additions & 0 deletions docs/src/web-client/.prettierrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"printWidth": 80,
"tabWidth": 2,
"singleQuote": true,
"trailingComma": "all"
}
2 changes: 1 addition & 1 deletion docs/src/web-client/about.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
---
title: "Web Client"
title: 'Web Client'
sidebar_position: 1
---

Expand Down
211 changes: 115 additions & 96 deletions docs/src/web-client/counter_contract_tutorial.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,8 @@
---
title: "Incrementing the Count of the Counter Contract"
title: 'Incrementing the Count of the Counter Contract'
sidebar_position: 5
---

# Incrementing the Count of the Counter Contract

_Using the Miden WebClient to interact with a custom smart contract_

## Overview
Expand All @@ -22,7 +20,7 @@ Using a script, we will invoke the increment function within the counter contrac

- Node `v20` or greater
- Familiarity with TypeScript
- `pnpm`
- `yarn`

This tutorial assumes you have a basic understanding of Miden assembly. To quickly get up to speed with Miden assembly (MASM), please play around with running basic Miden assembly programs in the [Miden playground](https://0xmiden.github.io/examples/).

Expand All @@ -31,7 +29,7 @@ This tutorial assumes you have a basic understanding of Miden assembly. To quick
1. Create a new Next.js app with TypeScript:

```bash
npx create-next-app@latest miden-web-app --typescript
yarn create next-app@latest miden-web-app --typescript
```

Hit enter for all terminal prompts.
Expand All @@ -44,7 +42,7 @@ This tutorial assumes you have a basic understanding of Miden assembly. To quick

3. Install the Miden WebClient SDK:
```bash
pnpm i @demox-labs/miden-sdk@0.11.1
yarn add @demox-labs/miden-sdk@0.12.3
```

**NOTE!**: Be sure to add the `--webpack` command to your `package.json` when running the `dev script`. The dev script should look like this:
Expand All @@ -63,9 +61,9 @@ This tutorial assumes you have a basic understanding of Miden assembly. To quick
Add the following code to the `app/page.tsx` file. This code defines the main page of our web application:

```tsx
"use client";
import { useState } from "react";
import { incrementCounterContract } from "../lib/incrementCounterContract";
'use client';
import { useState } from 'react';
import { incrementCounterContract } from '../lib/incrementCounterContract';

export default function Home() {
const [isIncrementCounter, setIsIncrementCounter] = useState(false);
Expand All @@ -88,8 +86,8 @@ export default function Home() {
className="w-full px-6 py-3 text-lg cursor-pointer bg-transparent border-2 border-orange-600 text-white rounded-lg transition-all hover:bg-orange-600 hover:text-white"
>
{isIncrementCounter
? "Working..."
: "Tutorial #3: Increment Counter Contract"}
? 'Working...'
: 'Tutorial #3: Increment Counter Contract'}
</button>
</div>
</div>
Expand All @@ -112,77 +110,80 @@ Copy and paste the following code into the `lib/incrementCounterContract.ts` fil
```ts
// lib/incrementCounterContract.ts
export async function incrementCounterContract(): Promise<void> {
if (typeof window === "undefined") {
console.warn("webClient() can only run in the browser");
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 {
AccountId,
AssemblerUtils,
TransactionKernel,
AccountBuilder,
AccountComponent,
AccountStorageMode,
AccountType,
SecretKey,
StorageMap,
StorageSlot,
TransactionRequestBuilder,
TransactionScript,
WebClient,
} = await import("@demox-labs/miden-sdk");
} = await import('@demox-labs/miden-sdk');

const nodeEndpoint = "https://rpc.testnet.miden.io";
const nodeEndpoint = 'https://rpc.testnet.miden.io';
const client = await WebClient.createClient(nodeEndpoint);
console.log("Current block number: ", (await client.syncState()).blockNum());
console.log('Current block number: ', (await client.syncState()).blockNum());

// Counter contract code in Miden Assembly
const counterContractCode = `
use.miden::account
use.std::sys
use.miden::active_account
use miden::native_account
use.std::sys

const.COUNTER_SLOT=0
const.COUNTER_SLOT=0

#! Inputs: []
#! Outputs: [count]
export.get_count
push.COUNTER_SLOT
# => [index]
#! Inputs: []
#! Outputs: [count]
export.get_count
push.COUNTER_SLOT
# => [index]

exec.account::get_item
# => [count]
exec.active_account::get_item
# => [count]

# clean up stack
movdn.4 dropw
# => [count]
end
# clean up stack
movdn.4 dropw
# => [count]
end

#! Inputs: []
#! Outputs: []
export.increment_count
push.COUNTER_SLOT
# => [index]
#! Inputs: []
#! Outputs: []
export.increment_count
push.COUNTER_SLOT
# => [index]

exec.account::get_item
# => [count]
exec.active_account::get_item
# => [count]

add.1
# => [count+1]
add.1
# => [count+1]

debug.stack
debug.stack

push.COUNTER_SLOT
# [index, count+1]
push.COUNTER_SLOT
# [index, count+1]

exec.account::set_item
# => [OLD_VALUE]
exec.native_account::set_item
# => [OLD_VALUE]

dropw
# => []
end
`;
dropw
# => []
end
`;

// Building the counter contract
let assembler = TransactionKernel.assembler();

// Counter contract account id on testnet
const counterContractId = AccountId.fromBech32(
"mtst1qre73e6qcrfevqqngx8wewvveacqqjh8p2a",
const counterContractId = AccountId.fromHex(
'0xe59d8cd3c9ff2a0055da0b83ed6432',
);

// Reading the public state of the counter contract from testnet,
Expand All @@ -197,64 +198,83 @@ export async function incrementCounterContract(): Promise<void> {
}
}

// Building the transaction script which will call the counter contract
let txScriptCode = `
use.external_contract::counter_contract
begin
call.counter_contract::increment_count
end
`;

// Creating the library to call the counter contract
let counterComponentLib = AssemblerUtils.createAccountComponentLibrary(
assembler, // assembler
"external_contract::counter_contract", // library path to call the contract
counterContractCode, // account code of the contract
);
const builder = client.createScriptBuilder();
const storageMap = new StorageMap();
const storageSlotMap = StorageSlot.map(storageMap);

const mappingAccountComponent = AccountComponent.compile(
counterContractCode,
builder,
[storageSlotMap],
).withSupportsAllTypes();

const walletSeed = new Uint8Array(32);
crypto.getRandomValues(walletSeed);

const secretKey = SecretKey.rpoFalconWithRNG(walletSeed);
const authComponent = AccountComponent.createAuthComponent(secretKey);

const accountBuilderResult = new AccountBuilder(walletSeed)
.accountType(AccountType.RegularAccountImmutableCode)
.storageMode(AccountStorageMode.public())
.withAuthComponent(authComponent)
.withComponent(mappingAccountComponent)
.build();

await client.addAccountSecretKeyToWebStore(secretKey);
await client.newAccount(accountBuilderResult.account, false);

// Creating the transaction script
let txScript = TransactionScript.compile(
txScriptCode,
assembler.withLibrary(counterComponentLib),
await client.syncState();

const accountCodeLib = builder.buildLibrary(
'external_contract::counter_contract',
counterContractCode,
);

// Creating a transaction request with the transaction script
let txIncrementRequest = new TransactionRequestBuilder()
builder.linkDynamicLibrary(accountCodeLib);

// Building the transaction script which will call the counter contract
const txScriptCode = `
use.external_contract::counter_contract
begin
call.counter_contract::increment_count
end
`;

const txScript = builder.compileTxScript(txScriptCode);
const txIncrementRequest = new TransactionRequestBuilder()
.withCustomScript(txScript)
.build();

// Executing the transaction script against the counter contract
let txResult = await client.newTransaction(
await client.submitNewTransaction(
counterContractAccount.id(),
txIncrementRequest,
);

// Submitting the transaction result to the node
await client.submitTransaction(txResult);

// Sync state
await client.syncState();

// Logging the count of counter contract
let counter = await client.getAccount(counterContractAccount.id());
const counter = await client.getAccount(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
let count = counter?.storage().getItem(0);
const count = counter?.storage().getItem(0);

// 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("")),
BigInt('0x' + count!.toHex().slice(-16).match(/../g)!.reverse().join('')),
);

console.log("Count: ", counterValue);
console.log('Count: ', counterValue);
}
```

To run the code above in our frontend, run the following command:

```
pnpm run dev
yarn dev
```

Open the browser console and click the button "Increment Counter Contract".
Expand Down Expand Up @@ -287,7 +307,8 @@ incrementCounterContract.ts:153 Count: 3
8. Calls `sys::truncate_stack` to truncate the stack to size 16.

```masm
use.miden::account
use.miden::active_account
use miden::native_account
use.std::sys

const.COUNTER_SLOT=0
Expand All @@ -298,7 +319,7 @@ export.get_count
push.COUNTER_SLOT
# => [index]

exec.account::get_item
exec.active_account::get_item
# => [count]

# clean up stack
Expand All @@ -312,7 +333,7 @@ export.increment_count
push.COUNTER_SLOT
# => [index]

exec.account::get_item
exec.active_account::get_item
# => [count]

add.1
Expand All @@ -323,7 +344,7 @@ export.increment_count
push.COUNTER_SLOT
# [index, count+1]

exec.account::set_item
exec.native_account::set_item
# => [OLD_VALUE]

dropw
Expand All @@ -333,13 +354,11 @@ end

**Note**: _It's a good habit to add comments below each line of MASM code with the expected stack state. This improves readability and helps with debugging._

### Concept of function visibility and modifiers in Miden smart contracts

The `export.increment_count` function in our Miden smart contract behaves like an "external" Solidity function without a modifier, meaning any user can call it to increment the contract's count. This is because it calls `account::incr_nonce` during execution. For internal procedures, use the `proc` keyword as opposed to `export`.
### Authentication Component

If the `increment_count` procedure did not call the `account::incr_nonce` procedure during its execution, only the deployer of the counter contract would be able to increment the count of the smart contract (if the RpoFalcon512 component was added to the account, in this case we didn't add it).
**Important**: Starting with Miden Client 0.10.0, all accounts must have an authentication component. For smart contracts that don't require authentication (like our counter contract), we use a `NoAuth` component.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's remove the reference to 0.10.0. Let's just say:

Suggested change
**Important**: Starting with Miden Client 0.10.0, all accounts must have an authentication component. For smart contracts that don't require authentication (like our counter contract), we use a `NoAuth` component.
**Important**: All accounts must have an authentication component. For smart contracts that do not require authentication (like our counter contract), we use a `NoAuth` component.

Nit: lets stick to avoiding contractions in our docs. Other projects follow this standard in technical writing. For example: https://docs.soliditylang.org/en/latest/contributing.html#expand-contractions


In essence, if a procedure performs a state change in the Miden smart contract, and does not call `account::incr_nonce` at some point during its execution, this function can be equated to having an `onlyOwner` Solidity modifer, meaning only the user with knowledge of the private key of the account can execute transactions that result in a state change.
This `NoAuth` component allows any user to interact with the smart contract without requiring signature verification.ivate key of the account can execute transactions that result in a state change.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: spelling


**Note**: _Adding the `account::incr_nonce` to a state changing procedure allows any user to call the procedure._

Expand All @@ -361,8 +380,8 @@ To run a full working example navigate to the `web-client` directory in the [mid

```bash
cd web-client
pnpm i
pnpm run start
yarn install
yarn start
```

### Resetting the `MidenClientDB`
Expand All @@ -376,6 +395,6 @@ The Miden webclient stores account and note data in the browser. If you get erro
await indexedDB.deleteDatabase(db.name);
console.log(`Deleted database: ${db.name}`);
}
console.log("All databases deleted.");
console.log('All databases deleted.');
})();
```
Loading