HashLockedMilestoneEscrow

hashlockedmilestoneescrow · conditional-settlement · 1623 B runtime · funds-movement

scores

source

// 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);
    }
}

published

published on testnet · 0xF672f6fd868550235280a90fc90be8654dF6227d · tx 0xae68d717977c3ba1… · base (84532) · testnet · source as deployed