
# Testril MCP — worked query/response examples

Testril answers on-chain metric questions for agents over the Model Context
Protocol (MCP). You pick a catalog **Function** (a metric template), **bind** it
to a concrete scope (token, wallet, chain), **materialize** a block window
(paying a quoted price), then **read** the values. An unpaid priced call returns
an MCP error holding x402 `PaymentRequired` plus the quote. The host repeats the
identical call with payment in `_meta["x402/payment"]`; the result carries
`_meta["x402/payment-response"]`.

Examples 1–36 are **real, historical captured requests and responses** from old
test runs. They are preserved as records and are not current integration
examples. Examples 37–46 are labeled illustrations of the 0.14.0 contract;
example 38 also records the read-only 0.14.0 discovery result observed on
2026-11-02. Trimming is marked with `…`.

**Notes**
- All amounts are **USDC** (6 decimals, so 1 atom = $0.000001).
- Wallet, contract and transaction addresses are shortened to the form `0x4c65…D999`.
- x402 payment payloads are signed client-side; signatures are never shown (`0x…sig…`).
- Prices come only from the server quote; callers never set a price.
- Examples 1–36 are historical captures from before Testril 0.9.0 and preserve
  their old numeric money values and MCP payment exchange as recorded. Those
  values are not current wire examples. In 0.9.0, ordinary money fields are
  `{"atoms":"23","usd":"$0.000023"}`; x402 `accepts[].amount` and NFT
  accounting remain bare atom strings. Current MCP clients attach x402 payment
  to the identical priced tool call; there is no separate payment tool or
  `payment_id` argument.

Key addresses referenced below:
- Payer (buyer) `0x4c65…D999` · receiver Safe (payTo) `0x4559…5e52`
- USDC `0x036C…CF7e` · batch-settlement escrow `0x4020…0003` · receiver authorizer `0x91b0…086b`

---

## A. Discovery & health

### 1. Open the MCP session (initialize)
The standard MCP handshake. The server reports its protocol version and a block of usage `instructions`.
```json
// Request
{ "jsonrpc": "2.0", "id": 1, "method": "initialize",
  "params": { "protocolVersion": "2025-06-18", "capabilities": {}, "clientInfo": { "name": "testril-mcp-direct", "version": "0.1" } } }
```
```json
// Response
{ "jsonrpc": "2.0", "id": 1, "result": {
    "protocolVersion": "2025-06-18",
    "capabilities": { "tools": {} },
    "serverInfo": { "name": "rmcp", "version": "3.1.4" },
    "instructions": "Testril answers on-chain metric questions for agents and builders: pick a catalog Function (template), bind it to concrete scope (token, wallet, chain, …), materialize a block window (pay if quoted), then read values. …" } }
```

### 2. List the tools (tools/list)
Testril exposes 11 tools. Schemas are omitted here for brevity.
```json
// Request
{ "jsonrpc": "2.0", "id": 2, "method": "tools/list" }
```
```json
// Response
{ "jsonrpc": "2.0", "id": 2, "result": { "tools": [
    { "name": "ask", "…": "…" },
    { "name": "bind", "…": "…" },
    { "name": "bind_prompt", "…": "…" },
    { "name": "claim_rewards", "…": "…" },
    { "name": "health_check", "…": "…" },
    { "name": "inspect", "…": "…" },
    { "name": "materialize", "…": "…" },
    { "name": "pay_quote", "…": "…" },
    { "name": "provenance", "…": "…" },
    { "name": "read", "…": "…" },
    { "name": "stop", "…": "…" } ] } }
```

### 3. Connectivity smoke test (health_check)
Confirms the process is up, which payment rail is active (`cdp` = live), and how many chains / functions / bound functions exist.
```json
// Request
{ "tool": "health_check" }
```
```json
// Response
{ "bound_functions": 21, "chains": [42161, 1, 84532], "functions": 3,
  "ok": true, "outcome": "success", "payment": "cdp",
  "transport": "http", "version": "0.1.3" }
```

### 4. Price list (inspect pricing)
Every priced unit and its per-unit price, in USD. Reads and materializes are billed from these rates.
```json
// Request
{ "tool": "inspect", "set_args": { "subject": "pricing" } }
```
```json
// Response
{ "subject": "pricing", "outcome": "success", "rates": [
    { "label": "rpc_ops",          "unit_price": 0.0 },
    { "label": "llm_tokens",       "unit_price": 0.0001 },
    { "label": "materialize_blocks","unit_price": 0.00012 },
    { "label": "query_read",       "unit_price": 0.00002 },
    { "label": "query_blocks",     "unit_price": 0.000001 },
    { "label": "extra_cache_days", "unit_price": 0.000001 } ] }
```

---

## B. Bind → quote → pay → deliver (the core loop, live CDP rail)

This section and the payment sections that follow are preserved captures of the
retired pre-0.9.0 exchange. Their separate `pay_quote` calls, `payment_id`
retries, numeric money fields, and shown network are historical evidence, not
instructions for a current client. Current clients retry the same priced call
with x402 payment in `_meta["x402/payment"]` and read ordinary money from
`{atoms, usd}`.

