@snowbridge/registry 1.0.5 → 1.0.6

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.
Files changed (41) hide show
  1. package/.prettierrc +23 -0
  2. package/.turbo/turbo-build.log +4 -0
  3. package/README.md +1 -3
  4. package/dist/index.d.ts +7 -2
  5. package/dist/index.d.ts.map +1 -1
  6. package/dist/index.js +30 -47
  7. package/dist/local_e2e_bridge_info.g.d.ts +158 -0
  8. package/dist/local_e2e_bridge_info.g.d.ts.map +1 -0
  9. package/dist/local_e2e_bridge_info.g.js +159 -0
  10. package/dist/paseo_sepolia_bridge_info.g.d.ts +353 -0
  11. package/dist/paseo_sepolia_bridge_info.g.d.ts.map +1 -0
  12. package/dist/paseo_sepolia_bridge_info.g.js +373 -0
  13. package/dist/polkadot_mainnet_bridge_info.g.d.ts +2249 -0
  14. package/dist/polkadot_mainnet_bridge_info.g.d.ts.map +1 -0
  15. package/dist/polkadot_mainnet_bridge_info.g.js +2573 -0
  16. package/dist/transfers.d.ts +4 -0
  17. package/dist/transfers.d.ts.map +1 -0
  18. package/dist/transfers.js +96 -0
  19. package/dist/westend_sepolia_bridge_info.g.d.ts +469 -0
  20. package/dist/westend_sepolia_bridge_info.g.d.ts.map +1 -0
  21. package/dist/westend_sepolia_bridge_info.g.js +526 -0
  22. package/package.json +21 -15
  23. package/scripts/buildRegistry.ts +1258 -0
  24. package/scripts/friendlyChains.ts +74 -0
  25. package/src/index.ts +22 -57
  26. package/src/local_e2e_bridge_info.g.ts +157 -0
  27. package/src/paseo_sepolia_bridge_info.g.ts +372 -0
  28. package/src/polkadot_mainnet_bridge_info.g.ts +2597 -0
  29. package/src/transfers.ts +97 -0
  30. package/src/westend_sepolia_bridge_info.g.ts +534 -0
  31. package/tsconfig.json +1 -1
  32. package/tsconfig.scripts.json +23 -0
  33. package/build.ts +0 -35
  34. package/dist/local_e2e.registry.json +0 -347
  35. package/dist/paseo_sepolia.registry.json +0 -150
  36. package/dist/polkadot_mainnet.registry.json +0 -1484
  37. package/dist/westend_sepolia.registry.json +0 -227
  38. package/src/local_e2e.registry.json +0 -347
  39. package/src/paseo_sepolia.registry.json +0 -150
  40. package/src/polkadot_mainnet.registry.json +0 -1484
  41. package/src/westend_sepolia.registry.json +0 -227
