@vue-solana/vue 0.1.3 → 0.2.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
@@ -9,8 +9,9 @@ New to Solana? Start with the official docs and the project concepts guide:
9
9
  - [Solana Documentation](https://solana.com/docs)
10
10
  - [Solana RPC Methods](https://solana.com/docs/rpc)
11
11
  - [Solana Clusters](https://solana.com/docs/references/clusters)
12
- - [Vue Solana Concepts Guide](https://github.com/vue-solana/vue-solana/tree/main/apps/docs/content/concepts/solana-for-vue-developers.md)
13
- - [`@vue-solana/vue` docs](https://github.com/vue-solana/vue-solana/tree/main/apps/docs/content/packages/vue.md)
12
+ - [Vue Solana Concepts Guide](https://vue-solana-docs.vercel.app/concepts/solana-for-vue-developers)
13
+ - [`@vue-solana/vue` docs](https://vue-solana-docs.vercel.app/packages/vue)
14
+ - [Live demo](https://vue-solana-docs.vercel.app/demo)
14
15
 
15
16
  ## Install
16
17
 
@@ -104,23 +105,36 @@ const { balance, loading, error, refresh } = useBalance(address);
104
105
 
105
106
  ```vue
106
107
  <script setup lang="ts">
107
- import { useWallet } from "@vue-solana/vue";
108
+ import { useWallet, useWallets } from "@vue-solana/vue";
108
109
 
110
+ const { wallets, selectedWallet, selectWallet, refreshWallets } = useWallets();
109
111
  const { publicKey, connected, connecting, connect, disconnect } = useWallet();
110
112
  </script>
111
113
 
112
114
  <template>
113
115
  <section>
116
+ <button type="button" @click="refreshWallets">Refresh Wallets</button>
117
+ <button
118
+ v-for="wallet in wallets"
119
+ :key="wallet.name"
120
+ type="button"
121
+ @click="selectWallet(wallet)"
122
+ >
123
+ {{ wallet.name }}
124
+ </button>
125
+ <p>Selected: {{ selectedWallet?.name ?? "None" }}</p>
114
126
  <p>Connected: {{ connected }}</p>
115
127
  <p>Public key: {{ publicKey?.toBase58() }}</p>
116
128
  <p v-if="connecting">Connecting...</p>
117
- <button type="button" @click="connect">Connect</button>
118
- <button type="button" @click="disconnect">Disconnect</button>
129
+ <button type="button" :disabled="!selectedWallet || connected || connecting" @click="connect">
130
+ Connect
131
+ </button>
132
+ <button type="button" :disabled="!connected" @click="disconnect">Disconnect</button>
119
133
  </section>
120
134
  </template>
121
135
  ```
122
136
 
123
- Browser wallet discovery is not included yet. `connect()` works only after you configure a wallet object that implements `SolanaWallet`.
137
+ Browser wallets are discovered through the Solana Wallet Standard. `connect()` works after selecting a discovered wallet or configuring a custom `SolanaWallet`.
124
138
 
125
139
  ## Transaction State
126
140
 
@@ -144,7 +158,9 @@ This README includes small snippets for quick reference. For a complete runnable
144
158
  pnpm dev:vue
145
159
  ```
146
160
 
147
- Source: [`examples/vue-vite`](https://github.com/vue-solana/vue-solana/tree/main/examples/vue-vite)
161
+ Docs: <a href="https://vue-solana-docs.vercel.app/examples/vue-vite" target="_blank" rel="noopener noreferrer"><code>examples/vue-vite</code></a>
162
+
163
+ Live demo: [vue-solana-docs.vercel.app/demo](https://vue-solana-docs.vercel.app/demo)
148
164
 
149
165
  ## API
150
166
 
@@ -183,4 +199,4 @@ Make sure your `tsconfig.json` includes `types/**/*.d.ts` or another pattern tha
183
199
 
184
200
  ## Status
185
201
 
186
- This package is early-stage. RPC and balance reads are usable; first-class browser wallet adapter support is planned.
202
+ This package is early-stage. RPC, balance, wallet, and transaction composables are usable.
package/dist/index.cjs CHANGED
@@ -74,14 +74,63 @@ function useBalance(address, commitment) {
74
74
  function useWallet() {
75
75
  const solana = useSolana();
76
76
  const wallet = solana.wallet;
77
+ const connecting = vue.ref(false);
78
+ const disconnecting = vue.ref(false);
79
+ async function connect() {
80
+ const activeWallet = wallet.value;
81
+ if (!activeWallet) {
82
+ throw new Error("No Solana wallet is configured");
83
+ }
84
+ connecting.value = true;
85
+ try {
86
+ const connection = activeWallet.connect();
87
+ vue.triggerRef(wallet);
88
+ await connection;
89
+ vue.triggerRef(wallet);
90
+ console.info("[Vue Solana] Wallet connected", {
91
+ publicKey: activeWallet.publicKey?.toBase58() ?? null
92
+ });
93
+ } catch (error) {
94
+ vue.triggerRef(wallet);
95
+ console.error("[Vue Solana] Wallet connection failed", error);
96
+ throw error;
97
+ } finally {
98
+ connecting.value = false;
99
+ }
100
+ }
101
+ async function disconnect() {
102
+ const activeWallet = wallet.value;
103
+ if (!activeWallet) {
104
+ return;
105
+ }
106
+ const publicKey = activeWallet.publicKey?.toBase58() ?? null;
107
+ disconnecting.value = true;
108
+ try {
109
+ await activeWallet.disconnect();
110
+ vue.triggerRef(wallet);
111
+ console.info("[Vue Solana] Wallet disconnected", { publicKey });
112
+ } catch (error) {
113
+ vue.triggerRef(wallet);
114
+ console.error("[Vue Solana] Wallet disconnection failed", error);
115
+ throw error;
116
+ } finally {
117
+ disconnecting.value = false;
118
+ }
119
+ }
120
+ const isConnecting = vue.computed(() => Boolean(connecting.value || wallet.value?.connecting));
121
+ const isDisconnecting = vue.computed(
122
+ () => Boolean(disconnecting.value || wallet.value?.disconnecting)
123
+ );
77
124
  return {
78
125
  wallet,
79
126
  publicKey: vue.computed(() => wallet.value?.publicKey ?? null),
80
127
  connected: vue.computed(() => Boolean(wallet.value?.connected && wallet.value.publicKey)),
81
- connecting: vue.computed(() => Boolean(wallet.value?.connecting)),
128
+ connecting: isConnecting,
129
+ disconnecting: isDisconnecting,
130
+ loading: vue.computed(() => isConnecting.value || isDisconnecting.value),
82
131
  setWallet: solana.setWallet,
83
- connect: () => wallet.value?.connect() ?? Promise.reject(new Error("No Solana wallet is configured")),
84
- disconnect: () => wallet.value?.disconnect() ?? Promise.resolve()
132
+ connect,
133
+ disconnect
85
134
  };
86
135
  }
87
136
 
@@ -121,15 +170,31 @@ function useSignAndSendTransaction() {
121
170
  });
122
171
  }
123
172
 
173
+ function useWallets() {
174
+ const solana = useSolana();
175
+ return {
176
+ wallets: solana.wallets,
177
+ selectedWallet: solana.selectedWallet,
178
+ refreshWallets: solana.refreshWallets,
179
+ selectWallet: solana.selectWallet
180
+ };
181
+ }
182
+
183
+ const RPC_CHECK_TIMEOUT_MS = 1e4;
124
184
  function createSolanaPlugin(options = {}) {
125
185
  return {
126
186
  install(app) {
127
187
  const context = core.createSolanaContext(options);
128
188
  const wallet = vue.shallowRef(options.wallet ?? null);
189
+ const wallets = vue.shallowRef([]);
190
+ const selectedWallet = vue.shallowRef(null);
129
191
  const status = vue.ref("idle");
130
192
  const error = vue.ref(null);
131
193
  const latestBlockhash = vue.ref(null);
194
+ let unsubscribeWallets = null;
195
+ let rpcCheckId = 0;
132
196
  async function checkConnection() {
197
+ const checkId = ++rpcCheckId;
133
198
  status.value = "checking";
134
199
  error.value = null;
135
200
  console.info("[Vue Solana] Checking RPC connection", {
@@ -138,7 +203,14 @@ function createSolanaPlugin(options = {}) {
138
203
  wsEndpoint: context.wsEndpoint
139
204
  });
140
205
  try {
141
- const blockhash = await context.connection.getLatestBlockhash();
206
+ const blockhash = await withTimeout(
207
+ context.connection.getLatestBlockhash(),
208
+ RPC_CHECK_TIMEOUT_MS,
209
+ `RPC connection check timed out after ${RPC_CHECK_TIMEOUT_MS / 1e3} seconds.`
210
+ );
211
+ if (checkId !== rpcCheckId) {
212
+ return;
213
+ }
142
214
  latestBlockhash.value = blockhash.blockhash;
143
215
  status.value = "connected";
144
216
  console.info("[Vue Solana] Connected", {
@@ -148,19 +220,44 @@ function createSolanaPlugin(options = {}) {
148
220
  blockhash
149
221
  });
150
222
  } catch (cause) {
223
+ if (checkId !== rpcCheckId) {
224
+ return;
225
+ }
151
226
  status.value = "error";
152
227
  error.value = cause instanceof Error ? cause.message : String(cause);
153
228
  console.error("[Vue Solana] Connection failed", cause);
154
229
  }
155
230
  }
231
+ function refreshWallets() {
232
+ unsubscribeWallets ??= core.subscribeSolanaWallets(refreshWallets);
233
+ wallets.value = core.getRegisteredSolanaWallets();
234
+ if (selectedWallet.value) {
235
+ selectedWallet.value = wallets.value.find((nextWallet) => nextWallet.name === selectedWallet.value?.name) ?? null;
236
+ if (!selectedWallet.value) {
237
+ wallet.value = options.wallet ?? null;
238
+ }
239
+ }
240
+ }
241
+ function selectWallet(nextWallet) {
242
+ selectedWallet.value = nextWallet;
243
+ wallet.value = nextWallet ? core.adaptSolanaStandardWallet(nextWallet, {
244
+ chain: core.getSolanaChain(context.cluster),
245
+ onChange: () => vue.triggerRef(wallet)
246
+ }) : options.wallet ?? null;
247
+ }
156
248
  const vueContext = {
157
249
  ...context,
158
250
  wallet,
159
251
  status,
160
252
  error,
161
253
  latestBlockhash,
254
+ wallets,
255
+ selectedWallet,
162
256
  checkConnection,
257
+ refreshWallets,
258
+ selectWallet,
163
259
  setWallet(nextWallet) {
260
+ selectedWallet.value = null;
164
261
  wallet.value = nextWallet;
165
262
  }
166
263
  };
@@ -170,6 +267,19 @@ function createSolanaPlugin(options = {}) {
170
267
  };
171
268
  }
172
269
  const VueSolana = createSolanaPlugin;
270
+ function withTimeout(promise, timeoutMs, message) {
271
+ let timeoutId;
272
+ const timeout = new Promise((_, reject) => {
273
+ timeoutId = setTimeout(() => {
274
+ reject(new Error(message));
275
+ }, timeoutMs);
276
+ });
277
+ return Promise.race([promise, timeout]).finally(() => {
278
+ if (timeoutId) {
279
+ clearTimeout(timeoutId);
280
+ }
281
+ });
282
+ }
173
283
 
174
284
  exports.VueSolana = VueSolana;
175
285
  exports.createSolanaPlugin = createSolanaPlugin;
@@ -181,3 +291,4 @@ exports.useSignAndSendTransaction = useSignAndSendTransaction;
181
291
  exports.useSolana = useSolana;
182
292
  exports.useTransaction = useTransaction;
183
293
  exports.useWallet = useWallet;
294
+ exports.useWallets = useWallets;
package/dist/index.d.cts CHANGED
@@ -2,7 +2,7 @@ import * as vue from 'vue';
2
2
  import { MaybeRefOrGetter, Ref, InjectionKey, App } from 'vue';
3
3
  import { PublicKey, Commitment, TransactionSignature } from '@solana/web3-compat';
4
4
  import * as _vue_solana_core from '@vue-solana/core';
5
- import { SendTransactionOptions, SolanaContext, SolanaWallet, SolanaConfig } from '@vue-solana/core';
5
+ import { SendTransactionOptions, SolanaContext, SolanaWallet, SolanaWalletInfo, SolanaConfig } from '@vue-solana/core';
6
6
 
7
7
  declare function useBalance(address: MaybeRefOrGetter<PublicKey | string | null | undefined>, commitment?: Commitment): {
8
8
  balance: vue.Ref<number | null, number | null>;
@@ -45,19 +45,32 @@ declare function useWallet(): {
45
45
  publicKey: vue.ComputedRef<any>;
46
46
  connected: vue.ComputedRef<boolean>;
47
47
  connecting: vue.ComputedRef<boolean>;
48
+ disconnecting: vue.ComputedRef<boolean>;
49
+ loading: vue.ComputedRef<boolean>;
48
50
  setWallet: (wallet: _vue_solana_core.SolanaWallet | null) => void;
49
51
  connect: () => Promise<void>;
50
52
  disconnect: () => Promise<void>;
51
53
  };
52
54
 
55
+ declare function useWallets(): {
56
+ wallets: vue.Ref<_vue_solana_core.SolanaWalletInfo[], _vue_solana_core.SolanaWalletInfo[]>;
57
+ selectedWallet: vue.Ref<_vue_solana_core.SolanaWalletInfo | null, _vue_solana_core.SolanaWalletInfo | null>;
58
+ refreshWallets: () => void;
59
+ selectWallet: (wallet: _vue_solana_core.SolanaWalletInfo | null) => void;
60
+ };
61
+
53
62
  type SolanaConnectionStatus = "idle" | "checking" | "connected" | "error";
54
63
  interface VueSolanaContext extends SolanaContext {
55
64
  wallet: Ref<SolanaWallet | null>;
56
65
  status: Ref<SolanaConnectionStatus>;
57
66
  error: Ref<string | null>;
58
67
  latestBlockhash: Ref<string | null>;
68
+ wallets: Ref<SolanaWalletInfo[]>;
69
+ selectedWallet: Ref<SolanaWalletInfo | null>;
59
70
  checkConnection: () => Promise<void>;
60
71
  setWallet: (wallet: SolanaWallet | null) => void;
72
+ refreshWallets: () => void;
73
+ selectWallet: (wallet: SolanaWalletInfo | null) => void;
61
74
  }
62
75
  declare const solanaInjectionKey: InjectionKey<VueSolanaContext>;
63
76
 
@@ -69,5 +82,5 @@ declare function createSolanaPlugin(options?: VueSolanaPluginOptions): {
69
82
  };
70
83
  declare const VueSolana: typeof createSolanaPlugin;
71
84
 
72
- export { VueSolana, createSolanaPlugin, solanaInjectionKey, useBalance, useConnection, useRpc, useSignAndSendTransaction, useSolana, useTransaction, useWallet };
85
+ export { VueSolana, createSolanaPlugin, solanaInjectionKey, useBalance, useConnection, useRpc, useSignAndSendTransaction, useSolana, useTransaction, useWallet, useWallets };
73
86
  export type { SolanaConnectionStatus, VueSolanaContext, VueSolanaPluginOptions };
package/dist/index.d.mts CHANGED
@@ -2,7 +2,7 @@ import * as vue from 'vue';
2
2
  import { MaybeRefOrGetter, Ref, InjectionKey, App } from 'vue';
3
3
  import { PublicKey, Commitment, TransactionSignature } from '@solana/web3-compat';
4
4
  import * as _vue_solana_core from '@vue-solana/core';
5
- import { SendTransactionOptions, SolanaContext, SolanaWallet, SolanaConfig } from '@vue-solana/core';
5
+ import { SendTransactionOptions, SolanaContext, SolanaWallet, SolanaWalletInfo, SolanaConfig } from '@vue-solana/core';
6
6
 
7
7
  declare function useBalance(address: MaybeRefOrGetter<PublicKey | string | null | undefined>, commitment?: Commitment): {
8
8
  balance: vue.Ref<number | null, number | null>;
@@ -45,19 +45,32 @@ declare function useWallet(): {
45
45
  publicKey: vue.ComputedRef<any>;
46
46
  connected: vue.ComputedRef<boolean>;
47
47
  connecting: vue.ComputedRef<boolean>;
48
+ disconnecting: vue.ComputedRef<boolean>;
49
+ loading: vue.ComputedRef<boolean>;
48
50
  setWallet: (wallet: _vue_solana_core.SolanaWallet | null) => void;
49
51
  connect: () => Promise<void>;
50
52
  disconnect: () => Promise<void>;
51
53
  };
52
54
 
55
+ declare function useWallets(): {
56
+ wallets: vue.Ref<_vue_solana_core.SolanaWalletInfo[], _vue_solana_core.SolanaWalletInfo[]>;
57
+ selectedWallet: vue.Ref<_vue_solana_core.SolanaWalletInfo | null, _vue_solana_core.SolanaWalletInfo | null>;
58
+ refreshWallets: () => void;
59
+ selectWallet: (wallet: _vue_solana_core.SolanaWalletInfo | null) => void;
60
+ };
61
+
53
62
  type SolanaConnectionStatus = "idle" | "checking" | "connected" | "error";
54
63
  interface VueSolanaContext extends SolanaContext {
55
64
  wallet: Ref<SolanaWallet | null>;
56
65
  status: Ref<SolanaConnectionStatus>;
57
66
  error: Ref<string | null>;
58
67
  latestBlockhash: Ref<string | null>;
68
+ wallets: Ref<SolanaWalletInfo[]>;
69
+ selectedWallet: Ref<SolanaWalletInfo | null>;
59
70
  checkConnection: () => Promise<void>;
60
71
  setWallet: (wallet: SolanaWallet | null) => void;
72
+ refreshWallets: () => void;
73
+ selectWallet: (wallet: SolanaWalletInfo | null) => void;
61
74
  }
62
75
  declare const solanaInjectionKey: InjectionKey<VueSolanaContext>;
63
76
 
@@ -69,5 +82,5 @@ declare function createSolanaPlugin(options?: VueSolanaPluginOptions): {
69
82
  };
70
83
  declare const VueSolana: typeof createSolanaPlugin;
71
84
 
72
- export { VueSolana, createSolanaPlugin, solanaInjectionKey, useBalance, useConnection, useRpc, useSignAndSendTransaction, useSolana, useTransaction, useWallet };
85
+ export { VueSolana, createSolanaPlugin, solanaInjectionKey, useBalance, useConnection, useRpc, useSignAndSendTransaction, useSolana, useTransaction, useWallet, useWallets };
73
86
  export type { SolanaConnectionStatus, VueSolanaContext, VueSolanaPluginOptions };
package/dist/index.d.ts CHANGED
@@ -2,7 +2,7 @@ import * as vue from 'vue';
2
2
  import { MaybeRefOrGetter, Ref, InjectionKey, App } from 'vue';
3
3
  import { PublicKey, Commitment, TransactionSignature } from '@solana/web3-compat';
4
4
  import * as _vue_solana_core from '@vue-solana/core';
5
- import { SendTransactionOptions, SolanaContext, SolanaWallet, SolanaConfig } from '@vue-solana/core';
5
+ import { SendTransactionOptions, SolanaContext, SolanaWallet, SolanaWalletInfo, SolanaConfig } from '@vue-solana/core';
6
6
 
7
7
  declare function useBalance(address: MaybeRefOrGetter<PublicKey | string | null | undefined>, commitment?: Commitment): {
8
8
  balance: vue.Ref<number | null, number | null>;
@@ -45,19 +45,32 @@ declare function useWallet(): {
45
45
  publicKey: vue.ComputedRef<any>;
46
46
  connected: vue.ComputedRef<boolean>;
47
47
  connecting: vue.ComputedRef<boolean>;
48
+ disconnecting: vue.ComputedRef<boolean>;
49
+ loading: vue.ComputedRef<boolean>;
48
50
  setWallet: (wallet: _vue_solana_core.SolanaWallet | null) => void;
49
51
  connect: () => Promise<void>;
50
52
  disconnect: () => Promise<void>;
51
53
  };
52
54
 
55
+ declare function useWallets(): {
56
+ wallets: vue.Ref<_vue_solana_core.SolanaWalletInfo[], _vue_solana_core.SolanaWalletInfo[]>;
57
+ selectedWallet: vue.Ref<_vue_solana_core.SolanaWalletInfo | null, _vue_solana_core.SolanaWalletInfo | null>;
58
+ refreshWallets: () => void;
59
+ selectWallet: (wallet: _vue_solana_core.SolanaWalletInfo | null) => void;
60
+ };
61
+
53
62
  type SolanaConnectionStatus = "idle" | "checking" | "connected" | "error";
54
63
  interface VueSolanaContext extends SolanaContext {
55
64
  wallet: Ref<SolanaWallet | null>;
56
65
  status: Ref<SolanaConnectionStatus>;
57
66
  error: Ref<string | null>;
58
67
  latestBlockhash: Ref<string | null>;
68
+ wallets: Ref<SolanaWalletInfo[]>;
69
+ selectedWallet: Ref<SolanaWalletInfo | null>;
59
70
  checkConnection: () => Promise<void>;
60
71
  setWallet: (wallet: SolanaWallet | null) => void;
72
+ refreshWallets: () => void;
73
+ selectWallet: (wallet: SolanaWalletInfo | null) => void;
61
74
  }
62
75
  declare const solanaInjectionKey: InjectionKey<VueSolanaContext>;
63
76
 
@@ -69,5 +82,5 @@ declare function createSolanaPlugin(options?: VueSolanaPluginOptions): {
69
82
  };
70
83
  declare const VueSolana: typeof createSolanaPlugin;
71
84
 
72
- export { VueSolana, createSolanaPlugin, solanaInjectionKey, useBalance, useConnection, useRpc, useSignAndSendTransaction, useSolana, useTransaction, useWallet };
85
+ export { VueSolana, createSolanaPlugin, solanaInjectionKey, useBalance, useConnection, useRpc, useSignAndSendTransaction, useSolana, useTransaction, useWallet, useWallets };
73
86
  export type { SolanaConnectionStatus, VueSolanaContext, VueSolanaPluginOptions };
package/dist/index.mjs CHANGED
@@ -1,6 +1,6 @@
1
1
  import { PublicKey } from '@solana/web3-compat';
2
- import { inject, computed, ref, watch, toValue, shallowRef } from 'vue';
3
- import { signAndSendTransaction, createSolanaContext } from '@vue-solana/core';
2
+ import { inject, computed, ref, watch, toValue, triggerRef, shallowRef } from 'vue';
3
+ import { signAndSendTransaction, createSolanaContext, adaptSolanaStandardWallet, getSolanaChain, subscribeSolanaWallets, getRegisteredSolanaWallets } from '@vue-solana/core';
4
4
 
5
5
  const solanaInjectionKey = Symbol("VueSolana");
6
6
 
@@ -72,14 +72,63 @@ function useBalance(address, commitment) {
72
72
  function useWallet() {
73
73
  const solana = useSolana();
74
74
  const wallet = solana.wallet;
75
+ const connecting = ref(false);
76
+ const disconnecting = ref(false);
77
+ async function connect() {
78
+ const activeWallet = wallet.value;
79
+ if (!activeWallet) {
80
+ throw new Error("No Solana wallet is configured");
81
+ }
82
+ connecting.value = true;
83
+ try {
84
+ const connection = activeWallet.connect();
85
+ triggerRef(wallet);
86
+ await connection;
87
+ triggerRef(wallet);
88
+ console.info("[Vue Solana] Wallet connected", {
89
+ publicKey: activeWallet.publicKey?.toBase58() ?? null
90
+ });
91
+ } catch (error) {
92
+ triggerRef(wallet);
93
+ console.error("[Vue Solana] Wallet connection failed", error);
94
+ throw error;
95
+ } finally {
96
+ connecting.value = false;
97
+ }
98
+ }
99
+ async function disconnect() {
100
+ const activeWallet = wallet.value;
101
+ if (!activeWallet) {
102
+ return;
103
+ }
104
+ const publicKey = activeWallet.publicKey?.toBase58() ?? null;
105
+ disconnecting.value = true;
106
+ try {
107
+ await activeWallet.disconnect();
108
+ triggerRef(wallet);
109
+ console.info("[Vue Solana] Wallet disconnected", { publicKey });
110
+ } catch (error) {
111
+ triggerRef(wallet);
112
+ console.error("[Vue Solana] Wallet disconnection failed", error);
113
+ throw error;
114
+ } finally {
115
+ disconnecting.value = false;
116
+ }
117
+ }
118
+ const isConnecting = computed(() => Boolean(connecting.value || wallet.value?.connecting));
119
+ const isDisconnecting = computed(
120
+ () => Boolean(disconnecting.value || wallet.value?.disconnecting)
121
+ );
75
122
  return {
76
123
  wallet,
77
124
  publicKey: computed(() => wallet.value?.publicKey ?? null),
78
125
  connected: computed(() => Boolean(wallet.value?.connected && wallet.value.publicKey)),
79
- connecting: computed(() => Boolean(wallet.value?.connecting)),
126
+ connecting: isConnecting,
127
+ disconnecting: isDisconnecting,
128
+ loading: computed(() => isConnecting.value || isDisconnecting.value),
80
129
  setWallet: solana.setWallet,
81
- connect: () => wallet.value?.connect() ?? Promise.reject(new Error("No Solana wallet is configured")),
82
- disconnect: () => wallet.value?.disconnect() ?? Promise.resolve()
130
+ connect,
131
+ disconnect
83
132
  };
84
133
  }
85
134
 
@@ -119,15 +168,31 @@ function useSignAndSendTransaction() {
119
168
  });
120
169
  }
121
170
 
171
+ function useWallets() {
172
+ const solana = useSolana();
173
+ return {
174
+ wallets: solana.wallets,
175
+ selectedWallet: solana.selectedWallet,
176
+ refreshWallets: solana.refreshWallets,
177
+ selectWallet: solana.selectWallet
178
+ };
179
+ }
180
+
181
+ const RPC_CHECK_TIMEOUT_MS = 1e4;
122
182
  function createSolanaPlugin(options = {}) {
123
183
  return {
124
184
  install(app) {
125
185
  const context = createSolanaContext(options);
126
186
  const wallet = shallowRef(options.wallet ?? null);
187
+ const wallets = shallowRef([]);
188
+ const selectedWallet = shallowRef(null);
127
189
  const status = ref("idle");
128
190
  const error = ref(null);
129
191
  const latestBlockhash = ref(null);
192
+ let unsubscribeWallets = null;
193
+ let rpcCheckId = 0;
130
194
  async function checkConnection() {
195
+ const checkId = ++rpcCheckId;
131
196
  status.value = "checking";
132
197
  error.value = null;
133
198
  console.info("[Vue Solana] Checking RPC connection", {
@@ -136,7 +201,14 @@ function createSolanaPlugin(options = {}) {
136
201
  wsEndpoint: context.wsEndpoint
137
202
  });
138
203
  try {
139
- const blockhash = await context.connection.getLatestBlockhash();
204
+ const blockhash = await withTimeout(
205
+ context.connection.getLatestBlockhash(),
206
+ RPC_CHECK_TIMEOUT_MS,
207
+ `RPC connection check timed out after ${RPC_CHECK_TIMEOUT_MS / 1e3} seconds.`
208
+ );
209
+ if (checkId !== rpcCheckId) {
210
+ return;
211
+ }
140
212
  latestBlockhash.value = blockhash.blockhash;
141
213
  status.value = "connected";
142
214
  console.info("[Vue Solana] Connected", {
@@ -146,19 +218,44 @@ function createSolanaPlugin(options = {}) {
146
218
  blockhash
147
219
  });
148
220
  } catch (cause) {
221
+ if (checkId !== rpcCheckId) {
222
+ return;
223
+ }
149
224
  status.value = "error";
150
225
  error.value = cause instanceof Error ? cause.message : String(cause);
151
226
  console.error("[Vue Solana] Connection failed", cause);
152
227
  }
153
228
  }
229
+ function refreshWallets() {
230
+ unsubscribeWallets ??= subscribeSolanaWallets(refreshWallets);
231
+ wallets.value = getRegisteredSolanaWallets();
232
+ if (selectedWallet.value) {
233
+ selectedWallet.value = wallets.value.find((nextWallet) => nextWallet.name === selectedWallet.value?.name) ?? null;
234
+ if (!selectedWallet.value) {
235
+ wallet.value = options.wallet ?? null;
236
+ }
237
+ }
238
+ }
239
+ function selectWallet(nextWallet) {
240
+ selectedWallet.value = nextWallet;
241
+ wallet.value = nextWallet ? adaptSolanaStandardWallet(nextWallet, {
242
+ chain: getSolanaChain(context.cluster),
243
+ onChange: () => triggerRef(wallet)
244
+ }) : options.wallet ?? null;
245
+ }
154
246
  const vueContext = {
155
247
  ...context,
156
248
  wallet,
157
249
  status,
158
250
  error,
159
251
  latestBlockhash,
252
+ wallets,
253
+ selectedWallet,
160
254
  checkConnection,
255
+ refreshWallets,
256
+ selectWallet,
161
257
  setWallet(nextWallet) {
258
+ selectedWallet.value = null;
162
259
  wallet.value = nextWallet;
163
260
  }
164
261
  };
@@ -168,5 +265,18 @@ function createSolanaPlugin(options = {}) {
168
265
  };
169
266
  }
170
267
  const VueSolana = createSolanaPlugin;
268
+ function withTimeout(promise, timeoutMs, message) {
269
+ let timeoutId;
270
+ const timeout = new Promise((_, reject) => {
271
+ timeoutId = setTimeout(() => {
272
+ reject(new Error(message));
273
+ }, timeoutMs);
274
+ });
275
+ return Promise.race([promise, timeout]).finally(() => {
276
+ if (timeoutId) {
277
+ clearTimeout(timeoutId);
278
+ }
279
+ });
280
+ }
171
281
 
172
- export { VueSolana, createSolanaPlugin, solanaInjectionKey, useBalance, useConnection, useRpc, useSignAndSendTransaction, useSolana, useTransaction, useWallet };
282
+ export { VueSolana, createSolanaPlugin, solanaInjectionKey, useBalance, useConnection, useRpc, useSignAndSendTransaction, useSolana, useTransaction, useWallet, useWallets };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vue-solana/vue",
3
- "version": "0.1.3",
3
+ "version": "0.2.1",
4
4
  "description": "Vue plugin and composables for Solana applications.",
5
5
  "license": "MIT",
6
6
  "keywords": [
@@ -38,7 +38,7 @@
38
38
  "access": "public"
39
39
  },
40
40
  "dependencies": {
41
- "@vue-solana/core": "0.1.2"
41
+ "@vue-solana/core": "0.2.1"
42
42
  },
43
43
  "peerDependencies": {
44
44
  "@solana/web3-compat": "^0.0.21",