### 5. Bind a Function to a concrete scope (bind)
Turns the `erc20_balance` template into a content-addressed **Bound Function** for one token + wallet on one chain. Binding is free.
```json
// Request
{ "tool": "bind", "set_args": {
    "function_slug": "erc20_balance",
    "chain_id": 1,
    "params": { "token_address": "0xA0b8…eB48", "wallet_address": "0x88e6…5640" } } }
```
```json
// Response
{ "outcome": "success", "created": true,
  "bound_function_id": "sha256:47ff…74c0",
  "chain_id": 1, "function_slug": "erc20_balance",
  "name": "erc20_balance@1:0xA0b8…eB48:0x88e6…5640",
  "params": { "token_address": "0xA0b8…eB48", "wallet_address": "0x88e6…5640" } }
```

### 6. Inspect a chain and a Bound Function (inspect)
Reads the observed chain head and the Bound Function's already-materialized block ranges (what you don't need to pay for again). Free.
```json
// Request
{ "tool": "inspect", "set_args": { "subject": "bound_function",
    "bound_function_id": "sha256:18ec…a097" } }
```
```json
// Response
{ "subject": "bound_function", "outcome": "success",
  "bound_function_id": "sha256:18ec…a097",
  "chain_id": 42161, "function_slug": "erc20_transfer_volume",
  "name": "erc20_transfer_volume@42161:0xaf88…5831",
  "params": { "chain_id": 42161, "token_address": "0xaf88…5831" },
  "data_endpoints": {
    "data": "https://dev.testril.ai/v1/bound_functions/sha256:18ec…a097/data" },
  "materialized": [
    { "from_block": 510282412, "to_block": 510282427, "cache_expiry_time": "1790844971" },
    { "from_block": 510316625, "to_block": 510316825, "cache_expiry_time": "1790855041" },
    "…" ],
  "materializing": [], "active_jobs": [] }
```

### 7. Ask to materialize a window → a priced quote (materialize → payment_required)
Requesting work you haven't paid for returns `outcome: "payment_required"` with the line items and an `accepts` menu of ways to pay. This is normal, not an error.
```json
// Request
{ "tool": "materialize", "set_args": {
    "bound_function_id": "sha256:18ec…a097",
    "from_block": 510271000, "to_block": 510271090, "blocks": 90 } }
```
```json
// Response
{ "outcome": "payment_required",
  "quote_id": "quote:01a0f618-de6d-7382-87d9-2545635a6570",
  "amount": 0.0108,
  "lines": [ { "label": "materialize_blocks", "units": 90, "unit_price": 0.00012, "amount": 0.0108 } ],
  "accepts": [
    { "scheme": "exact", "network": "eip155:84532",
      "asset": "0x036C…CF7e", "payTo": "0x4559…5e52", "amount": "10800",
      "extra": { "assetTransferMethod": "eip3009", "name": "USDC", "version": "2" } },
    { "scheme": "batch-settlement", "network": "eip155:84532",
      "asset": "0x036C…CF7e", "payTo": "0x4559…5e52", "amount": "10800",
      "extra": { "name": "USDC", "receiverAuthorizer": "0x91b0…086b", "version": "2", "withdrawDelay": 3600 } } ] }
```

### 8. The x402 challenge, explained
In this historical capture, the `accepts` array is the x402 payment challenge.
Each row is one acceptable payment: a `scheme` (`exact` = a single EIP-3009
USDC transfer; `batch-settlement` = a signed voucher against an escrow channel),
the `network` (CAIP-2), the `asset` and `amount` in atoms, and who to pay
(`payTo`). The captured client picked one row, signed the matching payload, and
submitted it with the now-retired `pay_quote` tool. Current clients use the
same-call `_meta["x402/payment"]` retry described above.

### 9. Pay the quote (pay_quote, exact scheme) → settlement receipt
The client signs an x402 `exact` payload (a USDC EIP-3009 authorization) and submits it. The response binds a `payment_id` to the quote and returns a receipt whose `reference` is the on-chain settlement tx hash.
```json
// Request  (signed payload omitted)
{ "tool": "pay_quote", "set_args": {
    "quote_id": "quote:01a0f618-de6d-7382-87d9-2545635a6570",
    "payload": "0x…sig…" } }
```
```json
// Response
{ "outcome": "success",
  "payment_id": "payment:01a0f618-de6d-7382-87d9-2545635a6570",
  "quote_id": "quote:01a0f618-de6d-7382-87d9-2545635a6570",
  "amount": 0.0108,
  "receipt": { "provider": "cdp", "state": "paid", "amount": 0.0108,
    "quote_id": "quote:01a0f618-de6d-7382-87d9-2545635a6570",
    "reference": "0x8e47…43a1" } }
```
On-chain, that settlement moved **10800 atoms ($0.0108) of USDC** from the payer `0x4c65…D999` to the Safe `0x4559…5e52`; the facilitator paid the gas.

