@miden-sdk/miden-wallet-adapter-react 0.13.0

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 (38) hide show
  1. package/MidenFiSignerProvider.tsx +732 -0
  2. package/WalletProvider.tsx +443 -0
  3. package/__tests__/MidenFiSignerProvider.test.tsx +426 -0
  4. package/dist/MidenFiSignerProvider.d.ts +113 -0
  5. package/dist/MidenFiSignerProvider.js +501 -0
  6. package/dist/MidenFiSignerProvider.js.map +1 -0
  7. package/dist/WalletProvider.d.ts +13 -0
  8. package/dist/WalletProvider.js +302 -0
  9. package/dist/WalletProvider.js.map +1 -0
  10. package/dist/index.d.ts +4 -0
  11. package/dist/index.js +7 -0
  12. package/dist/index.js.map +1 -0
  13. package/dist/useLocalStorage.d.ts +2 -0
  14. package/dist/useLocalStorage.js +39 -0
  15. package/dist/useLocalStorage.js.map +1 -0
  16. package/dist/useWallet.d.ts +30 -0
  17. package/dist/useWallet.js +81 -0
  18. package/dist/useWallet.js.map +1 -0
  19. package/dist/vitest.config.d.ts +2 -0
  20. package/dist/vitest.config.js +8 -0
  21. package/dist/vitest.config.js.map +1 -0
  22. package/docs/README.md +30 -0
  23. package/docs/functions/useLocalStorage.md +29 -0
  24. package/docs/functions/useMidenFiWallet.md +28 -0
  25. package/docs/functions/useWallet.md +13 -0
  26. package/docs/interfaces/MidenFiSignerProviderProps.md +87 -0
  27. package/docs/interfaces/Wallet.md +19 -0
  28. package/docs/interfaces/WalletContextState.md +235 -0
  29. package/docs/interfaces/WalletProviderProps.md +65 -0
  30. package/docs/variables/MidenFiSignerProvider.md +54 -0
  31. package/docs/variables/WalletContext.md +9 -0
  32. package/docs/variables/WalletProvider.md +9 -0
  33. package/index.ts +15 -0
  34. package/package.json +42 -0
  35. package/tsconfig.json +16 -0
  36. package/useLocalStorage.ts +39 -0
  37. package/useWallet.ts +197 -0
  38. package/vitest.config.ts +8 -0
