@rhinestone/shared-configs 2.20.1 → 2.21.1

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.
@@ -10,7 +10,8 @@
10
10
  "reorg_window_k": 32,
11
11
  "reverify_delay_ms": 500,
12
12
  "scan_batch_max_blocks": 200,
13
- "streaming_poll_interval_ms": 36000
13
+ "streaming_poll_interval_ms": 36000,
14
+ "vm": "evm"
14
15
  },
15
16
  "10": {
16
17
  "finality": "sequencer",
@@ -23,7 +24,8 @@
23
24
  "reorg_window_k": 16,
24
25
  "reverify_delay_ms": 500,
25
26
  "scan_batch_max_blocks": 200,
26
- "streaming_poll_interval_ms": 6000
27
+ "streaming_poll_interval_ms": 6000,
28
+ "vm": "evm"
27
29
  },
28
30
  "56": {
29
31
  "finality": "probabilistic",
@@ -36,7 +38,8 @@
36
38
  "reorg_window_k": 32,
37
39
  "reverify_delay_ms": 500,
38
40
  "scan_batch_max_blocks": 200,
39
- "streaming_poll_interval_ms": 3000
41
+ "streaming_poll_interval_ms": 3000,
42
+ "vm": "evm"
40
43
  },
41
44
  "100": {
42
45
  "finality": "probabilistic",
@@ -49,7 +52,8 @@
49
52
  "reorg_window_k": 32,
50
53
  "reverify_delay_ms": 500,
51
54
  "scan_batch_max_blocks": 200,
52
- "streaming_poll_interval_ms": 15000
55
+ "streaming_poll_interval_ms": 15000,
56
+ "vm": "evm"
53
57
  },
54
58
  "130": {
55
59
  "finality": "sequencer",
@@ -62,7 +66,8 @@
62
66
  "reorg_window_k": 16,
63
67
  "reverify_delay_ms": 500,
64
68
  "scan_batch_max_blocks": 200,
65
- "streaming_poll_interval_ms": 3000
69
+ "streaming_poll_interval_ms": 3000,
70
+ "vm": "evm"
66
71
  },
67
72
  "137": {
68
73
  "finality": "probabilistic",
@@ -75,7 +80,8 @@
75
80
  "reorg_window_k": 64,
76
81
  "reverify_delay_ms": 500,
77
82
  "scan_batch_max_blocks": 200,
78
- "streaming_poll_interval_ms": 4500
83
+ "streaming_poll_interval_ms": 4500,
84
+ "vm": "evm"
79
85
  },
80
86
  "143": {
81
87
  "finality": "instant",
@@ -88,7 +94,8 @@
88
94
  "reorg_window_k": 4,
89
95
  "reverify_delay_ms": 0,
90
96
  "scan_batch_max_blocks": 200,
91
- "streaming_poll_interval_ms": 3000
97
+ "streaming_poll_interval_ms": 3000,
98
+ "vm": "evm"
92
99
  },
93
100
  "146": {
94
101
  "finality": "instant",
@@ -101,7 +108,8 @@
101
108
  "reorg_window_k": 4,
102
109
  "reverify_delay_ms": 0,
103
110
  "scan_batch_max_blocks": 200,
104
- "streaming_poll_interval_ms": 4500
111
+ "streaming_poll_interval_ms": 4500,
112
+ "vm": "evm"
105
113
  },
106
114
  "196": {
107
115
  "finality": "sequencer",
@@ -114,7 +122,8 @@
114
122
  "reorg_window_k": 16,
115
123
  "reverify_delay_ms": 500,
116
124
  "scan_batch_max_blocks": 200,
117
- "streaming_poll_interval_ms": 3000
125
+ "streaming_poll_interval_ms": 3000,
126
+ "vm": "evm"
118
127
  },
119
128
  "999": {
120
129
  "finality": "instant",
@@ -127,7 +136,8 @@
127
136
  "reorg_window_k": 4,
128
137
  "reverify_delay_ms": 0,
129
138
  "scan_batch_max_blocks": 200,
130
- "streaming_poll_interval_ms": 3000
139
+ "streaming_poll_interval_ms": 3000,
140
+ "vm": "evm"
131
141
  },
132
142
  "1868": {
133
143
  "finality": "sequencer",
@@ -140,7 +150,8 @@
140
150
  "reorg_window_k": 16,
141
151
  "reverify_delay_ms": 500,
142
152
  "scan_batch_max_blocks": 200,
143
- "streaming_poll_interval_ms": 6000
153
+ "streaming_poll_interval_ms": 6000,
154
+ "vm": "evm"
144
155
  },
145
156
  "4663": {
146
157
  "finality": "sequencer",
@@ -153,7 +164,22 @@
153
164
  "reorg_window_k": 16,
154
165
  "reverify_delay_ms": 500,
155
166
  "scan_batch_max_blocks": 200,
156
- "streaming_poll_interval_ms": 3000
167
+ "streaming_poll_interval_ms": 3000,
168
+ "vm": "evm"
169
+ },
170
+ "5042": {
171
+ "finality": "instant",
172
+ "finality_poll_interval_ms": 0,
173
+ "finality_source": "instant",
174
+ "grace_ms": 0,
175
+ "max_catchup_blocks": 5000,
176
+ "parse_concurrency": 8,
177
+ "poll_interval_ms": 1000,
178
+ "reorg_window_k": 4,
179
+ "reverify_delay_ms": 0,
180
+ "scan_batch_max_blocks": 200,
181
+ "streaming_poll_interval_ms": 15000,
182
+ "vm": "evm"
157
183
  },
158
184
  "8453": {
159
185
  "finality": "sequencer",
@@ -166,7 +192,8 @@
166
192
  "reorg_window_k": 16,
167
193
  "reverify_delay_ms": 500,
168
194
  "scan_batch_max_blocks": 200,
169
- "streaming_poll_interval_ms": 6000
195
+ "streaming_poll_interval_ms": 6000,
196
+ "vm": "evm"
170
197
  },
171
198
  "9745": {
172
199
  "finality": "instant",
@@ -179,7 +206,8 @@
179
206
  "reorg_window_k": 4,
180
207
  "reverify_delay_ms": 0,
181
208
  "scan_batch_max_blocks": 200,
182
- "streaming_poll_interval_ms": 3000
209
+ "streaming_poll_interval_ms": 3000,
210
+ "vm": "evm"
183
211
  },
184
212
  "9746": {
185
213
  "finality": "instant",
@@ -192,7 +220,8 @@
192
220
  "reorg_window_k": 4,
193
221
  "reverify_delay_ms": 0,
194
222
  "scan_batch_max_blocks": 200,
195
- "streaming_poll_interval_ms": 3000
223
+ "streaming_poll_interval_ms": 3000,
224
+ "vm": "evm"
196
225
  },
197
226
  "42161": {
198
227
  "finality": "sequencer",
@@ -205,7 +234,8 @@
205
234
  "reorg_window_k": 16,
206
235
  "reverify_delay_ms": 500,
207
236
  "scan_batch_max_blocks": 200,
208
- "streaming_poll_interval_ms": 3000
237
+ "streaming_poll_interval_ms": 3000,
238
+ "vm": "evm"
209
239
  },
210
240
  "43114": {
211
241
  "finality": "instant",
@@ -218,7 +248,8 @@
218
248
  "reorg_window_k": 4,
219
249
  "reverify_delay_ms": 0,
220
250
  "scan_batch_max_blocks": 200,
221
- "streaming_poll_interval_ms": 3000
251
+ "streaming_poll_interval_ms": 3000,
252
+ "vm": "evm"
222
253
  },
223
254
  "57073": {
224
255
  "finality": "sequencer",
@@ -231,7 +262,8 @@
231
262
  "reorg_window_k": 16,
232
263
  "reverify_delay_ms": 500,
233
264
  "scan_batch_max_blocks": 200,
234
- "streaming_poll_interval_ms": 3000
265
+ "streaming_poll_interval_ms": 3000,
266
+ "vm": "evm"
235
267
  },