### 10. Run the work with the receipt (materialize with payment_id only)
After paying, re-call the **same verb** with only `payment_id`. The job is queued and runs asynchronously.
```json
// Request
{ "tool": "materialize", "set_args": { "payment_id": "payment:01a0f618-de6d-7382-87d9-2545635a6570" } }
```
```json
// Response
{ "outcome": "success", "result": "materialized", "regime": "async",
  "job": { "job_id": "job:37", "quote_id": "quote:01a0f618-…-5a6570",
           "blocks_total": 90, "state": "queued" } }
```

### 11. Poll the job (inspect subject=job)
```json
// Request
{ "tool": "inspect", "set_args": { "subject": "job", "job_id": "job:37" } }
```
```json
// Response
{ "subject": "job", "outcome": "success", "state": "done",
  "job_id": "job:37", "blocks_done": 90, "blocks_total": 90,
  "bound_function_id": "sha256:18ec…a097",
  "from_block": 510271000, "to_block": 510271090, "spent": 0.0108 }
```

### 12. Ask to read values → a priced quote (read → payment_required)
Reading a window prices two lines: a fixed `query_read` plus a per-block `query_blocks`. Here 1 read over 10 blocks = $0.00003 (30 atoms).
```json
// Request
{ "tool": "read", "set_args": {
    "bound_function_id": "sha256:18ec…a097",
    "from_block": 510603582, "to_block": 510603592 } }
```
```json
// Response
{ "outcome": "payment_required",
  "quote_id": "quote:01a0f6ca-d6bf-7d72-add2-d45ac59f8dfa",
  "amount": 0.00003,
  "from_block": 510603582, "to_block": 510603592,
  "bound_function_id": "sha256:18ec…a097",
  "lines": [
    { "label": "query_read",   "units": 1,  "unit_price": 0.00002,  "amount": 0.00002 },
    { "label": "query_blocks", "units": 10, "unit_price": 0.000001, "amount": 0.00001 } ],
  "accepts": [ { "scheme": "batch-settlement", "network": "eip155:84532",
      "asset": "0x036C…CF7e", "payTo": "0x4559…5e52", "amount": "30",
      "extra": { "name": "USDC", "receiverAuthorizer": "0x91b0…086b", "version": "2", "withdrawDelay": 3600 } } ] }
```

### 13. Read the data (read with payment_id only) — the data is the answer
After paying that read quote, re-call `read` with the `payment_id`. The paid result is the metric itself; there is no second bill.
```json
// Request
{ "tool": "read", "set_args": { "payment_id": "payment:01a0f6ca-d6bf-7d72-add2-d45ac59f8dfa" } }
```
```json
// Response
{ "outcome": "success",
  "bound_function_id": "sha256:18ec…a097",
  "from_block": 510603582, "to_block": 510603592,
  "fields": [ { "name": "count", "type": "u256" }, { "name": "volume", "type": "u256" } ],
  "values": [
    { "block": 510603582, "entity": "0xaf88…5831", "count": "3", "volume": "1181594340" },
    { "block": 510603584, "entity": "0xaf88…5831", "count": "2", "volume": "26179016" },
    { "block": 510603585, "entity": "0xaf88…5831", "count": "5", "volume": "146701109" },
    { "block": 510603589, "entity": "0xaf88…5831", "count": "4", "volume": "14786922" },
    "…" ] }
```

### 14. Attest the inputs (provenance)
`provenance` returns the exact block hashes behind a materialized attribute, so a result can be independently verified.
```json
// Request
{ "tool": "provenance", "set_args": {
    "bound_function_id": "sha256:47ff…74c0", "attribute": "balance",
    "from_block": 26096269, "to_block": 26096369 } }
```
```json
// Response
{ "attribute": "balance", "block_count": 100, "blocks": [
    { "number": 26096269, "block_hash": "0x3d5d…2084" },
    { "number": 26096270, "block_hash": "0x37ce…53f4" },
    "…" ] }
```

---

## C. NFT reward card (purchases, rewards, refunds, claims)

Every paid purchase is recorded to a claim-right "NFT" P&L card keyed by wallet. The card tracks lifetime `paid` / `refunded` / `cost` / `earned` / `claimed` / `unclaimed` / `pnl` (all in atoms) and lists each purchase.

### 15. Inspect the P&L card (inspect subject=nft)
```json
// Request
{ "tool": "inspect", "set_args": { "subject": "nft", "wallet": "0x0000…0000" } }
```
```json
// Response  (purchases trimmed)
{ "subject": "nft", "outcome": "success", "nft": "1", "wallet": "0x0000…0000",
  "paid": "124830", "refunded": "2520", "cost": "122310",
  "earned": "208", "claimed": "0", "unclaimed": "208", "pnl": "-122102",
  "purchases": [
    { "bound_function_id": "sha256:18ec…a097", "quote_id": "quote:9b40…2cf6",
      "paid": "1800", "refunded": "0", "cost": "1800",
      "requested_window": { "from_block": 510309176, "to_block": 510309191 } },
    { "bound_function_id": "sha256:18ec…a097", "quote_id": "quote:4485…83d5",
      "paid": "1830", "refunded": "1800", "cost": "30",
      "requested_window": { "from_block": 510343589, "to_block": 510343604 } },
    "…" ] }
```