@@ -0,0 +1,1258 @@
1
+ import "dotenv/config"
2
+ import {
3
+ AssetOverrideMap,
4
+ AssetRegistry,
5
+ ChainProperties,
6
+ Environment,
7
+ ERC20Metadata,
8
+ ERC20MetadataMap,
9
+ ERC20MetadataOverrideMap,
10
+ EthereumChain,
11
+ KusamaConfig,
12
+ L2ForwardMetadata,
13
+ Parachain,
14
+ ParachainMap,
15
+ PNAMap,
16
+ PrecompileMap,
17
+ XC20TokenMap,
18
+ XcmVersion,
19
+ BridgeInfo,
20
+ ChainMap,
21
+ TransferRoute,
22
+ ChainId,
23
+ ChainKey,
24
+ ParachainKind,
25
+ } from "@snowbridge/base-types"
26
+ import { ApiPromise, HttpProvider, WsProvider } from "@polkadot/api"
27
+ import { isFunction } from "@polkadot/util"
28
+ import { writeFile } from "fs/promises"
29
+ import { AbstractProvider, Contract, ethers } from "ethers"
30
+ import { IGatewayV1__factory as IGateway__factory } from "@snowbridge/contract-types"
31
+ import { assetsV2 } from "@snowbridge/api"
32
+ import { ParachainBase, paraImplementation } from "@snowbridge/api/dist/parachains"
33
+ import { buildParachainERC20ReceivedXcmOnDestination } from "@snowbridge/api/dist/xcmBuilder"
34
+ import { EthersEthereumProvider, EthersProviderTypes } from "@snowbridge/provider-ethers"
35
+ import { buildFriendlyChains } from "./friendlyChains"
36
+
37
+ export type Path = {
38
+ source: ChainId
39
+ destination: ChainId
40
+ asset: string
41
+ }
42
+
43
+ const SNOWBRIDGE_ENV: { [env: string]: Environment } = {
44
+ local_e2e: {
45
+ name: "local_e2e",
46
+ ethChainId: 11155111,
47
+ beaconApiUrl: "http://127.0.0.1:9596",
48
+ ethereumChains: {
49
+ "11155111": "ws://127.0.0.1:8546",
50
+ },
51
+ relaychainUrl: "ws://127.0.0.1:9944",
52
+ parachains: {
53
+ "1000": "ws://127.0.0.1:12144",
54
+ "1002": "ws://127.0.0.1:11144",
55
+ "2000": "ws://127.0.0.1:13144",
56
+ },
57
+ gatewayContract: "0xb1185ede04202fe62d38f5db72f71e38ff3e8305",
58
+ beefyContract: "0x83428c7db9815f482a39a1715684dcf755021997",
59
+ assetHubParaId: 1000,
60
+ bridgeHubParaId: 1002,
61
+ v2_parachains: [1000],
62
+ indexerGraphQlUrl: "http://127.0.0.1/does/not/exist",
63
+ },
64
+ paseo_sepolia: {
65
+ name: "paseo_sepolia",
66
+ ethChainId: 11155111,
67
+ beaconApiUrl: "https://lodestar-sepolia.chainsafe.io",
68
+ ethereumChains: {
69
+ "11155111": "https://ethereum-sepolia-rpc.publicnode.com",
70
+ },
71
+ relaychainUrl: "wss://paseo-rpc.n.dwellir.com",
72
+ parachains: {
73
+ "1000": "wss://asset-hub-paseo-rpc.n.dwellir.com",
74
+ "1002": "wss://bridge-hub-paseo.dotters.network",
75
+ "3369": "wss://paseo-muse-rpc.polkadot.io",
76
+ "2043": `wss://parachain-testnet-rpc.origin-trail.network`,
77
+ },
78
+ gatewayContract: "0x1607c1368bc943130258318c91bbd8cff3d063e6",
79
+ beefyContract: "0x2c780945beb1241fe9c645800110cb9c4bbbb639",
80
+ assetHubParaId: 1000,
81
+ bridgeHubParaId: 1002,
82
+ v2_parachains: [1000],
83
+ indexerGraphQlUrl:
84
+ "https://snowbridge.squids.live/snowbridge-subsquid-paseo@v1/api/graphql",
85
+ metadataOverrides: {
86
+ // Change the name of TRAC
87
+ "0xef32abea56beff54f61da319a7311098d6fbcea9": {
88
+ name: "OriginTrail TRAC",
89
+ symbol: "TRAC",
90
+ },
91
+ },
92
+ },
93
+ polkadot_mainnet: {
94
+ name: "polkadot_mainnet",
95
+ ethChainId: 1,
96
+ beaconApiUrl: "https://lodestar-mainnet.chainsafe.io",
97
+ ethereumChains: {
98
+ "1": "https://ethereum-rpc.publicnode.com",
99
+ "1284": "https://rpc.api.moonbeam.network",
100
+ "8453": "https://base-rpc.publicnode.com",
101
+ "42161": "https://arbitrum-one-rpc.publicnode.com",
102
+ "10": "https://optimism-rpc.publicnode.com",
103
+ },
104
+ relaychainUrl: "https://polkadot-rpc.n.dwellir.com",
105
+ parachains: {
106
+ "1000": "wss://polkadot-asset-hub-rpc.polkadot.io",
107
+ "1002": "wss://polkadot-bridge-hub-rpc.polkadot.io",
108
+ "3369": "wss://polkadot-mythos-rpc.polkadot.io",
109
+ "2034": "wss://hydration-rpc.n.dwellir.com",
110
+ "2030": "wss://bifrost-polkadot.ibp.network",
111
+ "2004": "wss://moonbeam.ibp.network",
112
+ "2000": "wss://acala-rpc-0.aca-api.network",
113
+ "2043": "wss://parachain-rpc.origin-trail.network",
114
+ // TODO: Add back in jampton once we have an indexer in place.
115
+ //"3397": "wss://rpc.jamton.network",
116
+ },
117
+ gatewayContract: "0x27ca963c279c93801941e1eb8799c23f407d68e7",
118
+ beefyContract: "0x7cfc5c8b341991993080af67d940b6ad19a010e1",
119
+ assetHubParaId: 1000,
120
+ bridgeHubParaId: 1002,
121
+ v2_parachains: [1000],
122
+ indexerGraphQlUrl: "https://subsquid.snowbridge.network/graphql",
123
+ kusama: {
124
+ assetHubParaId: 1000,
125
+ bridgeHubParaId: 1002,
126
+ parachains: {
127
+ "1000": "wss://asset-hub-kusama-rpc.n.dwellir.com",
128
+ "1002": "https://bridge-hub-kusama-rpc.n.dwellir.com",
129
+ },
130
+ },
131
+ precompiles: {
132
+ // Add override for mythos token and add precompile for moonbeam
133
+ "2004": "0x000000000000000000000000000000000000081a",
134
+ },
135
+ metadataOverrides: {
136
+ // Change the name of TRAC
137
+ "0xaa7a9ca87d3694b5755f213b5d04094b8d0f0a6f": {
138
+ name: "OriginTrail TRAC",
139
+ },
140
+ },
141
+ l2Bridge: {
142
+ acrossAPIUrl: "https://app.across.to/api",
143
+ l1AdapterAddress: "0xd3b11c36404b092645522b682832fcdee07d2668",
144
+ l1HandlerAddress: "0x924a9f036260ddd5808007e1aa95f08ed08aa569",
145
+ l1FeeTokenAddress: "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2",
146
+ l1SwapQuoterAddress: "0x61ffe014ba17989e743c5f6cb21bf9697530b21e",
147
+ l1SwapRouterAddress: "0xe592427a0aece92de3edee1f18e0157c05861564",
148
+ l2Chains: {
149
+ "8453": {
150
+ adapterAddress: "0x07fe4E7340976FC873B74bAfe3C3e5b0e01f3665".toLowerCase(),
151
+ feeTokenAddress: "0x4200000000000000000000000000000000000006",
152
+ swapRoutes: [
153
+ // WETH
154
+ {
155
+ inputToken: "0x4200000000000000000000000000000000000006",
156
+ outputToken: "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2",
157
+ swapFee: 0,
158
+ },
159
+ // USDC
160
+ {
161
+ inputToken: "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913",
162
+ outputToken: "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48",
163
+ swapFee: 500,
164
+ },
165
+ ],
166
+ },
167
+ "42161": {
168
+ adapterAddress: "0x836895Ad176235Dfe9C59b3df56C7579d90ea338".toLowerCase(),
169
+ feeTokenAddress: "0x82aF49447D8a07e3bd95BD0d56f35241523fBab1".toLowerCase(),
170
+ swapRoutes: [
171
+ // WETH
172
+ {
173
+ inputToken: "0x82aF49447D8a07e3bd95BD0d56f35241523fBab1".toLowerCase(),
174
+ outputToken: "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2",
175
+ swapFee: 0,
176
+ },
177
+ // USDC
178
+ {
179
+ inputToken: "0xaf88d065e77c8cC2239327C5EDb3A432268e5831".toLowerCase(),
180
+ outputToken: "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48",
181
+ swapFee: 500,
182
+ },
183
+ ],
184
+ },
185
+ "10": {
186
+ adapterAddress: "0x836895Ad176235Dfe9C59b3df56C7579d90ea338".toLowerCase(),
187
+ feeTokenAddress: "0x4200000000000000000000000000000000000006".toLowerCase(),
188
+ swapRoutes: [
189
+ // WETH
190
+ {
191
+ inputToken: "0x4200000000000000000000000000000000000006".toLowerCase(),
192
+ outputToken: "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2",
193
+ swapFee: 0,
194
+ },
195
+ // USDC
196
+ {
197
+ inputToken: "0x0b2C639c533813f4Aa9D7837CAf62653d097Ff85".toLowerCase(),
198
+ outputToken: "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48",
199
+ swapFee: 500,
200
+ },
201
+ ],
202
+ },
203
+ },
204
+ },
205
+ },
206
+ westend_sepolia: {
207
+ name: "westend_sepolia",
208
+ ethChainId: 11155111,
209
+ beaconApiUrl: "https://lodestar-sepolia.chainsafe.io",
210
+ ethereumChains: {
211
+ "11155111": "https://ethereum-sepolia-rpc.publicnode.com",
212
+ "84532": "https://base-sepolia-rpc.publicnode.com",
213
+ "421614": "https://arbitrum-sepolia-rpc.publicnode.com",
214
+ },
215
+ relaychainUrl: "https://westend-rpc.polkadot.io",
216
+ parachains: {
217
+ "1000": "https://westend-asset-hub-rpc.polkadot.io",
218
+ "1002": "https://westend-bridge-hub-rpc.polkadot.io",
219
+ },
220
+ gatewayContract: "0x9ed8b47bc3417e3bd0507adc06e56e2fa360a4e9",
221
+ beefyContract: "0xEBD1CFcF82BaA170b86BDe532f69A6A49c6c790D".toLowerCase(),
222
+ assetHubParaId: 1000,
223
+ bridgeHubParaId: 1002,
224
+ v2_parachains: [1000],
225
+ indexerGraphQlUrl:
226
+ "https://snowbridge.squids.live/snowbridge-subsquid-westend@v1/api/graphql",
227
+ l2Bridge: {
228
+ acrossAPIUrl: "https://testnet.across.to/api",
229
+ l1AdapterAddress: "0xCDa9bFf39cdF39E95F4B699422E422195091126d".toLowerCase(),
230
+ l1HandlerAddress: "0x924a9f036260ddd5808007e1aa95f08ed08aa569",
231
+ l1FeeTokenAddress: "0xfff9976782d46cc05630d1f6ebab18b2324d6b14",
232
+ l1SwapRouterAddress: "0x3bfa4769fb09eefc5a80d6e87c3b9c650f7ae48e",
233
+ l1SwapQuoterAddress: "0xed1f6473345f45b75f8179591dd5ba1888cf2fb3",
234
+ l2Chains: {
235
+ "84532": {
236
+ adapterAddress: "0xf06939613a3838af11104c898758220db9093679",
237
+ feeTokenAddress: "0x4200000000000000000000000000000000000006",
238
+ swapRoutes: [
239
+ // WETH
240
+ {
241
+ inputToken: "0x4200000000000000000000000000000000000006",
242
+ outputToken: "0xfff9976782d46cc05630d1f6ebab18b2324d6b14",
243
+ swapFee: 0,
244
+ },
245
+ // USDC
246
+ {
247
+ inputToken: "0x036cbd53842c5426634e7929541ec2318f3dcf7e",
248
+ outputToken: "0x1c7d4b196cb0c7b01d743fbc6116a902379c7238",
249
+ swapFee: 500,
250
+ },
251
+ ],
252
+ },
253
+ "421614": {
254
+ adapterAddress: "0xcB3d8043bDbfB0D9b30de279A09132073d1dE561".toLowerCase(),
255
+ feeTokenAddress: "0x980B62Da83eFf3D4576C647993b0c1D7faf17c73".toLowerCase(),
256
+ swapRoutes: [
257
+ // WETH
258
+ {
259
+ inputToken: "0x980B62Da83eFf3D4576C647993b0c1D7faf17c73".toLowerCase(),
260
+ outputToken: "0xfff9976782d46cc05630d1f6ebab18b2324d6b14".toLowerCase(),
261
+ swapFee: 0,
262
+ },
263
+ // USDC
264
+ {
265
+ inputToken: "0x75faf114eafb1BDbe2F0316DF893fd58CE46AA4d".toLowerCase(),
266
+ outputToken: "0x1c7d4b196cb0c7b01d743fbc6116a902379c7238".toLowerCase(),
267
+ swapFee: 500,
268
+ },
269
+ ],
270
+ },
271
+ },
272
+ },
273
+ },
274
+ }
275
+
276
+ export function defaultPathFilter(envName: string): (_: Path) => boolean {
277
+ switch (envName) {
278
+ case "westend_sepolia": {
279
+ return (path: Path) => {
280
+ // Frequency
281
+ if (path.asset === "0x72c610e05eaafcdf1fa7a2da15374ee90edb1620") {
282
+ return false
283
+ }
284
+ // Disable para to para transfers
285
+ if (path.source.kind === "polkadot" && path.destination.kind === "polkadot") {
286
+ return false
287
+ }
288
+ return true
289
+ }
290
+ }
291
+ case "paseo_sepolia":
292
+ return (path: Path) => {
293
+ // Disallow MUSE to any location but 3369
294
+ if (
295
+ path.asset === "0xb34a6924a02100ba6ef12af1c798285e8f7a16ee" &&
296
+ ((path.destination.kind === "polkadot" &&
297
+ path.destination.id !== 3369 &&
298
+ path.source.kind === "ethereum") ||
299
+ (path.source.id !== 3369 && path.source.kind === "polkadot"))
300
+ ) {
301
+ return false
302
+ }
303
+ // Disable para to para transfers
304
+ if (path.source.kind === "polkadot" && path.destination.kind === "polkadot") {
305
+ return false
306
+ }
307
+ return true
308
+ }
309
+ case "polkadot_mainnet":
310
+ return (path: Path) => {
311
+ // Disallow Robonomic
312
+ if (path.asset === "0x7de91b204c1c737bcee6f000aaa6569cf7061cb7") {
313
+ return false
314
+ }
315
+
316
+ // Disallow MYTH to any location but 3369
317
+ if (
318
+ path.asset === "0xba41ddf06b7ffd89d1267b5a93bfef2424eb2003" &&
319
+ ((path.destination.kind === "polkadot" &&
320
+ path.destination.id !== 3369 &&
321
+ path.source.kind === "ethereum") ||
322
+ (path.source.id !== 3369 && path.source.kind === "polkadot"))
323
+ ) {
324
+ return false
325
+ }
326
+
327
+ // Allow TRAC to go to Hydration (2034) and Neuroweb (2043) only
328
+ if (
329
+ path.asset === "0xaa7a9ca87d3694b5755f213b5d04094b8d0f0a6f" &&
330
+ ((path.destination.kind === "polkadot" &&
331
+ path.destination.id !== 2034 &&
332
+ path.destination.id !== 2043 &&
333
+ path.source.kind === "ethereum") ||
334
+ (path.source.id !== 2034 &&
335
+ path.source.id !== 2043 &&
336
+ path.source.kind === "polkadot"))
337
+ ) {
338
+ return false
339
+ }
340
+
341
+ // Disable stable coins in the UI from Ethereum to Polkadot
342
+ if (
343
+ (path.asset === "0x9d39a5de30e57443bff2a8307a4256c8797a3497" || // Staked USDe
344
+ path.asset === "0xa3931d71877c0e7a3148cb7eb4463524fec27fbd" || // Savings USD
345
+ path.asset === "0x6b175474e89094c44da98b954eedeac495271d0f") && // DAI
346
+ path.destination.kind === "polkadot" &&
347
+ path.destination.id === 2034 // Hydration
348
+ ) {
349
+ return false
350
+ }
351
+ // Disable para to para transfers except for hydration
352
+ if (
353
+ path.source.kind === "polkadot" &&
354
+ path.destination.kind === "polkadot" &&
355
+ !(
356
+ (path.source.id === 2034 && path.destination.id == 1000) ||
357
+ (path.source.id === 1000 && path.destination.id === 2034)
358
+ )
359
+ ) {
360
+ return false
361
+ }
362
+ return true
363
+ }
364
+
365
+ default:
366
+ return (_: Path) => true
367
+ }
368
+ }
369
+
370
+ function buildTransferLocations(
371
+ registry: AssetRegistry,
372
+ environment: Environment,
373
+ filter?: (path: Path) => boolean,
374
+ ): TransferRoute[] {
375
+ const ethChain = registry.ethereumChains[`ethereum_${registry.ethChainId}`]
376
+ const parachains = Object.values(registry.parachains).filter(
377
+ (p) => !(p.kind === "polkadot" && p.id === registry.bridgeHubParaId),
378
+ )
379
+
380
+ const pathFilter = filter ?? defaultPathFilter(registry.environment)
381
+
382
+ const locations: Path[] = []
383
+
384
+ const ethAssets = Object.keys(ethChain.assets)
385
+ // Bridged paths
386
+ for (const parachain of parachains) {
387
+ const destinationAssets = Object.keys(parachain.assets)
388
+ const commonAssets = new Set(
389
+ ethAssets.filter((sa) => destinationAssets.find((da) => da === sa)),
390
+ )
391
+ for (const asset of commonAssets) {
392
+ const p1: Path = {
393
+ source: { kind: ethChain.kind, id: ethChain.id },
394
+ destination: { kind: parachain.kind, id: parachain.id },
395
+ asset,
396
+ }
397
+ if (pathFilter(p1)) {
398
+ locations.push(p1)
399
+ }
400
+ const p2: Path = {
401
+ source: p1.destination,
402
+ destination: p1.source,
403
+ asset,
404
+ }
405
+ if (pathFilter(p2)) {
406
+ locations.push(p2)
407
+ }
408
+ if (
409
+ parachain.info.evmChainId &&
410
+ registry.ethereumChains[`ethereum_${parachain.info.evmChainId}`]
411
+ ) {
412
+ const p3: Path = {
413
+ source: {
414
+ kind: "ethereum",
415
+ id: parachain.info.evmChainId,
416
+ },
417
+ destination: p1.source, // Ethereum
418
+ asset,
419
+ }
420
+ if (pathFilter(p3)) {
421
+ locations.push(p3)
422
+ }
423
+ }
424
+ }
425
+ }
426
+
427
+ // Local paths
428
+ const assetHub = registry.parachains[`polkadot_${registry.assetHubParaId}`]
429
+ for (const parachain of parachains) {
430
+ if (parachain.kind === assetHub.kind && parachain.id === assetHub.id) continue
431
+ const assetHubAssets = Object.keys(assetHub.assets)
432
+ const destinationAssets = Object.keys(parachain.assets)
433
+
434
+ // The asset exists on ethereum, parachain and asset hub
435
+ const commonAssets = new Set(
436
+ ethAssets.filter(
437
+ (sa) =>
438
+ assetHubAssets.find((da) => da === sa) &&
439
+ destinationAssets.find((da) => da === sa),
440
+ ),
441
+ )
442
+ for (const asset of commonAssets) {
443
+ const p1: Path = {
444
+ source: { kind: assetHub.kind, id: assetHub.id },
445
+ destination: { kind: parachain.kind, id: parachain.id },
446
+ asset,
447
+ }
448
+ if (pathFilter(p1)) {
449
+ locations.push(p1)
450
+ }
451
+ const p2: Path = {
452
+ source: p1.destination, // Parachain
453
+ destination: p1.source, // Asset Hub
454
+ asset,
455
+ }
456
+ if (pathFilter(p2)) {
457
+ locations.push(p2)
458
+ }
459
+ }
460
+ }
461
+
462
+ // L2 paths
463
+ if (environment.l2Bridge) {
464
+ // Do asset hub only, in future we can loop through all v2 enabled parachains.
465
+ for (const l2ChainKey of Object.keys(environment.l2Bridge.l2Chains)) {
466
+ const l2ChainId = Number(l2ChainKey)
467
+ const l2Chain = environment.l2Bridge.l2Chains[l2ChainId]
468
+ const ethChain = registry.ethereumChains[`ethereum_l2_${l2ChainId}`]
469
+ if (!ethChain || !l2Chain) {
470
+ console.warn(`Could not find ethereum l2 chain ${l2ChainId}. Skipping...`)
471
+ continue
472
+ }
473
+ const assetHubAssets = Object.keys(assetHub.assets)
474
+ const destinationAssets = Object.values(ethChain.assets)
475
+ .map((a) => a.swapTokenAddress?.toLowerCase())
476
+ .filter((a) => a !== undefined)
477
+
478
+ // The asset exists on ethereum, parachain and asset hub
479
+ const commonAssets = new Set(
480
+ ethAssets.filter(
481
+ (sa) =>
482
+ assetHubAssets.find((da) => da === sa) &&
483
+ destinationAssets.find((da) => da === sa),
484
+ ),
485
+ )
486
+ for (const asset of commonAssets) {
487
+ const p1: Path = {
488
+ source: { kind: assetHub.kind, id: assetHub.id },
489
+ destination: { kind: ethChain.kind, id: ethChain.id },
490
+ asset,
491
+ }
492
+ if (pathFilter(p1)) {
493
+ locations.push(p1)
494
+ }
495
+ const p2: Path = {
496
+ source: p1.destination, // L2 Chain
497
+ destination: p1.source, // Asset Hub
498
+ asset,
499
+ }
500
+ if (pathFilter(p2)) {
501
+ locations.push(p2)
502
+ }
503
+ }
504
+ }
505
+
506
+ const results: TransferRoute[] = []
507
+ for (const location of locations) {
508
+ let source = results.find(
509
+ (s) =>
510
+ s.from.kind === location.source.kind &&
511
+ s.from.id === location.source.id &&
512
+ s.to.kind === location.destination.kind &&
513
+ s.to.id === location.destination.id,
514
+ )
515
+
516
+ if (!source) {
517
+ source = {
518
+ from: location.source,
519
+ to: location.destination,
520
+ assets: [],
521
+ }
522
+ results.push(source)
523
+ }
524
+ source.assets = source.assets.concat(location.asset)
525
+ }
526
+ }
527
+
528
+ // Combine all paths into routes
529
+ const results: TransferRoute[] = []
530
+ for (const location of locations) {
531
+ let source = results.find(
532
+ (s) =>
533
+ s.from.kind === location.source.kind &&
534
+ s.from.id === location.source.id &&
535
+ s.to.kind === location.destination.kind &&
536
+ s.to.id === location.destination.id,
537
+ )
538
+
539
+ if (!source) {
540
+ source = {
541
+ from: location.source,
542
+ to: location.destination,
543
+ assets: [],
544
+ }
545
+ results.push(source)
546
+ }
547
+ source.assets = source.assets.concat(location.asset)
548
+ }
549
+ return results
550
+ }
551
+
552
+ async function buildRegistry(environment: Environment): Promise<AssetRegistry> {
553
+ const {
554
+ relaychainUrl,
555
+ ethereumChains,
556
+ ethChainId,
557
+ assetHubParaId,
558
+ bridgeHubParaId,
559
+ v2_parachains,
560
+ parachains,
561
+ gatewayContract,
562
+ assetOverrides,
563
+ precompiles,
564
+ metadataOverrides,
565
+ kusama,
566
+ name,
567
+ l2Bridge,
568
+ } = environment
569
+
570
+ let relayInfo: ChainProperties
571
+ {
572
+ let provider = await ApiPromise.create({
573
+ noInitWarn: true,
574
+ provider: relaychainUrl.startsWith("http")
575
+ ? new HttpProvider(relaychainUrl)
576
+ : new WsProvider(relaychainUrl),
577
+ })
578
+ relayInfo = await (await paraImplementation(provider)).chainProperties()
579
+
580
+ await provider.disconnect()
581
+ }
582
+
583
+ // Connect to all eth connections
584
+ const ethProviders: {
585
+ [chainId: string]: {
586
+ chainId: number
587
+ provider: AbstractProvider
588
+ name: string
589
+ }
590
+ } = {}
591
+ {
592
+ for (const result of await Promise.all(
593
+ Object.keys(ethereumChains).map(async (ethChain) => {
594
+ let provider = ethers.getDefaultProvider(ethereumChains[ethChain])
595
+ const network = await provider.getNetwork()
596
+ return { chainId: Number(network.chainId), provider, name: network.name }
597
+ }),
598
+ )) {
599
+ ethProviders[result.chainId.toString()] = result
600
+ }
601
+ if (!(ethChainId.toString() in ethProviders)) {
602
+ throw Error(`Cannot find ethereum chain ${ethChainId} in the list of ethereum chains.`)
603
+ }
604
+ }
605
+
606
+ let pnaAssets: PNAMap = {}
607
+ let bridgeHubInfo: ChainProperties
608
+ {
609
+ if (!(bridgeHubParaId.toString() in parachains)) {
610
+ throw Error(`Cannot find bridge hub ${bridgeHubParaId} in the list of parachains.`)
611
+ }
612
+ const bridgeHubUrl = parachains[bridgeHubParaId.toString()]
613
+ let provider = await ApiPromise.create({
614
+ noInitWarn: true,
615
+ provider: bridgeHubUrl.startsWith("http")
616
+ ? new HttpProvider(bridgeHubUrl)
617
+ : new WsProvider(bridgeHubUrl),
618
+ })
619
+ bridgeHubInfo = await (await paraImplementation(provider)).chainProperties()
620
+ pnaAssets = await getRegisteredPnas(
621
+ provider,
622
+ ethProviders[ethChainId].provider,
623
+ gatewayContract,
624
+ )
625
+
626
+ await provider.disconnect()
627
+ }
628
+
629
+ // Connect to all substrate parachains.
630
+ const providers: {
631
+ [paraIdKey: string]: { parachainId: number; accessor: ParachainBase }
632
+ } = {}
633
+ {
634
+ for (const { parachainId, accessor } of await Promise.all(
635
+ Object.keys(parachains).map(async (parachainId) => {
636
+ const parachainUrl = parachains[parachainId]
637
+ const provider = await ApiPromise.create({
638
+ noInitWarn: true,
639
+ provider: parachainUrl.startsWith("http")
640
+ ? new HttpProvider(parachainUrl)
641
+ : new WsProvider(parachainUrl),
642
+ })
643
+ const accessor = await paraImplementation(provider, new EthersEthereumProvider())
644
+ return { parachainId: accessor.parachainId, accessor }
645
+ }),
646
+ )) {
647
+ providers[parachainId.toString()] = { parachainId, accessor }
648
+ }
649
+ if (!(assetHubParaId.toString() in providers)) {
650
+ throw Error(
651
+ `Could not resolve asset hub para id ${assetHubParaId} in the list of parachains provided.`,
652
+ )
653
+ }
654
+ }
655
+
656
+ // Index parachains
657
+ const paras: ParachainMap = {}
658
+ for (const { parachainId, para } of await Promise.all(
659
+ Object.keys(providers)
660
+ .filter((parachainIdKey) => parachainIdKey !== bridgeHubParaId.toString())
661
+ .map(async (parachainIdKey) => {
662
+ const { parachainId, accessor } = providers[parachainIdKey]
663
+ const para = await indexParachain(
664
+ accessor,
665
+ providers[assetHubParaId.toString()].accessor,
666
+ "polkadot",
667
+ ethChainId,
668
+ parachainId,
669
+ assetHubParaId,
670
+ pnaAssets,
671
+ assetOverrides ?? {},
672
+ v2_parachains,
673
+ )
674
+ return { parachainId, para }
675
+ }),
676
+ )) {
677
+ paras[`polkadot_${parachainId}`] = para
678
+ }
679
+
680
+ // Index Ethereum chain
681
+ const ethChains: { [chainId: string]: EthereumChain } = {}
682
+ for (const ethChainInfo of await Promise.all(
683
+ Object.keys(ethProviders).map(async (ethChainKey) => {
684
+ return indexEthChain(
685
+ ethProviders[ethChainKey].provider,
686
+ ethProviders[ethChainKey].chainId,
687
+ ethProviders[ethChainKey].name,
688
+ ethChainId,
689
+ gatewayContract,
690
+ assetHubParaId,
691
+ paras,
692
+ precompiles ?? {},
693
+ metadataOverrides ?? {},
694
+ l2Bridge?.l2Chains ?? {},
695
+ )
696
+ }),
697
+ )) {
698
+ ethChains[ethChainInfo.key] = ethChainInfo
699
+ }
700
+
701
+ let kusamaConfig: KusamaConfig | undefined
702
+ if (kusama) {
703
+ const assetHubUrl = kusama.parachains[kusama.assetHubParaId.toString()]
704
+ let provider = await ApiPromise.create({
705
+ noInitWarn: true,
706
+ provider: assetHubUrl.startsWith("http")
707
+ ? new HttpProvider(assetHubUrl)
708
+ : new WsProvider(assetHubUrl),
709
+ })
710
+ const accessor = await paraImplementation<EthersProviderTypes>(
711
+ provider,
712
+ new EthersEthereumProvider(),
713
+ )
714
+
715
+ const para = await indexParachain(
716
+ accessor,
717
+ providers[assetHubParaId].accessor,
718
+ "kusama",
719
+ ethChainId,
720
+ accessor.parachainId,
721
+ assetHubParaId,
722
+ pnaAssets,
723
+ assetOverrides ?? {},
724
+ )
725
+
726
+ const kusamaParas: ParachainMap = {}
727
+ kusamaParas[para.key] = para
728
+
729
+ kusamaConfig = {
730
+ parachains: kusamaParas,
731
+ assetHubParaId: kusama.assetHubParaId,
732
+ bridgeHubParaId: kusama.bridgeHubParaId,
733
+ }
734
+
735
+ await accessor.provider.disconnect()
736
+ }
737
+ // Dispose of all substrate connections
738
+ await Promise.all(
739
+ Object.keys(providers).map(
740
+ async (parachainKey) => await providers[parachainKey].accessor.provider.disconnect(),
741
+ ),
742
+ )
743
+
744
+ // Dispose all eth connections
745
+ Object.keys(ethProviders).forEach((parachainKey) =>
746
+ ethProviders[parachainKey].provider.destroy(),
747
+ )
748
+
749
+ return {
750
+ timestamp: new Date().toISOString(),
751
+ environment: name,
752
+ ethChainId,
753
+ gatewayAddress: gatewayContract,
754
+ assetHubParaId,
755
+ bridgeHubParaId,
756
+ relaychain: relayInfo,
757
+ bridgeHub: bridgeHubInfo,
758
+ ethereumChains: ethChains,
759
+ parachains: paras,
760
+ kusama: kusamaConfig,
761
+ }
762
+ }
763
+
764
+ async function checkSnowbridgeV2Support(
765
+ parachain: ParachainBase,
766
+ ethChainId: number,
767
+ ): Promise<{
768
+ xcmVersion: XcmVersion
769
+ supportsAliasOrigin: boolean
770
+ hasEthBalance: boolean
771
+ }> {
772
+ let supportsAliasOrigin = false
773
+ let hasEthBalance = false
774
+ let xcmVersion: XcmVersion
775
+
776
+ try {
777
+ const testXcm = parachain.provider.registry.createType("XcmVersionedXcm", {
778
+ v5: [
779
+ {
780
+ aliasOrigin: {
781
+ parents: 0,
782
+ interior: {
783
+ x1: [
784
+ {
785
+ accountId32: {
786
+ id: "0x0000000000000000000000000000000000000000000000000000000000000000",
787
+ },
788
+ },
789
+ ],
790
+ },
791
+ },
792
+ },
793
+ ],
794
+ })
795
+
796
+ const weightResult = (
797
+ await parachain.provider.call.xcmPaymentApi.queryXcmWeight(testXcm)
798
+ ).toPrimitive() as any
799
+
800
+ if (weightResult.ok) {
801
+ const refTime = BigInt(weightResult.ok.refTime.toString())
802
+ const MAX_REASONABLE_WEIGHT = 10n ** 15n
803
+ // Check if AliasOrigin is supported. Often, the XCM instruction
804
+ // weight is set to MAX to make it unusable
805
+ supportsAliasOrigin = refTime < MAX_REASONABLE_WEIGHT
806
+
807
+ const etherLocation = {
808
+ parents: 2,
809
+ interior: { x1: [{ GlobalConsensus: { Ethereum: { chain_id: ethChainId } } }] },
810
+ }
811
+
812
+ // Check if ether is supported as a fee asset
813
+ const feeResult = (
814
+ await parachain.provider.call.xcmPaymentApi.queryWeightToAssetFee(weightResult.ok, {
815
+ v5: etherLocation,
816
+ })
817
+ ).toPrimitive() as any
818
+
819
+ if (feeResult.ok) {
820
+ hasEthBalance = true
821
+ }
822
+ }
823
+
824
+ xcmVersion = "v5"
825
+ } catch {
826
+ // If any call throws an error, XCM V5 is likely not supported.
827
+ xcmVersion = "v4"
828
+ }
829
+
830
+ return { xcmVersion, supportsAliasOrigin, hasEthBalance }
831
+ }
832
+
833
+ async function indexParachain(
834
+ parachain: ParachainBase,
835
+ assetHub: ParachainBase,
836
+ kind: ParachainKind,
837
+ ethChainId: number,
838
+ parachainId: number,
839
+ assetHubParaId: number,
840
+ pnaAssets: PNAMap,
841
+ assetOverrides: AssetOverrideMap,
842
+ v2_parachains?: readonly number[],
843
+ ): Promise<Parachain> {
844
+ const info = await parachain.chainProperties()
845
+
846
+ const assets = await parachain.getAssets(ethChainId, pnaAssets)
847
+ const xcDOT = parachain.getXC20DOT()
848
+ const parachainIdKey = parachainId.toString()
849
+ if (parachainIdKey in assetOverrides) {
850
+ for (const asset of assetOverrides[parachainIdKey]) {
851
+ assets[asset.token.toLowerCase()] = asset
852
+ }
853
+ }
854
+
855
+ if (Object.keys(assets).length === 0) {
856
+ console.warn(
857
+ `Cannot discover assets for ${info.specName} (parachain ${parachainId}). Please add a handler for that runtime or add overrides.`,
858
+ )
859
+ }
860
+
861
+ const hasPalletXcm = isFunction(
862
+ parachain.provider.tx.polkadotXcm.transferAssetsUsingTypeAndThen,
863
+ )
864
+ const hasDryRunRpc = isFunction(parachain.provider.rpc.system?.dryRun)
865
+ const hasDryRunApi =
866
+ isFunction(parachain.provider.call.dryRunApi?.dryRunCall) &&
867
+ isFunction(parachain.provider.call.dryRunApi?.dryRunXcm)
868
+ const hasTxPaymentApi = isFunction(parachain.provider.call.transactionPaymentApi?.queryInfo)
869
+ const hasXcmPaymentApi = isFunction(parachain.provider.call.xcmPaymentApi?.queryXcmWeight)
870
+
871
+ const { xcmVersion, supportsAliasOrigin, hasEthBalance } = await checkSnowbridgeV2Support(
872
+ parachain,
873
+ ethChainId,
874
+ )
875
+
876
+ // test getting balances
877
+ let hasDotBalance = true
878
+ try {
879
+ await parachain.getDotBalance(
880
+ info.accountType === "AccountId32"
881
+ ? "0x0000000000000000000000000000000000000000000000000000000000000000"
882
+ : "0x0000000000000000000000000000000000000000",
883
+ )
884
+ } catch (err) {
885
+ console.warn(`Spec ${info.specName} does not support dot ${err}`)
886
+ hasDotBalance = false
887
+ }
888
+
889
+ await parachain.getNativeBalance(
890
+ info.accountType === "AccountId32"
891
+ ? "0x0000000000000000000000000000000000000000000000000000000000000000"
892
+ : "0x0000000000000000000000000000000000000000",
893
+ false,
894
+ )
895
+
896
+ let estimatedExecutionFeeDOT = 0n
897
+ let estimatedDeliveryFeeDOT = 0n
898
+ if (parachainId !== assetHubParaId) {
899
+ const destinationXcm = buildParachainERC20ReceivedXcmOnDestination(
900
+ parachain.provider.registry,
901
+ ethChainId,
902
+ assetsV2.ETHER_TOKEN_ADDRESS,
903
+ 340282366920938463463374607431768211455n,
904
+ 340282366920938463463374607431768211455n,
905
+ info.accountType === "AccountId32"
906
+ ? "0x0000000000000000000000000000000000000000000000000000000000000000"
907
+ : "0x0000000000000000000000000000000000000000",
908
+ "0x0000000000000000000000000000000000000000000000000000000000000000",
909
+ )
910
+ estimatedDeliveryFeeDOT = await assetHub.calculateDeliveryFeeInDOT(
911
+ parachainId,
912
+ destinationXcm,
913
+ )
914
+ estimatedExecutionFeeDOT = await parachain.calculateXcmFee(
915
+ destinationXcm,
916
+ assetsV2.DOT_LOCATION,
917
+ )
918
+ }
919
+ return {
920
+ id: parachainId,
921
+ kind,
922
+ key: `${kind}_${parachainId}`,
923
+ features: {
924
+ hasPalletXcm,
925
+ hasDryRunApi,
926
+ hasTxPaymentApi,
927
+ hasDryRunRpc,
928
+ hasDotBalance,
929
+ hasEthBalance,
930
+ hasXcmPaymentApi,
931
+ supportsAliasOrigin,
932
+ xcmVersion,
933
+ supportsV2: v2_parachains?.includes(parachainId) ?? false,
934
+ },
935
+ info,
936
+ xcDOT,
937
+ assets,
938
+ estimatedExecutionFeeDOT,
939
+ estimatedDeliveryFeeDOT,
940
+ }
941
+ }
942
+
943
+ async function indexEthChain(
944
+ provider: AbstractProvider,
945
+ networkChainId: number,
946
+ networkName: string,
947
+ ethChainId: number,
948
+ gatewayAddress: string,
949
+ assetHubParaId: number,
950
+ parachains: ParachainMap,
951
+ precompiles: PrecompileMap,
952
+ metadataOverrides: ERC20MetadataOverrideMap,
953
+ l2Chains: { [l2ChainId: number]: L2ForwardMetadata },
954
+ ): Promise<EthereumChain> {
955
+ const name = networkName !== "unknown" ? networkName : undefined
956
+ if (networkChainId == ethChainId) {
957
+ // Asset Hub and get meta data
958
+ const assetHub = parachains[`polkadot_${assetHubParaId}`]
959
+ const gateway = IGateway__factory.connect(gatewayAddress, provider)
960
+
961
+ const assets: ERC20MetadataMap = {}
962
+ for (const token in assetHub.assets) {
963
+ if (!(await gateway.isTokenRegistered(token))) {
964
+ console.warn(`Token ${token} is not registered with the gateway.`)
965
+ continue // Skip unregistered assets
966
+ }
967
+ if (token === assetsV2.ETHER_TOKEN_ADDRESS) {
968
+ assets[token] = {
969
+ token: assetHub.assets[token].token,
970
+ name: assetHub.assets[token].name,
971
+ symbol: assetHub.assets[token].symbol,
972
+ decimals: assetHub.assets[token].decimals,
973
+ }
974
+ } else {
975
+ const [asset, foreignId] = await Promise.all([
976
+ assetErc20Metadata(provider, token),
977
+ gateway.queryForeignTokenID(token),
978
+ ])
979
+ assets[token] = {
980
+ ...asset,
981
+ foreignId:
982
+ foreignId !=
983
+ "0x0000000000000000000000000000000000000000000000000000000000000000"
984
+ ? foreignId
985
+ : undefined,
986
+ // LDO gas from https://etherscan.io/tx/0x4e984250beacf693e7407c6cfdcb51229f6a549aa857d601db868b572ee2364b
987
+ // Other ERC20 token transfer on Ethereum typically ranges from 45,000 to 65,000 gas units; use 80_000 to leave a margin
988
+ deliveryGas: asset.symbol == "LDO" ? 150_000n : 80_000n,
989
+ }
990
+ }
991
+ if (token in metadataOverrides) {
992
+ const override = metadataOverrides[token]
993
+ const asset = assets[token]
994
+ if (override.name) {
995
+ asset.name = override.name
996
+ }
997
+ if (override.symbol) {
998
+ asset.symbol = override.symbol
999
+ }
1000
+ if (override.decimals) {
1001
+ asset.decimals = override.decimals
1002
+ }
1003
+ }
1004
+ }
1005
+ if ((await provider.getCode(gatewayAddress)) === undefined) {
1006
+ throw Error(
1007
+ `Could not fetch code for gateway address ${gatewayAddress} on ethereum chain ${networkChainId}.`,
1008
+ )
1009
+ }
1010
+ return {
1011
+ kind: "ethereum",
1012
+ id: networkChainId,
1013
+ name,
1014
+ assets,
1015
+ key: `ethereum_${networkChainId}`,
1016
+ baseDeliveryGas: 120_000n,
1017
+ }
1018
+ } else if (networkChainId in l2Chains) {
1019
+ const assets: ERC20MetadataMap = {}
1020
+ for (const route of l2Chains[networkChainId].swapRoutes) {
1021
+ let asset = await assetErc20Metadata(provider, route.inputToken)
1022
+ assets[route.inputToken.toLowerCase()] = {
1023
+ ...asset,
1024
+ swapTokenAddress: route.outputToken.toLowerCase(),
1025
+ swapFee: route.swapFee,
1026
+ }
1027
+ }
1028
+ assets[assetsV2.ETHER_TOKEN_ADDRESS] = {
1029
+ token: assetsV2.ETHER_TOKEN_ADDRESS,
1030
+ name: "Ether",
1031
+ symbol: "Ether",
1032
+ decimals: 18,
1033
+ swapTokenAddress: assetsV2.ETHER_TOKEN_ADDRESS,
1034
+ swapFee: 0,
1035
+ }
1036
+ return {
1037
+ kind: "ethereum_l2",
1038
+ id: networkChainId,
1039
+ name,
1040
+ assets,
1041
+ key: `ethereum_l2_${networkChainId}`,
1042
+ }
1043
+ } else {
1044
+ let evmParachainChain: Parachain | undefined
1045
+ for (const paraId in parachains) {
1046
+ const parachain = parachains[paraId as ChainKey<"polkadot">]
1047
+ if (parachain.info.evmChainId === networkChainId) {
1048
+ evmParachainChain = parachain
1049
+ break
1050
+ }
1051
+ }
1052
+ if (!evmParachainChain) {
1053
+ throw Error(`Could not find evm chain ${networkChainId} in the list of parachains.`)
1054
+ }
1055
+ const xcTokenMap: XC20TokenMap = {}
1056
+ const assets: ERC20MetadataMap = {}
1057
+ for (const token in evmParachainChain.assets) {
1058
+ const xc20 = evmParachainChain.assets[token].xc20
1059
+ if (!xc20) {
1060
+ continue
1061
+ }
1062
+ const asset = await assetErc20Metadata(provider, xc20.toLowerCase())
1063
+ xcTokenMap[token] = xc20
1064
+ assets[xc20] = asset
1065
+ }
1066
+ const paraId = evmParachainChain.id.toString()
1067
+ if (!(paraId in precompiles)) {
1068
+ throw Error(
1069
+ `No precompile configured for parachain ${paraId} (ethereum chain ${networkChainId}).`,
1070
+ )
1071
+ }
1072
+ const precompile = precompiles[paraId]
1073
+ if ((await provider.getCode(precompile)) === undefined) {
1074
+ throw Error(
1075
+ `Could not fetch code for ${precompile} on parachain ${paraId} (ethereum chain ${networkChainId}).`,
1076
+ )
1077
+ }
1078
+ if (!evmParachainChain.xcDOT) {
1079
+ throw Error(`Could not find DOT XC20 address for evm chain ${networkChainId}.`)
1080
+ }
1081
+ const xc20DOTAsset: ERC20Metadata = await assetErc20Metadata(
1082
+ provider,
1083
+ evmParachainChain.xcDOT,
1084
+ )
1085
+ assets[evmParachainChain.xcDOT] = xc20DOTAsset
1086
+
1087
+ return {
1088
+ kind: "ethereum",
1089
+ id: networkChainId,
1090
+ key: `ethereum_${networkChainId}`,
1091
+ name,
1092
+ evmParachainId: evmParachainChain.id,
1093
+ assets,
1094
+ precompile,
1095
+ xcDOT: evmParachainChain.xcDOT,
1096
+ xcTokenMap,
1097
+ }
1098
+ }
1099
+ }
1100
+
1101
+ const ERC20_METADATA_ABI = [
1102
+ {
1103
+ type: "function",
1104
+ name: "decimals",
1105
+ inputs: [],
1106
+ outputs: [
1107
+ {
1108
+ name: "",
1109
+ type: "uint8",
1110
+ internalType: "uint8",
1111
+ },
1112
+ ],
1113
+ stateMutability: "view",
1114
+ },
1115
+ {
1116
+ type: "function",
1117
+ name: "name",
1118
+ inputs: [],
1119
+ outputs: [
1120
+ {
1121
+ name: "",
1122
+ type: "string",
1123
+ internalType: "string",
1124
+ },
1125
+ ],
1126
+ stateMutability: "view",
1127
+ },
1128
+ {
1129
+ type: "function",
1130
+ name: "symbol",
1131
+ inputs: [],
1132
+ outputs: [
1133
+ {
1134
+ name: "",
1135
+ type: "string",
1136
+ internalType: "string",
1137
+ },
1138
+ ],
1139
+ stateMutability: "view",
1140
+ },
1141
+ ]
1142
+
1143
+ async function assetErc20Metadata(
1144
+ provider: AbstractProvider,
1145
+ token: string,
1146
+ foreignId?: string,
1147
+ ): Promise<ERC20Metadata> {
1148
+ const erc20Metadata = new Contract(token, ERC20_METADATA_ABI, provider)
1149
+ const [name, symbol, decimals] = await Promise.all([
1150
+ erc20Metadata.name(),
1151
+ erc20Metadata.symbol(),
1152
+ erc20Metadata.decimals(),
1153
+ ])
1154
+ return {
1155
+ token: token.toLowerCase(),
1156
+ name: String(name),
1157
+ symbol: String(symbol),
1158
+ decimals: Number(decimals),
1159
+ foreignId: foreignId,
1160
+ }
1161
+ }
1162
+
1163
+ async function getRegisteredPnas(
1164
+ bridgehub: ApiPromise,
1165
+ ethereum: AbstractProvider,
1166
+ gatewayAddress: string,
1167
+ ): Promise<PNAMap> {
1168
+ let gateway = IGateway__factory.connect(gatewayAddress, ethereum)
1169
+ const entries = await bridgehub.query.ethereumSystem.foreignToNativeId.entries()
1170
+ const pnas: { [token: string]: { token: string; foreignId: string; ethereumlocation: any } } =
1171
+ {}
1172
+ for (const [key, value] of entries) {
1173
+ const location: any = value.toPrimitive()
1174
+ if (!location) {
1175
+ console.warn(`Could not convert ${key.toHuman()} to location`)
1176
+ continue
1177
+ }
1178
+ const tokenId = (key.args[0]?.toPrimitive() as string).toLowerCase()
1179
+ const token = await gateway.tokenAddressOf(tokenId)
1180
+ pnas[token.toLowerCase()] = {
1181
+ token: token.toLowerCase(),
1182
+ ethereumlocation: location,
1183
+ foreignId: tokenId,
1184
+ }
1185
+ }
1186
+ return pnas
1187
+ }
1188
+
1189
+ ;(async () => {
1190
+ let env = "local_e2e"
1191
+ if (process.env.NODE_ENV !== undefined) {
1192
+ env = process.env.NODE_ENV
1193
+ }
1194
+ if (!(env in SNOWBRIDGE_ENV)) {
1195
+ throw Error(`Unknown environment ${env}.`)
1196
+ }
1197
+ const environment = SNOWBRIDGE_ENV[env]
1198
+ const registry = await buildRegistry(environment)
1199
+ const routes = buildTransferLocations(registry, environment)
1200
+ const chains = buildFriendlyChains([
1201
+ ...Object.values(registry.ethereumChains),
1202
+ ...Object.values(registry.parachains),
1203
+ ...Object.values(registry.kusama?.parachains ?? {}),
1204
+ ])
1205
+ const bridge: BridgeInfo = { environment, routes, registry, chains }
1206
+ const json = generateTsObject(bridge, 4)
1207
+ const fileContents = `const registry = ${json} as const\nexport default registry\n`
1208
+ const filepath = `src/${env}_bridge_info.g.ts`
1209
+ await writeFile(filepath, fileContents)
1210
+ })()
1211
+
1212
+ function generateTsObject(value: unknown, indentSize = 4): string {
1213
+ const indentUnit = " ".repeat(indentSize)
1214
+ const serialize = (val: unknown, depth: number): string | undefined => {
1215
+ if (val === null) return "null"
1216
+ if (val === undefined) return undefined
1217
+ if (typeof val === "function" || typeof val === "symbol") return undefined
1218
+ if (typeof val === "bigint") return `${val}n`
1219
+ if (typeof val === "string") return JSON.stringify(val)
1220
+ if (typeof val === "number" || typeof val === "boolean") return String(val)
1221
+ if (Array.isArray(val)) {
1222
+ if (val.length === 0) return "[]"
1223
+ const indent = indentUnit.repeat(depth + 1)
1224
+ const closingIndent = indentUnit.repeat(depth)
1225
+ const items = val
1226
+ .map((item) => {
1227
+ const serialized = serialize(item, depth + 1)
1228
+ return `${indent}${serialized ?? "null"}`
1229
+ })
1230
+ .join(",\n")
1231
+ return `[\n${items}\n${closingIndent}]`
1232
+ }
1233
+ if (typeof val === "object") {
1234
+ const obj = val as Record<string, unknown>
1235
+ const keys = Object.keys(obj)
1236
+ const indent = indentUnit.repeat(depth + 1)
1237
+ const closingIndent = indentUnit.repeat(depth)
1238
+ const items: string[] = []
1239
+ for (const key of keys) {
1240
+ const serialized = serialize(obj[key], depth + 1)
1241
+ if (serialized === undefined) continue
1242
+ const keyLiteral = /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(key)
1243
+ ? key
1244
+ : JSON.stringify(key)
1245
+ items.push(`${indent}${keyLiteral}: ${serialized},`)
1246
+ }
1247
+ if (items.length === 0) return "{}"
1248
+ return `{\n${items.join("\n")}\n${closingIndent}}`
1249
+ }
1250
+ throw new Error(`Unsupported type in registry output: ${typeof val}`)
1251
+ }
1252
+
1253
+ const serialized = serialize(value, 0)
1254
+ if (serialized === undefined) {
1255
+ throw new Error("Registry output is not serializable")
1256
+ }
1257
+ return serialized
1258
+ }