@solana/react-hooks 0.3.0 → 0.5.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
@@ -285,6 +285,10 @@ Wrap a subtree with `<SolanaQueryProvider>` and call hooks like `useLatestBlockh
285
285
  Suspense later? Pass `suspense` to `SolanaQueryProvider` and wrap just the section that should pause in a
286
286
  local `<Suspense>` boundary—no hook changes required:
287
287
 
288
+ The query provider ships SWR v2-aligned defaults: `revalidateOnFocus`/`revalidateOnReconnect`/`revalidateIfStale`
289
+ are `true`, `dedupingInterval` is `2000`, and `focusThrottleInterval` is `5000`. Override them per-provider via the
290
+ `query.config` prop or per-hook via the `swr` option.
291
+
288
292
  ```tsx
289
293
  import { SolanaQueryProvider, useBalance } from '@solana/react-hooks';
290
294
  import { Suspense } from 'react';
@@ -394,12 +398,15 @@ function SimulationLogs({ transaction }) {
394
398
  ## Going further
395
399
 
396
400
  - Wallet connection UI: `useWalletConnection` gives you the current wallet, connect/disconnect
397
- helpers, and the connector list (from `client.connectors` when provided, with optional Wallet
398
- Standard discovery fallback). Pair it with your preferred UI, or `WalletConnectionManager` for a
399
- simple modal state helper.
401
+ helpers, and the connector list from `client.connectors` (or an explicit override). Pair it with
402
+ your preferred UI, or `WalletConnectionManager` for a simple modal state helper.
400
403
  - Signing helpers: the wallet session returned by `useWallet` exposes `signMessage`,
401
404
  `signTransaction`, and `sendTransaction` when supported by the connector. These connector methods
402
405
  replace the deprecated Wallet Standard shims.
406
+ - Query hooks keep SWR options under `swr` for consistency (for example,
407
+ `useProgramAccounts(address, { swr: { revalidateOnFocus: false } })`) and expose typed parameter
408
+ and return aliases across all hooks.
409
+ - Type helpers: use `UseHookNameParameters` / `UseHookNameReturnType` for public hooks.
403
410
  - Looking for examples? See `examples/react-hooks` for a ready-to-run, tabbed playground that wires
404
411
  the provider, hooks, and mock UIs together across wallet/state, transaction, and query demos.
405
412
 
@@ -62,11 +62,11 @@ var QUERY_NAMESPACE = "@solana/react-hooks";
62
62
  function useSolanaRpcQuery(scope, args, fetcher, options = {}) {
63
63
  const client = useSolanaClient();
64
64
  const cluster = useClientStore((state) => state.cluster);
65
- const { disabled = false, ...restOptions } = options;
65
+ const { disabled = false, swr } = options;
66
66
  const providerSuspensePreference = useQuerySuspensePreference();
67
67
  const suspenseEnabled = !disabled && Boolean(providerSuspensePreference);
68
68
  const swrOptions = {
69
- ...restOptions,
69
+ ...swr ?? {},
70
70
  suspense: suspenseEnabled
71
71
  };
72
72
  const key = react.useMemo(() => {
@@ -75,59 +75,105 @@ function useSolanaRpcQuery(scope, args, fetcher, options = {}) {
75
75
  }
76
76
  return [QUERY_NAMESPACE, scope, cluster.endpoint, cluster.commitment, ...args];
77
77
  }, [cluster.commitment, cluster.endpoint, args, scope, disabled]);
78
- const swr = useSWR__default.default(key, () => fetcher(client), swrOptions);
78
+ const swrResponse = useSWR__default.default(key, () => fetcher(client), swrOptions);
79
79
  const [dataUpdatedAt, setDataUpdatedAt] = react.useState(
80
- () => swr.data !== void 0 ? Date.now() : void 0
80
+ () => swrResponse.data !== void 0 ? Date.now() : void 0
81
81
  );
82
82
  react.useEffect(() => {
83
- if (swr.data !== void 0) {
83
+ if (swrResponse.data !== void 0) {
84
84
  setDataUpdatedAt(Date.now());
85
85
  }
86
- }, [swr.data]);
87
- const status = swr.error ? "error" : swr.isLoading ? "loading" : swr.data !== void 0 ? "success" : "idle";
88
- const refresh = react.useCallback(() => swr.mutate(void 0, { revalidate: true }), [swr.mutate]);
86
+ }, [swrResponse.data]);
87
+ const status = swrResponse.error ? "error" : swrResponse.isLoading ? "loading" : swrResponse.data !== void 0 ? "success" : "idle";
88
+ const refresh = react.useCallback(() => swrResponse.mutate(void 0, { revalidate: true }), [swrResponse.mutate]);
89
89
  return {
90
- data: swr.data,
90
+ data: swrResponse.data,
91
91
  dataUpdatedAt,
92
- error: swr.error ?? null,
92
+ error: swrResponse.error ?? null,
93
93
  isError: status === "error",
94
- isLoading: swr.isLoading,
94
+ isLoading: swrResponse.isLoading,
95
95
  isSuccess: status === "success",
96
- isValidating: swr.isValidating,
97
- mutate: swr.mutate,
96
+ isValidating: swrResponse.isValidating,
97
+ mutate: swrResponse.mutate,
98
98
  refresh,
99
99
  status
100
100
  };
101
101
  }
