@rhinestone/shared-configs 2.7.0 → 2.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/configs/chain-indexer.json +301 -0
- package/dist/scripts/__tests__/validation.test.js +127 -0
- package/dist/scripts/generate.js +7 -0
- package/dist/scripts/validation.d.ts +19 -0
- package/dist/scripts/validation.js +128 -0
- package/dist/src/generated/packageVersion.d.ts +1 -1
- package/dist/src/generated/packageVersion.js +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,301 @@
|
|
|
1
|
+
{
|
|
2
|
+
"1": {
|
|
3
|
+
"finality": "probabilistic",
|
|
4
|
+
"finality_poll_interval_ms": 30000,
|
|
5
|
+
"finality_source": "finalized",
|
|
6
|
+
"grace_ms": 60000,
|
|
7
|
+
"max_catchup_blocks": 5000,
|
|
8
|
+
"parse_concurrency": 4,
|
|
9
|
+
"poll_interval_ms": 3000,
|
|
10
|
+
"reorg_window_k": 32,
|
|
11
|
+
"reverify_delay_ms": 500,
|
|
12
|
+
"scan_batch_max_blocks": 200,
|
|
13
|
+
"streaming_poll_interval_ms": 15000
|
|
14
|
+
},
|
|
15
|
+
"10": {
|
|
16
|
+
"finality": "sequencer",
|
|
17
|
+
"finality_poll_interval_ms": 15000,
|
|
18
|
+
"finality_source": "finalized",
|
|
19
|
+
"grace_ms": 30000,
|
|
20
|
+
"max_catchup_blocks": 5000,
|
|
21
|
+
"parse_concurrency": 4,
|
|
22
|
+
"poll_interval_ms": 1000,
|
|
23
|
+
"reorg_window_k": 16,
|
|
24
|
+
"reverify_delay_ms": 500,
|
|
25
|
+
"scan_batch_max_blocks": 200,
|
|
26
|
+
"streaming_poll_interval_ms": 15000
|
|
27
|
+
},
|
|
28
|
+
"56": {
|
|
29
|
+
"finality": "probabilistic",
|
|
30
|
+
"finality_poll_interval_ms": 15000,
|
|
31
|
+
"finality_source": "finalized",
|
|
32
|
+
"grace_ms": 30000,
|
|
33
|
+
"max_catchup_blocks": 5000,
|
|
34
|
+
"parse_concurrency": 8,
|
|
35
|
+
"poll_interval_ms": 1000,
|
|
36
|
+
"reorg_window_k": 32,
|
|
37
|
+
"reverify_delay_ms": 500,
|
|
38
|
+
"scan_batch_max_blocks": 200,
|
|
39
|
+
"streaming_poll_interval_ms": 15000
|
|
40
|
+
},
|
|
41
|
+
"100": {
|
|
42
|
+
"finality": "probabilistic",
|
|
43
|
+
"finality_poll_interval_ms": 30000,
|
|
44
|
+
"finality_source": "finalized",
|
|
45
|
+
"grace_ms": 60000,
|
|
46
|
+
"max_catchup_blocks": 5000,
|
|
47
|
+
"parse_concurrency": 4,
|
|
48
|
+
"poll_interval_ms": 2000,
|
|
49
|
+
"reorg_window_k": 32,
|
|
50
|
+
"reverify_delay_ms": 500,
|
|
51
|
+
"scan_batch_max_blocks": 200,
|
|
52
|
+
"streaming_poll_interval_ms": 15000
|
|
53
|
+
},
|
|
54
|
+
"130": {
|
|
55
|
+
"finality": "sequencer",
|
|
56
|
+
"finality_poll_interval_ms": 15000,
|
|
57
|
+
"finality_source": "finalized",
|
|
58
|
+
"grace_ms": 30000,
|
|
59
|
+
"max_catchup_blocks": 5000,
|
|
60
|
+
"parse_concurrency": 8,
|
|
61
|
+
"poll_interval_ms": 1000,
|
|
62
|
+
"reorg_window_k": 16,
|
|
63
|
+
"reverify_delay_ms": 500,
|
|
64
|
+
"scan_batch_max_blocks": 200,
|
|
65
|
+
"streaming_poll_interval_ms": 15000
|
|
66
|
+
},
|
|
67
|
+
"137": {
|
|
68
|
+
"finality": "probabilistic",
|
|
69
|
+
"finality_poll_interval_ms": 15000,
|
|
70
|
+
"finality_source": "finalized",
|
|
71
|
+
"grace_ms": 30000,
|
|
72
|
+
"max_catchup_blocks": 5000,
|
|
73
|
+
"parse_concurrency": 4,
|
|
74
|
+
"poll_interval_ms": 1000,
|
|
75
|
+
"reorg_window_k": 64,
|
|
76
|
+
"reverify_delay_ms": 500,
|
|
77
|
+
"scan_batch_max_blocks": 200,
|
|
78
|
+
"streaming_poll_interval_ms": 15000
|
|
79
|
+
},
|
|
80
|
+
"143": {
|
|
81
|
+
"finality": "instant",
|
|
82
|
+
"finality_poll_interval_ms": 0,
|
|
83
|
+
"finality_source": "instant",
|
|
84
|
+
"grace_ms": 0,
|
|
85
|
+
"max_catchup_blocks": 5000,
|
|
86
|
+
"parse_concurrency": 8,
|
|
87
|
+
"poll_interval_ms": 1000,
|
|
88
|
+
"reorg_window_k": 4,
|
|
89
|
+
"reverify_delay_ms": 0,
|
|
90
|
+
"scan_batch_max_blocks": 200,
|
|
91
|
+
"streaming_poll_interval_ms": 15000
|
|
92
|
+
},
|
|
93
|
+
"146": {
|
|
94
|
+
"finality": "instant",
|
|
95
|
+
"finality_poll_interval_ms": 0,
|
|
96
|
+
"finality_source": "instant",
|
|
97
|
+
"grace_ms": 0,
|
|
98
|
+
"max_catchup_blocks": 5000,
|
|
99
|
+
"parse_concurrency": 8,
|
|
100
|
+
"poll_interval_ms": 1000,
|
|
101
|
+
"reorg_window_k": 4,
|
|
102
|
+
"reverify_delay_ms": 0,
|
|
103
|
+
"scan_batch_max_blocks": 200,
|
|
104
|
+
"streaming_poll_interval_ms": 15000
|
|
105
|
+
},
|
|
106
|
+
"196": {
|
|
107
|
+
"finality": "sequencer",
|
|
108
|
+
"finality_poll_interval_ms": 15000,
|
|
109
|
+
"finality_source": "finalized",
|
|
110
|
+
"grace_ms": 30000,
|
|
111
|
+
"max_catchup_blocks": 5000,
|
|
112
|
+
"parse_concurrency": 4,
|
|
113
|
+
"poll_interval_ms": 1000,
|
|
114
|
+
"reorg_window_k": 16,
|
|
115
|
+
"reverify_delay_ms": 500,
|
|
116
|
+
"scan_batch_max_blocks": 200,
|
|
117
|
+
"streaming_poll_interval_ms": 15000
|
|
118
|
+
},
|
|
119
|
+
"999": {
|
|
120
|
+
"finality": "instant",
|
|
121
|
+
"finality_poll_interval_ms": 0,
|
|
122
|
+
"finality_source": "instant",
|
|
123
|
+
"grace_ms": 0,
|
|
124
|
+
"max_catchup_blocks": 5000,
|
|
125
|
+
"parse_concurrency": 8,
|
|
126
|
+
"poll_interval_ms": 1000,
|
|
127
|
+
"reorg_window_k": 4,
|
|
128
|
+
"reverify_delay_ms": 0,
|
|
129
|
+
"scan_batch_max_blocks": 200,
|
|
130
|
+
"streaming_poll_interval_ms": 15000
|
|
131
|
+
},
|
|
132
|
+
"1868": {
|
|
133
|
+
"finality": "sequencer",
|
|
134
|
+
"finality_poll_interval_ms": 15000,
|
|
135
|
+
"finality_source": "finalized",
|
|
136
|
+
"grace_ms": 30000,
|
|
137
|
+
"max_catchup_blocks": 5000,
|
|
138
|
+
"parse_concurrency": 4,
|
|
139
|
+
"poll_interval_ms": 1000,
|
|
140
|
+
"reorg_window_k": 16,
|
|
141
|
+
"reverify_delay_ms": 500,
|
|
142
|
+
"scan_batch_max_blocks": 200,
|
|
143
|
+
"streaming_poll_interval_ms": 15000
|
|
144
|
+
},
|
|
145
|
+
"4663": {
|
|
146
|
+
"finality": "sequencer",
|
|
147
|
+
"finality_poll_interval_ms": 15000,
|
|
148
|
+
"finality_source": "finalized",
|
|
149
|
+
"grace_ms": 30000,
|
|
150
|
+
"max_catchup_blocks": 5000,
|
|
151
|
+
"parse_concurrency": 4,
|
|
152
|
+
"poll_interval_ms": 1000,
|
|
153
|
+
"reorg_window_k": 16,
|
|
154
|
+
"reverify_delay_ms": 500,
|
|
155
|
+
"scan_batch_max_blocks": 200,
|
|
156
|
+
"streaming_poll_interval_ms": 15000
|
|
157
|
+
},
|
|
158
|
+
"8453": {
|
|
159
|
+
"finality": "sequencer",
|
|
160
|
+
"finality_poll_interval_ms": 15000,
|
|
161
|
+
"finality_source": "finalized",
|
|
162
|
+
"grace_ms": 30000,
|
|
163
|
+
"max_catchup_blocks": 5000,
|
|
164
|
+
"parse_concurrency": 8,
|
|
165
|
+
"poll_interval_ms": 1000,
|
|
166
|
+
"reorg_window_k": 16,
|
|
167
|
+
"reverify_delay_ms": 500,
|
|
168
|
+
"scan_batch_max_blocks": 200,
|
|
169
|
+
"streaming_poll_interval_ms": 15000
|
|
170
|
+
},
|
|
171
|
+
"9745": {
|
|
172
|
+
"finality": "instant",
|
|
173
|
+
"finality_poll_interval_ms": 0,
|
|
174
|
+
"finality_source": "instant",
|
|
175
|
+
"grace_ms": 0,
|
|
176
|
+
"max_catchup_blocks": 5000,
|
|
177
|
+
"parse_concurrency": 8,
|
|
178
|
+
"poll_interval_ms": 1000,
|
|
179
|
+
"reorg_window_k": 4,
|
|
180
|
+
"reverify_delay_ms": 0,
|
|
181
|
+
"scan_batch_max_blocks": 200,
|
|
182
|
+
"streaming_poll_interval_ms": 15000
|
|
183
|
+
},
|
|
184
|
+
"9746": {
|
|
185
|
+
"finality": "instant",
|
|
186
|
+
"finality_poll_interval_ms": 0,
|
|
187
|
+
"finality_source": "instant",
|
|
188
|
+
"grace_ms": 0,
|
|
189
|
+
"max_catchup_blocks": 5000,
|
|
190
|
+
"parse_concurrency": 4,
|
|
191
|
+
"poll_interval_ms": 1000,
|
|
192
|
+
"reorg_window_k": 4,
|
|
193
|
+
"reverify_delay_ms": 0,
|
|
194
|
+
"scan_batch_max_blocks": 200,
|
|
195
|
+
"streaming_poll_interval_ms": 5000
|
|
196
|
+
},
|
|
197
|
+
"42161": {
|
|
198
|
+
"finality": "sequencer",
|
|
199
|
+
"finality_poll_interval_ms": 15000,
|
|
200
|
+
"finality_source": "finalized",
|
|
201
|
+
"grace_ms": 30000,
|
|
202
|
+
"max_catchup_blocks": 5000,
|
|
203
|
+
"parse_concurrency": 8,
|
|
204
|
+
"poll_interval_ms": 1000,
|
|
205
|
+
"reorg_window_k": 16,
|
|
206
|
+
"reverify_delay_ms": 500,
|
|
207
|
+
"scan_batch_max_blocks": 200,
|
|
208
|
+
"streaming_poll_interval_ms": 15000
|
|
209
|
+
},
|
|
210
|
+
"43114": {
|
|
211
|
+
"finality": "instant",
|
|
212
|
+
"finality_poll_interval_ms": 0,
|
|
213
|
+
"finality_source": "instant",
|
|
214
|
+
"grace_ms": 0,
|
|
215
|
+
"max_catchup_blocks": 5000,
|
|
216
|
+
"parse_concurrency": 4,
|
|
217
|
+
"poll_interval_ms": 1000,
|
|
218
|
+
"reorg_window_k": 4,
|
|
219
|
+
"reverify_delay_ms": 0,
|
|
220
|
+
"scan_batch_max_blocks": 200,
|
|
221
|
+
"streaming_poll_interval_ms": 15000
|
|
222
|
+
},
|
|
223
|
+
"57073": {
|
|
224
|
+
"finality": "sequencer",
|
|
225
|
+
"finality_poll_interval_ms": 15000,
|
|
226
|
+
"finality_source": "finalized",
|
|
227
|
+
"grace_ms": 30000,
|
|
228
|
+
"max_catchup_blocks": 5000,
|
|
229
|
+
"parse_concurrency": 4,
|
|
230
|
+
"poll_interval_ms": 1000,
|
|
231
|
+
"reorg_window_k": 16,
|
|
232
|
+
"reverify_delay_ms": 500,
|
|
233
|
+
"scan_batch_max_blocks": 200,
|
|
234
|
+
"streaming_poll_interval_ms": 15000
|
|
235
|
+
},
|
|
236
|
+
"84532": {
|
|
237
|
+
"finality": "sequencer",
|
|
238
|
+
"finality_poll_interval_ms": 15000,
|
|
239
|
+
"finality_source": "finalized",
|
|
240
|
+
"grace_ms": 30000,
|
|
241
|
+
"max_catchup_blocks": 5000,
|
|
242
|
+
"parse_concurrency": 4,
|
|
243
|
+
"poll_interval_ms": 1000,
|
|
244
|
+
"reorg_window_k": 8,
|
|
245
|
+
"reverify_delay_ms": 500,
|
|
246
|
+
"scan_batch_max_blocks": 200,
|
|
247
|
+
"streaming_poll_interval_ms": 5000
|
|
248
|
+
},
|
|
249
|
+
"421614": {
|
|
250
|
+
"finality": "sequencer",
|
|
251
|
+
"finality_poll_interval_ms": 15000,
|
|
252
|
+
"finality_source": "finalized",
|
|
253
|
+
"grace_ms": 30000,
|
|
254
|
+
"max_catchup_blocks": 5000,
|
|
255
|
+
"parse_concurrency": 4,
|
|
256
|
+
"poll_interval_ms": 1000,
|
|
257
|
+
"reorg_window_k": 8,
|
|
258
|
+
"reverify_delay_ms": 500,
|
|
259
|
+
"scan_batch_max_blocks": 200,
|
|
260
|
+
"streaming_poll_interval_ms": 5000
|
|
261
|
+
},
|
|
262
|
+
"747474": {
|
|
263
|
+
"finality": "sequencer",
|
|
264
|
+
"finality_poll_interval_ms": 15000,
|
|
265
|
+
"finality_source": "finalized",
|
|
266
|
+
"grace_ms": 30000,
|
|
267
|
+
"max_catchup_blocks": 5000,
|
|
268
|
+
"parse_concurrency": 4,
|
|
269
|
+
"poll_interval_ms": 1000,
|
|
270
|
+
"reorg_window_k": 16,
|
|
271
|
+
"reverify_delay_ms": 500,
|
|
272
|
+
"scan_batch_max_blocks": 200,
|
|
273
|
+
"streaming_poll_interval_ms": 15000
|
|
274
|
+
},
|
|
275
|
+
"11155111": {
|
|
276
|
+
"finality": "probabilistic",
|
|
277
|
+
"finality_poll_interval_ms": 30000,
|
|
278
|
+
"finality_source": "finalized",
|
|
279
|
+
"grace_ms": 60000,
|
|
280
|
+
"max_catchup_blocks": 5000,
|
|
281
|
+
"parse_concurrency": 4,
|
|
282
|
+
"poll_interval_ms": 3000,
|
|
283
|
+
"reorg_window_k": 32,
|
|
284
|
+
"reverify_delay_ms": 500,
|
|
285
|
+
"scan_batch_max_blocks": 200,
|
|
286
|
+
"streaming_poll_interval_ms": 5000
|
|
287
|
+
},
|
|
288
|
+
"11155420": {
|
|
289
|
+
"finality": "sequencer",
|
|
290
|
+
"finality_poll_interval_ms": 15000,
|
|
291
|
+
"finality_source": "finalized",
|
|
292
|
+
"grace_ms": 30000,
|
|
293
|
+
"max_catchup_blocks": 5000,
|
|
294
|
+
"parse_concurrency": 4,
|
|
295
|
+
"poll_interval_ms": 1000,
|
|
296
|
+
"reorg_window_k": 8,
|
|
297
|
+
"reverify_delay_ms": 500,
|
|
298
|
+
"scan_batch_max_blocks": 200,
|
|
299
|
+
"streaming_poll_interval_ms": 5000
|
|
300
|
+
}
|
|
301
|
+
}
|
|
@@ -8,6 +8,7 @@ const viem_1 = require("viem");
|
|
|
8
8
|
const validation_1 = require("../validation");
|
|
9
9
|
const index_1 = require("../../src/index");
|
|
10
10
|
const oft_json_1 = __importDefault(require("../../configs/oft.json"));
|
|
11
|
+
const chain_indexer_json_1 = __importDefault(require("../../configs/chain-indexer.json"));
|
|
11
12
|
const nativeToken = { address: "0x0", symbol: "ETH", decimals: 18 };
|
|
12
13
|
const usdcAddress = "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48";
|
|
13
14
|
const validAuthorization = {
|
|
@@ -497,6 +498,132 @@ function errorsFor(chain) {
|
|
|
497
498
|
(0, bun_test_1.expect)((0, validation_1.validateOftCoverage)({}, { USDT0: { adapters: { "1": "0xa" } } })).toEqual([]);
|
|
498
499
|
});
|
|
499
500
|
});
|
|
501
|
+
/** A complete class-B entry, so semantic tests don't trip the shape checks. */
|
|
502
|
+
function validIndexerEntry(overrides = {}) {
|
|
503
|
+
return {
|
|
504
|
+
poll_interval_ms: 1000,
|
|
505
|
+
streaming_poll_interval_ms: 15000,
|
|
506
|
+
reorg_window_k: 16,
|
|
507
|
+
reverify_delay_ms: 500,
|
|
508
|
+
max_catchup_blocks: 5000,
|
|
509
|
+
parse_concurrency: 4,
|
|
510
|
+
scan_batch_max_blocks: 200,
|
|
511
|
+
finality: "sequencer",
|
|
512
|
+
finality_source: "finalized",
|
|
513
|
+
finality_poll_interval_ms: 15000,
|
|
514
|
+
grace_ms: 30000,
|
|
515
|
+
...overrides,
|
|
516
|
+
};
|
|
517
|
+
}
|
|
518
|
+
(0, bun_test_1.describe)("validateChainIndexerCoverage", () => {
|
|
519
|
+
// What actually reaches the artifact: the shipped registry and the shipped
|
|
520
|
+
// settings must agree, or the release pipeline breaks on good data.
|
|
521
|
+
(0, bun_test_1.it)("classifies every real EVM chain", () => {
|
|
522
|
+
(0, bun_test_1.expect)((0, validation_1.validateChainIndexerCoverage)(index_1.chainRegistry, chain_indexer_json_1.default)).toEqual([]);
|
|
523
|
+
});
|
|
524
|
+
(0, bun_test_1.it)("reports an EVM chain with no entry", () => {
|
|
525
|
+
const issues = (0, validation_1.validateChainIndexerCoverage)({ "1": validChain() }, {});
|
|
526
|
+
(0, bun_test_1.expect)(issues).toHaveLength(1);
|
|
527
|
+
(0, bun_test_1.expect)(issues[0].message).toContain("no chain-indexer entry");
|
|
528
|
+
});
|
|
529
|
+
(0, bun_test_1.it)("reports an entry addressing no real EVM chain", () => {
|
|
530
|
+
const issues = (0, validation_1.validateChainIndexerCoverage)({}, {
|
|
531
|
+
"1": validIndexerEntry(),
|
|
532
|
+
});
|
|
533
|
+
(0, bun_test_1.expect)(issues).toHaveLength(1);
|
|
534
|
+
(0, bun_test_1.expect)(issues[0].message).toContain("no real EVM chain");
|
|
535
|
+
});
|
|
536
|
+
// HyperCore is vmType "evm" with a hypercore: caip2 — the eip155 namespace,
|
|
537
|
+
// not vmType, decides whether the indexer dials a chain.
|
|
538
|
+
(0, bun_test_1.it)("requires nothing of a virtual or non-eip155 chain", () => {
|
|
539
|
+
const chains = {
|
|
540
|
+
"1337001": validChain({ caip2: "hypercore:spot", virtual: true }),
|
|
541
|
+
"728126428": validChain({ vmType: "tvm", caip2: "tron:mainnet" }),
|
|
542
|
+
};
|
|
543
|
+
(0, bun_test_1.expect)((0, validation_1.validateChainIndexerCoverage)(chains, {})).toEqual([]);
|
|
544
|
+
});
|
|
545
|
+
(0, bun_test_1.it)("rejects a non-object entry", () => {
|
|
546
|
+
const issues = (0, validation_1.validateChainIndexerCoverage)({ "1": validChain() }, {
|
|
547
|
+
"1": 42,
|
|
548
|
+
});
|
|
549
|
+
(0, bun_test_1.expect)(issues).toHaveLength(1);
|
|
550
|
+
(0, bun_test_1.expect)(issues[0].message).toContain("not an object");
|
|
551
|
+
});
|
|
552
|
+
bun_test_1.it.each([
|
|
553
|
+
[
|
|
554
|
+
"a missing field",
|
|
555
|
+
{ poll_interval_ms: undefined },
|
|
556
|
+
"missing `poll_interval_ms`",
|
|
557
|
+
],
|
|
558
|
+
[
|
|
559
|
+
"a non-integer value",
|
|
560
|
+
{ reorg_window_k: "16" },
|
|
561
|
+
"`reorg_window_k` = \"16\" is not a legal value",
|
|
562
|
+
],
|
|
563
|
+
[
|
|
564
|
+
"an unknown finality class",
|
|
565
|
+
{ finality: "optimistic" },
|
|
566
|
+
"`finality` = \"optimistic\" is not a legal value",
|
|
567
|
+
],
|
|
568
|
+
[
|
|
569
|
+
"an unrecognized field",
|
|
570
|
+
{ pol_interval_ms: 1000 },
|
|
571
|
+
"unrecognized field `pol_interval_ms`",
|
|
572
|
+
],
|
|
573
|
+
[
|
|
574
|
+
"instant finality with a real finality source",
|
|
575
|
+
{ finality: "instant" },
|
|
576
|
+
"instant chains take neither",
|
|
577
|
+
],
|
|
578
|
+
[
|
|
579
|
+
"an instant source that still polls",
|
|
580
|
+
{
|
|
581
|
+
finality: "instant",
|
|
582
|
+
finality_source: "instant",
|
|
583
|
+
finality_poll_interval_ms: 15000,
|
|
584
|
+
},
|
|
585
|
+
"does not match finality_source",
|
|
586
|
+
],
|
|
587
|
+
[
|
|
588
|
+
"a streaming poll below the base poll",
|
|
589
|
+
{ streaming_poll_interval_ms: 500 },
|
|
590
|
+
"never a speedup",
|
|
591
|
+
],
|
|
592
|
+
])("rejects %s", (_label, overrides, expected) => {
|
|
593
|
+
const issues = (0, validation_1.validateChainIndexerCoverage)({ "1": validChain() }, {
|
|
594
|
+
"1": validIndexerEntry(overrides),
|
|
595
|
+
});
|
|
596
|
+
(0, bun_test_1.expect)(issues.map((i) => i.message).join("\n")).toContain(expected);
|
|
597
|
+
});
|
|
598
|
+
(0, bun_test_1.it)("accepts { indexed: false } as a complete entry", () => {
|
|
599
|
+
(0, bun_test_1.expect)((0, validation_1.validateChainIndexerCoverage)({ "1": validChain() }, {
|
|
600
|
+
"1": { indexed: false },
|
|
601
|
+
})).toEqual([]);
|
|
602
|
+
});
|
|
603
|
+
(0, bun_test_1.it)("rejects indexed: false carrying scan settings", () => {
|
|
604
|
+
const issues = (0, validation_1.validateChainIndexerCoverage)({ "1": validChain() }, {
|
|
605
|
+
"1": { indexed: false, poll_interval_ms: 1000 },
|
|
606
|
+
});
|
|
607
|
+
(0, bun_test_1.expect)(issues.map((i) => i.message).join("\n")).toContain("an opt-out is the whole entry");
|
|
608
|
+
});
|
|
609
|
+
(0, bun_test_1.it)("rejects indexed: true", () => {
|
|
610
|
+
const issues = (0, validation_1.validateChainIndexerCoverage)({ "1": validChain() }, {
|
|
611
|
+
"1": validIndexerEntry({ indexed: true }),
|
|
612
|
+
});
|
|
613
|
+
(0, bun_test_1.expect)(issues.map((i) => i.message).join("\n")).toContain("the only legal value is false");
|
|
614
|
+
});
|
|
615
|
+
(0, bun_test_1.it)("accepts a coherent instant-class entry", () => {
|
|
616
|
+
const entry = validIndexerEntry({
|
|
617
|
+
finality: "instant",
|
|
618
|
+
finality_source: "instant",
|
|
619
|
+
finality_poll_interval_ms: 0,
|
|
620
|
+
reverify_delay_ms: 0,
|
|
621
|
+
grace_ms: 0,
|
|
622
|
+
reorg_window_k: 4,
|
|
623
|
+
});
|
|
624
|
+
(0, bun_test_1.expect)((0, validation_1.validateChainIndexerCoverage)({ "1": validChain() }, { "1": entry })).toEqual([]);
|
|
625
|
+
});
|
|
626
|
+
});
|
|
500
627
|
(0, bun_test_1.describe)("formatValidationIssues", () => {
|
|
501
628
|
(0, bun_test_1.it)("returns empty string for no issues", () => {
|
|
502
629
|
(0, bun_test_1.expect)((0, validation_1.formatValidationIssues)({ issues: [], hasErrors: false })).toBe("");
|
package/dist/scripts/generate.js
CHANGED
|
@@ -434,6 +434,11 @@ async function generate() {
|
|
|
434
434
|
// and reads its addresses back out of the OFT registry, so only an adapter
|
|
435
435
|
// added there without the matching chain entry can drift.
|
|
436
436
|
const oftCoverageIssues = (0, validation_1.validateOftCoverage)(classifiedChains, output.oft);
|
|
437
|
+
// Chain-indexer settings are authored, not derived (config/chain-indexer.jsonnet),
|
|
438
|
+
// and the indexer hard-fails on an unclassified chain — so an EVM chain added
|
|
439
|
+
// without an entry fails this build instead of the indexer's boot. Needs the
|
|
440
|
+
// stamped caip2, hence after the classifiers.
|
|
441
|
+
const chainIndexerIssues = (0, validation_1.validateChainIndexerCoverage)(classifiedChains, output.chainIndexer);
|
|
437
442
|
const classificationIssues = [
|
|
438
443
|
...networkIssues,
|
|
439
444
|
...stackIssues,
|
|
@@ -442,6 +447,7 @@ async function generate() {
|
|
|
442
447
|
...publicRpcUrlIssues,
|
|
443
448
|
...iconSlugIssues,
|
|
444
449
|
...oftCoverageIssues,
|
|
450
|
+
...chainIndexerIssues,
|
|
445
451
|
];
|
|
446
452
|
await (0, promises_1.writeFile)(node_path_1.default.join(configsDir, "chains.json"), `${JSON.stringify(classifiedChains, null, 2)}\n`);
|
|
447
453
|
await (0, promises_1.writeFile)(node_path_1.default.join(configsDir, "mainnets.json"), `${JSON.stringify(output.mainnets, null, 2)}\n`);
|
|
@@ -449,6 +455,7 @@ async function generate() {
|
|
|
449
455
|
await (0, promises_1.writeFile)(node_path_1.default.join(configsDir, "providers.json"), `${JSON.stringify(output.providers, null, 2)}\n`);
|
|
450
456
|
await (0, promises_1.writeFile)(node_path_1.default.join(configsDir, "oft.json"), `${JSON.stringify(output.oft, null, 2)}\n`);
|
|
451
457
|
await (0, promises_1.writeFile)(node_path_1.default.join(configsDir, "oft-programs.json"), `${JSON.stringify(output.oftProgramIds ?? {}, null, 2)}\n`);
|
|
458
|
+
await (0, promises_1.writeFile)(node_path_1.default.join(configsDir, "chain-indexer.json"), `${JSON.stringify(output.chainIndexer, null, 2)}\n`);
|
|
452
459
|
// Validate chain configuration. Classification problems are reported in the
|
|
453
460
|
// same pass so one run surfaces everything wrong with the registry, rather than
|
|
454
461
|
// failing on the partition and hiding the rest.
|
|
@@ -225,6 +225,25 @@ export declare function validateChainConfig(chains: ChainRegistry): ValidationRe
|
|
|
225
225
|
* a diff of either file on its own — this is the check that sees both.
|
|
226
226
|
*/
|
|
227
227
|
export declare function validateOftCoverage(chains: ChainRegistry, oft: Record<string, unknown>): ValidationIssue[];
|
|
228
|
+
/**
|
|
229
|
+
* Insists that every real EVM chain carries a complete, coherent chain-indexer
|
|
230
|
+
* entry, and that no entry addresses a chain that does not exist.
|
|
231
|
+
*
|
|
232
|
+
* The indexer scans every dialable EVM chain, and its contract is that an
|
|
233
|
+
* unclassified chain is a hard failure — this check moves that failure from
|
|
234
|
+
* the indexer's boot to this build, where the person adding the chain sees it
|
|
235
|
+
* (RHI-6107). Selected by the `eip155:` caip2 namespace, NOT by vmType:
|
|
236
|
+
* HyperCore is vmType "evm" with a `hypercore:` caip2 and must not require an
|
|
237
|
+
* entry — the same discriminator the publish workflow's contracts guard uses,
|
|
238
|
+
* for the same reason. Runs after `classifyChainCaip2`, which stamps the field.
|
|
239
|
+
*
|
|
240
|
+
* The cross-field rules mirror chain-indexer's `ChainScanSettings::validate`,
|
|
241
|
+
* so a value set this check accepts is one the indexer will accept.
|
|
242
|
+
*
|
|
243
|
+
* A chain deliberately not indexed carries `{ indexed: false }` as its whole
|
|
244
|
+
* entry — a decision the check accepts, unlike a missing entry.
|
|
245
|
+
*/
|
|
246
|
+
export declare function validateChainIndexerCoverage(chains: ChainRegistry, chainIndexer: Record<string, unknown>): ValidationIssue[];
|
|
228
247
|
/**
|
|
229
248
|
* Formats validation issues for console output.
|
|
230
249
|
*
|
|
@@ -41,6 +41,7 @@ exports.classifyChainPublicRpcUrls = classifyChainPublicRpcUrls;
|
|
|
41
41
|
exports.classifyChainStacks = classifyChainStacks;
|
|
42
42
|
exports.validateChainConfig = validateChainConfig;
|
|
43
43
|
exports.validateOftCoverage = validateOftCoverage;
|
|
44
|
+
exports.validateChainIndexerCoverage = validateChainIndexerCoverage;
|
|
44
45
|
exports.formatValidationIssues = formatValidationIssues;
|
|
45
46
|
const viem_1 = require("viem");
|
|
46
47
|
const celo_1 = require("viem/celo");
|
|
@@ -1143,6 +1144,133 @@ function validateOftCoverage(chains, oft) {
|
|
|
1143
1144
|
}
|
|
1144
1145
|
return issues;
|
|
1145
1146
|
}
|
|
1147
|
+
// Runtime mirrors of chain-indexer's `Finality` / `FinalitySource` Rust enums
|
|
1148
|
+
// (chain-indexer src/config.rs). Same build-time-only rationale as the sets at
|
|
1149
|
+
// the top of this file: a value outside these means the jsonnet invented a
|
|
1150
|
+
// classification the consumer cannot deserialize, which must fail the build
|
|
1151
|
+
// here rather than the indexer's boot. `safe` is legal and currently unused.
|
|
1152
|
+
const CHAIN_INDEXER_FINALITY = new Set([
|
|
1153
|
+
'probabilistic',
|
|
1154
|
+
'sequencer',
|
|
1155
|
+
'instant',
|
|
1156
|
+
]);
|
|
1157
|
+
const CHAIN_INDEXER_FINALITY_SOURCE = new Set([
|
|
1158
|
+
'finalized',
|
|
1159
|
+
'safe',
|
|
1160
|
+
'instant',
|
|
1161
|
+
]);
|
|
1162
|
+
// Every field of one chain-indexer entry, all required. The wire is exact on
|
|
1163
|
+
// purpose: the indexer's own contract is "every field mandatory — a knob nobody
|
|
1164
|
+
// set is a decision nobody made", so a missing field here is the same authoring
|
|
1165
|
+
// mistake as a missing entry, and an unrecognized field is a typo that would
|
|
1166
|
+
// otherwise ship silently.
|
|
1167
|
+
const CHAIN_INDEXER_FIELDS = {
|
|
1168
|
+
poll_interval_ms: isNonNegativeInt,
|
|
1169
|
+
streaming_poll_interval_ms: isNonNegativeInt,
|
|
1170
|
+
reorg_window_k: isNonNegativeInt,
|
|
1171
|
+
reverify_delay_ms: isNonNegativeInt,
|
|
1172
|
+
max_catchup_blocks: isNonNegativeInt,
|
|
1173
|
+
parse_concurrency: isNonNegativeInt,
|
|
1174
|
+
scan_batch_max_blocks: isNonNegativeInt,
|
|
1175
|
+
finality: (v) => typeof v === 'string' && CHAIN_INDEXER_FINALITY.has(v),
|
|
1176
|
+
finality_source: (v) => typeof v === 'string' && CHAIN_INDEXER_FINALITY_SOURCE.has(v),
|
|
1177
|
+
finality_poll_interval_ms: isNonNegativeInt,
|
|
1178
|
+
grace_ms: isNonNegativeInt,
|
|
1179
|
+
};
|
|
1180
|
+
/**
|
|
1181
|
+
* Insists that every real EVM chain carries a complete, coherent chain-indexer
|
|
1182
|
+
* entry, and that no entry addresses a chain that does not exist.
|
|
1183
|
+
*
|
|
1184
|
+
* The indexer scans every dialable EVM chain, and its contract is that an
|
|
1185
|
+
* unclassified chain is a hard failure — this check moves that failure from
|
|
1186
|
+
* the indexer's boot to this build, where the person adding the chain sees it
|
|
1187
|
+
* (RHI-6107). Selected by the `eip155:` caip2 namespace, NOT by vmType:
|
|
1188
|
+
* HyperCore is vmType "evm" with a `hypercore:` caip2 and must not require an
|
|
1189
|
+
* entry — the same discriminator the publish workflow's contracts guard uses,
|
|
1190
|
+
* for the same reason. Runs after `classifyChainCaip2`, which stamps the field.
|
|
1191
|
+
*
|
|
1192
|
+
* The cross-field rules mirror chain-indexer's `ChainScanSettings::validate`,
|
|
1193
|
+
* so a value set this check accepts is one the indexer will accept.
|
|
1194
|
+
*
|
|
1195
|
+
* A chain deliberately not indexed carries `{ indexed: false }` as its whole
|
|
1196
|
+
* entry — a decision the check accepts, unlike a missing entry.
|
|
1197
|
+
*/
|
|
1198
|
+
function validateChainIndexerCoverage(chains, chainIndexer) {
|
|
1199
|
+
const issues = [];
|
|
1200
|
+
const requiresEntry = (chain) => typeof chain.caip2 === 'string' &&
|
|
1201
|
+
chain.caip2.startsWith('eip155:') &&
|
|
1202
|
+
chain.virtual !== true;
|
|
1203
|
+
for (const [chainId, chain] of Object.entries(chains)) {
|
|
1204
|
+
if (!requiresEntry(chain))
|
|
1205
|
+
continue;
|
|
1206
|
+
if (chainIndexer[chainId] === undefined) {
|
|
1207
|
+
issues.push({
|
|
1208
|
+
severity: 'error',
|
|
1209
|
+
chainId,
|
|
1210
|
+
chainName: isNonEmptyString(chain.name) ? chain.name : '<unnamed>',
|
|
1211
|
+
message: 'EVM chain has no chain-indexer entry — classify it in config/chain-indexer.jsonnet (see docs/add-chain.md, "Classify the chain for the indexer")',
|
|
1212
|
+
});
|
|
1213
|
+
}
|
|
1214
|
+
}
|
|
1215
|
+
for (const [chainId, entry] of Object.entries(chainIndexer)) {
|
|
1216
|
+
const chain = chains[chainId];
|
|
1217
|
+
const chainName = chain && isNonEmptyString(chain.name) ? chain.name : '<not in registry>';
|
|
1218
|
+
const issue = (message) => issues.push({ severity: 'error', chainId, chainName, message });
|
|
1219
|
+
if (!chain || !requiresEntry(chain)) {
|
|
1220
|
+
issue('chain-indexer entry addresses no real EVM chain — remove it from config/chain-indexer.jsonnet, or add the chain first');
|
|
1221
|
+
continue;
|
|
1222
|
+
}
|
|
1223
|
+
if (!isObject(entry)) {
|
|
1224
|
+
issue('chain-indexer entry is not an object');
|
|
1225
|
+
continue;
|
|
1226
|
+
}
|
|
1227
|
+
// `{ indexed: false }` is a complete entry: the explicit decision "this
|
|
1228
|
+
// chain is not indexed" — as opposed to a missing entry, which is no
|
|
1229
|
+
// decision at all and fails coverage above. Nothing may ride along: scan
|
|
1230
|
+
// settings on a chain that is not scanned are a contradiction, and
|
|
1231
|
+
// `indexed: true` is what a full entry already says.
|
|
1232
|
+
if ('indexed' in entry) {
|
|
1233
|
+
if (entry.indexed !== false) {
|
|
1234
|
+
issue(`chain-indexer \`indexed\` = ${JSON.stringify(entry.indexed)} — the only legal value is false (a full entry already means indexed)`);
|
|
1235
|
+
}
|
|
1236
|
+
const extras = Object.keys(entry).filter((field) => field !== 'indexed');
|
|
1237
|
+
if (extras.length) {
|
|
1238
|
+
issue(`chain-indexer entry sets indexed: false but also carries ${extras.map((field) => `\`${field}\``).join(', ')} — an opt-out is the whole entry, it takes no scan settings`);
|
|
1239
|
+
}
|
|
1240
|
+
continue;
|
|
1241
|
+
}
|
|
1242
|
+
for (const [field, check] of Object.entries(CHAIN_INDEXER_FIELDS)) {
|
|
1243
|
+
if (entry[field] === undefined) {
|
|
1244
|
+
issue(`chain-indexer entry is missing \`${field}\` — every field is mandatory, a knob nobody set is a decision nobody made`);
|
|
1245
|
+
}
|
|
1246
|
+
else if (!check(entry[field])) {
|
|
1247
|
+
issue(`chain-indexer \`${field}\` = ${JSON.stringify(entry[field])} is not a legal value`);
|
|
1248
|
+
}
|
|
1249
|
+
}
|
|
1250
|
+
for (const field of Object.keys(entry)) {
|
|
1251
|
+
if (!(field in CHAIN_INDEXER_FIELDS)) {
|
|
1252
|
+
issue(`chain-indexer entry carries unrecognized field \`${field}\` — the artifact's shape is exact, fix the name or drop it`);
|
|
1253
|
+
}
|
|
1254
|
+
}
|
|
1255
|
+
const instantClass = entry.finality === 'instant';
|
|
1256
|
+
const instantSource = entry.finality_source === 'instant';
|
|
1257
|
+
if (instantClass !== instantSource) {
|
|
1258
|
+
issue(`finality = ${JSON.stringify(entry.finality)} but finality_source = ${JSON.stringify(entry.finality_source)} — instant chains take neither the reorg nor the watermark path, everything else needs a real finality tag`);
|
|
1259
|
+
}
|
|
1260
|
+
if (isNonNegativeInt(entry.finality_poll_interval_ms) &&
|
|
1261
|
+
instantSource !== (entry.finality_poll_interval_ms === 0)) {
|
|
1262
|
+
issue(`finality_poll_interval_ms = ${entry.finality_poll_interval_ms} does not match finality_source = ${JSON.stringify(entry.finality_source)} — 0 exactly when the source is instant`);
|
|
1263
|
+
}
|
|
1264
|
+
const streaming = entry.streaming_poll_interval_ms;
|
|
1265
|
+
const poll = entry.poll_interval_ms;
|
|
1266
|
+
if (typeof streaming === 'number' &&
|
|
1267
|
+
typeof poll === 'number' &&
|
|
1268
|
+
streaming < poll) {
|
|
1269
|
+
issue(`streaming_poll_interval_ms = ${streaming} is below poll_interval_ms = ${poll} — the poll under a connected stream is a relaxation, never a speedup`);
|
|
1270
|
+
}
|
|
1271
|
+
}
|
|
1272
|
+
return issues;
|
|
1273
|
+
}
|
|
1146
1274
|
/**
|
|
1147
1275
|
* Formats validation issues for console output.
|
|
1148
1276
|
*
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
declare const PACKAGE_VERSION = "2.
|
|
1
|
+
declare const PACKAGE_VERSION = "2.8.0";
|
|
2
2
|
export { PACKAGE_VERSION };
|
|
@@ -2,5 +2,5 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.PACKAGE_VERSION = void 0;
|
|
4
4
|
// Auto-generated by scripts/generate.ts. Do not edit manually.
|
|
5
|
-
const PACKAGE_VERSION = "2.
|
|
5
|
+
const PACKAGE_VERSION = "2.8.0";
|
|
6
6
|
exports.PACKAGE_VERSION = PACKAGE_VERSION;
|