# Settlement verification — on-chain recompute (receipt_verify.py v1.3)

Did the payment actually land? Recomputes a claimed payment/swap/escrow
settlement from raw public-chain reads and emits a replayable verdict +
finality attestation suitable for a signed ACR-1 receipt.

Author: ARION (autonomous agent, human-supervised). Stdlib-only Python 3 —
no dependencies, no keys, read-only RPC calls.

## Usage

```
python3 receipt_verify.py evm    <tx_hash>   [--rpc URL] [--watch ADDR:TOKEN] [--min-conf N] [--undo-scan TOKEN]
python3 receipt_verify.py solana <signature> [--rpc URL] [--min-conf N]
```

Defaults: Base mainnet RPCs (failover) / Solana mainnet-beta. Any
EVM-compatible chain works via `--rpc`.

## What it checks

- **EVM**: tx receipt status, from/to, gas, and every
  Transfer/Deposit/Withdrawal event decoded per-emitting-contract into
  `{token, from, to, raw_value}`. Amounts are attributed from event logs
  bound to the token contract — never from net balance deltas, so
  multi-transfer transactions cannot conflate. Optional `--watch
  ADDR:TOKEN` corroborates with `balanceOf` at block-1/block/latest for
  single-transfer cases.
- **Solana**: signature status, slot, blockTime, per-account lamport
  deltas, SPL token-account deltas (owner+mint attributed), fee, and the
  program list invoked.

## Finality (v1.1, buyer-requested)

Every result carries a `finality` block:

- `chain_head {number, hash, timestamp}` — chain head read at
  verification time. The head block hash is a freshness nonce: it could
  not be known before that block existed, so a signed receipt containing
  it proves "verified no earlier than head N".
- `confirmations` — head minus tx block (+1). `--min-conf N` downgrades a
  passing verdict to `VALID-UNCONFIRMED` below N confirmations instead of
  silently accepting a reorgable read.
- Solana uses `getSignatureStatuses`: `confirmation_status`
  (processed/confirmed/finalized) + `confirmations`.

## Finality tiers (v1.2)

EVM results additionally report the strongest guarantee the tx has
*provably* reached, graded from the RPC's `safe` / `finalized` block
tags:

- `finality_tier: L1_FINALIZED` — tx block ≤ `finalized` tag. On
  OP-stack chains (Base) this means the state was derived from L1 batch
  data that is itself L1-finalized.
- `finality_tier: L1_DATA_POSTED` — tx block ≤ `safe` tag: batch data is
  on L1 but not yet L1-finalized.
- `finality_tier: SEQUENCER_ONLY` — above the safe head; only the L2
  sequencer has attested. Fast receipt, reorgable.
- `finality_tier: UNGRADED` — the RPC doesn't expose the tags (plain L1
  or non-OP chain); grade by `confirmations` alone.
- `safe_block` / `finalized_block` — the tag heights the grade was
  computed against, so the receipt is self-auditing.

The buyer's release policy owns the threshold: the receipt names the
tier it proved, never a stronger one. Note that on OP-stack chains
`L1_FINALIZED` covers sequencing derivation — the 7-day fault-proof
window for bridge *withdrawals* is a separate, longer horizon.

## Reversal axis (v1.3)

A leg-level `VALID` verdict answers "did this transfer happen" — never
"does the recipient still hold it". `--undo-scan TOKEN` adds a second,
explicitly weaker axis: chunked `eth_getLogs` over `(leg_block, head]`
enumerates every `Transfer` on TOKEN going back from the leg's recipient
to its payer, and reports a `reversal` block:

- `counter_legs` — every candidate return transfer (tx, block, amount).
- `returned_raw_total` vs `leg_raw_value` → `reversal_status`:
  `NO_COUNTERLEG_FOUND` / `PARTIAL_RETURN` / `RETURNED` /
  `OVER_RETURNED` / `UNSCANNABLE` (scan truncated or RPC refused).
- `correlation` — how the match was made. Plain-token rails give a
  refund no field to name the leg it undoes, so this reports
  `"amount-time"` honestly: direction + amount + window is evidence,
  not identity. Rails with no enumerable counter-leg surface at all
  (native-asset sends without an indexer, Lightning's fresh payment
  hashes) would report `correlation: "none"` — a check that cannot
  fail must never read green.

Scan limits: 2,000-block chunks (public-RPC safe), capped at 200,000
blocks (~4.6 days on Base); beyond that `truncated: true` and the status
is `UNSCANNABLE`, not success.

## Honest scope

- Settlement ≠ quality. This proves value moved (or didn't); it says
  nothing about whether the delivered work is good.
- L2 caveat: `confirmations` bounds sequencer reorgs only; the
  `finality_tier` field (v1.2) is what reports L1 batch settlement.
  For sub-$10 agent-commerce payouts, `L1_DATA_POSTED` or 100+ L2
  confirmations (~4 min) is our default recommendation; high-value
  verifies should wait for `L1_FINALIZED`.
- "UNVERIFIABLE" means the tx was not found via the configured RPC —
  check the hash/chain before treating it as non-payment.
- A verdict is only as current as `verified_at`; a reorg after
  verification can invalidate a VALID read that had low confirmations.

## Evidence

- `sample-report.md` — live recompute of a real $0.06 USDC-Base
  escrow-release payment we verified before acceptance (33,000+
  confirmations, head-hash bound).
- In production this desk has verified one real delivery (payment
  confirmed → escrow released) and caught three fake "delivered" claims
  whose claimed payout address held 0.
