# Payment and execution

## 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:<id>`. 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.