@@ -0,0 +1,732 @@
1
+ import {
2
+ useState,
3
+ useEffect,
4
+ useCallback,
5
+ useMemo,
6
+ useRef,
7
+ createContext,
8
+ useContext,
9
+ type FC,
10
+ type ReactNode,
11
+ } from 'react';
12
+ import { SignerContext, type SignerContextValue } from '@miden-sdk/react';
13
+ import {
14
+ type Adapter,
15
+ AllowedPrivateData,
16
+ type MessageSignerWalletAdapterProps,
17
+ type MidenTransaction,
18
+ PrivateDataPermission,
19
+ SignKind,
20
+ WalletAdapterNetwork,
21
+ WalletError,
22
+ type WalletName,
23
+ WalletNotConnectedError,
24
+ WalletNotReadyError,
25
+ WalletNotSelectedError,
26
+ WalletReadyState,
27
+ type MidenSendTransaction,
28
+ type MidenConsumeTransaction,
29
+ type Asset,
30
+ type InputNoteDetails,
31
+ type TransactionOutput,
32
+ } from '@miden-sdk/miden-wallet-adapter-base';
33
+ import type { NoteFilterTypes } from '@miden-sdk/miden-sdk';
34
+ import { MidenWalletAdapter } from '@miden-sdk/miden-wallet-adapter-miden';
35
+ import { useLocalStorage } from './useLocalStorage';
36
+
37
+ // TYPES
38
+ // ================================================================================================
39
+
40
+ export interface Wallet {
41
+ adapter: Adapter;
42
+ readyState: WalletReadyState;
43
+ }
44
+
45
+ export interface WalletContextState {
46
+ autoConnect: boolean;
47
+ wallets: Wallet[];
48
+ wallet: Wallet | null;
49
+ address: string | null;
50
+ publicKey: Uint8Array | null;
51
+ connected: boolean;
52
+ connecting: boolean;
53
+ disconnecting: boolean;
54
+
55
+ select(walletName: WalletName): void;
56
+ connect(): Promise<void>;
57
+ disconnect(): Promise<void>;
58
+
59
+ requestTransaction?: MessageSignerWalletAdapterProps['requestTransaction'];
60
+ requestAssets?: MessageSignerWalletAdapterProps['requestAssets'];
61
+ requestPrivateNotes?: MessageSignerWalletAdapterProps['requestPrivateNotes'];
62
+ signBytes?: MessageSignerWalletAdapterProps['signBytes'];
63
+ importPrivateNote?: MessageSignerWalletAdapterProps['importPrivateNote'];
64
+ requestConsumableNotes?: MessageSignerWalletAdapterProps['requestConsumableNotes'];
65
+ waitForTransaction?: MessageSignerWalletAdapterProps['waitForTransaction'];
66
+ requestSend?: MessageSignerWalletAdapterProps['requestSend'];
67
+ requestConsume?: MessageSignerWalletAdapterProps['requestConsume'];
68
+ }
69
+
70
+ const WalletContext = createContext<WalletContextState>({} as WalletContextState);
71
+
72
+ // MIDENFI SIGNER PROVIDER
73
+ // ================================================================================================
74
+
75
+ export interface MidenFiSignerProviderProps {
76
+ children: ReactNode;
77
+ /** Wallet adapters to use. Defaults to [MidenWalletAdapter] */
78
+ wallets?: Adapter[];
79
+ /** App name passed to the default MidenWalletAdapter */
80
+ appName?: string;
81
+ /** Network to connect to */
82
+ network?: WalletAdapterNetwork;
83
+ /** Auto-connect to previously selected wallet on mount. Defaults to true */
84
+ autoConnect?: boolean;
85
+ /** Private data permission level */
86
+ privateDataPermission?: PrivateDataPermission;
87
+ /** Allowed private data types */
88
+ allowedPrivateData?: AllowedPrivateData;
89
+ /** Error handler */
90
+ onError?: (error: WalletError) => void;
91
+ /** LocalStorage key for persisting wallet selection */
92
+ localStorageKey?: string;
93
+ }
94
+
95
+ const initialState: {
96
+ wallet: Wallet | null;
97
+ adapter: Adapter | null;
98
+ address: string | null;
99
+ publicKey: Uint8Array | null;
100
+ connected: boolean;
101
+ } = {
102
+ wallet: null,
103
+ adapter: null,
104
+ address: null,
105
+ publicKey: null,
106
+ connected: false,
107
+ };
108
+
109
+ /**
110
+ * MidenFiSignerProvider bridges the MidenFi wallet with MidenProvider.
111
+ *
112
+ * This is a unified provider that handles both wallet connection and signer context.
113
+ *
114
+ * @example
115
+ * ```tsx
116
+ * // Simplest usage - uses MidenWalletAdapter by default
117
+ * <MidenFiSignerProvider>
118
+ * <MidenProvider config={{ rpcUrl: "testnet" }}>
119
+ * <App />
120
+ * </MidenProvider>
121
+ * </MidenFiSignerProvider>
122
+ *
123
+ * // With custom options
124
+ * <MidenFiSignerProvider
125
+ * appName="My DApp"
126
+ * network={WalletAdapterNetwork.Testnet}
127
+ * autoConnect={true}
128
+ * >
129
+ * <MidenProvider config={{ rpcUrl: "testnet" }}>
130
+ * <App />
131
+ * </MidenProvider>
132
+ * </MidenFiSignerProvider>
133
+ *
134
+ * // With custom wallets
135
+ * <MidenFiSignerProvider wallets={[new CustomWalletAdapter()]}>
136
+ * <MidenProvider config={{ rpcUrl: "testnet" }}>
137
+ * <App />
138
+ * </MidenProvider>
139
+ * </MidenFiSignerProvider>
140
+ * ```
141
+ *
142
+ * For wallet operations, use the useMidenFiWallet hook:
143
+ *
144
+ * @example
145
+ * ```tsx
146
+ * const { connected, connect, disconnect, select, wallets } = useMidenFiWallet();
147
+ *
148
+ * // If multiple wallets, select one first
149
+ * select(wallets[0].adapter.name);
150
+ *
151
+ * // Then connect
152
+ * await connect();
153
+ * ```
154
+ */
155
+ export const MidenFiSignerProvider: FC<MidenFiSignerProviderProps> = ({
156
+ children,
157
+ wallets: walletsProp,
158
+ appName = 'Miden DApp',
159
+ network = WalletAdapterNetwork.Testnet,
160
+ autoConnect = true,
161
+ privateDataPermission = PrivateDataPermission.UponRequest,
162
+ allowedPrivateData = AllowedPrivateData.None,
163
+ onError,
164
+ localStorageKey = 'walletName',
165
+ }) => {
166
+ // Create default wallets if not provided
167
+ const adapters = useMemo(
168
+ () => walletsProp ?? [new MidenWalletAdapter({ appName })],
169
+ [walletsProp, appName]
170
+ );
171
+
172
+ const [name, setName] = useLocalStorage<WalletName | null>(
173
+ localStorageKey,
174
+ null
175
+ );
176
+ const [{ wallet, adapter, address, publicKey, connected }, setState] =
177
+ useState(initialState);
178
+ const readyState = adapter?.readyState || WalletReadyState.Unsupported;
179
+ const [connecting, setConnecting] = useState(false);
180
+ const [disconnecting, setDisconnecting] = useState(false);
181
+ const isConnecting = useRef(false);
182
+ const isDisconnecting = useRef(false);
183
+ const isUnloading = useRef(false);
184
+
185
+ // Wrap adapters to conform to the `Wallet` interface
186
+ const [wallets, setWallets] = useState(() =>
187
+ adapters.map((adapter) => ({
188
+ adapter,
189
+ readyState: adapter.readyState,
190
+ }))
191
+ );
192
+
193
+ // When the adapters change, start to listen for changes to their `readyState`
194
+ useEffect(() => {
195
+ setWallets((wallets) =>
196
+ adapters.map((adapter, index) => {
197
+ const wallet = wallets[index];
198
+ return wallet &&
199
+ wallet.adapter === adapter &&
200
+ wallet.readyState === adapter.readyState
201
+ ? wallet
202
+ : {
203
+ adapter: adapter,
204
+ readyState: adapter.readyState,
205
+ };
206
+ })
207
+ );
208
+
209
+ function handleReadyStateChange(
210
+ this: Adapter,
211
+ readyState: WalletReadyState
212
+ ) {
213
+ setWallets((prevWallets) => {
214
+ const index = prevWallets.findIndex(({ adapter }) => adapter === this);
215
+ if (index === -1) return prevWallets;
216
+
217
+ const { adapter } = prevWallets[index]!;
218
+ return [
219
+ ...prevWallets.slice(0, index),
220
+ { adapter, readyState },
221
+ ...prevWallets.slice(index + 1),
222
+ ];
223
+ });
224
+ }
225
+
226
+ adapters.forEach((adapter) =>
227
+ adapter.on('readyStateChange', handleReadyStateChange, adapter)
228
+ );
229
+ return () =>
230
+ adapters.forEach((adapter) =>
231
+ adapter.off('readyStateChange', handleReadyStateChange, adapter)
232
+ );
233
+ }, [adapters]);
234
+
235
+ // When the selected wallet changes, initialize the state.
236
+ // Use a functional update to bail out when nothing has actually changed,
237
+ // preventing unnecessary re-renders that cause WASM concurrency races.
238
+ useEffect(() => {
239
+ const found = name && wallets.find(({ adapter }) => adapter.name === name);
240
+ if (found) {
241
+ setState((prev) => {
242
+ if (
243
+ prev.wallet === found &&
244
+ prev.adapter === found.adapter &&
245
+ prev.connected === found.adapter.connected &&
246
+ prev.address === found.adapter.address &&
247
+ prev.publicKey === found.adapter.publicKey
248
+ )
249
+ return prev;
250
+ return {
251
+ wallet: found,
252
+ adapter: found.adapter,
253
+ connected: found.adapter.connected,
254
+ address: found.adapter.address,
255
+ publicKey: found.adapter.publicKey,
256
+ };
257
+ });
258
+ } else {
259
+ setState((prev) => (prev === initialState ? prev : initialState));
260
+ }
261
+ }, [name, wallets]);
262
+
263
+ // If the window is closing or reloading, ignore disconnect and error events
264
+ useEffect(() => {
265
+ function listener() {
266
+ isUnloading.current = true;
267
+ }
268
+
269
+ window.addEventListener('beforeunload', listener);
270
+ return () => window.removeEventListener('beforeunload', listener);
271
+ }, [isUnloading]);
272
+
273
+ // Handle the adapter's connect event.
274
+ // Functional update bails out when values haven't changed to prevent re-render loops.
275
+ const handleConnect = useCallback(() => {
276
+ if (!adapter) return;
277
+ setState((prev) => {
278
+ if (
279
+ prev.connected === adapter.connected &&
280
+ prev.address === adapter.address &&
281
+ prev.publicKey === adapter.publicKey
282
+ )
283
+ return prev;
284
+ return {
285
+ ...prev,
286
+ connected: adapter.connected,
287
+ address: adapter.address,
288
+ publicKey: adapter.publicKey,
289
+ };
290
+ });
291
+ }, [adapter]);
292
+
293
+ // Handle the adapter's disconnect event
294
+ const handleDisconnect = useCallback(() => {
295
+ if (!isUnloading.current) setName(null);
296
+ }, [isUnloading, setName]);
297
+
298
+ // Handle the adapter's error event
299
+ const handleError = useCallback(
300
+ (error: WalletError) => {
301
+ if (!isUnloading.current) (onError || console.error)(error);
302
+ return error;
303
+ },
304
+ [isUnloading, onError]
305
+ );
306
+
307
+ // Setup and teardown event listeners when the adapter changes
308
+ useEffect(() => {
309
+ if (adapter) {
310
+ adapter.on('connect', handleConnect);
311
+ adapter.on('disconnect', handleDisconnect);
312
+ adapter.on('error', handleError);
313
+ return () => {
314
+ adapter.off('connect', handleConnect);
315
+ adapter.off('disconnect', handleDisconnect);
316
+ adapter.off('error', handleError);
317
+ };
318
+ }
319
+ }, [adapter, handleConnect, handleDisconnect, handleError]);
320
+
321
+ // When the adapter changes, disconnect the old one
322
+ useEffect(() => {
323
+ return () => {
324
+ adapter?.disconnect();
325
+ };
326
+ }, [adapter]);
327
+
328
+ // Auto-select the first wallet if only one is available and none selected
329
+ useEffect(() => {
330
+ if (!name && wallets.length === 1) {
331
+ setName(wallets[0].adapter.name);
332
+ }
333
+ }, [name, wallets, setName]);
334
+
335
+ // If autoConnect is enabled, try to connect when the adapter changes and is ready
336
+ useEffect(() => {
337
+ if (
338
+ isConnecting.current ||
339
+ connected ||
340
+ !autoConnect ||
341
+ !adapter ||
342
+ !(
343
+ readyState === WalletReadyState.Installed ||
344
+ readyState === WalletReadyState.Loadable
345
+ )
346
+ )
347
+ return;
348
+
349
+ (async function () {
350
+ isConnecting.current = true;
351
+ setConnecting(true);
352
+ try {
353
+ await adapter.connect(
354
+ privateDataPermission,
355
+ network,
356
+ allowedPrivateData
357
+ );
358
+ } catch (error: any) {
359
+ setName(null);
360
+ } finally {
361
+ setConnecting(false);
362
+ isConnecting.current = false;
363
+ }
364
+ })();
365
+ }, [isConnecting, connected, autoConnect, adapter, readyState, setName, privateDataPermission, network, allowedPrivateData]);
366
+
367
+ // Connect the adapter to the wallet
368
+ const connect = useCallback(async () => {
369
+ if (isConnecting.current || isDisconnecting.current || connected) return;
370
+ if (!adapter) throw handleError(new WalletNotSelectedError());
371
+
372
+ if (
373
+ !(
374
+ readyState === WalletReadyState.Installed ||
375
+ readyState === WalletReadyState.Loadable
376
+ )
377
+ ) {
378
+ setName(null);
379
+
380
+ if (typeof window !== 'undefined') {
381
+ window.open(adapter.url, '_blank');
382
+ }
383
+
384
+ throw handleError(new WalletNotReadyError());
385
+ }
386
+
387
+ isConnecting.current = true;
388
+ setConnecting(true);
389
+ try {
390
+ await adapter.connect(privateDataPermission, network, allowedPrivateData);
391
+ } catch (error: any) {
392
+ setName(null);
393
+ throw error;
394
+ } finally {
395
+ setConnecting(false);
396
+ isConnecting.current = false;
397
+ }
398
+ }, [
399
+ isConnecting,
400
+ isDisconnecting,
401
+ connected,
402
+ adapter,
403
+ readyState,
404
+ handleError,
405
+ setName,
406
+ privateDataPermission,
407
+ network,
408
+ allowedPrivateData,
409
+ ]);
410
+
411
+ // Disconnect the adapter from the wallet
412
+ const disconnect = useCallback(async () => {
413
+ if (isDisconnecting.current) return;
414
+ if (!adapter) return setName(null);
415
+
416
+ isDisconnecting.current = true;
417
+ setDisconnecting(true);
418
+ try {
419
+ await adapter.disconnect();
420
+ } catch (error: any) {
421
+ setName(null);
422
+ throw error;
423
+ } finally {
424
+ setDisconnecting(false);
425
+ isDisconnecting.current = false;
426
+ }
427
+ }, [isDisconnecting, adapter, setName]);
428
+
429
+ // Request transaction
430
+ const requestTransaction:
431
+ | MessageSignerWalletAdapterProps['requestTransaction']
432
+ | undefined = useMemo(
433
+ () =>
434
+ adapter && 'requestTransaction' in adapter
435
+ ? async (transaction: MidenTransaction) => {
436
+ if (!connected) throw handleError(new WalletNotConnectedError());
437
+ return await adapter.requestTransaction(transaction);
438
+ }
439
+ : undefined,
440
+ [adapter, handleError, connected]
441
+ );
442
+
443
+ // Request assets
444
+ const requestAssets:
445
+ | MessageSignerWalletAdapterProps['requestAssets']
446
+ | undefined = useMemo(
447
+ () =>
448
+ adapter && 'requestAssets' in adapter
449
+ ? async () => {
450
+ if (!connected) throw handleError(new WalletNotConnectedError());
451
+ return await adapter.requestAssets();
452
+ }
453
+ : undefined,
454
+ [adapter, handleError, connected]
455
+ );
456
+
457
+ // Request private notes
458
+ const requestPrivateNotes:
459
+ | MessageSignerWalletAdapterProps['requestPrivateNotes']
460
+ | undefined = useMemo(
461
+ () =>
462
+ adapter && 'requestPrivateNotes' in adapter
463
+ ? async (noteFilterType: NoteFilterTypes, noteIds?: string[]) => {
464
+ if (!connected) throw handleError(new WalletNotConnectedError());
465
+ return await adapter.requestPrivateNotes(noteFilterType, noteIds);
466
+ }
467
+ : undefined,
468
+ [adapter, handleError, connected]
469
+ );
470
+
471
+ const signBytes: MessageSignerWalletAdapterProps['signBytes'] | undefined =
472
+ useMemo(
473
+ () =>
474
+ adapter && 'signBytes' in adapter
475
+ ? async (message: Uint8Array, kind: SignKind) => {
476
+ if (!connected) throw handleError(new WalletNotConnectedError());
477
+ return await adapter.signBytes(message, kind);
478
+ }
479
+ : undefined,
480
+ [adapter, handleError, connected]
481
+ );
482
+
483
+ const importPrivateNote:
484
+ | MessageSignerWalletAdapterProps['importPrivateNote']
485
+ | undefined = useMemo(
486
+ () =>
487
+ adapter && 'importPrivateNote' in adapter
488
+ ? async (note: Uint8Array) => {
489
+ if (!connected) throw handleError(new WalletNotConnectedError());
490
+ return await adapter.importPrivateNote(note);
491
+ }
492
+ : undefined,
493
+ [adapter, handleError, connected]
494
+ );
495
+
496
+ const requestConsumableNotes:
497
+ | MessageSignerWalletAdapterProps['requestConsumableNotes']
498
+ | undefined = useMemo(
499
+ () =>
500
+ adapter && 'requestConsumableNotes' in adapter
501
+ ? async () => {
502
+ if (!connected) throw handleError(new WalletNotConnectedError());
503
+ return await adapter.requestConsumableNotes();
504
+ }
505
+ : undefined,
506
+ [adapter, handleError, connected]
507
+ );
508
+
509
+ const waitForTransaction:
510
+ | MessageSignerWalletAdapterProps['waitForTransaction']
511
+ | undefined = useMemo(
512
+ () =>
513
+ adapter && 'waitForTransaction' in adapter
514
+ ? async (txId: string, timeout?: number) => {
515
+ if (!connected) throw handleError(new WalletNotConnectedError());
516
+ return await adapter.waitForTransaction(txId, timeout);
517
+ }
518
+ : undefined,
519
+ [adapter, handleError, connected]
520
+ );
521
+
522
+ const requestSend:
523
+ | MessageSignerWalletAdapterProps['requestSend']
524
+ | undefined = useMemo(
525
+ () =>
526
+ adapter && 'requestSend' in adapter
527
+ ? async (transaction) => {
528
+ if (!connected) throw handleError(new WalletNotConnectedError());
529
+ return await adapter.requestSend(transaction);
530
+ }
531
+ : undefined,
532
+ [adapter, handleError, connected]
533
+ );
534
+
535
+ const requestConsume:
536
+ | MessageSignerWalletAdapterProps['requestConsume']
537
+ | undefined = useMemo(
538
+ () =>
539
+ adapter && 'requestConsume' in adapter
540
+ ? async (transaction) => {
541
+ if (!connected) throw handleError(new WalletNotConnectedError());
542
+ return await adapter.requestConsume(transaction);
543
+ }
544
+ : undefined,
545
+ [adapter, handleError, connected]
546
+ );
547
+
548
+ // Build SignerContext value.
549
+ //
550
+ // CRITICAL: signerContext MUST be referentially stable. MidenProvider's init
551
+ // effect has signerContext in its deps — every new object re-triggers initClient
552
+ // which does async WASM work. Two concurrent initClient calls cause the
553
+ // "recursive use of an object" crash.
554
+ //
555
+ // We keep a single mutable ref and only call setSignerContext when the ref
556
+ // identity actually needs to change (disconnected ↔ connected, or address change).
557
+ //
558
+ // Initialise as {isConnected:false} rather than null so MidenProvider's init
559
+ // effect hits the early-return path instead of creating a local-keystore client
560
+ // that races with our buildContext's WASM operations.
561
+
562
+ // Keep signBytes in a ref so buildContext doesn't re-run when its identity changes.
563
+ const signBytesRef = useRef(signBytes);
564
+ useEffect(() => { signBytesRef.current = signBytes; }, [signBytes]);
565
+ const connectRef = useRef(connect);
566
+ useEffect(() => { connectRef.current = connect; }, [connect]);
567
+ const disconnectRef = useRef(disconnect);
568
+ useEffect(() => { disconnectRef.current = disconnect; }, [disconnect]);
569
+
570
+ const disconnectedCtx = useRef<SignerContextValue>({
571
+ signCb: async () => { throw new Error('MidenFi wallet not connected'); },
572
+ accountConfig: null as any,
573
+ storeName: '',
574
+ name: 'MidenFi',
575
+ isConnected: false,
576
+ connect: connectRef.current,
577
+ disconnect: disconnectRef.current,
578
+ });
579
+
580
+ // The connected context ref — reused across renders to maintain referential identity.
581
+ const connectedCtxRef = useRef<SignerContextValue | null>(null);
582
+ // Track which address the connected context was built for.
583
+ const connectedAddressRef = useRef<string | null>(null);
584
+
585
+ const [signerContext, setSignerContext] = useState<SignerContextValue>(
586
+ disconnectedCtx.current
587
+ );
588
+
589
+ useEffect(() => {
590
+ let cancelled = false;
591
+
592
+ async function buildContext() {
593
+ if (!connected || !publicKey || !address || !signBytesRef.current) {
594
+ // Already disconnected — don't set state again (same ref = no re-render).
595
+ if (connectedCtxRef.current !== null) {
596
+ connectedCtxRef.current = null;
597
+ connectedAddressRef.current = null;
598
+ setSignerContext(disconnectedCtx.current);
599
+ }
600
+ return;
601
+ }
602
+
603
+ // Already built for this address — reuse existing context (same ref).
604
+ if (connectedCtxRef.current && connectedAddressRef.current === address) {
605
+ return;
606
+ }
607
+
608
+ try {
609
+ if (!cancelled) {
610
+ const { AccountStorageMode } = await import('@miden-sdk/miden-sdk');
611
+
612
+ const signCb = async (_: Uint8Array, signingInputs: Uint8Array) => {
613
+ const result = await signBytesRef.current!(signingInputs, 'signingInputs');
614
+ return result;
615
+ };
616
+
617
+ const ctx: SignerContextValue = {
618
+ signCb,
619
+ accountConfig: {
620
+ publicKeyCommitment: publicKey,
621
+ accountType: 'RegularAccountImmutableCode',
622
+ storageMode: AccountStorageMode.public(),
623
+ },
624
+ storeName: `midenfi_${address}`,
625
+ name: 'MidenFi',
626
+ isConnected: true,
627
+ connect: connectRef.current,
628
+ disconnect: disconnectRef.current,
629
+ };
630
+
631
+ connectedCtxRef.current = ctx;
632
+ connectedAddressRef.current = address;
633
+ setSignerContext(ctx);
634
+ }
635
+ } catch (error) {
636
+ console.error('Failed to build MidenFi signer context:', error);
637
+ if (!cancelled) {
638
+ connectedCtxRef.current = null;
639
+ connectedAddressRef.current = null;
640
+ setSignerContext(disconnectedCtx.current);
641
+ }
642
+ }
643
+ }
644
+
645
+ buildContext();
646
+ return () => {
647
+ cancelled = true;
648
+ };
649
+ }, [connected, publicKey, address]);
650
+
651
+ const walletContextValue = useMemo(
652
+ () => ({
653
+ autoConnect,
654
+ wallets,
655
+ wallet,
656
+ address,
657
+ publicKey,
658
+ connected,
659
+ connecting,
660
+ disconnecting,
661
+ select: setName,
662
+ connect,
663
+ disconnect,
664
+ requestTransaction,
665
+ requestAssets,
666
+ requestPrivateNotes,
667
+ signBytes,
668
+ importPrivateNote,
669
+ requestConsumableNotes,
670
+ waitForTransaction,
671
+ requestSend,
672
+ requestConsume,
673
+ }),
674
+ [
675
+ autoConnect,
676
+ wallets,
677
+ wallet,
678
+ address,
679
+ publicKey,
680
+ connected,
681
+ connecting,
682
+ disconnecting,
683
+ setName,
684
+ connect,
685
+ disconnect,
686
+ requestTransaction,
687
+ requestAssets,
688
+ requestPrivateNotes,
689
+ signBytes,
690
+ importPrivateNote,
691
+ requestConsumableNotes,
692
+ waitForTransaction,
693
+ requestSend,
694
+ requestConsume,
695
+ ]
696
+ );
697
+
698
+ return (
699
+ <WalletContext.Provider value={walletContextValue}>
700
+ <SignerContext.Provider value={signerContext}>
701
+ {children}
702
+ </SignerContext.Provider>
703
+ </WalletContext.Provider>
704
+ );
705
+ };
706
+
707
+ /**
708
+ * Hook for MidenFi wallet operations beyond the unified useSigner interface.
709
+ * Use this to access wallet-specific methods like requestTransaction, requestAssets, etc.
710
+ *
711
+ * @example
712
+ * ```tsx
713
+ * const { connected, connect, disconnect, wallets, select } = useMidenFiWallet();
714
+ *
715
+ * // Connect
716
+ * await connect();
717
+ *
718
+ * // Request a transaction
719
+ * const txId = await requestTransaction({ ... });
720
+ * ```
721
+ */
722
+ export function useMidenFiWallet(): WalletContextState {
723
+ const context = useContext(WalletContext);
724
+ if (!context || Object.keys(context).length === 0) {
725
+ throw new Error('useMidenFiWallet must be used within MidenFiSignerProvider');
726
+ }
727
+ return context;
728
+ }
729
+
730
+ // Re-export for backward compatibility
731
+ export { WalletContext };
732
+ export type { WalletContextState as MidenFiWalletContextState };