@snowbridge/registry 0.2.20 → 0.3.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@snowbridge/registry",
3
- "version": "0.2.20",
3
+ "version": "0.3.0",
4
4
  "description": "Snowbridge Asset Registry",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {
@@ -11,6 +11,9 @@
11
11
  "main": "dist/index.js",
12
12
  "types": "dist/index.d.ts",
13
13
  "devDependencies": {
14
+ "@polkadot/api": "16.4.8",
15
+ "@polkadot/util": "13.5.6",
16
+ "ethers": "6.15.0",
14
17
  "@types/node": "24.6.2",
15
18
  "@typescript-eslint/eslint-plugin": "8.45.0",
16
19
  "@typescript-eslint/parser": "8.45.0",
@@ -20,15 +23,16 @@
20
23
  "ts-node": "10.9.2",
21
24
  "tsconfig-paths": "4.2.0",
22
25
  "typescript": "5.9.3",
23
- "@snowbridge/api": "0.2.20"
26
+ "@snowbridge/contract-types": "0.3.0",
27
+ "@snowbridge/api": "0.3.0"
24
28
  },
25
29
  "dependencies": {
26
- "@snowbridge/base-types": "0.2.20"
30
+ "@snowbridge/base-types": "0.3.0"
27
31
  },
28
32
  "scripts": {
29
- "build": "tsc --build --force",
30
- "build-registry": "npx ts-node build.ts",
33
+ "build": "tsc --build --force && tsc -p tsconfig.scripts.json",
34
+ "build-registry": "npx ts-node scripts/buildRegistry.ts",
31
35
  "lint": "eslint .",
32
- "format": "prettier src --write"
36
+ "format": "prettier src --write && prettier scripts --write"
33
37
  }
34
38
  }
