@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,443 @@
1
+ import {
2
+ Adapter,
3
+ AllowedPrivateData,
4
+ MessageSignerWalletAdapterProps,
5
+ MidenTransaction,
6
+ PrivateDataPermission,
7
+ SignKind,
8
+ WalletAdapterNetwork,
9
+ WalletError,
10
+ WalletName,
11
+ WalletNotConnectedError,
12
+ WalletNotReadyError,
13
+ WalletNotSelectedError,
14
+ WalletReadyState,
15
+ } from '@miden-sdk/miden-wallet-adapter-base';
16
+ import type { FC, ReactNode } from 'react';
17
+ import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
18
+ import { useLocalStorage } from './useLocalStorage';
19
+ import type { Wallet } from './useWallet';
20
+ import { WalletContext } from './useWallet';
21
+ import type { NoteFilterTypes } from '@miden-sdk/miden-sdk';
22
+
23
+ export interface WalletProviderProps {
24
+ children: ReactNode;
25
+ wallets: Adapter[];
26
+ privateDataPermission?: PrivateDataPermission;
27
+ allowedPrivateData?: AllowedPrivateData;
28
+ network?: WalletAdapterNetwork;
29
+ autoConnect?: boolean;
30
+ onError?: (error: WalletError) => void;
31
+ localStorageKey?: string;
32
+ }
33
+
34
+ const initialState: {
35
+ wallet: Wallet | null;
36
+ adapter: Adapter | null;
37
+ address: string | null;
38
+ publicKey: Uint8Array | null;
39
+ connected: boolean;
40
+ } = {
41
+ wallet: null,
42
+ adapter: null,
43
+ address: null,
44
+ publicKey: null,
45
+ connected: false,
46
+ };
47
+
48
+ export const WalletProvider: FC<WalletProviderProps> = ({
49
+ children,
50
+ wallets: adapters,
51
+ autoConnect = false,
52
+ privateDataPermission = PrivateDataPermission.UponRequest,
53
+ network = WalletAdapterNetwork.Testnet,
54
+ onError,
55
+ localStorageKey = 'walletName',
56
+ allowedPrivateData = AllowedPrivateData.None,
57
+ }) => {
58
+ const [name, setName] = useLocalStorage<WalletName | null>(
59
+ localStorageKey,
60
+ null
61
+ );
62
+ const [{ wallet, adapter, address, publicKey, connected }, setState] =
63
+ useState(initialState);
64
+ const readyState = adapter?.readyState || WalletReadyState.Unsupported;
65
+ const [connecting, setConnecting] = useState(false);
66
+ const [disconnecting, setDisconnecting] = useState(false);
67
+ const isConnecting = useRef(false);
68
+ const isDisconnecting = useRef(false);
69
+ const isUnloading = useRef(false);
70
+
71
+ // Wrap adapters to conform to the `Wallet` interface
72
+ const [wallets, setWallets] = useState(() =>
73
+ adapters.map((adapter) => ({
74
+ adapter,
75
+ readyState: adapter.readyState,
76
+ }))
77
+ );
78
+
79
+ // When the adapters change, start to listen for changes to their `readyState`
80
+ useEffect(() => {
81
+ // When the adapters change, wrap them to conform to the `Wallet` interface
82
+ setWallets((wallets) =>
83
+ adapters.map((adapter, index) => {
84
+ const wallet = wallets[index];
85
+ // If the wallet hasn't changed, return the same instance
86
+ return wallet &&
87
+ wallet.adapter === adapter &&
88
+ wallet.readyState === adapter.readyState
89
+ ? wallet
90
+ : {
91
+ adapter: adapter,
92
+ readyState: adapter.readyState,
93
+ };
94
+ })
95
+ );
96
+
97
+ function handleReadyStateChange(
98
+ this: Adapter,
99
+ readyState: WalletReadyState
100
+ ) {
101
+ setWallets((prevWallets) => {
102
+ const index = prevWallets.findIndex(({ adapter }) => adapter === this);
103
+ if (index === -1) return prevWallets;
104
+
105
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
106
+ const { adapter } = prevWallets[index]!;
107
+ return [
108
+ ...prevWallets.slice(0, index),
109
+ { adapter, readyState },
110
+ ...prevWallets.slice(index + 1),
111
+ ];
112
+ });
113
+ }
114
+
115
+ adapters.forEach((adapter) =>
116
+ adapter.on('readyStateChange', handleReadyStateChange, adapter)
117
+ );
118
+ return () =>
119
+ adapters.forEach((adapter) =>
120
+ adapter.off('readyStateChange', handleReadyStateChange, adapter)
121
+ );
122
+ }, [adapters]);
123
+
124
+ // When the selected wallet changes, initialize the state
125
+ useEffect(() => {
126
+ const wallet = name && wallets.find(({ adapter }) => adapter.name === name);
127
+ if (wallet) {
128
+ setState({
129
+ wallet,
130
+ adapter: wallet.adapter,
131
+ connected: wallet.adapter.connected,
132
+ address: wallet.adapter.address,
133
+ publicKey: wallet.adapter.publicKey,
134
+ });
135
+ } else {
136
+ setState(initialState);
137
+ }
138
+ }, [name, wallets]);
139
+
140
+ // If the window is closing or reloading, ignore disconnect and error events from the adapter
141
+ useEffect(() => {
142
+ function listener() {
143
+ isUnloading.current = true;
144
+ }
145
+
146
+ window.addEventListener('beforeunload', listener);
147
+ return () => window.removeEventListener('beforeunload', listener);
148
+ }, [isUnloading]);
149
+
150
+ // Handle the adapter's connect event
151
+ const handleConnect = useCallback(() => {
152
+ if (!adapter) return;
153
+ setState((state) => ({
154
+ ...state,
155
+ connected: adapter.connected,
156
+ address: adapter.address,
157
+ publicKey: adapter.publicKey,
158
+ }));
159
+ }, [adapter]);
160
+
161
+ // Handle the adapter's disconnect event
162
+ const handleDisconnect = useCallback(() => {
163
+ // Clear the selected wallet unless the window is unloading
164
+ if (!isUnloading.current) setName(null);
165
+ }, [isUnloading, setName]);
166
+
167
+ // Handle the adapter's error event, and local errors
168
+ const handleError = useCallback(
169
+ (error: WalletError) => {
170
+ // Call onError unless the window is unloading
171
+ if (!isUnloading.current) (onError || console.error)(error);
172
+ return error;
173
+ },
174
+ [isUnloading, onError]
175
+ );
176
+
177
+ // Setup and teardown event listeners when the adapter changes
178
+ useEffect(() => {
179
+ if (adapter) {
180
+ adapter.on('connect', handleConnect);
181
+ adapter.on('disconnect', handleDisconnect);
182
+ adapter.on('error', handleError);
183
+ return () => {
184
+ adapter.off('connect', handleConnect);
185
+ adapter.off('disconnect', handleDisconnect);
186
+ adapter.off('error', handleError);
187
+ };
188
+ }
189
+ }, [adapter, handleConnect, handleDisconnect, handleError]);
190
+
191
+ // When the adapter changes, disconnect the old one
192
+ useEffect(() => {
193
+ return () => {
194
+ adapter?.disconnect();
195
+ };
196
+ }, [adapter]);
197
+
198
+ // If autoConnect is enabled, try to connect when the adapter changes and is ready
199
+ useEffect(() => {
200
+ if (
201
+ isConnecting.current ||
202
+ connected ||
203
+ !autoConnect ||
204
+ !adapter ||
205
+ !(
206
+ readyState === WalletReadyState.Installed ||
207
+ readyState === WalletReadyState.Loadable
208
+ )
209
+ )
210
+ return;
211
+
212
+ (async function () {
213
+ isConnecting.current = true;
214
+ setConnecting(true);
215
+ try {
216
+ await adapter.connect(
217
+ privateDataPermission,
218
+ network,
219
+ allowedPrivateData
220
+ );
221
+ } catch (error: any) {
222
+ // Clear the selected wallet
223
+ setName(null);
224
+ // Don't throw error, but handleError will still be called
225
+ } finally {
226
+ setConnecting(false);
227
+ isConnecting.current = false;
228
+ }
229
+ })();
230
+ }, [isConnecting, connected, autoConnect, adapter, readyState, setName]);
231
+
232
+ // Connect the adapter to the wallet
233
+ const connect = useCallback(async () => {
234
+ if (isConnecting.current || isDisconnecting.current || connected) return;
235
+ if (!adapter) throw handleError(new WalletNotSelectedError());
236
+
237
+ if (
238
+ !(
239
+ readyState === WalletReadyState.Installed ||
240
+ readyState === WalletReadyState.Loadable
241
+ )
242
+ ) {
243
+ // Clear the selected wallet
244
+ setName(null);
245
+
246
+ if (typeof window !== 'undefined') {
247
+ window.open(adapter.url, '_blank');
248
+ }
249
+
250
+ throw handleError(new WalletNotReadyError());
251
+ }
252
+
253
+ isConnecting.current = true;
254
+ setConnecting(true);
255
+ try {
256
+ await adapter.connect(privateDataPermission, network, allowedPrivateData);
257
+ } catch (error: any) {
258
+ // Clear the selected wallet
259
+ setName(null);
260
+ // Rethrow the error, and handleError will also be called
261
+ throw error;
262
+ } finally {
263
+ setConnecting(false);
264
+ isConnecting.current = false;
265
+ }
266
+ }, [
267
+ isConnecting,
268
+ isDisconnecting,
269
+ connected,
270
+ adapter,
271
+ readyState,
272
+ handleError,
273
+ setName,
274
+ ]);
275
+
276
+ // Disconnect the adapter from the wallet
277
+ const disconnect = useCallback(async () => {
278
+ if (isDisconnecting.current) return;
279
+ if (!adapter) return setName(null);
280
+
281
+ isDisconnecting.current = true;
282
+ setDisconnecting(true);
283
+ try {
284
+ await adapter.disconnect();
285
+ } catch (error: any) {
286
+ // Clear the selected wallet
287
+ setName(null);
288
+ // Rethrow the error, and handleError will also be called
289
+ throw error;
290
+ } finally {
291
+ setDisconnecting(false);
292
+ isDisconnecting.current = false;
293
+ }
294
+ }, [isDisconnecting, adapter, setName]);
295
+
296
+ // Request transaction
297
+ const requestTransaction:
298
+ | MessageSignerWalletAdapterProps['requestTransaction']
299
+ | undefined = useMemo(
300
+ () =>
301
+ adapter && 'requestTransaction' in adapter
302
+ ? async (transaction: MidenTransaction) => {
303
+ if (!connected) throw handleError(new WalletNotConnectedError());
304
+ return await adapter.requestTransaction(transaction);
305
+ }
306
+ : undefined,
307
+ [adapter, handleError, connected]
308
+ );
309
+
310
+ // Request assets
311
+ const requestAssets:
312
+ | MessageSignerWalletAdapterProps['requestAssets']
313
+ | undefined = useMemo(
314
+ () =>
315
+ adapter && 'requestAssets' in adapter
316
+ ? async () => {
317
+ if (!connected) throw handleError(new WalletNotConnectedError());
318
+ return await adapter.requestAssets();
319
+ }
320
+ : undefined,
321
+ [adapter, handleError, connected]
322
+ );
323
+
324
+ // Request private notes
325
+ const requestPrivateNotes:
326
+ | MessageSignerWalletAdapterProps['requestPrivateNotes']
327
+ | undefined = useMemo(
328
+ () =>
329
+ adapter && 'requestPrivateNotes' in adapter
330
+ ? async (noteFilterType: NoteFilterTypes, noteIds?: string[]) => {
331
+ if (!connected) throw handleError(new WalletNotConnectedError());
332
+ return await adapter.requestPrivateNotes(noteFilterType, noteIds);
333
+ }
334
+ : undefined,
335
+ [adapter, handleError, connected]
336
+ );
337
+
338
+ const signBytes: MessageSignerWalletAdapterProps['signBytes'] | undefined =
339
+ useMemo(
340
+ () =>
341
+ adapter && 'signBytes' in adapter
342
+ ? async (message: Uint8Array, kind: SignKind) => {
343
+ if (!connected) throw handleError(new WalletNotConnectedError());
344
+ return await adapter.signBytes(message, kind);
345
+ }
346
+ : undefined,
347
+ [adapter, handleError, connected]
348
+ );
349
+
350
+ const importPrivateNote:
351
+ | MessageSignerWalletAdapterProps['importPrivateNote']
352
+ | undefined = useMemo(
353
+ () =>
354
+ adapter && 'importPrivateNote' in adapter
355
+ ? async (note: Uint8Array) => {
356
+ if (!connected) throw handleError(new WalletNotConnectedError());
357
+ return await adapter.importPrivateNote(note);
358
+ }
359
+ : undefined,
360
+ [adapter, handleError, connected]
361
+ );
362
+
363
+ const requestConsumableNotes:
364
+ | MessageSignerWalletAdapterProps['requestConsumableNotes']
365
+ | undefined = useMemo(
366
+ () =>
367
+ adapter && 'requestConsumableNotes' in adapter
368
+ ? async () => {
369
+ if (!connected) throw handleError(new WalletNotConnectedError());
370
+ return await adapter.requestConsumableNotes();
371
+ }
372
+ : undefined,
373
+ [adapter, handleError, connected]
374
+ );
375
+
376
+ const waitForTransaction:
377
+ | MessageSignerWalletAdapterProps['waitForTransaction']
378
+ | undefined = useMemo(
379
+ () =>
380
+ adapter && 'waitForTransaction' in adapter
381
+ ? async (txId: string, timeout?: number) => {
382
+ if (!connected) throw handleError(new WalletNotConnectedError());
383
+ return await adapter.waitForTransaction(txId, timeout);
384
+ }
385
+ : undefined,
386
+ [adapter, handleError, connected]
387
+ );
388
+
389
+ const requestSend:
390
+ | MessageSignerWalletAdapterProps['requestSend']
391
+ | undefined = useMemo(
392
+ () =>
393
+ adapter && 'requestSend' in adapter
394
+ ? async (transaction) => {
395
+ if (!connected) throw handleError(new WalletNotConnectedError());
396
+ return await adapter.requestSend(transaction);
397
+ }
398
+ : undefined,
399
+ [adapter, handleError, connected]
400
+ );
401
+
402
+ const requestConsume:
403
+ | MessageSignerWalletAdapterProps['requestConsume']
404
+ | undefined = useMemo(
405
+ () =>
406
+ adapter && 'requestConsume' in adapter
407
+ ? async (transaction) => {
408
+ if (!connected) throw handleError(new WalletNotConnectedError());
409
+ return await adapter.requestConsume(transaction);
410
+ }
411
+ : undefined,
412
+ [adapter, handleError, connected]
413
+ );
414
+
415
+ return (
416
+ <WalletContext.Provider
417
+ value={{
418
+ autoConnect,
419
+ wallets,
420
+ wallet,
421
+ address,
422
+ publicKey,
423
+ connected,
424
+ connecting,
425
+ disconnecting,
426
+ select: setName,
427
+ connect,
428
+ disconnect,
429
+ requestTransaction,
430
+ requestAssets,
431
+ requestPrivateNotes,
432
+ signBytes,
433
+ importPrivateNote,
434
+ requestConsumableNotes,
435
+ waitForTransaction,
436
+ requestSend,
437
+ requestConsume,
438
+ }}
439
+ >
440
+ {children}
441
+ </WalletContext.Provider>
442
+ );
443
+ };