create-miden-app 1.0.4 → 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 -1
  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 +1580 -781
  83. package/template/src/miden/lib/demo.ts +0 -106
@@ -0,0 +1,136 @@
1
+ import { useMemo, useState, useCallback, useEffect } from "react";
2
+ import {
3
+ useSyncState,
4
+ useAccount,
5
+ useImportAccount,
6
+ } from "@miden-sdk/react";
7
+ import {
8
+ useWallet,
9
+ Transaction,
10
+ } from "@miden-sdk/miden-wallet-adapter";
11
+ import {
12
+ TransactionRequestBuilder,
13
+ Package,
14
+ NoteScript,
15
+ Note,
16
+ NoteAssets,
17
+ NoteMetadata,
18
+ NoteRecipient,
19
+ NoteInputs,
20
+ NoteTag,
21
+ NoteType,
22
+ NoteAttachment,
23
+ NoteExecutionHint,
24
+ OutputNote,
25
+ OutputNoteArray,
26
+ AccountId,
27
+ Felt,
28
+ FeltArray,
29
+ Word,
30
+ } from "@miden-sdk/miden-sdk";
31
+ import { randomWord } from "@/lib/miden";
32
+ import {
33
+ COUNTER_SLOT_NAME,
34
+ EXPLORER_BASE_URL,
35
+ NETWORK_SYNC_DELAY_MS,
36
+ } from "@/config";
37
+
38
+ export function useIncrementCounter(counterAddress: string) {
39
+ const [error, setError] = useState<string | null>(null);
40
+ const [isSubmitting, setIsSubmitting] = useState(false);
41
+ const [isWaiting, setIsWaiting] = useState(false);
42
+
43
+ const { address: walletAddress, connected, requestTransaction } = useWallet();
44
+ const { importAccount } = useImportAccount();
45
+ const { account, refetch } = useAccount(counterAddress);
46
+ const { sync } = useSyncState();
47
+
48
+ // Import the counter account so the local client tracks it.
49
+ // The catch is intentional — the account may already be imported.
50
+ useEffect(() => {
51
+ importAccount({ type: "id", accountId: counterAddress }).catch(() => {});
52
+ }, [importAccount, counterAddress]);
53
+
54
+ // Read count from StorageMap
55
+ const count = useMemo(() => {
56
+ if (!account) return null;
57
+ const countKey = Word.newFromFelts([
58
+ new Felt(0n),
59
+ new Felt(0n),
60
+ new Felt(0n),
61
+ new Felt(1n),
62
+ ]);
63
+ const value = account.storage().getMapItem(COUNTER_SLOT_NAME, countKey);
64
+ return value ? Number(value.toU64s()[3]) : 0;
65
+ }, [account]);
66
+
67
+ const increment = useCallback(async () => {
68
+ if (!walletAddress || !requestTransaction) return;
69
+ setError(null);
70
+ setIsSubmitting(true);
71
+ try {
72
+ // Load pre-compiled increment-note package
73
+ const buf = await fetch("/packages/increment_note.masp").then((r) =>
74
+ r.arrayBuffer(),
75
+ );
76
+ const pkg = Package.deserialize(new Uint8Array(buf));
77
+ const noteScript = NoteScript.fromPackage(pkg);
78
+
79
+ const counterAccountId = AccountId.fromBech32(counterAddress);
80
+ const walletAccountId = AccountId.fromBech32(walletAddress);
81
+
82
+ // Build note recipient
83
+ const serialNum = randomWord();
84
+ const inputs = new NoteInputs(new FeltArray());
85
+ const recipient = new NoteRecipient(serialNum, noteScript, inputs);
86
+
87
+ // Build note metadata targeting the network counter account
88
+ const tag = NoteTag.withAccountTarget(counterAccountId);
89
+ const attachment = NoteAttachment.newNetworkAccountTarget(
90
+ counterAccountId,
91
+ NoteExecutionHint.always(),
92
+ );
93
+ const metadata = new NoteMetadata(
94
+ walletAccountId,
95
+ NoteType.Public,
96
+ tag,
97
+ ).withAttachment(attachment);
98
+
99
+ // Assemble the note and submit via wallet adapter
100
+ const note = new Note(new NoteAssets(), metadata, recipient);
101
+ const outputNote = OutputNote.full(note);
102
+ const txRequest = new TransactionRequestBuilder()
103
+ .withOwnOutputNotes(new OutputNoteArray([outputNote]))
104
+ .build();
105
+
106
+ const tx = Transaction.createCustomTransaction(
107
+ walletAddress,
108
+ counterAddress,
109
+ txRequest,
110
+ );
111
+ await requestTransaction(tx);
112
+ setIsSubmitting(false);
113
+
114
+ // Wait for network to process the note, then re-sync
115
+ setIsWaiting(true);
116
+ await new Promise((r) => setTimeout(r, NETWORK_SYNC_DELAY_MS));
117
+ await sync();
118
+ await refetch();
119
+ setIsWaiting(false);
120
+ } catch (err) {
121
+ setIsSubmitting(false);
122
+ setIsWaiting(false);
123
+ setError(err instanceof Error ? err.message : String(err));
124
+ }
125
+ }, [walletAddress, requestTransaction, counterAddress, sync, refetch]);
126
+
127
+ return {
128
+ increment,
129
+ count,
130
+ isSubmitting,
131
+ isWaiting,
132
+ error,
133
+ walletConnected: connected,
134
+ explorerUrl: `${EXPLORER_BASE_URL}/account/${counterAddress}`,
135
+ };
136
+ }
@@ -54,6 +54,13 @@ button:focus-visible {
54
54
  outline: 4px auto -webkit-focus-ring-color;
55
55
  }
