@gearbox-protocol/cli-utils 9.2.1 → 9.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/dist/index.js CHANGED
@@ -1,1384 +1,1147 @@
1
- import {
2
- addressArrayLike,
3
- addressLike,
4
- boolLike,
5
- clientWhitelistItemSchema,
6
- optionalAddressArrayLike,
7
- optionalStringArrayLike,
8
- stringArrayLike,
9
- whitelistItemSchema,
10
- whitelistItemsSchema
11
- } from "./chunk-GGX3YGGH.js";
12
-
13
- // src/CensoredString.ts
14
- var CensoredString = class _CensoredString {
15
- #value;
16
- #censored;
17
- static transform(value) {
18
- if (value instanceof _CensoredString) {
19
- return value;
20
- }
21
- return new _CensoredString(value);
22
- }
23
- constructor(value) {
24
- this.#value = value;
25
- const visibleLen = Math.min(4, Math.floor(0.2 * value.length));
26
- if (visibleLen < 1) {
27
- this.#censored = "*".repeat(Math.min(8, value.length));
28
- } else {
29
- this.#censored = "*".repeat(Math.min(8, value.length - visibleLen)) + value.slice(-1 * visibleLen);
30
- }
31
- }
32
- get value() {
33
- return this.#value;
34
- }
35
- toString() {
36
- return this.#censored;
37
- }
38
- toJSON() {
39
- return this.toString();
40
- }
41
- };
42
-
43
- // src/CensoredURL.ts
44
- var CensoredURL = class _CensoredURL {
45
- #value;
46
- #censored;
47
- static transform(value) {
48
- if (value instanceof _CensoredURL) {
49
- return value;
50
- }
51
- return new _CensoredURL(value);
52
- }
53
- constructor(url) {
54
- this.#value = url;
55
- if (url.includes("127.0.0.1") || url.includes("localhost") || url.includes("host.docker.internal")) {
56
- this.#censored = url;
57
- } else {
58
- try {
59
- const u = new URL(url);
60
- if (u.username || u.password) {
61
- u.username = u.username ? "****" : "";
62
- u.password = u.password ? "****" : "";
63
- }
64
- u.pathname = "/";
65
- u.search = "";
66
- this.#censored = `${u.href}******`;
67
- } catch {
68
- const visibleLen = Math.min(4, Math.floor(0.2 * url.length));
69
- if (visibleLen < 1) {
70
- this.#censored = "*".repeat(Math.min(8, url.length));
71
- } else {
72
- this.#censored = "*".repeat(Math.min(8, url.length - visibleLen)) + url.slice(-1 * visibleLen);
73
- }
74
- }
75
- }
76
- }
77
- get value() {
78
- return this.#value;
79
- }
80
- toString() {
81
- return this.#censored;
82
- }
83
- toJSON() {
84
- return this.toString();
85
- }
86
- };
87
-
88
- // src/compressedConfig.ts
89
- import { deflateSync, inflateSync } from "zlib";
90
-
91
- // src/resolveYamlFiles.ts
92
- import { readFile } from "fs/promises";
93
- import { dirname, resolve as pathResolve } from "path";
1
+ import { addressArrayLike, addressLike, boolLike, clientWhitelistItemSchema, optionalAddressArrayLike, optionalStringArrayLike, stringArrayLike, whitelistItemSchema, whitelistItemsSchema } from "./schema.js";
2
+ import { deflateSync, inflateSync } from "node:zlib";
3
+ import { readFile } from "node:fs/promises";
4
+ import { dirname, resolve } from "node:path";
94
5
  import { GetObjectCommand, S3Client } from "@aws-sdk/client-s3";
95
6
  import YAML, { isPair, isScalar, isSeq } from "yaml";
96
-
97
- // src/getPromisePath.ts
7
+ import { ADDRESS_0X0, AddressMap, SUPPORTED_NETWORKS, chains, getChain } from "@gearbox-protocol/sdk";
8
+ import { prettifyError } from "zod";
9
+ import { z } from "zod/v4";
10
+ import { withRetry } from "viem";
11
+ import { md } from "@vlad-yakovlev/telegram-md";
12
+ import Queue from "p-queue";
13
+ import { LRUCache } from "lru-cache";
14
+ import { Command, Option } from "@commander-js/extra-typings";
15
+ import * as z4 from "zod/v4/core";
16
+ import { GetSecretValueCommand, SecretsManagerClient } from "@aws-sdk/client-secrets-manager";
17
+ import { GetParameterCommand, SSMClient } from "@aws-sdk/client-ssm";
18
+ //#region src/CensoredString.ts
19
+ var CensoredString = class CensoredString {
20
+ #value;
21
+ #censored;
22
+ static transform(value) {
23
+ if (value instanceof CensoredString) return value;
24
+ return new CensoredString(value);
25
+ }
26
+ constructor(value) {
27
+ this.#value = value;
28
+ const visibleLen = Math.min(4, Math.floor(.2 * value.length));
29
+ if (visibleLen < 1) this.#censored = "*".repeat(Math.min(8, value.length));
30
+ else this.#censored = "*".repeat(Math.min(8, value.length - visibleLen)) + value.slice(-1 * visibleLen);
31
+ }
32
+ get value() {
33
+ return this.#value;
34
+ }
35
+ toString() {
36
+ return this.#censored;
37
+ }
38
+ toJSON() {
39
+ return this.toString();
40
+ }
41
+ };
42
+ //#endregion
43
+ //#region src/CensoredURL.ts
44
+ /**
45
+ * Assumes that host is always visible, and the secret part is in path or query
46
+ */
47
+ var CensoredURL = class CensoredURL {
48
+ #value;
49
+ #censored;
50
+ static transform(value) {
51
+ if (value instanceof CensoredURL) return value;
52
+ return new CensoredURL(value);
53
+ }
54
+ constructor(url) {
55
+ this.#value = url;
56
+ if (url.includes("127.0.0.1") || url.includes("localhost") || url.includes("host.docker.internal")) this.#censored = url;
57
+ else try {
58
+ const u = new URL(url);
59
+ if (u.username || u.password) {
60
+ u.username = u.username ? "****" : "";
61
+ u.password = u.password ? "****" : "";
62
+ }
63
+ u.pathname = "/";
64
+ u.search = "";
65
+ this.#censored = `${u.href}******`;
66
+ } catch {
67
+ const visibleLen = Math.min(4, Math.floor(.2 * url.length));
68
+ if (visibleLen < 1) this.#censored = "*".repeat(Math.min(8, url.length));
69
+ else this.#censored = "*".repeat(Math.min(8, url.length - visibleLen)) + url.slice(-1 * visibleLen);
70
+ }
71
+ }
72
+ get value() {
73
+ return this.#value;
74
+ }
75
+ toString() {
76
+ return this.#censored;
77
+ }
78
+ toJSON() {
79
+ return this.toString();
80
+ }
81
+ };
82
+ //#endregion
83
+ //#region src/getPromisePath.ts
84
+ /**
85
+ * Similar to lodash.get, but supports promises
86
+ * @param object
87
+ * @param keys
88
+ * @returns
89
+ */
98
90
  async function getPromisePath(object, path) {
99
- return getPromised(object, path.split("."));
91
+ return getPromised(object, path.split("."));
100
92
  }
101
93
  async function getPromised(object, keys) {
102
- if (keys.length === 0) {
103
- return Promise.resolve(object);
104
- }
105
- const [key, ...rest] = keys;
106
- const value = object[key];
107
- const resolved = await Promise.resolve(value);
108
- return getPromised(resolved, rest);
94
+ if (keys.length === 0) return Promise.resolve(object);
95
+ const [key, ...rest] = keys;
96
+ const value = object[key];
97
+ return getPromised(await Promise.resolve(value), rest);
109
98
  }
110
-
111
- // src/resolveYamlFiles.ts
112
- var templateKeyRegex = /\$\{([\w-]+(?:\.[\w-]+)*)(?::-([^}]*))?\}/g;
99
+ //#endregion
100
+ //#region src/resolveYamlFiles.ts
101
+ /**
102
+ * Template key regexp
103
+ * Consists of alphanumeric+underscore parts separated by dots, all wrappped in env-style brackets
104
+ * e.g. ${KEY} or ${KEY.SUBKEY}
105
+ *
106
+ * Optionally supports a bash-style default value via `:-`, e.g. `${KEY:-fallback}`.
107
+ * The default is used when the resolved value is undefined, null, or an empty string.
108
+ * Capture groups: 1 = key path, 2 = optional default literal (no `}` allowed).
109
+ */
110
+ const templateKeyRegex = /\$\{([\w-]+(?:\.[\w-]+)*)(?::-([^}]*))?\}/g;
111
+ /**
112
+ * Replaces all `${...}` template tokens in a string using the provided template data.
113
+ * Supports bash-style `${KEY:-default}` fallback when the resolved value is missing
114
+ * (undefined, null, or empty string). The default is a literal and is not further
115
+ * interpolated.
116
+ */
113
117
  async function resolveScalarValue(val, templateData) {
114
- const matches = val.matchAll(templateKeyRegex);
115
- for (const match of matches) {
116
- const [whole, key, defaultValue] = match;
117
- const resolved = await getPromisePath(templateData, key);
118
- if (resolved !== void 0 && resolved !== null && resolved !== "") {
119
- val = val.replace(whole, String(resolved));
120
- } else if (defaultValue !== void 0) {
121
- val = val.replace(whole, defaultValue);
122
- }
123
- }
124
- return val;
118
+ const matches = val.matchAll(templateKeyRegex);
119
+ for (const match of matches) {
120
+ const [whole, key, defaultValue] = match;
121
+ const resolved = await getPromisePath(templateData, key);
122
+ if (resolved !== void 0 && resolved !== null && resolved !== "") val = val.replace(whole, String(resolved));
123
+ else if (defaultValue !== void 0) val = val.replace(whole, defaultValue);
124
+ }
125
+ return val;
125
126
  }
127
+ /**
128
+ * Merges yaml files, respecting .extends references.
129
+ * Performs env variable substitution on the config.
130
+ * Respects YAML anchors and aliases, and merge keys - so that they can be shared across two files
131
+ *
132
+ * x-* anchors are removed from resulting document (a-la docker compose yaml)
133
+ *
134
+ * @param file
135
+ * @param templateData
136
+ * @returns
137
+ */
126
138
  async function resolveYamlFiles(file, templateData) {
127
- const doc = await resolveYamlDoc(file, templateData);
128
- const obj = doc.toJSON();
129
- return Object.fromEntries(
130
- Object.entries(obj).filter(([k]) => !k.startsWith("x-"))
131
- );
139
+ const obj = (await resolveYamlDoc(file, templateData)).toJSON();
140
+ return Object.fromEntries(Object.entries(obj).filter(([k]) => !k.startsWith("x-")));
132
141
  }
142
+ /**
143
+ * Merges yaml documents, respecting .extends references.
144
+ * Optionally, performs env-style variable substitution on the config.
145
+ * Respects YAML anchors and aliases, and merge keys - so that they can be shared across two files
146
+ * Supports s3 links for file
147
+ *
148
+ * @param file
149
+ * @param templateData
150
+ * @returns
151
+ */
133
152
  async function resolveYamlDoc(file, templateData) {
134
- const content = await loadFile(file);
135
- let cfgRaw = YAML.parseDocument(content, { merge: true });
136
- if (cfgRaw.has("extends")) {
137
- const baseFile = cfgRaw.get("extends");
138
- const extended = cfgRaw;
139
- cfgRaw = await resolveYamlDoc(
140
- getRelativePath(file, baseFile),
141
- templateData
142
- );
143
- YAML.visit(extended, {
144
- Pair(_, pair, rawPath) {
145
- const path = getNodesPath(rawPath);
146
- if (isScalar(pair.key) && (isScalar(pair.value) || isSeq(pair.value))) {
147
- if (pair.key.value === "extends") {
148
- return;
149
- }
150
- cfgRaw.setIn([...path, pair.key.value], pair.value);
151
- return YAML.visit.SKIP;
152
- }
153
- }
154
- });
155
- }
156
- await YAML.visitAsync(cfgRaw, {
157
- Scalar: async (_, node) => {
158
- if (typeof node.value === "string") {
159
- node.value = await resolveScalarValue(node.value, templateData);
160
- }
161
- }
162
- });
163
- return cfgRaw;
153
+ const content = await loadFile(file);
154
+ let cfgRaw = YAML.parseDocument(content, { merge: true });
155
+ if (cfgRaw.has("extends")) {
156
+ const baseFile = cfgRaw.get("extends");
157
+ const extended = cfgRaw;
158
+ cfgRaw = await resolveYamlDoc(getRelativePath(file, baseFile), templateData);
159
+ YAML.visit(extended, { Pair(_, pair, rawPath) {
160
+ const path = getNodesPath(rawPath);
161
+ if (isScalar(pair.key) && (isScalar(pair.value) || isSeq(pair.value))) {
162
+ if (pair.key.value === "extends") return;
163
+ cfgRaw.setIn([...path, pair.key.value], pair.value);
164
+ return YAML.visit.SKIP;
165
+ }
166
+ } });
167
+ }
168
+ await YAML.visitAsync(cfgRaw, { Scalar: async (_, node) => {
169
+ if (typeof node.value === "string") node.value = await resolveScalarValue(node.value, templateData);
170
+ } });
171
+ return cfgRaw;
164
172
  }
