// SPDX-License-Identifier: MIT pragma solidity ^0.8.13; contract HashLockedMilestoneEscrow { error Unauthorized(); error InvalidState(); error TransferFailed(); address public immutable creator; address public immutable agent; uint256 public immutable share; uint256 public immutable deadline; bytes32[] private milestoneHashes; uint256 public currentMilestone; event MilestoneReleased(uint256 indexed index, bytes preimage, uint256 amount); event Refunded(uint256 amount); constructor( address _agent, bytes32[] memory _milestoneHashes, uint256 _deadline ) payable { if (_milestoneHashes.length == 0) revert InvalidState(); if (_deadline <= block.timestamp) revert InvalidState(); if (msg.value == 0) revert InvalidState(); if (msg.value % _milestoneHashes.length != 0) revert InvalidState(); creator = msg.sender; agent = _agent; share = msg.value / _milestoneHashes.length; deadline = _deadline; milestoneHashes = _milestoneHashes; } function reveal(bytes calldata preimage) external { if (msg.sender != agent) revert Unauthorized(); if (currentMilestone >= milestoneHashes.length) revert InvalidState(); if (block.timestamp > deadline) revert InvalidState(); if (keccak256(preimage) != milestoneHashes[currentMilestone]) revert InvalidState(); uint256 amount = share; currentMilestone++; (bool ok, ) = payable(msg.sender).call{value: amount}(""); if (!ok) revert TransferFailed(); emit MilestoneReleased(currentMilestone - 1, preimage, amount); } function refund() external { if (msg.sender != creator) revert Unauthorized(); if (block.timestamp <= deadline) revert InvalidState(); uint256 amount = share * (milestoneHashes.length - currentMilestone); if (amount == 0) revert InvalidState(); currentMilestone = milestoneHashes.length; (bool ok, ) = payable(msg.sender).call{value: amount}(""); if (!ok) revert TransferFailed(); emit Refunded(amount); } }