# Testril agent reference Testril 0.14.0 source tag 62aba5a2d4bccc6077cc42b6b68b79a0352a4e2c was reviewed 2026-11-02 against public registrations, argument types, instructions, handlers, REST transport, payment recovery, rewards and relevant test sources. Read-only development initialize/tools/list at 2026-11-02T16:02:40+00:00 reported 0.14.0 and nine tools with matching input fields and requiredness. Exact deployed Git revision and production status remain unconfirmed. No paid work, binding, materialization, metric read, stop or reward claim was performed; backend tests were read, not executed. Earlier review records and Workflow captures remain historical. Live tools/list, instructions and actual quotes take precedence. # Agent reference Source: https://testril.si/agents/ Testril is an Ormi product and an MCP service for on-demand blockchain data. An agent discovers a metric template, binds its scope, checks coverage, materializes missing blocks, reads values, and verifies their sources. **A Universe of Blockchain Data.** ## Current release The current release review on 2026-11-02 checked the Testril 0.14.0 source tag `62aba5a2d4bccc6077cc42b6b68b79a0352a4e2c`: public MCP registrations, argument types, server instructions, handlers, REST transport, payment/reward paths, and relevant test sources. Read-only development `initialize` and `tools/list` reported server 0.14.0 and protocol `2025-06-18`, with the same nine tool names, input fields, and requiredness as this reference. Free health, chain, pricing, and `erc20.token_balance` Function-schema calls also matched the documented contract. Exact deployed Git revision and production status remain unconfirmed. No paid work, binding, materialization, metric-data read, stop, or reward claim was performed; backend tests were inspected, not executed. Live discovery and actual quotes remain authoritative. Historical source/live observations below retain their original dates. The reviewed service endpoint is `https://dev.testril.ai/mcp`; payments use USDC. Version 0.14.0 expands the Function catalog for ENS, The Graph, launchpads, prediction markets, token metadata, wallet transfers, and NFT collection metadata, and adds payment-completion recovery. It does not change the nine public tool names or their input fields. Discover the deployed Function slugs and schemas through `inspect` rather than treating this release summary as a catalog. The canonical marketing website is `https://testril.si/`. The service remains at `https://dev.testril.ai/mcp` during this website migration; do not substitute the new website domain for the MCP endpoint. ## Start here 1. [Connect and discover the contract](/agents/connection/). 2. [Run the catalog-first quickstart](/agents/quickstart/). 3. [Look up the nine public MCP tools](/agents/mcp-tools/). 4. [Handle outcomes and recovery](/agents/outcomes/). 5. [Understand payment and execution](/agents/payments/). 6. [Verify provenance](/agents/provenance/). ## Reference library - [Identities, parameters, and block windows](/agents/data-contract/). - [Coverage, asynchronous jobs, and retention](/agents/coverage/). - [Resolve a question with ask](/agents/authoring/). - [Reusable agent recipes](/agents/recipes/). - [Data liquidity, claim rights, and rewards](/agents/rewards/). ## Machine-readable entry points - [MCP service card](/.well-known/mcp.json): connection, usage, and pricing overview for directories such as ASI. It describes the development endpoint; live MCP discovery and each per-request quote remain authoritative. - [llms.txt](/llms.txt): compact navigation and integration constraints. - [llms-full.txt](/llms-full.txt): the complete reference as plain text. - [MCP tool reference JSON](/agents/mcp-tools.json): documented tools, input fields, and inspect subjects. This is an editorial reference, **not an exported runtime JSON Schema**. - [Existing product content JSON](/agents/site-content.json): homepage copy, visible FAQ, and blog summaries. This is editorial context; the live tool contract remains authoritative. - [Workflow examples](/agents/workflow-examples.md) and [scene JSON](/agents/workflow-examples.json): historical recorded MCP exchanges plus labeled current illustrations. The recorded pre-0.9.0 tools, arguments, and money values are archival, not current wire examples. These snapshots are not runtime schemas. - Every page has a Markdown counterpart: replace its trailing slash with `.md`. For example, `/agents/quickstart.md`. ## Agent operating rules Discover the catalog before binding. Never infer a chain, wallet, contract, time window, price, or missing parameter from an unrelated example. Read `outcome` before using any result. Respect the operator's metric choice and spending authorization. Keep payment receipts and the exact block windows used for verification. Never send a private key in tool arguments. The main website preserves its existing product narrative. This reference is the detailed integration surface; the homepage Workflow replays captured exchanges, including shortened identifiers and omitted signatures. Its archived messages are not the current deployed schema. ## Release history ### Release review / 2026-10-27 / 0.13.0 The current release review on 2026-10-27 checked the Testril 0.13.0 source tag `345cd5a857faed75c17ac0c94f05e96e8f935b15` and current main `089892e0224cb8e4d748563ba7b2d9bd0e298e24`: public MCP registrations, argument types, server instructions, handlers, payment/reward paths, and relevant test sources. Read-only development `initialize` and `tools/list` reported server 0.13.0 and protocol `2025-06-18`, with the same nine tool names, input fields, and requiredness as this reference. Free health, chain, and pricing calls also matched the documented contract. Exact deployed Git revision and production status remain unconfirmed. No paid work, binding, materialization, metric-data read, stop, or reward claim was performed; backend tests were inspected, not executed. Live discovery and actual quotes remain authoritative. Historical source/live observations below retain their original dates. The service endpoint is `https://dev.testril.ai/mcp`; payments use USDC. ### Development endpoint observation / 2026-10-19 / 0.10.0 Read-only MCP `initialize` and `tools/list` returned server `testril` version 0.10.0, protocol `2025-06-18`, and the same nine public tool names and input field names as this reference. The field inventory now reflects the live schema's optional block ends for `provenance`, since a period pair can be used instead. Free `health_check` and `inspect subject:pricing` calls confirmed the server reports configured chains and meter rates. No paid call or metric-data read was made for this review. The deployment's implementation revision, handlers, and tests could not be independently inspected in this workspace; the 2026-10-13 source review remains specific to backend PR #274. Use live discovery for any integration, and do not infer that this older guide verifies every 0.10.0 behavior. ### Release review / 2026-10-13 / 0.9.0 and 0.9.1 owner clarification A source review covered Testril backend PR [`#274`](https://github.com/ormi-labs/testril/pull/274), reviewed commit `319b8eb3c4ae94475b189f0015e9ade30c043fda`: its 0.9.0 release notes, public MCP registrations and argument types, REST and MCP wire models, served `instructions.rs`, handlers, and relevant tests. The target release was not deployed, so no live `initialize` or `tools/list` claim is made for 0.9.0. No metric work, write, or payment was performed. An owner clarification recorded on 2026-10-13 supersedes two payment details from that review: agent-facing priced tools settle the exact quoted amount, with spending caps enforced by the agent's wallet or signer; and Testril 0.9.1 declares and enforces a 10,000-atom ($0.01) batch minimum deposit. The 0.9.1 implementation revision and live deployment were not independently reviewed for this correction. ### Historical Workflow note Historical Workflow MCP captures remain historical rather than being rewritten to resemble 0.14.0. Their retired tools, arguments, and numeric money values are records of earlier releases, not current wire examples. Current illustrative scenes are labeled separately. --- # Connect an agent Source: https://testril.si/agents/connection/ Connect through a compatible HTTP MCP client, discover the advertised tools and instructions, then follow the [catalog-first quickstart](/agents/quickstart/). The marketing website at `https://testril.si/` hosts documentation; it is separate from the service endpoint. ## Obtain the endpoint The supplied captures use `https://dev.testril.ai/mcp`, with USDC payments. See the [captured Workflow examples](/agents/workflow-examples.md). This identifies the recorded service; confirm access requirements with the operator before integration. `` in generic examples is a placeholder; do not infer an endpoint from the website domain. The implementation exposes an HTTP MCP route at `/mcp`. A self-hosted daemon's documented local default is `http://127.0.0.1:8000/mcp`. This loopback URL refers to your own machine, not the hosted Testril service. ## Discover the live contract Use your MCP client's connection lifecycle, then request `tools/list`. Save the advertised tool names, input schemas, output schemas, and server instructions. The implementation supports the legacy initialize lifecycle as well as a stateless protocol generation; let a compatible client negotiate its supported version instead of guessing transport metadata. The reviewed Testril 0.14.0 source and live discovery advertise nine public tools: `health_check`, `inspect`, `bind`, `ask`, `materialize`, `read`, `provenance`, `stop`, and `claim_rewards`. Read-only development discovery reported version 0.14.0 with those same nine names, input-field names, and requiredness on 2026-11-02. Its live descriptions, schemas, and instructions remain authoritative. The website's [MCP service card](/.well-known/mcp.json) provides a directory entry point to this development endpoint; it does not replace `tools/list`. Priced `materialize` and `read` calls use x402 on the call itself; there is no separate payment tool. Administrative operations are outside this public reference; do not infer access from the public MCP endpoint. If the deployed list differs, use the deployed schemas and report the mismatch to the operator. Do not call an unadvertised tool. The current release review is recorded on the [agent reference](/agents/). It combines a 0.14.0 source-tag review with read-only development discovery; the exact deployed Git revision remains unconfirmed. The earlier release review on 2026-10-13 checked the 0.9.0 release notes, public wire types, handlers, tests, and served instructions at backend PR [`ormi-labs/testril#274`](https://github.com/ormi-labs/testril/pull/274), commit `319b8eb3c4ae94475b189f0015e9ade30c043fda`. The target release was not deployed, so its MCP endpoint could not be rechecked through `initialize` or `tools/list`. No metric work, write, or payment was performed. See the [release review](/agents/). ## First calls ```json {"tool":"health_check","arguments":{}} ``` ```json {"tool":"inspect","arguments":{"subject":"chains"}} ``` ```json {"tool":"inspect","arguments":{"subject":"functions"}} ``` The `{tool, arguments}` objects throughout this reference are **documentation notation**, not raw MCP transport messages. Invoke the named tool with those arguments through your client. Chain support is deployment-specific: a marketing coverage number does not establish which RPC providers a particular server has configured. ## REST data reads A bound-function card names its one HTTP route in `data_endpoints.data`: `GET https://dev.testril.ai/v1/bound_functions/{id}/data`, where `{id}` is the returned `sha256:` identity. Put the read window and each row-key part in query parameters. An unpaid request returns HTTP 402, a `PAYMENT-REQUIRED` header, and the quote object in the body. Retry the same GET with the x402 payment in `PAYMENT-SIGNATURE`; a successful paid response carries `PAYMENT-RESPONSE`. A signature that is not base64 JSON returns 400; a decoded value that is not a payment returns 402 `invalid_payload`; reusing a payment-identifier for another request returns 409. There is no quote, status, provenance, write, or OpenAPI route; writes stay on MCP. Do not construct any other `/v1` path. The REST quote uses the same 0.9.0 money contract as MCP: ordinary money fields are `{atoms, usd}` objects. x402 `accepts[].amount` remains a bare atom string. ## Record an integration fingerprint Store the endpoint, negotiated protocol version, discovered tool schemas, configured chain IDs, and the time of discovery. Recheck after server upgrades. Keep authentication and RPC credentials outside logs and prompts; inspect and provenance data should be sufficient without exposing a provider's secret URL. --- # Catalog-first quickstart Source: https://testril.si/agents/quickstart/ To obtain blockchain data with Testril, discover a supported metric, bind the requested scope, check coverage, materialize missing blocks, read values, and verify provenance. Materialization and reading have separate payment gates; confirm spending before settlement. First [connect and discover the live contract](/agents/connection/). ## 1. Discover the chain and template Call `inspect` with `subject: "chains"`, then `subject: "functions"`. Confirm the requested chain is configured. Select a template whose semantics match the operator's request. For example, **if advertised**: ```json {"tool":"inspect","arguments":{"subject":"function","function_slug":"erc20.token_balance"}} ``` Read its `params` and `row` schemas, descriptions, and defaults. The catalog's Function names are metric templates; they are not MCP tools. ## 2. Bind the scope The following addresses and chain are placeholders. Substitute operator-supplied values, and use exactly the parameter names advertised by the selected template. ```json {"tool":"bind","arguments":{"function_slug":"erc20.token_balance","chain_id":1,"params":{"reads":{"tokens":[""],"owners":[""]}}}} ``` Proceed only on `outcome: "success"`. Save the returned `bound_function_id`. Binding creates an identity and scope, not an indexed dataset. `created: false` does not prove the requested window is covered. ## 3. Check coverage and choose the window ```json {"tool":"inspect","arguments":{"subject":"bound_function","bound_function_id":""}} ``` Read `materialized`, `materializing`, and `active_jobs`. Choose a requested half-open block interval `[from_block, to_block)` using the correct chain's `head` and the operator's intended period. The head is not a finality guarantee. Example block numbers below illustrate syntax; they do not prescribe a useful query window. ## 4. Materialize missing blocks ```json {"tool":"materialize","arguments":{"bound_function_id":"","from_block":100,"to_block":110}} ``` If payment is required, read x402 `PaymentRequired` from `structuredContent`/`content[0]` and the quote `{quote_id, amount, lines}` from `content[1]` of the MCP error result. Inspect `lines`, `amount`, and `accepts`, obtain spending authorization, and prepare the signed PaymentPayload for an offered rail. Read exact quote values from `amount.atoms` and line-item atom values; use each money object's `usd` only for display. The x402 `accepts[].amount` field remains a bare atom string and must equal the quoted charge. ```json {"tool":"materialize","arguments":{"bound_function_id":"","from_block":100,"to_block":110},"_meta":{"x402/payment":{"x402Version":2,"scheme":"","accepted":"","payload":""}}} ``` The host attaches `_meta`; do not put a private key in tool arguments. Preserve the returned `_meta["x402/payment-response"]` receipt. A successful accepted job can still be running. Inspect the returned `job.job_id` and the bound function's coverage before reading. Start paid execution within the quote's 120-second lifetime. ## 5. Read values ```json {"tool":"read","arguments":{"bound_function_id":"","from_block":100,"to_block":110}} ``` Reading has its own pay gate. If payment is required, retry the same `read` arguments with the signed PaymentPayload attached in MCP transport metadata. A successful read returns bare `values`, one row per block and key with all its attributes, typed by the Function’s `row` schema from inspect. Within fully covered blocks, absent rows follow the Function's declared emission rules; an unavailable window is not a zero balance. ## 6. Verify the source window Use the same bound identity and exact half-open window; there is no attribute selector: ```json {"tool":"provenance","arguments":{"bound_function_id":"","from_block":100,"to_block":110}} ``` Keep the result identity, exact window, values, source block hashes, and applicable digest together. See [provenance verification](/agents/provenance/) for limits and interpretation. --- # MCP tool reference Source: https://testril.si/agents/mcp-tools/ The deployed `tools/list` schemas are authoritative. Unknown fields are refused by the documented argument types. Optional does not mean meaningful in every combination: identity, window, and subject rules still apply. ## Money values In Testril 0.9.0, ordinary money fields are `{"atoms":"","usd":"$"}`. Read `atoms` for exact USDC arithmetic and `usd` for display. This includes quote totals and lines, native estimated spend, materialize and read replay receipts, job spend, stop refunds, pricing rates, and ask retention prices and spend. The exceptions are intentional: x402 `accepts[].amount` is a bare atom string, and NFT accounting values are bare atom strings. Payment settles the exact quoted amount. Enforce spending caps in the agent's wallet or signer, not in Testril tool arguments or quote objects. See [payment and execution](/agents/payments/) for the complete field inventory. ## health_check Arguments: `{}`. Shallow process/connect diagnostics: configured chain IDs, version, catalog counts, and payment rail. It does not probe dependency health. A health response is not proof that a particular metric window is available. ## inspect Arguments: `subject` plus only that subject's relevant fields. Metadata and inventory, not metric values. Subject-specific stray fields are rejected. | Subject | Required extra fields | Optional extra fields | Purpose | | --- | --- | --- | --- | | `functions` | None | None | Catalog template slugs | | `function` | `function_slug` | None | Template description, params schema, and row schema | | `bound_functions` | None | `function_slug` | Live identities; grouped inventory or filtered IDs | | `bound_function` | `bound_function_id` | None | Scope, materialized/materializing ranges, active jobs | | `jobs` | None | None | Job inventory | | `job` | `job_id` | None | State, paid window, block progress, errors | | `chains` | None | None | Configured chains and heads | | `chain` | `chain_id` | None | One chain's landmarks | | `channels` | `payer` | `network` | Buyer's open batch-settlement escrow channels | | `nft` | `wallet` | None | Wallet's claim-right cards in `nfts`, plus unreadable-chain entries in `unavailable` | | `pricing` | None | None | Published meter rates | `payer` and `wallet` are public `0x` addresses, not signing keys. The optional channel `network` is the CAIP-2 identifier advertised by the selected payment offer; do not hardcode one from an example. ## bind Required: `function_slug` (string), `chain_id` (unsigned integer). Optional: `params` (object; defaults to empty). Template-specific requirements come from `inspect subject:function`. Returns a scope-bound identity, canonical debug name, template, chain, parameters, and `created`. Rebinding the same scope is idempotent. No block window belongs in this call; no metric values are returned. ## ask Required: `metric` (string, at most 500 characters), `chain_id` (unsigned integer). Optional: `function_slug`, `params`, and the paired block, hour, day, or month window appropriate to the Function. Selects an existing catalog Function and binds the question's scope. It keeps addresses written in the question or supplied in `params`, and asks for an address it cannot establish. It authors nothing. A params suggestion may offer up to five already-landed Bound Functions with the params needed to use one. An unsupported question returns `error` / `unsupported` with `needs`. A success carries the bound-function card, description, row schema, `created`, `follow`, and retention disclosure; it is not a metric value series. A `suggested` response asks for parameters or a window. Merge the operator's answer under the field named by `resume` and repeat. See [question resolution](/agents/authoring/). `bind_prompt` and the old `address`, `context`, `shape`, and `explain` inputs are no longer advertised. ## materialize Required: `bound_function_id`. Also state the appropriate paired block or period window. Optional: `extra_cache_days` (unsigned integer, default 0). Omitted or zero extra cache days adds no extra retention line. Computes and caches unpaid holes, excluding existing coverage and open paid jobs. An unpaid request returns the x402 payment requirement and quote. Repeat the same call with the same work arguments and attach the signed PaymentPayload in MCP `_meta["x402/payment"]`. A paid execution returns `success`, `result: "materializing"`, and a `job` handle. Inspect its `job_id` until done or failed. Coverage and progress remain on `inspect`. For a Function that carries state across blocks, the window is quoted, billed, and walked from its contract's creation block. A window ending at or before creation is refused. A future tail waits until the chain head reaches those blocks; a stateful job can remain queued until its preceding coverage is complete. ## read Required: `bound_function_id`. Optional: `key` (an object mapping key-part names to strings) and the paired block, hour, day, or month window appropriate to the Function. Payment is MCP transport metadata, not a tool argument. Omit both block ends to request the latest **materialized** block, which need not be the chain head. Every returned row includes all its attributes. `key` filters a leading run of the key parts listed after `block` in the Function's row schema; it does not change the bound scope. For example, use `{"token":""}` only when `token` is the first advertised key part. Unknown names, skipped leading parts, and malformed typed values are refused before quoting. `attributes` and `entity` are retired inputs. Returns bare `values`, typed by the Function’s `row` schema from inspect, never a coverage inventory and never new indexing. There is no `cursor` or `limit` argument. Split deliberate block windows when needed. A not-fully-covered request returns `range_unavailable`; it does not silently answer only the covered prefix. ## provenance Required: `bound_function_id` and the same paired block or period window used by `read`. Expand the contributing sources for that identity and window. No `attribute` argument is accepted. Provenance remains an MCP tool; there is no HTTP provenance route. ## stop Required: `bound_function_id`, `wallet`, and `quote_id` (strings). The wallet must be the current NFT owner of that quote's claim-right, checked at the chain tip when a collection is configured. Cancels the named in-flight materialize quote and attempts to refund its unspent part. Delivered rows and other jobs remain. The response carries one `job` with `job_id`, `quote_id`, `unspent_blocks`, and optional `refunded` or `error`. An error response may still include that stopped-job detail; reconcile it before retrying. ## claim_rewards Required: `nft` (decimal string), the claim-right ID from an `inspect subject:nft` card. Claims that NFT's eligible unclaimed USDC atoms to the wallet its current `ownerOf` names. The minimum is 10,000 atoms ($0.01); amounts below it return claimed `"0"` and `reason: "below_minimum"` without a transfer. A card with `minting: true` refuses as `claim_right_minting` until the mint lands. Inspect the same card before and after claiming. This operation pays existing eligible rewards; it does not promise earnings or create a claim-right simply from a data read. See [tool reference JSON](/agents/mcp-tools.json) for the same input-field inventory in a machine-readable form. --- # Identities and data contracts Source: https://testril.si/agents/data-contract/ ## Three names with different jobs A **Function** is a reusable catalog template, identified by `function_slug` such as `erc20.token_balance`. A **Bound Function** is that computation with its chain and scope parameters filled, identified by a content-addressed `bound_function_id` beginning `sha256:`. An **MCP tool** is a verb such as `bind`, `materialize`, or `read`. Pass the returned bound-function ID unchanged. A readable `name`, metric phrasing, or function slug cannot stand in for it. Two phrasings may resolve to one identity; one template bound to two contracts can produce two different identities. ## Scope is structural Obtain template parameter names from `inspect subject:function`. Pass addresses in typed fields or `params`, not hidden in explanatory prose. Set `chain_id` explicitly. There is no default chain. Use the exact JSON field names from the deployed schema: unknown fields are rejected. Function dependencies are structural `params`: supply a dependency's scope nested under its slot, using only the structure and names advertised by `inspect subject:function`. An `ask` response may offer already-landed Bound Functions when a required address is missing; reissue with the selected option's supplied params. Data tools still require the returned `bound_function_id`. `bind` takes scope, not a block range. `ask` returns a resolution, not a metric value series. ## Every explicit block window is half-open `[from_block, to_block)` includes the start and excludes the end. `[100, 110)` contains ten blocks; block 110 is not included. A single block `n` is `[n, n+1)`. State both ends or omit both where the tool permits omission. One end alone is invalid. Never treat block numbers as timestamps or add ranges from different chains. A materialize window may extend beyond the current chain head; that tail waits for those blocks to exist. A read or provenance request requires full coverage of its stated window. For a wall-clock request, first discover chain landmarks. The chain card includes `blocks_per_day` and either `head` or `unavailable`. A block-rate estimate is not an exact timestamp-to-block conversion; define the actual block boundaries before spending. The head does not establish finality. A Function bound `by` `hour`, `day`, or `month` uses the matching paired UTC fields instead: for example `from_day` and `to_day`. Do not mix period and block fields. The bound-function card tells clients which spelling to use. ## Schema once, values many times A successful `read` returns bare `values`, one object per block and row key, with every attribute of that row. Obtain their types and key-part names from the Function’s `row` schema through inspect. Preserve declared types; do not round large integer strings through floating-point arithmetic. A sparse row, an empty result, and missing coverage are distinct states. Within fully materialized coverage, a block with no row means the Function emitted nothing there; consult its description before interpreting that absence. Inspect the actual row schema and prefer exact integer fields for accounting when supplied. Human-readable floating-point units can lose precision above 2^53. The optional `read.key` is an object of string values for a leading run of row-key parts. It filters stored rows without rebinding scope. `read.attributes`, `read.entity`, and `provenance.attribute` are no longer accepted. Provenance uses the bound identity and exact window. ## Keep useful agent state Persist the endpoint, chain, function slug, bound-function ID, scope parameters, requested window, selected measures, quote, job ID, payment response, and source verification evidence. Store secrets separately. This small state record lets another agent resume a workflow without reconstructing intent from prose. --- # Outcomes and recovery Source: https://testril.si/agents/outcomes/ Always inspect the tool result's top-level `outcome` before reading its payload. A valid MCP response is not necessarily completed work. | Outcome | Interpretation | Next action | | --- | --- | --- | | `success` | The verb succeeded or work was accepted | Interpret that verb's fields; poll an accepted async job | | x402 payment required | A priced offer has been frozen in an MCP error result | Confirm scope and spending authority; retry the same call with payment metadata | | `suggested` | A choice is required; no bound result yet | Show options and basis; repeat with the selected field named by `resume` | | `error` | Product refusal or failure | Inspect `kind`, `message`, optional `not` and `remedy`; repair the specific issue | Product errors normally arrive with MCP `isError: false`; x402 payment required is the intentional exception. Transport failures are different: inspect MCP error/connection status and do not assume an interrupted charge or accepted job was rolled back. ## range_unavailable `read` does not index and refuses a window that is not fully covered. Its `materialized` and `materializing` geometry is clipped to the requested range. If missing blocks are already in flight, inspect their jobs and wait. Otherwise materialize the remaining holes. Use the full bound-function inspect card for the complete range ledger. A provenance coverage error reports its available range lists according to that tool's contract. Do not blindly reuse the read-specific clipping assumption for other tools. ## invalid_params Check the deployed schema, identity type, exact field spelling, required subject fields, paired block ends, address shape, and declared measure names. Remove retired `cursor` and `limit`. Do not add generic fields such as `query` or `sql` to tools that do not advertise them. Priced `materialize` and `read` calls accept only their advertised work fields; extra payment-policy fields return `invalid_params`. The payment settles the exact quoted amount. Keep spending caps in the agent's wallet or signer. `read` accepts row-key filtering through `key`; its old `attributes` and `entity` inputs are retired. `provenance` no longer accepts `attribute`. ## suggested The server supplies the answer's target field through `resume`. Merge the chosen answer into the original call instead of replacing the whole request. Do not automatically choose a different metric, window, or economic tradeoff. The operator's prior explicit choice can supply the answer; otherwise request it. ## Interrupted paid work Keep the frozen quote and payment response. Inspect coverage and job state before retrying or paying again. A lost answer retried with the same spent payment or the same x402 `payment-identifier` is served again without a second charge. Reusing an identifier for different work is refused as `payment_identifier_conflict`. Quote expiry does not cancel a background job already started. `payment_recovery_pending` means the original signed payment has been retained while the daemon resolves or records its settlement. Do not sign a replacement: retry the original paid call. An unknown settlement remains pending after quote expiry until finalized chain evidence resolves it. Inspect job errors and refund records; see [payments](/agents/payments/). ## Final answer quality Report only successful data, with chain, scope, exact block window, measure meanings, and verification evidence. If coverage or assembly failed, report that limitation explicitly. Do not invent a value from the metric's name, marketing copy, or the animated homepage demo. --- # Payment and execution Source: https://testril.si/agents/payments/ ## Freeze, authorize, settle, execute Both `materialize` and `read` use the same handshake: 1. Request the unpaid work with its scope and window. 2. Read x402 `PaymentRequired` from `structuredContent` or `content[0]` and the quote `{quote_id, amount, lines}` from `content[1]` of the MCP error result. 3. Confirm that spending is authorized for this scope and amount. A configured Testril CLI treats its operator-set budgets as that authorization and pays qualifying quotes without another prompt. Otherwise present unresolved choices to the operator. Mock payments follow the same consent workflow. 4. Retry the same tool and arguments with the signed x402 PaymentPayload in `_meta["x402/payment"]`. 5. Save `_meta["x402/payment-response"]` with the result. A quote is not the result; a settlement receipt is not the result; an accepted job is not necessarily completed data. ## The quote clock Every quote expires 120 seconds after freeze, whether paid or unpaid. Pay **and start execution** within that interval. Repeating a pending request does not extend the clock. After expiry, the same unpaid call freezes a new quote. A lost paid answer retried with the same spent payment or the same x402 `payment-identifier` returns the stored purchase without a second charge, including after the original quote expires. Reusing an identifier for different work is refused as `payment_identifier_conflict`. An already-started paid background job continues after quote expiry. ## Amounts and rails are discovered A deployment can offer several payment networks. Select one complete `accepts` row and keep its network, asset, receiver, and channel state together through signing and retries. Do not mix fields from different offers. A client using the Testril CLI can select an advertised network with `--network eip155:`. This is separate from the computation chain. Use `inspect subject:pricing` for published meters and the actual quote for this operation's price. Do not hardcode rates, payment networks, contracts, or pay-to addresses from an example. Chain of computation and payment network are separate choices. Testril 0.9.0 represents every ordinary money field on the MCP and REST wire as an object with two strings: ```json {"atoms":"23","usd":"$0.000023"} ``` Use `atoms` for exact arithmetic and signing decisions. It is the base-10 integer count of USDC atoms (1 USDC = 1,000,000 atoms). Present `usd` exactly as the server supplies it; it is display text, including the `$`. This shape applies to payment-required and quote `amount`, quote `lines[].amount` and `lines[].unit_price`, native quote `est_spend`, materialize receipt `amount`, materialize and read replay `amount` and nested `receipt.amount`, job `spent`, stop `job.refunded`, pricing `rates[].unit_price`, and ask retention `price_per_row` and `spend`. Two fields deliberately use strings instead of money objects: - x402 `accepts[].amount` is the bare decimal string of atom units required by x402. It settles the exact quoted amount; compare it with the quote amount's `atoms`. - NFT accounting amounts remain bare decimal strings of USDC atoms. See [data liquidity and rewards](/agents/rewards/). An illustrative quote fragment shows the boundaries: ```json { "amount": {"atoms":"23","usd":"$0.000023"}, "lines": [{ "label":"query_blocks", "units":13, "unit_price":{"atoms":"1","usd":"$0.000001"}, "amount":{"atoms":"13","usd":"$0.000013"} }], "accepts": [{ "scheme":"batch-settlement", "amount":"23", "extra":{"minDeposit":"10000"} }] } ``` For example, exact-value code should cross-check atom strings rather than parse the dollar display: ```python quote_atoms = int(quote["amount"]["atoms"]) assert int(selected_accepts["amount"]) == quote_atoms display_price = quote["amount"]["usd"] ``` The payment is a signed x402 v2 object attached by the MCP host in `_meta["x402/payment"]`. It is not a tool argument, private key, or caller-invented amount. Follow the server instructions and selected `accepts` row. A model normally cannot set `_meta`; a payment-capable host or CLI does. Enforce spending caps in the agent's wallet or signer, outside Testril tool arguments and quote objects. ## Testril CLI relay The published CLI can run as a local MCP relay with `npx -y testril@latest mcp`. Give the payer key to that process through `TESTRIL_PAYER_KEY`, never as a tool argument. Configure `--per-tx-budget-usd` and, when needed, the daily, weekly, monthly, minimum deposit, and advertised-network options before starting it. Those budgets are the operator's go-ahead: the relay pays a qualifying quote without an extra dialogue, refuses an amount outside them before signing, and returns an `outcome: "error"` such as `payment_over_budget` with a remedy. After a paid call, report the charge note and retain `_meta["x402/payment-response"]`; do not describe a small read as free merely because it used an existing channel balance. ## Batch settlement For an offered batch rail, inspect `subject: "channels"` with the payer address and optional advertised CAIP-2 network. Reuse eligible open channel state according to the server's instructions. A first deposit and a later voucher are different actions. The declared and server-enforced minimum deposit is 10,000 atoms ($0.01), whatever the call's price; an offer carries it as the bare atom string `"10000"` in `accepts[].extra.minDeposit`. Obtain authorization for the full deposit commitment, not just the small individual query price. Channel fields such as balances and cumulative amounts are decimal strings. Keep precise integer arithmetic. Withdrawal is a separate on-chain escrow workflow; Testril has no MCP withdrawal tool. Use the delay and contract details advertised by the live service. ## Materialize pricing and retries Materialize buys unpaid holes, excluding durable coverage and open paid ranges. `extra_cache_days > 0` adds retention beyond the included stamp. Read has its own query pricing; cached computation does not imply free reads. For a Function that carries state across blocks, materialization is quoted, billed, and walked from its contract's creation block. Read the actual frozen window and line items; a later requested start does not remove that prerequisite work. Paid materialization starts an asynchronous job. Inspect the returned job until done or failed, and reconcile its error and refund records before retrying. Do not blanket-repay every error or assume a settlement receipt means the data is complete. ## Payment recovery If settlement may have succeeded but the purchase could not be recorded, the tool returns `outcome: "error"` with `kind: "payment_recovery_pending"`. Its `payment` names the quote, network, amount, settlement status, and any known settlement reference. The daemon retains the original signed payment and retries completion, including after restart. Do not sign a replacement payment. Retry the original paid call to retrieve the purchase. Quote expiry and a missing receipt do not prove that settlement failed; an unknown outcome remains pending until finalized chain evidence resolves it. ## Agent expense record Log quote scope, lines, amount, rail, time frozen, settlement response, job ID, and final coverage. Log signed payloads only under the operator's security policy; avoid leaking keys or reusable authorizations into ordinary diagnostic output. --- # Coverage, jobs, and retention Source: https://testril.si/agents/coverage/ ## Inspect before filling `inspect subject:bound_function` is the coverage ledger. Its `materialized` ranges describe durable data; `materializing` describes paid in-flight work; `active_jobs` identifies open jobs. `created`, a metric name, or a previous successful bind does not establish coverage. Materialize only missing blocks. With `[100, 115)` already cached, a request for `[97, 118)` has six uncovered blocks: `[97, 100)` and `[115, 118)`. Existing paid in-flight ranges also count toward the exclusion. Read the returned quote rather than pricing the full requested window yourself. ## Follow asynchronous work A paid materialize response returns `result: "materializing"` and a `job` handle. Poll `inspect subject:job` with its `job_id`. The job card describes its own paid half-open window, `blocks_done`, `blocks_total`, and diagnostic fields where present. Its `spent` field is a 0.9.0 `{atoms, usd}` money object. The seven states are `queued`, `running`, `partial`, `waiting_blocks`, `done`, `failed`, and `cancelled`. A partial job can make a durable prefix visible; reading the full original window still requires full coverage. A future tail waits for the chain head to reach it instead of being fabricated. A cancelled job was stopped by its claim-right holder. A Function with memory across blocks is billed and walked from its contract's creation block, even when the requested start is later. Its job can stay queued until every preceding block from creation is covered. Check the quote's actual window and lines before authorizing payment. The live instructions advertise dependency-aware indexing: a Function that reads another Function cannot claim blocks its dependency has not materialized. Materialization quotes needed blocks across both and walks the dependency first; dynamically discovered dependency keys are walked from contract creation. Inspect the actual quote and jobs before paying or reading. This behavior was advertised, not executed, in the release review. Use bounded polling and a backoff appropriate to the chain. Do not repeatedly materialize a window while an active paid job already covers its holes. If a process or client restarts, inspect the durable state before resuming. ## Latest materialized is a precise request A `read` with neither block end asks for the latest materialized block. It does not promise latest chain head. For a time-sensitive decision, compare its resolved range with the chain's `head` and materialize a deliberate updated window when authorized. ## Retention and cache titles The implementation includes a 24-hour land stamp for new paid coverage; additional days are specified with `extra_cache_days` when freezing materialize work. Actual range titles and expiry are inspectable on the bound-function card. A titled range can carry `cache_expiry_time` as a Unix-seconds string; untitled ranges may omit it. Expiry belongs to each range, not one global wallet timer. Adjacent ranges may have separate titles and expiry records. Do not assume all historic values remain available forever. ## Stop a specific paid window ```json {"tool":"stop","arguments":{"bound_function_id":"","wallet":"","quote_id":""}} ``` This targets a named in-flight materialize quote. The wallet must currently hold that quote's claim-right; the original payer may no longer be its owner. When a collection is configured, ownership is checked at the chain tip. Examine the returned `job` and its `unspent_blocks`, optional `refunded`, and optional `error`. `job.refunded` is a `{atoms, usd}` money object; NFT-card refund accounting remains a bare atom string. A refund failure is returned as `outcome: "error"` and can still include the stopped-job detail; reconcile against inspect and accounting before retrying. Delivered rows remain available. --- # Every Action Leaves a Trace Source: https://testril.si/agents/provenance/ **Every Action Leaves a Trace.** For an agent, provenance is a usable evidence record, not an adjective attached to an answer. ## Ask for the exact identity and window After a successful read, keep its bound-function ID, returned field names, and resolved window. Invoke `provenance` with the same identity and paired block or UTC period window. Its block ends are optional in the wire schema because period windows use their own pair. It takes no `attribute` or `key` selector; the evidence describes the bound function's window. A different window is a different evidence request. ```json {"tool":"provenance","arguments":{"bound_function_id":"","from_block":100,"to_block":110}} ``` Provenance is a separate tool call. Do not claim that every read automatically contains the expanded source-block list. For period-based Functions, use the matching pair, such as `from_day` and `to_day`. A period-end read can cite its source blocks directly. A derived period row can instead cite dependency rows in `sources` without a `blocks` field; follow each cited bound-function/window to expand its block evidence. An absent `blocks` field is not the same as an enumerated empty block list. ## What to preserve The implementation returns chain sources and contributing rows or blocks for the requested identity and window. Preserve only evidence the response actually supplies; do not invent absent blocks or digests. Provenance is available through this MCP tool, not through an HTTP route. Persist the identity, chain, measure, window, compact record where supplied, expanded source list, digest, and comparison outcome. Preserve the algorithm and encoding supplied by the contract if independently recomputing; an invented concatenation of hashes is not a valid verification procedure. ## Canonical source-digest encoding The observed implementation sorts contributing pairs by ascending block number, then hashes UTF-8 lines in this exact form: ```text {chain_id}:{block_number}:{lowercase_0x_block_hash}\n ``` The result is `sha256:` followed by lowercase SHA-256 hex. The newline is one actual LF byte per pair, including the final pair. Chain ID and block number are decimal integers. Use the exact contributing set returned by the contract; do not add every block in the enclosing span. A verifier can reproduce that digest locally: ```python import hashlib def source_digest(chain_id, blocks): ordered = sorted(blocks, key=lambda block: block["number"]) encoded = "".join( f"{chain_id}:{block['number']}:{block['block_hash'].lower()}\n" for block in ordered ).encode("utf-8") return "sha256:" + hashlib.sha256(encoded).hexdigest() ``` This verifies encoding consistency for the supplied list. For independent source verification, obtain the corresponding canonical block hashes independently as well. **Two range conventions differ:** tool requests use the exclusive `to_block`, while a compact `ChainRange` citation's `from` and `to` are the **inclusive minimum and maximum contributing block numbers**. Its `block_count` is the actual contributing count and may be smaller than the enclosing span because of gaps. Do not pass the citation's inclusive end directly into a half-open tool request. ## Source evidence and computation evidence Matching source hashes establishes the requested source evidence's consistency with its digest. It does not, by itself, prove the metric definition matches the operator's intent or independently replay the computation. Distinguish source verification, definition review, and independent recomputation in the final answer. ## Missing coverage and empty blocks A block with no matching event can be legitimate evidence. A missing block is unavailable coverage. Do not conflate them or collapse both into a zero value. Provenance requires its stated window to be covered; inspect the reported geometry and complete missing work before asserting verification. ## Bounded expansion Expansion is limited to 10,000 contributing blocks per response. For larger requests, use smaller meaningful windows and retain the boundaries of each verification record. Do not claim that independently expanded subwindows match one whole-window digest unless the contract supplies a valid composition rule. ## Credentials stay out of evidence Agent-facing source descriptions should identify providers or chains without exposing credential-bearing RPC URLs. Block numbers, hashes, function identity, and reproducible definitions are more useful evidence than a secret endpoint string. --- # Resolve a question with ask Source: https://testril.si/agents/authoring/ ## Two routes to a bound function Inspect the catalog with `inspect subject:functions`. For a known Function, inspect its `params` and `row` schemas and call `bind`. For a question in words, `ask` selects a catalog Function and binds its scope. It keeps addresses written in the question or supplied in `params`; it does not invent an address to resolve a brand name. When an address is missing, a params suggestion can offer already-landed Bound Functions on that chain. It authors nothing. A question that no catalog Function answers returns `error` / `unsupported`, naming what it `needs`. `bind_prompt` is no longer advertised. The older authoring fields `address`, `context`, `shape`, and `explain` are not accepted by `ask`. This page retains its historical URL so existing links still work. ## A deliberate ask ```json {"tool":"ask","arguments":{"metric":"","chain_id":1,"function_slug":"","params":{"":""},"from_block":100,"to_block":110}} ``` Only `metric` and `chain_id` are required by the wire schema. `function_slug`, `params`, and paired block ends are optional inputs. Replace placeholders with the selected Function's advertised scope fields. The question is limited to 500 characters. An address not supplied by the operator must not be invented. ## A suggestion is a question A `suggested` outcome writes nothing. Its `kind` is `params` or `window`; show the returned `card`, chain facts, and missing parameters where provided. A params response may include up to five `options`, each naming a landed `function_slug`, `bound_function_id`, and the `params` that would bind the asked Function. Let the operator choose one, if any. Merge the answer into the original request under the field named by `resume`, then repeat. Never choose a period or economic tradeoff merely because it looks reasonable. Supply both block ends together. ## Examine the resolution On success, preserve the returned card's bound-function identity and scope, `description`, `row`, `created`, `follow`, and retention disclosure. `name` is a debug label, not an identity accepted by data tools. `follow.already_indexed` and an optional checkpoint do not prove full coverage of your requested window. Inspect coverage, materialize missing blocks, settle authorized quotes, inspect jobs, read values, and request provenance. Resolution does not deliver a paid read or bypass the usual execution path. Unsupported questions require an appropriate catalog capability; repeating the request cannot author one. --- # Agent recipes Source: https://testril.si/agents/recipes/ These recipes describe orchestration. Template availability, parameters, chains, prices, and ranges must be discovered from the live service. All example IDs and addresses are placeholders. ## Read a token balance 1. Discover `erc20.token_balance` if it is present; inspect its bind parameters. A single token and wallet use `reads: {tokens: [...], owners: [...]}`. 2. Bind the explicit token, wallet, and configured chain. 3. Inspect coverage and the chain head, then select one block as `[n, n+1)`. Confirm finality separately if the application requires it. 4. Materialize a missing block under authorized spending. 5. Read the balance through its own pay gate. 6. Report the declared value type and block; request provenance for the same bound identity and window. A balance read at the latest materialized block is convenient, but a trading decision may require newer coverage. Compare landmarks before describing it as current. ## Measure transfers over a period Discover a transfer-volume template, such as `erc20.token_volume` if advertised. Inspect its exact scope holes and definition. Establish block boundaries for the requested period, bind the contract and chain, then use inspect/materialize/read. Do not substitute transferred amount for trade volume, wallet net flow, or unique senders without a matching definition. If the template does not express the requested quantity, use [question resolution](/agents/authoring/) to check catalog support; unsupported requests cannot create a new Function. ## Resolve ownership If `erc721.owner_of` is advertised, inspect its token/contract parameters before binding. Read the explicit block window and preserve the chain and token scope. Ownership at a historical block is not ownership at the current head. ## Produce an auditable report For each measure, retain its bound identity, operator's intended definition, exact window, returned field type and values, payment receipt, and provenance record. State which source digests were compared. Keep unsupported interpretations out of the numerical result. ## Resume after interruption Recover the stored quote, original paid call, payment response, and job ID. Inspect the bound-function geometry and any known job. If already running, wait; if done, read. A lost answer can be requested again with the same spent payment or payment-identifier without a second charge. If the quote expired before any payment, make the same unpaid call for a new quote. If the response is `payment_recovery_pending`, retry the original paid call and do not sign another payment. Treat timeout as unknown state until inspected. ## Read only the needed rows Use `read.key` to restrict a leading run of key parts advertised by the Function's row schema. For example, `{"token":""}` applies when `token` is the first key part. Every matching row includes all its attributes. Narrow an explicit window when useful. `attributes`, `entity`, `cursor`, and `limit` are retired inputs. Do not assume a key filter changes the price; the quote decides. ## Reuse another agent's work Share the endpoint and bound-function identity with its chain/scope record. Inspect coverage independently and read the required window. Shared materialization reduces repeated computation; reads still use the current read pay gate. Keep any claim-right ownership separate from the identity of an agent that happens to query the dataset. --- # Data liquidity and rewards Source: https://testril.si/agents/rewards/ Testril's data-liquidity model connects paid materialization, titled coverage, subsequent paid reads, and wallet accounting. For agents, the useful questions are: what was bought, what is still covered, what was earned, and what can be claimed? ## Inspect a wallet's accounting ```json {"tool":"inspect","arguments":{"subject":"nft","wallet":""}} ``` The response's `nfts` array has one card per claim-right the wallet holds. Each card includes its `nft`, `nft_chain_id`, `nft_contract`, `purchases`, and lifetime totals such as `paid`, `refunded`, `cost`, `earned`, `claimed`, `unclaimed`, and `pnl`. NFT accounting is the deliberate exception to the 0.9.0 `{atoms, usd}` money object: its monetary values, including `cost`, `earned`, and signed `pnl`, remain bare decimal strings of USDC atoms. Preserve exact integer arithmetic. A purchase can appear at payment before data delivery. Inspect the relevant job and coverage separately. Each purchase records its quote ID, bound-function ID, original requested window, and accounting. The original window may include blocks already covered even though only unpaid holes were bought. Do not equate requested block count with charged block count. Purchase earnings and read counts follow the blocks actually delivered. When historical attribution cannot be reconstructed, a purchase omits `earned`, `pnl`, and `reads`. On each card, sum known purchase earnings plus `earned_unattributed` to reconcile lifetime `earned`; missing attribution does not forfeit unclaimed rewards. ## Data availability is a different ledger Expiry and materialized geometry live on `inspect subject:bound_function`. Wallet accounting is not a guarantee that all purchased blocks are currently available. A purchase is titled before its claim-right mint finishes. During that interval its card carries `minting: true`; retry inspection until its NFT is ready. Cards follow current ownership at the chain tip. The response's `unavailable` array lists claim-rights on a chain that could not be read as `{nft, nft_chain_id, reason}` beside cards that were read; it is not a zero balance. The original payer may no longer hold a claim-right. ## Claim existing rewards ```json {"tool":"claim_rewards","arguments":{"nft":""}} ``` Inspect the selected card's unclaimed amount first. Keep the actual claim response and re-inspect the wallet afterward. The implementation handles eligible USDC atoms; it does not promise that a newly bought window will earn enough to cover its cost. The minimum claim is 10,000 atoms ($0.01). Below that minimum, the response reports claimed `"0"`, `reason: "below_minimum"`, and no transfer. A card still marked `minting: true` returns `claim_right_minting` and pays nothing. Claims follow current claim-right ownership; an unreadable ownership record is a refusal, not proof that no rewards exist. Rewards are paid on the NFT's recorded collection network even when read fees were paid on other networks. The claim response's `network`, when present, identifies that payout chain; `claim_rewards` takes no network selector. A wallet can hold one claim-right per payment chain and can receive more through on-chain transfers. Its first materialize payment on a payment chain starts a claim-right there; if the wallet later no longer owns it, its next purchase on that chain starts another. Keep a pending claim's network during recovery. ## Paid reads and shared value A paid read of titled coverage can contribute to holder accounting according to the implementation's pricing and overlap rules. A quote alone is not earned revenue. Coverage overlap, retention, prices, and the actual completed paid read matter. Use the current card and settlement records as evidence instead of projecting a yield from a marketing tagline.