All notable changes to the Aptos Explorer will be documented in this file.
The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.
- Coin / Fungible Asset detail — Mintable / Burnable / Freezable capability chips: The mint, burn, and freeze chips previously rendered in the Properties row on Coin (
/coin/$struct/info) and Fungible Asset (/fungible_asset/$address/info) detail pages have been removed. The resource-derived flags were frequently misleading — on-chainMintRef/BurnRef/TransferRefentries do not always reflect the issuer's actual ability to exercise those capabilities (some are stored but unreachable, transferred, or otherwise neutralized), and there is no general on-chain signal the explorer can use to distinguish the two cases. The supportingFaPropertiesDisplaycomponent, theuseGetFaPropertieshook, and the per-networkapp/data/{mainnet,testnet,devnet}/coinPropertyOverrides.tsmanual-override registry (including the previous mainnetPROPSoverride) are all gone, along with their tests. The Dispatchable indicator survives on FA pages only — when the FA metadata object owns a0x1::fungible_asset::DispatchFunctionStoreresource, a "Dispatchable" chip is rendered (backed by the newuseGetFaIsDispatchablehook). The new chip is annotated with one clickable source link per registered hook (withdraw / deposit / derived_balance fromDispatchFunctionStore, derived_supply from the separate0x1::fungible_asset::DeriveSupplyresource), each pointing at/account/{module_address}/modules/code/{module_name}so operators can audit the actual Move source backing the dispatch.FEAT-FA-002indocs/FEATURES_SPECIFICATION.mdhas been updated to match.
-
Account modules View/Run —
(0 , aa.default) is not a function(#1646): Deep links such as/account/0x1/.../modules/view/primary_fungible_store/balancecrashed with the root error boundary ("Something went wrong") when the selected function rendered Move source inCodeSnippet. Rolldown's CJS→ESM interop forreact-syntax-highlighter/dist/cjs/create-element.jsexposed the module namespace asdefaultinstead of the innercreateElementfunction, so production bundles called a non-callable object. A sharedresolveCjsDefaultExporthelper now unwraps one or two interop layers before rendering highlighted code. -
Loudly surface missing Aptos API keys on both client and server instead of silently dropping into the anonymous rate-limit bucket:
getApiKeynow emits a one-timeconsole.errorper network on the client when noVITE_APTOS_<NETWORK>_API_KEY(and no per-user Settings override) is configured for a network that normally has one. The error names the missing env var and points to Settings → API Keys, so deployments that forgot to wire the variable get a loud signal in browser devtools instead of silently routing every browser request into the shared anonymous bucket (the most recent root cause of widespread 429 reports). A symmetric warning,warnIfServerMissingApiKey, fires fromgetServerApiKeyin the SSR / Netlify Function process when noAPTOS_<NETWORK>_API_KEY(or client-side fallback) is configured, naming both the server env var and the matchingVITE_*client env var so operators can fix both at once. Both warnings throttle to once per network per process so they're visible without flooding logs, andlocalis excluded — it intentionally has no public key. No hardcoded fallback keys are introduced; deployments remain responsible for settingVITE_APTOS_<NETWORK>_API_KEY(browser) andAPTOS_<NETWORK>_API_KEY(SSR). To prevent future regressions in this area,AGENTS.mdgained a new "Never rename or remove an environment variable" section under Environment Configuration. -
Light mode rendering after MUI v9 upgrade: After the MUI v7 → v9 upgrade the explorer rendered with a mixed light/dark palette in light mode — the page background, body text, and
Containerpainted with light tokens, but most styled components (headerToolbar, nav buttons,TitleHashButton, tabs, alerts/banners, table cells, and form inputs) stayed on the dark-theme class names that SSR emitted, producing the black-on-black hash button and tab strip and the dark "Transaction History Limited" banner reported by users. The root cause was a hydration mismatch inuseProvideColorMode: SSR rendered withmode="dark"(the server has no access to thecolor_schemecookie), but the client'suseStateinitializer immediately read the cookie and returned"light". React 19 explicitly does not "patch up" attribute mismatches it detects during hydration (the runtime now emits a warning to that effect), so the className attributes on existing DOM elements stayed pinned to the server's dark-theme hashes even after Emotion generated fresh light-theme rules — and the page remained stuck in that mixed state.useProvideColorModenow initializesmodeto a constantSSR_DEFAULT_MODE("dark") on both the server and the first client render, then resolves the real mode from the cookie /prefers-color-schemeinside a post-hydrationuseEffectand schedulessetModeon the next macrotask so React processes it as an ordinary client-side update — which means MUI / Emotion regenerate the styled-component class names and React swaps the new hashes onto the DOM. Light-mode users will see a brief flash of dark mode before the post-hydration update commits; eliminating that flash will require turning onpalette.cssVariablesin the theme plus an inline color-scheme script and is tracked separately. -
Validators page: minute-long load with wallet connected: The Delegation tab (
/validators/delegationand/validators/enhanced_delegation) no longer waits up to a minute to display the validator list when a wallet is connected. Previously,useValidatorDelegationDatatriggered one sequential0x1::delegation_pool::get_stakeview call per validator (~150 on mainnet) just to populate the "My Deposit" column, and the entire table render was gated on those calls finishing.getBatchUserStakesnow first asks the indexer (delegator_distinct_pool) which pools the connected wallet actually has positions in — typically a handful — and only issuesget_stakeview calls for that subset, in parallel. The validator list itself no longer waits on this query at all: it renders immediately whether the wallet is connected, disconnected, or still loading, and the "My Deposit" column populates progressively. If the indexer call fails or any view call errors out, individual rows fall back to a neutral0instead of blocking the whole page.
- Account resources — framework resource summaries: Structured summaries (with collapsible raw JSON) on the account Resources tab and in transaction Changes for
0x1::decryption::PerEpochEncryptionKey(epoch + encryption key),0x1::decryption::PerBlockDecryptionKey(epoch + round + decryption key), and0x1::confidential_asset::GlobalConfig(allow list, global auditor, pool extend ref). - PWA share button: When the explorer is launched as an installed Progressive Web App (display-mode
standaloneorwindow-controls-overlay, plus legacy iOSnavigator.standalone), the persistent header now shows a Share icon on every page. Tapping it opens the OS share sheet vianavigator.sharewith the current page URL and title; on platforms without Web Share, it falls back to copying the URL to the clipboard and shows a "Link copied to clipboard" snackbar. The button is hidden in regular browser tabs to avoid duplicating the address-bar share/copy action. NewuseIsStandalonePWAhook (app/hooks/useIsStandalonePWA.ts) andsharePagehelper (app/components/layout/sharePage.ts) cover the detection and share/clipboard logic with unit tests. - Share button shown in iframe embeddings: The header Share icon now also appears when the explorer is running inside an iframe (same-origin or cross-origin), not just when installed as a PWA. Like a standalone PWA, an embedded iframe hides the browser address bar and its native share/copy affordance, so surfacing an in-app Share action keeps current-URL sharing one tap away. Detection is handled by a new
useIsInIframehook (app/hooks/useIsInIframe.ts, SSR-safe; treats cross-originwindow.topaccess errors as "framed") with unit tests covering top-level, same-origin frame, and cross-origin frame cases. - Account Info tab — key type identification: the account
Infotab now displays the authentication scheme used by the account, inferred from the most recent transaction it submitted. Recognized variants are Ed25519, Multi-Ed25519 (with K-of-N threshold), Single Key (annotated with the inner type — Ed25519, Secp256k1 ECDSA, Keyless, Federated Keyless, etc.), and MultiKey (withsignatures_required-of-totaland the per-position list of constituent sub-key types). Multi-agent and fee-payer authenticators are unwrapped to the primary signer first. Accounts with no submitted transactions (sequence_number === 0) are clearly labeled as such, and loading / error states match the existing object-refs UX.
- Vite v7 → v8 upgrade: Bumped
vitefrom7.3.2to8.0.11. Vite 8 ships Rolldown (Rust-based bundler) and Oxc as the unified replacements for Rollup and esbuild, and Lightning CSS replaces esbuild for CSS minification. None of the explorer's plugins (@vitejs/plugin-react-swc,@netlify/vite-plugin-tanstack-start,@tanstack/react-start,@tanstack/router-plugin,vite-plugin-svgr,vite-plugin-compression,rollup-plugin-visualizer) required a version bump — they already declaredvite ^8support.vite.config.tshad to swap the unsupported object form ofbuild.rollupOptions.output.manualChunksfor Rolldown'sbuild.rolldownOptions.output.codeSplitting.groups, with regex tests matching the same set of vendor packages (vendor-react,vendor-mui,vendor-charts,vendor-aptos,vendor-query,vendor-wallet) so the production code-splitting layout is preserved. The defaultbuild.target(baseline-widely-available) now tracks Chrome 111 / Edge 111 / Firefox 114 / Safari 16.4 instead of Chrome 107 / Edge 107 / Firefox 104 / Safari 16.0; all four are >2.5 years old so the supported audience is essentially unchanged. Build output is faster but otherwise byte-compatible with v7 for the same source. New Rolldown warnings aboutreact-helmet-async's default import are benign — the existingReactHelmetAsync.Helmet ?? ReactHelmetAsync.default?.Helmetfallbacks already handle both CJS and ESM interop shapes at runtime. - Material UI v7 → v9 upgrade:
@mui/materialand@mui/icons-materialbumped from7.3.10to9.0.1(MUI skipped v8). The official@mui/codemod@9.0.1 v9.0.0/system-propscodemod migrated ~400 system shorthand props (mt,mb,p,display,gap,width,bgcolor, etc.) onBox/Stack/Typography/Link/Gridinto thesxprop. Manual fixes:TextField'sInputProps/InputLabelProps→slotProps.input/slotProps.inputLabel;Dialog/Menu/SelectPaperProps→slotProps.paper;MenuMenuListProps→slotProps.list;SwitchinputProps→slotProps.input;ListItemTextprimaryTypographyProps→slotProps.primary;FormControlLabelcomponentsProps→slotProps;DialogTitle/TooltipTypographytextAlign/fontWeightprops →sx. v9 narrowsGriddirectionto'row' | 'row-reverse', sodirection="column"(including responsive forms) was moved tosx.flexDirection. Several@mui/icons-materialmodules were renamed (ErrorOutline→ErrorOutlineOutlined,HelpOutline→HelpOutlineOutlined,CheckCircleOutline→CheckCircleOutlineOutlined); imports were updated with aliases to preserve in-file naming. The autocomplete-drivenSearchInputwas updated to read the newparams.slotProps.input/htmlInputshape. Theme: removed an invalidmt: 5no-op fromMuiList.styleOverrides.root. No user-visible behavior changes are intended. - Light/dark mode toggle moved into the menu on every viewport: The dedicated dark-mode icon button has been removed from the header toolbar. The theme toggle now lives exclusively inside the new
HeaderOverflowMenu(renamed from the formerNavMobile, which is no longer mobile-only), rendered on all viewports — on compact viewports (xs–md) it continues to act as the primary navigation menu (with Transactions, Validators, Blocks, etc., plus the wallet connector), and on wide viewports (lg+) it collapses to a small "preferences" drop-down anchored next to the Settings icon containing just the "Switch to light mode" / "Switch to dark mode" item. This replaces the previous behavior where the menu only carried the toggle when running as a mobile PWA, and removes the special-case branching fromHeader.tsx.
- lint-staged upgraded to v17: Bumped
lint-stagedfrom16.4.0to17.0.2. v17 drops Node.js < 22.22.1 support; the repo already targets Node 24 via.node-version, so contributors are unaffected. The existinglint-stagedconfig (*→biome check --write --no-errors-on-unmatched) is unchanged. - Stricter type-only import enforcement: TypeScript
verbatimModuleSyntaxis now enabled (tsconfig.json) and Biome'suseImportTypewas promoted fromwarntoerror(biome.json). Type-only imports must be marked withimport type(and type-only re-exports withexport type); the compiler / linter will reject value-form imports of types instead of allowing them through with a warning. The codebase already complied with both rules under the previous warn-level enforcement, so this commit contains no source changes — it just prevents regressions in future PRs. This is also a prerequisite for confident bundle-size work on@aptos-labs/ts-sdk: with type-only imports guaranteed-erased, the only remaining bundle-size lever is the path-import shape of the runtime-value imports. - pnpm 11 migration — single-file configuration: Upgraded
packageManagerfrompnpm@10.33.2topnpm@11.1.1and consolidated all pnpm settings into a newpnpm-workspace.yaml(the v11 SSOT)..npmrcis gone, and thepnpmblock has been removed frompackage.json. Settings now live in camelCase underpnpm-workspace.yaml:registry,autoInstallPeers,strictPeerDependencies,minimumReleaseAge/minimumReleaseAgeExclude,overrides,patchedDependencies, and the renamedallowBuildsmap (replaces v10'sonlyBuiltDependencies; v11 requires an explicittrue/falseper build-script-bearing dependency, so@swc/coreandesbuildare nowtruewhile@parcel/watcherandsharpare pinned tofalseto match the previous v10 deny-by-default behavior).pnpm-lock.yamlwas regenerated under v11.@aptos-labs/ts-sdkwas added tominimumReleaseAgeExcludeas a temporary in-window exception (audit and remove after the version ages past the 7-day cutoff). CI is unaffected:pnpm/action-setup@v6reads the version frompackageManager, so the bump cascades automatically. - Coin / FA properties — manual override registry: The "Mintable / Burnable / Freezable / Dispatchable" chips on Coin and Fungible Asset detail pages can now be overridden per network via
app/data/{mainnet,testnet,devnet}/coinPropertyOverrides.ts. Overrides are partial (any flag you omit still falls back to the value derived from on-chain refs) and can key off either the legacy coin struct (e.g.0x…::propbase_coin::PROPS) or the FA metadata object address. First override: Propbase PROPS on mainnet now displays as not mintable, not burnable, and not freezable to reflect the issuer's destroyedMintRef/BurnRef/TransferRefcapabilities, even though the on-chain resources would otherwise mark those refs as present. - Deploy / markdown negotiation: Homepage
Accept: text/markdownhandling now runs in the TanStack Start SSR handler (bundledllms.txtviaapp/utils/markdownHomeNegotiation.ts) instead of a Netlify Edge Function, so deploys no longer registernetlify/edge-functions. - SSR server entry:
vite.config.tsnow setsserver.entry: "ssr"so production usesapp/ssr.tsx(cache-awareCache-Controland markdown negotiation) instead of the framework default server stub.
- pnpm 7-day minimum release age + exact version pinning:
.npmrcnow setsminimum-release-age=10080(minutes), sopnpm installrefuses to pick up any package version published in the last 7 days — including transitive ones referenced by the lockfile. This mitigates fast-moving supply-chain attacks (compromised maintainer tokens, malicious dependency releases) by giving the community time to detect and yank bad versions before they enter the explorer's dependency graph. Paired with this, every direct dependency inpackage.json(dependencies,devDependencies) and every entry inpnpm.overridesis now pinned to an exact version instead of a^/~/>=range, sopnpm install(and Renovate-driven bumps) cannot silently float the resolved version when the lockfile is regenerated. The only escape hatch is a narrowminimum-release-age-exclude[]=fast-uriin.npmrc, because the currently-lockedfast-uri@3.1.2sits right on the 7-day boundary; the exclusion should be removed once that version ages past the cutoff. Requires pnpm ≥ 10.16; the repo already pinspnpm@10.33.0viapackageManager. No installed package versions changed — the lockfile diff is specifier-only churn. - AIPs author cleanup — defense-in-depth against incomplete sanitization: the
cleanAuthorshelper now runs its<...>and(...)strip regexes in a fixed-point loop (until the string stops changing) and finishes with a sweep that removes any stray angle brackets/parens, addressing the CodeQL "Incomplete multi-character sanitization" finding. Author values are still rendered as React text (never as HTML), but the explorer now reduces nested patterns like<<script>>cleanly to nothing instead of leaving a stray>. Test mocks inuseGetReleases.test.tswere also tightened fromurl.includes("registry.npmjs.org")to exactnew URL(url).hostname === "registry.npmjs.org"checks (CodeQL "Incomplete URL substring sanitization"). - AIPs fetch — scope GitHub auth to api.github.com only:
fetchAIPsno longer attaches theAuthorizationheader (or any custom GitHub-API header) toraw.githubusercontent.comrequests. Sending those headers cross-origin to a host that doesn't accept them triggers a CORS preflight that the raw host doesn't answer, which would silently drop most AIP rows. Auth headers are now only sent toapi.github.com. .env.exampleGitHub PAT guidance: theVITE_GITHUB_TOKENblock now spells out that anyVITE_-prefixed env var ships in client JS and must therefore not be a credential meant to remain secret. The variable remains supported for local development against the GitHub API rate limit, but the example now explicitly recommends unauthenticated requests (or a server-side proxy) for deployed environments.
-
Account Transactions — pagination state now lives in
?page=: The legacy REST fallback for the accountTransactionstab (used when the GraphQL indexer is unavailable, e.g. on devnet) tracked the current page in component-local React state, so reloads, deep links, and shared URLs always landed back on page 1. The page index is now driven by?page=like every other transactions surface in the explorer, and is clamped to the available range so a stale?page=999URL still renders the closest valid page instead of an empty table. Behavior is unchanged for filtered queries on theUser Transactionspage (/transactions) and theAccount → TransactionsGraphQL path, which already used?page=— but those call sites now share a singlePageNumberPaginationhelper (app/components/PageNumberPagination.tsx) that canonicalizes URLs by dropping the redundant?page=1and preserves all other search params (type,network,fn_addr,fn_module,fn_name). New tests cover the URL ↔ page round-trip (app/components/PageNumberPagination.test.tsx) and lock inuseFunctionFilter's reset-?page=-on-filter-change behavior (app/api/hooks/useFunctionFilter.test.ts). -
Validators — patch missing operator addresses from
0x1::stake::StakePool: Some rows in mainnet'svalidator_stats_v2.jsonship without anoperator_address(e.g. when the upstream alloydb-proxy pipeline is missing the join row for a staking pool), which surfaces on the All Nodes and Delegation tables as a blank operator avatar and unlinkable operator column.useGetValidatorsnow detects rows whereoperator_addressis empty, unstandardizable, or the zero address (isOperatorAddressMissing) and fetches the on-chain0x1::stake::StakePool::operator_addressfor just those pools to patch the value, while leaving rows that already have a valid operator address untouched. The same fetch path (nowfetchStakePoolOperators, which reads the resource's top-leveloperator_addressfield directly — the previous chain-only fallback was looking up the wrong nested field) is reused for the existing empty-stats-JSON fallback. Coverage:app/api/hooks/useGetValidators.test.ts(buildValidatorsFromSourcespatch behavior,isOperatorAddressMissing). -
Validators — All Nodes when mainnet stats JSON is empty: The explorer merges off-chain
validator_stats_v2.jsonwith on-chainValidatorSetfor the validators table and map. When that JSON is empty (for example a stalled upload to the stats bucket), the UI used to show no rows and no map markers even though the chain still reported active validators. The merge step now falls back to on-chain rows so voting power and addresses stay visible; operator addresses are filled from each pool’s0x1::stake::StakePoolwhen the stats file is empty. Rewards performance, last epoch performance, and geo columns stay sparse until the JSON is restored. -
Transactions — function filter on All Transactions tab: The All Transactions view used to apply the entry-function filter only to the current page of mixed ledger transactions from the REST API, so sparse matches (for example
0x1::confidential_asset::*on mainnet or testnet) often showed an empty table after "View All Transactions" even though the User Transactions tab had hits. When a function filter is active, that tab now runs the same indexer-backed user-transaction query as User Transactions. Indexer GraphQL responses that omituser_transactions/account_transactionsor returnnullinstead of an array are normalized to empty lists so the UI does not throw. -
Transaction function filter now uses indexed columns: the "Filter by Entry Function" UI on
/transactions(User Transactions tab) and per-account transaction pages now queries theentry_function_contract_address,entry_function_module_name, andentry_function_function_namecolumns instead of the un-indexedentry_function_id_strcolumn. This fixes timeouts / errors when filtering by function. The filter UI now shows three separate fields (Contract Address, Module, Function) so users can filter by any combination without having to know the fulladdress::module::functionstring. -
Netlify SSR function —
TypeError: y.handler is not a function: After moving the SSR entry toapp/ssr.tsx(vite.config.tsserver.entry: "ssr"), the deployed Netlify Function failed at runtime withTypeError: y.handler is not a functionand brokeexplorer.aptoslabs.com.@netlify/vite-pluginwrites a wrapper that doesserverEntrypoint.fetchon the SSR module's default export, butapp/ssr.tsxexported the handler returned bycreateStartHandler(...)directly (a callable, not{ fetch }), so.fetchresolved toundefinedand the lambda runtime failed when it tried to call the handler. The default export is now wrapped in{ fetch }to match the framework's owndefault-entry/server.jsshape, which restores SSR. -
Netlify markdown Edge Function —
context.fetchnot a function: Production logs showedTypeError: context.fetch is not a functioninsidemarkdown-negotiationwhen the handler tried to load/llms.txtvia Netlify’sContext.fetchAPI. That runtime mismatch caused the edge layer to error for affected homepage requests (for example, clients sendingAccept: text/markdown) instead of falling through to SSR. Markdown negotiation was moved to TanStack Start SSR with bundledllms.txt, which avoidscontext.fetchentirely. -
Network deployments — framework release uses gas schedule:
/releases/networkscards no longer label0x1::version::Version.majoras “framework version”. The UI now shows Framework Release from0x1::gas_schedule::GasScheduleV2.feature_version, mapped to the framework train per aptos-coregas_feature_versions, plus Bytecode Format (max) from VM Binary Format feature flags. -
Network deployments — release branch URL never points to a non-existent branch: the commit-message parser used to accept three-component release tags like
[aptos-release-v1.43.1](the patch-suffixed form is malformed; Aptos cuts release branches per minor only). When such a tag slipped through, the network card on/releases/networksrenderedv1.43.1.xand linked toaptos-release-v1.43.1, which 404s. The parser now only accepts the canonical[aptos-release-vX.Y]form and the consumer adds a defensive^\d+\.\d+$guard, so malformed tags fall back to the commit-only display rather than producing a broken link. -
Releases hub — preserve
?network=across unknown-tab redirect:/releases/<garbage>?network=testnetused to redirect to/releases/networks(correct) but drop the?network=testnetselection (wrong), bouncing users back to mainnet. The redirect now forwards thenetworksearch param so the chain selection survives the bounce. -
Mobile nav — single Releases entry: the hamburger menu used to list "Deployments", "AIPs", and "Releases" as three separate items even though the desktop nav and
/releasesconsolidation collapse them into one tabbed hub. Mobile now matches: one Releases entry that lands on the Networks tab. -
Feature Flags by Network — distinct unknown state: when a per-network query fails (e.g. fullnode unreachable), the comparison table on
/releases/networkspreviously kept showing a spinner for that network's column, indistinguishable from a still-loading state. Failed networks now render a warning icon ("Network unreachable — value unknown") instead, matching the existing "Cells for those networks show as unknown" copy. -
Releases hub — reject unknown tab values: visiting
/releases/<garbage>used to silently render the default Networks tab while leaving the bad URL in the address bar (creating duplicate-content URLs and broken bookmarks). The route now validatesparams.tabinbeforeLoadand 302-replaces unknowns with/releases/networks. -
AIPs status — strip trailing comments: AIP frontmatter sometimes annotates the
Status:field with a YAML-style comment likeStatus: Draft # discussion: https://.... The explorer's lightweight frontmatter parser now drops the#and everything after it (for unquoted scalar values, matching YAML 1.2 semantics), so the AIPs table showsDraftrather than the full annotated string. Quoted values that contain#characters are preserved verbatim. -
AIPs author — strip emails and github links: AIP authors are written in many shapes (
Alice <alice@example.com>,[Alice](https://github.com/alice),Alice (@alice), etc.). The AIPs table now runs each comma-separated author through a cleanup pass that drops angle-bracketed emails/URLs, parenthesized URLs and@handles, markdown link wrappers (keeping the link text), and barehttps://...URLs, so each row shows just the names — e.g.Alice <a@b.io>, Bob (@bob)becomesAlice, Bob. Plain handles (davidiw, wrwg, msmouse) flow through unchanged.
-
Releases / Feature flags: Registered Aptos feature flag 112 as “Versioned Transaction Validation”. For IDs not yet in the explorer’s static list, names are now resolved on the fly from the aptos-core
FeatureFlagenum (via a CORS-friendly mirror), falling back to “Feature #N” only when that fetch fails. -
Header — compact layout earlier on medium widths: the main nav, wallet, and settings now switch to the hamburger menu at the
lgbreakpoint (~1200px) instead ofmd(~900px), so the top bar no longer crowds or clips between those widths. -
Releases hub mobile polish: AIPs table now puts a "Open on GitHub" icon button as the leading column instead of a "View →" link buried at the right; the Author column collapses on screens narrower than
sm(~600px) so the title stays readable; long titles wrap withword-breakrather than overflowing. The Feature Flags by Network comparison table now scrolls horizontally on small screens instead of squishing names ("Disallow init_module to Publish Modules (rolled out)" etc.) and wraps long names within the cell on tablet widths. The "Recent releases" inner table on each SDK/tool card keeps its publish-date column on a single line and lets long pre-release version strings wrap. Each network card row now wraps long monospace values (like the node commit short hash and release branch label) instead of letting them overflow the card. -
Releases hub consolidates Deployments, AIPs, and SDK & Tool Releases under one nav entry (
/releases): the previously separate top-level pages/deployments,/aips, and/releasesare now sub-tabs of a single Releases page (/releases/networks,/releases/aips,/releases/sdks). The header nav exposes a single Releases entry instead of three. Bare/releasesredirects to the defaultnetworkstab;/deploymentsand/aipspermanently redirect to/releases/networksand/releases/aipsso existing bookmarks and external links keep working.llms.txt,llms-full.txt,sitemap.xml, and the LLM-doc drift test were updated to document the new paths.
-
Transaction detail — Modules tab: When a transaction publishes or upgrades Move packages (
PublishPackageevents) or touches module bytecode (write_module/delete_modulewrite-set changes), the explorer shows a Modules tab before Changes with publish addresses and per-module rows (with links to the account module code view when the module name is known). -
Confidential assets — aggregate supply and account hints: fungible asset and coin info tabs show Confidential supply (pool) from
0x1::confidential_asset::get_total_confidential_supplywhen the view succeeds. The account Coins tab callshas_confidential_storeper FA metadata (v2 balances, or v1 when Panora lists a pairedfaAddress); when initialized, the row shows the actual visible balance/USD and a new Confidential column displays aVisibilityOfficon (with tooltip) signalling that an additional encrypted balance exists. Mobile card view shows the same icon next to the verification badge. -
Agent-readiness discovery surfaces: the explorer now ships structured metadata for autonomous agents and LLM-powered crawlers:
Linkresponse headers (RFC 8288) on/and/*advertising the API catalog, the Agent Skills index, MCP Server Card,llms.txt,llms-full.txt, and the sitemap/.well-known/api-catalog(RFC 9727 / RFC 9264 linkset JSON) pointing to the upstream Aptos fullnode REST APIs (mainnet/testnet/devnet), the indexer GraphQL API, and the explorer itself/.well-known/agent-skills/index.jsonplus per-skillSKILL.mdbundles (aptos-explorer-urls,aptos-explorer-search) following the Agent Skills Discovery RFC v0.2.0, with SHA-256 digests/.well-known/mcp/server-card.json(SEP-1649 / SEP-2127 draft) describing the Explorer server, WebMCP transport endpoint, and read-only navigation tool capabilities for pre-connection MCP agent discoveryscripts/update-agent-skills-index.mjshelper to regenerate the discovery index when skills changerobots.txtContent-Signaldirectives —ai-train=no, search=yes, ai-input=yes— at the top of the file and inside every AI-crawler group (contentsignals.org / draft-romm-aipref-contentsignals)- SSR markdown negotiation (
app/utils/markdownHomeNegotiation.ts) that serves the homepage asContent-Type: text/markdown(backed by bundledllms.txt) when the request includesAccept: text/markdown; HTML remains the default for browsers - WebMCP
navigator.modelContexttools (search_explorer,open_transaction,open_account,open_block,open_releases,open_coin) registered read-only from a new<WebMCPProvider />mounted in the root layout — a no-op on browsers without WebMCP support. When an agent explicitly passesnetwork(including"mainnet"), the tool forwards it to the router so it overrides any previously-selected network; omittingnetworkpreserves the user's current network. Each tool also returns the full effective URL (path + query string) alongside its{ok: true}result, so agents can surface the canonical link they just navigated to. Theaptos-explorer-urlsagent skill (public/.well-known/agent-skills/aptos-explorer-urls/SKILL.md) was extended to document the/releases/{networks,aips,sdks}hub and its legacy/deploymentsand/aipsredirects.
-
Network deployments — feature flag comparison table: a new "Feature Flags by Network" table on
/releases/networks(legacy/deploymentsredirects there) shows every known0x1::features::Featuresflag side-by-side across mainnet, testnet, and devnet (with check/cross/spinner cells per network). The default view is "Differences" so flags that aren't in sync across environments are immediately visible, with chip filters to switch to "All", "Enabled (anywhere)", or "Disabled (everywhere)". Unknown flag IDs that are enabled on chain but not in our static name registry still surface asFeature #Nrows so nothing is silently hidden. The static registry now mirrors every flag inaptos-core's canonicalFeatureFlagRust enum (IDs 1..=111), including deprecated and "rolled-out" slots, so users see real names everywhere instead ofFeature #Nplaceholders -
Network deployments — node commit on each card: each network card on
/releases/networks(legacy/deploymentsredirects there) now shows the running node binary's git commit (7-char short hash linked to theaptos-labs/aptos-coreGitHub commit) sourced from the fullnode index endpoint'sgit_hash. The per-card feature-flag accordion was removed in favor of the new comparison table above -
Network deployments — node release version: each network card on
/releases/networksnow resolves the runninggit_hashto a human-readableaptos-noderelease (e.g.v1.43.2) by parsing the commit message on GitHub for the[aptos-release-vX.Y]branch tag and theBump version to X.Y.Zline. When the commit is on a release branch but is not itself a version-bump, the card showsv1.43.xso operators still see which minor train the binary belongs to. The label links to the matchingaptos-release-vX.Ybranch on GitHub -
SDK & tool releases — stable release headline + drill-in: each card on
/releases/sdks(legacy/releasesredirects to the default Networks tab) now shows the latest stable SemVer release (excluding-rc,-alpha,-beta,-zeta, etc.) as the headline number rather than whatever the registry tags as "latest"; cards expand to a "Recent releases" table listing up to 25 recent versions per registry with publish dates, working links, and a "Pre-release" badge on each non-stable entry. When a registry has no stable release in the recent window, the card surfaces the latest pre-release with a clear "No stable release found" hint instead of silently showing a pre-release -
SDK & tool releases — publish dates for npm and PyPI: the TypeScript SDK card on
/releases/sdksnow resolves publish timestamps viatime[version]from the full npm registry document (instead of/latest, which omits them), and the Python SDK card now readsreleases[version][0].upload_time_iso_8601from PyPI — previously both cards never showed a relative date, which made older releases look stale -
Decibel bulk order detail: the Decibel tab on transaction pages now displays full bid/ask price-size ladders parsed from
place_bulk_orders_to_subaccountpayload arguments, structuredBulkOrderPlacedEventdata (including cancelled orders), andBulkOrderFilledEventfill tables — previously only the market name and subaccount were shown for bulk orders -
Labs verified token: Decibel Dollar fungible asset (
usDCBL, metadata object0x9640…45b0on mainnet) is included in the explorer manual verification list and coin metadata merge so it shows the Labs verified badge and correct branding where supported -
Dev tooling: Playwright (
@playwright/test,playwright.config.ts,test:e2e/test:e2e:install); Vitest excludese2e/**from unit runs; CI maps eachAPTOS_<NETWORK>_API_KEYrepository secret to bothVITE_APTOS_<NETWORK>_API_KEYandAPTOS_<NETWORK>_API_KEYon the verify job so builds use one API key identity for client and SSR -
CI: Playwright smoke tests (home, network query param, Blocks nav) run after
pnpm ci:verify -
CI / e2e: Playwright regression for FEAT-TXN-003 — testnet transaction Balance Change tab loads indexer fungible-asset data (
e2e/transaction-balance-change.spec.ts; outside CI, skips when the gateway returns 401 for the local preview origin so localpnpm exec playwright testis not flaky) -
Settings page (
/settings): dedicated full-page settings replacing the old header popup dialog. Includes API key overrides (per network) and a new Move Bytecode Decompilation opt-in toggle -
Decompilation opt-in: decompiled and disassembly code views now require explicit user opt-in at
/settings. Users must acknowledge a disclaimer that decompiled output may not match original source before the feature is enabled -
Settings: optional geomi.dev API key override can be set per network (instead of one key for all networks). Existing saved single keys are migrated to all networks on load until you save again.
-
Features Specification (
docs/FEATURES_SPECIFICATION.md): canonical catalog of every user-facing explorer feature with uniqueFEAT-*identifiers, test coverage map (Appendix B), and prioritized coverage gaps (Appendix C) -
AGENTS.md: regression prevention rules requiring agents to referencedocs/FEATURES_SPECIFICATION.mdwhen adding, modifying, or removing features and to keep the spec in sync with the codebase -
Behavioral tests for 25+ spec features covering search, transactions, accounts, validators, coins, tokens, network config, rate limiting, analytics, wallet, modules, SEO, and more — see
docs/FEATURES_SPECIFICATION.mdAppendix B for the full list -
Extracted pure redirect/navigation utilities into
app/utils/routeRedirects.tsfor testability (entity default tabs, token legacy redirects, validators redirects, header search routing, wallet network mismatch, portfolio URLs) -
Decibel perps transaction parser: transaction overview now shows human-readable action labels for Decibel perpetual exchange entry functions — order placement (limit, market, bulk, TWAP), order cancellation, deposit, and withdrawal actions display with amount, asset, and a link to the Decibel contract
-
Rate-limit drawer: a bottom sheet appears when any API request receives HTTP 429, informing the user they have been rate-limited and offering a button to open Settings and set an API key override or wait ~5 minutes for the limit to reset
-
Trace tab failure highlighting: when a transaction failed, the Trace tab shows an error banner with the VM status and visually highlights the failed call (red background, error chip with the abort reason) along with a dashed-red path from the root to the failing node
- Blocks list (
/blocks): block REST fetches run with bounded concurrency instead of one simultaneous request per row, and React Query keys include the saved API key identity so a new Settings key does not reuse cached data from the old key. Default visible row count is 20 (was 30). - Transaction events tab (
/txn/.../events): Fee Statement and Decibel formatted event views stack labels above values on small screens (below themdbreakpoint) instead of squeezing two table columns; nested price/size tables use the same stacked pattern; tables stay in horizontal scroll containers on wider layouts. Raw JSON (JsonViewCard) respects the content width on narrow viewports. The event Type row uses a fit-width title column so long Move type strings wrap more predictably. Shared implementation:ResponsiveKeyValueTable/ResponsiveKeyValueRow(app/components/Table/ResponsiveKeyValueTable.tsx) for reuse across the explorer. - Transaction overview (
/txn/.../userTxnOverview): the Signature block uses the same outlined table layout as the Fee Statement event view—labeled rows for scheme, public key, and signature (with expandable hex chips)—instead of only a collapsible JSON tree. Pending transactions use the same presentation. On small screens, label and value stack vertically; wide content can scroll horizontally so nothing is clipped. - Account modules (Packages / Code / Run / View): loading and empty states no longer flash “No Data” while package metadata is still fetching; addresses without
PackageRegistryor on-chain modules get clearer guidance instead of a generic empty state or misleading “Account Not Found”; on modules URLs, a 404 from the account-resources fetch no longer shows the account-not-found banner; Run/View show “No modules found” when the modules API returns 404 - Settings: API Key Overrides section includes an info icon with a short explanation of why to use your own geomi.dev key (dedicated rate limits) and a link to geomi.dev
- Rate-limit drawer is now non-blocking (persistent banner instead of modal overlay) so users can still interact with the page, settings, and navigation while the notice is visible
- Saving an API key in Settings automatically dismisses the rate-limit drawer and suppresses re-triggering for 10 seconds, preventing stale in-flight 429 responses from immediately re-showing the drawer
- Light theme: neutral grey app background (
#ECEEF2), white cards/panels, cooler borders, and soft grey stripes for tables and filled inputs instead of warm cream-on-bright-off-white; primary body text uses ink (#171612) for slightly softer contrast than pure black. Lighttheme-colormeta updated to match the canvas.
- Transaction Balance Change tab: the indexer GraphQL query now types
transaction_versionasbigint(matching the schema). The previousStringvariable caused Hasura validation errors, so fungible-asset activities never loaded and the tab appeared empty even when the indexer had data (for example gas fee rows on testnet). - AIPs page (
/aips): the table was empty because the GitHub tree filter regex (/^aips\/aip-\d+\.md$/) only matched the legacy filename shape; theaptos-foundation/AIPsrepo standardized onaips/aip-NNN-some-slug.md(zero-padded number plus a kebab-case slug) some time ago, so no files matched. The filter now accepts both shapes and the canonical AIP number is taken from the file'saip:frontmatter (with the filename digits as a fallback) so zero-padded filenames don't change the rendered AIP number. - Wallet transactions on localnet: non-Petra wallets that report a custom network name but use a loopback REST URL (for example
http://127.0.0.1:8080/v1) are no longer blocked when the explorer is on the local network; submission still requires a real loopback endpoint match. - Blocks list (
/blocks): recent rows now use the same REST block API as block detail pages, so block hash, timestamps, and transaction version ranges match what you see after opening a block (previously the indexerblock_metadata_transactionsrow was mislabeled as the block hash and version bounds could disagree with the node). - Coins page (
/coins): changing verification filters or the Emojicoins toggle no longer freezes the tab when thousands of assets match — the table virtualizes rows via an on-demandrenderRowpath and scrolls inside a bounded-height container instead of allocating every row up front - Dev mode:
useFeatureName()now reads from afeature_namecookie or theVITE_FEATURE_NAMEenv var instead of always returning"prod"— the Development Mode banner, Early Development Mode banner, and dev-only UI (e.g. Aptos Names banner) are functional again - SSR dev server: removed duplicate
tanstackRouter()plugin fromvite.config.ts— it conflicted with the code-splitting plugins already bundled insidetanstackStart(), causingReferenceError: TSRSplitComponent is not definedon every SSR request duringpnpm dev - Transaction overview Arguments table: column widths follow content again (no fixed 100px/140px layout), so long argument names no longer overlap type chips; type badges stay on one line where possible; the Arguments: label row uses a compact title column so the table has more horizontal room
- Object account pages (e.g. Decibel perp DEX) crashing with "Cannot convert object to primitive value" — the
/account/→/object/redirect now correctly passes TanStack Router's parsed search params instead of interpolating the search object in a URL template literal
- AIP-141 gas impact UI: account Gas Impact tab, gas warnings on account transaction lists, and the user-transaction overview banner; removed
useGetGasScheduleVersionand related utilities.
- Known address Greg (greg.apt →
0xc675…edce9e) with vendored profile image from @greg_nazario for explorer identicon and account banner (public/address-icons/greg-nazario.jpg) - Module Code (and Run/View source panel): qualified
module::functionand0x..::module::functionreferences in highlighted Move source navigate to the Code tab for that module/function (Cmd/Ctrl+click opens in a new tab) - Known-address branding (per network): optional
iconanddescriptionreplace blockies where configured, render an account-page banner, and enrich metadata; Decibel on mainnet is the first entry (app/data/mainnet/knownAddressBranding.ts) - Known-address
iconBadge(e.g.0x1on the Aptos mark) for framework accounts; mainnet icons/descriptions for bridges, DEX deployers, and CEX-labeled wallets; framework branding shared on testnet/devnet (app/data/aptosFrameworkAddressBranding.ts,public/address-icons/*) - CEX icons (Bybit, Bitfinex, Kraken, Gate.io, MEXC, Crypto.com, Flipster, Binance US): profile images from official X handles via
unavatar.io/twitter/..., vendored as PNG underpublic/address-icons/ public/address-icons/*.pngraster assets normalized to 128×128 RGBA (aspect-preserving scale, transparent letterboxing), except Decibel which keeps its original PNG dimensions; SVG brand marks unchanged- AnimeSwap: removed known-address branding (icon + banner copy); address label in
knownAddressesunchanged - Search (header autocomplete and inline
/+/searchresults): token logos when available, otherwise blockies or known-address brand marks viaidenticonKeyonSearchResult(SearchResultAvatar,searchUtils.ts) - Known-address DEX/bridge marks: vendor favicons (or official SVG/avatars) for Tapp, Sushi, Panora, PancakeSwap, Liquidswap, Kana, Hyperion, Cellana, Cetus, Aux, Wormhole, LayerZero; Econia via @EconiaLabs (unavatar); AptoSwap via GitHub org avatar; remove Obric from named-address list and branding (
defunctProtocolsunchanged) - GitHub Actions workflow CI (
pnpm ci:verify: generate TanStack route tree, lint, test, production build) tsr.config.jsonand@tanstack/router-clisoapp/routeTree.gen.tscan be regenerated without starting Vite; file is gitignored and produced before dev/build/lint/test viapre*scripts- LLM doc drift tests extended with additional path/tab snippets (
/validators/all, transaction tabs, multisig, enhanced delegation, etc.) sollms.txtandllms-full.txtstay aligned - JSON-LD
CollectionPagestructured data for network analytics (/analytics) and validator list tabs (/validators/{tab}), in addition to transactions/blocks/coins list hubs - Contributor docs:
docs/LLM_ACCESS.mdTanStack Routerhead()audit (only roothead(); child routes usePageMetadata) - JSON-LD:
Articlefortype="article"pages; home?search=aligns title/description and setsWebPage.about; Vitest coverage forgenerateStructuredData - SEO: avoid empty-segment canonical URLs on coin / FA / account / token titles;
.node-version+ CI pins pnpm10.32.1and Node from file - Function filter on transaction tables — filter by entry function ID (e.g.
0x1::coin::transfer) via?fn=URL parameter across User Transactions (server-side), All Transactions (client-side), and Account Transactions (server-side) views - Progressive Web App (PWA) support with service worker for offline caching
- Enhanced manifest.json with full PWA configuration (icons, screenshots, categories)
- Service worker with intelligent caching strategies for static assets, fonts, and API calls
- Netlify caching headers optimized for PWA assets (sw.js, manifest.json)
- Multi-agent documentation system with 7 specialized roles (Architect, Coder, Reviewer, Tester, QA/Auditor, Cost Cutter, Modernizer)
- Cross-tool compatibility with symlinks for Claude Code, Gemini, Warp, and GitHub Copilot
- Cursor IDE integration with role-specific rules (
.cursor/rules/) and notepads (.cursor/notepads/) - Antigravity support via
.agent/rules/ - Mistral Vibe support via
.vibe/agents/(TOML format) - OpenCode support via
.opencode/agent/ - Kanban-style task management in
.agents/tasks/ - Issue tracking templates in
.agents/issues/ - Task and issue templates in
.agents/templates/ - Browser-persisted Settings dialog with a client-side geomi.dev API key override
- Transaction page: the collapsible Transaction Debug Info block (full JSON + API link) appears only on the Overview tab, not under Events, Changes, Payload, etc.
- Module Code SSR/runtime: guard
_splatparsing and highlight.js AST props so malformed router params or DOM attributes cannot trigger “Cannot convert object to primitive value”; normalizeclassNameto string arrays beforereact-syntax-highlighter’screateElement - Module Code links: anchor
resolveMoveCodeLinkPathregex with a non-capturing group; keyboard (Enter/Space) activates the same navigation as click; injected spans userole="link"+ focusabletabIndex+aria-label - SSR / Netlify functions: import
react-syntax-highlighter’s CJScreate-elementhelper (not the ESM subpath) so Node does not throw “Cannot use import statement outside a module” when rendering module code - Account URL → object URL redirect preserves the path (e.g.
/modules/code/...) instead of always landing on Transactions, so links to object-published module code resolve correctly
- Hash “pill” buttons (
HashType.OTHERS, e.g. block hash) in light mode usebackground.defaultplus a hairline border so they read clearly on creme table rows (previously matchedneutralShade.darker==background.paper) - Semantic colors tuned for WCAG 2.1 AA text contrast in light and dark mode (links, disabled text, success/error/warning copy, JSON/code accents); added
aptosBrandColors.a11y.test.tsregression checks - Explorer UI typography and theme tokens aligned with Aptos Brand Guidelines: IBM Plex Sans (UI), IBM Plex Serif (display headings), IBM Plex Mono (data/code-adjacent UI) as specified stand-ins for Season / Akkurat Mono; tuned heading and label letter-spacing; semantic
info/divider/actionpalette fields; antialiased text baseline; brandtheme-color/ PWA colors (#0F0E0B,#F9F9F0) - ANS lookups now use the ts-sdk client (getPrimaryName, getAccountNames, getName) instead of the aptosnames.com public API; mainnet/testnet-only behavior and caching unchanged
- Enhanced
AGENTS.mdwith comprehensive role definitions and workflow documentation - Unified footer action link styling for consistency
- New features
- Changes to existing functionality
- Features that will be removed in future
- Removed features
- Bug fixes
- Security improvements