@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.
- package/.prettierrc +23 -0
- package/.turbo/turbo-build.log +4 -0
- package/README.md +1 -3
- package/dist/index.d.ts +7 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +30 -47
- package/dist/local_e2e_bridge_info.g.d.ts +158 -0
- package/dist/local_e2e_bridge_info.g.d.ts.map +1 -0
- package/dist/local_e2e_bridge_info.g.js +159 -0
- package/dist/paseo_sepolia_bridge_info.g.d.ts +353 -0
- package/dist/paseo_sepolia_bridge_info.g.d.ts.map +1 -0
- package/dist/paseo_sepolia_bridge_info.g.js +373 -0
- package/dist/polkadot_mainnet_bridge_info.g.d.ts +2249 -0
- package/dist/polkadot_mainnet_bridge_info.g.d.ts.map +1 -0
- package/dist/polkadot_mainnet_bridge_info.g.js +2573 -0
- package/dist/transfers.d.ts +4 -0
- package/dist/transfers.d.ts.map +1 -0
- package/dist/transfers.js +96 -0
- package/dist/westend_sepolia_bridge_info.g.d.ts +469 -0
- package/dist/westend_sepolia_bridge_info.g.d.ts.map +1 -0
- package/dist/westend_sepolia_bridge_info.g.js +526 -0
- package/package.json +21 -15
- package/scripts/buildRegistry.ts +1258 -0
- package/scripts/friendlyChains.ts +74 -0
- package/src/index.ts +22 -57
- package/src/local_e2e_bridge_info.g.ts +157 -0
- package/src/paseo_sepolia_bridge_info.g.ts +372 -0
- package/src/polkadot_mainnet_bridge_info.g.ts +2597 -0
- package/src/transfers.ts +97 -0
- package/src/westend_sepolia_bridge_info.g.ts +534 -0
- package/tsconfig.json +1 -1
- package/tsconfig.scripts.json +23 -0
- package/build.ts +0 -35
- package/dist/local_e2e.registry.json +0 -347
- package/dist/paseo_sepolia.registry.json +0 -150
- package/dist/polkadot_mainnet.registry.json +0 -1484
- package/dist/westend_sepolia.registry.json +0 -227
- package/src/local_e2e.registry.json +0 -347
- package/src/paseo_sepolia.registry.json +0 -150
- package/src/polkadot_mainnet.registry.json +0 -1484
- 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
|
+
}
|