@vue-solana/vue 3.0.1 → 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.
Files changed (104) hide show
  1. package/README.md +10 -4
  2. package/dist/index.d.ts +12 -0
  3. package/dist/index.mjs +176 -86
  4. package/dist/shared/{vue.hG_FieA3.mjs → vue.BHBifdbc.mjs} +2 -2
  5. package/dist/shared/{vue.CjSw0Hh1.mjs → vue.BTD4usyi.mjs} +4 -4
  6. package/dist/shared/{vue.CsFY1mfz.mjs → vue.BXNjyJ2c.mjs} +1 -1
  7. package/dist/shared/{vue.D218Ow7t.mjs → vue.Bc_Kbwr5.mjs} +11 -10
  8. package/dist/shared/{vue.DlihC5GG.mjs → vue.BiKE9Xuv.mjs} +6 -4
  9. package/dist/shared/{vue.CXB4NEMA.mjs → vue.Bu2cQ690.mjs} +47 -17
  10. package/dist/shared/{vue.C1JbUTpJ.mjs → vue.C-7v1l8q.mjs} +2 -2
  11. package/dist/shared/{vue.CpkwC8Th.mjs → vue.C6OkOK2W.mjs} +1 -1
  12. package/dist/shared/{vue.B9uRpSDJ.mjs → vue.CAltY57B.mjs} +6 -3
  13. package/dist/shared/{vue.DDQxNX5q.mjs → vue.CELGI7de.mjs} +9 -9
  14. package/dist/shared/{vue.BxtOHzY_.mjs → vue.CLAqNcLp.mjs} +1 -1
  15. package/dist/shared/{vue.Mxc8w6Qk.mjs → vue.CZApWQAY.mjs} +1 -1
  16. package/dist/shared/vue.CbmzveKF.mjs +44 -0
  17. package/dist/shared/{vue.CQh3AUE8.mjs → vue.CcOZ9858.mjs} +3 -5
  18. package/dist/shared/{vue.B-xtuLtD.mjs → vue.D2J7_NpQ.mjs} +2 -2
  19. package/dist/shared/{vue.BsGwt78R.mjs → vue.D8zNWw2t.mjs} +1 -1
  20. package/dist/shared/{vue.CaAgH2Xt.mjs → vue.DPwdqRO_.mjs} +3 -3
  21. package/dist/shared/{vue.DNKD6jqM.mjs → vue.DTpvqDyY.mjs} +3 -3
  22. package/dist/shared/vue.DZVdVfy1.mjs +7 -0
  23. package/dist/shared/{vue.CvenSbZZ.mjs → vue.DZhPXLZz.mjs} +4 -4
  24. package/dist/shared/{vue.BFSboXMB.mjs → vue.DbJKnSrl.mjs} +3 -3
  25. package/dist/shared/{vue.1Qwco2kv.mjs → vue.DcZ_R_RH.mjs} +4 -4
  26. package/dist/shared/{vue.o98pAmDg.mjs → vue.Ddf_Cdjx.mjs} +2 -2
  27. package/dist/shared/{vue.C_4qJDiF.mjs → vue.DyLgoSbX.mjs} +1 -1
  28. package/dist/shared/{vue.XlfH_nkc.mjs → vue.FehFeE3w.mjs} +3 -3
  29. package/dist/shared/{vue.C9OOmvkD.mjs → vue.SeuJipQ8.mjs} +2 -2
  30. package/dist/swr.d.ts +7 -1
  31. package/dist/swr.mjs +16 -1
  32. package/dist/useAccountInfo.mjs +1 -1
  33. package/dist/useAirdrop.mjs +1 -1
  34. package/dist/useBalance.mjs +1 -1
  35. package/dist/useClientCapability.d.ts +6 -1
  36. package/dist/useClientCapability.mjs +1 -1
  37. package/dist/useConnection.d.ts +14 -7
  38. package/dist/useConnection.mjs +1 -1
  39. package/dist/useIdentity.mjs +1 -1
  40. package/dist/usePayer.mjs +1 -1
  41. package/dist/usePlanTransaction.mjs +1 -1
  42. package/dist/usePlanTransactions.mjs +1 -1
  43. package/dist/useProgramAccounts.mjs +1 -1
  44. package/dist/useRpc.d.ts +12 -6
  45. package/dist/useRpc.mjs +1 -1
  46. package/dist/useSelectedWalletAccount.d.ts +3 -0
  47. package/dist/useSendTransaction.mjs +1 -1
  48. package/dist/useSendTransactions.mjs +1 -1
  49. package/dist/useSignAndSendTransaction.mjs +1 -1
  50. package/dist/useSignAndSendTransactions.mjs +1 -1
  51. package/dist/useSignIn.mjs +1 -1
  52. package/dist/useSignMessage.mjs +1 -1
  53. package/dist/useSignTransactions.mjs +1 -1
  54. package/dist/useSignatureStatus.d.ts +0 -1
  55. package/dist/useSignatureStatus.mjs +1 -1
  56. package/dist/useSolana.mjs +1 -1
  57. package/dist/useSolanaClient.d.ts +12 -6
  58. package/dist/useSolanaClient.mjs +1 -1
  59. package/dist/useTokenAccounts.mjs +1 -1
  60. package/dist/useTokenBalance.mjs +1 -1
  61. package/dist/useTransactionConfirmation.mjs +1 -1
  62. package/dist/useWallet.mjs +1 -1
  63. package/dist/useWallets.mjs +1 -1
  64. package/package.json +8 -5
  65. package/dist/buffer-polyfill.d.mts +0 -2
  66. package/dist/index.d.mts +0 -64
  67. package/dist/kit.d.mts +0 -1
  68. package/dist/shared/vue.BGGixph-.d.mts +0 -5
  69. package/dist/shared/vue.Bfu1dCKe.mjs +0 -10
  70. package/dist/shared/vue.CGv0ip3w.mjs +0 -52
  71. package/dist/shared/vue.DL3z5PxP.d.mts +0 -21
  72. package/dist/swr.d.mts +0 -41
  73. package/dist/useAccountInfo.d.mts +0 -24
  74. package/dist/useAction.d.mts +0 -45
  75. package/dist/useAirdrop.d.mts +0 -27
  76. package/dist/useBalance.d.mts +0 -15
  77. package/dist/useClientCapability.d.mts +0 -39
  78. package/dist/useConnection.d.mts +0 -46
  79. package/dist/useIdentity.d.mts +0 -3
  80. package/dist/usePayer.d.mts +0 -18
  81. package/dist/usePlanTransaction.d.mts +0 -44
  82. package/dist/usePlanTransactions.d.mts +0 -5
  83. package/dist/useProgramAccounts.d.mts +0 -45
  84. package/dist/useRequest.d.mts +0 -73
  85. package/dist/useRpc.d.mts +0 -55
  86. package/dist/useSelectedWalletAccount.d.mts +0 -117
  87. package/dist/useSendTransaction.d.mts +0 -50
  88. package/dist/useSendTransactions.d.mts +0 -5
  89. package/dist/useSignAndSendTransaction.d.mts +0 -40
  90. package/dist/useSignAndSendTransactions.d.mts +0 -35
  91. package/dist/useSignIn.d.mts +0 -23
  92. package/dist/useSignMessage.d.mts +0 -236
  93. package/dist/useSignTransactions.d.mts +0 -24
  94. package/dist/useSignatureStatus.d.mts +0 -26
  95. package/dist/useSolana.d.mts +0 -9
  96. package/dist/useSolanaClient.d.mts +0 -45
  97. package/dist/useSubscription.d.mts +0 -70
  98. package/dist/useTokenAccounts.d.mts +0 -19
  99. package/dist/useTokenBalance.d.mts +0 -17
  100. package/dist/useTrackedData.d.mts +0 -98
  101. package/dist/useTransaction.d.mts +0 -27
  102. package/dist/useTransactionConfirmation.d.mts +0 -38
  103. package/dist/useWallet.d.mts +0 -31
  104. package/dist/useWallets.d.mts +0 -11