@@ -0,0 +1,637 @@
1
+ import {
2
+ AssetOverrideMap,
3
+ AssetRegistry,
4
+ ChainProperties,
5
+ Environment,
6
+ ERC20Metadata,
7
+ ERC20MetadataMap,
8
+ ERC20MetadataOverrideMap,
9
+ EthereumChain,
10
+ KusamaConfig,
11
+ Parachain,
12
+ ParachainMap,
13
+ PNAMap,
14
+ PrecompileMap,
15
+ XC20TokenMap,
16
+ XcmVersion,
17
+ } from "@snowbridge/base-types"
18
+ import { ApiPromise, HttpProvider, WsProvider } from "@polkadot/api"
19
+ import { isFunction } from "@polkadot/util"
20
+ import { writeFile } from "fs/promises"
21
+ import { environmentFor } from "../src"
22
+ import { AbstractProvider, Contract, ethers } from "ethers"
23
+ import { IGatewayV1__factory as IGateway__factory } from "@snowbridge/contract-types"
24
+ import { parachains as ParaImpl, xcmBuilder, assetsV2 } from "@snowbridge/api"
25
+
26
+ async function buildRegistry(environment: Environment): Promise<AssetRegistry> {
27
+ const {
28
+ relaychainUrl,
29
+ ethereumChains,
30
+ ethChainId,
31
+ assetHubParaId,
32
+ bridgeHubParaId,
33
+ parachains,
34
+ gatewayContract,
35
+ assetOverrides,
36
+ precompiles,
37
+ metadataOverrides,
38
+ kusama,
39
+ name,
40
+ } = environment
41
+
42
+ let relayInfo: ChainProperties
43
+ {
44
+ let provider = await ApiPromise.create({
45
+ noInitWarn: true,
46
+ provider: relaychainUrl.startsWith("http")
47
+ ? new HttpProvider(relaychainUrl)
48
+ : new WsProvider(relaychainUrl),
49
+ })
50
+ relayInfo = await (await ParaImpl.paraImplementation(provider)).chainProperties()
51
+
52
+ await provider.disconnect()
53
+ }
54
+
55
+ // Connect to all eth connections
56
+ const ethProviders: {
57
+ [chainId: string]: {
58
+ chainId: number
59
+ provider: AbstractProvider
60
+ name: string
61
+ }
62
+ } = {}
63
+ {
64
+ for (const result of await Promise.all(
65
+ Object.keys(ethereumChains).map(async (ethChain) => {
66
+ let provider = ethers.getDefaultProvider(ethereumChains[ethChain])
67
+ const network = await provider.getNetwork()
68
+ return { chainId: Number(network.chainId), provider, name: network.name }
69
+ }),
70
+ )) {
71
+ ethProviders[result.chainId.toString()] = result
72
+ }
73
+ if (!(ethChainId.toString() in ethProviders)) {
74
+ throw Error(`Cannot find ethereum chain ${ethChainId} in the list of ethereum chains.`)
75
+ }
76
+ }
77
+
78
+ let pnaAssets: PNAMap = {}
79
+ let bridgeHubInfo: ChainProperties
80
+ {
81
+ if (!(bridgeHubParaId.toString() in parachains)) {
82
+ throw Error(`Cannot find bridge hub ${bridgeHubParaId} in the list of parachains.`)
83
+ }
84
+ const bridgeHubUrl = parachains[bridgeHubParaId.toString()]
85
+ let provider = await ApiPromise.create({
86
+ noInitWarn: true,
87
+ provider: bridgeHubUrl.startsWith("http")
88
+ ? new HttpProvider(bridgeHubUrl)
89
+ : new WsProvider(bridgeHubUrl),
90
+ })
91
+ bridgeHubInfo = await (await ParaImpl.paraImplementation(provider)).chainProperties()
92
+ pnaAssets = await getRegisteredPnas(
93
+ provider,
94
+ ethProviders[ethChainId].provider,
95
+ gatewayContract,
96
+ )
97
+
98
+ await provider.disconnect()
99
+ }
100
+
101
+ // Connect to all substrate parachains.
102
+ const providers: {
103
+ [paraIdKey: string]: { parachainId: number; accessor: ParaImpl.ParachainBase }
104
+ } = {}
105
+ {
106
+ for (const { parachainId, accessor } of await Promise.all(
107
+ Object.keys(parachains).map(async (parachainId) => {
108
+ const parachainUrl = parachains[parachainId]
109
+ const provider = await ApiPromise.create({
110
+ noInitWarn: true,
111
+ provider: parachainUrl.startsWith("http")
112
+ ? new HttpProvider(parachainUrl)
113
+ : new WsProvider(parachainUrl),
114
+ })
115
+ const accessor = await ParaImpl.paraImplementation(provider)
116
+ return { parachainId: accessor.parachainId, accessor }
117
+ }),
118
+ )) {
119
+ providers[parachainId.toString()] = { parachainId, accessor }
120
+ }
121
+ if (!(assetHubParaId.toString() in providers)) {
122
+ throw Error(
123
+ `Could not resolve asset hub para id ${assetHubParaId} in the list of parachains provided.`,
124
+ )
125
+ }
126
+ }
127
+
128
+ // Index parachains
129
+ const paras: ParachainMap = {}
130
+ for (const { parachainId, para } of await Promise.all(
131
+ Object.keys(providers)
132
+ .filter((parachainIdKey) => parachainIdKey !== bridgeHubParaId.toString())
133
+ .map(async (parachainIdKey) => {
134
+ const { parachainId, accessor } = providers[parachainIdKey]
135
+ const para = await indexParachain(
136
+ accessor,
137
+ providers[assetHubParaId.toString()].accessor,
138
+ ethChainId,
139
+ parachainId,
140
+ assetHubParaId,
141
+ pnaAssets,
142
+ assetOverrides ?? {},
143
+ )
144
+ return { parachainId, para }
145
+ }),
146
+ )) {
147
+ paras[parachainId.toString()] = para
148
+ }
149
+
150
+ // Index Ethereum chain
151
+ const ethChains: { [chainId: string]: EthereumChain } = {}
152
+ for (const ethChainInfo of await Promise.all(
153
+ Object.keys(ethProviders).map(async (ethChainKey) => {
154
+ return indexEthChain(
155
+ ethProviders[ethChainKey].provider,
156
+ ethProviders[ethChainKey].chainId,
157
+ ethProviders[ethChainKey].name,
158
+ ethChainId,
159
+ gatewayContract,
160
+ assetHubParaId,
161
+ paras,
162
+ precompiles ?? {},
163
+ metadataOverrides ?? {},
164
+ )
165
+ }),
166
+ )) {
167
+ ethChains[ethChainInfo.chainId.toString()] = ethChainInfo
168
+ }
169
+
170
+ let kusamaConfig: KusamaConfig | undefined
171
+ if (kusama) {
172
+ const assetHubUrl = kusama.parachains[kusama.assetHubParaId.toString()]
173
+ let provider = await ApiPromise.create({
174
+ noInitWarn: true,
175
+ provider: assetHubUrl.startsWith("http")
176
+ ? new HttpProvider(assetHubUrl)
177
+ : new WsProvider(assetHubUrl),
178
+ })
179
+ const accessor = await ParaImpl.paraImplementation(provider)
180
+
181
+ const para = await indexParachain(
182
+ accessor,
183
+ providers[assetHubParaId].accessor,
184
+ ethChainId,
185
+ accessor.parachainId,
186
+ assetHubParaId,
187
+ pnaAssets,
188
+ assetOverrides ?? {},
189
+ )
190
+
191
+ const kusamaParas: ParachainMap = {}
192
+ kusamaParas[para.parachainId] = para
193
+
194
+ kusamaConfig = {
195
+ parachains: kusamaParas,
196
+ assetHubParaId: kusama.assetHubParaId,
197
+ bridgeHubParaId: kusama.bridgeHubParaId,
198
+ }
199
+
200
+ await accessor.provider.disconnect()
201
+ }
202
+ // Dispose of all substrate connections
203
+ await Promise.all(
204
+ Object.keys(providers).map(
205
+ async (parachainKey) => await providers[parachainKey].accessor.provider.disconnect(),
206
+ ),
207
+ )
208
+
209
+ // Dispose all eth connections
210
+ Object.keys(ethProviders).forEach((parachainKey) =>
211
+ ethProviders[parachainKey].provider.destroy(),
212
+ )
213
+
214
+ return {
215
+ timestamp: new Date().toISOString(),
216
+ environment: name,
217
+ ethChainId,
218
+ gatewayAddress: gatewayContract,
219
+ assetHubParaId,
220
+ bridgeHubParaId,
221
+ relaychain: relayInfo,
222
+ bridgeHub: bridgeHubInfo,
223
+ ethereumChains: ethChains,
224
+ parachains: paras,
225
+ kusama: kusamaConfig,
226
+ }
227
+ }
228
+
229
+ async function checkSnowbridgeV2Support(
230
+ parachain: ParaImpl.ParachainBase,
231
+ ethChainId: number,
232
+ ): Promise<{
233
+ xcmVersion: XcmVersion
234
+ supportsAliasOrigin: boolean
235
+ hasEthBalance: boolean
236
+ }> {
237
+ let supportsAliasOrigin = false
238
+ let hasEthBalance = false
239
+ let xcmVersion: XcmVersion
240
+
241
+ try {
242
+ const testXcm = parachain.provider.registry.createType("XcmVersionedXcm", {
243
+ v5: [
244
+ {
245
+ aliasOrigin: {
246
+ parents: 0,
247
+ interior: {
248
+ x1: [
249
+ {
250
+ accountId32: {
251
+ id: "0x0000000000000000000000000000000000000000000000000000000000000000",
252
+ },
253
+ },
254
+ ],
255
+ },
256
+ },
257
+ },
258
+ ],
259
+ })
260
+
261
+ const weightResult = (
262
+ await parachain.provider.call.xcmPaymentApi.queryXcmWeight(testXcm)
263
+ ).toPrimitive() as any
264
+
265
+ if (weightResult.ok) {
266
+ const refTime = BigInt(weightResult.ok.refTime.toString())
267
+ const MAX_REASONABLE_WEIGHT = 10n ** 15n
268
+ // Check if AliasOrigin is supported. Often, the XCM instruction
269
+ // weight is set to MAX to make it unusable
270
+ supportsAliasOrigin = refTime < MAX_REASONABLE_WEIGHT
271
+
272
+ const etherLocation = {
273
+ parents: 2,
274
+ interior: { x1: [{ GlobalConsensus: { Ethereum: { chain_id: ethChainId } } }] },
275
+ }
276
+
277
+ // Check if ether is supported as a fee asset
278
+ const feeResult = (
279
+ await parachain.provider.call.xcmPaymentApi.queryWeightToAssetFee(weightResult.ok, {
280
+ v5: etherLocation,
281
+ })
282
+ ).toPrimitive() as any
283
+
284
+ if (feeResult.ok) {
285
+ hasEthBalance = true
286
+ }
287
+ }
288
+
289
+ xcmVersion = "v5"
290
+ } catch {
291
+ // If any call throws an error, XCM V5 is likely not supported.
292
+ xcmVersion = "v4"
293
+ }
294
+
295
+ return { xcmVersion, supportsAliasOrigin, hasEthBalance }
296
+ }
297
+
298
+ async function indexParachain(
299
+ parachain: ParaImpl.ParachainBase,
300
+ assetHub: ParaImpl.ParachainBase,
301
+ ethChainId: number,
302
+ parachainId: number,
303
+ assetHubParaId: number,
304
+ pnaAssets: PNAMap,
305
+ assetOverrides: AssetOverrideMap,
306
+ ): Promise<Parachain> {
307
+ const info = await parachain.chainProperties()
308
+
309
+ const assets = await parachain.getAssets(ethChainId, pnaAssets)
310
+ const xcDOT = parachain.getXC20DOT()
311
+ const parachainIdKey = parachainId.toString()
312
+ if (parachainIdKey in assetOverrides) {
313
+ for (const asset of assetOverrides[parachainIdKey]) {
314
+ assets[asset.token.toLowerCase()] = asset
315
+ }
316
+ }
317
+
318
+ if (Object.keys(assets).length === 0) {
319
+ console.warn(
320
+ `Cannot discover assets for ${info.specName} (parachain ${parachainId}). Please add a handler for that runtime or add overrides.`,
321
+ )
322
+ }
323
+
324
+ const hasPalletXcm = isFunction(
325
+ parachain.provider.tx.polkadotXcm.transferAssetsUsingTypeAndThen,
326
+ )
327
+ const hasDryRunRpc = isFunction(parachain.provider.rpc.system?.dryRun)
328
+ const hasDryRunApi =
329
+ isFunction(parachain.provider.call.dryRunApi?.dryRunCall) &&
330
+ isFunction(parachain.provider.call.dryRunApi?.dryRunXcm)
331
+ const hasTxPaymentApi = isFunction(parachain.provider.call.transactionPaymentApi?.queryInfo)
332
+ const hasXcmPaymentApi = isFunction(parachain.provider.call.xcmPaymentApi?.queryXcmWeight)
333
+
334
+ const { xcmVersion, supportsAliasOrigin, hasEthBalance } = await checkSnowbridgeV2Support(
335
+ parachain,
336
+ ethChainId,
337
+ )
338
+
339
+ // test getting balances
340
+ let hasDotBalance = true
341
+ try {
342
+ await parachain.getDotBalance(
343
+ info.accountType === "AccountId32"
344
+ ? "0x0000000000000000000000000000000000000000000000000000000000000000"
345
+ : "0x0000000000000000000000000000000000000000",
346
+ )
347
+ } catch (err) {
348
+ console.warn(`Spec ${info.specName} does not support dot ${err}`)
349
+ hasDotBalance = false
350
+ }
351
+
352
+ await parachain.getNativeBalance(
353
+ info.accountType === "AccountId32"
354
+ ? "0x0000000000000000000000000000000000000000000000000000000000000000"
355
+ : "0x0000000000000000000000000000000000000000",
356
+ )
357
+
358
+ let estimatedExecutionFeeDOT = 0n
359
+ let estimatedDeliveryFeeDOT = 0n
360
+ if (parachainId !== assetHubParaId) {
361
+ const destinationXcm = xcmBuilder.buildParachainERC20ReceivedXcmOnDestination(
362
+ parachain.provider.registry,
363
+ ethChainId,
364
+ "0x0000000000000000000000000000000000000000",
365
+ 340282366920938463463374607431768211455n,
366
+ 340282366920938463463374607431768211455n,
367
+ info.accountType === "AccountId32"
368
+ ? "0x0000000000000000000000000000000000000000000000000000000000000000"
369
+ : "0x0000000000000000000000000000000000000000",
370
+ "0x0000000000000000000000000000000000000000000000000000000000000000",
371
+ )
372
+ estimatedDeliveryFeeDOT = await assetHub.calculateDeliveryFeeInDOT(
373
+ parachainId,
374
+ destinationXcm,
375
+ )
376
+ estimatedExecutionFeeDOT = await parachain.calculateXcmFee(
377
+ destinationXcm,
378
+ xcmBuilder.DOT_LOCATION,
379
+ )
380
+ }
381
+ return {
382
+ parachainId,
383
+ features: {
384
+ hasPalletXcm,
385
+ hasDryRunApi,
386
+ hasTxPaymentApi,
387
+ hasDryRunRpc,
388
+ hasDotBalance,
389
+ hasEthBalance,
390
+ hasXcmPaymentApi,
391
+ supportsAliasOrigin,
392
+ xcmVersion,
393
+ },
394
+ info,
395
+ xcDOT,
396
+ assets,
397
+ estimatedExecutionFeeDOT,
398
+ estimatedDeliveryFeeDOT,
399
+ }
400
+ }
401
+
402
+ async function indexEthChain(
403
+ provider: AbstractProvider,
404
+ networkChainId: number,
405
+ networkName: string,
406
+ ethChainId: number,
407
+ gatewayAddress: string,
408
+ assetHubParaId: number,
409
+ parachains: ParachainMap,
410
+ precompiles: PrecompileMap,
411
+ metadataOverrides: ERC20MetadataOverrideMap,
412
+ ): Promise<EthereumChain> {
413
+ const id = networkName !== "unknown" ? networkName : undefined
414
+ if (networkChainId == ethChainId) {
415
+ // Asset Hub and get meta data
416
+ const assetHub = parachains[assetHubParaId.toString()]
417
+ const gateway = IGateway__factory.connect(gatewayAddress, provider)
418
+
419
+ const assets: ERC20MetadataMap = {}
420
+ for (const token in assetHub.assets) {
421
+ if (!(await gateway.isTokenRegistered(token))) {
422
+ console.warn(`Token ${token} is not registered with the gateway.`)
423
+ continue // Skip unregistered assets
424
+ }
425
+ if (token === assetsV2.ETHER_TOKEN_ADDRESS) {
426
+ assets[token] = {
427
+ token: assetHub.assets[token].token,
428
+ name: assetHub.assets[token].name,
429
+ symbol: assetHub.assets[token].symbol,
430
+ decimals: assetHub.assets[token].decimals,
431
+ }
432
+ } else {
433
+ const [asset, foreignId] = await Promise.all([
434
+ assetErc20Metadata(provider, token),
435
+ gateway.queryForeignTokenID(token),
436
+ ])
437
+ assets[token] = {
438
+ ...asset,
439
+ foreignId:
440
+ foreignId !=
441
+ "0x0000000000000000000000000000000000000000000000000000000000000000"
442
+ ? foreignId
443
+ : undefined,
444
+ // LDO gas from https://etherscan.io/tx/0x4e984250beacf693e7407c6cfdcb51229f6a549aa857d601db868b572ee2364b
445
+ // Other ERC20 token transfer on Ethereum typically ranges from 45,000 to 65,000 gas units; use 80_000 to leave a margin
446
+ deliveryGas: asset.symbol == "LDO" ? 150_000n : 80_000n,
447
+ }
448
+ }
449
+ if (token in metadataOverrides) {
450
+ const override = metadataOverrides[token]
451
+ const asset = assets[token]
452
+ if (override.name) {
453
+ asset.name = override.name
454
+ }
455
+ if (override.symbol) {
456
+ asset.symbol = override.symbol
457
+ }
458
+ if (override.decimals) {
459
+ asset.decimals = override.decimals
460
+ }
461
+ }
462
+ }
463
+ if ((await provider.getCode(gatewayAddress)) === undefined) {
464
+ throw Error(
465
+ `Could not fetch code for gateway address ${gatewayAddress} on ethereum chain ${networkChainId}.`,
466
+ )
467
+ }
468
+ return {
469
+ chainId: networkChainId,
470
+ assets,
471
+ id: id ?? `chain_${networkChainId}`,
472
+ baseDeliveryGas: 120_000n,
473
+ }
474
+ } else {
475
+ let evmParachainChain: Parachain | undefined
476
+ for (const paraId in parachains) {
477
+ const parachain = parachains[paraId]
478
+ if (parachain.info.evmChainId === networkChainId) {
479
+ evmParachainChain = parachain
480
+ break
481
+ }
482
+ }
483
+ if (!evmParachainChain) {
484
+ throw Error(`Could not find evm chain ${networkChainId} in the list of parachains.`)
485
+ }
486
+ const xcTokenMap: XC20TokenMap = {}
487
+ const assets: ERC20MetadataMap = {}
488
+ for (const token in evmParachainChain.assets) {
489
+ const xc20 = evmParachainChain.assets[token].xc20
490
+ if (!xc20) {
491
+ continue
492
+ }
493
+ const asset = await assetErc20Metadata(provider, xc20.toLowerCase())
494
+ xcTokenMap[token] = xc20
495
+ assets[xc20] = asset
496
+ }
497
+ const paraId = evmParachainChain.parachainId.toString()
498
+ if (!(paraId in precompiles)) {
499
+ throw Error(
500
+ `No precompile configured for parachain ${paraId} (ethereum chain ${networkChainId}).`,
501
+ )
502
+ }
503
+ const precompile = precompiles[paraId]
504
+ if ((await provider.getCode(precompile)) === undefined) {
505
+ throw Error(
506
+ `Could not fetch code for ${precompile} on parachain ${paraId} (ethereum chain ${networkChainId}).`,
507
+ )
508
+ }
509
+ if (!evmParachainChain.xcDOT) {
510
+ throw Error(`Could not find DOT XC20 address for evm chain ${networkChainId}.`)
511
+ }
512
+ const xc20DOTAsset: ERC20Metadata = await assetErc20Metadata(
513
+ provider,
514
+ evmParachainChain.xcDOT,
515
+ )
516
+ assets[evmParachainChain.xcDOT] = xc20DOTAsset
517
+
518
+ return {
519
+ chainId: networkChainId,
520
+ evmParachainId: evmParachainChain.parachainId,
521
+ assets,
522
+ precompile,
523
+ xcDOT: evmParachainChain.xcDOT,
524
+ xcTokenMap,
525
+ id: id ?? `evm_${evmParachainChain.info.specName}`,
526
+ }
527
+ }
528
+ }
529
+
530
+ const ERC20_METADATA_ABI = [
531
+ {
532
+ type: "function",
533
+ name: "decimals",
534
+ inputs: [],
535
+ outputs: [
536
+ {
537
+ name: "",
538
+ type: "uint8",
539
+ internalType: "uint8",
540
+ },
541
+ ],
542
+ stateMutability: "view",
543
+ },
544
+ {
545
+ type: "function",
546
+ name: "name",
547
+ inputs: [],
548
+ outputs: [
549
+ {
550
+ name: "",
551
+ type: "string",
552
+ internalType: "string",
553
+ },
554
+ ],
555
+ stateMutability: "view",
556
+ },
557
+ {
558
+ type: "function",
559
+ name: "symbol",
560
+ inputs: [],
561
+ outputs: [
562
+ {
563
+ name: "",
564
+ type: "string",
565
+ internalType: "string",
566
+ },
567
+ ],
568
+ stateMutability: "view",
569
+ },
570
+ ]
571
+
572
+ async function assetErc20Metadata(
573
+ provider: AbstractProvider,
574
+ token: string,
575
+ foreignId?: string,
576
+ ): Promise<ERC20Metadata> {
577
+ const erc20Metadata = new Contract(token, ERC20_METADATA_ABI, provider)
578
+ const [name, symbol, decimals] = await Promise.all([
579
+ erc20Metadata.name(),
580
+ erc20Metadata.symbol(),
581
+ erc20Metadata.decimals(),
582
+ ])
583
+ return {
584
+ token,
585
+ name: String(name),
586
+ symbol: String(symbol),
587
+ decimals: Number(decimals),
588
+ foreignId: foreignId,
589
+ }
590
+ }
591
+
592
+ async function getRegisteredPnas(
593
+ bridgehub: ApiPromise,
594
+ ethereum: AbstractProvider,
595
+ gatewayAddress: string,
596
+ ): Promise<PNAMap> {
597
+ let gateway = IGateway__factory.connect(gatewayAddress, ethereum)
598
+ const entries = await bridgehub.query.ethereumSystem.foreignToNativeId.entries()
599
+ const pnas: { [token: string]: { token: string; foreignId: string; ethereumlocation: any } } =
600
+ {}
601
+ for (const [key, value] of entries) {
602
+ const location: any = value.toPrimitive()
603
+ if (!location) {
604
+ console.warn(`Could not convert ${key.toHuman()} to location`)
605
+ continue
606
+ }
607
+ const tokenId = (key.args[0]?.toPrimitive() as string).toLowerCase()
608
+ const token = await gateway.tokenAddressOf(tokenId)
609
+ pnas[token.toLowerCase()] = {
610
+ token: token.toLowerCase(),
611
+ ethereumlocation: location,
612
+ foreignId: tokenId,
613
+ }
614
+ }
615
+ return pnas
616
+ }
617
+
618
+ ;(async () => {
619
+ let env = "local_e2e"
620
+ if (process.env.NODE_ENV !== undefined) {
621
+ env = process.env.NODE_ENV
622
+ }
623
+ const registry = await buildRegistry(environmentFor(env))
624
+ const json = JSON.stringify(
625
+ registry,
626
+ (key, value) => {
627
+ if (typeof value === "bigint") {
628
+ return `bigint:${value.toString()}`
629
+ }
630
+ return value
631
+ },
632
+ 2,
633
+ )
634
+
635
+ const filepath = `src/${env}.registry.json`
636
+ await writeFile(filepath, json)
637
+ })()