create-miden-app 1.0.4 → 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 (56) hide show
  1. package/cli.js +6 -6
  2. package/package.json +1 -1
  3. package/template/.claude/commands/review-security.md +67 -0
  4. package/template/.claude/hooks/check-artifacts.sh +45 -0
  5. package/template/.claude/hooks/run-affected-tests.sh +31 -0
  6. package/template/.claude/hooks/typecheck.sh +27 -0
  7. package/template/.claude/settings.json +29 -0
  8. package/template/.claude/settings.local.json +24 -0
  9. package/template/.claude/skills/frontend-pitfalls/SKILL.md +186 -0
  10. package/template/.claude/skills/frontend-source-guide/SKILL.md +163 -0
  11. package/template/.claude/skills/miden-concepts/SKILL.md +110 -0
  12. package/template/.claude/skills/react-sdk-patterns/SKILL.md +562 -0
  13. package/template/.claude/skills/signer-integration/SKILL.md +177 -0
  14. package/template/.claude/skills/testing-patterns/SKILL.md +338 -0
  15. package/template/.claude/skills/vite-wasm-setup/SKILL.md +134 -0
  16. package/template/.claude/skills/web-client-usage/SKILL.md +454 -0
  17. package/template/.env.example +18 -0
  18. package/template/.mcp.json +9 -0
  19. package/template/CLAUDE.md +243 -0
  20. package/template/README.md +119 -14
  21. package/template/index.html +1 -1
  22. package/template/package.json +18 -8
  23. package/template/public/packages/counter_account.masp +0 -0
  24. package/template/public/packages/increment_note.masp +0 -0
  25. package/template/src/App.tsx +6 -59
  26. package/template/src/__tests__/fixtures/accounts.ts +68 -0
  27. package/template/src/__tests__/fixtures/index.ts +22 -0
  28. package/template/src/__tests__/fixtures/notes.ts +33 -0
  29. package/template/src/__tests__/mocks/miden-sdk-react.ts +261 -0
  30. package/template/src/__tests__/patterns/README.md +44 -0
  31. package/template/src/__tests__/patterns/mutation-hook.test.tsx +146 -0
  32. package/template/src/__tests__/patterns/provider-setup.test.tsx +77 -0
  33. package/template/src/__tests__/patterns/query-hook.test.tsx +143 -0
  34. package/template/src/{App.css → components/AppContent.css} +9 -9
  35. package/template/src/components/AppContent.tsx +80 -0
  36. package/template/src/components/ConfiguredCounter.tsx +48 -0
  37. package/template/src/components/Counter.css +27 -0
  38. package/template/src/components/Counter.tsx +16 -0
  39. package/template/src/components/__tests__/AppContent.test.tsx +274 -0
  40. package/template/src/components/__tests__/ConfiguredCounter.test.tsx +116 -0
  41. package/template/src/components/__tests__/Counter.test.tsx +44 -0
  42. package/template/src/config.ts +41 -0
  43. package/template/src/hooks/__tests__/useIncrementCounter.test.tsx +257 -0
  44. package/template/src/hooks/useIncrementCounter.ts +195 -0
  45. package/template/src/index.css +7 -0
  46. package/template/src/lib/miden.ts +9 -0
  47. package/template/src/main.tsx +6 -6
  48. package/template/src/providers.tsx +27 -0
  49. package/template/src/vite-env.d.ts +1 -0
  50. package/template/tsconfig.app.json +8 -4
  51. package/template/tsconfig.node.json +1 -3
  52. package/template/vite.config.ts +5 -17
  53. package/template/vitest.config.ts +25 -0
  54. package/template/vitest.setup.ts +1 -0
  55. package/template/yarn.lock +1687 -815
  56. package/template/src/miden/lib/demo.ts +0 -106
@@ -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.
@@ -0,0 +1,18 @@
1
+ # Miden SDK network configuration
2
+ # Supported values: devnet | testnet | local | https://your-rpc-url
3
+ VITE_MIDEN_RPC_URL=testnet
4
+
5
+ # Prover configuration
6
+ # Supported values: devnet | testnet | local | https://your-prover-url
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
+ }