@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.
- package/MidenFiSignerProvider.tsx +732 -0
- package/WalletProvider.tsx +443 -0
- package/__tests__/MidenFiSignerProvider.test.tsx +426 -0
- package/dist/MidenFiSignerProvider.d.ts +113 -0
- package/dist/MidenFiSignerProvider.js +501 -0
- package/dist/MidenFiSignerProvider.js.map +1 -0
- package/dist/WalletProvider.d.ts +13 -0
- package/dist/WalletProvider.js +302 -0
- package/dist/WalletProvider.js.map +1 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +7 -0
- package/dist/index.js.map +1 -0
- package/dist/useLocalStorage.d.ts +2 -0
- package/dist/useLocalStorage.js +39 -0
- package/dist/useLocalStorage.js.map +1 -0
- package/dist/useWallet.d.ts +30 -0
- package/dist/useWallet.js +81 -0
- package/dist/useWallet.js.map +1 -0
- package/dist/vitest.config.d.ts +2 -0
- package/dist/vitest.config.js +8 -0
- package/dist/vitest.config.js.map +1 -0
- package/docs/README.md +30 -0
- package/docs/functions/useLocalStorage.md +29 -0
- package/docs/functions/useMidenFiWallet.md +28 -0
- package/docs/functions/useWallet.md +13 -0
- package/docs/interfaces/MidenFiSignerProviderProps.md +87 -0
- package/docs/interfaces/Wallet.md +19 -0
- package/docs/interfaces/WalletContextState.md +235 -0
- package/docs/interfaces/WalletProviderProps.md +65 -0
- package/docs/variables/MidenFiSignerProvider.md +54 -0
- package/docs/variables/WalletContext.md +9 -0
- package/docs/variables/WalletProvider.md +9 -0
- package/index.ts +15 -0
- package/package.json +42 -0
- package/tsconfig.json +16 -0
- package/useLocalStorage.ts +39 -0
- package/useWallet.ts +197 -0
- 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
|
+
};
|