From 35b9f7cd21765224f503b9a2a5e3d432c39db6dd Mon Sep 17 00:00:00 2001 From: Jack Hamer Date: Wed, 5 Jun 2024 16:00:28 +0300 Subject: [PATCH] fix: improve hyperchain config creation process --- data/networks.ts | 25 ++- hyperchains/README.md | 19 +-- package.json | 1 - pages/send-methods.vue | 3 +- scripts/hyperchains/common.ts | 9 + scripts/hyperchains/configure.ts | 2 +- scripts/hyperchains/create.ts | 182 ++++++++++++--------- scripts/hyperchains/utils.ts | 11 +- views/transactions/Transfer.vue | 8 +- views/transactions/WithdrawalSubmitted.vue | 7 + 10 files changed, 158 insertions(+), 109 deletions(-) create mode 100644 scripts/hyperchains/common.ts diff --git a/data/networks.ts b/data/networks.ts index 567368e8c..cf0fdb991 100644 --- a/data/networks.ts +++ b/data/networks.ts @@ -1,6 +1,7 @@ import { mainnet, sepolia } from "@wagmi/core/chains"; import Hyperchains from "@/hyperchains/config.json"; +import { PUBLIC_L1_CHAINS, type Config } from "@/scripts/hyperchains/common"; import type { Token } from "@/types"; import type { Chain } from "@wagmi/core/chains"; @@ -101,6 +102,25 @@ const publicChains: ZkSyncNetwork[] = [ }, ]; +const getHyperchains = (): ZkSyncNetwork[] => { + const hyperchains = Hyperchains as Config; + return hyperchains.map((e) => { + const network: ZkSyncNetwork = { + ...e.network, + getTokens: () => e.tokens, + }; + if (e.network.publicL1NetworkId) { + network.l1Network = PUBLIC_L1_CHAINS.find((chain) => chain.id === e.network.publicL1NetworkId); + if (!network.l1Network) { + throw new Error( + `L1 network with ID ${e.network.publicL1NetworkId} from ${network.name} config wasn't found in the list of public L1 networks.` + ); + } + } + return network; + }); +}; + const nodeType = portalRuntimeConfig.nodeType; const determineChainList = (): ZkSyncNetwork[] => { switch (nodeType) { @@ -109,10 +129,7 @@ const determineChainList = (): ZkSyncNetwork[] => { case "dockerized": return [dockerizedNode]; case "hyperchain": - return (Hyperchains as unknown as Array<{ network: ZkSyncNetwork; tokens: Token[] }>).map((e) => ({ - ...e.network, - getTokens: () => e.tokens, - })); + return getHyperchains(); default: return [...publicChains]; } diff --git a/hyperchains/README.md b/hyperchains/README.md index 6fdaec904..803b8bdf4 100644 --- a/hyperchains/README.md +++ b/hyperchains/README.md @@ -8,23 +8,6 @@ Portal supports custom ZK Stack Hyperchain nodes. There are a few different ways to configure the application: -### 📁 Configure using ZK Stack configuration files -
-If you're using ZK Stack, just link your zksync-era repo directory to configure Portal. - -1. If you haven't already setup your hyperchain yet, follow the [instructions](https://zkstack.io/quickstart) -2. Make sure to install the dependencies: - ```bash - npm install - ``` -3. 🔄 Pull your hyperchain config files by running: - ```bash - npm run hyperchain:configure - ``` - This will regenerate `/hyperchains/config.json` file. You can edit this file manually if needed. -4. 🚀 Now you can start or build the application. See [Development](#development-server) or [Production](#production) section below for more details. -
- ### 🖊️ Configure automatically with form
Fill out a simple form to configure the application. @@ -61,7 +44,9 @@ Array<{ rpcUrl: string; // L2 RPC URL name: string; blockExplorerUrl?: string; // L2 Block Explorer URL + blockExplorerApi?: string; // L2 Block Explorer API hidden?: boolean; // Hidden in the network selector + publicL1NetworkId?: number; // If you wish to use Ethereum Mainnet or Ethereum Sepolia Testnet with default configuration. Can be provided instead of `l1Network` l1Network?: { // @wagmi `Chain` structure https://wagmi.sh/core/chains#build-your-own // minimal required fields shown id: number; diff --git a/package.json b/package.json index 295e1fb54..c1d063686 100644 --- a/package.json +++ b/package.json @@ -13,7 +13,6 @@ "generate:node:hyperchain": "ts-node --transpile-only scripts/hyperchains/empty-check.ts && cross-env NODE_TYPE=hyperchain npm run generate", "generate-meta": "ts-node --transpile-only scripts/updateBridgeMetaTags.ts", "hyperchain:create": "ts-node --transpile-only scripts/hyperchains/create.ts", - "hyperchain:configure": "ts-node --transpile-only scripts/hyperchains/configure.ts", "preview": "nuxt preview", "postinstall": "nuxt prepare", "prepare": "husky install", diff --git a/pages/send-methods.vue b/pages/send-methods.vue index 934945229..043507e83 100644 --- a/pages/send-methods.vue +++ b/pages/send-methods.vue @@ -20,9 +20,8 @@ :to="{ name: 'bridge-withdraw', query: $route.query }" /> - + ; +export type Config = { network: Network; tokens: Token[] }[]; + +export const PUBLIC_L1_CHAINS = [mainnet, sepolia]; diff --git a/scripts/hyperchains/configure.ts b/scripts/hyperchains/configure.ts index 850d24c55..9678de096 100644 --- a/scripts/hyperchains/configure.ts +++ b/scripts/hyperchains/configure.ts @@ -10,7 +10,7 @@ import { join as pathJoin, parse as pathParse } from "path"; import { generateNetworkConfig, logUserInfo, promptNetworkReplacement } from "./utils"; -import type { Network } from "./utils"; +import type { Network } from "./common"; import type { Token } from "../../types"; const rootPath = process.env.ZKSYNC_HOME; diff --git a/scripts/hyperchains/create.ts b/scripts/hyperchains/create.ts index a84a7b0a7..c63ba8a9a 100644 --- a/scripts/hyperchains/create.ts +++ b/scripts/hyperchains/create.ts @@ -1,10 +1,10 @@ import { prompt } from "enquirer"; import slugify from "slugify"; +import { mainnet, Chain } from "viem/chains"; +import { PUBLIC_L1_CHAINS, type Network } from "./common"; import { generateNetworkConfig, logUserInfo, promptNetworkReplacement } from "./utils"; -import type { Network } from "./utils"; - const promptHyperchainInfo = async (): Promise => { const { id, name }: { id: number; name: string } = await prompt([ { @@ -25,95 +25,127 @@ const promptHyperchainInfo = async (): Promise => { key, rpcUrl, blockExplorerUrl, + blockExplorerApi, connectedToL1, - }: { key: string; rpcUrl: string; blockExplorerUrl: string; connectedToL1: boolean } = await prompt([ - { - message: "Hyperchain key", - name: "key", - type: "input", - required: true, - initial: slugify(name, { - lower: true, - replacement: "-", - strict: true, - }), - }, - { - message: "Hyperchain RPC URL", - name: "rpcUrl", - type: "input", - required: true, - }, - { - message: "Hyperchain Block Explorer URL (optional)", - name: "blockExplorerUrl", - type: "input", - }, - { - message: "Is hyperchain connected to L1 network?", - name: "connectedToL1", - type: "confirm", - required: true, - initial: true, - }, - ]); - - let l1Network: Network["l1Network"] | undefined; - if (connectedToL1) { - const { - l1NetworkId, - l1NetworkName, - l1NetworkRpcUrl, - l1NetworkBlockExplorerUrl, - }: { - l1NetworkId: number; - l1NetworkName: string; - l1NetworkRpcUrl: string; - l1NetworkBlockExplorerUrl: string; - } = await prompt([ + }: { key: string; rpcUrl: string; blockExplorerUrl: string; blockExplorerApi: string; connectedToL1: boolean } = + await prompt([ { - message: "L1 chain id", - name: "l1NetworkId", - type: "numeral", + message: "Hyperchain key", + name: "key", + type: "input", required: true, - float: false, + initial: slugify(name, { + lower: true, + replacement: "-", + strict: true, + }), }, { - message: "Displayed L1 chain name", - name: "l1NetworkName", + message: "Hyperchain RPC URL", + name: "rpcUrl", type: "input", required: true, }, { - message: "L1 chain RPC URL", - name: "l1NetworkRpcUrl", + message: "Hyperchain Block Explorer URL (optional)", + name: "blockExplorerUrl", type: "input", - required: true, }, { - message: "L1 chain Block Explorer URL (optional)", - name: "l1NetworkBlockExplorerUrl", + message: "Hyperchain Block Explorer API (optional)", + name: "blockExplorerApi", type: "input", }, + { + message: "Is hyperchain connected to L1 network?", + name: "connectedToL1", + type: "confirm", + required: true, + initial: true, + }, ]); - l1Network = { - id: l1NetworkId, - name: l1NetworkName, - nativeCurrency: { name: "Ether", symbol: "ETH", decimals: 18 }, - rpcUrls: { - default: { http: [l1NetworkRpcUrl] }, - public: { http: [l1NetworkRpcUrl] }, + let l1Network: Network["l1Network"] | undefined; + let publicL1NetworkId: number | undefined; + + if (connectedToL1) { + // Ask select from public L1 chains (should list all options) or `+ Add custom L1 chain` + const { selectedOption }: { selectedOption: "add-custom-chain" | Chain } = await prompt({ + message: "Select L1 chain", + name: "selectedOption", + type: "select", + required: true, + choices: [ + ...PUBLIC_L1_CHAINS.map((chain) => ({ + name: `${chain.id === mainnet.id ? "Ethereum Mainnet" : "Ethereum " + chain.name}${ + chain.testnet ? " Testnet" : "" + }`, + value: chain, + })), + { name: "+ Add custom L1 chain", value: "add-custom-chain" }, + ], + result(resultName) { + return this.choices.find((choice: { name: string }) => choice.name === resultName).value; }, - blockExplorers: l1NetworkBlockExplorerUrl - ? { - default: { - name: l1NetworkName, - url: l1NetworkBlockExplorerUrl, - }, - } - : undefined, - }; + }); + if (selectedOption === "add-custom-chain") { + const { + l1NetworkId, + l1NetworkName, + l1NetworkRpcUrl, + l1NetworkBlockExplorerUrl, + }: { + l1NetworkId: number; + l1NetworkName: string; + l1NetworkRpcUrl: string; + l1NetworkBlockExplorerUrl: string; + } = await prompt([ + { + message: "L1 chain id", + name: "l1NetworkId", + type: "numeral", + required: true, + float: false, + }, + { + message: "Displayed L1 chain name", + name: "l1NetworkName", + type: "input", + required: true, + }, + { + message: "L1 chain RPC URL", + name: "l1NetworkRpcUrl", + type: "input", + required: true, + }, + { + message: "L1 chain Block Explorer URL (optional)", + name: "l1NetworkBlockExplorerUrl", + type: "input", + }, + ]); + + l1Network = { + id: l1NetworkId, + name: l1NetworkName, + nativeCurrency: { name: "Ether", symbol: "ETH", decimals: 18 }, + rpcUrls: { + default: { http: [l1NetworkRpcUrl] }, + public: { http: [l1NetworkRpcUrl] }, + }, + blockExplorers: l1NetworkBlockExplorerUrl + ? { + default: { + name: l1NetworkName, + url: l1NetworkBlockExplorerUrl, + }, + } + : undefined, + }; + } else { + publicL1NetworkId = selectedOption.id; + } } return { @@ -122,7 +154,9 @@ const promptHyperchainInfo = async (): Promise => { key, rpcUrl, blockExplorerUrl, + blockExplorerApi, l1Network, + publicL1NetworkId, }; }; diff --git a/scripts/hyperchains/utils.ts b/scripts/hyperchains/utils.ts index c50614ba3..6a0b5e92c 100644 --- a/scripts/hyperchains/utils.ts +++ b/scripts/hyperchains/utils.ts @@ -3,14 +3,9 @@ import { prompt } from "enquirer"; import { readFileSync, writeFileSync } from "fs"; import { join as pathJoin } from "path"; -import { ETH_TOKEN } from "../../utils/constants"; - -import type { ZkSyncNetwork } from "../../data/networks"; +import type { Network, Config } from "./common"; import type { Token } from "../../types"; -export type Network = Omit; -export type Config = { network: Network; tokens: Token[] }[]; - export const configPath = pathJoin(__dirname, "../../hyperchains/config.json"); const getConfig = (): Config => { return JSON.parse(readFileSync(configPath).toString()); @@ -50,10 +45,10 @@ export const promptNetworkReplacement = async (network: Network) => { export const generateNetworkConfig = (network: Network, tokens: Token[]) => { const config = getConfig(); - // Add ETH token if it's not in the list + /* // Add ETH token if it's not in the list if (!tokens.some((token: Token) => token.address === ETH_TOKEN.address)) { tokens.unshift(ETH_TOKEN); - } + } */ config.unshift({ network, tokens }); saveConfig(config); diff --git a/views/transactions/Transfer.vue b/views/transactions/Transfer.vue index c402ab67f..0f68a36c3 100644 --- a/views/transactions/Transfer.vue +++ b/views/transactions/Transfer.vue @@ -109,7 +109,7 @@