@gibs/common 1.13.0 → 1.17.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 +34 -4
- package/cache/package.json +6 -0
- package/client/package.json +6 -0
- package/dist/cache.d.ts +25 -0
- package/dist/cache.js +27 -0
- package/dist/cjs/assert.d.ts +18 -0
- package/dist/cjs/assert.js +31 -0
- package/dist/cjs/cache.d.ts +39 -0
- package/dist/cjs/cache.js +77 -0
- package/dist/cjs/client.d.ts +48 -0
- package/dist/cjs/client.js +171 -0
- package/dist/cjs/erc20.d.ts +18 -0
- package/dist/cjs/erc20.js +39 -0
- package/dist/cjs/index.d.ts +8 -0
- package/dist/cjs/index.js +24 -0
- package/dist/cjs/multicall.d.ts +56 -0
- package/dist/cjs/multicall.js +217 -0
- package/dist/cjs/package.json +3 -0
- package/dist/cjs/rpcPacer.d.ts +88 -0
- package/dist/cjs/rpcPacer.js +158 -0
- package/dist/cjs/serialize.d.ts +19 -0
- package/dist/cjs/serialize.js +48 -0
- package/dist/cjs/types.d.ts +11 -0
- package/dist/cjs/types.js +2 -0
- package/dist/cjs/url.d.ts +110 -0
- package/dist/cjs/url.js +292 -0
- package/dist/client.js +2 -1
- package/dist/erc20.d.ts +3 -5
- package/dist/erc20.js +5 -7
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/multicall.js +1 -2
- package/dist/url.d.ts +110 -0
- package/dist/url.js +281 -0
- package/erc20/package.json +6 -0
- package/llms.txt +89 -0
- package/multicall/package.json +6 -0
- package/package.json +205 -8
- package/serialize/package.json +6 -0
- package/types/package.json +6 -0
- package/url/package.json +6 -0
package/README.md
CHANGED
|
@@ -48,11 +48,33 @@ A collection of essential blockchain utilities and helpers that provide foundati
|
|
|
48
48
|
## 📦 Installation
|
|
49
49
|
|
|
50
50
|
```sh
|
|
51
|
-
npm install @gibs/common
|
|
51
|
+
npm install @gibs/common viem
|
|
52
52
|
# or
|
|
53
|
-
yarn add @gibs/common
|
|
53
|
+
yarn add @gibs/common viem
|
|
54
54
|
```
|
|
55
55
|
|
|
56
|
+
## Ways to use the package
|
|
57
|
+
|
|
58
|
+
The package ships two builds of the same code. The `exports` map picks one, so
|
|
59
|
+
you need no configuration.
|
|
60
|
+
|
|
61
|
+
| Option | What you write | What loads |
|
|
62
|
+
|---|---|---|
|
|
63
|
+
| ECMAScript modules | `import { memoizeByKey } from '@gibs/common/cache'` | `dist/cache.js` |
|
|
64
|
+
| CommonJS | `const { memoizeByKey } = require('@gibs/common/cache')` | `dist/cjs/cache.js` |
|
|
65
|
+
| React Native (Metro) | either of the above | `import` gets `dist/`, `require` gets `dist/cjs/` |
|
|
66
|
+
| React Native (jest) | either of the above | `dist/cjs/` |
|
|
67
|
+
|
|
68
|
+
React Native 0.77 or later needs no change to `metro.config.js`, no
|
|
69
|
+
`resolveRequest` hook, no jest `transformIgnorePatterns` and no polyfill.
|
|
70
|
+
It runs on React Native's own `URL` and `URLSearchParams`, which React Native
|
|
71
|
+
0.77 implements only in part: no code in the package reads them.
|
|
72
|
+
Metro's default configuration in React Native 0.79 or later reads the
|
|
73
|
+
`exports` map. Metro in React Native 0.77 and 0.78 ignores
|
|
74
|
+
`exports`: it reads the `react-native` field for the root, and a folder the
|
|
75
|
+
package ships for each subpath, such as `cache/package.json`, which points at
|
|
76
|
+
the CommonJS build.
|
|
77
|
+
|
|
56
78
|
## 📖 API Examples
|
|
57
79
|
|
|
58
80
|
### Client (`@gibs/common/client`)
|
|
@@ -445,6 +467,7 @@ console.log(defaultBatchConfig)
|
|
|
445
467
|
| `@gibs/common/serialize` | JSON serialization with BigInt support |
|
|
446
468
|
| `@gibs/common/multicall` | Batch contract call utilities |
|
|
447
469
|
| `@gibs/common/types` | TypeScript type definitions |
|
|
470
|
+
| `@gibs/common/url` | Request addresses built and read without `URL` or `URLSearchParams` |
|
|
448
471
|
|
|
449
472
|
### Key Functions
|
|
450
473
|
|
|
@@ -493,6 +516,12 @@ function ttlMemoizeSingle<A extends unknown[], B>(
|
|
|
493
516
|
fn: (...a: A) => B,
|
|
494
517
|
ttl?: number
|
|
495
518
|
): (...a: A) => B
|
|
519
|
+
|
|
520
|
+
// One answer per resolver key, never expired; `cache` is a Map you can evict from
|
|
521
|
+
function memoizeByKey<A extends unknown[], B, K>(
|
|
522
|
+
fn: (...args: A) => B,
|
|
523
|
+
resolver: (...args: A) => K
|
|
524
|
+
): ((...args: A) => B) & { cache: Map<K, B> }
|
|
496
525
|
```
|
|
497
526
|
|
|
498
527
|
#### Serialization
|
|
@@ -560,8 +589,9 @@ interface SerializedBigInt {
|
|
|
560
589
|
|
|
561
590
|
## 🔗 Dependencies
|
|
562
591
|
|
|
563
|
-
- `viem
|
|
564
|
-
|
|
592
|
+
- `viem` (peer dependency): Type-safe Ethereum library for blockchain interactions
|
|
593
|
+
|
|
594
|
+
There are no other runtime dependencies.
|
|
565
595
|
|
|
566
596
|
## 📝 Best Practices
|
|
567
597
|
|
package/dist/cache.d.ts
CHANGED
|
@@ -12,3 +12,28 @@ export declare const maxMemoize: <A extends unknown[], B>(fn: (...a: A) => B, ma
|
|
|
12
12
|
* @returns the memoized function
|
|
13
13
|
*/
|
|
14
14
|
export declare const ttlMemoizeSingle: <A extends unknown[], B>(fn: (...a: A) => B, ttl?: number) => (...a: A) => B;
|
|
15
|
+
/**
|
|
16
|
+
* A memoized function, with the cache it reads exposed so a caller can evict
|
|
17
|
+
* one answer or drop them all.
|
|
18
|
+
*/
|
|
19
|
+
export type KeyedMemoized<A extends unknown[], B, K> = ((...args: A) => B) & {
|
|
20
|
+
/**
|
|
21
|
+
* The answers held so far, keyed by the resolver's output. Each call reads
|
|
22
|
+
* this property afresh, so assigning a new `Map` resets the cache.
|
|
23
|
+
*/
|
|
24
|
+
cache: Map<K, B>;
|
|
25
|
+
};
|
|
26
|
+
/**
|
|
27
|
+
* Memoizes a function under a key the caller computes from its arguments.
|
|
28
|
+
*
|
|
29
|
+
* This is the subset of `lodash.memoize` the packages used: one answer per
|
|
30
|
+
* key, never expired, with `cache.delete` and `cache.clear` for eviction. As
|
|
31
|
+
* with lodash, the `cache` property may be replaced outright. It
|
|
32
|
+
* exists so the published packages carry no lodash-es, whose ECMAScript-module
|
|
33
|
+
* only build a React Native jest run cannot load from `node_modules`.
|
|
34
|
+
*
|
|
35
|
+
* @param fn - the function to memoize
|
|
36
|
+
* @param resolver - computes the cache key from the arguments
|
|
37
|
+
* @returns the memoized function, with its `cache`
|
|
38
|
+
*/
|
|
39
|
+
export declare const memoizeByKey: <A extends unknown[], B, K>(fn: (...args: A) => B, resolver: (...args: A) => K) => KeyedMemoized<A, B, K>;
|
package/dist/cache.js
CHANGED
|
@@ -42,3 +42,30 @@ export const ttlMemoizeSingle = (fn, ttl = hour1) => {
|
|
|
42
42
|
return result;
|
|
43
43
|
};
|
|
44
44
|
};
|
|
45
|
+
/**
|
|
46
|
+
* Memoizes a function under a key the caller computes from its arguments.
|
|
47
|
+
*
|
|
48
|
+
* This is the subset of `lodash.memoize` the packages used: one answer per
|
|
49
|
+
* key, never expired, with `cache.delete` and `cache.clear` for eviction. As
|
|
50
|
+
* with lodash, the `cache` property may be replaced outright. It
|
|
51
|
+
* exists so the published packages carry no lodash-es, whose ECMAScript-module
|
|
52
|
+
* only build a React Native jest run cannot load from `node_modules`.
|
|
53
|
+
*
|
|
54
|
+
* @param fn - the function to memoize
|
|
55
|
+
* @param resolver - computes the cache key from the arguments
|
|
56
|
+
* @returns the memoized function, with its `cache`
|
|
57
|
+
*/
|
|
58
|
+
export const memoizeByKey = (fn, resolver) => {
|
|
59
|
+
const memoized = Object.assign((...args) => {
|
|
60
|
+
const key = resolver(...args);
|
|
61
|
+
// Read the property on every call: a caller may have replaced the Map.
|
|
62
|
+
const { cache } = memoized;
|
|
63
|
+
if (cache.has(key)) {
|
|
64
|
+
return cache.get(key);
|
|
65
|
+
}
|
|
66
|
+
const result = fn(...args);
|
|
67
|
+
cache.set(key, result);
|
|
68
|
+
return result;
|
|
69
|
+
}, { cache: new Map() });
|
|
70
|
+
return memoized;
|
|
71
|
+
};
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Asserts that a value is defined (not null, undefined, or falsy)
|
|
3
|
+
* Throws a descriptive error if the value is falsy
|
|
4
|
+
*
|
|
5
|
+
* @param value - The value to check
|
|
6
|
+
* @param context - Context description for the error message
|
|
7
|
+
* @returns The value if defined
|
|
8
|
+
* @throws Error if value is falsy
|
|
9
|
+
*/
|
|
10
|
+
export declare function ensureResult<T>(value: T | null | undefined | false | 0, context: string): T;
|
|
11
|
+
/**
|
|
12
|
+
* Type guard that asserts a value is defined
|
|
13
|
+
* Similar to ensureResult but uses TypeScript's asserts syntax
|
|
14
|
+
*
|
|
15
|
+
* @param value - The value to check
|
|
16
|
+
* @param message - Error message if assertion fails
|
|
17
|
+
*/
|
|
18
|
+
export declare function assertDefined<T>(value: T | null | undefined, message: string): asserts value is T;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ensureResult = ensureResult;
|
|
4
|
+
exports.assertDefined = assertDefined;
|
|
5
|
+
/**
|
|
6
|
+
* Asserts that a value is defined (not null, undefined, or falsy)
|
|
7
|
+
* Throws a descriptive error if the value is falsy
|
|
8
|
+
*
|
|
9
|
+
* @param value - The value to check
|
|
10
|
+
* @param context - Context description for the error message
|
|
11
|
+
* @returns The value if defined
|
|
12
|
+
* @throws Error if value is falsy
|
|
13
|
+
*/
|
|
14
|
+
function ensureResult(value, context) {
|
|
15
|
+
if (!value) {
|
|
16
|
+
throw new Error(`${context}: Expected result but got ${value}`);
|
|
17
|
+
}
|
|
18
|
+
return value;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Type guard that asserts a value is defined
|
|
22
|
+
* Similar to ensureResult but uses TypeScript's asserts syntax
|
|
23
|
+
*
|
|
24
|
+
* @param value - The value to check
|
|
25
|
+
* @param message - Error message if assertion fails
|
|
26
|
+
*/
|
|
27
|
+
function assertDefined(value, message) {
|
|
28
|
+
if (value === null || value === undefined) {
|
|
29
|
+
throw new Error(message);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Memoize a function result and return a single value
|
|
3
|
+
* @param fn the function to memoize
|
|
4
|
+
* @param max the maximum number of values to cache
|
|
5
|
+
* @returns the memoized function
|
|
6
|
+
*/
|
|
7
|
+
export declare const maxMemoize: <A extends unknown[], B>(fn: (...a: A) => B, max?: number) => (...a: A) => B | undefined;
|
|
8
|
+
/**
|
|
9
|
+
* Memoize a function result and return a single value
|
|
10
|
+
* @param fn the function to memoize
|
|
11
|
+
* @param ttl the time to live for the cache
|
|
12
|
+
* @returns the memoized function
|
|
13
|
+
*/
|
|
14
|
+
export declare const ttlMemoizeSingle: <A extends unknown[], B>(fn: (...a: A) => B, ttl?: number) => (...a: A) => B;
|
|
15
|
+
/**
|
|
16
|
+
* A memoized function, with the cache it reads exposed so a caller can evict
|
|
17
|
+
* one answer or drop them all.
|
|
18
|
+
*/
|
|
19
|
+
export type KeyedMemoized<A extends unknown[], B, K> = ((...args: A) => B) & {
|
|
20
|
+
/**
|
|
21
|
+
* The answers held so far, keyed by the resolver's output. Each call reads
|
|
22
|
+
* this property afresh, so assigning a new `Map` resets the cache.
|
|
23
|
+
*/
|
|
24
|
+
cache: Map<K, B>;
|
|
25
|
+
};
|
|
26
|
+
/**
|
|
27
|
+
* Memoizes a function under a key the caller computes from its arguments.
|
|
28
|
+
*
|
|
29
|
+
* This is the subset of `lodash.memoize` the packages used: one answer per
|
|
30
|
+
* key, never expired, with `cache.delete` and `cache.clear` for eviction. As
|
|
31
|
+
* with lodash, the `cache` property may be replaced outright. It
|
|
32
|
+
* exists so the published packages carry no lodash-es, whose ECMAScript-module
|
|
33
|
+
* only build a React Native jest run cannot load from `node_modules`.
|
|
34
|
+
*
|
|
35
|
+
* @param fn - the function to memoize
|
|
36
|
+
* @param resolver - computes the cache key from the arguments
|
|
37
|
+
* @returns the memoized function, with its `cache`
|
|
38
|
+
*/
|
|
39
|
+
export declare const memoizeByKey: <A extends unknown[], B, K>(fn: (...args: A) => B, resolver: (...args: A) => K) => KeyedMemoized<A, B, K>;
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.memoizeByKey = exports.ttlMemoizeSingle = exports.maxMemoize = void 0;
|
|
4
|
+
const serialize_js_1 = require("./serialize.js");
|
|
5
|
+
/**
|
|
6
|
+
* Memoize a function result and return a single value
|
|
7
|
+
* @param fn the function to memoize
|
|
8
|
+
* @param max the maximum number of values to cache
|
|
9
|
+
* @returns the memoized function
|
|
10
|
+
*/
|
|
11
|
+
const maxMemoize = (fn, max = 1024) => {
|
|
12
|
+
const cache = new Map();
|
|
13
|
+
return (...a) => {
|
|
14
|
+
const key = JSON.stringify(a, serialize_js_1.jsonAnyStringify);
|
|
15
|
+
const existing = cache.get(key);
|
|
16
|
+
if (cache.has(key)) {
|
|
17
|
+
return existing;
|
|
18
|
+
}
|
|
19
|
+
for (const k of cache.keys()) {
|
|
20
|
+
if (cache.size < max)
|
|
21
|
+
break;
|
|
22
|
+
cache.delete(k);
|
|
23
|
+
}
|
|
24
|
+
const result = fn(...a);
|
|
25
|
+
cache.set(key, result);
|
|
26
|
+
return result;
|
|
27
|
+
};
|
|
28
|
+
};
|
|
29
|
+
exports.maxMemoize = maxMemoize;
|
|
30
|
+
const hour1 = 1000 * 60 * 60;
|
|
31
|
+
/**
|
|
32
|
+
* Memoize a function result and return a single value
|
|
33
|
+
* @param fn the function to memoize
|
|
34
|
+
* @param ttl the time to live for the cache
|
|
35
|
+
* @returns the memoized function
|
|
36
|
+
*/
|
|
37
|
+
const ttlMemoizeSingle = (fn, ttl = hour1) => {
|
|
38
|
+
let cache = null;
|
|
39
|
+
return (...a) => {
|
|
40
|
+
const now = Date.now();
|
|
41
|
+
if (cache && cache.timestamp + ttl > now) {
|
|
42
|
+
return cache.result;
|
|
43
|
+
}
|
|
44
|
+
const result = fn(...a);
|
|
45
|
+
cache = { timestamp: now, result };
|
|
46
|
+
return result;
|
|
47
|
+
};
|
|
48
|
+
};
|
|
49
|
+
exports.ttlMemoizeSingle = ttlMemoizeSingle;
|
|
50
|
+
/**
|
|
51
|
+
* Memoizes a function under a key the caller computes from its arguments.
|
|
52
|
+
*
|
|
53
|
+
* This is the subset of `lodash.memoize` the packages used: one answer per
|
|
54
|
+
* key, never expired, with `cache.delete` and `cache.clear` for eviction. As
|
|
55
|
+
* with lodash, the `cache` property may be replaced outright. It
|
|
56
|
+
* exists so the published packages carry no lodash-es, whose ECMAScript-module
|
|
57
|
+
* only build a React Native jest run cannot load from `node_modules`.
|
|
58
|
+
*
|
|
59
|
+
* @param fn - the function to memoize
|
|
60
|
+
* @param resolver - computes the cache key from the arguments
|
|
61
|
+
* @returns the memoized function, with its `cache`
|
|
62
|
+
*/
|
|
63
|
+
const memoizeByKey = (fn, resolver) => {
|
|
64
|
+
const memoized = Object.assign((...args) => {
|
|
65
|
+
const key = resolver(...args);
|
|
66
|
+
// Read the property on every call: a caller may have replaced the Map.
|
|
67
|
+
const { cache } = memoized;
|
|
68
|
+
if (cache.has(key)) {
|
|
69
|
+
return cache.get(key);
|
|
70
|
+
}
|
|
71
|
+
const result = fn(...args);
|
|
72
|
+
cache.set(key, result);
|
|
73
|
+
return result;
|
|
74
|
+
}, { cache: new Map() });
|
|
75
|
+
return memoized;
|
|
76
|
+
};
|
|
77
|
+
exports.memoizeByKey = memoizeByKey;
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { type Chain, type HttpTransportConfig, type PublicClient } from 'viem';
|
|
2
|
+
export { MAX_SAME_METHOD_CALLS_PER_SECOND } from './rpcPacer.js';
|
|
3
|
+
/**
|
|
4
|
+
* JSON-RPC request batching config. When enabled, viem coalesces requests made
|
|
5
|
+
* within `wait` milliseconds (up to `batchSize`) into ONE HTTP POST carrying a
|
|
6
|
+
* JSON-RPC array body. This is the default because it sharply cuts request
|
|
7
|
+
* volume on nodes that support it.
|
|
8
|
+
*
|
|
9
|
+
* Note: some public nodes (notably some Binance Smart Chain endpoints) reject the
|
|
10
|
+
* array-wrapped body — even for a single request — which makes every call fail
|
|
11
|
+
* and get retried, BURNING through requests. Such chains must construct their
|
|
12
|
+
* client with `batch: false` (see {@link clientFromChain}'s `batch` option) so
|
|
13
|
+
* requests are sent as plain JSON-RPC objects the node accepts.
|
|
14
|
+
*/
|
|
15
|
+
export declare const defaultRpcBatch: {
|
|
16
|
+
readonly wait: 10;
|
|
17
|
+
readonly batchSize: 32;
|
|
18
|
+
};
|
|
19
|
+
/** The default batch config for the client */
|
|
20
|
+
export declare const defaultBatchConfig: {
|
|
21
|
+
batch: {
|
|
22
|
+
readonly wait: 10;
|
|
23
|
+
readonly batchSize: 32;
|
|
24
|
+
};
|
|
25
|
+
};
|
|
26
|
+
/**
|
|
27
|
+
* Returns the chain key for the given chain id and urls
|
|
28
|
+
* @param chainId - the chain id
|
|
29
|
+
* @param urls - the urls
|
|
30
|
+
* @returns the chain key
|
|
31
|
+
*/
|
|
32
|
+
export declare const chainKey: (chainId: number, urls: string[]) => string;
|
|
33
|
+
/** The client cache for the given chain id */
|
|
34
|
+
export declare const clientCache: Map<number, {
|
|
35
|
+
key: string;
|
|
36
|
+
client: PublicClient;
|
|
37
|
+
}>;
|
|
38
|
+
export declare const clientFromChain: ({ chain, urls, batch, }: {
|
|
39
|
+
chain: Chain;
|
|
40
|
+
urls: string[];
|
|
41
|
+
/**
|
|
42
|
+
* JSON-RPC request batching for this client's transport. Defaults to
|
|
43
|
+
* {@link defaultRpcBatch}. Pass `false` for chains whose RPC nodes reject the
|
|
44
|
+
* array-wrapped batch body (some Binance Smart Chain endpoints), so requests
|
|
45
|
+
* are sent as plain JSON-RPC objects and are not failed-and-retried.
|
|
46
|
+
*/
|
|
47
|
+
batch?: HttpTransportConfig["batch"];
|
|
48
|
+
}) => PublicClient;
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.clientFromChain = exports.clientCache = exports.chainKey = exports.defaultBatchConfig = exports.defaultRpcBatch = exports.MAX_SAME_METHOD_CALLS_PER_SECOND = void 0;
|
|
4
|
+
const viem_1 = require("viem");
|
|
5
|
+
const rpcPacer_js_1 = require("./rpcPacer.js");
|
|
6
|
+
const url_js_1 = require("./url.js");
|
|
7
|
+
var rpcPacer_js_2 = require("./rpcPacer.js");
|
|
8
|
+
Object.defineProperty(exports, "MAX_SAME_METHOD_CALLS_PER_SECOND", { enumerable: true, get: function () { return rpcPacer_js_2.MAX_SAME_METHOD_CALLS_PER_SECOND; } });
|
|
9
|
+
/**
|
|
10
|
+
* JSON-RPC request batching config. When enabled, viem coalesces requests made
|
|
11
|
+
* within `wait` milliseconds (up to `batchSize`) into ONE HTTP POST carrying a
|
|
12
|
+
* JSON-RPC array body. This is the default because it sharply cuts request
|
|
13
|
+
* volume on nodes that support it.
|
|
14
|
+
*
|
|
15
|
+
* Note: some public nodes (notably some Binance Smart Chain endpoints) reject the
|
|
16
|
+
* array-wrapped body — even for a single request — which makes every call fail
|
|
17
|
+
* and get retried, BURNING through requests. Such chains must construct their
|
|
18
|
+
* client with `batch: false` (see {@link clientFromChain}'s `batch` option) so
|
|
19
|
+
* requests are sent as plain JSON-RPC objects the node accepts.
|
|
20
|
+
*/
|
|
21
|
+
exports.defaultRpcBatch = {
|
|
22
|
+
wait: 10,
|
|
23
|
+
batchSize: 32,
|
|
24
|
+
};
|
|
25
|
+
/** The default batch config for the client */
|
|
26
|
+
exports.defaultBatchConfig = {
|
|
27
|
+
batch: exports.defaultRpcBatch,
|
|
28
|
+
};
|
|
29
|
+
/**
|
|
30
|
+
* Returns the chain key for the given chain id and urls
|
|
31
|
+
* @param chainId - the chain id
|
|
32
|
+
* @param urls - the urls
|
|
33
|
+
* @returns the chain key
|
|
34
|
+
*/
|
|
35
|
+
const chainKey = (chainId, urls) => {
|
|
36
|
+
return `${chainId},${urls.join(',')}`.toLowerCase();
|
|
37
|
+
};
|
|
38
|
+
exports.chainKey = chainKey;
|
|
39
|
+
/** The client cache for the given chain id */
|
|
40
|
+
exports.clientCache = new Map();
|
|
41
|
+
/**
|
|
42
|
+
* Returns the client for the given chain and urls
|
|
43
|
+
* @param chain - the chain
|
|
44
|
+
* @param urls - the urls
|
|
45
|
+
* @returns the client
|
|
46
|
+
*/
|
|
47
|
+
/**
|
|
48
|
+
* The one pacer for the whole application.
|
|
49
|
+
*
|
|
50
|
+
* MODULE LEVEL, DELIBERATELY. The endpoint keys its budget on the METHOD, not
|
|
51
|
+
* the chain — six `eth_blockNumber` on PulseChain plus six on Ethereum were
|
|
52
|
+
* answered exactly eight times, the same as twelve on one chain. A pacer per
|
|
53
|
+
* client would let two chains spend one budget twice and neither would know it
|
|
54
|
+
* had.
|
|
55
|
+
*
|
|
56
|
+
* It is built with the same ceiling the batch size uses, so the two can never
|
|
57
|
+
* describe different limits.
|
|
58
|
+
*/
|
|
59
|
+
const pacer = (0, rpcPacer_js_1.createRpcPacer)({ ceiling: rpcPacer_js_1.MAX_SAME_METHOD_CALLS_PER_SECOND });
|
|
60
|
+
/**
|
|
61
|
+
* Holds a request until its methods fit under the ceiling.
|
|
62
|
+
*
|
|
63
|
+
* WIRED HERE RATHER THAN AROUND THE CALLERS because this is the only place that
|
|
64
|
+
* sees the FINAL body: viem coalesces separate reads into one post, so a limiter
|
|
65
|
+
* upstream of the transport would count intentions and this counts what is
|
|
66
|
+
* actually sent.
|
|
67
|
+
*
|
|
68
|
+
* Returning nothing leaves the request untouched — the hook exists to delay, not
|
|
69
|
+
* to rewrite.
|
|
70
|
+
*/
|
|
71
|
+
const paceRequest = async (request) => {
|
|
72
|
+
// The HOST, not the full url: one proxy serves every chain from one budget,
|
|
73
|
+
// so `/evm/1` and `/evm/369` must count against the same tally.
|
|
74
|
+
const { host } = (0, url_js_1.splitUrl)(request.url);
|
|
75
|
+
await pacer.reserve(host, (0, rpcPacer_js_1.countMethods)(await request.clone().text().catch(() => null)));
|
|
76
|
+
};
|
|
77
|
+
/**
|
|
78
|
+
* Spreads requests across a chain's endpoints instead of resting on the first.
|
|
79
|
+
*
|
|
80
|
+
* WHAT `fallback` ACTUALLY DOES, and why it is not this. With `rank: false` it
|
|
81
|
+
* sends every request to entry zero and moves on only when that entry ERRORS. So
|
|
82
|
+
* the spares behind a chain carry no load at all: on Ethereum, `valve.city` took
|
|
83
|
+
* one hundred percent of every read while two perfectly good nodes sat idle, and
|
|
84
|
+
* the only way traffic ever reached them was a refusal — a wasted round trip
|
|
85
|
+
* spent discovering a spare we could have used on purpose.
|
|
86
|
+
*
|
|
87
|
+
* That matters because the first entry is a SHARED proxy. Its key is published
|
|
88
|
+
* on chainlist.org, so its per-second budget is not ours alone: measured at a
|
|
89
|
+
* refusal, our own calls in the preceding second were seven against a ceiling of
|
|
90
|
+
* eight. We were not over the limit. Somebody else was using the rest of it, and
|
|
91
|
+
* no amount of pacing on our side fixes that — only using less of that host
|
|
92
|
+
* does.
|
|
93
|
+
*
|
|
94
|
+
* ROUND ROBIN, AND FALLBACK STILL INTACT. Each request starts at the next
|
|
95
|
+
* endpoint in rotation and walks the rest on failure, so a dead host costs one
|
|
96
|
+
* attempt exactly as it did before while a healthy one now carries its share.
|
|
97
|
+
*
|
|
98
|
+
* NOT `rank: true`, which is viem's own answer to this and is the wrong one
|
|
99
|
+
* here: ranking pings EVERY url on EVERY chain every few seconds forever, which
|
|
100
|
+
* on an idle page was once the entire outbound traffic and is what spent the
|
|
101
|
+
* shared budget in the first place. It also reorders a list this project curates
|
|
102
|
+
* deliberately.
|
|
103
|
+
*/
|
|
104
|
+
const balanced = (transports) => {
|
|
105
|
+
if (transports.length === 1)
|
|
106
|
+
return transports[0];
|
|
107
|
+
return (config) => {
|
|
108
|
+
const instances = transports.map((transport) => transport(config));
|
|
109
|
+
// Module-level rotation per client, not per call site: every reader on the
|
|
110
|
+
// page shares one cursor, which is what makes the split even rather than
|
|
111
|
+
// each caller starting at zero and piling onto the same host.
|
|
112
|
+
let cursor = 0;
|
|
113
|
+
return (0, viem_1.createTransport)({
|
|
114
|
+
key: 'balanced',
|
|
115
|
+
name: 'Balanced',
|
|
116
|
+
type: 'balanced',
|
|
117
|
+
// CAST AT THE BOUNDARY, deliberately. `EIP1193RequestFn` is generic over
|
|
118
|
+
// the method being called and resolves its own return type; a rotation
|
|
119
|
+
// cannot preserve that relationship because it does not know which
|
|
120
|
+
// transport will answer until it has tried one. The delegates below are
|
|
121
|
+
// fully typed and the shape passing through is unchanged — only the
|
|
122
|
+
// compiler's ability to follow it across the loop is lost.
|
|
123
|
+
request: (async (args) => {
|
|
124
|
+
const start = cursor;
|
|
125
|
+
cursor = (cursor + 1) % instances.length;
|
|
126
|
+
let lastError;
|
|
127
|
+
for (let offset = 0; offset < instances.length; offset += 1) {
|
|
128
|
+
try {
|
|
129
|
+
return await instances[(start + offset) % instances.length].request(args);
|
|
130
|
+
}
|
|
131
|
+
catch (error) {
|
|
132
|
+
// KEPT AND RETHROWN AT THE END, not swallowed. If every endpoint
|
|
133
|
+
// refuses, the caller must see a real error — a rotation that
|
|
134
|
+
// returns undefined when all its hosts are down turns an outage
|
|
135
|
+
// into a silently empty read.
|
|
136
|
+
lastError = error;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
throw lastError;
|
|
140
|
+
}),
|
|
141
|
+
});
|
|
142
|
+
};
|
|
143
|
+
};
|
|
144
|
+
const clientFromChain = ({ chain, urls, batch = exports.defaultRpcBatch, }) => {
|
|
145
|
+
const key = (0, exports.chainKey)(chain.id, urls);
|
|
146
|
+
const existing = exports.clientCache.get(chain.id);
|
|
147
|
+
if (existing && existing.key === key) {
|
|
148
|
+
return existing.client;
|
|
149
|
+
}
|
|
150
|
+
const transport = !urls?.length
|
|
151
|
+
? (0, viem_1.http)()
|
|
152
|
+
: balanced(urls.map((rpc) => rpc.startsWith('http')
|
|
153
|
+
? (0, viem_1.http)(rpc, { batch, onFetchRequest: paceRequest })
|
|
154
|
+
: (0, viem_1.webSocket)(rpc, {
|
|
155
|
+
keepAlive: true,
|
|
156
|
+
reconnect: true,
|
|
157
|
+
retryDelay: 250,
|
|
158
|
+
retryCount: 10,
|
|
159
|
+
timeout: 4_000,
|
|
160
|
+
})));
|
|
161
|
+
const client = (0, viem_1.createPublicClient)({
|
|
162
|
+
chain,
|
|
163
|
+
transport,
|
|
164
|
+
});
|
|
165
|
+
exports.clientCache.set(chain.id, {
|
|
166
|
+
key,
|
|
167
|
+
client,
|
|
168
|
+
});
|
|
169
|
+
return client;
|
|
170
|
+
};
|
|
171
|
+
exports.clientFromChain = clientFromChain;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { type Chain, type Hex, type PublicClient } from 'viem';
|
|
2
|
+
import type { Erc20Metadata } from './types.js';
|
|
3
|
+
/** The erc20 metadata calls */
|
|
4
|
+
export declare const erc20MetadataCalls: {
|
|
5
|
+
functionName: string;
|
|
6
|
+
}[];
|
|
7
|
+
/**
|
|
8
|
+
* The multicall for the erc20 metadata.
|
|
9
|
+
*
|
|
10
|
+
* The annotation spells out the plain call signature, which keeps the
|
|
11
|
+
* memoization an implementation detail: no caller touches the `.cache`
|
|
12
|
+
* property it would otherwise publish.
|
|
13
|
+
*/
|
|
14
|
+
export declare const multicallErc20: (inputs: {
|
|
15
|
+
client: PublicClient;
|
|
16
|
+
target: Hex;
|
|
17
|
+
chain: Chain;
|
|
18
|
+
}) => Promise<Erc20Metadata>;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.multicallErc20 = exports.erc20MetadataCalls = void 0;
|
|
4
|
+
const viem_1 = require("viem");
|
|
5
|
+
const cache_js_1 = require("./cache.js");
|
|
6
|
+
const multicall_js_1 = require("./multicall.js");
|
|
7
|
+
/** The erc20 metadata calls */
|
|
8
|
+
exports.erc20MetadataCalls = [
|
|
9
|
+
{ functionName: 'name' },
|
|
10
|
+
{ functionName: 'symbol' },
|
|
11
|
+
{ functionName: 'decimals' },
|
|
12
|
+
];
|
|
13
|
+
/**
|
|
14
|
+
* The multicall for the erc20 metadata.
|
|
15
|
+
*
|
|
16
|
+
* The annotation spells out the plain call signature, which keeps the
|
|
17
|
+
* memoization an implementation detail: no caller touches the `.cache`
|
|
18
|
+
* property it would otherwise publish.
|
|
19
|
+
*/
|
|
20
|
+
exports.multicallErc20 = (0, cache_js_1.memoizeByKey)(async ({ client, target, chain }) => {
|
|
21
|
+
const options = {
|
|
22
|
+
chain: chain,
|
|
23
|
+
client: client,
|
|
24
|
+
abi: viem_1.erc20Abi,
|
|
25
|
+
target,
|
|
26
|
+
calls: exports.erc20MetadataCalls,
|
|
27
|
+
};
|
|
28
|
+
try {
|
|
29
|
+
return await (0, multicall_js_1.multicallRead)(options);
|
|
30
|
+
}
|
|
31
|
+
catch {
|
|
32
|
+
return await (0, multicall_js_1.multicallRead)({
|
|
33
|
+
...options,
|
|
34
|
+
abi: viem_1.erc20Abi_bytes32,
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
}, ({ target, chain }) => {
|
|
38
|
+
return !target ? chain.id : (0, viem_1.getAddress)(target, chain.id);
|
|
39
|
+
});
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
+
};
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
__exportStar(require("./assert.js"), exports);
|
|
18
|
+
__exportStar(require("./cache.js"), exports);
|
|
19
|
+
__exportStar(require("./client.js"), exports);
|
|
20
|
+
__exportStar(require("./erc20.js"), exports);
|
|
21
|
+
__exportStar(require("./multicall.js"), exports);
|
|
22
|
+
__exportStar(require("./serialize.js"), exports);
|
|
23
|
+
__exportStar(require("./types.js"), exports);
|
|
24
|
+
__exportStar(require("./url.js"), exports);
|