Ship Tier A Week 1–2: posture glossary, delivery mode, freshness UI, canonical tokens.
Some checks failed
Deploy Explorer Live / deploy (push) Failing after 13s
Validate Explorer / frontend (push) Failing after 18s
Validate Explorer / smoke-e2e (push) Has been skipped

Expose mission-control mode on home/bridge/analytics, quiet-chain freshness copy, and a canonical-first indexed token list with WETH9 metadata override and non-canonical warnings.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
defiQUG
2026-05-23 03:48:22 -07:00
parent ab9c1f9f98
commit 763ca75c21
25 changed files with 873 additions and 68 deletions

View File

@@ -0,0 +1,78 @@
package track1
import (
"strings"
"github.com/explorer/backend/api/freshness"
)
type bridgeDeliveryMode struct {
Kind string
Reason any
Scope any
}
func resolveBridgeDeliveryMode(hasRelays bool, diagnostics *freshness.Diagnostics, txFeed freshness.Completeness) bridgeDeliveryMode {
if !hasRelays {
if diagnostics != nil && isStaleTransactionVisibility(diagnostics) {
return bridgeDeliveryMode{
Kind: "mixed",
Reason: "partial_observability_inputs",
Scope: "homepage_summary_only",
}
}
return bridgeDeliveryMode{
Kind: "live",
Reason: nil,
Scope: nil,
}
}
if diagnostics != nil && isStaleTransactionVisibility(diagnostics) {
return bridgeDeliveryMode{
Kind: "mixed",
Reason: "relay_snapshot_only_source",
Scope: "bridge_monitoring_and_homepage",
}
}
if txFeed == freshness.CompletenessPartial || txFeed == freshness.CompletenessStale {
return bridgeDeliveryMode{
Kind: "mixed",
Reason: "partial_observability_inputs",
Scope: "bridge_monitoring_and_homepage",
}
}
return bridgeDeliveryMode{
Kind: "snapshot",
Reason: "live_homepage_stream_not_attached",
Scope: "relay_monitoring_homepage_card_only",
}
}
func isStaleTransactionVisibility(diagnostics *freshness.Diagnostics) bool {
if diagnostics == nil {
return false
}
state := strings.ToLower(strings.TrimSpace(diagnostics.ActivityState))
switch state {
case "fresh_head_stale_transaction_visibility", "lagging", "stale_transaction_visibility":
return true
default:
return strings.Contains(state, "stale") && strings.Contains(state, "transaction")
}
}
func buildBridgeModePayload(now string, resolved bridgeDeliveryMode) map[string]interface{} {
return map[string]interface{}{
"kind": resolved.Kind,
"updated_at": now,
"age_seconds": int64(0),
"reason": resolved.Reason,
"scope": resolved.Scope,
"source": freshness.SourceReported,
"confidence": freshness.ConfidenceHigh,
"provenance": freshness.ProvenanceMissionFeed,
}
}

View File

@@ -0,0 +1,36 @@
package track1
import (
"testing"
"github.com/explorer/backend/api/freshness"
"github.com/stretchr/testify/require"
)
func TestResolveBridgeDeliveryModeLiveWithoutRelays(t *testing.T) {
got := resolveBridgeDeliveryMode(false, nil, freshness.CompletenessComplete)
require.Equal(t, "live", got.Kind)
require.Nil(t, got.Reason)
}
func TestResolveBridgeDeliveryModeSnapshotWithRelays(t *testing.T) {
got := resolveBridgeDeliveryMode(true, nil, freshness.CompletenessComplete)
require.Equal(t, "snapshot", got.Kind)
require.Equal(t, "live_homepage_stream_not_attached", got.Reason)
require.Equal(t, "relay_monitoring_homepage_card_only", got.Scope)
}
func TestResolveBridgeDeliveryModeMixedWhenTransactionVisibilityStale(t *testing.T) {
diagnostics := &freshness.Diagnostics{
ActivityState: "fresh_head_stale_transaction_visibility",
}
got := resolveBridgeDeliveryMode(true, diagnostics, freshness.CompletenessPartial)
require.Equal(t, "mixed", got.Kind)
require.Equal(t, "relay_snapshot_only_source", got.Reason)
require.Equal(t, "bridge_monitoring_and_homepage", got.Scope)
}
func TestIsStaleTransactionVisibility(t *testing.T) {
require.True(t, isStaleTransactionVisibility(&freshness.Diagnostics{ActivityState: "fresh_head_stale_transaction_visibility"}))
require.False(t, isStaleTransactionVisibility(&freshness.Diagnostics{ActivityState: "healthy"}))
}

View File

@@ -133,6 +133,8 @@ func (s *Server) BuildBridgeStatusData(ctx context.Context) map[string]interface
}
if s.freshnessLoader != nil {
if snapshot, completeness, sampling, diagnostics, err := s.freshnessLoader(ctx); err == nil && snapshot != nil {
txFeed := completeness.TransactionsFeed
resolvedMode := resolveBridgeDeliveryMode(false, diagnostics, txFeed)
subsystems := map[string]interface{}{
"rpc_head": map[string]interface{}{
"status": chainStatusFromProbe(p138),
@@ -174,39 +176,13 @@ func (s *Server) BuildBridgeStatusData(ctx context.Context) map[string]interface
"issues": sampling.Issues,
}
}
modeKind := "live"
modeReason := any(nil)
modeScope := any(nil)
if relays, ok := data["ccip_relays"].(map[string]interface{}); ok && len(relays) > 0 {
modeKind = "snapshot"
modeReason = "live_homepage_stream_not_attached"
modeScope = "relay_monitoring_homepage_card_only"
subsystems["bridge_relay_monitoring"] = map[string]interface{}{
"status": overall,
"updated_at": now,
"age_seconds": int64(0),
"source": freshness.SourceReported,
"confidence": freshness.ConfidenceHigh,
"provenance": freshness.ProvenanceMissionFeed,
"completeness": freshness.CompletenessComplete,
}
}
data["freshness"] = snapshot
data["subsystems"] = subsystems
data["sampling"] = sampling
if diagnostics != nil {
data["diagnostics"] = diagnostics
}
data["mode"] = map[string]interface{}{
"kind": modeKind,
"updated_at": now,
"age_seconds": int64(0),
"reason": modeReason,
"scope": modeScope,
"source": freshness.SourceReported,
"confidence": freshness.ConfidenceHigh,
"provenance": freshness.ProvenanceMissionFeed,
}
data["mode"] = buildBridgeModePayload(now, resolvedMode)
}
}
if relays := FetchCCIPRelayHealths(ctx); relays != nil {
@@ -224,9 +200,22 @@ func (s *Server) BuildBridgeStatusData(ctx context.Context) map[string]interface
}
if mode, ok := data["mode"].(map[string]interface{}); ok {
if relays, ok := data["ccip_relays"].(map[string]interface{}); ok && len(relays) > 0 {
mode["kind"] = "snapshot"
mode["reason"] = "live_homepage_stream_not_attached"
mode["scope"] = "relay_monitoring_homepage_card_only"
var diagnostics *freshness.Diagnostics
if diag, ok := data["diagnostics"].(*freshness.Diagnostics); ok {
diagnostics = diag
}
txFeed := freshness.CompletenessUnavailable
if subsystems, ok := data["subsystems"].(map[string]interface{}); ok {
if txIndex, ok := subsystems["tx_index"].(map[string]interface{}); ok {
if feed, ok := txIndex["completeness"].(freshness.Completeness); ok {
txFeed = feed
}
}
}
resolved := resolveBridgeDeliveryMode(true, diagnostics, txFeed)
mode["kind"] = resolved.Kind
mode["reason"] = resolved.Reason
mode["scope"] = resolved.Scope
if subsystems, ok := data["subsystems"].(map[string]interface{}); ok {
subsystems["bridge_relay_monitoring"] = map[string]interface{}{
"status": data["status"],
@@ -239,6 +228,9 @@ func (s *Server) BuildBridgeStatusData(ctx context.Context) map[string]interface
}
}
}
} else if relays, ok := data["ccip_relays"].(map[string]interface{}); ok && len(relays) > 0 {
resolved := resolveBridgeDeliveryMode(true, nil, freshness.CompletenessUnavailable)
data["mode"] = buildBridgeModePayload(now, resolved)
}
return data
}

