create-miden-app 1.0.6 → 1.0.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (70) hide show
  1. package/package.json +1 -1
  2. package/template/.claude/commands/review-security.md +67 -0
  3. package/template/.claude/settings.json +1 -7
  4. package/template/.claude/settings.local.json +24 -0
  5. package/template/.claude/skills/frontend-pitfalls/SKILL.md +28 -31
  6. package/template/.claude/skills/frontend-source-guide/SKILL.md +14 -14
  7. package/template/.claude/skills/miden-concepts/SKILL.md +4 -2
  8. package/template/.claude/skills/react-sdk-patterns/SKILL.md +294 -28
  9. package/template/.claude/skills/signer-integration/SKILL.md +22 -3
  10. package/template/.claude/skills/testing-patterns/SKILL.md +201 -40
  11. package/template/.claude/skills/vite-wasm-setup/SKILL.md +20 -14
  12. package/template/.claude/skills/web-client-usage/SKILL.md +454 -0
  13. package/template/.env.example +15 -2
  14. package/template/.mcp.json +9 -0
  15. package/template/CLAUDE.md +49 -16
  16. package/template/README.md +85 -19
  17. package/template/package.json +5 -4
  18. package/template/public/packages/counter_account.masp +0 -0
  19. package/template/public/packages/increment_note.masp +0 -0
  20. package/template/src/__tests__/fixtures/accounts.ts +17 -6
  21. package/template/src/__tests__/fixtures/index.ts +1 -0
  22. package/template/src/__tests__/mocks/miden-sdk-react.ts +18 -1
  23. package/template/src/__tests__/patterns/mutation-hook.test.tsx +2 -2
  24. package/template/src/__tests__/patterns/provider-setup.test.tsx +2 -0
  25. package/template/src/components/AppContent.tsx +33 -3
  26. package/template/{create-miden-app/template/src/components/Counter.tsx → src/components/ConfiguredCounter.tsx} +7 -4
  27. package/template/src/components/Counter.tsx +12 -41
  28. package/template/src/components/__tests__/AppContent.test.tsx +192 -4
  29. package/template/src/components/__tests__/ConfiguredCounter.test.tsx +116 -0
  30. package/template/src/components/__tests__/Counter.test.tsx +24 -94
  31. package/template/src/config.ts +26 -6
  32. package/template/src/hooks/__tests__/useIncrementCounter.test.tsx +257 -0
  33. package/template/src/hooks/useIncrementCounter.ts +109 -50
  34. package/template/src/providers.tsx +20 -24
  35. package/template/vite.config.ts +1 -1
  36. package/template/vitest.config.ts +1 -2
  37. package/template/yarn.lock +761 -688
  38. package/template/create-miden-app/template/.claude/hooks/typecheck.sh +0 -27
  39. package/template/create-miden-app/template/.claude/settings.json +0 -17
  40. package/template/create-miden-app/template/.claude/skills/frontend-pitfalls/SKILL.md +0 -189
  41. package/template/create-miden-app/template/.claude/skills/frontend-source-guide/SKILL.md +0 -163
  42. package/template/create-miden-app/template/.claude/skills/miden-concepts/SKILL.md +0 -108
  43. package/template/create-miden-app/template/.claude/skills/react-sdk-patterns/SKILL.md +0 -294
  44. package/template/create-miden-app/template/.claude/skills/signer-integration/SKILL.md +0 -158
  45. package/template/create-miden-app/template/.claude/skills/vite-wasm-setup/SKILL.md +0 -128
  46. package/template/create-miden-app/template/.env.example +0 -5
  47. package/template/create-miden-app/template/CLAUDE.md +0 -116
  48. package/template/create-miden-app/template/README.md +0 -61
  49. package/template/create-miden-app/template/eslint.config.js +0 -23
  50. package/template/create-miden-app/template/index.html +0 -13
  51. package/template/create-miden-app/template/package.json +0 -34
  52. package/template/create-miden-app/template/public/vite.svg +0 -1
  53. package/template/create-miden-app/template/src/App.tsx +0 -10
  54. package/template/create-miden-app/template/src/assets/miden.svg +0 -3
  55. package/template/create-miden-app/template/src/assets/react.svg +0 -1
  56. package/template/create-miden-app/template/src/components/AppContent.css +0 -45
  57. package/template/create-miden-app/template/src/components/AppContent.tsx +0 -50
  58. package/template/create-miden-app/template/src/components/Counter.css +0 -27
  59. package/template/create-miden-app/template/src/config.ts +0 -21
  60. package/template/create-miden-app/template/src/hooks/useIncrementCounter.ts +0 -136
  61. package/template/create-miden-app/template/src/index.css +0 -75
  62. package/template/create-miden-app/template/src/lib/miden.ts +0 -9
  63. package/template/create-miden-app/template/src/main.tsx +0 -10
  64. package/template/create-miden-app/template/src/providers.tsx +0 -31
  65. package/template/create-miden-app/template/src/vite-env.d.ts +0 -1
  66. package/template/create-miden-app/template/tsconfig.app.json +0 -32
  67. package/template/create-miden-app/template/tsconfig.json +0 -7
  68. package/template/create-miden-app/template/tsconfig.node.json +0 -24
  69. package/template/create-miden-app/template/vite.config.ts +0 -17
  70. package/template/create-miden-app/template/yarn.lock +0 -1697
