关于 nuthatch
nuthatch 是供开发者以 SQL 查询 EVM 合约历史的本地索引器。它根据 ABI 生成数据表,以单个 Rust 二进制文件运行,无需子图或独立数据库。
主要功能
- 01根据合约 ABI 生成事件表
- 02通过 JSON-RPC 索引 EVM 历史
- 03用 SQL 查询实时及历史数据
- 04以单个 Rust 二进制文件本地运行
- 05通过 HTTP 提供 SQL 查询
适用场景
- 01用 SQL 分析代币转账
- 02构建本地合约历史数据集
- 03让 Agent 检查已索引的合约数据
MCP 相关
nuthatch 内置 MCP 服务,Claude 和其他 MCP 客户端可借此查询已索引的合约数据。
配置
本地服务的配置在各客户端中通用,复制后粘贴到客户端的 MCP 配置文件即可。
docker run -i --rm ghcr.io/nightswatchhq/nuthatch:4.0.0本地服务需要在你的电脑上安装运行环境(如 Node.js 18+)。各客户端的配置文件位置不同,请参考客户端文档。
ghcr.io/nightswatchhq/nuthatch:4.0.0stdio工具
本地服务需要在你的电脑上运行后才能读取工具,维护者暂未提供工具列表。
详细介绍
nuthatch
将 EVM 合约的历史记录转为本地 SQL 数据库。 一个 Rust 二进制文件,无需 Postgres,无需编写子图,并内置 MCP 服务。
curl -fsSL https://nuthatch-indexer.com/install.sh | sh
export PATH="$HOME/.local/bin:$PATH"
nuthatch init 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2 --alias weth
nuthatch dev --backfill 300
然后,在第二个终端中:
nuthatch sql "SELECT count(*) FROM weth__transfer"
安装程序支持 macOS Apple Silicon、Linux x86_64 和 aarch64;export 会添加其目录,而全新的 macOS shell 中没有这个目录。init 会为 WETH 建立索引并检测其链。代码块中没有 # 注释,因为 macOS 默认使用的 zsh 不会将粘贴的 # 视为注释:它会把该行剩余部分作为参数传递。
从 4.1.0 起,Linux 二进制文件需要 glibc 2.35 或更高版本才能运行;它也是构建所用的版本。4.0.x 可在 2.34 上运行。我们不会发布 Intel Mac 二进制文件:在该平台及其他平台上,请使用 Rust 1.95.0 从源代码构建(docs/install.md)。
init 会创建一个 nest:一个目录,包含合约的 ABI、配置,以及在运行 dev 后的索引数据。--backfill 300 会从链顶端往回处理 300 个区块,大约相当于主网一小时的数据,因此使用捆绑的公共端点,几秒内就会有数据可供查询。如果不指定该参数,dev 会从合约的部署区块开始回填:对于 WETH,这意味着 2,100 万个区块,使用免费的公共端点需要很长时间;使用自己的 RPC 则可胜任这项工作(--rpc)。
这些命令行在 2026-10-05 发布的 4.10.1 版本中按原样运行,从执行 curl 到得到非零计数,在 M5 Pro 上的全新 macOS shell 中耗时 13 秒,在全新的 ubuntu:24.04 容器中耗时 21 秒(docs/readme-check-2026-10-05.md,其中记录了本文中的每条命令)。
| 需要子图 | 需要处理程序代码 | 数据来源 | 需要运行什么 | 使用什么查询 | |
|---|---|---|---|---|---|
| The Graph | 是 | 是(AssemblyScript) | 网络上的索引器 | 无,或 graph-node + Postgres + IPFS | GraphQL |
| Ponder | 否 | 是(TypeScript) | 你的 RPC 端点 | Node.js,生产环境还需 Postgres | SQL、GraphQL |
| nuthatch | 否 | 否:表由 ABI 生成 | 你的 RPC 端点 | 一个二进制文件 | SQL、HTTP、MCP |
与 Ponder 一样,nuthatch 通过 JSON-RPC 读取链,因此需要端点,而提供商可能会对此收费。它捆绑的公共端点仅供试用,不适合长期运行。它是 The Graph 的补充,而非替代品:子图通过索引器网络为应用提供服务,而 nest 则将合约历史记录存入你自己机器上的数据库。
为什么要做这个工具。 要访问合约的历史记录,通常需要编写子图或处理程序代码、运行数据库,或者租用他人保存的数据副本。nuthatch 根据 ABI 生成表,作为单个进程运行,无需安装其他组件,并将数据保存在你的机器上:每条链最多使用 2 GB RAM;除非选择加入,否则不会进行遥测;无需账号。内置的 MCP 服务可让 Claude 或任何 MCP 客户端查询数据。
你将获得
- 无需编写代码。
init 0xAddr会解析 ABI(先查询 Sourcify,再查询 Etherscan)、生成架构和解码器,并搭建项目框架。你无需编写任何内容。 - 无需基础设施。 一个 Rust 二进制文件。嵌入式模式无需 Postgres、Docker 或 IPFS。
- 就是 SQL。 你的合约事件会变为按事件划分的表,你可以用真正的分析型 SQL 查询它们——实时链顶数据和封存历史数据都可通过同一接口访问。
- 数据归你,体量小。 单链跟随链顶时 RAM 使用量 ≤2 GB,并由 CI 强制检查。默认不进行遥测,永远不强制要求 API 令牌:唯一的可选项是在
init时进行人数统计(nuthatch count),并且统计总数公开可查。大多数人会拒绝,或者根本不会被询问,因此这些数字只是下限。
谁在运行
- Lodestar 是 The Graph Protocol 在 Arbitrum One 上的分析仪表板。在 2026-10-13 撤下部署之前,其面板数据由自托管的巢提供,而不是来自 The Graph 网关(记录)。
- GraphOps 是 The Graph 的索引器和核心开发者,也是设计合作伙伴。其反馈促成了受维护的实体(RFC-0041)。
一个较早的案例,现已结束:Arcaidia 是在 ETHOnline 2026 构建的 Circle CCTP 加速层。它从 Ethereum Sepolia 和 Arc Testnet 上的两个巢读取索引状态:其求解器在那里发现意图,其结算 Agent 在那里跟踪 CCTP,其 Web 控制台则基于这些数据进行渲染。巢已于 2026-09-29 停止运行。
更多案例请见 nuthatch-indexer.com/stories。
我们没有托管平台。我们会使用标准二进制文件为你运行一个巢,不收费,也不需要账号:请在 Discord 上联系。
安装
curl -fsSL https://nuthatch-indexer.com/install.sh | sh
这会从最新版本下载适用于你平台的预编译二进制文件,验证其 SHA-256,并将其安装到 ~/.local/bin(可通过 NUTHATCH_INSTALL_DIR 覆盖)。无需编译器。 macOS 的默认 Shell 不会将 ~/.local/bin 加入 PATH,因此上面的快速入门才会导出该路径;将同一行添加到 ~/.zshrc(或 ~/.bashrc),即可在新终端中继续使用。每个版本都会附带预编译二进制文件及其校验和,支持 macOS Apple Silicon 和 Linux x86_64 与 aarch64(aarch64 从 4.11.0 之后的版本开始支持)。如果你更愿意手动获取,也可以下载这些文件。不会发布 Intel Mac 二进制文件;安装程序会说明这一点,并指向下面的源码构建方式。
Linux 二进制文件采用动态链接,且有一项依赖要求,该要求是通过 objdump -T 对已发布的文件进行测量得出的,而非推测:
- glibc 2.35 或更高版本——这是测得的 ABI 最低版本,也是该版本构建所用的版本。直到 4.0.2,二进制文件引用的符号都不高于
GLIBC_2.34,因此运行所需版本是 2.34,编译所用版本则是 2.35(#978);4.1.0 会在GLIBC_2.35引用hypot,这是因为 libm 为其重新指定了版本,因此这两个版本号现在一致。
它链接 libc、libm 和 libgcc,不依赖 C++ 运行时。4.1 之前的版本内嵌 DuckDB,还需要 GCC 11 提供的 libstdc++。
Debian 12 和 Ubuntu 22.04 满足要求。RHEL 9 和 Amazon Linux 2023 提供 glibc 2.34,可运行 4.0.x;从 4.1.0 开始,这些系统需要通过源码构建。
验证构建者身份。 每个版本的二进制文件都附带构建来源证明,这是校验和无法提供的:
gh attestation verify nuthatch-x86_64-unknown-linux-gnu.tar.gz --repo nuthatch-org/nuthatch
从源码构建是未提供预编译二进制文件的平台(包括 Intel Mac)唯一可用的方式(Intel 版本尚未在 Intel 硬件上验证):
rustup toolchain install 1.95.0
cargo +1.95.0 install --git https://github.com/nuthatch-org/nuthatch nuthatch
必须使用 +1.95.0:cargo install --git 会忽略仓库的工具链固定版本,而更新的默认工具链会导致依赖项编译失败。
容器镜像按版本发布到 ghcr.io/nuthatch-org/nuthatch——嵌入式版本使用 :<version>,可扩展版本使用 :<version>-scaled。镜像附带的二进制文件与版本发布中附带的完全相同,因此两者不会出现版本偏差。
docs/install.md 详细介绍了以上各项:如何测量 ABI 最低版本、构建来源证明能证明什么,以及 --repo 的用途;还说明了为何要固定工具链版本。
链。 Ethereum、Arbitrum One、Base、BSC、Polygon、Gnosis、Optimism、Monad 和 Robinhood Chain 均为内置链,配有经过测量的公共端点和经过调优的最终性设置——省略 --chain,nuthatch 就会逐一探测这些链,检查你的合约字节码,并选择合约所在的链。 使用 --rpc 指定你自己的节点。
其他任何 EVM 链也都可用——World Chain、Base Sepolia 或你自己的开发网。指定链名称,并说明它所在的位置:
nuthatch init 0xADDR --chain world-chain --rpc https://your-endpoint.example
链 ID 会直接从端点读取,无需查找,也不用担心输错。对于九个内置名称之一,--rpc 不会用于获取链 ID,但会作为节点池使用:你提供的端点会完全取代随附的公共端点。对于未注册的名称,如果省略 --rpc,程序会拒绝执行并告知你如何处理。
请参阅运行未列出的 EVM 链,了解最终性方面的注意事项;这部分尤其值得阅读:如果某条链的 finalized 标签距离链头很近,就需要改用基于区块深度的策略,否则就会封存无法修正的不可变 Parquet 数据。
自带 RPC 端点
对于任何打算持续运行的任务,nuthatch 都假定你使用付费 RPC 端点或自己的节点。 这是最佳路径。
有一点值得了解,我们在这里把话说清楚:docs/benchmarks.md 中目前的大多数数据都是针对公共端点测得的。这是这些数据的已知局限,而不是建议——通过限流端点进行的基准测试,测量的其实是端点。在一项工作负载中,网络耗时占回填总耗时的 99.3%(同一段数据在 2026-08-23 通过公共端点实时处理,并从磁盘重放,见 docs/benchmarks.md),这正是重放工具链(RFC-0039)存在的原因,也是这些数据在页面上附带该说明的原因。
每条链附带的免费公共端点只有一个用途:让 init → dev 能够零配置运行。请将它们用于测试和初步验证。这里先说明它们为何不适合实际工作,免得你凌晨 3 点才发现:
- 它们有速率限制且由多人共享,吞吐量每小时都可能变化。
- 它们会间歇性故障,而且不总会明显报错。 nuthatch 会在端点池中自动切换并重试,但如果所有端点都拒绝某个时间窗口的请求,任务就会停滞,直到有端点恢复;发生这种情况时,
/ready会报告stalled。 - 深度回填会变得极慢或停止,而且许多免费端点会裁剪旧区块,因此从 2020 年的部署区块开始回填可能会中途失败。
在信任某个端点来执行回填前,请先检查它。 nuthatch doctor 会探测端点,并报告它实际支持的最大 getLogs 窗口、批次限制,以及是否具有归档历史——这些数据来自实测,而非服务提供商的文档:
nuthatch doctor --rpc https://your-endpoint.example --address 0xADDR
对于任何重要任务,请使用自己的端点——自己的节点或付费服务提供商:
nuthatch init 0xADDR --chain arbitrum-one --rpc https://your-endpoint.example/arbitrum
nuthatch dev --rpc https://your-endpoint.example/arbitrum # or set rpc_urls in nuthatch.toml
--rpc 可以重复指定,nuthatch 会在端点池中轮询,并跟踪每个端点的健康状况,因此列出两三个端点既能提高吞吐量,也能实现故障切换。池中的所有端点都必须位于同一条链上——nuthatch 会在启动时验证这一点,并拒绝使用混合端点池运行,因为针对错误链进行索引会在不报错的情况下破坏状态。
查询数据——这才是重点
每个声明的事件都会变成一个名为 {alias}__{event} 的表(例如 usdc__transfer),其中包含事件字段,以及
block_number、block_hash、block_timestamp、tx_hash、log_index、
address 和序号 _seq。
从 3.11 开始,
block_timestamp取自日志,当前的执行客户端会在日志中放入blockTimestamp。如果节点未提供该字段,每个区块都要额外往返请求一次区块头:在 OBIB 用例 1 中,3.11 之前,这使回填耗时从 74.8 秒增至 1,689 秒,慢了 22.6 倍 (RFC-0029 §4c,2026-07-31)。如果某个 nest 永远不会查询 时间序列数据,可以通过init --no-timestamps删除该列。这是一个 初始化时 的选择:之后更改会造成不兼容的架构变更,并且需要 完整重新索引,因此值得仔细考虑,也正因如此,它不会是一个可以随手切换的标志。 详情。
# one-shot from the terminal (prints an aligned table; --json to pipe to jq)
nuthatch sql 'SELECT src AS sender, count(*) AS n FROM weth__transfer GROUP BY 1 ORDER BY n DESC LIMIT 5'
# or over HTTP, against a running `nuthatch dev`
curl 'localhost:8288/sql?q=SELECT%20count(*)%20FROM%20weth__transfer'
nuthatch sql在dev停止时查询本地存储;当dev占用存储时,则会透明地改用 正在运行的实例的 API——两种情况下都可以使用同一条命令。- nest 降级时会明确提示。 如果某个封存段无法读取,nuthatch 会提供表中的其余数据,而不是让查询失败——
但不会对此保持沉默。
/sql会返回degraded和列出受影响表的degraded_tables,nuthatch sql会打印一行警告,MCP 服务也会带上相同的提示。这个注意事项描述的是 nest 的状态,而不是你 恰好获得的行数,因此无论这次查询是否触及缺失部分,都会显示。 - 查询失败时会告诉你如何修复。 引擎错误会根据 nest 自身的架构进行分类,并在引擎原始消息后附加提示:未知表会指出最接近的实际表;未能
构建 的视图会明确说明这一点,而不是说“表不存在”;Solidity
bool列以精确文本'true'/'false'存储,并会说明为什么它在COALESCE、CASE和bool_and中会导致错误。/sql、MCPsql工具和nuthatch sql都会采用相同的处理方式。 - 冷热数据,一个入口。 查询会跨越实时的未封存顶端(redb)和 已封存的历史数据 (Parquet),整个过程透明无感——你无需考虑两者的边界。
- 支持大整数。
uint256值以精确文本存储;不超过 38 位的数值还会提供{col}_decDECIMAL 视图。SUM(value_dec)会对这些值求和:完整位宽的数值为 NULL,不会参与求和。WHERE NOT {col}_overflow会生成相同的总和。ID、nonce 和哈希仍保留在原始列中。 - 发生了什么,代币去了哪里。
nuthatch diff --from X --to Y(或--block、--tx、--since-last)会用固定句式描述一个范围,并提供精确金额:所有权、暂停、升级和角色变更,每种代币的最大转移、新持有者、注明无限额度的授权,以及其他每张表的计数。--flow会以文本、JSON、DOT 或 Mermaid 绘制代币流向。同一份文档也可通过GET /diff和 MCPdiff工具获取,每种输出都会以 nest 无法获知的信息结尾(RFC-0071)。 - 为 AI 原生打造。 内置了一个 Model Context Protocol 服务(
nuthatch mcp)——将 Claude(或任意 MCP 客户端)连接到你的索引器,就能用自然语言查询合约数据,完全离线运行。
速度如何
我们采用了别人的基准测试,而不是自己编写:Sentio 的 OBIB。
用例 1 会从 LBTC 索引横跨以太坊 2,220 万个区块的 Transfer 事件。
| 实际耗时 | 74.8 秒(3 次测试的中位数) |
| 事件数 | 294,278(与 Sentio 自己的 README 完全一致) |
| RPC 请求数 | 321 |
| 峰值 RSS | 320 MB |
| 测量环境 | commit 8e94f6c,2026-07-30,Alchemy,11 核笔记本:docs/bench/obib-case1.json |
| 案例 2 是案例 1 中带有每个账户余额的合约,而 OBIB 的实现通过对每个账户调用一次 | |
balanceOf() 来获取余额。我们一次都不调用。 对于普通的 ERC-20,余额就是转账历史,因此我们改为索引代币的整个生命周期并推导余额——多处理 250 万个廉价的 getLogs | |
区块,换取零次 eth_call 往返。 |
| 实际用时 | 49.2 秒(3 次运行的中位数) |
| 账户数 | 7,634——与 OBIB 发布的数据完全一致 |
eth_call 往返次数 | 0 |
| RPC 请求数 | 136 |
| 峰值 RSS | 325 MB |
| 测量信息 | commit 5a81a37,2026-08-07,Alchemy,11 核笔记本:docs/bench/obib-case2.json |
| 在 4.10.1 上 | 73.4 秒(3 次运行的中位数),174 个请求,403 MB;使用针对 Tenderly 无密钥网关的发布版二进制,该网关对其进行了限流,2026-10-05:docs/bench/obib-case2-4.10.1-tenderly-2026-10-05.json 和旁边的说明 |
OBIB README 中同一案例的参考用时:Sentio 7.78 分钟,Envio 8.54 分钟,Subsquid 46.85 分钟。
两点注意事项,明确说明,不藏着掖着。 首先,这在范围上刻意不作同类比较:
OBIB 的窗口为 100,001 个区块,而我们索引 2,611,334 个区块。在 OBIB 自己的范围内,我们用时 9.3 秒——但该次运行根本无法生成案例所需的输出,因为绝对余额需要窗口之前的历史记录,这正是该基准测试需要发起 RPC 调用的原因。其次,“推导”有实证支持,而非只是声称:在固定的结束区块,抽样的 39 个账户——余额最大的 10 个、余额非零且最小的 10 个、余额为零的 10 个,以及按地址排序的 10 个——全部与 balanceOf() 的结果一致,包括所有余额为零的账户;如果账本存在差一错误,这类账户恰好会将其暴露出来。账户数是 7,634,而不是 7,635,因为 0x0 是铸造/销毁交易的对手方,而非持有人。
案例 6 是工厂模板案例:在区块 19,000,000-19,010,000 上索引 Uniswap V2 工厂,通过 PairCreated 发现交易对,并索引找到的每个子合约上的 Swap。无需为每个子合约单独配置,无需重新部署,只需一条规则。
| 实际用时 | 4.67 秒(5 次运行的中位数;每次运行 4.5-5.4 秒) |
| 事件数 | 35,271 = 35,039 次交换,与 OBIB 的预期数量完全一致,另有 232 条 PairCreated 记录 |
| 发现的子合约数 | 232 |
| RPC 请求数 | 14 |
| 峰值 RSS | 229 MB(中位数;各次运行为 205-251 MB) |
| 测量信息 | nuthatch 4.7.0,已发布的 aarch64-apple-darwin 发布版二进制,使用 Tenderly 无密钥公共网关(mainnet.gateway.tenderly.co),在配备 48 GB 内存的 18 核 Apple M5 Pro 上测得,2026-10-05 |
| 测量产物 | docs/bench/obib-case6-4.7.0-tenderly-2026-10-05.json,包含二进制文件的 sha256、命令和每次运行的记录,详见旁边的说明 |
此表曾显示的 49.5 秒、16 个请求和 247 MB 的数据,现已撤回:这是在已关闭的 Alchemy 账户上使用 1.0.1 测得的。包括我们在内,没有人能重新运行该测试。为留档起见,报告仍保留在代码树中,见docs/bench/obib-case6.json,但不再作为性能声明。
作为参考,OBIB 发布的案例 6 数据在其两张表中并不一致:结果表给出的数据为 Envio HyperIndex 1.92 分钟、Subsquid 5.34 分钟和 Sentio 14.36 分钟;而案例 6 页面报告 Envio 在更早一轮测试中的用时为 30 秒。我们同时列出两组数据,但两者都不能作为同类排名:这些测试使用了不同的机器、不同的日期和不同的端点;此外,Envio 和 Subsquid 使用各自已经预索引的网络提供数据,而 nuthatch 使用普通 JSON-RPC 进行测试。
案例 1 和案例 2 于 2026 年 7 月和 8 月使用同一个 Alchemy 账户进行了测量;案例 6 的旧数据正是因这个账户而撤回的,因此无法按原样重新运行前两个案例。保留它们是因为其产物记录了提交、提供商和硬件信息;而案例 2 已在上方的行中使用当前版本重新运行,且未使用账户。相关产物如下:
docs/bench/obib-case1.json、
docs/bench/obib-case2.json、
docs/bench/obib-case2-4.10.1-tenderly-2026-10-05.json 和
docs/bench/obib-case6-4.7.0-tenderly-2026-10-05.json;
nuthatch bench backfill 可以重新运行其中任意一个。
案例 2 的 nest 已提交到 obib-case2/——无需密钥,因此通过 --rpc 提供端点;并且已验证可以从干净的检出版本重新构建。
案例 6 的 nest 已发布到 nuthatch-org/obib-case6,这样任何人都能复现运行结果,而非只能相信它;该案例也已提交至上游:sentioxyz/open-blockchain-indexer-benchmark#3。
案例 6 完全不需要账户:发布版二进制文件、该 nest 和公共网关就是全部配置。
共享端点上的实际耗时,既取决于提供商,也取决于我们。 在已撤回的 Alchemy 配置中,我们通过重新运行一个相邻且从未获取过、大小相同的范围,检查提供商缓存是否让案例 6 的数据显得更好:耗时 48.2 秒,而原数据为 49.5 秒(obib-case6-cold-control.json,2026-08-04),因此缓存并非原因。每次运行的事件数都相同;同一版本的请求数也相同:当时为 16,而 4.7.0 的五次运行中均为 14。这些才是范围控制效果的真实衡量指标。
关于案例 1 的数据,有两点值得了解:
- 在 v0.9.0 之前,它根本无法完成。 Alchemy 会以 HTTP 400 返回超大范围拒绝响应,而我们的状态分类器未将其列入;因此,需要拆分的窗口会原样重试,永无止境。一次外部基准测试发现了我们自己的测试未能发现的缺陷。
- 原始耗时的大部分都花在获取
block_timestamp上了——每个区块都要串行往返一次,而这个工作负载根本不会存储该列:同一范围和端点下,带时间戳耗时 1,689 秒,不带时间戳耗时 74.8 秒(RFC-0029 §4c,2026-07-31)。现在,时间戳按需获取,日志窗口也会根据端点实际可提供的数据量进行调整。
案例 6 还发现了一个缺陷,不过问题出在测试框架,而不是索引器:bench backfill 获取的是固定地址列表,因此测量工厂 nest 时未包含其子项——实际只有 232 个事件,耗时 2.6 秒,而预期应有 35,039 个事件,却被报告为成功(#310)。一次外部基准测试现在已经发现了两项我们自己的测试未能发现的问题。
分析查询在 Burrmill 上运行,这是我们基于 DataFusion 构建的引擎,使用封存的 Parquet 数据。4.1 之前,查询在 DuckDB 上运行;切换并非为了提速:在最大的 nest 上通过 /sql 执行时,Burrmill 每条语句的耗时约为 DuckDB 的 2.5 倍,而每个会话可用的内存是 DuckDB 的八倍;该数据于 2026-10-01 为 4.1.0 发布说明测得。这样做的好处是二进制文件中只需一种语言,并且精确运算会在无法处理时拒绝,而不是发生溢出。切换的原因和记录见最终替换 DuckDB。
工作原理(30 秒版)
RPC ingestion → deterministic decode → redb hot store (the unsealed tip)
│
past finality → content-addressed Parquet segments
│
Burrmill reads segments read-only → SQL (hot ∪ sealed)
- 每条链使用一个游标。 游标通过
eth_getLogs跟踪链的最新区块,将每个区间解码到热存储中,并在区块超过该链的最终性阈值后封存一个分段(主网的深度为 64;每条链可配置标签或深度)。重组只会影响热存储;已封存的分段绝不会被重写。 - 确定性核心。 解码、重组处理和实体派生都是确定性的,且可重新执行:输入相同,内容寻址的输出就相同。数据处理链路中没有 LLM。
- 单写入者,只读查询。 一个数据摄取线程负责写入;
/sql以只读方式连接。自 4.1.0 起,分析型 SQL 使用我们基于 DataFusion 构建的引擎 Burrmill 执行;此前使用的是 DuckDB,现在它已不再包含在二进制文件中。 - 派生表就是电路。 DBSP 会在区块到达时维护三个内置关系,以及
entities.toml中声明的所有实体;重组会被处理为撤销操作。在 Arbitrum 上,[extract] l1_blocks = true会添加一张表,记录每个区块的 L1 区块编号(4.6.0)。 - 历史数据可以迁移。
publish会将一个 nest 的已封存分段镜像到存储桶,seed则可从存储桶启动 nest,而无需回填(4.9.0);四个 Graph Protocol nest 的公开镜像位于 nuthatch-indexer.com/mirror。 - 发布版本在进入生产环境前必须通过门禁。 候选版本会在生产预算范围内提供生产 nest 的副本,处理该 nest 实际收到的查询,并将每个回答与生产环境的回答进行比较(docs/release-gate.md)。之所以设立这道门禁,是因为 4.1.1 虽然通过了 CI,却在部署后几分钟内就无法提供仪表板所需的视图。
让 AI 使用它
nuthatch 内置了 Model Context Protocol 服务,因此编码 Agent 可以用简单的英语查询合约数据——离线运行,不会向外发送数据。只需一步即可完成配置:
nuthatch dev & # the index the agent will query
nuthatch mcp --print-config # prints a copy-paste config for Claude Code / any MCP client
也可以直接将它添加到 Claude Code:
claude mcp add nuthatch -- nuthatch mcp --url http://127.0.0.1:8288
如果网关后面的 nest 需要承载令牌:--token-env NEST_TOKEN 用于指定保存令牌的变量(默认读取 NUTHATCH_MCP_TOKEN),因此令牌不会出现在命令行中。
然后只需询问:“USDC 持有量最高的账户有哪些?”——Agent 会编写 SQL 并在你的 nest 上运行。(要让它第一次就回答正确,需要进行语义层工作。)
教你的 Agent 构建 nest。 安装构建器技能后,Agent 就能自行操作 nuthatch——从 init、配置、工厂、合规、多 nest 运行时到故障排除,甚至在你拥有 nest 之前就能完成这些工作:
cp -r skills/nuthatch-builder ~/.claude/skills/ # or your repo's .claude/skills/
CLI 和配置参考文档由二进制文件生成,并由 CI 检查是否存在差异,因此技能对标志的说明始终准确(RFC-0017)。
其他功能
核心功能是“你的合约 → SQL”。除此之外,nuthatch 还为需要更多功能的团队和运维人员提供了完整的功能集,而且这些功能都不会妨碍顺畅的使用流程:
- 多个合约,一个 nest。 在
nuthatch.toml中声明多个合约;将它们一起索引。 - 固定的合约状态(RFC-0023)。
[[calls]]块会在固定区块读取合约,并将结果存为表;还可使用触发该调用的行来构建 calldata——这相当于子图会写入的contract.balanceOf(event.params.user)。固定区块才能保证结果确定:答案不会变化,因此两个操作员重新运行相同的 nest 会得到相同的字节;结果按(chain_id, block, contract, calldata)进行内容寻址。需要将--state-rpc指向归档节点。 - 经过验证的 IPFS 文档(RFC-0037)。
[[ipfs]]块会将一列内容地址转换为已解析文档的表。每个正文都会重新计算哈希,并与其声称的 CID 核对,因此网关若提供了错误字节,就不会产生行,而不是给出看似可信的结果。CID 会从合约存储的任何形式中提取——裸 CID、ipfs://URI、网关 URL 或原始 32 字节摘要——并且主机名会被丢弃,因为该字符串来自日志;若遵从它,就会让发出事件的人决定索引器连接到哪里。 - 工厂/动态合约(RFC-0009)。监视工厂(例如池工厂);运行时发现子合约,并将其索引到共享的
{template}__*表中——无需为每个子合约重新部署。 - 派生,三种速度。 已解码的事件会成为表,并在索引时封存。三个内置关系——余额、敞口、速度——是增量式 DBSP 电路;链重组会触发撤回。由 nest 编写的
views/*.sql是命名 SQL,在查询时对热数据 ∪ 已封存数据求值,并非 IVM。nest 还可在entities.toml中声明自己的自定义增量实体(RFC-0041):DBSP 会随着区块到达维护其中的SELECT,实体从/derived提供,并可通过/sql按名称查询;链重组会像内置实体一样通过撤回来处理。在 Lodestar nest 的副本上,这将indexer_rewards面板的 p50 从 2.15 s 降至 87.7 ms(docs/bench/3.0.0-alpha-live.md)。 - 标签与标记(RFC-0008)。地址标签、阈值与速度标记,以及交易对手敞口视图。
- 告警与 Webhook(RFC-0010)。通过 HMAC 签名的出站请求,配有持久化的至少一次投递发件箱;缓慢的端点不会阻塞索引。
- 内置管理 UI。 独立页面位于
/_admin/,提供状态、表和视图/nest 检查器。localhost 可直接访问;非 localhost 访问时,每个请求都需要令牌。 - 地址历史(RFC-0063,5.1.0)。配置了
[address_history]表且没有合约的 nest 会监视账户而非合约,并在/api提供 Etherscan 格式的历史记录,供 rotki 及其他使用该接口的工具访问。它在本地通过独立路径运行,行数据存储在 nest 自己的存储中,而不是 SQL 表;你的 RPC 服务会看到你监视的地址。 配置参考。 - 浏览器标签页中的 nest(RFC-0065、RFC-0066、RFC-0068)。nuthatch-indexer.com/play 可查询已发布的 nest;通过合约地址搭建并索引新 nest,其中
init自带的代码会编译为 wasm;还可打开明信片:这类链接按哈希固定已发布 nest 的片段,因此主机可以拒绝提供答案,却无法篡改答案。这里使用 wasm 中的 decode 和 Burrmill,而不是已移除的 WASM handler 层。 - 多个 nest,一个运行时,一个或多个链(RFC-0012、RFC-0021)。在一个进程中托管多个 nest;同一条链上的 nest 共享一个游标,每个窗口只调用一次
getLogs;一个运行时还可跨越多条链,并为每条链设置一个独立游标——例如在同一个运行时中运行 Base nest 和 Arbitrum nest。每个 nest 相互隔离,并且每个活跃链游标都有资源占用预算(≤2 GB)。这是一项能力,而非强制要求:每个运行时只运行一条链仍是简单的默认方案。 - 无需重启即可挂载和卸载 nest(RFC-0027)。
POST /_admin/nests可实时挂载 nest,DELETE /_admin/nests/<name>可实时卸载 nest,因此更改 nest 集合不再会让所有共租户停机。只有在游标 RAM 预算允许时才会接受挂载(否则拒绝,而非警告);nest 会先追赶进度,再加入运行时,因此不会拖着共租户回头重放历史,之后才会获得路由;卸载时会先排空,而不是直接移除路由。nest 集合会持久化到mounts.toml,因此重启后会恢复到你最后要求的状态。暂停、恢复、无缝将名称迁移到新的 NID、试运行挂载以计算预估资源占用,以及在挂载时从注册表获取 NID:这些功能都属于 nest 生命周期操作。 - 扩展模式——跨机器组成集群(RFC-0022)。当一台机器无法再容纳你的游标时,相同的 crates 可作为三种角色运行:保存预期运行内容的控制平面、由成员获取游标租约的写入节点池(
nuthatch worker),以及从共享状态提供服务且不持有任何数据的查询前端层(nuthatch serve)。通过角色标志切换,而非分叉代码;在构建时选择启用(--features postgres-store),因此发布的二进制不包含数据库驱动程序。所有权由存储层强制执行:每次写入都会携带栅栏标记,停滞后恢复运行的 worker 会发现其写入遭到拒绝。Worker 会按内容地址从注册表拉取分配给它们的 nest,因此重新标记版本不会改变集群运行的内容。这是供一个操作员协作管理多个 nest 的可自托管分布式方案;不涉及按租户计费和授权。 - Nest 包与注册表——打包、发布,随处加载。
nuthatch nest bundle会将 nest 编写的输入打包成一个可移植、按内容寻址的.bundle;nest load <bundle-or-url>会验证并安装它,重新生成 decode 注册表并断言其匹配,让任何人都能运行你所用的精确 nest。注册表(RFC-0019)可以是文件系统路径,也可以是任何兼容 S3 的存储桶(使用AWS_*环境变量;非 AWS 环境使用AWS_ENDPOINT):先运行nest publish <bundle> --registry … --as name@version,再运行nest load name@version --registry …。注册表并非必需;bundle 和load <file|dir>都不需要注册表。 - 将 nest 镜像到存储桶(RFC-0052)。
nuthatch publish sync --target s3://bucket/prefix会将 nest 已封存的 Parquet 片段、目录和来源信息封装复制到任何兼容 S3 的存储桶或目录;dev --publish-target会在片段封存时持续更新镜像,并以流式分块上传,因此数据摄取不必等待存储桶响应。镜像以 nest 的数据身份为键,而不是以其 NID 为键:编辑若只更改 NID、不更改数据身份(这就是下文“安全升级”所述的外观更改),仍会发布到同一数据集;任何改变解码内容的编辑都会分叉出新的数据集。publish status会显示仍待上传的内容,publish verify会将每个对象与本地片段核对(--deep会重新下载并重新计算哈希),doctor --publish会将镜像纳入健康检查。表中最后几百行要等到合并进下一个片段后才会镜像;如果 nest 所在的链已停止出块,可用publish finalise让这些行完成合并。读取镜像不需要 nuthatch:DuckDB、Trino 或任何能读取 Parquet 的工具都可以,如读取已发布的 nest所述。 - 从镜像启动 nest(4.9.0)。
nuthatch seed --from <mirror> --dir <nest>会从路径、s3://前缀或公共存储桶的https://地址填充尚未开始索引的 nest,并根据镜像目录核对每个文件;下一次运行dev时会从镜像末尾之后的区块继续跟踪链。哈希能证明每个文件与目录中列出的文件一致,但不能证明目录符合链上数据:请从你信任其运行 nest 的操作员处获取镜像。四个 Graph Protocol nest 已发布于 nuthatch-indexer.com/mirror。 - 安全升级——无需重新同步(RFC-0020、RFC-0033)。升级二进制可直接替换,数据目录会延续使用。编辑 nest 的影响取决于编辑内容。外观更改——注释、重命名视图、描述——不会改变其 decode:独立目录会继续提供已有数据;在运行时中,只有当编辑后 nest 的新身份与原数据的身份及记录的 decode 身份都匹配时,才会采用现有数据集,因此无需重新索引。解码更改——合约、事件、ABI——会改变行的内容,因此独立目录会拒绝使用旧数据启动,并将新 nest 索引到全新的目录;在运行时中,
POST /_admin/move/<name>会在新 NID 追赶进度后将名称切换过去,全程不会中断。--allow-breaking仅用于nuthatch migrate,即一次性迁移 2.0 之前的目录。片段按内容寻址,并在运行时之间共享,因此两个解码同一合约的 nest 只会保存一份数据,而非两份。子图需要为之付出完整重新同步代价的操作,在这里仅需通过哈希比较即可完成外观更改。 - 优先派生——无需调用的
eth_call(RFC-0023)。The Graph Foundation 的数据显示,超过 70% 的子图会调用eth_call,其中很多调用读取的数据其实可以从它们已索引的事件中派生出来——它们只是因为无法派生才去读取。Nuthatch 可以做到:nuthatch recipe add total_supply会添加一个 SQL 视图,根据 Transfer 事件计算 ERC-20 的totalSupply(),即 Σ 铸造量 − Σ 销毁量——结果确定、免费,且无需归档节点。该视图在查询时运行,并非 DBSP 电路。它能派生出子图需要付费让归档节点读取的数据。对于少数无法派生但永远不会变化的读取——decimals/symbol/name——nuthatch metadata fetch只会调用一次并永久缓存。 - 能够适应真实服务提供商的数据摄取(RFC-0028)。如果
eth_getLogs请求的数据过多,就会拆分并重试;如果服务提供商给出建议范围,则采用该范围。对于无法归类的失败,无论如何都会拆分一次,因此即使端点使用了我们从未见过的错误措辞,也能正常工作,不会卡住。限流、传输故障和凭证拒绝会分别处理——被拒绝的 API 密钥会触发明显的冷却处理,而不是无限重试。此外,已封存片段现在会根据数据来确定边界并刷新,而不是根据某个抓取窗口恰好结束的位置确定;因此,两个操作员索引相同范围时,无论 RPC 配置如何调整,都会生成字节完全相同的片段。 - 指标。 Prometheus
/metrics:链头延迟、已解码/已封存行数、链重组次数、查询次数、RSS。
在生产环境中运行
nuthatch 专为置于前端之后而构建,不应直接暴露——网关、身份验证和计量属于运维层;nuthatch 提供让前置部署安全的防护措施(查询超时、行数上限、结果字节数上限、并发限制,以及 /sql 上的文件系统访问拒绝列表)和信号(/metrics)。它默认绑定到 127.0.0.1;如需绑定到其他地址,请使用 --listen,并在前面部署网关。请参阅 docs/operators.md。
- **资源占用:**每个活跃链游标的内存占用 ≤2 GB,单个二进制文件,通过检查点恢复实现优雅的 SIGTERM 关闭。启动检查会统计引擎内存池、摄取预留空间和余量,并与
NUTHATCH_MAX_RSS比较;如果配置无法满足要求,就会拒绝启动并指出可行的最大值。自 4.10.0 起,运维人员可以根据测量结果设置余量和预留空间(NUTHATCH_RUNTIME_HEADROOM、NUTHATCH_INGESTION_RESERVATION;容量与规格估算)。 - **持久性:**内容寻址的分段可在运行期间安全复制;请备份 nest 目录。
- 发布前:发布门禁会使用生产 nest 的副本和它所服务的语句运行候选版本,并将每个结果与生产环境进行比较。同样的原则也适用于你自己的 nest:比较结果,而不只是状态。
dev是服务命令——它会在一个进程中完成回填、跟随最新区块并提供服务。docs/operators.md中提供了可直接复制粘贴的 systemd 和 Docker 配置示例。- 一台机器不够用了?扩展模式会将游标分配到写入节点池,并使用可独立扩展的服务层。当单台机器无法在 RAM 预算内容纳游标时,再考虑使用它——不要过早采用,因为在一台机器上运行多个 nest 更简单,而这种简洁正是嵌入式路径的优势所在。
docs/operators.md 是完整的运维指南,在正式运行之前就值得一读,而不是等到之后。docs/verification.md 与之配套:这是一份验收操作手册,通过每一步都可证伪的检查,证明部署能够正常工作,并明确说明哪些层级是我们自行验证过的,哪些尚未验证。
还在犹豫是否要相信它? docs/kicking-the-tyres.md 提供了一份指南,帮助你证伪 nuthatch,而不是确认它:冷启动检查、与公开子图进行正确性对比、持续运行的成本、对 /sql 的红队测试,以及我们已经犯过的错误。我们宁愿由你发现下一个问题,也不愿让用户先发现。
本指南涵盖了人们实际会遇到的问题:
| 如果你想了解 | 请参阅 |
|---|---|
| 如何根据 RPC 的限制调整回填? | 配置选项 - --window、--concurrency、--seal-direct |
| 应采集哪些指标?哪些情况应触发告警? | 可观测性 - 指标、告警、健康状态与就绪状态 |
| 出问题时会发生什么? | 故障模型和操作手册 |
| 如何备份? | 数据生命周期 |
| 如何运行未列出的链? | 运行未列出的 EVM 链 |
| 还有哪些内容尚未完成? | 已知差距 - 如实说明 |
此处稳定版本的含义
主版本承诺的是稳定性,而不是声称功能已经完整。
- 5.x 是稳定版本线。 在 5.x 中,
nuthatch.toml、mounts.toml和entities.toml会继续正常工作;数据目录可直接升级,无需重新索引;HTTP、SQL 和 MCP 接口不会发生破坏性变更。只承诺升级:不保证可以降级。默认关闭的 cargo 功能属于实验性功能,不在保障范围内。完整条款见稳定性契约。 - 次版本增加功能,补丁版本修复问题。 已发布的 5.x 只会获得补丁版本;功能要等到下一个次版本,正确性和安全修复则会立即以补丁形式发布。发布节奏视工作需要而定:4.1.0 至 4.10.1 于 2026-10-02 至 2026-10-05 期间发布,每个版本的说明都在
docs/releases/下。 - 升级只需替换二进制文件。 无需迁移数据,也无需转换步骤。该方式已在生产机器上经过验证,适用于 0.3.0 → 0.6.0 → 0.7.2 以及之后的每个主版本;CI 中也会验证:每次构建都会打开冻结的 v3.13.2 数据目录,并精确读回数据(
tests/upgrade_golden.rs)。 - MSRV 为 1.95,这是实际使用的版本,而非仅仅声称如此:CI、
rust-toolchain.toml和发布构建都使用该版本。没人测试的版本,就不算承诺。 - 嵌入式模式是生产环境的首选路径。 如今
dev已在生产环境运行,可托管一个或多个 nest。扩展模式已构建完成,并在真实机器上通过验证,但它还比较新:在 0.9.3 之前,其写入器池完全不会建立索引。如果每台机器运行一个进程就够用,仍应优先采用这种架构。
刻意不包含的内容: 按量计费或计量的托管服务、令牌、默认启用的遥测,或超出二进制文件 + compose 的任何部署方案。这些不是待办事项,而是不在项目范围内。
安全
nuthatch 默认绑定到 127.0.0.1,设计上需要置于前端服务之后。在向任何不可信对象开放 /sql 之前,请阅读 SECURITY.md——并确保使用当前版本:
- v0.9.3 修复了
/sql上的任意文件读取漏洞。DuckDB 接受带引号的函数名,而防护逻辑只匹配不带引号的名称,因此SELECT * FROM "read_csv"('/etc/passwd')会被执行。所有更早的版本都受影响。 - v0.6.2 修复了通过用
;堆叠的COPY … TO在/sql上进行的任意文件写入漏洞。
两项漏洞都已在仓库的 Security 选项卡发布安全公告。完整的 1.0 之前对抗性审查,包括我们判定为不应由我们修复的发现及其原因,见
docs/security-audit-2026-07-31.md。
项目
- 设计记录在 RFC 中(状态见索引);核心愿景以及 CLI/UX 方向见
RFC-0015。延期或遗留工作见
docs/backlog.md;各版本的变更见docs/releases/。 - 治理: 由一人维护、自筹资金的公共项目;所有内容均为开源。没有令牌,也不会自动向外发送数据。请参阅
GOVERNANCE.md和长期有效的设计简报CLAUDE.md。 - 社区: Nuthatch Discord。如果你希望有人使用标准二进制文件、免费为你运行一个 nest,或需要支持 nuthatch 尚未支持的链,欢迎在那里提出。如今所有受支持的链都是 EVM 链;对于具名用户提出的非 EVM 链请求,会逐案考虑,但不作承诺。
- 不在项目范围内: 按量计量或计费的服务,或超出二进制文件 + compose 的任何部署方案。
许可证
你可以自行选择采用 MIT 或 Apache-2.0 许可证。
除非你明确声明另有安排,否则你有意提交以纳入本作品的任何贡献,均应按上述双重许可授权,且不附加任何条款或条件。
做自己的索引器。
评论
一个开放协议,规定了 AI 应用如何以统一方式连接外部工具与数据源。客户端负责与模型对话,MCP 服务负责提供工具、资源和提示模板,两者通过标准消息通信,同一个服务可以被不同客户端复用。