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,177 @@
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/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
+ >
84
+ <MidenProvider config={{ rpcUrl: "testnet" }}>
85
+ <App />
86
+ </MidenProvider>
87
+ </MidenFiSignerProvider>
88
+ ```
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
+
99
+ ## Unified Signer Interface
100
+
101
+ Works with any signer provider above:
102
+ ```tsx
103
+ import { useSigner } from "@miden-sdk/react";
104
+
105
+ const { isConnected, connect, disconnect, name } = useSigner();
106
+
107
+ if (!isConnected) {
108
+ return <button onClick={connect}>Connect {name}</button>;
109
+ }
110
+ ```
111
+
112
+ ## Building a Custom Signer
113
+
114
+ Implement `SignerContextValue` via `SignerContext.Provider`:
115
+
116
+ ```tsx
117
+ import { SignerContext } from "@miden-sdk/react";
118
+
119
+ <SignerContext.Provider value={{
120
+ name: "MyWallet",
121
+ storeName: `mywallet_${userAddress}`, // unique per user for DB isolation
122
+ isConnected: true,
123
+ accountConfig: {
124
+ publicKey: userPublicKeyCommitment, // Uint8Array
125
+ storageMode: "private",
126
+ },
127
+ signCb: async (pubKey, signingInputs) => {
128
+ // Route to your signing service
129
+ return signature; // Uint8Array
130
+ },
131
+ connect: async () => { /* trigger wallet connection */ },
132
+ disconnect: async () => { /* clear session */ },
133
+ }}>
134
+ <MidenProvider config={{ rpcUrl: "testnet" }}>
135
+ <App />
136
+ </MidenProvider>
137
+ </SignerContext.Provider>
138
+ ```
139
+
140
+ **Required fields:**
141
+ - `name` — Display name for the signer
142
+ - `storeName` — Unique string per user (isolates IndexedDB data between users)
143
+ - `accountConfig` — Public key commitment + storage mode
144
+ - `signCb` — Callback that signs transaction data with your key management service
145
+ - `connect` / `disconnect` — Session lifecycle handlers
146
+
147
+ ## Custom Account Components
148
+
149
+ Attach application-specific `AccountComponent` instances (e.g., DEX logic from `.masp` packages) to accounts created by the signer:
150
+
151
+ ```tsx
152
+ import { type SignerAccountConfig } from "@miden-sdk/react";
153
+ import { AccountComponent } from "@miden-sdk/miden-sdk";
154
+
155
+ const myDexComponent: AccountComponent = await loadCompiledComponent();
156
+
157
+ const accountConfig: SignerAccountConfig = {
158
+ publicKeyCommitment: userPublicKeyCommitment,
159
+ accountType: "RegularAccountUpdatableCode",
160
+ storageMode: myStorageMode,
161
+ customComponents: [myDexComponent],
162
+ };
163
+ ```
164
+
165
+ Components are appended to the `AccountBuilder` after the default basic wallet component. The field is optional — omitting it preserves default behavior.
166
+
167
+ ## Which Signer to Choose
168
+
169
+ | Signer | Auth Method | Keys Stored | Best For |
170
+ |--------|-------------|-------------|----------|
171
+ | Local keystore (default) | None | Browser IndexedDB | Development, demos |
172
+ | Para | EVM wallet | Para servers | Apps with existing EVM users |
173
+ | Turnkey | Passkey (biometric) | Turnkey servers | Consumer apps, no seed phrases |
174
+ | MidenFi Wallet | Browser extension | Extension | Power users with MidenFi wallet |
175
+ | Custom | Your choice | Your infrastructure | Enterprise, custom auth flows |
176
+
177
+ **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,338 @@
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 }`. Its `result` type is `SendResult { txId, note }` - distinct from `TransactionResult { transactionId }` used by `useMint`/`useConsume`/`useSwap`/`useMultiSend`/`useTransaction`.
55
+ - `useMint()`, `useConsume()`, `useSwap()`, `useTransaction()`, `useMultiSend()` - idle shape with `result: TransactionResult | null`.
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 - useSend returns SendResult { txId, note }
72
+ vi.mocked(useSend).mockReturnValue({
73
+ send: vi.fn(),
74
+ result: { txId: "0xabc123", note: null },
75
+ isLoading: false,
76
+ stage: "complete",
77
+ error: null,
78
+ reset: vi.fn(),
79
+ });
80
+
81
+ // Other mutation hooks return TransactionResult { transactionId }
82
+ vi.mocked(useMint).mockReturnValue({
83
+ mint: vi.fn(),
84
+ result: { transactionId: "0xdef456" },
85
+ isLoading: false,
86
+ stage: "complete",
87
+ error: null,
88
+ reset: vi.fn(),
89
+ });
90
+ ```
91
+
92
+ ## Fixtures
93
+
94
+ Realistic test data in `src/__tests__/fixtures/`:
95
+
96
+ ```tsx
97
+ import {
98
+ WALLET_ID_1, // "0x0a00000000000001"
99
+ WALLET_ID_2, // "0x0a00000000000002"
100
+ FAUCET_ID, // "0x0a00000000000003"
101
+ COUNTER_ID, // "0x0a00000000000004"
102
+ MOCK_WALLET_HEADER, // { id, nonce, storageCommitment }
103
+ MOCK_FAUCET_HEADER, // { id, nonce, storageCommitment }
104
+ MOCK_ASSET_BALANCE, // { assetId, amount: 1000000000n, symbol: "TEST", decimals: 8 }
105
+ MOCK_ACCOUNT, // { id, nonce, bech32id() }
106
+ MOCK_TRANSACTION_RESULT, // { transactionId: "0x..." } - useMint / useConsume / useSwap / useMultiSend / useTransaction
107
+ MOCK_SEND_RESULT, // { txId: "0x...", note: null } - useSend
108
+ MOCK_NOTE_SUMMARY, // { id, assets, sender }
109
+ } from "@/__tests__/fixtures";
110
+ ```
111
+
112
+ Key characteristics:
113
+ - Account IDs use hex format (`0x...`) - network-agnostic test fixtures
114
+ - Amounts are `bigint` (e.g., `1000000000n` = 10.0 with 8 decimals)
115
+ - Asset metadata uses TEST token with 8 decimals
116
+
117
+ ## Test Patterns (copy-adaptable)
118
+
119
+ Reference tests in `src/__tests__/patterns/`:
120
+
121
+ | Pattern | File | Tests |
122
+ |---------|------|-------|
123
+ | Provider/context setup | `provider-setup.test.tsx` | ready, loading, error states |
124
+ | Query hook component | `query-hook.test.tsx` | data, loading, error, empty states |
125
+ | Mutation hook component | `mutation-hook.test.tsx` | idle, stages, success, error, argument verification |
126
+
127
+ ### Minimum test coverage per component
128
+
129
+ Every component test should cover:
130
+ 1. **Success state** - renders correctly with data
131
+ 2. **Loading state** - shows loading indicator
132
+ 3. **Error state** - shows error message, recovery action
133
+ 4. **User interactions** - buttons, forms trigger correct handler calls
134
+
135
+ ## Wallet connection state in tests
136
+
137
+ This template's wallet button (`src/components/AppContent.tsx`) drives off **`useMidenFiWallet()`** from `@miden-sdk/miden-wallet-adapter-react`, not the generic `useSigner()`. The button gates on `wallet.readyState` (from `@miden-sdk/miden-wallet-adapter-base`) so the UI can render an "Install MidenFi Wallet" state before the extension is detected, rather than falling through to the adapter's Chrome-Web-Store fallback. When testing wallet-connect UI, mock both modules and override per test.
138
+
139
+ The mock factory must return the **full `WalletContextState`** shape - `useIncrementCounter` reads `address` and `requestTransaction` directly off the hook return, and the wallet button reads `wallet.readyState`. A partial mock will compile (with broad casts) and silently miss contract drift. Setup:
140
+
141
+ ```tsx
142
+ vi.mock("@miden-sdk/react", () => import("@/__tests__/mocks/miden-sdk-react"));
143
+ vi.mock("@miden-sdk/miden-wallet-adapter-react", () => ({
144
+ useMidenFiWallet: vi.fn(() => ({
145
+ autoConnect: false,
146
+ wallets: [],
147
+ wallet: null,
148
+ address: null,
149
+ publicKey: null,
150
+ connected: false,
151
+ connecting: false,
152
+ disconnecting: false,
153
+ select: vi.fn(),
154
+ connect: vi.fn(async () => undefined),
155
+ disconnect: vi.fn(async () => undefined),
156
+ requestTransaction: vi.fn(async () => "0xtx"),
157
+ requestAssets: undefined,
158
+ requestPrivateNotes: undefined,
159
+ signBytes: undefined,
160
+ importPrivateNote: undefined,
161
+ requestConsumableNotes: undefined,
162
+ waitForTransaction: undefined,
163
+ requestSend: undefined,
164
+ requestConsume: undefined,
165
+ createAccount: undefined,
166
+ })),
167
+ }));
168
+ vi.mock("@miden-sdk/miden-wallet-adapter-base", () => ({
169
+ WalletReadyState: {
170
+ Installed: "Installed",
171
+ NotDetected: "NotDetected",
172
+ Loadable: "Loadable",
173
+ Unsupported: "Unsupported",
174
+ },
175
+ }));
176
+
177
+ import { useMidenFiWallet } from "@miden-sdk/miden-wallet-adapter-react";
178
+ ```
179
+
180
+ Use a typed factory for per-test overrides - `WalletContextState` is the `useMidenFiWallet()` return type:
181
+
182
+ ```tsx
183
+ type WalletState = ReturnType<typeof useMidenFiWallet>;
184
+ type WalletInner = NonNullable<WalletState["wallet"]>;
185
+
186
+ function walletState(
187
+ overrides: Partial<{
188
+ readyState: "Installed" | "NotDetected" | "Loadable" | "Unsupported";
189
+ connected: boolean;
190
+ address: string | null;
191
+ requestTransaction: WalletState["requestTransaction"];
192
+ }> = {},
193
+ ): WalletState {
194
+ const {
195
+ readyState = "Installed",
196
+ connected = false,
197
+ address = connected ? "mtst1arwk88k8smzcq5p30upr6eerw5npmnyz" : null,
198
+ requestTransaction = vi.fn(async () => "0xtx"),
199
+ } = overrides;
200
+ // The inner Wallet's `adapter` is an `Adapter` (eventemitter + polling
201
+ // strategy) - we stub it structurally because the components under test
202
+ // only read `readyState` off the inner wallet object.
203
+ const innerWallet = {
204
+ adapter: {} as WalletInner["adapter"],
205
+ readyState,
206
+ } as WalletInner;
207
+ return {
208
+ autoConnect: false,
209
+ wallets: [innerWallet],
210
+ wallet: innerWallet,
211
+ address,
212
+ publicKey: null,
213
+ connected,
214
+ connecting: false,
215
+ disconnecting: false,
216
+ select: vi.fn(),
217
+ connect: vi.fn(async () => undefined),
218
+ disconnect: vi.fn(async () => undefined),
219
+ requestTransaction,
220
+ requestAssets: undefined,
221
+ requestPrivateNotes: undefined,
222
+ signBytes: undefined,
223
+ importPrivateNote: undefined,
224
+ requestConsumableNotes: undefined,
225
+ waitForTransaction: undefined,
226
+ requestSend: undefined,
227
+ requestConsume: undefined,
228
+ createAccount: undefined,
229
+ };
230
+ }
231
+
232
+ // extension not detected - shows disabled "Install MidenFi Wallet"
233
+ vi.mocked(useMidenFiWallet).mockReturnValue(
234
+ walletState({ readyState: "NotDetected" }),
235
+ );
236
+
237
+ // installed + connected with an account - shows "Disconnect Wallet"
238
+ vi.mocked(useMidenFiWallet).mockReturnValue(
239
+ walletState({ readyState: "Installed", connected: true }),
240
+ );
241
+ ```
242
+
243
+ The factory satisfies `WalletContextState` without `as unknown as` over the whole object - the only narrow `as` is the inner adapter stub, which is unavoidable until we want to construct a real `Adapter` in tests. See `src/components/__tests__/AppContent.test.tsx` for the canonical version.
244
+
245
+ For app code that needs the selected signer account for client-side flows (transaction-building hooks, etc.), `useMiden()` exposes `signerAccountId` / `signerConnected` as lower-level provider state - mock those via the `@miden-sdk/react` mock factory.
246
+
247
+ Vitest config externalizes `@miden-sdk/miden-wallet-adapter-react` to prevent broken transitive resolution.
248
+
249
+ ## Mocking Classes Called with `new` (Vitest v4)
250
+
251
+ Vitest v4 enforces that mock implementations passed to `vi.fn()` must be `function` declarations (not arrow functions) when the mocked function is invoked with `new`. Arrow functions cannot be called as constructors and will throw `TypeError: ... is not a constructor`.
252
+
253
+ ```ts
254
+ // WRONG: arrow function - throws when production code does `new MidenClient(...)`
255
+ vi.mock("@miden-sdk/miden-sdk", () => ({
256
+ MidenClient: vi.fn(() => ({ /* ... */ })),
257
+ }));
258
+
259
+ // RIGHT: function expression - usable with `new`
260
+ vi.mock("@miden-sdk/miden-sdk", () => ({
261
+ MidenClient: vi.fn(function () {
262
+ return { /* ... */ };
263
+ }),
264
+ }));
265
+ ```
266
+
267
+ This applies to any class mocked at module level that production code instantiates with `new` (`new MidenClient(...)`, `new WasmWebClient(...)`, etc.). When tests fail with `TypeError: ... is not a constructor` after a Vitest v4 upgrade, swap the arrow-function bodies for `vi.fn(function () { ... })`.
268
+
269
+ For component-level wallet adapters and hooks that are function references rather than classes (the existing `vi.mock("@miden-sdk/miden-wallet-adapter-react", ...)` example above), arrow-function mocks remain fine.
270
+
271
+ ## Testing Time-Dependent Code (Network Sync Delay)
272
+
273
+ Production code that polls or waits on chain state should accept the delay interval as an injectable parameter rather than hardcoding it. This lets tests replace the production default (e.g. `5000` ms) with `0` so the loop drains synchronously without `vi.useFakeTimers()` plumbing.
274
+
275
+ Pattern:
276
+
277
+ ```ts
278
+ // Production: optional delay parameter with a sensible default
279
+ export function pollUntilCommit(
280
+ txId: string,
281
+ intervalMs = 5000, // production default
282
+ ) {
283
+ // ... uses setTimeout(..., intervalMs) or `await sleep(intervalMs)`
284
+ }
285
+
286
+ // Tests: pass 0 to skip waits
287
+ const result = await pollUntilCommit(txId, 0);
288
+ ```
289
+
290
+ When the value comes from `src/config.ts` (e.g. `NETWORK_SYNC_DELAY_MS`), expose the same override there so tests can stub it via `vi.mock("@/config", ...)` without touching app code:
291
+
292
+ ```ts
293
+ // src/config.ts
294
+ export const NETWORK_SYNC_DELAY_MS = Number(import.meta.env.VITE_NETWORK_SYNC_DELAY_MS ?? 5000);
295
+
296
+ // test
297
+ vi.mock("@/config", () => ({ NETWORK_SYNC_DELAY_MS: 0 }));
298
+ ```
299
+
300
+ Document the production default and the test override at the call site so the contract between app code and tests is obvious.
301
+
302
+ ## Automated Verification Pipeline
303
+
304
+ Hooks in `.claude/settings.json` enforce quality automatically:
305
+
306
+ 1. **PostToolUse: typecheck** - `npx tsc -b --noEmit` on every `.ts`/`.tsx` edit in `src/`
307
+ 2. **PostToolUse: affected tests** - `npx vitest --changed --run` on every `.ts`/`.tsx` edit in `src/`
308
+ 3. **PostToolUse: full suite** - Full `vitest --run && tsc -b --noEmit && vite build` after each edit
309
+
310
+ If any hook fails (exit code 2), the agent is blocked from proceeding until the issue is fixed.
311
+
312
+ ## TDD Flow
313
+
314
+ ```
315
+ 1. Write test (describe expected behavior)
316
+
317
+ 2. yarn test → RED (test fails)
318
+
319
+ 3. Implement code
320
+
321
+ 4. Auto hooks fire → typecheck + affected tests
322
+
323
+ 5. yarn test → GREEN (all pass)
324
+
325
+ 6. Refactor if needed
326
+
327
+ 7. Task complete → full suite already ran after last edit
328
+ ```
329
+
330
+ ## Common Mistakes
331
+
332
+ **Forgetting vi.clearAllMocks()**: Always call in `beforeEach` to prevent mock state leaking between tests.
333
+
334
+ **Not mocking the SDK**: Components importing from `@miden-sdk/react` will fail without `vi.mock()` because the real SDK requires WASM initialization.
335
+
336
+ **Using number instead of bigint**: Mock amounts must use `bigint` (`1000n`, not `1000`). The SDK enforces this at the type level.
337
+
338
+ **Testing implementation details**: Test what the user sees (text, buttons, states), not internal hook calls. Use `screen.getByRole`, `screen.getByText`, not internal component state.
@@ -0,0 +1,134 @@
1
+ ---
2
+ name: vite-wasm-setup
3
+ description: Guide to configuring Vite for Miden WASM applications. Covers the midenVitePlugin() setup, COOP/COEP headers, production deployment headers, TypeScript compatibility, and troubleshooting common Vite + WASM issues. Use when setting up a new Miden frontend, debugging build or runtime errors related to WASM or Vite configuration, or deploying to production.
4
+ ---
5
+
6
+ # Vite + WASM Configuration for Miden
7
+
8
+ ## Required vite.config.ts
9
+
10
+ ```typescript
11
+ import { defineConfig } from "vite";
12
+ import react from "@vitejs/plugin-react";
13
+ import { midenVitePlugin } from "@miden-sdk/vite-plugin";
14
+
15
+ export default defineConfig({
16
+ plugins: [react(), midenVitePlugin({ crossOriginIsolation: true })],
17
+ });
18
+ ```
19
+
20
+ Pass `crossOriginIsolation: true` explicitly. The Miden WASM client uses `SharedArrayBuffer` via Rust atomics, which is only available when the page is cross-origin-isolated (COOP `same-origin` + COEP `require-corp`). Don't rely on the plugin's own default — it has shifted across releases, so the template's config is source-of-truth.
21
+
22
+ If your app must host third-party iframes, OAuth popups, or other cross-origin resources that don't emit `require-corp`, either (a) embed them via `credentialless` COEP as a workaround (see the Gotchas section below), or (b) set `crossOriginIsolation: false` and accept that Miden client operations won't work on that route.
23
+
24
+ ## What midenVitePlugin() Handles
25
+
26
+ `@miden-sdk/vite-plugin` abstracts Miden-specific Vite configuration:
27
+
28
+ - **WASM loading** — Configures Vite to correctly import `.wasm` modules
29
+ - **Top-level await** — Enables top-level `await` required by the WASM SDK initialization
30
+ - **optimizeDeps** — Excludes `@miden-sdk/miden-sdk` from pre-bundling (pre-bundling corrupts the WASM binary)
31
+ - **COOP/COEP headers** — Emits `Cross-Origin-Opener-Policy: same-origin` + `Cross-Origin-Embedder-Policy: require-corp` on the dev server when `crossOriginIsolation: true`
32
+
33
+ You don't need to install or configure `vite-plugin-wasm`, `vite-plugin-top-level-await`, or dexie aliases manually.
34
+
35
+ ## Required Dependencies
36
+
37
+ Keep all `@miden-sdk/*` runtime packages aligned. The template's `package.json` pins them as an exact-version set; upgrade all four together and re-run the full verification suite (including the wallet-confirmed increment E2E) whenever you bump.
38
+
39
+ ```json
40
+ {
41
+ "dependencies": {
42
+ "@miden-sdk/react": "<matches miden-sdk>",
43
+ "@miden-sdk/miden-sdk": "<authoritative version>",
44
+ "@miden-sdk/miden-wallet-adapter-base": "<may lag by a patch>",
45
+ "@miden-sdk/miden-wallet-adapter-react": "<may lag by a patch>"
46
+ },
47
+ "devDependencies": {
48
+ "@miden-sdk/vite-plugin": "<matches miden-sdk>"
49
+ }
50
+ }
51
+ ```
52
+
53
+ Notes:
54
+ - **Always check `package.json` for the authoritative versions** — this skill intentionally doesn't inline them because they shift across SDK releases.
55
+ - The wallet adapter packages are versioned separately from the core SDK. Their `peerDependencies` typically allow `^<major>.<minor>.x`, so a patch-level gap between the adapter and the core SDK is expected and fine.
56
+ - When you bump, clean-install: `rm -rf node_modules yarn.lock && yarn install`. Vite's dep optimizer caches resolved SDK paths, and stale caches can surface as `ERR_BLOCKED_BY_RESPONSE` or spurious `Failed to fetch` errors on module workers.
57
+
58
+ ## Production Deployment Headers
59
+
60
+ COOP/COEP headers must be set on the production server. `midenVitePlugin({ crossOriginIsolation: true })` only affects the Vite dev server.
61
+
62
+ ### Nginx
63
+ ```nginx
64
+ add_header Cross-Origin-Opener-Policy same-origin;
65
+ add_header Cross-Origin-Embedder-Policy require-corp;
66
+ ```
67
+
68
+ ### Vercel (vercel.json)
69
+ ```json
70
+ {
71
+ "headers": [
72
+ {
73
+ "source": "/(.*)",
74
+ "headers": [
75
+ { "key": "Cross-Origin-Opener-Policy", "value": "same-origin" },
76
+ { "key": "Cross-Origin-Embedder-Policy", "value": "require-corp" }
77
+ ]
78
+ }
79
+ ]
80
+ }
81
+ ```
82
+
83
+ ### Cloudflare Pages (_headers)
84
+ ```
85
+ /*
86
+ Cross-Origin-Opener-Policy: same-origin
87
+ Cross-Origin-Embedder-Policy: require-corp
88
+ ```
89
+
90
+ ### WASM MIME Type
91
+ Ensure your server serves `.wasm` files with `application/wasm` MIME type.
92
+
93
+ ## COOP/COEP Gotchas
94
+
95
+ These headers break:
96
+ - **Third-party iframes** (YouTube embeds, Twitter embeds, analytics)
97
+ - **External scripts** without CORS headers
98
+ - **OAuth popups** from different origins
99
+
100
+ Workaround: Use `credentialless` for COEP if you need cross-origin resources:
101
+ ```
102
+ Cross-Origin-Embedder-Policy: credentialless
103
+ ```
104
+
105
+ Note: `credentialless` provides weaker isolation but allows most cross-origin resources.
106
+
107
+ ## TypeScript Compatibility
108
+
109
+ Standard Vite-compatible tsconfig settings work with Miden. The only actual constraint is ES2020+ for `bigint` support:
110
+
111
+ ```json
112
+ {
113
+ "compilerOptions": {
114
+ "target": "ES2022",
115
+ "lib": ["ES2022", "DOM", "DOM.Iterable"],
116
+ "module": "ESNext",
117
+ "moduleResolution": "bundler"
118
+ }
119
+ }
120
+ ```
121
+
122
+ `module: "ESNext"` and `moduleResolution: "bundler"` are standard Vite defaults, not Miden-specific requirements. If you're using the Vite-generated tsconfig, no changes are needed beyond ensuring `target` is ES2020+.
123
+
124
+ ## Troubleshooting
125
+
126
+ | Issue | Cause | Fix |
127
+ |-------|-------|-----|
128
+ | "SharedArrayBuffer is not defined" | COOP/COEP headers not reaching the browser | Verify `midenVitePlugin({ crossOriginIsolation: true })` is in plugins; check production server headers separately |
129
+ | WASM module not found | SDK not configured correctly | Ensure `midenVitePlugin()` is in plugins array |
130
+ | "Top-level await not supported" | Missing plugin setup | Ensure `midenVitePlugin()` is in plugins array |
131
+ | WASM init hangs | COEP blocking WASM fetch | Check network tab for blocked requests; verify COOP/COEP headers are present |
132
+ | Build succeeds but WASM fails at runtime | Wrong MIME type | Serve .wasm as application/wasm |
133
+ | "recursive use of an object" | Concurrent WASM access | Use runExclusive() from useMiden() |
134
+ | Double initialization in dev | React StrictMode | Use MidenProvider (handles this internally) |