### 16. Buy a window (sub-cent) via a batch-settlement voucher
A 15-block materialize costs $0.0018 (1800 atoms) — below the $0.01 floor, so only `batch-settlement` is offered. Paying it signs a voucher against the escrow channel; the receipt `reference` is the channel id.
```json
// Request (quote)
{ "tool": "materialize", "set_args": {
    "bound_function_id": "sha256:18ec…a097", "from_block": 510603580, "to_block": 510603595 } }
```
```json
// Response (quote)
{ "outcome": "payment_required", "quote_id": "quote:01a0f6ca-93dc-7b42-9c9c-2a9b0ec91961",
  "amount": 0.0018,
  "lines": [ { "label": "materialize_blocks", "units": 15, "unit_price": 0.00012, "amount": 0.0018 } ],
  "accepts": [ { "scheme": "batch-settlement", "network": "eip155:84532",
    "asset": "0x036C…CF7e", "payTo": "0x4559…5e52", "amount": "1800",
    "extra": { "name": "USDC", "receiverAuthorizer": "0x91b0…086b", "version": "2", "withdrawDelay": 3600 } } ] }
```
```json
// Request (pay — signed voucher omitted)
{ "tool": "pay_quote", "set_args": { "quote_id": "quote:01a0f6ca-93dc-7b42-9c9c-2a9b0ec91961", "payload": "0x…sig…" } }
```
```json
// Response (pay)
{ "outcome": "success", "amount": 0.0018,
  "payment_id": "payment:01a0f6ca-93dc-7b42-9c9c-2a9b0ec91961",
  "quote_id": "quote:01a0f6ca-93dc-7b42-9c9c-2a9b0ec91961",
  "receipt": { "provider": "cdp", "state": "paid", "amount": 0.0018, "reference": "0xdf9f…783f" } }
```

### 17. The purchase is credited to the card immediately (demo/mock rail)
Right after paying, the new purchase appears on the P&L card with its `quote_id` and window — no claim step needed to record it.
```json
// Request
{ "tool": "inspect", "set_args": { "subject": "nft", "wallet": "0x0000…0000" } }
```
```json
// Response  (lifetime totals + the just-added line; others trimmed)
{ "subject": "nft", "outcome": "success", "nft": "1", "wallet": "0x0000…0000",
  "paid": "2020350", "refunded": "30960", "cost": "1989390",
  "earned": "912", "claimed": "0", "unclaimed": "912", "pnl": "-1988478",
  "purchases": [ "…",
    { "bound_function_id": "sha256:18ec…a097", "quote_id": "quote:01a0f729-b68c-7fc0-b812-e116ae414102",
      "paid": "1800", "refunded": "0", "cost": "1800",
      "requested_window": { "from_block": 509988008, "to_block": 509988023 } } ] }
```

### 18. Paid reads earn rewards (demo/mock rail)
Each paid read earns rewards at **0.5 atoms per block**. After a paid 10-block read the card's `earned`/`unclaimed` rise by 5 (912 → 917); a 100-block read earns +50, a 2-block cross-boundary read +1.
```json
// Request
{ "tool": "inspect", "set_args": { "subject": "nft", "wallet": "0x0000…0000" } }
```
```json
// Response  (totals; purchases trimmed)
{ "subject": "nft", "outcome": "success", "nft": "1", "wallet": "0x0000…0000",
  "paid": "2020350", "refunded": "30960", "cost": "1989390",
  "earned": "917", "claimed": "0", "unclaimed": "917", "pnl": "-1988473",
  "purchases": [ "…" ] }
```

### 19. Buy a future window, then stop it for a refund (stop) — demo/mock rail
A window past the observed chain head is billed but not yet deliverable; `stop` cancels the undelivered part and refunds it. Here a $0.00183 future buy (1830 atoms = 1800 materialize + 30 cache) is stopped, refunding the 1800 undelivered atoms (cost settles to 30).
```json
// Request (buy a future window)
{ "tool": "materialize", "set_args": {
    "bound_function_id": "sha256:18ec…a097", "from_block": 510664710, "to_block": 510664725, "extra_cache_days": 2 } }
```
```json
// Response (quote)
{ "outcome": "payment_required", "quote_id": "quote:01a0f72a-3573-7f21-84c3-9e46491bc006",
  "amount": 0.00183,
  "lines": [
    { "label": "materialize_blocks", "units": 15, "unit_price": 0.00012,  "amount": 0.0018 },
    { "label": "extra_cache_days",   "units": 30, "unit_price": 0.000001, "amount": 0.00003 } ],
  "accepts": [ { "scheme": "batch-settlement", "…": "…" } ] }
```
```json
// Request (stop the paid-but-undelivered quote)
{ "tool": "stop", "set_args": {
    "bound_function_id": "sha256:18ec…a097",
    "wallet": "0x0000…0000",
    "quote_id": "quote:01a0f72a-3573-7f21-84c3-9e46491bc006" } }
```
```json
// Response
{ "outcome": "success", "unspent_blocks": 15,
  "bound_function_id": "sha256:18ec…a097",
  "jobs": [ { "job_id": "job:163", "quote_id": "quote:01a0f72a-3573-…-1bc006",
              "refunded": 0.0018, "unspent_blocks": 15 } ] }
```

