Reader

io.nablaz由 nablaz.io 发布官方发布者v0.1.0更新于 Nov 7, 2026

Read NablaZ's open record: agent actions under permission, cited onchain. Evidence, not a verdict.

已验证Streamable HTTP可网页运行数据库金融安全与监控

概览

AI 生成的概览

读取某地址在 Arbitrum One 与 Base 上的链上支付权限记录,返回其被允许支出什么、实际支出了什么的可引用事实。

可以做什么
  • 读取某地址在某条链上所有已索引事实,每条含来源、权限标签及区块、交易、日志索引引用
  • 查看来自 Coinbase、MetaMask 与 NablaZ 来源的消费权限、已兑换委托与支付
  • 查看 NablaZ 账户的记录条目、债务、金库结算、实现变更历史与交易对手
  • 查看 ERC-8004 反馈依据、代理钱包与所有者,以及按代币统计的不可判定权限占比
  • 获取无结果来源的说明与已覆盖的区块范围
适用场景
当你需要核查某个代理或地址在 Arbitrum One 或 Base 上被授权支出什么、实际支付了什么,并希望证据可自行复现时使用。适合审计、尽职调查与代理权限复核,而非用于发起支付。
运行要求
托管端点无需账户、密钥或登录,作为自定义连接器地址添加即可。本地运行需 Node 22.12+ 与 pnpm,四个 RPC 地址变量(Arbitrum One 与 Base 各含一个 grounding RPC),部分状态读取还需归档 RPC。
安装前请注意
只读且无需密钥,没有写入或签名工具。托管读取按客户端 IP 限流。部分字段依赖归档 RPC,可能被标为不可判定。它只陈述事实而非结论;空结果表示所覆盖范围内无索引,并不代表不存在。反馈的数值、标签与 URI 被有意不读取。

安装

在 SourceWeft 中

  1. 打开 控制台中的 Reader,将其添加到工作区。
  2. 为需要使用其工具的对话启用该服务。

Web executable,通过 Streamable HTTP。 远程服务在工作区中配置后即可从网页运行时运行。

交给智能体安装

把这段提示词粘贴到 Claude Code、Codex、Cursor 等智能体,或 SourceWeft 对话中。智能体会读取该服务器的安装指南,先向你展示发布者、端点和登录方式,经你同意后再把它添加到你的客户端。密钥不会写进任何命令。

阅读 https://sourceweft.com/mcp/io.nablaz%2Freader/install.md,并按其中说明把这个 MCP 服务器接入我的客户端。修改任何配置之前,先告诉我它的发布者、端点和登录方式,等我确认。不要把 API 密钥写进命令或我会提交的文件;问我密钥应从哪里获取。

查看智能体所依据的安装指南

其他 MCP 客户端

把它添加到你客户端的 mcpServers 配置中。

{
  "mcpServers": {
    "reader": {
      "type": "http",
      "url": "https://read.nablaz.io/mcp"
    }
  }
}

README

nablaz-sdk

An open-source reader for agent payment permissions. It rebuilds, from public chain data, what an address was allowed to spend, what it spent, and under which terms, and keeps every fact next to the command that reproduces it. It reads the NablaZ record and grantee vaults, Coinbase spend permissions, MetaMask delegations and Agent Credit Lines.

It holds no keys, needs no account or API key, and runs anywhere you have an RPC endpoint.

What it reads

Arbitrum One and Base are both enabled. The NablaZ contracts are live on Arbitrum One only, so on Base the reader indexes the third-party sources alone.

