@vue-solana/core 4.1.2 → 4.1.4

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.
@@ -1,4 +1,4 @@
1
- import { c as SolanaCluster } from './shared/core.BhF1J_tt.js';
1
+ import { c as SolanaCluster } from './shared/core.C7oPQAH3.js';
2
2
  import '@solana/kit-plugin-rpc';
3
3
  import '@solana/kit';
4
4
 
package/dist/errors.d.ts CHANGED
@@ -1,3 +1,5 @@
1
+ import { Signature } from '@solana/kit';
2
+
1
3
  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
4
  interface SolanaErrorOptions {
3
5
  cause?: unknown;
@@ -9,9 +11,18 @@ declare class SolanaError extends Error {
9
11
  readonly feature?: string;
10
12
  constructor(code: SolanaErrorCode, message: string, options?: SolanaErrorOptions);
11
13
  }
14
+ /**
15
+ * Thrown when a multi-transaction send fails partway through: the earlier
16
+ * transactions in the batch have already been sent, and their signatures are
17
+ * exposed here so callers can avoid resubmitting them.
18
+ */
19
+ declare class PartialSignAndSendError extends SolanaError {
20
+ readonly signatures: Signature[];
21
+ constructor(cause: SolanaError, signatures: Signature[]);
22
+ }
12
23
  declare function createSolanaError(code: SolanaErrorCode, message: string, options?: SolanaErrorOptions): SolanaError;
13
24
  declare function isSolanaError(error: unknown): error is SolanaError;
14
25
  declare function normalizeSolanaError(cause: unknown, fallbackCode: SolanaErrorCode, fallbackMessage?: string, options?: Omit<SolanaErrorOptions, "cause">): SolanaError;
15
26
 
16
- export { SolanaError, createSolanaError, isSolanaError, normalizeSolanaError };
27
+ export { PartialSignAndSendError, SolanaError, createSolanaError, isSolanaError, normalizeSolanaError };
17
28
  export type { SolanaErrorCode, SolanaErrorOptions };
package/dist/errors.mjs CHANGED
@@ -9,6 +9,13 @@ class SolanaError extends Error {
9
9
  cause;
10
10
  feature;
11
11
  }
12
+ class PartialSignAndSendError extends SolanaError {
13
+ constructor(cause, signatures) {
14
+ super(cause.code, cause.message, { cause: cause.cause, feature: cause.feature });
15
+ this.signatures = signatures;
16
+ this.name = "PartialSignAndSendError";
17
+ }
18
+ }
12
19
  function createSolanaError(code, message, options) {
13
20
  return new SolanaError(code, message, options);
14
21
  }
@@ -45,4 +52,4 @@ function hasErrorCode(cause) {
45
52
  );
46
53
  }
47
54
 
48
- export { SolanaError, createSolanaError, isSolanaError, normalizeSolanaError };
55
+ export { PartialSignAndSendError, SolanaError, createSolanaError, isSolanaError, normalizeSolanaError };
package/dist/index.d.ts CHANGED
@@ -1,11 +1,11 @@
1
1
  export { SolanaActionState, SolanaActionStore, createSolanaActionStore, isAbortError, isSolanaActionAborted } from './action.js';
2
2
  export { DEFAULT_CLUSTER, getClusterEndpoint, getClusterWebSocketEndpoint, getWebSocketEndpoint } from './clusters.js';
3
3
  export { AddressInput, MaybeAddressInput, parseAddress } from './address.js';
4
- export { SolanaError, SolanaErrorCode, SolanaErrorOptions, createSolanaError, isSolanaError, normalizeSolanaError } from './errors.js';
5
- export { C as ConfirmTransactionOptions, S as SendTransactionOptions, a as SolanaChain, b as SolanaClient, c as SolanaCluster, d as SolanaConfig, e as SolanaContext, f as SolanaSignInAccount, g as SolanaSignInInput, h as SolanaSignInResult, i as SolanaSignMessageResult, j as SolanaTransaction, k as SolanaWallet, l as SolanaWalletInfo, T as TransactionConfirmation, m as TransactionStatus, n as createSolanaClient } from './shared/core.BhF1J_tt.js';
4
+ export { PartialSignAndSendError, SolanaError, SolanaErrorCode, SolanaErrorOptions, createSolanaError, isSolanaError, normalizeSolanaError } from './errors.js';
5
+ export { C as ConfirmTransactionOptions, S as SendTransactionOptions, a as SolanaChain, b as SolanaClient, c as SolanaCluster, d as SolanaConfig, e as SolanaContext, f as SolanaSignInAccount, g as SolanaSignInInput, h as SolanaSignInResult, i as SolanaSignMessageResult, j as SolanaTransaction, k as SolanaWallet, l as SolanaWalletConnectOptions, m as SolanaWalletInfo, T as TransactionConfirmation, n as TransactionStatus, o as createSolanaClient } from './shared/core.C7oPQAH3.js';
6
6
  export { createSolanaContext } from './rpc.js';
