@vue-solana/core 4.0.0-rc.0 → 4.0.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/README.md CHANGED
@@ -7,6 +7,16 @@
7
7
 
8
8
  Framework-agnostic Solana primitives for Vue Solana libraries and apps that want shared RPC, wallet, and transaction helpers without installing a Vue plugin.
9
9
 
10
+ ## Related packages
11
+
12
+ This package is part of the Vue Solana family. Choose based on your app framework:
13
+
14
+ - [`@vue-solana/core`](https://www.npmjs.com/package/@vue-solana/core) — framework-agnostic Solana primitives: Kit RPC client helpers, wallet types, Android MWA registration, iOS browser wallet helpers, message signing, token account reads, and transaction helpers. **Use this directly** when you want shared RPC/wallet/transaction helpers without installing a Vue plugin (non-Vue apps, server/relayer code, or your own framework integration).
15
+ - [`@vue-solana/vue`](https://www.npmjs.com/package/@vue-solana/vue) — Vue 3 plugin plus composables for RPC, account, balance, wallet, message signing, live data, and transactions. **Use this** in plain Vue 3 apps (Vite, etc.).
16
+ - [`@vue-solana/nuxt`](https://www.npmjs.com/package/@vue-solana/nuxt) — Nuxt module that installs the Vue plugin and auto-imports the composables. **Use this** in Nuxt 3/4 apps.
17
+
18
+ This package (`@vue-solana/core`) is the foundation the other two build on; most apps should install `@vue-solana/vue` or `@vue-solana/nuxt` rather than this package directly.
19
+
10
20
  Use this package directly when you want Kit RPC client helpers, shared wallet types, Android Mobile Wallet Adapter registration helpers, message signing support, token account reads, and transaction helpers without installing the Vue plugin.
11
21
 
12
22
  `@vue-solana/core` builds on `@solana/kit`. It exposes `createSolanaClient()` and re-exports all of `@solana/kit` from `@vue-solana/core/kit`. The legacy `@solana/web3-compat` surface and the `web3` subpaths were removed in v2.0.0; transactions flow through the packages as raw `Uint8Array` wire bytes.
@@ -185,6 +195,7 @@ The client-sent API is the official Kit `ClientWithTransactionSending` capabilit
185
195
  | `getSolanaIosWallets(options?)` | Builds iOS browser wallet entries (Phantom, Solflare, Backpack) as universal links. |
186
196
  | `handleSolanaIosWalletCallback()` | Completes a pending iOS wallet deep-link callback after redirect. |
187
197
  | `adaptSolanaStandardWallet(wallet)` | Adapts a Solana Wallet Standard wallet into `SolanaWallet`. |
198
+ | `disconnectSolanaStandardWallet(walletInfo)` | Disconnects a discovered Wallet Standard wallet through its own `standard:disconnect` feature, without adapting it. Resolves `false` if the wallet has no disconnect feature, and ignores failures after a 2 s timeout. |
188
199
  | `getRegisteredSolanaWallets()` | Returns the Wallet Standard wallets currently registered in the app. |
189
200
  | `withTimeout(promise, timeoutMs, options?)` | Races a promise against a timeout, with optional abort signal. |
190
201
  | `withSolanaTimeout(promise, timeoutMs, options?)` | Solana-specific timeout wrapper with abort and error-code support. |
@@ -210,7 +221,7 @@ const wallet: SolanaWallet = {
210
221
 
211
222
  Browser extension wallets discovered through the Solana Wallet Standard are adapted into `SolanaWallet`. Android Mobile Wallet Adapter is registered through `@solana-mobile/wallet-standard-mobile` and then adapted through the same Wallet Standard adapter on supported Android Chrome clients. iOS browser wallet entries for Phantom, Solflare, and Backpack are adapted through wallet-specific universal links. Wallet capability metadata exposes whether each wallet supports message signing, transaction signing, and sign-and-send flows. You can also provide a custom object that implements `SolanaWallet` for tests or custom adapters.
212
223
 
213
- The Wallet Standard adapter verifies the signature a wallet returns from `signMessage` / `signIn` against the account's public key before resolving, where Ed25519 (WebCrypto) is available. A signature over the wrong bytes or from a different account rejects with `INVALID_SIGNATURE`. This is a best-effort, client-side guard — it is skipped where Ed25519 is unavailable — so server-side verification remains mandatory for auth flows.
224
+ The Wallet Standard adapter verifies the signature a wallet returns from `signMessage` / `signIn` against the account's public key before resolving, where Ed25519 (WebCrypto) is available. A signature over the wrong bytes or from a different account rejects with `INVALID_SIGNATURE`. `signIn` also binds the result to the requested account (or active account) and rejects if the account is absent from `wallet.accounts`. This is a best-effort, client-side guard — a malformed signature is rejected even if Ed25519 verification is unavailable, but full verification is skipped where Ed25519 is unavailable — so server-side verification remains mandatory for auth flows. Connecting a wallet disconnects every other discovered wallet (swept over discovery, uncached wallets disconnected via `standard:disconnect`), so only one wallet stays authorized.
214
225
 
215
226
  Current wallet support:
216
227
 
package/dist/errors.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- type SolanaErrorCode = "NO_WALLET_SELECTED" | "WALLET_NOT_CONNECTED" | "WALLET_FEATURE_UNSUPPORTED" | "USER_REJECTED" | "INVALID_ADDRESS" | "TRANSACTION_TIMEOUT" | "TRANSACTION_NOT_FOUND" | "TRANSACTION_FAILED" | "RPC_FAILURE" | "STORAGE_FAILURE" | "INVALID_INPUT" | "DECRYPTION_FAILED" | "REDIRECT_TIMEOUT" | "MISSING_CLIENT_CAPABILITY" | "INVALID_SIGNATURE";
1
+ type SolanaErrorCode = "NO_WALLET_SELECTED" | "WALLET_NOT_CONNECTED" | "WALLET_FEATURE_UNSUPPORTED" | "WALLET_FAILURE" | "USER_REJECTED" | "INVALID_ADDRESS" | "TRANSACTION_TIMEOUT" | "TRANSACTION_NOT_FOUND" | "TRANSACTION_FAILED" | "RPC_FAILURE" | "STORAGE_FAILURE" | "INVALID_INPUT" | "DECRYPTION_FAILED" | "REDIRECT_TIMEOUT" | "MISSING_CLIENT_CAPABILITY" | "INVALID_SIGNATURE";
2
2
  interface SolanaErrorOptions {
3
3
  cause?: unknown;
4
4
  feature?: string;
package/dist/index.d.ts CHANGED
@@ -9,6 +9,6 @@ export { confirmTransactionSignature, signAndSendTransaction } from './transacti
9
9
  export { WithTimeoutOptions, withSolanaTimeout, withTimeout } from './timeout.js';
10
10
  export { SolanaWalletError, SolanaWalletErrorCode, assertWalletCanSign, assertWalletCanSignAndSendTransactions, assertWalletCanSignIn, assertWalletCanSignMessage, assertWalletCanSignTransactions, assertWalletConnected, createNoWalletSelectedError, isWalletConnected } from './wallet.js';
11
11
  export * from '@solana/kit';
12
- export { AdaptSolanaWalletOptions, AdaptedSolanaWallet, SOLANA_CHAINS, SOLANA_MOBILE_WALLET_ADAPTER_WALLET_NAME, adaptSolanaStandardWallet, getRegisteredSolanaWallets, getSolanaChain, isSolanaStandardWallet, subscribeSolanaWallets } from './wallet-standard.js';
12
+ export { AdaptSolanaWalletOptions, AdaptedSolanaWallet, SOLANA_CHAINS, SOLANA_MOBILE_WALLET_ADAPTER_WALLET_NAME, adaptSolanaStandardWallet, disconnectSolanaStandardWallet, getRegisteredSolanaWallets, getSolanaChain, isSolanaStandardWallet, subscribeSolanaWallets } from './wallet-standard.js';
13
13
  import '@solana/kit-plugin-rpc';
14
14
  import '@wallet-standard/base';
package/dist/index.mjs CHANGED
@@ -9,4 +9,4 @@ export { confirmTransactionSignature, signAndSendTransaction } from './transacti
9
9
  export { withSolanaTimeout, withTimeout } from './timeout.mjs';
10
10
  export { SolanaWalletError, assertWalletCanSign, assertWalletCanSignAndSendTransactions, assertWalletCanSignIn, assertWalletCanSignMessage, assertWalletCanSignTransactions, assertWalletConnected, createNoWalletSelectedError, isWalletConnected } from './wallet.mjs';
11
11
  export * from '@solana/kit';
12
- export { S as SOLANA_CHAINS, a as SOLANA_MOBILE_WALLET_ADAPTER_WALLET_NAME, b as adaptSolanaStandardWallet, g as getRegisteredSolanaWallets, c as getSolanaChain, i as isSolanaStandardWallet, s as subscribeSolanaWallets } from './shared/core.BlqTJRXd.mjs';
12
+ export { S as SOLANA_CHAINS, a as SOLANA_MOBILE_WALLET_ADAPTER_WALLET_NAME, b as adaptSolanaStandardWallet, d as disconnectSolanaStandardWallet, g as getRegisteredSolanaWallets, c as getSolanaChain, i as isSolanaStandardWallet, s as subscribeSolanaWallets } from './shared/core.A4I__4IR.mjs';
@@ -1,4 +1,4 @@
1
- import { createSolanaError } from './errors.mjs';
1
+ import { createSolanaError, normalizeSolanaError } from './errors.mjs';
2
2
  import { getBase58Encoder, getBase58Decoder, isAddress } from '@solana/kit';
3
3
  import * as naclModule from 'tweetnacl';
4
4
 
@@ -359,17 +359,23 @@ function handleSolanaIosWalletCallback(options = {}) {
359
359
  if (errorCode || errorMessage) {
360
360
  clearPendingRequest();
361
361
  cleanCallbackUrl(url, options.clearUrl);
362
- throw new Error(errorMessage || `iOS wallet request failed: ${errorCode}`);
362
+ throw normalizeSolanaError(
363
+ new Error(errorMessage || `iOS wallet request failed: ${errorCode}`),
364
+ "WALLET_FAILURE"
365
+ );
363
366
  }
364
367
  const pending = getPendingRequest();
365
368
  if (!pending) {
366
369
  cleanCallbackUrl(url, options.clearUrl);
367
- throw new Error("Received an iOS wallet callback without a pending request");
370
+ throw createSolanaError(
371
+ "INVALID_INPUT",
372
+ "Received an iOS wallet callback without a pending request"
373
+ );
368
374
  }
369
375
  if (isPendingRequestExpired(pending)) {
370
376
  clearPendingRequest();
371
377
  cleanCallbackUrl(url, options.clearUrl);
372
- throw new Error("Received an expired iOS wallet callback");
378
+ throw createSolanaError("REDIRECT_TIMEOUT", "Received an expired iOS wallet callback");
373
379
  }
374
380
  const definition = getIosWalletDefinitionById(pending.walletId);
375
381
  const walletEncryptionPublicKey = url.searchParams.get(definition.encryptionPublicKeyParam);
@@ -378,7 +384,7 @@ function handleSolanaIosWalletCallback(options = {}) {
378
384
  if (!nonce || !data) {
379
385
  clearPendingRequest();
380
386
  cleanCallbackUrl(url, options.clearUrl);
381
- throw new Error("Received an incomplete iOS wallet callback");
387
+ throw createSolanaError("INVALID_INPUT", "Received an incomplete iOS wallet callback");
382
388
  }
383
389
  try {
384
390
  const sharedSecret = getSharedSecret(
@@ -389,7 +395,10 @@ function handleSolanaIosWalletCallback(options = {}) {
389
395
  const result = createCallbackResult(pending, payload);
390
396
  if (pending.method === "connect") {
391
397
  if (!walletEncryptionPublicKey) {
392
- throw new Error("iOS wallet connect callback did not include an encryption public key");
398
+ throw createSolanaError(
399
+ "DECRYPTION_FAILED",
400
+ "iOS wallet connect callback did not include an encryption public key"
401
+ );
393
402
  }
394
403
  const publicKey = getPublicKeyField(payload, "public_key");
395
404
  storeSession({
@@ -428,10 +437,14 @@ function createCallbackResult(pending, payload) {
428
437
  if (pending.method === "signAllTransactions") {
429
438
  const transactions = payload.transactions;
430
439
  if (!Array.isArray(transactions) || transactions.some((transaction) => typeof transaction !== "string")) {
431
- throw new Error("iOS wallet returned invalid signed transactions");
440
+ throw createSolanaError(
441
+ "DECRYPTION_FAILED",
442
+ "iOS wallet returned invalid signed transactions"
443
+ );
432
444
  }
433
445
  if (typeof pending.requestedTransactionCount === "number" && transactions.length !== pending.requestedTransactionCount) {
434
- throw new Error(
446
+ throw createSolanaError(
447
+ "WALLET_FAILURE",
435
448
  `iOS wallet returned ${transactions.length} signed transactions for ${pending.requestedTransactionCount} requested transactions`
436
449
  );
437
450
  }
@@ -452,14 +465,17 @@ function createCallbackResult(pending, payload) {
452
465
  function getStringField(payload, field) {
453
466
  const value = payload[field];
454
467
  if (typeof value !== "string" || !value) {
455
- throw new Error(`iOS wallet callback is missing ${field}`);
468
+ throw createSolanaError("DECRYPTION_FAILED", `iOS wallet callback is missing ${field}`);
456
469
  }
457
470
  return value;
458
471
  }
459
472
  function getPublicKeyField(payload, field) {
460
473
  const value = getStringField(payload, field);
461
474
  if (!isAddress(value)) {
462
- throw new Error(`iOS wallet callback returned an invalid ${field}`);
475
+ throw createSolanaError(
476
+ "DECRYPTION_FAILED",
477
+ `iOS wallet callback returned an invalid ${field}`
478
+ );
463
479
  }
464
480
  return value;
465
481
  }
@@ -5,6 +5,12 @@ import { createSolanaError } from '../errors.mjs';
5
5
  import { getWallets } from '@wallet-standard/app';
6
6
 
7
7
  async function assertWalletSignature(publicKey, signedMessage, signature, label) {
8
+ if (!isSignatureBytes(signature)) {
9
+ throw createSolanaError(
10
+ "INVALID_SIGNATURE",
11
+ `The wallet returned a malformed ${label} signature.`
12
+ );
13
+ }
8
14
  let key;
9
15
  try {
10
16
  key = await globalThis.crypto.subtle.importKey(
@@ -17,12 +23,6 @@ async function assertWalletSignature(publicKey, signedMessage, signature, label)
17
23
  } catch {
18
24
  return;
19
25
  }
20
- if (!isSignatureBytes(signature)) {
21
- throw createSolanaError(
22
- "INVALID_SIGNATURE",
23
- `The wallet returned a malformed ${label} signature.`
24
- );
25
- }
26
26
  let valid;
27
27
  try {
28
28
  valid = await verifySignature(key, signature, signedMessage);
@@ -149,8 +149,24 @@ function adaptSolanaStandardWallet(walletInfo, options = {}) {
149
149
  throw new Error("Solana wallet did not return a sign-in result");
150
150
  }
151
151
  const signInResult = toSolanaSignInResult(result);
152
+ const requestedAddress = input?.address ?? account?.address;
153
+ const signInAccount = accounts.find(
154
+ (candidate) => candidate.address === signInResult.account.address
155
+ );
156
+ if (!signInAccount) {
157
+ throw createSolanaError(
158
+ "INVALID_SIGNATURE",
159
+ "The wallet returned a sign-in account it does not expose."
160
+ );
161
+ }
162
+ if (requestedAddress !== void 0 && requestedAddress !== signInAccount.address) {
163
+ throw createSolanaError(
164
+ "INVALID_SIGNATURE",
165
+ `The wallet signed in as ${signInResult.account.address} instead of the requested ${requestedAddress}.`
166
+ );
167
+ }
152
168
  await assertWalletSignature(
153
- signInResult.account.publicKey,
169
+ signInAccount.publicKey,
154
170
  signInResult.signedMessage,
155
171
  signInResult.signature,
156
172
  "sign-in"
@@ -235,6 +251,19 @@ function adaptSolanaStandardWallet(walletInfo, options = {}) {
235
251
  } : void 0
236
252
  };
237
253
  }
254
+ async function disconnectSolanaStandardWallet(walletInfo) {
255
+ const wallet = walletInfo.wallet;
256
+ if (typeof wallet !== "object" && typeof wallet !== "function") {
257
+ return false;
258
+ }
259
+ const features = wallet?.features;
260
+ const feature = features?.[StandardDisconnect];
261
+ if (typeof feature?.disconnect !== "function") {
262
+ return false;
263
+ }
264
+ await feature.disconnect();
265
+ return true;
266
+ }
238
267
  async function signAllThroughFeature(wallet, getAccount, transactions, chain) {
239
268
  const activeAccount = getAccount();
240
269
  const results = await wallet.features[SolanaSignTransaction].signTransaction(
@@ -352,4 +381,4 @@ function getWalletUrl(wallet) {
352
381
  return "url" in wallet && typeof wallet.url === "string" ? wallet.url : void 0;
353
382
  }
354
383
 
355
- export { SOLANA_CHAINS as S, SOLANA_MOBILE_WALLET_ADAPTER_WALLET_NAME as a, adaptSolanaStandardWallet as b, getSolanaChain as c, getRegisteredSolanaWallets as g, isSolanaStandardWallet as i, subscribeSolanaWallets as s };
384
+ export { SOLANA_CHAINS as S, SOLANA_MOBILE_WALLET_ADAPTER_WALLET_NAME as a, adaptSolanaStandardWallet as b, getSolanaChain as c, disconnectSolanaStandardWallet as d, getRegisteredSolanaWallets as g, isSolanaStandardWallet as i, subscribeSolanaWallets as s };
@@ -78,6 +78,9 @@ async function confirmTransactionSignature(client, signature, options = {}) {
78
78
  `Timed out waiting for transaction ${signature} to reach ${commitment} commitment.`
79
79
  );
80
80
  } catch (cause) {
81
+ if (abortSignal?.aborted) {
82
+ throw abortSignal.reason ?? cause;
83
+ }
81
84
  if (isAbortErrorLike(cause)) {
82
85
  throw cause;
83
86
  }
@@ -17,6 +17,18 @@ interface AdaptedSolanaWallet extends SolanaWallet {
17
17
  dispose(): void;
18
18
  }
19
19
  declare function adaptSolanaStandardWallet(walletInfo: SolanaWalletInfo, options?: AdaptSolanaWalletOptions): AdaptedSolanaWallet;
20
+ /**
21
+ * Disconnects a discovered Wallet Standard wallet without adapting it.
22
+ *
23
+ * Reads `standard:disconnect` straight off the wallet instead of going through
24
+ * an adapter on purpose: a wallet the app authorized in an earlier page session
25
+ * has no adapter yet, so an adapter-only sweep misses it, and adapting it just
26
+ * to disconnect would register a `change` subscription for a wallet the user
27
+ * never selects. Resolves `false` when the wallet exposes no disconnect feature
28
+ * (for example an iOS deep-link definition); rejects with the wallet's own
29
+ * error, so a caller sweeping a list should catch per wallet.
30
+ */
31
+ declare function disconnectSolanaStandardWallet(walletInfo: SolanaWalletInfo): Promise<boolean>;
20
32
 
21
33
  declare const SOLANA_CHAINS: readonly SolanaChain[];
22
34
  declare function getSolanaChain(cluster: SolanaCluster): SolanaChain;
@@ -26,5 +38,5 @@ declare function isSolanaStandardWallet(wallet: Wallet): boolean;
26
38
  declare function getRegisteredSolanaWallets(): SolanaWalletInfo[];
27
39
  declare function subscribeSolanaWallets(listener: () => void): () => void;
28
40
 
29
- export { SOLANA_CHAINS, SOLANA_MOBILE_WALLET_ADAPTER_WALLET_NAME, adaptSolanaStandardWallet, getRegisteredSolanaWallets, getSolanaChain, isSolanaStandardWallet, subscribeSolanaWallets };
41
+ export { SOLANA_CHAINS, SOLANA_MOBILE_WALLET_ADAPTER_WALLET_NAME, adaptSolanaStandardWallet, disconnectSolanaStandardWallet, getRegisteredSolanaWallets, getSolanaChain, isSolanaStandardWallet, subscribeSolanaWallets };
30
42
  export type { AdaptSolanaWalletOptions, AdaptedSolanaWallet };
@@ -1 +1 @@
1
- export { S as SOLANA_CHAINS, a as SOLANA_MOBILE_WALLET_ADAPTER_WALLET_NAME, b as adaptSolanaStandardWallet, g as getRegisteredSolanaWallets, c as getSolanaChain, i as isSolanaStandardWallet, s as subscribeSolanaWallets } from './shared/core.BlqTJRXd.mjs';
1
+ export { S as SOLANA_CHAINS, a as SOLANA_MOBILE_WALLET_ADAPTER_WALLET_NAME, b as adaptSolanaStandardWallet, d as disconnectSolanaStandardWallet, g as getRegisteredSolanaWallets, c as getSolanaChain, i as isSolanaStandardWallet, s as subscribeSolanaWallets } from './shared/core.A4I__4IR.mjs';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vue-solana/core",
3
- "version": "4.0.0-rc.0",
3
+ "version": "4.0.0",
4
4
  "description": "Framework-agnostic primitives for Vue Solana libraries.",
5
5
  "license": "MIT",
6
6
  "keywords": [