@@ -7,7 +7,7 @@ description: Complete guide to building Miden frontends with @miden-sdk/react ho
7
7
 
8
8
  ## SDK Choice
9
9
 
10
- ALWAYS use `@miden-sdk/react` hooks. Only fall back to raw `@miden-sdk/miden-sdk` WebClient via `useMidenClient()` for operations not covered by hooks. The React SDK handles WASM safety (runExclusive), state management (Zustand), auto-sync, and transaction stage tracking automatically.
10
+ ALWAYS use `@miden-sdk/react` hooks. Only fall back to the raw `WasmWebClient` (exported as `WebClient`) via `useMidenClient()` for operations not covered by hooks. The React SDK handles WASM safety (runExclusive), state management (Zustand), auto-sync, and transaction stage tracking automatically.
11
11
 
12
12
  ## MidenProvider Configuration
13
13
 
@@ -16,8 +16,8 @@ import { MidenProvider } from "@miden-sdk/react";
16
16
 
17
17
  <MidenProvider
18
18
  config={{
19
- rpcUrl: "devnet", // "devnet" | "testnet" | "localhost" | custom URL
20
- prover: "devnet", // "local" | "devnet" | "testnet" | custom URL
19
+ rpcUrl: "testnet", // "devnet" | "testnet" | "localhost" | custom URL
20
+ prover: "testnet", // "local" | "devnet" | "testnet" | custom URL
21
21
  autoSyncInterval: 15000, // ms, set to 0 to disable. Default: 15000
22
22
  noteTransportUrl: "...", // optional: for private note delivery
23
23
  }}
@@ -30,8 +30,8 @@ import { MidenProvider } from "@miden-sdk/react";
30
30
 
31
31
  | Network | rpcUrl | Use When |
32
32
  |---------|--------|----------|
33
- | Devnet | `"devnet"` | Development, testing with fake tokens |
34
- | Testnet | `"testnet"` | Pre-production testing |
33
+ | Testnet | `"testnet"` | Recommended for new projects - primary development network |
34
+ | Devnet | `"devnet"` | Early-access testing (may lag feature parity with testnet) |
35
35
  | Localhost | `"localhost"` | Local node at `http://localhost:57291` |
36
36
 
37
37
  ## Query Hooks
@@ -41,31 +41,51 @@ Each returns its own result shape plus `isLoading`, `error`, `refetch`.
41
41
  ### useAccounts()
42
42
  ```tsx
43
43
  const { accounts, wallets, faucets, isLoading, error, refetch } = useAccounts();
44
- // wallets AccountHeader[] (regular wallet accounts)
45
- // faucets AccountHeader[] (token faucet accounts)
46
- // accounts AccountHeader[] (everything)
44
+ // wallets - AccountHeader[] (regular wallet accounts)
45
+ // faucets - AccountHeader[] (token faucet accounts)
46
+ // accounts - AccountHeader[] (everything)
47
47
  ```
48
48
 
49
49
  ### useAccount(accountId: string)
50
50
  ```tsx
51
51
  const { account, assets, getBalance, isLoading, error, refetch } = useAccount(accountId);
52
- // account Account object (.id, .nonce, .bech32id())
53
- // assets AssetBalance[] (assetId, amount, symbol?, decimals?)
54
- // getBalance(faucetId) bigint balance for specific token
52
+ // account - Account object (.id, .nonce, .bech32id())
53
+ // assets - AssetBalance[] (assetId, amount, symbol?, decimals?)
54
+ // getBalance(faucetId) - bigint balance for specific token
55
55
  ```
56
56
 
57
57
  ### useNotes(filter?)
