@vue-solana/vue 4.0.0-rc.0 → 4.0.0-rc.1

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/README.md CHANGED
@@ -276,6 +276,8 @@ const { publicKey, connected, connecting, canSignMessage, connect, disconnect }
276
276
 
277
277
  Browser extension wallets are discovered through the Solana Wallet Standard. Android Mobile Wallet Adapter wallets are registered through `@solana-mobile/wallet-standard-mobile` and exposed through the same `useWallets()` list on supported Android Chrome clients. iOS Phantom, Solflare, and Backpack entries are exposed through wallet-specific universal links on iOS browsers. `connect()` works after selecting a discovered wallet or configuring a custom `SolanaWallet`.
278
278
 
279
+ Connecting a wallet also disconnects every other discovered wallet through its `standard:disconnect` feature, so only one wallet stays authorized for the app. An extension the user authorized in an earlier page session can otherwise keep answering signing requests, which is the usual cause of the wrong wallet prompting for a `signMessage` confirmation. A wallet that fails or never answers its disconnect is ignored after a 2 second timeout, and `connect()` resolves only after the sweep finishes.
280
+
279
281
  Selected discovered wallets are persisted under `localStorage["vue-solana:selected-wallet"]` as non-sensitive identity metadata: `name`, and `platform`/`source` when available. On reload, Vue Solana restores the selected wallet if the same wallet is discovered again. Pass `autoConnect: true` to opt into calling `connect()` for that restored wallet; arbitrary installed wallets are never auto-connected. Calling `selectWallet(null)` or `setWallet(customWallet)` clears the stored selection.
280
282
 
281
283
  Desktop native app wallet adapters are planned but not implemented yet.
package/dist/index.mjs CHANGED
@@ -11,12 +11,12 @@ export { u as useRequest } from './shared/vue.B8VvC8d2.mjs';
11
11
  export { u as useRpc } from './shared/vue.D8zNWw2t.mjs';
12
12
  export { S as SelectedWalletAccountProvider, c as createSelectedWalletAccountContext, p as provideSelectedWalletAccount, s as selectedWalletAccountInjectionKey, u as useSelectedWalletAccount } from './shared/vue.BngCaeXJ.mjs';
13
13
  export { u as useSendTransaction, a as useSendTransactions } from './shared/vue.CLAqNcLp.mjs';
14
- export { u as useSignMessage } from './shared/vue.DbJG4-ku.mjs';
15
- export { u as useSignAndSendTransaction } from './shared/vue.4ne5EBD9.mjs';
16
- export { u as useSignTransactions } from './shared/vue.CW-1AuSO.mjs';
17
- export { P as PartialSignAndSendError, u as useSignAndSendTransactions } from './shared/vue.UrCjmf0T.mjs';
14
+ export { u as useSignMessage } from './shared/vue.Ddf_Cdjx.mjs';
15
+ export { u as useSignAndSendTransaction } from './shared/vue.BTD4usyi.mjs';
16
+ export { u as useSignTransactions } from './shared/vue.BHBifdbc.mjs';
17
+ export { P as PartialSignAndSendError, u as useSignAndSendTransactions } from './shared/vue.C-7v1l8q.mjs';
18
18
  export { u as useSignatureStatus } from './shared/vue.Bu2cQ690.mjs';
19
- export { u as useSignIn } from './shared/vue.BBN9aU6D.mjs';
19
+ export { u as useSignIn } from './shared/vue.SeuJipQ8.mjs';
20
20
  export { t as tryUseSolana, u as useSolana } from './shared/vue.CcOZ9858.mjs';
21
21
  export { u as useSolanaClient } from './shared/vue.CZApWQAY.mjs';
22
22
  export { u as useSubscription } from './shared/vue.D5pqR5Fa.mjs';
@@ -25,7 +25,7 @@ export { u as useTokenBalance } from './shared/vue.FehFeE3w.mjs';
25
25
  export { u as useTrackedData } from './shared/vue.BNJTXSnQ.mjs';
26
26
  export { u as useTransaction } from './shared/vue.5euWd6-V.mjs';
