Cycle Mechanics
A cycle is the core accountability unit in ShipLock: a time window in which the project must produce an accepted check-in.
Cycle Timeline
Every cycle follows this sequence:
- Cycle starts → countdown to deadline begins
- Deadline hits → grace buffer begins
- Grace ends → if no accepted check-in → project becomes slashable
Formula
deadline = cycleStartAt + cadence
graceEndsAt = deadline + grace
Canonical Time Variables
Every project stores these timestamps on-chain:
| Variable | Description |
|---|---|
cycleNonce | Integer cycle counter (starts at 0) |
cycleStartAt | Timestamp when the current cycle began |
nextDeadlineAt | Deadline timestamp for current cycle |
graceEndsAt | nextDeadlineAt + grace |
lastAcceptedCheckinAt | Timestamp of last accepted check-in (for streaks) |
lastSlashedCycleNonce | Cycle nonce that was last slashed (prevents double-slash) |
Both nextDeadlineAt and graceEndsAt are stored explicitly to avoid repeated computation and enforcement ambiguity.
Project States
These are derived from canonical timestamps — not stored as a field. The UI computes them in real time.
ACTIVE
now <= nextDeadlineAt
Project is within its deadline window. Either already has an accepted check-in or still has time.
AT_RISK
now is within riskWindow of deadline (e.g. 12h before)
AND hasAcceptedInCycle == false
Purely a UX state — drives alerts and highlighting. Not an on-chain enforcement boundary.
VIOLATION (Slashable)
now > graceEndsAt
AND hasAcceptedInCycle == false
This is the enforcement boundary. The project can now be slashed by anyone.
SLASHED
lastSlashedCycleNonce == cycleNonce
The cycle has been resolved by penalty. Prevents re-slashing the same missed cycle.
Check-in Lifecycle
Each check-in has its own lifecycle within a cycle:
| Field | Description |
|---|---|
submittedAt | When the check-in was posted |
endorseWindowEndsAt | When endorsements close |
totalWeight | Running sum of endorsement weights |
endorserCount | Number of unique endorsers |
status | PENDING → ACCEPTED or REJECTED |
PENDING
During the endorse window: watchers can endorse.
ACCEPTED
Reached when within the endorse window:
totalWeight >= thresholdWeight
AND endorserCount >= minEndorsers
AND now <= endorseWindowEndsAt
When a check-in becomes ACCEPTED, the cycle is immediately satisfied and the project advances to the next cycle.
REJECTED
When now > endorseWindowEndsAt and thresholds were never met.
Critical Rule: Endorse Window vs Grace
The endorse window must end before grace ends. Otherwise ambiguity arises: a project could be slashable while endorsements are still happening.
endorseWindowHours <= graceHours
ShipLock enforces this constraint at project creation.
Cycle Advancement
When a check-in becomes ACCEPTED, the cycle advances immediately:
lastAcceptedCheckinAt = now
cycleNonce += 1
cycleStartAt = now
nextDeadlineAt = now + cadence
graceEndsAt = nextDeadlineAt + grace
This is a rolling cadence: shipping sooner resets the timer. There is no fixed weekly schedule — accountability is continuous.
One Accepted Check-in Per Cycle
Only the first check-in that reaches ACCEPTED satisfies the cycle.
After that:
- Additional check-ins can be posted for transparency
- But they do not affect cycle timing or slashability
For MVP: once a check-in is accepted, new check-ins are blocked until the next cycle starts.
Multiple Attempts Per Cycle
A builder can submit multiple check-ins in the same cycle:
- Check-in #1: weak proof → no endorsement → rejected
- Check-in #2: improved proof → endorsement → accepted
This creates a strong incentive: "If the community isn't convinced, improve your proof."
Window Overlap Prevention
To keep things clean, only one PENDING check-in is allowed per project at a time. Before submitting, the protocol verifies no check-in exists with status PENDING.
What Happens If a Check-in Is Never Endorsed?
That's normal and intended:
- Builder submits check-in at time T
- Endorse window ends, thresholds not met
- Check-in becomes REJECTED
- Cycle continues — builder can try again
- If grace ends with no accepted check-in → slashable
Rejection is passive. The project is not punished unless grace ends with no accepted check-in.
Edge Cases
Exactly at graceEndsAt
Strict inequality: slashable only if now > graceEndsAt (not >=).
Endorsement at exact window end
Endorsements count only if now <= endorseWindowEndsAt.
Indexer lag
UI must not rely on indexer for slash eligibility. If the indexer is behind, show a warning and allow direct RPC check.
Project owner disappears
No issue. Enforcement is permissionless. The project either ships or gets slashed.