@rhinestone/shared-configs 1.11.2 → 1.13.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 +49 -1
- 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 +195 -0
- package/dist/src/chains.d.ts +8 -0
- package/dist/src/chains.js +49 -1
- package/dist/src/generated/packageVersion.d.ts +1 -1
- package/dist/src/generated/packageVersion.js +1 -1
- package/dist/src/types.d.ts +41 -1
- 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",
|
|
@@ -333,7 +343,8 @@
|
|
|
333
343
|
"1inch",
|
|
334
344
|
"0x",
|
|
335
345
|
"velora",
|
|
336
|
-
"fynd"
|
|
346
|
+
"fynd",
|
|
347
|
+
"fynd-hosted"
|
|
337
348
|
],
|
|
338
349
|
"tokens": [
|
|
339
350
|
{
|
|
@@ -401,6 +412,7 @@
|
|
|
401
412
|
"addressPath": "/address/",
|
|
402
413
|
"txPath": "/tx/"
|
|
403
414
|
},
|
|
415
|
+
"iconSlug": "polygon",
|
|
404
416
|
"name": "Polygon",
|
|
405
417
|
"nativeToken": {
|
|
406
418
|
"address": "0x0000000000000000000000000000000000000000",
|
|
@@ -408,6 +420,7 @@
|
|
|
408
420
|
"symbol": "POL"
|
|
409
421
|
},
|
|
410
422
|
"network": "mainnet",
|
|
423
|
+
"publicRpcUrl": "https://polygon.drpc.org",
|
|
411
424
|
"settlementLayers": [
|
|
412
425
|
"ACROSS",
|
|
413
426
|
"ECO",
|
|
@@ -478,6 +491,7 @@
|
|
|
478
491
|
"addressPath": "/address/",
|
|
479
492
|
"txPath": "/tx/"
|
|
480
493
|
},
|
|
494
|
+
"iconSlug": "monad",
|
|
481
495
|
"name": "Monad",
|
|
482
496
|
"nativeToken": {
|
|
483
497
|
"address": "0x0000000000000000000000000000000000000000",
|
|
@@ -485,6 +499,7 @@
|
|
|
485
499
|
"symbol": "MON"
|
|
486
500
|
},
|
|
487
501
|
"network": "mainnet",
|
|
502
|
+
"publicRpcUrl": "https://rpc.monad.xyz",
|
|
488
503
|
"settlementLayers": [
|
|
489
504
|
"ACROSS",
|
|
490
505
|
"RELAY",
|
|
@@ -553,6 +568,7 @@
|
|
|
553
568
|
"addressPath": "/address/",
|
|
554
569
|
"txPath": "/tx/"
|
|
555
570
|
},
|
|
571
|
+
"iconSlug": "sonic",
|
|
556
572
|
"name": "Sonic",
|
|
557
573
|
"nativeToken": {
|
|
558
574
|
"address": "0x0000000000000000000000000000000000000000",
|
|
@@ -560,6 +576,7 @@
|
|
|
560
576
|
"symbol": "S"
|
|
561
577
|
},
|
|
562
578
|
"network": "mainnet",
|
|
579
|
+
"publicRpcUrl": "https://rpc.soniclabs.com",
|
|
563
580
|
"settlementLayers": [
|
|
564
581
|
"ECO",
|
|
565
582
|
"RELAY",
|
|
@@ -600,6 +617,7 @@
|
|
|
600
617
|
"addressPath": "/address/",
|
|
601
618
|
"txPath": "/tx/"
|
|
602
619
|
},
|
|
620
|
+
"iconSlug": "hyperevm",
|
|
603
621
|
"name": "HyperEVM",
|
|
604
622
|
"nativeToken": {
|
|
605
623
|
"address": "0x0000000000000000000000000000000000000000",
|
|
@@ -607,6 +625,7 @@
|
|
|
607
625
|
"symbol": "HYPE"
|
|
608
626
|
},
|
|
609
627
|
"network": "mainnet",
|
|
628
|
+
"publicRpcUrl": "https://rpc.hyperliquid.xyz/evm",
|
|
610
629
|
"settlementLayers": [
|
|
611
630
|
"ACROSS",
|
|
612
631
|
"ECO",
|
|
@@ -658,6 +677,7 @@
|
|
|
658
677
|
"txPath": "/explorer/tx/",
|
|
659
678
|
"url": "https://app.hyperliquid.xyz"
|
|
660
679
|
},
|
|
680
|
+
"iconSlug": "hyperevm",
|
|
661
681
|
"name": "HyperCore",
|
|
662
682
|
"nativeToken": {
|
|
663
683
|
"address": "0x0000000000000000000000000000000000000000",
|
|
@@ -697,6 +717,7 @@
|
|
|
697
717
|
"addressPath": "/address/",
|
|
698
718
|
"txPath": "/tx/"
|
|
699
719
|
},
|
|
720
|
+
"iconSlug": "soneium",
|
|
700
721
|
"name": "Soneium Mainnet",
|
|
701
722
|
"nativeToken": {
|
|
702
723
|
"address": "0x0000000000000000000000000000000000000000",
|
|
@@ -704,6 +725,7 @@
|
|
|
704
725
|
"symbol": "ETH"
|
|
705
726
|
},
|
|
706
727
|
"network": "mainnet",
|
|
728
|
+
"publicRpcUrl": "https://rpc.soneium.org",
|
|
707
729
|
"settlementLayers": [
|
|
708
730
|
"ACROSS",
|
|
709
731
|
"RELAY"
|
|
@@ -771,6 +793,7 @@
|
|
|
771
793
|
"addressPath": "/address/",
|
|
772
794
|
"txPath": "/tx/"
|
|
773
795
|
},
|
|
796
|
+
"iconSlug": "robinhood",
|
|
774
797
|
"name": "Robinhood Chain",
|
|
775
798
|
"nativeToken": {
|
|
776
799
|
"address": "0x0000000000000000000000000000000000000000",
|
|
@@ -778,6 +801,7 @@
|
|
|
778
801
|
"symbol": "ETH"
|
|
779
802
|
},
|
|
780
803
|
"network": "mainnet",
|
|
804
|
+
"publicRpcUrl": "https://rpc.mainnet.chain.robinhood.com",
|
|
781
805
|
"settlementLayers": [
|
|
782
806
|
"ACROSS",
|
|
783
807
|
"RELAY"
|
|
@@ -834,6 +858,7 @@
|
|
|
834
858
|
"addressPath": "/address/",
|
|
835
859
|
"txPath": "/tx/"
|
|
836
860
|
},
|
|
861
|
+
"iconSlug": "base",
|
|
837
862
|
"name": "Base",
|
|
838
863
|
"nativeToken": {
|
|
839
864
|
"address": "0x0000000000000000000000000000000000000000",
|
|
@@ -841,6 +866,7 @@
|
|
|
841
866
|
"symbol": "ETH"
|
|
842
867
|
},
|
|
843
868
|
"network": "mainnet",
|
|
869
|
+
"publicRpcUrl": "https://mainnet.base.org",
|
|
844
870
|
"settlementLayers": [
|
|
845
871
|
"ACROSS",
|
|
846
872
|
"ECO",
|
|
@@ -909,6 +935,7 @@
|
|
|
909
935
|
"addressPath": "/address/",
|
|
910
936
|
"txPath": "/tx/"
|
|
911
937
|
},
|
|
938
|
+
"iconSlug": "plasma",
|
|
912
939
|
"name": "Plasma",
|
|
913
940
|
"nativeToken": {
|
|
914
941
|
"address": "0x0000000000000000000000000000000000000000",
|
|
@@ -916,6 +943,7 @@
|
|
|
916
943
|
"symbol": "XPL"
|
|
917
944
|
},
|
|
918
945
|
"network": "mainnet",
|
|
946
|
+
"publicRpcUrl": "https://rpc.plasma.to",
|
|
919
947
|
"settlementLayers": [
|
|
920
948
|
"ACROSS",
|
|
921
949
|
"ECO",
|
|
@@ -958,6 +986,7 @@
|
|
|
958
986
|
"addressPath": "/address/",
|
|
959
987
|
"txPath": "/tx/"
|
|
960
988
|
},
|
|
989
|
+
"iconSlug": "plasma",
|
|
961
990
|
"name": "Plasma Testnet",
|
|
962
991
|
"nativeToken": {
|
|
963
992
|
"address": "0x0000000000000000000000000000000000000000",
|
|
@@ -965,6 +994,7 @@
|
|
|
965
994
|
"symbol": "XPL"
|
|
966
995
|
},
|
|
967
996
|
"network": "testnet",
|
|
997
|
+
"publicRpcUrl": "https://testnet-rpc.plasma.to",
|
|
968
998
|
"settlementLayers": [
|
|
969
999
|
"ECO"
|
|
970
1000
|
],
|
|
@@ -1009,6 +1039,7 @@
|
|
|
1009
1039
|
"addressPath": "/address/",
|
|
1010
1040
|
"txPath": "/tx/"
|
|
1011
1041
|
},
|
|
1042
|
+
"iconSlug": "arbitrum",
|
|
1012
1043
|
"name": "Arbitrum One",
|
|
1013
1044
|
"nativeToken": {
|
|
1014
1045
|
"address": "0x0000000000000000000000000000000000000000",
|
|
@@ -1016,6 +1047,7 @@
|
|
|
1016
1047
|
"symbol": "ETH"
|
|
1017
1048
|
},
|
|
1018
1049
|
"network": "mainnet",
|
|
1050
|
+
"publicRpcUrl": "https://arb1.arbitrum.io/rpc",
|
|
1019
1051
|
"settlementLayers": [
|
|
1020
1052
|
"ACROSS",
|
|
1021
1053
|
"ECO",
|
|
@@ -1099,6 +1131,7 @@
|
|
|
1099
1131
|
"addressPath": "/address/",
|
|
1100
1132
|
"txPath": "/tx/"
|
|
1101
1133
|
},
|
|
1134
|
+
"iconSlug": "avalanche",
|
|
1102
1135
|
"name": "Avalanche",
|
|
1103
1136
|
"nativeToken": {
|
|
1104
1137
|
"address": "0x0000000000000000000000000000000000000000",
|
|
@@ -1106,6 +1139,7 @@
|
|
|
1106
1139
|
"symbol": "AVAX"
|
|
1107
1140
|
},
|
|
1108
1141
|
"network": "mainnet",
|
|
1142
|
+
"publicRpcUrl": "https://api.avax.network/ext/bc/C/rpc",
|
|
1109
1143
|
"settlementLayers": [
|
|
1110
1144
|
"ACROSS",
|
|
1111
1145
|
"RELAY",
|
|
@@ -1182,6 +1216,7 @@
|
|
|
1182
1216
|
"addressPath": "/address/",
|
|
1183
1217
|
"txPath": "/tx/"
|
|
1184
1218
|
},
|
|
1219
|
+
"iconSlug": "base",
|
|
1185
1220
|
"name": "Base Sepolia",
|
|
1186
1221
|
"nativeToken": {
|
|
1187
1222
|
"address": "0x0000000000000000000000000000000000000000",
|
|
@@ -1189,6 +1224,7 @@
|
|
|
1189
1224
|
"symbol": "ETH"
|
|
1190
1225
|
},
|
|
1191
1226
|
"network": "testnet",
|
|
1227
|
+
"publicRpcUrl": "https://sepolia.base.org",
|
|
1192
1228
|
"settlementLayers": [
|
|
1193
1229
|
"ACROSS",
|
|
1194
1230
|
"ECO",
|
|
@@ -1263,6 +1299,7 @@
|
|
|
1263
1299
|
"addressPath": "/address/",
|
|
1264
1300
|
"txPath": "/tx/"
|
|
1265
1301
|
},
|
|
1302
|
+
"iconSlug": "arbitrum",
|
|
1266
1303
|
"name": "Arbitrum Sepolia",
|
|
1267
1304
|
"nativeToken": {
|
|
1268
1305
|
"address": "0x0000000000000000000000000000000000000000",
|
|
@@ -1270,6 +1307,7 @@
|
|
|
1270
1307
|
"symbol": "ETH"
|
|
1271
1308
|
},
|
|
1272
1309
|
"network": "testnet",
|
|
1310
|
+
"publicRpcUrl": "https://sepolia-rollup.arbitrum.io/rpc",
|
|
1273
1311
|
"settlementLayers": [
|
|
1274
1312
|
"ACROSS",
|
|
1275
1313
|
"ECO",
|
|
@@ -1330,6 +1368,7 @@
|
|
|
1330
1368
|
"addressPath": "/address/",
|
|
1331
1369
|
"txPath": "/tx/"
|
|
1332
1370
|
},
|
|
1371
|
+
"iconSlug": "katana",
|
|
1333
1372
|
"name": "Katana",
|
|
1334
1373
|
"nativeToken": {
|
|
1335
1374
|
"address": "0x0000000000000000000000000000000000000000",
|
|
@@ -1337,6 +1376,7 @@
|
|
|
1337
1376
|
"symbol": "ETH"
|
|
1338
1377
|
},
|
|
1339
1378
|
"network": "mainnet",
|
|
1379
|
+
"publicRpcUrl": "https://rpc.katana.network",
|
|
1340
1380
|
"settlementLayers": [
|
|
1341
1381
|
"RELAY",
|
|
1342
1382
|
"RHINO"
|
|
@@ -1394,6 +1434,7 @@
|
|
|
1394
1434
|
"txPath": "/explorer/tx/",
|
|
1395
1435
|
"url": "https://app.hyperliquid.xyz"
|
|
1396
1436
|
},
|
|
1437
|
+
"iconSlug": "hyperevm",
|
|
1397
1438
|
"name": "HyperCore Spot",
|
|
1398
1439
|
"nativeToken": {
|
|
1399
1440
|
"address": "0x0000000000000000000000000000000000000000",
|
|
@@ -1433,6 +1474,7 @@
|
|
|
1433
1474
|
"txPath": "/explorer/tx/",
|
|
1434
1475
|
"url": "https://app.hyperliquid.xyz"
|
|
1435
1476
|
},
|
|
1477
|
+
"iconSlug": "hyperevm",
|
|
1436
1478
|
"name": "HyperCore Perp",
|
|
1437
1479
|
"nativeToken": {
|
|
1438
1480
|
"address": "0x0000000000000000000000000000000000000000",
|
|
@@ -1472,6 +1514,7 @@
|
|
|
1472
1514
|
"addressPath": "/address/",
|
|
1473
1515
|
"txPath": "/tx/"
|
|
1474
1516
|
},
|
|
1517
|
+
"iconSlug": "mainnet",
|
|
1475
1518
|
"name": "Sepolia",
|
|
1476
1519
|
"nativeToken": {
|
|
1477
1520
|
"address": "0x0000000000000000000000000000000000000000",
|
|
@@ -1479,6 +1522,7 @@
|
|
|
1479
1522
|
"symbol": "ETH"
|
|
1480
1523
|
},
|
|
1481
1524
|
"network": "testnet",
|
|
1525
|
+
"publicRpcUrl": "https://11155111.rpc.thirdweb.com",
|
|
1482
1526
|
"settlementLayers": [
|
|
1483
1527
|
"ACROSS",
|
|
1484
1528
|
"ECO",
|
|
@@ -1549,6 +1593,7 @@
|
|
|
1549
1593
|
"addressPath": "/address/",
|
|
1550
1594
|
"txPath": "/tx/"
|
|
1551
1595
|
},
|
|
1596
|
+
"iconSlug": "optimism",
|
|
1552
1597
|
"name": "OP Sepolia",
|
|
1553
1598
|
"nativeToken": {
|
|
1554
1599
|
"address": "0x0000000000000000000000000000000000000000",
|
|
@@ -1556,6 +1601,7 @@
|
|
|
1556
1601
|
"symbol": "ETH"
|
|
1557
1602
|
},
|
|
1558
1603
|
"network": "testnet",
|
|
1604
|
+
"publicRpcUrl": "https://sepolia.optimism.io",
|
|
1559
1605
|
"settlementLayers": [
|
|
1560
1606
|
"ACROSS",
|
|
1561
1607
|
"ECO",
|
|
@@ -1616,6 +1662,7 @@
|
|
|
1616
1662
|
"txPath": "/#/transaction/",
|
|
1617
1663
|
"url": "https://tronscan.org"
|
|
1618
1664
|
},
|
|
1665
|
+
"iconSlug": "tron",
|
|
1619
1666
|
"name": "Tron",
|
|
1620
1667
|
"nativeToken": {
|
|
1621
1668
|
"address": "0x0000000000000000000000000000000000000000",
|
|
@@ -1662,6 +1709,7 @@
|
|
|
1662
1709
|
"txPath": "/tx/",
|
|
1663
1710
|
"url": "https://solscan.io"
|
|
1664
1711
|
},
|
|
1712
|
+
"iconSlug": "solana",
|
|
1665
1713
|
"name": "Solana",
|
|
1666
1714
|
"nativeToken": {
|
|
1667
1715
|
"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;
|
|
@@ -65,6 +67,7 @@ const SWAP_QUOTERS = new Set([
|
|
|
65
67
|
'velora',
|
|
66
68
|
'kyberswap',
|
|
67
69
|
'fynd',
|
|
70
|
+
'fynd-hosted',
|
|
68
71
|
]);
|
|
69
72
|
const VM_TYPES = new Set(['evm', 'svm', 'tvm']);
|
|
70
73
|
const PEG_GROUPS = new Set(['USD', 'ETH']);
|
|
@@ -141,6 +144,18 @@ function structuralProblems(chain) {
|
|
|
141
144
|
else if (chain.explorer !== null && !isChainExplorer(chain.explorer)) {
|
|
142
145
|
problems.push(`explorer ${JSON.stringify(chain.explorer)} is not null or { url (no trailing slash), addressPath, txPath (both slash-wrapped) }`);
|
|
143
146
|
}
|
|
147
|
+
// Optional, like publicRpcUrl below: a chain with no mark renders as a
|
|
148
|
+
// coloured initial chip. Only the shape is checked here — whether a chain is
|
|
149
|
+
// ALLOWED to have none is validateChainIconSlugs' job.
|
|
150
|
+
if (!isAbsentOr(chain.iconSlug, isIconSlug)) {
|
|
151
|
+
problems.push(`iconSlug ${JSON.stringify(chain.iconSlug)} is not a bare filename stem (lowercase letters, digits and hyphens)`);
|
|
152
|
+
}
|
|
153
|
+
// Optional, unlike the four above: a chain with no known public endpoint is
|
|
154
|
+
// display-only, which is a real answer. Only its shape is checked, and only
|
|
155
|
+
// when present — see classifyChainPublicRpcUrls for why it must be https.
|
|
156
|
+
if (!isAbsentOr(chain.publicRpcUrl, isPublicRpcUrl)) {
|
|
157
|
+
problems.push(`publicRpcUrl ${JSON.stringify(chain.publicRpcUrl)} is not an https:// URL`);
|
|
158
|
+
}
|
|
144
159
|
if (!isAbsentOr(chain.virtual, (v) => typeof v === 'boolean')) {
|
|
145
160
|
problems.push('virtual is not a boolean');
|
|
146
161
|
}
|
|
@@ -499,6 +514,186 @@ function classifyChainExplorers(chains, networks) {
|
|
|
499
514
|
}
|
|
500
515
|
return { chains: stamped, issues };
|
|
501
516
|
}
|
|
517
|
+
/**
|
|
518
|
+
* A bare filename stem: no directory, no extension, no scheme.
|
|
519
|
+
*
|
|
520
|
+
* The shape is what keeps the field a chain fact rather than a URL. A value
|
|
521
|
+
* carrying `https://`, a `chains/` prefix or a `.svg` suffix would have baked
|
|
522
|
+
* this CDN's layout into a signed artifact every service reads — the exact
|
|
523
|
+
* coupling the field is a slug to avoid.
|
|
524
|
+
*/
|
|
525
|
+
const ICON_SLUG = /^[a-z0-9][a-z0-9-]*$/;
|
|
526
|
+
const isIconSlug = (value) => typeof value === 'string' && ICON_SLUG.test(value);
|
|
527
|
+
/**
|
|
528
|
+
* Chain ids that are allowed to ship with no `iconSlug`.
|
|
529
|
+
*
|
|
530
|
+
* An entry here is a decision that this chain renders as a coloured initial
|
|
531
|
+
* chip. Every id needs a reason next to it, and the reason should stop being
|
|
532
|
+
* true eventually — when a mark is published, the entry goes.
|
|
533
|
+
*/
|
|
534
|
+
// Empty, and worth keeping that way: every chain in the registry has a mark.
|
|
535
|
+
// An entry here permanently excuses a chain from the check below, so it is for
|
|
536
|
+
// a chain whose mark genuinely cannot be obtained — not for one nobody has got
|
|
537
|
+
// round to yet.
|
|
538
|
+
const CHAINS_WITHOUT_ICON = new Map();
|
|
539
|
+
/**
|
|
540
|
+
* Fails the build for a chain that has neither a mark nor an explicit exemption.
|
|
541
|
+
*
|
|
542
|
+
* Why an error and not a warning: `iconSlug` is authored by hand, so the failure
|
|
543
|
+
* mode of a new chain is silence — nobody forgets a field they were never
|
|
544
|
+
* prompted for, and the result is a blank tile in production that no test, type
|
|
545
|
+
* or log names. The gate turns that into a build failure at the moment someone
|
|
546
|
+
* is already editing the registry, which is the only point where the decision is
|
|
547
|
+
* cheap. CHAINS_WITHOUT_ICON is how "this chain genuinely has no mark" is said
|
|
548
|
+
* out loud, in code review, rather than being indistinguishable from an oversight.
|
|
549
|
+
*
|
|
550
|
+
* Deliberately offline. Whether `chains/<slug>.svg` actually exists on the CDN is
|
|
551
|
+
* not checked and must not be: the build would then fail on a CDN blip or in a
|
|
552
|
+
* sandboxed CI runner, which is a far more frequent failure than a typo'd slug
|
|
553
|
+
* and produces a red build with nothing wrong in the diff. The cost of a bad slug
|
|
554
|
+
* is one missing image; the cost of a network-dependent build gate is every build.
|
|
555
|
+
*/
|
|
556
|
+
function validateChainIconSlugs(chains,
|
|
557
|
+
// Injectable so the exemption paths stay covered while the real map is empty.
|
|
558
|
+
// Testing them against a live entry would mean keeping a chain un-iconed just
|
|
559
|
+
// to have something to assert on.
|
|
560
|
+
exemptions = CHAINS_WITHOUT_ICON) {
|
|
561
|
+
const issues = [];
|
|
562
|
+
for (const [chainId, chain] of Object.entries(chains)) {
|
|
563
|
+
const chainName = isNonEmptyString(chain?.name)
|
|
564
|
+
? chain.name
|
|
565
|
+
: '<unnamed>';
|
|
566
|
+
const slug = chain?.iconSlug;
|
|
567
|
+
const exemption = exemptions.get(chainId);
|
|
568
|
+
if (slug === undefined) {
|
|
569
|
+
if (exemption === undefined) {
|
|
570
|
+
issues.push({
|
|
571
|
+
severity: 'error',
|
|
572
|
+
chainId,
|
|
573
|
+
chainName,
|
|
574
|
+
message: 'has no iconSlug — add the filename stem of its mark in rhinestonewtf/s3-public ' +
|
|
575
|
+
"(`chains/<slug>.svg`) to this chain's chainMeta entry in " +
|
|
576
|
+
'config/shared-configs.jsonnet, or, if it genuinely has none, add its id to ' +
|
|
577
|
+
'CHAINS_WITHOUT_ICON in scripts/validation.ts with the reason. It is not ' +
|
|
578
|
+
'defaulted: a chain with no mark renders as a blank tile with nothing logged',
|
|
579
|
+
});
|
|
580
|
+
}
|
|
581
|
+
continue;
|
|
582
|
+
}
|
|
583
|
+
// A stale exemption is worse than no exemption: it permanently excuses the
|
|
584
|
+
// one chain it names, so the next person to drop the slug gets no warning.
|
|
585
|
+
if (exemption !== undefined) {
|
|
586
|
+
issues.push({
|
|
587
|
+
severity: 'error',
|
|
588
|
+
chainId,
|
|
589
|
+
chainName,
|
|
590
|
+
message: `has an iconSlug ${JSON.stringify(slug)} but is still listed in CHAINS_WITHOUT_ICON (${exemption}) — remove the exemption`,
|
|
591
|
+
});
|
|
592
|
+
}
|
|
593
|
+
// Shape is also checked structurally; repeated here so this function is a
|
|
594
|
+
// complete gate on its own and a malformed slug cannot pass as "present".
|
|
595
|
+
if (!isIconSlug(slug)) {
|
|
596
|
+
issues.push({
|
|
597
|
+
severity: 'error',
|
|
598
|
+
chainId,
|
|
599
|
+
chainName,
|
|
600
|
+
message: `declares a malformed iconSlug ${JSON.stringify(slug)} — expected a bare filename stem, with no directory, extension or scheme`,
|
|
601
|
+
});
|
|
602
|
+
}
|
|
603
|
+
}
|
|
604
|
+
return issues;
|
|
605
|
+
}
|
|
606
|
+
/**
|
|
607
|
+
* `https://` only, and deliberately so. The field exists for a browser, which
|
|
608
|
+
* cannot hold an API key and — on an https page — cannot call an `http://`
|
|
609
|
+
* endpoint at all: the request is blocked as mixed content. Publishing one
|
|
610
|
+
* would hand consumers a URL that never connects, which is strictly worse than
|
|
611
|
+
* publishing nothing and letting the chain fall back to display-only.
|
|
612
|
+
*/
|
|
613
|
+
const isPublicRpcUrl = (value) => typeof value === 'string' && /^https:\/\/\S+$/.test(value);
|
|
614
|
+
/**
|
|
615
|
+
* Stamps every chain with a public JSON-RPC endpoint, derived from the chain's
|
|
616
|
+
* viem definition where it has one and authored in the jsonnet where it does
|
|
617
|
+
* not.
|
|
618
|
+
*
|
|
619
|
+
* Why this exists: a browser consumer building a viem `Chain` with
|
|
620
|
+
* `defineChain` — for a chain newer than its pinned viem — already gets id,
|
|
621
|
+
* name, native currency, `explorer` and `stack` from this registry. The RPC
|
|
622
|
+
* endpoint was the only missing piece, and it is the piece that decides whether
|
|
623
|
+
* a newly added chain is a first-class wallet-connect target or display-only.
|
|
624
|
+
* It is the PUBLIC endpoint on purpose: the rpc-proxy path needs an API key,
|
|
625
|
+
* which is the one thing a browser cannot have.
|
|
626
|
+
*
|
|
627
|
+
* Resolved by the entry's declared `viemChain` **name**, never by chain id, for
|
|
628
|
+
* the same reason as `classifyChainExplorers` — only worse here, because the
|
|
629
|
+
* consequence is a live endpoint for the wrong chain rather than a dead link.
|
|
630
|
+
* viem 2.55 has 23 chain ids claimed by more than one export: id 999 is
|
|
631
|
+
* `hyperEvm`, `hyperliquid`, `wanchainTestnet` and `zoraTestnet`, so an id
|
|
632
|
+
* lookup for HyperEVM can hand out a Zora testnet RPC.
|
|
633
|
+
*
|
|
634
|
+
* A missing endpoint is not an error — it resolves to the field being absent,
|
|
635
|
+
* and a consumer treats that chain as display-only. It is worth a warning for a
|
|
636
|
+
* real EVM chain, where the usual cause is a chain viem does not ship yet; for
|
|
637
|
+
* a non-EVM or virtual chain there is nothing to warn about, since none of them
|
|
638
|
+
* is ever built as a viem chain.
|
|
639
|
+
*/
|
|
640
|
+
function classifyChainPublicRpcUrls(chains, networks) {
|
|
641
|
+
const issues = [];
|
|
642
|
+
const stamped = {};
|
|
643
|
+
const entriesById = new Map();
|
|
644
|
+
for (const entry of [...networks.mainnets, ...networks.testnets]) {
|
|
645
|
+
entriesById.set(String(entry.id), entry);
|
|
646
|
+
}
|
|
647
|
+
for (const [chainId, chain] of Object.entries(chains)) {
|
|
648
|
+
const chainName = isNonEmptyString(chain?.name)
|
|
649
|
+
? chain.name
|
|
650
|
+
: '<unnamed>';
|
|
651
|
+
const authored = chain?.publicRpcUrl;
|
|
652
|
+
// Authoring wins outright, as with `explorer`: the jsonnet is the only place
|
|
653
|
+
// an endpoint can be given for a chain viem does not ship, or a better one
|
|
654
|
+
// preferred over viem's default.
|
|
655
|
+
if (authored !== undefined) {
|
|
656
|
+
if (!isPublicRpcUrl(authored)) {
|
|
657
|
+
issues.push({
|
|
658
|
+
severity: 'error',
|
|
659
|
+
chainId,
|
|
660
|
+
chainName,
|
|
661
|
+
message: `declares a malformed publicRpcUrl ${JSON.stringify(authored)} — expected an https:// URL`,
|
|
662
|
+
});
|
|
663
|
+
stamped[chainId] = withSortedKeys({ ...chain });
|
|
664
|
+
continue;
|
|
665
|
+
}
|
|
666
|
+
stamped[chainId] = withSortedKeys({ ...chain, publicRpcUrl: authored });
|
|
667
|
+
continue;
|
|
668
|
+
}
|
|
669
|
+
const viemChainName = entriesById.get(chainId)?.viemChain;
|
|
670
|
+
const url = viemChainName
|
|
671
|
+
? viemChains[viemChainName]?.rpcUrls?.default?.http?.[0]
|
|
672
|
+
: undefined;
|
|
673
|
+
// viem's own value carries a trailing slash on some chains (`unichain`) and
|
|
674
|
+
// not others. Both address the same endpoint, so it is normalised here
|
|
675
|
+
// rather than published as an inconsistency every consumer has to absorb.
|
|
676
|
+
const normalised = typeof url === 'string' ? url.replace(/\/+$/, '') : undefined;
|
|
677
|
+
if (!isPublicRpcUrl(normalised)) {
|
|
678
|
+
// A chain that is never synthesised as a viem chain has nothing to be
|
|
679
|
+
// missing — only a real EVM chain is worth reporting.
|
|
680
|
+
if (chain.vmType === 'evm' && chain.virtual !== true) {
|
|
681
|
+
issues.push({
|
|
682
|
+
severity: 'warning',
|
|
683
|
+
chainId,
|
|
684
|
+
chainName,
|
|
685
|
+
message: viemChainName
|
|
686
|
+
? `viem chain "${viemChainName}" ships no usable https rpc url — the chain stays display-only for consumers that synthesise it until one is authored`
|
|
687
|
+
: 'has no viemChain to derive a public rpc url from and authors none — the chain stays display-only for consumers that synthesise it',
|
|
688
|
+
});
|
|
689
|
+
}
|
|
690
|
+
stamped[chainId] = withSortedKeys({ ...chain });
|
|
691
|
+
continue;
|
|
692
|
+
}
|
|
693
|
+
stamped[chainId] = withSortedKeys({ ...chain, publicRpcUrl: normalised });
|
|
694
|
+
}
|
|
695
|
+
return { chains: stamped, issues };
|
|
696
|
+
}
|
|
502
697
|
const isChainStack = (v) => typeof v === 'string' && STACKS.has(v);
|
|
503
698
|
const STACK_FORMATTERS = [
|
|
504
699
|
['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",
|
|
@@ -353,7 +363,8 @@ const chains = {
|
|
|
353
363
|
"1inch",
|
|
354
364
|
"0x",
|
|
355
365
|
"velora",
|
|
356
|
-
"fynd"
|
|
366
|
+
"fynd",
|
|
367
|
+
"fynd-hosted"
|
|
357
368
|
],
|
|
358
369
|
"tokens": [
|
|
359
370
|
{
|
|
@@ -425,6 +436,7 @@ const chains = {
|
|
|
425
436
|
"addressPath": "/address/",
|
|
426
437
|
"txPath": "/tx/"
|
|
427
438
|
},
|
|
439
|
+
"iconSlug": "polygon",
|
|
428
440
|
"name": "Polygon",
|
|
429
441
|
"nativeToken": {
|
|
430
442
|
"address": "0x0000000000000000000000000000000000000000",
|
|
@@ -432,6 +444,7 @@ const chains = {
|
|
|
432
444
|
"symbol": "POL"
|
|
433
445
|
},
|
|
434
446
|
"network": "mainnet",
|
|
447
|
+
"publicRpcUrl": "https://polygon.drpc.org",
|
|
435
448
|
"settlementLayers": [
|
|
436
449
|
"ACROSS",
|
|
437
450
|
"ECO",
|
|
@@ -506,6 +519,7 @@ const chains = {
|
|
|
506
519
|
"addressPath": "/address/",
|
|
507
520
|
"txPath": "/tx/"
|
|
508
521
|
},
|
|
522
|
+
"iconSlug": "monad",
|
|
509
523
|
"name": "Monad",
|
|
510
524
|
"nativeToken": {
|
|
511
525
|
"address": "0x0000000000000000000000000000000000000000",
|
|
@@ -513,6 +527,7 @@ const chains = {
|
|
|
513
527
|
"symbol": "MON"
|
|
514
528
|
},
|
|
515
529
|
"network": "mainnet",
|
|
530
|
+
"publicRpcUrl": "https://rpc.monad.xyz",
|
|
516
531
|
"settlementLayers": [
|
|
517
532
|
"ACROSS",
|
|
518
533
|
"RELAY",
|
|
@@ -585,6 +600,7 @@ const chains = {
|
|
|
585
600
|
"addressPath": "/address/",
|
|
586
601
|
"txPath": "/tx/"
|
|
587
602
|
},
|
|
603
|
+
"iconSlug": "sonic",
|
|
588
604
|
"name": "Sonic",
|
|
589
605
|
"nativeToken": {
|
|
590
606
|
"address": "0x0000000000000000000000000000000000000000",
|
|
@@ -592,6 +608,7 @@ const chains = {
|
|
|
592
608
|
"symbol": "S"
|
|
593
609
|
},
|
|
594
610
|
"network": "mainnet",
|
|
611
|
+
"publicRpcUrl": "https://rpc.soniclabs.com",
|
|
595
612
|
"settlementLayers": [
|
|
596
613
|
"ECO",
|
|
597
614
|
"RELAY",
|
|
@@ -636,6 +653,7 @@ const chains = {
|
|
|
636
653
|
"addressPath": "/address/",
|
|
637
654
|
"txPath": "/tx/"
|
|
638
655
|
},
|
|
656
|
+
"iconSlug": "hyperevm",
|
|
639
657
|
"name": "HyperEVM",
|
|
640
658
|
"nativeToken": {
|
|
641
659
|
"address": "0x0000000000000000000000000000000000000000",
|
|
@@ -643,6 +661,7 @@ const chains = {
|
|
|
643
661
|
"symbol": "HYPE"
|
|
644
662
|
},
|
|
645
663
|
"network": "mainnet",
|
|
664
|
+
"publicRpcUrl": "https://rpc.hyperliquid.xyz/evm",
|
|
646
665
|
"settlementLayers": [
|
|
647
666
|
"ACROSS",
|
|
648
667
|
"ECO",
|
|
@@ -699,6 +718,7 @@ const chains = {
|
|
|
699
718
|
"txPath": "/explorer/tx/",
|
|
700
719
|
"url": "https://app.hyperliquid.xyz"
|
|
701
720
|
},
|
|
721
|
+
"iconSlug": "hyperevm",
|
|
702
722
|
"name": "HyperCore",
|
|
703
723
|
"nativeToken": {
|
|
704
724
|
"address": "0x0000000000000000000000000000000000000000",
|
|
@@ -739,6 +759,7 @@ const chains = {
|
|
|
739
759
|
"addressPath": "/address/",
|
|
740
760
|
"txPath": "/tx/"
|
|
741
761
|
},
|
|
762
|
+
"iconSlug": "soneium",
|
|
742
763
|
"name": "Soneium Mainnet",
|
|
743
764
|
"nativeToken": {
|
|
744
765
|
"address": "0x0000000000000000000000000000000000000000",
|
|
@@ -746,6 +767,7 @@ const chains = {
|
|
|
746
767
|
"symbol": "ETH"
|
|
747
768
|
},
|
|
748
769
|
"network": "mainnet",
|
|
770
|
+
"publicRpcUrl": "https://rpc.soneium.org",
|
|
749
771
|
"settlementLayers": [
|
|
750
772
|
"ACROSS",
|
|
751
773
|
"RELAY"
|
|
@@ -817,6 +839,7 @@ const chains = {
|
|
|
817
839
|
"addressPath": "/address/",
|
|
818
840
|
"txPath": "/tx/"
|
|
819
841
|
},
|
|
842
|
+
"iconSlug": "robinhood",
|
|
820
843
|
"name": "Robinhood Chain",
|
|
821
844
|
"nativeToken": {
|
|
822
845
|
"address": "0x0000000000000000000000000000000000000000",
|
|
@@ -824,6 +847,7 @@ const chains = {
|
|
|
824
847
|
"symbol": "ETH"
|
|
825
848
|
},
|
|
826
849
|
"network": "mainnet",
|
|
850
|
+
"publicRpcUrl": "https://rpc.mainnet.chain.robinhood.com",
|
|
827
851
|
"settlementLayers": [
|
|
828
852
|
"ACROSS",
|
|
829
853
|
"RELAY"
|
|
@@ -884,6 +908,7 @@ const chains = {
|
|
|
884
908
|
"addressPath": "/address/",
|
|
885
909
|
"txPath": "/tx/"
|
|
886
910
|
},
|
|
911
|
+
"iconSlug": "base",
|
|
887
912
|
"name": "Base",
|
|
888
913
|
"nativeToken": {
|
|
889
914
|
"address": "0x0000000000000000000000000000000000000000",
|
|
@@ -891,6 +916,7 @@ const chains = {
|
|
|
891
916
|
"symbol": "ETH"
|
|
892
917
|
},
|
|
893
918
|
"network": "mainnet",
|
|
919
|
+
"publicRpcUrl": "https://mainnet.base.org",
|
|
894
920
|
"settlementLayers": [
|
|
895
921
|
"ACROSS",
|
|
896
922
|
"ECO",
|
|
@@ -963,6 +989,7 @@ const chains = {
|
|
|
963
989
|
"addressPath": "/address/",
|
|
964
990
|
"txPath": "/tx/"
|
|
965
991
|
},
|
|
992
|
+
"iconSlug": "plasma",
|
|
966
993
|
"name": "Plasma",
|
|
967
994
|
"nativeToken": {
|
|
968
995
|
"address": "0x0000000000000000000000000000000000000000",
|
|
@@ -970,6 +997,7 @@ const chains = {
|
|
|
970
997
|
"symbol": "XPL"
|
|
971
998
|
},
|
|
972
999
|
"network": "mainnet",
|
|
1000
|
+
"publicRpcUrl": "https://rpc.plasma.to",
|
|
973
1001
|
"settlementLayers": [
|
|
974
1002
|
"ACROSS",
|
|
975
1003
|
"ECO",
|
|
@@ -1017,6 +1045,7 @@ const chains = {
|
|
|
1017
1045
|
"addressPath": "/address/",
|
|
1018
1046
|
"txPath": "/tx/"
|
|
1019
1047
|
},
|
|
1048
|
+
"iconSlug": "plasma",
|
|
1020
1049
|
"name": "Plasma Testnet",
|
|
1021
1050
|
"nativeToken": {
|
|
1022
1051
|
"address": "0x0000000000000000000000000000000000000000",
|
|
@@ -1024,6 +1053,7 @@ const chains = {
|
|
|
1024
1053
|
"symbol": "XPL"
|
|
1025
1054
|
},
|
|
1026
1055
|
"network": "testnet",
|
|
1056
|
+
"publicRpcUrl": "https://testnet-rpc.plasma.to",
|
|
1027
1057
|
"settlementLayers": [
|
|
1028
1058
|
"ECO"
|
|
1029
1059
|
],
|
|
@@ -1073,6 +1103,7 @@ const chains = {
|
|
|
1073
1103
|
"addressPath": "/address/",
|
|
1074
1104
|
"txPath": "/tx/"
|
|
1075
1105
|
},
|
|
1106
|
+
"iconSlug": "arbitrum",
|
|
1076
1107
|
"name": "Arbitrum One",
|
|
1077
1108
|
"nativeToken": {
|
|
1078
1109
|
"address": "0x0000000000000000000000000000000000000000",
|
|
@@ -1080,6 +1111,7 @@ const chains = {
|
|
|
1080
1111
|
"symbol": "ETH"
|
|
1081
1112
|
},
|
|
1082
1113
|
"network": "mainnet",
|
|
1114
|
+
"publicRpcUrl": "https://arb1.arbitrum.io/rpc",
|
|
1083
1115
|
"settlementLayers": [
|
|
1084
1116
|
"ACROSS",
|
|
1085
1117
|
"ECO",
|
|
@@ -1167,6 +1199,7 @@ const chains = {
|
|
|
1167
1199
|
"addressPath": "/address/",
|
|
1168
1200
|
"txPath": "/tx/"
|
|
1169
1201
|
},
|
|
1202
|
+
"iconSlug": "avalanche",
|
|
1170
1203
|
"name": "Avalanche",
|
|
1171
1204
|
"nativeToken": {
|
|
1172
1205
|
"address": "0x0000000000000000000000000000000000000000",
|
|
@@ -1174,6 +1207,7 @@ const chains = {
|
|
|
1174
1207
|
"symbol": "AVAX"
|
|
1175
1208
|
},
|
|
1176
1209
|
"network": "mainnet",
|
|
1210
|
+
"publicRpcUrl": "https://api.avax.network/ext/bc/C/rpc",
|
|
1177
1211
|
"settlementLayers": [
|
|
1178
1212
|
"ACROSS",
|
|
1179
1213
|
"RELAY",
|
|
@@ -1254,6 +1288,7 @@ const chains = {
|
|
|
1254
1288
|
"addressPath": "/address/",
|
|
1255
1289
|
"txPath": "/tx/"
|
|
1256
1290
|
},
|
|
1291
|
+
"iconSlug": "base",
|
|
1257
1292
|
"name": "Base Sepolia",
|
|
1258
1293
|
"nativeToken": {
|
|
1259
1294
|
"address": "0x0000000000000000000000000000000000000000",
|
|
@@ -1261,6 +1296,7 @@ const chains = {
|
|
|
1261
1296
|
"symbol": "ETH"
|
|
1262
1297
|
},
|
|
1263
1298
|
"network": "testnet",
|
|
1299
|
+
"publicRpcUrl": "https://sepolia.base.org",
|
|
1264
1300
|
"settlementLayers": [
|
|
1265
1301
|
"ACROSS",
|
|
1266
1302
|
"ECO",
|
|
@@ -1339,6 +1375,7 @@ const chains = {
|
|
|
1339
1375
|
"addressPath": "/address/",
|
|
1340
1376
|
"txPath": "/tx/"
|
|
1341
1377
|
},
|
|
1378
|
+
"iconSlug": "arbitrum",
|
|
1342
1379
|
"name": "Arbitrum Sepolia",
|
|
1343
1380
|
"nativeToken": {
|
|
1344
1381
|
"address": "0x0000000000000000000000000000000000000000",
|
|
@@ -1346,6 +1383,7 @@ const chains = {
|
|
|
1346
1383
|
"symbol": "ETH"
|
|
1347
1384
|
},
|
|
1348
1385
|
"network": "testnet",
|
|
1386
|
+
"publicRpcUrl": "https://sepolia-rollup.arbitrum.io/rpc",
|
|
1349
1387
|
"settlementLayers": [
|
|
1350
1388
|
"ACROSS",
|
|
1351
1389
|
"ECO",
|
|
@@ -1410,6 +1448,7 @@ const chains = {
|
|
|
1410
1448
|
"addressPath": "/address/",
|
|
1411
1449
|
"txPath": "/tx/"
|
|
1412
1450
|
},
|
|
1451
|
+
"iconSlug": "katana",
|
|
1413
1452
|
"name": "Katana",
|
|
1414
1453
|
"nativeToken": {
|
|
1415
1454
|
"address": "0x0000000000000000000000000000000000000000",
|
|
@@ -1417,6 +1456,7 @@ const chains = {
|
|
|
1417
1456
|
"symbol": "ETH"
|
|
1418
1457
|
},
|
|
1419
1458
|
"network": "mainnet",
|
|
1459
|
+
"publicRpcUrl": "https://rpc.katana.network",
|
|
1420
1460
|
"settlementLayers": [
|
|
1421
1461
|
"RELAY",
|
|
1422
1462
|
"RHINO"
|
|
@@ -1478,6 +1518,7 @@ const chains = {
|
|
|
1478
1518
|
"txPath": "/explorer/tx/",
|
|
1479
1519
|
"url": "https://app.hyperliquid.xyz"
|
|
1480
1520
|
},
|
|
1521
|
+
"iconSlug": "hyperevm",
|
|
1481
1522
|
"name": "HyperCore Spot",
|
|
1482
1523
|
"nativeToken": {
|
|
1483
1524
|
"address": "0x0000000000000000000000000000000000000000",
|
|
@@ -1518,6 +1559,7 @@ const chains = {
|
|
|
1518
1559
|
"txPath": "/explorer/tx/",
|
|
1519
1560
|
"url": "https://app.hyperliquid.xyz"
|
|
1520
1561
|
},
|
|
1562
|
+
"iconSlug": "hyperevm",
|
|
1521
1563
|
"name": "HyperCore Perp",
|
|
1522
1564
|
"nativeToken": {
|
|
1523
1565
|
"address": "0x0000000000000000000000000000000000000000",
|
|
@@ -1558,6 +1600,7 @@ const chains = {
|
|
|
1558
1600
|
"addressPath": "/address/",
|
|
1559
1601
|
"txPath": "/tx/"
|
|
1560
1602
|
},
|
|
1603
|
+
"iconSlug": "mainnet",
|
|
1561
1604
|
"name": "Sepolia",
|
|
1562
1605
|
"nativeToken": {
|
|
1563
1606
|
"address": "0x0000000000000000000000000000000000000000",
|
|
@@ -1565,6 +1608,7 @@ const chains = {
|
|
|
1565
1608
|
"symbol": "ETH"
|
|
1566
1609
|
},
|
|
1567
1610
|
"network": "testnet",
|
|
1611
|
+
"publicRpcUrl": "https://11155111.rpc.thirdweb.com",
|
|
1568
1612
|
"settlementLayers": [
|
|
1569
1613
|
"ACROSS",
|
|
1570
1614
|
"ECO",
|
|
@@ -1639,6 +1683,7 @@ const chains = {
|
|
|
1639
1683
|
"addressPath": "/address/",
|
|
1640
1684
|
"txPath": "/tx/"
|
|
1641
1685
|
},
|
|
1686
|
+
"iconSlug": "optimism",
|
|
1642
1687
|
"name": "OP Sepolia",
|
|
1643
1688
|
"nativeToken": {
|
|
1644
1689
|
"address": "0x0000000000000000000000000000000000000000",
|
|
@@ -1646,6 +1691,7 @@ const chains = {
|
|
|
1646
1691
|
"symbol": "ETH"
|
|
1647
1692
|
},
|
|
1648
1693
|
"network": "testnet",
|
|
1694
|
+
"publicRpcUrl": "https://sepolia.optimism.io",
|
|
1649
1695
|
"settlementLayers": [
|
|
1650
1696
|
"ACROSS",
|
|
1651
1697
|
"ECO",
|
|
@@ -1710,6 +1756,7 @@ const chains = {
|
|
|
1710
1756
|
"txPath": "/#/transaction/",
|
|
1711
1757
|
"url": "https://tronscan.org"
|
|
1712
1758
|
},
|
|
1759
|
+
"iconSlug": "tron",
|
|
1713
1760
|
"name": "Tron",
|
|
1714
1761
|
"nativeToken": {
|
|
1715
1762
|
"address": "0x0000000000000000000000000000000000000000",
|
|
@@ -1757,6 +1804,7 @@ const chains = {
|
|
|
1757
1804
|
"txPath": "/tx/",
|
|
1758
1805
|
"url": "https://solscan.io"
|
|
1759
1806
|
},
|
|
1807
|
+
"iconSlug": "solana",
|
|
1760
1808
|
"name": "Solana",
|
|
1761
1809
|
"nativeToken": {
|
|
1762
1810
|
"address": "11111111111111111111111111111111",
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
declare const PACKAGE_VERSION = "1.
|
|
1
|
+
declare const PACKAGE_VERSION = "1.13.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.13.0";
|
|
6
6
|
exports.PACKAGE_VERSION = PACKAGE_VERSION;
|
package/dist/src/types.d.ts
CHANGED
|
@@ -8,7 +8,7 @@ type SupportedOPStackMainnet = Extract<EnumNumberValues<typeof OPStackChains>, S
|
|
|
8
8
|
type SupportedOPStackTestnet = Extract<EnumNumberValues<typeof OPStackChains>, SupportedTestnet>;
|
|
9
9
|
type SupportedChain = SupportedMainnet | SupportedTestnet;
|
|
10
10
|
type SettlementLayer = 'ACROSS' | 'ECO' | 'RELAY' | 'OFT' | 'NEAR' | 'RHINO' | 'CCTP';
|
|
11
|
-
type SwapQuoter = '1inch' | '0x' | 'velora' | 'kyberswap' | 'fynd';
|
|
11
|
+
type SwapQuoter = '1inch' | '0x' | 'velora' | 'kyberswap' | 'fynd' | 'fynd-hosted';
|
|
12
12
|
type PegGroup = 'USD' | 'ETH';
|
|
13
13
|
type VmType = 'evm' | 'svm' | 'tvm';
|
|
14
14
|
type ChainNetwork = 'mainnet' | 'testnet';
|
|
@@ -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
|