feat: explorer API, wallet, CCIP scripts, and config refresh

- Backend REST/gateway/track routes, analytics, Blockscout proxy paths.
- Frontend wallet and liquidity surfaces; MetaMask token list alignment.
- Deployment docs, verification scripts, address inventory updates.

Check: go build ./... under backend/ (pass).
Made-with: Cursor
This commit is contained in:
defiQUG
2026-04-07 23:22:12 -07:00
parent d931be8e19
commit 6eef6b07f6
224 changed files with 19671 additions and 3291 deletions

View File

@@ -0,0 +1,177 @@
import { useEffect, useMemo, useState } from 'react'
import { Card } from '@/libs/frontend-ui-primitives'
import { explorerFeaturePages } from '@/data/explorerOperations'
import { blocksApi, type Block } from '@/services/api/blocks'
import {
missionControlApi,
type MissionControlBridgeStatusResponse,
type MissionControlChainStatus,
} from '@/services/api/missionControl'
import { statsApi, type ExplorerStats } from '@/services/api/stats'
import { transactionsApi, type Transaction } from '@/services/api/transactions'
import OperationsPageShell, {
MetricCard,
StatusBadge,
formatNumber,
relativeAge,
truncateMiddle,
} from './OperationsPageShell'
function getChainStatus(bridgeStatus: MissionControlBridgeStatusResponse | null): MissionControlChainStatus | null {
const chains = bridgeStatus?.data?.chains
if (!chains) return null
const [firstChain] = Object.values(chains)
return firstChain || null
}
export default function AnalyticsOperationsPage() {
const [stats, setStats] = useState<ExplorerStats | null>(null)
const [blocks, setBlocks] = useState<Block[]>([])
const [transactions, setTransactions] = useState<Transaction[]>([])
const [bridgeStatus, setBridgeStatus] = useState<MissionControlBridgeStatusResponse | null>(null)
const [loadingError, setLoadingError] = useState<string | null>(null)
const page = explorerFeaturePages.analytics
useEffect(() => {
let cancelled = false
const load = async () => {
const [statsResult, blocksResult, transactionsResult, bridgeResult] = await Promise.allSettled([
statsApi.get(),
blocksApi.list({ chain_id: 138, page: 1, page_size: 5 }),
transactionsApi.list(138, 1, 5),
missionControlApi.getBridgeStatus(),
])
if (cancelled) return
if (statsResult.status === 'fulfilled') setStats(statsResult.value)
if (blocksResult.status === 'fulfilled') setBlocks(blocksResult.value.data)
if (transactionsResult.status === 'fulfilled') setTransactions(transactionsResult.value.data)
if (bridgeResult.status === 'fulfilled') setBridgeStatus(bridgeResult.value)
const failedCount = [statsResult, blocksResult, transactionsResult, bridgeResult].filter(
(result) => result.status === 'rejected'
).length
if (failedCount === 4) {
setLoadingError('Analytics data is temporarily unavailable from the public explorer APIs.')
}
}
load().catch((error) => {
if (!cancelled) {
setLoadingError(error instanceof Error ? error.message : 'Analytics data is temporarily unavailable from the public explorer APIs.')
}
})
return () => {
cancelled = true
}
}, [])
const chainStatus = useMemo(() => getChainStatus(bridgeStatus), [bridgeStatus])
return (
<OperationsPageShell page={page}>
{loadingError ? (
<Card className="mb-6 border border-red-200 bg-red-50/70 dark:border-red-900/50 dark:bg-red-950/20">
<p className="text-sm leading-6 text-red-900 dark:text-red-100">{loadingError}</p>
</Card>
) : null}
<div className="mb-6 grid gap-4 md:grid-cols-2 xl:grid-cols-4">
<MetricCard
title="Total Blocks"
value={formatNumber(stats?.total_blocks)}
description="Current block count from the public Blockscout stats endpoint."
className="border border-sky-200 bg-sky-50/70 dark:border-sky-900/50 dark:bg-sky-950/20"
/>
<MetricCard
title="Transactions"
value={formatNumber(stats?.total_transactions)}
description="Total transactions currently indexed by the public explorer."
className="border border-emerald-200 bg-emerald-50/70 dark:border-emerald-900/50 dark:bg-emerald-950/20"
/>
<MetricCard
title="Addresses"
value={formatNumber(stats?.total_addresses)}
description="Known addresses from the public stats surface."
/>
<MetricCard
title="Chain Head"
value={chainStatus?.head_age_sec != null ? `${Math.round(chainStatus.head_age_sec)}s` : 'Unknown'}
description={
chainStatus?.latency_ms != null
? `RPC latency ${Math.round(chainStatus.latency_ms)}ms on Chain 138.`
: 'Latest public RPC head age from mission control.'
}
/>
</div>
<div className="mb-8 grid gap-6 lg:grid-cols-[1fr_1fr]">
<Card title="Recent Blocks">
<div className="space-y-4">
{blocks.map((block) => (
<div
key={block.hash}
className="rounded-2xl border border-gray-200 bg-gray-50 p-4 dark:border-gray-700 dark:bg-gray-900/40"
>
<div className="flex flex-col gap-3 sm:flex-row sm:items-start sm:justify-between">
<div>
<div className="text-base font-semibold text-gray-900 dark:text-white">
Block {formatNumber(block.number)}
</div>
<div className="mt-1 text-xs text-gray-500 dark:text-gray-400">
{truncateMiddle(block.hash)} · miner {truncateMiddle(block.miner)}
</div>
</div>
<div className="text-sm text-gray-600 dark:text-gray-400">
{formatNumber(block.transaction_count)} tx · {relativeAge(block.timestamp)}
</div>
</div>
</div>
))}
{blocks.length === 0 ? (
<p className="text-sm text-gray-600 dark:text-gray-400">No recent block data available.</p>
) : null}
</div>
</Card>
<Card title="Recent Transactions">
<div className="space-y-4">
{transactions.map((transaction) => (
<div
key={transaction.hash}
className="rounded-2xl border border-gray-200 bg-gray-50 p-4 dark:border-gray-700 dark:bg-gray-900/40"
>
<div className="flex flex-col gap-3 sm:flex-row sm:items-start sm:justify-between">
<div>
<div className="text-base font-semibold text-gray-900 dark:text-white">
{truncateMiddle(transaction.hash, 12, 10)}
</div>
<div className="mt-1 text-xs text-gray-500 dark:text-gray-400">
Block {formatNumber(transaction.block_number)} · from {truncateMiddle(transaction.from_address)}
</div>
</div>
<div className="flex items-center gap-3">
<StatusBadge
status={transaction.status === 1 ? 'success' : 'failed'}
tone={transaction.status === 1 ? 'normal' : 'danger'}
/>
<div className="text-sm text-gray-600 dark:text-gray-400">
{relativeAge(transaction.created_at)}
</div>
</div>
</div>
</div>
))}
{transactions.length === 0 ? (
<p className="text-sm text-gray-600 dark:text-gray-400">No recent transaction data available.</p>
) : null}
</div>
</Card>
</div>
</OperationsPageShell>
)
}