@@ -115,6 +115,7 @@ export const TransferComponent: FC = () => {
Under Maintenance: Check back soon!
);
+ }
return
;
};
@@ -167,7 +168,9 @@ export const TransferForm: FC = () => {
});
useEffect(() => {
- if (context == null) return;
+ if (context == null) {
+ return;
+ }
switch (source.type) {
case "substrate": {
toEthereum
@@ -290,7 +293,9 @@ export const TransferForm: FC = () => {
]);
useEffect(() => {
- if (context == null) return;
+ if (context == null) {
+ return;
+ }
if (assetErc20MetaData !== null && assetErc20MetaData[token]) {
setTokenMetadata(assetErc20MetaData[token]);
return;
@@ -328,8 +333,9 @@ export const TransferForm: FC = () => {
ethereumChainId == null ||
token === "" ||
tokenMetadata == null
- )
+ ) {
return;
+ }
updateBalance(
context,
ethereumChainId,
@@ -431,8 +437,9 @@ export const TransferForm: FC = () => {
context == null ||
ethereumChainId == null ||
sourceAccount == undefined
- )
+ ) {
return;
+ }
const toastTitle = "Approve Token Spend";
setBusyMessage("Approving spend...");
try {
diff --git a/hooks/useBridgeStatus.ts b/hooks/useBridgeStatus.ts
index 7afca2f..36f9d01 100644
--- a/hooks/useBridgeStatus.ts
+++ b/hooks/useBridgeStatus.ts
@@ -20,7 +20,9 @@ const fetchStatus = async ([env, context]: [
]): Promise
=> {
if (process.env.NEXT_PUBLIC_USE_CLIENT_SIDE_HISTORY_FETCH === "true") {
try {
- if (context === null) return null;
+ if (context === null) {
+ return null;
+ }
return await getBridgeStatus(context, env);
} catch (err) {
console.error(err);
diff --git a/hooks/useConnectEthereumWallet.ts b/hooks/useConnectEthereumWallet.ts
index 059a74d..465582c 100644
--- a/hooks/useConnectEthereumWallet.ts
+++ b/hooks/useConnectEthereumWallet.ts
@@ -39,7 +39,9 @@ export const useConnectEthereumWallet = (): [
}
} catch (err) {
let message = "Unknown Error";
- if (err instanceof Error) message = err.message;
+ if (err instanceof Error) {
+ message = err.message;
+ }
setError(message);
}
setLoading(false);
diff --git a/hooks/useConnectPolkadotWallet.ts b/hooks/useConnectPolkadotWallet.ts
index e5db0a9..76baa51 100644
--- a/hooks/useConnectPolkadotWallet.ts
+++ b/hooks/useConnectPolkadotWallet.ts
@@ -16,7 +16,9 @@ export const useConnectPolkadotWallet = (ss58Format?: number): void => {
const [walletName] = useAtom(walletNameAtom);
useEffect(() => {
- if (wallet != null || walletName == null) return;
+ if (wallet != null || walletName == null) {
+ return;
+ }
let unmounted = false;
const connect = async (): Promise => {
const { getWalletBySource } = await import("@talismn/connect-wallets");
@@ -43,7 +45,9 @@ export const useConnectPolkadotWallet = (ss58Format?: number): void => {
let unsub: () => void;
let unmounted = false;
const saveAccounts = (accounts?: WalletAccount[]): void => {
- if (accounts == null || unmounted) return;
+ if (accounts == null || unmounted) {
+ return;
+ }
if (ss58Format === undefined) {
setAccounts(accounts);
} else {
diff --git a/hooks/useEthereumProvider.ts b/hooks/useEthereumProvider.ts
index 2b84f89..9f6cc83 100644
--- a/hooks/useEthereumProvider.ts
+++ b/hooks/useEthereumProvider.ts
@@ -30,10 +30,14 @@ export const useEthereumProvider = () => {
const setEthereumChainId = useSetAtom(ethereumChainIdAtom);
useEffect(() => {
- if (ethereumProvider != null) return;
+ if (ethereumProvider != null) {
+ return;
+ }
const init = async (): Promise => {
const provider = await getEthereumProvider();
- if (provider == null) return;
+ if (provider == null) {
+ return;
+ }
const updateAccounts = (accounts: string[]): void => {
setEthereumAccount(accounts[0] ?? null);
setEthereumAccounts(accounts);
diff --git a/hooks/useSnowbridgeContext.ts b/hooks/useSnowbridgeContext.ts
index 9985ad7..a68a05b 100644
--- a/hooks/useSnowbridgeContext.ts
+++ b/hooks/useSnowbridgeContext.ts
@@ -87,7 +87,9 @@ export const useSnowbridgeContext = (): [
})
.catch((error) => {
let message = "Unknown Error";
- if (error instanceof Error) message = error.message;
+ if (error instanceof Error) {
+ message = error.message;
+ }
setLoading(false);
setError(message);
});
diff --git a/hooks/useSwitchEthereumNetwork.ts b/hooks/useSwitchEthereumNetwork.ts
index 53ac192..032b650 100644
--- a/hooks/useSwitchEthereumNetwork.ts
+++ b/hooks/useSwitchEthereumNetwork.ts
@@ -14,7 +14,9 @@ export const useSwitchEthereumNetwork = (): {
const shouldSwitchNetwork = providerChainID !== envChainId;
const switchNetwork = useCallback(async () => {
- if (!shouldSwitchNetwork || ethereum === null) return;
+ if (!shouldSwitchNetwork || ethereum === null) {
+ return;
+ }
const chainIdHex = `0x${envChainId.toString(16)}`;
try {
await ethereum.request({
diff --git a/lib/snowbridge.ts b/lib/snowbridge.ts
index 81f68b8..6ee54be 100644
--- a/lib/snowbridge.ts
+++ b/lib/snowbridge.ts
@@ -1,3 +1,8 @@
+import {
+ buildParachainConfig,
+ RegisterOfParaConfigs,
+} from "@/utils/parachainConfigs/buildParachainConfig";
+import { printify } from "@/utils/printify";
import { u8aToHex } from "@polkadot/util";
import { blake2AsU8a, encodeAddress } from "@polkadot/util-crypto";
import {
@@ -21,19 +26,93 @@ export const HISTORY_IN_SECONDS = 60 * 60 * 24 * 7 * 2; // 2 Weeks
export const ETHEREUM_BLOCK_TIME_SECONDS = 12;
export const ACCEPTABLE_BRIDGE_LATENCY = 28800; // 8 hours
+export const parachainConfigs: RegisterOfParaConfigs = {};
+
+export async function populateParachainConfigs() {
+ const paraNodes = process.env.PARACHAIN_ENDPOINTS?.split(";");
+ const etherApiKey = process.env.NEXT_PUBLIC_ALCHEMY_KEY;
+
+ if (!paraNodes || !etherApiKey) {
+ return;
+ }
+
+ for await (const endpoint of paraNodes) {
+ const newConfig = await buildParachainConfig(endpoint, etherApiKey);
+
+ if (!newConfig) {
+ return;
+ }
+ if (newConfig.name in parachainConfigs) {
+ // don't overwrite
+ } else {
+ parachainConfigs[newConfig.name] = newConfig;
+ }
+ }
+}
+
+function addParachains(
+ env: environment.SnowbridgeEnvironment,
+ parachainConfigs: RegisterOfParaConfigs,
+) {
+ const assetHubLocation = env.locations.find(({ id }) => id === "assethub");
+ if (!assetHubLocation) {
+ throw new Error(
+ `Could not find the asset hub configuration object inside of the chosen environment "${env.name}."`,
+ );
+ }
+ const pertinentParaConfigs = Object.values(parachainConfigs).filter(
+ ({ snowEnv, location }) =>
+ snowEnv === env.name &&
+ !assetHubLocation.destinationIds.includes(location.id),
+ );
+
+ if (pertinentParaConfigs.length == 0) {
+ console.log(
+ `No suitable parachains to add to the given snowbridge environment "${env.name}".`,
+ );
+ return;
+ }
+
+ // add the parachains as destinations on the assetHub location
+ // and the corresponding tokens as receivable
+
+ pertinentParaConfigs.forEach((paraConfig) => {
+ assetHubLocation.destinationIds.push(paraConfig.location.id);
+ assetHubLocation.erc20tokensReceivable.push(
+ ...paraConfig.location.erc20tokensReceivable,
+ );
+ });
+
+ env.locations.push(...pertinentParaConfigs.map((para) => para.location));
+ env.config.PARACHAINS.push(
+ ...pertinentParaConfigs.map((para) => para.endpoint),
+ );
+
+ // TODO: delete this log later
+ // during developing only:
+ console.log("SnowbridgeEnvironment after adding parachains: ", printify(env));
+ console.log(
+ `Added this parachains to the "${env.name}" snowbridge environment: ${pertinentParaConfigs.map(({ name }) => name).join(";")}.`,
+ );
+}
+
export function getEnvironmentName() {
const name = process.env.NEXT_PUBLIC_SNOWBRIDGE_ENV;
- if (!name) throw new Error("NEXT_PUBLIC_SNOWBRIDGE_ENV var not configured.");
+ if (!name) {
+ throw new Error("NEXT_PUBLIC_SNOWBRIDGE_ENV var not configured.");
+ }
return name;
}
export function getEnvironment() {
const envName = getEnvironmentName();
const env = environment.SNOWBRIDGE_ENV[envName];
- if (env === undefined)
+ if (env === undefined) {
throw new Error(
`NEXT_PUBLIC_SNOWBRIDGE_ENV configured for unknown environment '${envName}'`,
);
+ }
+ addParachains(env, parachainConfigs);
return env;
}
@@ -325,8 +404,8 @@ export async function getBridgeStatus(
!toPolkadot.bridgeOperational || !toPolkadot.channelOperational
? "Halted"
: !toPolkadot.lightClientLatencyIsAcceptable
- ? "Delayed"
- : "Normal";
+ ? "Delayed"
+ : "Normal";
const toEthereum = {
bridgeOperational:
@@ -337,8 +416,8 @@ export async function getBridgeStatus(
const toEthereumOperatingMode = !toEthereum.bridgeOperational
? "Halted"
: !toEthereum.lightClientLatencyIsAcceptable
- ? "Delayed"
- : "Normal";
+ ? "Delayed"
+ : "Normal";
let overallStatus: StatusValue = toEthereumOperatingMode;
if (toEthereumOperatingMode === "Normal") {
diff --git a/package.json b/package.json
index c1b3133..40ca27f 100644
--- a/package.json
+++ b/package.json
@@ -7,6 +7,7 @@
"build": "jest && next build",
"start": "next start",
"lint": "eslint .",
+ "lint:fix": "eslint --fix .",
"format": "prettier --write \"**/*.{js,jsx,ts,tsx,json}\"",
"test": "jest"
},
diff --git a/store/polkadot.ts b/store/polkadot.ts
index 935a764..6ffa51b 100644
--- a/store/polkadot.ts
+++ b/store/polkadot.ts
@@ -10,7 +10,9 @@ const polkadotAccountAddressAtom = atomWithStorage(
export const polkadotAccountAtom = atom(
(get) => {
const polkadotAccountAddress = get(polkadotAccountAddressAtom);
- if (polkadotAccountAddress == null) return null;
+ if (polkadotAccountAddress == null) {
+ return null;
+ }
return (
get(polkadotAccountsAtom)?.find(
(account) => account.address === get(polkadotAccountAddressAtom),
diff --git a/store/snowbridge.ts b/store/snowbridge.ts
index 627bd51..0e58e74 100644
--- a/store/snowbridge.ts
+++ b/store/snowbridge.ts
@@ -1,4 +1,10 @@
-import { getEnvironment, getEnvironmentName } from "@/lib/snowbridge";
+import {
+ getEnvironment,
+ getEnvironmentName,
+ parachainConfigs,
+ populateParachainConfigs,
+} from "@/lib/snowbridge";
+import { printify } from "@/utils/printify";
import { Context, assets } from "@snowbridge/api";
import { atom } from "jotai";
@@ -8,5 +14,13 @@ export const assetErc20MetaDataAtom = atom<{
} | null>(null);
export const snowbridgeContextAtom = atom(null);
-export const snowbridgeEnvNameAtom = atom((_) => getEnvironmentName());
-export const snowbridgeEnvironmentAtom = atom((_) => getEnvironment());
+export const snowbridgeEnvNameAtom = atom((_) => getEnvironmentName()); // this one is unnecessary. snowbridgeEnvironmentAtom.name can be used instead
+export const snowbridgeEnvironmentAtom = atom(async () => {
+ await populateParachainConfigs();
+ console.log(
+ "Getting environment after adding this parachain configs: ",
+ printify(parachainConfigs),
+ );
+
+ return getEnvironment();
+});
diff --git a/utils/doApproveSpend.ts b/utils/doApproveSpend.ts
index 6444be7..85f5068 100644
--- a/utils/doApproveSpend.ts
+++ b/utils/doApproveSpend.ts
@@ -8,7 +8,9 @@ export async function doApproveSpend(
token: string,
amount: bigint,
): Promise {
- if (context == null || ethereumProvider == null) return;
+ if (context == null || ethereumProvider == null) {
+ return;
+ }
const signer = await ethereumProvider.getSigner();
const response = await toPolkadot.approveTokenSpend(
diff --git a/utils/doDepositAndApproveWeth.ts b/utils/doDepositAndApproveWeth.ts
index f363db1..4144f97 100644
--- a/utils/doDepositAndApproveWeth.ts
+++ b/utils/doDepositAndApproveWeth.ts
@@ -9,7 +9,9 @@ export async function doDepositAndApproveWeth(
token: string,
amount: bigint,
): Promise {
- if (context == null || ethereumProvider == null) return;
+ if (context == null || ethereumProvider == null) {
+ return;
+ }
const signer = await ethereumProvider.getSigner();
const response = await toPolkadot.depositWeth(context, signer, token, amount);
diff --git a/utils/formatting.ts b/utils/formatting.ts
index 3126594..c4a52ae 100644
--- a/utils/formatting.ts
+++ b/utils/formatting.ts
@@ -24,7 +24,9 @@ export function formatBalance({
}): string {
const replaceZeros = (str: string): string => {
const newStr = str.replace(/(\.0+)$/, "").replace(/(0+)$/, "");
- if (newStr !== "") return newStr;
+ if (newStr !== "") {
+ return newStr;
+ }
return "0";
};
@@ -42,8 +44,12 @@ export function formatTime(time: number): string {
let minutes = Math.floor((time % 3600) / 60);
let seconds = Math.floor(time % 60);
let fmt = "";
- if (hours > 0) fmt += `${hours}h `;
- if (minutes > 0) fmt += `${minutes}m `;
+ if (hours > 0) {
+ fmt += `${hours}h `;
+ }
+ if (minutes > 0) {
+ fmt += `${minutes}m `;
+ }
fmt += `${seconds}s`;
return fmt;
}
diff --git a/utils/parachainConfigs/buildParachainConfig.ts b/utils/parachainConfigs/buildParachainConfig.ts
new file mode 100644
index 0000000..e82569c
--- /dev/null
+++ b/utils/parachainConfigs/buildParachainConfig.ts
@@ -0,0 +1,149 @@
+import { parachainNativeAsset } from "@snowbridge/api/dist/assets";
+import {
+ SNOWBRIDGE_ENV,
+ TransferLocation,
+} from "@snowbridge/api/dist/environment";
+// eslint-disable-next-line import/no-extraneous-dependencies
+import { IERC20Metadata__factory } from "@snowbridge/contract-types";
+
+import { getEtherApi, getSubstApi } from "./getApi";
+import { getAddressType, getSnowEnvBasedOnRelayChain } from "./paraUtils";
+
+/** Mock up from:
+ *
+ * const snowbridgeEnvironmentNames = Object.keys(SNOWBRIDGE_ENV) as Array;
+ *
+ * type SnowbridgeEnvironmentNames = (typeof snowbridgeEnvironmentNames)[number]; */
+type SnowbridgeEnvironmentNames =
+ | "local_e2e"
+ | "rococo_sepolia"
+ | "polkadot_mainnet"
+ | "unsupported_relaychain";
+
+interface ParaConfig {
+ name: string;
+ snowEnv: SnowbridgeEnvironmentNames;
+ endpoint: string;
+ pallet: string;
+ parachainId: number;
+ location: TransferLocation;
+}
+
+export interface RegisterOfParaConfigs {
+ [name: string]: ParaConfig;
+}
+/**
+ * Gets all necessary Information about a _Substrate_ parachain to extend the Snowbridge Environment with it.
+ *
+ * It gathers information from a node of the parachain under `paraEndpoint` and the contract on the ethereum side through and _Alchemy_ node.
+ *
+ * If the name of the Switch Pallet is not passed, it assumes that first switch pool is between native token and its erc20 wrapped counterpart.
+ *
+ * @param paraEndpoint Endpoint of a Substrate Parachain Node.
+ * @param etherApiKey API Key to use connect to ether-node through Alchemy.
+ * @param switchPalletName Name of wished switch pallet on the parachain itself.
+ * @returns necessary data to extend the _Snowbridge Environment_ or `void` on failure.
+ */
+export async function buildParachainConfig(
+ paraEndpoint: string,
+ etherApiKey: string,
+ switchPalletName: string = "assetSwitchPool1",
+): Promise {
+ const paraApi = await getSubstApi(paraEndpoint);
+
+ if (!paraApi) {
+ console.log(`Could not connect to parachain API under "${paraEndpoint}"`);
+ return;
+ }
+
+ const paraId = (
+ await paraApi.query.parachainInfo.parachainId()
+ ).toPrimitive() as number;
+
+ // Get information about the token on it's native parachain
+ const chainName = (await paraApi.rpc.system.chain()).toString();
+ const snowBridgeEnvName = (await getSnowEnvBasedOnRelayChain(
+ paraApi,
+ SNOWBRIDGE_ENV,
+ )) as SnowbridgeEnvironmentNames;
+
+ if (snowBridgeEnvName === "unsupported_relaychain") {
+ // error message already logged from getSnowEnvBasedOnRelayChain()
+ return;
+ }
+
+ // debugger
+ console.log("snowBridgeEnvName: ", snowBridgeEnvName);
+
+ /** The Snowbridge team decided to set the amount of the existential deposit as the minimal transfer amount. */
+ const minimumTransferAmount = BigInt(
+ paraApi.consts.balances.existentialDeposit.toString(),
+ );
+
+ const { tokenDecimal } = await parachainNativeAsset(paraApi);
+
+ const addressType = await getAddressType(paraApi);
+
+ // debugger
+ console.log(`The address type used is: ${addressType}`);
+
+ // Get information about the wrapped erc20 token from parachain
+ const switchPair = await paraApi.query[switchPalletName].switchPair();
+ const contractAddress = (switchPair as any).unwrap().remoteAssetId.toJSON().v4
+ .interior.x2[1].accountKey20.key;
+
+ const xcmFee = (switchPair as any).unwrap().remoteXcmFee.toJSON().v4.fun
+ .fungible as number;
+
+ // debuggers
+ console.log("contractAddress: ", contractAddress);
+ console.log("xcmFee: ", xcmFee);
+
+ // Get information about the wrapped erc20 token from ethereum
+ const etherEndpoint =
+ SNOWBRIDGE_ENV[snowBridgeEnvName].config.ETHEREUM_API(etherApiKey);
+ const etherApi = await getEtherApi(etherEndpoint);
+
+ if (!etherApi) {
+ console.log(`Could not connect to ethereum API under "${etherEndpoint}"`);
+ return;
+ }
+
+ const ercTokenMetadata = IERC20Metadata__factory.connect(
+ contractAddress,
+ etherApi,
+ );
+ const ercSymbol = await ercTokenMetadata.symbol();
+
+ paraApi.disconnect();
+ etherApi.destroy();
+
+ return {
+ name: chainName,
+ snowEnv: snowBridgeEnvName,
+ endpoint: paraEndpoint,
+ pallet: switchPalletName,
+ parachainId: paraId,
+ location: {
+ id: chainName.toLowerCase().replaceAll(/\s/g, ""),
+ name: chainName,
+ type: "substrate",
+ destinationIds: ["assethub"],
+ paraInfo: {
+ paraId: paraId,
+ destinationFeeDOT: BigInt(xcmFee),
+ skipExistentialDepositCheck: false,
+ addressType: addressType,
+ decimals: tokenDecimal,
+ maxConsumers: 16,
+ },
+ erc20tokensReceivable: [
+ {
+ id: ercSymbol,
+ address: contractAddress,
+ minimumTransferAmount,
+ },
+ ],
+ },
+ };
+}
diff --git a/utils/parachainConfigs/getApi.ts b/utils/parachainConfigs/getApi.ts
new file mode 100644
index 0000000..8c1aa2f
--- /dev/null
+++ b/utils/parachainConfigs/getApi.ts
@@ -0,0 +1,79 @@
+import { ApiPromise, HttpProvider, WsProvider } from "@polkadot/api";
+
+/** Attempts to establish an API connection with a _Substrate_ blockchain node using the provided URL `wsUrl`.
+ *
+ * If the initial connection is successful, returns a ready to use `ApiPromise` instance; otherwise returns `undefined`.
+ *
+ * If the WebSocket turns unavailable after the initial connection, it will persistently retry to connect.
+ *
+ * @param wsUrl HTTP or WS endpoint of a _Substrate_ blockchain node.
+ * @returns the api `ApiPromise` if connection was established or `undefined` otherwise.
+ */
+export async function getSubstApi(
+ wsUrl: string,
+): Promise {
+ const provider = wsUrl.startsWith("http")
+ ? new HttpProvider(wsUrl)
+ : new WsProvider(wsUrl);
+ try {
+ // // #1 Variant
+ // const api = await ApiPromise.create({
+ // provider,
+ // throwOnConnect: true,
+ // });
+
+ // return api;
+
+ // #2 Variant
+ const api = new ApiPromise({
+ provider,
+ });
+
+ return await api.isReadyOrError;
+ } catch (error) {
+ console.error(
+ `Could not connect to API under ${wsUrl}. Because: ${error instanceof Error ? error.message : JSON.stringify(error)}`,
+ );
+
+ // stop from trying to reconnect to the webSocket
+ provider.disconnect();
+ return undefined;
+ }
+}
+
+import { AbstractProvider, JsonRpcProvider, WebSocketProvider } from "ethers";
+
+/**
+ * Attempts to establish a connection with an _Ethereum_ node using the provided URL `nodeUrl`.
+ *
+ * If the initial connection is successful, it returns a ready to use "provider" API instance.
+ *
+ * When passing a HTTP endpoint:
+ * If the connection attempt fails, the function returns `undefined`.
+ *
+ * Sadly, when passing a WebSocket endpoint:
+ * If the connection attempt fails, an error will be thrown asynchronously, crashing the app.
+ *
+ * @param nodeUrl - The HTTP or WebSocket endpoint of an Ethereum node.
+ * @returns A promise that resolves to a provider instance if the connection is established, or `undefined` if the connection attempt fails.
+ */
+export async function getEtherApi(
+ nodeUrl: string,
+): Promise {
+ const provider = nodeUrl.startsWith("http")
+ ? new JsonRpcProvider(nodeUrl)
+ : new WebSocketProvider(nodeUrl, undefined, { polling: true });
+
+ try {
+ // Verify the connection is successful by making a basic request
+ await provider.getBlockNumber();
+
+ return provider;
+ } catch (err) {
+ console.error(
+ `Could not connect to Ethereum node at ${nodeUrl}. Reason: ${err instanceof Error ? err.message : JSON.stringify(err)}`,
+ );
+ provider.destroy();
+ return undefined;
+ }
+}
diff --git a/utils/parachainConfigs/paraUtils.ts b/utils/parachainConfigs/paraUtils.ts
new file mode 100644
index 0000000..81e7a6e
--- /dev/null
+++ b/utils/parachainConfigs/paraUtils.ts
@@ -0,0 +1,115 @@
+import { ApiPromise } from "@polkadot/api";
+import { Bytes, Option, Struct, TypeDefInfo, u32 } from "@polkadot/types";
+import { H256 } from "@polkadot/types/interfaces";
+
+import {
+ AddressType,
+ SnowbridgeEnvironment,
+} from "@snowbridge/api/dist/environment";
+
+import { getSubstApi } from "./getApi";
+
+// explicit Definition:
+interface PolkadotPrimitivesV5PersistedValidationData extends Struct {
+ readonly parentHead: Bytes;
+ readonly relayParentNumber: u32;
+ readonly relayParentStorageRoot: H256;
+ readonly maxPovSize: u32;
+}
+
+async function getRelaysChainLastParentBlockInfo(api: ApiPromise) {
+ const validationData =
+ await api.query.parachainSystem.validationData<
+ Option
+ >();
+
+ if (validationData.isNone) {
+ throw new Error(
+ "This is not a parachain or validation data is unavailable",
+ );
+ }
+ const { relayParentNumber, relayParentStorageRoot } = validationData.unwrap();
+
+ const lastRelayParentBlock = relayParentNumber.toNumber();
+ const lastRelayParentBlockStorageRoot = relayParentStorageRoot.toHex();
+
+ // console.log("lastRelayParentBlock: ", lastRelayParentBlock);
+ // console.log(
+ // "lastRelayParentBlockStorageRoot: ",
+ // lastRelayParentBlockStorageRoot,
+ // );
+
+ return {
+ lastRelayParentBlock,
+ lastRelayParentBlockStorageRoot,
+ };
+}
+
+/** Returns to which `SnowbridgeEnvironment` the parachain under the give `paraApi` corresponds to. */
+export async function getSnowEnvBasedOnRelayChain(
+ paraApi: ApiPromise,
+ snowEnvironments: { [id: string]: SnowbridgeEnvironment },
+) {
+ const { lastRelayParentBlock, lastRelayParentBlockStorageRoot } =
+ await getRelaysChainLastParentBlockInfo(paraApi);
+
+ const parachainName = await paraApi.rpc.system.chain();
+
+ const coldEnvironments = Object.values(snowEnvironments);
+
+ for await (const env of coldEnvironments) {
+ const relayApi = await getSubstApi(env.config.RELAY_CHAIN_URL);
+
+ if (!relayApi) {
+ continue;
+ }
+
+ const examinedBlockHash =
+ await relayApi.rpc.chain.getBlockHash(lastRelayParentBlock);
+
+ const examinedBlock = await relayApi.rpc.chain.getBlock(examinedBlockHash);
+ const relaychainName = await relayApi.rpc.system.chain();
+
+ await relayApi.disconnect();
+
+ if (
+ examinedBlock.block.header.stateRoot.toHex() ===
+ lastRelayParentBlockStorageRoot
+ ) {
+ console.log(`"${parachainName}" relays on chain: ${relaychainName}`);
+ return env.name;
+ }
+ console.log(
+ `"${parachainName}" does not relay on chain: ${relaychainName}`,
+ );
+ }
+
+ console.log(
+ `"${parachainName}" relays on a blockchain that is not part of the Snowbridge API.`,
+ );
+
+ return "unsupported_relaychain";
+}
+
+export async function getAddressType(api: ApiPromise): Promise {
+ // Assume that the first type defined in the runtime is the AccountId
+ const lookedUpType = api.registry.lookup.getTypeDef(0);
+ if (lookedUpType.type === "AccountId32") {
+ return "32byte";
+ }
+
+ if (lookedUpType.type === "AccountId20") {
+ return "20byte";
+ }
+
+ if (lookedUpType.info === TypeDefInfo.VecFixed) {
+ const length = lookedUpType.length;
+ if (length === 20) {
+ return "20byte";
+ }
+ if (length === 32) {
+ return "32byte";
+ }
+ }
+ return "both";
+}
diff --git a/utils/printify.ts b/utils/printify.ts
new file mode 100644
index 0000000..81cfecb
--- /dev/null
+++ b/utils/printify.ts
@@ -0,0 +1,15 @@
+/**
+ * Turns `object` into a printable and human readable sting.
+ *
+ * It handles all JSON compatible types and BigInts.
+ *
+ * @param object
+ * @returns string representation of the object.
+ */
+export function printify(object: object) {
+ return JSON.stringify(
+ object,
+ (_, v) => (typeof v === "bigint" ? v.toString() : v), // replacer of bigInts
+ 2,
+ );
+}