Unibase Explorer

Unibase 网络的区块链浏览器——8 类链上资源(agent / memory / file / piece / replica / edge node / owner / proof)的列表、详情、跨类型搜索,前端隐藏 4 个上游服务的同源代理层。
activeNext.jsReactTypeScriptTanStack QueryEChartsRadix UIethersdaisyUITailwind
GitHub →Demo →

一、业务背景:在浏览什么

Unibase Explorer 是 Unibase 网络的"以太坊 Etherscan"—— 但被浏览的不是普通的交易和合约,而是这套网络独有的8 类资源

路由资源
/agentsAI Agent(链上身份)
/memoryMemory Bucket(Agent 的记忆容器)
/storage/file文件
/storage/piece文件分片
/storage/replica副本
/storage/edge边缘节点
/owner/[address]某地址名下的全部资源
/search?q=跨类型聚合搜索

首页是"总览大屏"——核心统计卡片 + 一张 ECharts 的 daily-stats 时间序列, 让用户一眼看出"网络当前规模 + 最近一周活跃度"。

业务本质:一套对外可信的、可追溯的、可分享的链上数据视图。 浏览器本身不写入数据,所有内容都是把后端的真实状态以人类可读的方式呈现出来。

二、数据流架构:4 个上游 + 1 个同源代理

链上浏览器最特殊的一件事是数据源是多个——身份、存储、链扫描、边缘统计分别由不同后端服务承担。 直接让浏览器调 4 个 host 意味着 4 套 CORS、4 套 host 配置、4 套环境切换地狱。

方案:在 next.config.js 用 rewrites 收口到 4 个同源前缀。

