From c482084170c755ad173adba8e5b9b431f9cf9ab0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20Go=C5=82aszewski?= Date: Mon, 16 Feb 2026 17:17:37 +0100 Subject: [PATCH 1/4] fix: allow fetching base token data --- packages/api/src/address/address.controller.spec.ts | 6 ++++++ packages/api/src/address/address.controller.ts | 6 +++++- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/packages/api/src/address/address.controller.spec.ts b/packages/api/src/address/address.controller.spec.ts index 1db8bb003d..993698ca5e 100644 --- a/packages/api/src/address/address.controller.spec.ts +++ b/packages/api/src/address/address.controller.spec.ts @@ -18,6 +18,7 @@ import { ForbiddenException } from "@nestjs/common"; import { Wallet, zeroPadValue } from "ethers"; import { UserWithRoles } from "../api/pipes/addUserRoles.pipe"; import { ConfigService } from "@nestjs/config"; +import { BASE_TOKEN_L2_ADDRESS } from "../common/constants"; jest.mock("../common/utils", () => ({ ...jest.requireActual("../common/utils"), @@ -278,6 +279,11 @@ describe("AddressController", () => { await expect(controller.getAddress(blockchainAddress, user)).rejects.toThrow(ForbiddenException); }); + it("does not throw if address is the base token address", async () => { + serviceMock.findOne.mockResolvedValue(null); + await expect(controller.getAddress(BASE_TOKEN_L2_ADDRESS, user)).resolves.toBeDefined(); + }); + describe("when address is a contract", () => { beforeEach(() => { serviceMock.findOne.mockResolvedValue(mock
({ address: blockchainAddress, bytecode: "0x123" })); diff --git a/packages/api/src/address/address.controller.ts b/packages/api/src/address/address.controller.ts index 2e0224df37..09fdbeffa7 100644 --- a/packages/api/src/address/address.controller.ts +++ b/packages/api/src/address/address.controller.ts @@ -25,6 +25,7 @@ import { TransferService } from "../transfer/transfer.service"; import { TransferDto } from "../transfer/transfer.dto"; import { swagger } from "../config/featureFlags"; import { constants } from "../config/docs"; +import { BASE_TOKEN_L2_ADDRESS } from "../common/constants"; import { User } from "../user/user.decorator"; import { AddUserRolesPipe, UserWithRoles } from "../api/pipes/addUserRoles.pipe"; @@ -70,6 +71,9 @@ export class AddressController { const addressType = !!(addressRecord && addressRecord.bytecode.length > 2) ? AddressType.Contract : AddressType.Account; + + const isPublicBaseTokenAddress = isAddressEqual(address, BASE_TOKEN_L2_ADDRESS); + let includeBalances = true; let includeBytecode = true; let includeCreatorAddress = true; @@ -77,7 +81,7 @@ export class AddressController { if (user && !user.isAdmin) { // If address is an account and is not own address, forbid access - if (addressType === AddressType.Account && !isAddressEqual(user.address, address)) { + if (addressType === AddressType.Account && !isAddressEqual(user.address, address) && !isPublicBaseTokenAddress) { throw new ForbiddenException(); } From 973209d277909cdc00bb2f6dff22b146d6457052 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20Go=C5=82aszewski?= Date: Mon, 16 Feb 2026 19:30:35 +0100 Subject: [PATCH 2/4] feat: display prividium base token as token --- packages/app/src/components/Token.vue | 26 +++++++--- .../app/src/components/transfers/Table.vue | 10 +++- packages/app/src/composables/useTransfers.ts | 21 +++++--- packages/app/src/utils/validators.ts | 14 +++++- packages/app/src/views/TokenView.vue | 50 +++++++++++++++++-- .../tests/composables/useTransfers.spec.ts | 9 ++++ 6 files changed, 110 insertions(+), 20 deletions(-) diff --git a/packages/app/src/components/Token.vue b/packages/app/src/components/Token.vue index 33af5ceb65..88966c43a7 100644 --- a/packages/app/src/components/Token.vue +++ b/packages/app/src/components/Token.vue @@ -54,9 +54,10 @@ - @@ -16,11 +22,13 @@ import PageError from "@/components/PageError.vue"; import TokenView from "@/components/Token.vue"; import useAddress, { type Account, type Contract } from "@/composables/useAddress"; +import useContext from "@/composables/useContext"; import useNotFound from "@/composables/useNotFound"; -import { isAddress } from "@/utils/validators"; +import { isAddress, isAddressEqual } from "@/utils/validators"; const { useNotFoundView, setNotFoundView } = useNotFound(); +const { currentNetwork } = useContext(); const { item, isRequestPending: pending, isRequestFailed: failed, getByAddress } = useAddress(); @@ -35,6 +43,42 @@ const pageType = computed(() => { return item.value?.type ? item.value?.type : "account"; }); +const isPrividiumBaseTokenAddress = computed(() => { + const baseTokenAddress = currentNetwork.value.baseTokenAddress; + return currentNetwork.value.prividium && isAddressEqual(props.address, baseTokenAddress); +}); + +const showTokenComponent = computed(() => pageType.value !== "account" || isPrividiumBaseTokenAddress.value); + +const tokenContract = computed(() => { + if (!item.value) { + return null; + } + + if (item.value.type === "contract") { + return item.value as Contract; + } + + if (isPrividiumBaseTokenAddress.value) { + return { + type: "contract", + address: item.value.address, + blockNumber: item.value.blockNumber, + balances: item.value.balances, + bytecode: "", + creatorAddress: "", + creatorTxHash: "", + createdInBlockNumber: 0, + totalTransactions: 0, + isEvmLike: true, + verificationInfo: null, + proxyInfo: null, + }; + } + + return null; +}); + useNotFoundView(pending, failed, item); watchEffect(() => { diff --git a/packages/app/tests/composables/useTransfers.spec.ts b/packages/app/tests/composables/useTransfers.spec.ts index bd32b7aaea..1c67572294 100644 --- a/packages/app/tests/composables/useTransfers.spec.ts +++ b/packages/app/tests/composables/useTransfers.spec.ts @@ -157,4 +157,13 @@ describe("useTransfers:", () => { expect(composable.data.value).toEqual(null); mock.mockRestore(); }); + + it("requests token transfers endpoint when token mode is enabled", async () => { + const composable = useTransfers(address, { forToken: true }); + await composable.load(1); + + expect($fetch).toHaveBeenCalledWith( + "https://block-explorer-api.testnets.zksync.dev/tokens/address/transfers?limit=10&page=1" + ); + }); }); From 8635b5405d70f3fbc943983f38cb40aa76f9745d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20Go=C5=82aszewski?= Date: Mon, 16 Feb 2026 19:33:35 +0100 Subject: [PATCH 3/4] feat: detect base token in useSearch --- packages/app/src/composables/useSearch.ts | 22 +++++++++++++------ .../app/tests/composables/useSearch.spec.ts | 20 +++++++++++++++++ 2 files changed, 35 insertions(+), 7 deletions(-) diff --git a/packages/app/src/composables/useSearch.ts b/packages/app/src/composables/useSearch.ts index 2f04fc87c6..476eb2fe09 100644 --- a/packages/app/src/composables/useSearch.ts +++ b/packages/app/src/composables/useSearch.ts @@ -6,23 +6,31 @@ import { FetchError } from "ohmyfetch"; import useContext from "./useContext"; import { FetchInstance } from "./useFetchInstance"; -import { isAddress, isBlockNumber, isTransactionHash } from "@/utils/validators"; +import { isAddress, isAddressEqual, isBlockNumber, isTransactionHash } from "@/utils/validators"; export default (context = useContext()) => { const router = useRouter(); const isRequestPending = ref(false); const isRequestFailed = ref(false); + const isPrividiumBaseTokenAddress = (address: string) => + !!context.currentNetwork.value.prividium && isAddressEqual(address, context.currentNetwork.value.baseTokenAddress); + const getSearchRoute = (param: string) => { try { - const searchRoutes = [ - { + if (isAddress(param)) { + const isBaseTokenAddress = isPrividiumBaseTokenAddress(param); + const apiRoute = isBaseTokenAddress ? "tokens" : "address"; + return { routeParam: { address: param }, - apiRoute: "address", - isValid: () => isAddress(param), - routeName: "address", + apiRoute: apiRoute, + isValid: () => true, + routeName: apiRoute, prefetch: true, - }, + }; + } + + const searchRoutes = [ { routeParam: { id: param }, apiRoute: "blocks", diff --git a/packages/app/tests/composables/useSearch.spec.ts b/packages/app/tests/composables/useSearch.spec.ts index f364c90dec..c81d9270b2 100644 --- a/packages/app/tests/composables/useSearch.spec.ts +++ b/packages/app/tests/composables/useSearch.spec.ts @@ -1,3 +1,5 @@ +import { computed, ref } from "vue"; + import { describe, expect, it, vi } from "vitest"; import { $fetch } from "ohmyfetch"; @@ -72,6 +74,24 @@ describe("UseSearch:", () => { const searchRoute = getSearchRoute("123"); expect(searchRoute).toBeNull(); }); + + it("routes base token address to token page in prividium mode", () => { + const { getSearchRoute } = useSearch({ + currentNetwork: computed(() => ({ + prividium: true, + baseTokenAddress: "0x000000000000000000000000000000000000800A", + apiUrl: "http://localhost:3020", + })), + user: ref({ loggedIn: false }), + } as never); + + const searchRoute = getSearchRoute("0x000000000000000000000000000000000000800A"); + expect(searchRoute!.apiRoute).toBe("tokens"); + expect(searchRoute!.routeName).toBe("token"); + expect(searchRoute!.routeParam).toEqual({ + address: "0x000000000000000000000000000000000000800A", + }); + }); }); describe("search", () => { From ef7cdc0924203621b113dfa6ddeb077c21bfb44b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20Go=C5=82aszewski?= Date: Tue, 17 Feb 2026 11:29:05 +0100 Subject: [PATCH 4/4] fix: use address api route to fetch base token data in useSearch --- packages/app/src/composables/useSearch.ts | 5 ++--- packages/app/tests/composables/useSearch.spec.ts | 2 +- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/packages/app/src/composables/useSearch.ts b/packages/app/src/composables/useSearch.ts index 476eb2fe09..357ea39534 100644 --- a/packages/app/src/composables/useSearch.ts +++ b/packages/app/src/composables/useSearch.ts @@ -20,12 +20,11 @@ export default (context = useContext()) => { try { if (isAddress(param)) { const isBaseTokenAddress = isPrividiumBaseTokenAddress(param); - const apiRoute = isBaseTokenAddress ? "tokens" : "address"; return { routeParam: { address: param }, - apiRoute: apiRoute, + apiRoute: "address", isValid: () => true, - routeName: apiRoute, + routeName: isBaseTokenAddress ? "token" : "address", prefetch: true, }; } diff --git a/packages/app/tests/composables/useSearch.spec.ts b/packages/app/tests/composables/useSearch.spec.ts index c81d9270b2..0045c07540 100644 --- a/packages/app/tests/composables/useSearch.spec.ts +++ b/packages/app/tests/composables/useSearch.spec.ts @@ -86,7 +86,7 @@ describe("UseSearch:", () => { } as never); const searchRoute = getSearchRoute("0x000000000000000000000000000000000000800A"); - expect(searchRoute!.apiRoute).toBe("tokens"); + expect(searchRoute!.apiRoute).toBe("address"); expect(searchRoute!.routeName).toBe("token"); expect(searchRoute!.routeParam).toEqual({ address: "0x000000000000000000000000000000000000800A",