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