create-miden-app 1.0.3 → 1.0.6

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 (83) hide show
  1. package/cli.js +6 -6
  2. package/package.json +1 -6
  3. package/template/.claude/hooks/check-artifacts.sh +45 -0
  4. package/template/.claude/hooks/run-affected-tests.sh +31 -0
  5. package/template/.claude/hooks/typecheck.sh +27 -0
  6. package/template/.claude/settings.json +35 -0
  7. package/template/.claude/skills/frontend-pitfalls/SKILL.md +189 -0
  8. package/template/.claude/skills/frontend-source-guide/SKILL.md +163 -0
  9. package/template/.claude/skills/miden-concepts/SKILL.md +108 -0
  10. package/template/.claude/skills/react-sdk-patterns/SKILL.md +296 -0
  11. package/template/.claude/skills/signer-integration/SKILL.md +158 -0
  12. package/template/.claude/skills/testing-patterns/SKILL.md +177 -0
  13. package/template/.claude/skills/vite-wasm-setup/SKILL.md +128 -0
  14. package/template/.env.example +5 -0
  15. package/template/CLAUDE.md +210 -0
  16. package/template/README.md +53 -14
  17. package/template/create-miden-app/template/.claude/hooks/typecheck.sh +27 -0
  18. package/template/create-miden-app/template/.claude/settings.json +17 -0
  19. package/template/create-miden-app/template/.claude/skills/frontend-pitfalls/SKILL.md +189 -0
  20. package/template/create-miden-app/template/.claude/skills/frontend-source-guide/SKILL.md +163 -0
  21. package/template/create-miden-app/template/.claude/skills/miden-concepts/SKILL.md +108 -0
  22. package/template/create-miden-app/template/.claude/skills/react-sdk-patterns/SKILL.md +294 -0
  23. package/template/create-miden-app/template/.claude/skills/signer-integration/SKILL.md +158 -0
  24. package/template/create-miden-app/template/.claude/skills/vite-wasm-setup/SKILL.md +128 -0
  25. package/template/create-miden-app/template/.env.example +5 -0
  26. package/template/create-miden-app/template/CLAUDE.md +116 -0
  27. package/template/create-miden-app/template/README.md +61 -0
  28. package/template/create-miden-app/template/eslint.config.js +23 -0
  29. package/template/create-miden-app/template/index.html +13 -0
  30. package/template/create-miden-app/template/package.json +34 -0
  31. package/template/create-miden-app/template/public/vite.svg +1 -0
  32. package/template/create-miden-app/template/src/App.tsx +10 -0
  33. package/template/create-miden-app/template/src/assets/miden.svg +3 -0
  34. package/template/create-miden-app/template/src/assets/react.svg +1 -0
  35. package/template/{src/App.css → create-miden-app/template/src/components/AppContent.css} +9 -9
  36. package/template/create-miden-app/template/src/components/AppContent.tsx +50 -0
  37. package/template/create-miden-app/template/src/components/Counter.css +27 -0
  38. package/template/create-miden-app/template/src/components/Counter.tsx +45 -0
  39. package/template/create-miden-app/template/src/config.ts +21 -0
  40. package/template/create-miden-app/template/src/hooks/useIncrementCounter.ts +136 -0
  41. package/template/create-miden-app/template/src/index.css +75 -0
  42. package/template/create-miden-app/template/src/lib/miden.ts +9 -0
  43. package/template/create-miden-app/template/src/main.tsx +10 -0
  44. package/template/create-miden-app/template/src/providers.tsx +31 -0
  45. package/template/create-miden-app/template/src/vite-env.d.ts +1 -0
  46. package/template/create-miden-app/template/tsconfig.app.json +32 -0
  47. package/template/create-miden-app/template/tsconfig.json +7 -0
  48. package/template/create-miden-app/template/tsconfig.node.json +24 -0
  49. package/template/create-miden-app/template/vite.config.ts +17 -0
  50. package/template/create-miden-app/template/yarn.lock +1697 -0
  51. package/template/index.html +1 -1
  52. package/template/package.json +17 -8
  53. package/template/public/packages/counter_account.masp +0 -0
  54. package/template/public/packages/increment_note.masp +0 -0
  55. package/template/src/App.tsx +6 -59
  56. package/template/src/__tests__/fixtures/accounts.ts +57 -0
  57. package/template/src/__tests__/fixtures/index.ts +21 -0
  58. package/template/src/__tests__/fixtures/notes.ts +33 -0
  59. package/template/src/__tests__/mocks/miden-sdk-react.ts +244 -0
  60. package/template/src/__tests__/patterns/README.md +44 -0
  61. package/template/src/__tests__/patterns/mutation-hook.test.tsx +146 -0
  62. package/template/src/__tests__/patterns/provider-setup.test.tsx +75 -0
  63. package/template/src/__tests__/patterns/query-hook.test.tsx +143 -0
  64. package/template/src/components/AppContent.css +45 -0
  65. package/template/src/components/AppContent.tsx +50 -0
  66. package/template/src/components/Counter.css +27 -0
  67. package/template/src/components/Counter.tsx +45 -0
  68. package/template/src/components/__tests__/AppContent.test.tsx +86 -0
  69. package/template/src/components/__tests__/Counter.test.tsx +114 -0
  70. package/template/src/config.ts +21 -0
  71. package/template/src/hooks/useIncrementCounter.ts +136 -0
  72. package/template/src/index.css +7 -0
  73. package/template/src/lib/miden.ts +9 -0
  74. package/template/src/main.tsx +6 -6
  75. package/template/src/providers.tsx +31 -0
  76. package/template/src/vite-env.d.ts +1 -0
  77. package/template/tsconfig.app.json +8 -4
  78. package/template/tsconfig.node.json +1 -3
  79. package/template/vite.config.ts +5 -17
  80. package/template/vitest.config.ts +26 -0
  81. package/template/vitest.setup.ts +1 -0
  82. package/template/yarn.lock +1318 -799
  83. package/template/src/miden/lib/demo.ts +0 -105
