@snowbridge/registry 0.3.3 → 0.4.1-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. package/.turbo/turbo-build.log +1 -2
  2. package/dist/index.d.ts +6 -2
  3. package/dist/index.d.ts.map +1 -1
  4. package/dist/index.js +27 -2
  5. package/dist/local_e2e_bridge_info.g.d.ts +141 -0
  6. package/dist/local_e2e_bridge_info.g.d.ts.map +1 -0
  7. package/dist/local_e2e_bridge_info.g.js +142 -0
  8. package/dist/paseo_sepolia_bridge_info.g.d.ts +331 -0
  9. package/dist/paseo_sepolia_bridge_info.g.d.ts.map +1 -0
  10. package/dist/paseo_sepolia_bridge_info.g.js +351 -0
  11. package/dist/polkadot_mainnet_bridge_info.g.d.ts +2137 -0
  12. package/dist/polkadot_mainnet_bridge_info.g.d.ts.map +1 -0
  13. package/dist/polkadot_mainnet_bridge_info.g.js +2454 -0
  14. package/dist/transfers.d.ts +3 -8
  15. package/dist/transfers.d.ts.map +1 -1
  16. package/dist/transfers.js +73 -215
  17. package/dist/westend_sepolia_bridge_info.g.d.ts +432 -0
  18. package/dist/westend_sepolia_bridge_info.g.d.ts.map +1 -0
  19. package/dist/westend_sepolia_bridge_info.g.js +487 -0
  20. package/package.json +7 -7
  21. package/scripts/buildRegistry.ts +609 -37
  22. package/src/index.ts +27 -2
  23. package/src/local_e2e_bridge_info.g.ts +140 -0
  24. package/src/paseo_sepolia_bridge_info.g.ts +350 -0
  25. package/src/polkadot_mainnet_bridge_info.g.ts +2477 -0
  26. package/src/transfers.ts +72 -265
  27. package/src/westend_sepolia_bridge_info.g.ts +495 -0
  28. package/dist/environment.d.ts +0 -3
  29. package/dist/environment.d.ts.map +0 -1
  30. package/dist/environment.js +0 -181
  31. package/dist/local_e2e.registry.json +0 -391
  32. package/dist/paseo_sepolia.registry.json +0 -231
  33. package/dist/polkadot_mainnet.registry.json +0 -1805
  34. package/dist/registry.d.ts +0 -3
  35. package/dist/registry.d.ts.map +0 -1
  36. package/dist/registry.js +0 -61
  37. package/dist/westend_sepolia.registry.json +0 -283
  38. package/src/environment.ts +0 -185
  39. package/src/local_e2e.registry.json +0 -391
  40. package/src/paseo_sepolia.registry.json +0 -231
  41. package/src/polkadot_mainnet.registry.json +0 -1805
  42. package/src/registry.ts +0 -63
  43. package/src/westend_sepolia.registry.json +0 -283
@@ -16,15 +16,530 @@ import {
16
16
  PrecompileMap,
17
17
  XC20TokenMap,
18
18
  XcmVersion,
19
+ BridgeInfo,
20
+ TransferRoute,
21
+ ChainId,
22
+ ChainKey,
23
+ ParachainKind,
19
24
  } from "@snowbridge/base-types"
20
25
  import { ApiPromise, HttpProvider, WsProvider } from "@polkadot/api"
21
26
  import { isFunction } from "@polkadot/util"
22
27
  import { writeFile } from "fs/promises"
23
- import { environmentFor } from "../src"
24
28
  import { AbstractProvider, Contract, ethers } from "ethers"
25
29
  import { IGatewayV1__factory as IGateway__factory } from "@snowbridge/contract-types"
26
30
  import { parachains as ParaImpl, xcmBuilder, assetsV2 } from "@snowbridge/api"
27
31
 
