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