pragma solidity ^0.8.13; contract HeartbeatGapFailureReceipt { struct Agent { uint96 bond; uint64 lastBlock; uint64 interval; bool active; } mapping(address => Agent) public agents; event Registered(address indexed agent, uint96 bond, uint64 interval, uint64 blockNumber); event Heartbeat(address indexed agent, uint64 blockNumber); event MissedHeartbeat( address indexed agent, address indexed filer, uint96 bond, uint64 lastBlock, uint64 interval, uint64 filedAt, uint96 reward, uint96 burned ); function register(uint64 interval) external payable { require(interval > 0, "IntervalZero"); require(!agents[msg.sender].active, "AlreadyActive"); uint96 bond = uint96(msg.value); require(bond > 0, "BondZero"); require(bond == msg.value, "BondTooHigh"); agents[msg.sender] = Agent({ bond: bond, lastBlock: uint64(block.number), interval: interval, active: true }); emit Registered(msg.sender, bond, interval, uint64(block.number)); } function heartbeat() external { Agent storage agent = agents[msg.sender]; require(agent.active, "NotActive"); agent.lastBlock = uint64(block.number); emit Heartbeat(msg.sender, uint64(block.number)); } function fileFailure(address agentAddress) external { Agent storage agent = agents[agentAddress]; require(agent.active, "NotActive"); require(block.number >= uint256(agent.lastBlock) + uint256(agent.interval), "NotLapsed"); uint96 bond = agent.bond; uint64 lastBlock = agent.lastBlock; uint64 interval = agent.interval; uint96 reward = bond / 2; uint96 burned = bond - reward; agent.active = false; agent.bond = 0; agent.lastBlock = 0; agent.interval = 0; (bool ok, ) = payable(msg.sender).call{value: reward}(""); require(ok, "RewardFailed"); (bool burnOk, ) = payable(address(0)).call{value: burned}(""); require(burnOk, "BurnFailed"); emit MissedHeartbeat(agentAddress, msg.sender, bond, lastBlock, interval, uint64(block.number), reward, burned); } }