173
+ /**
174
+ * Helper function that gets object with templated values
175
+ * Just because async ast already exists in yaml, but not in json reviver
176
+ * @param json
177
+ * @param templateData
178
+ * @returns
179
+ */
165
180
  async function resolveFromObject(json, templateData) {
166
- const content = YAML.stringify(json);
167
- const cfgRaw = YAML.parseDocument(content, { merge: true });
168
- await YAML.visitAsync(cfgRaw, {
169
- Scalar: async (_, node) => {
170
- if (typeof node.value === "string") {
171
- node.value = await resolveScalarValue(node.value, templateData);
172
- }
173
- }
174
- });
175
- return cfgRaw.toJSON();
181
+ const content = YAML.stringify(json);
182
+ const cfgRaw = YAML.parseDocument(content, { merge: true });
183
+ await YAML.visitAsync(cfgRaw, { Scalar: async (_, node) => {
184
+ if (typeof node.value === "string") node.value = await resolveScalarValue(node.value, templateData);
185
+ } });
186
+ return cfgRaw.toJSON();
176
187
  }
177
188
  function getNodesPath(nodes) {
178
- return nodes.map((n) => {
179
- if (isPair(n) && isScalar(n.key)) {
180
- return n.key.value;
181
- }
182
- return "";
183
- }).filter(Boolean);
189
+ return nodes.map((n) => {
190
+ if (isPair(n) && isScalar(n.key)) return n.key.value;
191
+ return "";
192
+ }).filter(Boolean);
184
193
  }
194
+ /**
195
+ * Loads file from s3 or local file system
196
+ * @param file - s3 link or local file path
197
+ * @returns file content
198
+ */
185
199
  async function loadFile(file) {
186
- if (file.startsWith("s3://")) {
187
- return await loadS3File(file);
188
- }
189
- return await readFile(file, "utf-8");
200
+ if (file.startsWith("s3://")) return await loadS3File(file);
201
+ return await readFile(file, "utf-8");
190
202
  }
203
+ /**
204
+ * Loads file from s3
205
+ * @param file - s3 link like s3://bucket/path/to/file.yaml
206
+ * @returns file content
207
+ */
191
208
  async function loadS3File(file) {
192
- const url = new URL(file);
193
- const bucket = url.hostname;
194
- const key = url.pathname.slice(1);
195
- const s3 = new S3Client({});
196
- const response = await s3.send(
197
- new GetObjectCommand({
198
- Bucket: bucket,
199
- Key: key
200
- })
201
- );
202
- if (!response.Body) {
203
- throw new Error(`file ${file} not found`);
204
- }
205
- return response.Body.transformToString("utf-8");
209
+ const url = new URL(file);
210
+ const bucket = url.hostname;
211
+ const key = url.pathname.slice(1);
212
+ const response = await new S3Client({}).send(new GetObjectCommand({
213
+ Bucket: bucket,
214
+ Key: key
215
+ }));
216
+ if (!response.Body) throw new Error(`file ${file} not found`);
217
+ return response.Body.transformToString("utf-8");
206
218
  }
219
+ /**
220
+ * Given file and a relative file path, returns full path of the relative file
221
+ * Supports s3 links for base file
222
+ * @param file
223
+ * @param relativeFile
224
+ */
207
225
  function getRelativePath(baseFile, relativeFile) {
208
- if (baseFile.startsWith("s3://")) {
209
- const url = new URL(baseFile);
210
- const relPathname = pathResolve(dirname(url.pathname), relativeFile);
211
- url.pathname = relPathname;
212
- return url.toString();
213
- }
214
- return pathResolve(dirname(baseFile), relativeFile);
226
+ if (baseFile.startsWith("s3://")) {
227
+ const url = new URL(baseFile);
228
+ url.pathname = resolve(dirname(url.pathname), relativeFile);
229
+ return url.toString();
230
+ }
231
+ return resolve(dirname(baseFile), relativeFile);
215
232
  }
216
-
217
- // src/compressedConfig.ts
233
+ //#endregion
234
+ //#region src/compressedConfig.ts
235
+ /**
236
+ * Compress a plain JS object into a base64url string
237
+ * suitable for env vars.
238
+ */
218
239
  function compressObject(obj) {
219
- const json = JSON.stringify(obj);
220
- const compressed = deflateSync(Buffer.from(json, "utf8"));
221
- return compressed.toString("base64url");
240
+ const json = JSON.stringify(obj);
241
+ return deflateSync(Buffer.from(json, "utf8")).toString("base64url");
222
242
  }
243
+ /**
244
+ * Decompress a base64url string back into a JS object
245
+ * Allows to pass template data to resolve promises/env
246
+ */
223
247
  async function decompressObject(value, templateData) {
224
- const compressed = Buffer.from(value, "base64url");
225
- const str = inflateSync(compressed).toString("utf8");
226
- const json = JSON.parse(str);
227
- const result = await resolveFromObject(json, templateData);
228
- return result;
248
+ const str = inflateSync(Buffer.from(value, "base64url")).toString("utf8");
249
+ return await resolveFromObject(JSON.parse(str), templateData);
229
250
  }
230
-
231
- // src/createPinoCensor.ts
232
- var MIN_SECRET_LENGTH = 4;
251
+ //#endregion
252
+ //#region src/createPinoCensor.ts
253
+ /**
254
+ * Raw values shorter than this are not censored, to avoid masking unrelated
255
+ * substrings throughout the log output.
256
+ */
257
+ const MIN_SECRET_LENGTH = 4;
233
258
  function escapeRegExp(value) {
234
- return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
259
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
235
260
  }
236
261
  function collectSecrets(value, out) {
237
- if (value == null) {
238
- return;
239
- }
240
- if (value instanceof CensoredString || value instanceof CensoredURL) {
241
- const raw = value.value;
242
- if (raw.length >= MIN_SECRET_LENGTH) {
243
- out.set(raw, value.toString());
244
- }
245
- return;
246
- }
247
- if (typeof value !== "object") {
248
- return;
249
- }
250
- if (Array.isArray(value)) {
251
- for (const item of value) {
252
- collectSecrets(item, out);
253
- }
254
- return;
255
- }
256
- for (const item of Object.values(value)) {
257
- collectSecrets(item, out);
258
- }
262
+ if (value == null) return;
263
+ if (value instanceof CensoredString || value instanceof CensoredURL) {
264
+ const raw = value.value;
265
+ if (raw.length >= MIN_SECRET_LENGTH) out.set(raw, value.toString());
266
+ return;
267
+ }
268
+ if (typeof value !== "object") return;
269
+ if (Array.isArray(value)) {
270
+ for (const item of value) collectSecrets(item, out);
271
+ return;
272
+ }
273
+ for (const item of Object.values(value)) collectSecrets(item, out);
259
274
  }
275
+ /**
276
+ * Gathers known secrets from a config and returns a function that replaces each raw secret with its masked representation.
277
+ * Intended for pino's `hooks.streamWrite`
278
+ */
260
279
  function createPinoCensor(config) {
261
- const map = /* @__PURE__ */ new Map();
262
- collectSecrets(config, map);
263
- if (map.size === 0) {
264
- return (s) => s;
265
- }
266
- const pattern = [...map.keys()].sort((a, b) => b.length - a.length).map(escapeRegExp).join("|");
267
- const re = new RegExp(pattern, "g");
268
- return (s) => s.replace(re, (m) => map.get(m) ?? m);
280
+ const map = /* @__PURE__ */ new Map();
281
+ collectSecrets(config, map);
282
+ if (map.size === 0) return (s) => s;
283
+ const pattern = [...map.keys()].sort((a, b) => b.length - a.length).map(escapeRegExp).join("|");
284
+ const re = new RegExp(pattern, "g");
285
+ return (s) => s.replace(re, (m) => map.get(m) ?? m);
269
286
  }
270
-
271
- // src/sleep.ts
287
+ //#endregion
288
+ //#region src/sleep.ts
272
289
  async function sleep(ms) {
273
- return new Promise((resolve) => {
274
- setTimeout(resolve, ms);
275
- });
290
+ return new Promise((resolve) => {
291
+ setTimeout(resolve, ms);
292
+ });
276
293
  }
277
-
278
- // src/fetchRetry.ts
279
- var DEFAULT_RETRIES = 5;
280
- var DEFAULT_RETRY_INTERVAL = 1e3;
294
+ //#endregion
295
+ //#region src/fetchRetry.ts
296
+ const DEFAULT_RETRIES = 5;
297
+ const DEFAULT_RETRY_INTERVAL$2 = 1e3;
281
298
  async function fetchRetry(input, init = {}) {
282
- const {
283
- retries = DEFAULT_RETRIES,
284
- retryInterval = DEFAULT_RETRY_INTERVAL,
285
- validateResponse,
286
- ...options
287
- } = init;
288
- try {
289
- const response = await fetch(input, options);
290
- validateResponse?.(response);
291
- return response;
292
- } catch (e) {
293
- if (retries > 0) {
294
- await sleep(retryInterval);
295
- return fetchRetry(input, {
296
- ...options,
297
- retries: retries - 1,
298
- retryInterval,
299
- validateResponse
300
- });
301
- } else {
302
- throw e;
303
- }
304
- }
299
+ const { retries = DEFAULT_RETRIES, retryInterval = DEFAULT_RETRY_INTERVAL$2, validateResponse, ...options } = init;
300
+ try {
301
+ const response = await fetch(input, options);
302
+ validateResponse?.(response);
303
+ return response;
304
+ } catch (e) {
305
+ if (retries > 0) {
306
+ await sleep(retryInterval);
307
+ return fetchRetry(input, {
308
+ ...options,
309
+ retries: retries - 1,
310
+ retryInterval,
311
+ validateResponse
312
+ });
313
+ } else throw e;
314
+ }
305
315
  }
306
-
307
- // src/providers.ts
308
- import { chains, getChain } from "@gearbox-protocol/sdk";
316
+ //#endregion
317
+ //#region src/providers.ts
309
318
  function getAlchemyUrl(network, apiKey, protocol = "http") {
310
- const alchemyDomain = ALCHEMY_DOMAINS[network];
311
- if (!alchemyDomain) {
312
- return void 0;
313
- }
314
- return `${protocol}s://${alchemyDomain}.g.alchemy.com/v2/${apiKey}`;
319
+ const alchemyDomain = ALCHEMY_DOMAINS[network];
320
+ if (!alchemyDomain) return;
321
+ return `${protocol}s://${alchemyDomain}.g.alchemy.com/v2/${apiKey}`;
315
322
  }
