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