@gibs/common 1.19.0 → 1.20.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/dist/cjs/url.js CHANGED
@@ -1,33 +1,9 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.isHttpUrl = exports.splitUrl = exports.buildUrl = exports.setQueryParameter = exports.buildQuery = exports.parseQuery = exports.decodeQueryComponent = exports.encodeQueryComponent = void 0;
4
- /**
5
- * Request addresses, built and read with plain string work.
6
- *
7
- * WHY THIS EXISTS: React Native 0.77 installs its own `URL` and
8
- * `URLSearchParams` (react-native/Libraries/Blob/URL.js), and both are partial.
9
- * `new URL(x).protocol`, `.host`, `.pathname` and `.search` throw "not
10
- * implemented". `searchParams` is always a new, empty list, so a reader of it
11
- * silently sees no parameters. `URLSearchParams` can only append and
12
- * serialize: `get`, `set`, `has` and `delete` throw, and a string passed to
13
- * its constructor is ignored. A package that uses either class needs a
14
- * polyfill in a wallet. These functions need nothing from the runtime but
15
- * `encodeURIComponent` and `decodeURIComponent`.
16
- *
17
- * Their output matches what the WHATWG classes produce for the addresses the
18
- * Gibs packages build: a lowercase `http:` or `https:` origin with a plain
19
- * path. A query is encoded exactly as `URLSearchParams` encodes it, as
20
- * `application/x-www-form-urlencoded`: a space becomes `+`, and only letters,
21
- * digits and `*-._` stay as they are. They do not normalize the address
22
- * itself: an uppercase host or a `..` path segment passes through as written.
23
- */
24
- /** A surrogate pair, or a lone surrogate half. */
25
4
  const surrogates = /[\uD800-\uDBFF][\uDC00-\uDFFF]|[\uD800-\uDFFF]/g;