Third-party sources, on both chains:

  • Coinbase SpendPermissionManager: SpendPermissionApproved, SpendPermissionUsed, SpendPermissionRevoked.
  • MetaMask DelegationManager (delegation framework v1.3.0): RedeemedDelegation, with every caveat decoded. For each redemption the reader reads the transaction receipt and keeps the ERC-20 Transfers sent by the delegator. A redemption alone is not a payment; only such a Transfer is.
  • Agent Credit Lines (Borrowed, Repaid, Defaulted) at a configured lender. No lender is known on either chain today, so no lender is registered. The decoder ships with tests, and a read with nothing indexed says "nothing indexed" with its block range.
  • ERC-8004: NewFeedback from the "Reputation Registry" (who rated which agentId, and where; never the feedback's value, decimals, tags, endpoint or URIs), and the agentWallet and owner histories from the Identity Registry's MetadataSet and ERC-721 Transfer. Addresses from src/generated/erc8004.json (vendor commit pinned there).

NablaZ sources, where deployed:

  • RecordEntry from NablaRecord, every field.
  • AccountCreated from NablaAccountFactoryPermanent and AccountRegistryV6, to discover accounts.
  • ERC-1967 Upgraded on every discovered account proxy.
  • USDC AuthorizationUsed and Transfer where the authorizer or sender is a grantee vault. Vaults are discovered from logs alone: the PAY_AUTHORIZED entry names the vault, and the funding Transfer in the same transaction confirms it.

What it writes

TableOne row per
record_entriesRecordEntry, raw
obligation_pairsOBLIGATION_OPENED / REPAY / OBLIGATION_CLOSED entry, grouped by obligationRef
vault_paymentssettlement: a vault's AuthorizationUsed paired with its Transfer, and the grant and envelope it drew against
implementation_historyUpgraded log or IMPLEMENTATION_CHANGED entry, per account
counterpartiesdistinct (subject, counterparty) pair, with first evidence and counts
accounts, grants, vaults, vault_fundingsdiscovery and pairing state the tables above are built from
factory_accountsaccount created on the permanent factory, with the owner from the factory's own event
external_permissionsspend permission or redeemed delegation, with its shape, label and decoded terms
external_paymentsSpendPermissionUsed, or an ERC-20 Transfer from the delegator in a redemption's transaction
external_redemptionsRedeemedDelegation, whether or not it moved value
credit_line_eventsBorrowed / Repaid / Defaulted at a configured lender
feedbacksERC-8004 NewFeedback: agentId, rater, feedback index; no value, tags or URIs
agent_walletsERC-8004 MetadataSet for the agentWallet key: agentId and wallet (null when cleared)
agent_ownersERC-8004 identity Transfer (ERC-721): agentId and new owner

Rows hold facts and counts. The reader states facts and passes no judgement on any subject.

In vault_payments, the party that submitted the settlement transaction (tx.from) is recorded as submitter. It is never treated as the counterparty; the counterparty is the Transfer recipient.

A settlement writes no NablaRecord entry. A payment's record is two parts, paired by the reader: the PAY_AUTHORIZED entry the account wrote when it reserved funds into the grantee vault (the reservation, in record_entries), and USDC's own AuthorizationUsed and Transfer events (the settlement, one vault_payments row). A citation of a payment cites both: the PAY_AUTHORIZED log (block, transaction hash, log index) and the USDC settlement transaction and its block.

Citation rule

The logs are the ground truth; this reader is a convenience over them. Every row carries the chain id, block number, transaction hash and log index of its evidence, plus a cast logs command and the equivalent raw eth_getLogs request that return that log from any RPC. A row you cannot reproduce from its command is a bug.

One field is a state read, not a log: the envelope expiry in vault_payments. It is labelled "state read — needs archive RPC". The reader calls envelopeOf(account, granteeKey, token) on the account's mandate at block N−1 and at the settlement block N, and stores both calls as cast call --block commands. If the two reads agree, the value is stored. If they differ, the envelope changed within block N, and the field is stored as "not determinable" with both reads cited. On 2026-10-23, the same historical eth_call at 50,000 blocks below head answered 6 of 10 times through https://arb1.arbitrum.io/rpc, and the other 4 returned "historical state … is not available", so a public endpoint cannot be relied on for this field. Everything else rebuilds from logs on any RPC.

Read

GET /read/:chainId/:address returns every indexed fact about an address. No key is needed: the endpoint is a read over public logs.

json
{ "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: one per record entry, vault payment, spend permission, delegation, payment, redemption and credit-line event. Each names its source and carries a permission label, the block, transaction and log index, and the one-line command that returns the same log from any RPC.
  • selfDealing (NablaZ accounts only): distinct counterparties paid, share of value paid to accounts under the same controller, share paid to counterparties with no record, and the sentence "A payee controlled by the same person under another key cannot be detected from chain data."
  • exposure (NablaZ accounts only): per grant and token, the least remaining allowance across windows plus the vault balance, with the limit that outstanding authorizations are not observable onchain.
  • cShare: per token, the count and value of this read's payment facts labelled (c), over all of them; never summed across tokens.
  • notes: every source that returned nothing ("nothing indexed (blocks X..Y)", or "indexed up to X; source starts at Y" while the indexer is below the source's start), and every fact that is not determinable, stated as text.

Every permission label has one of five shapes:

ShapeLabelWhere it comes from
(a)allowance, per period / total / per call / per execution / streamedCoinbase spend permissions; MetaMask amount enforcers; NablaZ CAM V4 windows ("within mandate by construction" for a canonical NablaZ account)
(b)exact execution, once / N times / count unboundedMetaMask exact-call enforcers; the count from LimitedCallsEnforcer or a single-use enforcer
(c)not determinablean enforcer in no vendor list, terms that do not decode, a non-canonical NablaZ account
(d)no limit on amount or calla delegation whose caveats only restrict the call or the redeemer
(e)third-party attestation, writer namedAgent Credit Lines; record entries another writer placed; ERC-8004 feedback, the rater named

The MetaMask enforcer table is generated from the vendor's Deployments.md at a pinned commit (scripts/gen-enforcers.ts, src/generated/enforcers.json). A test fails when the vendor's main branch lists an enforcer the table lacks.

One-liners are the ground truth. The reader is a convenience over the logs. Each reproduce field is a cast command you can run yourself with an RPC URL in $ARB_ONE_RPC_URL or $BASE_RPC_URL.

Feedback grounding

When the subject has been the agentWallet or the owner of an ERC-8004 agentId, every NewFeedback about that agentId becomes an erc8004:feedback_grounding fact (R-D2B-2). A feedback is grounded when an ERC-20 Transfer from the rater to the identity's payment address settled before it (by block, then by log index within a block), whatever the token or route (R-S191-11). The payment address is resolved at the transfer's block, never later: getAgentWallet there, else ownerOf there, and the fact says which binding applied (R-S191-16). A transfer made before the identity existed does not ground ("identity did not exist at the payment block"). The wallet at the feedback block is reported beside the grounding ("wallet at feedback:

" or "wallet at feedback: cleared") and is never the test (R-S191-12).

Plain transfers are not indexed. At read time the reader calls eth_getLogs with topic0 = Transfer and topic1 = the rater, any token address, from the Identity Registry's start block (or the instance's start) to the feedback block, and states that range in the fact. Candidates are the addresses in the identity's agentWallet history (MetadataSet) and owner history (ERC-721 Transfer), each confirmed at the transfer's block. A settlement from the rater's own grantee vault is a payment from the rater (R-S191-17). Native-token transfers are not read ("native-token transfers not read", R-S191-18). A transfer that is one of the reader's payment facts keeps its permission label; any other is labelled (c) with reason "direct transfer, no permission object". The fact also says whether the rater is the subject, and whether the two share a controller when both are NablaZ accounts. It carries no value, decimals, tags or URIs, and the feedback file is not fetched ("feedbackURI not read").

test/fork/afg.fork.test.ts runs this grounding on the 816 grounded feedback entries in Agentic Finance Graph's Base fixture (CC BY 4.0, see NOTICE) and writes the comparison to docs/reader/afg-agreement-2026-10-20.csv. docs/reader/retroactive-binding-2026-10.md describes the remaining disagreements.

Hosted read

https://read.nablaz.io runs the reader and the MCP server below as one instance, on both home chains (chain IDs 42161 and 8453), with every source indexed from its deployment block. It is keyless and reads only. It has two doors:

  • GET https://read.nablaz.io/read/:chainId/:address: the JSON a local GET /read returns.
  • POST https://read.nablaz.io/mcp: the MCP server's Streamable HTTP transport, with its one tool, read_subject. For an MCP client that takes a URL: { "type": "http", "url": "https://read.nablaz.io/mcp" }.

Both doors are rate-limited per client IP; a request over the limit gets 429 with a plain-text body. Beside them, GET /live (the process is up) answers with a status code and a short text, and GET /ready (the index has caught up) answers HTTP 200 with an empty body when ready, and 503 with {"ready":false} when not; nothing else is served: no status, metrics or counts endpoint. Reads are taken at the indexed block and cite the contracts' own logs and state, so each fact can be checked with its one-liner against an RPC of your own. The deployment is fly.toml, the Dockerfile and scripts/reader-start.sh; docs/runbooks/hosted-read.md lists the variables it reads, the two health paths and the database backup schedule.

Using the hosted reader from Claude

Read NablaZ's open record: agent actions under permission, cited onchain. Evidence, not a verdict.

Setup. In Claude, Settings → Connectors → Add custom connector → https://read.nablaz.io/mcp → No sign-in (the server is keyless). The same read is available over plain HTTP: GET https://read.nablaz.io/read/{chainId}/{address}.

Examples.

Example 1 — read a subject's record. Prompt: "Using NablaZ Reader, what's on the record for 0x8E65fa412e14c1859a9a278fdEFDE89A3c15b11c on chain 42161?" What happens: read_subject calls GET /read/42161/<address> and returns the indexed facts for that account; every fact carries its own block number and transaction citation, and a payment cites both halves — the authorization entry and the settlement transaction.

Example 2 — the permission behind each payment. Prompt: "What permission was each of those payments made under?" What happens: each payment fact carries a permission label by the shape of its terms (exact execution, allowance, no limit, not determinable, or third-party attestation) and a source label naming the permission system it came from.

Example 3 — a subject with nothing on record. Prompt: "What's on the record for 0x0000000000000000000000000000000000051900 on chain 42161?" What happens: the reader answers "nothing indexed" together with the block range it has covered — an empty read states its coverage; it never asserts absence.

Privacy. The reader is keyless and holds no user accounts; it serves public onchain data and logs only what operating the service requires. Full policy: https://nablaz.io/privacy

Support. GitHub issues on this repository, or [email protected].

Running it locally

Everything below runs on your machine: the reader, the MCP server and the skill. Requires Node 22.12+ and pnpm. From a clean checkout:

sh
pnpm install --frozen-lockfileexport READER_ARBITRUM_RPC_URL=...             # Arbitrum One RPC; archive for the envelope and exposure readsexport READER_ARBITRUM_GROUNDING_RPC_URL=...   # Arbitrum One RPC for backfill eth_getLogs and grounding readsexport READER_BASE_RPC_URL=...                 # Base RPCexport READER_BASE_GROUNDING_RPC_URL=...       # Base RPC for backfill eth_getLogs and grounding readsexport PONDER_TELEMETRY_DISABLED=trueexport READER_EXTERNAL_FROM=42161:518780000,8453:53160000   # optional; see belowpnpm reader                          # = cd packages/reader && pnpm start --schema reader

Each chain takes two RPC URLs, one per role, and both are required; one URL may serve both roles. The first carries head tracking and the state reads. The second carries every eth_getLogs over a block range (the backfill, and the grounding reads), so it should accept unlimited ranges. Head-tracking calls fall back to it only when the first URL fails; the grounding reads never use the first.

The reader serves on localhost:42069. Once /ready answers 200 it has caught up, and reads are taken at the indexed block:

sh
curl localhost:42069/readycurl localhost:42069/read/42161/0x8E65fa412e14c1859a9a278fdEFDE89A3c15b11c

Tables land in a local PGlite database under packages/reader/.ponder/. RPC URLs are read from the environment only and are never written to the repository. With the READER_EXTERNAL_FROM line above, a first run took about ten minutes to catch up on both chains (Oct 27 2026).

The third-party sources start at their deployment blocks, which is years of history. READER_EXTERNAL_FROM starts them later, per chain. Facts before that block are then not indexed, and the notes in each read state the range read. READER_CHAINS=8453 limits the instance to one chain; READER_SOURCES=external (or nabla) to one source group.

MCP server

packages/mcp is an MCP server, on stdio by default, with one tool, read_subject (chainId, address). It calls GET /read/:chainId/:address on the reader and returns the JSON unchanged, after a line naming the chain and the as-of block. It reads only: there is no write or signing tool, and it holds no key. The reader URL comes from --reader-url or NABLAZ_READER_URL, and defaults to http://localhost:42069.

sh
pnpm --silent mcp                    # = cd packages/mcp && pnpm start

stdout carries the protocol, so launch it without anything that prints to stdout first (pnpm without --silent prints a banner). For an MCP client that launches stdio servers from a JSON config:

json
{  "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

--transport http serves the same server, with the same single read_subject tool, over Streamable HTTP at POST /mcp. It is stateless: no session, no key, no write or signing path. The port comes from --port or PORT (default 3042). Requests are limited per client IP in process, MCP_RATE_LIMIT_PER_MIN per minute (default 60), and a request over the limit gets 429 with a plain-text body. The client IP is the socket peer, unless MCP_CLIENT_IP_HEADER names a header that a proxy in front overwrites with it (for example X-Real-IP). Name only a header the proxy always sets: a header the client can send is a header the client can spoof. GET /read/:chainId/:address and GET /ready are relayed to the reader under the same limit, so one port serves both doors: below 500 unchanged; /ready at 503 as {"ready":false}; any other 5xx as {"error":"internal error"}, the reader's own body going only to the front's log. GET /live answers 200 while the front is up and the reader process answers, 503 otherwise, whatever the index progress. There are no CORS headers and no counts or metrics endpoint; every other path returns 404.

sh
pnpm --silent mcp -- --transport http --port 3042

To run the reader and the HTTP MCP server together as one instance over full history, use this environment. Both chains are read, and every source starts at its deployment block:

sh
READER_ARBITRUM_RPC_URL=...             # Arbitrum One RPC, archive: head tracking and state readsREADER_ARBITRUM_GROUNDING_RPC_URL=...   # Arbitrum One RPC, archive, unlimited eth_getLogs rangeREADER_BASE_RPC_URL=...                 # Base RPC, archiveREADER_BASE_GROUNDING_RPC_URL=...       # Base RPC, archive, unlimited eth_getLogs rangePONDER_TELEMETRY_DISABLED=trueNABLAZ_READER_URL=http://localhost:42069PORT=3042MCP_RATE_LIMIT_PER_MIN=60MCP_CLIENT_IP_HEADER=                # the client-IP header the proxy in front overwrites; unset without one

Leave READER_EXTERNAL_FROM, READER_CHAINS, READER_SOURCES and READER_START_BLOCK unset.

Feedback grounding and the (c) share, locally

The ERC-8004 sources are part of the third-party group, so the run above indexes them; with READER_EXTERNAL_FROM, they start at the same block as the other third-party sources. An agentWallet set before that block is not seen, and the read says nothing indexed for it. Then:

sh
curl localhost:42069/read/8453/<agentWallet>   # grounding facts, cShare, notes

The share of payment facts labelled (c) over the whole index, per token, over a block range (stop the reader first: the script opens its database):

sh
pnpm report:c-share 8453 53160000 53160200   # READER_PGLITE_DIR defaults to packages/reader/.ponder/pglite

It prints its inputs, one line per token, and the SQL that reproduces the count. Vault payments on Arbitrum One are labelled through their account, which needs READER_ARBITRUM_RPC_URL.

Skills

skills/read-record/SKILL.md explains how to call the tool or GET /read, how to read each source and label shape, and what the reader never says. skills/pay-inside-grant/SKILL.md covers paying from a grantee vault.

Tests

sh
pnpm typecheckpnpm test          # unitpnpm test:fork     # needs anvil and cast (Foundry) and the RPC variables belowNABLAZ_PROTOCOL_DIR=/path/to/nablaz-protocol pnpm test   # also checks the lineage copy against nablaz-protocol

The reader's fork tests read READER_ARBITRUM_RPC_URL (archive), READER_BASE_RPC_URL and their two *_GROUNDING_RPC_URL; packages/sdk's read READER_ARB_RPC_URL (archive).

The fork tests run an anvil fork of Arbitrum One, drive the live contracts from TypeScript using the ABIs in abis/, index the result from scratch, and assert the derived rows. They index twice and require identical tables. A separate test indexes Arbitrum One up to the block before the first account and requires every table to come back empty. On both chains, a third test indexes the third-party sources over a fixed window. It runs one rendered one-liner per source through cast, requires the same count as the reader's rows, and prints the share of sampled redemptions labelled "not determinable". The account-grantee test creates a NablaAccount on the live factory, makes it the grantee of a grant on the TEST account, and settles one x402 payment whose grantee signature is the PersonalSign shape. The MCP test runs the stdio server against a served reader and compares its output with GET /read.

x402

The dependency floor is @x402/* ≥ 2.23.0, the fix line of the Oct 22 V12 disclosure (no default spending limit in the client); this repo pins the version in packages/sdk/package.json (2.28.0), and the payment limit is enforced by the grant envelope in the account, not by the client.

Provenance

Contract addresses (src/generated/addresses.ts) and ABIs (abis/*.json) are generated from a pinned nablaz-protocol commit; each generated file records the commit in its header. The reader's currentAccountImpl lineage, bootstrap and ProtocolConfig (packages/reader/src/generated/account-lineage.json) are copied from script/expected/permanent-arbitrum.txt at the commit and blob the file names (packages/reader/scripts/gen-lineage.ts). Only addresses and ABIs come from there: both are public on chain.

License

MIT. See LICENSE.

来源:README.md,提交 87f63fa

工具

1

read_subject

Read indexed facts about an address

Returns every fact the reader has indexed about an address on one chain, as GET /read/:chainId/:address JSON: each fact names its source, carries a permission label of shape (a) to (e), and cites the block, transaction and log index with a command that returns the same log from any RPC. A note reading nothing indexed (blocks X..Y) covers only that block range, and does not mean nothing happened on chain.

输入参数address必填chainId必填

版本历史

1
  1. v0.1.0最新Nov 7, 2026

评分

已满足适用检查项的 100%

该评分衡量此收录条目的完整程度,依据注册中心条目、端点与公开来源计算得出。它不是对服务器本身的评价,也不表示服务器安全或当前可用:可连通性是带日期的检查结果,评分也不包含安装量或用户评价。

  • 近 30 天内可连通必需25 分
  • 已声明认证方式必需10 分
  • 已列出工具必需15 分
  • 描述必需15 分
  • 图标10 分
  • 文档15 分
  • 许可证10 分