102
102
  __name(useSolanaRpcQuery, "useSolanaRpcQuery");
103
+ function getLatestBlockhashKey(params = {}) {
104
+ const { commitment = null, minContextSlot = null } = params;
105
+ return ["latestBlockhash", commitment, normalizeBigint(minContextSlot)];
106
+ }
107
+ __name(getLatestBlockhashKey, "getLatestBlockhashKey");
108
+ function getProgramAccountsKey(params = {}) {
109
+ const { programAddress, config } = params;
110
+ const address = programAddress ? client.toAddress(programAddress) : void 0;
111
+ const addressKey = address ? client.toAddressString(address) : null;
112
+ const configKey = client.stableStringify(config ?? null);
113
+ return ["programAccounts", addressKey, configKey];
114
+ }
115
+ __name(getProgramAccountsKey, "getProgramAccountsKey");
116
+ function getSimulateTransactionKey(params = {}) {
117
+ const { transaction, config } = params;
118
+ const wire = transaction ? normalizeWire(transaction) : null;
119
+ const configKey = client.stableStringify(config ?? null);
120
+ return ["simulateTransaction", wire, configKey];
121
+ }
122
+ __name(getSimulateTransactionKey, "getSimulateTransactionKey");
123
+ function getSignatureStatusKey(params = {}) {
124
+ const { config, signature } = params;
125
+ const signatureKey = signature?.toString() ?? null;
126
+ const configKey = JSON.stringify(config ?? null);
127
+ return ["signatureStatus", signatureKey, configKey];
128
+ }
129
+ __name(getSignatureStatusKey, "getSignatureStatusKey");
130
+ function normalizeBigint(value) {
131
+ if (value === void 0 || value === null) return null;
132
+ return typeof value === "bigint" ? value : BigInt(Math.floor(value));
133
+ }
134
+ __name(normalizeBigint, "normalizeBigint");
135
+ function normalizeWire(input) {
136
+ if (!input) return null;
137
+ if (typeof input === "string") {
138
+ return input;
139
+ }
140
+ return kit.getBase64EncodedWireTransaction(input);
141
+ }
142
+ __name(normalizeWire, "normalizeWire");
143
+
144
+ // src/queryHooks.ts
103
145
  var DEFAULT_BLOCKHASH_REFRESH_INTERVAL = 3e4;
