Redemption
The job lifecycle, request, dispatch, attestation quorum, settle or refund, with a timeout escape hatch.
Redemption
Redemption is where a voucher stops being a token and becomes a running job. It is the only path that destroys a voucher.
The problem the design has to solve: the work happens off-chain, on physical hardware, and the chain cannot observe it. So the contract holds the voucher in escrow while an attestation set reports what happened, and it resolves to exactly one of three terminal states, settled, refunded, or reclaimed.
Lifecycle
request
│
▼
┌────────► Requested ────────────┐
│ │ │
│ dispatch (no dispatch
│ │ before deadline)
│ ▼ │
│ Dispatched │
│ ╱ ╲ │
│ quorum: ok quorum: failed │
│ ╱ ╲ │
▼ ▼ ▼ ▼
Reclaimed Settled Refunded Reclaimed
(voucher (voucher (voucher (voucher
returned) burned) returned) returned)
1. Request
The holder calls requestJob(id, quantity, jobSpecHash, deadline). The vouchers transfer into the redemption escrow and a job record is opened.
jobSpecHash commits to the workload, image, entrypoint, data location, resource shape, without publishing it. The full spec goes to the operator over a side channel. The chain only needs the hash so that both parties are bound to the same job.
deadline must fall before the vouchers' expiry. The contract enforces this. A job cannot be scheduled into a window in which its own vouchers are already dead.
2. Dispatch
An operator claims the job by calling dispatch(jobId) and posting a bond. Dispatch is the operator asserting that it has capacity matching the SKU and will run the work. From this point the operator is accountable: a failed job is a slashable event against the bond, not merely a non-event.
The bond parameters are TBD.
If no operator dispatches before the deadline, the job never leaves Requested and the holder takes the timeout path below.
3. Attestation quorum
When the job finishes, attesters observe the outcome and sign a receipt over (jobId, jobSpecHash, outcome). The contract accepts signatures until m of n registered attesters agree, where m and n are TBD.
Attesters are independent of the operator running the job. A single operator self-reporting delivery is not a proof of anything; a quorum of distinct parties is a considerably better one. It is not a cryptographic proof of computation, and these docs do not claim it is. It is an economic assumption: a quorum of bonded, identifiable attesters is expensive to corrupt, and the assumption is stated plainly here so a reader can price it.
Two outcomes carry quorum:
- Delivered. The job ran to completion within the agreed resource envelope.
- Not delivered. The operator failed to run it, or ran it out of spec.
4a. Settle
On a delivered quorum, settle(jobId) burns the escrowed vouchers and releases the operator's bond. Supply drops by the redeemed quantity. This is the intended end state: energy absorbed at a solar site months earlier is now a completed job, and the claim on it no longer exists.
4b. Refund
On a not-delivered quorum, the escrowed vouchers return to the holder in full. The operator's bond is slashed under the policy in the operator agreement, parameters TBD.
Refund returns the same token ids that went in, so the vouchers come back with their original expiry. A failed job does not extend the life of your voucher. If the failure happens close to a bucket boundary you may be left with vouchers that are hard to reuse in time. Requesting jobs with headroom before expiry is the holder's protection against this.
The timeout escape hatch
The failure that matters most is not a job that fails. It is a job that produces no answer at all, the operator vanishes, the attesters go quiet, the network partitions. Without a way out, the vouchers sit in escrow forever and the holder has lost them to a bug rather than to a decision.
So every job has an unconditional exit:
function reclaim(uint256 jobId) external {
Job storage j = jobs[jobId];
require(msg.sender == j.holder, "not holder");
require(block.timestamp > j.deadline + GRACE, "too early");
require(j.state == Requested || j.state == Dispatched, "resolved");
// no quorum was reached; return escrow to the holder
j.state = Reclaimed;
VOUCHER.safeTransferFrom(address(this), j.holder, j.id, j.quantity, "");
}
Three properties make this an escape hatch rather than an ordinary path:
- It is permissionless for the holder. No operator, attester, governance action or admin key is required. If the deadline plus grace has passed and no quorum has resolved the job, the holder can always get their vouchers back.
- It cannot race a quorum. Once a job reaches
SettledorRefundedthe state check fails. There is no window in which both a settlement and a reclaim can succeed. - It has no discretion. The condition is a timestamp comparison. Nobody decides whether you qualify.
GRACE exists so that a quorum which is merely slow, attesters submitting a few blocks late, is not overtaken by a reclaim. Its value is TBD.
Reclaim returns the vouchers as they were, including their expiry. If the whole ordeal has run past the expiry bucket, the returned vouchers are dead on arrival. That is the residual risk of redeeming late, and it is another argument for leaving headroom.
Summary of terminal states
| State | Voucher | Operator bond | Trigger |
|---|---|---|---|
| Settled | Burned | Released | Quorum: delivered |
| Refunded | Returned to holder | Slashed | Quorum: not delivered |
| Reclaimed | Returned to holder | Released or slashed per policy | Deadline + grace elapsed, no quorum |
Every job ends in exactly one of these. There is no path in which escrowed vouchers are neither burned nor returned.