32
+ export type Path = {
33
+ source: ChainId
34
+ destination: ChainId
35
+ asset: string
36
+ }
37
+
38
+ const SNOWBRIDGE_ENV: { [env: string]: Environment } = {
39
+ local_e2e: {
40
+ name: "local_e2e",
41
+ ethChainId: 11155111,
42
+ beaconApiUrl: "http://127.0.0.1:9596",
43
+ ethereumChains: {
44
+ "11155111": "ws://127.0.0.1:8546",
45
+ },
46
+ relaychainUrl: "ws://127.0.0.1:9944",
47
+ parachains: {
48
+ "1000": "ws://127.0.0.1:12144",
49
+ "1002": "ws://127.0.0.1:11144",
50
+ "2000": "ws://127.0.0.1:13144",
51
+ },
52
+ gatewayContract: "0xb1185ede04202fe62d38f5db72f71e38ff3e8305",
53
+ beefyContract: "0x83428c7db9815f482a39a1715684dcf755021997",
54
+ assetHubParaId: 1000,
55
+ bridgeHubParaId: 1002,
56
+ v2_parachains: [1000],
57
+ indexerGraphQlUrl: "http://127.0.0.1/does/not/exist",
58
+ },
59
+ paseo_sepolia: {
60
+ name: "paseo_sepolia",
61
+ ethChainId: 11155111,
62
+ beaconApiUrl: "https://lodestar-sepolia.chainsafe.io",
63
+ ethereumChains: {
64
+ "11155111": "https://ethereum-sepolia-rpc.publicnode.com",
65
+ },
66
+ relaychainUrl: "wss://paseo-rpc.n.dwellir.com",
67
+ parachains: {
68
+ "1000": "wss://asset-hub-paseo-rpc.n.dwellir.com",
69
+ "1002": "wss://bridge-hub-paseo.dotters.network",
70
+ "3369": "wss://paseo-muse-rpc.polkadot.io",
71
+ "2043": `wss://parachain-testnet-rpc.origin-trail.network`,
72
+ },
73
+ gatewayContract: "0x1607c1368bc943130258318c91bbd8cff3d063e6",
74
+ beefyContract: "0x2c780945beb1241fe9c645800110cb9c4bbbb639",
75
+ assetHubParaId: 1000,
76
+ bridgeHubParaId: 1002,
77
+ v2_parachains: [1000],
78
+ indexerGraphQlUrl:
79
+ "https://snowbridge.squids.live/snowbridge-subsquid-paseo@v1/api/graphql",
80
+ metadataOverrides: {
81
+ // Change the name of TRAC
82
+ "0xef32abea56beff54f61da319a7311098d6fbcea9": {
83
+ name: "OriginTrail TRAC",
84
+ symbol: "TRAC",
85
+ },
86
+ },
87
+ },
88
+ polkadot_mainnet: {
89
+ name: "polkadot_mainnet",
90
+ ethChainId: 1,
91
+ beaconApiUrl: "https://lodestar-mainnet.chainsafe.io",
92
+ ethereumChains: {
93
+ "1": "https://ethereum-rpc.publicnode.com",
94
+ "1284": "https://rpc.api.moonbeam.network",
95
+ "8453": "https://base-rpc.publicnode.com",
96
+ "42161": "https://arbitrum-one-rpc.publicnode.com",
97
+ "10": "https://optimism-rpc.publicnode.com",
98
+ },
99
+ relaychainUrl: "https://polkadot-rpc.n.dwellir.com",
100
+ parachains: {
101
+ "1000": "wss://polkadot-asset-hub-rpc.polkadot.io",
102
+ "1002": "wss://polkadot-bridge-hub-rpc.polkadot.io",
103
+ "3369": "wss://polkadot-mythos-rpc.polkadot.io",
104
+ "2034": "wss://hydration-rpc.n.dwellir.com",
105
+ "2030": "wss://bifrost-polkadot.ibp.network",
106
+ "2004": "wss://moonbeam.ibp.network",
107
+ "2000": "wss://acala-rpc-0.aca-api.network",
108
+ "2043": "wss://parachain-rpc.origin-trail.network",
109
+ // TODO: Add back in jampton once we have an indexer in place.
110
+ //"3397": "wss://rpc.jamton.network",
111
+ },
112
+ gatewayContract: "0x27ca963c279c93801941e1eb8799c23f407d68e7",
113
+ beefyContract: "0x1817874feab3ce053d0f40abc23870db35c2affc",
114
+ assetHubParaId: 1000,
115
+ bridgeHubParaId: 1002,
116
+ v2_parachains: [1000],
117
+ indexerGraphQlUrl:
118
+ "https://subsquid.snowbridge.network/graphql",
119
+ kusama: {
120
+ assetHubParaId: 1000,
121
+ bridgeHubParaId: 1002,
122
+ parachains: {
123
+ "1000": "wss://asset-hub-kusama-rpc.n.dwellir.com",
124
+ "1002": "https://bridge-hub-kusama-rpc.n.dwellir.com",
125
+ },
126
+ },
127
+ precompiles: {
128
+ // Add override for mythos token and add precompile for moonbeam
129
+ "2004": "0x000000000000000000000000000000000000081a",
130
+ },
131
+ metadataOverrides: {
132
+ // Change the name of TRAC
133
+ "0xaa7a9ca87d3694b5755f213b5d04094b8d0f0a6f": {
134
+ name: "OriginTrail TRAC",
135
+ },
136
+ },
137
+ l2Bridge: {
138
+ acrossAPIUrl: "https://app.across.to/api",
139
+ l1AdapterAddress: "0xd3b11c36404b092645522b682832fcdee07d2668",
140
+ l1HandlerAddress: "0x924a9f036260ddd5808007e1aa95f08ed08aa569",
141
+ l1FeeTokenAddress: "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2",
142
+ l1SwapQuoterAddress: "0x61ffe014ba17989e743c5f6cb21bf9697530b21e",
143
+ l1SwapRouterAddress: "0xe592427a0aece92de3edee1f18e0157c05861564",
144
+ l2Chains: {
145
+ "8453": {
146
+ adapterAddress: "0x07fe4E7340976FC873B74bAfe3C3e5b0e01f3665".toLowerCase(),
147
+ feeTokenAddress: "0x4200000000000000000000000000000000000006",
148
+ swapRoutes: [
149
+ // WETH
150
+ {
151
+ inputToken: "0x4200000000000000000000000000000000000006",
152
+ outputToken: "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2",
153
+ swapFee: 0,
154
+ },
155
+ // USDC
156
+ {
157
+ inputToken: "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913",
158
+ outputToken: "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48",
159
+ swapFee: 500,
160
+ },
161
+ ],
162
+ },
163
+ "42161": {
164
+ adapterAddress: "0x836895Ad176235Dfe9C59b3df56C7579d90ea338".toLowerCase(),
165
+ feeTokenAddress: "0x82aF49447D8a07e3bd95BD0d56f35241523fBab1".toLowerCase(),
166
+ swapRoutes: [
167
+ // WETH
168
+ {
169
+ inputToken: "0x82aF49447D8a07e3bd95BD0d56f35241523fBab1".toLowerCase(),
170
+ outputToken: "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2",
171
+ swapFee: 0,
172
+ },
173
+ // USDC
174
+ {
175
+ inputToken: "0xaf88d065e77c8cC2239327C5EDb3A432268e5831".toLowerCase(),
176
+ outputToken: "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48",
177
+ swapFee: 500,
178
+ },
179
+ ],
180
+ },
181
+ "10": {
182
+ adapterAddress: "0x836895Ad176235Dfe9C59b3df56C7579d90ea338".toLowerCase(),
183
+ feeTokenAddress: "0x4200000000000000000000000000000000000006".toLowerCase(),
184
+ swapRoutes: [
185
+ // WETH
186
+ {
187
+ inputToken: "0x4200000000000000000000000000000000000006".toLowerCase(),
188
+ outputToken: "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2",
189
+ swapFee: 0,
190
+ },
191
+ // USDC
192
+ {
193
+ inputToken: "0x0b2C639c533813f4Aa9D7837CAf62653d097Ff85".toLowerCase(),
194
+ outputToken: "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48",
195
+ swapFee: 500,
196
+ },
197
+ ],
198
+ },
199
+ },
200
+ },
201
+ },
202
+ westend_sepolia: {
203
+ name: "westend_sepolia",
204
+ ethChainId: 11155111,
205
+ beaconApiUrl: "https://lodestar-sepolia.chainsafe.io",
206
+ ethereumChains: {
207
+ "11155111": "https://ethereum-sepolia-rpc.publicnode.com",
208
+ "84532": "https://base-sepolia-rpc.publicnode.com",
209
+ "421614": "https://arbitrum-sepolia-rpc.publicnode.com",
210
+ },
211
+ relaychainUrl: "https://westend-rpc.polkadot.io",
212
+ parachains: {
213
+ "1000": "https://westend-asset-hub-rpc.polkadot.io",
214
+ "1002": "https://westend-bridge-hub-rpc.polkadot.io",
215
+ },
216
+ gatewayContract: "0x9ed8b47bc3417e3bd0507adc06e56e2fa360a4e9",
217
+ beefyContract: "0xEBD1CFcF82BaA170b86BDe532f69A6A49c6c790D".toLowerCase(),
218
+ assetHubParaId: 1000,
219
+ bridgeHubParaId: 1002,
220
+ v2_parachains: [1000],
221
+ indexerGraphQlUrl:
222
+ "https://snowbridge.squids.live/snowbridge-subsquid-westend@v1/api/graphql",
223
+ l2Bridge: {
224
+ acrossAPIUrl: "https://testnet.across.to/api",
225
+ l1AdapterAddress: "0xCDa9bFf39cdF39E95F4B699422E422195091126d".toLowerCase(),
226
+ l1HandlerAddress: "0x924a9f036260ddd5808007e1aa95f08ed08aa569",
227
+ l1FeeTokenAddress: "0xfff9976782d46cc05630d1f6ebab18b2324d6b14",
228
+ l1SwapRouterAddress: "0x3bfa4769fb09eefc5a80d6e87c3b9c650f7ae48e",
229
+ l1SwapQuoterAddress: "0xed1f6473345f45b75f8179591dd5ba1888cf2fb3",
230
+ l2Chains: {
231
+ "84532": {
232
+ adapterAddress: "0xf06939613a3838af11104c898758220db9093679",
233
+ feeTokenAddress: "0x4200000000000000000000000000000000000006",
234
+ swapRoutes: [
235
+ // WETH
236
+ {
237
+ inputToken: "0x4200000000000000000000000000000000000006",
238
+ outputToken: "0xfff9976782d46cc05630d1f6ebab18b2324d6b14",
239
+ swapFee: 0,
240
+ },
241
+ // USDC
242
+ {
243
+ inputToken: "0x036cbd53842c5426634e7929541ec2318f3dcf7e",
244
+ outputToken: "0x1c7d4b196cb0c7b01d743fbc6116a902379c7238",
245
+ swapFee: 500,
246
+ },
247
+ ],
248
+ },
249
+ "421614": {
250
+ adapterAddress: "0xcB3d8043bDbfB0D9b30de279A09132073d1dE561".toLowerCase(),
251
+ feeTokenAddress: "0x980B62Da83eFf3D4576C647993b0c1D7faf17c73".toLowerCase(),
252
+ swapRoutes: [
253
+ // WETH
254
+ {
255
+ inputToken: "0x980B62Da83eFf3D4576C647993b0c1D7faf17c73".toLowerCase(),
256
+ outputToken: "0xfff9976782d46cc05630d1f6ebab18b2324d6b14".toLowerCase(),
257
+ swapFee: 0,
258
+ },
259
+ // USDC
260
+ {
261
+ inputToken: "0x75faf114eafb1BDbe2F0316DF893fd58CE46AA4d".toLowerCase(),
262
+ outputToken: "0x1c7d4b196cb0c7b01d743fbc6116a902379c7238".toLowerCase(),
263
+ swapFee: 500,
264
+ },
265
+ ],
266
+ },
267
+ },
268
+ },
269
+ },
270
+ }
271
+
272
+ export function defaultPathFilter(envName: string): (_: Path) => boolean {
273
+ switch (envName) {
274
+ case "westend_sepolia": {
275
+ return (path: Path) => {
276
+ // Frequency
277
+ if (path.asset === "0x72c610e05eaafcdf1fa7a2da15374ee90edb1620") {
278
+ return false
279
+ }
280
+ // Disable para to para transfers
281
+ if (path.source.kind === "polkadot" && path.destination.kind === "polkadot") {
282
+ return false
283
+ }
284
+ return true
285
+ }
286
+ }
287
+ case "paseo_sepolia":
288
+ return (path: Path) => {
289
+ // Disallow MUSE to any location but 3369
290
+ if (
291
+ path.asset === "0xb34a6924a02100ba6ef12af1c798285e8f7a16ee" &&
292
+ ((path.destination.kind === "polkadot" &&
293
+ path.destination.id !== 3369 &&
294
+ path.source.kind === "ethereum") ||
295
+ (path.source.id !== 3369 && path.source.kind === "polkadot"))
296
+ ) {
297
+ return false
298
+ }
299
+ // Disable para to para transfers
300
+ if (path.source.kind === "polkadot" && path.destination.kind === "polkadot") {
301
+ return false
302
+ }
303
+ return true
304
+ }
305
+ case "polkadot_mainnet":
306
+ return (path: Path) => {
307
+ // Disallow MYTH to any location but 3369
308
+ if (
309
+ path.asset === "0xba41ddf06b7ffd89d1267b5a93bfef2424eb2003" &&
310
+ ((path.destination.kind === "polkadot" &&
311
+ path.destination.id !== 3369 &&
312
+ path.source.kind === "ethereum") ||
313
+ (path.source.id !== 3369 && path.source.kind === "polkadot"))
314
+ ) {
315
+ return false
316
+ }
317
+
318
+ // Allow TRAC to go to Hydration (2034) and Neuroweb (2043) only
319
+ if (
320
+ path.asset === "0xaa7a9ca87d3694b5755f213b5d04094b8d0f0a6f" &&
321
+ ((path.destination.kind === "polkadot" &&
322
+ path.destination.id !== 2034 &&
323
+ path.destination.id !== 2043 &&
324
+ path.source.kind === "ethereum") ||
325
+ (path.source.id !== 2034 &&
326
+ path.source.id !== 2043 &&
327
+ path.source.kind === "polkadot"))
328
+ ) {
329
+ return false
330
+ }
331
+
332
+ // Disable stable coins in the UI from Ethereum to Polkadot
333
+ if (
334
+ (path.asset === "0x9d39a5de30e57443bff2a8307a4256c8797a3497" || // Staked USDe
335
+ path.asset === "0xa3931d71877c0e7a3148cb7eb4463524fec27fbd" || // Savings USD
336
+ path.asset === "0x6b175474e89094c44da98b954eedeac495271d0f") && // DAI
337
+ path.destination.kind === "polkadot" &&
338
+ path.destination.id === 2034 // Hydration
339
+ ) {
340
+ return false
341
+ }
342
+ // Disable para to para transfers except for hydration
343
+ if (
344
+ path.source.kind === "polkadot" &&
345
+ path.destination.kind === "polkadot" &&
346
+ !(
347
+ (path.source.id === 2034 && path.destination.id == 1000) ||
348
+ (path.source.id === 1000 && path.destination.id === 2034)
349
+ )
350
+ ) {
351
+ return false
352
+ }
353
+ return true
354
+ }
355
+
356
+ default:
357
+ return (_: Path) => true
358
+ }
359
+ }
360
+
361
+ function buildTransferLocations(
362
+ registry: AssetRegistry,
363
+ environment: Environment,
364
+ filter?: (path: Path) => boolean,
365
+ ): TransferRoute[] {
366
+ const ethChain = registry.ethereumChains[`ethereum_${registry.ethChainId}`]
367
+ const parachains = Object.values(registry.parachains).filter(
368
+ (p) => !(p.kind === "polkadot" && p.id === registry.bridgeHubParaId),
369
+ )
370
+
371
+ const pathFilter = filter ?? defaultPathFilter(registry.environment)
372
+
373
+ const locations: Path[] = []
374
+
375
+ const ethAssets = Object.keys(ethChain.assets)
376
+ // Bridged paths
377
+ for (const parachain of parachains) {
378
+ const destinationAssets = Object.keys(parachain.assets)
379
+ const commonAssets = new Set(
380
+ ethAssets.filter((sa) => destinationAssets.find((da) => da === sa)),
381
+ )
382
+ for (const asset of commonAssets) {
383
+ const p1: Path = {
384
+ source: { kind: ethChain.kind, id: ethChain.id },
385
+ destination: { kind: parachain.kind, id: parachain.id },
386
+ asset,
387
+ }
388
+ if (pathFilter(p1)) {
389
+ locations.push(p1)
390
+ }
391
+ const p2: Path = {
392
+ source: p1.destination,
393
+ destination: p1.source,
394
+ asset,
395
+ }
396
+ if (pathFilter(p2)) {
397
+ locations.push(p2)
398
+ }
399
+ if (
400
+ parachain.info.evmChainId &&
401
+ registry.ethereumChains[`ethereum_${parachain.info.evmChainId}`]
402
+ ) {
403
+ const p3: Path = {
404
+ source: {
405
+ kind: "ethereum",
406
+ id: parachain.info.evmChainId,
407
+ },
408
+ destination: p1.source, // Ethereum
409
+ asset,
410
+ }
411
+ if (pathFilter(p3)) {
412
+ locations.push(p3)
413
+ }
414
+ }
415
+ }
416
+ }
417
+
418
+ // Local paths
419
+ const assetHub = registry.parachains[`polkadot_${registry.assetHubParaId}`]
420
+ for (const parachain of parachains) {
421
+ if (parachain.kind === assetHub.kind && parachain.id === assetHub.id) continue
422
+ const assetHubAssets = Object.keys(assetHub.assets)
423
+ const destinationAssets = Object.keys(parachain.assets)
424
+
425
+ // The asset exists on ethereum, parachain and asset hub
426
+ const commonAssets = new Set(
427
+ ethAssets.filter(
428
+ (sa) =>
429
+ assetHubAssets.find((da) => da === sa) &&
430
+ destinationAssets.find((da) => da === sa),
431
+ ),
432
+ )
433
+ for (const asset of commonAssets) {
434
+ const p1: Path = {
435
+ source: { kind: assetHub.kind, id: assetHub.id },
436
+ destination: { kind: parachain.kind, id: parachain.id },
437
+ asset,
438
+ }
439
+ if (pathFilter(p1)) {
440
+ locations.push(p1)
441
+ }
442
+ const p2: Path = {
443
+ source: p1.destination, // Parachain
444
+ destination: p1.source, // Asset Hub
445
+ asset,
446
+ }
447
+ if (pathFilter(p2)) {
448
+ locations.push(p2)
449
+ }
450
+ }
451
+ }
452
+
453
+ // L2 paths
454
+ if (environment.l2Bridge) {
455
+ // Do asset hub only, in future we can loop through all v2 enabled parachains.
456
+ for (const l2ChainKey of Object.keys(environment.l2Bridge.l2Chains)) {
457
+ const l2ChainId = Number(l2ChainKey)
458
+ const l2Chain = environment.l2Bridge.l2Chains[l2ChainId]
459
+ const ethChain = registry.ethereumChains[`ethereum_l2_${l2ChainId}`]
460
+ if (!ethChain || !l2Chain) {
461
+ console.warn(`Could not find ethereum l2 chain ${l2ChainId}. Skipping...`)
462
+ continue
463
+ }
464
+ const assetHubAssets = Object.keys(assetHub.assets)
465
+ const destinationAssets = Object.values(ethChain.assets)
466
+ .map((a) => a.swapTokenAddress?.toLowerCase())
467
+ .filter((a) => a !== undefined)
468
+
469
+ // The asset exists on ethereum, parachain and asset hub
470
+ const commonAssets = new Set(
471
+ ethAssets.filter(
472
+ (sa) =>
473
+ assetHubAssets.find((da) => da === sa) &&
474
+ destinationAssets.find((da) => da === sa),
475
+ ),
476
+ )
477
+ for (const asset of commonAssets) {
478
+ const p1: Path = {
479
+ source: { kind: assetHub.kind, id: assetHub.id },
480
+ destination: { kind: ethChain.kind, id: ethChain.id },
481
+ asset,
482
+ }
483
+ if (pathFilter(p1)) {
484
+ locations.push(p1)
485
+ }
486
+ const p2: Path = {
487
+ source: p1.destination, // L2 Chain
488
+ destination: p1.source, // Asset Hub
489
+ asset,
490
+ }
491
+ if (pathFilter(p2)) {
492
+ locations.push(p2)
493
+ }
494
+ }
495
+ }
496
+
497
+ const results: TransferRoute[] = []
498
+ for (const location of locations) {
499
+ let source = results.find(
500
+ (s) =>
501
+ s.from.kind === location.source.kind &&
502
+ s.from.id === location.source.id &&
503
+ s.to.kind === location.destination.kind &&
504
+ s.to.id === location.destination.id,
505
+ )
506
+
507
+ if (!source) {
508
+ source = {
509
+ from: location.source,
510
+ to: location.destination,
511
+ assets: [],
512
+ }
513
+ results.push(source)
514
+ }
515
+ source.assets = source.assets.concat(location.asset)
516
+ }
517
+ }
518
+
519
+ // Combine all paths into routes
520
+ const results: TransferRoute[] = []
521
+ for (const location of locations) {
522
+ let source = results.find(
523
+ (s) =>
524
+ s.from.kind === location.source.kind &&
525
+ s.from.id === location.source.id &&
526
+ s.to.kind === location.destination.kind &&
527
+ s.to.id === location.destination.id,
528
+ )
529
+
530
+ if (!source) {
531
+ source = {
532
+ from: location.source,
533
+ to: location.destination,
534
+ assets: [],
535
+ }
536
+ results.push(source)
537
+ }
538
+ source.assets = source.assets.concat(location.asset)
539
+ }
540
+ return results
541
+ }
542
+
28
543
  async function buildRegistry(environment: Environment): Promise<AssetRegistry> {
29
544
  const {
30
545
  relaychainUrl,
@@ -139,6 +654,7 @@ async function buildRegistry(environment: Environment): Promise<AssetRegistry> {
139
654
  const para = await indexParachain(
140
655
  accessor,
141
656
  providers[assetHubParaId.toString()].accessor,
657
+ "polkadot",
142
658
  ethChainId,
143
659
  parachainId,
144
660
  assetHubParaId,
@@ -149,7 +665,7 @@ async function buildRegistry(environment: Environment): Promise<AssetRegistry> {
149
665
  return { parachainId, para }
150
666
  }),
151
667
  )) {
152
- paras[parachainId.toString()] = para
668
+ paras[`polkadot_${parachainId}`] = para
153
669
  }
154
670
 
155
671
  // Index Ethereum chain
@@ -170,7 +686,7 @@ async function buildRegistry(environment: Environment): Promise<AssetRegistry> {
170
686
  )
171
687
  }),