104
- function useLatestBlockhash(options) {
105
- const { commitment, minContextSlot, refreshInterval = DEFAULT_BLOCKHASH_REFRESH_INTERVAL, ...rest } = options ?? {};
106
- const normalizedMinContextSlot = react.useMemo(() => {
107
- if (minContextSlot === void 0) {
108
- return void 0;
109
- }
110
- return typeof minContextSlot === "bigint" ? minContextSlot : BigInt(Math.floor(minContextSlot));
111
- }, [minContextSlot]);
112
- const keyArgs = react.useMemo(
113
- () => [commitment ?? null, normalizedMinContextSlot ?? null],
114
- [commitment, normalizedMinContextSlot]
115
- );
146
+ function useLatestBlockhash(options = {}) {
147
+ const {
148
+ commitment,
149
+ minContextSlot,
150
+ refreshInterval = DEFAULT_BLOCKHASH_REFRESH_INTERVAL,
151
+ disabled = false,
152
+ swr
153
+ } = options;
116
154
  const fetcher = react.useCallback(
117
155
  async (client) => {
118
156
  const fallbackCommitment = commitment ?? client.store.getState().cluster.commitment;
119
157
  const plan = client.runtime.rpc.getLatestBlockhash({
120
158
  commitment: fallbackCommitment,
121
- minContextSlot: normalizedMinContextSlot
159
+ minContextSlot: normalizeMinContextSlot(minContextSlot)
122
160
  });
123
161
  return plan.send({ abortSignal: AbortSignal.timeout(15e3) });
124
162
  },
125
- [commitment, normalizedMinContextSlot]
163
+ [commitment, minContextSlot]
164
+ );
165
+ const query = useSolanaRpcQuery(
166
+ "latestBlockhash",
167
+ getLatestBlockhashKey(options),
168
+ fetcher,
169
+ {
170
+ disabled,
171
+ swr: {
172
+ refreshInterval,
173
+ ...swr
174
+ }
175
+ }
126
176
  );
127
- const query = useSolanaRpcQuery("latestBlockhash", keyArgs, fetcher, {
128
- refreshInterval,
129
- ...rest
130
- });
131
177
  return {
132
178
  ...query,
133
179
  blockhash: query.data?.value.blockhash ?? null,
@@ -137,31 +183,33 @@ function useLatestBlockhash(options) {
137
183
  }
138
184
  __name(useLatestBlockhash, "useLatestBlockhash");
139
185
  function useProgramAccounts(programAddress, options) {
140
- const { commitment, config, ...queryOptions } = options ?? {};
141
- const { disabled: disabledOption, ...restQueryOptions } = queryOptions;
142
- const address = react.useMemo(() => programAddress ? client.toAddress(programAddress) : void 0, [programAddress]);
143
- const addressKey = react.useMemo(() => address ? client.toAddressString(address) : null, [address]);
144
- const configKey = react.useMemo(() => client.stableStringify(config ?? null), [config]);
186
+ const { commitment, config, swr, disabled: disabledOption } = options ?? {};
145
187
  const fetcher = react.useCallback(
146
- async (client) => {
188
+ async (client$1) => {
189
+ const address = programAddress ? client.toAddress(programAddress) : void 0;
147
190
  if (!address) {
148
191
  throw new Error("Provide a program address before querying program accounts.");
149
192
  }
150
- const fallbackCommitment = commitment ?? config?.commitment ?? client.store.getState().cluster.commitment;
193
+ const fallbackCommitment = commitment ?? config?.commitment ?? client$1.store.getState().cluster.commitment;
151
194
  const mergedConfig = {
152
195
  ...config ?? {},
153
196
  commitment: fallbackCommitment
154
197
  };
155
- const plan = client.runtime.rpc.getProgramAccounts(address, mergedConfig);
198
+ const plan = client$1.runtime.rpc.getProgramAccounts(address, mergedConfig);
156
199
  return plan.send({ abortSignal: AbortSignal.timeout(2e4) });
157
200
  },
158
- [address, commitment, config]
201
+ [commitment, config, programAddress]
202
+ );
203
+ const disabled = disabledOption ?? !programAddress;
204
+ const query = useSolanaRpcQuery(
205
+ "programAccounts",
206
+ getProgramAccountsKey({ programAddress, config }),
207
+ fetcher,
208
+ {
209
+ disabled,
210
+ swr
211
+ }
159
212
  );
160
- const disabled = disabledOption ?? !address;
161
- const query = useSolanaRpcQuery("programAccounts", [addressKey, configKey], fetcher, {
162
- ...restQueryOptions,
163
- disabled
164
- });
165
213
  return {
166
214
  ...query,
167
215
  accounts: query.data ?? []
@@ -169,8 +217,7 @@ function useProgramAccounts(programAddress, options) {
169
217
  }
170
218
  __name(useProgramAccounts, "useProgramAccounts");
171
219
  function useSimulateTransaction(transaction, options) {
172
- const { commitment, config, refreshInterval, ...rest } = options ?? {};
173
- const { disabled: disabledOption, revalidateIfStale, revalidateOnFocus, ...queryOptions } = rest;
220
+ const { commitment, config, refreshInterval, disabled: disabledOption, swr } = options ?? {};
174
221
  const wire = react.useMemo(() => {
175
222
  if (!transaction) {
176
223
  return null;
@@ -180,7 +227,6 @@ function useSimulateTransaction(transaction, options) {
180
227
  }
181
228
  return kit.getBase64EncodedWireTransaction(transaction);
182
229
  }, [transaction]);
183
- const configKey = react.useMemo(() => client.stableStringify(config ?? null), [config]);
184
230
  const fetcher = react.useCallback(
185
231
  async (client) => {
186
232
  if (!wire) {
@@ -196,19 +242,31 @@ function useSimulateTransaction(transaction, options) {
196
242
  [commitment, config, wire]
197
243
  );
198
244
  const disabled = disabledOption ?? !wire;
199
- const query = useSolanaRpcQuery("simulateTransaction", [wire, configKey], fetcher, {
200
- ...queryOptions,
201
- refreshInterval,
202
- disabled,
203
- revalidateIfStale: revalidateIfStale ?? false,
204
- revalidateOnFocus: revalidateOnFocus ?? false
205
- });
245
+ const query = useSolanaRpcQuery(
246
+ "simulateTransaction",
247
+ getSimulateTransactionKey({ transaction, config }),
248
+ fetcher,
249
+ {
250
+ disabled,
251
+ swr: {
252
+ refreshInterval,
253
+ revalidateIfStale: false,
254
+ revalidateOnFocus: false,
255
+ ...swr
256
+ }
257
+ }
258
+ );
206
259
  return {
207
260
  ...query,
208
261
  logs: query.data?.value.logs ?? []
209
262
  };
210
263
  }
211
264
  __name(useSimulateTransaction, "useSimulateTransaction");
265
+ function normalizeMinContextSlot(minContextSlot) {
266
+ if (minContextSlot === void 0) return void 0;
267
+ return typeof minContextSlot === "bigint" ? minContextSlot : BigInt(Math.floor(minContextSlot));
268
+ }
269
+ __name(normalizeMinContextSlot, "normalizeMinContextSlot");
212
270
 
213
271
  // src/hooks.ts
214
272
  function createClusterSelector() {
@@ -358,10 +416,19 @@ function useSplToken(mint, options = {}) {
358
416
  }
359
417
  return helper.fetchBalance(owner, options.commitment);
360
418
  }, [helper, owner, options.commitment]);
361
- const { data, error, isLoading, isValidating, mutate } = useSWR__default.default(balanceKey, fetchBalance, {
362
- revalidateOnFocus: options.revalidateOnFocus ?? false,
363
- suspense
364
- });
419
+ const swrOptions = react.useMemo(
420
+ () => ({
421
+ revalidateOnFocus: options.revalidateOnFocus ?? false,
422
+ suspense,
423
+ ...options.swr ?? {}
424
+ }),
425
+ [options.revalidateOnFocus, options.swr, suspense]
426
+ );
427
+ const { data, error, isLoading, isValidating, mutate } = useSWR__default.default(
428
+ balanceKey,
429
+ fetchBalance,
430
+ swrOptions
431
+ );
365
432
  const sessionRef = react.useRef(session);
366
433
  react.useEffect(() => {
367
434
  sessionRef.current = session;
@@ -527,24 +594,6 @@ function useBalance(addressLike, options = {}) {
527
594
  );
528
595
  }
529
596
  __name(useBalance, "useBalance");
530
- function useWalletStandardConnectors(options) {
531
- const overrides = options?.overrides;
532
- const disabled = options?.disabled ?? false;
533
- const memoisedOptions = react.useMemo(() => overrides ? { overrides } : void 0, [overrides]);
534
- const [connectors, setConnectors] = react.useState(
535
- () => disabled ? [] : client.getWalletStandardConnectors(memoisedOptions ?? {})
536
- );
537
- react.useEffect(() => {
538
- if (disabled) return;
539
- setConnectors(client.getWalletStandardConnectors(memoisedOptions ?? {}));
540
- const unwatch = client.watchWalletStandardConnectors(setConnectors, memoisedOptions ?? {});
541
- return () => {
542
- unwatch();
543
- };
544
- }, [disabled, memoisedOptions]);
545
- return connectors;
546
- }
547
- __name(useWalletStandardConnectors, "useWalletStandardConnectors");
548
597
  function useTransactionPool(config = {}) {
549
598
  const initialInstructions = react.useMemo(
550
599
  () => config.instructions ?? [],
@@ -678,10 +727,9 @@ function useSendTransaction() {
678
727
  }
679
728
  __name(useSendTransaction, "useSendTransaction");
680
729
  function useSignatureStatus(signatureInput, options = {}) {
681
- const { config, ...queryOptions } = options;
730
+ const { config, disabled: disabledOption, swr } = options;
682
731
  const signature = react.useMemo(() => client.normalizeSignature(signatureInput), [signatureInput]);
683
732
  const signatureKey = signature?.toString() ?? null;
684
- const configKey = react.useMemo(() => JSON.stringify(config ?? null), [config]);
685
733
  const fetcher = react.useCallback(
686
734
  async (client$1) => {
687
735
  if (!signatureKey) {
@@ -696,14 +744,14 @@ function useSignatureStatus(signatureInput, options = {}) {
696
744
  },
697
745
  [config, signature, signatureKey]
698
746
  );
699
- const disabled = queryOptions.disabled ?? !signatureKey;
747
+ const disabled = disabledOption ?? !signatureKey;
700
748
  const query = useSolanaRpcQuery(
701
749
  "signatureStatus",
702
- [signatureKey, configKey],
750
+ getSignatureStatusKey({ signature: signatureInput, config }),
703
751
  fetcher,
704
752
  {
705
- ...queryOptions,
706
- disabled
753
+ disabled,
754
+ swr
707
755
  }
708
756
  );
709
757
  const confirmationStatus = client.deriveConfirmationStatus(query.data ?? null);
@@ -722,14 +770,17 @@ function useWaitForSignature(signatureInput, options = {}) {
722
770
  watchCommitment,
723
771
  ...signatureStatusOptions
724
772
  } = options;
725
- const { refreshInterval, ...restStatusOptions } = signatureStatusOptions;
773
+ const { swr, ...restStatusOptions } = signatureStatusOptions;
726
774
  const subscribeCommitment = watchCommitment ?? commitment;
727
775
  const client$1 = useSolanaClient();
728
776
  const normalizedSignature = react.useMemo(() => client.normalizeSignature(signatureInput), [signatureInput]);
729
777
  const disabled = disabledOption ?? !normalizedSignature;
730
778
  const statusQuery = useSignatureStatus(signatureInput, {
731
779
  ...restStatusOptions,
732
- refreshInterval: refreshInterval ?? 2e3,
780
+ swr: {
781
+ refreshInterval: 2e3,
782
+ ...swr
783
+ },
733
784
  disabled
734
785
  });
735
786
  const [subscriptionSettled, setSubscriptionSettled] = react.useState(false);
@@ -785,10 +836,11 @@ function useWaitForSignature(signatureInput, options = {}) {
785
836
  __name(useWaitForSignature, "useWaitForSignature");
786
837
  var createCache = /* @__PURE__ */ __name(() => /* @__PURE__ */ new Map(), "createCache");
787
838
  var DEFAULT_QUERY_CONFIG = Object.freeze({
788
- dedupingInterval: 1e3,
789
- focusThrottleInterval: 1e3,
839
+ dedupingInterval: 2e3,
840
+ focusThrottleInterval: 5e3,
790
841
  provider: /* @__PURE__ */ __name(() => createCache(), "provider"),
791
- revalidateOnFocus: false,
842
+ revalidateIfStale: true,
843
+ revalidateOnFocus: true,
792
844
  revalidateOnReconnect: true
793
845
  });
794
846
  function SolanaQueryProvider({
@@ -976,12 +1028,7 @@ function useWalletConnection(options = {}) {
976
1028
  const connectWallet = useConnectWallet();
977
1029
  const disconnectWallet = useDisconnectWallet();
978
1030
  const client = useSolanaClient();
979
- const shouldDiscover = !options.connectors && client.connectors.all.length === 0;
980
- const discovered = useWalletStandardConnectors({
981
- ...options.discoveryOptions,
982
- disabled: !shouldDiscover
983
- });
984
- const connectors = options.connectors ?? (client.connectors.all.length > 0 ? client.connectors.all : discovered);
1031
+ const connectors = options.connectors ?? client.connectors.all;
985
1032
  const connect = react.useCallback(
986
1033
  (connectorId, connectOptions) => connectWallet(connectorId, connectOptions),
987
1034
  [connectWallet]
@@ -989,6 +1036,7 @@ function useWalletConnection(options = {}) {
989
1036
  const disconnect = react.useCallback(() => disconnectWallet(), [disconnectWallet]);
990
1037
  const state = react.useMemo(() => {
991
1038
  const connectorId = "connectorId" in wallet ? wallet.connectorId : void 0;
1039
+ const currentConnector = connectorId ? connectors.find((connector) => connector.id === connectorId) : void 0;
992
1040
  const session = wallet.status === "connected" ? wallet.session : void 0;
993
1041
  const error = wallet.status === "error" ? wallet.error ?? null : null;
994
1042
  return {
@@ -997,6 +1045,7 @@ function useWalletConnection(options = {}) {
997
1045
  connecting: wallet.status === "connecting",
998
1046
  connectors,
999
1047
  connectorId,
1048
+ currentConnector,
1000
1049
  disconnect,
1001
1050
  error,
1002
1051
  status: wallet.status,
@@ -1006,8 +1055,8 @@ function useWalletConnection(options = {}) {
1006
1055
  return state;
1007
1056
  }
1008
1057
  __name(useWalletConnection, "useWalletConnection");
1009
- function WalletConnectionManager({ children, connectors, discoveryOptions }) {
1010
- const state = useWalletConnection({ connectors, discoveryOptions });
1058
+ function WalletConnectionManager({ children, connectors }) {
1059
+ const state = useWalletConnection({ connectors });
1011
1060
  return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: children(state) });
1012
1061
  }
1013
1062
  __name(WalletConnectionManager, "WalletConnectionManager");
@@ -1047,6 +1096,10 @@ exports.SolanaClientProvider = SolanaClientProvider;
1047
1096
  exports.SolanaProvider = SolanaProvider;
1048
1097
  exports.SolanaQueryProvider = SolanaQueryProvider;
1049
1098
  exports.WalletConnectionManager = WalletConnectionManager;
1099
+ exports.getLatestBlockhashKey = getLatestBlockhashKey;
1100
+ exports.getProgramAccountsKey = getProgramAccountsKey;
1101
+ exports.getSignatureStatusKey = getSignatureStatusKey;
1102
+ exports.getSimulateTransactionKey = getSimulateTransactionKey;
1050
1103
  exports.useAccount = useAccount;
1051
1104
  exports.useBalance = useBalance;
1052
1105
  exports.useClientStore = useClientStore;
@@ -1069,6 +1122,5 @@ exports.useWalletActions = useWalletActions;
1069
1122
  exports.useWalletConnection = useWalletConnection;
1070
1123
  exports.useWalletModalState = useWalletModalState;
1071
1124
  exports.useWalletSession = useWalletSession;
1072
- exports.useWalletStandardConnectors = useWalletStandardConnectors;
1073
1125
  //# sourceMappingURL=index.browser.cjs.map
1074
1126
  //# sourceMappingURL=index.browser.cjs.map