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
@@ -0,0 +1,454 @@
1
+ ---
2
+ name: web-client-usage
3
+ description: Conventions for writing JavaScript/TypeScript code that uses the Miden web SDK (`@miden-sdk/miden-sdk`). Use when building apps on Miden, writing integration tests, or calling MidenClient methods - covers initialization, the resource-based API (accounts, transactions, notes, keystore, compile), sync ordering, type conversions, transaction flows, custom contracts, private note transport, and pitfalls.
4
+ ---
5
+
6
+ NOTE: In the frontend-template context, prefer @miden-sdk/react hooks for all
7
+ operations they cover. Use the raw WebClient patterns documented here ONLY for
8
+ operations not available through React hooks (e.g., custom TransactionRequests,
9
+ direct store import/export, advanced sync control).
10
+
11
+ # Web SDK Usage Patterns
12
+
13
+ This skill targets the `@miden-sdk/miden-sdk` npm package shipped from
14
+ [`0xMiden/miden-client`](https://github.com/0xMiden/miden-client). For
15
+ React-hook usage, prefer the `react-sdk-patterns` skill - only fall through
16
+ to the raw client when a hook does not cover what you need.
17
+
18
+ ## API Overview
19
+
20
+ The SDK exposes a top-level `MidenClient` whose state is split across typed
21
+ **resources**:
22
+
23
+ | Resource | What it covers |
24
+ |----------|----------------|
25
+ | `client.accounts` | Wallets, faucets, custom contracts, listing, import/export |
26
+ | `client.transactions` | `send` / `mint` / `consume` / `consumeAll` / `swap` / `execute` / `preview` / `waitFor` |
27
+ | `client.notes` | Listing, fetching, importing/exporting, private-note transport |
28
+ | `client.tags` | Note-tag subscriptions |
29
+ | `client.settings` | Persistent client settings |
30
+ | `client.compile` | Compiling MASM into account components, tx scripts, note scripts |
31
+ | `client.keystore` | Inserting / fetching / removing secret keys |
32
+
33
+ `MidenClient` is the public surface. The underlying WASM-bound class is still
34
+ exported as `WasmWebClient` (alias for the legacy `WebClient`) for low-level
35
+ operations the resource API does not yet wrap. To reach it, either use the
36
+ React `useMidenClient()` hook or import `WasmWebClient` directly from
37
+ `@miden-sdk/miden-sdk` (the class is `@internal` but exported). `MidenClient`
38
+ keeps its wrapped client in a real JS private field (`#inner`), so external
39
+ code cannot reach in directly.
40
+
41
+ ## Client Initialization
42
+
43
+ ### Convenience constructors (recommended)
44
+
45
+ ```typescript
46
+ import { MidenClient } from "@miden-sdk/miden-sdk";
47
+
48
+ // Testnet - autoSync on, testnet RPC + prover + note transport
49
+ const client = await MidenClient.createTestnet();
50
+
51
+ // Devnet equivalent
52
+ const client = await MidenClient.createDevnet();
53
+ ```
54
+
55
+ Both accept the same `ClientOptions` for overrides:
56
+
57
+ ```typescript
58
+ const client = await MidenClient.createTestnet({
59
+ storeName: "my-app-tests", // isolates the IndexedDB store
60
+ proverUrl: "local", // prove locally instead of remote
61
+ autoSync: false, // disable initial sync
62
+ });
63
+ ```
64
+
65
+ ### Generic constructor
66
+
67
+ ```typescript
68
+ const client = await MidenClient.create({
69
+ rpcUrl: "https://rpc.testnet.miden.io", // string URL or "testnet"/"devnet"/"localhost"
70
+ noteTransportUrl: "https://ntx.testnet.miden.io",
71
+ storeName: "my-store",
72
+ seed: new Uint8Array(32), // optional - deterministic key generation
73
+ proverUrl: "testnet", // optional - sets a default prover
74
+ autoSync: true, // optional - call sync() after init
75
+ keystore: { // optional - external HSM/keystore
76
+ getKey: async (pubKey) => { /* return secretKey or null */ },
77
+ insertKey: async (pubKey, secretKey) => { /* persist */ },
78
+ sign: async (pubKey, signingInputs) => { /* return signature */ },
79
+ },
80
+ });
81
+ ```
82
+
83
+ If `rpcUrl` is omitted, `create()` delegates to `createTestnet()`.
84
+
85
+ ### Lazy / SSR-safe init
86
+
87
+ Some bundles (Next.js, Capacitor, raw `/lazy` entry) cannot await WASM at
88
+ import time. Use `MidenClient.ready()` to wait for WASM in-band - it is
89
+ idempotent and shared across callers:
90
+
91
+ ```typescript
92
+ await MidenClient.ready();
93
+ const client = await MidenClient.createTestnet();
94
+ ```
95
+
96
+ ### Termination
97
+
98
+ ```typescript
99
+ client.terminate(); // free WASM resources, close the store handle
100
+ ```
101
+
102
+ After `terminate()`, every method throws - guard against late callbacks on
103
+ unmount.
104
+
105
+ ## Sync - Always Sync First
106
+
107
+ The client's view of the chain is only as fresh as its last sync. **Always
108
+ call `sync()` before reading account state or building a transaction that
109
+ depends on freshly received notes.**
110
+
111
+ ```typescript
112
+ const summary = await client.sync(); // returns SyncSummary
113
+ const height = await client.getSyncHeight(); // current local block number
114
+ ```
115
+
116
+ Common patterns:
117
+
118
+ - Sync before consuming notes (notes must be committed on-chain)
119
+ - Sync after submitting a transaction to observe the result
120
+ - Pass `waitForConfirmation: true` to a `transactions.send/mint/consume/swap`
121
+ call to let the SDK wait for the tx commit instead of polling manually
122
+ - Use `client.waitForIdle()` to flush all queued WASM calls before doing a
123
+ side-effect that must not race with a kernel callback (e.g. clearing an
124
+ in-memory unlock token after a wallet "lock")
125
+
126
+ `autoSync: true` (default for `createTestnet`/`createDevnet`) only triggers a
127
+ single sync at construction time - it is not a polling loop. Use the React
128
+ SDK's `useSyncState` or `MidenProvider` `autoSyncInterval` for periodic sync.
129
+
130
+ ## Type Conversions
131
+
132
+ Type confusion across the WASM boundary is the leading source of bugs.
133
+
134
+ ### `AccountId`
135
+
136
+ ```typescript
137
+ const id = AccountId.fromHex("0xabc123..."); // throws on invalid hex
138
+ const id = Address.fromBech32("mtst1abc...").accountId();
139
+ const hex = id.toString(); // "0x..."
140
+ ```
141
+
142
+ Pass `AccountId` (or any account ref the resource accepts: `string` hex,
143
+ `Address`, `Account`, `AccountHeader`) to resource methods - never raw
144
+ strings to methods that ask for `AccountId` directly.
145
+
146
+ `AccountId.fromHex` throws on malformed input; wrap in `try/catch` when
147
+ accepting user input.
148
+
149
+ ### Amounts - Always `BigInt`
150
+
151
+ ```typescript
152
+ BigInt(1000)
153
+ 1000n // numeric literal
154
+ BigInt("1000")
155
+ ```
156
+
157
+ All token amounts in the SDK are `bigint`. Mixing `number` causes silent
158
+ precision loss above `Number.MAX_SAFE_INTEGER` and `TypeError` below.
159
+
160
+ ### Visibility & Account Types
161
+
162
+ ```typescript
163
+ import { NoteVisibility, AccountType, AuthScheme, StorageMode } from "@miden-sdk/miden-sdk";
164
+
165
+ NoteVisibility.Public // "public"
166
+ NoteVisibility.Private // "private"
167
+
168
+ AccountType.MutableWallet
169
+ AccountType.ImmutableWallet
170
+ AccountType.FungibleFaucet
171
+ AccountType.NonFungibleFaucet
172
+ AccountType.MutableContract
173
+ AccountType.ImmutableContract
174
+
175
+ AuthScheme.Falcon // default - Falcon-512 over Poseidon2
176
+ AuthScheme.ECDSA // EcdsaK256Keccak
177
+
178
+ StorageMode.Public
179
+ StorageMode.Private
180
+ StorageMode.Network
181
+ ```
182
+
183
+ Use `NoteVisibility` (not the legacy `NoteType` enum) and `AuthScheme.Falcon`
184
+ for the Poseidon2-based Falcon-512 scheme.
185
+
186
+ ## Account Creation
187
+
188
+ Type discriminator on `auth`: wallets and faucets take `auth: AuthSchemeType` (a `"falcon" | "ecdsa"` string-union, e.g. `AuthScheme.Falcon`); custom contracts take `auth: AuthSecretKey` (a concrete WASM instance).
189
+
190
+ ```typescript
191
+ // Wallet - defaults: mutable, private, Falcon
192
+ const wallet = await client.accounts.create();
193
+
194
+ // Wallet with explicit options
195
+ const wallet = await client.accounts.create({
196
+ type: AccountType.MutableWallet,
197
+ storage: "private",
198
+ auth: AuthScheme.Falcon,
199
+ });
200
+
201
+ // Faucet
202
+ const faucet = await client.accounts.create({
203
+ type: AccountType.FungibleFaucet,
204
+ storage: "public",
205
+ symbol: "DAG",
206
+ decimals: 8,
207
+ maxSupply: 10_000_000n,
208
+ });
209
+
210
+ // Custom contract - requires seed and AuthSecretKey
211
+ const component = await client.compile.component({ code: contractMasm, slots: [] });
212
+ const contract = await client.accounts.create({
213
+ type: AccountType.MutableContract,
214
+ seed: new Uint8Array(32),
215
+ auth: secretKey, // AuthSecretKey, not the AuthScheme enum
216
+ components: [component],
217
+ });
218
+ ```
219
+
220
+ ## Transactions
221
+
222
+ The transactions API is option-bag-based and accepts any account ref
223
+ (`Account`, `AccountHeader`, hex string, `AccountId`).
224
+
225
+ ### Send
226
+
227
+ ```typescript
228
+ const { txId } = await client.transactions.send({
229
+ account: wallet, // sender
230
+ to: "0xrecipient...", // any account ref
231
+ token: faucet, // faucet account ref - identifies the asset
232
+ amount: 100n,
233
+ type: NoteVisibility.Public, // optional, defaults to "public" (raw SDK; resolveNoteType in dist/index.js:307-316). React useSend defaults to "private".
234
+ reclaimAfter: 100, // optional - sender can reclaim after this block
235
+ timelockUntil: 50, // optional - recipient can consume after this block
236
+ waitForConfirmation: true,
237
+ timeout: 30_000,
238
+ });
239
+ ```
240
+
241
+ For private sends where you also need to deliver the note out-of-band, set
242
+ `returnNote: true` and the call returns the constructed `Note` object -
243
+ incompatible with `reclaimAfter`/`timelockUntil`.
244
+
245
+ ```typescript
246
+ const { txId, note } = await client.transactions.send({
247
+ account: wallet,
248
+ to: recipientAddress,
249
+ token: faucet,
250
+ amount: 100n,
251
+ type: NoteVisibility.Private,
252
+ returnNote: true,
253
+ });
254
+
255
+ // Stream the note via the note-transport service
256
+ await client.notes.sendPrivate({ note, to: Address.fromBech32("mtst1...") });
257
+ ```
258
+
259
+ ### Mint
260
+
261
+ ```typescript
262
+ const { txId } = await client.transactions.mint({
263
+ account: faucet, // faucet executes the mint
264
+ to: targetAccountId, // recipient
265
+ amount: 1000n,
266
+ type: NoteVisibility.Public,
267
+ waitForConfirmation: true,
268
+ });
269
+ ```
270
+
271
+ The transaction executes on the **faucet** - a frequent bug is passing the
272
+ recipient as `account`.
273
+
274
+ ### Consume
275
+
276
+ ```typescript
277
+ // Specific notes
278
+ await client.transactions.consume({
279
+ account: wallet,
280
+ notes: [noteId1, noteRecord, "0xnote..."], // any of: hex, NoteId, InputNoteRecord, Note
281
+ waitForConfirmation: true,
282
+ });
283
+
284
+ // Drain everything consumable for the account
285
+ const { txId, consumed, remaining } = await client.transactions.consumeAll({
286
+ account: wallet,
287
+ maxNotes: 50, // optional cap
288
+ });
289
+ ```
290
+
291
+ ### Swap
292
+
293
+ ```typescript
294
+ await client.transactions.swap({
295
+ account: wallet,
296
+ offered: { token: tokenA, amount: 100n },
297
+ requested: { token: tokenB, amount: 50n },
298
+ type: NoteVisibility.Public, // swap-note visibility
299
+ paybackType: NoteVisibility.Private, // payback-note visibility
300
+ });
301
+ ```
302
+
303
+ ### Execute (custom scripts)
304
+
305
+ ```typescript
306
+ const script = await client.compile.txScript({
307
+ code: scriptMasm,
308
+ libraries: [{ namespace: "my::lib", code: libMasm, linking: "dynamic" }],
309
+ });
310
+
311
+ await client.transactions.execute({
312
+ account: contract,
313
+ script,
314
+ foreignAccounts: [
315
+ publicAccountId, // public - auto-fetched via RPC
316
+ { id: privateContractId, storage: storageRequirements },
317
+ ],
318
+ waitForConfirmation: true,
319
+ });
320
+ ```
321
+
322
+ **Public foreign accounts are auto-fetched** during execution - only private
323
+ foreign accounts must be supplied with their storage requirements.
324
+
325
+ ### Preview (dry run)
326
+
327
+ `transactions.preview({ operation: "send" | "mint" | "consume" | "swap" | "custom", ... })`
328
+ runs the same kernel as the real call but without proving or submitting,
329
+ returning a summary suitable for UI confirmation screens.
330
+
331
+ ## Notes
332
+
333
+ ```typescript
334
+ await client.notes.list(); // all input notes
335
+ await client.notes.list({ status: "committed" }); // filter
336
+ await client.notes.get(noteId); // single record
337
+ await client.notes.listSent(); // output notes
338
+ await client.notes.listAvailable({ account: wallet });// consumable for an account
339
+
340
+ // Import/export
341
+ await client.notes.import(noteFile);
342
+ const file = await client.notes.export(noteId);
343
+
344
+ // Private-note transport
345
+ await client.notes.fetchPrivate(); // pulls anything addressed to tracked accounts
346
+ await client.notes.sendPrivate({ note, to: addr }); // delivers via the transport service
347
+ ```
348
+
349
+ ## Accounts (querying)
350
+
351
+ ```typescript
352
+ await client.accounts.list(); // tracked accounts
353
+ await client.accounts.get(ref); // single (returns null if not tracked)
354
+ await client.accounts.getOrImport(ref); // tries get(), falls back to import()
355
+ await client.accounts.getDetails(ref); // header + status + vault summary
356
+ await client.accounts.insert({ account, overwrite }); // start tracking an existing account
357
+ ```
358
+
359
+ For balance reads, use `getDetails` or - if you need a single asset balance
360
+ without loading the full vault - drop into the underlying WASM client's
361
+ `accountReader(id)` lazy reader.
362
+
363
+ ## Keystore
364
+
365
+ ```typescript
366
+ await client.keystore.insert(accountId, secretKey);
367
+ await client.keystore.get(pubKeyCommitment);
368
+ await client.keystore.remove(pubKeyCommitment);
369
+ await client.keystore.getCommitments(accountId);
370
+ await client.keystore.getAccountId(pubKeyCommitment);
371
+ ```
372
+
373
+ `keystore.insert` is the single call that both stores the key and registers
374
+ its commitment with the account.
375
+
376
+ ## Compile
377
+
378
+ ```typescript
379
+ await client.compile.component({ code, slots, supportAllTypes: true }); // supportAllTypes defaults to true (api-types.d.ts:784); set false if your component supplies its own auth-tx kernel invocation.
380
+ await client.compile.txScript({ code, libraries });
381
+ await client.compile.noteScript({ code, libraries });
382
+ ```
383
+
384
+ Note scripts are **MASM libraries with a single `@note_script`-annotated
385
+ procedure**, not begin/end programs - `client.compile.noteScript` builds the
386
+ correct shape from a procedure body. The same applies to `@auth_script` for
387
+ authentication scripts.
388
+
389
+ ## Common Workflows
390
+
391
+ ### Mint and consume (fund a fresh wallet)
392
+
393
+ ```typescript
394
+ const wallet = await client.accounts.create();
395
+ const faucet = await client.accounts.create({
396
+ type: AccountType.FungibleFaucet,
397
+ storage: "public",
398
+ symbol: "TEST",
399
+ decimals: 8,
400
+ maxSupply: 1_000_000n,
401
+ });
402
+
403
+ await client.transactions.mint({
404
+ account: faucet,
405
+ to: wallet,
406
+ amount: 10_000n,
407
+ type: NoteVisibility.Public,
408
+ waitForConfirmation: true,
409
+ });
410
+
411
+ await client.sync();
412
+ await client.transactions.consumeAll({
413
+ account: wallet,
414
+ waitForConfirmation: true,
415
+ });
416
+ ```
417
+
418
+ ### Wait for an external transfer
419
+
420
+ ```typescript
421
+ await client.sync();
422
+ const before = (await client.notes.listAvailable({ account: wallet })).length;
423
+
424
+ while (true) {
425
+ await new Promise(r => setTimeout(r, 3000));
426
+ await client.sync();
427
+ const now = (await client.notes.listAvailable({ account: wallet })).length;
428
+ if (now > before) break;
429
+ }
430
+ ```
431
+
432
+ ## Common Pitfalls
433
+
434
+ 1. **Forgetting to sync.** Notes won't appear, balances will be stale, foreign
435
+ accounts will be at the wrong block.
436
+ 2. **`number` instead of `BigInt`** for amounts - silent precision loss.
437
+ 3. **Passing strings where the SDK expects an account ref** - pre-parse with
438
+ `AccountId.fromHex()` (and catch its throw).
439
+ 4. **Consuming notes before they're committed** - sync first, check status.
440
+ 5. **Submitting `mint` with the recipient as `account`** - mint executes on
441
+ the faucet account, not the target.
442
+ 6. **Private notes without transport** - must call `notes.sendPrivate()` (or
443
+ pass `returnNote: true` to `transactions.send` and deliver out-of-band).
444
+ 7. **Holding WASM-owned objects across `terminate()`** - every `Account`,
445
+ `Note`, `AccountId`, `NoteAndArgsArray` etc. owns Rust memory through the
446
+ WASM ArrayBuffer. After `terminate()` they panic with "null pointer
447
+ passed to rust" - drop references on unmount.
448
+ 8. **Calling `accountReader(...)` in parallel with a write** (the method lives
449
+ on the raw `WasmWebClient`, accessed via React's `useMidenClient()` or a
450
+ direct `WasmWebClient` import; not on `MidenClient`) - the readers share
451
+ the WASM client. Wrap concurrent flows with `client.waitForIdle()` or rely
452
+ on the React SDK's `runExclusive`. For multiple browser clients or tabs,
453
+ give each isolated client a distinct `storeName`; see the initialization
454
+ examples above and `signer-integration` for per-user store isolation.
@@ -1,5 +1,18 @@
1
- # Miden RPC endpoint (default: "testnet")
1
+ # Miden SDK network configuration
2
+ # Supported values: devnet | testnet | local | https://your-rpc-url
2
3
  VITE_MIDEN_RPC_URL=testnet
3
4
 
4
- # Miden prover (default: "testnet", alternative: "local")
5
+ # Prover configuration
6
+ # Supported values: devnet | testnet | local | https://your-prover-url
5
7
  VITE_MIDEN_PROVER=testnet
8
+
9
+ # Counter contract address (bech32, e.g. mtst1...).
10
+ #
11
+ # Resolution rules:
12
+ # - Leave this line commented out / unset → app uses the live testnet
13
+ # default counter shipped with the template (recommended for first-run).
14
+ # - Set to "" (empty string) → unconfigured; <Counter> shows the
15
+ # "address not configured" card (useful when you've deployed nothing
16
+ # yet and don't want network calls).
17
+ # - Set to your own bech32 address → app reads from that counter.
18
+ # VITE_MIDEN_COUNTER_ADDRESS=
@@ -0,0 +1,9 @@
1
+ {
2
+ "mcpServers": {
3
+ "playwright": {
4
+ "type": "stdio",
5
+ "command": "npx",
6
+ "args": ["-y", "@playwright/mcp@latest"]
7
+ }
8
+ }
9
+ }
@@ -30,20 +30,32 @@ Type checking alone:
30
30
  npx tsc -b --noEmit
31
31
  ```
32
32
 
33
- ## SDK Choice: React SDK over Raw WebClient
33
+ ## SDK Choice: React SDK Hooks First
34
34
 
35
- ALWAYS prefer `@miden-sdk/react` hooks over raw `@miden-sdk/miden-sdk` WebClient methods.
36
- Only use WebClient directly via `useMidenClient()` for operations not covered by hooks.
35
+ ALWAYS prefer `@miden-sdk/react` hooks over low-level `WasmWebClient` methods.
36
+ Only use the WASM client directly via `useMidenClient()` for operations not covered by hooks.
37
37
 
38
- ### Setup (main.tsx or App.tsx)
38
+ ### Setup this template's actual providers (`src/providers.tsx`)
39
39
  ```tsx
40
40
  import { MidenProvider } from "@miden-sdk/react";
41
-
42
- <MidenProvider config={{ rpcUrl: "testnet", prover: "testnet" }}>
43
- <App />
44
- </MidenProvider>
41
+ import { MidenFiSignerProvider } from "@miden-sdk/miden-wallet-adapter-react";
42
+
43
+ <MidenFiSignerProvider
44
+ appName={APP_NAME}
45
+ network={WalletAdapterNetwork.Testnet}
46
+ autoConnect
47
+ >
48
+ <MidenProvider
49
+ config={{ rpcUrl: MIDEN_RPC_URL, prover: MIDEN_PROVER }}
50
+ loadingComponent={<div className="loading">Loading Miden WASM...</div>}
51
+ >
52
+ <App />
53
+ </MidenProvider>
54
+ </MidenFiSignerProvider>
45
55
  ```
46
56
 
57
+ > `MidenFiSignerProvider` must wrap `MidenProvider`. `MidenProvider` reads from `SignerContext` during initialization (to wire its external-keystore client), so the signer context has to exist before `MidenProvider` mounts.
58
+
47
59
  ### Query Hooks
48
60
  Each returns its own result shape plus `isLoading`, `error`, `refetch`:
49
61
  ```tsx
@@ -73,6 +85,8 @@ const display = formatAssetAmount(balance, 8); // bigint → string
73
85
  const amount = parseAssetAmount("1.5", 8); // string → bigint
74
86
  ```
75
87
 
88
+ For exhaustive hook API reference, read `node_modules/@miden-sdk/react/CLAUDE.md` and `node_modules/@miden-sdk/react/README.md`.
89
+
76
90
  ## TDD Workflow
77
91
 
78
92
  When building features, follow this test-driven cycle:
@@ -110,15 +124,28 @@ Automated verification runs in layers (each catches different failure classes):
110
124
 
111
125
  1. **TypeScript type check** (auto, per-edit) — catches type errors immediately
112
126
  2. **Affected tests** (auto, per-edit) — catches logic regressions from changes
113
- 3. **Full test suite + type check + build** (auto, on task completion via Stop hook) — catches integration issues
114
- 4. **Browser verification** (manual/MCP) — catches "compiles but doesn't work" failures
127
+ 3. **Full test suite + type check + build** (auto, on each edit via PostToolUse) — catches integration issues
128
+ 4. **Browser verification** (Playwright MCP / Claude in Chrome) — catches "compiles but doesn't work" failures
115
129
 
116
130
  ### Browser verification (when needed)
117
- After completing a feature, verify in the browser:
131
+
132
+ Two tools are available for browser verification. Use whichever is appropriate:
133
+
134
+ #### Playwright MCP (visual verification, no wallet)
135
+ Configured in `.mcp.json`. Use for checking that the UI renders correctly, no console errors, layout looks right. Cannot interact with the MidenFi wallet extension.
136
+
118
137
  1. Start dev server: `yarn dev`
119
- 2. Navigate to `http://localhost:5173`
120
- 3. Check for render errors and console messages
121
- 4. If wallet-dependent features: ask the PM to verify with the MidenFi wallet extension
138
+ 2. Use Playwright MCP tools to navigate to `http://localhost:5173`
139
+ 3. Take a screenshot, check for render errors
140
+ 4. Check the browser console for errors
141
+
142
+ #### Claude in Chrome (full verification, with wallet)
143
+ Use for wallet-dependent features. Connects to the user's real browser where MidenFi is installed.
144
+
145
+ 1. Start Claude Code with `claude --chrome` (or run `/chrome` in session)
146
+ 2. Start dev server: `yarn dev`
147
+ 3. Navigate to `http://localhost:5173`
148
+ 4. Interact with wallet connect, transaction flows, etc.
122
149
 
123
150
  ## Contract Artifact Handoff
124
151
 
@@ -148,6 +175,12 @@ cargo miden build --release
148
175
  - **Stale artifacts**: Rebuild and re-copy after contract changes
149
176
  - **Deserialization failure at runtime**: Version mismatch — rebuild contracts with the SDK version matching `@miden-sdk/miden-sdk` in `package.json`
150
177
 
178
+ ## Known Temporary Workarounds
179
+
180
+ One remaining upstream feature gap. The README has full rationale + removal steps; summary below.
181
+
182
+ **Fixed-interval network poll** ([0xMiden/miden-client#2111](https://github.com/0xMiden/miden-client/issues/2111)): `useIncrementCounter.ts::increment` polls the counter's storage map every `NETWORK_POLL_INTERVAL_MS` (2.5 s) until the value changes or `NETWORK_POLL_TIMEOUT_MS` (30 s) elapses. `useWaitForCommit` doesn't apply because the increment is wallet-submitted and consumed externally by the network operator — the local client never sees the tx. #2111 tracks a React-SDK subscription primitive for this case (narrowed from the broader event-system discussion in #467).
183
+
151
184
  ## Critical Pitfalls
152
185
 
153
186
  **WASM init must complete first**: Always use MidenProvider's `loadingComponent` or check `useMiden().isReady`. Components rendering before WASM init will crash.
@@ -167,7 +200,7 @@ For non-developer users building with this template:
167
200
  3. Describe the app you want to build in natural language
168
201
  4. Claude will implement features using TDD — tests are written first, then code
169
202
  5. Automated hooks verify correctness at every step
170
- 6. When Claude says "done", the Stop hook runs full tests + build automatically
203
+ 6. Automated hooks run full tests + build after each code edit
171
204
  7. Review the app in the browser: `yarn dev` → open `http://localhost:5173`
172
205
  8. If using wallet features, install the MidenFi browser extension to test
173
206
 
@@ -201,7 +234,7 @@ git clone https://github.com/vercel-labs/agent-skills.git
201
234
 
202
235
  ## Advanced Development
203
236
 
204
- For complex applications beyond basic hook usage (custom signers, raw WebClient, advanced note flows):
237
+ For complex applications beyond basic hook usage (custom signers, direct WasmWebClient access, advanced note flows):
205
238
 
206
239
  1. Clone `miden-client` repo alongside this project (see `frontend-source-guide` skill)
207
240
  2. Use Plan Mode first — Claude explores React SDK source + examples before coding