Utility Bill Sharing Contract

What it does:
Automatically splits and tracks shared utility bills (electricity, water, internet, gas) among multiple tenants and enforces fair, transparent payments.

Why it matters:
Eliminates disputes, late payments, and manual calculations by enforcing clear cost-sharing rules that everyone can verify on-chain.

How it works:

  • A utility bill group is created for a property or household

  • Participants and their payment shares are defined upfront

  • Monthly utility costs are submitted by the landlord or admin

  • Each participant’s payable amount is calculated automatically

  • Tenants pay their portion directly to the contract

  • Unpaid balances are transparently visible to all members

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

/**
 * @title UtilityBillSharing
 * @author Nam
 * @notice Splits and manages shared utility bill payments on-chain
 */
contract UtilityBillSharing {

    // -------------------- ROLES --------------------

    address public admin;

    // -------------------- PARTICIPANTS --------------------

    address[] public participants;
    mapping(address => uint256) public shares; // percentage-based (sum = 100)

    // -------------------- BILL DATA --------------------

    uint256 public currentBillAmount;
    mapping(address => bool) public hasPaid;
    bool public billActive;

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

    event ParticipantAdded(address indexed participant, uint256 share);
    event BillIssued(uint256 amount);
    event PaymentReceived(address indexed payer, uint256 amount);
    event BillSettled();

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

    modifier onlyAdmin() {
        require(msg.sender == admin, "Not admin");
        _;
    }

    modifier onlyParticipant() {
        require(shares[msg.sender] > 0, "Not participant");
        _;
    }

    modifier billOpen() {
        require(billActive, "No active bill");
        _;
    }

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

    constructor(address[] memory _participants, uint256[] memory _shares) {
        require(_participants.length == _shares.length, "Length mismatch");
        require(_participants.length > 0, "No participants");

        admin = msg.sender;

        uint256 totalShare;
        for (uint256 i = 0; i  0, "Invalid share");

            participants.push(_participants[i]);
            shares[_participants[i]] = _shares[i];
            totalShare += _shares[i];

            emit ParticipantAdded(_participants[i], _shares[i]);
        }

        require(totalShare == 100, "Total share must be 100");
    }

    // -------------------- BILL LOGIC --------------------

    /**
     * @notice Issue a new utility bill
     */
    function issueBill(uint256 _amount) external onlyAdmin {
        require(!billActive, "Previous bill active");
        require(_amount > 0, "Invalid bill amount");

        currentBillAmount = _amount;
        billActive = true;

        for (uint256 i = 0; i < participants.length; i++) {
            hasPaid[participants[i]] = false;
        }

        emit BillIssued(_amount);
    }

    /**
     * @notice Pay individual share of the utility bill
     */
    function payBill() external payable onlyParticipant billOpen {
        require(!hasPaid[msg.sender], "Already paid");

        uint256 requiredPayment =
            (currentBillAmount * shares[msg.sender]) / 100;

        require(msg.value == requiredPayment, "Incorrect payment");

        hasPaid[msg.sender] = true;
        emit PaymentReceived(msg.sender, msg.value);

        if (address(this).balance == currentBillAmount) {
            billActive = false;
            payable(admin).transfer(currentBillAmount);
            emit BillSettled();
        }
    }

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

    /**
     * @notice Check how much a participant owes
     */
    function amountDue(address _participant)
        external
        view
        returns (uint256)
    {
        if (!billActive || hasPaid[_participant]) {
            return 0;
        }
        return (currentBillAmount * shares[_participant]) / 100;
    }
}