From 6f61a4863d4e7be2227e77189cb477340241a730 Mon Sep 17 00:00:00 2001 From: Jack Hamer <47187316+JackHamer09@users.noreply.github.com> Date: Thu, 23 May 2024 21:53:56 +0300 Subject: [PATCH] fix: improve config and error messages (#176) --- composables/usePortalRuntimeConfig.ts | 18 ++++++++++ composables/useScreening.ts | 9 +++-- composables/zksync/deposit/useFee.ts | 47 ++++++++++++++------------- data/networks.ts | 4 ++- data/wagmi.ts | 18 +++++++--- nuxt.config.ts | 23 +++++-------- public/config.js | 0 scripts/create-release-assets.sh | 12 +++---- store/ethereumBalance.ts | 7 ++-- store/onboard.ts | 3 +- store/zksync/ethereumBalance.ts | 5 +-- types/index.d.ts | 12 +++++++ utils/analytics.ts | 17 +++------- utils/formatters.ts | 9 +++++ 14 files changed, 112 insertions(+), 72 deletions(-) create mode 100644 composables/usePortalRuntimeConfig.ts create mode 100644 public/config.js diff --git a/composables/usePortalRuntimeConfig.ts b/composables/usePortalRuntimeConfig.ts new file mode 100644 index 000000000..332cd214f --- /dev/null +++ b/composables/usePortalRuntimeConfig.ts @@ -0,0 +1,18 @@ +export const usePortalRuntimeConfig = () => { + const runtimeConfig = window && window["##runtimeConfig"]; + + return { + nodeType: runtimeConfig?.nodeType || (process.env.NODE_TYPE as undefined | "memory" | "dockerized" | "hyperchain"), + walletConnectProjectId: runtimeConfig?.walletConnectProjectId || process.env.WALLET_CONNECT_PROJECT_ID, + ankrToken: runtimeConfig?.ankrToken || process.env.ANKR_TOKEN, + screeningApiUrl: runtimeConfig?.screeningApiUrl || process.env.SCREENING_API_URL, + analytics: { + rudder: runtimeConfig?.analytics?.rudder + ? { + key: (runtimeConfig.analytics.rudder.key || process.env.RUDDER_KEY)!, + dataplaneUrl: (runtimeConfig.analytics.rudder.dataplaneUrl || process.env.DATAPLANE_URL)!, + } + : undefined, + }, + }; +}; diff --git a/composables/useScreening.ts b/composables/useScreening.ts index ac04c41d4..02ca02d8c 100644 --- a/composables/useScreening.ts +++ b/composables/useScreening.ts @@ -4,11 +4,10 @@ import { $fetch } from "ofetch"; /* Returns void if address screening was successful */ /* Fails if address screening was unsuccessful */ const validateAddress = useMemoize(async (address: string) => { - const runtimeConfig = useRuntimeConfig(); - if (!runtimeConfig.public.screeningApiUrl) { - return; - } - const url = new URL(runtimeConfig.public.screeningApiUrl); + const portalRuntimeConfig = usePortalRuntimeConfig(); + if (!portalRuntimeConfig.screeningApiUrl) return; + + const url = new URL(portalRuntimeConfig.screeningApiUrl); url.searchParams.append("address", address); const response = await $fetch(url.toString()).catch(() => ({ result: true })); if (!response.result) { diff --git a/composables/zksync/deposit/useFee.ts b/composables/zksync/deposit/useFee.ts index 167f713ea..fb843580f 100644 --- a/composables/zksync/deposit/useFee.ts +++ b/composables/zksync/deposit/useFee.ts @@ -59,24 +59,12 @@ export default (tokens: Ref, balances: Ref) const signer = getL1VoidSigner(); if (!signer) throw new Error("Signer is not available"); - return await retry(async () => { - try { - return await signer.getFullRequiredDepositFee({ - token: ETH_TOKEN.l1Address!, - to: params.to, - }); - } catch (err) { - if (err instanceof Error && err.message.startsWith("Not enough balance for deposit!")) { - const match = err.message.match(/([\d\\.]+) ETH/); - if (feeToken.value && match?.length) { - const ethAmount = match[1].split(" ")?.[0]; - recommendedBalance.value = parseEther(ethAmount); - return; - } - } - throw err; - } - }); + return await retry(() => + signer.getFullRequiredDepositFee({ + token: ETH_TOKEN.l1Address!, + to: params.to, + }) + ); }; const getERC20TransactionFee = () => { return { @@ -98,10 +86,25 @@ export default (tokens: Ref, balances: Ref) recommendedBalance.value = undefined; if (!feeToken.value) throw new Error("Fee tokens is not available"); - if (params.tokenAddress === feeToken.value?.address) { - fee.value = await getEthTransactionFee(); - } else { - fee.value = getERC20TransactionFee(); + try { + if (params.tokenAddress === feeToken.value?.address) { + fee.value = await getEthTransactionFee(); + } else { + fee.value = getERC20TransactionFee(); + } + } catch (err) { + const message = (err as any)?.message; + if (message?.startsWith("Not enough balance for deposit!")) { + const match = message.match(/([\d\\.]+) ETH/); + if (feeToken.value && match?.length) { + const ethAmount = match[1].split(" ")?.[0]; + recommendedBalance.value = parseEther(ethAmount); + return; + } + } else if (message?.includes("insufficient funds for gas * price + value")) { + throw new Error("Insufficient funds to cover deposit fee! Please, top up your account with ETH."); + } + throw err; } /* It can be either maxFeePerGas or gasPrice */ if (fee.value && !fee.value?.maxFeePerGas) { diff --git a/data/networks.ts b/data/networks.ts index 34e7d249f..567368e8c 100644 --- a/data/networks.ts +++ b/data/networks.ts @@ -5,6 +5,8 @@ import Hyperchains from "@/hyperchains/config.json"; import type { Token } from "@/types"; import type { Chain } from "@wagmi/core/chains"; +const portalRuntimeConfig = usePortalRuntimeConfig(); + export const l1Networks = { mainnet: { ...mainnet, @@ -99,6 +101,7 @@ const publicChains: ZkSyncNetwork[] = [ }, ]; +const nodeType = portalRuntimeConfig.nodeType; const determineChainList = (): ZkSyncNetwork[] => { switch (nodeType) { case "memory": @@ -114,7 +117,6 @@ const determineChainList = (): ZkSyncNetwork[] => { return [...publicChains]; } }; -const nodeType = process.env.NODE_TYPE as undefined | "memory" | "dockerized" | "hyperchain"; export const isCustomNode = !!nodeType; export const chainList: ZkSyncNetwork[] = determineChainList(); export const defaultNetwork = chainList[0]; diff --git a/data/wagmi.ts b/data/wagmi.ts index 8839332a8..f2470cf89 100644 --- a/data/wagmi.ts +++ b/data/wagmi.ts @@ -4,6 +4,8 @@ import { defaultWagmiConfig } from "@web3modal/wagmi"; import { chainList, type ZkSyncNetwork } from "@/data/networks"; +const portalRuntimeConfig = usePortalRuntimeConfig(); + const metadata = { name: "zkSync Portal", description: "zkSync Portal - view balances, transfer and bridge tokens", @@ -11,7 +13,7 @@ const metadata = { icons: ["https://portal.zksync.io/icon.png"], }; -if (!process.env.WALLET_CONNECT_PROJECT_ID) { +if (!portalRuntimeConfig.walletConnectProjectId) { throw new Error("WALLET_CONNECT_PROJECT_ID is not set. Please set it in .env file"); } @@ -19,7 +21,7 @@ const useExistingEraChain = (network: ZkSyncNetwork) => { const existingNetworks = [zkSync, zkSyncSepoliaTestnet, zkSyncTestnet]; return existingNetworks.find((existingNetwork) => existingNetwork.id === network.id); }; -const createEraChain = (network: ZkSyncNetwork) => { +const formatZkSyncChain = (network: ZkSyncNetwork) => { return { id: network.id, name: network.name, @@ -29,6 +31,14 @@ const createEraChain = (network: ZkSyncNetwork) => { default: { http: [network.rpcUrl] }, public: { http: [network.rpcUrl] }, }, + blockExplorers: network.blockExplorerUrl + ? { + default: { + name: "Explorer", + url: network.blockExplorerUrl, + }, + } + : undefined, }; }; const getAllChains = () => { @@ -39,10 +49,10 @@ const getAllChains = () => { } }; for (const network of chainList) { + addUniqueChain(useExistingEraChain(network) ?? formatZkSyncChain(network)); if (network.l1Network) { addUniqueChain(network.l1Network); } - addUniqueChain(useExistingEraChain(network) ?? createEraChain(network)); } return chains; @@ -54,7 +64,7 @@ export const wagmiConfig = defaultWagmiConfig({ transports: Object.fromEntries( chains.map((chain) => [chain.id, fallback(chain.rpcUrls.default.http.map((e) => http(e)))]) ), - projectId: process.env.WALLET_CONNECT_PROJECT_ID, + projectId: portalRuntimeConfig.walletConnectProjectId, metadata, enableCoinbase: false, }); diff --git a/nuxt.config.ts b/nuxt.config.ts index 0e74612c1..967802968 100644 --- a/nuxt.config.ts +++ b/nuxt.config.ts @@ -37,10 +37,15 @@ export default defineNuxtConfig({ ], script: [ { - hid: "Rudder-JS", - src: "https://cdn.rudderlabs.com/v1.1/rudder-analytics.min.js", - defer: true, + src: "/config.js", }, + process.env.RUDDER_KEY + ? { + hid: "Rudder-JS", + src: "https://cdn.rudderlabs.com/v1.1/rudder-analytics.min.js", + defer: true, + } + : undefined, ], }, }, @@ -72,18 +77,6 @@ export default defineNuxtConfig({ autoprefixer: {}, }, }, - runtimeConfig: { - public: { - ankrToken: process.env.ANKR_TOKEN, - screeningApiUrl: process.env.SCREENING_API_URL, - analytics: { - rudder: { - key: process.env.RUDDER_KEY, - dataplaneUrl: process.env.DATAPLANE_URL, - }, - }, - }, - }, vite: { define: { // make these env available even outside of the Nuxt context diff --git a/public/config.js b/public/config.js new file mode 100644 index 000000000..e69de29bb diff --git a/scripts/create-release-assets.sh b/scripts/create-release-assets.sh index 9861e6dfd..517e7de36 100644 --- a/scripts/create-release-assets.sh +++ b/scripts/create-release-assets.sh @@ -3,13 +3,11 @@ # Ensure the script stops if any command fails set -e +# Run the final npm command +npm run generate + # Run the first npm command and move folder -npm run generate:node:memory -mv .output/public ./dist-node-memory +cp -r .output/public/ ./dist-node-memory/ # Run the second npm command and move folder -npm run generate:node:docker -mv .output/public ./dist-node-docker - -# Run the final npm command -npm run generate \ No newline at end of file +cp -r .output/public/ ./dist-node-docker/ \ No newline at end of file diff --git a/store/ethereumBalance.ts b/store/ethereumBalance.ts index d8d77f1a7..badebb63e 100644 --- a/store/ethereumBalance.ts +++ b/store/ethereumBalance.ts @@ -7,7 +7,8 @@ import type { TokenAmount } from "@/types"; import type { Blockchain as AnkrSupportedChains } from "@ankr.com/ankr.js"; export const useEthereumBalanceStore = defineStore("ethereumBalance", () => { - const runtimeConfig = useRuntimeConfig(); + const portalRuntimeConfig = usePortalRuntimeConfig(); + const onboardStore = useOnboardStore(); const { account } = storeToRefs(onboardStore); const { eraNetwork } = storeToRefs(useZkSyncProviderStore()); @@ -22,9 +23,9 @@ export const useEthereumBalanceStore = defineStore("ethereumBalance", () => { async () => { if (!account.value.address) throw new Error("Account is not available"); if (!eraNetwork.value.l1Network) throw new Error(`L1 network is not available on ${eraNetwork.value.name}`); - if (!runtimeConfig.public.ankrToken) throw new Error("Ankr token is not available"); + if (!portalRuntimeConfig.ankrToken) throw new Error("Ankr token is not available"); - const ankrProvider = new AnkrProvider(`https://rpc.ankr.com/multichain/${runtimeConfig.public.ankrToken}`); + const ankrProvider = new AnkrProvider(`https://rpc.ankr.com/multichain/${portalRuntimeConfig.ankrToken}`); const networkIdToAnkr = new Map([[l1Networks.mainnet.id, "eth"]]); if (!networkIdToAnkr.has(eraNetwork.value.l1Network.id)) { throw new Error(`Ankr does not support ${eraNetwork.value.l1Network.name}`); diff --git a/store/onboard.ts b/store/onboard.ts index 2a3661f62..1ab12bd20 100644 --- a/store/onboard.ts +++ b/store/onboard.ts @@ -14,6 +14,7 @@ import { wagmiConfig } from "@/data/wagmi"; import { confirmedSupportedWallets, disabledWallets } from "@/data/wallets"; export const useOnboardStore = defineStore("onboard", () => { + const portalRuntimeConfig = usePortalRuntimeConfig(); const { selectedColorMode } = useColorMode(); const { selectedNetwork, l1Network } = storeToRefs(useNetworkStore()); @@ -49,7 +50,7 @@ export const useOnboardStore = defineStore("onboard", () => { const web3modal = createWeb3Modal({ wagmiConfig, - projectId: process.env.WALLET_CONNECT_PROJECT_ID!, + projectId: portalRuntimeConfig.walletConnectProjectId!, termsConditionsUrl: "https://zksync.io/terms", privacyPolicyUrl: "https://zksync.io/privacy", themeMode: selectedColorMode.value, diff --git a/store/zksync/ethereumBalance.ts b/store/zksync/ethereumBalance.ts index 61e758ce2..f6dde1197 100644 --- a/store/zksync/ethereumBalance.ts +++ b/store/zksync/ethereumBalance.ts @@ -6,7 +6,8 @@ import { wagmiConfig } from "@/data/wagmi"; import type { Hash, TokenAmount } from "@/types"; export const useZkSyncEthereumBalanceStore = defineStore("zkSyncEthereumBalances", () => { - const runtimeConfig = useRuntimeConfig(); + const portalRuntimeConfig = usePortalRuntimeConfig(); + const onboardStore = useOnboardStore(); const ethereumBalancesStore = useEthereumBalanceStore(); const tokensStore = useZkSyncTokensStore(); @@ -73,7 +74,7 @@ export const useZkSyncEthereumBalanceStore = defineStore("zkSyncEthereumBalances async () => { if (!l1Network.value) throw new Error(`L1 network is not available on ${selectedNetwork.value.name}`); - if (([l1Networks.mainnet.id] as number[]).includes(l1Network.value?.id) && runtimeConfig.public.ankrToken) { + if (([l1Networks.mainnet.id] as number[]).includes(l1Network.value?.id) && portalRuntimeConfig.ankrToken) { return await getBalancesFromApi(); } else { return await getBalancesFromRPC(); diff --git a/types/index.d.ts b/types/index.d.ts index 0910b4c22..b64fdc37e 100644 --- a/types/index.d.ts +++ b/types/index.d.ts @@ -131,5 +131,17 @@ declare global { track: (eventName: string, params?: unknown) => void; initialized: boolean; }; + '##runtimeConfig'?: { + nodeType?: string; + walletConnectProjectId?: string; + ankrToken?: string; + screeningApiUrl?: string; + analytics?: { + rudder?: { + key: string; + dataplaneUrl: string; + } + } + } } } diff --git a/utils/analytics.ts b/utils/analytics.ts index 693252037..3efa137e2 100644 --- a/utils/analytics.ts +++ b/utils/analytics.ts @@ -1,3 +1,4 @@ +const portalRuntimeConfig = usePortalRuntimeConfig(); let analyticsLoaded = false; async function loadRudder() { @@ -5,29 +6,21 @@ async function loadRudder() { await new Promise((resolve) => setTimeout(resolve, 250)); throw new Error("Rudder not loaded"); } - const runtimeConfig = useRuntimeConfig(); window.rudderanalytics.load( - runtimeConfig.public.analytics.rudder.key, - runtimeConfig.public.analytics.rudder.dataplaneUrl + portalRuntimeConfig.analytics.rudder!.key, + portalRuntimeConfig.analytics.rudder!.dataplaneUrl ); } export async function initAnalytics(): Promise { if (analyticsLoaded) return true; - const runtimeConfig = useRuntimeConfig(); - const useRudder = Boolean( - runtimeConfig.public.analytics.rudder.key && runtimeConfig.public.analytics.rudder.dataplaneUrl - ); - + const useRudder = Boolean(portalRuntimeConfig.analytics.rudder); if (!useRudder || analyticsLoaded) { return false; } - const services = []; - if (useRudder) services.push(loadRudder()); - - await Promise.all(services); + await loadRudder(); analyticsLoaded = true; return true; } diff --git a/utils/formatters.ts b/utils/formatters.ts index 39f9e3192..6d8309dc7 100644 --- a/utils/formatters.ts +++ b/utils/formatters.ts @@ -121,6 +121,15 @@ export function formatError(error?: Error) { (error instanceof BaseError && error?.details?.startsWith("Failed to fetch")) ) { return new Error("Network error. Check your internet connection and try again."); + } else if (message.includes("missing response")) { + return new Error("Server error. Please try again later."); + } else if ( + // eslint-disable-next-line prettier/prettier + message.includes("\"finalizeEthWithdrawal\" reverted with the following reason: xx") || + // eslint-disable-next-line prettier/prettier + message.includes("\"finalizeWithdrawal\" reverted with the following reason: xx") + ) { + return new Error("Withdrawal is already finalized!"); } } return error;