// SPDX-License-Identifier: MIT pragma solidity 0.8.30; /// @notice Self-reported public activity intervals. No rewards or proof of real work. contract ChronyxIntervals { struct Interval { bytes32 topic; uint64 startedAt; uint64 endedAt; } mapping(address => uint256) public intervalCount; mapping(address => uint256) public activeInterval; mapping(address => mapping(uint256 => Interval)) public intervals; event IntervalStarted(address indexed account, uint256 indexed id, bytes32 topic, uint64 startedAt); event IntervalEnded(address indexed account, uint256 indexed id, uint64 endedAt); error EmptyTopic(); error AlreadyActive(); error NoActiveInterval(); error StaleInterval(); function start(bytes32 topic) external returns (uint256 id) { if (topic == bytes32(0)) revert EmptyTopic(); if (activeInterval[msg.sender] != 0) revert AlreadyActive(); id = ++intervalCount[msg.sender]; uint64 timestamp = uint64(block.timestamp); intervals[msg.sender][id] = Interval(topic, timestamp, 0); activeInterval[msg.sender] = id; emit IntervalStarted(msg.sender, id, topic, timestamp); } function end(uint256 expectedId) external { uint256 id = activeInterval[msg.sender]; if (id == 0) revert NoActiveInterval(); if (id != expectedId) revert StaleInterval(); uint64 timestamp = uint64(block.timestamp); intervals[msg.sender][id].endedAt = timestamp; activeInterval[msg.sender] = 0; emit IntervalEnded(msg.sender, id, timestamp); } }