27
27
  export { g as getConfirmedTransactionStatus, u as useTransactionConfirmation } from './shared/vue.DPwdqRO_.mjs';
28
- export { u as useWallet } from './shared/vue.DY9VadLH.mjs';
28
+ export { u as useWallet } from './shared/vue.Bc_Kbwr5.mjs';
29
29
  export { u as useWallets } from './shared/vue.DyLgoSbX.mjs';
30
30
  import { s as solanaInjectionKey } from './shared/vue.D_P5SqqD.mjs';
31
31
  import { createSolanaError, normalizeSolanaError } from '@vue-solana/core/errors';
@@ -33,7 +33,7 @@ export { SolanaError, isSolanaError } from '@vue-solana/core/errors';
33
33
  import { isSolanaMobileWalletSupported } from '@vue-solana/core/mobile-wallet-support';
34
34
  import { createSolanaContext } from '@vue-solana/core/rpc';
35
35
  import { withTimeout } from '@vue-solana/core/timeout';
36
- import { getRegisteredSolanaWallets, getSolanaChain, adaptSolanaStandardWallet, subscribeSolanaWallets } from '@vue-solana/core/wallet-standard';
36
+ import { getRegisteredSolanaWallets, getSolanaChain, adaptSolanaStandardWallet, disconnectSolanaStandardWallet, subscribeSolanaWallets } from '@vue-solana/core/wallet-standard';
37
37
  import { shallowRef, ref, triggerRef } from 'vue';
38
38
  import { handleSolanaIosWalletCallback, getSolanaIosWallets, isSolanaIosWalletInfo, adaptSolanaIosWallet } from '@vue-solana/core/ios-wallet';
39
39
  export * from '@vue-solana/core/types';
@@ -112,6 +112,7 @@ function getLocalStorage() {
112
112
  }
113
113
  }
114
114
 