### 20. Claim rewards (claim_rewards)
Claims convert `unclaimed` reward atoms on-chain once they reach the threshold (10000 atoms). Below threshold the call still succeeds and claims nothing (dust), leaving the card unchanged.
```json
// Request
{ "tool": "claim_rewards", "set_args": { "wallet": "0x0000…0000" } }
```
```json
// Response
{ "outcome": "success", "claimed": "0", "unclaimed": "208" }
```

---

## D. Batch-settlement rail — escrow channel, vouchers, claim, sweep (live CDP)

Sub-cent charges are paid off-chain with signed **vouchers** against an on-chain **escrow channel**. The buyer makes one **deposit**, then each payment is a cumulative voucher; Testril's receiver authorizer later **claims** on-chain and **sweeps** to the Safe.

### 21. Look up open channels (inspect subject=channels) — none yet
Before a batch payment, inspect channels for the buyer address. Empty means the first payment must open a channel (deposit + voucher).
```json
// Request
{ "tool": "inspect", "set_args": { "subject": "channels", "payer": "0x4c65…D999", "network": "eip155:84532" } }
```
```json
// Response
{ "subject": "channels", "outcome": "success",
  "payer": "0x4c65…D999", "network": "eip155:84532", "channels": [] }
```

### 22. First batch payment = deposit + voucher
A $0.0036 charge (3600 atoms) with no channel yet: the client signs a **$0.10 escrow deposit** plus a voucher for `maxClaimable = 3600`. The paid receipt returns the new channel id.
```json
// Request (signed deposit + voucher omitted)
{ "tool": "pay_quote", "set_args": { "quote_id": "quote:01a0f6ab-2385-7cd1-85f0-0f71af78f715", "payload": "0x…sig…" } }
```
```json
// Response
{ "outcome": "success", "amount": 0.0036,
  "payment_id": "payment:01a0f6ab-2385-7cd1-85f0-0f71af78f715",
  "quote_id": "quote:01a0f6ab-2385-7cd1-85f0-0f71af78f715",
  "receipt": { "provider": "cdp", "state": "paid", "amount": 0.0036, "reference": "0x96f2…7bd5" } }
```

### 23. Channel now open (inspect subject=channels)
The channel shows escrow `balance` (100000 atoms = $0.10 deposited), `chargedCumulativeAmount` (3600 so far) and `totalClaimed` (0, nothing claimed yet). Reuse its `channelConfig` for top-ups.
```json
// Response  (one row)
{ "subject": "channels", "outcome": "success", "payer": "0x4c65…D999", "network": "eip155:84532",
  "channels": [ {
    "channelId": "0xdf9f…783f",
    "network": "eip155:84532",
    "channelConfig": {
      "payer": "0x4c65…D999", "payerAuthorizer": "0x4c65…D999",
      "receiver": "0x4559…5e52", "receiverAuthorizer": "0x91b0…086b",
      "token": "0x036C…CF7e", "withdrawDelay": 3600, "salt": "0x4080…a889" },
    "balance": "100000", "chargedCumulativeAmount": "3600",
    "signedMaxClaimable": "3600", "totalClaimed": "0", "closed": false } ] }
```

### 24. Top-up with a voucher only (no new deposit)
The next sub-cent charge reuses the channel: sign a voucher raising `maxClaimable` from 3600 to 7200. No on-chain tx and no new deposit — only the signed cumulative amount changes.
```json
// Response (pay)
{ "outcome": "success", "amount": 0.0036,
  "payment_id": "payment:01a0f6ab-3ae7-7661-85dc-1802ee5df705",
  "quote_id": "quote:01a0f6ab-3ae7-7661-85dc-1802ee5df705",
  "receipt": { "provider": "cdp", "state": "paid", "amount": 0.0036, "reference": "0xdf9f…783f" } }
```
After three such charges the channel reads `chargedCumulativeAmount: "10800"` while `balance` stays `100000` and `totalClaimed` stays `0`.

### 25. Testril claims the vouchers on-chain (claim)
Once cumulative charges cross the $0.01 claim threshold, Testril's receiver authorizer submits a single on-chain claim for all 10800 charged atoms. Observed by polling the escrow:
```json
// Observed on-chain (escrow)
{ "totalClaimed_api": "10800",
  "claim_txs": [ { "tx": "0xce25…46a1", "claimAmount": 10800, "newTotalClaimed": 10800, "block": 47538278 } ] }
```

