diff --git a/packages/app/README.md b/packages/app/README.md
index 061fdc096a..1023fc9388 100644
--- a/packages/app/README.md
+++ b/packages/app/README.md
@@ -44,7 +44,6 @@ const config: EnvironmentConfig = {
verificationApiUrl: "https://zksync2-testnet-explorer.zksync.dev",
hostnames: ["localhost"],
icon: "/images/icons/zksync-arrows.svg",
- l1ExplorerUrl: "https://goerli.etherscan.io",
l2ChainId: 270,
l2NetworkName: "Local",
l2WalletUrl: "https://goerli.staging-portal.zksync.dev/",
@@ -59,7 +58,6 @@ const config: EnvironmentConfig = {
verificationApiUrl: "https://zksync2-testnet-explorer.zksync.dev",
hostnames: ["localhost"],
icon: "/images/icons/zksync-arrows.svg",
- l1ExplorerUrl: "https://goerli.etherscan.io",
l2ChainId: 270,
l2NetworkName: "Local Hyperchain",
l2WalletUrl: "https://goerli.staging-portal.zksync.dev/",
diff --git a/packages/app/src/components/AddressLink.vue b/packages/app/src/components/AddressLink.vue
index e936f4517e..922573341e 100644
--- a/packages/app/src/components/AddressLink.vue
+++ b/packages/app/src/components/AddressLink.vue
@@ -1,9 +1,18 @@
-
+
{{ formattedAddress }}
+
+
+ {{ formattedAddress }}
+
+
{{ formattedAddress }}
diff --git a/packages/app/src/components/batches/InfoTable.vue b/packages/app/src/components/batches/InfoTable.vue
index 189a610649..934f06ac9d 100644
--- a/packages/app/src/components/batches/InfoTable.vue
+++ b/packages/app/src/components/batches/InfoTable.vue
@@ -85,7 +85,9 @@ const tableInfoItems = computed(() => {
tooltip: t(`batches.${key}Tooltip`),
value: { value: props.batch[key] },
component: CopyContent,
- url: `${currentNetwork.value.l1ExplorerUrl}/tx/${props.batch[key]}`,
+ url: currentNetwork.value.l1ExplorerUrl
+ ? `${currentNetwork.value.l1ExplorerUrl}/tx/${props.batch[key]}`
+ : undefined,
...(key === "proveTxHash" &&
props.batch.isProvenByNewProver && {
additionalContentComponent: NewProverInfoBox,
diff --git a/packages/app/src/components/blocks/InfoTable.vue b/packages/app/src/components/blocks/InfoTable.vue
index e7719399c0..1b3eb3bdaf 100644
--- a/packages/app/src/components/blocks/InfoTable.vue
+++ b/packages/app/src/components/blocks/InfoTable.vue
@@ -110,7 +110,9 @@ const tableInfoItems = computed(() => {
tooltip: t(`blocks.table.${key}Tooltip`),
value: { value: props.block[key] },
component: CopyContent,
- url: `${currentNetwork.value.l1ExplorerUrl}/tx/${props.block[key]}`,
+ url: currentNetwork.value.l1ExplorerUrl
+ ? `${currentNetwork.value.l1ExplorerUrl}/tx/${props.block[key]}`
+ : undefined,
...(key === "proveTxHash" &&
props.block.isProvenByNewProver && {
additionalContentComponent: NewProverInfoBox,
diff --git a/packages/app/src/components/transactions/Status.vue b/packages/app/src/components/transactions/Status.vue
index ee442bf5f1..9fb3172d57 100644
--- a/packages/app/src/components/transactions/Status.vue
+++ b/packages/app/src/components/transactions/Status.vue
@@ -20,12 +20,14 @@
-
{{ finishedStatus.text }}
-
+
@@ -36,12 +38,12 @@
{{ item.text }}
-
+
{{ item.text }}
@@ -103,19 +105,25 @@
:key="index"
>
{{ finishedStatus.text }}
-
+
diff --git a/packages/app/src/components/transfers/Table.vue b/packages/app/src/components/transfers/Table.vue
index bf1278e2d7..446e3becbc 100644
--- a/packages/app/src/components/transfers/Table.vue
+++ b/packages/app/src/components/transfers/Table.vue
@@ -244,6 +244,9 @@ watch(
.transfers-data-link-value {
@apply block cursor-pointer text-sm font-medium;
}
+ span.transfers-data-link-value {
+ @apply cursor-default;
+ }
}
}
diff --git a/packages/app/src/configs/index.ts b/packages/app/src/configs/index.ts
index aaa7da16c4..9c5ac1de48 100644
--- a/packages/app/src/configs/index.ts
+++ b/packages/app/src/configs/index.ts
@@ -9,7 +9,7 @@ export type NetworkConfig = {
l2NetworkName: string;
l2WalletUrl: string;
l2ChainId: 270 | 280 | 324;
- l1ExplorerUrl: string;
+ l1ExplorerUrl?: string;
maintenance: boolean;
published: boolean;
hostnames: string[];
diff --git a/packages/app/tests/components/AddressLink.spec.ts b/packages/app/tests/components/AddressLink.spec.ts
index add4f8a8b0..cde2801f78 100644
--- a/packages/app/tests/components/AddressLink.spec.ts
+++ b/packages/app/tests/components/AddressLink.spec.ts
@@ -1,9 +1,20 @@
-import { describe, expect, it } from "vitest";
+import { computed } from "vue";
+
+import { afterEach, beforeEach, describe, expect, it, type Mock, vi } from "vitest";
import { mount, RouterLinkStub } from "@vue/test-utils";
import AddressLink from "@/components/AddressLink.vue";
+const l1ExplorerUrlMock = vi.fn((): string | null => "https://goerli.etherscan.io");
+vi.mock("@/composables/useContext", () => {
+ return {
+ default: () => ({
+ currentNetwork: computed(() => ({ l1ExplorerUrl: l1ExplorerUrlMock() })),
+ }),
+ };
+});
+
const global = {
stubs: {
RouterLink: RouterLinkStub,
@@ -58,4 +69,26 @@ describe("Address Link", () => {
"https://goerli.etherscan.io/address/0x0000000000000000000000000000000000000001"
);
});
+ describe("when L1 explorer url is not set", () => {
+ let mock1ExplorerUrl: Mock;
+ beforeEach(() => {
+ mock1ExplorerUrl = l1ExplorerUrlMock.mockReturnValue(null);
+ });
+
+ afterEach(() => {
+ mock1ExplorerUrl.mockRestore();
+ });
+
+ it("renders L1 address as text instead of link", async () => {
+ const wrapper = mount(AddressLink, {
+ global,
+ props: {
+ address: "0x0000000000000000000000000000000000000001",
+ network: "L1",
+ },
+ });
+ expect(wrapper.findAll("a").length).toBe(0);
+ expect(wrapper.find("span").text()).toBe("0x0000000000000000000000000000000000000001");
+ });
+ });
});
diff --git a/packages/app/tests/components/batches/InfoTable.spec.ts b/packages/app/tests/components/batches/InfoTable.spec.ts
index a1fdbe60e0..63fc2948a8 100644
--- a/packages/app/tests/components/batches/InfoTable.spec.ts
+++ b/packages/app/tests/components/batches/InfoTable.spec.ts
@@ -1,6 +1,7 @@
+import { computed } from "vue";
import { createI18n } from "vue-i18n";
-import { describe, expect, it } from "vitest";
+import { afterEach, beforeEach, describe, expect, it, type Mock, vi } from "vitest";
import { mount, RouterLinkStub } from "@vue/test-utils";
@@ -13,6 +14,15 @@ import type { BatchDetails } from "@/composables/useBatch";
import { localDateFromISOString } from "@/utils/helpers";
+const l1ExplorerUrlMock = vi.fn((): string | null => "https://goerli.etherscan.io");
+vi.mock("@/composables/useContext", () => {
+ return {
+ default: () => ({
+ currentNetwork: computed(() => ({ l1ExplorerUrl: l1ExplorerUrlMock() })),
+ }),
+ };
+});
+
describe("InfoTable:", () => {
const i18n = createI18n({
locale: "en",
@@ -145,4 +155,42 @@ describe("InfoTable:", () => {
);
wrapper.unmount();
});
+ describe("when L1 explorer url is not set", () => {
+ let mock1ExplorerUrl: Mock;
+ beforeEach(() => {
+ mock1ExplorerUrl = l1ExplorerUrlMock.mockReturnValue(null);
+ });
+
+ afterEach(() => {
+ mock1ExplorerUrl.mockRestore();
+ });
+
+ it("renders L1 hashes as texts instead of links", async () => {
+ const wrapper = mount(InfoTable, {
+ global: {
+ stubs: {
+ RouterLink: RouterLinkStub,
+ },
+ plugins: [i18n],
+ },
+ props: {
+ batch: batchItem,
+ loading: false,
+ },
+ });
+ expect(wrapper.findAll(".actual-string")[0].text()).toEqual(
+ "0x8983f748ff6c2f9038904d65dc63a344db33c29d97f1741a931e90689f86b2be"
+ );
+ expect(wrapper.findAll(".actual-string")[1].text()).toEqual(
+ "0x0ab34d8523b67f80783305760a2989ffe6ab205621813db5420a3012845f5ac7"
+ );
+ expect(wrapper.findAll(".actual-string")[2].text()).toEqual(
+ "0x87c5c5bf78100d88766101f13ec78d3b3356929556ee971cfacb6fe2a53b210a"
+ );
+ expect(wrapper.findAll(".actual-string")[3].text()).toEqual(
+ "0x57c44d7c183633f81bfa155bd30e68a94e3ff12c1e6265a4b5e06b6d4a7a1fa8"
+ );
+ wrapper.unmount();
+ });
+ });
});
diff --git a/packages/app/tests/components/blocks/InfoTable.spec.ts b/packages/app/tests/components/blocks/InfoTable.spec.ts
index c66e685530..17ca8b8227 100644
--- a/packages/app/tests/components/blocks/InfoTable.spec.ts
+++ b/packages/app/tests/components/blocks/InfoTable.spec.ts
@@ -1,6 +1,7 @@
+import { computed } from "vue";
import { createI18n } from "vue-i18n";
-import { describe, expect, it } from "vitest";
+import { afterEach, beforeEach, describe, expect, it, type Mock, vi } from "vitest";
import { mount, RouterLinkStub } from "@vue/test-utils";
@@ -14,6 +15,15 @@ import type { Block } from "@/composables/useBlock";
import { localDateFromISOString } from "@/utils/helpers";
+const l1ExplorerUrlMock = vi.fn((): string | null => "https://goerli.etherscan.io");
+vi.mock("@/composables/useContext", () => {
+ return {
+ default: () => ({
+ currentNetwork: computed(() => ({ l1ExplorerUrl: l1ExplorerUrlMock() })),
+ }),
+ };
+});
+
describe("InfoTable:", () => {
const i18n = createI18n({
locale: "en",
@@ -241,4 +251,54 @@ describe("InfoTable:", () => {
expect(batch[1].findComponent(Tooltip).find("span").text()).toBe("1");
expect(batch[1].findComponent(RouterLinkStub).exists()).toBeFalsy();
});
+ describe("when L1 explorer url is not set", () => {
+ let mock1ExplorerUrl: Mock;
+ beforeEach(() => {
+ mock1ExplorerUrl = l1ExplorerUrlMock.mockReturnValue(null);
+ });
+
+ afterEach(() => {
+ mock1ExplorerUrl.mockRestore();
+ });
+
+ it("renders L1 hashes as texts instead of links", async () => {
+ const wrapper = mount(InfoTable, {
+ global: {
+ plugins: [i18n],
+ stubs: {
+ RouterLink: RouterLinkStub,
+ },
+ },
+ props: {
+ block: {
+ number: 1,
+ timestamp: "2022-04-13T16:48:32.000Z",
+ l1TxCount: 1,
+ l2TxCount: 0,
+ hash: "0xcd7533748f8f0c8f406f366e83d5e92d174845405418745d0f7228b85025cd6e",
+ status: "verified",
+ commitTxHash: "0x5b5a05691d974803f5f095c1b918d2dd19152ed0a9de506d545c96df6cb9cac2",
+ committedAt: "2022-04-13T16:54:37.622380Z",
+ proveTxHash: "0xfb3532f4c38c2eaf78248da64cf80a354429d58204761d6ea6439391043f6fa9",
+ provenAt: "2022-04-13T16:54:37.700089Z",
+ executeTxHash: "0x8d1a78d1da5aba1d0755ec9dbcba938f3920681d2a3d4d374ef265a50858f364",
+ executedAt: "2022-04-13T16:54:37.784185Z",
+ },
+ loading: false,
+ },
+ });
+ expect(wrapper.findAll(".actual-string")[0].text()).toEqual(
+ "0xcd7533748f8f0c8f406f366e83d5e92d174845405418745d0f7228b85025cd6e"
+ );
+ expect(wrapper.findAll(".actual-string")[1].text()).toEqual(
+ "0x5b5a05691d974803f5f095c1b918d2dd19152ed0a9de506d545c96df6cb9cac2"
+ );
+ expect(wrapper.findAll(".actual-string")[2].text()).toEqual(
+ "0xfb3532f4c38c2eaf78248da64cf80a354429d58204761d6ea6439391043f6fa9"
+ );
+ expect(wrapper.findAll(".actual-string")[3].text()).toEqual(
+ "0x8d1a78d1da5aba1d0755ec9dbcba938f3920681d2a3d4d374ef265a50858f364"
+ );
+ });
+ });
});
diff --git a/packages/app/tests/components/transactions/Status.spec.ts b/packages/app/tests/components/transactions/Status.spec.ts
index 0dacc3c1c8..82f3c72e77 100644
--- a/packages/app/tests/components/transactions/Status.spec.ts
+++ b/packages/app/tests/components/transactions/Status.spec.ts
@@ -1,6 +1,7 @@
+import { computed } from "vue";
import { createI18n } from "vue-i18n";
-import { describe, expect, it } from "vitest";
+import { afterEach, beforeEach, describe, expect, it, type Mock, vi } from "vitest";
import { mount } from "@vue/test-utils";
@@ -17,6 +18,15 @@ import $testId from "@/plugins/testId";
const { currentNetwork } = useContext();
+const l1ExplorerUrlMock = vi.fn((): string | null => "https://goerli.etherscan.io");
+vi.mock("@/composables/useContext", () => {
+ return {
+ default: () => ({
+ currentNetwork: computed(() => ({ l1ExplorerUrl: l1ExplorerUrlMock() })),
+ }),
+ };
+});
+
describe("Status", () => {
const i18n = createI18n({
locale: "en",
@@ -148,6 +158,32 @@ describe("Status", () => {
expect(spinnerComponents.length).toBe(1);
expect(spinnerComponents[0].props().color).toBe("dark-neutral");
});
+ describe("when L1 explorer url is not set", () => {
+ let mock1ExplorerUrl: Mock;
+ beforeEach(() => {
+ mock1ExplorerUrl = l1ExplorerUrlMock.mockReturnValue(null);
+ });
+
+ afterEach(() => {
+ mock1ExplorerUrl.mockRestore();
+ });
+
+ it("does not render links for 'committed' status", async () => {
+ const wrapper = mount(Status, {
+ global,
+ props: {
+ status: "committed",
+ commitTxHash: "commitTxHash",
+ proveTxHash: "proveTxHash",
+ executeTxHash: "executeTxHash",
+ },
+ });
+ const badges = wrapper.findAllComponents(Badge);
+ const l1StatusBadgeValueDesktop = badges[3];
+ const [sentLink] = l1StatusBadgeValueDesktop.findAll(".badge-pre-content a");
+ expect(sentLink.attributes("href")).toBeUndefined();
+ });
+ });
it("shows l2 completed badge and l1 validating badge for 'proved' status", async () => {
const wrapper = mount(Status, {
global,
@@ -203,6 +239,33 @@ describe("Status", () => {
expect(spinnerComponents.length).toBe(1);
expect(spinnerComponents[0].props().color).toBe("dark-neutral");
});
+ describe("when L1 explorer url is not set", () => {
+ let mock1ExplorerUrl: Mock;
+ beforeEach(() => {
+ mock1ExplorerUrl = l1ExplorerUrlMock.mockReturnValue(null);
+ });
+
+ afterEach(() => {
+ mock1ExplorerUrl.mockRestore();
+ });
+
+ it("does not render links for 'proved' status", async () => {
+ const wrapper = mount(Status, {
+ global,
+ props: {
+ status: "proved",
+ commitTxHash: "commitTxHash",
+ proveTxHash: "proveTxHash",
+ executeTxHash: "executeTxHash",
+ },
+ });
+ const badges = wrapper.findAllComponents(Badge);
+ const l1StatusBadgeValueDesktop = badges[3];
+ const [sentLink, validatedLink] = l1StatusBadgeValueDesktop.findAll(".badge-pre-content a");
+ expect(sentLink.attributes("href")).toBeUndefined();
+ expect(validatedLink.attributes("href")).toBeUndefined();
+ });
+ });
it("shows l2 completed badge and l1 executed badge for 'verified' status", async () => {
const wrapper = mount(Status, {
global,
@@ -257,6 +320,36 @@ describe("Status", () => {
expect(l1StatusBadgeValueMobile.text()).toBe(i18n.global.t("transactions.statusComponent.executed"));
expect(l1StatusBadgeValueMobile.props().color).toBe("dark-success");
});
+ describe("when L1 explorer url is not set", () => {
+ let mock1ExplorerUrl: Mock;
+ beforeEach(() => {
+ mock1ExplorerUrl = l1ExplorerUrlMock.mockReturnValue(null);
+ });
+
+ afterEach(() => {
+ mock1ExplorerUrl.mockRestore();
+ });
+
+ it("does not render links for 'verified' status", async () => {
+ const wrapper = mount(Status, {
+ global,
+ props: {
+ status: "verified",
+ commitTxHash: "commitTxHash",
+ proveTxHash: "proveTxHash",
+ executeTxHash: "executeTxHash",
+ },
+ });
+ const badges = wrapper.findAllComponents(Badge);
+ const l1StatusBadgeValueDesktop = badges[3];
+ const [sentLink, validatedLink] = l1StatusBadgeValueDesktop.findAll(".badge-pre-content a");
+ expect(sentLink.attributes("href")).toBeUndefined();
+ expect(validatedLink.attributes("href")).toBeUndefined();
+
+ const l1ExecutedLink = l1StatusBadgeValueDesktop.find(".badge-content a");
+ expect(l1ExecutedLink.attributes("href")).toBeUndefined();
+ });
+ });
it("shows icon tooltip and single indexing badge for 'indexing' status", async () => {
const wrapper = mount(Status, {
global,
diff --git a/packages/app/tests/components/transactions/TransferInfo.spec.ts b/packages/app/tests/components/transactions/TransferInfo.spec.ts
index efa85c9db5..f4830ecb21 100644
--- a/packages/app/tests/components/transactions/TransferInfo.spec.ts
+++ b/packages/app/tests/components/transactions/TransferInfo.spec.ts
@@ -1,6 +1,7 @@
+import { computed } from "vue";
import { createI18n } from "vue-i18n";
-import { describe, expect, it, vi } from "vitest";
+import { afterEach, beforeEach, describe, expect, it, type Mock, vi } from "vitest";
import { mount, RouterLinkStub } from "@vue/test-utils";
import { $fetch } from "ohmyfetch";
@@ -15,6 +16,15 @@ vi.mock("ohmyfetch", () => {
};
});
+const l1ExplorerUrlMock = vi.fn((): string | null => "https://goerli.etherscan.io");
+vi.mock("@/composables/useContext", () => {
+ return {
+ default: () => ({
+ currentNetwork: computed(() => ({ l1ExplorerUrl: l1ExplorerUrlMock() })),
+ }),
+ };
+});
+
describe("TransferInfo:", () => {
const i18n = createI18n({
locale: "en",
@@ -50,4 +60,58 @@ describe("TransferInfo:", () => {
mock.mockRestore();
wrapper.unmount();
});
+ it("renders component properly for L1 network", async () => {
+ /* eslint-disable @typescript-eslint/no-explicit-any */
+ const mock = ($fetch as any).mockResolvedValue({ accountType: "eOA" });
+
+ const wrapper = mount(TransferInfo, {
+ global,
+ props: {
+ label: "From",
+ address: "0x6c10d9c1744f149d4b17660e14faa247964749c7",
+ network: "L1",
+ },
+ });
+ expect(wrapper.find("span")?.text()).toBe("From");
+ expect(wrapper.findAll("a")[0].attributes("href")).toEqual(
+ "https://goerli.etherscan.io/address/0x6c10d9c1744f149d4b17660e14faa247964749c7"
+ );
+ expect(wrapper.findAll("a")[0].text()).toEqual("0x6c10d9c1744...49c7");
+ expect(wrapper.find(".copy-btn")).toBeTruthy();
+ expect(wrapper.find(".transactions-data-link-network")?.text()).toBe("L1");
+
+ mock.mockRestore();
+ wrapper.unmount();
+ });
+ describe("when L1 explorer url is not set", () => {
+ let mock1ExplorerUrl: Mock;
+ beforeEach(() => {
+ mock1ExplorerUrl = l1ExplorerUrlMock.mockReturnValue(null);
+ });
+
+ afterEach(() => {
+ mock1ExplorerUrl.mockRestore();
+ });
+
+ it("renders L1 address as text instead of a link", async () => {
+ /* eslint-disable @typescript-eslint/no-explicit-any */
+ const mock = ($fetch as any).mockResolvedValue({ accountType: "eOA" });
+
+ const wrapper = mount(TransferInfo, {
+ global,
+ props: {
+ label: "From",
+ address: "0x6c10d9c1744f149d4b17660e14faa247964749c7",
+ network: "L1",
+ },
+ });
+ expect(wrapper.find("span")?.text()).toBe("From");
+ expect(wrapper.findAll("span.address")[0].text()).toEqual("0x6c10d9c1744...49c7");
+ expect(wrapper.find(".copy-btn")).toBeTruthy();
+ expect(wrapper.find(".transactions-data-link-network")?.text()).toBe("L1");
+
+ mock.mockRestore();
+ wrapper.unmount();
+ });
+ });
});
diff --git a/packages/app/tests/mocks.ts b/packages/app/tests/mocks.ts
index e5c590dbb1..a5886be231 100644
--- a/packages/app/tests/mocks.ts
+++ b/packages/app/tests/mocks.ts
@@ -29,7 +29,7 @@ export const GOERLI_NETWORK: NetworkConfig = {
rpcUrl: "",
l2NetworkName: "Goerli",
l2WalletUrl: "",
- l1ExplorerUrl: "",
+ l1ExplorerUrl: "http://goerli-block-explorer",
maintenance: false,
published: true,
hostnames: [],
@@ -44,7 +44,7 @@ export const GOERLI_BETA_NETWORK: NetworkConfig = {
rpcUrl: "",
l2NetworkName: "Goerli Beta",
l2WalletUrl: "",
- l1ExplorerUrl: "",
+ l1ExplorerUrl: "http://goerli-beta-block-explorer",
maintenance: false,
published: true,
hostnames: ["https://goerli-beta.staging-scan-v2.zksync.dev/"],