@rhinestone/shared-configs 1.11.2 → 1.12.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/chains.json +47 -0
- package/dist/scripts/__tests__/validation.test.js +145 -0
- package/dist/scripts/generate.js +19 -1
- package/dist/scripts/validation.d.ts +51 -0
- package/dist/scripts/validation.js +194 -0
- package/dist/src/chains.d.ts +8 -0
- package/dist/src/chains.js +47 -0
- package/dist/src/generated/packageVersion.d.ts +1 -1
- package/dist/src/generated/packageVersion.js +1 -1
- package/dist/src/types.d.ts +40 -0
- package/package.json +1 -1
package/dist/configs/chains.json
CHANGED
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
"addressPath": "/address/",
|
|
7
7
|
"txPath": "/tx/"
|
|
8
8
|
},
|
|
9
|
+
"iconSlug": "mainnet",
|
|
9
10
|
"name": "Ethereum",
|
|
10
11
|
"nativeToken": {
|
|
11
12
|
"address": "0x0000000000000000000000000000000000000000",
|
|
@@ -13,6 +14,7 @@
|
|
|
13
14
|
"symbol": "ETH"
|
|
14
15
|
},
|
|
15
16
|
"network": "mainnet",
|
|
17
|
+
"publicRpcUrl": "https://eth.merkle.io",
|
|
16
18
|
"settlementLayers": [
|
|
17
19
|
"ACROSS",
|
|
18
20
|
"ECO",
|
|
@@ -96,6 +98,7 @@
|
|
|
96
98
|
"addressPath": "/address/",
|
|
97
99
|
"txPath": "/tx/"
|
|
98
100
|
},
|
|
101
|
+
"iconSlug": "optimism",
|
|
99
102
|
"name": "OP Mainnet",
|
|
100
103
|
"nativeToken": {
|
|
101
104
|
"address": "0x0000000000000000000000000000000000000000",
|
|
@@ -103,6 +106,7 @@
|
|
|
103
106
|
"symbol": "ETH"
|
|
104
107
|
},
|
|
105
108
|
"network": "mainnet",
|
|
109
|
+
"publicRpcUrl": "https://mainnet.optimism.io",
|
|
106
110
|
"settlementLayers": [
|
|
107
111
|
"ACROSS",
|
|
108
112
|
"ECO",
|
|
@@ -197,6 +201,7 @@
|
|
|
197
201
|
"addressPath": "/address/",
|
|
198
202
|
"txPath": "/tx/"
|
|
199
203
|
},
|
|
204
|
+
"iconSlug": "bsc",
|
|
200
205
|
"name": "BNB Smart Chain",
|
|
201
206
|
"nativeToken": {
|
|
202
207
|
"address": "0x0000000000000000000000000000000000000000",
|
|
@@ -204,6 +209,7 @@
|
|
|
204
209
|
"symbol": "BNB"
|
|
205
210
|
},
|
|
206
211
|
"network": "mainnet",
|
|
212
|
+
"publicRpcUrl": "https://56.rpc.thirdweb.com",
|
|
207
213
|
"settlementLayers": [
|
|
208
214
|
"ACROSS",
|
|
209
215
|
"ECO",
|
|
@@ -271,6 +277,7 @@
|
|
|
271
277
|
"addressPath": "/address/",
|
|
272
278
|
"txPath": "/tx/"
|
|
273
279
|
},
|
|
280
|
+
"iconSlug": "gnosis",
|
|
274
281
|
"name": "Gnosis",
|
|
275
282
|
"nativeToken": {
|
|
276
283
|
"address": "0x0000000000000000000000000000000000000000",
|
|
@@ -278,6 +285,7 @@
|
|
|
278
285
|
"symbol": "XDAI"
|
|
279
286
|
},
|
|
280
287
|
"network": "mainnet",
|
|
288
|
+
"publicRpcUrl": "https://rpc.gnosischain.com",
|
|
281
289
|
"settlementLayers": [
|
|
282
290
|
"RELAY",
|
|
283
291
|
"NEAR",
|
|
@@ -312,6 +320,7 @@
|
|
|
312
320
|
"addressPath": "/address/",
|
|
313
321
|
"txPath": "/tx/"
|
|
314
322
|
},
|
|
323
|
+
"iconSlug": "unichain",
|
|
315
324
|
"name": "Unichain",
|
|
316
325
|
"nativeToken": {
|
|
317
326
|
"address": "0x0000000000000000000000000000000000000000",
|
|
@@ -319,6 +328,7 @@
|
|
|
319
328
|
"symbol": "ETH"
|
|
320
329
|
},
|
|
321
330
|
"network": "mainnet",
|
|
331
|
+
"publicRpcUrl": "https://mainnet.unichain.org",
|
|
322
332
|
"settlementLayers": [
|
|
323
333
|
"ACROSS",
|
|
324
334
|
"ECO",
|
|
@@ -401,6 +411,7 @@
|
|
|
401
411
|
"addressPath": "/address/",
|
|
402
412
|
"txPath": "/tx/"
|
|
403
413
|
},
|
|
414
|
+
"iconSlug": "polygon",
|
|
404
415
|
"name": "Polygon",
|
|
405
416
|
"nativeToken": {
|
|
406
417
|
"address": "0x0000000000000000000000000000000000000000",
|
|
@@ -408,6 +419,7 @@
|
|
|
408
419
|
"symbol": "POL"
|
|
409
420
|
},
|
|
410
421
|
"network": "mainnet",
|
|
422
|
+
"publicRpcUrl": "https://polygon.drpc.org",
|
|
411
423
|
"settlementLayers": [
|
|
412
424
|
"ACROSS",
|
|
413
425
|
"ECO",
|
|
@@ -478,6 +490,7 @@
|
|
|
478
490
|
"addressPath": "/address/",
|
|
479
491
|
"txPath": "/tx/"
|
|
480
492
|
},
|
|
493
|
+
"iconSlug": "monad",
|
|
481
494
|
"name": "Monad",
|
|
482
495
|
"nativeToken": {
|
|
483
496
|
"address": "0x0000000000000000000000000000000000000000",
|
|
@@ -485,6 +498,7 @@
|
|
|
485
498
|
"symbol": "MON"
|
|
486
499
|
},
|
|
487
500
|
"network": "mainnet",
|
|
501
|
+
"publicRpcUrl": "https://rpc.monad.xyz",
|
|
488
502
|
"settlementLayers": [
|
|
489
503
|
"ACROSS",
|
|
490
504
|
"RELAY",
|
|
@@ -553,6 +567,7 @@
|
|
|
553
567
|
"addressPath": "/address/",
|
|
554
568
|
"txPath": "/tx/"
|
|
555
569
|
},
|
|
570
|
+
"iconSlug": "sonic",
|
|
556
571
|
"name": "Sonic",
|
|
557
572
|
"nativeToken": {
|
|
558
573
|
"address": "0x0000000000000000000000000000000000000000",
|
|
@@ -560,6 +575,7 @@
|
|
|
560
575
|
"symbol": "S"
|
|
561
576
|
},
|
|
562
577
|
"network": "mainnet",
|
|
578
|
+
"publicRpcUrl": "https://rpc.soniclabs.com",
|
|
563
579
|
"settlementLayers": [
|
|
564
580
|
"ECO",
|
|
565
581
|
"RELAY",
|
|
@@ -600,6 +616,7 @@
|
|
|
600
616
|
"addressPath": "/address/",
|
|
601
617
|
"txPath": "/tx/"
|
|
602
618
|
},
|
|
619
|
+
"iconSlug": "hyperevm",
|
|
603
620
|
"name": "HyperEVM",
|
|
604
621
|
"nativeToken": {
|
|
605
622
|
"address": "0x0000000000000000000000000000000000000000",
|
|
@@ -607,6 +624,7 @@
|
|
|
607
624
|
"symbol": "HYPE"
|
|
608
625
|
},
|
|
609
626
|
"network": "mainnet",
|
|
627
|
+
"publicRpcUrl": "https://rpc.hyperliquid.xyz/evm",
|
|
610
628
|
"settlementLayers": [
|
|
611
629
|
"ACROSS",
|
|
612
630
|
"ECO",
|
|
@@ -658,6 +676,7 @@
|
|
|
658
676
|
"txPath": "/explorer/tx/",
|
|
659
677
|
"url": "https://app.hyperliquid.xyz"
|
|
660
678
|
},
|
|
679
|
+
"iconSlug": "hyperevm",
|
|
661
680
|
"name": "HyperCore",
|
|
662
681
|
"nativeToken": {
|
|
663
682
|
"address": "0x0000000000000000000000000000000000000000",
|
|
@@ -697,6 +716,7 @@
|
|
|
697
716
|
"addressPath": "/address/",
|
|
698
717
|
"txPath": "/tx/"
|
|
699
718
|
},
|
|
719
|
+
"iconSlug": "soneium",
|
|
700
720
|
"name": "Soneium Mainnet",
|
|
701
721
|
"nativeToken": {
|
|
702
722
|
"address": "0x0000000000000000000000000000000000000000",
|
|
@@ -704,6 +724,7 @@
|
|
|
704
724
|
"symbol": "ETH"
|
|
705
725
|
},
|
|
706
726
|
"network": "mainnet",
|
|
727
|
+
"publicRpcUrl": "https://rpc.soneium.org",
|
|
707
728
|
"settlementLayers": [
|
|
708
729
|
"ACROSS",
|
|
709
730
|
"RELAY"
|
|
@@ -771,6 +792,7 @@
|
|
|
771
792
|
"addressPath": "/address/",
|
|
772
793
|
"txPath": "/tx/"
|
|
773
794
|
},
|
|
795
|
+
"iconSlug": "robinhood",
|
|
774
796
|
"name": "Robinhood Chain",
|
|
775
797
|
"nativeToken": {
|
|
776
798
|
"address": "0x0000000000000000000000000000000000000000",
|
|
@@ -778,6 +800,7 @@
|
|
|
778
800
|
"symbol": "ETH"
|
|
779
801
|
},
|
|
780
802
|
"network": "mainnet",
|
|
803
|
+
"publicRpcUrl": "https://rpc.mainnet.chain.robinhood.com",
|
|
781
804
|
"settlementLayers": [
|
|
782
805
|
"ACROSS",
|
|
783
806
|
"RELAY"
|
|
@@ -834,6 +857,7 @@
|
|
|
834
857
|
"addressPath": "/address/",
|
|
835
858
|
"txPath": "/tx/"
|
|
836
859
|
},
|
|
860
|
+
"iconSlug": "base",
|
|
837
861
|
"name": "Base",
|
|
838
862
|
"nativeToken": {
|
|
839
863
|
"address": "0x0000000000000000000000000000000000000000",
|
|
@@ -841,6 +865,7 @@
|
|
|
841
865
|
"symbol": "ETH"
|
|
842
866
|
},
|
|
843
867
|
"network": "mainnet",
|
|
868
|
+
"publicRpcUrl": "https://mainnet.base.org",
|
|
844
869
|
"settlementLayers": [
|
|
845
870
|
"ACROSS",
|
|
846
871
|
"ECO",
|
|
@@ -909,6 +934,7 @@
|
|
|
909
934
|
"addressPath": "/address/",
|
|
910
935
|
"txPath": "/tx/"
|
|
911
936
|
},
|
|
937
|
+
"iconSlug": "plasma",
|
|
912
938
|
"name": "Plasma",
|
|
913
939
|
"nativeToken": {
|
|
914
940
|
"address": "0x0000000000000000000000000000000000000000",
|
|
@@ -916,6 +942,7 @@
|
|
|
916
942
|
"symbol": "XPL"
|
|
917
943
|
},
|
|
918
944
|
"network": "mainnet",
|
|
945
|
+
"publicRpcUrl": "https://rpc.plasma.to",
|
|
919
946
|
"settlementLayers": [
|
|
920
947
|
"ACROSS",
|
|
921
948
|
"ECO",
|
|
@@ -958,6 +985,7 @@
|
|
|
958
985
|
"addressPath": "/address/",
|
|
959
986
|
"txPath": "/tx/"
|
|
960
987
|
},
|
|
988
|
+
"iconSlug": "plasma",
|
|
961
989
|
"name": "Plasma Testnet",
|
|
962
990
|
"nativeToken": {
|
|
963
991
|
"address": "0x0000000000000000000000000000000000000000",
|
|
@@ -965,6 +993,7 @@
|
|
|
965
993
|
"symbol": "XPL"
|
|
966
994
|
},
|
|
967
995
|
"network": "testnet",
|
|
996
|
+
"publicRpcUrl": "https://testnet-rpc.plasma.to",
|
|
968
997
|
"settlementLayers": [
|
|
969
998
|
"ECO"
|
|
970
999
|
],
|
|
@@ -1009,6 +1038,7 @@
|
|
|
1009
1038
|
"addressPath": "/address/",
|
|
1010
1039
|
"txPath": "/tx/"
|
|
1011
1040
|
},
|
|
1041
|
+
"iconSlug": "arbitrum",
|
|
1012
1042
|
"name": "Arbitrum One",
|
|
1013
1043
|
"nativeToken": {
|
|
1014
1044
|
"address": "0x0000000000000000000000000000000000000000",
|
|
@@ -1016,6 +1046,7 @@
|
|
|
1016
1046
|
"symbol": "ETH"
|
|
1017
1047
|
},
|
|
1018
1048
|
"network": "mainnet",
|
|
1049
|
+
"publicRpcUrl": "https://arb1.arbitrum.io/rpc",
|
|
1019
1050
|
"settlementLayers": [
|
|
1020
1051
|
"ACROSS",
|
|
1021
1052
|
"ECO",
|
|
@@ -1099,6 +1130,7 @@
|
|
|
1099
1130
|
"addressPath": "/address/",
|
|
1100
1131
|
"txPath": "/tx/"
|
|
1101
1132
|
},
|
|
1133
|
+
"iconSlug": "avalanche",
|
|
1102
1134
|
"name": "Avalanche",
|
|
1103
1135
|
"nativeToken": {
|
|
1104
1136
|
"address": "0x0000000000000000000000000000000000000000",
|
|
@@ -1106,6 +1138,7 @@
|
|
|
1106
1138
|
"symbol": "AVAX"
|
|
1107
1139
|
},
|
|
1108
1140
|
"network": "mainnet",
|
|
1141
|
+
"publicRpcUrl": "https://api.avax.network/ext/bc/C/rpc",
|
|
1109
1142
|
"settlementLayers": [
|
|
1110
1143
|
"ACROSS",
|
|
1111
1144
|
"RELAY",
|
|
@@ -1182,6 +1215,7 @@
|
|
|
1182
1215
|
"addressPath": "/address/",
|
|
1183
1216
|
"txPath": "/tx/"
|
|
1184
1217
|
},
|
|
1218
|
+
"iconSlug": "base",
|
|
1185
1219
|
"name": "Base Sepolia",
|
|
1186
1220
|
"nativeToken": {
|
|
1187
1221
|
"address": "0x0000000000000000000000000000000000000000",
|
|
@@ -1189,6 +1223,7 @@
|
|
|
1189
1223
|
"symbol": "ETH"
|
|
1190
1224
|
},
|
|
1191
1225
|
"network": "testnet",
|
|
1226
|
+
"publicRpcUrl": "https://sepolia.base.org",
|
|
1192
1227
|
"settlementLayers": [
|
|
1193
1228
|
"ACROSS",
|
|
1194
1229
|
"ECO",
|
|
@@ -1263,6 +1298,7 @@
|
|
|
1263
1298
|
"addressPath": "/address/",
|
|
1264
1299
|
"txPath": "/tx/"
|
|
1265
1300
|
},
|
|
1301
|
+
"iconSlug": "arbitrum",
|
|
1266
1302
|
"name": "Arbitrum Sepolia",
|
|
1267
1303
|
"nativeToken": {
|
|
1268
1304
|
"address": "0x0000000000000000000000000000000000000000",
|
|
@@ -1270,6 +1306,7 @@
|
|
|
1270
1306
|
"symbol": "ETH"
|
|
1271
1307
|
},
|
|
1272
1308
|
"network": "testnet",
|
|
1309
|
+
"publicRpcUrl": "https://sepolia-rollup.arbitrum.io/rpc",
|
|
1273
1310
|
"settlementLayers": [
|
|
1274
1311
|
"ACROSS",
|
|
1275
1312
|
"ECO",
|
|
@@ -1330,6 +1367,7 @@
|
|
|
1330
1367
|
"addressPath": "/address/",
|
|
1331
1368
|
"txPath": "/tx/"
|
|
1332
1369
|
},
|
|
1370
|
+
"iconSlug": "katana",
|
|
1333
1371
|
"name": "Katana",
|
|
1334
1372
|
"nativeToken": {
|
|
1335
1373
|
"address": "0x0000000000000000000000000000000000000000",
|
|
@@ -1337,6 +1375,7 @@
|
|
|
1337
1375
|
"symbol": "ETH"
|
|
1338
1376
|
},
|
|
1339
1377
|
"network": "mainnet",
|
|
1378
|
+
"publicRpcUrl": "https://rpc.katana.network",
|
|
1340
1379
|
"settlementLayers": [
|
|
1341
1380
|
"RELAY",
|
|
1342
1381
|
"RHINO"
|
|
@@ -1394,6 +1433,7 @@
|
|
|
1394
1433
|
"txPath": "/explorer/tx/",
|
|
1395
1434
|
"url": "https://app.hyperliquid.xyz"
|
|
1396
1435
|
},
|
|
1436
|
+
"iconSlug": "hyperevm",
|
|
1397
1437
|
"name": "HyperCore Spot",
|
|
1398
1438
|
"nativeToken": {
|
|
1399
1439
|
"address": "0x0000000000000000000000000000000000000000",
|
|
@@ -1433,6 +1473,7 @@
|
|
|
1433
1473
|
"txPath": "/explorer/tx/",
|
|
1434
1474
|
"url": "https://app.hyperliquid.xyz"
|
|
1435
1475
|
},
|
|
1476
|
+
"iconSlug": "hyperevm",
|
|
1436
1477
|
"name": "HyperCore Perp",
|
|
1437
1478
|
"nativeToken": {
|
|
1438
1479
|
"address": "0x0000000000000000000000000000000000000000",
|
|
@@ -1472,6 +1513,7 @@
|
|
|
1472
1513
|
"addressPath": "/address/",
|
|
1473
1514
|
"txPath": "/tx/"
|
|
1474
1515
|
},
|
|
1516
|
+
"iconSlug": "mainnet",
|
|
1475
1517
|
"name": "Sepolia",
|
|
1476
1518
|
"nativeToken": {
|
|
1477
1519
|
"address": "0x0000000000000000000000000000000000000000",
|
|
@@ -1479,6 +1521,7 @@
|
|
|
1479
1521
|
"symbol": "ETH"
|
|
1480
1522
|
},
|
|
1481
1523
|
"network": "testnet",
|
|
1524
|
+
"publicRpcUrl": "https://11155111.rpc.thirdweb.com",
|
|
1482
1525
|
"settlementLayers": [
|
|
1483
1526
|
"ACROSS",
|
|
1484
1527
|
"ECO",
|
|
@@ -1549,6 +1592,7 @@
|
|
|
1549
1592
|
"addressPath": "/address/",
|
|
1550
1593
|
"txPath": "/tx/"
|
|
1551
1594
|
},
|
|
1595
|
+
"iconSlug": "optimism",
|
|
1552
1596
|
"name": "OP Sepolia",
|
|
1553
1597
|
"nativeToken": {
|
|
1554
1598
|
"address": "0x0000000000000000000000000000000000000000",
|
|
@@ -1556,6 +1600,7 @@
|
|
|
1556
1600
|
"symbol": "ETH"
|
|
1557
1601
|
},
|
|
1558
1602
|
"network": "testnet",
|
|
1603
|
+
"publicRpcUrl": "https://sepolia.optimism.io",
|
|
1559
1604
|
"settlementLayers": [
|
|
1560
1605
|
"ACROSS",
|
|
1561
1606
|
"ECO",
|
|
@@ -1616,6 +1661,7 @@
|
|
|
1616
1661
|
"txPath": "/#/transaction/",
|
|
1617
1662
|
"url": "https://tronscan.org"
|
|
1618
1663
|
},
|
|
1664
|
+
"iconSlug": "tron",
|
|
1619
1665
|
"name": "Tron",
|
|
1620
1666
|
"nativeToken": {
|
|
1621
1667
|
"address": "0x0000000000000000000000000000000000000000",
|
|
@@ -1662,6 +1708,7 @@
|
|
|
1662
1708
|
"txPath": "/tx/",
|
|
1663
1709
|
"url": "https://solscan.io"
|
|
1664
1710
|
},
|
|
1711
|
+
"iconSlug": "solana",
|
|
1665
1712
|
"name": "Solana",
|
|
1666
1713
|
"nativeToken": {
|
|
1667
1714
|
"address": "11111111111111111111111111111111",
|
|
@@ -73,6 +73,12 @@ function errorsFor(chain) {
|
|
|
73
73
|
["an explorer url with a trailing slash", {
|
|
74
74
|
explorer: { url: "https://etherscan.io/", addressPath: "/address/", txPath: "/tx/" },
|
|
75
75
|
}],
|
|
76
|
+
// A stem, not a path or a URL — the whole point of publishing a slug is that
|
|
77
|
+
// the artifact says nothing about where the file is served from.
|
|
78
|
+
["an iconSlug that is a full URL", { iconSlug: "https://s3.rhinestone.dev/chains/base.svg" }],
|
|
79
|
+
["an iconSlug carrying its extension", { iconSlug: "base.svg" }],
|
|
80
|
+
["an iconSlug carrying a directory", { iconSlug: "chains/base" }],
|
|
81
|
+
["an iconSlug that is not a string", { iconSlug: 42 }],
|
|
76
82
|
])("rejects %s", (_label, overrides) => {
|
|
77
83
|
(0, bun_test_1.expect)(errorsFor(validChain(overrides)).length).toBeGreaterThan(0);
|
|
78
84
|
});
|
|
@@ -387,6 +393,145 @@ function errorsFor(chain) {
|
|
|
387
393
|
}
|
|
388
394
|
});
|
|
389
395
|
});
|
|
396
|
+
(0, bun_test_1.describe)("classifyChainPublicRpcUrls", () => {
|
|
397
|
+
const nets = (mainnets, testnets = []) => ({ mainnets, testnets });
|
|
398
|
+
(0, bun_test_1.it)("derives the endpoint from viem's own chain definition", () => {
|
|
399
|
+
const { chains, issues } = (0, validation_1.classifyChainPublicRpcUrls)({ "8453": validChain({ name: "Base" }) }, nets([{ id: 8453, viemChain: "base" }]));
|
|
400
|
+
(0, bun_test_1.expect)(chains["8453"].publicRpcUrl).toBe("https://mainnet.base.org");
|
|
401
|
+
(0, bun_test_1.expect)(issues).toEqual([]);
|
|
402
|
+
});
|
|
403
|
+
// The regression this exists for, and the reason it is worse here than for
|
|
404
|
+
// explorers: viem's id 999 is claimed by hyperEvm, hyperliquid, wanchainTestnet
|
|
405
|
+
// and zoraTestnet. An id lookup would publish a live endpoint for the wrong
|
|
406
|
+
// chain, which fails at connect time rather than at render time.
|
|
407
|
+
(0, bun_test_1.it)("resolves by declared viemChain name, never by chain id", () => {
|
|
408
|
+
const { chains } = (0, validation_1.classifyChainPublicRpcUrls)({ "999": validChain({ name: "HyperEVM", caip2: "eip155:999" }) }, nets([{ id: 999, viemChain: "hyperEvm" }]));
|
|
409
|
+
(0, bun_test_1.expect)(chains["999"].publicRpcUrl).toBe("https://rpc.hyperliquid.xyz/evm");
|
|
410
|
+
});
|
|
411
|
+
(0, bun_test_1.it)("lets an authored endpoint win, for a chain viem does not ship", () => {
|
|
412
|
+
const { chains, issues } = (0, validation_1.classifyChainPublicRpcUrls)({
|
|
413
|
+
"424242": validChain({
|
|
414
|
+
name: "Newchain",
|
|
415
|
+
publicRpcUrl: "https://rpc.newchain.example",
|
|
416
|
+
}),
|
|
417
|
+
}, nets([{ id: 424242 }]));
|
|
418
|
+
(0, bun_test_1.expect)(chains["424242"].publicRpcUrl).toBe("https://rpc.newchain.example");
|
|
419
|
+
(0, bun_test_1.expect)(issues).toEqual([]);
|
|
420
|
+
});
|
|
421
|
+
// A browser on an https page cannot call an http endpoint at all, so an
|
|
422
|
+
// authored one is a URL that never connects rather than a working fallback.
|
|
423
|
+
(0, bun_test_1.it)("errors on an authored endpoint that is not https", () => {
|
|
424
|
+
const { issues } = (0, validation_1.classifyChainPublicRpcUrls)({ "1": validChain({ publicRpcUrl: "http://rpc.example" }) }, nets([{ id: 1, viemChain: "mainnet" }]));
|
|
425
|
+
(0, bun_test_1.expect)(issues).toHaveLength(1);
|
|
426
|
+
(0, bun_test_1.expect)(issues[0].severity).toBe("error");
|
|
427
|
+
});
|
|
428
|
+
// Absent is a real answer — the chain is display-only — so a real EVM chain
|
|
429
|
+
// with nothing to derive warns rather than failing the build.
|
|
430
|
+
(0, bun_test_1.it)("warns and leaves the field absent when nothing can supply one", () => {
|
|
431
|
+
const { chains, issues } = (0, validation_1.classifyChainPublicRpcUrls)({ "424242": validChain({ name: "Newchain" }) }, nets([{ id: 424242 }]));
|
|
432
|
+
(0, bun_test_1.expect)("publicRpcUrl" in chains["424242"]).toBe(false);
|
|
433
|
+
(0, bun_test_1.expect)(issues).toHaveLength(1);
|
|
434
|
+
(0, bun_test_1.expect)(issues[0].severity).toBe("warning");
|
|
435
|
+
});
|
|
436
|
+
// Non-EVM and virtual chains are never synthesised as viem chains, so there
|
|
437
|
+
// is nothing for them to be missing and nothing worth reporting.
|
|
438
|
+
(0, bun_test_1.it)("stays silent for chains that are never built as viem chains", () => {
|
|
439
|
+
const { issues } = (0, validation_1.classifyChainPublicRpcUrls)({
|
|
440
|
+
"792703809": validChain({ name: "Solana", vmType: "svm" }),
|
|
441
|
+
"1337": validChain({ name: "HyperCore", virtual: true }),
|
|
442
|
+
}, nets([{ id: 792703809 }, { id: 1337 }]));
|
|
443
|
+
(0, bun_test_1.expect)(issues).toEqual([]);
|
|
444
|
+
});
|
|
445
|
+
// Asserted against the shipped registry rather than by re-running the
|
|
446
|
+
// classifier, so this covers what actually reaches the artifact.
|
|
447
|
+
(0, bun_test_1.it)("gives every real EVM chain a well-formed https endpoint", () => {
|
|
448
|
+
for (const [id, entry] of Object.entries(index_1.chainRegistry)) {
|
|
449
|
+
const e = entry;
|
|
450
|
+
const isSynthesisable = e.vmType === "evm" && e.virtual !== true;
|
|
451
|
+
if (!isSynthesisable) {
|
|
452
|
+
(0, bun_test_1.expect)("publicRpcUrl" in e, id).toBe(false);
|
|
453
|
+
continue;
|
|
454
|
+
}
|
|
455
|
+
(0, bun_test_1.expect)(e.publicRpcUrl, id).toMatch(/^https:\/\/\S+$/);
|
|
456
|
+
(0, bun_test_1.expect)(e.publicRpcUrl.endsWith("/"), id).toBe(false);
|
|
457
|
+
}
|
|
458
|
+
});
|
|
459
|
+
});
|
|
460
|
+
(0, bun_test_1.describe)("validateChainIconSlugs", () => {
|
|
461
|
+
(0, bun_test_1.it)("passes a chain that carries a mark", () => {
|
|
462
|
+
(0, bun_test_1.expect)((0, validation_1.validateChainIconSlugs)({ "8453": validChain({ name: "Base", iconSlug: "base" }) })).toEqual([]);
|
|
463
|
+
});
|
|
464
|
+
// The whole point of the gate. `iconSlug` is authored by hand, so a chain
|
|
465
|
+
// added without one produces a blank tile in production and nothing anywhere
|
|
466
|
+
// reports it — no type, no test, no log. This turns that into a build failure
|
|
467
|
+
// while someone is already editing the registry.
|
|
468
|
+
(0, bun_test_1.it)("errors when a chain has neither a mark nor an exemption", () => {
|
|
469
|
+
const issues = (0, validation_1.validateChainIconSlugs)({
|
|
470
|
+
"424242": validChain({ name: "Newchain" }),
|
|
471
|
+
});
|
|
472
|
+
(0, bun_test_1.expect)(issues).toHaveLength(1);
|
|
473
|
+
(0, bun_test_1.expect)(issues[0].severity).toBe("error");
|
|
474
|
+
(0, bun_test_1.expect)(issues[0].chainId).toBe("424242");
|
|
475
|
+
// Both remedies are named, because which one is right is a product call.
|
|
476
|
+
(0, bun_test_1.expect)(issues[0].message).toContain("s3-public");
|
|
477
|
+
(0, bun_test_1.expect)(issues[0].message).toContain("CHAINS_WITHOUT_ICON");
|
|
478
|
+
});
|
|
479
|
+
// Absence is allowed, but only as a decision written down in code. The real
|
|
480
|
+
// exemption map is empty, so these two inject one rather than requiring some
|
|
481
|
+
// chain be left un-iconed purely to have something to assert against.
|
|
482
|
+
const exempt = new Map([["424242", "Newchain — no mark published"]]);
|
|
483
|
+
(0, bun_test_1.it)("accepts an absent mark for a chain listed in CHAINS_WITHOUT_ICON", () => {
|
|
484
|
+
(0, bun_test_1.expect)((0, validation_1.validateChainIconSlugs)({ "424242": validChain({ name: "Newchain" }) }, exempt)).toEqual([]);
|
|
485
|
+
});
|
|
486
|
+
// A stale exemption permanently excuses the chain it names, so the next person
|
|
487
|
+
// to drop that slug gets no warning at all.
|
|
488
|
+
(0, bun_test_1.it)("errors when an exempted chain gains a mark, so the exemption cannot rot", () => {
|
|
489
|
+
const issues = (0, validation_1.validateChainIconSlugs)({ "424242": validChain({ name: "Newchain", iconSlug: "newchain" }) }, exempt);
|
|
490
|
+
(0, bun_test_1.expect)(issues).toHaveLength(1);
|
|
491
|
+
(0, bun_test_1.expect)(issues[0].severity).toBe("error");
|
|
492
|
+
(0, bun_test_1.expect)(issues[0].message).toContain("remove the exemption");
|
|
493
|
+
});
|
|
494
|
+
bun_test_1.it.each([
|
|
495
|
+
["a full URL", "https://s3.rhinestone.dev/chains/base.svg"],
|
|
496
|
+
["an extension", "base.svg"],
|
|
497
|
+
["a directory", "chains/base"],
|
|
498
|
+
["uppercase", "Base"],
|
|
499
|
+
])("errors on a slug carrying %s", (_label, iconSlug) => {
|
|
500
|
+
const issues = (0, validation_1.validateChainIconSlugs)({
|
|
501
|
+
"8453": validChain({ name: "Base", iconSlug }),
|
|
502
|
+
});
|
|
503
|
+
(0, bun_test_1.expect)(issues).toHaveLength(1);
|
|
504
|
+
(0, bun_test_1.expect)(issues[0].message).toContain("malformed");
|
|
505
|
+
});
|
|
506
|
+
(0, bun_test_1.it)("passes the real generated registry", () => {
|
|
507
|
+
(0, bun_test_1.expect)((0, validation_1.validateChainIconSlugs)(index_1.chainRegistry)).toEqual([]);
|
|
508
|
+
});
|
|
509
|
+
// Asserted against the shipped registry rather than by re-running the check,
|
|
510
|
+
// so this covers what actually reaches the artifact.
|
|
511
|
+
(0, bun_test_1.it)("gives every shipped chain a mark", () => {
|
|
512
|
+
const withoutIcon = Object.entries(index_1.chainRegistry)
|
|
513
|
+
.filter(([, e]) => e.iconSlug === undefined)
|
|
514
|
+
.map(([id]) => id);
|
|
515
|
+
(0, bun_test_1.expect)(withoutIcon).toEqual([]);
|
|
516
|
+
});
|
|
517
|
+
// Two reuse rules that would otherwise look like copy-paste errors in the
|
|
518
|
+
// jsonnet. A testnet is visually the same chain as its mainnet, and all three
|
|
519
|
+
// HyperCore ids are Hyperliquid — the same branding as HyperEVM.
|
|
520
|
+
bun_test_1.it.each([
|
|
521
|
+
["11155111", "1"],
|
|
522
|
+
["11155420", "10"],
|
|
523
|
+
["84532", "8453"],
|
|
524
|
+
["421614", "42161"],
|
|
525
|
+
["9746", "9745"],
|
|
526
|
+
["1337", "999"],
|
|
527
|
+
["1337001", "999"],
|
|
528
|
+
["1337002", "999"],
|
|
529
|
+
])("reuses chain %s's glyph from chain %s", (child, parent) => {
|
|
530
|
+
const slug = index_1.chainRegistry[parent].iconSlug;
|
|
531
|
+
(0, bun_test_1.expect)(slug).toBeDefined();
|
|
532
|
+
(0, bun_test_1.expect)(index_1.chainRegistry[child].iconSlug).toBe(slug);
|
|
533
|
+
});
|
|
534
|
+
});
|
|
390
535
|
(0, bun_test_1.describe)("classifyChainStacks", () => {
|
|
391
536
|
const nets = (mainnets, testnets = []) => ({ mainnets, testnets });
|
|
392
537
|
// The common case: nothing is authored anywhere. viem already knows what Base
|
package/dist/scripts/generate.js
CHANGED
|
@@ -132,6 +132,14 @@ function renderChains(chainRegistry, mainnets, testnets) {
|
|
|
132
132
|
" caip2: string;",
|
|
133
133
|
" /** Block explorer, or null for a chain that has none (render plain text). */",
|
|
134
134
|
" explorer: Explorer | null;",
|
|
135
|
+
" /** Filename stem of this chain's mark in s3-public's `chains/` directory,",
|
|
136
|
+
" * served today at `https://s3.rhinestone.dev/chains/<slug>.svg`. A slug and",
|
|
137
|
+
" * not a URL so the CDN stays a consumer's concern. Absent means no mark —",
|
|
138
|
+
" * render a coloured initial chip, never an error. */",
|
|
139
|
+
" iconSlug?: string;",
|
|
140
|
+
" /** Public, unauthenticated https JSON-RPC endpoint — what a browser needs to",
|
|
141
|
+
" * build this chain with `defineChain`. Absent means display-only. */",
|
|
142
|
+
" publicRpcUrl?: string;",
|
|
135
143
|
" /** True for virtual chains (e.g. HyperCore) that settle on another chain. */",
|
|
136
144
|
" virtual?: boolean;",
|
|
137
145
|
" /** Which directions of transfer the chain supports; absent means both.",
|
|
@@ -394,12 +402,22 @@ async function generate() {
|
|
|
394
402
|
const { chains: caip2Classified, issues: caip2Issues } = (0, validation_1.classifyChainCaip2)(stackClassified);
|
|
395
403
|
// Then the block explorer, so a consumer rendering a link for a chain added
|
|
396
404
|
// after its release does not need a hand-maintained table of its own.
|
|
397
|
-
const { chains:
|
|
405
|
+
const { chains: explorerClassified, issues: explorerIssues } = (0, validation_1.classifyChainExplorers)(caip2Classified, output.networks);
|
|
406
|
+
// Then the public RPC endpoint, the last thing a browser consumer needs to
|
|
407
|
+
// build a viem `Chain` for a chain it was never compiled against — and the
|
|
408
|
+
// one that decides whether that chain is connectable or only displayable.
|
|
409
|
+
const { chains: classifiedChains, issues: publicRpcUrlIssues } = (0, validation_1.classifyChainPublicRpcUrls)(explorerClassified, output.networks);
|
|
410
|
+
// `iconSlug` is authored, not derived, so there is nothing to classify — only
|
|
411
|
+
// to insist on. A chain added without one would otherwise reach production as
|
|
412
|
+
// a blank tile, which nothing anywhere reports.
|
|
413
|
+
const iconSlugIssues = (0, validation_1.validateChainIconSlugs)(classifiedChains);
|
|
398
414
|
const classificationIssues = [
|
|
399
415
|
...networkIssues,
|
|
400
416
|
...stackIssues,
|
|
401
417
|
...caip2Issues,
|
|
402
418
|
...explorerIssues,
|
|
419
|
+
...publicRpcUrlIssues,
|
|
420
|
+
...iconSlugIssues,
|
|
403
421
|
];
|
|
404
422
|
await (0, promises_1.writeFile)(node_path_1.default.join(configsDir, "chains.json"), `${JSON.stringify(classifiedChains, null, 2)}\n`);
|
|
405
423
|
await (0, promises_1.writeFile)(node_path_1.default.join(configsDir, "mainnets.json"), `${JSON.stringify(output.mainnets, null, 2)}\n`);
|
|
@@ -122,6 +122,57 @@ export declare function classifyChainExplorers(chains: ChainRegistry, networks:
|
|
|
122
122
|
chains: ChainRegistry;
|
|
123
123
|
issues: ValidationIssue[];
|
|
124
124
|
};
|
|
125
|
+
/**
|
|
126
|
+
* Fails the build for a chain that has neither a mark nor an explicit exemption.
|
|
127
|
+
*
|
|
128
|
+
* Why an error and not a warning: `iconSlug` is authored by hand, so the failure
|
|
129
|
+
* mode of a new chain is silence — nobody forgets a field they were never
|
|
130
|
+
* prompted for, and the result is a blank tile in production that no test, type
|
|
131
|
+
* or log names. The gate turns that into a build failure at the moment someone
|
|
132
|
+
* is already editing the registry, which is the only point where the decision is
|
|
133
|
+
* cheap. CHAINS_WITHOUT_ICON is how "this chain genuinely has no mark" is said
|
|
134
|
+
* out loud, in code review, rather than being indistinguishable from an oversight.
|
|
135
|
+
*
|
|
136
|
+
* Deliberately offline. Whether `chains/<slug>.svg` actually exists on the CDN is
|
|
137
|
+
* not checked and must not be: the build would then fail on a CDN blip or in a
|
|
138
|
+
* sandboxed CI runner, which is a far more frequent failure than a typo'd slug
|
|
139
|
+
* and produces a red build with nothing wrong in the diff. The cost of a bad slug
|
|
140
|
+
* is one missing image; the cost of a network-dependent build gate is every build.
|
|
141
|
+
*/
|
|
142
|
+
export declare function validateChainIconSlugs(chains: ChainRegistry, exemptions?: ReadonlyMap<string, string>): ValidationIssue[];
|
|
143
|
+
/**
|
|
144
|
+
* Stamps every chain with a public JSON-RPC endpoint, derived from the chain's
|
|
145
|
+
* viem definition where it has one and authored in the jsonnet where it does
|
|
146
|
+
* not.
|
|
147
|
+
*
|
|
148
|
+
* Why this exists: a browser consumer building a viem `Chain` with
|
|
149
|
+
* `defineChain` — for a chain newer than its pinned viem — already gets id,
|
|
150
|
+
* name, native currency, `explorer` and `stack` from this registry. The RPC
|
|
151
|
+
* endpoint was the only missing piece, and it is the piece that decides whether
|
|
152
|
+
* a newly added chain is a first-class wallet-connect target or display-only.
|
|
153
|
+
* It is the PUBLIC endpoint on purpose: the rpc-proxy path needs an API key,
|
|
154
|
+
* which is the one thing a browser cannot have.
|
|
155
|
+
*
|
|
156
|
+
* Resolved by the entry's declared `viemChain` **name**, never by chain id, for
|
|
157
|
+
* the same reason as `classifyChainExplorers` — only worse here, because the
|
|
158
|
+
* consequence is a live endpoint for the wrong chain rather than a dead link.
|
|
159
|
+
* viem 2.55 has 23 chain ids claimed by more than one export: id 999 is
|
|
160
|
+
* `hyperEvm`, `hyperliquid`, `wanchainTestnet` and `zoraTestnet`, so an id
|
|
161
|
+
* lookup for HyperEVM can hand out a Zora testnet RPC.
|
|
162
|
+
*
|
|
163
|
+
* A missing endpoint is not an error — it resolves to the field being absent,
|
|
164
|
+
* and a consumer treats that chain as display-only. It is worth a warning for a
|
|
165
|
+
* real EVM chain, where the usual cause is a chain viem does not ship yet; for
|
|
166
|
+
* a non-EVM or virtual chain there is nothing to warn about, since none of them
|
|
167
|
+
* is ever built as a viem chain.
|
|
168
|
+
*/
|
|
169
|
+
export declare function classifyChainPublicRpcUrls(chains: ChainRegistry, networks: {
|
|
170
|
+
mainnets: StackNetworkEntry[];
|
|
171
|
+
testnets: StackNetworkEntry[];
|
|
172
|
+
}): {
|
|
173
|
+
chains: ChainRegistry;
|
|
174
|
+
issues: ValidationIssue[];
|
|
175
|
+
};
|
|
125
176
|
/** The subset of a generated network entry that stack classification needs. */
|
|
126
177
|
export interface StackNetworkEntry {
|
|
127
178
|
id: number;
|
|
@@ -36,6 +36,8 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
36
36
|
exports.classifyChainNetworks = classifyChainNetworks;
|
|
37
37
|
exports.classifyChainCaip2 = classifyChainCaip2;
|
|
38
38
|
exports.classifyChainExplorers = classifyChainExplorers;
|
|
39
|
+
exports.validateChainIconSlugs = validateChainIconSlugs;
|
|
40
|
+
exports.classifyChainPublicRpcUrls = classifyChainPublicRpcUrls;
|
|
39
41
|
exports.classifyChainStacks = classifyChainStacks;
|
|
40
42
|
exports.validateChainConfig = validateChainConfig;
|
|
41
43
|
exports.formatValidationIssues = formatValidationIssues;
|
|
@@ -141,6 +143,18 @@ function structuralProblems(chain) {
|
|
|
141
143
|
else if (chain.explorer !== null && !isChainExplorer(chain.explorer)) {
|
|
142
144
|
problems.push(`explorer ${JSON.stringify(chain.explorer)} is not null or { url (no trailing slash), addressPath, txPath (both slash-wrapped) }`);
|
|
143
145
|
}
|
|
146
|
+
// Optional, like publicRpcUrl below: a chain with no mark renders as a
|
|
147
|
+
// coloured initial chip. Only the shape is checked here — whether a chain is
|
|
148
|
+
// ALLOWED to have none is validateChainIconSlugs' job.
|
|
149
|
+
if (!isAbsentOr(chain.iconSlug, isIconSlug)) {
|
|
150
|
+
problems.push(`iconSlug ${JSON.stringify(chain.iconSlug)} is not a bare filename stem (lowercase letters, digits and hyphens)`);
|
|
151
|
+
}
|
|
152
|
+
// Optional, unlike the four above: a chain with no known public endpoint is
|
|
153
|
+
// display-only, which is a real answer. Only its shape is checked, and only
|
|
154
|
+
// when present — see classifyChainPublicRpcUrls for why it must be https.
|
|
155
|
+
if (!isAbsentOr(chain.publicRpcUrl, isPublicRpcUrl)) {
|
|
156
|
+
problems.push(`publicRpcUrl ${JSON.stringify(chain.publicRpcUrl)} is not an https:// URL`);
|
|
157
|
+
}
|
|
144
158
|
if (!isAbsentOr(chain.virtual, (v) => typeof v === 'boolean')) {
|
|
145
159
|
problems.push('virtual is not a boolean');
|
|
146
160
|
}
|
|
@@ -499,6 +513,186 @@ function classifyChainExplorers(chains, networks) {
|
|
|
499
513
|
}
|
|
500
514
|
return { chains: stamped, issues };
|
|
501
515
|
}
|
|
516
|
+
/**
|
|
517
|
+
* A bare filename stem: no directory, no extension, no scheme.
|
|
518
|
+
*
|
|
519
|
+
* The shape is what keeps the field a chain fact rather than a URL. A value
|
|
520
|
+
* carrying `https://`, a `chains/` prefix or a `.svg` suffix would have baked
|
|
521
|
+
* this CDN's layout into a signed artifact every service reads — the exact
|
|
522
|
+
* coupling the field is a slug to avoid.
|
|
523
|
+
*/
|
|
524
|
+
const ICON_SLUG = /^[a-z0-9][a-z0-9-]*$/;
|
|
525
|
+
const isIconSlug = (value) => typeof value === 'string' && ICON_SLUG.test(value);
|
|
526
|
+
/**
|
|
527
|
+
* Chain ids that are allowed to ship with no `iconSlug`.
|
|
528
|
+
*
|
|
529
|
+
* An entry here is a decision that this chain renders as a coloured initial
|
|
530
|
+
* chip. Every id needs a reason next to it, and the reason should stop being
|
|
531
|
+
* true eventually — when a mark is published, the entry goes.
|
|
532
|
+
*/
|
|
533
|
+
// Empty, and worth keeping that way: every chain in the registry has a mark.
|
|
534
|
+
// An entry here permanently excuses a chain from the check below, so it is for
|
|
535
|
+
// a chain whose mark genuinely cannot be obtained — not for one nobody has got
|
|
536
|
+
// round to yet.
|
|
537
|
+
const CHAINS_WITHOUT_ICON = new Map();
|
|
538
|
+
/**
|
|
539
|
+
* Fails the build for a chain that has neither a mark nor an explicit exemption.
|
|
540
|
+
*
|
|
541
|
+
* Why an error and not a warning: `iconSlug` is authored by hand, so the failure
|
|
542
|
+
* mode of a new chain is silence — nobody forgets a field they were never
|
|
543
|
+
* prompted for, and the result is a blank tile in production that no test, type
|
|
544
|
+
* or log names. The gate turns that into a build failure at the moment someone
|
|
545
|
+
* is already editing the registry, which is the only point where the decision is
|
|
546
|
+
* cheap. CHAINS_WITHOUT_ICON is how "this chain genuinely has no mark" is said
|
|
547
|
+
* out loud, in code review, rather than being indistinguishable from an oversight.
|
|
548
|
+
*
|
|
549
|
+
* Deliberately offline. Whether `chains/<slug>.svg` actually exists on the CDN is
|
|
550
|
+
* not checked and must not be: the build would then fail on a CDN blip or in a
|
|
551
|
+
* sandboxed CI runner, which is a far more frequent failure than a typo'd slug
|
|
552
|
+
* and produces a red build with nothing wrong in the diff. The cost of a bad slug
|
|
553
|
+
* is one missing image; the cost of a network-dependent build gate is every build.
|
|
554
|
+
*/
|
|
555
|
+
function validateChainIconSlugs(chains,
|
|
556
|
+
// Injectable so the exemption paths stay covered while the real map is empty.
|
|
557
|
+
// Testing them against a live entry would mean keeping a chain un-iconed just
|
|
558
|
+
// to have something to assert on.
|
|
559
|
+
exemptions = CHAINS_WITHOUT_ICON) {
|
|
560
|
+
const issues = [];
|
|
561
|
+
for (const [chainId, chain] of Object.entries(chains)) {
|
|
562
|
+
const chainName = isNonEmptyString(chain?.name)
|
|
563
|
+
? chain.name
|
|
564
|
+
: '<unnamed>';
|
|
565
|
+
const slug = chain?.iconSlug;
|
|
566
|
+
const exemption = exemptions.get(chainId);
|
|
567
|
+
if (slug === undefined) {
|
|
568
|
+
if (exemption === undefined) {
|
|
569
|
+
issues.push({
|
|
570
|
+
severity: 'error',
|
|
571
|
+
chainId,
|
|
572
|
+
chainName,
|
|
573
|
+
message: 'has no iconSlug — add the filename stem of its mark in rhinestonewtf/s3-public ' +
|
|
574
|
+
"(`chains/<slug>.svg`) to this chain's chainMeta entry in " +
|
|
575
|
+
'config/shared-configs.jsonnet, or, if it genuinely has none, add its id to ' +
|
|
576
|
+
'CHAINS_WITHOUT_ICON in scripts/validation.ts with the reason. It is not ' +
|
|
577
|
+
'defaulted: a chain with no mark renders as a blank tile with nothing logged',
|
|
578
|
+
});
|
|
579
|
+
}
|
|
580
|
+
continue;
|
|
581
|
+
}
|
|
582
|
+
// A stale exemption is worse than no exemption: it permanently excuses the
|
|
583
|
+
// one chain it names, so the next person to drop the slug gets no warning.
|
|
584
|
+
if (exemption !== undefined) {
|
|
585
|
+
issues.push({
|
|
586
|
+
severity: 'error',
|
|
587
|
+
chainId,
|
|
588
|
+
chainName,
|
|
589
|
+
message: `has an iconSlug ${JSON.stringify(slug)} but is still listed in CHAINS_WITHOUT_ICON (${exemption}) — remove the exemption`,
|
|
590
|
+
});
|
|
591
|
+
}
|
|
592
|
+
// Shape is also checked structurally; repeated here so this function is a
|
|
593
|
+
// complete gate on its own and a malformed slug cannot pass as "present".
|
|
594
|
+
if (!isIconSlug(slug)) {
|
|
595
|
+
issues.push({
|
|
596
|
+
severity: 'error',
|
|
597
|
+
chainId,
|
|
598
|
+
chainName,
|
|
599
|
+
message: `declares a malformed iconSlug ${JSON.stringify(slug)} — expected a bare filename stem, with no directory, extension or scheme`,
|
|
600
|
+
});
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
return issues;
|
|
604
|
+
}
|
|
605
|
+
/**
|
|
606
|
+
* `https://` only, and deliberately so. The field exists for a browser, which
|
|
607
|
+
* cannot hold an API key and — on an https page — cannot call an `http://`
|
|
608
|
+
* endpoint at all: the request is blocked as mixed content. Publishing one
|
|
609
|
+
* would hand consumers a URL that never connects, which is strictly worse than
|
|
610
|
+
* publishing nothing and letting the chain fall back to display-only.
|
|
611
|
+
*/
|
|
612
|
+
const isPublicRpcUrl = (value) => typeof value === 'string' && /^https:\/\/\S+$/.test(value);
|
|
613
|
+
/**
|
|
614
|
+
* Stamps every chain with a public JSON-RPC endpoint, derived from the chain's
|
|
615
|
+
* viem definition where it has one and authored in the jsonnet where it does
|
|
616
|
+
* not.
|
|
617
|
+
*
|
|
618
|
+
* Why this exists: a browser consumer building a viem `Chain` with
|
|
619
|
+
* `defineChain` — for a chain newer than its pinned viem — already gets id,
|
|
620
|
+
* name, native currency, `explorer` and `stack` from this registry. The RPC
|
|
621
|
+
* endpoint was the only missing piece, and it is the piece that decides whether
|
|
622
|
+
* a newly added chain is a first-class wallet-connect target or display-only.
|
|
623
|
+
* It is the PUBLIC endpoint on purpose: the rpc-proxy path needs an API key,
|
|
624
|
+
* which is the one thing a browser cannot have.
|
|
625
|
+
*
|
|
626
|
+
* Resolved by the entry's declared `viemChain` **name**, never by chain id, for
|
|
627
|
+
* the same reason as `classifyChainExplorers` — only worse here, because the
|
|
628
|
+
* consequence is a live endpoint for the wrong chain rather than a dead link.
|
|
629
|
+
* viem 2.55 has 23 chain ids claimed by more than one export: id 999 is
|
|
630
|
+
* `hyperEvm`, `hyperliquid`, `wanchainTestnet` and `zoraTestnet`, so an id
|
|
631
|
+
* lookup for HyperEVM can hand out a Zora testnet RPC.
|
|
632
|
+
*
|
|
633
|
+
* A missing endpoint is not an error — it resolves to the field being absent,
|
|
634
|
+
* and a consumer treats that chain as display-only. It is worth a warning for a
|
|
635
|
+
* real EVM chain, where the usual cause is a chain viem does not ship yet; for
|
|
636
|
+
* a non-EVM or virtual chain there is nothing to warn about, since none of them
|
|
637
|
+
* is ever built as a viem chain.
|
|
638
|
+
*/
|
|
639
|
+
function classifyChainPublicRpcUrls(chains, networks) {
|
|
640
|
+
const issues = [];
|
|
641
|
+
const stamped = {};
|
|
642
|
+
const entriesById = new Map();
|
|
643
|
+
for (const entry of [...networks.mainnets, ...networks.testnets]) {
|
|
644
|
+
entriesById.set(String(entry.id), entry);
|
|
645
|
+
}
|
|
646
|
+
for (const [chainId, chain] of Object.entries(chains)) {
|
|
647
|
+
const chainName = isNonEmptyString(chain?.name)
|
|
648
|
+
? chain.name
|
|
649
|
+
: '<unnamed>';
|
|
650
|
+
const authored = chain?.publicRpcUrl;
|
|
651
|
+
// Authoring wins outright, as with `explorer`: the jsonnet is the only place
|
|
652
|
+
// an endpoint can be given for a chain viem does not ship, or a better one
|
|
653
|
+
// preferred over viem's default.
|
|
654
|
+
if (authored !== undefined) {
|
|
655
|
+
if (!isPublicRpcUrl(authored)) {
|
|
656
|
+
issues.push({
|
|
657
|
+
severity: 'error',
|
|
658
|
+
chainId,
|
|
659
|
+
chainName,
|
|
660
|
+
message: `declares a malformed publicRpcUrl ${JSON.stringify(authored)} — expected an https:// URL`,
|
|
661
|
+
});
|
|
662
|
+
stamped[chainId] = withSortedKeys({ ...chain });
|
|
663
|
+
continue;
|
|
664
|
+
}
|
|
665
|
+
stamped[chainId] = withSortedKeys({ ...chain, publicRpcUrl: authored });
|
|
666
|
+
continue;
|
|
667
|
+
}
|
|
668
|
+
const viemChainName = entriesById.get(chainId)?.viemChain;
|
|
669
|
+
const url = viemChainName
|
|
670
|
+
? viemChains[viemChainName]?.rpcUrls?.default?.http?.[0]
|
|
671
|
+
: undefined;
|
|
672
|
+
// viem's own value carries a trailing slash on some chains (`unichain`) and
|
|
673
|
+
// not others. Both address the same endpoint, so it is normalised here
|
|
674
|
+
// rather than published as an inconsistency every consumer has to absorb.
|
|
675
|
+
const normalised = typeof url === 'string' ? url.replace(/\/+$/, '') : undefined;
|
|
676
|
+
if (!isPublicRpcUrl(normalised)) {
|
|
677
|
+
// A chain that is never synthesised as a viem chain has nothing to be
|
|
678
|
+
// missing — only a real EVM chain is worth reporting.
|
|
679
|
+
if (chain.vmType === 'evm' && chain.virtual !== true) {
|
|
680
|
+
issues.push({
|
|
681
|
+
severity: 'warning',
|
|
682
|
+
chainId,
|
|
683
|
+
chainName,
|
|
684
|
+
message: viemChainName
|
|
685
|
+
? `viem chain "${viemChainName}" ships no usable https rpc url — the chain stays display-only for consumers that synthesise it until one is authored`
|
|
686
|
+
: 'has no viemChain to derive a public rpc url from and authors none — the chain stays display-only for consumers that synthesise it',
|
|
687
|
+
});
|
|
688
|
+
}
|
|
689
|
+
stamped[chainId] = withSortedKeys({ ...chain });
|
|
690
|
+
continue;
|
|
691
|
+
}
|
|
692
|
+
stamped[chainId] = withSortedKeys({ ...chain, publicRpcUrl: normalised });
|
|
693
|
+
}
|
|
694
|
+
return { chains: stamped, issues };
|
|
695
|
+
}
|
|
502
696
|
const isChainStack = (v) => typeof v === 'string' && STACKS.has(v);
|
|
503
697
|
const STACK_FORMATTERS = [
|
|
504
698
|
['op-stack', op_stack_1.chainConfig.formatters],
|
package/dist/src/chains.d.ts
CHANGED
|
@@ -38,6 +38,14 @@ interface Chain {
|
|
|
38
38
|
caip2: string;
|
|
39
39
|
/** Block explorer, or null for a chain that has none (render plain text). */
|
|
40
40
|
explorer: Explorer | null;
|
|
41
|
+
/** Filename stem of this chain's mark in s3-public's `chains/` directory,
|
|
42
|
+
* served today at `https://s3.rhinestone.dev/chains/<slug>.svg`. A slug and
|
|
43
|
+
* not a URL so the CDN stays a consumer's concern. Absent means no mark —
|
|
44
|
+
* render a coloured initial chip, never an error. */
|
|
45
|
+
iconSlug?: string;
|
|
46
|
+
/** Public, unauthenticated https JSON-RPC endpoint — what a browser needs to
|
|
47
|
+
* build this chain with `defineChain`. Absent means display-only. */
|
|
48
|
+
publicRpcUrl?: string;
|
|
41
49
|
/** True for virtual chains (e.g. HyperCore) that settle on another chain. */
|
|
42
50
|
virtual?: boolean;
|
|
43
51
|
/** Which directions of transfer the chain supports; absent means both.
|
package/dist/src/chains.js
CHANGED
|
@@ -9,6 +9,7 @@ const chains = {
|
|
|
9
9
|
"addressPath": "/address/",
|
|
10
10
|
"txPath": "/tx/"
|
|
11
11
|
},
|
|
12
|
+
"iconSlug": "mainnet",
|
|
12
13
|
"name": "Ethereum",
|
|
13
14
|
"nativeToken": {
|
|
14
15
|
"address": "0x0000000000000000000000000000000000000000",
|
|
@@ -16,6 +17,7 @@ const chains = {
|
|
|
16
17
|
"symbol": "ETH"
|
|
17
18
|
},
|
|
18
19
|
"network": "mainnet",
|
|
20
|
+
"publicRpcUrl": "https://eth.merkle.io",
|
|
19
21
|
"settlementLayers": [
|
|
20
22
|
"ACROSS",
|
|
21
23
|
"ECO",
|
|
@@ -103,6 +105,7 @@ const chains = {
|
|
|
103
105
|
"addressPath": "/address/",
|
|
104
106
|
"txPath": "/tx/"
|
|
105
107
|
},
|
|
108
|
+
"iconSlug": "optimism",
|
|
106
109
|
"name": "OP Mainnet",
|
|
107
110
|
"nativeToken": {
|
|
108
111
|
"address": "0x0000000000000000000000000000000000000000",
|
|
@@ -110,6 +113,7 @@ const chains = {
|
|
|
110
113
|
"symbol": "ETH"
|
|
111
114
|
},
|
|
112
115
|
"network": "mainnet",
|
|
116
|
+
"publicRpcUrl": "https://mainnet.optimism.io",
|
|
113
117
|
"settlementLayers": [
|
|
114
118
|
"ACROSS",
|
|
115
119
|
"ECO",
|
|
@@ -208,6 +212,7 @@ const chains = {
|
|
|
208
212
|
"addressPath": "/address/",
|
|
209
213
|
"txPath": "/tx/"
|
|
210
214
|
},
|
|
215
|
+
"iconSlug": "bsc",
|
|
211
216
|
"name": "BNB Smart Chain",
|
|
212
217
|
"nativeToken": {
|
|
213
218
|
"address": "0x0000000000000000000000000000000000000000",
|
|
@@ -215,6 +220,7 @@ const chains = {
|
|
|
215
220
|
"symbol": "BNB"
|
|
216
221
|
},
|
|
217
222
|
"network": "mainnet",
|
|
223
|
+
"publicRpcUrl": "https://56.rpc.thirdweb.com",
|
|
218
224
|
"settlementLayers": [
|
|
219
225
|
"ACROSS",
|
|
220
226
|
"ECO",
|
|
@@ -286,6 +292,7 @@ const chains = {
|
|
|
286
292
|
"addressPath": "/address/",
|
|
287
293
|
"txPath": "/tx/"
|
|
288
294
|
},
|
|
295
|
+
"iconSlug": "gnosis",
|
|
289
296
|
"name": "Gnosis",
|
|
290
297
|
"nativeToken": {
|
|
291
298
|
"address": "0x0000000000000000000000000000000000000000",
|
|
@@ -293,6 +300,7 @@ const chains = {
|
|
|
293
300
|
"symbol": "XDAI"
|
|
294
301
|
},
|
|
295
302
|
"network": "mainnet",
|
|
303
|
+
"publicRpcUrl": "https://rpc.gnosischain.com",
|
|
296
304
|
"settlementLayers": [
|
|
297
305
|
"RELAY",
|
|
298
306
|
"NEAR",
|
|
@@ -332,6 +340,7 @@ const chains = {
|
|
|
332
340
|
"addressPath": "/address/",
|
|
333
341
|
"txPath": "/tx/"
|
|
334
342
|
},
|
|
343
|
+
"iconSlug": "unichain",
|
|
335
344
|
"name": "Unichain",
|
|
336
345
|
"nativeToken": {
|
|
337
346
|
"address": "0x0000000000000000000000000000000000000000",
|
|
@@ -339,6 +348,7 @@ const chains = {
|
|
|
339
348
|
"symbol": "ETH"
|
|
340
349
|
},
|
|
341
350
|
"network": "mainnet",
|
|
351
|
+
"publicRpcUrl": "https://mainnet.unichain.org",
|
|
342
352
|
"settlementLayers": [
|
|
343
353
|
"ACROSS",
|
|
344
354
|
"ECO",
|
|
@@ -425,6 +435,7 @@ const chains = {
|
|
|
425
435
|
"addressPath": "/address/",
|
|
426
436
|
"txPath": "/tx/"
|
|
427
437
|
},
|
|
438
|
+
"iconSlug": "polygon",
|
|
428
439
|
"name": "Polygon",
|
|
429
440
|
"nativeToken": {
|
|
430
441
|
"address": "0x0000000000000000000000000000000000000000",
|
|
@@ -432,6 +443,7 @@ const chains = {
|
|
|
432
443
|
"symbol": "POL"
|
|
433
444
|
},
|
|
434
445
|
"network": "mainnet",
|
|
446
|
+
"publicRpcUrl": "https://polygon.drpc.org",
|
|
435
447
|
"settlementLayers": [
|
|
436
448
|
"ACROSS",
|
|
437
449
|
"ECO",
|
|
@@ -506,6 +518,7 @@ const chains = {
|
|
|
506
518
|
"addressPath": "/address/",
|
|
507
519
|
"txPath": "/tx/"
|
|
508
520
|
},
|
|
521
|
+
"iconSlug": "monad",
|
|
509
522
|
"name": "Monad",
|
|
510
523
|
"nativeToken": {
|
|
511
524
|
"address": "0x0000000000000000000000000000000000000000",
|
|
@@ -513,6 +526,7 @@ const chains = {
|
|
|
513
526
|
"symbol": "MON"
|
|
514
527
|
},
|
|
515
528
|
"network": "mainnet",
|
|
529
|
+
"publicRpcUrl": "https://rpc.monad.xyz",
|
|
516
530
|
"settlementLayers": [
|
|
517
531
|
"ACROSS",
|
|
518
532
|
"RELAY",
|
|
@@ -585,6 +599,7 @@ const chains = {
|
|
|
585
599
|
"addressPath": "/address/",
|
|
586
600
|
"txPath": "/tx/"
|
|
587
601
|
},
|
|
602
|
+
"iconSlug": "sonic",
|
|
588
603
|
"name": "Sonic",
|
|
589
604
|
"nativeToken": {
|
|
590
605
|
"address": "0x0000000000000000000000000000000000000000",
|
|
@@ -592,6 +607,7 @@ const chains = {
|
|
|
592
607
|
"symbol": "S"
|
|
593
608
|
},
|
|
594
609
|
"network": "mainnet",
|
|
610
|
+
"publicRpcUrl": "https://rpc.soniclabs.com",
|
|
595
611
|
"settlementLayers": [
|
|
596
612
|
"ECO",
|
|
597
613
|
"RELAY",
|
|
@@ -636,6 +652,7 @@ const chains = {
|
|
|
636
652
|
"addressPath": "/address/",
|
|
637
653
|
"txPath": "/tx/"
|
|
638
654
|
},
|
|
655
|
+
"iconSlug": "hyperevm",
|
|
639
656
|
"name": "HyperEVM",
|
|
640
657
|
"nativeToken": {
|
|
641
658
|
"address": "0x0000000000000000000000000000000000000000",
|
|
@@ -643,6 +660,7 @@ const chains = {
|
|
|
643
660
|
"symbol": "HYPE"
|
|
644
661
|
},
|
|
645
662
|
"network": "mainnet",
|
|
663
|
+
"publicRpcUrl": "https://rpc.hyperliquid.xyz/evm",
|
|
646
664
|
"settlementLayers": [
|
|
647
665
|
"ACROSS",
|
|
648
666
|
"ECO",
|
|
@@ -699,6 +717,7 @@ const chains = {
|
|
|
699
717
|
"txPath": "/explorer/tx/",
|
|
700
718
|
"url": "https://app.hyperliquid.xyz"
|
|
701
719
|
},
|
|
720
|
+
"iconSlug": "hyperevm",
|
|
702
721
|
"name": "HyperCore",
|
|
703
722
|
"nativeToken": {
|
|
704
723
|
"address": "0x0000000000000000000000000000000000000000",
|
|
@@ -739,6 +758,7 @@ const chains = {
|
|
|
739
758
|
"addressPath": "/address/",
|
|
740
759
|
"txPath": "/tx/"
|
|
741
760
|
},
|
|
761
|
+
"iconSlug": "soneium",
|
|
742
762
|
"name": "Soneium Mainnet",
|
|
743
763
|
"nativeToken": {
|
|
744
764
|
"address": "0x0000000000000000000000000000000000000000",
|
|
@@ -746,6 +766,7 @@ const chains = {
|
|
|
746
766
|
"symbol": "ETH"
|
|
747
767
|
},
|
|
748
768
|
"network": "mainnet",
|
|
769
|
+
"publicRpcUrl": "https://rpc.soneium.org",
|
|
749
770
|
"settlementLayers": [
|
|
750
771
|
"ACROSS",
|
|
751
772
|
"RELAY"
|
|
@@ -817,6 +838,7 @@ const chains = {
|
|
|
817
838
|
"addressPath": "/address/",
|
|
818
839
|
"txPath": "/tx/"
|
|
819
840
|
},
|
|
841
|
+
"iconSlug": "robinhood",
|
|
820
842
|
"name": "Robinhood Chain",
|
|
821
843
|
"nativeToken": {
|
|
822
844
|
"address": "0x0000000000000000000000000000000000000000",
|
|
@@ -824,6 +846,7 @@ const chains = {
|
|
|
824
846
|
"symbol": "ETH"
|
|
825
847
|
},
|
|
826
848
|
"network": "mainnet",
|
|
849
|
+
"publicRpcUrl": "https://rpc.mainnet.chain.robinhood.com",
|
|
827
850
|
"settlementLayers": [
|
|
828
851
|
"ACROSS",
|
|
829
852
|
"RELAY"
|
|
@@ -884,6 +907,7 @@ const chains = {
|
|
|
884
907
|
"addressPath": "/address/",
|
|
885
908
|
"txPath": "/tx/"
|
|
886
909
|
},
|
|
910
|
+
"iconSlug": "base",
|
|
887
911
|
"name": "Base",
|
|
888
912
|
"nativeToken": {
|
|
889
913
|
"address": "0x0000000000000000000000000000000000000000",
|
|
@@ -891,6 +915,7 @@ const chains = {
|
|
|
891
915
|
"symbol": "ETH"
|
|
892
916
|
},
|
|
893
917
|
"network": "mainnet",
|
|
918
|
+
"publicRpcUrl": "https://mainnet.base.org",
|
|
894
919
|
"settlementLayers": [
|
|
895
920
|
"ACROSS",
|
|
896
921
|
"ECO",
|
|
@@ -963,6 +988,7 @@ const chains = {
|
|
|
963
988
|
"addressPath": "/address/",
|
|
964
989
|
"txPath": "/tx/"
|
|
965
990
|
},
|
|
991
|
+
"iconSlug": "plasma",
|
|
966
992
|
"name": "Plasma",
|
|
967
993
|
"nativeToken": {
|
|
968
994
|
"address": "0x0000000000000000000000000000000000000000",
|
|
@@ -970,6 +996,7 @@ const chains = {
|
|
|
970
996
|
"symbol": "XPL"
|
|
971
997
|
},
|
|
972
998
|
"network": "mainnet",
|
|
999
|
+
"publicRpcUrl": "https://rpc.plasma.to",
|
|
973
1000
|
"settlementLayers": [
|
|
974
1001
|
"ACROSS",
|
|
975
1002
|
"ECO",
|
|
@@ -1017,6 +1044,7 @@ const chains = {
|
|
|
1017
1044
|
"addressPath": "/address/",
|
|
1018
1045
|
"txPath": "/tx/"
|
|
1019
1046
|
},
|
|
1047
|
+
"iconSlug": "plasma",
|
|
1020
1048
|
"name": "Plasma Testnet",
|
|
1021
1049
|
"nativeToken": {
|
|
1022
1050
|
"address": "0x0000000000000000000000000000000000000000",
|
|
@@ -1024,6 +1052,7 @@ const chains = {
|
|
|
1024
1052
|
"symbol": "XPL"
|
|
1025
1053
|
},
|
|
1026
1054
|
"network": "testnet",
|
|
1055
|
+
"publicRpcUrl": "https://testnet-rpc.plasma.to",
|
|
1027
1056
|
"settlementLayers": [
|
|
1028
1057
|
"ECO"
|
|
1029
1058
|
],
|
|
@@ -1073,6 +1102,7 @@ const chains = {
|
|
|
1073
1102
|
"addressPath": "/address/",
|
|
1074
1103
|
"txPath": "/tx/"
|
|
1075
1104
|
},
|
|
1105
|
+
"iconSlug": "arbitrum",
|
|
1076
1106
|
"name": "Arbitrum One",
|
|
1077
1107
|
"nativeToken": {
|
|
1078
1108
|
"address": "0x0000000000000000000000000000000000000000",
|
|
@@ -1080,6 +1110,7 @@ const chains = {
|
|
|
1080
1110
|
"symbol": "ETH"
|
|
1081
1111
|
},
|
|
1082
1112
|
"network": "mainnet",
|
|
1113
|
+
"publicRpcUrl": "https://arb1.arbitrum.io/rpc",
|
|
1083
1114
|
"settlementLayers": [
|
|
1084
1115
|
"ACROSS",
|
|
1085
1116
|
"ECO",
|
|
@@ -1167,6 +1198,7 @@ const chains = {
|
|
|
1167
1198
|
"addressPath": "/address/",
|
|
1168
1199
|
"txPath": "/tx/"
|
|
1169
1200
|
},
|
|
1201
|
+
"iconSlug": "avalanche",
|
|
1170
1202
|
"name": "Avalanche",
|
|
1171
1203
|
"nativeToken": {
|
|
1172
1204
|
"address": "0x0000000000000000000000000000000000000000",
|
|
@@ -1174,6 +1206,7 @@ const chains = {
|
|
|
1174
1206
|
"symbol": "AVAX"
|
|
1175
1207
|
},
|
|
1176
1208
|
"network": "mainnet",
|
|
1209
|
+
"publicRpcUrl": "https://api.avax.network/ext/bc/C/rpc",
|
|
1177
1210
|
"settlementLayers": [
|
|
1178
1211
|
"ACROSS",
|
|
1179
1212
|
"RELAY",
|
|
@@ -1254,6 +1287,7 @@ const chains = {
|
|
|
1254
1287
|
"addressPath": "/address/",
|
|
1255
1288
|
"txPath": "/tx/"
|
|
1256
1289
|
},
|
|
1290
|
+
"iconSlug": "base",
|
|
1257
1291
|
"name": "Base Sepolia",
|
|
1258
1292
|
"nativeToken": {
|
|
1259
1293
|
"address": "0x0000000000000000000000000000000000000000",
|
|
@@ -1261,6 +1295,7 @@ const chains = {
|
|
|
1261
1295
|
"symbol": "ETH"
|
|
1262
1296
|
},
|
|
1263
1297
|
"network": "testnet",
|
|
1298
|
+
"publicRpcUrl": "https://sepolia.base.org",
|
|
1264
1299
|
"settlementLayers": [
|
|
1265
1300
|
"ACROSS",
|
|
1266
1301
|
"ECO",
|
|
@@ -1339,6 +1374,7 @@ const chains = {
|
|
|
1339
1374
|
"addressPath": "/address/",
|
|
1340
1375
|
"txPath": "/tx/"
|
|
1341
1376
|
},
|
|
1377
|
+
"iconSlug": "arbitrum",
|
|
1342
1378
|
"name": "Arbitrum Sepolia",
|
|
1343
1379
|
"nativeToken": {
|
|
1344
1380
|
"address": "0x0000000000000000000000000000000000000000",
|
|
@@ -1346,6 +1382,7 @@ const chains = {
|
|
|
1346
1382
|
"symbol": "ETH"
|
|
1347
1383
|
},
|
|
1348
1384
|
"network": "testnet",
|
|
1385
|
+
"publicRpcUrl": "https://sepolia-rollup.arbitrum.io/rpc",
|
|
1349
1386
|
"settlementLayers": [
|
|
1350
1387
|
"ACROSS",
|
|
1351
1388
|
"ECO",
|
|
@@ -1410,6 +1447,7 @@ const chains = {
|
|
|
1410
1447
|
"addressPath": "/address/",
|
|
1411
1448
|
"txPath": "/tx/"
|
|
1412
1449
|
},
|
|
1450
|
+
"iconSlug": "katana",
|
|
1413
1451
|
"name": "Katana",
|
|
1414
1452
|
"nativeToken": {
|
|
1415
1453
|
"address": "0x0000000000000000000000000000000000000000",
|
|
@@ -1417,6 +1455,7 @@ const chains = {
|
|
|
1417
1455
|
"symbol": "ETH"
|
|
1418
1456
|
},
|
|
1419
1457
|
"network": "mainnet",
|
|
1458
|
+
"publicRpcUrl": "https://rpc.katana.network",
|
|
1420
1459
|
"settlementLayers": [
|
|
1421
1460
|
"RELAY",
|
|
1422
1461
|
"RHINO"
|
|
@@ -1478,6 +1517,7 @@ const chains = {
|
|
|
1478
1517
|
"txPath": "/explorer/tx/",
|
|
1479
1518
|
"url": "https://app.hyperliquid.xyz"
|
|
1480
1519
|
},
|
|
1520
|
+
"iconSlug": "hyperevm",
|
|
1481
1521
|
"name": "HyperCore Spot",
|
|
1482
1522
|
"nativeToken": {
|
|
1483
1523
|
"address": "0x0000000000000000000000000000000000000000",
|
|
@@ -1518,6 +1558,7 @@ const chains = {
|
|
|
1518
1558
|
"txPath": "/explorer/tx/",
|
|
1519
1559
|
"url": "https://app.hyperliquid.xyz"
|
|
1520
1560
|
},
|
|
1561
|
+
"iconSlug": "hyperevm",
|
|
1521
1562
|
"name": "HyperCore Perp",
|
|
1522
1563
|
"nativeToken": {
|
|
1523
1564
|
"address": "0x0000000000000000000000000000000000000000",
|
|
@@ -1558,6 +1599,7 @@ const chains = {
|
|
|
1558
1599
|
"addressPath": "/address/",
|
|
1559
1600
|
"txPath": "/tx/"
|
|
1560
1601
|
},
|
|
1602
|
+
"iconSlug": "mainnet",
|
|
1561
1603
|
"name": "Sepolia",
|
|
1562
1604
|
"nativeToken": {
|
|
1563
1605
|
"address": "0x0000000000000000000000000000000000000000",
|
|
@@ -1565,6 +1607,7 @@ const chains = {
|
|
|
1565
1607
|
"symbol": "ETH"
|
|
1566
1608
|
},
|
|
1567
1609
|
"network": "testnet",
|
|
1610
|
+
"publicRpcUrl": "https://11155111.rpc.thirdweb.com",
|
|
1568
1611
|
"settlementLayers": [
|
|
1569
1612
|
"ACROSS",
|
|
1570
1613
|
"ECO",
|
|
@@ -1639,6 +1682,7 @@ const chains = {
|
|
|
1639
1682
|
"addressPath": "/address/",
|
|
1640
1683
|
"txPath": "/tx/"
|
|
1641
1684
|
},
|
|
1685
|
+
"iconSlug": "optimism",
|
|
1642
1686
|
"name": "OP Sepolia",
|
|
1643
1687
|
"nativeToken": {
|
|
1644
1688
|
"address": "0x0000000000000000000000000000000000000000",
|
|
@@ -1646,6 +1690,7 @@ const chains = {
|
|
|
1646
1690
|
"symbol": "ETH"
|
|
1647
1691
|
},
|
|
1648
1692
|
"network": "testnet",
|
|
1693
|
+
"publicRpcUrl": "https://sepolia.optimism.io",
|
|
1649
1694
|
"settlementLayers": [
|
|
1650
1695
|
"ACROSS",
|
|
1651
1696
|
"ECO",
|
|
@@ -1710,6 +1755,7 @@ const chains = {
|
|
|
1710
1755
|
"txPath": "/#/transaction/",
|
|
1711
1756
|
"url": "https://tronscan.org"
|
|
1712
1757
|
},
|
|
1758
|
+
"iconSlug": "tron",
|
|
1713
1759
|
"name": "Tron",
|
|
1714
1760
|
"nativeToken": {
|
|
1715
1761
|
"address": "0x0000000000000000000000000000000000000000",
|
|
@@ -1757,6 +1803,7 @@ const chains = {
|
|
|
1757
1803
|
"txPath": "/tx/",
|
|
1758
1804
|
"url": "https://solscan.io"
|
|
1759
1805
|
},
|
|
1806
|
+
"iconSlug": "solana",
|
|
1760
1807
|
"name": "Solana",
|
|
1761
1808
|
"nativeToken": {
|
|
1762
1809
|
"address": "11111111111111111111111111111111",
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
declare const PACKAGE_VERSION = "1.
|
|
1
|
+
declare const PACKAGE_VERSION = "1.12.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 = "1.
|
|
5
|
+
const PACKAGE_VERSION = "1.12.0";
|
|
6
6
|
exports.PACKAGE_VERSION = PACKAGE_VERSION;
|
package/dist/src/types.d.ts
CHANGED
|
@@ -128,6 +128,46 @@ interface ChainEntry {
|
|
|
128
128
|
* jsonnet where it does not — see `classifyChainExplorers`.
|
|
129
129
|
*/
|
|
130
130
|
explorer: ChainExplorer | null;
|
|
131
|
+
/**
|
|
132
|
+
* Filename stem of this chain's mark in the `chains/` directory of
|
|
133
|
+
* rhinestonewtf/s3-public — today `https://s3.rhinestone.dev/chains/<slug>.svg`.
|
|
134
|
+
*
|
|
135
|
+
* The other half of the same problem `publicRpcUrl` solves: a frontend can
|
|
136
|
+
* already name, link and connect to a chain it was never compiled against,
|
|
137
|
+
* but it could not draw it. Every consumer instead carried a hand-maintained
|
|
138
|
+
* chain-id → asset table, which is the kind of table that is always one chain
|
|
139
|
+
* behind the registry.
|
|
140
|
+
*
|
|
141
|
+
* Deliberately a SLUG, not a URL. This artifact describes chain identity; the
|
|
142
|
+
* CDN it happens to be served from is deployment, and it is the consumer (the
|
|
143
|
+
* deposit-processor) that composes the URL. Publishing full URLs would put the
|
|
144
|
+
* host in a signed artifact read by every service, so moving the CDN would
|
|
145
|
+
* mean re-publishing facts and rolling the fleet rather than changing one
|
|
146
|
+
* template string.
|
|
147
|
+
*
|
|
148
|
+
* Optional, and absence is a normal answer — a chain with no mark renders as a
|
|
149
|
+
* coloured initial chip. A consumer must never treat it as an error. Which
|
|
150
|
+
* chains may be absent is decided at build time, not here: see
|
|
151
|
+
* `validateChainIconSlugs`.
|
|
152
|
+
*/
|
|
153
|
+
iconSlug?: string;
|
|
154
|
+
/**
|
|
155
|
+
* A public, unauthenticated JSON-RPC endpoint over HTTPS, when one is known.
|
|
156
|
+
*
|
|
157
|
+
* The one thing a browser consumer cannot get from anywhere else in this
|
|
158
|
+
* artifact. Building a viem `Chain` with `defineChain` — which the deposit
|
|
159
|
+
* modal does at runtime for chains it was never compiled against — needs the
|
|
160
|
+
* id, name, native currency, explorer and stack, all of which are above, plus
|
|
161
|
+
* an RPC endpoint. That single field is what decides whether a newly added
|
|
162
|
+
* chain is a first-class wallet-connect target or display-only.
|
|
163
|
+
*
|
|
164
|
+
* Deliberately the PUBLIC endpoint, never the rpc-proxy/eRPC one: that path
|
|
165
|
+
* requires an API key, and a browser cannot hold one.
|
|
166
|
+
*
|
|
167
|
+
* Optional. A chain without one degrades to display-only, which is not an
|
|
168
|
+
* error — non-EVM chains are never built as viem chains and never carry it.
|
|
169
|
+
*/
|
|
170
|
+
publicRpcUrl?: string;
|
|
131
171
|
/**
|
|
132
172
|
* True for virtual chains (e.g. HyperCore, chain id 1337) that are addressed
|
|
133
173
|
* as a distinct destination but have no RPC / providers / contracts / gas
|