### 26. …then sweeps the claimed funds to the Safe (sweep/settle)
Shortly after the claim, Testril sweeps the claimed amount from escrow into the receiver Safe, which rises by 10800 atoms ($0.0108).
```json
// Observed on-chain
{ "safe_usdc_delta_atoms": 10800,
  "sweep_txs": [ { "tx": "0xbdb2…df27", "amount_atoms": 10800, "block": 47538293 } ] }
```
The remaining escrow (balance 100000 − claimed 10800 = **89200 atoms, $0.0892**) stays refundable in the channel.

---

## E. Mock rail vs. live CDP rail

The demo/mock rail mirrors the exact same tool loop so integrators can rehearse without spending. Two tells distinguish it.

### 27. A mock quote offers a bare batch-settlement row
On the mock rail the `accepts` menu has no asset/payTo/escrow fields — there is nothing to sign on-chain.
```json
// Response (mock materialize quote)
{ "outcome": "payment_required", "quote_id": "quote:01a0f729-b68c-7fc0-b812-e116ae414102",
  "amount": 0.0018,
  "lines": [ { "label": "materialize_blocks", "units": 15, "unit_price": 0.00012, "amount": 0.0018 } ],
  "accepts": [ { "scheme": "batch-settlement" } ] }
```

### 28. A mock payment settles instantly with a `provider: "mock"` receipt
No signature is needed and the receipt `reference` is a synthetic `mock:…` id, not a tx hash.
```json
// Request
{ "tool": "pay_quote", "set_args": { "quote_id": "quote:01a0f729-b68c-7fc0-b812-e116ae414102" } }
```
```json
// Response
{ "outcome": "success", "amount": 0.0018,
  "payment_id": "payment:01a0f729-b68c-7fc0-b812-e116ae414102",
  "quote_id": "quote:01a0f729-b68c-7fc0-b812-e116ae414102",
  "receipt": { "provider": "mock", "state": "paid", "amount": 0.0018,
    "reference": "mock:quote:01a0f729-b68c-7fc0-b812-e116ae414102" } }
```

---

## F. Error cases (all real, captured)

### 29. Payment payload must be raw JSON, not base64 (pay_quote)
Submitting a base64-encoded x402 payload is rejected; the rail wants raw JSON.
```json
// Response
{ "outcome": "error", "kind": "invalid_params",
  "message": "invalid request: invalid payload json: expected value at line 1 column 1" }
```

### 30. The live rail requires a signed payment (pay_quote, no payload)
```json
// Response
{ "outcome": "error", "kind": "invalid_params",
  "message": "invalid request: missing payload: cdp rail requires a signed payment" }
```

### 31. `exact` is not offered below the sub-cent floor
Trying to pay a sub-cent quote with the `exact` scheme is refused — only `batch-settlement` is in that quote's menu.
```json
// Response
{ "outcome": "error", "kind": "exact_below_floor_payload_refused",
  "message": "invalid request: exact_below_floor_payload_refused: exact is not in this quote's accepts menu" }
```

### 32. Don't resubmit a paid quote (replay)
```json
// Response
{ "outcome": "error", "kind": "exact_replay_after_paid",
  "message": "this quote is already paid; do not resubmit a payment payload" }
```

### 33. Malformed batch voucher (missing channelId)
A voucher payload that isn't the expected `payload.voucher.{channelId,…}` shape is rejected.
```json
// Request (inner payload)
{ "channelId": "0x0000…0000", "maxClaimableAmount": "360" }
```
```json
// Response
{ "outcome": "error", "kind": "invalid_params",
  "message": "invalid request: invalid_batch_settlement_evm_voucher_payload: missing channelId" }
```

### 34. Deposit with an empty channelConfig (missing payer)
```json
// Request (inner payload)
{ "type": "deposit", "channelConfig": {},
  "voucher": { "channelId": "0x0000…0000", "maxClaimableAmount": "360", "signature": "0x…sig…" } }
```
```json
// Response
{ "outcome": "error", "kind": "invalid_params", "message": "invalid request: missing payer" }
```

### 35. Only the owner can stop/refund (wrong wallet)
`stop` takes an explicit wallet and refunds only the quote's owner; passing a non-owner wallet is refused.
```json
// Request
{ "tool": "stop", "set_args": {
    "bound_function_id": "sha256:18ec…a097", "wallet": "0x0000…0000",
    "quote_id": "quote:01a0f6ca-e6cd-7291-b43a-54565b265cb7" } }
```
```json
// Response
{ "outcome": "error", "kind": "invalid_params", "message": "invalid request: caller is not the agreed owner" }
```

