diff --git a/.agents/AGENTS.md b/.agents/AGENTS.md index 81cea00d45..20f59bcd62 100644 --- a/.agents/AGENTS.md +++ b/.agents/AGENTS.md @@ -61,6 +61,19 @@ Some directories have a `CONTEXT.md` documenting non-obvious patterns specific t If you encounter a `CONTEXT.md` not listed here, read it too (and consider adding it to this list). +## Architecture decision records + +Decisions with repo-wide consequences are recorded in `.agents/adr/`, named +`<0000>-.md`. Read the relevant one before changing what it decided — an ADR carries the +evidence and the trade-off, so it answers "why is it like this?" without a git archaeology session. + +- `0001-webpack-for-production-builds.md` — why production bundles are built with webpack while dev stays on Turbopack. + +Add a new record (next free number, and a line here) whenever a decision is expensive to rediscover: +it constrains future work, was reached by measurement or an investigation worth not repeating, or +looks wrong without its context. Supersede rather than rewrite — flip the old record's `Status` to +`superseded by ` and leave its reasoning intact. + ## Product task workflow Product tasks (GitHub issues) are worked through a spec-driven workflow, run by the `grill-the-task`, diff --git a/.agents/GLOSSARY.md b/.agents/GLOSSARY.md index 12a8809111..3d2fddedd1 100644 --- a/.agents/GLOSSARY.md +++ b/.agents/GLOSSARY.md @@ -36,6 +36,7 @@ vars are documented in `docs/ENVS.md`. Architectural concepts like | **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. | +| **Eden** | chain | A rollup built on `ev-reth` / evstack. Introduces the **Sponsored Transaction** type. | | **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. | @@ -52,6 +53,7 @@ vars are documented in `docs/ENVS.md`. Architectural concepts like | **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. | +| **Sponsored Transaction** | entity | **Eden**-specific transaction type (EIP-2718 type `0x76`): an executor submits an ordered batch of calls, while a separate sponsor signs for and pays the fee. No equivalent on standard EVM chains. | | **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. | diff --git a/.agents/adr/0001-webpack-for-production-builds.md b/.agents/adr/0001-webpack-for-production-builds.md new file mode 100644 index 0000000000..5a666000d7 --- /dev/null +++ b/.agents/adr/0001-webpack-for-production-builds.md @@ -0,0 +1,108 @@ +# 0001 — webpack for production builds, Turbopack for dev + +| | | +| --- | --- | +| Status | accepted | +| Date | 2026-08-04 | +| Deciders | @tom2drum | +| Supersedes | — | + +## Decision + +**Production builds use webpack (`next build --webpack`). Dev keeps Turbopack (the Next 16 default).** + +Applies to every entry point that emits a production bundle: + +| Entry point | Bundler | +| --- | --- | +| `pnpm build` — what the `Dockerfile` runs for the shipped image | webpack | +| `pnpm build:next` | webpack | +| `pnpm prod:preset ` — local production build, incl. perf measurements | webpack | +| `pnpm build:analyze`, `pnpm profile:preset` | webpack (already were) | +| `pnpm dev`, `pnpm dev:preset`, `pnpm dev:local` | Turbopack | + +Dev stays on Turbopack because it is roughly 3× faster to compile and the crash class below only +manifests in a minified production build. Production-build regressions are caught in QA rather than +by making every local dev start slower. + +## Why + +### Turbopack miscompiles the Dynamic-labs SDK + +Turbopack's scope hoisting emits code that reads the SDK's `UserFieldEditorContext` through the +wrong binding. `useContext` therefore receives a non-context value, returns `undefined`, and the SDK +throws from its own `useUpdateUserWithModal`: + +``` +useUserUpdateRequest can only be used inside the context of DynamicContextProvider +``` + +The throwing component is the SDK's internal `SyncAuthFlow`, which the SDK itself renders *inside* +`UserFieldEditorContextProvider` — so in a correct build the context cannot be missing. It is a +bundler defect, not a provider-tree bug in our code. + +Impact: **a hard crash on the initial load of every page**, for any instance configured with +`NEXT_PUBLIC_ACCOUNT_AUTH_PROVIDER=dynamic`. It is invisible in dev (unminified, no hoisting) and +was found only by running the `v2.10.0` image locally. The v2.10.0 release would have broken every +dynamic-auth instance on rollout; deployed instances were still on v2.9.4 and unaffected. + +Bisected to [#3574](https://github.com/blockscout/frontend/pull/3574) (wallet-stack deferral, +subtask 4 of [#3566](https://github.com/blockscout/frontend/issues/3566)) — parent commit good, that +commit bad. The trigger could **not** be reduced to a single import: reverting the lazy `import()` +wrappers, the `_app.tsx` provider restructure, and the `@wagmi/core` dependency each left it broken. +That fits the mechanism — scope hoisting groups modules across the whole graph, so the trigger is an +emergent property of how #3574 reshaped it, and any future graph change could re-trigger it +somewhere else. Next 16.3.0 does not fix it. + +### webpack is also the faster bundle + +Two options fixed the crash: `--webpack`, or `experimental.turbopackScopeHoisting: false`. The flag +turned out to be the expensive one. Production builds of `main`, medians of 3 +automated traces: + +| Metric | Turbopack | Turbopack, hoisting off | **webpack** | +| --- | --- | --- | --- | +| M1 FCP | 432 ms | 790 ms | **501 ms** | +| M2 first API request | 60 ms | 142 ms | **57 ms** | +| M5 blocking time | 133 ms | 408 ms | **155 ms** | +| M6 JS before FCP | 1038 KB | 1064 KB | **697 KB** | +| Emitted chunk bytes | 49.2 MB | 53.4 MB | **21.4 MB** | +| Build time | 48 s | 41 s | 2.4 min | + +Disabling scope hoisting nearly doubles FCP and triples blocking time while barely moving M6 (+2.5%) +— the cost lands in execution, not transfer, so M6 alone would not have caught it. webpack instead +*improves* pre-FCP JS by 341 KB (−33%) over the Turbopack build, more than any single lever in #3566 +delivered on its own. + +The measurement harness lives in +`.agents/tasks/3566-main-page-loading-perf/tools/` (see its README). Absolute values come from +headless Chromium on a local server and are not comparable to the numbers in that task's spec table; +the within-comparison deltas are what the decision rests on. + +## Consequences + +- **CI and image builds get slower** — webpack's compile step measured 84 s to 2.4 min across + machines and cache states, against 41–48 s for Turbopack, so budget roughly 2–3×. Accepted: + correctness plus a materially smaller bundle outweigh build latency. +- **Dev and production now use different bundlers.** A bug in either pipeline can only be caught on + that pipeline; production-only breakage will not appear in dev. QA runs against a real image. +- `next.config.js` must keep **both** the `webpack()` and `turbopack` sections in sync — it already + does, and this decision makes that non-optional. +- webpack surfaces one unresolvable import Turbopack silently tolerates: + `@react-native-async-storage/async-storage` inside `@metamask/sdk`, reached via + `@wagmi/connectors` → `@reown/appkit-adapter-wagmi` → `wagmi-config.ts`. It is an optional peer + dependency of a React Native code path a browser bundle never takes, so `next.config.js` maps it + to `false` in `resolve.fallback` (an empty module) and the build is warning-free. If a future + dependency bump introduces a similar optional import, extend that map rather than silencing + warnings wholesale. +- `next build --webpack` is a compatibility path in Next 16 and may eventually be removed. If that + happens before Turbopack is fixed, the fallback is `experimental.turbopackScopeHoisting: false` + and its performance cost. + +## Follow-ups + +- Report the miscompilation upstream to `vercel/next.js` with a minimal reproduction; the bisect + boundary and the flag that toggles it are the material. +- Re-test Turbopack on each Next upgrade. If a release fixes it, revisit — Turbopack's build speed + is worth reclaiming, but only with the M1/M5/M6 numbers above re-measured, not on the release + notes alone. diff --git a/.agents/tasks/3566-main-page-loading-perf/tools/README.md b/.agents/tasks/3566-main-page-loading-perf/tools/README.md index 9b0196913b..ed8adb3441 100644 --- a/.agents/tasks/3566-main-page-loading-perf/tools/README.md +++ b/.agents/tasks/3566-main-page-loading-perf/tools/README.md @@ -17,13 +17,25 @@ inflate everything 2–3×). Keep the **same preset** for every measurement — metrics M3/M4 depend on the instance's backend latency, so numbers from different presets are not comparable. -2. Open `http://localhost:3000/` in a **clean browser profile** (incognito, no extensions — - React DevTools alone adds ~150 ms of scripting). +2. Record the trace, either by hand or scripted. -3. DevTools → Performance → "Record and reload". Stop a couple of seconds after the - transactions/blocks lists show real data. Export the trace as JSON. + **By hand** — open `http://localhost:3000/` in a **clean browser profile** (incognito, no + extensions — React DevTools alone adds ~150 ms of scripting), then DevTools → Performance → + "Record and reload". Stop a couple of seconds after the transactions/blocks lists show real + data. Export the trace as JSON. -4. Extract the metrics: + **Scripted** — `trace.mjs` drives headless Chromium over CDP and writes the same JSON: + + ```bash + node trace.mjs http://localhost:3000/ /tmp/traces/before 3 # 3 runs -> before-1..3.json + ``` + + It records the same event categories the Performance panel does and uses a fresh browser + context per run (no extensions, cold cache), so it is the scripted equivalent of the clean + profile above. Prefer it whenever you need several runs per variant or a repeatable A/B; a + single exploratory trace is easier by hand, where you can also read the flame chart. + +3. Extract the metrics: ```bash python3 trace-metrics.py baseline.json # one trace @@ -40,3 +52,13 @@ inflate everything 2–3×). - The app under `prod:preset` proxies API calls through `localhost:3000/node-api/proxy` (the fetched config keeps `APP_ENV=development`). Both variants of an A/B pair share this hop, so deltas are valid — but do not compare absolute values against traces of a deployed instance. +- **Headless (`trace.mjs`) and headed absolute values are not comparable either** — same rule, + compare within one capture method. Do not mix them in a single row of the spec's table. +- **M6 is not a sufficient gate on its own.** A bundler change can leave the bytes-before-FCP + almost untouched while doubling FCP and tripling blocking time, because the cost is in executing + the code rather than transferring it. Always read M1 and M5 alongside M6 before concluding a + change is cheap — see `.agents/adr/0001-webpack-for-production-builds.md` for the case that + taught us this. +- `prod:preset` builds with the same bundler as the shipped image (webpack, per that ADR), so its + traces represent what users get. If you ever measure a build made another way, say so next to + the numbers. diff --git a/.agents/tasks/3566-main-page-loading-perf/tools/trace.mjs b/.agents/tasks/3566-main-page-loading-perf/tools/trace.mjs new file mode 100644 index 0000000000..387166e194 --- /dev/null +++ b/.agents/tasks/3566-main-page-loading-perf/tools/trace.mjs @@ -0,0 +1,80 @@ +// Record page-load performance traces without the DevTools UI. +// +// Produces the same JSON the Performance panel's "Record and reload" export produces, so the output +// feeds straight into trace-metrics.py. Use it when you want several runs per variant (M3/M4 need a +// median) or a repeatable A/B — the manual protocol in README.md is still fine for a one-off. +// +// Usage, against an already-running production server (`pnpm prod:preset `): +// +// node .agents/tasks/3566-main-page-loading-perf/tools/trace.mjs http://localhost:3000/ ./traces/after 3 +// python3 .agents/tasks/3566-main-page-loading-perf/tools/trace-metrics.py ./traces/before-2.json ./traces/after-2.json +// +// Writes -.json for n in 1..runs. + +/* eslint-disable no-console -- a CLI tool: stdout is its interface, for the usage hint and for + reporting each trace it wrote. */ + +import { chromium } from '@playwright/test'; +import { mkdirSync, writeFileSync } from 'node:fs'; +import { dirname } from 'node:path'; + +// Long enough for the transactions/blocks lists to fill with real data, which M3/M4 measure. +const SETTLE_MS = 10_000; + +// The capture set the Performance panel uses: devtools.timeline for resources/tasks/render commits, +// loading for navigationStart, blink.user_timing for paint marks, __metadata for thread names +// (trace-metrics.py needs those to tell CrRendererMain apart from other threads). +const CATEGORIES = [ + '-*', + 'devtools.timeline', + 'disabled-by-default-devtools.timeline', + 'disabled-by-default-devtools.timeline.frame', + 'blink.user_timing', + 'loading', + 'latencyInfo', + 'v8.execute', + '__metadata', +]; + +const [ url, outPrefix, runsArg ] = process.argv.slice(2); +if (!url || !outPrefix) { + console.error('Usage: node trace.mjs [runs=1]'); + process.exit(2); +} +const runs = Number(runsArg ?? 1); + +mkdirSync(dirname(outPrefix), { recursive: true }); + +// A fresh context per run is the scripted equivalent of the protocol's "clean browser profile": +// no extensions, no warm HTTP cache, no carried-over service worker. +const browser = await chromium.launch(); + +for (let run = 1; run <= runs; run++) { + const context = await browser.newContext(); + const page = await context.newPage(); + const client = await context.newCDPSession(page); + + const events = []; + client.on('Tracing.dataCollected', ({ value }) => events.push(...value)); + const complete = new Promise((resolve) => client.once('Tracing.tracingComplete', resolve)); + + // Tracing has to start before the navigation — that is what "Record and reload" does, and + // navigationStart is the zero point every metric is relative to. + await client.send('Tracing.start', { + transferMode: 'ReportEvents', + traceConfig: { includedCategories: CATEGORIES, recordMode: 'recordAsMuchAsPossible' }, + }); + + await page.goto(url, { waitUntil: 'load', timeout: 60_000 }); + await page.waitForTimeout(SETTLE_MS); + + await client.send('Tracing.end'); + await complete; + await context.close(); + + const out = `${ outPrefix }-${ run }.json`; + writeFileSync(out, JSON.stringify({ traceEvents: events })); + console.log(`${ out }: ${ events.length } events`); +} + +await browser.close(); diff --git a/.agents/tasks/3607-tx-details-fee-payer-calls/spec.md b/.agents/tasks/3607-tx-details-fee-payer-calls/spec.md new file mode 100644 index 0000000000..3c3cbd7256 --- /dev/null +++ b/.agents/tasks/3607-tx-details-fee-payer-calls/spec.md @@ -0,0 +1,275 @@ +# Display fee payer and calls on the transaction details page + +| | | +| --- | --- | +| Issue | https://github.com/blockscout/frontend/issues/3607 | +| Status | `done` | +| Size | `small` | +| Feature branch | `issue-3607` | +| PM | Ulyana | +| Designer | — | +| Backend | Victor (issue author); v11.2.4+ | +| Slack channel | — (default routing per `to-spec`) | + +## Context & goal + +Eden is an `ev-reth` / evstack rollup that adds a custom EIP-2718 transaction type `0x76` (decimal `118`): +a **sponsored batch transaction**. An *executor* submits an ordered batch of calls, and a separate *sponsor* +signs for and pays the fee. The backend now indexes those transactions and exposes two new optional fields +on the transaction model ([blockscout#14590](https://github.com/blockscout/blockscout/issues/14590), +implemented in [blockscout#14643](https://github.com/blockscout/blockscout/pull/14643)): `fee_payer` and +`calls`. + +Neither field is rendered today, so an Eden sponsored transaction page silently omits the two things that +distinguish it — who actually paid, and what the batch executed. The goal is to display both on `/tx/:hash`, +and to omit them cleanly on every other chain (where they are absent from the response entirely). + +## Functional requirements + +1. When `fee_payer` is present, the transaction details page shows a **Fee payer** row with the address. + Hint copy: `Address that paid the transaction fee on behalf of the sender`. +2. When `calls` is present and non-empty, the page shows a **Calls** row with a table of the batched calls + in API order. Hint copy: `Ordered list of calls batched into this sponsored transaction`. +3. The Calls table has three columns — `To`, `Value`, `Input`: + - `To` — `AddressEntity` with `truncation="dynamic"`. When `to` is `null` the cell reads + `[ Contract creation ]` (the same string [`TxDetails.tsx:395`](../../../src/slices/tx/pages/details/info/TxDetails.tsx) + already uses). `to` being null is a real case, confirmed by the backend owner (**Q3**). + - `Value` — `NativeCoinValue` with symbol, no exchange-rate toggle. + - `Input` — `TruncatedText` + `CopyToClipboard`, matching the `Data` cell of + [`LogDecodedInputDataTable`](../../../src/slices/log/components/LogDecodedInputDataTable.tsx). +4. Both rows render inside the collapsible *View details* section, immediately after **Other** and before + **Raw input** — so the batched calls sit next to the raw/decoded input data that shares their visual + language. No `DetailedInfo.ItemDivider` around the block (`Other`, `Raw input` and `Decoded input data` + have none between them either). +5. Neither field is gated by an env var or a feature config. They are rendered on **field presence**, + the established pattern for chain-variant transaction fields: `execution_node` / `allowed_peekers` at + [`TxDetails.tsx:308`](../../../src/slices/tx/pages/details/info/TxDetails.tsx) do the same, even though + SUAVE has an `NEXT_PUBLIC_IS_SUAVE_CHAIN` flag for its nav and pages. `do_with_chain_type_fields` only + extends the response for `:eden`, so presence is a sufficient and self-maintaining gate. +6. A **Sponsored** tag appears in the transaction details page header when `transaction_types` includes + `sponsored_transaction`, alongside the other header tags. Transaction **lists** get no badge for it + (**Q1**) — there is no room. `sponsored_transaction` is still added to `TYPES_ORDER`, last and with no + label of its own: absent from that list, `indexOf` returns `-1` and sorts it ahead of every real type, + making a sponsored contract call read as the generic "Transaction" instead of "Contract call". + +## Data & API + +**Endpoint** — `GET /api/v2/transactions/{hash}` (existing `core:tx` resource; no new API resource needed). + +**Readiness** — merged to backend `master` on 2026-07-31 and already deployed on +`eden-testnet.blockscout.com` (`backend_version: v11.2.4.+commit.ac947295`). Ships in backend tag **11.2.4** +(Q3) — worth naming in the frontend release notes. + +**Field shapes** — read from +[`schemas/api/v2/transaction.ex`](https://github.com/blockscout/blockscout/pull/14643/files) and verified +against a live response: + +- `fee_payer` — a full `Address` object, `nullable: true`. +- `calls` — `Array<{ to: AddressHashNullable; value: IntegerString; input: HexString }>`, `nullable: true`. +- `required: [:fee_payer, :calls]` means the keys are always present on an Eden response, not that the + values are non-null. + +Sample (`0x35310fd76c45f1441226c102f4dc1070b41ac66cb1e6ed3354da78aa69824a67` on `eden-testnet`): + +```json +{ + "type": 118, + "transaction_types": [ "sponsored_transaction" ], + "fee_payer": { "hash": "0x32648e6529BfCacE20422a7AA1E7fB7Bd8F408d7", "is_contract": false, "…": "…" }, + "calls": [ { "to": "0xf97cDCF1e5C0955Ed5c2EA0afb2c4Bb4eD506505", "value": "0", "input": "0x" } ] +} +``` + +The call's `to` is `null` on a contract creation — confirmed by the backend owner (**Q3**), not defensive +typing. + +**Scope of each field across endpoints** — `calls` is rendered for single-transaction responses only +(`prepare_calls` returns `nil` otherwise, the same policy the backend applies to token transfers). +`fee_payer` *is* returned on list endpoints too, but showing it there is out of scope. + +**Types package** — pinned at `@blockscout/api-types@0.0.1-beta.8e1692a`, published from backend `dev` once +`master` had been merged into it (**Q4**). It carries `eden.schema`, both fields on the transaction, and the +`operations` / `paths` shorthands the app depends on. + +Because `merged.schema` marks chain-specific properties **optional**, the fields type as +`Address | null | undefined` and `Array | null | undefined`. Guards must handle `undefined` as well as +`null`. + +**Env vars / feature flags** — none added. + +## UI inventory + +- **Single surface**: `/tx/:hash` details tab — + [`src/slices/tx/pages/details/info/TxDetails.tsx`](../../../src/slices/tx/pages/details/info/TxDetails.tsx), + inside the `CollapsibleDetails` block, between `` and the `Raw input` label. +- **New component**: `src/features/chain-variants/eden/pages/tx/TxDetailsEden.tsx` — renders both label/value + pairs and returns `null` when both fields are absent, so `TxDetails.tsx` composes it unconditionally + (matching `TxDetailsSetMaxGasLimit` and `TxDetailsWithdrawalStatusArbitrum`, already there doing the same). + Eden-specific UI is a **feature**, not a slice — it cannot exist on a vanilla EVM chain — and + [`TxDetails.tsx:83`](../../../src/slices/tx/pages/details/info/TxDetails.tsx) carries a standing + `// REFACTOR: Put feature related parts under the feature folder` note. No `config.ts` in the feature + folder: only chain variants needing an env flag have one (stability and zilliqa have none). +- **No Figma mockups** — none linked on the issue, and none needed. The Calls table reuses the styles of + [`LogDecodedInputDataTable`](../../../src/slices/log/components/LogDecodedInputDataTable.tsx): background + `{ _light: 'blackAlpha.50', _dark: 'whiteAlpha.50' }`, `p={4}`, `mt={2}`, `columnGap`/`rowGap={5}`, + `textStyle="sm"`, and header cells at `fontWeight={600} pb={1}`. One deliberate difference: **all four + corners are rounded** (`borderRadius="md"`), where the reference rounds only the bottom two because + `LogDecodedInputDataHeader` sits above it. Column template is `repeat(3, minmax(0, 1fr))` — equal widths + to start, tuned during verification (leaf 4). +- **Also affected by leaf 5**: the page header tags in + [`Transaction.tsx`](../../../src/slices/tx/pages/details/Transaction.tsx), and + [`TxType`](../../../src/slices/tx/components/TxType.tsx), which renders in the txs list, the home page + latest-transactions widget, and the address transactions tab. +- No new routes, navigation entries, or cross-links. +- No custom Mixpanel events: the only interactive elements are `AddressEntity` links and `CopyToClipboard`, + neither tracked elsewhere; there is no new page (view tracking is auto-wired) and no hardcoded external + link needing UTM params. + +## Out of scope + +- **Adapting the standard fields whose Eden semantics differ.** On a sponsored transaction the backend + derives `to` and `raw_input` from **call 0 only**, `value` from the **sum** of all calls, and `from` is + the *executor* rather than the fee payer. So on a multi-call transaction the "To" and "Raw input" rows + show one call while the Calls table shows all of them. The backend issue's UI requirements ask to "hide or + adapt standard fields whose Eden semantics differ" and to "present gas fields only where they are + meaningful"; #3607 asks for none of it. Raised as **Q2**, not blocking. +- A **Fee payer column in the transactions list**, even though the field is available there. +- The **Eden mainnet** dev preset (`eden.blockscout.com`) — it has no sponsored transactions to look at. +- A **Playwright visual scenario and transaction mock**. Dropped deliberately: the two rows use generic + building blocks already covered elsewhere, and a mock in `src/slices/tx/mocks/details.ts` exists only to + feed a `*.pw.tsx` scenario, so without one it would be dead code. Verification is against live + `eden-testnet` data. +- Backend work of any kind — already shipped. + +## Task breakdown + +- [x] 1 `[agent]` Add `eden` and `sponsored transaction` to `.agents/GLOSSARY.md` — skill: `update-glossary` + - done: `Eden` (chain) and `Sponsored Transaction` (entity) rows, cross-referencing each other + - inputs: + - `eden` — the chain type (`CHAIN_TYPE=eden`): an `ev-reth` / evstack rollup, explorers at + `eden.blockscout.com` and `eden-testnet.blockscout.com` + - `sponsored transaction` — scoped to Eden: EIP-2718 type `0x76` (decimal `118`); an executor submits an + ordered batch of calls and a separate sponsor signs for and pays the fee + - Also gets `eden` past cSpell, which has no entry for it today +- [x] 2 `[agent]` Add the `eden_testnet` dev-server preset + - done: `tools/dev-server/registry.json` + `pnpm presets:sync` (`deploy-review.yml`, `.vscode/tasks.json`) + - inputs: + - `"eden_testnet": "https://eden-testnet.blockscout.com"` in `tools/dev-server/registry.json` + - then `pnpm presets:sync` — regenerates the marker-bracketed alias lists in + `.github/workflows/deploy-review.yml` and `.vscode/tasks.json`; CI fails on drift + - Ordered before the UI leaves so their verification has a preset to run against +- [x] 3 `[agent]` Get `fee_payer` / `calls` into the pinned API types + - inputs: + - First check whether `@blockscout/api-types@0.0.1-beta.bb45bf1` already contains them; if so just bump + the pin in `package.json` + - Otherwise publish a beta from backend `master` via the `publish-beta-types` skill and pin that + - Verify afterwards that `schemas['TransactionResponse']` exposes `fee_payer` and `calls`, and that + `transaction_types` includes `sponsored_transaction` + - done: pinned `0.0.1-beta.8e1692a`, published from `dev` after `master` was merged into it (**Q4**). + `schemas['TransactionResponse']` exposes `fee_payer` and `calls`, and `transaction_types` includes + `sponsored_transaction`; `pnpm lint:tsc` is clean, so the merge cost the app no type churn. The interim + `eden/types/api.ts` shim is gone — the component reads both fields off the pinned schema. +- [x] 4 `[agent]` `[verify]` Build `TxDetailsEden.tsx` and wire it into the details page — requirements 1–4 + - inputs: + - New file `src/features/chain-variants/eden/pages/tx/TxDetailsEden.tsx`; no `config.ts` + - Composed unconditionally in `TxDetails.tsx` after ``, before the `Raw input` label + - Fully styled from the `LogDecodedInputDataTable` reference (see **UI inventory**) — this is a code + reference, not a mockup, so there is no separate `[human]` style leaf; width and spacing tweaks happen + during verification + - verify: `pnpm dev:preset eden_testnet`, open + `/tx/0x35310fd76c45f1441226c102f4dc1070b41ac66cb1e6ed3354da78aa69824a67`, expand *View details*, confirm + the Fee payer and Calls rows render correctly between Other and Raw input; adjust styles if needed. Also + open any non-Eden preset (e.g. `eth`) and confirm neither row appears. + - implemented: `TxDetailsEden.tsx` in the new `eden` feature folder, composed in + `TxDetails.tsx`; `dev-eden-testnet` added to `.claude/launch.json`. Functional check done on + `eden_testnet`: both rows render between Other and Raw input on the sponsored transaction, and both are + absent on a type-2 one; the table's computed styles match the reference (16px padding, 20px gaps, 12px + radius, `whiteAlpha.50`, 14px text, three equal columns). Styles reviewed and accepted by the developer + on 2026-08-04, with the column template tuned to `minmax(140px, 1fr) minmax(50px, 1fr) 1fr`; the designer + signed them off on the interim demo the same day. +- [x] 5 `[agent]` `[verify]` Show the **Sponsored** tag in the page header — requirement 6 + - inputs: + - Push a `{ slug: 'sponsored', name: 'Sponsored', tagType: 'custom' }` tag in `Transaction.tsx`, next to + the `relay_tx` / `init_tx` pushes that already feed `MetadataTags` + - Add `sponsored_transaction` to `TYPES_ORDER` last, with no `switch` case, so lists keep showing no + badge for it while the type stops masking more useful labels + - verify: on `eden_testnet`, open a sponsored transaction and confirm the header tag; check `/txs` still + labels a sponsored contract call as "Contract call" + - implemented: the header tag keys off `transaction_types`, so it carries no Eden-specific coupling. + `TxType.spec.tsx` pins both ordering outcomes. The dev server would not hydrate in the agent's browser + pane (Next dev's `_clientMiddlewareManifest.js` is served as JSON), so the header tag is verified by + types and tests only — confirm it visually on the next demo. +- [x] 6 `[agent]` Deploy a demo — skill: `deploy-demo` + - inputs: + - Run last, once every other box is checked + - done: deployed on 2026-08-04 from `2442fb48c` with the `eden_testnet` preset — + https://review-issue-3607.k8s-dev.blockscout.com — and shared in the Q1/Q2 thread, where the designer + signed off the styles. It covers leaves 1–4; the developer waived a redeploy for leaf 5, so the + **Sponsored** header tag is not on the demo. + +## Open questions + +### Q1 — Should a sponsored transaction get its own badge in the transactions list? + +The backend added `sponsored_transaction` to `transaction_types`, and the backend issue's UI requirements +ask for "a tag/badge such as `Sponsored`". #3607 does not mention it. Today the value falls through +`TxType`'s `default` branch to a generic purple "Transaction". If a dedicated badge is wanted: what label, +what colour (`purple` is the fallback's; `green` is unused), and what priority relative to "Contract call" / +"Token transfer" when a transaction is both? + +- Owner: PM (Ulyana) +- Status: `resolved` +- Slack: https://blockscout.slack.com/archives/C03MMUTQDNU/p1785778160250149 (reminder with the interim demo + at https://blockscout.slack.com/archives/C03MMUTQDNU/p1785851465775779) +- Answer: 2026-08-04 — answered by Nikita S. rather than Ulyana: a **Sponsored** tag in the details page + header, skipped in lists where it would not fit. Tags are in the SoW, but how to render them was left to + the team. +- Blocks: leaf 5 + +### Q2 — Should the compatibility fields be adapted on a multi-call sponsored transaction? + +`to` and `raw_input` reflect **call 0 only**, `value` is the **sum** across calls, and `from` is the executor +rather than the payer — so those rows can be misread on a batch of more than one call. Should they be +hidden, relabelled, or annotated for Eden, as the backend issue's UI requirements suggest? Shipping narrow +for now. + +- Owner: PM (Ulyana) +- Status: `waived` +- Slack: https://blockscout.slack.com/archives/C03MMUTQDNU/p1785778160250149 +- Answer: 2026-08-04 — deferred out of this task. Shipping the narrow scope; the team waits for client + feedback on whether the compatibility fields mislead in practice, and adapts them only if it does. + +### Q3 — Which backend release ships the Eden transaction fields? + +Needed for the frontend release notes. The PR merged to `master` on 2026-07-31 and `eden-testnet` already +runs it, but no tagged release is identified. Bundled with this: confirmation that the call's address is +nullable for a contract-creation call (read from the backend source, worth hearing from the owner before the +UI relies on it), and — if so — a request to correct #3607, which names the field `address_hash` where the +API and the OpenAPI schema both use **`to`**. + +- Owner: Backend (Victor) +- Status: `resolved` +- Slack: https://blockscout.slack.com/archives/D040DB9J5QQ/p1785778264416959 +- Answer: 2026-08-03 — backend tag **11.2.4**, planned for release that week. A call's `to` is confirmed + `null` on a contract creation, and #3607's description was corrected to name the field `to`. + +### Q4 — Which backend ref can publish api-types with both the Eden fields and the response shorthands? + +`@blockscout/api-types` betas are published from `dev`, which has no `eden` chain type. A beta published from +`master` (`0.0.1-beta.cf4c6f5`) has `eden.schema` plus `fee_payer` / `calls`, but its `index.ts` lacks the +`operations` and `paths` shorthands added by +[blockscout#14515](https://github.com/blockscout/blockscout/pull/14515) — the app imports those in 60+ +modules, and pinning that build yields 384 type errors across 227 files. So neither ref serves the frontend. +Can `master` be merged into `dev` (or #14515 forward-ported to `master`) so one ref carries both? Until then +the two fields are declared locally in the `eden` feature. + +- Owner: Backend (Victor) +- Status: `resolved` +- Slack: https://blockscout.slack.com/archives/D040DB9J5QQ/p1785780964199699 (compile failure reported at + https://blockscout.slack.com/archives/D040DB9J5QQ/p1785781999313979, the `HexString` rename at + https://blockscout.slack.com/archives/D040DB9J5QQ/p1785838420460469) +- Answer: 2026-08-04 — `dev` is the ref, once `master` was merged into it. Two follow-up fixes were needed: + a compile break the merge left in `read_system_config/2` (`7b60189`), then the Eden call schema still + naming `General.HexString`, which `dev` had renamed to `General.HexData` + ([#14656](https://github.com/blockscout/blockscout/pull/14656)). The publish from `8e1692a` then succeeded. +- Blocks: leaf 3 diff --git a/.claude/launch.json b/.claude/launch.json index b58d7bb472..3d21960168 100644 --- a/.claude/launch.json +++ b/.claude/launch.json @@ -7,6 +7,12 @@ "runtimeArgs": ["dev:preset", "staging"], "port": 3000 }, + { + "name": "dev-eden-testnet", + "runtimeExecutable": "pnpm", + "runtimeArgs": ["dev:preset", "eden_testnet"], + "port": 3000 + }, { "name": "dev-eth", "runtimeExecutable": "pnpm", diff --git a/.github/workflows/deploy-review.yml b/.github/workflows/deploy-review.yml new file mode 100644 index 0000000000..2858ceb965 --- /dev/null +++ b/.github/workflows/deploy-review.yml @@ -0,0 +1,115 @@ +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: staging + type: choice + options: + - none + # presets:start — generated from tools/dev-server/registry.json (run `pnpm presets:sync`) + - arbitrum + - arbitrum_sepolia + - base + - blackfort_testnet + - celo + - celo_sepolia + - eden_testnet + - eth + - eth_sepolia + - filecoin + - garnet + - gnosis + - gnosis_chiado + - hpp + - immutable + - mega_eth + - multichain + - neon_devnet + - numine + - optimism + - optimism_sepolia + - polygon + - rootstock + - rootstock_testnet + - robinhood + - scroll_sepolia + - shibarium + - stability_testnet + - staging + - staging_multichain + - tac + - tac_spb + - zetachain + - zetachain_testnet + - zilliqa + - zksync + - zora + # presets:end + +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 + # 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: + # Variant-independent tag: the image is preset- AND variant-agnostic, + # so one image serves both the `review` and `review-2` demos. This lets a + # deploy for one variant reuse (build_image=false) an image built under the other. + tags: | + type=raw,value=review-${{ 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: ${{ 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 + permissions: write-all diff --git a/.vscode/tasks.json b/.vscode/tasks.json index 7a85e0f985..51423577e6 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -328,6 +328,7 @@ "blackfort_testnet", "celo", "celo_sepolia", + "eden_testnet", "eth", "eth_sepolia", "filecoin", diff --git a/cspell.jsonc b/cspell.jsonc index 835750c092..ee73154924 100644 --- a/cspell.jsonc +++ b/cspell.jsonc @@ -116,6 +116,7 @@ "Emelyanov", "Enkrypt", "esbuild", + "evstack", "explorable", "facebookexternalhit", "favicons", @@ -168,6 +169,8 @@ "megaeth", "merkle", "metasuites", + "miscompilation", + "miscompiles", "mgas", "mload", "mmss", @@ -272,6 +275,7 @@ "uidotdev", "Ulyana", "unfinalized", + "unminified", "UNKN", "unparse", "unrs", diff --git a/next.config.js b/next.config.js index c3d10dbc88..4154a6399f 100644 --- a/next.config.js +++ b/next.config.js @@ -39,7 +39,16 @@ const moduleExports = { use: [ '@svgr/webpack' ], }, ); - config.resolve.fallback = { fs: false, net: false, tls: false }; + config.resolve.fallback = { + fs: false, + net: false, + tls: false, + // @metamask/sdk (reached via @wagmi/connectors -> @reown/appkit-adapter-wagmi) imports the + // React Native storage adapter unconditionally. It is an optional peer dep of a code path a + // browser bundle never takes, so resolve it to an empty module instead of letting webpack + // warn about it on every production build. + '@react-native-async-storage/async-storage': false, + }; config.externals.push('pino-pretty', 'lokijs', 'encoding'); config.experiments = { ...config.experiments, topLevelAwait: true }; diff --git a/package.json b/package.json index cd7826b4e1..f1c91edc98 100644 --- a/package.json +++ b/package.json @@ -17,8 +17,8 @@ "profile:analyze": "node ./tools/profiling/aggregate-react-profile.mjs", "presets:sync": "node ./tools/dev-server/sync-preset-lists.mjs --write", "presets:lint": "node ./tools/dev-server/sync-preset-lists.mjs", - "build": "next build", - "build:next": "./deploy/scripts/download_assets.sh ./public/assets/configs && pnpm svg:build-sprite && ./deploy/scripts/make_envs_script.sh && next build", + "build": "next build --webpack", + "build:next": "./deploy/scripts/download_assets.sh ./public/assets/configs && pnpm svg:build-sprite && ./deploy/scripts/make_envs_script.sh && next build --webpack", "build:docker": "./tools/scripts/build.docker.sh", "build:analyze": "BUNDLE_ANALYZER=true next build --webpack", "start": "next start", @@ -53,7 +53,7 @@ }, "dependencies": { "@blockscout/admin-rs-types": "1.5.0", - "@blockscout/api-types": "0.0.1-beta.82839e44ce", + "@blockscout/api-types": "0.0.1-beta.8e1692a", "@blockscout/bens-types": "1.7.1", "@blockscout/contracts-info-types": "1.5.2", "@blockscout/interchain-indexer-types": "1.6.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 8264ab44ef..cebcd73028 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -25,8 +25,8 @@ importers: specifier: 1.5.0 version: 1.5.0 '@blockscout/api-types': - specifier: 0.0.1-beta.82839e44ce - version: 0.0.1-beta.82839e44ce + specifier: 0.0.1-beta.8e1692a + version: 0.0.1-beta.8e1692a '@blockscout/bens-types': specifier: 1.7.1 version: 1.7.1 @@ -1321,8 +1321,8 @@ packages: '@blockscout/admin-rs-types@1.5.0': resolution: {integrity: sha512-QE+dpUaQDvOAb/wUJ98J3CBqfohbiBW/+AEqEmSdOOZ0XxPRUrke08krBnflq3GMACRwB+nwzQrsfl7KCrC1Eg==} - '@blockscout/api-types@0.0.1-beta.82839e44ce': - resolution: {integrity: sha512-DIyMKLqHqXmKCbf1eYCKbWsNY3VQ2NMXAlLyAgEkM7UYffEasUfAL0othyHfGyxvM4FMwU5tm9hlKHRFPmKYew==} + '@blockscout/api-types@0.0.1-beta.8e1692a': + resolution: {integrity: sha512-itkaQdbtJSwsJL6LQBAPQlSWuejDhuuUXxZTcfwDAup/6L/OOw0l0XwIEVEtqTaLVIxG1DyGOrr7gKz2fPrukQ==} '@blockscout/bens-types@1.7.1': resolution: {integrity: sha512-MNIvYbj1I2vcU2rb6otlRnVMIpFG2B1WDqC7HicNrt10DbM5yPZNfBcsNM+Mhk/965Aeg529Fd+BkV9zhINqjA==} @@ -14846,7 +14846,7 @@ snapshots: '@blockscout/admin-rs-types@1.5.0': {} - '@blockscout/api-types@0.0.1-beta.82839e44ce': {} + '@blockscout/api-types@0.0.1-beta.8e1692a': {} '@blockscout/bens-types@1.7.1': {} diff --git a/src/features/chain-variants/eden/pages/tx/TxDetailsEden.tsx b/src/features/chain-variants/eden/pages/tx/TxDetailsEden.tsx new file mode 100644 index 0000000000..0a78dbb689 --- /dev/null +++ b/src/features/chain-variants/eden/pages/tx/TxDetailsEden.tsx @@ -0,0 +1,115 @@ +// SPDX-License-Identifier: LicenseRef-Blockscout + +import { Flex, Grid } from '@chakra-ui/react'; +import React from 'react'; + +import type { schemas } from '@blockscout/api-types'; + +import AddressEntity from 'src/slices/address/components/entity/AddressEntity'; + +import * as DetailedInfo from 'src/shared/detailed-info/DetailedInfo'; +import CopyToClipboard from 'src/shared/texts/CopyToClipboard'; +import NativeCoinValue from 'src/shared/values/entity/NativeCoinValue'; + +import { Skeleton } from 'src/toolkit/chakra/skeleton'; +import { TruncatedText } from 'src/toolkit/components/truncation/TruncatedText'; + +/** One call of a sponsored batch transaction. `to` is `null` for a contract-creation call. */ +type TransactionEdenCall = NonNullable[number]; + +interface Props { + data: schemas['TransactionResponse']; + isLoading?: boolean; +} + +const HeaderItem = ({ children, isLoading }: { children: React.ReactNode; isLoading?: boolean }) => { + return ( + + { children } + + ); +}; + +const CallRow = ({ to, value, input, isLoading }: TransactionEdenCall & { isLoading?: boolean }) => { + return ( + <> +
+ { to ? + : + [ Contract creation ] + } +
+
+ +
+ + + + + + ); +}; + +const TxDetailsEden = ({ data, isLoading }: Props) => { + const { fee_payer: feePayer, calls } = data; + + if (!feePayer && !calls?.length) { + return null; + } + + return ( + <> + { feePayer && ( + <> + + Fee payer + + + + + + ) } + + { calls && calls.length > 0 && ( + <> + + Calls + + + + To + Value + Input + { calls.map((call, index) => ( + // a batch can repeat the same call, so the position in the batch is the only stable key + + )) } + + + + ) } + + ); +}; + +export default React.memo(TxDetailsEden); diff --git a/src/features/connect-wallet/CONTEXT.md b/src/features/connect-wallet/CONTEXT.md index cbe6e6a8a0..2829f0acd2 100644 --- a/src/features/connect-wallet/CONTEXT.md +++ b/src/features/connect-wallet/CONTEXT.md @@ -56,6 +56,14 @@ disabled *fallback*. would wait forever for a readiness signal only the deferred path emits. Moving dynamic mode onto the deferred model is a known follow-up. +**Before changing anything in the dynamic-mode graph, read +`.agents/adr/0001-webpack-for-production-builds.md`.** Reshaping it once already tripped a Turbopack +scope-hoisting bug that mis-binds a context inside the Dynamic-labs SDK and hard-crashes every page +in a production build — invisible in dev. That is why production bundles are built with webpack. The +trigger was never reducible to a single import, so treat any graph change here as able to re-trigger +it: verify with a production build (`pnpm prod:preset `) against an instance whose +`NEXT_PUBLIC_ACCOUNT_AUTH_PROVIDER=dynamic`, not just in dev. + ## Persisted connection Connection state is persisted in our **own** localStorage flag, not wagmi's diff --git a/src/slices/tx/components/TxType.spec.tsx b/src/slices/tx/components/TxType.spec.tsx new file mode 100644 index 0000000000..f17169a9be --- /dev/null +++ b/src/slices/tx/components/TxType.spec.tsx @@ -0,0 +1,25 @@ +// @vitest-environment jsdom +// SPDX-License-Identifier: LicenseRef-Blockscout + +import React from 'react'; + +import { afterEach, describe, expect, it } from 'vitest'; +import { cleanup, render, screen } from 'vitest/lib'; + +import TxType from './TxType'; + +describe('TxType', () => { + afterEach(cleanup); + + it('prefers a more informative type over sponsored_transaction', () => { + render(); + + expect(screen.queryByText('Contract call')).not.toBeNull(); + }); + + it('falls back to the generic label when a transaction is only sponsored', () => { + render(); + + expect(screen.queryByText('Transaction')).not.toBeNull(); + }); +}); diff --git a/src/slices/tx/components/TxType.tsx b/src/slices/tx/components/TxType.tsx index 9a0436fd02..2ce14cc069 100644 --- a/src/slices/tx/components/TxType.tsx +++ b/src/slices/tx/components/TxType.tsx @@ -21,6 +21,10 @@ const TYPES_ORDER: schemas['Transaction']['transaction_types'] = [ 'token_transfer', 'contract_call', 'coin_transfer', + // Listed last and deliberately given no label of its own — the details page header carries the + // "Sponsored" tag instead, and lists have no room for it. An unlisted type would score -1 here and sort + // ahead of every real one, masking labels like "Contract call". + 'sponsored_transaction', ]; const TxType = ({ types, isLoading, ...rest }: Props) => { diff --git a/src/slices/tx/pages/details/Transaction.tsx b/src/slices/tx/pages/details/Transaction.tsx index bce0a3e233..2a14a6dc8e 100644 --- a/src/slices/tx/pages/details/Transaction.tsx +++ b/src/slices/tx/pages/details/Transaction.tsx @@ -108,6 +108,10 @@ const TransactionPageContent = () => { } } + if (data?.transaction_types?.includes('sponsored_transaction')) { + txTags.push({ slug: 'sponsored', name: 'Sponsored', tagType: 'custom' as const, ordinal: 0 }); + } + const protocolTags = data?.to?.metadata?.tags?.filter(tag => tag.tagType === 'protocol'); if (protocolTags && protocolTags.length > 0) { txTags.push(...protocolTags); diff --git a/src/slices/tx/pages/details/info/TxDetails.tsx b/src/slices/tx/pages/details/info/TxDetails.tsx index 77ff4bd067..fdc8c64818 100644 --- a/src/slices/tx/pages/details/info/TxDetails.tsx +++ b/src/slices/tx/pages/details/info/TxDetails.tsx @@ -24,6 +24,7 @@ import LogDecodedInputData from 'src/slices/log/components/LogDecodedInputData'; import TxSocketAlert from 'src/slices/tx/components/TxSocketAlert'; import getConfirmationDuration from 'src/slices/tx/utils/get-confirmation-duration'; +import TxDetailsEden from 'src/features/chain-variants/eden/pages/tx/TxDetailsEden'; import TxAllowedPeekers from 'src/features/chain-variants/suave/pages/tx/TxAllowedPeekers'; import TxDetailsTacOperation from 'src/features/chain-variants/tac/pages/tx/TxDetailsTacOperation'; import TxDetailsCrossChainMessages from 'src/features/cross-chain-txs/pages/tx/TxDetailsCrossChainMessages'; @@ -807,6 +808,8 @@ const TxDetails = ({ data, isLoading, socketStatus, noTxActions }: Props) => { + + [--skip-build] # # --skip-build Start the server from the existing build (.next) without rebuilding. @@ -76,7 +79,7 @@ if [ "$skip_build" = false ]; then -v NEXT_PUBLIC_GIT_TAG=$(git describe --tags --abbrev=0) \ -v NEXT_PUBLIC_ICON_SPRITE_HASH="${NEXT_PUBLIC_ICON_SPRITE_HASH}" \ "${env_args[@]}" \ - -- bash -c 'source ./deploy/scripts/export_pro_api_flag.sh && ./deploy/scripts/make_envs_script.sh && next build' || exit 1 + -- bash -c 'source ./deploy/scripts/export_pro_api_flag.sh && ./deploy/scripts/make_envs_script.sh && next build --webpack' || exit 1 echo "" else if [ ! -f ./.env.tmp ]; then diff --git a/tools/dev-server/registry.json b/tools/dev-server/registry.json index f2d9fdb270..89929f9387 100644 --- a/tools/dev-server/registry.json +++ b/tools/dev-server/registry.json @@ -5,6 +5,7 @@ "blackfort_testnet": "https://blackfort-testnet.blockscout.com", "celo": "https://celo.blockscout.com", "celo_sepolia": "https://celo-sepolia.blockscout.com", + "eden_testnet": "https://eden-testnet.blockscout.com", "eth": "https://eth.blockscout.com", "eth_sepolia": "https://eth-sepolia.blockscout.com", "filecoin": "https://filecoin.blockscout.com",