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.
claim(predicate_id: u32, offer: SignedOffer, receipt: SignedReceipt, extra: ClaimExtra) -> Result<ClaimOutcome, ClaimError>
// ClaimOutcome: Paid(amount: i128) | Rejected(reason: u32) reason 1 = NotProven, 2 = NotVerifiableThe pipeline
The claim pipeline
- Version
Both
fails with: UnsupportedVersionoffer.versionandreceipt.versionequal 1 - 1 · Service
The service exists in the registry and is not Closed
fails with: UnknownService - 2 · Receipt signature
Valid for the key at
fails with: BadSignature · KeyRetiredreceipt.key_version, and that key was not retired beforeserved_at - 3 · Offer
fails with: OfferMismatch · BadSignatureoffer.registryis this registry,service_idmatches,receipt.offer_hashequals the offer hash, offer signature valid - 4 · Claim window
Terms found for
fails with: OfferMismatch · WindowClosedoffer.terms_hash;served_at ≤ now ≤ served_at + claim_window - 5 · Not paid before
No paid claim recorded for
fails with: AlreadyClaimed(service_id, payment_reference) - 6 · Predicate and stake
fails with: PredicateNotOffered · NoStakepredicate_idis in the terms; stake is above zero; for P1 the second receipt’s signature is verified too - 7 · Validator
The validator for the predicate returns proven
fails with: Rejected(NotProven) · Rejected(NotVerifiable) · ValidatorNotSet - Payout
Record the claim, then
fails with: SlashRateLimited · NoStakeslash(service_id, buyer, max_claim)on the registry
Checks, in order
- Version.
offer.versionandreceipt.versionare both 1; elseUnsupportedVersion. - Service.
get_service(receipt.service_id)succeeds and the state is notClosed; elseUnknownService. - Receipt signature. The key at
receipt.key_versionexists (elseBadSignature); it was not retired beforeserved_at(elseKeyRetired); the Ed25519 signature overreceipt_hashverifies (else the transaction traps). - Offer.
offer.registryis this registry andoffer.service_id == receipt.service_idandreceipt.offer_hash == offer_hash(offer); elseOfferMismatch. The key atoffer.key_versionexists (elseBadSignature) and the offer signature verifies. - Claim window.
get_terms(service_id, offer.terms_hash)succeeds (elseOfferMismatch);served_at ≤ nowandnow ≤ served_at + claim_window; elseWindowClosed. - Not paid before. No paid record exists for
(service_id, payment_reference); elseAlreadyClaimed. - Predicate and stake.
predicate_idis interms.predicates(elsePredicateNotOffered);stake > 0(elseNoStake). For P1, the second receipt inextrapasses the same signature and key-retirement checks. - Validator. A validator is set for the predicate (else
ValidatorNotSet).Ok(())is proven;Err(NotProven)isRejected(1); any other failure isRejected(2). Rejections emitClaimRejectedand returnOk(Rejected(reason)). - 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 atmax_payoutif set) and applies the slash rate limit.EpochCapReachedfrom the registry returnsSlashRateLimited, which rolls the record back so the claim can be retried; any other slash failure returnsNoStake. On success the record is updated with the amount,ClaimPaidis emitted andOk(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
__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