wagmi-extended 2.2.1 → 2.2.3

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
@@ -22,6 +22,7 @@ Whether you're building a DeFi platform, a governance system, or any blockchain
22
22
  - [Non-hook functions setup](#non-hook-functions-setup)
23
23
  - [Error handling](#error-handling)
24
24
  - [fetchTokenX](#fetchTokenX)
25
+ - [fetchDeploymentBlockX](#fetchDeploymentBlockX)
25
26
  - [Contributing](#contributing)
26
27
  - [Donations](#support--donations)
27
28
  - [License](#license)
@@ -365,14 +366,58 @@ const tokensData = await Promise.all(tokenAddresses.map((token) =>
365
366
 
366
367
  > **Note:** : if you did not setup configs (queryClient and wagmi config) you can call by passing client and config as params: `fetchTokenX(token, queryClient, wagmiConfig)`.
367
368
 
369
+ ### fetchDeploymentBlockX
370
+
371
+ The `fetchDeploymentBlockX` function finds and caches the earliest block where a contract was deployed
372
+ (i.e., the first block containing bytecode at a given address).
373
+
374
+ It uses an **exponential descent** followed by a **binary search**, making it optimal for locating deployment blocks with minimal RPC calls.
375
+
376
+ - **Exponential descent**: quickly finds a safe lower bound with no code.
377
+ - **Binary search**: efficiently pinpoints the exact deployment block in logarithmic steps.
378
+
379
+ #### Features
380
+
381
+ - **React Query caching**: results stored under the key
382
+ `["deploymentBlock", chainId, address.toLowerCase(), floor]`.
383
+ - **LocalStorage caching (browser only)**:
384
+ - On the **first run**, the block is computed and stored in both React Query and `localStorage`.
385
+ - On **subsequent runs**, the value is read from `localStorage` to fully prevent RPC refetches.
386
+ - **SSR-safe**: all localStorage access is gated by `typeof window !== 'undefined'`.
387
+ - **Opt-out**: pass `{ disableLocalStorage: true }` to bypass read/write of `localStorage`.
388
+
389
+ #### Example
390
+
391
+ ```ts
392
+ import { fetchDeploymentBlockX } from "wagmi-extended";
393
+
394
+ async function main() {
395
+ const deploymentBlock = await fetchDeploymentBlockX(
396
+ "0xYourContractAddress",
397
+ 0n
398
+ );
399
+
400
+ console.log("Contract was deployed at block:", deploymentBlock.toString());
401
+ }
402
+
403
+ main();
404
+ ```
405
+
406
+ Performance
407
+
408
+ Performs O(log N) RPC calls, where N is the distance between the latest block and the deployment block.
409
+
410
+ - Much more efficient than linear scanning.
411
+ - Automatically cached by React Query under the key:
412
+
368
413
  ## Contributing
369
414
 
370
415
  This project is open source and we welcome contributions from the community! If you have ideas, improvements, or bug fixes, please feel free to open pull requests or file issues. Your help makes this project better for everyone.
371
416
 
372
- - **Open Issues & PRs:**
417
+ - **Open Issues & PRs:**
373
418
  You can report bugs or request features by opening an issue on [GitHub Issues](https://github.com/WingsDevelopment/wagmi-extended/issues). Similarly, feel free to open a pull request (PR) with your changes.
374
419
 
375
- - **Star the Project:**
420
+ - **Star the Project:**
376
421
  If you like `wagmi-extended` or find it useful, please consider starring the repository on [GitHub](https://github.com/WingsDevelopment/wagmi-extended). It helps the project gain visibility and motivates further development.
377
422
 
378
423
  Thank you for your support and contributions!
@@ -381,10 +426,10 @@ Thank you for your support and contributions!
381
426
 
382
427
  If you enjoy this project and would like to support its ongoing development, please consider donating!
383
428
 
384
- - **Buy Me a Coffee:**
429
+ - **Buy Me a Coffee:**
385
430
  [buymeacoffee.com/srdjanr160N](https://buymeacoffee.com/srdjanr160N)
386
431
 
387
- - **Ethereum Donation Address:**
432
+ - **Ethereum Donation Address:**
388
433
  `0x410A11ed53a9a59094F24D2ae4ACbeF7f84955a1`
389
434
 
390
435
  Any donation, no matter how small, is greatly appreciated!
@@ -398,3 +443,7 @@ Anyone is free to copy, modify, publish, use, compile, sell, or distribute this
398
443
  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
399
444
 
400
445
  For more information, please refer to <http://unlicense.org/>
446
+
447
+ ```
448
+
449
+ ```
@@ -8,13 +8,21 @@ export declare const deploymentBlockKey: (chainId: number | undefined, address:
8
8
  *
9
9
  * Use with `queryClient.fetchQuery(...)`.
10
10
  *
11
- * @param address - Contract address to probe.
12
- * @param floor - Optional lower bound (inclusive) to speed up search. Defaults to `0n`.
13
- * @param wagmiConfig - Wagmi `Config` (optional; resolved via `ensureClientAndConfig` if omitted).
11
+ * @param address - Contract address to probe.
12
+ * @param floor - Optional lower bound (inclusive) to speed up search. Defaults to `0n`.
13
+ * @param wagmiConfig - Wagmi `Config` (optional; resolved via `ensureClientAndConfig` if omitted).
14
+ * @param options.disableLocalStorage - If `true`, skip reading/writing localStorage (default `false`).
14
15
  *
15
- * @returns A fully-configured query options object (key + fn + metadata).
16
+ * Local Storage behavior (SSR-safe):
17
+ * - If not disabled and a value exists in `localStorage`, the `queryFn` will
18
+ * return it immediately without performing RPC calls.
19
+ * - After an on-chain discovery, the result is written to `localStorage`
20
+ * (unless disabled). This pairs nicely with `staleTime: Infinity` to
21
+ * avoid future refetches.
16
22
  */
17
- export declare function getDeploymentBlockQueryOptionsX(address: Address, floor?: bigint, wagmiConfig?: Config): {
23
+ export declare function getDeploymentBlockQueryOptionsX(address: Address, floor?: bigint, wagmiConfig?: Config, options?: {
24
+ disableLocalStorage?: boolean;
25
+ }): {
18
26
  readonly staleTime: number;
19
27
  readonly meta: {
20
28
  readonly queryType: import("../../query-config/index.js").QueryType.MetaDataQuery;
@@ -31,24 +39,36 @@ export declare function getDeploymentBlockQueryOptionsX(address: Address, floor?
31
39
  *
32
40
  * #### Caching
33
41
  * - Query key: `["deploymentBlock", chainId, address.toLowerCase(), floor]`
34
- * - For long-lived results, we apply `queryConfig.metaDataQuery` (tweak as needed).
42
+ * - Long-lived results: `queryConfig.metaDataQuery` (e.g., `staleTime: Infinity`)
35
43
  *
36
- * #### Performance
37
- * - **O(log N)** `eth_getCode` calls, where `N` is the gap between the latest
38
- * block and the deployment block—optimal among comparison-based strategies.
44
+ * #### Local Storage (SSR-safe)
45
+ * - Before calling `fetchQuery`, we seed the Query Cache from `localStorage`
46
+ * (unless `disableLocalStorage` is true). When a cached value is present,
47
+ * `fetchQuery` will *not* execute the `queryFn`, fully preventing RPC refetches.
48
+ * - After on-chain discovery, the result is written back to `localStorage`
49
+ * (unless disabled).
39
50
  *
40
51
  * @example
41
52
  * ```ts
42
- * const block = await fetchDeploymentBlockX("0xContract...", 0n, queryClient, wagmiConfig);
53
+ * const block = await fetchDeploymentBlockX(
54
+ * "0xContract...",
55
+ * 0n,
56
+ * queryClient,
57
+ * wagmiConfig,
58
+ * { disableLocalStorage: false }
59
+ * );
43
60
  * ```
44
61
  *
45
- * @param address - Contract address to probe.
46
- * @param floor - Optional lower bound (inclusive) to speed up search. Defaults to `0n`.
47
- * @param queryClient - Optional TanStack `QueryClient`. If omitted, resolved by `ensureClientAndConfig`.
48
- * @param wagmiConfig - Optional Wagmi `Config`. If omitted, resolved by `ensureClientAndConfig`.
62
+ * @param address - Contract address to probe.
63
+ * @param floor - Optional lower bound (inclusive). Defaults to `0n`.
64
+ * @param queryClient - Optional TanStack `QueryClient`. If omitted, resolved by `ensureClientAndConfig`.
65
+ * @param wagmiConfig - Optional Wagmi `Config`. If omitted, resolved by `ensureClientAndConfig`.
66
+ * @param options.disableLocalStorage - If `true`, skip reading/writing localStorage (default `false`).
49
67
  *
50
68
  * @returns The earliest block number (bigint) where bytecode exists.
51
69
  *
52
70
  * @throws If the public client is missing or if no code is present at the latest block.
53
71
  */
54
- export declare function fetchDeploymentBlockX(address: Address, floor?: bigint, queryClient?: QueryClient, wagmiConfig?: Config): Promise<bigint>;
72
+ export declare function fetchDeploymentBlockX(address: Address, floor?: bigint, queryClient?: QueryClient, wagmiConfig?: Config, options?: {
73
+ disableLocalStorage?: boolean;
74
+ }): Promise<bigint>;
package/dist/index.cjs.js CHANGED
@@ -857,6 +857,33 @@ async function fetchERC4626DataX(vault, user, spender, queryClient, wagmiConfig)
857
857
 
858
858
  /** Reusable React Query key helper for deployment block lookups. */
859
859
  const deploymentBlockKey = (chainId, address, floor) => ["deploymentBlock", chainId, address?.toLowerCase(), floor];
860
+ /** Internal: SSR-safe localStorage guards */
861
+ const canUseBrowserStorage = typeof window !== "undefined" && typeof window.localStorage !== "undefined";
862
+ /** Internal: build a stable localStorage key */
863
+ const lsKeyForDeploymentBlock = (chainId, address, floor) => `wagmi-extended:deploymentBlock:${chainId}:${address.toLowerCase()}:${floor.toString()}`;
864
+ /** Internal: read bigint from localStorage (SSR safe) */
865
+ function readDeploymentBlockFromLS(chainId, address, floor) {
866
+ if (!canUseBrowserStorage)
867
+ return undefined;
868
+ try {
869
+ const raw = window.localStorage.getItem(lsKeyForDeploymentBlock(chainId, address, floor));
870
+ return raw ? BigInt(raw) : undefined;
871
+ }
872
+ catch {
873
+ return undefined;
874
+ }
875
+ }
876
+ /** Internal: write bigint to localStorage (SSR safe) */
877
+ function writeDeploymentBlockToLS(chainId, address, floor, value) {
878
+ if (!canUseBrowserStorage)
879
+ return;
880
+ try {
881
+ window.localStorage.setItem(lsKeyForDeploymentBlock(chainId, address, floor), value.toString());
882
+ }
883
+ catch {
884
+ /* ignore quota/security errors */
885
+ }
886
+ }
860
887
  /**
861
888
  * Internal helper: checks if there is bytecode at `address` on `blockNumber`.
862
889
  */
@@ -920,15 +947,22 @@ async function findDeploymentBlockRpcX(address, wagmiConfig, floor = 0n) {
920
947
  *
921
948
  * Use with `queryClient.fetchQuery(...)`.
922
949
  *
923
- * @param address - Contract address to probe.
924
- * @param floor - Optional lower bound (inclusive) to speed up search. Defaults to `0n`.
925
- * @param wagmiConfig - Wagmi `Config` (optional; resolved via `ensureClientAndConfig` if omitted).
950
+ * @param address - Contract address to probe.
951
+ * @param floor - Optional lower bound (inclusive) to speed up search. Defaults to `0n`.
952
+ * @param wagmiConfig - Wagmi `Config` (optional; resolved via `ensureClientAndConfig` if omitted).
953
+ * @param options.disableLocalStorage - If `true`, skip reading/writing localStorage (default `false`).
926
954
  *
927
- * @returns A fully-configured query options object (key + fn + metadata).
955
+ * Local Storage behavior (SSR-safe):
956
+ * - If not disabled and a value exists in `localStorage`, the `queryFn` will
957
+ * return it immediately without performing RPC calls.
958
+ * - After an on-chain discovery, the result is written to `localStorage`
959
+ * (unless disabled). This pairs nicely with `staleTime: Infinity` to
960
+ * avoid future refetches.
928
961
  */
929
- function getDeploymentBlockQueryOptionsX(address, floor = 0n, wagmiConfig) {
962
+ function getDeploymentBlockQueryOptionsX(address, floor = 0n, wagmiConfig, options) {
930
963
  if (!address)
931
964
  throw new Error("Address is required");
965
+ const disableLocalStorage = options?.disableLocalStorage ?? false;
932
966
  // Resolve config (caller may pass undefined; we'll normalize later in fetcher too)
933
967
  // We only need chainId for the key; if wagmiConfig is missing here,
934
968
  // we allow it since fetcher re-resolves. But key stability benefits from chainId.
@@ -939,9 +973,25 @@ function getDeploymentBlockQueryOptionsX(address, floor = 0n, wagmiConfig) {
939
973
  queryFn: async () => {
940
974
  if (!wagmiConfig)
941
975
  throw new Error("wagmiConfig is required at execution time");
942
- return findDeploymentBlockRpcX(address, wagmiConfig, floor);
976
+ const c = actions.getPublicClient(wagmiConfig);
977
+ const cid = c?.chain?.id;
978
+ if (!cid)
979
+ throw new Error("Client chain ID is missing");
980
+ // Try localStorage first (no refetches if we already know it)
981
+ if (!disableLocalStorage) {
982
+ const fromLS = readDeploymentBlockFromLS(cid, address, floor);
983
+ if (fromLS !== undefined)
984
+ return fromLS;
985
+ }
986
+ // Otherwise do the discovery via RPC
987
+ const discovered = await findDeploymentBlockRpcX(address, wagmiConfig, floor);
988
+ // Persist to localStorage for subsequent sessions
989
+ if (!disableLocalStorage) {
990
+ writeDeploymentBlockToLS(cid, address, floor, discovered);
991
+ }
992
+ return discovered;
943
993
  },
944
- ...queryConfig.metaDataQuery,
994
+ ...queryConfig.metaDataQuery, // typically sets staleTime: Infinity, gcTime, etc.
945
995
  };
946
996
  }
947
997
  /**
@@ -953,39 +1003,59 @@ function getDeploymentBlockQueryOptionsX(address, floor = 0n, wagmiConfig) {
953
1003
  *
954
1004
  * #### Caching
955
1005
  * - Query key: `["deploymentBlock", chainId, address.toLowerCase(), floor]`
956
- * - For long-lived results, we apply `queryConfig.metaDataQuery` (tweak as needed).
1006
+ * - Long-lived results: `queryConfig.metaDataQuery` (e.g., `staleTime: Infinity`)
957
1007
  *
958
- * #### Performance
959
- * - **O(log N)** `eth_getCode` calls, where `N` is the gap between the latest
960
- * block and the deployment block—optimal among comparison-based strategies.
1008
+ * #### Local Storage (SSR-safe)
1009
+ * - Before calling `fetchQuery`, we seed the Query Cache from `localStorage`
1010
+ * (unless `disableLocalStorage` is true). When a cached value is present,
1011
+ * `fetchQuery` will *not* execute the `queryFn`, fully preventing RPC refetches.
1012
+ * - After on-chain discovery, the result is written back to `localStorage`
1013
+ * (unless disabled).
961
1014
  *
962
1015
  * @example
963
1016
  * ```ts
964
- * const block = await fetchDeploymentBlockX("0xContract...", 0n, queryClient, wagmiConfig);
1017
+ * const block = await fetchDeploymentBlockX(
1018
+ * "0xContract...",
1019
+ * 0n,
1020
+ * queryClient,
1021
+ * wagmiConfig,
1022
+ * { disableLocalStorage: false }
1023
+ * );
965
1024
  * ```
966
1025
  *
967
- * @param address - Contract address to probe.
968
- * @param floor - Optional lower bound (inclusive) to speed up search. Defaults to `0n`.
969
- * @param queryClient - Optional TanStack `QueryClient`. If omitted, resolved by `ensureClientAndConfig`.
970
- * @param wagmiConfig - Optional Wagmi `Config`. If omitted, resolved by `ensureClientAndConfig`.
1026
+ * @param address - Contract address to probe.
1027
+ * @param floor - Optional lower bound (inclusive). Defaults to `0n`.
1028
+ * @param queryClient - Optional TanStack `QueryClient`. If omitted, resolved by `ensureClientAndConfig`.
1029
+ * @param wagmiConfig - Optional Wagmi `Config`. If omitted, resolved by `ensureClientAndConfig`.
1030
+ * @param options.disableLocalStorage - If `true`, skip reading/writing localStorage (default `false`).
971
1031
  *
972
1032
  * @returns The earliest block number (bigint) where bytecode exists.
973
1033
  *
974
1034
  * @throws If the public client is missing or if no code is present at the latest block.
975
1035
  */
976
- async function fetchDeploymentBlockX(address, floor = 0n, queryClient, wagmiConfig) {
1036
+ async function fetchDeploymentBlockX(address, floor = 0n, queryClient, wagmiConfig, options) {
977
1037
  if (!address)
978
1038
  throw new Error("Address is required");
979
1039
  ({ queryClient, wagmiConfig } = ensureClientAndConfig(queryClient, wagmiConfig));
980
- // Resolve chainId for a stable cache key
981
1040
  const client = actions.getPublicClient(wagmiConfig);
982
1041
  const chainId = client?.chain?.id;
983
1042
  if (!chainId)
984
1043
  throw new Error("Client chain ID is missing");
1044
+ const key = deploymentBlockKey(chainId, address, floor);
1045
+ const disableLocalStorage = options?.disableLocalStorage ?? false;
1046
+ // Seed cache from localStorage so fetchQuery returns immediately w/o running queryFn
1047
+ if (!disableLocalStorage) {
1048
+ const fromLS = readDeploymentBlockFromLS(chainId, address, floor);
1049
+ if (fromLS !== undefined) {
1050
+ queryClient.setQueryData(key, fromLS);
1051
+ }
1052
+ }
985
1053
  return queryClient.fetchQuery({
986
- ...getDeploymentBlockQueryOptionsX(address, floor, wagmiConfig),
1054
+ ...getDeploymentBlockQueryOptionsX(address, floor, wagmiConfig, {
1055
+ disableLocalStorage,
1056
+ }),
987
1057
  // Ensure the final key includes a concrete chainId
988
- queryKey: deploymentBlockKey(chainId, address, floor),
1058
+ queryKey: key,
989
1059
  // Reinstate metadata (in case your ensure/util merges)
990
1060
  ...queryConfig.metaDataQuery,
991
1061
  });