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,27 @@
1
+ .card {
2
+ padding: 2em;
3
+ }
4
+
5
+ .counter-button {
6
+ font-size: 1.2em;
7
+ padding: 0.8em 1.6em;
8
+ }
9
+
10
+ .account-id {
11
+ font-size: 0.8em;
12
+ color: #888;
13
+ font-family: monospace;
14
+ margin-top: 0.5rem;
15
+ text-decoration: none;
16
+ }
17
+
18
+ .account-id:hover {
19
+ text-decoration: underline;
20
+ }
21
+
22
+ .error {
23
+ color: #ff4444;
24
+ font-size: 0.9em;
25
+ margin-top: 0.5rem;
26
+ word-break: break-word;
27
+ }
@@ -0,0 +1,45 @@
1
+ import { useIncrementCounter } from "@/hooks/useIncrementCounter";
2
+ import { COUNTER_ADDRESS } from "@/config";
3
+ import "./Counter.css";
4
+
5
+ export function Counter() {
6
+ const {
7
+ increment,
8
+ count,
9
+ isSubmitting,
10
+ isWaiting,
11
+ error,
12
+ walletConnected,
13
+ explorerUrl,
14
+ } = useIncrementCounter(COUNTER_ADDRESS);
15
+
16
+ const busy = isSubmitting || isWaiting;
17
+ const buttonLabel = isSubmitting
18
+ ? "Submitting..."
19
+ : isWaiting
20
+ ? "Waiting for network..."
21
+ : `count is ${count ?? "..."}`;
22
+
23
+ return (
24
+ <div className="card">
25
+ <button
26
+ className="counter-button"
27
+ onClick={increment}
28
+ disabled={busy || count === null || !walletConnected}
29
+ >
30
+ {buttonLabel}
31
+ </button>
32
+ <p>
33
+ <a
34
+ href={explorerUrl}
35
+ target="_blank"
36
+ rel="noreferrer"
37
+ className="account-id"
38
+ >
39
+ Counter: {COUNTER_ADDRESS}
40
+ </a>
41
+ </p>
42
+ {error && <p className="error">{error}</p>}
43
+ </div>
44
+ );
45
+ }
@@ -0,0 +1,21 @@
1
+ // Network counter account deployed on Miden testnet
2
+ export const COUNTER_ADDRESS = "mtst1aru8adnrqspgcsr3drk2n990lyc070ll";
3
+
4
+ // StorageMap slot name for the counter
5
+ export const COUNTER_SLOT_NAME =
6
+ "miden::component::miden_counter_account::count_map";
7
+
8
+ // Block explorer base URL
9
+ export const EXPLORER_BASE_URL = "https://testnet.midenscan.com";
10
+
11
+ // Delay (ms) to wait for the network to process a note before re-syncing
12
+ export const NETWORK_SYNC_DELAY_MS = 10_000;
13
+
14
+ // Application display name (used by wallet adapter)
15
+ export const APP_NAME = "Miden Template";
16
+
17
+ // Miden SDK configuration — override via environment variables
18
+ export const MIDEN_RPC_URL =
19
+ import.meta.env.VITE_MIDEN_RPC_URL ?? "testnet";
20
+ export const MIDEN_PROVER =
21
+ (import.meta.env.VITE_MIDEN_PROVER as "testnet" | "local") ?? "testnet";
@@ -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 "@demox-labs/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
+ }
@@ -0,0 +1,75 @@
1
+ :root {
2
+ font-family: system-ui, Avenir, Helvetica, Arial, sans-serif;
3
+ line-height: 1.5;
4
+ font-weight: 400;
5
+
6
+ color-scheme: light dark;
7
+ color: rgba(255, 255, 255, 0.87);
8
+ background-color: #242424;
9
+
10
+ font-synthesis: none;
11
+ text-rendering: optimizeLegibility;
12
+ -webkit-font-smoothing: antialiased;
13
+ -moz-osx-font-smoothing: grayscale;
14
+ }
15
+
16
+ a {
17
+ font-weight: 500;
18
+ color: #646cff;
19
+ text-decoration: inherit;
20
+ }
21
+ a:hover {
22
+ color: #535bf2;
23
+ }
24
+
25
+ body {
26
+ margin: 0;
27
+ display: flex;
28
+ place-items: center;
29
+ min-width: 320px;
30
+ min-height: 100vh;
31
+ }
32
+
33
+ h1 {
34
+ font-size: 3.2em;
35
+ line-height: 1.1;
36
+ }
37
+
38
+ button {
39
+ border-radius: 8px;
40
+ border: 1px solid transparent;
41
+ padding: 0.6em 1.2em;
42
+ font-size: 1em;
43
+ font-weight: 500;
44
+ font-family: inherit;
45
+ background-color: #1a1a1a;
46
+ cursor: pointer;
47
+ transition: border-color 0.25s;
48
+ }
49
+ button:hover {
50
+ border-color: #646cff;
51
+ }
52
+ button:focus,
53
+ button:focus-visible {
54
+ outline: 4px auto -webkit-focus-ring-color;
55
+ }
56
+
57
+ #root {
58
+ max-width: 1280px;
59
+ margin: 0 auto;
60
+ padding: 2rem;
61
+ text-align: center;
62
+ }
63
+
64
+ @media (prefers-color-scheme: light) {
65
+ :root {
66
+ color: #213547;
67
+ background-color: #ffffff;
68
+ }
69
+ a:hover {
70
+ color: #747bff;
71
+ }
72
+ button {
73
+ background-color: #f9f9f9;
74
+ }
75
+ }
@@ -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
+ }
@@ -0,0 +1,10 @@
1
+ import { StrictMode } from "react";
2
+ import { createRoot } from "react-dom/client";
3
+ import "./index.css";
4
+ import App from "./App.tsx";
5
+
6
+ createRoot(document.getElementById("root")!).render(
7
+ <StrictMode>
8
+ <App />
9
+ </StrictMode>,
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 "@demox-labs/miden-wallet-adapter";
8
+ import "@demox-labs/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" />
@@ -0,0 +1,32 @@
1
+ {
2
+ "compilerOptions": {
3
+ "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
4
+ "target": "ES2022",
5
+ "useDefineForClassFields": true,
6
+ "lib": ["ES2022", "DOM", "DOM.Iterable"],
7
+ "module": "ESNext",
8
+ "types": ["vite/client"],
9
+ "skipLibCheck": true,
10
+
11
+ /* Bundler mode */
12
+ "moduleResolution": "bundler",
13
+ "allowImportingTsExtensions": true,
14
+ "verbatimModuleSyntax": true,
15
+ "moduleDetection": "force",
16
+ "noEmit": true,
17
+ "jsx": "react-jsx",
18
+
19
+ /* Path aliases */
20
+ "baseUrl": ".",
21
+ "paths": {
22
+ "@/*": ["./src/*"]
23
+ },
24
+
25
+ /* Linting */
26
+ "strict": true,
27
+ "noUnusedLocals": true,
28
+ "noUnusedParameters": true,
29
+ "noFallthroughCasesInSwitch": true
30
+ },
31
+ "include": ["src"]
32
+ }
@@ -0,0 +1,7 @@
1
+ {
2
+ "files": [],
3
+ "references": [
4
+ { "path": "./tsconfig.app.json" },
5
+ { "path": "./tsconfig.node.json" }
6
+ ]
7
+ }
@@ -0,0 +1,24 @@
1
+ {
2
+ "compilerOptions": {
3
+ "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
4
+ "target": "ES2023",
5
+ "lib": ["ES2023"],
6
+ "module": "ESNext",
7
+ "types": ["node"],
8
+ "skipLibCheck": true,
9
+
10
+ /* Bundler mode */
11
+ "moduleResolution": "bundler",
12
+ "allowImportingTsExtensions": true,
13
+ "verbatimModuleSyntax": true,
14
+ "moduleDetection": "force",
15
+ "noEmit": true,
16
+
17
+ /* Linting */
18
+ "strict": true,
19
+ "noUnusedLocals": true,
20
+ "noUnusedParameters": true,
21
+ "noFallthroughCasesInSwitch": true
22
+ },
23
+ "include": ["vite.config.ts"]
24
+ }
@@ -0,0 +1,17 @@
1
+ import path from "node:path";
2
+ import { defineConfig } from "vite";
3
+ import react from "@vitejs/plugin-react";
4
+ import { midenVitePlugin } from "@miden-sdk/vite-plugin";
5
+
6
+ export default defineConfig({
7
+ plugins: [react(), midenVitePlugin()],
8
+ resolve: {
9
+ dedupe: ["react", "react-dom", "react/jsx-runtime"],
10
+ alias: {
11
+ "@": path.resolve(__dirname, "./src"),
12
+ // The wallet adapter was published under @demox-labs but imports
13
+ // @demox-labs/miden-sdk internally. Redirect to the current package.
14
+ "@demox-labs/miden-sdk": "@miden-sdk/miden-sdk",
15
+ },
16
+ },
17
+ });