316
- var DRPC_NETS = {
317
- Arbitrum: "arbitrum",
318
- Base: "base",
319
- BNB: "bsc",
320
- Mainnet: "ethereum",
321
- Optimism: "optimism",
322
- Sonic: "sonic",
323
- WorldChain: "worldchain",
324
- Berachain: "berachain",
325
- Avalanche: "avalanche",
326
- Monad: "monad",
327
- Hemi: "hemi",
328
- Lisk: "lisk",
329
- MegaETH: null,
330
- Etherlink: null,
331
- Plasma: "plasma",
332
- Somnia: null
323
+ const DRPC_NETS = {
324
+ Arbitrum: "arbitrum",
325
+ Base: "base",
326
+ BNB: "bsc",
327
+ Mainnet: "ethereum",
328
+ Optimism: "optimism",
329
+ Sonic: "sonic",
330
+ WorldChain: "worldchain",
331
+ Berachain: "berachain",
332
+ Avalanche: "avalanche",
333
+ Monad: "monad",
334
+ Hemi: "hemi",
335
+ Lisk: "lisk",
336
+ MegaETH: null,
337
+ Etherlink: null,
338
+ Plasma: "plasma",
339
+ Somnia: null
333
340
  };
334
- var ALCHEMY_DOMAINS = {
335
- Mainnet: "eth-mainnet",
336
- Arbitrum: "arb-mainnet",
337
- Optimism: "opt-mainnet",
338
- Base: "base-mainnet",
339
- Sonic: "sonic-mainnet",
340
- Monad: "monad-mainnet",
341
- Berachain: "berachain-mainnet",
342
- Avalanche: "avax-mainnet",
343
- BNB: "bnb-mainnet",
344
- WorldChain: "worldchain-mainnet",
345
- MegaETH: null,
346
- Etherlink: null,
347
- Hemi: null,
348
- Lisk: null,
349
- Plasma: "plasma-mainnet",
350
- Somnia: null
341
+ const ALCHEMY_DOMAINS = {
342
+ Mainnet: "eth-mainnet",
343
+ Arbitrum: "arb-mainnet",
344
+ Optimism: "opt-mainnet",
345
+ Base: "base-mainnet",
346
+ Sonic: "sonic-mainnet",
347
+ Monad: "monad-mainnet",
348
+ Berachain: "berachain-mainnet",
349
+ Avalanche: "avax-mainnet",
350
+ BNB: "bnb-mainnet",
351
+ WorldChain: "worldchain-mainnet",
352
+ MegaETH: null,
353
+ Etherlink: null,
354
+ Hemi: null,
355
+ Lisk: null,
356
+ Plasma: "plasma-mainnet",
357
+ Somnia: null
351
358
  };
352
359
  function getDrpcUrl(network, apiKey, protocol = "http") {
353
- const net = DRPC_NETS[network];
354
- return net ? `${protocol}s://lb.drpc.org/${net}/${apiKey}` : void 0;
360
+ const net = DRPC_NETS[network];
361
+ return net ? `${protocol}s://lb.drpc.org/${net}/${apiKey}` : void 0;
355
362
  }
356
- var ANKR_DOMAINS = {
357
- Arbitrum: "arbitrum",
358
- Avalanche: "avalanche",
359
- Base: "base",
360
- Berachain: null,
361
- BNB: "bsc",
362
- Etherlink: "etherlink_mainnet",
363
- Hemi: null,
364
- Lisk: null,
365
- Mainnet: "eth",
366
- MegaETH: null,
367
- Monad: null,
368
- Optimism: "optimism",
369
- Plasma: null,
370
- Sonic: "sonic_mainnet",
371
- WorldChain: null,
372
- Somnia: "somnia_mainnet"
363
+ const ANKR_DOMAINS = {
364
+ Arbitrum: "arbitrum",
365
+ Avalanche: "avalanche",
366
+ Base: "base",
367
+ Berachain: null,
368
+ BNB: "bsc",
369
+ Etherlink: "etherlink_mainnet",
370
+ Hemi: null,
371
+ Lisk: null,
372
+ Mainnet: "eth",
373
+ MegaETH: null,
374
+ Monad: null,
375
+ Optimism: "optimism",
376
+ Plasma: null,
377
+ Sonic: "sonic_mainnet",
378
+ WorldChain: null,
379
+ Somnia: "somnia_mainnet"
373
380
  };
374
381
  function getAnkrUrl(network, apiKey, protocol = "http") {
375
- const net = ANKR_DOMAINS[network];
376
- const sep = protocol === "ws" ? "/ws/" : "/";
377
- return net ? `${protocol}s://rpc.ankr.com/${net}${sep}${apiKey}` : void 0;
382
+ const net = ANKR_DOMAINS[network];
383
+ return net ? `${protocol}s://rpc.ankr.com/${net}${protocol === "ws" ? "/ws/" : "/"}${apiKey}` : void 0;
378
384
  }
379
- var THIRDWEB_DOMAINS = {
380
- Arbitrum: chains.Arbitrum.id.toString(),
381
- Avalanche: chains.Avalanche.id.toString(),
382
- Base: chains.Base.id.toString(),
383
- Berachain: chains.Berachain.id.toString(),
384
- BNB: chains.BNB.id.toString(),
385
- Etherlink: chains.Etherlink.id.toString(),
386
- Hemi: chains.Hemi.id.toString(),
387
- Lisk: chains.Lisk.id.toString(),
388
- Mainnet: chains.Mainnet.id.toString(),
389
- MegaETH: chains.MegaETH.id.toString(),
390
- Monad: chains.Monad.id.toString(),
391
- Optimism: chains.Optimism.id.toString(),
392
- Plasma: chains.Plasma.id.toString(),
393
- Sonic: chains.Sonic.id.toString(),
394
- WorldChain: chains.WorldChain.id.toString(),
395
- Somnia: chains.Somnia.id.toString()
385
+ const THIRDWEB_DOMAINS = {
386
+ Arbitrum: chains.Arbitrum.id.toString(),
387
+ Avalanche: chains.Avalanche.id.toString(),
388
+ Base: chains.Base.id.toString(),
389
+ Berachain: chains.Berachain.id.toString(),
390
+ BNB: chains.BNB.id.toString(),
391
+ Etherlink: chains.Etherlink.id.toString(),
392
+ Hemi: chains.Hemi.id.toString(),
393
+ Lisk: chains.Lisk.id.toString(),
394
+ Mainnet: chains.Mainnet.id.toString(),
395
+ MegaETH: chains.MegaETH.id.toString(),
396
+ Monad: chains.Monad.id.toString(),
397
+ Optimism: chains.Optimism.id.toString(),
398
+ Plasma: chains.Plasma.id.toString(),
399
+ Sonic: chains.Sonic.id.toString(),
400
+ WorldChain: chains.WorldChain.id.toString(),
401
+ Somnia: chains.Somnia.id.toString()
396
402
  };
397
403
  function getThirdWebUrl(network, apiKey, protocol = "http") {
398
- if (protocol === "ws") {
399
- return void 0;
400
- }
401
- const net = THIRDWEB_DOMAINS[network];
402
- return `https://${net}.rpc.thirdweb.com/${apiKey}`;
404
+ if (protocol === "ws") return;
405
+ return `https://${THIRDWEB_DOMAINS[network]}.rpc.thirdweb.com/${apiKey}`;
403
406
  }
404
407
  function getErpcKey(network, projectId, urlBase) {
405
- if (!projectId || !urlBase) {
406
- return void 0;
407
- }
408
- const chain = getChain(network);
409
- return `${urlBase}/${projectId}/evm/${chain.id}`;
408
+ if (!projectId || !urlBase) return;
409
+ return `${urlBase}/${projectId}/evm/${getChain(network).id}`;
410
410
  }
411
-
412
- // src/getProviders.ts
411
+ //#endregion
412
+ //#region src/getProviders.ts
413
413
  function getProviders(config, logger) {
414
- const {
415
- network,
416
- enabledProviders = [
417
- "erpc",
418
- "custom",
419
- "drpc",
420
- "alchemy",
421
- "ankr",
422
- "thirdweb"
423
- ],
424
- alchemyKeys = [],
425
- ankrKeys = [],
426
- drpcKeys = [],
427
- thirdwebKeys = [],
428
- jsonRpcProviders = [],
429
- erpcProjectId,
430
- erpcUrl
431
- } = config;
432
- const result = [];
433
- for (const p of enabledProviders) {
434
- switch (p) {
435
- case "alchemy":
436
- result.push(
437
- ...alchemyKeys.map((k) => ({
438
- name: "alchemy",
439
- url: getAlchemyUrl(network, k.value)
440
- }))
441
- );
442
- break;
443
- case "drpc":
444
- result.push(
445
- ...drpcKeys.map((k) => ({
446
- name: "drpc",
447
- url: getDrpcUrl(network, k.value)
448
- }))
449
- );
450
- break;
451
- case "ankr":
452
- result.push(
453
- ...ankrKeys.map((k) => ({
454
- name: "ankr",
455
- url: getAnkrUrl(network, k.value)
456
- }))
457
- );
458
- break;
459
- case "thirdweb":
460
- result.push(
461
- ...thirdwebKeys.map((k) => ({
462
- name: "thirdweb",
463
- url: getThirdWebUrl(network, k.value)
464
- }))
465
- );
466
- break;
467
- case "custom":
468
- result.push(
469
- ...jsonRpcProviders.map((url) => {
470
- const urlObj = new URL(url.value);
471
- const hasCredentials = urlObj.username || urlObj.password;
472
- let cleanUrl = url.value;
473
- let httpTransportOptions = {};
474
- if (hasCredentials) {
475
- const cleanUrlObj = new URL(url.value);
476
- cleanUrlObj.username = "";
477
- cleanUrlObj.password = "";
478
- cleanUrl = cleanUrlObj.toString();
479
- const credentials = `${urlObj.username}:${urlObj.password}`;
480
- const encodedCredentials = Buffer.from(credentials).toString("base64");
481
- httpTransportOptions = {
482
- fetchOptions: {
483
- headers: {
484
- Authorization: `Basic ${encodedCredentials}`
485
- }
486
- }
487
- };
488
- }
489
- return {
490
- name: getTopLevelDomain(urlObj),
491
- url: cleanUrl,
492
- httpTransportOptions
493
- };
494
- })
495
- );
496
- break;
497
- case "erpc":
498
- if (erpcProjectId && erpcUrl) {
499
- result.push({
500
- name: "erpc",
501
- url: getErpcKey(network, erpcProjectId, erpcUrl)
502
- });
503
- }
504
- break;
505
- }
506
- }
507
- if (logger) {
508
- const providers = result.filter((p) => !!p.url).map((p) => ({
509
- name: p.name,
510
- url: new CensoredURL(p.url)
511
- }));
512
- logger.debug({ providers, network }, "gathered providers");
513
- }
514
- return result.map((p) => ({
515
- ...p,
516
- httpClientOptions: {
517
- ...p.httpTransportOptions,
518
- methods: { exclude: ["eth_fillTransaction"] }
519
- }
520
- })).filter((p) => !!p.url);
414
+ const { network, enabledProviders = [
415
+ "erpc",
416
+ "custom",
417
+ "drpc",
418
+ "alchemy",
419
+ "ankr",
420
+ "thirdweb"
421
+ ], alchemyKeys = [], ankrKeys = [], drpcKeys = [], thirdwebKeys = [], jsonRpcProviders = [], erpcProjectId, erpcUrl } = config;
422
+ const result = [];
423
+ for (const p of enabledProviders) switch (p) {
424
+ case "alchemy":
425
+ result.push(...alchemyKeys.map((k) => ({
426
+ name: "alchemy",
427
+ url: getAlchemyUrl(network, k.value)
428
+ })));
429
+ break;
430
+ case "drpc":
431
+ result.push(...drpcKeys.map((k) => ({
432
+ name: "drpc",
433
+ url: getDrpcUrl(network, k.value)
434
+ })));
435
+ break;
436
+ case "ankr":
437
+ result.push(...ankrKeys.map((k) => ({
438
+ name: "ankr",
439
+ url: getAnkrUrl(network, k.value)
440
+ })));
441
+ break;
442
+ case "thirdweb":
443
+ result.push(...thirdwebKeys.map((k) => ({
444
+ name: "thirdweb",
445
+ url: getThirdWebUrl(network, k.value)
446
+ })));
447
+ break;
448
+ case "custom":
449
+ result.push(...jsonRpcProviders.map((url) => {
450
+ const urlObj = new URL(url.value);
451
+ const hasCredentials = urlObj.username || urlObj.password;
452
+ let cleanUrl = url.value;
453
+ let httpTransportOptions = {};
454
+ if (hasCredentials) {
455
+ const cleanUrlObj = new URL(url.value);
456
+ cleanUrlObj.username = "";
457
+ cleanUrlObj.password = "";
458
+ cleanUrl = cleanUrlObj.toString();
459
+ const credentials = `${urlObj.username}:${urlObj.password}`;
460
+ httpTransportOptions = { fetchOptions: { headers: { Authorization: `Basic ${Buffer.from(credentials).toString("base64")}` } } };
461
+ }
462
+ return {
463
+ name: getTopLevelDomain(urlObj),
464
+ url: cleanUrl,
465
+ httpTransportOptions
466
+ };
467
+ }));
468
+ break;
469
+ case "erpc":
470
+ if (erpcProjectId && erpcUrl) result.push({
471
+ name: "erpc",
472
+ url: getErpcKey(network, erpcProjectId, erpcUrl)
473
+ });
474
+ break;
475
+ }
476
+ if (logger) {
477
+ const providers = result.filter((p) => !!p.url).map((p) => ({
478
+ name: p.name,
479
+ url: new CensoredURL(p.url)
480
+ }));
481
+ logger.debug({
482
+ providers,
483
+ network
484
+ }, "gathered providers");
485
+ }
486
+ return result.map((p) => ({
487
+ ...p,
488
+ httpClientOptions: {
489
+ ...p.httpTransportOptions,
490
+ methods: { exclude: ["eth_fillTransaction"] }
491
+ }
492
+ })).filter((p) => !!p.url);
521
493
  }
494
+ /**
495
+ * Take the last two parts for top-level domain (e.g., "google.com" from "a.b.c.google.com")
496
+ **/
522
497
  function getTopLevelDomain({ hostname }) {
523
- const parts = hostname.split(".");
524
- return parts.length >= 2 ? parts.slice(-2).join(".") : hostname;
498
+ const parts = hostname.split(".");
499
+ return parts.length >= 2 ? parts.slice(-2).join(".") : hostname;
525
500
  }
526
-
527
- // src/getFirstHttpTransportOptions.ts
501
+ //#endregion
502
+ //#region src/getFirstHttpTransportOptions.ts
528
503
  function getFirstHttpTransportOptions(config) {
529
- const [provider] = getProviders(config);
530
- if (!provider) {
531
- throw new Error("No provider found");
532
- }
533
- return {
534
- url: provider.url,
535
- config: {
536
- ...provider.httpTransportOptions
537
- }
538
- };
504
+ const [provider] = getProviders(config);
505
+ if (!provider) throw new Error("No provider found");
506
+ return {
507
+ url: provider.url,
508
+ config: { ...provider.httpTransportOptions }
509
+ };
539
510
  }
540
-
541
- // src/notifications/createNotifier.ts
542
- import { prettifyError } from "zod";
543
-
544
- // src/notifications/SlackNotifier.ts
511
+ //#endregion
512
+ //#region src/notifications/SlackNotifier.ts
545
513
  var SlackNotifier = class {
546
- #config;
547
- #logger;
548
- constructor(config, logger) {
549
- this.#config = config;
550
- this.#logger = logger;
551
- }
552
- notify(message, severity) {
553
- if (this.#config.alertsOnly && severity === "notification") {
554
- return;
555
- }
556
- const emoji = severity === "alert" ? "\u{1F6A8}" : "\u2139\uFE0F";
557
- const messageText = typeof message === "string" ? message : message.plain;
558
- void this.#sendToSlack(`${emoji} Alert: ${messageText}`);
559
- }
560
- async #sendToSlack(text) {
561
- try {
562
- this.#logger?.debug(`Sending message to Slack: ${text}`);
563
- const response = await fetch(this.#config.webhookUrl.value, {
564
- method: "POST",
565
- headers: {
566
- "Content-Type": "application/json"
567
- },
568
- body: JSON.stringify({ text })
569
- });
570
- if (!response.ok) {
571
- throw new Error(`HTTP error! status: ${response.status}`);
572
- }
573
- this.#logger?.debug("Slack message sent successfully");
574
- } catch (error) {
575
- this.#logger?.error("Error sending Slack message:", error);
576
- throw error;
577
- }
578
- }
514
+ #config;
515
+ #logger;
516
+ constructor(config, logger) {
517
+ this.#config = config;
518
+ this.#logger = logger;
519
+ }
520
+ notify(message, severity) {
521
+ if (this.#config.alertsOnly && severity === "notification") return;
522
+ const emoji = severity === "alert" ? "🚨" : "ℹ️";
523
+ const messageText = typeof message === "string" ? message : message.plain;
524
+ this.#sendToSlack(`${emoji} Alert: ${messageText}`);
525
+ }
526
+ async #sendToSlack(text) {
527
+ try {
528
+ this.#logger?.debug(`Sending message to Slack: ${text}`);
529
+ const response = await fetch(this.#config.webhookUrl.value, {
530
+ method: "POST",
531
+ headers: { "Content-Type": "application/json" },
532
+ body: JSON.stringify({ text })
533
+ });
534
+ if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`);
535
+ this.#logger?.debug("Slack message sent successfully");
536
+ } catch (error) {
537
+ this.#logger?.error("Error sending Slack message:", error);
538
+ throw error;
539
+ }
540
+ }
579
541
  };
580
-
581
- // src/notifications/schema.ts
582
- import { z } from "zod/v4";
583
- var TelegramConfig = z.object({
584
- type: z.literal("telegram"),
585
- token: z.union([z.string(), z.instanceof(CensoredString)]).transform(CensoredString.transform),
586
- alertsChannel: z.union([z.string().startsWith("-"), z.instanceof(CensoredString)]).transform(CensoredString.transform),
587
- /**
588
- * Optional channel to send notifications to
589
- * If not set, notifications will not be sent
590
- */
591
- notificationsChannel: z.union([z.string().startsWith("-"), z.instanceof(CensoredString)]).transform(CensoredString.transform).optional(),
592
- prefix: z.string().optional()
542
+ //#endregion
543
+ //#region src/notifications/schema.ts
544
+ const TelegramConfig = z.object({
545
+ type: z.literal("telegram"),
546
+ token: z.union([z.string(), z.instanceof(CensoredString)]).transform(CensoredString.transform),
547
+ alertsChannel: z.union([z.string().startsWith("-"), z.instanceof(CensoredString)]).transform(CensoredString.transform),
548
+ /**
549
+ * Optional channel to send notifications to
550
+ * If not set, notifications will not be sent
551
+ */
552
+ notificationsChannel: z.union([z.string().startsWith("-"), z.instanceof(CensoredString)]).transform(CensoredString.transform).optional(),
553
+ prefix: z.string().optional()
593
554
  });
594
- var SlackConfig = z.object({
595
- type: z.literal("slack"),
596
- /**
597
- * Slack webhook URL for sending notifications
598
- */
599
- webhookUrl: z.union([z.string(), z.instanceof(CensoredString)]).transform(CensoredString.transform),
600
- /**
601
- * If true, only alerts will be sent. Informational notifications are suppressed
602
- */
603
- alertsOnly: boolLike().optional()
555
+ const SlackConfig = z.object({
556
+ type: z.literal("slack"),
557
+ /**
558
+ * Slack webhook URL for sending notifications
559
+ */
560
+ webhookUrl: z.union([z.string(), z.instanceof(CensoredString)]).transform(CensoredString.transform),
561
+ /**
562
+ * If true, only alerts will be sent. Informational notifications are suppressed
563
+ */
564
+ alertsOnly: boolLike().optional()
604
565
  });
605
- var NotificationConfig = z.discriminatedUnion("type", [
606
- TelegramConfig,
607
- SlackConfig
608
- ]);
609
- var SignlConfig = z.object({
610
- apiKey: z.union([z.string(), z.instanceof(CensoredString)]).transform(CensoredString.transform),
611
- teamId: z.union([z.string(), z.instanceof(CensoredString)]).transform(CensoredString.transform)
566
+ const NotificationConfig = z.discriminatedUnion("type", [TelegramConfig, SlackConfig]);
567
+ const SignlConfig = z.object({
568
+ apiKey: z.union([z.string(), z.instanceof(CensoredString)]).transform(CensoredString.transform),
569
+ teamId: z.union([z.string(), z.instanceof(CensoredString)]).transform(CensoredString.transform)
612
570
  });
613
-
614
- // src/notifications/TelegramNotifier.ts
615
- import { md } from "@vlad-yakovlev/telegram-md";
616
- import Queue from "p-queue";
617
- import { withRetry } from "viem";
618
-
619
- // src/notifications/ThrottleManager.ts
620
- import { LRUCache } from "lru-cache";
571
+ //#endregion
572
+ //#region src/notifications/ThrottleManager.ts
621
573
  function defaultBackoffFn(prevWait) {
622
- return Math.min(
623
- prevWait ? prevWait * 2 : 10 * 60 * 1e3,
624
- // 10 minutes
625
- 24 * 60 * 60 * 1e3
626
- // 24 hours
627
- );
574
+ return Math.min(prevWait ? prevWait * 2 : 600 * 1e3, 1440 * 60 * 1e3);
628
575
  }
576
+ /**
577
+ * Manages per-feed throttling with exponential backoff.
578
+ * Each dedupe key has its own independent throttle timer.
579
+ */
629
580
  var ThrottleManager = class {
630
- #state = new LRUCache({ max: 5e3 });
631
- /**
632
- * Checks if it's permitted to send a message for the given dedupe key
633
- * @returns
634
- */
635
- canSend(dedupeKey) {
636
- if (!dedupeKey) {
637
- return true;
638
- }
639
- const state = this.#getState(dedupeKey);
640
- const timeSinceLastAlert = Date.now() - state.lastAlertTime;
641
- return timeSinceLastAlert >= state.currentWait;
642
- }
643
- /**
644
- * Records that notification was sent for the given dedupe key
645
- */
646
- onSent(dedupeKey, throttleFn = defaultBackoffFn) {
647
- if (!dedupeKey) {
648
- return;
649
- }
650
- const state = this.#getState(dedupeKey);
651
- this.#state.set(dedupeKey, {
652
- lastAlertTime: Date.now(),
653
- currentWait: throttleFn(state.currentWait)
654
- });
655
- }
656
- #getState(dedupeKey) {
657
- return this.#state.get(dedupeKey) ?? { lastAlertTime: 0, currentWait: 0 };
658
- }
581
+ #state = new LRUCache({ max: 5e3 });
582
+ /**
583
+ * Checks if it's permitted to send a message for the given dedupe key
584
+ * @returns
585
+ */
586
+ canSend(dedupeKey) {
587
+ if (!dedupeKey) return true;
588
+ const state = this.#getState(dedupeKey);
589
+ return Date.now() - state.lastAlertTime >= state.currentWait;
590
+ }
591
+ /**
592
+ * Records that notification was sent for the given dedupe key
593
+ */
594
+ onSent(dedupeKey, throttleFn = defaultBackoffFn) {
595
+ if (!dedupeKey) return;
596
+ const state = this.#getState(dedupeKey);
597
+ this.#state.set(dedupeKey, {
598
+ lastAlertTime: Date.now(),
599
+ currentWait: throttleFn(state.currentWait)
600
+ });
601
+ }
602
+ #getState(dedupeKey) {
603
+ return this.#state.get(dedupeKey) ?? {
604
+ lastAlertTime: 0,
605
+ currentWait: 0
606
+ };
607
+ }
659
608
  };
660
-
661
- // src/notifications/TelegramNotifier.ts
662
- var DEFAULT_RETRY_INTERVAL2 = 1e3;
663
- var DEFAULT_RETRY_COUNT = 5;
664
- var DEFAULT_MAX_CHUNK_SIZE = 4096;
609
+ //#endregion
610
+ //#region src/notifications/TelegramNotifier.ts
611
+ const DEFAULT_RETRY_INTERVAL$1 = 1e3;
612
+ const DEFAULT_RETRY_COUNT$1 = 5;
613
+ const DEFAULT_MAX_CHUNK_SIZE = 4096;
665
614
  var TelegramNotifier = class {
666
- #logger;
667
- #alertsChannel;
668
- #notificationsChannel;
669
- #botToken;
670
- #prefix;
671
- #retryInterval;
672
- #retryCount;
673
- #maxChunkSize;
674
- #fetchFn;
675
- #queue = new Queue({
676
- concurrency: 1,
677
- // https://core.telegram.org/bots/faq#my-bot-is-hitting-limits-how-do-i-avoid-this
678
- // 20 messages/minute limit
679
- interval: 6e4,
680
- intervalCap: 20,
681
- strict: true
682
- });
683
- #throttleManager = new ThrottleManager();
684
- constructor(opts, logger) {
685
- this.#logger = logger;
686
- this.#alertsChannel = opts.alertsChannel.value;
687
- this.#botToken = opts.token.value;
688
- this.#notificationsChannel = opts.notificationsChannel?.value;
689
- this.#prefix = opts.prefix;
690
- this.#retryInterval = opts.retryInterval ?? DEFAULT_RETRY_INTERVAL2;
691
- this.#retryCount = opts.retryCount ?? DEFAULT_RETRY_COUNT;
692
- this.#maxChunkSize = opts.maxChunkSize ?? DEFAULT_MAX_CHUNK_SIZE;
693
- this.#fetchFn = opts.fetchFn ?? fetch;
694
- }
695
- notify(message, severity) {
696
- const dest = severity === "alert" ? this.#alertsChannel : this.#notificationsChannel;
697
- if (!dest) {
698
- return;
699
- }
700
- this.#enqueueTelegram(message, dest, severity === "alert");
701
- }
702
- async #enqueueTelegram(message, channelId, severe = false) {
703
- if (!this.#throttleManager.canSend(message.dedupeKey)) {
704
- return;
705
- }
706
- const payloads = this.#toTelegramPayloads(message);
707
- for (const payload of payloads) {
708
- this.#queue.add(
709
- () => this.#sendToTelegram(payload, channelId, severe).catch(
710
- (e) => this.#logger?.error(e)
711
- ),
712
- {
713
- priority: severe ? 100 : 0,
714
- timeout: this.#retryInterval * this.#retryCount * 2
715
- }
716
- );
717
- }
718
- this.#throttleManager.onSent(message.dedupeKey, message.throttleFn);
719
- }
720
- async #sendToTelegram(payload, channelId, severe = false) {
721
- const severity = severe ? "alert" : "notification";
722
- this.#logger?.debug(
723
- `sending telegram ${severity} to channel ${channelId}...`
724
- );
725
- const url = `https://api.telegram.org/bot${this.#botToken}/sendMessage`;
726
- try {
727
- await withRetry(
728
- async () => {
729
- const resp = await this.#fetchFn(url, {
730
- method: "POST",
731
- headers: {
732
- "Content-Type": "application/json"
733
- },
734
- body: JSON.stringify({
735
- ...payload,
736
- chat_id: channelId
737
- })
738
- });
739
- if (resp.ok) {
740
- this.#logger?.debug(`telegram ${severity} sent successfully`);
741
- } else {
742
- const txt = await resp.text();
743
- throw new Error(`api error ${resp.status}: ${txt}`);
744
- }
745
- },
746
- { delay: this.#retryInterval, retryCount: this.#retryCount }
747
- );
748
- } catch (e) {
749
- this.#logger?.error(`cannot send telegram ${severity}: ${e}`);
750
- }
751
- }
752
- #toTelegramPayloads(message) {
753
- const { md: mdMessage, plain } = message;
754
- if (!mdMessage) {
755
- return this.#slicePlainText(plain);
756
- }
757
- let text = mdMessage;
758
- if (this.#prefix) {
759
- text = md.join([md.bold(this.#prefix), text], " ");
760
- }
761
- const mdText = text.toString();
762
- if (mdText.length > this.#maxChunkSize) {
763
- return this.#slicePlainText(plain);
764
- }
765
- return [{ text: mdText, parse_mode: "MarkdownV2" }];
766
- }
767
- /**
768
- * Telegram has a limit on message size (default 4096 characters).
769
- * This function slices the text into chunks respecting the limit.
770
- * Each chunk is prefixed with the prefix (if set), ensuring the total length <= maxChunkSize.
771
- * @param text - The text to slice.
772
- * @returns An array of texts.
773
- */
774
- #slicePlainText(text) {
775
- const chunks = [];
776
- const prefix = this.#prefix ? `${this.#prefix} ` : "";
777
- const maxChunkSize = this.#maxChunkSize - prefix.length;
778
- for (let i = 0; i < text.length; i += maxChunkSize) {
779
- const chunkText = text.slice(i, i + maxChunkSize);
780
- chunks.push({ text: `${prefix}${chunkText}` });
781
- }
782
- return chunks;
783
- }
615
+ #logger;
616
+ #alertsChannel;
617
+ #notificationsChannel;
618
+ #botToken;
619
+ #prefix;
620
+ #retryInterval;
621
+ #retryCount;
622
+ #maxChunkSize;
623
+ #fetchFn;
624
+ #queue = new Queue({
625
+ concurrency: 1,
626
+ interval: 6e4,
627
+ intervalCap: 20,
628
+ strict: true
629
+ });
630
+ #throttleManager = new ThrottleManager();
631
+ constructor(opts, logger) {
632
+ this.#logger = logger;
633
+ this.#alertsChannel = opts.alertsChannel.value;
634
+ this.#botToken = opts.token.value;
635
+ this.#notificationsChannel = opts.notificationsChannel?.value;
636
+ this.#prefix = opts.prefix;
637
+ this.#retryInterval = opts.retryInterval ?? DEFAULT_RETRY_INTERVAL$1;
638
+ this.#retryCount = opts.retryCount ?? DEFAULT_RETRY_COUNT$1;
639
+ this.#maxChunkSize = opts.maxChunkSize ?? DEFAULT_MAX_CHUNK_SIZE;
640
+ this.#fetchFn = opts.fetchFn ?? fetch;
641
+ }
642
+ notify(message, severity) {
643
+ const dest = severity === "alert" ? this.#alertsChannel : this.#notificationsChannel;
644
+ if (!dest) return;
645
+ this.#enqueueTelegram(message, dest, severity === "alert");
646
+ }
647
+ async #enqueueTelegram(message, channelId, severe = false) {
648
+ if (!this.#throttleManager.canSend(message.dedupeKey)) return;
649
+ const payloads = this.#toTelegramPayloads(message);
650
+ for (const payload of payloads) this.#queue.add(() => this.#sendToTelegram(payload, channelId, severe).catch((e) => this.#logger?.error(e)), {
651
+ priority: severe ? 100 : 0,
652
+ timeout: this.#retryInterval * this.#retryCount * 2
653
+ });
654
+ this.#throttleManager.onSent(message.dedupeKey, message.throttleFn);
655
+ }
656
+ async #sendToTelegram(payload, channelId, severe = false) {
657
+ const severity = severe ? "alert" : "notification";
658
+ this.#logger?.debug(`sending telegram ${severity} to channel ${channelId}...`);
659
+ const url = `https://api.telegram.org/bot${this.#botToken}/sendMessage`;
660
+ try {
661
+ await withRetry(async () => {
662
+ const resp = await this.#fetchFn(url, {
663
+ method: "POST",
664
+ headers: { "Content-Type": "application/json" },
665
+ body: JSON.stringify({
666
+ ...payload,
667
+ chat_id: channelId
668
+ })
669
+ });
670
+ if (resp.ok) this.#logger?.debug(`telegram ${severity} sent successfully`);
671
+ else {
672
+ const txt = await resp.text();
673
+ throw new Error(`api error ${resp.status}: ${txt}`);
674
+ }
675
+ }, {
676
+ delay: this.#retryInterval,
677
+ retryCount: this.#retryCount
678
+ });
679
+ } catch (e) {
680
+ this.#logger?.error(`cannot send telegram ${severity}: ${e}`);
681
+ }
682
+ }
683
+ #toTelegramPayloads(message) {
684
+ const { md: mdMessage, plain } = message;
685
+ if (!mdMessage) return this.#slicePlainText(plain);
686
+ let text = mdMessage;
687
+ if (this.#prefix) text = md.join([md.bold(this.#prefix), text], " ");
688
+ const mdText = text.toString();
689
+ if (mdText.length > this.#maxChunkSize) return this.#slicePlainText(plain);
690
+ return [{
691
+ text: mdText,
692
+ parse_mode: "MarkdownV2"
693
+ }];
694
+ }
695
+ /**
696
+ * Telegram has a limit on message size (default 4096 characters).
697
+ * This function slices the text into chunks respecting the limit.
698
+ * Each chunk is prefixed with the prefix (if set), ensuring the total length <= maxChunkSize.
699
+ * @param text - The text to slice.
700
+ * @returns An array of texts.
701
+ */
702
+ #slicePlainText(text) {
703
+ const chunks = [];
704
+ const prefix = this.#prefix ? `${this.#prefix} ` : "";
705
+ const maxChunkSize = this.#maxChunkSize - prefix.length;
706
+ for (let i = 0; i < text.length; i += maxChunkSize) {
707
+ const chunkText = text.slice(i, i + maxChunkSize);
708
+ chunks.push({ text: `${prefix}${chunkText}` });
709
+ }
710
+ return chunks;
711
+ }
784
712
  };
