@provablehq/aleo-wallet-adaptor-shield 0.3.0-alpha.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.
@@ -0,0 +1,463 @@
1
+ import {
2
+ Account,
3
+ Network,
4
+ TransactionOptions,
5
+ TransactionStatusResponse,
6
+ TxHistoryResult,
7
+ } from '@provablehq/aleo-types';
8
+ import {
9
+ AleoDeployment,
10
+ WalletDecryptPermission,
11
+ WalletName,
12
+ WalletReadyState,
13
+ } from '@provablehq/aleo-wallet-standard';
14
+ import {
15
+ BaseAleoWalletAdapter,
16
+ WalletConnectionError,
17
+ WalletDisconnectionError,
18
+ WalletError,
19
+ WalletNotConnectedError,
20
+ WalletSwitchNetworkError,
21
+ WalletSignMessageError,
22
+ WalletTransactionError,
23
+ WalletDecryptionError,
24
+ WalletDecryptionNotAllowedError,
25
+ scopePollingDetectionStrategy,
26
+ } from '@provablehq/aleo-wallet-adaptor-core';
27
+ import { ShieldWallet, ShieldWalletAdapterConfig, ShieldWindow } from './types';
28
+
29
+ /**
30
+ * Shield wallet adapter
31
+ */
32
+ export class ShieldWalletAdapter extends BaseAleoWalletAdapter {
33
+ /**
34
+ * The wallet name
35
+ */
36
+ readonly name = 'Shield Wallet' as WalletName<'Shield Wallet'>;
37
+
38
+ /**
39
+ * The wallet URL
40
+ */
41
+ url = 'https://provable.com/';
42
+
43
+ /**
44
+ * The wallet icon (base64-encoded SVG)
45
+ */
46
+ readonly icon =
47
+ 'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTAyNCIgaGVpZ2h0PSIxMDI0IiB2aWV3Qm94PSIwIDAgMTAyNCAxMDI0IiBmaWxsPSJub25lIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPgo8cmVjdCB3aWR0aD0iMTAyNCIgaGVpZ2h0PSIxMDI0IiBmaWxsPSIjMDkwNzA3Ii8+CjxwYXRoIGQ9Ik01MTIgODcxQzcxMC4yNyA4NzEgODcxIDcxMC4yNyA4NzEgNTEyQzg3MSA1MDUuMTI0IDg3MC44MDcgNDk4LjI5MyA4NzAuNDI1IDQ5MS41MTJDNzQ2LjQzIDYyNy4wNTggNDYxLjk5NCA3NjIuNDcgMzE0LjM5OSA4MTAuNjY0TDMxMi42ODQgODEwLjYzM0MzNjkuNzA0IDg0OC43NjUgNDM4LjI1NSA4NzEgNTEyIDg3MVoiIGZpbGw9IndoaXRlIi8+CjxwYXRoIGQ9Ik0yNTAuNDA4IDc1Ny44NjhDMTkwLjAwNiA2OTMuNjI4IDE1MyA2MDcuMTM2IDE1MyA1MTJDMTUzIDMxMy43MyAzMTMuNzMgMTUzIDUxMiAxNTNDNjkyLjk5MyAxNTMgODQyLjcwMyAyODYuOTM4IDg2Ny40MiA0NjEuMTA1QzYxMS4yMTIgNjM4LjgyMyAzNzcuNjU5IDcxOC42OCAyNTEuMDQ2IDc1Ny44NjhIMjUwLjQwOFoiIGZpbGw9IndoaXRlIi8+Cjwvc3ZnPgo=';
48
+
49
+ /**
50
+ * The window object
51
+ */
52
+ private _window: ShieldWindow | undefined;
53
+
54
+ /**
55
+ * Current network
56
+ */
57
+ network: Network;
58
+
59
+ /**
60
+ * The wallet's decrypt permission
61
+ */
62
+ decryptPermission: WalletDecryptPermission = WalletDecryptPermission.NoDecrypt;
63
+
64
+ /**
65
+ * Public key
66
+ */
67
+ private _publicKey: string = '';
68
+
69
+ _readyState: WalletReadyState =
70
+ typeof window === 'undefined' || typeof document === 'undefined'
71
+ ? WalletReadyState.UNSUPPORTED
72
+ : WalletReadyState.NOT_DETECTED;
73
+
74
+ /**
75
+ * Shield wallet instance
76
+ */
77
+ private _shieldWallet: ShieldWallet | undefined;
78
+
79
+ /**
80
+ * Create a new Shield wallet adapter
81
+ * @param config Adapter configuration
82
+ */
83
+ constructor(config?: ShieldWalletAdapterConfig) {
84
+ super();
85
+ console.debug('ShieldWalletAdapter constructor', config);
86
+ this.network = Network.TESTNET3;
87
+ if (this._readyState !== WalletReadyState.UNSUPPORTED) {
88
+ scopePollingDetectionStrategy(() => this._checkAvailability());
89
+ }
90
+ }
91
+
92
+ /**
93
+ * Check if Shield wallet is available
94
+ */
95
+ private _checkAvailability(): boolean {
96
+ this._window = window as ShieldWindow;
97
+
98
+ if (this._window.shield) {
99
+ this.readyState = WalletReadyState.INSTALLED;
100
+ this._shieldWallet = this._window?.shield;
101
+ return true;
102
+ } else {
103
+ // Check if user is on a mobile device
104
+ const isMobile = /iPhone|iPad|iPod|Android/i.test(navigator.userAgent);
105
+ if (isMobile) {
106
+ this.readyState = WalletReadyState.LOADABLE;
107
+ return true;
108
+ }
109
+ return false;
110
+ }
111
+ }
112
+
113
+ /**
114
+ * Connect to Shield wallet
115
+ * @returns The connected account
116
+ */
117
+ async connect(
118
+ network: Network,
119
+ decryptPermission: WalletDecryptPermission,
120
+ programs?: string[],
121
+ ): Promise<Account> {
122
+ try {
123
+ if (this.readyState !== WalletReadyState.INSTALLED) {
124
+ throw new WalletConnectionError('Shield Wallet is not available');
125
+ }
126
+
127
+ // Call connect and extract address safely
128
+ try {
129
+ const connectResult = await this._shieldWallet?.connect(
130
+ network,
131
+ decryptPermission,
132
+ programs,
133
+ );
134
+ this._publicKey = connectResult?.address || '';
135
+ this._onNetworkChange(network);
136
+ } catch (error: unknown) {
137
+ throw new WalletConnectionError(
138
+ error instanceof Error ? error.message : 'Connection failed',
139
+ );
140
+ }
141
+
142
+ if (!this._publicKey) {
143
+ throw new WalletConnectionError('No address returned from wallet');
144
+ }
145
+
146
+ this._setupListeners();
147
+
148
+ const account: Account = {
149
+ address: this._publicKey,
150
+ };
151
+
152
+ this.account = account;
153
+ this.decryptPermission = decryptPermission;
154
+ this.emit('connect', account);
155
+
156
+ return account;
157
+ } catch (err: Error | unknown) {
158
+ this.emit('error', err instanceof Error ? err : new Error(String(err)));
159
+ throw new WalletConnectionError(err instanceof Error ? err.message : 'Connection failed');
160
+ }
161
+ }
162
+
163
+ /**
164
+ * Disconnect from Shield wallet
165
+ */
166
+ async disconnect(): Promise<void> {
167
+ try {
168
+ this._cleanupListeners();
169
+
170
+ await this._shieldWallet?.disconnect();
171
+ this._onDisconnect();
172
+ } catch (err: Error | unknown) {
173
+ this.emit('error', err instanceof Error ? err : new Error(String(err)));
174
+ throw new WalletDisconnectionError(
175
+ err instanceof Error ? err.message : 'Disconnection failed',
176
+ );
177
+ }
178
+ }
179
+
180
+ /**
181
+ * Sign a transaction with Shield wallet
182
+ * @param message The message to sign
183
+ * @returns The signed message
184
+ */
185
+ async signMessage(message: Uint8Array): Promise<Uint8Array> {
186
+ if (!this._publicKey || !this.account) {
187
+ throw new WalletNotConnectedError();
188
+ }
189
+
190
+ try {
191
+ // Pass only the parameters expected by the Shield SDK
192
+ const signature = await this._shieldWallet?.signMessage(message);
193
+ if (!signature) {
194
+ throw new WalletSignMessageError('Failed to sign message');
195
+ }
196
+
197
+ return signature;
198
+ } catch (error: Error | unknown) {
199
+ throw new WalletSignMessageError(
200
+ error instanceof Error ? error.message : 'Failed to sign message',
201
+ );
202
+ }
203
+ }
204
+
205
+ async decrypt(cipherText: string) {
206
+ if (!this._shieldWallet || !this._publicKey) {
207
+ throw new WalletNotConnectedError();
208
+ }
209
+ switch (this.decryptPermission) {
210
+ case WalletDecryptPermission.NoDecrypt:
211
+ throw new WalletDecryptionNotAllowedError();
212
+ case WalletDecryptPermission.UponRequest:
213
+ case WalletDecryptPermission.AutoDecrypt:
214
+ case WalletDecryptPermission.OnChainHistory: {
215
+ try {
216
+ return await this._shieldWallet.decrypt(cipherText);
217
+ } catch (error: Error | unknown) {
218
+ throw new WalletDecryptionError(
219
+ error instanceof Error ? error.message : 'Failed to decrypt',
220
+ );
221
+ }
222
+ }
223
+ default:
224
+ throw new WalletDecryptionError();
225
+ }
226
+ }
227
+
228
+ /**
229
+ * Execute a transaction with Shield wallet
230
+ * @param options Transaction options
231
+ * @returns The executed temporary transaction ID
232
+ */
233
+ async executeTransaction(options: TransactionOptions): Promise<{ transactionId: string }> {
234
+ if (!this._publicKey || !this.account) {
235
+ throw new WalletNotConnectedError();
236
+ }
237
+
238
+ try {
239
+ const result = await this._shieldWallet?.executeTransaction({
240
+ ...options,
241
+ network: this.network,
242
+ });
243
+
244
+ if (!result?.transactionId) {
245
+ throw new WalletTransactionError('Could not create transaction');
246
+ }
247
+
248
+ return {
249
+ transactionId: result.transactionId,
250
+ };
251
+ } catch (error: Error | unknown) {
252
+ console.error('ShieldWalletAdapter executeTransaction error', error);
253
+ if (error instanceof WalletError) {
254
+ throw error;
255
+ }
256
+ throw new WalletTransactionError(
257
+ error instanceof Error ? error.message : 'Failed to execute transaction',
258
+ );
259
+ }
260
+ }
261
+
262
+ /**
263
+ * Get transaction status
264
+ * @param transactionId The transaction ID
265
+ * @returns The transaction status
266
+ */
267
+ async transactionStatus(transactionId: string): Promise<TransactionStatusResponse> {
268
+ if (!this._publicKey || !this.account) {
269
+ throw new WalletNotConnectedError();
270
+ }
271
+
272
+ try {
273
+ const result = await this._shieldWallet?.transactionStatus(transactionId);
274
+
275
+ if (!result?.status) {
276
+ throw new WalletTransactionError('Could not get transaction status');
277
+ }
278
+
279
+ return result;
280
+ } catch (error: Error | unknown) {
281
+ throw new WalletTransactionError(
282
+ error instanceof Error ? error.message : 'Failed to get transaction status',
283
+ );
284
+ }
285
+ }
286
+
287
+ /**
288
+ * Switch the network
289
+ * @param network The network to switch to
290
+ */
291
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
292
+ async switchNetwork(_network: Network): Promise<void> {
293
+ if (!this._publicKey || !this.account) {
294
+ throw new WalletNotConnectedError();
295
+ }
296
+
297
+ try {
298
+ await this._shieldWallet?.switchNetwork(_network);
299
+ this._onNetworkChange(_network);
300
+ } catch (error: unknown) {
301
+ throw new WalletSwitchNetworkError(
302
+ error instanceof Error ? error.message : 'Failed to switch network',
303
+ );
304
+ }
305
+ }
306
+
307
+ /**
308
+ * Request records from Shield wallet
309
+ * @param program The program to request records from
310
+ * @param includePlaintext Whether to include plaintext on each record
311
+ * @returns The records
312
+ */
313
+ async requestRecords(program: string, includePlaintext: boolean): Promise<unknown[]> {
314
+ if (!this._publicKey || !this.account) {
315
+ throw new WalletNotConnectedError();
316
+ }
317
+
318
+ try {
319
+ const result = await this._shieldWallet?.requestRecords(program, includePlaintext);
320
+
321
+ return result || [];
322
+ } catch (error: Error | unknown) {
323
+ throw new WalletError(error instanceof Error ? error.message : 'Failed to request records');
324
+ }
325
+ }
326
+
327
+ /**
328
+ * Execute a deployment
329
+ * @param deployment The deployment to execute
330
+ * @returns The executed transaction ID
331
+ */
332
+ async executeDeployment(deployment: AleoDeployment): Promise<{ transactionId: string }> {
333
+ try {
334
+ if (!this._publicKey || !this.account) {
335
+ throw new WalletNotConnectedError();
336
+ }
337
+ try {
338
+ const result = await this._shieldWallet?.executeDeployment({
339
+ ...deployment,
340
+ network: this.network,
341
+ });
342
+ if (!result?.transactionId) {
343
+ throw new WalletTransactionError('Could not create deployment');
344
+ }
345
+ return {
346
+ transactionId: result.transactionId,
347
+ };
348
+ } catch (error: Error | unknown) {
349
+ throw new WalletTransactionError(
350
+ error instanceof Error ? error.message : 'Failed to execute deployment',
351
+ );
352
+ }
353
+ } catch (error: Error | unknown) {
354
+ this.emit('error', error instanceof Error ? error : new Error(String(error)));
355
+ throw error;
356
+ }
357
+ }
358
+
359
+ /**
360
+ * get transition view keys(tvk) for a transaction
361
+ * @param transactionId The transaction ID
362
+ * @returns The tvk array
363
+ */
364
+ async transitionViewKeys(transactionId: string): Promise<string[]> {
365
+ try {
366
+ if (!this._publicKey || !this.account) {
367
+ throw new WalletNotConnectedError();
368
+ }
369
+ try {
370
+ const result = await this._shieldWallet?.transitionViewKeys(transactionId);
371
+ if (!Array.isArray(result)) {
372
+ throw new WalletTransactionError('Could not get transitionViewKeys');
373
+ }
374
+ return result;
375
+ } catch (error: Error | unknown) {
376
+ throw new WalletTransactionError(
377
+ error instanceof Error ? error.message : 'Failed to get transitionViewKeys',
378
+ );
379
+ }
380
+ } catch (error: Error | unknown) {
381
+ this.emit('error', error instanceof Error ? error : new Error(String(error)));
382
+ throw error;
383
+ }
384
+ }
385
+
386
+ /**
387
+ * get transaction of specific program
388
+ * @param program The program ID
389
+ * @returns array of transactionId
390
+ */
391
+ async requestTransactionHistory(program: string): Promise<TxHistoryResult> {
392
+ try {
393
+ if (!this._publicKey || !this.account) {
394
+ throw new WalletNotConnectedError();
395
+ }
396
+ try {
397
+ const result = await this._shieldWallet?.requestTransactionHistory(program);
398
+ if (!result?.transactions || !Array.isArray(result.transactions)) {
399
+ throw new WalletTransactionError('Could not get TransactionHistory');
400
+ }
401
+ return result;
402
+ } catch (error: Error | unknown) {
403
+ throw new WalletTransactionError(
404
+ error instanceof Error ? error.message : 'Failed to get transitionViewKeys',
405
+ );
406
+ }
407
+ } catch (error: Error | unknown) {
408
+ this.emit('error', error instanceof Error ? error : new Error(String(error)));
409
+ throw error;
410
+ }
411
+ }
412
+
413
+ /**
414
+ * EVENTS HANDLING
415
+ */
416
+
417
+ // Network change listener
418
+ _onNetworkChange = (network: Network) => {
419
+ console.debug('Shield Wallet network changed to: ', network);
420
+ this.network = network;
421
+ this.emit('networkChange', network);
422
+ };
423
+
424
+ // Account change listener
425
+ _onAccountChange = () => {
426
+ console.debug('Shield Wallet account change detected – reauthorization required');
427
+ this._publicKey = '';
428
+ this.account = undefined;
429
+ this.emit('accountChange');
430
+ };
431
+
432
+ // Disconnect listener
433
+ _onDisconnect = () => {
434
+ console.debug('Shield Wallet disconnected');
435
+ this._cleanupListeners();
436
+ this._publicKey = '';
437
+ this.account = undefined;
438
+ this.emit('disconnect');
439
+ };
440
+
441
+ /**
442
+ * Set up event listeners with structured approach
443
+ */
444
+ private _setupListeners(): void {
445
+ if (!this._shieldWallet) return;
446
+
447
+ // Register listeners
448
+ this._shieldWallet.on('networkChanged', this._onNetworkChange);
449
+ this._shieldWallet.on('disconnect', this._onDisconnect);
450
+ this._shieldWallet.on('accountChanged', this._onAccountChange);
451
+ }
452
+
453
+ /**
454
+ * Clean up all event listeners
455
+ */
456
+ private _cleanupListeners(): void {
457
+ if (!this._shieldWallet) return;
458
+
459
+ this._shieldWallet.off('networkChanged', this._onNetworkChange);
460
+ this._shieldWallet.off('disconnect', this._onDisconnect);
461
+ this._shieldWallet.off('accountChanged', this._onAccountChange);
462
+ }
463
+ }
package/src/index.ts ADDED
@@ -0,0 +1,2 @@
1
+ export * from './ShieldWalletAdapter';
2
+ export * from './types';
package/src/types.ts ADDED
@@ -0,0 +1,50 @@
1
+ import {
2
+ Network,
3
+ TransactionOptions,
4
+ TransactionStatusResponse,
5
+ TxHistoryResult,
6
+ } from '@provablehq/aleo-types';
7
+ import {
8
+ AleoDeployment,
9
+ EventEmitter,
10
+ WalletDecryptPermission,
11
+ } from '@provablehq/aleo-wallet-standard';
12
+
13
+ export interface ShieldWalletAdapterConfig {}
14
+
15
+ export interface ShieldTransaction extends TransactionOptions {
16
+ network: Network;
17
+ }
18
+
19
+ export interface ShieldDeployment extends AleoDeployment {
20
+ network: Network;
21
+ }
22
+
23
+ export interface ShieldWalletEvents {
24
+ networkChanged(network: Network): void;
25
+ disconnect(): void;
26
+ accountChanged(): void;
27
+ }
28
+
29
+ export interface ShieldWallet extends EventEmitter<ShieldWalletEvents> {
30
+ publicKey?: string;
31
+ connect(
32
+ network: Network,
33
+ decryptPermission: WalletDecryptPermission,
34
+ programs?: string[],
35
+ ): Promise<{ address: string }>;
36
+ disconnect(): Promise<void>;
37
+ signMessage(message: Uint8Array): Promise<Uint8Array>;
38
+ decrypt(cipherText: string): Promise<string>;
39
+ executeTransaction(transactionOptions: ShieldTransaction): Promise<{ transactionId?: string }>;
40
+ transactionStatus(transactionId: string): Promise<TransactionStatusResponse>;
41
+ switchNetwork(network: Network): Promise<void>;
42
+ requestRecords(program: string, includePlaintext?: boolean): Promise<unknown[]>;
43
+ executeDeployment(deployment: ShieldDeployment): Promise<{ transactionId: string }>;
44
+ transitionViewKeys: (transactionId: string) => Promise<string[]>;
45
+ requestTransactionHistory: (program: string) => Promise<TxHistoryResult>;
46
+ }
47
+
48
+ export interface ShieldWindow extends Window {
49
+ shield?: ShieldWallet;
50
+ }