26
- /** The characters `encodeURIComponent` keeps that a form encoder escapes. */
27
5
  const formEscapedCharacters = /[!'()~]/g;
28
- /** A run of percent-encoded bytes. */
29
6
  const percentEncodedRun = /(?:%[0-9A-Fa-f]{2})+/g;
30
- /** The port each special scheme leaves out of its host. */
31
7
  const defaultPorts = Object.freeze({
32
8
  http: '80',
33
9
  https: '443',
@@ -35,39 +11,12 @@ const defaultPorts = Object.freeze({
35
11
  wss: '443',
36
12
  ftp: '21',
37
13
  });
38
- /**
39
- * Replaces each lone surrogate half with U+FFFD, as the WHATWG classes do
40
- * before they encode. `encodeURIComponent` would throw on one instead.
41
- *
42
- * @param text - any string
43
- * @returns the string, well formed
44
- */
45
14
  const toWellFormed = (text) => text.replace(surrogates, (match) => (match.length === 2 ? match : '�'));
46
- /**
47
- * Percent-encodes one character as two uppercase hex digits.
48
- *
49
- * @param character - a single ASCII character
50
- * @returns the character as `%XX`
51
- */
52
15
  const percentEncodeCharacter = (character) => `%${character.charCodeAt(0).toString(16).toUpperCase().padStart(2, '0')}`;
53
- /**
54
- * Encodes a query name or value exactly as `URLSearchParams.toString` does.
55
- *
56
- * @param text - the raw name or value
57
- * @returns the encoded text
58
- */
59
16
  const encodeQueryComponent = (text) => encodeURIComponent(toWellFormed(text))
60
17
  .replace(formEscapedCharacters, percentEncodeCharacter)
61
18
  .replace(/%20/g, '+');
62
19
  exports.encodeQueryComponent = encodeQueryComponent;
63
- /**
64
- * Counts the continuation bytes a UTF-8 lead byte announces, and the bounds
65
- * the first of them must fall in. The bounds reject overlong forms and
66
- * surrogates, as the WHATWG decoder does.
67
- *
68
- * @param lead - the first byte of a sequence
69
- * @returns the count, the lead byte's own bits, and the first continuation byte's bounds; null for a byte that cannot start one
70
- */
71
20
  const utf8Lead = (lead) => {
72
21
  if (lead >= 0xc2 && lead <= 0xdf)
73
22
  return { count: 1, bits: lead & 0x1f, lower: 0x80, upper: 0xbf };
@@ -85,14 +34,6 @@ const utf8Lead = (lead) => {
85
34
  return { count: 3, bits: lead & 0x07, lower: 0x80, upper: 0xbf };
86
35
  return null;
87
36
  };
88
- /**
89
- * Decodes UTF-8 bytes as the WHATWG decoder does: each malformed part becomes
90
- * one U+FFFD, and a byte that ends a malformed part is read again as the
91
- * start of the next. Only reached when `decodeURIComponent` refuses a run.
92
- *
93
- * @param bytes - the bytes
94
- * @returns the decoded text
95
- */
96
37
  const decodeUtf8Leniently = (bytes) => {
97
38
  const decode = (index, decoded) => {
98
39
  if (index >= bytes.length)
@@ -120,12 +61,6 @@ const decodeUtf8Leniently = (bytes) => {
120
61
  };
121
62
  return decode(0, '');
122
63
  };
123
- /**
124
- * Decodes one run of `%XX` bytes.
125
- *
126
- * @param run - consecutive `%XX` sequences
127
- * @returns the text they encode
128
- */
129
64
  const decodePercentRun = (run) => {
130
65
  try {
131
66
  return decodeURIComponent(run);
@@ -134,23 +69,8 @@ const decodePercentRun = (run) => {
134
69
  return decodeUtf8Leniently(run.slice(1).split('%').map((pair) => Number.parseInt(pair, 16)));
135
70
  }
136
71
  };
137
- /**
138
- * Decodes a query name or value as the WHATWG URL standard says
139
- * `URLSearchParams` must: `+` is a space, a valid `%XX` is a byte, a `%` that
140
- * starts no valid sequence stays as it is, and each malformed UTF-8 part, or
141
- * lone surrogate half, becomes U+FFFD.
142
- *
143
- * @param text - the encoded name or value
144
- * @returns the decoded text
145
- */
146
72
  const decodeQueryComponent = (text) => toWellFormed(text).replace(/\+/g, ' ').replace(percentEncodedRun, decodePercentRun);
147
73
  exports.decodeQueryComponent = decodeQueryComponent;
148
- /**
149
- * Lists the parameters of a query, decoded and in order.
150
- *
151
- * @param query - a query, with or without its leading `?`
152
- * @returns each parameter's name and value; a parameter with no `=` has an empty value
153
- */
154
74
  const parseQuery = (query) => query
155
75
  .replace(/^\?/, '')
156
76
  .split('&')
@@ -162,37 +82,15 @@ const parseQuery = (query) => query
162
82
  return [(0, exports.decodeQueryComponent)(name), (0, exports.decodeQueryComponent)(value)];
163
83
  });
164
84
  exports.parseQuery = parseQuery;
165
- /**
166
- * Turns query parameters into ordered pairs.
167
- *
168
- * @param parameters - a record or pairs; see {@link QueryParameters}
169
- * @returns the pairs, with every `undefined` record value left out
170
- */
171
85
  const toEntries = (parameters) => {
172
86
  if (Array.isArray(parameters))
173
87
  return parameters;
174
88
  return Object.entries(parameters).filter((entry) => entry[1] !== undefined);
175
89
  };
176
- /**
177
- * Writes a query, as `new URLSearchParams(parameters).toString()` does.
178
- *
179
- * @param parameters - a record or pairs; see {@link QueryParameters}
180
- * @returns the encoded query, with no leading `?`; empty for no parameters
181
- */
182
90
  const buildQuery = (parameters) => toEntries(parameters)
183
91
  .map(([name, value]) => `${(0, exports.encodeQueryComponent)(name)}=${(0, exports.encodeQueryComponent)(value)}`)
184
92
  .join('&');
185
93
  exports.buildQuery = buildQuery;
186
- /**
187
- * Sets one parameter in a query, as `URLSearchParams.set` does: the first
188
- * parameter of that name takes the value and any later ones are removed, or
189
- * the parameter is appended when none has the name.
190
- *
191
- * @param query - an encoded query, with or without its leading `?`
192
- * @param name - the parameter's name
193
- * @param value - its value
194
- * @returns the encoded query, with no leading `?`
195
- */
196
94
  const setQueryParameter = (query, name, value) => {
197
95
  const entries = (0, exports.parseQuery)(query);
198
96
  const firstAt = entries.findIndex(([entryName]) => entryName === name);
@@ -205,12 +103,6 @@ const setQueryParameter = (query, name, value) => {
205
103
  }));
206
104
  };
207
105
  exports.setQueryParameter = setQueryParameter;
208
- /**
209
- * Splits an address at its first `#` and then at its first `?`.
210
- *
211
- * @param address - any address
212
- * @returns everything before the query, the query and the fragment, each without its delimiter
213
- */
214
106
  const splitAtQuery = (address) => {
215
107
  const fragmentAt = address.indexOf('#');
216
108
  const beforeFragment = fragmentAt === -1 ? address : address.slice(0, fragmentAt);
@@ -220,15 +112,6 @@ const splitAtQuery = (address) => {
220
112
  return { beforeQuery: beforeFragment, query: '', fragment };
221
113
  return { beforeQuery: beforeFragment.slice(0, queryAt), query: beforeFragment.slice(queryAt + 1), fragment };
222
114
  };
223
- /**
224
- * Adds query parameters to an address, as `url.searchParams.set` does for
225
- * each one in turn. A query already on the address is kept, and a fragment
226
- * stays at the end.
227
- *
228
- * @param address - the address, with or without a query
229
- * @param parameters - a record or pairs; see {@link QueryParameters}
230
- * @returns the address with the parameters set; the address unchanged when there are none
231
- */
232
115
  const buildUrl = (address, parameters) => {
233
116
  const entries = toEntries(parameters);
234
117
  if (entries.length === 0)
@@ -238,15 +121,6 @@ const buildUrl = (address, parameters) => {
238
121
  return `${beforeQuery}?${updated}${fragment === '' ? '' : `#${fragment}`}`;
239
122
  };
240
123
  exports.buildUrl = buildUrl;
241
- /**
242
- * Reads the host the WHATWG `URL` reports from an authority: lowercase, with
243
- * any user name and password dropped, and the port dropped when it is the
244
- * scheme's default.
245
- *
246
- * @param authority - the text between `//` and the path
247
- * @param scheme - the lowercase scheme
248
- * @returns the host
249
- */
250
124
  const hostOfAuthority = (authority, scheme) => {
251
125
  const hostAndPort = authority.slice(authority.lastIndexOf('@') + 1).toLowerCase();
252
126
  const port = /:(\d*)$/.exec(hostAndPort);
@@ -258,12 +132,6 @@ const hostOfAuthority = (authority, scheme) => {
258
132
  const portNumber = String(Number(port[1]));
259
133
  return portNumber === defaultPorts[scheme] ? hostname : `${hostname}:${portNumber}`;
260
134
  };
261
- /**
262
- * Splits an address into its parts, without the WHATWG `URL` class.
263
- *
264
- * @param address - any address; leading and trailing spaces and control characters are ignored, as the WHATWG parser ignores them
265
- * @returns the parts; see {@link UrlParts}
266
- */
267
135
  const splitUrl = (address) => {
268
136
  const trimmed = address.replace(/^[\u0000- ]+|[\u0000- ]+$/g, '');
269
137
  const { beforeQuery, query, fragment } = splitAtQuery(trimmed);
@@ -278,13 +146,6 @@ const splitUrl = (address) => {
278
146
  return { scheme, host: hostOfAuthority(authority, scheme), path, query, fragment };
279
147
  };
280
148
  exports.splitUrl = splitUrl;
281
- /**
282
- * Says whether a value is an absolute `http:` or `https:` address with a host.
283
- * It reads the scheme by its prefix, so it needs no `URL` class.
284
- *
285
- * @param value - any string
286
- * @returns true for an absolute `http:` or `https:` address
287
- */
288
149
  const isHttpUrl = (value) => {
289
150
  const { scheme, host } = (0, exports.splitUrl)(value);
290
151
  return (scheme === 'http' || scheme === 'https') && host !== '';
package/dist/client.js CHANGED
@@ -2,119 +2,32 @@ import { createPublicClient, createTransport, http, webSocket, } from 'viem';
2
2
  import { countMethods, createRpcPacer, MAX_SAME_METHOD_CALLS_PER_SECOND, } from './rpcPacer.js';
3
3
  import { splitUrl } from './url.js';
4
4
  export { MAX_SAME_METHOD_CALLS_PER_SECOND } from './rpcPacer.js';
5
- /**
6
- * JSON-RPC request batching config. When enabled, viem coalesces requests made
7
- * within `wait` milliseconds (up to `batchSize`) into ONE HTTP POST carrying a
8
- * JSON-RPC array body. This is the default because it sharply cuts request
9
- * volume on nodes that support it.
10
- *
11
- * Note: some public nodes (notably some Binance Smart Chain endpoints) reject the
12
- * array-wrapped body — even for a single request — which makes every call fail
13
- * and get retried, BURNING through requests. Such chains must construct their
14
- * client with `batch: false` (see {@link clientFromChain}'s `batch` option) so
15
- * requests are sent as plain JSON-RPC objects the node accepts.
16
- */
17
5
  export const defaultRpcBatch = {
18
6
  wait: 10,
19
7
  batchSize: 32,
20
8
  };
21
- /** The default batch config for the client */
22
9
  export const defaultBatchConfig = {
23
10
  batch: defaultRpcBatch,
24
11
  };
25
- /**
26
- * Returns the chain key for the given chain id and urls
27
- * @param chainId - the chain id
28
- * @param urls - the urls
29
- * @returns the chain key
30
- */
31
12
  export const chainKey = (chainId, urls) => {
32
13
  return `${chainId},${urls.join(',')}`.toLowerCase();
33
14
  };
34
- /** The client cache for the given chain id */
35
15
  export const clientCache = new Map();
36
- /**
37
- * Returns the client for the given chain and urls
38
- * @param chain - the chain
39
- * @param urls - the urls
40
- * @returns the client
41
- */
42
- /**
43
- * The one pacer for the whole application.
44
- *
45
- * MODULE LEVEL, DELIBERATELY. The endpoint keys its budget on the METHOD, not
46
- * the chain — six `eth_blockNumber` on PulseChain plus six on Ethereum were
47
- * answered exactly eight times, the same as twelve on one chain. A pacer per
48
- * client would let two chains spend one budget twice and neither would know it
49
- * had.
50
- *
51
- * It is built with the same ceiling the batch size uses, so the two can never
52
- * describe different limits.
53
- */
54
16
  const pacer = createRpcPacer({ ceiling: MAX_SAME_METHOD_CALLS_PER_SECOND });
55
- /**
56
- * Holds a request until its methods fit under the ceiling.
57
- *
58
- * WIRED HERE RATHER THAN AROUND THE CALLERS because this is the only place that
59
- * sees the FINAL body: viem coalesces separate reads into one post, so a limiter
60
- * upstream of the transport would count intentions and this counts what is
61
- * actually sent.
62
- *
63
- * Returning nothing leaves the request untouched — the hook exists to delay, not
64
- * to rewrite.
65
- */
66
17
  const paceRequest = async (request) => {
67
- // The HOST, not the full url: one proxy serves every chain from one budget,
68
- // so `/evm/1` and `/evm/369` must count against the same tally.
69
18
  const { host } = splitUrl(request.url);
70
19
  await pacer.reserve(host, countMethods(await request.clone().text().catch(() => null)));
71
20
  };
72
- /**
73
- * Spreads requests across a chain's endpoints instead of resting on the first.
74
- *
75
- * WHAT `fallback` ACTUALLY DOES, and why it is not this. With `rank: false` it
76
- * sends every request to entry zero and moves on only when that entry ERRORS. So
77
- * the spares behind a chain carry no load at all: on Ethereum, `valve.city` took
78
- * one hundred percent of every read while two perfectly good nodes sat idle, and
79
- * the only way traffic ever reached them was a refusal — a wasted round trip
80
- * spent discovering a spare we could have used on purpose.
81
- *
82
- * That matters because the first entry is a SHARED proxy. Its key is published
83
- * on chainlist.org, so its per-second budget is not ours alone: measured at a
84
- * refusal, our own calls in the preceding second were seven against a ceiling of
85
- * eight. We were not over the limit. Somebody else was using the rest of it, and
86
- * no amount of pacing on our side fixes that — only using less of that host
87
- * does.
88
- *
89
- * ROUND ROBIN, AND FALLBACK STILL INTACT. Each request starts at the next
90
- * endpoint in rotation and walks the rest on failure, so a dead host costs one
91
- * attempt exactly as it did before while a healthy one now carries its share.
92
- *
93
- * NOT `rank: true`, which is viem's own answer to this and is the wrong one
94
- * here: ranking pings EVERY url on EVERY chain every few seconds forever, which
95
- * on an idle page was once the entire outbound traffic and is what spent the
96
- * shared budget in the first place. It also reorders a list this project curates
97
- * deliberately.
98
- */
99
21
  const balanced = (transports) => {
100
22
  if (transports.length === 1)
101
23
  return transports[0];
102
24
  return (config) => {
103
25
  const instances = transports.map((transport) => transport(config));
104
- // Module-level rotation per client, not per call site: every reader on the
105
- // page shares one cursor, which is what makes the split even rather than
106
- // each caller starting at zero and piling onto the same host.
107
26
  let cursor = 0;
108
27
  return createTransport({
109
28
  key: 'balanced',
110
29
  name: 'Balanced',
111
30
  type: 'balanced',
112
- // CAST AT THE BOUNDARY, deliberately. `EIP1193RequestFn` is generic over
113
- // the method being called and resolves its own return type; a rotation
114
- // cannot preserve that relationship because it does not know which
115
- // transport will answer until it has tried one. The delegates below are
116
- // fully typed and the shape passing through is unchanged — only the
117
- // compiler's ability to follow it across the loop is lost.
118
31
  request: (async (args) => {
119
32
  const start = cursor;
120
33
  cursor = (cursor + 1) % instances.length;
@@ -124,10 +37,6 @@ const balanced = (transports) => {
124
37
  return await instances[(start + offset) % instances.length].request(args);
125
38
  }
126
39
  catch (error) {
127
- // KEPT AND RETHROWN AT THE END, not swallowed. If every endpoint
128
- // refuses, the caller must see a real error — a rotation that
129
- // returns undefined when all its hosts are down turns an outage
130
- // into a silently empty read.
131
40
  lastError = error;
132
41
  }
133
42
  }
package/dist/erc20.js CHANGED
@@ -1,19 +1,11 @@
1
1
  import { erc20Abi, erc20Abi_bytes32, getAddress, } from 'viem';
2
2
  import { memoizeByKey } from './cache.js';
3
3
  import { multicallRead } from './multicall.js';
4
- /** The erc20 metadata calls */
5
4
  export const erc20MetadataCalls = [
6
5
  { functionName: 'name' },
7
6
  { functionName: 'symbol' },
8
7
  { functionName: 'decimals' },
9
8
  ];
10
- /**
11
- * The multicall for the erc20 metadata.
12
- *
13
- * The annotation spells out the plain call signature, which keeps the
14
- * memoization an implementation detail: no caller touches the `.cache`
15
- * property it would otherwise publish.
16
- */
17
9
  export const multicallErc20 = memoizeByKey(async ({ client, target, chain }) => {
18
10
  const options = {
19
11
  chain: chain,
package/dist/multicall.js CHANGED
@@ -1,90 +1,10 @@
1
1
  import { decodeFunctionResult, encodeFunctionData, getContract, multicall3Abi, } from 'viem';
2
2
  import { ensureResult } from './assert.js';
3
- /**
4
- * The largest request body the leading endpoint accepts, in bytes. MEASURED,
5
- * not assumed.
6
- *
7
- * `one.valve.city` answers a 102,400 byte body with 200 and a 102,656 byte body
8
- * with 413, bisected on 2026-09-12. That is the 100 kibibyte default of
9
- * `express.json`, and the proxy leads every chain list this application ships,
10
- * so it is the number every other budget here has to clear.
11
- *
12
- * THE PREVIOUS VALUE WAS A GUESS AND IT WAS FIVE TIMES TOO LARGE. It said "the
13
- * one-megabyte body limit that is the default in the reverse proxies these
14
- * endpoints sit behind", which is Nginx's default, not Express's — and the
15
- * endpoint in front is Express. Nobody had asked the endpoint.
16
- */
17
3
  const ENDPOINT_BODY_LIMIT_BYTES = 100 * 1024;
18
- /**
19
- * How large a single HTTP request body to the node may get, in bytes.
20
- *
21
- * Nothing bounded this before, so the whole call list went out at once and the
22
- * size followed whatever the caller happened to be reading. The token picker
23
- * reads a balance per token, and PulseChain's list runs to about eleven and a
24
- * half thousand of them: a single 8.5 MB `eth_call`, repeated on every open,
25
- * which `rpc.pulsechain.com` answered with HTTP 413. The caller catches that and
26
- * falls back to an empty map, so the visible result was not an error — it was
27
- * every balance quietly missing.
28
- *
29
- * Four kibibytes under {@link ENDPOINT_BODY_LIMIT_BYTES}, because the budget
30
- * below counts only the calls: `aggregate3` adds a fixed header of its own and
31
- * the JSON-RPC envelope wraps the lot, and neither is charged to a call.
32
- */
33
4
  const HTTP_BODY_TARGET_BYTES = ENDPOINT_BODY_LIMIT_BYTES - 4 * 1024;
34
- /**
35
- * How many chunks may be in flight at once.
36
- *
37
- * Splitting a request is only an improvement if the pieces do not arrive as a
38
- * burst; an endpoint that rejected one large body will just as happily rate-limit
39
- * two dozen small ones.
40
- *
41
- * ONE, AND RUNNING MORE BUYS NOTHING. The transport coalesces calls made in the
42
- * same tick into one post, so four chunks at a quarter of the budget each arrive
43
- * as ONE post of the same total size as a single full-budget chunk — the same
44
- * bytes, in the same round trip, at four times the call count. The proxy's
45
- * budget counts CALLS, not posts and not bytes, so the wider setting spent four
46
- * times the rate limit for identical throughput.
47
- *
48
- * Measured on one token-picker open, 2026-09-12: at a concurrency of four the
49
- * peak was eleven calls of one method in a second against a ceiling of eight,
50
- * and one post was refused; at one the peak was seven and every post was
51
- * answered. Request bytes fell from 737,057 to 638,890 in the same comparison,
52
- * because nothing had to be resent to a fallback endpoint.
53
- */
54
5
  const CHUNK_CONCURRENCY = 1;
55
- /**
56
- * How much encoded call data one `aggregate3` may carry.
57
- *
58
- * Divided by the concurrency rather than set directly, because the transport
59
- * COALESCES calls made in the same tick into one HTTP body. Chunking to a
60
- * quarter-megabyte budget and running four at a time was measured arriving as a
61
- * single 1.37 MB request — the split was real, and then undone at the layer
62
- * below. What the node sees is this budget multiplied by the concurrency, so
63
- * that is the relationship worth writing down; changing either constant alone
64
- * cannot silently blow the target.
65
- *
66
- * STILL TRUE AT A CONCURRENCY OF ONE, and that is why the division stays rather
67
- * than being folded away: raising the concurrency again without cutting this
68
- * budget would put the target back over the endpoint's limit, and the division
69
- * is what stops that being silent.
70
- */
71
6
  const CALLDATA_BUDGET_BYTES = Math.floor(HTTP_BODY_TARGET_BYTES / CHUNK_CONCURRENCY);
72
- /**
73
- * Bytes of request body a single call costs beyond its own encoded data.
74
- * MEASURED against a real `aggregate3` encoding, not estimated.
75
- *
76
- * `aggregate3` spends five thirty-two byte words per call — the head offset, the
77
- * target, the failure flag, the data offset and the data length — which is 320
78
- * hexadecimal characters, plus up to 63 more padding the call data out to a word
79
- * boundary. A `balanceOf` whose own call data is 74 characters was measured
80
- * costing 448.6 bytes of body, so the real overhead is about 375 and the old
81
- * value of 200 UNDERSTATED IT BY NEARLY A HALF.
82
- *
83
- * That is how a chunk budgeted at 128 kibibytes went out as a 214 kilobyte body.
84
- * Four hundred keeps a small margin over the measurement.
85
- */
86
7
  const PER_CALL_OVERHEAD_BYTES = 400;
87
- /** The size limits, exported so tests assert against the real values. */
88
8
  export const multicallLimits = {
89
9
  endpointBodyLimitBytes: ENDPOINT_BODY_LIMIT_BYTES,
90
10
  httpBodyTargetBytes: HTTP_BODY_TARGET_BYTES,
@@ -92,22 +12,6 @@ export const multicallLimits = {
92
12
  chunkConcurrency: CHUNK_CONCURRENCY,
93
13
  perCallOverheadBytes: PER_CALL_OVERHEAD_BYTES,
94
14
  };
95
- /**
96
- * Splits encoded calls into groups whose combined request body stays under
97
- * {@link CALLDATA_BUDGET_BYTES}.
98
- *
99
- * Grouped by measured size rather than by a fixed count on purpose: callers
100
- * differ by more than an order of magnitude in how large one call encodes to — a
101
- * bare `balanceOf` against a router quote carrying a swap path — so any single
102
- * call limit would be far too cautious for one caller and useless for another.
103
- *
104
- * A call that exceeds the budget on its own still gets its own group; splitting
105
- * it further is not possible, and refusing to send it would turn an oversized
106
- * request into a missing result.
107
- *
108
- * @param encoded - the calls to group, in order
109
- * @returns groups preserving the original order
110
- */
111
15
  export const chunkBySize = (encoded) => {
112
16
  const chunks = [];
113
17
  let current = [];
@@ -126,14 +30,6 @@ export const chunkBySize = (encoded) => {
126
30
  chunks.push(current);
127
31
  return chunks;
128
32
  };
129
- /**
130
- * Runs `task` over every item with at most `concurrency` in flight, preserving
131
- * input order in the result.
132
- *
133
- * @param items - the work items
134
- * @param concurrency - how many may run at once
135
- * @param task - what to do with one item
136
- */
137
33
  const mapWithConcurrency = async (items, concurrency, task) => {
138
34
  const results = new Array(items.length);
139
35
  let next = 0;
@@ -147,33 +43,16 @@ const mapWithConcurrency = async (items, concurrency, task) => {
147
43
  await Promise.all(Array.from({ length: Math.min(concurrency, items.length) }, worker));
148
44
  return results;
149
45
  };
150
- /**
151
- * Reads many contract calls through Multicall3, splitting the work across as
152
- * many requests as the size budget needs and reassembling the results in order.
153
- *
154
- * Chunking is invisible to callers: the returned array lines up with `calls`
155
- * one-for-one exactly as it did when every call went out together, and a chunk
156
- * that rejects still rejects the whole read rather than returning a short array
157
- * that a caller would silently misread as "these tokens have no balance".
158
- *
159
- * @param options.chain - the chain, used for its Multicall3 deployment
160
- * @param options.client - the public client to read through
161
- * @param options.abi - the default ABI, when a call does not carry its own
162
- * @param options.calls - the calls to make, in the order results are wanted
163
- * @param options.target - the default contract, when a call does not name one
164
- */
165
46
  export const multicallRead = async ({ chain, client, abi, calls, target, }) => {
166
47
  const multicall = getContract({
167
48
  abi: multicall3Abi,
168
49
  address: chain.contracts.multicall3.address,
169
- // this seems like a bug in viem, it should be able to infer the type of the client
170
50
  client: client,
171
51
  });
172
52
  const arg = calls.map((call) => ({
173
53
  callData: encodeFunctionData({
174
54
  abi: call.abi || abi,
175
55
  functionName: call.functionName,
176
- // this seems like a bug in viem, it should be able to infer the type of the args
177
56
  args: (call.args || []),
178
57
  }),
179
58
  allowFailure: call.allowFailure || false,
@@ -198,9 +77,6 @@ export const multicallRead = async ({ chain, client, abi, calls, target, }) => {
198
77
  }));
199
78
  }
200
79
  catch (err) {
201
- // Deliberately a summary rather than the calls themselves: a failing read can
202
- // hold tens of thousands of them, and dumping the array cost more than the
203
- // failure did.
204
80
  console.error('Multicall aggregate3 failed', {
205
81
  chainId: client.chain?.id,
206
82
  target,