785
-
786
- // src/notifications/createNotifier.ts
713
+ //#endregion
714
+ //#region src/notifications/createNotifier.ts
787
715
  function createNotifier(config, logger) {
788
- const parsed = NotificationConfig.safeParse(config);
789
- if (!parsed.success) {
790
- console.log(">>>>", parsed.error);
791
- logger?.warn(`invalid notification config: ${prettifyError(parsed.error)}`);
792
- return void 0;
793
- }
794
- switch (parsed.data.type) {
795
- case "telegram":
796
- return new TelegramNotifier(parsed.data, logger);
797
- case "slack":
798
- return new SlackNotifier(parsed.data, logger);
799
- }
716
+ const parsed = NotificationConfig.safeParse(config);
717
+ if (!parsed.success) {
718
+ console.log(">>>>", parsed.error);
719
+ logger?.warn(`invalid notification config: ${prettifyError(parsed.error)}`);
720
+ return;
721
+ }
722
+ switch (parsed.data.type) {
723
+ case "telegram": return new TelegramNotifier(parsed.data, logger);
724
+ case "slack": return new SlackNotifier(parsed.data, logger);
725
+ }
800
726
  }
801
-
802
- // src/notifications/NotificationsService.ts
803
- import { ADDRESS_0X0, AddressMap } from "@gearbox-protocol/sdk";
804
-
805
- // src/notifications/SignlNotifier.ts
806
- import { withRetry as withRetry2 } from "viem";
807
- var DEFAULT_RETRY_INTERVAL3 = 1e3;
808
- var DEFAULT_RETRY_COUNT2 = 5;
727
+ //#endregion
728
+ //#region src/notifications/SignlNotifier.ts
729
+ const DEFAULT_RETRY_INTERVAL = 1e3;
730
+ const DEFAULT_RETRY_COUNT = 5;
809
731
  var SignlNotifier = class {
810
- #logger;
811
- #apiKey;
812
- #teamId;
813
- #retryInterval;
814
- #retryCount;
815
- #throttleManager = new ThrottleManager();
816
- #throttleFn;
817
- constructor(opts, logger) {
818
- this.#apiKey = opts.apiKey;
819
- this.#teamId = opts.teamId;
820
- this.#retryInterval = opts.retryInterval ?? DEFAULT_RETRY_INTERVAL3;
821
- this.#retryCount = opts.retryCount ?? DEFAULT_RETRY_COUNT2;
822
- this.#logger = logger;
823
- this.#throttleFn = opts.throttleFn;
824
- this.#logger?.info(opts, "signl notifier configured");
825
- }
826
- alert(message, dedupeKey) {
827
- void this.#sendAlert(message, dedupeKey).catch((e) => this.#logger?.error(e));
828
- }
829
- async #sendAlert(message, dedupeKey) {
830
- if (dedupeKey && !this.#throttleManager.canSend(dedupeKey)) {
831
- return;
832
- }
833
- const text = message.slice(0, 64);
834
- this.#logger?.debug("sending signl alert");
835
- await withRetry2(
836
- async () => {
837
- const resp = await fetch(
838
- `https://connect.signl4.com/api/v2/alerts?x-s4-api-key=${this.#apiKey.value}`,
839
- {
840
- method: "POST",
841
- headers: {
842
- "Content-Type": "application/json"
843
- },
844
- body: JSON.stringify({
845
- category: "emergency",
846
- severity: 0,
847
- teamId: this.#teamId.value,
848
- text,
849
- title: "GEARBOX ALERT"
850
- })
851
- }
852
- );
853
- if (resp.ok) {
854
- this.#logger?.warn("signl alert sent");
855
- const data = await resp.json();
856
- this.#logger?.info(`signl alert response: ${JSON.stringify(data)}`);
857
- } else {
858
- throw new Error(`signl api error ${resp.status}: ${resp.statusText}`);
859
- }
860
- },
861
- { delay: this.#retryInterval, retryCount: this.#retryCount }
862
- );
863
- if (dedupeKey) {
864
- this.#throttleManager.onSent(dedupeKey, this.#throttleFn);
865
- }
866
- }
732
+ #logger;
733
+ #apiKey;
734
+ #teamId;
735
+ #retryInterval;
736
+ #retryCount;
737
+ #throttleManager = new ThrottleManager();
738
+ #throttleFn;
739
+ constructor(opts, logger) {
740
+ this.#apiKey = opts.apiKey;
741
+ this.#teamId = opts.teamId;
742
+ this.#retryInterval = opts.retryInterval ?? DEFAULT_RETRY_INTERVAL;
743
+ this.#retryCount = opts.retryCount ?? DEFAULT_RETRY_COUNT;
744
+ this.#logger = logger;
745
+ this.#throttleFn = opts.throttleFn;
746
+ this.#logger?.info(opts, "signl notifier configured");
747
+ }
748
+ alert(message, dedupeKey) {
749
+ this.#sendAlert(message, dedupeKey).catch((e) => this.#logger?.error(e));
750
+ }
751
+ async #sendAlert(message, dedupeKey) {
752
+ if (dedupeKey && !this.#throttleManager.canSend(dedupeKey)) return;
753
+ const text = message.slice(0, 64);
754
+ this.#logger?.debug("sending signl alert");
755
+ await withRetry(async () => {
756
+ const resp = await fetch(`https://connect.signl4.com/api/v2/alerts?x-s4-api-key=${this.#apiKey.value}`, {
757
+ method: "POST",
758
+ headers: { "Content-Type": "application/json" },
759
+ body: JSON.stringify({
760
+ category: "emergency",
761
+ severity: 0,
762
+ teamId: this.#teamId.value,
763
+ text,
764
+ title: "GEARBOX ALERT"
765
+ })
766
+ });
767
+ if (resp.ok) {
768
+ this.#logger?.warn("signl alert sent");
769
+ const data = await resp.json();
770
+ this.#logger?.info(`signl alert response: ${JSON.stringify(data)}`);
771
+ } else throw new Error(`signl api error ${resp.status}: ${resp.statusText}`);
772
+ }, {
773
+ delay: this.#retryInterval,
774
+ retryCount: this.#retryCount
775
+ });
776
+ if (dedupeKey) this.#throttleManager.onSent(dedupeKey, this.#throttleFn);
777
+ }
867
778
  };