58
58
  ```tsx
59
59
  const { notes, consumableNotes, noteSummaries, consumableNoteSummaries, isLoading, error, refetch } = useNotes();
60
- // notes InputNoteRecord[]
61
- // consumableNotes ConsumableNoteRecord[]
62
- // noteSummaries NoteSummary[] (id, assets, sender)
63
- // consumableNoteSummaries NoteSummary[]
60
+ // notes - InputNoteRecord[]
61
+ // consumableNotes - ConsumableNoteRecord[]
62
+ // noteSummaries - NoteSummary[] (id, assets, sender)
63
+ // consumableNoteSummaries - NoteSummary[]
64
64
 
65
65
  // Filter by account:
66
66
  const { notes } = useNotes({ accountId: "0x..." });
67
67
  // Filter by status:
68
68
  const { notes } = useNotes({ status: "committed" });
69
+ // Filter by sender:
70
+ const { notes } = useNotes({ sender: "0x..." });
71
+ // Exclude specific notes:
72
+ const { notes } = useNotes({ excludeIds: ["0xnote1", "0xnote2"] });
73
+ ```
74
+
75
+ ### useNoteStream(options?)
76
+ ```tsx
77
+ const { notes, latest, markHandled, markAllHandled, snapshot, isLoading, error } = useNoteStream();
78
+ // notes - StreamedNote[] (matching filter criteria)
79
+ // latest - most recent StreamedNote (convenience)
80
+ // markHandled(noteId) - exclude a note from future renders
81
+ // markAllHandled() - exclude all current notes
82
+ // snapshot() - capture { ids, timestamp } for cross-phase filtering
83
+
84
+ // Options:
85
+ const { notes } = useNoteStream({ status: "committed", sender: "0x..." });
86
+ const { notes } = useNoteStream({ since: Date.now() - 60000 }); // last 60s
87
+ const { notes } = useNoteStream({ excludeIds: new Set(["0xnote1"]) });
88
+ const { notes } = useNoteStream({ amountFilter: (amount) => amount > 100n });
69
89
  ```
70
90
 
71
91
  ### useSyncState()
@@ -77,11 +97,11 @@ await sync(); // Manual sync
77
97
  ### useAssetMetadata(faucetId: string | string[])
78
98
  ```tsx
79
99
  const { assetMetadata } = useAssetMetadata(faucetId);
80
- // assetMetadata Map<string, AssetMetadata>
100
+ // assetMetadata - Map<string, AssetMetadata>
81
101
  // Each entry: { assetId, symbol?, decimals? }
82
102
  const meta = assetMetadata.get(faucetId);
83
- // meta.symbol "TEST"
84
- // meta.decimals 8
103
+ // meta.symbol - "TEST"
104
+ // meta.decimals - 8
85
105
  ```
86
106
 
87
107
  ### useTransactionHistory(options?)
@@ -117,6 +137,20 @@ const account = await createFaucet({
117
137
  });
118
138
  ```
119
139
 
140
+ ### useImportAccount()
141
+ ```tsx
142
+ const { importAccount, account, isImporting, error, reset } = useImportAccount();
143
+
144
+ // Import by account ID (network lookup):
145
+ const account = await importAccount({ type: "id", accountId: "0x..." });
146
+
147
+ // Import from file:
148
+ const account = await importAccount({ type: "file", file: accountFileOrBytes });
149
+
150
+ // Import from seed:
151
+ const account = await importAccount({ type: "seed", seed: seedBytes, mutable: true });
152
+ ```
153
+
120
154
  ### useSend()
121
155
  ```tsx
122
156
  const { send, result, isLoading, stage, error, reset } = useSend();
@@ -128,6 +162,8 @@ await send({
128
162
  noteType: "private", // "private" | "public". Default: "private"
129
163
  recallHeight: 100, // optional: sender can reclaim after this block
130
164
  timelockHeight: 50, // optional: recipient can consume after this block
165
+ sendAll: true, // optional: send entire balance (ignores amount)
166
+ attachment: [1n, 2n], // optional: arbitrary data attached to the note
131
167
  });
132
168
  ```
133
169
 
@@ -139,9 +175,10 @@ await sendMany({
139
175
  assetId: faucetId,
140
176
  recipients: [
141
177
  { to: recipient1, amount: 500n },
142
- { to: recipient2, amount: 300n },
178
+ { to: recipient2, amount: 300n, noteType: "public" }, // per-recipient override
179
+ { to: recipient3, amount: 200n, attachment: [1n, 2n, 3n] }, // per-recipient attachment
143
180
  ],
144
- noteType: "private",
181
+ noteType: "private", // default for all recipients
145
182
  });
146
183
  ```
147
184
 
