@provablehq/aleo-wallet-adaptor-leo 0.1.1-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.
package/package.json ADDED
@@ -0,0 +1,44 @@
1
+ {
2
+ "name": "@provablehq/aleo-wallet-adaptor-leo",
3
+ "version": "0.1.1-alpha.0",
4
+ "description": "Leo wallet adapter for Aleo",
5
+ "main": "dist/index.js",
6
+ "module": "dist/index.mjs",
7
+ "types": "dist/index.d.ts",
8
+ "license": "GPL-3.0-or-later",
9
+ "files": [
10
+ "dist",
11
+ "src"
12
+ ],
13
+ "dependencies": {
14
+ "@provablehq/aleo-types": "0.1.1-alpha.0",
15
+ "@provablehq/aleo-wallet-standard": "0.1.1-alpha.0",
16
+ "@provablehq/aleo-wallet-adaptor-core": "0.1.1-alpha.0"
17
+ },
18
+ "repository": {
19
+ "type": "git",
20
+ "url": "git+https://github.com/ProvableHQ/aleo-dev-toolkit.git",
21
+ "directory": "packages/aleo-wallet-adaptor/wallets/leo"
22
+ },
23
+ "homepage": "https://provable.com/",
24
+ "keywords": [
25
+ "aleo",
26
+ "wallet",
27
+ "adapter",
28
+ "leo"
29
+ ],
30
+ "author": "Provable Labs",
31
+ "publishConfig": {
32
+ "access": "public"
33
+ },
34
+ "devDependencies": {
35
+ "rimraf": "^5.0.5",
36
+ "tsup": "^7.0.0",
37
+ "typescript": "^5.0.0"
38
+ },
39
+ "scripts": {
40
+ "build": "tsup",
41
+ "clean": "rimraf dist",
42
+ "dev": "tsup --watch"
43
+ }
44
+ }
@@ -0,0 +1,405 @@
1
+ import {
2
+ Account,
3
+ Network,
4
+ TransactionOptions,
5
+ TransactionStatus,
6
+ TransactionStatusResponse,
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
+ MethodNotImplementedError,
17
+ WalletConnectionError,
18
+ WalletDecryptionNotAllowedError,
19
+ WalletDecryptionError,
20
+ WalletDisconnectionError,
21
+ WalletError,
22
+ WalletNotConnectedError,
23
+ WalletSignMessageError,
24
+ WalletTransactionError,
25
+ } from '@provablehq/aleo-wallet-adaptor-core';
26
+ import {
27
+ AleoTransaction,
28
+ Deployment,
29
+ LEO_NETWORK_MAP,
30
+ LeoWallet,
31
+ LeoWalletAdapterConfig,
32
+ LeoWindow,
33
+ } from './types';
34
+
35
+ /**
36
+ * Leo wallet adapter
37
+ */
38
+ export class LeoWalletAdapter extends BaseAleoWalletAdapter {
39
+ /**
40
+ * The wallet name
41
+ */
42
+ readonly name = 'Leo Wallet' as WalletName<'Leo Wallet'>;
43
+
44
+ /**
45
+ * The wallet URL
46
+ */
47
+ url = 'https://app.leo.app';
48
+
49
+ /**
50
+ * The wallet icon (base64-encoded SVG)
51
+ */
52
+ readonly icon =
53
+ 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAFwAAABcCAMAAADUMSJqAAAASFBMVEVjTP////9kTf9GIP/5+P9dRP+ajf9WO//Jwv9fR/9KJv+Ccf9ZP//08/+Xiv9aQf+upf/c2P+Qgf96aP9TNv/Nx/+Fdf+6sv91nL8+AAABCUlEQVRoge2YyQ6EIBBEccVWBHf//09H42FESNRJk+ik3s06vANCQ0oIAAAAf0ps48u8XHKXiYVZsyE5pbxgj8s63RPpTAhZROkZ9QV7nKSRRb7JT0kTyCGHHPInyceAcppyBz0zyQVlDlJzyT1k+XvkZKQ0FEROqmqKoqkU8ctV327fba+45ar7Jp3ilVO/j3pilat2H7WKU06VnVXEKDeNnTWGUX6cioV8izzosgT9ocetuKzKWw5R0OMfdnAtKyPDjVwR9LI48ny5lu6zgm0rztp9EE1cJ9THyDW4fLBNRcghhxxyDvmh+vOxybVdB16p/pzS0sewesz90vJGtfpD3QoAAOCNfADu9hzTpMe3fQAAAABJRU5ErkJggg==';
54
+ /**
55
+ * The window object
56
+ */
57
+ private _window: LeoWindow | undefined;
58
+
59
+ /**
60
+ * Current network
61
+ */
62
+ network: Network;
63
+
64
+ /**
65
+ * The wallet's decrypt permission
66
+ */
67
+ decryptPermission: WalletDecryptPermission = WalletDecryptPermission.NoDecrypt;
68
+
69
+ /**
70
+ * Public key
71
+ */
72
+ private _publicKey: string = '';
73
+
74
+ _readyState: WalletReadyState =
75
+ typeof window === 'undefined' || typeof document === 'undefined'
76
+ ? WalletReadyState.UNSUPPORTED
77
+ : WalletReadyState.NOT_DETECTED;
78
+
79
+ /**
80
+ * Leo wallet instance
81
+ */
82
+ private _leoWallet: LeoWallet | undefined;
83
+
84
+ /**
85
+ * Create a new Leo wallet adapter
86
+ * @param config Adapter configuration
87
+ */
88
+ constructor(config?: LeoWalletAdapterConfig) {
89
+ super();
90
+ console.debug('LeoWalletAdapter constructor', config);
91
+ this.network = Network.TESTNET3;
92
+ this._checkAvailability();
93
+ this._leoWallet = this._window?.leoWallet || this._window?.leo;
94
+ if (config?.isMobile) {
95
+ this.url = `https://app.leo.app/browser?url=${config.mobileWebviewUrl}`;
96
+ }
97
+ }
98
+
99
+ /**
100
+ * Check if Leo wallet is available
101
+ */
102
+ private _checkAvailability(): void {
103
+ if (typeof window === 'undefined' || typeof document === 'undefined') {
104
+ this.readyState = WalletReadyState.UNSUPPORTED;
105
+ return;
106
+ }
107
+
108
+ this._window = window as LeoWindow;
109
+
110
+ if (this._window.leoWallet || this._window.leo) {
111
+ this.readyState = WalletReadyState.INSTALLED;
112
+ } else {
113
+ // Check if user is on a mobile device
114
+ const isMobile = /iPhone|iPad|iPod|Android/i.test(navigator.userAgent);
115
+ if (isMobile) {
116
+ this.readyState = WalletReadyState.LOADABLE;
117
+ }
118
+ }
119
+ }
120
+
121
+ /**
122
+ * Connect to Leo wallet
123
+ * @returns The connected account
124
+ */
125
+ async connect(
126
+ network: Network,
127
+ decryptPermission: WalletDecryptPermission,
128
+ programs?: string[],
129
+ ): Promise<Account> {
130
+ try {
131
+ if (this.readyState !== WalletReadyState.INSTALLED) {
132
+ throw new WalletConnectionError('Leo Wallet is not available');
133
+ }
134
+
135
+ // Call connect and extract address safely
136
+ try {
137
+ await this._leoWallet?.connect(decryptPermission, LEO_NETWORK_MAP[network], programs);
138
+ this.network = network;
139
+ } catch (error: unknown) {
140
+ if (
141
+ error instanceof Object &&
142
+ 'name' in error &&
143
+ (error.name === 'InvalidParamsAleoWalletError' ||
144
+ error.name !== 'NotGrantedAleoWalletError')
145
+ ) {
146
+ // TODO: Handle wrongNetwork at WalletProvider level?
147
+ throw new WalletConnectionError(
148
+ 'Connection failed: Likely due to a difference in configured network and the selected wallet network. Configured network: ' +
149
+ network,
150
+ );
151
+ }
152
+
153
+ throw new WalletConnectionError(
154
+ error instanceof Error ? error.message : 'Connection failed',
155
+ );
156
+ }
157
+
158
+ this._publicKey = this._leoWallet?.publicKey || '';
159
+ this.decryptPermission = decryptPermission;
160
+ if (!this._publicKey) {
161
+ throw new WalletConnectionError('No address returned from wallet');
162
+ }
163
+
164
+ const account: Account = {
165
+ address: this._publicKey,
166
+ };
167
+
168
+ this.account = account;
169
+ this.emit('connect', account);
170
+
171
+ return account;
172
+ } catch (err: Error | unknown) {
173
+ this.emit('error', err instanceof Error ? err : new Error(String(err)));
174
+ throw new WalletConnectionError(err instanceof Error ? err.message : 'Connection failed');
175
+ }
176
+ }
177
+
178
+ /**
179
+ * Disconnect from Leo wallet
180
+ */
181
+ async disconnect(): Promise<void> {
182
+ try {
183
+ await this._leoWallet?.disconnect();
184
+ this._publicKey = '';
185
+ this.account = undefined;
186
+ this.emit('disconnect');
187
+ } catch (err: Error | unknown) {
188
+ this.emit('error', err instanceof Error ? err : new Error(String(err)));
189
+ throw new WalletDisconnectionError(
190
+ err instanceof Error ? err.message : 'Disconnection failed',
191
+ );
192
+ }
193
+ }
194
+
195
+ /**
196
+ * Sign a transaction with Leo wallet
197
+ * @param options Transaction options
198
+ * @returns The signed transaction
199
+ */
200
+ async signMessage(message: Uint8Array): Promise<Uint8Array> {
201
+ if (!this._publicKey || !this.account) {
202
+ throw new WalletNotConnectedError();
203
+ }
204
+
205
+ try {
206
+ // Pass only the parameters expected by the Leo SDK
207
+ const signature = await this._leoWallet?.signMessage(message);
208
+
209
+ if (!signature) {
210
+ throw new WalletSignMessageError('Failed to sign message');
211
+ }
212
+
213
+ return signature.signature;
214
+ } catch (error: Error | unknown) {
215
+ throw new WalletSignMessageError(
216
+ error instanceof Error ? error.message : 'Failed to sign message',
217
+ );
218
+ }
219
+ }
220
+
221
+ async decrypt(
222
+ cipherText: string,
223
+ tpk?: string,
224
+ programId?: string,
225
+ functionName?: string,
226
+ index?: number,
227
+ ) {
228
+ if (!this._leoWallet || !this._publicKey) {
229
+ throw new WalletNotConnectedError();
230
+ }
231
+ switch (this.decryptPermission) {
232
+ case WalletDecryptPermission.NoDecrypt:
233
+ throw new WalletDecryptionNotAllowedError();
234
+ case WalletDecryptPermission.UponRequest:
235
+ case WalletDecryptPermission.AutoDecrypt:
236
+ case WalletDecryptPermission.OnChainHistory: {
237
+ try {
238
+ const text = await this._leoWallet.decrypt(
239
+ cipherText,
240
+ tpk,
241
+ programId,
242
+ functionName,
243
+ index,
244
+ );
245
+ return text.text;
246
+ } catch (error: Error | unknown) {
247
+ throw new WalletDecryptionError(
248
+ error instanceof Error ? error.message : 'Failed to decrypt',
249
+ );
250
+ }
251
+ }
252
+ default:
253
+ throw new WalletDecryptionError();
254
+ }
255
+ }
256
+
257
+ /**
258
+ * Execute a transaction with Leo wallet
259
+ * @param options Transaction options
260
+ * @returns The executed temporary transaction ID
261
+ */
262
+ async executeTransaction(options: TransactionOptions): Promise<{ transactionId: string }> {
263
+ if (!this._publicKey || !this.account) {
264
+ throw new WalletNotConnectedError();
265
+ }
266
+
267
+ try {
268
+ const requestData = {
269
+ address: this._publicKey,
270
+ chainId: LEO_NETWORK_MAP[this.network],
271
+ fee: options.fee ? options.fee : 0.001,
272
+ feePrivate: options.privateFee ?? false,
273
+ transitions: [
274
+ {
275
+ program: options.program,
276
+ functionName: options.function,
277
+ inputs: options.inputs,
278
+ },
279
+ ],
280
+ } as AleoTransaction;
281
+
282
+ const result = await this._leoWallet?.requestTransaction(requestData);
283
+
284
+ if (!result?.transactionId) {
285
+ throw new WalletTransactionError('Could not create transaction');
286
+ }
287
+
288
+ return {
289
+ transactionId: result.transactionId,
290
+ };
291
+ } catch (error: Error | unknown) {
292
+ console.error('Leo Wallet executeTransaction error', error);
293
+ if (error instanceof WalletError) {
294
+ throw error;
295
+ }
296
+ throw new WalletTransactionError(
297
+ error instanceof Error ? error.message : 'Failed to execute transaction',
298
+ );
299
+ }
300
+ }
301
+
302
+ /**
303
+ * Get transaction status
304
+ * @param transactionId The transaction ID
305
+ * @returns The transaction status
306
+ */
307
+ async transactionStatus(transactionId: string): Promise<TransactionStatusResponse> {
308
+ if (!this._publicKey || !this.account) {
309
+ throw new WalletNotConnectedError();
310
+ }
311
+
312
+ try {
313
+ const result = await this._leoWallet?.transactionStatus(transactionId);
314
+
315
+ if (!result?.status) {
316
+ throw new WalletTransactionError('Could not get transaction status');
317
+ }
318
+
319
+ const leoStatus = result.status;
320
+ console.log('leoStatus', leoStatus);
321
+ const status =
322
+ leoStatus === 'Finalized'
323
+ ? TransactionStatus.ACCEPTED
324
+ : leoStatus === 'Completed' // Completed probably means Proving completed
325
+ ? TransactionStatus.PENDING
326
+ : leoStatus;
327
+
328
+ return {
329
+ status,
330
+ };
331
+ } catch (error: Error | unknown) {
332
+ throw new WalletTransactionError(
333
+ error instanceof Error ? error.message : 'Failed to get transaction status',
334
+ );
335
+ }
336
+ }
337
+
338
+ /**
339
+ * Request records from Leo wallet
340
+ * @param program The program to request records from
341
+ * @param includePlaintext Whether to include plaintext on each record
342
+ * @returns The records
343
+ */
344
+ async requestRecords(program: string, includePlaintext: boolean): Promise<unknown[]> {
345
+ if (!this._publicKey || !this.account) {
346
+ throw new WalletNotConnectedError();
347
+ }
348
+
349
+ try {
350
+ const result = includePlaintext
351
+ ? await this._leoWallet?.requestRecordPlaintexts(program)
352
+ : await this._leoWallet?.requestRecords(program);
353
+
354
+ return result?.records || [];
355
+ } catch (error: Error | unknown) {
356
+ throw new WalletError(error instanceof Error ? error.message : 'Failed to request records');
357
+ }
358
+ }
359
+
360
+ /**
361
+ * Switch the network
362
+ * @param network The network to switch to
363
+ */
364
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
365
+ async switchNetwork(_network: Network): Promise<void> {
366
+ console.error('Leo Wallet does not support switching networks');
367
+ throw new MethodNotImplementedError('switchNetwork');
368
+ }
369
+
370
+ /**
371
+ * Execute a deployment
372
+ * @param deployment The deployment to execute
373
+ * @returns The executed transaction ID
374
+ */
375
+ async executeDeployment(deployment: AleoDeployment): Promise<{ transactionId: string }> {
376
+ try {
377
+ if (!this._publicKey || !this.account) {
378
+ throw new WalletNotConnectedError();
379
+ }
380
+ try {
381
+ const leoDeployment = new Deployment(
382
+ this._publicKey,
383
+ LEO_NETWORK_MAP[this.network],
384
+ deployment.program,
385
+ deployment.fee,
386
+ deployment.feePrivate,
387
+ );
388
+ const result = await this._leoWallet?.requestDeploy(leoDeployment);
389
+ if (!result?.transactionId) {
390
+ throw new WalletTransactionError('Could not create deployment');
391
+ }
392
+ return {
393
+ transactionId: result.transactionId,
394
+ };
395
+ } catch (error: Error | unknown) {
396
+ throw new WalletTransactionError(
397
+ error instanceof Error ? error.message : 'Failed to execute deployment',
398
+ );
399
+ }
400
+ } catch (error: Error | unknown) {
401
+ this.emit('error', error instanceof Error ? error : new Error(String(error)));
402
+ throw error;
403
+ }
404
+ }
405
+ }
package/src/index.ts ADDED
@@ -0,0 +1,2 @@
1
+ export * from './LeoWalletAdapter';
2
+ export * from './types';
package/src/types.ts ADDED
@@ -0,0 +1,141 @@
1
+ import { Network } from '@provablehq/aleo-types';
2
+ import { EventEmitter, WalletDecryptPermission } from '@provablehq/aleo-wallet-standard';
3
+
4
+ /**
5
+ * Leo wallet adapter configuration
6
+ */
7
+ export interface LeoWalletAdapterConfig {
8
+ /**
9
+ * Application name
10
+ */
11
+ appName?: string;
12
+
13
+ /**
14
+ * Application icon URL
15
+ */
16
+ appIconUrl?: string;
17
+
18
+ /**
19
+ * Application description
20
+ */
21
+ appDescription?: string;
22
+
23
+ /**
24
+ * Program ID permissions by network
25
+ */
26
+ programIdPermissions?: Record<string, string[]>;
27
+
28
+ /**
29
+ * Whether the wallet is mobile
30
+ */
31
+ isMobile?: boolean;
32
+
33
+ /**
34
+ * The mobile webview URL
35
+ */
36
+ mobileWebviewUrl?: string;
37
+ }
38
+
39
+ export interface LeoWalletEvents {
40
+ connect(...args: unknown[]): unknown;
41
+ disconnect(...args: unknown[]): unknown;
42
+ accountChange(...args: unknown[]): unknown;
43
+ }
44
+
45
+ export type LeoNetwork = 'mainnet' | 'testnetbeta';
46
+
47
+ export const LEO_NETWORK_MAP: Record<Network, LeoNetwork> = {
48
+ [Network.MAINNET]: 'mainnet',
49
+ [Network.TESTNET3]: 'testnetbeta',
50
+ [Network.CANARY]: 'testnetbeta',
51
+ };
52
+
53
+ export interface LeoWallet extends EventEmitter<LeoWalletEvents> {
54
+ publicKey?: string;
55
+ isAvailable(): Promise<boolean>;
56
+ signMessage(message: Uint8Array): Promise<{ signature: Uint8Array }>;
57
+ decrypt(
58
+ cipherText: string,
59
+ tpk?: string,
60
+ programId?: string,
61
+ functionName?: string,
62
+ index?: number,
63
+ ): Promise<{ text: string }>;
64
+ requestRecords(program: string): Promise<{ records: unknown[] }>;
65
+ requestTransaction(transaction: AleoTransaction): Promise<{ transactionId?: string }>;
66
+ requestExecution(transaction: AleoTransaction): Promise<{ transactionId?: string }>;
67
+ requestBulkTransactions(transactions: AleoTransaction[]): Promise<{ transactionIds?: string[] }>;
68
+ requestDeploy(deployment: AleoDeployment): Promise<{ transactionId?: string }>;
69
+ transactionStatus(transactionId: string): Promise<{ status: string }>;
70
+ transitionViewKeys(transactionId: string): Promise<{ viewKeys?: string[] }>;
71
+ getExecution(transactionId: string): Promise<{ execution: string }>;
72
+ requestRecordPlaintexts(program: string): Promise<{ records: unknown[] }>;
73
+ requestTransactionHistory(program: string): Promise<{ transactions: unknown[] }>;
74
+ connect(
75
+ decryptPermission: WalletDecryptPermission,
76
+ network: LeoNetwork,
77
+ programs?: string[],
78
+ ): Promise<void>;
79
+ disconnect(): Promise<void>;
80
+ }
81
+
82
+ export interface LeoWindow extends Window {
83
+ leoWallet?: LeoWallet;
84
+ leo?: LeoWallet;
85
+ }
86
+
87
+ export interface AleoTransaction {
88
+ address: string;
89
+ chainId: string;
90
+ transitions: AleoTransition[];
91
+ fee: number;
92
+ feePrivate: boolean;
93
+ }
94
+
95
+ export interface AleoTransition {
96
+ program: string;
97
+ functionName: string;
98
+ inputs: unknown[];
99
+ }
100
+
101
+ export class Transition implements AleoTransition {
102
+ program: string;
103
+ functionName: string;
104
+ inputs: unknown[];
105
+
106
+ constructor(program: string, functionName: string, inputs: unknown[]) {
107
+ this.program = program;
108
+ this.functionName = functionName;
109
+ this.inputs = inputs;
110
+ }
111
+ }
112
+
113
+ export interface AleoDeployment {
114
+ address: string;
115
+ chainId: string;
116
+ program: string;
117
+ fee: number;
118
+ feePrivate: boolean;
119
+ }
120
+
121
+ export class Deployment implements AleoDeployment {
122
+ address: string;
123
+ chainId: string;
124
+ program: string;
125
+ fee: number;
126
+ feePrivate: boolean;
127
+
128
+ constructor(
129
+ address: string,
130
+ chainId: string,
131
+ program: string,
132
+ fee: number,
133
+ feePrivate: boolean = true,
134
+ ) {
135
+ this.address = address;
136
+ this.chainId = chainId;
137
+ this.program = program;
138
+ this.fee = fee;
139
+ this.feePrivate = feePrivate;
140
+ }
141
+ }