Property Maintenance Escrow

What it does:
A smart contract escrow that locks funds specifically for property maintenance and repairs, releasing payments only when approved work is completed.

Why it matters:
Maintenance disputes are common between landlords, tenants, and contractors. This contract ensures repair funds are used only for their intended purpose, with transparent approvals and payouts.

How it works:

  • The property owner funds a maintenance escrow.

  • Repair tasks are registered with budgets.

  • Contractors submit completion proofs.

  • An approver (landlord / DAO / oracle) verifies the work.

  • Funds are released directly to contractors upon approval.

      // SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

/**
 * @title PropertyMaintenanceEscrow
 * @author Nam
 * @notice Escrow contract for property maintenance and repair payments
 */
contract PropertyMaintenanceEscrow {

    // -------------------- DATA STRUCTURES --------------------

    enum TaskStatus { Pending, InProgress, Completed, Approved, Paid }

    struct Task {
        string description;     // Work description or IPFS hash
        address contractor;
        uint256 budget;
        TaskStatus status;
    }

    Task[] public tasks;

    address public owner;
    address public approver;
    uint256 public totalFunded;
    uint256 public totalPaid;

    // -------------------- EVENTS --------------------

    event Funded(uint256 amount);
    event TaskAdded(uint256 indexed taskId, string description, uint256 budget);
    event TaskStarted(uint256 indexed taskId);
    event TaskCompleted(uint256 indexed taskId);
    event TaskApproved(uint256 indexed taskId);
    event TaskPaid(uint256 indexed taskId, uint256 amount);

    // -------------------- MODIFIERS --------------------

    modifier onlyOwner() {
        require(msg.sender == owner, "Not owner");
        _;
    }

    modifier onlyApprover() {
        require(msg.sender == approver, "Not approver");
        _;
    }

    modifier validTask(uint256 _taskId) {
        require(_taskId  0, "Zero funding");
        totalFunded += msg.value;
        emit Funded(msg.value);
    }

    // -------------------- TASK MANAGEMENT --------------------

    /**
     * @notice Add maintenance task
     */
    function addTask(
        string calldata _description,
        address _contractor,
        uint256 _budget
    ) external onlyOwner {
        require(_contractor != address(0), "Invalid contractor");
        require(_budget > 0, "Invalid budget");
        require(
            totalPaid + _budget <= totalFunded,
            "Insufficient escrow"
        );

        tasks.push(
            Task({
                description: _description,
                contractor: _contractor,
                budget: _budget,
                status: TaskStatus.Pending
            })
        );

        emit TaskAdded(
            tasks.length - 1,
            _description,
            _budget
        );
    }

    /**
     * @notice Contractor starts task
     */
    function startTask(uint256 _taskId)
        external
        validTask(_taskId)
    {
        Task storage t = tasks[_taskId];
        require(msg.sender == t.contractor, "Not contractor");
        require(t.status == TaskStatus.Pending, "Invalid status");

        t.status = TaskStatus.InProgress;
        emit TaskStarted(_taskId);
    }

    /**
     * @notice Contractor marks task completed
     */
    function completeTask(uint256 _taskId)
        external
        validTask(_taskId)
    {
        Task storage t = tasks[_taskId];
        require(msg.sender == t.contractor, "Not contractor");
        require(t.status == TaskStatus.InProgress, "Invalid status");

        t.status = TaskStatus.Completed;
        emit TaskCompleted(_taskId);
    }

    // -------------------- APPROVAL --------------------

    /**
     * @notice Approver verifies and approves task
     */
    function approveTask(uint256 _taskId)
        external
        onlyApprover
        validTask(_taskId)
    {
        Task storage t = tasks[_taskId];
        require(t.status == TaskStatus.Completed, "Not completed");

        t.status = TaskStatus.Approved;
        emit TaskApproved(_taskId);
    }

    // -------------------- PAYMENT --------------------

    /**
     * @notice Pay contractor after approval
     */
    function payTask(uint256 _taskId)
        external
        onlyOwner
        validTask(_taskId)
    {
        Task storage t = tasks[_taskId];
        require(t.status == TaskStatus.Approved, "Not approved");

        t.status = TaskStatus.Paid;
        totalPaid += t.budget;

        payable(t.contractor).transfer(t.budget);
        emit TaskPaid(_taskId, t.budget);
    }

    // -------------------- VIEW FUNCTIONS --------------------

    function taskCount() external view returns (uint256) {
        return tasks.length;
    }
}