868
-
869
- // src/notifications/NotificationsService.ts
779
+ //#endregion
780
+ //#region src/notifications/NotificationsService.ts
870
781
  var NotificationsService = class {
871
- #logger;
872
- #notifiers = new AddressMap();
873
- #signlNotifier;
874
- constructor(options, logger) {
875
- this.#logger = logger;
876
- for (const n of options.notifications) {
877
- const recipient = n.recipient ?? ADDRESS_0X0;
878
- const notifier = createNotifier(n, logger);
879
- if (notifier) {
880
- this.#notifiers.upsert(recipient, notifier);
881
- }
882
- }
883
- if (options.signl) {
884
- this.#signlNotifier = new SignlNotifier(options.signl);
885
- } else {
886
- this.#logger?.warn("signl notifier not configured");
887
- }
888
- }
889
- registerRecipient(address, options) {
890
- const notifier = createNotifier(options, this.#logger);
891
- if (notifier) {
892
- this.#notifiers.upsert(address, notifier);
893
- }
894
- }
895
- signl(message, dedupeKey) {
896
- this.#signlNotifier?.alert(message, dedupeKey);
897
- }
898
- alert(msg) {
899
- this.#notify(msg, "alert");
900
- }
901
- notify(msg) {
902
- this.#notify(msg, "notification");
903
- }
904
- #notify(msg, severity) {
905
- const message = this.#toNotification(msg);
906
- for (const [addr, notifier] of this.#notifiers.entries()) {
907
- const recipient = addr === ADDRESS_0X0 ? void 0 : addr;
908
- const recipientNotification = message.messageFor(recipient);
909
- if (recipientNotification) {
910
- const notification = this.#toDedupableNotification(
911
- recipientNotification
912
- );
913
- notifier.notify(notification, severity);
914
- if (addr === ADDRESS_0X0) {
915
- if (severity === "alert") {
916
- this.#logger?.warn(notification.plain);
917
- } else {
918
- this.#logger?.debug(notification.plain);
919
- }
920
- }
921
- }
922
- }
923
- }
924
- #toNotification(message) {
925
- if (typeof message === "string" || !("messageFor" in message)) {
926
- return {
927
- messageFor: (recipient) => recipient ? void 0 : message
928
- };
929
- }
930
- return message;
931
- }
932
- #toDedupableNotification(msg) {
933
- if (typeof msg === "string") {
934
- return {
935
- plain: msg
936
- };
937
- }
938
- return msg;
939
- }
782
+ #logger;
783
+ #notifiers = new AddressMap();
784
+ #signlNotifier;
785
+ constructor(options, logger) {
786
+ this.#logger = logger;
787
+ for (const n of options.notifications) {
788
+ const recipient = n.recipient ?? ADDRESS_0X0;
789
+ const notifier = createNotifier(n, logger);
790
+ if (notifier) this.#notifiers.upsert(recipient, notifier);
791
+ }
792
+ if (options.signl) this.#signlNotifier = new SignlNotifier(options.signl);
793
+ else this.#logger?.warn("signl notifier not configured");
794
+ }
795
+ registerRecipient(address, options) {
796
+ const notifier = createNotifier(options, this.#logger);
797
+ if (notifier) this.#notifiers.upsert(address, notifier);
798
+ }
799
+ signl(message, dedupeKey) {
800
+ this.#signlNotifier?.alert(message, dedupeKey);
801
+ }
802
+ alert(msg) {
803
+ this.#notify(msg, "alert");
804
+ }
805
+ notify(msg) {
806
+ this.#notify(msg, "notification");
807
+ }
808
+ #notify(msg, severity) {
809
+ const message = this.#toNotification(msg);
810
+ for (const [addr, notifier] of this.#notifiers.entries()) {
811
+ const recipient = addr === ADDRESS_0X0 ? void 0 : addr;
812
+ const recipientNotification = message.messageFor(recipient);
813
+ if (recipientNotification) {
814
+ const notification = this.#toDedupableNotification(recipientNotification);
815
+ notifier.notify(notification, severity);
816
+ if (addr === ADDRESS_0X0) if (severity === "alert") this.#logger?.warn(notification.plain);
817
+ else this.#logger?.debug(notification.plain);
818
+ }
819
+ }
820
+ }
821
+ #toNotification(message) {
822
+ if (typeof message === "string" || !("messageFor" in message)) return { messageFor: (recipient) => recipient ? void 0 : message };
823
+ return message;
824
+ }
825
+ #toDedupableNotification(msg) {
826
+ if (typeof msg === "string") return { plain: msg };
827
+ return msg;
828
+ }
940
829
  };
