@miden-sdk/react 0.13.1 → 0.13.2

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/CLAUDE.md ADDED
@@ -0,0 +1,398 @@
1
+ # Miden React SDK - Usage Guide
2
+
3
+ ## Installation
4
+
5
+ ```bash
6
+ npm install @miden-sdk/react @miden-sdk/miden-sdk
7
+ # or
8
+ yarn add @miden-sdk/react @miden-sdk/miden-sdk
9
+ ```
10
+
11
+ ## Getting Started
12
+
13
+ ```tsx
14
+ import { MidenProvider } from "@miden-sdk/react";
15
+
16
+ function App() {
17
+ return (
18
+ <MidenProvider config={{ rpcUrl: "testnet" }}>
19
+ <YourApp />
20
+ </MidenProvider>
21
+ );
22
+ }
23
+ ```
24
+
25
+ ## Configuration
26
+
27
+ ```tsx
28
+ <MidenProvider
29
+ config={{
30
+ rpcUrl: "testnet", // "devnet" | "testnet" | "localhost" | custom URL
31
+ prover: "testnet", // "local" | "devnet" | "testnet" | custom URL
32
+ autoSyncInterval: 15000, // ms, set to 0 to disable
33
+ noteTransportUrl: "...", // optional: for private note delivery
34
+ }}
35
+ loadingComponent={<Loading />} // shown during WASM init
36
+ errorComponent={<Error />} // shown on init failure
37
+ >
38
+ ```
39
+
40
+ | Network | Use When |
41
+ |---------|----------|
42
+ | `devnet` | Development, testing with fake tokens |
43
+ | `testnet` | Pre-production testing |
44
+ | `localhost` | Local node at `http://localhost:57291` |
45
+
46
+ ## Reading Data (Query Hooks)
47
+
48
+ All query hooks return `{ data, isLoading, error, refetch }`.
49
+
50
+ ### List Accounts
51
+ ```tsx
52
+ const { data: accounts, isLoading } = useAccounts();
53
+
54
+ // accounts.wallets - regular accounts
55
+ // accounts.faucets - token faucets
56
+ // accounts.all - everything
57
+ ```
58
+
59
+ ### Get Account Details
60
+ ```tsx
61
+ const { data: account } = useAccount(accountId);
62
+
63
+ // account.id, account.nonce, account.bech32id()
64
+ // account.balance(faucetId) - get token balance
65
+ ```
66
+
67
+ ### Get Notes
68
+ ```tsx
69
+ const { data: notes } = useNotes();
70
+
71
+ // notes.input - incoming notes
72
+ // notes.consumable - ready to claim
73
+ ```
74
+
75
+ ### Check Sync Status
76
+ ```tsx
77
+ const { syncHeight, isSyncing, sync } = useSyncState();
78
+
79
+ // Manual sync
80
+ await sync();
81
+ ```
82
+
83
+ ### Get Token Metadata
84
+ ```tsx
85
+ const { data: metadata } = useAssetMetadata(faucetId);
86
+ // metadata.symbol, metadata.decimals
87
+ ```
88
+
89
+ ## Writing Data (Mutation Hooks)
90
+
91
+ All mutation hooks return `{ mutate, data, isLoading, stage, error, reset }`.
92
+
93
+ **Transaction stages:** `idle` → `executing` → `proving` → `submitting` → `complete`
94
+
95
+ ### Create Wallet
96
+ ```tsx
97
+ const { mutate: createWallet, isLoading } = useCreateWallet();
98
+
99
+ const account = await createWallet({
100
+ storageMode: "private", // "private" | "public" | "network"
101
+ });
102
+ ```
103
+
104
+ ### Send Tokens
105
+ ```tsx
106
+ const { mutate: send, stage } = useSend();
107
+
108
+ await send({
109
+ from: senderAccountId,
110
+ to: recipientAccountId,
111
+ faucetId: tokenFaucetId,
112
+ amount: 1000n,
113
+ noteType: "private", // "private" | "public"
114
+ });
115
+ ```
116
+
117
+ ### Send to Multiple Recipients
118
+ ```tsx
119
+ const { mutate: multiSend } = useMultiSend();
120
+
121
+ await multiSend({
122
+ from: senderAccountId,
123
+ outputs: [
124
+ { to: recipient1, faucetId, amount: 500n },
125
+ { to: recipient2, faucetId, amount: 300n },
126
+ ],
127
+ });
128
+ ```
129
+
130
+ ### Claim Notes
131
+ ```tsx
132
+ const { mutate: consume } = useConsume();
133
+
134
+ await consume({
135
+ accountId: myAccountId,
136
+ noteIds: [noteId1, noteId2], // optional: consume specific notes
137
+ });
138
+ ```
139
+
140
+ ### Mint Tokens (Faucet Owner)
141
+ ```tsx
142
+ const { mutate: mint } = useMint();
143
+
144
+ await mint({
145
+ faucetId: myFaucetId,
146
+ to: recipientAccountId,
147
+ amount: 10000n,
148
+ });
149
+ ```
150
+
151
+ ### Create Faucet
152
+ ```tsx
153
+ const { mutate: createFaucet } = useCreateFaucet();
154
+
155
+ const faucet = await createFaucet({
156
+ symbol: "TOKEN",
157
+ decimals: 8,
158
+ maxSupply: 1000000n,
159
+ storageMode: "public",
160
+ });
161
+ ```
162
+
163
+ ## Common Patterns
164
+
165
+ ### Show Transaction Progress
166
+ ```tsx
167
+ function SendButton() {
168
+ const { mutate: send, stage, isLoading, error } = useSend();
169
+
170
+ const handleSend = async () => {
171
+ try {
172
+ await send({ from, to, faucetId, amount });
173
+ } catch (err) {
174
+ console.error("Transaction failed:", err);
175
+ }
176
+ };
177
+
178
+ return (
179
+ <div>
180
+ <button onClick={handleSend} disabled={isLoading}>
181
+ {isLoading ? `${stage}...` : "Send"}
182
+ </button>
183
+ {error && <p>Error: {error.message}</p>}
184
+ </div>
185
+ );
186
+ }
187
+ ```
188
+
189
+ ### Format Token Amounts
190
+ ```tsx
191
+ import { formatAssetAmount, parseAssetAmount } from "@miden-sdk/react";
192
+
193
+ // Display: 1000000n with 8 decimals → "0.01"
194
+ const display = formatAssetAmount(balance, 8);
195
+
196
+ // User input: "0.01" with 8 decimals → 1000000n
197
+ const amount = parseAssetAmount("0.01", 8);
198
+ ```
199
+
200
+ ### Display Note Summary
201
+ ```tsx
202
+ import { getNoteSummary, formatNoteSummary } from "@miden-sdk/react";
203
+
204
+ const summary = getNoteSummary(note);
205
+ const text = formatNoteSummary(summary); // "1.5 TOKEN"
206
+ ```
207
+
208
+ ### Wait for Transaction Confirmation
209
+ ```tsx
210
+ const { mutate: waitForCommit } = useWaitForCommit();
211
+
212
+ // After sending
213
+ const result = await send({ ... });
214
+ await waitForCommit({ transactionId: result.transactionId });
215
+ ```
216
+
217
+ ### Access Client Directly
218
+ ```tsx
219
+ const client = useMidenClient();
220
+
221
+ // For advanced operations not covered by hooks
222
+ const blockHeader = await client.getBlockHeaderByNumber(100);
223
+ ```
224
+
225
+ ### Prevent Race Conditions
226
+ ```tsx
227
+ const { runExclusive } = useMiden();
228
+
229
+ // Ensures sequential execution
230
+ await runExclusive(async (client) => {
231
+ // Multiple operations that must not interleave
232
+ });
233
+ ```
234
+
235
+ ## External Signer Integration
236
+
237
+ For wallets using external key management, use the pre-built signer providers:
238
+
239
+ ### Para (EVM Wallets)
240
+ ```tsx
241
+ import { ParaSignerProvider } from "@miden-sdk/para";
242
+
243
+ <ParaSignerProvider apiKey="your-api-key" environment="PRODUCTION">
244
+ <MidenProvider config={{ rpcUrl: "testnet" }}>
245
+ <App />
246
+ </MidenProvider>
247
+ </ParaSignerProvider>
248
+
249
+ // Access Para-specific data
250
+ const { para, wallet, isConnected } = useParaSigner();
251
+ ```
252
+
253
+ ### Turnkey
254
+ ```tsx
255
+ import { TurnkeySignerProvider } from "@miden-sdk/miden-turnkey-react";
256
+
257
+ // Config is optional — defaults to https://api.turnkey.com
258
+ // and reads VITE_TURNKEY_ORG_ID from environment
259
+ <TurnkeySignerProvider>
260
+ <MidenProvider config={{ rpcUrl: "testnet" }}>
261
+ <App />
262
+ </MidenProvider>
263
+ </TurnkeySignerProvider>
264
+
265
+ // Or with explicit config:
266
+ <TurnkeySignerProvider config={{
267
+ apiBaseUrl: "https://api.turnkey.com",
268
+ defaultOrganizationId: "your-org-id",
269
+ }}>
270
+ ...
271
+ </TurnkeySignerProvider>
272
+ ```
273
+
274
+ Connect via passkey authentication:
275
+ ```tsx
276
+ import { useSigner } from "@miden-sdk/react";
277
+ import { useTurnkeySigner } from "@miden-sdk/miden-turnkey-react";
278
+
279
+ // useSigner() handles connect/disconnect
280
+ const { isConnected, connect, disconnect } = useSigner();
281
+ await connect(); // triggers passkey flow, auto-selects account
282
+
283
+ // useTurnkeySigner() exposes Turnkey-specific extras
284
+ const { client, account, setAccount } = useTurnkeySigner();
285
+ ```
286
+
287
+ ### MidenFi Wallet Adapter
288
+ ```tsx
289
+ import { MidenFiSignerProvider } from "@miden-sdk/wallet-adapter-react";
290
+
291
+ <MidenFiSignerProvider network="Testnet">
292
+ <MidenProvider config={{ rpcUrl: "testnet" }}>
293
+ <App />
294
+ </MidenProvider>
295
+ </MidenFiSignerProvider>
296
+ ```
297
+
298
+ ### Using the Unified Signer Interface
299
+ ```tsx
300
+ import { useSigner } from "@miden-sdk/react";
301
+
302
+ // Works with any signer provider above
303
+ const { isConnected, connect, disconnect, name } = useSigner();
304
+
305
+ if (!isConnected) {
306
+ return <button onClick={connect}>Connect {name}</button>;
307
+ }
308
+ ```
309
+
310
+ ### Building a Custom Signer Provider
311
+ ```tsx
312
+ import { SignerContext } from "@miden-sdk/react";
313
+
314
+ <SignerContext.Provider value={{
315
+ name: "MyWallet",
316
+ storeName: `mywallet_${userAddress}`, // unique per user for DB isolation
317
+ isConnected: true,
318
+ accountConfig: {
319
+ publicKey: userPublicKeyCommitment, // Uint8Array
320
+ storageMode: "private",
321
+ },
322
+ signCb: async (pubKey, signingInputs) => {
323
+ // Route to your signing service
324
+ return signature; // Uint8Array
325
+ },
326
+ connect: async () => { /* trigger wallet connection */ },
327
+ disconnect: async () => { /* clear session */ },
328
+ }}>
329
+ <MidenProvider config={{ rpcUrl: "testnet" }}>
330
+ <App />
331
+ </MidenProvider>
332
+ </SignerContext.Provider>
333
+ ```
334
+
335
+ ## Account ID Formats
336
+
337
+ Both formats work interchangeably in all hooks:
338
+
339
+ ```tsx
340
+ // Hex format
341
+ useAccount("0x1234567890abcdef");
342
+
343
+ // Bech32 format
344
+ useAccount("miden1qy35...");
345
+
346
+ // Convert to bech32 for display
347
+ account.bech32id(); // "miden1qy35..."
348
+ ```
349
+
350
+ ## Hook Reference
351
+
352
+ | Hook | Returns | Purpose |
353
+ |------|---------|---------|
354
+ | `useAccounts()` | `{ wallets, faucets, all }` | List all accounts |
355
+ | `useAccount(id)` | `Account` | Account details + balances |
356
+ | `useNotes(filter?)` | `{ input, consumable }` | Available notes |
357
+ | `useSyncState()` | `{ syncHeight, sync() }` | Sync status |
358
+ | `useAssetMetadata(id)` | `{ symbol, decimals }` | Token info |
359
+ | `useCreateWallet()` | `Account` | Create wallet |
360
+ | `useCreateFaucet()` | `Account` | Create faucet |
361
+ | `useImportAccount()` | `Account` | Import account |
362
+ | `useSend()` | `TransactionResult` | Send tokens |
363
+ | `useMultiSend()` | `TransactionResult` | Multi-recipient send |
364
+ | `useMint()` | `TransactionResult` | Mint tokens |
365
+ | `useConsume()` | `TransactionResult` | Claim notes |
366
+ | `useSwap()` | `TransactionResult` | Atomic swap |
367
+ | `useTransaction()` | `TransactionResult` | Custom transaction |
368
+
369
+ ## Type Imports
370
+
371
+ ```tsx
372
+ import type {
373
+ // Config
374
+ MidenConfig,
375
+
376
+ // Hook results
377
+ QueryResult,
378
+ MutationResult,
379
+ AccountsResult,
380
+
381
+ // SDK types (re-exported)
382
+ Account,
383
+ AccountId,
384
+ Note,
385
+ TransactionRecord,
386
+ FungibleAsset,
387
+ } from "@miden-sdk/react";
388
+ ```
389
+
390
+ ## Troubleshooting
391
+
392
+ | Issue | Solution |
393
+ |-------|----------|
394
+ | "Client not ready" | Wrap component in `MidenProvider`, check `useMiden().isReady` |
395
+ | Transaction stuck | Check `stage` value, network connectivity, prover availability |
396
+ | Notes not appearing | Call `sync()` manually, check `autoSyncInterval` config |
397
+ | Bech32 address wrong | Verify `rpcUrl` matches intended network |
398
+ | WASM init fails | Check browser compatibility, ensure WASM served with correct MIME type |
package/dist/index.js CHANGED
@@ -499,7 +499,7 @@ function MidenProvider({
499
499
  (0, import_react2.useEffect)(() => {
500
500
  if (!signerContext && isInitializedRef.current) return;
501
501
  if (signerContext && !signerContext.isConnected) {
502
- if (client) {
502
+ if (useMidenStore.getState().client) {
503
503
  useMidenStore.getState().reset();
504
504
  setClient(null);
505
505
  setSignerAccountId(null);
@@ -509,53 +509,80 @@ function MidenProvider({
509
509
  if (!signerContext) {
510
510
  isInitializedRef.current = true;
511
511
  }
512
+ let cancelled = false;
512
513
  const initClient = async () => {
513
- setInitializing(true);
514
- setConfig(resolvedConfig);
515
- try {
516
- let webClient;
517
- if (signerContext && signerContext.isConnected) {
518
- const storeName = `MidenClientDB_${signerContext.storeName}`;
519
- webClient = await import_miden_sdk5.WebClient.createClientWithExternalKeystore(
520
- resolvedConfig.rpcUrl,
521
- resolvedConfig.noteTransportUrl,
522
- resolvedConfig.seed,
523
- storeName,
524
- void 0,
525
- // getKeyCb - not needed for public accounts
526
- void 0,
527
- // insertKeyCb - not needed for public accounts
528
- signerContext.signCb
529
- );
530
- const accountId = await initializeSignerAccount(
531
- webClient,
532
- signerContext.accountConfig
533
- );
534
- setSignerAccountId(accountId);
535
- } else {
536
- const seed = resolvedConfig.seed;
537
- webClient = await import_miden_sdk5.WebClient.createClient(
538
- resolvedConfig.rpcUrl,
539
- resolvedConfig.noteTransportUrl,
540
- seed
541
- );
542
- }
543
- setClient(webClient);
514
+ await runExclusive(async () => {
515
+ if (cancelled) return;
516
+ setInitializing(true);
517
+ setConfig(resolvedConfig);
544
518
  try {
545
- const summary = await runExclusive(() => webClient.syncState());
546
- setSyncState({
547
- syncHeight: summary.blockNum(),
548
- lastSyncTime: Date.now()
549
- });
550
- const accounts = await webClient.getAccounts();
551
- useMidenStore.getState().setAccounts(accounts);
552
- } catch {
519
+ let webClient;
520
+ let didSignerInit = false;
521
+ if (signerContext && signerContext.isConnected) {
522
+ const storeName = `MidenClientDB_${signerContext.storeName}`;
523
+ webClient = await import_miden_sdk5.WebClient.createClientWithExternalKeystore(
524
+ resolvedConfig.rpcUrl,
525
+ resolvedConfig.noteTransportUrl,
526
+ resolvedConfig.seed,
527
+ storeName,
528
+ void 0,
529
+ // getKeyCb - not needed for public accounts
530
+ void 0,
531
+ // insertKeyCb - not needed for public accounts
532
+ signerContext.signCb
533
+ );
534
+ if (cancelled) return;
535
+ const accountId = await initializeSignerAccount(
536
+ webClient,
537
+ signerContext.accountConfig
538
+ );
539
+ if (cancelled) return;
540
+ setSignerAccountId(accountId);
541
+ didSignerInit = true;
542
+ } else {
543
+ const seed = resolvedConfig.seed;
544
+ webClient = await import_miden_sdk5.WebClient.createClient(
545
+ resolvedConfig.rpcUrl,
546
+ resolvedConfig.noteTransportUrl,
547
+ seed
548
+ );
549
+ if (cancelled) return;
550
+ }
551
+ if (!didSignerInit) {
552
+ try {
553
+ const summary = await webClient.syncState();
554
+ if (cancelled) return;
555
+ setSyncState({
556
+ syncHeight: summary.blockNum(),
557
+ lastSyncTime: Date.now()
558
+ });
559
+ } catch {
560
+ }
561
+ }
562
+ if (!cancelled) {
563
+ try {
564
+ const accounts = await webClient.getAccounts();
565
+ if (cancelled) return;
566
+ useMidenStore.getState().setAccounts(accounts);
567
+ } catch {
568
+ }
569
+ }
570
+ if (!cancelled) {
571
+ setClient(webClient);
572
+ }
573
+ } catch (error) {
574
+ if (!cancelled) {
575
+ setInitError(
576
+ error instanceof Error ? error : new Error(String(error))
577
+ );
578
+ }
553
579
  }
554
- } catch (error) {
555
- setInitError(error instanceof Error ? error : new Error(String(error)));
556
- }
580
+ });
557
581
  };
558
582
  initClient();
583
+ return () => {
584
+ cancelled = true;
585
+ };
559
586
  }, [
560
587
  runExclusive,
561
588
  resolvedConfig,
@@ -564,8 +591,7 @@ function MidenProvider({
564
591
  setInitError,
565
592
  setInitializing,
566
593
  setSyncState,
567
- signerContext,
568
- client
594
+ signerContext
569
595
  ]);
570
596
  (0, import_react2.useEffect)(() => {
571
597
  if (!isReady || !client) return;
package/dist/index.mjs CHANGED
@@ -448,7 +448,7 @@ function MidenProvider({
448
448
  useEffect(() => {
449
449
  if (!signerContext && isInitializedRef.current) return;
450
450
  if (signerContext && !signerContext.isConnected) {
451
- if (client) {
451
+ if (useMidenStore.getState().client) {
452
452
  useMidenStore.getState().reset();
453
453
  setClient(null);
454
454
  setSignerAccountId(null);
@@ -458,53 +458,80 @@ function MidenProvider({
458
458
  if (!signerContext) {
459
459
  isInitializedRef.current = true;
460
460
  }
461
+ let cancelled = false;
461
462
  const initClient = async () => {
462
- setInitializing(true);
463
- setConfig(resolvedConfig);
464
- try {
465
- let webClient;
466
- if (signerContext && signerContext.isConnected) {
467
- const storeName = `MidenClientDB_${signerContext.storeName}`;
468
- webClient = await WebClient.createClientWithExternalKeystore(
469
- resolvedConfig.rpcUrl,
470
- resolvedConfig.noteTransportUrl,
471
- resolvedConfig.seed,
472
- storeName,
473
- void 0,
474
- // getKeyCb - not needed for public accounts
475
- void 0,
476
- // insertKeyCb - not needed for public accounts
477
- signerContext.signCb
478
- );
479
- const accountId = await initializeSignerAccount(
480
- webClient,
481
- signerContext.accountConfig
482
- );
483
- setSignerAccountId(accountId);
484
- } else {
485
- const seed = resolvedConfig.seed;
486
- webClient = await WebClient.createClient(
487
- resolvedConfig.rpcUrl,
488
- resolvedConfig.noteTransportUrl,
489
- seed
490
- );
491
- }
492
- setClient(webClient);
463
+ await runExclusive(async () => {
464
+ if (cancelled) return;
465
+ setInitializing(true);
466
+ setConfig(resolvedConfig);
493
467
  try {
494
- const summary = await runExclusive(() => webClient.syncState());
495
- setSyncState({
496
- syncHeight: summary.blockNum(),
497
- lastSyncTime: Date.now()
498
- });
499
- const accounts = await webClient.getAccounts();
500
- useMidenStore.getState().setAccounts(accounts);
501
- } catch {
468
+ let webClient;
469
+ let didSignerInit = false;
470
+ if (signerContext && signerContext.isConnected) {
471
+ const storeName = `MidenClientDB_${signerContext.storeName}`;
472
+ webClient = await WebClient.createClientWithExternalKeystore(
473
+ resolvedConfig.rpcUrl,
474
+ resolvedConfig.noteTransportUrl,
475
+ resolvedConfig.seed,
476
+ storeName,
477
+ void 0,
478
+ // getKeyCb - not needed for public accounts
479
+ void 0,
480
+ // insertKeyCb - not needed for public accounts
481
+ signerContext.signCb
482
+ );
483
+ if (cancelled) return;
484
+ const accountId = await initializeSignerAccount(
485
+ webClient,
486
+ signerContext.accountConfig
487
+ );
488
+ if (cancelled) return;
489
+ setSignerAccountId(accountId);
490
+ didSignerInit = true;
491
+ } else {
492
+ const seed = resolvedConfig.seed;
493
+ webClient = await WebClient.createClient(
494
+ resolvedConfig.rpcUrl,
495
+ resolvedConfig.noteTransportUrl,
496
+ seed
497
+ );
498
+ if (cancelled) return;
499
+ }
500
+ if (!didSignerInit) {
501
+ try {
502
+ const summary = await webClient.syncState();
503
+ if (cancelled) return;
504
+ setSyncState({
505
+ syncHeight: summary.blockNum(),
506
+ lastSyncTime: Date.now()
507
+ });
508
+ } catch {
509
+ }
510
+ }
511
+ if (!cancelled) {
512
+ try {
513
+ const accounts = await webClient.getAccounts();
514
+ if (cancelled) return;
515
+ useMidenStore.getState().setAccounts(accounts);
516
+ } catch {
517
+ }
518
+ }
519
+ if (!cancelled) {
520
+ setClient(webClient);
521
+ }
522
+ } catch (error) {
523
+ if (!cancelled) {
524
+ setInitError(
525
+ error instanceof Error ? error : new Error(String(error))
526
+ );
527
+ }
502
528
  }
503
- } catch (error) {
504
- setInitError(error instanceof Error ? error : new Error(String(error)));
505
- }
529
+ });
506
530
  };
507
531
  initClient();
532
+ return () => {
533
+ cancelled = true;
534
+ };
508
535
  }, [
509
536
  runExclusive,
510
537
  resolvedConfig,
@@ -513,8 +540,7 @@ function MidenProvider({
513
540
  setInitError,
514
541
  setInitializing,
515
542
  setSyncState,
516
- signerContext,
517
- client
543
+ signerContext
518
544
  ]);
519
545
  useEffect(() => {
520
546
  if (!isReady || !client) return;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@miden-sdk/react",
3
- "version": "0.13.1",
3
+ "version": "0.13.2",
4
4
  "description": "React hooks library for Miden Web Client",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",
@@ -14,7 +14,8 @@
14
14
  },
15
15
  "files": [
16
16
  "dist",
17
- "README.md"
17
+ "README.md",
18
+ "CLAUDE.md"
18
19
  ],
19
20
  "scripts": {
20
21
  "build": "tsup src/index.ts --format cjs,esm --dts --clean",