@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/.turbo/turbo-build.log +2 -2
- package/README.md +1 -3
- package/dist/environment.d.ts +3 -0
- package/dist/environment.d.ts.map +1 -0
- package/dist/environment.js +119 -0
- package/dist/index.d.ts +3 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +16 -58
- package/dist/polkadot_mainnet.registry.json +10 -10
- package/dist/registry.d.ts +3 -0
- package/dist/registry.d.ts.map +1 -0
- package/dist/registry.js +61 -0
- package/dist/transfers.d.ts +9 -0
- package/dist/transfers.d.ts.map +1 -0
- package/dist/transfers.js +238 -0
- package/package.json +10 -6
- package/scripts/buildRegistry.ts +637 -0
- package/src/environment.ts +123 -0
- package/src/index.ts +3 -63
- package/src/polkadot_mainnet.registry.json +10 -10
- package/src/registry.ts +63 -0
- package/src/transfers.ts +290 -0
- package/tsconfig.json +1 -1
- package/tsconfig.scripts.json +23 -0
- package/build.ts +0 -28
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@snowbridge/registry",
|
|
3
|
-
"version": "0.
|
|
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/
|
|
26
|
+
"@snowbridge/contract-types": "0.3.0",
|
|
27
|
+
"@snowbridge/api": "0.3.0"
|
|
24
28
|
},
|
|
25
29
|
"dependencies": {
|
|
26
|
-
"@snowbridge/base-types": "0.
|
|
30
|
+
"@snowbridge/base-types": "0.3.0"
|
|
27
31
|
},
|
|
28
32
|
"scripts": {
|
|
29
|
-
"build": "tsc --build --force",
|
|
30
|
-
"build-registry": "npx ts-node
|
|
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
|
+
})()
|