---
name: rootz-node
description: Read Polygon chain data through the Rootz measured node at node.rootz.global — signed answers, a committed index that proves absence, and coverage refusals instead of false zeros. Use when asked about V6 data wallets, notes, identities, rivets, messages, revocation, or any Polygon read that needs to be verifiable rather than merely plausible.
---

# Reading the Rootz measured node

A Polygon full node behind a signing reverse proxy on a TPM-measured box. It answers
three kinds of question, and the third is the one nothing else can answer:

1. **Live chain reads** — standard JSON-RPC, byte-transparent to Bor.
2. **Signed answers** — the response is signed and bound to *your* request.
3. **Completeness** — "there are none" as a **proof**, not a promise.

Base: `https://node.rootz.global` · MCP: `POST /mcp` · no API key, ever.

## Start here — 30 seconds

```bash
curl -s https://node.rootz.global/healthz | jq
```

Returns `hardwareRooted`, `anchored`, the chain head, and **`index.coverage`** — how far
the committed index actually reaches per key space. Read `index.coverage` before you
believe any index answer: **coverage is the answer's scope.**

Two more:

```bash
curl -s https://node.rootz.global/.well-known/rootz-node/claims | jq   # what it claims, SIGNED
curl -s https://node.rootz.global/index/keyspaces | jq                 # what can be asked
```

## THE THREE RULES — read these before your first query

**1. An empty result is not an answer.**
"No events" and "I have not indexed that range" are different, and this node tells you
which. A coverage refusal means **UNKNOWN**, never zero. Never report a zero from a query
you could not run.

**2. For an absence that matters, demand coverage.**
Pass `require_coverage_through`. Without it you can get an honest answer over a stale
range — "not revoked, as of six months ago" is honest and wrong. With it, the node
**refuses** rather than answering:

```
HTTP 400 {"ok":false,"error":"index covers only through block 80699999, and the caller
required 93800000 — refusing to report an absence over a stale range"}
```

**That refusal is the correct outcome. Report it as UNKNOWN, do not retry without the bound.**

**3. `absence_is_proved: false` means nothing was proved.**
Absence is proved by the two **bracketing neighbours** around the missing key. An empty
chunk has an all-zero Merkle root and proves nothing — the answer will carry
`absence_is_proved: false` and list `empty_chunks`. Do not read that as "none found".

## MCP — the fast path for an agent

`POST https://node.rootz.global/mcp`, streamable HTTP, stateless, no session needed.
Send header `accept: application/json, text/event-stream`.

Eight tools, measured live:

| tool | arguments |
|---|---|
| `node_attestation` | — |
| `polygon_head` | — |
| `v6_datawallet_notes` | `key`, `from_block`, `to_block`, `require_coverage_through?` |
| `v6_datawallet_byWriter` | same |
| `v6_datawallet_created` | same |
| `v6_identity_created` | same |
| `v6_messages` | same |
| `v6_rivets` | same |

**Call `node_attestation` first**, once per session, and keep the result. It states the
depth of every claim and what the node refuses to claim.

## What each key space answers

MCP tool names are on the left; the `key_space` string on the right is what the HTTP
`/index/query` door wants. They are the same six things.

| MCP tool | HTTP `key_space` | keyed on | replaces |
|---|---|---|---|
| `v6_datawallet_notes` | `rootz.v6.datawallet.notes` | data wallet address | Etherscan `logs/getLogs` on the wallet |
| `v6_datawallet_byWriter` | `rootz.v6.datawallet.byWriter` | writer address | **nothing — Etherscan cannot answer this without a full scan** |
| `v6_datawallet_created` | `rootz.v6.datawallet.created` | creator address | `getSecretsCreatedByWallet`, without internal-tx traces |
| `v6_identity_created` | `rootz.v6.identity.created` | creator address | Etherscan `account/txlist` filtering |
| `v6_messages` | `rootz.v6.messages` | sender address | `getWalletMessages` / `getIdentityMessages` |
| `v6_rivets` | `rootz.v6.rivets` | rivet address | `getIdentityPublicKey` |

Chunk size is 100,000 blocks everywhere. Queries are limited to **8 chunks (800,000
blocks)** per call — a wider range is refused with a `hint` telling you how to split it,
never silently truncated.

## HTTP, if you are not speaking MCP

```bash
# Live read — byte-transparent JSON-RPC
curl -s https://node.rootz.global/ -H 'content-type: application/json' \
  -d '{"jsonrpc":"2.0","id":1,"method":"eth_blockNumber","params":[]}'

# Same, but SIGNED: add one header, and the reply carries x-rootz-sig
curl -si https://node.rootz.global/ -H 'content-type: application/json' \
  -H 'x-rootz-sign: v1' \
  -d '{"jsonrpc":"2.0","id":1,"method":"eth_blockNumber","params":[]}'

# What is actually indexed, per key space
curl -s 'https://node.rootz.global/index/coverage?key_space=rootz.v6.datawallet.created'

# A completeness-committed index query, with a freshness bound
curl -s https://node.rootz.global/index/query -H 'content-type: application/json' -d '{
  "key_space": "rootz.v6.rivets",
  "key": "0xYOUR_ADDRESS",
  "from_block": 80600000,
  "to_block": 80699999,
  "require_coverage_through": 80699999
}'
```

