# Claims

> The LOPW Claims contract: the seven checks in order (animated), outcomes, every error code, the payout rule, records, events and functions.

*LOPW documentation · Protocol reference.* Canonical page: https://lopw.com/docs/protocol/claims.html · This Markdown twin: https://lopw.com/docs/protocol/claims.md · Index of all pages: https://lopw.com/llms.txt · Everything in one file: https://lopw.com/llms-full.txt

A claim is one transaction with one outcome. Anyone may submit it; the payout always goes to the `buyer` named in the receipt. Evidence or preconditions that fail return an error and record nothing. When the evidence is valid, the validator decides: a proven breach pays, an unproven one is recorded as rejected.

*Claims contract*

```text
claim(predicate_id: u32, offer: SignedOffer, receipt: SignedReceipt, extra: ClaimExtra) -> Result<ClaimOutcome, ClaimError>
// ClaimOutcome: Paid(amount: i128) | Rejected(reason: u32)      reason 1 = NotProven, 2 = NotVerifiable
```

## The pipeline

1. **Version.** Both `offer.version` and `receipt.version` equal 1 (fails with: UnsupportedVersion)
2. **1 · Service.** The service exists in the registry and is not Closed (fails with: UnknownService)
3. **2 · Receipt signature.** Valid for the key at `receipt.key_version`, and that key was not retired before `served_at` (fails with: BadSignature · KeyRetired)
4. **3 · Offer.** `offer.registry` is this registry, `service_id` matches, `receipt.offer_hash` equals the offer hash, offer signature valid (fails with: OfferMismatch · BadSignature)
5. **4 · Claim window.** Terms found for `offer.terms_hash`; `served_at ≤ now ≤ served_at + claim_window` (fails with: OfferMismatch · WindowClosed)
6. **5 · Not paid before.** No paid claim recorded for `(service_id, payment_reference)` (fails with: AlreadyClaimed)
7. **6 · Predicate and stake.** `predicate_id` is in the terms; stake is above zero; for P1 the second receipt’s signature is verified too (fails with: PredicateNotOffered · NoStake)
8. **7 · Validator.** The validator for the predicate returns proven (fails with: Rejected(NotProven) · Rejected(NotVerifiable) · ValidatorNotSet)
9. **Payout.** Record the claim, then `slash(service_id, buyer, max_claim)` on the registry (fails with: SlashRateLimited · NoStake)

## Checks, in order

1. **Version.** `offer.version` and `receipt.version` are both 1; else `UnsupportedVersion`.
2. **Service.** `get_service(receipt.service_id)` succeeds and the state is not `Closed`; else `UnknownService`.
3. **Receipt signature.** The key at `receipt.key_version` exists (else `BadSignature`); it was not retired before `served_at` (else `KeyRetired`); the Ed25519 signature over `receipt_hash` verifies (else the transaction traps).
4. **Offer.** `offer.registry` is this registry and `offer.service_id == receipt.service_id` and `receipt.offer_hash == offer_hash(offer)`; else `OfferMismatch`. The key at `offer.key_version` exists (else `BadSignature`) and the offer signature verifies.
5. **Claim window.** `get_terms(service_id, offer.terms_hash)` succeeds (else `OfferMismatch`); `served_at ≤ now` and `now ≤ served_at + claim_window`; else `WindowClosed`.
6. **Not paid before.** No paid record exists for `(service_id, payment_reference)`; else `AlreadyClaimed`.
7. **Predicate and stake.** `predicate_id` is in `terms.predicates` (else `PredicateNotOffered`); `stake > 0` (else `NoStake`). For P1, the second receipt in `extra` passes the same signature and key-retirement checks.
8. **Validator.** A validator is set for the predicate (else `ValidatorNotSet`). `Ok(())` is proven; `Err(NotProven)` is `Rejected(1)`; any other failure is `Rejected(2)`. Rejections emit `ClaimRejected` and return `Ok(Rejected(reason))`.
9. **Payout.** The claim record is written first, then `slash(service_id, receipt.buyer, offer.max_claim)` is called on the registry, which caps at the remaining stake (and at `max_payout` if set) and applies the slash rate limit. `EpochCapReached` from the registry returns `SlashRateLimited`, which rolls the record back so the claim can be retried; any other slash failure returns `NoStake`. On success the record is updated with the amount, `ClaimPaid` is emitted and `Ok(Paid(amount))` returned.

## Outcomes