View File

@@ -212,6 +212,11 @@ func TestBuildBridgeStatusDataIncludesCCIPRelay(t *testing.T) {
require.Contains(t, got, "diagnostics")
require.Contains(t, got, "subsystems")
require.Contains(t, got, "mode")
mode, ok := got["mode"].(map[string]interface{})
require.True(t, ok)
require.Equal(t, "mixed", mode["kind"])
require.Equal(t, "relay_snapshot_only_source", mode["reason"])
require.Equal(t, "bridge_monitoring_and_homepage", mode["scope"])
}
func TestBuildBridgeStatusDataDegradesWhenNamedRelayFails(t *testing.T) {

View File

@@ -75,6 +75,9 @@ export default function ActivityContextPanel({
</Explain>
</div>
<EntityBadge label={resolveLabel(context.state)} tone={tone} />
{context.head_is_idle && context.state === 'low' ? (
<EntityBadge label="quiet chain" tone="info" />
) : null}
</div>
{compact ? (
@@ -130,6 +133,11 @@ export default function ActivityContextPanel({
Open last non-empty block
</Link>
) : null}
{context.block_gap_to_latest_transaction != null ? (
<span>
Block gap to latest visible transaction: {context.block_gap_to_latest_transaction.toLocaleString()}
</span>
) : null}
{context.latest_transaction_timestamp ? (
<span>Latest visible transaction time: {formatTimestamp(context.latest_transaction_timestamp)}</span>
) : null}

View File

@@ -3,10 +3,12 @@ import Navbar from './Navbar'
import Footer from './Footer'
import ExplorerAgentTool from './ExplorerAgentTool'
import { UiModeProvider } from './UiModeContext'
import { PostureGlossaryProvider } from './PostureGlossaryProvider'
export default function ExplorerChrome({ children }: { children: ReactNode }) {
return (
<UiModeProvider>
<PostureGlossaryProvider>
<div className="flex min-h-screen flex-col bg-gray-50 text-gray-900 dark:bg-gray-900 dark:text-gray-100">
<a
href="#main-content"
@@ -21,6 +23,7 @@ export default function ExplorerChrome({ children }: { children: ReactNode }) {
<ExplorerAgentTool />
<Footer />
</div>
</PostureGlossaryProvider>
</UiModeProvider>
)
}

View File

@@ -8,11 +8,15 @@ import {
import { formatRelativeAge } from '@/utils/format'
import { useUiMode } from './UiModeContext'
function buildSummary(context: ChainActivityContext) {
function buildSummary(context: ChainActivityContext, activityState?: string | null) {
if (context.transaction_visibility_unavailable) {
return 'Chain-head visibility is current, while transaction freshness is currently unavailable.'
}
if (activityState === 'quiet_chain' || (context.head_is_idle && context.state === 'low')) {
return 'The chain head is current, but recent head blocks are quiet — this is normal low-activity visibility, not a broken index.'
}
if (context.state === 'active') {
return 'Chain head and latest indexed transactions are closely aligned.'
}
@@ -28,11 +32,21 @@ function buildSummary(context: ChainActivityContext) {
return 'Freshness context is based on the latest visible public explorer evidence.'
}
function buildDetail(context: ChainActivityContext, diagnosticExplanation?: string | null) {
function buildDetail(context: ChainActivityContext, diagnosticExplanation?: string | null, activityState?: string | null) {
if (diagnosticExplanation) {
return diagnosticExplanation
}
if (activityState === 'quiet_chain') {
const latestNonEmptyBlock =
context.last_non_empty_block_number != null ? `#${context.last_non_empty_block_number.toLocaleString()}` : 'unknown'
const blockGap =
context.block_gap_to_latest_transaction != null
? `${context.block_gap_to_latest_transaction.toLocaleString()} blocks`
: 'unknown'
return `Quiet-chain signal: head blocks may be empty while the chain remains current. Block gap to latest visible transaction: ${blockGap}. Last non-empty block: ${latestNonEmptyBlock}.`
}
if (context.transaction_visibility_unavailable) {
return 'Use chain-head visibility and the last non-empty block as the current trust anchors.'
}
@@ -40,9 +54,13 @@ function buildDetail(context: ChainActivityContext, diagnosticExplanation?: stri
const latestTxAge = formatRelativeAge(context.latest_transaction_timestamp)
const latestNonEmptyBlock =
context.last_non_empty_block_number != null ? `#${context.last_non_empty_block_number.toLocaleString()}` : 'unknown'
const blockGap =
context.block_gap_to_latest_transaction != null
? `${context.block_gap_to_latest_transaction.toLocaleString()} blocks`
: null
if (context.head_is_idle) {
return `Latest visible transaction: ${latestTxAge}. Last non-empty block: ${latestNonEmptyBlock}.`
return `Latest visible transaction: ${latestTxAge}. Block gap: ${blockGap || 'unknown'}. Last non-empty block: ${latestNonEmptyBlock}.`
}
if (context.state === 'active') {
@@ -74,13 +92,14 @@ export default function FreshnessTrustNote({
const sourceLabel = resolveFreshnessSourceLabel(stats, bridgeStatus)
const confidenceBadges = summarizeFreshnessConfidence(stats, bridgeStatus)
const diagnosticExplanation = stats?.diagnostics?.explanation || bridgeStatus?.data?.diagnostics?.explanation || null
const activityState = stats?.diagnostics?.activity_state || bridgeStatus?.data?.diagnostics?.activity_state || null
const normalizedClassName = className ? ` ${className}` : ''
if (mode === 'expert') {
return (
<div className={`rounded-2xl border border-gray-200 bg-white/80 px-4 py-3 text-sm dark:border-gray-800 dark:bg-gray-950/40${normalizedClassName}`}>
<div className="flex flex-col gap-2 lg:flex-row lg:items-center lg:justify-between">
<div className="font-medium text-gray-900 dark:text-white">{buildSummary(context)}</div>
<div className="font-medium text-gray-900 dark:text-white">{buildSummary(context, activityState)}</div>
<div className="text-xs text-gray-600 dark:text-gray-400">{sourceLabel}</div>
</div>
<div className="mt-2 flex flex-wrap gap-2 text-xs text-gray-500 dark:text-gray-400">
@@ -99,9 +118,9 @@ export default function FreshnessTrustNote({
return (
<div className={`rounded-2xl border border-gray-200 bg-white/80 px-4 py-3 text-sm dark:border-gray-800 dark:bg-gray-950/40${normalizedClassName}`}>
<div className="font-medium text-gray-900 dark:text-white">{buildSummary(context)}</div>
<div className="font-medium text-gray-900 dark:text-white">{buildSummary(context, activityState)}</div>
<div className="mt-1 text-gray-600 dark:text-gray-400">
{normalizeSentence(buildDetail(context, diagnosticExplanation))}.{' '}
{normalizeSentence(buildDetail(context, diagnosticExplanation, activityState))}.{' '}
{scopeLabel ? `${normalizeSentence(scopeLabel)}. ` : ''}
{normalizeSentence(sourceLabel)}.
</div>

View File

@@ -0,0 +1,76 @@
'use client'
import { Card } from '@/libs/frontend-ui-primitives'
import EntityBadge from '@/components/common/EntityBadge'
import type { MissionControlMode } from '@/services/api/missionControl'
import { formatRelativeAge } from '@/utils/format'
const REASON_LABELS: Record<string, string> = {
live_homepage_stream_not_attached: 'Live homepage stream is not attached; relay posture uses snapshot polling.',
relay_snapshot_only_source: 'Relay monitoring uses snapshot sources while other explorer feeds remain live.',
partial_observability_inputs: 'Some freshness inputs are partial, so posture is reported conservatively.',
}
const SCOPE_LABELS: Record<string, string> = {
relay_monitoring_homepage_card_only: 'Affects relay monitoring and the homepage mission card only.',
bridge_monitoring_and_homepage: 'Affects bridge monitoring and homepage summary surfaces.',
homepage_summary_only: 'Affects homepage summary messaging only.',
}
function humanizeKey(value?: string | null): string {
if (!value) return 'Not specified'
return SCOPE_LABELS[value] || REASON_LABELS[value] || value.replaceAll('_', ' ')
}
function modeTone(kind?: string | null): 'success' | 'warning' | 'info' | 'neutral' {
switch (String(kind || '').toLowerCase()) {
case 'live':
return 'success'
case 'mixed':
return 'warning'
case 'snapshot':
return 'info'
default:
return 'neutral'
}
}
export default function MissionDeliveryModePanel({
mode,
title = 'Delivery mode',
className = '',
}: {
mode?: MissionControlMode | null
title?: string
className?: string
}) {
if (!mode?.kind) return null
const normalizedClassName = className ? ` ${className}` : ''
return (
<Card
className={`border border-indigo-200 bg-indigo-50/60 dark:border-indigo-900/40 dark:bg-indigo-950/20${normalizedClassName}`}
title={title}
>
<div className="flex flex-col gap-3">
<div className="flex flex-wrap items-center gap-2">
<EntityBadge label={`mode ${mode.kind}`} tone={modeTone(mode.kind)} />
{mode.updated_at ? (
<span className="text-xs text-gray-600 dark:text-gray-400">Updated {formatRelativeAge(mode.updated_at)}</span>
) : null}
</div>
{mode.reason ? (
<p className="text-sm leading-6 text-gray-700 dark:text-gray-300">
<span className="font-medium text-gray-900 dark:text-white">Reason:</span> {humanizeKey(String(mode.reason))}
</p>
) : null}
{mode.scope ? (
<p className="text-sm leading-6 text-gray-700 dark:text-gray-300">
<span className="font-medium text-gray-900 dark:text-white">Scope:</span> {humanizeKey(String(mode.scope))}
</p>
) : null}
</div>
</Card>
)
}

View File

@@ -0,0 +1,33 @@
'use client'
import EntityBadge from '@/components/common/EntityBadge'
import { usePostureGlossary } from '@/components/common/PostureGlossaryProvider'
import { resolvePostureTermId } from '@/data/postureGlossary'
export default function PostureBadge({
label,
tone,
className,
}: {
label: string
tone?: 'neutral' | 'success' | 'warning' | 'info'
className?: string
}) {
const { openTerm } = usePostureGlossary()
const termId = resolvePostureTermId(label)
if (!termId) {
return <EntityBadge label={label} tone={tone} className={className} />
}
return (
<button
type="button"
onClick={() => openTerm(termId)}
title="Open posture glossary"
className="rounded-full focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary-500"
>
<EntityBadge label={label} tone={tone} className={className} />
</button>
)
}

View File

@@ -0,0 +1,82 @@
'use client'
import { createContext, useCallback, useContext, useMemo, useState, type ReactNode } from 'react'
import Link from 'next/link'
import { getPostureGlossaryTerm, type PostureGlossaryTermId } from '@/data/postureGlossary'
interface PostureGlossaryContextValue {
openTerm: (termId: PostureGlossaryTermId) => void
close: () => void
}
const PostureGlossaryContext = createContext<PostureGlossaryContextValue | null>(null)
export function PostureGlossaryProvider({ children }: { children: ReactNode }) {
const [activeTermId, setActiveTermId] = useState<PostureGlossaryTermId | null>(null)
const activeTerm = useMemo(
() => (activeTermId ? getPostureGlossaryTerm(activeTermId) ?? null : null),
[activeTermId],
)
const openTerm = useCallback((termId: PostureGlossaryTermId) => {
setActiveTermId(termId)
}, [])
const close = useCallback(() => {
setActiveTermId(null)
}, [])
return (
<PostureGlossaryContext.Provider value={{ openTerm, close }}>
{children}
{activeTerm ? (
<div className="fixed inset-0 z-[80] flex items-end justify-center bg-black/45 p-4 sm:items-center" onClick={close}>
<div
role="dialog"
aria-modal="true"
aria-labelledby="posture-glossary-title"
className="max-h-[85vh] w-full max-w-xl overflow-y-auto rounded-2xl border border-gray-200 bg-white p-6 shadow-2xl dark:border-gray-700 dark:bg-gray-950"
onClick={(event) => event.stopPropagation()}
>
<div className="flex items-start justify-between gap-4">
<div>
<p className="text-xs font-semibold uppercase tracking-wide text-gray-500 dark:text-gray-400">Posture glossary</p>
<h2 id="posture-glossary-title" className="mt-1 text-xl font-semibold text-gray-900 dark:text-white">
{activeTerm.title}
</h2>
</div>
<button
type="button"
onClick={close}
className="rounded-lg border border-gray-300 px-3 py-1.5 text-sm font-medium text-gray-700 hover:bg-gray-50 dark:border-gray-700 dark:text-gray-200 dark:hover:bg-gray-900"
>
Close
</button>
</div>
<p className="mt-4 text-sm leading-6 text-gray-700 dark:text-gray-300">{activeTerm.summary}</p>
<div className="mt-4 rounded-xl border border-sky-200 bg-sky-50/70 p-4 text-sm leading-6 text-sky-950 dark:border-sky-900/50 dark:bg-sky-950/20 dark:text-sky-100">
<p className="text-xs font-semibold uppercase tracking-wide text-sky-700 dark:text-sky-300">Methodology</p>
<p className="mt-2">{activeTerm.methodology}</p>
</div>
<div className="mt-5 flex flex-wrap gap-3 text-sm">
<Link href="/docs/posture-glossary" className="font-medium text-primary-600 hover:underline" onClick={close}>
Full glossary
</Link>
<Link href="/docs/gru" className="font-medium text-primary-600 hover:underline" onClick={close}>
GRU guide
</Link>
</div>
</div>
</div>
) : null}
</PostureGlossaryContext.Provider>
)
}
export function usePostureGlossary() {
const context = useContext(PostureGlossaryContext)
if (!context) {
throw new Error('usePostureGlossary must be used within PostureGlossaryProvider')
}
return context
}

View File

@@ -19,6 +19,7 @@ import { formatWeiAsEth } from '@/utils/format'
import { summarizeChainActivity } from '@/utils/activityContext'
import ActivityContextPanel from '@/components/common/ActivityContextPanel'
import FreshnessTrustNote from '@/components/common/FreshnessTrustNote'
import MissionDeliveryModePanel from '@/components/common/MissionDeliveryModePanel'
import SubsystemPosturePanel from '@/components/common/SubsystemPosturePanel'
import { resolveEffectiveFreshness, shouldExplainEmptyHeadBlocks } from '@/utils/explorerFreshness'
import OperationsPageShell, {
@@ -156,6 +157,7 @@ export default function AnalyticsOperationsPage({
bridgeStatus={bridgeStatus}
scopeLabel="This page combines public stats, recent block samples, and indexed transactions."
/>
<MissionDeliveryModePanel className="mt-3" mode={bridgeStatus?.data?.mode} title="Analytics delivery mode" />
<SubsystemPosturePanel
className="mt-3"
subsystems={bridgeStatus?.data?.subsystems}

View File

@@ -14,6 +14,7 @@ import { explorerFeaturePages } from '@/data/explorerOperations'
import { summarizeChainActivity } from '@/utils/activityContext'
import ActivityContextPanel from '@/components/common/ActivityContextPanel'
import FreshnessTrustNote from '@/components/common/FreshnessTrustNote'
import MissionDeliveryModePanel from '@/components/common/MissionDeliveryModePanel'
import SubsystemPosturePanel from '@/components/common/SubsystemPosturePanel'
import { resolveEffectiveFreshness } from '@/utils/explorerFreshness'
import { bridgeRoutesApi, normalizeBridgeRouteEntries, type BridgeRoutesResponse } from '@/services/api/bridgeRoutes'
@@ -340,6 +341,7 @@ export default function BridgeMonitoringPage({
bridgeStatus={bridgeStatus}
scopeLabel="Bridge relay posture is shown alongside the same explorer freshness model used on the homepage and core explorer routes"
/>
<MissionDeliveryModePanel className="mt-3" mode={bridgeStatus?.data?.mode} title="Bridge delivery mode" />
<SubsystemPosturePanel
className="mt-3"
subsystems={bridgeStatus?.data?.subsystems}

View File

@@ -22,6 +22,7 @@ import { transactionsApi, type Transaction } from '@/services/api/transactions'
import { summarizeChainActivity } from '@/utils/activityContext'
import ActivityContextPanel from '@/components/common/ActivityContextPanel'
import FreshnessTrustNote from '@/components/common/FreshnessTrustNote'
import MissionDeliveryModePanel from '@/components/common/MissionDeliveryModePanel'
import MarketEvidenceNote from '@/components/common/MarketEvidenceNote'
import { Explain, useUiMode } from '@/components/common/UiModeContext'
import { resolveEffectiveFreshness, shouldExplainEmptyHeadBlocks } from '@/utils/explorerFreshness'
@@ -796,12 +797,30 @@ export default function Home({
))}
</div>
<MissionDeliveryModePanel className="mt-1" mode={missionMode} title="Homepage delivery mode" />
<ActivityContextPanel
context={activityContext}
title="Freshness Interpretation"
compact
/>
<FreshnessTrustNote
className="mt-3"
context={activityContext}
stats={stats}
bridgeStatus={bridgeStatus}
scopeLabel={
mode === 'guided'
? 'Homepage status combines chain freshness, transaction visibility, and mission-control posture.'
: 'Homepage freshness view aligns chain, transaction, and mission-control posture.'
}
/>
<button
type="button"
onClick={() => setStatsDetailsExpanded((current) => !current)}
className="text-sm font-semibold text-primary-600 hover:underline"
>
{statsDetailsExpanded ? 'Hide telemetry and freshness' : 'Show telemetry and freshness'}
{statsDetailsExpanded ? 'Hide extended telemetry' : 'Show extended telemetry'}
</button>
{statsDetailsExpanded ? (
@@ -849,22 +868,6 @@ export default function Home({
</Card>
)}
<ActivityContextPanel
context={activityContext}
title="Freshness Interpretation"
compact
/>
<FreshnessTrustNote
className="mt-3"
context={activityContext}
stats={stats}
bridgeStatus={bridgeStatus}
scopeLabel={
mode === 'guided'
? 'Homepage status combines chain freshness, transaction visibility, and mission-control posture.'
: 'Homepage freshness view aligns chain, transaction, and mission-control posture.'
}
/>
</>
) : null}
</div>

View File

@@ -0,0 +1,101 @@
export type PostureGlossaryTermId =
| 'x402'
| 'iso20022'
| 'forward-canonical'
| 'reference-asset'
| 'cw-public-network'
| 'transport-active'
| 'gru'
export interface PostureGlossaryTerm {
id: PostureGlossaryTermId
title: string
shortLabel: string
summary: string
methodology: string
}
export const postureGlossaryTerms: PostureGlossaryTerm[] = [
{
id: 'gru',
title: 'GRU instrument',
shortLabel: 'GRU',
summary: 'A Global Reserve Unit instrument issued under DBIS taxonomy on Chain 138 or represented on public networks.',
methodology:
'The explorer marks GRU when token metadata, registry tags, or curated catalog entries align with the GRU transport and compliance model documented in the GRU guide.',
},
{
id: 'x402',
title: 'x402 readiness',
shortLabel: 'x402 ready',
summary: 'The token surface exposes the typed-data and domain metadata commonly required for HTTP-native payment authorization flows.',
methodology:
'Readiness is inferred from detected signing surfaces (EIP-712 domain, ERC-5267, and ERC-2612 or ERC-3009). It describes technical capability — not a guarantee that a live x402 merchant endpoint exists.',
},
{
id: 'iso20022',
title: 'ISO-20022 alignment',
shortLabel: 'ISO-20022',
summary: 'The asset is modeled as part of the ISO-20022-aligned settlement and reporting posture for institutional messaging.',
methodology:
'This badge reflects GRU metadata and governance expectations around supervised disclosure — not a claim that every transfer is already formatted as an ISO-20022 message on-chain.',
},
{
id: 'forward-canonical',
title: 'Forward-canonical posture',
shortLabel: 'forward canonical',
summary: 'The asset version is the forward-looking canonical representation operators should wire for new integrations.',
methodology:
'Used when a token family has legacy or parallel deployments. Prefer forward-canonical addresses for routers, wallets, and explorer deep links unless a migration note says otherwise.',
},
{
id: 'reference-asset',
title: 'Reference asset',
shortLabel: 'reference asset',
summary: 'A non-GRU mirrored or externally issued asset shown for routing, liquidity, or price context.',
methodology:
'Reference assets help explain cross-chain routes and pool composition. They are not implied to be DBIS-issued compliant instruments unless separately tagged.',
},
{
id: 'cw-public-network',
title: 'cW public-network representation',
shortLabel: 'cW public-network',
summary: 'A wrapped GRU instrument activated on a public network (for example mainnet cWUSDC) while Chain 138 remains the program ledger.',
methodology:
'Public-network overlays use cW* naming. Liquidity and bridge lanes may reference these addresses even when the canonical compliant token lives on Chain 138.',
},
{
id: 'transport-active',
title: 'transportActive (config compatibility)',
shortLabel: 'transportActive',
summary: 'Legacy JSON key indicating whether a public-network transport overlay is active in published `/config` manifests.',
methodology:
'Machine consumers should treat this as a v1 compatibility field. A future v2 schema will expose `publicNetworkActive` aliases before old keys are removed.',
},
]
const labelToTermId: Record<string, PostureGlossaryTermId> = {
gru: 'gru',
'x402 ready': 'x402',
'x402 not ready': 'x402',
'iso-20022': 'iso20022',
'iso-20022 aligned': 'iso20022',
'iso-20022 unclear': 'iso20022',
'forward canonical': 'forward-canonical',
'reference asset': 'reference-asset',
wrapped: 'cw-public-network',
transportactive: 'transport-active',
}
export function resolvePostureTermId(label: string): PostureGlossaryTermId | null {
const normalized = label.trim().toLowerCase()
if (labelToTermId[normalized]) return labelToTermId[normalized]
if (normalized.startsWith('cw public-network')) return 'cw-public-network'
if (normalized.startsWith('forward ')) return 'forward-canonical'
if (normalized.includes('transportactive') || normalized.includes('transport active')) return 'transport-active'
return null
}
export function getPostureGlossaryTerm(id: PostureGlossaryTermId): PostureGlossaryTerm | undefined {
return postureGlossaryTerms.find((term) => term.id === id)
}

View File

@@ -21,6 +21,7 @@ import {
import { formatTimestamp, formatTokenAmount, formatWeiAsEth } from '@/utils/format'
import { DetailRow } from '@/components/common/DetailRow'
import EntityBadge from '@/components/common/EntityBadge'
import PostureBadge from '@/components/common/PostureBadge'
import {
isWatchlistEntry,
readWatchlistFromStorage,
@@ -377,8 +378,8 @@ export default function AddressDetailPage() {
<span>{balance.token_symbol || balance.token_name || 'Token'}</span>
)}
{gruMetadata ? <EntityBadge label="GRU" tone="success" /> : null}
{gruMetadata?.x402Ready ? <EntityBadge label="x402 ready" tone="info" /> : null}
{gruMetadata?.iso20022Ready ? <EntityBadge label="ISO-20022" tone="info" /> : null}
{gruMetadata?.x402Ready ? <PostureBadge label="x402 ready" tone="info" /> : null}
{gruMetadata?.iso20022Ready ? <PostureBadge label="ISO-20022" tone="info" /> : null}
</div>
{balance.token_name && balance.token_symbol && (
<div className="text-xs text-gray-500 dark:text-gray-400">{balance.token_name}</div>
@@ -430,9 +431,9 @@ export default function AddressDetailPage() {
<div className="flex flex-wrap items-center gap-2">
<div className="font-medium text-gray-900 dark:text-white">{transfer.token_symbol || transfer.token_name || 'Token'}</div>
{gruMetadata ? <EntityBadge label="GRU" tone="success" /> : null}
{gruMetadata?.x402Ready ? <EntityBadge label="x402 ready" tone="info" /> : null}
{gruMetadata?.iso20022Ready ? <EntityBadge label="ISO-20022" tone="info" /> : null}
{gruMetadata?.transportActiveVersion ? <EntityBadge label={`cW public-network ${gruMetadata.transportActiveVersion}`} tone="warning" /> : null}
{gruMetadata?.x402Ready ? <PostureBadge label="x402 ready" tone="info" /> : null}
{gruMetadata?.iso20022Ready ? <PostureBadge label="ISO-20022" tone="info" /> : null}
{gruMetadata?.transportActiveVersion ? <PostureBadge label={`cW public-network ${gruMetadata.transportActiveVersion}`} tone="warning" /> : null}
</div>
{transfer.token_address && (
<Link href={`/tokens/${transfer.token_address}`} className="text-primary-600 hover:underline">

View File

@@ -5,6 +5,11 @@ import { Card } from '@/libs/frontend-ui-primitives'
import PageIntro from '@/components/common/PageIntro'
const docsCards = [
{
title: 'Posture glossary',
href: '/docs/posture-glossary',
description: 'First-read definitions for x402, ISO-20022, forward-canonical, cW public-network, and related explorer badges.',
},
{
title: 'Public API access',
href: '/docs/public-api-access',

View File

@@ -0,0 +1,80 @@
'use client'
import Link from 'next/link'
import { Card } from '@/libs/frontend-ui-primitives'
import PageIntro from '@/components/common/PageIntro'
import PostureBadge from '@/components/common/PostureBadge'
import { postureGlossaryTerms } from '@/data/postureGlossary'
export default function PostureGlossaryDocsPage() {
return (
<div className="container mx-auto px-4 py-6 sm:py-8">
<PageIntro
eyebrow="Explorer Documentation"
title="Posture glossary"
description="First-read explanations for institutional posture badges shown on token, address, transaction, and search surfaces."
actions={[
{ href: '/docs/gru', label: 'GRU guide' },
{ href: '/tokens', label: 'Browse tokens' },
{ href: '/search?q=cUSDC', label: 'Search cUSDC' },
]}
/>
<div className="space-y-6">
<Card title="How to use this glossary">
<p className="text-sm leading-6 text-gray-600 dark:text-gray-400">
Click any posture badge on a live token, address, or transaction page to open the same definitions in a drawer.
This page is the canonical long-form reference for audit and policy reviewers.
</p>
<div className="mt-4 flex flex-wrap gap-2">
<PostureBadge label="GRU" tone="success" />
<PostureBadge label="x402 ready" tone="info" />
<PostureBadge label="ISO-20022" tone="info" />
<PostureBadge label="forward canonical" tone="success" />
<PostureBadge label="reference asset" tone="info" />
</div>
</Card>
{postureGlossaryTerms.map((term) => (
<Card key={term.id} title={term.title}>
<div className="space-y-3 text-sm leading-6 text-gray-600 dark:text-gray-400">
<p>{term.summary}</p>
<div className="rounded-xl border border-sky-200 bg-sky-50/70 p-4 text-sky-950 dark:border-sky-900/50 dark:bg-sky-950/20 dark:text-sky-100">
<p className="text-xs font-semibold uppercase tracking-wide text-sky-700 dark:text-sky-300">Methodology</p>
<p className="mt-2">{term.methodology}</p>
</div>
{term.id === 'transport-active' ? (
<p>
Planned v2 aliases: <code className="rounded bg-gray-100 px-1.5 py-0.5 text-xs dark:bg-gray-900">publicNetworkActive</code>
{' '}and{' '}
<code className="rounded bg-gray-100 px-1.5 py-0.5 text-xs dark:bg-gray-900">livePublicNetworkAssets</code>.
</p>
) : null}
</div>
</Card>
))}
<Card title="Related references">
<ul className="list-disc space-y-2 pl-5 text-sm leading-6 text-gray-600 dark:text-gray-400">
<li>
<Link href="/docs/gru" className="text-primary-600 hover:underline">
GRU guide
</Link>
</li>
<li>
<Link href="/docs/public-api-access" className="text-primary-600 hover:underline">
Public API access
</Link>
</li>
<li>
Public machine config at{' '}
<a href="/config/DUAL_CHAIN_TOKEN_LIST.tokenlist.json" className="text-primary-600 hover:underline">
/config/DUAL_CHAIN_TOKEN_LIST.tokenlist.json
</a>
</li>
</ul>
</Card>
</div>
</div>
)
}

View File

@@ -7,6 +7,7 @@ import type { TokenListToken } from '@/services/api/config'
import { tokensApi } from '@/services/api/tokens'
import { tokenAggregationApi, type TokenAggregationTokenSnapshot } from '@/services/api/tokenAggregation'
import EntityBadge from '@/components/common/EntityBadge'
import PostureBadge from '@/components/common/PostureBadge'
import {
inferDirectSearchTarget,
inferTokenSearchTarget,
@@ -394,7 +395,7 @@ export default function SearchPage({
{result.token_type && <EntityBadge label={result.token_type} tone="warning" />}
{result.is_curated_token && <EntityBadge label="listed" tone="success" />}
{result.is_gru_token && <EntityBadge label="GRU" tone="success" />}
{result.is_x402_ready && <EntityBadge label="x402 ready" tone="info" />}
{result.is_x402_ready && <PostureBadge label="x402 ready" tone="info" />}
{result.is_wrapped_transport && <EntityBadge label="cW public-network" tone="warning" />}
{result.currency_code ? <EntityBadge label={result.currency_code} tone="neutral" /> : null}
{result.match_reason ? <EntityBadge label={result.match_reason} tone="info" className="normal-case tracking-normal" /> : null}

View File

@@ -10,6 +10,7 @@ import type { MissionControlLiquidityPool } from '@/services/api/routes'
import PageIntro from '@/components/common/PageIntro'
import { DetailRow } from '@/components/common/DetailRow'
import EntityBadge from '@/components/common/EntityBadge'
import PostureBadge from '@/components/common/PostureBadge'
import GruStandardsCard from '@/components/common/GruStandardsCard'
import TokenSigningSurfaceCard from '@/components/common/TokenSigningSurfaceCard'
import MarketEvidenceNote from '@/components/common/MarketEvidenceNote'
@@ -260,8 +261,8 @@ export default function TokenDetailPage() {
return (
<div className="flex flex-wrap gap-2">
<EntityBadge label="GRU" tone="success" />
{gruMetadata.x402Ready ? <EntityBadge label="x402 ready" tone="info" /> : null}
{gruMetadata.iso20022Ready ? <EntityBadge label="ISO-20022" tone="info" /> : null}
{gruMetadata.x402Ready ? <PostureBadge label="x402 ready" tone="info" /> : null}
{gruMetadata.iso20022Ready ? <PostureBadge label="ISO-20022" tone="info" /> : null}
</div>
)
},
@@ -358,6 +359,16 @@ export default function TokenDetailPage() {
</div>
) : (
<div className="space-y-6">
{!provenance?.listed ? (
<Card className="border border-amber-200 bg-amber-50/70 dark:border-amber-900/50 dark:bg-amber-950/20">
<div className="flex flex-wrap items-center gap-2">
<PostureBadge label="Non-canonical indexed token" tone="warning" />
</div>
<p className="mt-3 text-sm leading-6 text-amber-950 dark:text-amber-100">
This contract is indexed by Blockscout but is not in the curated Chain 138 token registry. Prefer canonical addresses from the token index for trading, liquidity, and bridge routing.
</p>
</Card>
) : null}
<Card title="Token Overview">
<dl className="space-y-4">
<DetailRow label="Name">{token.name || provenance?.name || 'Unknown'}</DetailRow>
@@ -451,9 +462,9 @@ export default function TokenDetailPage() {
<div className="rounded-2xl border border-gray-200 bg-gray-50 p-4 dark:border-gray-700 dark:bg-gray-900/40">
<div className="text-xs font-semibold uppercase tracking-wide text-gray-500 dark:text-gray-400">x402 readiness</div>
<div className="mt-3 flex flex-wrap gap-2">
<EntityBadge label={gruExplorerMetadata.x402Ready ? 'x402 ready' : 'x402 not ready'} tone={gruExplorerMetadata.x402Ready ? 'success' : 'warning'} />
<PostureBadge label={gruExplorerMetadata.x402Ready ? 'x402 ready' : 'x402 not ready'} tone={gruExplorerMetadata.x402Ready ? 'success' : 'warning'} />
{gruExplorerMetadata.x402PreferredVersion ? <EntityBadge label={`preferred ${gruExplorerMetadata.x402PreferredVersion}`} tone="info" /> : null}
{gruExplorerMetadata.canonicalForwardVersion ? <EntityBadge label={`forward ${gruExplorerMetadata.canonicalForwardVersion}`} tone="success" /> : null}
{gruExplorerMetadata.canonicalForwardVersion ? <PostureBadge label={`forward ${gruExplorerMetadata.canonicalForwardVersion}`} tone="success" /> : null}
</div>
<div className="mt-3 text-sm text-gray-600 dark:text-gray-400">
{gruExplorerMetadata.x402Ready
@@ -464,7 +475,7 @@ export default function TokenDetailPage() {
<div className="rounded-2xl border border-gray-200 bg-gray-50 p-4 dark:border-gray-700 dark:bg-gray-900/40">
<div className="text-xs font-semibold uppercase tracking-wide text-gray-500 dark:text-gray-400">ISO-20022 and governance</div>
<div className="mt-3 flex flex-wrap gap-2">
<EntityBadge label={gruExplorerMetadata.iso20022Ready ? 'ISO-20022 aligned' : 'ISO-20022 unclear'} tone={gruExplorerMetadata.iso20022Ready ? 'success' : 'warning'} />
<PostureBadge label={gruExplorerMetadata.iso20022Ready ? 'ISO-20022 aligned' : 'ISO-20022 unclear'} tone={gruExplorerMetadata.iso20022Ready ? 'success' : 'warning'} />
{gruExplorerMetadata.currencyCode ? <EntityBadge label={gruExplorerMetadata.currencyCode} tone="neutral" /> : null}
</div>
<div className="mt-3 text-sm text-gray-600 dark:text-gray-400">
@@ -473,6 +484,9 @@ export default function TokenDetailPage() {
: 'The explorer does not currently have a strong ISO-20022 posture signal for this asset.'}
</div>
<div className="mt-3 flex flex-wrap gap-3 text-sm">
<Link href="/docs/posture-glossary" className="text-primary-600 hover:underline">
Posture glossary
</Link>
<Link href="/docs/gru" className="text-primary-600 hover:underline">
GRU guide
</Link>

View File

@@ -6,8 +6,13 @@ import { Card } from '@/libs/frontend-ui-primitives'
import PageIntro from '@/components/common/PageIntro'
import EntityBadge from '@/components/common/EntityBadge'
import MarketEvidenceNote from '@/components/common/MarketEvidenceNote'
import { tokensApi } from '@/services/api/tokens'
import { tokensApi, type IndexedTokenListItem } from '@/services/api/tokens'
import type { TokenListToken } from '@/services/api/config'
import {
buildCanonicalAddressSet,
isCanonicalTokenAddress,
sortIndexedTokensCanonicalFirst,
} from '@/utils/canonicalTokens'
import { tokenAggregationApi, type TokenAggregationTokenSnapshot } from '@/services/api/tokenAggregation'
import { fetchTokenListForSurface, TOKEN_LIST_SURFACE_LABELS } from '@/services/api/tokenListSurfaces'
import { selectCuratedFeaturedTokens } from '@/utils/featuredTokens'
@@ -56,6 +61,8 @@ export default function TokensPage({ initialCuratedTokens }: TokensPageProps) {
const router = useRouter()
const [query, setQuery] = useState('')
const [curatedTokens, setCuratedTokens] = useState<TokenListToken[]>(initialCuratedTokens)
const [indexedTokens, setIndexedTokens] = useState<IndexedTokenListItem[]>([])
const [indexedLoading, setIndexedLoading] = useState(true)
const [featuredMarkets, setFeaturedMarkets] = useState<Record<string, TokenAggregationTokenSnapshot>>({})
const handleSubmit = (event: React.FormEvent) => {
@@ -90,6 +97,33 @@ export default function TokensPage({ initialCuratedTokens }: TokensPageProps) {
[curatedTokens],
)
const canonicalAddressSet = useMemo(() => buildCanonicalAddressSet(curatedTokens), [curatedTokens])
const sortedIndexedTokens = useMemo(
() => sortIndexedTokensCanonicalFirst(indexedTokens, canonicalAddressSet),
[canonicalAddressSet, indexedTokens],
)
useEffect(() => {
let active = true
setIndexedLoading(true)
tokensApi.listIndexedSafe(1, 50).then(({ ok, data }) => {
if (!active) return
setIndexedTokens(ok ? data : [])
setIndexedLoading(false)
}).catch(() => {
if (active) {
setIndexedTokens([])
setIndexedLoading(false)
}
})
return () => {
active = false
}
}, [])
useEffect(() => {
let active = true
@@ -197,6 +231,49 @@ export default function TokensPage({ initialCuratedTokens }: TokensPageProps) {
</Card>
</div>
<div className="mt-5">
<Card title="Indexed tokens (Blockscout)">
<p className="mb-4 text-sm text-gray-600 dark:text-gray-400">
Canonical registry tokens appear first. Non-canonical indexed duplicates are labeled so operators can distinguish curated assets from stray contract listings.
</p>
{indexedLoading ? (
<p className="text-sm text-gray-600 dark:text-gray-400">Loading indexed token feed</p>
) : sortedIndexedTokens.length === 0 ? (
<p className="text-sm text-gray-600 dark:text-gray-400">Indexed token feed is temporarily unavailable.</p>
) : (
<div className="grid gap-2">
{sortedIndexedTokens.map((token) => {
const canonical = isCanonicalTokenAddress(token.address, canonicalAddressSet)
return (
<Link
key={token.address}
href={`/tokens/${token.address}`}
className="flex flex-col gap-2 rounded-lg border border-gray-200 px-3 py-3 transition hover:border-primary-400 hover:bg-gray-50 dark:border-gray-700 dark:hover:bg-gray-950/50 sm:flex-row sm:items-center sm:justify-between"
>
<div className="min-w-0">
<div className="flex flex-wrap items-center gap-2">
<span className="text-sm font-semibold text-gray-900 dark:text-white">
{token.symbol || token.name || 'Token'}
</span>
{canonical ? (
<EntityBadge label="canonical" tone="success" className="px-2 py-0.5 text-[11px]" />
) : (
<EntityBadge label="non-canonical indexed" tone="warning" className="px-2 py-0.5 text-[11px]" />
)}
</div>
<p className="mt-1 truncate text-xs text-gray-600 dark:text-gray-400">{token.name || token.address}</p>
</div>
<div className="text-xs text-gray-500 dark:text-gray-400">
{token.holders != null ? `${token.holders.toLocaleString()} holders` : 'Holders unavailable'}
</div>
</Link>
)
})}
</div>
)}
</Card>
</div>
<div className="mt-5">
<Card title="Common token searches">
<div className="grid gap-2 sm:grid-cols-2 lg:grid-cols-3">

View File

@@ -14,6 +14,7 @@ import {
import { formatTimestamp, formatTokenAmount, formatWeiAsEth } from '@/utils/format'
import { DetailRow } from '@/components/common/DetailRow'
import EntityBadge from '@/components/common/EntityBadge'
import PostureBadge from '@/components/common/PostureBadge'
import PageIntro from '@/components/common/PageIntro'
import PaginationControls from '@/components/common/PaginationControls'
import SectionTabs, { type SectionTab } from '@/components/common/SectionTabs'
@@ -228,8 +229,8 @@ export default function TransactionDetailPage() {
<div className="flex flex-wrap items-center gap-2">
<div className="font-medium text-gray-900 dark:text-white">{transfer.token_symbol || transfer.token_name || 'Token'}</div>
{gruPosture?.isGru ? <EntityBadge label="GRU" tone="success" /> : null}
{gruPosture?.isX402Ready ? <EntityBadge label="x402 ready" tone="info" /> : null}
{gruPosture?.isWrappedTransport ? <EntityBadge label="wrapped" tone="warning" /> : null}
{gruPosture?.isX402Ready ? <PostureBadge label="x402 ready" tone="info" /> : null}
{gruPosture?.isWrappedTransport ? <PostureBadge label="wrapped" tone="warning" /> : null}
</div>
{transfer.token_address && (
<Link href={`/tokens/${transfer.token_address}`} className="text-primary-600 hover:underline">

View File

@@ -7,8 +7,20 @@ import {
mergeTokenListLookups,
type TokenListSurface,
} from './tokenListSurfaces'
import { applyTokenDisplayOverrides } from '@/utils/canonicalTokens'
import type { AddressTokenTransfer } from './addresses'
export interface IndexedTokenListItem {
address: string
name?: string
symbol?: string
decimals: number
type?: string
holders?: number
exchange_rate?: string | number | null
is_canonical?: boolean
}
export interface TokenProfile {
address: string
name?: string
@@ -76,6 +88,32 @@ function normalizeTokenProfile(raw: {
}
}
function normalizeIndexedToken(raw: {
address?: string | null
address_hash?: string | null
name?: string | null
symbol?: string | null
decimals?: string | number | null
type?: string | null
holders?: string | number | null
exchange_rate?: string | number | null
}): IndexedTokenListItem | null {
const address = raw.address || raw.address_hash || ''
if (!address) {
return null
}
return applyTokenDisplayOverrides({
address,
name: raw.name || undefined,
symbol: raw.symbol || undefined,
decimals: Number(raw.decimals || 0),
type: raw.type || undefined,
holders: raw.holders != null ? Number(raw.holders) : undefined,
exchange_rate: raw.exchange_rate ?? null,
})
}
function computeMarketCap(totalSupply: string | undefined, decimals: number, priceUsd: number | undefined): number | null {
if (!totalSupply || priceUsd == null || !Number.isFinite(priceUsd)) {
return null
@@ -187,8 +225,9 @@ export const tokensApi = {
const aggregationToken =
aggregationResult.status === 'fulfilled' && aggregationResult.value.ok ? aggregationResult.value.data : null
const merged = mergeTokenProfileWithAggregation(blockscoutToken, aggregationToken)
const displayReady = merged ? applyTokenDisplayOverrides(merged) : null
return { ok: merged != null, data: merged }
return { ok: displayReady != null, data: displayReady }
} catch {
return { ok: false, data: null }
}
@@ -318,4 +357,36 @@ export const tokensApi = {
return { ok: false, data: [] }
}
},
listIndexedSafe: async (
page = 1,
pageSize = 50,
): Promise<{ ok: boolean; data: IndexedTokenListItem[] }> => {
try {
const params = new URLSearchParams({
page: page.toString(),
items_count: pageSize.toString(),
})
const raw = await fetchBlockscoutJson<{ items?: Array<{
address?: string | null
address_hash?: string | null
name?: string | null
symbol?: string | null
decimals?: string | number | null
type?: string | null
holders?: string | number | null
exchange_rate?: string | number | null
}> }>(`/api/v2/tokens?${params.toString()}`)
const data = Array.isArray(raw.items)
? raw.items
.map((item) => normalizeIndexedToken(item))
.filter((item): item is IndexedTokenListItem => item != null)
: []
return { ok: true, data }
} catch {
return { ok: false, data: [] }
}
},
}

View File

@@ -0,0 +1,47 @@
import { describe, expect, it } from 'vitest'
import {
applyTokenDisplayOverrides,
buildCanonicalAddressSet,
isCanonicalTokenAddress,
sortIndexedTokensCanonicalFirst,
WETH9_CANONICAL_ADDRESS,
} from './canonicalTokens'
describe('canonicalTokens', () => {
it('builds a lowercase canonical address set', () => {
const set = buildCanonicalAddressSet([
{ chainId: 138, address: '0x93E66202A11B1772E55407B32B44e5Cd8eda7f22', symbol: 'cUSDT' },
])
expect(isCanonicalTokenAddress('0x93e66202a11b1772e55407b32b44e5cd8eda7f22', set)).toBe(true)
expect(isCanonicalTokenAddress('0x0000000000000000000000000000000000000001', set)).toBe(false)
})
it('sorts canonical tokens before non-canonical entries', () => {
const canonicalSet = buildCanonicalAddressSet([
{ chainId: 138, address: WETH9_CANONICAL_ADDRESS, symbol: 'WETH9' },
])
const sorted = sortIndexedTokensCanonicalFirst(
[
{ address: '0x0000000000000000000000000000000000000001', symbol: 'AAA' },
{ address: WETH9_CANONICAL_ADDRESS, symbol: 'WETH9' },
],
canonicalSet,
)
expect(sorted[0]?.address).toBe(WETH9_CANONICAL_ADDRESS)
})
it('applies WETH9 metadata override', () => {
const token = applyTokenDisplayOverrides({
address: WETH9_CANONICAL_ADDRESS,
name: null as unknown as string,
symbol: 'WETH',
decimals: 18,
})
expect(token.symbol).toBe('WETH9')
expect(token.name).toBe('Wrapped Ether (WETH9)')
})
})

View File

@@ -0,0 +1,58 @@
import type { TokenListToken } from '@/services/api/config'
export const WETH9_CANONICAL_ADDRESS = '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2'
export const TOKEN_DISPLAY_OVERRIDES: Record<string, { name?: string; symbol?: string; decimals?: number }> = {
[WETH9_CANONICAL_ADDRESS.toLowerCase()]: {
name: 'Wrapped Ether (WETH9)',
symbol: 'WETH9',
decimals: 18,
},
}
export function buildCanonicalAddressSet(tokens: TokenListToken[]): Set<string> {
const addresses = new Set<string>()
for (const token of tokens) {
if (typeof token.address === 'string' && token.address.trim().length > 0) {
addresses.add(token.address.toLowerCase())
}
}
return addresses
}
export function isCanonicalTokenAddress(address: string, canonicalSet: Set<string>): boolean {
return canonicalSet.has(address.toLowerCase())
}
export function applyTokenDisplayOverrides<T extends { address: string; name?: string; symbol?: string; decimals?: number }>(
token: T,
): T {
const override = TOKEN_DISPLAY_OVERRIDES[token.address.toLowerCase()]
if (!override) {
return token
}
return {
...token,
name: override.name || token.name,
symbol: override.symbol || token.symbol,
decimals: override.decimals ?? token.decimals,
}
}
export function sortIndexedTokensCanonicalFirst<T extends { address: string; symbol?: string; name?: string }>(
tokens: T[],
canonicalSet: Set<string>,
): T[] {
return [...tokens].sort((left, right) => {
const leftCanonical = isCanonicalTokenAddress(left.address, canonicalSet)
const rightCanonical = isCanonicalTokenAddress(right.address, canonicalSet)
if (leftCanonical !== rightCanonical) {
return leftCanonical ? -1 : 1
}
const leftLabel = left.symbol || left.name || left.address
const rightLabel = right.symbol || right.name || right.address
return leftLabel.localeCompare(rightLabel, undefined, { sensitivity: 'base' })
})
}

View File

@@ -8,6 +8,7 @@ test.describe('Explorer sprint smoke', () => {
await page.goto(`${EXPLORER_URL}/`, { waitUntil: 'domcontentloaded', timeout: 20000 })
await expect(page.getByText(/Network overview/i)).toBeVisible({ timeout: 10000 })
await expect(page.getByRole('heading', { name: /Recent Transactions/i })).toBeVisible({ timeout: 10000 })
await expect(page.getByText(/Freshness Interpretation/i).first()).toBeVisible({ timeout: 10000 })
})
test('wallet page loads', async ({ page }) => {
@@ -24,6 +25,7 @@ test.describe('Explorer sprint smoke', () => {
await page.goto(`${EXPLORER_URL}/tokens`, { waitUntil: 'domcontentloaded', timeout: 20000 })
await expect(page.getByRole('heading', { name: /^Tokens$/i })).toBeVisible({ timeout: 10000 })
await expect(page.getByText(/Canonical Chain 138 trading set/i).first()).toBeVisible({ timeout: 10000 })
await expect(page.getByText(/Indexed tokens \(Blockscout\)/i).first()).toBeVisible({ timeout: 10000 })
})
test('canonical cUSDT token detail loads', async ({ page }) => {
@@ -42,6 +44,13 @@ test.describe('Explorer sprint smoke', () => {
await expect(page.getByRole('heading', { name: /Bridge & Relay Monitoring/i })).toBeVisible({ timeout: 15000 })
await expect(page.getByText(/CCIP route catalog/i).first()).toBeVisible({ timeout: 15000 })
await expect(page.getByText(/Wemix/i).first()).toBeVisible({ timeout: 15000 })
await expect(page.getByText(/Bridge Freshness Context/i).first()).toBeVisible({ timeout: 10000 })
})
test('posture glossary doc page loads', async ({ page }) => {
await page.goto(`${EXPLORER_URL}/docs/posture-glossary`, { waitUntil: 'domcontentloaded', timeout: 30000 })
await expect(page.getByRole('heading', { name: /Posture glossary/i })).toBeVisible({ timeout: 15000 })
await expect(page.getByText(/x402 readiness/i).first()).toBeVisible({ timeout: 10000 })
})
test('public API access doc page loads', async ({ page }) => {
@@ -87,6 +96,7 @@ test.describe('Explorer sprint smoke', () => {
await page.goto(`${EXPLORER_URL}/analytics`, { waitUntil: 'domcontentloaded', timeout: 30000 })
await expect(page.getByRole('heading', { name: /Analytics & Network Activity/i })).toBeVisible({ timeout: 15000 })
await expect(page.getByText(/Track 3 public surface/i).first()).toBeVisible({ timeout: 10000 })
await expect(page.getByText(/Analytics Freshness Context/i).first()).toBeVisible({ timeout: 10000 })
})
test('operator page shows track 4 surface note', async ({ page }) => {