浏览器只看到这 4 个前缀:
  /api_v2/_scan/*   →  unibasechain-scan ELB(链扫描)
  /api_v2/*         →  testnet.gateway.membase.io(Membase 网关)
  /api_hub/*        →  identity stats hub(身份统计)
  /api_gateway/*    →  edge stats gateway(边缘统计)

收益是真实的:

  • 浏览器代码全程同源,不存在 CORS
  • 上游 host 集中在一处,环境切换(mainnet / testnet / staging)只动 env
  • 任一上游迁移 host 也不需要改前端代码

STAGING=1 这个 env switch 就是配合 rewrites 的——一行环境变量切换全部上游目标。

三、api/ 目录:一个文件一个资源

整个网络层的组织非常克制:

api/
├── http.ts        # fetch 包装:Resp<T> 类型 / 错误映射 / 默认值兜底
├── client.ts      # TanStack Query 的 HTTP client
├── list.ts        # 分页列表的通用 hook
├── search.ts      # 跨资源聚合搜索
├── home.ts        edge.ts files.ts piece.ts replica.ts memeory.ts
├── stats.ts       space.ts model.ts proof.ts service.ts user.ts gpu.ts
└── types/         # ambient global types(脚本式 .ts,无 export)

设计原则:

  • 一个文件 = 一类资源agents.ts 只关心 agent 的列表/详情/owner-by-address; 跨资源的事(搜索)单独有 search.ts
  • http.ts 是唯一通道。所有请求都过它——错误处理、空值兜底、类型约束集中在一处
  • types/ 用 ambient 而不是 export。这是个有意识的选择:链上数据类型用得太频繁, 全局可见省去到处 import { Agent } from '@/api/types',配合 ESLint 关掉对应 no-unused-vars

四、TanStack Query + SWR:迁移现实下的共存

package.json 同时存在 @tanstack/react-queryswr,看起来是冗余——但其实是迁移中的现实。 项目早期用 SWR,后来在 TanStack Query 5 上做了一次升级评估:

维度SWRTanStack Query 5
缓存粒度单 key,扁平queryKey 数组,天然多维
列表 + 分页自己做placeholderData: keepPreviousData 原生
失效控制mutateinvalidateQueries 模式更强
DevTools简陋独立面板,链上数据调试很有用

最终结论:新写的资源(agents / search / 大表)全部走 TanStack Query, 遗留的 SWR 路径不主动改写——这是务实的迁移策略,不是技术债的借口。 共存期成本是多了一个包,收益是任何时候都能渐进推进,不需要一个"重写 PR"。

五、几个领域细节

1. 地址、哈希、大数

ethers 6 + bignumber.js 是链上场景的常客:

  • 地址校验ethers.isAddress(input) 做搜索入口的判别——长得像地址的走地址聚合页,否则走全局搜索
  • 大数显示:存储字节数、累计 gas 这种动辄 20 位的数字必须用 BigNumber, 原生 number 在 2^53 之后就开始丢精度——展示一份"错的状态"对一个浏览器来说是 unrecoverable 的信任问题

2. ECharts 用于首页 daily-stats

首页那张时间序列图选 ECharts 而不是 recharts / visx,因为:

  • 链上数据经常单点跳动大(某天 +1 万个文件),ECharts 的 dataZoom 内建支持
  • 鼠标 hover 多 series 联动 tooltip 是 ECharts 的强项
  • bundle 体积虽大,但只有首页用,路由级 code-split 后影响有限

3. 全局搜索:跨资源类型聚合

search.ts 不是简单的"调一个后端搜索接口"——它要在前端把多种资源类型的搜索结果归并:

用户输入 "0x..."  → 同时查 agent owner / file uploader / replica holder
用户输入 hash     → 同时查 file CID / piece CID / replica ID
用户输入 自由文本 → 走后端的全文索引

前端的责任是判别 + 并发 + 合并,让用户在一个搜索框里得到统一的结果列表。

4. proxy.ts 在根目录

不是 Next.js 标准约定的 middleware 位置,但用作"路由 gating / rewrite glue"—— 和 next.config.js 的 rewrites 配合处理一些动态情形。 (保留这个细节是因为容易和官方 middleware.ts 混淆,看代码时要注意)

六、UI 层:daisyUI + Radix + Floating UI 的组合

不是单一组件库吃天下,而是按场景选:

选型用在哪
daisyUI 4基础组件(按钮、徽章、卡片)——配 Tailwind 用得快
Radix UI primitivesDropdownMenu / HoverCard / Slot——需要无障碍 + 键盘交互的场景
Floating UI自定义浮层定位(地址 hover 卡、长字段截断 tooltip)
lucide-react图标
ECharts图表

这种"组件碎片化"在中型项目里反而是优势——daisyUI 解决 80% 的快开发,剩下 20% 用 Radix / Floating UI 保证无障碍和精度。没有一个库被强行拿来做它不擅长的事。

七、目录与代码分层

app/
├── (home)/            # 首页 stat cards + DailyStatsChart
├── (explorer)/        # 探索器主区,共享 layout
│   ├── agents/        # Agent 列表 + 详情
│   ├── memory/        # Memory bucket 列表 + 详情
│   ├── owner/[address]/   # 地址聚合页
│   └── storage/
│       ├── edge/  file/  piece/  replica/
├── search/            # 全局搜索结果
└── layout.tsx         # 根布局:QueryProvider + Toaster + Footer

components/
├── business/          # Hash / Footer / Nav / GlobalSearch
├── home/              # DailyStatsChart
├── ui/                # 低层原语(Table / Button / HoverCard)
├── Pagination/        # 列表 + 分页外壳
└── emptyState/ search/ text/

lib/
├── hooks/             # useClientFetchData 等
├── utils/             # utils.ts / dayjs.ts / ethers.ts / bignumber.ts
└── constants/ types/

config/                # 环境派生的 URL builder(链浏览器外链等)
context/               # QueryProvider
icons/ public/icons/   # SVG 图标

路径别名:@ui / @comps / @hooks / @utils——和姊妹项目 unibase 官网 保持一致。

八、时间线

  • 2023-11 项目初始化,与 unibase 官网 几乎同期启动
  • 2024 - 2025 资源类型从最初的"agent + file"逐步扩展到目前的 8 类,每加一类就是一组 list + detail + owner-by 视图
  • Next.js 12 → 13 → 14 → 16 / Turbopack 多次升级
  • 数据层 从 SWR 切到 TanStack Query 5(共存策略)
  • 至今(2026) 423 个 commit、54+ PR、持续随网络规模迭代

总结:链上浏览器的"克制"

营销站 unibase-website 的工程化诱惑是动效, 浏览器项目的工程化诱惑是抽象—— 8 类资源每类都有 list + detail + by-owner,看起来就该有一个"通用资源 CRUD 框架"。

但实际做下来选择了克制的复用

  • http.tsPaginationHashuseClientFetchData 这些确定会被复用的抽象
  • 每类资源的列表页和详情页仍然各自写——因为每类资源的字段语义、关联跳转、空状态文案都不一样
  • 三个看起来很像的列表,用类型约束保证字段不会错位,不用一套引擎强行收口

对一个长期演化的浏览器项目来说,保留每类资源的独立性每行代码都要复用 重要得多—— 后者会让"加一类资源"变成动框架,而不是写一个页面。