@vue-solana/core 3.0.1 → 4.0.0-rc.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.
Files changed (50) hide show
  1. package/README.md +38 -2
  2. package/dist/action.d.ts +10 -3
  3. package/dist/action.mjs +4 -4
  4. package/dist/address.d.ts +7 -2
  5. package/dist/address.mjs +8 -2
  6. package/dist/clusters.d.ts +1 -1
  7. package/dist/clusters.mjs +10 -1
  8. package/dist/errors.d.ts +1 -1
  9. package/dist/errors.mjs +4 -1
  10. package/dist/index.d.ts +4 -11
  11. package/dist/index.mjs +2 -8
  12. package/dist/ios-wallet.d.ts +1 -2
  13. package/dist/ios-wallet.mjs +659 -1
  14. package/dist/kit.d.ts +1 -1
  15. package/dist/kit.mjs +22 -9
  16. package/dist/mobile-wallet.d.ts +12 -2
  17. package/dist/mobile-wallet.mjs +4 -1
  18. package/dist/rpc.d.ts +1 -1
  19. package/dist/shared/{core.C9vxHRXl.d.mts → core.BhF1J_tt.d.ts} +40 -2
  20. package/dist/shared/{core.DaUlAmcN.mjs → core.BlqTJRXd.mjs} +56 -5
  21. package/dist/timeout.d.ts +22 -1
  22. package/dist/timeout.mjs +53 -17
  23. package/dist/token-accounts.d.ts +1 -1
  24. package/dist/token-accounts.mjs +31 -2
  25. package/dist/transaction.d.ts +9 -1
  26. package/dist/transaction.mjs +49 -10
  27. package/dist/types.d.ts +1 -1
  28. package/dist/wallet-standard.d.ts +11 -3
  29. package/dist/wallet-standard.mjs +1 -1
  30. package/dist/wallet.d.ts +1 -1
  31. package/package.json +13 -5
  32. package/dist/action.d.mts +0 -28
  33. package/dist/address.d.mts +0 -16
  34. package/dist/buffer-polyfill.d.mts +0 -7
  35. package/dist/clusters.d.mts +0 -10
  36. package/dist/errors.d.mts +0 -17
  37. package/dist/index.d.mts +0 -21
  38. package/dist/ios-wallet.d.mts +0 -62
  39. package/dist/kit.d.mts +0 -3
  40. package/dist/mobile-wallet-support.d.mts +0 -3
  41. package/dist/mobile-wallet.d.mts +0 -20
  42. package/dist/rpc.d.mts +0 -7
  43. package/dist/shared/core.C9vxHRXl.d.ts +0 -193
  44. package/dist/shared/core.DJsPYgx_.mjs +0 -625
  45. package/dist/timeout.d.mts +0 -4
  46. package/dist/token-accounts.d.mts +0 -36
  47. package/dist/transaction.d.mts +0 -18
  48. package/dist/types.d.mts +0 -3
  49. package/dist/wallet-standard.d.mts +0 -22
  50. package/dist/wallet.d.mts +0 -25
package/dist/kit.mjs CHANGED
@@ -1,25 +1,38 @@
1
- import { getBase64Encoder, getBase58Decoder, signBytes, createClient, extendClient, createKeyPairFromBytes, isSolanaError, SOLANA_ERROR__KEYS__PUBLIC_KEY_MUST_MATCH_PRIVATE_KEY } from '@solana/kit';
1
+ import { getBase64Encoder, getBase58Decoder, signBytes, createClient, extendClient, createRpc, createDefaultRpcTransport, createSolanaRpcApi, DEFAULT_RPC_CONFIG, createSubscriptionRpc, createDefaultRpcSubscriptionsTransport, createDefaultSolanaRpcSubscriptionsChannelCreator, createSolanaRpcSubscriptionsApi, DEFAULT_RPC_SUBSCRIPTIONS_CONFIG, createKeyPairFromBytes, isSolanaError, SOLANA_ERROR__KEYS__PUBLIC_KEY_MUST_MATCH_PRIVATE_KEY } from '@solana/kit';
2
2
  export * from '@solana/kit';
3
- import { solanaRpc, rpcAirdrop } from '@solana/kit-plugin-rpc';
3
+ import { solanaRpc, rpcAirdrop, rpcGetMinimumBalance, rpcTransactionPlanner, rpcTransactionPlanSigningExecutor, rpcTransactionPlanSendingExecutor } from '@solana/kit-plugin-rpc';
4
4
  import { DEFAULT_CLUSTER, getClusterEndpoint, getWebSocketEndpoint, getClusterWebSocketEndpoint } from './clusters.mjs';
5
5
 