package/README.md CHANGED
@@ -98,7 +98,7 @@ createApp(App).use(
98
98
 
99
99
  Supported clusters are `mainnet` (legacy alias `mainnet-beta`), `devnet`, `testnet`, and `localnet`. Use `mainnet` for Solana mainnet; this is Solana's official mainnet cluster name.
100
100
 
101
- `payer` and `payerSecretKey` are supported by direct Vue/core clients. A client-sent transaction requires a payer. Never put a raw secret or `payerSecretKey` in Nuxt public runtime config, and never ship a funded signing key to an end-user browser. The default `createSolanaPlugin()` client composes the official `solanaRpc()`, `rpcTransactionPlanner()`, and `rpcTransactionPlanSendingExecutor()` stack; the old custom fallback sender is not used.
101
+ `payer` and `payerSecretKey` are supported by direct Vue/core clients. A client-sent transaction requires a payer. Never put a raw secret or `payerSecretKey` in Nuxt public runtime config, and never ship a funded signing key to an end-user browser. The default `createSolanaPlugin()` client composes `solanaRpc()` followed by `rpcAirdrop()`; `solanaRpc()` installs `rpcTransactionPlanner()`, `rpcTransactionPlanSigningExecutor()`, and `rpcTransactionPlanSendingExecutor()` itself, so the client exposes RPC reads and subscriptions plus `planTransaction(s)`, `signTransaction(s)`, and `sendTransaction(s)` with no extra plugin. The old custom fallback sender is not used.
102
102
 
103
103
  ### Plugin Options
104
104
 
@@ -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.
@@ -312,7 +314,7 @@ async function signIn() {
312
314
  </template>
313
315
  ```
314
316
 
315
- Message signing proves wallet ownership for flows like authentication challenges. It does not sign, submit, or authorize Solana transactions. For authentication, issue a server-generated nonce, include domain and expiration details in the message, and verify the returned signature server-side.
317
+ Message signing proves wallet ownership for flows like authentication challenges. It does not sign, submit, or authorize Solana transactions. For authentication, issue a server-generated nonce, include domain and expiration details in the message, and verify the returned signature server-side. The adapter also checks the signature against the account's public key before resolving, where Ed25519 (WebCrypto) is available — a best-effort client-side guard that rejects with `INVALID_SIGNATURE`, not a replacement for server-side verification.
316
318
 
317
319
  ## Transaction State
318
320
 
@@ -391,7 +393,7 @@ Configure a direct client with `payer` or `payerSecretKey`. `payerSecretKey` is
391
393
 
392
394
  Use the client flow for automated or server-backed signing (airdrop faucet, cron jobs, relayer fees paid by your keypair), and the wallet flow when the end user must own and approve each transaction.
393
395
 
394
- **Default client composition.** `createSolanaPlugin()` and `createSolanaClient()` install the official `solanaRpc()`, `rpcTransactionPlanner()`, and `rpcTransactionPlanSendingExecutor()` plugins by default. The old custom fallback sender is not used, so `useSendTransaction()` and `useSendTransactions()` do not need a second manual plugin installation. The official executor waits for `confirmed` commitment before `execute()` resolves and the composable reports `sent`. A custom client must still provide the planner and sending executor and a `payer`.
396
+ **Default client composition.** `createSolanaPlugin()` and `createSolanaClient()` compose `solanaRpc()` followed by `rpcAirdrop()` by default; `solanaRpc()` installs `rpcTransactionPlanner()`, `rpcTransactionPlanSigningExecutor()`, and `rpcTransactionPlanSendingExecutor()` itself. The old custom fallback sender is not used, so `useSendTransaction()` and `useSendTransactions()` do not need a second manual plugin installation. The official executor waits for `confirmed` commitment before `execute()` resolves and the composable reports `sent`. A custom client must still provide the planner and sending executor and a `payer`.
395
397
 
396
398
  ```ts
397
399
  import { useSendTransaction } from "@vue-solana/vue/useSendTransaction";
@@ -488,6 +490,8 @@ import { useRequestSwr } from "@vue-solana/vue/swr";
488
490
  const balance = useRequestSwr(["balance", address], () => client.rpc.getBalance(address).send());
489
491
  ```
490
492
 
493
+ The SWR cache is module-level and shared across the whole app, including across SSR requests. Namespace cache keys per request (for example by including a request or session id) or call `clearSwrCache()` at the start of each server request to avoid leaking one request's cached data into another. On the client the cache is intentionally shared for stale-while-revalidate seeding.
494
+
491
495
  ### Cancellation & Timeouts
492
496
 
493
497
  `useRequest()`, `useSubscription()`, and `useTrackedData()` accept a `getAbortSignal` option — a factory called once per attempt (request) or connection (subscription/tracked data). It is the natural place for a timeout:
@@ -592,7 +596,9 @@ Docs: [Vue Solana Agent Skill](https://vue-solana-docs.vercel.app/agent-skill)
592
596
  | ------------------------------------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------- |
593
597
  | `createSolanaPlugin(options?)` | Installs the Vue Solana context. |
594
598
  | `VueSolana` | Alias for `createSolanaPlugin`. |
595
- | `useSolana()` | Returns the full injected Solana context. |
599
+ | `useSolana()` | Returns the full injected Solana context; throws when no plugin context is available. |
600
+ | `tryUseSolana()` | Returns the injected Solana context or `null` when no plugin context is available (SSR-safe). |
601
+ | `MissingClientCapabilityError` | Thrown by `useClientCapability()` (and client-sent composables) when the configured client lacks a required capability. |
596
602
  | `useRpc()` | Returns cluster, endpoint, connection status, latest blockhash, the Kit `client`, and `checkConnection()`. |
597
603
  | `useSolanaClient()` | Returns the injected Kit client as `{ client, rpc }`. The recommended RPC path. |
598
604
  | `useConnection()` | Returns the Kit `client`. Deprecated in favor of `useSolanaClient()`. |
package/dist/index.d.ts CHANGED
@@ -56,6 +56,18 @@ interface VueSolanaPlugin {
56
56
  * context to consumers that run outside a component setup.
57
57
  */
58
58
  context: VueSolanaContext | null;
59
+ /**
60
+ * Stops the wallet discovery subscription and the deferred bootstrap started
61
+ * by `install`. Vue has no plugin unmount hook, so call this from an app
62
+ * teardown hook (`app.onUnmount` in Nuxt, after `app.unmount()` in Vue).
63
+ * Safe to call more than once and before `install`. Without it the Wallet
64
+ * Standard subscription outlives the app that created it. After `dispose()`,
65
+ * `refreshWallets()` is a no-op, so a late mobile-wallet import or manual
66
+ * call cannot resurrect the subscription. A connected wallet stays
67
+ * connected; `dispose()` does not disconnect it or abort an in-flight
68
+ * `connect()`.
69
+ */
70
+ dispose(): void;
59
71
  }
60
72
  declare function createSolanaPlugin(options?: VueSolanaPluginOptions): VueSolanaPlugin;
61
73
  declare const VueSolana: typeof createSolanaPlugin;
package/dist/index.mjs CHANGED
@@ -1,39 +1,39 @@
1
- export { u as useAccountInfo } from './shared/vue.CvenSbZZ.mjs';
1
+ export { u as useAccountInfo } from './shared/vue.DZhPXLZz.mjs';
2
2
  export { u as useAction } from './shared/vue.DYk0Xfr3.mjs';
3
- export { u as useAirdrop } from './shared/vue.DDQxNX5q.mjs';
4
- export { u as useBalance } from './shared/vue.BFSboXMB.mjs';
5
- export { M as MissingClientCapabilityError, u as useClientCapability } from './shared/vue.B9uRpSDJ.mjs';
6
- export { u as useConnection } from './shared/vue.CpkwC8Th.mjs';
7
- export { u as usePlanTransaction, a as usePlanTransactions } from './shared/vue.CsFY1mfz.mjs';
8
- export { u as useProgramAccounts } from './shared/vue.1Qwco2kv.mjs';
9
- export { u as useIdentity, a as usePayer } from './shared/vue.B-xtuLtD.mjs';
3
+ export { u as useAirdrop } from './shared/vue.CELGI7de.mjs';
4
+ export { u as useBalance } from './shared/vue.DbJKnSrl.mjs';
5
+ export { M as MissingClientCapabilityError, u as useClientCapability } from './shared/vue.CAltY57B.mjs';
6
+ export { u as useConnection } from './shared/vue.C6OkOK2W.mjs';
7
+ export { u as usePlanTransaction, a as usePlanTransactions } from './shared/vue.BXNjyJ2c.mjs';
8
+ export { u as useProgramAccounts } from './shared/vue.DcZ_R_RH.mjs';
9
+ export { u as useIdentity, a as usePayer } from './shared/vue.D2J7_NpQ.mjs';
10
10
  export { u as useRequest } from './shared/vue.B8VvC8d2.mjs';
11
- export { u as useRpc } from './shared/vue.BsGwt78R.mjs';
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
- export { u as useSendTransaction, a as useSendTransactions } from './shared/vue.BxtOHzY_.mjs';
14
- export { u as useSignMessage } from './shared/vue.o98pAmDg.mjs';
15
- export { u as useSignAndSendTransaction } from './shared/vue.CjSw0Hh1.mjs';
16
- export { u as useSignTransactions } from './shared/vue.hG_FieA3.mjs';
17
- export { P as PartialSignAndSendError, u as useSignAndSendTransactions } from './shared/vue.C1JbUTpJ.mjs';
18
- export { u as useSignatureStatus } from './shared/vue.CXB4NEMA.mjs';
19
- export { u as useSignIn } from './shared/vue.C9OOmvkD.mjs';
20
- export { t as tryUseSolana, u as useSolana } from './shared/vue.CQh3AUE8.mjs';
21
- export { u as useSolanaClient } from './shared/vue.Mxc8w6Qk.mjs';
13
+ export { u as useSendTransaction, a as useSendTransactions } from './shared/vue.CLAqNcLp.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
+ export { u as useSignatureStatus } from './shared/vue.Bu2cQ690.mjs';
19
+ export { u as useSignIn } from './shared/vue.SeuJipQ8.mjs';
20
+ export { t as tryUseSolana, u as useSolana } from './shared/vue.CcOZ9858.mjs';
21
+ export { u as useSolanaClient } from './shared/vue.CZApWQAY.mjs';
22
22
  export { u as useSubscription } from './shared/vue.D5pqR5Fa.mjs';
23
- export { u as useTokenAccounts } from './shared/vue.DNKD6jqM.mjs';
24
- export { u as useTokenBalance } from './shared/vue.XlfH_nkc.mjs';
23
+ export { u as useTokenAccounts } from './shared/vue.DTpvqDyY.mjs';
24
+ 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
- export { g as getConfirmedTransactionStatus, u as useTransactionConfirmation } from './shared/vue.CaAgH2Xt.mjs';
28
- export { u as useWallet } from './shared/vue.D218Ow7t.mjs';
29
- export { u as useWallets } from './shared/vue.C_4qJDiF.mjs';
27
+ export { g as getConfirmedTransactionStatus, u as useTransactionConfirmation } from './shared/vue.DPwdqRO_.mjs';
28
+ export { u as useWallet } from './shared/vue.Bc_Kbwr5.mjs';
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';
32
32
  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,50 +112,65 @@ 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();
118
+ const adaptedPrimitiveWallets = /* @__PURE__ */ new Map();
119
+ const trackedAdapters = /* @__PURE__ */ new Set();
120
+ function trackAdapter(adapter) {
121
+ trackedAdapters.add(adapter);
122
+ return adapter;
123
+ }
117
124
  function getAdaptedWallet(walletInfo) {
118
125
  if (isSolanaIosWalletInfo(walletInfo)) {
119
- return adaptSolanaIosWallet(walletInfo, {
120
- chain: getSolanaChain(options.cluster),
121
- cluster: options.cluster,
122
- onChange: options.onWalletChange,
123
- ...options.iosWallet || {}
124
- });
126
+ const cachedIosWallet = adaptedWallets.get(walletInfo.wallet);
127
+ if (cachedIosWallet) {
128
+ return cachedIosWallet;
129
+ }
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
+ );
139
+ adaptedWallets.set(walletInfo.wallet, iosAdapter);
140
+ return trackAdapter(iosAdapter);
125
141
  }
126
142
  if (!isObject(walletInfo.wallet)) {
127
- return adaptSolanaStandardWallet(walletInfo, {
128
- chain: getSolanaChain(options.cluster),
129
- onChange: options.onWalletChange
130
- });
143
+ const primitiveKey = getPrimitiveWalletKey(walletInfo);
144
+ const cachedPrimitiveWallet = adaptedPrimitiveWallets.get(primitiveKey);
145
+ if (cachedPrimitiveWallet) {
146
+ return cachedPrimitiveWallet;
147
+ }
148
+ const primitiveAdapter = makeConnectExclusive(
149
+ adaptSolanaStandardWallet(walletInfo, {
150
+ chain: getSolanaChain(options.cluster),
151
+ onChange: options.onWalletChange
152
+ }),
153
+ walletInfo
154
+ );
155
+ adaptedPrimitiveWallets.set(primitiveKey, primitiveAdapter);
156
+ return trackAdapter(primitiveAdapter);
131
157
  }
132
158
  const cachedWallet = adaptedWallets.get(walletInfo.wallet);
133
159
  if (cachedWallet) {
134
160
  return cachedWallet;
135
161
  }
136
- const adaptedWallet = adaptSolanaStandardWallet(walletInfo, {
137
- chain: getSolanaChain(options.cluster),
138
- onChange: options.onWalletChange
139
- });
140
- const cachedAdapter = new Proxy(adaptedWallet, {
141
- get(target, property) {
142
- if (property === "connect") {
143
- return connectAndDisconnectOthers;
144
- }
145
- const value = Reflect.get(target, property);
146
- return typeof value === "function" ? value.bind(target) : value;
147
- }
148
- });
149
- async function connectAndDisconnectOthers() {
150
- await adaptedWallet.connect();
151
- await Promise.all(
152
- Array.from(getCachedWallets()).map(
153
- (otherWallet) => otherWallet !== cachedAdapter && otherWallet.connected ? otherWallet.disconnect() : void 0
154
- )
155
- );
156
- }
162
+ const cachedAdapter = makeConnectExclusive(
163
+ adaptSolanaStandardWallet(walletInfo, {
164
+ chain: getSolanaChain(options.cluster),
165
+ onChange: options.onWalletChange
166
+ }),
167
+ walletInfo
168
+ );
157
169
  adaptedWallets.set(walletInfo.wallet, cachedAdapter);
158
- return cachedAdapter;
170
+ return trackAdapter(cachedAdapter);
171
+ }
172
+ function getCachedAdapter(walletInfo) {
173
+ return isObject(walletInfo.wallet) ? adaptedWallets.get(walletInfo.wallet) : adaptedPrimitiveWallets.get(getPrimitiveWalletKey(walletInfo));
159
174
  }
160
175
  function getDiscoveredWallets() {
161
176
  return [
@@ -171,31 +186,67 @@ function createSolanaWalletRegistry(options) {
171
186
  try {
172
187
  handleSolanaIosWalletCallback({ clearUrl: true });
173
188
  } catch (cause) {
174
- console.error("[Vue Solana] iOS wallet callback failed", cause);
189
+ options.onError?.(cause);
175
190
  }
176
191
  }
177
- function* getCachedWallets() {
178
- for (const walletInfo of options.getWalletInfos()) {
179
- if (isObject(walletInfo.wallet)) {
180
- const cachedWallet = adaptedWallets.get(walletInfo.wallet);
181
- if (cachedWallet) {
182
- 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;
183
206
  }
184
- }
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;
222
+ }
223
+ await disconnectSolanaStandardWallet(walletInfo);
224
+ }
225
+ function disposeAllAdaptedWallets() {
226
+ for (const adapter of trackedAdapters) {
227
+ adapter.dispose?.();
185
228
  }
186
229
  }
187
230
  return {
188
231
  getAdaptedWallet,
189
232
  getDiscoveredWallets,
190
- handleIosWalletCallback
233
+ handleIosWalletCallback,
234
+ disposeAllAdaptedWallets
191
235
  };
192
236
  }
193
237
  function isObject(value) {
194
238
  return typeof value === "object" && value !== null || typeof value === "function";
195
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
+ }
196
246
 
197
247
  const RPC_CHECK_TIMEOUT_MS = 1e4;
198
248
  function createSolanaPlugin(options = {}) {
249
+ let teardown = null;
199
250
  const plugin = {
200
251
  context: null,
201
252
  install(app) {
@@ -210,21 +261,22 @@ function createSolanaPlugin(options = {}) {
210
261
  cluster: context.cluster,
211
262
  iosWallet: options.iosWallet,
212
263
  getWalletInfos: () => wallets.value,
213
- 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
+ }
214
269
  });
215
270
  let unsubscribeWallets = null;
216
271
  let mobileWalletRegistrationPromise = null;
217
272
  let attemptedAutoConnectWallet = null;
218
273
  let rpcCheckId = 0;
274
+ let bootstrapId = 0;
275
+ let disposed = false;
219
276
  async function checkConnection() {
220
277
  const checkId = ++rpcCheckId;
221
278
  status.value = "checking";
222
279
  error.value = null;
223
- console.info("[Vue Solana] Checking RPC connection", {
224
- cluster: context.cluster,
225
- endpoint: context.endpoint,
226
- wsEndpoint: context.wsEndpoint
227
- });
228
280
  try {
229
281
  const response = await withTimeout(
230
282
  context.client.rpc.getLatestBlockhash().send(),
@@ -239,22 +291,19 @@ function createSolanaPlugin(options = {}) {
239
291
  }
240
292
  latestBlockhash.value = blockhash.blockhash;
241
293
  status.value = "connected";
242
- console.info("[Vue Solana] Connected", {
243
- cluster: context.cluster,
244
- endpoint: context.endpoint,
245
- wsEndpoint: context.wsEndpoint,
246
- blockhash
247
- });
248
294
  } catch (cause) {
249
295
  if (checkId !== rpcCheckId) {
250
296
  return;
251
297
  }
252
298
  status.value = "error";
253
299
  error.value = normalizeSolanaError(cause, "RPC_FAILURE");
254
- console.error("[Vue Solana] Connection failed", cause);
300
+ logDevError("Connection failed", cause);
255
301
  }
256
302
  }
257
303
  function refreshWallets() {
304
+ if (disposed) {
305
+ return;
306
+ }
258
307
  unsubscribeWallets ??= subscribeSolanaWallets(refreshWallets);
259
308
  walletRegistry.handleIosWalletCallback();
260
309
  wallets.value = walletRegistry.getDiscoveredWallets();
@@ -268,7 +317,7 @@ function createSolanaPlugin(options = {}) {
268
317
  const persistedWallet = readSelectedWallet();
269
318
  if (persistedWallet.error) {
270
319
  error.value = persistedWallet.error;
271
- console.error("[Vue Solana] Selected wallet restoration failed", persistedWallet.error);
320
+ logDevError("Failed to read the persisted Solana wallet", persistedWallet.error);
272
321
  }
273
322
  restoredWallet = persistedWallet.wallet ? wallets.value.find(
274
323
  (nextWallet) => isSameWallet(nextWallet, persistedWallet.wallet)
@@ -286,14 +335,21 @@ function createSolanaPlugin(options = {}) {
286
335
  }
287
336
  }
288
337
  function registerMobileWallets() {
338
+ if (disposed) {
339
+ return;
340
+ }
289
341
  mobileWalletRegistrationPromise ??= import('@vue-solana/core/mobile-wallet').then(({ registerSolanaMobileWallet }) => {
342
+ if (disposed) {
343
+ return;
344
+ }
290
345
  registerSolanaMobileWallet({
291
346
  chains: [getSolanaChain(context.cluster)],
292
347
  ...options.mobileWallet || {}
293
348
  });
294
349
  refreshWallets();
295
350
  }).catch((cause) => {
296
- console.error("[Vue Solana] Mobile wallet registration failed", cause);
351
+ error.value = normalizeSolanaError(cause, "WALLET_FAILURE");
352
+ logDevError("Failed to register Solana mobile wallet", cause);
297
353
  }).finally(() => {
298
354
  mobileWalletRegistrationPromise = null;
299
355
  });
@@ -304,7 +360,7 @@ function createSolanaPlugin(options = {}) {
304
360
  const storageError = writeSelectedWallet(nextWallet);
305
361
  if (storageError) {
306
362
  error.value = storageError;
307
- console.error("[Vue Solana] Selected wallet persistence failed", storageError);
363
+ logDevError("Failed to persist the selected Solana wallet", storageError);
308
364
  }
309
365
  }
310
366
  function autoConnectWallet(walletInfo) {
@@ -318,7 +374,8 @@ function createSolanaPlugin(options = {}) {
318
374
  }
319
375
  attemptedAutoConnectWallet = storageValue;
320
376
  void activeWallet.connect().catch((cause) => {
321
- console.error("[Vue Solana] Wallet auto-connect failed", cause);
377
+ error.value = normalizeSolanaError(cause, "WALLET_FAILURE");
378
+ logDevError("Failed to auto-connect Solana wallet", cause);
322
379
  }).finally(() => {
323
380
  triggerRef(wallet);
324
381
  });
@@ -340,22 +397,37 @@ function createSolanaPlugin(options = {}) {
340
397
  const storageError = writeSelectedWallet(null);
341
398
  if (storageError) {
342
399
  error.value = storageError;
343
- console.error("[Vue Solana] Selected wallet persistence failed", storageError);
400
+ logDevError("Failed to clear the persisted Solana wallet", storageError);
344
401
  }
345
402
  }
346
403
  };
347
404
  app.provide(solanaInjectionKey, vueContext);
348
405
  plugin.context = vueContext;
406
+ teardown = () => {
407
+ disposed = true;
408
+ if (typeof window !== "undefined") {
409
+ window.clearTimeout(bootstrapId);
410
+ }
411
+ unsubscribeWallets?.();
412
+ unsubscribeWallets = null;
413
+ walletRegistry.disposeAllAdaptedWallets();
414
+ rpcCheckId += 1;
415
+ teardown = null;
416
+ };
349
417
  if (typeof window !== "undefined") {
350
- window.setTimeout(() => {
418
+ bootstrapId = window.setTimeout(() => {
419
+ void checkConnection();
351
420
  try {
352
421
  refreshWallets();
353
422
  } catch (cause) {
354
- console.error("[Vue Solana] Wallet refresh failed", cause);
423
+ error.value = normalizeSolanaError(cause, "WALLET_FAILURE");
424
+ logDevError("Failed to refresh Solana wallets", cause);
355
425
  }
356
- void checkConnection();
357
426
  }, 0);
358
427
  }
428
+ },
429
+ dispose() {
430
+ teardown?.();
359
431
  }
360
432
  };
361
433
  return plugin;
@@ -363,6 +435,24 @@ function createSolanaPlugin(options = {}) {
363
435
  function isSameWallet(wallet, selectedWallet) {
364
436
  return wallet.name === selectedWallet?.name && wallet.source === selectedWallet.source && wallet.platform === selectedWallet.platform;
365
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
+ }
366
456
  const VueSolana = createSolanaPlugin;
367
457
 
368
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
- import { u as useExecution } from './vue.DlihC5GG.mjs';
4
- import { u as useWallet } from './vue.D218Ow7t.mjs';
3
+ import { u as useExecution } from './vue.BiKE9Xuv.mjs';
4
+ import { u as useWallet } from './vue.Bc_Kbwr5.mjs';
5
5
 
6
6
  function useSignTransactions() {
7
7
  const { wallet } = useWallet();
@@ -3,13 +3,13 @@ import { withSolanaTimeout } from '@vue-solana/core/timeout';
3
3
  import { signAndSendTransaction, confirmTransactionSignature } from '@vue-solana/core/transaction';
4
4
  import { createNoWalletSelectedError } from '@vue-solana/core/wallet';
5
5
  import { ref, computed } from 'vue';
6
- import { u as useConnection } from './vue.CpkwC8Th.mjs';
7
- import { u as useWallet } from './vue.D218Ow7t.mjs';
8
- import { g as getConfirmedTransactionStatus } from './vue.CaAgH2Xt.mjs';
6
+ import { u as useSolanaClient } from './vue.CZApWQAY.mjs';
7
+ import { u as useWallet } from './vue.Bc_Kbwr5.mjs';
8
+ import { g as getConfirmedTransactionStatus } from './vue.DPwdqRO_.mjs';
9
9
 
10
10
  const SIGN_AND_SEND_TIMEOUT_MS = 12e4;
11
11
  function useSignAndSendTransaction() {
12
- const client = useConnection();
12
+ const client = useSolanaClient().client;
13
13
  const { wallet } = useWallet();
14
14
  const signature = ref(null);
15
15
  const confirmation = ref(null);
@@ -1,4 +1,4 @@
1
- import { u as useClientAction } from './vue.CGv0ip3w.mjs';
1
+ import { u as useClientAction } from './vue.CbmzveKF.mjs';
2
2
 
3
3
  const PLANNING_PROVIDER_HINT = "Install a planner plugin, e.g. `createClient().use(rpcTransactionPlanner())` from `@solana/kit-plugin-rpc`, and plan with a client that has a `payer` signer \u2014 Kit reads `client.payer` to set the fee payer.";
4
4
  const PLAN_STATUSES = {
@@ -1,13 +1,19 @@
1
1
  import { normalizeSolanaError } from '@vue-solana/core/errors';
2
2
  import { createNoWalletSelectedError, assertWalletConnected } from '@vue-solana/core/wallet';
3
3
  import { ref, computed, triggerRef } from 'vue';
4
- import { u as useSolana } from './vue.CQh3AUE8.mjs';
4
+ import { u as useSolana } from './vue.CcOZ9858.mjs';
5
5
 
6
6
  function useWallet() {
7
7
  const solana = useSolana();
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,14 +28,11 @@ function useWallet() {
22
28
  triggerRef(wallet);
23
29
  await connection;
24
30
  assertWalletConnected(activeWallet);
31
+ clearWalletError();
25
32
  triggerRef(wallet);
26
- console.info("[Vue Solana] Wallet connected", {
27
- publicKey: activeWallet.publicKey ?? null
28
- });
29
33
  } catch (error) {
30
- const normalizedError = normalizeSolanaError(error, "RPC_FAILURE");
34
+ const normalizedError = normalizeSolanaError(error, "WALLET_FAILURE");
31
35
  triggerRef(wallet);
32
- console.error("[Vue Solana] Wallet connection failed", normalizedError);
33
36
  throw normalizedError;
34
37
  } finally {
35
38
  connecting.value = false;
@@ -40,16 +43,14 @@ function useWallet() {
40
43
  if (!activeWallet) {
41
44
  return;
42
45
  }
43
- const publicKey = activeWallet.publicKey ?? null;
44
46
  disconnecting.value = true;
45
47
  try {
46
48
  await activeWallet.disconnect();
49
+ clearWalletError();
47
50
  triggerRef(wallet);
48
- console.info("[Vue Solana] Wallet disconnected", { publicKey });
49
51
  } catch (error) {
50
- const normalizedError = normalizeSolanaError(error, "RPC_FAILURE");
52
+ const normalizedError = normalizeSolanaError(error, "WALLET_FAILURE");
51
53
  triggerRef(wallet);
52
- console.error("[Vue Solana] Wallet disconnection failed", normalizedError);
53
54
  throw normalizedError;
54
55
  } finally {
55
56
  disconnecting.value = false;
@@ -1,8 +1,10 @@
1
1
  import { normalizeSolanaError } from '@vue-solana/core/errors';
2
2
  import { shallowRef, onScopeDispose, computed } from 'vue';
3
3
 
4
- function useExecution(busy) {
5
- const status = shallowRef("idle");
4
+ function useExecution(busy, statuses = {}) {
5
+ const errorStatus = statuses.error ?? "error";
6
+ const idleStatus = statuses.idle ?? "idle";
7
+ const status = shallowRef(idleStatus);
6
8
  const error = shallowRef(null);
7
9
  let id = 0;
8
10
  async function execute(run, settled) {
@@ -19,14 +21,14 @@ function useExecution(busy) {
19
21
  const normalizedError = normalizeSolanaError(cause, "RPC_FAILURE");
20
22
  if (currentId === id) {
21
23
  error.value = normalizedError;
22
- status.value = "error";
24
+ status.value = errorStatus;
23
25
  }
24
26
  throw normalizedError;
25
27
  }
26
28
  }
27
29
  function reset() {
28
30
  id += 1;
29
- status.value = "idle";
31
+ status.value = idleStatus;
30
32
  error.value = null;
31
33
  }
32
34
  onScopeDispose(() => {