| Result | Recorded? | Event | Meaning |
| --- | --- | --- | --- |
| `Ok(Paid(amount))` | Yes, under `(service_id, payment_reference)` | `ClaimPaid` | Breach proven; `amount` transferred to `receipt.buyer` from the service’s stake in this transaction |
| `Ok(Rejected(1))` | Event only | `ClaimRejected` reason 1 | Evidence valid, validator did not prove a breach (NotProven). A later valid claim for the same payment is still possible |
| `Ok(Rejected(2))` | Event only | `ClaimRejected` reason 2 | The breach could not be checked (NotVerifiable): no oracle evidence or observation, or the validator failed |
| `Err(ClaimError)` | No | none | Evidence or preconditions failed; the transaction fails and nothing changes |

**Payout rule.** `payout = min(offer.max_claim, stake remaining)`, further capped by the launch limit `max_payout` when it is non-zero, and subject to the slash rate limit (at most 50% of epoch-start stake per epoch, first slash of an epoch always allowed). Recording the claim before slashing means a payout can never be made twice for one payment, even if the slash fails part-way.

## Error codes

| Code | Error | Cause |
| --- | --- | --- |
| 1 | `UnknownService` | No such service, or it is Closed |
| 2 | `BadSignature` | No signing key at the cited `key_version` (a signature that does not verify traps the transaction instead). No penalty to the seller |
| 3 | `OfferMismatch` | Offer names another registry or service, the receipt does not cite the supplied offer, or the offer’s terms are unknown |
| 4 | `WindowClosed` | Claim window has passed, or `served_at` is in the future |
| 5 | `AlreadyClaimed` | A claim was already paid for this payment |
| 6 | `PredicateNotOffered` | The terms do not include this predicate |
| 7 | `NoStake` | Stake is zero, or the registry refused the slash for a reason other than the rate limit |
| 8 | `ValidatorNotSet` | No validator is registered for the predicate |
| 9 | `KeyRetired` | The signing key was retired at or before the receipt’s `served_at` |
| 10 | `UnsupportedVersion` | Offer or receipt version is not 1 |
| 11 | `SlashRateLimited` | The epoch cap is reached; retry in the next epoch while the window is open |
| 12 | `NoPendingChange` | Admin: no validator change is pending (apply or cancel) |
| 13 | `ChangeNotReady` | Admin: the pending validator change’s time lock has not passed |
| 14 | `InvalidPredicate` | Admin: predicate ID outside 1 to 6 |

The TypeScript binding maps these codes to names (`CLAIM_ERRORS`), so a failed submission reads as `WindowClosed (#4)`.

## Claim records

| `ClaimRecord` field | Meaning |
| --- | --- |
| `predicate_id` | The predicate that was proven |
| `buyer` | Who was paid |
| `amount` | What was paid |
| `paid_at` | Ledger time of the claim |
| `receipt_hash` | Which receipt proved it |

Records are keyed by `(service_id, payment_reference)` in persistent storage; `get_claim(service_id, payment_reference)` returns one. Their lifetime is extended on every claim (threshold 30 days, extended to 60 days, counted in ledgers), far beyond the longest claim window.

## Events

| Event | Topics | Data |
| --- | --- | --- |
| `ClaimPaid` | `service_id`, `predicate_id` | `buyer`, `amount`, `payment_reference` |
| `ClaimRejected` | `service_id`, `predicate_id` | `reason` (1 NotProven, 2 NotVerifiable), `payment_reference` |
| `ValidatorSet` | `predicate_id` | `validator` |
| `ValidatorChangeProposed` | `predicate_id` | `validator`, `eta` |
| `ValidatorChangeCancelled` | `predicate_id` | — |

The registry additionally emits `Slashed` and, when the payout takes stake below the minimum, `StateChanged` to `Suspended`.

## Functions

*Claims contract interface*

```text
__constructor(admin, registry, change_delay)                        // change_delay > any unbonding delay (93,600 s on the first deployment)
claim(predicate_id, offer, receipt, extra) -> Result<ClaimOutcome, ClaimError>
get_claim(service_id, payment_reference) -> Option<ClaimRecord>
get_validator(predicate_id) -> Option<Address>
get_registry() -> Address
get_pending_validator(predicate_id) -> Option<PendingValidator { validator, eta }>
set_validator(predicate_id, validator) -> Result<u64, ClaimError>  // admin; first setting immediate (returns 0), replacement time-locked (returns eta)
apply_validator(predicate_id) -> Result<Address, ClaimError>       // anyone, after eta
cancel_validator_change(predicate_id) -> Result<(), ClaimError>    // admin
extend_instance_ttl()                                               // anyone; keeps instance storage alive
```
