create-miden-app 1.0.6 → 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/package.json +1 -1
- package/template/.claude/commands/review-security.md +67 -0
- package/template/.claude/settings.json +1 -7
- package/template/.claude/settings.local.json +24 -0
- package/template/.claude/skills/frontend-pitfalls/SKILL.md +28 -31
- package/template/.claude/skills/frontend-source-guide/SKILL.md +14 -14
- package/template/.claude/skills/miden-concepts/SKILL.md +4 -2
- package/template/.claude/skills/react-sdk-patterns/SKILL.md +294 -28
- package/template/.claude/skills/signer-integration/SKILL.md +22 -3
- package/template/.claude/skills/testing-patterns/SKILL.md +201 -40
- package/template/.claude/skills/vite-wasm-setup/SKILL.md +20 -14
- package/template/.claude/skills/web-client-usage/SKILL.md +454 -0
- package/template/.env.example +15 -2
- package/template/.mcp.json +9 -0
- package/template/CLAUDE.md +49 -16
- package/template/README.md +85 -19
- package/template/package.json +5 -4
- package/template/public/packages/counter_account.masp +0 -0
- package/template/public/packages/increment_note.masp +0 -0
- package/template/src/__tests__/fixtures/accounts.ts +17 -6
- package/template/src/__tests__/fixtures/index.ts +1 -0
- package/template/src/__tests__/mocks/miden-sdk-react.ts +18 -1
- package/template/src/__tests__/patterns/mutation-hook.test.tsx +2 -2
- package/template/src/__tests__/patterns/provider-setup.test.tsx +2 -0
- package/template/src/components/AppContent.tsx +33 -3
- package/template/{create-miden-app/template/src/components/Counter.tsx → src/components/ConfiguredCounter.tsx} +7 -4
- package/template/src/components/Counter.tsx +12 -41
- package/template/src/components/__tests__/AppContent.test.tsx +192 -4
- package/template/src/components/__tests__/ConfiguredCounter.test.tsx +116 -0
- package/template/src/components/__tests__/Counter.test.tsx +24 -94
- package/template/src/config.ts +26 -6
- package/template/src/hooks/__tests__/useIncrementCounter.test.tsx +257 -0
- package/template/src/hooks/useIncrementCounter.ts +109 -50
- package/template/src/providers.tsx +20 -24
- package/template/vite.config.ts +1 -1
- package/template/vitest.config.ts +1 -2
- package/template/yarn.lock +761 -688
- package/template/create-miden-app/template/.claude/hooks/typecheck.sh +0 -27
- package/template/create-miden-app/template/.claude/settings.json +0 -17
- package/template/create-miden-app/template/.claude/skills/frontend-pitfalls/SKILL.md +0 -189
- package/template/create-miden-app/template/.claude/skills/frontend-source-guide/SKILL.md +0 -163
- package/template/create-miden-app/template/.claude/skills/miden-concepts/SKILL.md +0 -108
- package/template/create-miden-app/template/.claude/skills/react-sdk-patterns/SKILL.md +0 -294
- package/template/create-miden-app/template/.claude/skills/signer-integration/SKILL.md +0 -158
- package/template/create-miden-app/template/.claude/skills/vite-wasm-setup/SKILL.md +0 -128
- package/template/create-miden-app/template/.env.example +0 -5
- package/template/create-miden-app/template/CLAUDE.md +0 -116
- package/template/create-miden-app/template/README.md +0 -61
- package/template/create-miden-app/template/eslint.config.js +0 -23
- package/template/create-miden-app/template/index.html +0 -13
- package/template/create-miden-app/template/package.json +0 -34
- package/template/create-miden-app/template/public/vite.svg +0 -1
- package/template/create-miden-app/template/src/App.tsx +0 -10
- package/template/create-miden-app/template/src/assets/miden.svg +0 -3
- package/template/create-miden-app/template/src/assets/react.svg +0 -1
- package/template/create-miden-app/template/src/components/AppContent.css +0 -45
- package/template/create-miden-app/template/src/components/AppContent.tsx +0 -50
- package/template/create-miden-app/template/src/components/Counter.css +0 -27
- package/template/create-miden-app/template/src/config.ts +0 -21
- package/template/create-miden-app/template/src/hooks/useIncrementCounter.ts +0 -136
- package/template/create-miden-app/template/src/index.css +0 -75
- package/template/create-miden-app/template/src/lib/miden.ts +0 -9
- package/template/create-miden-app/template/src/main.tsx +0 -10
- package/template/create-miden-app/template/src/providers.tsx +0 -31
- package/template/create-miden-app/template/src/vite-env.d.ts +0 -1
- package/template/create-miden-app/template/tsconfig.app.json +0 -32
- package/template/create-miden-app/template/tsconfig.json +0 -7
- package/template/create-miden-app/template/tsconfig.node.json +0 -24
- package/template/create-miden-app/template/vite.config.ts +0 -17
- package/template/create-miden-app/template/yarn.lock +0 -1697
|
@@ -0,0 +1,454 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: web-client-usage
|
|
3
|
+
description: Conventions for writing JavaScript/TypeScript code that uses the Miden web SDK (`@miden-sdk/miden-sdk`). Use when building apps on Miden, writing integration tests, or calling MidenClient methods - covers initialization, the resource-based API (accounts, transactions, notes, keystore, compile), sync ordering, type conversions, transaction flows, custom contracts, private note transport, and pitfalls.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
NOTE: In the frontend-template context, prefer @miden-sdk/react hooks for all
|
|
7
|
+
operations they cover. Use the raw WebClient patterns documented here ONLY for
|
|
8
|
+
operations not available through React hooks (e.g., custom TransactionRequests,
|
|
9
|
+
direct store import/export, advanced sync control).
|
|
10
|
+
|
|
11
|
+
# Web SDK Usage Patterns
|
|
12
|
+
|
|
13
|
+
This skill targets the `@miden-sdk/miden-sdk` npm package shipped from
|
|
14
|
+
[`0xMiden/miden-client`](https://github.com/0xMiden/miden-client). For
|
|
15
|
+
React-hook usage, prefer the `react-sdk-patterns` skill - only fall through
|
|
16
|
+
to the raw client when a hook does not cover what you need.
|
|
17
|
+
|
|
18
|
+
## API Overview
|
|
19
|
+
|
|
20
|
+
The SDK exposes a top-level `MidenClient` whose state is split across typed
|
|
21
|
+
**resources**:
|
|
22
|
+
|
|
23
|
+
| Resource | What it covers |
|
|
24
|
+
|----------|----------------|
|
|
25
|
+
| `client.accounts` | Wallets, faucets, custom contracts, listing, import/export |
|
|
26
|
+
| `client.transactions` | `send` / `mint` / `consume` / `consumeAll` / `swap` / `execute` / `preview` / `waitFor` |
|
|
27
|
+
| `client.notes` | Listing, fetching, importing/exporting, private-note transport |
|
|
28
|
+
| `client.tags` | Note-tag subscriptions |
|
|
29
|
+
| `client.settings` | Persistent client settings |
|
|
30
|
+
| `client.compile` | Compiling MASM into account components, tx scripts, note scripts |
|
|
31
|
+
| `client.keystore` | Inserting / fetching / removing secret keys |
|
|
32
|
+
|
|
33
|
+
`MidenClient` is the public surface. The underlying WASM-bound class is still
|
|
34
|
+
exported as `WasmWebClient` (alias for the legacy `WebClient`) for low-level
|
|
35
|
+
operations the resource API does not yet wrap. To reach it, either use the
|
|
36
|
+
React `useMidenClient()` hook or import `WasmWebClient` directly from
|
|
37
|
+
`@miden-sdk/miden-sdk` (the class is `@internal` but exported). `MidenClient`
|
|
38
|
+
keeps its wrapped client in a real JS private field (`#inner`), so external
|
|
39
|
+
code cannot reach in directly.
|
|
40
|
+
|
|
41
|
+
## Client Initialization
|
|
42
|
+
|
|
43
|
+
### Convenience constructors (recommended)
|
|
44
|
+
|
|
45
|
+
```typescript
|
|
46
|
+
import { MidenClient } from "@miden-sdk/miden-sdk";
|
|
47
|
+
|
|
48
|
+
// Testnet - autoSync on, testnet RPC + prover + note transport
|
|
49
|
+
const client = await MidenClient.createTestnet();
|
|
50
|
+
|
|
51
|
+
// Devnet equivalent
|
|
52
|
+
const client = await MidenClient.createDevnet();
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
Both accept the same `ClientOptions` for overrides:
|
|
56
|
+
|
|
57
|
+
```typescript
|
|
58
|
+
const client = await MidenClient.createTestnet({
|
|
59
|
+
storeName: "my-app-tests", // isolates the IndexedDB store
|
|
60
|
+
proverUrl: "local", // prove locally instead of remote
|
|
61
|
+
autoSync: false, // disable initial sync
|
|
62
|
+
});
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
### Generic constructor
|
|
66
|
+
|
|
67
|
+
```typescript
|
|
68
|
+
const client = await MidenClient.create({
|
|
69
|
+
rpcUrl: "https://rpc.testnet.miden.io", // string URL or "testnet"/"devnet"/"localhost"
|
|
70
|
+
noteTransportUrl: "https://ntx.testnet.miden.io",
|
|
71
|
+
storeName: "my-store",
|
|
72
|
+
seed: new Uint8Array(32), // optional - deterministic key generation
|
|
73
|
+
proverUrl: "testnet", // optional - sets a default prover
|
|
74
|
+
autoSync: true, // optional - call sync() after init
|
|
75
|
+
keystore: { // optional - external HSM/keystore
|
|
76
|
+
getKey: async (pubKey) => { /* return secretKey or null */ },
|
|
77
|
+
insertKey: async (pubKey, secretKey) => { /* persist */ },
|
|
78
|
+
sign: async (pubKey, signingInputs) => { /* return signature */ },
|
|
79
|
+
},
|
|
80
|
+
});
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
If `rpcUrl` is omitted, `create()` delegates to `createTestnet()`.
|
|
84
|
+
|
|
85
|
+
### Lazy / SSR-safe init
|
|
86
|
+
|
|
87
|
+
Some bundles (Next.js, Capacitor, raw `/lazy` entry) cannot await WASM at
|
|
88
|
+
import time. Use `MidenClient.ready()` to wait for WASM in-band - it is
|
|
89
|
+
idempotent and shared across callers:
|
|
90
|
+
|
|
91
|
+
```typescript
|
|
92
|
+
await MidenClient.ready();
|
|
93
|
+
const client = await MidenClient.createTestnet();
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
### Termination
|
|
97
|
+
|
|
98
|
+
```typescript
|
|
99
|
+
client.terminate(); // free WASM resources, close the store handle
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
After `terminate()`, every method throws - guard against late callbacks on
|
|
103
|
+
unmount.
|
|
104
|
+
|
|
105
|
+
## Sync - Always Sync First
|
|
106
|
+
|
|
107
|
+
The client's view of the chain is only as fresh as its last sync. **Always
|
|
108
|
+
call `sync()` before reading account state or building a transaction that
|
|
109
|
+
depends on freshly received notes.**
|
|
110
|
+
|
|
111
|
+
```typescript
|
|
112
|
+
const summary = await client.sync(); // returns SyncSummary
|
|
113
|
+
const height = await client.getSyncHeight(); // current local block number
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
Common patterns:
|
|
117
|
+
|
|
118
|
+
- Sync before consuming notes (notes must be committed on-chain)
|
|
119
|
+
- Sync after submitting a transaction to observe the result
|
|
120
|
+
- Pass `waitForConfirmation: true` to a `transactions.send/mint/consume/swap`
|
|
121
|
+
call to let the SDK wait for the tx commit instead of polling manually
|
|
122
|
+
- Use `client.waitForIdle()` to flush all queued WASM calls before doing a
|
|
123
|
+
side-effect that must not race with a kernel callback (e.g. clearing an
|
|
124
|
+
in-memory unlock token after a wallet "lock")
|
|
125
|
+
|
|
126
|
+
`autoSync: true` (default for `createTestnet`/`createDevnet`) only triggers a
|
|
127
|
+
single sync at construction time - it is not a polling loop. Use the React
|
|
128
|
+
SDK's `useSyncState` or `MidenProvider` `autoSyncInterval` for periodic sync.
|
|
129
|
+
|
|
130
|
+
## Type Conversions
|
|
131
|
+
|
|
132
|
+
Type confusion across the WASM boundary is the leading source of bugs.
|
|
133
|
+
|
|
134
|
+
### `AccountId`
|
|
135
|
+
|
|
136
|
+
```typescript
|
|
137
|
+
const id = AccountId.fromHex("0xabc123..."); // throws on invalid hex
|
|
138
|
+
const id = Address.fromBech32("mtst1abc...").accountId();
|
|
139
|
+
const hex = id.toString(); // "0x..."
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
Pass `AccountId` (or any account ref the resource accepts: `string` hex,
|
|
143
|
+
`Address`, `Account`, `AccountHeader`) to resource methods - never raw
|
|
144
|
+
strings to methods that ask for `AccountId` directly.
|
|
145
|
+
|
|
146
|
+
`AccountId.fromHex` throws on malformed input; wrap in `try/catch` when
|
|
147
|
+
accepting user input.
|
|
148
|
+
|
|
149
|
+
### Amounts - Always `BigInt`
|
|
150
|
+
|
|
151
|
+
```typescript
|
|
152
|
+
BigInt(1000)
|
|
153
|
+
1000n // numeric literal
|
|
154
|
+
BigInt("1000")
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
All token amounts in the SDK are `bigint`. Mixing `number` causes silent
|
|
158
|
+
precision loss above `Number.MAX_SAFE_INTEGER` and `TypeError` below.
|
|
159
|
+
|
|
160
|
+
### Visibility & Account Types
|
|
161
|
+
|
|
162
|
+
```typescript
|
|
163
|
+
import { NoteVisibility, AccountType, AuthScheme, StorageMode } from "@miden-sdk/miden-sdk";
|
|
164
|
+
|
|
165
|
+
NoteVisibility.Public // "public"
|
|
166
|
+
NoteVisibility.Private // "private"
|
|
167
|
+
|
|
168
|
+
AccountType.MutableWallet
|
|
169
|
+
AccountType.ImmutableWallet
|
|
170
|
+
AccountType.FungibleFaucet
|
|
171
|
+
AccountType.NonFungibleFaucet
|
|
172
|
+
AccountType.MutableContract
|
|
173
|
+
AccountType.ImmutableContract
|
|
174
|
+
|
|
175
|
+
AuthScheme.Falcon // default - Falcon-512 over Poseidon2
|
|
176
|
+
AuthScheme.ECDSA // EcdsaK256Keccak
|
|
177
|
+
|
|
178
|
+
StorageMode.Public
|
|
179
|
+
StorageMode.Private
|
|
180
|
+
StorageMode.Network
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
Use `NoteVisibility` (not the legacy `NoteType` enum) and `AuthScheme.Falcon`
|
|
184
|
+
for the Poseidon2-based Falcon-512 scheme.
|
|
185
|
+
|
|
186
|
+
## Account Creation
|
|
187
|
+
|
|
188
|
+
Type discriminator on `auth`: wallets and faucets take `auth: AuthSchemeType` (a `"falcon" | "ecdsa"` string-union, e.g. `AuthScheme.Falcon`); custom contracts take `auth: AuthSecretKey` (a concrete WASM instance).
|
|
189
|
+
|
|
190
|
+
```typescript
|
|
191
|
+
// Wallet - defaults: mutable, private, Falcon
|
|
192
|
+
const wallet = await client.accounts.create();
|
|
193
|
+
|
|
194
|
+
// Wallet with explicit options
|
|
195
|
+
const wallet = await client.accounts.create({
|
|
196
|
+
type: AccountType.MutableWallet,
|
|
197
|
+
storage: "private",
|
|
198
|
+
auth: AuthScheme.Falcon,
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
// Faucet
|
|
202
|
+
const faucet = await client.accounts.create({
|
|
203
|
+
type: AccountType.FungibleFaucet,
|
|
204
|
+
storage: "public",
|
|
205
|
+
symbol: "DAG",
|
|
206
|
+
decimals: 8,
|
|
207
|
+
maxSupply: 10_000_000n,
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
// Custom contract - requires seed and AuthSecretKey
|
|
211
|
+
const component = await client.compile.component({ code: contractMasm, slots: [] });
|
|
212
|
+
const contract = await client.accounts.create({
|
|
213
|
+
type: AccountType.MutableContract,
|
|
214
|
+
seed: new Uint8Array(32),
|
|
215
|
+
auth: secretKey, // AuthSecretKey, not the AuthScheme enum
|
|
216
|
+
components: [component],
|
|
217
|
+
});
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
## Transactions
|
|
221
|
+
|
|
222
|
+
The transactions API is option-bag-based and accepts any account ref
|
|
223
|
+
(`Account`, `AccountHeader`, hex string, `AccountId`).
|
|
224
|
+
|
|
225
|
+
### Send
|
|
226
|
+
|
|
227
|
+
```typescript
|
|
228
|
+
const { txId } = await client.transactions.send({
|
|
229
|
+
account: wallet, // sender
|
|
230
|
+
to: "0xrecipient...", // any account ref
|
|
231
|
+
token: faucet, // faucet account ref - identifies the asset
|
|
232
|
+
amount: 100n,
|
|
233
|
+
type: NoteVisibility.Public, // optional, defaults to "public" (raw SDK; resolveNoteType in dist/index.js:307-316). React useSend defaults to "private".
|
|
234
|
+
reclaimAfter: 100, // optional - sender can reclaim after this block
|
|
235
|
+
timelockUntil: 50, // optional - recipient can consume after this block
|
|
236
|
+
waitForConfirmation: true,
|
|
237
|
+
timeout: 30_000,
|
|
238
|
+
});
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
For private sends where you also need to deliver the note out-of-band, set
|
|
242
|
+
`returnNote: true` and the call returns the constructed `Note` object -
|
|
243
|
+
incompatible with `reclaimAfter`/`timelockUntil`.
|
|
244
|
+
|
|
245
|
+
```typescript
|
|
246
|
+
const { txId, note } = await client.transactions.send({
|
|
247
|
+
account: wallet,
|
|
248
|
+
to: recipientAddress,
|
|
249
|
+
token: faucet,
|
|
250
|
+
amount: 100n,
|
|
251
|
+
type: NoteVisibility.Private,
|
|
252
|
+
returnNote: true,
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
// Stream the note via the note-transport service
|
|
256
|
+
await client.notes.sendPrivate({ note, to: Address.fromBech32("mtst1...") });
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
### Mint
|
|
260
|
+
|
|
261
|
+
```typescript
|
|
262
|
+
const { txId } = await client.transactions.mint({
|
|
263
|
+
account: faucet, // faucet executes the mint
|
|
264
|
+
to: targetAccountId, // recipient
|
|
265
|
+
amount: 1000n,
|
|
266
|
+
type: NoteVisibility.Public,
|
|
267
|
+
waitForConfirmation: true,
|
|
268
|
+
});
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
The transaction executes on the **faucet** - a frequent bug is passing the
|
|
272
|
+
recipient as `account`.
|
|
273
|
+
|
|
274
|
+
### Consume
|
|
275
|
+
|
|
276
|
+
```typescript
|
|
277
|
+
// Specific notes
|
|
278
|
+
await client.transactions.consume({
|
|
279
|
+
account: wallet,
|
|
280
|
+
notes: [noteId1, noteRecord, "0xnote..."], // any of: hex, NoteId, InputNoteRecord, Note
|
|
281
|
+
waitForConfirmation: true,
|
|
282
|
+
});
|
|
283
|
+
|
|
284
|
+
// Drain everything consumable for the account
|
|
285
|
+
const { txId, consumed, remaining } = await client.transactions.consumeAll({
|
|
286
|
+
account: wallet,
|
|
287
|
+
maxNotes: 50, // optional cap
|
|
288
|
+
});
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
### Swap
|
|
292
|
+
|
|
293
|
+
```typescript
|
|
294
|
+
await client.transactions.swap({
|
|
295
|
+
account: wallet,
|
|
296
|
+
offered: { token: tokenA, amount: 100n },
|
|
297
|
+
requested: { token: tokenB, amount: 50n },
|
|
298
|
+
type: NoteVisibility.Public, // swap-note visibility
|
|
299
|
+
paybackType: NoteVisibility.Private, // payback-note visibility
|
|
300
|
+
});
|
|
301
|
+
```
|
|
302
|
+
|
|
303
|
+
### Execute (custom scripts)
|
|
304
|
+
|
|
305
|
+
```typescript
|
|
306
|
+
const script = await client.compile.txScript({
|
|
307
|
+
code: scriptMasm,
|
|
308
|
+
libraries: [{ namespace: "my::lib", code: libMasm, linking: "dynamic" }],
|
|
309
|
+
});
|
|
310
|
+
|
|
311
|
+
await client.transactions.execute({
|
|
312
|
+
account: contract,
|
|
313
|
+
script,
|
|
314
|
+
foreignAccounts: [
|
|
315
|
+
publicAccountId, // public - auto-fetched via RPC
|
|
316
|
+
{ id: privateContractId, storage: storageRequirements },
|
|
317
|
+
],
|
|
318
|
+
waitForConfirmation: true,
|
|
319
|
+
});
|
|
320
|
+
```
|
|
321
|
+
|
|
322
|
+
**Public foreign accounts are auto-fetched** during execution - only private
|
|
323
|
+
foreign accounts must be supplied with their storage requirements.
|
|
324
|
+
|
|
325
|
+
### Preview (dry run)
|
|
326
|
+
|
|
327
|
+
`transactions.preview({ operation: "send" | "mint" | "consume" | "swap" | "custom", ... })`
|
|
328
|
+
runs the same kernel as the real call but without proving or submitting,
|
|
329
|
+
returning a summary suitable for UI confirmation screens.
|
|
330
|
+
|
|
331
|
+
## Notes
|
|
332
|
+
|
|
333
|
+
```typescript
|
|
334
|
+
await client.notes.list(); // all input notes
|
|
335
|
+
await client.notes.list({ status: "committed" }); // filter
|
|
336
|
+
await client.notes.get(noteId); // single record
|
|
337
|
+
await client.notes.listSent(); // output notes
|
|
338
|
+
await client.notes.listAvailable({ account: wallet });// consumable for an account
|
|
339
|
+
|
|
340
|
+
// Import/export
|
|
341
|
+
await client.notes.import(noteFile);
|
|
342
|
+
const file = await client.notes.export(noteId);
|
|
343
|
+
|
|
344
|
+
// Private-note transport
|
|
345
|
+
await client.notes.fetchPrivate(); // pulls anything addressed to tracked accounts
|
|
346
|
+
await client.notes.sendPrivate({ note, to: addr }); // delivers via the transport service
|
|
347
|
+
```
|
|
348
|
+
|
|
349
|
+
## Accounts (querying)
|
|
350
|
+
|
|
351
|
+
```typescript
|
|
352
|
+
await client.accounts.list(); // tracked accounts
|
|
353
|
+
await client.accounts.get(ref); // single (returns null if not tracked)
|
|
354
|
+
await client.accounts.getOrImport(ref); // tries get(), falls back to import()
|
|
355
|
+
await client.accounts.getDetails(ref); // header + status + vault summary
|
|
356
|
+
await client.accounts.insert({ account, overwrite }); // start tracking an existing account
|
|
357
|
+
```
|
|
358
|
+
|
|
359
|
+
For balance reads, use `getDetails` or - if you need a single asset balance
|
|
360
|
+
without loading the full vault - drop into the underlying WASM client's
|
|
361
|
+
`accountReader(id)` lazy reader.
|
|
362
|
+
|
|
363
|
+
## Keystore
|
|
364
|
+
|
|
365
|
+
```typescript
|
|
366
|
+
await client.keystore.insert(accountId, secretKey);
|
|
367
|
+
await client.keystore.get(pubKeyCommitment);
|
|
368
|
+
await client.keystore.remove(pubKeyCommitment);
|
|
369
|
+
await client.keystore.getCommitments(accountId);
|
|
370
|
+
await client.keystore.getAccountId(pubKeyCommitment);
|
|
371
|
+
```
|
|
372
|
+
|
|
373
|
+
`keystore.insert` is the single call that both stores the key and registers
|
|
374
|
+
its commitment with the account.
|
|
375
|
+
|
|
376
|
+
## Compile
|
|
377
|
+
|
|
378
|
+
```typescript
|
|
379
|
+
await client.compile.component({ code, slots, supportAllTypes: true }); // supportAllTypes defaults to true (api-types.d.ts:784); set false if your component supplies its own auth-tx kernel invocation.
|
|
380
|
+
await client.compile.txScript({ code, libraries });
|
|
381
|
+
await client.compile.noteScript({ code, libraries });
|
|
382
|
+
```
|
|
383
|
+
|
|
384
|
+
Note scripts are **MASM libraries with a single `@note_script`-annotated
|
|
385
|
+
procedure**, not begin/end programs - `client.compile.noteScript` builds the
|
|
386
|
+
correct shape from a procedure body. The same applies to `@auth_script` for
|
|
387
|
+
authentication scripts.
|
|
388
|
+
|
|
389
|
+
## Common Workflows
|
|
390
|
+
|
|
391
|
+
### Mint and consume (fund a fresh wallet)
|
|
392
|
+
|
|
393
|
+
```typescript
|
|
394
|
+
const wallet = await client.accounts.create();
|
|
395
|
+
const faucet = await client.accounts.create({
|
|
396
|
+
type: AccountType.FungibleFaucet,
|
|
397
|
+
storage: "public",
|
|
398
|
+
symbol: "TEST",
|
|
399
|
+
decimals: 8,
|
|
400
|
+
maxSupply: 1_000_000n,
|
|
401
|
+
});
|
|
402
|
+
|
|
403
|
+
await client.transactions.mint({
|
|
404
|
+
account: faucet,
|
|
405
|
+
to: wallet,
|
|
406
|
+
amount: 10_000n,
|
|
407
|
+
type: NoteVisibility.Public,
|
|
408
|
+
waitForConfirmation: true,
|
|
409
|
+
});
|
|
410
|
+
|
|
411
|
+
await client.sync();
|
|
412
|
+
await client.transactions.consumeAll({
|
|
413
|
+
account: wallet,
|
|
414
|
+
waitForConfirmation: true,
|
|
415
|
+
});
|
|
416
|
+
```
|
|
417
|
+
|
|
418
|
+
### Wait for an external transfer
|
|
419
|
+
|
|
420
|
+
```typescript
|
|
421
|
+
await client.sync();
|
|
422
|
+
const before = (await client.notes.listAvailable({ account: wallet })).length;
|
|
423
|
+
|
|
424
|
+
while (true) {
|
|
425
|
+
await new Promise(r => setTimeout(r, 3000));
|
|
426
|
+
await client.sync();
|
|
427
|
+
const now = (await client.notes.listAvailable({ account: wallet })).length;
|
|
428
|
+
if (now > before) break;
|
|
429
|
+
}
|
|
430
|
+
```
|
|
431
|
+
|
|
432
|
+
## Common Pitfalls
|
|
433
|
+
|
|
434
|
+
1. **Forgetting to sync.** Notes won't appear, balances will be stale, foreign
|
|
435
|
+
accounts will be at the wrong block.
|
|
436
|
+
2. **`number` instead of `BigInt`** for amounts - silent precision loss.
|
|
437
|
+
3. **Passing strings where the SDK expects an account ref** - pre-parse with
|
|
438
|
+
`AccountId.fromHex()` (and catch its throw).
|
|
439
|
+
4. **Consuming notes before they're committed** - sync first, check status.
|
|
440
|
+
5. **Submitting `mint` with the recipient as `account`** - mint executes on
|
|
441
|
+
the faucet account, not the target.
|
|
442
|
+
6. **Private notes without transport** - must call `notes.sendPrivate()` (or
|
|
443
|
+
pass `returnNote: true` to `transactions.send` and deliver out-of-band).
|
|
444
|
+
7. **Holding WASM-owned objects across `terminate()`** - every `Account`,
|
|
445
|
+
`Note`, `AccountId`, `NoteAndArgsArray` etc. owns Rust memory through the
|
|
446
|
+
WASM ArrayBuffer. After `terminate()` they panic with "null pointer
|
|
447
|
+
passed to rust" - drop references on unmount.
|
|
448
|
+
8. **Calling `accountReader(...)` in parallel with a write** (the method lives
|
|
449
|
+
on the raw `WasmWebClient`, accessed via React's `useMidenClient()` or a
|
|
450
|
+
direct `WasmWebClient` import; not on `MidenClient`) - the readers share
|
|
451
|
+
the WASM client. Wrap concurrent flows with `client.waitForIdle()` or rely
|
|
452
|
+
on the React SDK's `runExclusive`. For multiple browser clients or tabs,
|
|
453
|
+
give each isolated client a distinct `storeName`; see the initialization
|
|
454
|
+
examples above and `signer-integration` for per-user store isolation.
|
package/template/.env.example
CHANGED
|
@@ -1,5 +1,18 @@
|
|
|
1
|
-
# Miden
|
|
1
|
+
# Miden SDK network configuration
|
|
2
|
+
# Supported values: devnet | testnet | local | https://your-rpc-url
|
|
2
3
|
VITE_MIDEN_RPC_URL=testnet
|
|
3
4
|
|
|
4
|
-
#
|
|
5
|
+
# Prover configuration
|
|
6
|
+
# Supported values: devnet | testnet | local | https://your-prover-url
|
|
5
7
|
VITE_MIDEN_PROVER=testnet
|
|
8
|
+
|
|
9
|
+
# Counter contract address (bech32, e.g. mtst1...).
|
|
10
|
+
#
|
|
11
|
+
# Resolution rules:
|
|
12
|
+
# - Leave this line commented out / unset → app uses the live testnet
|
|
13
|
+
# default counter shipped with the template (recommended for first-run).
|
|
14
|
+
# - Set to "" (empty string) → unconfigured; <Counter> shows the
|
|
15
|
+
# "address not configured" card (useful when you've deployed nothing
|
|
16
|
+
# yet and don't want network calls).
|
|
17
|
+
# - Set to your own bech32 address → app reads from that counter.
|
|
18
|
+
# VITE_MIDEN_COUNTER_ADDRESS=
|
package/template/CLAUDE.md
CHANGED
|
@@ -30,20 +30,32 @@ Type checking alone:
|
|
|
30
30
|
npx tsc -b --noEmit
|
|
31
31
|
```
|
|
32
32
|
|
|
33
|
-
## SDK Choice: React SDK
|
|
33
|
+
## SDK Choice: React SDK Hooks First
|
|
34
34
|
|
|
35
|
-
ALWAYS prefer `@miden-sdk/react` hooks over
|
|
36
|
-
Only use
|
|
35
|
+
ALWAYS prefer `@miden-sdk/react` hooks over low-level `WasmWebClient` methods.
|
|
36
|
+
Only use the WASM client directly via `useMidenClient()` for operations not covered by hooks.
|
|
37
37
|
|
|
38
|
-
### Setup
|
|
38
|
+
### Setup — this template's actual providers (`src/providers.tsx`)
|
|
39
39
|
```tsx
|
|
40
40
|
import { MidenProvider } from "@miden-sdk/react";
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
41
|
+
import { MidenFiSignerProvider } from "@miden-sdk/miden-wallet-adapter-react";
|
|
42
|
+
|
|
43
|
+
<MidenFiSignerProvider
|
|
44
|
+
appName={APP_NAME}
|
|
45
|
+
network={WalletAdapterNetwork.Testnet}
|
|
46
|
+
autoConnect
|
|
47
|
+
>
|
|
48
|
+
<MidenProvider
|
|
49
|
+
config={{ rpcUrl: MIDEN_RPC_URL, prover: MIDEN_PROVER }}
|
|
50
|
+
loadingComponent={<div className="loading">Loading Miden WASM...</div>}
|
|
51
|
+
>
|
|
52
|
+
<App />
|
|
53
|
+
</MidenProvider>
|
|
54
|
+
</MidenFiSignerProvider>
|
|
45
55
|
```
|
|
46
56
|
|
|
57
|
+
> `MidenFiSignerProvider` must wrap `MidenProvider`. `MidenProvider` reads from `SignerContext` during initialization (to wire its external-keystore client), so the signer context has to exist before `MidenProvider` mounts.
|
|
58
|
+
|
|
47
59
|
### Query Hooks
|
|
48
60
|
Each returns its own result shape plus `isLoading`, `error`, `refetch`:
|
|
49
61
|
```tsx
|
|
@@ -73,6 +85,8 @@ const display = formatAssetAmount(balance, 8); // bigint → string
|
|
|
73
85
|
const amount = parseAssetAmount("1.5", 8); // string → bigint
|
|
74
86
|
```
|
|
75
87
|
|
|
88
|
+
For exhaustive hook API reference, read `node_modules/@miden-sdk/react/CLAUDE.md` and `node_modules/@miden-sdk/react/README.md`.
|
|
89
|
+
|
|
76
90
|
## TDD Workflow
|
|
77
91
|
|
|
78
92
|
When building features, follow this test-driven cycle:
|
|
@@ -110,15 +124,28 @@ Automated verification runs in layers (each catches different failure classes):
|
|
|
110
124
|
|
|
111
125
|
1. **TypeScript type check** (auto, per-edit) — catches type errors immediately
|
|
112
126
|
2. **Affected tests** (auto, per-edit) — catches logic regressions from changes
|
|
113
|
-
3. **Full test suite + type check + build** (auto, on
|
|
114
|
-
4. **Browser verification** (
|
|
127
|
+
3. **Full test suite + type check + build** (auto, on each edit via PostToolUse) — catches integration issues
|
|
128
|
+
4. **Browser verification** (Playwright MCP / Claude in Chrome) — catches "compiles but doesn't work" failures
|
|
115
129
|
|
|
116
130
|
### Browser verification (when needed)
|
|
117
|
-
|
|
131
|
+
|
|
132
|
+
Two tools are available for browser verification. Use whichever is appropriate:
|
|
133
|
+
|
|
134
|
+
#### Playwright MCP (visual verification, no wallet)
|
|
135
|
+
Configured in `.mcp.json`. Use for checking that the UI renders correctly, no console errors, layout looks right. Cannot interact with the MidenFi wallet extension.
|
|
136
|
+
|
|
118
137
|
1. Start dev server: `yarn dev`
|
|
119
|
-
2.
|
|
120
|
-
3.
|
|
121
|
-
4.
|
|
138
|
+
2. Use Playwright MCP tools to navigate to `http://localhost:5173`
|
|
139
|
+
3. Take a screenshot, check for render errors
|
|
140
|
+
4. Check the browser console for errors
|
|
141
|
+
|
|
142
|
+
#### Claude in Chrome (full verification, with wallet)
|
|
143
|
+
Use for wallet-dependent features. Connects to the user's real browser where MidenFi is installed.
|
|
144
|
+
|
|
145
|
+
1. Start Claude Code with `claude --chrome` (or run `/chrome` in session)
|
|
146
|
+
2. Start dev server: `yarn dev`
|
|
147
|
+
3. Navigate to `http://localhost:5173`
|
|
148
|
+
4. Interact with wallet connect, transaction flows, etc.
|
|
122
149
|
|
|
123
150
|
## Contract Artifact Handoff
|
|
124
151
|
|
|
@@ -148,6 +175,12 @@ cargo miden build --release
|
|
|
148
175
|
- **Stale artifacts**: Rebuild and re-copy after contract changes
|
|
149
176
|
- **Deserialization failure at runtime**: Version mismatch — rebuild contracts with the SDK version matching `@miden-sdk/miden-sdk` in `package.json`
|
|
150
177
|
|
|
178
|
+
## Known Temporary Workarounds
|
|
179
|
+
|
|
180
|
+
One remaining upstream feature gap. The README has full rationale + removal steps; summary below.
|
|
181
|
+
|
|
182
|
+
**Fixed-interval network poll** ([0xMiden/miden-client#2111](https://github.com/0xMiden/miden-client/issues/2111)): `useIncrementCounter.ts::increment` polls the counter's storage map every `NETWORK_POLL_INTERVAL_MS` (2.5 s) until the value changes or `NETWORK_POLL_TIMEOUT_MS` (30 s) elapses. `useWaitForCommit` doesn't apply because the increment is wallet-submitted and consumed externally by the network operator — the local client never sees the tx. #2111 tracks a React-SDK subscription primitive for this case (narrowed from the broader event-system discussion in #467).
|
|
183
|
+
|
|
151
184
|
## Critical Pitfalls
|
|
152
185
|
|
|
153
186
|
**WASM init must complete first**: Always use MidenProvider's `loadingComponent` or check `useMiden().isReady`. Components rendering before WASM init will crash.
|
|
@@ -167,7 +200,7 @@ For non-developer users building with this template:
|
|
|
167
200
|
3. Describe the app you want to build in natural language
|
|
168
201
|
4. Claude will implement features using TDD — tests are written first, then code
|
|
169
202
|
5. Automated hooks verify correctness at every step
|
|
170
|
-
6.
|
|
203
|
+
6. Automated hooks run full tests + build after each code edit
|
|
171
204
|
7. Review the app in the browser: `yarn dev` → open `http://localhost:5173`
|
|
172
205
|
8. If using wallet features, install the MidenFi browser extension to test
|
|
173
206
|
|
|
@@ -201,7 +234,7 @@ git clone https://github.com/vercel-labs/agent-skills.git
|
|
|
201
234
|
|
|
202
235
|
## Advanced Development
|
|
203
236
|
|
|
204
|
-
For complex applications beyond basic hook usage (custom signers,
|
|
237
|
+
For complex applications beyond basic hook usage (custom signers, direct WasmWebClient access, advanced note flows):
|
|
205
238
|
|
|
206
239
|
1. Clone `miden-client` repo alongside this project (see `frontend-source-guide` skill)
|
|
207
240
|
2. Use Plan Mode first — Claude explores React SDK source + examples before coding
|