### 36. Overlapping quotes: re-quote when coverage changed
If a competing quote changes a window's "holes" before you pay, the stale quote is refused with a remedy to re-quote the same window.
```json
// Response
{ "outcome": "error", "kind": "state",
  "message": "[state] pay for a backfill of `sha256:18ec…a097`: the quoted holes changed — coverage or paid jobs changed since this quote was issued — request a new materialize quote for the same window, then pay the returned quote_id (materialize)",
  "remedy": [ { "action": "request a new materialize quote for the same window, then pay the returned quote_id", "call": "materialize" } ] }
```

### 37. REST read — one data GET with x402 payment
This is an **illustrative 0.14.0 exchange, not a live capture**. REST has one
route. The bound-function ID, read window, and row-key parts belong in the URL.
An unpaid request returns HTTP 402 with x402's `PAYMENT-REQUIRED` header and the
quote object as its body:
```json
// Request — GET https://dev.testril.ai/v1/bound_functions/sha256:7c1e…9a42/data?from_block=0&to_block=13&token=0xaf88…5831
{ "headers": { "Accept": "application/json" } }
```
```json
// Response — HTTP 402
{ "status": 402,
  "headers": { "PAYMENT-REQUIRED": "<base64-encoded x402 PaymentRequired>" },
  "body": {
    "quote_id": "quote:01b6…42f0",
    "amount": { "atoms": "23", "usd": "$0.000023" },
    "lines": [
      { "label": "query_read",
        "unit_price": { "atoms": "10", "usd": "$0.000010" },
        "amount": { "atoms": "10", "usd": "$0.000010" } },
      { "label": "query_blocks", "units": 13,
        "unit_price": { "atoms": "1", "usd": "$0.000001" },
        "amount": { "atoms": "13", "usd": "$0.000013" } }
    ] } }
```
After authorization, the client retries the **same GET** with the x402 payment
attached. The successful response is the data:
```json
// Request — GET https://dev.testril.ai/v1/bound_functions/sha256:7c1e…9a42/data?from_block=0&to_block=13&token=0xaf88…5831
{ "headers": {
    "Accept": "application/json",
    "PAYMENT-SIGNATURE": "<base64-encoded x402 PaymentPayload>" } }
```
```json
// Response — HTTP 200
{ "status": 200,
  "headers": { "PAYMENT-RESPONSE": "<base64-encoded x402 payment response>" },
  "body": {
    "outcome": "success",
    "bound_function_id": "sha256:7c1e…9a42",
    "from_block": 0,
    "to_block": 13,
    "values": [
      { "block": 7, "token": "0xaf88…5831", "count": "3", "volume": "1181594340" },
      { "block": 11, "token": "0xaf88…5831", "count": "1", "volume": "42000000" }
    ] } }
```

---

## G. Current 0.14.0 illustrations

These examples drive the visible Workflow scenes. They use placeholders and
only fields documented by the 0.14.0 schemas and server instructions.

### 38. Discover the 0.14.0 public surface
The development endpoint reported version 0.14.0 and these nine public tools on
2026-11-02.
```json
// Request
{ "jsonrpc": "2.0", "id": 2, "method": "tools/list", "params": {} }
```
```json
// Response
{ "jsonrpc": "2.0", "id": 2, "result": { "tools": [
  { "name": "ask" },
  { "name": "bind" },
  { "name": "claim_rewards" },
  { "name": "health_check" },
  { "name": "inspect" },
  { "name": "materialize" },
  { "name": "provenance" },
  { "name": "read" },
  { "name": "stop" }
] } }
```

### 39. Bind current token-balance scope
The Function's live schema supplies the parameter names; this illustration uses
the current `erc20.token_balance` shape.
```json
// Request
{ "tool": "bind", "arguments": {
  "function_slug": "erc20.token_balance",
  "chain_id": 1,
  "params": { "reads": {
    "tokens": ["<TOKEN_ADDRESS>"],
    "owners": ["<WALLET_ADDRESS>"]
  } } } }
```
```json
// Response
{ "outcome": "success",
  "bound_function_id": "sha256:<id>",
  "function_slug": "erc20.token_balance",
  "chain_id": 1,
  "created": true }
```

### 40. Unpaid materialize returns PaymentRequired and the quote
`structuredContent` and `content[0]` hold x402 `PaymentRequired`;
`content[1]` holds `{quote_id, amount, lines}`. The string placeholders stand
for those same JSON objects, not a third response shape.
```json
// Request
{ "tool": "materialize", "arguments": {
  "bound_function_id": "sha256:<id>",
  "from_block": 100,
  "to_block": 110
} }
```
```json
// Response
{ "isError": true,
  "structuredContent": {
    "x402Version": 2,
    "error": "payment_required",
    "resource": { "url": "mcp://tool/materialize", "mimeType": "application/json" },
    "accepts": [{
      "scheme": "batch-settlement",
      "network": "<OFFERED_NETWORK>",
      "asset": "<OFFERED_USDC_ADDRESS>",
      "payTo": "<OFFERED_RECEIVER>",
      "amount": "1200",
      "maxTimeoutSeconds": 3600,
      "extra": { "minDeposit": "10000" }
    }]
  },
  "content": [
    { "type": "text", "text": "<PaymentRequired JSON>" },
    { "type": "text", "text": "{\"quote_id\":\"quote:<id>\",\"amount\":{\"atoms\":\"1200\",\"usd\":\"$0.0012\"},\"lines\":[{\"label\":\"materialize_blocks\",\"units\":10,\"unit_price\":{\"atoms\":\"120\",\"usd\":\"$0.00012\"},\"amount\":{\"atoms\":\"1200\",\"usd\":\"$0.0012\"}}]}" }
  ] }
```