@@ -161,7 +198,7 @@ await mint({
161
198
  const { consume, result, isLoading, stage, error, reset } = useConsume();
162
199
  await consume({
163
200
  accountId: myAccountId,
164
- noteIds: [noteId1, noteId2],
201
+ notes: [noteId1, noteId2], // accepts: hex string IDs, NoteId, InputNoteRecord, or Note
165
202
  });
166
203
  ```
167
204
 
@@ -179,7 +216,7 @@ await swap({
179
216
  });
180
217
  ```
181
218
 
182
- ### useTransaction() Escape Hatch
219
+ ### useTransaction() - Escape Hatch
183
220
  ```tsx
184
221
  const { execute, result, isLoading, stage, error, reset } = useTransaction();
185
222
 
@@ -196,7 +233,7 @@ await execute({
196
233
  ### useWaitForCommit()
197
234
  ```tsx
198
235
  const { waitForCommit } = useWaitForCommit();
199
- await waitForCommit(result.transactionId, {
236
+ await waitForCommit(result.txId, { // useSend returns { txId, note }; other hooks use { transactionId }
200
237
  timeoutMs: 10000, // Default: 10000
201
238
  intervalMs: 1000, // Default: 1000
202
239
  });
@@ -212,6 +249,25 @@ await waitForConsumableNotes({
212
249
  });
213
250
  ```
214
251
 
252
+ ### useSessionAccount(options)
253
+ ```tsx
254
+ const { initialize, sessionAccountId, isReady, step, error, reset } = useSessionAccount({
255
+ fund: async (sessionId) => {
256
+ // Called after session wallet is created - fund it here
257
+ await send({ from: mainWallet, to: sessionId, assetId: faucetId, amount: 100n });
258
+ },
259
+ assetId: faucetId, // optional: for note filtering
260
+ walletOptions: { // optional: session wallet creation options
261
+ storageMode: "private",
262
+ mutable: true,
263
+ authScheme: 0,
264
+ },
265
+ pollIntervalMs: 3000, // optional: funding detection interval. Default: 3000
266
+ });
267
+ // Steps: "idle" → "creating" → "funding" → "consuming" → "ready"
268
+ // Call initialize() to start the flow. isReady becomes true when fully funded.
269
+ ```
270
+
215
271
  ## Transaction Progress UI
216
272
 
217
273
  ```tsx
@@ -236,9 +292,9 @@ No signer provider needed. Keys are managed in the browser via IndexedDB.
236
292
 
237
293
  ### External Signers
238
294
  Wrap MidenProvider with a signer provider. Three pre-built options:
239
- - `ParaSignerProvider` from `@miden-sdk/para` EVM wallets
240
- - `TurnkeySignerProvider` from `@miden-sdk/miden-turnkey-react` passkey auth
241
- - `MidenFiSignerProvider` from `@miden-sdk/wallet-adapter-react` MidenFi wallet
295
+ - `ParaSignerProvider` from `@miden-sdk/para` - EVM wallets
296
+ - `TurnkeySignerProvider` from `@miden-sdk/miden-turnkey-react` - passkey auth
297
+ - `MidenFiSignerProvider` from `@miden-sdk/miden-wallet-adapter-react` - MidenFi wallet
242
298
 
243
299
  ```tsx
244
300
  // Example: Para signer wrapping MidenProvider
@@ -248,7 +304,7 @@ import { ParaSignerProvider } from "@miden-sdk/para";
248
304
  </ParaSignerProvider>
249
305
  ```
250
306
 
251
- ### useSigner() Unified Interface
307
+ ### useSigner() - Unified Interface
252
308
  ```tsx
253
309
  const { isConnected, connect, disconnect, name } = useSigner();
254
310
  ```
@@ -275,7 +331,7 @@ const client = useMidenClient(); // throws if not ready
275
331
  const { runExclusive } = useMiden();
276
332
 
277
333
  // For operations not covered by hooks:
278
- await runExclusive(async (client) => {
334
+ await runExclusive(async () => {
279
335
  const header = await client.getBlockHeaderByNumber(100);
280
336
  });
281
337
  ```
@@ -294,3 +350,213 @@ import type {
294
350
  SignerContextValue, SignCallback, SignerAccountConfig,
295
351
  } from "@miden-sdk/react";
296
352
  ```
353
+
354
+ ## Reading Account Storage
355
+
356
+ For the high-level vault summary on a tracked account, the existing query hook is enough:
357
+
358
+ ```tsx
359
+ const { account, assets, getBalance } = useAccount(accountId);
360
+ // account.id, account.nonce, account.bech32id()
361
+ // assets: AssetBalance[]; getBalance(faucetId): bigint
362
+ ```
363
+
364
+ For lower-level reads (custom contract storage slots, map items), use `Account.storage()`. The React SDK serializes WASM access via `runExclusive`:
365
+
366
+ ```tsx
367
+ const client = useMidenClient();
368
+ const { runExclusive } = useMiden();
369
+
370
+ await runExclusive(async () => {
371
+ const id = AccountId.fromBech32(addressBech32);
372
+ if (!(await client.getAccount(id))) {
373
+ await client.importAccountById(id);
374
+ }
375
+ await client.syncState();
376
+ const account = await client.getAccount(id);
377
+ if (!account) return;
378
+ // AccountStorage (miden_client_web.d.ts:639-660):
379
+ const value = account.storage().getItem("my_slot_name");
380
+ // For storage maps:
381
+ const mapValue = account.storage().getMapItem("my_map_slot", keyWord);
382
+ });
383
+ ```
384
+
385
+ `Account.storage()` returns an `AccountStorage`. Both `getItem(slot_name: string)` and `getMapItem(slot_name: string, key: Word)` return `Word | undefined`. Use slot-name strings (e.g. `COUNTER_SLOT_NAME` in `src/config.ts`), not numeric indices. See `src/hooks/useIncrementCounter.ts:73-83` for the live in-template `getMapItem` example.
386
+
387
+ `useMidenClient()` returns the raw `WasmWebClient`. Its direct methods include `getAccount(accountId)`, `getAccountStorage(accountId)`, `importAccountById(accountId)`, `syncState()`, and the transaction-request factories (`newSendTransactionRequest`, `newConsumeTransactionRequest`, `newMintTransactionRequest`, `newSwapTransactionRequest`). For compile-from-source, call `client.createCodeBuilder()` and use the returned `CodeBuilder`'s `compileNoteScript(program: string)` / `compileTxScript(tx_script: string)` (`miden_client_web.d.ts`:1058-1115, factory at :4237). The higher-level `MidenClient.accounts.getOrImport` resource API lives on the standalone `MidenClient` (see `web-client-usage`).
388
+
389
+ ## Account Import then Sync then Read Storage Flow
390
+
391
+ Hook-based pattern for the common "import a remote account, sync to current chain head, then read its state" workflow:
392
+
393
+ ```tsx
394
+ function ImportAndInspect({ accountIdHex }: { accountIdHex: string }) {
395
+ const { importAccount, isImporting } = useImportAccount();
396
+ const { sync, isSyncing, syncHeight } = useSyncState();
397
+ const { account, assets, getBalance, refetch } = useAccount(accountIdHex);
398
+
399
+ async function load() {
400
+ await importAccount({ type: "id", accountId: accountIdHex });
401
+ await sync();
402
+ await refetch();
403
+ }
404
+ // Render account.id, syncHeight, balances...
405
+ }
406
+ ```
407
+
408
+ `useImportAccount` accepts `{ type: "id" | "file" | "seed", ... }`. After `sync()` resolves, the `useAccount(accountIdHex)` view reflects the latest chain state. For the raw `WasmWebClient` methods used by these hooks under the hood (`client.getAccount`, `client.importAccountById`, `client.syncState`), see `src/hooks/useIncrementCounter.ts` for a worked example. For the higher-level `MidenClient.accounts.*` resource API on a standalone `MidenClient` (outside React), see the `web-client-usage` skill.
409
+
410
+ ## Custom Notes and .masp Package Loading
411
+
412
+ `.masp` package files (compiled MASM artifacts) are produced by `cargo miden build` in the `project-template/` workspace and copied into `frontend-template/public/packages/` during the contract handoff (see `CLAUDE.md` "Contract Artifact Handoff" section).
413
+
414
+ The example below builds a custom transaction that emits two output notes carrying fungible assets. Each note has multi-felt input storage that the note script (compiled from MASM into `.masp`) reads and asserts on at consume time. The transaction is signed and submitted by the connected wallet via `useMidenFiWallet().requestTransaction(...)`.
415
+
416
+ ```tsx
417
+ import { useMidenFiWallet } from "@miden-sdk/miden-wallet-adapter-react";
418
+ import { Transaction } from "@miden-sdk/miden-wallet-adapter-base";
419
+ import {
420
+ Package,
421
+ NoteScript,
422
+ Note,
423
+ NoteAssets,
424
+ NoteMetadata,
425
+ NoteRecipient,
426
+ NoteStorage,
427
+ NoteTag,
428
+ NoteType,
429
+ NoteAttachment,
430
+ NoteExecutionHint,
431
+ NoteArray,
432
+ AccountId,
433
+ Felt,
434
+ FeltArray,
435
+ FungibleAsset,
436
+ TransactionRequestBuilder,
437
+ } from "@miden-sdk/miden-sdk";
438
+ import { randomWord } from "@/lib/miden";
439
+
440
+ const { requestTransaction } = useMidenFiWallet();
441
+
442
+ async function submitMultiNoteTx(
443
+ senderBech32: string,
444
+ targetBech32: string,
445
+ faucetBech32: string,
446
+ ) {
447
+ // (a) .masp loading: fetch the pre-built artifact and decode the note script.
448
+ const buf = await fetch("/packages/my_note.masp").then((r) => r.arrayBuffer());
449
+ const pkg = Package.deserialize(new Uint8Array(buf));
450
+ const noteScript = NoteScript.fromPackage(pkg);
451
+
452
+ const sender = AccountId.fromBech32(senderBech32);
453
+ const target = AccountId.fromBech32(targetBech32);
454
+ const faucet = AccountId.fromBech32(faucetBech32);
455
+
456
+ // (b) Multi-input note storage: each output note carries multiple Felt
457
+ // inputs that the MASM script reads from its NoteStorage. Assertions on
458
+ // these felts (e.g. "the first felt must equal the expected nonce") live
459
+ // in the .masp script source under project-template/contracts/.
460
+ function makeRecipient(seedFelts: bigint[]): NoteRecipient {
461
+ const inputs = new FeltArray();
462
+ for (const v of seedFelts) inputs.push(new Felt(v));
463
+ return new NoteRecipient(randomWord(), noteScript, new NoteStorage(inputs));
464
+ }
465
+
466
+ // (c) Asset transfers: each note carries fungible assets that move to the
467
+ // recipient when the note is consumed. FungibleAsset is declared at
468
+ // miden_client_web.d.ts:1474 (constructor `(faucet_id, amount: bigint)` at :1492).
469
+ const assets1 = new NoteAssets([new FungibleAsset(faucet, 1000n)]);
470
+ const assets2 = new NoteAssets([new FungibleAsset(faucet, 500n)]);
471
+
472
+ const tag = NoteTag.withAccountTarget(target);
473
+ const attachment = NoteAttachment.newNetworkAccountTarget(
474
+ target,
475
+ NoteExecutionHint.always(),
476
+ );
477
+ const metadata = new NoteMetadata(sender, NoteType.Public, tag).withAttachment(
478
+ attachment,
479
+ );
480
+
481
+ // Two output notes with different felt inputs and asset amounts.
482
+ const note1 = new Note(assets1, metadata, makeRecipient([1n, 2n, 3n]));
483
+ const note2 = new Note(assets2, metadata, makeRecipient([4n, 5n, 6n]));
484
+
485
+ // (d) Multi-output transaction: emit both notes in one transaction.
486
+ // For transactions that consume multiple input notes simultaneously,
487
+ // TransactionRequestBuilder.withInputNotes(NoteAndArgsArray) is the
488
+ // counterpart (miden_client_web.d.ts:3963).
489
+ const txRequest = new TransactionRequestBuilder()
490
+ .withOwnOutputNotes(new NoteArray([note1, note2]))
491
+ .build();
492
+
493
+ // (f) Submission via the wallet adapter.
494
+ const tx = Transaction.createCustomTransaction(senderBech32, targetBech32, txRequest);
495
+ if (!requestTransaction) throw new Error("Wallet does not support requestTransaction");
496
+ await requestTransaction(tx);
497
+ }
498
+ ```
499
+
500
+ Notes on this pattern:
501
+
502
+ - **Assertions live in MASM, not in TypeScript.** The note script compiled into `.masp` reads its `NoteStorage` felts at consume time and aborts the transaction if its assertions fail. The frontend's job is to construct and submit; MASM enforces. See `project-template/contracts/` for where to author MASM with assertion ops.
503
+ - **Single-output reference for simpler flows.** For a simpler worked example using only one output note with empty assets and a single consume action, see `src/hooks/useIncrementCounter.ts`. That hook is intentionally simpler and does not exercise asset transfers or multi-output emission.
504
+ - **Compile from source (no `.masp`) when needed.** When the note script is not pre-bundled as `.masp`, compile it through `CodeBuilder`. **`compileNoteScript`/`compileTxScript` are methods of `CodeBuilder`, not of `WasmWebClient`.** The pattern is:
505
+
506
+ ```tsx
507
+ const client = useMidenClient();
508
+ const builder = client.createCodeBuilder();
509
+ // optionally: builder.linkStaticLibrary(myLib) or builder.linkDynamicLibrary(myLib)
510
+ const noteScript = builder.compileNoteScript(noteSourceMasm);
511
+ const txScript = builder.compileTxScript(txSourceMasm);
512
+ ```
513
+
514
+ See `miden_client_web.d.ts`:1058-1115 for `CodeBuilder` and :4237 for `createCodeBuilder()`. As the React-idiomatic alternative, `useCompile()` (`@miden-sdk/react/dist/index.d.ts`:1171-1196) wraps `CompilerResource` from the standalone `MidenClient` and exposes `noteScript`, `txScript`, `component`, `isReady` at the hook layer.
515
+ - **Inside `useTransaction`'s `request` callback** the parameter is a `WasmWebClient` (`@miden-sdk/react/dist/index.d.ts`:393). Use `client.createCodeBuilder()` for compile, then build the `TransactionRequest` with `TransactionRequestBuilder` and return it. For the higher-level `MidenClient.compile.*` and `MidenClient.transactions.execute` resource API on a standalone `MidenClient`, see `web-client-usage`.
516
+
517
+ ## Cross-SDK Type Reference
518
+
519
+ The runtime types come from `@miden-sdk/react` (re-exported from `@miden-sdk/miden-sdk` for the underlying `MidenClient` types). The `.d.ts` files are the source of truth. Look them up in:
520
+
521
+ - the installed `.d.ts` for `@miden-sdk/react` (hook return types and option types)
522
+ - the installed `.d.ts` for `@miden-sdk/miden-sdk` (`MidenClient`, `Account`, `AccountId`, `Note`, `Word`, etc.)
523
+
524
+ Path layout differs across package managers (npm flat, pnpm nested, Yarn PnP virtual), so resolve them via your IDE's "Go to Definition" or the installed package surface rather than hard-coded paths.
525
+
526
+ Common app-developer types:
527
+
528
+ | Type | Source package | Notes |
529
+ |------|----------------|-------|
530
+ | `Account`, `AccountHeader` | `@miden-sdk/react` | `id`, `nonce`, `bech32id()` |
531
+ | `AccountId` | `@miden-sdk/miden-sdk` | construct via `AccountId.fromHex(hex)`; throws on invalid hex |
532
+ | `Address` | `@miden-sdk/miden-sdk` | bech32 wrapper; `Address.fromBech32(...)` |
533
+ | `Note`, `InputNoteRecord`, `ConsumableNoteRecord` | `@miden-sdk/react` | re-exported from `@miden-sdk/miden-sdk`. Input notes are received; for output-note types and private-note flows see `web-client-usage`. |
534
+ | `NoteVisibility` (constants + string-union) | `@miden-sdk/miden-sdk` | `const NoteVisibility = { Public: 'public', Private: 'private' }` plus `type NoteVisibility = 'public' \| 'private'` (`api-types.d.ts`:95-101). NOT an enum. Coexists with the raw WASM `NoteType` enum (`miden_client_web.d.ts`:2889) which the template uses directly when building notes via the WASM types (see `src/hooks/useIncrementCounter.ts`). |
535
+ | `AccountType`, `AuthScheme`, `StorageMode` | `@miden-sdk/miden-sdk` | enums; see `web-client-usage` "Visibility & Account Types". |
536
+ | `TransactionRequest` | `@miden-sdk/react` | client factory functions return this |
537
+ | `Word` | `@miden-sdk/miden-sdk` | 32-byte (4 felts) value; `Word.toU64s()` returns `BigUint64Array` of length 4 (each lane is a `bigint` after subscript). See `miden_client_web.d.ts`:4535. |
538
+
539
+ Do not hardcode this table for long-term reference. The `.d.ts` files stay in lockstep with the installed package version; this list will drift.
540
+
541
+ ## Rust to TypeScript Type Mapping
542
+
543
+ The Rust (`miden-client`) and TypeScript (`@miden-sdk/miden-sdk`) SDKs share concepts but diverge on naming and primitive shapes. When porting between them:
544
+
545
+ | Concept | Rust (`miden_objects` / `miden_client`) | TypeScript (`@miden-sdk/miden-sdk`) |
546
+ |---------|------------------------------------------|--------------------------------------|
547
+ | Token amount | `u64` | `bigint` |
548
+ | Field element | `Felt` (Goldilocks `u64` mod p) | `Felt` (wraps `u64`) |
549
+ | 32-byte word | `Word` (`[Felt; 4]`) | `Word`; `toU64s(): BigUint64Array` length 4 (each lane is `bigint` after subscript). |
550
+ | Account identifier | `AccountId` | `AccountId`; construct via `AccountId.fromHex` |
551
+ | Note visibility | `NoteType` enum | constants + string-union `NoteVisibility` (`'public' \| 'private'`) at the high-level `MidenClient` resource API; raw WASM `NoteType` enum (`Private = 2`, `Public = 1`) is also exported and used directly when constructing notes via the WASM types (see `src/hooks/useIncrementCounter.ts`). The two coexist; pick the layer your code lives in. |
552
+ | Account type | `AccountType` | `AccountType` enum |
553
+ | Authentication scheme | `AuthScheme` | `AuthScheme` enum |
554
+ | Storage mode | `StorageMode` | `StorageMode` enum |
555
+
556
+ Common gotchas:
557
+
558
+ - The TS method is `FeltArray.push(element: Felt)` (`miden_client_web.d.ts`:1324); there is no `FeltArray.append`. To convert a `Felt` to a JS `bigint`, use `Felt.asInt()` (`miden_client_web.d.ts`:1296). When a Rust method appears missing in TS, consult `node_modules/@miden-sdk/miden-sdk/dist/index.d.ts` and `dist/crates/miden_client_web.d.ts` first instead of guessing the TS spelling.
559
+ - TS amounts are always `bigint`. Mixing `number` causes silent precision loss above `Number.MAX_SAFE_INTEGER` and `TypeError` below.
560
+ - `Word.toU64s()` returns `BigUint64Array` of length 4 (`miden_client_web.d.ts`:4535). Each lane is a `bigint` (e.g. `word.toU64s()[0]`). Use it when reading the four `u64` lanes from a Value storage slot or building assertions on `Word` outputs.
561
+
562
+ For the canonical Rust types, see [`0xMiden/miden-client`](https://github.com/0xMiden/miden-client) and the `miden_objects` crate. For the canonical TS types, see `node_modules/@miden-sdk/miden-sdk/dist/index.d.ts`.
@@ -68,15 +68,34 @@ const { client, account, setAccount } = useTurnkeySigner();
68
68
 
69
69
  ### MidenFi Wallet Adapter (Browser Extension)
70
70
  ```tsx
71
- import { MidenFiSignerProvider } from "@miden-sdk/wallet-adapter-react";
72
-
73
- <MidenFiSignerProvider network="Testnet">
71
+ import { MidenFiSignerProvider } from "@miden-sdk/miden-wallet-adapter-react";
72
+ import { WalletAdapterNetwork } from "@miden-sdk/miden-wallet-adapter-base";
73
+
74
+ <MidenFiSignerProvider
75
+ appName="My App" // optional: passed to MidenWalletAdapter
76
+ network={WalletAdapterNetwork.Testnet} // WalletAdapterNetwork enum: Devnet | Testnet | Localnet
77
+ autoConnect // reconnect on mount. Default: false
78
+ accountType="RegularAccountImmutableCode" // Default: "RegularAccountImmutableCode"
79
+ storageMode="public" // "private" | "public" | "network". Default: "public"
80
+ customComponents={[myComponent]} // optional: custom AccountComponents
81
+ privateDataPermission={permission} // optional: private data access level
82
+ allowedPrivateData={allowedData} // optional: allowed private data types
83
+ >
74
84
  <MidenProvider config={{ rpcUrl: "testnet" }}>
75
85
  <App />
76
86
  </MidenProvider>
77
87
  </MidenFiSignerProvider>
78
88
  ```
79
89
 
90
+ With `MidenFiSignerProvider` in place, use `useSigner()` from the React SDK to manage connection state. The regular React SDK hooks (`useSend`, `useConsume`, etc.) automatically sign via the connected wallet — no additional wiring needed.
91
+
92
+ ### This template's MidenFi-specific pattern
93
+
94
+ This template deviates from the generic `useSigner()` approach in two places — worth knowing because it's a pattern you'll likely want when the wallet extension is the primary signer:
95
+
96
+ - **Wallet button uses `useMidenFiWallet()` + `WalletReadyState`** (`src/components/AppContent.tsx`). The button gates on `wallet.readyState` so it can render a disabled "Install MidenFi Wallet" state before the extension is detected. `useSigner().connect()` would silently fall through to the adapter's `window.open(adapter.url, ...)` install fallback (Chrome Web Store → Play Store redirect on some platforms); gating on `readyState` avoids that path entirely.
97
+ - **Custom transaction flow calls `wallet.requestTransaction(...)` directly** (`src/hooks/useIncrementCounter.ts`). The counter increment builds a bespoke `TransactionRequest` (via `TransactionRequestBuilder`, a custom `Note` with `NoteAttachment.newNetworkAccountTarget`, etc.) and hands it to the wallet for signing + submission. The React SDK mutation hooks (`useSend`, `useConsume`, ...) don't cover this kind of custom note construction, and the tx is submitted by the wallet rather than the local client — so `useWaitForCommit` doesn't apply either.
98
+
80
99
  ## Unified Signer Interface
81
100
 
82
101
  Works with any signer provider above: