+ );
+}
diff --git a/docs/src/components/index.ts b/docs/src/components/index.ts
new file mode 100644
index 00000000..33f91fd9
--- /dev/null
+++ b/docs/src/components/index.ts
@@ -0,0 +1 @@
+export { default as CodeSdkTabs } from './CodeSdkTabs';
diff --git a/docs/src/web-client/create_deploy_tutorial.md b/docs/src/web-client/create_deploy_tutorial.md
index c350229d..80666c29 100644
--- a/docs/src/web-client/create_deploy_tutorial.md
+++ b/docs/src/web-client/create_deploy_tutorial.md
@@ -3,6 +3,8 @@ title: 'Creating Accounts and Deploying Faucets'
sidebar_position: 2
---
+import { CodeSdkTabs } from '@site/src/components';
+
_Using the Miden WebClient in TypeScript to create accounts and deploy faucets_
## Overview
@@ -28,7 +30,7 @@ Before we dive into code, a quick refresher:
- **Public accounts**: The account's data and code are stored on-chain and are openly visible, including its assets.
- **Private accounts**: The account's state and logic are off-chain, only known to its owner.
-- **Public notes**: The note's state is visible to anyone - perfect for scenarios where transparency is desired.
+- **Public notes**: The note's state is visible to anyone - perfect for scenarios where transparency is desired.
- **Private notes**: The note's state is stored off-chain, you will need to share the note data with the relevant parties (via email or Telegram) for them to be able to consume the note.
> **Important**: In Miden, "accounts" and "smart contracts" can be used interchangeably due to native account abstraction. Every account is programmable and can contain custom logic.
@@ -51,10 +53,12 @@ It is useful to think of notes on Miden as "cryptographic cashier's checks" that
cd miden-web-app
```
-3. Install the Miden WebClient SDK:
- ```bash
- yarn add @miden-sdk/miden-sdk@0.13.0
- ```
+3. Install the Miden SDK:
+
+
**NOTE!**: Be sure to add the `--webpack` command to your `package.json` when running the `dev script`. The dev script should look like this:
@@ -71,50 +75,95 @@ It is useful to think of notes on Miden as "cryptographic cashier's checks" that
The WebClient is your gateway to interact with the Miden blockchain. It handles state synchronization, transaction creation, and proof generation. Let's set it up.
-### Create `lib/createMintConsume.ts`
+### Create the library file
-First, we'll create a separate file for our blockchain logic. In the project root, create a folder `lib/` and inside it `createMintConsume.ts`:
+First, we'll create a separate file for our blockchain logic. In the project root, create a folder `lib/` and inside it `lib/react/createMintConsume.tsx` (React) or `lib/createMintConsume.ts` (TypeScript):
```bash
mkdir -p lib
-touch lib/createMintConsume.ts
```
-```ts
-// lib/createMintConsume.ts
+ {
+..// We'll add our logic here
+..console.log('Ready to go!');
+.};
+
+.return (
+..
+...
+..
+.);
+}
+
+export default function CreateMintConsume() {
+.return (
+..
+...
+..
+.);
+}`},
+ typescript: { code:`// lib/createMintConsume.ts
export async function createMintConsume(): Promise {
- if (typeof window === 'undefined') {
- console.warn('webClient() can only run in the browser');
- return;
- }
+.if (typeof window === 'undefined') {
+..console.warn('webClient() can only run in the browser');
+..return;
+.}
- // dynamic import → only in the browser, so WASM is loaded client‑side
- const { WebClient, AccountStorageMode, AccountId, NoteType } =
- await import('@miden-sdk/miden-sdk');
+.// dynamic import → only in the browser, so WASM is loaded client‑side
+.const { WebClient } =
+..await import('@miden-sdk/miden-sdk');
- // Connect to Miden testnet RPC endpoint
- const nodeEndpoint = 'https://rpc.testnet.miden.io';
- const client = await WebClient.createClient(nodeEndpoint);
+.// Connect to Miden testnet RPC endpoint
+.const nodeEndpoint = 'https://rpc.testnet.miden.io';
+.const client = await WebClient.createClient(nodeEndpoint);
- // 1. Sync with the latest blockchain state
- // This fetches the latest block header and state commitments
- const state = await client.syncState();
- console.log('Latest block number:', state.blockNum());
+.// 1. Sync with the latest blockchain state
+.// This fetches the latest block header and state commitments
+.const state = await client.syncState();
+.console.log('Latest block number:', state.blockNum());
- // At this point, your client is connected and synchronized
- // Ready to create accounts and deploy contracts!
-}
-```
+.// At this point, your client is connected and synchronized
+.// Ready to create accounts and deploy contracts!
+}` },
+}} reactFilename="lib/react/createMintConsume.tsx" tsFilename="lib/createMintConsume.ts" />
> Since we will be handling proof generation in the browser, it will be slower than proof generation handled by the Rust client. Check out the [tutorial on delegated proving](./creating_multiple_notes_tutorial.md#what-is-delegated-proving) to speed up proof generation in the browser.
## Step 3: Create the User Interface
-Now let's create a simple UI that will trigger our blockchain interactions. We'll replace the default Next.js page with a button that calls our `createMintConsume()` function.
+Now let's create a simple UI that will trigger our blockchain interactions. We'll replace the default Next.js page with a button that calls our function.
-Edit `app/page.tsx` to call `createMintConsume()` on a button click:
+Edit `app/page.tsx`:
+
+If you're using the **React SDK**, the page simply renders your self-contained component:
+
+```tsx
+// app/page.tsx
+'use client';
+import CreateMintConsume from '../lib/react/createMintConsume';
+
+export default function Home() {
+ return ;
+}
+```
+
+If you're using the **TypeScript SDK**, the page manages state and calls the library function directly:
```tsx
+// app/page.tsx
'use client';
import { useState } from 'react';
import { createMintConsume } from '../lib/createMintConsume';
@@ -154,39 +203,43 @@ export default function Home() {
Now we'll create Alice's account. Let's create a **public** account so we can easily track her transactions.
-Back in `lib/createMintConsume.ts`, extend the `createMintConsume()` function:
+Back in your library file, extend the function:
-
-```ts
-// lib/createMintConsume.ts
+ {
+.// 1. Create Alice's wallet (public, mutable)
+.console.log('Creating account for Alice…');
+.const alice = await createWallet({ storageMode: 'public' });
+.console.log('Alice ID:', alice.id().toString());
+};` },
+typescript: { code: `// lib/createMintConsume.ts
export async function createMintConsume(): Promise {
- if (typeof window === 'undefined') {
- console.warn('webClient() can only run in the browser');
- return;
- }
-
- const { WebClient, AccountStorageMode, AuthScheme } = await import(
- "@miden-sdk/miden-sdk"
- );
-
- const nodeEndpoint = 'https://rpc.testnet.miden.io';
- const client = await WebClient.createClient(nodeEndpoint);
-
- // 1. Sync with the latest blockchain state
- const state = await client.syncState();
- console.log('Latest block number:', state.blockNum());
-
- // 2. Create Alice's account
- console.log('Creating account for Alice…');
- const alice = await client.newWallet(
- AccountStorageMode.public(), // Public: account state is visible on-chain
- true, // Mutable: account code can be upgraded later
- AuthScheme.AuthRpoFalcon512 // Auth Scheme: RPO Falcon 512
- );
- console.log('Alice ID:', alice.id().toString());
-}
-```
-
+.if (typeof window === 'undefined') {
+..console.warn('webClient() can only run in the browser');
+..return;
+.}
+
+.const { WebClient, AccountStorageMode, AuthScheme } = await import(
+.."@miden-sdk/miden-sdk"
+.);
+
+.const nodeEndpoint = 'https://rpc.testnet.miden.io';
+.const client = await WebClient.createClient(nodeEndpoint);
+
+.// 1. Sync with the latest blockchain state
+.const state = await client.syncState();
+.console.log('Latest block number:', state.blockNum());
+
+.// 2. Create Alice's account
+.console.log('Creating account for Alice…');
+.const alice = await client.newWallet(
+..AccountStorageMode.public(), // Public: account state is visible on-chain
+..true, // Mutable: account code can be upgraded later
+..AuthScheme.AuthRpoFalcon512 // Auth Scheme: RPO Falcon 512
+.);
+.console.log('Alice ID:', alice.id().toString());
+}` },
+}} reactFilename="lib/react/createMintConsume.tsx" tsFilename="lib/createMintConsume.ts" />
## Step 5: Deploy a Fungible Faucet
@@ -194,24 +247,33 @@ A faucet in Miden is a special type of account that can mint new tokens. Think o
Add this code after creating Alice's account:
-
-```ts
-// 3. Deploy a fungible faucet
+
+console.log('Setup complete.');` },
+}} reactFilename="lib/react/createMintConsume.tsx" tsFilename="lib/createMintConsume.ts" />
### Understanding Faucet Parameters:
@@ -232,51 +294,95 @@ In this tutorial, we've successfully:
3. Created a wallet account for Alice
4. Deployed a fungible faucet that can mint custom tokens
-Your final `lib/createMintConsume.ts` should look like:
+Your final `lib/react/createMintConsume.tsx` (React) or `lib/createMintConsume.ts` (TypeScript) should look like:
+
+ {
+..// 1. Create Alice's wallet (public, mutable)
+..console.log('Creating account for Alice…');
+..const alice = await createWallet({ storageMode: 'public' });
+..console.log('Alice ID:', alice.id().toString());
+
+..// 2. Deploy a fungible faucet
+..console.log('Creating faucet…');
+..const faucet = await createFaucet({
+...tokenSymbol: 'MID',
+...decimals: 8,
+...maxSupply: BigInt(1_000_000),
+...storageMode: 'public',
+..});
+..console.log('Faucet ID:', faucet.id().toString());
+
+..console.log('Setup complete.');
+.};
+
+.return (
+..
+...
+..
+.);
+}
-```ts
-// lib/createMintConsume.ts
+export default function CreateMintConsume() {
+.return (
+..
+...
+..
+.);
+}`},
+ typescript: { code:`// lib/createMintConsume.ts
export async function createMintConsume(): Promise {
- if (typeof window === 'undefined') {
- console.warn('webClient() can only run in the browser');
- return;
- }
-
- // dynamic import → only in the browser, so WASM is loaded client‑side
- const { WebClient, AccountStorageMode, AuthScheme, NoteType, Address } =
- await import('@miden-sdk/miden-sdk');
-
- const nodeEndpoint = 'https://rpc.testnet.miden.io';
- const client = await WebClient.createClient(nodeEndpoint);
-
- // 1. Sync with the latest blockchain state
- const state = await client.syncState();
- console.log('Latest block number:', state.blockNum());
-
- // 2. Create Alice's account
- console.log('Creating account for Alice…');
- const alice = await client.newWallet(
- AccountStorageMode.public(),
- true,
- AuthScheme.AuthRpoFalcon512,
- );
- console.log('Alice ID:', alice.id().toString());
-
- // 3. Deploy a fungible faucet
- console.log('Creating faucet…');
- const faucet = await client.newFaucet(
- AccountStorageMode.public(),
- false,
- 'MID',
- 8,
- BigInt(1_000_000),
- AuthScheme.AuthRpoFalcon512,
- );
- console.log('Faucet ID:', faucet.id().toString());
-
- console.log('Setup complete.');
-}
-```
+.if (typeof window === 'undefined') {
+..console.warn('webClient() can only run in the browser');
+..return;
+.}
+
+.// dynamic import → only in the browser, so WASM is loaded client‑side
+.const { WebClient, AccountStorageMode, AuthScheme } =
+..await import('@miden-sdk/miden-sdk');
+
+.const nodeEndpoint = 'https://rpc.testnet.miden.io';
+.const client = await WebClient.createClient(nodeEndpoint);
+
+.// 1. Sync with the latest blockchain state
+.const state = await client.syncState();
+.console.log('Latest block number:', state.blockNum());
+
+.// 2. Create Alice's account
+.console.log('Creating account for Alice…');
+.const alice = await client.newWallet(
+..AccountStorageMode.public(),
+..true,
+..AuthScheme.AuthRpoFalcon512,
+.);
+.console.log('Alice ID:', alice.id().toString());
+
+.// 3. Deploy a fungible faucet
+.console.log('Creating faucet…');
+.const faucet = await client.newFaucet(
+..AccountStorageMode.public(),
+..false,
+..'MID',
+..8,
+..BigInt(1_000_000),
+..AuthScheme.AuthRpoFalcon512,
+.);
+.console.log('Faucet ID:', faucet.id().toString());
+
+.console.log('Setup complete.');
+}` },
+}} reactFilename="lib/react/createMintConsume.tsx" tsFilename="lib/createMintConsume.ts" />
### Running the example
diff --git a/docs/src/web-client/creating_multiple_notes_tutorial.md b/docs/src/web-client/creating_multiple_notes_tutorial.md
index b673c316..08ed894c 100644
--- a/docs/src/web-client/creating_multiple_notes_tutorial.md
+++ b/docs/src/web-client/creating_multiple_notes_tutorial.md
@@ -3,22 +3,24 @@ title: 'Creating Multiple Notes in a Single Transaction'
sidebar_position: 4
---
+import { CodeSdkTabs } from '@site/src/components';
+
_Using the Miden WebClient in TypeScript to create several P2ID notes in a single transaction_
## Overview
-In the previous sections we learned how to create accounts, deploy faucets, and mint tokens. In this tutorial we will:
+In the previous sections we learned how to create accounts, deploy faucets, and mint tokens. In this tutorial we will:
- **Mint** test tokens from a faucet to Alice
-- **Consume** the minted notes so the assets appear in Alice’s wallet
+- **Consume** the minted notes so the assets appear in Alice's wallet
- **Create three P2ID notes in a _single_ transaction** using a custom note‑script and delegated proving
The entire flow is wrapped in a helper called `multiSendWithDelegatedProver()` that you can call from any browser page.
-## What we’ll cover
+## What we'll cover
-1. Setting‑up the WebClient and initializing a local prover
-2. Building three P2ID notes worth 100 `MID` each
+1. Setting‑up the WebClient
+2. Building three P2ID notes worth 100 `MID` each
3. Submitting the transaction _using delegated proving_
## Prerequisites
@@ -37,8 +39,8 @@ _How does it work?_ When a user choses to use delegated proving, they send off a
Anyone can run their own delegated prover server. If you are building a product on Miden, it may make sense to run your own delegated prover server for your users. To run your own delegated proving server, follow the instructions here: https://crates.io/crates/miden-proving-service
-To keep this tutorial runnable without external services, the code below uses a local prover. You
-can switch to delegated proving later by swapping in `TransactionProver.newRemoteProver(...)`.
+The code below uses `submitNewTransaction`, which handles proving via the network's delegated
+proving service. This means your browser never has to generate the full ZK proof locally.
## Step 1: Initialize your Next.js project
@@ -56,10 +58,12 @@ can switch to delegated proving later by swapping in `TransactionProver.newRemot
cd miden-web-app
```
-3. Install the Miden WebClient SDK:
- ```bash
- yarn add @miden-sdk/miden-sdk@0.13.0
- ```
+3. Install the Miden SDK:
+
+
**NOTE!**: Be sure to add the `--webpack` command to your `package.json` when running the `dev script`. The dev script should look like this:
@@ -76,7 +80,22 @@ can switch to delegated proving later by swapping in `TransactionProver.newRemot
Add the following code to the `app/page.tsx` file:
+If you're using the **React SDK**, the page simply renders your self-contained component:
+
+```tsx
+// app/page.tsx
+'use client';
+import MultiSendWithDelegatedProver from '../lib/react/multiSendWithDelegatedProver';
+
+export default function Home() {
+ return ;
+}
+```
+
+If you're using the **TypeScript SDK**, the page manages state and calls the library function directly:
+
```tsx
+// app/page.tsx
'use client';
import { useState } from 'react';
import { multiSendWithDelegatedProver } from '../lib/multiSendWithDelegatedProver';
@@ -112,280 +131,391 @@ export default function Home() {
}
```
-## Step 3 — Initalize the WebClient
+## Step 3 — Initialize the WebClient
-Create the file `lib/multiSendWithDelegatedProver.ts` and add the following code. This snippet implements the function `multiSendWithDelegatedProver`, and initializes the WebClient along with a local prover.
+Create `lib/react/multiSendWithDelegatedProver.tsx` (React) or `lib/multiSendWithDelegatedProver.ts` (TypeScript) and add the following code. This snippet initializes the WebClient.
```
mkdir -p lib
-touch lib/multiSendWithDelegatedProver.ts
```
-```ts
-export async function multiSendWithDelegatedProver(): Promise {
- // Ensure this runs only in a browser context
- if (typeof window === 'undefined') return console.warn('Run in browser');
-
- const {
- WebClient,
- AccountStorageMode,
- AuthScheme,
- Address,
- NoteType,
- TransactionProver,
- Note,
- NoteAssets,
- OutputNoteArray,
- NoteAttachment,
- FungibleAsset,
- TransactionRequestBuilder,
- OutputNote,
- } = await import('@miden-sdk/miden-sdk');
-
- const client = await WebClient.createClient('https://rpc.testnet.miden.io');
- const prover = TransactionProver.newLocalProver();
-
- console.log('Latest block:', (await client.syncState()).blockNum());
+ {
+..// We'll add our logic here
+.};
+
+.return (
+..
+...
+..
+.);
}
-```
-## Step 4 — Create an account, deploy a faucet, mint and consume tokens
+export default function MultiSendWithDelegatedProver() {
+.return (
+..
+...
+..
+.);
+}`},
+ typescript: { code:`export async function multiSendWithDelegatedProver(): Promise {
+.// Ensure this runs only in a browser context
+.if (typeof window === 'undefined') return console.warn('Run in browser');
+
+.const {
+..WebClient,
+..AccountStorageMode,
+..AuthScheme,
+..Address,
+..NoteType,
+..Note,
+..NoteAssets,
+..OutputNoteArray,
+..NoteAttachment,
+..FungibleAsset,
+..TransactionRequestBuilder,
+..OutputNote,
+.} = await import('@miden-sdk/miden-sdk');
+
+.const client = await WebClient.createClient('https://rpc.testnet.miden.io');
+
+.console.log('Latest block:', (await client.syncState()).blockNum());
+}` },
+}} reactFilename="lib/react/multiSendWithDelegatedProver.tsx" tsFilename="lib/multiSendWithDelegatedProver.ts" />
+
+## Step 4 — Create an account, deploy a faucet, mint and consume tokens
+
+Add the code snippet below to the function. This code creates a wallet and faucet, mints tokens from the faucet for the wallet, and then consumes the minted tokens.
+
+ n.inputNoteRecord().id().toString());
+await consume({ accountId: aliceId, noteIds });`},
+ typescript: { code:`// ── Creating new account ──────────────────────────────────────────────────────
console.log('Creating account for Alice…');
const alice = await client.newWallet(
- AccountStorageMode.public(),
- true,
- AuthScheme.AuthRpoFalcon512,
+.AccountStorageMode.public(),
+.true,
+.AuthScheme.AuthRpoFalcon512,
);
-console.log('Alice accout ID:', alice.id().toString());
+console.log('Alice account ID:', alice.id().toString());
// ── Creating new faucet ──────────────────────────────────────────────────────
const faucet = await client.newFaucet(
- AccountStorageMode.public(),
- false,
- 'MID',
- 8,
- BigInt(1_000_000),
- AuthScheme.AuthRpoFalcon512,
+.AccountStorageMode.public(),
+.false,
+.'MID',
+.8,
+.BigInt(1_000_000),
+.AuthScheme.AuthRpoFalcon512,
);
console.log('Faucet ID:', faucet.id().toString());
// ── mint 10 000 MID to Alice ──────────────────────────────────────────────────────
-{
- const txResult = await client.executeTransaction(
- faucet.id(),
- client.newMintTransactionRequest(
- alice.id(),
- faucet.id(),
- NoteType.Public,
- BigInt(10_000),
- ),
- );
- const proven = await client.proveTransaction(txResult, prover);
- const submissionHeight = await client.submitProvenTransaction(
- proven,
- txResult,
- );
- await client.applyTransaction(txResult, submissionHeight);
+await client.submitNewTransaction(
+.faucet.id(),
+.client.newMintTransactionRequest(
+..alice.id(),
+..faucet.id(),
+..NoteType.Public,
+..BigInt(10_000),
+.),
+);
- console.log('waiting for settlement');
- await new Promise((r) => setTimeout(r, 7_000));
- await client.syncState();
-}
+console.log('waiting for settlement');
+await new Promise((r) => setTimeout(r, 7_000));
+await client.syncState();
// ── consume the freshly minted notes ──────────────────────────────────────────────
const noteList = (await client.getConsumableNotes(alice.id())).map((rec) =>
- rec.inputNoteRecord().toNote(),
+.rec.inputNoteRecord().toNote(),
);
-{
- const txResult = await client.executeTransaction(
- alice.id(),
- client.newConsumeTransactionRequest(noteList),
- );
- const proven = await client.proveTransaction(txResult, prover);
- await client.syncState();
- const submissionHeight = await client.submitProvenTransaction(
- proven,
- txResult,
- );
- await client.applyTransaction(txResult, submissionHeight);
-}
-```
-
-## Step 5 — Build and Create P2ID notes
-
-Add the following code to the `multiSendWithDelegatedProver` function. This code defines three recipient addresses, builds three P2ID notes with 100 `MID` each, and then creates all three notes in the same transaction.
+await client.submitNewTransaction(
+.alice.id(),
+.client.newConsumeTransactionRequest(noteList),
+);` },
+}} reactFilename="lib/react/multiSendWithDelegatedProver.tsx" tsFilename="lib/multiSendWithDelegatedProver.ts" />
+
+## Step 5 — Build and Create P2ID notes
+
+Add the following code to the function. This code defines three recipient addresses, builds three P2ID notes with 100 `MID` each, and then creates all three notes in the same transaction.
+
+ {
- const receiverAccountId = Address.fromBech32(addr).accountId();
- const note = Note.createP2IDNote(
- alice.id(),
- receiverAccountId,
- assets,
- NoteType.Public,
- new NoteAttachment(),
- );
-
- return OutputNote.full(note);
+.const receiverAccountId = Address.fromBech32(addr).accountId();
+.const note = Note.createP2IDNote(
+..alice.id(),
+..receiverAccountId,
+..assets,
+..NoteType.Public,
+..new NoteAttachment(),
+.);
+
+.return OutputNote.full(note);
});
// ── create all P2ID notes ───────────────────────────────────────────────────────────────
-await client.submitNewTransaction(
- alice.id(),
- new TransactionRequestBuilder()
- .withOwnOutputNotes(new OutputNoteArray(p2idNotes))
- .build(),
-);
+const builder = new TransactionRequestBuilder();
+const txRequest = builder.withOwnOutputNotes(new OutputNoteArray(p2idNotes)).build();
+await client.submitNewTransaction(alice.id(), txRequest);
-console.log('All notes created ✅');
-```
+console.log('All notes created ✅');` },
+}} reactFilename="lib/react/multiSendWithDelegatedProver.tsx" tsFilename="lib/multiSendWithDelegatedProver.ts" />
## Summary
-Your `lib/multiSendWithDelegatedProver.ts` file sould now look like this:
+Your library file should now look like this:
+
+ {
+..// 1. Create Alice's wallet
+..console.log('Creating account for Alice…');
+..const alice = await createWallet({ storageMode: 'public' });
+..const aliceId = alice.id().toString();
+..console.log('Alice account ID:', aliceId);
+
+..// 2. Deploy a fungible faucet
+..const faucet = await createFaucet({
+...tokenSymbol: 'MID',
+...decimals: 8,
+...maxSupply: BigInt(1_000_000),
+...storageMode: 'public',
+..});
+..const faucetId = faucet.id().toString();
+..console.log('Faucet ID:', faucetId);
+
+..// 3. Mint 10,000 MID to Alice
+..const mintResult = await mint({
+...faucetId,
+...targetAccountId: aliceId,
+...amount: BigInt(10_000),
+...noteType: 'public',
+..});
+
+..console.log('Waiting for settlement…');
+..await waitForCommit(mintResult.transactionId);
+
+..// 4. Consume the freshly minted notes
+..const notes = await waitForConsumableNotes({ accountId: aliceId });
+..const noteIds = notes.map((n) => n.inputNoteRecord().id().toString());
+..await consume({ accountId: aliceId, noteIds });
+
+..// 5. Send 100 MID to three recipients in a single transaction
+..await sendMany({
+...from: aliceId,
+...assetId: faucetId,
+...recipients: [
+....{ to: 'mtst1aqezqc90x7dkzypr9m5fmlpp85w6cl04', amount: BigInt(100) },
+....{ to: 'mtst1apjg2ul76wrkxyr5qlcnczaskypa4ljn', amount: BigInt(100) },
+....{ to: 'mtst1arpee6y9cm8t7ypn33pc8fzj6gkzz7kd', amount: BigInt(100) },
+...],
+...noteType: 'public',
+..});
+
+..console.log('All notes created ✅');
+.};
+
+.return (
+..
+...
+..
+.);
+}
-```ts
-/**
- * Demonstrates multi-send functionality using a local prover on the Miden Network
- * Creates multiple P2ID (Pay to ID) notes for different recipients
- *
- * @throws {Error} If the function cannot be executed in a browser environment
- */
+export default function MultiSendWithDelegatedProver() {
+.return (
+..
+...
+..
+.);
+}`},
+ typescript: { code:`/\*\*
+.\* Demonstrates multi-send functionality with delegated proving on the Miden Network
+.\* Creates multiple P2ID (Pay to ID) notes for different recipients
+.\*
+.\* @throws {Error} If the function cannot be executed in a browser environment
+.\*/
export async function multiSendWithDelegatedProver(): Promise {
- // Ensure this runs only in a browser context
- if (typeof window === 'undefined') return console.warn('Run in browser');
-
- const {
- WebClient,
- AccountStorageMode,
- AuthScheme,
- Address,
- NoteType,
- TransactionProver,
- Note,
- NoteAssets,
- OutputNoteArray,
- FungibleAsset,
- NoteAttachment,
- TransactionRequestBuilder,
- OutputNote,
- } = await import('@miden-sdk/miden-sdk');
-
- const client = await WebClient.createClient('https://rpc.testnet.miden.io');
- const prover = TransactionProver.newLocalProver();
-
- console.log('Latest block:', (await client.syncState()).blockNum());
-
- // ── Creating new account ──────────────────────────────────────────────────────
- console.log('Creating account for Alice…');
- const alice = await client.newWallet(
- AccountStorageMode.public(),
- true,
- AuthScheme.AuthRpoFalcon512,
- );
- console.log('Alice accout ID:', alice.id().toString());
-
- // ── Creating new faucet ──────────────────────────────────────────────────────
- const faucet = await client.newFaucet(
- AccountStorageMode.public(),
- false,
- 'MID',
- 8,
- BigInt(1_000_000),
- AuthScheme.AuthRpoFalcon512,
- );
- console.log('Faucet ID:', faucet.id().toString());
-
- // ── mint 10 000 MID to Alice ──────────────────────────────────────────────────────
- {
- const txResult = await client.executeTransaction(
- faucet.id(),
- client.newMintTransactionRequest(
- alice.id(),
- faucet.id(),
- NoteType.Public,
- BigInt(10_000),
- ),
- );
- const proven = await client.proveTransaction(txResult, prover);
- const submissionHeight = await client.submitProvenTransaction(
- proven,
- txResult,
- );
- await client.applyTransaction(txResult, submissionHeight);
-
- console.log('waiting for settlement');
- await new Promise((r) => setTimeout(r, 7_000));
- await client.syncState();
- }
-
- // ── consume the freshly minted notes ──────────────────────────────────────────────
- const noteList = (await client.getConsumableNotes(alice.id())).map((rec) =>
- rec.inputNoteRecord().toNote(),
- );
-
- {
- const txResult = await client.executeTransaction(
- alice.id(),
- client.newConsumeTransactionRequest(noteList),
- );
- const proven = await client.proveTransaction(txResult, prover);
- await client.syncState();
- const submissionHeight = await client.submitProvenTransaction(
- proven,
- txResult,
- );
- await client.applyTransaction(txResult, submissionHeight);
- }
-
- // ── build 3 P2ID notes (100 MID each) ─────────────────────────────────────────────
- const recipientAddresses = [
- 'mtst1aqezqc90x7dkzypr9m5fmlpp85w6cl04',
- 'mtst1apjg2ul76wrkxyr5qlcnczaskypa4ljn',
- 'mtst1arpee6y9cm8t7ypn33pc8fzj6gkzz7kd',
- ];
-
- const assets = new NoteAssets([new FungibleAsset(faucet.id(), BigInt(100))]);
-
- const p2idNotes = recipientAddresses.map((addr) => {
- const receiverAccountId = Address.fromBech32(addr).accountId();
- const note = Note.createP2IDNote(
- alice.id(),
- receiverAccountId,
- assets,
- NoteType.Public,
- new NoteAttachment(),
- );
-
- return OutputNote.full(note);
- });
-
- // ── create all P2ID notes ───────────────────────────────────────────────────────────────
- await client.submitNewTransaction(
- alice.id(),
- new TransactionRequestBuilder()
- .withOwnOutputNotes(new OutputNoteArray(p2idNotes))
- .build(),
- );
-
- console.log('All notes created ✅');
-}
-```
+.// Ensure this runs only in a browser context
+.if (typeof window === 'undefined') return console.warn('Run in browser');
+
+.const {
+..WebClient,
+..AccountStorageMode,
+..AuthScheme,
+..Address,
+..NoteType,
+..Note,
+..NoteAssets,
+..OutputNoteArray,
+..FungibleAsset,
+..NoteAttachment,
+..TransactionRequestBuilder,
+..OutputNote,
+.} = await import('@miden-sdk/miden-sdk');
+
+.const client = await WebClient.createClient('https://rpc.testnet.miden.io');
+
+.console.log('Latest block:', (await client.syncState()).blockNum());
+
+.// ── Creating new account ──────────────────────────────────────────────────────
+.console.log('Creating account for Alice…');
+.const alice = await client.newWallet(
+..AccountStorageMode.public(),
+..true,
+..AuthScheme.AuthRpoFalcon512,
+.);
+.console.log('Alice account ID:', alice.id().toString());
+
+.// ── Creating new faucet ──────────────────────────────────────────────────────
+.const faucet = await client.newFaucet(
+..AccountStorageMode.public(),
+..false,
+..'MID',
+..8,
+..BigInt(1_000_000),
+..AuthScheme.AuthRpoFalcon512,
+.);
+.console.log('Faucet ID:', faucet.id().toString());
+
+.// ── mint 10 000 MID to Alice ──────────────────────────────────────────────────────
+.await client.submitNewTransaction(
+..faucet.id(),
+..client.newMintTransactionRequest(
+...alice.id(),
+...faucet.id(),
+...NoteType.Public,
+...BigInt(10_000),
+..),
+.);
+
+.console.log('waiting for settlement');
+.await new Promise((r) => setTimeout(r, 7_000));
+.await client.syncState();
+
+.// ── consume the freshly minted notes ──────────────────────────────────────────────
+.const noteList = (await client.getConsumableNotes(alice.id())).map((rec) =>
+..rec.inputNoteRecord().toNote(),
+.);
+
+.await client.submitNewTransaction(
+..alice.id(),
+..client.newConsumeTransactionRequest(noteList),
+.);
+
+.// ── build 3 P2ID notes (100 MID each) ─────────────────────────────────────────────
+.const recipientAddresses = [
+..'mtst1aqezqc90x7dkzypr9m5fmlpp85w6cl04',
+..'mtst1apjg2ul76wrkxyr5qlcnczaskypa4ljn',
+..'mtst1arpee6y9cm8t7ypn33pc8fzj6gkzz7kd',
+.];
+
+.const assets = new NoteAssets([new FungibleAsset(faucet.id(), BigInt(100))]);
+
+.const p2idNotes = recipientAddresses.map((addr) => {
+..const receiverAccountId = Address.fromBech32(addr).accountId();
+..const note = Note.createP2IDNote(
+...alice.id(),
+...receiverAccountId,
+...assets,
+...NoteType.Public,
+...new NoteAttachment(),
+..);
+
+..return OutputNote.full(note);
+.});
+
+.// ── create all P2ID notes ───────────────────────────────────────────────────────────────
+.const builder = new TransactionRequestBuilder();
+.const txRequest = builder.withOwnOutputNotes(new OutputNoteArray(p2idNotes)).build();
+.await client.submitNewTransaction(alice.id(), txRequest);
+
+.console.log('All notes created ✅');
+}` },
+}} reactFilename="lib/react/multiSendWithDelegatedProver.tsx" tsFilename="lib/multiSendWithDelegatedProver.ts" />
### 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 7b8c12b1..81c03c7b 100644
--- a/docs/src/web-client/mint_consume_create_tutorial.md
+++ b/docs/src/web-client/mint_consume_create_tutorial.md
@@ -3,6 +3,8 @@ title: 'Mint, Consume, and Create Notes'
sidebar_position: 3
---
+import { CodeSdkTabs } from '@site/src/components';
+
_Using the Miden WebClient in TypeScript to mint, consume, and transfer assets_
## Overview
@@ -34,20 +36,30 @@ Before we start coding, it's important to understand **notes**:
Let's mint some tokens for Alice. When we mint from a faucet, it creates a note containing the specified amount of tokens targeted to Alice's account.
-Add this to the end of your `createMintConsume` function in `lib/createMintConsume.ts`:
-
-
-
-```ts
-// 4. Mint tokens from the faucet to Alice
+Add this to the end of your `createMintConsume` function:
+
+ setTimeout(resolve, 10000));
-await client.syncState();
-```
-
-
+await client.syncState();` },
+}} reactFilename="lib/react/createMintConsume.tsx" tsFilename="lib/createMintConsume.ts" />
### What's happening here?
@@ -72,31 +82,40 @@ await client.syncState();
After minting, Alice has a note waiting for her but the tokens aren't in her account yet.
To identify notes that are ready to consume, the Miden WebClient provides the `getConsumableNotes` function:
-```ts
-// 5. Find notes available for consumption
-const consumableNotes = await client.getConsumableNotes(alice.id());
-console.log(`Found ${consumableNotes.length} note(s) to consume`);
-
-const noteIds = consumableNotes.map((note) =>
- note.inputNoteRecord().id().toString(),
-);
-console.log('Consumable note IDs:', noteIds);
-```
+ n.inputNoteRecord().id().toString());
+console.log('Consumable notes:', noteIds);` },
+typescript: { code: `// 5. Find notes available for consumption
+const mintedNotes = await client.getConsumableNotes(alice.id());
+console.log(\`Found \${mintedNotes.length} note(s) to consume\`);
+
+const mintedNoteList = mintedNotes.map((n) => n.inputNoteRecord().toNote());
+console.log(
+.'Minted notes:',
+.mintedNoteList.map((note) => note.id().toString()),
+);` },
+}} reactFilename="lib/react/createMintConsume.tsx" tsFilename="lib/createMintConsume.ts" />
## Step 3: Consume notes in a single transaction
Now let's consume the notes to add the tokens to Alice's account balance:
-```ts
-// 6. Consume the notes to add tokens to Alice's balance
+
## Step 4: Sending tokens to other accounts
@@ -106,32 +125,36 @@ _The standard asset transfer note on Miden is the P2ID note (Pay-to-Id). There i
Now that Alice has tokens in her account, she can send some to Bob:
-
-```ts
-// Add this import at the top of the file
-import { NoteType } from "@miden-sdk/miden-sdk";
-// ...
-
-// 7. Send tokens from Alice to Bob
+
+console.log('Tokens sent successfully!');` },
+}} reactFilename="lib/react/createMintConsume.tsx" tsFilename="lib/createMintConsume.ts" />
### Understanding P2ID notes
@@ -143,106 +166,190 @@ The transaction creates a **P2ID (Pay-to-ID)** note:
## Summary
-Here's the complete `lib/createMintConsume.ts` file:
-
-```ts
-// lib/createMintConsume.ts
-export async function createMintConsume(): Promise {
- if (typeof window === 'undefined') {
- console.warn('webClient() can only run in the browser');
- return;
- }
-
- // dynamic import → only in the browser, so WASM is loaded client‑side
- const { WebClient, AccountStorageMode, AuthScheme, NoteType, Address } =
- await import('@miden-sdk/miden-sdk');
-
- const nodeEndpoint = 'https://rpc.testnet.miden.io';
- const client = await WebClient.createClient(nodeEndpoint);
-
- // 1. Sync with the latest blockchain state
- const state = await client.syncState();
- console.log('Latest block number:', state.blockNum());
-
- // 2. Create Alice's account
- console.log('Creating account for Alice…');
- const aliceSeed = new Uint8Array(32);
- crypto.getRandomValues(aliceSeed);
- const alice = await client.newWallet(
- AccountStorageMode.public(),
- true,
- AuthScheme.AuthRpoFalcon512,
- aliceSeed,
- );
- console.log('Alice ID:', alice.id().toString());
-
- // 3. Deploy a fungible faucet
- console.log('Creating faucet…');
- const faucet = await client.newFaucet(
- AccountStorageMode.public(),
- false,
- 'MID',
- 8,
- BigInt(1_000_000),
- AuthScheme.AuthRpoFalcon512,
- );
- console.log('Faucet ID:', faucet.id().toString());
-
- await client.syncState();
-
- // 4. Mint tokens to Alice
- await client.syncState();
-
- console.log('Minting tokens to Alice...');
- const mintTxRequest = client.newMintTransactionRequest(
- alice.id(),
- faucet.id(),
- NoteType.Public,
- BigInt(1000),
- );
-
- await client.submitNewTransaction(faucet.id(), mintTxRequest);
-
- console.log('Waiting 10 seconds for transaction confirmation...');
- await new Promise((resolve) => setTimeout(resolve, 10000));
- await client.syncState();
-
- // 5. Fetch minted notes
- const mintedNotes = await client.getConsumableNotes(alice.id());
- const mintedNoteList = mintedNotes.map((n) => n.inputNoteRecord().toNote());
- console.log(
- 'Minted notes:',
- mintedNoteList.map((note) => note.id().toString()),
- );
-
- // 6. Consume minted notes
- console.log('Consuming minted notes...');
- const consumeTxRequest = client.newConsumeTransactionRequest(mintedNoteList);
-
- await client.submitNewTransaction(alice.id(), consumeTxRequest);
-
- await client.syncState();
- console.log('Notes consumed.');
-
- // 7. Send tokens to Bob
- const bobAccountId = Address.fromBech32(
- 'mtst1apve54rq8ux0jqqqqrkh5y0r0y8cwza6_qruqqypuyph',
- ).accountId();
- console.log("Sending tokens to Bob's account...");
- const sendTxRequest = client.newSendTransactionRequest(
- alice.id(),
- bobAccountId,
- faucet.id(),
- NoteType.Public,
- BigInt(100),
- );
-
- await client.submitNewTransaction(alice.id(), sendTxRequest);
- console.log('Tokens sent successfully!');
+Here's the complete `lib/react/createMintConsume.tsx` (React) or `lib/createMintConsume.ts` (TypeScript):
+
+ {
+..// 1. Create Alice's wallet (public, mutable)
+..console.log('Creating account for Alice…');
+..const alice = await createWallet({ storageMode: 'public' });
+..const aliceId = alice.id().toString();
+..console.log('Alice ID:', aliceId);
+
+..// 2. Deploy a fungible faucet
+..console.log('Creating faucet…');
+..const faucet = await createFaucet({
+...tokenSymbol: 'MID',
+...decimals: 8,
+...maxSupply: BigInt(1_000_000),
+...storageMode: 'public',
+..});
+..const faucetId = faucet.id().toString();
+..console.log('Faucet ID:', faucetId);
+
+..// 3. Mint 1000 tokens to Alice
+..console.log('Minting tokens to Alice...');
+..const mintResult = await mint({
+...faucetId,
+...targetAccountId: aliceId,
+...amount: BigInt(1000),
+...noteType: 'public',
+..});
+..console.log('Mint tx:', mintResult.transactionId);
+
+..// 4. Wait for the mint transaction to be committed
+..await waitForCommit(mintResult.transactionId);
+
+..// 5. Wait for consumable notes to appear
+..const notes = await waitForConsumableNotes({ accountId: aliceId });
+..const noteIds = notes.map((n) => n.inputNoteRecord().id().toString());
+..console.log('Consumable notes:', noteIds);
+
+..// 6. Consume minted notes
+..console.log('Consuming minted notes...');
+..await consume({ accountId: aliceId, noteIds });
+..console.log('Notes consumed.');
+
+..// 7. Send 100 tokens to Bob
+..const bobAddress = 'mtst1apve54rq8ux0jqqqqrkh5y0r0y8cwza6_qruqqypuyph';
+..console.log("Sending tokens to Bob's account...");
+..await send({
+...from: aliceId,
+...to: bobAddress,
+...assetId: faucetId,
+...amount: BigInt(100),
+...noteType: 'public',
+..});
+..console.log('Tokens sent successfully!');
+.};
+
+.return (
+..
+...
+..
+.);
}
-```
-Let's run the `lib/createMintConsume.ts` function again. Reload the page and click "Start WebClient".
+export default function CreateMintConsume() {
+.return (
+..
+...
+..
+.);
+}`},
+ typescript: { code:`// lib/createMintConsume.ts
+export async function createMintConsume(): Promise {
+.if (typeof window === 'undefined') {
+..console.warn('webClient() can only run in the browser');
+..return;
+.}
+
+.// dynamic import → only in the browser, so WASM is loaded client‑side
+.const { WebClient, AccountStorageMode, AuthScheme, NoteType, Address } =
+..await import('@miden-sdk/miden-sdk');
+
+.const nodeEndpoint = 'https://rpc.testnet.miden.io';
+.const client = await WebClient.createClient(nodeEndpoint);
+
+.// 1. Sync with the latest blockchain state
+.const state = await client.syncState();
+.console.log('Latest block number:', state.blockNum());
+
+.// 2. Create Alice's account
+.console.log('Creating account for Alice…');
+.const aliceSeed = new Uint8Array(32);
+.crypto.getRandomValues(aliceSeed);
+.const alice = await client.newWallet(
+..AccountStorageMode.public(),
+..true,
+..AuthScheme.AuthRpoFalcon512,
+..aliceSeed,
+.);
+.console.log('Alice ID:', alice.id().toString());
+
+.// 3. Deploy a fungible faucet
+.console.log('Creating faucet…');
+.const faucet = await client.newFaucet(
+..AccountStorageMode.public(),
+..false,
+..'MID',
+..8,
+..BigInt(1_000_000),
+..AuthScheme.AuthRpoFalcon512,
+.);
+.console.log('Faucet ID:', faucet.id().toString());
+
+.await client.syncState();
+
+.// 4. Mint tokens to Alice
+.await client.syncState();
+
+.console.log('Minting tokens to Alice...');
+.const mintTxRequest = client.newMintTransactionRequest(
+..alice.id(),
+..faucet.id(),
+..NoteType.Public,
+..BigInt(1000),
+.);
+
+.await client.submitNewTransaction(faucet.id(), mintTxRequest);
+
+.console.log('Waiting 10 seconds for transaction confirmation...');
+.await new Promise((resolve) => setTimeout(resolve, 10000));
+.await client.syncState();
+
+.// 5. Fetch minted notes
+.const mintedNotes = await client.getConsumableNotes(alice.id());
+.const mintedNoteList = mintedNotes.map((n) => n.inputNoteRecord().toNote());
+.console.log(
+..'Minted notes:',
+..mintedNoteList.map((note) => note.id().toString()),
+.);
+
+.// 6. Consume minted notes
+.console.log('Consuming minted notes...');
+.const consumeTxRequest = client.newConsumeTransactionRequest(mintedNoteList);
+
+.await client.submitNewTransaction(alice.id(), consumeTxRequest);
+
+.await client.syncState();
+.console.log('Notes consumed.');
+
+.// 7. Send tokens to Bob
+.const bobAccountId = Address.fromBech32(
+..'mtst1apve54rq8ux0jqqqqrkh5y0r0y8cwza6_qruqqypuyph',
+.).accountId();
+.console.log("Sending tokens to Bob's account...");
+.const sendTxRequest = client.newSendTransactionRequest(
+..alice.id(),
+..bobAccountId,
+..faucet.id(),
+..NoteType.Public,
+..BigInt(100),
+.);
+
+.await client.submitNewTransaction(alice.id(), sendTxRequest);
+.console.log('Tokens sent successfully!');
+}` },
+}} reactFilename="lib/react/createMintConsume.tsx" tsFilename="lib/createMintConsume.ts" />
+
+Let's run the function again. Reload the page and click "Start WebClient".
The output will look like this:
diff --git a/docs/src/web-client/react_wallet_tutorial.md b/docs/src/web-client/react_wallet_tutorial.md
new file mode 100644
index 00000000..24bfc0b5
--- /dev/null
+++ b/docs/src/web-client/react_wallet_tutorial.md
@@ -0,0 +1,930 @@
+---
+title: 'Building a React Wallet'
+sidebar_position: 8
+---
+
+# Building a React Wallet
+
+_Using the Miden React SDK to build a complete wallet UI with account management, token transfers, and note claiming_
+
+## Overview
+
+In this tutorial we will build a complete wallet application using the `@miden-sdk/react` package. The Miden React SDK provides a set of hooks and utilities that make it easy to integrate Miden functionality into React applications.
+
+By the end of this tutorial, you will have a working wallet that can:
+
+- Create new accounts
+- Display account balances
+- List and claim unclaimed notes
+- Send tokens to other accounts
+
+## What we'll cover
+
+- Setting up a React project with the Miden React SDK
+- Using the `MidenProvider` to configure the client
+- Managing accounts with `useAccounts`, `useAccount`, and `useCreateWallet`
+- Displaying and claiming notes with `useNotes` and `useConsume`
+- Sending tokens with `useSend`
+- Formatting utilities for assets and notes
+- External signer integration patterns
+
+## Prerequisites
+
+- Node `v20` or greater
+- Familiarity with React and TypeScript
+- `yarn`
+
+---
+
+## Step 1: Project Setup and MidenProvider
+
+First, create a new Vite + React project and install the Miden React SDK.
+
+1. Create a new Vite project with React and TypeScript:
+
+ ```bash
+ yarn create vite miden-wallet --template react-ts
+ cd miden-wallet
+ ```
+
+2. Install the Miden React SDK:
+
+ ```bash
+ yarn add @miden-sdk/react
+ ```
+
+3. Configure the `MidenProvider` in your `main.tsx` file. The provider initializes the Miden client and makes it available to all child components:
+
+```tsx
+// main.tsx
+import React from 'react';
+import ReactDOM from 'react-dom/client';
+import { MidenProvider } from '@miden-sdk/react';
+import App from './App';
+
+ReactDOM.createRoot(document.getElementById('root')!).render(
+
+
+
+
+ ,
+);
+```
+
+The `MidenProvider` accepts a `config` object with the following options:
+
+- `rpcUrl`: The RPC endpoint to connect to (`"testnet"`, `"devnet"`, or a custom URL)
+- `prover`: The prover to use (`"testnet"` for delegated proving, or `"local"` for local proving)
+
+---
+
+## Step 2: App Shell with useMiden
+
+The `useMiden()` hook provides access to the client's initialization state. Use it to show loading and error states while the client initializes.
+
+```tsx
+// App.tsx
+import { useMiden } from '@miden-sdk/react';
+
+export default function App() {
+ const { isReady, error } = useMiden();
+
+ if (error) return
Error: {error.message}
;
+ if (!isReady) return
Initializing...
;
+
+ return
Wallet ready!
;
+}
+```
+
+The `useMiden()` hook returns:
+
+- `isReady`: `true` when the client has finished initializing
+- `error`: An error object if initialization failed
+
+---
+
+## Step 3: Listing Accounts with useAccounts
+
+The `useAccounts()` hook provides access to all accounts stored in the client. Use it to check if the user has any existing wallets.
+
+```tsx
+import { useMiden, useAccounts } from '@miden-sdk/react';
+
+export default function App() {
+ const { isReady, error } = useMiden();
+ const { wallets, isLoading } = useAccounts();
+
+ if (error) return
;
+}
+```
+
+The `useCreateWallet()` hook returns:
+
+- `createWallet(options?)`: Function to create a new wallet
+- `isCreating`: `true` while a wallet is being created
+
+---
+
+## Step 5: Displaying Account Details with useAccount
+
+The `useAccount(accountId)` hook provides detailed information about a specific account, including its assets and balances.
+
+```tsx
+import { useAccount, formatAssetAmount } from '@miden-sdk/react';
+
+function Wallet({ accountId }: { accountId: string }) {
+ const { account, assets } = useAccount(accountId);
+
+ return (
+