## Reading an index answer

```jsonc
{
  "ok": true,
  "chunks": [ { "header": {...}, "proof": {...}, "signature": {...} } ],
  "meta": {
    "event_count": 0,
    "absence_is_proved": true,          // ← FALSE means nothing was proved
    "empty_chunks": [],                 // ← chunks with no keys at all
    "coverage_proven_through": 80699999,// ← the range this answer is an answer WITHIN
    "required_coverage_through": 80699999,
    "finality_basis": ["heimdall-checkpoint: final through 93852325 (checkpoint 111520 anchored to Ethereum)"],
    "read_class": "attestation"
  }
}
```

`finality_basis` says **why that range was safe to commit, and on whose authority.**
`heimdall-checkpoint` means the range is anchored to Ethereum's consensus, not to a depth
we chose. **`NONE — committed without a finality check` means the chunk predates
2026-09-15 and could have been committed across a reorg.** Treat those as weaker.

## Verifying a signed response

**Before you write verification code, reproduce the vectors:**

```bash
curl -s https://node.rootz.global/verify            # the exact preimage rule
curl -s https://node.rootz.global/verify/vectors.json   # frozen vectors
```

★ **The trap that has already caught one implementer:** `payload.hash` is **base64url**
and `certifiedKeysetDigest` is **hex** — the same algorithm, two encodings, and they look
nothing alike. Comparing one against the other looks exactly like a broken signature while
the crypto is fine. Also: for a JSON-RPC response the signed object is *not* the response —
it is `{rpc_body_bytes, rpc_body_sha256}` over the **raw wire bytes**, because parsing
chain data corrupts integers past 2⁵³.


Four headers ride on a signed reply — `x-rootz-sig`, `x-rootz-keyset`,
`x-rootz-certification`, `x-rootz-certification-signature`. Do not hand-roll the check:

```js
import { RootzNodeClient, READ_CLASS } from '@rootz/node-client';   // in rootz-v6
const pin = await new RootzNodeClient({ endpoint: 'https://node.rootz.global' }).pinFromWellKnown();
// PERSIST `pin`. Re-fetching it every startup is not pinning.
const c = new RootzNodeClient({ endpoint: 'https://node.rootz.global', rootPin: pin, signer });
const r = await c.call('eth_blockNumber', [], { readClass: READ_CLASS.ATTESTATION });
```

**Without a pinned root, a valid signature proves only that _some_ node signed.** Anyone
can stand up a binder and sign correctly.

`READ_CLASS.ATTESTATION` **refuses** on a verification failure; `LIVENESS` warns and
returns the value. A balance can be re-read; a provenance answer that failed to verify has
no value at all.

## Signing your request makes you faster

| tier | added delay | rate |
|---|---|---|
| unsigned | **750 ms** | 30/min |
| signed, unknown key | 250 ms | 120/min |
| signed, registered rivet | **0 ms** | 1200/min |

Identity here **removes** latency. There is no shared API key to leak — the signature *is*
the identity. Pass an ethers `Signer` to `RootzNodeClient` and you are on the fast path.

## What this node does NOT claim

Fetch the authoritative list — it is signed and it is the point:

```bash
curl -s https://node.rootz.global/.well-known/rootz-node/claims | jq '.does_not_claim'
```

Nine refusals, including: that the TPM is genuine hardware (it is a software vTPM with no
EK certificate), that the node was intact at *runtime* (PCRs are frozen at boot), that the
chain client is measured, that the chain data is *true* (that is consensus; this attests
custody of a read), that the exchange is confidential, and **that a revocation answer is
authoritative unless you passed `require_coverage_through`.**

## When something goes wrong

| what you see | what it means | what to do |
|---|---|---|
| `400 range spans N chunks, limit is 8` | too wide | split as the `hint` says; do not truncate |
| `400 ... refusing to report an absence over a stale range` | the index does not reach your bound | report **UNKNOWN**. Do **not** retry without the bound |
| `400 unknown key space "..."` | typo | `GET /index/keyspaces` |
| `absence_is_proved: false` | no bracketing neighbours | **not** "none found" |
| `502 upstream unreachable` | Bor is down | an error, never a fabricated answer |
| `429` with `retry-after` | rate limited | sign your request and get 40× the budget |
| a slow first query on a fresh range | the node is scanning 100k blocks live | check `/healthz` → `index.coverage`; it is building, not hung |

## Honest current limits

- **The index is still catching up.** It builds in the background at roughly 1.3
  chunks/minute and started from block 80,600,000. Check `index.coverage` on `/healthz`
  rather than assuming a range is present.
- `getTransactionByHash` / `getTransactionReceipt` return **`null`, not an error**, for
  anything older than ~57 days — the transaction index is pruned. A null there is a
  **silent gap**, not "not found".
- Historical *state* is pruned. Logs and block bodies are retained.
- One node, so no quorum. A node that lies consistently produces a consistent index.

---
Full technical page: **https://node.rootz.global** · this skill: `GET /skill`