@@ -0,0 +1,296 @@
1
+ ---
2
+ name: react-sdk-patterns
3
+ description: Complete guide to building Miden frontends with @miden-sdk/react hooks. Covers MidenProvider setup, all query hooks (useAccounts, useAccount, useNotes, useSyncState, useAssetMetadata), all mutation hooks (useCreateWallet, useSend, useMultiSend, useMint, useConsume, useSwap, useTransaction, useCreateFaucet), transaction stages, signer integration, and utility functions. Use when writing, editing, or reviewing Miden React frontend code.
4
+ ---
5
+
6
+ # Miden React SDK Patterns
7
+
8
+ ## SDK Choice
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.
11
+
12
+ ## MidenProvider Configuration
13
+
14
+ ```tsx
15
+ import { MidenProvider } from "@miden-sdk/react";
16
+
17
+ <MidenProvider
18
+ config={{
19
+ rpcUrl: "devnet", // "devnet" | "testnet" | "localhost" | custom URL
20
+ prover: "devnet", // "local" | "devnet" | "testnet" | custom URL
21
+ autoSyncInterval: 15000, // ms, set to 0 to disable. Default: 15000
22
+ noteTransportUrl: "...", // optional: for private note delivery
23
+ }}
24
+ loadingComponent={<Loading />} // shown during WASM init
25
+ errorComponent={<Error />} // shown on init failure (receives Error prop)
26
+ >
27
+ <App />
28
+ </MidenProvider>
29
+ ```
30
+
31
+ | Network | rpcUrl | Use When |
32
+ |---------|--------|----------|
33
+ | Devnet | `"devnet"` | Development, testing with fake tokens |
34
+ | Testnet | `"testnet"` | Pre-production testing |
35
+ | Localhost | `"localhost"` | Local node at `http://localhost:57291` |
36
+
37
+ ## Query Hooks
38
+
39
+ Each returns its own result shape plus `isLoading`, `error`, `refetch`.
40
+
41
+ ### useAccounts()
42
+ ```tsx
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)
47
+ ```
48
+
49
+ ### useAccount(accountId: string)
50
+ ```tsx
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
55
+ ```
56
+
57
+ ### useNotes(filter?)
58
+ ```tsx
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[]
64
+
65
+ // Filter by account:
66
+ const { notes } = useNotes({ accountId: "0x..." });
67
+ // Filter by status:
68
+ const { notes } = useNotes({ status: "committed" });
69
+ ```
70
+
71
+ ### useSyncState()
72
+ ```tsx
73
+ const { syncHeight, isSyncing, lastSyncTime, sync, error } = useSyncState();
74
+ await sync(); // Manual sync
75
+ ```
76
+
77
+ ### useAssetMetadata(faucetId: string | string[])
78
+ ```tsx
79
+ const { assetMetadata } = useAssetMetadata(faucetId);
80
+ // assetMetadata — Map<string, AssetMetadata>
81
+ // Each entry: { assetId, symbol?, decimals? }
82
+ const meta = assetMetadata.get(faucetId);
83
+ // meta.symbol — "TEST"
84
+ // meta.decimals — 8
85
+ ```
86
+
87
+ ### useTransactionHistory(options?)
88
+ ```tsx
89
+ const { records, record, status, isLoading, error, refetch } = useTransactionHistory({ id: txId });
90
+ // status: "pending" | "committed" | "discarded" | null
91
+ ```
92
+
93
+ ## Mutation Hooks
94
+
95
+ Each returns its own action function plus `isLoading`, `stage`, `error`, `reset`.
96
+
97
+ **Transaction stages**: `"idle"` → `"executing"` → `"proving"` → `"submitting"` → `"complete"`
98
+
99
+ ### useCreateWallet()
100
+ ```tsx
101
+ const { createWallet, wallet, isCreating, error, reset } = useCreateWallet();
102
+ const account = await createWallet({
103
+ storageMode: "private", // "private" | "public" | "network". Default: "private"
104
+ mutable: true, // Default: true
105
+ authScheme: 0, // 0 = RpoFalcon512, 1 = EcdsaK256Keccak. Default: 0
106
+ });
107
+ ```
108
+
109
+ ### useCreateFaucet()
110
+ ```tsx
111
+ const { createFaucet, faucet, isCreating, error, reset } = useCreateFaucet();
112
+ const account = await createFaucet({
113
+ tokenSymbol: "TEST",
114
+ decimals: 8, // Default: 8
115
+ maxSupply: 1000000n, // bigint!
116
+ storageMode: "public", // Default: "private"
117
+ });
118
+ ```
119
+
120
+ ### useSend()
121
+ ```tsx
122
+ const { send, result, isLoading, stage, error, reset } = useSend();
123
+ await send({
124
+ from: senderAccountId,
125
+ to: recipientAccountId,
126
+ assetId: faucetId, // token faucet ID
127
+ amount: 1000n, // bigint!
128
+ noteType: "private", // "private" | "public". Default: "private"
129
+ recallHeight: 100, // optional: sender can reclaim after this block
130
+ timelockHeight: 50, // optional: recipient can consume after this block
131
+ });
132
+ ```
133
+
134
+ ### useMultiSend()
135
+ ```tsx
136
+ const { sendMany, result, isLoading, stage, error, reset } = useMultiSend();
137
+ await sendMany({
138
+ from: senderAccountId,
139
+ assetId: faucetId,
140
+ recipients: [
141
+ { to: recipient1, amount: 500n },
142
+ { to: recipient2, amount: 300n },
143
+ ],
144
+ noteType: "private",
145
+ });
146
+ ```
147
+
148
+ ### useMint()
149
+ ```tsx
150
+ const { mint, result, isLoading, stage, error, reset } = useMint();
151
+ await mint({
152
+ targetAccountId: recipientId,
153
+ faucetId: myFaucetId,
154
+ amount: 10000n, // bigint!
155
+ noteType: "public",
156
+ });
157
+ ```
158
+
159
+ ### useConsume()
160
+ ```tsx
161
+ const { consume, result, isLoading, stage, error, reset } = useConsume();
162
+ await consume({
163
+ accountId: myAccountId,
164
+ noteIds: [noteId1, noteId2],
165
+ });
166
+ ```
167
+
168
+ ### useSwap()
169
+ ```tsx
170
+ const { swap, result, isLoading, stage, error, reset } = useSwap();
171
+ await swap({
172
+ accountId: myAccountId,
173
+ offeredFaucetId: tokenA,
174
+ offeredAmount: 100n,
175
+ requestedFaucetId: tokenB,
176
+ requestedAmount: 50n,
177
+ noteType: "private",
178
+ paybackNoteType: "private",
179
+ });
180
+ ```
181
+
182
+ ### useTransaction() — Escape Hatch
183
+ ```tsx
184
+ const { execute, result, isLoading, stage, error, reset } = useTransaction();
185
+
186
+ // With pre-built TransactionRequest:
187
+ await execute({ accountId, request: txRequest });
188
+
189
+ // With factory function (gets access to client):
190
+ await execute({
191
+ accountId,
192
+ request: (client) => client.newSwapTransactionRequest(/* ... */),
193
+ });
194
+ ```
195
+
196
+ ### useWaitForCommit()
197
+ ```tsx
198
+ const { waitForCommit } = useWaitForCommit();
199
+ await waitForCommit(result.transactionId, {
200
+ timeoutMs: 10000, // Default: 10000
201
+ intervalMs: 1000, // Default: 1000
202
+ });
203
+ ```
204
+
205
+ ### useWaitForNotes()
206
+ ```tsx
207
+ const { waitForConsumableNotes } = useWaitForNotes();
208
+ await waitForConsumableNotes({
209
+ accountId: myAccountId,
210
+ minCount: 1, // Default: 1
211
+ timeoutMs: 10000,
212
+ });
213
+ ```
214
+
215
+ ## Transaction Progress UI
216
+
217
+ ```tsx
218
+ function SendButton({ from, to, assetId, amount }) {
219
+ const { send, stage, isLoading, error } = useSend();
220
+
221
+ return (
222
+ <div>
223
+ <button onClick={() => send({ from, to, assetId, amount })} disabled={isLoading}>
224
+ {isLoading ? `${stage}...` : "Send"}
225
+ </button>
226
+ {error && <p>Error: {error.message}</p>}
227
+ </div>
228
+ );
229
+ }
230
+ ```
231
+
232
+ ## Signer Integration
233
+
234
+ ### Local Keystore (Default)
235
+ No signer provider needed. Keys are managed in the browser via IndexedDB.
236
+
237
+ ### External Signers
238
+ 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
242
+
243
+ ```tsx
244
+ // Example: Para signer wrapping MidenProvider
245
+ import { ParaSignerProvider } from "@miden-sdk/para";
246
+ <ParaSignerProvider apiKey="..." environment="PRODUCTION">
247
+ <MidenProvider config={...}><App /></MidenProvider>
248
+ </ParaSignerProvider>
249
+ ```
250
+
251
+ ### useSigner() — Unified Interface
252
+ ```tsx
253
+ const { isConnected, connect, disconnect, name } = useSigner();
254
+ ```
255
+
256
+ ### Custom Signer
257
+ Implement `SignerContextValue` interface via `SignerContext.Provider`. Requires: `name`, `storeName` (unique per user for DB isolation), `accountConfig`, `signCb`, `connect`, `disconnect`. See `frontend-source-guide` skill for source references.
258
+
259
+ ## Utility Functions
260
+
261
+ ```tsx
262
+ import { formatAssetAmount, parseAssetAmount, getNoteSummary, formatNoteSummary, toBech32AccountId } from "@miden-sdk/react";
263
+
264
+ formatAssetAmount(1000000n, 8) // "0.01"
265
+ parseAssetAmount("0.01", 8) // 1000000n
266
+ const summary = getNoteSummary(note); // { id, assets, sender }
267
+ formatNoteSummary(summary); // "1.5 TEST"
268
+ toBech32AccountId("0x1234..."); // "miden1qy35..."
269
+ ```
270
+
271
+ ## Direct Client Access
272
+
273
+ ```tsx
274
+ const client = useMidenClient(); // throws if not ready
275
+ const { runExclusive } = useMiden();
276
+
277
+ // For operations not covered by hooks:
278
+ await runExclusive(async (client) => {
279
+ const header = await client.getBlockHeaderByNumber(100);
280
+ });
281
+ ```
282
+
283
+ ## Type Imports
284
+
285
+ ```tsx
286
+ import type {
287
+ MidenConfig, QueryResult, MutationResult, TransactionStage,
288
+ AccountsResult, AccountResult, AssetBalance, NotesResult, NoteSummary,
289
+ SendOptions, MultiSendOptions, MintOptions, ConsumeOptions, SwapOptions,
290
+ CreateWalletOptions, CreateFaucetOptions, ExecuteTransactionOptions,
291
+ TransactionResult, SyncState, WaitForCommitOptions, WaitForNotesOptions,
292
+ Account, AccountId, InputNoteRecord, ConsumableNoteRecord,
293
+ TransactionRecord, TransactionRequest, NoteType, AccountStorageMode,
294
+ SignerContextValue, SignCallback, SignerAccountConfig,
295
+ } from "@miden-sdk/react";
296
+ ```
@@ -0,0 +1,158 @@
1
+ ---
2
+ name: signer-integration
3
+ description: Guide to integrating external signers (Para, Turnkey, MidenFi wallet adapter) and building custom signers for Miden React frontends. Covers provider setup, passkey authentication, unified signer interface, custom SignerContext implementation, and custom account components. Use when adding wallet connection, authentication, or external key management to a Miden frontend.
4
+ ---
5
+
6
+ # Miden Signer Integration
7
+
8
+ ## Overview
9
+
10
+ By default, MidenProvider uses a **local keystore** (keys in IndexedDB, no wallet connection needed). For production apps, wrap MidenProvider with a signer provider to use external key management.
11
+
12
+ Signer providers must wrap MidenProvider (outer → inner):
13
+ ```
14
+ <SignerProvider> ← manages keys + auth
15
+ <MidenProvider> ← manages Miden client
16
+ <App />
17
+ </MidenProvider>
18
+ </SignerProvider>
19
+ ```
20
+
21
+ ## Pre-Built Signer Providers
22
+
23
+ ### Para (EVM Wallets)
24
+ ```tsx
25
+ import { ParaSignerProvider } from "@miden-sdk/para";
26
+
27
+ <ParaSignerProvider apiKey="your-api-key" environment="PRODUCTION">
28
+ <MidenProvider config={{ rpcUrl: "testnet" }}>
29
+ <App />
30
+ </MidenProvider>
31
+ </ParaSignerProvider>
32
+
33
+ const { para, wallet, isConnected } = useParaSigner();
34
+ ```
35
+
36
+ ### Turnkey (Passkey Authentication)
37
+ ```tsx
38
+ import { TurnkeySignerProvider } from "@miden-sdk/miden-turnkey-react";
39
+
40
+ // Config is optional — defaults to https://api.turnkey.com
41
+ // and reads VITE_TURNKEY_ORG_ID from environment
42
+ <TurnkeySignerProvider>
43
+ <MidenProvider config={{ rpcUrl: "testnet" }}>
44
+ <App />
45
+ </MidenProvider>
46
+ </TurnkeySignerProvider>
47
+
48
+ // Or with explicit config:
49
+ <TurnkeySignerProvider config={{
50
+ apiBaseUrl: "https://api.turnkey.com",
51
+ defaultOrganizationId: "your-org-id",
52
+ }}>
53
+ ...
54
+ </TurnkeySignerProvider>
55
+ ```
56
+
57
+ Connect via passkey:
58
+ ```tsx
59
+ import { useSigner } from "@miden-sdk/react";
60
+ import { useTurnkeySigner } from "@miden-sdk/miden-turnkey-react";
61
+
62
+ const { isConnected, connect, disconnect } = useSigner();
63
+ await connect(); // triggers passkey flow, auto-selects account
64
+
65
+ // Turnkey-specific extras
66
+ const { client, account, setAccount } = useTurnkeySigner();
67
+ ```
68
+
69
+ ### MidenFi Wallet Adapter (Browser Extension)
70
+ ```tsx
71
+ import { MidenFiSignerProvider } from "@miden-sdk/wallet-adapter-react";
72
+
73
+ <MidenFiSignerProvider network="Testnet">
74
+ <MidenProvider config={{ rpcUrl: "testnet" }}>
75
+ <App />
76
+ </MidenProvider>
77
+ </MidenFiSignerProvider>
78
+ ```
79
+
80
+ ## Unified Signer Interface
81
+
82
+ Works with any signer provider above:
83
+ ```tsx
84
+ import { useSigner } from "@miden-sdk/react";
85
+
86
+ const { isConnected, connect, disconnect, name } = useSigner();
87
+
88
+ if (!isConnected) {
89
+ return <button onClick={connect}>Connect {name}</button>;
90
+ }
91
+ ```
92
+
93
+ ## Building a Custom Signer
94
+
95
+ Implement `SignerContextValue` via `SignerContext.Provider`:
96
+
97
+ ```tsx
98
+ import { SignerContext } from "@miden-sdk/react";
99
+
100
+ <SignerContext.Provider value={{
101
+ name: "MyWallet",
102
+ storeName: `mywallet_${userAddress}`, // unique per user for DB isolation
103
+ isConnected: true,
104
+ accountConfig: {
105
+ publicKey: userPublicKeyCommitment, // Uint8Array
106
+ storageMode: "private",
107
+ },
108
+ signCb: async (pubKey, signingInputs) => {
109
+ // Route to your signing service
110
+ return signature; // Uint8Array
111
+ },
112
+ connect: async () => { /* trigger wallet connection */ },
113
+ disconnect: async () => { /* clear session */ },
114
+ }}>
115
+ <MidenProvider config={{ rpcUrl: "testnet" }}>
116
+ <App />
117
+ </MidenProvider>
118
+ </SignerContext.Provider>
119
+ ```
120
+
121
+ **Required fields:**
122
+ - `name` — Display name for the signer
123
+ - `storeName` — Unique string per user (isolates IndexedDB data between users)
124
+ - `accountConfig` — Public key commitment + storage mode
125
+ - `signCb` — Callback that signs transaction data with your key management service
126
+ - `connect` / `disconnect` — Session lifecycle handlers
127
+
128
+ ## Custom Account Components
129
+
130
+ Attach application-specific `AccountComponent` instances (e.g., DEX logic from `.masp` packages) to accounts created by the signer:
131
+
132
+ ```tsx
133
+ import { type SignerAccountConfig } from "@miden-sdk/react";
134
+ import { AccountComponent } from "@miden-sdk/miden-sdk";
135
+
136
+ const myDexComponent: AccountComponent = await loadCompiledComponent();
137
+
138
+ const accountConfig: SignerAccountConfig = {
139
+ publicKeyCommitment: userPublicKeyCommitment,
140
+ accountType: "RegularAccountUpdatableCode",
141
+ storageMode: myStorageMode,
142
+ customComponents: [myDexComponent],
143
+ };
144
+ ```
145
+
146
+ Components are appended to the `AccountBuilder` after the default basic wallet component. The field is optional — omitting it preserves default behavior.
147
+
148
+ ## Which Signer to Choose
149
+
150
+ | Signer | Auth Method | Keys Stored | Best For |
151
+ |--------|-------------|-------------|----------|
152
+ | Local keystore (default) | None | Browser IndexedDB | Development, demos |
153
+ | Para | EVM wallet | Para servers | Apps with existing EVM users |
154
+ | Turnkey | Passkey (biometric) | Turnkey servers | Consumer apps, no seed phrases |
155
+ | MidenFi Wallet | Browser extension | Extension | Power users with MidenFi wallet |
156
+ | Custom | Your choice | Your infrastructure | Enterprise, custom auth flows |
157
+
158
+ **Key trade-off**: Local keystore requires no setup but keys are lost if the user clears browser data. External signers persist keys server-side but add a dependency.
@@ -0,0 +1,177 @@
1
+ ---
2
+ name: testing-patterns
3
+ description: Testing conventions, mock factory, fixtures, and TDD workflow for Miden frontend development. Covers Vitest + testing-library setup, @miden-sdk/react module mocking, realistic fixture data, test patterns for query and mutation hooks, and the automated verification pipeline. Use when writing, running, or debugging tests for Miden React components.
4
+ ---
5
+
6
+ # Miden Frontend Testing Patterns
7
+
8
+ ## Test Stack
9
+
10
+ - **Vitest** — Test runner (extends Vite config for consistent behavior)
11
+ - **@testing-library/react** — Component rendering and queries
12
+ - **@testing-library/user-event** — User interaction simulation
13
+ - **@testing-library/jest-dom** — DOM assertion matchers (toBeInTheDocument, toBeDisabled, etc.)
14
+ - **jsdom** — Browser environment for tests
15
+
16
+ ## Mock Factory: `@miden-sdk/react`
17
+
18
+ All Miden SDK hooks are mocked via `src/__tests__/mocks/miden-sdk-react.ts`. This module exports mock implementations of every hook with realistic default return values.
19
+
20
+ ### Usage in test files
21
+
22
+ ```tsx
23
+ // 1. Mock the entire module (hoisted to top by vitest)
24
+ vi.mock("@miden-sdk/react", () => import("@/__tests__/mocks/miden-sdk-react"));
25
+
26
+ // 2. Import hooks you want to override
27
+ import { useAccounts, useSend } from "@miden-sdk/react";
28
+
29
+ // 3. Override per-test
30
+ it("shows empty state", () => {
31
+ vi.mocked(useAccounts).mockReturnValue({
32
+ accounts: [],
33
+ wallets: [],
34
+ faucets: [],
35
+ isLoading: false,
36
+ error: null,
37
+ refetch: vi.fn(),
38
+ });
39
+ render(<MyComponent />);
40
+ });
41
+ ```
42
+
43
+ ### Default mock return values
44
+
45
+ **Query hooks** return populated data by default:
46
+ - `useAccounts()` — 2 wallets, 1 faucet
47
+ - `useAccount()` — account with 10.0 TEST token balance
48
+ - `useNotes()` — 1 input note, 1 consumable note
49
+ - `useSyncState()` — syncHeight: 12345, not syncing
50
+ - `useAssetMetadata()` — TEST token metadata (symbol, decimals: 8)
51
+ - `useMiden()` — isReady: true
52
+
53
+ **Mutation hooks** return idle state by default:
54
+ - `useSend()` — `{ send: vi.fn(), stage: "idle", isLoading: false }`
55
+ - `useMint()`, `useConsume()`, `useSwap()`, `useTransaction()` — similar pattern
56
+ - `useCreateWallet()` — `{ createWallet: vi.fn(), isCreating: false }`
57
+
58
+ ### Simulating transaction stages
59
+
60
+ ```tsx
61
+ // Show "proving" stage
62
+ vi.mocked(useSend).mockReturnValue({
63
+ send: vi.fn(),
64
+ result: null,
65
+ isLoading: true,
66
+ stage: "proving",
67
+ error: null,
68
+ reset: vi.fn(),
69
+ });
70
+
71
+ // Show completed transaction
72
+ vi.mocked(useSend).mockReturnValue({
73
+ send: vi.fn(),
74
+ result: { transactionId: "0xabc123" },
75
+ isLoading: false,
76
+ stage: "complete",
77
+ error: null,
78
+ reset: vi.fn(),
79
+ });
80
+ ```
81
+
82
+ ## Fixtures
83
+
84
+ Realistic test data in `src/__tests__/fixtures/`:
85
+
86
+ ```tsx
87
+ import {
88
+ WALLET_ID_1, // "mtst1qy35qfqdvpjx2e5zf9hkp4vr"
89
+ WALLET_ID_2, // "mtst1qa7k9qjf8dp4x2e5zf9hkp5vr"
90
+ FAUCET_ID, // "mtst1qx9y8zjf2dp4x2e5zf9hkp3vr"
91
+ COUNTER_ID, // "mtst1aru8adnrqspgcsr3drk2n990lyc070ll"
92
+ MOCK_WALLET_HEADER, // { id, nonce, storageCommitment }
93
+ MOCK_FAUCET_HEADER, // { id, nonce, storageCommitment }
94
+ MOCK_ASSET_BALANCE, // { assetId, amount: 1000000000n, symbol: "TEST", decimals: 8 }
95
+ MOCK_ACCOUNT, // { id, nonce, bech32id() }
96
+ MOCK_TRANSACTION_RESULT, // { transactionId: "0x..." }
97
+ MOCK_NOTE_SUMMARY, // { id, assets, sender }
98
+ } from "@/__tests__/fixtures";
99
+ ```
100
+
101
+ Key characteristics:
102
+ - Account IDs use bech32 format (`mtst1...`)
103
+ - Amounts are `bigint` (e.g., `1000000000n` = 10.0 with 8 decimals)
104
+ - Asset metadata uses TEST token with 8 decimals
105
+
106
+ ## Test Patterns (copy-adaptable)
107
+
108
+ Reference tests in `src/__tests__/patterns/`:
109
+
110
+ | Pattern | File | Tests |
111
+ |---------|------|-------|
112
+ | Provider/context setup | `provider-setup.test.tsx` | ready, loading, error states |
113
+ | Query hook component | `query-hook.test.tsx` | data, loading, error, empty states |
114
+ | Mutation hook component | `mutation-hook.test.tsx` | idle, stages, success, error, argument verification |
115
+
116
+ ### Minimum test coverage per component
117
+
118
+ Every component test should cover:
119
+ 1. **Success state** — renders correctly with data
120
+ 2. **Loading state** — shows loading indicator
121
+ 3. **Error state** — shows error message, recovery action
122
+ 4. **User interactions** — buttons, forms trigger correct handler calls
123
+
124
+ ## Mocking the wallet adapter
125
+
126
+ The app uses `@miden-sdk/miden-wallet-adapter`. Mock it at the module level:
127
+
128
+ ```tsx
129
+ vi.mock("@miden-sdk/miden-wallet-adapter", () => ({
130
+ WalletMultiButton: () => <button>Connect Wallet</button>,
131
+ useWallet: vi.fn(() => ({
132
+ address: "mtst1...",
133
+ connected: true,
134
+ requestTransaction: vi.fn(),
135
+ })),
136
+ }));
137
+ ```
138
+
139
+ Vitest config externalizes `@miden-sdk/miden-wallet-adapter*` sub-packages to prevent broken transitive resolution from the reactui sub-package.
140
+
141
+ ## Automated Verification Pipeline
142
+
143
+ Hooks in `.claude/settings.json` enforce quality automatically:
144
+
145
+ 1. **PostToolUse: typecheck** — `npx tsc -b --noEmit` on every `.ts`/`.tsx` edit in `src/`
146
+ 2. **PostToolUse: affected tests** — `npx vitest --changed --run` on every `.ts`/`.tsx` edit in `src/`
147
+ 3. **Stop hook** — Full `vitest --run && tsc -b --noEmit && vite build` before task completion
148
+
149
+ If any hook fails (exit code 2), the agent is blocked from proceeding until the issue is fixed.
150
+
151
+ ## TDD Flow
152
+
153
+ ```
154
+ 1. Write test (describe expected behavior)
155
+
156
+ 2. yarn test → RED (test fails)
157
+
158
+ 3. Implement code
159
+
160
+ 4. Auto hooks fire → typecheck + affected tests
161
+
162
+ 5. yarn test → GREEN (all pass)
163
+
164
+ 6. Refactor if needed
165
+
166
+ 7. Task complete → Stop hook: full suite + build
167
+ ```
168
+
169
+ ## Common Mistakes
170
+
171
+ **Forgetting vi.clearAllMocks()**: Always call in `beforeEach` to prevent mock state leaking between tests.
172
+
173
+ **Not mocking the SDK**: Components importing from `@miden-sdk/react` will fail without `vi.mock()` because the real SDK requires WASM initialization.
174
+
175
+ **Using number instead of bigint**: Mock amounts must use `bigint` (`1000n`, not `1000`). The SDK enforces this at the type level.
176
+
177
+ **Testing implementation details**: Test what the user sees (text, buttons, states), not internal hook calls. Use `screen.getByRole`, `screen.getByText`, not internal component state.