56
56
 
57
+ #root {
58
+ max-width: 1280px;
59
+ margin: 0 auto;
60
+ padding: 2rem;
61
+ text-align: center;
62
+ }
63
+
57
64
  @media (prefers-color-scheme: light) {
58
65
  :root {
59
66
  color: #213547;
@@ -0,0 +1,9 @@
1
+ import { Felt, Word } from "@miden-sdk/miden-sdk";
2
+
3
+ /** Generate a random 4-felt Word (used as note serial number). */
4
+ export function randomWord(): Word {
5
+ const felts = Array.from({ length: 4 }, () =>
6
+ new Felt(BigInt(Math.floor(Math.random() * 2 ** 32))),
7
+ );
8
+ return Word.newFromFelts(felts);
9
+ }
@@ -1,10 +1,10 @@
1
- import { StrictMode } from 'react'
2
- import { createRoot } from 'react-dom/client'
3
- import './index.css'
4
- import App from './App.tsx'
1
+ import { StrictMode } from "react";
2
+ import { createRoot } from "react-dom/client";
3
+ import "./index.css";
4
+ import App from "./App.tsx";
5
5
 
6
- createRoot(document.getElementById('root')!).render(
6
+ createRoot(document.getElementById("root")!).render(
7
7
  <StrictMode>
8
8
  <App />
9
9
  </StrictMode>,
10
- )
10
+ );
@@ -0,0 +1,31 @@
1
+ import { useMemo, type ReactNode } from "react";
2
+ import { MidenProvider } from "@miden-sdk/react";
3
+ import {
4
+ MidenWalletAdapter,
5
+ WalletProvider,
6
+ WalletModalProvider,
7
+ } from "@miden-sdk/miden-wallet-adapter";
8
+ import "@miden-sdk/miden-wallet-adapter/styles.css";
9
+ import { APP_NAME, MIDEN_RPC_URL, MIDEN_PROVER } from "@/config";
10
+
11
+ export function AppProviders({ children }: { children: ReactNode }) {
12
+ const wallets = useMemo(
13
+ () => [new MidenWalletAdapter({ appName: APP_NAME })],
14
+ [],
15
+ );
16
+
17
+ return (
18
+ <WalletProvider wallets={wallets} autoConnect>
19
+ <WalletModalProvider>
20
+ <MidenProvider
21
+ config={{ rpcUrl: MIDEN_RPC_URL, prover: MIDEN_PROVER }}
22
+ loadingComponent={
23
+ <div className="loading">Loading Miden WASM...</div>
24
+ }
25
+ >
26
+ {children}
27
+ </MidenProvider>
28
+ </WalletModalProvider>
29
+ </WalletProvider>
30
+ );
31
+ }
@@ -0,0 +1 @@
1
+ /// <reference types="vite/client" />
@@ -5,7 +5,7 @@
5
5
  "useDefineForClassFields": true,
6
6
  "lib": ["ES2022", "DOM", "DOM.Iterable"],
7
7
  "module": "ESNext",
8
- "types": ["vite/client"],
8
+ "types": ["vite/client", "vitest/globals", "@testing-library/jest-dom/vitest"],
9
9
  "skipLibCheck": true,
10
10
 
11
11
  /* Bundler mode */
@@ -16,13 +16,17 @@
16
16
  "noEmit": true,
17
17
  "jsx": "react-jsx",
18
18
 
19
+ /* Path aliases */
20
+ "baseUrl": ".",
21
+ "paths": {
22
+ "@/*": ["./src/*"]
23
+ },
24
+
19
25
  /* Linting */
20
26
  "strict": true,
21
27
  "noUnusedLocals": true,
22
28
  "noUnusedParameters": true,
23
- "erasableSyntaxOnly": true,
24
- "noFallthroughCasesInSwitch": true,
25
- "noUncheckedSideEffectImports": true
29
+ "noFallthroughCasesInSwitch": true
26
30
  },
27
31
  "include": ["src"]
28
32
  }