115
+ const DISCONNECT_TIMEOUT_MS = 2e3;
115
116
  function createSolanaWalletRegistry(options) {
116
117
  const adaptedWallets = /* @__PURE__ */ new WeakMap();
117
118
  const adaptedPrimitiveWallets = /* @__PURE__ */ new Map();
@@ -126,25 +127,31 @@ function createSolanaWalletRegistry(options) {
126
127
  if (cachedIosWallet) {
127
128
  return cachedIosWallet;
128
129
  }
129
- const iosAdapter = adaptSolanaIosWallet(walletInfo, {
130
- chain: getSolanaChain(options.cluster),
131
- cluster: options.cluster,
132
- onChange: options.onWalletChange,
133
- ...options.iosWallet || {}
134
- });
130
+ const iosAdapter = makeConnectExclusive(
131
+ adaptSolanaIosWallet(walletInfo, {
132
+ chain: getSolanaChain(options.cluster),
133
+ cluster: options.cluster,
134
+ onChange: options.onWalletChange,
135
+ ...options.iosWallet || {}
136
+ }),
137
+ walletInfo
138
+ );
135
139
  adaptedWallets.set(walletInfo.wallet, iosAdapter);
136
140
  return trackAdapter(iosAdapter);
137
141
  }
138
142
  if (!isObject(walletInfo.wallet)) {
139
- const primitiveKey = `${walletInfo.name}::${String(walletInfo.wallet)}`;
143
+ const primitiveKey = getPrimitiveWalletKey(walletInfo);
140
144
  const cachedPrimitiveWallet = adaptedPrimitiveWallets.get(primitiveKey);
141
145
  if (cachedPrimitiveWallet) {
142
146
  return cachedPrimitiveWallet;
143
147
  }
144
- const primitiveAdapter = adaptSolanaStandardWallet(walletInfo, {
145
- chain: getSolanaChain(options.cluster),
146
- onChange: options.onWalletChange
147
- });
148
+ const primitiveAdapter = makeConnectExclusive(
149
+ adaptSolanaStandardWallet(walletInfo, {
150
+ chain: getSolanaChain(options.cluster),
151
+ onChange: options.onWalletChange
152
+ }),
153
+ walletInfo
154
+ );
148
155
  adaptedPrimitiveWallets.set(primitiveKey, primitiveAdapter);
149
156
  return trackAdapter(primitiveAdapter);
150
157
  }
@@ -152,30 +159,19 @@ function createSolanaWalletRegistry(options) {
152
159
  if (cachedWallet) {
153
160
  return cachedWallet;
154
161
  }
155
- const adaptedWallet = adaptSolanaStandardWallet(walletInfo, {
156
- chain: getSolanaChain(options.cluster),
157
- onChange: options.onWalletChange
158
- });
159
- const cachedAdapter = new Proxy(adaptedWallet, {
160
- get(target, property) {
161
- if (property === "connect") {
162
- return connectAndDisconnectOthers;
163
- }
164
- const value = Reflect.get(target, property);
165
- return typeof value === "function" ? value.bind(target) : value;
166
- }
167
- });
168
- async function connectAndDisconnectOthers() {
169
- await adaptedWallet.connect();
170
- await Promise.all(
171
- Array.from(getCachedWallets()).map(
172
- (otherWallet) => otherWallet !== cachedAdapter && otherWallet.connected ? otherWallet.disconnect() : void 0
173
- )
174
- );
175
- }
162
+ const cachedAdapter = makeConnectExclusive(
163
+ adaptSolanaStandardWallet(walletInfo, {
164
+ chain: getSolanaChain(options.cluster),
165
+ onChange: options.onWalletChange
166
+ }),
167
+ walletInfo
168
+ );
176
169
  adaptedWallets.set(walletInfo.wallet, cachedAdapter);
177
170
  return trackAdapter(cachedAdapter);
178
171
  }
172
+ function getCachedAdapter(walletInfo) {
173
+ return isObject(walletInfo.wallet) ? adaptedWallets.get(walletInfo.wallet) : adaptedPrimitiveWallets.get(getPrimitiveWalletKey(walletInfo));
174
+ }
179
175
  function getDiscoveredWallets() {
180
176
  return [
181
177
  ...getRegisteredSolanaWallets(),
@@ -190,17 +186,41 @@ function createSolanaWalletRegistry(options) {
190
186
  try {
191
187
  handleSolanaIosWalletCallback({ clearUrl: true });
192
188
  } catch (cause) {
189
+ options.onError?.(cause);
193
190
  }
194
191
  }
195
- function* getCachedWallets() {
196
- for (const walletInfo of options.getWalletInfos()) {
197
- if (isObject(walletInfo.wallet)) {
198
- const cachedWallet = adaptedWallets.get(walletInfo.wallet);
199
- if (cachedWallet) {
200
- yield cachedWallet;
192
+ function makeConnectExclusive(adapter, walletInfo) {
193
+ const connect = adapter.connect.bind(adapter);
194
+ adapter.connect = async () => {
195
+ await connect();
196
+ await disconnectOtherWallets(walletInfo, adapter);
197
+ };
198
+ return adapter;
199
+ }
200
+ async function disconnectOtherWallets(currentWallet, currentAdapter) {
201
+ await Promise.all(
202
+ options.getWalletInfos().map(async (walletInfo) => {
203
+ const cachedWallet = getCachedAdapter(walletInfo);
204
+ if (cachedWallet === currentAdapter || isSameWalletInfo(walletInfo, currentWallet)) {
205
+ return;
201
206
  }
202
- }
207
+ try {
208
+ await withTimeout(
209
+ disconnectWallet(walletInfo, cachedWallet),
210
+ DISCONNECT_TIMEOUT_MS,
211
+ () => new Error(`Solana wallet "${walletInfo.name}" did not disconnect within its timeout`)
212
+ );
213
+ } catch {
214
+ }
215
+ })
216
+ );
217
+ }
218
+ async function disconnectWallet(walletInfo, cachedWallet) {
219
+ if (cachedWallet) {
220
+ await cachedWallet.disconnect();
221
+ return;
203
222
  }
223
+ await disconnectSolanaStandardWallet(walletInfo);
204
224
  }
205
225
  function disposeAllAdaptedWallets() {
206
226
  for (const adapter of trackedAdapters) {
@@ -217,6 +237,12 @@ function createSolanaWalletRegistry(options) {
217
237
  function isObject(value) {
218
238
  return typeof value === "object" && value !== null || typeof value === "function";
219
239
  }
240
+ function getPrimitiveWalletKey(walletInfo) {
241
+ return `${walletInfo.name}::${String(walletInfo.wallet)}`;
242
+ }
243
+ function isSameWalletInfo(a, b) {
244
+ return a.wallet === b.wallet || a.name === b.name && a.source === b.source;
245
+ }
220
246
 
221
247
  const RPC_CHECK_TIMEOUT_MS = 1e4;
222
248
  function createSolanaPlugin(options = {}) {
@@ -235,7 +261,11 @@ function createSolanaPlugin(options = {}) {
235
261
  cluster: context.cluster,
236
262
  iosWallet: options.iosWallet,
237
263
  getWalletInfos: () => wallets.value,
238
- onWalletChange: () => triggerRef(wallet)
264
+ onWalletChange: () => triggerRef(wallet),
265
+ onError: (cause) => {
266
+ error.value = normalizeSolanaError(cause, "WALLET_FAILURE");
267
+ logDevError("Failed to handle Solana iOS wallet callback", cause);
268
+ }
239
269
  });
240
270
  let unsubscribeWallets = null;
241
271
  let mobileWalletRegistrationPromise = null;
@@ -267,6 +297,7 @@ function createSolanaPlugin(options = {}) {
267
297
  }
268
298
  status.value = "error";
269
299
  error.value = normalizeSolanaError(cause, "RPC_FAILURE");
300
+ logDevError("Connection failed", cause);
270
301
  }
271
302
  }
272
303
  function refreshWallets() {
@@ -286,6 +317,7 @@ function createSolanaPlugin(options = {}) {
286
317
  const persistedWallet = readSelectedWallet();
287
318
  if (persistedWallet.error) {
288
319
  error.value = persistedWallet.error;
320
+ logDevError("Failed to read the persisted Solana wallet", persistedWallet.error);
289
321
  }
290
322
  restoredWallet = persistedWallet.wallet ? wallets.value.find(
291
323
  (nextWallet) => isSameWallet(nextWallet, persistedWallet.wallet)
@@ -316,6 +348,8 @@ function createSolanaPlugin(options = {}) {
316
348
  });
317
349
  refreshWallets();
318
350
  }).catch((cause) => {
351
+ error.value = normalizeSolanaError(cause, "WALLET_FAILURE");
352
+ logDevError("Failed to register Solana mobile wallet", cause);
319
353
  }).finally(() => {
320
354
  mobileWalletRegistrationPromise = null;
321
355
  });
@@ -326,6 +360,7 @@ function createSolanaPlugin(options = {}) {
326
360
  const storageError = writeSelectedWallet(nextWallet);
327
361
  if (storageError) {
328
362
  error.value = storageError;
363
+ logDevError("Failed to persist the selected Solana wallet", storageError);
329
364
  }
330
365
  }
331
366
  function autoConnectWallet(walletInfo) {
@@ -339,6 +374,8 @@ function createSolanaPlugin(options = {}) {
339
374
  }
340
375
  attemptedAutoConnectWallet = storageValue;
341
376
  void activeWallet.connect().catch((cause) => {
377
+ error.value = normalizeSolanaError(cause, "WALLET_FAILURE");
378
+ logDevError("Failed to auto-connect Solana wallet", cause);
342
379
  }).finally(() => {
343
380
  triggerRef(wallet);
344
381
  });
@@ -360,6 +397,7 @@ function createSolanaPlugin(options = {}) {
360
397
  const storageError = writeSelectedWallet(null);
361
398
  if (storageError) {
362
399
  error.value = storageError;
400
+ logDevError("Failed to clear the persisted Solana wallet", storageError);
363
401
  }
364
402
  }
365
403
  };
@@ -378,11 +416,13 @@ function createSolanaPlugin(options = {}) {
378
416
  };
379
417
  if (typeof window !== "undefined") {
380
418
  bootstrapId = window.setTimeout(() => {
419
+ void checkConnection();
381
420
  try {
382
421
  refreshWallets();
383
422
  } catch (cause) {
423
+ error.value = normalizeSolanaError(cause, "WALLET_FAILURE");
424
+ logDevError("Failed to refresh Solana wallets", cause);
384
425
  }
385
- void checkConnection();
386
426
  }, 0);
387
427
  }
388
428
  },
@@ -395,6 +435,24 @@ function createSolanaPlugin(options = {}) {
395
435
  function isSameWallet(wallet, selectedWallet) {
396
436
  return wallet.name === selectedWallet?.name && wallet.source === selectedWallet.source && wallet.platform === selectedWallet.platform;
397
437
  }
438
+ function isDevEnvironment() {
439
+ try {
440
+ if (import.meta.env?.DEV === true) {
441
+ return true;
442
+ }
443
+ } catch {
444
+ }
445
+ if (typeof process === "undefined") {
446
+ return false;
447
+ }
448
+ const mode = process.env?.NODE_ENV;
449
+ return mode === "development" || mode === "test";
450
+ }
451
+ function logDevError(message, cause) {
452
+ if (isDevEnvironment()) {
453
+ console.error(`[Vue Solana] ${message}`, cause);
454
+ }
455
+ }
398
456
  const VueSolana = createSolanaPlugin;
399
457
 
400
458
  export { VueSolana, createSolanaPlugin, solanaInjectionKey };
@@ -1,7 +1,7 @@
1
1
  import { createNoWalletSelectedError, assertWalletCanSignTransactions } from '@vue-solana/core/wallet';
2
2
  import { shallowRef } from 'vue';
3
3
  import { u as useExecution } from './vue.BiKE9Xuv.mjs';
4
- import { u as useWallet } from './vue.DY9VadLH.mjs';
4
+ import { u as useWallet } from './vue.Bc_Kbwr5.mjs';
5
5
 
6
6
  function useSignTransactions() {
7
7
  const { wallet } = useWallet();
@@ -4,7 +4,7 @@ import { signAndSendTransaction, confirmTransactionSignature } from '@vue-solana
4
4
  import { createNoWalletSelectedError } from '@vue-solana/core/wallet';
5
5
  import { ref, computed } from 'vue';
6
6
  import { u as useSolanaClient } from './vue.CZApWQAY.mjs';
7
- import { u as useWallet } from './vue.DY9VadLH.mjs';
7
+ import { u as useWallet } from './vue.Bc_Kbwr5.mjs';
8
8
  import { g as getConfirmedTransactionStatus } from './vue.DPwdqRO_.mjs';
9
9
 
10
10
  const SIGN_AND_SEND_TIMEOUT_MS = 12e4;
@@ -8,6 +8,12 @@ function useWallet() {
8
8
  const wallet = solana.wallet;
9
9
  const connecting = ref(false);
10
10
  const disconnecting = ref(false);
11
+ function clearWalletError() {
12
+ const current = solana.error.value;
13
+ if (current?.code === "WALLET_FAILURE" || current?.code === "USER_REJECTED") {
14
+ solana.error.value = null;
15
+ }
16
+ }
11
17
  async function connect() {
12
18
  if (connecting.value || wallet.value?.connecting) {
13
19
  return;
@@ -22,9 +28,10 @@ function useWallet() {
22
28
  triggerRef(wallet);
23
29
  await connection;
24
30
  assertWalletConnected(activeWallet);
31
+ clearWalletError();
25
32
  triggerRef(wallet);
26
33
  } catch (error) {
27
- const normalizedError = normalizeSolanaError(error, "RPC_FAILURE");
34
+ const normalizedError = normalizeSolanaError(error, "WALLET_FAILURE");
28
35
  triggerRef(wallet);
29
36
  throw normalizedError;
30
37
  } finally {
@@ -39,9 +46,10 @@ function useWallet() {
39
46
  disconnecting.value = true;
40
47
  try {
41
48
  await activeWallet.disconnect();
49
+ clearWalletError();
42
50
  triggerRef(wallet);
43
51
  } catch (error) {
44
- const normalizedError = normalizeSolanaError(error, "RPC_FAILURE");
52
+ const normalizedError = normalizeSolanaError(error, "WALLET_FAILURE");
45
53
  triggerRef(wallet);
46
54
  throw normalizedError;
47
55
  } finally {
@@ -3,7 +3,7 @@ import { withSolanaTimeout } from '@vue-solana/core/timeout';
3
3
  import { createNoWalletSelectedError, assertWalletCanSignAndSendTransactions } from '@vue-solana/core/wallet';
4
4
  import { shallowRef } from 'vue';
5
5
  import { u as useExecution } from './vue.BiKE9Xuv.mjs';
6
- import { u as useWallet } from './vue.DY9VadLH.mjs';
6
+ import { u as useWallet } from './vue.Bc_Kbwr5.mjs';
7
7
 
8
8
  const SIGN_AND_SEND_TIMEOUT_MS = 12e4;
9
9
  class PartialSignAndSendError extends SolanaError {
@@ -1,7 +1,7 @@
1
1
  import { createNoWalletSelectedError, assertWalletCanSignMessage } from '@vue-solana/core/wallet';
2
2
  import { ref } from 'vue';
3
3
  import { u as useExecution } from './vue.BiKE9Xuv.mjs';
4
- import { u as useWallet } from './vue.DY9VadLH.mjs';
4
+ import { u as useWallet } from './vue.Bc_Kbwr5.mjs';
5
5
 
6
6
  function useSignMessage() {
7
7
  const { wallet } = useWallet();
@@ -1,7 +1,7 @@
1
1
  import { createNoWalletSelectedError, assertWalletCanSignIn } from '@vue-solana/core/wallet';
2
2
  import { shallowRef } from 'vue';
3
3
  import { u as useExecution } from './vue.BiKE9Xuv.mjs';
4
- import { u as useWallet } from './vue.DY9VadLH.mjs';
4
+ import { u as useWallet } from './vue.Bc_Kbwr5.mjs';
5
5
 
6
6
  function useSignIn() {
7
7
  const { wallet } = useWallet();
@@ -1 +1 @@
1
- export { u as useSignAndSendTransaction } from './shared/vue.4ne5EBD9.mjs';
1
+ export { u as useSignAndSendTransaction } from './shared/vue.BTD4usyi.mjs';
@@ -1 +1 @@
1
- export { P as PartialSignAndSendError, u as useSignAndSendTransactions } from './shared/vue.UrCjmf0T.mjs';
1
+ export { P as PartialSignAndSendError, u as useSignAndSendTransactions } from './shared/vue.C-7v1l8q.mjs';
@@ -1 +1 @@
1
- export { u as useSignIn } from './shared/vue.BBN9aU6D.mjs';
1
+ export { u as useSignIn } from './shared/vue.SeuJipQ8.mjs';
@@ -1 +1 @@
1
- export { u as useSignMessage } from './shared/vue.DbJG4-ku.mjs';
1
+ export { u as useSignMessage } from './shared/vue.Ddf_Cdjx.mjs';
@@ -1 +1 @@
1
- export { u as useSignTransactions } from './shared/vue.CW-1AuSO.mjs';
1
+ export { u as useSignTransactions } from './shared/vue.BHBifdbc.mjs';
@@ -1 +1 @@
1
- export { u as useWallet } from './shared/vue.DY9VadLH.mjs';
1
+ export { u as useWallet } from './shared/vue.Bc_Kbwr5.mjs';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vue-solana/vue",
3
- "version": "4.0.0-rc.0",
3
+ "version": "4.0.0-rc.1",
4
4
  "description": "Vue plugin and composables for Solana applications.",
5
5
  "license": "MIT",
6
6
  "keywords": [
@@ -178,7 +178,7 @@
178
178
  "access": "public"
179
179
  },
180
180
  "dependencies": {
181
- "@vue-solana/core": "4.0.0-rc.0"
181
+ "@vue-solana/core": "4.0.0-rc.1"
182
182
  },
183
183
  "peerDependencies": {
184
184
  "vue": "^3.5.0"