@solana-mobile/mobile-wallet-adapter-protocol 0.0.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/LICENSE ADDED
@@ -0,0 +1,13 @@
1
+ Copyright 2022 Solana Mobile Inc.
2
+
3
+ Licensed under the Apache License, Version 2.0 (the "License");
4
+ you may not use this file except in compliance with the License.
5
+ You may obtain a copy of the License at
6
+
7
+ http://www.apache.org/licenses/LICENSE-2.0
8
+
9
+ Unless required by applicable law or agreed to in writing, software
10
+ distributed under the License is distributed on an "AS IS" BASIS,
11
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ See the License for the specific language governing permissions and
13
+ limitations under the License.
@@ -0,0 +1,479 @@
1
+ 'use strict';
2
+
3
+ Object.defineProperty(exports, '__esModule', { value: true });
4
+
5
+ /*! *****************************************************************************
6
+ Copyright (c) Microsoft Corporation.
7
+
8
+ Permission to use, copy, modify, and/or distribute this software for any
9
+ purpose with or without fee is hereby granted.
10
+
11
+ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
12
+ REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
13
+ AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
14
+ INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
15
+ LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
16
+ OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
17
+ PERFORMANCE OF THIS SOFTWARE.
18
+ ***************************************************************************** */
19
+
20
+ function __awaiter(thisArg, _arguments, P, generator) {
21
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
22
+ return new (P || (P = Promise))(function (resolve, reject) {
23
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
24
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
25
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
26
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
27
+ });
28
+ }
29
+
30
+ function createHelloReq(ecdhPublicKey, associationKeypairPrivateKey) {
31
+ return __awaiter(this, void 0, void 0, function* () {
32
+ const publicKeyBuffer = yield crypto.subtle.exportKey('raw', ecdhPublicKey);
33
+ const signatureBuffer = yield crypto.subtle.sign({ hash: 'SHA-256', name: 'ECDSA' }, associationKeypairPrivateKey, publicKeyBuffer);
34
+ const response = new Uint8Array(publicKeyBuffer.byteLength + signatureBuffer.byteLength);
35
+ response.set(new Uint8Array(publicKeyBuffer), 0);
36
+ response.set(new Uint8Array(signatureBuffer), publicKeyBuffer.byteLength);
37
+ return response;
38
+ });
39
+ }
40
+
41
+ class SolanaMobileWalletAdapterSecureContextRequiredError extends Error {
42
+ constructor() {
43
+ super('The mobile wallet adapter protocol must be used in a secure context (`https`).');
44
+ this.name = 'SolanaMobileWalletAdapterSecureContextRequiredError';
45
+ }
46
+ }
47
+ class SolanaMobileWalletAdapterForbiddenWalletBaseURLError extends Error {
48
+ constructor() {
49
+ super('Base URLs supplied by wallets must be valid `https` URLs');
50
+ this.name = 'SolanaMobileWalletAdapterForbiddenWalletBaseURLError';
51
+ }
52
+ }
53
+ class SolanaMobileWalletAdapterWalletNotInstalledError extends Error {
54
+ constructor() {
55
+ super(`Found no installed wallet that supports the mobile wallet protocol.`);
56
+ this.name = 'SolanaMobileWalletAdapterWalletNotInstalledError';
57
+ }
58
+ }
59
+ class SolanaMobileWalletAdapterProtocolSessionEstablishmentError extends Error {
60
+ constructor(port) {
61
+ super(`Failed to connect to the wallet websocket on port ${port}.`);
62
+ this.name = 'SolanaMobileWalletAdapterProtocolSessionEstablishmentError';
63
+ }
64
+ }
65
+ class SolanaMobileWalletAdapterProtocolAssociationPortOutOfRangeError extends Error {
66
+ constructor(port) {
67
+ super(`Association port number must be between 49152 and 65535. ${port} given.`);
68
+ this.name = 'SolanaMobileWalletAdapterProtocolAssociationPortOutOfRangeError';
69
+ }
70
+ }
71
+ class SolanaMobileWalletAdapterProtocolSessionClosedError extends Error {
72
+ constructor(code, reason) {
73
+ super(`The wallet session dropped unexpectedly (${code}: ${reason}).`);
74
+ this.name = 'SolanaMobileWalletAdapterProtocolSessionClosedError';
75
+ }
76
+ }
77
+ class SolanaMobileWalletAdapterProtocolReauthorizeError extends Error {
78
+ constructor() {
79
+ super('The auth token provided has gone stale and needs reauthorization.');
80
+ this.name = 'SolanaMobileWalletAdapterProtocolReauthorizeError';
81
+ }
82
+ }
83
+ // Typescript `enums` thwart tree-shaking. See https://bargsten.org/jsts/enums/
84
+ const SolanaMobileWalletAdapterProtocolError = {
85
+ ERROR_REAUTHORIZE: -1,
86
+ ERROR_AUTHORIZATION_FAILED: -2,
87
+ ERROR_INVALID_PAYLOAD: -3,
88
+ ERROR_NOT_SIGNED: -4,
89
+ ERROR_NOT_COMMITTED: -5,
90
+ ERROR_ATTEST_ORIGIN_ANDROID: -100,
91
+ };
92
+ class SolanaMobileWalletAdapterProtocolJsonRpcError extends Error {
93
+ constructor(...args) {
94
+ const [jsonRpcMessageId, code, message, data] = args;
95
+ super(message);
96
+ this.code = code;
97
+ this.data = data;
98
+ this.jsonRpcMessageId = jsonRpcMessageId;
99
+ this.name = 'SolanaNativeWalletAdapterJsonRpcError';
100
+ }
101
+ }
102
+
103
+ function generateAssociationKeypair() {
104
+ return __awaiter(this, void 0, void 0, function* () {
105
+ return yield crypto.subtle.generateKey({
106
+ name: 'ECDSA',
107
+ namedCurve: 'P-256',
108
+ }, false /* extractable */, ['sign'] /* keyUsages */);
109
+ });
110
+ }
111
+
112
+ function generateECDHKeypair() {
113
+ return __awaiter(this, void 0, void 0, function* () {
114
+ return yield crypto.subtle.generateKey({
115
+ name: 'ECDH',
116
+ namedCurve: 'P-256',
117
+ }, false /* extractable */, ['deriveKey', 'deriveBits'] /* keyUsages */);
118
+ });
119
+ }
120
+
121
+ const INITIALIZATION_VECTOR_BYTES = 12;
122
+ function encryptJsonRpcMessage(jsonRpcMessage, sharedSecret) {
123
+ return __awaiter(this, void 0, void 0, function* () {
124
+ const plaintext = JSON.stringify(jsonRpcMessage);
125
+ const initializationVector = new Uint8Array(INITIALIZATION_VECTOR_BYTES);
126
+ crypto.getRandomValues(initializationVector);
127
+ const ciphertext = yield crypto.subtle.encrypt(getAlgorithmParams(initializationVector), sharedSecret, Buffer.from(plaintext));
128
+ const response = new Uint8Array(initializationVector.byteLength + ciphertext.byteLength);
129
+ response.set(new Uint8Array(initializationVector), 0);
130
+ response.set(new Uint8Array(ciphertext), initializationVector.byteLength);
131
+ return response;
132
+ });
133
+ }
134
+ function decryptJsonRpcMessage(message, sharedSecret) {
135
+ return __awaiter(this, void 0, void 0, function* () {
136
+ const initializationVector = message.slice(0, INITIALIZATION_VECTOR_BYTES);
137
+ const ciphertext = message.slice(INITIALIZATION_VECTOR_BYTES);
138
+ const plaintextBuffer = yield crypto.subtle.decrypt(getAlgorithmParams(initializationVector), sharedSecret, ciphertext);
139
+ const plaintext = getUtf8Decoder().decode(plaintextBuffer);
140
+ const jsonRpcMessage = JSON.parse(plaintext);
141
+ if (Object.hasOwnProperty.call(jsonRpcMessage, 'error')) {
142
+ throw new SolanaMobileWalletAdapterProtocolJsonRpcError(jsonRpcMessage.id, jsonRpcMessage.error.code, jsonRpcMessage.error.message);
143
+ }
144
+ return jsonRpcMessage;
145
+ });
146
+ }
147
+ function getAlgorithmParams(initializationVector) {
148
+ return {
149
+ iv: initializationVector,
150
+ name: 'AES-GCM',
151
+ tagLength: 128, // 16 byte tag => 128 bits
152
+ };
153
+ }
154
+ let _utf8Decoder;
155
+ function getUtf8Decoder() {
156
+ if (_utf8Decoder === undefined) {
157
+ _utf8Decoder = new TextDecoder('utf-8');
158
+ }
159
+ return _utf8Decoder;
160
+ }
161
+
162
+ function parseHelloRsp(payloadBuffer, // The X9.62-encoded wallet endpoint ephemeral ECDH public keypoint.
163
+ associationPublicKey, ecdhPrivateKey) {
164
+ return __awaiter(this, void 0, void 0, function* () {
165
+ const [associationPublicKeyBuffer, walletPublicKey] = yield Promise.all([
166
+ crypto.subtle.exportKey('raw', associationPublicKey),
167
+ crypto.subtle.importKey('raw', payloadBuffer, { name: 'ECDH', namedCurve: 'P-256' }, false /* extractable */, [] /* keyUsages */),
168
+ ]);
169
+ const sharedSecret = yield crypto.subtle.deriveBits({ name: 'ECDH', public: walletPublicKey }, ecdhPrivateKey, 256);
170
+ const ecdhSecretKey = yield crypto.subtle.importKey('raw', sharedSecret, 'HKDF', false /* extractable */, ['deriveKey'] /* keyUsages */);
171
+ const aesKeyMaterialVal = yield crypto.subtle.deriveKey({
172
+ name: 'HKDF',
173
+ hash: 'SHA-256',
174
+ salt: new Uint8Array(associationPublicKeyBuffer),
175
+ info: new Uint8Array(),
176
+ }, ecdhSecretKey, { name: 'AES-GCM', length: 128 }, false /* extractable */, ['encrypt', 'decrypt']);
177
+ return aesKeyMaterialVal;
178
+ });
179
+ }
180
+
181
+ function getRandomAssociationPort() {
182
+ return assertAssociationPort(49152 + Math.floor(Math.random() * (65535 - 49152 + 1)));
183
+ }
184
+ function assertAssociationPort(port) {
185
+ if (port < 49152 || port > 65535) {
186
+ throw new SolanaMobileWalletAdapterProtocolAssociationPortOutOfRangeError(port);
187
+ }
188
+ return port;
189
+ }
190
+
191
+ // https://stackoverflow.com/a/9458996/802047
192
+ function arrayBufferToBase64String(buffer) {
193
+ let binary = '';
194
+ const bytes = new Uint8Array(buffer);
195
+ const len = bytes.byteLength;
196
+ for (let ii = 0; ii < len; ii++) {
197
+ binary += String.fromCharCode(bytes[ii]);
198
+ }
199
+ return window.btoa(binary);
200
+ }
201
+
202
+ function getStringWithURLUnsafeCharactersReplaced(unsafeBase64EncodedString) {
203
+ return unsafeBase64EncodedString.replace(/[/+=]/g, (m) => ({
204
+ '/': '_',
205
+ '+': '-',
206
+ '=': '.',
207
+ }[m]));
208
+ }
209
+
210
+ const INTENT_NAME = 'solana-wallet';
211
+ function getPathParts(pathString) {
212
+ return (pathString
213
+ // Strip leading and trailing slashes
214
+ .replace(/(^\/+|\/+$)/g, '')
215
+ // Return an array of directories
216
+ .split('/'));
217
+ }
218
+ function getIntentURL(methodPathname, intentUrlBase) {
219
+ let baseUrl = null;
220
+ if (intentUrlBase) {
221
+ try {
222
+ baseUrl = new URL(intentUrlBase);
223
+ }
224
+ catch (_a) { } // eslint-disable-line no-empty
225
+ if ((baseUrl === null || baseUrl === void 0 ? void 0 : baseUrl.protocol) !== 'https:') {
226
+ throw new SolanaMobileWalletAdapterForbiddenWalletBaseURLError();
227
+ }
228
+ }
229
+ baseUrl || (baseUrl = new URL(`${INTENT_NAME}:/`));
230
+ const pathname = methodPathname.startsWith('/')
231
+ ? // Method is an absolute path. Replace it wholesale.
232
+ methodPathname
233
+ : // Method is a relative path. Merge it with the existing one.
234
+ [...getPathParts(baseUrl.pathname), ...getPathParts(methodPathname)].join('/');
235
+ return new URL(pathname, baseUrl);
236
+ }
237
+ function getAssociateAndroidIntentURL(associationPublicKey, putativePort, associationURLBase) {
238
+ return __awaiter(this, void 0, void 0, function* () {
239
+ const associationPort = assertAssociationPort(putativePort);
240
+ const exportedKey = yield crypto.subtle.exportKey('raw', associationPublicKey);
241
+ const encodedKey = arrayBufferToBase64String(exportedKey);
242
+ const url = getIntentURL('v1/associate/local', associationURLBase);
243
+ url.searchParams.set('association', getStringWithURLUnsafeCharactersReplaced(encodedKey));
244
+ url.searchParams.set('port', `${associationPort}`);
245
+ return url;
246
+ });
247
+ }
248
+
249
+ // Typescript `enums` thwart tree-shaking. See https://bargsten.org/jsts/enums/
250
+ const Browser = {
251
+ Firefox: 0,
252
+ Other: 1,
253
+ };
254
+ function assertUnreachable(x) {
255
+ return x;
256
+ }
257
+ function getBrowser() {
258
+ return navigator.userAgent.indexOf('Firefox/') !== -1 ? Browser.Firefox : Browser.Other;
259
+ }
260
+ function getDetectionPromise() {
261
+ // Chrome and others silently fail if a custom protocol is not supported.
262
+ // For these, we wait to see if the browser is navigated away from in
263
+ // a reasonable amount of time (ie. the native wallet opened).
264
+ return new Promise((resolve, reject) => {
265
+ function cleanup() {
266
+ clearTimeout(timeoutId);
267
+ window.removeEventListener('blur', handleBlur);
268
+ }
269
+ function handleBlur() {
270
+ cleanup();
271
+ resolve();
272
+ }
273
+ window.addEventListener('blur', handleBlur);
274
+ const timeoutId = setTimeout(() => {
275
+ cleanup();
276
+ reject();
277
+ }, 2000);
278
+ });
279
+ }
280
+ let _frame = null;
281
+ function launchUrlThroughHiddenFrame(url) {
282
+ if (_frame == null) {
283
+ _frame = document.createElement('iframe');
284
+ _frame.style.display = 'none';
285
+ document.body.appendChild(_frame);
286
+ }
287
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
288
+ _frame.contentWindow.location.href = url.toString();
289
+ }
290
+ function startSession(associationPublicKey, associationURLBase) {
291
+ return __awaiter(this, void 0, void 0, function* () {
292
+ const randomAssociationPort = getRandomAssociationPort();
293
+ const associationUrl = yield getAssociateAndroidIntentURL(associationPublicKey, randomAssociationPort, associationURLBase);
294
+ if (associationUrl.protocol === 'https:') {
295
+ // The association URL is an Android 'App Link' or iOS 'Universal Link'.
296
+ // These are regular web URLs that are designed to launch an app if it
297
+ // is installed or load the actual target webpage if not.
298
+ window.location.assign(associationUrl);
299
+ }
300
+ else {
301
+ // The association URL has a custom protocol (eg. `solana-wallet:`)
302
+ try {
303
+ const browser = getBrowser();
304
+ switch (browser) {
305
+ case Browser.Firefox:
306
+ // If a custom protocol is not supported in Firefox, it throws.
307
+ launchUrlThroughHiddenFrame(associationUrl);
308
+ // If we reached this line, it's supported.
309
+ break;
310
+ case Browser.Other: {
311
+ const detectionPromise = getDetectionPromise();
312
+ window.location.assign(associationUrl);
313
+ yield detectionPromise;
314
+ break;
315
+ }
316
+ default:
317
+ assertUnreachable(browser);
318
+ }
319
+ }
320
+ catch (e) {
321
+ throw new SolanaMobileWalletAdapterWalletNotInstalledError();
322
+ }
323
+ }
324
+ return randomAssociationPort;
325
+ });
326
+ }
327
+
328
+ const WEBSOCKET_CONNECTION_CONFIG = {
329
+ maxAttempts: 34,
330
+ /**
331
+ * 300 milliseconds is a generally accepted threshold for what someone
332
+ * would consider an acceptable response time for a user interface
333
+ * after having performed a low-attention tapping task. We set the
334
+ * interval at which we wait for the wallet to set up the websocket at
335
+ * half this, as per the Nyquist frequency.
336
+ */
337
+ retryDelayMs: 150,
338
+ };
339
+ const WEBSOCKET_PROTOCOL = 'com.solana.mobilewalletadapter.v1';
340
+ function assertSecureContext() {
341
+ if (typeof window === 'undefined' || window.isSecureContext !== true) {
342
+ throw new SolanaMobileWalletAdapterSecureContextRequiredError();
343
+ }
344
+ }
345
+ function withLocalWallet(callback, config) {
346
+ return __awaiter(this, void 0, void 0, function* () {
347
+ assertSecureContext();
348
+ const associationKeypair = yield generateAssociationKeypair();
349
+ const sessionPort = yield startSession(associationKeypair.publicKey, config === null || config === void 0 ? void 0 : config.baseUri);
350
+ const websocketURL = `ws://localhost:${sessionPort}/solana-wallet`;
351
+ let nextJsonRpcMessageId = 1;
352
+ let state = { __type: 'disconnected' };
353
+ return new Promise((resolve, reject) => {
354
+ let attempts = 0;
355
+ let socket;
356
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
357
+ const jsonRpcResponsePromises = {};
358
+ const handleOpen = () => __awaiter(this, void 0, void 0, function* () {
359
+ if (state.__type !== 'connecting') {
360
+ console.warn('Expected adapter state to be `connecting` at the moment the websocket opens. ' +
361
+ `Got \`${state.__type}\`.`);
362
+ return;
363
+ }
364
+ const { associationKeypair } = state;
365
+ socket.removeEventListener('open', handleOpen);
366
+ const ecdhKeypair = yield generateECDHKeypair();
367
+ socket.send(yield createHelloReq(ecdhKeypair.publicKey, associationKeypair.privateKey));
368
+ state = {
369
+ __type: 'hello_req_sent',
370
+ associationPublicKey: associationKeypair.publicKey,
371
+ ecdhPrivateKey: ecdhKeypair.privateKey,
372
+ };
373
+ });
374
+ const handleClose = (evt) => {
375
+ if (evt.wasClean) {
376
+ state = { __type: 'disconnected' };
377
+ }
378
+ else {
379
+ reject(new SolanaMobileWalletAdapterProtocolSessionClosedError(evt.code, evt.reason));
380
+ }
381
+ disposeSocket();
382
+ };
383
+ const handleError = (_evt) => __awaiter(this, void 0, void 0, function* () {
384
+ disposeSocket();
385
+ if (++attempts >= WEBSOCKET_CONNECTION_CONFIG.maxAttempts) {
386
+ reject(new SolanaMobileWalletAdapterProtocolSessionEstablishmentError(sessionPort));
387
+ }
388
+ else {
389
+ yield new Promise((resolve) => {
390
+ retryWaitTimeoutId = window.setTimeout(resolve, WEBSOCKET_CONNECTION_CONFIG.retryDelayMs);
391
+ });
392
+ attemptSocketConnection();
393
+ }
394
+ });
395
+ const handleMessage = (evt) => __awaiter(this, void 0, void 0, function* () {
396
+ const responseBuffer = yield evt.data.arrayBuffer();
397
+ switch (state.__type) {
398
+ case 'connected':
399
+ try {
400
+ const jsonRpcMessage = yield decryptJsonRpcMessage(responseBuffer, state.sharedSecret);
401
+ const responsePromise = jsonRpcResponsePromises[jsonRpcMessage.id];
402
+ delete jsonRpcResponsePromises[jsonRpcMessage.id];
403
+ responsePromise.resolve(jsonRpcMessage.result);
404
+ }
405
+ catch (e) {
406
+ if (e instanceof SolanaMobileWalletAdapterProtocolJsonRpcError) {
407
+ const responsePromise = jsonRpcResponsePromises[e.jsonRpcMessageId];
408
+ delete jsonRpcResponsePromises[e.jsonRpcMessageId];
409
+ responsePromise.reject(e);
410
+ }
411
+ else {
412
+ throw e;
413
+ }
414
+ }
415
+ break;
416
+ case 'hello_req_sent': {
417
+ const sharedSecret = yield parseHelloRsp(responseBuffer, state.associationPublicKey, state.ecdhPrivateKey);
418
+ state = { __type: 'connected', sharedSecret };
419
+ const wallet = (method, params) => __awaiter(this, void 0, void 0, function* () {
420
+ const id = nextJsonRpcMessageId++;
421
+ socket.send(yield encryptJsonRpcMessage({
422
+ id,
423
+ jsonrpc: '2.0',
424
+ method,
425
+ params,
426
+ }, sharedSecret));
427
+ return new Promise((resolve, reject) => {
428
+ jsonRpcResponsePromises[id] = { resolve, reject };
429
+ });
430
+ });
431
+ try {
432
+ resolve(yield callback(wallet));
433
+ }
434
+ catch (e) {
435
+ reject(e);
436
+ }
437
+ finally {
438
+ disposeSocket();
439
+ socket.close();
440
+ }
441
+ break;
442
+ }
443
+ }
444
+ });
445
+ let disposeSocket;
446
+ let retryWaitTimeoutId;
447
+ const attemptSocketConnection = () => {
448
+ if (disposeSocket) {
449
+ disposeSocket();
450
+ }
451
+ state = { __type: 'connecting', associationKeypair };
452
+ socket = new WebSocket(websocketURL, [WEBSOCKET_PROTOCOL]);
453
+ socket.addEventListener('open', handleOpen);
454
+ socket.addEventListener('close', handleClose);
455
+ socket.addEventListener('error', handleError);
456
+ socket.addEventListener('message', handleMessage);
457
+ disposeSocket = () => {
458
+ window.clearTimeout(retryWaitTimeoutId);
459
+ socket.removeEventListener('open', handleOpen);
460
+ socket.removeEventListener('close', handleClose);
461
+ socket.removeEventListener('error', handleError);
462
+ socket.removeEventListener('message', handleMessage);
463
+ };
464
+ };
465
+ attemptSocketConnection();
466
+ });
467
+ });
468
+ }
469
+
470
+ exports.SolanaMobileWalletAdapterForbiddenWalletBaseURLError = SolanaMobileWalletAdapterForbiddenWalletBaseURLError;
471
+ exports.SolanaMobileWalletAdapterProtocolAssociationPortOutOfRangeError = SolanaMobileWalletAdapterProtocolAssociationPortOutOfRangeError;
472
+ exports.SolanaMobileWalletAdapterProtocolError = SolanaMobileWalletAdapterProtocolError;
473
+ exports.SolanaMobileWalletAdapterProtocolJsonRpcError = SolanaMobileWalletAdapterProtocolJsonRpcError;
474
+ exports.SolanaMobileWalletAdapterProtocolReauthorizeError = SolanaMobileWalletAdapterProtocolReauthorizeError;
475
+ exports.SolanaMobileWalletAdapterProtocolSessionClosedError = SolanaMobileWalletAdapterProtocolSessionClosedError;
476
+ exports.SolanaMobileWalletAdapterProtocolSessionEstablishmentError = SolanaMobileWalletAdapterProtocolSessionEstablishmentError;
477
+ exports.SolanaMobileWalletAdapterSecureContextRequiredError = SolanaMobileWalletAdapterSecureContextRequiredError;
478
+ exports.SolanaMobileWalletAdapterWalletNotInstalledError = SolanaMobileWalletAdapterWalletNotInstalledError;
479
+ exports.withLocalWallet = withLocalWallet;