@@ -18,9 +18,7 @@
18
18
  "strict": true,
19
19
  "noUnusedLocals": true,
20
20
  "noUnusedParameters": true,
21
- "erasableSyntaxOnly": true,
22
- "noFallthroughCasesInSwitch": true,
23
- "noUncheckedSideEffectImports": true
21
+ "noFallthroughCasesInSwitch": true
24
22
  },
25
23
  "include": ["vite.config.ts"]
26
24
  }
@@ -1,26 +1,14 @@
1
+ import path from "node:path";
1
2
  import { defineConfig } from "vite";
2
3
  import react from "@vitejs/plugin-react";
3
- import wasm from "vite-plugin-wasm";
4
- import topLevelAwait from "vite-plugin-top-level-await";
5
- import path from "path";
4
+ import { midenVitePlugin } from "@miden-sdk/vite-plugin";
6
5
 
7
- // https://vite.dev/config/
8
6
  export default defineConfig({
9
- plugins: [react(), wasm(), topLevelAwait()],
7
+ plugins: [react(), midenVitePlugin()],
10
8
  resolve: {
9
+ dedupe: ["react", "react-dom", "react/jsx-runtime"],
11
10
  alias: {
12
- // Help resolve dexie when imported from linked packages
13
- dexie: path.resolve(__dirname, "node_modules/dexie"),
14
- },
15
- },
16
- optimizeDeps: {
17
- exclude: ["@miden-sdk/miden-sdk"],
18
- include: ["dexie"],
19
- },
20
- server: {
21
- headers: {
22
- "Cross-Origin-Opener-Policy": "same-origin",
23
- "Cross-Origin-Embedder-Policy": "require-corp",
11
+ "@": path.resolve(__dirname, "./src"),
24
12
  },
25
13
  },
26
14
  });
@@ -0,0 +1,26 @@
1
+ import path from "node:path";
2
+ import { defineConfig } from "vitest/config";
3
+ import react from "@vitejs/plugin-react";
4
+
5
+ export default defineConfig({
6
+ plugins: [react()],
7
+ resolve: {
8
+ alias: {
9
+ "@": path.resolve(__dirname, "./src"),
10
+ },
11
+ },
12
+ test: {
13
+ environment: "jsdom",
14
+ globals: true,
15
+ setupFiles: ["./vitest.setup.ts"],
16
+ include: ["src/**/*.{test,spec}.{ts,tsx}"],
17
+ passWithNoTests: true,
18
+ server: {
19
+ deps: {
20
+ // The wallet-adapter-reactui sub-package has incorrect exports in package.json.
21
+ // Tests mock the wallet adapter at the module level, so externalizing is safe.
22
+ external: [/@miden-sdk\/miden-wallet-adapter/],
23
+ },
24
+ },
25
+ },
26
+ });
@@ -0,0 +1 @@
1
+ import "@testing-library/jest-dom/vitest";