236
268
  "84532": {
237
269
  "finality": "sequencer",
@@ -244,7 +276,8 @@
244
276
  "reorg_window_k": 8,
245
277
  "reverify_delay_ms": 500,
246
278
  "scan_batch_max_blocks": 200,
247
- "streaming_poll_interval_ms": 6000
279
+ "streaming_poll_interval_ms": 6000,
280
+ "vm": "evm"
248
281
  },
249
282
  "421614": {
250
283
  "finality": "sequencer",
@@ -257,7 +290,8 @@
257
290
  "reorg_window_k": 8,
258
291
  "reverify_delay_ms": 500,
259
292
  "scan_batch_max_blocks": 200,
260
- "streaming_poll_interval_ms": 3000
293
+ "streaming_poll_interval_ms": 3000,
294
+ "vm": "evm"
261
295
  },
262
296
  "747474": {
263
297
  "finality": "sequencer",
@@ -270,7 +304,8 @@
270
304
  "reorg_window_k": 16,
271
305
  "reverify_delay_ms": 500,
272
306
  "scan_batch_max_blocks": 200,
273
- "streaming_poll_interval_ms": 3000
307
+ "streaming_poll_interval_ms": 3000,
308
+ "vm": "evm"
274
309
  },
275
310
  "11155111": {
276
311
  "finality": "probabilistic",
@@ -283,7 +318,8 @@
283
318
  "reorg_window_k": 32,
284
319
  "reverify_delay_ms": 500,
285
320
  "scan_batch_max_blocks": 200,
286
- "streaming_poll_interval_ms": 36000
321
+ "streaming_poll_interval_ms": 36000,
322
+ "vm": "evm"
287
323
  },
288
324
  "11155420": {
289
325
  "finality": "sequencer",
@@ -296,6 +332,29 @@
296
332
  "reorg_window_k": 8,
297
333
  "reverify_delay_ms": 500,
298
334
  "scan_batch_max_blocks": 200,
299
- "streaming_poll_interval_ms": 6000
335
+ "streaming_poll_interval_ms": 6000,
336
+ "vm": "evm"
337
+ },
338
+ "792703809": {
339
+ "commitment": "finalized",
340
+ "max_catchup_signatures": 5000,
341
+ "overlap_slots": 300,
342
+ "page_limit": 1000,
343
+ "poll_interval_ms": 2000,
344
+ "topics": [
345
+ "5q44zFF8gziimbCipqNQ7GYed8pkTRDmUKFUFTwd4DjB"
346
+ ],
347
+ "vm": "svm"
348
+ },
349
+ "792703810": {
350
+ "commitment": "finalized",
351
+ "max_catchup_signatures": 5000,
352
+ "overlap_slots": 300,
353
+ "page_limit": 1000,
354
+ "poll_interval_ms": 2000,
355
+ "topics": [
356
+ "5q44zFF8gziimbCipqNQ7GYed8pkTRDmUKFUFTwd4DjB"
357
+ ],
358
+ "vm": "svm"
300
359
  }
301
360
  }
@@ -1519,6 +1519,66 @@
1519
1519
  "symbol": "WETH"
1520
1520
  }
1521
1521
  },