7
7
  export { TokenAccountInfo, TokenAccountsByOwnerOptions, TokenBalanceResult, getTokenAccount, getTokenAccountsByOwner, getTokenBalance } from './token-accounts.js';
8
- export { confirmTransactionSignature, signAndSendTransaction } from './transaction.js';
8
+ export { confirmTransactionSignature, sendSignedTransaction, signAndSendTransaction } from './transaction.js';
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 { SolanaPayVerificationOptions, SolanaPayVerificationResult, createSolanaPayReference, findTransactionSignaturesByReference, verifySolanaPayPayment } from './solana-pay-confirmation.js';
package/dist/index.mjs CHANGED
@@ -1,15 +1,15 @@
1
1
  export { createSolanaActionStore, isAbortError, isSolanaActionAborted } from './action.mjs';
2
2
  export { DEFAULT_CLUSTER, getClusterEndpoint, getClusterWebSocketEndpoint, getWebSocketEndpoint } from './clusters.mjs';
3
3
  export { parseAddress } from './address.mjs';
4
- export { SolanaError, createSolanaError, isSolanaError, normalizeSolanaError } from './errors.mjs';
4
+ export { PartialSignAndSendError, SolanaError, createSolanaError, isSolanaError, normalizeSolanaError } from './errors.mjs';
5
5
  export { createSolanaClient } from './kit.mjs';
6
6
  export { createSolanaContext } from './rpc.mjs';
7
7
  export { getTokenAccount, getTokenAccountsByOwner, getTokenBalance } from './token-accounts.mjs';
8
- export { confirmTransactionSignature, signAndSendTransaction } from './transaction.mjs';
8
+ export { confirmTransactionSignature, sendSignedTransaction, signAndSendTransaction } from './transaction.mjs';
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 { createSolanaPayReference, findTransactionSignaturesByReference, verifySolanaPayPayment } from './solana-pay-confirmation.mjs';
12
12
  export { getSolanaPayPaymentStatus } from './solana-pay-status.mjs';
13
13
  export * from '@solana/kit';
14
14
  export { C as CreateTransferError, H as HTTPS_PROTOCOL, M as MEMO_PROGRAM_ID, P as ParseURLError, S as SOLANA_PROTOCOL, a as SOL_DECIMALS, T as TEN, b as TOKEN_PROGRAM_ID, c as createQR, d as createQRCanvas, e as createQRDataURL, f as createQROptions, g as createRecipient, h as createSPLToken, i as createStyledQRCode, j as encodeTransactionRequestURL, k as encodeTransferRequestURL, l as encodeURL, m as isValidSolanaAddress, p as parseURL } from './shared/core.CQFtdLrA.mjs';
15
- 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';
15
+ 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.D8oIzL0q.mjs';
@@ -1,4 +1,4 @@
1
- import { a as SolanaChain, c as SolanaCluster, l as SolanaWalletInfo, k as SolanaWallet } from './shared/core.BhF1J_tt.js';
1
+ import { a as SolanaChain, c as SolanaCluster, m as SolanaWalletInfo, k as SolanaWallet } from './shared/core.C7oPQAH3.js';
2
2
  import '@solana/kit-plugin-rpc';
3
3
  import '@solana/kit';
4
4
 
