Fractional Real Estate Investment

What it does:
Allows multiple investors to collectively invest in a real estate property by owning fractional shares represented as on-chain tokens.

Why it matters:
Real estate becomes accessible, liquid, and transparent—no need to buy an entire property or trust intermediaries.

How it works:

  • A property is tokenized into fixed total shares.

  • Investors buy shares by depositing ETH.

  • Rental income or sale proceeds are distributed proportionally.

  • Ownership, balances, and payouts are fully on-chain.

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

/**
 * @title FractionalRealEstateInvestment
 * @author Nam
 * @notice Tokenized fractional ownership of a real estate property
 */
contract FractionalRealEstateInvestment {

    // -------------------- TOKEN METADATA --------------------

    string public name = "Fractional Property Share";
    string public symbol = "FPS";
    uint8 public decimals = 18;

    uint256 public totalSupply;
    uint256 public immutable sharePrice; // price per share in wei

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

    address public owner;

    // -------------------- STATE --------------------

    mapping(address => uint256) public balanceOf;
    mapping(address => mapping(address => uint256)) public allowance;

    uint256 public totalRentalIncome;
    uint256 public totalDistributed;

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

    event Transfer(address indexed from, address indexed to, uint256 value);
    event Approval(address indexed owner, address indexed spender, uint256 value);
    event SharesPurchased(address indexed buyer, uint256 shares);
    event RentalIncomeAdded(uint256 amount);
    event IncomeClaimed(address indexed investor, uint256 amount);

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

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

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

    constructor(
        uint256 _totalShares,
        uint256 _sharePrice
    ) {
        require(_totalShares > 0, "Invalid shares");
        require(_sharePrice > 0, "Invalid price");

        owner = msg.sender;
        totalSupply = _totalShares * 10 ** decimals;
        sharePrice = _sharePrice;

        // Owner initially holds all shares
        balanceOf[owner] = totalSupply;
        emit Transfer(address(0), owner, totalSupply);
    }

    // -------------------- ERC20 CORE --------------------

    function transfer(address to, uint256 amount) external returns (bool) {
        _transfer(msg.sender, to, amount);
        return true;
    }

    function approve(address spender, uint256 amount) external returns (bool) {
        allowance[msg.sender][spender] = amount;
        emit Approval(msg.sender, spender, amount);
        return true;
    }

    function transferFrom(
        address from,
        address to,
        uint256 amount
    ) external returns (bool) {
        require(allowance[from][msg.sender] >= amount, "Allowance exceeded");
        allowance[from][msg.sender] -= amount;
        _transfer(from, to, amount);
        return true;
    }

    function _transfer(address from, address to, uint256 amount) internal {
        require(to != address(0), "Invalid recipient");
        require(balanceOf[from] >= amount, "Insufficient balance");

        balanceOf[from] -= amount;
        balanceOf[to] += amount;

        emit Transfer(from, to, amount);
    }

    // -------------------- INVESTMENT --------------------

    /**
     * @notice Buy property shares with ETH
     */
    function buyShares(uint256 shares) external payable {
        uint256 amount = shares * 10 ** decimals;
        uint256 cost = shares * sharePrice;

        require(msg.value == cost, "Incorrect ETH amount");
        require(balanceOf[owner] >= amount, "Not enough shares");

        _transfer(owner, msg.sender, amount);
        emit SharesPurchased(msg.sender, shares);
    }

    // -------------------- RENTAL INCOME --------------------

    /**
     * @notice Add rental income to the pool
     */
    function addRentalIncome() external payable onlyOwner {
        require(msg.value > 0, "Zero income");
        totalRentalIncome += msg.value;
        emit RentalIncomeAdded(msg.value);
    }

    /**
     * @notice Claim proportional rental income
     */
    function claimIncome() external {
        uint256 investorBalance = balanceOf[msg.sender];
        require(investorBalance > 0, "No shares owned");

        uint256 entitled = (totalRentalIncome * investorBalance) / totalSupply;
        uint256 alreadyClaimed = (totalDistributed * investorBalance) / totalSupply;
        uint256 claimable = entitled - alreadyClaimed;

        require(claimable > 0, "Nothing to claim");

        totalDistributed += claimable;
        payable(msg.sender).transfer(claimable);

        emit IncomeClaimed(msg.sender, claimable);
    }

    // -------------------- ADMIN --------------------

    /**
     * @notice Withdraw remaining ETH (e.g., property sale)
     */
    function withdrawAll() external onlyOwner {
        payable(owner).transfer(address(this).balance);
    }
}