172
688
  )) {
173
- ethChains[ethChainInfo.chainId.toString()] = ethChainInfo
689
+ ethChains[ethChainInfo.key] = ethChainInfo
174
690
  }
175
691
 
176
692
  let kusamaConfig: KusamaConfig | undefined
@@ -187,6 +703,7 @@ async function buildRegistry(environment: Environment): Promise<AssetRegistry> {
187
703
  const para = await indexParachain(
188
704
  accessor,
189
705
  providers[assetHubParaId].accessor,
706
+ "kusama",
190
707
  ethChainId,
191
708
  accessor.parachainId,
192
709
  assetHubParaId,
@@ -195,7 +712,7 @@ async function buildRegistry(environment: Environment): Promise<AssetRegistry> {
195
712
  )
196
713
 
197
714
  const kusamaParas: ParachainMap = {}
198
- kusamaParas[para.parachainId] = para
715
+ kusamaParas[para.key] = para
199
716
 
200
717
  kusamaConfig = {
201
718
  parachains: kusamaParas,
@@ -304,12 +821,13 @@ async function checkSnowbridgeV2Support(
304
821
  async function indexParachain(
305
822
  parachain: ParaImpl.ParachainBase,
306
823
  assetHub: ParaImpl.ParachainBase,
824
+ kind: ParachainKind,
307
825
  ethChainId: number,
308
826
  parachainId: number,
309
827
  assetHubParaId: number,
310
828
  pnaAssets: PNAMap,
311
829
  assetOverrides: AssetOverrideMap,
312
- v2_parachains?: number[],
830
+ v2_parachains?: readonly number[],
313
831
  ): Promise<Parachain> {
314
832
  const info = await parachain.chainProperties()
315
833
 
@@ -360,6 +878,7 @@ async function indexParachain(
360
878
  info.accountType === "AccountId32"
361
879
  ? "0x0000000000000000000000000000000000000000000000000000000000000000"
362
880
  : "0x0000000000000000000000000000000000000000",
881
+ false,
363
882
  )
364
883
 
365
884
  let estimatedExecutionFeeDOT = 0n
@@ -368,7 +887,7 @@ async function indexParachain(
368
887
  const destinationXcm = xcmBuilder.buildParachainERC20ReceivedXcmOnDestination(
369
888
  parachain.provider.registry,
370
889
  ethChainId,
371
- "0x0000000000000000000000000000000000000000",
890
+ assetsV2.ETHER_TOKEN_ADDRESS,
372
891
  340282366920938463463374607431768211455n,
373
892
  340282366920938463463374607431768211455n,
374
893
  info.accountType === "AccountId32"
@@ -386,7 +905,9 @@ async function indexParachain(
386
905
  )
387
906
  }
388
907
  return {
389
- parachainId,
908
+ id: parachainId,
909
+ kind,
910
+ key: `${kind}_${parachainId}`,
390
911
  features: {
391
912
  hasPalletXcm,
392
913
  hasDryRunApi,
@@ -419,10 +940,10 @@ async function indexEthChain(
419
940
  metadataOverrides: ERC20MetadataOverrideMap,
420
941
  l2Chains: { [l2ChainId: number]: L2ForwardMetadata },
421
942
  ): Promise<EthereumChain> {
422
- const id = networkName !== "unknown" ? networkName : undefined
943
+ const name = networkName !== "unknown" ? networkName : undefined
423
944
  if (networkChainId == ethChainId) {
424
945
  // Asset Hub and get meta data
425
- const assetHub = parachains[assetHubParaId.toString()]
946
+ const assetHub = parachains[`polkadot_${assetHubParaId}`]
426
947
  const gateway = IGateway__factory.connect(gatewayAddress, provider)
427
948
 
428
949
  const assets: ERC20MetadataMap = {}
@@ -475,38 +996,42 @@ async function indexEthChain(
475
996
  )
476
997
  }
477
998
  return {
478
- chainId: networkChainId,
999
+ kind: "ethereum",
1000
+ id: networkChainId,
1001
+ name,
479
1002
  assets,
480
- id: id ?? `chain_${networkChainId}`,
1003
+ key: `ethereum_${networkChainId}`,
481
1004
  baseDeliveryGas: 120_000n,
482
1005
  }
483
1006
  } else if (networkChainId in l2Chains) {
484
1007
  const assets: ERC20MetadataMap = {}
485
1008
  for (const route of l2Chains[networkChainId].swapRoutes) {
486
1009
  let asset = await assetErc20Metadata(provider, route.inputToken)
487
- assets[route.inputToken] = {
1010
+ assets[route.inputToken.toLowerCase()] = {
488
1011
  ...asset,
489
- swapTokenAddress: route.outputToken,
1012
+ swapTokenAddress: route.outputToken.toLowerCase(),
490
1013
  swapFee: route.swapFee,
491
1014
  }
492
1015
  }
493
- assets["0x0000000000000000000000000000000000000000"] = {
494
- token: "0x0000000000000000000000000000000000000000",
1016
+ assets[assetsV2.ETHER_TOKEN_ADDRESS] = {
1017
+ token: assetsV2.ETHER_TOKEN_ADDRESS,
495
1018
  name: "Ether",
496
1019
  symbol: "Ether",
497
1020
  decimals: 18,
498
- swapTokenAddress: "0x0000000000000000000000000000000000000000",
1021
+ swapTokenAddress: assetsV2.ETHER_TOKEN_ADDRESS,
499
1022
  swapFee: 0,
500
1023
  }
501
1024
  return {
502
- chainId: networkChainId,
1025
+ kind: "ethereum_l2",
1026
+ id: networkChainId,
1027
+ name,
503
1028
  assets,
504
- id: id ?? `l2_${networkChainId}`,
1029
+ key: `ethereum_l2_${networkChainId}`,
505
1030
  }
506
1031
  } else {
507
1032
  let evmParachainChain: Parachain | undefined
508
1033
  for (const paraId in parachains) {
509
- const parachain = parachains[paraId]
1034
+ const parachain = parachains[paraId as ChainKey<"polkadot">]
510
1035
  if (parachain.info.evmChainId === networkChainId) {
511
1036
  evmParachainChain = parachain
512
1037
  break
@@ -526,7 +1051,7 @@ async function indexEthChain(
526
1051
  xcTokenMap[token] = xc20
527
1052
  assets[xc20] = asset
528
1053
  }
529
- const paraId = evmParachainChain.parachainId.toString()
1054
+ const paraId = evmParachainChain.id.toString()
530
1055
  if (!(paraId in precompiles)) {
531
1056
  throw Error(
532
1057
  `No precompile configured for parachain ${paraId} (ethereum chain ${networkChainId}).`,
@@ -548,13 +1073,15 @@ async function indexEthChain(
548
1073
  assets[evmParachainChain.xcDOT] = xc20DOTAsset
549
1074
 
550
1075
  return {
551
- chainId: networkChainId,
552
- evmParachainId: evmParachainChain.parachainId,
1076
+ kind: "ethereum",
1077
+ id: networkChainId,
1078
+ key: `ethereum_${networkChainId}`,
1079
+ name,
1080
+ evmParachainId: evmParachainChain.id,
553
1081
  assets,
554
1082
  precompile,
555
1083
  xcDOT: evmParachainChain.xcDOT,
556
1084
  xcTokenMap,
557
- id: id ?? `evm_${evmParachainChain.info.specName}`,
558
1085
  }
559
1086
  }
560
1087
  }
@@ -613,7 +1140,7 @@ async function assetErc20Metadata(
613
1140
  erc20Metadata.decimals(),
614
1141
  ])
615
1142
  return {
616
- token,
1143
+ token: token.toLowerCase(),
617
1144
  name: String(name),
618
1145
  symbol: String(symbol),
619
1146
  decimals: Number(decimals),
@@ -652,18 +1179,63 @@ async function getRegisteredPnas(
652
1179
  if (process.env.NODE_ENV !== undefined) {
653
1180
  env = process.env.NODE_ENV
654
1181
  }
655
- const registry = await buildRegistry(environmentFor(env))
656
- const json = JSON.stringify(
657
- registry,
658
- (key, value) => {
659
- if (typeof value === "bigint") {
660
- return `bigint:${value.toString()}`
1182
+ if (!(env in SNOWBRIDGE_ENV)) {
1183
+ throw Error(`Unknown environment ${env}.`)
1184
+ }
1185
+ const environment = SNOWBRIDGE_ENV[env]
1186
+ const registry = await buildRegistry(environment)
1187
+ const routes = buildTransferLocations(registry, environment)
1188
+ const bridge: BridgeInfo = { environment, routes, registry }
1189
+ const json = generateTsObject(bridge, 4)
1190
+ const fileContents = `const registry = ${json} as const\nexport default registry\n`
1191
+ const filepath = `src/${env}_bridge_info.g.ts`
1192
+ await writeFile(filepath, fileContents)
1193
+ })()
1194
+
1195
+ function generateTsObject(value: unknown, indentSize = 4): string {
1196
+ const indentUnit = " ".repeat(indentSize)
1197
+ const serialize = (val: unknown, depth: number): string | undefined => {
1198
+ if (val === null) return "null"
1199
+ if (val === undefined) return undefined
1200
+ if (typeof val === "function" || typeof val === "symbol") return undefined
1201
+ if (typeof val === "bigint") return `${val}n`
1202
+ if (typeof val === "string") return JSON.stringify(val)
1203
+ if (typeof val === "number" || typeof val === "boolean") return String(val)
1204
+ if (Array.isArray(val)) {
1205
+ if (val.length === 0) return "[]"
1206
+ const indent = indentUnit.repeat(depth + 1)
1207
+ const closingIndent = indentUnit.repeat(depth)
1208
+ const items = val
1209
+ .map((item) => {
1210
+ const serialized = serialize(item, depth + 1)
1211
+ return `${indent}${serialized ?? "null"}`
1212
+ })
1213
+ .join(",\n")
1214
+ return `[\n${items}\n${closingIndent}]`
1215
+ }
1216
+ if (typeof val === "object") {
1217
+ const obj = val as Record<string, unknown>
1218
+ const keys = Object.keys(obj)
1219
+ const indent = indentUnit.repeat(depth + 1)
1220
+ const closingIndent = indentUnit.repeat(depth)
1221
+ const items: string[] = []
1222
+ for (const key of keys) {
1223
+ const serialized = serialize(obj[key], depth + 1)
1224
+ if (serialized === undefined) continue
1225
+ const keyLiteral = /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(key)
1226
+ ? key
1227
+ : JSON.stringify(key)
1228
+ items.push(`${indent}${keyLiteral}: ${serialized},`)
661
1229
  }
662
- return value
663
- },
664
- 2,
665
- )
1230
+ if (items.length === 0) return "{}"
1231
+ return `{\n${items.join("\n")}\n${closingIndent}}`
1232
+ }
1233
+ throw new Error(`Unsupported type in registry output: ${typeof val}`)
1234
+ }
666
1235
 
667
- const filepath = `src/${env}.registry.json`
668
- await writeFile(filepath, json)
669
- })()
1236
+ const serialized = serialize(value, 0)
1237
+ if (serialized === undefined) {
1238
+ throw new Error("Registry output is not serializable")
1239
+ }
1240
+ return serialized
1241
+ }