### 41. Repeat the identical materialize call with payment
There is no `pay_quote` step and no `payment_id` argument.
```json
// Request
{ "tool": "materialize",
  "arguments": {
    "bound_function_id": "sha256:<id>",
    "from_block": 100,
    "to_block": 110
  },
  "_meta": { "x402/payment": {
    "x402Version": 2,
    "accepted": "<COPIED_ACCEPTS_ROW>",
    "payload": "<SIGNED_PAYMENT_FIELDS>"
  } } }
```
```json
// Response
{ "outcome": "success",
  "result": "materializing",
  "job": {
    "job_id": "job:<id>",
    "quote_id": "quote:<id>",
    "blocks_total": 10,
    "state": "queued"
  },
  "_meta": { "x402/payment-response": {
    "success": true,
    "network": "<SELECTED_NETWORK>",
    "payer": "<PAYER_ADDRESS>"
  } } }
```

### 42. A paid read repeats the identical read arguments
An unpaid read uses the same two-part payment-required result as materialize.
```json
// Request
{ "tool": "read",
  "arguments": {
    "bound_function_id": "sha256:<id>",
    "from_block": 100,
    "to_block": 110
  },
  "_meta": { "x402/payment": {
    "x402Version": 2,
    "accepted": "<COPIED_ACCEPTS_ROW>",
    "payload": "<SIGNED_PAYMENT_FIELDS>"
  } } }
```
```json
// Response
{ "outcome": "success",
  "bound_function_id": "sha256:<id>",
  "from_block": 100,
  "to_block": 110,
  "values": [],
  "_meta": { "x402/payment-response": {
    "success": true,
    "network": "<SELECTED_NETWORK>",
    "payer": "<PAYER_ADDRESS>"
  } } }
```

### 43. Provenance uses the same identity and window
```json
// Request
{ "tool": "provenance", "arguments": {
  "bound_function_id": "sha256:<id>",
  "from_block": 100,
  "to_block": 110
} }
```
```json
// Response
{ "outcome": "success",
  "bound_function_id": "sha256:<id>",
  "chain_id": 42161,
  "from_block": 100,
  "to_block": 110,
  "block_count": 0,
  "blocks": [],
  "digest": "sha256:<digest>",
  "computation": {
    "kind": "function",
    "id": "erc20.token_volume",
    "version": 1
  } }
```

### 44. Inspect claim rights, then claim by NFT id
`claim_rewards` takes `nft`, not a wallet.
```json
// Request
{ "tool": "claim_rewards", "arguments": { "nft": "1" } }
```
```json
// Response
{ "outcome": "success",
  "claimed": "0",
  "unclaimed": "208",
  "reason": "below_minimum" }
```

### 45. Stop returns one stopped-job record
```json
// Request
{ "tool": "stop", "arguments": {
  "bound_function_id": "sha256:<id>",
  "wallet": "<CURRENT_NFT_OWNER_ADDRESS>",
  "quote_id": "quote:<id>"
} }
```
```json
// Response
{ "outcome": "success",
  "bound_function_id": "sha256:<id>",
  "job": {
    "job_id": "job:<id>",
    "quote_id": "quote:<id>",
    "unspent_blocks": 10,
    "refunded": { "atoms": "1200", "usd": "$0.0012" }
  } }
```

### 46. Preserve a payment while recovery is pending
Do not sign a replacement. Retry the original paid call; Testril retains that
payment and retries completion.
```json
// Request
{ "tool": "materialize",
  "arguments": {
    "bound_function_id": "sha256:<id>",
    "from_block": 100,
    "to_block": 110
  },
  "_meta": { "x402/payment": "<ORIGINAL_SIGNED_PAYMENT>" } }
```
```json
// Response
{ "outcome": "error",
  "kind": "payment_recovery_pending",
  "message": "payment recovery pending; do not make another payment",
  "payment": {
    "quote_id": "quote:<id>",
    "network": "<SELECTED_NETWORK>",
    "amount": { "atoms": "1200", "usd": "$0.0012" },
    "settlement_status": "outcome_unknown",
    "settlement_reference": null
  } }
```

---

*Examples 1–36 are preserved historical Testril test-run captures. Examples
37–46 are clearly illustrative of the current 0.14.0 contract; example 38's
tool names and version were also observed through read-only discovery. Prices
and amounts are USDC. Identifiers and addresses are shortened; signatures and
payment payloads are never shown.*
