LOPWDocumentation

Docs / Protocol reference

Claims

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
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

The claim 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

ResultRecorded?EventMeaning
Ok(Paid(amount))Yes, under (service_id, payment_reference)ClaimPaidBreach proven; amount transferred to receipt.buyer from the service’s stake in this transaction
Ok(Rejected(1))Event onlyClaimRejected reason 1Evidence valid, validator did not prove a breach (NotProven). A later valid claim for the same payment is still possible
Ok(Rejected(2))Event onlyClaimRejected reason 2The breach could not be checked (NotVerifiable): no oracle evidence or observation, or the validator failed
Err(ClaimError)NononeEvidence 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

CodeErrorCause
1UnknownServiceNo such service, or it is Closed
2BadSignatureNo signing key at the cited key_version (a signature that does not verify traps the transaction instead). No penalty to the seller
3OfferMismatchOffer names another registry or service, the receipt does not cite the supplied offer, or the offer’s terms are unknown
4WindowClosedClaim window has passed, or served_at is in the future
5AlreadyClaimedA claim was already paid for this payment
6PredicateNotOfferedThe terms do not include this predicate
7NoStakeStake is zero, or the registry refused the slash for a reason other than the rate limit
8ValidatorNotSetNo validator is registered for the predicate
9KeyRetiredThe signing key was retired at or before the receipt’s served_at
10UnsupportedVersionOffer or receipt version is not 1
11SlashRateLimitedThe epoch cap is reached; retry in the next epoch while the window is open
12NoPendingChangeAdmin: no validator change is pending (apply or cancel)
13ChangeNotReadyAdmin: the pending validator change’s time lock has not passed
14InvalidPredicateAdmin: 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 fieldMeaning
predicate_idThe predicate that was proven
buyerWho was paid
amountWhat was paid
paid_atLedger time of the claim
receipt_hashWhich 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

EventTopicsData
ClaimPaidservice_id, predicate_idbuyer, amount, payment_reference
ClaimRejectedservice_id, predicate_idreason (1 NotProven, 2 NotVerifiable), payment_reference
ValidatorSetpredicate_idvalidator
ValidatorChangeProposedpredicate_idvalidator, eta
ValidatorChangeCancelledpredicate_id—

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

Functions

Claims contract interface
__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