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.
- package/cli.js +6 -6
- package/package.json +1 -1
- package/template/.claude/commands/review-security.md +67 -0
- package/template/.claude/hooks/check-artifacts.sh +45 -0
- package/template/.claude/hooks/run-affected-tests.sh +31 -0
- package/template/.claude/hooks/typecheck.sh +27 -0
- package/template/.claude/settings.json +29 -0
- package/template/.claude/settings.local.json +24 -0
- package/template/.claude/skills/frontend-pitfalls/SKILL.md +186 -0
- package/template/.claude/skills/frontend-source-guide/SKILL.md +163 -0
- package/template/.claude/skills/miden-concepts/SKILL.md +110 -0
- package/template/.claude/skills/react-sdk-patterns/SKILL.md +562 -0
- package/template/.claude/skills/signer-integration/SKILL.md +177 -0
- package/template/.claude/skills/testing-patterns/SKILL.md +338 -0
- package/template/.claude/skills/vite-wasm-setup/SKILL.md +134 -0
- package/template/.claude/skills/web-client-usage/SKILL.md +454 -0
- package/template/.env.example +18 -0
- package/template/.mcp.json +9 -0
- package/template/CLAUDE.md +243 -0
- package/template/README.md +119 -14
- package/template/index.html +1 -1
- package/template/package.json +18 -8
- package/template/public/packages/counter_account.masp +0 -0
- package/template/public/packages/increment_note.masp +0 -0
- package/template/src/App.tsx +6 -59
- package/template/src/__tests__/fixtures/accounts.ts +68 -0
- package/template/src/__tests__/fixtures/index.ts +22 -0
- package/template/src/__tests__/fixtures/notes.ts +33 -0
- package/template/src/__tests__/mocks/miden-sdk-react.ts +261 -0
- package/template/src/__tests__/patterns/README.md +44 -0
- package/template/src/__tests__/patterns/mutation-hook.test.tsx +146 -0
- package/template/src/__tests__/patterns/provider-setup.test.tsx +77 -0
- package/template/src/__tests__/patterns/query-hook.test.tsx +143 -0
- package/template/src/{App.css → components/AppContent.css} +9 -9
- package/template/src/components/AppContent.tsx +80 -0
- package/template/src/components/ConfiguredCounter.tsx +48 -0
- package/template/src/components/Counter.css +27 -0
- package/template/src/components/Counter.tsx +16 -0
- package/template/src/components/__tests__/AppContent.test.tsx +274 -0
- package/template/src/components/__tests__/ConfiguredCounter.test.tsx +116 -0
- package/template/src/components/__tests__/Counter.test.tsx +44 -0
- package/template/src/config.ts +41 -0
- package/template/src/hooks/__tests__/useIncrementCounter.test.tsx +257 -0
- package/template/src/hooks/useIncrementCounter.ts +195 -0
- package/template/src/index.css +7 -0
- package/template/src/lib/miden.ts +9 -0
- package/template/src/main.tsx +6 -6
- package/template/src/providers.tsx +27 -0
- package/template/src/vite-env.d.ts +1 -0
- package/template/tsconfig.app.json +8 -4
- package/template/tsconfig.node.json +1 -3
- package/template/vite.config.ts +5 -17
- package/template/vitest.config.ts +25 -0
- package/template/vitest.setup.ts +1 -0
- package/template/yarn.lock +1687 -815
- package/template/src/miden/lib/demo.ts +0 -106
package/cli.js
CHANGED
package/package.json
CHANGED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
---
|
|
2
|
+
model: opus
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
You are a security-focused code reviewer specializing in web3, WASM, and TypeScript/Rust applications.
|
|
6
|
+
|
|
7
|
+
Review the code at: $ARGUMENTS
|
|
8
|
+
|
|
9
|
+
This can be a file, directory, or glob pattern. Read the relevant source files and perform a focused security audit across these dimensions, reporting only actual findings (skip any section with no issues):
|
|
10
|
+
|
|
11
|
+
## 1. Input Validation & Injection
|
|
12
|
+
- Are user-supplied strings (hex IDs, bech32 addresses, amounts) validated before being passed to WASM/Rust?
|
|
13
|
+
- Can malformed input cause panics in Rust that propagate as unhandled exceptions in JS?
|
|
14
|
+
- Are there any paths where unsanitized input reaches sensitive operations (key derivation, transaction building)?
|
|
15
|
+
- Could numeric inputs overflow (e.g., BigInt conversion from plain numbers)?
|
|
16
|
+
|
|
17
|
+
## 2. Key Material & Secrets
|
|
18
|
+
- Is key material ever logged, serialized to JSON, or exposed in error messages?
|
|
19
|
+
- Are private keys properly zeroed after use or held only in WASM memory?
|
|
20
|
+
- Could keystore callbacks leak secrets through exception messages or stack traces?
|
|
21
|
+
- Is seed material handled securely (not stored in localStorage, not logged)?
|
|
22
|
+
|
|
23
|
+
## 3. Transaction Safety
|
|
24
|
+
- Can transaction parameters be manipulated to send to unintended recipients?
|
|
25
|
+
- Are there TOCTOU (time-of-check-time-of-use) issues between building and submitting transactions?
|
|
26
|
+
- Could amount values be manipulated through type coercion (string→number precision loss)?
|
|
27
|
+
- Are note IDs validated to prevent consuming the wrong notes?
|
|
28
|
+
|
|
29
|
+
## 4. WASM Boundary
|
|
30
|
+
- Are there memory safety issues at the JS↔WASM boundary?
|
|
31
|
+
- Could JsValue conversions fail in ways that leave state inconsistent?
|
|
32
|
+
- Are WASM errors properly caught or could they crash the runtime?
|
|
33
|
+
- Is there potential for use-after-free when JS holds references to WASM objects?
|
|
34
|
+
|
|
35
|
+
## 5. State & Storage
|
|
36
|
+
- Is IndexedDB data integrity protected (could concurrent tabs corrupt state)?
|
|
37
|
+
- Are there race conditions in async operations that could lead to double-spends or stale reads?
|
|
38
|
+
- Could store export/import be used to replay old state maliciously?
|
|
39
|
+
- Are database transactions (Dexie) used appropriately for atomic operations?
|
|
40
|
+
|
|
41
|
+
## 6. Network & RPC
|
|
42
|
+
- Are RPC responses validated before being trusted?
|
|
43
|
+
- Could a malicious RPC endpoint feed incorrect block data or note metadata?
|
|
44
|
+
- Is TLS enforced for RPC and prover connections?
|
|
45
|
+
- Are there timing side-channels in how responses are processed?
|
|
46
|
+
|
|
47
|
+
## 7. Supply Chain & Dependencies
|
|
48
|
+
- Are there known vulnerable dependencies?
|
|
49
|
+
- Are lockfile integrity hashes (`package-lock.json` / `yarn.lock` / `pnpm-lock.yaml`) unchanged when WASM-bundled dependencies (e.g. `@miden-sdk/*`) update?
|
|
50
|
+
- Could wasm-bindgen-generated glue code introduce issues?
|
|
51
|
+
|
|
52
|
+
## Output Format
|
|
53
|
+
|
|
54
|
+
For each finding, use severity levels:
|
|
55
|
+
- **[CRITICAL]** - Exploitable vulnerability that could cause loss of funds or key compromise
|
|
56
|
+
- **[HIGH]** - Security issue that should be fixed before production use
|
|
57
|
+
- **[MEDIUM]** - Weakness that could be exploited under specific conditions
|
|
58
|
+
- **[LOW]** - Defense-in-depth improvement
|
|
59
|
+
- **[INFO]** - Observation worth noting but not directly exploitable
|
|
60
|
+
|
|
61
|
+
For each finding include:
|
|
62
|
+
1. File and line number
|
|
63
|
+
2. Description of the vulnerability
|
|
64
|
+
3. Attack scenario (how it could be exploited)
|
|
65
|
+
4. Recommended fix
|
|
66
|
+
|
|
67
|
+
End with a summary: count of findings by severity, and the top 3 highest-priority items to address.
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# Validates that required .masp contract artifacts exist in public/packages/.
|
|
3
|
+
# Run before build or as a standalone check.
|
|
4
|
+
|
|
5
|
+
cd "$CLAUDE_PROJECT_DIR" 2>/dev/null || cd "$(dirname "$0")/../.." || exit 1
|
|
6
|
+
|
|
7
|
+
PACKAGES_DIR="public/packages"
|
|
8
|
+
ERRORS=0
|
|
9
|
+
|
|
10
|
+
if [ ! -d "$PACKAGES_DIR" ]; then
|
|
11
|
+
echo "ERROR: $PACKAGES_DIR directory does not exist"
|
|
12
|
+
exit 1
|
|
13
|
+
fi
|
|
14
|
+
|
|
15
|
+
# Check for .masp files
|
|
16
|
+
MASP_FILES=$(find "$PACKAGES_DIR" -name "*.masp" 2>/dev/null)
|
|
17
|
+
|
|
18
|
+
if [ -z "$MASP_FILES" ]; then
|
|
19
|
+
echo "ERROR: No .masp files found in $PACKAGES_DIR"
|
|
20
|
+
echo "Run 'cargo miden build' in the contract project and copy .masp files here."
|
|
21
|
+
exit 1
|
|
22
|
+
fi
|
|
23
|
+
|
|
24
|
+
echo "Contract artifacts in $PACKAGES_DIR:"
|
|
25
|
+
while IFS= read -r file; do
|
|
26
|
+
SIZE=$(wc -c < "$file" | tr -d ' ')
|
|
27
|
+
if [ "$SIZE" -eq 0 ]; then
|
|
28
|
+
echo " ERROR: $file is empty (0 bytes)"
|
|
29
|
+
ERRORS=$((ERRORS + 1))
|
|
30
|
+
elif [ "$SIZE" -lt 100 ]; then
|
|
31
|
+
echo " WARN: $file is suspiciously small ($SIZE bytes)"
|
|
32
|
+
else
|
|
33
|
+
echo " OK: $file ($SIZE bytes)"
|
|
34
|
+
fi
|
|
35
|
+
done <<< "$MASP_FILES"
|
|
36
|
+
|
|
37
|
+
if [ $ERRORS -gt 0 ]; then
|
|
38
|
+
echo ""
|
|
39
|
+
echo "$ERRORS artifact(s) have errors. Rebuild contracts with 'cargo miden build'."
|
|
40
|
+
exit 1
|
|
41
|
+
fi
|
|
42
|
+
|
|
43
|
+
echo ""
|
|
44
|
+
echo "All artifacts valid."
|
|
45
|
+
exit 0
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# Post-edit hook: runs tests affected by changes since last commit.
|
|
3
|
+
# Only triggers for .ts/.tsx files in src/. Uses vitest --changed for detection.
|
|
4
|
+
|
|
5
|
+
INPUT=$(cat)
|
|
6
|
+
FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path // .tool_input.filePath // empty')
|
|
7
|
+
|
|
8
|
+
# Only trigger for TypeScript/React files in src/
|
|
9
|
+
if [[ -z "$FILE_PATH" ]] || [[ "$FILE_PATH" != *.ts && "$FILE_PATH" != *.tsx ]]; then
|
|
10
|
+
exit 0
|
|
11
|
+
fi
|
|
12
|
+
|
|
13
|
+
if [[ "$FILE_PATH" != *"/src/"* ]]; then
|
|
14
|
+
exit 0
|
|
15
|
+
fi
|
|
16
|
+
|
|
17
|
+
cd "$CLAUDE_PROJECT_DIR" || exit 0
|
|
18
|
+
|
|
19
|
+
# Run tests affected by uncommitted changes (non-watch mode, no coverage)
|
|
20
|
+
TEST_OUTPUT=$(npx vitest --changed --run 2>&1)
|
|
21
|
+
EXIT_CODE=$?
|
|
22
|
+
|
|
23
|
+
if [ $EXIT_CODE -eq 0 ]; then
|
|
24
|
+
echo '{"hookSpecificOutput": {"additionalContext": "Affected tests passed"}}'
|
|
25
|
+
exit 0
|
|
26
|
+
else
|
|
27
|
+
# Show last 30 lines of test output for context
|
|
28
|
+
TRIMMED=$(echo "$TEST_OUTPUT" | tail -30)
|
|
29
|
+
echo "{\"hookSpecificOutput\": {\"additionalContext\": \"Affected tests FAILED. Fix failing tests before continuing.\n$TRIMMED\"}}"
|
|
30
|
+
exit 2
|
|
31
|
+
fi
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# Post-edit hook: runs TypeScript type checking when .ts/.tsx files in src/ are modified.
|
|
3
|
+
# Reads JSON input from stdin (Claude Code hook protocol).
|
|
4
|
+
|
|
5
|
+
INPUT=$(cat)
|
|
6
|
+
FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path // .tool_input.filePath // empty')
|
|
7
|
+
|
|
8
|
+
# Only trigger for TypeScript/React files in src/
|
|
9
|
+
if [[ -z "$FILE_PATH" ]] || [[ "$FILE_PATH" != *.ts && "$FILE_PATH" != *.tsx ]]; then
|
|
10
|
+
exit 0
|
|
11
|
+
fi
|
|
12
|
+
|
|
13
|
+
if [[ "$FILE_PATH" != *"/src/"* ]]; then
|
|
14
|
+
exit 0
|
|
15
|
+
fi
|
|
16
|
+
|
|
17
|
+
# Run TypeScript type checking from project root
|
|
18
|
+
cd "$CLAUDE_PROJECT_DIR" || exit 0
|
|
19
|
+
|
|
20
|
+
if npx tsc -b --noEmit 2>&1; then
|
|
21
|
+
echo '{"hookSpecificOutput": {"additionalContext": "TypeScript type check passed"}}'
|
|
22
|
+
exit 0
|
|
23
|
+
else
|
|
24
|
+
CHECK_OUTPUT=$(npx tsc -b --noEmit 2>&1 | tail -30)
|
|
25
|
+
echo "{\"hookSpecificOutput\": {\"additionalContext\": \"TypeScript type check FAILED. Fix type errors before continuing.\n$CHECK_OUTPUT\"}}"
|
|
26
|
+
exit 2
|
|
27
|
+
fi
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
{
|
|
2
|
+
"hooks": {
|
|
3
|
+
"PostToolUse": [
|
|
4
|
+
{
|
|
5
|
+
"matcher": "Edit|Write",
|
|
6
|
+
"hooks": [
|
|
7
|
+
{
|
|
8
|
+
"type": "command",
|
|
9
|
+
"command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/typecheck.sh",
|
|
10
|
+
"timeout": 60,
|
|
11
|
+
"statusMessage": "Type checking modified files..."
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
"type": "command",
|
|
15
|
+
"command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/run-affected-tests.sh",
|
|
16
|
+
"timeout": 120,
|
|
17
|
+
"statusMessage": "Running affected tests..."
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
"type": "command",
|
|
21
|
+
"command": "cd \"$CLAUDE_PROJECT_DIR\" && npx vitest --run && npx tsc -b --noEmit && npx vite build",
|
|
22
|
+
"timeout": 300,
|
|
23
|
+
"statusMessage": "Running full test suite + type check + build..."
|
|
24
|
+
}
|
|
25
|
+
]
|
|
26
|
+
}
|
|
27
|
+
]
|
|
28
|
+
}
|
|
29
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
{
|
|
2
|
+
"permissions": {
|
|
3
|
+
"allow": [
|
|
4
|
+
"WebFetch(domain:github.com)",
|
|
5
|
+
"WebFetch(domain:api.github.com)",
|
|
6
|
+
"mcp__playwright__browser_navigate",
|
|
7
|
+
"mcp__playwright__browser_console_messages",
|
|
8
|
+
"mcp__playwright__browser_take_screenshot",
|
|
9
|
+
"WebFetch(domain:registry.npmjs.org)",
|
|
10
|
+
"mcp__playwright__browser_wait_for",
|
|
11
|
+
"mcp__playwright__browser_snapshot",
|
|
12
|
+
"mcp__playwright__browser_evaluate",
|
|
13
|
+
"WebFetch(domain:rpc.testnet.miden.io)",
|
|
14
|
+
"WebFetch(domain:testnet.midenscan.com)",
|
|
15
|
+
"WebFetch(domain:transport.miden.io)",
|
|
16
|
+
"mcp__playwright__browser_close",
|
|
17
|
+
"WebFetch(domain:scan-backend-testnet-miden.eu-central-8.gateway.fm)",
|
|
18
|
+
"WebFetch(domain:transport.devnet.miden.io)",
|
|
19
|
+
"WebFetch(domain:tx-prover.testnet.miden.io)",
|
|
20
|
+
"WebFetch(domain:registry.yarnpkg.com)",
|
|
21
|
+
"mcp__playwright__browser_network_requests"
|
|
22
|
+
]
|
|
23
|
+
}
|
|
24
|
+
}
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: frontend-pitfalls
|
|
3
|
+
description: Critical pitfalls and safety rules for Miden frontend development. Covers WASM initialization, concurrent access crashes, COOP/COEP headers, BigInt handling, Bech32 network mismatches, IndexedDB state loss, auto-sync side effects, Vite configuration, and React rendering race conditions. Use when reviewing, debugging, or writing Miden frontend code.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Miden Frontend Pitfalls
|
|
7
|
+
|
|
8
|
+
## FP1: WASM Initialization Race (CRITICAL)
|
|
9
|
+
|
|
10
|
+
Components that use Miden hooks before MidenProvider finishes WASM initialization will crash.
|
|
11
|
+
|
|
12
|
+
```tsx
|
|
13
|
+
// WRONG — crashes if WASM not ready
|
|
14
|
+
function App() {
|
|
15
|
+
const { data } = useAccounts(); // throws before init
|
|
16
|
+
return <div>{data?.wallets.length}</div>;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// CORRECT — use loadingComponent or check isReady
|
|
20
|
+
<MidenProvider
|
|
21
|
+
config={{ rpcUrl: "testnet" }}
|
|
22
|
+
loadingComponent={<p>Loading WASM...</p>}
|
|
23
|
+
>
|
|
24
|
+
<App />
|
|
25
|
+
</MidenProvider>
|
|
26
|
+
|
|
27
|
+
// CORRECT — guard with isReady
|
|
28
|
+
function App() {
|
|
29
|
+
const { isReady } = useMiden();
|
|
30
|
+
if (!isReady) return <p>Loading...</p>;
|
|
31
|
+
return <WalletView />;
|
|
32
|
+
}
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## FP2: Recursive WASM Access Crash (CRITICAL)
|
|
36
|
+
|
|
37
|
+
The WASM client is single-threaded. Concurrent calls crash with "recursive use of an object detected".
|
|
38
|
+
|
|
39
|
+
```tsx
|
|
40
|
+
// WRONG — two operations running simultaneously
|
|
41
|
+
const handleClick = async () => {
|
|
42
|
+
sync(); // fires async
|
|
43
|
+
await send({ ... }); // runs concurrently — CRASH
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
// CORRECT — use runExclusive for sequential execution
|
|
47
|
+
const client = useMidenClient();
|
|
48
|
+
const { runExclusive } = useMiden();
|
|
49
|
+
await runExclusive(async () => {
|
|
50
|
+
await client.syncState();
|
|
51
|
+
// now safe to do next operation
|
|
52
|
+
});
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
Built-in hooks (useSend, useConsume, etc.) already use runExclusive internally. This pitfall applies when using `useMidenClient()` directly or mixing manual client calls with hook mutations.
|
|
56
|
+
|
|
57
|
+
## FP3: COOP/COEP Headers Missing (CRITICAL)
|
|
58
|
+
|
|
59
|
+
WASM SharedArrayBuffer requires these headers. Without them, WASM init silently fails.
|
|
60
|
+
|
|
61
|
+
The template's source-of-truth pattern is to opt into COOP/COEP via the Vite plugin explicitly:
|
|
62
|
+
|
|
63
|
+
```ts
|
|
64
|
+
// vite.config.ts — template default
|
|
65
|
+
import { midenVitePlugin } from "@miden-sdk/vite-plugin";
|
|
66
|
+
|
|
67
|
+
export default defineConfig({
|
|
68
|
+
plugins: [react(), midenVitePlugin({ crossOriginIsolation: true })],
|
|
69
|
+
});
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
Do not rely on the plugin's own default — `@miden-sdk/vite-plugin` defaults `crossOriginIsolation` to `false`, and that default has shifted across releases. Pass `true` explicitly on any route that runs Miden client code.
|
|
73
|
+
|
|
74
|
+
COOP/COEP must also be set on the production server — the plugin only covers the Vite dev server. See `vite-wasm-setup` for per-host configs (Nginx, Vercel, Cloudflare).
|
|
75
|
+
|
|
76
|
+
**Gotcha (opt-out path)**: Cross-origin-isolation breaks third-party iframes, external scripts without CORS, and OAuth popups. If a route must host those and cannot use Miden client code, either (a) use `Cross-Origin-Embedder-Policy: credentialless` for weaker isolation that still allows most cross-origin resources, or (b) scope `crossOriginIsolation: false` to that specific route and accept that Miden operations won't work there. Do not disable isolation globally as a convenience.
|
|
77
|
+
|
|
78
|
+
## FP4: BigInt Type Mismatch (HIGH)
|
|
79
|
+
|
|
80
|
+
All token amounts in the SDK are `bigint`. Passing `number` causes TypeScript errors or runtime failures.
|
|
81
|
+
|
|
82
|
+
```tsx
|
|
83
|
+
// WRONG
|
|
84
|
+
await send({ from, to, assetId, amount: 1000 }); // number — fails
|
|
85
|
+
await createFaucet({ maxSupply: 1000000, ... }); // number — fails
|
|
86
|
+
|
|
87
|
+
// CORRECT
|
|
88
|
+
await send({ from, to, assetId, amount: 1000n }); // bigint literal
|
|
89
|
+
await createFaucet({ maxSupply: BigInt(1000000), ... }); // BigInt constructor
|
|
90
|
+
|
|
91
|
+
// CORRECT — use parseAssetAmount for user input
|
|
92
|
+
import { parseAssetAmount } from "@miden-sdk/react";
|
|
93
|
+
const amount = parseAssetAmount(inputValue, 8); // string → bigint
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
**Gotcha**: `JSON.stringify` cannot serialize `bigint`. Use a custom replacer or convert to string first.
|
|
97
|
+
|
|
98
|
+
## FP5: Bech32 Network Mismatch (HIGH)
|
|
99
|
+
|
|
100
|
+
Bech32-encoded account IDs include the network. A devnet address on testnet points to a different or nonexistent account.
|
|
101
|
+
|
|
102
|
+
```tsx
|
|
103
|
+
// WRONG — hardcoding a bech32 address used across networks
|
|
104
|
+
const ADMIN = "miden1qy35..."; // this is network-specific!
|
|
105
|
+
|
|
106
|
+
// CORRECT — use hex format for cross-network compatibility
|
|
107
|
+
const ADMIN = "0x1234567890abcdef";
|
|
108
|
+
|
|
109
|
+
// CORRECT — derive bech32 per network
|
|
110
|
+
account.bech32id(); // returns correct bech32 for current network
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
Both hex and bech32 formats work in all hooks. Prefer hex for constants, bech32 for display.
|
|
114
|
+
|
|
115
|
+
## FP6: Auto-Sync Side Effects (MEDIUM)
|
|
116
|
+
|
|
117
|
+
Default `autoSyncInterval` is 15000ms (15 seconds). Each sync triggers re-renders in useAccounts, useAccount, useNotes, etc.
|
|
118
|
+
|
|
119
|
+
```tsx
|
|
120
|
+
// PROBLEM — form resets every 15 seconds because parent re-renders
|
|
121
|
+
<MidenProvider config={{ rpcUrl: "testnet" }}>
|
|
122
|
+
<SendForm /> {/* re-renders on every sync */}
|
|
123
|
+
</MidenProvider>
|
|
124
|
+
|
|
125
|
+
// SOLUTION 1 — preferred: use stable keys and memoization
|
|
126
|
+
const MemoizedForm = React.memo(SendForm);
|
|
127
|
+
|
|
128
|
+
// SOLUTION 2 — disable auto-sync for manual control
|
|
129
|
+
<MidenProvider config={{ rpcUrl: "testnet", autoSyncInterval: 0 }}>
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
## FP7: IndexedDB State Loss (MEDIUM)
|
|
133
|
+
|
|
134
|
+
The client persists accounts, keys, and notes in IndexedDB. Browser "Clear site data", private browsing, or storage pressure can delete everything.
|
|
135
|
+
|
|
136
|
+
- Warn users that clearing browser data deletes their wallet
|
|
137
|
+
- Consider external signers (Para, Turnkey) for production — keys are server-side
|
|
138
|
+
- Implement account export/backup for local keystore users
|
|
139
|
+
|
|
140
|
+
## FP8: Vite Configuration Requirements (MEDIUM)
|
|
141
|
+
|
|
142
|
+
The `@miden-sdk/vite-plugin` package handles all Miden-specific Vite config. The template's source-of-truth pattern — which you should copy for any new Miden app — is:
|
|
143
|
+
|
|
144
|
+
```ts
|
|
145
|
+
import { midenVitePlugin } from "@miden-sdk/vite-plugin";
|
|
146
|
+
|
|
147
|
+
export default defineConfig({
|
|
148
|
+
plugins: [react(), midenVitePlugin({ crossOriginIsolation: true })],
|
|
149
|
+
});
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
`midenVitePlugin()` handles WASM loading, top-level await, pre-bundling exclusion, and — when `crossOriginIsolation: true` is passed — emits the COOP `same-origin` + COEP `require-corp` headers the SDK requires for `SharedArrayBuffer`.
|
|
153
|
+
|
|
154
|
+
| Option | Plugin default | Template default | Purpose |
|
|
155
|
+
|--------|----------------|------------------|---------|
|
|
156
|
+
| `crossOriginIsolation` | `false` | **`true`** | Emit COOP/COEP headers for SharedArrayBuffer |
|
|
157
|
+
|
|
158
|
+
Always pass `crossOriginIsolation: true` explicitly. The plugin's `false` default is wrong for a Miden app, and relying on it risks silent WASM-init failures if the default shifts in a future release. The opt-out path (routes that host OAuth popups or cross-origin iframes incompatible with isolation) is discussed in FP3. For production, set the same headers at the server level — see `vite-wasm-setup` for host-specific configs.
|
|
159
|
+
|
|
160
|
+
## FP9: React StrictMode Double-Init (LOW)
|
|
161
|
+
|
|
162
|
+
React 19 StrictMode double-invokes effects in development. MidenProvider handles this via `isInitializedRef`, but direct `WasmWebClient.createClient()` calls will initialize twice. (The SDK exports `WasmWebClient` as `WebClient` for convenience.)
|
|
163
|
+
|
|
164
|
+
```tsx
|
|
165
|
+
// WRONG — manual client creation in useEffect
|
|
166
|
+
useEffect(() => {
|
|
167
|
+
const client = await WasmWebClient.createClient(url); // called twice in dev
|
|
168
|
+
}, []);
|
|
169
|
+
|
|
170
|
+
// CORRECT — always use MidenProvider
|
|
171
|
+
<MidenProvider config={{ rpcUrl: "testnet" }}>
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
## Quick Reference
|
|
175
|
+
|
|
176
|
+
| # | Pitfall | Severity | One-Line Rule |
|
|
177
|
+
|---|---------|----------|---------------|
|
|
178
|
+
| FP1 | WASM init race | CRITICAL | Use loadingComponent or check isReady |
|
|
179
|
+
| FP2 | Recursive WASM | CRITICAL | Use runExclusive() for all direct client access |
|
|
180
|
+
| FP3 | COOP/COEP | CRITICAL | Add headers in vite.config.ts AND production server |
|
|
181
|
+
| FP4 | BigInt | HIGH | All amounts are bigint (1000n not 1000) |
|
|
182
|
+
| FP5 | Bech32 mismatch | HIGH | Match network in rpcUrl and addresses |
|
|
183
|
+
| FP6 | Auto-sync | MEDIUM | Set autoSyncInterval: 0 if UI stability matters |
|
|
184
|
+
| FP7 | IndexedDB loss | MEDIUM | Warn users; use external signers for production |
|
|
185
|
+
| FP8 | Vite config | MEDIUM | Always pass `midenVitePlugin({ crossOriginIsolation: true })` — don't rely on the plugin default |
|
|
186
|
+
| FP9 | StrictMode | LOW | Use MidenProvider, not manual client creation |
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: frontend-source-guide
|
|
3
|
+
description: Guide for advanced Miden frontend development using source repo exploration. Covers AI development practices (Plan Mode, verification-driven development, context engineering, sub-agents) and maps the miden-client source repository for discovering advanced patterns. Use when building complex applications beyond basic hook usage, implementing custom signers, working with WasmWebClient directly, or troubleshooting SDK internals.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Advanced Miden Frontend Development: Source-Guided Context Engineering
|
|
7
|
+
|
|
8
|
+
## Development Approach
|
|
9
|
+
|
|
10
|
+
### 1. Plan Mode First
|
|
11
|
+
|
|
12
|
+
For any non-trivial frontend application, start in Plan Mode before writing code.
|
|
13
|
+
|
|
14
|
+
- Explore React SDK source and examples to understand available patterns
|
|
15
|
+
- Design the component hierarchy, data flow, and which hooks to use
|
|
16
|
+
- Identify which built-in hooks cover your needs vs what requires direct WasmWebClient access
|
|
17
|
+
- Map out the user flow: account creation, token operations, note handling
|
|
18
|
+
|
|
19
|
+
Rule of thumb: if the task involves custom transactions, external signers, or patterns not covered by the basic skills, plan first.
|
|
20
|
+
|
|
21
|
+
### 2. Verification-Driven Development
|
|
22
|
+
|
|
23
|
+
This is the single highest-leverage practice for AI-assisted frontend development.
|
|
24
|
+
|
|
25
|
+
**Type check loop**: After every file edit, run `npx tsc -b --noEmit`. The project's type check hook does this automatically. If types fail:
|
|
26
|
+
1. Read the error message
|
|
27
|
+
2. Search the React SDK source for the correct type signature or hook usage
|
|
28
|
+
3. Adapt the working pattern to your use case
|
|
29
|
+
4. Recheck
|
|
30
|
+
|
|
31
|
+
**Dev server loop**: Run `npm run dev` and check the browser. When something fails:
|
|
32
|
+
1. Check the browser console for WASM errors, network errors, or React errors
|
|
33
|
+
2. For WASM errors: check COOP/COEP headers and Vite config (see frontend-pitfalls skill)
|
|
34
|
+
3. For unexpected behavior: compare your code against the example wallet in the React SDK
|
|
35
|
+
|
|
36
|
+
Never submit code that doesn't type-check. The verification loop is your quality guarantee.
|
|
37
|
+
|
|
38
|
+
### 3. Context Engineering with Source Repos
|
|
39
|
+
|
|
40
|
+
The basic skills (react-sdk-patterns, frontend-pitfalls, vite-wasm-setup) cover standard patterns. For anything beyond those patterns, the miden-client source repository is the knowledge base.
|
|
41
|
+
|
|
42
|
+
**How to use source repos effectively**:
|
|
43
|
+
- Don't load entire repos into context. Use sub-agents to explore — they search, read relevant files, and summarize findings without filling the main conversation context.
|
|
44
|
+
- Read source files only when you need a specific answer (progressive disclosure)
|
|
45
|
+
- Look for working examples first, then adapt. The example wallet app is the most reliable reference.
|
|
46
|
+
- When you find a useful pattern in source, extract just what you need — the exact hook call, the exact type, the exact provider setup.
|
|
47
|
+
|
|
48
|
+
**Using sub-agents for exploration**:
|
|
49
|
+
- Launch an explore sub-agent with a specific question: "Find how useSwap handles the payback note type in the React SDK"
|
|
50
|
+
- The sub-agent searches, reads the relevant files, and returns a focused summary
|
|
51
|
+
- Your main context stays clean for implementation
|
|
52
|
+
|
|
53
|
+
### 4. Iterative Frontend Development
|
|
54
|
+
|
|
55
|
+
Break complex applications into stages. Complete each before starting the next:
|
|
56
|
+
|
|
57
|
+
1. **Design** (Plan Mode) — Component hierarchy, data flow, hook selection
|
|
58
|
+
2. **Provider setup** — MidenProvider config, signer integration if needed
|
|
59
|
+
3. **Query components** — Account display, balance rendering, note lists
|
|
60
|
+
4. **Mutation components** — Send forms, mint buttons, consume flows
|
|
61
|
+
5. **Transaction UX** — Stage progress, error handling, loading states
|
|
62
|
+
6. **Polish** — Auto-sync tuning, memoization, edge cases
|
|
63
|
+
|
|
64
|
+
When stuck at any stage: search the React SDK source for a similar working pattern. Adapt it, don't guess.
|
|
65
|
+
|
|
66
|
+
---
|
|
67
|
+
|
|
68
|
+
## Miden Source Repository Map
|
|
69
|
+
|
|
70
|
+
Clone this repo alongside your project for reference. Claude will explore it when needed for advanced patterns.
|
|
71
|
+
|
|
72
|
+
```bash
|
|
73
|
+
# Contains React SDK source, WasmWebClient WASM bindings, and working examples
|
|
74
|
+
git clone --depth 1 https://github.com/0xMiden/miden-client.git ../miden-client
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
### `packages/react-sdk/` — React SDK Source
|
|
78
|
+
|
|
79
|
+
The primary reference for all frontend development.
|
|
80
|
+
|
|
81
|
+
- **`src/hooks/`** — All 18+ hook implementations. Each file is self-contained. Read these to understand exact parameters, error handling, and stage progression.
|
|
82
|
+
- **`src/context/MidenProvider.tsx`** — Client initialization, sync loop, signer detection, runExclusive lock. Read this to understand initialization order.
|
|
83
|
+
- **`src/context/SignerContext.ts`** — External signer interface. Read this when implementing custom signers.
|
|
84
|
+
- **`src/store/MidenStore.ts`** — Zustand store structure. Read this to understand cached state and what triggers re-renders.
|
|
85
|
+
- **`src/utils/`** — Utility implementations (amounts, notes, bech32, runExclusive, accountParsing).
|
|
86
|
+
- **`src/types/index.ts`** — All TypeScript interfaces. The single source of truth for option types, result types, and configuration.
|
|
87
|
+
- **`examples/wallet/`** — Complete working wallet app. The most reliable reference for how to set up MidenProvider, create accounts, display balances, claim notes, and send tokens.
|
|
88
|
+
|
|
89
|
+
**Explore when**: Writing any new component, understanding exact hook behavior, finding how a specific feature works, debugging unexpected behavior.
|
|
90
|
+
|
|
91
|
+
### `crates/web-client/` — WASM Client Bindings
|
|
92
|
+
|
|
93
|
+
The Rust-to-WASM bridge that the React SDK wraps.
|
|
94
|
+
|
|
95
|
+
- Contains the `WasmWebClient` struct and all methods available via `useMidenClient()`
|
|
96
|
+
- JavaScript bindings in `js/` directory
|
|
97
|
+
|
|
98
|
+
**Explore when**: A hook doesn't exist for your operation, understanding what WasmWebClient methods are available, debugging WASM-level errors.
|
|
99
|
+
|
|
100
|
+
### `crates/idxdb-store/` — IndexedDB Persistence
|
|
101
|
+
|
|
102
|
+
The browser storage layer for accounts, keys, notes, and transaction history.
|
|
103
|
+
|
|
104
|
+
**Explore when**: Debugging data persistence issues, understanding what's stored in IndexedDB, investigating storage isolation for external signers.
|
|
105
|
+
|
|
106
|
+
---
|
|
107
|
+
|
|
108
|
+
## What to Explore for Each Pattern
|
|
109
|
+
|
|
110
|
+
| Building This | Explore These Paths | What to Look For |
|
|
111
|
+
|---|---|---|
|
|
112
|
+
| Basic wallet UI | `examples/wallet/` | MidenProvider setup, useAccounts, useSend |
|
|
113
|
+
| Custom transaction | `src/hooks/useTransaction.ts` | Request factory pattern, client methods |
|
|
114
|
+
| External signer | `src/context/SignerContext.ts` | SignerContextValue interface, signCb |
|
|
115
|
+
| Note consumption flow | `src/hooks/useConsume.ts` | NoteId parsing, filter construction |
|
|
116
|
+
| Swap UI | `src/hooks/useSwap.ts` | Swap options, dual note types |
|
|
117
|
+
| Token display | `src/utils/amounts.ts` | formatAssetAmount, parseAssetAmount |
|
|
118
|
+
| Account ID formatting | `src/utils/accountBech32.ts` | toBech32AccountId |
|
|
119
|
+
| State management | `src/store/MidenStore.ts` | Zustand selectors, cached state |
|
|
120
|
+
| Direct WasmWebClient usage | `src/context/MidenProvider.tsx` | useMidenClient(), runExclusive |
|
|
121
|
+
| Multi-step workflow | `src/hooks/useWaitForCommit.ts`, `useWaitForNotes.ts` | Polling, timeout patterns |
|
|
122
|
+
|
|
123
|
+
---
|
|
124
|
+
|
|
125
|
+
## Common Advanced Patterns
|
|
126
|
+
|
|
127
|
+
### Custom Hooks Wrapping WasmWebClient
|
|
128
|
+
For operations not covered by built-in hooks, create custom hooks that use useMidenClient() and runExclusive:
|
|
129
|
+
```tsx
|
|
130
|
+
function useBlockHeader(blockNumber: number) {
|
|
131
|
+
const client = useMidenClient();
|
|
132
|
+
const { runExclusive } = useMiden();
|
|
133
|
+
const [data, setData] = useState(null);
|
|
134
|
+
useEffect(() => {
|
|
135
|
+
// Note: runExclusive() may be simplified in a future SDK version.
|
|
136
|
+
// Check SDK changelog when upgrading.
|
|
137
|
+
runExclusive(async () => {
|
|
138
|
+
const header = await client.getBlockHeaderByNumber(blockNumber);
|
|
139
|
+
setData(header);
|
|
140
|
+
});
|
|
141
|
+
}, [blockNumber]);
|
|
142
|
+
return data;
|
|
143
|
+
}
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
### Multi-Step Workflows
|
|
147
|
+
Compose hooks for complex flows (mint → wait for commit → sync → consume):
|
|
148
|
+
```tsx
|
|
149
|
+
const { mint } = useMint();
|
|
150
|
+
const { waitForCommit } = useWaitForCommit();
|
|
151
|
+
const { waitForConsumableNotes } = useWaitForNotes();
|
|
152
|
+
const { consume } = useConsume();
|
|
153
|
+
|
|
154
|
+
const mintAndConsume = async () => {
|
|
155
|
+
const { transactionId } = await mint({ targetAccountId, faucetId, amount });
|
|
156
|
+
await waitForCommit(transactionId);
|
|
157
|
+
await waitForConsumableNotes({ accountId: targetAccountId });
|
|
158
|
+
await consume({ accountId: targetAccountId, notes: [...] });
|
|
159
|
+
};
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
### Custom Signer Implementation
|
|
163
|
+
Implement the SignerContextValue interface, wrap MidenProvider in your provider. Reference `src/context/SignerContext.ts` for the exact interface contract. The `storeName` field must be unique per user to ensure IndexedDB isolation.
|