@@ -616,12 +616,16 @@ function adaptSolanaIosWallet(walletInfo, options = {}) {
616
616
  get disconnecting() {
617
617
  return disconnecting;
618
618
  },
619
- async connect() {
619
+ async connect(connectOptions) {
620
620
  const callback = handleSolanaIosWalletCallback({ clearUrl: true });
621
621
  if (callback?.walletId === definition.id) {
622
622
  refreshSession();
623
623
  return;
624
624
  }
625
+ if (connectOptions?.silent) {
626
+ refreshSession();
627
+ return;
628
+ }
625
629
  connecting = true;
626
630
  options.onChange?.();
627
631
  try {
package/dist/kit.d.ts CHANGED
@@ -1,3 +1,3 @@
1
1
  import '@solana/kit-plugin-rpc';
2
2
  export * from '@solana/kit';
3
- export { b as SolanaClient, n as createSolanaClient } from './shared/core.BhF1J_tt.js';
3
+ export { b as SolanaClient, o as createSolanaClient } from './shared/core.C7oPQAH3.js';
@@ -1,5 +1,6 @@
1
+ import { AuthorizationCache } from '@solana-mobile/wallet-standard-mobile';
1
2
  export { isSolanaMobileWalletSupported } from './mobile-wallet-support.js';
2
- import { a as SolanaChain } from './shared/core.BhF1J_tt.js';
3
+ import { a as SolanaChain } from './shared/core.C7oPQAH3.js';
3
4
  import '@solana/kit-plugin-rpc';
4
5
  import '@solana/kit';
5
6
 
@@ -14,6 +15,14 @@ interface RegisterSolanaMobileWalletOptions {
14
15
  remoteHostAuthority?: string;
15
16
  }
16
17
  declare function registerSolanaMobileWallet(options?: RegisterSolanaMobileWalletOptions): boolean;
18
+ /**
19
+ * The default MWA authorization cache is a single localStorage entry shared by
20
+ * every network, and re-authorization reuses the cached `chain`. An app that
21
+ * moved from devnet to mainnet would otherwise keep signing against the devnet
22
+ * authorization, so one for a chain this registration does not serve is
23
+ * dropped and the next request authorizes afresh.
24
+ */
25
+ declare function createChainScopedAuthorizationCache(chains: readonly SolanaChain[]): AuthorizationCache;
17
26
  /**
18
27
  * Clears this package's local record of which Mobile Wallet Adapter
19
28
  * registration keys have been applied. This only affects the deduplication guard
@@ -24,7 +33,15 @@ declare function registerSolanaMobileWallet(options?: RegisterSolanaMobileWallet
24
33
  * host-side registration. Intended for test/HMR reset only.
25
34
  */
26
35
  declare function unregisterSolanaMobileWallet(): void;
36
+ /**
37
+ * The identity MWA sends with every `authorize`, including the re-authorization
38
+ * that precedes each signing request. Wallets bind the auth token to it, so it
39
+ * must not change between page loads: `document.title` did (per route, per
40
+ * locale, and empty on a client-only route at registration time), and a session
41
+ * restored under one title was then re-authorized under another, which Phantom
42
+ * answered by opening without a prompt. Pass `appIdentity` to pin it explicitly.
43
+ */
27
44
  declare function getDefaultMobileWalletAppIdentity(): SolanaMobileWalletAppIdentity;
28
45
 
29
- export { getDefaultMobileWalletAppIdentity, registerSolanaMobileWallet, unregisterSolanaMobileWallet };
46
+ export { createChainScopedAuthorizationCache, getDefaultMobileWalletAppIdentity, registerSolanaMobileWallet, unregisterSolanaMobileWallet };
30
47
  export type { RegisterSolanaMobileWalletOptions, SolanaMobileWalletAppIdentity };
@@ -1,4 +1,5 @@
1
- import { registerMwa, createDefaultWalletNotFoundHandler, createDefaultChainSelector, createDefaultAuthorizationCache } from '@solana-mobile/wallet-standard-mobile';
1
+ import { createDefaultAuthorizationCache, registerMwa, createDefaultWalletNotFoundHandler, createDefaultChainSelector } from '@solana-mobile/wallet-standard-mobile';
2
+ import { s as setSolanaMobileWalletAuthorizationStore } from './shared/core.DQCWbyEu.mjs';
2
3
  import { isSolanaMobileWalletSupported } from './mobile-wallet-support.mjs';
3
4
 
4
5
  const DEFAULT_MOBILE_WALLET_CHAINS = ["solana:mainnet", "solana:devnet"];
@@ -17,19 +18,40 @@ function registerSolanaMobileWallet(options = {}) {
17
18
  if (registeredMobileWalletKeys.has(registrationKey)) {
18
19
  return true;
19
20
  }
21
+ const authorizationCache = createChainScopedAuthorizationCache(chains);
20
22
  registerMwa({
21
23
  appIdentity,
22
- authorizationCache: createDefaultAuthorizationCache(),
24
+ authorizationCache,
23
25
  chains,
24
26
  chainSelector: createDefaultChainSelector(),
25
27
  remoteHostAuthority: options.remoteHostAuthority,
26
28
  onWalletNotFound: createDefaultWalletNotFoundHandler()
27
29
  });
28
30
  registeredMobileWalletKeys.add(registrationKey);
31
+ setSolanaMobileWalletAuthorizationStore({
32
+ get: () => authorizationCache.get(),
33
+ set: (authorization) => authorizationCache.set(authorization)
34
+ });
29
35
  return true;
30
36
  }
37
+ function createChainScopedAuthorizationCache(chains) {
38
+ const cache = createDefaultAuthorizationCache();
39
+ return {
40
+ clear: () => cache.clear(),
41
+ set: (authorization) => cache.set(authorization),
42
+ async get() {
43
+ const authorization = await cache.get();
44
+ if (authorization && !chains.includes(authorization.chain)) {
45
+ await cache.clear();
46
+ return void 0;
47
+ }
48
+ return authorization;
49
+ }
50
+ };
51
+ }
31
52
  function unregisterSolanaMobileWallet() {
32
53
  registeredMobileWalletKeys.clear();
54
+ setSolanaMobileWalletAuthorizationStore(void 0);
33
55
  }
34
56
  function getDefaultMobileWalletAppIdentity() {
35
57
  if (typeof window === "undefined") {
@@ -39,11 +61,18 @@ function getDefaultMobileWalletAppIdentity() {
39
61
  };
40
62
  }
41
63
  return {
42
- name: document.title || "Vue Solana App",
64
+ name: getDocumentAppName(),
43
65
  uri: window.location.origin,
44
66
  icon: getDocumentIconPath()
45
67
  };
46
68
  }
69
+ function getDocumentAppName() {
70
+ const name = getMetaContent('meta[name="application-name"]') ?? getMetaContent('meta[property="og:site_name"]') ?? window.location.hostname;
71
+ return name || "Vue Solana App";
72
+ }
73
+ function getMetaContent(selector) {
74
+ return document.querySelector(selector)?.content.trim() || void 0;
75
+ }
47
76
  function getDocumentIconPath() {
48
77
  const icon = document.querySelector(
49
78
  'link[rel="icon"], link[rel="shortcut icon"], link[rel="apple-touch-icon"]'
@@ -51,10 +80,13 @@ function getDocumentIconPath() {
51
80
  if (!icon?.href) {
52
81
  return void 0;
53
82
  }
54
- if (icon.href.startsWith(window.location.origin)) {
55
- return icon.href.slice(window.location.origin.length).replace(/^\//, "");
83
+ if (icon.href.startsWith("data:")) {
84
+ return icon.href;
85
+ }
86
+ if (icon.href.startsWith(`${window.location.origin}/`)) {
87
+ return icon.href.slice(window.location.origin.length + 1);
56
88
  }
57
- return icon.href;
89
+ return void 0;
58
90
  }
59
91
 
60
- export { getDefaultMobileWalletAppIdentity, isSolanaMobileWalletSupported, registerSolanaMobileWallet, unregisterSolanaMobileWallet };
92
+ export { createChainScopedAuthorizationCache, getDefaultMobileWalletAppIdentity, isSolanaMobileWalletSupported, registerSolanaMobileWallet, unregisterSolanaMobileWallet };
package/dist/rpc.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { d as SolanaConfig, e as SolanaContext } from './shared/core.BhF1J_tt.js';
1
+ import { d as SolanaConfig, e as SolanaContext } from './shared/core.C7oPQAH3.js';
2
2
  import '@solana/kit-plugin-rpc';
3
3
  import '@solana/kit';
4
4
 
@@ -1,6 +1,6 @@
1
1
  import * as _solana_kit_plugin_rpc from '@solana/kit-plugin-rpc';
2
2
  import * as _solana_kit from '@solana/kit';
3
- import { Address, ReadonlyUint8Array, Slot, Commitment, TransactionSigner, Signature } from '@solana/kit';
3
+ import { ReadonlyUint8Array, Slot, Commitment, Address, TransactionSigner, Signature } from '@solana/kit';
4
4
 
5
5
  /**
6
6
  * Solana cluster names. `mainnet` is Solana's official mainnet cluster name.
@@ -73,6 +73,16 @@ interface SolanaSignInInput {
73
73
  requestId?: string;
74
74
  resources?: readonly string[];
75
75
  }
76
+ interface SolanaWalletConnectOptions {
77
+ /**
78
+ * Restore an authorization the wallet already granted without prompting the
79
+ * user. When there is none the wallet stays disconnected and `connect()`
80
+ * resolves without throwing. Used by auto-connect: a prompt (or, for the
81
+ * Android Mobile Wallet Adapter, an app launch) needs a user gesture that a
82
+ * page load does not have.
83
+ */
84
+ silent?: boolean;
85
+ }
76
86
  interface SolanaWallet {
77
87
  publicKey: Address | null;
78
88
  connected: boolean;
@@ -80,7 +90,7 @@ interface SolanaWallet {
80
90
  disconnecting?: boolean;
81
91
  platform?: SolanaWalletInfo["platform"];
82
92
  source?: SolanaWalletInfo["source"];
83
- connect: () => Promise<void>;
93
+ connect: (options?: SolanaWalletConnectOptions) => Promise<void>;
84
94
  disconnect: () => Promise<void>;
85
95
  /**
86
96
  * Releases wallet-event subscriptions captured at adapt time (e.g. the
@@ -227,5 +237,5 @@ declare function createSolanaClient(config: SolanaConfigWithPayer): SolanaClient
227
237
  declare function createSolanaClient(config?: SolanaConfig): SolanaClientWithOptionalPayer;
228
238
  type SolanaClient = SolanaClientWithOptionalPayer;
229
239
 
230
- export { createSolanaClient as n };
231
- export type { ConfirmTransactionOptions as C, SendTransactionOptions as S, TransactionConfirmation as T, SolanaChain as a, SolanaClient as b, SolanaCluster as c, SolanaConfig as d, SolanaContext as e, SolanaSignInAccount as f, SolanaSignInInput as g, SolanaSignInResult as h, SolanaSignMessageResult as i, SolanaTransaction as j, SolanaWallet as k, SolanaWalletInfo as l, TransactionStatus as m };
240
+ export { createSolanaClient as o };
241
+ export type { ConfirmTransactionOptions as C, SendTransactionOptions as S, TransactionConfirmation as T, SolanaChain as a, SolanaClient as b, SolanaCluster as c, SolanaConfig as d, SolanaContext as e, SolanaSignInAccount as f, SolanaSignInInput as g, SolanaSignInResult as h, SolanaSignMessageResult as i, SolanaTransaction as j, SolanaWallet as k, SolanaWalletConnectOptions as l, SolanaWalletInfo as m, TransactionStatus as n };
@@ -1,7 +1,8 @@
1
1
  import { StandardEvents, StandardDisconnect, StandardConnect } from '@wallet-standard/features';
2
2
  import { SolanaSignTransaction, SolanaSignIn, SolanaSignAndSendTransaction, SolanaSignMessage } from '@solana/wallet-standard-features';
3
3
  import { isSignatureBytes, verifySignature, getBase58Decoder } from '@solana/kit';
4
- import { createSolanaError } from '../errors.mjs';
4
+ import { createSolanaError, PartialSignAndSendError, normalizeSolanaError } from '../errors.mjs';
5
+ import { g as getSolanaMobileWalletAuthorizationStore } from './core.DQCWbyEu.mjs';
5
6
  import { getWallets } from '@wallet-standard/app';
6
7
 
7
8
  async function assertWalletSignature(publicKey, signedMessage, signature, label) {
@@ -75,17 +76,97 @@ function adaptSolanaStandardWallet(walletInfo, options = {}) {
75
76
  let connecting = false;
76
77
  let disconnecting = false;
77
78
  let manuallyDisconnected = false;
79
+ let signingIn = false;
80
+ const isMobileWalletAdapter = walletInfo.source === "mobile-wallet-adapter";
78
81
  const eventsFeature = wallet.features[StandardEvents];
79
82
  const unsubscribeChange = eventsFeature?.on("change", (properties) => {
80
83
  if (properties.accounts) {
81
84
  accounts = properties.accounts;
82
- account = allowAccountUpdates && !manuallyDisconnected ? getSolanaAccount(accounts, options.chain) : void 0;
85
+ if (!signingIn) {
86
+ account = allowAccountUpdates && !manuallyDisconnected ? getSolanaAccount(accounts, options.chain) : void 0;
87
+ }
83
88
  options.onChange?.();
84
89
  }
85
90
  });
86
91
  function dispose() {
87
92
  unsubscribeChange?.();
88
93
  }
94
+ async function verifySignInResult(result, requestedAddress) {
95
+ if (!result) {
96
+ throw new Error("Solana wallet did not return a sign-in result");
97
+ }
98
+ const signInResult = toSolanaSignInResult(result);
99
+ const isSignInAccount = (candidate) => candidate.address === signInResult.account.address;
100
+ const signInAccount = accounts.find(isSignInAccount) ?? wallet.accounts.find(isSignInAccount);
101
+ if (!signInAccount) {
102
+ throw createSolanaError(
103
+ "INVALID_SIGNATURE",
104
+ "The wallet returned a sign-in account it does not expose."
105
+ );
106
+ }
107
+ if (requestedAddress !== void 0 && requestedAddress !== signInAccount.address) {
108
+ throw createSolanaError(
109
+ "INVALID_SIGNATURE",
110
+ `The wallet signed in as ${signInResult.account.address} instead of the requested ${requestedAddress}.`
111
+ );
112
+ }
113
+ await assertWalletSignature(
114
+ signInAccount.publicKey,
115
+ signInResult.signedMessage,
116
+ signInResult.signature,
117
+ "sign-in"
118
+ );
119
+ return { signInResult, signInAccount };
120
+ }
121
+ async function signInThroughMobileWalletAdapter(signInWallet, signInInput, requestedAddress) {
122
+ const disconnectFeature = wallet.features[StandardDisconnect];
123
+ const authorizationStore = getSolanaMobileWalletAuthorizationStore();
124
+ const previous = { account, allowAccountUpdates, manuallyDisconnected };
125
+ const wasAuthorized = wallet.accounts.length > 0;
126
+ const previousAuthorization = await authorizationStore?.get().catch(() => void 0);
127
+ signingIn = true;
128
+ try {
129
+ await disconnectFeature.disconnect();
130
+ const [result] = await signInWallet.features[SolanaSignIn].signIn(signInInput);
131
+ const { signInResult, signInAccount } = await verifySignInResult(result, requestedAddress);
132
+ accounts = wallet.accounts;
133
+ allowAccountUpdates = true;
134
+ manuallyDisconnected = false;
135
+ account = signInAccount;
136
+ return signInResult;
137
+ } catch (error) {
138
+ await restoreMobileWalletAuthorization(
139
+ disconnectFeature,
140
+ authorizationStore,
141
+ previousAuthorization,
142
+ wasAuthorized
143
+ );
144
+ accounts = wallet.accounts;
145
+ allowAccountUpdates = previous.allowAccountUpdates;
146
+ manuallyDisconnected = previous.manuallyDisconnected;
147
+ account = previous.account && accounts.some((candidate) => candidate.address === previous.account?.address) ? previous.account : void 0;
148
+ throw error;
149
+ } finally {
150
+ signingIn = false;
151
+ options.onChange?.();
152
+ }
153
+ }
154
+ async function restoreMobileWalletAuthorization(disconnectFeature, authorizationStore, previousAuthorization, wasAuthorized) {
155
+ try {
156
+ if (wallet.accounts.length > 0) {
157
+ await disconnectFeature.disconnect();
158
+ }
159
+ if (!authorizationStore || previousAuthorization === void 0) {
160
+ return;
161
+ }
162
+ await authorizationStore.set(previousAuthorization);
163
+ if (wasAuthorized) {
164
+ const connectFeature = wallet.features[StandardConnect];
165
+ await connectFeature.connect({ silent: true });
166
+ }
167
+ } catch {
168
+ }
169
+ }
89
170
  return {
90
171
  platform: walletInfo.platform,
91
172
  source: walletInfo.source,
@@ -102,16 +183,20 @@ function adaptSolanaStandardWallet(walletInfo, options = {}) {
102
183
  get disconnecting() {
103
184
  return disconnecting;
104
185
  },
105
- async connect() {
186
+ async connect(connectOptions) {
106
187
  connecting = true;
107
188
  manuallyDisconnected = false;
108
189
  options.onChange?.();
109
190
  try {
110
191
  const feature = wallet.features[StandardConnect];
111
- const result = await feature.connect();
192
+ const result = await (connectOptions?.silent ? feature.connect({ silent: true }) : feature.connect());
193
+ const nextAccount = getSolanaAccount(result.accounts, options.chain);
194
+ if (!nextAccount && connectOptions?.silent) {
195
+ return;
196
+ }
112
197
  accounts = result.accounts;
113
198
  allowAccountUpdates = true;
114
- account = getSolanaAccount(accounts, options.chain);
199
+ account = nextAccount;
115
200
  if (!account) {
116
201
  throw new Error("Solana wallet did not authorize a Solana account");
117
202
  }
@@ -142,36 +227,18 @@ function adaptSolanaStandardWallet(walletInfo, options = {}) {
142
227
  }
143
228
  },
144
229
  signIn: hasSignIn(wallet) ? async (input) => {
145
- const [result] = await wallet.features[SolanaSignIn].signIn(
146
- input ?? getDefaultSignInInput(getActiveAccount(account))
147
- );
148
- if (!result) {
149
- throw new Error("Solana wallet did not return a sign-in result");
150
- }
151
- const signInResult = toSolanaSignInResult(result);
152
230
  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}.`
231
+ if (isMobileWalletAdapter) {
232
+ return signInThroughMobileWalletAdapter(
233
+ wallet,
234
+ input ?? getDefaultSignInInput(account),
235
+ requestedAddress
166
236
  );
167
237
  }
168
- await assertWalletSignature(
169
- signInAccount.publicKey,
170
- signInResult.signedMessage,
171
- signInResult.signature,
172
- "sign-in"
238
+ const [result] = await wallet.features[SolanaSignIn].signIn(
239
+ input ?? getDefaultSignInInput(getActiveAccount(account))
173
240
  );
174
- return signInResult;
241
+ return (await verifySignInResult(result, requestedAddress)).signInResult;
175
242
  } : void 0,
176
243
  signMessage: hasSignMessage(wallet) ? async (message) => {
177
244
  const activeAccount = getActiveAccount(account);
@@ -182,13 +249,18 @@ function adaptSolanaStandardWallet(walletInfo, options = {}) {
182
249
  if (!result) {
183
250
  throw new Error("Solana wallet did not return a message signature");
184
251
  }
252
+ const signedMessage = getSignedMessageBytes(
253
+ message,
254
+ result.signedMessage,
255
+ result.signature
256
+ );
185
257
  await assertWalletSignature(
186
258
  activeAccount.publicKey,
187
- result.signedMessage,
259
+ signedMessage,
188
260
  result.signature,
189
261
  "message"
190
262
  );
191
- return result;
263
+ return { ...result, signedMessage };
192
264
  } : void 0,
193
265
  signTransaction: hasSignTransaction(wallet) ? async (transaction) => {
194
266
  const activeAccount = getActiveAccount(account);
@@ -229,6 +301,15 @@ function adaptSolanaStandardWallet(walletInfo, options = {}) {
229
301
  } : void 0,
230
302
  signAndSendTransactions: hasSignAndSendTransaction(wallet) ? async (transactions, sendOptions) => {
231
303
  const activeAccount = getActiveAccount(account);
304
+ if (isMobileWalletAdapter && options.sendTransaction && transactions.length > 1 && hasSignTransaction(wallet)) {
305
+ const signedTransactions = await signAllThroughFeature(
306
+ wallet,
307
+ () => activeAccount,
308
+ transactions,
309
+ options.chain
310
+ );
311
+ return sendSequentially(signedTransactions, options.sendTransaction, sendOptions);
312
+ }
232
313
  const results = await wallet.features[SolanaSignAndSendTransaction].signAndSendTransaction(
233
314
  ...transactions.map((transaction) => ({
234
315
  account: activeAccount,
@@ -264,6 +345,17 @@ async function disconnectSolanaStandardWallet(walletInfo) {
264
345
  await feature.disconnect();
265
346
  return true;
266
347
  }
348
+ async function sendSequentially(signedTransactions, sendTransaction, sendOptions) {
349
+ const signatures = [];
350
+ for (const signedTransaction of signedTransactions) {
351
+ try {
352
+ signatures.push(await sendTransaction(signedTransaction, sendOptions));
353
+ } catch (cause) {
354
+ throw new PartialSignAndSendError(normalizeSolanaError(cause, "RPC_FAILURE"), signatures);
355
+ }
356
+ }
357
+ return signatures;
358
+ }
267
359
  async function signAllThroughFeature(wallet, getAccount, transactions, chain) {
268
360
  const activeAccount = getAccount();
269
361
  const results = await wallet.features[SolanaSignTransaction].signTransaction(
@@ -286,10 +378,20 @@ async function signAllThroughFeature(wallet, getAccount, transactions, chain) {
286
378
  });
287
379
  }
288
380
  function getDefaultSignInInput(account) {
289
- return {
290
- domain: typeof location === "undefined" ? "" : location.host,
291
- address: account.address
292
- };
381
+ const domain = typeof location === "undefined" ? "" : location.host;
382
+ return account ? { domain, address: account.address } : { domain };
383
+ }
384
+ function getSignedMessageBytes(message, signedMessage, signature) {
385
+ if (signedMessage.length === message.length + signature.length && bytesEqual(signedMessage.subarray(0, message.length), message) && bytesEqual(signedMessage.subarray(message.length), signature)) {
386
+ return message;
387
+ }
388
+ if (bytesEqual(signedMessage, signature) && !bytesEqual(message, signature)) {
389
+ return message;
390
+ }
391
+ return signedMessage;
392
+ }
393
+ function bytesEqual(a, b) {
394
+ return a.length === b.length && a.every((byte, index) => byte === b[index]);
293
395
  }
294
396
  function toSolanaSignInResult(result) {
295
397
  return {
@@ -0,0 +1,9 @@
1
+ const STORE_KEY = Symbol.for("vue-solana:mobile-wallet-authorization-store");
2
+ function setSolanaMobileWalletAuthorizationStore(store) {
3
+ globalThis[STORE_KEY] = store;
4
+ }
5
+ function getSolanaMobileWalletAuthorizationStore() {
6
+ return globalThis[STORE_KEY];
7
+ }
8
+
9
+ export { getSolanaMobileWalletAuthorizationStore as g, setSolanaMobileWalletAuthorizationStore as s };
package/dist/timeout.d.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  import { SolanaErrorCode } from './errors.js';
2
+ import '@solana/kit';
2
3
 
3
4
  interface WithTimeoutOptions {
4
5
  /**
@@ -1,5 +1,5 @@
1
1
  import { Address, Commitment } from '@solana/kit';
2
- import { b as SolanaClient } from './shared/core.BhF1J_tt.js';
2
+ import { b as SolanaClient } from './shared/core.C7oPQAH3.js';
3
3
  import '@solana/kit-plugin-rpc';
4
4
 
5
5
  interface TokenAccountInfo {
@@ -1,5 +1,5 @@
1
1
  import { Signature } from '@solana/kit';
2
- import { b as SolanaClient, C as ConfirmTransactionOptions, T as TransactionConfirmation, k as SolanaWallet, j as SolanaTransaction, S as SendTransactionOptions } from './shared/core.BhF1J_tt.js';
2
+ import { b as SolanaClient, C as ConfirmTransactionOptions, T as TransactionConfirmation, j as SolanaTransaction, S as SendTransactionOptions, k as SolanaWallet } from './shared/core.C7oPQAH3.js';
3
3
  import '@solana/kit-plugin-rpc';
4
4
 
5
5
  /**
@@ -9,6 +9,10 @@ import '@solana/kit-plugin-rpc';
9
9
  * transaction, and the client sends it via `client.rpc.sendTransaction`.
10
10
  */
11
11
  declare function signAndSendTransaction(client: SolanaClient, wallet: SolanaWallet, transaction: SolanaTransaction, options?: SendTransactionOptions): Promise<Signature>;
12
+ /**
13
+ * Submit an already-signed transaction through the client RPC.
14
+ */
15
+ declare function sendSignedTransaction(client: SolanaClient, signedTransaction: SolanaTransaction, options?: SendTransactionOptions): Promise<Signature>;
12
16
  /**
13
17
  * Confirm a transaction signature against the kit RPC until it reaches the
14
18
  * requested commitment.
@@ -23,4 +27,4 @@ declare function signAndSendTransaction(client: SolanaClient, wallet: SolanaWall
23
27
  */
24
28
  declare function confirmTransactionSignature(client: SolanaClient, signature: Signature, options?: ConfirmTransactionOptions): Promise<TransactionConfirmation>;
25
29
 
26
- export { confirmTransactionSignature, signAndSendTransaction };
30
+ export { confirmTransactionSignature, sendSignedTransaction, signAndSendTransaction };
@@ -15,9 +15,15 @@ async function signAndSendTransaction(client, wallet, transaction, options) {
15
15
  }
16
16
  assertWalletCanSign(wallet);
17
17
  const signedTransaction = await wallet.signTransaction(transaction);
18
- const base64Transaction = toBase64WireTransaction(signedTransaction);
19
- const { maxRetries, minContextSlot, preflightCommitment, skipPreflight } = options ?? {};
20
- return await client.rpc.sendTransaction(base64Transaction, {
18
+ return await sendSignedTransaction(client, signedTransaction, options);
19
+ } catch (cause) {
20
+ throw normalizeSolanaError(cause, "RPC_FAILURE");
21
+ }
22
+ }
23
+ async function sendSignedTransaction(client, signedTransaction, options) {
24
+ const { maxRetries, minContextSlot, preflightCommitment, skipPreflight } = options ?? {};
25
+ try {
26
+ return await client.rpc.sendTransaction(toBase64WireTransaction(signedTransaction), {
21
27
  encoding: "base64",
22
28
  maxRetries,
23
29
  minContextSlot,
@@ -117,4 +123,4 @@ function isAbortErrorLike(cause) {
117
123
  return typeof cause === "object" && cause !== null && cause.name === "AbortError";
118
124
  }
119
125
 
120
- export { confirmTransactionSignature, signAndSendTransaction };
126
+ export { confirmTransactionSignature, sendSignedTransaction, signAndSendTransaction };
package/dist/types.d.ts CHANGED
@@ -1,3 +1,3 @@
1
1
  import '@solana/kit';
2
- export { C as ConfirmTransactionOptions, S as SendTransactionOptions, a as SolanaChain, c as SolanaCluster, d as SolanaConfig, e as SolanaContext, f as SolanaSignInAccount, g as SolanaSignInInput, h as SolanaSignInResult, i as SolanaSignMessageResult, j as SolanaTransaction, k as SolanaWallet, l as SolanaWalletInfo, T as TransactionConfirmation, m as TransactionStatus } from './shared/core.BhF1J_tt.js';
2
+ export { C as ConfirmTransactionOptions, S as SendTransactionOptions, a as SolanaChain, c as SolanaCluster, d as SolanaConfig, e as SolanaContext, f as SolanaSignInAccount, g as SolanaSignInInput, h as SolanaSignInResult, i as SolanaSignMessageResult, j as SolanaTransaction, k as SolanaWallet, l as SolanaWalletConnectOptions, m as SolanaWalletInfo, T as TransactionConfirmation, n as TransactionStatus } from './shared/core.C7oPQAH3.js';
3
3
  import '@solana/kit-plugin-rpc';
@@ -1,5 +1,5 @@
1
1
  import { WalletAccount, Wallet } from '@wallet-standard/base';
2
- import { a as SolanaChain, k as SolanaWallet, l as SolanaWalletInfo, c as SolanaCluster } from './shared/core.BhF1J_tt.js';
2
+ import { a as SolanaChain, j as SolanaTransaction, S as SendTransactionOptions, k as SolanaWallet, m as SolanaWalletInfo, c as SolanaCluster } from './shared/core.C7oPQAH3.js';
3
3
  import '@solana/kit-plugin-rpc';
4
4
  import '@solana/kit';
5
5
 
@@ -7,6 +7,13 @@ interface AdaptSolanaWalletOptions {
7
7
  chain?: SolanaChain;
8
8
  account?: WalletAccount;
9
9
  onChange?: () => void;
10
+ /**
11
+ * Submits a signed transaction (typically through the app's RPC). Lets a
12
+ * Mobile Wallet Adapter batch be signed in one wallet session and sent here,
13
+ * instead of MWA's own `signAndSendTransaction`, which opens the wallet app
14
+ * once per transaction.
15
+ */
16
+ sendTransaction?: (signedTransaction: SolanaTransaction, options?: SendTransactionOptions) => Promise<string>;
10
17
  }
11
18
  /**
12
19
  * A Wallet Standard wallet adapted to {@link SolanaWallet}. `dispose` is
@@ -1 +1 @@
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';
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.D8oIzL0q.mjs';
package/dist/wallet.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { k as SolanaWallet } from './shared/core.BhF1J_tt.js';
1
+ import { k as SolanaWallet } from './shared/core.C7oPQAH3.js';
2
2
  import { SolanaError } from './errors.js';
3
3
  import '@solana/kit-plugin-rpc';
4
4
  import '@solana/kit';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vue-solana/core",
3
- "version": "4.1.2",
3
+ "version": "4.1.4",
4
4
  "description": "Framework-agnostic primitives for Vue Solana libraries.",
5
5
  "license": "MIT",
6
6
  "keywords": [