关于 reader
reader 是一项开源服务,可从公开区块链数据重建 Agent 的付款权限和活动记录。它面向需要可复现且带引用事实、而非评价结论的团队和分析人员。
主要功能
- 01索引付款权限及相关事件
- 02读取 NablaZ 和第三方记录
- 03引用链、区块、交易和日志信息
- 04提供复现每条日志的命令
- 05通过只读 API 查询地址事实
- 06支持 Arbitrum One 和 Base
适用场景
- 01核验地址记录的支出权限
- 02追踪付款及其权限条款
- 03通过 RPC 复现引用的链上证据
- 04查看 Agent 反馈和钱包历史
MCP 相关
托管服务通过 Streamable HTTP 提供 MCP 服务,包含一个名为 read_subject 的工具。MCP 客户端可提交链 ID 和地址,以获取已索引且带引用的事实。
配置
远程服务无需本地安装。选择你使用的客户端,复制配置或使用一键安装。
https://read.nablaz.io/mcpclaude mcp add --transport http io-nablaz-reader https://read.nablaz.io/mcp{"mcpServers":{"io-nablaz-reader":{"type":"http","url":"https://read.nablaz.io/mcp"}}}在 Cursor 中打开{"servers":{"io-nablaz-reader":{"type":"http","url":"https://read.nablaz.io/mcp"}}}在 VS Code 中打开{"mcpServers":{"io-nablaz-reader":{"type":"http","url":"https://read.nablaz.io/mcp"}}}{"mcpServers":{"io-nablaz-reader":{"serverUrl":"https://read.nablaz.io/mcp"}}}命令在终端中运行;JSON 粘贴到对应客户端的 MCP 配置后重启。需要授权的服务会在首次连接时提示登录。
工具
详细介绍
nablaz-sdk
一款用于读取 Agent 付款权限的开源工具。它根据公开的链上数据,还原某个地址获准支出的金额、实际支出金额及相关条款,并将每条事实与可重现该事实的命令放在一起。它读取 NablaZ 记录和受托人金库、Coinbase 支出权限、MetaMask 委托以及 Agent Credit Lines。
它不持有任何密钥,无需账户或 API 密钥,只要有 RPC 端点,就能在任何地方运行。
读取内容
Arbitrum One 和 Base 均已启用。NablaZ 合约目前仅部署在 Arbitrum One 上,因此在 Base 上,读取器只会索引第三方来源。
两个链上的第三方来源:
- Coinbase
SpendPermissionManager:SpendPermissionApproved、SpendPermissionUsed、SpendPermissionRevoked。 - MetaMask
DelegationManager(委托框架 v1.3.0):RedeemedDelegation,并解码所有 caveat。对于每次兑换,读取器都会读取交易收据,并保留委托人发出的 ERC-20Transfer。单独的兑换不构成付款;只有这类Transfer才算。 - 在已配置的贷款方处读取 Agent Credit Lines(
Borrowed、Repaid、Defaulted)。目前这两个链上都没有已知的贷款方,因此尚未注册任何贷款方。解码器附带测试;如果没有索引到任何内容,读取结果会显示“没有索引到内容”及其区块范围。 - ERC-8004:来自“Reputation Registry”的
NewFeedback(谁对哪个 agentId 进行了评分,以及评分发生在哪里;绝不读取反馈值、小数位数、标签、端点或 URI),以及来自 Identity Registry 的MetadataSet和 ERC-721Transfer中的 agentWallet 和所有者历史记录。地址来自src/generated/erc8004.json(其中固定了供应商提交版本)。
已部署 NablaZ 的链上来源:
NablaRecord中的RecordEntry,包含所有字段。NablaAccountFactoryPermanent和AccountRegistryV6中的AccountCreated,用于发现账户。- 每个已发现账户代理上的 ERC-1967
Upgraded。 - 当授权方或发送方是受托人金库时,读取 USDC
AuthorizationUsed和Transfer。金库仅通过日志发现:PAY_AUTHORIZED条目会指出金库,而同一笔交易中的资金Transfer会确认该金库。
写入内容
| 表 | 每行对应 |
|---|---|
record_entries | RecordEntry 原始记录 |
obligation_pairs | 按 obligationRef 分组的 OBLIGATION_OPENED / REPAY / OBLIGATION_CLOSED 记录 |
vault_payments | 结算:某个金库的 AuthorizationUsed 与其 Transfer 配对,以及该笔付款所依据的授权和信封 |
implementation_history | 每个账户的 Upgraded 日志或 IMPLEMENTATION_CHANGED 记录 |
counterparties | 不重复的(主体、交易对手方)配对,包含首条证据和计数 |
accounts、grants、vaults、vault_fundings | 上述各表所依据的发现和配对状态 |
factory_accounts | 在永久工厂上创建的账户,以及来自工厂自身事件的所有者 |
external_permissions | 支出权限或已赎回的委托,包含其类型、标签和解码后的条款 |
external_payments | SpendPermissionUsed,或赎回交易中来自委托方的 ERC-20 Transfer |
external_redemptions | RedeemedDelegation,无论是否发生价值转移 |
credit_line_events | 配置的贷款方处发生的 Borrowed / Repaid / Defaulted 事件 |
feedbacks | ERC-8004 NewFeedback:agentId、评分者、反馈索引;不含价值、标签或 URI |
agent_wallets | 代理的 agentWallet 键对应的 ERC-8004 MetadataSet:agentId 和钱包(清除时为 null) |
agent_owners | ERC-8004 身份 Transfer(ERC-721):agentId 和新所有者 |
行中记录事实和计数。读取器只陈述事实,不对任何主体作出判断。
在 vault_payments 中,提交结算交易的一方(tx.from)记录为
submitter。它绝不会被视为交易对手方;交易对手方是 Transfer 的接收方。
结算不会写入 NablaRecord 记录。付款记录由两部分组成,由读取器配对:账户在预留资金至被授权方金库时写入的 PAY_AUTHORIZED 记录(预留记录,位于 record_entries),以及 USDC 自身的 AuthorizationUsed 和 Transfer 事件(结算记录,位于一行 vault_payments 中)。引用一笔付款时,须同时引用两者:PAY_AUTHORIZED 日志(区块、交易哈希、日志索引),以及 USDC 结算交易及其区块。
引用规则
日志是事实依据;此读取器只是便于查询日志的工具。每行都包含其证据的链 ID、区块号、交易哈希和日志索引,以及可从任意 RPC 返回该日志的 cast logs 命令和等效的原始 eth_getLogs 请求。如果无法用该行的命令复现该行内容,就是一个错误。
有一个字段读取的是状态,而非日志:vault_payments 中的信封过期时间。它标记为“状态读取 — 需要归档 RPC”。读取器会在区块 N−1 和结算区块 N 调用账户授权合约上的 envelopeOf(account, granteeKey, token),并将两次调用都保存为 cast call --block 命令。如果两次读取结果一致,就保存该值。如果结果不同,则表示信封在区块 N 内发生了变化,该字段会保存为“无法确定”,并引用两次读取结果。2026-10-23,通过 https://arb1.arbitrum.io/rpc 在距离链头 50,000 个区块处进行相同的历史 eth_call,10 次中有 6 次返回结果,另外 4 次返回“historical state … is not available”,因此不能依赖公共端点获取此字段。其他所有内容都可在任意 RPC 上通过日志重建。
读取
GET /read/:chainId/:address 会返回某个地址的所有已索引事实。无需密钥:该端点读取公开日志。
{ "subject": "0x…", "chainId": 8453, "asOfBlock": "53160200",
"facts": [ { "source": "metamask:delegation_payment", "label": "(b) exact execution, once", "shape": "b",
"terms": { "payer": "0x…", "payee": "0x…", "token": "0x…", "amount": "…" },
"cite": { "chainId": 8453, "block": "…", "tx": "0x…", "logIndex": 0 },
"reproduce": "cast receipt 0x… --rpc-url \"$BASE_RPC_URL\" --json | jq …" } ],
"selfDealing": null, "exposure": null,
"cShare": { "chainId": 8453, "asOfBlock": "53160200",
"byToken": { "0x…": { "count": { "c": 0, "total": 1 }, "value": { "c": "0", "total": "…" } } } },
"notes": [ "no Agent Credit Lines lender registered on this chain",
"SpendPermissionManager permissions and spends for this address: nothing indexed (blocks 53160000..53160200)" ] }
- facts:每条记录条目、金库付款、支出权限、委托、付款、赎回和信用额度事件各一条。每条都会注明来源和权限标签、区块、交易和日志索引,以及一条可从任意 RPC 获取相同日志的单行命令。
- selfDealing(仅限 NablaZ 账户):付款对象数量、付给同一控制者名下账户的金额占比、付给无记录付款对象的金额占比,以及句子“A payee controlled by the same person under another key cannot be detected from chain data.”。
- exposure(仅限 NablaZ 账户):按授权和代币列出各窗口中剩余额度的最小值,以及金库余额;同时说明链上无法观测未完成的授权。
- cShare:按代币列出本次读取中标记为 (c) 的付款事实数量和金额,以及所有付款事实的总数和金额;绝不跨代币求和。
- notes:每个未返回结果的来源(“未索引任何内容(区块 X..Y)”,或者索引器落后于来源起始区块时显示“已索引至 X;来源从 Y 开始”),以及每条无法确定的事实,并以文本说明。
每个权限标签都属于以下五种形式之一:
| 形式 | 标签 | 来源 |
|---|---|---|
| (a) | 按周期 / 总额 / 每次调用 / 每次执行 / 流式的额度 | Coinbase 支出权限;MetaMask 金额执行器;NablaZ CAM V4 窗口(规范的 NablaZ 账户会标注“按构造符合授权范围”) |
| (b) | 精确执行,执行一次 / N 次 / 次数不限 | MetaMask 精确调用执行器;LimitedCallsEnforcer 或单次使用执行器中的计数 |
| (c) | 无法确定 | 不在任何厂商列表中的执行器、无法解码的条款、非规范的 NablaZ 账户 |
| (d) | 金额或调用次数不受限制 | 限制条件仅限制调用或赎回者的委托 |
| (e) | 第三方证明,注明写入者 | Agent Credit Lines;其他写入者创建的记录条目;ERC-8004 反馈(注明评分者) |
MetaMask 执行器表由厂商在固定提交版本中的 Deployments.md 生成(scripts/gen-enforcers.ts、src/generated/enforcers.json)。如果厂商的主分支列出了表中没有的执行器,测试就会失败。
单行命令才是事实依据。 读取器是对日志的便捷封装。每个 reproduce 字段都是一条 cast 命令,你可以使用 $ARB_ONE_RPC_URL 或 $BASE_RPC_URL 中的 RPC URL 自行运行。
反馈溯源
当主体是 agentWallet 或 ERC-8004 agentId 的所有者时,关于该 agentId 的每条 NewFeedback 都会成为一个 erc8004:feedback_grounding 事实(R-D2B-2)。如果评分者向身份的付款地址发起的 ERC-20
Transfer 在该反馈之前结算(按区块排序,同一区块内再按日志索引排序),则该反馈具有溯源依据,无论代币或路径如何(R-S191-11)。付款地址按转账所在区块解析,绝不使用之后的地址:先在该区块调用 getAgentWallet,否则调用该区块的 ownerOf;事实会说明适用的是哪项绑定(R-S191-16)。在身份存在之前发生的转账不构成溯源依据(“付款区块时身份不存在”)。反馈区块时的钱包地址会与溯源依据一并报告(“feedback block 的钱包:
普通转账不会被索引。读取时,读取器会调用 eth_getLogs,设置 topic0 = Transfer、topic1 = 评分者、任意代币地址,并指定从 Identity Registry 的起始区块(或该实例的起始区块)到反馈区块的范围;事实会注明该范围。候选地址来自身份的 agentWallet 历史记录(MetadataSet)和所有者历史记录(ERC-721 Transfer),并会在转账所在区块逐一确认。来自评分者自己的受托人金库的结算视为由评分者付款(R-S191-17)。不会读取原生代币转账(“未读取原生代币转账”,R-S191-18)。如果某笔转账属于读取器的付款事实,则保留其权限标签;其他转账会标记为 (c),原因是“直接转账,没有权限对象”。事实还会说明评分者是否为主体,以及当双方都是 NablaZ 账户时是否共用一个控制者。事实不包含金额、小数位数、标签或 URI,也不会获取反馈文件(“未读取 feedbackURI”)。
test/fork/afg.fork.test.ts 会在 Agentic Finance Graph 的 Base 测试夹具中,对 816 条具有溯源依据的反馈条目运行此溯源检查(CC BY 4.0,见 NOTICE),并将比较结果写入
docs/reader/afg-agreement-2026-10-20.csv。docs/reader/retroactive-binding-2026-10.md 说明了其余差异。
托管读取
https://read.nablaz.io 在同一实例中运行读取器和下方的 MCP 服务,支持两条主链(链 ID 为 42161 和 8453),并从每个来源的部署区块开始索引。它无需密钥,且仅提供读取功能。它有两个入口:
GET https://read.nablaz.io/read/:chainId/:address:返回本地GET /read所返回的 JSON。POST https://read.nablaz.io/mcp:MCP 服务的 Streamable HTTP 传输,包含一个工具read_subject。对于需要 URL 的 MCP 客户端:{ "type": "http", "url": "https://read.nablaz.io/mcp" }。
两个入口都会按客户端 IP 限流;超过限额的请求会收到 429 响应和纯文本正文。此外,GET /live(进程正在运行)会返回状态码和简短文本;GET /ready(索引已追上)在就绪时返回 HTTP 200 和空正文,未就绪时返回 503 和 {"ready":false};除此之外不提供任何内容:没有状态、指标或计数端点。读取基于已索引的区块,并引用合约自身的日志和状态,因此每条事实都可以通过你自己的 RPC 和一行命令进行核查。部署配置包括 fly.toml、Dockerfile 和 scripts/reader-start.sh;
docs/runbooks/hosted-read.md 列出了读取器读取的变量、两个健康检查路径以及数据库备份计划。
在 Claude 中使用托管版读取器
阅读 NablaZ 的开放记录:受权限约束、带链上引用的 Agent 操作。证据,而非裁决。
设置。 在 Claude 中,依次选择 Settings → Connectors → Add custom connector → https://read.nablaz.io/mcp → No sign-in
(该服务无需密钥)。也可通过普通 HTTP 执行相同读取:
GET https://read.nablaz.io/read/{chainId}/{address}。
示例。
示例 1 — 读取某个主体的记录。
提示词:“使用 NablaZ Reader,查询链 42161 上 0x8E65fa412e14c1859a9a278fdEFDE89A3c15b11c 的记录。”
执行过程:read_subject 调用 GET /read/42161/<address>,并返回该账户的已索引事实;
每条事实都附带自己的区块编号和交易引用,付款则同时引用两部分——授权记录和结算交易。
示例 2 — 每笔付款所依据的权限。 提示词:“这些付款各自依据什么权限进行?” 执行过程:每条付款事实都会根据条款形式附带权限标签(精确执行、额度授权、无限额、无法判定或第三方证明),并附带指出其来源权限系统的来源标签。
示例 3 — 没有记录的主体。 提示词:“查询链 42161 上 0x0000000000000000000000000000000000051900 的记录。” 执行过程:读取器会回答“未索引任何内容”,并同时说明已覆盖的区块范围——空结果会注明其覆盖范围;它绝不会声称相关内容不存在。
隐私。 读取器无需密钥,也不持有用户账户;它提供公开的链上数据,且只记录运行服务所需的信息。完整政策:https://nablaz.io/privacy
支持。 请在此代码库提交 GitHub 问题,或联系 [email protected]。
在本地运行
以下所有内容都在你的计算机上运行:读取器、MCP 服务和技能。需要 Node 22.12+ 和 pnpm。使用干净的代码检出目录:
pnpm install --frozen-lockfile
export READER_ARBITRUM_RPC_URL=... # Arbitrum One RPC; archive for the envelope and exposure reads
export READER_ARBITRUM_GROUNDING_RPC_URL=... # Arbitrum One RPC for backfill eth_getLogs and grounding reads
export READER_BASE_RPC_URL=... # Base RPC
export READER_BASE_GROUNDING_RPC_URL=... # Base RPC for backfill eth_getLogs and grounding reads
export PONDER_TELEMETRY_DISABLED=true
export READER_EXTERNAL_FROM=42161:518780000,8453:53160000 # optional; see below
pnpm reader # = cd packages/reader && pnpm start --schema reader
每条链需要两个 RPC URL,分别用于两种角色,且两者都是必需的;同一个 URL 可以承担两种角色。第一个 URL 负责跟踪最新区块和读取状态。第二个 URL 负责处理所有按区块范围执行的 eth_getLogs(回填和用于事实依据的读取),因此应支持无限范围。只有第一个 URL 出错时,跟踪最新区块的调用才会回退到第二个 URL;用于事实依据的读取绝不会使用第一个 URL。
读取器在 localhost:42069 上提供服务。/ready 返回 200 后,表示已追上进度,读取内容会截至已索引区块:
curl localhost:42069/ready
curl localhost:42069/read/42161/0x8E65fa412e14c1859a9a278fdEFDE89A3c15b11c
数据表存储在 packages/reader/.ponder/ 下的本地 PGlite 数据库中。RPC URL 只从环境中读取,不会写入代码库。使用上方的 READER_EXTERNAL_FROM 行时,首次运行大约花了十分钟才追上两条链的进度(2026 年 10 月 27 日)。
第三方数据源从其部署区块开始,涵盖数年的历史记录。
READER_EXTERNAL_FROM 可按链指定较晚的起始区块。该区块之前的事实不会被索引,每次读取的备注都会注明读取范围。READER_CHAINS=8453 可将实例限制为一条链;
READER_SOURCES=external(或 nabla)可将实例限制为一个数据源组。
MCP 服务
packages/mcp 是一个 MCP 服务,默认使用 stdio,提供一个工具 read_subject(参数为 chainId、address)。它调用读取器上的
GET /read/:chainId/:address,并在一行链名称和截至区块后,原样返回 JSON。它只读:没有写入或签名工具,也不持有任何密钥。读取器 URL 通过 --reader-url 或 NABLAZ_READER_URL 指定,默认值为 http://localhost:42069。
pnpm --silent mcp # = cd packages/mcp && pnpm start
stdout 承载协议,因此启动时不要先运行任何会向 stdout 打印内容的命令(不带
--silent 的 pnpm 会打印横幅)。对于从 JSON 配置启动 stdio 服务的 MCP 客户端:
{
"mcpServers": {
"nablaz-read": {
"command": "node",
"args": ["--experimental-strip-types", "--disable-warning=ExperimentalWarning", "/path/to/nablaz-sdk/packages/mcp/src/main.ts"],
"env": { "NABLAZ_READER_URL": "http://localhost:42069" }
}
}
}
HTTP 传输
--transport http 会通过 Streamable HTTP 在 POST /mcp 上提供相同的服务和同一个 read_subject 工具。它是无状态的:没有会话、密钥、写入或签名路径。端口由 --port
或 PORT 决定(默认值为 3042)。进程会按客户端 IP 限制请求频率,每分钟上限由 MCP_RATE_LIMIT_PER_MIN 指定(默认值为 60);超限请求会收到状态码 429 和纯文本响应体。客户端 IP 是套接字对端地址,除非 MCP_CLIENT_IP_HEADER 指定了一个由前置代理覆盖的请求头(例如
X-Real-IP)。只能指定代理始终会设置的请求头:客户端可以发送的请求头,也可以由客户端伪造。GET /read/:chainId/:address 和 GET /ready 会在相同限制下转发给读取器,因此一个端口同时提供这两个入口:状态码低于 500 时原样返回;/ready 返回 503 时响应体为 {"ready":false};其他任何 5xx 响应体均为 {"error":"internal error"},读取器自身的响应体只会写入前端日志。只要前端已启动且读取器进程有响应,GET /live 就返回 200;否则返回 503,无论索引进度如何。没有 CORS 请求头,也没有计数或指标端点;其他所有路径均返回 404。
pnpm --silent mcp -- --transport http --port 3042
要将读取器和 HTTP MCP 服务作为一个实例一起运行,并读取完整历史记录,请使用以下环境变量。两个链都会被读取,每个来源都从其部署区块开始:
READER_ARBITRUM_RPC_URL=... # Arbitrum One RPC, archive: head tracking and state reads
READER_ARBITRUM_GROUNDING_RPC_URL=... # Arbitrum One RPC, archive, unlimited eth_getLogs range
READER_BASE_RPC_URL=... # Base RPC, archive
READER_BASE_GROUNDING_RPC_URL=... # Base RPC, archive, unlimited eth_getLogs range
PONDER_TELEMETRY_DISABLED=true
NABLAZ_READER_URL=http://localhost:42069
PORT=3042
MCP_RATE_LIMIT_PER_MIN=60
MCP_CLIENT_IP_HEADER= # the client-IP header the proxy in front overwrites; unset without one
请将 READER_EXTERNAL_FROM、READER_CHAINS、READER_SOURCES 和 READER_START_BLOCK 保持未设置状态。
在本地核实反馈归因和 (c) 占比
ERC-8004 来源属于第三方组,因此上述运行会对它们进行索引;设置 READER_EXTERNAL_FROM 后,它们会与其他第三方来源从同一区块开始。该区块之前设置的 agentWallet 不会被识别,读取结果会显示没有为其索引任何内容。接着:
curl localhost:42069/read/8453/<agentWallet> # grounding facts, cShare, notes
整个索引中、按代币统计、在一个区块范围内标记为 (c) 的支付事实占比(请先停止读取器:此脚本会打开其数据库):
pnpm report:c-share 8453 53160000 53160200 # READER_PGLITE_DIR defaults to packages/reader/.ponder/pglite
脚本会打印输入、每个代币一行,以及可复现该计数的 SQL。Arbitrum One 上的 Vault 支付通过其账户进行标记,因此需要 READER_ARBITRUM_RPC_URL。
技能
skills/read-record/SKILL.md 介绍如何调用该工具或 GET /read、如何读取各来源及其标签格式,以及读取器不会说明的内容。skills/pay-inside-grant/SKILL.md 介绍如何从受款人 Vault 付款。
测试
pnpm typecheck
pnpm test # unit
pnpm test:fork # needs anvil and cast (Foundry) and the RPC variables below
NABLAZ_PROTOCOL_DIR=/path/to/nablaz-protocol pnpm test # also checks the lineage copy against nablaz-protocol
读取器的分叉测试会读取 READER_ARBITRUM_RPC_URL(归档节点)、READER_BASE_RPC_URL 以及对应的两个 *_GROUNDING_RPC_URL;packages/sdk 的读取测试会读取 READER_ARB_RPC_URL(归档节点)。
分叉测试会运行 Arbitrum One 的 anvil 分叉,通过 TypeScript 使用 abis/ 中的 ABI 驱动线上合约,从头开始索引结果,并断言派生出的行。测试会索引两次,并要求表格完全一致。另有一项独立测试,会将 Arbitrum One 索引到第一个账户之前的区块,并要求所有表均为空。在两条链上,第三项测试会在一个固定窗口内索引第三方来源。它会通过 cast 对每个来源运行一条格式化后的一行命令,要求计数与读取器的行数一致,并打印标记为“无法确定”的抽样赎回占比。账户受款人测试会在链上工厂创建一个 NablaAccount,将其设为 TEST 账户中某项 grant 的受款人,并结算一笔 x402 支付,其受款人签名采用 PersonalSign 格式。MCP 测试会针对一个已提供服务的读取器运行 stdio 服务端,并将其输出与 GET /read 的结果进行比较。
x402
依赖最低版本为 @x402/* ≥ 2.23.0,这是 10 月 22 日 V12 披露问题的修复版本(客户端没有默认支出限额);本仓库在 packages/sdk/package.json 中固定了版本(2.28.0),支付限额由账户中的 grant 信封强制执行,而非由客户端执行。
来源
合约地址(src/generated/addresses.ts)和 ABI(abis/*.json)由固定的
nablaz-protocol 提交版本生成;每个生成文件的标头中都会记录该提交版本。读取器的
currentAccountImpl 沿袭关系、引导配置和 ProtocolConfig(packages/reader/src/generated/account-lineage.json)
复制自该提交版本及文件所记录的 blob 中的 script/expected/permanent-arbitrum.txt
(packages/reader/scripts/gen-lineage.ts)。只有地址和 ABI
来自该文件:二者均已在链上公开。
许可证
MIT。请参阅 LICENSE。
评论
一个开放协议,规定了 AI 应用如何以统一方式连接外部工具与数据源。客户端负责与模型对话,MCP 服务负责提供工具、资源和提示模板,两者通过标准消息通信,同一个服务可以被不同客户端复用。