Milestone-Based Fund Release

What it does:
A smart contract that releases funds in stages based on predefined milestones being completed and approved, ensuring money is only paid when real progress is made.

Why it matters:
Whether for startups, freelancers, grants, or DAOs, this contract prevents misuse of funds and aligns incentives by enforcing pay-for-performance on-chain.

How it works:

  • A funder deposits the full budget into escrow.

  • The project is divided into milestones with specific payout amounts.

  • The project owner submits milestone completion proofs.

  • An approver (or DAO) verifies and approves each milestone.

  • Funds are released incrementally as milestones are approved.

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

/**
 * @title MilestoneBasedFundRelease
 * @author Nam
 * @notice Escrow contract that releases funds per approved milestone
 */
contract MilestoneBasedFundRelease {

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

    enum MilestoneStatus { Pending, Submitted, Approved, Paid }

    struct Milestone {
        string description;   // Description or IPFS hash
        uint256 amount;       // Payout amount
        MilestoneStatus status;
    }

    Milestone[] public milestones;

    address public funder;
    address public recipient;
    address public approver;

    uint256 public totalBudget;
    uint256 public totalPaid;

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

    event Funded(uint256 amount);
    event MilestoneSubmitted(uint256 indexed index);
    event MilestoneApproved(uint256 indexed index);
    event MilestonePaid(uint256 indexed index, uint256 amount);

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

    modifier onlyFunder() {
        require(msg.sender == funder, "Not funder");
        _;
    }

    modifier onlyRecipient() {
        require(msg.sender == recipient, "Not recipient");
        _;
    }

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

    // -------------------- CONSTRUCTOR --------------------

    constructor(
        address _recipient,
        address _approver
    ) {
        require(_recipient != address(0), "Invalid recipient");
        require(_approver != address(0), "Invalid approver");

        funder = msg.sender;
        recipient = _recipient;
        approver = _approver;
    }

    // -------------------- FUNDING --------------------

    /**
     * @notice Fund the escrow contract
     */
    function fund() external payable onlyFunder {
        require(msg.value > 0, "Zero funding");
        totalBudget += msg.value;
        emit Funded(msg.value);
    }

    // -------------------- MILESTONE SETUP --------------------

    /**
     * @notice Add milestones before work begins
     */
    function addMilestone(
        string calldata _description,
        uint256 _amount
    ) external onlyFunder {
        require(_amount > 0, "Invalid amount");
        require(
            totalPaid + _amount = m.amount,
            "Insufficient balance"
        );

        m.status = MilestoneStatus.Paid;
        totalPaid += m.amount;

        payable(recipient).transfer(m.amount);
        emit MilestonePaid(_index, m.amount);
    }

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

    /**
     * @notice Total milestones count
     */
    function milestoneCount() external view returns (uint256) {
        return milestones.length;
    }
}