diff --git a/.agents/AGENTS.md b/.agents/AGENTS.md index cc0f2c5528a..32fc412b971 100644 --- a/.agents/AGENTS.md +++ b/.agents/AGENTS.md @@ -1,5 +1,18 @@ # Frontend application for Blockscout +## Per-directory context + +Some directories have a `CONTEXT.md` documenting non-obvious patterns specific to that area. Read the relevant one before working in (or reaching into) that directory: + +- `deploy/scripts/` — how the frontend container is built and starts up (Dockerfile stages, entrypoint). +- `deploy/tools/envs-validator/` — startup validation of `NEXT_PUBLIC_*` envs against yup schemas. +- `src/slices/` — slice ownership model (who owns an entity's rendering). +- `src/sprite/` — SVG sprite build pipeline and which outputs are tracked vs. generated. +- `src/toolkit/` — the `@blockscout/ui-toolkit` workspace package structure. +- `tools/dev-server/` — how the dev server and demo deploy get their env vars from a running instance config. + +If you encounter a `CONTEXT.md` not listed here, read it too (and consider adding it to this list). + ## Architecture See `./rules/architecture.mdc`. diff --git a/.agents/GLOSSARY.md b/.agents/GLOSSARY.md new file mode 100644 index 00000000000..825f58231c1 --- /dev/null +++ b/.agents/GLOSSARY.md @@ -0,0 +1,64 @@ +# Ubiquitous Language Glossary + +Domain terms used in the Blockscout frontend codebase. + +The glossary carries only what isn't already in code or other docs: +disambiguation between easily-confused terms, etymology, and scope. Folder +paths and env var names are intentionally **not** listed — paths follow +predictably from feature names (`src/features//`), and env +vars are documented in `docs/ENVS.md`. Architectural concepts like +**slice** and **feature** are defined in `.agents/rules/architecture.mdc`. + +**Kinds** in the table below: + +- *entity* — blockchain object that appears as a first-class UI element (detail page, list row, API resource) +- *feature* — a config-gated product area +- *service* — an external service (Blockscout-operated or third-party) backing a feature +- *chain* — a specific chain or chain concept +- *concept* — architectural / structural term + +--- + +| Term | Kind | Definition | +|------|------|------------| +| **Account** | feature | Authenticated-user area covering watchlist, private tags, custom ABI, API keys, verified addresses, and profile. See also: **Watchlist**, **Public Tags**. | +| **Address Metadata** | feature | Provides address labels, metadata enrichment, and label-based address search. Backs the metadata panel on address pages and the Label Search page. **Public Tags** is one sub-feature. Distinct from **Address Profile API**. | +| **Address Profile API** | feature | Integration with a third-party API that decorates address pages with external profile tags and links. Distinct from **Address Metadata**, which surfaces Blockscout-managed labels. | +| **Advanced Filter** | feature | UI for filtering transactions using multi-criteria queries; distinct from the basic filter controls on most list pages. | +| **Alternative Explorers** | feature | "Verify with other explorers" menu linking to third-party explorers (e.g. Etherscan). | +| **Beacon Chain** | chain | The Ethereum Proof-of-Stake consensus layer. When enabled, exposes beacon-chain deposits and withdrawals alongside regular transactions. Not a rollup — it is the Ethereum L1 consensus mechanism. | +| **BENS (Blockscout Name Service)** | service | Blockscout's own chain-agnostic address naming service. Distinct from ENS (Ethereum-specific). Co-located with **Clusters** in the name-services UI. | +| **Blob** | entity | An individual EIP-4844 data blob attached to a transaction; a single tx can carry multiple. The entity is `Blob`; the feature folder/flag that surfaces it uses the name `data-availability`. | +| **Block Reward** | entity | On-chain payout to a block producer (miner, validator, etc.). Entirely distinct from the **Rewards** (Merits) program — no shared code, API, or folder. | +| **CCTX (Cross-Chain Transaction)** | entity | ZetaChain-specific transaction type that spans multiple chains. Displayed as a separate tab on the transactions list. Distinct from the general cross-chain transactions feature (**Interchain Indexer**). | +| **Chain Variant** | concept | A non-rollup chain that ships custom UI or domain entities (e.g. Celo, TAC, ZetaChain, Beacon Chain, Zilliqa). Contrast with **Rollup**, which implies an L1/L2 settlement relationship. | +| **Clusters** | service | Address identity and grouping service: aggregates multiple addresses under a named cluster (individual, protocol, organization). Co-located with **BENS** in the name-services UI. | +| **Connect Wallet** | feature | Lets users write to contracts, sign transactions, and connect a wallet to the explorer. Previously named `blockchain-interaction`; the current config key is `connectWallet`. Distinct from **Web3 Wallet**. | +| **Dispute Games** | entity | Part of the Optimism **Fault Proof System**. On-chain games used to challenge and resolve disputed L2 output roots. | +| **Easter Eggs** | feature | Hidden mini-games wired to claim links for badge rewards. | +| **Epoch** | entity | A consensus time period specific to **Celo**. Has its own index and detail pages. Always refers to a Celo epoch in this codebase — not a generic blockchain concept. | +| **Fault Proof System** | feature | Optimism's mechanism for proving the correctness of L2 state transitions on L1 via **Dispute Games**. | +| **Flashblocks** | feature | MegaETH's sub-second block streaming mechanism. | +| **Hot Contracts** | feature | Ranked list of the most recently and frequently interacted-with smart contracts on the network. | +| **Interchain Indexer** | service | Microservice that indexes cross-chain messages and token transfers across heterogeneous chains. General-purpose interop indexer, not ZetaChain-specific. Provides "Cross chain txs" feature. Distinct from **CCTX**. | +| **Interop Messages** | entity | **Deprecated** Cross-rollup messages passed between OP Stack chains using the native interoperability protocol. Distinct from **Interchain Indexer** messages. | +| **Kettle** | entity | In the **SUAVE** architecture, a Kettle is a trusted execution environment (TEE) node that processes MEV bundles. SUAVE transactions are associated with a Kettle. | +| **Marketplace** | feature | Curated directory of dApps and DeFi applications integrated with Blockscout. | +| **MetaSuites** | service | Third-party browser extension that enhances the Blockscout UI with additional data and links. | +| **Multichain** | feature | Aggregates data across multiple Blockscout-indexed chains into a single explorer view. | +| **Operation** | entity | **TAC**-specific entity representing a bridge operation between the TON and EVM ecosystems. No equivalent on standard EVM chains. | +| **Pools** | entity | DEX liquidity pool positions tracked on-chain. | +| **Public Tags** | feature | Community-submitted labels for addresses, visible on address pages. A sub-feature of **Address Metadata**. | +| **Rewards** | feature | The Blockscout Merits program — a token rewards and incentives system operated by Blockscout. Entirely distinct from **Block Reward** (on-chain block-producer payouts). | +| **Rollup** | concept | A chain that settles transactions on a parent (L1) chain. Introduces specific entities: deposits, withdrawals, transaction batches, output roots. Contrast with **Chain Variant**. | +| **SolidityScan** | service | Third-party smart contract security vulnerability scanner integrated into contract detail pages. | +| **SUAVE** | chain | MEV-focused chain developed by Flashbots, built around a trusted execution environment (TEE) architecture. Introduces the **Kettle** entity. | +| **TAC (Ton Application Chain)** | chain | A chain that bridges the TON blockchain and EVM ecosystems. Introduces the **Operation** entity. | +| **Tx Actions** | feature | Structured per-transaction action breakdown rendered on the tx details page — a first-party Blockscout interpretation of what a tx did. Distinct from **Tx Interpretation** (natural-language summary) and from raw calldata. | +| **Tx Interpretation** | feature | Natural-language summary of a transaction shown on detail pages. Distinct from **Tx Actions**. | +| **User Op (User Operation)** | entity | ERC-4337 account-abstraction operation — a transaction-like object submitted to a bundler rather than directly to the network. | +| **Validators** | feature | The set of active block validators on chains that expose this concept (e.g. zkSync Era, Celo). Not present on standard PoW/PoS EVM chains without explicit support. | +| **Visualize** | service | Service that converts Solidity source code into UML diagrams (class and storage layout). | +| **Watchlist** | feature | An **Account** feature that lets authenticated users track a set of addresses and receive notifications for their activity. | +| **Web3 Wallet** | feature | Lets users add tokens and networks directly from the explorer to their browser wallet. Distinct from **Connect Wallet** (signing/writing). | +| **X-Star Score** | feature | Third-party reputation score shown on address pages, linking out to the X-Star service. | diff --git a/.agents/rules/architecture.mdc b/.agents/rules/architecture.mdc index 818eea26443..c9d856ce603 100644 --- a/.agents/rules/architecture.mdc +++ b/.agents/rules/architecture.mdc @@ -1,5 +1,5 @@ --- -description: Project overview, tech stack, directory layout, data flow, and ongoing client/ architecture migration +description: Project overview, tech stack, and directory layout globs: alwaysApply: true --- @@ -25,60 +25,72 @@ Blockscout frontend — a blockchain explorer UI. Distributed as a Docker image; ## Domain terminology -Feature and product codenames used throughout the codebase are defined in `docs/GLOSSARY.md`. Consult it whenever you encounter an unfamiliar term — e.g. `tac`, `bens`, `cctx`, `kettle`, `epoch`, `blobs`. +Feature and product codenames used throughout the codebase are defined in `.agents/GLOSSARY.md`. Consult it whenever you encounter an unfamiliar term — e.g. `tac`, `bens`, `cctx`, `kettle`, `epoch`, `blobs`. ## Directory layout -``` -pages/ Next.js file-based routes — thin wrappers only (dynamic import + getServerSideProps) -ui/ Legacy React UI components organized by feature (~65 subdirectories) → being migrated to client/ -lib/ Legacy business logic, API utilities, custom hooks, context providers → being migrated to client/ -client/ New home for all product code (see Migration section below) -toolkit/ Design system — Chakra wrappers, theme tokens, shared hooks/components (stays here permanently) -configs/app/ Runtime app configuration; must not import from client/ -nextjs/ Next.js config utilities: headers, rewrites, redirects, type-safe routes -mocks/ Shared mock data for tests → being co-located under client/ slices/features -deploy/ Docker scripts, env validator, build tools -docs/ ENVS.md, GLOSSARY.md, CONTRIBUTING.md -``` +All application source lives under `/src`. Code is co-located by **slice** (core explorer domain) or **feature** (optional / config-gated area). -## Data flow +### Top-level repo -- **Getting data:** pages fetch data via React Query. Query keys and fetcher functions live in `lib/api/` (moving to `client/api/`). -- **Global UI state:** React Context providers initialized in `pages/_app.tsx` — `AppContextProvider`, `SettingsContextProvider`, `MarketplaceContextProvider`, etc. -- **Real-time data:** WebSocket via `SocketProvider` (moving to `client/api/socket/`). -- **App config:** always read via `configs/app/` (which reads `window.__envs` at runtime) — never `process.env.*` directly in component code. +| Path | Role | +|---|---| +| `/src` | All application source — product UI, server plumbing, routing, config, design system, and code-imported assets | +| `/tools/` | Developer tooling — local dev utilities and scripts (e.g. `tools/dev-server/`, which loads the dev server's env vars) | +| `/deploy/` | CI/CD scripts, Docker, env validator, build tools | +| `/public/` | Static assets served as-is (sprite output, etc.) — never bundled | +| `/docs/` | ENVS.md, CONTRIBUTING.md, etc. | +| Root config files | `next.config.js`, `tsconfig.json`, `eslint.config.mjs`, `package.json` | ---- +### Inside `/src` -## Ongoing migration: `client/` architecture +| Path | Role | +|---|---| +| `src/api/` | Transport, fetch utilities, query client, WebSocket, data-fetching hooks | +| `src/shell/` | App chrome: layout, header, footer, navigation, top-bar, root contexts | +| `src/slices/` | Core explorer entities — always present on any vanilla EVM chain | +| `src/features/` | Optional / config-gated product areas | +| `src/shared/` | Cross-cutting utilities with no single domain owner | +| `src/config/` | Runtime app configuration (aggregator + cross-cutting parts) | +| `src/services/` | Third-party integrations: analytics, error reporting, A/B flags | +| `src/pages/` | Next.js file-based routes — thin wrappers only | +| `src/server/` | Next.js server plumbing: SSR helpers, middleware, CSP, headers, rewrites | +| `src/sprite/` | SVG sprite runtime component + icon source files | +| `src/toolkit/` | Design system — pnpm workspace package, published as `@blockscout/ui-toolkit` | -The codebase is being progressively restructured. The long-term target moves all product code from `ui/` and `lib/` into a new `client/` directory with a clear domain-based layout. +### Slice vs feature -**Blueprint:** `client/ARCH_REDESIGN.md` -**Task backlog and current status:** `client/MIGRATION_TASKS.md` +Key classification question: *"Can this area exist on a vanilla EVM chain with no feature flag?"* -### Target layout inside `client/` +- **Slice** — yes. Always present. Examples: `tx`, `block`, `address`, `token`, etc. +- **Feature** — no. Config-gated or chain-specific. Examples: `rollup/optimism`, `chain-variants/celo`, `account`, `stats`, `gas-tracker`. Every config-gated feature with user-facing UI gets its own folder under `src/features/`, regardless of size. +- **Service, not feature** — config-gated infrastructure with no user-facing UI (analytics, error reporting, A/B flags) lives under `src/services/`, not `src/features/`. -| Directory | Contents | -|---|---| -| `client/shell/` | App chrome: layout, header, footer, navigation, top-bar, root contexts | -| `client/api/` | API transport, fetch utilities, query client, WebSocket; migrated from `lib/api/` and `lib/socket/` | -| `client/slices/` | Core explorer entities always present on any EVM chain (tx, block, address, token, contract, search, …) | -| `client/features/` | Optional / config-gated areas: rollups, chain variants, account, rewards, marketplace, stats, … | -| `client/shared/` | Cross-cutting utilities with no single domain owner (analytics, errors, hooks, router helpers, text utils, …) | +Both slices and features share the same internal shape: `pages/`, `components/`, `hooks/`, `utils/`, `types/`, `config.ts`, `mocks.ts` or `mocks/`, `stubs.ts` or `stubs/`. -### Slice vs feature +### Naming conventions -- **Slice** — present on every vanilla EVM chain, no feature flag required. Examples: `tx`, `block`, `address`, `token`, `contract`. -- **Feature** — config-gated or chain-specific. Examples: `rollup/optimism`, `chain-variants/celo`, `account`, `stats`, `gas-tracker`. +| Artifact | Convention | +|---|---| +| Directories | `kebab-case` | +| React components | `PascalCase.tsx` | +| Hooks | `use` + `camelCase.ts` | +| Helper / util modules | `kebab-case.ts` | +| Role files | lowercase — `types.ts`, `mocks.ts`, `stubs.ts`, `config.ts` | + +### Key rules + +- **No re-export-only barrel `index.ts` files** inside `/src`. Aggregator files that curate a genuine public surface are fine. +- **`src/api/resources` must not have runtime imports** from `src/slices/*` or `src/features/*`; `import type` is allowed. +- **`config.ts` and `types/config.ts` files** must not import React, browser APIs, or code from other slices/features — they are executed by the Node.js env validator. +- **`src/pages/` files are thin wrappers** — a dynamic import and optionally `getServerSideProps`; no UI components or business logic inline. +- **Cross-domain type imports** go through `/types/api.ts`, never deeper internal paths. +- **Compose at the page level** when optional feature UI plugs into a core slice — the slice page renders the feature's component, not the other way around. Avoids cycles and keeps the feature opt-in. +- **No files directly at the root of `src/shared/`** — only subfolders grouped by purpose. -### Key rules for migrated code +## Data flow -- **No barrel `index.ts` files** inside `client/` unless the file defines a genuine public facade (not just `export * from ...`). -- **`client/api` must not have runtime imports** from `client/slices/*` or `client/features/*`; `import type` is allowed. -- **`configs/` never imports `client/`.** -- **`pages/` files are thin wrappers** — a dynamic import and optionally `getServerSideProps`; no UI components or business logic inline. -- **Naming:** kebab-case for directories and non-component modules; `PascalCase.tsx` for React components; `useCamelCase.ts` for hooks. -- **Types:** each slice/feature owns its API response types in `/types/api.ts`. Do not import from deeper internal paths across domain boundaries. -- **When editing legacy `lib/` or `ui/` code:** check `client/MIGRATION_TASKS.md` to see if that area has a migration task in progress or planned. If so, note the target destination in your PR description. +- **Getting data:** pages fetch data via React Query. Query keys and fetcher functions live in `src/api/hooks` and `src/api/utils`. +- **Global UI state:** React Context providers initialized in `src/pages/_app.tsx` — `AppContextProvider`, `SettingsContextProvider`, `MarketplaceContextProvider`, etc. +- **Real-time data:** WebSocket via `SocketProvider` in `src/api/socket/`. +- **App config:** always read via `src/config/` (which reads `window.__envs` at runtime) — never `process.env.*` directly in component code, except for server code (`src/server/`). diff --git a/.agents/rules/code-quality.mdc b/.agents/rules/code-quality.mdc index 9971165cb55..99d431a6e1c 100644 --- a/.agents/rules/code-quality.mdc +++ b/.agents/rules/code-quality.mdc @@ -128,11 +128,11 @@ Remove commented-out code blocks. The git history preserves anything that might ### Links - Use `toolkit/chakra/link` instead of `next/link`. Never import `Link` from `next/link` directly. -- When links to **application pages** are constructed, verify that `nextjs-routes` or `nextjs/routes` utilities are used instead of string concatenation or template literals. The full list of application routes is available in `nextjs/nextjs-routes.d.ts`. +- When links to **application pages** are constructed, verify that `nextjs-routes` or `src/shared/router/routes` utilities are used instead of string concatenation or template literals. The full list of application routes is available in `src/shared/router/nextjs-routes.d.ts`. ### Date and time -- Import `dayjs` only via `lib/date/dayjs.ts` — never directly from the `dayjs` package. +- Import `dayjs` only via `client/shared/date-and-time/dayjs.ts` — never directly from the `dayjs` package. - Render all dates and times through the shared `Time` or `TimeWithTooltip` components. Do not format timestamps inline. ### Strict comparison diff --git a/.agents/rules/env-vars.mdc b/.agents/rules/env-vars.mdc index 61b31455787..508f5920b42 100644 --- a/.agents/rules/env-vars.mdc +++ b/.agents/rules/env-vars.mdc @@ -11,73 +11,45 @@ All environment variables are documented in `docs/ENVS.md`. This is the authorit ## Runtime delivery (not build-time) -Almost all variables are injected at **runtime**, not baked into the Next.js build. Only three variables are true build-time constants, compiled into the image via Docker `ARG`: `NEXT_PUBLIC_GIT_COMMIT_SHA`, `NEXT_PUBLIC_GIT_TAG`, `NEXT_OPEN_TELEMETRY_ENABLED`. +Almost all variables are injected at **runtime**, not baked into the Next.js build — they're written to `window.__envs` by a container-startup script and read from there in the browser. The only true build-time constants are the ones listed in `docs/BUILD-TIME_ENVS.md` (compiled into the image via Docker `ARG`). -Everything else follows this pipeline: +A variable that isn't documented in `docs/ENVS.md` will never reach the browser, even if it's set in the container's environment. -**1. Build time — `deploy/scripts/collect_envs.sh`** - -Scans `docs/ENVS.md` for every `NEXT_PUBLIC_*` name and writes `.env.registry` (a list of expected variable names with placeholder values). This registry is copied into the Docker image and tells the runtime which variables the app expects. **If a variable is not in `docs/ENVS.md`, it will not appear in the registry and will not be delivered to the browser.** - -**2. Container startup — `deploy/scripts/entrypoint.sh`** - -Runs in sequence: -1. Optionally loads a named preset from `configs/envs/` -2. Downloads external asset files (images, JSON configs) via `download_assets.sh` -3. Validates environment values against the schema (`validate_envs.sh` → `deploy/tools/envs-validator/`) -4. Writes `public/assets/envs.js` via `make_envs_script.sh`: - ```js - window.__envs = { - NEXT_PUBLIC_API_HOST: "...", - ... - } - ``` - -**3. In the app — `configs/app/utils.ts` `getEnvValue()`** - -Reads `window.__envs` when running in the browser, and `process.env` during SSR / build-time evaluation. +For the full pipeline (registry generation, container startup order, validation, `window.__envs` emission), see `deploy/scripts/CONTEXT.md`. ## Accessing variables in code -Never read `process.env.NEXT_PUBLIC_*` directly in component or library code. Always go through `configs/app/`, which exposes a frozen, typed config object. The config is split into sections: +- **Read env values via `getEnvValue('NEXT_PUBLIC_...')`** from `src/config/utils/envs.ts` — `process.env` is empty in the browser, and the helper handles SSR/client transparently. (Server-only code may use `process.env` directly.) +- **Co-locate sub-configs with the code that owns them.** The aggregator at `src/config/index.ts` (sections: `app`, `apis`, `chain`, `shell`, `slices`, `features`, `services`, `metadata`, `misc`) re-exports them; add new values in the owning sub-config (e.g. `src/features/marketplace/config.ts`, `src/slices/tx/config.ts`), not in a central file. -| Section | Purpose | -|---|---| -| `app` | App-level settings (host, protocol, env flags) | -| `apis` | Main API and related service endpoints | -| `chain` | Blockchain parameters | -| `UI` | Visual customizations (colors, fonts, layout) | -| `features` | Feature flags | -| `services` | Third-party service integrations | -| `meta` | SEO and meta-tag settings | +## Value types -## Validation +Every env var arrives in the app as a string. There are three patterns for what that string can mean, and a matching helper in `src/config/utils/envs.ts` for each. Pick the lightest one that fits. -At container startup, `deploy/tools/envs-validator/` validates the current environment against a [Valibot](https://valibot.dev/) schema defined in `deploy/tools/envs-validator/schema.ts`. The app will not start if validation fails (unless `SKIP_ENVS_VALIDATION=true`). +### 1. Primitives (string, boolean, number) -For complex config shapes, define a **separate sub-schema** (see `tacSchema`, `beaconChainSchema` for examples) rather than inlining everything. +The most common case — a single scalar value. Read with `getEnvValue('NEXT_PUBLIC_…')` and cast at the call site (`=== 'true'`, `Number(…)`, etc.). Booleans are conventionally encoded as the literal strings `'true'` / `'false'`; for optional flags, prefer the opt-out form (`!== 'false'`) so the variable can be omitted. -## How to add a new variable +### 2. JSON-encoded strings -Follow all steps — skipping any one of them will cause the variable to be missing at runtime or fail CI validation. +For mid-sized structured config — anything with two or more fields, or a small homogeneous list — that needs to be available **synchronously at module load** (for example, a color-scheme override that has to be ready before Chakra builds its theme). -1. **`docs/ENVS.md`** — document the variable: name, expected type, required/optional, default value, example value. This step is mandatory; without it the runtime script will not deliver the value to the browser. +Read with `parseEnvJson(getEnvValue('NEXT_PUBLIC_…'))`. It returns `null` on parse failure, so always provide a fallback. The operator-facing JSON syntax uses single quotes (e.g. `'[{"id":1}]'`) so the value can be pasted into a shell or `.env` file without escaping. -2. **`configs/app/`** — add a property in the appropriate section file (`app.ts`, `apis.ts`, `chain.ts`, `ui.ts`, `features/`, `services.ts`, `meta.ts`). Read the value via `getEnvValue('NEXT_PUBLIC_...')`. Never use the env var name directly outside `configs/app/`. +### 3. Links to JSON config files -3. **`deploy/tools/envs-validator/schema.ts`** — add or update the validation rule. For complex structures, create a dedicated sub-schema. +For large configs (marketplace apps, featured-networks list, footer links, etc.) where the payload is too big to embed in an env var and benefits from being fetched and cached separately. The env var itself only carries the URL; the JSON content is loaded **asynchronously** in the browser and is **not** validated at container startup. -4. **`deploy/tools/envs-validator/test/.env.base`** — add the variable to the base test preset. If the variable supports alternative configurations, add examples to `.env.alt` as well. Verify locally: - ```bash - cd deploy/tools/envs-validator && pnpm test - ``` +Read with `getExternalAssetFilePath('NEXT_PUBLIC_…_URL')` — it returns the local path under `/assets/configs/` where the file is served (the startup pipeline downloads the remote URL into the image so the browser always fetches it same-origin). -5. **CSP** (if the variable holds an external non-asset URL) — add the domain to the appropriate policy in `nextjs/csp/policies/`. Only add the policy when the relevant config option is enabled. +## Validation -6. **`deploy/scripts/download_assets.sh`** (if the variable holds an asset URL — image or JSON config) — extend the `ASSETS_ENVS` array with the variable name. +Every `NEXT_PUBLIC_*` variable is validated at container startup against a schema; see `deploy/tools/envs-validator/CONTEXT.md` for how the schemas are organized and where to add new rules. + +## How to add a new variable -7. **JSON config URL only** — add an example file to `deploy/tools/envs-validator/test/assets/configs/` (filename derived by stripping `NEXT_PUBLIC_` prefix and `_URL` suffix, lowercased — e.g. `NEXT_PUBLIC_MARKETPLACE_CONFIG_URL` → `marketplace_config.json`) and add the variable name to the `envsWithJsonConfig` array in `deploy/tools/envs-validator/index.ts`. +Use the `add-env-var` skill — it's the step-by-step checklist for documenting, exposing, validating, and (when applicable) CSP-allowing or download-registering a new `NEXT_PUBLIC_*` variable. -## TBD: Deprecating a variable +## How to deprecate a variable -> This section is not yet defined. Topics to cover: how to announce deprecation, the grace period before removal, how to update `docs/ENVS.md` and `schema.ts`, and how to handle backwards compatibility for existing deployments. +Use the `deprecate-env-var` skill — the checklist for immediate removal or a grace-period deprecation. diff --git a/.agents/rules/glossary.mdc b/.agents/rules/glossary.mdc index fd04c18e02a..660d002a17e 100644 --- a/.agents/rules/glossary.mdc +++ b/.agents/rules/glossary.mdc @@ -1,8 +1,8 @@ --- -description: Project domain terms and ubiquitous language — consult docs/GLOSSARY.md +description: Project domain terms and ubiquitous language alwaysApply: true --- # Ubiquitous language (Glossary) -Product and feature codenames used in this codebase are defined in `docs/GLOSSARY.md`. Consult it when you encounter an unfamiliar term (e.g. `tac`, `bens`, `cctx`, `kettle`, `epoch`). +Product and feature codenames used in this codebase are defined in `.agents/GLOSSARY.md`. Consult it when you encounter an unfamiliar term (e.g. `tac`, `bens`, `cctx`, `kettle`, `epoch`). diff --git a/.agents/skills/add-env-var/SKILL.md b/.agents/skills/add-env-var/SKILL.md new file mode 100644 index 00000000000..05eefcfeb12 --- /dev/null +++ b/.agents/skills/add-env-var/SKILL.md @@ -0,0 +1,170 @@ +--- +name: add-env-var +description: Step-by-step checklist for adding a new NEXT_PUBLIC_* environment variable. Use when introducing any new runtime env var. +--- + +# Add a new env variable + +Read `.agents/rules/env-vars.mdc` first for background: how runtime delivery +works, how the config object is structured, the three value types +(primitive / JSON-encoded / JSON config URL), and where validation lives. This +skill is the checklist; the rule is the concept doc. + +## Step 0 — Decide the variable's shape + +Three answers drive every step below. Settle them first. + +1. **Value type** (see env-vars.mdc § "Value types"): + - **Primitive** — string, boolean, number. + - **JSON-encoded string** — small synchronous structured config. + - **JSON config URL** — large async-loaded payload. Variable name ends in `_URL`. + +2. **External URL?** Does the value point to a third-party host (image, JSON + payload, HTTP endpoint)? If yes → CSP and download-script considerations + apply (see Step 4). + +3. **Mode** — Does the variable apply in the default mode, multichain mode, + or both? This decides which validator schema(s) you touch. + +## Step 1 — Document in `docs/ENVS.md` + +Add a row in the section that mirrors where the variable's config lives: +App configuration / APIs configuration / App shell / Slices / Features / +External services / Misc. The doc sections mirror `src/config/index.ts`. + +Fill in: name, type, description, required/optional, default, example. + +Set the **Version** column to `upcoming`. The release process replaces this +with the actual version number when the change ships. + +Why this is mandatory: `collect_envs.sh` scans `docs/ENVS.md` for +`NEXT_PUBLIC_*` names to emit `.env.registry`, which gates what +`make_envs_script.sh` writes into `window.__envs`. An undocumented variable +never reaches the browser. + +## Step 2 — Expose through the config object + +### Existing config area + +Find the matching sub-config: + +- Slices / features / services / shell areas: `src///config.ts`. +- Cross-cutting sections (`app`, `apis`, `chain`, `misc`, `metadata`): `src/config/.ts`. + +Read the value with the helper that matches its type: + +- Primitive — `getEnvValue('NEXT_PUBLIC_…')`; cast at the call site + (`=== 'true'`, `Number(…)`, …). +- JSON-encoded — `parseEnvJson(getEnvValue('NEXT_PUBLIC_…'))` with a + fallback (returns `null` on parse failure). +- JSON config URL — `getExternalAssetFilePath('NEXT_PUBLIC_…_URL')`. + +Never read `process.env.NEXT_PUBLIC_*` in client code. + +### New feature + +If the variable belongs to a **brand-new feature**, also: + +1. Create the folder `src/features//`. +2. Add `src/features//config.ts` following the template below. +3. Register the feature in the aggregator `src/config/features.ts`: + ```ts + export { default as } from 'src/features//config'; + ``` + Keep the export list alphabetised by export name. + +#### Feature `config.ts` template + +```ts +// SPDX-License-Identifier: LicenseRef-Blockscout + +import { getEnvValue } from 'src/config/utils/envs'; +import type { Feature } from 'src/config/utils/features'; + +const title = 'Human-readable feature name'; + +const config: Feature<{ /* payload fields available when enabled */ }> = (() => { + if (getEnvValue('NEXT_PUBLIC__ENABLED') === 'true') { + return Object.freeze({ + title, + isEnabled: true, + // payload fields here + }); + } + + return Object.freeze({ + title, + isEnabled: false, + }); +})(); + +export default config; +``` + +`Feature` is a discriminated union on `isEnabled`. Consumers narrow +to the enabled branch (`if (config.isEnabled) { … config.payloadField … }`) +to get typed access to the payload. Real examples: +`src/features/web3-wallet/config.ts`, `src/features/chain-stats/config.ts`. + +### Private mode + +Anything that integrates with a 3rd-party able to collect user info +(analytics, error tracking, A/B testing, captcha, …) must respect the +`app.isPrivateMode` flag. The flag lives in `src/config/app`. + +- **Feature config** — gate the entire enabled branch on + `!app.isPrivateMode` so consumers see `isEnabled: false` in private mode. + Example: `src/features/web3-wallet/config.ts`. + ```ts + if (!app.isPrivateMode && /* other conditions */) { + return Object.freeze({ title, isEnabled: true, /* payload */ }); + } + return Object.freeze({ title, isEnabled: false }); + ``` + +- **Service config** — never store the service's client key (or any + identifier the SDK would use to attach the user's session to a remote + account) in the config when private mode is on. Set the field to + `undefined` and let consumers no-op when they see it missing. Other + non-identifying fields (config overrides, etc.) can remain populated. + Examples: `src/services/mixpanel/config.ts`, + `src/services/google-analytics/config.ts`. + ```ts + const projectToken = !app.isPrivateMode + ? getEnvValue('NEXT_PUBLIC_MIXPANEL_PROJECT_TOKEN') + : undefined; + ``` + +If the new variable doesn't touch user data (purely cosmetic, chain config, +URL of a same-origin asset, etc.), this section doesn't apply. + +## Step 3 — Validator schema and tests + +Add the rule and a test preset entry. The full procedure (which schema file, +where in the schema, JSON shape conventions, JSON-URL example assets, +companion-variable rules, running the tests, verifying the negative path) +lives in `deploy/tools/envs-validator/CONTEXT.md` — follow the "Adding a new +variable" section there. + +## Step 4 — Only if the variable holds an external non-asset URL + +Most URL variables need a CSP allowance under `src/server/csp/policies/`. +Gate the addition on the relevant config option being enabled — don't widen +the CSP unconditionally. + +**Exceptions** — these are already auto-included by `policies/app.ts` and +need no manual CSP work: + +- new API `endpoint` and `socketEndpoint` values that flow into `config.apis.*`. + +If the new variable lands inside one of those config paths, skip this step. +For any other external host (analytics, third-party services, custom +integrations, etc.), add the domain to the matching policy and gate it on +the feature/option being enabled. + +## Step 5 — Only if the variable holds an asset URL (image or JSON config) + +Append the variable name to the `ASSETS_ENVS` array in +`deploy/scripts/download_assets.sh`. The container entrypoint downloads the +asset into the image at startup so the browser serves it same-origin. + diff --git a/.agents/skills/add-new-page/SKILL.md b/.agents/skills/add-new-page/SKILL.md new file mode 100644 index 00000000000..3e1a55bce34 --- /dev/null +++ b/.agents/skills/add-new-page/SKILL.md @@ -0,0 +1,251 @@ +--- +name: add-new-page +description: Scaffold and wire up a new page (index / detail / general). Use when adding any new route to the app. +--- + +# Add a new page + +This skill scaffolds the **page layout** from a per-type template and wires the page into every system that +must know about it (navigation, metadata, server guard, route types, analytics, sitemap). + +**Scope — layout only.** The scaffold produces layout skeletons with clearly-marked `TODO`s. **Wiring up API +data, pagination, filtering, sorting, and action bars is out of scope** and left to follow-up work. No stubs, +mocks, or tests are produced here. As a consequence, a freshly-scaffolded page contains intentional `TODO`s +and will not fully pass `lint`/`tsc` until its data is wired — that is expected. + +**Rule of thumb: never guess.** Whenever information is missing or required to proceed, **pause and ask the +user, or confirm your findings**, before writing files. + +## Templates + +Templates live in `.agents/skills/add-new-page/templates/`. Copy the set matching the page type, then +substitute the placeholders and delete any optional blocks you don't need: + +| Placeholder | Meaning | Example | +|---|---|---| +| `__PageName__` | PascalCase component name | `Validators`, `ValidatorDetails` | +| `__area__` | `features` (config-gated) or `slices` (core) | `features` | +| `__featureName__` | folder name + `config.features` key | `validators` | +| `__pageDir__` | `index` (root page) or `details` (detail page) | `index` | +| `__route__` | the route path | `/validators` | +| `__pathname__` | the typed pathname (route, or with `[param]`) | `/validators/[id]` | +| `__title__` | `` text | `Validators` | +| `__entityParam__` | dynamic-route param name (detail pages) | `id`, `hash` | +| `__gsspName__` | `getServerSideProps` export name in `main.ts` | `validators` | + +Template sets: +- `templates/general/{Page,GeneralInfo}.tsx.tmpl` (`Page` = non-tabbed general page; `GeneralInfo` = a general-content body, used as a tab panel) +- `templates/detail/{Page,Details}.tsx.tmpl` +- `templates/index/{Page,Content,Table,TableItem,List,ListItem}.tsx.tmpl` +- `templates/tabs/PageWithTabs.tsx.tmpl` (tabbed page shell — `PageTitle` + `RoutedTabs`) +- `templates/route.tsx.tmpl` (shared route wrapper; has a commented dynamic-route variant) + +A **page shell** is one of: a non-tabbed `Page.tsx.tmpl` (embeds a single content body) or the tabbed +`PageWithTabs.tsx.tmpl` (renders `RoutedTabs`). A **content body** is `Details` (detail), `Content` + +`Table`/`List`/items (index), or `GeneralInfo` (general). Tabbed pages combine one shell with one body per tab. + +**Naming the destination files.** Drop the `.tmpl` extension and name each component file after the +component it exports — i.e. the `__PageName__`-prefixed name used in the template's `const` / `import` +statements. The base `Page.tsx.tmpl` is special: it becomes just `__PageName__.tsx` (no "Page" suffix). The +route wrapper is named after the route, not the component. + +| Template | Destination (for `__PageName__` = `Validators`) | +|---|---| +| `general/Page.tsx.tmpl` | `__PageName__.tsx` → `Validators.tsx` | +| `detail/Page.tsx.tmpl` | `__PageName__.tsx` → `Validators.tsx` | +| `detail/Details.tsx.tmpl` | `__PageName__Details.tsx` → `ValidatorsDetails.tsx` | +| `index/Page.tsx.tmpl` | `__PageName__.tsx` → `Validators.tsx` | +| `index/Content.tsx.tmpl` | `__PageName__Content.tsx` → `ValidatorsContent.tsx` | +| `index/Table.tsx.tmpl` | `__PageName__Table.tsx` → `ValidatorsTable.tsx` | +| `index/TableItem.tsx.tmpl` | `__PageName__TableItem.tsx` → `ValidatorsTableItem.tsx` | +| `index/List.tsx.tmpl` | `__PageName__List.tsx` → `ValidatorsList.tsx` | +| `index/ListItem.tsx.tmpl` | `__PageName__ListItem.tsx` → `ValidatorsListItem.tsx` | +| `general/GeneralInfo.tsx.tmpl` | `__PageName__GeneralInfo.tsx` → `ValidatorsGeneralInfo.tsx` | +| `tabs/PageWithTabs.tsx.tmpl` | `__PageName__.tsx` → `Validators.tsx` | +| `route.tsx.tmpl` | `src/pages/__route__.tsx` → `src/pages/validators.tsx` | + +**Tabbed pages — folders & per-tab names.** The page shell sits directly in the page dir; **each tab's body +components live in their own kebab-case sub-folder** under it (mirrors +`src/slices/tx/pages/details/{info,logs,state,…}`). Name a tab's components after the tab so same-type tabs +don't collide: replace the `__PageName__` base with `__PageName__` (PascalCase tab name), and rename +the body's sub-components/imports to match. E.g. an `index`-type tab "Active" on page `Validators` → folder +`active/` holding `ValidatorsActive` (from `Content`), `ValidatorsActiveTable`, `ValidatorsActiveTableItem`, +`ValidatorsActiveList`, `ValidatorsActiveListItem`. A `detail`-type tab "Details" → folder `info/` holding +`ValidatorsDetails` (from `Details`). A `general`-type tab "Stats" → folder `stats/` holding `ValidatorsStats` +(from `GeneralInfo`). + +**Details-tab rule:** a `details`-type tab either **comes first** (id `'index'`, title `Details`) **or doesn't +exist at all** — never place it after another tab. + +## Step 0 — Agree on the shape (REQUIRED before writing anything) + +Propose each of the following and **wait for the user's explicit approval**. Ask whenever an answer is not +obvious from the conversation or the codebase. + +1. **Layout** — does the page have **tabs**? Decide this first; ask if it isn't obvious from the conversation. + - **No tabs** → pick the single content type: `index` (list + table views), `detail` (label/value grid), + or `general` (title + content). + - **Tabs** → list the tabs **in order**; for each, give a **name** (the tab title) and a **content type** + (`details` / `index` / `general info`). That's enough at this stage. **A `details` tab, if present, must + be the first tab**. +2. **Route & path params** — the exact path. If it's **dynamic** (`/foo/[param]`), confirm the param + name(s) and what each represents (`__entityParam__`, `__pathname__`). +3. **Gated or core?** — apply the architecture rule's test: *"Can this exist on a vanilla EVM chain with no + feature flag?"* If yes → **core slice** (`__area__` = `slices`, no feature config/sitemap). If no → + **config-gated feature** (`__area__` = `features`). +4. **Title** and **navigation placement** — the `` text, the nav label, and which nav group it + belongs to. +5. **Metadata** — **suggest** a default page title and description; let the user confirm or refine. Default + only — do **not** add `enhanced` variants. + +Proceed only once these are settled. + +## Step 1 — Feature & env variable (config-gated pages only) + +Skip this step entirely for core slice pages. + +The route's server guard (Step 3) depends on the feature flag, so the feature and its env variable must exist +**before** the route file. If the feature and its config already exist, proceed to the next step. + +- **Invoke the `add-env-var` skill** to create the guarding env variable(s), the feature + `src/features/__featureName__/config.ts`, and its registration in `src/config/features.ts`. That skill + interviews the user about the new feature and its variable(s) — **all env-var questions happen there, not + here.** +- `add-env-var` covers `docs/ENVS.md`, `config.ts`, `features.ts`, the validator schema, and CSP/assets. It + does **not** create the server guard — that lives in Step 3. + +## Step 2 — Scaffold the page component(s) (layout only) + +Create the component folder, mirroring `src/slices/block/pages/{index,details}`. `__pageDir__` is `details` +for an entity/detail page (typically a dynamic route), otherwise `index`: + +- `src/__area__/__featureName__/pages/__pageDir__/` + +The **content bodies** are the same regardless of tabs — reference these exemplars while filling in real layout: + +- **general** (`GeneralInfo.tsx`, or inline in a non-tabbed `general/Page.tsx`) — a content slot. Exemplar + `src/features/gas-tracker/pages/index/GasTracker.tsx`. +- **detail** (`Details.tsx`) — a `DetailedInfo.Container` grid of `ItemLabel`/`ItemValue` rows wrapped in + `Skeleton`. Exemplars `src/slices/block/pages/details/BlockDetails.tsx`; grid `src/shared/detailed-info/DetailedInfo.tsx`. +- **index** (`Content.tsx` + `Table`/`TableItem` + `List`/`ListItem`) — `Content.tsx` is `DataList` + dual + view (`` mobile `List`, `` desktop `Table`); `Table`/`List` render + one row per item via dedicated `TableItem`/`ListItem` components (mirroring `BlocksTableItem`/`BlocksListItem`). + Exemplars `src/slices/block/pages/index/{BlocksContent,BlocksTable,BlocksTableItem,BlocksList,BlocksListItem}.tsx`; + `src/shared/lists/DataList.tsx`. + Replace the `DataList` placeholders with the page's entity (don't leave the generic "items"): + set `emptyText` to a plural no-data sentence (`"There are no blocks."`) and `emptyStateProps={{ term: '' }}` to + the singular form (`'block'`). + +Then branch on the Step 0 layout decision: + +### A. No tabs + +Use the single page shell that embeds its one content body directly: + +- **general** → `general/Page.tsx.tmpl` (content inline; no separate body needed). +- **detail** → `detail/Page.tsx.tmpl` (reads the route param, renders `PageTitle` + `Details`) + `detail/Details.tsx.tmpl`. +- **index** → `index/Page.tsx.tmpl` (renders `PageTitle` + `Content`) + `index/{Content,Table,TableItem,List,ListItem}.tsx.tmpl`. + +### B. With tabs + +1. **Page shell** — copy `tabs/PageWithTabs.tsx.tmpl` (→ `__PageName__.tsx`, directly in the page dir). It + renders `PageTitle` + `RoutedTabs`. Fill the `tabs` array with one `{ id, title, component }` entry per + tab; the first tab's id is conventionally `'index'`. If a `details` tab exists it is the **first** entry + (`id: 'index'`, title `Details`). +2. **One body per tab** — for each tab, create a kebab-case **sub-folder** under the page dir and scaffold a + body there from the content template matching its type (`Details` / index `Content` + sub-components / + `GeneralInfo`), **named after the tab** (`__PageName__`, see the folders & naming rule above). + Wire the renamed components into the shell's `tabs` array and imports. + +Leave the `// TODO: wire up data …` markers in place — data fetching is out of scope. + +## Step 3 — Route file + server guard + +The guard and the route belong together: the route's `getServerSideProps` references the guard. + +1. **Server guard** (config-gated pages) — in `src/server/getServerSideProps/guards.ts` add: + ```ts + export const __gsspName__: Guard = (chainConfig: typeof config) => async() => { + if (!chainConfig.features.__featureName__.isEnabled) { + return { notFound: true }; + } + }; + ``` + For a **core slice** page, check `chainConfig.slices.__featureName__.isEnabled` instead. Then register the + factory in `src/server/getServerSideProps/main.ts`: + ```ts + export const __gsspName__ = factory([ guards.__gsspName__ ]); + ``` + An **un-gated** page needs no new guard — reuse `base` (or `notMultichain` for non-multichain pages) as + `__gsspName__` and skip the guard creation. + +2. **Route file** — create `src/pages/__route__.tsx` from `templates/route.tsx.tmpl`. For a dynamic route use + the commented dynamic-route variant in the template (threads `query` through `PageNextJs`; exemplar + `src/pages/tx/[hash].tsx`). + +## Step 4 — Generate route types + +Run **`pnpm run routes:generate`** (runs `nextjs-routes`) to regenerate `src/shared/router/nextjs-routes.d.ts`. +`__pathname__` only type-checks after this, so do it before the steps below. + +## Step 5 — Navigation + +In `src/shell/navigation/useNavItems.tsx` add a `NavItem` to the group agreed in Step 0: +```ts +const __featureName__: NavItem | null = config.features.__featureName__.isEnabled ? { + text: '__title__', + nextRoute: { pathname: '__route__' as const }, + // TODO: set nav icon + isActive: pathname.startsWith('__route__'), +} : null; +``` +For a core slice page drop the `config.features.*` guard (or gate on `config.slices.*`). **Do not invent an +icon** — leave the `// TODO: set nav icon` comment and omit the `icon` field so the user assigns the correct +sprite later. Then insert the item into the appropriate group array. + +## Step 6 — Metadata + +In `src/shell/metadata/templates/index.ts` add an entry keyed by `__pathname__`, using the user-approved +title/description (`default` only, no `enhanced`): +```ts +'__pathname__': { + metadata: { + title: { 'default': '…' }, + description: { 'default': '…' }, + }, + // og below — INDEX pages only + og: OG_ROOT_PAGE, +}, +``` +**`og: OG_ROOT_PAGE` is for `index` pages only.** Detail and general pages **omit the `og` field entirely** +(matches existing detail entries such as `/block/[height_or_hash]`). + +## Step 7 — Page-type analytics + +In `src/services/mixpanel/get-page-type.ts` add an entry to `PAGE_TYPE_DICT` (a +`Record`). **Suggest** a human-readable name; let the user adjust: +```ts +'__pathname__': '__title__', +``` + +## Step 8 — Sitemap (static routes only) + +**If the route is dynamic, skip this step** — dynamic routes are required API data and therefore are out of the +scope of this workflow. + +For a **static, config-gated** route, add a `case` to the `transform` switch in +`deploy/tools/sitemap-generator/next-sitemap.config.js` so it's excluded when the feature is off: +```js +case '__route__': + if (process.env.NEXT_PUBLIC__ENABLED !== 'true') { + return null; + } + break; +``` + +## Step 9 — Check TypeScript compiles + +Run `pnpm run lint:tsc` (`tsc -p ./tsconfig.json`) to confirm the new files type-check. The scaffold uses the +`unknown` item type and never reads properties off it, so TypeScript should pass as-is. **ESLint is expected +to fail** on the scaffold's intentional `TODO`s (unused `item` props, single-value `const`s) until the data +is wired — don't gate on it at this stage. diff --git a/.agents/skills/add-new-page/templates/detail/Details.tsx.tmpl b/.agents/skills/add-new-page/templates/detail/Details.tsx.tmpl new file mode 100644 index 00000000000..79f3b1c6154 --- /dev/null +++ b/.agents/skills/add-new-page/templates/detail/Details.tsx.tmpl @@ -0,0 +1,34 @@ +// SPDX-License-Identifier: LicenseRef-Blockscout + +import React from 'react'; + +import * as DetailedInfo from 'src/shared/detailed-info/DetailedInfo'; + +import { Skeleton } from 'src/toolkit/chakra/skeleton'; + +interface Props { + data: unknown; + isLoading?: boolean; +} + +const __PageName__Details = ({ data, isLoading }: Props) => { + // TODO: replace placeholder rows with real fields from the fetched data. + // Each field is a label/value pair. Use DetailedInfo.ItemDivider to group sections. + + return ( + + + Label + + + + Value + + + + { /* TODO: add more DetailedInfo.ItemLabel / DetailedInfo.ItemValue rows */ } + + ); +}; + +export default __PageName__Details; diff --git a/.agents/skills/add-new-page/templates/detail/Page.tsx.tmpl b/.agents/skills/add-new-page/templates/detail/Page.tsx.tmpl new file mode 100644 index 00000000000..087d2467b4a --- /dev/null +++ b/.agents/skills/add-new-page/templates/detail/Page.tsx.tmpl @@ -0,0 +1,29 @@ +// SPDX-License-Identifier: LicenseRef-Blockscout + +import { useRouter } from 'next/router'; +import React from 'react'; + +import PageTitle from 'src/shell/page/title/PageTitle'; + +import __PageName__Details from 'src/__area__/__featureName__/pages/details/__PageName__Details'; + +import getQueryParamString from 'src/shared/router/get-query-param-string'; + +const __PageName__ = () => { + const router = useRouter(); + const __entityParam__ = getQueryParamString(router.query.__entityParam__); + + // TODO: wire up data (useApiQuery keyed by __entityParam__) — out of scaffold scope. + // Drive `isLoading` from the query's `isPlaceholderData` once wired. + const isLoading = false; + const data: unknown = {}; + + return ( + <> + + <__PageName__Details isLoading={ isLoading } data={ data }/> + + ); +}; + +export default __PageName__; diff --git a/.agents/skills/add-new-page/templates/general/GeneralInfo.tsx.tmpl b/.agents/skills/add-new-page/templates/general/GeneralInfo.tsx.tmpl new file mode 100644 index 00000000000..5cf65c2cd1e --- /dev/null +++ b/.agents/skills/add-new-page/templates/general/GeneralInfo.tsx.tmpl @@ -0,0 +1,17 @@ +// SPDX-License-Identifier: LicenseRef-Blockscout + +import { Box } from '@chakra-ui/react'; +import React from 'react'; + +// General-info body — usable as a tab panel (or extract for a non-tabbed general page). +const __PageName__GeneralInfo = () => { + // TODO: wire up data (useApiQuery) — out of scaffold scope + + return ( + + { /* TODO: content */ } + + ); +}; + +export default __PageName__GeneralInfo; diff --git a/.agents/skills/add-new-page/templates/general/Page.tsx.tmpl b/.agents/skills/add-new-page/templates/general/Page.tsx.tmpl new file mode 100644 index 00000000000..2f743aff002 --- /dev/null +++ b/.agents/skills/add-new-page/templates/general/Page.tsx.tmpl @@ -0,0 +1,18 @@ +// SPDX-License-Identifier: LicenseRef-Blockscout + +import React from 'react'; + +import PageTitle from 'src/shell/page/title/PageTitle'; + +const __PageName__ = () => { + // TODO: wire up data (useApiQuery) — out of scaffold scope + + return ( + <> + + { /* TODO: page content */ } + + ); +}; + +export default __PageName__; diff --git a/.agents/skills/add-new-page/templates/index/Content.tsx.tmpl b/.agents/skills/add-new-page/templates/index/Content.tsx.tmpl new file mode 100644 index 00000000000..99132dd43e9 --- /dev/null +++ b/.agents/skills/add-new-page/templates/index/Content.tsx.tmpl @@ -0,0 +1,39 @@ +// SPDX-License-Identifier: LicenseRef-Blockscout + +import { Box } from '@chakra-ui/react'; +import React from 'react'; + +import __PageName__List from 'src/__area__/__featureName__/pages/index/__PageName__List'; +import __PageName__Table from 'src/__area__/__featureName__/pages/index/__PageName__Table'; + +import DataList from 'src/shared/lists/DataList'; + +const __PageName__Content = () => { + // TODO: wire up data (useQueryWithPages) — out of scaffold scope. + // Pagination, filtering, sorting and the action bar are intentionally omitted from + // this scaffold. See src/slices/block/pages/index/BlocksContent.tsx for the full pattern. + // Three placeholder items so the table/list render visible example rows — replace with fetched data. + const items: Array = [ {}, {}, {} ]; + const isError = false; + const isLoading = false; + + return ( + // TODO: replace the "items"/"item" placeholders below with this page's entity — + // plural in `emptyText` ("There are no validators."), singular in `emptyStateProps.term` ("validator"). + + + <__PageName__List items={ items } isLoading={ isLoading }/> + + + <__PageName__Table items={ items } isLoading={ isLoading }/> + + + ); +}; + +export default __PageName__Content; diff --git a/.agents/skills/add-new-page/templates/index/List.tsx.tmpl b/.agents/skills/add-new-page/templates/index/List.tsx.tmpl new file mode 100644 index 00000000000..3d3200c2fb6 --- /dev/null +++ b/.agents/skills/add-new-page/templates/index/List.tsx.tmpl @@ -0,0 +1,24 @@ +// SPDX-License-Identifier: LicenseRef-Blockscout + +import { Box } from '@chakra-ui/react'; +import React from 'react'; + +import __PageName__ListItem from 'src/__area__/__featureName__/pages/index/__PageName__ListItem'; + +interface Props { + // TODO: replace `unknown` with the real item type + items: Array; + isLoading?: boolean; +} + +const __PageName__List = ({ items, isLoading }: Props) => { + return ( + + { items.map((item, index) => ( + <__PageName__ListItem key={ index } item={ item } isLoading={ isLoading }/> + )) } + + ); +}; + +export default __PageName__List; diff --git a/.agents/skills/add-new-page/templates/index/ListItem.tsx.tmpl b/.agents/skills/add-new-page/templates/index/ListItem.tsx.tmpl new file mode 100644 index 00000000000..26d37895142 --- /dev/null +++ b/.agents/skills/add-new-page/templates/index/ListItem.tsx.tmpl @@ -0,0 +1,37 @@ +// SPDX-License-Identifier: LicenseRef-Blockscout + +import React from 'react'; + +import ListItemMobileGrid from 'src/shared/lists/ListItemMobileGrid'; + +import { Skeleton } from 'src/toolkit/chakra/skeleton'; + +interface Props { + // TODO: replace `unknown` with the real item type + item: unknown; + isLoading?: boolean; +} + +const __PageName__ListItem = ({ item, isLoading }: Props) => { + return ( + + { /* TODO: one Label/Value pair per field; render values from `item` */ } + Label 1 + + Value 1 + + + Label 2 + + Value 2 + + + Label 3 + + Value 3 + + + ); +}; + +export default __PageName__ListItem; diff --git a/.agents/skills/add-new-page/templates/index/Page.tsx.tmpl b/.agents/skills/add-new-page/templates/index/Page.tsx.tmpl new file mode 100644 index 00000000000..905f2c74421 --- /dev/null +++ b/.agents/skills/add-new-page/templates/index/Page.tsx.tmpl @@ -0,0 +1,18 @@ +// SPDX-License-Identifier: LicenseRef-Blockscout + +import React from 'react'; + +import PageTitle from 'src/shell/page/title/PageTitle'; + +import __PageName__Content from 'src/__area__/__featureName__/pages/index/__PageName__Content'; + +const __PageName__ = () => { + return ( + <> + + <__PageName__Content/> + + ); +}; + +export default __PageName__; diff --git a/.agents/skills/add-new-page/templates/index/Table.tsx.tmpl b/.agents/skills/add-new-page/templates/index/Table.tsx.tmpl new file mode 100644 index 00000000000..2dd73728cde --- /dev/null +++ b/.agents/skills/add-new-page/templates/index/Table.tsx.tmpl @@ -0,0 +1,35 @@ +// SPDX-License-Identifier: LicenseRef-Blockscout + +import React from 'react'; + +import __PageName__TableItem from 'src/__area__/__featureName__/pages/index/__PageName__TableItem'; + +import { TableBody, TableColumnHeader, TableHeaderSticky, TableRoot, TableRow } from 'src/toolkit/chakra/table'; + +interface Props { + // TODO: replace `unknown` with the real item type + items: Array; + isLoading?: boolean; +} + +const __PageName__Table = ({ items, isLoading }: Props) => { + return ( + + + + { /* TODO: define columns */ } + Column 1 + Column 2 + Column 3 + + + + { items.map((item, index) => ( + <__PageName__TableItem key={ index } item={ item } isLoading={ isLoading }/> + )) } + + + ); +}; + +export default __PageName__Table; diff --git a/.agents/skills/add-new-page/templates/index/TableItem.tsx.tmpl b/.agents/skills/add-new-page/templates/index/TableItem.tsx.tmpl new file mode 100644 index 00000000000..acddfe5f05a --- /dev/null +++ b/.agents/skills/add-new-page/templates/index/TableItem.tsx.tmpl @@ -0,0 +1,31 @@ +// SPDX-License-Identifier: LicenseRef-Blockscout + +import React from 'react'; + +import { Skeleton } from 'src/toolkit/chakra/skeleton'; +import { TableCell, TableRow } from 'src/toolkit/chakra/table'; + +interface Props { + // TODO: replace `unknown` with the real item type + item: unknown; + isLoading?: boolean; +} + +const __PageName__TableItem = ({ item, isLoading }: Props) => { + return ( + + { /* TODO: one TableCell per column in __PageName__Table; render values from `item` */ } + + Value 1 + + + Value 2 + + + Value 3 + + + ); +}; + +export default __PageName__TableItem; diff --git a/.agents/skills/add-new-page/templates/route.tsx.tmpl b/.agents/skills/add-new-page/templates/route.tsx.tmpl new file mode 100644 index 00000000000..1a28a3cb131 --- /dev/null +++ b/.agents/skills/add-new-page/templates/route.tsx.tmpl @@ -0,0 +1,41 @@ +// SPDX-License-Identifier: LicenseRef-Blockscout + +import type { NextPage } from 'next'; +import dynamic from 'next/dynamic'; +import React from 'react'; + +import PageNextJs from 'src/server/PageNextJs'; + +const __PageName__ = dynamic(() => import('src/__area__/__featureName__/pages/__pageDir__/__PageName__'), { ssr: false }); + +const Page: NextPage = () => { + return ( + + <__PageName__/> + + ); +}; + +export default Page; + +export { __gsspName__ as getServerSideProps } from 'src/server/getServerSideProps/main'; + +/* + ── DYNAMIC ROUTE VARIANT ────────────────────────────────────────────── + If `__pathname__` contains a param (e.g. /__route__/[__entityParam__]), + thread `query` through so the param is available on first render. Replace + the two declarations above with: + + import type { Props } from 'src/server/getServerSideProps/handlers'; + + const Page: NextPage = (props: Props) => { + return ( + + <__PageName__/> + + ); + }; + + Real example: src/pages/tx/[hash].tsx + ─────────────────────────────────────────────────────────────────────── +*/ diff --git a/.agents/skills/add-new-page/templates/tabs/PageWithTabs.tsx.tmpl b/.agents/skills/add-new-page/templates/tabs/PageWithTabs.tsx.tmpl new file mode 100644 index 00000000000..aa7010e09e3 --- /dev/null +++ b/.agents/skills/add-new-page/templates/tabs/PageWithTabs.tsx.tmpl @@ -0,0 +1,36 @@ +// SPDX-License-Identifier: LicenseRef-Blockscout + +import React from 'react'; + +import type { TabItemRegular } from 'src/toolkit/components/AdaptiveTabs/types'; + +import PageTitle from 'src/shell/page/title/PageTitle'; + +// Each tab's body lives in its own kebab-case sub-folder under the page dir +// (mirrors src/slices/tx/pages/details/{info,logs,state,...}). Name components __PageName__. +import __PageName__Tab1 from 'src/__area__/__featureName__/pages/__pageDir__/tab1/__PageName__Tab1'; +import __PageName__Tab2 from 'src/__area__/__featureName__/pages/__pageDir__/tab2/__PageName__Tab2'; + +import RoutedTabs from 'src/toolkit/components/RoutedTabs/RoutedTabs'; + +const __PageName__ = () => { + // Detail (entity) pages: read the route param here for the title, e.g. + // const router = useRouter(); + // const __entityParam__ = getQueryParamString(router.query.__entityParam__); + + // TODO: wire up data (useApiQuery / useQueryWithPages) — out of scaffold scope. + + const tabs: Array = [ + { id: 'tab1', title: 'Tab 1', component: <__PageName__Tab1/> }, + { id: 'tab2', title: 'Tab 2', component: <__PageName__Tab2/> }, + ]; + + return ( + <> + + + + ); +}; + +export default __PageName__; diff --git a/.agents/skills/arch-migrate/SKILL.md b/.agents/skills/arch-migrate/SKILL.md deleted file mode 100644 index 5170d5ce10e..00000000000 --- a/.agents/skills/arch-migrate/SKILL.md +++ /dev/null @@ -1,117 +0,0 @@ ---- -name: arch-migrate -description: Execute a migration task from a GitHub issue through to a PR, or fix unresolved review comments on an existing migration PR. Reads scope and acceptance criteria from the issue body. ---- - -You are executing a step of the Blockscout frontend client architecture migration. - -## Invocation -The skill can **ONLY** be invoked as: `/arch-migrate ("execute" mode)` or `/arch-migrate fix ("fix review comments" mode)`. - -## GitHub tool selection - -Use whichever GitHub access method is available in your environment — in order of preference: - -1. **`gh` CLI** — if available and authenticated (`gh auth status` succeeds), use it for all GitHub operations. -2. **GitHub MCP server tools** — if `gh` is unavailable, use the MCP tools provided by the GitHub MCP server. -3. **REST API via `curl`** — if neither above is available, use the GitHub REST API directly with `curl` and a `GITHUB_TOKEN` env var. - -Detect availability once at the start and stick with that method throughout. Do not mix methods. - -## Mode - -**Execute** (`/arch-migrate `): fetch the GitHub issue and execute the migration through to a PR. - -**Fix** (`/arch-migrate fix `): address unresolved review comments on an existing PR. - ---- - -## Execute mode - -### 1. Read context - -Fetch the issue body from `blockscout/frontend` using the available GitHub tool. The issue number is the argument passed to the skill. - -Also read: -- `client/ARCH_REDESIGN.md` — naming conventions (§2), dependency rules (§8), execution rules (§10) -- `docs/GLOSSARY.md` — domain terminology - -The issue body contains the **Scope**, **Findings**, and **Acceptance criteria** for this task. That is your working spec. - -### 2. Explore before touching -Read all source files listed in the issue **Scope**. Understand what they export and what tests exist. - -### 3. Move and delete files -- Destination comes from the issue Scope and `ARCH_REDESIGN.md §6` migration map. -- Rename files to kebab-case **at move time** — no separate rename PR. -- Component files stay PascalCase. Hook files stay `useCamelCase.ts`. Everything else: kebab-case. -- Extract types/interfaces flagged in the issue **Findings** section into their new homes. -- **After writing each file at its destination, delete the source.** Use the `## Files to delete` list in the issue as your checklist — work through it item by item. If an entry is a folder, delete the entire folder (`rm -rf`). If an entry is a file, delete that file. -- **Verify deletions before moving on.** Once all moves are done, check that every path listed under `## Files to delete` is gone. For any that still exist, delete them now. - -### 4. Update all imports in the same PR -- Update every import path across the entire repo. -- For high-fanout files, write a codemod script, run it, then **delete the script** — do not commit it to the repo. Commit only the resulting file changes. -- No re-export shims. No long-lived compatibility aliases. - -### 5. Run checks -``` -pnpm lint:tsc # must pass -pnpm lint:eslint:fix # must pass -``` - -Fix errors if any (warnings are acceptable). - -### 6. Cross-slice dependencies left at old paths -If a dependency is not yet migrated, leave its import at the old path and list it explicitly in the PR description as a follow-up for the relevant future task. - -### 7. Branch and PR - -- Extract the task ID from the issue title (format: `[Migration] : ...`). -- Branch name: `migration/-` (e.g. `migration/1-1-client-api`) -- Branch from `main`. -- PR title: `[Migration ] ` -- PR targets `main`. -- Create the PR using the available GitHub tool (see **GitHub tool selection** above). -- PR body must include: - - `Closes #` — on the first line, so GitHub auto-closes the issue on merge - - What moved and where - - Any codemods run (include the command / script body used, but do not commit the script itself) - - Cross-slice deps left at legacy paths (list file + old import path) - - Checklist: `pnpm lint:tsc` passing, `pnpm lint:eslint` clean within `client/`, all source files/folders deleted from old paths -- PR labels: `refactoring` - ---- - -## Fix mode - -`/arch-migrate fix ` — address unresolved review comments only. - -1. Fetch unresolved review threads for the PR from `blockscout/frontend` using the available GitHub tool. If using `gh`: - -```bash -gh api graphql -f query=' -{ - repository(owner: "blockscout", name: "frontend") { - pullRequest(number: ) { - reviewThreads(first: 50) { - nodes { - isResolved - path - line - comments(first: 5) { - nodes { body author { login } } - } - } - } - } - } -}' -``` - -If using the GitHub MCP server, call `list_review_comments_on_pull_request` (for inline comments) and `get_pull_request_reviews` (for review-level comments), then filter to unresolved threads manually. If using the REST API, `GET /repos/blockscout/frontend/pulls//comments` returns inline comments; filter by `in_reply_to_id` to group threads. - -Keep only threads where `isResolved: false`. Skip everything else — the reviewer marks threads resolved when they are no longer relevant. - -2. Address each unresolved thread. Push fixes to the existing branch. -3. Summarise which threads were addressed and what changed. diff --git a/.agents/skills/arch-research/SKILL.md b/.agents/skills/arch-research/SKILL.md deleted file mode 100644 index a5386b25297..00000000000 --- a/.agents/skills/arch-research/SKILL.md +++ /dev/null @@ -1,201 +0,0 @@ ---- -name: arch-research -description: Research a migration task, create a GitHub sub-issue with scope and acceptance criteria, and update the task backlog. Takes a task ID from docs/MIGRATION_TASKS.md, explores the codebase, creates an issue in blockscout/frontend, links it to the parent migration issue, and commits the status update. ---- - -You are preparing a migration task for execution by exploring the codebase and producing a well-scoped GitHub issue. - -## Invocation -The skill can **ONLY** be invoked as: `/arch-research ` (e.g. `/arch-research 1-1`) - -## Prerequisites - -Follow the **check-github-cli** skill first to ensure `gh` is available and authenticated. - -## Steps - -### 1. Find the task - -Read `client/MIGRATION_TASKS.md`. Locate the task by the given ID (e.g. `1-1`). Read its **Scope** section. - -If the task already has `[~]` or `[x]` status, stop and report that it has already been researched or completed. - -Also read: -- `client/ARCH_REDESIGN.md` — conventions and migration map (§2, §4, §6) -- `docs/GLOSSARY.md` — domain terminology - -### 2. Plan the areas - -Do a quick top-level scan of the task Scope and the standard source directories to understand what exists. Then present the user with the list of areas you will cover, in order, with a one-line description of what each contains for this specific task. Skip any area where you found nothing relevant. - -**Standard areas (always in this order):** - -1. **Types** — `types/api/*.ts`, `types/client/*.ts`, `types/views/*.ts`, and any related param/shared type files -2. **Stubs and mocks** — `stubs/*.ts`, `mocks/*.ts` -3. **Hooks, utilities, and contexts** — `lib/hooks/`, `lib//`, `lib/contexts/`, and any `utils/` files within `ui//` -4. **Shared components** — `ui/shared/entities//`, `ui/shared//` -5. **Pages** — `ui/pages/*.tsx`, `ui//` (the detail page and all its tabs, plus any index/list pages) -6. **Feature impact** — all features identified as affected across the previous areas - -Present as a short numbered list. For each area, note the rough file count or key directories found. End with: *"I'll work through them one at a time. Let me know if you want to skip or reorder any, otherwise I'll start with Area 1."* - -Wait for a go-ahead (or redirect) before proceeding. - -### 3. Analyze each area, one at a time - -Work through each area in sequence. For each one: - -**a. Analyze** — read the relevant files and apply the rules below that apply to this area. - -**b. Present a mini-plan** in this format: - -``` -### Area [N/total]: [Name] - -[One sentence describing what this area covers for this task.] - -**Plan** -| Source | Destination | -|--------|-------------| -| ... | ... | - -**Findings** -- [extractions, splits, mixed-concern files, conflicts, or "None."] -``` - -**c. Wait for explicit approval.** End each area with: *"Any corrections, or shall I move to Area [N+1]: [name]?"* - -Apply any corrections. If the changes are significant, re-show the updated mini-plan before proceeding. Do not move to the next area until the user approves the current one. - ---- - -**Rules to apply per area:** - -**Types (Area 1)** -- Two strictly separate type layers: - - `types/api.ts` destination: API/DTO shapes from `types/api/*.ts`. For each optional field group that maps to a feature (e.g. `celo?`, `arbitrum?`), extract it to `client/features//types/api.ts`. The slice's own `types/api.ts` composes them via `interface Entity extends FeatureTypeA, FeatureTypeB, ...`. - - `types/client.ts` destination: frontend-only derived types from `types/client/*.ts` and `types/views/*.ts`. Same feature decomposition applies — feature-specific client types go to `client/features//types/client.ts`. Never mix API shapes and client types in the same file. -- Verify the path exists for each source file; note any missing files. -- Check whether destination `types/api.ts` / `types/client.ts` already exists in `client/` (conflict check). - -**Stubs and mocks (Area 2)** -- Identify which stubs entries are feature-specific vs. entity-core. Feature-specific entries go to `client/features//stubs/.ts` (named after the slice, not the feature). Slice keeps only entity-core entries. -- Flag any high-fanout stub constants (e.g. `ENTITY_HASH`, `ENTITY_PARAMS`) that are imported across many test files — these require a codemod; note this in Findings. - -**Hooks, utilities, and contexts (Area 3)** -- For each hook file: check if it reads `config.features.`. If yes → `client/features//hooks/`. If no → `client/slices//hooks/` (or wherever the slice keeps its hooks). -- `lib/contexts/` scan: look for files whose name contains the entity name. Destination is `client/slices//contexts/` or `client/features//contexts/` — a dedicated sub-folder, not the root. -- Mixed-concern utility files: open any `utils.ts` in scope and check if it mixes logic from different domains. If yes, split rather than move; list what each new file should contain. - -**Shared components (Area 4)** -- `ui/shared/` sweep: search for components whose filename begins with the slice entity prefix. These are slice-owned components that ended up in the shared bucket. -- For each component file with a sibling `.pw.tsx` or `__screenshots__/` directory, include those siblings in the mapping. -- Classify each component: core slice → `client/slices//components/`; feature/chain-specific → `client/features//components/`. - -**Pages (Area 5)** -- `ui/pages/` sweep: search for page components whose filename begins with the entity prefix. **Also** scan the Next.js `pages/` directory for entries that `dynamic(() => import('ui/pages/'))` a component related to the entity — page filenames may not match the route (e.g. `Accounts.tsx` → `/accounts`). -- For a tabbed detail page, each tab becomes a named sub-folder under `pages/details/` (e.g. `info/`, `txs/`, `token-transfers/`). Feature-owned tab components go to `client/features//pages//` — never as sub-folders inside the slice's `pages/` tree. -- For index/list pages, all related list-item and table components go flat into `pages/index/`. -- Include `.pw.tsx` siblings and `__screenshots__/` directories for every page component. -- For each Next.js entry file, note the import path update required (the entry file itself stays in `pages/`). - -**Feature impact (Area 6)** -- Compile all features identified in Areas 1–5. -- For each feature, list: feature path, files to create or merge (`types/api.ts`, `types/client.ts`, `stubs/.ts`, page components), and what each file should contain. -- If no features are affected, state that explicitly. - -### 4. Compile and confirm - -Once all areas are approved, assemble the full issue body: - -```markdown -## Scope - -[All approved source → destination file mappings, organised by slice and feature. One mapping table per section.] - -## Feature impact - -[For each affected feature: - - Feature path - - Files to create or merge, and what they should contain -If none: "No feature-side files required."] - -## Findings - -[All Findings from the individual areas, consolidated. Remove duplicates.] - -If nothing non-obvious was found: "No conflicts or extractions identified — straightforward move." - -## Files to delete - -[Flat list of every source path that must be removed once its contents have been moved. Use a folder path when the entire folder moves; list individual files only when part of the folder stays. Paths that stay in place (e.g. Next.js `pages/` entry files) must NOT appear here.] - -## Acceptance criteria - -- [ ] All files moved to target paths per `ARCH_REDESIGN.md §6` -- [ ] All import paths updated repo-wide (no references to old paths remain) -- [ ] Extracted API types live in their new slice/feature `types/api.ts` -- [ ] Extracted client/UI types live in their new slice/feature `types/client.ts` -- [ ] `pnpm lint:tsc` passes -- [ ] `pnpm lint:eslint:fix` clean within `client/` (warnings in legacy paths acceptable) -- [ ] All source files/folders deleted from old paths (none remain) -- [ ] Cross-slice deps left at old paths are explicitly listed in the PR description -``` - -Present the full draft with the issue title (`[Migration] : `) and ask: *"Shall I create this issue? You can request changes before I proceed."* - -Wait for explicit confirmation. Apply any requested edits and re-confirm if the changes are significant. Do not proceed to step 5 until the user approves. - -### 5. Create the GitHub issue - -```bash -gh issue create \ - --repo blockscout/frontend \ - --title "[Migration] : " \ - --body "" \ - --label "Task for agent" -``` - -Capture the issue number from the output URL (e.g. `https://github.com/blockscout/frontend/issues/42` → number is `42`). - -### 6. Link to the parent issue - -Get the internal numeric `id` of the new issue (this is different from the issue number): - -```bash -gh api repos/blockscout/frontend/issues/ --jq '.id' -``` - -Link it as a sub-issue of the parent (parent issue number is in the `client/MIGRATION_TASKS.md` header): - -```bash -gh api \ - --method POST \ - repos/blockscout/frontend/issues//sub_issues \ - --field sub_issue_id= -``` - -### 7. Update client/MIGRATION_TASKS.md - -In the task entry, make two changes: -- Change status from `[ ]` to `[~]` -- Append the issue link to the task heading line - -Example — before: -``` -### 1-1 · [ ] Migrate `client/api/` -``` -After: -``` -### 1-1 · [~] Migrate `client/api/` · [#42](https://github.com/blockscout/frontend/issues/42) -``` - -Commit directly to `main`: - -```bash -git add client/MIGRATION_TASKS.md -git commit -m "track: 1-1 in progress — blockscout/frontend#42" -git push origin main -``` - -Replace `1-1` and `42` with the actual task ID and issue number. diff --git a/.agents/skills/deploy-demo/SKILL.md b/.agents/skills/deploy-demo/SKILL.md index b5fbc40301e..71e87942fc1 100644 --- a/.agents/skills/deploy-demo/SKILL.md +++ b/.agents/skills/deploy-demo/SKILL.md @@ -30,20 +30,32 @@ _Note:_ In the command output, format all URLs as clickable Markdown links: `[Li - `gh run list --workflow=deploy-review.yml --branch --status queued` - If any run is returned, **abort the command** and tell the user a deployment is already running for this branch (include a link to the run). -### 3. Resolve the "envs_preset" workflow input (optional) +### 3. Resolve the workflow inputs (all optional) -- **Get valid options from the workflow file (single source of truth):** Read `.github/workflows/deploy-review.yml` and extract the list of allowed values from `on.workflow_dispatch.inputs.envs_preset.options` (the YAML array under that key). Do not hardcode or copy the preset list into this skill — always read it from the file. -- Infer from the user’s prompt which preset they want. Examples: - - "deploy a demo for base" or "deploy with base preset" → preset `base` - - "spin up a demo using main preset" → preset `main` -- **If the user did not mention any preset:** skip passing `envs_preset` (the workflow uses its default). -- **Validate the chosen preset:** Check that the user’s chosen preset is one of the values in the options list you extracted from the workflow file. If it is not in that list, notify the user (you can show the valid options from the file) and **abort the command**. +**Always read `.github/workflows/deploy-review.yml` as the single source of truth for valid option values — never hardcode them in this skill.** The workflow has three `workflow_dispatch` inputs: + +**a. `envs_preset`** — which chain's config the demo fetches at startup. +- Allowed values: `on.workflow_dispatch.inputs.envs_preset.options`. +- Infer from the prompt: "deploy a demo for base" / "with base preset" → `base`; "spin up a demo using staging" → `staging`. +- If no preset is mentioned: skip the flag (workflow default). +- Validate against the options list; if invalid, show the valid options and **abort**. + +**b. `variant`** — which demo flavor (`on.workflow_dispatch.inputs.variant.options`, e.g. `review` / `review-2`). `review-2` disables ENVs validation (used for multichain). +- Pass `-f variant=review-2` only if the user clearly asks for it ("review-2", "validation-disabled / multichain variant", "second demo"). Otherwise skip (default `review`). +- Validate against the options list; if invalid, **abort**. + +**c. `build_image`** — whether to build & publish a fresh image (default `true`). +- The image is preset-agnostic, so **re-pointing an existing demo to a different chain does not need a rebuild**. If the user asks to *redeploy / re-point / switch an existing demo to another preset* ("redeploy it for celo", "switch the demo to base without rebuilding", "re-point to gnosis"), pass `-f build_image=false` — this skips the multi-minute image build and just re-runs the deploy (~1–2 min), changing `ENVS_PRESET` so the pods roll. +- For a first/normal deploy, skip the flag (default `true`, builds fresh). +- **Caveat:** `build_image=false` only works if an image for this branch + `variant` already exists (a prior successful build). If unsure, prefer building. Keep `variant` the same as the original deploy when re-pointing — the image tag and namespace are `-`. ### 4. Trigger the workflow and capture the run ID -- Run the workflow from the current branch (use the branch name from step 1 or 2): - - Without preset: `gh workflow run deploy-review.yml --ref ` - - With preset: `gh workflow run deploy-review.yml --ref -f envs_preset=` +- Run the workflow from the current branch (from step 1/2), passing only the flags resolved in step 3: + - `gh workflow run deploy-review.yml --ref [-f envs_preset=] [-f variant=] [-f build_image=false]` + - Examples: + - First deploy for base: `gh workflow run deploy-review.yml --ref -f envs_preset=base` + - Re-point existing demo to celo (no rebuild): `gh workflow run deploy-review.yml --ref -f build_image=false -f envs_preset=celo` - The CLI does not print the new run ID. To get it: wait a few seconds, then list the latest run for this workflow and branch: `gh run list --workflow=deploy-review.yml --branch --limit 1`. Use that run’s ID for the next steps. ### 5. Monitor the workflow until it completes diff --git a/.agents/skills/deprecate-env-var/SKILL.md b/.agents/skills/deprecate-env-var/SKILL.md new file mode 100644 index 00000000000..420586aa7dc --- /dev/null +++ b/.agents/skills/deprecate-env-var/SKILL.md @@ -0,0 +1,179 @@ +--- +name: deprecate-env-var +description: Checklist for deprecating a NEXT_PUBLIC_* environment variable — immediate removal, or a grace period before a later removal. Use when removing, renaming, or replacing a runtime env var. +--- + +# Deprecate an env variable + +This is the reverse of the `add-env-var` skill. Read `.agents/rules/env-vars.mdc` +first for background: how runtime delivery works, how the config object is +structured, the three value types, and where validation lives. + +## How removal is enforced + +Two startup checks make a removed variable fail loudly, so you rarely need to +write a custom guard: + +- The validator schemas use `.noUnknown(true)` — any `NEXT_PUBLIC_*` key not + declared in a schema fails validation. +- `checkPlaceholdersCongruity` (in `deploy/tools/envs-validator/index.ts`) + throws if an env has no build-time placeholder. Placeholders come from + `.env.registry`, which `collect_envs.sh` generates by scanning + `docs/ENVS.md`. + +Net effect: **a variable removed from both `docs/ENVS.md` and the schema will +fail container startup if an operator still sets it.** This is the hard stop +behind "immediate" removal, and the reason a grace period must keep the +variable in *both* places until the final removal. + +## Step 0 — Find the current state, then pick the path + +A variable already mid-grace-period must **not** be treated as a fresh +deprecation. First check whether it is **already deprecated** from an earlier +release: grep `deploy/tools/envs-validator/index.ts` for a +`printDeprecationWarning` / `checkDeprecatedEnvs` entry naming it, and check its +`docs/ENVS.md` row for a "Deprecated…" annotation. + +- **Already in a grace period** — a request to "remove it" *is* the removal + phase. Go straight to **Branch B → Phase 2**; do **not** ask the user which + branch. + +- **Not yet deprecated** — **ask the user explicitly** which branch applies + (do not infer it): + + - **Immediate removal** — the variable is deleted in this release. Operators + who still pass it get a startup failure. Right when the feature is gone + entirely, or the value now comes from elsewhere (e.g. the API) and no + migration by the operator is expected. Precedents: + `NEXT_PUBLIC_HAS_MUD_FRAMEWORK`, `NEXT_PUBLIC_SAVE_ON_GAS_ENABLED`, the + Sentry variables. + + - **Grace period** — the variable keeps working for one or more releases with + a runtime deprecation warning, then is removed in a later release. Right + when operators need time to migrate, typically because the variable was + **renamed or replaced** by a new one. Precedent: + `NEXT_PUBLIC_RE_CAPTCHA_V3_APP_SITE_KEY` → `NEXT_PUBLIC_RE_CAPTCHA_APP_SITE_KEY` + (#2384). + +Also settle: **is there a replacement variable?** This changes the grace-period +recipe and the `Comment` you write in the docs. + +--- + +## Branch A — Immediate removal + +Do all of the following in one PR. + +### A1 — Move the docs row + +- Delete the variable's row (and its section/heading if it was the only row) + from `docs/ENVS.md`. +- Append a row to `docs/DEPRECATED_ENVS.md`. The **Description** must be the + variable's *original functional* description — if a grace period appended a + deprecation note (`Deprecated — use NEXT_PUBLIC_FOO instead.`), strip it back + to the original. Set **Deprecated in version** to `upcoming` (the release + process replaces it), and put the deprecation reason or replacement **only in + the Comment column** — e.g. `Feature is deprecated.`, `Replaced with + NEXT_PUBLIC_FOO`, or `Removed; configuration done on the API side`. + +### A2 — Remove the validator rule + +- Delete the rule from wherever it lives: inline in `schema.ts`, in + `schema_multichain.ts`, or in a feature sub-schema under + `schemas/features/.ts`. Remove it from **both** schemas / sub-schemas + if it applied in both modes. +- Remove the variable from every test preset under + `deploy/tools/envs-validator/test/` (`.env.base`, `.env.alt`, + `.env.multichain`, scenario presets). If it was a JSON-config-URL variable, + also delete its example payload under `test/assets/configs/` and its entry in + the `envsWithJsonConfig` array in `index.ts`. + +### A3 — Remove the app code + +- Remove the sub-config that reads it (the `getEnvValue('NEXT_PUBLIC_…')` / + `parseEnvJson` / `getExternalAssetFilePath` call) and every consumer. +- If a **whole feature** is being removed: delete the feature folder + (`config.ts`, components, `mocks/`, `*.pw.tsx` and their committed + screenshots) and remove its export from the aggregator + `src/config/features.ts`. + +### A4 — Remove downstream references (only those that existed) + +Check each and clean up if the variable/feature touched it: + +- CSP allowance under `src/server/csp/policies/`. +- `ASSETS_ENVS` array in `deploy/scripts/download_assets.sh` (asset-URL vars). +- `deploy/tools/sitemap-generator/next-sitemap.config.js`. +- `deploy/tools/llms-txt-generator/` generators. +- `public/icons/name.d.ts` (if a feature-only icon is gone). +- `.agents/GLOSSARY.md`. + +### A5 — Stop the demo deploy from re-supplying it + +Add the removed variable to the `deprecatedEnvs` array in +`tools/dev-server/envs-rules.json`. The local dev server and the demo (review) +deploy fetch a **live instance's** config over HTTP, and that instance still +serves the old variable; `deprecatedEnvs` drops it before validation. Without +this, the demo deploy fails on a variable that no longer exists in the schema. +See `tools/dev-server/CONTEXT.md` § "Dropped envs" for the full why. + +### A6 — (Optional) friendlier error for a replaced variable + +The `.noUnknown` + congruity checks already fail startup with a generic +message. If the variable was **replaced** and you want operators to see a +clear "use X instead" message, add a guard to `checkDeprecatedEnvs()` in +`deploy/tools/envs-validator/index.ts` that throws with that message. + +--- + +## Branch B — Grace period (two phases) + +### Phase 1 — Deprecation release (this PR) + +1. **Docs** — keep the row in `docs/ENVS.md` (it still functions). **Append** a + deprecation note to its Description, leaving the original text intact (e.g. + `… original description. Deprecated — use NEXT_PUBLIC_FOO instead.`) — Phase 2 + strips this note when it moves the row. Do **not** touch + `docs/DEPRECATED_ENVS.md` yet. If there is a replacement variable, add it now + following the `add-env-var` skill. + +2. **App code** — keep reading the variable. For a replacement, read the new one + with a fallback to the old at the call site: + `getEnvValue('NEXT_PUBLIC_FOO') || getEnvValue('NEXT_PUBLIC_OLD')`. + +3. **Validator schema** — keep the old variable accepted in `schema.ts` / + `schema_multichain.ts`. If it was **Required**, make it optional now (the new + variable carries the requirement). Keep its test-preset entries valid. + +4. **Validator messaging** (`deploy/tools/envs-validator/index.ts`): + - Always add a non-fatal warning in `printDeprecationWarning()` when the old + variable is present (the `❗❗❗ … will be removed in the next release …` + block). + - **If there is a replacement:** also add a guard in `checkDeprecatedEnvs()` + that **throws** when the old variable is set *without* the new one — forces + operators onto the new name while still accepting both. (This is the + `NEXT_PUBLIC_RE_CAPTCHA_*` pattern from #2384.) + - **If there is no replacement:** warn only; do not throw. + +### Phase 2 — Removal release (a later PR) + +Run the entire **Branch A** checklist for the old variable, **plus**: remove +the warning block you added to `printDeprecationWarning()` and the guard in +`checkDeprecatedEnvs()` in Phase 1. + +--- + +## Both branches — finishing up + +- **Version columns** stay `upcoming`; the `prepare-release` skill rewrites them + to the shipping tag. +- **Label the PR `ENVs`** so the change is picked up into the "Changes in ENV + variables" section of the release notes. +- **Run the validator suite and check the negative path:** + ```bash + pnpm --filter envs-validator test + ``` + Each preset should end with `👍 All good!`. For a grace-period guard, + temporarily set the old variable without the new one in a preset and confirm + startup fails as intended, then revert. See + `deploy/tools/envs-validator/CONTEXT.md` for details. diff --git a/.agents/skills/grill-me/SKILL.md b/.agents/skills/grill-me/SKILL.md deleted file mode 100644 index 75e8484f3cc..00000000000 --- a/.agents/skills/grill-me/SKILL.md +++ /dev/null @@ -1,8 +0,0 @@ ---- -name: grill-me -description: Interview the user relentlessly about a plan or design until reaching shared understanding, resolving each branch of the decision tree. Use when user wants to stress-test a plan, get grilled on their design, or mentions "grill me". ---- - -Interview me relentlessly about every aspect of this plan until we reach a shared understanding. Walk down each branch of the design tree, resolving dependencies between decisions one-by-one. For each question, provide your recommended answer. - -If a question can be answered by exploring the codebase, explore the codebase instead. \ No newline at end of file diff --git a/.agents/skills/prepare-release/SKILL.md b/.agents/skills/prepare-release/SKILL.md index cae2ea6a8d4..15730294c82 100644 --- a/.agents/skills/prepare-release/SKILL.md +++ b/.agents/skills/prepare-release/SKILL.md @@ -1,6 +1,6 @@ --- name: prepare-release -description: Prepare the next release of the application (tag, docs, release notes) +description: Prepare the next release of the application (tag, docs, release notes), then publish the pre-release and request a staging roll-up disable-model-invocation: true --- # Prepare Release @@ -36,7 +36,7 @@ Perform the following sub-steps **in order**. - **Reference files:** Use `./RELEASE_NOTES.md` as the template and, for format inspiration, this [example release](https://github.com/blockscout/frontend/releases/tag/v2.3.0). - **Get release notes draft:** Use the GitHub API to generate draft release notes with main as the target (e.g. `gh api repos/OWNER/REPO/releases/generate-notes -f tag_name= -f target_commitish=main -f previous_tag_name=`). Parse the response to get the list of pull request **numbers** and new contributors. Keep this list; every PR in it must appear in the final notes. - **Fetch and store PR data (labels and descriptions):** Before editing the release notes file, fetch labels and body for all PRs from the draft **once** and save them to a temporary file so later steps do not hit the GitHub API repeatedly. Use the script that rate-limits requests (authenticated API limit is 5000 requests/hour; the script adds a delay between calls). - - From the **repository root**, run: `node .cursor/skills/prepare-release/fetch-release-prs.js ... --out release-prs-data.json` with every PR number from the release notes draft. Example: `node .cursor/skills/prepare-release/fetch-release-prs.js 2725 2726 2727 --out release-prs-data.json`. + - From the **repository root**, run: `node .agents/skills/prepare-release/fetch-release-prs.js ... --out release-prs-data.json` with every PR number from the release notes draft. Example: `node .agents/skills/prepare-release/fetch-release-prs.js 2725 2726 2727 --out release-prs-data.json`. - The script writes JSON to `release-prs-data.json` in the repository root (format: `{ "prs": [ { "number", "title", "author", "url", "labels", "body" }, ... ] }`). Use this file as the **single source of truth** for labels and descriptions in the next two steps; do not call the GitHub API again when mapping sections or writing the ENV section. - **Copy and fill the notes file:** Copy `./RELEASE_NOTES.md` to a new file `./RELEASE_NOTES_.md` (e.g. `RELEASE_NOTES_v1.3.0.md`). - **Map PRs to sections:** For each section in the new file **except** "Changes in ENV variables", add the relevant pull requests. **Use only the data in `release-prs-data.json`** (labels, title, author, url) — do not call the GitHub API again. For each line use the format: `- by @ in `. Example: `- API documentation page by @tom2drum in https://github.com/blockscout/frontend/pull/2725`. Capitalize the first letter of the PR title if needed. Assign PRs to sections using each PR’s **labels** from the stored file and this mapping: @@ -48,6 +48,7 @@ Perform the following sub-steps **in order**. | Performance Improvements | performance | | Dependencies updates | dependencies | | Design updates | design | + | DX & tooling | refactoring | If a PR has no matching labels, try to categorize it based on its description (the **body** field in the `release-prs-data.json` file). If the section remains unclear, categorize it as "Other Changes." Never exclude a PR from the notes. The list of PRs in the release notes must match exactly the list from "Get release notes draft." Always double-check this. @@ -71,3 +72,66 @@ If a PR has no matching labels, try to categorize it based on its description (t - Update "Full list of the ENV variables" and "Full Changelog" with the correct version tags/links. - Update the "New Contributors" section if the "Get release notes draft" response included new contributors. - Leave the "Compatibility" section content unchanged. + +### 4. Verify with the user (stop and wait) + +The release notes draft and the doc changes are now prepared but **nothing has been committed or published**. Hand control back to the user for review: + +- Tell the user the path to the release notes draft (`./RELEASE_NOTES_.md`) so they can open and **manually edit** it. +- Show a `git diff` of the doc changes (`docs/ENVS.md` and `docs/DEPRECATED_ENVS.md`) so they can see exactly what changed and edit those files too if needed. +- Explicitly ask the user to reply once everything is in place. +- **Stop and wait.** Do not commit, tag, or push anything until the user confirms. +- After the user confirms, **re-read** `docs/ENVS.md`, `docs/DEPRECATED_ENVS.md`, and the release notes draft, since the user may have edited them. + +### 5. Commit and push the docs to `main` + +Once the user has confirmed: + +- Stage **only the changed files under `docs/`** (typically `docs/ENVS.md` and `docs/DEPRECATED_ENVS.md`). Stage them explicitly by path — **never** `git add -A`. +- **Do not commit** the release notes draft (`RELEASE_NOTES_.md`) or the temp artifact `release-prs-data.json` (the latter is gitignored anyway). +- Commit with the message `chore: prepare release ` (e.g. `chore: prepare release v1.3.0`). +- Push **directly to `main`** (branch protection is off for the release author). If there were no doc changes to stage, skip the commit/push and continue. + +### 6. Create and publish the pre-release + +Publish a pre-release for the `-alpha` variant of the tag. This single command creates and pushes the `-alpha` tag, which fires the `pre-release.yml` workflow (it triggers on a pushed tag matching `v[0-9]+.[0-9]+.[0-9]+-[a-z]+*`). + +- Build the alpha tag by appending `-alpha` to the release tag: `-alpha` (e.g. `v1.3.0-alpha`). +- Run: + ``` + gh release create -alpha \ + --target main \ + --prerelease \ + --title "-alpha" \ + --notes-file ./RELEASE_NOTES_.md + ``` + - `--prerelease` is GitHub's "Pre-release" release label (the `prerelease` boolean) — it marks the release as non-production-ready. + - `--target main` makes the tag point at the latest `main` (the commit you just pushed). + - The notes body is the **draft you prepared** (named for the final `v…` tag, even though the published tag is the `-alpha` one). +- Capture the published release URL from the command output (or `gh release view -alpha --json url -q .url`) — you'll need it for the Slack message. + +### 7. Wait for the pre-release workflow to finish (background, auto-resume) + +The pushed `-alpha` tag triggers `pre-release.yml`, which builds and publishes a Docker image and usually takes **~30 minutes**. Do **not** block the session watching it in the foreground. + +- Find the run for the tag: + ``` + gh run list --workflow=pre-release.yml --limit 5 + ``` + Identify the run for the `-alpha` tag / the commit you just pushed and capture its ``. +- Start a **background** watcher so the session stays interactive (the user can keep chatting; the harness re-invokes you when it exits): + ``` + gh run watch --exit-status --interval 60 + ``` + Run this with `run_in_background: true`. `--exit-status` returns non-zero if the run fails. +- When the watcher exits: + - **Success (exit 0):** continue to step 8. + - **Failure (non-zero):** report which job failed with a link to the run (`gh run view --web` URL), and **do not** post to Slack. Let the user decide how to proceed. +- **Fallback:** if the background watch dies early (e.g. a network drop over the long run), do a single `gh run view --json status,conclusion` to determine the real state before deciding. + +### 8. Request a staging roll-up in Slack (draft, then send on approval) + +Once the pre-release workflow has finished **successfully**, ask the DevOps team to roll up the new pre-release on staging. + +- Build the message from `./slack-message-template.md` (in this skill directory) — it holds the format, the breaking-ENV-change rule, the target channel, and the QA cc. Fill it with the `-alpha` tag, the breaking-ENV list derived per that rule (or `None.`), and the pre-release URL from step 6. The breaking-ENV input comes from the "Changes in ENV variables" section / `release-prs-data.json`. +- **Draft first:** show the user the fully rendered message and **wait for their approval** before sending it to the channel. diff --git a/.agents/skills/prepare-release/slack-message-template.md b/.agents/skills/prepare-release/slack-message-template.md new file mode 100644 index 00000000000..ff4af477ed4 --- /dev/null +++ b/.agents/skills/prepare-release/slack-message-template.md @@ -0,0 +1,67 @@ +# Slack message template — staging roll-up request + +Used in the final step of the `prepare-release` skill to ask the DevOps team to roll up +a freshly published **frontend** pre-release on the staging instances. + +- **Channel:** `#blockscout-devops-requests` (ID `C050U1F2E9M`) +- **QA cc:** the QA team user group, mention token `` +- **Always draft first** and get the user's approval before sending. + +## Placeholders + +| Placeholder | Meaning | +| ------------------------ | ----------------------------------------------------------------------- | +| `` | The pre-release tag, e.g. `v1.3.0-alpha`. | +| `` | Bulleted list of breaking ENV changes, or the single line `None.` | +| `` | Link to the published GitHub pre-release. | + +A change is **breaking** if a deployment must change its config to keep working: a +**removed** variable, a **renamed** variable, or a change to a **required**/default value +or **allowed value set**. New optional variables are *not* breaking. When in doubt, list it +and mark it `(potentially breaking)` — better to over-report than to miss one. + +## Template + +``` +📦 Frontend pre-release *``* is ready for staging. + +Could you please roll up this pre-release tag on the staging instances? + +*Breaking ENV changes:* + + +Release notes: + +cc +``` + +### Example — with breaking changes + +``` +📦 Frontend pre-release *`v1.3.0-alpha`* is ready for staging. + +Could you please roll up this pre-release tag on the staging instances? + +*Breaking ENV changes:* +• Removed `NEXT_PUBLIC_FOO` — the X feature now reads from `NEXT_PUBLIC_BAR` instead. +• Renamed `NEXT_PUBLIC_OLD` → `NEXT_PUBLIC_NEW`. +• `NEXT_PUBLIC_AD_BANNER_PROVIDER`: removed the `hype` option (potentially breaking). + +Release notes: https://github.com/blockscout/frontend/releases/tag/v1.3.0-alpha + +cc +``` + +### Example — no breaking changes + +``` +📦 Frontend pre-release *`v1.3.0-alpha`* is ready for staging. + +Could you please roll up this pre-release tag on the staging instances? + +*Breaking ENV changes:* None. + +Release notes: https://github.com/blockscout/frontend/releases/tag/v1.3.0-alpha + +cc +``` diff --git a/.agents/skills/update-glossary/SKILL.md b/.agents/skills/update-glossary/SKILL.md new file mode 100644 index 00000000000..361c6f7b77b --- /dev/null +++ b/.agents/skills/update-glossary/SKILL.md @@ -0,0 +1,201 @@ +--- +name: update-glossary +description: Add a new term, update an existing definition, or remove an outdated entry in `.agents/GLOSSARY.md`. Use whenever the user wants to write something into the project glossary, or asks "should this be in the glossary?". Enforces the file's principle — disambiguation and etymology only, no paths/env vars/UI-location restatements. +--- + +# Update the project glossary + +`.agents/GLOSSARY.md` is the **ubiquitous-language** reference for the Blockscout +frontend. It exists to disambiguate easily-confused terms and explain +non-obvious names. It is **not** a feature index, a config reference, or a +folder map — those live in code, `docs/ENVS.md`, and `.agents/rules/`. + +This skill ensures any glossary edit follows the same principles the file was +designed around. + +## When to use this skill + +- The user asks to "add", "update", or "define" a term in the glossary. +- The user asks whether a term *should* be in the glossary. +- During other work, you notice a term that meets the inclusion criteria + below and the user agrees to add it. + +## Inclusion criteria — does the term belong? + +A term earns a row **only if at least one is true**: + +1. It has a non-obvious meaning a reader will likely misread (acronyms, + codenames, chain-specific words like `Epoch`, `Kettle`, `Operation`). +2. It is easily confused with another term in this codebase (e.g. + `Block Reward` vs `Rewards`, `Connect Wallet` vs `Web3 Wallet`, + `CCTX` vs `Interchain Indexer`). +3. It has etymology worth recording (acronym expansion, original product + name, why it was renamed). +4. It establishes scope a reader can't recover by grepping (e.g. `Account` + covers watchlist + private tags + custom ABI + API keys + …). + +**Reject** if the term is self-explanatory from its name alone (`CSV Export`, +`API Docs`, `Verified Tokens`, `Multichain Button`). Folder existence is +not a reason to add a row. + +## What a row must NOT contain + +Do not write any of these — they drift, they bloat the file, and they're all +recoverable from code or other docs: + +- **Folder paths** (`src/features//`, `client/...`, etc.). Path follows + predictably from the term's kebab-case feature name. +- **Env var names** (`NEXT_PUBLIC_X_ENABLED`). These live in `docs/ENVS.md`. +- **UI-location restatements** ("Displayed in the rollup navigation", + "Exposed as a section under Tokens", "Surfaces as a button in the top bar"). + Trivially recoverable from the rendered app or a grep. +- **Implementation details** ("Has two provider backends", "Notifies via a + hook", "Has its own context provider"). +- **Status qualifiers like `(config-gated)` or `(chain-specific)`.** The Kind + column already implies this (every `feature` row is config-gated). +- **First-class-entity restatements** ("Has its own detail page"). The Kind + column says it. + +## Table structure + +The file is a single alphabetical table with three columns: + +| Column | Content | +|---|---| +| **Term** | Bold term name. Codename in parens when it differs from the product name: `**TAC (Ton Application Chain)**`, `**User Op (User Operation)**`. | +| **Kind** | One of `entity` / `feature` / `service` / `chain` / `concept`. See the Kinds list at the top of the file. | +| **Definition** | One or two short sentences. Disambiguation, etymology, scope. May end with a cross-reference. | + +### Picking the Kind + +- **entity** — a blockchain object that surfaces as a first-class UI element + (detail page, list row, API resource). E.g. `Blob`, `Block Reward`, `Kettle`. +- **feature** — a config-gated product area. Most rows are this. E.g. + `Marketplace`, `Connect Wallet`, `Advanced Filter`. +- **service** — an external system (Blockscout-operated or third-party) that + backs a feature. E.g. `BENS`, `Clusters`, `SolidityScan`, `MetaSuites`. +- **chain** — a specific chain or chain concept. E.g. `Beacon Chain`, + `SUAVE`, `TAC`. +- **concept** — an architectural / structural term. E.g. `Rollup`, + `Chain Variant`. + +If the term could fit two kinds, pick the one that best matches how a reader +will first encounter it. + +### Cross-reference phrasings (use these verbatim) + +- `Distinct from **X**.` — strong disambiguation between commonly-confused + terms. +- `Contrast with **X**.` — paired-opposite concepts (`Rollup` ↔ `Chain Variant`). +- `Co-located with **X**.` — terms that share a UI surface (`BENS` ↔ `Clusters`). +- `A sub-feature of **X**.` — hierarchical relation. +- `See also: **X**, **Y**.` — related but not strictly confusable. + +The referenced term must already be in the table (or be added in the same +edit). + +## Workflow + +### 1. Confirm the term and its meaning + +Search the codebase to confirm what the term actually refers to. Read the +relevant folder's `config.ts`, the page components, or the types file. Do +not guess from the name alone — codename / product-name mismatches happen +(e.g. `connectWallet` config key for the "Connect Wallet" feature, formerly +`blockchain-interaction`). + +Reuse the existing exploration tools: + +- `ls src/features/` to confirm folder names. +- `grep -r ""` to find usages. +- Read the feature's `config.ts` for the canonical title and any + rename history. + +### 2. Check if it's already in the glossary + +`grep -i "" .agents/GLOSSARY.md`. If a row already exists under a +different label (e.g. the user says "Merits" but the row is `Rewards`), +update that row rather than creating a duplicate. + +### 3. Identify cross-references + +Before drafting, scan the glossary for terms that: + +- The new term could be confused with. +- The new term depends on or relates to. +- Currently reference *this* term and may need their wording updated. + +Plan the cross-references in both directions — if you add `X` with +"Distinct from **Y**", check that **Y**'s row mentions **X** when it should. + +### 4. Draft the row + +Write a one- or two-sentence definition that: + +- States what the term *is* (not where it lives or how it's wired). +- Includes any etymology or codename in parens after the bold term. +- Ends with a cross-reference clause when there's a confusable neighbor. + +Apply every "MUST NOT contain" rule from above. If a sentence ends up just +describing where something appears in the UI or how the code is structured, +delete it. + +### 5. Place it alphabetically + +The table is sorted by the term as it appears in bold (the codename in +parens does not count for ordering — `TAC (Ton Application Chain)` sorts +under T). Insert in the right place; do not append at the bottom. + +### 6. Update cross-referencing rows + +If you added a "Distinct from **Y**" clause, ensure **Y**'s row reciprocates +when warranted. Add or refine the cross-ref there. + +### 7. Verify + +- `grep -nE "src/|NEXT_PUBLIC_|\.ts\b" .agents/GLOSSARY.md` — should be empty + apart from the intro paragraph and intentional package names like + `@blockscout/points-types`. +- Visual scan: the new row's sentence shape matches its neighbors; the Kind + column has one of the five valid values; cross-refs use **bold** and the + approved phrasings. + +## Examples + +### Good + +``` +| **Operation** | entity | **TAC**-specific entity representing a bridge operation between the TON and EVM ecosystems. No equivalent on standard EVM chains. | +``` + +Why it works: explains what the term is, where the name comes from +(TAC-specific), and the scope (no equivalent elsewhere). No folder path, no +env var, no UI location. + +``` +| **Rewards** | feature | The Blockscout Merits program — a token rewards and incentives system operated by Blockscout. Entirely distinct from **Block Reward** (on-chain block-producer payouts). | +``` + +Why it works: includes the codename ("Merits"), establishes who operates it, +and disambiguates from the easily-confused `Block Reward`. + +### Bad (and how to fix) + +``` +| **Marketplace** | feature | Curated directory of dApps. Lives at `src/features/marketplace/`. Config-gated via `NEXT_PUBLIC_MARKETPLACE_ENABLED`. Surfaces a full-page UI with categories and app detail views. *(config-gated)* | +``` + +Problems: folder path, env var name, UI-shape restatement, and a +status qualifier that duplicates the Kind. Fix: + +``` +| **Marketplace** | feature | Curated directory of dApps and DeFi applications integrated with Blockscout. | +``` + +## When the user asks "should X be in the glossary?" + +Apply the inclusion criteria honestly. The answer is often "no" — most +feature folders do not need a glossary entry. If the term is self-explanatory +or there's no neighbor it can be confused with, recommend leaving it out. +A short glossary that means something beats a long one that mirrors the +folder listing. diff --git a/.env.extra b/.env.extra new file mode 100644 index 00000000000..943d499c57a --- /dev/null +++ b/.env.extra @@ -0,0 +1,9 @@ +# Committed branch/feature ENV overrides and additions. +# +# Values here are layered OVER the fetched instance config by both the dev server +# (`pnpm dev:preset `) and the demo deploy (the container entrypoint reads this file). +# Use it for ENVs a feature branch needs that do not exist on the instance yet, or to override +# an instance value for the review deploy. +# +# This file IS committed — keep it empty on main. Do NOT put secrets here (use .env.secrets, +# git-ignored). For purely-local, personal overrides use .env.local (git-ignored). diff --git a/.github/workflows/checks.yml b/.github/workflows/checks.yml index 95f33c98e60..83770114ad7 100644 --- a/.github/workflows/checks.yml +++ b/.github/workflows/checks.yml @@ -39,7 +39,7 @@ jobs: - name: Install pnpm uses: pnpm/action-setup@v4 with: - version: 10.32.1 + version: 11.5.1 cache: true - name: Setup node @@ -63,6 +63,9 @@ jobs: - name: Check spelling run: pnpm lint:cspell --no-progress + - name: Check preset lists are in sync with registry + run: pnpm presets:lint + toolkit_build_check: name: Toolkit build check needs: [ code_quality ] @@ -74,7 +77,7 @@ jobs: - name: Install pnpm uses: pnpm/action-setup@v4 with: - version: 10.32.1 + version: 11.5.1 cache: true - name: Setup node @@ -94,7 +97,7 @@ jobs: - name: Verify build output run: | - cd ./toolkit/package + cd ./src/toolkit/package if [ ! -d "dist" ]; then echo "Build failed: dist directory not found" exit 1 @@ -121,7 +124,7 @@ jobs: - name: Install pnpm uses: pnpm/action-setup@v4 with: - version: 10.32.1 + version: 11.5.1 cache: true - name: Setup node @@ -149,7 +152,7 @@ jobs: - name: Install pnpm uses: pnpm/action-setup@v4 with: - version: 10.32.1 + version: 11.5.1 cache: true - name: Setup node @@ -178,7 +181,7 @@ jobs: - name: Install pnpm uses: pnpm/action-setup@v4 with: - version: 10.32.1 + version: 11.5.1 cache: true - name: Setup node @@ -229,7 +232,7 @@ jobs: - name: Install pnpm uses: pnpm/action-setup@v4 with: - version: 10.32.1 + version: 11.5.1 cache: true - name: Setup node @@ -296,7 +299,7 @@ jobs: if: steps.check-reports.outputs.has_reports == 'true' uses: pnpm/action-setup@v4 with: - version: 10.32.1 + version: 11.5.1 cache: true - name: Setup node diff --git a/.github/workflows/copilot-setup-steps.yml b/.github/workflows/copilot-setup-steps.yml index 5813d3bb5db..8604521fb64 100644 --- a/.github/workflows/copilot-setup-steps.yml +++ b/.github/workflows/copilot-setup-steps.yml @@ -23,7 +23,7 @@ jobs: - name: Install pnpm uses: pnpm/action-setup@v4 with: - version: 10.32.1 + version: 11.5.1 - name: Setup node uses: actions/setup-node@v4 diff --git a/.github/workflows/deploy-review-2.yml b/.github/workflows/deploy-review-2.yml deleted file mode 100644 index cd8999668c9..00000000000 --- a/.github/workflows/deploy-review-2.yml +++ /dev/null @@ -1,85 +0,0 @@ -name: Deploy review environment (2) - -on: - workflow_dispatch: - inputs: - envs_preset: - description: ENVs preset - required: false - default: "" - type: choice - options: - - none - - arbitrum - - arbitrum_nova - - arbitrum_sepolia - - base - - celo - - celo_sepolia - - garnet - - gnosis - - eth - - eth_sepolia - - filecoin - - immutable - - mega_eth - - multichain_dev - - multichain_prod - - neon_devnet - - numine - - optimism - - optimism_sepolia - - polygon - - rootstock - - rootstock_testnet - - scroll_sepolia - - shibarium - - stability - - zetachain - - zetachain_testnet - - zilliqa - - zksync - - zora - -permissions: - contents: read - packages: write - pull-requests: write - -jobs: - make_slug: - name: Make GitHub reference slug - runs-on: ubuntu-latest - outputs: - REF_SLUG: ${{ steps.output.outputs.REF_SLUG }} - steps: - - name: Inject slug/short variables - uses: rlespinasse/github-slug-action@v4.4.1 - - - name: Set output - id: output - run: echo "REF_SLUG=${{ env.GITHUB_REF_NAME_SLUG }}" >> $GITHUB_OUTPUT - - publish_image: - name: Publish Docker image - needs: make_slug - uses: './.github/workflows/publish-image.yml' - with: - tags: | - type=raw,value=review-2-${{ needs.make_slug.outputs.REF_SLUG }} - build_args: ENVS_PRESET=${{ inputs.envs_preset }} - platforms: linux/amd64 - secrets: inherit - - deploy_review: - name: Deploy frontend - needs: [ make_slug, publish_image ] - uses: blockscout/actions/.github/workflows/deploy_helmfile.yaml@main - with: - appName: review-2-${{ needs.make_slug.outputs.REF_SLUG }} - globalEnv: review - helmfileDir: deploy - kubeConfigSecret: ci/data/dev/kubeconfig/k8s-dev - vaultRole: ci-dev - secrets: inherit - permissions: write-all diff --git a/.github/workflows/deploy-review.yml b/.github/workflows/deploy-review.yml index 24eb62d2666..95f3ded615c 100644 --- a/.github/workflows/deploy-review.yml +++ b/.github/workflows/deploy-review.yml @@ -3,41 +3,53 @@ name: Deploy review environment on: workflow_dispatch: inputs: + variant: + description: 'Demo variant — "review-2" disables ENVs validation (e.g. for multichain)' + required: false + default: review + type: choice + options: + - review + - review-2 + build_image: + description: 'Build & publish a new image. Disable to redeploy an existing demo with a different preset (no rebuild).' + required: false + default: true + type: boolean envs_preset: description: ENVs preset required: false - default: main + default: staging type: choice options: - none + # presets:start — generated from tools/dev-server/registry.json (run `pnpm presets:sync`) - arbitrum - - arbitrum_nova - arbitrum_sepolia - base + - blackfort_testnet - celo - celo_sepolia - - garnet - - gnosis - eth - eth_sepolia - filecoin + - garnet + - gnosis - immutable - - main - mega_eth - - mekong - - multichain_dev - - multichain_prod + - multichain - neon_devnet - numine - optimism - optimism_sepolia - polygon - - rari_testnet - rootstock - rootstock_testnet - - shibarium - scroll_sepolia - - stability + - shibarium + - stability_testnet + - staging + - staging_multichain - tac - tac_spb - zetachain @@ -45,6 +57,7 @@ on: - zilliqa - zksync - zora + # presets:end permissions: contents: read @@ -68,22 +81,27 @@ jobs: publish_image: name: Publish Docker image needs: make_slug + # Skipped when redeploying an existing demo with a different preset (the image is preset-agnostic). + if: ${{ inputs.build_image }} uses: './.github/workflows/publish-image.yml' with: tags: | - type=raw,value=review-${{ needs.make_slug.outputs.REF_SLUG }} - build_args: ENVS_PRESET=${{ inputs.envs_preset }} + type=raw,value=${{ inputs.variant }}-${{ needs.make_slug.outputs.REF_SLUG }} platforms: linux/amd64 secrets: inherit deploy_review: name: Deploy frontend needs: [ make_slug, publish_image ] + # Run after a successful build, or directly when the build was skipped (redeploy-only). + if: ${{ always() && needs.make_slug.result == 'success' && (needs.publish_image.result == 'success' || needs.publish_image.result == 'skipped') }} uses: blockscout/actions/.github/workflows/deploy_helmfile.yaml@main with: - appName: review-${{ needs.make_slug.outputs.REF_SLUG }} + appName: ${{ inputs.variant }}-${{ needs.make_slug.outputs.REF_SLUG }} globalEnv: review helmfileDir: deploy + # Inject the chosen preset as a runtime env (ENVS_PRESET) instead of baking it into the image. + helmfileParameters: --suppress-diff --state-values-set envsPreset=${{ inputs.envs_preset }} kubeConfigSecret: ci/data/dev/kubeconfig/k8s-dev vaultRole: ci-dev secrets: inherit diff --git a/.github/workflows/toolkit-npm-publisher.yml b/.github/workflows/toolkit-npm-publisher.yml index a0f28fbc197..ff17bcf9952 100644 --- a/.github/workflows/toolkit-npm-publisher.yml +++ b/.github/workflows/toolkit-npm-publisher.yml @@ -29,7 +29,7 @@ jobs: - name: Install pnpm uses: pnpm/action-setup@v4 with: - version: 10.32.1 + version: 11.5.1 # Also it will setup .npmrc file to publish to npm - name: Setup node @@ -45,7 +45,7 @@ jobs: - name: Update package version run: | - cd ./toolkit/package + cd ./src/toolkit/package npm version ${{ inputs.version }} - name: Install dependencies and build the package @@ -55,7 +55,7 @@ jobs: - name: Publish to NPM registry run: | - cd ./toolkit/package + cd ./src/toolkit/package # Determine npm tag based on version if [[ "${{ inputs.version }}" =~ ^v?[0-9]+\.[0-9]+\.[0-9]+$ ]]; then # Stable release (e.g., 1.7.2 or v1.7.2) diff --git a/.gitignore b/.gitignore index 0d4dbc32c05..9159454cc96 100644 --- a/.gitignore +++ b/.gitignore @@ -44,9 +44,14 @@ npm-debug.log* .pnpm-debug.log* # local env files +# NOTE: .env.extra is intentionally NOT ignored — it is committed so branch/feature ENVs +# reach the demo deploy. Purely-local overrides go in .env.local (matched by .env*.local below). .env*.local -/configs/envs/.env.secrets -/configs/envs/.samples +/.env.secrets +.env.tmp + +# compiled dev-server fetch script +/tools/dev-server/fetch.js # typescript *.tsbuildinfo @@ -65,7 +70,6 @@ npm-debug.log* **.dec** # build outputs -/tools/preset-sync/index.js /toolkit/package/dist license.json release-prs-data.json diff --git a/.npmrc b/.npmrc deleted file mode 100644 index cc82dc35afe..00000000000 --- a/.npmrc +++ /dev/null @@ -1,2 +0,0 @@ -# Enables `pnpm deploy` for workspace packages (pnpm 10+). See https://pnpm.io/cli/deploy -inject-workspace-packages=true diff --git a/.vscode/settings.json b/.vscode/settings.json index ee3f389ee42..297df683b42 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,11 +1,11 @@ { "typescript.tsdk": "node_modules/typescript/lib", "javascript.preferences.autoImportFileExcludePatterns": [ - "./toolkit/package/**", - "./toolkit/components/**/index.ts", + "./src/toolkit/package/**", + "./src/toolkit/components/**/index.ts", ], "typescript.preferences.autoImportFileExcludePatterns": [ - "./toolkit/package/**", - "./toolkit/components/**/index.ts", + "./src/toolkit/package/**", + "./src/toolkit/components/**/index.ts", ] } \ No newline at end of file diff --git a/.vscode/tasks.json b/.vscode/tasks.json index fbf61187f48..edd58f60595 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -25,27 +25,6 @@ "instanceLimit": 1 } }, - { - "type": "shell", - "command": "pnpm dev:preset:sync --name=${input:dev_config_preset}", - "problemMatcher": [], - "label": "dev preset sync", - "detail": "synchronize dev preset", - "presentation": { - "reveal": "always", - "panel": "shared", - "focus": true, - "close": false, - "revealProblems": "onProblem", - }, - "icon": { - "color": "terminal.ansiMagenta", - "id": "repo-sync" - }, - "runOptions": { - "instanceLimit": 1 - } - }, // CODE CHECKS { @@ -336,37 +315,33 @@ "id": "dev_config_preset", "description": "Choose dev server config preset:", "options": [ - "all", - "main", - "localhost", + // presets:start — generated from tools/dev-server/registry.json (run `pnpm presets:sync`) "arbitrum", "arbitrum_sepolia", - "arbitrum_nova", "base", "blackfort_testnet", "celo", "celo_sepolia", - "garnet", - "gnosis", - "immutable", "eth", "eth_sepolia", "filecoin", + "garnet", + "gnosis", + "immutable", "mega_eth", - "mekong", - "multichain_dev", - "multichain_prod", + "multichain", "neon_devnet", "numine", "optimism", "optimism_sepolia", "polygon", - "rari_testnet", "rootstock", "rootstock_testnet", "scroll_sepolia", "shibarium", "stability_testnet", + "staging", + "staging_multichain", "tac", "tac_spb", "zetachain", @@ -374,8 +349,9 @@ "zilliqa", "zksync", "zora", + // presets:end ], - "default": "main" + "default": "staging" }, ], } \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index c5ab384436c..26b2780e07a 100644 --- a/Dockerfile +++ b/Dockerfile @@ -5,7 +5,7 @@ FROM node:22.14.0-alpine AS deps # Check https://github.com/nodejs/docker-node/tree/b4117f9333da4138b03a546ec926ef50a31506c3#nodealpine to understand why libc6-compat might be needed. RUN apk add --no-cache libc6-compat python3 make g++ RUN ln -sf /usr/bin/python3 /usr/bin/python -RUN corepack enable && corepack prepare pnpm@10.32.1 --activate +RUN corepack enable && corepack prepare pnpm@11.5.1 --activate ### Install all workspace dependencies in one place WORKDIR /app @@ -19,7 +19,7 @@ RUN pnpm install --frozen-lockfile # ***************************** FROM node:22.14.0-alpine AS builder RUN apk add --no-cache --upgrade libc6-compat bash jq -RUN corepack enable && corepack prepare pnpm@10.32.1 --activate +RUN corepack enable && corepack prepare pnpm@11.5.1 --activate # pass build args to env variables ARG GIT_COMMIT_SHA @@ -78,6 +78,10 @@ RUN cd ./deploy/tools/essential-dapps-chains-config-generator && pnpm run build RUN cd ./deploy/tools/llms-txt-generator && pnpm run build +### DEV-SERVER ENV FETCHER +RUN pnpm exec tsc -p ./tools/dev-server/tsconfig.json + + # ***************************** # ******* STAGE 3: Run ******** # ***************************** @@ -119,6 +123,8 @@ COPY --chmod=755 ./deploy/scripts/make_envs_script.sh . COPY --chmod=755 ./deploy/scripts/download_assets.sh . ## OG image generator COPY ./deploy/scripts/og_image_generator.js . +## Pro API support flag +COPY --chmod=755 ./deploy/scripts/export_pro_api_flag.sh . ## Favicon generator COPY --chmod=755 ./deploy/scripts/favicon_generator.sh . COPY --from=builder /app/favicon-generator-bundle ./deploy/tools/favicon-generator @@ -132,10 +138,13 @@ COPY --from=builder /app/sitemap-generator-bundle ./deploy/tools/sitemap-generat COPY --from=builder /app/.env.registry . COPY --from=builder /app/.env . -# Copy ENVs presets -ARG ENVS_PRESET -ENV ENVS_PRESET=$ENVS_PRESET -COPY ./configs/envs ./configs/envs +# Dev-server env fetcher (compiled script + registry/rules data + committed overrides). +# The image is preset-agnostic: ENVS_PRESET is supplied at runtime (k8s env), and at startup +# the entrypoint runs fetch.js to pull that instance's public config over HTTP. +COPY --from=builder /app/tools/dev-server/fetch.js ./tools/dev-server/fetch.js +COPY ./tools/dev-server/registry.json ./tools/dev-server/registry.json +COPY ./tools/dev-server/envs-rules.json ./tools/dev-server/envs-rules.json +COPY ./.env.extra ./.env.extra # Automatically leverage output traces to reduce image size # https://nextjs.org/docs/advanced-features/output-file-tracing diff --git a/client/ARCH_REDESIGN.md b/client/ARCH_REDESIGN.md deleted file mode 100644 index 1e1d63e2b6e..00000000000 --- a/client/ARCH_REDESIGN.md +++ /dev/null @@ -1,414 +0,0 @@ -# Client architecture blueprint - ---- - -## 1. Purpose - -- **Co-locate** everything that belongs to a **slice** (core explorer domain) or **feature** (optional / config-driven) in one place: UI, hooks, utils, **API response types** for that area, mocks, stubs, etc. -- **Retire** top-level `lib/`, `mocks/`, `stubs/`, `types/` (and `ui/`) as *unscoped* buckets — their contents move under **`/client`** or other agreed homes (see §6). -- Keep **clear boundaries** so the graph stays maintainable (see §8). - ---- - -## 2. Naming conventions (agreed) - -Aligned with **Next.js-style** paths: **kebab-case for directories** and for **non-component TS modules** (helpers, parsers, constants files). - -| Artifact | Convention | Examples | -|----------|------------|----------| -| **Directories** | `kebab-case` | `slices/tx/`, `features/user-ops/`, `pages/tx-details/` | -| **React components** | `PascalCase.tsx` | `TxDetails.tsx`, `TxIndexTable.tsx` | -| **Hooks** | `use` + `camelCase` in the filename (ecosystem norm) | `useTxQuery.ts`, `useIsMobile.ts` | -| **Helper / util modules** (non-hook) | `kebab-case.ts` | `format-tx-hash.ts`, `get-tab-list.ts` | -| **Role files** | lowercase | `types.ts`, `mocks.ts`, `stubs.ts` | - -Same folder may contain `TxDetails.tsx`, `useTxQuery.ts` — hooks stay **`useCamelCase.ts`**; everything else non-component prefers **kebab-case**. - -**No re-export-only barrel files inside `/client`.** Deep imports are preferred; dumb barrels (files that only `export * from ...`) slow TypeScript server performance and break tree-shaking at scale. Aggregation files that define or curate their own public surface (e.g. `client/api/services/general/index.ts` acting as a facade for the General API) are acceptable where they provide genuine value. - ---- - -## 3. Top-level repo layout (conceptual) - -| Path | Role | -|------|------| -| **`/client`** | Product explorer: shell, API client layer, slices, features, shared. Replaces **`/ui`** and absorbs most of **`lib`**, **`mocks`**, **`stubs`**, **`types`** (see §6). | -| **`/configs`** | Env-derived config and feature flags. | -| **`/nextjs`** | Next integration, SSR helpers, server utilities. | -| **`/pages`** | Next.js **file-based routes** only (thin wrappers — a dynamic import and `getServerSideProps` call; nothing else). **404 vs "empty page"** is defined here — invalid routes do not render the page component. | -| **`/toolkit`** | Design system and shared code **published to npm** / reused across company projects — **stays outside** `client`. Referenced as a local path alias during this migration; publishing scope is out of scope for this task. | -| **Root `lib/`** | **Removed** after migration except pieces explicitly moved elsewhere (e.g. monitoring → `nextjs`). | - ---- - -## 4. Inside `/client` - -### 4.1 `client/shell` - -Application **chrome**: layout, error UI, header, footer, page title, top-bar, and other frame-level concerns. - -**`ui/snippets/` split (complete):** - -| Source | Destination | -|--------|-------------| -| `header/`, `footer/`, `navigation/`, `topBar/` | `client/shell/` | -| `searchBar/` | `client/slices/search/` | -| `networkLogo/` | `client/shared/` | -| `networkMenu/` | `client/features/multichain/` | -| `auth/`, `user/` | `client/features/account/` | - -**Context split from `lib/contexts/`:** - -| Source | Destination | -|--------|-------------| -| `app.tsx`, `fallback.tsx` | `client/shell/` | -| `settings.tsx` | `client/shell/top-bar/` (alongside `TopBar` component) | -| `multichain.tsx` | `client/features/multichain/` | -| `rewards.tsx` | `client/features/rewards/` | -| `marketplace.tsx` | `client/features/marketplace/` | -| `addressHighlight.tsx` | `client/slices/address/` | - -**Hooks from `lib/hooks/`:** - -- `useNavItems` → `client/shell/` -- Everything else generic → `client/shared/hooks/` (see §4.5) - -### 4.2 `client/api` - -- **Full migration** of today's **`lib/api`** (transport: fetch, resources, URL building, query client wiring, etc.). -- **`client/api` must not import runtime logic** from `client/slices/*` or `client/features/*` (avoids cycles). **`import type`** from slices is permitted when an API service file maps a resource to its response type — the type lives with the slice that owns it, and `client/api` references it for the payload mapping only. -- **Per-resource response types** and view-specific types are **not** centralized under `client/api`; they live in the **slice or feature** that owns the domain (co-location with hooks/components). -- **WebSocket transport** (`lib/socket/`) → `client/api/socket/` — same reasoning as fetch: it is a transport mechanism, not a UI concern. - -**`client/api/services/` structure:** - -Keep the current organization largely intact. Do not flatten everything into a 1:1 mapping with slices — feature services often have only one or two resources and do not warrant separate files. - -`general/` maps to the **Blockscout General API namespace** — it is not restricted to vanilla-EVM resources. Files inside it follow the API grouping, not the slice/feature taxonomy. - -```text -client/api/ - hooks/ ← React hooks for data fetching (useApiFetch, useApiQuery, useFetch, …) - services/ - general/ ← Blockscout General API namespace; only split files inside where genuinely needed - tx.ts - block.ts - misc.ts ← resources with no clear single-domain home stay here - rollup.ts ← rollup resources are part of the General API - v1.ts ← previous API version; one active resource - ... - rewards.ts ← feature-specific APIs stay flat alongside general/ - stats.ts - user-ops.ts - bens.ts - ... - types.ts ← shared API types incl. IsPaginated (absorbs services/utils.ts) - socket/ ← migrated from lib/socket/ -``` - -> **Decision note — `hooks/` subfolder:** React hooks that wire transport/query-client logic (`useApiFetch`, `useApiQuery`, `useApiInfiniteQuery`, `useApiQueries`, `useQueryClientConfig`, `useFetch`) live under `client/api/hooks/` rather than at the `client/api/` root. This keeps the root flat (utilities + config) and the hooks discoverable as a group, without introducing an extra barrel file. - -### 4.3 `client/slices` - -**Core explorer entities** — always part of the product surface (not optional "features" in the `configs/app/features` sense). - -**Classification criterion:** *"Can this area exist on a vanilla EVM chain with no feature flag?"* Yes → slice. No (requires a `configs/app/features` flag) → feature. - -**Confirmed slices (non-exhaustive):** `tx`, `block`, `search`, `token`, `address`, `contract`, `internal-tx`, `home`, `token`, `token-transfer`, `log`, `gas` … - -- **`contract` (slice):** **Contract verification** and **SolidityScan** (`lib/solidityScan/`) are **slice** concerns (available on any chain). -- **`internal-tx` (slice):** Own slice; **`tx`** composes **internal-tx** components (e.g. tab content). Prefer **no imports** from `internal-tx` back into `tx` to avoid cycles; share via **`client/shared`** or a **thin types-only surface** if needed. -- **`gas` (slice):** Owns the gas-price domain — `GasPrices`/`GasPriceInfo` API types, `GasUnit` client type, display primitives (`GasPrice.tsx`, `GasInfoTooltip.tsx`, `GasInfoUpdateTimer.tsx`), and formatting utils (`formatGasValue.ts`). These are used unconditionally across `slices/home/`, `slices/tx/`, and `client/shell/`. The config-gated tracker **page** belongs to `features/gas-tracker/`, which imports from this slice. - -**Rollup sub-types on slice types:** `slices/tx/types/api.ts` imports feature-specific sub-types (e.g. `ArbitrumTransactionData`) from their respective feature via `import type`. Optional fields fan out at the slice type level — this mirrors the real API contract and is intentional (see §11). - -**Typical slice shape:** - -```text -client/slices/tx/ - pages/ - index/ # kebab-case folder = one "screen" / route target - TxIndex.tsx - TxIndexTable.tsx - ... - details/ - TxDetails.tsx - ... - components/ - hooks/ # e.g. useTxQuery.ts - utils/ # kebab-case .ts files - types/ - api.ts # response / DTO types owned by this slice - client.ts # frontend-only types (client types or types derived from API responses) - mocks.ts - stubs.ts -``` - -**Routing rule:** **One Next route ≈ one folder under** `pages/` inside the slice (or feature). The **file** under root `pages/` stays a thin dynamic import into `client/...`. - -### 4.4 `client/features` - -**Optional** product areas — **roughly mirrors `configs/app/features`**, but: - -- Folders may exist for **readability and maintenance** (e.g. per rollup type) even when not everything is enabled for a given chain; **runtime behavior** still comes from **config/env**, not "folder exists = on." - -**Confirmed features (non-exhaustive):** `user-ops`, `data-availability`, `multichain`, `name-domains`, `account`, `stats`, `gas-tracker`, `get-gas-button`, `validators`, `marketplace`, `rewards`, `rollup/*`, `chain-variants/*`, `advanced-filter`, `ad-banner`, `safe-address-tags`, `metasuites`, `csv-export`, … - -- **`stats` and `gas-tracker`** are **features** (not slices) — they are config-gated per chain. Gas-price primitives (shared display components, formatting utils, API types) live in **`slices/gas/`**; `features/gas-tracker/` owns the tracker page and imports from that slice. -- **`get-gas-button`** is its own feature — independently config-gated (`NEXT_PUBLIC_GAS_REFUEL_PROVIDER_CONFIG`), surfaces in the top bar, and has no hard dependency on `gas-tracker` being enabled. -- **`validators`** is a **feature** — chain-specific, not present on a vanilla EVM chain. -- **`epochs`** is a sub-concern of the Celo chain variant (`features/chain-variants/celo/`), not a standalone feature. -- **`data-availability`** was previously referred to as `blobs` — use `data-availability` throughout. - -**Classification rules:** -1. *"Can this area exist on a vanilla EVM chain with no feature flag?"* Yes → slice. No → feature. -2. **Config-gated infrastructure with no user-facing UI** (analytics providers, error monitoring, A/B flag providers) lives in **`client/shared/`**, not `features/` — the presence of a config flag alone is not sufficient to qualify for a feature folder. -3. Every config-gated feature with user-facing UI gets its own folder under `features/`, regardless of size — consistency and discoverability outweigh folder count. - -**Chain-specific non-rollup features** — chain variants that are not rollups (ZetaChain, TAC, SUAVE, Celo, MegaETH, Beacon chain, etc.) are grouped under **`features/chain-variants//`**, parallel to `features/rollup//`. Example: `features/chain-variants/celo/`, `features/chain-variants/tac/`, `features/chain-variants/zeta-chain/`. - -**Variant / chain-specific transaction lists** (e.g. kettle, zeta-specific list UIs) live under **`features//pages/...`**, **not** as variants inside `slices/tx`. - -**Rollups:** `features/rollup//` (e.g. `optimism`, `arbitrum`, …) — **organized by rollup type**, because **views and helpers differ by rollup**. Sub-areas (deposits, batches, withdrawals) are **subfolders** under that rollup, not separate top-level rollup taxonomies. - -**Chain variants (non-rollup):** `features/chain-variants//` (e.g. `celo`, `tac`, `zeta-chain`, `mega-eth`, `suave`, `beacon-chain`) — same sub-folder structure as rollups. - -**Typical feature shape:** same idea as slices — `pages/`, `components/`, `hooks/`, `utils/`, `types/`, `mocks.ts`, `stubs.ts`. - -### 4.5 `client/shared` - -**Cross-cutting UI and helpers** with **no single domain owner**. - -- **No files directly in `client/shared/`** — only **subfolders** grouped by purpose. -- **No `index.ts` barrel files** (same rule as the rest of `/client`). - -**Confirmed subfolders (round 2 decisions):** - -| Subfolder | Contents / origin | -|-----------|-------------------| -| `analytics/` | `lib/mixpanel/` | -| `auth/` | `lib/decodeJWT.ts` | -| `chain/` | `lib/networks/` (all `network` → `chain` renamed) + `lib/units.ts` | -| `date-and-time/` | `lib/hooks/useTimeAgoIncrement` and similar date utils | -| `errors/` | `lib/errors/` | -| `feature-flags/` | `lib/growthbook/` (runtime A/B flags — distinct from build-time `configs/`) | -| `hooks/` | Generic hooks from `lib/hooks/` with no single domain owner | -| `i18n/` | `lib/setLocale.ts` | -| `links/utils/` | `lib/utils/stripUtmParams.ts` and URL-related helpers | -| `lists/` | `lib/hooks/useLazyRenderedList`, `lib/hooks/useInitialList`, `lib/getItemIndex.ts` | -| `metadata/` | `lib/metadata/` (centralized — see note below) | -| `monitoring/rollbar/` | `lib/rollbar/` (client-side observability infrastructure) | -| `router/` | `lib/router/` + query-param filter helpers (`getFilterValueFromQuery`, etc.) | -| `storage/` | `lib/cookies.ts` | -| `text/` | `lib/capitalizeFirstLetter.ts`, `shortenString.ts`, `escapeRegExp.ts`, `highlightText.ts` | -| `transformers/` | Hex / bytes / base64 conversion utils (`hexToBytes`, `base64ToHex`, etc.) | -| `utils/` | Tiny misc utilities with no better home (`delay.ts`, `isMetaKey.tsx`) | -| `web3/` | `lib/web3/` | -| *(existing)* | `pagination/`, `charts/`, `entities/`, etc. — keep as-is | - -**`client/shared/metadata/` note:** The title/description template map is a routing manifest (`Record`) with TypeScript exhaustiveness enforced across all routes. Splitting it into slices/features loses that guarantee. It stays **centralized** in `client/shared/metadata/`. The `ApiData` type within it uses `import type` from the relevant slices (e.g. `TokenInfo` from `slices/token/types/api.ts`) — a shared → slice `import type` is acceptable here (no cycle, no runtime import). - ---- - -## 5. Config vs client - -- **`/configs` may import from `/client`**, but only from **import-free `config.ts` files** (see below). All other `/client` files are off-limits to `configs/`. -- **`/client`** imports **`/configs`** for feature flags and app configuration. -- **Runtime feature flags** (Growthbook A/B) live in **`client/shared/feature-flags/`** — they require a React context and cannot live in `configs/`. - -**`config.ts` role file convention:** When a slice or feature owns constants and types that a `configs/` module needs at runtime (e.g. valid widget IDs for env-var parsing), those are extracted into a **`types/config.ts`** file inside the slice/feature. This file must have **zero imports** — only `const` arrays, derived `type` aliases, and pure interfaces. No React, no utilities, no other modules. This keeps the file as a leaf node in the module graph, avoiding initialization-order issues in the Vite-built envs-validator and SSR contexts. - -Example: `client/slices/home/types/config.ts` → imported by `configs/app/ui/homepage.ts`. - ---- - -## 6. Migration map (sources → destinations) - -### `ui/` → `client/` - -| Current | Destination | -|---------|-------------| -| `ui/**` | `client/**` (restructured into shell / slices / features / shared) | -| `ui/snippets/header/`, `footer/`, `navigation/`, `topBar/` | `client/shell/` | -| `ui/snippets/searchBar/` | `client/slices/search/` | -| `ui/snippets/networkLogo/` | `client/shared/` | -| `ui/snippets/networkMenu/` | `client/features/multichain/` | -| `ui/snippets/auth/`, `user/` | `client/features/account/` | -| `ui/shared/gas/` | `client/slices/gas/` (display components + formatting utils) | -| `ui/gasTracker/`, `ui/pages/GasTracker.tsx` | `client/features/gas-tracker/` | -| `ui/snippets/topBar/GetGasButton.tsx` | `client/features/get-gas-button/` | -| `types/client/gasTracker.ts` (`GasUnit`) | `client/slices/gas/types/client.ts` | -| `types/client/gasRefuelProviderConfig.ts` | `client/features/get-gas-button/types/` | - -### `lib/` → destinations - -| Current | Destination | -|---------|-------------| -| `lib/api/**` | `client/api/**` (services structure preserved; `services/utils.ts` → `client/api/types.ts`) | -| `lib/socket/` | `client/api/socket/` | -| `lib/monitoring/` | `nextjs/` | -| `lib/rollbar/` | `client/shared/monitoring/rollbar/` | -| `lib/metadata/` | `client/shared/metadata/` | -| `lib/router/` | `client/shared/router/` | -| `lib/web3/` | `client/shared/web3/` | -| `lib/errors/` | `client/shared/errors/` | -| `lib/mixpanel/` | `client/shared/analytics/mixpanel` | -| `lib/growthbook/` | `client/shared/feature-flags/` | -| `lib/networks/` + `lib/units.ts` | `client/shared/chain/` (rename `network` → `chain` throughout) | -| `lib/hooks/useNavItems` | `client/shell/` | -| `lib/hooks/useTimeAgoIncrement` | `client/shared/date-and-time/` | -| `lib/hooks/useLazyRenderedList` | `client/shared/lists/` | -| `lib/hooks/useInitialList` | `client/shared/lists/` | -| `lib/hooks/useRewardsActivity` | `client/features/rewards/` | -| `lib/hooks/useAddressProfileApiQuery` | `client/features/address-profile-api/hooks/useAddressProfileApiQuery.tsx` | -| `lib/hooks/useFetch` | `client/api/hooks/` | -| `lib/hooks/useGetCsrfToken` | `client/features/account/` | -| `lib/hooks/useGraphLinks` | `client/features/marketplace/` | -| `lib/hooks/useAdblockDetect` | `client/features/ad-banner/` | -| `lib/hooks/useIsSafeAddress` | `client/features/safe-address-tags/` | -| `lib/hooks/useNotifyOnNavigation` | `client/features/metasuites/` | -| `lib/hooks/` (remaining) | `client/shared/hooks/` | -| `lib/contexts/app.tsx`, `fallback.tsx` | `client/shell/` | -| `lib/contexts/settings.tsx` | `client/shell/top-bar/` | -| `lib/contexts/multichain.tsx` | `client/features/multichain/` | -| `lib/contexts/rewards.tsx` | `client/features/rewards/` | -| `lib/contexts/marketplace.tsx` | `client/features/marketplace/` | -| `lib/contexts/addressHighlight.tsx` | `client/slices/address/contexts/address-highlight.tsx` | -| `lib/tx/` | `client/slices/tx/utils/` | -| `lib/token/` | `client/slices/token/` | -| `lib/address/parseMetaPayload.ts` | `client/features/address-metadata/utils/parseMetaPayload.ts` | -| `lib/address/useAddressMetadataInfoQuery.ts` | `client/features/address-metadata/hooks/useAddressMetadataInfoQuery.ts` | -| `lib/address/useAddressMetadataInitUpdate.ts` | `client/features/address-metadata/hooks/useAddressMetadataInitUpdate.ts` | -| `lib/address/` (remaining) | `client/slices/address/` | -| `lib/rollups/` | `client/features/rollup/` | -| `lib/multichain/` | `client/features/multichain/` | -| `lib/search/` | `client/slices/search/` | -| `lib/contracts/` | `client/slices/contract/` | -| `lib/solidityScan/` | `client/slices/contract/` | -| `lib/stats/` | `client/features/stats/` | -| `lib/utils/stripUtmParams.ts` | `client/shared/links/utils/` | -| `lib/capitalizeFirstLetter.ts`, `shortenString.ts`, `escapeRegExp.ts`, `highlightText.ts` | `client/shared/text/` | -| `lib/base64ToHex.ts`, `bytesToBase64.ts`, `bytesToHex.ts`, `hexToBase64.ts`, `hexToBytes.ts`, `hexToAddress.ts`, `hexToDecimal.ts`, `hexToUtf8.ts` | `client/shared/transformers/` | -| `lib/cookies.ts` | `client/shared/storage/` | -| `lib/decodeJWT.ts` | `client/shared/auth/` | -| `lib/setLocale.ts` | `client/shared/i18n/` | -| `lib/delay.ts`, `lib/isMetaKey.tsx` | `client/shared/utils/` | -| `lib/recentSearchKeywords.ts` | `client/slices/search/` | -| `lib/getFilterValueFromQuery.ts`, `getFilterValuesFromQuery.ts`, `getValuesArrayFromQuery.ts` | `client/shared/router/` | -| `lib/getItemIndex.ts` | `client/shared/lists/` | -| `lib/getErrorMessage.ts` | `client/shared/errors/` | - -### Other sources - -| Current | Destination | -|---------|-------------| -| `mocks/**`, `stubs/**` | Co-located **`mocks.ts` / `stubs.ts`** under the relevant **slice/feature**; shared test data → `client/shared/...` if truly cross-domain | -| `types/api/*`, `types/client/*`, etc. | **Slice/feature `types/`** (and config-owned types → `configs`) — **not** a single global `types/` tree at repo root after migration | - -**Migration process:** **incremental PRs/tasks** — update all imports in the **same PR** as the move. **No long-lived re-export shims.** Import path updates for test files can be scripted with a codemod. - ---- - -## 7. Testing - -- **Playwright / visual tests:** stay **next to** the implementation (e.g. `*.pw.tsx` alongside components), same as today. - ---- - -## 8. Dependency rules (high level) - -- **ESLint rules (`import/no-cycle` + explicit `boundaries` rules for `client/api`, `configs`, `toolkit`) must be enabled _before_ the migration starts.** Running them against the existing code surfaces real problem areas cheaply, before they become migration blockers. -- **Enforcement strategy: warn on legacy, error on new.** Configure boundary rules to emit **errors** only within `client/`; legacy `lib/` and `ui/` paths receive **warnings** only. This avoids a costly upfront clean-up of the entire codebase while ensuring every migrated file is immediately held to the new standard. -- **Guidelines:** - - **`client/api`** allows **`import type`** from slices/features for response-shape typing; **no runtime imports** (no logic, hooks, or components — types only). - - **Slice → feature type imports** are intentional: the backend includes feature-specific fields (e.g. `arbitrum?`, `scroll?`) in API responses unconditionally when the rollup is enabled — the frontend type must mirror the full API contract. UI component imports follow the same direction (e.g. `TxDetails` imports `TxDetailsArbitrum`). - - **`client/shared` → slice `import type`** is acceptable where shared infrastructure genuinely needs a slice-owned type (e.g. `client/shared/metadata/` referencing `TokenInfo` from `slices/token/types/api.ts`). No runtime imports from shared → slice. - - **Avoid circular graphs** between slices/features; use **composition at a shallow level** (page or a single container component) when optional UI plugs into a core screen. - - **`internal-tx` → `tx`:** avoid **`tx` → `internal-tx` → `tx`** cycles; lift shared bits to **`shared`** or **types-only** exports. - ---- - -## 9. Config ↔ feature folder naming - -- **Feature directories** under `client/features/`: **`kebab-case`** (`user-ops`, `name-domains`). -- **Config files** under `configs/app/features/`: **migrate to `kebab-case`** in a **single dedicated pre-migration PR** (before feature folder migration starts), so config file names and feature folder names share one mental model. - ---- - -## 10. Migration execution plan - -### Order of operations - -1. **Pre-migration PR:** rename `configs/app/features/` files to kebab-case. -2. **Pre-migration PR:** enable ESLint `import/no-cycle` + `boundaries` rules; fix existing violations. -3. **Bottom-up migration:** start with `client/api` and `client/shared` (lowest in the dependency graph, most imported). Establishes the foundation without touching any UI. -4. **Pilot slice — `tx`:** migrate `lib/tx/`, the `tx` API service, `ui/tx/**`, hooks, types, mocks end-to-end. Use the result as the canonical template for all subsequent slices. -5. **Remaining slices** in parallel (one PR per slice). -6. **Features** in parallel (one PR per feature or logical group). -7. **`client/shell`** — migrate after slices/features it depends on are in place. -8. **Remove root `lib/`** — confirm empty, delete. - -### Rules during migration - -- Update **all imports in the same PR** as the file move — no shims. For high-fanout files use an automated codemod as a dedicated commit within the branch. -- **Rename to kebab-case at move time** — file moves and naming convention changes happen in one pass, never two separate PRs. -- Each PR leaves ESLint green within `client/`; do not merge with new cycle/boundary violations inside `client/`. -- **`types/api.ts` is the stable external type surface** for each slice/feature. External consumers (other slices, features, `client/api/services/`) must import types only from `/types/api.ts`, never from deeper internal paths. Enforced via ESLint `boundaries`. -- `pages/` thin wrappers: **no UI component or business logic** — a dynamic import and, if needed, a `getServerSideProps` inline (per-page logic may stay inline; only shared/reusable SSR helpers move to `nextjs/`). SEO metadata lives in the slice/feature page folder. -- **Tx pilot PR strategy:** migrate the tx slice end-to-end (UI, hooks, utils, types, mocks) while leaving cross-slice dependencies (address, rollup types, etc.) at their old `lib/` paths. Every remaining old-path import in `client/slices/tx/` after the pilot becomes an explicit migration task for the relevant slice/feature PR. - ---- - -## 11. Open topics (round 4) - -- [x] ~~**Path aliases**~~ — `baseUrl: "."` in `tsconfig.json` makes `client/**` absolute imports work without explicit `paths` entries. No action needed. -- [x] ~~**Public type surfaces**~~ — `types/api.ts` is the stable external surface per slice/feature; documented in §10. Cross-slice `types/client.ts` imports left as an open question to resolve with real examples during implementation. -- [x] ~~**Storybook / toolkit**~~ — project does not use Storybook. Topic closed. -- [x] ~~**`lib/hooks/` full audit**~~ — all hooks explicitly placed; see §6 migration map. -- [x] ~~**`lib/api/services/general/` audit**~~ — all files stay under `general/` (General API namespace). No splits needed beyond existing file boundaries. -- [x] ~~**Project glossary**~~ — a `GLOSSARY.md` (or section in CONTRIBUTING) mapping internal codenames to their product/feature meaning (e.g. `tac` → Ton Application Chain, `blobs`/`data-availability`, `cctx` → cross-chain transactions, `epochs` → Celo epoch pages). Aids both engineers and agents navigating the codebase. - ---- - -## 12. Example: rollup-specific field on tx page - -- **Core** tx UI stays in **`slices/tx`**. -- **Arbitrum-only** (or other rollup) presentation lives in **`features/rollup/arbitrum/`**. -- **Compose** at a **high level** (`tx-details` page or `TxDetails` section) so rollup-specific imports are localized and **cycles** remain unlikely. -- **Types:** the `Transaction` type in `slices/tx/types/api.ts` imports feature-specific sub-types (e.g. `ArbitrumTransactionData` from `features/rollup/arbitrum/types/api.ts`) because the backend includes these fields in the response unconditionally when the rollup is enabled — the frontend type must reflect the full API contract. `client/api` then references `Transaction` via `import type` from the slice. UI component imports follow the same direction: `TxDetails` imports `TxDetailsArbitrum`. - -### File layout for this example - -```text -client/ - api/ - services/ - general/ - tx.ts - └─ import type { Transaction } from 'client/slices/tx/types/api' - slices/ - tx/ - pages/ - tx-details/ - TxDetails.tsx - └─ import TxDetailsArbitrum from 'client/features/rollup/arbitrum/components/TxDetailsArbitrum' - types/ - api.ts # owns the Transaction interface - └─ import type { ArbitrumTransactionData } from 'client/features/rollup/arbitrum/types/api' - export interface Transaction { - // ... base tx fields ... - arbitrum?: ArbitrumTransactionData - optimism?: OptimismTransactionData - scroll?: ScrollTransactionData - // ... other rollup optional fields ... - } - features/ - rollup/ - arbitrum/ - types/ - api.ts # owns ArbitrumTransactionData - components/ - TxDetailsArbitrum.tsx -``` - ---- diff --git a/client/MIGRATION_TASKS.md b/client/MIGRATION_TASKS.md deleted file mode 100644 index a093ef3899d..00000000000 --- a/client/MIGRATION_TASKS.md +++ /dev/null @@ -1,309 +0,0 @@ -# Architecture Migration — Task Backlog - -Progress tracker for the `client/` architecture redesign. -Blueprint: `client/ARCH_REDESIGN.md`. Terminology: `docs/GLOSSARY.md`. - -**Task ID format:** `-` — stage number + sequential index within that stage. -Adding tasks to a stage: append the next index; no renumbering needed. - -**Status:** `[ ]` to do · `[~]` in progress · `[x]` done -**Base branch:** `main` — each task branches from `main`; PRs target `main`. -**Parent issue:** [`blockscout/frontend#3341`](https://github.com/blockscout/frontend/issues/3341) - -> `/arch-research ` creates the sub-issue and marks the task `[~]`. -> `/arch-migrate ` executes the task and opens a PR. -> Change `[~]` → `[x]` after the PR is merged. - ---- - -## Stage 0 — Pre-migration - -### 0-1 · [x] Rename `configs/app/features/` files to kebab-case · [#3345](https://github.com/blockscout/frontend/issues/3345) - -**Scope:** Rename every `.ts` file under `configs/app/features/` from camelCase to kebab-case -(e.g. `userOps.ts` → `user-ops.ts`, `adsBanner.ts` → `ads-banner.ts`). -Update all import paths repo-wide. Named exports inside files are unchanged — filenames only. - ---- - -### 0-2 · [x] Enable ESLint `import/no-cycle` + `boundaries` rules · [#3348](https://github.com/blockscout/frontend/issues/3348) - -**Scope:** Add `eslint-plugin-boundaries` (if not already installed). Configure rules so that: -- Imports within `client/` emit **errors** on violations. -- Imports in legacy `lib/` and `ui/` emit **warnings** only. -Fix all pre-existing errors (there should be none in `client/` yet). Confirm warnings in legacy code are expected and do not block CI. - ---- - -## Stage 1 — Foundation - -### 1-1 · [x] Migrate `client/api/` · [#3369](https://github.com/blockscout/frontend/issues/3369) - -**Scope:** Move `lib/api/**` → `client/api/**` (preserve `services/` structure). -Additional moves in the same PR: -- `lib/socket/` → `client/api/socket/` -- `lib/hooks/useFetch.ts` → `client/api/` -- `lib/api/services/utils.ts` → `client/api/types.ts` - -Update all repo-wide imports. Verify `client/api` has no runtime imports from `client/slices/*` or -`client/features/*` (`import type` is permitted). - ---- - -### 1-2 · [x] Migrate `client/shared/` · [#3371](https://github.com/blockscout/frontend/issues/3371) - -**Scope:** Move all cross-cutting utilities from `lib/` to `client/shared//`. -Key moves (see `ARCH_REDESIGN.md §6` for full list): - -| Source | Destination | -|--------|-------------| -| `lib/mixpanel/` | `client/shared/analytics/` | -| `lib/rollbar/` | `client/shared/monitoring/rollbar/` | -| `lib/growthbook/` | `client/shared/feature-flags/` | -| `lib/networks/` + `lib/units.ts` | `client/shared/chain/` (rename `network` → `chain` throughout) | -| `lib/router/` + filter-value helpers | `client/shared/router/` | -| `lib/errors/` + `lib/getErrorMessage.ts` | `client/shared/errors/` | -| `lib/web3/` | `client/shared/web3/` | -| `lib/metadata/` | `client/shared/metadata/` | -| `lib/hooks/useTimeAgoIncrement` | `client/shared/date-and-time/` | -| `lib/hooks/useLazyRenderedList`, `useInitialList`, `lib/getItemIndex.ts` | `client/shared/lists/` | -| `lib/cookies.ts` | `client/shared/storage/` | -| `lib/decodeJWT.ts` | `client/shared/auth/` | -| `lib/setLocale.ts` | `client/shared/i18n/` | -| `lib/capitalizeFirstLetter.ts`, `shortenString.ts`, `escapeRegExp.ts`, `highlightText.ts` | `client/shared/text/` | -| `lib/base64ToHex.ts` and other hex/bytes helpers | `client/shared/transformers/` | -| `lib/utils/stripUtmParams.ts` | `client/shared/links/utils/` | -| `lib/delay.ts`, `lib/isMetaKey.tsx` | `client/shared/utils/` | -| Remaining generic hooks from `lib/hooks/` | `client/shared/hooks/` | - -Note: `lib/monitoring/` (server-side) → `nextjs/`, not `client/shared/`. - ---- - -## Stage 2 — Pilot slice - -### 2-1 · [x] Pilot: migrate `slices/tx` end-to-end - -**Scope:** Full migration of the `tx` slice. Canonical template for all subsequent slices. -- `lib/tx/` → `client/slices/tx/utils/` -- `lib/api/services/general/tx.ts` → `client/api/services/general/tx.ts` (if not already done in 1-1) -- `ui/tx/**` → `client/slices/tx/` (restructure into `pages/`, `components/`, `hooks/`, `utils/`, `types/`, `mocks.ts`, `stubs.ts`) -- Move associated `types/api/tx.ts` → `client/slices/tx/types/api.ts` -- Move associated `mocks/` and `stubs/` entries - -Cross-slice dependencies (address types, rollup sub-types, etc.) may remain at their old `lib/` paths -after this PR — note each as an explicit follow-up in the PR description. - ---- - -## Stage 3 — Core slices - -Each slice follows the template established in 2-1. - -### 3-1 · [x] Slice: `block` · [#3378](https://github.com/blockscout/frontend/issues/3378) -**Scope:** `lib/block/` (if any), `ui/block/**`, related `types/api/`, `mocks/`, `stubs/` → `client/slices/block/` - -### 3-2 · [x] Slice: `address` · [#3380](https://github.com/blockscout/frontend/issues/3380) -**Scope:** `lib/address/`, `ui/address/**`, related types/mocks/stubs, `lib/hooks/useAddressProfileApiQuery`, `lib/contexts/addressHighlight.tsx` → `client/slices/address/` - -### 3-3 · [x] Slice: `search` · [#3405](https://github.com/blockscout/frontend/issues/3405) -**Scope:** `lib/search/`, `ui/snippets/searchBar/`, `lib/recentSearchKeywords.ts`, related types/mocks/stubs → `client/slices/search/` - -### 3-4 · [x] Slice: `token` · [#3393](https://github.com/blockscout/frontend/issues/3393) -**Scope:** `lib/token/`, `ui/token/**`, `ui/tokens/**`, `ui/tokenInstance/**`, related types/mocks/stubs → `client/slices/token/` - -### 3-5 · [x] Slice: `contract` · [#3401](https://github.com/blockscout/frontend/issues/3401) -**Scope:** `lib/contracts/`, `lib/solidityScan/`, `ui/contract/**`, related types/mocks/stubs → `client/slices/contract/` - -### 3-6 · [x] Slice: `internal-tx` · [#3396](https://github.com/blockscout/frontend/issues/3396) -**Scope:** `ui/internalTxs/**` (and any `lib/` counterparts), related types/mocks/stubs → `client/slices/internal-tx/` - -### 3-7 · [x] Slice: `home` · [#3414](https://github.com/blockscout/frontend/issues/3414) -**Scope:** `ui/home/**`, related types/mocks/stubs → `client/slices/home/` - -### 3-8 · [x] Slice: `log` · [#3403](https://github.com/blockscout/frontend/issues/3403) -**Scope:** `ui/shared/log/**`, related types/mocks/stubs → `client/slices/log/` - -### 3-9 · [x] Slice: `token-transfer` · [#3399](https://github.com/blockscout/frontend/issues/3399) -**Scope:** TBD - -### 3-10 · [x] Slice: `gas` · [#3418](https://github.com/blockscout/frontend/issues/3418) -**Scope:** Gas-price domain primitives — no tracker page (that belongs to `features/gas-tracker/`). -- `ui/shared/gas/` → `client/slices/gas/components/` and `client/slices/gas/utils/` -- `types/client/gasTracker.ts` (`GasUnit`, `GAS_UNITS`) → `client/slices/gas/types/client.ts` -- `client/slices/gas/types/api.ts` already exists — verify and complete (`GasPrices`, `GasPriceInfo`) -- Update all import paths in `slices/home/`, `slices/tx/`, `client/shell/`, and `features/gas-tracker/` - ---- - -## Stage 4 — Features: rollups - -One PR per rollup type. Each goes under `client/features/rollup//`. - -> Enumerate additional rollup tasks when this stage begins, based on what exists under `lib/rollups/` and `ui/`. - -### 4-1 · [x] Feature: `rollup/optimism` · [#3421](https://github.com/blockscout/frontend/issues/3421) -**Scope:** All Optimism-specific UI, hooks, utils, types → `client/features/rollup/optimism/`. Includes fault proof system and dispute games. - -### 4-2 · [x] Feature: `rollup/arbitrum` · [#3423](https://github.com/blockscout/frontend/issues/3423) -**Scope:** All Arbitrum-specific UI, hooks, utils, types → `client/features/rollup/arbitrum/` - -### 4-3 · [x] Feature: `rollup/zk-sync` · [#3426](https://github.com/blockscout/frontend/issues/3426) -**Scope:** zkSync-specific UI, hooks, utils, types → `client/features/rollup/zk-sync/`. Check for other zk-based rollup types (scroll, etc.) and add tasks if needed. - -### 4-4 · [x] Feature: `rollup/common` — shared types and utils · [#3432](https://github.com/blockscout/frontend/issues/3432) -**Scope:** Migrate cross-rollup primitives shared by all rollup types. -- `types/client/rollup.ts` (`ROLLUP_TYPES`, `RollupType`, `ParentChain`) → `client/features/rollup/common/types/config.ts`; inline `ArrayElement` to achieve zero imports (required for `configs/` compatibility) -- `client/features/rollup/common/utils/layer.ts` → split: `layerLabels` → `client/features/rollup/common/utils/layer-labels.ts`; `formatZkSyncL2TxnBatchStatus` → `client/features/rollup/zk-sync/utils/format-txn-batch-status.ts` (coordinate with 4-3) -- Update all import paths repo-wide. Delete `client/features/rollup/common/utils/layer.ts` and `types/client/rollup.ts`. - -### 4-5 · [x] Feature: `rollup/scroll` · [#3429](https://github.com/blockscout/frontend/issues/3429) -**Scope:** Scroll-specific UI, hooks, utils, types → `client/features/rollup/scroll/`. Includes deposits, withdrawals, and txn batches pages. -- `types/api/scrollL2.ts` → `client/features/rollup/scroll/types/api.ts` (2 files already started there) -- `stubs/scrollL2.ts` → `client/features/rollup/scroll/stubs.ts` -- `mocks/scroll/` → `client/features/rollup/scroll/mocks/` -- `ui/shared/statusTag/ScrollL2TxnBatchStatus.tsx` → `client/features/rollup/scroll/components/` -- `ui/shared/batch/ScrollL2TxnBatchDA.tsx` → `client/features/rollup/scroll/components/` -- `ui/txnBatches/scrollL2/` → `client/features/rollup/scroll/pages/batches/` -- `ui/deposits/scrollL2/` → `client/features/rollup/scroll/pages/deposits/` -- `ui/withdrawals/scrollL2/` → `client/features/rollup/scroll/pages/withdrawals/` -- `ui/pages/ScrollL2TxnBatches.tsx`, `ScrollL2TxnBatch.tsx` → `client/features/rollup/scroll/pages/batches/` and `batch-details/` -- `ui/pages/ScrollL2Deposits.tsx`, `ScrollL2Withdrawals.tsx` → `client/features/rollup/scroll/pages/deposits/` and `withdrawals/` - -### 4-6 · [x] Feature: `rollup/shibarium` · [#3431](https://github.com/blockscout/frontend/issues/3431) - ---- - -## Stage 5 — Features: chain variants - -One PR per chain variant. Each goes under `client/features/chain-variants//`. - -### 5-1 · [x] Feature: `chain-variants/celo` · [#3434](https://github.com/blockscout/frontend/issues/3434) -**Scope:** All Celo-specific UI and logic including epochs → `client/features/chain-variants/celo/`. See `configs/app/features/celo.ts`. - -### 5-2 · [x] Feature: `chain-variants/tac` · [#3440](https://github.com/blockscout/frontend/issues/3440) -**Scope:** TAC operations and bridge UI → `client/features/chain-variants/tac/`. See `configs/app/features/tac.ts`. - -### 5-3 · [x] Feature: `chain-variants/zeta-chain` · [#3441](https://github.com/blockscout/frontend/issues/3441) -**Scope:** ZetaChain CCTX UI → `client/features/chain-variants/zeta-chain/`. See `configs/app/features/zetachain.ts`. - -### 5-4 · [x] Feature: `chain-variants/suave` · [#3443](https://github.com/blockscout/frontend/issues/3443) -**Scope:** SUAVE Kettle UI → `client/features/chain-variants/suave/`. See `configs/app/features/suave.ts`. - -### 5-5 · [~] Feature: `chain-variants/mega-eth` · [#3449](https://github.com/blockscout/frontend/issues/3449) -**Scope:** MegaETH Flashblocks UI → `client/features/chain-variants/mega-eth/`. See `configs/app/features/megaEth.ts` and `flashblocks.ts`. - -### 5-6 · [x] Feature: `chain-variants/beacon-chain` · [#3442](https://github.com/blockscout/frontend/issues/3442) -**Scope:** Beacon chain deposits/withdrawals UI → `client/features/chain-variants/beacon-chain/`. See `configs/app/features/beaconChain.ts`. - -### 5-7 · [~] Feature: `chain-variants/mud` · [#3450](https://github.com/blockscout/frontend/issues/3450) - ---- - -## Stage 6 — Features: standalone - -One PR per feature. Features that are pure infrastructure (analytics, monitoring, A/B flags) were -migrated to `client/shared/` in 1-2 and do not appear here. - -### 6-1 · [~] Feature: `user-ops` · [#3452](https://github.com/blockscout/frontend/issues/3452) - -### 6-2 · [~] Feature: `data-availability` · [#3453](https://github.com/blockscout/frontend/issues/3453) - -### 6-3 · [ ] Feature: `multichain` -**Scope:** Includes `lib/multichain/`, `lib/contexts/multichain.tsx`, `ui/snippets/networkMenu/` → `client/features/multichain/` - -### 6-4 · [~] Feature: `name-services` (domains and clusters) · [#3458](https://github.com/blockscout/frontend/issues/3458) - -### 6-5 · [~] Feature: `account` · [#3456](https://github.com/blockscout/frontend/issues/3456) -**Scope:** Includes `lib/hooks/useGetCsrfToken`, `ui/snippets/auth/`, `ui/snippets/user/` → `client/features/account/` - -### 6-6 · [ ] Feature: `stats` -**Scope:** `lib/stats/` and stats UI → `client/features/stats/` - -### 6-7 · [ ] Feature: `gas-tracker` -**Scope:** Config-gated tracker page (`NEXT_PUBLIC_GAS_TRACKER_ENABLED`). Depends on task 3-10 (`slices/gas/`) being merged first. -- `ui/gasTracker/` + `ui/pages/GasTracker.tsx` → `client/features/gas-tracker/` -- Imports gas-price primitives from `client/slices/gas/` - -### 6-8 · [ ] Feature: `validators` - -### 6-9 · [ ] Feature: `marketplace` -**Scope:** Includes `lib/contexts/marketplace.tsx`, `lib/hooks/useGraphLinks` → `client/features/marketplace/` - -### 6-10 · [ ] Feature: `rewards` -**Scope:** Includes `lib/contexts/rewards.tsx`, `lib/hooks/useRewardsActivity` → `client/features/rewards/` - -### 6-11 · [ ] Feature: `advanced-filter` - -### 6-12 · [ ] Feature: `ad-banner` -**Scope:** Includes `lib/hooks/useAdblockDetect` → `client/features/ad-banner/`. Covers both `adsBanner.ts` and `adsText.ts` configs. - -### 6-13 · [ ] Feature: `safe-address-tags` -**Scope:** Includes `lib/hooks/useIsSafeAddress` → `client/features/safe-address-tags/`. See `configs/app/features/safe.ts`. - -### 6-14 · [ ] Feature: `metasuites` -**Scope:** Includes `lib/hooks/useNotifyOnNavigation` → `client/features/metasuites/` - -### 6-15 · [ ] Feature: `csv-export` - -### 6-16 · [ ] Feature: `pools` - -### 6-17 · [ ] Feature: `hot-contracts` - -### 6-18 · [ ] Feature: `interchain-indexer` -**Scope:** Cross-chain message indexer (not ZetaChain). See `configs/app/features/crossChainTxs.ts`. - -### 6-19 · [ ] Feature: `mud-framework` - -### 6-20 · [ ] Feature: `visualize` -**Scope:** Solidity-to-UML diagrams. See `configs/app/features/sol2uml.ts`. - -### 6-21 · [ ] Feature: `tx-interpretation` - -### 6-22 · [ ] Feature: `public-tags` -**Scope:** Community address labels. See `configs/app/features/publicTagsSubmission.ts`. - -### 6-23 · [ ] Feature: `address-widgets` -**Scope:** Third-party widgets on address pages. See `configs/app/features/address3rdPartyWidgets.ts`. - -### 6-24 · [ ] Feature: `address-metadata` - -### 6-25 · [ ] Feature: `address-verification` -**Scope:** Covers `addressProfileAPI.ts` and `addressVerification.ts` configs. - -### 6-26 · [ ] Feature: `bridged-tokens` - -### 6-27 · [ ] Feature: `web3-wallet` -**Scope:** Includes `blockchainInteraction.ts` config. - -### 6-28 · [ ] Feature: `alternative-explorers` -**Scope:** The "Verify with other explorers" menu shown on tx, block, address, and token pages. Move `ui/shared/NetworkExplorers.tsx` (and its `.pw.tsx` test) → `client/features/alternative-explorers/`. The util `client/features/alternative-explorers/utils/explorers.ts` already exists (landed in 1-2). Config-gated via `NEXT_PUBLIC_NETWORK_EXPLORERS`. - -### 6-29 · [ ] Feature: `get-gas-button` -**Scope:** Gas refuel CTA — independently config-gated (`NEXT_PUBLIC_GAS_REFUEL_PROVIDER_CONFIG`), no dependency on `gas-tracker` being enabled. -- `ui/snippets/topBar/GetGasButton.tsx` → `client/features/get-gas-button/` -- `types/client/gasRefuelProviderConfig.ts` → `client/features/get-gas-button/types/` -- See `configs/app/features/get-gas-button.ts` - -> Other small features (`externalTxs`, `xStarScore`, `deFiDropdown`, `easterEgg*`, `apiDocs`, `verifiedTokens`, `multichainButton`) — enumerate as separate tasks or group with related features when this stage begins. - ---- - -## Stage 7 — Shell - -### 7-1 · [ ] Migrate `client/shell` - -**Scope:** Migrate after all slices and features it depends on are in place. -- `ui/snippets/header/`, `footer/`, `navigation/`, `topBar/` → `client/shell/` -- `lib/contexts/app.tsx`, `fallback.tsx` → `client/shell/` -- `lib/contexts/settings.tsx` → `client/shell/top-bar/` -- `lib/hooks/useNavItems` → `client/shell/` - ---- - -## Stage 8 — Cleanup - -### 8-1 · [ ] Remove legacy root directories - -**Scope:** Confirm `lib/`, `ui/`, `mocks/`, `stubs/`, `types/` are empty (no remaining unconverted files). -Delete them. Fix any remaining lint warnings that referenced legacy paths. diff --git a/client/api/build-url.spec.ts b/client/api/build-url.spec.ts deleted file mode 100644 index 56a1d3ca9e7..00000000000 --- a/client/api/build-url.spec.ts +++ /dev/null @@ -1,45 +0,0 @@ -import { expect, test, describe } from 'vitest'; - -import buildUrl from './build-url'; - -test('builds URL for resource without path params', () => { - const url = buildUrl('general:config_backend_version'); - expect(url).toBe('https://localhost:3003/api/v2/config/backend-version'); -}); - -test('builds URL for resource with path params', () => { - const url = buildUrl('general:block', { height_or_hash: '42' }); - expect(url).toBe('https://localhost:3003/api/v2/blocks/42'); -}); - -describe('falsy query parameters', () => { - test('leaves "false" as query parameter', () => { - const url = buildUrl('general:block', { height_or_hash: '42' }, { includeTx: false }); - expect(url).toBe('https://localhost:3003/api/v2/blocks/42?includeTx=false'); - }); - - test('leaves "null" as query parameter', () => { - const url = buildUrl('general:block', { height_or_hash: '42' }, { includeTx: null }); - expect(url).toBe('https://localhost:3003/api/v2/blocks/42?includeTx=null'); - }); - - test('strips out empty string as query parameter', () => { - const url = buildUrl('general:block', { height_or_hash: '42' }, { includeTx: null, sort: '' }); - expect(url).toBe('https://localhost:3003/api/v2/blocks/42?includeTx=null'); - }); - - test('strips out "undefined" as query parameter', () => { - const url = buildUrl('general:block', { height_or_hash: '42' }, { includeTx: null, sort: undefined }); - expect(url).toBe('https://localhost:3003/api/v2/blocks/42?includeTx=null'); - }); -}); - -test('builds URL with array-like query parameters', () => { - const url = buildUrl('general:block', { height_or_hash: '42' }, { includeTx: [ '0x11', '0x22' ], sort: 'asc' }); - expect(url).toBe('https://localhost:3003/api/v2/blocks/42?includeTx=0x11%2C0x22&sort=asc'); -}); - -test('builds URL for resource with custom API endpoint', () => { - const url = buildUrl('contractInfo:token_verified_info', { instanceId: '42', hash: '0x11' }); - expect(url).toBe('https://localhost:3005/api/v1/chains/42/token-infos/0x11'); -}); diff --git a/client/api/get-resource-params.ts b/client/api/get-resource-params.ts deleted file mode 100644 index 3220878e9d8..00000000000 --- a/client/api/get-resource-params.ts +++ /dev/null @@ -1,31 +0,0 @@ -// SPDX-License-Identifier: LicenseRef-Blockscout - -import type { ApiName, ApiResource } from './types'; -import type { ExternalChainExtended } from 'types/externalChains'; - -import config from 'configs/app'; - -import type { ResourceName } from './resources'; -import { RESOURCES } from './resources'; - -export default function getResourceParams(resourceFullName: ResourceName, chain?: ExternalChainExtended) { - const [ apiName, resourceName ] = resourceFullName.split(':') as [ ApiName, string ]; - - const apiConfig = (() => { - if (chain?.app_config?.apis) { - return chain.app_config.apis[apiName as keyof typeof chain.app_config.apis]; - } - - return config.apis[apiName]; - })(); - - if (!apiConfig) { - throw new Error(`API config for ${ apiName } not found`); - } - - return { - api: apiConfig, - apiName, - resource: RESOURCES[apiName][resourceName as keyof typeof RESOURCES[ApiName]] as ApiResource, - }; -} diff --git a/client/api/get-socket-url.ts b/client/api/get-socket-url.ts deleted file mode 100644 index 5f6382bec6a..00000000000 --- a/client/api/get-socket-url.ts +++ /dev/null @@ -1,7 +0,0 @@ -// SPDX-License-Identifier: LicenseRef-Blockscout - -import appConfig from 'configs/app'; - -export default function getSocketUrl(config: typeof appConfig = appConfig) { - return config.apis.general ? `${ config.apis.general.socketEndpoint }${ config.apis.general.basePath ?? '' }/socket/v2` : undefined; -} diff --git a/client/api/resources.ts b/client/api/resources.ts deleted file mode 100644 index d2e8f48cb81..00000000000 --- a/client/api/resources.ts +++ /dev/null @@ -1,176 +0,0 @@ -// SPDX-License-Identifier: LicenseRef-Blockscout - -import type { ApiName, ApiResource, IsPaginated } from './types'; - -import type { AdminApiResourceName, AdminApiResourcePayload } from './services/admin'; -import { ADMIN_API_RESOURCES } from './services/admin'; -import { BENS_API_RESOURCES } from './services/bens'; -import type { BensApiResourceName, BensApiResourcePayload, BensApiPaginationFilters, BensApiPaginationSorting } from './services/bens'; -import { CLUSTERS_API_RESOURCES } from './services/clusters'; -import type { ClustersApiResourceName, ClustersApiResourcePayload, ClustersApiPaginationFilters, ClustersApiPaginationSorting } from './services/clusters'; -import { CONTRACT_INFO_API_RESOURCES } from './services/contract-info'; -import type { ContractInfoApiPaginationFilters, ContractInfoApiResourceName, ContractInfoApiResourcePayload } from './services/contract-info'; -import { GENERAL_API_RESOURCES } from './services/general'; -import type { GeneralApiResourceName, GeneralApiResourcePayload, GeneralApiPaginationFilters, GeneralApiPaginationSorting } from './services/general'; -import type { - InterchainIndexerApiPaginationFilters, - InterchainIndexerApiResourceName, - InterchainIndexerApiResourcePayload, - InterchainIndexerApiPaginationSorting, -} from './services/interchain-indexer'; -import { INTERCHAIN_INDEXER_API_RESOURCES } from './services/interchain-indexer'; -import type { MetadataApiResourceName, MetadataApiResourcePayload } from './services/metadata'; -import { METADATA_API_RESOURCES } from './services/metadata'; -import type { - MultichainAggregatorApiPaginationFilters, - MultichainAggregatorApiResourceName, - MultichainAggregatorApiResourcePayload, -} from './services/multichain-aggregator'; -import { MULTICHAIN_AGGREGATOR_API_RESOURCES } from './services/multichain-aggregator'; -import type { MultichainStatsApiResourcePayload, MultichainStatsApiResourceName } from './services/multichain-stats'; -import { MULTICHAIN_STATS_API_RESOURCES } from './services/multichain-stats'; -import type { RewardsApiResourceName, RewardsApiResourcePayload } from './services/rewards'; -import { REWARDS_API_RESOURCES } from './services/rewards'; -import type { StatsApiResourceName, StatsApiResourcePayload } from './services/stats'; -import { STATS_API_RESOURCES } from './services/stats'; -import { TAC_OPERATION_LIFECYCLE_API_RESOURCES } from './services/tac-operation-lifecycle'; -import type { - TacOperationLifecycleApiPaginationFilters, - TacOperationLifecycleApiResourceName, - TacOperationLifecycleApiResourcePayload, -} from './services/tac-operation-lifecycle'; -import { USER_OPS_API_RESOURCES } from './services/user-ops'; -import { VISUALIZE_API_RESOURCES } from './services/visualize'; -import type { VisualizeApiResourceName, VisualizeApiResourcePayload } from './services/visualize'; -import { ZETA_CHAIN_API_RESOURCES } from './services/zeta-chain'; -import type { ZetaChainApiPaginationFilters, ZetaChainApiResourceName, ZetaChainApiResourcePayload } from './services/zeta-chain'; - -export const RESOURCES = { - admin: ADMIN_API_RESOURCES, - bens: BENS_API_RESOURCES, - clusters: CLUSTERS_API_RESOURCES, - contractInfo: CONTRACT_INFO_API_RESOURCES, - general: GENERAL_API_RESOURCES, - interchainIndexer: INTERCHAIN_INDEXER_API_RESOURCES, - metadata: METADATA_API_RESOURCES, - multichainAggregator: MULTICHAIN_AGGREGATOR_API_RESOURCES, - multichainStats: MULTICHAIN_STATS_API_RESOURCES, - rewards: REWARDS_API_RESOURCES, - stats: STATS_API_RESOURCES, - tac: TAC_OPERATION_LIFECYCLE_API_RESOURCES, - userOps: USER_OPS_API_RESOURCES, - visualize: VISUALIZE_API_RESOURCES, - zetachain: ZETA_CHAIN_API_RESOURCES, - // external API resources - // there is no type definition for them, use valibot to parse the response - external: { - safe_transaction_api: { - path: '', - }, - }, -} satisfies Record>; - -export const resourceKey = (x: ResourceName) => x; - -export type ResourceName = { - [K in keyof typeof RESOURCES]: `${ K & string }:${ keyof (typeof RESOURCES)[K] & string }` -}[keyof typeof RESOURCES]; - -export type ResourcePath = string; - -/* eslint-disable @stylistic/indent */ -export type ResourcePayload = -R extends AdminApiResourceName ? AdminApiResourcePayload : -R extends BensApiResourceName ? BensApiResourcePayload : -R extends ClustersApiResourceName ? ClustersApiResourcePayload : -R extends ContractInfoApiResourceName ? ContractInfoApiResourcePayload : -R extends GeneralApiResourceName ? GeneralApiResourcePayload : -R extends InterchainIndexerApiResourceName ? InterchainIndexerApiResourcePayload : -R extends MetadataApiResourceName ? MetadataApiResourcePayload : -R extends MultichainAggregatorApiResourceName ? MultichainAggregatorApiResourcePayload : -R extends MultichainStatsApiResourceName ? MultichainStatsApiResourcePayload : -R extends RewardsApiResourceName ? RewardsApiResourcePayload : -R extends StatsApiResourceName ? StatsApiResourcePayload : -R extends TacOperationLifecycleApiResourceName ? TacOperationLifecycleApiResourcePayload : -R extends VisualizeApiResourceName ? VisualizeApiResourcePayload : -R extends ZetaChainApiResourceName ? ZetaChainApiResourcePayload : -never; -/* eslint-enable @stylistic/indent */ - -type ResourcePathParamName = Q extends `${ infer A }:${ infer R }` ? - (typeof RESOURCES)[A & keyof typeof RESOURCES][R & keyof (typeof RESOURCES)[A & keyof typeof RESOURCES]] extends { pathParams: Array } ? - (typeof RESOURCES)[A & keyof typeof RESOURCES][R & keyof (typeof RESOURCES)[A & keyof typeof RESOURCES]]['pathParams'][number] : - never : - never; - -export type ResourcePathParams = Q extends `${ infer A }:${ infer R }` ? - (typeof RESOURCES)[A & keyof typeof RESOURCES][R & keyof (typeof RESOURCES)[A & keyof typeof RESOURCES]] extends { pathParams: Array } ? - Record, string | undefined> : - never : - never; - -export interface ResourceError { - payload?: T; - status: Response['status']; - statusText: Response['statusText']; -} - -export type ResourceErrorAccount = ResourceError<{ errors: T }>; - -// PAGINATION - -/* eslint-disable @stylistic/indent */ -export type PaginationFilters = -R extends BensApiResourceName ? BensApiPaginationFilters : -R extends ClustersApiResourceName ? ClustersApiPaginationFilters : -R extends GeneralApiResourceName ? GeneralApiPaginationFilters : -R extends ContractInfoApiResourceName ? ContractInfoApiPaginationFilters : -R extends InterchainIndexerApiResourceName ? InterchainIndexerApiPaginationFilters : -R extends MultichainAggregatorApiResourceName ? MultichainAggregatorApiPaginationFilters : -R extends TacOperationLifecycleApiResourceName ? TacOperationLifecycleApiPaginationFilters : -R extends ZetaChainApiResourceName ? ZetaChainApiPaginationFilters : -never; -/* eslint-enable @stylistic/indent */ - -export const SORTING_FIELDS = [ 'sort', 'order' ]; - -/* eslint-disable @stylistic/indent */ -export type PaginationSorting = -R extends BensApiResourceName ? BensApiPaginationSorting : -R extends ClustersApiResourceName ? ClustersApiPaginationSorting : -R extends GeneralApiResourceName ? GeneralApiPaginationSorting : -R extends InterchainIndexerApiResourceName ? InterchainIndexerApiPaginationSorting : -never; -/* eslint-enable @stylistic/indent */ - -export type PaginatedResourceName = { - [A in keyof typeof RESOURCES]: { - [R in keyof (typeof RESOURCES)[A]]: (typeof RESOURCES)[A][R] extends ApiResource ? - IsPaginated<(typeof RESOURCES)[A][R]> extends true ? `${ A & string }:${ R & string }` : never : - never - }[keyof (typeof RESOURCES)[A]] -}[keyof typeof RESOURCES]; - -export type PaginatedResourceResponse = ResourcePayload; - -export type PaginatedResourceResponseItems = R extends PaginatedResourceName ? - ResourcePayload['items'] : - never; - -export type PaginatedResourceResponseNextPageParams = R extends PaginatedResourceName ? - ResourcePayload['next_page_params'] : - never; - -// TESTS -export const a: ResourcePayload<'general:api_keys'> = [ { - api_key: '123', - name: '123', -} ]; - -export const b: PaginatedResourceName = 'general:addresses'; - -export const c: PaginatedResourceResponseItems<'general:addresses'> = []; - -export const d: ResourcePathParams<'bens:address_domain'> = { - address: '123', -}; diff --git a/client/api/services/clusters.ts b/client/api/services/clusters.ts deleted file mode 100644 index 7746a2f1984..00000000000 --- a/client/api/services/clusters.ts +++ /dev/null @@ -1,59 +0,0 @@ -// SPDX-License-Identifier: LicenseRef-Blockscout - -import type { ApiResource } from '../types'; -import type { - ClustersByAddressResponse, - ClusterByNameResponse, - ClustersLeaderboardResponse, - ClustersDirectoryResponse, - ClustersByAddressQueryParams, - ClusterByNameQueryParams, - ClustersLeaderboardQueryParams, - ClustersDirectoryQueryParams, - ClusterByIdQueryParams, - ClusterByIdResponse, -} from 'types/api/clusters'; - -export const CLUSTERS_API_RESOURCES = { - get_clusters_by_address: { - path: '/v1/trpc/names.getNamesByOwnerAddress', - pathParams: [], - }, - get_cluster_by_name: { - path: '/v1/trpc/names.get', - pathParams: [], - }, - get_cluster_by_id: { - path: '/v1/trpc/clusters.getClusterById', - pathParams: [], - }, - get_leaderboard: { - path: '/v1/trpc/names.leaderboard', - pathParams: [], - }, - get_directory: { - path: '/v1/trpc/names.search', - pathParams: [], - }, -} satisfies Record; - -export type ClustersApiResourceName = `clusters:${ keyof typeof CLUSTERS_API_RESOURCES }`; - -export type ClustersApiResourcePayload = - R extends 'clusters:get_clusters_by_address' ? ClustersByAddressResponse : - R extends 'clusters:get_cluster_by_name' ? ClusterByNameResponse : - R extends 'clusters:get_cluster_by_id' ? ClusterByIdResponse : - R extends 'clusters:get_leaderboard' ? ClustersLeaderboardResponse : - R extends 'clusters:get_directory' ? ClustersDirectoryResponse : - never; - -export type ClustersApiQueryParams = - R extends 'clusters:get_clusters_by_address' ? ClustersByAddressQueryParams : - R extends 'clusters:get_cluster_by_name' ? ClusterByNameQueryParams : - R extends 'clusters:get_cluster_by_id' ? ClusterByIdQueryParams : - R extends 'clusters:get_leaderboard' ? ClustersLeaderboardQueryParams : - R extends 'clusters:get_directory' ? ClustersDirectoryQueryParams : - never; - -export type ClustersApiPaginationFilters = never; -export type ClustersApiPaginationSorting = never; diff --git a/client/api/services/general/account.ts b/client/api/services/general/account.ts deleted file mode 100644 index 5b2c7c58580..00000000000 --- a/client/api/services/general/account.ts +++ /dev/null @@ -1,79 +0,0 @@ -// SPDX-License-Identifier: LicenseRef-Blockscout - -import type { ApiResource } from '../../types'; -import type { AddressTagsResponse, ApiKeys, CustomAbis, TransactionTagsResponse, UserInfo, WatchlistResponse } from 'client/features/account/types/api'; - -export const GENERAL_API_ACCOUNT_RESOURCES = { - // ACCOUNT - csrf: { - path: '/api/account/v2/get_csrf', - }, - user_info: { - path: '/api/account/v2/user/info', - }, - custom_abi: { - path: '/api/account/v2/user/custom_abis{/:id}', - pathParams: [ 'id' as const ], - }, - watchlist: { - path: '/api/account/v2/user/watchlist{/:id}', - pathParams: [ 'id' as const ], - filterFields: [ ], - paginated: true, - }, - private_tags_address: { - path: '/api/account/v2/user/tags/address{/:id}', - pathParams: [ 'id' as const ], - filterFields: [ ], - paginated: true, - }, - private_tags_tx: { - path: '/api/account/v2/user/tags/transaction{/:id}', - pathParams: [ 'id' as const ], - filterFields: [ ], - paginated: true, - }, - api_keys: { - path: '/api/account/v2/user/api_keys{/:id}', - pathParams: [ 'id' as const ], - }, - - // AUTH - auth_send_otp: { - path: '/api/account/v2/send_otp', - }, - auth_confirm_otp: { - path: '/api/account/v2/confirm_otp', - }, - auth_siwe_message: { - path: '/api/account/v2/siwe_message', - }, - auth_siwe_verify: { - path: '/api/account/v2/authenticate_via_wallet', - }, - auth_link_email: { - path: '/api/account/v2/email/link', - }, - auth_link_address: { - path: '/api/account/v2/address/link', - }, - auth_logout: { - path: '/api/account/auth/logout', - }, - auth_dynamic: { - path: '/api/account/v2/authenticate_via_dynamic', - }, -} satisfies Record; - -export type GeneralApiAccountResourceName = `general:${ keyof typeof GENERAL_API_ACCOUNT_RESOURCES }`; - -/* eslint-disable @stylistic/indent */ -export type GeneralApiAccountResourcePayload = -R extends 'general:user_info' ? UserInfo : -R extends 'general:custom_abi' ? CustomAbis : -R extends 'general:private_tags_address' ? AddressTagsResponse : -R extends 'general:private_tags_tx' ? TransactionTagsResponse : -R extends 'general:api_keys' ? ApiKeys : -R extends 'general:watchlist' ? WatchlistResponse : -never; -/* eslint-enable @stylistic/indent */ diff --git a/client/api/services/general/address.ts b/client/api/services/general/address.ts deleted file mode 100644 index 7f1d4f5e85f..00000000000 --- a/client/api/services/general/address.ts +++ /dev/null @@ -1,215 +0,0 @@ -// SPDX-License-Identifier: LicenseRef-Blockscout - -import type { ApiResource } from '../../types'; -import type { AddressesMetadataSearchFilters, AddressesMetadataSearchResult } from 'client/features/address-metadata/types/api'; -import type { DepositsResponse } from 'client/features/chain-variants/beacon-chain/types/api'; -import type { AddressEpochRewardsResponse } from 'client/features/chain-variants/celo/types/api'; -import type { - AddressCounters, - AddressBlocksValidatedResponse, - AddressTokensResponse, - AddressCollectionsResponse, - AddressNFTsResponse, - AddressWithdrawalsResponse, - AddressXStarResponse, - AddressCoinBalanceHistoryChart, - AddressCoinBalanceHistoryResponse, - AddressTokenTransferResponse, - AddressInternalTxsResponse, - AddressTransactionsResponse, - AddressTabsCounters, - Address, - AddressTxsFilters, - AddressTokenTransferFilters, - AddressTokensFilter, - AddressNFTTokensFilter, - AddressTokenBalancesResponse, - AddressesResponse } from 'client/slices/address/types/api'; -import type { LogsResponseAddress } from 'client/slices/log/types/api'; -import type { TransactionsSorting } from 'client/slices/tx/types/api'; - -export const GENERAL_API_ADDRESS_RESOURCES = { - // ADDRESSES - addresses: { - path: '/api/v2/addresses/', - filterFields: [ ], - paginated: true, - }, - addresses_metadata_search: { - path: '/api/v2/proxy/metadata/addresses', - filterFields: [ 'slug' as const, 'tag_type' as const ], - paginated: true, - }, - - // ADDRESS INFO - address: { - path: '/api/v2/addresses/:hash', - pathParams: [ 'hash' as const ], - }, - address_counters: { - path: '/api/v2/addresses/:hash/counters', - pathParams: [ 'hash' as const ], - }, - address_tabs_counters: { - path: '/api/v2/addresses/:hash/tabs-counters', - pathParams: [ 'hash' as const ], - }, - address_txs: { - path: '/api/v2/addresses/:hash/transactions', - pathParams: [ 'hash' as const ], - filterFields: [ 'filter' as const ], - paginated: true, - }, - address_internal_txs: { - path: '/api/v2/addresses/:hash/internal-transactions', - pathParams: [ 'hash' as const ], - filterFields: [ 'filter' as const ], - paginated: true, - }, - address_token_transfers: { - path: '/api/v2/addresses/:hash/token-transfers', - pathParams: [ 'hash' as const ], - filterFields: [ 'filter' as const, 'type' as const, 'token' as const ], - paginated: true, - }, - address_blocks_validated: { - path: '/api/v2/addresses/:hash/blocks-validated', - pathParams: [ 'hash' as const ], - filterFields: [ ], - paginated: true, - }, - address_coin_balance: { - path: '/api/v2/addresses/:hash/coin-balance-history', - pathParams: [ 'hash' as const ], - filterFields: [ ], - paginated: true, - }, - address_coin_balance_chart: { - path: '/api/v2/addresses/:hash/coin-balance-history-by-day', - pathParams: [ 'hash' as const ], - filterFields: [ ], - }, - address_logs: { - path: '/api/v2/addresses/:hash/logs', - pathParams: [ 'hash' as const ], - filterFields: [ ], - paginated: true, - }, - address_tokens: { - path: '/api/v2/addresses/:hash/tokens', - pathParams: [ 'hash' as const ], - filterFields: [ 'type' as const ], - paginated: true, - }, - address_token_balances: { - path: '/api/v2/addresses/:hash/token-balances', - pathParams: [ 'hash' as const ], - filterFields: [ ], - }, - address_nfts: { - path: '/api/v2/addresses/:hash/nft', - pathParams: [ 'hash' as const ], - filterFields: [ 'type' as const ], - paginated: true, - }, - address_collections: { - path: '/api/v2/addresses/:hash/nft/collections', - pathParams: [ 'hash' as const ], - filterFields: [ 'type' as const ], - paginated: true, - }, - address_deposits: { - path: '/api/v2/addresses/:hash/beacon/deposits', - pathParams: [ 'hash' as const ], - filterFields: [], - paginated: true, - }, - address_withdrawals: { - path: '/api/v2/addresses/:hash/withdrawals', - pathParams: [ 'hash' as const ], - filterFields: [], - paginated: true, - }, - address_epoch_rewards: { - path: '/api/v2/addresses/:hash/celo/election-rewards', - pathParams: [ 'hash' as const ], - filterFields: [], - paginated: true, - }, - address_xstar_score: { - path: '/api/v2/proxy/3rdparty/xname/addresses/:hash', - pathParams: [ 'hash' as const ], - }, - address_3rd_party_info: { - path: '/api/v2/proxy/3rdparty/:name', - pathParams: [ 'name' as const ], - filterFields: [ 'address' as const, 'chain_id' as const ], - }, - - // CSV EXPORTS - address_csv_export_txs: { - path: '/api/v2/addresses/:hash/transactions/csv', - pathParams: [ 'hash' as const ], - }, - address_csv_export_internal_txs: { - path: '/api/v2/addresses/:hash/internal-transactions/csv', - pathParams: [ 'hash' as const ], - }, - address_csv_export_token_transfers: { - path: '/api/v2/addresses/:hash/token-transfers/csv', - pathParams: [ 'hash' as const ], - }, - address_csv_export_logs: { - path: '/api/v2/addresses/:hash/logs/csv', - pathParams: [ 'hash' as const ], - }, - address_csv_export_celo_election_rewards: { - path: '/api/v2/addresses/:hash/celo/election-rewards/csv', - pathParams: [ 'hash' as const ], - }, -} satisfies Record; - -export type GeneralApiAddressResourceName = `general:${ keyof typeof GENERAL_API_ADDRESS_RESOURCES }`; - -/* eslint-disable @stylistic/indent */ -export type GeneralApiAddressResourcePayload = -R extends 'general:addresses' ? AddressesResponse : -R extends 'general:addresses_metadata_search' ? AddressesMetadataSearchResult : -R extends 'general:address' ? Address : -R extends 'general:address_counters' ? AddressCounters : -R extends 'general:address_tabs_counters' ? AddressTabsCounters : -R extends 'general:address_txs' ? AddressTransactionsResponse : -R extends 'general:address_internal_txs' ? AddressInternalTxsResponse : -R extends 'general:address_token_transfers' ? AddressTokenTransferResponse : -R extends 'general:address_blocks_validated' ? AddressBlocksValidatedResponse : -R extends 'general:address_coin_balance' ? AddressCoinBalanceHistoryResponse : -R extends 'general:address_coin_balance_chart' ? AddressCoinBalanceHistoryChart : -R extends 'general:address_logs' ? LogsResponseAddress : -R extends 'general:address_tokens' ? AddressTokensResponse : -R extends 'general:address_token_balances' ? AddressTokenBalancesResponse : -R extends 'general:address_nfts' ? AddressNFTsResponse : -R extends 'general:address_collections' ? AddressCollectionsResponse : -R extends 'general:address_withdrawals' ? AddressWithdrawalsResponse : -R extends 'general:address_deposits' ? DepositsResponse : -R extends 'general:address_epoch_rewards' ? AddressEpochRewardsResponse : -R extends 'general:address_xstar_score' ? AddressXStarResponse : -R extends 'general:address_3rd_party_info' ? unknown : -never; -/* eslint-enable @stylistic/indent */ - -/* eslint-disable @stylistic/indent */ -export type GeneralApiAddressPaginationFilters = -R extends 'general:addresses_metadata_search' ? AddressesMetadataSearchFilters : -R extends 'general:address_txs' | 'general:address_internal_txs' ? AddressTxsFilters : -R extends 'general:address_token_transfers' ? AddressTokenTransferFilters : -R extends 'general:address_tokens' ? AddressTokensFilter : -R extends 'general:address_nfts' ? AddressNFTTokensFilter : -R extends 'general:address_collections' ? AddressNFTTokensFilter : -never; -/* eslint-enable @stylistic/indent */ - -/* eslint-disable @stylistic/indent */ -export type GeneralApiAddressPaginationSorting = -R extends 'general:address_txs' ? TransactionsSorting : -never; -/* eslint-enable @stylistic/indent */ diff --git a/client/api/services/general/block.ts b/client/api/services/general/block.ts deleted file mode 100644 index 38d436f6725..00000000000 --- a/client/api/services/general/block.ts +++ /dev/null @@ -1,70 +0,0 @@ -// SPDX-License-Identifier: LicenseRef-Blockscout - -import type { ApiResource } from '../../types'; -import type { DepositsResponse } from 'client/features/chain-variants/beacon-chain/types/api'; -import type { TxsWithBlobsFilters } from 'client/features/data-availability/types/api'; -import type { - BlocksResponse, - BlockTransactionsResponse, - Block, - BlockFilters, - BlockWithdrawalsResponse, - BlockCountdownResponse, - BlockInternalTransactionsResponse, -} from 'client/slices/block/types/api'; - -export const GENERAL_API_BLOCK_RESOURCES = { - blocks: { - path: '/api/v2/blocks', - filterFields: [ 'type' as const ], - paginated: true, - }, - block: { - path: '/api/v2/blocks/:height_or_hash', - pathParams: [ 'height_or_hash' as const ], - }, - block_txs: { - path: '/api/v2/blocks/:height_or_hash/transactions', - pathParams: [ 'height_or_hash' as const ], - filterFields: [ 'type' as const ], - paginated: true, - }, - block_internal_txs: { - path: '/api/v2/blocks/:height_or_hash/internal-transactions', - pathParams: [ 'height_or_hash' as const ], - paginated: true, - }, - block_deposits: { - path: '/api/v2/blocks/:height_or_hash/beacon/deposits', - pathParams: [ 'height_or_hash' as const ], - filterFields: [], - paginated: true, - }, - block_withdrawals: { - path: '/api/v2/blocks/:height_or_hash/withdrawals', - pathParams: [ 'height_or_hash' as const ], - filterFields: [], - paginated: true, - }, -} satisfies Record; - -export type GeneralApiBlockResourceName = `general:${ keyof typeof GENERAL_API_BLOCK_RESOURCES }`; - -/* eslint-disable @stylistic/indent */ -export type GeneralApiBlockResourcePayload = -R extends 'general:blocks' ? BlocksResponse : -R extends 'general:block' ? Block : -R extends 'general:block_countdown' ? BlockCountdownResponse : -R extends 'general:block_txs' ? BlockTransactionsResponse : -R extends 'general:block_internal_txs' ? BlockInternalTransactionsResponse : -R extends 'general:block_withdrawals' ? BlockWithdrawalsResponse : -R extends 'general:block_deposits' ? DepositsResponse : -never; -/* eslint-enable @stylistic/indent */ - -/* eslint-disable @stylistic/indent */ -export type GeneralApiBlockPaginationFilters = -R extends 'general:blocks' ? BlockFilters : -R extends 'general:block_txs' ? TxsWithBlobsFilters : -never; -/* eslint-enable @stylistic/indent */ diff --git a/client/api/services/general/contract.ts b/client/api/services/general/contract.ts deleted file mode 100644 index 6e6a4624b94..00000000000 --- a/client/api/services/general/contract.ts +++ /dev/null @@ -1,63 +0,0 @@ -// SPDX-License-Identifier: LicenseRef-Blockscout - -import type { ApiResource } from '../../types'; -import type { SmartContractSecurityAudits } from 'client/features/contract-audit-reports/types/api'; -import type { - SmartContract, - SmartContractVerificationConfigRaw, - VerifiedContractsResponse, VerifiedContractsCounters, VerifiedContractsFilters, VerifiedContractsSorting } from 'client/slices/contract/types/api'; - -export const GENERAL_API_CONTRACT_RESOURCES = { - contract: { - path: '/api/v2/smart-contracts/:hash', - pathParams: [ 'hash' as const ], - }, - contract_verification_config: { - path: '/api/v2/smart-contracts/verification/config', - }, - contract_verification_via: { - path: '/api/v2/smart-contracts/:hash/verification/via/:method', - pathParams: [ 'hash' as const, 'method' as const ], - }, - contract_solidity_scan_report: { - path: '/api/v2/proxy/3rdparty/solidityscan/smart-contracts/:hash/report', - pathParams: [ 'hash' as const ], - }, - contract_security_audits: { - path: '/api/v2/smart-contracts/:hash/audit-reports', - pathParams: [ 'hash' as const ], - }, - verified_contracts: { - path: '/api/v2/smart-contracts', - filterFields: [ 'q' as const, 'filter' as const ], - paginated: true, - }, - verified_contracts_counters: { - path: '/api/v2/smart-contracts/counters', - }, -} satisfies Record; - -export type GeneralApiContractResourceName = `general:${ keyof typeof GENERAL_API_CONTRACT_RESOURCES }`; - -/* eslint-disable @stylistic/indent */ -export type GeneralApiContractResourcePayload = -R extends 'general:contract' ? SmartContract : -R extends 'general:contract_solidity_scan_report' ? unknown : -R extends 'general:verified_contracts' ? VerifiedContractsResponse : -R extends 'general:verified_contracts_counters' ? VerifiedContractsCounters : -R extends 'general:contract_verification_config' ? SmartContractVerificationConfigRaw : -R extends 'general:contract_security_audits' ? SmartContractSecurityAudits : -never; -/* eslint-enable @stylistic/indent */ - -/* eslint-disable @stylistic/indent */ -export type GeneralApiContractPaginationFilters = -R extends 'general:verified_contracts' ? VerifiedContractsFilters : -never; -/* eslint-enable @stylistic/indent */ - -/* eslint-disable @stylistic/indent */ -export type GeneralApiContractPaginationSorting = -R extends 'general:verified_contracts' ? VerifiedContractsSorting : -never; -/* eslint-enable @stylistic/indent */ diff --git a/client/api/services/general/index.ts b/client/api/services/general/index.ts deleted file mode 100644 index 2da63bdf9d0..00000000000 --- a/client/api/services/general/index.ts +++ /dev/null @@ -1,99 +0,0 @@ -// SPDX-License-Identifier: LicenseRef-Blockscout - -import type { ApiResource } from '../../types'; - -import type { GeneralApiAccountResourceName, GeneralApiAccountResourcePayload } from './account'; -import { GENERAL_API_ACCOUNT_RESOURCES } from './account'; -import type { - GeneralApiAddressPaginationFilters, - GeneralApiAddressPaginationSorting, - GeneralApiAddressResourceName, - GeneralApiAddressResourcePayload, -} from './address'; -import { GENERAL_API_ADDRESS_RESOURCES } from './address'; -import type { - GeneralApiBlockPaginationFilters, - GeneralApiBlockResourceName, GeneralApiBlockResourcePayload } from './block'; -import { GENERAL_API_BLOCK_RESOURCES } from './block'; -import { GENERAL_API_CONTRACT_RESOURCES } from './contract'; -import type { - GeneralApiContractPaginationFilters, - GeneralApiContractPaginationSorting, - GeneralApiContractResourceName, - GeneralApiContractResourcePayload, -} from './contract'; -import type { - GeneralApiMiscPaginationFilters, - GeneralApiMiscPaginationSorting, - GeneralApiMiscResourceName, - GeneralApiMiscResourcePayload, -} from './misc'; -import { GENERAL_API_MISC_RESOURCES } from './misc'; -import type { - GeneralApiRollupPaginationFilters, - GeneralApiRollupPaginationSorting, - GeneralApiRollupResourceName, - GeneralApiRollupResourcePayload, -} from './rollup'; -import { GENERAL_API_ROLLUP_RESOURCES } from './rollup'; -import type { - GeneralApiTokenPaginationFilters, - GeneralApiTokenPaginationSorting, - GeneralApiTokenResourceName, - GeneralApiTokenResourcePayload, -} from './token'; -import { GENERAL_API_TOKEN_RESOURCES } from './token'; -import type { GeneralApiTxResourceName, GeneralApiTxResourcePayload, GeneralApiTxPaginationFilters } from './tx'; -import { GENERAL_API_TX_RESOURCES } from './tx'; -import type { GeneralApiV1ResourceName, GeneralApiV1ResourcePayload } from './v1'; -import { GENERAL_API_V1_RESOURCES } from './v1'; - -export const GENERAL_API_RESOURCES = { - ...GENERAL_API_ACCOUNT_RESOURCES, - ...GENERAL_API_ADDRESS_RESOURCES, - ...GENERAL_API_BLOCK_RESOURCES, - ...GENERAL_API_CONTRACT_RESOURCES, - ...GENERAL_API_MISC_RESOURCES, - ...GENERAL_API_ROLLUP_RESOURCES, - ...GENERAL_API_TOKEN_RESOURCES, - ...GENERAL_API_TX_RESOURCES, - ...GENERAL_API_V1_RESOURCES, -} satisfies Record; - -export type GeneralApiResourceName = `general:${ keyof typeof GENERAL_API_RESOURCES }`; - -/* eslint-disable @stylistic/indent */ -export type GeneralApiResourcePayload = -R extends GeneralApiAccountResourceName ? GeneralApiAccountResourcePayload : -R extends GeneralApiAddressResourceName ? GeneralApiAddressResourcePayload : -R extends GeneralApiBlockResourceName ? GeneralApiBlockResourcePayload : -R extends GeneralApiContractResourceName ? GeneralApiContractResourcePayload : -R extends GeneralApiMiscResourceName ? GeneralApiMiscResourcePayload : -R extends GeneralApiRollupResourceName ? GeneralApiRollupResourcePayload : -R extends GeneralApiTokenResourceName ? GeneralApiTokenResourcePayload : -R extends GeneralApiTxResourceName ? GeneralApiTxResourcePayload : -R extends GeneralApiV1ResourceName ? GeneralApiV1ResourcePayload : -never; -/* eslint-enable @stylistic/indent */ - -/* eslint-disable @stylistic/indent */ -export type GeneralApiPaginationFilters = -R extends GeneralApiAddressResourceName ? GeneralApiAddressPaginationFilters : -R extends GeneralApiBlockResourceName ? GeneralApiBlockPaginationFilters : -R extends GeneralApiContractResourceName ? GeneralApiContractPaginationFilters : -R extends GeneralApiMiscResourceName ? GeneralApiMiscPaginationFilters : -R extends GeneralApiRollupResourceName ? GeneralApiRollupPaginationFilters : -R extends GeneralApiTokenResourceName ? GeneralApiTokenPaginationFilters : -R extends GeneralApiTxResourceName ? GeneralApiTxPaginationFilters : -never; -/* eslint-enable @stylistic/indent */ - -/* eslint-disable @stylistic/indent */ -export type GeneralApiPaginationSorting = -R extends GeneralApiAddressResourceName ? GeneralApiAddressPaginationSorting : -R extends GeneralApiContractResourceName ? GeneralApiContractPaginationSorting : -R extends GeneralApiMiscResourceName ? GeneralApiMiscPaginationSorting : -R extends GeneralApiRollupResourceName ? GeneralApiRollupPaginationSorting : -R extends GeneralApiTokenResourceName ? GeneralApiTokenPaginationSorting : -never; -/* eslint-enable @stylistic/indent */ diff --git a/client/api/services/general/misc.ts b/client/api/services/general/misc.ts deleted file mode 100644 index 881f03cba40..00000000000 --- a/client/api/services/general/misc.ts +++ /dev/null @@ -1,346 +0,0 @@ -// SPDX-License-Identifier: LicenseRef-Blockscout - -import type { ApiResource } from '../../types'; -import type { DepositsResponse, DepositsCounters, WithdrawalsResponse, WithdrawalsCounters } from 'client/features/chain-variants/beacon-chain/types/api'; -import type { CeloEpochDetails, CeloEpochElectionRewardDetailsResponse, CeloEpochListResponse } from 'client/features/chain-variants/celo/types/api'; -import type { CsvExportItemResponse, CsvExportConfig } from 'client/features/csv-export/types/api'; -import type { Blob } from 'client/features/data-availability/types/api'; -import type { HotContractsFilters, HotContractsResponse, HotContractsSorting } from 'client/features/hot-contracts/types/api'; -import type { - ArbitrumL2TxnBatchesItem, - ArbitrumLatestDepositsResponse, -} from 'client/features/rollup/arbitrum/types/api'; -import type { - OptimisticL2DepositsItem, -} from 'client/features/rollup/optimism/types/api'; -import type { UserOpsResponse, UserOp, UserOpsFilters, UserOpsAccount } from 'client/features/user-ops/types/api'; -import type { Block } from 'client/slices/block/types/api'; -import type { HomeStats } from 'client/slices/home/types/api'; -import type { SearchRedirectResult, SearchResult, SearchResultFilters, SearchResultItem } from 'client/slices/search/types/api'; -import type { - Transaction, -} from 'client/slices/tx/types/api'; -import type { AdvancedFilterParams, AdvancedFilterResponse, AdvancedFilterMethodsResponse } from 'types/api/advancedFilter'; -import type { ChartMarketResponse, ChartSecondaryCoinPriceResponse, ChartTransactionResponse } from 'types/api/charts'; -import type { BackendConfig, BackendVersionConfig, CeloConfig, ContractLanguagesConfig } from 'types/api/configs'; -import type { IndexingStatus } from 'types/api/indexingStatus'; -import type { NovesAccountHistoryResponse, NovesDescribeTxsResponse, NovesResponseData } from 'types/api/noves'; -import type { TxInterpretationResponse } from 'types/api/txInterpretation'; -import type { - ValidatorsStabilityCountersResponse, - ValidatorsStabilityFilters, - ValidatorsStabilityResponse, - ValidatorsStabilitySorting, - ValidatorsBlackfortCountersResponse, - ValidatorsBlackfortResponse, - ValidatorsBlackfortSorting, - ValidatorsZilliqaResponse, - ValidatorZilliqa, -} from 'types/api/validators'; - -export const GENERAL_API_MISC_RESOURCES = { - // WITHDRAWALS - withdrawals: { - path: '/api/v2/withdrawals', - filterFields: [], - paginated: true, - }, - withdrawals_counters: { - path: '/api/v2/withdrawals/counters', - }, - - // DEPOSITS - deposits: { - path: '/api/v2/beacon/deposits', - filterFields: [], - paginated: true, - }, - deposits_counters: { - path: '/api/v2/beacon/deposits/count', - }, - - // APP STATS - stats: { - path: '/api/v2/stats', - headers: { - 'updated-gas-oracle': 'true', - }, - }, - stats_charts_txs: { - path: '/api/v2/stats/charts/transactions', - }, - stats_charts_market: { - path: '/api/v2/stats/charts/market', - }, - stats_charts_secondary_coin_price: { - path: '/api/v2/stats/charts/secondary-coin-market', - }, - stats_hot_contracts: { - path: '/api/v2/stats/hot-smart-contracts', - paginated: true, - filterFields: [ 'scale' as const ], - }, - - // HOMEPAGE - homepage_blocks: { - path: '/api/v2/main-page/blocks', - }, - homepage_optimistic_deposits: { - path: '/api/v2/main-page/optimism-deposits', - }, - homepage_arbitrum_deposits: { - path: '/api/v2/main-page/arbitrum/messages/to-rollup', - }, - homepage_txs: { - path: '/api/v2/main-page/transactions', - }, - homepage_arbitrum_l2_batches: { - path: '/api/v2/main-page/arbitrum/batches/committed', - }, - homepage_txs_watchlist: { - path: '/api/v2/main-page/transactions/watchlist', - }, - homepage_indexing_status: { - path: '/api/v2/main-page/indexing-status', - }, - homepage_zksync_latest_batch: { - path: '/api/v2/main-page/zksync/batches/latest-number', - }, - homepage_arbitrum_latest_batch: { - path: '/api/v2/main-page/arbitrum/batches/latest-number', - }, - - // SEARCH - quick_search: { - path: '/api/v2/search/quick', - filterFields: [ 'q' ], - }, - search: { - path: '/api/v2/search', - filterFields: [ 'q' ], - paginated: true, - }, - search_check_redirect: { - path: '/api/v2/search/check-redirect', - }, - - // NOVES-FI - noves_transaction: { - path: '/api/v2/proxy/3rdparty/noves-fi/transactions/:hash', - pathParams: [ 'hash' as const ], - }, - noves_address_history: { - path: '/api/v2/proxy/3rdparty/noves-fi/addresses/:address/transactions', - pathParams: [ 'address' as const ], - filterFields: [], - paginated: true, - }, - noves_describe_txs: { - path: '/api/v2/proxy/3rdparty/noves-fi/transaction-descriptions', - }, - - // USER OPS - user_ops: { - path: '/api/v2/proxy/account-abstraction/operations', - filterFields: [ 'transaction_hash' as const, 'sender' as const ], - paginated: true, - }, - user_op: { - path: '/api/v2/proxy/account-abstraction/operations/:hash', - pathParams: [ 'hash' as const ], - }, - user_ops_account: { - path: '/api/v2/proxy/account-abstraction/accounts/:hash', - pathParams: [ 'hash' as const ], - }, - user_op_interpretation: { - path: '/api/v2/proxy/account-abstraction/operations/:hash/summary', - pathParams: [ 'hash' as const ], - }, - - // VALIDATORS - validators_stability: { - path: '/api/v2/validators/stability', - filterFields: [ 'address_hash' as const, 'state_filter' as const ], - paginated: true, - }, - validators_stability_counters: { - path: '/api/v2/validators/stability/counters', - }, - validators_blackfort: { - path: '/api/v2/validators/blackfort', - filterFields: [], - paginated: true, - }, - validators_blackfort_counters: { - path: '/api/v2/validators/blackfort/counters', - }, - validators_zilliqa: { - path: '/api/v2/validators/zilliqa', - filterFields: [], - paginated: true, - }, - validator_zilliqa: { - path: '/api/v2/validators/zilliqa/:bls_public_key', - pathParams: [ 'bls_public_key' as const ], - filterFields: [], - }, - - // BLOBS - blob: { - path: '/api/v2/blobs/:hash', - pathParams: [ 'hash' as const ], - }, - - // EPOCHS - epochs_celo: { - path: '/api/v2/celo/epochs', - filterFields: [], - paginated: true, - }, - epoch_celo: { - path: '/api/v2/celo/epochs/:number', - pathParams: [ 'number' as const ], - }, - epoch_celo_election_rewards: { - path: '/api/v2/celo/epochs/:number/election-rewards/:reward_type', - pathParams: [ 'number' as const, 'reward_type' as const ], - filterFields: [], - paginated: true, - }, - - // ADVANCED FILTER - advanced_filter: { - path: '/api/v2/advanced-filters', - filterFields: [ - 'transaction_types' as const, - 'methods' as const, - 'methods_names' as const /* frontend only */, - 'age_from' as const, - 'age_to' as const, - 'age' as const /* frontend only */, - 'from_address_hashes_to_include' as const, - 'from_address_hashes_to_exclude' as const, - 'to_address_hashes_to_include' as const, - 'to_address_hashes_to_exclude' as const, - 'address_relation' as const, - 'amount_from' as const, - 'amount_to' as const, - 'token_contract_address_hashes_to_include' as const, - 'token_contract_symbols_to_include' as const /* frontend only */, - 'token_contract_address_hashes_to_exclude' as const, - 'token_contract_symbols_to_exclude' as const /* frontend only */, - 'block_number' as const, - 'transaction_index' as const, - 'internal_transaction_index' as const, - 'token_transfer_index' as const, - ], - paginated: true, - }, - advanced_filter_methods: { - path: '/api/v2/advanced-filters/methods', - filterFields: [ 'q' as const ], - }, - advanced_filter_csv: { - path: '/api/v2/advanced-filters/csv', - }, - - // CSV EXPORT - csv_exports_item: { - path: '/api/v2/csv-exports/:id', - pathParams: [ 'id' as const ], - }, - - // CONFIGS - config_backend: { - path: '/api/v2/config/backend', - }, - config_backend_version: { - path: '/api/v2/config/backend-version', - }, - config_csv_export: { - path: '/api/v2/config/csv-export', - }, - config_celo: { - path: '/api/v2/config/celo', - }, - config_contract_languages: { - path: '/api/v2/config/smart-contracts/languages', - }, - - // OTHER - api_v2_key: { - path: '/api/v2/key', - }, -} satisfies Record; - -export type GeneralApiMiscResourceName = `general:${ keyof typeof GENERAL_API_MISC_RESOURCES }`; - -/* eslint-disable @stylistic/indent */ -export type GeneralApiMiscResourcePayload = -R extends 'general:stats' ? HomeStats : -R extends 'general:stats_charts_txs' ? ChartTransactionResponse : -R extends 'general:stats_charts_market' ? ChartMarketResponse : -R extends 'general:stats_charts_secondary_coin_price' ? ChartSecondaryCoinPriceResponse : -R extends 'general:stats_hot_contracts' ? HotContractsResponse : -R extends 'general:homepage_blocks' ? Array : -R extends 'general:homepage_txs' ? Array : -R extends 'general:homepage_txs_watchlist' ? Array : -R extends 'general:homepage_optimistic_deposits' ? Array : -R extends 'general:homepage_arbitrum_deposits' ? ArbitrumLatestDepositsResponse : -R extends 'general:homepage_arbitrum_l2_batches' ? { items: Array } : -R extends 'general:homepage_indexing_status' ? IndexingStatus : -R extends 'general:homepage_zksync_latest_batch' ? number : -R extends 'general:homepage_arbitrum_latest_batch' ? number : -R extends 'general:quick_search' ? Array : -R extends 'general:search' ? SearchResult : -R extends 'general:search_check_redirect' ? SearchRedirectResult : -R extends 'general:config_backend' ? BackendConfig : -R extends 'general:config_backend_version' ? BackendVersionConfig : -R extends 'general:config_csv_export' ? CsvExportConfig : -R extends 'general:config_celo' ? CeloConfig : -R extends 'general:config_contract_languages' ? ContractLanguagesConfig : -R extends 'general:blob' ? Blob : -R extends 'general:validators_stability' ? ValidatorsStabilityResponse : -R extends 'general:validators_stability_counters' ? ValidatorsStabilityCountersResponse : -R extends 'general:validators_blackfort' ? ValidatorsBlackfortResponse : -R extends 'general:validators_blackfort_counters' ? ValidatorsBlackfortCountersResponse : -R extends 'general:validators_zilliqa' ? ValidatorsZilliqaResponse : -R extends 'general:validator_zilliqa' ? ValidatorZilliqa : -R extends 'general:epochs_celo' ? CeloEpochListResponse : -R extends 'general:epoch_celo' ? CeloEpochDetails : -R extends 'general:epoch_celo_election_rewards' ? CeloEpochElectionRewardDetailsResponse : -R extends 'general:user_ops' ? UserOpsResponse : -R extends 'general:user_op' ? UserOp : -R extends 'general:user_ops_account' ? UserOpsAccount : -R extends 'general:user_op_interpretation' ? TxInterpretationResponse : -R extends 'general:noves_transaction' ? NovesResponseData : -R extends 'general:noves_address_history' ? NovesAccountHistoryResponse : -R extends 'general:noves_describe_txs' ? NovesDescribeTxsResponse : -R extends 'general:withdrawals' ? WithdrawalsResponse : -R extends 'general:withdrawals_counters' ? WithdrawalsCounters : -R extends 'general:deposits' ? DepositsResponse : -R extends 'general:deposits_counters' ? DepositsCounters : -R extends 'general:advanced_filter' ? AdvancedFilterResponse : -R extends 'general:advanced_filter_methods' ? AdvancedFilterMethodsResponse : -R extends 'general:csv_exports_item' ? CsvExportItemResponse : -never; -/* eslint-enable @stylistic/indent */ - -/* eslint-disable @stylistic/indent */ -export type GeneralApiMiscPaginationFilters = -R extends 'general:stats_hot_contracts' ? HotContractsFilters : -R extends 'general:search' ? SearchResultFilters : -R extends 'general:user_ops' ? UserOpsFilters : -R extends 'general:validators_stability' ? ValidatorsStabilityFilters : -R extends 'general:advanced_filter' ? AdvancedFilterParams : -never; -/* eslint-enable @stylistic/indent */ - -/* eslint-disable @stylistic/indent */ -export type GeneralApiMiscPaginationSorting = -R extends 'general:stats_hot_contracts' ? HotContractsSorting : -R extends 'general:validators_stability' ? ValidatorsStabilitySorting : -R extends 'general:validators_blackfort' ? ValidatorsBlackfortSorting : -never; -/* eslint-enable @stylistic/indent */ diff --git a/client/api/services/general/rollup.ts b/client/api/services/general/rollup.ts deleted file mode 100644 index ea5b8b1affa..00000000000 --- a/client/api/services/general/rollup.ts +++ /dev/null @@ -1,348 +0,0 @@ -// SPDX-License-Identifier: LicenseRef-Blockscout - -import type { ApiResource } from '../../types'; -import type { - AddressMudTables, - AddressMudTablesFilter, - AddressMudRecords, - AddressMudRecordsFilter, - AddressMudRecordsSorting, - AddressMudRecord, - SmartContractMudSystemsResponse, SmartContractMudSystemInfo, MudWorldsResponse } from 'client/features/chain-variants/mud/types/api'; -import type { InteropMessageListResponse } from 'client/features/op-interop/types/api'; -import type { - ArbitrumL2MessagesResponse, - ArbitrumL2TxnBatch, - ArbitrumL2TxnBatchesResponse, - ArbitrumL2BatchTxs, - ArbitrumL2BatchBlocks, - ArbitrumL2TxnWithdrawalsResponse, - ArbitrumL2MessageClaimResponse, -} from 'client/features/rollup/arbitrum/types/api'; -import type { - OptimisticL2DepositsResponse, - OptimisticL2OutputRootsResponse, - OptimisticL2TxnBatchesResponse, - OptimisticL2WithdrawalsResponse, - OptimisticL2DisputeGamesResponse, - OptimismL2TxnBatch, - OptimismL2BatchTxs, - OptimismL2BatchBlocks, -} from 'client/features/rollup/optimism/types/api'; -import type { - ScrollL2BatchesResponse, - ScrollL2TxnBatch, - ScrollL2TxnBatchTxs, - ScrollL2TxnBatchBlocks, - ScrollL2MessagesResponse, -} from 'client/features/rollup/scroll/types/api'; -import type { ShibariumWithdrawalsResponse, ShibariumDepositsResponse } from 'client/features/rollup/shibarium/types/api'; -import type { ZkSyncBatch, ZkSyncBatchesResponse, ZkSyncBatchTxs } from 'client/features/rollup/zk-sync/types/api'; - -export const GENERAL_API_ROLLUP_RESOURCES = { - optimistic_l2_deposits: { - path: '/api/v2/optimism/deposits', - filterFields: [], - paginated: true, - }, - optimistic_l2_deposits_count: { - path: '/api/v2/optimism/deposits/count', - }, - optimistic_l2_withdrawals: { - path: '/api/v2/optimism/withdrawals', - filterFields: [], - paginated: true, - }, - optimistic_l2_withdrawals_count: { - path: '/api/v2/optimism/withdrawals/count', - }, - optimistic_l2_output_roots: { - path: '/api/v2/optimism/output-roots', - filterFields: [], - paginated: true, - }, - optimistic_l2_output_roots_count: { - path: '/api/v2/optimism/output-roots/count', - }, - optimistic_l2_txn_batches: { - path: '/api/v2/optimism/batches', - filterFields: [], - paginated: true, - }, - optimistic_l2_txn_batches_count: { - path: '/api/v2/optimism/batches/count', - }, - optimistic_l2_txn_batch: { - path: '/api/v2/optimism/batches/:number', - pathParams: [ 'number' as const ], - }, - optimistic_l2_txn_batch_celestia: { - path: '/api/v2/optimism/batches/da/celestia/:height/:commitment', - pathParams: [ 'height' as const, 'commitment' as const ], - }, - optimistic_l2_txn_batch_txs: { - path: '/api/v2/transactions/optimism-batch/:number', - pathParams: [ 'number' as const ], - filterFields: [], - paginated: true, - }, - optimistic_l2_txn_batch_blocks: { - path: '/api/v2/blocks/optimism-batch/:number', - pathParams: [ 'number' as const ], - filterFields: [], - paginated: true, - }, - optimistic_l2_dispute_games: { - path: '/api/v2/optimism/games', - filterFields: [], - paginated: true, - }, - optimistic_l2_dispute_games_count: { - path: '/api/v2/optimism/games/count', - }, - - // OPTIMISTIC INTEROP - optimistic_l2_interop_messages: { - path: '/api/v2/optimism/interop/messages', - filterFields: [], - paginated: true, - }, - optimistic_l2_interop_messages_count: { - path: '/api/v2/optimism/interop/messages/count', - }, - - // MUD - mud_worlds: { - path: '/api/v2/mud/worlds', - filterFields: [], - paginated: true, - }, - mud_tables: { - path: '/api/v2/mud/worlds/:hash/tables', - pathParams: [ 'hash' as const ], - filterFields: [ 'q' as const ], - paginated: true, - }, - mud_tables_count: { - path: '/api/v2/mud/worlds/:hash/tables/count', - pathParams: [ 'hash' as const ], - }, - mud_records: { - path: '/api/v2/mud/worlds/:hash/tables/:table_id/records', - pathParams: [ 'hash' as const, 'table_id' as const ], - filterFields: [ 'filter_key0' as const, 'filter_key1' as const ], - paginated: true, - }, - mud_record: { - path: '/api/v2/mud/worlds/:hash/tables/:table_id/records/:record_id', - pathParams: [ 'hash' as const, 'table_id' as const, 'record_id' as const ], - }, - mud_systems: { - path: '/api/v2/mud/worlds/:hash/systems', - pathParams: [ 'hash' as const ], - }, - mud_system_info: { - path: '/api/v2/mud/worlds/:hash/systems/:system_address', - pathParams: [ 'hash' as const, 'system_address' as const ], - }, - - // ARBITRUM - arbitrum_l2_messages: { - path: '/api/v2/arbitrum/messages/:direction', - pathParams: [ 'direction' as const ], - filterFields: [], - paginated: true, - }, - arbitrum_l2_messages_count: { - path: '/api/v2/arbitrum/messages/:direction/count', - pathParams: [ 'direction' as const ], - }, - arbitrum_l2_txn_batches: { - path: '/api/v2/arbitrum/batches', - filterFields: [], - paginated: true, - }, - arbitrum_l2_txn_batches_count: { - path: '/api/v2/arbitrum/batches/count', - }, - arbitrum_l2_txn_batch: { - path: '/api/v2/arbitrum/batches/:number', - pathParams: [ 'number' as const ], - }, - arbitrum_l2_txn_batch_celestia: { - path: '/api/v2/arbitrum/batches/da/celestia/:height/:commitment', - pathParams: [ 'height' as const, 'commitment' as const ], - }, - arbitrum_l2_txn_batch_txs: { - path: '/api/v2/transactions/arbitrum-batch/:number', - pathParams: [ 'number' as const ], - filterFields: [], - paginated: true, - }, - arbitrum_l2_txn_batch_blocks: { - path: '/api/v2/blocks/arbitrum-batch/:number', - pathParams: [ 'number' as const ], - filterFields: [], - paginated: true, - }, - arbitrum_l2_txn_withdrawals: { - path: '/api/v2/arbitrum/messages/withdrawals/:hash', - pathParams: [ 'hash' as const ], - filterFields: [], - }, - arbitrum_l2_message_claim: { - path: '/api/v2/arbitrum/messages/claim/:id', - pathParams: [ 'id' as const ], - filterFields: [], - }, - - // zkSync - zksync_l2_txn_batches: { - path: '/api/v2/zksync/batches', - filterFields: [], - paginated: true, - }, - zksync_l2_txn_batches_count: { - path: '/api/v2/zksync/batches/count', - }, - zksync_l2_txn_batch: { - path: '/api/v2/zksync/batches/:number', - pathParams: [ 'number' as const ], - }, - zksync_l2_txn_batch_txs: { - path: '/api/v2/transactions/zksync-batch/:number', - pathParams: [ 'number' as const ], - filterFields: [], - paginated: true, - }, - - // SHIBARIUM - shibarium_deposits: { - path: '/api/v2/shibarium/deposits', - filterFields: [], - paginated: true, - }, - shibarium_deposits_count: { - path: '/api/v2/shibarium/deposits/count', - }, - shibarium_withdrawals: { - path: '/api/v2/shibarium/withdrawals', - filterFields: [], - paginated: true, - }, - shibarium_withdrawals_count: { - path: '/api/v2/shibarium/withdrawals/count', - }, - - // SCROLL - scroll_l2_deposits: { - path: '/api/v2/scroll/deposits', - filterFields: [], - paginated: true, - }, - scroll_l2_deposits_count: { - path: '/api/v2/scroll/deposits/count', - }, - scroll_l2_withdrawals: { - path: '/api/v2/scroll/withdrawals', - filterFields: [], - paginated: true, - }, - scroll_l2_withdrawals_count: { - path: '/api/v2/scroll/withdrawals/count', - }, - scroll_l2_txn_batches: { - path: '/api/v2/scroll/batches', - filterFields: [], - paginated: true, - }, - scroll_l2_txn_batches_count: { - path: '/api/v2/scroll/batches/count', - }, - scroll_l2_txn_batch: { - path: '/api/v2/scroll/batches/:number', - pathParams: [ 'number' as const ], - }, - scroll_l2_txn_batch_txs: { - path: '/api/v2/transactions/scroll-batch/:number', - pathParams: [ 'number' as const ], - filterFields: [], - paginated: true, - }, - scroll_l2_txn_batch_blocks: { - path: '/api/v2/blocks/scroll-batch/:number', - pathParams: [ 'number' as const ], - filterFields: [], - paginated: true, - }, -} satisfies Record; - -export type GeneralApiRollupResourceName = `general:${ keyof typeof GENERAL_API_ROLLUP_RESOURCES }`; - -/* eslint-disable @stylistic/indent */ -export type GeneralApiRollupResourcePayload = -R extends 'general:optimistic_l2_output_roots' ? OptimisticL2OutputRootsResponse : -R extends 'general:optimistic_l2_withdrawals' ? OptimisticL2WithdrawalsResponse : -R extends 'general:optimistic_l2_deposits' ? OptimisticL2DepositsResponse : -R extends 'general:optimistic_l2_txn_batches' ? OptimisticL2TxnBatchesResponse : -R extends 'general:optimistic_l2_txn_batches_count' ? number : -R extends 'general:optimistic_l2_txn_batch' ? OptimismL2TxnBatch : -R extends 'general:optimistic_l2_txn_batch_celestia' ? OptimismL2TxnBatch : -R extends 'general:optimistic_l2_txn_batch_txs' ? OptimismL2BatchTxs : -R extends 'general:optimistic_l2_txn_batch_blocks' ? OptimismL2BatchBlocks : -R extends 'general:optimistic_l2_dispute_games' ? OptimisticL2DisputeGamesResponse : -R extends 'general:optimistic_l2_output_roots_count' ? number : -R extends 'general:optimistic_l2_withdrawals_count' ? number : -R extends 'general:optimistic_l2_deposits_count' ? number : -R extends 'general:optimistic_l2_dispute_games_count' ? number : -R extends 'general:optimistic_l2_interop_messages' ? InteropMessageListResponse : -R extends 'general:optimistic_l2_interop_messages_count' ? number : -R extends 'general:shibarium_withdrawals' ? ShibariumWithdrawalsResponse : -R extends 'general:shibarium_deposits' ? ShibariumDepositsResponse : -R extends 'general:shibarium_withdrawals_count' ? number : -R extends 'general:shibarium_deposits_count' ? number : -R extends 'general:arbitrum_l2_messages' ? ArbitrumL2MessagesResponse : -R extends 'general:arbitrum_l2_messages_count' ? number : -R extends 'general:arbitrum_l2_txn_batches' ? ArbitrumL2TxnBatchesResponse : -R extends 'general:arbitrum_l2_txn_batches_count' ? number : -R extends 'general:arbitrum_l2_txn_batch' ? ArbitrumL2TxnBatch : -R extends 'general:arbitrum_l2_txn_batch_celestia' ? ArbitrumL2TxnBatch : -R extends 'general:arbitrum_l2_txn_batch_txs' ? ArbitrumL2BatchTxs : -R extends 'general:arbitrum_l2_txn_batch_blocks' ? ArbitrumL2BatchBlocks : -R extends 'general:arbitrum_l2_txn_withdrawals' ? ArbitrumL2TxnWithdrawalsResponse : -R extends 'general:arbitrum_l2_message_claim' ? ArbitrumL2MessageClaimResponse : -R extends 'general:zksync_l2_txn_batches' ? ZkSyncBatchesResponse : -R extends 'general:zksync_l2_txn_batches_count' ? number : -R extends 'general:zksync_l2_txn_batch' ? ZkSyncBatch : -R extends 'general:zksync_l2_txn_batch_txs' ? ZkSyncBatchTxs : -R extends 'general:scroll_l2_txn_batch_txs' ? ScrollL2TxnBatchTxs : -R extends 'general:scroll_l2_txn_batch_blocks' ? ScrollL2TxnBatchBlocks : -R extends 'general:scroll_l2_txn_batches' ? ScrollL2BatchesResponse : -R extends 'general:scroll_l2_txn_batches_count' ? number : -R extends 'general:scroll_l2_txn_batch' ? ScrollL2TxnBatch : -R extends 'general:scroll_l2_deposits' ? ScrollL2MessagesResponse : -R extends 'general:scroll_l2_deposits_count' ? number : -R extends 'general:scroll_l2_withdrawals' ? ScrollL2MessagesResponse : -R extends 'general:scroll_l2_withdrawals_count' ? number : -R extends 'general:mud_worlds' ? MudWorldsResponse : -R extends 'general:mud_tables' ? AddressMudTables : -R extends 'general:mud_tables_count' ? number : -R extends 'general:mud_records' ? AddressMudRecords : -R extends 'general:mud_record' ? AddressMudRecord : -R extends 'general:mud_systems' ? SmartContractMudSystemsResponse : -R extends 'general:mud_system_info' ? SmartContractMudSystemInfo : -never; -/* eslint-enable @stylistic/indent */ - -/* eslint-disable @stylistic/indent */ -export type GeneralApiRollupPaginationFilters = -R extends 'general:mud_tables' ? AddressMudTablesFilter : -R extends 'general:mud_records' ? AddressMudRecordsFilter : -never; -/* eslint-enable @stylistic/indent */ - -/* eslint-disable @stylistic/indent */ -export type GeneralApiRollupPaginationSorting = -R extends 'general:mud_records' ? AddressMudRecordsSorting : -never; -/* eslint-enable @stylistic/indent */ diff --git a/client/api/services/general/token.ts b/client/api/services/general/token.ts deleted file mode 100644 index fad18adbc89..00000000000 --- a/client/api/services/general/token.ts +++ /dev/null @@ -1,127 +0,0 @@ -// SPDX-License-Identifier: LicenseRef-Blockscout - -import type { ApiResource } from '../../types'; -import type { TokenTransferResponse, TokenTransferFilters } from 'client/slices/token-transfer/types/api'; -import type { - TokenCounters, - TokenInfo, - TokenHolders, - TokenInventoryResponse, - TokenInstance, - TokenInstanceTransfersCount, - TokenInventoryFilters, - TokensResponse, TokensFilters, TokensSorting, TokenInstanceTransferResponse, TokensBridgedFilters } from 'client/slices/token/types/api'; - -export const GENERAL_API_TOKEN_RESOURCES = { - // TOKEN - token: { - path: '/api/v2/tokens/:hash', - pathParams: [ 'hash' as const ], - }, - token_counters: { - path: '/api/v2/tokens/:hash/counters', - pathParams: [ 'hash' as const ], - }, - token_holders: { - path: '/api/v2/tokens/:hash/holders', - pathParams: [ 'hash' as const ], - filterFields: [], - paginated: true, - }, - token_transfers: { - path: '/api/v2/tokens/:hash/transfers', - pathParams: [ 'hash' as const ], - filterFields: [], - paginated: true, - }, - token_inventory: { - path: '/api/v2/tokens/:hash/instances', - pathParams: [ 'hash' as const ], - filterFields: [ 'holder_address_hash' as const ], - paginated: true, - }, - tokens: { - path: '/api/v2/tokens', - filterFields: [ 'q' as const, 'type' as const ], - paginated: true, - }, - tokens_bridged: { - path: '/api/v2/tokens/bridged', - filterFields: [ 'q' as const, 'chain_ids' as const ], - paginated: true, - }, - token_csv_export_holders: { - path: '/api/v2/tokens/:hash/holders/csv', - pathParams: [ 'hash' as const ], - }, - - // TOKEN INSTANCE - token_instance: { - path: '/api/v2/tokens/:hash/instances/:id', - pathParams: [ 'hash' as const, 'id' as const ], - }, - token_instance_transfers_count: { - path: '/api/v2/tokens/:hash/instances/:id/transfers-count', - pathParams: [ 'hash' as const, 'id' as const ], - }, - token_instance_transfers: { - path: '/api/v2/tokens/:hash/instances/:id/transfers', - pathParams: [ 'hash' as const, 'id' as const ], - filterFields: [], - paginated: true, - }, - token_instance_holders: { - path: '/api/v2/tokens/:hash/instances/:id/holders', - pathParams: [ 'hash' as const, 'id' as const ], - filterFields: [], - paginated: true, - }, - token_instance_refresh_metadata: { - path: '/api/v2/tokens/:hash/instances/:id/refetch-metadata', - pathParams: [ 'hash' as const, 'id' as const ], - filterFields: [], - }, - - // TOKEN TRANSFERS - token_transfers_all: { - path: '/api/v2/token-transfers', - filterFields: [ 'type' as const ], - paginated: true, - }, -} satisfies Record; - -export type GeneralApiTokenResourceName = `general:${ keyof typeof GENERAL_API_TOKEN_RESOURCES }`; - -/* eslint-disable @stylistic/indent */ -export type GeneralApiTokenResourcePayload = -R extends 'general:token' ? TokenInfo : -R extends 'general:token_counters' ? TokenCounters : -R extends 'general:token_transfers' ? TokenTransferResponse : -R extends 'general:token_holders' ? TokenHolders : -R extends 'general:token_instance' ? TokenInstance : -R extends 'general:token_instance_transfers_count' ? TokenInstanceTransfersCount : -R extends 'general:token_instance_transfers' ? TokenInstanceTransferResponse : -R extends 'general:token_instance_holders' ? TokenHolders : -R extends 'general:token_inventory' ? TokenInventoryResponse : -R extends 'general:tokens' ? TokensResponse : -R extends 'general:tokens_bridged' ? TokensResponse : -R extends 'general:token_transfers_all' ? TokenTransferResponse : -never; -/* eslint-enable @stylistic/indent */ - -/* eslint-disable @stylistic/indent */ -export type GeneralApiTokenPaginationFilters = -R extends 'general:token_transfers' ? TokenTransferFilters : -R extends 'general:token_inventory' ? TokenInventoryFilters : -R extends 'general:tokens' ? TokensFilters : -R extends 'general:tokens_bridged' ? TokensBridgedFilters : -R extends 'general:token_transfers_all' ? TokenTransferFilters : -never; -/* eslint-enable @stylistic/indent */ - -/* eslint-disable @stylistic/indent */ -export type GeneralApiTokenPaginationSorting = -R extends 'general:tokens' ? TokensSorting : -R extends 'general:tokens_bridged' ? TokensSorting : -never; -/* eslint-enable @stylistic/indent */ diff --git a/client/api/services/general/tx.ts b/client/api/services/general/tx.ts deleted file mode 100644 index b6a0b50eacf..00000000000 --- a/client/api/services/general/tx.ts +++ /dev/null @@ -1,139 +0,0 @@ -// SPDX-License-Identifier: LicenseRef-Blockscout - -import type { ApiResource } from '../../types'; -import type { TransactionsResponseWatchlist } from 'client/features/account/types/api'; -import type { TransactionsResponseWithBlobs, TxsWithBlobsFilters, TxBlobs } from 'client/features/data-availability/types/api'; -import type { InternalTransactionFilters, InternalTransactionsResponse } from 'client/slices/internal-tx/types/api'; -import type { LogsResponseTx } from 'client/slices/log/types/api'; -import type { TokenTransferResponse, TokenTransferFilters } from 'client/slices/token-transfer/types/api'; -import type { - TransactionsResponseValidated, - TransactionsResponsePending, - Transaction, - TransactionsStats, - TxsFilters, - TxStateChanges, - TxRawTracesResponse, -} from 'client/slices/tx/types/api'; -import type { FheOperationsResponse } from 'types/api/fheOperations'; -import type { TxInterpretationResponse } from 'types/api/txInterpretation'; - -export const GENERAL_API_TX_RESOURCES = { - txs_stats: { - path: '/api/v2/transactions/stats', - }, - txs_validated: { - path: '/api/v2/transactions', - filterFields: [ 'filter' as const, 'type' as const, 'method' as const ], - paginated: true, - }, - txs_pending: { - path: '/api/v2/transactions', - filterFields: [ 'filter' as const, 'type' as const, 'method' as const ], - paginated: true, - }, - txs_with_blobs: { - path: '/api/v2/transactions', - filterFields: [ 'type' as const ], - paginated: true, - }, - txs_watchlist: { - path: '/api/v2/transactions/watchlist', - filterFields: [ ], - paginated: true, - }, - txs_execution_node: { - path: '/api/v2/transactions/execution-node/:hash', - pathParams: [ 'hash' as const ], - filterFields: [ ], - paginated: true, - }, - tx: { - path: '/api/v2/transactions/:hash', - pathParams: [ 'hash' as const ], - }, - tx_internal_txs: { - path: '/api/v2/transactions/:hash/internal-transactions', - pathParams: [ 'hash' as const ], - filterFields: [ ], - paginated: true, - }, - tx_logs: { - path: '/api/v2/transactions/:hash/logs', - pathParams: [ 'hash' as const ], - filterFields: [ ], - paginated: true, - }, - tx_fhe_operations: { - path: '/api/v2/transactions/:hash/fhe-operations', - pathParams: [ 'hash' as const ], - }, - - tx_token_transfers: { - path: '/api/v2/transactions/:hash/token-transfers', - pathParams: [ 'hash' as const ], - filterFields: [ 'type' as const ], - paginated: true, - }, - tx_raw_trace: { - path: '/api/v2/transactions/:hash/raw-trace', - pathParams: [ 'hash' as const ], - }, - tx_state_changes: { - path: '/api/v2/transactions/:hash/state-changes', - pathParams: [ 'hash' as const ], - filterFields: [], - paginated: true, - }, - tx_blobs: { - path: '/api/v2/transactions/:hash/blobs', - pathParams: [ 'hash' as const ], - paginated: true, - }, - tx_interpretation: { - path: '/api/v2/transactions/:hash/summary', - pathParams: [ 'hash' as const ], - }, - tx_external_transactions: { - path: '/api/v2/transactions/:hash/external-transactions', - pathParams: [ 'hash' as const ], - }, - internal_txs: { - path: '/api/v2/internal-transactions', - paginated: true, - filterFields: [ 'transaction_hash' as const ], - }, -} satisfies Record; - -export type GeneralApiTxResourceName = `general:${ keyof typeof GENERAL_API_TX_RESOURCES }`; - -/* eslint-disable @stylistic/indent */ -export type GeneralApiTxResourcePayload = -R extends 'general:txs_stats' ? TransactionsStats : -R extends 'general:txs_validated' ? TransactionsResponseValidated : -R extends 'general:txs_pending' ? TransactionsResponsePending : -R extends 'general:txs_with_blobs' ? TransactionsResponseWithBlobs : -R extends 'general:txs_watchlist' ? TransactionsResponseWatchlist : -R extends 'general:txs_execution_node' ? TransactionsResponseValidated : -R extends 'general:tx_internal_txs' ? InternalTransactionsResponse : -R extends 'general:tx' ? Transaction : -R extends 'general:tx_logs' ? LogsResponseTx : -R extends 'general:tx_token_transfers' ? TokenTransferResponse : -R extends 'general:tx_fhe_operations' ? FheOperationsResponse : -R extends 'general:tx_raw_trace' ? TxRawTracesResponse : -R extends 'general:tx_state_changes' ? TxStateChanges : -R extends 'general:tx_blobs' ? TxBlobs : -R extends 'general:tx_interpretation' ? TxInterpretationResponse : -R extends 'general:tx_external_transactions' ? Array : -R extends 'general:internal_txs' ? InternalTransactionsResponse : -never; -/* eslint-enable @stylistic/indent */ - -/* eslint-disable @stylistic/indent */ -export type GeneralApiTxPaginationFilters = -R extends 'general:txs_validated' | 'general:txs_pending' ? TxsFilters : -R extends 'general:txs_with_blobs' ? TxsWithBlobsFilters : -R extends 'general:tx_token_transfers' ? TokenTransferFilters : -R extends 'general:internal_txs' ? InternalTransactionFilters : -never; -/* eslint-enable @stylistic/indent */ diff --git a/client/api/services/general/v1.ts b/client/api/services/general/v1.ts deleted file mode 100644 index a076433bdbb..00000000000 --- a/client/api/services/general/v1.ts +++ /dev/null @@ -1,21 +0,0 @@ -// SPDX-License-Identifier: LicenseRef-Blockscout - -import type { ApiResource } from '../../types'; -import type { BlockCountdownResponse } from 'client/slices/block/types/api'; - -export const GENERAL_API_V1_RESOURCES = { - graphql: { - path: '/api/v1/graphql', - }, - block_countdown: { - path: '/api', - }, -} satisfies Record; - -export type GeneralApiV1ResourceName = `general:${ keyof typeof GENERAL_API_V1_RESOURCES }`; - -/* eslint-disable @stylistic/indent */ -export type GeneralApiV1ResourcePayload = -R extends 'general:block_countdown' ? BlockCountdownResponse : -never; -/* eslint-enable @stylistic/indent */ diff --git a/client/api/socket/types.ts b/client/api/socket/types.ts deleted file mode 100644 index 8140d9d6715..00000000000 --- a/client/api/socket/types.ts +++ /dev/null @@ -1,92 +0,0 @@ -// SPDX-License-Identifier: LicenseRef-Blockscout - -import type { Channel } from 'phoenix'; - -import type * as multichain from '@blockscout/multichain-aggregator-types'; -import type * as zetaChainCCTXType from '@blockscout/zetachain-cctx-types'; -import type { NewArbitrumBatchSocketResponse } from 'client/features/rollup/arbitrum/types/api'; -import type { AddressCoinBalanceHistoryItem, AddressTokensBalancesSocketMessage } from 'client/slices/address/types/api'; -import type { NewBlockSocketResponse } from 'client/slices/block/types/api'; -import type { SmartContractVerificationResponse } from 'client/slices/contract/types/api'; -import type { TokenTransfer } from 'client/slices/token-transfer/types/api'; -import type { TokenInstanceMetadataSocketMessage } from 'client/slices/token/types/api'; -import type { Transaction, TxRawTracesResponse } from 'client/slices/tx/types/api'; - -export type SocketMessageParams = SocketMessage.NewBlock | -SocketMessage.NewBlockMultichain | -SocketMessage.BlocksIndexStatus | -SocketMessage.InternalTxsIndexStatus | -SocketMessage.TxStatusUpdate | -SocketMessage.TxRawTrace | -SocketMessage.NewTx | -SocketMessage.NewInteropMessage | -SocketMessage.NewPendingTx | -SocketMessage.NewOptimisticDeposits | -SocketMessage.NewArbitrumDeposits | -SocketMessage.AddressBalance | -SocketMessage.AddressCurrentCoinBalance | -SocketMessage.AddressTokenBalance | -SocketMessage.AddressTokenBalancesErc20 | -SocketMessage.AddressTokenBalancesErc721 | -SocketMessage.AddressTokenBalancesErc1155 | -SocketMessage.AddressTokenBalancesErc404 | -SocketMessage.AddressCoinBalance | -SocketMessage.AddressTxs | -SocketMessage.AddressTxsPending | -SocketMessage.AddressTokenTransfer | -SocketMessage.AddressChangedBytecode | -SocketMessage.AddressFetchedBytecode | -SocketMessage.EthBytecodeDbLookupStarted | -SocketMessage.SmartContractWasVerified | -SocketMessage.SmartContractWasNotVerified | -SocketMessage.TokenTransfers | -SocketMessage.TokenTotalSupply | -SocketMessage.TokenInstanceMetadataFetched | -SocketMessage.ContractVerification | -SocketMessage.NewArbitrumL2Batch | -SocketMessage.NewZetaChainCCTXs | -SocketMessage.Unknown; - -interface SocketMessageParamsGeneric { - channel: Channel | undefined; - event: Event; - handler: (payload: Payload) => void; -} - -export namespace SocketMessage { - export type NewBlock = SocketMessageParamsGeneric<'new_block', NewBlockSocketResponse>; - export type NewBlockMultichain = SocketMessageParamsGeneric<'new_blocks', Array<{ block_number: number; chain_id: number }>>; - export type BlocksIndexStatus = SocketMessageParamsGeneric<'index_status', { finished: boolean; ratio: string }>; - export type InternalTxsIndexStatus = SocketMessageParamsGeneric<'index_status', { finished: boolean; ratio: string }>; - export type TxStatusUpdate = SocketMessageParamsGeneric<'collated', NewBlockSocketResponse>; - export type TxRawTrace = SocketMessageParamsGeneric<'raw_trace', TxRawTracesResponse>; - export type NewTx = SocketMessageParamsGeneric<'transaction', { transaction: number }>; - export type NewInteropMessage = SocketMessageParamsGeneric<'new_messages', Array>; - export type NewPendingTx = SocketMessageParamsGeneric<'pending_transaction', { pending_transaction: number }>; - export type NewOptimisticDeposits = SocketMessageParamsGeneric<'new_optimism_deposits', { deposits: number }>; - export type NewArbitrumDeposits = SocketMessageParamsGeneric<'new_messages_to_rollup_amount', { new_messages_to_rollup_amount: number }>; - export type AddressBalance = SocketMessageParamsGeneric<'balance', { balance: string; block_number: number; exchange_rate: string }>; - export type AddressCurrentCoinBalance = - SocketMessageParamsGeneric<'current_coin_balance', { coin_balance: string; block_number: number; exchange_rate: string }>; - export type AddressTokenBalance = SocketMessageParamsGeneric<'token_balance', { block_number: number }>; - export type AddressTokenBalancesErc20 = SocketMessageParamsGeneric<'updated_token_balances_erc_20', AddressTokensBalancesSocketMessage>; - export type AddressTokenBalancesErc721 = SocketMessageParamsGeneric<'updated_token_balances_erc_721', AddressTokensBalancesSocketMessage>; - export type AddressTokenBalancesErc1155 = SocketMessageParamsGeneric<'updated_token_balances_erc_1155', AddressTokensBalancesSocketMessage>; - export type AddressTokenBalancesErc404 = SocketMessageParamsGeneric<'updated_token_balances_erc_404', AddressTokensBalancesSocketMessage>; - export type AddressCoinBalance = SocketMessageParamsGeneric<'coin_balance', { coin_balance: AddressCoinBalanceHistoryItem }>; - export type AddressTxs = SocketMessageParamsGeneric<'transaction', { transactions: Array }>; - export type AddressTxsPending = SocketMessageParamsGeneric<'pending_transaction', { transactions: Array }>; - export type AddressTokenTransfer = SocketMessageParamsGeneric<'token_transfer', { token_transfers: Array }>; - export type AddressChangedBytecode = SocketMessageParamsGeneric<'changed_bytecode', Record>; - export type AddressFetchedBytecode = SocketMessageParamsGeneric<'fetched_bytecode', { fetched_bytecode: string }>; - export type EthBytecodeDbLookupStarted = SocketMessageParamsGeneric<'eth_bytecode_db_lookup_started', Record>; - export type SmartContractWasVerified = SocketMessageParamsGeneric<'smart_contract_was_verified', Record>; - export type SmartContractWasNotVerified = SocketMessageParamsGeneric<'smart_contract_was_not_verified', Record>; - export type TokenTransfers = SocketMessageParamsGeneric<'token_transfer', { token_transfer: number }>; - export type TokenTotalSupply = SocketMessageParamsGeneric<'total_supply', { total_supply: number }>; - export type TokenInstanceMetadataFetched = SocketMessageParamsGeneric<'fetched_token_instance_metadata', TokenInstanceMetadataSocketMessage>; - export type ContractVerification = SocketMessageParamsGeneric<'verification_result', SmartContractVerificationResponse>; - export type NewArbitrumL2Batch = SocketMessageParamsGeneric<'new_arbitrum_batch', NewArbitrumBatchSocketResponse>; - export type NewZetaChainCCTXs = SocketMessageParamsGeneric<'new_cctxs', Array>; - export type Unknown = SocketMessageParamsGeneric; -} diff --git a/client/api/types.ts b/client/api/types.ts deleted file mode 100644 index 694524f8b70..00000000000 --- a/client/api/types.ts +++ /dev/null @@ -1,16 +0,0 @@ -// SPDX-License-Identifier: LicenseRef-Blockscout - -export type ApiName = -'general' | 'admin' | 'bens' | 'contractInfo' | 'clusters' | 'external' | 'interchainIndexer' | -'metadata' | 'multichainAggregator' | 'multichainStats' | 'rewards' | 'stats' | 'tac' | -'userOps' | 'visualize' | 'zetachain'; - -export interface ApiResource { - path: string; - pathParams?: Array; - filterFields?: Array; - paginated?: boolean; - headers?: RequestInit['headers']; -} - -export type IsPaginated = R extends { paginated: true } ? true : false; diff --git a/client/features/account/components/auth-modal/types.ts b/client/features/account/components/auth-modal/types.ts deleted file mode 100644 index 4b58f948018..00000000000 --- a/client/features/account/components/auth-modal/types.ts +++ /dev/null @@ -1,38 +0,0 @@ -// SPDX-License-Identifier: LicenseRef-Blockscout - -import type { UserInfo } from 'client/features/account/types/api'; - -export type ScreenSuccess = { - type: 'success_email'; - email: string; - profile: UserInfo; - isAuth?: boolean; -} | { - type: 'success_wallet'; - address: string; - profile: UserInfo; - isAuth?: boolean; - rewardsToken?: string; -}; -export type Screen = { - type: 'select_method'; -} | { - type: 'connect_wallet'; - isAuth?: boolean; - loginToRewards?: boolean; -} | { - type: 'email'; - isAuth?: boolean; -} | { - type: 'otp_code'; - email: string; - isAuth?: boolean; -} | ScreenSuccess; - -export interface EmailFormFields { - email: string; -} - -export interface OtpCodeFormFields { - code: Array; -} diff --git a/client/features/account/components/user-profile/UserProfileDesktop.tsx b/client/features/account/components/user-profile/UserProfileDesktop.tsx deleted file mode 100644 index 6e94cc03191..00000000000 --- a/client/features/account/components/user-profile/UserProfileDesktop.tsx +++ /dev/null @@ -1,36 +0,0 @@ -// SPDX-License-Identifier: LicenseRef-Blockscout - -import { type ButtonProps } from '@chakra-ui/react'; -import dynamic from 'next/dynamic'; - -import UserProfileAuth0 from 'client/features/account/components/user-profile/auth0/UserProfileDesktop'; -import UserWalletDesktop from 'client/features/account/components/user-profile/wallet/UserWalletDesktop'; - -import config from 'configs/app'; - -const UserProfileDynamic = dynamic(() => import('client/features/account/components/user-profile/dynamic/UserProfile'), { ssr: false }); - -interface Props { - buttonSize?: ButtonProps['size']; - buttonVariant?: ButtonProps['variant']; -} - -const UserProfileDesktop = ({ buttonSize, buttonVariant = 'header' }: Props) => { - const accountFeature = config.features.account; - if (accountFeature.isEnabled) { - switch (accountFeature.authProvider) { - case 'auth0': - return ; - case 'dynamic': - return ; - default: - return null; - } - } - if (config.features.blockchainInteraction.isEnabled) { - return ; - } - return null; -}; - -export default UserProfileDesktop; diff --git a/client/features/account/components/user-profile/auth0/UserProfileButton.tsx b/client/features/account/components/user-profile/auth0/UserProfileButton.tsx deleted file mode 100644 index 85dbf5a5fc1..00000000000 --- a/client/features/account/components/user-profile/auth0/UserProfileButton.tsx +++ /dev/null @@ -1,104 +0,0 @@ -// SPDX-License-Identifier: LicenseRef-Blockscout - -import type { ButtonProps } from '@chakra-ui/react'; -import { Box, HStack } from '@chakra-ui/react'; -import type { UseQueryResult } from '@tanstack/react-query'; -import React, { useCallback, useState } from 'react'; - -import type { UserInfo } from 'client/features/account/types/api'; - -import { getUserHandle } from 'client/features/account/utils/user-handle'; -import useWeb3AccountWithDomain from 'client/features/connect-wallet/hooks/useAccountWithDomain'; - -import useIsMobile from 'client/shared/hooks/useIsMobile'; -import shortenString from 'client/shared/text/shorten-string'; - -import { useMarketplaceContext } from 'lib/contexts/marketplace'; -import { Button } from 'toolkit/chakra/button'; -import { Tooltip } from 'toolkit/chakra/tooltip'; -import IconSvg from 'ui/shared/IconSvg'; - -import UserIdenticon from '../UserIdenticon'; - -interface Props { - profileQuery: UseQueryResult; - size?: ButtonProps['size']; - variant?: ButtonProps['variant']; - onClick: () => void; - isPending?: boolean; -} - -const UserProfileButton = ({ profileQuery, size, variant, onClick, isPending, ...rest }: Props, ref: React.ForwardedRef) => { - const [ isFetched, setIsFetched ] = useState(false); - const isMobile = useIsMobile(); - - const { data, isLoading } = profileQuery; - const web3AccountWithDomain = useWeb3AccountWithDomain(true); - const { isAutoConnectDisabled } = useMarketplaceContext(); - - React.useEffect(() => { - if (!isLoading) { - setIsFetched(true); - } - }, [ isLoading ]); - - const handleFocus = useCallback((e: React.FocusEvent) => { - e.preventDefault(); - }, []); - - const isButtonLoading = isPending || !isFetched || web3AccountWithDomain.isLoading; - const dataExists = !isButtonLoading && (Boolean(data) || Boolean(web3AccountWithDomain.address)); - - const content = (() => { - if (web3AccountWithDomain.address && !isButtonLoading) { - return ( - - - - { web3AccountWithDomain.domain || shortenString(web3AccountWithDomain.address) } - - - ); - } - - if (!data || isButtonLoading) { - return 'Log in'; - } - - return ( - - - { data.email ? getUserHandle(data.email) : 'My profile' } - - ); - })(); - - return ( - Sign in to My Account to add tags,
create watchlists, access API keys and more } - disabled={ isMobile || isLoading || Boolean(data) } - openDelay={ 500 } - disableOnMobile - > - - - -
- ); -}; - -export default React.forwardRef(UserProfileButton); diff --git a/client/features/account/components/user-profile/auth0/UserProfileDesktop.tsx b/client/features/account/components/user-profile/auth0/UserProfileDesktop.tsx deleted file mode 100644 index 2ac007a3cc4..00000000000 --- a/client/features/account/components/user-profile/auth0/UserProfileDesktop.tsx +++ /dev/null @@ -1,116 +0,0 @@ -// SPDX-License-Identifier: LicenseRef-Blockscout - -import { type ButtonProps } from '@chakra-ui/react'; -import { useRouter } from 'next/router'; -import React from 'react'; - -import type { Screen } from 'client/features/account/components/auth-modal/types'; - -import AuthModal from 'client/features/account/components/auth-modal/AuthModal'; -import useProfileQuery from 'client/features/account/hooks/useProfileQuery'; -import useAccount from 'client/features/connect-wallet/hooks/useAccount'; - -import * as mixpanel from 'client/shared/analytics/mixpanel'; - -import config from 'configs/app'; -import { PopoverBody, PopoverContent, PopoverRoot, PopoverTrigger } from 'toolkit/chakra/popover'; -import { useDisclosure } from 'toolkit/hooks/useDisclosure'; - -import UserProfileButton from './UserProfileButton'; -import UserProfileContent from './UserProfileContent'; - -interface Props { - buttonSize?: ButtonProps['size']; - buttonVariant?: ButtonProps['variant']; -} - -const initialScreen = { - type: config.features.blockchainInteraction.isEnabled ? 'select_method' as const : 'email' as const, -}; - -const UserProfileDesktop = ({ buttonSize, buttonVariant = 'header' }: Props) => { - const [ authInitialScreen, setAuthInitialScreen ] = React.useState(initialScreen); - const router = useRouter(); - - const authModal = useDisclosure(); - const profileMenu = useDisclosure(); - - const profileQuery = useProfileQuery(); - const { address: web3Address } = useAccount(); - - const handleProfileButtonClick = React.useCallback(() => { - if (profileQuery.data || web3Address) { - mixpanel.logEvent(mixpanel.EventTypes.ACCOUNT_ACCESS, { Action: 'Dropdown open' }); - profileMenu.onOpen(); - return; - } - - if (router.pathname === '/apps/[id]' && config.features.blockchainInteraction.isEnabled) { - setAuthInitialScreen({ type: 'connect_wallet', loginToRewards: true }); - } - - authModal.onOpen(); - }, [ profileQuery.data, router.pathname, authModal, profileMenu, web3Address ]); - - const handleAddEmailClick = React.useCallback(() => { - setAuthInitialScreen({ type: 'email', isAuth: true }); - authModal.onOpen(); - profileMenu.onClose(); - }, [ authModal, profileMenu ]); - - const handleAddAddressClick = React.useCallback(() => { - setAuthInitialScreen({ type: 'connect_wallet', isAuth: true, loginToRewards: true }); - authModal.onOpen(); - profileMenu.onClose(); - }, [ authModal, profileMenu ]); - - const handleAuthModalClose = React.useCallback(() => { - setAuthInitialScreen(initialScreen); - authModal.onClose(); - }, [ authModal ]); - - const handleLoginClick = React.useCallback(() => { - authModal.onOpen(); - profileMenu.onClose(); - }, [ authModal, profileMenu ]); - - const handleProfileMenuOpenChange = React.useCallback(({ open }: { open: boolean }) => { - !open && profileMenu.onOpenChange({ open }); - }, [ profileMenu ]); - - return ( - <> - - - - - { (profileQuery.data || web3Address) && profileMenu.open && ( - - - - - - ) } - - { authModal.open && ( - - ) } - - ); -}; - -export default React.memo(UserProfileDesktop); diff --git a/client/features/account/components/user-profile/dynamic/UserProfileButton.tsx b/client/features/account/components/user-profile/dynamic/UserProfileButton.tsx deleted file mode 100644 index 4309167381a..00000000000 --- a/client/features/account/components/user-profile/dynamic/UserProfileButton.tsx +++ /dev/null @@ -1,61 +0,0 @@ -// SPDX-License-Identifier: LicenseRef-Blockscout - -import { Box, HStack } from '@chakra-ui/react'; -import React from 'react'; - -import { getUserHandle } from 'client/features/account/utils/user-handle'; -import useAccountWithDomain from 'client/features/connect-wallet/hooks/useAccountWithDomain'; - -import shortenString from 'client/shared/text/shorten-string'; - -import { useMarketplaceContext } from 'lib/contexts/marketplace'; -import { Button, type ButtonProps } from 'toolkit/chakra/button'; -import IconSvg from 'ui/shared/IconSvg'; - -import UserIdenticon from '../UserIdenticon'; - -interface Props extends ButtonProps { - email?: string; -} - -const UserProfileButton = ({ selected, email, ...rest }: Props) => { - const { isAutoConnectDisabled } = useMarketplaceContext(); - const accountWithDomain = useAccountWithDomain(true); - - const isLoading = accountWithDomain.isLoading; - - const content = (() => { - if (selected && !isLoading) { - return accountWithDomain.address ? ( - - - - { accountWithDomain.domain || shortenString(accountWithDomain.address) } - - - ) : ( - - - { email ? getUserHandle(email) : 'My profile' } - - ); - } - - return 'Log in'; - })(); - - return ( - - ); -}; - -export default React.memo(UserProfileButton); diff --git a/client/features/account/hooks/useGetCsrfToken.tsx b/client/features/account/hooks/useGetCsrfToken.tsx deleted file mode 100644 index 1032399fcfb..00000000000 --- a/client/features/account/hooks/useGetCsrfToken.tsx +++ /dev/null @@ -1,41 +0,0 @@ -// SPDX-License-Identifier: LicenseRef-Blockscout - -import { useQuery } from '@tanstack/react-query'; - -import buildUrl from 'client/api/build-url'; -import { getResourceKey } from 'client/api/hooks/useApiQuery'; -import useFetch from 'client/api/hooks/useFetch'; -import isNeedProxy from 'client/api/is-need-proxy'; - -import * as cookies from 'client/shared/storage/cookies'; - -export default function useGetCsrfToken() { - const nodeApiFetch = useFetch(); - - return useQuery({ - queryKey: getResourceKey('general:csrf'), - queryFn: async() => { - if (!isNeedProxy()) { - const url = buildUrl('general:csrf'); - const apiResponse = await fetch(url, { credentials: 'include' }); - const csrfFromHeader = apiResponse.headers.get('x-bs-account-csrf'); - - if (!csrfFromHeader) { - // I am not sure should we log this error or not - // so I commented it out for now - // rollbar?.warn('Client fetch failed', { - // resource: 'csrf', - // status_code: 500, - // status_text: 'Unable to obtain csrf token from header', - // }); - return; - } - - return { token: csrfFromHeader }; - } - - return nodeApiFetch('/node-api/csrf'); - }, - enabled: Boolean(cookies.get(cookies.NAMES.API_TOKEN)), - }); -} diff --git a/client/features/account/hooks/useLinkEmailDynamic.ts b/client/features/account/hooks/useLinkEmailDynamic.ts deleted file mode 100644 index af36e100749..00000000000 --- a/client/features/account/hooks/useLinkEmailDynamic.ts +++ /dev/null @@ -1,34 +0,0 @@ -// SPDX-License-Identifier: LicenseRef-Blockscout - -import { useUserUpdateRequest } from '@dynamic-labs/sdk-react-core'; -import { useQueryClient } from '@tanstack/react-query'; -import React from 'react'; - -import type { UserInfo } from 'client/features/account/types/api'; - -import { getResourceKey } from 'client/api/hooks/useApiQuery'; - -import * as mixpanel from 'client/shared/analytics/mixpanel'; - -export default function useLinkEmailDynamic(): () => void { - const { updateUserWithModal } = useUserUpdateRequest(); - const queryClient = useQueryClient(); - - return React.useCallback(() => { - mixpanel.logEvent(mixpanel.EventTypes.ACCOUNT_LINK_INFO, { - Status: 'Started', - Type: 'Email', - Source: 'Profile dropdown', - }); - updateUserWithModal([ 'email' ]).then((fields) => { - queryClient.setQueryData(getResourceKey('general:user_info'), (prevData: UserInfo | undefined) => { - return { ...prevData, email: fields.email }; - }); - mixpanel.logEvent(mixpanel.EventTypes.ACCOUNT_LINK_INFO, { - Status: 'Finished', - Type: 'Email', - Source: 'Profile dropdown', - }); - }); - }, [ queryClient, updateUserWithModal ]); -} diff --git a/client/features/account/hooks/useLogout.ts b/client/features/account/hooks/useLogout.ts deleted file mode 100644 index 1d121a22108..00000000000 --- a/client/features/account/hooks/useLogout.ts +++ /dev/null @@ -1,69 +0,0 @@ -// SPDX-License-Identifier: LicenseRef-Blockscout - -import { useQueryClient } from '@tanstack/react-query'; -import { useRouter } from 'next/router'; -import React from 'react'; - -import type { Route } from 'nextjs-routes'; - -import useApiFetch from 'client/api/hooks/useApiFetch'; -import { getResourceKey } from 'client/api/hooks/useApiQuery'; - -import * as mixpanel from 'client/shared/analytics/mixpanel'; -import * as cookies from 'client/shared/storage/cookies'; - -import config from 'configs/app'; -import { useRewardsContext } from 'lib/contexts/rewards'; -import { toaster } from 'toolkit/chakra/toaster'; - -const PROTECTED_ROUTES: Array = [ - '/account/api-key', - '/account/custom-abi', - '/account/merits', - '/account/tag-address', - '/account/verified-addresses', - '/account/watchlist', - '/auth/profile', -]; - -export default function useLogout() { - const router = useRouter(); - const queryClient = useQueryClient(); - const apiFetch = useApiFetch(); - const { logout: rewardsLogout } = useRewardsContext(); - - return React.useCallback(async() => { - try { - await apiFetch('general:auth_logout'); - cookies.remove(cookies.NAMES.API_TOKEN); - - if (config.features.rewards.isEnabled) { - rewardsLogout(); - } - - mixpanel.logEvent(mixpanel.EventTypes.ACCOUNT_ACCESS, { Action: 'Logged out' }, { send_immediately: true }); - mixpanel.reset(); - - if ( - PROTECTED_ROUTES.includes(router.pathname) || - (router.pathname === '/txs' && router.query.tab === 'watchlist') - ) { - await router.push({ pathname: '/' }, undefined, { shallow: true }); - } - - queryClient.resetQueries({ - queryKey: getResourceKey('general:user_info'), - exact: true, - }); - queryClient.resetQueries({ - queryKey: getResourceKey('general:custom_abi'), - exact: true, - }); - } catch (error) { - toaster.error({ - title: 'Logout failed', - description: 'Please try again later', - }); - } - }, [ apiFetch, rewardsLogout, queryClient, router ]); -} diff --git a/client/features/account/hooks/useProfileQuery.ts b/client/features/account/hooks/useProfileQuery.ts deleted file mode 100644 index 14c0aba904c..00000000000 --- a/client/features/account/hooks/useProfileQuery.ts +++ /dev/null @@ -1,16 +0,0 @@ -// SPDX-License-Identifier: LicenseRef-Blockscout - -import useApiQuery from 'client/api/hooks/useApiQuery'; - -import * as cookies from 'client/shared/storage/cookies'; - -import config from 'configs/app'; - -export default function useProfileQuery() { - return useApiQuery('general:user_info', { - queryOptions: { - refetchOnMount: false, - enabled: config.features.account.isEnabled && Boolean(cookies.get(cookies.NAMES.API_TOKEN)), - }, - }); -} diff --git a/client/features/account/hooks/useRedirectForInvalidAuthToken.ts b/client/features/account/hooks/useRedirectForInvalidAuthToken.ts deleted file mode 100644 index c644c26fcc8..00000000000 --- a/client/features/account/hooks/useRedirectForInvalidAuthToken.ts +++ /dev/null @@ -1,25 +0,0 @@ -// SPDX-License-Identifier: LicenseRef-Blockscout - -import React from 'react'; - -import useProfileQuery from 'client/features/account/hooks/useProfileQuery'; - -import { useRollbar } from 'client/shared/monitoring/rollbar'; -import * as cookies from 'client/shared/storage/cookies'; - -export default function useRedirectForInvalidAuthToken() { - const rollbar = useRollbar(); - const profileQuery = useProfileQuery(); - const errorStatus = profileQuery.error?.status; - - React.useEffect(() => { - if (errorStatus === 401) { - const apiToken = cookies.get(cookies.NAMES.API_TOKEN); - - if (apiToken) { - cookies.remove(cookies.NAMES.API_TOKEN); - window.location.assign('/'); - } - } - }, [ errorStatus, rollbar ]); -} diff --git a/client/features/account/mocks/address-tags.ts b/client/features/account/mocks/address-tags.ts deleted file mode 100644 index 73f26f37053..00000000000 --- a/client/features/account/mocks/address-tags.ts +++ /dev/null @@ -1,18 +0,0 @@ -import type { AddressTag, WatchlistName } from 'client/slices/address/types/api'; - -export const privateTag: AddressTag = { - label: 'my-private-tag', - display_name: 'my private tag', - address_hash: '0x', -}; - -export const publicTag: AddressTag = { - label: 'some-public-tag', - display_name: 'some public tag', - address_hash: '0x', -}; - -export const watchlistName: WatchlistName = { - label: 'watchlist-name', - display_name: 'watchlist name', -}; diff --git a/client/features/account/mocks/user-profile.ts b/client/features/account/mocks/user-profile.ts deleted file mode 100644 index 79b844a4d28..00000000000 --- a/client/features/account/mocks/user-profile.ts +++ /dev/null @@ -1,25 +0,0 @@ -import type { UserInfo } from 'client/features/account/types/api'; - -export const base: UserInfo = { - avatar: 'https://avatars.githubusercontent.com/u/22130104', - email: 'tom@ohhhh.me', - name: 'tom goriunov', - nickname: 'tom2drum', - address_hash: null, -}; - -export const withoutEmail: UserInfo = { - avatar: 'https://avatars.githubusercontent.com/u/22130104', - email: null, - name: 'tom goriunov', - nickname: 'tom2drum', - address_hash: '0xd789a607CEac2f0E14867de4EB15b15C9FFB5859', -}; - -export const withEmailAndWallet: UserInfo = { - avatar: 'https://avatars.githubusercontent.com/u/22130104', - email: 'tom@ohhhh.me', - name: 'tom goriunov', - nickname: 'tom2drum', - address_hash: '0xd789a607CEac2f0E14867de4EB15b15C9FFB5859', -}; diff --git a/client/features/account/pages/address/AddressFavoriteButton.tsx b/client/features/account/pages/address/AddressFavoriteButton.tsx deleted file mode 100644 index b5d89f1668c..00000000000 --- a/client/features/account/pages/address/AddressFavoriteButton.tsx +++ /dev/null @@ -1,102 +0,0 @@ -// SPDX-License-Identifier: LicenseRef-Blockscout - -import { chakra } from '@chakra-ui/react'; -import { useQueryClient } from '@tanstack/react-query'; -import { useRouter } from 'next/router'; -import React from 'react'; - -import { getResourceKey } from 'client/api/hooks/useApiQuery'; - -import AuthGuard from 'client/features/account/components/auth-modal/guard/AuthGuard'; -import useProfileQuery from 'client/features/account/hooks/useProfileQuery'; -import WatchlistAddModal from 'client/features/account/pages/watchlist/AddressModal/AddressModal'; -import DeleteAddressModal from 'client/features/account/pages/watchlist/DeleteAddressModal'; - -import * as mixpanel from 'client/shared/analytics/mixpanel'; -import usePreventFocusAfterModalClosing from 'client/shared/hooks/usePreventFocusAfterModalClosing'; - -import config from 'configs/app'; -import { IconButton } from 'toolkit/chakra/icon-button'; -import { Tooltip } from 'toolkit/chakra/tooltip'; -import { useDisclosure } from 'toolkit/hooks/useDisclosure'; -import IconSvg from 'ui/shared/IconSvg'; - -interface Props { - className?: string; - hash: string; - watchListId: number | null | undefined; -} - -const AddressFavoriteButton = ({ className, hash, watchListId }: Props) => { - const addModalProps = useDisclosure(); - const deleteModalProps = useDisclosure(); - const queryClient = useQueryClient(); - const router = useRouter(); - const onFocusCapture = usePreventFocusAfterModalClosing(); - const profileQuery = useProfileQuery(); - - const handleAddToFavorite = React.useCallback(() => { - watchListId ? deleteModalProps.onOpen() : addModalProps.onOpen(); - !watchListId && mixpanel.logEvent(mixpanel.EventTypes.PAGE_WIDGET, { Type: 'Add to watchlist' }); - }, [ watchListId, deleteModalProps, addModalProps ]); - - const handleAddOrDeleteSuccess = React.useCallback(async() => { - const queryKey = getResourceKey('general:address', { pathParams: { hash: router.query.hash?.toString() } }); - await queryClient.refetchQueries({ queryKey }); - addModalProps.onClose(); - }, [ addModalProps, queryClient, router.query.hash ]); - - const formData = React.useMemo(() => { - if (typeof watchListId !== 'number') { - return { address_hash: hash }; - } - - return { - address_hash: hash, - id: watchListId, - }; - }, [ hash, watchListId ]); - - if (!config.features.account.isEnabled) { - return null; - } - - return ( - <> - - { ({ onClick }) => ( - - - - - - ) } - - - { formData.id && ( - - ) } - - ); -}; - -export default chakra(AddressFavoriteButton); diff --git a/client/features/account/pages/api-keys/ApiKeyTable/ApiKeyListItem.tsx b/client/features/account/pages/api-keys/ApiKeyTable/ApiKeyListItem.tsx deleted file mode 100644 index 7812d757538..00000000000 --- a/client/features/account/pages/api-keys/ApiKeyTable/ApiKeyListItem.tsx +++ /dev/null @@ -1,37 +0,0 @@ -// SPDX-License-Identifier: LicenseRef-Blockscout - -import React, { useCallback } from 'react'; - -import type { ApiKey } from 'client/features/account/types/api'; - -import TableItemActionButtons from 'client/features/account/components/TableItemActionButtons'; -import ApiKeySnippet from 'client/features/account/pages/api-keys/ApiKeySnippet'; - -import ListItemMobile from 'ui/shared/ListItemMobile/ListItemMobile'; - -interface Props { - item: ApiKey; - isLoading?: boolean; - onEditClick: (item: ApiKey) => void; - onDeleteClick: (item: ApiKey) => void; -} - -const ApiKeyListItem = ({ item, isLoading, onEditClick, onDeleteClick }: Props) => { - - const onItemEditClick = useCallback(() => { - return onEditClick(item); - }, [ item, onEditClick ]); - - const onItemDeleteClick = useCallback(() => { - return onDeleteClick(item); - }, [ item, onDeleteClick ]); - - return ( - - - - - ); -}; - -export default ApiKeyListItem; diff --git a/client/features/account/pages/home/LatestWatchlistTxs.tsx b/client/features/account/pages/home/LatestWatchlistTxs.tsx deleted file mode 100644 index 75e2e096c6d..00000000000 --- a/client/features/account/pages/home/LatestWatchlistTxs.tsx +++ /dev/null @@ -1,71 +0,0 @@ -// SPDX-License-Identifier: LicenseRef-Blockscout - -import { Box, Flex, Text } from '@chakra-ui/react'; -import React from 'react'; - -import { route } from 'nextjs-routes'; - -import useApiQuery from 'client/api/hooks/useApiQuery'; - -import LatestTxsFallback from 'client/slices/home/pages/index/txs/LatestTxsFallback'; -import LatestTxsItem from 'client/slices/home/pages/index/txs/LatestTxsItem'; -import LatestTxsItemMobile from 'client/slices/home/pages/index/txs/LatestTxsItemMobile'; -import { TX } from 'client/slices/tx/stubs/tx'; - -import useRedirectForInvalidAuthToken from 'client/features/account/hooks/useRedirectForInvalidAuthToken'; - -import useIsMobile from 'client/shared/hooks/useIsMobile'; - -import { Link } from 'toolkit/chakra/link'; - -const LatestWatchlistTxs = () => { - useRedirectForInvalidAuthToken(); - const isMobile = useIsMobile(); - const txsCount = isMobile ? 2 : 5; - const { data, isPlaceholderData, isError } = useApiQuery('general:homepage_txs_watchlist', { - queryOptions: { - placeholderData: Array(txsCount).fill(TX), - }, - }); - - if (isError) { - return ; - } - - if (!data?.length) { - return No latest transactions found.; - } - - if (data) { - const txsUrl = route({ pathname: '/txs', query: { tab: 'watchlist' } }); - return ( - <> - - { data.slice(0, txsCount).map(((tx, index) => ( - - ))) } - - - { data.slice(0, txsCount).map(((tx, index) => ( - - ))) } - - - View all watch list transactions - - - ); - } - - return null; -}; - -export default LatestWatchlistTxs; diff --git a/client/features/account/pages/private-tags/AddressModal/AddressForm.tsx b/client/features/account/pages/private-tags/AddressModal/AddressForm.tsx deleted file mode 100644 index 66004619bed..00000000000 --- a/client/features/account/pages/private-tags/AddressModal/AddressForm.tsx +++ /dev/null @@ -1,117 +0,0 @@ -// SPDX-License-Identifier: LicenseRef-Blockscout - -import { useMutation } from '@tanstack/react-query'; -import React, { useState } from 'react'; -import type { SubmitHandler } from 'react-hook-form'; -import { FormProvider, useForm } from 'react-hook-form'; - -import type { AddressTag, AddressTagErrors } from 'client/features/account/types/api'; - -import useApiFetch from 'client/api/hooks/useApiFetch'; -import type { ResourceErrorAccount } from 'client/api/resources'; - -import getErrorMessage from 'client/features/account/utils/get-api-error-text'; - -import { Button } from 'toolkit/chakra/button'; -import { FormFieldAddress } from 'toolkit/components/forms/fields/FormFieldAddress'; -import { FormFieldText } from 'toolkit/components/forms/fields/FormFieldText'; - -const TAG_MAX_LENGTH = 35; - -type Props = { - data?: Partial; - onOpenChange: ({ open }: { open: boolean }) => void; - onSuccess: () => Promise; - setAlertVisible: (isAlertVisible: boolean) => void; -}; - -type Inputs = { - address: string; - tag: string; -}; - -const AddressForm: React.FC = ({ data, onOpenChange, onSuccess, setAlertVisible }) => { - const apiFetch = useApiFetch(); - const [ pending, setPending ] = useState(false); - const formApi = useForm({ - mode: 'onTouched', - defaultValues: { - address: data?.address_hash || '', - tag: data?.name || '', - }, - }); - - const { mutateAsync } = useMutation({ - mutationFn: (formData: Inputs) => { - const body = { - name: formData?.tag, - address_hash: formData?.address, - }; - - const isEdit = data?.id; - if (isEdit) { - return apiFetch('general:private_tags_address', { - pathParams: { id: String(data.id) }, - fetchParams: { method: 'PUT', body }, - }); - } - - return apiFetch('general:private_tags_address', { fetchParams: { method: 'POST', body } }); - }, - onError: (error: ResourceErrorAccount) => { - setPending(false); - const errorMap = error.payload?.errors; - if (errorMap?.address_hash || errorMap?.name) { - errorMap?.address_hash && formApi.setError('address', { type: 'custom', message: getErrorMessage(errorMap, 'address_hash') }); - errorMap?.name && formApi.setError('tag', { type: 'custom', message: getErrorMessage(errorMap, 'name') }); - } else if (errorMap?.identity_id) { - formApi.setError('address', { type: 'custom', message: getErrorMessage(errorMap, 'identity_id') }); - } else { - setAlertVisible(true); - } - }, - onSuccess: async() => { - await onSuccess(); - onOpenChange({ open: false }); - setPending(false); - }, - }); - - const onSubmit: SubmitHandler = async(formData) => { - setAlertVisible(false); - setPending(true); - await mutateAsync(formData); - }; - - return ( - -
- - name="address" - required - bgColor="dialog.bg" - mb={ 5 } - /> - - name="tag" - placeholder="Private tag (max 35 characters)" - required - rules={{ - maxLength: TAG_MAX_LENGTH, - }} - bgColor="dialog.bg" - mb={ 8 } - /> - - -
- ); -}; - -export default AddressForm; diff --git a/client/features/account/pages/private-tags/AddressModal/AddressModal.tsx b/client/features/account/pages/private-tags/AddressModal/AddressModal.tsx deleted file mode 100644 index 4a19f246009..00000000000 --- a/client/features/account/pages/private-tags/AddressModal/AddressModal.tsx +++ /dev/null @@ -1,60 +0,0 @@ -// SPDX-License-Identifier: LicenseRef-Blockscout - -import React, { useCallback, useState } from 'react'; - -import type { AddressTag } from 'client/features/account/types/api'; - -import * as mixpanel from 'client/shared/analytics/mixpanel'; - -import FormModal from 'ui/shared/FormModal'; - -import AddressForm from './AddressForm'; - -type Props = { - open: boolean; - onOpenChange: ({ open }: { open: boolean }) => void; - onSuccess: () => Promise; - data?: Partial; - pageType: string; -}; - -const AddressModal: React.FC = ({ open, onOpenChange, onSuccess, data, pageType }) => { - const title = data?.id ? 'Edit address tag' : 'New address tag'; - const text = !data?.id ? 'Label any address with a private address tag (up to 35 chars) to customize your explorer experience.' : ''; - - const [ isAlertVisible, setAlertVisible ] = useState(false); - - React.useEffect(() => { - open && !data?.id && mixpanel.logEvent( - mixpanel.EventTypes.PRIVATE_TAG, - { Action: 'Form opened', 'Page type': pageType, 'Tag type': 'Address' }, - ); - }, [ data?.id, open, pageType ]); - - const handleSuccess = React.useCallback(() => { - if (!data?.id) { - mixpanel.logEvent( - mixpanel.EventTypes.PRIVATE_TAG, - { Action: 'Submit', 'Page type': pageType, 'Tag type': 'Address' }, - ); - } - return onSuccess(); - }, [ data?.id, onSuccess, pageType ]); - - const renderForm = useCallback(() => { - return ; - }, [ data, onOpenChange, handleSuccess ]); - return ( - - open={ open } - onOpenChange={ onOpenChange } - title={ title } - text={ text } - renderForm={ renderForm } - isAlertVisible={ isAlertVisible } - setAlertVisible={ setAlertVisible } - /> - ); -}; - -export default AddressModal; diff --git a/client/features/account/pages/private-tags/DeletePrivateTagModal.tsx b/client/features/account/pages/private-tags/DeletePrivateTagModal.tsx deleted file mode 100644 index f515db0a9fe..00000000000 --- a/client/features/account/pages/private-tags/DeletePrivateTagModal.tsx +++ /dev/null @@ -1,69 +0,0 @@ -// SPDX-License-Identifier: LicenseRef-Blockscout - -import { Text } from '@chakra-ui/react'; -import { useQueryClient } from '@tanstack/react-query'; -import React, { useCallback } from 'react'; - -import type { AddressTag, TransactionTag, AddressTagsResponse, TransactionTagsResponse } from 'client/features/account/types/api'; - -import useApiFetch from 'client/api/hooks/useApiFetch'; -import { getResourceKey } from 'client/api/hooks/useApiQuery'; - -import DeleteModal from 'client/features/account/components/DeleteModal'; - -type Props = { - open: boolean; - onOpenChange: ({ open }: { open: boolean }) => void; - data: AddressTag | TransactionTag; - type: 'address' | 'transaction'; -}; - -const DeletePrivateTagModal: React.FC = ({ open, onOpenChange, data, type }) => { - const tag = data.name; - const id = data.id; - - const queryClient = useQueryClient(); - const apiFetch = useApiFetch(); - - const mutationFn = useCallback(() => { - const resourceName = type === 'address' ? 'general:private_tags_address' : 'general:private_tags_tx'; - return apiFetch(resourceName, { - pathParams: { id: String(data.id) }, - fetchParams: { method: 'DELETE' }, - }); - }, [ type, apiFetch, data.id ]); - - const onSuccess = useCallback(async() => { - if (type === 'address') { - queryClient.setQueryData(getResourceKey('general:private_tags_address'), (prevData: AddressTagsResponse | undefined) => { - const newItems = prevData?.items.filter((item: AddressTag) => item.id !== id); - return { ...prevData, items: newItems }; - - }); - } else { - queryClient.setQueryData(getResourceKey('general:private_tags_tx'), (prevData: TransactionTagsResponse | undefined) => { - const newItems = prevData?.items.filter((item: TransactionTag) => item.id !== id); - return { ...prevData, items: newItems }; - }); - } - }, [ type, id, queryClient ]); - - const renderText = useCallback(() => { - return ( - Tag{ ` "${ tag || 'tag' }" ` }will be deleted - ); - }, [ tag ]); - - return ( - - ); -}; - -export default DeletePrivateTagModal; diff --git a/client/features/account/pages/private-tags/PrivateTags.tsx b/client/features/account/pages/private-tags/PrivateTags.tsx deleted file mode 100644 index bcfddfde8c6..00000000000 --- a/client/features/account/pages/private-tags/PrivateTags.tsx +++ /dev/null @@ -1,31 +0,0 @@ -// SPDX-License-Identifier: LicenseRef-Blockscout - -import React from 'react'; - -import type { TabItemRegular } from 'toolkit/components/AdaptiveTabs/types'; - -import useRedirectForInvalidAuthToken from 'client/features/account/hooks/useRedirectForInvalidAuthToken'; - -import RoutedTabs from 'toolkit/components/RoutedTabs/RoutedTabs'; -import PageTitle from 'ui/shared/Page/PageTitle'; - -import PrivateAddressTags from './PrivateAddressTags'; -import PrivateTransactionTags from './PrivateTransactionTags'; - -const TABS: Array = [ - { id: 'address', title: 'Address', component: }, - { id: 'tx', title: 'Transaction', component: }, -]; - -const PrivateTags = () => { - useRedirectForInvalidAuthToken(); - - return ( - <> - - - - ); -}; - -export default PrivateTags; diff --git a/client/features/account/pages/profile/MyProfile.tsx b/client/features/account/pages/profile/MyProfile.tsx deleted file mode 100644 index c1a08e1f588..00000000000 --- a/client/features/account/pages/profile/MyProfile.tsx +++ /dev/null @@ -1,77 +0,0 @@ -// SPDX-License-Identifier: LicenseRef-Blockscout - -import { Flex } from '@chakra-ui/react'; -import React from 'react'; - -import type { Screen } from 'client/features/account/components/auth-modal/types'; - -import AccountPageDescription from 'client/features/account/components/AccountPageDescription'; -import AuthModal from 'client/features/account/components/auth-modal/AuthModal'; -import useProfileQuery from 'client/features/account/hooks/useProfileQuery'; -import useRedirectForInvalidAuthToken from 'client/features/account/hooks/useRedirectForInvalidAuthToken'; - -import config from 'configs/app'; -import { ContentLoader } from 'toolkit/components/loaders/ContentLoader'; -import { useDisclosure } from 'toolkit/hooks/useDisclosure'; -import DataFetchAlert from 'ui/shared/DataFetchAlert'; -import PageTitle from 'ui/shared/Page/PageTitle'; - -import MyProfileEmail from './MyProfileEmail'; -import MyProfileWallet from './MyProfileWallet'; - -const MIXPANEL_CONFIG = { - wallet_connect: { - source: 'Profile' as const, - }, - account_link_info: { - source: 'Profile' as const, - }, -}; - -const MyProfile = () => { - const [ authInitialScreen, setAuthInitialScreen ] = React.useState(); - const authModal = useDisclosure(); - - const profileQuery = useProfileQuery(); - useRedirectForInvalidAuthToken(); - - const handleAddWalletClick = React.useCallback(() => { - setAuthInitialScreen({ type: 'connect_wallet', isAuth: true, loginToRewards: true }); - authModal.onOpen(); - }, [ authModal ]); - - const content = (() => { - if (profileQuery.isPending) { - return ; - } - - if (profileQuery.isError) { - return ; - } - - return ( - <> - - You can add your email to receive watchlist notifications. - Additionally, you can manage your wallet address and email, which can be used for logging into your Blockscout account. - - - - { config.features.blockchainInteraction.isEnabled && - } - - { authModal.open && authInitialScreen && - } - - ); - })(); - - return ( - <> - - { content } - - ); -}; - -export default MyProfile; diff --git a/client/features/account/pages/profile/MyProfileEmail.tsx b/client/features/account/pages/profile/MyProfileEmail.tsx deleted file mode 100644 index b0cb777b949..00000000000 --- a/client/features/account/pages/profile/MyProfileEmail.tsx +++ /dev/null @@ -1,126 +0,0 @@ -// SPDX-License-Identifier: LicenseRef-Blockscout - -import { chakra } from '@chakra-ui/react'; -import type { UseQueryResult } from '@tanstack/react-query'; -import React from 'react'; -import type { SubmitHandler } from 'react-hook-form'; -import { FormProvider, useForm } from 'react-hook-form'; - -import type { FormFields } from './types'; -import type { UserInfo } from 'client/features/account/types/api'; - -import useApiFetch from 'client/api/hooks/useApiFetch'; - -import AuthModal from 'client/features/account/components/auth-modal/AuthModal'; - -import * as mixpanel from 'client/shared/analytics/mixpanel'; -import getErrorMessage from 'client/shared/errors/get-error-message'; -import getErrorObjPayload from 'client/shared/errors/get-error-obj-payload'; - -import config from 'configs/app'; -import { Button } from 'toolkit/chakra/button'; -import { Heading } from 'toolkit/chakra/heading'; -import { toaster } from 'toolkit/chakra/toaster'; -import { FormFieldText } from 'toolkit/components/forms/fields/FormFieldText'; -import { useDisclosure } from 'toolkit/hooks/useDisclosure'; -import ReCaptcha from 'ui/shared/reCaptcha/ReCaptcha'; -import useReCaptcha from 'ui/shared/reCaptcha/useReCaptcha'; - -import MyProfileFieldsEmail from './fields/MyProfileFieldsEmail'; - -const MIXPANEL_CONFIG = { - account_link_info: { - source: 'Profile' as const, - }, -}; - -interface Props { - profileQuery: UseQueryResult; -} - -const MyProfileEmail = ({ profileQuery }: Props) => { - const authModal = useDisclosure(); - const apiFetch = useApiFetch(); - const recaptcha = useReCaptcha(); - - const formApi = useForm({ - mode: 'onBlur', - defaultValues: { - email: profileQuery.data?.email || '', - name: profileQuery.data?.name || profileQuery.data?.nickname || '', - }, - }); - - const authFetchFactory = React.useCallback((email: string) => (recaptchaToken?: string) => { - return apiFetch('general:auth_send_otp', { - fetchParams: { - method: 'POST', - body: { email, recaptcha_response: recaptchaToken }, - headers: { - ...(recaptchaToken && { 'recaptcha-v2-response': recaptchaToken }), - }, - }, - }); - }, [ apiFetch ]); - - const onFormSubmit: SubmitHandler = React.useCallback(async(formData) => { - try { - await recaptcha.fetchProtectedResource(authFetchFactory(formData.email)); - mixpanel.logEvent(mixpanel.EventTypes.ACCOUNT_LINK_INFO, { - Source: 'Profile', - Status: 'OTP sent', - Type: 'Email', - }); - authModal.onOpen(); - } catch (error) { - const apiError = getErrorObjPayload<{ message: string }>(error); - toaster.error({ - title: 'Error', - description: apiError?.message || getErrorMessage(error) || 'Something went wrong', - }); - } - }, [ authFetchFactory, authModal, recaptcha ]); - - const hasDirtyFields = Object.keys(formApi.formState.dirtyFields).length > 0; - - return ( -
- Notifications - - - name="name" placeholder="Name" readOnly mb={ 3 }/> - - { config.services.reCaptchaV2.siteKey && !profileQuery.data?.email && } - { config.services.reCaptchaV2.siteKey && !profileQuery.data?.email && ( - - ) } - - - { authModal.open && ( - - ) } -
- ); -}; - -export default React.memo(MyProfileEmail); diff --git a/client/features/account/pages/profile/fields/MyProfileFieldsEmail.tsx b/client/features/account/pages/profile/fields/MyProfileFieldsEmail.tsx deleted file mode 100644 index aeb1e5395d0..00000000000 --- a/client/features/account/pages/profile/fields/MyProfileFieldsEmail.tsx +++ /dev/null @@ -1,34 +0,0 @@ -// SPDX-License-Identifier: LicenseRef-Blockscout - -import React from 'react'; - -import type { FormFields } from '../types'; - -import { FormFieldEmail } from 'toolkit/components/forms/fields/FormFieldEmail'; -import IconSvg from 'ui/shared/IconSvg'; - -interface Props { - isReadOnly?: boolean; - defaultValue: string | undefined; -} - -const MyProfileFieldsEmail = ({ isReadOnly, defaultValue }: Props) => { - - return ( - - name="email" - placeholder="Email" - required - readOnly={ isReadOnly } - helperText="Email for watch list notifications and private tags" - group={{ - endElement: ({ field }) => { - const isVerified = defaultValue && field.value === defaultValue; - return isVerified ? : null; - }, - }} - /> - ); -}; - -export default React.memo(MyProfileFieldsEmail); diff --git a/client/features/account/pages/tx-index-watchlist/TxsWatchlist.tsx b/client/features/account/pages/tx-index-watchlist/TxsWatchlist.tsx deleted file mode 100644 index de9a66e5022..00000000000 --- a/client/features/account/pages/tx-index-watchlist/TxsWatchlist.tsx +++ /dev/null @@ -1,21 +0,0 @@ -// SPDX-License-Identifier: LicenseRef-Blockscout - -import React from 'react'; - -import TxsWithFrontendSorting from 'client/slices/tx/pages/index/list/TxsWithFrontendSorting'; - -import useRedirectForInvalidAuthToken from 'client/features/account/hooks/useRedirectForInvalidAuthToken'; - -import type { QueryWithPagesResult } from 'ui/shared/pagination/useQueryWithPages'; - -type Props = { - query: QueryWithPagesResult<'general:txs_watchlist'>; - top?: number; -}; - -const TxsWatchlist = ({ query, top }: Props) => { - useRedirectForInvalidAuthToken(); - return ; -}; - -export default TxsWatchlist; diff --git a/client/features/account/pages/verified-addresses/address-verification/types.ts b/client/features/account/pages/verified-addresses/address-verification/types.ts deleted file mode 100644 index 614d1dd6088..00000000000 --- a/client/features/account/pages/verified-addresses/address-verification/types.ts +++ /dev/null @@ -1,52 +0,0 @@ -// SPDX-License-Identifier: LicenseRef-Blockscout - -import type { VerifiedAddress } from 'client/features/account/types/api'; - -export interface AddressVerificationFormFirstStepFields { - address: string; -} - -export interface AddressVerificationFormSecondStepFields { - signature: string; - message: string; -} - -export interface RootFields { - root: string; -} - -export interface AddressCheckStatusSuccess { - contractCreator?: string; - contractOwner?: string; - signingMessage: string; -} - -export type AddressCheckResponseSuccess = { - status: 'SUCCESS'; - result: AddressCheckStatusSuccess; -} | -{ status: 'IS_OWNER_ERROR' } | -{ status: 'OWNERSHIP_VERIFIED_ERROR' } | -{ status: 'SOURCE_CODE_NOT_VERIFIED_ERROR' } | -{ status: 'INVALID_ADDRESS_ERROR' }; - -export interface AddressVerificationResponseError { - code: number; - message: string; -} - -export type AddressValidationResponseSuccess = { - status: 'SUCCESS'; - result: { - verifiedAddress: VerifiedAddress; - }; -} | -{ - status: 'INVALID_SIGNER_ERROR'; - invalidSigner: { - signer: string; - }; -} | -{ status: 'VALIDITY_EXPIRED_ERROR' } | -{ status: 'INVALID_SIGNATURE_ERROR' } | -{ status: 'UNKNOWN_STATUS' }; diff --git a/client/features/account/pages/verified-addresses/index/VerifiedAddressesEmailAlert.tsx b/client/features/account/pages/verified-addresses/index/VerifiedAddressesEmailAlert.tsx deleted file mode 100644 index e1ea0d0a0f4..00000000000 --- a/client/features/account/pages/verified-addresses/index/VerifiedAddressesEmailAlert.tsx +++ /dev/null @@ -1,45 +0,0 @@ -// SPDX-License-Identifier: LicenseRef-Blockscout - -import React from 'react'; - -import AuthModal from 'client/features/account/components/auth-modal/AuthModal'; -import useLinkEmail from 'client/features/account/hooks/useLinkEmail'; - -import config from 'configs/app'; -import { Alert } from 'toolkit/chakra/alert'; -import { Button } from 'toolkit/chakra/button'; -import { useDisclosure } from 'toolkit/hooks/useDisclosure'; - -const feature = config.features.account; - -const VerifiedAddressesEmailAlert = () => { - const authModal = useDisclosure(); - const linkEmail = useLinkEmail(); - - const handleButtonClick = React.useCallback(() => { - if (feature.isEnabled && feature.authProvider === 'dynamic') { - linkEmail(); - } else { - authModal.onOpen(); - } - }, [ authModal, linkEmail ]); - - return ( - <> - - You need a valid email address to verify contracts. Please add your email to your account. - - - { authModal.open && } - - ); -}; - -export default React.memo(VerifiedAddressesEmailAlert); diff --git a/client/features/account/pages/verified-addresses/token-info/fields/TokenInfoFieldIconUrl.tsx b/client/features/account/pages/verified-addresses/token-info/fields/TokenInfoFieldIconUrl.tsx deleted file mode 100644 index 14f0c62ccec..00000000000 --- a/client/features/account/pages/verified-addresses/token-info/fields/TokenInfoFieldIconUrl.tsx +++ /dev/null @@ -1,48 +0,0 @@ -// SPDX-License-Identifier: LicenseRef-Blockscout - -import { Flex } from '@chakra-ui/react'; -import React from 'react'; - -import type { Fields } from '../types'; - -import TokenLogoPlaceholder from 'client/slices/token/components/icon/TokenIconPlaceholder'; - -import type { FieldProps } from 'toolkit/chakra/field'; -import { FormFieldUrl } from 'toolkit/components/forms/fields/FormFieldUrl'; -import { FormFieldImagePreview } from 'toolkit/components/forms/fields/image/FormFieldImagePreview'; -import { useImageField } from 'toolkit/components/forms/fields/image/useImageField'; -import { times } from 'toolkit/utils/htmlEntities'; - -import TokenInfoIconPreview from '../TokenInfoIconPreview'; - -interface Props { - readOnly?: boolean; - size?: FieldProps['size']; -} - -const TokenInfoFieldIconUrl = ({ readOnly, size }: Props) => { - - const imageField = useImageField({ name: 'icon_url', isRequired: true }); - - return ( - - - name="icon_url" - placeholder={ `Link to icon URL, link to download a SVG or 48${ times }48 PNG icon logo` } - readOnly={ readOnly } - size={ size } - { ...imageField.input } - /> - - } - boxSize={ 10 } - borderRadius="base" - /> - - - ); -}; - -export default React.memo(TokenInfoFieldIconUrl); diff --git a/client/features/account/pages/verified-addresses/token-info/fields/TokenInfoFieldSupport.tsx b/client/features/account/pages/verified-addresses/token-info/fields/TokenInfoFieldSupport.tsx deleted file mode 100644 index 5bfbc2cddb7..00000000000 --- a/client/features/account/pages/verified-addresses/token-info/fields/TokenInfoFieldSupport.tsx +++ /dev/null @@ -1,43 +0,0 @@ -// SPDX-License-Identifier: LicenseRef-Blockscout - -import React from 'react'; - -import type { Fields } from '../types'; - -import type { FieldProps } from 'toolkit/chakra/field'; -import { FormFieldText } from 'toolkit/components/forms/fields/FormFieldText'; -import { emailValidator } from 'toolkit/components/forms/validators/email'; -import { urlValidator } from 'toolkit/components/forms/validators/url'; - -interface Props { - readOnly?: boolean; - size?: FieldProps['size']; -} - -const TokenInfoFieldSupport = (props: Props) => { - const validate = React.useCallback((newValue: string | undefined) => { - if (typeof newValue !== 'string') { - return true; - } - - const urlValidationResult = urlValidator(newValue); - const emailValidationResult = emailValidator(newValue); - - if (urlValidationResult === true || emailValidationResult === true) { - return true; - } - - return 'Invalid format'; - }, []); - - return ( - - name="support" - placeholder="Support URL or email" - rules={{ validate }} - { ...props } - /> - ); -}; - -export default React.memo(TokenInfoFieldSupport); diff --git a/client/features/account/pages/verified-addresses/token-info/utils.ts b/client/features/account/pages/verified-addresses/token-info/utils.ts deleted file mode 100644 index b78cb153304..00000000000 --- a/client/features/account/pages/verified-addresses/token-info/utils.ts +++ /dev/null @@ -1,69 +0,0 @@ -// SPDX-License-Identifier: LicenseRef-Blockscout - -import type { Fields } from './types'; -import type { TokenInfoApplication } from 'client/features/account/types/api'; - -export function getFormDefaultValues(address: string, tokenName: string, application: TokenInfoApplication | undefined): Partial { - if (!application) { - return { address, token_name: tokenName }; - } - - return { - address, - token_name: tokenName, - requester_name: application.requesterName, - requester_email: application.requesterEmail, - project_name: application.projectName, - project_sector: application.projectSector ? [ application.projectSector ] : undefined, - project_email: application.projectEmail, - project_website: application.projectWebsite, - project_description: application.projectDescription || '', - docs: application.docs || '', - support: application.support || '', - icon_url: application.iconUrl, - ticker_coin_gecko: application.coinGeckoTicker || '', - ticker_coin_market_cap: application.coinMarketCapTicker, - ticker_defi_llama: application.defiLlamaTicker, - github: application.github || '', - telegram: application.telegram || '', - linkedin: application.linkedin || '', - discord: application.discord || '', - slack: application.slack || '', - twitter: application.twitter || '', - opensea: application.openSea || '', - facebook: application.facebook || '', - medium: application.medium || '', - reddit: application.reddit || '', - comment: application.comment || '', - }; -} - -export function prepareRequestBody(data: Fields): Omit { - return { - coinGeckoTicker: data.ticker_coin_gecko, - coinMarketCapTicker: data.ticker_coin_market_cap, - defiLlamaTicker: data.ticker_defi_llama, - discord: data.discord, - docs: data.docs, - facebook: data.facebook, - github: data.github, - iconUrl: data.icon_url, - linkedin: data.linkedin, - medium: data.medium, - openSea: data.opensea, - projectDescription: data.project_description, - projectEmail: data.project_email, - projectName: data.project_name, - projectSector: data.project_sector?.[0], - projectWebsite: data.project_website, - reddit: data.reddit, - requesterEmail: data.requester_email, - requesterName: data.requester_name, - slack: data.slack, - support: data.support, - telegram: data.telegram, - tokenAddress: data.address, - twitter: data.twitter, - comment: data.comment, - }; -} diff --git a/client/features/account/pages/watchlist/AddressModal/AddressForm.tsx b/client/features/account/pages/watchlist/AddressModal/AddressForm.tsx deleted file mode 100644 index 889df159e56..00000000000 --- a/client/features/account/pages/watchlist/AddressModal/AddressForm.tsx +++ /dev/null @@ -1,177 +0,0 @@ -// SPDX-License-Identifier: LicenseRef-Blockscout - -import { Box, Text } from '@chakra-ui/react'; -import { useMutation } from '@tanstack/react-query'; -import React, { useState } from 'react'; -import type { SubmitHandler } from 'react-hook-form'; -import { useForm, FormProvider } from 'react-hook-form'; - -import type { WatchlistAddress, WatchlistErrors } from 'client/features/account/types/api'; - -import useApiFetch from 'client/api/hooks/useApiFetch'; -import type { ResourceErrorAccount } from 'client/api/resources'; - -import getErrorMessage from 'client/features/account/utils/get-api-error-text'; - -import { Alert } from 'toolkit/chakra/alert'; -import { Button } from 'toolkit/chakra/button'; -import { FormFieldAddress } from 'toolkit/components/forms/fields/FormFieldAddress'; -import { FormFieldCheckbox } from 'toolkit/components/forms/fields/FormFieldCheckbox'; -import { FormFieldText } from 'toolkit/components/forms/fields/FormFieldText'; - -import AddressFormNotifications, { NOTIFICATIONS } from './AddressFormNotifications'; - -const TAG_MAX_LENGTH = 35; - -type Props = { - data?: Partial; - onSuccess: () => Promise; - setAlertVisible: (isAlertVisible: boolean) => void; - isAdd: boolean; - hasEmail: boolean; - showEmailAlert?: boolean; -}; - -export type Inputs = { - address: string; - tag: string; - notification: boolean; - notification_settings: { - [key: string]: { - outcoming: boolean; - incoming: boolean; - }; - }; -}; - -const AddressForm: React.FC = ({ data, onSuccess, setAlertVisible, isAdd, hasEmail, showEmailAlert }) => { - const [ pending, setPending ] = useState(false); - - let notificationsDefault = {} as Inputs['notification_settings']; - if (!data?.notification_settings) { - NOTIFICATIONS.forEach((notificationType) => { - notificationsDefault[notificationType] = { incoming: hasEmail, outcoming: hasEmail }; - }); - } else { - notificationsDefault = data.notification_settings; - } - - const formApi = useForm({ - defaultValues: { - address: data?.address_hash || '', - tag: data?.name || '', - notification: data?.notification_methods ? data.notification_methods.email : hasEmail, - notification_settings: notificationsDefault, - }, - mode: 'onTouched', - }); - - const apiFetch = useApiFetch(); - - function updateWatchlist(formData: Inputs) { - const body = { - name: formData?.tag, - address_hash: formData?.address, - notification_settings: formData.notification_settings, - notification_methods: { - email: formData.notification, - }, - }; - if (!isAdd && data) { - // edit address - return apiFetch('general:watchlist', { - pathParams: { id: data?.id ? String(data.id) : '' }, - fetchParams: { method: 'PUT', body }, - }); - - } else { - // add address - return apiFetch('general:watchlist', { fetchParams: { method: 'POST', body } }); - } - } - - const { mutateAsync } = useMutation({ - mutationFn: updateWatchlist, - onSuccess: async() => { - await onSuccess(); - setPending(false); - }, - onError: (error: ResourceErrorAccount) => { - setPending(false); - const errorMap = error.payload?.errors; - if (errorMap?.address_hash || errorMap?.name) { - errorMap?.address_hash && formApi.setError('address', { type: 'custom', message: getErrorMessage(errorMap, 'address_hash') }); - errorMap?.name && formApi.setError('tag', { type: 'custom', message: getErrorMessage(errorMap, 'name') }); - } else if (errorMap?.watchlist_id) { - formApi.setError('address', { type: 'custom', message: getErrorMessage(errorMap, 'watchlist_id') }); - } else { - setAlertVisible(true); - } - }, - }); - - const onSubmit: SubmitHandler = async(formData) => { - setAlertVisible(false); - setPending(true); - await mutateAsync(formData); - }; - - return ( - -
- - name="address" - required - bgColor="dialog.bg" - mb={ 5 } - /> - - name="tag" - placeholder="Private tag (max 35 characters)" - required - rules={{ - maxLength: TAG_MAX_LENGTH, - }} - bgColor="dialog.bg" - mb={ 8 } - /> - { hasEmail ? ( - <> - - Please select what types of notifications you will receive - - - - - Notification methods - - name="notification" - label="Email notifications" - /> - - ) : null } - { !hasEmail && showEmailAlert ? ( - - To receive notifications you need to add an email to your profile. - - ) : null } - - -
- - ); -}; - -export default AddressForm; diff --git a/client/features/account/pages/watchlist/AddressModal/AddressModal.tsx b/client/features/account/pages/watchlist/AddressModal/AddressModal.tsx deleted file mode 100644 index 7c81ed06709..00000000000 --- a/client/features/account/pages/watchlist/AddressModal/AddressModal.tsx +++ /dev/null @@ -1,53 +0,0 @@ -// SPDX-License-Identifier: LicenseRef-Blockscout - -import React, { useCallback, useState } from 'react'; - -import type { WatchlistAddress } from 'client/features/account/types/api'; - -import FormModal from 'ui/shared/FormModal'; - -import AddressForm from './AddressForm'; - -type Props = { - isAdd: boolean; - open: boolean; - onOpenChange: ({ open }: { open: boolean }) => void; - onSuccess: () => Promise; - data?: Partial; - hasEmail: boolean; - showEmailAlert?: boolean; -}; - -const AddressModal: React.FC = ({ open, onOpenChange, onSuccess, data, isAdd, hasEmail, showEmailAlert }) => { - const title = !isAdd ? 'Edit watch list address' : 'New address to watch list'; - const text = isAdd ? 'An email notification can be sent to you when an address on your watch list sends or receives any transactions.' : ''; - - const [ isAlertVisible, setAlertVisible ] = useState(false); - - const renderForm = useCallback(() => { - return ( - - ); - }, [ data, isAdd, onSuccess, hasEmail, showEmailAlert ]); - - return ( - - open={ open } - onOpenChange={ onOpenChange } - title={ title } - text={ text } - renderForm={ renderForm } - isAlertVisible={ isAlertVisible } - setAlertVisible={ setAlertVisible } - /> - ); -}; - -export default AddressModal; diff --git a/client/features/account/pages/watchlist/WatchlistEmailAlert.tsx b/client/features/account/pages/watchlist/WatchlistEmailAlert.tsx deleted file mode 100644 index bebc78a5d5a..00000000000 --- a/client/features/account/pages/watchlist/WatchlistEmailAlert.tsx +++ /dev/null @@ -1,43 +0,0 @@ -// SPDX-License-Identifier: LicenseRef-Blockscout - -import React from 'react'; - -import AuthModal from 'client/features/account/components/auth-modal/AuthModal'; -import useLinkEmail from 'client/features/account/hooks/useLinkEmail'; - -import config from 'configs/app'; -import { Alert } from 'toolkit/chakra/alert'; -import { Button } from 'toolkit/chakra/button'; -import { useDisclosure } from 'toolkit/hooks/useDisclosure'; - -const feature = config.features.account; - -const WatchlistEmailAlert = () => { - const authModal = useDisclosure(); - const linkEmail = useLinkEmail(); - - const handleButtonClick = React.useCallback(() => { - if (feature.isEnabled && feature.authProvider === 'dynamic') { - linkEmail(); - } else { - authModal.onOpen(); - } - }, [ authModal, linkEmail ]); - - return ( - <> - - To receive notifications you need to add an email to your profile. - - - { authModal.open && } - - ); -}; - -export default React.memo(WatchlistEmailAlert); diff --git a/client/features/account/pages/watchlist/WatchlistTable/WatchListAddressItem.tsx b/client/features/account/pages/watchlist/WatchlistTable/WatchListAddressItem.tsx deleted file mode 100644 index 6d2f8b6fbd1..00000000000 --- a/client/features/account/pages/watchlist/WatchlistTable/WatchListAddressItem.tsx +++ /dev/null @@ -1,93 +0,0 @@ -// SPDX-License-Identifier: LicenseRef-Blockscout - -import { HStack, VStack, Flex, Text } from '@chakra-ui/react'; -import BigNumber from 'bignumber.js'; -import React from 'react'; - -import type { WatchlistAddress } from 'client/features/account/types/api'; - -import AddressEntity from 'client/slices/address/components/entity/AddressEntity'; -import * as TokenEntity from 'client/slices/token/components/entity/TokenEntity'; - -import { currencyUnits } from 'client/shared/chain/units'; - -import config from 'configs/app'; -import { Skeleton } from 'toolkit/chakra/skeleton'; -import { nbsp } from 'toolkit/utils/htmlEntities'; -import IconSvg from 'ui/shared/IconSvg'; -import calculateUsdValue from 'ui/shared/value/calculateUsdValue'; -import NativeCoinValue from 'ui/shared/value/NativeCoinValue'; -import SimpleValue from 'ui/shared/value/SimpleValue'; -import { DEFAULT_ACCURACY_USD } from 'ui/shared/value/utils'; - -const WatchListAddressItem = ({ item, isLoading }: { item: WatchlistAddress; isLoading?: boolean }) => { - const nativeTokenData = React.useMemo(() => ({ - name: config.chain.currency.name || '', - icon_url: '', - symbol: '', - address_hash: '', - type: 'ERC-20' as const, - reputation: null, - }), [ ]); - - const { usdBn: usdNative } = calculateUsdValue( - { - amount: item.address_balance, - exchangeRate: item.exchange_rate, - decimals: String(config.chain.currency.decimals), - }, - ); - - return ( - - - - - - { currencyUnits.ether } balance: - - - - { Boolean(item.tokens_count) && ( - - - - { `Tokens:${ nbsp }` + item.tokens_count + (item.tokens_overflow ? '+' : '') } - { `${ nbsp }($${ BigNumber(item.tokens_fiat_value).toFormat(2) })` } - - - ) } - { Boolean(item.tokens_fiat_value) && ( - - - Net worth:{ nbsp } - - ) } - accuracy={ DEFAULT_ACCURACY_USD } - loading={ isLoading } - overflowed={ item.tokens_overflow } - pl={ 7 } - fontSize="sm" - /> - ) } - - ); -}; - -export default WatchListAddressItem; diff --git a/client/features/account/stubs.ts b/client/features/account/stubs.ts deleted file mode 100644 index e5836ab8a7e..00000000000 --- a/client/features/account/stubs.ts +++ /dev/null @@ -1,98 +0,0 @@ -import type { AddressTag, TransactionTag, ApiKey, CustomAbi, VerifiedAddress, TokenInfoApplication, WatchlistAddress } from 'client/features/account/types/api'; - -import { ADDRESS_PARAMS, ADDRESS_HASH } from 'client/slices/address/stubs/address-params'; -import { TX_HASH } from 'client/slices/tx/stubs/tx'; - -export const PRIVATE_TAG_ADDRESS: AddressTag = { - address: ADDRESS_PARAMS, - address_hash: ADDRESS_HASH, - id: 4, - name: 'placeholder', -}; - -export const PRIVATE_TAG_TX: TransactionTag = { - id: 1, - name: 'placeholder', - transaction_hash: TX_HASH, -}; - -export const WATCH_LIST_ITEM_WITH_TOKEN_INFO: WatchlistAddress = { - address: ADDRESS_PARAMS, - address_balance: '7072643779453701031672', - address_hash: ADDRESS_HASH, - exchange_rate: '0.00099052', - id: 18, - name: 'placeholder', - notification_methods: { - email: false, - }, - notification_settings: { - 'ERC-20': { - incoming: true, - outcoming: true, - }, - 'ERC-721': { - incoming: true, - outcoming: true, - }, - 'ERC-404': { - incoming: true, - outcoming: true, - }, - 'native': { - incoming: true, - outcoming: true, - }, - }, - tokens_count: 42, - tokens_fiat_value: '12345', - tokens_overflow: false, -}; - -export const API_KEY: ApiKey = { - api_key: '9c3ecf44-a1ca-4ff1-b28e-329e8b65f652', - name: 'placeholder', -}; - -export const CUSTOM_ABI: CustomAbi = { - abi: [ - { - constant: false, - payable: false, - inputs: [ { name: 'target', type: 'address' } ], - name: 'unknownWriteMethod', - outputs: [ { name: 'result', type: 'address' } ], - stateMutability: 'nonpayable', - type: 'function', - }, - ], - contract_address: ADDRESS_PARAMS, - contract_address_hash: ADDRESS_HASH, - id: 1, - name: 'placeholder', -}; - -export const VERIFIED_ADDRESS: VerifiedAddress = { - userId: 'john.doe@gmail.com', - chainId: '5', - contractAddress: ADDRESS_HASH, - verifiedDate: '2022-11-11', - metadata: { - tokenName: 'Placeholder Token', - tokenSymbol: 'PLC', - }, -}; - -export const TOKEN_INFO_APPLICATION: TokenInfoApplication = { - id: '1', - tokenAddress: ADDRESS_HASH, - status: 'IN_PROCESS', - updatedAt: '2022-11-11 13:49:48.031453Z', - requesterName: 'John Doe', - requesterEmail: 'john.doe@gmail.com', - projectWebsite: 'http://example.com', - projectEmail: 'info@example.com', - iconUrl: 'https://example.com/100/100', - projectDescription: 'Hello!', - projectSector: 'DeFi', -}; diff --git a/client/features/account/types/api.ts b/client/features/account/types/api.ts deleted file mode 100644 index 9c1786f31b1..00000000000 --- a/client/features/account/types/api.ts +++ /dev/null @@ -1,207 +0,0 @@ -// SPDX-License-Identifier: LicenseRef-Blockscout - -import type { Abi } from 'viem'; - -import type { AddressParam } from 'client/slices/address/types/api'; -import type { Transaction } from 'client/slices/tx/types/api'; - -export interface TransactionsResponseWatchlist { - items: Array; - next_page_params: { - block_number: number; - index: number; - items_count: 50; - } | null; -} -export interface AddressTag { - address_hash: string; - address: AddressParam; - name: string; - id: number; -} - -export type AddressTags = Array; - -export type AddressTagsResponse = { - items: AddressTags; - next_page_params: { - id: number; - items_count: number; - } | null; -}; - -export interface ApiKey { - api_key: string; - name: string; -} - -export type ApiKeys = Array; - -export interface ModelError { - message: string; -} - -export interface NotificationDirection { - incoming: boolean; - outcoming: boolean; -} - -export interface NotificationSettings { - 'native': NotificationDirection; - 'ERC-20': NotificationDirection; - 'ERC-721': NotificationDirection; - 'ERC-404': NotificationDirection; - [key: string]: NotificationDirection; -} - -export interface NotificationMethods { - email: boolean; -} - -export interface TransactionTag { - transaction_hash: string; - name: string; - id: number; -} - -export type TransactionTags = Array; - -export type TransactionTagsResponse = { - items: TransactionTags; - next_page_params: { - id: number; - items_count: number; - } | null; -}; - -export interface UserInfo { - name?: string; - nickname?: string; - email: string | null; - address_hash: string | null; - avatar?: string; -} - -export interface WatchlistAddress { - address_hash: string; - name: string; - address_balance: string; - exchange_rate: string; - notification_settings: NotificationSettings; - notification_methods: NotificationMethods; - id: number; - address: AddressParam; - tokens_count: number; - tokens_fiat_value: string; - tokens_overflow: boolean; -} - -export interface WatchlistAddressNew { - addressName: string; - notificationSettings: NotificationSettings; -} - -export type WatchlistAddresses = Array; - -export type WatchlistResponse = { - items: WatchlistAddresses; - next_page_params: { - id: number; - items_count: number; - } | null; -}; - -export type CustomAbis = Array; - -export interface CustomAbi { - name: string; - id: number; - contract_address_hash: string; - contract_address: AddressParam; - abi: Abi; -} - -export type WatchlistErrors = { - address_hash?: Array; - name?: Array; - watchlist_id?: Array; -}; - -export type CustomAbiErrors = { - address_hash?: Array; - name?: Array; - abi?: Array; - identity_id?: Array; -}; - -export type ApiKeyErrors = { - name?: Array; - identity_id?: Array; -}; - -export type AddressTagErrors = { - address_hash: Array; - name: Array; - identity_id?: Array; -}; - -export type TransactionTagErrors = { - transaction_hash: Array; - name: Array; - identity_id?: Array; -}; - -export interface VerifiedAddress { - userId: string; - chainId: string; - contractAddress: string; - verifiedDate: string; - metadata: { - tokenName: string | null; - tokenSymbol: string | null; - }; -} - -export interface VerifiedAddressResponse { - verifiedAddresses: Array; -} - -export interface TokenInfoApplicationConfig { - projectSectors: Array; -} - -export interface TokenInfoApplication { - adminComments?: string; - coinGeckoTicker?: string; - coinMarketCapTicker?: string; - comment?: string; - defiLlamaTicker?: string; - discord?: string; - docs?: string; - facebook?: string; - github?: string; - iconUrl: string; - id: string; - linkedin?: string; - medium?: string; - openSea?: string; - projectDescription?: string; - projectEmail: string; - projectName?: string; - projectSector?: string; - projectWebsite: string; - reddit?: string; - requesterEmail: string; - requesterName: string; - slack?: string; - status: 'STATUS_UNKNOWN' | 'IN_PROCESS' | 'APPROVED' | 'REJECTED' | 'UPDATE_REQUIRED'; - support?: string; - telegram?: string; - tokenAddress: string; - twitter?: string; - updatedAt: string; -} - -export interface TokenInfoApplications { - submissions: Array; -} diff --git a/client/features/address-3rd-party-widgets/mocks.ts b/client/features/address-3rd-party-widgets/mocks.ts deleted file mode 100644 index 512f752b174..00000000000 --- a/client/features/address-3rd-party-widgets/mocks.ts +++ /dev/null @@ -1,96 +0,0 @@ -import type { Address3rdPartyWidget } from 'client/features/address-3rd-party-widgets/types/view'; - -export const widgets = [ - 'widget-1', - 'widget-2', - 'widget-3', - 'widget-4', - 'widget-5', - 'widget-6', - 'widget-7', - 'widget-8', - 'widget-9', -] as const; - -export const values = [ 0, 2534783, 75.34, undefined, 1553.5, 100, 0.99, 333, undefined ]; - -export const config: Record = { - 'widget-1': { - name: 'Widget 1', - url: 'https://www.example.com', - pages: [ 'eoa', 'contract', 'token' ], - icon: 'http://localhost:3000/widget-logo.png', - title: 'Value', - hint: 'Hint', - valuePath: 'value', - valueTitlePath: 'valueTitle', - }, - 'widget-2': { - name: 'Widget 2', - url: 'https://www.example.com', - pages: [ 'eoa', 'contract', 'token' ], - icon: 'http://localhost:3000/widget-logo.png', - title: 'Another value', - valuePath: 'value', - }, - 'widget-3': { - name: 'Widget 3', - url: 'https://www.example.com', - pages: [ 'eoa', 'contract', 'token' ], - icon: 'http://localhost:3000/widget-logo.png', - title: 'One more value', - hint: 'Hint', - valuePath: 'value', - }, - 'widget-4': { - name: 'Widget 4', - url: 'https://www.example.com', - pages: [ 'eoa', 'contract', 'token' ], - icon: 'http://localhost:3000/widget-logo.png', - title: 'Empty value', - valuePath: 'another_value', - }, - 'widget-5': { - name: 'Widget 5', - url: 'https://www.example.com', - pages: [ 'eoa', 'contract', 'token' ], - icon: 'http://localhost:3000/widget-logo.png', - title: 'Test value', - hint: 'Hint', - valuePath: 'value', - }, - 'widget-6': { - name: 'Widget 6', - url: 'https://www.example.com', - pages: [ 'eoa', 'contract', 'token' ], - icon: 'http://localhost:3000/widget-logo.png', - title: 'Another test value', - valuePath: 'value', - }, - 'widget-7': { - name: 'Widget 7', - url: 'https://www.example.com', - pages: [ 'eoa', 'contract', 'token' ], - icon: 'http://localhost:3000/widget-logo.png', - title: 'One more test value', - hint: 'Hint', - valuePath: 'value', - }, - 'widget-8': { - name: 'Widget 8', - url: 'https://www.example.com', - pages: [ 'eoa', 'contract', 'token' ], - icon: 'http://localhost:3000/widget-logo.png', - title: 'Final test value', - valuePath: 'value', - }, - 'widget-9': { - name: 'Widget 9', - url: 'https://www.example.com', - pages: [ 'eoa', 'contract', 'token' ], - icon: 'http://localhost:3000/widget-logo.png', - title: 'Another empty value', - hint: 'Hint', - valuePath: 'value', - }, -}; diff --git a/client/features/address-3rd-party-widgets/pages/address/useWidgetsConfigQuery.tsx b/client/features/address-3rd-party-widgets/pages/address/useWidgetsConfigQuery.tsx deleted file mode 100644 index 597e8f36bbf..00000000000 --- a/client/features/address-3rd-party-widgets/pages/address/useWidgetsConfigQuery.tsx +++ /dev/null @@ -1,27 +0,0 @@ -// SPDX-License-Identifier: LicenseRef-Blockscout - -import { useQuery } from '@tanstack/react-query'; - -import type { Address3rdPartyWidget } from 'client/features/address-3rd-party-widgets/types/view'; - -import useApiFetch from 'client/api/hooks/useFetch'; -import type { ResourceError } from 'client/api/resources'; - -import config from 'configs/app'; -import { WIDGET_CONFIG } from 'stubs/address3rdPartyWidgets'; - -const feature = config.features.address3rdPartyWidgets; -const configUrl = (feature.isEnabled && feature.configUrl) || ''; -const widgets = (feature.isEnabled && feature.widgets) || []; - -export default function useWidgetsConfigQuery(isQueryEnabled = true) { - const apiFetch = useApiFetch(); - - return useQuery, Record>({ - queryKey: [ 'address-3rd-party-widgets-config' ], - queryFn: async() => apiFetch(configUrl, undefined, { resource: 'address-3rd-party-widgets-config' }), - placeholderData: widgets.reduce((acc, widget) => ({ ...acc, [widget]: WIDGET_CONFIG }), {}), - staleTime: Infinity, - enabled: Boolean(configUrl) && isQueryEnabled, - }); -} diff --git a/client/features/address-metadata/hooks/useAddressMetadataInitUpdate.ts b/client/features/address-metadata/hooks/useAddressMetadataInitUpdate.ts deleted file mode 100644 index 3e08dfa0287..00000000000 --- a/client/features/address-metadata/hooks/useAddressMetadataInitUpdate.ts +++ /dev/null @@ -1,43 +0,0 @@ -// SPDX-License-Identifier: LicenseRef-Blockscout - -import React from 'react'; - -import type { AddressCounters } from 'client/slices/address/types/api'; - -import useApiFetch from 'client/api/hooks/useApiFetch'; - -import config from 'configs/app'; - -const feature = config.features.addressMetadata; - -interface Params { - address: string | undefined; - counters: AddressCounters | undefined; - isEnabled: boolean; -} - -const TXS_THRESHOLD = 500; - -export default function useAddressMetadataInitUpdate({ address, counters, isEnabled }: Params) { - - const apiFetch = useApiFetch(); - - React.useEffect(() => { - if ( - feature.isEnabled && - feature.isAddressTagsUpdateEnabled && - address && - isEnabled && - counters?.transactions_count && Number(counters.transactions_count) > TXS_THRESHOLD - ) { - apiFetch('metadata:address_submit', { - fetchParams: { - method: 'POST', - body: { - addresses: [ address ], - }, - }, - }); - } - }, [ address, apiFetch, counters?.transactions_count, isEnabled ]); -} diff --git a/client/features/address-metadata/mocks/search.ts b/client/features/address-metadata/mocks/search.ts deleted file mode 100644 index c4983f84531..00000000000 --- a/client/features/address-metadata/mocks/search.ts +++ /dev/null @@ -1,58 +0,0 @@ -import type { SearchResultMetadataTag } from 'client/features/address-metadata/types/api'; - -export const metatag1: SearchResultMetadataTag = { - address_hash: '0xb64a30399f7F6b0C154c2E7Af0a3ec7B0A5b131a', - name: null, - type: 'metadata_tag', - is_smart_contract_verified: false, - is_smart_contract_address: false, - url: '/address/0xb64a30399f7F6b0C154c2E7Af0a3ec7B0A5b131a', - metadata: { - name: 'utko', - slug: 'utko', - meta: {}, - tagType: 'name', - ordinal: 1, - }, -}; - -export const metatag2: SearchResultMetadataTag = { - address_hash: '0xb64a30399f7F6b0C154c2E7Af0a3ec7B0A5b131b', - name: null, - type: 'metadata_tag', - is_smart_contract_verified: false, - is_smart_contract_address: false, - url: '/address/0xb64a30399f7F6b0C154c2E7Af0a3ec7B0A5b131b', - ens_info: { - address_hash: '0x1234567890123456789012345678901234567890', - expiry_date: '2022-12-11T17:55:20Z', - name: 'utko.eth', - names_count: 1, - }, - metadata: { - name: 'utko', - slug: 'utko', - meta: { - cexDeposit: 'true', - }, - tagType: 'name', - ordinal: 1, - }, -}; - -export const metatag3: SearchResultMetadataTag = { - address_hash: '0xb64a30399f7F6b0C154c2E7Af0a3ec7B0A5b131a', - name: 'Super utko', - type: 'metadata_tag', - is_smart_contract_verified: true, - certified: true, - is_smart_contract_address: true, - url: '/address/0xb64a30399f7F6b0C154c2E7Af0a3ec7B0A5b131a', - metadata: { - name: 'super utko', - slug: 'super-utko', - meta: {}, - tagType: 'protocol', - ordinal: 1, - }, -}; diff --git a/client/features/address-metadata/types/api.ts b/client/features/address-metadata/types/api.ts deleted file mode 100644 index 249bfc2ee7a..00000000000 --- a/client/features/address-metadata/types/api.ts +++ /dev/null @@ -1,87 +0,0 @@ -// SPDX-License-Identifier: LicenseRef-Blockscout - -import type { AddressesItem } from 'client/slices/address/types/api'; - -export interface AddressMetadataInfo { - addresses: Record; - reputation: number | null; - }>; -} - -export type AddressMetadataTagType = 'name' | 'generic' | 'classifier' | 'information' | 'note' | 'protocol'; - -// Response model from Metadata microservice API -export interface AddressMetadataTag { - slug: string; - name: string; - tagType: AddressMetadataTagType; - ordinal: number; - meta: string | null; -} - -// Response model from Blockscout API with parsed meta field -export interface AddressMetadataTagApi extends Omit { - meta: { - textColor?: string; - bgColor?: string; - tagIcon?: string; - tagUrl?: string; - tooltipIcon?: string; - tooltipTitle?: string; - tooltipDescription?: string; - tooltipUrl?: string; - tooltipAttribution?: string; - tooltipAttributionIcon?: string; - appID?: string; - appMarketplaceURL?: string; - appLogoURL?: string; - appActionButtonText?: string; - warpcastHandle?: string; - data?: string; - alertBgColor?: string; - alertTextColor?: string; - alertStatus?: string; - cexDeposit?: string; - } | null; -} - -// TAG SUBMISSION - -export interface PublicTagType { - id: string; - type: AddressMetadataTagType; - description: string; -} - -export interface PublicTagTypesResponse { - tagTypes: Array; -} - -export interface AddressesMetadataSearchResult { - items: Array; - next_page_params: null; -} - -export interface AddressesMetadataSearchFilters { - slug: string; - tag_type: string; -} - -export interface SearchResultMetadataTag { - type: 'metadata_tag'; - name: string | null; - address_hash: string; - is_smart_contract_verified: boolean; - is_smart_contract_address: boolean; - certified?: true; - filecoin_robust_address?: string | null; - url?: string; - ens_info?: { - address_hash: string; - expiry_date?: string; - name: string; - names_count: number; - } | null; - metadata: AddressMetadataTagApi; -} diff --git a/client/features/address-metadata/utils/parseMetaPayload.ts b/client/features/address-metadata/utils/parseMetaPayload.ts deleted file mode 100644 index d27e95d04ca..00000000000 --- a/client/features/address-metadata/utils/parseMetaPayload.ts +++ /dev/null @@ -1,51 +0,0 @@ -// SPDX-License-Identifier: LicenseRef-Blockscout - -import type { AddressMetadataTag } from 'client/features/address-metadata/types/api'; -import type { AddressMetadataTagFormatted } from 'client/features/address-metadata/types/view'; - -type MetaParsed = NonNullable; - -export default function parseMetaPayload(meta: AddressMetadataTag['meta']): AddressMetadataTagFormatted['meta'] { - try { - const parsedMeta = JSON.parse(meta || ''); - - if (typeof parsedMeta !== 'object' || parsedMeta === null || Array.isArray(parsedMeta)) { - throw new Error('Invalid JSON'); - } - - const result: AddressMetadataTagFormatted['meta'] = {}; - - const stringFields: Array = [ - 'textColor', - 'bgColor', - 'tagIcon', - 'tagUrl', - 'tooltipIcon', - 'tooltipTitle', - 'tooltipDescription', - 'tooltipUrl', - 'tooltipAttribution', - 'tooltipAttributionIcon', - 'appID', - 'appMarketplaceURL', - 'appLogoURL', - 'appActionButtonText', - 'warpcastHandle', - 'data', - 'alertBgColor', - 'alertTextColor', - 'alertStatus', - 'cexDeposit', - ]; - - for (const stringField of stringFields) { - if (stringField in parsedMeta && typeof parsedMeta[stringField as keyof typeof parsedMeta] === 'string') { - result[stringField] = parsedMeta[stringField as keyof typeof parsedMeta]; - } - } - - return result; - } catch (error) { - return null; - } -} diff --git a/client/features/advanced-filter/components/AddressAdvancedFilterLink.tsx b/client/features/advanced-filter/components/AddressAdvancedFilterLink.tsx deleted file mode 100644 index f70f07cafd0..00000000000 --- a/client/features/advanced-filter/components/AddressAdvancedFilterLink.tsx +++ /dev/null @@ -1,56 +0,0 @@ -// SPDX-License-Identifier: LicenseRef-Blockscout - -import { pickBy } from 'es-toolkit'; -import React from 'react'; - -import type { AddressFromToFilter } from 'client/slices/address/types/api'; -import type { TokenType } from 'client/slices/token/types/api'; -import type { ClusterChainConfig } from 'types/multichain'; - -import useIsInitialLoading from 'client/shared/hooks/useIsInitialLoading'; - -import config from 'configs/app'; -import { useMultichainContext } from 'lib/contexts/multichain'; -import { getAdvancedFilterTypes } from 'ui/advancedFilter/constants'; -import AdvancedFilterLink from 'ui/shared/links/AdvancedFilterLink'; - -interface Props { - isLoading?: boolean; - address: string; - typeFilter: Array; - directionFilter: AddressFromToFilter; - chainData?: ClusterChainConfig; -} - -const AddressAdvancedFilterLink = ({ isLoading, address, typeFilter, directionFilter, chainData }: Props) => { - const isInitialLoading = useIsInitialLoading(isLoading); - const multichainContext = useMultichainContext(); - - const chainConfig = chainData?.app_config || multichainContext?.chain.app_config || config; - - if (!chainConfig?.features?.advancedFilter.isEnabled) { - return null; - } - - const advancedFilterTypes = getAdvancedFilterTypes(chainConfig); - - const queryParams = pickBy({ - to_address_hashes_to_include: !directionFilter || directionFilter === 'to' ? [ address ] : undefined, - from_address_hashes_to_include: !directionFilter || directionFilter === 'from' ? [ address ] : undefined, - transaction_types: typeFilter.length > 0 ? - typeFilter : - advancedFilterTypes.filter((type) => type.id !== 'coin_transfer').map((type) => type.id), - }, (value) => value !== undefined); - - const routeParams = (chainData ? { chain: chainData } : undefined) ?? multichainContext; - - return ( - - ); -}; - -export default React.memo(AddressAdvancedFilterLink); diff --git a/client/features/advanced-filter/pages/token/TokenAdvancedFilterLink.tsx b/client/features/advanced-filter/pages/token/TokenAdvancedFilterLink.tsx deleted file mode 100644 index 4adbc0b22e6..00000000000 --- a/client/features/advanced-filter/pages/token/TokenAdvancedFilterLink.tsx +++ /dev/null @@ -1,41 +0,0 @@ -// SPDX-License-Identifier: LicenseRef-Blockscout - -import React from 'react'; - -import type { TokenInfo } from 'client/slices/token/types/api'; - -import useIsInitialLoading from 'client/shared/hooks/useIsInitialLoading'; - -import config from 'configs/app'; -import { useMultichainContext } from 'lib/contexts/multichain'; -import type { LinkProps } from 'toolkit/chakra/link'; -import AdvancedFilterLink from 'ui/shared/links/AdvancedFilterLink'; - -interface Props extends LinkProps { - isLoading?: boolean; - token?: TokenInfo; -} - -const TokenAdvancedFilterLink = ({ isLoading, token, ...rest }: Props) => { - const isInitialLoading = useIsInitialLoading(isLoading); - const multichainContext = useMultichainContext(); - - if (!token || !config.features.advancedFilter.isEnabled) { - return null; - } - - const queryParams = { - token_contract_address_hashes_to_include: [ token.address_hash ], - }; - - return ( - - ); -}; - -export default React.memo(TokenAdvancedFilterLink); diff --git a/client/features/alternative-explorers/utils/explorers.ts b/client/features/alternative-explorers/utils/explorers.ts deleted file mode 100644 index f5e15bc4ef6..00000000000 --- a/client/features/alternative-explorers/utils/explorers.ts +++ /dev/null @@ -1,40 +0,0 @@ -// SPDX-License-Identifier: LicenseRef-Blockscout - -import { mapValues } from 'es-toolkit'; - -import type { NetworkExplorer } from 'types/networks'; - -import config from 'configs/app'; - -// for easy .env update -// const NETWORK_EXPLORERS = JSON.stringify([ -// { -// title: 'Anyblock', -// baseUrl: 'https://explorer.anyblock.tools', -// paths: { -// tx: '/ethereum/ethereum/goerli/transaction', -// address: '/ethereum/ethereum/goerli/address' -// }, -// }, -// { -// title: 'Etherscan', -// baseUrl: 'https://goerli.etherscan.io/', -// paths: { -// tx: '/tx', -// address: '/address', -// }, -// }, -// ]).replaceAll('"', '\''); - -const stripTrailingSlash = (str: string) => str[str.length - 1] === '/' ? str.slice(0, -1) : str; -const addLeadingSlash = (str: string) => str[0] === '/' ? str : '/' + str; - -const chainExplorers: Array = (() => { - return config.UI.explorers.items.map((explorer) => ({ - ...explorer, - baseUrl: stripTrailingSlash(explorer.baseUrl), - paths: mapValues(explorer.paths, (value) => value ? stripTrailingSlash(addLeadingSlash(value)) : value), - })); -})(); - -export default chainExplorers; diff --git a/client/features/bridged-tokens/hooks/useBridgedTokensQuery.ts b/client/features/bridged-tokens/hooks/useBridgedTokensQuery.ts deleted file mode 100644 index 5c2625544c3..00000000000 --- a/client/features/bridged-tokens/hooks/useBridgedTokensQuery.ts +++ /dev/null @@ -1,75 +0,0 @@ -// SPDX-License-Identifier: LicenseRef-Blockscout - -import { useRouter } from 'next/router'; -import React from 'react'; - -import type { TokensSortingValue, TokensSortingField, TokensSorting } from 'client/slices/token/types/api'; - -import { TOKEN_INFO_ERC_20 } from 'client/slices/token/stubs'; -import { SORT_OPTIONS } from 'client/slices/token/utils/list-utils'; - -import { getBridgedChainsFilterValue } from 'client/features/bridged-tokens/utils/bridged-chains-filter'; - -import useDebounce from 'client/shared/hooks/useDebounce'; -import getQueryParamString from 'client/shared/router/get-query-param-string'; - -import { generateListStub } from 'stubs/utils'; -import type { OnValueChangeHandler } from 'toolkit/chakra/select'; -import useQueryWithPages from 'ui/shared/pagination/useQueryWithPages'; -import getSortParamsFromValue from 'ui/shared/sort/getSortParamsFromValue'; -import getSortValueFromQuery from 'ui/shared/sort/getSortValueFromQuery'; - -interface Props { - enabled?: boolean; -} - -export default function useBridgedTokensQuery({ enabled }: Props) { - const router = useRouter(); - - const q = getQueryParamString(router.query.q); - - const [ searchTerm, setSearchTerm ] = React.useState(q ?? ''); - const [ bridgeChains, setBridgeChains ] = React.useState | undefined>(getBridgedChainsFilterValue(router.query.chain_ids)); - const [ sort, setSort ] = React.useState(getSortValueFromQuery(router.query, SORT_OPTIONS) ?? 'default'); - - const debouncedSearchTerm = useDebounce(searchTerm, 300); - const query = useQueryWithPages({ - resourceName: 'general:tokens_bridged', - filters: { q: debouncedSearchTerm, chain_ids: bridgeChains }, - sorting: getSortParamsFromValue(sort), - options: { - enabled, - placeholderData: generateListStub<'general:tokens_bridged'>( - TOKEN_INFO_ERC_20, - 50, - { next_page_params: { holders_count: 81528, items_count: 50, name: '', market_cap: null }, - }), - }, - }); - - const onSearchTermChange = React.useCallback((value: string) => { - query.onFilterChange({ q: value, chain_ids: bridgeChains }); - setSearchTerm(value); - }, [ bridgeChains, query ]); - - const onBridgeChainsChange = React.useCallback((value: Array) => { - query.onFilterChange({ q: debouncedSearchTerm, chain_ids: value }); - setBridgeChains(value); - }, [ debouncedSearchTerm, query ]); - - const onSortChange: OnValueChangeHandler = React.useCallback(({ value }) => { - const sortValue = value[0] as TokensSortingValue; - setSort(sortValue); - query.onSortingChange(getSortParamsFromValue(sortValue)); - }, [ query ]); - - return React.useMemo(() => ({ - query, - searchTerm, - bridgeChains, - sort, - onSearchTermChange, - onBridgeChainsChange, - onSortChange, - }), [ query, searchTerm, bridgeChains, sort, onSearchTermChange, onBridgeChainsChange, onSortChange ]); -} diff --git a/client/features/bridged-tokens/utils/bridged-chains-filter.ts b/client/features/bridged-tokens/utils/bridged-chains-filter.ts deleted file mode 100644 index f2a3a425310..00000000000 --- a/client/features/bridged-tokens/utils/bridged-chains-filter.ts +++ /dev/null @@ -1,16 +0,0 @@ -// SPDX-License-Identifier: LicenseRef-Blockscout - -import getFilterValuesFromQuery from 'client/shared/router/get-filter-values-from-query'; - -import config from 'configs/app'; - -const bridgedTokensChainIds = (() => { - const feature = config.features.bridgedTokens; - if (!feature.isEnabled) { - return []; - } - - return feature.chains.map(chain => chain.id); -})(); - -export const getBridgedChainsFilterValue = (getFilterValuesFromQuery).bind(null, bridgedTokensChainIds); diff --git a/client/features/chain-stats/mocks/home.ts b/client/features/chain-stats/mocks/home.ts deleted file mode 100644 index d3b7e3f69cb..00000000000 --- a/client/features/chain-stats/mocks/home.ts +++ /dev/null @@ -1,71 +0,0 @@ -import type * as stats from '@blockscout/stats-types'; - -import { averageGasPrice } from 'client/features/chain-stats/mocks/line'; - -export const base: stats.MainPageStats = { - average_block_time: { - id: 'averageBlockTime', - value: '14.909090909090908', - title: 'Average block time', - units: 's', - description: 'Average time taken in seconds for a block to be included in the blockchain', - }, - total_addresses: { - id: 'totalAddresses', - value: '113606435', - title: 'Total addresses', - description: 'Number of addresses that participated in the blockchain', - }, - total_blocks: { - id: 'totalBlocks', - value: '7660515', - title: 'Latest block', - description: 'Number of blocks over all time', - }, - total_transactions: { - id: 'totalTxns', - value: '411264599', - title: 'Total txns', - description: 'All transactions including pending, dropped, replaced, failed transactions', - }, - yesterday_transactions: { - id: 'yesterdayTxns', - value: '213019', - title: 'Yesterday txns', - description: 'Number of transactions yesterday (0:00 - 23:59 UTC)', - }, - total_operational_transactions: { - id: 'totalOperationalTxns', - value: '403598877', - title: 'Total operational txns', - description: '\'Total txns\' without block creation transactions', - }, - yesterday_operational_transactions: { - id: 'yesterdayOperationalTxns', - value: '210852', - title: 'Yesterday operational txns', - description: 'Number of transactions yesterday (0:00 - 23:59 UTC) without block creation transactions', - }, - daily_new_transactions: { - chart: averageGasPrice.chart, - info: { - id: 'newTxnsWindow', - title: 'Daily transactions', - description: 'The chart displays daily transactions for the past 30 days', - resolutions: [ - 'DAY', - ], - }, - }, - daily_new_operational_transactions: { - chart: averageGasPrice.chart, - info: { - id: 'newOperationalTxnsWindow', - title: 'Daily operational transactions', - description: 'The chart displays daily transactions for the past 30 days (without block creation transactions)', - resolutions: [ - 'DAY', - ], - }, - }, -}; diff --git a/client/features/chain-stats/pages/index/ChainStatsCounters.tsx b/client/features/chain-stats/pages/index/ChainStatsCounters.tsx deleted file mode 100644 index cb019b6d498..00000000000 --- a/client/features/chain-stats/pages/index/ChainStatsCounters.tsx +++ /dev/null @@ -1,60 +0,0 @@ -// SPDX-License-Identifier: LicenseRef-Blockscout - -import { Grid } from '@chakra-ui/react'; -import React from 'react'; - -import useApiQuery from 'client/api/hooks/useApiQuery'; - -import DataFetchAlert from 'ui/shared/DataFetchAlert'; -import StatsWidget from 'ui/shared/stats/StatsWidget'; - -import { CHAIN_STATS_COUNTER } from '../../stubs/counters'; - -const UNITS_WITHOUT_SPACE = [ 's' ]; - -const ChainStatsCounters = () => { - const { data, isPlaceholderData, isError } = useApiQuery('stats:counters', { - queryOptions: { - placeholderData: { counters: Array(10).fill(CHAIN_STATS_COUNTER) }, - }, - }); - - if (isError) { - return ; - } - - return ( - - { - data?.counters?.map(({ id, title, value, units, description }, index) => { - - let unitsStr = ''; - if (units && UNITS_WITHOUT_SPACE.includes(units)) { - unitsStr = units; - } else if (units) { - unitsStr = ' ' + units; - } - - const valueNum = Number(value); - const maximumFractionDigits = valueNum < 10 ** -3 ? undefined : 3; - - return ( - - ); - }) - } - - ); -}; - -export default ChainStatsCounters; diff --git a/client/features/chain-stats/stubs/home.ts b/client/features/chain-stats/stubs/home.ts deleted file mode 100644 index 48fed562f65..00000000000 --- a/client/features/chain-stats/stubs/home.ts +++ /dev/null @@ -1,22 +0,0 @@ -import type * as stats from '@blockscout/stats-types'; - -import { CHAIN_STATS_CHART_INFO } from 'client/features/chain-stats/stubs/charts'; -import { CHAIN_STATS_COUNTER } from 'client/features/chain-stats/stubs/counters'; - -export const HOMEPAGE_STATS_MICROSERVICE: stats.MainPageStats = { - average_block_time: CHAIN_STATS_COUNTER, - total_addresses: CHAIN_STATS_COUNTER, - total_blocks: CHAIN_STATS_COUNTER, - total_transactions: CHAIN_STATS_COUNTER, - yesterday_transactions: CHAIN_STATS_COUNTER, - total_operational_transactions: CHAIN_STATS_COUNTER, - yesterday_operational_transactions: CHAIN_STATS_COUNTER, - daily_new_transactions: { - chart: [], - info: { ...CHAIN_STATS_CHART_INFO, title: 'Daily transactions' }, - }, - daily_new_operational_transactions: { - chart: [], - info: { ...CHAIN_STATS_CHART_INFO, title: 'Daily op txns' }, - }, -}; diff --git a/client/features/chain-stats/types/client.ts b/client/features/chain-stats/types/client.ts deleted file mode 100644 index 57bd5d5d6a5..00000000000 --- a/client/features/chain-stats/types/client.ts +++ /dev/null @@ -1,46 +0,0 @@ -// SPDX-License-Identifier: LicenseRef-Blockscout - -import type { LineChartInfo, LineChartSection } from '@blockscout/stats-types'; -import type { LineChartItem } from 'toolkit/components/charts/line/types'; -import type { SankeyChartData } from 'toolkit/components/charts/sankey/types'; - -export type ChartType = 'line' | 'sankey'; - -export interface ChainStatsChart extends LineChartInfo { - resourceName?: 'interchainIndexer:stats_chain_messages_sent' | 'interchainIndexer:stats_chain_messages_received'; - type?: ChartType; -} - -export interface ChainStatsSection extends Omit { - charts: Array; -} - -export interface ChainStatsPayload { - sections: Array; -} - -export type ChartData = ChartDataPayloadLine | ChartDataPayloadSankey; - -export interface ChartDataPayloadLine { - type: 'line'; - info: LineChartInfo; - data: Array; -} - -export interface ChartDataPayloadSankey { - type: 'sankey'; - info: LineChartInfo; - data: SankeyChartData; -} - -export type StatsInterval = { id: StatsIntervalIds; title: string }; - -export type StatsIntervalIds = keyof typeof StatsIntervalId; - -export enum StatsIntervalId { - all = 'all', - oneMonth = 'oneMonth', - threeMonths = 'threeMonths', - sixMonths = 'sixMonths', - oneYear = 'oneYear', -} diff --git a/client/features/chain-stats/utils/chart.ts b/client/features/chain-stats/utils/chart.ts deleted file mode 100644 index de779573bfd..00000000000 --- a/client/features/chain-stats/utils/chart.ts +++ /dev/null @@ -1,10 +0,0 @@ -// SPDX-License-Identifier: LicenseRef-Blockscout - -import type { Route } from 'nextjs-routes'; -import { route } from 'nextjs-routes'; - -import config from 'configs/app'; - -export function getChartUrl(href?: Route) { - return href ? `${ config.app.baseUrl }${ route(href) }` : undefined; -} diff --git a/client/features/chain-variants/beacon-chain/components/BeaconChainDepositStatusTag.tsx b/client/features/chain-variants/beacon-chain/components/BeaconChainDepositStatusTag.tsx deleted file mode 100644 index f6be083b6a8..00000000000 --- a/client/features/chain-variants/beacon-chain/components/BeaconChainDepositStatusTag.tsx +++ /dev/null @@ -1,27 +0,0 @@ -// SPDX-License-Identifier: LicenseRef-Blockscout - -import { capitalize } from 'es-toolkit'; -import React from 'react'; - -import type { DepositsItem } from 'client/features/chain-variants/beacon-chain/types/api'; - -import StatusTag from 'ui/shared/statusTag/StatusTag'; - -const BeaconChainDepositStatusTag = ({ status, isLoading }: { status: DepositsItem['status']; isLoading: boolean }) => { - const statusValue = (() => { - switch (status) { - case 'pending': - return 'pending'; - case 'completed': - return 'ok'; - case 'invalid': - return 'error'; - default: - return 'pending'; - } - })(); - - return ; -}; - -export default BeaconChainDepositStatusTag; diff --git a/client/features/chain-variants/beacon-chain/mocks/block.ts b/client/features/chain-variants/beacon-chain/mocks/block.ts deleted file mode 100644 index 4ef93fb42e6..00000000000 --- a/client/features/chain-variants/beacon-chain/mocks/block.ts +++ /dev/null @@ -1,8 +0,0 @@ -import type { Block } from 'client/slices/block/types/api'; - -import { base } from 'client/slices/block/mocks/block'; - -export const withWithdrawals: Block = { - ...base, - withdrawals_count: 2, -}; diff --git a/client/features/chain-variants/beacon-chain/mocks/deposits.ts b/client/features/chain-variants/beacon-chain/mocks/deposits.ts deleted file mode 100644 index a8483ff03ea..00000000000 --- a/client/features/chain-variants/beacon-chain/mocks/deposits.ts +++ /dev/null @@ -1,86 +0,0 @@ -import type { DepositsResponse } from 'client/features/chain-variants/beacon-chain/types/api'; -import type { AddressParam } from 'client/slices/address/types/api'; - -export const data: DepositsResponse = { - items: [ - { - amount: '192175000000000', - block_number: 43242, - index: 11688, - pubkey: '0xf97e180c050e5Ab072211Ad2C213Eb5AEE4DF134', - signature: '0xf97e180c050e5Ab072211Ad2C213Eb5AEE4DF134', - status: 'completed', - from_address: { - hash: '0xf97e180c050e5Ab072211Ad2C213Eb5AEE4DF134', - implementations: null, - is_contract: false, - is_verified: null, - name: null, - } as AddressParam, - block_hash: '0xf97e180c050e5Ab072211Ad2C213Eb5AEE4DF134', - block_timestamp: '2022-06-07T18:12:24.000000Z', - transaction_hash: '0xf97e180c050e5Ab072211Ad2C213Eb5AEE4DF134', - withdrawal_address: { - hash: '0xf97e180c050e5Ab072211Ad2C213Eb5AEE4DF134', - implementations: null, - is_contract: false, - is_verified: null, - name: null, - } as AddressParam, - }, - { - amount: '192175000000000', - block_number: 43242, - index: 11687, - pubkey: '0xf97e180c050e5Ab072211Ad2C213Eb5AEE4DF134', - signature: '0xf97e180c050e5Ab072211Ad2C213Eb5AEE4DF134', - status: 'pending', - from_address: { - hash: '0xf97e987c050e5Ab072211Ad2C213Eb5AEE4DF134', - implementations: null, - is_contract: false, - is_verified: null, - name: null, - } as AddressParam, - block_hash: '0xf97e180c050e5Ab072211Ad2C213Eb5AEE4DF134', - block_timestamp: '2022-05-07T18:12:24.000000Z', - transaction_hash: '0xf97e180c050e5Ab072211Ad2C213Eb5AEE4DF134', - withdrawal_address: { - hash: '0xf97e180c050e5Ab072211Ad2C213Eb5AEE4DF134', - implementations: null, - is_contract: false, - is_verified: null, - name: null, - } as AddressParam, - }, - { - amount: '182773000000000', - block_number: 43242, - index: 11686, - pubkey: '0xf97e180c050e5Ab072211Ad2C213Eb5AEE4DF134', - signature: '0xf97e180c050e5Ab072211Ad2C213Eb5AEE4DF134', - status: 'invalid', - from_address: { - hash: '0xf97e123c050e5Ab072211Ad2C213Eb5AEE4DF134', - implementations: null, - is_contract: false, - is_verified: null, - name: null, - } as AddressParam, - block_hash: '0xf97e180c050e5Ab072211Ad2C213Eb5AEE4DF134', - block_timestamp: '2022-04-07T18:12:24.000000Z', - transaction_hash: '0xf97e180c050e5Ab072211Ad2C213Eb5AEE4DF134', - withdrawal_address: { - hash: '0xf97e180c050e5Ab072211Ad2C213Eb5AEE4DF134', - implementations: null, - is_contract: false, - is_verified: null, - name: null, - } as AddressParam, - }, - ], - next_page_params: { - index: 11639, - items_count: 50, - }, -}; diff --git a/client/features/chain-variants/beacon-chain/mocks/withdrawals.ts b/client/features/chain-variants/beacon-chain/mocks/withdrawals.ts deleted file mode 100644 index 44173079ba0..00000000000 --- a/client/features/chain-variants/beacon-chain/mocks/withdrawals.ts +++ /dev/null @@ -1,53 +0,0 @@ -import type { WithdrawalsResponse } from 'client/features/chain-variants/beacon-chain/types/api'; -import type { AddressParam } from 'client/slices/address/types/api'; - -export const data: WithdrawalsResponse = { - items: [ - { - amount: '192175000000000', - block_number: 43242, - index: 11688, - receiver: { - hash: '0xf97e180c050e5Ab072211Ad2C213Eb5AEE4DF134', - implementations: null, - is_contract: false, - is_verified: null, - name: null, - } as AddressParam, - timestamp: '2022-06-07T18:12:24.000000Z', - validator_index: 49622, - }, - { - amount: '192175000000000', - block_number: 43242, - index: 11687, - receiver: { - hash: '0xf97e987c050e5Ab072211Ad2C213Eb5AEE4DF134', - implementations: null, - is_contract: false, - is_verified: null, - name: null, - } as AddressParam, - timestamp: '2022-05-07T18:12:24.000000Z', - validator_index: 49621, - }, - { - amount: '182773000000000', - block_number: 43242, - index: 11686, - receiver: { - hash: '0xf97e123c050e5Ab072211Ad2C213Eb5AEE4DF134', - implementations: null, - is_contract: false, - is_verified: null, - name: null, - } as AddressParam, - timestamp: '2022-04-07T18:12:24.000000Z', - validator_index: 49620, - }, - ], - next_page_params: { - index: 11639, - items_count: 50, - }, -}; diff --git a/client/features/chain-variants/beacon-chain/pages/address/AddressDeposits.tsx b/client/features/chain-variants/beacon-chain/pages/address/AddressDeposits.tsx deleted file mode 100644 index 666bada2d10..00000000000 --- a/client/features/chain-variants/beacon-chain/pages/address/AddressDeposits.tsx +++ /dev/null @@ -1,87 +0,0 @@ -// SPDX-License-Identifier: LicenseRef-Blockscout - -import { Box } from '@chakra-ui/react'; -import { useRouter } from 'next/router'; -import React from 'react'; - -import useIsMounted from 'client/shared/hooks/useIsMounted'; -import getQueryParamString from 'client/shared/router/get-query-param-string'; - -import { generateListStub } from 'stubs/utils'; -import ActionBar, { ACTION_BAR_HEIGHT_DESKTOP } from 'ui/shared/ActionBar'; -import DataListDisplay from 'ui/shared/DataListDisplay'; -import Pagination from 'ui/shared/pagination/Pagination'; -import useQueryWithPages from 'ui/shared/pagination/useQueryWithPages'; - -import { DEPOSIT } from '../../stubs/deposits'; -import BeaconChainDepositsListItem from '../deposits/BeaconChainDepositsListItem'; -import BeaconChainDepositsTable from '../deposits/BeaconChainDepositsTable'; - -type Props = { - shouldRender?: boolean; - isQueryEnabled?: boolean; -}; -const AddressDeposits = ({ shouldRender = true, isQueryEnabled = true }: Props) => { - const router = useRouter(); - const isMounted = useIsMounted(); - - const hash = getQueryParamString(router.query.hash); - - const { data, isPlaceholderData, isError, pagination } = useQueryWithPages({ - resourceName: 'general:address_deposits', - pathParams: { hash }, - options: { - enabled: isQueryEnabled, - placeholderData: generateListStub<'general:address_deposits'>(DEPOSIT, 50, { next_page_params: { - index: 5, - items_count: 50, - } }), - }, - }); - - if (!isMounted || !shouldRender) { - return null; - } - - const content = data?.items ? ( - <> - - { data.items.map((item, index) => ( - - )) } - - - - - - ) : null ; - - const actionBar = pagination.isVisible ? ( - - - - ) : null; - - return ( - - { content } - - ); -}; - -export default AddressDeposits; diff --git a/client/features/chain-variants/beacon-chain/pages/block/useBlockDepositsQuery.ts b/client/features/chain-variants/beacon-chain/pages/block/useBlockDepositsQuery.ts deleted file mode 100644 index d293a5694ce..00000000000 --- a/client/features/chain-variants/beacon-chain/pages/block/useBlockDepositsQuery.ts +++ /dev/null @@ -1,46 +0,0 @@ -// SPDX-License-Identifier: LicenseRef-Blockscout - -import type { BlockQuery } from 'client/slices/block/hooks/useBlockQuery'; - -import config from 'configs/app'; -import { generateListStub } from 'stubs/utils'; -import type { QueryWithPagesResult } from 'ui/shared/pagination/useQueryWithPages'; -import useQueryWithPages from 'ui/shared/pagination/useQueryWithPages'; - -import { DEPOSIT } from '../../stubs/deposits'; - -export type BlockDepositsQuery = QueryWithPagesResult<'general:block_deposits'> & { - isDegradedData: boolean; -}; - -interface Params { - heightOrHash: string; - blockQuery: BlockQuery; - tab: string; -} - -const beaconChainFeature = config.features.beaconChain; - -// No deposits data in RPC, so we use API only -export default function useBlockDepositsQuery({ heightOrHash, blockQuery, tab }: Params): BlockDepositsQuery { - const apiQuery = useQueryWithPages({ - resourceName: 'general:block_deposits', - pathParams: { height_or_hash: heightOrHash }, - options: { - enabled: - tab === 'deposits' && - beaconChainFeature.isEnabled && !beaconChainFeature.withdrawalsOnly && - !blockQuery.isPlaceholderData && !blockQuery.isDegradedData, - placeholderData: generateListStub<'general:block_deposits'>(DEPOSIT, 50, { next_page_params: { - index: 5, - items_count: 50, - } }), - refetchOnMount: false, - }, - }); - - return { - ...apiQuery, - isDegradedData: false, - }; -} diff --git a/client/features/chain-variants/beacon-chain/pages/block/useBlockWithdrawalsQuery.ts b/client/features/chain-variants/beacon-chain/pages/block/useBlockWithdrawalsQuery.ts deleted file mode 100644 index 514742a190e..00000000000 --- a/client/features/chain-variants/beacon-chain/pages/block/useBlockWithdrawalsQuery.ts +++ /dev/null @@ -1,148 +0,0 @@ -// SPDX-License-Identifier: LicenseRef-Blockscout - -import type { UseQueryResult } from '@tanstack/react-query'; -import { useQuery } from '@tanstack/react-query'; -import React from 'react'; -import type { Chain, GetBlockReturnType } from 'viem'; - -import type { BlockWithdrawalsResponse } from 'client/features/chain-variants/beacon-chain/types/api'; - -import { retry } from 'client/api/hooks/useQueryClientConfig'; -import type { ResourceError } from 'client/api/resources'; - -import { unknownAddress } from 'client/slices/address/utils/consts'; -import type { BlockQuery } from 'client/slices/block/hooks/useBlockQuery'; - -import { publicClient } from 'client/features/connect-wallet/utils/public-client'; - -import hexToDecimal from 'client/shared/transformers/hex-to-decimal'; - -import config from 'configs/app'; -import { GET_BLOCK } from 'stubs/RPC'; -import { generateListStub } from 'stubs/utils'; -import { SECOND } from 'toolkit/utils/consts'; -import type { QueryWithPagesResult } from 'ui/shared/pagination/useQueryWithPages'; -import useQueryWithPages from 'ui/shared/pagination/useQueryWithPages'; -import { emptyPagination } from 'ui/shared/pagination/utils'; - -import { WITHDRAWAL } from '../../stubs/withdrawals'; - -type RpcResponseType = GetBlockReturnType | null; - -export type BlockWithdrawalsQuery = QueryWithPagesResult<'general:block_withdrawals'> & { - isDegradedData: boolean; -}; - -interface Params { - heightOrHash: string; - blockQuery: BlockQuery; - tab: string; -} - -export default function useBlockWithdrawalsQuery({ heightOrHash, blockQuery, tab }: Params): BlockWithdrawalsQuery { - const [ isRefetchEnabled, setRefetchEnabled ] = React.useState(false); - - const apiQuery = useQueryWithPages({ - resourceName: 'general:block_withdrawals', - pathParams: { height_or_hash: heightOrHash }, - options: { - enabled: - tab === 'withdrawals' && - config.features.beaconChain.isEnabled && - !blockQuery.isPlaceholderData && !blockQuery.isDegradedData, - placeholderData: generateListStub<'general:block_withdrawals'>(WITHDRAWAL, 50, { next_page_params: { - index: 5, - items_count: 50, - } }), - refetchOnMount: false, - retry: (failureCount, error) => { - if (isRefetchEnabled) { - return false; - } - - return retry(failureCount, error); - }, - refetchInterval: (): number | false => { - return isRefetchEnabled ? 15 * SECOND : false; - }, - }, - }); - - const rpcQuery = useQuery({ - queryKey: [ 'RPC', 'block', { heightOrHash } ], - queryFn: async() => { - if (!publicClient) { - return null; - } - - const blockParams = heightOrHash.startsWith('0x') ? { blockHash: heightOrHash as `0x${ string }` } : { blockNumber: BigInt(heightOrHash) }; - return publicClient.getBlock(blockParams).catch(() => null); - }, - select: (block) => { - if (!block) { - return null; - } - - return { - items: block.withdrawals - ?.map((withdrawal) => { - return { - amount: hexToDecimal(withdrawal.amount).toString(), - index: hexToDecimal(withdrawal.index), - validator_index: hexToDecimal(withdrawal.validatorIndex), - receiver: { ...unknownAddress, hash: withdrawal.address }, - }; - }) - .sort((a, b) => b.index - a.index) ?? [], - next_page_params: null, - }; - }, - placeholderData: GET_BLOCK, - enabled: - publicClient !== undefined && - tab === 'withdrawals' && - config.features.beaconChain.isEnabled && - (blockQuery.isDegradedData || apiQuery.isError || apiQuery.errorUpdateCount > 0), - retry: false, - refetchOnMount: false, - }); - - React.useEffect(() => { - if (apiQuery.isPlaceholderData || !publicClient) { - return; - } - - if (apiQuery.isError && apiQuery.errorUpdateCount === 1) { - setRefetchEnabled(true); - } else if (!apiQuery.isError) { - setRefetchEnabled(false); - } - }, [ apiQuery.errorUpdateCount, apiQuery.isError, apiQuery.isPlaceholderData ]); - - React.useEffect(() => { - if (!rpcQuery.isPlaceholderData && !rpcQuery.data) { - setRefetchEnabled(false); - } - }, [ rpcQuery.data, rpcQuery.isPlaceholderData ]); - - const isRpcQuery = Boolean(( - blockQuery.isDegradedData || - ((apiQuery.isError || apiQuery.isPlaceholderData) && apiQuery.errorUpdateCount > 0) - ) && rpcQuery.data && publicClient); - - const rpcQueryWithPages: QueryWithPagesResult<'general:block_withdrawals'> = { - ...rpcQuery as UseQueryResult, - pagination: emptyPagination, - onFilterChange: () => {}, - onSortingChange: () => {}, - chainValue: undefined, - onChainValueChange: () => {}, - }; - - const query = isRpcQuery ? rpcQueryWithPages : apiQuery; - - return { - ...query, - isDegradedData: isRpcQuery, - }; -} diff --git a/client/features/chain-variants/beacon-chain/pages/deposits/BeaconChainDeposits.tsx b/client/features/chain-variants/beacon-chain/pages/deposits/BeaconChainDeposits.tsx deleted file mode 100644 index d389c39a966..00000000000 --- a/client/features/chain-variants/beacon-chain/pages/deposits/BeaconChainDeposits.tsx +++ /dev/null @@ -1,102 +0,0 @@ -// SPDX-License-Identifier: LicenseRef-Blockscout - -import { Box, Text } from '@chakra-ui/react'; -import BigNumber from 'bignumber.js'; -import React from 'react'; - -import useApiQuery from 'client/api/hooks/useApiQuery'; - -import config from 'configs/app'; -import { generateListStub } from 'stubs/utils'; -import { Skeleton } from 'toolkit/chakra/skeleton'; -import { ACTION_BAR_HEIGHT_DESKTOP } from 'ui/shared/ActionBar'; -import DataListDisplay from 'ui/shared/DataListDisplay'; -import PageTitle from 'ui/shared/Page/PageTitle'; -import useQueryWithPages from 'ui/shared/pagination/useQueryWithPages'; -import StickyPaginationWithText from 'ui/shared/StickyPaginationWithText'; - -import { DEPOSIT } from '../../stubs/deposits'; -import BeaconChainDepositsListItem from './BeaconChainDepositsListItem'; -import BeaconChainDepositsTable from './BeaconChainDepositsTable'; - -const feature = config.features.beaconChain; - -const BeaconChainDeposits = () => { - const { data, isError, isPlaceholderData, pagination } = useQueryWithPages({ - resourceName: 'general:deposits', - options: { - placeholderData: generateListStub<'general:deposits'>(DEPOSIT, 50, { next_page_params: { - index: 5, - items_count: 50, - } }), - }, - }); - - const countersQuery = useApiQuery('general:deposits_counters', { - queryOptions: { - placeholderData: { - deposits_count: '19091878', - }, - }, - }); - - const content = data?.items ? ( - <> - - { data.items.map(((item, index) => ( - - ))) } - - - - - - ) : null; - - const text = (() => { - if (countersQuery.isError || !feature.isEnabled || feature.withdrawalsOnly) { - return null; - } - - return ( - - { countersQuery.data && ( - - { BigNumber(countersQuery.data.deposits_count).toFormat() } deposits processed - - ) } - - ); - })(); - - const actionBar = ; - - return ( - <> - - - { content } - - - ); -}; - -export default BeaconChainDeposits; diff --git a/client/features/chain-variants/beacon-chain/pages/deposits/BeaconChainDepositsListItem.tsx b/client/features/chain-variants/beacon-chain/pages/deposits/BeaconChainDepositsListItem.tsx deleted file mode 100644 index e6d1a9b5e07..00000000000 --- a/client/features/chain-variants/beacon-chain/pages/deposits/BeaconChainDepositsListItem.tsx +++ /dev/null @@ -1,103 +0,0 @@ -// SPDX-License-Identifier: LicenseRef-Blockscout - -import React from 'react'; - -import type { DepositsItem } from 'client/features/chain-variants/beacon-chain/types/api'; - -import AddressEntity from 'client/slices/address/components/entity/AddressEntity'; -import BlockEntity from 'client/slices/block/components/entity/BlockEntity'; -import TxEntity from 'client/slices/tx/components/entity/TxEntity'; - -import config from 'configs/app'; -import ListItemMobileGrid from 'ui/shared/ListItemMobile/ListItemMobileGrid'; -import TimeWithTooltip from 'ui/shared/time/TimeWithTooltip'; -import NativeCoinValue from 'ui/shared/value/NativeCoinValue'; - -import BeaconChainDepositSignature from '../../components/BeaconChainDepositSignature'; -import BeaconChainDepositStatusTag from '../../components/BeaconChainDepositStatusTag'; -import BeaconChainValidatorLink from '../../components/BeaconChainValidatorLink'; - -const feature = config.features.beaconChain; - -type Props = { - item: DepositsItem; - view: 'list' | 'address' | 'block'; - isLoading?: boolean; -}; - -const BeaconChainDepositsListItem = ({ item, isLoading, view }: Props) => { - if (!feature.isEnabled || feature.withdrawalsOnly) { - return null; - } - - return ( - - - Transaction hash - - - - - { view !== 'block' && ( - <> - Block - - - - - Age - - - - - ) } - - Value - - - - - { view !== 'address' && ( - <> - From - - - - - ) } - - PubKey - - - - - Signature - - - - - Status - - - - - - ); -}; - -export default BeaconChainDepositsListItem; diff --git a/client/features/chain-variants/beacon-chain/pages/deposits/BeaconChainDepositsTableItem.tsx b/client/features/chain-variants/beacon-chain/pages/deposits/BeaconChainDepositsTableItem.tsx deleted file mode 100644 index 96418b9054b..00000000000 --- a/client/features/chain-variants/beacon-chain/pages/deposits/BeaconChainDepositsTableItem.tsx +++ /dev/null @@ -1,82 +0,0 @@ -// SPDX-License-Identifier: LicenseRef-Blockscout - -import React from 'react'; - -import type { DepositsItem } from 'client/features/chain-variants/beacon-chain/types/api'; - -import AddressEntity from 'client/slices/address/components/entity/AddressEntity'; -import BlockEntity from 'client/slices/block/components/entity/BlockEntity'; -import TxEntity from 'client/slices/tx/components/entity/TxEntity'; - -import { TableCell, TableRow } from 'toolkit/chakra/table'; -import TimeWithTooltip from 'ui/shared/time/TimeWithTooltip'; -import NativeCoinValue from 'ui/shared/value/NativeCoinValue'; - -import BeaconChainDepositSignature from '../../components/BeaconChainDepositSignature'; -import BeaconChainDepositStatusTag from '../../components/BeaconChainDepositStatusTag'; -import BeaconChainValidatorLink from '../../components/BeaconChainValidatorLink'; - -type Props = { - item: DepositsItem; - view: 'list' | 'address' | 'block'; - isLoading?: boolean; -}; - -const BeaconChainDepositsTableItem = ({ item, view, isLoading }: Props) => { - return ( - - - - - { view !== 'block' && ( - - - - ) } - { view !== 'block' && ( - - - - ) } - - - - { view !== 'address' && ( - - - - ) } - - - - - - - - - - - ); -}; - -export default BeaconChainDepositsTableItem; diff --git a/client/features/chain-variants/beacon-chain/pages/deposits/__screenshots__/BeaconChainDeposits.pw.tsx_default_base-view-1.png b/client/features/chain-variants/beacon-chain/pages/deposits/__screenshots__/BeaconChainDeposits.pw.tsx_default_base-view-1.png deleted file mode 100644 index 349bb639d6e..00000000000 Binary files a/client/features/chain-variants/beacon-chain/pages/deposits/__screenshots__/BeaconChainDeposits.pw.tsx_default_base-view-1.png and /dev/null differ diff --git a/client/features/chain-variants/beacon-chain/pages/deposits/__screenshots__/BeaconChainDeposits.pw.tsx_default_mobile-base-view-1.png b/client/features/chain-variants/beacon-chain/pages/deposits/__screenshots__/BeaconChainDeposits.pw.tsx_default_mobile-base-view-1.png deleted file mode 100644 index a8c003a070e..00000000000 Binary files a/client/features/chain-variants/beacon-chain/pages/deposits/__screenshots__/BeaconChainDeposits.pw.tsx_default_mobile-base-view-1.png and /dev/null differ diff --git a/client/features/chain-variants/beacon-chain/pages/withdrawals/BeaconChainWithdrawals.pw.tsx b/client/features/chain-variants/beacon-chain/pages/withdrawals/BeaconChainWithdrawals.pw.tsx deleted file mode 100644 index 506867ad10b..00000000000 --- a/client/features/chain-variants/beacon-chain/pages/withdrawals/BeaconChainWithdrawals.pw.tsx +++ /dev/null @@ -1,16 +0,0 @@ -import React from 'react'; - -import { ENVS_MAP } from 'playwright/fixtures/mockEnvs'; -import { test, expect } from 'playwright/lib'; - -import { data as withdrawalsData } from '../../mocks/withdrawals'; -import BeaconChainWithdrawals from './BeaconChainWithdrawals'; - -test('base view +@mobile', async({ render, mockEnvs, mockTextAd, mockApiResponse }) => { - await mockEnvs(ENVS_MAP.beaconChain); - await mockTextAd(); - await mockApiResponse('general:withdrawals', withdrawalsData); - await mockApiResponse('general:withdrawals_counters', { withdrawals_count: '111111', withdrawals_sum: '1010101010110101001101010' }); - const component = await render(); - await expect(component).toHaveScreenshot(); -}); diff --git a/client/features/chain-variants/beacon-chain/pages/withdrawals/BeaconChainWithdrawals.tsx b/client/features/chain-variants/beacon-chain/pages/withdrawals/BeaconChainWithdrawals.tsx deleted file mode 100644 index 106204bc431..00000000000 --- a/client/features/chain-variants/beacon-chain/pages/withdrawals/BeaconChainWithdrawals.tsx +++ /dev/null @@ -1,107 +0,0 @@ -// SPDX-License-Identifier: LicenseRef-Blockscout - -import { Box, Text } from '@chakra-ui/react'; -import BigNumber from 'bignumber.js'; -import React from 'react'; - -import useApiQuery from 'client/api/hooks/useApiQuery'; - -import { currencyUnits } from 'client/shared/chain/units'; - -import config from 'configs/app'; -import { generateListStub } from 'stubs/utils'; -import { Skeleton } from 'toolkit/chakra/skeleton'; -import { ACTION_BAR_HEIGHT_DESKTOP } from 'ui/shared/ActionBar'; -import DataListDisplay from 'ui/shared/DataListDisplay'; -import PageTitle from 'ui/shared/Page/PageTitle'; -import useQueryWithPages from 'ui/shared/pagination/useQueryWithPages'; -import StickyPaginationWithText from 'ui/shared/StickyPaginationWithText'; -import calculateUsdValue from 'ui/shared/value/calculateUsdValue'; - -import { WITHDRAWAL } from '../../stubs/withdrawals'; -import BeaconChainWithdrawalsListItem from './BeaconChainWithdrawalsListItem'; -import BeaconChainWithdrawalsTable from './BeaconChainWithdrawalsTable'; - -const feature = config.features.beaconChain; - -const BeaconChainWithdrawals = () => { - const { data, isError, isPlaceholderData, pagination } = useQueryWithPages({ - resourceName: 'general:withdrawals', - options: { - placeholderData: generateListStub<'general:withdrawals'>(WITHDRAWAL, 50, { next_page_params: { - index: 5, - items_count: 50, - } }), - }, - }); - - const countersQuery = useApiQuery('general:withdrawals_counters', { - queryOptions: { - placeholderData: { - withdrawals_count: '19091878', - withdrawals_sum: '4630710684332438', - }, - }, - }); - - const content = data?.items ? ( - <> - - { data.items.map(((item, index) => ( - - ))) } - - - - - - ) : null; - - const text = (() => { - if (countersQuery.isError || !feature.isEnabled) { - return null; - } - - return ( - - { countersQuery.data && ( - - { BigNumber(countersQuery.data.withdrawals_count).toFormat() } withdrawals processed - and { calculateUsdValue({ amount: countersQuery.data.withdrawals_sum }).valueStr } { currencyUnits.ether } withdrawn - - ) } - - ); - })(); - - const actionBar = ; - - return ( - <> - - - { content } - - - ); -}; - -export default BeaconChainWithdrawals; diff --git a/client/features/chain-variants/beacon-chain/pages/withdrawals/BeaconChainWithdrawalsList.tsx b/client/features/chain-variants/beacon-chain/pages/withdrawals/BeaconChainWithdrawalsList.tsx deleted file mode 100644 index 2724a0e6f7a..00000000000 --- a/client/features/chain-variants/beacon-chain/pages/withdrawals/BeaconChainWithdrawalsList.tsx +++ /dev/null @@ -1,74 +0,0 @@ -// SPDX-License-Identifier: LicenseRef-Blockscout - -import { Box } from '@chakra-ui/react'; -import React from 'react'; - -import type { WithdrawalsItem } from 'client/features/chain-variants/beacon-chain/types/api'; -import type { AddressWithdrawalsItem } from 'client/slices/address/types/api'; -import type { BlockWithdrawalsItem } from 'client/slices/block/types/api'; - -import useLazyRenderedList from 'client/shared/lists/useLazyRenderedList'; - -import BeaconChainWithdrawalsListItem from './BeaconChainWithdrawalsListItem'; - -type Props = { - isLoading?: boolean; -} & ({ - items: Array; - view: 'list'; -} | { - items: Array; - view: 'address'; -} | { - items: Array; - view: 'block'; -}); - -const WithdrawalsList = ({ items, view, isLoading }: Props) => { - const { cutRef, renderedItemsNum } = useLazyRenderedList(items, !isLoading); - - return ( - - { items.slice(0, renderedItemsNum).map((item, index) => { - - const key = item.index + (isLoading ? String(index) : ''); - - switch (view) { - case 'address': { - return ( - - ); - } - case 'block': { - return ( - - ); - } - case 'list': { - return ( - - ); - } - } - }) } -
- - ); -}; - -export default React.memo(WithdrawalsList); diff --git a/client/features/chain-variants/beacon-chain/pages/withdrawals/BeaconChainWithdrawalsListItem.tsx b/client/features/chain-variants/beacon-chain/pages/withdrawals/BeaconChainWithdrawalsListItem.tsx deleted file mode 100644 index 7ab155c2561..00000000000 --- a/client/features/chain-variants/beacon-chain/pages/withdrawals/BeaconChainWithdrawalsListItem.tsx +++ /dev/null @@ -1,96 +0,0 @@ -// SPDX-License-Identifier: LicenseRef-Blockscout - -import React from 'react'; - -import type { WithdrawalsItem } from 'client/features/chain-variants/beacon-chain/types/api'; -import type { AddressWithdrawalsItem } from 'client/slices/address/types/api'; -import type { BlockWithdrawalsItem } from 'client/slices/block/types/api'; - -import AddressEntity from 'client/slices/address/components/entity/AddressEntity'; -import BlockEntity from 'client/slices/block/components/entity/BlockEntity'; - -import config from 'configs/app'; -import { Skeleton } from 'toolkit/chakra/skeleton'; -import ListItemMobileGrid from 'ui/shared/ListItemMobile/ListItemMobileGrid'; -import TimeWithTooltip from 'ui/shared/time/TimeWithTooltip'; -import NativeCoinValue from 'ui/shared/value/NativeCoinValue'; - -const feature = config.features.beaconChain; - -type Props = ({ - item: WithdrawalsItem; - view: 'list'; -} | { - item: AddressWithdrawalsItem; - view: 'address'; -} | { - item: BlockWithdrawalsItem; - view: 'block'; -}) & { isLoading?: boolean }; - -const BeaconChainWithdrawalsListItem = ({ item, isLoading, view }: Props) => { - if (!feature.isEnabled) { - return null; - } - - return ( - - - Index - - { item.index } - - - Validator index - - { item.validator_index } - - - { view !== 'block' && ( - <> - Block - - - - - ) } - - { view !== 'address' && ( - <> - To - - - - ) } - - { view !== 'block' && ( - <> - Age - - - - - Value - - - - - ) } - - - ); -}; - -export default BeaconChainWithdrawalsListItem; diff --git a/client/features/chain-variants/beacon-chain/pages/withdrawals/BeaconChainWithdrawalsTable.tsx b/client/features/chain-variants/beacon-chain/pages/withdrawals/BeaconChainWithdrawalsTable.tsx deleted file mode 100644 index d0656a67d56..00000000000 --- a/client/features/chain-variants/beacon-chain/pages/withdrawals/BeaconChainWithdrawalsTable.tsx +++ /dev/null @@ -1,72 +0,0 @@ -// SPDX-License-Identifier: LicenseRef-Blockscout - -import React from 'react'; - -import type { WithdrawalsItem } from 'client/features/chain-variants/beacon-chain/types/api'; -import type { AddressWithdrawalsItem } from 'client/slices/address/types/api'; -import type { BlockWithdrawalsItem } from 'client/slices/block/types/api'; - -import { AddressHighlightProvider } from 'client/slices/address/contexts/address-highlight'; - -import useLazyRenderedList from 'client/shared/lists/useLazyRenderedList'; - -import config from 'configs/app'; -import { TableBody, TableColumnHeader, TableHeaderSticky, TableRoot, TableRow } from 'toolkit/chakra/table'; -import TimeFormatToggle from 'ui/shared/time/TimeFormatToggle'; - -import BeaconChainWithdrawalsTableItem from './BeaconChainWithdrawalsTableItem'; - -const feature = config.features.beaconChain; - -type Props = { - top: number; - isLoading?: boolean; -} & ({ - items: Array; - view: 'list'; -} | { - items: Array; - view: 'address'; -} | { - items: Array; - view: 'block'; -}); - -const BeaconChainWithdrawalsTable = ({ items, isLoading, top, view }: Props) => { - const { cutRef, renderedItemsNum } = useLazyRenderedList(items, !isLoading); - - if (!feature.isEnabled) { - return null; - } - - return ( - - - - - Index - Validator index - { view !== 'block' && Block } - { view !== 'address' && To } - { view !== 'block' && Timestamp } - { `Value ${ feature.currency.symbol }` } - - - - { view === 'list' && (items as Array).slice(0, renderedItemsNum).map((item, index) => ( - - )) } - { view === 'address' && (items as Array).slice(0, renderedItemsNum).map((item, index) => ( - - )) } - { view === 'block' && (items as Array).slice(0, renderedItemsNum).map((item, index) => ( - - )) } - - - - - ); -}; - -export default BeaconChainWithdrawalsTable; diff --git a/client/features/chain-variants/beacon-chain/pages/withdrawals/BeaconChainWithdrawalsTableItem.tsx b/client/features/chain-variants/beacon-chain/pages/withdrawals/BeaconChainWithdrawalsTableItem.tsx deleted file mode 100644 index 6eed5d2a7a8..00000000000 --- a/client/features/chain-variants/beacon-chain/pages/withdrawals/BeaconChainWithdrawalsTableItem.tsx +++ /dev/null @@ -1,73 +0,0 @@ -// SPDX-License-Identifier: LicenseRef-Blockscout - -import React from 'react'; - -import type { WithdrawalsItem } from 'client/features/chain-variants/beacon-chain/types/api'; -import type { AddressWithdrawalsItem } from 'client/slices/address/types/api'; -import type { BlockWithdrawalsItem } from 'client/slices/block/types/api'; - -import AddressEntity from 'client/slices/address/components/entity/AddressEntity'; -import BlockEntity from 'client/slices/block/components/entity/BlockEntity'; - -import { Skeleton } from 'toolkit/chakra/skeleton'; -import { TableCell, TableRow } from 'toolkit/chakra/table'; -import TimeWithTooltip from 'ui/shared/time/TimeWithTooltip'; -import NativeCoinValue from 'ui/shared/value/NativeCoinValue'; - -type Props = ({ - item: WithdrawalsItem; - view: 'list'; -} | { - item: AddressWithdrawalsItem; - view: 'address'; -} | { - item: BlockWithdrawalsItem; - view: 'block'; -}) & { isLoading?: boolean }; - -const BeaconChainWithdrawalsTableItem = ({ item, view, isLoading }: Props) => { - return ( - - - { item.index } - - - { item.validator_index } - - { view !== 'block' && ( - - - - ) } - { view !== 'address' && ( - - - - ) } - { view !== 'block' && ( - - - - ) } - - - - - ); -}; - -export default BeaconChainWithdrawalsTableItem; diff --git a/client/features/chain-variants/beacon-chain/pages/withdrawals/__screenshots__/BeaconChainWithdrawals.pw.tsx_default_base-view-mobile-1.png b/client/features/chain-variants/beacon-chain/pages/withdrawals/__screenshots__/BeaconChainWithdrawals.pw.tsx_default_base-view-mobile-1.png deleted file mode 100644 index 807407f4576..00000000000 Binary files a/client/features/chain-variants/beacon-chain/pages/withdrawals/__screenshots__/BeaconChainWithdrawals.pw.tsx_default_base-view-mobile-1.png and /dev/null differ diff --git a/client/features/chain-variants/beacon-chain/pages/withdrawals/__screenshots__/BeaconChainWithdrawals.pw.tsx_mobile_base-view-mobile-1.png b/client/features/chain-variants/beacon-chain/pages/withdrawals/__screenshots__/BeaconChainWithdrawals.pw.tsx_mobile_base-view-mobile-1.png deleted file mode 100644 index 439d88aeba4..00000000000 Binary files a/client/features/chain-variants/beacon-chain/pages/withdrawals/__screenshots__/BeaconChainWithdrawals.pw.tsx_mobile_base-view-mobile-1.png and /dev/null differ diff --git a/client/features/chain-variants/beacon-chain/stubs/deposits.ts b/client/features/chain-variants/beacon-chain/stubs/deposits.ts deleted file mode 100644 index e9d29257ad9..00000000000 --- a/client/features/chain-variants/beacon-chain/stubs/deposits.ts +++ /dev/null @@ -1,18 +0,0 @@ -import type { DepositsItem } from 'client/features/chain-variants/beacon-chain/types/api'; - -import { ADDRESS_PARAMS } from 'client/slices/address/stubs/address-params'; -import { TX_HASH } from 'client/slices/tx/stubs/tx'; - -export const DEPOSIT: DepositsItem = { - amount: '12565723', - index: 1, - block_number: 1231111111, - block_hash: '0x1234567890', - block_timestamp: '2023-05-12T19:29:12.000000Z', - pubkey: '0x1234567890123456789012345678901234567890', - status: 'pending', - from_address: ADDRESS_PARAMS, - transaction_hash: TX_HASH, - withdrawal_address: ADDRESS_PARAMS, - signature: '0x1234567890123456789012345678901234567890', -}; diff --git a/client/features/chain-variants/beacon-chain/stubs/withdrawals.ts b/client/features/chain-variants/beacon-chain/stubs/withdrawals.ts deleted file mode 100644 index 06dbbf75eed..00000000000 --- a/client/features/chain-variants/beacon-chain/stubs/withdrawals.ts +++ /dev/null @@ -1,12 +0,0 @@ -import type { WithdrawalsItem } from 'client/features/chain-variants/beacon-chain/types/api'; - -import { ADDRESS_PARAMS } from 'client/slices/address/stubs/address-params'; - -export const WITHDRAWAL: WithdrawalsItem = { - amount: '12565723', - index: 3810697, - receiver: ADDRESS_PARAMS, - validator_index: 25987, - block_number: 9005713, - timestamp: '2023-05-12T19:29:12.000000Z', -}; diff --git a/client/features/chain-variants/beacon-chain/types/api.ts b/client/features/chain-variants/beacon-chain/types/api.ts deleted file mode 100644 index 0fcb2c36b5c..00000000000 --- a/client/features/chain-variants/beacon-chain/types/api.ts +++ /dev/null @@ -1,68 +0,0 @@ -// SPDX-License-Identifier: LicenseRef-Blockscout - -import type { AddressParam } from 'client/slices/address/types/api'; - -export type BlockWithdrawalsResponse = { - items: Array; - next_page_params: { - index: number; - items_count: number; - } | null; -}; - -export type BlockWithdrawalsItem = { - amount: string; - index: number; - receiver: AddressParam; - validator_index: number; -}; - -export type DepositStatus = 'pending' | 'invalid' | 'completed'; - -export type DepositsResponse = { - items: Array; - next_page_params: { - index: number; - items_count: number; - } | null; -}; - -export type DepositsItem = { - amount: string; - block_number: number; - block_hash: string; - block_timestamp: string; - index: number; - pubkey: string; - signature: string; - status: DepositStatus; - from_address: AddressParam; - transaction_hash: string; - withdrawal_address: AddressParam; -}; - -export type DepositsCounters = { - deposits_count: string; -}; - -export type WithdrawalsResponse = { - items: Array; - next_page_params: { - index: number; - items_count: number; - }; -}; - -export type WithdrawalsItem = { - amount: string; - block_number: number; - index: number; - receiver: AddressParam; - timestamp: string; - validator_index: number; -}; - -export type WithdrawalsCounters = { - withdrawals_count: string; - withdrawals_sum: string; -}; diff --git a/client/features/chain-variants/celo/components/CeloEpochStatus.tsx b/client/features/chain-variants/celo/components/CeloEpochStatus.tsx deleted file mode 100644 index 1e279c8103b..00000000000 --- a/client/features/chain-variants/celo/components/CeloEpochStatus.tsx +++ /dev/null @@ -1,22 +0,0 @@ -// SPDX-License-Identifier: LicenseRef-Blockscout - -import React from 'react'; - -import type { Props as StatusTagProps } from 'ui/shared/statusTag/StatusTag'; -import StatusTag from 'ui/shared/statusTag/StatusTag'; - -export interface Props extends Omit { - isFinalized: boolean; -} - -const CeloEpochStatus = ({ isFinalized, ...rest }: Props) => { - return ( - - ); -}; - -export default CeloEpochStatus; diff --git a/client/features/chain-variants/celo/components/EpochRewardTypeTag.tsx b/client/features/chain-variants/celo/components/EpochRewardTypeTag.tsx deleted file mode 100644 index dc0ecab5159..00000000000 --- a/client/features/chain-variants/celo/components/EpochRewardTypeTag.tsx +++ /dev/null @@ -1,53 +0,0 @@ -// SPDX-License-Identifier: LicenseRef-Blockscout - -import React from 'react'; - -import type { CeloEpochRewardsType } from 'client/features/chain-variants/celo/types/api'; - -import type { BadgeProps } from 'toolkit/chakra/badge'; -import { Badge } from 'toolkit/chakra/badge'; -import { Tooltip } from 'toolkit/chakra/tooltip'; - -type Props = { - type: CeloEpochRewardsType; - isLoading?: boolean; -}; - -const TYPE_TAGS: Record = { - group: { - text: 'Validator group rewards', - // eslint-disable-next-line max-len - label: 'Reward given to a validator group. The address being viewed is the group\'s address; the associated address is the validator\'s address on whose behalf the reward was paid.', - color: 'teal', - }, - validator: { - text: 'Validator rewards', - label: 'Reward given to a validator. The address being viewed is the validator\'s address; the associated address is the validator group\'s address.', - color: 'purple', - }, - delegated_payment: { - text: 'Delegated payments', - // eslint-disable-next-line max-len - label: 'Reward portion delegated by a validator to another address. The address being viewed is the beneficiary receiving the reward; the associated address is the validator who set the delegation.', - color: 'blue', - }, - voter: { - text: 'Voting rewards', - label: 'Reward given to a voter. The address being viewed is the voter\'s address; the associated address is the group address.', - color: 'yellow', - }, -}; - -const EpochRewardTypeTag = ({ type, isLoading }: Props) => { - const { text, label, color } = TYPE_TAGS[type]; - - return ( - - - { text } - - - ); -}; - -export default React.memo(EpochRewardTypeTag); diff --git a/client/features/chain-variants/celo/mocks/block.ts b/client/features/chain-variants/celo/mocks/block.ts deleted file mode 100644 index 248b7bc660a..00000000000 --- a/client/features/chain-variants/celo/mocks/block.ts +++ /dev/null @@ -1,35 +0,0 @@ -import type { Block } from 'client/slices/block/types/api'; - -import * as addressMock from 'client/slices/address/mocks/address'; -import { base } from 'client/slices/block/mocks/block'; -import * as tokenMock from 'client/slices/token/mocks/info'; - -import { ZERO_ADDRESS } from 'toolkit/utils/consts'; - -export const celo: Block = { - ...base, - celo: { - base_fee: { - token: tokenMock.tokenInfoERC20a, - amount: '445690000000000', - breakdown: [ - { - address: addressMock.withName, - amount: '356552000000000.0000000000000', - percentage: 80, - }, - { - address: { - ...addressMock.withoutName, - hash: ZERO_ADDRESS, - }, - amount: '89138000000000.0000000000000', - percentage: 20, - }, - ], - recipient: addressMock.contract, - }, - epoch_number: 1486, - l1_era_finalized_epoch_number: 1485, - }, -}; diff --git a/client/features/chain-variants/celo/mocks/epoch.ts b/client/features/chain-variants/celo/mocks/epoch.ts deleted file mode 100644 index 60bdcf4ccf4..00000000000 --- a/client/features/chain-variants/celo/mocks/epoch.ts +++ /dev/null @@ -1,129 +0,0 @@ -import { padStart } from 'es-toolkit/compat'; - -import type { - CeloEpochDetails, - CeloEpochElectionRewardDetails, - CeloEpochElectionRewardDetailsResponse, - CeloEpochListResponse, -} from 'client/features/chain-variants/celo/types/api'; - -import * as addressMock from 'client/slices/address/mocks/address'; -import * as tokenTransferMock from 'client/slices/token-transfer/mocks'; -import * as tokenMock from 'client/slices/token/mocks/info'; - -export const epoch1: CeloEpochDetails = { - number: 1739, - is_finalized: true, - type: 'L1', - timestamp: '2022-06-10T01:27:52.000000Z', - start_block_number: 48477132, - start_processing_block_hash: '0x9dece1eb0e26a95fdf57d2f3a65a6f2e00ca0192e8e3dd157eca0cd323670fa1', - start_processing_block_number: 48563546, - end_processing_block_hash: '0x9dece1eb0e26a95fdf57d2f3a65a6f2e00ca0192e8e3dd157eca0cd323670fa2', - end_processing_block_number: 48563552, - end_block_number: 48563551, - distribution: { - carbon_offsetting_transfer: tokenTransferMock.erc20, - community_transfer: tokenTransferMock.erc20, - transfers_total: { - token: tokenMock.tokenInfoERC20a, - total: { - value: '1000000000000000000', - decimals: '18', - }, - }, - }, - aggregated_election_rewards: { - delegated_payment: { - count: 0, - total: '71210001063118670575', - token: tokenMock.tokenInfoERC20d, - }, - group: { - count: 10, - total: '157705500305820107521', - token: tokenMock.tokenInfoERC20b, - }, - validator: { - count: 10, - total: '1348139501689262297152', - token: tokenMock.tokenInfoERC20c, - }, - voter: { - count: 38, - total: '2244419545166303388', - token: tokenMock.tokenInfoERC20a, - }, - }, -}; - -export const epochUnfinalized: CeloEpochDetails = { - number: 1740, - is_finalized: false, - type: 'L2', - timestamp: null, - start_block_number: 48477132, - start_processing_block_hash: null, - start_processing_block_number: null, - end_processing_block_hash: null, - end_processing_block_number: null, - end_block_number: null, - distribution: null, - aggregated_election_rewards: null, -}; - -export const list: CeloEpochListResponse = { - items: [ - { - timestamp: '2022-11-10T01:27:52.000000Z', - number: 1739, - type: 'L2', - is_finalized: false, - start_block_number: 48477132, - end_block_number: null, - distribution: null, - }, - { - timestamp: '2022-06-09T01:27:32.000000Z', - number: 1738, - type: 'L1', - is_finalized: true, - end_block_number: 18477131, - start_block_number: 18390714, - distribution: { - carbon_offsetting_transfer: { - decimals: '18', - value: '1723199576750509130678', - }, - community_transfer: { - decimals: '18', - value: '68927983070020365227', - }, - transfers_total: { - decimals: '18', - value: '1792127559820529495905', - }, - }, - }, - ], - next_page_params: null, -}; - -function getRewardDetailsItem(index: number): CeloEpochElectionRewardDetails { - return { - amount: `${ 100 - index }210001063118670575`, - account: { - ...addressMock.withoutName, - hash: `0x30D060F129817c4DE5fBc1366d53e19f43c8c6${ padStart(String(index), 2, '0') }`, - }, - associated_account: { - ...addressMock.withoutName, - hash: `0x456f41406B32c45D59E539e4BBA3D7898c3584${ padStart(String(index), 2, '0') }`, - }, - }; -} - -export const electionRewardDetails1: CeloEpochElectionRewardDetailsResponse = { - items: Array(15).fill('').map((item, index) => getRewardDetailsItem(index)), - next_page_params: null, -}; diff --git a/client/features/chain-variants/celo/mocks/tx.ts b/client/features/chain-variants/celo/mocks/tx.ts deleted file mode 100644 index b5b2818a21d..00000000000 --- a/client/features/chain-variants/celo/mocks/tx.ts +++ /dev/null @@ -1,22 +0,0 @@ -import type { Transaction } from 'client/slices/tx/types/api'; - -import { base } from 'client/slices/tx/mocks/tx'; - -export const celoTxn: Transaction = { - ...base, - celo: { - gas_token: { - address_hash: '0x874069Fa1Eb16D44d622F2e0Ca25eeA172369bC1', - circulating_market_cap: null, - decimals: '18', - exchange_rate: '0.42', - holders_count: '205738', - icon_url: 'https://example.com/icon.png', - name: 'Celo Dollar', - symbol: 'cUSD', - total_supply: '7145754483836626799435133', - type: 'ERC-20', - reputation: 'ok', - }, - }, -}; diff --git a/client/features/chain-variants/celo/pages/address/AddressCeloAccount.pw.tsx b/client/features/chain-variants/celo/pages/address/AddressCeloAccount.pw.tsx deleted file mode 100644 index 1472a7505ea..00000000000 --- a/client/features/chain-variants/celo/pages/address/AddressCeloAccount.pw.tsx +++ /dev/null @@ -1,30 +0,0 @@ -import React from 'react'; - -import * as addressMock from 'client/slices/address/mocks/address'; - -import { test, expect } from 'playwright/lib'; -import { Container } from 'ui/shared/DetailedInfo/DetailedInfo'; - -import AddressCeloAccount from './AddressCeloAccount'; - -const DATA = { - name: 'CLabs Validator #2 on alfajores', - type: 'validator', - locked_celo: '10000000000000000000000', - // eslint-disable-next-line max-len - metadata_url: 'https://storage.googleapis.com/clabs_validator_metadata/alfajores/validator-alfajores-0x050f34537F5b2a00B9B9C752Cb8500a3fcE3DA7d-metadata.json', - nonvoting_locked_celo: '10000000000000000000000', - vote_signer_address: addressMock.withName, - validator_signer_address: addressMock.withEns, - attestation_signer_address: addressMock.withoutName, -}; - -test('default view +@mobile', async({ render }) => { - const component = await render( - - - , - ); - await component.getByText('View details').click(); - await expect(component).toHaveScreenshot(); -}); diff --git a/client/features/chain-variants/celo/pages/address/AddressEpochRewards.pw.tsx b/client/features/chain-variants/celo/pages/address/AddressEpochRewards.pw.tsx deleted file mode 100644 index 4a809f74540..00000000000 --- a/client/features/chain-variants/celo/pages/address/AddressEpochRewards.pw.tsx +++ /dev/null @@ -1,26 +0,0 @@ -import { Box } from '@chakra-ui/react'; -import React from 'react'; - -import { epochRewards } from 'client/features/chain-variants/celo/mocks/epoch-rewards'; - -import { test, expect } from 'playwright/lib'; - -import AddressEpochRewards from './AddressEpochRewards'; - -const ADDRESS_HASH = '0x1234'; -const hooksConfig = { - router: { - query: { hash: ADDRESS_HASH }, - }, -}; - -test('base view +@mobile', async({ render, mockApiResponse }) => { - await mockApiResponse('general:address_epoch_rewards', epochRewards, { pathParams: { hash: ADDRESS_HASH } }); - const component = await render( - - - , - { hooksConfig }, - ); - await expect(component).toHaveScreenshot(); -}); diff --git a/client/features/chain-variants/celo/pages/address/AddressEpochRewards.tsx b/client/features/chain-variants/celo/pages/address/AddressEpochRewards.tsx deleted file mode 100644 index 53fadf7f3e1..00000000000 --- a/client/features/chain-variants/celo/pages/address/AddressEpochRewards.tsx +++ /dev/null @@ -1,104 +0,0 @@ -// SPDX-License-Identifier: LicenseRef-Blockscout - -import { Box } from '@chakra-ui/react'; -import { useRouter } from 'next/router'; -import React from 'react'; - -import { EPOCH_REWARD_ITEM } from 'client/features/chain-variants/celo/stubs/address'; -import CsvExport from 'client/features/csv-export/components/CsvExport'; - -import useIsMounted from 'client/shared/hooks/useIsMounted'; -import getQueryParamString from 'client/shared/router/get-query-param-string'; - -import { generateListStub } from 'stubs/utils'; -import ActionBar, { ACTION_BAR_HEIGHT_DESKTOP } from 'ui/shared/ActionBar'; -import DataListDisplay from 'ui/shared/DataListDisplay'; -import Pagination from 'ui/shared/pagination/Pagination'; -import useQueryWithPages from 'ui/shared/pagination/useQueryWithPages'; - -import AddressEpochRewardsListItem from './AddressEpochRewardsListItem'; -import AddressEpochRewardsTable from './AddressEpochRewardsTable'; - -type Props = { - shouldRender?: boolean; - isQueryEnabled?: boolean; -}; - -const AddressEpochRewards = ({ shouldRender = true, isQueryEnabled = true }: Props) => { - const router = useRouter(); - const isMounted = useIsMounted(); - - const hash = getQueryParamString(router.query.hash); - - const rewardsQuery = useQueryWithPages({ - resourceName: 'general:address_epoch_rewards', - pathParams: { - hash, - }, - options: { - enabled: isQueryEnabled && Boolean(hash), - placeholderData: generateListStub<'general:address_epoch_rewards'>(EPOCH_REWARD_ITEM, 50, { next_page_params: { - amount: '1', - items_count: 50, - type: 'voter', - associated_account_address_hash: '1', - epoch_number: 10355938, - } }), - }, - }); - - if (!isMounted || !shouldRender) { - return null; - } - - const content = rewardsQuery.data?.items ? ( - <> - - - - - { rewardsQuery.data.items.map((item, index) => ( - - )) } - - - ) : null; - - const actionBar = ( - - - { rewardsQuery.pagination.isVisible && ( - - ) } - - ); - - return ( - - { content } - - ); -}; - -export default AddressEpochRewards; diff --git a/client/features/chain-variants/celo/pages/address/AddressEpochRewardsListItem.tsx b/client/features/chain-variants/celo/pages/address/AddressEpochRewardsListItem.tsx deleted file mode 100644 index 2fe80adf831..00000000000 --- a/client/features/chain-variants/celo/pages/address/AddressEpochRewardsListItem.tsx +++ /dev/null @@ -1,66 +0,0 @@ -// SPDX-License-Identifier: LicenseRef-Blockscout - -import React from 'react'; - -import type { AddressEpochRewardsItem } from 'client/features/chain-variants/celo/types/api'; - -import AddressEntity from 'client/slices/address/components/entity/AddressEntity'; - -import EpochEntity from 'client/features/chain-variants/celo/components/entity/EpochEntity'; -import EpochRewardTypeTag from 'client/features/chain-variants/celo/components/EpochRewardTypeTag'; - -import ListItemMobileGrid from 'ui/shared/ListItemMobile/ListItemMobileGrid'; -import TimeWithTooltip from 'ui/shared/time/TimeWithTooltip'; -import TokenValue from 'ui/shared/value/TokenValue'; - -type Props = { - item: AddressEpochRewardsItem; - isLoading?: boolean; -}; - -const AddressEpochRewardsListItem = ({ item, isLoading }: Props) => { - return ( - - - Epoch # - - - - - Age - - - - - Reward type - - - - - Associated address - - - - - Value - - - - - - ); -}; - -export default AddressEpochRewardsListItem; diff --git a/client/features/chain-variants/celo/pages/address/AddressEpochRewardsTable.tsx b/client/features/chain-variants/celo/pages/address/AddressEpochRewardsTable.tsx deleted file mode 100644 index 43f2206f250..00000000000 --- a/client/features/chain-variants/celo/pages/address/AddressEpochRewardsTable.tsx +++ /dev/null @@ -1,51 +0,0 @@ -// SPDX-License-Identifier: LicenseRef-Blockscout - -import React from 'react'; - -import type { AddressEpochRewardsItem } from 'client/features/chain-variants/celo/types/api'; - -import { AddressHighlightProvider } from 'client/slices/address/contexts/address-highlight'; - -import { TableBody, TableColumnHeader, TableHeaderSticky, TableRoot, TableRow } from 'toolkit/chakra/table'; -import TimeFormatToggle from 'ui/shared/time/TimeFormatToggle'; - -import AddressEpochRewardsTableItem from './AddressEpochRewardsTableItem'; - -type Props = { - items: Array; - isLoading?: boolean; - top: number; -}; - -const AddressEpochRewardsTable = ({ items, isLoading, top }: Props) => { - return ( - - - - - - Epoch - - - Reward type - Associated address - Value - - - - { items.map((item, index) => { - return ( - - ); - }) } - - - - ); -}; - -export default AddressEpochRewardsTable; diff --git a/client/features/chain-variants/celo/pages/address/AddressEpochRewardsTableItem.tsx b/client/features/chain-variants/celo/pages/address/AddressEpochRewardsTableItem.tsx deleted file mode 100644 index 8f9ed268948..00000000000 --- a/client/features/chain-variants/celo/pages/address/AddressEpochRewardsTableItem.tsx +++ /dev/null @@ -1,57 +0,0 @@ -// SPDX-License-Identifier: LicenseRef-Blockscout - -import { Flex } from '@chakra-ui/react'; -import React from 'react'; - -import type { AddressEpochRewardsItem } from 'client/features/chain-variants/celo/types/api'; - -import { route } from 'nextjs-routes'; - -import AddressEntity from 'client/slices/address/components/entity/AddressEntity'; - -import EpochRewardTypeTag from 'client/features/chain-variants/celo/components/EpochRewardTypeTag'; - -import { Link } from 'toolkit/chakra/link'; -import { TableCell, TableRow } from 'toolkit/chakra/table'; -import TimeWithTooltip from 'ui/shared/time/TimeWithTooltip'; -import TokenValue from 'ui/shared/value/TokenValue'; - -type Props = { - item: AddressEpochRewardsItem; - isLoading?: boolean; -}; - -const AddressEpochRewardsTableItem = ({ item, isLoading }: Props) => { - return ( - - - - - { item.epoch_number } - - - - - - - - - - - - - - - ); -}; - -export default AddressEpochRewardsTableItem; diff --git a/client/features/chain-variants/celo/pages/address/__screenshots__/AddressEpochRewards.pw.tsx_mobile_base-view-mobile-1.png b/client/features/chain-variants/celo/pages/address/__screenshots__/AddressEpochRewards.pw.tsx_mobile_base-view-mobile-1.png deleted file mode 100644 index 5b562da54fd..00000000000 Binary files a/client/features/chain-variants/celo/pages/address/__screenshots__/AddressEpochRewards.pw.tsx_mobile_base-view-mobile-1.png and /dev/null differ diff --git a/client/features/chain-variants/celo/pages/epoch-details/Epoch.pw.tsx b/client/features/chain-variants/celo/pages/epoch-details/Epoch.pw.tsx deleted file mode 100644 index 3303c1d0425..00000000000 --- a/client/features/chain-variants/celo/pages/epoch-details/Epoch.pw.tsx +++ /dev/null @@ -1,40 +0,0 @@ -import React from 'react'; - -import * as epochMock from 'client/features/chain-variants/celo/mocks/epoch'; - -import { ENVS_MAP } from 'playwright/fixtures/mockEnvs'; -import { test, expect } from 'playwright/lib'; - -import Epoch from './Epoch'; - -test('base view +@mobile', async({ render, mockEnvs, mockTextAd, mockApiResponse }) => { - const hooksConfig = { - router: { - query: { number: String(epochMock.epoch1.number) }, - }, - }; - - await mockEnvs(ENVS_MAP.celo); - await mockTextAd(); - await mockApiResponse('general:epoch_celo', epochMock.epoch1, { pathParams: { number: String(epochMock.epoch1.number) } }); - - const component = await render(, { hooksConfig }); - - await expect(component).toHaveScreenshot(); -}); - -test('unfinalized epoch', async({ render, mockEnvs, mockTextAd, mockApiResponse }) => { - const hooksConfig = { - router: { - query: { number: String(epochMock.epochUnfinalized.number) }, - }, - }; - - await mockEnvs(ENVS_MAP.celo); - await mockTextAd(); - await mockApiResponse('general:epoch_celo', epochMock.epochUnfinalized, { pathParams: { number: String(epochMock.epochUnfinalized.number) } }); - - const component = await render(, { hooksConfig }); - - await expect(component).toHaveScreenshot(); -}); diff --git a/client/features/chain-variants/celo/pages/epoch-details/election-rewards/utils.ts b/client/features/chain-variants/celo/pages/epoch-details/election-rewards/utils.ts deleted file mode 100644 index 319264d851e..00000000000 --- a/client/features/chain-variants/celo/pages/epoch-details/election-rewards/utils.ts +++ /dev/null @@ -1,47 +0,0 @@ -// SPDX-License-Identifier: LicenseRef-Blockscout - -import type { CeloEpochDetails } from 'client/features/chain-variants/celo/types/api'; -import type { ExcludeNull } from 'types/utils'; - -export function getRewardNumText(type: keyof CeloEpochDetails['aggregated_election_rewards'], num: number) { - const postfix1 = num !== 1 ? 's' : ''; - const postfix2 = num !== 1 ? 'es' : ''; - - const text = (() => { - switch (type) { - case 'delegated_payment': - return 'payment' + postfix1; - case 'group': - return 'group reward' + postfix1; - case 'validator': - return 'validator' + postfix1; - case 'voter': - return 'voting address' + postfix2; - default: - return ''; - } - })(); - - if (!text) { - return ''; - } - - return `${ num } ${ text }`; -} - -export function getRewardDetailsTableTitles(type: keyof ExcludeNull): [string, string] { - switch (type) { - case 'delegated_payment': - return [ 'Beneficiary', 'Validator' ]; - case 'group': - return [ 'Validator group', 'Associated validator' ]; - case 'validator': - return [ 'Validator', 'Validator group' ]; - case 'voter': - return [ 'Voter', 'Validator group' ]; - } -} - -export function formatRewardType(type: keyof ExcludeNull) { - return type.replaceAll('_', '-'); -} diff --git a/client/features/chain-variants/celo/pages/epoch-index/Epochs.pw.tsx b/client/features/chain-variants/celo/pages/epoch-index/Epochs.pw.tsx deleted file mode 100644 index 8499186a773..00000000000 --- a/client/features/chain-variants/celo/pages/epoch-index/Epochs.pw.tsx +++ /dev/null @@ -1,18 +0,0 @@ -import React from 'react'; - -import { list as epochsList } from 'client/features/chain-variants/celo/mocks/epoch'; - -import { ENVS_MAP } from 'playwright/fixtures/mockEnvs'; -import { test, expect } from 'playwright/lib'; - -import Epochs from './Epochs'; - -test('base view +@mobile', async({ render, mockEnvs, mockTextAd, mockApiResponse }) => { - await mockEnvs(ENVS_MAP.celo); - await mockTextAd(); - await mockApiResponse('general:epochs_celo', epochsList); - - const component = await render(); - - await expect(component).toHaveScreenshot(); -}); diff --git a/client/features/chain-variants/celo/pages/epoch-index/Epochs.tsx b/client/features/chain-variants/celo/pages/epoch-index/Epochs.tsx deleted file mode 100644 index 76fec5f3130..00000000000 --- a/client/features/chain-variants/celo/pages/epoch-index/Epochs.tsx +++ /dev/null @@ -1,80 +0,0 @@ -// SPDX-License-Identifier: LicenseRef-Blockscout - -import { Box } from '@chakra-ui/react'; -import React from 'react'; - -import { CELO_EPOCH_ITEM } from 'client/features/chain-variants/celo/stubs/epoch'; - -import { generateListStub } from 'stubs/utils'; -import ActionBar, { ACTION_BAR_HEIGHT_DESKTOP } from 'ui/shared/ActionBar'; -import DataFetchAlert from 'ui/shared/DataFetchAlert'; -import DataListDisplay from 'ui/shared/DataListDisplay'; -import PageTitle from 'ui/shared/Page/PageTitle'; -import Pagination from 'ui/shared/pagination/Pagination'; -import useQueryWithPages from 'ui/shared/pagination/useQueryWithPages'; - -import EpochsListItem from './EpochsListItem'; -import EpochsTable from './EpochsTable'; - -const EpochsPageContent = () => { - const epochsQuery = useQueryWithPages({ - resourceName: 'general:epochs_celo', - options: { - placeholderData: generateListStub<'general:epochs_celo'>(CELO_EPOCH_ITEM, 50, { next_page_params: { - number: 1739, - items_count: 50, - } }), - }, - }); - - const actionBar = epochsQuery.pagination.isVisible ? ( - - - - ) : null; - - const isLoading = epochsQuery.isPlaceholderData; - - const content = (() => { - if (epochsQuery.isError) { - return ; - } - - return epochsQuery.data?.items ? ( - <> - - - - - { epochsQuery.data.items.map((item, index) => ( - - )) } - - - ) : null; - })(); - - return ( - <> - - - { content } - - - ); -}; - -export default EpochsPageContent; diff --git a/client/features/chain-variants/celo/pages/epoch-index/EpochsListItem.tsx b/client/features/chain-variants/celo/pages/epoch-index/EpochsListItem.tsx deleted file mode 100644 index f50e8be3a83..00000000000 --- a/client/features/chain-variants/celo/pages/epoch-index/EpochsListItem.tsx +++ /dev/null @@ -1,79 +0,0 @@ -// SPDX-License-Identifier: LicenseRef-Blockscout - -import { HStack } from '@chakra-ui/react'; -import React from 'react'; - -import type { CeloEpochListItem } from 'client/features/chain-variants/celo/types/api'; - -import CeloEpochStatus from 'client/features/chain-variants/celo/components/CeloEpochStatus'; -import EpochEntity from 'client/features/chain-variants/celo/components/entity/EpochEntity'; - -import dayjs from 'lib/date/dayjs'; -import { Skeleton } from 'toolkit/chakra/skeleton'; -import ListItemMobile from 'ui/shared/ListItemMobile/ListItemMobile'; -import TextSeparator from 'ui/shared/TextSeparator'; -import Time from 'ui/shared/time/Time'; -import NativeCoinValue from 'ui/shared/value/NativeCoinValue'; - -interface Props { - item: CeloEpochListItem; - isLoading?: boolean; -} - -const EpochsListItem = ({ item, isLoading }: Props) => { - return ( - - - - { item.type } - - - { item.timestamp && ( - -
{ dayjs(item.timestamp).fromNow() }
- -
- ) } - - Block range - - { item.start_block_number } - { item.end_block_number || '' } - - - { item.distribution?.community_transfer ? ( - - Community - - - ) : null } - { item.distribution?.carbon_offsetting_transfer ? ( - - Carbon offset - - - ) : null } - { item.distribution?.transfers_total ? ( - - Total - - - ) : null } -
- ); -}; - -export default EpochsListItem; diff --git a/client/features/chain-variants/celo/pages/epoch-index/EpochsTableItem.tsx b/client/features/chain-variants/celo/pages/epoch-index/EpochsTableItem.tsx deleted file mode 100644 index 0a2807caf9d..00000000000 --- a/client/features/chain-variants/celo/pages/epoch-index/EpochsTableItem.tsx +++ /dev/null @@ -1,73 +0,0 @@ -// SPDX-License-Identifier: LicenseRef-Blockscout - -import { HStack } from '@chakra-ui/react'; -import React from 'react'; - -import type { CeloEpochListItem } from 'client/features/chain-variants/celo/types/api'; - -import CeloEpochStatus from 'client/features/chain-variants/celo/components/CeloEpochStatus'; -import EpochEntity from 'client/features/chain-variants/celo/components/entity/EpochEntity'; - -import { Skeleton } from 'toolkit/chakra/skeleton'; -import { TableCell, TableRow } from 'toolkit/chakra/table'; -import TimeWithTooltip from 'ui/shared/time/TimeWithTooltip'; -import NativeCoinValue from 'ui/shared/value/NativeCoinValue'; - -interface Props { - item: CeloEpochListItem; - isLoading?: boolean; -}; - -const EpochsTableItem = ({ item, isLoading }: Props) => { - return ( - - - - - { item.type } - - - - - - - - - { item.start_block_number } - { item.end_block_number || '' } - - - - - - - - - - - - - ); -}; - -export default EpochsTableItem; diff --git a/client/features/chain-variants/celo/stubs/address.ts b/client/features/chain-variants/celo/stubs/address.ts deleted file mode 100644 index def7672b94a..00000000000 --- a/client/features/chain-variants/celo/stubs/address.ts +++ /dev/null @@ -1,14 +0,0 @@ -import type { AddressEpochRewardsItem } from 'client/features/chain-variants/celo/types/api'; - -import { ADDRESS_PARAMS } from 'client/slices/address/stubs/address-params'; -import { TOKEN_INFO_ERC_20 } from 'client/slices/token/stubs'; - -export const EPOCH_REWARD_ITEM: AddressEpochRewardsItem = { - amount: '136609473658452408568', - block_timestamp: '2022-05-15T13:16:24Z', - type: 'voter', - token: TOKEN_INFO_ERC_20, - account: ADDRESS_PARAMS, - epoch_number: 1234, - associated_account: ADDRESS_PARAMS, -}; diff --git a/client/features/chain-variants/celo/stubs/epoch.ts b/client/features/chain-variants/celo/stubs/epoch.ts deleted file mode 100644 index 8e3ebb2277b..00000000000 --- a/client/features/chain-variants/celo/stubs/epoch.ts +++ /dev/null @@ -1,52 +0,0 @@ -import type { CeloEpochListItem, CeloEpochDetails, CeloEpochElectionReward } from 'client/features/chain-variants/celo/types/api'; - -import { BLOCK_HASH } from 'client/slices/block/stubs/block'; -import { TOKEN_TRANSFER_ERC_20, TOKEN_TRANSFER_ERC_20_TOTAL } from 'client/slices/token-transfer/stubs'; -import { TOKEN_INFO_ERC_20 } from 'client/slices/token/stubs'; - -export const CELO_EPOCH_ITEM: CeloEpochListItem = { - timestamp: '2025-06-10T01:27:52.000000Z', - number: 1739, - end_block_number: 48563551, - start_block_number: 48477132, - type: 'L1', - is_finalized: true, - distribution: { - carbon_offsetting_transfer: TOKEN_TRANSFER_ERC_20_TOTAL, - community_transfer: TOKEN_TRANSFER_ERC_20_TOTAL, - transfers_total: TOKEN_TRANSFER_ERC_20_TOTAL, - }, -}; - -const CELO_EPOCH_REWARD: CeloEpochElectionReward = { - count: 10, - total: '157705500305820107521', - token: TOKEN_INFO_ERC_20, -}; - -export const CELO_EPOCH: CeloEpochDetails = { - timestamp: '2025-06-10T01:27:52.000000Z', - number: 1739, - start_block_number: 48477132, - start_processing_block_hash: BLOCK_HASH, - start_processing_block_number: 48563546, - end_processing_block_hash: BLOCK_HASH, - end_processing_block_number: 48563552, - end_block_number: 48563551, - type: 'L1', - is_finalized: true, - distribution: { - carbon_offsetting_transfer: TOKEN_TRANSFER_ERC_20, - community_transfer: TOKEN_TRANSFER_ERC_20, - transfers_total: { - token: TOKEN_INFO_ERC_20, - total: TOKEN_TRANSFER_ERC_20_TOTAL, - }, - }, - aggregated_election_rewards: { - group: CELO_EPOCH_REWARD, - validator: CELO_EPOCH_REWARD, - voter: CELO_EPOCH_REWARD, - delegated_payment: CELO_EPOCH_REWARD, - }, -}; diff --git a/client/features/chain-variants/celo/types/api.ts b/client/features/chain-variants/celo/types/api.ts deleted file mode 100644 index 230bf308979..00000000000 --- a/client/features/chain-variants/celo/types/api.ts +++ /dev/null @@ -1,112 +0,0 @@ -// SPDX-License-Identifier: LicenseRef-Blockscout - -import type { AddressParam } from 'client/slices/address/types/api'; -import type { Erc20TotalPayload, TokenTransfer } from 'client/slices/token-transfer/types/api'; -import type { TokenInfo } from 'client/slices/token/types/api'; - -export interface TransactionCelo { - celo?: { - gas_token: TokenInfo | null; - }; -} - -export interface BlockBaseFeeCelo { - amount: string; - breakdown: Array<{ amount: string; percentage: number; address: AddressParam }>; - recipient: AddressParam; - token: TokenInfo; -} - -export interface BlockCelo { - celo?: { - epoch_number: number; - l1_era_finalized_epoch_number: number | null; - base_fee?: BlockBaseFeeCelo; - }; -} - -export type CeloEpochType = 'L1' | 'L2'; - -export type CeloEpochListItem = { - number: number; - type: CeloEpochType; - is_finalized: boolean; - start_block_number: number; - end_block_number: number | null; - timestamp: string | null; - distribution: { - carbon_offsetting_transfer: Erc20TotalPayload | null; - community_transfer: Erc20TotalPayload | null; - transfers_total: Erc20TotalPayload | null; - } | null; -}; - -export type CeloEpochListResponse = { - items: Array; - next_page_params: { - items_count: number; - number: number; - } | null; -}; - -export type CeloEpochDetails = { - number: number; - type: CeloEpochType; - is_finalized: boolean; - timestamp: string | null; - start_block_number: number; - start_processing_block_hash: string | null; - start_processing_block_number: number | null; - end_block_number: number | null; - end_processing_block_hash: string | null; - end_processing_block_number: number | null; - distribution: { - carbon_offsetting_transfer: TokenTransfer | null; - community_transfer: TokenTransfer | null; - transfers_total: { - token: TokenInfo | null; - total: Erc20TotalPayload | null; - } | null; - } | null; - aggregated_election_rewards: Record | null; -}; - -export interface CeloEpochElectionReward { - count: number; - token: TokenInfo; - total: string; -} - -export type CeloEpochRewardsType = 'group' | 'validator' | 'delegated_payment' | 'voter'; - -export interface CeloEpochElectionRewardDetails { - account: AddressParam; - amount: string; - associated_account: AddressParam; -} - -export interface CeloEpochElectionRewardDetailsResponse { - items: Array; - next_page_params: null; -} - -export type AddressEpochRewardsResponse = { - items: Array; - next_page_params: { - amount: string; - associated_account_address_hash: string; - epoch_number: number; - items_count: number; - type: CeloEpochRewardsType; - } | null; -}; - -export type AddressEpochRewardsItem = { - type: CeloEpochRewardsType; - token: TokenInfo; - amount: string; - block_timestamp: string; - account: AddressParam; - epoch_number: number; - associated_account: AddressParam; -}; diff --git a/client/features/chain-variants/filecoin/types/api.ts b/client/features/chain-variants/filecoin/types/api.ts deleted file mode 100644 index fcc19513402..00000000000 --- a/client/features/chain-variants/filecoin/types/api.ts +++ /dev/null @@ -1,25 +0,0 @@ -// SPDX-License-Identifier: LicenseRef-Blockscout - -export type AddressFilecoinParams = { - actor_type?: FilecoinActorType; - id?: string | null; - robust?: string | null; -}; - -export type FilecoinActorType = - 'account' | - 'cron' | - 'datacap' | - 'eam' | - 'ethaccount' | - 'evm' | - 'init' | - 'market' | - 'miner' | - 'multisig' | - 'paych' | - 'placeholder' | - 'power' | - 'reward' | - 'system' | - 'verifreg'; diff --git a/client/features/chain-variants/mud/mocks/mud-tables.ts b/client/features/chain-variants/mud/mocks/mud-tables.ts deleted file mode 100644 index 122aeaece45..00000000000 --- a/client/features/chain-variants/mud/mocks/mud-tables.ts +++ /dev/null @@ -1,93 +0,0 @@ -/* eslint-disable max-len */ -import type { AddressMudRecord, AddressMudRecords, AddressMudRecordsItem, AddressMudTables, MudWorldSchema, MudWorldTable } from 'client/features/chain-variants/mud/types/api'; - -export const table1: MudWorldTable = { - table_full_name: 'tb.store.Tables', - table_id: '0x746273746f72650000000000000000005461626c657300000000000000000000', - table_name: 'Tables', - table_namespace: 'store', - table_type: 'onchain', -}; - -export const table2: MudWorldTable = { - table_full_name: 'ot.world.FunctionSignatur', - table_id: '0x6f74776f726c6400000000000000000046756e6374696f6e5369676e61747572', - table_name: 'FunctionSignatur', - table_namespace: 'world', - table_type: 'offchain', -}; - -export const schema1: MudWorldSchema = { - key_names: [ 'moduleAddress', 'argumentsHash' ], - key_types: [ 'address', 'bytes32' ], - value_names: [ 'fieldLayout', 'keySchema', 'valueSchema', 'abiEncodedKeyNames', 'abiEncodedFieldNames' ], - value_types: [ 'bytes32', 'bytes32', 'bytes32', 'bytes', 'bytes' ], -}; - -export const schema2: MudWorldSchema = { - key_names: [], - key_types: [], - value_names: [ 'value' ], - value_types: [ 'address' ], -}; - -export const mudTables: AddressMudTables = { - items: [ - { - table: table1, - schema: schema1, - }, - { - table: table2, - schema: schema2, - }, - ], - next_page_params: { - items_count: 50, - table_id: '1', - }, -}; - -const record: AddressMudRecordsItem = { - decoded: { - abiEncodedFieldNames: '0x00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000e00000000000000000000000000000000000000000000000000000000000000006706c617965720000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000576616c7565000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000974696d657374616d700000000000000000000000000000000000000000000000', - abiEncodedKeyNames: '0x00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000026964000000000000000000000000000000000000000000000000000000000000', - goldCosts: [ '100000', '150000', '200000', '250000', '400000', '550000', '700000' ], - prototypeIds: [ - '0x53776f7264736d616e0000000000000000000000000000000000000000000000', - '0x50696b656d616e00000000000000000000000000000000000000000000000000', - '0x50696b656d616e00000000000000000000000000000000000000000000000000', - '0x4172636865720000000000000000000000000000000000000000000000000000', - '0x4b6e696768740000000000000000000000000000000000000000000000000000', - ], - keySchema: '0x002001001f000000000000000000000000000000000000000000000000000000', - tableId: '0x6f74000000000000000000000000000044726177557064617465000000000000', - valueSchema: '0x00540300611f1f00000000000000000000000000000000000000000000000000', - }, - id: '0x007a651a000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007', - is_deleted: false, - timestamp: '2024-05-09T15:14:32.000000Z', -}; - -export const mudRecords: AddressMudRecords = { - items: [ record, record ], - next_page_params: { - items_count: 50, - key0: '1', - key1: '2', - key_bytes: '3', - }, - schema: { - key_names: [ 'tableId' ], - key_types: [ 'bytes32' ], - value_names: [ 'prototypeIds', 'goldCosts', 'keySchema', 'valueSchema', 'abiEncodedKeyNames', 'abiEncodedFieldNames' ], - value_types: [ 'bytes32[]', 'int32[]', 'bytes32', 'bytes32', 'bytes32', 'bytes', 'bytes' ], - }, - table: table1, -}; - -export const mudRecord: AddressMudRecord = { - record, - schema: mudRecords.schema, - table: table1, -}; diff --git a/client/features/chain-variants/mud/mocks/mud-worlds.ts b/client/features/chain-variants/mud/mocks/mud-worlds.ts deleted file mode 100644 index 0aa85e355cc..00000000000 --- a/client/features/chain-variants/mud/mocks/mud-worlds.ts +++ /dev/null @@ -1,27 +0,0 @@ -import type { MudWorldsResponse } from 'client/features/chain-variants/mud/types/api'; - -import { withName, withoutName } from 'client/slices/address/mocks/address'; - -export const mudWorlds: MudWorldsResponse = { - items: [ - { - address: withName, - coin_balance: '300000000000000000', - transactions_count: 3938, - }, - { - address: withoutName, - coin_balance: '0', - transactions_count: 0, - }, - { - address: withoutName, - coin_balance: '0', - transactions_count: 0, - }, - ], - next_page_params: { - items_count: 50, - world: '0x18f01f12ca21b6fc97b917c3e32f671f8a933caa', - }, -}; diff --git a/client/features/chain-variants/mud/pages/address/AddressMud.tsx b/client/features/chain-variants/mud/pages/address/AddressMud.tsx deleted file mode 100644 index 65d958ba169..00000000000 --- a/client/features/chain-variants/mud/pages/address/AddressMud.tsx +++ /dev/null @@ -1,38 +0,0 @@ -// SPDX-License-Identifier: LicenseRef-Blockscout - -import { useRouter } from 'next/router'; -import React from 'react'; - -import useIsMounted from 'client/shared/hooks/useIsMounted'; - -import AddressMudRecord from './AddressMudRecord'; -import AddressMudTable from './AddressMudTable'; -import AddressMudTables from './AddressMudTables'; - -type Props = { - shouldRender?: boolean; - isQueryEnabled?: boolean; -}; - -const AddressMud = ({ shouldRender = true, isQueryEnabled = true }: Props) => { - const isMounted = useIsMounted(); - const router = useRouter(); - const tableId = router.query.table_id?.toString(); - const recordId = router.query.record_id?.toString(); - - if (!isMounted || !shouldRender) { - return null; - } - - if (tableId && recordId) { - return ; - } - - if (tableId) { - return ; - } - - return ; -}; - -export default AddressMud; diff --git a/client/features/chain-variants/mud/pages/address/AddressMudBreadcrumbs.tsx b/client/features/chain-variants/mud/pages/address/AddressMudBreadcrumbs.tsx deleted file mode 100644 index 4db0c042d26..00000000000 --- a/client/features/chain-variants/mud/pages/address/AddressMudBreadcrumbs.tsx +++ /dev/null @@ -1,114 +0,0 @@ -// SPDX-License-Identifier: LicenseRef-Blockscout - -import { Box, chakra, Grid } from '@chakra-ui/react'; -import React from 'react'; - -import { route } from 'nextjs-routes'; - -import useAddressQuery from 'client/slices/address/hooks/useAddressQuery'; - -import useIsMobile from 'client/shared/hooks/useIsMobile'; - -import { Link } from 'toolkit/chakra/link'; -import { isBrowser } from 'toolkit/utils/isBrowser'; -import CopyToClipboard from 'ui/shared/CopyToClipboard'; -import IconSvg from 'ui/shared/IconSvg'; - -type TableViewProps = { - className?: string; - hash: string; - tableId: string; - tableName: string; - recordId?: never; - recordName?: never; -}; - -type RecordViewProps = Omit & { - recordId: string; - recordName: string; -}; - -type BreadcrumbItemProps = { - text: string; - href: string; - isLast?: boolean; -}; - -const BreadcrumbItem = ({ text, href, isLast }: BreadcrumbItemProps) => { - const currentUrl = isBrowser() ? window.location.href : ''; - - const onLinkClick = React.useCallback(() => { - window.scrollTo({ top: 0, behavior: 'smooth' }); - }, []); - - if (isLast) { - return ( - - - { text } - - - - ); - } - - return ( - - - { text } - - { !isLast && } - - ); -}; - -const AddressMudBreadcrumbs = (props: TableViewProps | RecordViewProps) => { - const queryParams = { tab: 'mud', hash: props.hash }; - const isMobile = useIsMobile(); - - const addressQuery = useAddressQuery({ hash: props.hash }); - - return ( - - - - - { ('recordId' in props && typeof props.recordId === 'string') && ('recordName' in props && typeof props.recordName === 'string') && ( - - ) } - - ); -}; - -export default React.memo(chakra(AddressMudBreadcrumbs)); diff --git a/client/features/chain-variants/mud/pages/address/AddressMudRecord.pw.tsx b/client/features/chain-variants/mud/pages/address/AddressMudRecord.pw.tsx deleted file mode 100644 index c6e1d99d947..00000000000 --- a/client/features/chain-variants/mud/pages/address/AddressMudRecord.pw.tsx +++ /dev/null @@ -1,47 +0,0 @@ -import { Box } from '@chakra-ui/react'; -import React from 'react'; - -import { mudRecord } from 'client/features/chain-variants/mud/mocks/mud-tables'; - -import { test, expect, devices } from 'playwright/lib'; - -import AddressMudRecord from './AddressMudRecord'; - -const ADDRESS_HASH = 'hash'; -const TABLE_ID = '123'; -const RECORD_ID = '234'; -const hooksConfig = { - router: { - query: { hash: ADDRESS_HASH }, - }, -}; - -test('base view', async({ render, mockApiResponse }) => { - await mockApiResponse('general:mud_record', mudRecord, { pathParams: { hash: ADDRESS_HASH, table_id: TABLE_ID, record_id: RECORD_ID } }); - - const component = await render( - - - , - { hooksConfig }, - ); - - await expect(component).toHaveScreenshot(); -}); - -test.describe('mobile', () => { - test.use({ viewport: devices['iPhone 13 Pro'].viewport }); - - test('base view', async({ render, mockApiResponse }) => { - await mockApiResponse('general:mud_record', mudRecord, { pathParams: { hash: ADDRESS_HASH, table_id: TABLE_ID, record_id: RECORD_ID } }); - - const component = await render( - - - , - { hooksConfig }, - ); - - await expect(component).toHaveScreenshot(); - }); -}); diff --git a/client/features/chain-variants/mud/pages/address/AddressMudRecord.tsx b/client/features/chain-variants/mud/pages/address/AddressMudRecord.tsx deleted file mode 100644 index 3171f897bf5..00000000000 --- a/client/features/chain-variants/mud/pages/address/AddressMudRecord.tsx +++ /dev/null @@ -1,98 +0,0 @@ -// SPDX-License-Identifier: LicenseRef-Blockscout - -import { Box, Flex, Separator, Text, VStack } from '@chakra-ui/react'; -import { useRouter } from 'next/router'; -import React from 'react'; - -import useApiQuery from 'client/api/hooks/useApiQuery'; - -import getQueryParamString from 'client/shared/router/get-query-param-string'; - -import { TableRoot, TableRow, TableCell } from 'toolkit/chakra/table'; -import { ContentLoader } from 'toolkit/components/loaders/ContentLoader'; -import { TruncatedText } from 'toolkit/components/truncation/TruncatedText'; -import DataFetchAlert from 'ui/shared/DataFetchAlert'; -import Time from 'ui/shared/time/Time'; - -import AddressMudBreadcrumbs from './AddressMudBreadcrumbs'; -import AddressMudRecordValues from './AddressMudRecordValues'; -import { getValueString } from './utils'; - -type Props = { - isQueryEnabled?: boolean; - tableId: string; - recordId: string; -}; - -const AddressMudRecord = ({ tableId, recordId, isQueryEnabled = true }: Props) => { - const router = useRouter(); - - const hash = getQueryParamString(router.query.hash); - - const { data, isLoading, isError } = useApiQuery('general:mud_record', { - pathParams: { hash, table_id: tableId, record_id: recordId }, - queryOptions: { - enabled: isQueryEnabled, - }, - }); - - if (isLoading) { - return ; - } - - if (isError) { - return ; - } - - return ( - <> - { data && ( - - ) } - - - { data?.schema.key_names.length && data?.schema.key_names.map((keyName, index) => ( - - - { keyName } ({ data.schema.key_types[index] }) - - - - - { index === 0 && - - - )) } - - - - - <> - { data?.schema.key_names.length && data?.schema.key_names.map((keyName, index) => ( - - - - { keyName } ({ data.schema.key_types[index] }) - - { getValueString(data.record.decoded[keyName]) } - { index === 0 && - )) } - - - - - - - ); -}; - -export default AddressMudRecord; diff --git a/client/features/chain-variants/mud/pages/address/AddressMudRecordValues.tsx b/client/features/chain-variants/mud/pages/address/AddressMudRecordValues.tsx deleted file mode 100644 index 7b65443d0b5..00000000000 --- a/client/features/chain-variants/mud/pages/address/AddressMudRecordValues.tsx +++ /dev/null @@ -1,47 +0,0 @@ -// SPDX-License-Identifier: LicenseRef-Blockscout - -import { Box } from '@chakra-ui/react'; -import React from 'react'; - -import type { AddressMudRecord } from 'client/features/chain-variants/mud/types/api'; - -import { TableCell, TableRow } from 'toolkit/chakra/table'; - -import { getValueString } from './utils'; - -type Props = { - data?: AddressMudRecord; -}; - -const AddressMudRecordValues = ({ data }: Props) => { - const valuesBgColor = { _light: 'blackAlpha.50', _dark: 'whiteAlpha.50' }; - - if (!data?.schema.value_names.length) { - return null; - } - - return ( - <> - - Field - Type - Value - - { - data?.schema.value_names.map((valName, index) => ( - - { valName } - { data.schema.value_types[index] } - - - { getValueString(data.record.decoded[valName]) } - - - - )) - } - - ); -}; - -export default AddressMudRecordValues; diff --git a/client/features/chain-variants/mud/pages/address/AddressMudRecordsKeyFilter.tsx b/client/features/chain-variants/mud/pages/address/AddressMudRecordsKeyFilter.tsx deleted file mode 100644 index 0418a693d3e..00000000000 --- a/client/features/chain-variants/mud/pages/address/AddressMudRecordsKeyFilter.tsx +++ /dev/null @@ -1,35 +0,0 @@ -// SPDX-License-Identifier: LicenseRef-Blockscout - -import React from 'react'; - -import TableColumnFilterWrapper from 'ui/shared/filters/TableColumnFilterWrapper'; - -import AddressMudRecordsKeyFilterContent from './AddressMudRecordsKeyFilterContent'; - -type Props = { - value?: string; - handleFilterChange: (val: string) => void; - title: string; - columnName: string; - isLoading?: boolean; -}; - -const AddressMudRecordsKeyFilter = ({ value = '', handleFilterChange, columnName, title, isLoading }: Props) => { - return ( - - - - ); -}; - -export default AddressMudRecordsKeyFilter; diff --git a/client/features/chain-variants/mud/pages/address/AddressMudRecordsKeyFilterContent.tsx b/client/features/chain-variants/mud/pages/address/AddressMudRecordsKeyFilterContent.tsx deleted file mode 100644 index 8159acc3fd2..00000000000 --- a/client/features/chain-variants/mud/pages/address/AddressMudRecordsKeyFilterContent.tsx +++ /dev/null @@ -1,39 +0,0 @@ -// SPDX-License-Identifier: LicenseRef-Blockscout - -import React from 'react'; - -import { FilterInput } from 'toolkit/components/filters/FilterInput'; -import TableColumnFilter from 'ui/shared/filters/TableColumnFilter'; - -type Props = { - value?: string; - handleFilterChange: (val: string) => void; - title: string; - columnName: string; -}; - -const AddressMudRecordsKeyFilterContent = ({ value = '', handleFilterChange, columnName, title }: Props) => { - const [ filterValue, setFilterValue ] = React.useState(value); - - const onFilter = React.useCallback(() => { - handleFilterChange(filterValue); - }, [ handleFilterChange, filterValue ]); - - return ( - - - - ); -}; - -export default AddressMudRecordsKeyFilterContent; diff --git a/client/features/chain-variants/mud/pages/address/AddressMudRecordsTable.tsx b/client/features/chain-variants/mud/pages/address/AddressMudRecordsTable.tsx deleted file mode 100644 index 32ff59047a8..00000000000 --- a/client/features/chain-variants/mud/pages/address/AddressMudRecordsTable.tsx +++ /dev/null @@ -1,229 +0,0 @@ -// SPDX-License-Identifier: LicenseRef-Blockscout - -import { Box, Flex } from '@chakra-ui/react'; -import { useRouter } from 'next/router'; -import React from 'react'; - -import type { AddressMudRecords, AddressMudRecordsFilter, AddressMudRecordsSorting } from 'client/features/chain-variants/mud/types/api'; - -import { route } from 'nextjs-routes'; - -import useIsMobile from 'client/shared/hooks/useIsMobile'; -import capitalizeFirstLetter from 'client/shared/text/capitalize-first-letter'; - -import { Link } from 'toolkit/chakra/link'; -import { TableBody, TableCell, TableColumnHeader, TableHeaderSticky, TableRoot, TableRow } from 'toolkit/chakra/table'; -import type { TableColumnHeaderProps } from 'toolkit/chakra/table'; -import { Tooltip } from 'toolkit/chakra/tooltip'; -import { middot } from 'toolkit/utils/htmlEntities'; -import CopyToClipboard from 'ui/shared/CopyToClipboard'; -import IconSvg from 'ui/shared/IconSvg'; -import Time from 'ui/shared/time/Time'; - -import AddressMudRecordsKeyFilter from './AddressMudRecordsKeyFilter'; -import { getNameTypeText, getValueString } from './utils'; - -const COL_MIN_WIDTH = 180; -const COL_MIN_WIDTH_MOBILE = 140; -const CUT_COL_WIDTH = 36; -const MIN_CUT_COUNT = 2; - -type Props = { - data: AddressMudRecords; - top: number; - sorting?: AddressMudRecordsSorting; - toggleSorting: (key: AddressMudRecordsSorting['sort']) => void; - setFilters: React.Dispatch>; - filters: AddressMudRecordsFilter; - toggleTableHasHorizontalScroll: () => void; - scrollRef?: React.RefObject; - hash: string; -}; - -const AddressMudRecordsTable = ({ - data, - top, - sorting, - toggleSorting, - filters, - setFilters, - toggleTableHasHorizontalScroll, - scrollRef, - hash, -}: Props) => { - const totalColsCut = data.schema.key_names.length + data.schema.value_names.length; - const isMobile = useIsMobile(); - const [ colsCutCount, setColsCutCount ] = React.useState(isMobile ? MIN_CUT_COUNT : 0); - const [ isOpened, setIsOpened ] = React.useState(false); - const [ hasCut, setHasCut ] = React.useState(isMobile ? totalColsCut > MIN_CUT_COUNT : true); - - const containerRef = React.useRef(null); - const tableRef = React.useRef(null); - - const router = useRouter(); - - const toggleIsOpen = React.useCallback(() => { - isOpened && tableRef.current?.scroll({ left: 0 }); - setIsOpened((prev) => !prev); - toggleTableHasHorizontalScroll(); - }, [ setIsOpened, toggleTableHasHorizontalScroll, isOpened ]); - - const onRecordClick = React.useCallback((e: React.MouseEvent) => { - if (e.metaKey || e.ctrlKey) { - // Allow opening in a new tab/window with right-click or ctrl/cmd+click - return; - } - - e.preventDefault(); - - const recordId = e.currentTarget.getAttribute('data-id'); - if (recordId) { - router.push( - { pathname: '/address/[hash]', query: { hash, tab: 'mud', table_id: data.table.table_id, record_id: recordId } }, - undefined, - { shallow: true }, - ); - } - scrollRef?.current?.scrollIntoView(); - }, [ router, scrollRef, hash, data.table.table_id ]); - - const handleFilterChange = React.useCallback((field: keyof AddressMudRecordsFilter) => (val: string) => { - setFilters(prev => { - const newVal = { ...prev }; - newVal[field] = val; - return newVal; - }); - }, [ setFilters ]); - - const onKeySortClick = React.useCallback( - (e: React.MouseEvent) => toggleSorting('key' + e.currentTarget.getAttribute('data-id') as AddressMudRecordsSorting['sort']), - [ toggleSorting ], - ); - - React.useEffect(() => { - if (hasCut && !colsCutCount && containerRef.current) { - const count = Math.floor((containerRef.current.getBoundingClientRect().width - CUT_COL_WIDTH) / COL_MIN_WIDTH); - if (totalColsCut > MIN_CUT_COUNT && count - 1 < totalColsCut) { - setColsCutCount(count - 1); - } else { - setHasCut(false); - } - } - }, [ colsCutCount, data.schema, hasCut, setHasCut, totalColsCut ]); - - const colW = isMobile ? COL_MIN_WIDTH_MOBILE : COL_MIN_WIDTH; - - const keys = (isOpened || !hasCut) ? data.schema.key_names : data.schema.key_names.slice(0, colsCutCount); - const values = (isOpened || !hasCut) ? data.schema.value_names : data.schema.value_names.slice(0, colsCutCount - data.schema.key_names.length); - const colsCount = (isOpened || !hasCut) ? totalColsCut : colsCutCount; - - const tdStyles: TableColumnHeaderProps = { - wordBreak: 'break-word', - whiteSpace: 'normal', - minW: `${ colW }px`, - w: `${ 100 / colsCount }%`, - verticalAlign: 'top', - lineHeight: '20px', - }; - - const hasHorizontalScroll = isMobile || isOpened; - - if (hasCut && !colsCutCount) { - return ; - } - - const cutButton = ( - - - { middot }{ middot }{ middot } - - - ); - - return ( - // can't implement both horizontal table scroll and sticky header - - - - - { keys.map((keyName, index) => { - const text = getNameTypeText(keyName, data.schema.key_types[index]); - return ( - - { index < 2 ? ( - - - { sorting?.sort === `key${ index }` && sorting.order && ( - - - - ) } - { text } - - - - - - ) : text } - - ); - }) } - { values.map((valName, index) => ( - - { capitalizeFirstLetter(valName) } ({ data.schema.value_types[index] }) - - )) } - { hasCut && !isOpened && cutButton } - Modified - { hasCut && isOpened && cutButton } - - - - { data.items.map((item) => ( - - { keys.map((keyName, index) => ( - - { index === 0 ? ( - - { getValueString(item.decoded[keyName]) } - - ) : getValueString(item.decoded[keyName]) } - - - )) } - { values.map((valName) => - { getValueString(item.decoded[valName]) }) } - { hasCut && !isOpened && } - - { hasCut && isOpened && } - - )) } - - - - ); -}; - -export default AddressMudRecordsTable; diff --git a/client/features/chain-variants/mud/pages/address/AddressMudTable.pw.tsx b/client/features/chain-variants/mud/pages/address/AddressMudTable.pw.tsx deleted file mode 100644 index 7bbcf62d65a..00000000000 --- a/client/features/chain-variants/mud/pages/address/AddressMudTable.pw.tsx +++ /dev/null @@ -1,60 +0,0 @@ -import { Box } from '@chakra-ui/react'; -import React from 'react'; - -import { mudRecords } from 'client/features/chain-variants/mud/mocks/mud-tables'; - -import { test, expect } from 'playwright/lib'; - -import AddressMudTable from './AddressMudTable'; - -const ADDRESS_HASH = 'hash'; -const TABLE_ID = '123'; -const hooksConfig = { - router: { - query: { hash: ADDRESS_HASH }, - }, -}; - -test('base view +@mobile', async({ render, mockApiResponse }) => { - await mockApiResponse('general:mud_records', mudRecords, { pathParams: { hash: ADDRESS_HASH, table_id: TABLE_ID } }); - - const component = await render( - - - , - { hooksConfig }, - ); - - await expect(component).toHaveScreenshot(); -}); - -test('expanded view +@mobile', async({ render, mockApiResponse }) => { - await mockApiResponse('general:mud_records', mudRecords, { pathParams: { hash: ADDRESS_HASH, table_id: TABLE_ID } }); - - const component = await render( - - - , - { hooksConfig }, - ); - - await component.getByLabel('show/hide columns').click(); - - await expect(component).toHaveScreenshot(); -}); - -test('empty +@mobile', async({ render, mockApiResponse }) => { - await mockApiResponse( - 'general:mud_records', - { ...mudRecords, items: [] }, - { pathParams: { hash: ADDRESS_HASH, table_id: TABLE_ID } }); - - const component = await render( - - - , - { hooksConfig }, - ); - - await expect(component).toHaveScreenshot(); -}); diff --git a/client/features/chain-variants/mud/pages/address/AddressMudTable.tsx b/client/features/chain-variants/mud/pages/address/AddressMudTable.tsx deleted file mode 100644 index 292fcd2d4af..00000000000 --- a/client/features/chain-variants/mud/pages/address/AddressMudTable.tsx +++ /dev/null @@ -1,168 +0,0 @@ -// SPDX-License-Identifier: LicenseRef-Blockscout - -import { Box, HStack, chakra } from '@chakra-ui/react'; -import { useRouter } from 'next/router'; -import React from 'react'; - -import type { AddressMudRecordsFilter, AddressMudRecordsSorting } from 'client/features/chain-variants/mud/types/api'; - -import useIsMobile from 'client/shared/hooks/useIsMobile'; -import getQueryParamString from 'client/shared/router/get-query-param-string'; - -import { Tag } from 'toolkit/chakra/tag'; -import { ContentLoader } from 'toolkit/components/loaders/ContentLoader'; -import ActionBar, { ACTION_BAR_HEIGHT_DESKTOP } from 'ui/shared/ActionBar'; -import DataListDisplay from 'ui/shared/DataListDisplay'; -import Pagination from 'ui/shared/pagination/Pagination'; -import useQueryWithPages from 'ui/shared/pagination/useQueryWithPages'; -import { getNextOrderValue } from 'ui/shared/sort/getNextSortValue'; -import getSortParamsFromQuery from 'ui/shared/sort/getSortParamsFromQuery'; - -import AddressMudBreadcrumbs from './AddressMudBreadcrumbs'; -import AddressMudRecordsTable from './AddressMudRecordsTable'; -import { getNameTypeText, SORT_SEQUENCE } from './utils'; - -const BREADCRUMBS_HEIGHT = 60; -const FILTERS_HEIGHT = 44; - -type Props = { - isQueryEnabled?: boolean; - tableId: string; -}; - -type FilterKeys = keyof AddressMudRecordsFilter; - -const AddressMudTable = ({ tableId, isQueryEnabled = true }: Props) => { - const router = useRouter(); - const [ sorting, setSorting ] = - React.useState(getSortParamsFromQuery(router.query, SORT_SEQUENCE)); - const [ filters, setFilters ] = React.useState({}); - const isMobile = useIsMobile(); - const [ tableHasHorizontalScroll, setTableHasHorizontalScroll ] = React.useState(isMobile); - - const hash = getQueryParamString(router.query.hash); - - const { data, isLoading, isError, pagination, onSortingChange } = useQueryWithPages({ - resourceName: 'general:mud_records', - pathParams: { hash, table_id: tableId }, - filters, - sorting, - options: { - // no placeholder data because the structure of a table is unpredictable - enabled: isQueryEnabled, - }, - }); - - const handleTableHasHorizontalScroll = React.useCallback(() => { - setTableHasHorizontalScroll((prev) => !prev); - }, []); - - const toggleSorting = React.useCallback((val: AddressMudRecordsSorting['sort']) => { - const newSorting = { sort: val, order: getNextOrderValue(sorting?.sort === val ? sorting.order : undefined) }; - setSorting(newSorting); - onSortingChange(newSorting); - }, [ onSortingChange, sorting ]); - - const onRemoveFilterClick = React.useCallback((key: FilterKeys) => () => { - setFilters(prev => { - const newFilters = { ...prev }; - delete newFilters[key]; - return newFilters; - }); - }, []); - - const hasActiveFilters = Object.values(filters).some(Boolean); - - const actionBarHeight = React.useMemo(() => { - const heightWithoutFilters = pagination.isVisible ? ACTION_BAR_HEIGHT_DESKTOP : BREADCRUMBS_HEIGHT; - - return hasActiveFilters ? heightWithoutFilters + FILTERS_HEIGHT : heightWithoutFilters; - }, [ pagination.isVisible, hasActiveFilters ]); - - if (isLoading) { - return ; - } - - const filtersTags = hasActiveFilters ? ( - - { Object.entries(filters).map(([ key, value ]) => { - const index = key as FilterKeys === 'filter_key0' ? 0 : 1; - return ( - - { value } - - ); - }) } - - ) : null; - - const breadcrumbs = data ? ( - - ) : null; - - const actionBar = (!isMobile || hasActiveFilters || pagination.isVisible) && ( - - - { !isMobile && breadcrumbs } - { filtersTags } - - - - ); - - const content = data?.items ? ( - - ) : null; - - const emptyText = ( - <> - There are no records for - { data?.table.table_full_name ? { data?.table.table_full_name } : 'this table' } - - ); - - return ( - <> - { isMobile && ( - { breadcrumbs } - ) } - - { content } - - - ); -}; - -export default AddressMudTable; diff --git a/client/features/chain-variants/mud/pages/address/AddressMudTables.pw.tsx b/client/features/chain-variants/mud/pages/address/AddressMudTables.pw.tsx deleted file mode 100644 index a457ef24e78..00000000000 --- a/client/features/chain-variants/mud/pages/address/AddressMudTables.pw.tsx +++ /dev/null @@ -1,43 +0,0 @@ -import { Box } from '@chakra-ui/react'; -import React from 'react'; - -import { mudTables } from 'client/features/chain-variants/mud/mocks/mud-tables'; - -import { test, expect } from 'playwright/lib'; - -import AddressMudTables from './AddressMudTables'; - -const ADDRESS_HASH = 'hash'; -const hooksConfig = { - router: { - query: { hash: ADDRESS_HASH, q: 'o' }, - }, -}; - -test('base view +@mobile', async({ render, mockApiResponse }) => { - await mockApiResponse('general:mud_tables', mudTables, { pathParams: { hash: ADDRESS_HASH }, queryParams: { q: 'o' } }); - - const component = await render( - - - , - { hooksConfig }, - ); - - await expect(component).toHaveScreenshot(); -}); - -test('with schema opened +@mobile', async({ render, mockApiResponse }, testInfo) => { - await mockApiResponse('general:mud_tables', mudTables, { pathParams: { hash: ADDRESS_HASH }, queryParams: { q: 'o' } }); - - const component = await render( - - - , - { hooksConfig }, - ); - - await component.getByLabel('View schema').nth(testInfo.project.name === 'mobile' ? 2 : 0).click(); - - await expect(component).toHaveScreenshot(); -}); diff --git a/client/features/chain-variants/mud/pages/address/AddressMudTables.tsx b/client/features/chain-variants/mud/pages/address/AddressMudTables.tsx deleted file mode 100644 index 45abb468625..00000000000 --- a/client/features/chain-variants/mud/pages/address/AddressMudTables.tsx +++ /dev/null @@ -1,108 +0,0 @@ -// SPDX-License-Identifier: LicenseRef-Blockscout - -import { Box } from '@chakra-ui/react'; -import { useRouter } from 'next/router'; -import React from 'react'; - -import { ADDRESS_MUD_TABLE_ITEM } from 'client/features/chain-variants/mud/stubs/address'; - -import useDebounce from 'client/shared/hooks/useDebounce'; -import useIsInitialLoading from 'client/shared/hooks/useIsInitialLoading'; -import getQueryParamString from 'client/shared/router/get-query-param-string'; - -import { generateListStub } from 'stubs/utils'; -import { FilterInput } from 'toolkit/components/filters/FilterInput'; -import ActionBar, { ACTION_BAR_HEIGHT_DESKTOP } from 'ui/shared/ActionBar'; -import DataListDisplay from 'ui/shared/DataListDisplay'; -import Pagination from 'ui/shared/pagination/Pagination'; -import useQueryWithPages from 'ui/shared/pagination/useQueryWithPages'; - -import AddressMudTablesListItem from './AddressMudTablesListItem'; -import AddressMudTablesTable from './AddressMudTablesTable'; - -type Props = { - isQueryEnabled?: boolean; -}; - -const AddressMudTables = ({ isQueryEnabled = true }: Props) => { - const router = useRouter(); - - const hash = getQueryParamString(router.query.hash); - const q = getQueryParamString(router.query.q); - const [ searchTerm, setSearchTerm ] = React.useState(q || ''); - const debouncedSearchTerm = useDebounce(searchTerm, 300); - - const { data, isPlaceholderData, isError, pagination } = useQueryWithPages({ - resourceName: 'general:mud_tables', - pathParams: { hash }, - filters: { q: debouncedSearchTerm }, - options: { - enabled: isQueryEnabled, - placeholderData: generateListStub<'general:mud_tables'>(ADDRESS_MUD_TABLE_ITEM, 3, { next_page_params: { - items_count: 50, - table_id: '1', - } }), - }, - }); - - const isInitialLoading = useIsInitialLoading(isPlaceholderData); - - const searchInput = ( - - ); - - const actionBar = ( - - { searchInput } - - - ); - - const content = data?.items ? ( - <> - - - - - { data.items.map((item, index) => ( - - )) } - - - ) : null; - - return ( - - { content } - - ); -}; - -export default AddressMudTables; diff --git a/client/features/chain-variants/mud/pages/address/AddressMudTablesListItem.tsx b/client/features/chain-variants/mud/pages/address/AddressMudTablesListItem.tsx deleted file mode 100644 index dd6095e9fd7..00000000000 --- a/client/features/chain-variants/mud/pages/address/AddressMudTablesListItem.tsx +++ /dev/null @@ -1,121 +0,0 @@ -// SPDX-License-Identifier: LicenseRef-Blockscout - -import { Text, Flex, VStack, chakra, Box, Grid, GridItem, Separator } from '@chakra-ui/react'; -import { useRouter } from 'next/router'; -import React from 'react'; - -import type { AddressMudTableItem } from 'client/features/chain-variants/mud/types/api'; - -import { route } from 'nextjs-routes'; - -import { Badge } from 'toolkit/chakra/badge'; -import { Link } from 'toolkit/chakra/link'; -import { Skeleton } from 'toolkit/chakra/skeleton'; -import HashStringShorten from 'ui/shared/HashStringShorten'; -import IconSvg from 'ui/shared/IconSvg'; -import ListItemMobile from 'ui/shared/ListItemMobile/ListItemMobile'; - -type Props = { - item: AddressMudTableItem; - isLoading: boolean; - scrollRef?: React.RefObject; - hash: string; -}; - -const AddressMudTablesListItem = ({ item, isLoading, scrollRef, hash }: Props) => { - const [ isOpened, setIsOpened ] = React.useState(false); - - const router = useRouter(); - - const handleIconClick = React.useCallback(() => { - setIsOpened((prev) => !prev); - }, []); - - const onTableClick = React.useCallback((e: React.MouseEvent) => { - if (e.metaKey || e.ctrlKey) { - // Allow opening in a new tab/window with right-click or ctrl/cmd+click - return; - } - - e.preventDefault(); - - const tableId = e.currentTarget.getAttribute('data-id'); - if (tableId) { - router.push( - { pathname: '/address/[hash]', query: { hash, tab: 'mud', table_id: tableId } }, - undefined, - { shallow: true }, - ); - } - - scrollRef?.current?.scrollIntoView(); - }, [ router, scrollRef, hash ]); - - return ( - - - - - - - - - - - - { item.table.table_full_name } - - - - { item.table.table_type } - - - - - - - - - { isOpened && ( - - { Boolean(item.schema.key_names.length) && ( - <> - Key - - { item.schema.key_names.map((name, index) => ( - - { item.schema.key_types[index] } { name } - - )) } - - - ) } - - Value - - { item.schema.value_names.map((name, index) => ( - - { item.schema.value_types[index] } { name } - - )) } - - - ) } - - ); -}; - -export default React.memo(AddressMudTablesListItem); diff --git a/client/features/chain-variants/mud/pages/address/AddressMudTablesTable.tsx b/client/features/chain-variants/mud/pages/address/AddressMudTablesTable.tsx deleted file mode 100644 index 9e81e0a1ad2..00000000000 --- a/client/features/chain-variants/mud/pages/address/AddressMudTablesTable.tsx +++ /dev/null @@ -1,44 +0,0 @@ -// SPDX-License-Identifier: LicenseRef-Blockscout - -import React from 'react'; - -import type { AddressMudTables } from 'client/features/chain-variants/mud/types/api'; - -import { TableBody, TableColumnHeader, TableHeaderSticky, TableRoot, TableRow } from 'toolkit/chakra/table'; - -import AddressMudTablesTableItem from './AddressMudTablesTableItem'; - -type Props = { - items: AddressMudTables['items']; - isLoading: boolean; - top: number; - hash: string; -}; - -//sorry for the naming -const AddressMudTablesTable = ({ items, isLoading, top, hash }: Props) => { - return ( - - - - - Full name - Table ID - Type - - - - { items.map((item, index) => ( - - )) } - - - ); -}; - -export default AddressMudTablesTable; diff --git a/client/features/chain-variants/mud/pages/address/AddressMudTablesTableItem.tsx b/client/features/chain-variants/mud/pages/address/AddressMudTablesTableItem.tsx deleted file mode 100644 index a08d5b7120c..00000000000 --- a/client/features/chain-variants/mud/pages/address/AddressMudTablesTableItem.tsx +++ /dev/null @@ -1,133 +0,0 @@ -// SPDX-License-Identifier: LicenseRef-Blockscout - -import { Text, VStack, chakra } from '@chakra-ui/react'; -import { useRouter } from 'next/router'; -import React from 'react'; - -import type { AddressMudTableItem } from 'client/features/chain-variants/mud/types/api'; - -import { route } from 'nextjs-routes'; - -import { Badge } from 'toolkit/chakra/badge'; -import { Link } from 'toolkit/chakra/link'; -import { Skeleton } from 'toolkit/chakra/skeleton'; -import { TableBody, TableCell, TableRoot, TableRow } from 'toolkit/chakra/table'; -import IconSvg from 'ui/shared/IconSvg'; -type Props = { - item: AddressMudTableItem; - isLoading: boolean; - hash: string; -}; - -const AddressMudTablesTableItem = ({ item, isLoading, hash }: Props) => { - const [ isOpened, setIsOpened ] = React.useState(false); - - const router = useRouter(); - - const handleIconClick = React.useCallback(() => { - setIsOpened((prev) => !prev); - }, []); - - const onTableClick = React.useCallback((e: React.MouseEvent) => { - if (e.metaKey || e.ctrlKey) { - // Allow opening in a new tab/window with right-click or ctrl/cmd+click - return; - } - - e.preventDefault(); - - const tableId = e.currentTarget.getAttribute('data-id'); - if (tableId) { - router.push( - { pathname: '/address/[hash]', query: { hash, tab: 'mud', table_id: tableId } }, - undefined, - { shallow: true }, - ); - } - - window.scrollTo({ top: 0, behavior: 'smooth' }); - }, [ router, hash ]); - - return ( - <> - - - - - - - - - - - - { item.table.table_full_name } - - - - - - { item.table.table_id } - - - - - { item.table.table_type } - - - - { isOpened && ( - - - - - - { Boolean(item.schema.key_names.length) && ( - - Key - - - { item.schema.key_names.map((name, index) => ( - - { item.schema.key_types[index] } { name } - - )) } - - - - ) } - - Value - - - { item.schema.value_names.map((name, index) => ( - - { item.schema.value_types[index] } { name } - - )) } - - - - - - - - ) } - - ); -}; - -export default React.memo(AddressMudTablesTableItem); diff --git a/client/features/chain-variants/mud/pages/address/__screenshots__/AddressMudRecord.pw.tsx_default_base-view-1.png b/client/features/chain-variants/mud/pages/address/__screenshots__/AddressMudRecord.pw.tsx_default_base-view-1.png deleted file mode 100644 index 0493fbae272..00000000000 Binary files a/client/features/chain-variants/mud/pages/address/__screenshots__/AddressMudRecord.pw.tsx_default_base-view-1.png and /dev/null differ diff --git a/client/features/chain-variants/mud/pages/address/__screenshots__/AddressMudRecord.pw.tsx_default_mobile-base-view-1.png b/client/features/chain-variants/mud/pages/address/__screenshots__/AddressMudRecord.pw.tsx_default_mobile-base-view-1.png deleted file mode 100644 index 9ae7ba337f6..00000000000 Binary files a/client/features/chain-variants/mud/pages/address/__screenshots__/AddressMudRecord.pw.tsx_default_mobile-base-view-1.png and /dev/null differ diff --git a/client/features/chain-variants/mud/pages/address/__screenshots__/AddressMudTable.pw.tsx_default_base-view-mobile-1.png b/client/features/chain-variants/mud/pages/address/__screenshots__/AddressMudTable.pw.tsx_default_base-view-mobile-1.png deleted file mode 100644 index c71a7d7c576..00000000000 Binary files a/client/features/chain-variants/mud/pages/address/__screenshots__/AddressMudTable.pw.tsx_default_base-view-mobile-1.png and /dev/null differ diff --git a/client/features/chain-variants/mud/pages/address/__screenshots__/AddressMudTable.pw.tsx_default_empty-mobile-1.png b/client/features/chain-variants/mud/pages/address/__screenshots__/AddressMudTable.pw.tsx_default_empty-mobile-1.png deleted file mode 100644 index 680a06b80db..00000000000 Binary files a/client/features/chain-variants/mud/pages/address/__screenshots__/AddressMudTable.pw.tsx_default_empty-mobile-1.png and /dev/null differ diff --git a/client/features/chain-variants/mud/pages/address/__screenshots__/AddressMudTable.pw.tsx_default_expanded-view-mobile-1.png b/client/features/chain-variants/mud/pages/address/__screenshots__/AddressMudTable.pw.tsx_default_expanded-view-mobile-1.png deleted file mode 100644 index 5f981d68dd5..00000000000 Binary files a/client/features/chain-variants/mud/pages/address/__screenshots__/AddressMudTable.pw.tsx_default_expanded-view-mobile-1.png and /dev/null differ diff --git a/client/features/chain-variants/mud/pages/address/__screenshots__/AddressMudTable.pw.tsx_mobile_base-view-mobile-1.png b/client/features/chain-variants/mud/pages/address/__screenshots__/AddressMudTable.pw.tsx_mobile_base-view-mobile-1.png deleted file mode 100644 index 26332f4baf3..00000000000 Binary files a/client/features/chain-variants/mud/pages/address/__screenshots__/AddressMudTable.pw.tsx_mobile_base-view-mobile-1.png and /dev/null differ diff --git a/client/features/chain-variants/mud/pages/address/__screenshots__/AddressMudTable.pw.tsx_mobile_empty-mobile-1.png b/client/features/chain-variants/mud/pages/address/__screenshots__/AddressMudTable.pw.tsx_mobile_empty-mobile-1.png deleted file mode 100644 index e5708e86d56..00000000000 Binary files a/client/features/chain-variants/mud/pages/address/__screenshots__/AddressMudTable.pw.tsx_mobile_empty-mobile-1.png and /dev/null differ diff --git a/client/features/chain-variants/mud/pages/address/__screenshots__/AddressMudTable.pw.tsx_mobile_expanded-view-mobile-1.png b/client/features/chain-variants/mud/pages/address/__screenshots__/AddressMudTable.pw.tsx_mobile_expanded-view-mobile-1.png deleted file mode 100644 index 133502822e0..00000000000 Binary files a/client/features/chain-variants/mud/pages/address/__screenshots__/AddressMudTable.pw.tsx_mobile_expanded-view-mobile-1.png and /dev/null differ diff --git a/client/features/chain-variants/mud/pages/address/__screenshots__/AddressMudTables.pw.tsx_default_base-view-mobile-1.png b/client/features/chain-variants/mud/pages/address/__screenshots__/AddressMudTables.pw.tsx_default_base-view-mobile-1.png deleted file mode 100644 index 906a0b98f10..00000000000 Binary files a/client/features/chain-variants/mud/pages/address/__screenshots__/AddressMudTables.pw.tsx_default_base-view-mobile-1.png and /dev/null differ diff --git a/client/features/chain-variants/mud/pages/address/__screenshots__/AddressMudTables.pw.tsx_default_with-schema-opened-mobile-1.png b/client/features/chain-variants/mud/pages/address/__screenshots__/AddressMudTables.pw.tsx_default_with-schema-opened-mobile-1.png deleted file mode 100644 index c1eb32b9bd1..00000000000 Binary files a/client/features/chain-variants/mud/pages/address/__screenshots__/AddressMudTables.pw.tsx_default_with-schema-opened-mobile-1.png and /dev/null differ diff --git a/client/features/chain-variants/mud/pages/address/__screenshots__/AddressMudTables.pw.tsx_mobile_base-view-mobile-1.png b/client/features/chain-variants/mud/pages/address/__screenshots__/AddressMudTables.pw.tsx_mobile_base-view-mobile-1.png deleted file mode 100644 index d511992469a..00000000000 Binary files a/client/features/chain-variants/mud/pages/address/__screenshots__/AddressMudTables.pw.tsx_mobile_base-view-mobile-1.png and /dev/null differ diff --git a/client/features/chain-variants/mud/pages/address/__screenshots__/AddressMudTables.pw.tsx_mobile_with-schema-opened-mobile-1.png b/client/features/chain-variants/mud/pages/address/__screenshots__/AddressMudTables.pw.tsx_mobile_with-schema-opened-mobile-1.png deleted file mode 100644 index 267e6b53653..00000000000 Binary files a/client/features/chain-variants/mud/pages/address/__screenshots__/AddressMudTables.pw.tsx_mobile_with-schema-opened-mobile-1.png and /dev/null differ diff --git a/client/features/chain-variants/mud/pages/address/utils.ts b/client/features/chain-variants/mud/pages/address/utils.ts deleted file mode 100644 index cddf99a1a0a..00000000000 --- a/client/features/chain-variants/mud/pages/address/utils.ts +++ /dev/null @@ -1,20 +0,0 @@ -// SPDX-License-Identifier: LicenseRef-Blockscout - -import capitalizeFirstLetter from 'client/shared/text/capitalize-first-letter'; - -export const SORT_SEQUENCE: Record<'key0' | 'key1', Array<'desc' | 'asc' | undefined>> = { - key0: [ 'desc', 'asc', undefined ], - key1: [ 'desc', 'asc', undefined ], -}; - -export const getNameTypeText = (name: string, type: string) => { - return capitalizeFirstLetter(name) + ' (' + type + ')'; -}; - -export const getValueString = (value: string | Array) => { - if (Array.isArray(value)) { - return value.join(', '); - } - - return value.toString(); -}; diff --git a/client/features/chain-variants/mud/pages/contract/ContractMethodsMudSystem.tsx b/client/features/chain-variants/mud/pages/contract/ContractMethodsMudSystem.tsx deleted file mode 100644 index 7d483496fed..00000000000 --- a/client/features/chain-variants/mud/pages/contract/ContractMethodsMudSystem.tsx +++ /dev/null @@ -1,87 +0,0 @@ -// SPDX-License-Identifier: LicenseRef-Blockscout - -import { Flex } from '@chakra-ui/react'; -import { useRouter } from 'next/router'; -import React from 'react'; - -import type { SmartContractMudSystemItem } from 'client/features/chain-variants/mud/types/api'; - -import useApiQuery from 'client/api/hooks/useApiQuery'; - -import type { Item } from 'client/slices/contract/pages/details/code/ContractSourceAddressSelector'; -import ContractSourceAddressSelector from 'client/slices/contract/pages/details/code/ContractSourceAddressSelector'; -import ContractAbi from 'client/slices/contract/pages/details/methods/ContractAbi'; -import ContractMethodsAlerts from 'client/slices/contract/pages/details/methods/ContractMethodsAlerts'; -import ContractMethodsContainer from 'client/slices/contract/pages/details/methods/ContractMethodsContainer'; -import ContractMethodsFilters from 'client/slices/contract/pages/details/methods/ContractMethodsFilters'; -import useMethodsFilters from 'client/slices/contract/pages/details/methods/useMethodsFilters'; -import { formatAbi } from 'client/slices/contract/pages/details/methods/utils'; - -import getQueryParamString from 'client/shared/router/get-query-param-string'; - -interface Props { - items: Array; -} - -const ContractMethodsMudSystem = ({ items }: Props) => { - - const router = useRouter(); - - const addressHash = getQueryParamString(router.query.hash); - const sourceAddress = getQueryParamString(router.query.source_address); - const tab = getQueryParamString(router.query.tab); - - const [ selectedItem, setSelectedItem ] = React.useState(items.find((item) => item.address_hash === sourceAddress) || items[0]); - - const systemInfoQuery = useApiQuery('general:mud_system_info', { - pathParams: { hash: addressHash, system_address: selectedItem.address_hash }, - queryOptions: { - enabled: Boolean(selectedItem?.address_hash), - refetchOnMount: false, - }, - }); - - const handleItemSelect = React.useCallback((item: Item) => { - setSelectedItem(item as SmartContractMudSystemItem); - }, []); - - const abi = React.useMemo(() => formatAbi(systemInfoQuery.data?.abi || []), [ systemInfoQuery.data?.abi ]); - const filters = useMethodsFilters({ abi }); - - return ( - - -
- - -
- - - -
- ); -}; - -export default React.memo(ContractMethodsMudSystem); diff --git a/client/features/chain-variants/mud/pages/mud-worlds/MudWorlds.pw.tsx b/client/features/chain-variants/mud/pages/mud-worlds/MudWorlds.pw.tsx deleted file mode 100644 index dd600ef347e..00000000000 --- a/client/features/chain-variants/mud/pages/mud-worlds/MudWorlds.pw.tsx +++ /dev/null @@ -1,14 +0,0 @@ -import React from 'react'; - -import { mudWorlds } from 'client/features/chain-variants/mud/mocks/mud-worlds'; - -import { test, expect } from 'playwright/lib'; - -import MudWorlds from './MudWorlds'; - -test('default view +@mobile', async({ mockTextAd, mockApiResponse, render }) => { - await mockTextAd(); - await mockApiResponse('general:mud_worlds', mudWorlds); - const component = await render(); - await expect(component).toHaveScreenshot(); -}); diff --git a/client/features/chain-variants/mud/pages/mud-worlds/MudWorlds.tsx b/client/features/chain-variants/mud/pages/mud-worlds/MudWorlds.tsx deleted file mode 100644 index 9400eb954f8..00000000000 --- a/client/features/chain-variants/mud/pages/mud-worlds/MudWorlds.tsx +++ /dev/null @@ -1,73 +0,0 @@ -// SPDX-License-Identifier: LicenseRef-Blockscout - -import { Box } from '@chakra-ui/react'; -import React from 'react'; - -import { MUD_WORLD } from 'client/features/chain-variants/mud/stubs/mud-worlds'; - -import { generateListStub } from 'stubs/utils'; -import ActionBar, { ACTION_BAR_HEIGHT_DESKTOP } from 'ui/shared/ActionBar'; -import DataListDisplay from 'ui/shared/DataListDisplay'; -import PageTitle from 'ui/shared/Page/PageTitle'; -import Pagination from 'ui/shared/pagination/Pagination'; -import useQueryWithPages from 'ui/shared/pagination/useQueryWithPages'; - -import MudWorldsListItem from './MudWorldsListItem'; -import MudWorldsTable from './MudWorldsTable'; - -const MudWorlds = () => { - const { data, isError, isPlaceholderData, pagination } = useQueryWithPages({ - resourceName: 'general:mud_worlds', - options: { - placeholderData: generateListStub<'general:mud_worlds'>( - MUD_WORLD, - 50, - { - next_page_params: { - items_count: 50, - world: '1', - }, - }, - ), - }, - }); - - const content = data?.items ? ( - <> - - { data.items.map(((item, index) => ( - - ))) } - - - - - - ) : null; - - const actionBar = pagination.isVisible ? ( - - - - ) : null; - - return ( - <> - - - { content } - - - ); -}; - -export default MudWorlds; diff --git a/client/features/chain-variants/mud/pages/mud-worlds/MudWorldsListItem.tsx b/client/features/chain-variants/mud/pages/mud-worlds/MudWorldsListItem.tsx deleted file mode 100644 index d218cdf9f68..00000000000 --- a/client/features/chain-variants/mud/pages/mud-worlds/MudWorldsListItem.tsx +++ /dev/null @@ -1,54 +0,0 @@ -// SPDX-License-Identifier: LicenseRef-Blockscout - -import { HStack } from '@chakra-ui/react'; -import React from 'react'; - -import type { MudWorldItem } from 'client/features/chain-variants/mud/types/api'; - -import AddressEntity from 'client/slices/address/components/entity/AddressEntity'; - -import { currencyUnits } from 'client/shared/chain/units'; - -import { Skeleton } from 'toolkit/chakra/skeleton'; -import ListItemMobile from 'ui/shared/ListItemMobile/ListItemMobile'; -import NativeCoinValue from 'ui/shared/value/NativeCoinValue'; - -type Props = { - item: MudWorldItem; - isLoading?: boolean; -}; - -const MudWorldsListItem = ({ - item, - isLoading, -}: Props) => { - - return ( - - - - { `Balance ${ currencyUnits.ether }` } - - - - Txn count - - { Number(item.transactions_count).toLocaleString() } - - - - ); -}; - -export default React.memo(MudWorldsListItem); diff --git a/client/features/chain-variants/mud/pages/mud-worlds/MudWorldsTable.tsx b/client/features/chain-variants/mud/pages/mud-worlds/MudWorldsTable.tsx deleted file mode 100644 index 099e05aec0d..00000000000 --- a/client/features/chain-variants/mud/pages/mud-worlds/MudWorldsTable.tsx +++ /dev/null @@ -1,42 +0,0 @@ -// SPDX-License-Identifier: LicenseRef-Blockscout - -import React from 'react'; - -import type { MudWorldItem } from 'client/features/chain-variants/mud/types/api'; - -import { currencyUnits } from 'client/shared/chain/units'; - -import { TableBody, TableColumnHeader, TableHeaderSticky, TableRoot, TableRow } from 'toolkit/chakra/table'; - -import MudWorldsTableItem from './MudWorldsTableItem'; - -type Props = { - items: Array; - top: number; - isLoading?: boolean; -}; - -const MudWorldsTable = ({ items, top, isLoading }: Props) => { - return ( - - - - Address - { `Balance ${ currencyUnits.ether }` } - Txn count - - - - { items.map((item, index) => ( - - )) } - - - ); -}; - -export default MudWorldsTable; diff --git a/client/features/chain-variants/mud/pages/mud-worlds/MudWorldsTableItem.tsx b/client/features/chain-variants/mud/pages/mud-worlds/MudWorldsTableItem.tsx deleted file mode 100644 index 60f02488be4..00000000000 --- a/client/features/chain-variants/mud/pages/mud-worlds/MudWorldsTableItem.tsx +++ /dev/null @@ -1,38 +0,0 @@ -// SPDX-License-Identifier: LicenseRef-Blockscout - -import React from 'react'; - -import type { MudWorldItem } from 'client/features/chain-variants/mud/types/api'; - -import AddressEntity from 'client/slices/address/components/entity/AddressEntity'; - -import { Skeleton } from 'toolkit/chakra/skeleton'; -import { TableCell, TableRow } from 'toolkit/chakra/table'; -import NativeCoinValue from 'ui/shared/value/NativeCoinValue'; - -type Props = { item: MudWorldItem; isLoading?: boolean }; - -const MudWorldsTableItem = ({ item, isLoading }: Props) => { - return ( - - - - - - - - - - { Number(item.transactions_count).toLocaleString() } - - - - ); -}; - -export default MudWorldsTableItem; diff --git a/client/features/chain-variants/mud/pages/mud-worlds/__screenshots__/MudWorlds.pw.tsx_default_default-view-mobile-1.png b/client/features/chain-variants/mud/pages/mud-worlds/__screenshots__/MudWorlds.pw.tsx_default_default-view-mobile-1.png deleted file mode 100644 index 3026c5a813c..00000000000 Binary files a/client/features/chain-variants/mud/pages/mud-worlds/__screenshots__/MudWorlds.pw.tsx_default_default-view-mobile-1.png and /dev/null differ diff --git a/client/features/chain-variants/mud/pages/mud-worlds/__screenshots__/MudWorlds.pw.tsx_mobile_default-view-mobile-1.png b/client/features/chain-variants/mud/pages/mud-worlds/__screenshots__/MudWorlds.pw.tsx_mobile_default-view-mobile-1.png deleted file mode 100644 index f0b5aa4f3e4..00000000000 Binary files a/client/features/chain-variants/mud/pages/mud-worlds/__screenshots__/MudWorlds.pw.tsx_mobile_default-view-mobile-1.png and /dev/null differ diff --git a/client/features/chain-variants/mud/stubs/address.ts b/client/features/chain-variants/mud/stubs/address.ts deleted file mode 100644 index 5f7fec94f77..00000000000 --- a/client/features/chain-variants/mud/stubs/address.ts +++ /dev/null @@ -1,8 +0,0 @@ -import type { AddressMudTableItem } from 'client/features/chain-variants/mud/types/api'; - -import { MUD_SCHEMA, MUD_TABLE } from 'client/features/chain-variants/mud/stubs/mud-worlds'; - -export const ADDRESS_MUD_TABLE_ITEM: AddressMudTableItem = { - schema: MUD_SCHEMA, - table: MUD_TABLE, -}; diff --git a/client/features/chain-variants/mud/stubs/contract.ts b/client/features/chain-variants/mud/stubs/contract.ts deleted file mode 100644 index 13f587c8e01..00000000000 --- a/client/features/chain-variants/mud/stubs/contract.ts +++ /dev/null @@ -1,12 +0,0 @@ -import type { SmartContractMudSystemsResponse } from '../types/api'; - -import { ADDRESS_HASH } from 'client/slices/address/stubs/address-params'; - -export const MUD_SYSTEMS: SmartContractMudSystemsResponse = { - items: [ - { - name: 'sy.AccessManagement', - address_hash: ADDRESS_HASH, - }, - ], -}; diff --git a/client/features/chain-variants/mud/stubs/mud-worlds.ts b/client/features/chain-variants/mud/stubs/mud-worlds.ts deleted file mode 100644 index b4cae3a4741..00000000000 --- a/client/features/chain-variants/mud/stubs/mud-worlds.ts +++ /dev/null @@ -1,24 +0,0 @@ -import type { MudWorldItem, MudWorldSchema, MudWorldTable } from 'client/features/chain-variants/mud/types/api'; - -import { ADDRESS_PARAMS } from 'client/slices/address/stubs/address-params'; - -export const MUD_TABLE: MudWorldTable = { - table_full_name: 'ot.Match', - table_id: '0x6f7400000000000000000000000000004d617463680000000000000000000000', - table_name: 'Match', - table_namespace: '', - table_type: 'offchain', -}; - -export const MUD_SCHEMA: MudWorldSchema = { - key_names: [ 'matchEntityKey', 'entity' ], - key_types: [ 'bytes32', 'bytes32' ], - value_names: [ 'matchEntity' ], - value_types: [ 'bytes32' ], -}; - -export const MUD_WORLD: MudWorldItem = { - address: ADDRESS_PARAMS, - coin_balance: '7072643779453701031672', - transactions_count: 442, -}; diff --git a/client/features/chain-variants/mud/types/api.ts b/client/features/chain-variants/mud/types/api.ts deleted file mode 100644 index bde2e9d223d..00000000000 --- a/client/features/chain-variants/mud/types/api.ts +++ /dev/null @@ -1,100 +0,0 @@ -// SPDX-License-Identifier: LicenseRef-Blockscout - -import type { Abi } from 'abitype'; - -import type { AddressParam } from 'client/slices/address/types/api'; - -export type MudWorldsResponse = { - items: Array; - next_page_params: { - items_count: number; - world: string; - }; -}; - -export type MudWorldItem = { - address: AddressParam; - coin_balance: string; - transactions_count: number | null; -}; - -export type MudWorldSchema = { - key_names: Array; - key_types: Array; - value_names: Array; - value_types: Array; -}; - -export type MudWorldTable = { - table_full_name: string; - table_id: string; - table_name: string; - table_namespace: string; - table_type: string; -}; - -export type AddressMudTableItem = { - schema: MudWorldSchema; - table: MudWorldTable; -}; - -export type AddressMudTables = { - items: Array; - next_page_params: { - items_count: number; - table_id: string; - }; -}; - -export type AddressMudTablesFilter = { - q?: string; -}; - -export type AddressMudRecords = { - items: Array; - schema: MudWorldSchema; - table: MudWorldTable; - next_page_params: { - items_count: number; - key0: string; - key1: string; - key_bytes: string; - }; -}; - -export type AddressMudRecordsItem = { - decoded: Record>; - id: string; - is_deleted: boolean; - timestamp: string; -}; - -export type AddressMudRecordsFilter = { - filter_key0?: string; - filter_key1?: string; -}; - -export type AddressMudRecordsSorting = { - sort: 'key0' | 'key1'; - order: 'asc' | 'desc' | undefined; -}; - -export type AddressMudRecord = { - record: AddressMudRecordsItem; - schema: MudWorldSchema; - table: MudWorldTable; -}; - -export interface SmartContractMudSystemsResponse { - items: Array; -} - -export interface SmartContractMudSystemItem { - address_hash: string; - name: string; -} - -export interface SmartContractMudSystemInfo { - name: string; - abi: Abi; -} diff --git a/client/features/chain-variants/rootstock/mocks/block.ts b/client/features/chain-variants/rootstock/mocks/block.ts deleted file mode 100644 index 68538b68ee1..00000000000 --- a/client/features/chain-variants/rootstock/mocks/block.ts +++ /dev/null @@ -1,13 +0,0 @@ -/* eslint-disable max-len */ -import type { Block } from 'client/slices/block/types/api'; - -import { base } from 'client/slices/block/mocks/block'; - -export const rootstock: Block = { - ...base, - bitcoin_merged_mining_coinbase_transaction: '0x0000000000000080a1219cea298d65d545b56abafe7c5421edfaf084cf9e374bb23ea985ebd86b206088ac0000000000000000266a24aa21a9edb2ac3022ad2a5327449f029b6aa3d2e55605061b5d8171b30abf5b330d1959c900000000000000002a6a52534b424c4f434b3a481d071e57c6c47cb8eb716295a7079b15859962abf35e32f107b21f003f0bb900000000', - bitcoin_merged_mining_header: '0x000000204a7e42cadf8b5b0a094755c5a13298e596d61f361c6d31171a00000000000000970e51977cd6f82bab9ed62e678c8d8ca664af9d5c3b5cea39d5d4337c7abedae334c9649fc63e1982a84aaa', - bitcoin_merged_mining_merkle_proof: '0x09f386e5e6feb20706a1b5d0817eae96f0ebb0d713eeefe6d5625afc6fd87fcdfe8cc9118bb49e32db87f8e928dcb13dd327b526ced76fb9de0115a5dca8d2a9657c929360ad07418fc7e1a3120da27e0002470d0c98c9b8b5b2835e64e379421d2469204533307bf0c5a087d93fd1dfb3aaea3ee83099928860f6cca891cf59d73c4e3c6053ea4b385dce39067e87c28805ddd89c4ff10500401bec7c248f749ad6f0933e6ad270e447d01711aca1cc26d7989ee59e1431fd2fd5d058edca6d', - hash_for_merged_mining: '0x481d071e57c6c47cb8eb716295a7079b15859962abf35e32f107b21f003f0bb9', - minimum_gas_price: '59240000', -}; diff --git a/client/features/chain-variants/stability/mocks/tx.ts b/client/features/chain-variants/stability/mocks/tx.ts deleted file mode 100644 index 684ff131832..00000000000 --- a/client/features/chain-variants/stability/mocks/tx.ts +++ /dev/null @@ -1,47 +0,0 @@ -import type { Transaction } from 'client/slices/tx/types/api'; - -import { base } from 'client/slices/tx/mocks/tx'; - -export const stabilityTx: Transaction = { - ...base, - stability_fee: { - dapp_address: { - hash: '0xDc2B93f3291030F3F7a6D9363ac37757f7AD5C43', - implementations: null, - is_contract: false, - is_verified: null, - name: null, - private_tags: [], - public_tags: [], - watchlist_names: [], - ens_domain_name: null, - }, - dapp_fee: '34381250000000', - token: { - address_hash: '0xDc2B93f3291030F3F7a6D9363ac37757f7AD5C43', - circulating_market_cap: null, - decimals: '18', - exchange_rate: '123.567', - holders_count: '92', - icon_url: 'https://example.com/icon.png', - name: 'Stability Gas', - symbol: 'GAS', - total_supply: '10000000000000000000000000', - type: 'ERC-20', - reputation: 'ok', - }, - total_fee: '68762500000000', - validator_address: { - hash: '0x1432997a4058acbBe562F3c1E79738c142039044', - implementations: null, - is_contract: false, - is_verified: null, - name: null, - private_tags: [], - public_tags: [], - watchlist_names: [], - ens_domain_name: null, - }, - validator_fee: '34381250000000', - }, -}; diff --git a/client/features/chain-variants/stability/types/api.ts b/client/features/chain-variants/stability/types/api.ts deleted file mode 100644 index c70dea3a35c..00000000000 --- a/client/features/chain-variants/stability/types/api.ts +++ /dev/null @@ -1,15 +0,0 @@ -// SPDX-License-Identifier: LicenseRef-Blockscout - -import type { AddressParam } from 'client/slices/address/types/api'; -import type { TokenInfo } from 'client/slices/token/types/api'; - -export interface TransactionStability { - stability_fee?: { - dapp_address: AddressParam; - dapp_fee: string; - token: TokenInfo; - total_fee: string; - validator_address: AddressParam; - validator_fee: string; - }; -} diff --git a/client/features/chain-variants/suave/pages/kettle/KettleTxs.tsx b/client/features/chain-variants/suave/pages/kettle/KettleTxs.tsx deleted file mode 100644 index 4088f9c58a7..00000000000 --- a/client/features/chain-variants/suave/pages/kettle/KettleTxs.tsx +++ /dev/null @@ -1,43 +0,0 @@ -// SPDX-License-Identifier: LicenseRef-Blockscout - -import { useRouter } from 'next/router'; -import React from 'react'; - -import AddressEntity from 'client/slices/address/components/entity/AddressEntity'; -import TxsWithFrontendSorting from 'client/slices/tx/pages/index/list/TxsWithFrontendSorting'; -import { TX } from 'client/slices/tx/stubs/tx'; - -import getQueryParamString from 'client/shared/router/get-query-param-string'; - -import { generateListStub } from 'stubs/utils'; -import PageTitle from 'ui/shared/Page/PageTitle'; -import useQueryWithPages from 'ui/shared/pagination/useQueryWithPages'; - -const KettleTxs = () => { - const router = useRouter(); - - const hash = getQueryParamString(router.query.hash); - - const query = useQueryWithPages({ - resourceName: 'general:txs_execution_node', - pathParams: { hash }, - options: { - placeholderData: generateListStub<'general:txs_execution_node'>(TX, 50, { next_page_params: { - block_number: 9005713, - index: 5, - items_count: 50, - filter: 'validated', - } }), - }, - }); - - return ( - <> - - - - - ); -}; - -export default KettleTxs; diff --git a/client/features/chain-variants/suave/pages/tx/TxDetailsWrapped.tsx b/client/features/chain-variants/suave/pages/tx/TxDetailsWrapped.tsx deleted file mode 100644 index 52bb425bc5b..00000000000 --- a/client/features/chain-variants/suave/pages/tx/TxDetailsWrapped.tsx +++ /dev/null @@ -1,132 +0,0 @@ -// SPDX-License-Identifier: LicenseRef-Blockscout - -import { Flex, Grid } from '@chakra-ui/react'; -import BigNumber from 'bignumber.js'; -import React from 'react'; - -import type { Transaction } from 'client/slices/tx/types/api'; -import type { ExcludeUndefined } from 'types/utils'; - -import AddressEntity from 'client/slices/address/components/entity/AddressEntity'; -import LogDecodedInputData from 'client/slices/log/components/LogDecodedInputData'; -import TxEntity from 'client/slices/tx/components/entity/TxEntity'; -import TxFee from 'client/slices/tx/components/TxFee'; -import TxDetailsGasPrice from 'client/slices/tx/pages/details/info/parts/TxDetailsGasPrice'; -import TxDetailsOther from 'client/slices/tx/pages/details/info/parts/TxDetailsOther'; - -import { Badge } from 'toolkit/chakra/badge'; -import * as DetailedInfo from 'ui/shared/DetailedInfo/DetailedInfo'; -import DetailedInfoNativeCoinValue from 'ui/shared/DetailedInfo/DetailedInfoNativeCoinValue'; -import RawInputData from 'ui/shared/RawInputData'; - -interface Props { - data: ExcludeUndefined; -} - -const TxDetailsWrapped = ({ data }: Props) => { - return ( - - - Transaction hash - - - - - - - Method - - - - { data.method } - - - - - - { data.to && ( - <> - - { data.to.is_contract ? 'Interacted with contract' : 'To' } - - - - - - - - ) } - - - - - Value - - - - { data.fee.value !== null && ( - <> - - Transaction fee - - - - - - ) } - - - - { data.gas_limit && ( - <> - - Gas limit - - - { BigNumber(data.gas_limit).toFormat() } - - - ) } - - - - - - - Raw input - - - - - - { data.decoded_input && ( - <> - - Decoded input data - - - - - - ) } - - ); -}; - -export default TxDetailsWrapped; diff --git a/client/features/chain-variants/suave/types/api.ts b/client/features/chain-variants/suave/types/api.ts deleted file mode 100644 index 1836d53a94c..00000000000 --- a/client/features/chain-variants/suave/types/api.ts +++ /dev/null @@ -1,13 +0,0 @@ -// SPDX-License-Identifier: LicenseRef-Blockscout - -import type { AddressParam } from 'client/slices/address/types/api'; -import type { Transaction } from 'client/slices/tx/types/api'; - -export type WrappedTransactionFields = 'decoded_input' | 'fee' | 'gas_limit' | 'gas_price' | 'hash' | 'max_fee_per_gas' | -'max_priority_fee_per_gas' | 'method' | 'nonce' | 'raw_input' | 'to' | 'type' | 'value'; - -export interface TransactionSuave { - execution_node?: AddressParam | null; - allowed_peekers?: Array; - wrapped?: Pick; -} diff --git a/client/features/chain-variants/tac/mocks/search.ts b/client/features/chain-variants/tac/mocks/search.ts deleted file mode 100644 index 74f4fd87a44..00000000000 --- a/client/features/chain-variants/tac/mocks/search.ts +++ /dev/null @@ -1,8 +0,0 @@ -import type { SearchResultTacOperation } from 'client/features/chain-variants/tac/types/api'; - -import * as tacOperationMock from './operations'; - -export const tacOperation1: SearchResultTacOperation = { - type: 'tac_operation', - tac_operation: tacOperationMock.tacOperation, -}; diff --git a/client/features/chain-variants/tac/stubs.ts b/client/features/chain-variants/tac/stubs.ts deleted file mode 100644 index ee20e5c4001..00000000000 --- a/client/features/chain-variants/tac/stubs.ts +++ /dev/null @@ -1,38 +0,0 @@ -import * as tac from '@blockscout/tac-operation-lifecycle-types'; - -import { ADDRESS_HASH } from 'client/slices/address/stubs/address-params'; - -export const TAC_OPERATION: tac.OperationBriefDetails = { - operation_id: '0x4d3d36b7fcab0a2f93f24bf313ebfe9cc0b2c7157d2aef7e7f7d5835528428c6', - type: tac.OperationType.TAC_TON, - timestamp: '2025-05-05T12:32:22.000Z', - sender: { - address: '0x4d3d36b7fcab0a2f93f24bf313ebfe9cc0b2c7157d2aef7e7f7d5835528428c6', - blockchain: tac.BlockchainType.TAC, - }, -}; - -export const TAC_OPERATION_DETAILS: tac.OperationDetails = { - operation_id: '0x6e7cdeea3f39e7664597a44ddb33ce47ba061cbee2992e2c7b0e3f9294ff8b30', - type: tac.OperationType.TAC_TON, - timestamp: '2025-05-05T12:32:22.000Z', - sender: { - address: ADDRESS_HASH, - blockchain: tac.BlockchainType.TAC, - }, - status_history: [ - { - type: tac.OperationStage_StageType.COLLECTED_IN_TAC, - is_exist: true, - is_success: true, - timestamp: '2025-05-05T12:32:22.000Z', - transactions: [ - { - hash: '0x064e57a9f43d032ac0c1cb0d7883b0d783a9fa5d207a39563a6ed06c5dc17622', - type: tac.BlockchainType.TON, - }, - ], - note: undefined, - }, - ], -}; diff --git a/client/features/chain-variants/tac/types/api.ts b/client/features/chain-variants/tac/types/api.ts deleted file mode 100644 index 3eacb9f92ba..00000000000 --- a/client/features/chain-variants/tac/types/api.ts +++ /dev/null @@ -1,8 +0,0 @@ -// SPDX-License-Identifier: LicenseRef-Blockscout - -import type * as tac from '@blockscout/tac-operation-lifecycle-types'; - -export interface SearchResultTacOperation { - type: 'tac_operation'; - tac_operation: tac.OperationDetails; -} diff --git a/client/features/chain-variants/zeta-chain/components/AddressEntityZetaChain.tsx b/client/features/chain-variants/zeta-chain/components/AddressEntityZetaChain.tsx deleted file mode 100644 index ac236c952ea..00000000000 --- a/client/features/chain-variants/zeta-chain/components/AddressEntityZetaChain.tsx +++ /dev/null @@ -1,71 +0,0 @@ -// SPDX-License-Identifier: LicenseRef-Blockscout - -import { chakra } from '@chakra-ui/react'; -import React from 'react'; - -import type { ExternalChain } from 'types/externalChains'; - -import { route } from 'nextjs/routes'; - -import type * as AddressEntityBase from 'client/slices/address/components/entity/AddressEntity'; -import AddressEntity from 'client/slices/address/components/entity/AddressEntity'; -import { unknownAddress } from 'client/slices/address/utils/consts'; - -import useZetaChainConfig from 'client/features/chain-variants/zeta-chain/hooks/useZetaChainConfig'; - -import config from 'configs/app'; -import { useColorModeValue } from 'toolkit/chakra/color-mode'; -import getChainTooltipText from 'ui/shared/externalChains/getChainTooltipText'; -import IconSvg from 'ui/shared/IconSvg'; - -interface Props extends Omit { - chainId?: string; - address: { hash: string }; -} - -const AddressEntityZetaChain = ({ chainId, address, ...props }: Props) => { - const { data: chainsConfig } = useZetaChainConfig(); - - const addressFull = { ...unknownAddress, hash: address.hash }; - const chain = chainsConfig?.find((chain) => chain.id.toString() === chainId); - const isCurrentChain = chainId === config.chain.id; - - const href = (() => { - if (chain && 'address_url_template' in chain && chain.address_url_template) { - return chain.address_url_template.replace('{hash}', address.hash); - } - return route({ - pathname: '/address/[hash]', - query: { - ...props.query, - hash: address.hash, - }, - }, { chain: isCurrentChain ? undefined : chain as ExternalChain, external: Boolean(chain) }); - })(); - - const zetaChainIcon = useColorModeValue(config.UI.navigation.icon.default, config.UI.navigation.icon.dark || config.UI.navigation.icon.default); - const chainLogo = isCurrentChain ? zetaChainIcon : chain?.logo; - const chainName = isCurrentChain ? config.chain.name : chain?.name; - const iconStub = ( - - ); - - return ( - - ); -}; - -export default chakra(AddressEntityZetaChain); diff --git a/client/features/chain-variants/zeta-chain/components/TxEntityZetaChainExternal.tsx b/client/features/chain-variants/zeta-chain/components/TxEntityZetaChainExternal.tsx deleted file mode 100644 index 91600cfddea..00000000000 --- a/client/features/chain-variants/zeta-chain/components/TxEntityZetaChainExternal.tsx +++ /dev/null @@ -1,32 +0,0 @@ -// SPDX-License-Identifier: LicenseRef-Blockscout - -import { chakra } from '@chakra-ui/react'; -import React from 'react'; - -import type { ExternalChain } from 'types/externalChains'; - -import { route } from 'nextjs/routes'; - -import * as TxEntity from 'client/slices/tx/components/entity/TxEntity'; - -import useZetaChainConfig from 'client/features/chain-variants/zeta-chain/hooks/useZetaChainConfig'; - -type Props = { - chainId: string; -} & Omit; - -const TxEntityZetaChainExternal = (props: Props) => { - const { data: chainsConfig } = useZetaChainConfig(); - const chain = chainsConfig?.find((chain) => chain.id.toString() === props.chainId); - - const defaultHref = (() => { - if (chain && 'tx_url_template' in chain && chain.tx_url_template) { - return chain.tx_url_template.replace('{hash}', props.hash); - } - return route({ pathname: '/tx/[hash]', query: { hash: props.hash } }, { chain: chain as ExternalChain, external: true }); - })(); - - return ; -}; - -export default chakra(TxEntityZetaChainExternal); diff --git a/client/features/chain-variants/zeta-chain/pages/cctx-details/ZetaChainCCTX.tsx b/client/features/chain-variants/zeta-chain/pages/cctx-details/ZetaChainCCTX.tsx deleted file mode 100644 index 59da07fba6f..00000000000 --- a/client/features/chain-variants/zeta-chain/pages/cctx-details/ZetaChainCCTX.tsx +++ /dev/null @@ -1,45 +0,0 @@ -// SPDX-License-Identifier: LicenseRef-Blockscout - -import { useRouter } from 'next/router'; -import React from 'react'; - -import useApiQuery from 'client/api/hooks/useApiQuery'; - -import TxEntityZetaChainCC from 'client/features/chain-variants/zeta-chain/components/TxEntityZetaChainCC'; -import { ZETA_CHAIN_CCTX } from 'client/features/chain-variants/zeta-chain/stubs'; - -import throwOnResourceLoadError from 'client/shared/errors/throw-on-resource-load-error'; -import getQueryParamString from 'client/shared/router/get-query-param-string'; - -import TextAd from 'ui/shared/ad/TextAd'; -import PageTitle from 'ui/shared/Page/PageTitle'; - -import ZetaChainCCTXDetails from './ZetaChainCCTXDetails'; - -const ZetaChainCCTX = () => { - const router = useRouter(); - - const hash = getQueryParamString(router.query.hash); - - const cctxQuery = useApiQuery('zetachain:transaction', { - queryParams: { cctx_id: hash }, - queryOptions: { - placeholderData: ZETA_CHAIN_CCTX, - }, - }); - - throwOnResourceLoadError(cctxQuery); - - return ( - <> - - } - /> - - - ); -}; - -export default ZetaChainCCTX; diff --git a/client/features/chain-variants/zeta-chain/pages/cctx-details/ZetaChainCCTXDetailsRelatedTx.tsx b/client/features/chain-variants/zeta-chain/pages/cctx-details/ZetaChainCCTXDetailsRelatedTx.tsx deleted file mode 100644 index 2ccd6b70b22..00000000000 --- a/client/features/chain-variants/zeta-chain/pages/cctx-details/ZetaChainCCTXDetailsRelatedTx.tsx +++ /dev/null @@ -1,56 +0,0 @@ -// SPDX-License-Identifier: LicenseRef-Blockscout - -import { Box } from '@chakra-ui/react'; -import React from 'react'; - -import { type RelatedCctx, CctxStatusReduced } from '@blockscout/zetachain-cctx-types'; - -import TxEntityZetaChainCC from 'client/features/chain-variants/zeta-chain/components/TxEntityZetaChainCC'; -import ZetaChainCCTXReducedStatus from 'client/features/chain-variants/zeta-chain/components/ZetaChainCCTXReducedStatus'; -import useZetaChainConfig from 'client/features/chain-variants/zeta-chain/hooks/useZetaChainConfig'; - -import { Skeleton } from 'toolkit/chakra/skeleton'; -import ChainIcon from 'ui/shared/externalChains/ChainIcon'; -import IconSvg from 'ui/shared/IconSvg'; - -type Props = { - tx: RelatedCctx; - isLoading: boolean; -}; - -const ZetaChainCCTXDetailsRelatedTx = ({ tx, isLoading }: Props) => { - const { data: chainsConfig } = useZetaChainConfig(); - const chainFrom = chainsConfig?.find((chain) => chain.id === tx.source_chain_id.toString()); - - const chainsTo = tx.outbound_params.map((p) => chainsConfig?.find((chain) => chain.id === p.chain_id.toString())); - - const color = (() => { - if (tx.status_reduced === CctxStatusReduced.SUCCESS) { - return 'text.success'; - } - if (tx.status_reduced === CctxStatusReduced.FAILED) { - return 'text.error'; - } - return 'text.secondary'; - })(); - - return ( - - - - { chainsTo.map((chain, index) => ) } - CCTX - - - - ); -}; - -export default ZetaChainCCTXDetailsRelatedTx; diff --git a/client/features/chain-variants/zeta-chain/pages/cctx-index/TransactionsZetaChain.tsx b/client/features/chain-variants/zeta-chain/pages/cctx-index/TransactionsZetaChain.tsx deleted file mode 100644 index 6c47e85befd..00000000000 --- a/client/features/chain-variants/zeta-chain/pages/cctx-index/TransactionsZetaChain.tsx +++ /dev/null @@ -1,156 +0,0 @@ -// SPDX-License-Identifier: LicenseRef-Blockscout - -import { useRouter } from 'next/router'; -import React from 'react'; - -import type { TabItemRegular } from 'toolkit/components/AdaptiveTabs/types'; - -import { SocketProvider } from 'client/api/socket/context'; - -import TxsWithFrontendSorting from 'client/slices/tx/pages/index/list/TxsWithFrontendSorting'; -import TxsStats from 'client/slices/tx/pages/index/stats/TxsStats'; -import { TX } from 'client/slices/tx/stubs/tx'; - -import useIsAuth from 'client/features/account/hooks/useIsAuth'; -import TxsWatchlist from 'client/features/account/pages/tx-index-watchlist/TxsWatchlist'; - -import useIsMobile from 'client/shared/hooks/useIsMobile'; -import getQueryParamString from 'client/shared/router/get-query-param-string'; - -import config from 'configs/app'; -import { generateListStub } from 'stubs/utils'; -import RoutedTabs from 'toolkit/components/RoutedTabs/RoutedTabs'; -import ActionBar from 'ui/shared/ActionBar'; -import AdvancedFilterLink from 'ui/shared/links/AdvancedFilterLink'; -import PageTitle from 'ui/shared/Page/PageTitle'; -import Pagination from 'ui/shared/pagination/Pagination'; -import useQueryWithPages from 'ui/shared/pagination/useQueryWithPages'; - -import ZetaChainCCTXsTab from './ZetaChainCCTXsTab'; -import ZetaChainEvmTransactions from './ZetaChainEvmTransactions'; - -const ZETACHAIN_TABS = [ 'zetachain_validated', 'zetachain_pending' ]; -const CROSS_CHAIN_TABS = [ 'cctx_pending', 'cctx_mined' ]; - -const TransactionsZetaChain = () => { - const router = useRouter(); - const isMobile = useIsMobile(); - const tab = getQueryParamString(router.query.tab); - - const txsWithBlobsQuery = useQueryWithPages({ - resourceName: 'general:txs_with_blobs', - filters: { type: 'blob_transaction' }, - options: { - enabled: config.features.dataAvailability.isEnabled && tab === 'blob_txs', - placeholderData: generateListStub<'general:txs_with_blobs'>(TX, 50, { next_page_params: { - block_number: 10602877, - index: 8, - items_count: 50, - } }), - }, - }); - - const txsWatchlistQuery = useQueryWithPages({ - resourceName: 'general:txs_watchlist', - options: { - enabled: tab === 'watchlist', - placeholderData: generateListStub<'general:txs_watchlist'>(TX, 50, { next_page_params: { - block_number: 9005713, - index: 5, - items_count: 50, - } }), - }, - }); - - const isAuth = useIsAuth(); - - // for cctxs and evm txs we show pagination with the secondary tabs - const pagination = (() => { - switch (tab) { - case 'watchlist': return txsWatchlistQuery.pagination; - case 'blob_txs': return txsWithBlobsQuery.pagination; - default: return null; - } - })(); - - const topRow = (() => { - if (isMobile) { - return null; - } - - if (tab !== 'blob_txs' && tab !== 'watchlist') { - return null; - } - - const isAdvancedFilterEnabled = config.features.advancedFilter.isEnabled; - - if (!isAdvancedFilterEnabled && !pagination?.isVisible) { - return null; - } - - return ( - - { isAdvancedFilterEnabled && } - { pagination?.isVisible && } - - ); - })(); - - const tabs: Array = [ - { - id: 'cctx', - title: 'Cross chain', - component: ( - - - - ), - subTabs: CROSS_CHAIN_TABS, - }, - { - id: 'zetachain', - title: 'ZetaChain EVM', - component: , - subTabs: ZETACHAIN_TABS, - }, - config.features.dataAvailability.isEnabled && { - id: 'blob_txs', - title: 'Blob txns', - component: ( - <> - - { topRow } - - - ), - }, - isAuth ? { - id: 'watchlist', - title: 'Watch list', - component: ( - <> - - { topRow } - - - ), - } : undefined, - ].filter(Boolean); - - return ( - <> - - - - ); -}; - -export default TransactionsZetaChain; diff --git a/client/features/chain-variants/zeta-chain/pages/cctx-index/ZetaChainAgeFilter.tsx b/client/features/chain-variants/zeta-chain/pages/cctx-index/ZetaChainAgeFilter.tsx deleted file mode 100644 index 3c107e715e9..00000000000 --- a/client/features/chain-variants/zeta-chain/pages/cctx-index/ZetaChainAgeFilter.tsx +++ /dev/null @@ -1,42 +0,0 @@ -// SPDX-License-Identifier: LicenseRef-Blockscout - -import type { ZetaChainCCTXFilterParams } from 'client/features/chain-variants/zeta-chain/types/client'; -import type { AdvancedFilterAge } from 'types/api/advancedFilter'; - -import dayjs from 'lib/date/dayjs'; -import { SECOND } from 'toolkit/utils/consts'; -import BaseAgeFilter, { type DateConverter } from 'ui/advancedFilter/filters/BaseAgeFilter'; - -const FILTER_PARAM_FROM = 'start_timestamp'; -const FILTER_PARAM_TO = 'end_timestamp'; -const FILTER_PARAM_AGE = 'age'; - -const dateConverter: DateConverter = { - toFilterValue: (date: string) => dayjs(date).unix().toString(), // Convert ISO to Unix timestamp - fromFilterValue: (value: string | undefined) => value || '', - getCurrentValue: (value: string | undefined) => value ? dayjs(Number(value) * SECOND).format('YYYY-MM-DD') : '', -}; - -type Props = { - value?: { age: AdvancedFilterAge | ''; from: string; to: string }; - handleFilterChange: (field: keyof ZetaChainCCTXFilterParams, value?: string) => void; - columnName: string; - isLoading?: boolean; - onClose?: () => void; -}; - -const ZetaChainAgeFilter = (props: Props) => { - return ( - - { ...props } - fieldNames={{ - from: FILTER_PARAM_FROM, - to: FILTER_PARAM_TO, - age: FILTER_PARAM_AGE, - }} - dateConverter={ dateConverter } - /> - ); -}; - -export default ZetaChainAgeFilter; diff --git a/client/features/chain-variants/zeta-chain/pages/cctx-index/ZetaChainCCTXListItem.tsx b/client/features/chain-variants/zeta-chain/pages/cctx-index/ZetaChainCCTXListItem.tsx deleted file mode 100644 index 87cc0b0b1ba..00000000000 --- a/client/features/chain-variants/zeta-chain/pages/cctx-index/ZetaChainCCTXListItem.tsx +++ /dev/null @@ -1,72 +0,0 @@ -// SPDX-License-Identifier: LicenseRef-Blockscout - -import { Grid, VStack, Text } from '@chakra-ui/react'; -import React from 'react'; - -import type { CctxListItem } from '@blockscout/zetachain-cctx-types'; - -import AddressEntityZetaChain from 'client/features/chain-variants/zeta-chain/components/AddressEntityZetaChain'; -import TxEntityZetaChainCC from 'client/features/chain-variants/zeta-chain/components/TxEntityZetaChainCC'; -import ZetaChainCCTXReducedStatus from 'client/features/chain-variants/zeta-chain/components/ZetaChainCCTXReducedStatus'; -import ZetaChainCCTXValue from 'client/features/chain-variants/zeta-chain/components/ZetaChainCCTXValue'; - -import dayjs from 'lib/date/dayjs'; -import { Skeleton } from 'toolkit/chakra/skeleton'; -import { SECOND } from 'toolkit/utils/consts'; -import TextSeparator from 'ui/shared/TextSeparator'; -import Time from 'ui/shared/time/Time'; - -type Props = { - tx: CctxListItem; - isLoading?: boolean; - animation?: string; -}; - -const LatestZetaChainCCTXItem = ({ tx, isLoading, animation }: Props) => { - return ( - - - - - { dayjs(Number(tx.last_update_timestamp) * SECOND).fromNow() } - - - - Sender - - Receiver - - Asset - - - - ); -}; - -export default React.memo(LatestZetaChainCCTXItem); diff --git a/client/features/chain-variants/zeta-chain/pages/cctx-index/ZetaChainEvmTransactions.tsx b/client/features/chain-variants/zeta-chain/pages/cctx-index/ZetaChainEvmTransactions.tsx deleted file mode 100644 index 34b8f46ab45..00000000000 --- a/client/features/chain-variants/zeta-chain/pages/cctx-index/ZetaChainEvmTransactions.tsx +++ /dev/null @@ -1,132 +0,0 @@ -// SPDX-License-Identifier: LicenseRef-Blockscout - -import { Flex } from '@chakra-ui/react'; -import { capitalize } from 'es-toolkit/compat'; -import { useRouter } from 'next/router'; -import React from 'react'; - -import type { TabItemRegular } from 'toolkit/components/AdaptiveTabs/types'; - -import TxsWithFrontendSorting from 'client/slices/tx/pages/index/list/TxsWithFrontendSorting'; -import TxsStats from 'client/slices/tx/pages/index/stats/TxsStats'; -import { TX } from 'client/slices/tx/stubs/tx'; - -import getChainValidationActionText from 'client/shared/chain/get-chain-validation-action-text'; -import useIsMobile from 'client/shared/hooks/useIsMobile'; -import getQueryParamString from 'client/shared/router/get-query-param-string'; - -import config from 'configs/app'; -import { generateListStub } from 'stubs/utils'; -import RoutedTabs from 'toolkit/components/RoutedTabs/RoutedTabs'; -import AdvancedFilterLink from 'ui/shared/links/AdvancedFilterLink'; -import Pagination from 'ui/shared/pagination/Pagination'; -import useQueryWithPages from 'ui/shared/pagination/useQueryWithPages'; - -const TAB_LIST_PROPS = { - marginBottom: 0, - pt: 6, - pb: 6, - marginTop: -5, -}; -const TABS_HEIGHT = 88; - -const ZetaChainEvmTransactions = () => { - const router = useRouter(); - const tab = getQueryParamString(router.query.tab); - const isMobile = useIsMobile(); - - const txsValidatedQuery = useQueryWithPages({ - resourceName: 'general:txs_validated', - filters: { filter: 'validated' }, - options: { - enabled: !tab || tab === 'zetachain' || tab === 'zetachain_validated', - placeholderData: generateListStub<'general:txs_validated'>(TX, 50, { next_page_params: { - block_number: 9005713, - index: 5, - items_count: 50, - filter: 'validated', - } }), - }, - }); - - const txsPendingQuery = useQueryWithPages({ - resourceName: 'general:txs_pending', - filters: { filter: 'pending' }, - options: { - enabled: tab === 'zetachain_pending', - placeholderData: generateListStub<'general:txs_pending'>(TX, 50, { next_page_params: { - inserted_at: '2024-02-05T07:04:47.749818Z', - hash: '0x00', - filter: 'pending', - } }), - }, - }); - - const verifiedTitle = capitalize(getChainValidationActionText()); - - const tabs: Array = [ - { - id: 'zetachain_validated', - title: verifiedTitle, - component: - }, - { - id: 'zetachain_pending', - title: 'Pending', - component: ( - - ), - }, - ]; - - const pagination = (() => { - switch (tab) { - case 'zetachain_pending': return txsPendingQuery.pagination; - default: return txsValidatedQuery.pagination; - } - })(); - - const rightSlot = (() => { - if (isMobile) { - return null; - } - - const isAdvancedFilterEnabled = config.features.advancedFilter.isEnabled; - - if (!isAdvancedFilterEnabled && !pagination.isVisible) { - return null; - } - - return ( - - { isAdvancedFilterEnabled && } - { pagination.isVisible && } - - ); - })(); - - return ( - <> - - - - ); -}; - -export default ZetaChainEvmTransactions; diff --git a/client/features/chain-variants/zeta-chain/pages/cctx-index/__screenshots__/ZetaChainCCTXsTab.pw.tsx_dark-color-mode_mobile-base-view-dark-mode-1.png b/client/features/chain-variants/zeta-chain/pages/cctx-index/__screenshots__/ZetaChainCCTXsTab.pw.tsx_dark-color-mode_mobile-base-view-dark-mode-1.png deleted file mode 100644 index ad6c03a1c82..00000000000 Binary files a/client/features/chain-variants/zeta-chain/pages/cctx-index/__screenshots__/ZetaChainCCTXsTab.pw.tsx_dark-color-mode_mobile-base-view-dark-mode-1.png and /dev/null differ diff --git a/client/features/chain-variants/zeta-chain/pages/cctx-index/__screenshots__/ZetaChainCCTXsTab.pw.tsx_default_base-view-dark-mode-1.png b/client/features/chain-variants/zeta-chain/pages/cctx-index/__screenshots__/ZetaChainCCTXsTab.pw.tsx_default_base-view-dark-mode-1.png deleted file mode 100644 index 30859ef191e..00000000000 Binary files a/client/features/chain-variants/zeta-chain/pages/cctx-index/__screenshots__/ZetaChainCCTXsTab.pw.tsx_default_base-view-dark-mode-1.png and /dev/null differ diff --git a/client/features/chain-variants/zeta-chain/pages/cctx-index/__screenshots__/ZetaChainCCTXsTab.pw.tsx_default_mobile-base-view-dark-mode-1.png b/client/features/chain-variants/zeta-chain/pages/cctx-index/__screenshots__/ZetaChainCCTXsTab.pw.tsx_default_mobile-base-view-dark-mode-1.png deleted file mode 100644 index d42400228bb..00000000000 Binary files a/client/features/chain-variants/zeta-chain/pages/cctx-index/__screenshots__/ZetaChainCCTXsTab.pw.tsx_default_mobile-base-view-dark-mode-1.png and /dev/null differ diff --git a/client/features/chain-variants/zeta-chain/pages/home/LatestZetaChainCCTXItem.tsx b/client/features/chain-variants/zeta-chain/pages/home/LatestZetaChainCCTXItem.tsx deleted file mode 100644 index 9d795f1649f..00000000000 --- a/client/features/chain-variants/zeta-chain/pages/home/LatestZetaChainCCTXItem.tsx +++ /dev/null @@ -1,57 +0,0 @@ -// SPDX-License-Identifier: LicenseRef-Blockscout - -import { Grid } from '@chakra-ui/react'; -import React from 'react'; - -import type { CctxListItem } from '@blockscout/zetachain-cctx-types'; - -import AddressFromTo from 'client/slices/address/components/from-to/AddressFromTo'; - -import TxEntityZetaChainCC from 'client/features/chain-variants/zeta-chain/components/TxEntityZetaChainCC'; -import ZetaChainCCTXReducedStatus from 'client/features/chain-variants/zeta-chain/components/ZetaChainCCTXReducedStatus'; -import ZetaChainCCTXValue from 'client/features/chain-variants/zeta-chain/components/ZetaChainCCTXValue'; - -import { SECOND } from 'toolkit/utils/consts'; -import TimeWithTooltip from 'ui/shared/time/TimeWithTooltip'; - -type Props = { - tx: CctxListItem; - isLoading?: boolean; - animation?: string; -}; - -const LatestZetaChainCCTXItem = ({ tx, isLoading, animation }: Props) => { - return ( - - - - - - - - ); -}; - -export default React.memo(LatestZetaChainCCTXItem); diff --git a/client/features/chain-variants/zeta-chain/stubs.ts b/client/features/chain-variants/zeta-chain/stubs.ts deleted file mode 100644 index 5ba09c9e5a7..00000000000 --- a/client/features/chain-variants/zeta-chain/stubs.ts +++ /dev/null @@ -1,94 +0,0 @@ -import * as zetaChainCCTXType from '@blockscout/zetachain-cctx-types'; - -import { ADDRESS_HASH } from 'client/slices/address/stubs/address-params'; -import { BLOCK_HASH } from 'client/slices/block/stubs/block'; -import { TX_HASH } from 'client/slices/tx/stubs/tx'; - -export const ZETA_CHAIN_CCTX: zetaChainCCTXType.CrossChainTx = { - creator: ADDRESS_HASH, - index: TX_HASH, - zeta_fees: '0', - relayed_message: '', - cctx_status_reduced: zetaChainCCTXType.CctxStatusReduced.PENDING, - token_symbol: 'USDT.ARBSEP', - token_name: 'USDT.ARBSEP', - zrc20_contract_address: ADDRESS_HASH, - decimals: 6, - cctx_status: { - status: zetaChainCCTXType.CctxStatus.OUTBOUND_MINED, - status_message: '', - error_message: '', - last_update_timestamp: 1641139818, - is_abort_refunded: false, - created_timestamp: 1641139810, - error_message_revert: '', - error_message_abort: '', - }, - inbound_params: { - sender: ADDRESS_HASH, - sender_chain_id: 7001, - tx_origin: ADDRESS_HASH, - coin_type: zetaChainCCTXType.CoinType.GAS, - asset: '', - amount: '434880247204065094', - observed_hash: BLOCK_HASH, - observed_external_height: 12324831, - ballot_index: '', - finalized_zeta_height: 0, - tx_finalization_status: zetaChainCCTXType.TxFinalizationStatus.NOT_FINALIZED, - is_cross_chain_call: false, - status: zetaChainCCTXType.InboundStatus.INBOUND_SUCCESS, - confirmation_mode: zetaChainCCTXType.ConfirmationMode.SAFE, - }, - outbound_params: [ - { - receiver: ADDRESS_HASH, - receiver_chain_id: 7001, - coin_type: zetaChainCCTXType.CoinType.GAS, - amount: '0', - tss_nonce: 0, - gas_limit: 0, - gas_price: '', - gas_priority_fee: '', - hash: '', - ballot_index: '', - observed_external_height: 0, - gas_used: 0, - effective_gas_price: '', - effective_gas_limit: 0, - tss_pubkey: '', - tx_finalization_status: zetaChainCCTXType.TxFinalizationStatus.NOT_FINALIZED, - call_options: { - gas_limit: 0, - is_arbitrary_call: false, - }, - confirmation_mode: zetaChainCCTXType.ConfirmationMode.SAFE, - }, - ], - protocol_contract_version: zetaChainCCTXType.ProtocolContractVersion.V1, - revert_options: { - revert_address: ADDRESS_HASH, - call_on_revert: false, - abort_address: ADDRESS_HASH, - revert_message: '', - revert_gas_limit: '0', - }, - related_cctxs: [], -}; - -export const ZETA_CHAIN_CCTX_LIST_ITEM: zetaChainCCTXType.CctxListItem = { - index: TX_HASH, - status: zetaChainCCTXType.CctxStatus.OUTBOUND_MINED, - status_reduced: zetaChainCCTXType.CctxStatusReduced.SUCCESS, - amount: '434880247204065094', - source_chain_id: 7001, - target_chain_id: 7001, - created_timestamp: 1641139810, - last_update_timestamp: 1641139818, - sender_address: ADDRESS_HASH, - receiver_address: ADDRESS_HASH, - asset: '', - coin_type: zetaChainCCTXType.CoinType.GAS, - token_symbol: 'USDT.ARBSEP', - decimals: 6, -}; diff --git a/client/features/chain-variants/zeta-chain/types/client.ts b/client/features/chain-variants/zeta-chain/types/client.ts deleted file mode 100644 index 817aed758b3..00000000000 --- a/client/features/chain-variants/zeta-chain/types/client.ts +++ /dev/null @@ -1,47 +0,0 @@ -// SPDX-License-Identifier: LicenseRef-Blockscout - -import type { CctxListItem } from '@blockscout/zetachain-cctx-types'; -import type { AdvancedFilterAge } from 'types/api/advancedFilter'; -import type { ExternalChain } from 'types/externalChains'; - -export interface SearchResultZetaChainCCTX { - type: 'zetaChainCCTX'; - cctx: CctxListItem; -} - -export const ZETA_CHAIN_CCTX_STATUS_REDUCED_FILTERS = [ 'Success', 'Pending', 'Failed' ] as const; -export type StatusReducedFilters = typeof ZETA_CHAIN_CCTX_STATUS_REDUCED_FILTERS[number]; - -export const ZETA_CHAIN_CCTX_COIN_TYPE_FILTER = 'Zeta' as const; -export type CoinTypeFilter = typeof ZETA_CHAIN_CCTX_COIN_TYPE_FILTER; - -export type ZetaChainCCTXFilterParams = { - start_timestamp?: string; - end_timestamp?: string; - age?: AdvancedFilterAge | ''; /* frontend only */ - status_reduced?: Array | StatusReducedFilters; - sender_address?: Array | string; - receiver_address?: Array | string; - source_chain_id?: Array | string; - target_chain_id?: Array | string; - token_symbol?: Array | string; - coin_type?: Array | CoinTypeFilter; - hash?: string; -}; - -export interface ZetaChainChainsConfigEnv { - chain_id: number; - chain_name: string | null; - chain_logo?: string | null; - instance_url?: string; - address_url_template?: string; - tx_url_template?: string; -} - -export type ZetaChainExternalChainConfig = ExternalChain | { - id: string; - name: string; - logo: string | undefined; - address_url_template?: string; - tx_url_template?: string; -}; diff --git a/client/features/chain-variants/zilliqa/mocks/block.ts b/client/features/chain-variants/zilliqa/mocks/block.ts deleted file mode 100644 index d84534d249d..00000000000 --- a/client/features/chain-variants/zilliqa/mocks/block.ts +++ /dev/null @@ -1,44 +0,0 @@ -/* eslint-disable max-len */ -import type { ZilliqaBlockData } from 'client/features/chain-variants/zilliqa/types/api'; -import type { Block } from 'client/slices/block/types/api'; - -import { base } from 'client/slices/block/mocks/block'; - -export const zilliqaWithAggregateQuorumCertificate: Block = { - ...base, - zilliqa: { - view: 1137735, - aggregate_quorum_certificate: { - signature: '0x82d29e8f06adc890f6574c3d0ae0c811de1db695b05ed2755ef384fe21bc44f6505b99e201f6000a65f38ff6a13e286306d0e380ef1b43a273eb9947b3f11f852e14b93c258c32b516f89696fcb1190b147364b789572ebdf85d79c4cf3cbbbb', - view: 1137735, - signers: [ 1, 2, 3, 8 ], - nested_quorum_certificates: [ - { - signature: '0xaeb3567577f9db68565c6f97c158b17522620a9684c6f6beaa78920951ad4cae0f287b630bdd034c4a4f89ada42e3dbe012985e976a6f64057d735a4531a26b4e46c182414eabbe625e5b15e6645be5b6522bdec113df408874f6d1e0d894dca', - view: 1137732, - proposed_by_validator_index: 1, - signers: [ 3, 8 ], - }, - { - signature: '0xaeb3567577f9db68565c6f97c158b17522620a9684c6f6beaa78920951ad4cae0f287b630bdd034c4a4f89ada42e3dbe012985e976a6f64057d735a4531a26b4e46c182414eabbe625e5b15e6645be5b6522bdec113df408874f6d1e0d894dca', - view: 1137732, - proposed_by_validator_index: 2, - signers: [ 0, 2 ], - }, - ], - }, - quorum_certificate: { - signature: '0xaeb3567577f9db68565c6f97c158b17522620a9684c6f6beaa78920951ad4cae0f287b630bdd034c4a4f89ada42e3dbe012985e976a6f64057d735a4531a26b4e46c182414eabbe625e5b15e6645be5b6522bdec113df408874f6d1e0d894dca', - view: 1137732, - signers: [ 0, 2, 3, 8 ], - }, - }, -}; - -export const zilliqaWithoutAggregateQuorumCertificate: Block = { - ...base, - zilliqa: { - ...zilliqaWithAggregateQuorumCertificate.zilliqa, - aggregate_quorum_certificate: null, - } as ZilliqaBlockData, -}; diff --git a/client/features/chain-variants/zilliqa/types/api.ts b/client/features/chain-variants/zilliqa/types/api.ts deleted file mode 100644 index 393e52f8c6f..00000000000 --- a/client/features/chain-variants/zilliqa/types/api.ts +++ /dev/null @@ -1,33 +0,0 @@ -// SPDX-License-Identifier: LicenseRef-Blockscout - -export interface AddressZilliqaParams { - is_scilla_contract: boolean; -} - -export interface TransactionZilliqa { - zilliqa?: { - is_scilla: boolean; - }; -} - -export interface ZilliqaQuorumCertificate { - view: number; - signature: string; - signers: Array; -} - -export interface ZilliqaNestedQuorumCertificate extends ZilliqaQuorumCertificate { - proposed_by_validator_index: number; -} - -export interface ZilliqaBlockData { - view: number; - quorum_certificate: ZilliqaQuorumCertificate; - aggregate_quorum_certificate: (ZilliqaQuorumCertificate & { - nested_quorum_certificates: Array; - }) | null; -} - -export interface BlockZilliqa { - zilliqa?: ZilliqaBlockData; -} diff --git a/client/features/connect-wallet/hooks/wallet/types.ts b/client/features/connect-wallet/hooks/wallet/types.ts deleted file mode 100644 index ea26eeadd82..00000000000 --- a/client/features/connect-wallet/hooks/wallet/types.ts +++ /dev/null @@ -1,19 +0,0 @@ -// SPDX-License-Identifier: LicenseRef-Blockscout - -import type * as mixpanel from 'client/shared/analytics/mixpanel'; - -export interface Params { - source: mixpanel.EventPayload['Source']; - onConnect?: () => void; -} - -export interface Result { - connect: () => void; - disconnect: () => void; - isOpen: boolean; - isConnected: boolean; - isReconnecting: boolean; - address: string | undefined; - openModal: () => void; - type?: 'dynamicwaas'; -} diff --git a/client/features/connect-wallet/utils/chains.ts b/client/features/connect-wallet/utils/chains.ts deleted file mode 100644 index 7cea37baedd..00000000000 --- a/client/features/connect-wallet/utils/chains.ts +++ /dev/null @@ -1,112 +0,0 @@ -// SPDX-License-Identifier: LicenseRef-Blockscout - -import type { Chain } from 'viem'; - -import appConfig from 'configs/app'; -import essentialDappsChainsConfig from 'configs/essential-dapps-chains'; -import multichainConfig from 'configs/multichain'; - -const getChainInfo = ( - config: Partial = appConfig, - contracts?: Chain['contracts'], - logoUrl?: string, -): Chain | undefined => { - if (!config.chain || !config.app) { - return; - } - - return { - id: Number(config.chain.id), - name: config.chain.name ?? '', - nativeCurrency: { - decimals: config.chain.currency.decimals, - name: config.chain.currency.name ?? '', - symbol: config.chain.currency.symbol ?? '', - }, - rpcUrls: { - 'default': { - http: config.chain.rpcUrls, - }, - }, - blockExplorers: { - 'default': { - name: 'Blockscout', - url: config.app.baseUrl, - }, - }, - testnet: config.chain.isTestnet, - contracts, - custom: { - logoUrl: logoUrl ?? config.UI?.navigation.icon.default, - }, - }; -}; - -export const currentChain: Chain | undefined = !appConfig.features.multichain.isEnabled ? getChainInfo() : undefined; - -export const parentChain: Chain | undefined = (() => { - const rollupFeature = appConfig.features.rollup; - - const parentChain = rollupFeature.isEnabled && rollupFeature.parentChain; - - if (!parentChain) { - return; - } - - if (!parentChain.id || !parentChain.name || !parentChain.rpcUrls || !parentChain.baseUrl || !parentChain.currency) { - return; - } - - return { - id: parentChain.id, - name: parentChain.name, - nativeCurrency: parentChain.currency, - rpcUrls: { - 'default': { - http: parentChain.rpcUrls, - }, - }, - blockExplorers: { - 'default': { - name: 'Blockscout', - url: parentChain.baseUrl, - }, - }, - testnet: parentChain.isTestnet, - }; -})(); - -export const clusterChains: Array | undefined = (() => { - const config = multichainConfig(); - - if (!config) { - return; - } - - return config.chains.map(({ app_config: config, logo }) => getChainInfo(config, undefined, logo)).filter(Boolean); -})(); - -export const essentialDappsChains: Array | undefined = (() => { - const config = essentialDappsChainsConfig(); - - if (!config) { - return; - } - - return config.chains.map(({ app_config: config, contracts, logo }) => getChainInfo(config, contracts, logo)).filter(Boolean); -})(); - -export const chains = (() => { - if (essentialDappsChains) { - const hasCurrentChain = essentialDappsChains.some((chain) => chain.id === currentChain?.id); - const hasParentChain = essentialDappsChains.some((chain) => chain.id === parentChain?.id); - - return [ - ...essentialDappsChains, - hasCurrentChain ? undefined : currentChain, - hasParentChain ? undefined : parentChain, - ].filter(Boolean); - } - - return [ currentChain, parentChain, ...(clusterChains ?? []) ].filter(Boolean); -})(); diff --git a/client/features/contract-audit-reports/components/ContractSecurityAudits.tsx b/client/features/contract-audit-reports/components/ContractSecurityAudits.tsx deleted file mode 100644 index e2d77e75417..00000000000 --- a/client/features/contract-audit-reports/components/ContractSecurityAudits.tsx +++ /dev/null @@ -1,71 +0,0 @@ -// SPDX-License-Identifier: LicenseRef-Blockscout - -import { Box } from '@chakra-ui/react'; -import React from 'react'; - -import type { SmartContractSecurityAuditSubmission } from 'client/features/contract-audit-reports/types/api'; - -import useApiQuery from 'client/api/hooks/useApiQuery'; - -import dayjs from 'lib/date/dayjs'; -import { Button } from 'toolkit/chakra/button'; -import { Link } from 'toolkit/chakra/link'; -import { useDisclosure } from 'toolkit/hooks/useDisclosure'; -import ContainerWithScrollY from 'ui/shared/ContainerWithScrollY'; -import FormModal from 'ui/shared/FormModal'; - -import ContractSubmitAuditForm from './ContractSubmitAuditForm'; - -type Props = { - addressHash?: string; -}; - -const ContractSecurityAudits = ({ addressHash }: Props) => { - const { data, isPlaceholderData } = useApiQuery('general:contract_security_audits', { - pathParams: { hash: addressHash }, - queryOptions: { - refetchOnMount: false, - placeholderData: { items: [] }, - enabled: Boolean(addressHash), - }, - }); - - const formTitle = 'Submit audit'; - - const modalProps = useDisclosure(); - - const renderForm = React.useCallback(() => { - return ; - }, [ addressHash, modalProps.onClose ]); - - return ( - <> - { data?.items && data.items.length > 0 && ( - - - { data.items.map(item => ( - - { `${ item.audit_company_name }, ${ dayjs(item.audit_publish_date).format('MMM DD, YYYY') }` } - - )) } - - - ) } - - - open={ modalProps.open } - onOpenChange={ modalProps.onOpenChange } - title={ formTitle } - renderForm={ renderForm } - /> - - ); -}; - -export default React.memo(ContractSecurityAudits); diff --git a/client/features/contract-audit-reports/mocks.ts b/client/features/contract-audit-reports/mocks.ts deleted file mode 100644 index 99c597ac7af..00000000000 --- a/client/features/contract-audit-reports/mocks.ts +++ /dev/null @@ -1,16 +0,0 @@ -import type { SmartContractSecurityAudits } from './types/api'; - -export const contractAudits: SmartContractSecurityAudits = { - items: [ - { - audit_company_name: 'OpenZeppelin', - audit_publish_date: '2023-03-01', - audit_report_url: 'https://blog.openzeppelin.com/eip-4337-ethereum-account-abstraction-incremental-audit', - }, - { - audit_company_name: 'OpenZeppelin', - audit_publish_date: '2023-03-01', - audit_report_url: 'https://blog.openzeppelin.com/eip-4337-ethereum-account-abstraction-incremental-audit', - }, - ], -}; diff --git a/client/features/contract-audit-reports/types/api.ts b/client/features/contract-audit-reports/types/api.ts deleted file mode 100644 index a7049a5db9a..00000000000 --- a/client/features/contract-audit-reports/types/api.ts +++ /dev/null @@ -1,24 +0,0 @@ -// SPDX-License-Identifier: LicenseRef-Blockscout - -export type SmartContractSecurityAudit = { - audit_company_name: string; - audit_publish_date: string; - audit_report_url: string; -}; - -export type SmartContractSecurityAudits = { - items: Array; -}; - -export type SmartContractSecurityAuditSubmission = { - address_hash: string; - submitter_name: string; - submitter_email: string; - is_project_owner: boolean; - project_name: string; - project_url: string; - audit_company_name: string; - audit_report_url: string; - audit_publish_date: string; - comment?: string; -}; diff --git a/client/features/cross-chain-txs/hooks/useBridgedTokensQuery.ts b/client/features/cross-chain-txs/hooks/useBridgedTokensQuery.ts deleted file mode 100644 index 92ef350b433..00000000000 --- a/client/features/cross-chain-txs/hooks/useBridgedTokensQuery.ts +++ /dev/null @@ -1,67 +0,0 @@ -// SPDX-License-Identifier: LicenseRef-Blockscout - -import { useRouter } from 'next/router'; -import React from 'react'; - -import type { CrossChainBridgedTokensSorting, CrossChainBridgedTokensSortingField, CrossChainBridgedTokensSortingValue } from '../types/api'; - -import useDebounce from 'client/shared/hooks/useDebounce'; -import getQueryParamString from 'client/shared/router/get-query-param-string'; - -import config from 'configs/app'; -import { INTERCHAIN_BRIDGED_TOKEN_ITEM } from 'stubs/interchainIndexer'; -import { generateListStub } from 'stubs/utils'; -import type { OnValueChangeHandler } from 'toolkit/chakra/select'; -import useQueryWithPages from 'ui/shared/pagination/useQueryWithPages'; -import getSortParamsFromValue from 'ui/shared/sort/getSortParamsFromValue'; -import getSortValueFromQuery from 'ui/shared/sort/getSortValueFromQuery'; - -import { BRIDGED_TOKENS_SORT_OPTIONS } from '../utils/bridged-tokens-sort'; - -interface Props { - enabled?: boolean; -} - -export default function useBridgedTokensQuery({ enabled }: Props) { - const router = useRouter(); - - const q = getQueryParamString(router.query.q); - - const [ searchTerm, setSearchTerm ] = React.useState(q ?? ''); - const [ sort, setSort ] = React.useState( - getSortValueFromQuery(router.query, BRIDGED_TOKENS_SORT_OPTIONS) ?? 'default', - ); - - const debouncedSearchTerm = useDebounce(searchTerm, 300); - - const query = useQueryWithPages({ - resourceName: 'interchainIndexer:bridged_tokens', - pathParams: { - chainId: config.chain.id, - }, - filters: { q: debouncedSearchTerm }, - sorting: getSortParamsFromValue(sort), - options: { - enabled, - placeholderData: generateListStub<'interchainIndexer:bridged_tokens'>(INTERCHAIN_BRIDGED_TOKEN_ITEM, 10, { next_page_params: { page_token: 'token' } }), - }, - }); - - const onSearchTermChange = React.useCallback((value: string) => { - query.onFilterChange({ q: value }); - setSearchTerm(value); - }, [ query ]); - - const onSortChange: OnValueChangeHandler = React.useCallback(({ value }) => { - setSort(value[0] as CrossChainBridgedTokensSortingValue); - query.onSortingChange(value[0] === 'default' ? undefined : getSortParamsFromValue(value[0] as CrossChainBridgedTokensSortingValue)); - }, [ query ]); - - return React.useMemo(() => ({ - query, - sort, - searchTerm, - onSearchTermChange, - onSortChange, - }), [ query, sort, searchTerm, onSearchTermChange, onSortChange ]); -} diff --git a/client/features/cross-chain-txs/hooks/useTxCrossChainTransfersQuery.ts b/client/features/cross-chain-txs/hooks/useTxCrossChainTransfersQuery.ts deleted file mode 100644 index 76b7f6c3ade..00000000000 --- a/client/features/cross-chain-txs/hooks/useTxCrossChainTransfersQuery.ts +++ /dev/null @@ -1,23 +0,0 @@ -// SPDX-License-Identifier: LicenseRef-Blockscout - -import config from 'configs/app'; -import { INTERCHAIN_TRANSFER } from 'stubs/interchainIndexer'; -import { generateListStub } from 'stubs/utils'; -import useQueryWithPages from 'ui/shared/pagination/useQueryWithPages'; - -interface Props { - hash: string; - enabled?: boolean; -} - -export default function useTxCrossChainTransfersQuery({ hash, enabled = true }: Props) { - return useQueryWithPages({ - resourceName: 'interchainIndexer:tx_transfers', - pathParams: { hash }, - options: { - placeholderData: generateListStub<'interchainIndexer:tx_transfers'>(INTERCHAIN_TRANSFER, 5, { next_page_params: undefined }), - enabled: enabled && config.features.crossChainTxs.isEnabled, - staleTime: Infinity, - }, - }); -} diff --git a/client/features/cross-chain-txs/pages/tx/TxTokenTransferCrossChain.tsx b/client/features/cross-chain-txs/pages/tx/TxTokenTransferCrossChain.tsx deleted file mode 100644 index a1a09f3b840..00000000000 --- a/client/features/cross-chain-txs/pages/tx/TxTokenTransferCrossChain.tsx +++ /dev/null @@ -1,57 +0,0 @@ -// SPDX-License-Identifier: LicenseRef-Blockscout - -import { Box } from '@chakra-ui/react'; -import React from 'react'; - -import type { TxQuery } from 'client/slices/tx/hooks/useTxQuery'; - -import TokenTransfersCrossChainListItem from 'ui/crossChain/transfers/TokenTransfersCrossChainListItem'; -import TokenTransfersCrossChainTable from 'ui/crossChain/transfers/TokenTransfersCrossChainTable'; -import { getItemKey } from 'ui/crossChain/transfers/utils'; -import DataListDisplay from 'ui/shared/DataListDisplay'; -import type { QueryWithPagesResult } from 'ui/shared/pagination/useQueryWithPages'; - -interface Props { - txQuery: TxQuery; - crossChainQuery: QueryWithPagesResult<'interchainIndexer:tx_transfers'>; - isLoading?: boolean; - tableTop?: number; -} - -const TxTokenTransferCrossChain = ({ txQuery, crossChainQuery, isLoading, tableTop }: Props) => { - const content = crossChainQuery.data?.items ? ( - <> - - { crossChainQuery.data.items.map((item, index) => ( - - )) } - - - - - - ) : null; - - return ( - - { content } - - ); -}; - -export default React.memo(TxTokenTransferCrossChain); diff --git a/client/features/csv-export/types/api.ts b/client/features/csv-export/types/api.ts deleted file mode 100644 index b382b95e55a..00000000000 --- a/client/features/csv-export/types/api.ts +++ /dev/null @@ -1,18 +0,0 @@ -// SPDX-License-Identifier: LicenseRef-Blockscout - -export type CsvExportItemStatus = 'pending' | 'completed' | 'failed'; - -export interface CsvExportItemResponse { - file_id: string | null; - status: CsvExportItemStatus; - expires_at: string | null; -} - -export interface CsvExportDownloadResponse { - request_id: string; -} - -export interface CsvExportConfig { - limit: number; - async_enabled: boolean; -} diff --git a/client/features/csv-export/types/client.ts b/client/features/csv-export/types/client.ts deleted file mode 100644 index 8506619d27c..00000000000 --- a/client/features/csv-export/types/client.ts +++ /dev/null @@ -1,14 +0,0 @@ -// SPDX-License-Identifier: LicenseRef-Blockscout - -import type { CsvExportItemStatus } from './api'; - -export type CsvExportType = - 'address_txs' | - 'address_internal_txs' | - 'address_token_transfers' | - 'address_logs' | - 'token_holders' | - 'address_epoch_rewards' | - 'advanced_filters'; - -export type CsvExportDownloadStatus = CsvExportItemStatus | 'expired'; diff --git a/client/features/csv-export/utils/context.tsx b/client/features/csv-export/utils/context.tsx deleted file mode 100644 index 062ece1083c..00000000000 --- a/client/features/csv-export/utils/context.tsx +++ /dev/null @@ -1,141 +0,0 @@ -// SPDX-License-Identifier: LicenseRef-Blockscout - -import type { UseQueryResult } from '@tanstack/react-query'; -import { queryOptions, useQueries } from '@tanstack/react-query'; -import React from 'react'; - -import type { CsvExportItemResponse } from '../types/api'; - -import useApiFetch from 'client/api/hooks/useApiFetch'; -import { getResourceKey } from 'client/api/hooks/useApiQuery'; - -import getErrorObjStatusCode from 'client/shared/errors/get-error-obj-status-code'; - -import multichainConfig from 'configs/multichain'; -import dayjs from 'lib/date/dayjs'; -import type { OnOpenChangeHandler } from 'toolkit/hooks/useDisclosure'; -import { useDisclosure } from 'toolkit/hooks/useDisclosure'; -import { SECOND } from 'toolkit/utils/consts'; -import { isBrowser } from 'toolkit/utils/isBrowser'; - -import type { StorageItem } from './storage'; -import * as storage from './storage'; - -interface CsvExportContextProviderProps { - children: React.ReactNode; -} - -export interface TCsvExportContext { - dialogOpen: boolean; - onDialogOpenChange: OnOpenChangeHandler; - items: Array; - addItems: (items: Array) => void; -} - -export const CsvExportContext = React.createContext(null); - -export function CsvExportContextProvider({ children }: CsvExportContextProviderProps) { - const dialog = useDisclosure(); - const apiFetch = useApiFetch(); - - const [ items, setItems ] = React.useState>(isBrowser() ? storage.getItems() : []); - - const queriesOptions = React.useMemo(() => { - const multichain = multichainConfig(); - return items.map((item) => { - const chain = item.params.chain_id ? multichain?.chains.find(({ id }) => id === item.params.chain_id) : undefined; - - return queryOptions({ - queryKey: getResourceKey('general:csv_exports_item', { pathParams: { id: item.request_id }, chainId: chain?.id }), - queryFn: async({ signal }) => { - try { - if (item.status === 'pending') { - const response = await (apiFetch('general:csv_exports_item', { - pathParams: { id: item.request_id }, - fetchParams: { signal }, - chain, - }) as Promise); - if (response.status !== 'pending') { - const newItem = { - ...item, - status: response.status, - expires_at: response.expires_at, - file_id: response.file_id, - is_highlighted: true, - }; - storage.updateItems([ newItem ]); - return newItem; - } - } - - const isExpired = item.status !== 'expired' && item.expires_at && dayjs().isAfter(dayjs(item.expires_at)); - if (isExpired) { - const newItem = { - ...item, - status: 'expired' as const, - is_highlighted: true, - }; - storage.updateItems([ newItem ]); - return newItem; - } - } catch (error) { - const statusCode = getErrorObjStatusCode(error); - if (statusCode === 404) { - const newItem = { - ...item, - status: 'expired' as const, - is_highlighted: true, - }; - storage.updateItems([ newItem ]); - return newItem; - } - } - - return item; - }, - refetchInterval: (query) => { - const status = query.state.data?.status; - return status === 'pending' ? 10 * SECOND : false; - }, - refetchOnMount: false, - }); - }); - }, [ items, apiFetch ]); - - const combineQueriesResult = React.useCallback((results: Array>) => { - return results.map(({ data }) => data).filter(Boolean); - }, []); - - const queriesResult = useQueries({ - queries: queriesOptions, - combine: combineQueriesResult, - }); - - const addItems = React.useCallback((items: Array) => { - setItems((prev) => ([ ...items, ...prev ])); - storage.addItems(items); - }, [ ]); - - const value = React.useMemo(() => { - return { - dialogOpen: dialog.open, - onDialogOpenChange: dialog.onOpenChange, - items: queriesResult, - addItems, - }; - }, [ dialog.open, dialog.onOpenChange, queriesResult, addItems ]); - - return ( - - { children } - - ); -} - -export function useCsvExportContext() { - const context = React.useContext(CsvExportContext); - if (!context) { - throw new Error('useCsvExportContext must be used within a CsvExportContextProvider'); - } - return context; -} diff --git a/client/features/data-availability/components/SearchBarSuggestBlob.tsx b/client/features/data-availability/components/SearchBarSuggestBlob.tsx deleted file mode 100644 index 8b0b2645371..00000000000 --- a/client/features/data-availability/components/SearchBarSuggestBlob.tsx +++ /dev/null @@ -1,24 +0,0 @@ -// SPDX-License-Identifier: LicenseRef-Blockscout - -import { chakra, Flex } from '@chakra-ui/react'; -import React from 'react'; - -import type { SearchResultBlob } from 'client/features/data-availability/types/api'; -import type { ItemsProps } from 'client/slices/search/components/search-bar/SearchBarSuggest/types'; - -import * as BlobEntity from 'client/features/data-availability/components/entity/BlobEntity'; - -import HashStringShortenDynamic from 'ui/shared/HashStringShortenDynamic'; - -const SearchBarSuggestBlob = ({ data }: ItemsProps) => { - return ( - - - - - - - ); -}; - -export default React.memo(SearchBarSuggestBlob); diff --git a/client/features/data-availability/hooks/useBlockBlobTxsQuery.ts b/client/features/data-availability/hooks/useBlockBlobTxsQuery.ts deleted file mode 100644 index c55e716cf88..00000000000 --- a/client/features/data-availability/hooks/useBlockBlobTxsQuery.ts +++ /dev/null @@ -1,28 +0,0 @@ -// SPDX-License-Identifier: LicenseRef-Blockscout - -import type { BlockQuery } from 'client/slices/block/hooks/useBlockQuery'; -import { TX } from 'client/slices/tx/stubs/tx'; - -import { generateListStub } from 'stubs/utils'; -import useQueryWithPages from 'ui/shared/pagination/useQueryWithPages'; - -interface Params { - heightOrHash: string; - blockQuery: BlockQuery; - tab: string; -} - -export default function useBlockBlobTxsQuery({ heightOrHash, blockQuery, tab }: Params) { - const apiQuery = useQueryWithPages({ - resourceName: 'general:block_txs', - pathParams: { height_or_hash: heightOrHash }, - filters: { type: 'blob_transaction' }, - options: { - enabled: Boolean(tab === 'blob_txs' && !blockQuery.isPlaceholderData && blockQuery.data?.blob_transactions_count), - placeholderData: generateListStub<'general:block_txs'>(TX, 3, { next_page_params: null }), - refetchOnMount: false, - }, - }); - - return apiQuery; -} diff --git a/client/features/data-availability/mocks/block.ts b/client/features/data-availability/mocks/block.ts deleted file mode 100644 index 76d9951b27f..00000000000 --- a/client/features/data-availability/mocks/block.ts +++ /dev/null @@ -1,12 +0,0 @@ -import type { Block } from 'client/slices/block/types/api'; - -import { base } from 'client/slices/block/mocks/block'; - -export const withBlobTxs: Block = { - ...base, - blob_gas_price: '21518435987', - blob_gas_used: '393216', - burnt_blob_fees: '8461393325064192', - excess_blob_gas: '79429632', - blob_transactions_count: 1, -}; diff --git a/client/features/data-availability/mocks/search.ts b/client/features/data-availability/mocks/search.ts deleted file mode 100644 index aa50b0aac84..00000000000 --- a/client/features/data-availability/mocks/search.ts +++ /dev/null @@ -1,7 +0,0 @@ -import type { SearchResultBlob } from 'client/features/data-availability/types/api'; - -export const blob1: SearchResultBlob = { - blob_hash: '0x0108dd3e414da9f3255f7a831afa606e8dfaea93d082dfa9b15305583cbbdbbe', - type: 'blob' as const, - timestamp: null, -}; diff --git a/client/features/data-availability/mocks/tx.ts b/client/features/data-availability/mocks/tx.ts deleted file mode 100644 index 7e6bb45e626..00000000000 --- a/client/features/data-availability/mocks/tx.ts +++ /dev/null @@ -1,17 +0,0 @@ -import type { Transaction } from 'client/slices/tx/types/api'; - -import { base } from 'client/slices/tx/mocks/tx'; - -export const withBlob: Transaction = { - ...base, - blob_gas_price: '21518435987', - blob_gas_used: '131072', - blob_versioned_hashes: [ - '0x01a8c328b0370068aaaef49c107f70901cd79adcda81e3599a88855532122e09', - '0x0197fdb17195c176b23160f335daabd4b6a231aaaadd73ec567877c66a3affd1', - ], - burnt_blob_fee: '2820464441688064', - max_fee_per_blob_gas: '60000000000', - transaction_types: [ 'blob_transaction' as const ], - type: 3, -}; diff --git a/client/features/data-availability/pages/blob-details/Blob.pw.tsx b/client/features/data-availability/pages/blob-details/Blob.pw.tsx deleted file mode 100644 index 6764972a6db..00000000000 --- a/client/features/data-availability/pages/blob-details/Blob.pw.tsx +++ /dev/null @@ -1,36 +0,0 @@ -import React from 'react'; - -import * as blobsMock from 'client/features/data-availability/mocks/blobs'; - -import { test, expect } from 'playwright/lib'; -import * as pwConfig from 'playwright/utils/config'; - -import Blob from './Blob'; - -const hooksConfig = { - router: { - query: { hash: blobsMock.base1.hash }, - }, -}; - -test.beforeEach(async({ mockTextAd }) => { - await mockTextAd(); -}); - -test('base view +@mobile +@dark-mode', async({ render, mockApiResponse, page }) => { - await mockApiResponse('general:blob', blobsMock.base1, { pathParams: { hash: blobsMock.base1.hash } }); - const component = await render(, { hooksConfig }); - await expect(component).toHaveScreenshot({ - mask: [ page.locator(pwConfig.adsBannerSelector) ], - maskColor: pwConfig.maskColor, - }); -}); - -test('without data', async({ render, mockApiResponse, page }) => { - await mockApiResponse('general:blob', blobsMock.withoutData, { pathParams: { hash: blobsMock.base1.hash } }); - const component = await render(, { hooksConfig }); - await expect(component).toHaveScreenshot({ - mask: [ page.locator(pwConfig.adsBannerSelector) ], - maskColor: pwConfig.maskColor, - }); -}); diff --git a/client/features/data-availability/pages/blob-details/Blob.tsx b/client/features/data-availability/pages/blob-details/Blob.tsx deleted file mode 100644 index d6db8254c0d..00000000000 --- a/client/features/data-availability/pages/blob-details/Blob.tsx +++ /dev/null @@ -1,73 +0,0 @@ -// SPDX-License-Identifier: LicenseRef-Blockscout - -import { useRouter } from 'next/router'; -import React from 'react'; - -import useApiQuery from 'client/api/hooks/useApiQuery'; - -import BlobEntity from 'client/features/data-availability/components/entity/BlobEntity'; -import { BLOB } from 'client/features/data-availability/stubs'; - -import throwOnResourceLoadError from 'client/shared/errors/throw-on-resource-load-error'; -import getQueryParamString from 'client/shared/router/get-query-param-string'; - -import TextAd from 'ui/shared/ad/TextAd'; -import isCustomAppError from 'ui/shared/AppError/isCustomAppError'; -import DataFetchAlert from 'ui/shared/DataFetchAlert'; -import NetworkExplorers from 'ui/shared/NetworkExplorers'; -import PageTitle from 'ui/shared/Page/PageTitle'; - -import BlobInfo from './BlobInfo'; - -const BlobPageContent = () => { - const router = useRouter(); - const hash = getQueryParamString(router.query.hash); - - const { data, isPlaceholderData, isError, error } = useApiQuery('general:blob', { - pathParams: { hash }, - queryOptions: { - placeholderData: BLOB, - refetchOnMount: false, - }, - }); - - const content = (() => { - if (isError) { - if (isCustomAppError(error)) { - throwOnResourceLoadError({ resource: 'general:blob', error, isError: true }); - } - - return ; - } - - if (!data) { - return null; - } - - return ; - })(); - - const titleSecondRow = ( - <> - - - - ); - - return ( - <> - - - { content } - - ); -}; - -export default BlobPageContent; diff --git a/client/features/data-availability/pages/tx/TxBlobListItem.tsx b/client/features/data-availability/pages/tx/TxBlobListItem.tsx deleted file mode 100644 index 24909266dbe..00000000000 --- a/client/features/data-availability/pages/tx/TxBlobListItem.tsx +++ /dev/null @@ -1,43 +0,0 @@ -// SPDX-License-Identifier: LicenseRef-Blockscout - -import React from 'react'; - -import type { TxBlob } from 'client/features/data-availability/types/api'; - -import BlobDataType from 'client/features/data-availability/components/BlobDataType'; -import BlobEntity from 'client/features/data-availability/components/entity/BlobEntity'; - -import { Skeleton } from 'toolkit/chakra/skeleton'; -import ListItemMobileGrid from 'ui/shared/ListItemMobile/ListItemMobileGrid'; - -interface Props { - data: TxBlob; - isLoading?: boolean; -} - -const TxBlobListItem = ({ data, isLoading }: Props) => { - const size = data.blob_data ? data.blob_data.replace('0x', '').length / 2 : '-'; - - return ( - - Blob hash - - - - - Data type - - { data.blob_data ? : '-' } - - - Size, bytes - - - { size.toLocaleString() } - - - - ); -}; - -export default React.memo(TxBlobListItem); diff --git a/client/features/data-availability/pages/tx/TxBlobs.pw.tsx b/client/features/data-availability/pages/tx/TxBlobs.pw.tsx deleted file mode 100644 index 437a13cde82..00000000000 --- a/client/features/data-availability/pages/tx/TxBlobs.pw.tsx +++ /dev/null @@ -1,27 +0,0 @@ -import React from 'react'; - -import type { TxQuery } from 'client/slices/tx/hooks/useTxQuery'; -import * as txMock from 'client/slices/tx/mocks/tx'; - -import * as blobsMock from 'client/features/data-availability/mocks/blobs'; - -import { test, expect } from 'playwright/lib'; - -import TxBlobs from './TxBlobs'; - -const hooksConfig = { - router: { - query: { hash: txMock.base.hash }, - }, -}; - -test('base view +@mobile', async({ render, mockApiResponse }) => { - await mockApiResponse('general:tx_blobs', blobsMock.txBlobs, { pathParams: { hash: txMock.base.hash } }); - const txQuery = { - data: txMock.base, - isPlaceholderData: false, - isError: false, - } as TxQuery; - const component = await render(, { hooksConfig }); - await expect(component).toHaveScreenshot(); -}); diff --git a/client/features/data-availability/pages/tx/TxBlobs.tsx b/client/features/data-availability/pages/tx/TxBlobs.tsx deleted file mode 100644 index 85d888a923e..00000000000 --- a/client/features/data-availability/pages/tx/TxBlobs.tsx +++ /dev/null @@ -1,68 +0,0 @@ -// SPDX-License-Identifier: LicenseRef-Blockscout - -import { Box } from '@chakra-ui/react'; -import React from 'react'; - -import TxPendingAlert from 'client/slices/tx/components/TxPendingAlert'; -import TxSocketAlert from 'client/slices/tx/components/TxSocketAlert'; -import type { TxQuery } from 'client/slices/tx/hooks/useTxQuery'; - -import { TX_BLOB } from 'client/features/data-availability/stubs'; - -import { generateListStub } from 'stubs/utils'; -import ActionBar, { ACTION_BAR_HEIGHT_DESKTOP } from 'ui/shared/ActionBar'; -import DataListDisplay from 'ui/shared/DataListDisplay'; -import Pagination from 'ui/shared/pagination/Pagination'; -import useQueryWithPages from 'ui/shared/pagination/useQueryWithPages'; - -import TxBlobsList from './TxBlobsList'; -import TxBlobsTable from './TxBlobsTable'; - -interface Props { - txQuery: TxQuery; -} - -const TxBlobs = ({ txQuery }: Props) => { - const { data, isPlaceholderData, isError, pagination } = useQueryWithPages({ - resourceName: 'general:tx_blobs', - pathParams: { hash: txQuery.data?.hash }, - options: { - enabled: !txQuery.isPlaceholderData && Boolean(txQuery.data?.hash) && Boolean(txQuery.data?.status), - placeholderData: generateListStub<'general:tx_blobs'>(TX_BLOB, 3, { next_page_params: null }), - }, - }); - - if (!txQuery.isPending && !txQuery.isPlaceholderData && !txQuery.isError && !txQuery.data.status) { - return txQuery.socketStatus ? : ; - } - - const content = data ? ( - <> - - - - - - - - ) : null; - - const actionBar = pagination.isVisible ? ( - - - - ) : null; - - return ( - - { content } - - ); -}; - -export default TxBlobs; diff --git a/client/features/data-availability/pages/tx/TxBlobsList.tsx b/client/features/data-availability/pages/tx/TxBlobsList.tsx deleted file mode 100644 index e1a31b1e0f9..00000000000 --- a/client/features/data-availability/pages/tx/TxBlobsList.tsx +++ /dev/null @@ -1,24 +0,0 @@ -// SPDX-License-Identifier: LicenseRef-Blockscout - -import { Box } from '@chakra-ui/react'; -import React from 'react'; - -import type { TxBlob } from 'client/features/data-availability/types/api'; - -import TxBlobListItem from './TxBlobListItem'; - -const TxBlobsList = ({ data, isLoading }: { data: Array; isLoading?: boolean }) => { - return ( - - { data.map((item, index) => ( - - )) } - - ); -}; - -export default TxBlobsList; diff --git a/client/features/data-availability/pages/tx/TxBlobsTableItem.tsx b/client/features/data-availability/pages/tx/TxBlobsTableItem.tsx deleted file mode 100644 index 317e76591c6..00000000000 --- a/client/features/data-availability/pages/tx/TxBlobsTableItem.tsx +++ /dev/null @@ -1,38 +0,0 @@ -// SPDX-License-Identifier: LicenseRef-Blockscout - -import React from 'react'; - -import type { TxBlob } from 'client/features/data-availability/types/api'; - -import BlobDataType from 'client/features/data-availability/components/BlobDataType'; -import BlobEntity from 'client/features/data-availability/components/entity/BlobEntity'; - -import { Skeleton } from 'toolkit/chakra/skeleton'; -import { TableCell, TableRow } from 'toolkit/chakra/table'; - -interface Props { - data: TxBlob; - isLoading?: boolean; -} - -const TxBlobsTableItem = ({ data, isLoading }: Props) => { - const size = data.blob_data ? data.blob_data.replace('0x', '').length / 2 : '-'; - - return ( - - - - - - { data.blob_data ? : '-' } - - - - { size.toLocaleString() } - - - - ); -}; - -export default React.memo(TxBlobsTableItem); diff --git a/client/features/data-availability/stubs.ts b/client/features/data-availability/stubs.ts deleted file mode 100644 index 21265b74ab1..00000000000 --- a/client/features/data-availability/stubs.ts +++ /dev/null @@ -1,20 +0,0 @@ -import type { Blob, TxBlob } from 'client/features/data-availability/types/api'; - -import { TX_HASH } from 'client/slices/tx/stubs/tx'; - -const BLOB_HASH = '0x0137cd898a9aaa92bbe94999d2a98241f5eabc829d9354160061789963f85995'; -const BLOB_PROOF = '0x82683d5d6e58a76f2a607b8712cad113621d46cb86a6bcfcffb1e274a70c7308b3243c6075ee22d904fecf8d4c147c6f'; - -export const TX_BLOB: TxBlob = { - blob_data: '0x010203040506070809101112', - hash: BLOB_HASH, - kzg_commitment: BLOB_PROOF, - kzg_proof: BLOB_PROOF, -}; - -export const BLOB: Blob = { - ...TX_BLOB, - transaction_hashes: [ - { block_consensus: true, transaction_hash: TX_HASH }, - ], -}; diff --git a/client/features/data-availability/types/api.ts b/client/features/data-availability/types/api.ts deleted file mode 100644 index f1866ec6506..00000000000 --- a/client/features/data-availability/types/api.ts +++ /dev/null @@ -1,57 +0,0 @@ -// SPDX-License-Identifier: LicenseRef-Blockscout - -import type { Transaction } from 'client/slices/tx/types/api'; - -export interface TxBlob { - hash: string; - blob_data: string | null; - kzg_commitment: string | null; - kzg_proof: string | null; -} - -export type TxBlobs = { - items: Array; - next_page_params: null; -}; - -export interface Blob extends TxBlob { - transaction_hashes: Array<{ - block_consensus: boolean; - transaction_hash: string; - }>; -} - -export interface TransactionDataAvailability { - blob_versioned_hashes?: Array; - blob_gas_used?: string; - blob_gas_price?: string; - burnt_blob_fee?: string; - max_fee_per_blob_gas?: string; -} - -export interface TransactionsResponseWithBlobs { - items: Array; - next_page_params: { - block_number: number; - index: number; - items_count: number; - } | null; -} - -export type TxsWithBlobsFilters = { - type: 'blob_transaction'; -}; - -export interface SearchResultBlob { - type: 'blob'; - blob_hash: string; - timestamp: null; -} - -export interface BlockDataAvailability { - blob_gas_price?: string; - blob_gas_used?: string; - burnt_blob_fees?: string; - excess_blob_gas?: string; - blob_transactions_count?: number; -} diff --git a/client/features/fhe-operations/mocks/token-transfer.ts b/client/features/fhe-operations/mocks/token-transfer.ts deleted file mode 100644 index 531450ec34c..00000000000 --- a/client/features/fhe-operations/mocks/token-transfer.ts +++ /dev/null @@ -1,38 +0,0 @@ -import type { TokenTransfer } from 'client/slices/token-transfer/types/api'; - -import { erc7984Token } from './token'; - -export const erc7984: TokenTransfer = { - from: { - hash: '0xd789a607CEac2f0E14867de4EB15b15C9FFB5859', - implementations: null, - is_contract: true, - is_verified: true, - name: 'ArianeeStore', - private_tags: [], - public_tags: [], - watchlist_names: [], - ens_domain_name: null, - }, - to: { - hash: '0x7d20a8D54F955b4483A66aB335635ab66e151c51', - implementations: null, - is_contract: true, - is_verified: false, - name: null, - private_tags: [], - public_tags: [], - watchlist_names: [], - ens_domain_name: null, - }, - token: erc7984Token, - total: null, - transaction_hash: '0x62d597ebcf3e8d60096dd0363bc2f0f5e2df27ba1dacd696c51aa7c9409f3193', - type: 'token_transfer', - token_type: 'ERC-20', - timestamp: '2022-10-10T14:34:30.000000Z', - block_number: '12345', - block_hash: '1', - log_index: '1', - method: 'transfer', -}; diff --git a/client/features/fhe-operations/mocks/token.ts b/client/features/fhe-operations/mocks/token.ts deleted file mode 100644 index 62abdb80870..00000000000 --- a/client/features/fhe-operations/mocks/token.ts +++ /dev/null @@ -1,15 +0,0 @@ -import type { TokenInfo } from 'client/slices/token/types/api'; - -export const erc7984Token: TokenInfo = { - address_hash: '0x55d536e4d6c1993d8ef2e2a4ef77f02088419420', - circulating_market_cap: null, - decimals: '18', - exchange_rate: null, - holders_count: '1200', - name: 'Confidential USDT', - symbol: 'cUSD', - type: 'ERC-7984', - total_supply: '0', - icon_url: null, - reputation: 'ok', -}; diff --git a/client/features/fhe-operations/pages/tx/TxFheOperations.tsx b/client/features/fhe-operations/pages/tx/TxFheOperations.tsx deleted file mode 100644 index 370313e1f9a..00000000000 --- a/client/features/fhe-operations/pages/tx/TxFheOperations.tsx +++ /dev/null @@ -1,71 +0,0 @@ -// SPDX-License-Identifier: LicenseRef-Blockscout - -import { Box } from '@chakra-ui/react'; -import React from 'react'; - -import useApiQuery from 'client/api/hooks/useApiQuery'; - -import TxPendingAlert from 'client/slices/tx/components/TxPendingAlert'; -import TxSocketAlert from 'client/slices/tx/components/TxSocketAlert'; -import type { TxQuery } from 'client/slices/tx/hooks/useTxQuery'; - -import { FHE_OPERATIONS_RESPONSE } from 'stubs/fheOperations'; -import DataFetchAlert from 'ui/shared/DataFetchAlert'; -import DataListDisplay from 'ui/shared/DataListDisplay'; - -import TxFHEOperationsList from './TxFheOperationsList'; -import TxFHEOperationsStats from './TxFheOperationsStats'; -import TxFHEOperationsTable from './TxFheOperationsTable'; - -interface Props { - txQuery: TxQuery; -} - -const TxFHEOperations = ({ txQuery }: Props) => { - const hash = txQuery.data?.hash || ''; - const isEnabled = Boolean(hash) && Boolean(txQuery.data?.status) && !txQuery.isPlaceholderData; - - const { data, isError, isPlaceholderData } = useApiQuery('general:tx_fhe_operations', { - pathParams: { hash }, - queryOptions: { - enabled: isEnabled, - retry: false, - placeholderData: FHE_OPERATIONS_RESPONSE, - }, - }); - - if (!txQuery.isPending && !txQuery.isPlaceholderData && !txQuery.isError && txQuery.data && !txQuery.data.status) { - return txQuery.socketStatus ? : ; - } - - if (txQuery.isError || isError) { - return ; - } - - const content = data ? ( - <> - - - - - - - ) : null; - - return ( - - { content } - - ); -}; - -export default React.memo(TxFHEOperations); diff --git a/client/features/fhe-operations/pages/tx/TxFheOperationsList.tsx b/client/features/fhe-operations/pages/tx/TxFheOperationsList.tsx deleted file mode 100644 index 20111b62d95..00000000000 --- a/client/features/fhe-operations/pages/tx/TxFheOperationsList.tsx +++ /dev/null @@ -1,29 +0,0 @@ -// SPDX-License-Identifier: LicenseRef-Blockscout - -import { Box } from '@chakra-ui/react'; -import React from 'react'; - -import type { FheOperation } from 'types/api/fheOperations'; - -import TxFHEOperationsListItem from './TxFheOperationsListItem'; - -interface Props { - data: Array; - isLoading?: boolean; -} - -const TxFHEOperationsList = ({ data, isLoading }: Props) => { - return ( - - { data.map((op) => ( - - )) } - - ); -}; - -export default React.memo(TxFHEOperationsList); diff --git a/client/features/fhe-operations/pages/tx/TxFheOperationsListItem.tsx b/client/features/fhe-operations/pages/tx/TxFheOperationsListItem.tsx deleted file mode 100644 index 62c08ec0717..00000000000 --- a/client/features/fhe-operations/pages/tx/TxFheOperationsListItem.tsx +++ /dev/null @@ -1,74 +0,0 @@ -// SPDX-License-Identifier: LicenseRef-Blockscout - -import { Box, Flex, Grid, Text } from '@chakra-ui/react'; -import { capitalize } from 'es-toolkit'; -import React from 'react'; - -import type { FheOperation } from 'types/api/fheOperations'; - -import AddressEntity from 'client/slices/address/components/entity/AddressEntity'; - -import { Badge } from 'toolkit/chakra/badge'; -import { Skeleton } from 'toolkit/chakra/skeleton'; -import ListItemMobile from 'ui/shared/ListItemMobile/ListItemMobile'; - -import { getTypeColor } from '../../utils/utils'; - -type Props = FheOperation & { isLoading?: boolean }; - -const TxFHEOperationsListItem = (props: Props) => { - const { log_index: logIndex, operation, type, fhe_type: fheType, is_scalar: isScalar, hcu_cost: hcuCost, hcu_depth: hcuDepth, caller, isLoading } = props; - - return ( - - - - { capitalize(type) } - - - { fheType } - - - { isScalar ? 'Scalar' : 'Non-scalar' } - - - - - Index - - { logIndex } - - - Operation - - { operation } - - - HCU cost - - { hcuCost.toLocaleString() } - - - HCU depth - - { hcuDepth.toLocaleString() } - - - Caller - - { caller && caller.hash ? ( - - ) : ( - - ) } - - - - ); -}; - -export default React.memo(TxFHEOperationsListItem); diff --git a/client/features/fhe-operations/pages/tx/TxFheOperationsTableItem.tsx b/client/features/fhe-operations/pages/tx/TxFheOperationsTableItem.tsx deleted file mode 100644 index eb9ddaef531..00000000000 --- a/client/features/fhe-operations/pages/tx/TxFheOperationsTableItem.tsx +++ /dev/null @@ -1,75 +0,0 @@ -// SPDX-License-Identifier: LicenseRef-Blockscout - -import { Text } from '@chakra-ui/react'; -import { capitalize } from 'es-toolkit'; -import React from 'react'; - -import type { FheOperation } from 'types/api/fheOperations'; - -import AddressEntity from 'client/slices/address/components/entity/AddressEntity'; - -import { Badge } from 'toolkit/chakra/badge'; -import { Skeleton } from 'toolkit/chakra/skeleton'; -import { TableCell, TableRow } from 'toolkit/chakra/table'; - -import { getTypeColor } from '../../utils/utils'; - -type Props = FheOperation & { isLoading?: boolean }; - -const TxFHEOperationsTableItem = (props: Props) => { - const { log_index: logIndex, operation, type, fhe_type: fheType, is_scalar: isScalar, hcu_cost: hcuCost, hcu_depth: hcuDepth, caller, isLoading } = props; - const hcuDepthValue = hcuDepth; - - return ( - - - - { logIndex } - - - - - { operation } - - - - - { capitalize(type) } - - - - - { fheType } - - - - - { isScalar ? 'Scalar' : 'Non-scalar' } - - - - - { hcuCost.toLocaleString() } - - - - - { hcuDepthValue.toLocaleString() } - - - - { caller && caller.hash ? ( - - ) : ( - - ) } - - - ); -}; - -export default React.memo(TxFHEOperationsTableItem); diff --git a/client/features/fhe-operations/types/api.ts b/client/features/fhe-operations/types/api.ts deleted file mode 100644 index 478f0086916..00000000000 --- a/client/features/fhe-operations/types/api.ts +++ /dev/null @@ -1,5 +0,0 @@ -// SPDX-License-Identifier: LicenseRef-Blockscout - -export interface TransactionFheOperations { - fhe_operations_count?: number; -} diff --git a/client/features/fhe-operations/utils/utils.ts b/client/features/fhe-operations/utils/utils.ts deleted file mode 100644 index 3948dfd91e9..00000000000 --- a/client/features/fhe-operations/utils/utils.ts +++ /dev/null @@ -1,19 +0,0 @@ -// SPDX-License-Identifier: LicenseRef-Blockscout - -import type { FheOperationType } from 'types/api/fheOperations'; - -import type { BadgeProps } from 'toolkit/chakra/badge'; - -// Maps FHE operation types to Blockscout color palette -export function getTypeColor(type: FheOperationType): BadgeProps['colorPalette'] { - const colors: Record = { - comparison: 'purple', - control: 'orange', - arithmetic: 'blue', - bitwise: 'teal', - encryption: 'cyan', - unary: 'yellow', - random: 'pink', - }; - return colors[type] || 'gray'; -} diff --git a/client/features/hot-contracts/mocks.ts b/client/features/hot-contracts/mocks.ts deleted file mode 100644 index eb557f4658e..00000000000 --- a/client/features/hot-contracts/mocks.ts +++ /dev/null @@ -1,34 +0,0 @@ -import type { HotContractsResponse } from './types/api'; - -import { contract1, contract2 } from 'client/slices/contract/mocks/list'; - -export const hotContractsResponse: HotContractsResponse = { - items: [ - { - contract_address: { ...contract1.address, name: null, reputation: 'scam' }, - balance: '1000000000000000000', - transactions_count: '1000', - total_gas_used: '100000000', - }, - { - contract_address: { - ...contract2.address, - metadata: { - reputation: null, - tags: [ - { tagType: 'protocol', name: 'Goose', slug: 'goose', ordinal: 1, meta: null }, - ], - }, - }, - balance: '420', - transactions_count: '42', - total_gas_used: '12343566', - }, - ], - next_page_params: { - items_count: '50', - transactions_count: '50', - total_gas_used: '50', - contract_address_hash: '50', - }, -}; diff --git a/client/features/hot-contracts/pages/index/HotContracts.pw.tsx b/client/features/hot-contracts/pages/index/HotContracts.pw.tsx deleted file mode 100644 index 849b874ca52..00000000000 --- a/client/features/hot-contracts/pages/index/HotContracts.pw.tsx +++ /dev/null @@ -1,25 +0,0 @@ -import React from 'react'; - -import * as statsMock from 'client/slices/home/mocks/stats'; - -import * as hotContractsMock from 'client/features/hot-contracts/mocks'; -import { getIntervalValueFromQuery } from 'client/features/hot-contracts/utils'; - -import { test, expect } from 'playwright/lib'; - -import HotContracts from './HotContracts'; - -test('base view +@mobile', async({ render, mockTextAd, mockApiResponse, mockEnvs }) => { - test.slow(); - await mockEnvs([ [ 'NEXT_PUBLIC_VIEWS_TOKEN_SCAM_TOGGLE_ENABLED', 'true' ] ]); - await mockTextAd(); - await mockApiResponse( - 'general:stats_hot_contracts', - hotContractsMock.hotContractsResponse, - { queryParams: { scale: getIntervalValueFromQuery(undefined) } }, - ); - await mockApiResponse('general:stats', { ...statsMock.base, coin_price: '3214.42' }); - - const component = await render(); - await expect(component).toHaveScreenshot({ timeout: 10_000 }); -}); diff --git a/client/features/hot-contracts/pages/index/HotContracts.tsx b/client/features/hot-contracts/pages/index/HotContracts.tsx deleted file mode 100644 index 4e515793824..00000000000 --- a/client/features/hot-contracts/pages/index/HotContracts.tsx +++ /dev/null @@ -1,141 +0,0 @@ -// SPDX-License-Identifier: LicenseRef-Blockscout - -import { Box, createListCollection, Flex } from '@chakra-ui/react'; -import { useRouter } from 'next/router'; -import React from 'react'; - -import type { HotContractsInterval, HotContractsSorting, HotContractsSortingField, HotContractsSortingValue } from 'client/features/hot-contracts/types/api'; - -import useApiQuery from 'client/api/hooks/useApiQuery'; - -import { HOMEPAGE_STATS } from 'client/slices/home/stubs'; - -import HotContractsIntervalSelect from 'client/features/hot-contracts/pages/index/HotContractsIntervalSelect'; -import HotContractsListItem from 'client/features/hot-contracts/pages/index/HotContractsListItem'; -import HotContractsTable from 'client/features/hot-contracts/pages/index/HotContractsTable'; -import { HOT_CONTRACTS } from 'client/features/hot-contracts/stubs'; -import { getIntervalValueFromQuery, SORT_OPTIONS } from 'client/features/hot-contracts/utils'; - -import { Skeleton } from 'toolkit/chakra/skeleton'; -import ActionBar from 'ui/shared/ActionBar'; -import DataListDisplay from 'ui/shared/DataListDisplay'; -import PageTitle from 'ui/shared/Page/PageTitle'; -import Pagination from 'ui/shared/pagination/Pagination'; -import useQueryWithPages from 'ui/shared/pagination/useQueryWithPages'; -import getSortParamsFromValue from 'ui/shared/sort/getSortParamsFromValue'; -import getSortValueFromQuery from 'ui/shared/sort/getSortValueFromQuery'; -import Sort from 'ui/shared/sort/Sort'; - -const sortCollection = createListCollection({ - items: SORT_OPTIONS, -}); - -const HotContracts = () => { - const router = useRouter(); - const [ interval, setInterval ] = React.useState(getIntervalValueFromQuery(router.query.scale)); - const [ sort, setSort ] = - React.useState(getSortValueFromQuery(router.query, SORT_OPTIONS) ?? 'default'); - - const { data, isError, isPlaceholderData, pagination, onSortingChange, onFilterChange } = useQueryWithPages({ - resourceName: 'general:stats_hot_contracts', - filters: { scale: interval }, - sorting: getSortParamsFromValue(sort), - options: { - placeholderData: { - items: Array(50).fill(HOT_CONTRACTS), - next_page_params: { items_count: '50', transactions_count: '50', total_gas_used: '50', contract_address_hash: '50' }, - }, - }, - }); - - const statsQuery = useApiQuery('general:stats', { - queryOptions: { - placeholderData: HOMEPAGE_STATS, - refetchOnMount: false, - }, - }); - - const handleSortChange = React.useCallback(({ value }: { value: Array }) => { - setSort(value[0] as HotContractsSortingValue); - onSortingChange(value[0] === 'default' ? undefined : getSortParamsFromValue(value[0] as HotContractsSortingValue)); - }, [ onSortingChange ]); - - const handleIntervalChange = React.useCallback((newInterval: HotContractsInterval) => { - setInterval(newInterval); - onFilterChange({ scale: newInterval }); - }, [ onFilterChange ]); - - const content = ( - <> - - { data?.items.map((item, index) => ( - - )) } - - - - - - ); - - const actionBar = ( - - - - { [ '1d', '7d', '30d' ].includes(interval) && ( - - The data is updated once a day. - - ) } - - - - - ); - - return ( - <> - - - { content } - - - ); -}; - -export default HotContracts; diff --git a/client/features/hot-contracts/pages/index/HotContractsTableItem.tsx b/client/features/hot-contracts/pages/index/HotContractsTableItem.tsx deleted file mode 100644 index 5046bbb9772..00000000000 --- a/client/features/hot-contracts/pages/index/HotContractsTableItem.tsx +++ /dev/null @@ -1,68 +0,0 @@ -// SPDX-License-Identifier: LicenseRef-Blockscout - -import { HStack } from '@chakra-ui/react'; -import { BigNumber } from 'bignumber.js'; -import React from 'react'; - -import type { HotContract } from 'client/features/hot-contracts/types/api'; - -import AddressEntity from 'client/slices/address/components/entity/AddressEntity'; -import { Reputation } from 'client/slices/token/components/entity/TokenEntity'; - -import { TableCell, TableRow } from 'toolkit/chakra/table'; -import { TruncatedText } from 'toolkit/components/truncation/TruncatedText'; -import EntityTags from 'ui/shared/EntityTags/EntityTags'; -import NativeCoinValue from 'ui/shared/value/NativeCoinValue'; - -interface Props { - isLoading?: boolean; - data: HotContract; - exchangeRate: string | null; -}; - -const HotContractsTableItem = ({ - isLoading, - data, - exchangeRate, -}: Props) => { - const protocolTags = data?.contract_address?.metadata?.tags?.filter(tag => tag.tagType === 'protocol'); - - return ( - - - - - - - { protocolTags && protocolTags.length > 0 && ( - - ) } - - - - - - - - - - - - ); -}; - -export default HotContractsTableItem; diff --git a/client/features/hot-contracts/stubs.ts b/client/features/hot-contracts/stubs.ts deleted file mode 100644 index 1f88404a97d..00000000000 --- a/client/features/hot-contracts/stubs.ts +++ /dev/null @@ -1,10 +0,0 @@ -import type { HotContract } from './types/api'; - -import { VERIFIED_CONTRACT_INFO } from 'client/slices/contract/stubs'; - -export const HOT_CONTRACTS: HotContract = { - contract_address: VERIFIED_CONTRACT_INFO.address, - balance: '1000000000000000000', - transactions_count: '1000', - total_gas_used: '100000000', -}; diff --git a/client/features/hot-contracts/types/api.ts b/client/features/hot-contracts/types/api.ts deleted file mode 100644 index eca3f27e12c..00000000000 --- a/client/features/hot-contracts/types/api.ts +++ /dev/null @@ -1,35 +0,0 @@ -// SPDX-License-Identifier: LicenseRef-Blockscout - -import type { AddressParam } from 'client/slices/address/types/api'; - -export interface HotContract { - contract_address: AddressParam; - balance: string; - transactions_count: string; - total_gas_used: string; -} - -export interface HotContractsResponse { - items: Array; - next_page_params: { - items_count: string; - transactions_count: string; - total_gas_used: string; - contract_address_hash: string; - } | null; -} - -export interface HotContractsFilters { - scale?: HotContractsInterval; -} - -export interface HotContractsSorting { - sort: 'transactions_count' | 'total_gas_used'; - order: 'asc' | 'desc'; -} - -export type HotContractsSortingField = HotContractsSorting['sort']; - -export type HotContractsSortingValue = `${ HotContractsSortingField }-${ HotContractsSorting['order'] }` | 'default'; - -export type HotContractsInterval = '5m' | '1h' | '3h' | '1d' | '7d' | '30d'; diff --git a/client/features/hot-contracts/utils.ts b/client/features/hot-contracts/utils.ts deleted file mode 100644 index df9e3de08a3..00000000000 --- a/client/features/hot-contracts/utils.ts +++ /dev/null @@ -1,41 +0,0 @@ -// SPDX-License-Identifier: LicenseRef-Blockscout - -import type { HotContractsSortingValue, HotContractsSortingField, HotContractsInterval } from './types/api'; - -import getQueryParamString from 'client/shared/router/get-query-param-string'; - -import type { SelectOption } from 'toolkit/chakra/select'; - -export const SORT_OPTIONS: Array> = [ - { label: 'Default', value: 'default' }, - { label: 'Txs count descending', value: 'transactions_count-desc' }, - { label: 'Txs count ascending', value: 'transactions_count-asc' }, - { label: 'Gas used descending', value: 'total_gas_used-desc' }, - { label: 'Gas used ascending', value: 'total_gas_used-asc' }, -]; - -export const SORT_SEQUENCE: Record> = { - transactions_count: [ 'transactions_count-desc', 'transactions_count-asc', 'default' ], - total_gas_used: [ 'total_gas_used-desc', 'total_gas_used-asc', 'default' ], -}; - -export const INTERVAL_ITEMS: Array<{ id: HotContractsInterval; labelShort: string; labelFull: string }> = [ - { id: '5m', labelShort: '5m', labelFull: '5 minutes' }, - { id: '1h', labelShort: '1h', labelFull: '1 hour' }, - { id: '3h', labelShort: '3h', labelFull: '3 hours' }, - { id: '1d', labelShort: '1D', labelFull: '1 day' }, - { id: '7d', labelShort: '1W', labelFull: '1 week' }, - { id: '30d', labelShort: '1M', labelFull: '1 month' }, -]; - -export const getIntervalValueFromQuery = (query: string | Array | undefined): HotContractsInterval => { - const queryString = getQueryParamString(query); - if (queryString) { - const interval = INTERVAL_ITEMS.find(item => item.id === queryString); - if (interval) { - return interval.id; - } - } - - return '1d'; -}; diff --git a/client/features/marketplace/components/SearchBarSuggestApp.tsx b/client/features/marketplace/components/SearchBarSuggestApp.tsx deleted file mode 100644 index e63c123e6a5..00000000000 --- a/client/features/marketplace/components/SearchBarSuggestApp.tsx +++ /dev/null @@ -1,109 +0,0 @@ -// SPDX-License-Identifier: LicenseRef-Blockscout - -import { Flex, Text } from '@chakra-ui/react'; -import { useRouter } from 'next/router'; -import React from 'react'; - -import type { MarketplaceApp } from 'types/client/marketplace'; - -import { route } from 'nextjs-routes'; - -import SearchBarSuggestItemLink from 'client/slices/search/components/search-bar/SearchBarSuggest/SearchBarSuggestItemLink'; - -import highlightText from 'client/shared/text/highlight-text'; - -import { useColorModeValue } from 'toolkit/chakra/color-mode'; -import { Image } from 'toolkit/chakra/image'; -import IconSvg from 'ui/shared/IconSvg'; - -interface Props { - data: MarketplaceApp; - isMobile: boolean | undefined; - searchTerm: string; - onClick: (event: React.MouseEvent) => void; -} - -const SearchBarSuggestApp = ({ data, isMobile, searchTerm, onClick }: Props) => { - const router = useRouter(); - const logo = ( - { - ); - - const content = (() => { - if (isMobile) { - return ( - <> - - { logo } - - - - { data.external && } - - - { data.description } - - - ); - } - return ( - - { logo } - - - - - { data.description } - - { data.external && ( - - ) } - - ); - })(); - - return ( - - { content } - - ); -}; - -export default React.memo(SearchBarSuggestApp); diff --git a/client/features/multichain/hooks/useSearchMultichain.ts b/client/features/multichain/hooks/useSearchMultichain.ts deleted file mode 100644 index a86b345cc61..00000000000 --- a/client/features/multichain/hooks/useSearchMultichain.ts +++ /dev/null @@ -1,102 +0,0 @@ -// SPDX-License-Identifier: LicenseRef-Blockscout - -import type * as bens from '@blockscout/bens-types'; -import type { QuickSearchResultItem } from 'client/slices/search/types/client'; -import type { QuickSearchResultBlock, QuickSearchResultToken } from 'types/client/multichainAggregator'; - -import useApiQuery from 'client/api/hooks/useApiQuery'; - -interface Props { - searchTerm: string; - enabled: boolean; -} - -export default function useSearchMultichain({ searchTerm, enabled }: Props) { - return useApiQuery<'multichainAggregator:quick_search', unknown, Array>('multichainAggregator:quick_search', { - queryParams: { q: searchTerm, unlimited_per_chain: true }, - queryOptions: { - enabled: searchTerm.trim().length > 0 && enabled, - select: (data) => { - const result: Array = []; - - if (data.block_numbers && data.block_numbers.length > 0) { - const items: Array = data.block_numbers.map((item) => ({ - type: 'block' as const, - block_number: item.block_number.toString(), - block_hash: undefined, - chain_id: item.chain_id.toString(), - })); - result.push(...items); - } - - if (data.blocks && data.blocks.length > 0) { - const items: Array = data.blocks.map((item) => ({ - type: 'block' as const, - block_number: undefined, - block_hash: item.hash, - chain_id: item.chain_id.toString(), - })); - result.push(...items); - } - - if (data.transactions && data.transactions.length > 0) { - const items: Array = data.transactions.map((item) => ({ - type: 'transaction' as const, - transaction_hash: item.hash, - chain_id: item.chain_id, - })); - result.push(...items); - } - - if (data.addresses && data.addresses.length > 0) { - const items: Array = data.addresses.map((item) => ({ - type: 'address' as const, - address_hash: item.hash, - chain_infos: item.chain_infos, - })); - result.push(...items); - } - - const tokens = data.tokens.concat(data.nfts); - - if (tokens && tokens.length > 0) { - const items: Array = tokens.map((item) => ({ - type: 'token' as const, - token_type: item.type as unknown as QuickSearchResultToken['token_type'], - address_hash: item.address_hash, - name: item.name ?? 'Unnamed token', - symbol: item.symbol ?? '', - icon_url: item.icon_url ?? null, - is_smart_contract_verified: false, - is_smart_contract_address: false, - // As of now, there can be only one chain in the object - chain_id: Object.keys(item.chain_infos)[0], - reputation: null, - total_supply: item.total_supply ?? null, - exchange_rate: item.exchange_rate ?? null, - chain_infos: item.chain_infos, - })); - result.push(...items); - } - - if (data.domains && data.domains.length > 0) { - const items: Array = data.domains - .map((item) => (item.address ? { - type: 'ens_domain' as const, - ens_info: { - address_hash: item.address, - expiry_date: item.expiry_date, - name: item.name, - protocol: item.protocol as bens.ProtocolInfo, - }, - address_hash: item.address, - } : undefined)) - .filter((item) => item !== undefined); - result.push(...items); - } - - return result; - }, - }, - }); -} diff --git a/client/features/multichain/mocks/search.ts b/client/features/multichain/mocks/search.ts deleted file mode 100644 index 96b351d88aa..00000000000 --- a/client/features/multichain/mocks/search.ts +++ /dev/null @@ -1,58 +0,0 @@ -import type * as multichain from '@blockscout/multichain-aggregator-types'; - -import { chainA } from 'mocks/multichain/chains'; - -export const searchAddressesA: multichain.GetAddressResponse = { - hash: '0x0000000002C5fE54822a1eD058AE2F937Fd42769', - chain_infos: { - [chainA.id]: { - coin_balance: '0', - is_contract: false, - is_verified: false, - contract_name: undefined, - }, - }, - has_tokens: false, - has_interop_message_transfers: false, - coin_balance: '0', - exchange_rate: '123.456', - domains: [], -}; - -export const searchAddressesB: multichain.GetAddressResponse = { - hash: '0x00883b68A6EcF2ea3D47BD735E5125a0B7625B53', - chain_infos: { - [chainA.id]: { - coin_balance: '0', - is_contract: true, - is_verified: true, - contract_name: 'USDT', - }, - }, - has_tokens: false, - has_interop_message_transfers: false, - coin_balance: '0', - exchange_rate: '123.456', - domains: [], -}; - -export const searchTokenA: multichain.AggregatedTokenInfo = { - address_hash: '0x94b008aA00579c1307B0EF2c499aD98a8ce58e58', - circulating_market_cap: '162513452583.22', - decimals: '6', - holders_count: '1148927', - icon_url: undefined, - name: 'Tether USD', - symbol: 'USDT', - total_supply: '1000000', - type: 'ERC-20' as multichain.TokenType, - exchange_rate: '1.00', - chain_infos: { - [chainA.id]: { - holders_count: '1148927', - total_supply: '1000000', - is_verified: false, - contract_name: undefined, - }, - }, -}; diff --git a/client/features/multichain/pages/search-results/SearchResultListItem.tsx b/client/features/multichain/pages/search-results/SearchResultListItem.tsx deleted file mode 100644 index 18df6841a17..00000000000 --- a/client/features/multichain/pages/search-results/SearchResultListItem.tsx +++ /dev/null @@ -1,41 +0,0 @@ -// SPDX-License-Identifier: LicenseRef-Blockscout - -import React from 'react'; - -import type { LinkProps } from 'toolkit/chakra/link'; -import { Link } from 'toolkit/chakra/link'; - -interface Props extends LinkProps { - children: React.ReactNode; -} - -const SearchResultListItem = ({ children, ...rest }: Props) => { - return ( - - { children } - - ); -}; - -export default React.memo(SearchResultListItem); diff --git a/client/features/multichain/pages/search-results/SearchResults.pw.tsx b/client/features/multichain/pages/search-results/SearchResults.pw.tsx deleted file mode 100644 index 8eb5bb4221d..00000000000 --- a/client/features/multichain/pages/search-results/SearchResults.pw.tsx +++ /dev/null @@ -1,75 +0,0 @@ -import React from 'react'; - -import type { GetAddressResponse } from '@blockscout/multichain-aggregator-types'; - -import * as searchMock from 'client/features/multichain/mocks/search'; - -import * as chainDataMock from 'mocks/multichain/chains'; -import { ENVS_MAP } from 'playwright/fixtures/mockEnvs'; -import { test, expect } from 'playwright/lib'; -import * as pwConfig from 'playwright/utils/config'; - -import SearchResults from './SearchResults'; - -const SEARCH_TERM = 'usd'; - -const hooksConfig = { - router: { - query: { q: SEARCH_TERM }, - }, -}; - -test.beforeEach(async({ mockApiResponse, mockMultichainConfig, mockEnvs, mockAssetResponse, mockTextAd }) => { - - await mockMultichainConfig(); - await mockEnvs(ENVS_MAP.multichain); - await mockTextAd(); - - await mockApiResponse('multichainAggregator:search_addresses', { - items: [ - searchMock.searchAddressesA, - ...Array(10).fill('').map((_, index) => ({ - ...searchMock.searchAddressesB, - hash: `0x00883b68A6EcF2ea3D47BD735E5125a0B7625B5${ index }`, - })) as Array, - ], - next_page_params: undefined, - }, { queryParams: { q: SEARCH_TERM } }); - await mockApiResponse('multichainAggregator:search_tokens', { - items: [ searchMock.searchTokenA ], - next_page_params: undefined, - }, { queryParams: { q: SEARCH_TERM } }); - await mockApiResponse('multichainAggregator:search_blocks', { items: [ ], next_page_params: undefined }, { queryParams: { q: SEARCH_TERM } }); - await mockApiResponse('multichainAggregator:search_block_numbers', { items: [ ], next_page_params: undefined }, { queryParams: { q: SEARCH_TERM } }); - await mockApiResponse('multichainAggregator:search_transactions', { items: [ ], next_page_params: undefined }, { queryParams: { q: SEARCH_TERM } }); - await mockApiResponse('multichainAggregator:search_nfts', { items: [ ], next_page_params: undefined }, { queryParams: { q: SEARCH_TERM } }); - await mockApiResponse('multichainAggregator:search_domains', { items: [ ], next_page_params: undefined }, { queryParams: { q: SEARCH_TERM } }); - - await mockAssetResponse(chainDataMock.chainA.logo as string, './playwright/mocks/image_s.jpg'); -}); - -test.describe('desktop', () => { - test.use({ viewport: pwConfig.viewport.xl }); - - test('base view', async({ render }) => { - const component = await render( - , - { hooksConfig }, - ); - - await expect(component).toHaveScreenshot(); - }); -}); - -test.describe('mobile', () => { - test.use({ viewport: pwConfig.viewport.mobile }); - - test('base view', async({ render }) => { - const component = await render( - , - { hooksConfig }, - ); - - await expect(component).toHaveScreenshot(); - }); -}); diff --git a/client/features/multichain/pages/search-results/SearchResults.tsx b/client/features/multichain/pages/search-results/SearchResults.tsx deleted file mode 100644 index 184a206afd7..00000000000 --- a/client/features/multichain/pages/search-results/SearchResults.tsx +++ /dev/null @@ -1,167 +0,0 @@ -// SPDX-License-Identifier: LicenseRef-Blockscout - -import { chakra } from '@chakra-ui/react'; -import React from 'react'; - -import SearchResultsInput from 'client/slices/search/pages/search-results/SearchResultsInput'; - -import useIsMobile from 'client/shared/hooks/useIsMobile'; - -import useRoutedChainSelect from 'lib/multichain/useRoutedChainSelect'; -import { Skeleton } from 'toolkit/chakra/skeleton'; -import { ContentLoader } from 'toolkit/components/loaders/ContentLoader'; -import RoutedTabs from 'toolkit/components/RoutedTabs/RoutedTabs'; -import ChainSelect from 'ui/multichain/components/ChainSelect'; -import AppErrorBoundary from 'ui/shared/AppError/AppErrorBoundary'; -import * as Layout from 'ui/shared/layout/components'; -import PageTitle from 'ui/shared/Page/PageTitle'; -import HeaderAlert from 'ui/snippets/header/HeaderAlert'; -import HeaderDesktop from 'ui/snippets/header/HeaderDesktop'; -import HeaderMobile from 'ui/snippets/header/HeaderMobile'; - -import SearchResultTabContent from './SearchResultTabContent'; -import useSearchQuery from './useSearchQuery'; -import useSearchRedirect from './useSearchRedirect'; -import type { QueryType } from './utils'; -import { SEARCH_TABS_IDS, SEARCH_TABS_NAMES } from './utils'; - -const TAB_LIST_PROPS = { - marginBottom: 0, - pt: 6, - pb: 6, - marginTop: -6, - minW: { base: 'auto', lg: '1080px' }, -}; -const PRESERVED_PARAMS = [ 'q', 'tab', 'chain_id' ]; - -const SearchResults = () => { - const isMobile = useIsMobile(); - - const chainSelect = useRoutedChainSelect({ - withAllOption: true, - persistedParams: [ 'q', 'tab' ], - }); - const chainId = chainSelect.value?.[0]; - const { searchTerm, debouncedSearchTerm, handleSearchTermChange, handleSubmit, queries, checkRedirectQuery } = useSearchQuery({ - chainId: chainId === 'all' ? undefined : chainId, - }); - const showContent = useSearchRedirect({ checkRedirectQuery, hasSearchTerm: debouncedSearchTerm.trim().length > 0 }); - - const isLoading = Object.values(queries).some((query) => query.isPending); - - const handleNavigateToResults = React.useCallback((searchTerm: string) => { - handleSearchTermChange(searchTerm); - }, [ handleSearchTermChange ]); - - const renderSearchBar = React.useCallback(() => { - return ( - - ); - }, [ handleSearchTermChange, handleSubmit, searchTerm ]); - - const totalResults = (() => { - if (isLoading) { - return; - } - - const isOverflow = Object.values(queries).some((query) => query.data?.pages?.some((page) => page.next_page_params)); - - const num = Object.values(queries).reduce((acc, query) => { - return acc + (query.data?.pages[0]?.items.length ?? 0); - }, 0); - - return { num, isOverflow }; - })(); - - const chainSelectElement = ( - - ); - - const detailedTabs = Object.entries(SEARCH_TABS_IDS).map(([ key, value ]) => { - const queryType = key as QueryType; - - return { - id: value, - title: SEARCH_TABS_NAMES[queryType], - component: ( - - ), - }; - }); - - const tabs = [ - { - id: 'all', - title: 'All', - component: ( - - ), - }, - ...detailedTabs, - ]; - - const pageContent = !showContent ? - : - ( - <> - - - Found { totalResults?.num }{ totalResults?.isOverflow ? '+' : '' } matching results - - - - ); - - return ( - <> - - - - - - - - - { pageContent } - - - - - - - ); -}; - -export default React.memo(SearchResults); diff --git a/client/features/multichain/pages/search-results/__screenshots__/SearchResults.pw.tsx_default_desktop-base-view-1.png b/client/features/multichain/pages/search-results/__screenshots__/SearchResults.pw.tsx_default_desktop-base-view-1.png deleted file mode 100644 index 65a26589590..00000000000 Binary files a/client/features/multichain/pages/search-results/__screenshots__/SearchResults.pw.tsx_default_desktop-base-view-1.png and /dev/null differ diff --git a/client/features/multichain/pages/search-results/__screenshots__/SearchResults.pw.tsx_default_mobile-base-view-1.png b/client/features/multichain/pages/search-results/__screenshots__/SearchResults.pw.tsx_default_mobile-base-view-1.png deleted file mode 100644 index ec39943f14a..00000000000 Binary files a/client/features/multichain/pages/search-results/__screenshots__/SearchResults.pw.tsx_default_mobile-base-view-1.png and /dev/null differ diff --git a/client/features/multichain/pages/search-results/utils.ts b/client/features/multichain/pages/search-results/utils.ts deleted file mode 100644 index 39cd83fd15b..00000000000 --- a/client/features/multichain/pages/search-results/utils.ts +++ /dev/null @@ -1,35 +0,0 @@ -// SPDX-License-Identifier: LicenseRef-Blockscout - -import type { ReturnType } from 'client/api/hooks/useApiInfiniteQuery'; - -export interface SearchQueries { - addresses: ReturnType<'multichainAggregator:search_addresses'>; - tokens: ReturnType<'multichainAggregator:search_tokens'>; - blockNumbers: ReturnType<'multichainAggregator:search_block_numbers'>; - blocks: ReturnType<'multichainAggregator:search_blocks'>; - transactions: ReturnType<'multichainAggregator:search_transactions'>; - nfts: ReturnType<'multichainAggregator:search_nfts'>; - domains: ReturnType<'multichainAggregator:search_domains'>; -} - -export type QueryType = keyof SearchQueries; - -export const SEARCH_TABS_NAMES: Record = { - tokens: 'Tokens (ERC-20)', - nfts: 'NFTs (ERC-721 & 1155)', - addresses: 'Addresses', - blockNumbers: 'Block numbers', - blocks: 'Blocks', - transactions: 'Transactions', - domains: 'Names', -}; - -export const SEARCH_TABS_IDS: Record = { - addresses: 'addresses', - tokens: 'tokens', - blockNumbers: 'block_numbers', - blocks: 'blocks', - transactions: 'transactions', - nfts: 'nfts', - domains: 'names', -}; diff --git a/client/features/name-services/clusters/hooks/useSearchWithClusters.spec.tsx b/client/features/name-services/clusters/hooks/useSearchWithClusters.spec.tsx deleted file mode 100644 index a12922012bc..00000000000 --- a/client/features/name-services/clusters/hooks/useSearchWithClusters.spec.tsx +++ /dev/null @@ -1,643 +0,0 @@ -// @vitest-environment jsdom - -import type { UseQueryResult } from '@tanstack/react-query'; -import { useQuery } from '@tanstack/react-query'; - -import useApiFetch from 'client/api/hooks/useApiFetch'; - -import useQuickSearchQuery from 'client/slices/search/hooks/useQuickSearchQuery'; - -import type { Mock } from 'vitest'; -import { describe, it, expect, beforeEach, vi } from 'vitest'; -import { renderHook } from 'vitest/lib'; - -import useSearchWithClusters from './useSearchWithClusters'; - -vi.mock('@tanstack/react-query', () => ({ - useQuery: vi.fn(), -})); - -const mockUseQuery = useQuery as Mock; - -type MockQuickSearchQuery = ReturnType; -type MockApiQuery = ReturnType; - -vi.mock('client/api/hooks/useApiFetch', () => ({ - 'default': vi.fn(), -})); -const mockUseApiFetch = useApiFetch as Mock; -vi.mock('client/slices/search/hooks/useQuickSearchQuery'); -vi.mock('client/shared/hooks/useDebounce', () => ({ - 'default': (value: unknown) => value, -})); - -const mockUseQuickSearchQuery = useQuickSearchQuery as Mock; - -const defaultUseQueryResult: Partial = { - data: [], - isError: false, - isLoading: false, - isFetching: false, - error: null, - isPending: false, - isLoadingError: false, - isRefetchError: false, - isSuccess: true, - isStale: false, - status: 'success', - fetchStatus: 'idle', - refetch: vi.fn(), - failureCount: 0, - failureReason: null, - errorUpdateCount: 0, - isFetched: true, - isFetchedAfterMount: true, - isPlaceholderData: false, - isRefetching: false, - isInitialLoading: false, - dataUpdatedAt: Date.now(), - errorUpdatedAt: 0, - isPaused: false, -}; - -vi.mock('configs/app', () => { - return { - 'default': { - UI: { - colorTheme: {}, - homepage: {}, - fonts: {}, - }, - features: { - nameServices: { - isEnabled: true, - ens: { isEnabled: true }, - clusters: { isEnabled: true }, - }, - }, - }, - }; -}); - -describe('useSearchWithClusters', () => { - beforeEach(() => { - vi.clearAllMocks(); - - mockUseQuickSearchQuery.mockReturnValue({ - searchTerm: '', - debouncedSearchTerm: '', - handleSearchTermChange: vi.fn(), - query: { - data: [], - isError: false, - isLoading: false, - }, - redirectCheckQuery: { - data: null, - isError: false, - isLoading: false, - }, - } as unknown as MockQuickSearchQuery); - - mockUseApiFetch.mockReturnValue({ - data: null, - isError: false, - isLoading: false, - } as unknown as MockApiQuery); - - mockUseQuery.mockReturnValue({ - ...defaultUseQueryResult, - } as UseQueryResult); - }); - - describe('cluster search pattern matching', () => { - it('should detect cluster search with trailing slash', () => { - mockUseQuickSearchQuery.mockReturnValue({ - searchTerm: 'test-cluster/', - debouncedSearchTerm: 'test-cluster/', - handleSearchTermChange: vi.fn(), - query: { data: [], isError: false, isLoading: false }, - redirectCheckQuery: { data: null, isError: false, isLoading: false }, - } as unknown as MockQuickSearchQuery); - - renderHook(() => useSearchWithClusters()); - - expect(mockUseQuery).toHaveBeenCalledWith({ - queryKey: [ 'clusters:get_cluster_by_name', { input: 'test-cluster' } ], - queryFn: expect.any(Function), - enabled: true, - select: expect.any(Function), - }); - }); - - it('should detect cluster search with slash in middle (no trailing slash)', () => { - mockUseQuickSearchQuery.mockReturnValue({ - searchTerm: 'campnetwork/lol', - debouncedSearchTerm: 'campnetwork/lol', - handleSearchTermChange: vi.fn(), - query: { data: [], isError: false, isLoading: false }, - redirectCheckQuery: { data: null, isError: false, isLoading: false }, - } as unknown as MockQuickSearchQuery); - - renderHook(() => useSearchWithClusters()); - - expect(mockUseQuery).toHaveBeenCalledWith({ - queryKey: [ 'clusters:get_cluster_by_name', { input: 'campnetwork/lol' } ], - queryFn: expect.any(Function), - enabled: true, - select: expect.any(Function), - }); - }); - - it('should not detect cluster search without any slash', () => { - mockUseQuickSearchQuery.mockReturnValue({ - searchTerm: 'test-cluster', - debouncedSearchTerm: 'test-cluster', - handleSearchTermChange: vi.fn(), - query: { data: [], isError: false, isLoading: false }, - redirectCheckQuery: { data: null, isError: false, isLoading: false }, - } as unknown as MockQuickSearchQuery); - - renderHook(() => useSearchWithClusters()); - - expect(mockUseQuery).toHaveBeenCalledWith({ - queryKey: [ 'clusters:get_cluster_by_name', { input: '' } ], - queryFn: expect.any(Function), - enabled: false, - select: expect.any(Function), - }); - }); - - it('should handle cluster search with whitespace and trailing slash', () => { - mockUseQuickSearchQuery.mockReturnValue({ - searchTerm: ' my-cluster/ ', - debouncedSearchTerm: ' my-cluster/ ', - handleSearchTermChange: vi.fn(), - query: { data: [], isError: false, isLoading: false }, - redirectCheckQuery: { data: null, isError: false, isLoading: false }, - } as unknown as MockQuickSearchQuery); - - renderHook(() => useSearchWithClusters()); - - expect(mockUseQuery).toHaveBeenCalledWith({ - queryKey: [ 'clusters:get_cluster_by_name', { input: 'my-cluster' } ], - queryFn: expect.any(Function), - enabled: true, - select: expect.any(Function), - }); - }); - - it('should handle complex cluster names with hyphens and numbers', () => { - mockUseQuickSearchQuery.mockReturnValue({ - searchTerm: 'test-cluster-123/', - debouncedSearchTerm: 'test-cluster-123/', - handleSearchTermChange: vi.fn(), - query: { data: [], isError: false, isLoading: false }, - redirectCheckQuery: { data: null, isError: false, isLoading: false }, - } as unknown as MockQuickSearchQuery); - - renderHook(() => useSearchWithClusters()); - - expect(mockUseQuery).toHaveBeenCalledWith({ - queryKey: [ 'clusters:get_cluster_by_name', { input: 'test-cluster-123' } ], - queryFn: expect.any(Function), - enabled: true, - select: expect.any(Function), - }); - }); - - it('should extract cluster name correctly from various formats', () => { - const testCases = [ - { input: 'simple/', expected: 'simple' }, - { input: 'cluster-name/', expected: 'cluster-name' }, - { input: 'my_cluster_123/', expected: 'my_cluster_123' }, - { input: 'ClusterWithCaps/', expected: 'ClusterWithCaps' }, - { input: 'campnetwork/lol', expected: 'campnetwork/lol' }, - { input: 'path/to/cluster/', expected: 'path/to/cluster' }, - { input: ' spaced/cluster/ ', expected: 'spaced/cluster' }, - ]; - - testCases.forEach(({ input, expected }) => { - mockUseQuickSearchQuery.mockReturnValue({ - searchTerm: input, - debouncedSearchTerm: input, - handleSearchTermChange: vi.fn(), - query: { data: [], isError: false, isLoading: false }, - redirectCheckQuery: { data: null, isError: false, isLoading: false }, - } as unknown as MockQuickSearchQuery); - - renderHook(() => useSearchWithClusters()); - - expect(mockUseQuery).toHaveBeenCalledWith({ - queryKey: [ 'clusters:get_cluster_by_name', { input: expected } ], - queryFn: expect.any(Function), - enabled: true, - select: expect.any(Function), - }); - - vi.clearAllMocks(); - }); - }); - - it('should detect cluster search with multiple slashes', () => { - mockUseQuickSearchQuery.mockReturnValue({ - searchTerm: 'org/team/project', - debouncedSearchTerm: 'org/team/project', - handleSearchTermChange: vi.fn(), - query: { data: [], isError: false, isLoading: false }, - redirectCheckQuery: { data: null, isError: false, isLoading: false }, - } as unknown as MockQuickSearchQuery); - - renderHook(() => useSearchWithClusters()); - - expect(mockUseQuery).toHaveBeenCalledWith({ - queryKey: [ 'clusters:get_cluster_by_name', { input: 'org/team/project' } ], - queryFn: expect.any(Function), - enabled: true, - select: expect.any(Function), - }); - }); - - it('should handle the reported issue: campnetwork/lol with and without trailing slash', () => { - mockUseQuickSearchQuery.mockReturnValue({ - searchTerm: 'campnetwork/lol/', - debouncedSearchTerm: 'campnetwork/lol/', - handleSearchTermChange: vi.fn(), - query: { data: [], isError: false, isLoading: false }, - redirectCheckQuery: { data: null, isError: false, isLoading: false }, - } as unknown as MockQuickSearchQuery); - - renderHook(() => useSearchWithClusters()); - - expect(mockUseQuery).toHaveBeenCalledWith({ - queryKey: [ 'clusters:get_cluster_by_name', { input: 'campnetwork/lol' } ], - queryFn: expect.any(Function), - enabled: true, - select: expect.any(Function), - }); - - vi.clearAllMocks(); - - mockUseQuickSearchQuery.mockReturnValue({ - searchTerm: 'campnetwork/lol', - debouncedSearchTerm: 'campnetwork/lol', - handleSearchTermChange: vi.fn(), - query: { data: [], isError: false, isLoading: false }, - redirectCheckQuery: { data: null, isError: false, isLoading: false }, - } as unknown as MockQuickSearchQuery); - - renderHook(() => useSearchWithClusters()); - - expect(mockUseQuery).toHaveBeenCalledWith({ - queryKey: [ 'clusters:get_cluster_by_name', { input: 'campnetwork/lol' } ], - queryFn: expect.any(Function), - enabled: true, - select: expect.any(Function), - }); - }); - }); - - describe('data transformation', () => { - it('should transform cluster API response to search result format', () => { - mockUseQuickSearchQuery.mockReturnValue({ - searchTerm: 'test-cluster/', - debouncedSearchTerm: 'test-cluster/', - handleSearchTermChange: vi.fn(), - query: { data: [], isError: false, isLoading: false }, - redirectCheckQuery: { data: null, isError: false, isLoading: false }, - } as unknown as MockQuickSearchQuery); - - const transformedData = [ - { - type: 'cluster', - name: 'test-cluster', - address_hash: '0x1234567890123456789012345678901234567890', - is_smart_contract_verified: false, - is_smart_contract_address: false, - cluster_info: { - cluster_id: 'cluster-123', - name: 'test-cluster', - owner: '0x1234567890123456789012345678901234567890', - created_at: '2024-01-01T00:00:00Z', - expires_at: '2025-01-01T00:00:00Z', - total_wei_amount: '1000000000000000000', - is_testnet: false, - }, - }, - ]; - - mockUseQuery.mockReturnValue({ - ...defaultUseQueryResult, - data: transformedData, - } as UseQueryResult); - - const { result } = renderHook(() => useSearchWithClusters()); - - expect(result.current.query.data).toEqual(transformedData); - }); - - it('should handle cluster data without optional fields', () => { - mockUseQuickSearchQuery.mockReturnValue({ - searchTerm: 'simple-cluster/', - debouncedSearchTerm: 'simple-cluster/', - handleSearchTermChange: vi.fn(), - query: { data: [], isError: false, isLoading: false }, - redirectCheckQuery: { data: null, isError: false, isLoading: false }, - } as unknown as MockQuickSearchQuery); - - const transformedData = [ - { - type: 'cluster', - name: 'simple-cluster', - address_hash: '0xabcdefabcdefabcdefabcdefabcdefabcdefabcdef', - is_smart_contract_verified: false, - is_smart_contract_address: false, - cluster_info: { - cluster_id: 'simple-cluster', - name: 'simple-cluster', - owner: '0xabcdefabcdefabcdefabcdefabcdefabcdefabcdef', - created_at: undefined, - expires_at: undefined, - total_wei_amount: undefined, - is_testnet: undefined, - }, - }, - ]; - - mockUseQuery.mockReturnValue({ - ...defaultUseQueryResult, - data: transformedData, - } as UseQueryResult); - - const { result } = renderHook(() => useSearchWithClusters()); - - expect(result.current.query.data).toEqual(transformedData); - }); - - it('should use clusterId as fallback when present', () => { - mockUseQuickSearchQuery.mockReturnValue({ - searchTerm: 'test/', - debouncedSearchTerm: 'test/', - handleSearchTermChange: vi.fn(), - query: { data: [], isError: false, isLoading: false }, - redirectCheckQuery: { data: null, isError: false, isLoading: false }, - } as unknown as MockQuickSearchQuery); - - const transformedData = [ - { - type: 'cluster', - name: 'test', - address_hash: '0x123', - is_smart_contract_verified: false, - is_smart_contract_address: false, - cluster_info: { - cluster_id: 'test', - name: 'test', - owner: '0x123', - created_at: undefined, - expires_at: undefined, - total_wei_amount: undefined, - is_testnet: undefined, - }, - }, - ]; - - mockUseQuery.mockReturnValue({ - ...defaultUseQueryResult, - data: transformedData, - } as UseQueryResult); - - const { result } = renderHook(() => useSearchWithClusters()); - - expect(result.current.query.data).toBeDefined(); - expect(result.current.query.data).toHaveLength(1); - const clusterResult = result.current.query.data![0] as unknown as Record; - expect((clusterResult.cluster_info as Record).cluster_id).toBe('test'); - }); - - it('should return empty results when cluster API returns error', () => { - mockUseQuickSearchQuery.mockReturnValue({ - searchTerm: 'nonexistent-cluster/', - debouncedSearchTerm: 'nonexistent-cluster/', - handleSearchTermChange: vi.fn(), - query: { data: [], isError: false, isLoading: false }, - redirectCheckQuery: { data: null, isError: false, isLoading: false }, - } as unknown as MockQuickSearchQuery); - - mockUseQuery.mockReturnValue({ - ...defaultUseQueryResult, - data: [], - isError: true, - error: new Error('API Error'), - isSuccess: false, - status: 'error', - failureCount: 1, - } as unknown as UseQueryResult); - - const { result } = renderHook(() => useSearchWithClusters()); - - expect(result.current.query.data).toEqual([]); - expect(result.current.query.isError).toBe(true); - }); - - it('should return empty results when cluster API returns no data', () => { - mockUseQuickSearchQuery.mockReturnValue({ - searchTerm: 'empty-cluster/', - debouncedSearchTerm: 'empty-cluster/', - handleSearchTermChange: vi.fn(), - query: { data: [], isError: false, isLoading: false }, - redirectCheckQuery: { data: null, isError: false, isLoading: false }, - } as unknown as MockQuickSearchQuery); - - mockUseQuery.mockReturnValue({ - ...defaultUseQueryResult, - data: [], - } as UseQueryResult); - - const { result } = renderHook(() => useSearchWithClusters()); - - expect(result.current.query.data).toEqual([]); - }); - }); - - describe('fallback to regular search', () => { - it('should return regular search results for non-cluster queries', () => { - const regularSearchData = [ - { type: 'address', address_hash: '0x123', is_smart_contract_verified: true, is_smart_contract_address: true }, - ]; - - mockUseQuickSearchQuery.mockReturnValue({ - searchTerm: '0x123456', - debouncedSearchTerm: '0x123456', - handleSearchTermChange: vi.fn(), - query: { - data: regularSearchData, - isError: false, - isLoading: false, - }, - redirectCheckQuery: { data: null, isError: false, isLoading: false }, - } as unknown as MockQuickSearchQuery); - - const { result } = renderHook(() => useSearchWithClusters()); - - expect(result.current.query.data).toEqual(regularSearchData); - }); - - it('should preserve regular search query properties', () => { - const mockQuickSearchQuery = { - searchTerm: 'regular search', - debouncedSearchTerm: 'regular search', - handleSearchTermChange: vi.fn(), - query: { - data: [], - isError: false, - isLoading: true, - isFetching: true, - }, - redirectCheckQuery: { data: null, isError: false, isLoading: false }, - }; - - mockUseQuickSearchQuery.mockReturnValue(mockQuickSearchQuery as unknown as MockQuickSearchQuery); - - const { result } = renderHook(() => useSearchWithClusters()); - - expect(result.current.query.isLoading).toBe(true); - expect(result.current.query.isFetching).toBe(true); - expect(result.current.searchTerm).toBe('regular search'); - expect(result.current.debouncedSearchTerm).toBe('regular search'); - }); - - it('should preserve error states from regular search', () => { - mockUseQuickSearchQuery.mockReturnValue({ - searchTerm: 'error-search', - debouncedSearchTerm: 'error-search', - handleSearchTermChange: vi.fn(), - query: { - data: [], - isError: true, - error: 'Network error', - isLoading: false, - }, - redirectCheckQuery: { data: null, isError: false, isLoading: false }, - } as unknown as MockQuickSearchQuery); - - const { result } = renderHook(() => useSearchWithClusters()); - - expect(result.current.query.isError).toBe(true); - expect(result.current.query.error).toBe('Network error'); - }); - }); - - describe('integration behavior', () => { - it('should enable cluster API query only for valid cluster searches', () => { - mockUseQuickSearchQuery.mockReturnValue({ - searchTerm: '', - debouncedSearchTerm: '', - handleSearchTermChange: vi.fn(), - query: { data: [], isError: false, isLoading: false }, - redirectCheckQuery: { data: null, isError: false, isLoading: false }, - } as unknown as MockQuickSearchQuery); - - renderHook(() => useSearchWithClusters()); - - expect(mockUseQuery).toHaveBeenCalledWith({ - queryKey: [ 'clusters:get_cluster_by_name', { input: '' } ], - queryFn: expect.any(Function), - enabled: false, - select: expect.any(Function), - }); - }); - - it('should not query cluster API when cluster name is empty', () => { - mockUseQuickSearchQuery.mockReturnValue({ - searchTerm: '/', - debouncedSearchTerm: '/', - handleSearchTermChange: vi.fn(), - query: { data: [], isError: false, isLoading: false }, - redirectCheckQuery: { data: null, isError: false, isLoading: false }, - } as unknown as MockQuickSearchQuery); - - renderHook(() => useSearchWithClusters()); - - expect(mockUseQuery).toHaveBeenCalledWith({ - queryKey: [ 'clusters:get_cluster_by_name', { input: '' } ], - queryFn: expect.any(Function), - enabled: false, - select: expect.any(Function), - }); - }); - - it('should return proper hook interface', () => { - mockUseQuickSearchQuery.mockReturnValue({ - searchTerm: 'test', - debouncedSearchTerm: 'test-debounced', - handleSearchTermChange: vi.fn(), - query: { data: [], isError: false, isLoading: false }, - redirectCheckQuery: { data: 'redirect-data', isError: false, isLoading: false }, - } as unknown as MockQuickSearchQuery); - - const { result } = renderHook(() => useSearchWithClusters()); - - expect(result.current).toHaveProperty('searchTerm', 'test'); - expect(result.current).toHaveProperty('debouncedSearchTerm', 'test-debounced'); - expect(result.current).toHaveProperty('handleSearchTermChange'); - expect(result.current).toHaveProperty('query'); - expect(result.current.redirectCheckQuery).toEqual({ data: 'redirect-data', isError: false, isLoading: false }); - }); - - it('should handle loading states correctly', () => { - mockUseQuickSearchQuery.mockReturnValue({ - searchTerm: 'loading-cluster/', - debouncedSearchTerm: 'loading-cluster/', - handleSearchTermChange: vi.fn(), - query: { data: [], isError: false, isLoading: false }, - redirectCheckQuery: { data: null, isError: false, isLoading: false }, - } as unknown as MockQuickSearchQuery); - - mockUseQuery.mockReturnValue({ - ...defaultUseQueryResult, - isLoading: true, - } as unknown as UseQueryResult); - - const { result } = renderHook(() => useSearchWithClusters()); - - expect(result.current.query.isLoading).toBe(true); - }); - }); - - describe('debouncing integration', () => { - it('should use debounced search term for cluster detection', () => { - mockUseQuickSearchQuery.mockReturnValue({ - searchTerm: 'original-term/', - debouncedSearchTerm: 'final-cluster/', - handleSearchTermChange: vi.fn(), - query: { data: [], isError: false, isLoading: false }, - redirectCheckQuery: { data: null, isError: false, isLoading: false }, - } as unknown as MockQuickSearchQuery); - - renderHook(() => useSearchWithClusters()); - - expect(mockUseQuery).toHaveBeenCalledWith({ - queryKey: [ 'clusters:get_cluster_by_name', { input: 'final-cluster' } ], - queryFn: expect.any(Function), - enabled: true, - select: expect.any(Function), - }); - }); - - it('should pass through original search term for display', () => { - mockUseQuickSearchQuery.mockReturnValue({ - searchTerm: 'original-term', - debouncedSearchTerm: 'debounced/', - handleSearchTermChange: vi.fn(), - query: { data: [], isError: false, isLoading: false }, - redirectCheckQuery: { data: null, isError: false, isLoading: false }, - } as unknown as MockQuickSearchQuery); - - const { result } = renderHook(() => useSearchWithClusters()); - - expect(result.current.searchTerm).toBe('original-term'); - expect(result.current.debouncedSearchTerm).toBe('debounced/'); - }); - }); -}); diff --git a/client/features/name-services/clusters/hooks/useSearchWithClusters.tsx b/client/features/name-services/clusters/hooks/useSearchWithClusters.tsx deleted file mode 100644 index 5250bca94be..00000000000 --- a/client/features/name-services/clusters/hooks/useSearchWithClusters.tsx +++ /dev/null @@ -1,109 +0,0 @@ -// SPDX-License-Identifier: LicenseRef-Blockscout - -import { useQuery } from '@tanstack/react-query'; -import React from 'react'; - -import type { SearchResultCluster } from 'client/features/name-services/clusters/types/api'; - -import useApiFetch from 'client/api/hooks/useApiFetch'; -import { getResourceKey } from 'client/api/hooks/useApiQuery'; -import type { ResourcePayload, ResourceError } from 'client/api/resources'; - -import useQuickSearchQuery from 'client/slices/search/hooks/useQuickSearchQuery'; - -import config from 'configs/app'; - -const nameServicesFeature = config.features.nameServices; -const isClustersEnabled = nameServicesFeature.isEnabled && nameServicesFeature.clusters.isEnabled; - -function isClusterSearch(term: string): boolean { - const trimmed = term.trim(); - const hasTrailingSlash = trimmed.endsWith('/'); - const looksLikeCluster = trimmed.includes('/') || hasTrailingSlash; - - return looksLikeCluster; -} - -function extractClusterName(term: string): string { - const trimmed = term.trim(); - return trimmed.endsWith('/') ? trimmed.slice(0, -1) : trimmed; -} - -function transformClusterToSearchResult(cluster: { - name: string; - clusterId?: string; - owner: string; - createdAt?: string; - expiresAt?: string | null; - backingWei?: string; - isTestnet?: boolean; -}, ownerAddress: string): SearchResultCluster { - return { - type: 'cluster', - name: cluster.name, - address_hash: ownerAddress, - is_smart_contract_verified: false, - is_smart_contract_address: false, - cluster_info: { - cluster_id: cluster.clusterId || cluster.name, - name: cluster.name, - owner: cluster.owner, - created_at: cluster.createdAt, - expires_at: cluster.expiresAt, - total_wei_amount: cluster.backingWei, - is_testnet: cluster.isTestnet, - }, - }; -} - -export default function useSearchWithClusters() { - const quickSearch = useQuickSearchQuery(); - - const isClusterQuery = isClustersEnabled ? - isClusterSearch(quickSearch.debouncedSearchTerm) : false; - - const clusterName = isClusterQuery ? extractClusterName(quickSearch.debouncedSearchTerm) : ''; - - const RESOURCE_NAME = 'clusters:get_cluster_by_name'; - type ClusterQueryResult = ResourcePayload; - - const apiFetch = useApiFetch(); - - const clusterQuery = useQuery, Array>({ - queryKey: getResourceKey(RESOURCE_NAME, { queryParams: { input: clusterName } }), - queryFn: async({ signal }) => { - try { - const result = await apiFetch(RESOURCE_NAME, { - queryParams: { input: JSON.stringify({ name: clusterName }) }, - fetchParams: { signal }, - }) as ClusterQueryResult; - return result; - } catch (error) { - return null; - } - }, - enabled: isClustersEnabled && isClusterQuery && clusterName.length > 0, - select: (data) => { - if (!data?.result?.data) return []; - return [ transformClusterToSearchResult(data.result.data, data.result.data.owner) ]; - }, - }); - - const combinedQuery = React.useMemo(() => { - if (!isClustersEnabled || !isClusterQuery) { - return quickSearch.query; - } - - return clusterQuery; - }, [ isClusterQuery, quickSearch, clusterQuery ]); - - const result = React.useMemo(() => ({ - ...quickSearch, - query: combinedQuery, - }), [ - quickSearch, - combinedQuery, - ]); - - return isClustersEnabled ? result : quickSearch; -} diff --git a/client/features/name-services/clusters/types/api.ts b/client/features/name-services/clusters/types/api.ts deleted file mode 100644 index 44193d55727..00000000000 --- a/client/features/name-services/clusters/types/api.ts +++ /dev/null @@ -1,21 +0,0 @@ -// SPDX-License-Identifier: LicenseRef-Blockscout - -export interface SearchResultCluster { - type: 'cluster'; - name: string | null; - address_hash: string; - is_smart_contract_verified: boolean; - is_smart_contract_address: boolean; - certified?: true; - filecoin_robust_address?: string | null; - url?: string; - cluster_info: { - cluster_id: string; - name: string; - owner: string; - created_at?: string; - expires_at?: string | null; - total_wei_amount?: string; - is_testnet?: boolean; - }; -} diff --git a/client/features/name-services/domains/components/SearchBarSuggestDomain.tsx b/client/features/name-services/domains/components/SearchBarSuggestDomain.tsx deleted file mode 100644 index 6e37879d730..00000000000 --- a/client/features/name-services/domains/components/SearchBarSuggestDomain.tsx +++ /dev/null @@ -1,93 +0,0 @@ -// SPDX-License-Identifier: LicenseRef-Blockscout - -import { Grid, Text, Flex } from '@chakra-ui/react'; -import React from 'react'; - -import type { SearchResultDomain } from 'client/features/name-services/domains/types/api'; -import type { ItemsProps } from 'client/slices/search/components/search-bar/SearchBarSuggest/types'; -import type * as multichain from 'types/client/multichainAggregator'; - -import { toBech32Address } from 'client/slices/address/utils/bech32'; - -import highlightText from 'client/shared/text/highlight-text'; - -import dayjs from 'lib/date/dayjs'; -import * as EnsEntity from 'ui/shared/entities/ens/EnsEntity'; -import HashStringShortenDynamic from 'ui/shared/HashStringShortenDynamic'; -import IconSvg from 'ui/shared/IconSvg'; - -const SearchBarSuggestDomain = ({ data, isMobile, searchTerm, addressFormat }: ItemsProps) => { - const icon = ; - const hash = (() => { - if ('filecoin_robust_address' in data && data.filecoin_robust_address) { - return data.filecoin_robust_address; - } - return addressFormat === 'bech32' && data.address_hash ? toBech32Address(data.address_hash) : data.address_hash; - })(); - - const name = ( - - - - ); - - const address = hash ? ( - - - - ) : null; - - const isContractVerified = 'is_smart_contract_verified' in data && data.is_smart_contract_verified && - ; - - const namesCount = 'names_count' in data.ens_info ? data.ens_info.names_count : 0; - - const expiresText = data.ens_info?.expiry_date ? ` expires ${ dayjs(data.ens_info.expiry_date).fromNow() }` : ''; - const ensNamesCount = namesCount > 39 ? '40+' : `+${ namesCount - 1 }`; - const additionalInfo = ( - - { namesCount > 1 ? ensNamesCount : expiresText } - - ); - - if (isMobile) { - return ( - <> - - { icon } - { name } - - - { address } - { isContractVerified } - - { additionalInfo } - - ); - } - - return ( - - - { icon } - { name } - - - { address } - { isContractVerified } - - { additionalInfo } - - ); -}; - -export default React.memo(SearchBarSuggestDomain); diff --git a/client/features/name-services/domains/stubs/search.ts b/client/features/name-services/domains/stubs/search.ts deleted file mode 100644 index be1d5ec75c7..00000000000 --- a/client/features/name-services/domains/stubs/search.ts +++ /dev/null @@ -1,16 +0,0 @@ -import type { SearchResultDomain } from 'client/features/name-services/domains/types/api'; - -export const domain1: SearchResultDomain = { - address_hash: '0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045', - ens_info: { - address_hash: '0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045', - expiry_date: '2039-09-01T07:36:18.000Z', - name: 'vitalik.eth', - names_count: 1, - }, - is_smart_contract_verified: false, - is_smart_contract_address: false, - name: null, - type: 'ens_domain', - url: '/address/0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045', -}; diff --git a/client/features/name-services/domains/types/api.ts b/client/features/name-services/domains/types/api.ts deleted file mode 100644 index a3f39db8851..00000000000 --- a/client/features/name-services/domains/types/api.ts +++ /dev/null @@ -1,21 +0,0 @@ -// SPDX-License-Identifier: LicenseRef-Blockscout - -import type * as bens from '@blockscout/bens-types'; - -export interface SearchResultDomain { - type: 'ens_domain'; - name: string | null; - address_hash: string | null; - is_smart_contract_verified: boolean; - is_smart_contract_address: boolean; - certified?: true; - filecoin_robust_address?: string | null; - url?: string; - ens_info: { - address_hash: string | null; - expiry_date?: string; - name: string; - names_count: number; - protocol?: bens.ProtocolInfo; - }; -} diff --git a/client/features/op-interop/components/InteropMessageDestinationTx.tsx b/client/features/op-interop/components/InteropMessageDestinationTx.tsx deleted file mode 100644 index 4f788ffcdeb..00000000000 --- a/client/features/op-interop/components/InteropMessageDestinationTx.tsx +++ /dev/null @@ -1,47 +0,0 @@ -// SPDX-License-Identifier: LicenseRef-Blockscout - -import React from 'react'; - -import type { ChainInfo } from 'client/features/op-interop/types/api'; - -import TxEntity from 'client/slices/tx/components/entity/TxEntity'; -import type { EntityProps } from 'client/slices/tx/components/entity/TxEntity'; - -import TxEntityInterop from 'client/features/op-interop/components/TxEntityInterop'; - -type Props = { - relay_transaction_hash?: string | null; - relay_chain?: ChainInfo | null; - truncation?: EntityProps['truncation']; - isLoading?: boolean; -}; - -const InteropMessageDestinationTx = (props: Props) => { - if (props.relay_chain !== undefined) { - return ( - - ); - } - - if (!props.relay_transaction_hash) { - return 'N/A'; - } - - return ( - - ); -}; - -export default InteropMessageDestinationTx; diff --git a/client/features/op-interop/components/InteropMessageSourceTx.tsx b/client/features/op-interop/components/InteropMessageSourceTx.tsx deleted file mode 100644 index 6cec8d841c8..00000000000 --- a/client/features/op-interop/components/InteropMessageSourceTx.tsx +++ /dev/null @@ -1,46 +0,0 @@ -// SPDX-License-Identifier: LicenseRef-Blockscout - -import React from 'react'; - -import type { ChainInfo } from 'client/features/op-interop/types/api'; - -import type { EntityProps } from 'client/slices/tx/components/entity/TxEntity'; -import TxEntity from 'client/slices/tx/components/entity/TxEntity'; - -import TxEntityInterop from 'client/features/op-interop/components/TxEntityInterop'; - -type Props = { - init_transaction_hash?: string | null; - init_chain?: ChainInfo | null; - isLoading?: boolean; - truncation?: EntityProps['truncation']; -}; -const InteropMessageSourceTx = (props: Props) => { - if (props.init_chain !== undefined) { - return ( - - ); - } - - if (!props.init_transaction_hash) { - return 'N/A'; - } - - return ( - - ); -}; - -export default InteropMessageSourceTx; diff --git a/client/features/op-interop/components/InteropMessageStatus.tsx b/client/features/op-interop/components/InteropMessageStatus.tsx deleted file mode 100644 index 6d7754424e3..00000000000 --- a/client/features/op-interop/components/InteropMessageStatus.tsx +++ /dev/null @@ -1,39 +0,0 @@ -// SPDX-License-Identifier: LicenseRef-Blockscout - -import React from 'react'; - -import type { InteropMessage } from 'client/features/op-interop/types/api'; - -import type { StatusTagType } from 'ui/shared/statusTag/StatusTag'; -import StatusTag from 'ui/shared/statusTag/StatusTag'; - -export interface Props { - status: InteropMessage['status']; - isLoading?: boolean; -} - -const InteropMessageStatus = ({ status, isLoading }: Props) => { - let type: StatusTagType; - - switch (status) { - case 'Relayed': { - type = 'ok'; - break; - } - case 'Failed': { - type = 'error'; - break; - } - case 'Sent': { - type = 'pending'; - break; - } - default: - type = 'pending'; - break; - } - - return ; -}; - -export default InteropMessageStatus; diff --git a/client/features/op-interop/mocks/tx.ts b/client/features/op-interop/mocks/tx.ts deleted file mode 100644 index 86fbdf28745..00000000000 --- a/client/features/op-interop/mocks/tx.ts +++ /dev/null @@ -1,33 +0,0 @@ -import type { Transaction } from 'client/slices/tx/types/api'; - -import * as addressMock from 'client/slices/address/mocks/address'; -import { base } from 'client/slices/tx/mocks/tx'; - -import * as interopMock from 'client/features/op-interop/mocks/interop'; - -export const withInteropInMessage: Transaction = { - ...base, - op_interop_messages: [ { - init_chain: interopMock.chain, - nonce: 1, - payload: '0x', - init_transaction_hash: '0x01a8c328b0370068aaaef49c107f70901cd79adcda81e3599a88855532122e09', - sender_address_hash: addressMock.hash, - status: 'Sent', - target_address_hash: addressMock.hash, - } ], -}; - -export const withInteropOutMessage: Transaction = { - ...base, - op_interop_messages: [ { - relay_chain: interopMock.chain, - nonce: 1, - // eslint-disable-next-line max-len - payload: '0xfa4b78b90000000000000000000000000000000000000000000000000000000005001bcfe835d1028984e9e6e7d016b77164eacbcc6cc061e9333c0b37982b504f7ea791000000000000000000000000a79b29ad7e0196c95b87f4663ded82fbf2e3add8', - relay_transaction_hash: '0x01a8c328b0370068aaaef49c107f70901cd79adcda81e3599a88855532122e09', - sender_address_hash: addressMock.hash, - status: 'Sent', - target_address_hash: addressMock.hash, - } ], -}; diff --git a/client/features/op-interop/pages/messages/InteropMessages.tsx b/client/features/op-interop/pages/messages/InteropMessages.tsx deleted file mode 100644 index a5390a7509f..00000000000 --- a/client/features/op-interop/pages/messages/InteropMessages.tsx +++ /dev/null @@ -1,92 +0,0 @@ -// SPDX-License-Identifier: LicenseRef-Blockscout - -import { Box } from '@chakra-ui/react'; -import React from 'react'; - -import useApiQuery from 'client/api/hooks/useApiQuery'; - -import { INTEROP_MESSAGE } from 'client/features/op-interop/stubs'; - -import { generateListStub } from 'stubs/utils'; -import { Skeleton } from 'toolkit/chakra/skeleton'; -import { ACTION_BAR_HEIGHT_DESKTOP } from 'ui/shared/ActionBar'; -import DataListDisplay from 'ui/shared/DataListDisplay'; -import PageTitle from 'ui/shared/Page/PageTitle'; -import useQueryWithPages from 'ui/shared/pagination/useQueryWithPages'; -import StickyPaginationWithText from 'ui/shared/StickyPaginationWithText'; - -import InteropMessagesListItem from './InteropMessagesListItem'; -import InteropMessagesTable from './InteropMessagesTable'; - -const InteropMessages = () => { - const interopMessagesQuery = useQueryWithPages({ - resourceName: 'general:optimistic_l2_interop_messages', - options: { - placeholderData: generateListStub<'general:optimistic_l2_interop_messages'>(INTEROP_MESSAGE, 50, { next_page_params: { - init_transaction_hash: '', - items_count: 50, - timestamp: 0, - } }), - }, - }); - - const countQuery = useApiQuery('general:optimistic_l2_interop_messages_count', { - queryOptions: { - placeholderData: 1927029, - }, - }); - - const text = (() => { - if (countQuery.isError) { - return null; - } - - return ( - - A total of { countQuery.data?.toLocaleString() } messages found - - ); - })(); - - const actionBar = ; - - const content = ( - <> - - { interopMessagesQuery.data?.items.map((item, index) => ( - - )) } - - - - - - ); - - return ( - <> - - - { content } - - - ); -}; - -export default InteropMessages; diff --git a/client/features/op-interop/pages/messages/InteropMessagesTableItem.tsx b/client/features/op-interop/pages/messages/InteropMessagesTableItem.tsx deleted file mode 100644 index 2816dff1fef..00000000000 --- a/client/features/op-interop/pages/messages/InteropMessagesTableItem.tsx +++ /dev/null @@ -1,84 +0,0 @@ -// SPDX-License-Identifier: LicenseRef-Blockscout - -import React from 'react'; - -import type { InteropMessage } from 'client/features/op-interop/types/api'; - -import AddressEntity from 'client/slices/address/components/entity/AddressEntity'; -import AddressFromToIcon from 'client/slices/address/components/from-to/AddressFromToIcon'; - -import AddressEntityInterop from 'client/features/op-interop/components/AddressEntityInterop'; -import InteropMessageDestinationTx from 'client/features/op-interop/components/InteropMessageDestinationTx'; -import InteropMessageSourceTx from 'client/features/op-interop/components/InteropMessageSourceTx'; -import InteropMessageStatus from 'client/features/op-interop/components/InteropMessageStatus'; - -import { Skeleton } from 'toolkit/chakra/skeleton'; -import { TableCell, TableRow } from 'toolkit/chakra/table'; -import TimeWithTooltip from 'ui/shared/time/TimeWithTooltip'; - -import InteropMessageAdditionalInfo from './InteropMessageAdditionalInfo'; - -interface Props { - item: InteropMessage; - isLoading?: boolean; -} - -const InteropMessagesTableItem = ({ item, isLoading }: Props) => { - return ( - - - - - - - { item.nonce } - - - - - - - - - - - - - - - - { item.init_chain !== undefined ? ( - - ) : - - } - - - - - - { item.relay_chain !== undefined ? ( - - ) : - - } - - - ); -}; - -export default React.memo(InteropMessagesTableItem); diff --git a/client/features/op-interop/pages/messages/__screenshots__/InteropMessages.pw.tsx_default_mobile-default-view-1.png b/client/features/op-interop/pages/messages/__screenshots__/InteropMessages.pw.tsx_default_mobile-default-view-1.png deleted file mode 100644 index f1190aefa01..00000000000 Binary files a/client/features/op-interop/pages/messages/__screenshots__/InteropMessages.pw.tsx_default_mobile-default-view-1.png and /dev/null differ diff --git a/client/features/op-interop/pages/tx/TxDetailsInterop.tsx b/client/features/op-interop/pages/tx/TxDetailsInterop.tsx deleted file mode 100644 index 8d02ba463dd..00000000000 --- a/client/features/op-interop/pages/tx/TxDetailsInterop.tsx +++ /dev/null @@ -1,130 +0,0 @@ -// SPDX-License-Identifier: LicenseRef-Blockscout - -import { Grid, Text, Flex, Box } from '@chakra-ui/react'; -import React from 'react'; - -import type { InteropTransactionInfo } from 'client/features/op-interop/types/api'; - -import AddressEntity from 'client/slices/address/components/entity/AddressEntity'; - -import AddressEntityInterop from 'client/features/op-interop/components/AddressEntityInterop'; -import InteropMessageDestinationTx from 'client/features/op-interop/components/InteropMessageDestinationTx'; -import InteropMessageSourceTx from 'client/features/op-interop/components/InteropMessageSourceTx'; -import InteropMessageStatus from 'client/features/op-interop/components/InteropMessageStatus'; -import { layerLabels } from 'client/features/rollup/common/utils/layer'; - -import config from 'configs/app'; -import { CollapsibleDetails } from 'toolkit/chakra/collapsible'; -import CopyToClipboard from 'ui/shared/CopyToClipboard'; -import * as DetailedInfo from 'ui/shared/DetailedInfo/DetailedInfo'; - -const rollupFeature = config.features.rollup; - -type Props = { - data?: InteropTransactionInfo; - isLoading: boolean; -}; - -const TxDetailsInterop = ({ data, isLoading }: Props) => { - const hasInterop = rollupFeature.isEnabled && rollupFeature.interopEnabled; - - if (!hasInterop || !data) { - return null; - } - - const details = ( - - Message id - { data.nonce } - Interop status - - - - Sender - { data.init_chain !== undefined ? ( - - ) : ( - - ) } - Target - { data.relay_chain !== undefined ? ( - - ) : ( - - ) } - Payload - - - { data.payload } - - - - - ); - - if (data.init_chain !== undefined) { - return ( - <> - - Interop source tx - - - - - { details } - - - - ); - } - - if (data.relay_chain !== undefined) { - return ( - <> - - Interop relay tx - - - - - { details } - - - - ); - } - return null; -}; - -export default TxDetailsInterop; diff --git a/client/features/op-interop/stubs.ts b/client/features/op-interop/stubs.ts deleted file mode 100644 index 4eef5a80d6f..00000000000 --- a/client/features/op-interop/stubs.ts +++ /dev/null @@ -1,21 +0,0 @@ -import type { InteropMessage } from 'client/features/op-interop/types/api'; - -import { ADDRESS_HASH } from 'client/slices/address/stubs/address-params'; -import { TX_HASH } from 'client/slices/tx/stubs/tx'; - -export const INTEROP_MESSAGE: InteropMessage = { - init_transaction_hash: TX_HASH, - nonce: 52, - payload: '0x4f0edcc90000000000000000000000007da521cbbe62e89cd75e0993c78b8c68c25f696b', - relay_chain: { - chain_id: 420120000, - chain_name: 'Optimism Testnet', - chain_logo: null, - instance_url: 'https://optimism-interop-alpha-0.blockscout.com/', - }, - relay_transaction_hash: TX_HASH, - sender_address_hash: ADDRESS_HASH, - status: 'Relayed', - target_address_hash: ADDRESS_HASH, - timestamp: '2025-02-20T01:05:14.000000Z', -}; diff --git a/client/features/op-interop/types/api.ts b/client/features/op-interop/types/api.ts deleted file mode 100644 index fccd5af01f2..00000000000 --- a/client/features/op-interop/types/api.ts +++ /dev/null @@ -1,48 +0,0 @@ -// SPDX-License-Identifier: LicenseRef-Blockscout - -export interface ChainInfo { - chain_id: number; - chain_name: string | null; - chain_logo: string | null; - instance_url: string; -} - -export type MessageStatus = 'Sent' | 'Relayed' | 'Failed'; - -export interface InteropMessage { - init_transaction_hash: string; - init_chain?: ChainInfo | null; - nonce: number; - payload: string; - relay_chain?: ChainInfo | null; - relay_transaction_hash: string | null; - sender_address_hash: string; - status: MessageStatus; - target_address_hash: string; - timestamp: string; -} - -export interface InteropMessageListResponse { - items: Array; - next_page_params?: { - init_transaction_hash: string; - items_count: number; - timestamp: number; - }; -} - -export interface InteropTransactionInfo { - nonce: number; - payload: string; - init_chain?: ChainInfo | null; - relay_chain?: ChainInfo | null; - init_transaction_hash?: string; - relay_transaction_hash?: string; - sender_address_hash: string; - status: MessageStatus; - target_address_hash: string; -} - -export interface TransactionOpInterop { - op_interop_messages?: Array; -} diff --git a/client/features/rollup/arbitrum/components/ArbitrumL2MessageStatus.tsx b/client/features/rollup/arbitrum/components/ArbitrumL2MessageStatus.tsx deleted file mode 100644 index 2f719a56c08..00000000000 --- a/client/features/rollup/arbitrum/components/ArbitrumL2MessageStatus.tsx +++ /dev/null @@ -1,49 +0,0 @@ -// SPDX-License-Identifier: LicenseRef-Blockscout - -import React from 'react'; - -import type { ArbitrumL2MessagesItem } from '../types/api'; - -import type { StatusTagType } from 'ui/shared/statusTag/StatusTag'; -import StatusTag from 'ui/shared/statusTag/StatusTag'; - -export interface Props { - status: ArbitrumL2MessagesItem['status']; - isLoading?: boolean; -} - -const ArbitrumL2MessageStatus = ({ status, isLoading }: Props) => { - let type: StatusTagType; - let text: string; - - switch (status) { - case 'relayed': { - type = 'ok'; - text = 'Relayed'; - break; - } - case 'confirmed': { - type = 'pending'; - text = 'Ready for relay'; - break; - } - case 'sent': { - type = 'pending'; - text = 'Waiting'; - break; - } - case 'initiated': { - type = 'pending'; - text = 'Pending'; - break; - } - default: - type = 'pending'; - text = status; - break; - } - - return ; -}; - -export default ArbitrumL2MessageStatus; diff --git a/client/features/rollup/arbitrum/components/ArbitrumL2Messages.tsx b/client/features/rollup/arbitrum/components/ArbitrumL2Messages.tsx deleted file mode 100644 index c0a4ad7ec57..00000000000 --- a/client/features/rollup/arbitrum/components/ArbitrumL2Messages.tsx +++ /dev/null @@ -1,110 +0,0 @@ -// SPDX-License-Identifier: LicenseRef-Blockscout - -import { Box } from '@chakra-ui/react'; -import React from 'react'; - -import useApiQuery from 'client/api/hooks/useApiQuery'; - -import { layerLabels } from 'client/features/rollup/common/utils/layer'; - -import { generateListStub } from 'stubs/utils'; -import { Skeleton } from 'toolkit/chakra/skeleton'; -import { rightLineArrow, nbsp } from 'toolkit/utils/htmlEntities'; -import { ACTION_BAR_HEIGHT_DESKTOP } from 'ui/shared/ActionBar'; -import DataListDisplay from 'ui/shared/DataListDisplay'; -import PageTitle from 'ui/shared/Page/PageTitle'; -import useQueryWithPages from 'ui/shared/pagination/useQueryWithPages'; -import StickyPaginationWithText from 'ui/shared/StickyPaginationWithText'; - -import { ARBITRUM_MESSAGES_ITEM } from '../stubs'; -import ArbitrumL2MessagesListItem from './ArbitrumL2MessagesListItem'; -import ArbitrumL2MessagesTable from './ArbitrumL2MessagesTable'; - -export type MessagesDirection = 'from-rollup' | 'to-rollup'; - -type Props = { - direction: MessagesDirection; -}; - -const ArbitrumL2Messages = ({ direction }: Props) => { - const type = direction === 'from-rollup' ? 'withdrawals' : 'deposits'; - const { data, isError, isPlaceholderData, pagination } = useQueryWithPages({ - resourceName: 'general:arbitrum_l2_messages', - pathParams: { direction }, - options: { - placeholderData: generateListStub<'general:arbitrum_l2_messages'>( - ARBITRUM_MESSAGES_ITEM, - 50, - { next_page_params: { items_count: 50, direction: 'to-rollup', id: 123456 } }, - ), - }, - }); - - const countersQuery = useApiQuery('general:arbitrum_l2_messages_count', { - pathParams: { direction }, - queryOptions: { - placeholderData: 1927029, - }, - }); - - const content = data?.items ? ( - <> - - { data.items.map(((item, index) => ( - - ))) } - - - - - - ) : null; - - const text = (() => { - if (countersQuery.isError) { - return null; - } - - return ( - - A total of { countersQuery.data?.toLocaleString() } { type } found - - ); - })(); - - const actionBar = ; - - return ( - <> - - - { content } - - - ); -}; - -export default ArbitrumL2Messages; diff --git a/client/features/rollup/arbitrum/components/ArbitrumL2TxnBatchStatus.tsx b/client/features/rollup/arbitrum/components/ArbitrumL2TxnBatchStatus.tsx deleted file mode 100644 index f25348431aa..00000000000 --- a/client/features/rollup/arbitrum/components/ArbitrumL2TxnBatchStatus.tsx +++ /dev/null @@ -1,30 +0,0 @@ -// SPDX-License-Identifier: LicenseRef-Blockscout - -import React from 'react'; - -import type { ArbitrumL2TxnBatchesItem } from '../types/api'; - -import type { StatusTagType } from 'ui/shared/statusTag/StatusTag'; -import StatusTag from 'ui/shared/statusTag/StatusTag'; - -export interface Props { - status: ArbitrumL2TxnBatchesItem['commitment_transaction']['status']; - isLoading?: boolean; -} - -const ArbitrumL2TxnBatchStatus = ({ status, isLoading }: Props) => { - let type: StatusTagType; - - switch (status) { - case 'finalized': - type = 'ok'; - break; - default: - type = 'pending'; - break; - } - - return ; -}; - -export default ArbitrumL2TxnBatchStatus; diff --git a/client/features/rollup/arbitrum/mocks/deposits.ts b/client/features/rollup/arbitrum/mocks/deposits.ts deleted file mode 100644 index 79c7f0e5e88..00000000000 --- a/client/features/rollup/arbitrum/mocks/deposits.ts +++ /dev/null @@ -1,46 +0,0 @@ -import type { ArbitrumL2MessagesResponse, ArbitrumLatestDepositsResponse } from '../types/api'; - -export const baseResponse: ArbitrumL2MessagesResponse = { - items: [ - { - completion_transaction_hash: '0x0b7d58c0a6b4695ba28d99df928591fb931c812c0aab6d0093ff5040d2f9bc5e', - id: 181920, - origination_address_hash: '0x2B51Ae4412F79c3c1cB12AA40Ea4ECEb4e80511a', - origination_transaction_block_number: 123456, - origination_transaction_hash: '0x210d9f70f411de1079e32a98473b04345a5ea6ff2340a8511ebc2df641274436', - origination_timestamp: '2023-06-01T14:46:48.000000Z', - status: 'initiated', - }, - { - completion_transaction_hash: '0x0b7d58c0a6b4695ba28d99df928591fb931c812c0aab6d0093ff5040d2f9bc5e', - id: 181921, - origination_address_hash: '0x2B51Ae4412F79c3c1cB12AA40Ea4ECEb4e80511a', - origination_transaction_block_number: 123400, - origination_transaction_hash: '0x210d9f70f411de1079e32a98473b04345a5ea6ff2340a8511ebc2df641274436', - origination_timestamp: '2023-06-01T14:46:48.000000Z', - status: 'relayed', - }, - ], - next_page_params: { - items_count: 50, - id: 123, - direction: 'to-rollup', - }, -}; - -export const latestDepositsResponse: ArbitrumLatestDepositsResponse = { - items: [ - { - completion_transaction_hash: '0x3ccdf87449d3de6a9dcd3eddb7bc9ecdf1770d4631f03cdf12a098911618d138', - origination_transaction_block_number: 123400, - origination_transaction_hash: '0x210d9f70f411de1079e32a98473b04345a5ea6ff2340a8511ebc2df641274436', - origination_timestamp: '2023-06-01T14:46:48.000000Z', - }, - { - completion_transaction_hash: '0xd16d918b2f95a5cdf66824f6291b6d5eb80b6f4acab3f9fb82ee0ec4109646a0', - origination_timestamp: null, - origination_transaction_block_number: null, - origination_transaction_hash: null, - }, - ], -}; diff --git a/client/features/rollup/arbitrum/mocks/tx.ts b/client/features/rollup/arbitrum/mocks/tx.ts deleted file mode 100644 index 39813c1bdd6..00000000000 --- a/client/features/rollup/arbitrum/mocks/tx.ts +++ /dev/null @@ -1,30 +0,0 @@ -import type { Transaction } from 'client/slices/tx/types/api'; - -import { base } from 'client/slices/tx/mocks/tx'; - -export const arbitrumTxn: Transaction = { - ...base, - arbitrum: { - batch_number: 743991, - commitment_transaction: { - hash: '0x71a25e01dde129a308704de217d200ea42e0f5b8c221c8ba8b2b680ff347f708', - status: 'unfinalized', - timestamp: '2024-11-19T14:26:23.000000Z', - }, - confirmation_transaction: { - hash: null, - status: null, - timestamp: null, - }, - contains_message: null, - gas_used_for_l1: '129773', - gas_used_for_l2: '128313', - message_related_info: { - associated_l1_transaction_hash: null, - message_status: 'Relayed', - }, - network_fee: '1283130000000', - poster_fee: '1297730000000', - status: 'Sent to base', - }, -}; diff --git a/client/features/rollup/arbitrum/mocks/txn-batch.ts b/client/features/rollup/arbitrum/mocks/txn-batch.ts deleted file mode 100644 index b613b7f0af1..00000000000 --- a/client/features/rollup/arbitrum/mocks/txn-batch.ts +++ /dev/null @@ -1,53 +0,0 @@ -/* eslint-disable max-len */ -import type { ArbitrumL2TxnBatch } from '../types/api'; - -import { finalized } from './txn-batches'; - -export const batchData: ArbitrumL2TxnBatch = { - ...finalized, - after_acc_hash: '0xcd064f3409015e8e6407e492e5275a185e492c6b43ccf127f22092d8057a9ffb', - before_acc_hash: '0x2ed7c4985eb778d76ec400a43805e7feecc8c2afcdb492dbe5caf227de6d37bc', - start_block_number: 1245209, - end_block_number: 1245490, - data_availability: { - batch_data_container: 'in_blob4844', - }, -}; - -export const batchDataAnytrust: ArbitrumL2TxnBatch = { - ...finalized, - after_acc_hash: '0xcd064f3409015e8e6407e492e5275a185e492c6b43ccf127f22092d8057a9ffb', - before_acc_hash: '0x2ed7c4985eb778d76ec400a43805e7feecc8c2afcdb492dbe5caf227de6d37bc', - start_block_number: 1245209, - end_block_number: 1245490, - data_availability: { - batch_data_container: 'in_anytrust', - bls_signature: '0x142577943e30b1ad1b4e40a1c08e00c24a68d6c366f953e361048b7127e327b5bdb8f168ba986beae40cfaf79ea2788004d750555684751e361d6f6445e5c521b45ac93a76da24add241a4a5410ca3a09fa82cf0aafd78801cbd0ad99d5be6b3', - data_hash: '0x4ffada101d8185bcba227f2cff9e0ea0a4deeb08f328601a898131429a436ebe', - timeout: '2024-08-22T12:39:22Z', - signers: [ - { - key: '0x0c6694955b524d718ca445831c5375393773401f33725a79661379dddabd5fff28619dc070befd9ed73d699e5c236c1a163be58ba81002b6130709bc064af5d7ba947130b72056bf17263800f1a3ab2269c6a510ef8e7412fd56d1ef1b916a1306e3b1d9c82c099371bd9861582acaada3a16e9dfee5d0ebce61096598a82f112d0a935e8cab5c48d82e3104b0c7ba79157dad1a019a3e7f6ad077b8e6308b116fec0f58239622463c3631fa01e2b4272409215b8009422c16715dbede590906', - proof: '0x06dcb5e56764bb72e6a45e6deb301ca85d8c4315c1da2efa29927f2ac8fb25571ce31d2d603735fe03196f6d56bcbf9a1999a89a74d5369822c4445d676c15ed52e5008daa775dc9a839c99ff963a19946ac740579874dac4f639907ae1bc69f', - trusted: false, - }, - { - key: '0x0ee5aaeabd57313285207eb89366b411286cf3f1c5e30eb7e355f55385308b91d5807284323ee89a9743c70676f4949504ced3ed41612cbfda06ad55200c1c77d3fb3700059befd64c44bc4a57cb567ec1481ee564cf6cd6cf1f2f4a2dee6db00c547c38400ab118dedae8afd5bab93b703f76a0991baa5d43fbb125194c06b5461f8c738a3c4278a3d98e5456aec0720883c0d28919537a36e2ffd5f731e742b6653557d154c164e068ef983b367ef626faaed46f4eadecbb12b7e55f23175d', - trusted: true, - }, - ], - }, -}; - -export const batchDataCelestia: ArbitrumL2TxnBatch = { - ...finalized, - after_acc_hash: '0xcd064f3409015e8e6407e492e5275a185e492c6b43ccf127f22092d8057a9ffb', - before_acc_hash: '0x2ed7c4985eb778d76ec400a43805e7feecc8c2afcdb492dbe5caf227de6d37bc', - start_block_number: 1245209, - end_block_number: 1245490, - data_availability: { - batch_data_container: 'in_celestia', - height: 4520041, - transaction_commitment: '0x3ebe5a43f47fbf69db003e543bb27e4875929ede2fa9a25d09f0bd082d5d20f0', - }, -}; diff --git a/client/features/rollup/arbitrum/mocks/txn-batches.ts b/client/features/rollup/arbitrum/mocks/txn-batches.ts deleted file mode 100644 index 09bff5caa0a..00000000000 --- a/client/features/rollup/arbitrum/mocks/txn-batches.ts +++ /dev/null @@ -1,39 +0,0 @@ -import type { ArbitrumL2TxnBatchesItem, ArbitrumL2TxnBatchesResponse } from '../types/api'; - -export const finalized: ArbitrumL2TxnBatchesItem = { - number: 12345, - blocks_count: 12345, - transactions_count: 10000, - commitment_transaction: { - block_number: 12345, - timestamp: '2022-04-17T08:51:58.000000Z', - hash: '0x262e7215739d6a7e33b2c20b45a838801a0f5f080f20bec8e54eb078420c4661', - status: 'finalized', - }, - batch_data_container: 'in_blob4844', -}; - -export const unfinalized: ArbitrumL2TxnBatchesItem = { - number: 12344, - blocks_count: 10000, - transactions_count: 103020, - commitment_transaction: { - block_number: 12340, - timestamp: '2022-04-17T08:51:58.000000Z', - hash: '0x262e7215739d6a7e33b2c20b45a838801a0f5f080f20bec8e54eb078420c4661', - status: 'unfinalized', - }, - batch_data_container: null, - -}; - -export const baseResponse: ArbitrumL2TxnBatchesResponse = { - items: [ - finalized, - unfinalized, - ], - next_page_params: { - items_count: 50, - number: 123, - }, -}; diff --git a/client/features/rollup/arbitrum/mocks/withdrawals.ts b/client/features/rollup/arbitrum/mocks/withdrawals.ts deleted file mode 100644 index 3fcb295a28e..00000000000 --- a/client/features/rollup/arbitrum/mocks/withdrawals.ts +++ /dev/null @@ -1,29 +0,0 @@ -import type { ArbitrumL2MessagesResponse } from '../types/api'; - -export const baseResponse: ArbitrumL2MessagesResponse = { - items: [ - { - completion_transaction_hash: '0x0b7d58c0a6b4695ba28d99df928591fb931c812c0aab6d0093ff5040d2f9bc5e', - id: 181920, - origination_address_hash: '0x2B51Ae4412F79c3c1cB12AA40Ea4ECEb4e80511a', - origination_transaction_block_number: 123456, - origination_transaction_hash: '0x210d9f70f411de1079e32a98473b04345a5ea6ff2340a8511ebc2df641274436', - origination_timestamp: '2023-06-01T14:46:48.000000Z', - status: 'sent', - }, - { - completion_transaction_hash: '0x0b7d58c0a6b4695ba28d99df928591fb931c812c0aab6d0093ff5040d2f9bc5e', - id: 181921, - origination_address_hash: '0x2B51Ae4412F79c3c1cB12AA40Ea4ECEb4e80511a', - origination_transaction_block_number: 123400, - origination_transaction_hash: '0x210d9f70f411de1079e32a98473b04345a5ea6ff2340a8511ebc2df641274436', - origination_timestamp: '2023-06-01T14:46:48.000000Z', - status: 'confirmed', - }, - ], - next_page_params: { - items_count: 50, - id: 123, - direction: 'from-rollup', - }, -}; diff --git a/client/features/rollup/arbitrum/pages/batch-details/ArbitrumL2TxnBatch.pw.tsx b/client/features/rollup/arbitrum/pages/batch-details/ArbitrumL2TxnBatch.pw.tsx deleted file mode 100644 index a1ab0c9a706..00000000000 --- a/client/features/rollup/arbitrum/pages/batch-details/ArbitrumL2TxnBatch.pw.tsx +++ /dev/null @@ -1,63 +0,0 @@ -import React from 'react'; - -import { ENVS_MAP } from 'playwright/fixtures/mockEnvs'; -import { test, expect, devices } from 'playwright/lib'; - -import { batchData, batchDataAnytrust, batchDataCelestia } from '../../mocks/txn-batch'; -import ArbitrumL2TxnBatch from './ArbitrumL2TxnBatch'; - -const batchNumber = '5'; -const hooksConfig = { - router: { - query: { number: batchNumber }, - }, -}; - -test.beforeEach(async({ mockTextAd, mockEnvs }) => { - await mockEnvs(ENVS_MAP.arbitrumRollup); - await mockTextAd(); -}); - -test('base view', async({ render, mockApiResponse }) => { - await mockApiResponse('general:arbitrum_l2_txn_batch', batchData, { pathParams: { number: batchNumber } }); - const component = await render(, { hooksConfig }); - await expect(component).toHaveScreenshot(); -}); - -test('with anytrust DA', async({ render, mockApiResponse }) => { - await mockApiResponse('general:arbitrum_l2_txn_batch', batchDataAnytrust, { pathParams: { number: batchNumber } }); - const component = await render(, { hooksConfig }); - await component.getByText('Show data availability info').click(); - await expect(component).toHaveScreenshot(); -}); - -test('with celestia DA', async({ render, mockApiResponse }) => { - await mockApiResponse('general:arbitrum_l2_txn_batch', batchDataCelestia, { pathParams: { number: batchNumber } }); - const component = await render(, { hooksConfig }); - await component.getByText('Show data availability info').click(); - await expect(component).toHaveScreenshot(); -}); - -test.describe('mobile', () => { - test.use({ viewport: devices['iPhone 13 Pro'].viewport }); - test('base view', async({ render, mockApiResponse }) => { - await mockApiResponse('general:arbitrum_l2_txn_batch', batchData, { pathParams: { number: batchNumber } }); - const component = await render(, { hooksConfig }); - await expect(component).toHaveScreenshot(); - }); - - test('with anytrust DA', async({ render, mockApiResponse }) => { - await mockApiResponse('general:arbitrum_l2_txn_batch', batchDataAnytrust, { pathParams: { number: batchNumber } }); - const component = await render(, { hooksConfig }); - await component.getByText('Show data availability info').click(); - await expect(component).toHaveScreenshot(); - }); - - test('with celestia DA', async({ render, mockApiResponse, page }) => { - await mockApiResponse('general:arbitrum_l2_txn_batch', batchDataCelestia, { pathParams: { number: batchNumber } }); - const component = await render(, { hooksConfig }); - await component.getByText('Show data availability info').click(); - await page.mouse.move(0, 0); - await expect(component).toHaveScreenshot(); - }); -}); diff --git a/client/features/rollup/arbitrum/pages/batch-details/ArbitrumL2TxnBatch.tsx b/client/features/rollup/arbitrum/pages/batch-details/ArbitrumL2TxnBatch.tsx deleted file mode 100644 index 05d796e51b7..00000000000 --- a/client/features/rollup/arbitrum/pages/batch-details/ArbitrumL2TxnBatch.tsx +++ /dev/null @@ -1,118 +0,0 @@ -// SPDX-License-Identifier: LicenseRef-Blockscout - -import { useRouter } from 'next/router'; -import React from 'react'; - -import type { TabItemRegular } from 'toolkit/components/AdaptiveTabs/types'; - -import BlocksContent from 'client/slices/block/pages/index/BlocksContent'; -import { BLOCK } from 'client/slices/block/stubs/block'; -import TxsWithFrontendSorting from 'client/slices/tx/pages/index/list/TxsWithFrontendSorting'; -import { TX } from 'client/slices/tx/stubs/tx'; - -import throwOnAbsentParamError from 'client/shared/errors/throw-on-absent-param-error'; -import throwOnResourceLoadError from 'client/shared/errors/throw-on-resource-load-error'; -import useIsMobile from 'client/shared/hooks/useIsMobile'; -import getQueryParamString from 'client/shared/router/get-query-param-string'; - -import { generateListStub } from 'stubs/utils'; -import RoutedTabs from 'toolkit/components/RoutedTabs/RoutedTabs'; -import TextAd from 'ui/shared/ad/TextAd'; -import PageTitle from 'ui/shared/Page/PageTitle'; -import Pagination from 'ui/shared/pagination/Pagination'; -import useQueryWithPages from 'ui/shared/pagination/useQueryWithPages'; - -import ArbitrumL2TxnBatchDetails from './ArbitrumL2TxnBatchDetails'; -import useBatchQuery from './useBatchQuery'; - -const TAB_LIST_PROPS = { - marginBottom: 0, - py: 5, - marginTop: -5, -}; - -const TABS_HEIGHT = 80; - -const ArbitrumL2TxnBatch = () => { - const router = useRouter(); - const number = getQueryParamString(router.query.number); - const height = getQueryParamString(router.query.height); - const commitment = getQueryParamString(router.query.commitment); - const tab = getQueryParamString(router.query.tab); - const isMobile = useIsMobile(); - - const batchQuery = useBatchQuery(); - - const batchTxsQuery = useQueryWithPages({ - resourceName: 'general:arbitrum_l2_txn_batch_txs', - pathParams: { number: String(batchQuery.data?.number) }, - options: { - enabled: Boolean(!batchQuery.isPlaceholderData && batchQuery.data?.number && tab === 'txs'), - placeholderData: generateListStub<'general:arbitrum_l2_txn_batch_txs'>(TX, 50, { next_page_params: { - batch_number: '8122', - block_number: 1338932, - index: 0, - items_count: 50, - } }), - }, - }); - - const batchBlocksQuery = useQueryWithPages({ - resourceName: 'general:arbitrum_l2_txn_batch_blocks', - pathParams: { number: String(batchQuery.data?.number) }, - options: { - enabled: Boolean(!batchQuery.isPlaceholderData && batchQuery.data?.number && tab === 'blocks'), - placeholderData: generateListStub<'general:arbitrum_l2_txn_batch_blocks'>(BLOCK, 50, { next_page_params: { - batch_number: '8122', - block_number: 1338932, - items_count: 50, - } }), - }, - }); - - throwOnAbsentParamError(number || (height && commitment)); - throwOnResourceLoadError(batchQuery); - - let pagination; - if (tab === 'txs') { - pagination = batchTxsQuery.pagination; - } - if (tab === 'blocks') { - pagination = batchBlocksQuery.pagination; - } - - const hasPagination = !isMobile && pagination?.isVisible; - - const tabs: Array = React.useMemo(() => ([ - { id: 'index', title: 'Details', component: }, - { - id: 'txs', - title: 'Transactions', - component: , - }, - { - id: 'blocks', - title: 'Blocks', - component: , - }, - ].filter(Boolean)), [ batchQuery, batchTxsQuery, batchBlocksQuery, hasPagination ]); - - return ( - <> - - - : null } - stickyEnabled={ hasPagination } - /> - - ); -}; - -export default ArbitrumL2TxnBatch; diff --git a/client/features/rollup/arbitrum/pages/batch-details/ArbitrumL2TxnBatchDetails.tsx b/client/features/rollup/arbitrum/pages/batch-details/ArbitrumL2TxnBatchDetails.tsx deleted file mode 100644 index 2a5851767c8..00000000000 --- a/client/features/rollup/arbitrum/pages/batch-details/ArbitrumL2TxnBatchDetails.tsx +++ /dev/null @@ -1,218 +0,0 @@ -// SPDX-License-Identifier: LicenseRef-Blockscout - -import { GridItem } from '@chakra-ui/react'; -import type { UseQueryResult } from '@tanstack/react-query'; -import { useRouter } from 'next/router'; -import React from 'react'; - -import type { ArbitrumL2TxnBatch } from '../../types/api'; - -import { route } from 'nextjs-routes'; - -import type { ResourceError } from 'client/api/resources'; - -import BlockEntityL1 from 'client/features/rollup/common/components/BlockEntityL1'; -import TxEntityL1 from 'client/features/rollup/common/components/TxEntityL1'; -import { layerLabels } from 'client/features/rollup/common/utils/layer'; - -import throwOnResourceLoadError from 'client/shared/errors/throw-on-resource-load-error'; - -import { CollapsibleDetails } from 'toolkit/chakra/collapsible'; -import { Link } from 'toolkit/chakra/link'; -import { Skeleton } from 'toolkit/chakra/skeleton'; -import isCustomAppError from 'ui/shared/AppError/isCustomAppError'; -import CopyToClipboard from 'ui/shared/CopyToClipboard'; -import DataFetchAlert from 'ui/shared/DataFetchAlert'; -import * as DetailedInfo from 'ui/shared/DetailedInfo/DetailedInfo'; -import DetailedInfoTimestamp from 'ui/shared/DetailedInfo/DetailedInfoTimestamp'; -import HashStringShortenDynamic from 'ui/shared/HashStringShortenDynamic'; -import PrevNext from 'ui/shared/PrevNext'; - -import ArbitrumL2TxnBatchDA from '../../components/ArbitrumL2TxnBatchDA'; -import ArbitrumL2TxnBatchDetailsAnyTrustDA from './ArbitrumL2TxnBatchDetailsAnyTrustDA'; -import ArbitrumL2TxnBatchDetailsCelestiaDA from './ArbitrumL2TxnBatchDetailsCelestiaDA'; - -interface Props { - query: UseQueryResult; -} - -const ArbitrumL2TxnBatchDetails = ({ query }: Props) => { - const router = useRouter(); - - const { data, isPlaceholderData, isError, error } = query; - - const handlePrevNextClick = React.useCallback((direction: 'prev' | 'next') => { - if (!data) { - return; - } - - const increment = direction === 'next' ? +1 : -1; - const nextId = String(data.number + increment); - - router.push({ pathname: '/batches/[number]', query: { number: nextId } }, undefined); - }, [ data, router ]); - - if (isError) { - if (isCustomAppError(error)) { - throwOnResourceLoadError({ isError, error }); - } - - return ; - } - - if (!data) { - return null; - } - - const blocksCount = data.end_block_number - data.start_block_number + 1; - - return ( - - - Txn batch number - - - - { data.number } - - - - - - Timestamp - - - { data.commitment_transaction.timestamp ? - : - 'Undefined' - } - - - - Transactions - - - - { data.transactions_count.toLocaleString() } transaction{ data.transactions_count === 1 ? '' : 's' } - - - - - Blocks - - - - { blocksCount.toLocaleString() } block{ blocksCount === 1 ? '' : 's' } - - - - - { layerLabels.parent } transaction hash - - - - - - - { layerLabels.parent } block - - - - - - { data.data_availability.batch_data_container && ( - <> - - Batch data container - - - - - ) } - - - Before acc - - - - - - - - - - After acc - - - - - - - - - { (data.data_availability.batch_data_container === 'in_anytrust' || data.data_availability.batch_data_container === 'in_celestia') && ( - - - - { data.data_availability.batch_data_container === 'in_anytrust' && ( - - ) } - { data.data_availability.batch_data_container === 'in_celestia' && ( - - ) } - - ) } - - ); -}; - -export default ArbitrumL2TxnBatchDetails; diff --git a/client/features/rollup/arbitrum/pages/batch-details/ArbitrumL2TxnBatchDetailsAnyTrustDA.tsx b/client/features/rollup/arbitrum/pages/batch-details/ArbitrumL2TxnBatchDetailsAnyTrustDA.tsx deleted file mode 100644 index e5af024c512..00000000000 --- a/client/features/rollup/arbitrum/pages/batch-details/ArbitrumL2TxnBatchDetailsAnyTrustDA.tsx +++ /dev/null @@ -1,121 +0,0 @@ -// SPDX-License-Identifier: LicenseRef-Blockscout - -import { Grid, Text, Flex, Box, VStack } from '@chakra-ui/react'; -import React from 'react'; - -import type { ArbitrumL2TxnBatchDAAnytrust } from '../../types/api'; - -import dayjs from 'lib/date/dayjs'; -import CopyToClipboard from 'ui/shared/CopyToClipboard'; -import * as DetailedInfo from 'ui/shared/DetailedInfo/DetailedInfo'; -import DetailsTimestamp from 'ui/shared/DetailedInfo/DetailedInfoTimestamp'; -import HashStringShorten from 'ui/shared/HashStringShorten'; -import IconSvg from 'ui/shared/IconSvg'; -import TextSeparator from 'ui/shared/TextSeparator'; - -type Props = { - data: ArbitrumL2TxnBatchDAAnytrust; -}; - -const ArbitrumL2TxnBatchDetailsAnyTrustDA = ({ data }: Props) => { - return ( - <> - - Signature - - { data.bls_signature } - - Data hash - - - { data.data_hash } - - - - Timeout - - { dayjs(data.timeout) < dayjs() ? - : - ( - <> - - - { dayjs(data.timeout).diff(dayjs(), 'day') } days left - - ) } - - - Signers - - - - Key - Trusted - Proof - { data.signers.map(signer => ( - <> - - { signer.key } - - - - { signer.trusted ? : } - - { signer.proof ? ( - - - - - ) : '-' } - - )) } - - - - { data.signers.map(signer => ( - - - Key - - - { signer.key } - - - Trusted - { signer.trusted ? : } - - - Proof - { signer.proof ? ( - - - - - ) : '-' } - - - - )) } - - - - ); -}; - -export default ArbitrumL2TxnBatchDetailsAnyTrustDA; diff --git a/client/features/rollup/arbitrum/pages/batch-details/ArbitrumL2TxnBatchDetailsCelestiaDA.tsx b/client/features/rollup/arbitrum/pages/batch-details/ArbitrumL2TxnBatchDetailsCelestiaDA.tsx deleted file mode 100644 index 049997889b5..00000000000 --- a/client/features/rollup/arbitrum/pages/batch-details/ArbitrumL2TxnBatchDetailsCelestiaDA.tsx +++ /dev/null @@ -1,54 +0,0 @@ -// SPDX-License-Identifier: LicenseRef-Blockscout - -import { Flex } from '@chakra-ui/react'; -import React from 'react'; - -import type { ArbitrumL2TxnBatchDACelestia } from '../../types/api'; - -import config from 'configs/app'; -import CeleniumLink from 'ui/shared/batch/CeleniumLink'; -import CopyToClipboard from 'ui/shared/CopyToClipboard'; -import * as DetailedInfo from 'ui/shared/DetailedInfo/DetailedInfo'; -import HashStringShortenDynamic from 'ui/shared/HashStringShortenDynamic'; - -const feature = config.features.rollup; - -interface Props { - data: ArbitrumL2TxnBatchDACelestia; -} - -const ArbitrumL2TxnBatchDetailsCelestiaDA = ({ data }: Props) => { - return ( - <> - - Height - - - { data.height } - - - - Commitment - - - - - - - { feature.isEnabled && feature.DA.celestia.namespace && ( - - ) } - - - ); -}; - -export default ArbitrumL2TxnBatchDetailsCelestiaDA; diff --git a/client/features/rollup/arbitrum/pages/batch-details/useBatchQuery.tsx b/client/features/rollup/arbitrum/pages/batch-details/useBatchQuery.tsx deleted file mode 100644 index 6c04f5f3716..00000000000 --- a/client/features/rollup/arbitrum/pages/batch-details/useBatchQuery.tsx +++ /dev/null @@ -1,34 +0,0 @@ -// SPDX-License-Identifier: LicenseRef-Blockscout - -import { useRouter } from 'next/router'; - -import useApiQuery from 'client/api/hooks/useApiQuery'; - -import getQueryParamString from 'client/shared/router/get-query-param-string'; - -import { ARBITRUM_L2_TXN_BATCH } from '../../stubs'; - -export default function useBatchQuery() { - const router = useRouter(); - const number = getQueryParamString(router.query.number); - const height = getQueryParamString(router.query.height); - const commitment = getQueryParamString(router.query.commitment); - - const batchByNumberQuery = useApiQuery('general:arbitrum_l2_txn_batch', { - pathParams: { number }, - queryOptions: { - enabled: Boolean(number), - placeholderData: ARBITRUM_L2_TXN_BATCH, - }, - }); - - const batchByHeightQuery = useApiQuery('general:arbitrum_l2_txn_batch_celestia', { - pathParams: { height, commitment }, - queryOptions: { - enabled: Boolean(height && commitment), - placeholderData: ARBITRUM_L2_TXN_BATCH, - }, - }); - - return number ? batchByNumberQuery : batchByHeightQuery; -} diff --git a/client/features/rollup/arbitrum/pages/batches/ArbitrumL2TxnBatches.pw.tsx b/client/features/rollup/arbitrum/pages/batches/ArbitrumL2TxnBatches.pw.tsx deleted file mode 100644 index 2c6983d6cb0..00000000000 --- a/client/features/rollup/arbitrum/pages/batches/ArbitrumL2TxnBatches.pw.tsx +++ /dev/null @@ -1,32 +0,0 @@ -import React from 'react'; - -import { ENVS_MAP } from 'playwright/fixtures/mockEnvs'; -import { test, expect, devices } from 'playwright/lib'; - -import * as arbitrumTxnBatchesMock from '../../mocks/txn-batches'; -import ArbitrumL2TxnBatches from './ArbitrumL2TxnBatches'; - -test('base view', async({ render, mockEnvs, mockTextAd, mockApiResponse }) => { - test.slow(); - await mockEnvs(ENVS_MAP.arbitrumRollup); - await mockTextAd(); - await mockApiResponse('general:arbitrum_l2_txn_batches', arbitrumTxnBatchesMock.baseResponse); - await mockApiResponse('general:arbitrum_l2_txn_batches_count', 9927); - - const component = await render(); - await expect(component).toHaveScreenshot(); -}); - -test.describe('mobile', () => { - test.use({ viewport: devices['iPhone 13 Pro'].viewport }); - test('base view', async({ render, mockEnvs, mockTextAd, mockApiResponse }) => { - test.slow(); - await mockEnvs(ENVS_MAP.arbitrumRollup); - await mockTextAd(); - await mockApiResponse('general:arbitrum_l2_txn_batches', arbitrumTxnBatchesMock.baseResponse); - await mockApiResponse('general:arbitrum_l2_txn_batches_count', 9927); - - const component = await render(); - await expect(component).toHaveScreenshot(); - }); -}); diff --git a/client/features/rollup/arbitrum/pages/batches/ArbitrumL2TxnBatches.tsx b/client/features/rollup/arbitrum/pages/batches/ArbitrumL2TxnBatches.tsx deleted file mode 100644 index 04ea8124eb9..00000000000 --- a/client/features/rollup/arbitrum/pages/batches/ArbitrumL2TxnBatches.tsx +++ /dev/null @@ -1,92 +0,0 @@ -// SPDX-License-Identifier: LicenseRef-Blockscout - -import { Box, Text } from '@chakra-ui/react'; -import React from 'react'; - -import useApiQuery from 'client/api/hooks/useApiQuery'; - -import { generateListStub } from 'stubs/utils'; -import { Skeleton } from 'toolkit/chakra/skeleton'; -import { ACTION_BAR_HEIGHT_DESKTOP } from 'ui/shared/ActionBar'; -import DataListDisplay from 'ui/shared/DataListDisplay'; -import PageTitle from 'ui/shared/Page/PageTitle'; -import useQueryWithPages from 'ui/shared/pagination/useQueryWithPages'; -import StickyPaginationWithText from 'ui/shared/StickyPaginationWithText'; - -import { ARBITRUM_L2_TXN_BATCHES_ITEM } from '../../stubs'; -import ArbitrumL2TxnBatchesListItem from './ArbitrumL2TxnBatchesListItem'; -import ArbitrumL2TxnBatchesTable from './ArbitrumL2TxnBatchesTable'; - -const ArbitrumL2TxnBatches = () => { - const { data, isError, isPlaceholderData, pagination } = useQueryWithPages({ - resourceName: 'general:arbitrum_l2_txn_batches', - options: { - placeholderData: generateListStub<'general:arbitrum_l2_txn_batches'>( - ARBITRUM_L2_TXN_BATCHES_ITEM, - 50, - { - next_page_params: { - items_count: 50, - number: 9045200, - }, - }, - ), - }, - }); - - const countersQuery = useApiQuery('general:arbitrum_l2_txn_batches_count', { - queryOptions: { - placeholderData: 5231746, - }, - }); - - const content = data?.items ? ( - <> - - { data.items.map(((item, index) => ( - - ))) } - - - - - - ) : null; - - const text = (() => { - if (countersQuery.isError || isError || !data?.items.length) { - return null; - } - - return ( - - Txn batch - #{ data.items[0].number } to - #{ data.items[data.items.length - 1].number } - (total of { countersQuery.data?.toLocaleString() } batches) - - ); - })(); - - const actionBar = ; - - return ( - <> - - - { content } - - - ); -}; - -export default ArbitrumL2TxnBatches; diff --git a/client/features/rollup/arbitrum/pages/batches/ArbitrumL2TxnBatchesListItem.tsx b/client/features/rollup/arbitrum/pages/batches/ArbitrumL2TxnBatchesListItem.tsx deleted file mode 100644 index 32a7f3a4d58..00000000000 --- a/client/features/rollup/arbitrum/pages/batches/ArbitrumL2TxnBatchesListItem.tsx +++ /dev/null @@ -1,107 +0,0 @@ -// SPDX-License-Identifier: LicenseRef-Blockscout - -import React from 'react'; - -import type { ArbitrumL2TxnBatchesItem } from '../../types/api'; - -import { route } from 'nextjs-routes'; - -import BatchEntityL2 from 'client/features/rollup/common/components/BatchEntityL2'; -import BlockEntityL1 from 'client/features/rollup/common/components/BlockEntityL1'; -import TxEntityL1 from 'client/features/rollup/common/components/TxEntityL1'; -import { layerLabels } from 'client/features/rollup/common/utils/layer'; - -import config from 'configs/app'; -import { Link } from 'toolkit/chakra/link'; -import { Skeleton } from 'toolkit/chakra/skeleton'; -import ListItemMobileGrid from 'ui/shared/ListItemMobile/ListItemMobileGrid'; -import TimeWithTooltip from 'ui/shared/time/TimeWithTooltip'; - -import ArbitrumL2TxnBatchDA from '../../components/ArbitrumL2TxnBatchDA'; -import ArbitrumL2TxnBatchStatus from '../../components/ArbitrumL2TxnBatchStatus'; - -const rollupFeature = config.features.rollup; - -type Props = { item: ArbitrumL2TxnBatchesItem; isLoading?: boolean }; - -const ArbitrumL2TxnBatchesListItem = ({ item, isLoading }: Props) => { - if (!rollupFeature.isEnabled || rollupFeature.type !== 'arbitrum') { - return null; - } - - return ( - - - Batch # - - - - - { layerLabels.parent } status - - - - - { layerLabels.parent } block - - - - - { item.blocks_count && ( - <> - Block count - - { item.blocks_count.toLocaleString() } - - - ) } - - { layerLabels.parent } transaction - - - - - Age - - - - - Txn count - - - { item.transactions_count.toLocaleString() } - - - - Data container - - - - - - ); -}; - -export default ArbitrumL2TxnBatchesListItem; diff --git a/client/features/rollup/arbitrum/pages/batches/ArbitrumL2TxnBatchesTableItem.tsx b/client/features/rollup/arbitrum/pages/batches/ArbitrumL2TxnBatchesTableItem.tsx deleted file mode 100644 index 6d6f98ff371..00000000000 --- a/client/features/rollup/arbitrum/pages/batches/ArbitrumL2TxnBatchesTableItem.tsx +++ /dev/null @@ -1,86 +0,0 @@ -// SPDX-License-Identifier: LicenseRef-Blockscout - -import { HStack } from '@chakra-ui/react'; -import React from 'react'; - -import type { ArbitrumL2TxnBatchesItem } from '../../types/api'; - -import { route } from 'nextjs-routes'; - -import BatchEntityL2 from 'client/features/rollup/common/components/BatchEntityL2'; -import BlockEntityL1 from 'client/features/rollup/common/components/BlockEntityL1'; -import TxEntityL1 from 'client/features/rollup/common/components/TxEntityL1'; - -import config from 'configs/app'; -import { Link } from 'toolkit/chakra/link'; -import { Skeleton } from 'toolkit/chakra/skeleton'; -import { TableCell, TableRow } from 'toolkit/chakra/table'; -import TimeWithTooltip from 'ui/shared/time/TimeWithTooltip'; - -import ArbitrumL2TxnBatchDA from '../../components/ArbitrumL2TxnBatchDA'; -import ArbitrumL2TxnBatchStatus from '../../components/ArbitrumL2TxnBatchStatus'; - -const rollupFeature = config.features.rollup; - -type Props = { item: ArbitrumL2TxnBatchesItem; isLoading?: boolean }; - -const ArbitrumL2TxnBatchesTableItem = ({ item, isLoading }: Props) => { - if (!rollupFeature.isEnabled || rollupFeature.type !== 'arbitrum') { - return null; - } - - return ( - - - - - - - - - - - - - - - { item.blocks_count ? item.blocks_count.toLocaleString() : 'N/A' } - - - - - - - - - - { item.transactions_count.toLocaleString() } - - - - ); -}; - -export default ArbitrumL2TxnBatchesTableItem; diff --git a/client/features/rollup/arbitrum/pages/batches/__screenshots__/ArbitrumL2TxnBatches.pw.tsx_default_base-view-1.png b/client/features/rollup/arbitrum/pages/batches/__screenshots__/ArbitrumL2TxnBatches.pw.tsx_default_base-view-1.png deleted file mode 100644 index 989325fb547..00000000000 Binary files a/client/features/rollup/arbitrum/pages/batches/__screenshots__/ArbitrumL2TxnBatches.pw.tsx_default_base-view-1.png and /dev/null differ diff --git a/client/features/rollup/arbitrum/pages/batches/__screenshots__/ArbitrumL2TxnBatches.pw.tsx_default_mobile-base-view-1.png b/client/features/rollup/arbitrum/pages/batches/__screenshots__/ArbitrumL2TxnBatches.pw.tsx_default_mobile-base-view-1.png deleted file mode 100644 index 372ff3f7a92..00000000000 Binary files a/client/features/rollup/arbitrum/pages/batches/__screenshots__/ArbitrumL2TxnBatches.pw.tsx_default_mobile-base-view-1.png and /dev/null differ diff --git a/client/features/rollup/arbitrum/pages/deposits/ArbitrumL2Deposits.pw.tsx b/client/features/rollup/arbitrum/pages/deposits/ArbitrumL2Deposits.pw.tsx deleted file mode 100644 index 7379833ac9a..00000000000 --- a/client/features/rollup/arbitrum/pages/deposits/ArbitrumL2Deposits.pw.tsx +++ /dev/null @@ -1,19 +0,0 @@ -import React from 'react'; - -import { ENVS_MAP } from 'playwright/fixtures/mockEnvs'; -import { test, expect } from 'playwright/lib'; - -import * as depositsMock from '../../mocks/deposits'; -import ArbitrumL2Deposits from './ArbitrumL2Deposits'; - -test('base view +@mobile', async({ render, mockApiResponse, mockEnvs, mockTextAd }) => { - test.slow(); - await mockTextAd(); - await mockEnvs(ENVS_MAP.arbitrumRollup); - await mockApiResponse('general:arbitrum_l2_messages', depositsMock.baseResponse, { pathParams: { direction: 'to-rollup' } }); - await mockApiResponse('general:arbitrum_l2_messages_count', 3971111, { pathParams: { direction: 'to-rollup' } }); - - const component = await render(); - - await expect(component).toHaveScreenshot({ timeout: 10_000 }); -}); diff --git a/client/features/rollup/arbitrum/pages/deposits/__screenshots__/ArbitrumL2Deposits.pw.tsx_default_base-view-mobile-1.png b/client/features/rollup/arbitrum/pages/deposits/__screenshots__/ArbitrumL2Deposits.pw.tsx_default_base-view-mobile-1.png deleted file mode 100644 index b84aab7114e..00000000000 Binary files a/client/features/rollup/arbitrum/pages/deposits/__screenshots__/ArbitrumL2Deposits.pw.tsx_default_base-view-mobile-1.png and /dev/null differ diff --git a/client/features/rollup/arbitrum/pages/deposits/__screenshots__/ArbitrumL2Deposits.pw.tsx_mobile_base-view-mobile-1.png b/client/features/rollup/arbitrum/pages/deposits/__screenshots__/ArbitrumL2Deposits.pw.tsx_mobile_base-view-mobile-1.png deleted file mode 100644 index 43a25f6a55b..00000000000 Binary files a/client/features/rollup/arbitrum/pages/deposits/__screenshots__/ArbitrumL2Deposits.pw.tsx_mobile_base-view-mobile-1.png and /dev/null differ diff --git a/client/features/rollup/arbitrum/pages/home/LatestArbitrumDeposits.tsx b/client/features/rollup/arbitrum/pages/home/LatestArbitrumDeposits.tsx deleted file mode 100644 index e351f76595c..00000000000 --- a/client/features/rollup/arbitrum/pages/home/LatestArbitrumDeposits.tsx +++ /dev/null @@ -1,83 +0,0 @@ -// SPDX-License-Identifier: LicenseRef-Blockscout - -import { Text } from '@chakra-ui/react'; -import React from 'react'; - -import type { SocketMessage } from 'client/api/socket/types'; - -import useApiQuery from 'client/api/hooks/useApiQuery'; -import useSocketChannel from 'client/api/socket/useSocketChannel'; -import useSocketMessage from 'client/api/socket/useSocketMessage'; - -import LatestTxsFallback from 'client/slices/home/pages/index/txs/LatestTxsFallback'; - -import LatestDeposits from 'client/features/rollup/common/pages/home/LatestDeposits'; - -import useGradualIncrement from 'client/shared/hooks/useGradualIncrement'; -import useIsMobile from 'client/shared/hooks/useIsMobile'; - -import { ARBITRUM_MESSAGES_ITEM } from '../../stubs'; - -const LatestArbitrumDeposits = () => { - const isMobile = useIsMobile(); - const itemsCount = isMobile ? 2 : 5; - const { data, isPlaceholderData, isError } = useApiQuery('general:homepage_arbitrum_deposits', { - queryOptions: { - placeholderData: { items: Array(itemsCount).fill(ARBITRUM_MESSAGES_ITEM) }, - }, - }); - - const [ num, setNum ] = useGradualIncrement(0); - const [ showSocketErrorAlert, setShowSocketErrorAlert ] = React.useState(false); - - const handleSocketClose = React.useCallback(() => { - setShowSocketErrorAlert(true); - }, []); - - const handleSocketError = React.useCallback(() => { - setShowSocketErrorAlert(true); - }, []); - - const handleNewDepositMessage: SocketMessage.NewArbitrumDeposits['handler'] = React.useCallback((payload) => { - setNum(payload.new_messages_to_rollup_amount); - }, [ setNum ]); - - const channel = useSocketChannel({ - topic: 'arbitrum:new_messages_to_rollup_amount', - onSocketClose: handleSocketClose, - onSocketError: handleSocketError, - isDisabled: false, - }); - - useSocketMessage({ - channel, - event: 'new_messages_to_rollup_amount', - handler: handleNewDepositMessage, - }); - - if (isError) { - return ; - } - - if (data) { - return ( - ( - { - l1BlockNumber: item.origination_transaction_block_number, - l1TxHash: item.origination_transaction_hash, - l2TxHash: item.completion_transaction_hash, - timestamp: item.origination_timestamp, - } - )) } - isLoading={ isPlaceholderData } - socketItemsNum={ num } - showSocketErrorAlert={ showSocketErrorAlert } - /> - ); - } - - return No latest deposits found.; -}; - -export default LatestArbitrumDeposits; diff --git a/client/features/rollup/arbitrum/pages/home/LatestArbitrumL2Batches.pw.tsx b/client/features/rollup/arbitrum/pages/home/LatestArbitrumL2Batches.pw.tsx deleted file mode 100644 index 8461d85e790..00000000000 --- a/client/features/rollup/arbitrum/pages/home/LatestArbitrumL2Batches.pw.tsx +++ /dev/null @@ -1,15 +0,0 @@ -import React from 'react'; - -import { ENVS_MAP } from 'playwright/fixtures/mockEnvs'; -import { test, expect } from 'playwright/lib'; - -import { finalized, unfinalized } from '../../mocks/txn-batches'; -import LatestArbitrumL2Batches from './LatestArbitrumL2Batches'; - -test('default view +@mobile +@dark-mode', async({ render, mockEnvs, mockApiResponse }) => { - await mockEnvs(ENVS_MAP.arbitrumRollup); - await mockApiResponse('general:homepage_arbitrum_l2_batches', { items: [ finalized, unfinalized ] }); - - const component = await render(); - await expect(component).toHaveScreenshot(); -}); diff --git a/client/features/rollup/arbitrum/pages/home/LatestArbitrumL2Batches.tsx b/client/features/rollup/arbitrum/pages/home/LatestArbitrumL2Batches.tsx deleted file mode 100644 index b3c875a0372..00000000000 --- a/client/features/rollup/arbitrum/pages/home/LatestArbitrumL2Batches.tsx +++ /dev/null @@ -1,105 +0,0 @@ -// SPDX-License-Identifier: LicenseRef-Blockscout - -import { Box, Flex, VStack } from '@chakra-ui/react'; -import { useQueryClient } from '@tanstack/react-query'; -// import { AnimatePresence } from 'framer-motion'; -import React from 'react'; - -import type { ArbitrumL2TxnBatchesItem } from '../../types/api'; -import type { SocketMessage } from 'client/api/socket/types'; - -import { route } from 'nextjs-routes'; - -import useApiQuery, { getResourceKey } from 'client/api/hooks/useApiQuery'; -import useSocketChannel from 'client/api/socket/useSocketChannel'; -import useSocketMessage from 'client/api/socket/useSocketMessage'; - -import LatestBlocksFallback from 'client/slices/home/pages/index/blocks/LatestBlocksFallback'; - -import useIsMobile from 'client/shared/hooks/useIsMobile'; -import useInitialList from 'client/shared/lists/useInitialList'; - -import { Heading } from 'toolkit/chakra/heading'; -import { Link } from 'toolkit/chakra/link'; - -import { ARBITRUM_L2_TXN_BATCHES_ITEM } from '../../stubs'; -import LatestBatchItem from './LatestBatchItem'; - -const LatestArbitrumL2Batches = () => { - const isMobile = useIsMobile(); - const batchesMaxCount = isMobile ? 2 : 6; - const queryClient = useQueryClient(); - - const { data, isPlaceholderData, isError } = useApiQuery('general:homepage_arbitrum_l2_batches', { - queryOptions: { - placeholderData: { items: Array(batchesMaxCount).fill(ARBITRUM_L2_TXN_BATCHES_ITEM) }, - }, - }); - - const initialList = useInitialList({ - data: data?.items ?? [], - idFn: (batch) => batch.number, - enabled: !isPlaceholderData, - }); - - const handleNewBatchMessage: SocketMessage.NewArbitrumL2Batch['handler'] = React.useCallback((payload) => { - queryClient.setQueryData(getResourceKey('general:homepage_arbitrum_l2_batches'), (prevData: { items: Array } | undefined) => { - const newItems = prevData?.items ? [ ...prevData.items ] : []; - - if (newItems.some((batch => batch.number === payload.batch.number))) { - return { items: newItems }; - } - - return { items: [ payload.batch, ...newItems ].sort((b1, b2) => b2.number - b1.number).slice(0, batchesMaxCount) }; - }); - }, [ queryClient, batchesMaxCount ]); - - const channel = useSocketChannel({ - topic: 'arbitrum:new_batch', - isDisabled: isPlaceholderData || isError, - }); - useSocketMessage({ - channel, - event: 'new_arbitrum_batch', - handler: handleNewBatchMessage, - }); - - const content = (() => { - if (isError) { - return ; - } - if (data && data.items.length > 0) { - const dataToShow = data.items.slice(0, batchesMaxCount); - - return ( - <> - - { dataToShow.map(((batch, index) => ( - - ))) } - - - View all batches - - - ); - } - return No latest batches found.; - })(); - - return ( - - Latest batches - { content } - - ); -}; - -export default LatestArbitrumL2Batches; diff --git a/client/features/rollup/arbitrum/pages/txn-withdrawals/ArbitrumL2TxnWithdrawalsClaimButton.tsx b/client/features/rollup/arbitrum/pages/txn-withdrawals/ArbitrumL2TxnWithdrawalsClaimButton.tsx deleted file mode 100644 index 593c3ef8695..00000000000 --- a/client/features/rollup/arbitrum/pages/txn-withdrawals/ArbitrumL2TxnWithdrawalsClaimButton.tsx +++ /dev/null @@ -1,144 +0,0 @@ -// SPDX-License-Identifier: LicenseRef-Blockscout - -import { useQueryClient } from '@tanstack/react-query'; -import React from 'react'; -import { useSendTransaction, useSwitchChain } from 'wagmi'; - -import type { ArbitrumL2MessageClaimResponse, ArbitrumL2TxnWithdrawalsResponse } from '../../types/api'; - -import useApiFetch from 'client/api/hooks/useApiFetch'; -import { getResourceKey } from 'client/api/hooks/useApiQuery'; -import type { ResourceError } from 'client/api/resources'; - -import useWallet from 'client/features/connect-wallet/hooks/useWallet'; - -import getErrorMessage from 'client/shared/errors/get-error-message'; -import getErrorObjPayload from 'client/shared/errors/get-error-obj-payload'; -import getErrorProp from 'client/shared/errors/get-error-prop'; -import capitalizeFirstLetter from 'client/shared/text/capitalize-first-letter'; - -import config from 'configs/app'; -import { Button } from 'toolkit/chakra/button'; -import { Skeleton } from 'toolkit/chakra/skeleton'; -import { toaster } from 'toolkit/chakra/toaster'; - -import ArbitrumL2TxnWithdrawalsClaimTx from './ArbitrumL2TxnWithdrawalsClaimTx'; - -const rollupFeature = config.features.rollup; - -interface Props { - messageId: number; - txHash: string | undefined; - completionTxHash?: string; - isLoading?: boolean; -} - -const ArbitrumL2TxnWithdrawalsClaimButton = ({ messageId, txHash, completionTxHash, isLoading: isDataLoading }: Props) => { - const [ isPending, setIsPending ] = React.useState(false); - const [ claimTxHash, setClaimTxHash ] = React.useState(completionTxHash); - const apiFetch = useApiFetch(); - - const { sendTransactionAsync } = useSendTransaction(); - const { switchChainAsync } = useSwitchChain(); - const queryClient = useQueryClient(); - - const sendClaimTx = React.useCallback(async() => { - - if (!rollupFeature.isEnabled) { - return; - } - - try { - setIsPending(true); - - const response = await apiFetch<'general:arbitrum_l2_message_claim', ArbitrumL2MessageClaimResponse, ResourceError>( - 'general:arbitrum_l2_message_claim', - { pathParams: { id: messageId.toString() }, - }); - - if ('calldata' in response) { - await switchChainAsync({ chainId: Number(rollupFeature.parentChain.id) }); - - const hash = await sendTransactionAsync({ - data: response.calldata as `0x${ string }`, - to: response.outbox_address_hash as `0x${ string }`, - }); - - setClaimTxHash(hash); - } - } catch (error) { - const apiError = getErrorObjPayload<{ message: string }>(error); - const message = capitalizeFirstLetter(apiError?.message || getErrorProp(error, 'shortMessage') || getErrorMessage(error) || 'Something went wrong'); - toaster.error({ - title: 'Error', - description: message, - }); - setIsPending(false); - } - }, [ apiFetch, messageId, sendTransactionAsync, switchChainAsync ]); - - const web3Wallet = useWallet({ source: 'Smart contracts', onConnect: sendClaimTx }); - - const handleClaimClick = React.useCallback(async() => { - if (!web3Wallet.address) { - web3Wallet.connect(); - } else { - sendClaimTx(); - } - }, [ sendClaimTx, web3Wallet ]); - - const handleSuccess = React.useCallback(() => { - queryClient.setQueryData( - getResourceKey('general:arbitrum_l2_txn_withdrawals', { pathParams: { hash: txHash } }), - (prevData: ArbitrumL2TxnWithdrawalsResponse | undefined) => { - if (!prevData) { - return; - } - - const newItems = prevData.items.map(item => item.id === messageId ? { ...item, status: 'relayed' } : item); - - return { - ...prevData, - items: newItems, - }; - }); - setIsPending(false); - }, [ messageId, queryClient, txHash ]); - - const handleError = React.useCallback((error: Error) => { - toaster.error({ - title: 'Error', - description: error.message, - }); - setIsPending(false); - }, [ ]); - - if (claimTxHash) { - return ( - - ); - } - - const isLoading = isPending || web3Wallet.isOpen; - - return ( - - - - ); -}; - -export default React.memo(ArbitrumL2TxnWithdrawalsClaimButton); diff --git a/client/features/rollup/arbitrum/pages/txn-withdrawals/ArbitrumL2TxnWithdrawalsValue.tsx b/client/features/rollup/arbitrum/pages/txn-withdrawals/ArbitrumL2TxnWithdrawalsValue.tsx deleted file mode 100644 index a19963f7c27..00000000000 --- a/client/features/rollup/arbitrum/pages/txn-withdrawals/ArbitrumL2TxnWithdrawalsValue.tsx +++ /dev/null @@ -1,55 +0,0 @@ -// SPDX-License-Identifier: LicenseRef-Blockscout - -import React from 'react'; - -import type { ArbitrumL2TxnWithdrawalsItem } from '../../types/api'; -import type { TokenInfo } from 'client/slices/token/types/api'; - -import { Skeleton } from 'toolkit/chakra/skeleton'; -import NativeCoinValue from 'ui/shared/value/NativeCoinValue'; -import TokenValue from 'ui/shared/value/TokenValue'; - -interface Props { - data: ArbitrumL2TxnWithdrawalsItem; - loading?: boolean; -} - -const ArbitrumL2TxnWithdrawalsValue = ({ data, loading }: Props) => { - - if (data.token) { - const token: TokenInfo | null = { - ...data.token, - decimals: String(data.token.decimals), - type: 'ERC-20', - holders_count: null, - exchange_rate: null, - total_supply: null, - circulating_market_cap: null, - icon_url: null, - reputation: null, - }; - - return ( - - ); - } - - if (data.callvalue && data.callvalue !== '0') { - return ( - - ); - } - - return -; -}; - -export default React.memo(ArbitrumL2TxnWithdrawalsValue); diff --git a/client/features/rollup/arbitrum/pages/withdrawals/ArbitrumL2Withdrawals.pw.tsx b/client/features/rollup/arbitrum/pages/withdrawals/ArbitrumL2Withdrawals.pw.tsx deleted file mode 100644 index 1a1d884f1b1..00000000000 --- a/client/features/rollup/arbitrum/pages/withdrawals/ArbitrumL2Withdrawals.pw.tsx +++ /dev/null @@ -1,19 +0,0 @@ -import React from 'react'; - -import { ENVS_MAP } from 'playwright/fixtures/mockEnvs'; -import { test, expect } from 'playwright/lib'; - -import * as depositsMock from '../../mocks/withdrawals'; -import ArbitrumL2Withdrawals from './ArbitrumL2Withdrawals'; - -test('base view +@mobile', async({ render, mockApiResponse, mockEnvs, mockTextAd }) => { - test.slow(); - await mockTextAd(); - await mockEnvs(ENVS_MAP.arbitrumRollup); - await mockApiResponse('general:arbitrum_l2_messages', depositsMock.baseResponse, { pathParams: { direction: 'from-rollup' } }); - await mockApiResponse('general:arbitrum_l2_messages_count', 3971111, { pathParams: { direction: 'from-rollup' } }); - - const component = await render(); - - await expect(component).toHaveScreenshot({ timeout: 10_000 }); -}); diff --git a/client/features/rollup/arbitrum/pages/withdrawals/__screenshots__/ArbitrumL2Withdrawals.pw.tsx_default_base-view-mobile-1.png b/client/features/rollup/arbitrum/pages/withdrawals/__screenshots__/ArbitrumL2Withdrawals.pw.tsx_default_base-view-mobile-1.png deleted file mode 100644 index 93259c47c15..00000000000 Binary files a/client/features/rollup/arbitrum/pages/withdrawals/__screenshots__/ArbitrumL2Withdrawals.pw.tsx_default_base-view-mobile-1.png and /dev/null differ diff --git a/client/features/rollup/arbitrum/pages/withdrawals/__screenshots__/ArbitrumL2Withdrawals.pw.tsx_mobile_base-view-mobile-1.png b/client/features/rollup/arbitrum/pages/withdrawals/__screenshots__/ArbitrumL2Withdrawals.pw.tsx_mobile_base-view-mobile-1.png deleted file mode 100644 index 31af2f3a114..00000000000 Binary files a/client/features/rollup/arbitrum/pages/withdrawals/__screenshots__/ArbitrumL2Withdrawals.pw.tsx_mobile_base-view-mobile-1.png and /dev/null differ diff --git a/client/features/rollup/arbitrum/stubs.ts b/client/features/rollup/arbitrum/stubs.ts deleted file mode 100644 index 415111945ce..00000000000 --- a/client/features/rollup/arbitrum/stubs.ts +++ /dev/null @@ -1,52 +0,0 @@ -import type { ArbitrumL2TxnBatchesItem, ArbitrumL2TxnBatch, ArbitrumL2MessagesItem, ArbitrumL2TxnWithdrawalsItem } from './types/api'; - -import { ADDRESS_HASH } from 'client/slices/address/stubs/address-params'; -import { TX_HASH } from 'client/slices/tx/stubs/tx'; - -export const ARBITRUM_MESSAGES_ITEM: ArbitrumL2MessagesItem = { - completion_transaction_hash: TX_HASH, - id: 181920, - origination_address_hash: ADDRESS_HASH, - origination_transaction_block_number: 123456, - origination_transaction_hash: TX_HASH, - origination_timestamp: '2023-06-01T14:46:48.000000Z', - status: 'relayed', -}; - -export const ARBITRUM_L2_TXN_BATCHES_ITEM: ArbitrumL2TxnBatchesItem = { - number: 12345, - blocks_count: 12345, - transactions_count: 10000, - commitment_transaction: { - block_number: 12345, - timestamp: '2024-04-17T08:51:58.000000Z', - hash: TX_HASH, - status: 'finalized', - }, - batch_data_container: 'in_blob4844', -}; - -export const ARBITRUM_L2_TXN_BATCH: ArbitrumL2TxnBatch = { - ...ARBITRUM_L2_TXN_BATCHES_ITEM, - after_acc_hash: '0xcd064f3409015e8e6407e492e5275a185e492c6b43ccf127f22092d8057a9ffb', - before_acc_hash: '0x2ed7c4985eb778d76ec400a43805e7feecc8c2afcdb492dbe5caf227de6d37bc', - start_block_number: 1245209, - end_block_number: 1245490, - data_availability: { - batch_data_container: 'in_blob4844', - }, -}; - -export const ARBITRUM_L2_TXN_WITHDRAWALS_ITEM: ArbitrumL2TxnWithdrawalsItem = { - arb_block_number: 70889261, - caller_address_hash: '0x507f55d716340fc836ba52c1a8daebcfeedeef1a', - completion_transaction_hash: null, - callvalue: '100000000000000', - data: '0x', - destination_address_hash: '0x507f55d716340fc836ba52c1a8daebcfeedeef1a', - eth_block_number: 6494128, - id: 43685, - l2_timestamp: 1723578569, - status: 'relayed', - token: null, -}; diff --git a/client/features/rollup/arbitrum/types/api.ts b/client/features/rollup/arbitrum/types/api.ts deleted file mode 100644 index 5045ee52881..00000000000 --- a/client/features/rollup/arbitrum/types/api.ts +++ /dev/null @@ -1,194 +0,0 @@ -// SPDX-License-Identifier: LicenseRef-Blockscout - -import type { Block } from 'client/slices/block/types/api'; -import type { Transaction } from 'client/slices/tx/types/api'; - -export interface ArbitrumLatestDepositsItem { - completion_transaction_hash: string; - origination_timestamp: string | null; - origination_transaction_block_number: number | null; - origination_transaction_hash: string | null; -} - -export interface ArbitrumLatestDepositsResponse { - items: Array; -} - -export type ArbitrumL2MessageStatus = 'initiated' | 'sent' | 'confirmed' | 'relayed'; - -export type ArbitrumL2MessagesItem = { - completion_transaction_hash: string | null; - id: number; - origination_address_hash: string; - origination_timestamp: string | null; - origination_transaction_block_number: number | null; - origination_transaction_hash: string; - status: ArbitrumL2MessageStatus; -}; - -export type ArbitrumL2MessagesResponse = { - items: Array; - next_page_params: { - direction: string; - id: number; - items_count: number; - }; -}; - -export type ArbitrumL2TxData = { - hash: string | null; - status: string | null; - timestamp: string | null; -}; - -type ArbitrumL2BatchCommitmentTx = { - block_number: number; - hash: string; - status: string; - timestamp: string; -}; - -type BatchDataContainer = 'in_blob4844' | 'in_calldata' | 'in_anytrust' | 'in_celestia' | null; - -export type ArbitrumL2TxnBatchesItem = { - blocks_count: number; - commitment_transaction: ArbitrumL2BatchCommitmentTx; - number: number; - transactions_count: number; - batch_data_container: BatchDataContainer; -}; - -export type ArbitrumL2TxnBatchesResponse = { - items: Array; - next_page_params: { - number: number; - items_count: number; - } | null; -}; - -export type ArbitrumL2TxnBatchDAAnytrust = { - batch_data_container: 'in_anytrust'; - bls_signature: string; - data_hash: string; - timeout: string; - signers: Array<{ - key: string; - trusted: boolean; - proof?: string; - }>; -}; - -export type ArbitrumL2TxnBatchDACelestia = { - batch_data_container: 'in_celestia'; - height: number; - transaction_commitment: string; -}; - -export type ArbitrumL2TxnBatchDataAvailability = ArbitrumL2TxnBatchDAAnytrust | ArbitrumL2TxnBatchDACelestia | { - batch_data_container: Exclude; -}; - -export type ArbitrumL2TxnBatch = { - after_acc_hash: string; - before_acc_hash: string; - commitment_transaction: ArbitrumL2BatchCommitmentTx; - end_block_number: number; - start_block_number: number; - number: number; - transactions_count: number; - data_availability: ArbitrumL2TxnBatchDataAvailability; -}; - -export type ArbitrumL2BatchTxs = { - items: Array; - next_page_params: { - batch_number: string; - block_number: number; - index: number; - items_count: number; - } | null; -}; - -export type ArbitrumL2BatchBlocks = { - items: Array; - next_page_params: { - batch_number: string; - block_number: number; - items_count: number; - } | null; -}; - -export interface ArbitrumL2TxnWithdrawalsItem { - arb_block_number: number; - caller_address_hash: string; - callvalue: string; - completion_transaction_hash: string | null; - data: string; - destination_address_hash: string; - eth_block_number: number; - id: number; - l2_timestamp: number; - status: ArbitrumL2MessageStatus; - token: { - address_hash: string; - amount: string | null; - destination_address_hash: string | null; - name: string | null; - symbol: string | null; - decimals: number | null; - } | null; -} - -export interface ArbitrumL2TxnWithdrawalsResponse { - items: Array; -} - -export interface ArbitrumL2MessageClaimResponse { - calldata: string; - outbox_address_hash: string; -} - -export const ARBITRUM_L2_TX_BATCH_STATUSES = [ - 'Processed on rollup' as const, - 'Sent to base' as const, - 'Confirmed on base' as const, -]; - -export type ArbitrumBatchStatus = typeof ARBITRUM_L2_TX_BATCH_STATUSES[number]; - -export type NewArbitrumBatchSocketResponse = { batch: ArbitrumL2TxnBatchesItem }; - -export type ArbitrumTransactionMessageStatus = 'Relayed' | 'Syncing with base layer' | 'Waiting for confirmation' | 'Ready for relay' | 'Settlement pending'; - -export interface TransactionArbitrum { - arbitrum?: { - batch_number: number; - commitment_transaction: ArbitrumL2TxData; - confirmation_transaction: ArbitrumL2TxData; - contains_message: 'incoming' | 'outcoming' | null; - gas_used_for_l1: string; - gas_used_for_l2: string; - network_fee: string; - poster_fee: string; - status: ArbitrumBatchStatus; - message_related_info: { - associated_l1_transaction_hash: string | null; - message_status: ArbitrumTransactionMessageStatus; - }; - }; -} - -export interface BlockArbitrum { - arbitrum?: ArbitrumBlockData; -} - -export type ArbitrumBlockData = { - batch_number: number; - commitment_transaction: ArbitrumL2TxData; - confirmation_transaction: ArbitrumL2TxData; - delayed_messages: number; - l1_block_number: number; - send_count: number | null; - send_root: string; - status: ArbitrumBatchStatus; -}; diff --git a/client/features/rollup/arbitrum/utils/batch-verification.ts b/client/features/rollup/arbitrum/utils/batch-verification.ts deleted file mode 100644 index 2507e9a2090..00000000000 --- a/client/features/rollup/arbitrum/utils/batch-verification.ts +++ /dev/null @@ -1,45 +0,0 @@ -// SPDX-License-Identifier: LicenseRef-Blockscout - -import { ARBITRUM_L2_TX_BATCH_STATUSES, type ArbitrumBatchStatus, type ArbitrumL2TxData } from '../types/api'; - -import config from 'configs/app'; - -const rollupFeature = config.features.rollup; - -type Args = { - status: ArbitrumBatchStatus; - commitment_transaction: ArbitrumL2TxData; - confirmation_transaction: ArbitrumL2TxData; -}; - -const parentChainName = rollupFeature.isEnabled ? rollupFeature.parentChain.name : undefined; - -export const VERIFICATION_STEPS_MAP: Record = { - 'Processed on rollup': 'Processed on rollup', - 'Sent to base': parentChainName ? `Sent to ${ parentChainName }` : 'Sent to parent chain', - 'Confirmed on base': parentChainName ? - `Confirmed on ${ parentChainName }` : - 'Confirmed on parent chain', -}; - -export const verificationSteps = (() => { - return ARBITRUM_L2_TX_BATCH_STATUSES.map((status) => VERIFICATION_STEPS_MAP[status]); -})(); - -export function getVerificationStepStatus({ - status, - commitment_transaction: commitTx, - confirmation_transaction: confirmTx, -}: Args) { - if (status === 'Sent to base') { - if (commitTx.status === 'unfinalized') { - return 'pending'; - } - } - if (status === 'Confirmed on base') { - if (confirmTx.status === 'unfinalized') { - return 'pending'; - } - } - return 'finalized'; -} diff --git a/client/features/rollup/common/mocks/tx.ts b/client/features/rollup/common/mocks/tx.ts deleted file mode 100644 index 599898103f3..00000000000 --- a/client/features/rollup/common/mocks/tx.ts +++ /dev/null @@ -1,12 +0,0 @@ -import type { Transaction } from 'client/slices/tx/types/api'; - -import { base } from 'client/slices/tx/mocks/tx'; - -export const l2tx: Transaction = { - ...base, - l1_gas_price: '82702201886', - l1_fee_scalar: '1.0', - l1_gas_used: '17060', - l1_fee: '1584574188135760', - operator_fee: '2769347953', -}; diff --git a/client/features/rollup/optimism/components/OptimisticL2ClaimButton.tsx b/client/features/rollup/optimism/components/OptimisticL2ClaimButton.tsx deleted file mode 100644 index d0332e2f30e..00000000000 --- a/client/features/rollup/optimism/components/OptimisticL2ClaimButton.tsx +++ /dev/null @@ -1,88 +0,0 @@ -// SPDX-License-Identifier: LicenseRef-Blockscout - -import React from 'react'; - -import type { OptimisticL2WithdrawalClaimInfo } from 'client/features/rollup/optimism/types/api'; -import type { AddressParam } from 'client/slices/address/types/api'; - -import { parentChain } from 'client/features/connect-wallet/utils/chains'; -import OptimisticL2ClaimModal from 'client/features/rollup/optimism/components/OptimisticL2ClaimModal'; - -import config from 'configs/app'; -import { Button } from 'toolkit/chakra/button'; -import { Link } from 'toolkit/chakra/link'; -import { useDisclosure } from 'toolkit/hooks/useDisclosure'; - -const rollupFeature = config.features.rollup; - -export const canClaimDirectlyGuard = (data: OptimisticL2WithdrawalClaimInfo) => { - return ( - config.features.blockchainInteraction.isEnabled && - Boolean(parentChain) && - data.portal_contract_address_hash !== null && - data.msg_sender_address_hash !== null && - data.msg_target_address_hash !== null && - data.msg_data !== null && - data.msg_gas_limit !== null && - data.msg_nonce_raw !== null && - data.msg_value !== null - ); -}; - -interface Props { - data: OptimisticL2WithdrawalClaimInfo; - from: AddressParam | null; - onSuccess: (txHash: string) => void; - source: 'list' | 'tx'; -} - -const OptimisticL2ClaimButton = ({ data, from, onSuccess, source }: Props) => { - - const modal = useDisclosure(); - - if (canClaimDirectlyGuard(data)) { - return ( - <> - { modal.open && ( - - ) } - - - ); - } - - if (!rollupFeature.isEnabled || !rollupFeature.L2WithdrawalUrl) { - if (source === 'list') { - return 'Ready for relay'; - } - return null; - } - - if (source === 'list') { - return ( - - Ready for relay - - ); - } - - return ( - - - - ); -}; - -export default React.memo(OptimisticL2ClaimButton); diff --git a/client/features/rollup/optimism/components/TxnBatchDA.tsx b/client/features/rollup/optimism/components/TxnBatchDA.tsx deleted file mode 100644 index 6381823ff7b..00000000000 --- a/client/features/rollup/optimism/components/TxnBatchDA.tsx +++ /dev/null @@ -1,42 +0,0 @@ -// SPDX-License-Identifier: LicenseRef-Blockscout - -import React from 'react'; - -import type { OptimisticL2TxnBatchesItem } from 'client/features/rollup/optimism/types/api'; -import type { ExcludeUndefined } from 'types/utils'; - -import type { BadgeProps } from 'toolkit/chakra/badge'; -import { Badge } from 'toolkit/chakra/badge'; - -export interface Props extends BadgeProps { - container: ExcludeUndefined; - isLoading?: boolean; -} - -const OptimisticL2TxnBatchDA = ({ container, isLoading, ...rest }: Props) => { - - const text = (() => { - switch (container) { - case 'in_blob4844': - return 'EIP-4844 blob'; - case 'in_calldata': - return 'Calldata'; - case 'in_celestia': - return 'Celestia blob'; - case 'in_eigenda': - return 'EigenDA'; - } - })(); - - if (!text) { - return null; - } - - return ( - - { text } - - ); -}; - -export default React.memo(OptimisticL2TxnBatchDA); diff --git a/client/features/rollup/optimism/mocks/txn-batches.ts b/client/features/rollup/optimism/mocks/txn-batches.ts deleted file mode 100644 index 61e7a8b6fb5..00000000000 --- a/client/features/rollup/optimism/mocks/txn-batches.ts +++ /dev/null @@ -1,124 +0,0 @@ -import type { - OptimismL2TxnBatchTypeCallData, - OptimismL2TxnBatchTypeCelestia, - OptimismL2TxnBatchTypeEigenda, - OptimismL2TxnBatchTypeEip4844, - OptimisticL2TxnBatchesResponse, -} from 'client/features/rollup/optimism/types/api'; - -export const txnBatchesData: OptimisticL2TxnBatchesResponse = { - items: [ - { - batch_data_container: 'in_blob4844', - number: 260998, - l1_timestamp: '2022-11-10T11:29:11.000000Z', - l1_transaction_hashes: [ - '0x9553351f6bd1577f4e782738c087be08697fb11f3b91745138d71ba166d62c3b', - ], - l2_end_block_number: 124882074, - l2_start_block_number: 124881833, - transactions_count: 4011, - }, - { - batch_data_container: 'in_calldata', - number: 260997, - l1_timestamp: '2022-11-03T11:20:59.000000Z', - l1_transaction_hashes: [ - '0x80f5fba70d5685bc2b70df836942e892b24afa7bba289a2fac0ca8f4d554cc72', - ], - l2_end_block_number: 124881832, - l2_start_block_number: 124881613, - transactions_count: 4206, - }, - { - number: 260996, - l1_timestamp: '2024-09-03T11:14:23.000000Z', - l1_transaction_hashes: [ - '0x39f4c46cae57bae936acb9159e367794f41f021ed3788adb80ad93830edb5f22', - ], - l2_end_block_number: 124881612, - l2_start_block_number: 124881380, - transactions_count: 4490, - }, - ], - next_page_params: { - id: 5902834, - items_count: 50, - }, -}; - -export const txnBatchTypeCallData: OptimismL2TxnBatchTypeCallData = { - batch_data_container: 'in_calldata', - number: 309123, - l1_timestamp: '2022-08-10T10:30:24.000000Z', - l1_transaction_hashes: [ - '0x478c45f182631ae6f7249d40f31fdac36f41d88caa2e373fba35340a7345ca67', - ], - l2_end_block_number: 10146784, - l2_start_block_number: 10145379, - transactions_count: 1608, -}; - -export const txnBatchTypeCelestia: OptimismL2TxnBatchTypeCelestia = { - batch_data_container: 'in_celestia', - blobs: [ - { - commitment: '0x39c18c21c6b127d58809b8d3b5931472421f9b51532959442f53038f10b78f2a', - height: 2584868, - l1_timestamp: '2024-08-28T16:51:12.000000Z', - l1_transaction_hash: '0x2bb0b96a8ba0f063a243ac3dee0b2f2d87edb2ba9ef44bfcbc8ed191af1c4c24', - namespace: '0x00000000000000000000000000000000000000000008e5f679bf7116cb', - }, - ], - number: 309667, - l1_timestamp: '2022-08-28T16:51:12.000000Z', - l1_transaction_hashes: [ - '0x2bb0b96a8ba0f063a243ac3dee0b2f2d87edb2ba9ef44bfcbc8ed191af1c4c24', - ], - l2_end_block_number: 10935879, - l2_start_block_number: 10934514, - transactions_count: 1574, -}; - -export const txnBatchTypeEip4844: OptimismL2TxnBatchTypeEip4844 = { - batch_data_container: 'in_blob4844', - blobs: [ - { - hash: '0x012a4f0c6db6bce9d3d357b2bf847764320bcb0107ab318f3a532f637bc60dfe', - l1_timestamp: '2022-08-23T03:59:12.000000Z', - l1_transaction_hash: '0x3870f136497e5501dc20d0974daf379c8636c958794d59a9c90d4f8a9f0ed20a', - }, - { - hash: '0x01d1097cce23229931afbc2fd1cf0d707da26df7b39cef1c542276ae718de4f6', - l1_timestamp: '2022-08-23T03:59:12.000000Z', - l1_transaction_hash: '0x3870f136497e5501dc20d0974daf379c8636c958794d59a9c90d4f8a9f0ed20a', - }, - ], - number: 2538459, - l1_timestamp: '2022-08-23T03:59:12.000000Z', - l1_transaction_hashes: [ - '0x3870f136497e5501dc20d0974daf379c8636c958794d59a9c90d4f8a9f0ed20a', - ], - l2_end_block_number: 16291502, - l2_start_block_number: 16291373, - transactions_count: 704, -}; - -export const txnBatchTypeEigenda: OptimismL2TxnBatchTypeEigenda = { - number: 8930, - transactions_count: 9048, - l1_timestamp: '2022-08-23T03:59:12.000000Z', - l1_transaction_hashes: [ - '0x2f4adadafec74aa938e60f26ad13fa8dc921c3a43f8760a88cf66c23064bc7ce', - ], - blobs: [ - { - cert: '0x02f9083ae6a024f087e05e88e8b241b9a7818257250c22af1edda43', - l1_timestamp: '2026-01-12T12:46:23.000000Z', - l1_transaction_hash: '0x2f4adadafec74aa938e60f26ad13fa8dc921c3a43f8760a88cf66c23064bc7ce', - }, - ], - batch_data_container: 'in_eigenda', - l2_end_block_number: 5424956, - l2_start_block_number: 5424574, -}; diff --git a/client/features/rollup/optimism/mocks/withdrawals.ts b/client/features/rollup/optimism/mocks/withdrawals.ts deleted file mode 100644 index 3e342b56569..00000000000 --- a/client/features/rollup/optimism/mocks/withdrawals.ts +++ /dev/null @@ -1,88 +0,0 @@ -import type { OptimisticL2WithdrawalsResponse } from 'client/features/rollup/optimism/types/api'; - -export const data: OptimisticL2WithdrawalsResponse = { - items: [ - { - challenge_period_end: null, - from: { - hash: '0x67aab90c548b284be30b05c376001b4db90b87ba', - implementations: null, - is_contract: false, - is_verified: false, - name: null, - private_tags: [], - public_tags: [], - watchlist_names: [], - ens_domain_name: null, - }, - l1_transaction_hash: '0x1a235bee32ac10cb7efdad98415737484ca66386e491cde9e17d42b136dca684', - l2_timestamp: '2022-02-15T12:50:02.000000Z', - l2_transaction_hash: '0x918cd8c5c24c17e06cd02b0379510c4ad56324bf153578fb9caaaa2fe4e7dc35', - msg_nonce: 396, - msg_nonce_version: 1, - status: 'Ready to prove', - portal_contract_address_hash: null, - msg_sender_address_hash: null, - msg_target_address_hash: null, - msg_data: null, - msg_gas_limit: null, - msg_nonce_raw: null, - msg_value: null, - }, - { - challenge_period_end: null, - from: null, - l1_transaction_hash: null, - l2_timestamp: null, - l2_transaction_hash: '0x2f117bee32ac10cb7efdad98415737484ca66386e491cde9e17d42b136def593', - msg_nonce: 391, - msg_nonce_version: 1, - status: 'Ready to prove', - portal_contract_address_hash: null, - msg_sender_address_hash: null, - msg_target_address_hash: null, - msg_data: null, - msg_gas_limit: null, - msg_nonce_raw: null, - msg_value: null, - }, - { - challenge_period_end: '2022-11-11T12:50:02.000000Z', - from: null, - l1_transaction_hash: null, - l2_timestamp: null, - l2_transaction_hash: '0xe14b1f46838176702244a5343629bcecf728ca2d9881d47b4ce46e00c387d7e3', - msg_nonce: 390, - msg_nonce_version: 1, - status: 'Ready for relay', - portal_contract_address_hash: null, - msg_sender_address_hash: null, - msg_target_address_hash: null, - msg_data: null, - msg_gas_limit: null, - msg_nonce_raw: null, - msg_value: null, - }, - { - challenge_period_end: '2022-11-13T12:50:02.000000Z', - from: null, - l1_transaction_hash: null, - l2_timestamp: null, - l2_transaction_hash: '0xe14b1f46838176702244a5343629bcecf728ca2d9881d47b4ce46e00c387d7aa', - msg_nonce: 388, - msg_nonce_version: 3, - status: 'Ready for relay', - portal_contract_address_hash: '0x67aab90c548b284be30b05c376001b4db90b87ba', - msg_sender_address_hash: '0x67aab90c548b284be30b05c376001b4db90b87ba', - msg_target_address_hash: '0x67aab90c548b284be30b05c376001b4db90b87ba', - msg_data: '0x01', - msg_gas_limit: '42', - msg_nonce_raw: '390', - msg_value: '1000000000000000000', - }, - ], - next_page_params: { - items_count: 50, - nonce: '1766847064778384329583297500742918515827483896875618958121606201292620123', - }, -}; diff --git a/client/features/rollup/optimism/pages/batch-details/OptimisticL2TxnBatch.tsx b/client/features/rollup/optimism/pages/batch-details/OptimisticL2TxnBatch.tsx deleted file mode 100644 index 34ac0b85b72..00000000000 --- a/client/features/rollup/optimism/pages/batch-details/OptimisticL2TxnBatch.tsx +++ /dev/null @@ -1,116 +0,0 @@ -// SPDX-License-Identifier: LicenseRef-Blockscout - -import { useRouter } from 'next/router'; -import React from 'react'; - -import type { TabItemRegular } from 'toolkit/components/AdaptiveTabs/types'; - -import BlocksContent from 'client/slices/block/pages/index/BlocksContent'; -import { BLOCK } from 'client/slices/block/stubs/block'; -import TxsWithFrontendSorting from 'client/slices/tx/pages/index/list/TxsWithFrontendSorting'; -import { TX } from 'client/slices/tx/stubs/tx'; - -import throwOnAbsentParamError from 'client/shared/errors/throw-on-absent-param-error'; -import throwOnResourceLoadError from 'client/shared/errors/throw-on-resource-load-error'; -import useIsMobile from 'client/shared/hooks/useIsMobile'; -import getQueryParamString from 'client/shared/router/get-query-param-string'; - -import { generateListStub } from 'stubs/utils'; -import RoutedTabs from 'toolkit/components/RoutedTabs/RoutedTabs'; -import TextAd from 'ui/shared/ad/TextAd'; -import PageTitle from 'ui/shared/Page/PageTitle'; -import Pagination from 'ui/shared/pagination/Pagination'; -import useQueryWithPages from 'ui/shared/pagination/useQueryWithPages'; - -import OptimisticL2TxnBatchDetails from './OptimisticL2TxnBatchDetails'; -import useBatchQuery from './useBatchQuery'; - -const TAB_LIST_PROPS = { - marginBottom: 0, - py: 5, - marginTop: -5, -}; - -const TABS_HEIGHT = 80; - -const OptimisticL2TxnBatch = () => { - const router = useRouter(); - const number = getQueryParamString(router.query.number); - const height = getQueryParamString(router.query.height); - const commitment = getQueryParamString(router.query.commitment); - const tab = getQueryParamString(router.query.tab); - const isMobile = useIsMobile(); - - const batchQuery = useBatchQuery(); - - const batchTxsQuery = useQueryWithPages({ - resourceName: 'general:optimistic_l2_txn_batch_txs', - pathParams: { number: String(batchQuery.data?.number) }, - options: { - enabled: Boolean(!batchQuery.isPlaceholderData && batchQuery.data?.number && tab === 'txs'), - placeholderData: generateListStub<'general:optimistic_l2_txn_batch_txs'>(TX, 50, { next_page_params: { - block_number: 1338932, - index: 1, - items_count: 50, - } }), - }, - }); - - const batchBlocksQuery = useQueryWithPages({ - resourceName: 'general:optimistic_l2_txn_batch_blocks', - pathParams: { number: String(batchQuery.data?.number) }, - options: { - enabled: Boolean(!batchQuery.isPlaceholderData && batchQuery.data?.number && tab === 'blocks'), - placeholderData: generateListStub<'general:optimistic_l2_txn_batch_blocks'>(BLOCK, 50, { next_page_params: { - batch_number: 1338932, - items_count: 50, - } }), - }, - }); - - throwOnAbsentParamError(number || (height && commitment)); - throwOnResourceLoadError(batchQuery); - - let pagination; - if (tab === 'txs') { - pagination = batchTxsQuery.pagination; - } - if (tab === 'blocks') { - pagination = batchBlocksQuery.pagination; - } - - const hasPagination = !isMobile && pagination?.isVisible; - - const tabs: Array = React.useMemo(() => ([ - { id: 'index', title: 'Details', component: }, - { - id: 'txs', - title: 'Transactions', - component: , - }, - { - id: 'blocks', - title: 'Blocks', - component: , - }, - ].filter(Boolean)), [ batchQuery, batchTxsQuery, batchBlocksQuery, hasPagination ]); - - return ( - <> - - - : null } - stickyEnabled={ hasPagination } - /> - - ); -}; - -export default OptimisticL2TxnBatch; diff --git a/client/features/rollup/optimism/pages/batch-details/OptimisticL2TxnBatchBlobEigenda.tsx b/client/features/rollup/optimism/pages/batch-details/OptimisticL2TxnBatchBlobEigenda.tsx deleted file mode 100644 index 2892f950628..00000000000 --- a/client/features/rollup/optimism/pages/batch-details/OptimisticL2TxnBatchBlobEigenda.tsx +++ /dev/null @@ -1,51 +0,0 @@ -// SPDX-License-Identifier: LicenseRef-Blockscout - -import { Flex, GridItem, VStack } from '@chakra-ui/react'; -import React from 'react'; - -import type { OptimisticL2BlobTypeEigenda } from 'client/features/rollup/optimism/types/api'; - -import TxEntityL1 from 'client/features/rollup/common/components/TxEntityL1'; -import { layerLabels } from 'client/features/rollup/common/utils/layer'; - -import CopyToClipboard from 'ui/shared/CopyToClipboard'; -import DetailedInfoTimestamp from 'ui/shared/DetailedInfo/DetailedInfoTimestamp'; -import HashStringShortenDynamic from 'ui/shared/HashStringShortenDynamic'; - -import OptimisticL2TxnBatchBlobWrapper from './OptimisticL2TxnBatchBlobWrapper'; - -interface Props { - blobs: Array; - isLoading: boolean; -} - -const OptimisticL2TxnBatchBlobEigenda = ({ blobs, isLoading }: Props) => { - return ( - - { blobs.map((blob) => { - return ( - - Cert - - - - - - - Timestamp - - - - { layerLabels.parent } txn hash - - - - - ); - }) } - - - ); -}; - -export default React.memo(OptimisticL2TxnBatchBlobEigenda); diff --git a/client/features/rollup/optimism/pages/batch-details/OptimisticL2TxnBatchBlobEip4844.tsx b/client/features/rollup/optimism/pages/batch-details/OptimisticL2TxnBatchBlobEip4844.tsx deleted file mode 100644 index 311d7e079ae..00000000000 --- a/client/features/rollup/optimism/pages/batch-details/OptimisticL2TxnBatchBlobEip4844.tsx +++ /dev/null @@ -1,47 +0,0 @@ -// SPDX-License-Identifier: LicenseRef-Blockscout - -import { GridItem, VStack } from '@chakra-ui/react'; -import React from 'react'; - -import type { OptimisticL2BlobTypeEip4844 } from 'client/features/rollup/optimism/types/api'; - -import BlobEntityL1 from 'client/features/data-availability/components/entity/BlobEntityL1'; -import TxEntityL1 from 'client/features/rollup/common/components/TxEntityL1'; -import { layerLabels } from 'client/features/rollup/common/utils/layer'; - -import DetailedInfoTimestamp from 'ui/shared/DetailedInfo/DetailedInfoTimestamp'; - -import OptimisticL2TxnBatchBlobWrapper from './OptimisticL2TxnBatchBlobWrapper'; - -interface Props { - blobs: Array; - isLoading: boolean; -} - -const OptimisticL2TxnBatchBlobEip4844 = ({ blobs, isLoading }: Props) => { - return ( - - { blobs.map((blob) => { - return ( - - Versioned hash - - - - Timestamp - - - - { layerLabels.parent } txn hash - - - - - ); - }) } - - - ); -}; - -export default React.memo(OptimisticL2TxnBatchBlobEip4844); diff --git a/client/features/rollup/optimism/pages/batch-details/useBatchQuery.tsx b/client/features/rollup/optimism/pages/batch-details/useBatchQuery.tsx deleted file mode 100644 index 7ebe0173bad..00000000000 --- a/client/features/rollup/optimism/pages/batch-details/useBatchQuery.tsx +++ /dev/null @@ -1,34 +0,0 @@ -// SPDX-License-Identifier: LicenseRef-Blockscout - -import { useRouter } from 'next/router'; - -import useApiQuery from 'client/api/hooks/useApiQuery'; - -import { L2_TXN_BATCH } from 'client/features/rollup/optimism/stubs'; - -import getQueryParamString from 'client/shared/router/get-query-param-string'; - -export default function useBatchQuery() { - const router = useRouter(); - const number = getQueryParamString(router.query.number); - const height = getQueryParamString(router.query.height); - const commitment = getQueryParamString(router.query.commitment); - - const batchByNumberQuery = useApiQuery('general:optimistic_l2_txn_batch', { - pathParams: { number }, - queryOptions: { - enabled: Boolean(number), - placeholderData: L2_TXN_BATCH, - }, - }); - - const batchByHeightQuery = useApiQuery('general:optimistic_l2_txn_batch_celestia', { - pathParams: { height, commitment }, - queryOptions: { - enabled: Boolean(height && commitment), - placeholderData: L2_TXN_BATCH, - }, - }); - - return number ? batchByNumberQuery : batchByHeightQuery; -} diff --git a/client/features/rollup/optimism/pages/batches/OptimisticL2TxnBatches.pw.tsx b/client/features/rollup/optimism/pages/batches/OptimisticL2TxnBatches.pw.tsx deleted file mode 100644 index b303a36f629..00000000000 --- a/client/features/rollup/optimism/pages/batches/OptimisticL2TxnBatches.pw.tsx +++ /dev/null @@ -1,21 +0,0 @@ -import React from 'react'; - -import { txnBatchesData } from 'client/features/rollup/optimism/mocks/txn-batches'; - -import { ENVS_MAP } from 'playwright/fixtures/mockEnvs'; -import { test, expect } from 'playwright/lib'; - -import OptimisticL2TxnBatches from './OptimisticL2TxnBatches'; - -test('base view +@mobile', async({ render, mockTextAd, mockEnvs, mockApiResponse }) => { - // test on mobile is flaky - // my assumption is there is not enough time to calculate hashes truncation so component is unstable - // so I raised the test timeout to check if it helps - test.slow(); - await mockTextAd(); - await mockEnvs(ENVS_MAP.optimisticRollup); - await mockApiResponse('general:optimistic_l2_txn_batches', txnBatchesData); - await mockApiResponse('general:optimistic_l2_txn_batches_count', 1235016); - const component = await render(); - await expect(component).toHaveScreenshot({ timeout: 10_000 }); -}); diff --git a/client/features/rollup/optimism/pages/batches/OptimisticL2TxnBatches.tsx b/client/features/rollup/optimism/pages/batches/OptimisticL2TxnBatches.tsx deleted file mode 100644 index c6868e3bbdd..00000000000 --- a/client/features/rollup/optimism/pages/batches/OptimisticL2TxnBatches.tsx +++ /dev/null @@ -1,92 +0,0 @@ -// SPDX-License-Identifier: LicenseRef-Blockscout - -import { Box, Text } from '@chakra-ui/react'; -import React from 'react'; - -import useApiQuery from 'client/api/hooks/useApiQuery'; - -import OptimisticL2TxnBatchesListItem from 'client/features/rollup/optimism/pages/batches/OptimisticL2TxnBatchesListItem'; -import OptimisticL2TxnBatchesTable from 'client/features/rollup/optimism/pages/batches/OptimisticL2TxnBatchesTable'; -import { L2_TXN_BATCHES_ITEM } from 'client/features/rollup/optimism/stubs'; - -import { generateListStub } from 'stubs/utils'; -import { Skeleton } from 'toolkit/chakra/skeleton'; -import { ACTION_BAR_HEIGHT_DESKTOP } from 'ui/shared/ActionBar'; -import DataListDisplay from 'ui/shared/DataListDisplay'; -import PageTitle from 'ui/shared/Page/PageTitle'; -import useQueryWithPages from 'ui/shared/pagination/useQueryWithPages'; -import StickyPaginationWithText from 'ui/shared/StickyPaginationWithText'; - -const OptimisticL2TxnBatches = () => { - const { data, isError, isPlaceholderData, pagination } = useQueryWithPages({ - resourceName: 'general:optimistic_l2_txn_batches', - options: { - placeholderData: generateListStub<'general:optimistic_l2_txn_batches'>( - L2_TXN_BATCHES_ITEM, - 50, - { - next_page_params: { - items_count: 50, - id: 9045200, - }, - }, - ), - }, - }); - - const countersQuery = useApiQuery('general:optimistic_l2_txn_batches_count', { - queryOptions: { - placeholderData: 5231746, - }, - }); - - const content = data?.items ? ( - <> - - { data.items.map(((item, index) => ( - - ))) } - - - - - - ) : null; - - const text = (() => { - if (countersQuery.isError || isError || !data?.items.length) { - return null; - } - - return ( - - Txn batch - #{ data.items[0].number } to - #{ data.items[data.items.length - 1].number } - (total of { countersQuery.data?.toLocaleString() } batches) - - ); - })(); - - const actionBar = ; - - return ( - <> - - - { content } - - - ); -}; - -export default OptimisticL2TxnBatches; diff --git a/client/features/rollup/optimism/pages/batches/__screenshots__/OptimisticL2TxnBatches.pw.tsx_default_base-view-mobile-1.png b/client/features/rollup/optimism/pages/batches/__screenshots__/OptimisticL2TxnBatches.pw.tsx_default_base-view-mobile-1.png deleted file mode 100644 index f970279c43b..00000000000 Binary files a/client/features/rollup/optimism/pages/batches/__screenshots__/OptimisticL2TxnBatches.pw.tsx_default_base-view-mobile-1.png and /dev/null differ diff --git a/client/features/rollup/optimism/pages/batches/__screenshots__/OptimisticL2TxnBatches.pw.tsx_mobile_base-view-mobile-1.png b/client/features/rollup/optimism/pages/batches/__screenshots__/OptimisticL2TxnBatches.pw.tsx_mobile_base-view-mobile-1.png deleted file mode 100644 index b6686ff66a5..00000000000 Binary files a/client/features/rollup/optimism/pages/batches/__screenshots__/OptimisticL2TxnBatches.pw.tsx_mobile_base-view-mobile-1.png and /dev/null differ diff --git a/client/features/rollup/optimism/pages/deposits/OptimisticDepositsTableItem.tsx b/client/features/rollup/optimism/pages/deposits/OptimisticDepositsTableItem.tsx deleted file mode 100644 index 0fff88dba6a..00000000000 --- a/client/features/rollup/optimism/pages/deposits/OptimisticDepositsTableItem.tsx +++ /dev/null @@ -1,81 +0,0 @@ -// SPDX-License-Identifier: LicenseRef-Blockscout - -import BigNumber from 'bignumber.js'; -import React from 'react'; - -import type { OptimisticL2DepositsItem } from 'client/features/rollup/optimism/types/api'; - -import TxEntity from 'client/slices/tx/components/entity/TxEntity'; - -import AddressEntityL1 from 'client/features/rollup/common/components/AddressEntityL1'; -import BlockEntityL1 from 'client/features/rollup/common/components/BlockEntityL1'; -import TxEntityL1 from 'client/features/rollup/common/components/TxEntityL1'; - -import config from 'configs/app'; -import { Skeleton } from 'toolkit/chakra/skeleton'; -import { TableCell, TableRow } from 'toolkit/chakra/table'; -import TimeWithTooltip from 'ui/shared/time/TimeWithTooltip'; - -const rollupFeature = config.features.rollup; - -type Props = { item: OptimisticL2DepositsItem; isLoading?: boolean }; - -const OptimisticDepositsTableItem = ({ item, isLoading }: Props) => { - - if (!rollupFeature.isEnabled || rollupFeature.type !== 'optimistic') { - return null; - } - - return ( - - - - - - - - - - - - - - - - - - - { BigNumber(item.l2_transaction_gas_limit).toFormat() } - - - - ); -}; - -export default OptimisticDepositsTableItem; diff --git a/client/features/rollup/optimism/pages/deposits/OptimisticL2Deposits.pw.tsx b/client/features/rollup/optimism/pages/deposits/OptimisticL2Deposits.pw.tsx deleted file mode 100644 index 52279cfd37e..00000000000 --- a/client/features/rollup/optimism/pages/deposits/OptimisticL2Deposits.pw.tsx +++ /dev/null @@ -1,23 +0,0 @@ -import React from 'react'; - -import { data as depositsData } from 'client/features/rollup/optimism/mocks/deposits'; - -import { ENVS_MAP } from 'playwright/fixtures/mockEnvs'; -import { test, expect } from 'playwright/lib'; - -import OptimisticL2Deposits from './OptimisticL2Deposits'; - -test('base view +@mobile', async({ render, mockEnvs, mockTextAd, mockApiResponse }) => { - // test on mobile is flaky - // my assumption is there is not enough time to calculate hashes truncation so component is unstable - // so I raised the test timeout to check if it helps - test.slow(); - await mockEnvs(ENVS_MAP.optimisticRollup); - await mockTextAd(); - await mockApiResponse('general:optimistic_l2_deposits', depositsData); - await mockApiResponse('general:optimistic_l2_deposits_count', 3971111); - - const component = await render(); - - await expect(component).toHaveScreenshot({ timeout: 10_000 }); -}); diff --git a/client/features/rollup/optimism/pages/deposits/OptimisticL2Deposits.tsx b/client/features/rollup/optimism/pages/deposits/OptimisticL2Deposits.tsx deleted file mode 100644 index f51a51e04ea..00000000000 --- a/client/features/rollup/optimism/pages/deposits/OptimisticL2Deposits.tsx +++ /dev/null @@ -1,92 +0,0 @@ -// SPDX-License-Identifier: LicenseRef-Blockscout - -import { Box } from '@chakra-ui/react'; -import React from 'react'; - -import useApiQuery from 'client/api/hooks/useApiQuery'; - -import { layerLabels } from 'client/features/rollup/common/utils/layer'; -import OptimisticDepositsListItem from 'client/features/rollup/optimism/pages/deposits/OptimisticDepositsListItem'; -import OptimisticDepositsTable from 'client/features/rollup/optimism/pages/deposits/OptimisticDepositsTable'; -import { L2_DEPOSIT_ITEM } from 'client/features/rollup/optimism/stubs'; - -import { generateListStub } from 'stubs/utils'; -import { Skeleton } from 'toolkit/chakra/skeleton'; -import { rightLineArrow, nbsp } from 'toolkit/utils/htmlEntities'; -import { ACTION_BAR_HEIGHT_DESKTOP } from 'ui/shared/ActionBar'; -import DataListDisplay from 'ui/shared/DataListDisplay'; -import PageTitle from 'ui/shared/Page/PageTitle'; -import useQueryWithPages from 'ui/shared/pagination/useQueryWithPages'; -import StickyPaginationWithText from 'ui/shared/StickyPaginationWithText'; - -const OptimisticL2Deposits = () => { - const { data, isError, isPlaceholderData, pagination } = useQueryWithPages({ - resourceName: 'general:optimistic_l2_deposits', - options: { - placeholderData: generateListStub<'general:optimistic_l2_deposits'>( - L2_DEPOSIT_ITEM, - 50, - { - next_page_params: { - items_count: 50, - l1_block_number: 9045200, - transaction_hash: '', - }, - }, - ), - }, - }); - - const countersQuery = useApiQuery('general:optimistic_l2_deposits_count', { - queryOptions: { - placeholderData: 1927029, - }, - }); - - const content = data?.items ? ( - <> - - { data.items.map(((item, index) => ( - - ))) } - - - - - - ) : null; - - const text = (() => { - if (countersQuery.isError) { - return null; - } - - return ( - - A total of { countersQuery.data?.toLocaleString() } deposits found - - ); - })(); - - const actionBar = ; - - return ( - <> - - - { content } - - - ); -}; - -export default OptimisticL2Deposits; diff --git a/client/features/rollup/optimism/pages/deposits/__screenshots__/OptimisticL2Deposits.pw.tsx_default_base-view-mobile-1.png b/client/features/rollup/optimism/pages/deposits/__screenshots__/OptimisticL2Deposits.pw.tsx_default_base-view-mobile-1.png deleted file mode 100644 index 32722fa48b2..00000000000 Binary files a/client/features/rollup/optimism/pages/deposits/__screenshots__/OptimisticL2Deposits.pw.tsx_default_base-view-mobile-1.png and /dev/null differ diff --git a/client/features/rollup/optimism/pages/deposits/__screenshots__/OptimisticL2Deposits.pw.tsx_mobile_base-view-mobile-1.png b/client/features/rollup/optimism/pages/deposits/__screenshots__/OptimisticL2Deposits.pw.tsx_mobile_base-view-mobile-1.png deleted file mode 100644 index f5a8079af83..00000000000 Binary files a/client/features/rollup/optimism/pages/deposits/__screenshots__/OptimisticL2Deposits.pw.tsx_mobile_base-view-mobile-1.png and /dev/null differ diff --git a/client/features/rollup/optimism/pages/dispute-games/OptimisticL2DisputeGames.pw.tsx b/client/features/rollup/optimism/pages/dispute-games/OptimisticL2DisputeGames.pw.tsx deleted file mode 100644 index 94399da86a9..00000000000 --- a/client/features/rollup/optimism/pages/dispute-games/OptimisticL2DisputeGames.pw.tsx +++ /dev/null @@ -1,20 +0,0 @@ -import React from 'react'; - -import { data as disputeGamesData } from 'client/features/rollup/optimism/mocks/dispute-games'; - -import { ENVS_MAP } from 'playwright/fixtures/mockEnvs'; -import { test, expect } from 'playwright/lib'; - -import OptimisticL2DisputeGames from './OptimisticL2DisputeGames'; - -test('base view +@mobile', async({ render, mockEnvs, mockTextAd, mockApiResponse }) => { - test.slow(); - await mockEnvs(ENVS_MAP.optimisticRollup); - await mockTextAd(); - await mockApiResponse('general:optimistic_l2_dispute_games', disputeGamesData); - await mockApiResponse('general:optimistic_l2_dispute_games_count', 3971111); - - const component = await render(); - - await expect(component).toHaveScreenshot(); -}); diff --git a/client/features/rollup/optimism/pages/dispute-games/OptimisticL2DisputeGames.tsx b/client/features/rollup/optimism/pages/dispute-games/OptimisticL2DisputeGames.tsx deleted file mode 100644 index d5456dafe91..00000000000 --- a/client/features/rollup/optimism/pages/dispute-games/OptimisticL2DisputeGames.tsx +++ /dev/null @@ -1,93 +0,0 @@ -// SPDX-License-Identifier: LicenseRef-Blockscout - -import { Box, Text } from '@chakra-ui/react'; -import React from 'react'; - -import useApiQuery from 'client/api/hooks/useApiQuery'; - -import { L2_DISPUTE_GAMES_ITEM } from 'client/features/rollup/optimism/stubs'; - -import { generateListStub } from 'stubs/utils'; -import { Skeleton } from 'toolkit/chakra/skeleton'; -import { ACTION_BAR_HEIGHT_DESKTOP } from 'ui/shared/ActionBar'; -import DataListDisplay from 'ui/shared/DataListDisplay'; -import PageTitle from 'ui/shared/Page/PageTitle'; -import useQueryWithPages from 'ui/shared/pagination/useQueryWithPages'; -import StickyPaginationWithText from 'ui/shared/StickyPaginationWithText'; - -import OptimisticL2DisputeGamesListItem from './OptimisticL2DisputeGamesListItem'; -import OptimisticL2DisputeGamesTable from './OptimisticL2DisputeGamesTable'; - -const OptimisticL2DisputeGames = () => { - const { data, isError, isPlaceholderData, pagination } = useQueryWithPages({ - resourceName: 'general:optimistic_l2_dispute_games', - options: { - placeholderData: generateListStub<'general:optimistic_l2_dispute_games'>( - L2_DISPUTE_GAMES_ITEM, - 50, - { - next_page_params: { - items_count: 50, - index: 9045200, - }, - }, - ), - }, - }); - - const countersQuery = useApiQuery('general:optimistic_l2_dispute_games_count', { - queryOptions: { - placeholderData: 50617, - }, - }); - - const content = data?.items ? ( - <> - - { data.items.map(((item, index) => ( - - ))) } - - - - - - ) : null; - - const text = (() => { - if (countersQuery.isError || isError || !data?.items.length) { - return null; - } - - return ( - - Dispute game index - #{ data.items[0].index } to - #{ data.items[data.items.length - 1].index } - (total of { countersQuery.data?.toLocaleString() } games) - - ); - })(); - - const actionBar = ; - - return ( - <> - - - { content } - - - ); -}; - -export default OptimisticL2DisputeGames; diff --git a/client/features/rollup/optimism/pages/dispute-games/OptimisticL2DisputeGamesTableItem.tsx b/client/features/rollup/optimism/pages/dispute-games/OptimisticL2DisputeGamesTableItem.tsx deleted file mode 100644 index 19f72cce006..00000000000 --- a/client/features/rollup/optimism/pages/dispute-games/OptimisticL2DisputeGamesTableItem.tsx +++ /dev/null @@ -1,71 +0,0 @@ -// SPDX-License-Identifier: LicenseRef-Blockscout - -import { Flex } from '@chakra-ui/react'; -import React from 'react'; - -import type { OptimisticL2DisputeGamesItem } from 'client/features/rollup/optimism/types/api'; - -import BlockEntityL2 from 'client/features/rollup/common/components/BlockEntityL2'; - -import config from 'configs/app'; -import { Skeleton } from 'toolkit/chakra/skeleton'; -import { TableCell, TableRow } from 'toolkit/chakra/table'; -import CopyToClipboard from 'ui/shared/CopyToClipboard'; -import HashStringShorten from 'ui/shared/HashStringShorten'; -import TimeWithTooltip from 'ui/shared/time/TimeWithTooltip'; - -const faultProofSystemFeature = config.features.faultProofSystem; - -type Props = { item: OptimisticL2DisputeGamesItem; isLoading?: boolean }; - -const OptimisticL2DisputeGamesTableItem = ({ item, isLoading }: Props) => { - if (!faultProofSystemFeature.isEnabled) { - return null; - } - - return ( - - - { item.index } - - - { item.game_type } - - - - - - - - - - - - - - - - - { item.status } - - - - - - ); -}; - -export default OptimisticL2DisputeGamesTableItem; diff --git a/client/features/rollup/optimism/pages/dispute-games/__screenshots__/OptimisticL2DisputeGames.pw.tsx_default_base-view-mobile-1.png b/client/features/rollup/optimism/pages/dispute-games/__screenshots__/OptimisticL2DisputeGames.pw.tsx_default_base-view-mobile-1.png deleted file mode 100644 index 2b08423b0df..00000000000 Binary files a/client/features/rollup/optimism/pages/dispute-games/__screenshots__/OptimisticL2DisputeGames.pw.tsx_default_base-view-mobile-1.png and /dev/null differ diff --git a/client/features/rollup/optimism/pages/dispute-games/__screenshots__/OptimisticL2DisputeGames.pw.tsx_mobile_base-view-mobile-1.png b/client/features/rollup/optimism/pages/dispute-games/__screenshots__/OptimisticL2DisputeGames.pw.tsx_mobile_base-view-mobile-1.png deleted file mode 100644 index 33170c01d26..00000000000 Binary files a/client/features/rollup/optimism/pages/dispute-games/__screenshots__/OptimisticL2DisputeGames.pw.tsx_mobile_base-view-mobile-1.png and /dev/null differ diff --git a/client/features/rollup/optimism/pages/home/LatestOptimisticDeposits.pw.tsx b/client/features/rollup/optimism/pages/home/LatestOptimisticDeposits.pw.tsx deleted file mode 100644 index d7eb2c641eb..00000000000 --- a/client/features/rollup/optimism/pages/home/LatestOptimisticDeposits.pw.tsx +++ /dev/null @@ -1,15 +0,0 @@ -import React from 'react'; - -import * as depositMock from 'client/features/rollup/optimism/mocks/deposits'; - -import { ENVS_MAP } from 'playwright/fixtures/mockEnvs'; -import { test, expect } from 'playwright/lib'; - -import LatestOptimisticDeposits from './LatestOptimisticDeposits'; - -test('default view +@mobile +@dark-mode', async({ render, mockApiResponse, mockEnvs }) => { - await mockEnvs(ENVS_MAP.optimisticRollup); - mockApiResponse('general:homepage_optimistic_deposits', depositMock.data.items); - const component = await render(); - await expect(component).toHaveScreenshot(); -}); diff --git a/client/features/rollup/optimism/pages/home/LatestOptimisticDeposits.tsx b/client/features/rollup/optimism/pages/home/LatestOptimisticDeposits.tsx deleted file mode 100644 index 4cb4920eeef..00000000000 --- a/client/features/rollup/optimism/pages/home/LatestOptimisticDeposits.tsx +++ /dev/null @@ -1,77 +0,0 @@ -// SPDX-License-Identifier: LicenseRef-Blockscout - -import { Text } from '@chakra-ui/react'; -import React from 'react'; - -import type { SocketMessage } from 'client/api/socket/types'; - -import useApiQuery from 'client/api/hooks/useApiQuery'; -import useSocketChannel from 'client/api/socket/useSocketChannel'; -import useSocketMessage from 'client/api/socket/useSocketMessage'; - -import LatestTxsFallback from 'client/slices/home/pages/index/txs/LatestTxsFallback'; - -import LatestDeposits from 'client/features/rollup/common/pages/home/LatestDeposits'; -import { L2_DEPOSIT_ITEM } from 'client/features/rollup/optimism/stubs'; - -import useGradualIncrement from 'client/shared/hooks/useGradualIncrement'; -import useIsMobile from 'client/shared/hooks/useIsMobile'; - -const LatestOptimisticDeposits = () => { - const isMobile = useIsMobile(); - const itemsCount = isMobile ? 2 : 5; - const { data, isPlaceholderData, isError } = useApiQuery('general:homepage_optimistic_deposits', { - queryOptions: { - placeholderData: Array(itemsCount).fill(L2_DEPOSIT_ITEM), - }, - }); - - const [ num, setNum ] = useGradualIncrement(0); - const [ showSocketErrorAlert, setShowSocketErrorAlert ] = React.useState(false); - - const handleSocketClose = React.useCallback(() => { - setShowSocketErrorAlert(true); - }, []); - - const handleSocketError = React.useCallback(() => { - setShowSocketErrorAlert(true); - }, []); - - const handleNewDepositMessage: SocketMessage.NewOptimisticDeposits['handler'] = React.useCallback((payload) => { - setNum(payload.deposits); - }, [ setNum ]); - - const channel = useSocketChannel({ - topic: 'optimism:new_deposits', - onSocketClose: handleSocketClose, - onSocketError: handleSocketError, - isDisabled: false, - }); - - useSocketMessage({ - channel, - event: 'new_optimism_deposits', - handler: handleNewDepositMessage, - }); - - if (isError) { - return ; - } - - if (data) { - return ( - ( - { l1BlockNumber: item.l1_block_number, l1TxHash: item.l1_transaction_hash, l2TxHash: item.l2_transaction_hash, timestamp: item.l1_block_timestamp } - )) } - isLoading={ isPlaceholderData } - socketItemsNum={ num } - showSocketErrorAlert={ showSocketErrorAlert } - /> - ); - } - - return No latest deposits found.; -}; - -export default LatestOptimisticDeposits; diff --git a/client/features/rollup/optimism/pages/output-roots/OptimisticL2OutputRoots.pw.tsx b/client/features/rollup/optimism/pages/output-roots/OptimisticL2OutputRoots.pw.tsx deleted file mode 100644 index 001b8748599..00000000000 --- a/client/features/rollup/optimism/pages/output-roots/OptimisticL2OutputRoots.pw.tsx +++ /dev/null @@ -1,18 +0,0 @@ -import React from 'react'; - -import { outputRootsData } from 'client/features/rollup/optimism/mocks/output-roots'; - -import { ENVS_MAP } from 'playwright/fixtures/mockEnvs'; -import { test, expect } from 'playwright/lib'; - -import OptimisticL2OutputRoots from './OptimisticL2OutputRoots'; - -test('base view +@mobile', async({ render, mockEnvs, mockTextAd, mockApiResponse }) => { - test.slow(); - await mockEnvs(ENVS_MAP.optimisticRollup); - await mockTextAd(); - await mockApiResponse('general:optimistic_l2_output_roots', outputRootsData); - await mockApiResponse('general:optimistic_l2_output_roots_count', 9927); - const component = await render(); - await expect(component).toHaveScreenshot({ timeout: 10_000 }); -}); diff --git a/client/features/rollup/optimism/pages/output-roots/OptimisticL2OutputRoots.tsx b/client/features/rollup/optimism/pages/output-roots/OptimisticL2OutputRoots.tsx deleted file mode 100644 index 1fa3f4a9e10..00000000000 --- a/client/features/rollup/optimism/pages/output-roots/OptimisticL2OutputRoots.tsx +++ /dev/null @@ -1,94 +0,0 @@ -// SPDX-License-Identifier: LicenseRef-Blockscout - -import { Box, Text } from '@chakra-ui/react'; -import React from 'react'; - -import useApiQuery from 'client/api/hooks/useApiQuery'; - -import { layerLabels } from 'client/features/rollup/common/utils/layer'; -import { L2_OUTPUT_ROOTS_ITEM } from 'client/features/rollup/optimism/stubs'; - -import { generateListStub } from 'stubs/utils'; -import { Skeleton } from 'toolkit/chakra/skeleton'; -import { ACTION_BAR_HEIGHT_DESKTOP } from 'ui/shared/ActionBar'; -import DataListDisplay from 'ui/shared/DataListDisplay'; -import PageTitle from 'ui/shared/Page/PageTitle'; -import useQueryWithPages from 'ui/shared/pagination/useQueryWithPages'; -import StickyPaginationWithText from 'ui/shared/StickyPaginationWithText'; - -import OptimisticL2OutputRootsListItem from './OptimisticL2OutputRootsListItem'; -import OptimisticL2OutputRootsTable from './OptimisticL2OutputRootsTable'; - -const OptimisticL2OutputRoots = () => { - const { data, isError, isPlaceholderData, pagination } = useQueryWithPages({ - resourceName: 'general:optimistic_l2_output_roots', - options: { - placeholderData: generateListStub<'general:optimistic_l2_output_roots'>( - L2_OUTPUT_ROOTS_ITEM, - 50, - { - next_page_params: { - items_count: 50, - index: 9045200, - }, - }, - ), - }, - }); - - const countersQuery = useApiQuery('general:optimistic_l2_output_roots_count', { - queryOptions: { - placeholderData: 50617, - }, - }); - - const content = data?.items ? ( - <> - - { data.items.map(((item, index) => ( - - ))) } - - - - - - ) : null; - - const text = (() => { - if (countersQuery.isError || isError || !data?.items.length) { - return null; - } - - return ( - - { layerLabels.current } output index - #{ data.items[0].l2_output_index } to - #{ data.items[data.items.length - 1].l2_output_index } - (total of { countersQuery.data?.toLocaleString() } roots) - - ); - })(); - - const actionBar = ; - - return ( - <> - - - { content } - - - ); -}; - -export default OptimisticL2OutputRoots; diff --git a/client/features/rollup/optimism/pages/output-roots/OptimisticL2OutputRootsTableItem.tsx b/client/features/rollup/optimism/pages/output-roots/OptimisticL2OutputRootsTableItem.tsx deleted file mode 100644 index 6cfe8eab92c..00000000000 --- a/client/features/rollup/optimism/pages/output-roots/OptimisticL2OutputRootsTableItem.tsx +++ /dev/null @@ -1,69 +0,0 @@ -// SPDX-License-Identifier: LicenseRef-Blockscout - -import { Flex } from '@chakra-ui/react'; -import React from 'react'; - -import type { OptimisticL2OutputRootsItem } from 'client/features/rollup/optimism/types/api'; - -import BlockEntityL2 from 'client/features/rollup/common/components/BlockEntityL2'; -import TxEntityL1 from 'client/features/rollup/common/components/TxEntityL1'; - -import config from 'configs/app'; -import { Skeleton } from 'toolkit/chakra/skeleton'; -import { TableCell, TableRow } from 'toolkit/chakra/table'; -import CopyToClipboard from 'ui/shared/CopyToClipboard'; -import HashStringShorten from 'ui/shared/HashStringShorten'; -import TimeWithTooltip from 'ui/shared/time/TimeWithTooltip'; - -const rollupFeature = config.features.rollup; - -type Props = { item: OptimisticL2OutputRootsItem; isLoading?: boolean }; - -const OptimisticL2OutputRootsTableItem = ({ item, isLoading }: Props) => { - if (!rollupFeature.isEnabled || rollupFeature.type !== 'optimistic') { - return null; - } - - return ( - - - { item.l2_output_index } - - - - - - - - - - - - - - - - - - - - ); -}; - -export default OptimisticL2OutputRootsTableItem; diff --git a/client/features/rollup/optimism/pages/output-roots/__screenshots__/OptimisticL2OutputRoots.pw.tsx_default_base-view-mobile-1.png b/client/features/rollup/optimism/pages/output-roots/__screenshots__/OptimisticL2OutputRoots.pw.tsx_default_base-view-mobile-1.png deleted file mode 100644 index d7690ae56aa..00000000000 Binary files a/client/features/rollup/optimism/pages/output-roots/__screenshots__/OptimisticL2OutputRoots.pw.tsx_default_base-view-mobile-1.png and /dev/null differ diff --git a/client/features/rollup/optimism/pages/output-roots/__screenshots__/OptimisticL2OutputRoots.pw.tsx_mobile_base-view-mobile-1.png b/client/features/rollup/optimism/pages/output-roots/__screenshots__/OptimisticL2OutputRoots.pw.tsx_mobile_base-view-mobile-1.png deleted file mode 100644 index 04d1e127abb..00000000000 Binary files a/client/features/rollup/optimism/pages/output-roots/__screenshots__/OptimisticL2OutputRoots.pw.tsx_mobile_base-view-mobile-1.png and /dev/null differ diff --git a/client/features/rollup/optimism/pages/tx/TxDetailsWithdrawalStatusOptimistic.tsx b/client/features/rollup/optimism/pages/tx/TxDetailsWithdrawalStatusOptimistic.tsx deleted file mode 100644 index 5cb0859a909..00000000000 --- a/client/features/rollup/optimism/pages/tx/TxDetailsWithdrawalStatusOptimistic.tsx +++ /dev/null @@ -1,125 +0,0 @@ -// SPDX-License-Identifier: LicenseRef-Blockscout - -import { useQueryClient } from '@tanstack/react-query'; -import React from 'react'; - -import type { OpWithdrawal, OptimisticL2WithdrawalStatus } from 'client/features/rollup/optimism/types/api'; -import type { AddressParam } from 'client/slices/address/types/api'; -import type { Transaction } from 'client/slices/tx/types/api'; - -import { getResourceKey } from 'client/api/hooks/useApiQuery'; - -import TxEntityL1 from 'client/features/rollup/common/components/TxEntityL1'; -import OptimisticL2ClaimButton, { canClaimDirectlyGuard } from 'client/features/rollup/optimism/components/OptimisticL2ClaimButton'; - -import config from 'configs/app'; -import VerificationSteps from 'ui/shared/verificationSteps/VerificationSteps'; - -interface Props { - data: OpWithdrawal; - from: AddressParam; - txHash: string; -} - -const WITHDRAWAL_STATUS_STEPS: Array = [ - 'Waiting for state root', - 'Ready to prove', - 'In challenge period', - 'Ready for relay', - 'Relayed', -]; - -const WITHDRAWAL_STATUS_ORDER_PROVEN: Array = [ - 'Waiting for state root', - 'Ready to prove', - 'Proven', - 'Relayed', -]; - -const WITHDRAWAL_STATUS_ORDER_GAME: Array = [ - 'Waiting for state root', - 'Ready to prove', - 'Waiting a game to resolve', - 'In challenge period', - 'Ready for relay', - 'Relayed', -]; - -const rollupFeature = config.features.rollup; - -const TxDetailsWithdrawalStatusOptimistic = ({ data, from, txHash }: Props) => { - const queryClient = useQueryClient(); - - const handleClaimSuccess = React.useCallback((l1TxHash: string) => { - queryClient.setQueryData( - getResourceKey('general:tx', { pathParams: { hash: txHash } }), - (prevData: Transaction | undefined) => { - if (!prevData) { - return; - } - - const newWithdrawals = prevData.op_withdrawals?.map((withdrawal) => { - if (withdrawal.nonce === data.nonce) { - return { - ...withdrawal, - l1_transaction_hash: l1TxHash, - status: 'Relayed', - }; - } - return withdrawal; - }); - - return { - ...prevData, - op_withdrawals: newWithdrawals, - }; - }); - }, [ data.nonce, queryClient, txHash ]); - - if (!data.status || !rollupFeature.isEnabled || rollupFeature.type !== 'optimistic') { - return null; - } - - const canClaimDirectly = canClaimDirectlyGuard(data); - const hasRightSlot = data.status === 'Ready for relay' && (rollupFeature.L2WithdrawalUrl || canClaimDirectly); - - const rightSlot = hasRightSlot ? - : - null; - - const steps = (() => { - switch (data.status) { - case 'Ready for relay': - return hasRightSlot ? WITHDRAWAL_STATUS_STEPS.slice(0, -1) : WITHDRAWAL_STATUS_STEPS; - case 'Proven': - return WITHDRAWAL_STATUS_ORDER_PROVEN; - case 'Waiting a game to resolve': - return WITHDRAWAL_STATUS_ORDER_GAME; - case 'Relayed': { - if (data.l1_transaction_hash) { - return WITHDRAWAL_STATUS_STEPS.map((status) => { - return status === 'Relayed' ? { - content: , - label: status, - } : status; - }); - } - - return WITHDRAWAL_STATUS_STEPS; - } - - default: - return WITHDRAWAL_STATUS_STEPS; - } - })(); - - return ( - } - currentStep={ data.status } - rightSlot={ rightSlot } - /> - ); -}; - -export default React.memo(TxDetailsWithdrawalStatusOptimistic); diff --git a/client/features/rollup/optimism/pages/withdrawals/OptimisticL2Withdrawals.pw.tsx b/client/features/rollup/optimism/pages/withdrawals/OptimisticL2Withdrawals.pw.tsx deleted file mode 100644 index 074999c7d51..00000000000 --- a/client/features/rollup/optimism/pages/withdrawals/OptimisticL2Withdrawals.pw.tsx +++ /dev/null @@ -1,21 +0,0 @@ -import React from 'react'; - -import { data as withdrawalsData } from 'client/features/rollup/optimism/mocks/withdrawals'; - -import { ENVS_MAP } from 'playwright/fixtures/mockEnvs'; -import { test, expect } from 'playwright/lib'; - -import OptimisticL2Withdrawals from './OptimisticL2Withdrawals'; - -test('base view +@mobile', async({ render, mockTextAd, mockEnvs, mockApiResponse }) => { - // test on mobile is flaky - // my assumption is there is not enough time to calculate hashes truncation so component is unstable - // so I raised the test timeout to check if it helps - test.slow(); - await mockTextAd(); - await mockEnvs(ENVS_MAP.optimisticRollup); - await mockApiResponse('general:optimistic_l2_withdrawals', withdrawalsData); - await mockApiResponse('general:optimistic_l2_withdrawals_count', 397); - const component = await render(); - await expect(component).toHaveScreenshot({ timeout: 10_000 }); -}); diff --git a/client/features/rollup/optimism/pages/withdrawals/OptimisticL2Withdrawals.tsx b/client/features/rollup/optimism/pages/withdrawals/OptimisticL2Withdrawals.tsx deleted file mode 100644 index e972ee3bb22..00000000000 --- a/client/features/rollup/optimism/pages/withdrawals/OptimisticL2Withdrawals.tsx +++ /dev/null @@ -1,94 +0,0 @@ -// SPDX-License-Identifier: LicenseRef-Blockscout - -import { Box } from '@chakra-ui/react'; -import React from 'react'; - -import useApiQuery from 'client/api/hooks/useApiQuery'; - -import { layerLabels } from 'client/features/rollup/common/utils/layer'; -import OptimisticL2WithdrawalsListItem from 'client/features/rollup/optimism/pages/withdrawals/OptimisticL2WithdrawalsListItem'; -import OptimisticL2WithdrawalsTable from 'client/features/rollup/optimism/pages/withdrawals/OptimisticL2WithdrawalsTable'; -import { L2_WITHDRAWAL_ITEM } from 'client/features/rollup/optimism/stubs'; - -import { generateListStub } from 'stubs/utils'; -import { Skeleton } from 'toolkit/chakra/skeleton'; -import { rightLineArrow, nbsp } from 'toolkit/utils/htmlEntities'; -import { ACTION_BAR_HEIGHT_DESKTOP } from 'ui/shared/ActionBar'; -import DataListDisplay from 'ui/shared/DataListDisplay'; -import PageTitle from 'ui/shared/Page/PageTitle'; -import useQueryWithPages from 'ui/shared/pagination/useQueryWithPages'; -import StickyPaginationWithText from 'ui/shared/StickyPaginationWithText'; - -const OptimisticL2Withdrawals = () => { - const { data, isError, isPlaceholderData, pagination } = useQueryWithPages({ - resourceName: 'general:optimistic_l2_withdrawals', - options: { - placeholderData: generateListStub<'general:optimistic_l2_withdrawals'>( - L2_WITHDRAWAL_ITEM, - 50, - { - next_page_params: { - items_count: 50, - nonce: '', - }, - }, - ), - }, - }); - - const countersQuery = useApiQuery('general:optimistic_l2_withdrawals_count', { - queryOptions: { - placeholderData: 23700, - }, - }); - - const content = data?.items ? ( - <> - - { data.items.map(((item, index) => ( - - ))) } - - - - - - ) : null; - - const text = (() => { - if (countersQuery.isError) { - return null; - } - - return ( - - A total of { countersQuery.data?.toLocaleString() } withdrawals found - - ); - })(); - - const actionBar = ; - - return ( - <> - - - { content } - - - ); -}; - -export default OptimisticL2Withdrawals; diff --git a/client/features/rollup/optimism/pages/withdrawals/OptimisticL2WithdrawalsItemStatus.tsx b/client/features/rollup/optimism/pages/withdrawals/OptimisticL2WithdrawalsItemStatus.tsx deleted file mode 100644 index 2b39b6218e4..00000000000 --- a/client/features/rollup/optimism/pages/withdrawals/OptimisticL2WithdrawalsItemStatus.tsx +++ /dev/null @@ -1,59 +0,0 @@ -// SPDX-License-Identifier: LicenseRef-Blockscout - -import { useQueryClient } from '@tanstack/react-query'; -import React from 'react'; - -import type { OptimisticL2WithdrawalsItem, OptimisticL2WithdrawalsResponse } from 'client/features/rollup/optimism/types/api'; - -import { getResourceKey } from 'client/api/hooks/useApiQuery'; - -import OptimisticL2ClaimButton from 'client/features/rollup/optimism/components/OptimisticL2ClaimButton'; - -import { Skeleton } from 'toolkit/chakra/skeleton'; - -interface Props { - data: OptimisticL2WithdrawalsItem; - isLoading?: boolean; -} - -const OptimisticL2WithdrawalsItemStatus = ({ data, isLoading }: Props) => { - - const queryClient = useQueryClient(); - - const handleClaimSuccess = React.useCallback((l1TxHash: string) => { - queryClient.setQueriesData({ - queryKey: getResourceKey('general:optimistic_l2_withdrawals'), - exact: false, - type: 'active', - }, - (prevData: OptimisticL2WithdrawalsResponse | undefined) => { - if (!prevData) { - return; - } - - const newItems = prevData.items.map((withdrawal) => { - if (`${ withdrawal.msg_nonce_version }-${ withdrawal.msg_nonce }` === `${ data.msg_nonce_version }-${ data.msg_nonce }`) { - return { - ...withdrawal, - l1_transaction_hash: l1TxHash, - status: 'Relayed', - }; - } - return withdrawal; - }); - - return { - ...prevData, - items: newItems, - }; - }); - }, [ data.msg_nonce, data.msg_nonce_version, queryClient ]); - - if (data.status !== 'Ready for relay') { - return { data.status }; - } - - return ; -}; - -export default React.memo(OptimisticL2WithdrawalsItemStatus); diff --git a/client/features/rollup/optimism/pages/withdrawals/__screenshots__/OptimisticL2Withdrawals.pw.tsx_default_base-view-mobile-1.png b/client/features/rollup/optimism/pages/withdrawals/__screenshots__/OptimisticL2Withdrawals.pw.tsx_default_base-view-mobile-1.png deleted file mode 100644 index 7ba2c987a77..00000000000 Binary files a/client/features/rollup/optimism/pages/withdrawals/__screenshots__/OptimisticL2Withdrawals.pw.tsx_default_base-view-mobile-1.png and /dev/null differ diff --git a/client/features/rollup/optimism/pages/withdrawals/__screenshots__/OptimisticL2Withdrawals.pw.tsx_mobile_base-view-mobile-1.png b/client/features/rollup/optimism/pages/withdrawals/__screenshots__/OptimisticL2Withdrawals.pw.tsx_mobile_base-view-mobile-1.png deleted file mode 100644 index 95e87ea0c93..00000000000 Binary files a/client/features/rollup/optimism/pages/withdrawals/__screenshots__/OptimisticL2Withdrawals.pw.tsx_mobile_base-view-mobile-1.png and /dev/null differ diff --git a/client/features/rollup/optimism/stubs.ts b/client/features/rollup/optimism/stubs.ts deleted file mode 100644 index 8498a080fa5..00000000000 --- a/client/features/rollup/optimism/stubs.ts +++ /dev/null @@ -1,81 +0,0 @@ -import type { - OptimismL2TxnBatch, - OptimisticL2DepositsItem, - OptimisticL2DisputeGamesItem, - OptimisticL2OutputRootsItem, - OptimisticL2TxnBatchesItem, - OptimisticL2WithdrawalsItem, -} from 'client/features/rollup/optimism/types/api'; - -import { ADDRESS_HASH, ADDRESS_PARAMS } from 'client/slices/address/stubs/address-params'; -import { TX_HASH } from 'client/slices/tx/stubs/tx'; - -export const L2_DEPOSIT_ITEM: OptimisticL2DepositsItem = { - l1_block_number: 9045233, - l1_block_timestamp: '2023-05-22T18:00:36.000000Z', - l1_transaction_hash: TX_HASH, - l1_transaction_origin: ADDRESS_HASH, - l2_transaction_gas_limit: '100000', - l2_transaction_hash: TX_HASH, -}; - -export const L2_WITHDRAWAL_ITEM: OptimisticL2WithdrawalsItem = { - challenge_period_end: null, - from: ADDRESS_PARAMS, - l1_transaction_hash: TX_HASH, - l2_timestamp: '2023-06-01T13:44:56.000000Z', - l2_transaction_hash: TX_HASH, - msg_nonce: 2393, - msg_nonce_version: 1, - status: 'Ready to prove', - portal_contract_address_hash: null, - msg_sender_address_hash: null, - msg_target_address_hash: null, - msg_data: null, - msg_gas_limit: null, - msg_nonce_raw: null, - msg_value: null, -}; - -export const L2_TXN_BATCHES_ITEM: OptimisticL2TxnBatchesItem = { - number: 260991, - batch_data_container: 'in_blob4844', - l1_timestamp: '2023-06-01T14:46:48.000000Z', - l1_transaction_hashes: [ - TX_HASH, - ], - l2_start_block_number: 5218590, - l2_end_block_number: 5218777, - transactions_count: 9, -}; - -export const L2_TXN_BATCH: OptimismL2TxnBatch = { - ...L2_TXN_BATCHES_ITEM, - batch_data_container: 'in_blob4844', - blobs: [ - { - hash: '0x01fb41e1ae9f827e13abb0ee94be2ee574a23ac31426cea630ddd18af854bc85', - l1_timestamp: '2024-09-03T13:26:23.000000Z', - l1_transaction_hash: '0xd25ee571f1701690615099b208a9431d8611d0130dc342bead6d9edc291f04b9', - }, - ], -}; - -export const L2_OUTPUT_ROOTS_ITEM: OptimisticL2OutputRootsItem = { - l1_block_number: 9103684, - l1_timestamp: '2023-06-01T15:26:12.000000Z', - l1_transaction_hash: TX_HASH, - l2_block_number: 10102468, - l2_output_index: 50655, - output_root: TX_HASH, -}; - -export const L2_DISPUTE_GAMES_ITEM: OptimisticL2DisputeGamesItem = { - contract_address_hash: ADDRESS_HASH, - created_at: '2023-06-01T15:26:12.000000Z', - game_type: 0, - index: 6594, - l2_block_number: 50655, - resolved_at: null, - status: 'In progress', -}; diff --git a/client/features/rollup/optimism/types/api.ts b/client/features/rollup/optimism/types/api.ts deleted file mode 100644 index cfbeb7b974c..00000000000 --- a/client/features/rollup/optimism/types/api.ts +++ /dev/null @@ -1,211 +0,0 @@ -// SPDX-License-Identifier: LicenseRef-Blockscout - -import type { AddressParam } from 'client/slices/address/types/api'; -import type { Block } from 'client/slices/block/types/api'; -import type { Transaction } from 'client/slices/tx/types/api'; - -export type OptimisticL2DepositsItem = { - l1_block_number: number; - l1_transaction_hash: string; - l1_block_timestamp: string; - l1_transaction_origin: string; - l2_transaction_gas_limit: string; - l2_transaction_hash: string; -}; - -export type OptimisticL2DepositsResponse = { - items: Array; - next_page_params: { - items_count: number; - l1_block_number: number; - transaction_hash: string; - }; -}; - -export type OptimisticL2OutputRootsItem = { - l1_block_number: number; - l1_timestamp: string; - l1_transaction_hash: string; - l2_block_number: number; - l2_output_index: number; - output_root: string; -}; - -export type OptimisticL2OutputRootsResponse = { - items: Array; - next_page_params: { - index: number; - items_count: number; - }; -}; - -export type OptimisticL2BatchDataContainer = 'in_blob4844' | 'in_celestia' | 'in_calldata' | 'in_eigenda'; - -export type OptimisticL2TxnBatchesItem = { - number: number; - batch_data_container?: OptimisticL2BatchDataContainer; - l1_timestamp: string; - l1_transaction_hashes: Array; - l2_start_block_number: number; - l2_end_block_number: number; - transactions_count: number; -}; - -export type OptimisticL2TxnBatchesResponse = { - items: Array; - next_page_params: { - id: number; - items_count: number; - }; -}; - -export interface OptimisticL2BlobTypeEip4844 { - hash: string; - l1_timestamp: string; - l1_transaction_hash: string; -} - -export interface OptimisticL2BlobTypeCelestia { - commitment: string; - height: number; - l1_timestamp: string; - l1_transaction_hash: string; - namespace: string; -} - -export interface OptimisticL2BlobTypeEigenda { - cert: string; - l1_timestamp: string; - l1_transaction_hash: string; -} - -interface OptimismL2TxnBatchBase { - number: number; - l1_timestamp: string; - l1_transaction_hashes: Array; - l2_start_block_number: number; - l2_end_block_number: number; - transactions_count: number; -} - -export interface OptimismL2TxnBatchTypeCallData extends OptimismL2TxnBatchBase { - batch_data_container: 'in_calldata'; -} - -export interface OptimismL2TxnBatchTypeEip4844 extends OptimismL2TxnBatchBase { - batch_data_container: 'in_blob4844'; - blobs: Array | null; -} - -export interface OptimismL2TxnBatchTypeCelestia extends OptimismL2TxnBatchBase { - batch_data_container: 'in_celestia'; - blobs: Array | null; -} - -export interface OptimismL2TxnBatchTypeEigenda extends OptimismL2TxnBatchBase { - batch_data_container: 'in_eigenda'; - blobs: Array | null; -} - -export type OptimismL2TxnBatch = - OptimismL2TxnBatchTypeCallData | - OptimismL2TxnBatchTypeEip4844 | - OptimismL2TxnBatchTypeCelestia | - OptimismL2TxnBatchTypeEigenda; - -export type OptimismL2BatchTxs = { - items: Array; - next_page_params: { - block_number: number; - index: number; - items_count: number; - } | null; -}; - -export type OptimismL2BatchBlocks = { - items: Array; - next_page_params: { - batch_number: number; - items_count: number; - } | null; -}; - -export interface OptimisticL2WithdrawalClaimInfo { - portal_contract_address_hash: string | null; - msg_sender_address_hash: string | null; - msg_target_address_hash: string | null; - msg_data: string | null; - msg_gas_limit: string | null; - msg_nonce_raw: string | null; - msg_value: string | null; -} - -export interface OptimisticL2WithdrawalsItem extends OptimisticL2WithdrawalClaimInfo { - challenge_period_end: string | null; - from: AddressParam | null; - l1_transaction_hash: string | null; - l2_timestamp: string | null; - l2_transaction_hash: string; - msg_nonce: number; - msg_nonce_version: number; - status: string; -}; - -export type OptimisticL2WithdrawalStatus = - 'Waiting for state root' | - 'Ready to prove' | - 'In challenge period' | - 'Waiting a game to resolve' | - 'Ready to prove' | - 'Proven' | - 'Ready for relay' | - 'Relayed'; - -export type OptimisticL2WithdrawalsResponse = { - items: Array; - next_page_params: { - items_count: number; - nonce: string; - }; -}; - -export type OptimisticL2DisputeGamesResponse = { - items: Array; - next_page_params: { - items_count: number; - index: number; - }; -}; - -export type OptimisticL2DisputeGamesItem = { - contract_address_hash: string; - created_at: string; - game_type: number; - index: number; - l2_block_number: number; - resolved_at: string | null; - status: string; -}; - -export interface OpWithdrawal extends OptimisticL2WithdrawalClaimInfo { - l1_transaction_hash: string; - nonce: number; - status: OptimisticL2WithdrawalStatus; -} - -export interface TransactionOptimistic { - op_withdrawals?: Array; - operator_fee?: string; -} - -export interface OptimismBlockData { - batch_data_container: OptimisticL2BatchDataContainer; - number: number; - blobs: Array | Array | null; - l1_timestamp: string; - l1_transaction_hashes: Array; -} - -export interface BlockOptimism { - optimism?: OptimismBlockData; -} diff --git a/client/features/rollup/scroll/components/ScrollL2TxnBatchDA.tsx b/client/features/rollup/scroll/components/ScrollL2TxnBatchDA.tsx deleted file mode 100644 index cfc56d50d32..00000000000 --- a/client/features/rollup/scroll/components/ScrollL2TxnBatchDA.tsx +++ /dev/null @@ -1,33 +0,0 @@ -// SPDX-License-Identifier: LicenseRef-Blockscout - -import React from 'react'; - -import type { ScrollL2TxnBatch } from 'client/features/rollup/scroll/types/api'; -import type { ExcludeUndefined } from 'types/utils'; - -import { Badge } from 'toolkit/chakra/badge'; - -export interface Props { - container: ExcludeUndefined; - isLoading?: boolean; -} - -const ScrollL2TxnBatchDA = ({ container, isLoading }: Props) => { - - const text = (() => { - switch (container) { - case 'in_blob4844': - return 'EIP-4844 blob'; - case 'in_calldata': - return 'Calldata'; - } - })(); - - return ( - - { text } - - ); -}; - -export default React.memo(ScrollL2TxnBatchDA); diff --git a/client/features/rollup/scroll/mocks/messages.ts b/client/features/rollup/scroll/mocks/messages.ts deleted file mode 100644 index f71ce763e52..00000000000 --- a/client/features/rollup/scroll/mocks/messages.ts +++ /dev/null @@ -1,26 +0,0 @@ -import type { ScrollL2MessagesResponse } from 'client/features/rollup/scroll/types/api'; - -export const baseResponse: ScrollL2MessagesResponse = { - items: [ - { - id: 930795, - origination_transaction_block_number: 20639178, - origination_transaction_hash: '0x70380f2c6ecd53aa6e0608e6c9d770acaa29c0508869ec296bae3e09678ea9f4', - origination_timestamp: '2024-08-30T05:03:23.000000Z', - completion_transaction_hash: null, - value: '5084131319054877748', - }, - { - id: 930748, - origination_transaction_block_number: 20638104, - origination_transaction_hash: '0x7e7b4d5ff0b7a6af5e52f4aa2ad9eca3c0c5664368cbb781e04b5b13c6109b2b', - origination_timestamp: '2024-08-30T01:26:35.000000Z', - completion_transaction_hash: '0x426b16ea3a42228f6d754ae55c348986122cdb1e4331b6fd454975776f513ea1', - value: '0', - }, - ], - next_page_params: { - items_count: 50, - id: 1, - }, -}; diff --git a/client/features/rollup/scroll/mocks/txn-batches.ts b/client/features/rollup/scroll/mocks/txn-batches.ts deleted file mode 100644 index c8b1b1f51a1..00000000000 --- a/client/features/rollup/scroll/mocks/txn-batches.ts +++ /dev/null @@ -1,50 +0,0 @@ -import type { ScrollL2BatchesResponse } from 'client/features/rollup/scroll/types/api'; - -export const batchData = { - number: 66928, - commitment_transaction: { - block_number: 19114878, - hash: '0x57552c0dbcf56383ee2efdf8fd6be143b355135fc300361924582c308877b8b7', - timestamp: '2024-01-29T21:31:35.000000Z', - }, - confirmation_transaction: { - block_number: null, - hash: null, - timestamp: null, - }, - data_availability: { - batch_data_container: 'in_blob4844' as const, - }, - start_block_number: 456000, - end_block_number: 789000, - transactions_count: 654, -}; - -export const baseResponse: ScrollL2BatchesResponse = { - items: [ - batchData, - { - number: 66879, - commitment_transaction: { - block_number: 19114386, - hash: '0x0d33245814b9e61c8f0ed6fd3fb7464f34be33d2c3aee69629d65e8995d77edc', - timestamp: '2024-01-29T19:52:35.000000Z', - }, - confirmation_transaction: { - block_number: 19114558, - hash: '0x6f9a19d503947ec91d6e9d5c2129913a7def86fd0f87061c06e5994cf857bee0', - timestamp: '2024-01-29T20:27:11.000000Z', - }, - data_availability: { - batch_data_container: 'in_calldata', - }, - start_block_number: 456000, - end_block_number: 789000, - transactions_count: 962, - }, - ], - next_page_params: { - items_count: 50, - number: 1, - }, -}; diff --git a/client/features/rollup/scroll/pages/batch-details/ScrollL2TxnBatch.tsx b/client/features/rollup/scroll/pages/batch-details/ScrollL2TxnBatch.tsx deleted file mode 100644 index 5871b2fed3c..00000000000 --- a/client/features/rollup/scroll/pages/batch-details/ScrollL2TxnBatch.tsx +++ /dev/null @@ -1,124 +0,0 @@ -// SPDX-License-Identifier: LicenseRef-Blockscout - -import { useRouter } from 'next/router'; -import React from 'react'; - -import type { TabItemRegular } from 'toolkit/components/AdaptiveTabs/types'; - -import useApiQuery from 'client/api/hooks/useApiQuery'; - -import BlocksContent from 'client/slices/block/pages/index/BlocksContent'; -import { BLOCK } from 'client/slices/block/stubs/block'; -import TxsWithFrontendSorting from 'client/slices/tx/pages/index/list/TxsWithFrontendSorting'; -import { TX } from 'client/slices/tx/stubs/tx'; - -import { SCROLL_L2_TXN_BATCH } from 'client/features/rollup/scroll/stubs'; - -import throwOnAbsentParamError from 'client/shared/errors/throw-on-absent-param-error'; -import throwOnResourceLoadError from 'client/shared/errors/throw-on-resource-load-error'; -import useIsMobile from 'client/shared/hooks/useIsMobile'; -import getQueryParamString from 'client/shared/router/get-query-param-string'; - -import { generateListStub } from 'stubs/utils'; -import RoutedTabs from 'toolkit/components/RoutedTabs/RoutedTabs'; -import TextAd from 'ui/shared/ad/TextAd'; -import PageTitle from 'ui/shared/Page/PageTitle'; -import Pagination from 'ui/shared/pagination/Pagination'; -import useQueryWithPages from 'ui/shared/pagination/useQueryWithPages'; - -import ScrollL2TxnBatchDetails from './ScrollL2TxnBatchDetails'; - -const TAB_LIST_PROPS = { - marginBottom: 0, - py: 5, - marginTop: -5, -}; - -const TABS_HEIGHT = 80; - -const ScrollL2TxnBatch = () => { - const router = useRouter(); - const number = getQueryParamString(router.query.number); - const tab = getQueryParamString(router.query.tab); - const isMobile = useIsMobile(); - - const batchQuery = useApiQuery('general:scroll_l2_txn_batch', { - pathParams: { number }, - queryOptions: { - enabled: Boolean(number), - placeholderData: SCROLL_L2_TXN_BATCH, - }, - }); - - const batchTxsQuery = useQueryWithPages({ - resourceName: 'general:scroll_l2_txn_batch_txs', - pathParams: { number }, - options: { - enabled: Boolean(!batchQuery.isPlaceholderData && batchQuery.data?.number && tab === 'txs'), - placeholderData: generateListStub<'general:scroll_l2_txn_batch_txs'>(TX, 50, { next_page_params: { - batch_number: 8122, - block_number: 1338932, - index: 0, - items_count: 50, - } }), - }, - }); - - const batchBlocksQuery = useQueryWithPages({ - resourceName: 'general:scroll_l2_txn_batch_blocks', - pathParams: { number }, - options: { - enabled: Boolean(!batchQuery.isPlaceholderData && batchQuery.data?.number && tab === 'blocks'), - placeholderData: generateListStub<'general:scroll_l2_txn_batch_blocks'>(BLOCK, 50, { next_page_params: { - batch_number: 8122, - block_number: 1338932, - items_count: 50, - } }), - }, - }); - - throwOnAbsentParamError(number); - throwOnResourceLoadError(batchQuery); - - let pagination; - if (tab === 'txs') { - pagination = batchTxsQuery.pagination; - } - if (tab === 'blocks') { - pagination = batchBlocksQuery.pagination; - } - - const hasPagination = !isMobile && pagination?.isVisible; - - const tabs: Array = React.useMemo(() => ([ - { id: 'index', title: 'Details', component: }, - { - id: 'txs', - title: 'Transactions', - component: , - }, - { - id: 'blocks', - title: 'Blocks', - component: , - }, - ].filter(Boolean)), [ batchQuery, batchTxsQuery, batchBlocksQuery, hasPagination ]); - - return ( - <> - - - : null } - stickyEnabled={ hasPagination } - /> - - ); -}; - -export default ScrollL2TxnBatch; diff --git a/client/features/rollup/scroll/pages/batches/ScrollL2TxnBatches.pw.tsx b/client/features/rollup/scroll/pages/batches/ScrollL2TxnBatches.pw.tsx deleted file mode 100644 index 7c8f7263ebd..00000000000 --- a/client/features/rollup/scroll/pages/batches/ScrollL2TxnBatches.pw.tsx +++ /dev/null @@ -1,34 +0,0 @@ -import React from 'react'; - -import * as scrollTxnBatchesMock from 'client/features/rollup/scroll/mocks/txn-batches'; - -import { ENVS_MAP } from 'playwright/fixtures/mockEnvs'; -import { test, expect, devices } from 'playwright/lib'; - -import ScrollL2TxnBatches from './ScrollL2TxnBatches'; - -test('base view', async({ render, mockEnvs, mockTextAd, mockApiResponse }) => { - test.slow(); - await mockEnvs(ENVS_MAP.scrollRollup); - await mockTextAd(); - await mockApiResponse('general:scroll_l2_txn_batches', scrollTxnBatchesMock.baseResponse); - await mockApiResponse('general:scroll_l2_txn_batches_count', 9927); - - const component = await render(); - await expect(component).toHaveScreenshot(); -}); - -test.describe('mobile', () => { - test.use({ viewport: devices['iPhone 13 Pro'].viewport }); - - test('base view', async({ render, mockEnvs, mockTextAd, mockApiResponse }) => { - test.slow(); - await mockEnvs(ENVS_MAP.scrollRollup); - await mockTextAd(); - await mockApiResponse('general:scroll_l2_txn_batches', scrollTxnBatchesMock.baseResponse); - await mockApiResponse('general:scroll_l2_txn_batches_count', 9927); - - const component = await render(); - await expect(component).toHaveScreenshot(); - }); -}); diff --git a/client/features/rollup/scroll/pages/batches/ScrollL2TxnBatches.tsx b/client/features/rollup/scroll/pages/batches/ScrollL2TxnBatches.tsx deleted file mode 100644 index 19f14545f3f..00000000000 --- a/client/features/rollup/scroll/pages/batches/ScrollL2TxnBatches.tsx +++ /dev/null @@ -1,93 +0,0 @@ -// SPDX-License-Identifier: LicenseRef-Blockscout - -import { Box, Text } from '@chakra-ui/react'; -import React from 'react'; - -import useApiQuery from 'client/api/hooks/useApiQuery'; - -import { SCROLL_L2_TXN_BATCH } from 'client/features/rollup/scroll/stubs'; - -import { generateListStub } from 'stubs/utils'; -import { Skeleton } from 'toolkit/chakra/skeleton'; -import { ACTION_BAR_HEIGHT_DESKTOP } from 'ui/shared/ActionBar'; -import DataListDisplay from 'ui/shared/DataListDisplay'; -import PageTitle from 'ui/shared/Page/PageTitle'; -import useQueryWithPages from 'ui/shared/pagination/useQueryWithPages'; -import StickyPaginationWithText from 'ui/shared/StickyPaginationWithText'; - -import ScrollL2TxnBatchesListItem from './ScrollL2TxnBatchesListItem'; -import ScrollL2TxnBatchesTable from './ScrollL2TxnBatchesTable'; - -const ScrollL2TxnBatches = () => { - const { data, isError, isPlaceholderData, pagination } = useQueryWithPages({ - resourceName: 'general:scroll_l2_txn_batches', - options: { - placeholderData: generateListStub<'general:scroll_l2_txn_batches'>( - SCROLL_L2_TXN_BATCH, - 50, - { - next_page_params: { - items_count: 50, - number: 224, - }, - }, - ), - }, - }); - - const countersQuery = useApiQuery('general:scroll_l2_txn_batches_count', { - queryOptions: { - placeholderData: 123456, - }, - }); - - const content = data?.items ? ( - <> - - { data.items.map(((item, index) => ( - - ))) } - - - - - - ) : null; - - const text = (() => { - if (countersQuery.isError || isError || !data?.items.length) { - return null; - } - - return ( - - Txn batch - #{ data.items[0].number } to - #{ data.items[data.items.length - 1].number } - (total of { countersQuery.data?.toLocaleString() } batches) - - ); - })(); - - const actionBar = ; - - return ( - <> - - - { content } - - - ); -}; - -export default ScrollL2TxnBatches; diff --git a/client/features/rollup/scroll/pages/batches/__screenshots__/ScrollL2TxnBatches.pw.tsx_default_base-view-1.png b/client/features/rollup/scroll/pages/batches/__screenshots__/ScrollL2TxnBatches.pw.tsx_default_base-view-1.png deleted file mode 100644 index 849126ef204..00000000000 Binary files a/client/features/rollup/scroll/pages/batches/__screenshots__/ScrollL2TxnBatches.pw.tsx_default_base-view-1.png and /dev/null differ diff --git a/client/features/rollup/scroll/pages/batches/__screenshots__/ScrollL2TxnBatches.pw.tsx_default_mobile-base-view-1.png b/client/features/rollup/scroll/pages/batches/__screenshots__/ScrollL2TxnBatches.pw.tsx_default_mobile-base-view-1.png deleted file mode 100644 index cdfe4dd929e..00000000000 Binary files a/client/features/rollup/scroll/pages/batches/__screenshots__/ScrollL2TxnBatches.pw.tsx_default_mobile-base-view-1.png and /dev/null differ diff --git a/client/features/rollup/scroll/pages/deposits/ScrollL2Deposits.pw.tsx b/client/features/rollup/scroll/pages/deposits/ScrollL2Deposits.pw.tsx deleted file mode 100644 index 120fcc456e4..00000000000 --- a/client/features/rollup/scroll/pages/deposits/ScrollL2Deposits.pw.tsx +++ /dev/null @@ -1,34 +0,0 @@ -import React from 'react'; - -import * as messagesMock from 'client/features/rollup/scroll/mocks/messages'; - -import { ENVS_MAP } from 'playwright/fixtures/mockEnvs'; -import { test, expect, devices } from 'playwright/lib'; - -import ScrollL2Deposits from './ScrollL2Deposits'; - -test('base view', async({ render, mockApiResponse, mockEnvs, mockTextAd }) => { - await mockTextAd(); - await mockEnvs(ENVS_MAP.scrollRollup); - await mockApiResponse('general:scroll_l2_deposits', messagesMock.baseResponse); - await mockApiResponse('general:scroll_l2_deposits_count', 3971111); - - const component = await render(); - - await expect(component).toHaveScreenshot(); -}); - -test.describe('mobile', () => { - test.use({ viewport: devices['iPhone 13 Pro'].viewport }); - - test('base view', async({ render, mockApiResponse, mockEnvs, mockTextAd }) => { - await mockTextAd(); - await mockEnvs(ENVS_MAP.scrollRollup); - await mockApiResponse('general:scroll_l2_deposits', messagesMock.baseResponse); - await mockApiResponse('general:scroll_l2_deposits_count', 3971111); - - const component = await render(); - - await expect(component).toHaveScreenshot(); - }); -}); diff --git a/client/features/rollup/scroll/pages/deposits/ScrollL2Deposits.tsx b/client/features/rollup/scroll/pages/deposits/ScrollL2Deposits.tsx deleted file mode 100644 index cca5a46f58e..00000000000 --- a/client/features/rollup/scroll/pages/deposits/ScrollL2Deposits.tsx +++ /dev/null @@ -1,90 +0,0 @@ -// SPDX-License-Identifier: LicenseRef-Blockscout - -import { Box } from '@chakra-ui/react'; -import React from 'react'; - -import useApiQuery from 'client/api/hooks/useApiQuery'; - -import { layerLabels } from 'client/features/rollup/common/utils/layer'; -import { SCROLL_L2_MESSAGE_ITEM } from 'client/features/rollup/scroll/stubs'; - -import { generateListStub } from 'stubs/utils'; -import { Skeleton } from 'toolkit/chakra/skeleton'; -import { rightLineArrow, nbsp } from 'toolkit/utils/htmlEntities'; -import { ACTION_BAR_HEIGHT_DESKTOP } from 'ui/shared/ActionBar'; -import DataListDisplay from 'ui/shared/DataListDisplay'; -import PageTitle from 'ui/shared/Page/PageTitle'; -import useQueryWithPages from 'ui/shared/pagination/useQueryWithPages'; -import StickyPaginationWithText from 'ui/shared/StickyPaginationWithText'; - -import ScrollL2DepositsListItem from './ScrollL2DepositsListItem'; -import ScrollL2DepositsTable from './ScrollL2DepositsTable'; - -const ScrollL2Deposits = () => { - const { data, isError, isPlaceholderData, pagination } = useQueryWithPages({ - resourceName: 'general:scroll_l2_deposits', - options: { - placeholderData: generateListStub<'general:scroll_l2_deposits'>( - SCROLL_L2_MESSAGE_ITEM, - 50, - { next_page_params: { items_count: 50, id: 1 } }, - ), - }, - }); - - const countersQuery = useApiQuery('general:scroll_l2_deposits_count', { - queryOptions: { - placeholderData: 1927029, - }, - }); - - const content = data?.items ? ( - <> - - { data.items.map(((item, index) => ( - - ))) } - - - - - - ) : null; - - const text = (() => { - if (countersQuery.isError) { - return null; - } - - return ( - - A total of { countersQuery.data?.toLocaleString() } deposits found - - ); - })(); - - const actionBar = ; - - return ( - <> - - - { content } - - - ); -}; - -export default ScrollL2Deposits; diff --git a/client/features/rollup/scroll/pages/deposits/ScrollL2DepositsListItem.tsx b/client/features/rollup/scroll/pages/deposits/ScrollL2DepositsListItem.tsx deleted file mode 100644 index f2bd1eaa3df..00000000000 --- a/client/features/rollup/scroll/pages/deposits/ScrollL2DepositsListItem.tsx +++ /dev/null @@ -1,94 +0,0 @@ -// SPDX-License-Identifier: LicenseRef-Blockscout - -import { chakra } from '@chakra-ui/react'; -import React from 'react'; - -import type { ScrollL2MessageItem } from 'client/features/rollup/scroll/types/api'; - -import TxEntity from 'client/slices/tx/components/entity/TxEntity'; - -import BlockEntityL1 from 'client/features/rollup/common/components/BlockEntityL1'; -import TxEntityL1 from 'client/features/rollup/common/components/TxEntityL1'; -import { layerLabels } from 'client/features/rollup/common/utils/layer'; - -import config from 'configs/app'; -import { Skeleton } from 'toolkit/chakra/skeleton'; -import ListItemMobileGrid from 'ui/shared/ListItemMobile/ListItemMobileGrid'; -import TimeWithTooltip from 'ui/shared/time/TimeWithTooltip'; -import NativeCoinValue from 'ui/shared/value/NativeCoinValue'; - -const rollupFeature = config.features.rollup; - -type Props = { item: ScrollL2MessageItem; isLoading?: boolean }; - -const ScrollL2DepositsListItem = ({ item, isLoading }: Props) => { - if (!rollupFeature.isEnabled || rollupFeature.type !== 'scroll') { - return null; - } - - return ( - - - { layerLabels.parent } block - - - - - Index - - - { item.id } - - - - { layerLabels.parent } txn hash - - - - - Age - - - - - { layerLabels.current } txn hash - - { item.completion_transaction_hash ? ( - - ) : ( - - Pending Claim - - ) } - - - Value - - - - - - ); -}; - -export default ScrollL2DepositsListItem; diff --git a/client/features/rollup/scroll/pages/deposits/ScrollL2DepositsTableItem.tsx b/client/features/rollup/scroll/pages/deposits/ScrollL2DepositsTableItem.tsx deleted file mode 100644 index ee43a608ec1..00000000000 --- a/client/features/rollup/scroll/pages/deposits/ScrollL2DepositsTableItem.tsx +++ /dev/null @@ -1,84 +0,0 @@ -// SPDX-License-Identifier: LicenseRef-Blockscout - -import { chakra } from '@chakra-ui/react'; -import React from 'react'; - -import type { ScrollL2MessageItem } from 'client/features/rollup/scroll/types/api'; - -import TxEntity from 'client/slices/tx/components/entity/TxEntity'; - -import BlockEntityL1 from 'client/features/rollup/common/components/BlockEntityL1'; -import TxEntityL1 from 'client/features/rollup/common/components/TxEntityL1'; - -import config from 'configs/app'; -import { Skeleton } from 'toolkit/chakra/skeleton'; -import { TableCell, TableRow } from 'toolkit/chakra/table'; -import TimeWithTooltip from 'ui/shared/time/TimeWithTooltip'; -import NativeCoinValue from 'ui/shared/value/NativeCoinValue'; - -const rollupFeature = config.features.rollup; - -type Props = { item: ScrollL2MessageItem; isLoading?: boolean }; - -const ScrollL2DepositsTableItem = ({ item, isLoading }: Props) => { - if (!rollupFeature.isEnabled || rollupFeature.type !== 'scroll') { - return null; - } - - return ( - - - - - - - { item.id } - - - - - - - - - - { item.completion_transaction_hash ? ( - - ) : ( - - Pending Claim - - ) } - - - - - - ); -}; - -export default ScrollL2DepositsTableItem; diff --git a/client/features/rollup/scroll/pages/deposits/__screenshots__/ScrollL2Deposits.pw.tsx_default_base-view-1.png b/client/features/rollup/scroll/pages/deposits/__screenshots__/ScrollL2Deposits.pw.tsx_default_base-view-1.png deleted file mode 100644 index 4aad7e643d3..00000000000 Binary files a/client/features/rollup/scroll/pages/deposits/__screenshots__/ScrollL2Deposits.pw.tsx_default_base-view-1.png and /dev/null differ diff --git a/client/features/rollup/scroll/pages/deposits/__screenshots__/ScrollL2Deposits.pw.tsx_default_mobile-base-view-1.png b/client/features/rollup/scroll/pages/deposits/__screenshots__/ScrollL2Deposits.pw.tsx_default_mobile-base-view-1.png deleted file mode 100644 index 20b9f67a073..00000000000 Binary files a/client/features/rollup/scroll/pages/deposits/__screenshots__/ScrollL2Deposits.pw.tsx_default_mobile-base-view-1.png and /dev/null differ diff --git a/client/features/rollup/scroll/pages/withdrawals/ScrollL2Withdrawals.pw.tsx b/client/features/rollup/scroll/pages/withdrawals/ScrollL2Withdrawals.pw.tsx deleted file mode 100644 index 27d929f464f..00000000000 --- a/client/features/rollup/scroll/pages/withdrawals/ScrollL2Withdrawals.pw.tsx +++ /dev/null @@ -1,34 +0,0 @@ -import React from 'react'; - -import * as messagesMock from 'client/features/rollup/scroll/mocks/messages'; - -import { ENVS_MAP } from 'playwright/fixtures/mockEnvs'; -import { test, expect, devices } from 'playwright/lib'; - -import ScrollL2Withdrawals from './ScrollL2Withdrawals'; - -test('base view', async({ render, mockApiResponse, mockEnvs, mockTextAd }) => { - await mockTextAd(); - await mockEnvs(ENVS_MAP.scrollRollup); - await mockApiResponse('general:scroll_l2_withdrawals', messagesMock.baseResponse); - await mockApiResponse('general:scroll_l2_withdrawals_count', 3971111); - - const component = await render(); - - await expect(component).toHaveScreenshot(); -}); - -test.describe('mobile', () => { - test.use({ viewport: devices['iPhone 13 Pro'].viewport }); - - test('base view', async({ render, mockApiResponse, mockEnvs, mockTextAd }) => { - await mockTextAd(); - await mockEnvs(ENVS_MAP.scrollRollup); - await mockApiResponse('general:scroll_l2_withdrawals', messagesMock.baseResponse); - await mockApiResponse('general:scroll_l2_withdrawals_count', 3971111); - - const component = await render(); - - await expect(component).toHaveScreenshot(); - }); -}); diff --git a/client/features/rollup/scroll/pages/withdrawals/ScrollL2Withdrawals.tsx b/client/features/rollup/scroll/pages/withdrawals/ScrollL2Withdrawals.tsx deleted file mode 100644 index d1bf803d7f4..00000000000 --- a/client/features/rollup/scroll/pages/withdrawals/ScrollL2Withdrawals.tsx +++ /dev/null @@ -1,90 +0,0 @@ -// SPDX-License-Identifier: LicenseRef-Blockscout - -import { Box } from '@chakra-ui/react'; -import React from 'react'; - -import useApiQuery from 'client/api/hooks/useApiQuery'; - -import { layerLabels } from 'client/features/rollup/common/utils/layer'; -import { SCROLL_L2_MESSAGE_ITEM } from 'client/features/rollup/scroll/stubs'; - -import { generateListStub } from 'stubs/utils'; -import { Skeleton } from 'toolkit/chakra/skeleton'; -import { rightLineArrow, nbsp } from 'toolkit/utils/htmlEntities'; -import { ACTION_BAR_HEIGHT_DESKTOP } from 'ui/shared/ActionBar'; -import DataListDisplay from 'ui/shared/DataListDisplay'; -import PageTitle from 'ui/shared/Page/PageTitle'; -import useQueryWithPages from 'ui/shared/pagination/useQueryWithPages'; -import StickyPaginationWithText from 'ui/shared/StickyPaginationWithText'; - -import ScrollL2WithdrawalsListItem from './ScrollL2WithdrawalsListItem'; -import ScrollL2WithdrawalsTable from './ScrollL2WithdrawalsTable'; - -const ScrollL2Withdrawals = () => { - const { data, isError, isPlaceholderData, pagination } = useQueryWithPages({ - resourceName: 'general:scroll_l2_withdrawals', - options: { - placeholderData: generateListStub<'general:scroll_l2_withdrawals'>( - SCROLL_L2_MESSAGE_ITEM, - 50, - { next_page_params: { items_count: 50, id: 1 } }, - ), - }, - }); - - const countersQuery = useApiQuery('general:scroll_l2_withdrawals_count', { - queryOptions: { - placeholderData: 1927029, - }, - }); - - const content = data?.items ? ( - <> - - { data.items.map(((item, index) => ( - - ))) } - - - - - - ) : null; - - const text = (() => { - if (countersQuery.isError) { - return null; - } - - return ( - - A total of { countersQuery.data?.toLocaleString() } withdrawals found - - ); - })(); - - const actionBar = ; - - return ( - <> - - - { content } - - - ); -}; - -export default ScrollL2Withdrawals; diff --git a/client/features/rollup/scroll/pages/withdrawals/ScrollL2WithdrawalsListItem.tsx b/client/features/rollup/scroll/pages/withdrawals/ScrollL2WithdrawalsListItem.tsx deleted file mode 100644 index cb9a718a387..00000000000 --- a/client/features/rollup/scroll/pages/withdrawals/ScrollL2WithdrawalsListItem.tsx +++ /dev/null @@ -1,94 +0,0 @@ -// SPDX-License-Identifier: LicenseRef-Blockscout - -import { chakra } from '@chakra-ui/react'; -import React from 'react'; - -import type { ScrollL2MessageItem } from 'client/features/rollup/scroll/types/api'; - -import BlockEntity from 'client/slices/block/components/entity/BlockEntity'; -import TxEntity from 'client/slices/tx/components/entity/TxEntity'; - -import TxEntityL1 from 'client/features/rollup/common/components/TxEntityL1'; -import { layerLabels } from 'client/features/rollup/common/utils/layer'; - -import config from 'configs/app'; -import { Skeleton } from 'toolkit/chakra/skeleton'; -import ListItemMobileGrid from 'ui/shared/ListItemMobile/ListItemMobileGrid'; -import TimeWithTooltip from 'ui/shared/time/TimeWithTooltip'; -import NativeCoinValue from 'ui/shared/value/NativeCoinValue'; - -const rollupFeature = config.features.rollup; - -type Props = { item: ScrollL2MessageItem; isLoading?: boolean }; - -const ScrollL2WithdrawalsListItem = ({ item, isLoading }: Props) => { - if (!rollupFeature.isEnabled || rollupFeature.type !== 'scroll') { - return null; - } - - return ( - - - { layerLabels.current } block - - - - - Index - - - { item.id } - - - - { layerLabels.current } txn hash - - - - - Age - - - - - { layerLabels.parent } txn hash - - { item.completion_transaction_hash ? ( - - ) : ( - - Pending Claim - - ) } - - - Value - - - - - - ); -}; - -export default ScrollL2WithdrawalsListItem; diff --git a/client/features/rollup/scroll/pages/withdrawals/ScrollL2WithdrawalsTableItem.tsx b/client/features/rollup/scroll/pages/withdrawals/ScrollL2WithdrawalsTableItem.tsx deleted file mode 100644 index 54d850fc5d1..00000000000 --- a/client/features/rollup/scroll/pages/withdrawals/ScrollL2WithdrawalsTableItem.tsx +++ /dev/null @@ -1,84 +0,0 @@ -// SPDX-License-Identifier: LicenseRef-Blockscout - -import { chakra } from '@chakra-ui/react'; -import React from 'react'; - -import type { ScrollL2MessageItem } from 'client/features/rollup/scroll/types/api'; - -import BlockEntity from 'client/slices/block/components/entity/BlockEntity'; -import TxEntity from 'client/slices/tx/components/entity/TxEntity'; - -import TxEntityL1 from 'client/features/rollup/common/components/TxEntityL1'; - -import config from 'configs/app'; -import { Skeleton } from 'toolkit/chakra/skeleton'; -import { TableCell, TableRow } from 'toolkit/chakra/table'; -import TimeWithTooltip from 'ui/shared/time/TimeWithTooltip'; -import NativeCoinValue from 'ui/shared/value/NativeCoinValue'; - -const rollupFeature = config.features.rollup; - -type Props = { item: ScrollL2MessageItem; isLoading?: boolean }; - -const ScrollL2WithdrawalsTableItem = ({ item, isLoading }: Props) => { - if (!rollupFeature.isEnabled || rollupFeature.type !== 'scroll') { - return null; - } - - return ( - - - - - - - { item.id } - - - - - - - - - - { item.completion_transaction_hash ? ( - - ) : ( - - Pending Claim - - ) } - - - - - - ); -}; - -export default ScrollL2WithdrawalsTableItem; diff --git a/client/features/rollup/scroll/pages/withdrawals/__screenshots__/ScrollL2Withdrawals.pw.tsx_default_base-view-1.png b/client/features/rollup/scroll/pages/withdrawals/__screenshots__/ScrollL2Withdrawals.pw.tsx_default_base-view-1.png deleted file mode 100644 index f95befa2e08..00000000000 Binary files a/client/features/rollup/scroll/pages/withdrawals/__screenshots__/ScrollL2Withdrawals.pw.tsx_default_base-view-1.png and /dev/null differ diff --git a/client/features/rollup/scroll/pages/withdrawals/__screenshots__/ScrollL2Withdrawals.pw.tsx_default_mobile-base-view-1.png b/client/features/rollup/scroll/pages/withdrawals/__screenshots__/ScrollL2Withdrawals.pw.tsx_default_mobile-base-view-1.png deleted file mode 100644 index d5088a4aa8d..00000000000 Binary files a/client/features/rollup/scroll/pages/withdrawals/__screenshots__/ScrollL2Withdrawals.pw.tsx_default_mobile-base-view-1.png and /dev/null differ diff --git a/client/features/rollup/scroll/stubs.ts b/client/features/rollup/scroll/stubs.ts deleted file mode 100644 index 03956241066..00000000000 --- a/client/features/rollup/scroll/stubs.ts +++ /dev/null @@ -1,32 +0,0 @@ -import type { ScrollL2MessageItem, ScrollL2TxnBatch } from 'client/features/rollup/scroll/types/api'; - -import { TX_HASH } from 'client/slices/tx/stubs/tx'; - -export const SCROLL_L2_TXN_BATCH: ScrollL2TxnBatch = { - commitment_transaction: { - block_number: 4053979, - hash: '0xd04d626495ef69abd37ae3ea585ed03319a3d3b50cf10874f7f36741c7b45a18', - timestamp: '2023-08-09T08:09:12.000000Z', - }, - confirmation_transaction: { - block_number: null, - hash: null, - timestamp: null, - }, - end_block_number: 1711, - number: 273, - start_block_number: 1697, - transactions_count: 15, - data_availability: { - batch_data_container: 'in_blob4844', - }, -}; - -export const SCROLL_L2_MESSAGE_ITEM: ScrollL2MessageItem = { - id: 930795, - origination_transaction_block_number: 20639178, - origination_transaction_hash: TX_HASH, - origination_timestamp: '2024-08-30T05:03:23.000000Z', - completion_transaction_hash: 'TX_HASH', - value: '5084131319054877748', -}; diff --git a/client/features/rollup/scroll/types/api.ts b/client/features/rollup/scroll/types/api.ts deleted file mode 100644 index 41f4f85a681..00000000000 --- a/client/features/rollup/scroll/types/api.ts +++ /dev/null @@ -1,96 +0,0 @@ -// SPDX-License-Identifier: LicenseRef-Blockscout - -import type { Block } from 'client/slices/block/types/api'; -import type { TransactionFee, Transaction } from 'client/slices/tx/types/api'; - -export const SCROLL_L2_BLOCK_STATUSES = [ - 'Confirmed by Sequencer' as const, - 'Committed' as const, - 'Finalized' as const, -]; - -export type ScrollL2BlockStatus = typeof SCROLL_L2_BLOCK_STATUSES[number]; - -export interface TransactionScroll { - scroll?: { - l1_fee: string; - l2_fee: TransactionFee; - l1_fee_commit_scalar: number; - l1_base_fee: number; - l1_blob_base_fee: number; - l1_fee_scalar: number; - l1_fee_overhead: number; - l1_fee_blob_scalar: number; - l1_gas_used: number; - l2_block_status: ScrollL2BlockStatus; - queue_index: number; - }; -} - -export interface ScrollL2BatchesResponse { - items: Array; - next_page_params: { - items_count: number; - number: number; - }; -} - -type ScrollL2TxnBatchCommitmentTransaction = { - block_number: number; - hash: string; - timestamp: string; -}; - -type ScrollL2TxnBatchConfirmationTransaction = { - block_number: number | null; - hash: string | null; - timestamp: string | null; -}; - -export type ScrollL2TxnBatch = { - number: number; - commitment_transaction: ScrollL2TxnBatchCommitmentTransaction; - confirmation_transaction: ScrollL2TxnBatchConfirmationTransaction; - start_block_number: number; - end_block_number: number; - transactions_count: number | null; - data_availability: { - batch_data_container: 'in_blob4844' | 'in_calldata'; - }; -}; - -export type ScrollL2TxnBatchTxs = { - items: Array; - next_page_params: { - batch_number: number; - block_number: number; - index: number; - items_count: number; - } | null; -}; - -export type ScrollL2TxnBatchBlocks = { - items: Array; - next_page_params: { - batch_number: number; - block_number: number; - items_count: number; - } | null; -}; - -export type ScrollL2MessagesResponse = { - items: Array; - next_page_params: { - id: number; - items_count: number; - } | null; -}; - -export type ScrollL2MessageItem = { - id: number; - origination_transaction_block_number: number; - origination_transaction_hash: string; - origination_timestamp: string; - completion_transaction_hash: string | null; - value: string; -}; diff --git a/client/features/rollup/shibarium/mocks/deposits.ts b/client/features/rollup/shibarium/mocks/deposits.ts deleted file mode 100644 index 9731077eb7a..00000000000 --- a/client/features/rollup/shibarium/mocks/deposits.ts +++ /dev/null @@ -1,61 +0,0 @@ -import type { ShibariumDepositsResponse } from 'client/features/rollup/shibarium/types/api'; - -export const data: ShibariumDepositsResponse = { - items: [ - { - l1_block_number: 8382841, - timestamp: '2022-05-27T01:13:48.000000Z', - l1_transaction_hash: '0xaf3e5f4ef03eac22a622b3434c5dc9f4465aa291900a86bcf0ad9fb14429f05e', - user: { - hash: '0x6197d1eef304eb5284a0f6720f79403b4e9bf3a5', - implementations: null, - is_contract: false, - is_verified: false, - name: null, - private_tags: [], - public_tags: [], - watchlist_names: [], - ens_domain_name: null, - }, - l2_transaction_hash: '0xb9212c76069b926917816767e4c5a0ef80e519b1ac1c3d3fb5818078f4984667', - }, - { - l1_block_number: 8382841, - timestamp: '2022-05-27T01:13:48.000000Z', - l1_transaction_hash: '0xaf3e5f4ef03eac22a622b3434c5dc9f4465aa291900a86bcf0ad9fb14429f05e', - user: { - hash: '0x6197d1eef304eb5284a0f6720f79403b4e9bf3a5', - implementations: null, - is_contract: false, - is_verified: false, - name: null, - private_tags: [], - public_tags: [], - watchlist_names: [], - ens_domain_name: null, - }, - l2_transaction_hash: '0xb9212c76069b926917816767e4c5a0ef80e519b1ac1c3d3fb5818078f4984667', - }, - { - l1_block_number: 8382841, - timestamp: '2022-05-27T01:13:48.000000Z', - l1_transaction_hash: '0xaf3e5f4ef03eac22a622b3434c5dc9f4465aa291900a86bcf0ad9fb14429f05e', - user: { - hash: '0x6197d1eef304eb5284a0f6720f79403b4e9bf3a5', - implementations: null, - is_contract: false, - is_verified: false, - name: null, - private_tags: [], - public_tags: [], - watchlist_names: [], - ens_domain_name: null, - }, - l2_transaction_hash: '0xb9212c76069b926917816767e4c5a0ef80e519b1ac1c3d3fb5818078f4984667', - }, - ], - next_page_params: { - items_count: 50, - block_number: 8382363, - }, -}; diff --git a/client/features/rollup/shibarium/mocks/withdrawals.ts b/client/features/rollup/shibarium/mocks/withdrawals.ts deleted file mode 100644 index 1d52403815d..00000000000 --- a/client/features/rollup/shibarium/mocks/withdrawals.ts +++ /dev/null @@ -1,61 +0,0 @@ -import type { ShibariumWithdrawalsResponse } from 'client/features/rollup/shibarium/types/api'; - -export const data: ShibariumWithdrawalsResponse = { - items: [ - { - l2_block_number: 8382841, - timestamp: '2022-05-27T01:13:48.000000Z', - l1_transaction_hash: '0xaf3e5f4ef03eac22a622b3434c5dc9f4465aa291900a86bcf0ad9fb14429f05e', - user: { - hash: '0x6197d1eef304eb5284a0f6720f79403b4e9bf3a5', - implementations: null, - is_contract: false, - is_verified: false, - name: null, - private_tags: [], - public_tags: [], - watchlist_names: [], - ens_domain_name: null, - }, - l2_transaction_hash: '0xb9212c76069b926917816767e4c5a0ef80e519b1ac1c3d3fb5818078f4984667', - }, - { - l2_block_number: 8382841, - timestamp: '2022-05-27T01:13:48.000000Z', - l1_transaction_hash: '0xaf3e5f4ef03eac22a622b3434c5dc9f4465aa291900a86bcf0ad9fb14429f05e', - user: { - hash: '0x6197d1eef304eb5284a0f6720f79403b4e9bf3a5', - implementations: null, - is_contract: false, - is_verified: false, - name: null, - private_tags: [], - public_tags: [], - watchlist_names: [], - ens_domain_name: null, - }, - l2_transaction_hash: '0xb9212c76069b926917816767e4c5a0ef80e519b1ac1c3d3fb5818078f4984667', - }, - { - l2_block_number: 8382841, - timestamp: '2022-05-27T01:13:48.000000Z', - l1_transaction_hash: '0xaf3e5f4ef03eac22a622b3434c5dc9f4465aa291900a86bcf0ad9fb14429f05e', - user: { - hash: '0x6197d1eef304eb5284a0f6720f79403b4e9bf3a5', - implementations: null, - is_contract: false, - is_verified: false, - name: null, - private_tags: [], - public_tags: [], - watchlist_names: [], - ens_domain_name: null, - }, - l2_transaction_hash: '0xb9212c76069b926917816767e4c5a0ef80e519b1ac1c3d3fb5818078f4984667', - }, - ], - next_page_params: { - items_count: 50, - block_number: 8382363, - }, -}; diff --git a/client/features/rollup/shibarium/pages/deposits/DepositsTableItem.tsx b/client/features/rollup/shibarium/pages/deposits/DepositsTableItem.tsx deleted file mode 100644 index a0233c943b6..00000000000 --- a/client/features/rollup/shibarium/pages/deposits/DepositsTableItem.tsx +++ /dev/null @@ -1,74 +0,0 @@ -// SPDX-License-Identifier: LicenseRef-Blockscout - -import React from 'react'; - -import type { ShibariumDepositsItem } from 'client/features/rollup/shibarium/types/api'; - -import AddressStringOrParam from 'client/slices/address/components/entity/AddressStringOrParam'; -import TxEntity from 'client/slices/tx/components/entity/TxEntity'; - -import BlockEntityL1 from 'client/features/rollup/common/components/BlockEntityL1'; -import TxEntityL1 from 'client/features/rollup/common/components/TxEntityL1'; - -import config from 'configs/app'; -import { TableCell, TableRow } from 'toolkit/chakra/table'; -import TimeWithTooltip from 'ui/shared/time/TimeWithTooltip'; - -const feature = config.features.rollup; - -type Props = { item: ShibariumDepositsItem; isLoading?: boolean }; - -const DepositsTableItem = ({ item, isLoading }: Props) => { - - if (!(feature.isEnabled && feature.type === 'shibarium')) { - return null; - } - - return ( - - - - - - - - - - - - - - - - - - ); -}; - -export default DepositsTableItem; diff --git a/client/features/rollup/shibarium/pages/deposits/ShibariumDeposits.pw.tsx b/client/features/rollup/shibarium/pages/deposits/ShibariumDeposits.pw.tsx deleted file mode 100644 index 53e4490e26b..00000000000 --- a/client/features/rollup/shibarium/pages/deposits/ShibariumDeposits.pw.tsx +++ /dev/null @@ -1,20 +0,0 @@ -import React from 'react'; - -import { data as depositsData } from 'client/features/rollup/shibarium/mocks/deposits'; - -import { ENVS_MAP } from 'playwright/fixtures/mockEnvs'; -import { test, expect } from 'playwright/lib'; - -import ShibariumDeposits from './ShibariumDeposits'; - -test('base view +@mobile', async({ render, mockApiResponse, mockEnvs, mockTextAd }) => { - test.slow(); - await mockTextAd(); - await mockEnvs(ENVS_MAP.shibariumRollup); - await mockApiResponse('general:shibarium_deposits', depositsData); - await mockApiResponse('general:shibarium_deposits_count', 3971111); - - const component = await render(); - - await expect(component).toHaveScreenshot({ timeout: 10_000 }); -}); diff --git a/client/features/rollup/shibarium/pages/deposits/ShibariumDeposits.tsx b/client/features/rollup/shibarium/pages/deposits/ShibariumDeposits.tsx deleted file mode 100644 index 3fb75489dc6..00000000000 --- a/client/features/rollup/shibarium/pages/deposits/ShibariumDeposits.tsx +++ /dev/null @@ -1,95 +0,0 @@ -// SPDX-License-Identifier: LicenseRef-Blockscout - -import { Box } from '@chakra-ui/react'; -import React from 'react'; - -import useApiQuery from 'client/api/hooks/useApiQuery'; - -import { layerLabels } from 'client/features/rollup/common/utils/layer'; -import { SHIBARIUM_DEPOSIT_ITEM } from 'client/features/rollup/shibarium/stubs'; - -import { generateListStub } from 'stubs/utils'; -import { Skeleton } from 'toolkit/chakra/skeleton'; -import { rightLineArrow, nbsp } from 'toolkit/utils/htmlEntities'; -import { ACTION_BAR_HEIGHT_DESKTOP } from 'ui/shared/ActionBar'; -import DataListDisplay from 'ui/shared/DataListDisplay'; -import PageTitle from 'ui/shared/Page/PageTitle'; -import useQueryWithPages from 'ui/shared/pagination/useQueryWithPages'; -import StickyPaginationWithText from 'ui/shared/StickyPaginationWithText'; - -import DepositsListItem from './DepositsListItem'; -import DepositsTable from './DepositsTable'; - -const ShibariumDeposits = () => { - const { data, isError, isPlaceholderData, pagination } = useQueryWithPages({ - resourceName: 'general:shibarium_deposits', - options: { - placeholderData: generateListStub<'general:shibarium_deposits'>( - SHIBARIUM_DEPOSIT_ITEM, - 50, - { - next_page_params: { - items_count: 50, - block_number: 9045200, - }, - }, - ), - }, - }); - - const countersQuery = useApiQuery('general:shibarium_deposits_count', { - queryOptions: { - placeholderData: 1927029, - }, - }); - - const content = data?.items ? ( - <> - - { data.items.map(((item, index) => ( - - ))) } - - - - - - ) : null; - - const text = (() => { - if (countersQuery.isError) { - return null; - } - - return ( - - A total of { countersQuery.data?.toLocaleString() } deposits found - - ); - })(); - - const actionBar = ; - - return ( - <> - - - { content } - - - ); -}; - -export default ShibariumDeposits; diff --git a/client/features/rollup/shibarium/pages/deposits/__screenshots__/ShibariumDeposits.pw.tsx_default_base-view-mobile-1.png b/client/features/rollup/shibarium/pages/deposits/__screenshots__/ShibariumDeposits.pw.tsx_default_base-view-mobile-1.png deleted file mode 100644 index 5eea741f716..00000000000 Binary files a/client/features/rollup/shibarium/pages/deposits/__screenshots__/ShibariumDeposits.pw.tsx_default_base-view-mobile-1.png and /dev/null differ diff --git a/client/features/rollup/shibarium/pages/deposits/__screenshots__/ShibariumDeposits.pw.tsx_mobile_base-view-mobile-1.png b/client/features/rollup/shibarium/pages/deposits/__screenshots__/ShibariumDeposits.pw.tsx_mobile_base-view-mobile-1.png deleted file mode 100644 index 4cc041b1c77..00000000000 Binary files a/client/features/rollup/shibarium/pages/deposits/__screenshots__/ShibariumDeposits.pw.tsx_mobile_base-view-mobile-1.png and /dev/null differ diff --git a/client/features/rollup/shibarium/pages/withdrawals/ShibariumWithdrawals.pw.tsx b/client/features/rollup/shibarium/pages/withdrawals/ShibariumWithdrawals.pw.tsx deleted file mode 100644 index 646312aaa28..00000000000 --- a/client/features/rollup/shibarium/pages/withdrawals/ShibariumWithdrawals.pw.tsx +++ /dev/null @@ -1,24 +0,0 @@ -import React from 'react'; - -import { data as withdrawalsData } from 'client/features/rollup/shibarium/mocks/withdrawals'; - -import { ENVS_MAP } from 'playwright/fixtures/mockEnvs'; -import { test, expect } from 'playwright/lib'; - -import ShibariumWithdrawals from './ShibariumWithdrawals'; - -test('base view +@mobile', async({ render, mockApiResponse, mockEnvs, mockTextAd }) => { - // test on mobile is flaky - // my assumption is there is not enough time to calculate hashes truncation so component is unstable - // so I raised the test timeout to check if it helps - test.slow(); - - await mockTextAd(); - await mockEnvs(ENVS_MAP.shibariumRollup); - await mockApiResponse('general:shibarium_withdrawals', withdrawalsData); - await mockApiResponse('general:shibarium_withdrawals_count', 397); - - const component = await render(); - - await expect(component).toHaveScreenshot({ timeout: 10_000 }); -}); diff --git a/client/features/rollup/shibarium/pages/withdrawals/ShibariumWithdrawals.tsx b/client/features/rollup/shibarium/pages/withdrawals/ShibariumWithdrawals.tsx deleted file mode 100644 index 6a47f9b1618..00000000000 --- a/client/features/rollup/shibarium/pages/withdrawals/ShibariumWithdrawals.tsx +++ /dev/null @@ -1,92 +0,0 @@ -// SPDX-License-Identifier: LicenseRef-Blockscout - -import { Box } from '@chakra-ui/react'; -import React from 'react'; - -import useApiQuery from 'client/api/hooks/useApiQuery'; - -import { layerLabels } from 'client/features/rollup/common/utils/layer'; -import { SHIBARIUM_WITHDRAWAL_ITEM } from 'client/features/rollup/shibarium/stubs'; - -import { generateListStub } from 'stubs/utils'; -import { Skeleton } from 'toolkit/chakra/skeleton'; -import { rightLineArrow, nbsp } from 'toolkit/utils/htmlEntities'; -import { ACTION_BAR_HEIGHT_DESKTOP } from 'ui/shared/ActionBar'; -import DataListDisplay from 'ui/shared/DataListDisplay'; -import PageTitle from 'ui/shared/Page/PageTitle'; -import useQueryWithPages from 'ui/shared/pagination/useQueryWithPages'; -import StickyPaginationWithText from 'ui/shared/StickyPaginationWithText'; - -import WithdrawalsListItem from './WithdrawalsListItem'; -import WithdrawalsTable from './WithdrawalsTable'; - -const ShibariumWithdrawals = () => { - const { data, isError, isPlaceholderData, pagination } = useQueryWithPages({ - resourceName: 'general:shibarium_withdrawals', - options: { - placeholderData: generateListStub<'general:shibarium_withdrawals'>( - SHIBARIUM_WITHDRAWAL_ITEM, - 50, - { - next_page_params: { - items_count: 50, - block_number: 123, - }, - }, - ), - }, - }); - - const countersQuery = useApiQuery('general:shibarium_withdrawals_count', { - queryOptions: { - placeholderData: 23700, - }, - }); - - const content = data?.items ? ( - <> - - { data.items.map(((item, index) => ( - - ))) } - - - - - - ) : null; - - const text = (() => { - if (countersQuery.isError) { - return null; - } - - return ( - - A total of { countersQuery.data?.toLocaleString() } withdrawals found - - ); - })(); - - const actionBar = ; - - return ( - <> - - - { content } - - - ); -}; - -export default ShibariumWithdrawals; diff --git a/client/features/rollup/shibarium/pages/withdrawals/WithdrawalsTableItem.tsx b/client/features/rollup/shibarium/pages/withdrawals/WithdrawalsTableItem.tsx deleted file mode 100644 index 0dfb9675907..00000000000 --- a/client/features/rollup/shibarium/pages/withdrawals/WithdrawalsTableItem.tsx +++ /dev/null @@ -1,73 +0,0 @@ -// SPDX-License-Identifier: LicenseRef-Blockscout - -import React from 'react'; - -import type { ShibariumWithdrawalsItem } from 'client/features/rollup/shibarium/types/api'; - -import AddressStringOrParam from 'client/slices/address/components/entity/AddressStringOrParam'; -import BlockEntity from 'client/slices/block/components/entity/BlockEntity'; -import TxEntity from 'client/slices/tx/components/entity/TxEntity'; - -import TxEntityL1 from 'client/features/rollup/common/components/TxEntityL1'; - -import config from 'configs/app'; -import { TableCell, TableRow } from 'toolkit/chakra/table'; -import TimeWithTooltip from 'ui/shared/time/TimeWithTooltip'; - -const feature = config.features.rollup; - -type Props = { item: ShibariumWithdrawalsItem; isLoading?: boolean }; - -const WithdrawalsTableItem = ({ item, isLoading }: Props) => { - if (!(feature.isEnabled && feature.type === 'shibarium')) { - return null; - } - - return ( - - - - - - - - - - - - - - - - - - ); -}; - -export default WithdrawalsTableItem; diff --git a/client/features/rollup/shibarium/pages/withdrawals/__screenshots__/ShibariumWithdrawals.pw.tsx_default_base-view-mobile-1.png b/client/features/rollup/shibarium/pages/withdrawals/__screenshots__/ShibariumWithdrawals.pw.tsx_default_base-view-mobile-1.png deleted file mode 100644 index e50c1cd1a88..00000000000 Binary files a/client/features/rollup/shibarium/pages/withdrawals/__screenshots__/ShibariumWithdrawals.pw.tsx_default_base-view-mobile-1.png and /dev/null differ diff --git a/client/features/rollup/shibarium/pages/withdrawals/__screenshots__/ShibariumWithdrawals.pw.tsx_mobile_base-view-mobile-1.png b/client/features/rollup/shibarium/pages/withdrawals/__screenshots__/ShibariumWithdrawals.pw.tsx_mobile_base-view-mobile-1.png deleted file mode 100644 index a7eb5748a4d..00000000000 Binary files a/client/features/rollup/shibarium/pages/withdrawals/__screenshots__/ShibariumWithdrawals.pw.tsx_mobile_base-view-mobile-1.png and /dev/null differ diff --git a/client/features/rollup/shibarium/stubs.ts b/client/features/rollup/shibarium/stubs.ts deleted file mode 100644 index 49ba191c0c6..00000000000 --- a/client/features/rollup/shibarium/stubs.ts +++ /dev/null @@ -1,20 +0,0 @@ -import type { ShibariumDepositsItem, ShibariumWithdrawalsItem } from 'client/features/rollup/shibarium/types/api'; - -import { ADDRESS_PARAMS } from 'client/slices/address/stubs/address-params'; -import { TX_HASH } from 'client/slices/tx/stubs/tx'; - -export const SHIBARIUM_DEPOSIT_ITEM: ShibariumDepositsItem = { - l1_block_number: 9045233, - l1_transaction_hash: TX_HASH, - l2_transaction_hash: TX_HASH, - timestamp: '2023-05-22T18:00:36.000000Z', - user: ADDRESS_PARAMS, -}; - -export const SHIBARIUM_WITHDRAWAL_ITEM: ShibariumWithdrawalsItem = { - l2_block_number: 9045233, - l1_transaction_hash: TX_HASH, - l2_transaction_hash: TX_HASH, - timestamp: '2023-05-22T18:00:36.000000Z', - user: ADDRESS_PARAMS, -}; diff --git a/client/features/rollup/shibarium/types/api.ts b/client/features/rollup/shibarium/types/api.ts deleted file mode 100644 index 3df9a09f350..00000000000 --- a/client/features/rollup/shibarium/types/api.ts +++ /dev/null @@ -1,35 +0,0 @@ -// SPDX-License-Identifier: LicenseRef-Blockscout - -import type { AddressParam } from 'client/slices/address/types/api'; - -export type ShibariumDepositsItem = { - l1_block_number: number; - l1_transaction_hash: string; - l2_transaction_hash: string; - timestamp: string; - user: AddressParam | string; -}; - -export type ShibariumDepositsResponse = { - items: Array; - next_page_params: { - items_count: number; - block_number: number; - }; -}; - -export type ShibariumWithdrawalsItem = { - l1_transaction_hash: string; - l2_block_number: number; - l2_transaction_hash: string; - timestamp: string; - user: AddressParam | string; -}; - -export type ShibariumWithdrawalsResponse = { - items: Array; - next_page_params: { - items_count: number; - block_number: number; - }; -}; diff --git a/client/features/rollup/zk-sync/components/ZkSyncL2TxnBatchStatus.tsx b/client/features/rollup/zk-sync/components/ZkSyncL2TxnBatchStatus.tsx deleted file mode 100644 index 09d4f065a30..00000000000 --- a/client/features/rollup/zk-sync/components/ZkSyncL2TxnBatchStatus.tsx +++ /dev/null @@ -1,34 +0,0 @@ -// SPDX-License-Identifier: LicenseRef-Blockscout - -import React from 'react'; - -import type { ZkSyncBatchStatus } from 'client/features/rollup/zk-sync/types/api'; - -import { layerLabels } from 'client/features/rollup/common/utils/layer'; - -import type { StatusTagType } from 'ui/shared/statusTag/StatusTag'; -import StatusTag from 'ui/shared/statusTag/StatusTag'; - -export interface Props { - status: ZkSyncBatchStatus; - isLoading?: boolean; -} - -const ZkSyncL2TxnBatchStatus = ({ status, isLoading }: Props) => { - let type: StatusTagType; - - switch (status) { - case 'Executed on L1': - type = 'ok'; - break; - default: - type = 'pending'; - break; - } - - const text = status.replace('L1', layerLabels.parent).replace('L2', layerLabels.current); - - return ; -}; - -export default ZkSyncL2TxnBatchStatus; diff --git a/client/features/rollup/zk-sync/mocks/txn-batch.ts b/client/features/rollup/zk-sync/mocks/txn-batch.ts deleted file mode 100644 index 0a55fff7798..00000000000 --- a/client/features/rollup/zk-sync/mocks/txn-batch.ts +++ /dev/null @@ -1,20 +0,0 @@ -import type { ZkSyncBatch } from 'client/features/rollup/zk-sync/types/api'; - -export const base: ZkSyncBatch = { - commit_transaction_hash: '0x7cd80c88977c2b310f79196b0b2136da18012be015ce80d0d9e9fe6cfad52b16', - commit_transaction_timestamp: '2022-03-19T09:37:38.726996Z', - end_block_number: 1245490, - execute_transaction_hash: '0x110b9a19afbabd5818a996ab2b493a9b23c888d73d95f1ab5272dbae503e103a', - execute_transaction_timestamp: '2022-03-19T10:29:05.358066Z', - l1_gas_price: '4173068062', - l1_transactions_count: 0, - l2_fair_gas_price: '100000000', - l2_transactions_count: 287, - number: 8051, - prove_transaction_hash: '0xb424162ba5afe17c710dceb5fc8d15d7d46a66223454dae8c74aa39f6802625b', - prove_transaction_timestamp: '2022-03-19T10:29:05.279177Z', - root_hash: '0x108c635b94f941fcabcb85500daec2f6be4f0747dff649b1cdd9dd7a7a264792', - start_block_number: 1245209, - status: 'Executed on L1', - timestamp: '2022-03-19T09:05:49.000000Z', -}; diff --git a/client/features/rollup/zk-sync/mocks/txn-batches.ts b/client/features/rollup/zk-sync/mocks/txn-batches.ts deleted file mode 100644 index d310bcf1eb5..00000000000 --- a/client/features/rollup/zk-sync/mocks/txn-batches.ts +++ /dev/null @@ -1,49 +0,0 @@ -import type { ZkSyncBatchesItem, ZkSyncBatchesResponse } from 'client/features/rollup/zk-sync/types/api'; - -export const sealed: ZkSyncBatchesItem = { - commit_transaction_hash: null, - commit_transaction_timestamp: null, - execute_transaction_hash: null, - execute_transaction_timestamp: null, - number: 8055, - prove_transaction_hash: null, - prove_transaction_timestamp: null, - status: 'Sealed on L2', - timestamp: '2022-03-19T12:53:36.000000Z', - transactions_count: 738, -}; - -export const sent: ZkSyncBatchesItem = { - commit_transaction_hash: '0x262e7215739d6a7e33b2c20b45a838801a0f5f080f20bec8e54eb078420c4661', - commit_transaction_timestamp: '2022-03-19T13:09:07.357570Z', - execute_transaction_hash: null, - execute_transaction_timestamp: null, - number: 8054, - prove_transaction_hash: null, - prove_transaction_timestamp: null, - status: 'Sent to L1', - timestamp: '2022-03-19T11:36:45.000000Z', - transactions_count: 766, -}; - -export const executed: ZkSyncBatchesItem = { - commit_transaction_hash: '0xa2628f477e1027ac1c60fa75c186b914647769ac1cb9c7e1cab50b13506a0035', - commit_transaction_timestamp: '2022-03-19T11:52:18.963070Z', - execute_transaction_hash: '0xb7bd6b2b17498c66d3f6e31ac3685133a81b7f728d4f6a6f42741daa257d0d68', - execute_transaction_timestamp: '2022-03-19T13:28:16.712706Z', - number: 8053, - prove_transaction_hash: '0x9d44f2b775bd771f8a53205755b3897929aa672d2cd419b3b988c16d41d4f21e', - prove_transaction_timestamp: '2022-03-19T13:28:16.603046Z', - status: 'Executed on L1', - timestamp: '2022-03-19T10:01:52.000000Z', - transactions_count: 1071, -}; - -export const baseResponse: ZkSyncBatchesResponse = { - items: [ - sealed, - sent, - executed, - ], - next_page_params: null, -}; diff --git a/client/features/rollup/zk-sync/pages/batch-details/ZkSyncL2TxnBatch.tsx b/client/features/rollup/zk-sync/pages/batch-details/ZkSyncL2TxnBatch.tsx deleted file mode 100644 index 14329b5975e..00000000000 --- a/client/features/rollup/zk-sync/pages/batch-details/ZkSyncL2TxnBatch.tsx +++ /dev/null @@ -1,94 +0,0 @@ -// SPDX-License-Identifier: LicenseRef-Blockscout - -import { useRouter } from 'next/router'; -import React from 'react'; - -import type { TabItemRegular } from 'toolkit/components/AdaptiveTabs/types'; - -import useApiQuery from 'client/api/hooks/useApiQuery'; - -import TxsWithFrontendSorting from 'client/slices/tx/pages/index/list/TxsWithFrontendSorting'; -import { TX } from 'client/slices/tx/stubs/tx'; - -import { ZKSYNC_L2_TXN_BATCH } from 'client/features/rollup/zk-sync/stubs'; - -import throwOnAbsentParamError from 'client/shared/errors/throw-on-absent-param-error'; -import throwOnResourceLoadError from 'client/shared/errors/throw-on-resource-load-error'; -import useIsMobile from 'client/shared/hooks/useIsMobile'; -import getQueryParamString from 'client/shared/router/get-query-param-string'; - -import { generateListStub } from 'stubs/utils'; -import RoutedTabs from 'toolkit/components/RoutedTabs/RoutedTabs'; -import TextAd from 'ui/shared/ad/TextAd'; -import PageTitle from 'ui/shared/Page/PageTitle'; -import Pagination from 'ui/shared/pagination/Pagination'; -import useQueryWithPages from 'ui/shared/pagination/useQueryWithPages'; - -import ZkSyncL2TxnBatchDetails from './ZkSyncL2TxnBatchDetails'; - -const TAB_LIST_PROPS = { - marginBottom: 0, - py: 5, - marginTop: -5, -}; - -const TABS_HEIGHT = 80; - -const ZkSyncL2TxnBatch = () => { - const router = useRouter(); - const number = getQueryParamString(router.query.number); - const tab = getQueryParamString(router.query.tab); - const isMobile = useIsMobile(); - - const batchQuery = useApiQuery('general:zksync_l2_txn_batch', { - pathParams: { number }, - queryOptions: { - enabled: Boolean(number), - placeholderData: ZKSYNC_L2_TXN_BATCH, - }, - }); - - const batchTxsQuery = useQueryWithPages({ - resourceName: 'general:zksync_l2_txn_batch_txs', - pathParams: { number }, - options: { - enabled: Boolean(!batchQuery.isPlaceholderData && batchQuery.data?.number && tab === 'txs'), - placeholderData: generateListStub<'general:zksync_l2_txn_batch_txs'>(TX, 50, { next_page_params: { - batch_number: '8122', - block_number: 1338932, - index: 0, - items_count: 50, - } }), - }, - }); - - throwOnAbsentParamError(number); - throwOnResourceLoadError(batchQuery); - - const hasPagination = !isMobile && batchTxsQuery.pagination.isVisible && tab === 'txs'; - - const tabs: Array = React.useMemo(() => ([ - { id: 'index', title: 'Details', component: }, - { - id: 'txs', - title: 'Transactions', - component: , - }, - ].filter(Boolean)), [ batchQuery, batchTxsQuery, hasPagination ]); - - return ( - <> - - - : null } - stickyEnabled={ hasPagination } - /> - - ); -}; - -export default ZkSyncL2TxnBatch; diff --git a/client/features/rollup/zk-sync/pages/batch-details/ZkSyncL2TxnBatchDetails.tsx b/client/features/rollup/zk-sync/pages/batch-details/ZkSyncL2TxnBatchDetails.tsx deleted file mode 100644 index f2ad703da36..00000000000 --- a/client/features/rollup/zk-sync/pages/batch-details/ZkSyncL2TxnBatchDetails.tsx +++ /dev/null @@ -1,184 +0,0 @@ -// SPDX-License-Identifier: LicenseRef-Blockscout - -import { GridItem } from '@chakra-ui/react'; -import type { UseQueryResult } from '@tanstack/react-query'; -import { useRouter } from 'next/router'; -import React from 'react'; - -import { ZKSYNC_L2_TX_BATCH_STATUSES, type ZkSyncBatch } from 'client/features/rollup/zk-sync/types/api'; - -import { route } from 'nextjs/routes'; - -import type { ResourceError } from 'client/api/resources'; - -import { layerLabels } from 'client/features/rollup/common/utils/layer'; -import { formatZkSyncL2TxnBatchStatus } from 'client/features/rollup/zk-sync/utils/format-txn-batch-status'; - -import { currencyUnits } from 'client/shared/chain/units'; -import throwOnResourceLoadError from 'client/shared/errors/throw-on-resource-load-error'; - -import config from 'configs/app'; -import { CollapsibleDetails } from 'toolkit/chakra/collapsible'; -import { Link } from 'toolkit/chakra/link'; -import { Skeleton } from 'toolkit/chakra/skeleton'; -import { TruncatedText } from 'toolkit/components/truncation/TruncatedText'; -import isCustomAppError from 'ui/shared/AppError/isCustomAppError'; -import CopyToClipboard from 'ui/shared/CopyToClipboard'; -import DataFetchAlert from 'ui/shared/DataFetchAlert'; -import * as DetailedInfo from 'ui/shared/DetailedInfo/DetailedInfo'; -import DetailedInfoTimestamp from 'ui/shared/DetailedInfo/DetailedInfoTimestamp'; -import PrevNext from 'ui/shared/PrevNext'; -import GasPriceValue from 'ui/shared/value/GasPriceValue'; -import VerificationSteps from 'ui/shared/verificationSteps/VerificationSteps'; - -import ZkSyncL2TxnBatchHashesInfo from './ZkSyncL2TxnBatchHashesInfo'; - -const rollupFeature = config.features.rollup; - -interface Props { - query: UseQueryResult; -} - -const ZkSyncL2TxnBatchDetails = ({ query }: Props) => { - const router = useRouter(); - - const { data, isPlaceholderData, isError, error } = query; - - const handlePrevNextClick = React.useCallback((direction: 'prev' | 'next') => { - if (!data) { - return; - } - - const increment = direction === 'next' ? +1 : -1; - const nextId = String(data.number + increment); - - router.push({ pathname: '/batches/[number]', query: { number: nextId } }, undefined); - }, [ data, router ]); - - if (isError) { - if (isCustomAppError(error)) { - throwOnResourceLoadError({ isError, error }); - } - - return ; - } - - if (!data) { - return null; - } - - const txNum = data.l2_transactions_count + data.l1_transactions_count; - const parentChainCurrency = rollupFeature.isEnabled ? rollupFeature.parentChain.currency?.symbol : undefined; - - return ( - - - Txn batch number - - - - { data.number } - - - - - - Status - - - - - - - Timestamp - - - { data.timestamp ? : 'Undefined' } - - - - Transactions - - - - - { txNum } transaction{ txNum === 1 ? '' : 's' } - - - - - - - - - - - - - Root hash - - - - - - - - { layerLabels.parent } gas price - - - - - - - { layerLabels.current } fair gas price - - - - - - - ); -}; - -export default ZkSyncL2TxnBatchDetails; diff --git a/client/features/rollup/zk-sync/pages/batches/ZkSyncL2TxnBatches.pw.tsx b/client/features/rollup/zk-sync/pages/batches/ZkSyncL2TxnBatches.pw.tsx deleted file mode 100644 index 1f701a4f7de..00000000000 --- a/client/features/rollup/zk-sync/pages/batches/ZkSyncL2TxnBatches.pw.tsx +++ /dev/null @@ -1,19 +0,0 @@ -import React from 'react'; - -import * as zkSyncTxnBatchesMock from 'client/features/rollup/zk-sync/mocks/txn-batches'; - -import { ENVS_MAP } from 'playwright/fixtures/mockEnvs'; -import { test, expect } from 'playwright/lib'; - -import ZkSyncL2TxnBatches from './ZkSyncL2TxnBatches'; - -test('base view +@mobile', async({ render, mockEnvs, mockTextAd, mockApiResponse }) => { - test.slow(); - await mockEnvs(ENVS_MAP.zkSyncRollup); - await mockTextAd(); - await mockApiResponse('general:zksync_l2_txn_batches', zkSyncTxnBatchesMock.baseResponse); - await mockApiResponse('general:zksync_l2_txn_batches_count', 9927); - - const component = await render(); - await expect(component).toHaveScreenshot({ timeout: 10_000 }); -}); diff --git a/client/features/rollup/zk-sync/pages/batches/ZkSyncL2TxnBatches.tsx b/client/features/rollup/zk-sync/pages/batches/ZkSyncL2TxnBatches.tsx deleted file mode 100644 index 670abbce613..00000000000 --- a/client/features/rollup/zk-sync/pages/batches/ZkSyncL2TxnBatches.tsx +++ /dev/null @@ -1,93 +0,0 @@ -// SPDX-License-Identifier: LicenseRef-Blockscout - -import { Box, Text } from '@chakra-ui/react'; -import React from 'react'; - -import useApiQuery from 'client/api/hooks/useApiQuery'; - -import { ZKSYNC_L2_TXN_BATCHES_ITEM } from 'client/features/rollup/zk-sync/stubs'; - -import { generateListStub } from 'stubs/utils'; -import { Skeleton } from 'toolkit/chakra/skeleton'; -import { ACTION_BAR_HEIGHT_DESKTOP } from 'ui/shared/ActionBar'; -import DataListDisplay from 'ui/shared/DataListDisplay'; -import PageTitle from 'ui/shared/Page/PageTitle'; -import useQueryWithPages from 'ui/shared/pagination/useQueryWithPages'; -import StickyPaginationWithText from 'ui/shared/StickyPaginationWithText'; - -import ZkSyncTxnBatchesListItem from './ZkSyncTxnBatchesListItem'; -import ZkSyncTxnBatchesTable from './ZkSyncTxnBatchesTable'; - -const ZkSyncL2TxnBatches = () => { - const { data, isError, isPlaceholderData, pagination } = useQueryWithPages({ - resourceName: 'general:zksync_l2_txn_batches', - options: { - placeholderData: generateListStub<'general:zksync_l2_txn_batches'>( - ZKSYNC_L2_TXN_BATCHES_ITEM, - 50, - { - next_page_params: { - items_count: 50, - number: 9045200, - }, - }, - ), - }, - }); - - const countersQuery = useApiQuery('general:zksync_l2_txn_batches_count', { - queryOptions: { - placeholderData: 5231746, - }, - }); - - const content = data?.items ? ( - <> - - { data.items.map(((item, index) => ( - - ))) } - - - - - - ) : null; - - const text = (() => { - if (countersQuery.isError || isError || !data?.items.length) { - return null; - } - - return ( - - Txn batch - #{ data.items[0].number } to - #{ data.items[data.items.length - 1].number } - (total of { countersQuery.data?.toLocaleString() } batches) - - ); - })(); - - const actionBar = ; - - return ( - <> - - - { content } - - - ); -}; - -export default ZkSyncL2TxnBatches; diff --git a/client/features/rollup/zk-sync/stubs.ts b/client/features/rollup/zk-sync/stubs.ts deleted file mode 100644 index fdbfb12658a..00000000000 --- a/client/features/rollup/zk-sync/stubs.ts +++ /dev/null @@ -1,27 +0,0 @@ -import type { ZkSyncBatch, ZkSyncBatchesItem } from 'client/features/rollup/zk-sync/types/api'; - -import { TX_HASH } from 'client/slices/tx/stubs/tx'; - -export const ZKSYNC_L2_TXN_BATCHES_ITEM: ZkSyncBatchesItem = { - commit_transaction_hash: TX_HASH, - commit_transaction_timestamp: '2022-03-17T19:33:04.519145Z', - execute_transaction_hash: TX_HASH, - execute_transaction_timestamp: '2022-03-17T20:49:48.856345Z', - number: 8002, - prove_transaction_hash: TX_HASH, - prove_transaction_timestamp: '2022-03-17T20:49:48.772442Z', - status: 'Executed on L1', - timestamp: '2022-03-17T17:00:11.000000Z', - transactions_count: 1215, -}; - -export const ZKSYNC_L2_TXN_BATCH: ZkSyncBatch = { - ...ZKSYNC_L2_TXN_BATCHES_ITEM, - start_block_number: 1245209, - end_block_number: 1245490, - l1_gas_price: '4173068062', - l1_transactions_count: 0, - l2_fair_gas_price: '100000000', - l2_transactions_count: 287, - root_hash: '0x108c635b94f941fcabcb85500daec2f6be4f0747dff649b1cdd9dd7a7a264792', -}; diff --git a/client/features/rollup/zk-sync/types/api.ts b/client/features/rollup/zk-sync/types/api.ts deleted file mode 100644 index af62e991531..00000000000 --- a/client/features/rollup/zk-sync/types/api.ts +++ /dev/null @@ -1,64 +0,0 @@ -// SPDX-License-Identifier: LicenseRef-Blockscout - -import type { Transaction } from 'client/slices/tx/types/api'; - -export const ZKSYNC_L2_TX_BATCH_STATUSES = [ - 'Processed on L2' as const, - 'Sealed on L2' as const, - 'Sent to L1' as const, - 'Validated on L1' as const, - 'Executed on L1' as const, -]; - -export type ZkSyncBatchStatus = typeof ZKSYNC_L2_TX_BATCH_STATUSES[number]; - -export interface ZkSyncBatchesItem { - commit_transaction_hash: string | null; - commit_transaction_timestamp: string | null; - execute_transaction_hash: string | null; - execute_transaction_timestamp: string | null; - number: number; - prove_transaction_hash: string | null; - prove_transaction_timestamp: string | null; - status: ZkSyncBatchStatus; - timestamp: string; - transactions_count: number; -} - -export type ZkSyncBatchesResponse = { - items: Array; - next_page_params: { - number: number; - items_count: number; - } | null; -}; - -export interface ZkSyncBatch extends Omit { - start_block_number: number; - end_block_number: number; - l1_gas_price: string; - l1_transactions_count: number; - l2_fair_gas_price: string; - l2_transactions_count: number; - root_hash: string; -} - -export type ZkSyncBatchTxs = { - items: Array; - next_page_params: { - batch_number: string; - block_number: number; - index: number; - items_count: number; - } | null; -}; - -export interface TransactionZkSync { - zksync?: Omit & { - batch_number: number | null; - }; -} - -export interface BlockZkSync { - zksync?: Omit & { batch_number: number | null }; -} diff --git a/client/features/rollup/zk-sync/utils/format-txn-batch-status.ts b/client/features/rollup/zk-sync/utils/format-txn-batch-status.ts deleted file mode 100644 index bbca8de4fb5..00000000000 --- a/client/features/rollup/zk-sync/utils/format-txn-batch-status.ts +++ /dev/null @@ -1,9 +0,0 @@ -// SPDX-License-Identifier: LicenseRef-Blockscout - -import type { ZKSYNC_L2_TX_BATCH_STATUSES } from 'client/features/rollup/zk-sync/types/api'; - -import { layerLabels } from 'client/features/rollup/common/utils/layer'; - -export const formatZkSyncL2TxnBatchStatus = (status: typeof ZKSYNC_L2_TX_BATCH_STATUSES[number]) => { - return status.replace('L2', layerLabels.current).replace('L1', layerLabels.parent); -}; diff --git a/client/features/tx-actions/mocks/tx.ts b/client/features/tx-actions/mocks/tx.ts deleted file mode 100644 index a263a0ea906..00000000000 --- a/client/features/tx-actions/mocks/tx.ts +++ /dev/null @@ -1,72 +0,0 @@ -import type { Transaction } from 'client/slices/tx/types/api'; - -import { base } from 'client/slices/tx/mocks/tx'; - -export const withActionsUniswap: Transaction = { - ...base, - actions: [ - { - data: { - address0: '0x6f16598F00eDabEA92B4Cef4b6aa0d45c898A9AE', - address1: '0xB4FBF271143F4FBf7B91A5ded31805e42b2208d6', - amount0: '7143.488560357232097378', - amount1: '10', - symbol0: 'Ring ding ding daa baa Baa aramba baa bom baa barooumba Wh-wha-what's going on-on? Ding, ding This is the Crazy Frog Ding, ding Bem', - symbol1: 'Ether', - }, - protocol: 'uniswap_v3', - type: 'mint', - }, - { - data: { - address: '0xC36442b4a4522E871399CD717aBDD847Ab11FE88', - ids: [ - '53699', - '53700123456', - '42', - ], - name: 'Uniswap V3: Positions NFT', - symbol: 'UNI-V3-POS', - to: '0x6d872Fb5F5B2B1f71fA9AadE159bc3976c1946B7', - }, - protocol: 'uniswap_v3', - type: 'mint_nft', - }, - { - data: { - address0: '0x6f16598F00eDabEA92B4Cef4b6aa0d45c898A9AE', - address1: '0xB4FBF271143F4FBf7B91A5ded31805e42b2208d6', - amount0: '42876.488560357232', - amount1: '345.908098203434', - symbol0: 'SHAVUHA', - symbol1: 'BOB', - }, - protocol: 'uniswap_v3', - type: 'swap', - }, - { - data: { - address0: '0x6f16598F00eDabEA92B4Cef4b6aa0d45c898A9AE', - address1: '0xB4FBF271143F4FBf7B91A5ded31805e42b2208d6', - amount0: '42', - amount1: '0.523523223232', - symbol0: 'VIC', - symbol1: 'USDT', - }, - protocol: 'uniswap_v3', - type: 'burn', - }, - { - data: { - address0: '0x6f16598F00eDabEA92B4Cef4b6aa0d45c898A9AE', - address1: '0xB4FBF271143F4FBf7B91A5ded31805e42b2208d6', - amount0: '42', - amount1: '0.523523223232', - symbol0: 'BOB', - symbol1: 'UNI', - }, - protocol: 'uniswap_v3', - type: 'collect', - }, - ], -}; diff --git a/client/features/tx-actions/pages/tx/TxDetailsAction.tsx b/client/features/tx-actions/pages/tx/TxDetailsAction.tsx deleted file mode 100644 index a5790b47f98..00000000000 --- a/client/features/tx-actions/pages/tx/TxDetailsAction.tsx +++ /dev/null @@ -1,157 +0,0 @@ -// SPDX-License-Identifier: LicenseRef-Blockscout - -import { Flex, chakra } from '@chakra-ui/react'; -import BigNumber from 'bignumber.js'; -import React from 'react'; - -import type { TxAction, TxActionGeneral } from 'types/api/txAction'; - -import AddressEntity from 'client/slices/address/components/entity/AddressEntity'; -import TokenEntity from 'client/slices/token/components/entity/TokenEntity'; - -import config from 'configs/app'; -import NftEntity from 'ui/shared/entities/nft/NftEntity'; -import IconSvg from 'ui/shared/IconSvg'; - -interface Props { - action: TxAction; -} - -function getActionText(actionType: TxActionGeneral['type']) { - switch (actionType) { - case 'mint': return [ 'Added', 'liquidity to' ]; - case 'burn': return [ 'Removed', 'liquidity from' ]; - case 'collect': return [ 'Collected', 'from' ]; - case 'swap': return [ 'Swapped', 'on' ]; - } -} - -const TxDetailsAction = ({ action }: Props) => { - const { protocol, type, data } = action; - - if (protocol !== 'uniswap_v3') { - return null; - } - - switch (type) { - case 'mint': - case 'burn': - case 'collect': - case 'swap': { - const amount0 = BigNumber(data.amount0).toFormat(); - const amount1 = BigNumber(data.amount1).toFormat(); - const [ text0, text1 ] = getActionText(type); - const token0 = { - address_hash: data.symbol0 === 'Ether' ? '' : data.address0, - name: data.symbol0 === 'Ether' ? config.chain.currency.symbol || null : data.symbol0, - type: 'ERC-20' as const, - symbol: null, - icon_url: null, - reputation: null, - }; - const token1 = { - address_hash: data.symbol1 === 'Ether' ? '' : data.address1, - name: data.symbol1 === 'Ether' ? config.chain.currency.symbol || null : data.symbol1, - type: 'ERC-20' as const, - symbol: null, - icon_url: null, - reputation: null, - }; - - return ( - - { text0 } - - { amount0 } - - - - { type === 'swap' ? 'for' : 'and' } - - { amount1 } - - - - { text1 } - - - - Uniswap V3 - - - ); - } - - case 'mint_nft' : { - const token = { - address_hash: data.address, - name: data.name, - type: 'ERC-20' as const, - symbol: null, - icon_url: null, - reputation: null, - }; - - return ( -
- - Minted - - - - to - - - - - - { - data.ids.map((id: string) => { - return ( - - 1 - of token ID - - - ); - }) - } - -
- ); - } - - default: - return null; - } -}; - -export default React.memo(TxDetailsAction); diff --git a/client/features/tx-actions/pages/tx/TxDetailsActions.tsx b/client/features/tx-actions/pages/tx/TxDetailsActions.tsx deleted file mode 100644 index 2813c883e4d..00000000000 --- a/client/features/tx-actions/pages/tx/TxDetailsActions.tsx +++ /dev/null @@ -1,31 +0,0 @@ -// SPDX-License-Identifier: LicenseRef-Blockscout - -import React from 'react'; - -import type { TxAction } from 'types/api/txAction'; - -import config from 'configs/app'; - -import TxDetailsActionsInterpretation from './TxDetailsActionsInterpretation'; -import TxDetailsActionsRaw from './TxDetailsActionsRaw'; - -type Props = { - isTxDataLoading: boolean; - actions?: Array; - hash?: string; -}; - -const TxDetailsActions = ({ isTxDataLoading, actions, hash }: Props) => { - if (config.features.txInterpretation.isEnabled) { - return ; - } - - /* if tx interpretation is not configured, show tx actions from tx info */ - if (actions && actions.length > 0) { - return ; - } - - return null; -}; - -export default TxDetailsActions; diff --git a/client/features/tx-actions/pages/tx/TxDetailsActionsInterpretation.tsx b/client/features/tx-actions/pages/tx/TxDetailsActionsInterpretation.tsx deleted file mode 100644 index 5f658828467..00000000000 --- a/client/features/tx-actions/pages/tx/TxDetailsActionsInterpretation.tsx +++ /dev/null @@ -1,51 +0,0 @@ -// SPDX-License-Identifier: LicenseRef-Blockscout - -import React from 'react'; - -import useApiQuery from 'client/api/hooks/useApiQuery'; - -import TxInterpretation from 'client/features/tx-interpretation/common/components/TxInterpretation'; - -import { TX_INTERPRETATION } from 'stubs/txInterpretation'; -import * as DetailedInfo from 'ui/shared/DetailedInfo/DetailedInfo'; -import DetailedInfoActionsWrapper from 'ui/shared/DetailedInfo/DetailedInfoActionsWrapper'; - -interface Props { - hash?: string; - isTxDataLoading: boolean; -} - -const TxDetailsActionsInterpretation = ({ hash, isTxDataLoading }: Props) => { - const txInterpretationQuery = useApiQuery('general:tx_interpretation', { - pathParams: { hash }, - queryOptions: { - enabled: Boolean(hash) && !isTxDataLoading, - placeholderData: TX_INTERPRETATION, - refetchOnMount: false, - }, - }); - - const actions = txInterpretationQuery.data?.data.summaries; - - if (!actions || actions.length < 2) { - return null; - } - - return ( - <> - - { actions.map((action, index: number) => ( - - ), - ) } - - - - ); -}; - -export default TxDetailsActionsInterpretation; diff --git a/client/features/tx-actions/pages/tx/TxDetailsActionsRaw.tsx b/client/features/tx-actions/pages/tx/TxDetailsActionsRaw.tsx deleted file mode 100644 index edc898d9e46..00000000000 --- a/client/features/tx-actions/pages/tx/TxDetailsActionsRaw.tsx +++ /dev/null @@ -1,28 +0,0 @@ -// SPDX-License-Identifier: LicenseRef-Blockscout - -import React from 'react'; - -import type { TxAction } from 'types/api/txAction'; - -import * as DetailedInfo from 'ui/shared/DetailedInfo/DetailedInfo'; -import DetailedInfoActionsWrapper from 'ui/shared/DetailedInfo/DetailedInfoActionsWrapper'; - -import TxDetailsAction from './TxDetailsAction'; - -interface Props { - actions: Array; - isLoading: boolean; -} - -const TxDetailsActionsRaw = ({ actions, isLoading }: Props) => { - return ( - <> - - { actions.map((action, index: number) => ) } - - - - ); -}; - -export default TxDetailsActionsRaw; diff --git a/client/features/tx-actions/types/api.ts b/client/features/tx-actions/types/api.ts deleted file mode 100644 index 431b0ed0ddc..00000000000 --- a/client/features/tx-actions/types/api.ts +++ /dev/null @@ -1,7 +0,0 @@ -// SPDX-License-Identifier: LicenseRef-Blockscout - -import type { TxAction } from 'types/api/txAction'; - -export interface TransactionActions { - actions?: Array; -} diff --git a/client/features/tx-authorization/pages/tx/TxAuthorizationsList.tsx b/client/features/tx-authorization/pages/tx/TxAuthorizationsList.tsx deleted file mode 100644 index 7db3a9d8ce2..00000000000 --- a/client/features/tx-authorization/pages/tx/TxAuthorizationsList.tsx +++ /dev/null @@ -1,23 +0,0 @@ -// SPDX-License-Identifier: LicenseRef-Blockscout - -import { Box } from '@chakra-ui/react'; -import React from 'react'; - -import type { TxAuthorization } from 'client/features/tx-authorization/types/api'; - -import TxAuthorizationsListItem from './TxAuthorizationsListItem'; - -interface Props { - data: Array | undefined; - isLoading?: boolean; -} - -const TxAuthorizationsList = ({ data, isLoading }: Props) => { - return ( - - { data?.map((item, index) => ) } - - ); -}; - -export default TxAuthorizationsList; diff --git a/client/features/tx-authorization/pages/tx/TxAuthorizationsListItem.tsx b/client/features/tx-authorization/pages/tx/TxAuthorizationsListItem.tsx deleted file mode 100644 index 431008b422a..00000000000 --- a/client/features/tx-authorization/pages/tx/TxAuthorizationsListItem.tsx +++ /dev/null @@ -1,46 +0,0 @@ -// SPDX-License-Identifier: LicenseRef-Blockscout - -import { HStack } from '@chakra-ui/react'; -import React from 'react'; - -import type { TxAuthorization } from 'client/features/tx-authorization/types/api'; - -import AddressEntity from 'client/slices/address/components/entity/AddressEntity'; - -import config from 'configs/app'; -import { Skeleton } from 'toolkit/chakra/skeleton'; -import ListItemMobile from 'ui/shared/ListItemMobile/ListItemMobile'; -import TxAuthorizationStatus from 'ui/shared/statusTag/TxAuthorizationStatus'; - -interface Props extends TxAuthorization { - isLoading?: boolean; -} - -const TxAuthorizationsListItem = ({ address_hash: addressHash, authority, chain_id: chainId, nonce, isLoading, status }: Props) => { - return ( - - - Authority - - - - Delegated address - - - - Chain - { chainId === Number(config.chain.id) ? 'this' : 'any' } - - - Nonce - { nonce } - - - Status - - - - ); -}; - -export default TxAuthorizationsListItem; diff --git a/client/features/tx-authorization/pages/tx/TxAuthorizationsTableItem.tsx b/client/features/tx-authorization/pages/tx/TxAuthorizationsTableItem.tsx deleted file mode 100644 index 4c90ed55aab..00000000000 --- a/client/features/tx-authorization/pages/tx/TxAuthorizationsTableItem.tsx +++ /dev/null @@ -1,49 +0,0 @@ -// SPDX-License-Identifier: LicenseRef-Blockscout - -import { Flex } from '@chakra-ui/react'; -import React from 'react'; - -import type { TxAuthorization } from 'client/features/tx-authorization/types/api'; - -import AddressEntity from 'client/slices/address/components/entity/AddressEntity'; - -import config from 'configs/app'; -import { Skeleton } from 'toolkit/chakra/skeleton'; -import { TableRow, TableCell } from 'toolkit/chakra/table'; -import TxAuthorizationStatus from 'ui/shared/statusTag/TxAuthorizationStatus'; - -interface Props extends TxAuthorization { - isLoading?: boolean; -} - -const TxAuthorizationsItem = ({ address_hash: addressHash, authority, chain_id: chainId, nonce, isLoading, status }: Props) => { - return ( - - - - - - - - - - - - - - { chainId === Number(config.chain.id) ? 'this' : 'any' } - - - - - { nonce } - - - - - - - ); -}; - -export default React.memo(TxAuthorizationsItem); diff --git a/client/features/tx-interpretation/common/utils/utils.ts b/client/features/tx-interpretation/common/utils/utils.ts deleted file mode 100644 index 36dcb709011..00000000000 --- a/client/features/tx-interpretation/common/utils/utils.ts +++ /dev/null @@ -1,53 +0,0 @@ -// SPDX-License-Identifier: LicenseRef-Blockscout - -// we use that regex as a separator when splitting template and don't want to capture variables - -import type { TxInterpretationVariable } from 'types/api/txInterpretation'; - -// eslint-disable-next-line regexp/no-useless-non-capturing-group -export const VAR_REGEXP = /\{(?:[^}]+)\}/g; - -export const NATIVE_COIN_SYMBOL_VAR_NAME = 'native'; -export const WEI_VAR_NAME = 'wei'; - -export function extractVariables(templateString: string) { - - const matches = templateString.match(VAR_REGEXP); - - const variablesNames = matches ? matches.map(match => match.slice(1, -1)) : []; - - return variablesNames; -} - -export function getStringChunks(template: string) { - return template.split(VAR_REGEXP); -} - -export function checkSummary(template: string, variables: Record) { - const variablesNames = extractVariables(template); - let result = true; - for (const name of variablesNames) { - if (name === NATIVE_COIN_SYMBOL_VAR_NAME || name === WEI_VAR_NAME) { - continue; - } - if (!variables[name] || variables[name].value === undefined || variables[name].value === null) { - result = false; - break; - } - } - - return result; -} - -export function fillStringVariables(template: string, variables: Record) { - const variablesNames = extractVariables(template); - - let result = template; - variablesNames.forEach(name => { - if (variables[name] && variables[name].type === 'string') { - result = result.replace(`{${ name }}`, variables[name].value as string); - } - }); - - return result; -} diff --git a/client/features/tx-interpretation/noves/components/TxTranslationType.tsx b/client/features/tx-interpretation/noves/components/TxTranslationType.tsx deleted file mode 100644 index 719f39b0c37..00000000000 --- a/client/features/tx-interpretation/noves/components/TxTranslationType.tsx +++ /dev/null @@ -1,35 +0,0 @@ -// SPDX-License-Identifier: LicenseRef-Blockscout - -import React from 'react'; - -import type { TransactionType } from 'client/slices/tx/types/api'; - -import TxType from 'client/slices/tx/components/TxType'; - -import { Badge } from 'toolkit/chakra/badge'; - -import { camelCaseToSentence } from '../utils/translation'; - -export interface Props { - txTypes: Array; - isLoading?: boolean; - type: string | undefined; -} - -const FILTERED_TYPES = [ 'unclassified' ]; - -const TxTranslationType = ({ txTypes, isLoading, type }: Props) => { - - if (!type || FILTERED_TYPES.includes(type.toLowerCase())) { - return ; - } - - return ( - - { camelCaseToSentence(type) } - - ); - -}; - -export default TxTranslationType; diff --git a/client/features/tx-interpretation/noves/hooks/useDescribeTxs.ts b/client/features/tx-interpretation/noves/hooks/useDescribeTxs.ts deleted file mode 100644 index 8949e8b52aa..00000000000 --- a/client/features/tx-interpretation/noves/hooks/useDescribeTxs.ts +++ /dev/null @@ -1,48 +0,0 @@ -// SPDX-License-Identifier: LicenseRef-Blockscout - -import { uniq, chunk } from 'es-toolkit'; -import React from 'react'; - -import type { Transaction } from 'client/slices/tx/types/api'; - -import type { ReturnType } from 'client/api/hooks/useApiQueries'; -import useApiQueries from 'client/api/hooks/useApiQueries'; - -import config from 'configs/app'; - -const feature = config.features.txInterpretation; - -const translateEnabled = feature.isEnabled && feature.provider === 'noves'; - -export type TxsTranslationQuery = ReturnType<'general:noves_describe_txs'> | undefined; - -export default function useDescribeTxs( - items: Array | undefined, - viewAsAccountAddress: string | undefined, - isPlaceholderData: boolean, -): TxsTranslationQuery { - const enabled = translateEnabled && !isPlaceholderData; - const chunks = React.useMemo(() => { - if (!enabled) { - return []; - } - - const txsHash = items ? uniq(items.map(({ hash }) => hash)) : []; - return chunk(txsHash, 10); - }, [ items, enabled ]); - - const query = useApiQueries( - 'general:noves_describe_txs', - chunks.map((hashes) => { - return { - queryParams: { - viewAsAccountAddress, - hashes, - }, - }; - }), - { enabled }, - ); - - return enabled ? query : undefined; -} diff --git a/client/features/tx-interpretation/noves/pages/address/AddressAccountHistory.tsx b/client/features/tx-interpretation/noves/pages/address/AddressAccountHistory.tsx deleted file mode 100644 index c5aa762cfe2..00000000000 --- a/client/features/tx-interpretation/noves/pages/address/AddressAccountHistory.tsx +++ /dev/null @@ -1,135 +0,0 @@ -// SPDX-License-Identifier: LicenseRef-Blockscout - -import { Box } from '@chakra-ui/react'; -import { useRouter } from 'next/router'; -import React from 'react'; - -import type { NovesHistoryFilterValue } from 'types/api/noves'; -import { NovesHistoryFilterValues } from 'types/api/noves'; - -import useIsMounted from 'client/shared/hooks/useIsMounted'; -import getFilterValueFromQuery from 'client/shared/router/get-filter-value-from-query'; -import getQueryParamString from 'client/shared/router/get-query-param-string'; - -import { NOVES_TRANSLATE } from 'stubs/noves/NovesTranslate'; -import { generateListStub } from 'stubs/utils'; -import { TableBody, TableColumnHeader, TableHeaderSticky, TableRoot, TableRow } from 'toolkit/chakra/table'; -import ActionBar from 'ui/shared/ActionBar'; -import DataListDisplay from 'ui/shared/DataListDisplay'; -import { getFromToValue } from 'ui/shared/Noves/utils'; -import Pagination from 'ui/shared/pagination/Pagination'; -import useQueryWithPages from 'ui/shared/pagination/useQueryWithPages'; - -import AccountHistoryFilter from './AddressAccountHistoryFilter'; -import AddressAccountHistoryListItem from './AddressAccountHistoryListItem'; -import AddressAccountHistoryTableItem from './AddressAccountHistoryTableItem'; - -const getFilterValue = (getFilterValueFromQuery).bind(null, NovesHistoryFilterValues); - -type Props = { - shouldRender?: boolean; - isQueryEnabled?: boolean; -}; - -const AddressAccountHistory = ({ shouldRender = true, isQueryEnabled = true }: Props) => { - const router = useRouter(); - const isMounted = useIsMounted(); - - const currentAddress = getQueryParamString(router.query.hash).toLowerCase(); - - const [ filterValue, setFilterValue ] = React.useState(getFilterValue(router.query.filter)); - - const { data, isError, pagination, isPlaceholderData } = useQueryWithPages({ - resourceName: 'general:noves_address_history', - pathParams: { address: currentAddress }, - options: { - enabled: isQueryEnabled, - placeholderData: generateListStub<'general:noves_address_history'>(NOVES_TRANSLATE, 10, { hasNextPage: false, pageNumber: 1, pageSize: 10 }), - }, - }); - - const handleFilterChange = React.useCallback((val: string | Array) => { - - const newVal = getFilterValue(val); - setFilterValue(newVal); - }, [ ]); - - if (!isMounted || !shouldRender) { - return null; - } - - const actionBar = ( - - - - - - ); - - const filteredData = isPlaceholderData ? data?.items : data?.items.filter(i => filterValue ? getFromToValue(i, currentAddress) === filterValue : i); - - const content = ( - - - { filteredData?.map((item, i) => ( - - )) } - - - - - - - - Age - - - Action - - - From/To - - - - - { filteredData?.map((item, i) => ( - - )) } - - - - - ); - - return ( - - { content } - - ); -}; - -export default AddressAccountHistory; diff --git a/client/features/user-ops/components/SearchBarSuggestUserOp.tsx b/client/features/user-ops/components/SearchBarSuggestUserOp.tsx deleted file mode 100644 index bdbc4cfddad..00000000000 --- a/client/features/user-ops/components/SearchBarSuggestUserOp.tsx +++ /dev/null @@ -1,45 +0,0 @@ -// SPDX-License-Identifier: LicenseRef-Blockscout - -import { chakra, Flex } from '@chakra-ui/react'; -import React from 'react'; - -import type { SearchResultUserOp } from 'client/features/user-ops/types/api'; -import type { ItemsProps } from 'client/slices/search/components/search-bar/SearchBarSuggest/types'; - -import * as UserOpEntity from 'client/features/user-ops/components/entity/UserOpEntity'; - -import HashStringShortenDynamic from 'ui/shared/HashStringShortenDynamic'; -import Time from 'ui/shared/time/Time'; - -const SearchBarSuggestUserOp = ({ data, isMobile }: ItemsProps) => { - const icon = ; - const hash = ( - - - - ); - - if (isMobile) { - return ( - <> - - { icon } - { hash } - -