1522
+ "5042": {
1523
+ "caip2": "eip155:5042",
1524
+ "explorer": {
1525
+ "addressPath": "/address/",
1526
+ "txPath": "/tx/",
1527
+ "url": "https://explorer.arc.io"
1528
+ },
1529
+ "iconSlug": "arc",
1530
+ "name": "Arc",
1531
+ "nativeToken": {
1532
+ "address": "0x0000000000000000000000000000000000000000",
1533
+ "decimals": 18,
1534
+ "symbol": "USDC"
1535
+ },
1536
+ "network": "mainnet",
1537
+ "providerIds": {
1538
+ "alchemy": null,
1539
+ "zerion": null
1540
+ },
1541
+ "publicRpcUrl": "https://rpc.mainnet.arc.io",
1542
+ "sessionCapable": false,
1543
+ "settlementConfig": {
1544
+ "cctp": {
1545
+ "domain": 26
1546
+ },
1547
+ "rhino": {
1548
+ "bridgeContract": "0x5e023c31e1d3dcd08a1b3e8c96f6ef8aa8fcacd1",
1549
+ "chainName": "ARC"
1550
+ }
1551
+ },
1552
+ "settlementLayers": [
1553
+ "ACROSS",
1554
+ "RELAY",
1555
+ "RHINO",
1556
+ "CCTP"
1557
+ ],
1558
+ "stack": "vanilla",
1559
+ "swapQuoters": [],
1560
+ "tokens": [
1561
+ {
1562
+ "address": "0x3600000000000000000000000000000000000000",
1563
+ "appFee": true,
1564
+ "approvalSlot": null,
1565
+ "balanceSlot": null,
1566
+ "decimals": 6,
1567
+ "gasRefund": true,
1568
+ "pegGroup": "USD",
1569
+ "settlementLayers": [
1570
+ "ACROSS"
1571
+ ],
1572
+ "symbol": "USDC"
1573
+ }
1574
+ ],
1575
+ "vmType": "evm",
1576
+ "wrappedNativeToken": {
1577
+ "address": "0x3600000000000000000000000000000000000000",
1578
+ "decimals": 6,
1579
+ "symbol": "USDC"
1580
+ }
1581
+ },
1522
1582
  "8453": {
1523
1583
  "caip2": "eip155:8453",
1524
1584
  "explorer": {
@@ -3142,6 +3202,7 @@
3142
3202
  "alchemy": null,
3143
3203
  "zerion": null
3144
3204
  },
3205
+ "rpcId": "mainnet-beta",
3145
3206
  "sessionCapable": false,
3146
3207
  "settlementConfig": {
3147
3208
  "cctp": {
@@ -3246,6 +3307,7 @@
3246
3307
  "alchemy": null,
3247
3308
  "zerion": null
3248
3309
  },
3310
+ "rpcId": "devnet",
3249
3311
  "sessionCapable": false,
3250
3312
  "settlementConfig": {
3251
3313
  "cctp": {
@@ -96,6 +96,10 @@
96
96
  "swapAdapter": "0x58BC7d9279A38a1537c2f74426dBDbD804AD3A18",
97
97
  "swapAdapterTokenSpender": "0xc5f6641b3ade357c5df77a1859d16528fe8b3e93"
98
98
  },
99
+ "5042": {
100
+ "acrossBridge": "0x9b4A302A548c7e313c2b74C461db7b84d3074A84",
101
+ "spokepool": "0x9b4A302A548c7e313c2b74C461db7b84d3074A84"
102
+ },
99
103
  "8453": {
100
104
  "ecoArbiter": "0x1BeBAfb3D05d84A5Bfd94800c88d1342f755d8AB",
101
105
  "acrossBridge": "0x09aea4b2242abC8bb4BB78D537A67a245A7bEC64",
@@ -96,6 +96,10 @@
96
96
  "swapAdapter": "0xaE448E141F230AC3c6F6dA981C0882356c9B91a6",
97
97
  "swapAdapterTokenSpender": "0x3115c923734471b9da1d5bfef161d9c9e9963859"
98
98
  },
99
+ "5042": {
100
+ "acrossBridge": "0x9b4A302A548c7e313c2b74C461db7b84d3074A84",
101
+ "spokepool": "0x9b4A302A548c7e313c2b74C461db7b84d3074A84"
102
+ },
99
103
  "8453": {
100
104
  "ecoArbiter": "0x2e7627CCfAe4eDb336eEb5a70f08415B0F1a2b8C",
101
105
  "acrossBridge": "0x09aea4b2242abC8bb4BB78D537A67a245A7bEC64",
@@ -73,6 +73,13 @@
73
73
  "Alchemy"
74
74
  ]
75
75
  },
76
+ "5042": {
77
+ "providers": [
78
+ "Alchemy",
79
+ "arc-public-rpc",
80
+ "DRPC"
81
+ ]
82
+ },
76
83
  "8453": {
77
84
  "providers": [
78
85
  "DRPC",
@@ -13,6 +13,7 @@
13
13
  "999": "hyperliquid-mainnet",
14
14
  "1868": "soneium-mainnet",
15
15
  "4663": "robinhood-mainnet",
16
+ "5042": "arc-mainnet",
16
17
  "8453": "base-mainnet",
17
18
  "9745": "plasma-mainnet",
18
19
  "9746": "plasma-testnet",
@@ -41,6 +42,7 @@
41
42
  "999": "hyperliquid",
42
43
  "1868": "soneium",
43
44
  "4663": "robinhood",
45
+ "5042": "arc",
44
46
  "8453": "base",
45
47
  "9745": "plasma",
46
48
  "9746": "plasma-testnet",
@@ -55,6 +57,12 @@
55
57
  },
56
58
  "url_template": "https://lb.drpc.org/ogrpc?network={{chain_param}}&dkey=${DRPC_API_KEY}"
57
59
  },
60
+ "arc-public-rpc": {
61
+ "chain_mapping": {
62
+ "5042": "5042"
63
+ },
64
+ "url_template": "https://rpc.mainnet.arc.io"
65
+ },
58
66
  "eRPC": {
59
67
  "chain_mapping": {
60
68
  "1": "1",
@@ -69,6 +77,7 @@
69
77
  "999": "999",
70
78
  "1868": "1868",
71
79
  "4663": "4663",
80
+ "5042": "5042",
72
81
  "8453": "8453",
73
82
  "9745": "9745",
74
83
  "9746": "9746",
@@ -327,6 +327,56 @@ function errorsFor(chain) {
327
327
  (0, bun_test_1.expect)(error.message).toContain("TELEPORT");
328
328
  (0, bun_test_1.expect)(error.message).toContain("src/types.ts");
329
329
  });
330
+ // Absent is fine: chainRegistry() defaults it to the chain id, and a
331
+ // consumer reading a document written before the field existed does the
332
+ // same. Only the two ways AUTHORING can get it wrong are checked.
333
+ (0, bun_test_1.it)("accepts a chain with no rpcId", () => {
334
+ (0, bun_test_1.expect)(errorsFor(validChain())).toEqual([]);
335
+ });
336
+ (0, bun_test_1.it)("accepts an rpcId that matches the chain id", () => {
337
+ (0, bun_test_1.expect)(errorsFor(validChain({ rpcId: "1" }))).toEqual([]);
338
+ });
339
+ (0, bun_test_1.it)("rejects an rpcId override on an EVM chain", () => {
340
+ const [error] = errorsFor(validChain({ rpcId: "mainnet" }));
341
+ (0, bun_test_1.expect)(error.message).toContain("does not match the chain id");
342
+ });
343
+ // The failure this prevents is an empty feed, which reads exactly like a
344
+ // chain with no traffic — so it has to fail the build, not runtime.
345
+ (0, bun_test_1.it)("rejects a Solana chain whose rpcId is still the numeric default", () => {
346
+ const errors = (0, validation_1.validateChainConfig)({
347
+ "792703810": validChain({
348
+ name: "Solana Devnet",
349
+ vmType: "svm",
350
+ network: "testnet",
351
+ caip2: "solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1",
352
+ rpcId: "792703810",
353
+ }),
354
+ }).issues.filter((i) => i.severity === "error");
355
+ (0, bun_test_1.expect)(errors.map((e) => e.message).join("\n")).toContain("addressed by its cluster name");
356
+ });
357
+ (0, bun_test_1.it)("rejects a Solana chain with no rpcId at all", () => {
358
+ const errors = (0, validation_1.validateChainConfig)({
359
+ "792703810": validChain({
360
+ name: "Solana Devnet",
361
+ vmType: "svm",
362
+ network: "testnet",
363
+ caip2: "solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1",
364
+ }),
365
+ }).issues.filter((i) => i.severity === "error");
366
+ (0, bun_test_1.expect)(errors.map((e) => e.message).join("\n")).toContain("addressed by its cluster name");
367
+ });
368
+ (0, bun_test_1.it)("accepts a Solana chain addressed by its cluster", () => {
369
+ const errors = (0, validation_1.validateChainConfig)({
370
+ "792703810": validChain({
371
+ name: "Solana Devnet",
372
+ vmType: "svm",
373
+ network: "testnet",
374
+ caip2: "solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1",
375
+ rpcId: "devnet",
376
+ }),
377
+ }).issues.filter((i) => i.severity === "error");
378
+ (0, bun_test_1.expect)(errors).toEqual([]);
379
+ });
330
380
  });
331
381
  (0, bun_test_1.describe)("validateChainConfig — reachability", () => {
332
382
  (0, bun_test_1.it)("returns no issues for chains with multiple settlement layers", () => {
@@ -516,6 +566,7 @@ function errorsFor(chain) {
516
566
  /** A complete class-B entry, so semantic tests don't trip the shape checks. */
517
567
  function validIndexerEntry(overrides = {}) {
518
568
  return {
569
+ vm: "evm",
519
570
  poll_interval_ms: 1000,
520
571
  streaming_poll_interval_ms: 15000,
521
572
  reorg_window_k: 16,
@@ -541,12 +592,12 @@ function validIndexerEntry(overrides = {}) {
541
592
  (0, bun_test_1.expect)(issues).toHaveLength(1);
542
593
  (0, bun_test_1.expect)(issues[0].message).toContain("no chain-indexer entry");
543
594
  });
544
- (0, bun_test_1.it)("reports an entry addressing no real EVM chain", () => {
595
+ (0, bun_test_1.it)("reports an entry addressing a chain the indexer does not dial", () => {
545
596
  const issues = (0, validation_1.validateChainIndexerCoverage)({}, {
546
597
  "1": validIndexerEntry(),
547
598
  });
548
599
  (0, bun_test_1.expect)(issues).toHaveLength(1);
549
- (0, bun_test_1.expect)(issues[0].message).toContain("no real EVM chain");
600
+ (0, bun_test_1.expect)(issues[0].message).toContain("no chain the indexer dials");
550
601
  });
551
602
  // HyperCore is vmType "evm" with a hypercore: caip2 — the eip155 namespace,
552
603
  // not vmType, decides whether the indexer dials a chain.
@@ -627,6 +678,105 @@ function validIndexerEntry(overrides = {}) {
627
678
  });
628
679
  (0, bun_test_1.expect)(issues.map((i) => i.message).join("\n")).toContain("the only legal value is false");
629
680
  });
681
+ /** The Solana entry as the artifact publishes it. */
682
+ function validSvmEntry(overrides = {}) {
683
+ return {
684
+ vm: "svm",
685
+ poll_interval_ms: 2000,
686
+ commitment: "finalized",
687
+ topics: ["5q44zFF8gziimbCipqNQ7GYed8pkTRDmUKFUFTwd4DjB"],
688
+ page_limit: 1000,
689
+ overlap_slots: 300,
690
+ max_catchup_signatures: 5000,
691
+ ...overrides,
692
+ };
693
+ }
694
+ function solanaChain(overrides = {}) {
695
+ return validChain({
696
+ name: "Solana Devnet",
697
+ vmType: "svm",
698
+ network: "testnet",
699
+ caip2: "solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1",
700
+ rpcId: "devnet",
701
+ ...overrides,
702
+ });
703
+ }
704
+ (0, bun_test_1.it)("accepts a complete SVM entry", () => {
705
+ (0, bun_test_1.expect)((0, validation_1.validateChainIndexerCoverage)({ "792703810": solanaChain() }, {
706
+ "792703810": validSvmEntry(),
707
+ })).toEqual([]);
708
+ });
709
+ (0, bun_test_1.it)("requires an entry for a Solana chain, like any other dialable chain", () => {
710
+ const issues = (0, validation_1.validateChainIndexerCoverage)({ "792703810": solanaChain() }, {});
711
+ (0, bun_test_1.expect)(issues).toHaveLength(1);
712
+ (0, bun_test_1.expect)(issues[0].message).toContain("SVM chain has no chain-indexer entry");
713
+ });
714
+ // The knob sets are disjoint, so judging an entry against the wrong one
715
+ // reports every field of both as wrong — which is the point: an SVM entry is
716
+ // not an EVM entry missing eleven fields.
717
+ (0, bun_test_1.it)("rejects EVM knobs on a Solana chain", () => {
718
+ const issues = (0, validation_1.validateChainIndexerCoverage)({ "792703810": solanaChain() }, { "792703810": validIndexerEntry() });
719
+ (0, bun_test_1.expect)(issues.map((i) => i.message).join("\n")).toContain('`vm` = "evm" but solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1 is a svm chain');
720
+ });
721
+ // The dangerous direction: the indexer reads `vm` first and passes over
722
+ // families it does not index, so an svm tag on an EVM chain would validate
723
+ // as a coherent SVM entry here and then leave that chain silently unscanned.
724
+ (0, bun_test_1.it)("rejects an svm tag on an EVM chain", () => {
725
+ const issues = (0, validation_1.validateChainIndexerCoverage)({ "1": validChain() }, {
726
+ "1": validSvmEntry(),
727
+ });
728
+ (0, bun_test_1.expect)(issues.map((i) => i.message).join("\n")).toContain("would never be scanned");
729
+ });
730
+ bun_test_1.it.each([
731
+ ["a missing SVM field", { topics: undefined }, "missing `topics`"],
732
+ ["an empty topic list", { topics: [] }, "`topics` = [] is not a legal value"],
733
+ [
734
+ "a malformed topic",
735
+ { topics: ["not-base58-и"] },
736
+ "is not a legal value",
737
+ ],
738
+ // A character-count regex accepts both of these — they are legal base58
739
+ // characters at a legal length — but neither decodes to a 32-byte account.
740
+ // A marker like this would pass the build and then name a feed no
741
+ // transaction can appear in, which reads as a chain with no traffic.
742
+ [
743
+ "a topic that decodes to 33 bytes",
744
+ { topics: ["z".repeat(44)] },
745
+ "is not a legal value",
746
+ ],
747
+ [
748
+ "a topic that decodes to 43 zero bytes",
749
+ { topics: ["1".repeat(43)] },
750
+ "is not a legal value",
751
+ ],
752
+ [
753
+ "a commitment nothing settles at",
754
+ { commitment: "processed" },
755
+ '`commitment` = "processed" is not a legal value',
756
+ ],
757
+ // Not a narrow allowlist: the SVM entry omits every reorg and retraction
758
+ // knob BECAUSE a finalized signature is settled. Reading at `confirmed`
759
+ // has to arrive with the retraction kind, not as a value a facts release
760
+ // can set on its own.
761
+ [
762
+ "a reversible commitment the schema cannot retract",
763
+ { commitment: "confirmed" },
764
+ '`commitment` = "confirmed" is not a legal value',
765
+ ],
766
+ [
767
+ "a page limit above the RPC ceiling",
768
+ { page_limit: 1001 },
769
+ "`page_limit` = 1001 is not a legal value",
770
+ ],
771
+ [
772
+ "an EVM knob smuggled in",
773
+ { reorg_window_k: 16 },
774
+ "unrecognized field `reorg_window_k`",
775
+ ],
776
+ ])("rejects %s on an SVM entry", (_label, overrides, expected) => {
777
+ const issues = (0, validation_1.validateChainIndexerCoverage)({ "792703810": solanaChain() }, { "792703810": validSvmEntry(overrides) });
778
+ (0, bun_test_1.expect)(issues.map((i) => i.message).join("\n")).toContain(expected);
779
+ });
630
780
  (0, bun_test_1.it)("accepts a coherent instant-class entry", () => {
631
781
  const entry = validIndexerEntry({
632
782
  finality: "instant",
@@ -162,6 +162,12 @@ function renderChains(chainRegistry, mainnets, testnets) {
162
162
  " /** Public, unauthenticated https JSON-RPC endpoint — what a browser needs to",
163
163
  " * build this chain with `defineChain`. Absent means display-only. */",
164
164
  " publicRpcUrl?: string;",
165
+ " /** How this chain is addressed when dialling it, within its architecture:",
166
+ " * the last segment of `{proxy}/{architecture}/{rpcId}`. Present only where",
167
+ " * the chain id is NOT what you dial — the cluster name (`mainnet-beta`,",
168
+ " * `devnet`) on Solana. Absent means the chain id is the id, so read it as",
169
+ " * `chain.rpcId ?? String(chainId)` rather than branching on vmType. */",
170
+ " rpcId?: string;",
165
171
  " /** True for virtual chains (e.g. HyperCore) that settle on another chain. */",
166
172
  " virtual?: boolean;",
167
173
  " /** Which directions of transfer the chain supports; absent means both.",
@@ -226,19 +226,24 @@ export declare function validateChainConfig(chains: ChainRegistry): ValidationRe
226
226
  */
227
227
  export declare function validateOftCoverage(chains: ChainRegistry, oft: Record<string, unknown>): ValidationIssue[];
228
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.
229
+ * Insists that every chain the indexer dials carries a complete, coherent
230
+ * chain-indexer entry, and that no entry addresses a chain that does not exist.
231
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:
232
+ * The indexer scans every dialable chain it has a machine for, and its contract
233
+ * is that an unclassified chain is a hard failure — this check moves that
234
+ * failure from the indexer's boot to this build, where the person adding the
235
+ * chain sees it (RHI-6107). Selected by the caip2 NAMESPACE, NOT by vmType:
236
236
  * HyperCore is vmType "evm" with a `hypercore:` caip2 and must not require an
237
237
  * entry — the same discriminator the publish workflow's contracts guard uses,
238
238
  * for the same reason. Runs after `classifyChainCaip2`, which stamps the field.
239
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.
240
+ * Two families today (`eip155:` → evm, `solana:` → svm), with disjoint knob
241
+ * sets — an entry is judged against the one its chain implies, and its own `vm`
242
+ * is cross-checked against that rather than trusted (RHI-7338).
243
+ *
244
+ * The EVM cross-field rules mirror chain-indexer's
245
+ * `ChainScanSettings::validate`, so a value set this check accepts is one the
246
+ * indexer will accept.
242
247
  *
243
248
  * A chain deliberately not indexed carries `{ indexed: false }` as its whole
244
249
  * entry — a decision the check accepts, unlike a missing entry.
@@ -104,6 +104,47 @@ const isObject = (v) => typeof v === 'object' && v !== null && !Array.isArray(v)
104
104
  const isNonEmptyString = (v) => typeof v === 'string' && v.length > 0;
105
105
  const isNonNegativeInt = (v) => typeof v === 'number' && Number.isInteger(v) && v >= 0;
106
106
  const isAbsentOr = (v, check) => v === undefined || check(v);
107
+ const BASE58_ALPHABET = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz';
108
+ /**
109
+ * Decode base58 (Bitcoin/Solana alphabet), or null if the string is not
110
+ * base58. Each leading `1` stands for one leading zero byte.
111
+ *
112
+ * Decoded rather than pattern-matched because the length is what actually
113
+ * matters and a character-count regex does not imply it: 44 `z`s are all legal
114
+ * base58 characters and decode to THIRTY-THREE bytes, while 43 `1`s decode to
115
+ * 43 zero bytes. Both would pass a `{43,44}` regex and neither is an account.
116
+ * The failure that buys is a marker that validates here and then names a feed
117
+ * no transaction can ever appear in — silence indistinguishable from a chain
118
+ * with no traffic.
119
+ */
120
+ function decodeBase58(value) {
121
+ if (value.length === 0)
122
+ return null;
123
+ const bytes = [];
124
+ for (const char of value) {
125
+ let carry = BASE58_ALPHABET.indexOf(char);
126
+ if (carry < 0)
127
+ return null;
128
+ for (let i = 0; i < bytes.length; i++) {
129
+ carry += bytes[i] * 58;
130
+ bytes[i] = carry & 0xff;
131
+ carry >>= 8;
132
+ }
133
+ while (carry > 0) {
134
+ bytes.push(carry & 0xff);
135
+ carry >>= 8;
136
+ }
137
+ }
138
+ let zeros = 0;
139
+ while (zeros < value.length && value[zeros] === '1')
140
+ zeros += 1;
141
+ return Uint8Array.from([
142
+ ...new Array(zeros).fill(0),
143
+ ...bytes.reverse(),
144
+ ]);
145
+ }
146
+ /** A base58 Solana account address: exactly 32 bytes once decoded. */
147
+ const isBase58Account = (v) => typeof v === 'string' && decodeBase58(v)?.length === 32;
107
148
  // Every vendor layer's REQUIRED payload, keyed by the SettlementLayer value
108
149
  // that switches it on. This is what replaces the chain-id allowlists the
109
150
  // jsonnet used to carry with "must match <map> in the orchestrator" comments —
@@ -282,6 +323,30 @@ function structuralProblems(registryChainId, chain) {
282
323
  if (!isAbsentOr(chain.publicRpcUrl, isPublicRpcUrl)) {
283
324
  problems.push(`publicRpcUrl ${JSON.stringify(chain.publicRpcUrl)} is not an https:// URL`);
284
325
  }
326
+ // `rpcId` is authored, and only where the chain id is not what you dial — so
327
+ // absence is the normal answer and carries meaning ("the chain id is the
328
+ // id"). There is deliberately no blanket presence check; what is checked is
329
+ // the two ways authoring it can go wrong.
330
+ //
331
+ // An `rpcId` on an EVM chain that is not its own id routes every call for
332
+ // that chain to the wrong network. A Solana chain with none leaves a consumer
333
+ // falling back to `{proxy}/svm/792703810`, which reaches nothing — and the
334
+ // symptom is an empty feed, indistinguishable from a chain with no traffic,
335
+ // which is why it has to fail here rather than at runtime.
336
+ if (!isAbsentOr(chain.rpcId, isNonEmptyString)) {
337
+ problems.push(`rpcId ${JSON.stringify(chain.rpcId)} is not a non-empty string`);
338
+ }
339
+ else if (typeof chain.caip2 === 'string' &&
340
+ chain.caip2.startsWith('eip155:') &&
341
+ chain.rpcId !== undefined &&
342
+ chain.rpcId !== registryChainId) {
343
+ problems.push(`rpcId ${JSON.stringify(chain.rpcId)} does not match the chain id for an eip155 chain — EVM chains are addressed by their id, so drop the override`);
344
+ }
345
+ else if (typeof chain.caip2 === 'string' &&
346
+ chain.caip2.startsWith('solana:') &&
347
+ (chain.rpcId === undefined || chain.rpcId === registryChainId)) {
348
+ problems.push('rpcId is missing or is the chain id, but a Solana chain is addressed by its cluster name (e.g. "devnet") — author it in config/non_evm_chains.jsonnet');
349
+ }
285
350
  if (!isAbsentOr(chain.virtual, (v) => typeof v === 'boolean')) {
286
351
  problems.push('virtual is not a boolean');
287
352
  }
@@ -1236,12 +1301,15 @@ const CHAIN_INDEXER_FINALITY_SOURCE = new Set([
1236
1301
  'safe',
1237
1302
  'instant',
1238
1303
  ]);
1239
- // Every field of one chain-indexer entry, all required. The wire is exact on
1240
- // purpose: the indexer's own contract is "every field mandatory — a knob nobody
1241
- // set is a decision nobody made", so a missing field here is the same authoring
1242
- // mistake as a missing entry, and an unrecognized field is a typo that would
1243
- // otherwise ship silently.
1304
+ // Every field of one EVM chain-indexer entry, all required. The wire is exact
1305
+ // on purpose: the indexer's own contract is "every field mandatory — a knob
1306
+ // nobody set is a decision nobody made", so a missing field here is the same
1307
+ // authoring mistake as a missing entry, and an unrecognized field is a typo
1308
+ // that would otherwise ship silently.
1309
+ //
1310
+ // The SVM set below is disjoint from this one — see CHAIN_INDEXER_FIELDS_BY_VM.
1244
1311
  const CHAIN_INDEXER_FIELDS = {
1312
+ vm: (v) => v === 'evm',
1245
1313
  poll_interval_ms: isNonNegativeInt,
1246
1314
  streaming_poll_interval_ms: isNonNegativeInt,
1247
1315
  reorg_window_k: isNonNegativeInt,
@@ -1254,38 +1322,104 @@ const CHAIN_INDEXER_FIELDS = {
1254
1322
  finality_poll_interval_ms: isNonNegativeInt,
1255
1323
  grace_ms: isNonNegativeInt,
1256
1324
  };
1325
+ // `finalized` is the ONLY legal commitment, and that is a coherence rule, not
1326
+ // a narrow allowlist waiting to be widened.
1327
+ //
1328
+ // The SVM entry carries no reorg window, no grace period and no retraction
1329
+ // vocabulary — the family has one message kind where EVM has four — and it can
1330
+ // omit all of that precisely BECAUSE a finalized signature is settled. Admit
1331
+ // `confirmed` here and that stops being true: the indexer would publish
1332
+ // reversible signatures as settled with nothing in the schema able to take
1333
+ // them back, and the build would pass.
1334
+ //
1335
+ // So the two travel together. Reading at `confirmed` is a real thing we may
1336
+ // want one day; it arrives with the retraction kind, as one change, not as a
1337
+ // value someone can set in a facts release. Same shape as the EVM rule that
1338
+ // `finality: instant` and `finality_source: instant` imply each other.
1339
+ //
1340
+ // `processed` is absent for a further reason: eRPC refuses it outright for
1341
+ // getSignaturesForAddress and getTransaction.
1342
+ const CHAIN_INDEXER_COMMITMENT = new Set(['finalized']);
1343
+ // The SVM knob set — a different machine, so none of the EVM fields above
1344
+ // apply. There is no block scan, no parent-hash chain and no log filter: the
1345
+ // unit is a transaction signature reached through the address → signature
1346
+ // index. And no reorg or finality knobs, because ingest is at `finalized`
1347
+ // commitment where a signature is settled (RHI-7338).
1348
+ //
1349
+ // `commitment` is its own field rather than an assumed constant because
1350
+ // omitting it on the wire silently yields eRPC's `confirmed` network default —
1351
+ // unrooted reads that look exactly like correct operation.
1352
+ const CHAIN_INDEXER_SVM_FIELDS = {
1353
+ vm: (v) => v === 'svm',
1354
+ poll_interval_ms: isNonNegativeInt,
1355
+ commitment: (v) => typeof v === 'string' && CHAIN_INDEXER_COMMITMENT.has(v),
1356
+ // A list, not a scalar: rotating the marker or adding our own no-op program
1357
+ // id as a second topic must be a facts release, not an indexer deploy. Each
1358
+ // entry is a base58 account address.
1359
+ topics: (v) => Array.isArray(v) && v.length > 0 && v.every((t) => isBase58Account(t)),
1360
+ page_limit: (v) => isNonNegativeInt(v) && v > 0 && v <= 1000,
1361
+ overlap_slots: isNonNegativeInt,
1362
+ max_catchup_signatures: isNonNegativeInt,
1363
+ };
1364
+ // Which machine an entry's knobs belong to. Mirrors the `vm` tag chain-indexer
1365
+ // reads before anything else in the entry: an unrecognised value there is
1366
+ // skipped rather than indexed, so a typo here would silently stop a chain
1367
+ // being scanned, which is why it fails the build instead.
1368
+ const CHAIN_INDEXER_FIELDS_BY_VM = {
1369
+ evm: CHAIN_INDEXER_FIELDS,
1370
+ svm: CHAIN_INDEXER_SVM_FIELDS,
1371
+ };
1372
+ // Which caip2 namespace implies which machine, for the cross-check that an
1373
+ // entry's `vm` matches the chain it addresses. Namespace rather than vmType,
1374
+ // for the same reason coverage is selected that way: HyperCore is vmType "evm"
1375
+ // under a `hypercore:` caip2 and is not a chain the indexer dials.
1376
+ const CHAIN_INDEXER_VM_BY_NAMESPACE = {
1377
+ eip155: 'evm',
1378
+ solana: 'svm',
1379
+ };
1257
1380
  /**
1258
- * Insists that every real EVM chain carries a complete, coherent chain-indexer
1259
- * entry, and that no entry addresses a chain that does not exist.
1381
+ * Insists that every chain the indexer dials carries a complete, coherent
1382
+ * chain-indexer entry, and that no entry addresses a chain that does not exist.
1260
1383
  *
1261
- * The indexer scans every dialable EVM chain, and its contract is that an
1262
- * unclassified chain is a hard failure — this check moves that failure from
1263
- * the indexer's boot to this build, where the person adding the chain sees it
1264
- * (RHI-6107). Selected by the `eip155:` caip2 namespace, NOT by vmType:
1384
+ * The indexer scans every dialable chain it has a machine for, and its contract
1385
+ * is that an unclassified chain is a hard failure — this check moves that
1386
+ * failure from the indexer's boot to this build, where the person adding the
1387
+ * chain sees it (RHI-6107). Selected by the caip2 NAMESPACE, NOT by vmType:
1265
1388
  * HyperCore is vmType "evm" with a `hypercore:` caip2 and must not require an
1266
1389
  * entry — the same discriminator the publish workflow's contracts guard uses,
1267
1390
  * for the same reason. Runs after `classifyChainCaip2`, which stamps the field.
1268
1391
  *
1269
- * The cross-field rules mirror chain-indexer's `ChainScanSettings::validate`,
1270
- * so a value set this check accepts is one the indexer will accept.
1392
+ * Two families today (`eip155:` → evm, `solana:` → svm), with disjoint knob
1393
+ * sets — an entry is judged against the one its chain implies, and its own `vm`
1394
+ * is cross-checked against that rather than trusted (RHI-7338).
1395
+ *
1396
+ * The EVM cross-field rules mirror chain-indexer's
1397
+ * `ChainScanSettings::validate`, so a value set this check accepts is one the
1398
+ * indexer will accept.
1271
1399
  *
1272
1400
  * A chain deliberately not indexed carries `{ indexed: false }` as its whole
1273
1401
  * entry — a decision the check accepts, unlike a missing entry.
1274
1402
  */
1275
1403
  function validateChainIndexerCoverage(chains, chainIndexer) {
1276
1404
  const issues = [];
1277
- const requiresEntry = (chain) => typeof chain.caip2 === 'string' &&
1278
- chain.caip2.startsWith('eip155:') &&
1279
- chain.virtual !== true;
1405
+ // Which machine's knobs a chain takes, or undefined for one the indexer does
1406
+ // not dial at all. Keyed on the caip2 NAMESPACE for the reason in the doc
1407
+ // comment: HyperCore is vmType "evm" under `hypercore:` and must stay
1408
+ // exempt.
1409
+ const machineOf = (chain) => typeof chain.caip2 === 'string'
1410
+ ? CHAIN_INDEXER_VM_BY_NAMESPACE[chain.caip2.split(':')[0]]
1411
+ : undefined;
1412
+ const requiresEntry = (chain) => machineOf(chain) !== undefined && chain.virtual !== true;
1280
1413
  for (const [chainId, chain] of Object.entries(chains)) {
1281
- if (!requiresEntry(chain))
1414
+ const machine = machineOf(chain);
1415
+ if (machine === undefined || chain.virtual === true)
1282
1416
  continue;
1283
1417
  if (chainIndexer[chainId] === undefined) {
1284
1418
  issues.push({
1285
1419
  severity: 'error',
1286
1420
  chainId,
1287
1421
  chainName: isNonEmptyString(chain.name) ? chain.name : '<unnamed>',
1288
- 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")',
1422
+ message: `${machine.toUpperCase()} chain has no chain-indexer entry — classify it in config/chain-indexer.jsonnet (see docs/add-chain.md, "Classify the chain for the indexer")`,
1289
1423
  });
1290
1424
  }
1291
1425
  }
@@ -1294,7 +1428,7 @@ function validateChainIndexerCoverage(chains, chainIndexer) {
1294
1428
  const chainName = chain && isNonEmptyString(chain.name) ? chain.name : '<not in registry>';
1295
1429
  const issue = (message) => issues.push({ severity: 'error', chainId, chainName, message });
1296
1430
  if (!chain || !requiresEntry(chain)) {
1297
- issue('chain-indexer entry addresses no real EVM chain — remove it from config/chain-indexer.jsonnet, or add the chain first');
1431
+ issue('chain-indexer entry addresses no chain the indexer dials — remove it from config/chain-indexer.jsonnet, or add the chain first');
1298
1432
  continue;
1299
1433
  }
1300
1434
  if (!isObject(entry)) {
@@ -1316,7 +1450,19 @@ function validateChainIndexerCoverage(chains, chainIndexer) {
1316
1450
  }
1317
1451
  continue;
1318
1452
  }
1319
- for (const [field, check] of Object.entries(CHAIN_INDEXER_FIELDS)) {
1453
+ // Which machine's knobs this entry is judged against. Cross-checked
1454
+ // against the chain rather than taken from the entry: an `svm` tag on an
1455
+ // eip155 chain would otherwise be validated as a coherent SVM entry and
1456
+ // then silently skipped by the indexer, which reads `vm` first and passes
1457
+ // over families it does not index — a chain that stops being scanned with
1458
+ // nothing failing anywhere (RHI-7338).
1459
+ const machine = machineOf(chain);
1460
+ if (entry.vm !== undefined && entry.vm !== machine) {
1461
+ issue(`chain-indexer \`vm\` = ${JSON.stringify(entry.vm)} but ${chain.caip2} is a ${machine} chain — the indexer skips entries for machines it does not index, so this one would never be scanned`);
1462
+ continue;
1463
+ }
1464
+ const fields = CHAIN_INDEXER_FIELDS_BY_VM[machine];
1465
+ for (const [field, check] of Object.entries(fields)) {
1320
1466
  if (entry[field] === undefined) {
1321
1467
  issue(`chain-indexer entry is missing \`${field}\` — every field is mandatory, a knob nobody set is a decision nobody made`);
1322
1468
  }
@@ -1325,10 +1471,17 @@ function validateChainIndexerCoverage(chains, chainIndexer) {
1325
1471
  }
1326
1472
  }
1327
1473
  for (const field of Object.keys(entry)) {
1328
- if (!(field in CHAIN_INDEXER_FIELDS)) {
1474
+ if (!(field in fields)) {
1329
1475
  issue(`chain-indexer entry carries unrecognized field \`${field}\` — the artifact's shape is exact, fix the name or drop it`);
1330
1476
  }
1331
1477
  }
1478
+ // The cross-field rules below are EVM classification coherence — reorg
1479
+ // window against finality source against watermark cadence. The SVM entry
1480
+ // has no reorg or finality knobs to be incoherent about: at `finalized`
1481
+ // commitment a signature is settled, so there is no second classification
1482
+ // to agree with.
1483
+ if (machine !== 'evm')
1484
+ continue;
1332
1485
  const instantClass = entry.finality === 'instant';
1333
1486
  const instantSource = entry.finality_source === 'instant';
1334
1487
  if (instantClass !== instantSource) {
@@ -66,6 +66,12 @@ interface Chain {
66
66
  /** Public, unauthenticated https JSON-RPC endpoint — what a browser needs to
67
67
  * build this chain with `defineChain`. Absent means display-only. */
68
68
  publicRpcUrl?: string;
69
+ /** How this chain is addressed when dialling it, within its architecture:
70
+ * the last segment of `{proxy}/{architecture}/{rpcId}`. Present only where
71
+ * the chain id is NOT what you dial — the cluster name (`mainnet-beta`,
72
+ * `devnet`) on Solana. Absent means the chain id is the id, so read it as
73
+ * `chain.rpcId ?? String(chainId)` rather than branching on vmType. */
74
+ rpcId?: string;
69
75
  /** True for virtual chains (e.g. HyperCore) that settle on another chain. */
70
76
  virtual?: boolean;
71
77
  /** Which directions of transfer the chain supports; absent means both.
@@ -1572,6 +1572,71 @@ const chains = {
1572
1572
  "Alchemy"
1573
1573
  ]
1574
1574
  },
1575
+ "5042": {
1576
+ "caip2": "eip155:5042",
1577
+ "explorer": {
1578
+ "addressPath": "/address/",
1579
+ "txPath": "/tx/",
1580
+ "url": "https://explorer.arc.io"
1581
+ },
1582
+ "iconSlug": "arc",
1583
+ "name": "Arc",
1584
+ "nativeToken": {
1585
+ "address": "0x0000000000000000000000000000000000000000",
1586
+ "decimals": 18,
1587
+ "symbol": "USDC"
1588
+ },
1589
+ "network": "mainnet",
1590
+ "providerIds": {
1591
+ "alchemy": null,
1592
+ "zerion": null
1593
+ },
1594
+ "publicRpcUrl": "https://rpc.mainnet.arc.io",
1595
+ "sessionCapable": false,
1596
+ "settlementConfig": {
1597
+ "cctp": {
1598
+ "domain": 26
1599
+ },
1600
+ "rhino": {
1601
+ "bridgeContract": "0x5e023c31e1d3dcd08a1b3e8c96f6ef8aa8fcacd1",
1602
+ "chainName": "ARC"
1603
+ }
1604
+ },
1605
+ "settlementLayers": [
1606
+ "ACROSS",
1607
+ "RELAY",
1608
+ "RHINO",
1609
+ "CCTP"
1610
+ ],
1611
+ "stack": "vanilla",
1612
+ "swapQuoters": [],
1613
+ "tokens": [
1614
+ {
1615
+ "address": "0x3600000000000000000000000000000000000000",
1616
+ "appFee": true,
1617
+ "approvalSlot": null,
1618
+ "balanceSlot": null,
1619
+ "decimals": 6,
1620
+ "gasRefund": true,
1621
+ "pegGroup": "USD",
1622
+ "settlementLayers": [
1623
+ "ACROSS"
1624
+ ],
1625
+ "symbol": "USDC"
1626
+ }
1627
+ ],
1628
+ "vmType": "evm",
1629
+ "wrappedNativeToken": {
1630
+ "address": "0x3600000000000000000000000000000000000000",
1631
+ "decimals": 6,
1632
+ "symbol": "USDC"
1633
+ },
1634
+ "providers": [
1635
+ "Alchemy",
1636
+ "arc-public-rpc",
1637
+ "DRPC"
1638
+ ]
1639
+ },
1575
1640
  "8453": {
1576
1641
  "caip2": "eip155:8453",
1577
1642
  "explorer": {
@@ -3245,6 +3310,7 @@ const chains = {
3245
3310
  "alchemy": null,
3246
3311
  "zerion": null
3247
3312
  },
3313
+ "rpcId": "mainnet-beta",
3248
3314
  "sessionCapable": false,
3249
3315
  "settlementConfig": {
3250
3316
  "cctp": {
@@ -3350,6 +3416,7 @@ const chains = {
3350
3416
  "alchemy": null,
3351
3417
  "zerion": null
3352
3418
  },
3419
+ "rpcId": "devnet",
3353
3420
  "sessionCapable": false,
3354
3421
  "settlementConfig": {
3355
3422
  "cctp": {
@@ -99,6 +99,10 @@ const contractAddresses = {
99
99
  "swapAdapter": "0x58BC7d9279A38a1537c2f74426dBDbD804AD3A18",
100
100
  "swapAdapterTokenSpender": "0xc5f6641b3ade357c5df77a1859d16528fe8b3e93"
101
101
  },
102
+ "5042": {
103
+ "acrossBridge": "0x9b4A302A548c7e313c2b74C461db7b84d3074A84",
104
+ "spokepool": "0x9b4A302A548c7e313c2b74C461db7b84d3074A84"
105
+ },
102
106
  "8453": {
103
107
  "ecoArbiter": "0x1BeBAfb3D05d84A5Bfd94800c88d1342f755d8AB",
104
108
  "acrossBridge": "0x09aea4b2242abC8bb4BB78D537A67a245A7bEC64",
@@ -99,6 +99,10 @@ const contractAddresses = {
99
99
  "swapAdapter": "0xaE448E141F230AC3c6F6dA981C0882356c9B91a6",
100
100
  "swapAdapterTokenSpender": "0x3115c923734471b9da1d5bfef161d9c9e9963859"
101
101
  },
102
+ "5042": {
103
+ "acrossBridge": "0x9b4A302A548c7e313c2b74C461db7b84d3074A84",
104
+ "spokepool": "0x9b4A302A548c7e313c2b74C461db7b84d3074A84"
105
+ },
102
106
  "8453": {
103
107
  "ecoArbiter": "0x2e7627CCfAe4eDb336eEb5a70f08415B0F1a2b8C",
104
108
  "acrossBridge": "0x09aea4b2242abC8bb4BB78D537A67a245A7bEC64",
@@ -2222,6 +2222,60 @@ declare const mainnetChains: ({
2222
2222
  }] | undefined;
2223
2223
  serializers?: import("viem").ChainSerializers<undefined, import("viem").TransactionSerializable> | undefined;
2224
2224
  verifyHash?: ((client: import("viem").Client, parameters: import("viem").VerifyHashActionParameters) => Promise<import("viem").VerifyHashActionReturnType>) | undefined;
2225
+ } | {
2226
+ blockExplorers?: {
2227
+ [key: string]: {
2228
+ name: string;
2229
+ url: string;
2230
+ apiUrl?: string | undefined;
2231
+ };
2232
+ default: {
2233
+ name: string;
2234
+ url: string;
2235
+ apiUrl?: string | undefined;
2236
+ };
2237
+ } | undefined | undefined;
2238
+ blockTime?: number | undefined | undefined;
2239
+ contracts?: {
2240
+ [x: string]: import("viem").ChainContract | {
2241
+ [sourceId: number]: import("viem").ChainContract | undefined;
2242
+ } | undefined;
2243
+ ensRegistry?: import("viem").ChainContract | undefined;
2244
+ ensUniversalResolver?: import("viem").ChainContract | undefined;
2245
+ multicall3?: import("viem").ChainContract | undefined;
2246
+ erc6492Verifier?: import("viem").ChainContract | undefined;
2247
+ } | undefined;
2248
+ ensTlds?: readonly string[] | undefined;
2249
+ id: 5042;
2250
+ name: "Arc";
2251
+ nativeCurrency: {
2252
+ readonly name: "USDC";
2253
+ readonly symbol: "USDC";
2254
+ readonly decimals: 18;
2255
+ };
2256
+ experimental_preconfirmationTime?: number | undefined | undefined;
2257
+ rpcUrls: {
2258
+ readonly default: {
2259
+ readonly http: readonly [];
2260
+ };
2261
+ };
2262
+ sourceId?: number | undefined | undefined;
2263
+ testnet?: boolean | undefined | undefined;
2264
+ custom?: Record<string, unknown> | undefined;
2265
+ extendSchema?: Record<string, unknown> | undefined;
2266
+ fees?: import("viem").ChainFees<undefined> | undefined;
2267
+ formatters?: undefined;
2268
+ prepareTransactionRequest?: ((args: import("viem").PrepareTransactionRequestParameters, options: {
2269
+ client: import("viem").Client;
2270
+ phase: "beforeFillTransaction" | "beforeFillParameters" | "afterFillParameters";
2271
+ }) => Promise<import("viem").PrepareTransactionRequestParameters>) | [fn: ((args: import("viem").PrepareTransactionRequestParameters, options: {
2272
+ client: import("viem").Client;
2273
+ phase: "beforeFillTransaction" | "beforeFillParameters" | "afterFillParameters";
2274
+ }) => Promise<import("viem").PrepareTransactionRequestParameters>) | undefined, options: {
2275
+ runAt: readonly ("beforeFillTransaction" | "beforeFillParameters" | "afterFillParameters")[];
2276
+ }] | undefined;
2277
+ serializers?: import("viem").ChainSerializers<undefined, import("viem").TransactionSerializable> | undefined;
2278
+ verifyHash?: ((client: import("viem").Client, parameters: import("viem").VerifyHashActionParameters) => Promise<import("viem").VerifyHashActionReturnType>) | undefined;
2225
2279
  })[];
2226
2280
  declare const testnetChains: ({
2227
2281
  blockExplorers: {
@@ -5242,6 +5296,60 @@ declare const chains: ({
5242
5296
  }] | undefined;
5243
5297
  serializers?: import("viem").ChainSerializers<undefined, import("viem").TransactionSerializable> | undefined;
5244
5298
  verifyHash?: ((client: import("viem").Client, parameters: import("viem").VerifyHashActionParameters) => Promise<import("viem").VerifyHashActionReturnType>) | undefined;
5299
+ } | {
5300
+ blockExplorers?: {
5301
+ [key: string]: {
5302
+ name: string;
5303
+ url: string;
5304
+ apiUrl?: string | undefined;
5305
+ };
5306
+ default: {
5307
+ name: string;
5308
+ url: string;
5309
+ apiUrl?: string | undefined;
5310
+ };
5311
+ } | undefined | undefined;
5312
+ blockTime?: number | undefined | undefined;
5313
+ contracts?: {
5314
+ [x: string]: import("viem").ChainContract | {
5315
+ [sourceId: number]: import("viem").ChainContract | undefined;
5316
+ } | undefined;
5317
+ ensRegistry?: import("viem").ChainContract | undefined;
5318
+ ensUniversalResolver?: import("viem").ChainContract | undefined;
5319
+ multicall3?: import("viem").ChainContract | undefined;
5320
+ erc6492Verifier?: import("viem").ChainContract | undefined;
5321
+ } | undefined;
5322
+ ensTlds?: readonly string[] | undefined;
5323
+ id: 5042;
5324
+ name: "Arc";
5325
+ nativeCurrency: {
5326
+ readonly name: "USDC";
5327
+ readonly symbol: "USDC";
5328
+ readonly decimals: 18;
5329
+ };
5330
+ experimental_preconfirmationTime?: number | undefined | undefined;
5331
+ rpcUrls: {
5332
+ readonly default: {
5333
+ readonly http: readonly [];
5334
+ };
5335
+ };
5336
+ sourceId?: number | undefined | undefined;
5337
+ testnet?: boolean | undefined | undefined;
5338
+ custom?: Record<string, unknown> | undefined;
5339
+ extendSchema?: Record<string, unknown> | undefined;
5340
+ fees?: import("viem").ChainFees<undefined> | undefined;
5341
+ formatters?: undefined;
5342
+ prepareTransactionRequest?: ((args: import("viem").PrepareTransactionRequestParameters, options: {
5343
+ client: import("viem").Client;
5344
+ phase: "beforeFillTransaction" | "beforeFillParameters" | "afterFillParameters";
5345
+ }) => Promise<import("viem").PrepareTransactionRequestParameters>) | [fn: ((args: import("viem").PrepareTransactionRequestParameters, options: {
5346
+ client: import("viem").Client;
5347
+ phase: "beforeFillTransaction" | "beforeFillParameters" | "afterFillParameters";
5348
+ }) => Promise<import("viem").PrepareTransactionRequestParameters>) | undefined, options: {
5349
+ runAt: readonly ("beforeFillTransaction" | "beforeFillParameters" | "afterFillParameters")[];
5350
+ }] | undefined;
5351
+ serializers?: import("viem").ChainSerializers<undefined, import("viem").TransactionSerializable> | undefined;
5352
+ verifyHash?: ((client: import("viem").Client, parameters: import("viem").VerifyHashActionParameters) => Promise<import("viem").VerifyHashActionReturnType>) | undefined;
5245
5353
  } | {
5246
5354
  blockExplorers: {
5247
5355
  readonly default: {
@@ -6060,7 +6168,8 @@ declare enum MainnetNetwork {
6060
6168
  ROBINHOOD,
6061
6169
  AVALANCHE,
6062
6170
  INK,
6063
- XLAYER
6171
+ XLAYER,
6172
+ ARC
6064
6173
  }
6065
6174
  declare enum TestnetNetwork {
6066
6175
  ETHEREUM_SEPOLIA,
@@ -3,7 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.OPStackChains = exports.TestnetNetwork = exports.MainnetNetwork = exports.chains = exports.testnetChains = exports.mainnetChains = void 0;
4
4
  // Auto-generated by scripts/generate.ts. Do not edit manually.
5
5
  const chains_1 = require("viem/chains");
6
- const mainnetChains = [chains_1.mainnet, chains_1.optimism, chains_1.base, chains_1.arbitrum, chains_1.polygon, chains_1.soneium, chains_1.sonic, chains_1.plasma, chains_1.gnosis, chains_1.bsc, chains_1.hyperEvm, chains_1.monad, chains_1.unichain, chains_1.katana, chains_1.robinhood, chains_1.avalanche, chains_1.ink, chains_1.xLayer];
6
+ const mainnetChains = [chains_1.mainnet, chains_1.optimism, chains_1.base, chains_1.arbitrum, chains_1.polygon, chains_1.soneium, chains_1.sonic, chains_1.plasma, chains_1.gnosis, chains_1.bsc, chains_1.hyperEvm, chains_1.monad, chains_1.unichain, chains_1.katana, chains_1.robinhood, chains_1.avalanche, chains_1.ink, chains_1.xLayer, chains_1.arc];
7
7
  exports.mainnetChains = mainnetChains;
8
8
  const testnetChains = [chains_1.sepolia, chains_1.optimismSepolia, chains_1.baseSepolia, chains_1.arbitrumSepolia, chains_1.plasmaTestnet];
9
9
  exports.testnetChains = testnetChains;
@@ -34,6 +34,7 @@ var MainnetNetwork;
34
34
  MainnetNetwork[MainnetNetwork["AVALANCHE"] = chains_1.avalanche.id] = "AVALANCHE";
35
35
  MainnetNetwork[MainnetNetwork["INK"] = chains_1.ink.id] = "INK";
36
36
  MainnetNetwork[MainnetNetwork["XLAYER"] = chains_1.xLayer.id] = "XLAYER";
37
+ MainnetNetwork[MainnetNetwork["ARC"] = chains_1.arc.id] = "ARC";
37
38
  })(MainnetNetwork || (exports.MainnetNetwork = MainnetNetwork = {}));
38
39
  var TestnetNetwork;
39
40
  (function (TestnetNetwork) {
@@ -1,2 +1,2 @@
1
- declare const PACKAGE_VERSION = "2.20.1";
1
+ declare const PACKAGE_VERSION = "2.21.1";
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.20.1";
5
+ const PACKAGE_VERSION = "2.21.1";
6
6
  exports.PACKAGE_VERSION = PACKAGE_VERSION;
@@ -349,6 +349,26 @@ interface ChainEntry {
349
349
  * `validateChainIconSlugs`.
350
350
  */
351
351
  iconSlug?: string;
352
+ /**
353
+ * The identifier this chain is addressed by when you dial it, within its
354
+ * architecture — the last path segment of `{proxy}/{architecture}/{rpcId}`.
355
+ *
356
+ * For an EVM chain it is the chain id as a string. For Solana it is the
357
+ * cluster name (`mainnet-beta`, `devnet`), which is what every Solana RPC
358
+ * endpoint and the CLI call it, and what eRPC keys its network by. Not a
359
+ * proxy-specific spelling: a vendor endpoint uses the same word.
360
+ *
361
+ * Read this instead of branching on `vmType` and rebuilding the segment per
362
+ * family: every consumer that reached Solana previously kept its own caip2 →
363
+ * cluster table, and those tables drifted from each other.
364
+ *
365
+ * Present only where the chain id is NOT what you dial — the two Solana
366
+ * entries today, and nowhere else. **Absent means the chain id is the id**,
367
+ * so a consumer reads `chain.rpcId ?? String(chainId)`. Storing the id again
368
+ * on every EVM entry would be a second copy of the key it sits under, which
369
+ * is the kind of copy that drifts.
370
+ */
371
+ rpcId?: string;
352
372
  /**
353
373
  * A public, unauthenticated JSON-RPC endpoint over HTTPS, when one is known.
354
374
  *
@@ -463,7 +483,7 @@ type ContractAddresses = Record<string, Record<string, Address>>;
463
483
  interface ChainRegistry {
464
484
  [chainId: string]: ChainEntry;
465
485
  }
466
- type ProviderName = 'DRPC' | 'Alchemy' | 'gnosis-tenderly' | 'plasma-tenderly' | 'local-rpc' | 'quicknode-hypervm' | 'soneium-public-rpc';
486
+ type ProviderName = 'DRPC' | 'Alchemy' | 'gnosis-tenderly' | 'plasma-tenderly' | 'local-rpc' | 'quicknode-hypervm' | 'soneium-public-rpc' | 'arc-public-rpc';
467
487
  interface ChainItem {
468
488
  providers: ProviderName[];
469
489
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rhinestone/shared-configs",
3
- "version": "2.20.1",
3
+ "version": "2.21.1",
4
4
  "description": "Reusable configuration files for Rhinestone services",
5
5
  "author": {
6
6
  "name": "Rhinestone",