941
-
942
- // src/providers-schema.ts
943
- import { SUPPORTED_NETWORKS } from "@gearbox-protocol/sdk";
944
- import { z as z2 } from "zod/v4";
945
-
946
- // src/Zommand.ts
947
- import { Command, Option } from "@commander-js/extra-typings";
948
- import * as z4 from "zod/v4/core";
949
- var zommandRegistry = z4.registry();
830
+ //#endregion
831
+ //#region src/Zommand.ts
832
+ const zommandRegistry = z4.registry();
950
833
  var Zommand = class extends Command {
951
- #schema;
952
- #templatedData;
953
- #envToObjectKeys = {};
954
- constructor(name, opts) {
955
- super(name);
956
- const {
957
- schema,
958
- templateData,
959
- configFile = true,
960
- inlineConfig = true
961
- } = opts;
962
- this.#schema = schema;
963
- this.#templatedData = templateData;
964
- const def = this.#schema._zod.def;
965
- if (def.type === "object") {
966
- this.#addOptionsFromObject(def);
967
- } else if (def.type === "union") {
968
- for (const o of def.options) {
969
- const opt = o;
970
- if (opt._zod.def.type === "object") {
971
- this.#addOptionsFromObject(opt._zod.def);
972
- }
973
- }
974
- } else {
975
- throw new Error(
976
- `expected object schema, got ${this.#schema._zod.def.type}`
977
- );
978
- }
979
- if (configFile) {
980
- let opt = new Option("--config [file]", "config file (or s3 link");
981
- if (typeof configFile === "string") {
982
- opt = opt.default(configFile);
983
- }
984
- this.addOption(opt);
985
- }
986
- if (inlineConfig) {
987
- this.addOption(new Option("--inline-config [config]", "inline config"));
988
- }
989
- }
990
- #addOptionsFromObject(def) {
991
- const shape = def.shape;
992
- for (const [key, optionSchema] of Object.entries(shape)) {
993
- const meta = zommandRegistry.get(optionSchema);
994
- if (!meta) {
995
- continue;
996
- }
997
- const existing = this.options.find((o) => o.flags === meta.flags);
998
- if (!existing) {
999
- let description = meta.description;
1000
- if (meta.env) {
1001
- description = `${description} (env variable ${meta.env})`;
1002
- }
1003
- this.addOption(new Option(meta.flags, description));
1004
- if (meta.env) {
1005
- this.#envToObjectKeys[meta.env] = key;
1006
- }
1007
- }
1008
- }
1009
- }
1010
- action(fn) {
1011
- return super.action(async (...args) => {
1012
- const argz = args;
1013
- const _cmd = argz.splice(-1);
1014
- const [{ config, inlineConfig, ...opts }] = argz.splice(-1);
1015
- let fromYaml = {};
1016
- if (config) {
1017
- fromYaml = await resolveYamlFiles(config, {
1018
- ...process.env,
1019
- ...this.#templatedData
1020
- });
1021
- }
1022
- let fromInlineConfig = {};
1023
- if (inlineConfig) {
1024
- fromInlineConfig = await decompressObject(
1025
- inlineConfig,
1026
- this.#templatedData
1027
- );
1028
- } else if (process.env.INLINE_CONFIG) {
1029
- fromInlineConfig = await decompressObject(
1030
- process.env.INLINE_CONFIG,
1031
- this.#templatedData
1032
- );
1033
- }
1034
- const schemaFromEnv = {};
1035
- for (const [env, key] of Object.entries(this.#envToObjectKeys)) {
1036
- if (process.env[env]) {
1037
- schemaFromEnv[key] = process.env[env];
1038
- }
1039
- }
1040
- const combined = {
1041
- ...schemaFromEnv,
1042
- ...fromYaml,
1043
- ...fromInlineConfig,
1044
- ...opts
1045
- };
1046
- const data = z4.parse(this.#schema, combined);
1047
- await fn.apply(this, [...argz, data, this]);
1048
- });
1049
- }
834
+ #schema;
835
+ #templatedData;
836
+ #envToObjectKeys = {};
837
+ constructor(name, opts) {
838
+ super(name);
839
+ const { schema, templateData, configFile = true, inlineConfig = true } = opts;
840
+ this.#schema = schema;
841
+ this.#templatedData = templateData;
842
+ const def = this.#schema._zod.def;
843
+ if (def.type === "object") this.#addOptionsFromObject(def);
844
+ else if (def.type === "union") for (const o of def.options) {
845
+ const opt = o;
846
+ if (opt._zod.def.type === "object") this.#addOptionsFromObject(opt._zod.def);
847
+ }
848
+ else throw new Error(`expected object schema, got ${this.#schema._zod.def.type}`);
849
+ if (configFile) {
850
+ let opt = new Option("--config [file]", "config file (or s3 link");
851
+ if (typeof configFile === "string") opt = opt.default(configFile);
852
+ this.addOption(opt);
853
+ }
854
+ if (inlineConfig) this.addOption(new Option("--inline-config [config]", "inline config"));
855
+ }
856
+ #addOptionsFromObject(def) {
857
+ const shape = def.shape;
858
+ for (const [key, optionSchema] of Object.entries(shape)) {
859
+ const meta = zommandRegistry.get(optionSchema);
860
+ if (!meta) continue;
861
+ if (!this.options.find((o) => o.flags === meta.flags)) {
862
+ let description = meta.description;
863
+ if (meta.env) description = `${description} (env variable ${meta.env})`;
864
+ this.addOption(new Option(meta.flags, description));
865
+ if (meta.env) this.#envToObjectKeys[meta.env] = key;
866
+ }
867
+ }
868
+ }
869
+ action(fn) {
870
+ return super.action(async (...args) => {
871
+ const argz = args;
872
+ argz.splice(-1);
873
+ const [{ config, inlineConfig, ...opts }] = argz.splice(-1);
874
+ let fromYaml = {};
875
+ if (config) fromYaml = await resolveYamlFiles(config, {
876
+ ...process.env,
877
+ ...this.#templatedData
878
+ });
879
+ let fromInlineConfig = {};
880
+ if (inlineConfig) fromInlineConfig = await decompressObject(inlineConfig, this.#templatedData);
881
+ else if (process.env.INLINE_CONFIG) fromInlineConfig = await decompressObject(process.env.INLINE_CONFIG, this.#templatedData);
882
+ const schemaFromEnv = {};
883
+ for (const [env, key] of Object.entries(this.#envToObjectKeys)) if (process.env[env]) schemaFromEnv[key] = process.env[env];
884
+ const combined = {
885
+ ...schemaFromEnv,
886
+ ...fromYaml,
887
+ ...fromInlineConfig,
888
+ ...opts
889
+ };
890
+ const data = z4.parse(this.#schema, combined);
891
+ await fn.apply(this, [
892
+ ...argz,
893
+ data,
894
+ this
895
+ ]);
896
+ });
897
+ }
1050
898
  };
1051
-
1052
- // src/providers-schema.ts
1053
- var RpcProvider = z2.enum([
1054
- "alchemy",
1055
- "drpc",
1056
- "ankr",
1057
- "thirdweb",
1058
- "custom",
1059
- "erpc"
899
+ //#endregion
900
+ //#region src/providers-schema.ts
901
+ const RpcProvider = z.enum([
902
+ "alchemy",
903
+ "drpc",
904
+ "ankr",
905
+ "thirdweb",
906
+ "custom",
907
+ "erpc"
1060
908
  ]);
1061
- var ProvidersSchema = z2.object({
1062
- network: z2.enum(SUPPORTED_NETWORKS).register(zommandRegistry, {
1063
- flags: "--network <network>",
1064
- description: "Gearbox-supported network",
1065
- env: "NETWORK"
1066
- }),
1067
- /**
1068
- * RPC providers to use
1069
- */
1070
- jsonRpcProviders: stringArrayLike().pipe(z2.array(z2.url().transform(CensoredURL.transform))).transform((a) => a.length ? a : void 0).optional().register(zommandRegistry, {
1071
- flags: "--json-rpc-providers <urls...>",
1072
- description: "RPC providers to use, comma separated full http urls.",
1073
- env: "JSON_RPC_PROVIDERS"
1074
- }),
1075
- /**
1076
- * RPC providers to use with their keys
1077
- * Order matters
1078
- */
1079
- enabledProviders: stringArrayLike().pipe(z2.array(RpcProvider)).optional().register(zommandRegistry, {
1080
- flags: "--enabled-providers <providers...>",
1081
- description: "Keyed RPC providers to use, comma separated, order matters. Possible values: custom (for full json rpc urls)/alchemy/drpc/ankr/thirdweb",
1082
- env: "ENABLED_PROVIDERS"
1083
- }),
1084
- /**
1085
- * Alchemy API keys to use
1086
- */
1087
- alchemyKeys: stringArrayLike().pipe(z2.array(z2.string().transform(CensoredString.transform))).transform((a) => a.length ? a : void 0).optional().register(zommandRegistry, {
1088
- flags: "--alchemy-keys <keys...>",
1089
- description: "Alchemy API keys to use, comma separated",
1090
- env: "ALCHEMY_KEYS"
1091
- }),
1092
- /**
1093
- * DRPC API keys to use
1094
- */
1095
- drpcKeys: stringArrayLike().pipe(z2.array(z2.string().transform(CensoredString.transform))).transform((a) => a.length ? a : void 0).optional().register(zommandRegistry, {
1096
- flags: "--drpc-keys <keys...>",
1097
- description: "DRPC API keys to use, comma separated",
1098
- env: "DRPC_KEYS"
1099
- }),
1100
- /**
1101
- * Ankr API keys to use
1102
- */
1103
- ankrKeys: stringArrayLike().pipe(z2.array(z2.string().transform(CensoredString.transform))).transform((a) => a.length ? a : void 0).optional().register(zommandRegistry, {
1104
- flags: "--ankr-keys <keys...>",
1105
- description: "Ankr API keys to use, comma separated",
1106
- env: "ANKR_KEYS"
1107
- }),
1108
- /**
1109
- * Thirdweb API keys to use
1110
- */
1111
- thirdwebKeys: stringArrayLike().pipe(z2.array(z2.string().transform(CensoredString.transform))).transform((a) => a.length ? a : void 0).optional().register(zommandRegistry, {
1112
- flags: "--thirdweb-keys <keys...>",
1113
- description: "Thirdweb API keys to use, comma separated",
1114
- env: "THIRDWEB_KEYS"
1115
- }),
1116
- /**
1117
- * If set, eRPC will be added as first provider
1118
- */
1119
- erpcProjectId: z2.string().optional().register(zommandRegistry, {
1120
- flags: "--erpc-project-id <project-id>",
1121
- description: "If set, eRPC will be added as first provider",
1122
- env: "ERPC_PROJECT_ID"
1123
- }),
1124
- /**
1125
- * eRPC URL to use
1126
- */
1127
- erpcUrl: z2.url().default("http://erpc.local").register(zommandRegistry, {
1128
- flags: "--erpc-url <url>",
1129
- description: "eRPC URL to use",
1130
- env: "ERPC_URL"
1131
- }),
1132
- /**
1133
- * Timeout for JSON RPC requests, in milliseconds
1134
- */
1135
- jsonRpcTimeout: z2.coerce.number().int().positive().optional().register(zommandRegistry, {
1136
- flags: "--json-rpc-timeout <timeout>",
1137
- description: "Timeout for JSON RPC requests, in milliseconds",
1138
- env: "JSON_RPC_TIMEOUT"
1139
- })
909
+ const ProvidersSchema = z.object({
910
+ network: z.enum(SUPPORTED_NETWORKS).register(zommandRegistry, {
911
+ flags: "--network <network>",
912
+ description: "Gearbox-supported network",
913
+ env: "NETWORK"
914
+ }),
915
+ /**
916
+ * RPC providers to use
917
+ */
918
+ jsonRpcProviders: stringArrayLike().pipe(z.array(z.url().transform(CensoredURL.transform))).transform((a) => a.length ? a : void 0).optional().register(zommandRegistry, {
919
+ flags: "--json-rpc-providers <urls...>",
920
+ description: "RPC providers to use, comma separated full http urls.",
921
+ env: "JSON_RPC_PROVIDERS"
922
+ }),
923
+ /**
924
+ * RPC providers to use with their keys
925
+ * Order matters
926
+ */
927
+ enabledProviders: stringArrayLike().pipe(z.array(RpcProvider)).optional().register(zommandRegistry, {
928
+ flags: "--enabled-providers <providers...>",
929
+ description: "Keyed RPC providers to use, comma separated, order matters. Possible values: custom (for full json rpc urls)/alchemy/drpc/ankr/thirdweb",
930
+ env: "ENABLED_PROVIDERS"
931
+ }),
932
+ /**
933
+ * Alchemy API keys to use
934
+ */
935
+ alchemyKeys: stringArrayLike().pipe(z.array(z.string().transform(CensoredString.transform))).transform((a) => a.length ? a : void 0).optional().register(zommandRegistry, {
936
+ flags: "--alchemy-keys <keys...>",
937
+ description: "Alchemy API keys to use, comma separated",
938
+ env: "ALCHEMY_KEYS"
939
+ }),
940
+ /**
941
+ * DRPC API keys to use
942
+ */
943
+ drpcKeys: stringArrayLike().pipe(z.array(z.string().transform(CensoredString.transform))).transform((a) => a.length ? a : void 0).optional().register(zommandRegistry, {
944
+ flags: "--drpc-keys <keys...>",
945
+ description: "DRPC API keys to use, comma separated",
946
+ env: "DRPC_KEYS"
947
+ }),
948
+ /**
949
+ * Ankr API keys to use
950
+ */
951
+ ankrKeys: stringArrayLike().pipe(z.array(z.string().transform(CensoredString.transform))).transform((a) => a.length ? a : void 0).optional().register(zommandRegistry, {
952
+ flags: "--ankr-keys <keys...>",
953
+ description: "Ankr API keys to use, comma separated",
954
+ env: "ANKR_KEYS"
955
+ }),
956
+ /**
957
+ * Thirdweb API keys to use
958
+ */
959
+ thirdwebKeys: stringArrayLike().pipe(z.array(z.string().transform(CensoredString.transform))).transform((a) => a.length ? a : void 0).optional().register(zommandRegistry, {
960
+ flags: "--thirdweb-keys <keys...>",
961
+ description: "Thirdweb API keys to use, comma separated",
962
+ env: "THIRDWEB_KEYS"
963
+ }),
964
+ /**
965
+ * If set, eRPC will be added as first provider
966
+ */
967
+ erpcProjectId: z.string().optional().register(zommandRegistry, {
968
+ flags: "--erpc-project-id <project-id>",
969
+ description: "If set, eRPC will be added as first provider",
970
+ env: "ERPC_PROJECT_ID"
971
+ }),
972
+ /**
973
+ * eRPC URL to use
974
+ */
975
+ erpcUrl: z.url().default("http://erpc.local").register(zommandRegistry, {
976
+ flags: "--erpc-url <url>",
977
+ description: "eRPC URL to use",
978
+ env: "ERPC_URL"
979
+ }),
980
+ /**
981
+ * Timeout for JSON RPC requests, in milliseconds
982
+ */
983
+ jsonRpcTimeout: z.coerce.number().int().positive().optional().register(zommandRegistry, {
984
+ flags: "--json-rpc-timeout <timeout>",
985
+ description: "Timeout for JSON RPC requests, in milliseconds",
986
+ env: "JSON_RPC_TIMEOUT"
987
+ })
1140
988
  });
1141
-
1142
- // src/SecretsManager.ts
1143
- import {
1144
- GetSecretValueCommand,
1145
- SecretsManagerClient
1146
- } from "@aws-sdk/client-secrets-manager";
989
+ //#endregion
990
+ //#region src/SecretsManager.ts
1147
991
  var SecretsManager = class {
1148
- #smClient;
1149
- #secrets = /* @__PURE__ */ new Map();
1150
- async secret(key) {
1151
- const [SecretId, ...jsonPath] = key.split(".");
1152
- const secretStr = await this.#getSecret(SecretId);
1153
- if (!jsonPath.length) {
1154
- return secretStr;
1155
- }
1156
- let v = JSON.parse(secretStr);
1157
- for (const p of jsonPath) {
1158
- v = v[p];
1159
- }
1160
- if (!v || typeof v !== "string") {
1161
- throw new Error(`cannot find secret '${key}'`);
1162
- }
1163
- return v;
1164
- }
1165
- async secretRaw(key) {
1166
- const val = await this.#getSecret(key);
1167
- try {
1168
- return JSON.parse(val);
1169
- } catch {
1170
- return val;
1171
- }
1172
- }
1173
- async #getSecret(SecretId) {
1174
- if (this.#secrets.has(SecretId)) {
1175
- return this.#secrets.get(SecretId);
1176
- }
1177
- if (!this.#smClient) {
1178
- this.#smClient = new SecretsManagerClient({});
1179
- }
1180
- const { SecretString } = await this.#smClient.send(
1181
- new GetSecretValueCommand({ SecretId })
1182
- );
1183
- if (!SecretString) {
1184
- throw new Error(`secret '${SecretId}' not found`);
1185
- }
1186
- this.#secrets.set(SecretId, SecretString);
1187
- return SecretString;
1188
- }
992
+ #smClient;
993
+ #secrets = /* @__PURE__ */ new Map();
994
+ async secret(key) {
995
+ const [SecretId, ...jsonPath] = key.split(".");
996
+ const secretStr = await this.#getSecret(SecretId);
997
+ if (!jsonPath.length) return secretStr;
998
+ let v = JSON.parse(secretStr);
999
+ for (const p of jsonPath) v = v[p];
1000
+ if (!v || typeof v !== "string") throw new Error(`cannot find secret '${key}'`);
1001
+ return v;
1002
+ }
1003
+ async secretRaw(key) {
1004
+ const val = await this.#getSecret(key);
1005
+ try {
1006
+ return JSON.parse(val);
1007
+ } catch {
1008
+ return val;
1009
+ }
1010
+ }
1011
+ async #getSecret(SecretId) {
1012
+ if (this.#secrets.has(SecretId)) return this.#secrets.get(SecretId);
1013
+ if (!this.#smClient) this.#smClient = new SecretsManagerClient({});
1014
+ const { SecretString } = await this.#smClient.send(new GetSecretValueCommand({ SecretId }));
1015
+ if (!SecretString) throw new Error(`secret '${SecretId}' not found`);
1016
+ this.#secrets.set(SecretId, SecretString);
1017
+ return SecretString;
1018
+ }
1189
1019
  };
1190
- var secretsManagerProxy = (mgr) => new Proxy(mgr ?? new SecretsManager(), {
1191
- get(target, prop) {
1192
- if (typeof prop === "string" && !["secret", "secretRaw", "then", "catch", "finally"].includes(prop)) {
1193
- return target.secretRaw(prop);
1194
- }
1195
- }
1196
- });
1197
-
1198
- // src/SSMManager.ts
1199
- import { GetParameterCommand, SSMClient } from "@aws-sdk/client-ssm";
1020
+ const secretsManagerProxy = (mgr) => new Proxy(mgr ?? new SecretsManager(), { get(target, prop) {
1021
+ if (typeof prop === "string" && ![
1022
+ "secret",
1023
+ "secretRaw",
1024
+ "then",
1025
+ "catch",
1026
+ "finally"
1027
+ ].includes(prop)) return target.secretRaw(prop);
1028
+ } });
1029
+ //#endregion
1030
+ //#region src/SSMManager.ts
1200
1031
  var SSMManager = class {
1201
- #ssmClient;
1202
- #parameters = /* @__PURE__ */ new Map();
1203
- async parameter(name) {
1204
- if (this.#parameters.has(name)) {
1205
- return this.#parameters.get(name);
1206
- }
1207
- if (!this.#ssmClient) {
1208
- this.#ssmClient = new SSMClient({});
1209
- }
1210
- const response = await this.#ssmClient.send(
1211
- new GetParameterCommand({
1212
- Name: name
1213
- })
1214
- );
1215
- const value = response.Parameter?.Value;
1216
- if (!value) {
1217
- throw new Error(`parameter ${name} value not found`);
1218
- }
1219
- this.#parameters.set(name, value);
1220
- return value;
1221
- }
1032
+ #ssmClient;
1033
+ #parameters = /* @__PURE__ */ new Map();
1034
+ async parameter(name) {
1035
+ if (this.#parameters.has(name)) return this.#parameters.get(name);
1036
+ if (!this.#ssmClient) this.#ssmClient = new SSMClient({});
1037
+ const value = (await this.#ssmClient.send(new GetParameterCommand({ Name: name }))).Parameter?.Value;
1038
+ if (!value) throw new Error(`parameter ${name} value not found`);
1039
+ this.#parameters.set(name, value);
1040
+ return value;
1041
+ }
1222
1042
  };
1223
- var ssmManagerProxy = (mgr) => new Proxy(mgr ?? new SSMManager(), {
1224
- get(target, prop) {
1225
- if (typeof prop === "string" && !["parameter", "then", "catch", "finally"].includes(prop)) {
1226
- return target.parameter(prop);
1227
- }
1228
- }
1229
- });
1230
-
1231
- // src/uncensorMap.ts
1043
+ const ssmManagerProxy = (mgr) => new Proxy(mgr ?? new SSMManager(), { get(target, prop) {
1044
+ if (typeof prop === "string" && ![
1045
+ "parameter",
1046
+ "then",
1047
+ "catch",
1048
+ "finally"
1049
+ ].includes(prop)) return target.parameter(prop);
1050
+ } });
1051
+ //#endregion
1052
+ //#region src/uncensorMap.ts
1232
1053
  function uncensorMap(map) {
1233
- return Object.fromEntries(Object.entries(map).map(([k, v]) => [k, v.value]));
1054
+ return Object.fromEntries(Object.entries(map).map(([k, v]) => [k, v.value]));
1234
1055
  }
1235
-
1236
- // src/Whitelist.ts
1237
- import { AddressMap as AddressMap2 } from "@gearbox-protocol/sdk";
1238
- import { z as z3 } from "zod/v4";
1239
- var REFRESH_INTERVAL_MS = 10 * 60 * 1e3;
1240
- var whitelistResponseSchema = z3.array(whitelistItemSchema);
1056
+ //#endregion
1057
+ //#region src/Whitelist.ts
1058
+ const REFRESH_INTERVAL_MS = 600 * 1e3;
1059
+ const whitelistResponseSchema = z.array(whitelistItemSchema);
1060
+ /**
1061
+ * Holds two whitelists:
1062
+ * - a `hard` whitelist built once from static config, always available;
1063
+ * - a `soft` whitelist fetched from the backend and kept in
1064
+ * memory with a periodic refresh.
1065
+ *
1066
+ * On soft fetch failure the previous in-memory copy is retained; if no
1067
+ * successful fetch has occurred yet the soft whitelist is considered empty.
1068
+ */
1241
1069
  var Whitelist = class {
1242
- #url;
1243
- #network;
1244
- #logger;
1245
- #soft = new AddressMap2();
1246
- #hard;
1247
- #refreshInterval;
1248
- constructor({ url, network, hard = [], logger }) {
1249
- this.#url = url;
1250
- this.#network = network;
1251
- this.#logger = logger?.child?.({ name: "whitelist" }) ?? logger;
1252
- this.#hard = new AddressMap2();
1253
- for (const entry of hard) {
1254
- this.#hard.upsert(entry.address, entry);
1255
- }
1256
- }
1257
- /**
1258
- * Performs the initial soft fetch and starts the periodic refresh timer.
1259
- * Failure of the initial fetch is non-fatal: the soft whitelist stays empty.
1260
- * When no `url` is configured this is a no-op (only the hard whitelist is used).
1261
- */
1262
- async start() {
1263
- if (!this.#url) {
1264
- this.#logger?.debug(
1265
- `no whitelist url configured, using ${this.#hard.size} hard whitelist entries for ${this.#network}`
1266
- );
1267
- return;
1268
- }
1269
- this.#logger?.info(
1270
- `loading ${this.#network} whitelist from ${this.#url}, refresh every ${REFRESH_INTERVAL_MS / 1e3}s`
1271
- );
1272
- await this.#refresh();
1273
- this.#refreshInterval = setInterval(() => {
1274
- this.#refresh().catch((e) => {
1275
- this.#logger?.warn(`unexpected whitelist refresh error: ${e}`);
1276
- });
1277
- }, REFRESH_INTERVAL_MS);
1278
- }
1279
- /** Stops the refresh timer. */
1280
- stop() {
1281
- if (this.#refreshInterval) {
1282
- clearInterval(this.#refreshInterval);
1283
- this.#refreshInterval = void 0;
1284
- }
1285
- }
1286
- /**
1287
- * Fetches the soft whitelist from the backend, parses it and atomically
1288
- * replaces the in-memory map. On any error logs a warning and retains
1289
- * the previous map.
1290
- */
1291
- async #refresh() {
1292
- if (!this.#url) {
1293
- return;
1294
- }
1295
- try {
1296
- const resp = await fetchRetry(this.#url);
1297
- if (!resp.ok) {
1298
- throw new Error(
1299
- `whitelist fetch failed: ${resp.status} ${resp.statusText}`
1300
- );
1301
- }
1302
- const data = await resp.json();
1303
- const items = whitelistResponseSchema.parse(data).filter((i) => i.network === this.#network);
1304
- const next = new AddressMap2();
1305
- for (const item of items) {
1306
- next.upsert(item.address, {
1307
- address: item.address,
1308
- reason: item.reason,
1309
- expiresAt: item.expiresAt,
1310
- contractType: item.contractType
1311
- });
1312
- }
1313
- this.#soft = next;
1314
- this.#logger?.debug(
1315
- `loaded ${items.length} soft whitelist entries for ${this.#network}`
1316
- );
1317
- } catch (e) {
1318
- this.#logger?.warn(
1319
- `failed to refresh whitelist from ${this.#url}, keeping previous version: ${e}`
1320
- );
1321
- }
1322
- }
1323
- /**
1324
- * Returns the matching whitelist entry for the given address in the
1325
- * requested mode, or `undefined` if absent or expired.
1326
- */
1327
- has(address, mode) {
1328
- const list = mode === "hard" ? this.#hard : this.#soft;
1329
- const item = list.get(address);
1330
- if (!item) {
1331
- return void 0;
1332
- }
1333
- if (item.expiresAt !== void 0 && Date.now() / 1e3 >= item.expiresAt) {
1334
- return void 0;
1335
- }
1336
- return item;
1337
- }
1338
- };
1339
- export {
1340
- CensoredString,
1341
- CensoredURL,
1342
- NotificationConfig,
1343
- NotificationsService,
1344
- ProvidersSchema,
1345
- RpcProvider,
1346
- SSMManager,
1347
- SecretsManager,
1348
- SignlConfig,
1349
- SlackConfig,
1350
- TelegramConfig,
1351
- Whitelist,
1352
- Zommand,
1353
- addressArrayLike,
1354
- addressLike,
1355
- boolLike,
1356
- clientWhitelistItemSchema,
1357
- compressObject,
1358
- createNotifier,
1359
- createPinoCensor,
1360
- decompressObject,
1361
- fetchRetry,
1362
- getAlchemyUrl,
1363
- getAnkrUrl,
1364
- getDrpcUrl,
1365
- getErpcKey,
1366
- getFirstHttpTransportOptions,
1367
- getProviders,
1368
- getRelativePath,
1369
- getThirdWebUrl,
1370
- optionalAddressArrayLike,
1371
- optionalStringArrayLike,
1372
- resolveFromObject,
1373
- resolveYamlDoc,
1374
- resolveYamlFiles,
1375
- secretsManagerProxy,
1376
- sleep,
1377
- ssmManagerProxy,
1378
- stringArrayLike,
1379
- uncensorMap,
1380
- whitelistItemSchema,
1381
- whitelistItemsSchema,
1382
- zommandRegistry
1070
+ #url;
1071
+ #network;
1072
+ #logger;
1073
+ #soft = new AddressMap();
1074
+ #hard;
1075
+ #refreshInterval;
1076
+ constructor({ url, network, hard = [], logger }) {
1077
+ this.#url = url;
1078
+ this.#network = network;
1079
+ this.#logger = logger?.child?.({ name: "whitelist" }) ?? logger;
1080
+ this.#hard = new AddressMap();
1081
+ for (const entry of hard) this.#hard.upsert(entry.address, entry);
1082
+ }
1083
+ /**
1084
+ * Performs the initial soft fetch and starts the periodic refresh timer.
1085
+ * Failure of the initial fetch is non-fatal: the soft whitelist stays empty.
1086
+ * When no `url` is configured this is a no-op (only the hard whitelist is used).
1087
+ */
1088
+ async start() {
1089
+ if (!this.#url) {
1090
+ this.#logger?.debug(`no whitelist url configured, using ${this.#hard.size} hard whitelist entries for ${this.#network}`);
1091
+ return;
1092
+ }
1093
+ this.#logger?.info(`loading ${this.#network} whitelist from ${this.#url}, refresh every ${REFRESH_INTERVAL_MS / 1e3}s`);
1094
+ await this.#refresh();
1095
+ this.#refreshInterval = setInterval(() => {
1096
+ this.#refresh().catch((e) => {
1097
+ this.#logger?.warn(`unexpected whitelist refresh error: ${e}`);
1098
+ });
1099
+ }, REFRESH_INTERVAL_MS);
1100
+ }
1101
+ /** Stops the refresh timer. */
1102
+ stop() {
1103
+ if (this.#refreshInterval) {
1104
+ clearInterval(this.#refreshInterval);
1105
+ this.#refreshInterval = void 0;
1106
+ }
1107
+ }
1108
+ /**
1109
+ * Fetches the soft whitelist from the backend, parses it and atomically
1110
+ * replaces the in-memory map. On any error logs a warning and retains
1111
+ * the previous map.
1112
+ */
1113
+ async #refresh() {
1114
+ if (!this.#url) return;
1115
+ try {
1116
+ const resp = await fetchRetry(this.#url);
1117
+ if (!resp.ok) throw new Error(`whitelist fetch failed: ${resp.status} ${resp.statusText}`);
1118
+ const data = await resp.json();
1119
+ const items = whitelistResponseSchema.parse(data).filter((i) => i.network === this.#network);
1120
+ const next = new AddressMap();
1121
+ for (const item of items) next.upsert(item.address, {
1122
+ address: item.address,
1123
+ reason: item.reason,
1124
+ expiresAt: item.expiresAt,
1125
+ contractType: item.contractType
1126
+ });
1127
+ this.#soft = next;
1128
+ this.#logger?.debug(`loaded ${items.length} soft whitelist entries for ${this.#network}`);
1129
+ } catch (e) {
1130
+ this.#logger?.warn(`failed to refresh whitelist from ${this.#url}, keeping previous version: ${e}`);
1131
+ }
1132
+ }
1133
+ /**
1134
+ * Returns the matching whitelist entry for the given address in the
1135
+ * requested mode, or `undefined` if absent or expired.
1136
+ */
1137
+ has(address, mode) {
1138
+ const item = (mode === "hard" ? this.#hard : this.#soft).get(address);
1139
+ if (!item) return;
1140
+ if (item.expiresAt !== void 0 && Date.now() / 1e3 >= item.expiresAt) return;
1141
+ return item;
1142
+ }
1383
1143
  };
1144
+ //#endregion
1145
+ export { CensoredString, CensoredURL, NotificationConfig, NotificationsService, ProvidersSchema, RpcProvider, SSMManager, SecretsManager, SignlConfig, SlackConfig, TelegramConfig, Whitelist, Zommand, addressArrayLike, addressLike, boolLike, clientWhitelistItemSchema, compressObject, createNotifier, createPinoCensor, decompressObject, fetchRetry, getAlchemyUrl, getAnkrUrl, getDrpcUrl, getErpcKey, getFirstHttpTransportOptions, getProviders, getRelativePath, getThirdWebUrl, optionalAddressArrayLike, optionalStringArrayLike, resolveFromObject, resolveYamlDoc, resolveYamlFiles, secretsManagerProxy, sleep, ssmManagerProxy, stringArrayLike, uncensorMap, whitelistItemSchema, whitelistItemsSchema, zommandRegistry };
1146
+
1384
1147
  //# sourceMappingURL=index.js.map