6
- function buildSolanaClient(endpoint, wsEndpoint, payer) {
6
+ function buildSolanaClient(endpoint, wsEndpoint, payer, commitment) {
7
7
  const client = createClient().use(
8
8
  (current) => extendClient(current, payer ? { payer } : {})
9
9
  );
10
- return client.use(
11
- solanaRpc({
12
- rpcUrl: endpoint,
13
- rpcSubscriptionsUrl: wsEndpoint
10
+ const rpc = commitment ? createRpc({
11
+ api: createSolanaRpcApi({ ...DEFAULT_RPC_CONFIG, defaultCommitment: commitment }),
12
+ transport: createDefaultRpcTransport({ url: endpoint })
13
+ }) : void 0;
14
+ const rpcSubscriptions = commitment ? createSubscriptionRpc({
15
+ api: createSolanaRpcSubscriptionsApi({
16
+ ...DEFAULT_RPC_SUBSCRIPTIONS_CONFIG,
17
+ defaultCommitment: commitment
18
+ }),
19
+ transport: createDefaultRpcSubscriptionsTransport({
20
+ createChannel: createDefaultSolanaRpcSubscriptionsChannelCreator({
21
+ url: wsEndpoint
22
+ })
14
23
  })
15
- ).use(rpcAirdrop());
24
+ }) : void 0;
25
+ if (!rpc || !rpcSubscriptions) {
26
+ return client.use(solanaRpc({ rpcUrl: endpoint, rpcSubscriptionsUrl: wsEndpoint })).use(rpcAirdrop());
27
+ }
28
+ return client.use((current) => extendClient(current, { rpc, rpcSubscriptions })).use(rpcGetMinimumBalance()).use(rpcTransactionPlanner()).use(rpcTransactionPlanSigningExecutor()).use(rpcTransactionPlanSendingExecutor()).use(rpcAirdrop());
16
29
  }
17
30
  function createSolanaClient(config = {}) {
18
31
  const cluster = config.cluster ?? DEFAULT_CLUSTER;
19
32
  const endpoint = config.endpoint ?? getClusterEndpoint(cluster);
20
33
  const wsEndpoint = config.wsEndpoint ?? (config.endpoint ? getWebSocketEndpoint(endpoint) : getClusterWebSocketEndpoint(cluster));
21
34
  const payer = config.payer ?? resolvePayerFromSecretKey(config.payerSecretKey);
22
- return buildSolanaClient(endpoint, wsEndpoint, payer);
35
+ return buildSolanaClient(endpoint, wsEndpoint, payer, config.commitment);
23
36
  }
24
37
  function resolvePayerFromSecretKey(payerSecretKey) {
25
38
  if (!payerSecretKey) {
@@ -1,5 +1,5 @@
1
1
  export { isSolanaMobileWalletSupported } from './mobile-wallet-support.js';
2
- import { a as SolanaChain } from './shared/core.C9vxHRXl.js';
2
+ import { a as SolanaChain } from './shared/core.BhF1J_tt.js';
3
3
  import '@solana/kit-plugin-rpc';
4
4
  import '@solana/kit';
5
5
 
@@ -14,7 +14,17 @@ interface RegisterSolanaMobileWalletOptions {
14
14
  remoteHostAuthority?: string;
15
15
  }
16
16
  declare function registerSolanaMobileWallet(options?: RegisterSolanaMobileWalletOptions): boolean;
17
+ /**
18
+ * Clears this package's local record of which Mobile Wallet Adapter
19
+ * registration keys have been applied. This only affects the deduplication guard
20
+ * inside `registerSolanaMobileWallet()`; it does not (and cannot) unregister an
21
+ * MWA wallet already installed into the host's Wallet Standard registry, because
22
+ * `@solana-mobile/wallet-standard-mobile` provides no unregister API. Calling
23
+ * `registerSolanaMobileWallet()` after this may therefore install a duplicate
24
+ * host-side registration. Intended for test/HMR reset only.
25
+ */
26
+ declare function unregisterSolanaMobileWallet(): void;
17
27
  declare function getDefaultMobileWalletAppIdentity(): SolanaMobileWalletAppIdentity;
18
28
 
19
- export { getDefaultMobileWalletAppIdentity, registerSolanaMobileWallet };
29
+ export { getDefaultMobileWalletAppIdentity, registerSolanaMobileWallet, unregisterSolanaMobileWallet };
20
30
  export type { RegisterSolanaMobileWalletOptions, SolanaMobileWalletAppIdentity };
@@ -28,6 +28,9 @@ function registerSolanaMobileWallet(options = {}) {
28
28
  registeredMobileWalletKeys.add(registrationKey);
29
29
  return true;
30
30
  }
31
+ function unregisterSolanaMobileWallet() {
32
+ registeredMobileWalletKeys.clear();
33
+ }
31
34
  function getDefaultMobileWalletAppIdentity() {
32
35
  if (typeof window === "undefined") {
33
36
  return {
@@ -54,4 +57,4 @@ function getDocumentIconPath() {
54
57
  return icon.href;
55
58
  }
56
59
 
57
- export { getDefaultMobileWalletAppIdentity, isSolanaMobileWalletSupported, registerSolanaMobileWallet };
60
+ export { 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.C9vxHRXl.js';
1
+ import { d as SolanaConfig, e as SolanaContext } from './shared/core.BhF1J_tt.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 { Commitment, Slot, TransactionSigner, Address, ReadonlyUint8Array, Signature } from '@solana/kit';
3
+ import { Address, ReadonlyUint8Array, Slot, Commitment, TransactionSigner, Signature } from '@solana/kit';
4
4
 
5
5
  /**
6
6
  * Solana cluster names. `mainnet` is Solana's official mainnet cluster name.
@@ -82,9 +82,21 @@ interface SolanaWallet {
82
82
  source?: SolanaWalletInfo["source"];
83
83
  connect: () => Promise<void>;
84
84
  disconnect: () => Promise<void>;
85
+ /**
86
+ * Releases wallet-event subscriptions captured at adapt time (e.g. the
87
+ * Wallet Standard `change` listener). The wallet stays connected; only
88
+ * event delivery stops. Idempotent. Present on adapted wallets; plain
89
+ * caller-supplied wallets typically omit it.
90
+ */
91
+ dispose?: () => void;
85
92
  signMessage?: (message: Uint8Array) => Promise<SolanaSignMessageResult>;
86
93
  signIn?: (input?: SolanaSignInInput) => Promise<SolanaSignInResult>;
87
94
  signTransaction?: (transaction: SolanaTransaction) => Promise<SolanaTransaction>;
95
+ /**
96
+ * @deprecated Prefer `signTransactions` (identical signature). `signTransactions`
97
+ * is tried first in `useSignTransactions()`; `signAllTransactions` remains as
98
+ * the legacy Wallet Standard naming.
99
+ */
88
100
  signAllTransactions?: (transactions: SolanaTransaction[]) => Promise<SolanaTransaction[]>;
89
101
  signTransactions?: (transactions: SolanaTransaction[]) => Promise<SolanaTransaction[]>;
90
102
  signAndSendTransaction?: (transaction: SolanaTransaction, options?: SendTransactionOptions) => Promise<{
@@ -128,6 +140,26 @@ interface SendTransactionOptions {
128
140
  interface ConfirmTransactionOptions {
129
141
  commitment?: Commitment;
130
142
  timeoutMs?: number;
143
+ /**
144
+ * Milliseconds between `getSignatureStatuses` polls. Defaults to 1500.
145
+ * Must be greater than 0: with no delay between polls an absent signature
146
+ * would hammer the RPC until the timeout expires.
147
+ */
148
+ pollIntervalMs?: number;
149
+ /**
150
+ * Rejects without further polling when the caller's own `AbortSignal`
151
+ * fires. An aborted confirmation rejects with the signal's reason (an
152
+ * `AbortError` by default) rather than a timeout error.
153
+ */
154
+ abortSignal?: AbortSignal;
155
+ /**
156
+ * Pass `true` to search transaction history when the signature may have
157
+ * left the recent status cache. Defaults to `false`: an older signature
158
+ * never appears in `getSignatureStatuses` results, so confirming one
159
+ * without this flag resolves to `TRANSACTION_NOT_FOUND` after the timeout
160
+ * rather than confirming.
161
+ */
162
+ searchTransactionHistory?: boolean;
131
163
  }
132
164
  interface TransactionStatus {
133
165
  slot: Slot;
@@ -141,9 +173,10 @@ interface TransactionConfirmation {
141
173
  status: TransactionStatus;
142
174
  }
143
175
 
144
- declare function buildSolanaClient(endpoint: string, wsEndpoint: string, payer: TransactionSigner | undefined): _solana_kit.Client<{
176
+ declare function buildSolanaClient(endpoint: string, wsEndpoint: string, payer: TransactionSigner | undefined, commitment: Commitment | undefined): _solana_kit.Client<{
145
177
  signTransactions: (input: _solana_kit.InstructionPlanInput | _solana_kit.TransactionPlanInput, config?: {
146
178
  abortSignal?: AbortSignal;
179
+ maxInstructionsPerTransaction?: number;
147
180
  }) => Promise<_solana_kit.TransactionPlanResult<_solana_kit_plugin_rpc.RpcSignContext>>;
148
181
  payer: TransactionSigner;
149
182
  rpc: _solana_kit.Rpc<_solana_kit.RequestAirdropApi & _solana_kit.GetAccountInfoApi & _solana_kit.GetAgGenesisCertApi & _solana_kit.GetBalanceApi & _solana_kit.GetBlockApi & _solana_kit.GetBlockCommitmentApi & _solana_kit.GetBlockHeightApi & _solana_kit.GetBlockProductionApi & _solana_kit.GetBlocksApi & _solana_kit.GetBlocksWithLimitApi & _solana_kit.GetBlockTimeApi & _solana_kit.GetClusterNodesApi & _solana_kit.GetEpochInfoApi & _solana_kit.GetEpochScheduleApi & _solana_kit.GetFeeForMessageApi & _solana_kit.GetFirstAvailableBlockApi & _solana_kit.GetGenesisHashApi & _solana_kit.GetHealthApi & _solana_kit.GetHighestSnapshotSlotApi & _solana_kit.GetIdentityApi & _solana_kit.GetInflationGovernorApi & _solana_kit.GetInflationRateApi & _solana_kit.GetInflationRewardApi & _solana_kit.GetLargestAccountsApi & _solana_kit.GetLatestBlockhashApi & _solana_kit.GetLeaderScheduleApi & _solana_kit.GetMaxRetransmitSlotApi & _solana_kit.GetMaxShredInsertSlotApi & _solana_kit.GetMinimumBalanceForRentExemptionApi & _solana_kit.GetMultipleAccountsApi & _solana_kit.GetProgramAccountsApi & _solana_kit.GetRecentPerformanceSamplesApi & _solana_kit.GetRecentPrioritizationFeesApi & _solana_kit.GetSignaturesForAddressApi & _solana_kit.GetSignatureStatusesApi & _solana_kit.GetSlotApi & _solana_kit.GetSlotLeaderApi & _solana_kit.GetSlotLeadersApi & _solana_kit.GetStakeMinimumDelegationApi & _solana_kit.GetSupplyApi & _solana_kit.GetTokenAccountBalanceApi & _solana_kit.GetTokenAccountsByDelegateApi & _solana_kit.GetTokenAccountsByOwnerApi & _solana_kit.GetTokenLargestAccountsApi & _solana_kit.GetTokenSupplyApi & _solana_kit.GetTransactionApi & _solana_kit.GetTransactionCountApi & _solana_kit.GetTransactionsForAddressApi & _solana_kit.GetVersionApi & _solana_kit.GetVoteAccountsApi & _solana_kit.IsBlockhashValidApi & _solana_kit.MinimumLedgerSlotApi & _solana_kit.SendTransactionApi & _solana_kit.SimulateTransactionApi>;
@@ -152,9 +185,11 @@ declare function buildSolanaClient(endpoint: string, wsEndpoint: string, payer:
152
185
  transactionPlanner: _solana_kit.TransactionPlanner;
153
186
  planTransaction: (input: _solana_kit.InstructionPlanInput, config?: {
154
187
  abortSignal?: AbortSignal;
188
+ maxInstructionsPerTransaction?: number;
155
189
  }) => Promise<_solana_kit.SingleTransactionPlan["message"]>;
156
190
  planTransactions: (input: _solana_kit.InstructionPlanInput, config?: {
157
191
  abortSignal?: AbortSignal;
192
+ maxInstructionsPerTransaction?: number;
158
193
  }) => Promise<_solana_kit.TransactionPlan>;
159
194
  signTransaction: (input: _solana_kit.InstructionPlanInput | (_solana_kit.TransactionMessage & _solana_kit.TransactionMessageWithFeePayer<string>) | Readonly<{
160
195
  kind: "single";
@@ -162,6 +197,7 @@ declare function buildSolanaClient(endpoint: string, wsEndpoint: string, payer:
162
197
  planType: "transactionPlan";
163
198
  }>, config?: {
164
199
  abortSignal?: AbortSignal;
200
+ maxInstructionsPerTransaction?: number;
165
201
  }) => Promise<_solana_kit.SuccessfulSingleTransactionPlanResult<_solana_kit_plugin_rpc.RpcSignContext>>;
166
202
  transactionPlanExecutor: _solana_kit.TransactionPlanExecutor<_solana_kit_plugin_rpc.RpcSendContext>;
167
203
  sendTransaction: (input: _solana_kit.InstructionPlanInput | (_solana_kit.TransactionMessage & _solana_kit.TransactionMessageWithFeePayer<string>) | Readonly<{
@@ -170,9 +206,11 @@ declare function buildSolanaClient(endpoint: string, wsEndpoint: string, payer:
170
206
  planType: "transactionPlan";
171
207
  }>, config?: {
172
208
  abortSignal?: AbortSignal;
209
+ maxInstructionsPerTransaction?: number;
173
210
  }) => Promise<_solana_kit.SuccessfulSingleTransactionPlanResult<_solana_kit_plugin_rpc.RpcSendContext>>;
174
211
  sendTransactions: (input: _solana_kit.InstructionPlanInput | _solana_kit.TransactionPlanInput, config?: {
175
212
  abortSignal?: AbortSignal;
213
+ maxInstructionsPerTransaction?: number;
176
214
  }) => Promise<_solana_kit.TransactionPlanResult<_solana_kit_plugin_rpc.RpcSendContext>>;
177
215
  airdrop: (address: Address, amount: _solana_kit.Lamports, abortSignal?: AbortSignal) => Promise<_solana_kit.Signature | undefined>;
178
216
  }>;
@@ -1,8 +1,42 @@
1
1
  import { StandardEvents, StandardDisconnect, StandardConnect } from '@wallet-standard/features';
2
2
  import { SolanaSignTransaction, SolanaSignIn, SolanaSignAndSendTransaction, SolanaSignMessage } from '@solana/wallet-standard-features';
3
- import bs58 from 'bs58';
3
+ import { isSignatureBytes, verifySignature, getBase58Decoder } from '@solana/kit';
4
+ import { createSolanaError } from '../errors.mjs';
4
5
  import { getWallets } from '@wallet-standard/app';
5
6
 
7
+ async function assertWalletSignature(publicKey, signedMessage, signature, label) {
8
+ let key;
9
+ try {
10
+ key = await globalThis.crypto.subtle.importKey(
11
+ "raw",
12
+ publicKey,
13
+ "Ed25519",
14
+ true,
15
+ ["verify"]
16
+ );
17
+ } catch {
18
+ return;
19
+ }
20
+ if (!isSignatureBytes(signature)) {
21
+ throw createSolanaError(
22
+ "INVALID_SIGNATURE",
23
+ `The wallet returned a malformed ${label} signature.`
24
+ );
25
+ }
26
+ let valid;
27
+ try {
28
+ valid = await verifySignature(key, signature, signedMessage);
29
+ } catch {
30
+ return;
31
+ }
32
+ if (!valid) {
33
+ throw createSolanaError(
34
+ "INVALID_SIGNATURE",
35
+ `The wallet returned an invalid ${label} signature.`
36
+ );
37
+ }
38
+ }
39
+
6
40
  const SOLANA_CHAINS = [
7
41
  "solana:mainnet",
8
42
  "solana:testnet",
@@ -42,16 +76,20 @@ function adaptSolanaStandardWallet(walletInfo, options = {}) {
42
76
  let disconnecting = false;
43
77
  let manuallyDisconnected = false;
44
78
  const eventsFeature = wallet.features[StandardEvents];
45
- eventsFeature?.on("change", (properties) => {
79
+ const unsubscribeChange = eventsFeature?.on("change", (properties) => {
46
80
  if (properties.accounts) {
47
81
  accounts = properties.accounts;
48
82
  account = allowAccountUpdates && !manuallyDisconnected ? getSolanaAccount(accounts, options.chain) : void 0;
49
83
  options.onChange?.();
50
84
  }
51
85
  });
86
+ function dispose() {
87
+ unsubscribeChange?.();
88
+ }
52
89
  return {
53
90
  platform: walletInfo.platform,
54
91
  source: walletInfo.source,
92
+ dispose,
55
93
  get publicKey() {
56
94
  return account ? account.address : null;
57
95
  },
@@ -110,7 +148,14 @@ function adaptSolanaStandardWallet(walletInfo, options = {}) {
110
148
  if (!result) {
111
149
  throw new Error("Solana wallet did not return a sign-in result");
112
150
  }
113
- return toSolanaSignInResult(result);
151
+ const signInResult = toSolanaSignInResult(result);
152
+ await assertWalletSignature(
153
+ signInResult.account.publicKey,
154
+ signInResult.signedMessage,
155
+ signInResult.signature,
156
+ "sign-in"
157
+ );
158
+ return signInResult;
114
159
  } : void 0,
115
160
  signMessage: hasSignMessage(wallet) ? async (message) => {
116
161
  const activeAccount = getActiveAccount(account);
@@ -121,6 +166,12 @@ function adaptSolanaStandardWallet(walletInfo, options = {}) {
121
166
  if (!result) {
122
167
  throw new Error("Solana wallet did not return a message signature");
123
168
  }
169
+ await assertWalletSignature(
170
+ activeAccount.publicKey,
171
+ result.signedMessage,
172
+ result.signature,
173
+ "message"
174
+ );
124
175
  return result;
125
176
  } : void 0,
126
177
  signTransaction: hasSignTransaction(wallet) ? async (transaction) => {
@@ -158,7 +209,7 @@ function adaptSolanaStandardWallet(walletInfo, options = {}) {
158
209
  if (!result) {
159
210
  throw new Error("Solana wallet did not return a transaction signature");
160
211
  }
161
- return { signature: bs58.encode(result.signature) };
212
+ return { signature: getBase58Decoder().decode(result.signature) };
162
213
  } : void 0,
163
214
  signAndSendTransactions: hasSignAndSendTransaction(wallet) ? async (transactions, sendOptions) => {
164
215
  const activeAccount = getActiveAccount(account);
@@ -179,7 +230,7 @@ function adaptSolanaStandardWallet(walletInfo, options = {}) {
179
230
  if (!result) {
180
231
  throw new Error("Solana wallet did not return a transaction signature");
181
232
  }
182
- return bs58.encode(result.signature);
233
+ return getBase58Decoder().decode(result.signature);
183
234
  });
184
235
  } : void 0
185
236
  };
package/dist/timeout.d.ts CHANGED
@@ -1,4 +1,25 @@
1
- declare function withTimeout<T>(promise: Promise<T>, timeoutMs: number | undefined, createError: () => unknown): Promise<T>;
1
+ import { SolanaErrorCode } from './errors.js';
2
+
3
+ interface WithTimeoutOptions {
4
+ /**
5
+ * Rejects without racing when the caller's own `AbortSignal` fires first.
6
+ * An aborted wait rejects with the signal's reason (an `AbortError` by
7
+ * default) rather than the timeout error.
8
+ */
9
+ signal?: AbortSignal;
10
+ /**
11
+ * Error code for the timeout rejection when `createError` is not given.
12
+ * Defaults to `"TRANSACTION_TIMEOUT"`.
13
+ */
14
+ code?: SolanaErrorCode;
15
+ /**
16
+ * Message for the timeout rejection when `createError` is not given.
17
+ */
18
+ message?: string;
19
+ }
20
+ declare function withTimeout<T>(promise: Promise<T>, timeoutMs: number | undefined, createError?: () => unknown, options?: WithTimeoutOptions): Promise<T>;
21
+ declare function withTimeout<T>(promise: Promise<T>, timeoutMs: number | undefined, options?: WithTimeoutOptions): Promise<T>;
2
22
  declare function withSolanaTimeout<T>(promise: Promise<T>, timeoutMs: number | undefined, message: string): Promise<T>;
3
23
 
4
24
  export { withSolanaTimeout, withTimeout };
25
+ export type { WithTimeoutOptions };
package/dist/timeout.mjs CHANGED
@@ -1,28 +1,64 @@
1
1
  import { createSolanaError } from './errors.mjs';
2
2
 
3
- function withTimeout(promise, timeoutMs, createError) {
3
+ function withTimeout(promise, timeoutMs, createErrorOrOptions, maybeOptions) {
4
+ const createError = typeof createErrorOrOptions === "function" ? createErrorOrOptions : void 0;
5
+ const {
6
+ signal,
7
+ code = "TRANSACTION_TIMEOUT",
8
+ message
9
+ } = (typeof createErrorOrOptions === "function" ? maybeOptions : createErrorOrOptions) ?? {};
4
10
  if (timeoutMs === void 0) {
5
- return promise;
11
+ return signal ? raceWithSignal(promise, signal) : promise;
6
12
  }
13
+ if (typeof timeoutMs !== "number" || Number.isNaN(timeoutMs) || timeoutMs < 0) {
14
+ throw createSolanaError(
15
+ "INVALID_INPUT",
16
+ `Invalid timeout: expected a non-negative number of milliseconds, received ${String(timeoutMs)}`
17
+ );
18
+ }
19
+ signal?.throwIfAborted();
7
20
  let timeoutId;
8
- return Promise.race([
9
- promise,
10
- // ponytail: the try/catch looks redundant but is not. `createError()` runs
11
- // in the `setTimeout` callback, not the executor body, so a throwing
12
- // factory would be an uncaught exception rather than a rejection.
13
- new Promise((_, reject) => {
14
- timeoutId = setTimeout(() => {
15
- try {
16
- reject(createError());
17
- } catch (error) {
18
- reject(error);
19
- }
20
- }, timeoutMs);
21
- })
22
- ]).finally(() => {
21
+ let onAbort;
22
+ const timeoutPromise = new Promise((_, reject) => {
23
+ timeoutId = setTimeout(() => {
24
+ try {
25
+ reject(createError?.() ?? createSolanaError(code, message ?? "Operation timed out"));
26
+ } catch (error) {
27
+ reject(error);
28
+ }
29
+ }, timeoutMs);
30
+ if (signal) {
31
+ onAbort = () => reject(signal.reason);
32
+ signal.addEventListener("abort", onAbort, { once: true });
33
+ }
34
+ });
35
+ const raced = signal ? raceWithSignal(promise, signal) : promise;
36
+ return Promise.race([raced, timeoutPromise]).finally(() => {
23
37
  if (timeoutId) {
24
38
  clearTimeout(timeoutId);
25
39
  }
40
+ if (signal && onAbort) {
41
+ signal.removeEventListener("abort", onAbort);
42
+ }
43
+ });
44
+ }
45
+ function raceWithSignal(promise, signal) {
46
+ if (signal.aborted) {
47
+ return Promise.reject(signal.reason);
48
+ }
49
+ return new Promise((resolve, reject) => {
50
+ const onAbort = () => reject(signal.reason);
51
+ signal.addEventListener("abort", onAbort, { once: true });
52
+ promise.then(
53
+ (value) => {
54
+ signal.removeEventListener("abort", onAbort);
55
+ resolve(value);
56
+ },
57
+ (cause) => {
58
+ signal.removeEventListener("abort", onAbort);
59
+ reject(cause);
60
+ }
61
+ );
26
62
  });
27
63
  }
28
64
  function withSolanaTimeout(promise, timeoutMs, message) {
@@ -1,5 +1,5 @@
1
1
  import { Address, Commitment } from '@solana/kit';
2
- import { b as SolanaClient } from './shared/core.C9vxHRXl.js';
2
+ import { b as SolanaClient } from './shared/core.BhF1J_tt.js';
3
3
  import '@solana/kit-plugin-rpc';
4
4
 
5
5
  interface TokenAccountInfo {
@@ -1,5 +1,5 @@
1
- import { address } from '@solana/kit';
2
- import { normalizeSolanaError } from './errors.mjs';
1
+ import { address, isAddress } from '@solana/kit';
2
+ import { normalizeSolanaError, createSolanaError } from './errors.mjs';
3
3
 
4
4
  const TOKEN_PROGRAM_ADDRESS = address("TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA");
5
5
  const TOKEN_2022_PROGRAM_ADDRESS = address("TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb");
@@ -54,6 +54,13 @@ function tokenAccountInfoFromParsed(address2, data) {
54
54
  if (!parsed) {
55
55
  throw new Error(`Token account ${address2} has no parsed data`);
56
56
  }
57
+ const mismatches = validateParsedTokenAccount(parsed);
58
+ if (mismatches.length > 0) {
59
+ throw createSolanaError(
60
+ "RPC_FAILURE",
61
+ `Token account ${address2} returned malformed data (${mismatches.join(", ")})`
62
+ );
63
+ }
57
64
  return {
58
65
  address: address2,
59
66
  mint: parsed.mint,
@@ -64,5 +71,27 @@ function tokenAccountInfoFromParsed(address2, data) {
64
71
  isNative: parsed.isNative
65
72
  };
66
73
  }
74
+ function validateParsedTokenAccount(parsed) {
75
+ const mismatches = [];
76
+ if (typeof parsed.mint !== "string" || !isAddress(parsed.mint)) {
77
+ mismatches.push("mint");
78
+ }
79
+ if (typeof parsed.owner !== "string" || !isAddress(parsed.owner)) {
80
+ mismatches.push("owner");
81
+ }
82
+ if (typeof parsed.tokenAmount?.amount !== "string" || !/^\d+$/.test(parsed.tokenAmount.amount)) {
83
+ mismatches.push("tokenAmount.amount");
84
+ }
85
+ if (typeof parsed.tokenAmount?.decimals !== "number" || !Number.isInteger(parsed.tokenAmount.decimals) || parsed.tokenAmount.decimals < 0) {
86
+ mismatches.push("tokenAmount.decimals");
87
+ }
88
+ if (parsed.state !== "frozen" && parsed.state !== "initialized" && parsed.state !== "uninitialized") {
89
+ mismatches.push("state");
90
+ }
91
+ if (typeof parsed.isNative !== "boolean") {
92
+ mismatches.push("isNative");
93
+ }
94
+ return mismatches;
95
+ }
67
96
 
68
97
  export { getTokenAccount, getTokenAccountsByOwner, getTokenBalance };
@@ -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.C9vxHRXl.js';
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';
3
3
  import '@solana/kit-plugin-rpc';
4
4
 
5
5
  /**
@@ -12,6 +12,14 @@ declare function signAndSendTransaction(client: SolanaClient, wallet: SolanaWall
12
12
  /**
13
13
  * Confirm a transaction signature against the kit RPC until it reaches the
14
14
  * requested commitment.
15
+ *
16
+ * Kit's `sendAndConfirmTransaction` cannot back this: it needs the whole
17
+ * transaction plus a `rpcSubscriptions` websocket and sends the transaction
18
+ * itself, whereas this confirms a signature a wallet already submitted.
19
+ *
20
+ * Kit's `getSignatureStatuses` takes no commitment parameter — the commitment
21
+ * check below is client-side. A missing status means the signature is unknown
22
+ * (not found), distinct from a failed transaction or an expired wait.
15
23
  */
16
24
  declare function confirmTransactionSignature(client: SolanaClient, signature: Signature, options?: ConfirmTransactionOptions): Promise<TransactionConfirmation>;
17
25
 
@@ -31,17 +31,36 @@ async function signAndSendTransaction(client, wallet, transaction, options) {
31
31
  async function confirmTransactionSignature(client, signature, options = {}) {
32
32
  const commitment = options.commitment ?? DEFAULT_CONFIRMATION_COMMITMENT;
33
33
  const timeoutMs = options.timeoutMs ?? DEFAULT_CONFIRMATION_TIMEOUT_MS;
34
- let elapsedMs = 0;
34
+ const pollIntervalMs = options.pollIntervalMs ?? SIGNATURE_STATUS_POLL_INTERVAL_MS;
35
+ const { abortSignal, searchTransactionHistory = false } = options;
36
+ if (typeof timeoutMs !== "number" || Number.isNaN(timeoutMs) || timeoutMs < 0) {
37
+ throw createSolanaError(
38
+ "INVALID_INPUT",
39
+ `Invalid confirmation timeout: expected a non-negative number of milliseconds, received ${String(timeoutMs)}`
40
+ );
41
+ }
42
+ if (typeof pollIntervalMs !== "number" || Number.isNaN(pollIntervalMs) || pollIntervalMs <= 0) {
43
+ throw createSolanaError(
44
+ "INVALID_INPUT",
45
+ `Invalid confirmation poll interval: expected a positive number of milliseconds, received ${String(pollIntervalMs)}`
46
+ );
47
+ }
48
+ abortSignal?.throwIfAborted();
49
+ const deadline = Date.now() + timeoutMs;
50
+ let sawStatus = false;
35
51
  try {
36
- while (elapsedMs < timeoutMs) {
37
- const response = await client.rpc.getSignatureStatuses([signature]).send();
52
+ while (Date.now() < deadline) {
53
+ abortSignal?.throwIfAborted();
54
+ const response = await client.rpc.getSignatureStatuses([signature], { searchTransactionHistory }).send({ abortSignal });
38
55
  const [status] = response.value;
39
56
  if (!status) {
40
- throw new Error(`Transaction ${signature} was not found.`);
57
+ await wait(pollIntervalMs, abortSignal);
58
+ continue;
41
59
  }
60
+ sawStatus = true;
42
61
  if (status.err) {
43
62
  throw createSolanaError(
44
- "RPC_FAILURE",
63
+ "TRANSACTION_FAILED",
45
64
  `Transaction ${signature} failed to reach ${commitment} commitment.`,
46
65
  { cause: status.err }
47
66
  );
@@ -49,14 +68,19 @@ async function confirmTransactionSignature(client, signature, options = {}) {
49
68
  if (hasReachedCommitment(status.confirmationStatus, commitment)) {
50
69
  return { signature, commitment, status };
51
70
  }
52
- await wait(SIGNATURE_STATUS_POLL_INTERVAL_MS);
53
- elapsedMs += SIGNATURE_STATUS_POLL_INTERVAL_MS;
71
+ await wait(pollIntervalMs, abortSignal);
72
+ }
73
+ if (!sawStatus) {
74
+ throw createSolanaError("TRANSACTION_NOT_FOUND", `Transaction ${signature} was not found.`);
54
75
  }
55
76
  throw createSolanaError(
56
77
  "TRANSACTION_TIMEOUT",
57
78
  `Timed out waiting for transaction ${signature} to reach ${commitment} commitment.`
58
79
  );
59
80
  } catch (cause) {
81
+ if (isAbortErrorLike(cause)) {
82
+ throw cause;
83
+ }
60
84
  throw normalizeSolanaError(cause, "RPC_FAILURE");
61
85
  }
62
86
  }
@@ -69,10 +93,25 @@ function hasReachedCommitment(actualStatus, targetCommitment) {
69
93
  }
70
94
  return (COMMITMENT_RANK[actualStatus] ?? 0) >= (COMMITMENT_RANK[targetCommitment] ?? 0);
71
95
  }
72
- function wait(ms) {
73
- return new Promise((resolve) => {
74
- setTimeout(resolve, ms);
96
+ function wait(ms, signal) {
97
+ if (ms <= 0) {
98
+ signal?.throwIfAborted();
99
+ return Promise.resolve();
100
+ }
101
+ return new Promise((resolve, reject) => {
102
+ const timeoutId = setTimeout(() => {
103
+ signal?.removeEventListener("abort", onAbort);
104
+ resolve();
105
+ }, ms);
106
+ function onAbort() {
107
+ clearTimeout(timeoutId);
108
+ reject(signal.reason);
109
+ }
110
+ signal?.addEventListener("abort", onAbort, { once: true });
75
111
  });
76
112
  }
113
+ function isAbortErrorLike(cause) {
114
+ return typeof cause === "object" && cause !== null && cause.name === "AbortError";
115
+ }
77
116
 
78
117
  export { confirmTransactionSignature, 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.C9vxHRXl.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 SolanaWalletInfo, T as TransactionConfirmation, m as TransactionStatus } from './shared/core.BhF1J_tt.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, l as SolanaWalletInfo, k as SolanaWallet, c as SolanaCluster } from './shared/core.C9vxHRXl.js';
2
+ import { a as SolanaChain, k as SolanaWallet, l as SolanaWalletInfo, c as SolanaCluster } from './shared/core.BhF1J_tt.js';
3
3
  import '@solana/kit-plugin-rpc';
4
4
  import '@solana/kit';
5
5
 
@@ -8,7 +8,15 @@ interface AdaptSolanaWalletOptions {
8
8
  account?: WalletAccount;
9
9
  onChange?: () => void;
10
10
  }
11
- declare function adaptSolanaStandardWallet(walletInfo: SolanaWalletInfo, options?: AdaptSolanaWalletOptions): SolanaWallet;
11
+ /**
12
+ * A Wallet Standard wallet adapted to {@link SolanaWallet}. `dispose` is
13
+ * always present here (optional on the base interface, where plain
14
+ * caller-supplied wallets typically omit it).
15
+ */
16
+ interface AdaptedSolanaWallet extends SolanaWallet {
17
+ dispose(): void;
18
+ }
19
+ declare function adaptSolanaStandardWallet(walletInfo: SolanaWalletInfo, options?: AdaptSolanaWalletOptions): AdaptedSolanaWallet;
12
20
 
13
21
  declare const SOLANA_CHAINS: readonly SolanaChain[];
14
22
  declare function getSolanaChain(cluster: SolanaCluster): SolanaChain;
@@ -19,4 +27,4 @@ declare function getRegisteredSolanaWallets(): SolanaWalletInfo[];
19
27
  declare function subscribeSolanaWallets(listener: () => void): () => void;
20
28
 
21
29
  export { SOLANA_CHAINS, SOLANA_MOBILE_WALLET_ADAPTER_WALLET_NAME, adaptSolanaStandardWallet, getRegisteredSolanaWallets, getSolanaChain, isSolanaStandardWallet, subscribeSolanaWallets };
22
- export type { AdaptSolanaWalletOptions };
30
+ 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.DaUlAmcN.mjs';
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';
package/dist/wallet.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { k as SolanaWallet } from './shared/core.C9vxHRXl.js';
1
+ import { k as SolanaWallet } from './shared/core.BhF1J_tt.js';
2
2
  import { SolanaError } from './errors.js';
3
3
  import '@solana/kit-plugin-rpc';
4
4
  import '@solana/kit';